Skip to content

Commit e26b254

Browse files
authored
feat(create-react-router): detect nub package manager (#15276)
* Detect nub package manager * Detect nub in dev package manager helper * Use package manager detector in create-react-router * Mark nub support changes as minor
1 parent 40a4364 commit e26b254

6 files changed

Lines changed: 94 additions & 7 deletions

File tree

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Detect nub as a supported package manager when creating new projects

packages/create-react-router/__tests__/create-react-router-test.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -818,6 +818,39 @@ describe("create-react-router CLI", () => {
818818
process.env.npm_config_user_agent = originalUserAgent;
819819
});
820820

821+
it("recognizes when nub was used to run the command", async () => {
822+
let originalUserAgent = process.env.npm_config_user_agent;
823+
process.env.npm_config_user_agent =
824+
"nub/0.1.0 npm/? node/v24.0.0 linux x64";
825+
826+
let projectDir = getProjectDir("nub-create-from-user-agent");
827+
828+
mockSpawnSuccess();
829+
830+
// Suppress terminal output
831+
let stdoutMock = jest
832+
.spyOn(process.stdout, "write")
833+
.mockImplementation(() => true);
834+
835+
await createReactRouter([
836+
projectDir,
837+
"--template",
838+
path.join(__dirname, "fixtures", "blank"),
839+
"--no-git-init",
840+
"--yes",
841+
"--no-agent-skills",
842+
]);
843+
844+
stdoutMock.mockReset();
845+
846+
expect(mockedSpawn).toHaveBeenCalledWith(
847+
"nub",
848+
expect.arrayContaining(["install"]),
849+
expect.anything(),
850+
);
851+
process.env.npm_config_user_agent = originalUserAgent;
852+
});
853+
821854
it("supports specifying the package manager, regardless of user agent", async () => {
822855
let originalUserAgent = process.env.npm_config_user_agent;
823856
process.env.npm_config_user_agent =

packages/create-react-router/index.ts

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -158,10 +158,7 @@ async function getContext(argv: string[]): Promise<Context> {
158158
showInstallOutput: getBooleanArg(values["show-install-output"]) ?? false,
159159
noMotion: getBooleanArg(values["no-motion"]),
160160
pkgManager: validatePackageManager(
161-
getStringArg(values["package-manager"]) ??
162-
// npm, pnpm, Yarn, Bun and Deno (v2.0.5+) set the user agent environment variable that can be used
163-
// to determine which package manager ran the command.
164-
(process.env.npm_config_user_agent ?? "npm").split("/")[0],
161+
getStringArg(values["package-manager"]) ?? detectPackageManager() ?? "npm",
165162
),
166163
projectName,
167164
prompt,
@@ -574,13 +571,36 @@ async function doneStep(ctx: Context) {
574571
await sleep(200);
575572
}
576573

577-
const validPackageManagers = ["npm", "yarn", "pnpm", "bun", "deno"] as const;
574+
const validPackageManagers = ["npm", "yarn", "pnpm", "bun", "deno", "nub"] as const;
578575
type PackageManager = (typeof validPackageManagers)[number];
579576

580577
function validatePackageManager(pkgManager: string): PackageManager {
581578
return validPackageManagers.find((name) => pkgManager === name) ?? "npm";
582579
}
583580

581+
/**
582+
* Determine which package manager the user prefers.
583+
*
584+
* npm, pnpm, Yarn, Bun, Deno, and nub set the user agent environment variable
585+
* that can be used to determine which package manager ran the command.
586+
*/
587+
function detectPackageManager(): PackageManager | undefined {
588+
let { npm_config_user_agent } = process.env;
589+
if (!npm_config_user_agent) return undefined;
590+
try {
591+
let pkgManager = npm_config_user_agent.split("/")[0];
592+
if (pkgManager === "npm") return "npm";
593+
if (pkgManager === "pnpm") return "pnpm";
594+
if (pkgManager === "yarn") return "yarn";
595+
if (pkgManager === "bun") return "bun";
596+
if (pkgManager === "deno") return "deno";
597+
if (pkgManager === "nub") return "nub";
598+
return undefined;
599+
} catch {
600+
return undefined;
601+
}
602+
}
603+
584604
async function installDependencies({
585605
pkgManager,
586606
cwd,
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Detect nub as a supported package manager when installing framework dependencies
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { detectPackageManager } from "../cli/detectPackageManager";
2+
3+
describe("detectPackageManager", () => {
4+
let originalUserAgent = process.env.npm_config_user_agent;
5+
6+
afterEach(() => {
7+
process.env.npm_config_user_agent = originalUserAgent;
8+
});
9+
10+
it.each(["npm", "pnpm", "yarn", "bun", "nub"] as const)(
11+
"detects %s from the user agent",
12+
(packageManager) => {
13+
process.env.npm_config_user_agent = `${packageManager}/1.0.0 npm/? node/v24.0.0 linux x64`;
14+
15+
expect(detectPackageManager()).toBe(packageManager);
16+
},
17+
);
18+
19+
it("returns undefined for unknown package managers", () => {
20+
process.env.npm_config_user_agent =
21+
"unknown/1.0.0 npm/? node/v24.0.0 linux x64";
22+
23+
expect(detectPackageManager()).toBeUndefined();
24+
});
25+
26+
it("returns undefined without a user agent", () => {
27+
process.env.npm_config_user_agent = undefined;
28+
29+
expect(detectPackageManager()).toBeUndefined();
30+
});
31+
});

packages/react-router-dev/cli/detectPackageManager.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
type PackageManager = "npm" | "pnpm" | "yarn" | "bun";
1+
type PackageManager = "npm" | "pnpm" | "yarn" | "bun" | "nub";
22

33
/**
44
* Determine which package manager the user prefers.
55
*
6-
* npm, pnpm and Yarn set the user agent environment variable
6+
* npm, pnpm, Yarn, Bun, and nub set the user agent environment variable
77
* that can be used to determine which package manager ran
88
* the command.
99
*/
@@ -16,6 +16,7 @@ export const detectPackageManager = (): PackageManager | undefined => {
1616
if (pkgManager === "pnpm") return "pnpm";
1717
if (pkgManager === "yarn") return "yarn";
1818
if (pkgManager === "bun") return "bun";
19+
if (pkgManager === "nub") return "nub";
1920
return undefined;
2021
} catch {
2122
return undefined;

0 commit comments

Comments
 (0)