mirror of
https://github.com/anomalyco/opencode.git
synced 2026-05-17 18:12:42 +00:00
fix(npm): respect npmrc for version lookups (#24016)
This commit is contained in:
@@ -3,6 +3,7 @@ import { Effect, Layer, Stream } from "effect"
|
||||
import { HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http"
|
||||
import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"
|
||||
import { Installation } from "../../src/installation"
|
||||
import { InstallationChannel } from "../../src/installation/version"
|
||||
|
||||
const encoder = new TextEncoder()
|
||||
|
||||
@@ -68,11 +69,15 @@ describe("installation", () => {
|
||||
expect(result).toBe("4.0.0-beta.1")
|
||||
})
|
||||
|
||||
test("reads npm registry versions", async () => {
|
||||
test("reads npm versions via npm view", async () => {
|
||||
const calls: string[][] = []
|
||||
const layer = testLayer(
|
||||
() => jsonResponse({ version: "1.5.0" }),
|
||||
() => {
|
||||
throw new Error("unexpected http request")
|
||||
},
|
||||
(cmd, args) => {
|
||||
if (cmd === "npm" && args.includes("registry")) return "https://registry.npmjs.org\n"
|
||||
calls.push([cmd, ...args])
|
||||
if (cmd === "npm" && args[0] === "view") return '"1.5.0"\n'
|
||||
return ""
|
||||
},
|
||||
)
|
||||
@@ -81,18 +86,47 @@ describe("installation", () => {
|
||||
Installation.Service.use((svc) => svc.latest("npm")).pipe(Effect.provide(layer)),
|
||||
)
|
||||
expect(result).toBe("1.5.0")
|
||||
expect(calls).toContainEqual(["npm", "view", `opencode-ai@${InstallationChannel}`, "version", "--json"])
|
||||
})
|
||||
|
||||
test("reads npm registry versions for bun method", async () => {
|
||||
test("reads npm versions via bun pm view", async () => {
|
||||
const calls: string[][] = []
|
||||
const layer = testLayer(
|
||||
() => jsonResponse({ version: "1.6.0" }),
|
||||
() => "",
|
||||
() => {
|
||||
throw new Error("unexpected http request")
|
||||
},
|
||||
(cmd, args) => {
|
||||
calls.push([cmd, ...args])
|
||||
if (cmd === "bun" && args[0] === "pm") return '"1.6.0"\n'
|
||||
return ""
|
||||
},
|
||||
)
|
||||
|
||||
const result = await Effect.runPromise(
|
||||
Installation.Service.use((svc) => svc.latest("bun")).pipe(Effect.provide(layer)),
|
||||
)
|
||||
expect(result).toBe("1.6.0")
|
||||
expect(calls).toContainEqual(["bun", "pm", "view", `opencode-ai@${InstallationChannel}`, "version", "--json"])
|
||||
})
|
||||
|
||||
test("reads npm versions via pnpm view", async () => {
|
||||
const calls: string[][] = []
|
||||
const layer = testLayer(
|
||||
() => {
|
||||
throw new Error("unexpected http request")
|
||||
},
|
||||
(cmd, args) => {
|
||||
calls.push([cmd, ...args])
|
||||
if (cmd === "pnpm" && args[0] === "view") return '"1.7.0"\n'
|
||||
return ""
|
||||
},
|
||||
)
|
||||
|
||||
const result = await Effect.runPromise(
|
||||
Installation.Service.use((svc) => svc.latest("pnpm")).pipe(Effect.provide(layer)),
|
||||
)
|
||||
expect(result).toBe("1.7.0")
|
||||
expect(calls).toContainEqual(["pnpm", "view", `opencode-ai@${InstallationChannel}`, "version", "--json"])
|
||||
})
|
||||
|
||||
test("reads scoop manifest versions", async () => {
|
||||
|
||||
@@ -1,10 +1,50 @@
|
||||
import fs from "fs/promises"
|
||||
import path from "path"
|
||||
import { describe, expect, test } from "bun:test"
|
||||
import { Effect, Layer, Stream } from "effect"
|
||||
import { NodeFileSystem } from "@effect/platform-node"
|
||||
import { AppFileSystem } from "@opencode-ai/shared/filesystem"
|
||||
import { Global } from "@opencode-ai/shared/global"
|
||||
import { EffectFlock } from "@opencode-ai/shared/util/effect-flock"
|
||||
import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"
|
||||
import { Npm } from "../src/npm"
|
||||
import { tmpdir } from "./fixture/fixture"
|
||||
|
||||
const win = process.platform === "win32"
|
||||
const encoder = new TextEncoder()
|
||||
function mockSpawner(handler: (cmd: string, args: readonly string[]) => string = () => "") {
|
||||
const spawner = ChildProcessSpawner.make((command) => {
|
||||
const std = ChildProcess.isStandardCommand(command) ? command : undefined
|
||||
const output = handler(std?.command ?? "", std?.args ?? [])
|
||||
return Effect.succeed(
|
||||
ChildProcessSpawner.makeHandle({
|
||||
pid: ChildProcessSpawner.ProcessId(0),
|
||||
exitCode: Effect.succeed(ChildProcessSpawner.ExitCode(0)),
|
||||
isRunning: Effect.succeed(false),
|
||||
kill: () => Effect.void,
|
||||
stdin: { [Symbol.for("effect/Sink/TypeId")]: Symbol.for("effect/Sink/TypeId") } as any,
|
||||
stdout: output ? Stream.make(encoder.encode(output)) : Stream.empty,
|
||||
stderr: Stream.empty,
|
||||
all: Stream.empty,
|
||||
getInputFd: () => ({ [Symbol.for("effect/Sink/TypeId")]: Symbol.for("effect/Sink/TypeId") }) as any,
|
||||
getOutputFd: () => Stream.empty,
|
||||
unref: Effect.succeed(Effect.void),
|
||||
}),
|
||||
)
|
||||
})
|
||||
return Layer.succeed(ChildProcessSpawner.ChildProcessSpawner, spawner)
|
||||
}
|
||||
|
||||
function testLayer(spawnHandler?: (cmd: string, args: readonly string[]) => string) {
|
||||
return Npm.layer.pipe(
|
||||
Layer.provide(mockSpawner(spawnHandler)),
|
||||
Layer.provide(EffectFlock.layer),
|
||||
Layer.provide(AppFileSystem.layer),
|
||||
Layer.provide(Global.layer),
|
||||
Layer.provide(NodeFileSystem.layer),
|
||||
)
|
||||
}
|
||||
|
||||
const writePackage = (dir: string, pkg: Record<string, unknown>) =>
|
||||
Bun.write(
|
||||
path.join(dir, "package.json"),
|
||||
@@ -53,3 +93,47 @@ describe("Npm.install", () => {
|
||||
await expect(fs.stat(path.join(tmp.path, "node_modules", "dev-pkg"))).rejects.toThrow()
|
||||
})
|
||||
})
|
||||
|
||||
describe("Npm.outdated", () => {
|
||||
test("checks latest via npm view", async () => {
|
||||
const calls: string[][] = []
|
||||
const layer = testLayer((cmd, args) => {
|
||||
calls.push([cmd, ...args])
|
||||
if (cmd === "npm" && args[0] === "view") return '"2.0.0"\n'
|
||||
return ""
|
||||
})
|
||||
|
||||
const result = await Effect.runPromise(Npm.Service.use((svc) => svc.outdated("example", "1.0.0")).pipe(Effect.provide(layer)))
|
||||
|
||||
expect(result).toBe(true)
|
||||
expect(calls).toContainEqual(["npm", "view", "example", "dist-tags.latest", "--json"])
|
||||
})
|
||||
|
||||
test("keeps range comparison behavior", async () => {
|
||||
const layer = testLayer((cmd, args) => {
|
||||
if (cmd === "npm" && args[0] === "view") return '"2.3.0"\n'
|
||||
return ""
|
||||
})
|
||||
|
||||
const result = await Effect.runPromise(
|
||||
Npm.Service.use((svc) => svc.outdated("example", "^2.0.0")).pipe(Effect.provide(layer)),
|
||||
)
|
||||
|
||||
expect(result).toBe(false)
|
||||
})
|
||||
|
||||
test("falls back when npm view is unavailable", async () => {
|
||||
const calls: string[][] = []
|
||||
const layer = testLayer((cmd, args) => {
|
||||
calls.push([cmd, ...args])
|
||||
if (cmd === "pnpm" && args[0] === "view") return '"2.0.0"\n'
|
||||
return ""
|
||||
})
|
||||
|
||||
const result = await Effect.runPromise(Npm.Service.use((svc) => svc.outdated("example", "1.0.0")).pipe(Effect.provide(layer)))
|
||||
|
||||
expect(result).toBe(true)
|
||||
expect(calls).toContainEqual(["npm", "view", "example", "dist-tags.latest", "--json"])
|
||||
expect(calls).toContainEqual(["pnpm", "view", "example", "dist-tags.latest", "--json"])
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user