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
@@ -1,5 +1,5 @@
|
||||
import { createHash } from "node:crypto";
|
||||
import { mkdir, mkdtemp, rm, symlink, writeFile } from "node:fs/promises";
|
||||
import { mkdtemp, rm, symlink, writeFile } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import path from "node:path";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
@@ -10,34 +10,15 @@ import {
|
||||
ModuleMigrationDiscoveryError,
|
||||
type ModuleMigrationDiscoveryIssueCode,
|
||||
} from "../src/modules/index.js";
|
||||
import {
|
||||
createMigrationModule,
|
||||
manifestFor,
|
||||
originSnapshotId,
|
||||
snapshotId,
|
||||
} from "./support/migration-module-fixture.js";
|
||||
|
||||
const originSnapshotId = "00000000-0000-0000-0000-000000000000";
|
||||
const temporaryDirectories: string[] = [];
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
interface MigrationModuleFixture {
|
||||
readonly dependencies?: Record<string, unknown>;
|
||||
readonly manifest?: Record<string, unknown>;
|
||||
readonly migrations?: readonly MigrationFixture[] | false;
|
||||
readonly schemaSource?: string;
|
||||
readonly serverSource?: string;
|
||||
}
|
||||
|
||||
interface CreatedMigrationModule {
|
||||
readonly migrationDirectory?: string;
|
||||
readonly moduleDirectory: string;
|
||||
}
|
||||
|
||||
afterEach(async () => {
|
||||
await Promise.all(
|
||||
temporaryDirectories
|
||||
@@ -52,139 +33,6 @@ async function createModulesDirectory(): Promise<string> {
|
||||
return directory;
|
||||
}
|
||||
|
||||
function snapshotId(index: number): string {
|
||||
return `10000000-0000-4000-8000-${(index + 1).toString(16).padStart(12, "0")}`;
|
||||
}
|
||||
|
||||
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" }),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function defaultMigrations(id: string): readonly MigrationFixture[] {
|
||||
return [
|
||||
{
|
||||
sql: `CREATE TABLE "${id.replaceAll("-", "_")}_records" ("id" integer);\n`,
|
||||
tag: "0000_initial",
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
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 };
|
||||
}
|
||||
|
||||
function discoveryOptions(modulesDirectory: string) {
|
||||
return {
|
||||
moduleSdkVersion: "0.1.0",
|
||||
|
||||
Reference in New Issue
Block a user