Luigit
repositories / pi-ext

pi-ext

bugabingas pi extensions

owned by admin

scripts/release/smoke.test.ts

Raw
import { 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);
});