Skip to content

Commit

Permalink
fix: asynchronously create packager instance (#352)
Browse files Browse the repository at this point in the history
  • Loading branch information
webdeveric authored Aug 20, 2022
1 parent f2d09a3 commit f60b9f5
Show file tree
Hide file tree
Showing 7 changed files with 101 additions and 44 deletions.
11 changes: 6 additions & 5 deletions src/pack-externals.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import fse from 'fs-extra';
import path from 'path';

import fse from 'fs-extra';
import {
compose,
forEach,
Expand All @@ -24,11 +25,11 @@ import {
without,
} from 'ramda';

import * as Packagers from './packagers';
import { JSONObject } from './types';
import { getPackager } from './packagers';
import { findProjectRoot, findUp } from './utils';

import EsbuildServerlessPlugin from './index';
import type EsbuildServerlessPlugin from './index';
import type { JSONObject } from './types';

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

// Determine and create packager
const packager = await Packagers.get(this.buildOptions.packager);
const packager = await getPackager.call(this, this.buildOptions.packager);

// Fetch needed original package.json sections
const sectionNames = packager.copyPackageSectionNames;
Expand Down
13 changes: 8 additions & 5 deletions src/pack.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import path from 'path';

import fs from 'fs-extra';
import globby from 'globby';
import path from 'path';
import {
intersection,
isEmpty,
Expand All @@ -14,13 +15,15 @@ import {
without,
} from 'ramda';
import semver from 'semver';
import EsbuildServerlessPlugin from '.';

import { ONLY_PREFIX, SERVERLESS_FOLDER } from './constants';
import { doSharePath, flatDep, getDepsFromBundle, isESM } from './helper';
import * as Packagers from './packagers';
import { IFiles } from './types';
import { getPackager } from './packagers';
import { humanSize, zip, trimExtension } from './utils';

import type EsbuildServerlessPlugin from './index';
import type { IFiles } from './types';

function setFunctionArtifactPath(this: EsbuildServerlessPlugin, func, artifactPath) {
const version = this.serverless.getVersion();
// Serverless changed the artifact path location in version 1.18
Expand Down Expand Up @@ -137,7 +140,7 @@ export async function pack(this: EsbuildServerlessPlugin) {
}

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

// get a list of every function bundle
const buildResults = this.buildResults;
Expand Down
78 changes: 46 additions & 32 deletions src/packagers/index.ts
Original file line number Diff line number Diff line change
@@ -1,44 +1,58 @@
/**
* Factory for supported packagers.
*
* All packagers must implement the following interface:
* All packagers must extend the Packager class.
*
* interface Packager {
*
* static get lockfileName(): string;
* static get copyPackageSectionNames(): Array<string>;
* static get mustCopyModules(): boolean;
* static getProdDependencies(cwd: string, depth: number = 1): BbPromise<Object>;
* static rebaseLockfile(pathToPackageRoot: string, lockfile: Object): void;
* static install(cwd: string): BbPromise<void>;
* static prune(cwd: string): BbPromise<void>;
* static runScripts(cwd: string, scriptNames): BbPromise<void>;
*
* }
* @see Packager
*/
import { memoizeWith } from 'ramda';

import { isPackagerId } from '../type-predicate';

import type EsbuildServerlessPlugin from '../index';
import type { PackagerId } from '../types';
import type { Packager } from './packager';

const packagerFactories: Record<PackagerId, () => Promise<Packager>> = {
async npm() {
const { NPM } = await import('./npm');

return new NPM();
},
async pnpm() {
const { Pnpm } = await import('./pnpm');

import { Packager } from './packager';
import { NPM } from './npm';
import { Pnpm } from './pnpm';
import { Yarn } from './yarn';
return new Pnpm();
},
async yarn() {
const { Yarn } = await import('./yarn');

const registeredPackagers = {
npm: new NPM(),
pnpm: new Pnpm(),
yarn: new Yarn(),
return new Yarn();
},
};

/**
* Factory method.
* @this ServerlessWebpack - Active plugin instance
* @param {string} packagerId - Well known packager id.
* @returns {Promise<Packager>} - Promised packager to allow packagers be created asynchronously.
* Asynchronously create a Packager instance and memoize it.
*
* @this EsbuildServerlessPlugin - Active plugin instance
* @param {string} packagerId - Well known packager id
* @returns {Promise<Packager>} - The selected Packager
*/
export function get(packagerId: string): Promise<Packager> {
if (!(packagerId in registeredPackagers)) {
const message = `Could not find packager '${packagerId}'`;
this.log.error(`ERROR: ${message}`);
throw new this.serverless.classes.Error(message);
export const getPackager = memoizeWith(
(packagerId) => packagerId,
async function (this: EsbuildServerlessPlugin, packagerId: PackagerId): Promise<Packager> {
this.log.debug(`Trying to create packager: ${packagerId}`);

if (!isPackagerId(packagerId)) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore Serverless typings (as of v3.0.2) are incorrect
throw new this.serverless.classes.Error(`Could not find packager '${packagerId}'`);
}

const packager = await packagerFactories[packagerId]();

this.log.debug(`Packager created: ${packagerId}`);

return packager;
}
return registeredPackagers[packagerId];
}
);
17 changes: 17 additions & 0 deletions src/tests/packagers/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { getPackager } from '../../packagers';

import type EsbuildServerlessPlugin from '../../index';

describe('getPackager()', () => {
const mockPlugin = {
log: {
debug: jest.fn(),
},
} as unknown as EsbuildServerlessPlugin;

it('Returns a Packager instance', async () => {
const npm = await getPackager.call(mockPlugin, 'npm');

expect(npm).toEqual(expect.any(Object));
});
});
15 changes: 15 additions & 0 deletions src/tests/type-predicate.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { isPackagerId } from '../type-predicate';

describe('isPackagerId()', () => {
it('Returns true for valid input', () => {
['npm', 'pnpm', 'yarn'].forEach((id) => {
expect(isPackagerId(id)).toBeTruthy();
});
});

it('Returns false for invalid input', () => {
['not-a-real-packager-id', false, 123, [], {}].forEach((id) => {
expect(isPackagerId(id)).toBeFalsy();
});
});
});
5 changes: 5 additions & 0 deletions src/type-predicate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import type { PackagerId } from './types';

export function isPackagerId(input: unknown): input is PackagerId {
return input === 'npm' || input === 'pnpm' || input === 'yarn';
}
6 changes: 4 additions & 2 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { BuildOptions, BuildResult, Plugin } from 'esbuild';
import Serverless from 'serverless';
import type { BuildOptions, BuildResult, Plugin } from 'esbuild';
import type Serverless from 'serverless';

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

Expand Down Expand Up @@ -74,3 +74,5 @@ export interface IFile {
readonly rootPath: string;
}
export type IFiles = readonly IFile[];

export type PackagerId = 'npm' | 'pnpm' | 'yarn';

0 comments on commit f60b9f5

Please sign in to comment.