Skip to content

Commit 0293a8b

Browse files
committed
chore: revert changes overlapping with #18308
1 parent 850dbb9 commit 0293a8b

File tree

12 files changed

+596
-327
lines changed

12 files changed

+596
-327
lines changed

packages/opencode/package.json

Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
"scripts": {
99
"prepare": "effect-language-service patch || true",
1010
"typecheck": "tsgo --noEmit",
11-
"test": "bun test --timeout 30000 registry",
11+
"test": "bun test --timeout 30000",
1212
"build": "bun run script/build.ts",
1313
"dev": "bun run --conditions=browser ./src/index.ts",
1414
"random": "echo 'Random script updated at $(date)' && echo 'Change queued successfully' && echo 'Another change made' && echo 'Yet another change' && echo 'One more change' && echo 'Final change' && echo 'Another final change' && echo 'Yet another final change'",
@@ -26,15 +26,9 @@
2626
"exports": {
2727
"./*": "./src/*.ts"
2828
},
29-
"imports": {
30-
"#db": {
31-
"bun": "./src/storage/db.bun.ts",
32-
"node": "./src/storage/db.node.ts",
33-
"default": "./src/storage/db.bun.ts"
34-
}
35-
},
3629
"devDependencies": {
3730
"@babel/core": "7.28.4",
31+
"@effect/language-service": "0.79.0",
3832
"@octokit/webhooks-types": "7.6.1",
3933
"@opencode-ai/script": "workspace:*",
4034
"@parcel/watcher-darwin-arm64": "2.5.1",
@@ -51,14 +45,13 @@
5145
"@types/bun": "catalog:",
5246
"@types/cross-spawn": "6.0.6",
5347
"@types/mime-types": "3.0.1",
54-
"@types/npmcli__arborist": "6.3.3",
5548
"@types/semver": "^7.5.8",
5649
"@types/turndown": "5.0.5",
5750
"@types/which": "3.0.4",
5851
"@types/yargs": "17.0.33",
5952
"@typescript/native-preview": "catalog:",
60-
"effect": "catalog:",
61-
"drizzle-kit": "catalog:",
53+
"drizzle-kit": "1.0.0-beta.16-ea816b6",
54+
"drizzle-orm": "1.0.0-beta.16-ea816b6",
6255
"typescript": "catalog:",
6356
"vscode-languageserver-types": "3.17.5",
6457
"why-is-node-running": "3.2.2",
@@ -91,12 +84,9 @@
9184
"@clack/prompts": "1.0.0-alpha.1",
9285
"@gitlab/gitlab-ai-provider": "3.6.0",
9386
"@gitlab/opencode-gitlab-auth": "1.3.3",
94-
"@hono/node-server": "1.19.11",
95-
"@hono/node-ws": "1.3.0",
9687
"@hono/standard-validator": "0.1.5",
9788
"@hono/zod-validator": "catalog:",
9889
"@modelcontextprotocol/sdk": "1.25.2",
99-
"@npmcli/arborist": "9.4.0",
10090
"@octokit/graphql": "9.0.2",
10191
"@octokit/rest": "catalog:",
10292
"@openauthjs/openauth": "catalog:",
@@ -123,7 +113,8 @@
123113
"cross-spawn": "^7.0.6",
124114
"decimal.js": "10.5.0",
125115
"diff": "catalog:",
126-
"drizzle-orm": "catalog:",
116+
"drizzle-orm": "1.0.0-beta.16-ea816b6",
117+
"effect": "catalog:",
127118
"fuzzysort": "3.1.0",
128119
"glob": "13.0.5",
129120
"google-auth-library": "10.5.0",

packages/opencode/src/bun/index.ts

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
import z from "zod"
2+
import { Global } from "../global"
3+
import { Log } from "../util/log"
4+
import path from "path"
5+
import { Filesystem } from "../util/filesystem"
6+
import { NamedError } from "@opencode-ai/util/error"
7+
import { Lock } from "../util/lock"
8+
import { PackageRegistry } from "./registry"
9+
import { proxied } from "@/util/proxied"
10+
import { Process } from "../util/process"
11+
12+
export namespace BunProc {
13+
const log = Log.create({ service: "bun" })
14+
15+
export async function run(cmd: string[], options?: Process.RunOptions) {
16+
const full = [which(), ...cmd]
17+
log.info("running", {
18+
cmd: full,
19+
...options,
20+
})
21+
const result = await Process.run(full, {
22+
cwd: options?.cwd,
23+
abort: options?.abort,
24+
kill: options?.kill,
25+
timeout: options?.timeout,
26+
nothrow: options?.nothrow,
27+
env: {
28+
...process.env,
29+
...options?.env,
30+
BUN_BE_BUN: "1",
31+
},
32+
})
33+
log.info("done", {
34+
code: result.code,
35+
stdout: result.stdout.toString(),
36+
stderr: result.stderr.toString(),
37+
})
38+
return result
39+
}
40+
41+
export function which() {
42+
return process.execPath
43+
}
44+
45+
export const InstallFailedError = NamedError.create(
46+
"BunInstallFailedError",
47+
z.object({
48+
pkg: z.string(),
49+
version: z.string(),
50+
}),
51+
)
52+
53+
export async function install(pkg: string, version = "latest") {
54+
// Use lock to ensure only one install at a time
55+
using _ = await Lock.write("bun-install")
56+
57+
const mod = path.join(Global.Path.cache, "node_modules", pkg)
58+
const pkgjsonPath = path.join(Global.Path.cache, "package.json")
59+
const parsed = await Filesystem.readJson<{ dependencies: Record<string, string> }>(pkgjsonPath).catch(async () => {
60+
const result = { dependencies: {} as Record<string, string> }
61+
await Filesystem.writeJson(pkgjsonPath, result)
62+
return result
63+
})
64+
if (!parsed.dependencies) parsed.dependencies = {} as Record<string, string>
65+
const dependencies = parsed.dependencies
66+
const modExists = await Filesystem.exists(mod)
67+
const cachedVersion = dependencies[pkg]
68+
69+
if (!modExists || !cachedVersion) {
70+
// continue to install
71+
} else if (version !== "latest" && cachedVersion === version) {
72+
return mod
73+
} else if (version === "latest") {
74+
const isOutdated = await PackageRegistry.isOutdated(pkg, cachedVersion, Global.Path.cache)
75+
if (!isOutdated) return mod
76+
log.info("Cached version is outdated, proceeding with install", { pkg, cachedVersion })
77+
}
78+
79+
// Build command arguments
80+
const args = [
81+
"add",
82+
"--force",
83+
"--exact",
84+
// TODO: get rid of this case (see: https://github.com/oven-sh/bun/issues/19936)
85+
...(proxied() || process.env.CI ? ["--no-cache"] : []),
86+
"--cwd",
87+
Global.Path.cache,
88+
pkg + "@" + version,
89+
]
90+
91+
// Let Bun handle registry resolution:
92+
// - If .npmrc files exist, Bun will use them automatically
93+
// - If no .npmrc files exist, Bun will default to https://registry.npmjs.org
94+
// - No need to pass --registry flag
95+
log.info("installing package using Bun's default registry resolution", {
96+
pkg,
97+
version,
98+
})
99+
100+
await BunProc.run(args, {
101+
cwd: Global.Path.cache,
102+
}).catch((e) => {
103+
throw new InstallFailedError(
104+
{ pkg, version },
105+
{
106+
cause: e,
107+
},
108+
)
109+
})
110+
111+
// Resolve actual version from installed package when using "latest"
112+
// This ensures subsequent starts use the cached version until explicitly updated
113+
let resolvedVersion = version
114+
if (version === "latest") {
115+
const installedPkg = await Filesystem.readJson<{ version?: string }>(path.join(mod, "package.json")).catch(
116+
() => null,
117+
)
118+
if (installedPkg?.version) {
119+
resolvedVersion = installedPkg.version
120+
}
121+
}
122+
123+
parsed.dependencies[pkg] = resolvedVersion
124+
await Filesystem.writeJson(pkgjsonPath, parsed)
125+
return mod
126+
}
127+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import semver from "semver"
2+
import { Log } from "../util/log"
3+
import { Process } from "../util/process"
4+
5+
export namespace PackageRegistry {
6+
const log = Log.create({ service: "bun" })
7+
8+
function which() {
9+
return process.execPath
10+
}
11+
12+
export async function info(pkg: string, field: string, cwd?: string): Promise<string | null> {
13+
const { code, stdout, stderr } = await Process.run([which(), "info", pkg, field], {
14+
cwd,
15+
env: {
16+
...process.env,
17+
BUN_BE_BUN: "1",
18+
},
19+
nothrow: true,
20+
})
21+
22+
if (code !== 0) {
23+
log.warn("bun info failed", { pkg, field, code, stderr: stderr.toString() })
24+
return null
25+
}
26+
27+
const value = stdout.toString().trim()
28+
if (!value) return null
29+
return value
30+
}
31+
32+
export async function isOutdated(pkg: string, cachedVersion: string, cwd?: string): Promise<boolean> {
33+
const latestVersion = await info(pkg, "version", cwd)
34+
if (!latestVersion) {
35+
log.warn("Failed to resolve latest version, using cached", { pkg, cachedVersion })
36+
return false
37+
}
38+
39+
const isRange = /[\s^~*xX<>|=]/.test(cachedVersion)
40+
if (isRange) return !semver.satisfies(latestVersion, cachedVersion)
41+
42+
return semver.lt(cachedVersion, latestVersion)
43+
}
44+
}

packages/opencode/src/config/config.ts

Lines changed: 77 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Log } from "../util/log"
22
import path from "path"
3-
import { pathToFileURL } from "url"
3+
import { pathToFileURL, fileURLToPath } from "url"
44
import { createRequire } from "module"
55
import os from "os"
66
import z from "zod"
@@ -22,18 +22,22 @@ import {
2222
} from "jsonc-parser"
2323
import { Instance } from "../project/instance"
2424
import { LSPServer } from "../lsp/server"
25+
import { BunProc } from "@/bun"
2526
import { Installation } from "@/installation"
2627
import { ConfigMarkdown } from "./markdown"
2728
import { constants, existsSync } from "fs"
2829
import { Bus } from "@/bus"
2930
import { GlobalBus } from "@/bus/global"
3031
import { Event } from "../server/event"
3132
import { Glob } from "../util/glob"
33+
import { PackageRegistry } from "@/bun/registry"
34+
import { proxied } from "@/util/proxied"
3235
import { iife } from "@/util/iife"
3336
import { Account } from "@/account"
3437
import { ConfigPaths } from "./paths"
3538
import { Filesystem } from "@/util/filesystem"
36-
import { Npm } from "@/npm"
39+
import { Process } from "@/util/process"
40+
import { Lock } from "@/util/lock"
3741

3842
export namespace Config {
3943
const ModelId = z.string().meta({ $ref: "https://models.dev/model-schema.json#/$defs/Model" })
@@ -150,7 +154,8 @@ export namespace Config {
150154

151155
deps.push(
152156
iife(async () => {
153-
await installDependencies(dir)
157+
const shouldInstall = await needsInstall(dir)
158+
if (shouldInstall) await installDependencies(dir)
154159
}),
155160
)
156161

@@ -266,10 +271,6 @@ export namespace Config {
266271
}
267272

268273
export async function installDependencies(dir: string) {
269-
if (!(await isWritable(dir))) {
270-
log.info("config dir is not writable, skipping dependency install", { dir })
271-
return
272-
}
273274
const pkg = path.join(dir, "package.json")
274275
const targetVersion = Installation.isLocal() ? "*" : Installation.VERSION
275276

@@ -283,15 +284,43 @@ export namespace Config {
283284
await Filesystem.writeJson(pkg, json)
284285

285286
const gitignore = path.join(dir, ".gitignore")
286-
if (!(await Filesystem.exists(gitignore)))
287-
await Filesystem.write(
288-
gitignore,
289-
["node_modules", "plans", "package.json", "bun.lock", ".gitignore", "package-lock.json"].join("\n"),
290-
)
287+
const hasGitIgnore = await Filesystem.exists(gitignore)
288+
if (!hasGitIgnore)
289+
await Filesystem.write(gitignore, ["node_modules", "package.json", "bun.lock", ".gitignore"].join("\n"))
291290

292291
// Install any additional dependencies defined in the package.json
293292
// This allows local plugins and custom tools to use external packages
294-
await Npm.install(dir)
293+
using _ = await Lock.write("bun-install")
294+
await BunProc.run(
295+
[
296+
"install",
297+
// TODO: get rid of this case (see: https://github.com/oven-sh/bun/issues/19936)
298+
...(proxied() || process.env.CI ? ["--no-cache"] : []),
299+
],
300+
{ cwd: dir },
301+
).catch((err) => {
302+
if (err instanceof Process.RunFailedError) {
303+
const detail = {
304+
dir,
305+
cmd: err.cmd,
306+
code: err.code,
307+
stdout: err.stdout.toString(),
308+
stderr: err.stderr.toString(),
309+
}
310+
if (Flag.OPENCODE_STRICT_CONFIG_DEPS) {
311+
log.error("failed to install dependencies", detail)
312+
throw err
313+
}
314+
log.warn("failed to install dependencies", detail)
315+
return
316+
}
317+
318+
if (Flag.OPENCODE_STRICT_CONFIG_DEPS) {
319+
log.error("failed to install dependencies", { dir, error: err })
320+
throw err
321+
}
322+
log.warn("failed to install dependencies", { dir, error: err })
323+
})
295324
}
296325

297326
async function isWritable(dir: string) {
@@ -303,6 +332,41 @@ export namespace Config {
303332
}
304333
}
305334

335+
export async function needsInstall(dir: string) {
336+
// Some config dirs may be read-only.
337+
// Installing deps there will fail; skip installation in that case.
338+
const writable = await isWritable(dir)
339+
if (!writable) {
340+
log.debug("config dir is not writable, skipping dependency install", { dir })
341+
return false
342+
}
343+
344+
const nodeModules = path.join(dir, "node_modules")
345+
if (!existsSync(nodeModules)) return true
346+
347+
const pkg = path.join(dir, "package.json")
348+
const pkgExists = await Filesystem.exists(pkg)
349+
if (!pkgExists) return true
350+
351+
const parsed = await Filesystem.readJson<{ dependencies?: Record<string, string> }>(pkg).catch(() => null)
352+
const dependencies = parsed?.dependencies ?? {}
353+
const depVersion = dependencies["@opencode-ai/plugin"]
354+
if (!depVersion) return true
355+
356+
const targetVersion = Installation.isLocal() ? "latest" : Installation.VERSION
357+
if (targetVersion === "latest") {
358+
const isOutdated = await PackageRegistry.isOutdated("@opencode-ai/plugin", depVersion, dir)
359+
if (!isOutdated) return false
360+
log.info("Cached version is outdated, proceeding with install", {
361+
pkg: "@opencode-ai/plugin",
362+
cachedVersion: depVersion,
363+
})
364+
return true
365+
}
366+
if (depVersion === targetVersion) return false
367+
return true
368+
}
369+
306370
function rel(item: string, patterns: string[]) {
307371
const normalizedItem = item.replaceAll("\\", "/")
308372
for (const pattern of patterns) {

0 commit comments

Comments
 (0)