From 4e31fd91978db8437c6b5bcda5c47e3abb5ccc8e Mon Sep 17 00:00:00 2001 From: lake2 Date: Sat, 14 Sep 2024 13:16:21 +0800 Subject: [PATCH] feat(cli): Add callbacks to cli (#56) Closes https://github.com/swc-project/pkgs/issues/55 I rename `packages/cli/src/swc/index.ts` to `packages/cli/src/swc/bin.ts`, and export `swcDir` function via new `packages/cli/src/swc/index.ts` add doc here: https://github.com/swc-project/website/pull/261 --- packages/cli/bin/swc.js | 2 +- .../cli/src/swc/__tests__/callback.test.ts | 70 ++++++++ packages/cli/src/swc/bin.ts | 17 ++ packages/cli/src/swc/dir.ts | 152 +++++++++++++----- packages/cli/src/swc/index.ts | 18 +-- packages/cli/src/swc/options.ts | 16 ++ 6 files changed, 216 insertions(+), 59 deletions(-) create mode 100644 packages/cli/src/swc/__tests__/callback.test.ts create mode 100644 packages/cli/src/swc/bin.ts diff --git a/packages/cli/bin/swc.js b/packages/cli/bin/swc.js index d2d17f9..7d99c9c 100755 --- a/packages/cli/bin/swc.js +++ b/packages/cli/bin/swc.js @@ -1,4 +1,4 @@ #!/usr/bin/env node process.title = "swc"; -require("../lib/swc"); +require("../lib/swc/bin.js"); diff --git a/packages/cli/src/swc/__tests__/callback.test.ts b/packages/cli/src/swc/__tests__/callback.test.ts new file mode 100644 index 0000000..a2ebb90 --- /dev/null +++ b/packages/cli/src/swc/__tests__/callback.test.ts @@ -0,0 +1,70 @@ +import { swcDir } from "../index"; + +jest.mock("../compile", () => ({ + outputResult: jest.fn(), +})); + +let mockComplie: any; + +jest.mock("../dirWorker", () => ({ + __esModule: true, + default: () => mockComplie(), +})); + +const cliOptions: any = { + outDir: "./.temp/", + watch: false, + filenames: ["./src/swcx/"], + extensions: [".ts"], + stripLeadingPaths: true, + sync: true, +}; +const swcOptions: any = { + jsc: { + target: "esnext", + externalHelpers: false, + }, + module: { + type: "commonjs", + }, +}; + +describe("dir callbacks", () => { + it("onSuccess should be called", async () => { + mockComplie = () => Promise.resolve(1); // mock complie success + + const onSuccess = jest.fn(); + const onFail = jest.fn(); + + await swcDir({ + cliOptions: cliOptions, + swcOptions: swcOptions, + callbacks: { + onSuccess, + onFail, + }, + }); + + expect(onSuccess.mock.calls).toHaveLength(1); + expect(onFail.mock.calls).toHaveLength(0); + }); + + it("onFail should be called", async () => { + mockComplie = () => Promise.reject(new Error("fail")); // mock complie fail + + const onSuccess = jest.fn(); + const onFail = jest.fn(); + + await swcDir({ + cliOptions: cliOptions, + swcOptions: swcOptions, + callbacks: { + onSuccess, + onFail, + }, + }); + + expect(onSuccess.mock.calls).toHaveLength(0); + expect(onFail.mock.calls).toHaveLength(1); + }); +}); diff --git a/packages/cli/src/swc/bin.ts b/packages/cli/src/swc/bin.ts new file mode 100644 index 0000000..bb12227 --- /dev/null +++ b/packages/cli/src/swc/bin.ts @@ -0,0 +1,17 @@ +import dirCommand from "./dir"; +import fileCommand from "./file"; +import parseArgs, { initProgram } from "./options"; + +initProgram(); +const opts = parseArgs(process.argv); +const fn = opts.cliOptions.outDir ? dirCommand : fileCommand; + +process.on("uncaughtException", function (err) { + console.error(err); + process.exit(1); +}); + +fn(opts).catch((err: Error) => { + console.error(err); + process.exit(1); +}); diff --git a/packages/cli/src/swc/dir.ts b/packages/cli/src/swc/dir.ts index 24390c8..03c4c8d 100644 --- a/packages/cli/src/swc/dir.ts +++ b/packages/cli/src/swc/dir.ts @@ -2,7 +2,7 @@ import { existsSync, promises } from "fs"; import { dirname, resolve } from "path"; import Piscina from "piscina"; import { CompileStatus } from "./constants"; -import { CliOptions } from "./options"; +import { Callbacks, CliOptions } from "./options"; import { exists, getDest } from "./util"; import handleCompile from "./dirWorker"; import { @@ -53,7 +53,11 @@ async function beforeStartCompilation(cliOptions: CliOptions) { } } -async function initialCompilation(cliOptions: CliOptions, swcOptions: Options) { +async function initialCompilation( + cliOptions: CliOptions, + swcOptions: Options, + callbacks?: Callbacks +) { const { includeDotfiles, filenames, @@ -70,6 +74,7 @@ async function initialCompilation(cliOptions: CliOptions, swcOptions: Options) { } = cliOptions; const results = new Map(); + const reasons = new Map(); const start = process.hrtime(); const sourceFiles = await globSources( @@ -83,7 +88,6 @@ async function initialCompilation(cliOptions: CliOptions, swcOptions: Options) { extensions, copyFiles ); - if (sync) { for (const filename of compilable) { try { @@ -97,7 +101,9 @@ async function initialCompilation(cliOptions: CliOptions, swcOptions: Options) { }); results.set(filename, result); } catch (err: any) { - console.error(err.message); + if (!callbacks?.onFail) { + console.error(err.message); + } results.set(filename, CompileStatus.Failed); } } @@ -110,7 +116,9 @@ async function initialCompilation(cliOptions: CliOptions, swcOptions: Options) { ); results.set(filename, result); } catch (err: any) { - console.error(err.message); + if (!callbacks?.onFail) { + console.error(err.message); + } results.set(filename, CompileStatus.Failed); } } @@ -134,7 +142,9 @@ async function initialCompilation(cliOptions: CliOptions, swcOptions: Options) { outFileExtension, }) .catch(err => { - console.error(err.message); + if (!callbacks?.onFail) { + console.error(err.message); + } throw err; }) ) @@ -151,6 +161,7 @@ async function initialCompilation(cliOptions: CliOptions, swcOptions: Options) { results.set(filename, result.value); } else { results.set(filename, CompileStatus.Failed); + reasons.set(filename, result.reason.message); } }); @@ -182,8 +193,9 @@ async function initialCompilation(cliOptions: CliOptions, swcOptions: Options) { break; } } + const duration = end[1] / 1000000; - if (!quiet && compiled + copied) { + if (compiled + copied) { let message = ""; if (compiled) { message += `Successfully compiled: ${compiled} ${ @@ -198,26 +210,40 @@ async function initialCompilation(cliOptions: CliOptions, swcOptions: Options) { } message += ` with swc (%dms)`; - console.log(message, (end[1] / 1000000).toFixed(2)); + if (callbacks?.onSuccess) { + if (!failed) { + callbacks.onSuccess({ duration, compiled, copied }); + } + } else if (!quiet) { + console.log(message, duration.toFixed(2)); + } } if (failed) { - console.log( - `Failed to compile ${failed} ${ - failed !== 1 ? "files" : "file" - } with swc.` - ); - if (!watch) { - const files = Array.from(results.entries()) - .filter(([, status]) => status === CompileStatus.Failed) - .map(([filename, _]) => filename) - .join("\n"); - throw new Error(`Failed to compile:\n${files}`); + if (callbacks?.onFail) { + callbacks.onFail({ duration, reasons }); + } else { + console.log( + `Failed to compile ${failed} ${ + failed !== 1 ? "files" : "file" + } with swc.` + ); + if (!watch) { + const files = Array.from(results.entries()) + .filter(([, status]) => status === CompileStatus.Failed) + .map(([filename, _]) => filename) + .join("\n"); + throw new Error(`Failed to compile:\n${files}`); + } } } } -async function watchCompilation(cliOptions: CliOptions, swcOptions: Options) { +async function watchCompilation( + cliOptions: CliOptions, + swcOptions: Options, + callbacks?: Callbacks +) { const { includeDotfiles, filenames, @@ -232,7 +258,9 @@ async function watchCompilation(cliOptions: CliOptions, swcOptions: Options) { const watcher = await watchSources(filenames, includeDotfiles); watcher.on("ready", () => { - if (!quiet) { + if (callbacks?.onWatchReady) { + callbacks.onWatchReady(); + } else if (!quiet) { console.info("Watching for file changes."); } }); @@ -264,8 +292,13 @@ async function watchCompilation(cliOptions: CliOptions, swcOptions: Options) { for (const type of ["add", "change"]) { watcher.on(type, async filename => { if (isCompilableExtension(filename, extensions)) { + const start = process.hrtime(); + const getDuration = () => { + const end = process.hrtime(start); + const duration = end[1] / 1000000; + return duration; + }; try { - const start = process.hrtime(); const result = await handleCompile({ filename, outDir, @@ -274,34 +307,67 @@ async function watchCompilation(cliOptions: CliOptions, swcOptions: Options) { swcOptions, outFileExtension, }); - if (!quiet && result === CompileStatus.Compiled) { - const end = process.hrtime(start); - console.log( - `Successfully compiled ${filename} with swc (%dms)`, - (end[1] / 1000000).toFixed(2) - ); + const duration = getDuration(); + if (result === CompileStatus.Compiled) { + if (callbacks?.onSuccess) { + callbacks.onSuccess({ + duration, + compiled: 1, + filename, + }); + } else if (!quiet) { + console.log( + `Successfully compiled ${filename} with swc (%dms)`, + duration.toFixed(2) + ); + } + } + } catch (error: any) { + if (callbacks?.onFail) { + const reasons = new Map(); + reasons.set(filename, error.message); + callbacks.onFail({ duration: getDuration(), reasons }); + } else { + console.error(error.message); } - } catch (err: any) { - console.error(err.message); } } else if (copyFiles) { + const start = process.hrtime(); + const getDuration = () => { + const end = process.hrtime(start); + const duration = end[1] / 1000000; + return duration; + }; try { - const start = process.hrtime(); const result = await handleCopy( filename, outDir, stripLeadingPaths ); - if (!quiet && result === CompileStatus.Copied) { - const end = process.hrtime(start); - console.log( - `Successfully copied ${filename} with swc (%dms)`, - (end[1] / 1000000).toFixed(2) - ); + if (result === CompileStatus.Copied) { + const duration = getDuration(); + if (callbacks?.onSuccess) { + callbacks.onSuccess({ + duration, + copied: 1, + filename, + }); + } else if (!quiet) { + console.log( + `Successfully copied ${filename} with swc (%dms)`, + duration.toFixed(2) + ); + } + } + } catch (error: any) { + if (callbacks?.onFail) { + const reasons = new Map(); + reasons.set(filename, error.message); + callbacks.onFail({ duration: getDuration(), reasons }); + } else { + console.error(`Failed to copy ${filename}`); + console.error(error.message); } - } catch (err: any) { - console.error(`Failed to copy ${filename}`); - console.error(err.message); } } }); @@ -311,16 +377,18 @@ async function watchCompilation(cliOptions: CliOptions, swcOptions: Options) { export default async function dir({ cliOptions, swcOptions, + callbacks, }: { cliOptions: CliOptions; swcOptions: Options; + callbacks?: Callbacks; }) { const { watch } = cliOptions; await beforeStartCompilation(cliOptions); - await initialCompilation(cliOptions, swcOptions); + await initialCompilation(cliOptions, swcOptions, callbacks); if (watch) { - await watchCompilation(cliOptions, swcOptions); + await watchCompilation(cliOptions, swcOptions, callbacks); } } diff --git a/packages/cli/src/swc/index.ts b/packages/cli/src/swc/index.ts index bb12227..44e6273 100644 --- a/packages/cli/src/swc/index.ts +++ b/packages/cli/src/swc/index.ts @@ -1,17 +1,3 @@ -import dirCommand from "./dir"; -import fileCommand from "./file"; -import parseArgs, { initProgram } from "./options"; +import swcDir from "./dir"; -initProgram(); -const opts = parseArgs(process.argv); -const fn = opts.cliOptions.outDir ? dirCommand : fileCommand; - -process.on("uncaughtException", function (err) { - console.error(err); - process.exit(1); -}); - -fn(opts).catch((err: Error) => { - console.error(err); - process.exit(1); -}); +export { swcDir }; diff --git a/packages/cli/src/swc/options.ts b/packages/cli/src/swc/options.ts index f7cd544..5935326 100644 --- a/packages/cli/src/swc/options.ts +++ b/packages/cli/src/swc/options.ts @@ -276,6 +276,22 @@ export interface CliOptions { readonly ignore: string[]; } +export interface Callbacks { + readonly onSuccess?: (data: { + duration: number; + /** count of compiled files */ + compiled?: number; + /** count of copied files */ + copied?: number; + filename?: string; + }) => any; + readonly onFail?: (data: { + duration: number; + reasons: Map; + }) => any; + readonly onWatchReady?: () => any; +} + export default function parserArgs(args: string[]) { program.parse(args); let opts = program.opts();