Something went wrong. Try again.
Shared settings UI and config loader for pi extensions
Something went wrong. Try again.
4.3 kB · 135 lines
TypeScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";import { tmpdir } from "node:os";import { join } from "node:path";import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";import { buildSchemaUrl } from "./schema";
// Mock getAgentDir - will be configured per test via mockReturnValueconst mockGetAgentDir = vi.fn(() => "");vi.mock("@earendil-works/pi-coding-agent", () => ({ getAgentDir: () => mockGetAgentDir(),}));
// Import after mock setupimport { ConfigLoader } from "./config-loader";
describe("buildSchemaUrl", () => { it("builds default URL with schema.json", () => { expect(buildSchemaUrl("@aliou/pi-guardrails", "0.8.0")).toBe( "https://unpkg.com/@aliou/pi-guardrails@0.8.0/schema.json", ); });
it("accepts custom schema path", () => { expect(buildSchemaUrl("my-pkg", "1.0.0", "schemas/config.json")).toBe( "https://unpkg.com/my-pkg@1.0.0/schemas/config.json", ); });
it("accepts custom base URL", () => { expect( buildSchemaUrl("my-pkg", "1.0.0", { baseUrl: "https://cdn.example.com/npm/", schemaPath: "schemas/config.json", }), ).toBe("https://cdn.example.com/npm/my-pkg@1.0.0/schemas/config.json"); });
it("accepts custom URL template", () => { expect( buildSchemaUrl("aliou/pi-linear", "v1.0.0", { template: "https://raw.githubusercontent.com/{packageName}/{version}/{schemaPath}", }), ).toBe( "https://raw.githubusercontent.com/aliou/pi-linear/v1.0.0/schema.json", ); });});
describe("ConfigLoader $schema round-trip", () => { let tempDir: string;
beforeEach(async () => { tempDir = await mkdtemp(join(tmpdir(), "schema-test-")); await mkdir(join(tempDir, "extensions"), { recursive: true }); mockGetAgentDir.mockReturnValue(tempDir); });
afterEach(async () => { await rm(tempDir, { recursive: true, force: true }); });
it("injects $schema on write and strips it on read", async () => { const schemaUrl = "https://unpkg.com/@aliou/test@1.0.0/schema.json";
const loader = new ConfigLoader<{ name?: string }, { name: string }>( "schema-round-trip", { name: "default" }, { scopes: ["global"], schemaUrl, }, );
await loader.save("global", { name: "hello" });
// Verify the raw file on disk has $schema as the first key const filePath = join(tempDir, "extensions/schema-round-trip.json"); const raw = await readFile(filePath, "utf-8"); const parsed = JSON.parse(raw);
expect(parsed.$schema).toBe(schemaUrl); expect(parsed.name).toBe("hello"); expect(Object.keys(parsed)[0]).toBe("$schema");
// Verify that getRawConfig does NOT contain $schema const rawConfig = loader.getRawConfig("global"); expect(rawConfig).toEqual({ name: "hello" }); expect((rawConfig as Record<string, unknown>).$schema).toBeUndefined(); });
it("writes without $schema when schemaUrl is not set", async () => { const loader = new ConfigLoader<{ name?: string }, { name: string }>( "no-schema-test", { name: "default" }, { scopes: ["global"] }, );
await loader.save("global", { name: "world" });
const filePath = join(tempDir, "extensions/no-schema-test.json"); const raw = await readFile(filePath, "utf-8"); const parsed = JSON.parse(raw);
expect(parsed.$schema).toBeUndefined(); expect(parsed.name).toBe("world"); });
it("strips $schema from existing files on load", async () => { // Write a file with $schema manually const filePath = join(tempDir, "extensions/strip-test.json"); await writeFile( filePath, JSON.stringify({ $schema: "https://example.com/schema.json", name: "existing", }), );
const loader = new ConfigLoader<{ name?: string }, { name: string }>( "strip-test", { name: "default" }, { scopes: ["global"] }, );
await loader.load();
const rawConfig = loader.getRawConfig("global"); expect(rawConfig).toEqual({ name: "existing" }); expect((rawConfig as Record<string, unknown>).$schema).toBeUndefined();
const resolved = loader.getConfig(); expect(resolved.name).toBe("existing"); });});