Skip to content

Commit

Permalink
fix: correct package individually patterns functionality (#249)
Browse files Browse the repository at this point in the history
* fix: correct package individually patterns functionality

* refactor: move folder names to separate file

* chore: add unit test coverage

* refactor: change folders to constants

* refactor: add only prefix

* chore: add comment

* feat: use functionAlias as zip name

* refactor: remove unused type

* refactor: use Object.values

* refactor: fix test case typo

* refactor: sort object keys
  • Loading branch information
samchungy authored Jan 4, 2022
1 parent 6d7db0c commit 09f08c7
Show file tree
Hide file tree
Showing 5 changed files with 132 additions and 49 deletions.
3 changes: 2 additions & 1 deletion jest.config.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
const config = {
preset: 'ts-jest',
verbose: true,
transform: {}
transform: {},
testPathIgnorePatterns: ['dist'],
};

module.exports = config;
4 changes: 4 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export const SERVERLESS_FOLDER = '.serverless';
export const BUILD_FOLDER = '.build';
export const WORK_FOLDER = '.esbuild';
export const ONLY_PREFIX = '__only_';
48 changes: 20 additions & 28 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import * as pMap from 'p-map';
import { concat, always, memoizeWith, mergeRight } from 'ramda';
import * as Serverless from 'serverless';
import * as ServerlessPlugin from 'serverless/classes/Plugin';
import * as Service from 'serverless/classes/Service';
import * as chokidar from 'chokidar';

import { extractFileNames, providerRuntimeMatcher } from './helper';
Expand All @@ -15,10 +14,7 @@ import { pack } from './pack';
import { preOffline } from './pre-offline';
import { preLocal } from './pre-local';
import { trimExtension } from './utils';

export const SERVERLESS_FOLDER = '.serverless';
export const BUILD_FOLDER = '.build';
export const WORK_FOLDER = '.esbuild';
import { BUILD_FOLDER, ONLY_PREFIX, SERVERLESS_FOLDER, WORK_FOLDER } from './constants';

type Plugins = Plugin[];
type ReturnPluginsFn = (sls: Serverless) => Plugins;
Expand Down Expand Up @@ -50,6 +46,13 @@ export interface Configuration extends Omit<BuildOptions, 'nativeZip' | 'watch'
disableIncremental?: boolean;
}

export interface FunctionBuildResult {
result: BuildResult;
bundlePath: string;
func: Serverless.FunctionDefinitionHandler;
functionAlias: string;
}

const DEFAULT_BUILD_OPTIONS: Partial<Configuration> = {
bundle: true,
target: 'node10',
Expand All @@ -74,12 +77,7 @@ export class EsbuildServerlessPlugin implements ServerlessPlugin {
serverless: Serverless;
options: OptionsExtended;
hooks: ServerlessPlugin.Hooks;
buildResults: {
result: BuildResult;
bundlePath: string;
func: Serverless.FunctionDefinitionHandler;
functionAlias: string;
}[];
buildResults: FunctionBuildResult[];
packExternalModules: () => Promise<void>;
pack: () => Promise<void>;
preOffline: () => Promise<void>;
Expand Down Expand Up @@ -170,21 +168,17 @@ export class EsbuildServerlessPlugin implements ServerlessPlugin {
}

get functions(): Record<string, Serverless.FunctionDefinitionHandler> {
let functions: Service['functions'];
if (this.options.function) {
functions = {
[this.options.function]: this.serverless.service.getFunction(this.options.function),
};
} else {
functions = this.serverless.service.functions;
}
const functions = this.options.function
? {
[this.options.function]: this.serverless.service.getFunction(this.options.function),
}
: this.serverless.service.functions;

// ignore all functions with a different runtime than nodejs:
const nodeFunctions: Record<string, Serverless.FunctionDefinitionHandler> = {};
for (const funcName in functions) {
const func = functions[funcName] as Serverless.FunctionDefinitionHandler;
if (this.isFunctionDefinitionHandler(func) && this.isNodeFunction(func)) {
nodeFunctions[funcName] = func;
for (const [functionAlias, fn] of Object.entries(functions)) {
if (this.isFunctionDefinitionHandler(fn) && this.isNodeFunction(fn)) {
nodeFunctions[functionAlias] = fn;
}
}
return nodeFunctions;
Expand Down Expand Up @@ -260,8 +254,7 @@ export class EsbuildServerlessPlugin implements ServerlessPlugin {
],
};

for (const fnName in this.functions) {
const fn = this.serverless.service.getFunction(fnName);
for (const fn of Object.values(this.functions)) {
fn.package = {
...(fn.package || {}),
patterns: [
Expand Down Expand Up @@ -365,15 +358,14 @@ export class EsbuildServerlessPlugin implements ServerlessPlugin {
}

// include any "extras" from the individual function "patterns" section
for (const fnName in this.functions) {
const fn = this.serverless.service.getFunction(fnName);
for (const [functionAlias, fn] of Object.entries(this.functions)) {
if (fn.package.patterns.length === 0) {
continue;
}
const files = await globby(fn.package.patterns);
for (const filename of files) {
const destFileName = path.resolve(
path.join(this.buildDirPath, `__only_${fn.name}`, filename)
path.join(this.buildDirPath, `${ONLY_PREFIX}${functionAlias}`, filename)
);
const dirname = path.dirname(destFileName);

Expand Down
26 changes: 12 additions & 14 deletions src/pack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ import {
without,
} from 'ramda';
import * as semver from 'semver';
import { EsbuildServerlessPlugin, SERVERLESS_FOLDER } from '.';
import { EsbuildServerlessPlugin } from '.';
import { ONLY_PREFIX, SERVERLESS_FOLDER } from './constants';
import { doSharePath, flatDep, getDepsFromBundle } from './helper';
import * as Packagers from './packagers';
import { IFiles } from './types';
Expand All @@ -40,15 +41,15 @@ const excludedFilesDefault = ['package-lock.json', 'pnpm-lock.yaml', 'yarn.lock'

export const filterFilesForZipPackage = ({
files,
fnNameWithoutStage,
functionAlias,
includedFiles,
excludedFiles,
hasExternals,
isGoogleProvider,
depWhiteList,
}: {
files: IFiles;
fnNameWithoutStage: string;
functionAlias: string;
includedFiles: string[];
excludedFiles: string[];
hasExternals: boolean;
Expand All @@ -65,7 +66,10 @@ export const filterFilesForZipPackage = ({
if (excludedFiles.find((p) => localPath.startsWith(`${p}.`))) return false;

// exclude files that belong to individual functions
if (localPath.startsWith('__only_') && !localPath.startsWith(`__only_${fnNameWithoutStage}/`))
if (
localPath.startsWith(ONLY_PREFIX) &&
!localPath.startsWith(`${ONLY_PREFIX}${functionAlias}/`)
)
return false;

// exclude non whitelisted dependencies
Expand Down Expand Up @@ -160,10 +164,6 @@ export async function pack(this: EsbuildServerlessPlugin) {
// package each function
await Promise.all(
buildResults.map(async ({ func, functionAlias, bundlePath }) => {
const name = `${this.serverless.service.getServiceName()}-${
this.serverless.service.provider.stage
}-${functionAlias}`;

const excludedFiles = bundlePathList
.filter((p) => !bundlePath.startsWith(p))
.map(trimExtension);
Expand All @@ -181,15 +181,13 @@ export async function pack(this: EsbuildServerlessPlugin) {
depWhiteList = flatDep(packagerDependenciesList.dependencies, bundleExternals);
}

const zipName = `${name}.zip`;
const zipName = `${functionAlias}.zip`;
const artifactPath = path.join(this.workDirPath, SERVERLESS_FOLDER, zipName);

const fnNameWithoutStage = `${this.serverless.service.getServiceName()}-${functionAlias}`; // this needs to match the directory name created in copyExtras function from index.ts

// filter files
const filesPathList = filterFilesForZipPackage({
files,
fnNameWithoutStage,
functionAlias,
includedFiles,
excludedFiles,
hasExternals,
Expand All @@ -198,7 +196,7 @@ export async function pack(this: EsbuildServerlessPlugin) {
})
// remove prefix from individual function extra files
.map(({ localPath, ...rest }) => ({
localPath: localPath.replace(`__only_${fnNameWithoutStage}/`, ''),
localPath: localPath.replace(`${ONLY_PREFIX}${functionAlias}/`, ''),
...rest,
}));

Expand All @@ -208,7 +206,7 @@ export async function pack(this: EsbuildServerlessPlugin) {
const { size } = fs.statSync(artifactPath);

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

// defined present zip as output artifact
Expand Down
100 changes: 94 additions & 6 deletions src/tests/pack.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,17 @@
import { filterFilesForZipPackage } from '../pack';
import { FunctionBuildResult } from '..';
import { filterFilesForZipPackage, pack } from '../pack';
import * as utils from '../utils';

import * as fs from 'fs-extra';
import * as globby from 'globby';
import { mocked } from 'ts-jest/utils';

jest.mock('globby');
jest.mock('fs-extra');

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

describe('filterFilesForZipPackage', () => {
it('should filter out files for another zip package', () => {
Expand All @@ -13,13 +26,13 @@ describe('filterFilesForZipPackage', () => {
},
{
localPath:
'__only_service-fnName/bin/imagemagick/include/ImageMagick/magick/method-attribute.h',
'__only_fnAlias/bin/imagemagick/include/ImageMagick/magick/method-attribute.h',
rootPath:
'/home/capaj/repos/google/search/.esbuild/.build/__only_service-fnName/bin/imagemagick/include/ImageMagick/magick/method-attribute.h',
'/home/capaj/repos/google/search/.esbuild/.build/__only_fnAlias/bin/imagemagick/include/ImageMagick/magick/method-attribute.h',
},
],
depWhiteList: [],
fnNameWithoutStage: 'service-fnName',
functionAlias: 'fnAlias',
isGoogleProvider: false,
hasExternals: false,
includedFiles: [],
Expand All @@ -28,10 +41,85 @@ describe('filterFilesForZipPackage', () => {
).toMatchInlineSnapshot(`
Array [
Object {
"localPath": "__only_service-fnName/bin/imagemagick/include/ImageMagick/magick/method-attribute.h",
"rootPath": "/home/capaj/repos/google/search/.esbuild/.build/__only_service-fnName/bin/imagemagick/include/ImageMagick/magick/method-attribute.h",
"localPath": "__only_fnAlias/bin/imagemagick/include/ImageMagick/magick/method-attribute.h",
"rootPath": "/home/capaj/repos/google/search/.esbuild/.build/__only_fnAlias/bin/imagemagick/include/ImageMagick/magick/method-attribute.h",
},
]
`);
});
});

describe('pack', () => {
afterEach(() => {
jest.resetAllMocks();
});

describe('individually', () => {
it('should create zips with the functionAlias as the name', async () => {
const zipSpy = jest.spyOn(utils, 'zip').mockResolvedValue();
mocked(globby, true).sync.mockReturnValue(['hello1.js', 'hello2.js']);
mocked(globby).mockResolvedValue([]);
mocked(fs).statSync.mockReturnValue({ size: 123 } as fs.Stats);

const buildResults: FunctionBuildResult[] = [
{
result: { errors: [], warnings: [] },
bundlePath: 'hello1.js',
func: {
handler: 'hello1.handler',
events: [{ http: { path: 'hello', method: 'get' } }],
name: 'serverless-example-dev-hello1',
package: { patterns: [] },
},
functionAlias: 'hello1',
},
{
result: { errors: [], warnings: [] },
bundlePath: 'hello2.js',
func: {
handler: 'hello2.handler',
events: [{ http: { path: 'hello', method: 'get' } }],
name: 'serverless-example-dev-hello2',
package: { patterns: [] },
},
functionAlias: 'hello2',
},
];

const esbuildPlugin = {
serverless: {
service: {
package: {
individually: true,
},
},
cli: mockCli,
getVersion: jest.fn().mockReturnValue('1.19.0'),
},
buildOptions: {
packager: 'yarn',
exclude: ['aws-sdk'],
external: [],
nativeZip: false,
},
buildDirPath: '/workdir/serverless-esbuild/examples/individually/.esbuild/.build',
workDirPath: '/workdir/serverless-esbuild/examples/individually/.esbuild/',
serviceDirPath: '/workdir/serverless-esbuild/examples/individually',
buildResults,
};

await pack.call(esbuildPlugin);

expect(zipSpy).toBeCalledWith(
'/workdir/serverless-esbuild/examples/individually/.esbuild/.serverless/hello1.zip',
expect.any(Array),
expect.any(Boolean)
);
expect(zipSpy).toBeCalledWith(
'/workdir/serverless-esbuild/examples/individually/.esbuild/.serverless/hello2.zip',
expect.any(Array),
expect.any(Boolean)
);
});
});
});

0 comments on commit 09f08c7

Please sign in to comment.