From 003a14469333499695fcc6dfe53fd43ca7edfba1 Mon Sep 17 00:00:00 2001 From: Matt Brophy Date: Wed, 24 Jun 2026 11:35:21 -0400 Subject: [PATCH 1/2] Use parseArgs in create-react-router (#15231) * Use parseArgs in create-react-router * Inline create-react-router context flags * Remove redundant version flag fallback --- .../.changes/patch.use-node-parse-args.md | 1 + .../__tests__/create-react-router-test.ts | 30 +++++ packages/create-react-router/index.ts | 116 +++++++++--------- packages/create-react-router/package.json | 1 - pnpm-lock.yaml | 3 - 5 files changed, 87 insertions(+), 64 deletions(-) create mode 100644 packages/create-react-router/.changes/patch.use-node-parse-args.md diff --git a/packages/create-react-router/.changes/patch.use-node-parse-args.md b/packages/create-react-router/.changes/patch.use-node-parse-args.md new file mode 100644 index 0000000000..bcaa6a4563 --- /dev/null +++ b/packages/create-react-router/.changes/patch.use-node-parse-args.md @@ -0,0 +1 @@ +Use Node's built-in `parseArgs` utility for CLI argument parsing and remove the `arg` dependency. diff --git a/packages/create-react-router/__tests__/create-react-router-test.ts b/packages/create-react-router/__tests__/create-react-router-test.ts index 2359a2f74a..8becab8462 100644 --- a/packages/create-react-router/__tests__/create-react-router-test.ts +++ b/packages/create-react-router/__tests__/create-react-router-test.ts @@ -148,6 +148,17 @@ describe("create-react-router CLI", () => { expect(!!semver.valid(stdout.trim())).toBe(true); }); + it("supports short aliases", async () => { + let [{ stdout: helpOutput }, { stdout: versionOutput }] = + await Promise.all([ + execCreateReactRouter({ args: ["-h"] }), + execCreateReactRouter({ args: ["-V"] }), + ]); + + expect(helpOutput).toContain("Usage:"); + expect(!!semver.valid(versionOutput.trim())).toBe(true); + }); + it("allows you to go through the prompts", async () => { let projectDir = getProjectDir("prompts"); @@ -195,6 +206,25 @@ describe("create-react-router CLI", () => { ).toBeTruthy(); }); + it("ignores unknown flags", async () => { + let projectDir = getProjectDir("unknown-flags"); + + let { status, stderr } = await execCreateReactRouter({ + args: [ + projectDir, + "--future-flag", + "--yes", + "--no-git-init", + "--no-install", + ], + }); + + expect(stderr.trim()).toBeFalsy(); + expect(status).toBe(0); + expect(existsSync(path.join(projectDir, "package.json"))).toBeTruthy(); + expect(existsSync(path.join(projectDir, "app/root.tsx"))).toBeTruthy(); + }); + it("supports the --no-agent-skills flag", async () => { let projectDir = getProjectDir("no-agent-skills"); diff --git a/packages/create-react-router/index.ts b/packages/create-react-router/index.ts index 6fa255d521..bfbf183dbd 100644 --- a/packages/create-react-router/index.ts +++ b/packages/create-react-router/index.ts @@ -4,9 +4,9 @@ import { cp, readFile, realpath, writeFile } from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import { fileURLToPath } from "node:url"; +import { parseArgs } from "node:util"; import stripAnsi from "strip-ansi"; import { execa } from "execa"; -import arg from "arg"; import * as semver from "semver"; import sortPackageJSON from "sort-package-json"; @@ -76,55 +76,45 @@ async function createReactRouter(argv: string[]) { } async function getContext(argv: string[]): Promise { - let flags = arg( - { - "--debug": Boolean, - "--react-router-version": String, - "-v": "--react-router-version", - "--template": String, - "--token": String, - "--yes": Boolean, - "-y": "--yes", - "--install": Boolean, - "--no-install": Boolean, - "--package-manager": String, - "--show-install-output": Boolean, - "--agent-skills": Boolean, - "--no-agent-skills": Boolean, - "--git-init": Boolean, - "--no-git-init": Boolean, - "--help": Boolean, - "-h": "--help", - "--version": Boolean, - "--V": "--version", - "--no-color": Boolean, - "--no-motion": Boolean, - "--overwrite": Boolean, + let { values, positionals } = parseArgs({ + args: argv, + allowPositionals: true, + // Preserve arg's permissive mode so unknown flags don't fail existing usage. + strict: false, + options: { + "agent-skills": { type: "boolean" }, + debug: { type: "boolean" }, + "git-init": { type: "boolean" }, + help: { type: "boolean", short: "h" }, + install: { type: "boolean" }, + "no-agent-skills": { type: "boolean" }, + "no-color": { type: "boolean" }, + "no-git-init": { type: "boolean" }, + "no-install": { type: "boolean" }, + "no-motion": { type: "boolean" }, + overwrite: { type: "boolean" }, + "package-manager": { type: "string" }, + "react-router-version": { type: "string", short: "v" }, + "show-install-output": { type: "boolean" }, + template: { type: "string" }, + token: { type: "string" }, + version: { type: "boolean", short: "V" }, + yes: { type: "boolean", short: "y" }, }, - { argv, permissive: true }, - ); + }); - let { - "--debug": debug = false, - "--help": help = false, - "--react-router-version": selectedReactRouterVersion, - "--template": template, - "--token": token, - "--install": install, - "--no-install": noInstall, - "--package-manager": pkgManager, - "--show-install-output": showInstallOutput = false, - "--agent-skills": agentSkills, - "--no-agent-skills": noAgentSkills, - "--git-init": git, - "--no-git-init": noGit, - "--no-motion": noMotion, - "--yes": yes, - "--version": versionRequested, - "--overwrite": overwrite, - } = flags; - - let cwd = flags["_"][0] as string; + let getBooleanArg = ( + value: string | boolean | Array | undefined, + ) => (typeof value === "boolean" ? value : undefined); + let getStringArg = ( + value: string | boolean | Array | undefined, + ) => (typeof value === "string" ? value : undefined); + + let selectedReactRouterVersion = getStringArg( + values["react-router-version"], + ); + let yes = getBooleanArg(values.yes); + let cwd = positionals[0] as string; let interactive = isInteractive(); let projectName = cwd; @@ -155,17 +145,23 @@ async function getContext(argv: string[]): Promise { `create-react-router--${Math.random().toString(36).substr(2, 8)}`, ), cwd, - overwrite, + overwrite: getBooleanArg(values.overwrite), interactive, - debug, - agentSkills: agentSkills ?? (noAgentSkills ? false : yes), - git: git ?? (noGit ? false : yes), - help, - install: install ?? (noInstall ? false : yes), - showInstallOutput, - noMotion, + debug: getBooleanArg(values.debug) ?? false, + agentSkills: + getBooleanArg(values["agent-skills"]) ?? + (getBooleanArg(values["no-agent-skills"]) ? false : yes), + git: + getBooleanArg(values["git-init"]) ?? + (getBooleanArg(values["no-git-init"]) ? false : yes), + help: getBooleanArg(values.help) ?? false, + install: + getBooleanArg(values.install) ?? + (getBooleanArg(values["no-install"]) ? false : yes), + showInstallOutput: getBooleanArg(values["show-install-output"]) ?? false, + noMotion: getBooleanArg(values["no-motion"]), pkgManager: validatePackageManager( - pkgManager ?? + getStringArg(values["package-manager"]) ?? // npm, pnpm, Yarn, Bun and Deno (v2.0.5+) set the user agent environment variable that can be used // to determine which package manager ran the command. (process.env.npm_config_user_agent ?? "npm").split("/")[0], @@ -173,9 +169,9 @@ async function getContext(argv: string[]): Promise { projectName, prompt, reactRouterVersion: selectedReactRouterVersion || pkgJson.version, - template, - token, - versionRequested, + template: getStringArg(values.template), + token: getStringArg(values.token), + versionRequested: getBooleanArg(values.version), }; return context; diff --git a/packages/create-react-router/package.json b/packages/create-react-router/package.json index cf3cb266d4..bec8dd197f 100644 --- a/packages/create-react-router/package.json +++ b/packages/create-react-router/package.json @@ -39,7 +39,6 @@ } }, "dependencies": { - "arg": "^5.0.1", "execa": "9.6.1", "gunzip-maybe": "^1.4.2", "log-update": "^8.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3809cc307d..d253fc3995 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -595,9 +595,6 @@ importers: packages/create-react-router: dependencies: - arg: - specifier: ^5.0.1 - version: 5.0.2 execa: specifier: 9.6.1 version: 9.6.1 From 59fdae90e5aa85054a4aae9a2dd389c8cb5e484c Mon Sep 17 00:00:00 2001 From: Remix Run Bot Date: Wed, 24 Jun 2026 15:35:58 +0000 Subject: [PATCH 2/2] chore: format --- .../__tests__/create-react-router-test.ts | 7 ++++--- packages/create-react-router/index.ts | 4 +--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/packages/create-react-router/__tests__/create-react-router-test.ts b/packages/create-react-router/__tests__/create-react-router-test.ts index 8becab8462..cedf9ecbab 100644 --- a/packages/create-react-router/__tests__/create-react-router-test.ts +++ b/packages/create-react-router/__tests__/create-react-router-test.ts @@ -149,11 +149,12 @@ describe("create-react-router CLI", () => { }); it("supports short aliases", async () => { - let [{ stdout: helpOutput }, { stdout: versionOutput }] = - await Promise.all([ + let [{ stdout: helpOutput }, { stdout: versionOutput }] = await Promise.all( + [ execCreateReactRouter({ args: ["-h"] }), execCreateReactRouter({ args: ["-V"] }), - ]); + ], + ); expect(helpOutput).toContain("Usage:"); expect(!!semver.valid(versionOutput.trim())).toBe(true); diff --git a/packages/create-react-router/index.ts b/packages/create-react-router/index.ts index bfbf183dbd..573ed66990 100644 --- a/packages/create-react-router/index.ts +++ b/packages/create-react-router/index.ts @@ -110,9 +110,7 @@ async function getContext(argv: string[]): Promise { value: string | boolean | Array | undefined, ) => (typeof value === "string" ? value : undefined); - let selectedReactRouterVersion = getStringArg( - values["react-router-version"], - ); + let selectedReactRouterVersion = getStringArg(values["react-router-version"]); let yes = getBooleanArg(values.yes); let cwd = positionals[0] as string; let interactive = isInteractive();