repositories / pi-ext
pi-ext
bugabingas pi extensions
owned by admin
scripts/release/smoke.test.ts
Rawimport { spawnSync } from "node:child_process";
import {
existsSync,
mkdirSync,
mkdtempSync,
rmSync,
symlinkSync,
writeFileSync,
} from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterEach, describe, expect, it } from "vitest";
import { ROOT } from "../build/build.ts";
import { deliverGit, snapshot } from "./index.ts";
// One eligible extension whose built package registers a runnable tool, plus a
// release scenario that loads the package and executes that tool. This proves
// the Git delivery channel end to end without a real product selection.
const TOOL_EXTENSION = `export default function (pi) {
pi.registerTool({
name: "smoke_echo",
label: "Smoke Echo",
description: "Echoes the provided text.",
parameters: {
type: "object",
properties: { text: { type: "string" } },
required: ["text"],
},
async execute(_id, args) {
return { content: [{ type: "text", text: String(args.text) }], details: {} };
},
});
}
`;
const RUN_TOOL_SCENARIO = `export async function verify({ session }) {
const [extension] = session.extensionRunner.extensions;
if (!extension) throw new Error("no extension loaded from delivered package");
const tool = extension.tools.get("smoke_echo");
if (!tool) throw new Error("smoke_echo tool was not registered");
const result = await tool.definition.execute("smoke-call", { text: "hi" });
const text = result?.content?.[0]?.text;
if (text !== "hi") throw new Error("unexpected tool output: " + text);
}
`;
const trash: string[] = [];
function eligibleRepo(): { root: string; dist: string; release: string } {
const root = mkdtempSync(join(tmpdir(), "pi-ext-smoke-src-"));
trash.push(root);
const ext = join(root, "extensions", "demo");
const dist = join(root, "dist");
mkdirSync(ext, { recursive: true });
writeFileSync(join(root, "package.json"), '{"version":"9.9.9"}\n');
writeFileSync(join(ext, "scenario.mjs"), RUN_TOOL_SCENARIO);
writeFileSync(
join(ext, "package.json"),
JSON.stringify({
"pi-ext": {
publish: true,
publicName: "@owned/demo",
git: "file:///never-push",
releaseScenario: "scenario.mjs",
},
}),
);
mkdirSync(join(dist, "demo"), { recursive: true });
writeFileSync(join(dist, "demo", "index.js"), TOOL_EXTENSION);
writeFileSync(
join(dist, "demo", ".pi-ext-build.json"),
'{"files":["index.js","package.json"]}\n',
);
writeFileSync(
join(dist, "demo", "package.json"),
'{"name":"@bugabinga/pi-ext-demo","private":true}\n',
);
return { root, dist, release: join(root, "release") };
}
afterEach(() => {
for (const path of trash.splice(0))
rmSync(path, { recursive: true, force: true });
});
describe("git consumer smoke", () => {
it("installs from the Git delivery and runs a tool without repository source", () => {
const input = eligibleRepo();
snapshot("demo", {
root: input.root,
distRoot: input.dist,
releaseRoot: input.release,
});
const delivery = deliverGit("demo", {
distRoot: input.dist,
releaseRoot: input.release,
});
trash.push(delivery);
// Model a consumer install: clone the delivery into a fresh directory
// that contains no repository source or toolchain.
const consumer = mkdtempSync(join(tmpdir(), "pi-ext-smoke-consumer-"));
trash.push(consumer);
const clone = spawnSync("git", ["clone", "-q", delivery, consumer], {
encoding: "utf8",
});
expect(clone.status, clone.stderr).toBe(0);
// The consumer carries only delivered bytes, never repository source.
expect(existsSync(join(consumer, "extensions"))).toBe(false);
expect(existsSync(join(consumer, "index.js"))).toBe(true);
// Model an independently installed Pi available to the consumer without
// vendoring it into the delivered package (ESM ignores NODE_PATH).
symlinkSync(
join(ROOT, "node_modules"),
join(consumer, "node_modules"),
process.platform === "win32" ? "junction" : "dir",
);
const result = spawnSync(
process.execPath,
["--no-warnings", "verify-package.mjs", "scenario.mjs"],
{
cwd: consumer,
encoding: "utf8",
env: {
...process.env,
PI_OFFLINE: "1",
PI_CODING_AGENT_DIR: join(consumer, ".agent"),
},
timeout: 120_000,
},
);
expect(result.stdout + result.stderr).toContain(
"PI_EXT_STANDALONE_VERIFY=ok",
);
expect(result.status, result.stderr).toBe(0);
}, 150_000);
});