237 lines
9.0 KiB
TypeScript
237 lines
9.0 KiB
TypeScript
import { afterEach, expect, test } from "bun:test";
|
|
import { chmod, mkdir, mkdtemp, readFile, readdir, rm, writeFile } from "node:fs/promises";
|
|
import { tmpdir } from "node:os";
|
|
import { dirname, join } from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
const installer = fileURLToPath(new URL("../install-local.sh", import.meta.url));
|
|
const directories: string[] = [];
|
|
const jsonc = '{\n // This file must remain untouched.\n "theme": "dark"\n}\n';
|
|
const oldPlugin = 'export default { id: "old", setup() { return "old"; } };\n';
|
|
type FixtureConfig =
|
|
| { permissions: { shell: string }; plugins: { package: string }[] }
|
|
| {
|
|
theme: string;
|
|
plugins: {
|
|
package: string;
|
|
options: { mode: string; coordinate: string; lunaPriority: boolean };
|
|
}[];
|
|
}
|
|
| { plugins: string };
|
|
|
|
afterEach(async () => {
|
|
await Promise.all(
|
|
directories.splice(0).map((path) => rm(path, { recursive: true, force: true })),
|
|
);
|
|
});
|
|
|
|
async function run(command: string[], cwd: string, env: NodeJS.ProcessEnv) {
|
|
const child = Bun.spawn(command, {
|
|
cwd,
|
|
env,
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
timeout: 20_000,
|
|
});
|
|
const [exitCode, stdout, stderr] = await Promise.all([
|
|
child.exited,
|
|
new Response(child.stdout).text(),
|
|
new Response(child.stderr).text(),
|
|
]);
|
|
return { exitCode, stdout, stderr };
|
|
}
|
|
|
|
async function fixture(configValue?: FixtureConfig) {
|
|
const directory = await mkdtemp(join(tmpdir(), "stanp-local-install-"));
|
|
directories.push(directory);
|
|
const checkout = join(directory, "checkout with spaces");
|
|
const cwd = join(directory, "different cwd");
|
|
const config = join(directory, "config with spaces");
|
|
const bin = join(directory, "bin");
|
|
const installed = join(config, "plugins", "stanp-opencode");
|
|
await Promise.all([
|
|
mkdir(checkout),
|
|
mkdir(cwd),
|
|
mkdir(bin),
|
|
mkdir(installed, { recursive: true }),
|
|
]);
|
|
await Promise.all([
|
|
Bun.write(join(checkout, "install-local.sh"), Bun.file(installer)),
|
|
writeFile(
|
|
join(checkout, "package.json"),
|
|
JSON.stringify({
|
|
name: "stanp-opencode",
|
|
version: "1.0.0",
|
|
type: "module",
|
|
exports: { ".": "./dist/index.js", "./tui": { import: "./dist/tui.js" } },
|
|
}),
|
|
),
|
|
writeFile(join(checkout, "bun.lock"), ""),
|
|
writeFile(join(config, "opencode.jsonc"), jsonc),
|
|
writeFile(
|
|
join(config, "opencode.json"),
|
|
JSON.stringify(
|
|
configValue ?? {
|
|
permissions: { shell: "ask" },
|
|
plugins: [{ package: "existing-plugin" }],
|
|
},
|
|
),
|
|
),
|
|
writeFile(join(installed, "index.js"), oldPlugin),
|
|
writeFile(
|
|
join(installed, "package.json"),
|
|
'{"name":"stanp-opencode","type":"module","main":"./index.js"}\n',
|
|
),
|
|
writeFile(join(config, "plugins", "stanp-opencode.js"), "legacy\n"),
|
|
writeFile(
|
|
join(bin, "bun"),
|
|
`#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
if [[ "\${1:-}" == "install" ]]; then
|
|
[[ "\${2:-}" == "--frozen-lockfile" ]]
|
|
printf '%s\n' "$PWD" >> "$CALLS"
|
|
[[ "$MODE" != "dependency" ]]
|
|
exit
|
|
fi
|
|
if [[ "\${1:-}" == "run" && "\${2:-}" == "build" ]]; then
|
|
printf '%s\n' "$PWD" >> "$CALLS"
|
|
[[ "$MODE" != "build" ]]
|
|
mkdir -p dist
|
|
if [[ "$MODE" == "validation" ]]; then
|
|
printf '%s\n' 'export default { id: "stanp-opencode", server() {} };' > dist/index.js
|
|
else
|
|
printf 'export default { id: "stanp-opencode", setup() { return "%s"; } };\n' "$GENERATION" > dist/index.js
|
|
fi
|
|
printf '%s\n' 'export default { id: "stanp-opencode.quota-tui", setup() {} };' > dist/tui.js
|
|
exit
|
|
fi
|
|
exec "$REAL_BUN" "$@"
|
|
`,
|
|
),
|
|
writeFile(
|
|
join(bin, "curl"),
|
|
`#!/usr/bin/env bash
|
|
touch "$FETCH_MARKER"
|
|
printf 'release fetching is forbidden\n' >&2
|
|
exit 99
|
|
`,
|
|
),
|
|
]);
|
|
await Promise.all([chmod(join(bin, "bun"), 0o755), chmod(join(bin, "curl"), 0o755)]);
|
|
const env = {
|
|
...Bun.env,
|
|
PATH: `${bin}:${dirname(process.execPath)}:${Bun.env.PATH ?? "/usr/bin:/bin"}`,
|
|
HOME: join(directory, "home"),
|
|
XDG_CONFIG_HOME: join(directory, "ignored-xdg"),
|
|
OPENCODE_CONFIG_DIR: config,
|
|
REAL_BUN: process.execPath,
|
|
MODE: "success",
|
|
GENERATION: "first",
|
|
CALLS: join(directory, "calls"),
|
|
FETCH_MARKER: join(directory, "release-fetch-attempted"),
|
|
};
|
|
return { directory, checkout, cwd, config, installed, env };
|
|
}
|
|
|
|
test("builds from its checkout and installs defaults when invoked elsewhere", async () => {
|
|
const setup = await fixture();
|
|
const result = await run(
|
|
["bash", join(setup.checkout, "install-local.sh")],
|
|
setup.cwd,
|
|
setup.env,
|
|
);
|
|
expect(result, result.stderr).toMatchObject({ exitCode: 0 });
|
|
expect(await readFile(setup.env.CALLS, "utf8")).toBe(`${setup.checkout}\n${setup.checkout}\n`);
|
|
expect(await Bun.file(setup.env.FETCH_MARKER).exists()).toBe(false);
|
|
expect(await readFile(join(setup.config, "opencode.jsonc"), "utf8")).toBe(jsonc);
|
|
expect(JSON.parse(await readFile(join(setup.config, "opencode.json"), "utf8"))).toEqual({
|
|
$schema: "https://opencode.ai/config.json",
|
|
permissions: { shell: "ask" },
|
|
plugins: [
|
|
{ package: "existing-plugin" },
|
|
{
|
|
package: "./plugins/stanp-opencode",
|
|
options: { mode: "gpt", provider: "openai", lunaPriority: true },
|
|
},
|
|
],
|
|
});
|
|
const imported = await run(
|
|
[
|
|
process.execPath,
|
|
"-e",
|
|
"const { default: plugin } = await import(process.argv[1]); console.log(plugin.setup());",
|
|
setup.installed,
|
|
],
|
|
setup.cwd,
|
|
setup.env,
|
|
);
|
|
expect(imported, imported.stderr).toMatchObject({ exitCode: 0, stdout: "first\n" });
|
|
expect((await readdir(setup.installed)).sort()).toEqual(["index.js", "package.json", "tui.js"]);
|
|
expect(await Bun.file(join(setup.config, "plugins", "stanp-opencode.js")).exists()).toBe(false);
|
|
});
|
|
|
|
test("reinstall replaces bundles while preserving existing settings and plugin options", async () => {
|
|
const configured = {
|
|
theme: "dark",
|
|
plugins: [
|
|
{
|
|
package: "./plugins/stanp-opencode",
|
|
options: { mode: "custom", coordinate: "provider/model", lunaPriority: false },
|
|
},
|
|
],
|
|
};
|
|
const setup = await fixture(configured);
|
|
const first = await run(
|
|
["bash", join(setup.checkout, "install-local.sh")],
|
|
setup.cwd,
|
|
setup.env,
|
|
);
|
|
expect(first, first.stderr).toMatchObject({ exitCode: 0 });
|
|
const second = await run(["bash", join(setup.checkout, "install-local.sh")], setup.cwd, {
|
|
...setup.env,
|
|
GENERATION: "second",
|
|
});
|
|
expect(second, second.stderr).toMatchObject({ exitCode: 0 });
|
|
expect(JSON.parse(await readFile(join(setup.config, "opencode.json"), "utf8"))).toEqual({
|
|
$schema: "https://opencode.ai/config.json",
|
|
...configured,
|
|
});
|
|
expect(await readFile(join(setup.installed, "index.js"), "utf8")).toContain('return "second"');
|
|
});
|
|
|
|
test("enables Playwright only when requested", async () => {
|
|
const setup = await fixture();
|
|
const result = await run(
|
|
["bash", join(setup.checkout, "install-local.sh"), "--playwright"],
|
|
setup.cwd,
|
|
setup.env,
|
|
);
|
|
expect(result, result.stderr).toMatchObject({ exitCode: 0 });
|
|
const installed = JSON.parse(await readFile(join(setup.config, "opencode.json"), "utf8"));
|
|
expect(installed.plugins.at(-1).options.playwright).toBe(true);
|
|
});
|
|
|
|
for (const mode of ["dependency", "build", "validation", "config"] as const) {
|
|
test(`${mode} failure leaves the previous installation and configuration untouched`, async () => {
|
|
const invalidConfig = mode === "config" ? { plugins: "not-an-array" } : undefined;
|
|
const setup = await fixture(invalidConfig);
|
|
const beforeConfig = await readFile(join(setup.config, "opencode.json"), "utf8");
|
|
const result = await run(["bash", join(setup.checkout, "install-local.sh")], setup.cwd, {
|
|
...setup.env,
|
|
MODE: mode === "config" ? "success" : mode,
|
|
});
|
|
expect(result.exitCode, result.stderr).not.toBe(0);
|
|
expect(await readFile(join(setup.installed, "index.js"), "utf8")).toBe(oldPlugin);
|
|
expect(await readFile(join(setup.config, "opencode.json"), "utf8")).toBe(beforeConfig);
|
|
expect(await readFile(join(setup.config, "opencode.jsonc"), "utf8")).toBe(jsonc);
|
|
expect(await readFile(join(setup.config, "plugins", "stanp-opencode.js"), "utf8")).toBe(
|
|
"legacy\n",
|
|
);
|
|
expect((await readdir(join(setup.config, "plugins"))).sort()).toEqual([
|
|
"stanp-opencode",
|
|
"stanp-opencode.js",
|
|
]);
|
|
expect(await Bun.file(setup.env.FETCH_MARKER).exists()).toBe(false);
|
|
});
|
|
}
|