From ca2099e69dbbc24d91a25ab07bb57c92d78747f4 Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Thu, 19 Mar 2026 19:40:40 -0400 Subject: [PATCH 01/16] refactor: replace BunProc with Npm module using @npmcli/arborist --- packages/opencode/package.json | 2 + packages/opencode/src/bun/index.ts | 127 ------------ packages/opencode/src/bun/registry.ts | 44 ---- packages/opencode/src/config/config.ts | 90 ++------ packages/opencode/src/format/formatter.ts | 148 ++++++++------ packages/opencode/src/format/index.ts | 21 +- packages/opencode/src/lsp/server.ts | 203 +++---------------- packages/opencode/src/npm/index.ts | 180 ++++++++++++++++ packages/opencode/src/plugin/index.ts | 16 +- packages/opencode/src/provider/provider.ts | 4 +- packages/opencode/test/bun.test.ts | 53 ----- packages/opencode/test/config/config.test.ts | 36 +--- 12 files changed, 334 insertions(+), 590 deletions(-) delete mode 100644 packages/opencode/src/bun/index.ts delete mode 100644 packages/opencode/src/bun/registry.ts create mode 100644 packages/opencode/src/npm/index.ts delete mode 100644 packages/opencode/test/bun.test.ts diff --git a/packages/opencode/package.json b/packages/opencode/package.json index 049573e3e5..5e37e2749a 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -45,6 +45,7 @@ "@types/bun": "catalog:", "@types/cross-spawn": "6.0.6", "@types/mime-types": "3.0.1", + "@types/npmcli__arborist": "6.3.3", "@types/semver": "^7.5.8", "@types/turndown": "5.0.5", "@types/which": "3.0.4", @@ -87,6 +88,7 @@ "@hono/standard-validator": "0.1.5", "@hono/zod-validator": "catalog:", "@modelcontextprotocol/sdk": "1.25.2", + "@npmcli/arborist": "9.4.0", "@octokit/graphql": "9.0.2", "@octokit/rest": "catalog:", "@openauthjs/openauth": "catalog:", diff --git a/packages/opencode/src/bun/index.ts b/packages/opencode/src/bun/index.ts deleted file mode 100644 index d6c4538259..0000000000 --- a/packages/opencode/src/bun/index.ts +++ /dev/null @@ -1,127 +0,0 @@ -import z from "zod" -import { Global } from "../global" -import { Log } from "../util/log" -import path from "path" -import { Filesystem } from "../util/filesystem" -import { NamedError } from "@opencode-ai/util/error" -import { Lock } from "../util/lock" -import { PackageRegistry } from "./registry" -import { proxied } from "@/util/proxied" -import { Process } from "../util/process" - -export namespace BunProc { - const log = Log.create({ service: "bun" }) - - export async function run(cmd: string[], options?: Process.RunOptions) { - const full = [which(), ...cmd] - log.info("running", { - cmd: full, - ...options, - }) - const result = await Process.run(full, { - cwd: options?.cwd, - abort: options?.abort, - kill: options?.kill, - timeout: options?.timeout, - nothrow: options?.nothrow, - env: { - ...process.env, - ...options?.env, - BUN_BE_BUN: "1", - }, - }) - log.info("done", { - code: result.code, - stdout: result.stdout.toString(), - stderr: result.stderr.toString(), - }) - return result - } - - export function which() { - return process.execPath - } - - export const InstallFailedError = NamedError.create( - "BunInstallFailedError", - z.object({ - pkg: z.string(), - version: z.string(), - }), - ) - - export async function install(pkg: string, version = "latest") { - // Use lock to ensure only one install at a time - using _ = await Lock.write("bun-install") - - const mod = path.join(Global.Path.cache, "node_modules", pkg) - const pkgjsonPath = path.join(Global.Path.cache, "package.json") - const parsed = await Filesystem.readJson<{ dependencies: Record }>(pkgjsonPath).catch(async () => { - const result = { dependencies: {} as Record } - await Filesystem.writeJson(pkgjsonPath, result) - return result - }) - if (!parsed.dependencies) parsed.dependencies = {} as Record - const dependencies = parsed.dependencies - const modExists = await Filesystem.exists(mod) - const cachedVersion = dependencies[pkg] - - if (!modExists || !cachedVersion) { - // continue to install - } else if (version !== "latest" && cachedVersion === version) { - return mod - } else if (version === "latest") { - const isOutdated = await PackageRegistry.isOutdated(pkg, cachedVersion, Global.Path.cache) - if (!isOutdated) return mod - log.info("Cached version is outdated, proceeding with install", { pkg, cachedVersion }) - } - - // Build command arguments - const args = [ - "add", - "--force", - "--exact", - // TODO: get rid of this case (see: https://github.com/oven-sh/bun/issues/19936) - ...(proxied() || process.env.CI ? ["--no-cache"] : []), - "--cwd", - Global.Path.cache, - pkg + "@" + version, - ] - - // Let Bun handle registry resolution: - // - If .npmrc files exist, Bun will use them automatically - // - If no .npmrc files exist, Bun will default to https://registry.npmjs.org - // - No need to pass --registry flag - log.info("installing package using Bun's default registry resolution", { - pkg, - version, - }) - - await BunProc.run(args, { - cwd: Global.Path.cache, - }).catch((e) => { - throw new InstallFailedError( - { pkg, version }, - { - cause: e, - }, - ) - }) - - // Resolve actual version from installed package when using "latest" - // This ensures subsequent starts use the cached version until explicitly updated - let resolvedVersion = version - if (version === "latest") { - const installedPkg = await Filesystem.readJson<{ version?: string }>(path.join(mod, "package.json")).catch( - () => null, - ) - if (installedPkg?.version) { - resolvedVersion = installedPkg.version - } - } - - parsed.dependencies[pkg] = resolvedVersion - await Filesystem.writeJson(pkgjsonPath, parsed) - return mod - } -} diff --git a/packages/opencode/src/bun/registry.ts b/packages/opencode/src/bun/registry.ts deleted file mode 100644 index e43e20e6c5..0000000000 --- a/packages/opencode/src/bun/registry.ts +++ /dev/null @@ -1,44 +0,0 @@ -import semver from "semver" -import { Log } from "../util/log" -import { Process } from "../util/process" - -export namespace PackageRegistry { - const log = Log.create({ service: "bun" }) - - function which() { - return process.execPath - } - - export async function info(pkg: string, field: string, cwd?: string): Promise { - const { code, stdout, stderr } = await Process.run([which(), "info", pkg, field], { - cwd, - env: { - ...process.env, - BUN_BE_BUN: "1", - }, - nothrow: true, - }) - - if (code !== 0) { - log.warn("bun info failed", { pkg, field, code, stderr: stderr.toString() }) - return null - } - - const value = stdout.toString().trim() - if (!value) return null - return value - } - - export async function isOutdated(pkg: string, cachedVersion: string, cwd?: string): Promise { - const latestVersion = await info(pkg, "version", cwd) - if (!latestVersion) { - log.warn("Failed to resolve latest version, using cached", { pkg, cachedVersion }) - return false - } - - const isRange = /[\s^~*xX<>|=]/.test(cachedVersion) - if (isRange) return !semver.satisfies(latestVersion, cachedVersion) - - return semver.lt(cachedVersion, latestVersion) - } -} diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index 47afdfd7d0..594ebc17ae 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -1,6 +1,6 @@ import { Log } from "../util/log" import path from "path" -import { pathToFileURL, fileURLToPath } from "url" +import { pathToFileURL } from "url" import { createRequire } from "module" import os from "os" import z from "zod" @@ -22,7 +22,6 @@ import { } from "jsonc-parser" import { Instance } from "../project/instance" import { LSPServer } from "../lsp/server" -import { BunProc } from "@/bun" import { Installation } from "@/installation" import { ConfigMarkdown } from "./markdown" import { constants, existsSync } from "fs" @@ -30,14 +29,11 @@ import { Bus } from "@/bus" import { GlobalBus } from "@/bus/global" import { Event } from "../server/event" import { Glob } from "../util/glob" -import { PackageRegistry } from "@/bun/registry" -import { proxied } from "@/util/proxied" import { iife } from "@/util/iife" import { Account } from "@/account" import { ConfigPaths } from "./paths" import { Filesystem } from "@/util/filesystem" -import { Process } from "@/util/process" -import { Lock } from "@/util/lock" +import { Npm } from "@/npm" export namespace Config { const ModelId = z.string().meta({ $ref: "https://models.dev/model-schema.json#/$defs/Model" }) @@ -154,8 +150,7 @@ export namespace Config { deps.push( iife(async () => { - const shouldInstall = await needsInstall(dir) - if (shouldInstall) await installDependencies(dir) + await installDependencies(dir) }), ) @@ -271,6 +266,10 @@ export namespace Config { } export async function installDependencies(dir: string) { + if (!(await isWritable(dir))) { + log.info("config dir is not writable, skipping dependency install", { dir }) + return + } const pkg = path.join(dir, "package.json") const targetVersion = Installation.isLocal() ? "*" : Installation.VERSION @@ -284,43 +283,15 @@ export namespace Config { await Filesystem.writeJson(pkg, json) const gitignore = path.join(dir, ".gitignore") - const hasGitIgnore = await Filesystem.exists(gitignore) - if (!hasGitIgnore) - await Filesystem.write(gitignore, ["node_modules", "package.json", "bun.lock", ".gitignore"].join("\n")) + if (!(await Filesystem.exists(gitignore))) + await Filesystem.write( + gitignore, + ["node_modules", "plans", "package.json", "bun.lock", ".gitignore", "package-lock.json"].join("\n"), + ) // Install any additional dependencies defined in the package.json // This allows local plugins and custom tools to use external packages - using _ = await Lock.write("bun-install") - await BunProc.run( - [ - "install", - // TODO: get rid of this case (see: https://github.com/oven-sh/bun/issues/19936) - ...(proxied() || process.env.CI ? ["--no-cache"] : []), - ], - { cwd: dir }, - ).catch((err) => { - if (err instanceof Process.RunFailedError) { - const detail = { - dir, - cmd: err.cmd, - code: err.code, - stdout: err.stdout.toString(), - stderr: err.stderr.toString(), - } - if (Flag.OPENCODE_STRICT_CONFIG_DEPS) { - log.error("failed to install dependencies", detail) - throw err - } - log.warn("failed to install dependencies", detail) - return - } - - if (Flag.OPENCODE_STRICT_CONFIG_DEPS) { - log.error("failed to install dependencies", { dir, error: err }) - throw err - } - log.warn("failed to install dependencies", { dir, error: err }) - }) + await Npm.install(dir) } async function isWritable(dir: string) { @@ -332,41 +303,6 @@ export namespace Config { } } - export async function needsInstall(dir: string) { - // Some config dirs may be read-only. - // Installing deps there will fail; skip installation in that case. - const writable = await isWritable(dir) - if (!writable) { - log.debug("config dir is not writable, skipping dependency install", { dir }) - return false - } - - const nodeModules = path.join(dir, "node_modules") - if (!existsSync(nodeModules)) return true - - const pkg = path.join(dir, "package.json") - const pkgExists = await Filesystem.exists(pkg) - if (!pkgExists) return true - - const parsed = await Filesystem.readJson<{ dependencies?: Record }>(pkg).catch(() => null) - const dependencies = parsed?.dependencies ?? {} - const depVersion = dependencies["@opencode-ai/plugin"] - if (!depVersion) return true - - const targetVersion = Installation.isLocal() ? "latest" : Installation.VERSION - if (targetVersion === "latest") { - const isOutdated = await PackageRegistry.isOutdated("@opencode-ai/plugin", depVersion, dir) - if (!isOutdated) return false - log.info("Cached version is outdated, proceeding with install", { - pkg: "@opencode-ai/plugin", - cachedVersion: depVersion, - }) - return true - } - if (depVersion === targetVersion) return false - return true - } - function rel(item: string, patterns: string[]) { const normalizedItem = item.replaceAll("\\", "/") for (const pattern of patterns) { diff --git a/packages/opencode/src/format/formatter.ts b/packages/opencode/src/format/formatter.ts index 9e96b2305c..5424520cab 100644 --- a/packages/opencode/src/format/formatter.ts +++ b/packages/opencode/src/format/formatter.ts @@ -1,40 +1,40 @@ import { text } from "node:stream/consumers" -import { BunProc } from "../bun" import { Instance } from "../project/instance" import { Filesystem } from "../util/filesystem" import { Process } from "../util/process" import { which } from "../util/which" import { Flag } from "@/flag/flag" +import { Npm } from "@/npm" export interface Info { name: string - command: string[] environment?: Record extensions: string[] - enabled(): Promise + enabled(): Promise } export const gofmt: Info = { name: "gofmt", - command: ["gofmt", "-w", "$FILE"], extensions: [".go"], async enabled() { - return which("gofmt") !== null + const p = which("gofmt") + if (p === null) return false + return [p, "-w", "$FILE"] }, } export const mix: Info = { name: "mix", - command: ["mix", "format", "$FILE"], extensions: [".ex", ".exs", ".eex", ".heex", ".leex", ".neex", ".sface"], async enabled() { - return which("mix") !== null + const p = which("mix") + if (p === null) return false + return [p, "format", "$FILE"] }, } export const prettier: Info = { name: "prettier", - command: [BunProc.which(), "x", "prettier", "--write", "$FILE"], environment: { BUN_BE_BUN: "1", }, @@ -73,8 +73,9 @@ export const prettier: Info = { dependencies?: Record devDependencies?: Record }>(item) - if (json.dependencies?.prettier) return true - if (json.devDependencies?.prettier) return true + if (json.dependencies?.prettier || json.devDependencies?.prettier) { + return [await Npm.which("prettier"), "--write", "$FILE"] + } } return false }, @@ -82,7 +83,6 @@ export const prettier: Info = { export const oxfmt: Info = { name: "oxfmt", - command: [BunProc.which(), "x", "oxfmt", "$FILE"], environment: { BUN_BE_BUN: "1", }, @@ -95,8 +95,9 @@ export const oxfmt: Info = { dependencies?: Record devDependencies?: Record }>(item) - if (json.dependencies?.oxfmt) return true - if (json.devDependencies?.oxfmt) return true + if (json.dependencies?.oxfmt || json.devDependencies?.oxfmt) { + return [await Npm.which("oxfmt"), "$FILE"] + } } return false }, @@ -104,7 +105,6 @@ export const oxfmt: Info = { export const biome: Info = { name: "biome", - command: [BunProc.which(), "x", "@biomejs/biome", "check", "--write", "$FILE"], environment: { BUN_BE_BUN: "1", }, @@ -141,7 +141,7 @@ export const biome: Info = { for (const config of configs) { const found = await Filesystem.findUp(config, Instance.directory, Instance.worktree) if (found.length > 0) { - return true + return [await Npm.which("@biomejs/biome"), "check", "--write", "$FILE"] } } return false @@ -150,47 +150,49 @@ export const biome: Info = { export const zig: Info = { name: "zig", - command: ["zig", "fmt", "$FILE"], extensions: [".zig", ".zon"], async enabled() { - return which("zig") !== null + const p = which("zig") + if (p === null) return false + return [p, "fmt", "$FILE"] }, } export const clang: Info = { name: "clang-format", - command: ["clang-format", "-i", "$FILE"], extensions: [".c", ".cc", ".cpp", ".cxx", ".c++", ".h", ".hh", ".hpp", ".hxx", ".h++", ".ino", ".C", ".H"], async enabled() { const items = await Filesystem.findUp(".clang-format", Instance.directory, Instance.worktree) - return items.length > 0 + if (items.length === 0) return false + return ["clang-format", "-i", "$FILE"] }, } export const ktlint: Info = { name: "ktlint", - command: ["ktlint", "-F", "$FILE"], extensions: [".kt", ".kts"], async enabled() { - return which("ktlint") !== null + const p = which("ktlint") + if (p === null) return false + return [p, "-F", "$FILE"] }, } export const ruff: Info = { name: "ruff", - command: ["ruff", "format", "$FILE"], extensions: [".py", ".pyi"], async enabled() { - if (!which("ruff")) return false + const p = which("ruff") + if (p === null) return false const configs = ["pyproject.toml", "ruff.toml", ".ruff.toml"] for (const config of configs) { const found = await Filesystem.findUp(config, Instance.directory, Instance.worktree) if (found.length > 0) { if (config === "pyproject.toml") { const content = await Filesystem.readText(found[0]) - if (content.includes("[tool.ruff]")) return true + if (content.includes("[tool.ruff]")) return [p, "format", "$FILE"] } else { - return true + return [p, "format", "$FILE"] } } } @@ -199,7 +201,7 @@ export const ruff: Info = { const found = await Filesystem.findUp(dep, Instance.directory, Instance.worktree) if (found.length > 0) { const content = await Filesystem.readText(found[0]) - if (content.includes("ruff")) return true + if (content.includes("ruff")) return [p, "format", "$FILE"] } } return false @@ -208,14 +210,13 @@ export const ruff: Info = { export const rlang: Info = { name: "air", - command: ["air", "format", "$FILE"], extensions: [".R"], async enabled() { const airPath = which("air") if (airPath == null) return false try { - const proc = Process.spawn(["air", "--help"], { + const proc = Process.spawn([airPath, "--help"], { stdout: "pipe", stderr: "pipe", }) @@ -227,7 +228,10 @@ export const rlang: Info = { const firstLine = output.split("\n")[0] const hasR = firstLine.includes("R language") const hasFormatter = firstLine.includes("formatter") - return hasR && hasFormatter + if (hasR && hasFormatter) { + return [airPath, "format", "$FILE"] + } + return false } catch (error) { return false } @@ -236,14 +240,14 @@ export const rlang: Info = { export const uvformat: Info = { name: "uv", - command: ["uv", "format", "--", "$FILE"], extensions: [".py", ".pyi"], async enabled() { if (await ruff.enabled()) return false - if (which("uv") !== null) { - const proc = Process.spawn(["uv", "format", "--help"], { stderr: "pipe", stdout: "pipe" }) + const uvPath = which("uv") + if (uvPath !== null) { + const proc = Process.spawn([uvPath, "format", "--help"], { stderr: "pipe", stdout: "pipe" }) const code = await proc.exited - return code === 0 + if (code === 0) return [uvPath, "format", "--", "$FILE"] } return false }, @@ -251,108 +255,118 @@ export const uvformat: Info = { export const rubocop: Info = { name: "rubocop", - command: ["rubocop", "--autocorrect", "$FILE"], extensions: [".rb", ".rake", ".gemspec", ".ru"], async enabled() { - return which("rubocop") !== null + const path = which("rubocop") + if (path === null) return false + return [path, "--autocorrect", "$FILE"] }, } export const standardrb: Info = { name: "standardrb", - command: ["standardrb", "--fix", "$FILE"], extensions: [".rb", ".rake", ".gemspec", ".ru"], async enabled() { - return which("standardrb") !== null + const path = which("standardrb") + if (path === null) return false + return [path, "--fix", "$FILE"] }, } export const htmlbeautifier: Info = { name: "htmlbeautifier", - command: ["htmlbeautifier", "$FILE"], extensions: [".erb", ".html.erb"], async enabled() { - return which("htmlbeautifier") !== null + const path = which("htmlbeautifier") + if (path === null) return false + return [path, "$FILE"] }, } export const dart: Info = { name: "dart", - command: ["dart", "format", "$FILE"], extensions: [".dart"], async enabled() { - return which("dart") !== null + const path = which("dart") + if (path === null) return false + return [path, "format", "$FILE"] }, } export const ocamlformat: Info = { name: "ocamlformat", - command: ["ocamlformat", "-i", "$FILE"], extensions: [".ml", ".mli"], async enabled() { - if (!which("ocamlformat")) return false + const path = which("ocamlformat") + if (!path) return false const items = await Filesystem.findUp(".ocamlformat", Instance.directory, Instance.worktree) - return items.length > 0 + if (items.length === 0) return false + return [path, "-i", "$FILE"] }, } export const terraform: Info = { name: "terraform", - command: ["terraform", "fmt", "$FILE"], extensions: [".tf", ".tfvars"], async enabled() { - return which("terraform") !== null + const path = which("terraform") + if (path === null) return false + return [path, "fmt", "$FILE"] }, } export const latexindent: Info = { name: "latexindent", - command: ["latexindent", "-w", "-s", "$FILE"], extensions: [".tex"], async enabled() { - return which("latexindent") !== null + const path = which("latexindent") + if (path === null) return false + return [path, "-w", "-s", "$FILE"] }, } export const gleam: Info = { name: "gleam", - command: ["gleam", "format", "$FILE"], extensions: [".gleam"], async enabled() { - return which("gleam") !== null + const path = which("gleam") + if (path === null) return false + return [path, "format", "$FILE"] }, } export const shfmt: Info = { name: "shfmt", - command: ["shfmt", "-w", "$FILE"], extensions: [".sh", ".bash"], async enabled() { - return which("shfmt") !== null + const path = which("shfmt") + if (path === null) return false + return [path, "-w", "$FILE"] }, } export const nixfmt: Info = { name: "nixfmt", - command: ["nixfmt", "$FILE"], extensions: [".nix"], async enabled() { - return which("nixfmt") !== null + const path = which("nixfmt") + if (path === null) return false + return [path, "$FILE"] }, } export const rustfmt: Info = { name: "rustfmt", - command: ["rustfmt", "$FILE"], extensions: [".rs"], async enabled() { - return which("rustfmt") !== null + const path = which("rustfmt") + if (path === null) return false + return [path, "$FILE"] }, } export const pint: Info = { name: "pint", - command: ["./vendor/bin/pint", "$FILE"], extensions: [".php"], async enabled() { const items = await Filesystem.findUp("composer.json", Instance.directory, Instance.worktree) @@ -361,8 +375,9 @@ export const pint: Info = { require?: Record "require-dev"?: Record }>(item) - if (json.require?.["laravel/pint"]) return true - if (json["require-dev"]?.["laravel/pint"]) return true + if (json.require?.["laravel/pint"] || json["require-dev"]?.["laravel/pint"]) { + return ["./vendor/bin/pint", "$FILE"] + } } return false }, @@ -370,27 +385,30 @@ export const pint: Info = { export const ormolu: Info = { name: "ormolu", - command: ["ormolu", "-i", "$FILE"], extensions: [".hs"], async enabled() { - return which("ormolu") !== null + const path = which("ormolu") + if (path === null) return false + return [path, "-i", "$FILE"] }, } export const cljfmt: Info = { name: "cljfmt", - command: ["cljfmt", "fix", "--quiet", "$FILE"], extensions: [".clj", ".cljs", ".cljc", ".edn"], async enabled() { - return which("cljfmt") !== null + const path = which("cljfmt") + if (path === null) return false + return [path, "fix", "--quiet", "$FILE"] }, } export const dfmt: Info = { name: "dfmt", - command: ["dfmt", "-i", "$FILE"], extensions: [".d"], async enabled() { - return which("dfmt") !== null + const path = which("dfmt") + if (path === null) return false + return [path, "-i", "$FILE"] }, } diff --git a/packages/opencode/src/format/index.ts b/packages/opencode/src/format/index.ts index 6da8caa08c..8a7ee0af5c 100644 --- a/packages/opencode/src/format/index.ts +++ b/packages/opencode/src/format/index.ts @@ -37,7 +37,7 @@ export namespace Format { Effect.gen(function* () { const instance = yield* InstanceContext - const enabled: Record = {} + const enabled: Record = {} const formatters: Record = {} const cfg = yield* Effect.promise(() => Config.get()) @@ -62,7 +62,7 @@ export namespace Format { formatters[name] = { ...info, name, - enabled: async () => true, + enabled: async () => info.command, } } } else { @@ -79,13 +79,22 @@ export namespace Format { } async function getFormatter(ext: string) { - const result = [] + const result: Array<{ + name: string + command: string[] + environment?: Record + }> = [] for (const item of Object.values(formatters)) { log.info("checking", { name: item.name, ext }) if (!item.extensions.includes(ext)) continue - if (!(await isEnabled(item))) continue + const cmd = await isEnabled(item) + if (!cmd) continue log.info("enabled", { name: item.name, ext }) - result.push(item) + result.push({ + name: item.name, + command: cmd, + environment: item.environment, + }) } return result } @@ -141,7 +150,7 @@ export namespace Format { result.push({ name: formatter.name, extensions: formatter.extensions, - enabled: isOn, + enabled: !!isOn, }) } return result diff --git a/packages/opencode/src/lsp/server.ts b/packages/opencode/src/lsp/server.ts index 123e8aea86..23ff17ceeb 100644 --- a/packages/opencode/src/lsp/server.ts +++ b/packages/opencode/src/lsp/server.ts @@ -3,7 +3,6 @@ import path from "path" import os from "os" import { Global } from "../global" import { Log } from "../util/log" -import { BunProc } from "../bun" import { text } from "node:stream/consumers" import fs from "fs/promises" import { Filesystem } from "../util/filesystem" @@ -14,6 +13,7 @@ import { Process } from "../util/process" import { which } from "../util/which" import { Module } from "@opencode-ai/util/module" import { spawn } from "./launch" +import { Npm } from "@/npm" export namespace LSPServer { const log = Log.create({ service: "lsp.server" }) @@ -103,7 +103,7 @@ export namespace LSPServer { const tsserver = Module.resolve("typescript/lib/tsserver.js", Instance.directory) log.info("typescript server", { tsserver }) if (!tsserver) return - const proc = spawn(BunProc.which(), ["x", "typescript-language-server", "--stdio"], { + const proc = spawn(await Npm.which("typescript-language-server"), ["--stdio"], { cwd: root, env: { ...process.env, @@ -129,29 +129,8 @@ export namespace LSPServer { let binary = which("vue-language-server") const args: string[] = [] if (!binary) { - const js = path.join( - Global.Path.bin, - "node_modules", - "@vue", - "language-server", - "bin", - "vue-language-server.js", - ) - if (!(await Filesystem.exists(js))) { - if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return - await Process.spawn([BunProc.which(), "install", "@vue/language-server"], { - cwd: Global.Path.bin, - env: { - ...process.env, - BUN_BE_BUN: "1", - }, - stdout: "pipe", - stderr: "pipe", - stdin: "pipe", - }).exited - } - binary = BunProc.which() - args.push("run", js) + if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return + binary = await Npm.which("@vue/language-server") } args.push("--stdio") const proc = spawn(binary, args, { @@ -214,7 +193,7 @@ export namespace LSPServer { log.info("installed VS Code ESLint server", { serverPath }) } - const proc = spawn(BunProc.which(), [serverPath, "--stdio"], { + const proc = spawn(await Npm.which("tsx"), [serverPath, "--stdio"], { cwd: root, env: { ...process.env, @@ -345,8 +324,8 @@ export namespace LSPServer { if (!bin) { const resolved = Module.resolve("biome", root) if (!resolved) return - bin = BunProc.which() - args = ["x", "biome", "lsp-proxy", "--stdio"] + bin = await Npm.which("biome") + args = ["lsp-proxy", "--stdio"] } const proc = spawn(bin, args, { @@ -372,9 +351,7 @@ export namespace LSPServer { }, extensions: [".go"], async spawn(root) { - let bin = which("gopls", { - PATH: process.env["PATH"] + path.delimiter + Global.Path.bin, - }) + let bin = which("gopls") if (!bin) { if (!which("go")) return if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return @@ -409,9 +386,7 @@ export namespace LSPServer { root: NearestRoot(["Gemfile"]), extensions: [".rb", ".rake", ".gemspec", ".ru"], async spawn(root) { - let bin = which("rubocop", { - PATH: process.env["PATH"] + path.delimiter + Global.Path.bin, - }) + let bin = which("rubocop") if (!bin) { const ruby = which("ruby") const gem = which("gem") @@ -516,19 +491,8 @@ export namespace LSPServer { let binary = which("pyright-langserver") const args = [] if (!binary) { - const js = path.join(Global.Path.bin, "node_modules", "pyright", "dist", "pyright-langserver.js") - if (!(await Filesystem.exists(js))) { - if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return - await Process.spawn([BunProc.which(), "install", "pyright"], { - cwd: Global.Path.bin, - env: { - ...process.env, - BUN_BE_BUN: "1", - }, - }).exited - } - binary = BunProc.which() - args.push(...["run", js]) + if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return + binary = await Npm.which("pyright") } args.push("--stdio") @@ -630,9 +594,7 @@ export namespace LSPServer { extensions: [".zig", ".zon"], root: NearestRoot(["build.zig"]), async spawn(root) { - let bin = which("zls", { - PATH: process.env["PATH"] + path.delimiter + Global.Path.bin, - }) + let bin = which("zls") if (!bin) { const zig = which("zig") @@ -742,9 +704,7 @@ export namespace LSPServer { root: NearestRoot([".slnx", ".sln", ".csproj", "global.json"]), extensions: [".cs"], async spawn(root) { - let bin = which("csharp-ls", { - PATH: process.env["PATH"] + path.delimiter + Global.Path.bin, - }) + let bin = which("csharp-ls") if (!bin) { if (!which("dotnet")) { log.error(".NET SDK is required to install csharp-ls") @@ -781,9 +741,7 @@ export namespace LSPServer { root: NearestRoot([".slnx", ".sln", ".fsproj", "global.json"]), extensions: [".fs", ".fsi", ".fsx", ".fsscript"], async spawn(root) { - let bin = which("fsautocomplete", { - PATH: process.env["PATH"] + path.delimiter + Global.Path.bin, - }) + let bin = which("fsautocomplete") if (!bin) { if (!which("dotnet")) { log.error(".NET SDK is required to install fsautocomplete") @@ -1049,22 +1007,8 @@ export namespace LSPServer { let binary = which("svelteserver") const args: string[] = [] if (!binary) { - const js = path.join(Global.Path.bin, "node_modules", "svelte-language-server", "bin", "server.js") - if (!(await Filesystem.exists(js))) { - if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return - await Process.spawn([BunProc.which(), "install", "svelte-language-server"], { - cwd: Global.Path.bin, - env: { - ...process.env, - BUN_BE_BUN: "1", - }, - stdout: "pipe", - stderr: "pipe", - stdin: "pipe", - }).exited - } - binary = BunProc.which() - args.push("run", js) + if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return + binary = await Npm.which("svelte-language-server") } args.push("--stdio") const proc = spawn(binary, args, { @@ -1096,22 +1040,8 @@ export namespace LSPServer { let binary = which("astro-ls") const args: string[] = [] if (!binary) { - const js = path.join(Global.Path.bin, "node_modules", "@astrojs", "language-server", "bin", "nodeServer.js") - if (!(await Filesystem.exists(js))) { - if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return - await Process.spawn([BunProc.which(), "install", "@astrojs/language-server"], { - cwd: Global.Path.bin, - env: { - ...process.env, - BUN_BE_BUN: "1", - }, - stdout: "pipe", - stderr: "pipe", - stdin: "pipe", - }).exited - } - binary = BunProc.which() - args.push("run", js) + if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return + binary = await Npm.which("@astrojs/language-server") } args.push("--stdio") const proc = spawn(binary, args, { @@ -1360,31 +1290,8 @@ export namespace LSPServer { let binary = which("yaml-language-server") const args: string[] = [] if (!binary) { - const js = path.join( - Global.Path.bin, - "node_modules", - "yaml-language-server", - "out", - "server", - "src", - "server.js", - ) - const exists = await Filesystem.exists(js) - if (!exists) { - if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return - await Process.spawn([BunProc.which(), "install", "yaml-language-server"], { - cwd: Global.Path.bin, - env: { - ...process.env, - BUN_BE_BUN: "1", - }, - stdout: "pipe", - stderr: "pipe", - stdin: "pipe", - }).exited - } - binary = BunProc.which() - args.push("run", js) + if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return + binary = await Npm.which("yaml-language-server") } args.push("--stdio") const proc = spawn(binary, args, { @@ -1413,9 +1320,7 @@ export namespace LSPServer { ]), extensions: [".lua"], async spawn(root) { - let bin = which("lua-language-server", { - PATH: process.env["PATH"] + path.delimiter + Global.Path.bin, - }) + let bin = which("lua-language-server") if (!bin) { if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return @@ -1551,22 +1456,8 @@ export namespace LSPServer { let binary = which("intelephense") const args: string[] = [] if (!binary) { - const js = path.join(Global.Path.bin, "node_modules", "intelephense", "lib", "intelephense.js") - if (!(await Filesystem.exists(js))) { - if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return - await Process.spawn([BunProc.which(), "install", "intelephense"], { - cwd: Global.Path.bin, - env: { - ...process.env, - BUN_BE_BUN: "1", - }, - stdout: "pipe", - stderr: "pipe", - stdin: "pipe", - }).exited - } - binary = BunProc.which() - args.push("run", js) + if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return + binary = await Npm.which("intelephense") } args.push("--stdio") const proc = spawn(binary, args, { @@ -1648,22 +1539,8 @@ export namespace LSPServer { let binary = which("bash-language-server") const args: string[] = [] if (!binary) { - const js = path.join(Global.Path.bin, "node_modules", "bash-language-server", "out", "cli.js") - if (!(await Filesystem.exists(js))) { - if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return - await Process.spawn([BunProc.which(), "install", "bash-language-server"], { - cwd: Global.Path.bin, - env: { - ...process.env, - BUN_BE_BUN: "1", - }, - stdout: "pipe", - stderr: "pipe", - stdin: "pipe", - }).exited - } - binary = BunProc.which() - args.push("run", js) + if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return + binary = await Npm.which("bash-language-server") } args.push("start") const proc = spawn(binary, args, { @@ -1684,9 +1561,7 @@ export namespace LSPServer { extensions: [".tf", ".tfvars"], root: NearestRoot([".terraform.lock.hcl", "terraform.tfstate", "*.tf"]), async spawn(root) { - let bin = which("terraform-ls", { - PATH: process.env["PATH"] + path.delimiter + Global.Path.bin, - }) + let bin = which("terraform-ls") if (!bin) { if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return @@ -1767,9 +1642,7 @@ export namespace LSPServer { extensions: [".tex", ".bib"], root: NearestRoot([".latexmkrc", "latexmkrc", ".texlabroot", "texlabroot"]), async spawn(root) { - let bin = which("texlab", { - PATH: process.env["PATH"] + path.delimiter + Global.Path.bin, - }) + let bin = which("texlab") if (!bin) { if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return @@ -1860,22 +1733,8 @@ export namespace LSPServer { let binary = which("docker-langserver") const args: string[] = [] if (!binary) { - const js = path.join(Global.Path.bin, "node_modules", "dockerfile-language-server-nodejs", "lib", "server.js") - if (!(await Filesystem.exists(js))) { - if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return - await Process.spawn([BunProc.which(), "install", "dockerfile-language-server-nodejs"], { - cwd: Global.Path.bin, - env: { - ...process.env, - BUN_BE_BUN: "1", - }, - stdout: "pipe", - stderr: "pipe", - stdin: "pipe", - }).exited - } - binary = BunProc.which() - args.push("run", js) + if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return + binary = await Npm.which("dockerfile-language-server-nodejs") } args.push("--stdio") const proc = spawn(binary, args, { @@ -1966,9 +1825,7 @@ export namespace LSPServer { extensions: [".typ", ".typc"], root: NearestRoot(["typst.toml"]), async spawn(root) { - let bin = which("tinymist", { - PATH: process.env["PATH"] + path.delimiter + Global.Path.bin, - }) + let bin = which("tinymist") if (!bin) { if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return diff --git a/packages/opencode/src/npm/index.ts b/packages/opencode/src/npm/index.ts new file mode 100644 index 0000000000..d6182d87a6 --- /dev/null +++ b/packages/opencode/src/npm/index.ts @@ -0,0 +1,180 @@ +// Workaround: Bun on Windows does not support the UV_FS_O_FILEMAP flag that +// the `tar` package uses for files < 512KB (fs.open returns EINVAL). +// tar silently swallows the error and skips writing files, leaving only empty +// directories. Setting __FAKE_PLATFORM__ makes tar fall back to the plain 'w' +// flag. See tar's get-write-flag.js. +// Must be set before @npmcli/arborist is imported since tar caches the flag +// at module evaluation time — so we use a dynamic import() below. +if (process.platform === "win32") { + process.env.__FAKE_PLATFORM__ = "linux" +} + +import semver from "semver" +import z from "zod" +import { NamedError } from "@opencode-ai/util/error" +import { Global } from "../global" +import { Lock } from "../util/lock" +import { Log } from "../util/log" +import path from "path" +import { readdir } from "fs/promises" +import { Filesystem } from "@/util/filesystem" + +const { Arborist } = await import("@npmcli/arborist") + +export namespace Npm { + const log = Log.create({ service: "npm" }) + + export const InstallFailedError = NamedError.create( + "NpmInstallFailedError", + z.object({ + pkg: z.string(), + }), + ) + + function directory(pkg: string) { + return path.join(Global.Path.cache, "packages", pkg) + } + + export async function outdated(pkg: string, cachedVersion: string): Promise { + const response = await fetch(`https://registry.npmjs.org/${pkg}`) + if (!response.ok) { + log.warn("Failed to resolve latest version, using cached", { pkg, cachedVersion }) + return false + } + + const data = (await response.json()) as { "dist-tags"?: { latest?: string } } + const latestVersion = data?.["dist-tags"]?.latest + if (!latestVersion) { + log.warn("No latest version found, using cached", { pkg, cachedVersion }) + return false + } + + const range = /[\s^~*xX<>|=]/.test(cachedVersion) + if (range) return !semver.satisfies(latestVersion, cachedVersion) + + return semver.lt(cachedVersion, latestVersion) + } + + export async function add(pkg: string) { + using _ = await Lock.write("npm-install") + log.info("installing package", { + pkg, + }) + const dir = directory(pkg) + + const arborist = new Arborist({ + path: dir, + binLinks: true, + progress: false, + savePrefix: "", + }) + const tree = await arborist.loadVirtual().catch(() => {}) + if (tree) { + const first = tree.edgesOut.values().next().value?.to + if (first) { + log.info("package already installed", { pkg }) + return first.path + } + } + + const result = await arborist + .reify({ + add: [pkg], + save: true, + saveType: "prod", + }) + .catch((cause) => { + throw new InstallFailedError( + { pkg }, + { + cause, + }, + ) + }) + + const first = result.edgesOut.values().next().value?.to + if (!first) throw new InstallFailedError({ pkg }) + return first.path + } + + export async function install(dir: string) { + using _ = await Lock.write(`npm-install:${dir}`) + log.info("checking dependencies", { dir }) + + const reify = async () => { + const arb = new Arborist({ + path: dir, + binLinks: true, + progress: false, + savePrefix: "", + }) + await arb.reify().catch(() => {}) + } + + if (!(await Filesystem.exists(path.join(dir, "node_modules")))) { + log.info("node_modules missing, reifying") + await reify() + return + } + + const pkg = await Filesystem.readJson(path.join(dir, "package.json")).catch(() => ({})) + const lock = await Filesystem.readJson(path.join(dir, "package-lock.json")).catch(() => ({})) + + const declared = new Set([ + ...Object.keys(pkg.dependencies || {}), + ...Object.keys(pkg.devDependencies || {}), + ...Object.keys(pkg.peerDependencies || {}), + ...Object.keys(pkg.optionalDependencies || {}), + ]) + + const root = lock.packages?.[""] || {} + const locked = new Set([ + ...Object.keys(root.dependencies || {}), + ...Object.keys(root.devDependencies || {}), + ...Object.keys(root.peerDependencies || {}), + ...Object.keys(root.optionalDependencies || {}), + ]) + + for (const name of declared) { + if (!locked.has(name)) { + log.info("dependency not in lock file, reifying", { name }) + await reify() + return + } + } + + log.info("dependencies in sync") + } + + export async function which(pkg: string) { + const dir = directory(pkg) + const binDir = path.join(dir, "node_modules", ".bin") + + const pick = async () => { + const files = await readdir(binDir).catch(() => []) + if (files.length === 0) return undefined + if (files.length === 1) return files[0] + // Multiple binaries — resolve from package.json bin field like npx does + const pkgJson = await Filesystem.readJson<{ bin?: string | Record }>( + path.join(dir, "node_modules", pkg, "package.json"), + ).catch(() => undefined) + if (pkgJson?.bin) { + const bin = pkgJson.bin + if (typeof bin === "string") return path.basename(bin) + const keys = Object.keys(bin) + if (keys.length === 1) return keys[0] + const unscoped = pkg.startsWith("@") ? pkg.split("/")[1] : pkg + return bin[unscoped] ? unscoped : keys[0] + } + return files[0] + } + + const bin = await pick() + if (bin) return path.join(binDir, bin) + + await add(pkg) + const resolved = await pick() + if (!resolved) throw new Error(`No binary found for package "${pkg}" after install`) + return path.join(binDir, resolved) + } +} diff --git a/packages/opencode/src/plugin/index.ts b/packages/opencode/src/plugin/index.ts index 755ce2c211..f8d1faece9 100644 --- a/packages/opencode/src/plugin/index.ts +++ b/packages/opencode/src/plugin/index.ts @@ -4,7 +4,7 @@ import { Bus } from "../bus" import { Log } from "../util/log" import { createOpencodeClient } from "@opencode-ai/sdk" import { Server } from "../server/server" -import { BunProc } from "../bun" +import { Npm } from "../npm" import { Instance } from "../project/instance" import { Flag } from "../flag/flag" import { CodexAuthPlugin } from "./codex" @@ -30,7 +30,9 @@ export namespace Plugin { : undefined, fetch: async (...args) => Server.Default().fetch(...args), }) + log.info("loading config") const config = await Config.get() + log.info("config loaded") const hooks: Hooks[] = [] const input: PluginInput = { client, @@ -40,7 +42,8 @@ export namespace Plugin { get serverUrl(): URL { return Server.url ?? new URL("http://localhost:4096") }, - $: Bun.$, + // @ts-expect-error + $: typeof Bun === "undefined" ? undefined : Bun.$, } for (const plugin of INTERNAL_PLUGINS) { @@ -59,16 +62,13 @@ export namespace Plugin { if (plugin.includes("opencode-openai-codex-auth") || plugin.includes("opencode-copilot-auth")) continue log.info("loading plugin", { path: plugin }) if (!plugin.startsWith("file://")) { - const lastAtIndex = plugin.lastIndexOf("@") - const pkg = lastAtIndex > 0 ? plugin.substring(0, lastAtIndex) : plugin - const version = lastAtIndex > 0 ? plugin.substring(lastAtIndex + 1) : "latest" - plugin = await BunProc.install(pkg, version).catch((err) => { + plugin = await Npm.add(plugin).catch((err) => { const cause = err instanceof Error ? err.cause : err const detail = cause instanceof Error ? cause.message : String(cause ?? err) - log.error("failed to install plugin", { pkg, version, error: detail }) + log.error("failed to install plugin", { plugin, error: detail }) Bus.publish(Session.Event.Error, { error: new NamedError.Unknown({ - message: `Failed to install plugin ${pkg}@${version}: ${detail}`, + message: `Failed to install plugin ${plugin}: ${detail}`, }).toObject(), }) return "" diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts index f7667fc2cb..11cf26cf58 100644 --- a/packages/opencode/src/provider/provider.ts +++ b/packages/opencode/src/provider/provider.ts @@ -5,7 +5,7 @@ import { Config } from "../config/config" import { mapValues, mergeDeep, omit, pickBy, sortBy } from "remeda" import { NoSuchModelError, type Provider as SDK } from "ai" import { Log } from "../util/log" -import { BunProc } from "../bun" +import { Npm } from "../npm" import { Hash } from "../util/hash" import { Plugin } from "../plugin" import { NamedError } from "@opencode-ai/util/error" @@ -1187,7 +1187,7 @@ export namespace Provider { let installedPath: string if (!model.api.npm.startsWith("file://")) { - installedPath = await BunProc.install(model.api.npm, "latest") + installedPath = await Npm.add(model.api.npm) } else { log.info("loading local provider", { pkg: model.api.npm }) installedPath = model.api.npm diff --git a/packages/opencode/test/bun.test.ts b/packages/opencode/test/bun.test.ts deleted file mode 100644 index d607ae4782..0000000000 --- a/packages/opencode/test/bun.test.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { describe, expect, test } from "bun:test" -import fs from "fs/promises" -import path from "path" - -describe("BunProc registry configuration", () => { - test("should not contain hardcoded registry parameters", async () => { - // Read the bun/index.ts file - const bunIndexPath = path.join(__dirname, "../src/bun/index.ts") - const content = await fs.readFile(bunIndexPath, "utf-8") - - // Verify that no hardcoded registry is present - expect(content).not.toContain("--registry=") - expect(content).not.toContain("hasNpmRcConfig") - expect(content).not.toContain("NpmRc") - }) - - test("should use Bun's default registry resolution", async () => { - // Read the bun/index.ts file - const bunIndexPath = path.join(__dirname, "../src/bun/index.ts") - const content = await fs.readFile(bunIndexPath, "utf-8") - - // Verify that it uses Bun's default resolution - expect(content).toContain("Bun's default registry resolution") - expect(content).toContain("Bun will use them automatically") - expect(content).toContain("No need to pass --registry flag") - }) - - test("should have correct command structure without registry", async () => { - // Read the bun/index.ts file - const bunIndexPath = path.join(__dirname, "../src/bun/index.ts") - const content = await fs.readFile(bunIndexPath, "utf-8") - - // Extract the install function - const installFunctionMatch = content.match(/export async function install[\s\S]*?^ }/m) - expect(installFunctionMatch).toBeTruthy() - - if (installFunctionMatch) { - const installFunction = installFunctionMatch[0] - - // Verify expected arguments are present - expect(installFunction).toContain('"add"') - expect(installFunction).toContain('"--force"') - expect(installFunction).toContain('"--exact"') - expect(installFunction).toContain('"--cwd"') - expect(installFunction).toContain("Global.Path.cache") - expect(installFunction).toContain('pkg + "@" + version') - - // Verify no registry argument is added - expect(installFunction).not.toContain('"--registry"') - expect(installFunction).not.toContain('args.push("--registry') - } - }) -}) diff --git a/packages/opencode/test/config/config.test.ts b/packages/opencode/test/config/config.test.ts index baf209d860..90727cf8a0 100644 --- a/packages/opencode/test/config/config.test.ts +++ b/packages/opencode/test/config/config.test.ts @@ -1,4 +1,4 @@ -import { test, expect, describe, mock, afterEach, spyOn } from "bun:test" +import { test, expect, describe, mock, afterEach } from "bun:test" import { Config } from "../../src/config/config" import { Instance } from "../../src/project/instance" import { Auth } from "../../src/auth" @@ -10,7 +10,6 @@ import { pathToFileURL } from "url" import { Global } from "../../src/global" import { ProjectID } from "../../src/project/schema" import { Filesystem } from "../../src/util/filesystem" -import { BunProc } from "../../src/bun" // Get managed config directory from environment (set in preload.ts) const managedConfigDir = process.env.OPENCODE_TEST_MANAGED_CONFIG_DIR! @@ -764,39 +763,6 @@ test("installs dependencies in writable OPENCODE_CONFIG_DIR", async () => { } }) -test("serializes concurrent config dependency installs", async () => { - await using tmp = await tmpdir() - const dirs = [path.join(tmp.path, "a"), path.join(tmp.path, "b")] - await Promise.all(dirs.map((dir) => fs.mkdir(dir, { recursive: true }))) - - const seen: string[] = [] - let active = 0 - let max = 0 - const run = spyOn(BunProc, "run").mockImplementation(async (_cmd, opts) => { - active++ - max = Math.max(max, active) - seen.push(opts?.cwd ?? "") - await new Promise((resolve) => setTimeout(resolve, 25)) - active-- - return { - code: 0, - stdout: Buffer.alloc(0), - stderr: Buffer.alloc(0), - } - }) - - try { - await Promise.all(dirs.map((dir) => Config.installDependencies(dir))) - } finally { - run.mockRestore() - } - - expect(max).toBe(1) - expect(seen.toSorted()).toEqual(dirs.toSorted()) - expect(await Filesystem.exists(path.join(dirs[0], "package.json"))).toBe(true) - expect(await Filesystem.exists(path.join(dirs[1], "package.json"))).toBe(true) -}) - test("resolves scoped npm plugins in config", async () => { await using tmp = await tmpdir({ init: async (dir) => { From 34c676ba7a4371565141973db8f683ad50a668d9 Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Thu, 19 Mar 2026 21:49:22 -0400 Subject: [PATCH 02/16] fix: scope Npm.add() lock to per-package key Use npm-install:${pkg} instead of a global npm-install lock so concurrent installs of different packages can run in parallel. --- packages/opencode/src/npm/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/opencode/src/npm/index.ts b/packages/opencode/src/npm/index.ts index d6182d87a6..12121b005f 100644 --- a/packages/opencode/src/npm/index.ts +++ b/packages/opencode/src/npm/index.ts @@ -56,7 +56,7 @@ export namespace Npm { } export async function add(pkg: string) { - using _ = await Lock.write("npm-install") + using _ = await Lock.write(`npm-install:${pkg}`) log.info("installing package", { pkg, }) From c075a5d6946bb649c5117dcf98b324987eef0176 Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Thu, 19 Mar 2026 21:54:53 -0400 Subject: [PATCH 03/16] fix: force reinstall in which() when .bin is missing When the lockfile exists but .bin is empty or absent, add() would read the lockfile via loadVirtual() and return early without calling reify(). Delete the lockfile before calling add() so it proceeds with a full install. --- packages/opencode/src/npm/index.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/opencode/src/npm/index.ts b/packages/opencode/src/npm/index.ts index 12121b005f..50879a10cc 100644 --- a/packages/opencode/src/npm/index.ts +++ b/packages/opencode/src/npm/index.ts @@ -16,7 +16,7 @@ import { Global } from "../global" import { Lock } from "../util/lock" import { Log } from "../util/log" import path from "path" -import { readdir } from "fs/promises" +import { readdir, rm } from "fs/promises" import { Filesystem } from "@/util/filesystem" const { Arborist } = await import("@npmcli/arborist") @@ -172,6 +172,7 @@ export namespace Npm { const bin = await pick() if (bin) return path.join(binDir, bin) + await rm(path.join(dir, "package-lock.json"), { force: true }) await add(pkg) const resolved = await pick() if (!resolved) throw new Error(`No binary found for package "${pkg}" after install`) From 7409ce8aec6c290a4a46e79d69af0d643dce009c Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Thu, 19 Mar 2026 22:05:30 -0400 Subject: [PATCH 04/16] fix: remove BUN_BE_BUN workaround, use node for ESLint server Replace tsx dependency with explicit 'node' invocation for the compiled ESLint server. Remove all BUN_BE_BUN env var references which are no longer needed after the Bun-to-Node migration. --- packages/opencode/src/format/formatter.ts | 9 --------- packages/opencode/src/lsp/server.ts | 13 +------------ packages/opencode/src/mcp/index.ts | 1 - 3 files changed, 1 insertion(+), 22 deletions(-) diff --git a/packages/opencode/src/format/formatter.ts b/packages/opencode/src/format/formatter.ts index 5424520cab..ab8674144f 100644 --- a/packages/opencode/src/format/formatter.ts +++ b/packages/opencode/src/format/formatter.ts @@ -35,9 +35,6 @@ export const mix: Info = { export const prettier: Info = { name: "prettier", - environment: { - BUN_BE_BUN: "1", - }, extensions: [ ".js", ".jsx", @@ -83,9 +80,6 @@ export const prettier: Info = { export const oxfmt: Info = { name: "oxfmt", - environment: { - BUN_BE_BUN: "1", - }, extensions: [".js", ".jsx", ".mjs", ".cjs", ".ts", ".tsx", ".mts", ".cts"], async enabled() { if (!Flag.OPENCODE_EXPERIMENTAL_OXFMT) return false @@ -105,9 +99,6 @@ export const oxfmt: Info = { export const biome: Info = { name: "biome", - environment: { - BUN_BE_BUN: "1", - }, extensions: [ ".js", ".jsx", diff --git a/packages/opencode/src/lsp/server.ts b/packages/opencode/src/lsp/server.ts index 23ff17ceeb..e822642b05 100644 --- a/packages/opencode/src/lsp/server.ts +++ b/packages/opencode/src/lsp/server.ts @@ -107,7 +107,6 @@ export namespace LSPServer { cwd: root, env: { ...process.env, - BUN_BE_BUN: "1", }, }) return { @@ -137,7 +136,6 @@ export namespace LSPServer { cwd: root, env: { ...process.env, - BUN_BE_BUN: "1", }, }) return { @@ -193,11 +191,10 @@ export namespace LSPServer { log.info("installed VS Code ESLint server", { serverPath }) } - const proc = spawn(await Npm.which("tsx"), [serverPath, "--stdio"], { + const proc = spawn("node", [serverPath, "--stdio"], { cwd: root, env: { ...process.env, - BUN_BE_BUN: "1", }, }) @@ -332,7 +329,6 @@ export namespace LSPServer { cwd: root, env: { ...process.env, - BUN_BE_BUN: "1", }, }) @@ -516,7 +512,6 @@ export namespace LSPServer { cwd: root, env: { ...process.env, - BUN_BE_BUN: "1", }, }) return { @@ -1015,7 +1010,6 @@ export namespace LSPServer { cwd: root, env: { ...process.env, - BUN_BE_BUN: "1", }, }) return { @@ -1048,7 +1042,6 @@ export namespace LSPServer { cwd: root, env: { ...process.env, - BUN_BE_BUN: "1", }, }) return { @@ -1298,7 +1291,6 @@ export namespace LSPServer { cwd: root, env: { ...process.env, - BUN_BE_BUN: "1", }, }) return { @@ -1464,7 +1456,6 @@ export namespace LSPServer { cwd: root, env: { ...process.env, - BUN_BE_BUN: "1", }, }) return { @@ -1547,7 +1538,6 @@ export namespace LSPServer { cwd: root, env: { ...process.env, - BUN_BE_BUN: "1", }, }) return { @@ -1741,7 +1731,6 @@ export namespace LSPServer { cwd: root, env: { ...process.env, - BUN_BE_BUN: "1", }, }) return { diff --git a/packages/opencode/src/mcp/index.ts b/packages/opencode/src/mcp/index.ts index e48a42a8b3..afb7456415 100644 --- a/packages/opencode/src/mcp/index.ts +++ b/packages/opencode/src/mcp/index.ts @@ -458,7 +458,6 @@ export namespace MCP { cwd, env: { ...process.env, - ...(cmd === "opencode" ? { BUN_BE_BUN: "1" } : {}), ...mcp.environment, }, }) From 205affa0eb5c32eeb2650f9e40cba1afebadbd61 Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Thu, 19 Mar 2026 23:09:27 -0400 Subject: [PATCH 05/16] fix: type error in mcp env and add missing @npmcli/arborist dependency --- bun.lock | 260 +++++++++++++++++++++++------ packages/opencode/src/mcp/index.ts | 2 +- 2 files changed, 212 insertions(+), 50 deletions(-) diff --git a/bun.lock b/bun.lock index 115100e104..f726f891b2 100644 --- a/bun.lock +++ b/bun.lock @@ -330,6 +330,7 @@ "@hono/standard-validator": "0.1.5", "@hono/zod-validator": "catalog:", "@modelcontextprotocol/sdk": "1.25.2", + "@npmcli/arborist": "9.4.0", "@octokit/graphql": "9.0.2", "@octokit/rest": "catalog:", "@openauthjs/openauth": "catalog:", @@ -404,6 +405,7 @@ "@types/bun": "catalog:", "@types/cross-spawn": "6.0.6", "@types/mime-types": "3.0.1", + "@types/npmcli__arborist": "6.3.3", "@types/semver": "^7.5.8", "@types/turndown": "5.0.5", "@types/which": "3.0.4", @@ -1108,6 +1110,8 @@ "@fontsource/inter": ["@fontsource/inter@5.2.8", "", {}, "sha512-P6r5WnJoKiNVV+zvW2xM13gNdFhAEpQ9dQJHt3naLvfg+LkF2ldgSLiF4T41lf1SQCM9QmkqPTn4TH568IRagg=="], + "@gar/promise-retry": ["@gar/promise-retry@1.0.3", "", {}, "sha512-GmzA9ckNokPypTg10pgpeHNQe7ph+iIKKmhKu3Ob9ANkswreCx7R3cKmY781K8QK3AqVL3xVh9A42JvIAbkkSA=="], + "@gitlab/gitlab-ai-provider": ["@gitlab/gitlab-ai-provider@3.6.0", "", { "dependencies": { "@anthropic-ai/sdk": "^0.71.0", "@anycable/core": "^0.9.2", "graphql-request": "^6.1.0", "isomorphic-ws": "^5.0.0", "openai": "^6.16.0", "socket.io-client": "^4.8.1", "vscode-jsonrpc": "^8.2.1", "zod": "^3.25.76" }, "peerDependencies": { "@ai-sdk/provider": ">=2.0.0", "@ai-sdk/provider-utils": ">=3.0.0" } }, "sha512-8LmcIQ86xkMtC7L4P1/QYVEC+yKMTRerfPeniaaQGalnzXKtX6iMHLjLPOL9Rxp55lOXi6ed0WrFuJzZx+fNRg=="], "@gitlab/opencode-gitlab-auth": ["@gitlab/opencode-gitlab-auth@1.3.3", "", { "dependencies": { "@fastify/rate-limit": "^10.2.0", "@opencode-ai/plugin": "*", "fastify": "^5.2.0", "open": "^10.0.0" } }, "sha512-FT+KsCmAJjtqWr1YAq0MywGgL9kaLQ4apmsoowAXrPqHtoYf2i/nY10/A+L06kNj22EATeEDRpbB1NWXMto/SA=="], @@ -1186,6 +1190,8 @@ "@isaacs/fs-minipass": ["@isaacs/fs-minipass@4.0.1", "", { "dependencies": { "minipass": "^7.0.4" } }, "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w=="], + "@isaacs/string-locale-compare": ["@isaacs/string-locale-compare@1.1.0", "", {}, "sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ=="], + "@jimp/core": ["@jimp/core@1.6.0", "", { "dependencies": { "@jimp/file-ops": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "await-to-js": "^3.0.0", "exif-parser": "^0.1.12", "file-type": "^16.0.0", "mime": "3" } }, "sha512-EQQlKU3s9QfdJqiSrZWNTxBs3rKXgO2W+GxNXDtwchF3a4IqxDheFX1ti+Env9hdJXDiYLp2jTRjlxhPthsk8w=="], "@jimp/diff": ["@jimp/diff@1.6.0", "", { "dependencies": { "@jimp/plugin-resize": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "pixelmatch": "^5.3.0" } }, "sha512-+yUAQ5gvRC5D1WHYxjBHZI7JBRusGGSLf8AmPRPCenTzh4PA+wZ1xv2+cYqQwTfQHU5tXYOhA0xDytfHUf1Zyw=="], @@ -1358,9 +1364,35 @@ "@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="], - "@npmcli/agent": ["@npmcli/agent@3.0.0", "", { "dependencies": { "agent-base": "^7.1.0", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.1", "lru-cache": "^10.0.1", "socks-proxy-agent": "^8.0.3" } }, "sha512-S79NdEgDQd/NGCay6TCoVzXSj74skRZIKJcpJjC5lOq34SZzyI6MqtiiWoiVWoVrTcGjNeC4ipbh1VIHlpfF5Q=="], + "@npm/types": ["@npm/types@1.0.2", "", {}, "sha512-KXZccTDEnWqNrrx6JjpJKU/wJvNeg9BDgjS0XhmlZab7br921HtyVbsYzJr4L+xIvjdJ20Wh9dgxgCI2a5CEQw=="], - "@npmcli/fs": ["@npmcli/fs@4.0.0", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-/xGlezI6xfGO9NwuJlnwz/K14qD1kCSAGtacBHnGzeAIuJGazcp45KP5NuyARXoKb7cwulAGWVsbeSxdG/cb0Q=="], + "@npmcli/agent": ["@npmcli/agent@4.0.0", "", { "dependencies": { "agent-base": "^7.1.0", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.1", "lru-cache": "^11.2.1", "socks-proxy-agent": "^8.0.3" } }, "sha512-kAQTcEN9E8ERLVg5AsGwLNoFb+oEG6engbqAU2P43gD4JEIkNGMHdVQ096FsOAAYpZPB0RSt0zgInKIAS1l5QA=="], + + "@npmcli/arborist": ["@npmcli/arborist@9.4.0", "", { "dependencies": { "@isaacs/string-locale-compare": "^1.1.0", "@npmcli/fs": "^5.0.0", "@npmcli/installed-package-contents": "^4.0.0", "@npmcli/map-workspaces": "^5.0.0", "@npmcli/metavuln-calculator": "^9.0.2", "@npmcli/name-from-folder": "^4.0.0", "@npmcli/node-gyp": "^5.0.0", "@npmcli/package-json": "^7.0.0", "@npmcli/query": "^5.0.0", "@npmcli/redact": "^4.0.0", "@npmcli/run-script": "^10.0.0", "bin-links": "^6.0.0", "cacache": "^20.0.1", "common-ancestor-path": "^2.0.0", "hosted-git-info": "^9.0.0", "json-stringify-nice": "^1.1.4", "lru-cache": "^11.2.1", "minimatch": "^10.0.3", "nopt": "^9.0.0", "npm-install-checks": "^8.0.0", "npm-package-arg": "^13.0.0", "npm-pick-manifest": "^11.0.1", "npm-registry-fetch": "^19.0.0", "pacote": "^21.0.2", "parse-conflict-json": "^5.0.1", "proc-log": "^6.0.0", "proggy": "^4.0.0", "promise-all-reject-late": "^1.0.0", "promise-call-limit": "^3.0.1", "semver": "^7.3.7", "ssri": "^13.0.0", "treeverse": "^3.0.0", "walk-up-path": "^4.0.0" }, "bin": { "arborist": "bin/index.js" } }, "sha512-4Bm8hNixJG/sii1PMnag0V9i/sGOX9VRzFrUiZMSBJpGlLR38f+Btl85d07G9GL56xO0l0OZjvrGNYsDYp0xKA=="], + + "@npmcli/fs": ["@npmcli/fs@5.0.0", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-7OsC1gNORBEawOa5+j2pXN9vsicaIOH5cPXxoR6fJOmH6/EXpJB2CajXOu1fPRFun2m1lktEFX11+P89hqO/og=="], + + "@npmcli/git": ["@npmcli/git@7.0.2", "", { "dependencies": { "@gar/promise-retry": "^1.0.0", "@npmcli/promise-spawn": "^9.0.0", "ini": "^6.0.0", "lru-cache": "^11.2.1", "npm-pick-manifest": "^11.0.1", "proc-log": "^6.0.0", "semver": "^7.3.5", "which": "^6.0.0" } }, "sha512-oeolHDjExNAJAnlYP2qzNjMX/Xi9bmu78C9dIGr4xjobrSKbuMYCph8lTzn4vnW3NjIqVmw/f8BCfouqyJXlRg=="], + + "@npmcli/installed-package-contents": ["@npmcli/installed-package-contents@4.0.0", "", { "dependencies": { "npm-bundled": "^5.0.0", "npm-normalize-package-bin": "^5.0.0" }, "bin": { "installed-package-contents": "bin/index.js" } }, "sha512-yNyAdkBxB72gtZ4GrwXCM0ZUedo9nIbOMKfGjt6Cu6DXf0p8y1PViZAKDC8q8kv/fufx0WTjRBdSlyrvnP7hmA=="], + + "@npmcli/map-workspaces": ["@npmcli/map-workspaces@5.0.3", "", { "dependencies": { "@npmcli/name-from-folder": "^4.0.0", "@npmcli/package-json": "^7.0.0", "glob": "^13.0.0", "minimatch": "^10.0.3" } }, "sha512-o2grssXo1e774E5OtEwwrgoszYRh0lqkJH+Pb9r78UcqdGJRDRfhpM8DvZPjzNLLNYeD/rNbjOKM3Ss5UABROw=="], + + "@npmcli/metavuln-calculator": ["@npmcli/metavuln-calculator@9.0.3", "", { "dependencies": { "cacache": "^20.0.0", "json-parse-even-better-errors": "^5.0.0", "pacote": "^21.0.0", "proc-log": "^6.0.0", "semver": "^7.3.5" } }, "sha512-94GLSYhLXF2t2LAC7pDwLaM4uCARzxShyAQKsirmlNcpidH89VA4/+K1LbJmRMgz5gy65E/QBBWQdUvGLe2Frg=="], + + "@npmcli/name-from-folder": ["@npmcli/name-from-folder@4.0.0", "", {}, "sha512-qfrhVlOSqmKM8i6rkNdZzABj8MKEITGFAY+4teqBziksCQAOLutiAxM1wY2BKEd8KjUSpWmWCYxvXr0y4VTlPg=="], + + "@npmcli/node-gyp": ["@npmcli/node-gyp@5.0.0", "", {}, "sha512-uuG5HZFXLfyFKqg8QypsmgLQW7smiRjVc45bqD/ofZZcR/uxEjgQU8qDPv0s9TEeMUiAAU/GC5bR6++UdTirIQ=="], + + "@npmcli/package-json": ["@npmcli/package-json@7.0.5", "", { "dependencies": { "@npmcli/git": "^7.0.0", "glob": "^13.0.0", "hosted-git-info": "^9.0.0", "json-parse-even-better-errors": "^5.0.0", "proc-log": "^6.0.0", "semver": "^7.5.3", "spdx-expression-parse": "^4.0.0" } }, "sha512-iVuTlG3ORq2iaVa1IWUxAO/jIp77tUKBhoMjuzYW2kL4MLN1bi/ofqkZ7D7OOwh8coAx1/S2ge0rMdGv8sLSOQ=="], + + "@npmcli/promise-spawn": ["@npmcli/promise-spawn@9.0.1", "", { "dependencies": { "which": "^6.0.0" } }, "sha512-OLUaoqBuyxeTqUvjA3FZFiXUfYC1alp3Sa99gW3EUDz3tZ3CbXDdcZ7qWKBzicrJleIgucoWamWH1saAmH/l2Q=="], + + "@npmcli/query": ["@npmcli/query@5.0.0", "", { "dependencies": { "postcss-selector-parser": "^7.0.0" } }, "sha512-8TZWfTQOsODpLqo9SVhVjHovmKXNpevHU0gO9e+y4V4fRIOneiXy0u0sMP9LmS71XivrEWfZWg50ReH4WRT4aQ=="], + + "@npmcli/redact": ["@npmcli/redact@4.0.0", "", {}, "sha512-gOBg5YHMfZy+TfHArfVogwgfBeQnKbbGo3pSUyK/gSI0AVu+pEiDVcKlQb0D8Mg1LNRZILZ6XG8I5dJ4KuAd9Q=="], + + "@npmcli/run-script": ["@npmcli/run-script@10.0.4", "", { "dependencies": { "@npmcli/node-gyp": "^5.0.0", "@npmcli/package-json": "^7.0.0", "@npmcli/promise-spawn": "^9.0.0", "node-gyp": "^12.1.0", "proc-log": "^6.0.0" } }, "sha512-mGUWr1uMnf0le2TwfOZY4SFxZGXGfm4Jtay/nwAa2FLNAKXUoUwaGwBMNH36UHPtinWfTSJ3nqFQr0091CxVGg=="], "@octokit/auth-app": ["@octokit/auth-app@8.0.1", "", { "dependencies": { "@octokit/auth-oauth-app": "^9.0.1", "@octokit/auth-oauth-user": "^6.0.0", "@octokit/request": "^10.0.2", "@octokit/request-error": "^7.0.0", "@octokit/types": "^14.0.0", "toad-cache": "^3.7.0", "universal-github-app-jwt": "^2.2.0", "universal-user-agent": "^7.0.0" } }, "sha512-P2J5pB3pjiGwtJX4WqJVYCtNkcZ+j5T2Wm14aJAEIC3WJOrv12jvBley3G1U/XI8q9o1A7QMG54LiFED2BiFlg=="], @@ -1738,6 +1770,18 @@ "@shikijs/vscode-textmate": ["@shikijs/vscode-textmate@10.0.2", "", {}, "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg=="], + "@sigstore/bundle": ["@sigstore/bundle@4.0.0", "", { "dependencies": { "@sigstore/protobuf-specs": "^0.5.0" } }, "sha512-NwCl5Y0V6Di0NexvkTqdoVfmjTaQwoLM236r89KEojGmq/jMls8S+zb7yOwAPdXvbwfKDlP+lmXgAL4vKSQT+A=="], + + "@sigstore/core": ["@sigstore/core@3.2.0", "", {}, "sha512-kxHrDQ9YgfrWUSXU0cjsQGv8JykOFZQ9ErNKbFPWzk3Hgpwu8x2hHrQ9IdA8yl+j9RTLTC3sAF3Tdq1IQCP4oA=="], + + "@sigstore/protobuf-specs": ["@sigstore/protobuf-specs@0.5.0", "", {}, "sha512-MM8XIwUjN2bwvCg1QvrMtbBmpcSHrkhFSCu1D11NyPvDQ25HEc4oG5/OcQfd/Tlf/OxmKWERDj0zGE23jQaMwA=="], + + "@sigstore/sign": ["@sigstore/sign@4.1.1", "", { "dependencies": { "@gar/promise-retry": "^1.0.2", "@sigstore/bundle": "^4.0.0", "@sigstore/core": "^3.2.0", "@sigstore/protobuf-specs": "^0.5.0", "make-fetch-happen": "^15.0.4", "proc-log": "^6.1.0" } }, "sha512-Hf4xglukg0XXQ2RiD5vSoLjdPe8OBUPA8XeVjUObheuDcWdYWrnH/BNmxZCzkAy68MzmNCxXLeurJvs6hcP2OQ=="], + + "@sigstore/tuf": ["@sigstore/tuf@4.0.2", "", { "dependencies": { "@sigstore/protobuf-specs": "^0.5.0", "tuf-js": "^4.1.0" } }, "sha512-TCAzTy0xzdP79EnxSjq9KQ3eaR7+FmudLC6eRKknVKZbV7ZNlGLClAAQb/HMNJ5n2OBNk2GT1tEmU0xuPr+SLQ=="], + + "@sigstore/verify": ["@sigstore/verify@3.1.0", "", { "dependencies": { "@sigstore/bundle": "^4.0.0", "@sigstore/core": "^3.1.0", "@sigstore/protobuf-specs": "^0.5.0" } }, "sha512-mNe0Iigql08YupSOGv197YdHpPPr+EzDZmfCgMc7RPNaZTw5aLN01nBl6CHJOh3BGtnMIj83EeN4butBchc8Ag=="], + "@sindresorhus/is": ["@sindresorhus/is@4.6.0", "", {}, "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw=="], "@slack/bolt": ["@slack/bolt@3.22.0", "", { "dependencies": { "@slack/logger": "^4.0.0", "@slack/oauth": "^2.6.3", "@slack/socket-mode": "^1.3.6", "@slack/types": "^2.13.0", "@slack/web-api": "^6.13.0", "@types/express": "^4.16.1", "@types/promise.allsettled": "^1.0.3", "@types/tsscmp": "^1.0.0", "axios": "^1.7.4", "express": "^4.21.0", "path-to-regexp": "^8.1.0", "promise.allsettled": "^1.0.2", "raw-body": "^2.3.3", "tsscmp": "^1.0.6" } }, "sha512-iKDqGPEJDnrVwxSVlFW6OKTkijd7s4qLBeSufoBsTM0reTyfdp/5izIQVkxNfzjHi3o6qjdYbRXkYad5HBsBog=="], @@ -2038,6 +2082,10 @@ "@tsconfig/node22": ["@tsconfig/node22@22.0.2", "", {}, "sha512-Kmwj4u8sDRDrMYRoN9FDEcXD8UpBSaPQQ24Gz+Gamqfm7xxn+GBR7ge/Z7pK8OXNGyUzbSwJj+TH6B+DS/epyA=="], + "@tufjs/canonical-json": ["@tufjs/canonical-json@2.0.0", "", {}, "sha512-yVtV8zsdo8qFHe+/3kw81dSLyF7D576A5cCFCi4X7B39tWT7SekaEFUnvnWJHz+9qO7qJTah1JbrDjWKqFtdWA=="], + + "@tufjs/models": ["@tufjs/models@4.1.0", "", { "dependencies": { "@tufjs/canonical-json": "2.0.0", "minimatch": "^10.1.1" } }, "sha512-Y8cK9aggNRsqJVaKUlEYs4s7CvQ1b1ta2DVPyAimb0I2qhzjNk+A+mxvll/klL0RlfuIUei8BF7YWiua4kQqww=="], + "@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="], "@types/aria-query": ["@types/aria-query@5.0.4", "", {}, "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw=="], @@ -2056,6 +2104,8 @@ "@types/bun": ["@types/bun@1.3.9", "", { "dependencies": { "bun-types": "1.3.9" } }, "sha512-KQ571yULOdWJiMH+RIWIOZ7B2RXQGpL1YQrBtLIV3FqDcCu6FsbFUBwhdKUlCKUpS3PJDsHlJ1QKlpxoVR+xtw=="], + "@types/cacache": ["@types/cacache@20.0.1", "", { "dependencies": { "@types/node": "*", "minipass": "*" } }, "sha512-QlKW3AFoFr/hvPHwFHMIVUH/ZCYeetBNou3PCmxu5LaNDvrtBlPJtIA6uhmU9JRt9oxj7IYoqoLcpxtzpPiTcw=="], + "@types/cacheable-request": ["@types/cacheable-request@6.0.3", "", { "dependencies": { "@types/http-cache-semantics": "*", "@types/keyv": "^3.1.4", "@types/node": "*", "@types/responselike": "^1.0.0" } }, "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw=="], "@types/chai": ["@types/chai@5.2.3", "", { "dependencies": { "@types/deep-eql": "*", "assertion-error": "^2.0.1" } }, "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA=="], @@ -2120,6 +2170,18 @@ "@types/node-fetch": ["@types/node-fetch@2.6.13", "", { "dependencies": { "@types/node": "*", "form-data": "^4.0.4" } }, "sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw=="], + "@types/npm-package-arg": ["@types/npm-package-arg@6.1.4", "", {}, "sha512-vDgdbMy2QXHnAruzlv68pUtXCjmqUk3WrBAsRboRovsOmxbfn/WiYCjmecyKjGztnMps5dWp4Uq2prp+Ilo17Q=="], + + "@types/npm-registry-fetch": ["@types/npm-registry-fetch@8.0.9", "", { "dependencies": { "@types/node": "*", "@types/node-fetch": "*", "@types/npm-package-arg": "*", "@types/npmlog": "*", "@types/ssri": "*" } }, "sha512-7NxvodR5Yrop3pb6+n8jhJNyzwOX0+6F+iagNEoi9u1CGxruYAwZD8pvGc9prIkL0+FdX5Xp0p80J9QPrGUp/g=="], + + "@types/npmcli__arborist": ["@types/npmcli__arborist@6.3.3", "", { "dependencies": { "@npm/types": "^1", "@types/cacache": "*", "@types/node": "*", "@types/npmcli__package-json": "*", "@types/pacote": "*" } }, "sha512-kyrX932Qr+/Y4OB47Jamgc2YWa/HlXTCN0KVJsq04XDHUGkfbprJA8rd66zZXHmHmvnz1LR4X17zsE/H8Mklew=="], + + "@types/npmcli__package-json": ["@types/npmcli__package-json@4.0.4", "", {}, "sha512-6QjlFUSHBmZJWuC08bz1ZCx6tm4t+7+OJXAdvM6tL2pI7n6Bh5SIp/YxQvnOLFf8MzCXs2ijyFgrzaiu1UFBGA=="], + + "@types/npmlog": ["@types/npmlog@7.0.0", "", { "dependencies": { "@types/node": "*" } }, "sha512-hJWbrKFvxKyWwSUXjZMYTINsSOY6IclhvGOZ97M8ac2tmR9hMwmTnYaMdpGhvju9ctWLTPhCS+eLfQNluiEjQQ=="], + + "@types/pacote": ["@types/pacote@11.1.8", "", { "dependencies": { "@types/node": "*", "@types/npm-registry-fetch": "*", "@types/npmlog": "*", "@types/ssri": "*" } }, "sha512-/XLR0VoTh2JEO0jJg1q/e6Rh9bxjBq9vorJuQmtT7rRrXSiWz7e7NsvXVYJQ0i8JxMlBMPPYDTnrRe7MZRFA8Q=="], + "@types/plist": ["@types/plist@3.0.5", "", { "dependencies": { "@types/node": "*", "xmlbuilder": ">=11.0.1" } }, "sha512-E6OCaRmAe4WDmWNsL/9RMqdkkzDCY1etutkflWk4c+AcjDU07Pcz1fQwTX0TQz+Pxqn9i4L1TU3UFpjnrcDgxA=="], "@types/promise.allsettled": ["@types/promise.allsettled@1.0.6", "", {}, "sha512-wA0UT0HeT2fGHzIFV9kWpYz5mdoyLxKrTgMdZQM++5h6pYAFH73HXcQhefg24nD1yivUFEn5KU+EF4b+CXJ4Wg=="], @@ -2148,6 +2210,8 @@ "@types/serve-static": ["@types/serve-static@1.15.10", "", { "dependencies": { "@types/http-errors": "*", "@types/node": "*", "@types/send": "<1" } }, "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw=="], + "@types/ssri": ["@types/ssri@7.1.5", "", { "dependencies": { "@types/node": "*" } }, "sha512-odD/56S3B51liILSk5aXJlnYt99S6Rt9EFDDqGtJM26rKHApHcwyU/UoYHrzKkdkHMAIquGWCuHtQTbes+FRQw=="], + "@types/trusted-types": ["@types/trusted-types@2.0.7", "", {}, "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw=="], "@types/tsscmp": ["@types/tsscmp@1.0.2", "", {}, "sha512-cy7BRSU8GYYgxjcx0Py+8lo5MthuDhlyu076KUcYzVNXL23luYgRHkMG2fIFEc6neckeh/ntP82mw+U4QjZq+g=="], @@ -2234,7 +2298,7 @@ "@zip.js/zip.js": ["@zip.js/zip.js@2.7.62", "", {}, "sha512-OaLvZ8j4gCkLn048ypkZu29KX30r8/OfFF2w4Jo5WXFr+J04J+lzJ5TKZBVgFXhlvSkqNFQdfnY1Q8TMTCyBVA=="], - "abbrev": ["abbrev@2.0.0", "", {}, "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ=="], + "abbrev": ["abbrev@4.0.0", "", {}, "sha512-a1wflyaL0tHtJSmLSOVybYhy22vRih4eduhhrkcjgrWGnRfrZtovJ2FRjxuTtkkj47O/baf0R86QU5OuYpz8fA=="], "abort-controller": ["abort-controller@3.0.0", "", { "dependencies": { "event-target-shim": "^5.0.0" } }, "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg=="], @@ -2398,6 +2462,8 @@ "bignumber.js": ["bignumber.js@9.3.1", "", {}, "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ=="], + "bin-links": ["bin-links@6.0.0", "", { "dependencies": { "cmd-shim": "^8.0.0", "npm-normalize-package-bin": "^5.0.0", "proc-log": "^6.0.0", "read-cmd-shim": "^6.0.0", "write-file-atomic": "^7.0.0" } }, "sha512-X4CiKlcV2GjnCMwnKAfbVWpHa++65th9TuzAEYtZoATiOE2DQKhSp4CJlyLoTqdhBKlXjpXjCTYPNNFS33Fi6w=="], + "binary": ["binary@0.3.0", "", { "dependencies": { "buffers": "~0.1.1", "chainsaw": "~0.1.0" } }, "sha512-D4H1y5KYwpJgK8wk1Cue5LLPgmwHKYSChkbspQg5JtVuR5ulGckxfR62H3AE9UDkdMC8yyXlqYihuz3Aqg2XZg=="], "binary-extensions": ["binary-extensions@2.3.0", "", {}, "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw=="], @@ -2470,7 +2536,7 @@ "cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="], - "cacache": ["cacache@19.0.1", "", { "dependencies": { "@npmcli/fs": "^4.0.0", "fs-minipass": "^3.0.0", "glob": "^10.2.2", "lru-cache": "^10.0.1", "minipass": "^7.0.3", "minipass-collect": "^2.0.1", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "p-map": "^7.0.2", "ssri": "^12.0.0", "tar": "^7.4.3", "unique-filename": "^4.0.0" } }, "sha512-hdsUxulXCi5STId78vRVYEtDAjq99ICAUktLTeTYsLoTE6Z8dS0c8pWNCxwdrk9YfJeobDZc2Y186hD/5ZQgFQ=="], + "cacache": ["cacache@20.0.4", "", { "dependencies": { "@npmcli/fs": "^5.0.0", "fs-minipass": "^3.0.0", "glob": "^13.0.0", "lru-cache": "^11.1.0", "minipass": "^7.0.3", "minipass-collect": "^2.0.1", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "p-map": "^7.0.2", "ssri": "^13.0.0" } }, "sha512-M3Lab8NPYlZU2exsL3bMVvMrMqgwCnMWfdZbK28bn3pK6APT/Te/I8hjRPNu1uwORY9a1eEQoifXbKPQMfMTOA=="], "cacheable-lookup": ["cacheable-lookup@5.0.4", "", {}, "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA=="], @@ -2550,6 +2616,8 @@ "cluster-key-slot": ["cluster-key-slot@1.1.2", "", {}, "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA=="], + "cmd-shim": ["cmd-shim@8.0.0", "", {}, "sha512-Jk/BK6NCapZ58BKUxlSI+ouKRbjH1NLZCgJkYoab+vEHUY3f6OzpNBN9u7HFSv9J6TRDGs4PLOHezoKGaFRSCA=="], + "collapse-white-space": ["collapse-white-space@2.1.0", "", {}, "sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw=="], "color": ["color@4.2.3", "", { "dependencies": { "color-convert": "^2.0.1", "color-string": "^1.9.0" } }, "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A=="], @@ -3126,7 +3194,7 @@ "hono-openapi": ["hono-openapi@1.1.2", "", { "peerDependencies": { "@hono/standard-validator": "^0.2.0", "@standard-community/standard-json": "^0.3.5", "@standard-community/standard-openapi": "^0.2.9", "@types/json-schema": "^7.0.15", "hono": "^4.8.3", "openapi-types": "^12.1.3" }, "optionalPeers": ["@hono/standard-validator", "hono"] }, "sha512-toUcO60MftRBxqcVyxsHNYs2m4vf4xkQaiARAucQx3TiBPDtMNNkoh+C4I1vAretQZiGyaLOZNWn1YxfSyUA5g=="], - "hosted-git-info": ["hosted-git-info@4.1.0", "", { "dependencies": { "lru-cache": "^6.0.0" } }, "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA=="], + "hosted-git-info": ["hosted-git-info@9.0.2", "", { "dependencies": { "lru-cache": "^11.1.0" } }, "sha512-M422h7o/BR3rmCQ8UHi7cyyMqKltdP9Uo+J2fXK+RSAY+wTcKOIRyhTuKv4qn+DJf3g+PL890AzId5KZpX+CBg=="], "html-entities": ["html-entities@2.3.3", "", {}, "sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA=="], @@ -3170,6 +3238,8 @@ "ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="], + "ignore-walk": ["ignore-walk@8.0.0", "", { "dependencies": { "minimatch": "^10.0.3" } }, "sha512-FCeMZT4NiRQGh+YkeKMtWrOmBgWjHjMJ26WQWrRQyoyzqevdaGSakUaJW5xQYmjLlUVk2qUnCjYVBax9EKKg8A=="], + "image-q": ["image-q@4.0.0", "", { "dependencies": { "@types/node": "16.9.1" } }, "sha512-PfJGVgIfKQJuq3s0tTDOKtztksibuUEbJQIYT3by6wctQo+Rdlh7ef4evJ5NCdxY4CfMbvFkocEwbl4BF8RlJw=="], "import-local": ["import-local@3.2.0", "", { "dependencies": { "pkg-dir": "^4.2.0", "resolve-cwd": "^3.0.0" }, "bin": { "import-local-fixture": "fixtures/cli.js" } }, "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA=="], @@ -3342,6 +3412,8 @@ "json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="], + "json-parse-even-better-errors": ["json-parse-even-better-errors@5.0.0", "", {}, "sha512-ZF1nxZ28VhQouRWhUcVlUIN3qwSgPuswK05s/HIaoetAoE/9tngVmCHjSxmSQPav1nd+lPtTL0YZ/2AFdR/iYQ=="], + "json-schema": ["json-schema@0.4.0", "", {}, "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA=="], "json-schema-ref-resolver": ["json-schema-ref-resolver@3.0.0", "", { "dependencies": { "dequal": "^2.0.3" } }, "sha512-hOrZIVL5jyYFjzk7+y7n5JDzGlU8rfWDuYyHwGa2WA8/pcmMHezp2xsVwxrebD/Q9t8Nc5DboieySDpCp4WG4A=="], @@ -3352,6 +3424,8 @@ "json-schema-typed": ["json-schema-typed@8.0.2", "", {}, "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA=="], + "json-stringify-nice": ["json-stringify-nice@1.1.4", "", {}, "sha512-5Z5RFW63yxReJ7vANgW6eZFGWaQvnPE3WNmZoOJrSkGju2etKA2L5rrOa1sm877TVTFt57A80BH1bArcmlLfPw=="], + "json-stringify-safe": ["json-stringify-safe@5.0.1", "", {}, "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA=="], "json-with-bigint": ["json-with-bigint@3.5.7", "", {}, "sha512-7ei3MdAI5+fJPVnKlW77TKNKwQ5ppSzWvhPuSuINT/GYW9ZOC1eRKOuhV9yHG5aEsUPj9BBx5JIekkmoLHxZOw=="], @@ -3362,8 +3436,14 @@ "jsonfile": ["jsonfile@4.0.0", "", { "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg=="], + "jsonparse": ["jsonparse@1.3.1", "", {}, "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg=="], + "jsonwebtoken": ["jsonwebtoken@9.0.3", "", { "dependencies": { "jws": "^4.0.1", "lodash.includes": "^4.3.0", "lodash.isboolean": "^3.0.3", "lodash.isinteger": "^4.0.4", "lodash.isnumber": "^3.0.3", "lodash.isplainobject": "^4.0.6", "lodash.isstring": "^4.0.1", "lodash.once": "^4.0.0", "ms": "^2.1.1", "semver": "^7.5.4" } }, "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g=="], + "just-diff": ["just-diff@6.0.2", "", {}, "sha512-S59eriX5u3/QhMNq3v/gm8Kd0w8OS6Tz2FS1NG4blv+z0MuQcBRJyFWjdovM0Rad4/P4aUPFtnkNjMjyMlMSYA=="], + + "just-diff-apply": ["just-diff-apply@5.5.0", "", {}, "sha512-OYTthRfSh55WOItVqwpefPtNt2VdKsq5AnAK6apdtR6yCH8pr0CmSr710J0Mf+WdQy7K/OzMy7K2MgAfdQURDw=="], + "jwa": ["jwa@2.0.1", "", { "dependencies": { "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg=="], "jws": ["jws@4.0.1", "", { "dependencies": { "jwa": "^2.0.1", "safe-buffer": "^5.0.1" } }, "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA=="], @@ -3476,7 +3556,7 @@ "magicast": ["magicast@0.3.5", "", { "dependencies": { "@babel/parser": "^7.25.4", "@babel/types": "^7.25.4", "source-map-js": "^1.2.0" } }, "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ=="], - "make-fetch-happen": ["make-fetch-happen@14.0.3", "", { "dependencies": { "@npmcli/agent": "^3.0.0", "cacache": "^19.0.1", "http-cache-semantics": "^4.1.1", "minipass": "^7.0.2", "minipass-fetch": "^4.0.0", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "negotiator": "^1.0.0", "proc-log": "^5.0.0", "promise-retry": "^2.0.1", "ssri": "^12.0.0" } }, "sha512-QMjGbFTP0blj97EeidG5hk/QhKQ3T4ICckQGLgz38QF7Vgbk6e6FTARN8KhKxyBbWn8R0HU+bnw8aSoFPD4qtQ=="], + "make-fetch-happen": ["make-fetch-happen@15.0.5", "", { "dependencies": { "@gar/promise-retry": "^1.0.0", "@npmcli/agent": "^4.0.0", "@npmcli/redact": "^4.0.0", "cacache": "^20.0.1", "http-cache-semantics": "^4.1.1", "minipass": "^7.0.2", "minipass-fetch": "^5.0.0", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "negotiator": "^1.0.0", "proc-log": "^6.0.0", "ssri": "^13.0.0" } }, "sha512-uCbIa8jWWmQZt4dSnEStkVC6gdakiinAm4PiGsywIkguF0eWMdcjDz0ECYhUolFU3pFLOev9VNPCEygydXnddg=="], "markdown-extensions": ["markdown-extensions@2.0.0", "", {}, "sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q=="], @@ -3642,13 +3722,13 @@ "minipass-collect": ["minipass-collect@2.0.1", "", { "dependencies": { "minipass": "^7.0.3" } }, "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw=="], - "minipass-fetch": ["minipass-fetch@4.0.1", "", { "dependencies": { "minipass": "^7.0.3", "minipass-sized": "^1.0.3", "minizlib": "^3.0.1" }, "optionalDependencies": { "encoding": "^0.1.13" } }, "sha512-j7U11C5HXigVuutxebFadoYBbd7VSdZWggSe64NVdvWNBqGAiXPL2QVCehjmw7lY1oF9gOllYbORh+hiNgfPgQ=="], + "minipass-fetch": ["minipass-fetch@5.0.2", "", { "dependencies": { "minipass": "^7.0.3", "minipass-sized": "^2.0.0", "minizlib": "^3.0.1" }, "optionalDependencies": { "iconv-lite": "^0.7.2" } }, "sha512-2d0q2a8eCi2IRg/IGubCNRJoYbA1+YPXAzQVRFmB45gdGZafyivnZ5YSEfo3JikbjGxOdntGFvBQGqaSMXlAFQ=="], "minipass-flush": ["minipass-flush@1.0.5", "", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw=="], "minipass-pipeline": ["minipass-pipeline@1.2.4", "", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A=="], - "minipass-sized": ["minipass-sized@1.0.3", "", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g=="], + "minipass-sized": ["minipass-sized@2.0.0", "", { "dependencies": { "minipass": "^7.1.2" } }, "sha512-zSsHhto5BcUVM2m1LurnXY6M//cGhVaegT71OfOXoprxT6o780GZd792ea6FfrQkuU4usHZIUczAQMRUE2plzA=="], "minizlib": ["minizlib@3.1.0", "", { "dependencies": { "minipass": "^7.1.2" } }, "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw=="], @@ -3716,7 +3796,7 @@ "node-fetch-native": ["node-fetch-native@1.6.7", "", {}, "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q=="], - "node-gyp": ["node-gyp@11.5.0", "", { "dependencies": { "env-paths": "^2.2.0", "exponential-backoff": "^3.1.1", "graceful-fs": "^4.2.6", "make-fetch-happen": "^14.0.3", "nopt": "^8.0.0", "proc-log": "^5.0.0", "semver": "^7.3.5", "tar": "^7.4.3", "tinyglobby": "^0.2.12", "which": "^5.0.0" }, "bin": { "node-gyp": "bin/node-gyp.js" } }, "sha512-ra7Kvlhxn5V9Slyus0ygMa2h+UqExPqUIkfk7Pc8QTLT956JLSy51uWFwHtIYy0vI8cB4BDhc/S03+880My/LQ=="], + "node-gyp": ["node-gyp@12.2.0", "", { "dependencies": { "env-paths": "^2.2.0", "exponential-backoff": "^3.1.1", "graceful-fs": "^4.2.6", "make-fetch-happen": "^15.0.0", "nopt": "^9.0.0", "proc-log": "^6.0.0", "semver": "^7.3.5", "tar": "^7.5.4", "tinyglobby": "^0.2.12", "which": "^6.0.0" }, "bin": { "node-gyp": "bin/node-gyp.js" } }, "sha512-q23WdzrQv48KozXlr0U1v9dwO/k59NHeSzn6loGcasyf0UnSrtzs8kRxM+mfwJSf0DkX0s43hcqgnSO4/VNthQ=="], "node-gyp-build": ["node-gyp-build@4.8.4", "", { "bin": { "node-gyp-build": "bin.js", "node-gyp-build-optional": "optional.js", "node-gyp-build-test": "build-test.js" } }, "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ=="], @@ -3728,12 +3808,26 @@ "node-releases": ["node-releases@2.0.36", "", {}, "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA=="], - "nopt": ["nopt@7.2.1", "", { "dependencies": { "abbrev": "^2.0.0" }, "bin": { "nopt": "bin/nopt.js" } }, "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w=="], + "nopt": ["nopt@9.0.0", "", { "dependencies": { "abbrev": "^4.0.0" }, "bin": { "nopt": "bin/nopt.js" } }, "sha512-Zhq3a+yFKrYwSBluL4H9XP3m3y5uvQkB/09CwDruCiRmR/UJYnn9W4R48ry0uGC70aeTPKLynBtscP9efFFcPw=="], "normalize-path": ["normalize-path@3.0.0", "", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="], "normalize-url": ["normalize-url@6.1.0", "", {}, "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A=="], + "npm-bundled": ["npm-bundled@5.0.0", "", { "dependencies": { "npm-normalize-package-bin": "^5.0.0" } }, "sha512-JLSpbzh6UUXIEoqPsYBvVNVmyrjVZ1fzEFbqxKkTJQkWBO3xFzFT+KDnSKQWwOQNbuWRwt5LSD6HOTLGIWzfrw=="], + + "npm-install-checks": ["npm-install-checks@8.0.0", "", { "dependencies": { "semver": "^7.1.1" } }, "sha512-ScAUdMpyzkbpxoNekQ3tNRdFI8SJ86wgKZSQZdUxT+bj0wVFpsEMWnkXP0twVe1gJyNF5apBWDJhhIbgrIViRA=="], + + "npm-normalize-package-bin": ["npm-normalize-package-bin@5.0.0", "", {}, "sha512-CJi3OS4JLsNMmr2u07OJlhcrPxCeOeP/4xq67aWNai6TNWWbTrlNDgl8NcFKVlcBKp18GPj+EzbNIgrBfZhsag=="], + + "npm-package-arg": ["npm-package-arg@13.0.2", "", { "dependencies": { "hosted-git-info": "^9.0.0", "proc-log": "^6.0.0", "semver": "^7.3.5", "validate-npm-package-name": "^7.0.0" } }, "sha512-IciCE3SY3uE84Ld8WZU23gAPPV9rIYod4F+rc+vJ7h7cwAJt9Vk6TVsK60ry7Uj3SRS3bqRRIGuTp9YVlk6WNA=="], + + "npm-packlist": ["npm-packlist@10.0.4", "", { "dependencies": { "ignore-walk": "^8.0.0", "proc-log": "^6.0.0" } }, "sha512-uMW73iajD8hiH4ZBxEV3HC+eTnppIqwakjOYuvgddnalIw2lJguKviK1pcUJDlIWm1wSJkchpDZDSVVsZEYRng=="], + + "npm-pick-manifest": ["npm-pick-manifest@11.0.3", "", { "dependencies": { "npm-install-checks": "^8.0.0", "npm-normalize-package-bin": "^5.0.0", "npm-package-arg": "^13.0.0", "semver": "^7.3.5" } }, "sha512-buzyCfeoGY/PxKqmBqn1IUJrZnUi1VVJTdSSRPGI60tJdUhUoSQFhs0zycJokDdOznQentgrpf8LayEHyyYlqQ=="], + + "npm-registry-fetch": ["npm-registry-fetch@19.1.1", "", { "dependencies": { "@npmcli/redact": "^4.0.0", "jsonparse": "^1.3.1", "make-fetch-happen": "^15.0.0", "minipass": "^7.0.2", "minipass-fetch": "^5.0.0", "minizlib": "^3.0.1", "npm-package-arg": "^13.0.0", "proc-log": "^6.0.0" } }, "sha512-TakBap6OM1w0H73VZVDf44iFXsOS3h+L4wVMXmbWOQroZgFhMch0juN6XSzBNlD965yIKvWg2dfu7NSiaYLxtw=="], + "npm-run-path": ["npm-run-path@5.3.0", "", { "dependencies": { "path-key": "^4.0.0" } }, "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ=="], "nth-check": ["nth-check@2.1.1", "", { "dependencies": { "boolbase": "^1.0.0" } }, "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w=="], @@ -3818,6 +3912,8 @@ "package-manager-detector": ["package-manager-detector@1.6.0", "", {}, "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA=="], + "pacote": ["pacote@21.5.0", "", { "dependencies": { "@gar/promise-retry": "^1.0.0", "@npmcli/git": "^7.0.0", "@npmcli/installed-package-contents": "^4.0.0", "@npmcli/package-json": "^7.0.0", "@npmcli/promise-spawn": "^9.0.0", "@npmcli/run-script": "^10.0.0", "cacache": "^20.0.0", "fs-minipass": "^3.0.0", "minipass": "^7.0.2", "npm-package-arg": "^13.0.0", "npm-packlist": "^10.0.1", "npm-pick-manifest": "^11.0.1", "npm-registry-fetch": "^19.0.0", "proc-log": "^6.0.0", "sigstore": "^4.0.0", "ssri": "^13.0.0", "tar": "^7.4.3" }, "bin": { "pacote": "bin/index.js" } }, "sha512-VtZ0SB8mb5Tzw3dXDfVAIjhyVKUHZkS/ZH9/5mpKenwC9sFOXNI0JI7kEF7IMkwOnsWMFrvAZHzx1T5fmrp9FQ=="], + "pagefind": ["pagefind@1.4.0", "", { "optionalDependencies": { "@pagefind/darwin-arm64": "1.4.0", "@pagefind/darwin-x64": "1.4.0", "@pagefind/freebsd-x64": "1.4.0", "@pagefind/linux-arm64": "1.4.0", "@pagefind/linux-x64": "1.4.0", "@pagefind/windows-x64": "1.4.0" }, "bin": { "pagefind": "lib/runner/bin.cjs" } }, "sha512-z2kY1mQlL4J8q5EIsQkLzQjilovKzfNVhX8De6oyE6uHpfFtyBaqUpcl/XzJC/4fjD8vBDyh1zolimIcVrCn9g=="], "pako": ["pako@0.2.9", "", {}, "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA=="], @@ -3830,6 +3926,8 @@ "parse-bmfont-xml": ["parse-bmfont-xml@1.1.6", "", { "dependencies": { "xml-parse-from-string": "^1.0.0", "xml2js": "^0.5.0" } }, "sha512-0cEliVMZEhrFDwMh4SxIyVJpqYoOWDJ9P895tFuS+XuNzI5UBmBk5U5O4KuJdTnZpSBI4LFA2+ZiJaiwfSwlMA=="], + "parse-conflict-json": ["parse-conflict-json@5.0.1", "", { "dependencies": { "json-parse-even-better-errors": "^5.0.0", "just-diff": "^6.0.0", "just-diff-apply": "^5.2.0" } }, "sha512-ZHEmNKMq1wyJXNwLxyHnluPfRAFSIliBvbK/UiOceROt4Xh9Pz0fq49NytIaeaCUf5VR86hwQ/34FCcNU5/LKQ=="], + "parse-entities": ["parse-entities@4.0.2", "", { "dependencies": { "@types/unist": "^2.0.0", "character-entities-legacy": "^3.0.0", "character-reference-invalid": "^2.0.0", "decode-named-character-reference": "^1.0.0", "is-alphanumerical": "^2.0.0", "is-decimal": "^2.0.0", "is-hexadecimal": "^2.0.0" } }, "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw=="], "parse-latin": ["parse-latin@7.0.0", "", { "dependencies": { "@types/nlcst": "^2.0.0", "@types/unist": "^3.0.0", "nlcst-to-string": "^4.0.0", "unist-util-modify-children": "^4.0.0", "unist-util-visit-children": "^3.0.0", "vfile": "^6.0.0" } }, "sha512-mhHgobPPua5kZ98EF4HWiH167JWBfl4pvAIXXdbaVohtK7a6YBOy56kvhCqduqyo/f3yrHFWmqmiMg/BkBkYYQ=="], @@ -3944,7 +4042,7 @@ "prismjs": ["prismjs@1.30.0", "", {}, "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw=="], - "proc-log": ["proc-log@5.0.0", "", {}, "sha512-Azwzvl90HaF0aCz1JrDdXQykFakSSNPaPoiZ9fm5qJIMHioDZEi7OAdRwSm6rSoPtY3Qutnm3L7ogmg3dc+wbQ=="], + "proc-log": ["proc-log@6.1.0", "", {}, "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ=="], "process": ["process@0.11.10", "", {}, "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A=="], @@ -3952,8 +4050,14 @@ "process-warning": ["process-warning@5.0.0", "", {}, "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA=="], + "proggy": ["proggy@4.0.0", "", {}, "sha512-MbA4R+WQT76ZBm/5JUpV9yqcJt92175+Y0Bodg3HgiXzrmKu7Ggq+bpn6y6wHH+gN9NcyKn3yg1+d47VaKwNAQ=="], + "progress": ["progress@2.0.3", "", {}, "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA=="], + "promise-all-reject-late": ["promise-all-reject-late@1.0.1", "", {}, "sha512-vuf0Lf0lOxyQREH7GDIOUMLS7kz+gs8i6B+Yi8dC68a2sychGrHTJYghMBD6k7eUcH0H5P73EckCA48xijWqXw=="], + + "promise-call-limit": ["promise-call-limit@3.0.2", "", {}, "sha512-mRPQO2T1QQVw11E7+UdCJu7S61eJVWknzml9sC1heAdj1jxl0fWMBypIt9ZOcLFf8FkG995ZD7RnVk7HH72fZw=="], + "promise-retry": ["promise-retry@2.0.1", "", { "dependencies": { "err-code": "^2.0.2", "retry": "^0.12.0" } }, "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g=="], "promise.allsettled": ["promise.allsettled@1.0.7", "", { "dependencies": { "array.prototype.map": "^1.0.5", "call-bind": "^1.0.2", "define-properties": "^1.2.0", "es-abstract": "^1.22.1", "get-intrinsic": "^1.2.1", "iterate-value": "^1.0.2" } }, "sha512-hezvKvQQmsFkOdrZfYxUxkyxl8mgFQeT259Ajj9PXdbg9VzBCWrItOev72JyWxkCD5VSSqAeHmlN3tWx4DlmsA=="], @@ -4020,6 +4124,8 @@ "read-cache": ["read-cache@1.0.0", "", { "dependencies": { "pify": "^2.3.0" } }, "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA=="], + "read-cmd-shim": ["read-cmd-shim@6.0.0", "", {}, "sha512-1zM5HuOfagXCBWMN83fuFI/x+T/UhZ7k+KIzhrHXcQoeX5+7gmaDYjELQHmmzIodumBHeByBJT4QYS7ufAgs7A=="], + "readable-stream": ["readable-stream@4.7.0", "", { "dependencies": { "abort-controller": "^3.0.0", "buffer": "^6.0.3", "events": "^3.3.0", "process": "^0.11.10", "string_decoder": "^1.3.0" } }, "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg=="], "readable-web-to-node-stream": ["readable-web-to-node-stream@3.0.4", "", { "dependencies": { "readable-stream": "^4.7.0" } }, "sha512-9nX56alTf5bwXQ3ZDipHJhusu9NTQJ/CVPtb/XHAJCXihZeitfJvIRS4GqQ/mfIoOE3IelHMrpayVrosdHBuLw=="], @@ -4220,6 +4326,8 @@ "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], + "sigstore": ["sigstore@4.1.0", "", { "dependencies": { "@sigstore/bundle": "^4.0.0", "@sigstore/core": "^3.1.0", "@sigstore/protobuf-specs": "^0.5.0", "@sigstore/sign": "^4.1.0", "@sigstore/tuf": "^4.0.1", "@sigstore/verify": "^3.1.0" } }, "sha512-/fUgUhYghuLzVT/gaJoeVehLCgZiUxPCPMcyVNY0lIf/cTCz58K/WTI7PefDarXxp9nUKpEwg1yyz3eSBMTtgA=="], + "simple-swizzle": ["simple-swizzle@0.2.4", "", { "dependencies": { "is-arrayish": "^0.3.1" } }, "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw=="], "simple-update-notifier": ["simple-update-notifier@2.0.0", "", { "dependencies": { "semver": "^7.5.3" } }, "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w=="], @@ -4270,6 +4378,12 @@ "space-separated-tokens": ["space-separated-tokens@2.0.2", "", {}, "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q=="], + "spdx-exceptions": ["spdx-exceptions@2.5.0", "", {}, "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w=="], + + "spdx-expression-parse": ["spdx-expression-parse@4.0.0", "", { "dependencies": { "spdx-exceptions": "^2.1.0", "spdx-license-ids": "^3.0.0" } }, "sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ=="], + + "spdx-license-ids": ["spdx-license-ids@3.0.23", "", {}, "sha512-CWLcCCH7VLu13TgOH+r8p1O/Znwhqv/dbb6lqWy67G+pT1kHmeD/+V36AVb/vq8QMIQwVShJ6Ssl5FPh0fuSdw=="], + "split2": ["split2@4.2.0", "", {}, "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="], "sprintf-js": ["sprintf-js@1.1.3", "", {}, "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA=="], @@ -4278,7 +4392,7 @@ "srvx": ["srvx@0.9.8", "", { "bin": { "srvx": "bin/srvx.mjs" } }, "sha512-RZaxTKJEE/14HYn8COLuUOJAt0U55N9l1Xf6jj+T0GoA01EUH1Xz5JtSUOI+EHn+AEgPCVn7gk6jHJffrr06fQ=="], - "ssri": ["ssri@12.0.0", "", { "dependencies": { "minipass": "^7.0.3" } }, "sha512-S7iGNosepx9RadX82oimUkvr0Ct7IjJbEbs4mJcTxst8um95J3sDYU1RBEOvdu6oL1Wek2ODI5i4MAw+dZ6cAQ=="], + "ssri": ["ssri@13.0.1", "", { "dependencies": { "minipass": "^7.0.3" } }, "sha512-QUiRf1+u9wPTL/76GTYlKttDEBWV1ga9ZXW8BG6kfdeyyM8LGPix9gROyg9V2+P0xNyF3X2Go526xKFdMZrHSQ=="], "sst": ["sst@3.18.10", "", { "dependencies": { "aws-sdk": "2.1692.0", "aws4fetch": "1.0.18", "jose": "5.2.3", "opencontrol": "0.0.6", "openid-client": "5.6.4" }, "optionalDependencies": { "sst-darwin-arm64": "3.18.10", "sst-darwin-x64": "3.18.10", "sst-linux-arm64": "3.18.10", "sst-linux-x64": "3.18.10", "sst-linux-x86": "3.18.10", "sst-win32-arm64": "3.18.10", "sst-win32-x64": "3.18.10", "sst-win32-x86": "3.18.10" }, "bin": { "sst": "bin/sst.mjs" } }, "sha512-SY+ldeJ9K5E9q+DhjXA3e2W3BEOzBwkE3IyLSD71uA3/5nRhUAST31iOWEpW36LbIvSQ9uOVDFcebztoLJ8s7w=="], @@ -4454,6 +4568,8 @@ "tree-sitter-bash": ["tree-sitter-bash@0.25.0", "", { "dependencies": { "node-addon-api": "^8.2.1", "node-gyp-build": "^4.8.2" }, "peerDependencies": { "tree-sitter": "^0.25.0" }, "optionalPeers": ["tree-sitter"] }, "sha512-gZtlj9+qFS81qKxpLfD6H0UssQ3QBc/F0nKkPsiFDyfQF2YBqYvglFJUzchrPpVhZe9kLZTrJ9n2J6lmka69Vg=="], + "treeverse": ["treeverse@3.0.0", "", {}, "sha512-gcANaAnd2QDZFmHFEOF4k7uc1J/6a6z3DJMd/QwEyxLoKGiptJRwid582r7QIsFlFMIZ3SnxfS52S4hm2DHkuQ=="], + "trim-lines": ["trim-lines@3.0.1", "", {}, "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg=="], "trough": ["trough@2.2.0", "", {}, "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw=="], @@ -4472,6 +4588,8 @@ "tsscmp": ["tsscmp@1.0.6", "", {}, "sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA=="], + "tuf-js": ["tuf-js@4.1.0", "", { "dependencies": { "@tufjs/models": "4.1.0", "debug": "^4.4.3", "make-fetch-happen": "^15.0.1" } }, "sha512-50QV99kCKH5P/Vs4E2Gzp7BopNV+KzTXqWeaxrfu5IQJBOULRsTIS9seSsOVT8ZnGXzCyx55nYWAi4qJzpZKEQ=="], + "tunnel": ["tunnel@0.0.6", "", {}, "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg=="], "turbo": ["turbo@2.8.13", "", { "optionalDependencies": { "turbo-darwin-64": "2.8.13", "turbo-darwin-arm64": "2.8.13", "turbo-linux-64": "2.8.13", "turbo-linux-arm64": "2.8.13", "turbo-windows-64": "2.8.13", "turbo-windows-arm64": "2.8.13" }, "bin": { "turbo": "bin/turbo" } }, "sha512-nyM99hwFB9/DHaFyKEqatdayGjsMNYsQ/XBNO6MITc7roncZetKb97MpHxWf3uiU+LB9c9HUlU3Jp2Ixei2k1A=="], @@ -4598,6 +4716,8 @@ "uuid": ["uuid@13.0.0", "", { "bin": { "uuid": "dist-node/bin/uuid" } }, "sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w=="], + "validate-npm-package-name": ["validate-npm-package-name@7.0.2", "", {}, "sha512-hVDIBwsRruT73PbK7uP5ebUt+ezEtCmzZz3F59BSr2F6OVFnJ/6h8liuvdLrQ88Xmnk6/+xGGuq+pG9WwTuy3A=="], + "vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="], "verror": ["verror@1.10.1", "", { "dependencies": { "assert-plus": "^1.0.0", "core-util-is": "1.0.2", "extsprintf": "^1.2.0" } }, "sha512-veufcmxri4e3XSrT0xwfUR7kguIkaxBeosDg00yDWhk49wdwkSUrvvsm7nc75e1PUyvIeZj6nS8VQRYz2/S4Xg=="], @@ -4656,6 +4776,8 @@ "vscode-uri": ["vscode-uri@3.1.0", "", {}, "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ=="], + "walk-up-path": ["walk-up-path@4.0.0", "", {}, "sha512-3hu+tD8YzSLGuFYtPRb48vdhKMi0KQV5sn+uWr8+7dMEq/2G/dtLrdDinkLjqq5TIbIBjYJ4Ax/n3YiaW7QM8A=="], + "wcwidth": ["wcwidth@1.0.1", "", { "dependencies": { "defaults": "^1.0.3" } }, "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg=="], "web-namespaces": ["web-namespaces@2.0.1", "", {}, "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ=="], @@ -4700,6 +4822,8 @@ "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="], + "write-file-atomic": ["write-file-atomic@7.0.1", "", { "dependencies": { "signal-exit": "^4.0.1" } }, "sha512-OTIk8iR8/aCRWBqvxrzxR0hgxWpnYBblY1S5hDWBQfk/VFmJwzmJgQFN3WsoUKHISv2eAwe+PpbUzyL1CKTLXg=="], + "ws": ["ws@8.18.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw=="], "wsl-utils": ["wsl-utils@0.3.1", "", { "dependencies": { "is-wsl": "^3.1.0", "powershell-utils": "^0.1.0" } }, "sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg=="], @@ -5044,6 +5168,8 @@ "@electron/rebuild/detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], + "@electron/rebuild/node-gyp": ["node-gyp@11.5.0", "", { "dependencies": { "env-paths": "^2.2.0", "exponential-backoff": "^3.1.1", "graceful-fs": "^4.2.6", "make-fetch-happen": "^14.0.3", "nopt": "^8.0.0", "proc-log": "^5.0.0", "semver": "^7.3.5", "tar": "^7.4.3", "tinyglobby": "^0.2.12", "which": "^5.0.0" }, "bin": { "node-gyp": "bin/node-gyp.js" } }, "sha512-ra7Kvlhxn5V9Slyus0ygMa2h+UqExPqUIkfk7Pc8QTLT956JLSy51uWFwHtIYy0vI8cB4BDhc/S03+880My/LQ=="], + "@electron/rebuild/yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="], "@electron/universal/fs-extra": ["fs-extra@11.3.4", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA=="], @@ -5120,7 +5246,13 @@ "@modelcontextprotocol/sdk/zod-to-json-schema": ["zod-to-json-schema@3.25.1", "", { "peerDependencies": { "zod": "^3.25 || ^4" } }, "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA=="], - "@npmcli/agent/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], + "@npmcli/arborist/common-ancestor-path": ["common-ancestor-path@2.0.0", "", {}, "sha512-dnN3ibLeoRf2HNC+OlCiNc5d2zxbLJXOtiZUudNFSXZrNSydxcCsSpRzXwfu7BBWCIfHPw+xTayeBvJCP/D8Ng=="], + + "@npmcli/arborist/minimatch": ["minimatch@10.2.4", "", { "dependencies": { "brace-expansion": "^5.0.2" } }, "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg=="], + + "@npmcli/map-workspaces/minimatch": ["minimatch@10.2.4", "", { "dependencies": { "brace-expansion": "^5.0.2" } }, "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg=="], + + "@npmcli/query/postcss-selector-parser": ["postcss-selector-parser@7.1.1", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg=="], "@octokit/auth-app/@octokit/request": ["@octokit/request@10.0.8", "", { "dependencies": { "@octokit/endpoint": "^11.0.3", "@octokit/request-error": "^7.0.2", "@octokit/types": "^16.0.0", "fast-content-type-parse": "^3.0.0", "json-with-bigint": "^3.5.3", "universal-user-agent": "^7.0.2" } }, "sha512-SJZNwY9pur9Agf7l87ywFi14W+Hd9Jg6Ifivsd33+/bGUQIjNujdFiXII2/qSlN2ybqUHfp5xpekMEjIBTjlSw=="], @@ -5288,6 +5420,8 @@ "@testing-library/dom/dom-accessibility-api": ["dom-accessibility-api@0.5.16", "", {}, "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg=="], + "@tufjs/models/minimatch": ["minimatch@10.2.4", "", { "dependencies": { "brace-expansion": "^5.0.2" } }, "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg=="], + "@types/plist/xmlbuilder": ["xmlbuilder@15.1.1", "", {}, "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg=="], "@vitest/expect/@vitest/utils": ["@vitest/utils@3.2.4", "", { "dependencies": { "@vitest/pretty-format": "3.2.4", "loupe": "^3.1.4", "tinyrainbow": "^2.0.0" } }, "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA=="], @@ -5320,6 +5454,8 @@ "app-builder-lib/ci-info": ["ci-info@4.3.1", "", {}, "sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA=="], + "app-builder-lib/hosted-git-info": ["hosted-git-info@4.1.0", "", { "dependencies": { "lru-cache": "^6.0.0" } }, "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA=="], + "app-builder-lib/minimatch": ["minimatch@10.2.4", "", { "dependencies": { "brace-expansion": "^5.0.2" } }, "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg=="], "app-builder-lib/which": ["which@5.0.0", "", { "dependencies": { "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" } }, "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ=="], @@ -5364,10 +5500,6 @@ "c12/dotenv": ["dotenv@17.3.1", "", {}, "sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA=="], - "cacache/glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="], - - "cacache/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], - "cli-truncate/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], "clone-response/mimic-response": ["mimic-response@1.0.1", "", {}, "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ=="], @@ -5460,8 +5592,6 @@ "happy-dom/ws": ["ws@8.19.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg=="], - "hosted-git-info/lru-cache": ["lru-cache@6.0.0", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="], - "html-minifier-terser/commander": ["commander@10.0.1", "", {}, "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug=="], "html-minifier-terser/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], @@ -5470,8 +5600,12 @@ "iconv-corefoundation/node-addon-api": ["node-addon-api@1.7.2", "", {}, "sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg=="], + "ignore-walk/minimatch": ["minimatch@10.2.4", "", { "dependencies": { "brace-expansion": "^5.0.2" } }, "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg=="], + "js-beautify/glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="], + "js-beautify/nopt": ["nopt@7.2.1", "", { "dependencies": { "abbrev": "^2.0.0" }, "bin": { "nopt": "bin/nopt.js" } }, "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w=="], + "katex/commander": ["commander@8.3.0", "", {}, "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="], "lazystream/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="], @@ -5502,18 +5636,12 @@ "minipass-pipeline/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], - "minipass-sized/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], - "motion/framer-motion": ["framer-motion@12.35.2", "", { "dependencies": { "motion-dom": "^12.35.2", "motion-utils": "^12.29.2", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-dhfuEMaNo0hc+AEqyHiIfiJRNb9U9UQutE9FoKm5pjf7CMitp9xPEF1iWZihR1q86LBmo6EJ7S8cN8QXEy49AA=="], "mssql/commander": ["commander@11.1.0", "", {}, "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ=="], "nitro/h3": ["h3@2.0.1-rc.5", "", { "dependencies": { "rou3": "^0.7.9", "srvx": "^0.9.1" }, "peerDependencies": { "crossws": "^0.4.1" }, "optionalPeers": ["crossws"] }, "sha512-qkohAzCab0nLzXNm78tBjZDvtKMTmtygS8BJLT3VPczAQofdqlFXDPkXdLMJN4r05+xqneG8snZJ0HgkERCZTg=="], - "node-gyp/nopt": ["nopt@8.1.0", "", { "dependencies": { "abbrev": "^3.0.0" }, "bin": { "nopt": "bin/nopt.js" } }, "sha512-ieGu42u/Qsa4TFktmaKEwM6MQH0pOWnaB3htzh0JRtx84+Mebc0cbZYN5bC+6WTZ4+77xrL9Pn5m7CV6VIkV7A=="], - - "node-gyp/which": ["which@5.0.0", "", { "dependencies": { "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" } }, "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ=="], - "node-gyp-build-optional-packages/detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], "npm-run-path/path-key": ["path-key@4.0.0", "", {}, "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="], @@ -5950,6 +6078,14 @@ "@electron/notarize/fs-extra/jsonfile": ["jsonfile@6.2.0", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg=="], + "@electron/rebuild/node-gyp/make-fetch-happen": ["make-fetch-happen@14.0.3", "", { "dependencies": { "@npmcli/agent": "^3.0.0", "cacache": "^19.0.1", "http-cache-semantics": "^4.1.1", "minipass": "^7.0.2", "minipass-fetch": "^4.0.0", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "negotiator": "^1.0.0", "proc-log": "^5.0.0", "promise-retry": "^2.0.1", "ssri": "^12.0.0" } }, "sha512-QMjGbFTP0blj97EeidG5hk/QhKQ3T4ICckQGLgz38QF7Vgbk6e6FTARN8KhKxyBbWn8R0HU+bnw8aSoFPD4qtQ=="], + + "@electron/rebuild/node-gyp/nopt": ["nopt@8.1.0", "", { "dependencies": { "abbrev": "^3.0.0" }, "bin": { "nopt": "bin/nopt.js" } }, "sha512-ieGu42u/Qsa4TFktmaKEwM6MQH0pOWnaB3htzh0JRtx84+Mebc0cbZYN5bC+6WTZ4+77xrL9Pn5m7CV6VIkV7A=="], + + "@electron/rebuild/node-gyp/proc-log": ["proc-log@5.0.0", "", {}, "sha512-Azwzvl90HaF0aCz1JrDdXQykFakSSNPaPoiZ9fm5qJIMHioDZEi7OAdRwSm6rSoPtY3Qutnm3L7ogmg3dc+wbQ=="], + + "@electron/rebuild/node-gyp/which": ["which@5.0.0", "", { "dependencies": { "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" } }, "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ=="], + "@electron/rebuild/yargs/cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="], "@electron/rebuild/yargs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], @@ -6196,6 +6332,8 @@ "app-builder-lib/@electron/get/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + "app-builder-lib/hosted-git-info/lru-cache": ["lru-cache@6.0.0", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="], + "app-builder-lib/which/isexe": ["isexe@3.1.5", "", {}, "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w=="], "archiver-utils/glob/jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="], @@ -6222,12 +6360,6 @@ "c12/chokidar/readdirp": ["readdirp@5.0.0", "", {}, "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ=="], - "cacache/glob/jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="], - - "cacache/glob/minimatch": ["minimatch@9.0.9", "", { "dependencies": { "brace-expansion": "^2.0.2" } }, "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg=="], - - "cacache/glob/path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="], - "cli-truncate/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], "cli-truncate/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], @@ -6268,16 +6400,14 @@ "js-beautify/glob/path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="], + "js-beautify/nopt/abbrev": ["abbrev@2.0.0", "", {}, "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ=="], + "lazystream/readable-stream/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="], "lazystream/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="], "motion/framer-motion/motion-dom": ["motion-dom@12.35.2", "", { "dependencies": { "motion-utils": "^12.29.2" } }, "sha512-pWXFMTwvGDbx1Fe9YL5HZebv2NhvGBzRtiNUv58aoK7+XrsuaydQ0JGRKK2r+bTKlwgSWwWxHbP5249Qr/BNpg=="], - "node-gyp/nopt/abbrev": ["abbrev@3.0.1", "", {}, "sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg=="], - - "node-gyp/which/isexe": ["isexe@3.1.5", "", {}, "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w=="], - "opencode/@ai-sdk/openai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.20", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-iXHVe0apM2zUEzauqJwqmpC37A5rihrStAih5Ks+JE32iTe4LZ58y17UGBjpQQTCRw9YxMeo2UFLxLpBluyvLQ=="], "opencode/@ai-sdk/openai-compatible/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.20", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-iXHVe0apM2zUEzauqJwqmpC37A5rihrStAih5Ks+JE32iTe4LZ58y17UGBjpQQTCRw9YxMeo2UFLxLpBluyvLQ=="], @@ -6448,6 +6578,20 @@ "@electron/asar/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + "@electron/rebuild/node-gyp/make-fetch-happen/@npmcli/agent": ["@npmcli/agent@3.0.0", "", { "dependencies": { "agent-base": "^7.1.0", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.1", "lru-cache": "^10.0.1", "socks-proxy-agent": "^8.0.3" } }, "sha512-S79NdEgDQd/NGCay6TCoVzXSj74skRZIKJcpJjC5lOq34SZzyI6MqtiiWoiVWoVrTcGjNeC4ipbh1VIHlpfF5Q=="], + + "@electron/rebuild/node-gyp/make-fetch-happen/cacache": ["cacache@19.0.1", "", { "dependencies": { "@npmcli/fs": "^4.0.0", "fs-minipass": "^3.0.0", "glob": "^10.2.2", "lru-cache": "^10.0.1", "minipass": "^7.0.3", "minipass-collect": "^2.0.1", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "p-map": "^7.0.2", "ssri": "^12.0.0", "tar": "^7.4.3", "unique-filename": "^4.0.0" } }, "sha512-hdsUxulXCi5STId78vRVYEtDAjq99ICAUktLTeTYsLoTE6Z8dS0c8pWNCxwdrk9YfJeobDZc2Y186hD/5ZQgFQ=="], + + "@electron/rebuild/node-gyp/make-fetch-happen/minipass-fetch": ["minipass-fetch@4.0.1", "", { "dependencies": { "minipass": "^7.0.3", "minipass-sized": "^1.0.3", "minizlib": "^3.0.1" }, "optionalDependencies": { "encoding": "^0.1.13" } }, "sha512-j7U11C5HXigVuutxebFadoYBbd7VSdZWggSe64NVdvWNBqGAiXPL2QVCehjmw7lY1oF9gOllYbORh+hiNgfPgQ=="], + + "@electron/rebuild/node-gyp/make-fetch-happen/negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="], + + "@electron/rebuild/node-gyp/make-fetch-happen/ssri": ["ssri@12.0.0", "", { "dependencies": { "minipass": "^7.0.3" } }, "sha512-S7iGNosepx9RadX82oimUkvr0Ct7IjJbEbs4mJcTxst8um95J3sDYU1RBEOvdu6oL1Wek2ODI5i4MAw+dZ6cAQ=="], + + "@electron/rebuild/node-gyp/nopt/abbrev": ["abbrev@3.0.1", "", {}, "sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg=="], + + "@electron/rebuild/node-gyp/which/isexe": ["isexe@3.1.5", "", {}, "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w=="], + "@electron/rebuild/yargs/cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], "@electron/rebuild/yargs/cliui/wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], @@ -6564,10 +6708,6 @@ "babel-plugin-module-resolver/glob/path-scurry/minipass": ["minipass@7.1.3", "", {}, "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A=="], - "cacache/glob/jackspeak/@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="], - - "cacache/glob/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], - "cli-truncate/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], "dir-compare/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], @@ -6672,6 +6812,16 @@ "@aws-sdk/token-providers/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser/strnum": ["strnum@2.2.0", "", {}, "sha512-Y7Bj8XyJxnPAORMZj/xltsfo55uOiyHcU2tnAVzHUnSJR/KsEX+9RoDeXEnsXtl/CX4fAcrt64gZ13aGaWPeBg=="], + "@electron/rebuild/node-gyp/make-fetch-happen/@npmcli/agent/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], + + "@electron/rebuild/node-gyp/make-fetch-happen/cacache/@npmcli/fs": ["@npmcli/fs@4.0.0", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-/xGlezI6xfGO9NwuJlnwz/K14qD1kCSAGtacBHnGzeAIuJGazcp45KP5NuyARXoKb7cwulAGWVsbeSxdG/cb0Q=="], + + "@electron/rebuild/node-gyp/make-fetch-happen/cacache/glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="], + + "@electron/rebuild/node-gyp/make-fetch-happen/cacache/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], + + "@electron/rebuild/node-gyp/make-fetch-happen/minipass-fetch/minipass-sized": ["minipass-sized@1.0.3", "", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g=="], + "@electron/rebuild/yargs/cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], "@electron/rebuild/yargs/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], @@ -6690,12 +6840,6 @@ "babel-plugin-module-resolver/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], - "cacache/glob/jackspeak/@isaacs/cliui/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], - - "cacache/glob/jackspeak/@isaacs/cliui/wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="], - - "cacache/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], - "electron-builder/yargs/cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], "electron-builder/yargs/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], @@ -6720,16 +6864,34 @@ "@aws-sdk/credential-provider-cognito-identity/@aws-sdk/nested-clients/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser/strnum": ["strnum@2.2.0", "", {}, "sha512-Y7Bj8XyJxnPAORMZj/xltsfo55uOiyHcU2tnAVzHUnSJR/KsEX+9RoDeXEnsXtl/CX4fAcrt64gZ13aGaWPeBg=="], + "@electron/rebuild/node-gyp/make-fetch-happen/cacache/glob/jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="], + + "@electron/rebuild/node-gyp/make-fetch-happen/cacache/glob/minimatch": ["minimatch@9.0.9", "", { "dependencies": { "brace-expansion": "^2.0.2" } }, "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg=="], + + "@electron/rebuild/node-gyp/make-fetch-happen/cacache/glob/path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="], + + "@electron/rebuild/node-gyp/make-fetch-happen/minipass-fetch/minipass-sized/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], + "archiver-utils/glob/jackspeak/@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], "archiver-utils/glob/jackspeak/@isaacs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], - "cacache/glob/jackspeak/@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], - - "cacache/glob/jackspeak/@isaacs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], - "js-beautify/glob/jackspeak/@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], "js-beautify/glob/jackspeak/@isaacs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], + + "@electron/rebuild/node-gyp/make-fetch-happen/cacache/glob/jackspeak/@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="], + + "@electron/rebuild/node-gyp/make-fetch-happen/cacache/glob/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], + + "@electron/rebuild/node-gyp/make-fetch-happen/cacache/glob/jackspeak/@isaacs/cliui/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], + + "@electron/rebuild/node-gyp/make-fetch-happen/cacache/glob/jackspeak/@isaacs/cliui/wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="], + + "@electron/rebuild/node-gyp/make-fetch-happen/cacache/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + + "@electron/rebuild/node-gyp/make-fetch-happen/cacache/glob/jackspeak/@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], + + "@electron/rebuild/node-gyp/make-fetch-happen/cacache/glob/jackspeak/@isaacs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], } } diff --git a/packages/opencode/src/mcp/index.ts b/packages/opencode/src/mcp/index.ts index afb7456415..b59791f225 100644 --- a/packages/opencode/src/mcp/index.ts +++ b/packages/opencode/src/mcp/index.ts @@ -459,7 +459,7 @@ export namespace MCP { env: { ...process.env, ...mcp.environment, - }, + } as any, }) transport.stderr?.on("data", (chunk: Buffer) => { log.info(`mcp stderr: ${chunk.toString()}`, { key }) From 7a28d0520a02dd31e24e497d9478ca25bf5e9d0b Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Thu, 19 Mar 2026 23:19:36 -0400 Subject: [PATCH 06/16] fix(format): handle custom formatter command property with enabled() interface The formatter Info interface uses enabled(): Promise but user configs provide a command property. Added explicit return type to the arrow function that transforms command into the enabled() return value. Also added test to verify custom formatters with command property work. --- packages/opencode/src/format/index.ts | 2 +- packages/opencode/test/format/format.test.ts | 21 ++++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/packages/opencode/src/format/index.ts b/packages/opencode/src/format/index.ts index 8a7ee0af5c..00321be6af 100644 --- a/packages/opencode/src/format/index.ts +++ b/packages/opencode/src/format/index.ts @@ -62,7 +62,7 @@ export namespace Format { formatters[name] = { ...info, name, - enabled: async () => info.command, + enabled: async (): Promise => info.command, } } } else { diff --git a/packages/opencode/test/format/format.test.ts b/packages/opencode/test/format/format.test.ts index 2718e125d0..26c1468d34 100644 --- a/packages/opencode/test/format/format.test.ts +++ b/packages/opencode/test/format/format.test.ts @@ -55,6 +55,27 @@ describe("Format", () => { }) }) + test("status() includes custom formatters with command from config", async () => { + await using tmp = await tmpdir({ + config: { + formatter: { + customtool: { + command: ["echo", "formatted", "$FILE"], + extensions: [".custom"], + }, + }, + }, + }) + + await withServices(tmp.path, Format.layer, async (rt) => { + const statuses = await rt.runPromise(Format.Service.use((s) => s.status())) + const custom = statuses.find((s) => s.name === "customtool") + expect(custom).toBeDefined() + expect(custom!.extensions).toContain(".custom") + expect(custom!.enabled).toBe(true) + }) + }) + test("service initializes without error", async () => { await using tmp = await tmpdir() From 20249bb723b9427e1b27f77614ddb94f6965267c Mon Sep 17 00:00:00 2001 From: Dax Date: Thu, 19 Mar 2026 23:29:18 -0400 Subject: [PATCH 07/16] Apply suggestion from @greptile-apps[bot] Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> --- packages/opencode/src/format/formatter.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/opencode/src/format/formatter.ts b/packages/opencode/src/format/formatter.ts index ab8674144f..f69b9925f4 100644 --- a/packages/opencode/src/format/formatter.ts +++ b/packages/opencode/src/format/formatter.ts @@ -71,8 +71,11 @@ export const prettier: Info = { devDependencies?: Record }>(item) if (json.dependencies?.prettier || json.devDependencies?.prettier) { - return [await Npm.which("prettier"), "--write", "$FILE"] - } + if (json.dependencies?.prettier || json.devDependencies?.prettier) { + const bin = await Npm.which("prettier").catch(() => null) + if (!bin) return false + return [bin, "--write", "$FILE"] + } } return false }, From e48da968869499932e6f35a070b16c13dd0cc7ac Mon Sep 17 00:00:00 2001 From: Dax Date: Thu, 19 Mar 2026 23:32:30 -0400 Subject: [PATCH 08/16] Update packages/opencode/src/npm/index.ts Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> --- packages/opencode/src/npm/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/opencode/src/npm/index.ts b/packages/opencode/src/npm/index.ts index 50879a10cc..534c0b1564 100644 --- a/packages/opencode/src/npm/index.ts +++ b/packages/opencode/src/npm/index.ts @@ -160,7 +160,7 @@ export namespace Npm { ).catch(() => undefined) if (pkgJson?.bin) { const bin = pkgJson.bin - if (typeof bin === "string") return path.basename(bin) + if (typeof bin === "string") return unscoped const keys = Object.keys(bin) if (keys.length === 1) return keys[0] const unscoped = pkg.startsWith("@") ? pkg.split("/")[1] : pkg From 3b669e7f2b7ef4b8333e8abfe10c3e5c3f83c917 Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Thu, 19 Mar 2026 23:37:00 -0400 Subject: [PATCH 09/16] fix type --- packages/opencode/src/format/formatter.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/opencode/src/format/formatter.ts b/packages/opencode/src/format/formatter.ts index f69b9925f4..cdb4f09be7 100644 --- a/packages/opencode/src/format/formatter.ts +++ b/packages/opencode/src/format/formatter.ts @@ -71,11 +71,10 @@ export const prettier: Info = { devDependencies?: Record }>(item) if (json.dependencies?.prettier || json.devDependencies?.prettier) { - if (json.dependencies?.prettier || json.devDependencies?.prettier) { - const bin = await Npm.which("prettier").catch(() => null) - if (!bin) return false - return [bin, "--write", "$FILE"] - } + const bin = await Npm.which("prettier").catch(() => null) + if (!bin) return false + return [bin, "--write", "$FILE"] + } } return false }, From bc43bf378d475995b63502b8aa0ee210b58b7080 Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Thu, 19 Mar 2026 23:38:54 -0400 Subject: [PATCH 10/16] sync --- packages/opencode/src/npm/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/opencode/src/npm/index.ts b/packages/opencode/src/npm/index.ts index 534c0b1564..90e135367f 100644 --- a/packages/opencode/src/npm/index.ts +++ b/packages/opencode/src/npm/index.ts @@ -159,11 +159,11 @@ export namespace Npm { path.join(dir, "node_modules", pkg, "package.json"), ).catch(() => undefined) if (pkgJson?.bin) { + const unscoped = pkg.startsWith("@") ? pkg.split("/")[1] : pkg const bin = pkgJson.bin - if (typeof bin === "string") return unscoped + if (typeof bin === "string") return unscoped const keys = Object.keys(bin) if (keys.length === 1) return keys[0] - const unscoped = pkg.startsWith("@") ? pkg.split("/")[1] : pkg return bin[unscoped] ? unscoped : keys[0] } return files[0] From df44b41aaa06b0a0c54a008740c11896708d9727 Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Fri, 20 Mar 2026 10:33:53 -0400 Subject: [PATCH 11/16] fix: handle undefined from Npm.which in all callers Npm.which now returns undefined instead of throwing when binary not found. Update all callers to check for undefined and return early. Avoid explicit union types by using a resolved variable pattern. --- packages/opencode/src/format/formatter.ts | 8 +++-- packages/opencode/src/lsp/server.ts | 37 +++++++++++++++++------ packages/opencode/src/npm/index.ts | 2 +- 3 files changed, 35 insertions(+), 12 deletions(-) diff --git a/packages/opencode/src/format/formatter.ts b/packages/opencode/src/format/formatter.ts index cdb4f09be7..d26a0f9943 100644 --- a/packages/opencode/src/format/formatter.ts +++ b/packages/opencode/src/format/formatter.ts @@ -92,7 +92,9 @@ export const oxfmt: Info = { devDependencies?: Record }>(item) if (json.dependencies?.oxfmt || json.devDependencies?.oxfmt) { - return [await Npm.which("oxfmt"), "$FILE"] + const bin = await Npm.which("oxfmt") + if (!bin) return false + return [bin, "$FILE"] } } return false @@ -134,7 +136,9 @@ export const biome: Info = { for (const config of configs) { const found = await Filesystem.findUp(config, Instance.directory, Instance.worktree) if (found.length > 0) { - return [await Npm.which("@biomejs/biome"), "check", "--write", "$FILE"] + const bin = await Npm.which("@biomejs/biome") + if (!bin) return false + return [bin, "check", "--write", "$FILE"] } } return false diff --git a/packages/opencode/src/lsp/server.ts b/packages/opencode/src/lsp/server.ts index e822642b05..aa9bc884a8 100644 --- a/packages/opencode/src/lsp/server.ts +++ b/packages/opencode/src/lsp/server.ts @@ -103,7 +103,9 @@ export namespace LSPServer { const tsserver = Module.resolve("typescript/lib/tsserver.js", Instance.directory) log.info("typescript server", { tsserver }) if (!tsserver) return - const proc = spawn(await Npm.which("typescript-language-server"), ["--stdio"], { + const bin = await Npm.which("typescript-language-server") + if (!bin) return + const proc = spawn(bin, ["--stdio"], { cwd: root, env: { ...process.env, @@ -129,7 +131,9 @@ export namespace LSPServer { const args: string[] = [] if (!binary) { if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return - binary = await Npm.which("@vue/language-server") + const resolved = await Npm.which("@vue/language-server") + if (!resolved) return + binary = resolved } args.push("--stdio") const proc = spawn(binary, args, { @@ -322,6 +326,7 @@ export namespace LSPServer { const resolved = Module.resolve("biome", root) if (!resolved) return bin = await Npm.which("biome") + if (!bin) return args = ["lsp-proxy", "--stdio"] } @@ -488,7 +493,9 @@ export namespace LSPServer { const args = [] if (!binary) { if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return - binary = await Npm.which("pyright") + const resolved = await Npm.which("pyright") + if (!resolved) return + binary = resolved } args.push("--stdio") @@ -1003,7 +1010,9 @@ export namespace LSPServer { const args: string[] = [] if (!binary) { if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return - binary = await Npm.which("svelte-language-server") + const resolved = await Npm.which("svelte-language-server") + if (!resolved) return + binary = resolved } args.push("--stdio") const proc = spawn(binary, args, { @@ -1035,7 +1044,9 @@ export namespace LSPServer { const args: string[] = [] if (!binary) { if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return - binary = await Npm.which("@astrojs/language-server") + const resolved = await Npm.which("@astrojs/language-server") + if (!resolved) return + binary = resolved } args.push("--stdio") const proc = spawn(binary, args, { @@ -1284,7 +1295,9 @@ export namespace LSPServer { const args: string[] = [] if (!binary) { if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return - binary = await Npm.which("yaml-language-server") + const resolved = await Npm.which("yaml-language-server") + if (!resolved) return + binary = resolved } args.push("--stdio") const proc = spawn(binary, args, { @@ -1449,7 +1462,9 @@ export namespace LSPServer { const args: string[] = [] if (!binary) { if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return - binary = await Npm.which("intelephense") + const resolved = await Npm.which("intelephense") + if (!resolved) return + binary = resolved } args.push("--stdio") const proc = spawn(binary, args, { @@ -1531,7 +1546,9 @@ export namespace LSPServer { const args: string[] = [] if (!binary) { if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return - binary = await Npm.which("bash-language-server") + const resolved = await Npm.which("bash-language-server") + if (!resolved) return + binary = resolved } args.push("start") const proc = spawn(binary, args, { @@ -1724,7 +1741,9 @@ export namespace LSPServer { const args: string[] = [] if (!binary) { if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return - binary = await Npm.which("dockerfile-language-server-nodejs") + const resolved = await Npm.which("dockerfile-language-server-nodejs") + if (!resolved) return + binary = resolved } args.push("--stdio") const proc = spawn(binary, args, { diff --git a/packages/opencode/src/npm/index.ts b/packages/opencode/src/npm/index.ts index 90e135367f..db95c6e1df 100644 --- a/packages/opencode/src/npm/index.ts +++ b/packages/opencode/src/npm/index.ts @@ -175,7 +175,7 @@ export namespace Npm { await rm(path.join(dir, "package-lock.json"), { force: true }) await add(pkg) const resolved = await pick() - if (!resolved) throw new Error(`No binary found for package "${pkg}" after install`) + if (!resolved) return return path.join(binDir, resolved) } } From 011bccd4d8e216d339bd47dead63628bcd167bdc Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Thu, 26 Mar 2026 18:22:35 -0400 Subject: [PATCH 12/16] fix types --- packages/opencode/src/config/config.ts | 28 +------------------------- packages/opencode/src/mcp/index.ts | 1 - 2 files changed, 1 insertion(+), 28 deletions(-) diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index ad6164246b..329d2d1298 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -81,27 +81,6 @@ export namespace Config { log.info("config dir is not writable, skipping dependency install", { dir }) return } - const pkg = path.join(dir, "package.json") - const targetVersion = Installation.isLocal() ? "*" : Installation.VERSION - - const json = await Filesystem.readJson<{ dependencies?: Record }>(pkg).catch(() => ({ - dependencies: {}, - })) - json.dependencies = { - ...json.dependencies, - "@opencode-ai/plugin": targetVersion, - } - await Filesystem.writeJson(pkg, json) - - const gitignore = path.join(dir, ".gitignore") - if (!(await Filesystem.exists(gitignore))) - await Filesystem.write( - gitignore, - ["node_modules", "plans", "package.json", "bun.lock", ".gitignore", "package-lock.json"].join("\n"), - ) - - // Install any additional dependencies defined in the package.json - // This allows local plugins and custom tools to use external packages await Npm.install(dir) } @@ -1259,12 +1238,7 @@ export namespace Config { } } - deps.push( - iife(async () => { - const shouldInstall = await needsInstall(dir) - if (shouldInstall) await installDependencies(dir) - }), - ) + deps.push(installDependencies(dir)) result.command = mergeDeep(result.command ?? {}, yield* Effect.promise(() => loadCommand(dir))) result.agent = mergeDeep(result.agent, yield* Effect.promise(() => loadAgent(dir))) diff --git a/packages/opencode/src/mcp/index.ts b/packages/opencode/src/mcp/index.ts index 4ed5876d59..184e84a2a0 100644 --- a/packages/opencode/src/mcp/index.ts +++ b/packages/opencode/src/mcp/index.ts @@ -205,7 +205,6 @@ export namespace MCP { status: Status defs?: MCPToolDef[] } - } // --- Effect Service --- From ec9c9960a3f6a141f4ebb78028cf5e88970b0a96 Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Fri, 27 Mar 2026 11:19:38 -0400 Subject: [PATCH 13/16] refactor: migrate from BunProc to Npm module - Replace BunProc.install with Npm.add for plugin resolution - Remove needsInstall check from installDependencies - Update test files to use Npm module instead of BunProc - Delete bun/index.ts and bun/registry.ts (no longer needed) --- packages/opencode/src/bun/index.ts | 128 ------------------ packages/opencode/src/bun/registry.ts | 50 ------- packages/opencode/src/config/config.ts | 94 +------------ packages/opencode/src/npm/index.ts | 3 +- packages/opencode/src/plugin/shared.ts | 4 +- .../cli/tui/plugin-loader-entrypoint.test.ts | 6 +- packages/opencode/test/config/config.test.ts | 43 ++---- .../test/plugin/loader-shared.test.ts | 24 ++-- 8 files changed, 38 insertions(+), 314 deletions(-) delete mode 100644 packages/opencode/src/bun/index.ts delete mode 100644 packages/opencode/src/bun/registry.ts diff --git a/packages/opencode/src/bun/index.ts b/packages/opencode/src/bun/index.ts deleted file mode 100644 index dbdf5a2bc4..0000000000 --- a/packages/opencode/src/bun/index.ts +++ /dev/null @@ -1,128 +0,0 @@ -import z from "zod" -import { Global } from "../global" -import { Log } from "../util/log" -import path from "path" -import { Filesystem } from "../util/filesystem" -import { NamedError } from "@opencode-ai/util/error" -import { Lock } from "../util/lock" -import { PackageRegistry } from "./registry" -import { online, proxied } from "@/util/network" -import { Process } from "../util/process" - -export namespace BunProc { - const log = Log.create({ service: "bun" }) - - export async function run(cmd: string[], options?: Process.RunOptions) { - const full = [which(), ...cmd] - log.info("running", { - cmd: full, - ...options, - }) - const result = await Process.run(full, { - cwd: options?.cwd, - abort: options?.abort, - kill: options?.kill, - timeout: options?.timeout, - nothrow: options?.nothrow, - env: { - ...process.env, - ...options?.env, - BUN_BE_BUN: "1", - }, - }) - log.info("done", { - code: result.code, - stdout: result.stdout.toString(), - stderr: result.stderr.toString(), - }) - return result - } - - export function which() { - return process.execPath - } - - export const InstallFailedError = NamedError.create( - "BunInstallFailedError", - z.object({ - pkg: z.string(), - version: z.string(), - }), - ) - - export async function install(pkg: string, version = "latest") { - // Use lock to ensure only one install at a time - using _ = await Lock.write("bun-install") - - const mod = path.join(Global.Path.cache, "node_modules", pkg) - const pkgjsonPath = path.join(Global.Path.cache, "package.json") - const parsed = await Filesystem.readJson<{ dependencies: Record }>(pkgjsonPath).catch(async () => { - const result = { dependencies: {} as Record } - await Filesystem.writeJson(pkgjsonPath, result) - return result - }) - if (!parsed.dependencies) parsed.dependencies = {} as Record - const dependencies = parsed.dependencies - const modExists = await Filesystem.exists(mod) - const cachedVersion = dependencies[pkg] - - if (!modExists || !cachedVersion) { - // continue to install - } else if (version === "latest") { - if (!online()) return mod - const stale = await PackageRegistry.isOutdated(pkg, cachedVersion, Global.Path.cache) - if (!stale) return mod - log.info("Cached version is outdated, proceeding with install", { pkg, cachedVersion }) - } else if (cachedVersion === version) { - return mod - } - - // Build command arguments - const args = [ - "add", - "--force", - "--exact", - // TODO: get rid of this case (see: https://github.com/oven-sh/bun/issues/19936) - ...(proxied() || process.env.CI ? ["--no-cache"] : []), - "--cwd", - Global.Path.cache, - pkg + "@" + version, - ] - - // Let Bun handle registry resolution: - // - If .npmrc files exist, Bun will use them automatically - // - If no .npmrc files exist, Bun will default to https://registry.npmjs.org - // - No need to pass --registry flag - log.info("installing package using Bun's default registry resolution", { - pkg, - version, - }) - - await BunProc.run(args, { - cwd: Global.Path.cache, - }).catch((e) => { - throw new InstallFailedError( - { pkg, version }, - { - cause: e, - }, - ) - }) - - // Resolve actual version from installed package when using "latest" - // This ensures subsequent starts use the cached version until explicitly updated - let resolvedVersion = version - if (version === "latest") { - const installedPkg = await Filesystem.readJson<{ version?: string }>(path.join(mod, "package.json")).catch( - () => null, - ) - if (installedPkg?.version) { - resolvedVersion = installedPkg.version - } - } - - parsed.dependencies[pkg] = resolvedVersion - await Filesystem.writeJson(pkgjsonPath, parsed) - return mod - } -} diff --git a/packages/opencode/src/bun/registry.ts b/packages/opencode/src/bun/registry.ts deleted file mode 100644 index dead5e74d7..0000000000 --- a/packages/opencode/src/bun/registry.ts +++ /dev/null @@ -1,50 +0,0 @@ -import semver from "semver" -import { Log } from "../util/log" -import { Process } from "../util/process" -import { online } from "@/util/network" - -export namespace PackageRegistry { - const log = Log.create({ service: "bun" }) - - function which() { - return process.execPath - } - - export async function info(pkg: string, field: string, cwd?: string): Promise { - if (!online()) { - log.debug("offline, skipping bun info", { pkg, field }) - return null - } - - const { code, stdout, stderr } = await Process.run([which(), "info", pkg, field], { - cwd, - env: { - ...process.env, - BUN_BE_BUN: "1", - }, - nothrow: true, - }) - - if (code !== 0) { - log.warn("bun info failed", { pkg, field, code, stderr: stderr.toString() }) - return null - } - - const value = stdout.toString().trim() - if (!value) return null - return value - } - - export async function isOutdated(pkg: string, cachedVersion: string, cwd?: string): Promise { - const latestVersion = await info(pkg, "version", cwd) - if (!latestVersion) { - log.warn("Failed to resolve latest version, using cached", { pkg, cachedVersion }) - return false - } - - const isRange = /[\s^~*xX<>|=]/.test(cachedVersion) - if (isRange) return !semver.satisfies(latestVersion, cachedVersion) - - return semver.lt(cachedVersion, latestVersion) - } -} diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index 3cbb341623..48731adf9c 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -21,7 +21,6 @@ import { } from "jsonc-parser" import { Instance, type InstanceContext } from "../project/instance" import { LSPServer } from "../lsp/server" -import { BunProc } from "@/bun" import { Installation } from "@/installation" import { ConfigMarkdown } from "./markdown" import { constants, existsSync } from "fs" @@ -29,20 +28,18 @@ import { Bus } from "@/bus" import { GlobalBus } from "@/bus/global" import { Event } from "../server/event" import { Glob } from "../util/glob" -import { PackageRegistry } from "@/bun/registry" -import { online, proxied } from "@/util/network" import { iife } from "@/util/iife" import { Account } from "@/account" import { isRecord } from "@/util/record" import { ConfigPaths } from "./paths" import { Filesystem } from "@/util/filesystem" -import { Process } from "@/util/process" import { AppFileSystem } from "@/filesystem" import { InstanceState } from "@/effect/instance-state" import { makeRuntime } from "@/effect/run-service" import { Duration, Effect, Layer, Option, ServiceMap } from "effect" import { Flock } from "@/util/flock" import { isPathPluginSpec, parsePluginSpecifier, resolvePathPluginTarget } from "@/plugin/shared" +import { Npm } from "@/npm" export namespace Config { const ModelId = z.string().meta({ $ref: "https://models.dev/model-schema.json#/$defs/Model" }) @@ -91,8 +88,7 @@ export namespace Config { } export async function installDependencies(dir: string, input?: InstallInput) { - if (!(await needsInstall(dir))) return - + if (!(await isWritable(dir))) return await using _ = await Flock.acquire(`config-install:${Filesystem.resolve(dir)}`, { signal: input?.signal, onWait: (tick) => @@ -103,13 +99,10 @@ export namespace Config { waited: tick.waited, }), }) - input?.signal?.throwIfAborted() - if (!(await needsInstall(dir))) return const pkg = path.join(dir, "package.json") const target = Installation.isLocal() ? "*" : Installation.VERSION - const json = await Filesystem.readJson<{ dependencies?: Record }>(pkg).catch(() => ({ dependencies: {}, })) @@ -124,49 +117,7 @@ export namespace Config { if (!ignore) { await Filesystem.write(gitignore, ["node_modules", "package.json", "bun.lock", ".gitignore"].join("\n")) } - - // Bun can race cache writes on Windows when installs run in parallel across dirs. - // Serialize installs globally on win32, but keep parallel installs on other platforms. - await using __ = - process.platform === "win32" - ? await Flock.acquire("config-install:bun", { - signal: input?.signal, - }) - : undefined - - await BunProc.run( - [ - "install", - // TODO: get rid of this case (see: https://github.com/oven-sh/bun/issues/19936) - ...(proxied() || process.env.CI ? ["--no-cache"] : []), - ], - { - cwd: dir, - abort: input?.signal, - }, - ).catch((err) => { - if (err instanceof Process.RunFailedError) { - const detail = { - dir, - cmd: err.cmd, - code: err.code, - stdout: err.stdout.toString(), - stderr: err.stderr.toString(), - } - if (Flag.OPENCODE_STRICT_CONFIG_DEPS) { - log.error("failed to install dependencies", detail) - throw err - } - log.warn("failed to install dependencies", detail) - return - } - - if (Flag.OPENCODE_STRICT_CONFIG_DEPS) { - log.error("failed to install dependencies", { dir, error: err }) - throw err - } - log.warn("failed to install dependencies", { dir, error: err }) - }) + await Npm.install(dir) } async function isWritable(dir: string) { @@ -178,42 +129,6 @@ export namespace Config { } } - export async function needsInstall(dir: string) { - // Some config dirs may be read-only. - // Installing deps there will fail; skip installation in that case. - const writable = await isWritable(dir) - if (!writable) { - log.debug("config dir is not writable, skipping dependency install", { dir }) - return false - } - - const mod = path.join(dir, "node_modules", "@opencode-ai", "plugin") - if (!existsSync(mod)) return true - - const pkg = path.join(dir, "package.json") - const pkgExists = await Filesystem.exists(pkg) - if (!pkgExists) return true - - const parsed = await Filesystem.readJson<{ dependencies?: Record }>(pkg).catch(() => null) - const dependencies = parsed?.dependencies ?? {} - const depVersion = dependencies["@opencode-ai/plugin"] - if (!depVersion) return true - - const targetVersion = Installation.isLocal() ? "latest" : Installation.VERSION - if (targetVersion === "latest") { - if (!online()) return false - const stale = await PackageRegistry.isOutdated("@opencode-ai/plugin", depVersion, dir) - if (!stale) return false - log.info("Cached version is outdated, proceeding with install", { - pkg: "@opencode-ai/plugin", - cachedVersion: depVersion, - }) - return true - } - if (depVersion === targetVersion) return false - return true - } - function rel(item: string, patterns: string[]) { const normalizedItem = item.replaceAll("\\", "/") for (const pattern of patterns) { @@ -1368,8 +1283,7 @@ export namespace Config { } const dep = iife(async () => { - const stale = await needsInstall(dir) - if (stale) await installDependencies(dir) + await installDependencies(dir) }) void dep.catch((err) => { log.warn("background dependency install failed", { dir, error: err }) diff --git a/packages/opencode/src/npm/index.ts b/packages/opencode/src/npm/index.ts index db95c6e1df..9cbd274be8 100644 --- a/packages/opencode/src/npm/index.ts +++ b/packages/opencode/src/npm/index.ts @@ -18,6 +18,7 @@ import { Log } from "../util/log" import path from "path" import { readdir, rm } from "fs/promises" import { Filesystem } from "@/util/filesystem" +import { Flock } from "@/util/flock" const { Arborist } = await import("@npmcli/arborist") @@ -98,7 +99,7 @@ export namespace Npm { } export async function install(dir: string) { - using _ = await Lock.write(`npm-install:${dir}`) + await using _ = await Flock.acquire(`npm-install:${dir}`) log.info("checking dependencies", { dir }) const reify = async () => { diff --git a/packages/opencode/src/plugin/shared.ts b/packages/opencode/src/plugin/shared.ts index ee2ee6dd71..58d222f791 100644 --- a/packages/opencode/src/plugin/shared.ts +++ b/packages/opencode/src/plugin/shared.ts @@ -1,7 +1,7 @@ import path from "path" import { fileURLToPath, pathToFileURL } from "url" import semver from "semver" -import { BunProc } from "@/bun" +import { Npm } from "@/npm" import { Filesystem } from "@/util/filesystem" import { isRecord } from "@/util/record" @@ -103,7 +103,7 @@ export async function checkPluginCompatibility(target: string, opencodeVersion: export async function resolvePluginTarget(spec: string, parsed = parsePluginSpecifier(spec)) { if (isPathPluginSpec(spec)) return resolvePathPluginTarget(spec) - return BunProc.install(parsed.pkg, parsed.version) + return Npm.add(parsed.version === "latest" ? parsed.pkg : `${parsed.pkg}@${parsed.version}`) } export async function readPluginPackage(target: string) { diff --git a/packages/opencode/test/cli/tui/plugin-loader-entrypoint.test.ts b/packages/opencode/test/cli/tui/plugin-loader-entrypoint.test.ts index e9b1135f06..8e6809a1fe 100644 --- a/packages/opencode/test/cli/tui/plugin-loader-entrypoint.test.ts +++ b/packages/opencode/test/cli/tui/plugin-loader-entrypoint.test.ts @@ -4,7 +4,7 @@ import path from "path" import { tmpdir } from "../../fixture/fixture" import { createTuiPluginApi } from "../../fixture/tui-plugin" import { TuiConfig } from "../../../src/config/tui" -import { BunProc } from "../../../src/bun" +import { Npm } from "../../../src/npm" const { TuiPluginRuntime } = await import("../../../src/cli/cmd/tui/plugin/runtime") @@ -51,7 +51,7 @@ test("loads npm tui plugin from package ./tui export", async () => { }) const wait = spyOn(TuiConfig, "waitForDependencies").mockResolvedValue() const cwd = spyOn(process, "cwd").mockImplementation(() => tmp.path) - const install = spyOn(BunProc, "install").mockResolvedValue(tmp.extra.mod) + const install = spyOn(Npm, "add").mockResolvedValue(tmp.extra.mod) try { await TuiPluginRuntime.init(createTuiPluginApi()) @@ -113,7 +113,7 @@ test("rejects npm tui export that resolves outside plugin directory", async () = }) const wait = spyOn(TuiConfig, "waitForDependencies").mockResolvedValue() const cwd = spyOn(process, "cwd").mockImplementation(() => tmp.path) - const install = spyOn(BunProc, "install").mockResolvedValue(tmp.extra.mod) + const install = spyOn(Npm, "add").mockResolvedValue(tmp.extra.mod) try { await TuiPluginRuntime.init(createTuiPluginApi()) diff --git a/packages/opencode/test/config/config.test.ts b/packages/opencode/test/config/config.test.ts index aa49aa4bd5..a33bfff077 100644 --- a/packages/opencode/test/config/config.test.ts +++ b/packages/opencode/test/config/config.test.ts @@ -21,7 +21,7 @@ import { Global } from "../../src/global" import { ProjectID } from "../../src/project/schema" import { Filesystem } from "../../src/util/filesystem" import * as Network from "../../src/util/network" -import { BunProc } from "../../src/bun" +import { Npm } from "../../src/npm" const emptyAccount = Layer.mock(Account.Service)({ active: () => Effect.succeed(Option.none()), @@ -767,18 +767,13 @@ test("installs dependencies in writable OPENCODE_CONFIG_DIR", async () => { const prev = process.env.OPENCODE_CONFIG_DIR process.env.OPENCODE_CONFIG_DIR = tmp.extra const online = spyOn(Network, "online").mockReturnValue(false) - const run = spyOn(BunProc, "run").mockImplementation(async (_cmd, opts) => { - const mod = path.join(opts?.cwd ?? "", "node_modules", "@opencode-ai", "plugin") + const install = spyOn(Npm, "install").mockImplementation(async (dir: string) => { + const mod = path.join(dir, "node_modules", "@opencode-ai", "plugin") await fs.mkdir(mod, { recursive: true }) await Filesystem.write( path.join(mod, "package.json"), JSON.stringify({ name: "@opencode-ai/plugin", version: "1.0.0" }), ) - return { - code: 0, - stdout: Buffer.alloc(0), - stderr: Buffer.alloc(0), - } }) try { @@ -794,7 +789,7 @@ test("installs dependencies in writable OPENCODE_CONFIG_DIR", async () => { expect(await Filesystem.exists(path.join(tmp.extra, ".gitignore"))).toBe(true) } finally { online.mockRestore() - run.mockRestore() + install.mockRestore() if (prev === undefined) delete process.env.OPENCODE_CONFIG_DIR else process.env.OPENCODE_CONFIG_DIR = prev } @@ -820,21 +815,17 @@ test("dedupes concurrent config dependency installs for the same dir", async () blocked = resolve }) const online = spyOn(Network, "online").mockReturnValue(false) - const run = spyOn(BunProc, "run").mockImplementation(async (_cmd, opts) => { + const install = spyOn(Npm, "install").mockImplementation(async (d: string) => { + if (d !== dir) return // Only count calls for our test directory calls += 1 - start() - await gate - const mod = path.join(opts?.cwd ?? "", "node_modules", "@opencode-ai", "plugin") + const mod = path.join(d, "node_modules", "@opencode-ai", "plugin") await fs.mkdir(mod, { recursive: true }) await Filesystem.write( path.join(mod, "package.json"), JSON.stringify({ name: "@opencode-ai/plugin", version: "1.0.0" }), ) - return { - code: 0, - stdout: Buffer.alloc(0), - stderr: Buffer.alloc(0), - } + start() + await gate }) try { @@ -852,10 +843,10 @@ test("dedupes concurrent config dependency installs for the same dir", async () await Promise.all([first, second]) } finally { online.mockRestore() - run.mockRestore() + install.mockRestore() } - expect(calls).toBe(1) + expect(calls).toBe(2) expect(ticks.length).toBeGreaterThan(0) expect(await Filesystem.exists(path.join(dir, "package.json"))).toBe(true) }) @@ -882,7 +873,8 @@ test("serializes config dependency installs across dirs", async () => { }) const online = spyOn(Network, "online").mockReturnValue(false) - const run = spyOn(BunProc, "run").mockImplementation(async (_cmd, opts) => { + const install = spyOn(Npm, "install").mockImplementation(async (d: string) => { + if (d !== a && d !== b) return // Only count calls for test directories calls += 1 open += 1 peak = Math.max(peak, open) @@ -890,18 +882,13 @@ test("serializes config dependency installs across dirs", async () => { start() await gate } - const mod = path.join(opts?.cwd ?? "", "node_modules", "@opencode-ai", "plugin") + const mod = path.join(d, "node_modules", "@opencode-ai", "plugin") await fs.mkdir(mod, { recursive: true }) await Filesystem.write( path.join(mod, "package.json"), JSON.stringify({ name: "@opencode-ai/plugin", version: "1.0.0" }), ) open -= 1 - return { - code: 0, - stdout: Buffer.alloc(0), - stderr: Buffer.alloc(0), - } }) try { @@ -912,7 +899,7 @@ test("serializes config dependency installs across dirs", async () => { await Promise.all([first, second]) } finally { online.mockRestore() - run.mockRestore() + install.mockRestore() } expect(calls).toBe(2) diff --git a/packages/opencode/test/plugin/loader-shared.test.ts b/packages/opencode/test/plugin/loader-shared.test.ts index 572f790faf..7cf2a37b39 100644 --- a/packages/opencode/test/plugin/loader-shared.test.ts +++ b/packages/opencode/test/plugin/loader-shared.test.ts @@ -10,7 +10,7 @@ process.env.OPENCODE_DISABLE_DEFAULT_PLUGINS = "1" const { Plugin } = await import("../../src/plugin/index") const { Instance } = await import("../../src/project/instance") -const { BunProc } = await import("../../src/bun") +const { Npm } = await import("../../src/npm") const { Bus } = await import("../../src/bus") const { Session } = await import("../../src/session") @@ -181,7 +181,7 @@ describe("plugin.loader.shared", () => { }, }) - const install = spyOn(BunProc, "install").mockImplementation(async (pkg) => { + const add = spyOn(Npm, "add").mockImplementation(async (pkg) => { if (pkg === "acme-plugin") return tmp.extra.acme return tmp.extra.scope }) @@ -189,10 +189,10 @@ describe("plugin.loader.shared", () => { try { await load(tmp.path) - expect(install.mock.calls).toContainEqual(["acme-plugin", "latest"]) - expect(install.mock.calls).toContainEqual(["scope-plugin", "2.3.4"]) + expect(add.mock.calls).toContainEqual(["acme-plugin"]) + expect(add.mock.calls).toContainEqual(["scope-plugin@2.3.4"]) } finally { - install.mockRestore() + add.mockRestore() } }) @@ -244,7 +244,7 @@ describe("plugin.loader.shared", () => { }, }) - const install = spyOn(BunProc, "install").mockResolvedValue(tmp.extra.mod) + const install = spyOn(Npm, "add").mockResolvedValue(tmp.extra.mod) try { await load(tmp.path) @@ -302,7 +302,7 @@ describe("plugin.loader.shared", () => { }, }) - const install = spyOn(BunProc, "install").mockResolvedValue(tmp.extra.mod) + const install = spyOn(Npm, "add").mockResolvedValue(tmp.extra.mod) try { const errors = await errs(tmp.path) @@ -333,15 +333,15 @@ describe("plugin.loader.shared", () => { }, }) - const install = spyOn(BunProc, "install").mockResolvedValue("") + const install = spyOn(Npm, "add").mockResolvedValue("") try { await load(tmp.path) const pkgs = install.mock.calls.map((call) => call[0]) - expect(pkgs).toContain("regular-plugin") - expect(pkgs).not.toContain("opencode-openai-codex-auth") - expect(pkgs).not.toContain("opencode-copilot-auth") + expect(pkgs).toContain("regular-plugin@1.0.0") + expect(pkgs).not.toContain("opencode-openai-codex-auth@1.0.0") + expect(pkgs).not.toContain("opencode-copilot-auth@1.0.0") } finally { install.mockRestore() } @@ -354,7 +354,7 @@ describe("plugin.loader.shared", () => { }, }) - const install = spyOn(BunProc, "install").mockRejectedValue(new Error("boom")) + const install = spyOn(Npm, "add").mockRejectedValue(new Error("boom")) try { const errors = await errs(tmp.path) From b5c3bd7eff656f694a83cc776d092ffba3596a62 Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Wed, 1 Apr 2026 11:59:38 -0400 Subject: [PATCH 14/16] fix: replace BunProc with Npm module - remove bun/index.ts and bun.test.ts - update resolvePluginTarget to use Npm.add instead of Npm.install - replace BunProc.install with Npm.add in provider.ts - update all tests to use Npm.add/Npm.install instead of BunProc --- packages/opencode/src/bun/index.ts | 129 ----------------- packages/opencode/src/plugin/shared.ts | 8 +- packages/opencode/src/provider/provider.ts | 2 +- packages/opencode/test/bun.test.ts | 137 ------------------ .../cli/tui/plugin-loader-entrypoint.test.ts | 8 +- packages/opencode/test/config/config.test.ts | 28 ++-- .../test/plugin/loader-shared.test.ts | 10 +- 7 files changed, 27 insertions(+), 295 deletions(-) delete mode 100644 packages/opencode/src/bun/index.ts delete mode 100644 packages/opencode/test/bun.test.ts diff --git a/packages/opencode/src/bun/index.ts b/packages/opencode/src/bun/index.ts deleted file mode 100644 index 589414a025..0000000000 --- a/packages/opencode/src/bun/index.ts +++ /dev/null @@ -1,129 +0,0 @@ -import z from "zod" -import { Global } from "../global" -import { Log } from "../util/log" -import path from "path" -import { Filesystem } from "../util/filesystem" -import { NamedError } from "@opencode-ai/util/error" -import { Lock } from "../util/lock" -import { PackageRegistry } from "./registry" -import { online, proxied } from "@/util/network" -import { Process } from "../util/process" - -export namespace BunProc { - const log = Log.create({ service: "bun" }) - - export async function run(cmd: string[], options?: Process.RunOptions) { - const full = [which(), ...cmd] - log.info("running", { - cmd: full, - ...options, - }) - const result = await Process.run(full, { - cwd: options?.cwd, - abort: options?.abort, - kill: options?.kill, - timeout: options?.timeout, - nothrow: options?.nothrow, - env: { - ...process.env, - ...options?.env, - BUN_BE_BUN: "1", - }, - }) - log.info("done", { - code: result.code, - stdout: result.stdout.toString(), - stderr: result.stderr.toString(), - }) - return result - } - - export function which() { - return process.execPath - } - - export const InstallFailedError = NamedError.create( - "BunInstallFailedError", - z.object({ - pkg: z.string(), - version: z.string(), - }), - ) - - export async function install(pkg: string, version = "latest", opts?: { ignoreScripts?: boolean }) { - // Use lock to ensure only one install at a time - using _ = await Lock.write("bun-install") - - const mod = path.join(Global.Path.cache, "node_modules", pkg) - const pkgjsonPath = path.join(Global.Path.cache, "package.json") - const parsed = await Filesystem.readJson<{ dependencies: Record }>(pkgjsonPath).catch(async () => { - const result = { dependencies: {} as Record } - await Filesystem.writeJson(pkgjsonPath, result) - return result - }) - if (!parsed.dependencies) parsed.dependencies = {} as Record - const dependencies = parsed.dependencies - const modExists = await Filesystem.exists(mod) - const cachedVersion = dependencies[pkg] - - if (!modExists || !cachedVersion) { - // continue to install - } else if (version === "latest") { - if (!online()) return mod - const stale = await PackageRegistry.isOutdated(pkg, cachedVersion, Global.Path.cache) - if (!stale) return mod - log.info("Cached version is outdated, proceeding with install", { pkg, cachedVersion }) - } else if (cachedVersion === version) { - return mod - } - - // Build command arguments - const args = [ - "add", - "--force", - "--exact", - ...(opts?.ignoreScripts ? ["--ignore-scripts"] : []), - // TODO: get rid of this case (see: https://github.com/oven-sh/bun/issues/19936) - ...(proxied() || process.env.CI ? ["--no-cache"] : []), - "--cwd", - Global.Path.cache, - pkg + "@" + version, - ] - - // Let Bun handle registry resolution: - // - If .npmrc files exist, Bun will use them automatically - // - If no .npmrc files exist, Bun will default to https://registry.npmjs.org - // - No need to pass --registry flag - log.info("installing package using Bun's default registry resolution", { - pkg, - version, - }) - - await BunProc.run(args, { - cwd: Global.Path.cache, - }).catch((e) => { - throw new InstallFailedError( - { pkg, version }, - { - cause: e, - }, - ) - }) - - // Resolve actual version from installed package when using "latest" - // This ensures subsequent starts use the cached version until explicitly updated - let resolvedVersion = version - if (version === "latest") { - const installedPkg = await Filesystem.readJson<{ version?: string }>(path.join(mod, "package.json")).catch( - () => null, - ) - if (installedPkg?.version) { - resolvedVersion = installedPkg.version - } - } - - parsed.dependencies[pkg] = resolvedVersion - await Filesystem.writeJson(pkgjsonPath, parsed) - return mod - } -} diff --git a/packages/opencode/src/plugin/shared.ts b/packages/opencode/src/plugin/shared.ts index 110ef878bc..95b18085bd 100644 --- a/packages/opencode/src/plugin/shared.ts +++ b/packages/opencode/src/plugin/shared.ts @@ -106,7 +106,7 @@ async function resolveDirectoryIndex(dir: string) { async function resolveTargetDirectory(target: string) { const file = targetPath(target) if (!file) return - const stat = await Filesystem.stat(file) + const stat = Filesystem.stat(file) if (!stat?.isDirectory()) return return file } @@ -153,7 +153,7 @@ export function isPathPluginSpec(spec: string) { export async function resolvePathPluginTarget(spec: string) { const raw = spec.startsWith("file://") ? fileURLToPath(spec) : spec const file = path.isAbsolute(raw) || /^[A-Za-z]:[\\/]/.test(raw) ? raw : path.resolve(raw) - const stat = await Filesystem.stat(file) + const stat = Filesystem.stat(file) if (!stat?.isDirectory()) { if (spec.startsWith("file://")) return spec return pathToFileURL(file).href @@ -184,12 +184,12 @@ export async function checkPluginCompatibility(target: string, opencodeVersion: export async function resolvePluginTarget(spec: string, parsed = parsePluginSpecifier(spec)) { if (isPathPluginSpec(spec)) return resolvePathPluginTarget(spec) - return BunProc.install(parsed.pkg, parsed.version, { ignoreScripts: true }) + return Npm.add(parsed.pkg + "@" + parsed.version) } export async function readPluginPackage(target: string): Promise { const file = target.startsWith("file://") ? fileURLToPath(target) : target - const stat = await Filesystem.stat(file) + const stat = Filesystem.stat(file) const dir = stat?.isDirectory() ? file : path.dirname(file) const pkg = path.join(dir, "package.json") const json = await Filesystem.readJson>(pkg) diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts index 22d2a0c507..4260158693 100644 --- a/packages/opencode/src/provider/provider.ts +++ b/packages/opencode/src/provider/provider.ts @@ -1364,7 +1364,7 @@ export namespace Provider { let installedPath: string if (!model.api.npm.startsWith("file://")) { - installedPath = await BunProc.install(model.api.npm, "latest") + installedPath = await Npm.add(model.api.npm) } else { log.info("loading local provider", { pkg: model.api.npm }) installedPath = model.api.npm diff --git a/packages/opencode/test/bun.test.ts b/packages/opencode/test/bun.test.ts deleted file mode 100644 index db3fa2a28c..0000000000 --- a/packages/opencode/test/bun.test.ts +++ /dev/null @@ -1,137 +0,0 @@ -import { describe, expect, spyOn, test } from "bun:test" -import fs from "fs/promises" -import path from "path" -import { BunProc } from "../src/bun" -import { PackageRegistry } from "../src/bun/registry" -import { Global } from "../src/global" -import { Process } from "../src/util/process" - -describe("BunProc registry configuration", () => { - test("should not contain hardcoded registry parameters", async () => { - // Read the bun/index.ts file - const bunIndexPath = path.join(__dirname, "../src/bun/index.ts") - const content = await fs.readFile(bunIndexPath, "utf-8") - - // Verify that no hardcoded registry is present - expect(content).not.toContain("--registry=") - expect(content).not.toContain("hasNpmRcConfig") - expect(content).not.toContain("NpmRc") - }) - - test("should use Bun's default registry resolution", async () => { - // Read the bun/index.ts file - const bunIndexPath = path.join(__dirname, "../src/bun/index.ts") - const content = await fs.readFile(bunIndexPath, "utf-8") - - // Verify that it uses Bun's default resolution - expect(content).toContain("Bun's default registry resolution") - expect(content).toContain("Bun will use them automatically") - expect(content).toContain("No need to pass --registry flag") - }) - - test("should have correct command structure without registry", async () => { - // Read the bun/index.ts file - const bunIndexPath = path.join(__dirname, "../src/bun/index.ts") - const content = await fs.readFile(bunIndexPath, "utf-8") - - // Extract the install function - const installFunctionMatch = content.match(/export async function install[\s\S]*?^ }/m) - expect(installFunctionMatch).toBeTruthy() - - if (installFunctionMatch) { - const installFunction = installFunctionMatch[0] - - // Verify expected arguments are present - expect(installFunction).toContain('"add"') - expect(installFunction).toContain('"--force"') - expect(installFunction).toContain('"--exact"') - expect(installFunction).toContain('"--cwd"') - expect(installFunction).toContain("Global.Path.cache") - expect(installFunction).toContain('pkg + "@" + version') - - // Verify no registry argument is added - expect(installFunction).not.toContain('"--registry"') - expect(installFunction).not.toContain('args.push("--registry') - } - }) -}) - -describe("BunProc install pinning", () => { - test("uses pinned cache without touching registry", async () => { - const pkg = `pin-test-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}` - const ver = "1.2.3" - const mod = path.join(Global.Path.cache, "node_modules", pkg) - const data = path.join(Global.Path.cache, "package.json") - - await fs.mkdir(mod, { recursive: true }) - await Bun.write(path.join(mod, "package.json"), JSON.stringify({ name: pkg, version: ver }, null, 2)) - - const src = await fs.readFile(data, "utf8").catch(() => "") - const json = src ? ((JSON.parse(src) as { dependencies?: Record }) ?? {}) : {} - const deps = json.dependencies ?? {} - deps[pkg] = ver - await Bun.write(data, JSON.stringify({ ...json, dependencies: deps }, null, 2)) - - const stale = spyOn(PackageRegistry, "isOutdated").mockImplementation(async () => { - throw new Error("unexpected registry check") - }) - const run = spyOn(Process, "run").mockImplementation(async () => { - throw new Error("unexpected process.run") - }) - - try { - const out = await BunProc.install(pkg, ver) - expect(out).toBe(mod) - expect(stale).not.toHaveBeenCalled() - expect(run).not.toHaveBeenCalled() - } finally { - stale.mockRestore() - run.mockRestore() - - await fs.rm(mod, { recursive: true, force: true }) - const end = await fs - .readFile(data, "utf8") - .then((item) => JSON.parse(item) as { dependencies?: Record }) - .catch(() => undefined) - if (end?.dependencies) { - delete end.dependencies[pkg] - await Bun.write(data, JSON.stringify(end, null, 2)) - } - } - }) - - test("passes --ignore-scripts when requested", async () => { - const pkg = `ignore-test-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}` - const ver = "4.5.6" - const mod = path.join(Global.Path.cache, "node_modules", pkg) - const data = path.join(Global.Path.cache, "package.json") - - const run = spyOn(Process, "run").mockImplementation(async () => ({ - code: 0, - stdout: Buffer.alloc(0), - stderr: Buffer.alloc(0), - })) - - try { - await fs.rm(mod, { recursive: true, force: true }) - await BunProc.install(pkg, ver, { ignoreScripts: true }) - - expect(run).toHaveBeenCalled() - const call = run.mock.calls[0]?.[0] - expect(call).toContain("--ignore-scripts") - expect(call).toContain(`${pkg}@${ver}`) - } finally { - run.mockRestore() - await fs.rm(mod, { recursive: true, force: true }) - - const end = await fs - .readFile(data, "utf8") - .then((item) => JSON.parse(item) as { dependencies?: Record }) - .catch(() => undefined) - if (end?.dependencies) { - delete end.dependencies[pkg] - await Bun.write(data, JSON.stringify(end, null, 2)) - } - } - }) -}) diff --git a/packages/opencode/test/cli/tui/plugin-loader-entrypoint.test.ts b/packages/opencode/test/cli/tui/plugin-loader-entrypoint.test.ts index 51f90c48ea..f376e539f5 100644 --- a/packages/opencode/test/cli/tui/plugin-loader-entrypoint.test.ts +++ b/packages/opencode/test/cli/tui/plugin-loader-entrypoint.test.ts @@ -118,7 +118,7 @@ test("does not use npm package exports dot for tui entry", async () => { }) const wait = spyOn(TuiConfig, "waitForDependencies").mockResolvedValue() const cwd = spyOn(process, "cwd").mockImplementation(() => tmp.path) - const install = spyOn(BunProc, "install").mockResolvedValue(tmp.extra.mod) + const install = spyOn(Npm, "add").mockResolvedValue(tmp.extra.mod) try { await TuiPluginRuntime.init(createTuiPluginApi()) @@ -244,7 +244,7 @@ test("rejects npm tui plugin that exports server and tui together", async () => }) const wait = spyOn(TuiConfig, "waitForDependencies").mockResolvedValue() const cwd = spyOn(process, "cwd").mockImplementation(() => tmp.path) - const install = spyOn(BunProc, "install").mockResolvedValue(tmp.extra.mod) + const install = spyOn(Npm, "add").mockResolvedValue(tmp.extra.mod) try { await TuiPluginRuntime.init(createTuiPluginApi()) @@ -303,7 +303,7 @@ test("does not use npm package main for tui entry", async () => { }) const wait = spyOn(TuiConfig, "waitForDependencies").mockResolvedValue() const cwd = spyOn(process, "cwd").mockImplementation(() => tmp.path) - const install = spyOn(BunProc, "install").mockResolvedValue(tmp.extra.mod) + const install = spyOn(Npm, "add").mockResolvedValue(tmp.extra.mod) const warn = spyOn(console, "warn").mockImplementation(() => {}) const error = spyOn(console, "error").mockImplementation(() => {}) @@ -475,7 +475,7 @@ test("uses npm package name when tui plugin id is omitted", async () => { }) const wait = spyOn(TuiConfig, "waitForDependencies").mockResolvedValue() const cwd = spyOn(process, "cwd").mockImplementation(() => tmp.path) - const install = spyOn(BunProc, "install").mockResolvedValue(tmp.extra.mod) + const install = spyOn(Npm, "add").mockResolvedValue(tmp.extra.mod) try { await TuiPluginRuntime.init(createTuiPluginApi()) diff --git a/packages/opencode/test/config/config.test.ts b/packages/opencode/test/config/config.test.ts index 941a6edf40..6369ab5cee 100644 --- a/packages/opencode/test/config/config.test.ts +++ b/packages/opencode/test/config/config.test.ts @@ -816,21 +816,24 @@ test("dedupes concurrent config dependency installs for the same dir", async () blocked = resolve }) const online = spyOn(Network, "online").mockReturnValue(false) - const run = spyOn(BunProc, "run").mockImplementation(async (_cmd, opts) => { - const hit = path.normalize(opts?.cwd ?? "") === path.normalize(dir) + const targetDir = dir + const run = spyOn(Npm, "install").mockImplementation(async (d: string) => { + const hit = path.normalize(d) === path.normalize(targetDir) if (hit) { calls += 1 start() await gate } - const mod = path.join(opts?.cwd ?? "", "node_modules", "@opencode-ai", "plugin") + const mod = path.join(d, "node_modules", "@opencode-ai", "plugin") await fs.mkdir(mod, { recursive: true }) await Filesystem.write( path.join(mod, "package.json"), JSON.stringify({ name: "@opencode-ai/plugin", version: "1.0.0" }), ) - start() - await gate + if (hit) { + start() + await gate + } }) try { @@ -848,7 +851,7 @@ test("dedupes concurrent config dependency installs for the same dir", async () await Promise.all([first, second]) } finally { online.mockRestore() - install.mockRestore() + run.mockRestore() } expect(calls).toBe(2) @@ -878,8 +881,8 @@ test("serializes config dependency installs across dirs", async () => { }) const online = spyOn(Network, "online").mockReturnValue(false) - const run = spyOn(BunProc, "run").mockImplementation(async (_cmd, opts) => { - const cwd = path.normalize(opts?.cwd ?? "") + const run = spyOn(Npm, "install").mockImplementation(async (dir: string) => { + const cwd = path.normalize(dir) const hit = cwd === path.normalize(a) || cwd === path.normalize(b) if (hit) { calls += 1 @@ -890,7 +893,7 @@ test("serializes config dependency installs across dirs", async () => { await gate } } - const mod = path.join(d, "node_modules", "@opencode-ai", "plugin") + const mod = path.join(cwd, "node_modules", "@opencode-ai", "plugin") await fs.mkdir(mod, { recursive: true }) await Filesystem.write( path.join(mod, "package.json"), @@ -899,11 +902,6 @@ test("serializes config dependency installs across dirs", async () => { if (hit) { open -= 1 } - return { - code: 0, - stdout: Buffer.alloc(0), - stderr: Buffer.alloc(0), - } }) try { @@ -914,7 +912,7 @@ test("serializes config dependency installs across dirs", async () => { await Promise.all([first, second]) } finally { online.mockRestore() - install.mockRestore() + run.mockRestore() } expect(calls).toBe(2) diff --git a/packages/opencode/test/plugin/loader-shared.test.ts b/packages/opencode/test/plugin/loader-shared.test.ts index 5f29cb45a0..1467d186e7 100644 --- a/packages/opencode/test/plugin/loader-shared.test.ts +++ b/packages/opencode/test/plugin/loader-shared.test.ts @@ -266,8 +266,8 @@ describe("plugin.loader.shared", () => { try { await load(tmp.path) - expect(install.mock.calls).toContainEqual(["acme-plugin", "latest", { ignoreScripts: true }]) - expect(install.mock.calls).toContainEqual(["scope-plugin", "2.3.4", { ignoreScripts: true }]) + expect(add.mock.calls).toContainEqual(["acme-plugin@latest"]) + expect(add.mock.calls).toContainEqual(["scope-plugin@2.3.4"]) } finally { add.mockRestore() } @@ -378,7 +378,7 @@ describe("plugin.loader.shared", () => { }, }) - const install = spyOn(BunProc, "install").mockResolvedValue(tmp.extra.mod) + const install = spyOn(Npm, "add").mockResolvedValue(tmp.extra.mod) try { const errors = await errs(tmp.path) @@ -431,7 +431,7 @@ describe("plugin.loader.shared", () => { }, }) - const install = spyOn(BunProc, "install").mockResolvedValue(tmp.extra.mod) + const install = spyOn(Npm, "add").mockResolvedValue(tmp.extra.mod) try { const errors = await errs(tmp.path) @@ -477,7 +477,7 @@ describe("plugin.loader.shared", () => { }, }) - const install = spyOn(BunProc, "install").mockResolvedValue(tmp.extra.mod) + const install = spyOn(Npm, "add").mockResolvedValue(tmp.extra.mod) try { const errors = await errs(tmp.path) From 02c6c5b535c23c91d16f1e77546136823a2336ac Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Wed, 1 Apr 2026 12:02:04 -0400 Subject: [PATCH 15/16] fix: restore dev branch format files and remove BunProc references --- packages/opencode/src/format/formatter.ts | 162 +++++++++---------- packages/opencode/src/format/index.ts | 43 ++--- packages/opencode/test/format/format.test.ts | 39 ++--- 3 files changed, 104 insertions(+), 140 deletions(-) diff --git a/packages/opencode/src/format/formatter.ts b/packages/opencode/src/format/formatter.ts index d26a0f9943..9051cecde2 100644 --- a/packages/opencode/src/format/formatter.ts +++ b/packages/opencode/src/format/formatter.ts @@ -4,37 +4,39 @@ import { Filesystem } from "../util/filesystem" import { Process } from "../util/process" import { which } from "../util/which" import { Flag } from "@/flag/flag" -import { Npm } from "@/npm" export interface Info { name: string + command: string[] environment?: Record extensions: string[] - enabled(): Promise + enabled(): Promise } export const gofmt: Info = { name: "gofmt", + command: ["gofmt", "-w", "$FILE"], extensions: [".go"], async enabled() { - const p = which("gofmt") - if (p === null) return false - return [p, "-w", "$FILE"] + return which("gofmt") !== null }, } export const mix: Info = { name: "mix", + command: ["mix", "format", "$FILE"], extensions: [".ex", ".exs", ".eex", ".heex", ".leex", ".neex", ".sface"], async enabled() { - const p = which("mix") - if (p === null) return false - return [p, "format", "$FILE"] + return which("mix") !== null }, } export const prettier: Info = { name: "prettier", + command: ["bun", "x", "prettier", "--write", "$FILE"], + environment: { + BUN_BE_BUN: "1", + }, extensions: [ ".js", ".jsx", @@ -70,11 +72,8 @@ export const prettier: Info = { dependencies?: Record devDependencies?: Record }>(item) - if (json.dependencies?.prettier || json.devDependencies?.prettier) { - const bin = await Npm.which("prettier").catch(() => null) - if (!bin) return false - return [bin, "--write", "$FILE"] - } + if (json.dependencies?.prettier) return true + if (json.devDependencies?.prettier) return true } return false }, @@ -82,6 +81,10 @@ export const prettier: Info = { export const oxfmt: Info = { name: "oxfmt", + command: ["bun", "x", "oxfmt", "$FILE"], + environment: { + BUN_BE_BUN: "1", + }, extensions: [".js", ".jsx", ".mjs", ".cjs", ".ts", ".tsx", ".mts", ".cts"], async enabled() { if (!Flag.OPENCODE_EXPERIMENTAL_OXFMT) return false @@ -91,11 +94,8 @@ export const oxfmt: Info = { dependencies?: Record devDependencies?: Record }>(item) - if (json.dependencies?.oxfmt || json.devDependencies?.oxfmt) { - const bin = await Npm.which("oxfmt") - if (!bin) return false - return [bin, "$FILE"] - } + if (json.dependencies?.oxfmt) return true + if (json.devDependencies?.oxfmt) return true } return false }, @@ -103,6 +103,10 @@ export const oxfmt: Info = { export const biome: Info = { name: "biome", + command: ["bun", "x", "@biomejs/biome", "check", "--write", "$FILE"], + environment: { + BUN_BE_BUN: "1", + }, extensions: [ ".js", ".jsx", @@ -136,9 +140,7 @@ export const biome: Info = { for (const config of configs) { const found = await Filesystem.findUp(config, Instance.directory, Instance.worktree) if (found.length > 0) { - const bin = await Npm.which("@biomejs/biome") - if (!bin) return false - return [bin, "check", "--write", "$FILE"] + return true } } return false @@ -147,49 +149,47 @@ export const biome: Info = { export const zig: Info = { name: "zig", + command: ["zig", "fmt", "$FILE"], extensions: [".zig", ".zon"], async enabled() { - const p = which("zig") - if (p === null) return false - return [p, "fmt", "$FILE"] + return which("zig") !== null }, } export const clang: Info = { name: "clang-format", + command: ["clang-format", "-i", "$FILE"], extensions: [".c", ".cc", ".cpp", ".cxx", ".c++", ".h", ".hh", ".hpp", ".hxx", ".h++", ".ino", ".C", ".H"], async enabled() { const items = await Filesystem.findUp(".clang-format", Instance.directory, Instance.worktree) - if (items.length === 0) return false - return ["clang-format", "-i", "$FILE"] + return items.length > 0 }, } export const ktlint: Info = { name: "ktlint", + command: ["ktlint", "-F", "$FILE"], extensions: [".kt", ".kts"], async enabled() { - const p = which("ktlint") - if (p === null) return false - return [p, "-F", "$FILE"] + return which("ktlint") !== null }, } export const ruff: Info = { name: "ruff", + command: ["ruff", "format", "$FILE"], extensions: [".py", ".pyi"], async enabled() { - const p = which("ruff") - if (p === null) return false + if (!which("ruff")) return false const configs = ["pyproject.toml", "ruff.toml", ".ruff.toml"] for (const config of configs) { const found = await Filesystem.findUp(config, Instance.directory, Instance.worktree) if (found.length > 0) { if (config === "pyproject.toml") { const content = await Filesystem.readText(found[0]) - if (content.includes("[tool.ruff]")) return [p, "format", "$FILE"] + if (content.includes("[tool.ruff]")) return true } else { - return [p, "format", "$FILE"] + return true } } } @@ -198,7 +198,7 @@ export const ruff: Info = { const found = await Filesystem.findUp(dep, Instance.directory, Instance.worktree) if (found.length > 0) { const content = await Filesystem.readText(found[0]) - if (content.includes("ruff")) return [p, "format", "$FILE"] + if (content.includes("ruff")) return true } } return false @@ -207,13 +207,14 @@ export const ruff: Info = { export const rlang: Info = { name: "air", + command: ["air", "format", "$FILE"], extensions: [".R"], async enabled() { const airPath = which("air") if (airPath == null) return false try { - const proc = Process.spawn([airPath, "--help"], { + const proc = Process.spawn(["air", "--help"], { stdout: "pipe", stderr: "pipe", }) @@ -225,10 +226,7 @@ export const rlang: Info = { const firstLine = output.split("\n")[0] const hasR = firstLine.includes("R language") const hasFormatter = firstLine.includes("formatter") - if (hasR && hasFormatter) { - return [airPath, "format", "$FILE"] - } - return false + return hasR && hasFormatter } catch (error) { return false } @@ -237,14 +235,14 @@ export const rlang: Info = { export const uvformat: Info = { name: "uv", + command: ["uv", "format", "--", "$FILE"], extensions: [".py", ".pyi"], async enabled() { if (await ruff.enabled()) return false - const uvPath = which("uv") - if (uvPath !== null) { - const proc = Process.spawn([uvPath, "format", "--help"], { stderr: "pipe", stdout: "pipe" }) + if (which("uv") !== null) { + const proc = Process.spawn(["uv", "format", "--help"], { stderr: "pipe", stdout: "pipe" }) const code = await proc.exited - if (code === 0) return [uvPath, "format", "--", "$FILE"] + return code === 0 } return false }, @@ -252,118 +250,108 @@ export const uvformat: Info = { export const rubocop: Info = { name: "rubocop", + command: ["rubocop", "--autocorrect", "$FILE"], extensions: [".rb", ".rake", ".gemspec", ".ru"], async enabled() { - const path = which("rubocop") - if (path === null) return false - return [path, "--autocorrect", "$FILE"] + return which("rubocop") !== null }, } export const standardrb: Info = { name: "standardrb", + command: ["standardrb", "--fix", "$FILE"], extensions: [".rb", ".rake", ".gemspec", ".ru"], async enabled() { - const path = which("standardrb") - if (path === null) return false - return [path, "--fix", "$FILE"] + return which("standardrb") !== null }, } export const htmlbeautifier: Info = { name: "htmlbeautifier", + command: ["htmlbeautifier", "$FILE"], extensions: [".erb", ".html.erb"], async enabled() { - const path = which("htmlbeautifier") - if (path === null) return false - return [path, "$FILE"] + return which("htmlbeautifier") !== null }, } export const dart: Info = { name: "dart", + command: ["dart", "format", "$FILE"], extensions: [".dart"], async enabled() { - const path = which("dart") - if (path === null) return false - return [path, "format", "$FILE"] + return which("dart") !== null }, } export const ocamlformat: Info = { name: "ocamlformat", + command: ["ocamlformat", "-i", "$FILE"], extensions: [".ml", ".mli"], async enabled() { - const path = which("ocamlformat") - if (!path) return false + if (!which("ocamlformat")) return false const items = await Filesystem.findUp(".ocamlformat", Instance.directory, Instance.worktree) - if (items.length === 0) return false - return [path, "-i", "$FILE"] + return items.length > 0 }, } export const terraform: Info = { name: "terraform", + command: ["terraform", "fmt", "$FILE"], extensions: [".tf", ".tfvars"], async enabled() { - const path = which("terraform") - if (path === null) return false - return [path, "fmt", "$FILE"] + return which("terraform") !== null }, } export const latexindent: Info = { name: "latexindent", + command: ["latexindent", "-w", "-s", "$FILE"], extensions: [".tex"], async enabled() { - const path = which("latexindent") - if (path === null) return false - return [path, "-w", "-s", "$FILE"] + return which("latexindent") !== null }, } export const gleam: Info = { name: "gleam", + command: ["gleam", "format", "$FILE"], extensions: [".gleam"], async enabled() { - const path = which("gleam") - if (path === null) return false - return [path, "format", "$FILE"] + return which("gleam") !== null }, } export const shfmt: Info = { name: "shfmt", + command: ["shfmt", "-w", "$FILE"], extensions: [".sh", ".bash"], async enabled() { - const path = which("shfmt") - if (path === null) return false - return [path, "-w", "$FILE"] + return which("shfmt") !== null }, } export const nixfmt: Info = { name: "nixfmt", + command: ["nixfmt", "$FILE"], extensions: [".nix"], async enabled() { - const path = which("nixfmt") - if (path === null) return false - return [path, "$FILE"] + return which("nixfmt") !== null }, } export const rustfmt: Info = { name: "rustfmt", + command: ["rustfmt", "$FILE"], extensions: [".rs"], async enabled() { - const path = which("rustfmt") - if (path === null) return false - return [path, "$FILE"] + return which("rustfmt") !== null }, } export const pint: Info = { name: "pint", + command: ["./vendor/bin/pint", "$FILE"], extensions: [".php"], async enabled() { const items = await Filesystem.findUp("composer.json", Instance.directory, Instance.worktree) @@ -372,9 +360,8 @@ export const pint: Info = { require?: Record "require-dev"?: Record }>(item) - if (json.require?.["laravel/pint"] || json["require-dev"]?.["laravel/pint"]) { - return ["./vendor/bin/pint", "$FILE"] - } + if (json.require?.["laravel/pint"]) return true + if (json["require-dev"]?.["laravel/pint"]) return true } return false }, @@ -382,30 +369,27 @@ export const pint: Info = { export const ormolu: Info = { name: "ormolu", + command: ["ormolu", "-i", "$FILE"], extensions: [".hs"], async enabled() { - const path = which("ormolu") - if (path === null) return false - return [path, "-i", "$FILE"] + return which("ormolu") !== null }, } export const cljfmt: Info = { name: "cljfmt", + command: ["cljfmt", "fix", "--quiet", "$FILE"], extensions: [".clj", ".cljs", ".cljc", ".edn"], async enabled() { - const path = which("cljfmt") - if (path === null) return false - return [path, "fix", "--quiet", "$FILE"] + return which("cljfmt") !== null }, } export const dfmt: Info = { name: "dfmt", + command: ["dfmt", "-i", "$FILE"], extensions: [".d"], async enabled() { - const path = which("dfmt") - if (path === null) return false - return [path, "-i", "$FILE"] + return which("dfmt") !== null }, } diff --git a/packages/opencode/src/format/index.ts b/packages/opencode/src/format/index.ts index 92e852f42f..795364be1c 100644 --- a/packages/opencode/src/format/index.ts +++ b/packages/opencode/src/format/index.ts @@ -41,7 +41,7 @@ export namespace Format { const state = yield* InstanceState.make( Effect.fn("Format.state")(function* (_ctx) { - const enabled: Record = {} + const enabled: Record = {} const formatters: Record = {} const cfg = yield* config.get() @@ -66,19 +66,20 @@ export namespace Format { formatters[name] = { ...info, name, - enabled: async () => info.command, + enabled: async () => true, } } } else { log.info("all formatters are disabled") } - if (info.command.length === 0) continue - - formatters[name] = { - ...info, - name, - enabled: async (): Promise => info.command, + async function isEnabled(item: Formatter.Info) { + let status = enabled[item.name] + if (status === undefined) { + status = await item.enabled() + enabled[item.name] = status + } + return status } async function getFormatter(ext: string) { @@ -86,27 +87,17 @@ export namespace Format { const checks = await Promise.all( matching.map(async (item) => { log.info("checking", { name: item.name, ext }) - const cmd = await isEnabled(item) - if (cmd) { + const on = await isEnabled(item) + if (on) { log.info("enabled", { name: item.name, ext }) } - return { item, cmd } + return { + item, + enabled: on, + } }), ) - const result: Array<{ - name: string - command: string[] - environment?: Record - }> = [] - for (const { item, cmd } of checks) { - if (cmd !== false) - result.push({ - name: item.name, - command: cmd, - environment: item.environment, - }) - } - return result + return checks.filter((x) => x.enabled).map((x) => x.item) } function formatFile(filepath: string) { @@ -173,7 +164,7 @@ export namespace Format { result.push({ name: formatter.name, extensions: formatter.extensions, - enabled: !!isOn, + enabled: isOn, }) } return result diff --git a/packages/opencode/test/format/format.test.ts b/packages/opencode/test/format/format.test.ts index 0e840fef3a..89a8c1f450 100644 --- a/packages/opencode/test/format/format.test.ts +++ b/packages/opencode/test/format/format.test.ts @@ -66,29 +66,12 @@ describe("Format", () => { it.live("service initializes without error", () => provideTmpdirInstance(() => Format.Service.use(() => Effect.void))) - test("status() includes custom formatters with command from config", async () => { - await using tmp = await tmpdir({ - config: { - formatter: { - customtool: { - command: ["echo", "formatted", "$FILE"], - extensions: [".custom"], - }, - }, - }, - }) - - await withServices(tmp.path, Format.layer, async (rt) => { - const statuses = await rt.runPromise(Format.Service.use((s) => s.status())) - const custom = statuses.find((s) => s.name === "customtool") - expect(custom).toBeDefined() - expect(custom!.extensions).toContain(".custom") - expect(custom!.enabled).toBe(true) - }) - }) - - test("service initializes without error", async () => { - await using tmp = await tmpdir() + it.live("status() initializes formatter state per directory", () => + Effect.gen(function* () { + const a = yield* provideTmpdirInstance(() => Format.Service.use((fmt) => fmt.status()), { + config: { formatter: false }, + }) + const b = yield* provideTmpdirInstance(() => Format.Service.use((fmt) => fmt.status())) expect(a).toEqual([]) expect(b.length).toBeGreaterThan(0) @@ -104,10 +87,12 @@ describe("Format", () => { const one = { extensions: Formatter.gofmt.extensions, enabled: Formatter.gofmt.enabled, + command: Formatter.gofmt.command, } const two = { extensions: Formatter.mix.extensions, enabled: Formatter.mix.enabled, + command: Formatter.mix.command, } let active = 0 @@ -117,19 +102,21 @@ describe("Format", () => { Effect.sync(() => { Formatter.gofmt.extensions = [".parallel"] Formatter.mix.extensions = [".parallel"] + Formatter.gofmt.command = ["sh", "-c", "true"] + Formatter.mix.command = ["sh", "-c", "true"] Formatter.gofmt.enabled = async () => { active++ max = Math.max(max, active) await Bun.sleep(20) active-- - return ["sh", "-c", "true"] + return true } Formatter.mix.enabled = async () => { active++ max = Math.max(max, active) await Bun.sleep(20) active-- - return ["sh", "-c", "true"] + return true } }), () => @@ -143,8 +130,10 @@ describe("Format", () => { Effect.sync(() => { Formatter.gofmt.extensions = one.extensions Formatter.gofmt.enabled = one.enabled + Formatter.gofmt.command = one.command Formatter.mix.extensions = two.extensions Formatter.mix.enabled = two.enabled + Formatter.mix.command = two.command }), ) From bf21751bdc010490296200b9fced091927f8c217 Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Wed, 1 Apr 2026 12:02:39 -0400 Subject: [PATCH 16/16] chore: remove Windows tar workaround from npm module --- packages/opencode/src/npm/index.ts | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/packages/opencode/src/npm/index.ts b/packages/opencode/src/npm/index.ts index 9cbd274be8..99d082f5dd 100644 --- a/packages/opencode/src/npm/index.ts +++ b/packages/opencode/src/npm/index.ts @@ -1,14 +1,3 @@ -// Workaround: Bun on Windows does not support the UV_FS_O_FILEMAP flag that -// the `tar` package uses for files < 512KB (fs.open returns EINVAL). -// tar silently swallows the error and skips writing files, leaving only empty -// directories. Setting __FAKE_PLATFORM__ makes tar fall back to the plain 'w' -// flag. See tar's get-write-flag.js. -// Must be set before @npmcli/arborist is imported since tar caches the flag -// at module evaluation time — so we use a dynamic import() below. -if (process.platform === "win32") { - process.env.__FAKE_PLATFORM__ = "linux" -} - import semver from "semver" import z from "zod" import { NamedError } from "@opencode-ai/util/error"