Skip to content

Commit

Permalink
Merge pull request #9 from beforeyoubid/task/defects
Browse files Browse the repository at this point in the history
Task/defects
  • Loading branch information
alice-byb authored Apr 18, 2023
2 parents 91b5f2c + 5d96ea3 commit 64cb219
Show file tree
Hide file tree
Showing 6 changed files with 162 additions and 40 deletions.
21 changes: 21 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,38 @@ module.exports = {
es6: true,
node: true,
},
rules: {
'require-jsdoc': [
'error',
{
require: {
FunctionDeclaration: true,
MethodDefinition: true,
ClassDeclaration: true,
FunctionExpression: true,
ArrowFunctionExpression: true,
},
},
],
},
overrides: [
{
files: ['*.js'],
rules: {
'@typescript-eslint/no-var-requires': 0,
},
},
{
files: ['**.test.ts', '**/__tests/**.*'],
rules: {
'require-jsdoc': 0,
},
},
{
files: ['module_test/**/*'],
rules: {
'import/no-unresolved': 0,
'require-jsdoc': 0,
},
},
{
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "serverless-esbuild-layers",
"version": "0.1.3",
"version": "0.1.4",
"author": {
"name": "Bailey Sheather",
"email": "[email protected]",
Expand Down
65 changes: 64 additions & 1 deletion src/__tests__/utils.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import Serverless from 'serverless';
import { notEmpty, isFunctionDefinition } from '../utils';
import { notEmpty, isFunctionDefinition, fixModuleName, findEntriesSpecified, globPromise } from '../utils';

describe('notEmpty', () => {
it('should false for null', () => {
Expand All @@ -24,3 +24,66 @@ describe('isFunctionDefinition', () => {
expect(isFunctionDefinition({ handler: '' } as unknown as Serverless.FunctionDefinitionHandler)).toEqual(true);
});
});

describe('fixModuleName', () => {
const modules = {
'@beforeyoubid/ui-lib': '@beforeyoubid/ui-lib',
'@beforeyoubid/ui-lib/something': '@beforeyoubid/ui-lib',
'uuid/v4': 'uuid',
};
for (const [lib, expectedLib] of Object.entries(modules)) {
it(`${lib} should output as ${expectedLib}`, () => {
expect(fixModuleName(lib)).toEqual(expectedLib);
});
}
});

describe('globPromise', () => {
it('will match .default', async () => {
const res = await globPromise('examples/example-layer-service/hello.default');
expect(res).toEqual(['examples/example-layer-service/hello.js']);
});
it('will match an export key', async () => {
const res = await globPromise('examples/example-layer-service/hello.someExport');
expect(res).toEqual(['examples/example-layer-service/hello.js']);
});
it('will match file type', async () => {
const res = await globPromise('examples/example-layer-service/hello.js');
expect(res).toEqual(['examples/example-layer-service/hello.js']);
});
describe('will match files with no folder', () => {
it('will match .default', async () => {
const res = await globPromise('.eslintrc.default');
expect(res).toEqual(['.eslintrc.js']);
});
it('will match an export key', async () => {
const res = await globPromise('.eslintrc.someExport');
expect(res).toEqual(['.eslintrc.js']);
});
it('will match file type', async () => {
const res = await globPromise('.eslintrc.default');
expect(res).toEqual(['.eslintrc.js']);
});
});
});

describe('findEntriesSpecified', () => {
it('if given a string it will handle it', async () => {
const key = 'examples/example-layer-service/hello.default';
const res = await findEntriesSpecified(key);
expect(res).toEqual(['examples/example-layer-service/hello.js']);
});
it('if given an array it will handle it', async () => {
const key = 'examples/example-layer-service/hello.default';
const res = await findEntriesSpecified([key]);
expect(res).toEqual(['examples/example-layer-service/hello.js']);
});
it('if given an empty array it will return nothing', async () => {
const res = await findEntriesSpecified([]);
expect(res).toEqual([]);
});
it('if given a bad variable it will return an empty array', async () => {
const res = await findEntriesSpecified(123 as unknown as string);
expect(res).toEqual([]);
});
});
50 changes: 30 additions & 20 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ class EsbuildLayersPlugin implements Plugin {
const deps: Record<string, string> = {};
for (const [name, version] of Object.entries(depsWithVersion)) {
if (!version) {
this.log.warning(`Skipping ${name} as it is not defined in the package.json folder`);
this.log.verbose(`Skipping ${name} as it is not defined in the package.json folder`);
continue;
}
deps[name] = version;
Expand All @@ -158,32 +158,38 @@ class EsbuildLayersPlugin implements Plugin {
const depPackageJson = JSON.parse(depPackageJsonText) as PackageJsonFile;
const { peerDependencies, peerDependenciesMeta } = depPackageJson;
for (const [peerDepName] of Object.entries(peerDependencies ?? {})) {
const optional = peerDependenciesMeta?.[peerDepName]?.optional ?? false;
if (optional) {
this.log.warning(`Skipping peer dep ${peerDepName} of package ${name} as it is optional`);
continue;
}
try {
const optional = peerDependenciesMeta?.[peerDepName]?.optional ?? false;
if (optional) {
this.log.verbose(`Skipping peer dep ${peerDepName} of package ${name} as it is optional`);
continue;
}

if (deps[peerDepName]) {
this.log.warning(
`Skipping peer dep ${peerDepName} of package ${name} as it is already added at the root level`
if (deps[peerDepName]) {
this.log.verbose(
`Skipping peer dep ${peerDepName} of package ${name} as it is already added at the root level`
);
continue;
}
const peerPackageJsonText = await fs.promises.readFile(
path.join(basePath, 'node_modules', ...peerDepName.split('/'), 'package.json'),
{
encoding: 'utf-8',
}
);
continue;
}
const peerPackageJsonText = await fs.promises.readFile(
path.join(basePath, 'node_modules', ...peerDepName.split('/'), 'package.json'),
{
encoding: 'utf-8',
const peerPackageJson = JSON.parse(peerPackageJsonText) as PackageJsonFile;
if (!peerPackageJson.version) {
throw new Error(`Submodule ${peerDepName} is missing version in its package.json file`);
}
);
const peerPackageJson = JSON.parse(peerPackageJsonText) as PackageJsonFile;
if (!peerPackageJson.version) {
throw new Error(`Submodule ${peerDepName} is missing version in its package.json file`);
deps[peerDepName] = peerPackageJson.version;
} catch (err) {
this.log.warning(`Unable to add peer dep ${peerDepName} for package ${name} as an error occurred`);
this.log.verbose(err);
}
deps[peerDepName] = peerPackageJson.version;
}
} catch (err) {
this.log.warning(`Unable to check for peer deps for package ${name} as an error occurred`);
this.log.verbose(err);
}
}
return deps;
Expand Down Expand Up @@ -283,6 +289,10 @@ class EsbuildLayersPlugin implements Plugin {
this.log.info(`Cleaned ${filesDeleted.length} files at ${nodeLayerPath}`);
}

/**
* function to transform the layer resources for cloudformation
* @returns the transformed layer resources
*/
transformLayerResources(): TransformedLayerResources {
const layers = getLayers(this.serverless);
const { compiledCloudFormationTemplate: cf } = this.serverless.service.provider;
Expand Down
25 changes: 15 additions & 10 deletions src/logger.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,33 @@
import { Level, LevelName } from './types';
import type Plugin from 'serverless/classes/Plugin';

/** function to log to console */
export function log(...s: unknown[]) {
console.log('[esbuild-layers]', ...s);
}
/** function to error to console */
export function error(...s: unknown[]) {
console.error('[esbuild-layers]', ...s);
}
/** function to warn to console */
export function warn(...s: unknown[]) {
console.warn('[esbuild-layers]', ...s);
}

export function verbose({ level }: { level: LevelName }, ...s: unknown[]) {
Number(Level[level]) >= Level.verbose && log(...s);
}

export function info({ level }: { level: LevelName }, ...s: unknown[]) {
Number(Level[level]) >= Level.info && log(...s);
}
/** function to build a specific levelled-logger */
const buildLogger =
(targetLevel: Level) =>
({ level }: { level: LevelName }, ...s: unknown[]) =>
Number(Level[level]) >= targetLevel && log(...s);

export function debug({ level }: { level: LevelName }, ...s: unknown[]) {
Number(Level[level]) >= Level.debug && log(...s);
}
/** function to log a verbose message console */
export const verbose = buildLogger(Level.verbose);
/** function to log an info message console */
export const info = buildLogger(Level.info);
/** function to log a debug message console */
export const debug = buildLogger(Level.debug);

/** function to create a serverless compatible logger instance */
export const Log = (level: LevelName): Plugin.Logging['log'] => ({
info: info.bind(level),
debug: debug.bind(level),
Expand Down
39 changes: 31 additions & 8 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,25 +34,26 @@ export const isFunctionDefinition = (
* @param pattern the pattern to check
* @returns a string list of all the file matches
*/
const globPromise = async (pattern: string): Promise<string[]> => {
const paths = await glob(pattern.replace(/.default$/, '.@(ts|js)?(x)'), { withFileTypes: true });
export const globPromise = async (pattern: string): Promise<string[]> => {
const paths = await glob(pattern.replace(/\.(?!(?:j|t)sx?$)\w+$/, '.@(ts|js)?(x)'), { withFileTypes: true });
const basePath = pattern.includes('/') ? pattern.replace(/(.+)\/.+$/, (_full, match) => match) : '';
return paths.map(p => path.join(basePath, p.name));
};

/**
*
* @param specifiedEntries
* @returns
* function to resolve serverless entry parts to strings
* @param specifiedEntries the entry keys
* @returns a string list of the resolved entries
*/
async function findEntriesSpecified(specifiedEntries: string | string[]) {
export async function findEntriesSpecified(specifiedEntries: string | string[]) {
let entries = specifiedEntries;
if (typeof specifiedEntries === 'string') {
entries = [specifiedEntries];
}
if (!Array.isArray(entries)) {
return [];
}
if (entries.length === 0) return [];
const allMapped = await Promise.all(entries.map(globPromise));
return allMapped.reduce((arr, list) => arr.concat(list), []);
}
Expand Down Expand Up @@ -131,7 +132,8 @@ export async function getExternalModules(
const entries = await resolvedEntries(serverless, layerRefName);
if (entries.length === 0) return [];
let modules: esbuild.Plugin[] = [];
const pluginFile = serverless.service.custom?.esbuild?.plugins;
const esbuildConfig = serverless.service.custom?.esbuild ?? {};
const pluginFile = esbuildConfig.plugins;
if (pluginFile) {
try {
const resolvedPath = path.resolve(process.cwd(), pluginFile);
Expand All @@ -143,13 +145,14 @@ export async function getExternalModules(
}
}
const result = await esbuild.build({
loader: esbuildConfig?.loader,
entryPoints: entries,
plugins: [nodeExternalsPlugin(), ...modules],
metafile: true,
bundle: true,
platform: 'node',
logLevel: 'silent',
outfile: '.serverless/tmp_build_file',
outdir: '.serverless/tmp_build',
});

const importedModules = Object.values(result.metafile.outputs).map(({ imports }) => imports.map(i => i.path));
Expand All @@ -166,15 +169,35 @@ export async function getExternalModules(
[...importedModules, ...requiredModules]
.reduce((list, listsOfMods) => list.concat(...listsOfMods), [])
.filter(module => !isBuiltinModule(module))
.map(fixModuleName)
);
return Array.from(imports)
.filter(dep => !DEFAULT_AWS_MODULES.includes(dep) && !config.forceExclude.includes(dep))
.concat(config.forceInclude);
}

/**
* function to merge the user config with the default config to create a complete config
* @param userConfig the user's config
* @returns a complete config object
*/
export function compileConfig(userConfig: Partial<Config>): Config {
return {
...DEFAULT_CONFIG,
...userConfig,
};
}

/**
* function to fix a module name
* @param mod the module name
* @returns the fixed module name
*/
export function fixModuleName(mod: string): string {
if (mod.startsWith('@')) {
const [group, packageName] = mod.split('/');
return `${group}/${packageName}`;
}
const [packageName] = mod.split('/');
return packageName;
}

0 comments on commit 64cb219

Please sign in to comment.