From d4d3062b3b90ae821a797f3287aaa8eb47da34ae Mon Sep 17 00:00:00 2001 From: emily-shen <69125074+emily-shen@users.noreply.github.com> Date: Fri, 15 Nov 2024 10:38:27 +0000 Subject: [PATCH] chore: defineCommand refactor for `wrangler dev` (#7249) * use defineCommand * fix types * move into validate * reorder * capitalisation * never using the web editor again --- packages/wrangler/src/__tests__/dev.test.ts | 8 +- packages/wrangler/src/dev.ts | 667 ++++++++++---------- packages/wrangler/src/index.ts | 9 +- 3 files changed, 337 insertions(+), 347 deletions(-) diff --git a/packages/wrangler/src/__tests__/dev.test.ts b/packages/wrangler/src/__tests__/dev.test.ts index b1e97a653da9..211c1176e8cf 100644 --- a/packages/wrangler/src/__tests__/dev.test.ts +++ b/packages/wrangler/src/__tests__/dev.test.ts @@ -1379,7 +1379,7 @@ describe.sequential("wrangler dev", () => { 👂 Start a local server for developing your Worker POSITIONALS - script The path to an entry point for your worker [string] + script The path to an entry point for your Worker [string] GLOBAL FLAGS -j, --experimental-json-config Experimental: support wrangler.json [boolean] @@ -1389,10 +1389,10 @@ describe.sequential("wrangler dev", () => { -v, --version Show version number [boolean] OPTIONS - --name Name of the worker [string] + --name Name of the Worker [string] --compatibility-date Date to use for compatibility checks [string] --compatibility-flags, --compatibility-flag Flags to use for compatibility checks [array] - --latest Use the latest version of the worker runtime [boolean] [default: true] + --latest Use the latest version of the Workers runtime [boolean] [default: true] --assets Static assets to be served. Replaces Workers Sites. [string] --no-bundle Skip internal build steps and directly deploy script [boolean] [default: false] --ip IP address to listen on [string] @@ -1418,7 +1418,7 @@ describe.sequential("wrangler dev", () => { --live-reload Auto reload HTML pages when change is detected in local mode [boolean] --test-scheduled Test scheduled events by visiting /__scheduled in browser [boolean] [default: false] --log-level Specify logging level [choices: \\"debug\\", \\"info\\", \\"log\\", \\"warn\\", \\"error\\", \\"none\\"] [default: \\"log\\"] - --show-interactive-dev-session Show interactive dev session (defaults to true if the terminal supports interactivity) [boolean] + --show-interactive-dev-session Show interactive dev session (defaults to true if the terminal supports interactivity) [boolean] --experimental-registry, --x-registry Use the experimental file based dev registry for multi-worker development [boolean] [default: false] --experimental-vectorize-bind-to-prod Bind to production Vectorize indexes in local development mode [boolean] [default: false]", "warn": "", diff --git a/packages/wrangler/src/dev.ts b/packages/wrangler/src/dev.ts index 2c9336ef4371..fe916df5b4b2 100644 --- a/packages/wrangler/src/dev.ts +++ b/packages/wrangler/src/dev.ts @@ -9,6 +9,7 @@ import { extractBindingsOfType, } from "./api/startDevWorker/utils"; import { findWranglerToml } from "./config"; +import { defineCommand } from "./core"; import { validateRoutes } from "./deploy/deploy"; import { validateNodeCompatMode } from "./deployment-bundle/node-compat"; import { devRegistry, getBoundRegisteredWorkers } from "./dev-registry"; @@ -28,7 +29,6 @@ import { } from "./utils/collectKeyValues"; import { mergeWithOverride } from "./utils/mergeWithOverride"; import { getHostFromRoute } from "./zones"; -import { printWranglerBanner } from "./index"; import type { ReloadCompleteEvent, StartDevWorkerInput, Trigger } from "./api"; import type { Config, Environment } from "./config"; import type { @@ -40,350 +40,343 @@ import type { CfModule, CfWorkerInit } from "./deployment-bundle/worker"; import type { WorkerRegistry } from "./dev-registry"; import type { LoggerLevel } from "./logger"; import type { EnablePagesAssetsServiceBindingOptions } from "./miniflare-cli/types"; -import type { - CommonYargsArgv, - StrictYargsOptionsToInterface, -} from "./yargs-types"; import type { watch } from "chokidar"; import type { Json } from "miniflare"; -export function devOptions(yargs: CommonYargsArgv) { - return ( - yargs - .positional("script", { - describe: "The path to an entry point for your worker", - type: "string", - }) - .option("name", { - describe: "Name of the worker", - type: "string", - requiresArg: true, - }) - .option("compatibility-date", { - describe: "Date to use for compatibility checks", - type: "string", - requiresArg: true, - }) - .option("compatibility-flags", { - describe: "Flags to use for compatibility checks", - alias: "compatibility-flag", - type: "string", - requiresArg: true, - array: true, - }) - .option("latest", { - describe: "Use the latest version of the worker runtime", - type: "boolean", - default: true, - }) - .option("assets", { - describe: "Static assets to be served. Replaces Workers Sites.", - type: "string", - requiresArg: true, - }) - // We want to have a --no-bundle flag, but yargs requires that - // we also have a --bundle flag (that it adds the --no to by itself) - // So we make a --bundle flag, but hide it, and then add a --no-bundle flag - // that's visible to the user but doesn't "do" anything. - .option("bundle", { - describe: "Run wrangler's compilation step before publishing", - type: "boolean", - hidden: true, - }) - .option("no-bundle", { - describe: "Skip internal build steps and directly deploy script", - type: "boolean", - default: false, - }) - .option("format", { - choices: ["modules", "service-worker"] as const, - describe: "Choose an entry type", - hidden: true, - deprecated: true, - }) - .option("ip", { - describe: "IP address to listen on", - type: "string", - }) - .option("port", { - describe: "Port to listen on", - type: "number", - }) - .option("inspector-port", { - describe: "Port for devtools to connect to", - type: "number", - }) - .option("routes", { - describe: "Routes to upload", - alias: "route", - type: "string", - requiresArg: true, - array: true, - }) - .option("host", { - type: "string", - requiresArg: true, - describe: - "Host to forward requests to, defaults to the zone of project", - }) - .option("local-protocol", { - describe: "Protocol to listen to requests on, defaults to http.", - choices: ["http", "https"] as const, - }) - .option("https-key-path", { - describe: "Path to a custom certificate key", - type: "string", - requiresArg: true, - }) - .option("https-cert-path", { - describe: "Path to a custom certificate", - type: "string", - requiresArg: true, - }) - .options("local-upstream", { - type: "string", - describe: - "Host to act as origin in local mode, defaults to dev.host or route", - }) - .option("experimental-public", { - describe: "(Deprecated) Static assets to be served", - type: "string", - requiresArg: true, - deprecated: true, - hidden: true, - }) - .option("legacy-assets", { - describe: "Static assets to be served", - type: "string", - requiresArg: true, - deprecated: true, - hidden: true, - }) - - .option("public", { - describe: "(Deprecated) Static assets to be served", - type: "string", - requiresArg: true, - deprecated: true, - hidden: true, - }) - .option("site", { - describe: "Root folder of static assets for Workers Sites", - type: "string", - requiresArg: true, - hidden: true, - deprecated: true, - }) - .option("site-include", { - describe: - "Array of .gitignore-style patterns that match file or directory names from the sites directory. Only matched items will be uploaded.", - type: "string", - requiresArg: true, - array: true, - hidden: true, - deprecated: true, - }) - .option("site-exclude", { - describe: - "Array of .gitignore-style patterns that match file or directory names from the sites directory. Matched items will not be uploaded.", - type: "string", - requiresArg: true, - array: true, - hidden: true, - deprecated: true, - }) - .option("upstream-protocol", { - describe: "Protocol to forward requests to host on, defaults to https.", - choices: ["http", "https"] as const, - }) - .option("var", { - describe: - "A key-value pair to be injected into the script as a variable", - type: "string", - requiresArg: true, - array: true, - }) - .option("define", { - describe: "A key-value pair to be substituted in the script", - type: "string", - requiresArg: true, - array: true, - }) - .option("alias", { - describe: "A module pair to be substituted in the script", - type: "string", - requiresArg: true, - array: true, - }) - .option("jsx-factory", { - describe: "The function that is called for each JSX element", - type: "string", - requiresArg: true, - }) - .option("jsx-fragment", { - describe: "The function that is called for each JSX fragment", - type: "string", - requiresArg: true, - }) - .option("tsconfig", { - describe: "Path to a custom tsconfig.json file", - type: "string", - requiresArg: true, - }) - .option("remote", { - alias: "r", - describe: - "Run on the global Cloudflare network with access to production resources", - type: "boolean", - default: false, - }) - .option("local", { - alias: "l", - describe: "Run on my machine", - type: "boolean", - deprecated: true, - hidden: true, - }) - .option("experimental-local", { - describe: "Run on my machine using the Cloudflare Workers runtime", - type: "boolean", - deprecated: true, - hidden: true, - }) - .option("minify", { - describe: "Minify the script", - type: "boolean", - }) - .option("node-compat", { - describe: "Enable Node.js compatibility", - type: "boolean", - }) - .option("experimental-enable-local-persistence", { - describe: - "Enable persistence for local mode (deprecated, use --persist)", - type: "boolean", - deprecated: true, - hidden: true, - }) - .option("persist-to", { - describe: - "Specify directory to use for local persistence (defaults to .wrangler/state)", - type: "string", - requiresArg: true, - }) - .option("live-reload", { - describe: - "Auto reload HTML pages when change is detected in local mode", - type: "boolean", - }) - .check((argv) => { - if (argv["live-reload"] && argv.remote) { - throw new UserError( - "--live-reload is only supported in local mode. Please just use one of either --remote or --live-reload." - ); - } - return true; - }) - .option("inspect", { - describe: "Enable dev tools", - type: "boolean", - deprecated: true, - hidden: true, - }) - .option("legacy-env", { - type: "boolean", - describe: "Use legacy environments", - hidden: true, - }) - .option("test-scheduled", { - describe: "Test scheduled events by visiting /__scheduled in browser", - type: "boolean", - default: false, - }) - .option("log-level", { - choices: ["debug", "info", "log", "warn", "error", "none"] as const, - describe: "Specify logging level", - // Yargs requires this to type log-level properly - default: "log" as LoggerLevel, - }) - .option("show-interactive-dev-session", { - describe: - "Show interactive dev session (defaults to true if the terminal supports interactivity)", - type: "boolean", - }) - .option("experimental-dev-env", { - alias: ["x-dev-env"], - type: "boolean", - deprecated: true, - hidden: true, - }) - .option("experimental-registry", { - alias: ["x-registry"], - type: "boolean", - describe: - "Use the experimental file based dev registry for multi-worker development", - default: false, - }) - .option("experimental-vectorize-bind-to-prod", { - type: "boolean", - describe: - "Bind to production Vectorize indexes in local development mode", - default: false, - }) - ); -} - -type DevArguments = StrictYargsOptionsToInterface; - -export async function devHandler(args: DevArguments) { - await printWranglerBanner(); +const command = defineCommand({ + command: "wrangler dev", + behaviour: { + printConfigWarnings: false, + }, + metadata: { + description: "👂 Start a local server for developing your Worker", + owner: "Workers: Authoring and Testing", + status: "stable", + }, + positionalArgs: ["script"], + args: { + script: { + describe: "The path to an entry point for your Worker", + type: "string", + }, + name: { + describe: "Name of the Worker", + type: "string", + requiresArg: true, + }, + "compatibility-date": { + describe: "Date to use for compatibility checks", + type: "string", + requiresArg: true, + }, + "compatibility-flags": { + describe: "Flags to use for compatibility checks", + alias: "compatibility-flag", + type: "string", + requiresArg: true, + array: true, + }, + latest: { + describe: "Use the latest version of the Workers runtime", + type: "boolean", + default: true, + }, + assets: { + describe: "Static assets to be served. Replaces Workers Sites.", + type: "string", + requiresArg: true, + }, + // We want to have a --no-bundle flag, but yargs requires that + // we also have a --bundle flag (that it adds the --no to by itself) + // So we make a --bundle flag, but hide it, and then add a --no-bundle flag + // that's visible to the user but doesn't "do" anything. + bundle: { + describe: "Run wrangler's compilation step before publishing", + type: "boolean", + hidden: true, + }, + "no-bundle": { + describe: "Skip internal build steps and directly deploy script", + type: "boolean", + default: false, + }, + format: { + choices: ["modules", "service-worker"] as const, + describe: "Choose an entry type", + hidden: true, + deprecated: true, + }, + ip: { + describe: "IP address to listen on", + type: "string", + }, + port: { + describe: "Port to listen on", + type: "number", + }, + "inspector-port": { + describe: "Port for devtools to connect to", + type: "number", + }, + routes: { + describe: "Routes to upload", + alias: "route", + type: "string", + requiresArg: true, + array: true, + }, + host: { + type: "string", + requiresArg: true, + describe: "Host to forward requests to, defaults to the zone of project", + }, + "local-protocol": { + describe: "Protocol to listen to requests on, defaults to http.", + choices: ["http", "https"] as const, + }, + "https-key-path": { + describe: "Path to a custom certificate key", + type: "string", + requiresArg: true, + }, + "https-cert-path": { + describe: "Path to a custom certificate", + type: "string", + requiresArg: true, + }, + "local-upstream": { + type: "string", + describe: + "Host to act as origin in local mode, defaults to dev.host or route", + }, + "experimental-public": { + describe: "(Deprecated) Static assets to be served", + type: "string", + requiresArg: true, + deprecated: true, + hidden: true, + }, + "legacy-assets": { + describe: "Static assets to be served", + type: "string", + requiresArg: true, + deprecated: true, + hidden: true, + }, + public: { + describe: "(Deprecated) Static assets to be served", + type: "string", + requiresArg: true, + deprecated: true, + hidden: true, + }, + site: { + describe: "Root folder of static assets for Workers Sites", + type: "string", + requiresArg: true, + hidden: true, + deprecated: true, + }, + "site-include": { + describe: + "Array of .gitignore-style patterns that match file or directory names from the sites directory. Only matched items will be uploaded.", + type: "string", + requiresArg: true, + array: true, + hidden: true, + deprecated: true, + }, + "site-exclude": { + describe: + "Array of .gitignore-style patterns that match file or directory names from the sites directory. Matched items will not be uploaded.", + type: "string", + requiresArg: true, + array: true, + hidden: true, + deprecated: true, + }, + "upstream-protocol": { + describe: "Protocol to forward requests to host on, defaults to https.", + choices: ["http", "https"] as const, + }, + var: { + describe: "A key-value pair to be injected into the script as a variable", + type: "string", + requiresArg: true, + array: true, + }, + define: { + describe: "A key-value pair to be substituted in the script", + type: "string", + requiresArg: true, + array: true, + }, + alias: { + describe: "A module pair to be substituted in the script", + type: "string", + requiresArg: true, + array: true, + }, + "jsx-factory": { + describe: "The function that is called for each JSX element", + type: "string", + requiresArg: true, + }, + "jsx-fragment": { + describe: "The function that is called for each JSX fragment", + type: "string", + requiresArg: true, + }, + tsconfig: { + describe: "Path to a custom tsconfig.json file", + type: "string", + requiresArg: true, + }, + remote: { + alias: "r", + describe: + "Run on the global Cloudflare network with access to production resources", + type: "boolean", + default: false, + }, + local: { + alias: "l", + describe: "Run on my machine", + type: "boolean", + deprecated: true, + hidden: true, + }, + "experimental-local": { + describe: "Run on my machine using the Cloudflare Workers runtime", + type: "boolean", + deprecated: true, + hidden: true, + }, + minify: { + describe: "Minify the script", + type: "boolean", + }, + "node-compat": { + describe: "Enable Node.js compatibility", + type: "boolean", + }, + "experimental-enable-local-persistence": { + describe: "Enable persistence for local mode (deprecated, use --persist)", + type: "boolean", + deprecated: true, + hidden: true, + }, + "persist-to": { + describe: + "Specify directory to use for local persistence (defaults to .wrangler/state)", + type: "string", + requiresArg: true, + }, + "live-reload": { + describe: "Auto reload HTML pages when change is detected in local mode", + type: "boolean", + }, + inspect: { + describe: "Enable dev tools", + type: "boolean", + deprecated: true, + hidden: true, + }, + "legacy-env": { + type: "boolean", + describe: "Use legacy environments", + hidden: true, + }, + "test-scheduled": { + describe: "Test scheduled events by visiting /__scheduled in browser", + type: "boolean", + default: false, + }, + "log-level": { + choices: ["debug", "info", "log", "warn", "error", "none"] as const, + describe: "Specify logging level", + // Yargs requires this to type log-level properly + default: "log" as LoggerLevel, + }, + "show-interactive-dev-session": { + describe: + "Show interactive dev session (defaults to true if the terminal supports interactivity)", + type: "boolean", + }, + "experimental-dev-env": { + alias: ["x-dev-env"], + type: "boolean", + deprecated: true, + hidden: true, + }, + "experimental-registry": { + alias: ["x-registry"], + type: "boolean", + describe: + "Use the experimental file based dev registry for multi-worker development", + default: false, + }, + "experimental-vectorize-bind-to-prod": { + type: "boolean", + describe: + "Bind to production Vectorize indexes in local development mode", + default: false, + }, + }, + async validateArgs(args) { + if (args.liveReload && args.remote) { + throw new UserError( + "--live-reload is only supported in local mode. Please just use one of either --remote or --live-reload." + ); + } + if (args.experimentalDevEnv) { + logger.warn( + "--x-dev-env is now on by default and will be removed in a future version." + ); + } - if (args.experimentalDevEnv) { - logger.warn( - "--x-dev-env is now on by default and will be removed in a future version." - ); - } + if (isWebContainer()) { + logger.error( + `Oh no! 😟 You tried to run \`wrangler dev\` in a StackBlitz WebContainer. 🤯 + This is currently not supported 😭, but we think that we'll get it to work soon... hang in there! 🥺` + ); + process.exitCode = 1; + return; + } - if (isWebContainer()) { - logger.error( - `Oh no! 😟 You tried to run \`wrangler dev\` in a StackBlitz WebContainer. 🤯 -This is currently not supported 😭, but we think that we'll get it to work soon... hang in there! 🥺` - ); - process.exitCode = 1; - return; - } + if (args.remote) { + const isLoggedIn = await loginOrRefreshIfRequired(); + if (!isLoggedIn) { + throw new UserError( + "You must be logged in to use wrangler dev in remote mode. Try logging in, or run wrangler dev --local." + ); + } + } - if (args.remote) { - const isLoggedIn = await loginOrRefreshIfRequired(); - if (!isLoggedIn) { - throw new UserError( - "You must be logged in to use wrangler dev in remote mode. Try logging in, or run wrangler dev --local." + if (args.legacyAssets) { + logger.warn( + `The --legacy-assets argument has been deprecated. Please use --assets instead.\n` + + `To learn more about Workers with assets, visit our documentation at https://developers.cloudflare.com/workers/frameworks/.` ); } - } - - if (args.legacyAssets) { - logger.warn( - `The --legacy-assets argument has been deprecated. Please use --assets instead.\n` + - `To learn more about Workers with assets, visit our documentation at https://developers.cloudflare.com/workers/frameworks/.` + }, + async handler(args) { + const devInstance = await run( + { + FILE_BASED_REGISTRY: args.experimentalRegistry, + JSON_CONFIG_FILE: Boolean(args.experimentalJsonConfig), + }, + () => startDev(args) ); - } - - const devInstance = await run( - { - FILE_BASED_REGISTRY: args.experimentalRegistry, - JSON_CONFIG_FILE: Boolean(args.experimentalJsonConfig), - }, - () => startDev(args) - ); - assert(devInstance.devEnv !== undefined); - await events.once(devInstance.devEnv, "teardown"); - if (devInstance.teardownRegistryPromise) { - const teardownRegistry = await devInstance.teardownRegistryPromise; - await teardownRegistry(devInstance.devEnv.config.latestConfig?.name); - } - devInstance.unregisterHotKeys?.(); -} + assert(devInstance.devEnv !== undefined); + await events.once(devInstance.devEnv, "teardown"); + if (devInstance.teardownRegistryPromise) { + const teardownRegistry = await devInstance.teardownRegistryPromise; + await teardownRegistry(devInstance.devEnv.config.latestConfig?.name); + } + devInstance.unregisterHotKeys?.(); + }, +}); export type AdditionalDevProps = { vars?: Record; @@ -424,6 +417,8 @@ export type AdditionalDevProps = { showInteractiveDevSession?: boolean; }; +type DevArguments = (typeof command)["args"]; + export type StartDevOptions = DevArguments & // These options can be passed in directly when called with the `wrangler.dev()` API. // They aren't exposed as CLI arguments. diff --git a/packages/wrangler/src/index.ts b/packages/wrangler/src/index.ts index aea5058bf278..53c9cfb2dcbe 100644 --- a/packages/wrangler/src/index.ts +++ b/packages/wrangler/src/index.ts @@ -35,7 +35,6 @@ import { subdomainHandler, subdomainOptions, } from "./deprecated"; -import { devHandler, devOptions } from "./dev"; import { workerNamespaceCommands } from "./dispatch-namespace"; import { CommandLineArgsError, @@ -46,6 +45,7 @@ import { generateHandler, generateOptions } from "./generate"; import { hyperdrive } from "./hyperdrive/index"; import { initHandler, initOptions } from "./init"; import "./docs"; +import "./dev"; import "./kv"; import "./workflows"; import "./user/commands"; @@ -329,12 +329,7 @@ export function createCLIParser(argv: string[]) { ); // dev - wrangler.command( - "dev [script]", - "👂 Start a local server for developing your Worker", - devOptions, - devHandler - ); + register.registerNamespace("dev"); // deploy wrangler.command(