feat: add module schema discovery and validation
CI / verify (pull_request) Successful in 24s
CI / infrastructure-smoke (pull_request) Successful in 20s

This commit is contained in:
ekklesia
2026-07-13 21:35:21 -05:00
parent 2465d9c1f7
commit 80b56b72b1
31 changed files with 1584 additions and 34 deletions
+88 -4
View File
@@ -397,12 +397,17 @@ describe("module discovery", () => {
await createModule(modulesDirectory, "missing-server", {
manifest: {
...manifestFor("missing-server"),
entrypoints: { server: "./dist/server/missing.js" },
entrypoints: {
server: "./dist/server/missing.js",
schema: "./dist/schema/missing.js",
},
},
});
let error = await discoveryError(modulesDirectory);
expect(issueCodes(error)).toContain("MODULE_ENTRYPOINT_MISSING");
expect(error.issues.filter((issue) => issue.code === "MODULE_ENTRYPOINT_MISSING")).toHaveLength(
2,
);
error = await discoveryError(modulesDirectory, { platformVersion: "invalid" });
expect(issueCodes(error)).toContain("DISCOVERY_CONFIGURATION_INVALID");
@@ -427,21 +432,100 @@ describe("module discovery", () => {
);
});
it.each([
"./src/schema/index.ts",
"./dist/schema/index.cjs",
])("rejects non-ESM schema entrypoint %s", async (schemaEntrypoint) => {
const modulesDirectory = await createModulesDirectory();
await createModule(modulesDirectory, "schema-source", {
manifest: {
...manifestFor("schema-source"),
entrypoints: {
server: "./dist/server/index.js",
schema: schemaEntrypoint,
},
},
});
const error = await discoveryError(modulesDirectory);
expect(error.issues).toContainEqual(
expect.objectContaining({
code: "MODULE_ENTRYPOINT_INVALID",
message: "schema must reference compiled ESM JavaScript with a .js or .mjs extension.",
path: "entrypoints.schema",
}),
);
});
it("accepts an optional compiled ESM schema entrypoint", async () => {
const modulesDirectory = await createModulesDirectory();
await createModule(modulesDirectory, "compiled-schema", {
manifest: {
...manifestFor("compiled-schema"),
entrypoints: {
server: "./dist/server/index.js",
schema: "./dist/schema/index.mjs",
},
},
});
const schemaDirectory = path.join(modulesDirectory, "compiled-schema", "dist/schema");
await mkdir(schemaDirectory, { recursive: true });
await writeFile(path.join(schemaDirectory, "index.mjs"), "export default {};\n");
const modules = await discoverModules({
moduleSdkVersion: "0.1.0",
modulesDirectory,
platformVersion: "0.1.0",
});
expect(modules[0]?.entrypoints.schema).toBe(path.join(schemaDirectory, "index.mjs"));
});
it("requires executable entrypoints to be files and migrations to be a directory", async () => {
const modulesDirectory = await createModulesDirectory();
await createModule(modulesDirectory, "wrong-entrypoint-kind", {
manifest: {
...manifestFor("wrong-entrypoint-kind"),
entrypoints: {
server: "./dist/server",
server: "./dist/server.js",
schema: "./dist/schema.js",
migrations: "./dist/server/index.js",
},
},
});
await Promise.all([
mkdir(path.join(modulesDirectory, "wrong-entrypoint-kind", "dist/server.js")),
mkdir(path.join(modulesDirectory, "wrong-entrypoint-kind", "dist/schema.js")),
]);
const error = await discoveryError(modulesDirectory);
expect(error.issues.filter((issue) => issue.code === "MODULE_ENTRYPOINT_INVALID")).toHaveLength(
2,
3,
);
});
it("rejects schema entrypoint links that escape the module directory", async () => {
const modulesDirectory = await createModulesDirectory();
await createModule(modulesDirectory, "linked-schema", {
manifest: {
...manifestFor("linked-schema"),
entrypoints: {
server: "./dist/server/index.js",
schema: "./dist/schema/index.js",
},
},
});
const schemaDirectory = path.join(modulesDirectory, "linked-schema", "dist/schema");
const externalSchema = path.join(modulesDirectory, "external-schema.js");
await mkdir(schemaDirectory, { recursive: true });
await writeFile(externalSchema, "export default {};\n");
await symlink("../../../external-schema.js", path.join(schemaDirectory, "index.js"));
const error = await discoveryError(modulesDirectory);
expect(error.issues).toContainEqual(
expect.objectContaining({
code: "MODULE_ENTRYPOINT_INVALID",
path: "entrypoints.schema",
}),
);
});