Skip to content

Commit 38deb0f

Browse files
authored
fix(npm): respect npmrc config (#24001)
1 parent 9b6db08 commit 38deb0f

2 files changed

Lines changed: 73 additions & 2 deletions

File tree

packages/opencode/src/npm/index.ts

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
export * as Npm from "."
22

33
import path from "path"
4+
import { fileURLToPath } from "url"
45
import npa from "npm-package-arg"
56
import semver from "semver"
7+
import Config from "@npmcli/config"
8+
import { definitions, flatten, nerfDarts, shorthands } from "@npmcli/config/lib/definitions/index.js"
69
import { Effect, Schema, Context, Layer, Option, FileSystem } from "effect"
710
import { NodeFileSystem } from "@effect/platform-node"
811
import { AppFileSystem } from "@opencode-ai/shared/filesystem"
@@ -40,12 +43,39 @@ export interface Interface {
4043
export class Service extends Context.Service<Service, Interface>()("@opencode/Npm") {}
4144

4245
const illegal = process.platform === "win32" ? new Set(["<", ">", ":", '"', "|", "?", "*"]) : undefined
46+
const npmPath = fileURLToPath(new URL("../..", import.meta.url))
4347

4448
export function sanitize(pkg: string) {
4549
if (!illegal) return pkg
4650
return Array.from(pkg, (char) => (illegal.has(char) || char.charCodeAt(0) < 32 ? "_" : char)).join("")
4751
}
4852

53+
const loadOptions = (dir: string) =>
54+
Effect.tryPromise({
55+
try: async () => {
56+
const config = new Config({
57+
npmPath,
58+
cwd: dir,
59+
env: { ...process.env },
60+
argv: [process.execPath, process.execPath],
61+
execPath: process.execPath,
62+
platform: process.platform,
63+
definitions,
64+
flatten,
65+
nerfDarts,
66+
shorthands,
67+
warn: false,
68+
})
69+
await config.load()
70+
return config.flat
71+
},
72+
catch: (cause) =>
73+
new InstallFailedError({
74+
cause,
75+
dir,
76+
}),
77+
})
78+
4979
const resolveEntryPoint = (name: string, dir: string): EntryPoint => {
5080
let entrypoint: Option.Option<string>
5181
try {
@@ -81,7 +111,10 @@ export const layer = Layer.effect(
81111
Effect.gen(function* () {
82112
yield* flock.acquire(`npm-install:${input.dir}`)
83113
const { Arborist } = yield* Effect.promise(() => import("@npmcli/arborist"))
114+
const add = input.add ?? []
115+
const npmOptions = yield* loadOptions(input.dir)
84116
const arborist = new Arborist({
117+
...npmOptions,
85118
path: input.dir,
86119
binLinks: true,
87120
progress: false,
@@ -91,14 +124,15 @@ export const layer = Layer.effect(
91124
return yield* Effect.tryPromise({
92125
try: () =>
93126
arborist.reify({
94-
add: input?.add || [],
127+
...npmOptions,
128+
add,
95129
save: true,
96130
saveType: "prod",
97131
}),
98132
catch: (cause) =>
99133
new InstallFailedError({
100134
cause,
101-
add: input?.add,
135+
add,
102136
dir: input.dir,
103137
}),
104138
}) as Effect.Effect<ArboristTree, InstallFailedError>

packages/opencode/test/npm.test.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,18 @@
1+
import fs from "fs/promises"
2+
import path from "path"
13
import { describe, expect, test } from "bun:test"
24
import { Npm } from "../src/npm"
5+
import { tmpdir } from "./fixture/fixture"
36

47
const win = process.platform === "win32"
8+
const writePackage = (dir: string, pkg: Record<string, unknown>) =>
9+
Bun.write(
10+
path.join(dir, "package.json"),
11+
JSON.stringify({
12+
version: "1.0.0",
13+
...pkg,
14+
}),
15+
)
516

617
describe("Npm.sanitize", () => {
718
test("keeps normal scoped package specs unchanged", () => {
@@ -16,3 +27,29 @@ describe("Npm.sanitize", () => {
1627
expect(Npm.sanitize(spec)).toBe(expected)
1728
})
1829
})
30+
31+
describe("Npm.install", () => {
32+
test("respects omit from project .npmrc", async () => {
33+
await using tmp = await tmpdir()
34+
35+
await writePackage(tmp.path, {
36+
name: "fixture",
37+
dependencies: {
38+
"prod-pkg": "file:./prod-pkg",
39+
},
40+
devDependencies: {
41+
"dev-pkg": "file:./dev-pkg",
42+
},
43+
})
44+
await Bun.write(path.join(tmp.path, ".npmrc"), "omit=dev\n")
45+
await fs.mkdir(path.join(tmp.path, "prod-pkg"))
46+
await fs.mkdir(path.join(tmp.path, "dev-pkg"))
47+
await writePackage(path.join(tmp.path, "prod-pkg"), { name: "prod-pkg" })
48+
await writePackage(path.join(tmp.path, "dev-pkg"), { name: "dev-pkg" })
49+
50+
await Npm.install(tmp.path)
51+
52+
await expect(fs.stat(path.join(tmp.path, "node_modules", "prod-pkg"))).resolves.toBeDefined()
53+
await expect(fs.stat(path.join(tmp.path, "node_modules", "dev-pkg"))).rejects.toThrow()
54+
})
55+
})

0 commit comments

Comments
 (0)