Track HiveGrid Cloud planning reference
This commit is contained in:
1 parent
b3d5ea6764
commit
e341c5938a
5784 files changed
+1104929
-3
No files matched your search
@@ -0,0 +1,148 @@
|
||||
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<string, unknown>) {
|
||||
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<string, unknown> = {}) {
|
||||
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.",
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,218 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import pluginCandidates from "../../../docs/feature-manifests/plugin-candidates.json";
|
||||
import { validateFeatureManifestShape } from "../src/index";
|
||||
|
||||
function manifestWith(overrides: Record<string, unknown>) {
|
||||
return structuredClone({ ...pluginCandidates, ...overrides });
|
||||
}
|
||||
|
||||
describe("validateFeatureManifestShape", () => {
|
||||
it("accepts the plugin candidates fixture", () => {
|
||||
const result = validateFeatureManifestShape(pluginCandidates, { path: "docs/feature-manifests/plugin-candidates.json" });
|
||||
|
||||
expect(result.ok).toBe(true);
|
||||
expect(result.manifest?.id).toBe("plugin-candidates");
|
||||
expect(result.diagnostics).toEqual([]);
|
||||
});
|
||||
|
||||
it("reports invalid classification enum values with manifest context", () => {
|
||||
const result = validateFeatureManifestShape(manifestWith({ classification: "optional" }));
|
||||
|
||||
expect(result.ok).toBe(false);
|
||||
expect(result.diagnostics).toContainEqual({
|
||||
code: "invalid_enum",
|
||||
manifestId: "plugin-candidates",
|
||||
path: "classification",
|
||||
message: "classification must be one of: core, plugin-candidate, remove-candidate, mixed.",
|
||||
});
|
||||
});
|
||||
|
||||
it("reports missing integration worker sections", () => {
|
||||
const integration = structuredClone(pluginCandidates.integration) as Record<string, unknown>;
|
||||
delete integration.workers;
|
||||
const result = validateFeatureManifestShape(manifestWith({ integration }));
|
||||
|
||||
expect(result.ok).toBe(false);
|
||||
expect(result.diagnostics).toContainEqual({
|
||||
code: "missing_required",
|
||||
manifestId: "plugin-candidates",
|
||||
path: "integration.workers",
|
||||
message: "integration.workers is required.",
|
||||
});
|
||||
});
|
||||
|
||||
it("reports non-array dependencies", () => {
|
||||
const result = validateFeatureManifestShape(manifestWith({ dependencies: "core" }));
|
||||
|
||||
expect(result.ok).toBe(false);
|
||||
expect(result.diagnostics).toContainEqual({
|
||||
code: "invalid_type",
|
||||
manifestId: "plugin-candidates",
|
||||
path: "dependencies",
|
||||
message: "dependencies must be an array.",
|
||||
});
|
||||
});
|
||||
|
||||
it("reports invalid component ids", () => {
|
||||
const components = structuredClone(pluginCandidates.components);
|
||||
components[0] = { ...components[0], id: "CRM" };
|
||||
const result = validateFeatureManifestShape(manifestWith({ components }));
|
||||
|
||||
expect(result.ok).toBe(false);
|
||||
expect(result.diagnostics).toContainEqual({
|
||||
code: "invalid_format",
|
||||
manifestId: "plugin-candidates",
|
||||
path: "components.0.id",
|
||||
message: "components.0.id has an invalid format.",
|
||||
});
|
||||
});
|
||||
|
||||
it("reports invalid optional component collections and notes", () => {
|
||||
const components = [false, { ...pluginCandidates.components[0], notes: ["not prose"] }];
|
||||
const result = validateFeatureManifestShape(manifestWith({ components }));
|
||||
|
||||
expect(result.ok).toBe(false);
|
||||
expect(result.diagnostics).toContainEqual({
|
||||
code: "invalid_item",
|
||||
manifestId: "plugin-candidates",
|
||||
path: "components.0",
|
||||
message: "components.0 must be an object.",
|
||||
});
|
||||
expect(result.diagnostics).toContainEqual({
|
||||
code: "invalid_type",
|
||||
manifestId: "plugin-candidates",
|
||||
path: "components.1.notes",
|
||||
message: "components.1.notes must be a string.",
|
||||
});
|
||||
});
|
||||
|
||||
it("reports invalid nested integration section types", () => {
|
||||
const integration = {
|
||||
...pluginCandidates.integration,
|
||||
surfaces: [],
|
||||
configuration: "env",
|
||||
workers: { ...pluginCandidates.integration.workers, required: "yes", queues: ["mail", 7] },
|
||||
};
|
||||
const result = validateFeatureManifestShape(manifestWith({ integration }));
|
||||
|
||||
expect(result.ok).toBe(false);
|
||||
expect(result.diagnostics).toContainEqual({
|
||||
code: "invalid_type",
|
||||
manifestId: "plugin-candidates",
|
||||
path: "integration.surfaces",
|
||||
message: "integration.surfaces must be an object.",
|
||||
});
|
||||
expect(result.diagnostics).toContainEqual({
|
||||
code: "invalid_type",
|
||||
manifestId: "plugin-candidates",
|
||||
path: "integration.configuration",
|
||||
message: "integration.configuration must be an object.",
|
||||
});
|
||||
expect(result.diagnostics).toContainEqual({
|
||||
code: "invalid_type",
|
||||
manifestId: "plugin-candidates",
|
||||
path: "integration.workers.required",
|
||||
message: "integration.workers.required must be a boolean.",
|
||||
});
|
||||
expect(result.diagnostics).toContainEqual({
|
||||
code: "invalid_item",
|
||||
manifestId: "plugin-candidates",
|
||||
path: "integration.workers.queues.1",
|
||||
message: "integration.workers.queues.1 must be a string.",
|
||||
});
|
||||
});
|
||||
|
||||
it("reports invalid top-level constants, empty strings, and dependency tokens", () => {
|
||||
const result = validateFeatureManifestShape(manifestWith({ schemaVersion: "2.0", title: "", dependencies: ["CRM"] }));
|
||||
|
||||
expect(result.ok).toBe(false);
|
||||
expect(result.diagnostics).toContainEqual({
|
||||
code: "invalid_enum",
|
||||
manifestId: "plugin-candidates",
|
||||
path: "schemaVersion",
|
||||
message: "schemaVersion must be 1.0.",
|
||||
});
|
||||
expect(result.diagnostics).toContainEqual({
|
||||
code: "invalid_format",
|
||||
manifestId: "plugin-candidates",
|
||||
path: "title",
|
||||
message: "title must not be empty.",
|
||||
});
|
||||
expect(result.diagnostics).toContainEqual({
|
||||
code: "invalid_format",
|
||||
manifestId: "plugin-candidates",
|
||||
path: "dependencies.0",
|
||||
message: "dependencies.0 has an invalid format.",
|
||||
});
|
||||
});
|
||||
|
||||
it("uses context manifest ids when the input id is empty", () => {
|
||||
const result = validateFeatureManifestShape(manifestWith({ id: "" }), { manifestId: "fixture-id" });
|
||||
|
||||
expect(result.ok).toBe(false);
|
||||
expect(result.diagnostics).toContainEqual({
|
||||
code: "invalid_format",
|
||||
manifestId: "fixture-id",
|
||||
path: "id",
|
||||
message: "id has an invalid format.",
|
||||
});
|
||||
});
|
||||
|
||||
it("reports invalid lastReviewed dates", () => {
|
||||
const validation = { ...pluginCandidates.validation, lastReviewed: "2026-99-05" };
|
||||
const result = validateFeatureManifestShape(manifestWith({ validation }));
|
||||
|
||||
expect(result.ok).toBe(false);
|
||||
expect(result.diagnostics).toContainEqual({
|
||||
code: "invalid_format",
|
||||
manifestId: "plugin-candidates",
|
||||
path: "validation.lastReviewed",
|
||||
message: "validation.lastReviewed must be an ISO date in YYYY-MM-DD format.",
|
||||
});
|
||||
});
|
||||
|
||||
it("rejects prose in permission and audit-event token arrays", () => {
|
||||
const integration = {
|
||||
...pluginCandidates.integration,
|
||||
permissions: ["admin.email.* when settings are exposed"],
|
||||
auditEvents: ["Provider key updated"],
|
||||
};
|
||||
const result = validateFeatureManifestShape(manifestWith({ integration }));
|
||||
|
||||
expect(result.ok).toBe(false);
|
||||
expect(result.diagnostics).toContainEqual({
|
||||
code: "invalid_format",
|
||||
manifestId: "plugin-candidates",
|
||||
path: "integration.permissions.0",
|
||||
message: "integration.permissions.0 has an invalid format.",
|
||||
});
|
||||
expect(result.diagnostics).toContainEqual({
|
||||
code: "invalid_format",
|
||||
manifestId: "plugin-candidates",
|
||||
path: "integration.auditEvents.0",
|
||||
message: "integration.auditEvents.0 has an invalid format.",
|
||||
});
|
||||
});
|
||||
|
||||
it("accepts wildcard and camel-case permission tokens plus dot-separated audit events", () => {
|
||||
const integration = {
|
||||
...pluginCandidates.integration,
|
||||
permissions: ["admin.emails.*", "app.settings.apiKeys.view"],
|
||||
auditEvents: ["mail.provider.updated", "admin.ai.provider_key.updated"],
|
||||
};
|
||||
const result = validateFeatureManifestShape(manifestWith({ integration }));
|
||||
|
||||
expect(result.ok).toBe(true);
|
||||
});
|
||||
|
||||
it("returns diagnostics instead of throwing for non-object input", () => {
|
||||
const result = validateFeatureManifestShape(null, { manifestId: "bad", path: "fixture" });
|
||||
|
||||
expect(result).toEqual({
|
||||
ok: false,
|
||||
manifest: null,
|
||||
diagnostics: [{ code: "invalid_type", manifestId: "bad", path: "fixture", message: "fixture must be an object." }],
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user