squashed
This commit is contained in:
@@ -10,6 +10,7 @@ import { pathToFileURL } from "url"
|
||||
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"
|
||||
|
||||
// Get managed config directory from environment (set in preload.ts)
|
||||
@@ -746,6 +747,20 @@ 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")
|
||||
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 {
|
||||
await Instance.provide({
|
||||
@@ -759,25 +774,43 @@ test("installs dependencies in writable OPENCODE_CONFIG_DIR", async () => {
|
||||
expect(await Filesystem.exists(path.join(tmp.extra, "package.json"))).toBe(true)
|
||||
expect(await Filesystem.exists(path.join(tmp.extra, ".gitignore"))).toBe(true)
|
||||
} finally {
|
||||
online.mockRestore()
|
||||
run.mockRestore()
|
||||
if (prev === undefined) delete process.env.OPENCODE_CONFIG_DIR
|
||||
else process.env.OPENCODE_CONFIG_DIR = prev
|
||||
}
|
||||
})
|
||||
|
||||
test("serializes concurrent config dependency installs", async () => {
|
||||
test("dedupes concurrent config dependency installs for the same dir", 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 dir = path.join(tmp.path, "a")
|
||||
await fs.mkdir(dir, { recursive: true })
|
||||
|
||||
const seen: string[] = []
|
||||
let active = 0
|
||||
let max = 0
|
||||
const ticks: number[] = []
|
||||
let calls = 0
|
||||
let start = () => {}
|
||||
let done = () => {}
|
||||
let blocked = () => {}
|
||||
const ready = new Promise<void>((resolve) => {
|
||||
start = resolve
|
||||
})
|
||||
const gate = new Promise<void>((resolve) => {
|
||||
done = resolve
|
||||
})
|
||||
const waiting = new Promise<void>((resolve) => {
|
||||
blocked = resolve
|
||||
})
|
||||
const online = spyOn(Network, "online").mockReturnValue(false)
|
||||
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--
|
||||
calls += 1
|
||||
start()
|
||||
await gate
|
||||
const mod = path.join(opts?.cwd ?? "", "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),
|
||||
@@ -786,15 +819,26 @@ test("serializes concurrent config dependency installs", async () => {
|
||||
})
|
||||
|
||||
try {
|
||||
await Promise.all(dirs.map((dir) => Config.installDependencies(dir)))
|
||||
const first = Config.installDependencies(dir)
|
||||
await ready
|
||||
const second = Config.installDependencies(dir, {
|
||||
waitTick: (tick) => {
|
||||
ticks.push(tick.attempt)
|
||||
blocked()
|
||||
blocked = () => {}
|
||||
},
|
||||
})
|
||||
await waiting
|
||||
done()
|
||||
await Promise.all([first, second])
|
||||
} finally {
|
||||
online.mockRestore()
|
||||
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)
|
||||
expect(calls).toBe(1)
|
||||
expect(ticks.length).toBeGreaterThan(0)
|
||||
expect(await Filesystem.exists(path.join(dir, "package.json"))).toBe(true)
|
||||
})
|
||||
|
||||
test("resolves scoped npm plugins in config", async () => {
|
||||
@@ -1807,7 +1851,7 @@ describe("deduplicatePlugins", () => {
|
||||
|
||||
const myPlugins = plugins.filter((p) => Config.getPluginName(p) === "my-plugin")
|
||||
expect(myPlugins.length).toBe(1)
|
||||
expect(myPlugins[0].startsWith("file://")).toBe(true)
|
||||
expect(Config.pluginSpecifier(myPlugins[0]).startsWith("file://")).toBe(true)
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
@@ -458,9 +458,15 @@ test("applies file substitutions when first identical token is in a commented li
|
||||
test("loads managed tui config and gives it highest precedence", async () => {
|
||||
await using tmp = await tmpdir({
|
||||
init: async (dir) => {
|
||||
await Bun.write(path.join(dir, "tui.json"), JSON.stringify({ theme: "project-theme" }, null, 2))
|
||||
await Bun.write(
|
||||
path.join(dir, "tui.json"),
|
||||
JSON.stringify({ theme: "project-theme", plugin: ["shared-plugin@1.0.0"] }, null, 2),
|
||||
)
|
||||
await fs.mkdir(managedConfigDir, { recursive: true })
|
||||
await Bun.write(path.join(managedConfigDir, "tui.json"), JSON.stringify({ theme: "managed-theme" }, null, 2))
|
||||
await Bun.write(
|
||||
path.join(managedConfigDir, "tui.json"),
|
||||
JSON.stringify({ theme: "managed-theme", plugin: ["shared-plugin@2.0.0"] }, null, 2),
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
@@ -469,6 +475,13 @@ test("loads managed tui config and gives it highest precedence", async () => {
|
||||
fn: async () => {
|
||||
const config = await TuiConfig.get()
|
||||
expect(config.theme).toBe("managed-theme")
|
||||
expect(config.plugin).toEqual(["shared-plugin@2.0.0"])
|
||||
expect(config.plugin_meta).toEqual({
|
||||
"shared-plugin": {
|
||||
scope: "global",
|
||||
source: path.join(managedConfigDir, "tui.json"),
|
||||
},
|
||||
})
|
||||
},
|
||||
})
|
||||
})
|
||||
@@ -508,3 +521,110 @@ test("gracefully falls back when tui.json has invalid JSON", async () => {
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
test("supports tuple plugin specs with options in tui.json", async () => {
|
||||
await using tmp = await tmpdir({
|
||||
init: async (dir) => {
|
||||
await Bun.write(
|
||||
path.join(dir, "tui.json"),
|
||||
JSON.stringify({
|
||||
plugin: [["acme-plugin@1.2.3", { enabled: true, label: "demo" }]],
|
||||
}),
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
fn: async () => {
|
||||
const config = await TuiConfig.get()
|
||||
expect(config.plugin).toEqual([["acme-plugin@1.2.3", { enabled: true, label: "demo" }]])
|
||||
expect(config.plugin_meta).toEqual({
|
||||
"acme-plugin": {
|
||||
scope: "local",
|
||||
source: path.join(tmp.path, "tui.json"),
|
||||
},
|
||||
})
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
test("deduplicates tuple plugin specs by name with higher precedence winning", async () => {
|
||||
await using tmp = await tmpdir({
|
||||
init: async (dir) => {
|
||||
await Bun.write(
|
||||
path.join(Global.Path.config, "tui.json"),
|
||||
JSON.stringify({
|
||||
plugin: [["acme-plugin@1.0.0", { source: "global" }]],
|
||||
}),
|
||||
)
|
||||
await Bun.write(
|
||||
path.join(dir, "tui.json"),
|
||||
JSON.stringify({
|
||||
plugin: [
|
||||
["acme-plugin@2.0.0", { source: "project" }],
|
||||
["second-plugin@3.0.0", { source: "project" }],
|
||||
],
|
||||
}),
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
fn: async () => {
|
||||
const config = await TuiConfig.get()
|
||||
expect(config.plugin).toEqual([
|
||||
["acme-plugin@2.0.0", { source: "project" }],
|
||||
["second-plugin@3.0.0", { source: "project" }],
|
||||
])
|
||||
expect(config.plugin_meta).toEqual({
|
||||
"acme-plugin": {
|
||||
scope: "local",
|
||||
source: path.join(tmp.path, "tui.json"),
|
||||
},
|
||||
"second-plugin": {
|
||||
scope: "local",
|
||||
source: path.join(tmp.path, "tui.json"),
|
||||
},
|
||||
})
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
test("tracks global and local plugin metadata in merged tui config", async () => {
|
||||
await using tmp = await tmpdir({
|
||||
init: async (dir) => {
|
||||
await Bun.write(
|
||||
path.join(Global.Path.config, "tui.json"),
|
||||
JSON.stringify({
|
||||
plugin: ["global-plugin@1.0.0"],
|
||||
}),
|
||||
)
|
||||
await Bun.write(
|
||||
path.join(dir, "tui.json"),
|
||||
JSON.stringify({
|
||||
plugin: ["local-plugin@2.0.0"],
|
||||
}),
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
fn: async () => {
|
||||
const config = await TuiConfig.get()
|
||||
expect(config.plugin).toEqual(["global-plugin@1.0.0", "local-plugin@2.0.0"])
|
||||
expect(config.plugin_meta).toEqual({
|
||||
"global-plugin": {
|
||||
scope: "global",
|
||||
source: path.join(Global.Path.config, "tui.json"),
|
||||
},
|
||||
"local-plugin": {
|
||||
scope: "local",
|
||||
source: path.join(tmp.path, "tui.json"),
|
||||
},
|
||||
})
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user