From ea5133889220ef90b2eba255794cd2e7df657b27 Mon Sep 17 00:00:00 2001 From: ChristopherPHolder Date: Wed, 3 Jul 2024 11:22:43 +0200 Subject: [PATCH] fix(esbuild-meta): handle missing stats path --- .../e2e/__snapshots__/filter.test.e2e.ts.snap | 16 +++++++++++ packages/esbuild-meta/e2e/filter.test.e2e.ts | 28 +++++++++++++++++++ packages/esbuild-meta/e2e/root.test.e2e.ts | 26 ++++++++++------- packages/esbuild-meta/e2e/utils.ts | 20 +++++++++++-- packages/esbuild-meta/src/lib/filter-meta.ts | 14 ++++++---- packages/esbuild-meta/src/lib/utils.ts | 14 ++++++++-- packages/esbuild-meta/vitest.config.e2e.ts | 3 ++ 7 files changed, 100 insertions(+), 21 deletions(-) create mode 100644 packages/esbuild-meta/e2e/__snapshots__/filter.test.e2e.ts.snap create mode 100644 packages/esbuild-meta/e2e/filter.test.e2e.ts diff --git a/packages/esbuild-meta/e2e/__snapshots__/filter.test.e2e.ts.snap b/packages/esbuild-meta/e2e/__snapshots__/filter.test.e2e.ts.snap new file mode 100644 index 0000000..62535c4 --- /dev/null +++ b/packages/esbuild-meta/e2e/__snapshots__/filter.test.e2e.ts.snap @@ -0,0 +1,16 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`filter command > should have a help option 1`] = ` +"esbuild-meta filter + +Filters the meta file to only include chunks required by specified entry points + +Options: + -s, --statsPath The path to the stats.json file [string] [required] + -o, --outPath The path where the new file should be saved [string] [default: "initial-stats.json"] + --excludeDynamicImports, --eDI Should the dynamic imports be filtered out of the output chunk imports [boolean] [default: false] + -e, --entryPoints Entry points that should be considered for the bundle [array] [default: ["main-","polyfills-"]] + -v, --version Show version number [boolean] + -h, --help Show help [boolean] +" +`; diff --git a/packages/esbuild-meta/e2e/filter.test.e2e.ts b/packages/esbuild-meta/e2e/filter.test.e2e.ts new file mode 100644 index 0000000..1c705b0 --- /dev/null +++ b/packages/esbuild-meta/e2e/filter.test.e2e.ts @@ -0,0 +1,28 @@ +import { describe, expect, it } from 'vitest'; +import { cliProcess } from './utils.js'; +import { DEMAND_STATS_PATH } from '../src/lib/filter-meta.js'; +import { INVALID_FILE_PATH_ERROR_MSG } from '../src/lib/utils.js'; + +describe('filter command', () => { + it('should have a help option', async () => { + const { stdout, stderr, code } = await cliProcess('esbuild-meta filter --help'); + expect(stdout).toMatchSnapshot(); + expect(stderr).toBeFalsy(); + expect(code).toBe(0); + }); + + it('should demand stats path option', async () => { + const { stdout, stderr, code } = await cliProcess('esbuild-meta filter'); + expect(stderr).toContain(DEMAND_STATS_PATH); + expect(stdout).toBeFalsy(); + expect(code).toBe(1); + }); + + it('should throw if stats path does not point to a file', async () => { + const INVALID_STATS_FILE = 'invalid-path.json'; + const { stdout, stderr, code } = await cliProcess(`esbuild-meta filter --statsPath ${INVALID_STATS_FILE}`); + expect(stderr).toContain(INVALID_FILE_PATH_ERROR_MSG('invalid-path.json')); + expect(stdout).toBeFalsy(); + expect(code).toBe(1); + }); +}); diff --git a/packages/esbuild-meta/e2e/root.test.e2e.ts b/packages/esbuild-meta/e2e/root.test.e2e.ts index 8438e55..e89bdb3 100644 --- a/packages/esbuild-meta/e2e/root.test.e2e.ts +++ b/packages/esbuild-meta/e2e/root.test.e2e.ts @@ -1,26 +1,32 @@ import { describe, it, expect, beforeAll } from 'vitest'; import { version } from '../package.json'; -import { commandOutput } from './utils.js'; +import { cliProcess, CliProcessOutput } from './utils.js'; describe('--help', () => { - let helpOutput: string; + let output: CliProcessOutput; - beforeAll(() => { - helpOutput = commandOutput('esbuild-meta --help'); + beforeAll(async () => { + output = await cliProcess('esbuild-meta --help'); }); - it('should show help', () => { - expect(helpOutput).toMatchSnapshot(); + it('should show help', async () => { + const { stdout, stderr, code } = output; + expect(stdout).toMatchSnapshot(); + expect(stderr).toBeFalsy(); + expect(code).toBe(0); }); - it('should alias to -h', () => { - expect(commandOutput('esbuild-meta -h')).toBe(helpOutput); + it('should alias to -h', async () => { + expect(await cliProcess('esbuild-meta --help')).toEqual(output); }); }); describe('--version', () => { - it('should show version', () => { - expect(commandOutput('esbuild-meta --version')).toContain(version); + it('should show version', async () => { + const {stdout, stderr, code} = await cliProcess('esbuild-meta --version'); + expect(stdout).toContain(version); + expect(stderr).toBeFalsy(); + expect(code).toBe(0); }); }); diff --git a/packages/esbuild-meta/e2e/utils.ts b/packages/esbuild-meta/e2e/utils.ts index d61a3e2..c5f356c 100644 --- a/packages/esbuild-meta/e2e/utils.ts +++ b/packages/esbuild-meta/e2e/utils.ts @@ -1,3 +1,19 @@ -import { execSync } from 'child_process'; +import { spawn } from 'node:child_process'; -export const commandOutput = (command: string) => execSync(command).toString(); +export type CliProcessOutput = { + stdout: string; + stderr: string; + code: number | null; +} + +export const cliProcess = (command: string) => { + return new Promise((resolve) => { + const process = spawn(command, [], { stdio: 'pipe', shell: true }); + + let stdout = ''; + let stderr = ''; + process.stdout.on('data', (data) => stdout += String(data)); + process.stderr.on('data', (data) => stderr += String(data)); + process.on('close', code => resolve({ stdout, stderr, code })); + }) +} diff --git a/packages/esbuild-meta/src/lib/filter-meta.ts b/packages/esbuild-meta/src/lib/filter-meta.ts index 0fe0ead..6f914c3 100644 --- a/packages/esbuild-meta/src/lib/filter-meta.ts +++ b/packages/esbuild-meta/src/lib/filter-meta.ts @@ -8,11 +8,13 @@ import { makeJson, } from './utils.js'; -const distPath = { - alias: 'd', +export const DEMAND_STATS_PATH = 'The path to a stats.json file is required'; + +const statsPath = { + alias: 's', type: 'string', - default: 'dist', - description: 'The path to the stats.json file' + description: 'The path to the stats.json file', + demandOption: DEMAND_STATS_PATH, } as const satisfies Options; const outPath = { @@ -37,7 +39,7 @@ const entryPoints = { description: 'Entry points that should be considered for the bundle', } as const satisfies Options; -const filterMetaOptions = { distPath, outPath, excludeDynamicImports, entryPoints }; +const filterMetaOptions = { statsPath, outPath, excludeDynamicImports, entryPoints }; type FilterMetaOptions = InferredOptionTypes; type FilterMetaCommandModule = CommandModule; @@ -47,7 +49,7 @@ const filterMetaBuilder: CommandBuilder = (argv: Arg } const filterMetaHandler: FilterMetaCommandModule['handler'] = (argv: FilterMetaOptions) => { - const meta = getJson([argv.distPath]); + const meta = getJson(argv.statsPath); const entryPoints = extractEntryPoints(meta, argv.entryPoints); filterMetaFromEntryPoints(meta, entryPoints); if (argv.excludeDynamicImports) { diff --git a/packages/esbuild-meta/src/lib/utils.ts b/packages/esbuild-meta/src/lib/utils.ts index fb502a3..f1fa1b4 100644 --- a/packages/esbuild-meta/src/lib/utils.ts +++ b/packages/esbuild-meta/src/lib/utils.ts @@ -1,9 +1,17 @@ import { readFileSync, writeFileSync } from 'node:fs'; -import { join } from 'node:path'; +import { join, normalize } from 'node:path'; import { Metafile } from 'esbuild'; -export function getJson(path: string[]) { - return JSON.parse(readFileSync(join(...path), {encoding: 'utf-8'})) as T; +export const INVALID_FILE_PATH_ERROR_MSG = (path: string) => `No file found at ${path}`; + +export function getJson(path: string) { + const normalizedPath = normalize(path); + try { + return JSON.parse(readFileSync(normalizedPath, {encoding: 'utf-8'})) as T; + } + catch (e) { + throw new Error(INVALID_FILE_PATH_ERROR_MSG(normalizedPath)); + } } export function makeJson(path: string, file: any) { diff --git a/packages/esbuild-meta/vitest.config.e2e.ts b/packages/esbuild-meta/vitest.config.e2e.ts index 78d57d6..6cae177 100644 --- a/packages/esbuild-meta/vitest.config.e2e.ts +++ b/packages/esbuild-meta/vitest.config.e2e.ts @@ -9,6 +9,9 @@ export default defineConfig({ test: { watch: false, + pool: 'threads', + poolOptions: { threads: { singleThread: true } }, + testTimeout: 140_000, reporters: ['default'], globals: true, cache: { dir: '../../node_modules/.vitest' },