Skip to content

Commit 93b7cd4

Browse files
fix: .npmrc not written into workspace root (#73)
1 parent 5d9b0d7 commit 93b7cd4

File tree

5 files changed

+149
-14
lines changed

5 files changed

+149
-14
lines changed

src/commands.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -87,12 +87,15 @@ export interface InstallOptions extends BaseOptions {
8787
}
8888

8989
export async function install(packages: JsrPackage[], options: InstallOptions) {
90-
const pkgManager = await getPkgManager(process.cwd(), options.pkgManagerName);
90+
const { pkgManager, root } = await getPkgManager(
91+
process.cwd(),
92+
options.pkgManagerName,
93+
);
9194

9295
if (packages.length > 0) {
9396
if (pkgManager instanceof Bun) {
9497
// Bun doesn't support reading from .npmrc yet
95-
await setupBunfigToml(pkgManager.cwd);
98+
await setupBunfigToml(root);
9699
} else if (pkgManager instanceof YarnBerry) {
97100
// Yarn v2+ does not read from .npmrc intentionally
98101
// https://yarnpkg.com/migration/guide#update-your-configuration-to-the-new-settings
@@ -101,7 +104,7 @@ export async function install(packages: JsrPackage[], options: InstallOptions) {
101104
JSR_NPM_REGISTRY_URL,
102105
);
103106
} else {
104-
await setupNpmRc(pkgManager.cwd);
107+
await setupNpmRc(root);
105108
}
106109

107110
console.log(`Installing ${kl.cyan(packages.join(", "))}...`);
@@ -111,7 +114,10 @@ export async function install(packages: JsrPackage[], options: InstallOptions) {
111114
}
112115

113116
export async function remove(packages: JsrPackage[], options: BaseOptions) {
114-
const pkgManager = await getPkgManager(process.cwd(), options.pkgManagerName);
117+
const { pkgManager } = await getPkgManager(
118+
process.cwd(),
119+
options.pkgManagerName,
120+
);
115121
console.log(`Removing ${kl.cyan(packages.join(", "))}...`);
116122
await pkgManager.remove(packages);
117123
}
@@ -186,7 +192,7 @@ export async function runScript(
186192
script: string,
187193
options: BaseOptions,
188194
) {
189-
const pkgManager = await getPkgManager(cwd, options.pkgManagerName);
195+
const { pkgManager } = await getPkgManager(cwd, options.pkgManagerName);
190196
await pkgManager.runScript(script);
191197
}
192198

src/pkg_manager.ts

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -195,27 +195,32 @@ function getPkgManagerFromEnv(value: string): PkgManagerName | null {
195195
export async function getPkgManager(
196196
cwd: string,
197197
pkgManagerName: PkgManagerName | null,
198-
) {
198+
): Promise<{ root: string; pkgManager: PackageManager }> {
199199
const envPkgManager = process.env.npm_config_user_agent;
200200
const fromEnv = envPkgManager !== undefined
201201
? getPkgManagerFromEnv(envPkgManager)
202202
: null;
203203

204-
const { projectDir, pkgManagerName: fromLockfile } = await findProjectDir(
205-
cwd,
206-
);
204+
const { projectDir, pkgManagerName: fromLockfile, root } =
205+
await findProjectDir(
206+
cwd,
207+
);
208+
const rootPath = root || projectDir;
207209

208210
const result = pkgManagerName || fromEnv || fromLockfile || "npm";
209211

212+
let pkgManager: PackageManager;
210213
if (result === "yarn") {
211-
return await isYarnBerry(projectDir)
214+
pkgManager = await isYarnBerry(projectDir)
212215
? new YarnBerry(projectDir)
213216
: new Yarn(projectDir);
214217
} else if (result === "pnpm") {
215-
return new Pnpm(projectDir);
218+
pkgManager = new Pnpm(projectDir);
216219
} else if (result === "bun") {
217-
return new Bun(projectDir);
220+
pkgManager = new Bun(projectDir);
221+
} else {
222+
pkgManager = new Npm(projectDir);
218223
}
219224

220-
return new Npm(projectDir);
225+
return { root: rootPath, pkgManager };
221226
}

src/utils.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ export interface ProjectInfo {
7272
projectDir: string;
7373
pkgManagerName: PkgManagerName | null;
7474
pkgJsonPath: string | null;
75+
root: string | null;
7576
}
7677
export async function findProjectDir(
7778
cwd: string,
@@ -80,6 +81,7 @@ export async function findProjectDir(
8081
projectDir: cwd,
8182
pkgManagerName: null,
8283
pkgJsonPath: null,
84+
root: null,
8385
},
8486
): Promise<ProjectInfo> {
8587
// Ensure we check for `package.json` first as this defines
@@ -92,6 +94,18 @@ export async function findProjectDir(
9294
result.projectDir = dir;
9395
result.pkgJsonPath = pkgJsonPath;
9496
}
97+
} else {
98+
const pkgJsonPath = path.join(dir, "package.json");
99+
if (await fileExists(pkgJsonPath)) {
100+
const json = await readJson<PkgJson>(pkgJsonPath);
101+
// npm + yarn + bun workspaces
102+
if (Array.isArray(json.workspaces)) {
103+
result.root = dir;
104+
} // pnpm workspaces
105+
else if (await fileExists(path.join(dir, "pnpm-workspace.yaml"))) {
106+
result.root = dir;
107+
}
108+
}
95109
}
96110

97111
const npmLockfile = path.join(dir, "package-lock.json");
@@ -245,6 +259,7 @@ export interface PkgJson {
245259
name?: string;
246260
version?: string;
247261
license?: string;
262+
workspaces?: string[];
248263

249264
dependencies?: Record<string, string>;
250265
devDependencies?: Record<string, string>;

test/commands.test.ts

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,79 @@ describe("install", () => {
101101
});
102102
});
103103

104+
it("jsr i @std/encoding - inside workspace member", async () => {
105+
await runInTempDir(async (dir) => {
106+
const parentPkgJson = { name: "", private: true, workspaces: ["sub"] };
107+
const parentPkgJsonPath = path.join(dir, "package.json");
108+
await writeJson<PkgJson>(parentPkgJsonPath, parentPkgJson);
109+
110+
// Create sub folder with package.json
111+
const subPkgJsonPath = path.join(dir, "sub", "package.json");
112+
await writeJson(subPkgJsonPath, { name: "foo" });
113+
114+
await runJsr(["i", "@std/encoding"], path.join(dir, "sub"));
115+
116+
assert.deepEqual(
117+
await readJson<PkgJson>(path.join(dir, "package.json")),
118+
parentPkgJson,
119+
);
120+
121+
const pkgJson = await readJson<PkgJson>(subPkgJsonPath);
122+
assert.ok(
123+
pkgJson.dependencies && "@std/encoding" in pkgJson.dependencies,
124+
"Missing dependency entry",
125+
);
126+
127+
assert.match(
128+
pkgJson.dependencies["@std/encoding"],
129+
/^npm:@jsr\/std__encoding@\^\d+\.\d+\.\d+.*$/,
130+
);
131+
132+
const npmRc = await readTextFile(path.join(dir, ".npmrc"));
133+
assert.ok(
134+
npmRc.includes("@jsr:registry=https://npm.jsr.io"),
135+
"Missing npmrc registry",
136+
);
137+
});
138+
});
139+
140+
it("jsr i @std/encoding - inside pnpm workspace member", async () => {
141+
await runInTempDir(async (dir) => {
142+
const parentPkgJson = { name: "", private: true };
143+
const parentPkgJsonPath = path.join(dir, "package.json");
144+
await writeJson<PkgJson>(parentPkgJsonPath, parentPkgJson);
145+
await writeTextFile(path.join(dir, "pnpm-workspace.yaml"), "");
146+
147+
// Create sub folder with package.json
148+
const subPkgJsonPath = path.join(dir, "sub", "package.json");
149+
await writeJson(subPkgJsonPath, { name: "foo" });
150+
151+
await runJsr(["i", "--pnpm", "@std/encoding"], path.join(dir, "sub"));
152+
153+
assert.deepEqual(
154+
await readJson<PkgJson>(path.join(dir, "package.json")),
155+
parentPkgJson,
156+
);
157+
158+
const pkgJson = await readJson<PkgJson>(subPkgJsonPath);
159+
assert.ok(
160+
pkgJson.dependencies && "@std/encoding" in pkgJson.dependencies,
161+
"Missing dependency entry",
162+
);
163+
164+
assert.match(
165+
pkgJson.dependencies["@std/encoding"],
166+
/^npm:@jsr\/std__encoding@\^\d+\.\d+\.\d+.*$/,
167+
);
168+
169+
const npmRc = await readTextFile(path.join(dir, ".npmrc"));
170+
assert.ok(
171+
npmRc.includes("@jsr:registry=https://npm.jsr.io"),
172+
"Missing npmrc registry",
173+
);
174+
});
175+
});
176+
104177
it("jsr i @std/[email protected] - with version", async () => {
105178
await withTempEnv(["i", "@std/[email protected]"], async (dir) => {
106179
const pkgJson = await readJson<PkgJson>(path.join(dir, "package.json"));

test/utils.test.ts

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
import * as assert from "assert/strict";
22
import * as path from "node:path";
33
import { runInTempDir } from "./test_utils";
4-
import { findProjectDir, writeJson, writeTextFile } from "../src/utils";
4+
import {
5+
findProjectDir,
6+
PkgJson,
7+
writeJson,
8+
writeTextFile,
9+
} from "../src/utils";
510

611
describe("findProjectDir", () => {
712
it("should return npm if package-lock.json is found", async () => {
@@ -57,4 +62,35 @@ describe("findProjectDir", () => {
5762
assert.strictEqual(result.projectDir, sub);
5863
});
5964
});
65+
66+
it("should find workspace root folder", async () => {
67+
await runInTempDir(async (tempDir) => {
68+
const sub = path.join(tempDir, "sub");
69+
70+
await writeJson<PkgJson>(path.join(tempDir, "package.json"), {
71+
workspaces: ["sub"],
72+
});
73+
await writeJson(path.join(sub, "package.json"), {});
74+
const result = await findProjectDir(sub);
75+
assert.strictEqual(
76+
result.root,
77+
tempDir,
78+
);
79+
});
80+
});
81+
82+
it("should find workspace root folder with pnpm workspaces", async () => {
83+
await runInTempDir(async (tempDir) => {
84+
const sub = path.join(tempDir, "sub");
85+
86+
await writeJson<PkgJson>(path.join(tempDir, "package.json"), {});
87+
await writeJson(path.join(sub, "package.json"), {});
88+
await writeTextFile(path.join(tempDir, "pnpm-workspace.yaml"), "");
89+
const result = await findProjectDir(sub);
90+
assert.strictEqual(
91+
result.root,
92+
tempDir,
93+
);
94+
});
95+
});
6096
});

0 commit comments

Comments
 (0)