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 { 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 { tmpdir } from "node:os";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { afterEach, describe, expect, it } from "vitest";
|
import { afterEach, describe, expect, it } from "vitest";
|
||||||
@@ -10,34 +10,15 @@ import {
|
|||||||
ModuleMigrationDiscoveryError,
|
ModuleMigrationDiscoveryError,
|
||||||
type ModuleMigrationDiscoveryIssueCode,
|
type ModuleMigrationDiscoveryIssueCode,
|
||||||
} from "../src/modules/index.js";
|
} 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[] = [];
|
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 () => {
|
afterEach(async () => {
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
temporaryDirectories
|
temporaryDirectories
|
||||||
@@ -52,139 +33,6 @@ async function createModulesDirectory(): Promise<string> {
|
|||||||
return directory;
|
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) {
|
function discoveryOptions(modulesDirectory: string) {
|
||||||
return {
|
return {
|
||||||
moduleSdkVersion: "0.1.0",
|
moduleSdkVersion: "0.1.0",
|
||||||
|
|||||||
@@ -1,14 +1,16 @@
|
|||||||
import { cp, mkdtemp, readdir, readFile, rm } from "node:fs/promises";
|
import { createHash } from "node:crypto";
|
||||||
|
import { mkdtemp, readdir, readFile, rm } from "node:fs/promises";
|
||||||
import { tmpdir } from "node:os";
|
import { tmpdir } from "node:os";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { fileURLToPath } from "node:url";
|
import { fileURLToPath } from "node:url";
|
||||||
import { afterEach, describe, expect, it } from "vitest";
|
import { afterEach, describe, expect, it } from "vitest";
|
||||||
import { discoverRepositoryMigrations } from "../src/cli/migration-command-support.js";
|
import { discoverRepositoryMigrations } from "../src/cli/migration-command-support.js";
|
||||||
import { resolveCommandModulesDirectory } from "../src/cli/modules-directory-environment.js";
|
import { resolveCommandModulesDirectory } from "../src/cli/modules-directory-environment.js";
|
||||||
|
import { discoverCoreMigrations } from "../src/migrations/index.js";
|
||||||
import { ModuleDiscoveryError } from "../src/modules/index.js";
|
import { ModuleDiscoveryError } from "../src/modules/index.js";
|
||||||
|
import { createMigrationModule } from "./support/migration-module-fixture.js";
|
||||||
|
|
||||||
const repositoryRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..");
|
const repositoryRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..");
|
||||||
const templateModule = path.join(repositoryRoot, "templates", "module");
|
|
||||||
const temporaryDirectories: string[] = [];
|
const temporaryDirectories: string[] = [];
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
@@ -23,16 +25,6 @@ async function externalModulesDirectory(): Promise<string> {
|
|||||||
return directory;
|
return directory;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Installs the module template's migration-bearing package files as module `example`. Migration
|
|
||||||
* discovery imports nothing, so the template's manifest, package metadata, and migrations are all
|
|
||||||
* it reads; the compiled artifacts discovery checks for are copied too when they exist. */
|
|
||||||
async function installTemplate(modulesDirectory: string): Promise<void> {
|
|
||||||
const target = path.join(modulesDirectory, "example");
|
|
||||||
for (const entry of ["package.json", "ekklesia.module.json", "drizzle", "dist"]) {
|
|
||||||
await cp(path.join(templateModule, entry), path.join(target, entry), { recursive: true });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
describe("operator commands select the modules directory through host configuration", () => {
|
describe("operator commands select the modules directory through host configuration", () => {
|
||||||
it("defaults to the repository's own modules directory", () => {
|
it("defaults to the repository's own modules directory", () => {
|
||||||
expect(resolveCommandModulesDirectory({}, "/opt/ekklesia")).toEqual({
|
expect(resolveCommandModulesDirectory({}, "/opt/ekklesia")).toEqual({
|
||||||
@@ -62,23 +54,34 @@ describe("operator commands select the modules directory through host configurat
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("plans migrations for modules installed outside the repository, with Core's from the platform", async () => {
|
it("plans migrations for modules installed outside the repository, with Core's from the platform", async () => {
|
||||||
|
// A minimal installed module generated from nothing in the repository, so this test never
|
||||||
|
// depends on build output or ignored files: a clean checkout runs it before anything is built.
|
||||||
const modulesDirectory = await externalModulesDirectory();
|
const modulesDirectory = await externalModulesDirectory();
|
||||||
await installTemplate(modulesDirectory);
|
const installed = await createMigrationModule(modulesDirectory, "example");
|
||||||
|
expect(path.relative(repositoryRoot, modulesDirectory).startsWith("..")).toBe(true);
|
||||||
|
|
||||||
const aggregate = await discoverRepositoryMigrations(repositoryRoot, {
|
const aggregate = await discoverRepositoryMigrations(repositoryRoot, {
|
||||||
EKKLESIA_MODULES_DIR: modulesDirectory,
|
EKKLESIA_MODULES_DIR: modulesDirectory,
|
||||||
});
|
});
|
||||||
|
|
||||||
const sources = aggregate.modules.map((module) => module.moduleId);
|
expect(aggregate.modules.map((module) => module.moduleId)).toEqual(["core", "example"]);
|
||||||
expect(sources[0]).toBe("core");
|
|
||||||
expect(sources).toContain("example");
|
// Core's migrations are exactly the platform's own, byte for byte, from `core/drizzle`.
|
||||||
expect(
|
const platformCore = await discoverCoreMigrations({
|
||||||
aggregate.migrations.some((migration) => migration.identity === "example:0000_initial"),
|
coreMigrationsDirectory: path.join(repositoryRoot, "core", "drizzle"),
|
||||||
).toBe(true);
|
});
|
||||||
const coreTags = (await readdir(path.join(repositoryRoot, "core", "drizzle"))).filter((name) =>
|
const fingerprint = (migrations: readonly { identity: string; checksum: string }[]) =>
|
||||||
name.endsWith(".sql"),
|
migrations.map((migration) => `${migration.identity} ${migration.checksum}`);
|
||||||
|
expect(platformCore.length).toBeGreaterThan(0);
|
||||||
|
expect(fingerprint(aggregate.modules[0]?.migrations ?? [])).toEqual(fingerprint(platformCore));
|
||||||
|
|
||||||
|
// The module's migration is the one installed outside the repository, byte for byte.
|
||||||
|
const installedSql = await readFile(
|
||||||
|
path.join(installed.migrationDirectory ?? "", "0000_initial.sql"),
|
||||||
);
|
);
|
||||||
expect(aggregate.modules[0]?.migrations).toHaveLength(coreTags.length);
|
expect(fingerprint(aggregate.modules[1]?.migrations ?? [])).toEqual([
|
||||||
|
`example:0000_initial ${createHash("sha256").update(installedSql).digest("hex")}`,
|
||||||
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("fails closed when the configured directory does not exist", async () => {
|
it("fails closed when the configured directory does not exist", async () => {
|
||||||
|
|||||||
@@ -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