repositories / pi-ext
pi-ext
bugabingas pi extensions
owned by admin
scripts/build/build.test.ts
Rawimport { spawnSync } from "node:child_process";
import {
existsSync,
mkdirSync,
mkdtempSync,
readdirSync,
readFileSync,
rmSync,
writeFileSync,
} from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterAll, beforeAll, describe, expect, it } from "vitest";
import { buildExtension, ROOT, rootVersion } from "./build.ts";
import { registrationConflicts } from "./conflicts.ts";
import { BUILD_MANIFEST, listFiles, publishPackage } from "./publish.ts";
import { readBuildSpec } from "./spec.ts";
import {
forbiddenImports,
moduleImports,
verifyCollection,
verifyPackage,
} from "./verify.ts";
const PI_PACKAGE_ROOT = join(
ROOT,
"node_modules",
"@earendil-works",
"pi-coding-agent",
);
function temp(prefix: string): string {
return mkdtempSync(join(tmpdir(), prefix));
}
describe("ultra package", () => {
let distRoot: string;
let pkg: string;
beforeAll(async () => {
distRoot = temp("pi-ext-dist-");
pkg = (await buildExtension("ultra", { distRoot })).dir;
}, 60_000);
afterAll(() => {
rmSync(distRoot, { recursive: true, force: true });
});
it("emits a complete self-contained package with declared resources", () => {
const files = listFiles(pkg);
expect(files).toContain("index.js");
expect(files).toContain("dynamic-validator-worker.js");
expect(files).toContain("package.json");
expect(files).toContain("prompts/ultra-authoring/SKILL.md");
expect(files).toContain("workflows/feature.json");
expect(files.some((f) => /^implementation-[a-z0-9]+\.js$/u.test(f))).toBe(
true,
);
expect(files.some((f) => f.includes("/node_modules/"))).toBe(false);
expect(existsSync(join(distRoot, ".staging"))).toBe(false);
const manifest = JSON.parse(
readFileSync(join(pkg, "package.json"), "utf8"),
);
expect(manifest).toMatchObject({
name: "@bugabinga/pi-ext-ultra",
version: rootVersion(),
private: true,
type: "module",
main: "index.js",
pi: { extensions: ["index.js"] },
});
expect(manifest.scripts).toBeUndefined();
expect(manifest.dependencies).toBeUndefined();
expect(Object.keys(manifest.peerDependencies)).toEqual(
expect.arrayContaining(["@earendil-works/pi-coding-agent", "typebox"]),
);
});
it("keeps the lazy runtime out of the entry chunk", () => {
const entry = readFileSync(join(pkg, "index.js"), "utf8");
expect(entry).not.toContain("unknown sub-agent extension");
expect(moduleImports("index.js", entry)).toEqual(
expect.arrayContaining([
expect.stringMatching(/^\.\/implementation-[a-z0-9]+\.js$/u),
]),
);
});
it("imports only Node built-ins, Pi modules, and package-private chunks", () => {
expect(forbiddenImports(pkg)).toEqual([]);
});
it("loads relocated outside the checkout, activates the lazy runtime, and exposes the authoring skill", async () => {
const report = await verifyPackage(pkg, {
userSettings: { ultra: { autoEnable: true } },
});
expect(report.errors).toEqual([]);
expect(report.ok).toBe(true);
expect(report.extensions).toHaveLength(1);
const [ultra] = report.extensions;
expect(ultra.path).not.toContain(ROOT);
expect(ultra.commands).toContain("ultra");
expect(ultra.tools).toContain("run_workflow");
expect(ultra.activeTools).toContain("run_workflow");
expect(report.skills).toEqual([
expect.stringMatching(/\/prompts\/ultra-authoring\/SKILL\.md$/u),
]);
expect(report.skills[0]).not.toContain(ROOT);
}, 120_000);
it("runs the validator worker from a relocated package against the running Pi", () => {
const root = temp("pi-ext-worker-");
try {
const relocated = join(root, "ultra");
spawnSync("cp", ["-r", pkg, relocated]);
const draft = join(root, "draft");
mkdirSync(draft);
writeFileSync(
join(draft, "manifest.json"),
JSON.stringify({ name: "hello-tool", description: "says hello" }),
);
writeFileSync(
join(draft, "index.ts"),
`export default function (pi) {
pi.registerTool({
name: "hello_tool",
label: "Hello",
description: "hello",
parameters: { type: "object", properties: {} },
async execute() {
return { content: [{ type: "text", text: "hi" }], details: {} };
},
});
}
`,
);
const result = spawnSync(
process.execPath,
[
"--no-warnings",
join(relocated, "dynamic-validator-worker.js"),
draft,
],
{
encoding: "utf8",
env: {
...process.env,
ULTRA_PI_PACKAGE_ROOT: PI_PACKAGE_ROOT,
PI_CODING_AGENT_DIR: join(root, "agent"),
},
timeout: 60_000,
},
);
const line = result.stdout
.split("\n")
.find((candidate) => candidate.startsWith("ULTRA_VALIDATION_RESULT="));
expect(line, result.stdout + result.stderr).toBeDefined();
const report = JSON.parse(
(line ?? "").slice("ULTRA_VALIDATION_RESULT=".length),
);
expect(report.diagnostics).toEqual([]);
expect(report.ok).toBe(true);
} finally {
rmSync(root, { recursive: true, force: true });
}
}, 90_000);
});
describe("the-system package", () => {
let distRoot: string;
let pkg: string;
beforeAll(async () => {
distRoot = temp("pi-ext-dist-");
pkg = (await buildExtension("the-system", { distRoot })).dir;
}, 60_000);
afterAll(() => {
rmSync(distRoot, { recursive: true, force: true });
});
it("bundles every third-party dependency and ships skills plus browser assets", () => {
const files = listFiles(pkg);
expect(files).toContain("skills/vertical-slices/SKILL.md");
expect(files).toContain("skills/the-system-mermaid/SKILL.md");
expect(files).toContain("assets/mermaid.min.js");
expect(files).toContain("assets/minisearch.js");
expect(files.some((f) => f.startsWith("node_modules/"))).toBe(false);
expect(forbiddenImports(pkg)).toEqual([]);
const manifest = JSON.parse(
readFileSync(join(pkg, "package.json"), "utf8"),
);
expect(manifest.dependencies).toBeUndefined();
const renderer = files.find((f) =>
/^system-renderer-[a-z0-9]+\.js$/u.test(f),
);
expect(renderer).toBeDefined();
expect(readFileSync(join(pkg, renderer ?? ""), "utf8")).toContain(
"./assets/",
);
});
it("loads relocated with its skills served from the package", async () => {
const report = await verifyPackage(pkg);
expect(report.errors).toEqual([]);
expect(report.ok).toBe(true);
expect(report.extensions[0]?.commands).toContain("system");
expect(report.skills).toEqual([
expect.stringMatching(/\/skills\/vertical-slices\/SKILL\.md$/u),
]);
expect(report.skills[0]).not.toContain(ROOT);
}, 120_000);
});
describe("collection", () => {
it("reports identity, tool, command, flag, and skill conflicts", () => {
expect(
registrationConflicts(
[
{ path: "/a/index.js", tools: ["x"], commands: ["go"], flags: [] },
{ path: "/b/index.js", tools: ["x"], commands: [], flags: ["f"] },
{ path: "/c/index.js", tools: [], commands: ["go"], flags: ["f"] },
],
["@p/one", "@p/two", "@p/one"],
["/a/skills/s/SKILL.md", "/b/skills/s/SKILL.md"],
),
).toEqual([
"command go registered by /a/index.js, /c/index.js",
"flag f registered by /b/index.js, /c/index.js",
"package @p/one registered by @p/one, @p/one",
"skill s registered by /a/skills/s/SKILL.md, /b/skills/s/SKILL.md",
"tool x registered by /a/index.js, /b/index.js",
]);
expect(
registrationConflicts(
[{ path: "/a/index.js", tools: ["x"], commands: ["go"], flags: [] }],
["@p/one"],
["/a/skills/s/SKILL.md"],
),
).toEqual([]);
});
it("loads several generated packages together without conflicts", async () => {
const distRoot = temp("pi-ext-dist-");
try {
const dirs: string[] = [];
for (const name of ["pfui", "footer", "ultra", "nushell"])
dirs.push((await buildExtension(name, { distRoot })).dir);
const report = await verifyCollection(dirs);
expect(report.errors).toEqual([]);
expect(report.conflicts).toEqual([]);
expect(report.ok).toBe(true);
expect(report.extensions.map((e) => e.path.split("/").at(-2))).toEqual([
"pfui",
"footer",
"ultra",
"nushell",
]);
} finally {
rmSync(distRoot, { recursive: true, force: true });
}
}, 180_000);
});
describe("publishPackage", () => {
function stage(root: string, files: Record<string, string>): string {
const staging = join(
root,
`staging-${Math.random().toString(36).slice(2)}`,
);
for (const [name, content] of Object.entries(files)) {
mkdirSync(join(staging, name, ".."), { recursive: true });
writeFileSync(join(staging, name), content);
}
return staging;
}
it("keeps previous chunks for one generation and switches entries last", () => {
const root = temp("pi-ext-publish-");
try {
const target = join(root, "dist", "demo");
publishPackage(
stage(root, {
"index.js": "import './chunk-a.js'",
"chunk-a.js": "a",
"prompts/one.md": "one",
"package.json": "{}",
}),
target,
["index.js"],
);
expect(listFiles(target)).toEqual([
BUILD_MANIFEST,
"chunk-a.js",
"index.js",
"package.json",
"prompts/one.md",
]);
publishPackage(
stage(root, {
"index.js": "import './chunk-b.js'",
"chunk-b.js": "b",
"package.json": "{}",
}),
target,
["index.js"],
);
expect(listFiles(target)).toEqual([
BUILD_MANIFEST,
"chunk-a.js",
"chunk-b.js",
"index.js",
"package.json",
"prompts/one.md",
]);
expect(readFileSync(join(target, "index.js"), "utf8")).toBe(
"import './chunk-b.js'",
);
publishPackage(
stage(root, {
"index.js": "import './chunk-c.js'",
"chunk-c.js": "c",
"package.json": "{}",
}),
target,
["index.js"],
);
expect(listFiles(target)).toEqual([
BUILD_MANIFEST,
"chunk-b.js",
"chunk-c.js",
"index.js",
"package.json",
]);
expect(readdirSync(root).filter((n) => n.startsWith("staging-"))).toEqual(
[],
);
} finally {
rmSync(root, { recursive: true, force: true });
}
});
});
describe("buildExtension", () => {
function fakeRepo(): { root: string; ext: string } {
const root = temp("pi-ext-repo-");
writeFileSync(
join(root, "package.json"),
JSON.stringify({ name: "fake", version: "9.9.9" }),
);
const ext = join(root, "extensions", "demo");
mkdirSync(ext, { recursive: true });
writeFileSync(
join(ext, "package.json"),
JSON.stringify({
name: "@bugabinga/pi-ext-demo",
description: "demo",
license: "MIT",
peerDependencies: { "@earendil-works/pi-coding-agent": "*" },
}),
);
writeFileSync(
join(ext, "index.ts"),
'import { join } from "node:path";\nexport default function demo() { return join("a", "b"); }\n',
);
return { root, ext };
}
it("leaves the previous package intact when a rebuild fails", async () => {
const { root, ext } = fakeRepo();
try {
const built = await buildExtension("demo", { root });
const before = listFiles(built.dir).map((f) => [
f,
readFileSync(join(built.dir, f), "utf8"),
]);
expect(
JSON.parse(readFileSync(join(built.dir, "package.json"), "utf8")),
).toMatchObject({ version: "9.9.9", pi: { extensions: ["index.js"] } });
writeFileSync(join(ext, "index.ts"), 'import "./missing.ts";\n');
await expect(buildExtension("demo", { root })).rejects.toThrow();
const after = listFiles(built.dir).map((f) => [
f,
readFileSync(join(built.dir, f), "utf8"),
]);
expect(after).toEqual(before);
expect(existsSync(join(root, "dist", ".staging"))).toBe(false);
} finally {
rmSync(root, { recursive: true, force: true });
}
});
it("rejects undeclared or missing build declarations", () => {
const { root, ext } = fakeRepo();
try {
writeFileSync(
join(ext, "package.json"),
JSON.stringify({
name: "@bugabinga/pi-ext-demo",
"pi-ext": { resources: ["missing"] },
}),
);
expect(() => readBuildSpec(ext)).toThrow(/missing does not exist/u);
writeFileSync(
join(ext, "package.json"),
JSON.stringify({
name: "@bugabinga/pi-ext-demo",
"pi-ext": { externals: { left: "" } },
}),
);
expect(() => readBuildSpec(ext)).toThrow(/incompatibility reason/u);
writeFileSync(
join(ext, "package.json"),
JSON.stringify({
name: "@bugabinga/pi-ext-demo",
dependencies: { left: "^1" },
"pi-ext": { externals: { left: "native addon" } },
}),
);
expect(readBuildSpec(ext).externals).toEqual({ left: "native addon" });
} finally {
rmSync(root, { recursive: true, force: true });
}
});
});