Skip to content

Commit

Permalink
Feat: Serverless v3 logs (floydspace#282)
Browse files Browse the repository at this point in the history
* chore: bump @types/serverless to 3.0.0

* feat(logger): add new backward-compatible log interface

* feat(logger): use new backward-compatible logger
  • Loading branch information
fargito authored Feb 26, 2022
1 parent c30bf53 commit e9b1196
Show file tree
Hide file tree
Showing 9 changed files with 82 additions and 54 deletions.
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
"@types/node": "^12.12.38",
"@types/ramda": "0.27.40",
"@types/semver": "^7.3.8",
"@types/serverless": "^1.78.25",
"@types/serverless": "^3.0.0",
"@typescript-eslint/eslint-plugin": "^4.2.0",
"@typescript-eslint/parser": "^4.2.0",
"esbuild": "^0.14.2",
Expand Down
14 changes: 14 additions & 0 deletions src/helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import path from 'path';
import os from 'os';
import { uniq } from 'ramda';
import Serverless from 'serverless';
import ServerlessPlugin from 'serverless/classes/Plugin';
import matchAll from 'string.prototype.matchall';
import { DependencyMap } from './types';

Expand Down Expand Up @@ -145,3 +146,16 @@ export const providerRuntimeMatcher = Object.freeze({
'nodejs12.x': 'node12',
},
});

export const buildServerlessV3LoggerFromLegacyLogger = (
legacyLogger: (text: string) => void,
verbose?: boolean
): ServerlessPlugin.Logging['log'] => ({
error: legacyLogger,
warning: legacyLogger,
notice: legacyLogger,
info: legacyLogger,
debug: legacyLogger,
verbose: verbose ? legacyLogger : () => null,
success: legacyLogger,
});
30 changes: 19 additions & 11 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@ import Serverless from 'serverless';
import ServerlessPlugin from 'serverless/classes/Plugin';
import chokidar from 'chokidar';

import { extractFileNames, providerRuntimeMatcher } from './helper';
import {
buildServerlessV3LoggerFromLegacyLogger,
extractFileNames,
providerRuntimeMatcher,
} from './helper';
import { packExternalModules } from './pack-externals';
import { pack } from './pack';
import { preOffline } from './pre-offline';
Expand Down Expand Up @@ -44,6 +48,7 @@ class EsbuildServerlessPlugin implements ServerlessPlugin {
serviceDirPath: string;
workDirPath: string;
buildDirPath: string;
log: ServerlessPlugin.Logging['log'];

serverless: Serverless;
options: OptionsExtended;
Expand All @@ -54,9 +59,16 @@ class EsbuildServerlessPlugin implements ServerlessPlugin {
preOffline: () => Promise<void>;
preLocal: () => void;

constructor(serverless: Serverless, options: OptionsExtended) {
constructor(
serverless: Serverless,
options: OptionsExtended,
logging?: ServerlessPlugin.Logging
) {
this.serverless = serverless;
this.options = options;
this.log =
logging?.log ||
buildServerlessV3LoggerFromLegacyLogger(this.serverless.cli.log, this.options.verbose);
this.packExternalModules = packExternalModules.bind(this);
this.pack = pack.bind(this);
this.preOffline = preOffline.bind(this);
Expand Down Expand Up @@ -202,10 +214,8 @@ class EsbuildServerlessPlugin implements ServerlessPlugin {

chokidar.watch(this.buildOptions.watch.pattern, options).on('all', () =>
this.bundle(true)
.then(() => this.serverless.cli.log('Watching files for changes...'))
.catch(() =>
this.serverless.cli.log('Bundle error, waiting for a file change to reload...')
)
.then(() => this.log.verbose('Watching files for changes...'))
.catch(() => this.log.error('Bundle error, waiting for a file change to reload...'))
);
}

Expand Down Expand Up @@ -241,7 +251,7 @@ class EsbuildServerlessPlugin implements ServerlessPlugin {

async bundle(incremental = false): Promise<BuildResult[]> {
this.prepare();
this.serverless.cli.log(`Compiling to ${this.buildOptions.target} bundle with esbuild...`);
this.log.verbose(`Compiling to ${this.buildOptions.target} bundle with esbuild...`);
if (this.buildOptions.disableIncremental === true) {
incremental = false;
}
Expand Down Expand Up @@ -295,13 +305,11 @@ class EsbuildServerlessPlugin implements ServerlessPlugin {

return { result, bundlePath, func, functionAlias };
};
this.serverless.cli.log(
`Compiling with concurrency: ${this.buildOptions.concurrency ?? 'Infinity'}`
);
this.log.verbose(`Compiling with concurrency: ${this.buildOptions.concurrency ?? 'Infinity'}`);
this.buildResults = await pMap(this.rootFileNames, bundleMapper, {
concurrency: this.buildOptions.concurrency,
});
this.serverless.cli.log('Compiling completed.');
this.log.verbose('Compiling completed.');
return this.buildResults.map((r) => r.result);
}

Expand Down
58 changes: 26 additions & 32 deletions src/pack-externals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,10 +90,9 @@ function getProdModules(
!packageJson.dependencies[externalModule.external] &&
!packageJson.devDependencies[externalModule.external]
) {
this.options.verbose &&
this.serverless.cli.log(
`INFO: Runtime dependency '${externalModule.external}' not found in dependencies or devDependencies. It has been excluded automatically.`
);
this.log.verbose(
`INFO: Runtime dependency '${externalModule.external}' not found in dependencies or devDependencies. It has been excluded automatically.`
);

return;
}
Expand All @@ -109,18 +108,17 @@ function getProdModules(

if (!includes(externalModule.external, ignoredDevDependencies)) {
// Runtime dependency found in devDependencies but not forcefully excluded
this.serverless.cli.log(
this.log.error(
`ERROR: Runtime dependency '${externalModule.external}' found in devDependencies.`
);
throw new this.serverless.classes.Error(
`Serverless-webpack dependency error: ${externalModule.external}.`
);
}

this.options.verbose &&
this.serverless.cli.log(
`INFO: Runtime dependency '${externalModule.external}' found in devDependencies. It has been excluded automatically.`
);
this.log.verbose(
`INFO: Runtime dependency '${externalModule.external}' found in devDependencies. It has been excluded automatically.`
);

return;
}
Expand Down Expand Up @@ -167,10 +165,9 @@ function getProdModules(
);

if (!isEmpty(peerDependenciesWithoutOptionals)) {
this.options.verbose &&
this.serverless.cli.log(
`Adding explicit non-optionals peers for dependency ${externalModule.external}`
);
this.log.verbose(
`Adding explicit non-optionals peers for dependency ${externalModule.external}`
);
const peerModules = getProdModules.call(
this,
compose(
Expand All @@ -183,7 +180,7 @@ function getProdModules(
Array.prototype.push.apply(prodModules, peerModules);
}
} catch (e) {
this.serverless.cli.log(
this.log.warning(
`WARNING: Could not check for peer dependencies of ${externalModule.external}`
);
}
Expand Down Expand Up @@ -270,12 +267,11 @@ export async function packExternalModules(this: EsbuildServerlessPlugin) {
const packageSections = pick(sectionNames, packageJson);

if (!isEmpty(packageSections)) {
this.options.verbose &&
this.serverless.cli.log(`Using package.json sections ${join(', ', keys(packageSections))}`);
this.log.verbose(`Using package.json sections ${join(', ', keys(packageSections))}`);
}

// Get first level dependency graph
this.options.verbose && this.serverless.cli.log(`Fetch dependency graph from ${packageJson}`);
this.log.verbose(`Fetch dependency graph from ${packageJson}`);

// (1) Generate dependency composition
const externalModules = map((external) => ({ external }), externals);
Expand All @@ -285,7 +281,7 @@ export async function packExternalModules(this: EsbuildServerlessPlugin) {

if (isEmpty(compositeModules)) {
// The compiled code does not reference any external modules at all
this.serverless.cli.log('No external modules needed');
this.log.warning('No external modules needed');
return;
}

Expand Down Expand Up @@ -315,7 +311,7 @@ export async function packExternalModules(this: EsbuildServerlessPlugin) {
const packageLockPath = path.join(path.dirname(packageJsonPath), packager.lockfileName);
const exists = await fse.pathExists(packageLockPath);
if (exists) {
this.serverless.cli.log('Package lock found - Using locked versions');
this.log.verbose('Package lock found - Using locked versions');
try {
let packageLockFile = this.serverless.utils.readFileSync(packageLockPath);
packageLockFile = packager.rebaseLockfile(relativePath, packageLockFile);
Expand All @@ -328,7 +324,7 @@ export async function packExternalModules(this: EsbuildServerlessPlugin) {
packageLockFile as string
);
} catch (err) {
this.serverless.cli.log(`Warning: Could not read lock file: ${err.message}`);
this.log.warning(`Warning: Could not read lock file: ${err.message}`);
}
}

Expand All @@ -339,28 +335,26 @@ export async function packExternalModules(this: EsbuildServerlessPlugin) {
}

const start = Date.now();
this.serverless.cli.log('Packing external modules: ' + compositeModules.join(', '));
this.log.verbose('Packing external modules: ' + compositeModules.join(', '));
const installExtraArgs = this.buildOptions.installExtraArgs;
await packager.install(compositeModulePath, installExtraArgs, exists);
this.options.verbose && this.serverless.cli.log(`Package took [${Date.now() - start} ms]`);
this.log.verbose(`Package took [${Date.now() - start} ms]`);

// Prune extraneous packages - removes not needed ones
const startPrune = Date.now();
await packager.prune(compositeModulePath);
this.options.verbose &&
this.serverless.cli.log(`Prune: ${compositeModulePath} [${Date.now() - startPrune} ms]`);

this.log.verbose(`Prune: ${compositeModulePath} [${Date.now() - startPrune} ms]`);

// Run packager scripts
if (Object.keys(packagerScripts).length > 0) {
const startScripts = Date.now();
await packager.runScripts(this.buildDirPath, Object.keys(packagerScripts));
this.options.verbose &&
this.serverless.cli.log(
`Packager scripts took [${
Date.now() - startScripts
} ms].\nExecuted scripts: ${Object.values(packagerScripts).map(
(script) => `\n ${script}`
)}`
);

this.log.verbose(
`Packager scripts took [${Date.now() - startScripts} ms].\nExecuted scripts: ${Object.values(
packagerScripts
).map((script) => `\n ${script}`)}`
);
}
}
8 changes: 3 additions & 5 deletions src/pack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,7 @@ function setFunctionArtifactPath(this: EsbuildServerlessPlugin, func, artifactPa
if (semver.lt(version, '1.18.0')) {
func.artifact = artifactPath;
func.package = Object.assign({}, func.package, { disable: true });
this.serverless.cli.log(
`${func.name} is packaged by the esbuild plugin. Ignore messages from SLS.`
);
this.log.verbose(`${func.name} is packaged by the esbuild plugin. Ignore messages from SLS.`);
} else {
func.package = {
artifact: artifactPath,
Expand Down Expand Up @@ -128,7 +126,7 @@ export async function pack(this: EsbuildServerlessPlugin) {
await zip(artifactPath, filesPathList, this.buildOptions.nativeZip);
const { size } = fs.statSync(artifactPath);

this.serverless.cli.log(
this.log.verbose(
`Zip service ${this.serverless.service.service} - ${humanSize(size)} [${
Date.now() - startZip
} ms]`
Expand Down Expand Up @@ -208,7 +206,7 @@ export async function pack(this: EsbuildServerlessPlugin) {

const { size } = fs.statSync(artifactPath);

this.serverless.cli.log(
this.log.verbose(
`Zip function: ${functionAlias} - ${humanSize(size)} [${Date.now() - startZip} ms]`
);

Expand Down
2 changes: 1 addition & 1 deletion src/packagers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ const registeredPackagers = {
export function get(packagerId: string): Promise<Packager> {
if (!(packagerId in registeredPackagers)) {
const message = `Could not find packager '${packagerId}'`;
this.serverless.cli.log(`ERROR: ${message}`);
this.log.error(`ERROR: ${message}`);
throw new this.serverless.classes.Error(message);
}
return registeredPackagers[packagerId];
Expand Down
5 changes: 5 additions & 0 deletions src/tests/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,16 @@ const mockServerlessConfig = (serviceOverride?: Partial<Service>): Serverless =>
...serviceOverride,
} as Service;

const mockCli = {
log: jest.fn(),
};

return {
service,
config: {
servicePath: '/workDir',
},
cli: mockCli,
} as Partial<Serverless> as Serverless;
};

Expand Down
9 changes: 9 additions & 0 deletions src/tests/pack.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,15 @@ describe('pack', () => {
workDirPath: '/workdir/serverless-esbuild/examples/individually/.esbuild/',
serviceDirPath: '/workdir/serverless-esbuild/examples/individually',
buildResults,
log: {
error: jest.fn(),
warning: jest.fn(),
notice: jest.fn(),
info: jest.fn(),
debug: jest.fn(),
verbose: jest.fn(),
success: jest.fn(),
},
};

await pack.call(esbuildPlugin);
Expand Down

0 comments on commit e9b1196

Please sign in to comment.