@prisma-next/migration-tools 0.5.0-dev.1 → 0.5.0-dev.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +32 -20
- package/dist/{constants-BRi0X7B_.mjs → constants-WVGVMOdu.mjs} +1 -1
- package/dist/{constants-BRi0X7B_.mjs.map → constants-WVGVMOdu.mjs.map} +1 -1
- package/dist/{errors-BKbRGCJM.mjs → errors-CZ9JD4sd.mjs} +50 -21
- package/dist/errors-CZ9JD4sd.mjs.map +1 -0
- package/dist/exports/constants.mjs +1 -1
- package/dist/exports/dag.d.mts +4 -3
- package/dist/exports/dag.d.mts.map +1 -1
- package/dist/exports/dag.mjs +15 -15
- package/dist/exports/dag.mjs.map +1 -1
- package/dist/exports/{types.d.mts → errors.d.mts} +6 -8
- package/dist/exports/errors.d.mts.map +1 -0
- package/dist/exports/errors.mjs +3 -0
- package/dist/exports/graph.d.mts +2 -0
- package/dist/exports/graph.mjs +1 -0
- package/dist/exports/hash.d.mts +52 -0
- package/dist/exports/hash.d.mts.map +1 -0
- package/dist/exports/hash.mjs +3 -0
- package/dist/exports/io.d.mts +7 -6
- package/dist/exports/io.d.mts.map +1 -1
- package/dist/exports/io.mjs +156 -2
- package/dist/exports/io.mjs.map +1 -0
- package/dist/exports/metadata.d.mts +2 -0
- package/dist/exports/metadata.mjs +1 -0
- package/dist/exports/migration-ts.mjs +1 -1
- package/dist/exports/migration.d.mts +13 -10
- package/dist/exports/migration.d.mts.map +1 -1
- package/dist/exports/migration.mjs +20 -21
- package/dist/exports/migration.mjs.map +1 -1
- package/dist/exports/package.d.mts +2 -0
- package/dist/exports/package.mjs +1 -0
- package/dist/exports/refs.d.mts +11 -5
- package/dist/exports/refs.d.mts.map +1 -1
- package/dist/exports/refs.mjs +106 -30
- package/dist/exports/refs.mjs.map +1 -1
- package/dist/graph-HiqjZROg.d.mts +22 -0
- package/dist/graph-HiqjZROg.d.mts.map +1 -0
- package/dist/hash-BNWumjn7.mjs +76 -0
- package/dist/hash-BNWumjn7.mjs.map +1 -0
- package/dist/metadata-DDa5L-uD.d.mts +45 -0
- package/dist/metadata-DDa5L-uD.d.mts.map +1 -0
- package/dist/package-BJ5KAEcD.d.mts +21 -0
- package/dist/package-BJ5KAEcD.d.mts.map +1 -0
- package/package.json +23 -11
- package/src/dag.ts +19 -18
- package/src/errors.ts +57 -15
- package/src/exports/errors.ts +1 -0
- package/src/exports/graph.ts +1 -0
- package/src/exports/hash.ts +2 -0
- package/src/exports/io.ts +1 -1
- package/src/exports/metadata.ts +1 -0
- package/src/exports/package.ts +1 -0
- package/src/exports/refs.ts +10 -2
- package/src/graph.ts +19 -0
- package/src/hash.ts +91 -0
- package/src/io.ts +32 -20
- package/src/metadata.ts +36 -0
- package/src/migration-base.ts +32 -28
- package/src/package.ts +18 -0
- package/src/refs.ts +148 -37
- package/dist/attestation-DtF8tEOM.mjs +0 -65
- package/dist/attestation-DtF8tEOM.mjs.map +0 -1
- package/dist/errors-BKbRGCJM.mjs.map +0 -1
- package/dist/exports/attestation.d.mts +0 -37
- package/dist/exports/attestation.d.mts.map +0 -1
- package/dist/exports/attestation.mjs +0 -4
- package/dist/exports/types.d.mts.map +0 -1
- package/dist/exports/types.mjs +0 -3
- package/dist/io-CCnYsUHU.mjs +0 -153
- package/dist/io-CCnYsUHU.mjs.map +0 -1
- package/dist/types-DyGXcWWp.d.mts +0 -71
- package/dist/types-DyGXcWWp.d.mts.map +0 -1
- package/src/attestation.ts +0 -81
- package/src/exports/attestation.ts +0 -2
- package/src/exports/types.ts +0 -10
- package/src/types.ts +0 -66
package/dist/exports/io.d.mts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { n as MigrationMetadata } from "../metadata-DDa5L-uD.mjs";
|
|
2
|
+
import { n as MigrationPackage, t as MigrationOps } from "../package-BJ5KAEcD.mjs";
|
|
2
3
|
|
|
3
4
|
//#region src/io.d.ts
|
|
4
|
-
declare function writeMigrationPackage(dir: string,
|
|
5
|
+
declare function writeMigrationPackage(dir: string, metadata: MigrationMetadata, ops: MigrationOps): Promise<void>;
|
|
5
6
|
/**
|
|
6
7
|
* Copy a list of files into `destDir`, optionally renaming each one.
|
|
7
8
|
*
|
|
@@ -17,11 +18,11 @@ declare function copyFilesWithRename(destDir: string, files: readonly {
|
|
|
17
18
|
readonly sourcePath: string;
|
|
18
19
|
readonly destName: string;
|
|
19
20
|
}[]): Promise<void>;
|
|
20
|
-
declare function
|
|
21
|
+
declare function writeMigrationMetadata(dir: string, metadata: MigrationMetadata): Promise<void>;
|
|
21
22
|
declare function writeMigrationOps(dir: string, ops: MigrationOps): Promise<void>;
|
|
22
|
-
declare function readMigrationPackage(dir: string): Promise<
|
|
23
|
-
declare function readMigrationsDir(migrationsRoot: string): Promise<readonly
|
|
23
|
+
declare function readMigrationPackage(dir: string): Promise<MigrationPackage>;
|
|
24
|
+
declare function readMigrationsDir(migrationsRoot: string): Promise<readonly MigrationPackage[]>;
|
|
24
25
|
declare function formatMigrationDirName(timestamp: Date, slug: string): string;
|
|
25
26
|
//#endregion
|
|
26
|
-
export { copyFilesWithRename, formatMigrationDirName, readMigrationPackage, readMigrationsDir,
|
|
27
|
+
export { copyFilesWithRename, formatMigrationDirName, readMigrationPackage, readMigrationsDir, writeMigrationMetadata, writeMigrationOps, writeMigrationPackage };
|
|
27
28
|
//# sourceMappingURL=io.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"io.d.mts","names":[],"sources":["../../src/io.ts"],"sourcesContent":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"io.d.mts","names":[],"sources":["../../src/io.ts"],"sourcesContent":[],"mappings":";;;;iBA2DsB,qBAAA,wBAEV,wBACL,eACJ;;AAJH;;;;;AAiCA;AAaA;AAOA;AAIA;AAyEA;AAiCgB,iBAlIM,mBAAA,CAkI4B,OAAI,EAAA,MAAA,EAAA,KAAA,EAAA,SAAA;;;MA/HnD;iBAUmB,sBAAA,wBAEV,oBACT;iBAImB,iBAAA,mBAAoC,eAAe;iBAInD,oBAAA,eAAmC,QAAQ;iBAyE3C,iBAAA,0BAEnB,iBAAiB;iBA+BJ,sBAAA,YAAkC"}
|
package/dist/exports/io.mjs
CHANGED
|
@@ -1,3 +1,157 @@
|
|
|
1
|
-
import { a as
|
|
1
|
+
import { a as errorInvalidDestName, d as errorInvalidSlug, f as errorMigrationHashMismatch, o as errorInvalidJson, p as errorMissingFile, r as errorDirectoryExists, s as errorInvalidManifest } from "../errors-CZ9JD4sd.mjs";
|
|
2
|
+
import { n as verifyMigrationHash } from "../hash-BNWumjn7.mjs";
|
|
3
|
+
import { basename, dirname, join } from "pathe";
|
|
4
|
+
import { copyFile, mkdir, readFile, readdir, stat, writeFile } from "node:fs/promises";
|
|
5
|
+
import { type } from "arktype";
|
|
2
6
|
|
|
3
|
-
|
|
7
|
+
//#region src/io.ts
|
|
8
|
+
const MANIFEST_FILE = "migration.json";
|
|
9
|
+
const OPS_FILE = "ops.json";
|
|
10
|
+
const MAX_SLUG_LENGTH = 64;
|
|
11
|
+
function hasErrnoCode(error, code) {
|
|
12
|
+
return error instanceof Error && error.code === code;
|
|
13
|
+
}
|
|
14
|
+
const MigrationMetadataSchema = type({
|
|
15
|
+
from: "string",
|
|
16
|
+
to: "string",
|
|
17
|
+
migrationHash: "string",
|
|
18
|
+
kind: "'regular' | 'baseline'",
|
|
19
|
+
fromContract: "object | null",
|
|
20
|
+
toContract: "object",
|
|
21
|
+
hints: type({
|
|
22
|
+
used: "string[]",
|
|
23
|
+
applied: "string[]",
|
|
24
|
+
plannerVersion: "string"
|
|
25
|
+
}),
|
|
26
|
+
labels: "string[]",
|
|
27
|
+
"authorship?": type({
|
|
28
|
+
"author?": "string",
|
|
29
|
+
"email?": "string"
|
|
30
|
+
}),
|
|
31
|
+
"signature?": type({
|
|
32
|
+
keyId: "string",
|
|
33
|
+
value: "string"
|
|
34
|
+
}).or("null"),
|
|
35
|
+
createdAt: "string"
|
|
36
|
+
});
|
|
37
|
+
const MigrationOpsSchema = type({
|
|
38
|
+
id: "string",
|
|
39
|
+
label: "string",
|
|
40
|
+
operationClass: "'additive' | 'widening' | 'destructive' | 'data'"
|
|
41
|
+
}).array();
|
|
42
|
+
async function writeMigrationPackage(dir, metadata, ops) {
|
|
43
|
+
await mkdir(dirname(dir), { recursive: true });
|
|
44
|
+
try {
|
|
45
|
+
await mkdir(dir);
|
|
46
|
+
} catch (error) {
|
|
47
|
+
if (hasErrnoCode(error, "EEXIST")) throw errorDirectoryExists(dir);
|
|
48
|
+
throw error;
|
|
49
|
+
}
|
|
50
|
+
await writeFile(join(dir, MANIFEST_FILE), JSON.stringify(metadata, null, 2), { flag: "wx" });
|
|
51
|
+
await writeFile(join(dir, OPS_FILE), JSON.stringify(ops, null, 2), { flag: "wx" });
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Copy a list of files into `destDir`, optionally renaming each one.
|
|
55
|
+
*
|
|
56
|
+
* The destination directory is created (with `recursive: true`) if it
|
|
57
|
+
* does not already exist. Each source path is copied byte-for-byte into
|
|
58
|
+
* `destDir/<destName>`; missing sources throw `ENOENT`. The helper is
|
|
59
|
+
* intentionally generic: callers own the list of files (e.g. a contract
|
|
60
|
+
* emitter's emitted output) and the naming convention (e.g. renaming
|
|
61
|
+
* the destination contract to `end-contract.*` and the source contract
|
|
62
|
+
* to `start-contract.*`).
|
|
63
|
+
*/
|
|
64
|
+
async function copyFilesWithRename(destDir, files) {
|
|
65
|
+
await mkdir(destDir, { recursive: true });
|
|
66
|
+
for (const file of files) {
|
|
67
|
+
if (basename(file.destName) !== file.destName) throw errorInvalidDestName(file.destName);
|
|
68
|
+
await copyFile(file.sourcePath, join(destDir, file.destName));
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
async function writeMigrationMetadata(dir, metadata) {
|
|
72
|
+
await writeFile(join(dir, MANIFEST_FILE), `${JSON.stringify(metadata, null, 2)}\n`);
|
|
73
|
+
}
|
|
74
|
+
async function writeMigrationOps(dir, ops) {
|
|
75
|
+
await writeFile(join(dir, OPS_FILE), `${JSON.stringify(ops, null, 2)}\n`);
|
|
76
|
+
}
|
|
77
|
+
async function readMigrationPackage(dir) {
|
|
78
|
+
const manifestPath = join(dir, MANIFEST_FILE);
|
|
79
|
+
const opsPath = join(dir, OPS_FILE);
|
|
80
|
+
let manifestRaw;
|
|
81
|
+
try {
|
|
82
|
+
manifestRaw = await readFile(manifestPath, "utf-8");
|
|
83
|
+
} catch (error) {
|
|
84
|
+
if (hasErrnoCode(error, "ENOENT")) throw errorMissingFile(MANIFEST_FILE, dir);
|
|
85
|
+
throw error;
|
|
86
|
+
}
|
|
87
|
+
let opsRaw;
|
|
88
|
+
try {
|
|
89
|
+
opsRaw = await readFile(opsPath, "utf-8");
|
|
90
|
+
} catch (error) {
|
|
91
|
+
if (hasErrnoCode(error, "ENOENT")) throw errorMissingFile(OPS_FILE, dir);
|
|
92
|
+
throw error;
|
|
93
|
+
}
|
|
94
|
+
let metadata;
|
|
95
|
+
try {
|
|
96
|
+
metadata = JSON.parse(manifestRaw);
|
|
97
|
+
} catch (e) {
|
|
98
|
+
throw errorInvalidJson(manifestPath, e instanceof Error ? e.message : String(e));
|
|
99
|
+
}
|
|
100
|
+
let ops;
|
|
101
|
+
try {
|
|
102
|
+
ops = JSON.parse(opsRaw);
|
|
103
|
+
} catch (e) {
|
|
104
|
+
throw errorInvalidJson(opsPath, e instanceof Error ? e.message : String(e));
|
|
105
|
+
}
|
|
106
|
+
validateMetadata(metadata, manifestPath);
|
|
107
|
+
validateOps(ops, opsPath);
|
|
108
|
+
const pkg = {
|
|
109
|
+
dirName: basename(dir),
|
|
110
|
+
dirPath: dir,
|
|
111
|
+
metadata,
|
|
112
|
+
ops
|
|
113
|
+
};
|
|
114
|
+
const verification = verifyMigrationHash(pkg);
|
|
115
|
+
if (!verification.ok) throw errorMigrationHashMismatch(dir, verification.storedHash, verification.computedHash);
|
|
116
|
+
return pkg;
|
|
117
|
+
}
|
|
118
|
+
function validateMetadata(metadata, filePath) {
|
|
119
|
+
const result = MigrationMetadataSchema(metadata);
|
|
120
|
+
if (result instanceof type.errors) throw errorInvalidManifest(filePath, result.summary);
|
|
121
|
+
}
|
|
122
|
+
function validateOps(ops, filePath) {
|
|
123
|
+
const result = MigrationOpsSchema(ops);
|
|
124
|
+
if (result instanceof type.errors) throw errorInvalidManifest(filePath, result.summary);
|
|
125
|
+
}
|
|
126
|
+
async function readMigrationsDir(migrationsRoot) {
|
|
127
|
+
let entries;
|
|
128
|
+
try {
|
|
129
|
+
entries = await readdir(migrationsRoot);
|
|
130
|
+
} catch (error) {
|
|
131
|
+
if (hasErrnoCode(error, "ENOENT")) return [];
|
|
132
|
+
throw error;
|
|
133
|
+
}
|
|
134
|
+
const packages = [];
|
|
135
|
+
for (const entry of entries.sort()) {
|
|
136
|
+
const entryPath = join(migrationsRoot, entry);
|
|
137
|
+
if (!(await stat(entryPath)).isDirectory()) continue;
|
|
138
|
+
const manifestPath = join(entryPath, MANIFEST_FILE);
|
|
139
|
+
try {
|
|
140
|
+
await stat(manifestPath);
|
|
141
|
+
} catch {
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
packages.push(await readMigrationPackage(entryPath));
|
|
145
|
+
}
|
|
146
|
+
return packages;
|
|
147
|
+
}
|
|
148
|
+
function formatMigrationDirName(timestamp, slug) {
|
|
149
|
+
const sanitized = slug.toLowerCase().replace(/[^a-z0-9]/g, "_").replace(/_+/g, "_").replace(/^_|_$/g, "");
|
|
150
|
+
if (sanitized.length === 0) throw errorInvalidSlug(slug);
|
|
151
|
+
const truncated = sanitized.slice(0, MAX_SLUG_LENGTH);
|
|
152
|
+
return `${timestamp.getUTCFullYear()}${String(timestamp.getUTCMonth() + 1).padStart(2, "0")}${String(timestamp.getUTCDate()).padStart(2, "0")}T${String(timestamp.getUTCHours()).padStart(2, "0")}${String(timestamp.getUTCMinutes()).padStart(2, "0")}_${truncated}`;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
//#endregion
|
|
156
|
+
export { copyFilesWithRename, formatMigrationDirName, readMigrationPackage, readMigrationsDir, writeMigrationMetadata, writeMigrationOps, writeMigrationPackage };
|
|
157
|
+
//# sourceMappingURL=io.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"io.mjs","names":["manifestRaw: string","opsRaw: string","metadata: MigrationMetadata","ops: MigrationOps","pkg: MigrationPackage","entries: string[]","packages: MigrationPackage[]"],"sources":["../../src/io.ts"],"sourcesContent":["import { copyFile, mkdir, readdir, readFile, stat, writeFile } from 'node:fs/promises';\nimport { type } from 'arktype';\nimport { basename, dirname, join } from 'pathe';\nimport {\n errorDirectoryExists,\n errorInvalidDestName,\n errorInvalidJson,\n errorInvalidManifest,\n errorInvalidSlug,\n errorMigrationHashMismatch,\n errorMissingFile,\n} from './errors';\nimport { verifyMigrationHash } from './hash';\nimport type { MigrationMetadata } from './metadata';\nimport type { MigrationOps, MigrationPackage } from './package';\n\nconst MANIFEST_FILE = 'migration.json';\nconst OPS_FILE = 'ops.json';\nconst MAX_SLUG_LENGTH = 64;\n\nfunction hasErrnoCode(error: unknown, code: string): boolean {\n return error instanceof Error && (error as { code?: string }).code === code;\n}\n\nconst MigrationHintsSchema = type({\n used: 'string[]',\n applied: 'string[]',\n plannerVersion: 'string',\n});\n\nconst MigrationMetadataSchema = type({\n from: 'string',\n to: 'string',\n migrationHash: 'string',\n kind: \"'regular' | 'baseline'\",\n fromContract: 'object | null',\n toContract: 'object',\n hints: MigrationHintsSchema,\n labels: 'string[]',\n 'authorship?': type({\n 'author?': 'string',\n 'email?': 'string',\n }),\n 'signature?': type({\n keyId: 'string',\n value: 'string',\n }).or('null'),\n createdAt: 'string',\n});\n\nconst MigrationOpSchema = type({\n id: 'string',\n label: 'string',\n operationClass: \"'additive' | 'widening' | 'destructive' | 'data'\",\n});\n\n// Intentionally shallow: operation-specific payload validation is owned by planner/runner layers.\nconst MigrationOpsSchema = MigrationOpSchema.array();\n\nexport async function writeMigrationPackage(\n dir: string,\n metadata: MigrationMetadata,\n ops: MigrationOps,\n): Promise<void> {\n await mkdir(dirname(dir), { recursive: true });\n\n try {\n await mkdir(dir);\n } catch (error) {\n if (hasErrnoCode(error, 'EEXIST')) {\n throw errorDirectoryExists(dir);\n }\n throw error;\n }\n\n await writeFile(join(dir, MANIFEST_FILE), JSON.stringify(metadata, null, 2), {\n flag: 'wx',\n });\n await writeFile(join(dir, OPS_FILE), JSON.stringify(ops, null, 2), { flag: 'wx' });\n}\n\n/**\n * Copy a list of files into `destDir`, optionally renaming each one.\n *\n * The destination directory is created (with `recursive: true`) if it\n * does not already exist. Each source path is copied byte-for-byte into\n * `destDir/<destName>`; missing sources throw `ENOENT`. The helper is\n * intentionally generic: callers own the list of files (e.g. a contract\n * emitter's emitted output) and the naming convention (e.g. renaming\n * the destination contract to `end-contract.*` and the source contract\n * to `start-contract.*`).\n */\nexport async function copyFilesWithRename(\n destDir: string,\n files: readonly { readonly sourcePath: string; readonly destName: string }[],\n): Promise<void> {\n await mkdir(destDir, { recursive: true });\n for (const file of files) {\n if (basename(file.destName) !== file.destName) {\n throw errorInvalidDestName(file.destName);\n }\n await copyFile(file.sourcePath, join(destDir, file.destName));\n }\n}\n\nexport async function writeMigrationMetadata(\n dir: string,\n metadata: MigrationMetadata,\n): Promise<void> {\n await writeFile(join(dir, MANIFEST_FILE), `${JSON.stringify(metadata, null, 2)}\\n`);\n}\n\nexport async function writeMigrationOps(dir: string, ops: MigrationOps): Promise<void> {\n await writeFile(join(dir, OPS_FILE), `${JSON.stringify(ops, null, 2)}\\n`);\n}\n\nexport async function readMigrationPackage(dir: string): Promise<MigrationPackage> {\n const manifestPath = join(dir, MANIFEST_FILE);\n const opsPath = join(dir, OPS_FILE);\n\n let manifestRaw: string;\n try {\n manifestRaw = await readFile(manifestPath, 'utf-8');\n } catch (error) {\n if (hasErrnoCode(error, 'ENOENT')) {\n throw errorMissingFile(MANIFEST_FILE, dir);\n }\n throw error;\n }\n\n let opsRaw: string;\n try {\n opsRaw = await readFile(opsPath, 'utf-8');\n } catch (error) {\n if (hasErrnoCode(error, 'ENOENT')) {\n throw errorMissingFile(OPS_FILE, dir);\n }\n throw error;\n }\n\n let metadata: MigrationMetadata;\n try {\n metadata = JSON.parse(manifestRaw);\n } catch (e) {\n throw errorInvalidJson(manifestPath, e instanceof Error ? e.message : String(e));\n }\n\n let ops: MigrationOps;\n try {\n ops = JSON.parse(opsRaw);\n } catch (e) {\n throw errorInvalidJson(opsPath, e instanceof Error ? e.message : String(e));\n }\n\n validateMetadata(metadata, manifestPath);\n validateOps(ops, opsPath);\n\n const pkg: MigrationPackage = {\n dirName: basename(dir),\n dirPath: dir,\n metadata,\n ops,\n };\n\n const verification = verifyMigrationHash(pkg);\n if (!verification.ok) {\n throw errorMigrationHashMismatch(dir, verification.storedHash, verification.computedHash);\n }\n\n return pkg;\n}\n\nfunction validateMetadata(\n metadata: unknown,\n filePath: string,\n): asserts metadata is MigrationMetadata {\n const result = MigrationMetadataSchema(metadata);\n if (result instanceof type.errors) {\n throw errorInvalidManifest(filePath, result.summary);\n }\n}\n\nfunction validateOps(ops: unknown, filePath: string): asserts ops is MigrationOps {\n const result = MigrationOpsSchema(ops);\n if (result instanceof type.errors) {\n throw errorInvalidManifest(filePath, result.summary);\n }\n}\n\nexport async function readMigrationsDir(\n migrationsRoot: string,\n): Promise<readonly MigrationPackage[]> {\n let entries: string[];\n try {\n entries = await readdir(migrationsRoot);\n } catch (error) {\n if (hasErrnoCode(error, 'ENOENT')) {\n return [];\n }\n throw error;\n }\n\n const packages: MigrationPackage[] = [];\n\n for (const entry of entries.sort()) {\n const entryPath = join(migrationsRoot, entry);\n const entryStat = await stat(entryPath);\n if (!entryStat.isDirectory()) continue;\n\n const manifestPath = join(entryPath, MANIFEST_FILE);\n try {\n await stat(manifestPath);\n } catch {\n continue; // skip non-migration directories\n }\n\n packages.push(await readMigrationPackage(entryPath));\n }\n\n return packages;\n}\n\nexport function formatMigrationDirName(timestamp: Date, slug: string): string {\n const sanitized = slug\n .toLowerCase()\n .replace(/[^a-z0-9]/g, '_')\n .replace(/_+/g, '_')\n .replace(/^_|_$/g, '');\n\n if (sanitized.length === 0) {\n throw errorInvalidSlug(slug);\n }\n\n const truncated = sanitized.slice(0, MAX_SLUG_LENGTH);\n\n const y = timestamp.getUTCFullYear();\n const mo = String(timestamp.getUTCMonth() + 1).padStart(2, '0');\n const d = String(timestamp.getUTCDate()).padStart(2, '0');\n const h = String(timestamp.getUTCHours()).padStart(2, '0');\n const mi = String(timestamp.getUTCMinutes()).padStart(2, '0');\n\n return `${y}${mo}${d}T${h}${mi}_${truncated}`;\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { n as MigrationMetadata } from "../metadata-DDa5L-uD.mjs";
|
|
2
2
|
import { ControlStack, MigrationPlan, MigrationPlanOperation } from "@prisma-next/framework-components/control";
|
|
3
3
|
|
|
4
4
|
//#region src/migration-base.d.ts
|
|
@@ -13,7 +13,7 @@ interface MigrationMeta {
|
|
|
13
13
|
*
|
|
14
14
|
* A `Migration` subclass is itself a `MigrationPlan`: CLI commands and the
|
|
15
15
|
* runner can consume it directly via `targetId`, `operations`, `origin`, and
|
|
16
|
-
* `destination`. The
|
|
16
|
+
* `destination`. The metadata-shaped inputs come from `describe()`, which
|
|
17
17
|
* every migration must implement — `migration.json` is required for a
|
|
18
18
|
* migration to be valid.
|
|
19
19
|
*/
|
|
@@ -61,27 +61,30 @@ declare function isDirectEntrypoint(importMetaUrl: string): boolean;
|
|
|
61
61
|
declare function printMigrationHelp(): void;
|
|
62
62
|
/**
|
|
63
63
|
* In-memory artifacts produced from a `Migration` instance: the
|
|
64
|
-
* serialized `ops.json` body, the `migration.json`
|
|
64
|
+
* serialized `ops.json` body, the `migration.json` metadata object, and
|
|
65
65
|
* its serialized form. Returned by `buildMigrationArtifacts` so callers
|
|
66
66
|
* (today: `MigrationCLI.run` in `@prisma-next/cli/migration-cli`) can
|
|
67
67
|
* decide how to persist them — write to disk, print in dry-run, ship
|
|
68
68
|
* over the wire — without coupling artifact construction to file I/O.
|
|
69
|
+
*
|
|
70
|
+
* `metadataJson` is `JSON.stringify(metadata, null, 2)` — the canonical
|
|
71
|
+
* on-disk shape that the arktype loader-schema in `./io` validates.
|
|
69
72
|
*/
|
|
70
73
|
interface MigrationArtifacts {
|
|
71
74
|
readonly opsJson: string;
|
|
72
|
-
readonly
|
|
73
|
-
readonly
|
|
75
|
+
readonly metadata: MigrationMetadata;
|
|
76
|
+
readonly metadataJson: string;
|
|
74
77
|
}
|
|
75
78
|
/**
|
|
76
79
|
* Pure conversion from a `Migration` instance (plus the previously
|
|
77
|
-
* scaffolded
|
|
80
|
+
* scaffolded metadata, when one exists on disk) to the in-memory
|
|
78
81
|
* artifacts that downstream tooling persists. Owns metadata validation,
|
|
79
|
-
*
|
|
80
|
-
* content-addressed `
|
|
82
|
+
* metadata synthesis/preservation, hint normalization, and the
|
|
83
|
+
* content-addressed `migrationHash` computation, but performs no file I/O
|
|
81
84
|
* — callers handle reads (to source `existing`) and writes (to persist
|
|
82
|
-
* `opsJson` / `
|
|
85
|
+
* `opsJson` / `metadataJson`).
|
|
83
86
|
*/
|
|
84
|
-
declare function buildMigrationArtifacts(instance: Migration, existing: Partial<
|
|
87
|
+
declare function buildMigrationArtifacts(instance: Migration, existing: Partial<MigrationMetadata> | null): MigrationArtifacts;
|
|
85
88
|
//#endregion
|
|
86
89
|
export { Migration, type MigrationArtifacts, type MigrationMeta, buildMigrationArtifacts, isDirectEntrypoint, printMigrationHelp };
|
|
87
90
|
//# sourceMappingURL=migration.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"migration.d.mts","names":[],"sources":["../../src/migration-base.ts"],"sourcesContent":[],"mappings":";;;;
|
|
1
|
+
{"version":3,"file":"migration.d.mts","names":[],"sources":["../../src/migration-base.ts"],"sourcesContent":[],"mappings":";;;;UAciB,aAAA;;EAAA,SAAA,EAAA,EAAA,MAAa;EAuBR,SAAA,IAAS,CAAA,EAAA,SAAA,GAAA,UAAA;EACV,SAAA,MAAA,CAAA,EAAA,SAAA,MAAA,EAAA;;;;;;;;;;;AAGK,uBAJJ,SAII,CAAA,mBAHL,sBAGK,GAHoB,sBAGpB,EAAA,kBAAA,MAAA,GAAA,MAAA,EAAA,kBAAA,MAAA,GAAA,MAAA,CAAA,YAAb,aAAa,CAAA;EAuDV,kBAAA,QAAkB,EAAA,MAAA;EAWlB;AA4BhB;AAyEA;;;;;;;4BA1J4B,aAAa,WAAW;sBAE9B,aAAa,WAAW;;;;;;;sCAUR;;;;;;uBAOf;;;;;;;;;;;;;;;iBAuBP,kBAAA;iBAWA,kBAAA,CAAA;;;;;;;;;;;;UA4BC,kBAAA;;qBAEI;;;;;;;;;;;;iBAuEL,uBAAA,WACJ,qBACA,QAAQ,4BACjB"}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import "../
|
|
2
|
-
import { t as computeMigrationId } from "../attestation-DtF8tEOM.mjs";
|
|
1
|
+
import { t as computeMigrationHash } from "../hash-BNWumjn7.mjs";
|
|
3
2
|
import { type } from "arktype";
|
|
4
3
|
import { ifDefined } from "@prisma-next/utils/defined";
|
|
5
4
|
import { realpathSync } from "node:fs";
|
|
@@ -17,7 +16,7 @@ const MigrationMetaSchema = type({
|
|
|
17
16
|
*
|
|
18
17
|
* A `Migration` subclass is itself a `MigrationPlan`: CLI commands and the
|
|
19
18
|
* runner can consume it directly via `targetId`, `operations`, `origin`, and
|
|
20
|
-
* `destination`. The
|
|
19
|
+
* `destination`. The metadata-shaped inputs come from `describe()`, which
|
|
21
20
|
* every migration must implement — `migration.json` is required for a
|
|
22
21
|
* migration to be valid.
|
|
23
22
|
*/
|
|
@@ -74,24 +73,24 @@ function printHelp() {
|
|
|
74
73
|
].join("\n"));
|
|
75
74
|
}
|
|
76
75
|
/**
|
|
77
|
-
* Build the attested
|
|
78
|
-
* operations list, and the previously-scaffolded
|
|
76
|
+
* Build the attested metadata from `describe()`-derived metadata, the
|
|
77
|
+
* operations list, and the previously-scaffolded metadata (if any).
|
|
79
78
|
*
|
|
80
79
|
* When a `migration.json` already exists for this package (the common
|
|
81
80
|
* case: it was scaffolded by `migration plan`), preserve the contract
|
|
82
81
|
* bookends, hints, labels, and `createdAt` set there — those fields are
|
|
83
82
|
* owned by the CLI scaffolder, not the authored class. Only the
|
|
84
83
|
* `describe()`-derived fields (`from`, `to`, `kind`) and the operations
|
|
85
|
-
* change as the author iterates. When no
|
|
84
|
+
* change as the author iterates. When no metadata exists yet (a bare
|
|
86
85
|
* `migration.ts` run from scratch), synthesize a minimal but
|
|
87
|
-
* schema-conformant
|
|
86
|
+
* schema-conformant record so the resulting package can still be read,
|
|
88
87
|
* verified, and applied.
|
|
89
88
|
*
|
|
90
|
-
* The `
|
|
89
|
+
* The `migrationHash` is recomputed against the current metadata + ops so
|
|
91
90
|
* the on-disk artifacts are always fully attested.
|
|
92
91
|
*/
|
|
93
|
-
function
|
|
94
|
-
const
|
|
92
|
+
function buildAttestedMetadata(meta, ops, existing) {
|
|
93
|
+
const baseMetadata = {
|
|
95
94
|
from: meta.from,
|
|
96
95
|
to: meta.to,
|
|
97
96
|
kind: meta.kind ?? "regular",
|
|
@@ -102,15 +101,15 @@ function buildAttestedManifest(meta, ops, existing) {
|
|
|
102
101
|
hints: normalizeHints(existing?.hints),
|
|
103
102
|
...ifDefined("authorship", existing?.authorship)
|
|
104
103
|
};
|
|
105
|
-
const
|
|
104
|
+
const migrationHash = computeMigrationHash(baseMetadata, ops);
|
|
106
105
|
return {
|
|
107
|
-
...
|
|
108
|
-
|
|
106
|
+
...baseMetadata,
|
|
107
|
+
migrationHash
|
|
109
108
|
};
|
|
110
109
|
}
|
|
111
110
|
/**
|
|
112
111
|
* Project `existing.hints` down to the known `MigrationHints` shape, dropping
|
|
113
|
-
* any legacy keys that may linger in
|
|
112
|
+
* any legacy keys that may linger in metadata scaffolded by older CLI
|
|
114
113
|
* versions (e.g. `planningStrategy`). Picking fields explicitly instead of
|
|
115
114
|
* spreading keeps refreshed `migration.json` files schema-clean regardless
|
|
116
115
|
* of what was on disk before.
|
|
@@ -124,23 +123,23 @@ function normalizeHints(existing) {
|
|
|
124
123
|
}
|
|
125
124
|
/**
|
|
126
125
|
* Pure conversion from a `Migration` instance (plus the previously
|
|
127
|
-
* scaffolded
|
|
126
|
+
* scaffolded metadata, when one exists on disk) to the in-memory
|
|
128
127
|
* artifacts that downstream tooling persists. Owns metadata validation,
|
|
129
|
-
*
|
|
130
|
-
* content-addressed `
|
|
128
|
+
* metadata synthesis/preservation, hint normalization, and the
|
|
129
|
+
* content-addressed `migrationHash` computation, but performs no file I/O
|
|
131
130
|
* — callers handle reads (to source `existing`) and writes (to persist
|
|
132
|
-
* `opsJson` / `
|
|
131
|
+
* `opsJson` / `metadataJson`).
|
|
133
132
|
*/
|
|
134
133
|
function buildMigrationArtifacts(instance, existing) {
|
|
135
134
|
const ops = instance.operations;
|
|
136
135
|
if (!Array.isArray(ops)) throw new Error("operations must be an array");
|
|
137
136
|
const parsed = MigrationMetaSchema(instance.describe());
|
|
138
137
|
if (parsed instanceof type.errors) throw new Error(`describe() returned invalid metadata: ${parsed.summary}`);
|
|
139
|
-
const
|
|
138
|
+
const metadata = buildAttestedMetadata(parsed, ops, existing);
|
|
140
139
|
return {
|
|
141
140
|
opsJson: JSON.stringify(ops, null, 2),
|
|
142
|
-
|
|
143
|
-
|
|
141
|
+
metadata,
|
|
142
|
+
metadataJson: JSON.stringify(metadata, null, 2)
|
|
144
143
|
};
|
|
145
144
|
}
|
|
146
145
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"migration.mjs","names":["baseManifest: Omit<MigrationManifest, 'migrationId'>"],"sources":["../../src/migration-base.ts"],"sourcesContent":["import { realpathSync } from 'node:fs';\nimport { fileURLToPath } from 'node:url';\nimport type { Contract } from '@prisma-next/contract/types';\nimport type {\n ControlStack,\n MigrationPlan,\n MigrationPlanOperation,\n} from '@prisma-next/framework-components/control';\nimport { ifDefined } from '@prisma-next/utils/defined';\nimport { type } from 'arktype';\nimport { computeMigrationId } from './attestation';\nimport type { MigrationHints, MigrationManifest, MigrationOps } from './types';\n\nexport interface MigrationMeta {\n readonly from: string;\n readonly to: string;\n readonly kind?: 'regular' | 'baseline';\n readonly labels?: readonly string[];\n}\n\nconst MigrationMetaSchema = type({\n from: 'string',\n to: 'string',\n 'kind?': \"'regular' | 'baseline'\",\n 'labels?': type('string').array(),\n});\n\n/**\n * Base class for migrations.\n *\n * A `Migration` subclass is itself a `MigrationPlan`: CLI commands and the\n * runner can consume it directly via `targetId`, `operations`, `origin`, and\n * `destination`. The manifest-shaped inputs come from `describe()`, which\n * every migration must implement — `migration.json` is required for a\n * migration to be valid.\n */\nexport abstract class Migration<\n TOperation extends MigrationPlanOperation = MigrationPlanOperation,\n TFamilyId extends string = string,\n TTargetId extends string = string,\n> implements MigrationPlan\n{\n abstract readonly targetId: string;\n\n /**\n * Assembled `ControlStack` injected by the orchestrator (`runMigration`).\n *\n * Subclasses (e.g. `PostgresMigration`) read the stack to materialize their\n * adapter once per instance. Optional at the abstract level so unit tests can\n * construct `Migration` instances purely for `operations` / `describe`\n * assertions without needing a real stack; concrete subclasses that need the\n * stack at runtime should narrow the parameter to required.\n */\n protected readonly stack: ControlStack<TFamilyId, TTargetId> | undefined;\n\n constructor(stack?: ControlStack<TFamilyId, TTargetId>) {\n this.stack = stack;\n }\n\n /**\n * Ordered list of operations this migration performs.\n *\n * Implemented as a getter so that subclasses can either precompute the list\n * in their constructor or build it lazily per access.\n */\n abstract get operations(): readonly TOperation[];\n\n /**\n * Metadata inputs used to build `migration.json` and to derive the plan's\n * origin/destination identities. Every migration must provide this —\n * omitting it would produce an invalid on-disk migration package.\n */\n abstract describe(): MigrationMeta;\n\n get origin(): { readonly storageHash: string } | null {\n const from = this.describe().from;\n // An empty `from` represents a migration with no prior origin (e.g.\n // initial baseline, or an in-process plan that was never persisted).\n // Surface that as a null origin so runners treat the plan as\n // origin-less rather than matching against an empty storage hash.\n return from === '' ? null : { storageHash: from };\n }\n\n get destination(): { readonly storageHash: string } {\n return { storageHash: this.describe().to };\n }\n}\n\n/**\n * Returns true when `import.meta.url` resolves to the same file that was\n * invoked as the node entrypoint (`process.argv[1]`). Used by\n * `MigrationCLI.run` (in `@prisma-next/cli/migration-cli`) to no-op when\n * the migration module is being imported (e.g. by another script) rather\n * than executed directly.\n */\nexport function isDirectEntrypoint(importMetaUrl: string): boolean {\n const metaFilename = fileURLToPath(importMetaUrl);\n const argv1 = process.argv[1];\n if (!argv1) return false;\n try {\n return realpathSync(metaFilename) === realpathSync(argv1);\n } catch {\n return false;\n }\n}\n\nexport function printMigrationHelp(): void {\n printHelp();\n}\n\nfunction printHelp(): void {\n process.stdout.write(\n [\n 'Usage: node <migration-file> [options]',\n '',\n 'Options:',\n ' --dry-run Print operations to stdout without writing files',\n ' --help Show this help message',\n '',\n ].join('\\n'),\n );\n}\n\n/**\n * In-memory artifacts produced from a `Migration` instance: the\n * serialized `ops.json` body, the `migration.json` manifest object, and\n * its serialized form. Returned by `buildMigrationArtifacts` so callers\n * (today: `MigrationCLI.run` in `@prisma-next/cli/migration-cli`) can\n * decide how to persist them — write to disk, print in dry-run, ship\n * over the wire — without coupling artifact construction to file I/O.\n */\nexport interface MigrationArtifacts {\n readonly opsJson: string;\n readonly manifest: MigrationManifest;\n readonly manifestJson: string;\n}\n\n/**\n * Build the attested manifest from `describe()`-derived metadata, the\n * operations list, and the previously-scaffolded manifest (if any).\n *\n * When a `migration.json` already exists for this package (the common\n * case: it was scaffolded by `migration plan`), preserve the contract\n * bookends, hints, labels, and `createdAt` set there — those fields are\n * owned by the CLI scaffolder, not the authored class. Only the\n * `describe()`-derived fields (`from`, `to`, `kind`) and the operations\n * change as the author iterates. When no manifest exists yet (a bare\n * `migration.ts` run from scratch), synthesize a minimal but\n * schema-conformant manifest so the resulting package can still be read,\n * verified, and applied.\n *\n * The `migrationId` is recomputed against the current manifest + ops so\n * the on-disk artifacts are always fully attested.\n */\nfunction buildAttestedManifest(\n meta: MigrationMeta,\n ops: MigrationOps,\n existing: Partial<MigrationManifest> | null,\n): MigrationManifest {\n const baseManifest: Omit<MigrationManifest, 'migrationId'> = {\n from: meta.from,\n to: meta.to,\n kind: meta.kind ?? 'regular',\n labels: meta.labels ?? existing?.labels ?? [],\n createdAt: existing?.createdAt ?? new Date().toISOString(),\n fromContract: existing?.fromContract ?? null,\n // When no scaffolded manifest exists we synthesize a minimal contract\n // stub so the package is still readable end-to-end. The cast is\n // intentional: only the storage bookend matters for hash computation\n // (everything else is stripped by `computeMigrationId`), and a real\n // contract bookend would only be available after `migration plan`.\n toContract: existing?.toContract ?? ({ storage: { storageHash: meta.to } } as Contract),\n hints: normalizeHints(existing?.hints),\n ...ifDefined('authorship', existing?.authorship),\n };\n\n const migrationId = computeMigrationId(baseManifest, ops);\n return { ...baseManifest, migrationId };\n}\n\n/**\n * Project `existing.hints` down to the known `MigrationHints` shape, dropping\n * any legacy keys that may linger in manifests scaffolded by older CLI\n * versions (e.g. `planningStrategy`). Picking fields explicitly instead of\n * spreading keeps refreshed `migration.json` files schema-clean regardless\n * of what was on disk before.\n */\nfunction normalizeHints(existing: MigrationHints | undefined): MigrationHints {\n return {\n used: existing?.used ?? [],\n applied: existing?.applied ?? [],\n plannerVersion: existing?.plannerVersion ?? '2.0.0',\n };\n}\n\n/**\n * Pure conversion from a `Migration` instance (plus the previously\n * scaffolded manifest, when one exists on disk) to the in-memory\n * artifacts that downstream tooling persists. Owns metadata validation,\n * manifest synthesis/preservation, hint normalization, and the\n * content-addressed `migrationId` computation, but performs no file I/O\n * — callers handle reads (to source `existing`) and writes (to persist\n * `opsJson` / `manifestJson`).\n */\nexport function buildMigrationArtifacts(\n instance: Migration,\n existing: Partial<MigrationManifest> | null,\n): MigrationArtifacts {\n const ops = instance.operations;\n if (!Array.isArray(ops)) {\n throw new Error('operations must be an array');\n }\n\n const rawMeta: unknown = instance.describe();\n const parsed = MigrationMetaSchema(rawMeta);\n if (parsed instanceof type.errors) {\n throw new Error(`describe() returned invalid metadata: ${parsed.summary}`);\n }\n\n const manifest = buildAttestedManifest(parsed, ops, existing);\n\n return {\n opsJson: JSON.stringify(ops, null, 2),\n manifest,\n manifestJson: JSON.stringify(manifest, null, 2),\n };\n}\n"],"mappings":";;;;;;;;AAoBA,MAAM,sBAAsB,KAAK;CAC/B,MAAM;CACN,IAAI;CACJ,SAAS;CACT,WAAW,KAAK,SAAS,CAAC,OAAO;CAClC,CAAC;;;;;;;;;;AAWF,IAAsB,YAAtB,MAKA;;;;;;;;;;CAYE,AAAmB;CAEnB,YAAY,OAA4C;AACtD,OAAK,QAAQ;;CAkBf,IAAI,SAAkD;EACpD,MAAM,OAAO,KAAK,UAAU,CAAC;AAK7B,SAAO,SAAS,KAAK,OAAO,EAAE,aAAa,MAAM;;CAGnD,IAAI,cAAgD;AAClD,SAAO,EAAE,aAAa,KAAK,UAAU,CAAC,IAAI;;;;;;;;;;AAW9C,SAAgB,mBAAmB,eAAgC;CACjE,MAAM,eAAe,cAAc,cAAc;CACjD,MAAM,QAAQ,QAAQ,KAAK;AAC3B,KAAI,CAAC,MAAO,QAAO;AACnB,KAAI;AACF,SAAO,aAAa,aAAa,KAAK,aAAa,MAAM;SACnD;AACN,SAAO;;;AAIX,SAAgB,qBAA2B;AACzC,YAAW;;AAGb,SAAS,YAAkB;AACzB,SAAQ,OAAO,MACb;EACE;EACA;EACA;EACA;EACA;EACA;EACD,CAAC,KAAK,KAAK,CACb;;;;;;;;;;;;;;;;;;;AAkCH,SAAS,sBACP,MACA,KACA,UACmB;CACnB,MAAMA,eAAuD;EAC3D,MAAM,KAAK;EACX,IAAI,KAAK;EACT,MAAM,KAAK,QAAQ;EACnB,QAAQ,KAAK,UAAU,UAAU,UAAU,EAAE;EAC7C,WAAW,UAAU,8BAAa,IAAI,MAAM,EAAC,aAAa;EAC1D,cAAc,UAAU,gBAAgB;EAMxC,YAAY,UAAU,cAAe,EAAE,SAAS,EAAE,aAAa,KAAK,IAAI,EAAE;EAC1E,OAAO,eAAe,UAAU,MAAM;EACtC,GAAG,UAAU,cAAc,UAAU,WAAW;EACjD;CAED,MAAM,cAAc,mBAAmB,cAAc,IAAI;AACzD,QAAO;EAAE,GAAG;EAAc;EAAa;;;;;;;;;AAUzC,SAAS,eAAe,UAAsD;AAC5E,QAAO;EACL,MAAM,UAAU,QAAQ,EAAE;EAC1B,SAAS,UAAU,WAAW,EAAE;EAChC,gBAAgB,UAAU,kBAAkB;EAC7C;;;;;;;;;;;AAYH,SAAgB,wBACd,UACA,UACoB;CACpB,MAAM,MAAM,SAAS;AACrB,KAAI,CAAC,MAAM,QAAQ,IAAI,CACrB,OAAM,IAAI,MAAM,8BAA8B;CAIhD,MAAM,SAAS,oBADU,SAAS,UAAU,CACD;AAC3C,KAAI,kBAAkB,KAAK,OACzB,OAAM,IAAI,MAAM,yCAAyC,OAAO,UAAU;CAG5E,MAAM,WAAW,sBAAsB,QAAQ,KAAK,SAAS;AAE7D,QAAO;EACL,SAAS,KAAK,UAAU,KAAK,MAAM,EAAE;EACrC;EACA,cAAc,KAAK,UAAU,UAAU,MAAM,EAAE;EAChD"}
|
|
1
|
+
{"version":3,"file":"migration.mjs","names":["baseMetadata: Omit<MigrationMetadata, 'migrationHash'>"],"sources":["../../src/migration-base.ts"],"sourcesContent":["import { realpathSync } from 'node:fs';\nimport { fileURLToPath } from 'node:url';\nimport type { Contract } from '@prisma-next/contract/types';\nimport type {\n ControlStack,\n MigrationPlan,\n MigrationPlanOperation,\n} from '@prisma-next/framework-components/control';\nimport { ifDefined } from '@prisma-next/utils/defined';\nimport { type } from 'arktype';\nimport { computeMigrationHash } from './hash';\nimport type { MigrationHints, MigrationMetadata } from './metadata';\nimport type { MigrationOps } from './package';\n\nexport interface MigrationMeta {\n readonly from: string;\n readonly to: string;\n readonly kind?: 'regular' | 'baseline';\n readonly labels?: readonly string[];\n}\n\nconst MigrationMetaSchema = type({\n from: 'string',\n to: 'string',\n 'kind?': \"'regular' | 'baseline'\",\n 'labels?': type('string').array(),\n});\n\n/**\n * Base class for migrations.\n *\n * A `Migration` subclass is itself a `MigrationPlan`: CLI commands and the\n * runner can consume it directly via `targetId`, `operations`, `origin`, and\n * `destination`. The metadata-shaped inputs come from `describe()`, which\n * every migration must implement — `migration.json` is required for a\n * migration to be valid.\n */\nexport abstract class Migration<\n TOperation extends MigrationPlanOperation = MigrationPlanOperation,\n TFamilyId extends string = string,\n TTargetId extends string = string,\n> implements MigrationPlan\n{\n abstract readonly targetId: string;\n\n /**\n * Assembled `ControlStack` injected by the orchestrator (`runMigration`).\n *\n * Subclasses (e.g. `PostgresMigration`) read the stack to materialize their\n * adapter once per instance. Optional at the abstract level so unit tests can\n * construct `Migration` instances purely for `operations` / `describe`\n * assertions without needing a real stack; concrete subclasses that need the\n * stack at runtime should narrow the parameter to required.\n */\n protected readonly stack: ControlStack<TFamilyId, TTargetId> | undefined;\n\n constructor(stack?: ControlStack<TFamilyId, TTargetId>) {\n this.stack = stack;\n }\n\n /**\n * Ordered list of operations this migration performs.\n *\n * Implemented as a getter so that subclasses can either precompute the list\n * in their constructor or build it lazily per access.\n */\n abstract get operations(): readonly TOperation[];\n\n /**\n * Metadata inputs used to build `migration.json` and to derive the plan's\n * origin/destination identities. Every migration must provide this —\n * omitting it would produce an invalid on-disk migration package.\n */\n abstract describe(): MigrationMeta;\n\n get origin(): { readonly storageHash: string } | null {\n const from = this.describe().from;\n // An empty `from` represents a migration with no prior origin (e.g.\n // initial baseline, or an in-process plan that was never persisted).\n // Surface that as a null origin so runners treat the plan as\n // origin-less rather than matching against an empty storage hash.\n return from === '' ? null : { storageHash: from };\n }\n\n get destination(): { readonly storageHash: string } {\n return { storageHash: this.describe().to };\n }\n}\n\n/**\n * Returns true when `import.meta.url` resolves to the same file that was\n * invoked as the node entrypoint (`process.argv[1]`). Used by\n * `MigrationCLI.run` (in `@prisma-next/cli/migration-cli`) to no-op when\n * the migration module is being imported (e.g. by another script) rather\n * than executed directly.\n */\nexport function isDirectEntrypoint(importMetaUrl: string): boolean {\n const metaFilename = fileURLToPath(importMetaUrl);\n const argv1 = process.argv[1];\n if (!argv1) return false;\n try {\n return realpathSync(metaFilename) === realpathSync(argv1);\n } catch {\n return false;\n }\n}\n\nexport function printMigrationHelp(): void {\n printHelp();\n}\n\nfunction printHelp(): void {\n process.stdout.write(\n [\n 'Usage: node <migration-file> [options]',\n '',\n 'Options:',\n ' --dry-run Print operations to stdout without writing files',\n ' --help Show this help message',\n '',\n ].join('\\n'),\n );\n}\n\n/**\n * In-memory artifacts produced from a `Migration` instance: the\n * serialized `ops.json` body, the `migration.json` metadata object, and\n * its serialized form. Returned by `buildMigrationArtifacts` so callers\n * (today: `MigrationCLI.run` in `@prisma-next/cli/migration-cli`) can\n * decide how to persist them — write to disk, print in dry-run, ship\n * over the wire — without coupling artifact construction to file I/O.\n *\n * `metadataJson` is `JSON.stringify(metadata, null, 2)` — the canonical\n * on-disk shape that the arktype loader-schema in `./io` validates.\n */\nexport interface MigrationArtifacts {\n readonly opsJson: string;\n readonly metadata: MigrationMetadata;\n readonly metadataJson: string;\n}\n\n/**\n * Build the attested metadata from `describe()`-derived metadata, the\n * operations list, and the previously-scaffolded metadata (if any).\n *\n * When a `migration.json` already exists for this package (the common\n * case: it was scaffolded by `migration plan`), preserve the contract\n * bookends, hints, labels, and `createdAt` set there — those fields are\n * owned by the CLI scaffolder, not the authored class. Only the\n * `describe()`-derived fields (`from`, `to`, `kind`) and the operations\n * change as the author iterates. When no metadata exists yet (a bare\n * `migration.ts` run from scratch), synthesize a minimal but\n * schema-conformant record so the resulting package can still be read,\n * verified, and applied.\n *\n * The `migrationHash` is recomputed against the current metadata + ops so\n * the on-disk artifacts are always fully attested.\n */\nfunction buildAttestedMetadata(\n meta: MigrationMeta,\n ops: MigrationOps,\n existing: Partial<MigrationMetadata> | null,\n): MigrationMetadata {\n const baseMetadata: Omit<MigrationMetadata, 'migrationHash'> = {\n from: meta.from,\n to: meta.to,\n kind: meta.kind ?? 'regular',\n labels: meta.labels ?? existing?.labels ?? [],\n createdAt: existing?.createdAt ?? new Date().toISOString(),\n fromContract: existing?.fromContract ?? null,\n // When no scaffolded metadata exists we synthesize a minimal contract\n // stub so the package is still readable end-to-end. The cast is\n // intentional: only the storage bookend matters for hash computation\n // (everything else is stripped by `computeMigrationHash`), and a real\n // contract bookend would only be available after `migration plan`.\n toContract: existing?.toContract ?? ({ storage: { storageHash: meta.to } } as Contract),\n hints: normalizeHints(existing?.hints),\n ...ifDefined('authorship', existing?.authorship),\n };\n\n const migrationHash = computeMigrationHash(baseMetadata, ops);\n return { ...baseMetadata, migrationHash };\n}\n\n/**\n * Project `existing.hints` down to the known `MigrationHints` shape, dropping\n * any legacy keys that may linger in metadata scaffolded by older CLI\n * versions (e.g. `planningStrategy`). Picking fields explicitly instead of\n * spreading keeps refreshed `migration.json` files schema-clean regardless\n * of what was on disk before.\n */\nfunction normalizeHints(existing: MigrationHints | undefined): MigrationHints {\n return {\n used: existing?.used ?? [],\n applied: existing?.applied ?? [],\n plannerVersion: existing?.plannerVersion ?? '2.0.0',\n };\n}\n\n/**\n * Pure conversion from a `Migration` instance (plus the previously\n * scaffolded metadata, when one exists on disk) to the in-memory\n * artifacts that downstream tooling persists. Owns metadata validation,\n * metadata synthesis/preservation, hint normalization, and the\n * content-addressed `migrationHash` computation, but performs no file I/O\n * — callers handle reads (to source `existing`) and writes (to persist\n * `opsJson` / `metadataJson`).\n */\nexport function buildMigrationArtifacts(\n instance: Migration,\n existing: Partial<MigrationMetadata> | null,\n): MigrationArtifacts {\n const ops = instance.operations;\n if (!Array.isArray(ops)) {\n throw new Error('operations must be an array');\n }\n\n const rawMeta: unknown = instance.describe();\n const parsed = MigrationMetaSchema(rawMeta);\n if (parsed instanceof type.errors) {\n throw new Error(`describe() returned invalid metadata: ${parsed.summary}`);\n }\n\n const metadata = buildAttestedMetadata(parsed, ops, existing);\n\n return {\n opsJson: JSON.stringify(ops, null, 2),\n metadata,\n metadataJson: JSON.stringify(metadata, null, 2),\n };\n}\n"],"mappings":";;;;;;;AAqBA,MAAM,sBAAsB,KAAK;CAC/B,MAAM;CACN,IAAI;CACJ,SAAS;CACT,WAAW,KAAK,SAAS,CAAC,OAAO;CAClC,CAAC;;;;;;;;;;AAWF,IAAsB,YAAtB,MAKA;;;;;;;;;;CAYE,AAAmB;CAEnB,YAAY,OAA4C;AACtD,OAAK,QAAQ;;CAkBf,IAAI,SAAkD;EACpD,MAAM,OAAO,KAAK,UAAU,CAAC;AAK7B,SAAO,SAAS,KAAK,OAAO,EAAE,aAAa,MAAM;;CAGnD,IAAI,cAAgD;AAClD,SAAO,EAAE,aAAa,KAAK,UAAU,CAAC,IAAI;;;;;;;;;;AAW9C,SAAgB,mBAAmB,eAAgC;CACjE,MAAM,eAAe,cAAc,cAAc;CACjD,MAAM,QAAQ,QAAQ,KAAK;AAC3B,KAAI,CAAC,MAAO,QAAO;AACnB,KAAI;AACF,SAAO,aAAa,aAAa,KAAK,aAAa,MAAM;SACnD;AACN,SAAO;;;AAIX,SAAgB,qBAA2B;AACzC,YAAW;;AAGb,SAAS,YAAkB;AACzB,SAAQ,OAAO,MACb;EACE;EACA;EACA;EACA;EACA;EACA;EACD,CAAC,KAAK,KAAK,CACb;;;;;;;;;;;;;;;;;;;AAqCH,SAAS,sBACP,MACA,KACA,UACmB;CACnB,MAAMA,eAAyD;EAC7D,MAAM,KAAK;EACX,IAAI,KAAK;EACT,MAAM,KAAK,QAAQ;EACnB,QAAQ,KAAK,UAAU,UAAU,UAAU,EAAE;EAC7C,WAAW,UAAU,8BAAa,IAAI,MAAM,EAAC,aAAa;EAC1D,cAAc,UAAU,gBAAgB;EAMxC,YAAY,UAAU,cAAe,EAAE,SAAS,EAAE,aAAa,KAAK,IAAI,EAAE;EAC1E,OAAO,eAAe,UAAU,MAAM;EACtC,GAAG,UAAU,cAAc,UAAU,WAAW;EACjD;CAED,MAAM,gBAAgB,qBAAqB,cAAc,IAAI;AAC7D,QAAO;EAAE,GAAG;EAAc;EAAe;;;;;;;;;AAU3C,SAAS,eAAe,UAAsD;AAC5E,QAAO;EACL,MAAM,UAAU,QAAQ,EAAE;EAC1B,SAAS,UAAU,WAAW,EAAE;EAChC,gBAAgB,UAAU,kBAAkB;EAC7C;;;;;;;;;;;AAYH,SAAgB,wBACd,UACA,UACoB;CACpB,MAAM,MAAM,SAAS;AACrB,KAAI,CAAC,MAAM,QAAQ,IAAI,CACrB,OAAM,IAAI,MAAM,8BAA8B;CAIhD,MAAM,SAAS,oBADU,SAAS,UAAU,CACD;AAC3C,KAAI,kBAAkB,KAAK,OACzB,OAAM,IAAI,MAAM,yCAAyC,OAAO,UAAU;CAG5E,MAAM,WAAW,sBAAsB,QAAQ,KAAK,SAAS;AAE7D,QAAO;EACL,SAAS,KAAK,UAAU,KAAK,MAAM,EAAE;EACrC;EACA,cAAc,KAAK,UAAU,UAAU,MAAM,EAAE;EAChD"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
package/dist/exports/refs.d.mts
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
//#region src/refs.d.ts
|
|
2
|
-
|
|
2
|
+
interface RefEntry {
|
|
3
|
+
readonly hash: string;
|
|
4
|
+
readonly invariants: readonly string[];
|
|
5
|
+
}
|
|
6
|
+
type Refs = Readonly<Record<string, RefEntry>>;
|
|
3
7
|
declare function validateRefName(name: string): boolean;
|
|
4
8
|
declare function validateRefValue(value: string): boolean;
|
|
5
|
-
declare function
|
|
6
|
-
declare function
|
|
7
|
-
declare function
|
|
9
|
+
declare function readRef(refsDir: string, name: string): Promise<RefEntry>;
|
|
10
|
+
declare function readRefs(refsDir: string): Promise<Refs>;
|
|
11
|
+
declare function writeRef(refsDir: string, name: string, entry: RefEntry): Promise<void>;
|
|
12
|
+
declare function deleteRef(refsDir: string, name: string): Promise<void>;
|
|
13
|
+
declare function resolveRef(refs: Refs, name: string): RefEntry;
|
|
8
14
|
//#endregion
|
|
9
|
-
export { type Refs, readRefs, resolveRef, validateRefName, validateRefValue,
|
|
15
|
+
export { type RefEntry, type Refs, deleteRef, readRef, readRefs, resolveRef, validateRefName, validateRefValue, writeRef };
|
|
10
16
|
//# sourceMappingURL=refs.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"refs.d.mts","names":[],"sources":["../../src/refs.ts"],"sourcesContent":[],"mappings":";
|
|
1
|
+
{"version":3,"file":"refs.d.mts","names":[],"sources":["../../src/refs.ts"],"sourcesContent":[],"mappings":";UAUiB,QAAA;EAAA,SAAA,IAAQ,EAAA,MAAA;EAKb,SAAI,UAAA,EAAA,SAAA,MAAA,EAAA;;AAAY,KAAhB,IAAA,GAAO,QAAS,CAAA,MAAA,CAAA,MAAA,EAAe,QAAf,CAAA,CAAA;AAAT,iBAKH,eAAA,CALG,IAAA,EAAA,MAAA,CAAA,EAAA,OAAA;AAAQ,iBAaX,gBAAA,CAbW,KAAA,EAAA,MAAA,CAAA,EAAA,OAAA;AAKX,iBA8BM,OAAA,CA9BS,OAAA,EAAA,MAAA,EAAA,IAAA,EAAA,MAAA,CAAA,EA8B+B,OA9B/B,CA8BuC,QA9BvC,CAAA;AAQf,iBAyDM,QAAA,CAzDU,OAAA,EAAA,MAAA,CAAA,EAyDiB,OAzDjB,CAyDyB,IAzDzB,CAAA;AAsBV,iBAsFA,QAAA,CAtFgD,OAAR,EAAA,MAAO,EAAA,IAAA,EAAA,MAAA,EAAA,KAAA,EAsFA,QAtFA,CAAA,EAsFW,OAtFX,CAAA,IAAA,CAAA;AAmC/C,iBAuEA,SAAA,CAvE2B,OAAO,EAAA,MAAA,EAAA,IAAA,EAAA,MAAA,CAAA,EAuEQ,OAvER,CAAA,IAAA,CAAA;AAmDlC,iBA0DN,UAAA,CA1DqD,IAAW,EA0D/C,IA1DsD,EAAA,IAAA,EAAA,MAAA,CAAA,EA0DjC,QA1DiC"}
|