UNPKG

@zimic/interceptor

Version:

Next-gen TypeScript-first HTTP intercepting and mocking

345 lines (338 loc) 13.1 kB
#!/usr/bin/env node import { createCachedDynamicImport_default, logger, createInterceptorServer, createInterceptorToken, listInterceptorTokens, readInterceptorTokenFromFile, removeInterceptorToken, DEFAULT_INTERCEPTOR_TOKENS_DIRECTORY } from './chunk-7DRPXOOJ.mjs'; import { __require, __name } from './chunk-CGILA3WO.mjs'; import yargs from 'yargs'; import { hideBin } from 'yargs/helpers'; import fs from 'fs'; import path from 'path'; import color3 from 'picocolors'; // package.json var version = "1.0.1"; // src/cli/browser/shared/constants.ts var SERVICE_WORKER_FILE_NAME = "mockServiceWorker.js"; // src/cli/browser/init.ts var MSW_ROOT_PATH = path.join(__require.resolve("msw"), "..", "..", ".."); var MOCK_SERVICE_WORKER_PATH = path.join(MSW_ROOT_PATH, "lib", SERVICE_WORKER_FILE_NAME); async function initializeBrowserServiceWorker({ publicDirectory }) { await fs.promises.mkdir(publicDirectory, { recursive: true }); const destinationPath = path.join(publicDirectory, SERVICE_WORKER_FILE_NAME); await fs.promises.copyFile(MOCK_SERVICE_WORKER_PATH, destinationPath); logger.info(`Service worker script saved to ${color3.magenta(destinationPath)}.`); } __name(initializeBrowserServiceWorker, "initializeBrowserServiceWorker"); var init_default = initializeBrowserServiceWorker; // src/utils/processes.ts var PROCESS_EXIT_EVENTS = Object.freeze([ "beforeExit", "uncaughtExceptionMonitor", "SIGINT", "SIGTERM", "SIGHUP", "SIGBREAK" ]); var PROCESS_EXIT_CODE_BY_EXIT_EVENT = { beforeExit: void 0, uncaughtExceptionMonitor: void 0, SIGINT: 130, SIGTERM: 143, SIGHUP: 129, SIGBREAK: 131 }; var importExeca = createCachedDynamicImport_default(() => import('execa')); var CommandError = class _CommandError extends Error { static { __name(this, "CommandError"); } static DEFAULT_EXIT_CODE = 1; command; exitCode; signal; constructor(executable, options) { const message = _CommandError.createMessage(executable, options); super(message); this.name = "CommandError"; this.command = options.command ?? [executable]; this.exitCode = this.getExitCode(options); this.signal = options.signal; } getExitCode(options) { const existingExitCode = options.exitCode; const exitCodeInferredFromSignal = options.signal === void 0 ? void 0 : PROCESS_EXIT_CODE_BY_EXIT_EVENT[options.signal]; return existingExitCode ?? exitCodeInferredFromSignal ?? _CommandError.DEFAULT_EXIT_CODE; } static createMessage(command, options) { const suffix = options.originalMessage ? `: ${options.originalMessage}` : ""; if (options.exitCode === void 0 && options.signal === void 0) { return `Command '${command}' failed${suffix}`; } const prefix = `Command '${command}' exited `; const infix = options.exitCode === void 0 ? `after signal ${options.signal}` : `with code ${options.exitCode}`; return `${prefix}${infix}${suffix}`; } }; async function runCommand(commandEntry, commandArguments) { const { execa: $, ExecaError } = await importExeca(); try { await $(commandEntry, commandArguments, { stdio: "inherit" }); } catch (error) { if (!(error instanceof ExecaError)) { throw error; } const commandError = new CommandError(commandEntry, { command: [commandEntry, ...commandArguments], exitCode: error.exitCode, signal: error.signal, originalMessage: error.originalMessage }); throw commandError; } } __name(runCommand, "runCommand"); async function startInterceptorServer({ hostname, port, logUnhandledRequests, tokensDirectory, ephemeral, onReady }) { const server = createInterceptorServer({ hostname, port, logUnhandledRequests, tokensDirectory }); async function handleExitEvent(exitEvent) { await server.stop(); for (const { exitEvent: exitEvent2, exitHandler } of exitHandlerGroups) { process.off(exitEvent2, exitHandler); } const exitCode = exitEvent ? PROCESS_EXIT_CODE_BY_EXIT_EVENT[exitEvent] : void 0; if (exitCode !== void 0) { process.exit(exitCode); } } __name(handleExitEvent, "handleExitEvent"); const exitHandlerGroups = PROCESS_EXIT_EVENTS.map((exitEvent) => ({ exitEvent, exitHandler: handleExitEvent.bind(null, exitEvent) })); for (const { exitEvent, exitHandler } of exitHandlerGroups) { process.on(exitEvent, exitHandler); } await server.start(); logger.info( `${ephemeral ? "Ephemeral s" : "S"}erver is running on ${color3.yellow(`${server.hostname}:${server.port}`)}` ); const isDangerouslyUnprotected = !tokensDirectory && process.env.NODE_ENV === "production"; if (isDangerouslyUnprotected) { logger.warn( [ `Attention: this interceptor server is ${color3.bold(color3.red("unprotected"))}. Do not expose it publicly without authentication.`, "", "Learn more: https://zimic.dev/docs/interceptor/guides/http/remote-interceptors#interceptor-server-authentication" ].join("\n") ); } if (onReady) { try { await runCommand(onReady.command, onReady.arguments); } catch (error) { console.error(error); if (!(error instanceof CommandError)) { throw error; } process.exit(error.exitCode); } } if (ephemeral) { await handleExitEvent(void 0); process.exit(0); } } __name(startInterceptorServer, "startInterceptorServer"); var start_default = startInterceptorServer; async function createInterceptorServerToken({ tokenName, tokensDirectory }) { const token = await createInterceptorToken({ name: tokenName, tokensDirectory }); logger.info( [ `${color3.green(color3.bold("\u2714"))} Token${tokenName ? ` ${color3.green(tokenName)}` : ""} created:`, "", color3.yellow(token.value), "", "Store this token securely. It cannot be retrieved later.", "", `To enable authentication in your interceptor server, use the ${color3.cyan("--tokens-dir")} option. Only remote interceptors bearing a valid token will be allowed to connect.`, "", `${color3.dim("$")} zimic-interceptor server start ${color3.cyan("--tokens-dir")} ${color3.magenta(tokensDirectory)}`, "", "Learn more: https://zimic.dev/docs/interceptor/guides/http/remote-interceptors#interceptor-server-authentication" ].join("\n") ); } __name(createInterceptorServerToken, "createInterceptorServerToken"); // src/cli/server/token/list.ts async function listInterceptorServerTokens({ tokensDirectory }) { const tokens = await listInterceptorTokens({ tokensDirectory }); logger.raw.table( [ { title: "ID", property: "id" }, { title: "NAME", property: "name" }, { title: "CREATED AT", property: "createdAt" } ], tokens.map((token) => ({ id: token.id, name: token.name ?? "", createdAt: token.createdAt.toISOString() })) ); } __name(listInterceptorServerTokens, "listInterceptorServerTokens"); async function removeInterceptorServerToken({ tokenId, tokensDirectory }) { const token = await readInterceptorTokenFromFile(tokenId, { tokensDirectory }); if (!token) { logger.error(`${color3.red(color3.bold("\u2718"))} Token ${color3.red(tokenId)} not found.`); process.exit(1); } await removeInterceptorToken(token.id, { tokensDirectory }); logger.info(`${color3.green(color3.bold("\u2714"))} Token ${color3.green(token.name ?? token.id)} removed.`); } __name(removeInterceptorServerToken, "removeInterceptorServerToken"); // src/cli/cli.ts async function runCLI() { await yargs(hideBin(process.argv)).scriptName("zimic-interceptor").version(version).showHelpOnFail(false).strict().command( "browser", "Manage your browser mock configuration.", (yargs2) => yargs2.demandCommand().command( "init <publicDirectory>", "Initialize the browser service worker configuration.", (yargs3) => yargs3.positional("publicDirectory", { type: "string", description: "The path to the public directory of your application.", demandOption: true }), async (cliArguments) => { await init_default({ publicDirectory: cliArguments.publicDirectory }); } ) ).command( "server", "Manage interceptor servers.", (yargs2) => yargs2.demandCommand().command( "start [-- onReady]", "Start an interceptor server.", (yargs3) => yargs3.positional("onReady", { description: "A command to run when the server is ready to accept connections.", type: "string" }).option("hostname", { type: "string", description: "The hostname to start the server on.", alias: "h", default: "localhost" }).option("port", { type: "number", description: "The port to start the server on.", alias: "p" }).option("ephemeral", { type: "boolean", description: "Whether the server should stop automatically after the on-ready command finishes. If no on-ready command is provided and ephemeral is true, the server will stop immediately after starting.", alias: "e", default: false }).option("log-unhandled-requests", { type: "boolean", description: "Whether to log a warning when no interceptors were found for the base URL of a request. If an interceptor was matched, the logging behavior for that base URL is configured in the interceptor itself.", alias: "l" }).option("tokens-dir", { type: "string", description: "The directory where the authorized interceptor authentication tokens are saved. If provided, only remote interceptors bearing a valid token will be accepted. This option is essential if you are exposing your interceptor server publicly. For local development and testing, though, `--tokens-dir` is optional.", alias: "t" }), async (cliArguments) => { const onReadyCommand = cliArguments._.at(2)?.toString(); const onReadyCommandArguments = cliArguments._.slice(3).map((argument) => argument.toString()); await start_default({ hostname: cliArguments.hostname, port: cliArguments.port, ephemeral: cliArguments.ephemeral, logUnhandledRequests: cliArguments.logUnhandledRequests, tokensDirectory: cliArguments.tokensDir, onReady: onReadyCommand ? { command: onReadyCommand.toString(), arguments: onReadyCommandArguments } : void 0 }); } ).command( "token", "Manage remote interceptor authentication tokens.", (yargs3) => yargs3.demandCommand().command( "create", "Create an interceptor token.", (yargs4) => yargs4.option("name", { type: "string", description: "The name of the token to create.", alias: "n" }).option("tokens-dir", { type: "string", description: "The directory where the created interceptor token will be saved.", alias: "t", default: DEFAULT_INTERCEPTOR_TOKENS_DIRECTORY }), async (cliArguments) => { await createInterceptorServerToken({ tokenName: cliArguments.name, tokensDirectory: cliArguments.tokensDir }); } ).command( ["ls", "list"], "List the authorized interceptor tokens.", (yargs4) => yargs4.option("tokens-dir", { type: "string", description: "The directory where the interceptor tokens are saved.", alias: "t", default: DEFAULT_INTERCEPTOR_TOKENS_DIRECTORY }), async (cliArguments) => { await listInterceptorServerTokens({ tokensDirectory: cliArguments.tokensDir }); } ).command( ["rm <tokenId>", "remove <tokenId>"], "Remove (invalidate) an interceptor token. Existing connections will not be affected, so restarting the server is recommended to disconnect all interceptors.", (yargs4) => yargs4.positional("tokenId", { type: "string", description: "The identifier of the token to remove.", demandOption: true }).option("tokens-dir", { type: "string", description: "The directory where the interceptor tokens are saved.", alias: "t", default: DEFAULT_INTERCEPTOR_TOKENS_DIRECTORY }), async (cliArguments) => { await removeInterceptorServerToken({ tokenId: cliArguments.tokenId, tokensDirectory: cliArguments.tokensDir }); } ) ) ).parse(); } __name(runCLI, "runCLI"); var cli_default = runCLI; // src/cli/index.ts void cli_default(); /* istanbul ignore if -- @preserve * This is a safeguard if the error is not an ExecaError. It is not expected to run. */ /* istanbul ignore if -- @preserve * A CommandError is always expected here. */ //# sourceMappingURL=cli.mjs.map //# sourceMappingURL=cli.mjs.map