diff --git a/.changeset/nub-package-manager.md b/.changeset/nub-package-manager.md new file mode 100644 index 0000000000..ef87e32ef5 --- /dev/null +++ b/.changeset/nub-package-manager.md @@ -0,0 +1,8 @@ +--- +"@cloudflare/workers-utils": patch +"@cloudflare/cli-shared-helpers": patch +--- + +Add nub to the list of recognised package managers + +Projects using nub can now be automatically detected by their `nub.lock` file, and package installation helpers now support nub alongside npm, pnpm, yarn, and bun. diff --git a/packages/cli/packages.ts b/packages/cli/packages.ts index 63d801887c..e18d533e44 100644 --- a/packages/cli/packages.ts +++ b/packages/cli/packages.ts @@ -25,7 +25,7 @@ type InstallConfig = { * @param config.force - Whether to install with `--force` or not */ export const installPackages = async ( - packageManager: "npm" | "pnpm" | "yarn" | "bun", + packageManager: "npm" | "pnpm" | "yarn" | "bun" | "nub", packages: string[], config: InstallConfig = {} ) => { @@ -49,7 +49,9 @@ export const installPackages = async ( packageManager, ...(cmd ? [cmd] : []), ...packages, - ...(packageManager === "pnpm" ? ["--no-frozen-lockfile"] : []), + ...(packageManager === "pnpm" || packageManager === "nub" + ? ["--no-frozen-lockfile"] + : []), ...(force === true ? ["--force"] : []), ...getWorkspaceInstallRootFlag(packageManager, isWorkspaceRoot), ], @@ -129,7 +131,7 @@ export const installPackages = async ( * @returns an array containing the flag(/s) to use, or an empty array if not supported or not running in the workspace root. */ function getWorkspaceInstallRootFlag( - packageManager: "npm" | "pnpm" | "yarn" | "bun", + packageManager: "npm" | "pnpm" | "yarn" | "bun" | "nub", isWorkspaceRoot: boolean ): string[] { if (!isWorkspaceRoot) { @@ -137,6 +139,7 @@ function getWorkspaceInstallRootFlag( } switch (packageManager) { + case "nub": case "pnpm": return ["--workspace-root"]; case "yarn": @@ -152,7 +155,7 @@ function getWorkspaceInstallRootFlag( * Installs the latest version of wrangler in the project directory if it isn't already. */ export async function installWrangler( - packageManager: "npm" | "pnpm" | "yarn" | "bun", + packageManager: "npm" | "pnpm" | "yarn" | "bun" | "nub", isWorkspaceRoot: boolean ) { const packages = [`wrangler@latest`] satisfies string[]; diff --git a/packages/workers-utils/src/index.ts b/packages/workers-utils/src/index.ts index dccd99c3f4..e62bd93cf6 100644 --- a/packages/workers-utils/src/index.ts +++ b/packages/workers-utils/src/index.ts @@ -150,6 +150,7 @@ export { PnpmPackageManager, YarnPackageManager, BunPackageManager, + NubPackageManager, } from "./package-manager"; export { diff --git a/packages/workers-utils/src/package-manager.ts b/packages/workers-utils/src/package-manager.ts index b924b2c93f..3c58204eae 100644 --- a/packages/workers-utils/src/package-manager.ts +++ b/packages/workers-utils/src/package-manager.ts @@ -4,7 +4,7 @@ */ export interface PackageManager { /** The package manager identifier. */ - type: "npm" | "yarn" | "pnpm" | "bun"; + type: "npm" | "yarn" | "pnpm" | "bun" | "nub"; /** The command used to execute packages (e.g. `npx`, `pnpm`, `bunx`). */ npx: string; /** The command segments used to download and execute packages (e.g. `["npx"]`, `["pnpm", "dlx"]`). */ @@ -52,3 +52,13 @@ export const BunPackageManager = { dlx: ["bunx"], lockFiles: ["bun.lockb", "bun.lock"], } as const satisfies PackageManager; + +/** + * Manage packages using nub. + */ +export const NubPackageManager = { + type: "nub", + npx: "nubx", + dlx: ["nubx"], + lockFiles: ["nub.lock"], +} as const satisfies PackageManager; diff --git a/packages/workers-utils/tests/package-manager.test.ts b/packages/workers-utils/tests/package-manager.test.ts new file mode 100644 index 0000000000..dc3b6e3b03 --- /dev/null +++ b/packages/workers-utils/tests/package-manager.test.ts @@ -0,0 +1,47 @@ +import { existsSync } from "node:fs"; +import { join } from "node:path"; +import { describe, it } from "vitest"; +import { + BunPackageManager, + NpmPackageManager, + NubPackageManager, + PnpmPackageManager, + YarnPackageManager, +} from "../src/package-manager"; +import { runInTempDir, seed } from "../src/test-helpers"; +import type { PackageManager } from "../src/package-manager"; + +const packageManagers: PackageManager[] = [ + NpmPackageManager, + PnpmPackageManager, + YarnPackageManager, + BunPackageManager, + NubPackageManager, +]; + +describe("package managers", () => { + it("describes nub", ({ expect }) => { + expect(NubPackageManager).toEqual({ + type: "nub", + npx: "nubx", + dlx: ["nubx"], + lockFiles: ["nub.lock"], + }); + }); + + describe("lock file detection", () => { + runInTempDir(); + + // Detection is lock-file-based: a project is managed by the package + // manager whose lock file is present, matching how consumers resolve it. + const findByLockFile = (dir: string) => + packageManagers.find((pm) => + pm.lockFiles.some((lockFile) => existsSync(join(dir, lockFile))) + ); + + it("detects nub from nub.lock", async ({ expect }) => { + await seed({ "nub.lock": "" }); + expect(findByLockFile(process.cwd())).toBe(NubPackageManager); + }); + }); +});