Skip to content

Commit f60b9f5

Browse files
authored
fix: asynchronously create packager instance (floydspace#352)
1 parent f2d09a3 commit f60b9f5

File tree

7 files changed

+101
-44
lines changed

7 files changed

+101
-44
lines changed

src/pack-externals.ts

+6-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import fse from 'fs-extra';
21
import path from 'path';
2+
3+
import fse from 'fs-extra';
34
import {
45
compose,
56
forEach,
@@ -24,11 +25,11 @@ import {
2425
without,
2526
} from 'ramda';
2627

27-
import * as Packagers from './packagers';
28-
import { JSONObject } from './types';
28+
import { getPackager } from './packagers';
2929
import { findProjectRoot, findUp } from './utils';
3030

31-
import EsbuildServerlessPlugin from './index';
31+
import type EsbuildServerlessPlugin from './index';
32+
import type { JSONObject } from './types';
3233

3334
function rebaseFileReferences(pathToPackageRoot: string, moduleVersion: string) {
3435
if (/^(?:file:[^/]{2}|\.\/|\.\.\/)/.test(moduleVersion)) {
@@ -242,7 +243,7 @@ export async function packExternalModules(this: EsbuildServerlessPlugin) {
242243
path.relative(process.cwd(), path.join(findUp('package.json'), './package.json'));
243244

244245
// Determine and create packager
245-
const packager = await Packagers.get(this.buildOptions.packager);
246+
const packager = await getPackager.call(this, this.buildOptions.packager);
246247

247248
// Fetch needed original package.json sections
248249
const sectionNames = packager.copyPackageSectionNames;

src/pack.ts

+8-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1+
import path from 'path';
2+
13
import fs from 'fs-extra';
24
import globby from 'globby';
3-
import path from 'path';
45
import {
56
intersection,
67
isEmpty,
@@ -14,13 +15,15 @@ import {
1415
without,
1516
} from 'ramda';
1617
import semver from 'semver';
17-
import EsbuildServerlessPlugin from '.';
18+
1819
import { ONLY_PREFIX, SERVERLESS_FOLDER } from './constants';
1920
import { doSharePath, flatDep, getDepsFromBundle, isESM } from './helper';
20-
import * as Packagers from './packagers';
21-
import { IFiles } from './types';
21+
import { getPackager } from './packagers';
2222
import { humanSize, zip, trimExtension } from './utils';
2323

24+
import type EsbuildServerlessPlugin from './index';
25+
import type { IFiles } from './types';
26+
2427
function setFunctionArtifactPath(this: EsbuildServerlessPlugin, func, artifactPath) {
2528
const version = this.serverless.getVersion();
2629
// Serverless changed the artifact path location in version 1.18
@@ -137,7 +140,7 @@ export async function pack(this: EsbuildServerlessPlugin) {
137140
}
138141

139142
// 2) If individually is set, we'll optimize files and zip per-function
140-
const packager = await Packagers.get(this.buildOptions.packager);
143+
const packager = await getPackager.call(this, this.buildOptions.packager);
141144

142145
// get a list of every function bundle
143146
const buildResults = this.buildResults;

src/packagers/index.ts

+46-32
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,58 @@
11
/**
22
* Factory for supported packagers.
33
*
4-
* All packagers must implement the following interface:
4+
* All packagers must extend the Packager class.
55
*
6-
* interface Packager {
7-
*
8-
* static get lockfileName(): string;
9-
* static get copyPackageSectionNames(): Array<string>;
10-
* static get mustCopyModules(): boolean;
11-
* static getProdDependencies(cwd: string, depth: number = 1): BbPromise<Object>;
12-
* static rebaseLockfile(pathToPackageRoot: string, lockfile: Object): void;
13-
* static install(cwd: string): BbPromise<void>;
14-
* static prune(cwd: string): BbPromise<void>;
15-
* static runScripts(cwd: string, scriptNames): BbPromise<void>;
16-
*
17-
* }
6+
* @see Packager
187
*/
8+
import { memoizeWith } from 'ramda';
9+
10+
import { isPackagerId } from '../type-predicate';
11+
12+
import type EsbuildServerlessPlugin from '../index';
13+
import type { PackagerId } from '../types';
14+
import type { Packager } from './packager';
15+
16+
const packagerFactories: Record<PackagerId, () => Promise<Packager>> = {
17+
async npm() {
18+
const { NPM } = await import('./npm');
19+
20+
return new NPM();
21+
},
22+
async pnpm() {
23+
const { Pnpm } = await import('./pnpm');
1924

20-
import { Packager } from './packager';
21-
import { NPM } from './npm';
22-
import { Pnpm } from './pnpm';
23-
import { Yarn } from './yarn';
25+
return new Pnpm();
26+
},
27+
async yarn() {
28+
const { Yarn } = await import('./yarn');
2429

25-
const registeredPackagers = {
26-
npm: new NPM(),
27-
pnpm: new Pnpm(),
28-
yarn: new Yarn(),
30+
return new Yarn();
31+
},
2932
};
3033

3134
/**
32-
* Factory method.
33-
* @this ServerlessWebpack - Active plugin instance
34-
* @param {string} packagerId - Well known packager id.
35-
* @returns {Promise<Packager>} - Promised packager to allow packagers be created asynchronously.
35+
* Asynchronously create a Packager instance and memoize it.
36+
*
37+
* @this EsbuildServerlessPlugin - Active plugin instance
38+
* @param {string} packagerId - Well known packager id
39+
* @returns {Promise<Packager>} - The selected Packager
3640
*/
37-
export function get(packagerId: string): Promise<Packager> {
38-
if (!(packagerId in registeredPackagers)) {
39-
const message = `Could not find packager '${packagerId}'`;
40-
this.log.error(`ERROR: ${message}`);
41-
throw new this.serverless.classes.Error(message);
41+
export const getPackager = memoizeWith(
42+
(packagerId) => packagerId,
43+
async function (this: EsbuildServerlessPlugin, packagerId: PackagerId): Promise<Packager> {
44+
this.log.debug(`Trying to create packager: ${packagerId}`);
45+
46+
if (!isPackagerId(packagerId)) {
47+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
48+
// @ts-ignore Serverless typings (as of v3.0.2) are incorrect
49+
throw new this.serverless.classes.Error(`Could not find packager '${packagerId}'`);
50+
}
51+
52+
const packager = await packagerFactories[packagerId]();
53+
54+
this.log.debug(`Packager created: ${packagerId}`);
55+
56+
return packager;
4257
}
43-
return registeredPackagers[packagerId];
44-
}
58+
);

src/tests/packagers/index.test.ts

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { getPackager } from '../../packagers';
2+
3+
import type EsbuildServerlessPlugin from '../../index';
4+
5+
describe('getPackager()', () => {
6+
const mockPlugin = {
7+
log: {
8+
debug: jest.fn(),
9+
},
10+
} as unknown as EsbuildServerlessPlugin;
11+
12+
it('Returns a Packager instance', async () => {
13+
const npm = await getPackager.call(mockPlugin, 'npm');
14+
15+
expect(npm).toEqual(expect.any(Object));
16+
});
17+
});

src/tests/type-predicate.test.ts

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { isPackagerId } from '../type-predicate';
2+
3+
describe('isPackagerId()', () => {
4+
it('Returns true for valid input', () => {
5+
['npm', 'pnpm', 'yarn'].forEach((id) => {
6+
expect(isPackagerId(id)).toBeTruthy();
7+
});
8+
});
9+
10+
it('Returns false for invalid input', () => {
11+
['not-a-real-packager-id', false, 123, [], {}].forEach((id) => {
12+
expect(isPackagerId(id)).toBeFalsy();
13+
});
14+
});
15+
});

src/type-predicate.ts

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import type { PackagerId } from './types';
2+
3+
export function isPackagerId(input: unknown): input is PackagerId {
4+
return input === 'npm' || input === 'pnpm' || input === 'yarn';
5+
}

src/types.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { BuildOptions, BuildResult, Plugin } from 'esbuild';
2-
import Serverless from 'serverless';
1+
import type { BuildOptions, BuildResult, Plugin } from 'esbuild';
2+
import type Serverless from 'serverless';
33

44
export type ConfigFn = (sls: Serverless) => Configuration;
55

@@ -74,3 +74,5 @@ export interface IFile {
7474
readonly rootPath: string;
7575
}
7676
export type IFiles = readonly IFile[];
77+
78+
export type PackagerId = 'npm' | 'pnpm' | 'yarn';

0 commit comments

Comments
 (0)