diff --git a/.changeset/cli-command-module.md b/.changeset/cli-command-module.md new file mode 100644 index 0000000000..9eaf03cc41 --- /dev/null +++ b/.changeset/cli-command-module.md @@ -0,0 +1,7 @@ +--- +"@cloudflare/cli": minor +--- + +Add `runCommand` and `quoteShellArgs`, `installPackages` and `installWrangler` utilities to `@cloudflare/cli/command` + +These utilities are now available from `@cloudflare/cli` as dedicated sub-path exports: `runCommand` and `quoteShellArgs` via `@cloudflare/cli/command`, and `installPackages` and `installWrangler` via `@cloudflare/cli/packages`. This makes them reusable across packages in the SDK without duplication. diff --git a/packages/cli/__tests__/command.test.ts b/packages/cli/__tests__/command.test.ts new file mode 100644 index 0000000000..5491069479 --- /dev/null +++ b/packages/cli/__tests__/command.test.ts @@ -0,0 +1,72 @@ +import { spawn } from "cross-spawn"; +import { afterEach, beforeEach, describe, test, vi } from "vitest"; +import { quoteShellArgs, runCommand } from "../command"; +import type { ChildProcess } from "node:child_process"; + +// We can change how the mock spawn works by setting these variables +let spawnResultCode = 0; +let spawnStdout: string | undefined = undefined; +let spawnStderr: string | undefined = undefined; + +vi.mock("cross-spawn"); + +describe("Command Helpers", () => { + afterEach(() => { + spawnResultCode = 0; + spawnStdout = undefined; + spawnStderr = undefined; + }); + + beforeEach(() => { + vi.mocked(spawn).mockImplementation(() => { + return { + on: vi.fn().mockImplementation((event, cb) => { + if (event === "close") { + cb(spawnResultCode); + } + }), + stdout: { + on(_event: "data", cb: (data: string) => void) { + if (spawnStdout !== undefined) { + cb(spawnStdout); + } + }, + }, + stderr: { + on(_event: "data", cb: (data: string) => void) { + if (spawnStderr !== undefined) { + cb(spawnStderr); + } + }, + }, + } as unknown as ChildProcess; + }); + }); + + test("runCommand", async ({ expect }) => { + await runCommand(["ls", "-l"]); + expect(spawn).toHaveBeenCalledWith("ls", ["-l"], { + stdio: "inherit", + env: process.env, + signal: expect.any(AbortSignal), + }); + }); + + describe("quoteShellArgs", () => { + test.runIf(process.platform !== "win32")("mac", async ({ expect }) => { + expect(quoteShellArgs([`pages:dev`])).toEqual("pages:dev"); + expect(quoteShellArgs([`24.02 foo-bar`])).toEqual(`'24.02 foo-bar'`); + expect(quoteShellArgs([`foo/10 bar/20-baz/`])).toEqual( + `'foo/10 bar/20-baz/'` + ); + }); + + test.runIf(process.platform === "win32")("windows", async ({ expect }) => { + expect(quoteShellArgs([`pages:dev`])).toEqual("pages:dev"); + expect(quoteShellArgs([`24.02 foo-bar`])).toEqual(`"24.02 foo-bar"`); + expect(quoteShellArgs([`foo/10 bar/20-baz/`])).toEqual( + `"foo/10 bar/20-baz/"` + ); + }); + }); +}); diff --git a/packages/cli/__tests__/packages.test.ts b/packages/cli/__tests__/packages.test.ts new file mode 100644 index 0000000000..b55d404f52 --- /dev/null +++ b/packages/cli/__tests__/packages.test.ts @@ -0,0 +1,130 @@ +import { writeFile } from "node:fs/promises"; +import { resolve } from "node:path"; +import { parsePackageJSON, readFileSync } from "@cloudflare/workers-utils"; +import { afterEach, describe, test, vi } from "vitest"; +import { runCommand } from "../command"; +import { installPackages, installWrangler } from "../packages"; + +vi.mock("../command"); +vi.mock("@cloudflare/workers-utils", () => ({ + readFileSync: vi.fn(), + parsePackageJSON: vi.fn(), +})); +vi.mock("node:fs/promises", () => ({ + writeFile: vi.fn(), +})); + +describe("Package Helpers", () => { + afterEach(() => { + vi.clearAllMocks(); + }); + + describe("installPackages", async () => { + type TestCase = { + pm: "npm" | "pnpm" | "yarn" | "bun"; + initialArgs: string[]; + additionalArgs?: string[]; + }; + + const cases: TestCase[] = [ + { pm: "npm", initialArgs: ["npm", "install"] }, + { pm: "pnpm", initialArgs: ["pnpm", "install"] }, + { pm: "bun", initialArgs: ["bun", "add"] }, + { pm: "yarn", initialArgs: ["yarn", "add"] }, + ]; + + test.for(cases)( + "with $pm", + async ({ pm, initialArgs, additionalArgs }, { expect }) => { + const mockPkgJson = { + dependencies: { + foo: "^1.0.0", + bar: "^2.0.0", + baz: "^1.2.3", + }, + }; + vi.mocked(readFileSync).mockReturnValue(JSON.stringify(mockPkgJson)); + vi.mocked(parsePackageJSON).mockReturnValue(mockPkgJson); + + const packages = ["foo", "bar@latest", "baz@1.2.3"]; + await installPackages(pm, packages); + + expect(vi.mocked(runCommand)).toHaveBeenCalledWith( + [...initialArgs, ...packages, ...(additionalArgs ?? [])], + expect.anything() + ); + + if (pm === "npm") { + const writeFileCall = vi.mocked(writeFile).mock.calls[0]; + expect(writeFileCall[0]).toBe(resolve(process.cwd(), "package.json")); + expect(JSON.parse(writeFileCall[1] as string)).toMatchObject({ + dependencies: { + foo: "^1.0.0", + bar: "^2.0.0", + baz: "1.2.3", + }, + }); + } + } + ); + + const devCases: TestCase[] = [ + { pm: "npm", initialArgs: ["npm", "install", "--save-dev"] }, + { pm: "pnpm", initialArgs: ["pnpm", "install", "--save-dev"] }, + { pm: "bun", initialArgs: ["bun", "add", "-d"] }, + { pm: "yarn", initialArgs: ["yarn", "add", "-D"] }, + ]; + + test.for(devCases)( + "with $pm (dev = true)", + async ({ pm, initialArgs, additionalArgs }, { expect }) => { + const mockPkgJson = { + devDependencies: { + foo: "^1.0.0", + bar: "^2.0.0", + baz: "^1.2.3", + }, + }; + vi.mocked(readFileSync).mockReturnValue(JSON.stringify(mockPkgJson)); + vi.mocked(parsePackageJSON).mockReturnValue(mockPkgJson); + + const packages = ["foo", "bar@latest", "baz@1.2.3"]; + await installPackages(pm, packages, { dev: true }); + + expect(vi.mocked(runCommand)).toHaveBeenCalledWith( + [...initialArgs, ...packages, ...(additionalArgs ?? [])], + expect.anything() + ); + + if (pm === "npm") { + const writeFileCall = vi.mocked(writeFile).mock.calls[0]; + expect(writeFileCall[0]).toBe(resolve(process.cwd(), "package.json")); + expect(JSON.parse(writeFileCall[1] as string)).toMatchObject({ + devDependencies: { + foo: "^1.0.0", + bar: "^2.0.0", + baz: "1.2.3", + }, + }); + } + } + ); + }); + + test("installWrangler", async ({ expect }) => { + const mockPkgJson = { + devDependencies: { + wrangler: "^4.0.0", + }, + }; + vi.mocked(readFileSync).mockReturnValue(JSON.stringify(mockPkgJson)); + vi.mocked(parsePackageJSON).mockReturnValue(mockPkgJson); + + await installWrangler("npm", false); + + expect(vi.mocked(runCommand)).toHaveBeenCalledWith( + ["npm", "install", "--save-dev", "wrangler@latest"], + expect.anything() + ); + }); +}); diff --git a/packages/wrangler/src/autoconfig/c3-vendor/command.ts b/packages/cli/command.ts similarity index 96% rename from packages/wrangler/src/autoconfig/c3-vendor/command.ts rename to packages/cli/command.ts index 7f61ea4e73..e882dac00a 100644 --- a/packages/wrangler/src/autoconfig/c3-vendor/command.ts +++ b/packages/cli/command.ts @@ -1,14 +1,14 @@ -import { stripAnsi } from "@cloudflare/cli"; -import { CancelError } from "@cloudflare/cli/error"; -import { isInteractive, spinner } from "@cloudflare/cli/interactive"; import { spawn } from "cross-spawn"; +import { CancelError } from "./error"; +import { isInteractive, spinner } from "./interactive"; +import { stripAnsi } from "."; /** * Command is a string array, like ['git', 'commit', '-m', '"Initial commit"'] */ type Command = string[]; -type RunOptions = { +export type RunOptions = { startText?: string; doneText?: string | ((output: string) => string); silent?: boolean; diff --git a/packages/cli/package.json b/packages/cli/package.json index 576502752d..39e1691bfa 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -28,8 +28,12 @@ "@clack/core": "^0.3.2", "@cloudflare/workers-tsconfig": "workspace:*", "@cloudflare/workers-utils": "workspace:*", + "@types/cross-spawn": "^6.0.2", + "@types/which-pm-runs": "^1.0.0", "chalk": "^5.2.0", - "log-update": "^5.0.1" + "cross-spawn": "^7.0.3", + "log-update": "^5.0.1", + "which-pm-runs": "^2.0.0" }, "volta": { "extends": "../../package.json" diff --git a/packages/wrangler/src/autoconfig/c3-vendor/packages.ts b/packages/cli/packages.ts similarity index 82% rename from packages/wrangler/src/autoconfig/c3-vendor/packages.ts rename to packages/cli/packages.ts index 6eedbc716b..50e321f92a 100644 --- a/packages/wrangler/src/autoconfig/c3-vendor/packages.ts +++ b/packages/cli/packages.ts @@ -1,10 +1,9 @@ import assert from "node:assert"; import { writeFile } from "node:fs/promises"; import path from "node:path"; -import { brandColor, dim } from "@cloudflare/cli/colors"; import { parsePackageJSON, readFileSync } from "@cloudflare/workers-utils"; +import { brandColor, dim } from "./colors"; import { runCommand } from "./command"; -import type { PackageManager } from "../../package-manager"; type InstallConfig = { startText?: string; @@ -25,17 +24,16 @@ type InstallConfig = { * @param config.force - Whether to install with `--force` or not */ export const installPackages = async ( - packageManager: PackageManager, + packageManager: "npm" | "pnpm" | "yarn" | "bun", packages: string[], config: InstallConfig = {} ) => { - const { type } = packageManager; const { force, dev, startText, doneText } = config; const isWorkspaceRoot = config.isWorkspaceRoot ?? false; if (packages.length === 0) { let cmd; - switch (type) { + switch (packageManager) { case "yarn": break; case "npm": @@ -47,12 +45,12 @@ export const installPackages = async ( await runCommand( [ - type, + packageManager, ...(cmd ? [cmd] : []), ...packages, - ...(type === "pnpm" ? ["--no-frozen-lockfile"] : []), + ...(packageManager === "pnpm" ? ["--no-frozen-lockfile"] : []), ...(force === true ? ["--force"] : []), - ...getWorkspaceInstallRootFlag(type, isWorkspaceRoot), + ...getWorkspaceInstallRootFlag(packageManager, isWorkspaceRoot), ], { cwd: process.cwd(), @@ -66,11 +64,15 @@ export const installPackages = async ( let saveFlag; let cmd; - switch (type) { + switch (packageManager) { case "yarn": cmd = "add"; saveFlag = dev ? "-D" : ""; break; + case "bun": + cmd = "add"; + saveFlag = dev ? "-d" : ""; + break; case "npm": case "pnpm": default: @@ -80,12 +82,12 @@ export const installPackages = async ( } await runCommand( [ - type, + packageManager, cmd, ...(saveFlag ? [saveFlag] : []), ...packages, ...(force === true ? ["--force"] : []), - ...getWorkspaceInstallRootFlag(type, isWorkspaceRoot), + ...getWorkspaceInstallRootFlag(packageManager, isWorkspaceRoot), ], { startText, @@ -94,7 +96,7 @@ export const installPackages = async ( } ); - if (type === "npm") { + if (packageManager === "npm") { // Npm install will update the package.json with a caret-range rather than the exact version/range we asked for. // We can't use `npm install --save-exact` because that always pins to an exact version, and we want to allow ranges too. // So let's just fix that up now by rewriting the package.json. @@ -121,19 +123,19 @@ export const installPackages = async ( * Returns the potential flag(/s) that need to be added to a package manager's install command when it is * run at the root of a workspace. * - * @param packageManagerType The type of package manager + * @param packageManager The type of package manager * @param isWorkspaceRoot Flag indicating whether the install command is being run at the root of a workspace * @returns an array containing the flag(/s) to use, or an empty array if not supported or not running in the workspace root. */ -const getWorkspaceInstallRootFlag = ( - packageManagerType: PackageManager["type"], +function getWorkspaceInstallRootFlag( + packageManager: "npm" | "pnpm" | "yarn" | "bun", isWorkspaceRoot: boolean -): string[] => { +): string[] { if (!isWorkspaceRoot) { return []; } - switch (packageManagerType) { + switch (packageManager) { case "pnpm": return ["--workspace-root"]; case "yarn": @@ -143,17 +145,15 @@ const getWorkspaceInstallRootFlag = ( // npm and bun don't have the workspace check return []; } -}; +} /** * Installs the latest version of wrangler in the project directory if it isn't already. */ -export const installWrangler = async ( - packageManager: PackageManager, +export async function installWrangler( + packageManager: "npm" | "pnpm" | "yarn" | "bun", isWorkspaceRoot: boolean -) => { - const { type } = packageManager; - +) { // Even if Wrangler is already installed, make sure we install the latest version, as some framework CLIs are pinned to an older version await installPackages(packageManager, [`wrangler@latest`], { dev: true, @@ -162,7 +162,7 @@ export const installWrangler = async ( "A command line tool for building Cloudflare Workers" )}`, doneText: `${brandColor("installed")} ${dim( - `via \`${type} install wrangler --save-dev\`` + `via \`${packageManager} install wrangler --save-dev\`` )}`, }); -}; +} diff --git a/packages/create-cloudflare/e2e/helpers/framework-helpers.ts b/packages/create-cloudflare/e2e/helpers/framework-helpers.ts index 71bce887e1..e550fee307 100644 --- a/packages/create-cloudflare/e2e/helpers/framework-helpers.ts +++ b/packages/create-cloudflare/e2e/helpers/framework-helpers.ts @@ -2,8 +2,8 @@ import assert from "node:assert"; import { existsSync } from "node:fs"; import { join } from "node:path"; import { setTimeout } from "node:timers/promises"; +import { runCommand } from "@cloudflare/cli/command"; import getPort from "get-port"; -import { runCommand } from "helpers/command"; import { readFile, readJSON, diff --git a/packages/create-cloudflare/src/__tests__/deploy.test.ts b/packages/create-cloudflare/src/__tests__/deploy.test.ts index 0b6fa9917b..2b41f885ba 100644 --- a/packages/create-cloudflare/src/__tests__/deploy.test.ts +++ b/packages/create-cloudflare/src/__tests__/deploy.test.ts @@ -1,13 +1,13 @@ +import { runCommand } from "@cloudflare/cli/command"; import { inputPrompt } from "@cloudflare/cli/interactive"; import { mockPackageManager, mockSpinner } from "helpers/__tests__/mocks"; -import { runCommand } from "helpers/command"; import { readFile } from "helpers/files"; import { beforeEach, describe, test, vi } from "vitest"; import { offerToDeploy, runDeploy } from "../deploy"; import { chooseAccount, wranglerLogin } from "../wrangler/accounts"; import { createTestContext } from "./helpers"; -vi.mock("helpers/command"); +vi.mock("@cloudflare/cli/command"); vi.mock("../wrangler/accounts"); vi.mock("@cloudflare/cli/interactive"); vi.mock("which-pm-runs"); diff --git a/packages/create-cloudflare/src/__tests__/git.test.ts b/packages/create-cloudflare/src/__tests__/git.test.ts index 57ad451026..9b313d5500 100644 --- a/packages/create-cloudflare/src/__tests__/git.test.ts +++ b/packages/create-cloudflare/src/__tests__/git.test.ts @@ -1,7 +1,7 @@ import { updateStatus } from "@cloudflare/cli"; +import { runCommand } from "@cloudflare/cli/command"; import { mockSpinner } from "helpers/__tests__/mocks"; import { processArgument } from "helpers/args"; -import { runCommand } from "helpers/command"; import { beforeEach, describe, test, vi } from "vitest"; import { getProductionBranch, @@ -14,7 +14,7 @@ import { } from "../git"; import { createTestContext } from "./helpers"; -vi.mock("helpers/command"); +vi.mock("@cloudflare/cli/command"); vi.mock("helpers/args"); vi.mock("@cloudflare/cli/interactive"); vi.mock("@cloudflare/cli"); diff --git a/packages/create-cloudflare/src/cli.ts b/packages/create-cloudflare/src/cli.ts index ee4aad8ff2..132c0d97df 100644 --- a/packages/create-cloudflare/src/cli.ts +++ b/packages/create-cloudflare/src/cli.ts @@ -10,11 +10,12 @@ import { logRaw, startSection, } from "@cloudflare/cli"; +import { runCommand } from "@cloudflare/cli/command"; import { CancelError } from "@cloudflare/cli/error"; import { isInteractive } from "@cloudflare/cli/interactive"; import { cliDefinition, parseArgs, processArgument } from "helpers/args"; import { C3_DEFAULTS, isUpdateAvailable } from "helpers/cli"; -import { runCommand } from "helpers/command"; +import { runWranglerCommand } from "helpers/command"; import { detectPackageManager, rectifyPmMismatch, @@ -176,7 +177,7 @@ const configure = async (ctx: C3Context) => { if (ctx.args.experimental) { const { npx } = detectPackageManager(); - await runCommand([ + await runWranglerCommand([ npx, "wrangler", "setup", diff --git a/packages/create-cloudflare/src/deploy.ts b/packages/create-cloudflare/src/deploy.ts index 302663afc1..9ed5d00259 100644 --- a/packages/create-cloudflare/src/deploy.ts +++ b/packages/create-cloudflare/src/deploy.ts @@ -3,9 +3,9 @@ import { tmpdir } from "node:os"; import { join } from "node:path"; import { startSection, updateStatus } from "@cloudflare/cli"; import { blue, brandColor, dim } from "@cloudflare/cli/colors"; +import { quoteShellArgs, runCommand } from "@cloudflare/cli/command"; import { processArgument } from "helpers/args"; import { C3_DEFAULTS, openInBrowser } from "helpers/cli"; -import { quoteShellArgs, runCommand } from "helpers/command"; import { readFile } from "helpers/files"; import { detectPackageManager } from "helpers/packageManagers"; import { poll } from "helpers/poll"; diff --git a/packages/create-cloudflare/src/dialog.ts b/packages/create-cloudflare/src/dialog.ts index 8fcc18ddb4..44d8012190 100644 --- a/packages/create-cloudflare/src/dialog.ts +++ b/packages/create-cloudflare/src/dialog.ts @@ -1,7 +1,7 @@ import { relative } from "node:path"; import { hyperlink, logRaw, shapes, stripAnsi } from "@cloudflare/cli"; import { bgGreen, blue, gray } from "@cloudflare/cli/colors"; -import { quoteShellArgs } from "helpers/command"; +import { quoteShellArgs } from "@cloudflare/cli/command"; import { detectPackageManager } from "helpers/packageManagers"; import type { C3Args, C3Context } from "types"; diff --git a/packages/create-cloudflare/src/frameworks/__tests__/index.test.ts b/packages/create-cloudflare/src/frameworks/__tests__/index.test.ts index a53893cdfd..f81bcb34ca 100644 --- a/packages/create-cloudflare/src/frameworks/__tests__/index.test.ts +++ b/packages/create-cloudflare/src/frameworks/__tests__/index.test.ts @@ -1,11 +1,11 @@ +import { runCommand } from "@cloudflare/cli/command"; import { mockPackageManager } from "helpers/__tests__/mocks"; -import { runCommand } from "helpers/command"; import { describe, test, vi } from "vitest"; import { getFrameworkCli, runFrameworkGenerator } from ".."; import { createTestContext } from "../../__tests__/helpers"; vi.mock("which-pm-runs"); -vi.mock("helpers/command"); +vi.mock("@cloudflare/cli/command"); vi.mock("@cloudflare/cli"); describe("frameworks", () => { diff --git a/packages/create-cloudflare/src/frameworks/index.ts b/packages/create-cloudflare/src/frameworks/index.ts index 943681aadf..71f166b408 100644 --- a/packages/create-cloudflare/src/frameworks/index.ts +++ b/packages/create-cloudflare/src/frameworks/index.ts @@ -1,6 +1,6 @@ import { logRaw, updateStatus } from "@cloudflare/cli"; import { dim } from "@cloudflare/cli/colors"; -import { quoteShellArgs, runCommand } from "helpers/command"; +import { quoteShellArgs, runCommand } from "@cloudflare/cli/command"; import { detectPackageManager } from "helpers/packageManagers"; import frameworksPackageJson from "./package.json"; import type { C3Context } from "types"; diff --git a/packages/create-cloudflare/src/git.ts b/packages/create-cloudflare/src/git.ts index 5a60aaafd4..5db43ad398 100644 --- a/packages/create-cloudflare/src/git.ts +++ b/packages/create-cloudflare/src/git.ts @@ -1,11 +1,11 @@ import assert from "node:assert"; import { updateStatus } from "@cloudflare/cli"; import { brandColor, dim } from "@cloudflare/cli/colors"; +import { runCommand } from "@cloudflare/cli/command"; import { spinner } from "@cloudflare/cli/interactive"; import { getFrameworkCli } from "frameworks/index"; import { processArgument } from "helpers/args"; import { C3_DEFAULTS } from "helpers/cli"; -import { runCommand } from "helpers/command"; import { detectPackageManager } from "helpers/packageManagers"; import { version as wranglerVersion } from "wrangler/package.json"; import { version } from "../package.json"; diff --git a/packages/create-cloudflare/src/helpers/__tests__/command.test.ts b/packages/create-cloudflare/src/helpers/__tests__/command.test.ts index 18b9ef7091..5c6ad826ce 100644 --- a/packages/create-cloudflare/src/helpers/__tests__/command.test.ts +++ b/packages/create-cloudflare/src/helpers/__tests__/command.test.ts @@ -1,26 +1,17 @@ -import { existsSync } from "node:fs"; import { spawn } from "cross-spawn"; import { readMetricsConfig } from "helpers/metrics-config"; import { afterEach, beforeEach, describe, test, vi } from "vitest"; -import whichPMRuns from "which-pm-runs"; -import { quoteShellArgs, runCommand } from "../command"; +import { runWranglerCommand } from "../command"; import type { ChildProcess } from "node:child_process"; -// We can change how the mock spawn works by setting these variables let spawnResultCode = 0; -let spawnStdout: string | undefined = undefined; -let spawnStderr: string | undefined = undefined; vi.mock("cross-spawn"); -vi.mock("fs"); -vi.mock("which-pm-runs"); vi.mock("helpers/metrics-config"); -describe("Command Helpers", () => { +describe("runWranglerCommand", () => { afterEach(() => { spawnResultCode = 0; - spawnStdout = undefined; - spawnStderr = undefined; }); beforeEach(() => { @@ -32,112 +23,81 @@ describe("Command Helpers", () => { } }), stdout: { - on(_event: "data", cb: (data: string) => void) { - if (spawnStdout !== undefined) { - cb(spawnStdout); - } - }, + on(_event: "data", _cb: (data: string) => void) {}, }, stderr: { - on(_event: "data", cb: (data: string) => void) { - if (spawnStderr !== undefined) { - cb(spawnStderr); - } - }, + on(_event: "data", _cb: (data: string) => void) {}, }, } as unknown as ChildProcess; }); - - vi.mocked(whichPMRuns).mockReturnValue({ name: "npm", version: "8.3.1" }); - vi.mocked(existsSync).mockImplementation(() => false); }); - test("runCommand", async ({ expect }) => { - await runCommand(["ls", "-l"]); - expect(spawn).toHaveBeenCalledWith("ls", ["-l"], { - stdio: "inherit", - env: process.env, - signal: expect.any(AbortSignal), + test("has WRANGLER_SEND_METRICS=false when c3 telemetry is disabled", async ({ + expect, + }) => { + vi.mocked(readMetricsConfig).mockReturnValue({ + c3permission: { + enabled: false, + date: new Date(2000, 0, 1), + }, }); - }); + await runWranglerCommand(["npx", "wrangler", "login"]); - describe("respect telemetry permissions when running wrangler", () => { - test("runCommand has WRANGLER_SEND_METRICS=false if its a wrangler command and c3 telemetry is disabled", async ({ - expect, - }) => { - vi.mocked(readMetricsConfig).mockReturnValue({ - c3permission: { - enabled: false, - date: new Date(2000), - }, - }); - await runCommand(["npx", "wrangler"]); - - expect(spawn).toHaveBeenCalledWith( - "npx", - ["wrangler"], - expect.objectContaining({ - env: expect.objectContaining({ WRANGLER_SEND_METRICS: "false" }), - }) - ); - }); - - test("runCommand doesn't have WRANGLER_SEND_METRICS=false if its a wrangler command and c3 telemetry is enabled", async ({ - expect, - }) => { - vi.mocked(readMetricsConfig).mockReturnValue({ - c3permission: { - enabled: true, - date: new Date(2000), - }, - }); - await runCommand(["npx", "wrangler"]); + expect(spawn).toHaveBeenCalledWith( + "npx", + ["wrangler", "login"], + expect.objectContaining({ + env: expect.objectContaining({ + WRANGLER_SEND_METRICS: "false", + WRANGLER_HIDE_BANNER: "true", + }), + }) + ); + }); - expect(spawn).toHaveBeenCalledWith( - "npx", - ["wrangler"], - expect.objectContaining({ - env: expect.not.objectContaining({ WRANGLER_SEND_METRICS: "false" }), - }) - ); + test("doesn't have WRANGLER_SEND_METRICS=false when c3 telemetry is enabled", async ({ + expect, + }) => { + vi.mocked(readMetricsConfig).mockReturnValue({ + c3permission: { + enabled: true, + date: new Date(2000, 0, 1), + }, }); + await runWranglerCommand(["npx", "wrangler", "login"]); - test("runCommand doesn't have WRANGLER_SEND_METRICS=false if not a wrangler command", async ({ - expect, - }) => { - vi.mocked(readMetricsConfig).mockReturnValue({ - c3permission: { - enabled: false, - date: new Date(2000), - }, - }); - await runCommand(["ls", "-l"]); - - expect(spawn).toHaveBeenCalledWith( - "ls", - ["-l"], - expect.objectContaining({ - env: expect.not.objectContaining({ WRANGLER_SEND_METRICS: "false" }), - }) - ); - }); + expect(spawn).toHaveBeenCalledWith( + "npx", + ["wrangler", "login"], + expect.objectContaining({ + env: expect.objectContaining({ + WRANGLER_HIDE_BANNER: "true", + }), + }) + ); + expect(spawn).toHaveBeenCalledWith( + "npx", + ["wrangler", "login"], + expect.objectContaining({ + env: expect.not.objectContaining({ + WRANGLER_SEND_METRICS: "false", + }), + }) + ); }); - describe("quoteShellArgs", () => { - test.runIf(process.platform !== "win32")("mac", async ({ expect }) => { - expect(quoteShellArgs([`pages:dev`])).toEqual("pages:dev"); - expect(quoteShellArgs([`24.02 foo-bar`])).toEqual(`'24.02 foo-bar'`); - expect(quoteShellArgs([`foo/10 bar/20-baz/`])).toEqual( - `'foo/10 bar/20-baz/'` - ); - }); + test("always has WRANGLER_HIDE_BANNER=true", async ({ expect }) => { + vi.mocked(readMetricsConfig).mockReturnValue({}); + await runWranglerCommand(["npx", "wrangler", "whoami"]); - test.runIf(process.platform === "win32")("windows", async ({ expect }) => { - expect(quoteShellArgs([`pages:dev`])).toEqual("pages:dev"); - expect(quoteShellArgs([`24.02 foo-bar`])).toEqual(`"24.02 foo-bar"`); - expect(quoteShellArgs([`foo/10 bar/20-baz/`])).toEqual( - `"foo/10 bar/20-baz/"` - ); - }); + expect(spawn).toHaveBeenCalledWith( + "npx", + ["wrangler", "whoami"], + expect.objectContaining({ + env: expect.objectContaining({ + WRANGLER_HIDE_BANNER: "true", + }), + }) + ); }); }); diff --git a/packages/create-cloudflare/src/helpers/__tests__/packages.test.ts b/packages/create-cloudflare/src/helpers/__tests__/packages.test.ts deleted file mode 100644 index e6963d4284..0000000000 --- a/packages/create-cloudflare/src/helpers/__tests__/packages.test.ts +++ /dev/null @@ -1,164 +0,0 @@ -import { existsSync } from "node:fs"; -import { resolve } from "node:path"; -import { runCommand } from "helpers/command"; -import { installPackages, installWrangler, npmInstall } from "helpers/packages"; -import { afterEach, beforeEach, describe, test, vi } from "vitest"; -import whichPMRuns from "which-pm-runs"; -import { createTestContext } from "../../__tests__/helpers"; -import * as files from "../files"; -import { mockPackageManager } from "./mocks"; - -vi.mock("fs"); -vi.mock("which-pm-runs"); -vi.mock("which-pm-runs"); -vi.mock("helpers/command"); -vi.mock("../files", () => ({ - readJSON: vi.fn(), - writeJSON: vi.fn(), -})); -const mockReadJSON = vi.mocked(files.readJSON); -const mockWriteJSON = vi.mocked(files.writeJSON); - -describe("Package Helpers", () => { - beforeEach(() => { - vi.mocked(whichPMRuns).mockReturnValue({ name: "npm", version: "8.3.1" }); - vi.mocked(existsSync).mockImplementation(() => false); - }); - - afterEach(() => { - vi.clearAllMocks(); - }); - - describe("npmInstall", () => { - test("npm", async ({ expect }) => { - await npmInstall(createTestContext()); - - expect(vi.mocked(runCommand)).toHaveBeenCalledWith( - ["npm", "install"], - expect.anything() - ); - }); - - test("pnpm", async ({ expect }) => { - mockPackageManager("pnpm", "8.5.1"); - - await npmInstall(createTestContext()); - expect(vi.mocked(runCommand)).toHaveBeenCalledWith( - ["pnpm", "install"], - expect.anything() - ); - }); - }); - - describe("installPackages", async () => { - type TestCase = { - pm: string; - initialArgs: string[]; - additionalArgs?: string[]; - }; - - const cases: TestCase[] = [ - { pm: "npm", initialArgs: ["npm", "install"] }, - { pm: "pnpm", initialArgs: ["pnpm", "install"] }, - { pm: "bun", initialArgs: ["bun", "add"] }, - { pm: "yarn", initialArgs: ["yarn", "add"] }, - ]; - - test.for(cases)( - "with $pm", - async ({ pm, initialArgs, additionalArgs }, { expect }) => { - mockPackageManager(pm); - mockReadJSON.mockReturnValue({ - ["dependencies"]: { - foo: "^1.0.0", - bar: "^2.0.0", - baz: "^1.2.3", - }, - }); - const packages = ["foo", "bar@latest", "baz@1.2.3"]; - await installPackages(packages); - - expect(vi.mocked(runCommand)).toHaveBeenCalledWith( - [...initialArgs, ...packages, ...(additionalArgs ?? [])], - expect.anything() - ); - - if (pm === "npm") { - // Check that package.json was updated for npm - expect(mockReadJSON).toHaveBeenCalledWith( - resolve(process.cwd(), "package.json") - ); - expect(mockWriteJSON).toHaveBeenCalledWith( - resolve(process.cwd(), "package.json"), - expect.objectContaining({ - ["dependencies"]: { - foo: "^1.0.0", - bar: "^2.0.0", - baz: "1.2.3", - }, - }) - ); - } - } - ); - - const devCases: TestCase[] = [ - { pm: "npm", initialArgs: ["npm", "install", "--save-dev"] }, - { pm: "pnpm", initialArgs: ["pnpm", "install", "--save-dev"] }, - { pm: "bun", initialArgs: ["bun", "add", "-d"] }, - { pm: "yarn", initialArgs: ["yarn", "add", "-D"] }, - ]; - - test.for(devCases)( - "with $pm (dev = true)", - async ({ pm, initialArgs, additionalArgs }, { expect }) => { - mockPackageManager(pm); - mockReadJSON.mockReturnValue({ - ["devDependencies"]: { - foo: "^1.0.0", - bar: "^2.0.0", - baz: "^1.2.3", - }, - }); - const packages = ["foo", "bar@latest", "baz@1.2.3"]; - await installPackages(packages, { dev: true }); - - expect(vi.mocked(runCommand)).toHaveBeenCalledWith( - [...initialArgs, ...packages, ...(additionalArgs ?? [])], - expect.anything() - ); - - if (pm === "npm") { - // Check that package.json was updated for npm - expect(mockReadJSON).toHaveBeenCalledWith( - resolve(process.cwd(), "package.json") - ); - expect(mockWriteJSON).toHaveBeenCalledWith( - resolve(process.cwd(), "package.json"), - expect.objectContaining({ - ["devDependencies"]: { - foo: "^1.0.0", - bar: "^2.0.0", - baz: "1.2.3", - }, - }) - ); - } - } - ); - }); - - test("installWrangler", async ({ expect }) => { - mockReadJSON.mockReturnValue({ - ["devDependencies"]: { - wrangler: "^4.0.0", - }, - }); - await installWrangler(); - - expect(vi.mocked(runCommand)).toHaveBeenCalledWith( - ["npm", "install", "--save-dev", "wrangler@latest"], - expect.anything() - ); - }); -}); diff --git a/packages/create-cloudflare/src/helpers/command.ts b/packages/create-cloudflare/src/helpers/command.ts index 27ab2f2b77..e0eb2e8f75 100644 --- a/packages/create-cloudflare/src/helpers/command.ts +++ b/packages/create-cloudflare/src/helpers/command.ts @@ -1,200 +1,26 @@ -import { stripAnsi } from "@cloudflare/cli"; -import { CancelError } from "@cloudflare/cli/error"; -import { isInteractive, spinner } from "@cloudflare/cli/interactive"; -import { spawn } from "cross-spawn"; +import { runCommand } from "@cloudflare/cli/command"; import { readMetricsConfig } from "./metrics-config"; +import type { RunOptions } from "@cloudflare/cli/command"; /** - * Command is a string array, like ['git', 'commit', '-m', '"Initial commit"'] - */ -type Command = string[]; - -type RunOptions = { - startText?: string; - doneText?: string | ((output: string) => string); - silent?: boolean; - captureOutput?: boolean; - useSpinner?: boolean; - env?: NodeJS.ProcessEnv; - cwd?: string; - /** If defined this function is called to all you to transform the output from the command into a new string. */ - transformOutput?: (output: string) => string; -}; - -type PrintOptions = { - promise: Promise | (() => Promise); - useSpinner?: boolean; - startText: string; - doneText?: string | ((output: T) => string); -}; - -/** - * An awaitable wrapper around `spawn` that optionally displays progress to user and processes/captures the command's output - * - * @param command - The command to run as an array of strings - * @param opts.silent - Should the command's stdout and stderr be dispalyed to the user - * @param opts.captureOutput - Should the output of the command the returned as a string. - * @param opts.env - An object of environment variables to be injected when running the command - * @param opts.cwd - The directory in which the command should be run - * @param opts.useSpinner - Should a spinner be shown when running the command - * @param opts.startText - Spinner start text - * @param opts.endText - Spinner end text - * @param opts.transformOutput - A transformer to be run on command output before returning + * Runs a wrangler command with C3-specific telemetry handling. * - * @returns Output collected from the stdout of the command, if `captureOutput` was set to true. Otherwise `null`. + * - Sets WRANGLER_SEND_METRICS=false when C3 telemetry is disabled + * - Always sets WRANGLER_HIDE_BANNER=true for cleaner output */ -export const runCommand = async ( - command: Command, +export const runWranglerCommand = async ( + command: string[], opts: RunOptions = {} ): Promise => { - return printAsyncStatus({ - useSpinner: opts.useSpinner ?? opts.silent, - startText: opts.startText || quoteShellArgs(command), - doneText: opts.doneText, - promise() { - const [executable, ...args] = command; - // Don't send metrics data on any wrangler commands if telemetry is disabled in C3 - // If telemetry is disabled separately for wrangler, wrangler will handle that - if (args[0] === "wrangler") { - opts.env ??= {}; - const metrics = readMetricsConfig(); - if (metrics.c3permission?.enabled === false) { - opts.env["WRANGLER_SEND_METRICS"] = "false"; - } - opts.env["WRANGLER_HIDE_BANNER"] = "true"; - } - const abortController = new AbortController(); - const cmd = spawn(executable, [...args], { - // TODO: ideally inherit stderr, but npm install uses this for warnings - // stdio: [ioMode, ioMode, "inherit"], - stdio: opts.silent ? "pipe" : "inherit", - env: { - ...process.env, - ...opts.env, - }, - cwd: opts.cwd, - signal: abortController.signal, - }); - - let output = ``; - - if (opts.captureOutput ?? opts.silent) { - cmd.stdout?.on("data", (data) => { - output += data; - }); - cmd.stderr?.on("data", (data) => { - output += data; - }); - } - - let cleanup: (() => void) | null = null; - - return new Promise((resolvePromise, reject) => { - const cancel = (signal?: NodeJS.Signals) => { - reject(new CancelError(`Command cancelled`, signal)); - abortController.abort(signal ? `${signal} received` : null); - }; - - process.on("SIGTERM", cancel).on("SIGINT", cancel); - - // To cleanup the signal listeners when the promise settles - cleanup = () => { - process.off("SIGTERM", cancel).off("SIGINT", cancel); - }; - - cmd.on("close", (code) => { - try { - if (code !== 0) { - throw new Error(output, { cause: code }); - } - - // Process any captured output - const transformOutput = - opts.transformOutput ?? ((result: string) => result); - const processedOutput = transformOutput(stripAnsi(output)); + const env: NodeJS.ProcessEnv = { ...opts.env }; - // Send the captured (and processed) output back to the caller - resolvePromise(processedOutput); - } catch (e) { - // Something went wrong. - // Perhaps the command or the transform failed. - reject(new Error(output, { cause: e })); - } - }); - - cmd.on("error", (error) => { - reject(error); - }); - }).finally(() => { - cleanup?.(); - }); - }, - }); -}; - -const printAsyncStatus = async ({ - promise, - ...opts -}: PrintOptions): Promise => { - let s: ReturnType | undefined; - - if (opts.useSpinner && isInteractive()) { - s = spinner(); - } - - s?.start(opts?.startText); - - if (typeof promise === "function") { - promise = promise(); - } - - try { - const output = await promise; - - const doneText = - typeof opts.doneText === "function" - ? opts.doneText(output) - : opts.doneText; - s?.stop(doneText); - } catch (err) { - s?.stop((err as Error).message); - } finally { - s?.stop(); + // Don't send metrics data on wrangler commands if telemetry is disabled in C3 + // If telemetry is disabled separately for wrangler, wrangler will handle that + const metrics = readMetricsConfig(); + if (metrics.c3permission?.enabled === false) { + env["WRANGLER_SEND_METRICS"] = "false"; } + env["WRANGLER_HIDE_BANNER"] = "true"; - return promise; + return runCommand(command, { ...opts, env }); }; - -/** - * Formats an array of command line arguments to be displayed to the user - * in a platform safe way. Args used in conjunction with `runCommand` are safe - * since we use `cross-spawn` to handle multi-platform support. - * - * However, when we output commands to the user, we have to make sure that they - * are compatible with the platform they are using. - * - * @param args - The arguments to format to the user - */ -export function quoteShellArgs(args: string[]): string { - if (process.platform === "win32") { - // Simple Windows command prompt quoting if there are special characters. - const specialCharsMatcher = /[&<>[\]|{}^=;!'+,`~\s]/; - return args - .map((arg) => - arg.match(specialCharsMatcher) ? `"${arg.replaceAll(`"`, `""`)}"` : arg - ) - .join(" "); - } else { - return args - .map((s) => { - if (/["\s]/.test(s) && !/'/.test(s)) { - return "'" + s.replace(/(['\\])/g, "\\$1") + "'"; - } - if (/["'\s]/.test(s)) { - return '"' + s.replace(/(["\\$`!])/g, "\\$1") + '"'; - } - return s; - }) - .join(" "); - } -} diff --git a/packages/create-cloudflare/src/helpers/packageManagers.ts b/packages/create-cloudflare/src/helpers/packageManagers.ts index 802626cec0..695350a927 100644 --- a/packages/create-cloudflare/src/helpers/packageManagers.ts +++ b/packages/create-cloudflare/src/helpers/packageManagers.ts @@ -1,13 +1,13 @@ import { existsSync, rmSync } from "node:fs"; import nodePath from "node:path"; import { brandColor, dim } from "@cloudflare/cli/colors"; +import { runCommand } from "@cloudflare/cli/command"; import semver from "semver"; import whichPmRuns from "which-pm-runs"; import { testPackageManager, testPackageManagerVersion, } from "../../e2e/helpers/constants"; -import { runCommand } from "./command"; import type { C3Context } from "types"; /** @@ -39,7 +39,7 @@ export const detectPackageManager = () => { npm: "pnpm", npx: "pnpm", dlx: ["pnpm", "dlx"], - }; + } as const; } return { name, @@ -47,7 +47,7 @@ export const detectPackageManager = () => { npm: "pnpm", npx: "pnpx", dlx: ["pnpx"], - }; + } as const; case "yarn": if (semver.gt(version, "2.0.0")) { return { @@ -56,7 +56,7 @@ export const detectPackageManager = () => { npm: "yarn", npx: "yarn", dlx: ["yarn", "dlx"], - }; + } as const; } return { name, @@ -64,7 +64,7 @@ export const detectPackageManager = () => { npm: "yarn", npx: "yarn", dlx: ["yarn"], - }; + } as const; case "bun": return { name, @@ -72,7 +72,7 @@ export const detectPackageManager = () => { npm: "bun", npx: "bunx", dlx: ["bunx"], - }; + } as const; case "npm": default: @@ -82,7 +82,7 @@ export const detectPackageManager = () => { npm: "npm", npx: "npx", dlx: ["npx"], - }; + } as const; } }; diff --git a/packages/create-cloudflare/src/helpers/packages.ts b/packages/create-cloudflare/src/helpers/packages.ts index c39b590141..a73fe2b779 100644 --- a/packages/create-cloudflare/src/helpers/packages.ts +++ b/packages/create-cloudflare/src/helpers/packages.ts @@ -1,84 +1,41 @@ -import assert from "node:assert"; import { existsSync } from "node:fs"; import nodePath from "node:path"; import { brandColor, dim } from "@cloudflare/cli/colors"; +import { runCommand } from "@cloudflare/cli/command"; +import * as cliPackages from "@cloudflare/cli/packages"; import { fetch } from "undici"; -import { runCommand } from "./command"; -import { readJSON, writeJSON } from "./files"; import { detectPackageManager } from "./packageManagers"; -import type { C3Context, PackageJson } from "types"; +import type { C3Context } from "types"; type InstallConfig = { startText?: string; doneText?: string; dev?: boolean; + force?: boolean; + isWorkspaceRoot?: boolean; }; /** - * Install a list of packages to the local project directory and add it to `package.json` - * - * @param packages - An array of package specifiers to be installed - * @param config.dev - Add packages as `devDependencies` - * @param config.startText - Spinner start text - * @param config.doneText - Spinner done text + * Install a list of packages to the local project directory. + * Automatically detects the package manager from the environment. */ export const installPackages = async ( packages: string[], config: InstallConfig = {} ) => { - if (packages.length === 0) { - return; - } - const { npm } = detectPackageManager(); - - let saveFlag; - let cmd; - switch (npm) { - case "yarn": - cmd = "add"; - saveFlag = config.dev ? "-D" : ""; - break; - case "bun": - cmd = "add"; - saveFlag = config.dev ? "-d" : ""; - break; - case "npm": - case "pnpm": - default: - cmd = "install"; - saveFlag = config.dev ? "--save-dev" : ""; - break; - } - - await runCommand([npm, cmd, ...(saveFlag ? [saveFlag] : []), ...packages], { - ...config, - silent: true, - }); - - if (npm === "npm") { - // Npm install will update the package.json with a caret-range rather than the exact version/range we asked for. - // We can't use `npm install --save-exact` because that always pins to an exact version, and we want to allow ranges too. - // So let's just fix that up now by rewriting the package.json. - const pkgJsonPath = nodePath.join(process.cwd(), "package.json"); - const pkgJson = readJSON(pkgJsonPath) as PackageJson; - const deps = config.dev ? pkgJson.devDependencies : pkgJson.dependencies; - assert(deps, "dependencies should be defined"); - for (const pkg of packages) { - const versionMarker = pkg.lastIndexOf("@"); - if (versionMarker > 0) { - // (if versionMarker was 0 then this would indicate a scoped package with no version) - const pkgName = pkg.slice(0, versionMarker); - const pkgVersion = pkg.slice(versionMarker + 1); - if (pkgVersion !== "latest") { - deps[pkgName] = pkgVersion; - } - } - } - writeJSON(pkgJsonPath, pkgJson); - } + return cliPackages.installPackages(npm, packages, config); }; +/** + * Installs the latest version of wrangler in the project directory. + * Automatically detects the package manager from the environment. + */ +export async function installWrangler() { + const { npm } = detectPackageManager(); + return cliPackages.installWrangler(npm, false); +} + /** * Install dependencies in the project directory via `npm install` or its equivalent. */ @@ -110,21 +67,3 @@ export async function getLatestPackageVersion(packageSpecifier: string) { const npmInfo = (await resp.json()) as NpmInfoResponse; return npmInfo["dist-tags"].latest; } - -/** - * Installs the latest version of wrangler in the project directory if it isn't already. - */ -export const installWrangler = async () => { - const { npm } = detectPackageManager(); - - // Even if Wrangler is already installed, make sure we install the latest version, as some framework CLIs are pinned to an older version - await installPackages([`wrangler@latest`], { - dev: true, - startText: `Installing wrangler ${dim( - "A command line tool for building Cloudflare Workers" - )}`, - doneText: `${brandColor("installed")} ${dim( - `via \`${npm} install wrangler --save-dev\`` - )}`, - }); -}; diff --git a/packages/create-cloudflare/src/pages.ts b/packages/create-cloudflare/src/pages.ts index 93f465a4a1..89443d8a18 100644 --- a/packages/create-cloudflare/src/pages.ts +++ b/packages/create-cloudflare/src/pages.ts @@ -1,5 +1,6 @@ import { brandColor, dim } from "@cloudflare/cli/colors"; -import { quoteShellArgs, runCommand } from "helpers/command"; +import { quoteShellArgs } from "@cloudflare/cli/command"; +import { runWranglerCommand } from "helpers/command"; import { detectPackageManager } from "helpers/packageManagers"; import { retry } from "helpers/retry"; import { getProductionBranch } from "./git"; @@ -53,7 +54,7 @@ export const createProject = async (ctx: C3Context) => { }, }, async () => - runCommand(cmd, { + runWranglerCommand(cmd, { // Make this command more verbose in test mode to aid // troubleshooting API errors silent: process.env.VITEST == undefined, @@ -82,7 +83,7 @@ export const createProject = async (ctx: C3Context) => { ]; await retry({ times: VERIFY_PROJECT_RETRIES }, async () => - runCommand(verifyProject, { + runWranglerCommand(verifyProject, { silent: process.env.VITEST == undefined, cwd: ctx.project.path, env: { CLOUDFLARE_ACCOUNT_ID }, diff --git a/packages/create-cloudflare/src/workers.ts b/packages/create-cloudflare/src/workers.ts index a4beb8d78f..c22acfe890 100644 --- a/packages/create-cloudflare/src/workers.ts +++ b/packages/create-cloudflare/src/workers.ts @@ -2,7 +2,7 @@ import { existsSync } from "node:fs"; import { join } from "node:path"; import { warn } from "@cloudflare/cli"; import { brandColor, dim } from "@cloudflare/cli/colors"; -import { runCommand } from "helpers/command"; +import { runCommand } from "@cloudflare/cli/command"; import { getLatestTypesEntrypoint } from "helpers/compatDate"; import { readFile, readJSON, usesTypescript, writeFile } from "helpers/files"; import { detectPackageManager } from "helpers/packageManagers"; diff --git a/packages/create-cloudflare/src/wrangler/__tests__/accounts.test.ts b/packages/create-cloudflare/src/wrangler/__tests__/accounts.test.ts index d8592fed30..3a5d12cb9f 100644 --- a/packages/create-cloudflare/src/wrangler/__tests__/accounts.test.ts +++ b/packages/create-cloudflare/src/wrangler/__tests__/accounts.test.ts @@ -1,5 +1,5 @@ import { mockPackageManager, mockSpinner } from "helpers/__tests__/mocks"; -import { runCommand } from "helpers/command"; +import { runWranglerCommand } from "helpers/command"; import { hasSparrowSourceKey } from "helpers/sparrow"; import { beforeEach, describe, test, vi } from "vitest"; import { createTestContext } from "../../__tests__/helpers"; @@ -63,14 +63,14 @@ describe("wrangler account helpers", () => { expect(testCtx.account).toEqual({ id: "env-account-id-123", name: "" }); // Should not call wrangler whoami when env var is set - expect(runCommand).not.toHaveBeenCalled(); + expect(runWranglerCommand).not.toHaveBeenCalled(); }); }); describe("wranglerLogin", async () => { test("logged in", async ({ expect }) => { const mock = vi - .mocked(runCommand) + .mocked(runWranglerCommand) .mockReturnValueOnce(Promise.resolve(loggedInWhoamiOutput)); const loggedIn = await wranglerLogin(ctx); @@ -90,7 +90,7 @@ describe("wrangler account helpers", () => { test("logged out (successful login)", async ({ expect }) => { const mock = vi - .mocked(runCommand) + .mocked(runWranglerCommand) .mockReturnValueOnce(Promise.resolve(loggedOutWhoamiOutput)) .mockReturnValueOnce(Promise.resolve(loginSuccessOutput)); @@ -111,7 +111,7 @@ describe("wrangler account helpers", () => { test("logged out (login denied)", async ({ expect }) => { const mock = vi - .mocked(runCommand) + .mocked(runWranglerCommand) .mockReturnValueOnce(Promise.resolve(loggedOutWhoamiOutput)) .mockReturnValueOnce(Promise.resolve(loginDeniedOutput)); @@ -133,7 +133,7 @@ describe("wrangler account helpers", () => { test("listAccounts", async ({ expect }) => { const mock = vi - .mocked(runCommand) + .mocked(runWranglerCommand) .mockReturnValueOnce(Promise.resolve(loggedInWhoamiOutput)); const accounts = await listAccounts(); @@ -147,7 +147,7 @@ describe("wrangler account helpers", () => { describe("isLoggedIn", async () => { test("logged in", async ({ expect }) => { const mock = vi - .mocked(runCommand) + .mocked(runWranglerCommand) .mockReturnValueOnce(Promise.resolve(loggedInWhoamiOutput)); const result = await isLoggedIn(); @@ -161,7 +161,7 @@ describe("wrangler account helpers", () => { test("logged out", async ({ expect }) => { const mock = vi - .mocked(runCommand) + .mocked(runWranglerCommand) .mockReturnValueOnce(Promise.resolve(loggedOutWhoamiOutput)); const result = await isLoggedIn(); @@ -175,7 +175,7 @@ describe("wrangler account helpers", () => { test("wrangler whoami error", async ({ expect }) => { const mock = vi - .mocked(runCommand) + .mocked(runWranglerCommand) .mockRejectedValueOnce(new Error("fail!")); const result = await isLoggedIn(); diff --git a/packages/create-cloudflare/src/wrangler/accounts.ts b/packages/create-cloudflare/src/wrangler/accounts.ts index 04b5c7ad9e..bf53edc530 100644 --- a/packages/create-cloudflare/src/wrangler/accounts.ts +++ b/packages/create-cloudflare/src/wrangler/accounts.ts @@ -1,7 +1,7 @@ import { updateStatus } from "@cloudflare/cli"; import { brandColor, dim } from "@cloudflare/cli/colors"; import { inputPrompt, spinner } from "@cloudflare/cli/interactive"; -import { runCommand } from "helpers/command"; +import { runWranglerCommand } from "helpers/command"; import { detectPackageManager } from "helpers/packageManagers"; import { reporter } from "../metrics"; import type { C3Context } from "types"; @@ -84,7 +84,7 @@ export const wranglerLogin = async (ctx: C3Context) => { // We're using a custom spinner since this is a little complicated. // We want to vary the done status based on the output - const output = await runCommand([npx, "wrangler", "login"], { + const output = await runWranglerCommand([npx, "wrangler", "login"], { silent: true, }); const success = /Successfully logged in/.test(output); @@ -102,7 +102,7 @@ export const wranglerLogin = async (ctx: C3Context) => { export const listAccounts = async () => { const { npx } = detectPackageManager(); - const output = await runCommand([npx, "wrangler", "whoami"], { + const output = await runWranglerCommand([npx, "wrangler", "whoami"], { silent: true, }); @@ -120,7 +120,7 @@ export const listAccounts = async () => { export const isLoggedIn = async () => { const { npx } = detectPackageManager(); try { - const output = await runCommand([npx, "wrangler", "whoami"], { + const output = await runWranglerCommand([npx, "wrangler", "whoami"], { silent: true, }); return /You are logged in/.test(output); diff --git a/packages/create-cloudflare/templates/astro/pages/c3.ts b/packages/create-cloudflare/templates/astro/pages/c3.ts index ead39a86a8..edf8ac2459 100644 --- a/packages/create-cloudflare/templates/astro/pages/c3.ts +++ b/packages/create-cloudflare/templates/astro/pages/c3.ts @@ -1,8 +1,8 @@ import { logRaw, updateStatus } from "@cloudflare/cli"; import { blue, brandColor, dim } from "@cloudflare/cli/colors"; +import { runCommand } from "@cloudflare/cli/command"; import { runFrameworkGenerator } from "frameworks/index"; import { transformFile } from "helpers/codemod"; -import { runCommand } from "helpers/command"; import { usesTypescript } from "helpers/files"; import { detectPackageManager } from "helpers/packageManagers"; import * as recast from "recast"; diff --git a/packages/create-cloudflare/templates/astro/workers/c3.ts b/packages/create-cloudflare/templates/astro/workers/c3.ts index f4867217f8..bb075b6b66 100644 --- a/packages/create-cloudflare/templates/astro/workers/c3.ts +++ b/packages/create-cloudflare/templates/astro/workers/c3.ts @@ -1,7 +1,7 @@ import { logRaw } from "@cloudflare/cli"; import { brandColor, dim } from "@cloudflare/cli/colors"; +import { runCommand } from "@cloudflare/cli/command"; import { runFrameworkGenerator } from "frameworks/index"; -import { runCommand } from "helpers/command"; import { usesTypescript } from "helpers/files"; import { detectPackageManager } from "helpers/packageManagers"; import type { TemplateConfig } from "../../../src/templates"; diff --git a/packages/create-cloudflare/templates/pre-existing/c3.ts b/packages/create-cloudflare/templates/pre-existing/c3.ts index b0e79fa86e..c8d49900a2 100644 --- a/packages/create-cloudflare/templates/pre-existing/c3.ts +++ b/packages/create-cloudflare/templates/pre-existing/c3.ts @@ -3,8 +3,8 @@ import { cp, mkdtemp } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { brandColor, dim } from "@cloudflare/cli/colors"; +import { runCommand } from "@cloudflare/cli/command"; import { processArgument } from "helpers/args"; -import { runCommand } from "helpers/command"; import { detectPackageManager } from "helpers/packageManagers"; import { chooseAccount, wranglerLogin } from "../../src/wrangler/accounts"; import type { TemplateConfig } from "../../src/templates"; diff --git a/packages/create-cloudflare/templates/qwik/pages/c3.ts b/packages/create-cloudflare/templates/qwik/pages/c3.ts index e1a83e51f8..921d2a1bc3 100644 --- a/packages/create-cloudflare/templates/qwik/pages/c3.ts +++ b/packages/create-cloudflare/templates/qwik/pages/c3.ts @@ -1,9 +1,9 @@ import { endSection } from "@cloudflare/cli"; import { brandColor } from "@cloudflare/cli/colors"; +import { quoteShellArgs, runCommand } from "@cloudflare/cli/command"; import { spinner } from "@cloudflare/cli/interactive"; import { runFrameworkGenerator } from "frameworks/index"; import { loadTemplateSnippets, transformFile } from "helpers/codemod"; -import { quoteShellArgs, runCommand } from "helpers/command"; import { usesTypescript } from "helpers/files"; import { detectPackageManager } from "helpers/packageManagers"; import * as recast from "recast"; diff --git a/packages/create-cloudflare/templates/qwik/workers/c3.ts b/packages/create-cloudflare/templates/qwik/workers/c3.ts index 0d88fafcf4..3813011f39 100644 --- a/packages/create-cloudflare/templates/qwik/workers/c3.ts +++ b/packages/create-cloudflare/templates/qwik/workers/c3.ts @@ -1,9 +1,9 @@ import { endSection } from "@cloudflare/cli"; import { brandColor } from "@cloudflare/cli/colors"; +import { quoteShellArgs, runCommand } from "@cloudflare/cli/command"; import { spinner } from "@cloudflare/cli/interactive"; import { runFrameworkGenerator } from "frameworks/index"; import { loadTemplateSnippets, transformFile } from "helpers/codemod"; -import { quoteShellArgs, runCommand } from "helpers/command"; import { usesTypescript } from "helpers/files"; import { detectPackageManager } from "helpers/packageManagers"; import * as recast from "recast"; diff --git a/packages/wrangler/package.json b/packages/wrangler/package.json index ddefce4273..3e34d48e6d 100644 --- a/packages/wrangler/package.json +++ b/packages/wrangler/package.json @@ -92,7 +92,6 @@ "@sentry/types": "^7.86.0", "@sentry/utils": "^7.86.0", "@types/command-exists": "^1.2.0", - "@types/cross-spawn": "^6.0.2", "@types/esprima": "^4.0.3", "@types/glob-to-regexp": "^0.4.1", "@types/javascript-time-ago": "^2.0.3", @@ -120,7 +119,6 @@ "cmd-shim": "^4.1.0", "command-exists": "^1.2.9", "concurrently": "^8.2.2", - "cross-spawn": "^7.0.3", "date-fns": "^4.1.0", "devtools-protocol": "^0.0.1182435", "dotenv": "^16.3.1", diff --git a/packages/wrangler/src/__tests__/autoconfig/frameworks/angular.test.ts b/packages/wrangler/src/__tests__/autoconfig/frameworks/angular.test.ts index d42da1fe86..8d09fd6721 100644 --- a/packages/wrangler/src/__tests__/autoconfig/frameworks/angular.test.ts +++ b/packages/wrangler/src/__tests__/autoconfig/frameworks/angular.test.ts @@ -1,7 +1,7 @@ import { mkdir, writeFile } from "node:fs/promises"; import { resolve } from "node:path"; +import * as cliPackages from "@cloudflare/cli/packages"; import { beforeEach, describe, it, vi } from "vitest"; -import * as c3Packages from "../../../autoconfig/c3-vendor/packages"; import { Angular } from "../../../autoconfig/frameworks/angular"; import { NpmPackageManager } from "../../../package-manager"; import { runInTempDir } from "../../helpers/run-in-tmp"; @@ -22,7 +22,7 @@ describe("Angular framework configure()", () => { beforeEach(() => { installSpy = vi - .spyOn(c3Packages, "installPackages") + .spyOn(cliPackages, "installPackages") .mockImplementation(async () => {}); }); @@ -315,7 +315,7 @@ describe("Angular framework configure()", () => { await framework.configure(BASE_OPTIONS); expect(installSpy).toHaveBeenCalledWith( - NpmPackageManager, + NpmPackageManager.type, ["xhr2"], expect.objectContaining({ dev: true }) ); diff --git a/packages/wrangler/src/__tests__/autoconfig/run.test.ts b/packages/wrangler/src/__tests__/autoconfig/run.test.ts index 3916a1bf90..2bdab7bd0e 100644 --- a/packages/wrangler/src/__tests__/autoconfig/run.test.ts +++ b/packages/wrangler/src/__tests__/autoconfig/run.test.ts @@ -1,10 +1,10 @@ import { existsSync } from "node:fs"; import { mkdir, writeFile } from "node:fs/promises"; +import * as cliPackages from "@cloudflare/cli/packages"; import { FatalError, readFileSync } from "@cloudflare/workers-utils"; import { writeWranglerConfig } from "@cloudflare/workers-utils/test-helpers"; // eslint-disable-next-line no-restricted-imports import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -import * as c3 from "../../autoconfig/c3-vendor/packages"; import * as details from "../../autoconfig/details"; import { Static } from "../../autoconfig/frameworks/static"; import * as run from "../../autoconfig/run"; @@ -191,7 +191,7 @@ describe("autoconfig (deploy)", () => { let installSpy: MockInstance; beforeEach(() => { installSpy = vi - .spyOn(c3, "installWrangler") + .spyOn(cliPackages, "installWrangler") .mockImplementation(async () => {}); }); diff --git a/packages/wrangler/src/__tests__/deploy/assets.test.ts b/packages/wrangler/src/__tests__/deploy/assets.test.ts index e203a675e8..c42887acd0 100644 --- a/packages/wrangler/src/__tests__/deploy/assets.test.ts +++ b/packages/wrangler/src/__tests__/deploy/assets.test.ts @@ -53,7 +53,7 @@ vi.mock("../../package-manager", async (importOriginal) => ({ vi.mock("../../autoconfig/run"); vi.mock("../../autoconfig/frameworks/utils/packages"); -vi.mock("../../autoconfig/c3-vendor/command"); +vi.mock("@cloudflare/cli/command"); describe("deploy", () => { mockAccountId(); diff --git a/packages/wrangler/src/__tests__/deploy/bindings.test.ts b/packages/wrangler/src/__tests__/deploy/bindings.test.ts index 07215386da..cca632a0d5 100644 --- a/packages/wrangler/src/__tests__/deploy/bindings.test.ts +++ b/packages/wrangler/src/__tests__/deploy/bindings.test.ts @@ -54,7 +54,7 @@ vi.mock("../../package-manager", async (importOriginal) => ({ vi.mock("../../autoconfig/run"); vi.mock("../../autoconfig/frameworks/utils/packages"); -vi.mock("../../autoconfig/c3-vendor/command"); +vi.mock("@cloudflare/cli/command"); describe("deploy", () => { mockAccountId(); diff --git a/packages/wrangler/src/__tests__/deploy/build.test.ts b/packages/wrangler/src/__tests__/deploy/build.test.ts index 9ef7e19075..9ced626f5a 100644 --- a/packages/wrangler/src/__tests__/deploy/build.test.ts +++ b/packages/wrangler/src/__tests__/deploy/build.test.ts @@ -57,7 +57,7 @@ vi.mock("../../package-manager", async (importOriginal) => ({ vi.mock("../../autoconfig/run"); vi.mock("../../autoconfig/frameworks/utils/packages"); -vi.mock("../../autoconfig/c3-vendor/command"); +vi.mock("@cloudflare/cli/command"); describe("deploy", () => { mockAccountId(); diff --git a/packages/wrangler/src/__tests__/deploy/config-remote.test.ts b/packages/wrangler/src/__tests__/deploy/config-remote.test.ts index a9b53dcb42..03f5bf0144 100644 --- a/packages/wrangler/src/__tests__/deploy/config-remote.test.ts +++ b/packages/wrangler/src/__tests__/deploy/config-remote.test.ts @@ -64,7 +64,7 @@ vi.mock("../../package-manager", async (importOriginal) => ({ })); vi.mock("../../autoconfig/frameworks/utils/packages"); -vi.mock("../../autoconfig/c3-vendor/command"); +vi.mock("@cloudflare/cli/command"); describe("deploy", () => { mockAccountId(); diff --git a/packages/wrangler/src/__tests__/deploy/core.test.ts b/packages/wrangler/src/__tests__/deploy/core.test.ts index aa44463f6e..84d331df8c 100644 --- a/packages/wrangler/src/__tests__/deploy/core.test.ts +++ b/packages/wrangler/src/__tests__/deploy/core.test.ts @@ -76,7 +76,7 @@ vi.mock("../../package-manager", async (importOriginal) => ({ vi.mock("../../autoconfig/run"); vi.mock("../../autoconfig/frameworks/utils/packages"); -vi.mock("../../autoconfig/c3-vendor/command"); +vi.mock("@cloudflare/cli/command"); describe("deploy", () => { mockAccountId(); diff --git a/packages/wrangler/src/__tests__/deploy/durable-objects.test.ts b/packages/wrangler/src/__tests__/deploy/durable-objects.test.ts index eb33015266..28088ef4ae 100644 --- a/packages/wrangler/src/__tests__/deploy/durable-objects.test.ts +++ b/packages/wrangler/src/__tests__/deploy/durable-objects.test.ts @@ -49,7 +49,7 @@ vi.mock("../../package-manager", async (importOriginal) => ({ vi.mock("../../autoconfig/run"); vi.mock("../../autoconfig/frameworks/utils/packages"); -vi.mock("../../autoconfig/c3-vendor/command"); +vi.mock("@cloudflare/cli/command"); describe("deploy", () => { mockAccountId(); diff --git a/packages/wrangler/src/__tests__/deploy/entry-points.test.ts b/packages/wrangler/src/__tests__/deploy/entry-points.test.ts index 0b2b02705f..d08f226195 100644 --- a/packages/wrangler/src/__tests__/deploy/entry-points.test.ts +++ b/packages/wrangler/src/__tests__/deploy/entry-points.test.ts @@ -64,7 +64,7 @@ vi.mock("../../package-manager", async (importOriginal) => ({ vi.mock("../../autoconfig/run"); vi.mock("../../autoconfig/frameworks/utils/packages"); -vi.mock("../../autoconfig/c3-vendor/command"); +vi.mock("@cloudflare/cli/command"); describe("deploy", () => { mockAccountId(); diff --git a/packages/wrangler/src/__tests__/deploy/environments.test.ts b/packages/wrangler/src/__tests__/deploy/environments.test.ts index 5a120d2e33..f1ffbfe752 100644 --- a/packages/wrangler/src/__tests__/deploy/environments.test.ts +++ b/packages/wrangler/src/__tests__/deploy/environments.test.ts @@ -56,7 +56,7 @@ vi.mock("../../package-manager", async (importOriginal) => ({ vi.mock("../../autoconfig/run"); vi.mock("../../autoconfig/frameworks/utils/packages"); -vi.mock("../../autoconfig/c3-vendor/command"); +vi.mock("@cloudflare/cli/command"); describe("deploy", () => { mockAccountId(); diff --git a/packages/wrangler/src/__tests__/deploy/formats.test.ts b/packages/wrangler/src/__tests__/deploy/formats.test.ts index 1779c29902..0c14853598 100644 --- a/packages/wrangler/src/__tests__/deploy/formats.test.ts +++ b/packages/wrangler/src/__tests__/deploy/formats.test.ts @@ -54,7 +54,7 @@ vi.mock("../../package-manager", async (importOriginal) => ({ vi.mock("../../autoconfig/run"); vi.mock("../../autoconfig/frameworks/utils/packages"); -vi.mock("../../autoconfig/c3-vendor/command"); +vi.mock("@cloudflare/cli/command"); describe("deploy", () => { mockAccountId(); diff --git a/packages/wrangler/src/__tests__/deploy/legacy-assets.test.ts b/packages/wrangler/src/__tests__/deploy/legacy-assets.test.ts index 76f321b3a8..cd76b635f6 100644 --- a/packages/wrangler/src/__tests__/deploy/legacy-assets.test.ts +++ b/packages/wrangler/src/__tests__/deploy/legacy-assets.test.ts @@ -56,7 +56,7 @@ vi.mock("../../package-manager", async (importOriginal) => ({ vi.mock("../../autoconfig/run"); vi.mock("../../autoconfig/frameworks/utils/packages"); -vi.mock("../../autoconfig/c3-vendor/command"); +vi.mock("@cloudflare/cli/command"); describe("deploy", () => { mockAccountId(); diff --git a/packages/wrangler/src/__tests__/deploy/open-next.test.ts b/packages/wrangler/src/__tests__/deploy/open-next.test.ts index 9a16136649..0e036f15d1 100644 --- a/packages/wrangler/src/__tests__/deploy/open-next.test.ts +++ b/packages/wrangler/src/__tests__/deploy/open-next.test.ts @@ -59,7 +59,7 @@ vi.mock("../../package-manager", async (importOriginal) => ({ vi.mock("../../autoconfig/run"); vi.mock("../../autoconfig/frameworks/utils/packages"); -vi.mock("../../autoconfig/c3-vendor/command"); +vi.mock("@cloudflare/cli/command"); describe("deploy", () => { mockAccountId(); @@ -164,7 +164,7 @@ describe("deploy", () => { "deploy", "--x-autoconfig", ]); - const runCommandSpy = (await import("../../autoconfig/c3-vendor/command")) + const runCommandSpy = (await import("@cloudflare/cli/command")) .runCommand; await mockOpenNextLikeProject(); @@ -212,7 +212,7 @@ describe("deploy", () => { "--keep-vars", "--x-autoconfig", ]); - const runCommandSpy = (await import("../../autoconfig/c3-vendor/command")) + const runCommandSpy = (await import("@cloudflare/cli/command")) .runCommand; await mockOpenNextLikeProject(); @@ -258,7 +258,7 @@ describe("deploy", () => { }) => { vi.stubEnv("OPEN_NEXT_DEPLOY", "1"); - const runCommandSpy = (await import("../../autoconfig/c3-vendor/command")) + const runCommandSpy = (await import("@cloudflare/cli/command")) .runCommand; await mockOpenNextLikeProject(); @@ -289,7 +289,7 @@ describe("deploy", () => { it("should not delegate to open-next deploy when --x-autoconfig=false is provided", async ({ expect, }) => { - const runCommandSpy = (await import("../../autoconfig/c3-vendor/command")) + const runCommandSpy = (await import("@cloudflare/cli/command")) .runCommand; await mockOpenNextLikeProject(); @@ -320,7 +320,7 @@ describe("deploy", () => { it("should not delegate to open-next deploy when the Next.js config file is missing (to avoid false positives)", async ({ expect, }) => { - const runCommandSpy = (await import("../../autoconfig/c3-vendor/command")) + const runCommandSpy = (await import("@cloudflare/cli/command")) .runCommand; await mockOpenNextLikeProject(); @@ -354,7 +354,7 @@ describe("deploy", () => { it("should not delegate to open-next deploy when the open-next config file is missing (to avoid false positives)", async ({ expect, }) => { - const runCommandSpy = (await import("../../autoconfig/c3-vendor/command")) + const runCommandSpy = (await import("@cloudflare/cli/command")) .runCommand; await mockOpenNextLikeProject(); diff --git a/packages/wrangler/src/__tests__/deploy/queues.test.ts b/packages/wrangler/src/__tests__/deploy/queues.test.ts index db4abf76f0..152c19221b 100644 --- a/packages/wrangler/src/__tests__/deploy/queues.test.ts +++ b/packages/wrangler/src/__tests__/deploy/queues.test.ts @@ -52,7 +52,7 @@ vi.mock("../../package-manager", async (importOriginal) => ({ vi.mock("../../autoconfig/run"); vi.mock("../../autoconfig/frameworks/utils/packages"); -vi.mock("../../autoconfig/c3-vendor/command"); +vi.mock("@cloudflare/cli/command"); describe("deploy", () => { mockAccountId(); diff --git a/packages/wrangler/src/__tests__/deploy/routes.test.ts b/packages/wrangler/src/__tests__/deploy/routes.test.ts index fd95c5b3ee..31f0c3353d 100644 --- a/packages/wrangler/src/__tests__/deploy/routes.test.ts +++ b/packages/wrangler/src/__tests__/deploy/routes.test.ts @@ -69,7 +69,7 @@ vi.mock("../../package-manager", async (importOriginal) => ({ vi.mock("../../autoconfig/run"); vi.mock("../../autoconfig/frameworks/utils/packages"); -vi.mock("../../autoconfig/c3-vendor/command"); +vi.mock("@cloudflare/cli/command"); describe("deploy", () => { mockAccountId(); diff --git a/packages/wrangler/src/__tests__/deploy/secrets.test.ts b/packages/wrangler/src/__tests__/deploy/secrets.test.ts index c0b70dc30f..68bce14e86 100644 --- a/packages/wrangler/src/__tests__/deploy/secrets.test.ts +++ b/packages/wrangler/src/__tests__/deploy/secrets.test.ts @@ -50,7 +50,7 @@ vi.mock("../../package-manager", async (importOriginal) => ({ vi.mock("../../autoconfig/run"); vi.mock("../../autoconfig/frameworks/utils/packages"); -vi.mock("../../autoconfig/c3-vendor/command"); +vi.mock("@cloudflare/cli/command"); describe("deploy secrets", () => { mockAccountId(); diff --git a/packages/wrangler/src/__tests__/deploy/workers-dev.test.ts b/packages/wrangler/src/__tests__/deploy/workers-dev.test.ts index cc4863c9fc..a1fd7fd440 100644 --- a/packages/wrangler/src/__tests__/deploy/workers-dev.test.ts +++ b/packages/wrangler/src/__tests__/deploy/workers-dev.test.ts @@ -56,7 +56,7 @@ vi.mock("../../package-manager", async (importOriginal) => ({ vi.mock("../../autoconfig/run"); vi.mock("../../autoconfig/frameworks/utils/packages"); -vi.mock("../../autoconfig/c3-vendor/command"); +vi.mock("@cloudflare/cli/command"); describe("deploy", () => { mockAccountId(); diff --git a/packages/wrangler/src/__tests__/deploy/workflows.test.ts b/packages/wrangler/src/__tests__/deploy/workflows.test.ts index c30cb91af3..74ac7ea654 100644 --- a/packages/wrangler/src/__tests__/deploy/workflows.test.ts +++ b/packages/wrangler/src/__tests__/deploy/workflows.test.ts @@ -49,7 +49,7 @@ vi.mock("../../package-manager", async (importOriginal) => ({ vi.mock("../../autoconfig/run"); vi.mock("../../autoconfig/frameworks/utils/packages"); -vi.mock("../../autoconfig/c3-vendor/command"); +vi.mock("@cloudflare/cli/command"); describe("deploy", () => { mockAccountId(); diff --git a/packages/wrangler/src/__tests__/setup.test.ts b/packages/wrangler/src/__tests__/setup.test.ts index e55ebb4220..3b504e6d4c 100644 --- a/packages/wrangler/src/__tests__/setup.test.ts +++ b/packages/wrangler/src/__tests__/setup.test.ts @@ -1,7 +1,7 @@ import { readFile } from "node:fs/promises"; +import * as cliPackages from "@cloudflare/cli/packages"; import { seed } from "@cloudflare/workers-utils/test-helpers"; import { afterEach, assert, describe, test, vi } from "vitest"; -import * as c3 from "../autoconfig/c3-vendor/packages"; import * as run from "../autoconfig/run"; import { clearOutputFilePath } from "../output"; import { mockConsoleMethods } from "./helpers/mock-console"; @@ -81,7 +81,7 @@ describe("wrangler setup", () => { // Let's not actually install Wrangler, to speed up tests const installSpy = vi - .spyOn(c3, "installWrangler") + .spyOn(cliPackages, "installWrangler") .mockImplementation(async () => {}); const runSpy = vi.spyOn(run, "runAutoConfig"); @@ -106,7 +106,7 @@ describe("wrangler setup", () => { }); // Let's not actually install Wrangler, to speed up tests - vi.spyOn(c3, "installWrangler").mockImplementation(async () => {}); + vi.spyOn(cliPackages, "installWrangler").mockImplementation(async () => {}); const runSpy = vi.spyOn(run, "runAutoConfig"); @@ -125,7 +125,7 @@ describe("wrangler setup", () => { }); const installSpy = vi - .spyOn(c3, "installWrangler") + .spyOn(cliPackages, "installWrangler") .mockImplementation(async () => {}); const runSpy = vi.spyOn(run, "runAutoConfig"); diff --git a/packages/wrangler/src/autoconfig/frameworks/angular.ts b/packages/wrangler/src/autoconfig/frameworks/angular.ts index 9e0f0dd069..b102d64e45 100644 --- a/packages/wrangler/src/autoconfig/frameworks/angular.ts +++ b/packages/wrangler/src/autoconfig/frameworks/angular.ts @@ -3,9 +3,9 @@ import { readFile, writeFile } from "node:fs/promises"; import { resolve } from "node:path"; import { brandColor, dim } from "@cloudflare/cli/colors"; import { spinner } from "@cloudflare/cli/interactive"; +import { installPackages } from "@cloudflare/cli/packages"; import { parseJSONC } from "@cloudflare/workers-utils"; import { dedent } from "../../utils/dedent"; -import { installPackages } from "../c3-vendor/packages"; import { Framework } from "."; import type { ConfigurationOptions, ConfigurationResults } from "."; import type { PackageManager } from "../../package-manager"; @@ -119,7 +119,7 @@ async function installAdditionalDependencies( packageManager: PackageManager, isWorkspaceRoot: boolean ) { - await installPackages(packageManager, ["xhr2"], { + await installPackages(packageManager.type, ["xhr2"], { dev: true, startText: "Installing additional dependencies", doneText: `${brandColor("installed")}`, diff --git a/packages/wrangler/src/autoconfig/frameworks/astro.ts b/packages/wrangler/src/autoconfig/frameworks/astro.ts index 145c63ecae..476a5d8be7 100644 --- a/packages/wrangler/src/autoconfig/frameworks/astro.ts +++ b/packages/wrangler/src/autoconfig/frameworks/astro.ts @@ -7,13 +7,13 @@ import { import { join } from "node:path"; import { updateStatus } from "@cloudflare/cli"; import { blue, brandColor, dim } from "@cloudflare/cli/colors"; +import { runCommand } from "@cloudflare/cli/command"; +import { installPackages } from "@cloudflare/cli/packages"; import { parseJSONC } from "@cloudflare/workers-utils"; import * as recast from "recast"; import semiver from "semiver"; import { logger } from "../../logger"; import { mergeObjectProperties, transformFile } from "../c3-vendor/codemod"; -import { runCommand } from "../c3-vendor/command"; -import { installPackages } from "../c3-vendor/packages"; import { AutoConfigFrameworkConfigurationError } from "../errors"; import { getInstalledPackageVersion } from "./utils/packages"; import { Framework } from "."; @@ -319,7 +319,7 @@ async function configureAstroLegacy( const astroCloudflarePackageVersion = astroMajorVersion === 5 ? 12 : 11; await installPackages( - packageManager, + packageManager.type, [`@astrojs/cloudflare@${astroCloudflarePackageVersion}`], { startText: `Installing @astrojs/cloudflare adapter (version ${astroCloudflarePackageVersion})`, diff --git a/packages/wrangler/src/autoconfig/frameworks/next.ts b/packages/wrangler/src/autoconfig/frameworks/next.ts index 3b453923ee..9efc9fb755 100644 --- a/packages/wrangler/src/autoconfig/frameworks/next.ts +++ b/packages/wrangler/src/autoconfig/frameworks/next.ts @@ -1,5 +1,5 @@ +import { runCommand } from "@cloudflare/cli/command"; import semiver from "semiver"; -import { runCommand } from "../c3-vendor/command"; import { AutoConfigFrameworkConfigurationError } from "../errors"; import { getInstalledPackageVersion } from "./utils/packages"; import { Framework } from "."; diff --git a/packages/wrangler/src/autoconfig/frameworks/nuxt.ts b/packages/wrangler/src/autoconfig/frameworks/nuxt.ts index d26a84124a..7a20b6527a 100644 --- a/packages/wrangler/src/autoconfig/frameworks/nuxt.ts +++ b/packages/wrangler/src/autoconfig/frameworks/nuxt.ts @@ -1,8 +1,8 @@ import path from "node:path"; import { brandColor, dim } from "@cloudflare/cli/colors"; +import { installPackages } from "@cloudflare/cli/packages"; import * as recast from "recast"; import { mergeObjectProperties, transformFile } from "../c3-vendor/codemod"; -import { installPackages } from "../c3-vendor/packages"; import { Framework } from "."; import type { ConfigurationOptions, ConfigurationResults } from "."; @@ -59,7 +59,7 @@ export class Nuxt extends Framework { isWorkspaceRoot, }: ConfigurationOptions): Promise { if (!dryRun) { - await installPackages(packageManager, ["nitro-cloudflare-dev"], { + await installPackages(packageManager.type, ["nitro-cloudflare-dev"], { dev: true, startText: "Installing the Cloudflare dev module", doneText: `${brandColor(`installed`)} ${dim("nitro-cloudflare-dev")}`, diff --git a/packages/wrangler/src/autoconfig/frameworks/qwik.ts b/packages/wrangler/src/autoconfig/frameworks/qwik.ts index 5ec010ad60..49f5fd78a1 100644 --- a/packages/wrangler/src/autoconfig/frameworks/qwik.ts +++ b/packages/wrangler/src/autoconfig/frameworks/qwik.ts @@ -1,10 +1,10 @@ import { endSection } from "@cloudflare/cli"; import { brandColor } from "@cloudflare/cli/colors"; +import { quoteShellArgs, runCommand } from "@cloudflare/cli/command"; import { spinner } from "@cloudflare/cli/interactive"; import * as recast from "recast"; import * as typescriptParser from "recast/parsers/typescript"; import { transformFile } from "../c3-vendor/codemod"; -import { quoteShellArgs, runCommand } from "../c3-vendor/command"; import { usesTypescript } from "../uses-typescript"; import { Framework } from "."; import type { ConfigurationOptions, ConfigurationResults } from "."; diff --git a/packages/wrangler/src/autoconfig/frameworks/react-router.ts b/packages/wrangler/src/autoconfig/frameworks/react-router.ts index 0615162670..dba0d94ae6 100644 --- a/packages/wrangler/src/autoconfig/frameworks/react-router.ts +++ b/packages/wrangler/src/autoconfig/frameworks/react-router.ts @@ -2,12 +2,12 @@ import assert from "node:assert"; import { existsSync, mkdirSync, writeFileSync } from "node:fs"; import path from "node:path"; import { brandColor, dim } from "@cloudflare/cli/colors"; +import { installPackages } from "@cloudflare/cli/packages"; import * as recast from "recast"; import semiver from "semiver"; import dedent from "ts-dedent"; import { logger } from "../../logger"; import { transformFile } from "../c3-vendor/codemod"; -import { installPackages } from "../c3-vendor/packages"; import { getInstalledPackageVersion } from "./utils/packages"; import { transformViteConfig } from "./utils/vite-config"; import { Framework } from "."; @@ -151,7 +151,7 @@ export class ReactRouter extends Framework { }: ConfigurationOptions): Promise { const viteEnvironmentKey = configPropertyName(projectPath); if (!dryRun) { - await installPackages(packageManager, ["@cloudflare/vite-plugin"], { + await installPackages(packageManager.type, ["@cloudflare/vite-plugin"], { dev: true, startText: "Installing the Cloudflare Vite plugin", doneText: `${brandColor(`installed`)} ${dim("@cloudflare/vite-plugin")}`, @@ -189,7 +189,7 @@ export class ReactRouter extends Framework { ` ); - await installPackages(packageManager, ["isbot"], { + await installPackages(packageManager.type, ["isbot"], { dev: true, startText: "Installing the isbot package", doneText: `${brandColor(`installed`)} ${dim("isbot")}`, diff --git a/packages/wrangler/src/autoconfig/frameworks/sveltekit.ts b/packages/wrangler/src/autoconfig/frameworks/sveltekit.ts index ef7f7b05f9..ec7126a072 100644 --- a/packages/wrangler/src/autoconfig/frameworks/sveltekit.ts +++ b/packages/wrangler/src/autoconfig/frameworks/sveltekit.ts @@ -1,7 +1,7 @@ import { writeFileSync } from "node:fs"; import { brandColor, dim } from "@cloudflare/cli/colors"; -import { runCommand } from "../c3-vendor/command"; -import { installPackages } from "../c3-vendor/packages"; +import { runCommand } from "@cloudflare/cli/command"; +import { installPackages } from "@cloudflare/cli/packages"; import { Framework } from "."; import type { ConfigurationOptions, ConfigurationResults } from "."; @@ -32,7 +32,7 @@ export class SvelteKit extends Framework { ); writeFileSync("static/.assetsignore", "_worker.js\n_routes.json"); - await installPackages(packageManager, [], { + await installPackages(packageManager.type, [], { startText: "Installing packages", doneText: `${brandColor("installed")}`, isWorkspaceRoot, diff --git a/packages/wrangler/src/autoconfig/frameworks/tanstack.ts b/packages/wrangler/src/autoconfig/frameworks/tanstack.ts index ae15b88ab6..b2f52e32c3 100644 --- a/packages/wrangler/src/autoconfig/frameworks/tanstack.ts +++ b/packages/wrangler/src/autoconfig/frameworks/tanstack.ts @@ -1,5 +1,5 @@ import { brandColor, dim } from "@cloudflare/cli/colors"; -import { installPackages } from "../c3-vendor/packages"; +import { installPackages } from "@cloudflare/cli/packages"; import { transformViteConfig } from "./utils/vite-config"; import { Framework } from "."; import type { ConfigurationOptions, ConfigurationResults } from "."; @@ -12,7 +12,7 @@ export class TanstackStart extends Framework { isWorkspaceRoot, }: ConfigurationOptions): Promise { if (!dryRun) { - await installPackages(packageManager, ["@cloudflare/vite-plugin"], { + await installPackages(packageManager.type, ["@cloudflare/vite-plugin"], { dev: true, startText: "Installing the Cloudflare Vite plugin", doneText: `${brandColor(`installed`)} ${dim("@cloudflare/vite-plugin")}`, diff --git a/packages/wrangler/src/autoconfig/frameworks/vike.ts b/packages/wrangler/src/autoconfig/frameworks/vike.ts index 1c5898a0f0..345caca26c 100644 --- a/packages/wrangler/src/autoconfig/frameworks/vike.ts +++ b/packages/wrangler/src/autoconfig/frameworks/vike.ts @@ -2,9 +2,9 @@ import assert from "node:assert"; import { existsSync } from "node:fs"; import path from "node:path"; import { brandColor } from "@cloudflare/cli/colors"; +import { installPackages } from "@cloudflare/cli/packages"; import * as recast from "recast"; import { transformFile } from "../c3-vendor/codemod"; -import { installPackages } from "../c3-vendor/packages"; import { isPackageInstalled } from "./utils/packages"; import { Framework } from "."; import type { ConfigurationOptions, ConfigurationResults } from "."; @@ -37,7 +37,7 @@ export class Vike extends Framework { // note: the following installation steps follow the guide in: https://vike.dev/cloudflare#get-started await installPackages( - packageManager, + packageManager.type, ["vike-photon", "@photonjs/cloudflare"], { startText: "Installing vike-photon and @photonjs/cloudflare", @@ -45,7 +45,7 @@ export class Vike extends Framework { isWorkspaceRoot, } ); - await installPackages(packageManager, ["@cloudflare/vite-plugin"], { + await installPackages(packageManager.type, ["@cloudflare/vite-plugin"], { dev: true, isWorkspaceRoot, }); diff --git a/packages/wrangler/src/autoconfig/frameworks/vite.ts b/packages/wrangler/src/autoconfig/frameworks/vite.ts index cbabbeae03..3546c0a267 100644 --- a/packages/wrangler/src/autoconfig/frameworks/vite.ts +++ b/packages/wrangler/src/autoconfig/frameworks/vite.ts @@ -1,5 +1,5 @@ import { brandColor, dim } from "@cloudflare/cli/colors"; -import { installPackages } from "../c3-vendor/packages"; +import { installPackages } from "@cloudflare/cli/packages"; import { checkIfViteConfigUsesCloudflarePlugin, transformViteConfig, @@ -19,7 +19,7 @@ export class Vite extends Framework { isWorkspaceRoot, }: ConfigurationOptions): Promise { if (!dryRun) { - await installPackages(packageManager, ["@cloudflare/vite-plugin"], { + await installPackages(packageManager.type, ["@cloudflare/vite-plugin"], { dev: true, startText: "Installing the Cloudflare Vite plugin", doneText: `${brandColor(`installed`)} ${dim("@cloudflare/vite-plugin")}`, diff --git a/packages/wrangler/src/autoconfig/frameworks/waku.ts b/packages/wrangler/src/autoconfig/frameworks/waku.ts index dd2e5bf3d3..3e5d11de25 100644 --- a/packages/wrangler/src/autoconfig/frameworks/waku.ts +++ b/packages/wrangler/src/autoconfig/frameworks/waku.ts @@ -4,11 +4,11 @@ import { writeFile } from "node:fs/promises"; import { join } from "node:path"; import { updateStatus } from "@cloudflare/cli"; import { blue, brandColor } from "@cloudflare/cli/colors"; +import { installPackages } from "@cloudflare/cli/packages"; import * as recast from "recast"; import semiver from "semiver"; import dedent from "ts-dedent"; import { transformFile } from "../c3-vendor/codemod"; -import { installPackages } from "../c3-vendor/packages"; import { AutoConfigFrameworkConfigurationError } from "../errors"; import { getInstalledPackageVersion } from "./utils/packages"; import { Framework } from "."; @@ -29,7 +29,7 @@ export class Waku extends Framework { if (!dryRun) { await installPackages( - packageManager, + packageManager.type, ["hono", "@cloudflare/vite-plugin"], { dev: true, diff --git a/packages/wrangler/src/autoconfig/run.ts b/packages/wrangler/src/autoconfig/run.ts index be68fbfb1d..235c8e28e1 100644 --- a/packages/wrangler/src/autoconfig/run.ts +++ b/packages/wrangler/src/autoconfig/run.ts @@ -2,6 +2,7 @@ import assert from "node:assert"; import { existsSync } from "node:fs"; import { readFile, writeFile } from "node:fs/promises"; import { resolve } from "node:path"; +import { installWrangler } from "@cloudflare/cli/packages"; import { FatalError, getLocalWorkerdCompatibilityDate, @@ -14,7 +15,6 @@ import { sendMetricsEvent } from "../metrics"; import { sanitizeError } from "../metrics/sanitization"; import { addWranglerToAssetsIgnore } from "./add-wrangler-assetsignore"; import { addWranglerToGitIgnore } from "./c3-vendor/add-wrangler-gitignore"; -import { installWrangler } from "./c3-vendor/packages"; import { assertNonConfigured, confirmAutoConfigDetails, @@ -164,11 +164,15 @@ export async function runAutoConfig( } logger.debug( - `Running autoconfig with:\n${JSON.stringify(autoConfigDetails, null, 2)}...` + `Running autoconfig with:\n${JSON.stringify( + autoConfigDetails, + null, + 2 + )}...` ); if (autoConfigSummary.wranglerInstall && enableWranglerInstallation) { - await installWrangler(packageManager, isWorkspaceRoot); + await installWrangler(packageManager.type, isWorkspaceRoot); } const configurationResults = await autoConfigDetails.framework.configure({ diff --git a/packages/wrangler/src/deploy/open-next.ts b/packages/wrangler/src/deploy/open-next.ts index 22cff9bb46..6cf55e16c2 100644 --- a/packages/wrangler/src/deploy/open-next.ts +++ b/packages/wrangler/src/deploy/open-next.ts @@ -1,7 +1,7 @@ import assert from "node:assert"; import { readdir } from "node:fs/promises"; +import { runCommand } from "@cloudflare/cli/command"; import { getOpenNextDeployFromEnv } from "@cloudflare/workers-utils"; -import { runCommand } from "../autoconfig/c3-vendor/command"; import { getInstalledPackageVersion } from "../autoconfig/frameworks/utils/packages"; import { logger } from "../logger"; import { getPackageManager } from "../package-manager"; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1c77a06b34..d7f2d5ab38 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1602,12 +1602,24 @@ importers: '@cloudflare/workers-utils': specifier: workspace:* version: link:../workers-utils + '@types/cross-spawn': + specifier: ^6.0.2 + version: 6.0.2 + '@types/which-pm-runs': + specifier: ^1.0.0 + version: 1.0.0 chalk: specifier: ^5.2.0 version: 5.3.0 + cross-spawn: + specifier: ^7.0.3 + version: 7.0.6 log-update: specifier: ^5.0.1 version: 5.0.1 + which-pm-runs: + specifier: ^2.0.0 + version: 2.0.0 packages/containers-shared: devDependencies: @@ -3970,9 +3982,6 @@ importers: '@types/command-exists': specifier: ^1.2.0 version: 1.2.0 - '@types/cross-spawn': - specifier: ^6.0.2 - version: 6.0.2 '@types/esprima': specifier: ^4.0.3 version: 4.0.3 @@ -4054,9 +4063,6 @@ importers: concurrently: specifier: ^8.2.2 version: 8.2.2 - cross-spawn: - specifier: ^7.0.3 - version: 7.0.6 date-fns: specifier: ^4.1.0 version: 4.1.0 @@ -14579,6 +14585,10 @@ packages: resolution: {integrity: sha512-n1brCuqClxfFfq/Rb0ICg9giSZqCS+pLtccdag6C2HyufBrh3fBOiy9nb6ggRMvWOVH5GrdJskj5iGTZNxd7SA==} engines: {node: '>=4'} + which-pm-runs@2.0.0: + resolution: {integrity: sha512-nZMKNZWw3urJ6GnFOS+rXYe2glmWFVlQQWnKYzYXKvxKjyLO4QqQfp/wIYjMJl35yOp2YG/TR58DcVbQ24Giew==} + engines: {node: '>=22.13'} + which-typed-array@1.1.19: resolution: {integrity: sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==} engines: {node: '>= 0.4'} @@ -25857,6 +25867,8 @@ snapshots: which-pm-runs@1.1.0: {} + which-pm-runs@2.0.0: {} + which-typed-array@1.1.19: dependencies: available-typed-arrays: 1.0.7