repositories / pi-ext
pi-ext
bugabingas pi extensions
owned by admin
scripts/codegen.test.ts
Rawimport {
mkdirSync,
mkdtempSync,
readFileSync,
rmSync,
symlinkSync,
writeFileSync,
} from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { expect, it } from "vitest";
import {
checkGenerated,
expectedGenerated,
GENERATED_MARKER,
generate,
} from "./codegen.mjs";
function withTemp(fn: (root: string) => void) {
const root = mkdtempSync(join(tmpdir(), "pi-ext-codegen-"));
try {
fn(root);
} finally {
rmSync(root, { recursive: true, force: true });
}
}
function extension(root: string, slug: string) {
const dir = join(root, "extensions", slug);
mkdirSync(dir, { recursive: true });
writeFileSync(join(dir, "package.json"), "{}\n", "utf8");
return dir;
}
function targets(root: string, value: object) {
const dir = join(root, "scripts", "codegen");
mkdirSync(dir, { recursive: true });
writeFileSync(join(dir, "targets.json"), JSON.stringify(value), "utf8");
}
function template(
root: string,
text = "export const slug = '{{extension-slug}}';\n",
) {
const path = join(root, "scripts", "codegen", "fixture.ts");
mkdirSync(join(path, ".."), { recursive: true });
writeFileSync(path, text, "utf8");
return "scripts/codegen/fixture.ts";
}
it("selects every package extension for all", () => {
withTemp((root) => {
extension(root, "alpha");
extension(root, "beta");
const source = template(root);
targets(root, {
targets: [
{
name: "fixture",
template: source,
output: "extensions/{{extension-slug}}/generated.ts",
extensions: "all",
},
],
});
generate(root);
for (const slug of ["alpha", "beta"]) {
const output = readFileSync(
join(root, "extensions", slug, "generated.ts"),
"utf8",
);
expect(output).toMatch(
/^\/\/ Generated by scripts\/codegen\.mjs\. DO NOT EDIT\.\n\/\/ ={10,}\n\/\/ THIS FILE IS GENERATED\. DO NOT EDIT IT DIRECTLY\./,
);
expect(output).toContain(`// Instead, edit ${source}.`);
expect(output).toContain("// Then run: mise run //:codegen");
expect(output).toMatch(new RegExp(`export const slug = '${slug}';\\n$`));
}
});
});
it("selects listed extension slugs only", () => {
withTemp((root) => {
const alpha = extension(root, "alpha");
extension(root, "beta");
const source = template(root);
targets(root, {
targets: [
{
name: "fixture",
template: source,
output: "extensions/{{extension-slug}}/generated.ts",
extensions: ["alpha"],
},
],
});
generate(root);
expect(checkGenerated(alpha, root)).toEqual([]);
expect(expectedGenerated(root)).toHaveLength(1);
expect(() =>
readFileSync(join(root, "extensions", "beta", "generated.ts")),
).toThrow();
});
});
it("rejects traversal, symlinks, and output collisions", () => {
withTemp((root) => {
const alpha = extension(root, "alpha");
const source = template(root);
targets(root, {
targets: [
{
name: "traversal",
template: source,
output: "../{{extension-slug}}.ts",
extensions: "all",
},
],
});
expect(checkGenerated(alpha, root)[0]).toMatch(/extensions/);
targets(root, {
targets: [
{
name: "wrong-owner",
template: source,
output: "extensions/beta/generated-{{extension-slug}}.ts",
extensions: "all",
},
],
});
expect(checkGenerated(alpha, root)[0]).toMatch(/extensions/);
targets(root, {
targets: [
{
name: "symlink",
template: source,
output: "extensions/{{extension-slug}}/generated.ts",
extensions: "all",
},
],
});
symlinkSync(join(root, "extensions", "alpha"), join(root, "linked"));
writeFileSync(
join(root, "scripts", "codegen", "targets.json"),
JSON.stringify({
targets: [
{
name: "symlink",
template: "linked/fixture.ts",
output: "extensions/{{extension-slug}}/generated.ts",
extensions: "all",
},
],
}),
"utf8",
);
expect(checkGenerated(alpha, root)[0]).toMatch(/symlink/);
targets(root, {
targets: [
{
name: "one",
template: source,
output: "extensions/{{extension-slug}}/generated.ts",
extensions: "all",
},
{
name: "two",
template: source,
output: "extensions/{{extension-slug}}/generated.ts",
extensions: "all",
},
],
});
expect(checkGenerated(alpha, root)[0]).toMatch(/collision/);
});
});
it("rejects unsupported or incomplete placeholders", () => {
withTemp((root) => {
const alpha = extension(root, "alpha");
const source = template(root, 'export const value = "{{unknown}}";\n');
targets(root, {
targets: [
{
name: "fixture",
template: source,
output: "extensions/{{extension-slug}}/generated.ts",
extensions: "all",
},
],
});
expect(checkGenerated(alpha, root)[0]).toMatch(/unsupported placeholder/);
template(root, 'export const value = "{{extension-slug";\n');
expect(checkGenerated(alpha, root)[0]).toMatch(/unsupported placeholder/);
template(root);
targets(root, {
targets: [
{
name: "fixture",
template: source,
output: "extensions/{{extension-slug}}/{{unknown}}.ts",
extensions: "all",
},
],
});
expect(checkGenerated(alpha, root)[0]).toMatch(/unsupported placeholder/);
});
});
it("reports stale output without writing", () => {
withTemp((root) => {
const alpha = extension(root, "alpha");
const source = template(root);
targets(root, {
targets: [
{
name: "fixture",
template: source,
output: "extensions/{{extension-slug}}/generated.ts",
extensions: "all",
},
],
});
generate(root);
const output = join(root, "extensions", "alpha", "generated.ts");
writeFileSync(output, `${GENERATED_MARKER}stale\n`, "utf8");
expect(checkGenerated(alpha, root)).toEqual([
"alpha: stale generated extensions/alpha/generated.ts",
]);
expect(readFileSync(output, "utf8")).toBe(`${GENERATED_MARKER}stale\n`);
});
});
it("refuses to overwrite unmarked output", () => {
withTemp((root) => {
extension(root, "alpha");
const source = template(root);
targets(root, {
targets: [
{
name: "fixture",
template: source,
output: "extensions/{{extension-slug}}/generated.ts",
extensions: "all",
},
],
});
const output = join(root, "extensions", "alpha", "generated.ts");
writeFileSync(output, "manual\n", "utf8");
expect(() => generate(root)).toThrow(/unmarked/);
expect(readFileSync(output, "utf8")).toBe("manual\n");
});
});
it("does nothing without codegen config", () => {
withTemp((root) => {
const alpha = extension(root, "alpha");
expect(expectedGenerated(root)).toEqual([]);
expect(checkGenerated(alpha, root)).toEqual([]);
expect(generate(root)).toEqual([]);
});
});