test: generate the external module fixture instead of copying ignored build output
CI / verify (pull_request) Successful in 52s
CI / infrastructure-smoke (pull_request) Successful in 35s
CI / migration-smoke (pull_request) Successful in 1m48s
CI / image-build (pull_request) Successful in 28s
CI / deploy-smoke (same-origin) (pull_request) Successful in 41s
CI / deploy-smoke (split) (pull_request) Successful in 1m37s
CI / verify (pull_request) Successful in 52s
CI / infrastructure-smoke (pull_request) Successful in 35s
CI / migration-smoke (pull_request) Successful in 1m48s
CI / image-build (pull_request) Successful in 28s
CI / deploy-smoke (same-origin) (pull_request) Successful in 41s
CI / deploy-smoke (split) (pull_request) Successful in 1m37s
The test proving that migrations of a module installed outside the repository are discovered, while Core's come from the platform, copied templates/module including dist/. dist/ is gitignored and never committed. CI runs `pnpm test` before any build of the template, where only the template's own UI test may have written dist/ui, dist/display, and dist/contributions, so discovery failed with MODULE_ENTRYPOINT_MISSING for ./dist/server/index.js and ./dist/schema/index.js (and, had the UI test not run yet, the copy itself would have failed). Locally a previous `pnpm build` had left dist/ complete. The test now generates a minimal valid installed module in a temporary directory with createMigrationModule, moved unchanged from the migration discovery test into core/tests/support so both share it. It depends on no build output, ignored file, or node_modules. It also asserts more precisely: the module directory is outside the repository, Core's migrations match the platform's own core/drizzle inventory by identity and checksum, and the module's migration matches the SQL that was installed. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
e36b70d8d4
commit
f122e910fc
@@ -0,0 +1,171 @@
|
||||
import { mkdir, writeFile } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
|
||||
/**
|
||||
* A minimal, valid installed module written into a directory the caller owns: `package.json`,
|
||||
* `ekklesia.module.json`, a compiled-ESM server stub at `dist/server/index.js`, and, unless
|
||||
* disabled, a native Drizzle migration directory (journal, `_ekklesia.json` policy, snapshot chain,
|
||||
* and SQL) that passes Core's migration discovery.
|
||||
*
|
||||
* Everything is generated here, from nothing in the repository: no build output, no ignored file,
|
||||
* and no `node_modules`. Migration discovery imports no module code, so the stub is never run.
|
||||
*/
|
||||
|
||||
export const originSnapshotId = "00000000-0000-0000-0000-000000000000";
|
||||
|
||||
export interface MigrationFixture {
|
||||
readonly breakpoints?: boolean;
|
||||
readonly destructive?:
|
||||
| { readonly mode: "none" }
|
||||
| { readonly mode: "approval-required"; readonly reason: string };
|
||||
readonly snapshotId?: string;
|
||||
readonly sql: string | Uint8Array;
|
||||
readonly tag: string;
|
||||
readonly timestamp?: number;
|
||||
}
|
||||
|
||||
export interface MigrationModuleFixture {
|
||||
readonly dependencies?: Record<string, unknown>;
|
||||
readonly manifest?: Record<string, unknown>;
|
||||
readonly migrations?: readonly MigrationFixture[] | false;
|
||||
readonly schemaSource?: string;
|
||||
readonly serverSource?: string;
|
||||
}
|
||||
|
||||
export interface CreatedMigrationModule {
|
||||
readonly migrationDirectory?: string;
|
||||
readonly moduleDirectory: string;
|
||||
}
|
||||
|
||||
export function snapshotId(index: number): string {
|
||||
return `10000000-0000-4000-8000-${(index + 1).toString(16).padStart(12, "0")}`;
|
||||
}
|
||||
|
||||
export function manifestFor(
|
||||
id: string,
|
||||
options: {
|
||||
readonly dependencies?: Record<string, unknown>;
|
||||
readonly migrations?: boolean;
|
||||
readonly schema?: boolean;
|
||||
} = {},
|
||||
): Record<string, unknown> {
|
||||
return {
|
||||
manifestVersion: 7,
|
||||
id,
|
||||
name: `${id} module`,
|
||||
version: "0.1.0",
|
||||
compatibility: { platform: "^0.1.0", moduleSdk: "^0.1.0" },
|
||||
...(options.dependencies ? { dependencies: options.dependencies } : {}),
|
||||
capabilities: [],
|
||||
permissions: [],
|
||||
entrypoints: {
|
||||
server: "./dist/server/index.js",
|
||||
...(options.schema ? { schema: "./dist/schema/index.js" } : {}),
|
||||
...(options.migrations === false ? {} : { migrations: "./drizzle" }),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function defaultMigrations(id: string): readonly MigrationFixture[] {
|
||||
return [
|
||||
{
|
||||
sql: `CREATE TABLE "${id.replaceAll("-", "_")}_records" ("id" integer);\n`,
|
||||
tag: "0000_initial",
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export async function createMigrationModule(
|
||||
modulesDirectory: string,
|
||||
id: string,
|
||||
fixture: MigrationModuleFixture = {},
|
||||
): Promise<CreatedMigrationModule> {
|
||||
const migrations =
|
||||
fixture.migrations === false ? undefined : (fixture.migrations ?? defaultMigrations(id));
|
||||
const moduleDirectory = path.join(modulesDirectory, id);
|
||||
const migrationDirectory = migrations ? path.join(moduleDirectory, "drizzle") : undefined;
|
||||
await mkdir(path.join(moduleDirectory, "dist/server"), { recursive: true });
|
||||
if (fixture.schemaSource !== undefined) {
|
||||
await mkdir(path.join(moduleDirectory, "dist/schema"), { recursive: true });
|
||||
}
|
||||
if (migrationDirectory) {
|
||||
await mkdir(path.join(migrationDirectory, "meta"), { recursive: true });
|
||||
}
|
||||
|
||||
await Promise.all([
|
||||
writeFile(
|
||||
path.join(moduleDirectory, "package.json"),
|
||||
JSON.stringify({
|
||||
name: `@test/module-${id}`,
|
||||
version: "0.1.0",
|
||||
type: "module",
|
||||
ekklesia: { layer: "module" },
|
||||
}),
|
||||
),
|
||||
writeFile(
|
||||
path.join(moduleDirectory, "ekklesia.module.json"),
|
||||
JSON.stringify(
|
||||
fixture.manifest ??
|
||||
manifestFor(id, {
|
||||
...(fixture.dependencies ? { dependencies: fixture.dependencies } : {}),
|
||||
migrations: migrations !== undefined,
|
||||
schema: fixture.schemaSource !== undefined,
|
||||
}),
|
||||
),
|
||||
),
|
||||
writeFile(
|
||||
path.join(moduleDirectory, "dist/server/index.js"),
|
||||
fixture.serverSource ?? `export default { id: ${JSON.stringify(id)}, version: "0.1.0" };`,
|
||||
),
|
||||
...(fixture.schemaSource === undefined
|
||||
? []
|
||||
: [writeFile(path.join(moduleDirectory, "dist/schema/index.js"), fixture.schemaSource)]),
|
||||
]);
|
||||
|
||||
if (!migrationDirectory || !migrations) {
|
||||
return { moduleDirectory };
|
||||
}
|
||||
|
||||
const journalEntries = migrations.map((migration, index) => ({
|
||||
idx: index,
|
||||
version: "7",
|
||||
when: migration.timestamp ?? 1_700_000_000_000 + index,
|
||||
tag: migration.tag,
|
||||
breakpoints: migration.breakpoints ?? true,
|
||||
}));
|
||||
const policyEntries = migrations.map((migration) => ({
|
||||
tag: migration.tag,
|
||||
destructive: migration.destructive ?? { mode: "none" },
|
||||
}));
|
||||
await Promise.all([
|
||||
writeFile(
|
||||
path.join(migrationDirectory, "meta/_journal.json"),
|
||||
JSON.stringify({ version: "7", dialect: "postgresql", entries: journalEntries }),
|
||||
),
|
||||
writeFile(
|
||||
path.join(migrationDirectory, "meta/_ekklesia.json"),
|
||||
JSON.stringify({ version: 1, moduleId: id, entries: policyEntries }),
|
||||
),
|
||||
...migrations.flatMap((migration, index) => {
|
||||
const idForSnapshot = migration.snapshotId ?? snapshotId(index);
|
||||
const previousId =
|
||||
index === 0
|
||||
? originSnapshotId
|
||||
: (migrations[index - 1]?.snapshotId ?? snapshotId(index - 1));
|
||||
return [
|
||||
writeFile(path.join(migrationDirectory, `${migration.tag}.sql`), migration.sql),
|
||||
writeFile(
|
||||
path.join(migrationDirectory, `meta/${index.toString().padStart(4, "0")}_snapshot.json`),
|
||||
JSON.stringify({
|
||||
version: "7",
|
||||
dialect: "postgresql",
|
||||
id: idForSnapshot,
|
||||
prevId: previousId,
|
||||
}),
|
||||
),
|
||||
];
|
||||
}),
|
||||
]);
|
||||
|
||||
return { migrationDirectory, moduleDirectory };
|
||||
}
|
||||
Reference in New Issue
Block a user