import { mkdirSync, mkdtempSync, readFileSync, rmSync, symlinkSync, writeFileSync, } from "node:fs"; import { join } from "node:path"; import { afterEach, describe, expect, it } from "vitest"; import { ROOT } from "./build.ts"; import { archivePackage, collectionManifest, treeDigest } from "./release.ts"; const roots: string[] = []; afterEach(() => { for (const root of roots.splice(0)) rmSync(root, { recursive: true, force: true }); }); describe("repository release", () => { it("exposes every extension and resource from the root installation unit", () => { expect( collectionManifest("0.1.0", [ { name: "a", pi: { extensions: ["index.js"], skills: ["skills"] } }, { name: "b", pi: { extensions: ["entry.js"], prompts: ["prompts"] } }, ]), ).toEqual({ name: "pi-ext", version: "0.1.0", private: true, type: "module", pi: { extensions: ["packages/a/index.js", "packages/b/entry.js"], skills: ["packages/a/skills"], prompts: ["packages/b/prompts"], }, }); }); it("archives exact package bytes and records their SHA-256", () => { const scratch = join(ROOT, ".pi", "tmp"); mkdirSync(scratch, { recursive: true }); const root = mkdtempSync(join(scratch, "release-test-")); roots.push(root); const pkg = join(root, "package"); mkdirSync(pkg); writeFileSync(join(pkg, "index.js"), "export default () => {};\n"); symlinkSync("index.js", join(pkg, "entry.js")); const digest = treeDigest(pkg); const archive = join(root, "package.tgz"); expect(archivePackage(pkg, archive, root)).toMatch(/^[a-f0-9]{64}$/u); expect(readFileSync(archive).length).toBeGreaterThan(0); expect(treeDigest(pkg)).toBe(digest); writeFileSync(join(pkg, "index.js"), "changed\n"); expect(treeDigest(pkg)).not.toBe(digest); writeFileSync(join(root, "outside.js"), "outside\n"); symlinkSync("../outside.js", join(pkg, "escape.js")); expect(() => treeDigest(pkg)).toThrow(/Symlink escapes package/u); }); });