UNPKG

@zimic/interceptor

Version:

Next-gen TypeScript-first HTTP intercepting and mocking

354 lines (344 loc) 14.1 kB
#!/usr/bin/env node 'use strict'; var chunkLWUFSWRA_js = require('./chunk-LWUFSWRA.js'); var chunkWCQVDF3K_js = require('./chunk-WCQVDF3K.js'); var yargs = require('yargs'); var helpers = require('yargs/helpers'); var fs = require('fs'); var path = require('path'); var color3 = require('picocolors'); function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; } var yargs__default = /*#__PURE__*/_interopDefault(yargs); var fs__default = /*#__PURE__*/_interopDefault(fs); var path__default = /*#__PURE__*/_interopDefault(path); var color3__default = /*#__PURE__*/_interopDefault(color3); // 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__default.default.join(chunkWCQVDF3K_js.__require.resolve("msw"), "..", "..", ".."); var MOCK_SERVICE_WORKER_PATH = path__default.default.join(MSW_ROOT_PATH, "lib", SERVICE_WORKER_FILE_NAME); async function initializeBrowserServiceWorker({ publicDirectory }) { await fs__default.default.promises.mkdir(publicDirectory, { recursive: true }); const destinationPath = path__default.default.join(publicDirectory, SERVICE_WORKER_FILE_NAME); await fs__default.default.promises.copyFile(MOCK_SERVICE_WORKER_PATH, destinationPath); chunkLWUFSWRA_js.logger.info(`Service worker script saved to ${color3__default.default.magenta(destinationPath)}.`); } chunkWCQVDF3K_js.__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 = chunkLWUFSWRA_js.createCachedDynamicImport_default(() => import('execa')); var CommandError = class _CommandError extends Error { static { chunkWCQVDF3K_js.__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; } } chunkWCQVDF3K_js.__name(runCommand, "runCommand"); async function startInterceptorServer({ hostname, port, logUnhandledRequests, tokensDirectory, ephemeral, onReady }) { const server = chunkLWUFSWRA_js.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); } } chunkWCQVDF3K_js.__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(); chunkLWUFSWRA_js.logger.info( `${ephemeral ? "Ephemeral s" : "S"}erver is running on ${color3__default.default.yellow(`${server.hostname}:${server.port}`)}` ); const isDangerouslyUnprotected = !tokensDirectory && process.env.NODE_ENV === "production"; if (isDangerouslyUnprotected) { chunkLWUFSWRA_js.logger.warn( [ `Attention: this interceptor server is ${color3__default.default.bold(color3__default.default.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); } } chunkWCQVDF3K_js.__name(startInterceptorServer, "startInterceptorServer"); var start_default = startInterceptorServer; async function createInterceptorServerToken({ tokenName, tokensDirectory }) { const token = await chunkLWUFSWRA_js.createInterceptorToken({ name: tokenName, tokensDirectory }); chunkLWUFSWRA_js.logger.info( [ `${color3__default.default.green(color3__default.default.bold("\u2714"))} Token${tokenName ? ` ${color3__default.default.green(tokenName)}` : ""} created:`, "", color3__default.default.yellow(token.value), "", "Store this token securely. It cannot be retrieved later.", "", `To enable authentication in your interceptor server, use the ${color3__default.default.cyan("--tokens-dir")} option. Only remote interceptors bearing a valid token will be allowed to connect.`, "", `${color3__default.default.dim("$")} zimic-interceptor server start ${color3__default.default.cyan("--tokens-dir")} ${color3__default.default.magenta(tokensDirectory)}`, "", "Learn more: https://zimic.dev/docs/interceptor/guides/http/remote-interceptors#interceptor-server-authentication" ].join("\n") ); } chunkWCQVDF3K_js.__name(createInterceptorServerToken, "createInterceptorServerToken"); // src/cli/server/token/list.ts async function listInterceptorServerTokens({ tokensDirectory }) { const tokens = await chunkLWUFSWRA_js.listInterceptorTokens({ tokensDirectory }); chunkLWUFSWRA_js.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() })) ); } chunkWCQVDF3K_js.__name(listInterceptorServerTokens, "listInterceptorServerTokens"); async function removeInterceptorServerToken({ tokenId, tokensDirectory }) { const token = await chunkLWUFSWRA_js.readInterceptorTokenFromFile(tokenId, { tokensDirectory }); if (!token) { chunkLWUFSWRA_js.logger.error(`${color3__default.default.red(color3__default.default.bold("\u2718"))} Token ${color3__default.default.red(tokenId)} not found.`); process.exit(1); } await chunkLWUFSWRA_js.removeInterceptorToken(token.id, { tokensDirectory }); chunkLWUFSWRA_js.logger.info(`${color3__default.default.green(color3__default.default.bold("\u2714"))} Token ${color3__default.default.green(token.name ?? token.id)} removed.`); } chunkWCQVDF3K_js.__name(removeInterceptorServerToken, "removeInterceptorServerToken"); // src/cli/cli.ts async function runCLI() { await yargs__default.default(helpers.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: chunkLWUFSWRA_js.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: chunkLWUFSWRA_js.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: chunkLWUFSWRA_js.DEFAULT_INTERCEPTOR_TOKENS_DIRECTORY }), async (cliArguments) => { await removeInterceptorServerToken({ tokenId: cliArguments.tokenId, tokensDirectory: cliArguments.tokensDir }); } ) ) ).parse(); } chunkWCQVDF3K_js.__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.js.map //# sourceMappingURL=cli.js.map