import { mkdtemp, mkdir, writeFile } from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import { describe, expect, it } from "vitest"; import pluginCandidates from "../../../docs/feature-manifests/plugin-candidates.json"; import { loadFeatureManifests } from "../src/manifest-loader"; function manifestWith(overrides: Record) { return structuredClone({ ...pluginCandidates, ...overrides }); } async function makeFixtureDirectory() { const rootDir = await mkdtemp(path.join(os.tmpdir(), "feature-manifests-")); const manifestDir = path.join(rootDir, "docs", "feature-manifests"); await mkdir(manifestDir, { recursive: true }); return { rootDir, manifestDir }; } async function writeManifest(manifestDir: string, fileName: string, overrides: Record = {}) { const manifest = manifestWith(overrides); await writeFile(path.join(manifestDir, fileName), `${JSON.stringify(manifest, null, 2)}\n`, "utf8"); return manifest; } describe("loadFeatureManifests", () => { it("loads valid docs manifests in deterministic order and skips the schema by default", async () => { const result = await loadFeatureManifests(); expect(result.ok).toBe(true); expect(result.diagnostics).toEqual([]); expect(result.manifests.map((manifest) => manifest.id)).toContain("plugin-candidates"); expect(result.manifestFiles.map((file) => file.fileName)).toEqual([...result.manifestFiles.map((file) => file.fileName)].sort((a, b) => a.localeCompare(b))); expect(result.manifestFiles.some((file) => file.fileName === "schema.json")).toBe(false); }); it("reports a missing manifest directory", async () => { const rootDir = await mkdtemp(path.join(os.tmpdir(), "feature-manifests-missing-")); const result = await loadFeatureManifests({ rootDir }); expect(result).toMatchObject({ ok: false, manifests: [], manifestFiles: [] }); expect(result.diagnostics[0]).toMatchObject({ code: "missing_directory", manifestId: null, path: path.join(rootDir, "docs", "feature-manifests") }); }); it("preserves sibling valid manifests when one file contains malformed JSON", async () => { const { rootDir, manifestDir } = await makeFixtureDirectory(); await writeManifest(manifestDir, "plugin-candidates.json", { id: "plugin-candidates", dependencies: [] }); await writeFile(path.join(manifestDir, "broken.json"), "{ not json", "utf8"); const result = await loadFeatureManifests({ rootDir }); expect(result.ok).toBe(false); expect(result.manifests.map((manifest) => manifest.id)).toEqual(["plugin-candidates"]); expect(result.diagnostics).toContainEqual(expect.objectContaining({ code: "invalid_json", manifestId: null, path: path.join(manifestDir, "broken.json") })); }); it("loads from an absolute manifest directory", async () => { const { manifestDir } = await makeFixtureDirectory(); await writeManifest(manifestDir, "plugin-candidates.json", { id: "plugin-candidates", dependencies: [] }); const result = await loadFeatureManifests({ rootDir: "/ignored/root", manifestDir }); expect(result.ok).toBe(true); expect(result.manifests.map((manifest) => manifest.id)).toEqual(["plugin-candidates"]); expect(result.manifestFiles[0]).toMatchObject({ fileName: "plugin-candidates.json", filePath: path.join(manifestDir, "plugin-candidates.json"), manifestId: "plugin-candidates", diagnostics: [], }); }); it("can include schema-like json files when requested", async () => { const { rootDir, manifestDir } = await makeFixtureDirectory(); await writeManifest(manifestDir, "schema.json", { id: "schema-file", dependencies: [] }); await writeManifest(manifestDir, "plugin-candidates.json", { id: "plugin-candidates", dependencies: [] }); const result = await loadFeatureManifests({ rootDir, includeSchema: true }); expect(result.ok).toBe(true); expect(result.manifestFiles.map((file) => file.fileName)).toEqual(["plugin-candidates.json", "schema.json"]); expect(result.manifests.map((manifest) => manifest.id)).toEqual(["plugin-candidates", "schema-file"]); }); it("uses custom schema file names for the default skip rule", async () => { const { rootDir, manifestDir } = await makeFixtureDirectory(); await writeManifest(manifestDir, "manifest.schema.json", { id: "custom-schema", dependencies: [] }); await writeManifest(manifestDir, "schema.json", { id: "ordinary-schema-name", dependencies: [] }); const result = await loadFeatureManifests({ rootDir, schemaFileName: "manifest.schema.json" }); expect(result.ok).toBe(true); expect(result.manifestFiles.map((file) => file.fileName)).toEqual(["schema.json"]); expect(result.manifests.map((manifest) => manifest.id)).toEqual(["ordinary-schema-name"]); }); it("keeps manifest ids null for non-object json payloads", async () => { const { rootDir, manifestDir } = await makeFixtureDirectory(); const filePath = path.join(manifestDir, "array.json"); await writeFile(filePath, "[]\n", "utf8"); const result = await loadFeatureManifests({ rootDir }); expect(result.ok).toBe(false); expect(result.manifests).toEqual([]); expect(result.manifestFiles).toEqual([ { fileName: "array.json", filePath, manifestId: null, manifest: null, diagnostics: [{ code: "invalid_type", manifestId: null, path: `${filePath}#${filePath}`, message: `${filePath} must be an object.` }], }, ]); }); it("reports invalid manifest shape diagnostics with source file metadata", async () => { const { rootDir, manifestDir } = await makeFixtureDirectory(); await writeManifest(manifestDir, "invalid-shape.json", { id: "invalid-shape", classification: "optional", dependencies: [] }); const result = await loadFeatureManifests({ rootDir }); expect(result.ok).toBe(false); expect(result.manifests).toEqual([]); expect(result.diagnostics).toContainEqual({ code: "invalid_enum", manifestId: "invalid-shape", path: `${path.join(manifestDir, "invalid-shape.json")}#classification`, message: "classification must be one of: core, plugin-candidate, remove-candidate, mixed.", }); }); it("reports missing dependency references after loading valid manifests", async () => { const { rootDir, manifestDir } = await makeFixtureDirectory(); await writeManifest(manifestDir, "plugin-candidates.json", { id: "plugin-candidates", dependencies: ["missing-feature"] }); const result = await loadFeatureManifests({ rootDir }); expect(result.ok).toBe(false); expect(result.manifests.map((manifest) => manifest.id)).toEqual(["plugin-candidates"]); expect(result.diagnostics).toContainEqual({ code: "missing_dependency", manifestId: "plugin-candidates", path: `${path.join(manifestDir, "plugin-candidates.json")}#dependencies`, message: "plugin-candidates depends on missing feature manifest missing-feature.", }); }); });