Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .changeset/nub-package-manager.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
"@cloudflare/workers-utils": patch
Comment thread
devin-ai-integration[bot] marked this conversation as resolved.
Outdated
"@cloudflare/autoconfig": patch
"@cloudflare/cli-shared-helpers": patch
"wrangler": patch
---

Recognise nub as a package manager

wrangler now detects nub — from its `npm_config_user_agent` and an installed `nub` binary — and autoconfig detects nub projects by their `nub.lock`, alongside npm, pnpm, yarn, and bun. Package installation helpers use `nub`/`nubx` accordingly.
6 changes: 5 additions & 1 deletion packages/autoconfig/src/details/framework-detection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
BunPackageManager,
FatalError,
NpmPackageManager,
NubPackageManager,
PnpmPackageManager,
UserError,
YarnPackageManager,
Expand Down Expand Up @@ -82,7 +83,10 @@ export async function detectFramework(

// Convert the package manager detected by @netlify/build-info to our PackageManager type.
// This is populated after getBuildSettings() runs, which triggers the full detection chain.
const packageManager = convertDetectedPackageManager(project.packageManager);
// @netlify/build-info doesn't recognise nub, so prefer its nub.lock when present.
const packageManager = existsSync(join(projectPath, "nub.lock"))
? NubPackageManager
: convertDetectedPackageManager(project.packageManager);
Comment thread
NuroDev marked this conversation as resolved.
Outdated

const lockFileExists = packageManager.lockFiles.some((lockFile) =>
existsSync(join(projectPath, lockFile))
Expand Down
1 change: 1 addition & 0 deletions packages/cli/packages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@
}

switch (packageManager) {
case "nub":

Check failure on line 144 in packages/cli/packages.ts

View workflow job for this annotation

GitHub Actions / Checks

eslint(no-duplicate-case)

Duplicate case label
case "pnpm":
case "nub":
Comment thread
devin-ai-integration[bot] marked this conversation as resolved.
Outdated
return ["--workspace-root"];
Expand Down
1 change: 1 addition & 0 deletions packages/workers-utils/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ export {
PnpmPackageManager,
YarnPackageManager,
BunPackageManager,
NubPackageManager,
} from "./package-manager";

export {
Expand Down
12 changes: 11 additions & 1 deletion packages/workers-utils/src/package-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"]`). */
Expand Down Expand Up @@ -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;
Comment thread
devin-ai-integration[bot] marked this conversation as resolved.
47 changes: 47 additions & 0 deletions packages/workers-utils/tests/package-manager.test.ts
Original file line number Diff line number Diff line change
@@ -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);
});
});
Comment thread
NuroDev marked this conversation as resolved.
});
30 changes: 27 additions & 3 deletions packages/wrangler/src/package-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,25 @@ export {
PnpmPackageManager,
YarnPackageManager,
BunPackageManager,
NubPackageManager,
} from "@cloudflare/workers-utils";

import {
NpmPackageManager,
PnpmPackageManager,
YarnPackageManager,
BunPackageManager,
NubPackageManager,
} from "@cloudflare/workers-utils";
import type { PackageManager } from "@cloudflare/workers-utils";

export async function getPackageManager(): Promise<PackageManager> {
const [hasYarn, hasNpm, hasPnpm, hasBun] = await Promise.all([
const [hasYarn, hasNpm, hasPnpm, hasBun, hasNub] = await Promise.all([
supportsYarn(),
supportsNpm(),
supportsPnpm(),
supportsBun(),
supportsNub(),
]);

const userAgent = sniffUserAgent();
Expand All @@ -43,6 +46,9 @@ export async function getPackageManager(): Promise<PackageManager> {
} else if (userAgent === "bun" && hasBun) {
logger.debug("Using bun as package manager.");
return { ...BunPackageManager };
} else if (userAgent === "nub" && hasNub) {
logger.debug("Using nub as package manager.");
return { ...NubPackageManager };
}

// lastly, check what's installed
Expand All @@ -58,9 +64,12 @@ export async function getPackageManager(): Promise<PackageManager> {
} else if (hasBun) {
logger.debug("Using bun as package manager.");
return { ...BunPackageManager };
} else if (hasNub) {
logger.debug("Using nub as package manager.");
return { ...NubPackageManager };
} else {
throw new UserError(
"Unable to find a package manager. Supported managers are: npm, yarn, pnpm, and bun.",
"Unable to find a package manager. Supported managers are: npm, yarn, pnpm, bun, and nub.",
Comment thread
devin-ai-integration[bot] marked this conversation as resolved.
{
telemetryMessage: "package manager detection missing manager",
}
Expand Down Expand Up @@ -100,6 +109,10 @@ function supportsBun(): Promise<boolean> {
return supports("bun");
}

function supportsNub(): Promise<boolean> {
return supports("nub");
}

/**
* The environment variable `npm_config_user_agent` can be used to
* guess the package manager that was used to execute wrangler.
Expand All @@ -111,7 +124,13 @@ function supportsBun(): Promise<boolean> {
* - [yarn](https://yarnpkg.com/advanced/lifecycle-scripts#environment-variables)
* - [bun](https://github.com/oven-sh/bun/blob/550522e99b303d8172b7b16c5750d458cb056434/src/Global.zig#L205)
*/
export function sniffUserAgent(): "npm" | "pnpm" | "yarn" | "bun" | undefined {
export function sniffUserAgent():
| "npm"
| "pnpm"
| "yarn"
| "bun"
| "nub"
| undefined {
const userAgent = env.npm_config_user_agent;
if (userAgent === undefined) {
return undefined;
Expand All @@ -129,6 +148,11 @@ export function sniffUserAgent(): "npm" | "pnpm" | "yarn" | "bun" | undefined {
return "bun";
}

// nub's user agent contains "npm" (e.g. `nub/0.4.5 npm/? …`), so check it before npm.
if (userAgent.includes("nub")) {
return "nub";
}

// npm should come last as it is included in the user agent strings of other package managers
if (userAgent.includes("npm")) {
return "npm";
Expand Down
Loading