stricter checks
CI / verify (pull_request) Successful in 12s

This commit is contained in:
2026-10-10 15:16:53 +02:00
parent ced43fbb11
commit 513d51f702
30 changed files with 984 additions and 470 deletions
+51 -27
View File
@@ -3,6 +3,7 @@ import { chmod, mkdir, mkdtemp, readFile, readdir, rm, writeFile } from "node:fs
import { tmpdir } from "node:os";
import { dirname, join } from "node:path";
import { createHash } from "node:crypto";
import { z } from "zod";
import { install } from "../scripts/install.js";
const directories: string[] = [];
@@ -34,6 +35,15 @@ const empty = {
"deep-hi": "",
},
};
const pluginEntry = z
.object({ package: z.string(), options: z.record(z.string(), z.json()).optional() })
.loose();
const configFile = z
.object({ plugins: z.array(z.json()), experimental: z.json().optional() })
.loose();
const manifestFile = z.object({ exports: z.record(z.string(), z.json()) }).loose();
const entries = (plugins: z.infer<typeof configFile>["plugins"]) =>
z.array(pluginEntry).parse(plugins);
const helper = new URL("../scripts/install.ts", import.meta.url);
async function run(
command: string[],
@@ -50,8 +60,8 @@ async function run(
timeout: 30_000,
});
if (input !== undefined && process.stdin) {
process.stdin.write(input);
process.stdin.end();
void process.stdin.write(input);
void process.stdin.end();
}
const [exitCode, stdout, stderr] = await Promise.all([
process.exited,
@@ -83,7 +93,9 @@ async function fixture() {
format: "esm",
});
expect(built.success).toBe(true);
await Bun.write(join(source, "dist", "install.js"), built.outputs[0]!);
const [output] = built.outputs;
if (output === undefined) throw new Error("Missing installer build output");
await Bun.write(join(source, "dist", "install.js"), output);
await writeFile(join(source, "package.json"), '{"type":"module"}');
await writeFile(
join(config, "opencode.json"),
@@ -96,7 +108,7 @@ async function fixture() {
return { directory, source, config };
}
async function contents(config: string) {
return JSON.parse(await readFile(join(config, "opencode.json"), "utf8"));
return configFile.parse(JSON.parse(await readFile(join(config, "opencode.json"), "utf8")));
}
for (const selection of ["quota", "agents,multi-work,quota"]) {
@@ -106,14 +118,16 @@ for (const selection of ["quota", "agents,multi-work,quota"]) {
const result = await contents(f.config);
expect(result).toMatchObject(unrelated);
expect(result.experimental === undefined).toBe(selection === "quota");
expect(result.plugins.map((entry: { package: string }) => entry.package)).toEqual([
expect(entries(result.plugins).map((entry) => entry.package)).toEqual([
"before",
...selection.split(",").map((name) => `./plugins/stanp-opencode/${name}`),
]);
const target = join(f.config, "plugins", "stanp-opencode");
expect((await readdir(target)).sort()).toEqual(selection.split(",").sort());
expect((await readdir(target)).toSorted()).toEqual(selection.split(",").toSorted());
for (const name of selection.split(",")) {
const manifest = await Bun.file(join(target, name, "package.json")).json();
const manifest = manifestFile.parse(
await Bun.file(join(target, name, "package.json")).json(),
);
expect(manifest.exports["./tui"] !== undefined).toBe(name === "quota");
expect(await Bun.file(join(target, name, "tui.js")).exists()).toBe(name === "quota");
}
@@ -127,18 +141,23 @@ test("upgrades retain selection and options; explicit selection replaces only ow
const f = await fixture();
await install(f.source, f.config, "agents,quota");
const current = await contents(f.config);
current.plugins[1].options = {
agents: { ...empty.agents, "fast-lo": "openai/gpt-6-luna#low" },
};
current.plugins.push({ package: "after", options: { untouched: 1 } });
await writeFile(join(f.config, "opencode.json"), JSON.stringify(current));
const [before, agents, quota] = entries(current.plugins);
if (!before || !agents || !quota) throw new Error("Missing installed plugins");
const after = { package: "after", options: { untouched: 1 } };
const plugins = [
before,
{ ...agents, options: { agents: { ...empty.agents, "fast-lo": "openai/gpt-6-luna#low" } } },
quota,
after,
];
await writeFile(join(f.config, "opencode.json"), JSON.stringify({ ...current, plugins }));
await install(f.source, f.config);
expect((await contents(f.config)).plugins).toEqual(current.plugins);
expect(entries((await contents(f.config)).plugins)).toEqual(plugins);
await install(f.source, f.config, "quota");
expect((await contents(f.config)).plugins).toEqual([
current.plugins[0],
expect(entries((await contents(f.config)).plugins)).toEqual([
before,
{ package: "./plugins/stanp-opencode/quota" },
current.plugins[3],
after,
]);
});
@@ -152,14 +171,17 @@ test("installer writes empty option skeletons and subagent_depth, fills only mis
{ package: "./plugins/stanp-opencode/quota" },
]);
expect(fresh.experimental).toEqual({ subagent_depth: 2 });
fresh.plugins[1].options = {
agents: { "fast-lo": "openai/gpt-6-luna#low" },
extra: 1,
};
await writeFile(join(f.config, "opencode.json"), JSON.stringify(fresh));
const [before, agents, ...rest] = entries(fresh.plugins);
if (!before || !agents) throw new Error("Missing installed plugins");
const plugins = [
before,
{ ...agents, options: { agents: { "fast-lo": "openai/gpt-6-luna#low" }, extra: 1 } },
...rest,
];
await writeFile(join(f.config, "opencode.json"), JSON.stringify({ ...fresh, plugins }));
await install(f.source, f.config);
const filled = await contents(f.config);
expect(filled.plugins[1].options).toEqual({
expect(entries(filled.plugins)[1]?.options).toEqual({
agents: { ...empty.agents, "fast-lo": "openai/gpt-6-luna#low" },
extra: 1,
});
@@ -169,7 +191,7 @@ test("installer writes empty option skeletons and subagent_depth, fills only mis
for (const [depth, expected] of [
[1, 2],
[3, 3],
]) {
] as const) {
await writeFile(
join(f.config, "opencode.json"),
JSON.stringify({
@@ -214,12 +236,14 @@ test("replaces the legacy monolith without duplicates and retains Agents options
await writeFile(join(f.config, "plugins", "stanp-opencode.js"), "legacy");
await install(f.source, f.config);
const result = await contents(f.config);
expect(result.plugins.map((entry: { package: string }) => entry.package)).toEqual([
expect(entries(result.plugins).map((entry) => entry.package)).toEqual([
"before",
...names.map((name) => `./plugins/stanp-opencode/${name}`),
"after",
]);
expect(result.plugins[1].options.agents["fast-lo"]).toBe("openai/gpt-6-luna#low");
expect(result.plugins[1]).toMatchObject({
options: { agents: { "fast-lo": "openai/gpt-6-luna#low" } },
});
expect(await Bun.file(join(f.config, "plugins", "stanp-opencode.js")).exists()).toBe(false);
expect(result).toMatchObject(unrelated);
});
@@ -283,7 +307,7 @@ test("local shell installer builds its checkout and upgrades mixed entries witho
await chmod(join(bin, "bun"), 0o755);
const env = {
...Bun.env,
PATH: `${bin}:${dirname(process.execPath)}:${Bun.env.PATH}`,
PATH: `${bin}:${dirname(process.execPath)}:${Bun.env["PATH"]}`,
REAL_BUN: process.execPath,
CALLS: join(f.directory, "calls"),
OPENCODE_CONFIG_DIR: f.config,
@@ -357,7 +381,7 @@ for (const validChecksum of [true, false])
f.directory,
{
...Bun.env,
PATH: `${bin}:${dirname(process.execPath)}:${Bun.env.PATH}`,
PATH: `${bin}:${dirname(process.execPath)}:${Bun.env["PATH"]}`,
ARCHIVE: archive,
CHECKSUM: checksum,
RELEASE: release,