@prisma-next/migration-tools 0.5.0-dev.8 → 0.5.0-dev.80
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 +34 -22
- package/dist/{constants-BRi0X7B_.mjs → constants-DWV9_o2Z.mjs} +2 -2
- package/dist/{constants-BRi0X7B_.mjs.map → constants-DWV9_o2Z.mjs.map} +1 -1
- package/dist/errors-EPL_9p9f.mjs +297 -0
- package/dist/errors-EPL_9p9f.mjs.map +1 -0
- package/dist/exports/aggregate.d.mts +614 -0
- package/dist/exports/aggregate.d.mts.map +1 -0
- package/dist/exports/aggregate.mjs +611 -0
- package/dist/exports/aggregate.mjs.map +1 -0
- package/dist/exports/constants.d.mts.map +1 -1
- package/dist/exports/constants.mjs +2 -3
- package/dist/exports/errors.d.mts +68 -0
- package/dist/exports/errors.d.mts.map +1 -0
- package/dist/exports/errors.mjs +2 -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 +2 -0
- package/dist/exports/invariants.d.mts +34 -0
- package/dist/exports/invariants.d.mts.map +1 -0
- package/dist/exports/invariants.mjs +2 -0
- package/dist/exports/io.d.mts +66 -6
- package/dist/exports/io.d.mts.map +1 -1
- package/dist/exports/io.mjs +2 -3
- package/dist/exports/metadata.d.mts +2 -0
- package/dist/exports/metadata.mjs +1 -0
- package/dist/exports/migration-graph.d.mts +2 -0
- package/dist/exports/migration-graph.mjs +2 -0
- package/dist/exports/migration-ts.d.mts.map +1 -1
- package/dist/exports/migration-ts.mjs +2 -4
- package/dist/exports/migration-ts.mjs.map +1 -1
- package/dist/exports/migration.d.mts +15 -14
- package/dist/exports/migration.d.mts.map +1 -1
- package/dist/exports/migration.mjs +70 -43
- package/dist/exports/migration.mjs.map +1 -1
- package/dist/exports/package.d.mts +3 -0
- package/dist/exports/package.mjs +1 -0
- package/dist/exports/refs.d.mts.map +1 -1
- package/dist/exports/refs.mjs +3 -4
- package/dist/exports/refs.mjs.map +1 -1
- package/dist/exports/spaces.d.mts +550 -0
- package/dist/exports/spaces.d.mts.map +1 -0
- package/dist/exports/spaces.mjs +223 -0
- package/dist/exports/spaces.mjs.map +1 -0
- package/dist/graph-HMWAldoR.d.mts +28 -0
- package/dist/graph-HMWAldoR.d.mts.map +1 -0
- package/dist/hash-By50zM_E.mjs +74 -0
- package/dist/hash-By50zM_E.mjs.map +1 -0
- package/dist/invariants-Duc8f9NM.mjs +52 -0
- package/dist/invariants-Duc8f9NM.mjs.map +1 -0
- package/dist/io-D13dLvUh.mjs +239 -0
- package/dist/io-D13dLvUh.mjs.map +1 -0
- package/dist/metadata-CFvm3ayn.d.mts +2 -0
- package/dist/migration-graph-DGNnKDY5.mjs +523 -0
- package/dist/migration-graph-DGNnKDY5.mjs.map +1 -0
- package/dist/migration-graph-DulOITvG.d.mts +124 -0
- package/dist/migration-graph-DulOITvG.d.mts.map +1 -0
- package/dist/op-schema-D5qkXfEf.mjs +13 -0
- package/dist/op-schema-D5qkXfEf.mjs.map +1 -0
- package/dist/package-BjiZ7KDy.d.mts +21 -0
- package/dist/package-BjiZ7KDy.d.mts.map +1 -0
- package/dist/read-contract-space-contract-C3-1eyaI.mjs +298 -0
- package/dist/read-contract-space-contract-C3-1eyaI.mjs.map +1 -0
- package/package.json +44 -19
- package/src/aggregate/loader.ts +409 -0
- package/src/aggregate/marker-types.ts +16 -0
- package/src/aggregate/planner-types.ts +171 -0
- package/src/aggregate/planner.ts +158 -0
- package/src/aggregate/project-schema-to-space.ts +64 -0
- package/src/aggregate/strategies/graph-walk.ts +118 -0
- package/src/aggregate/strategies/synth.ts +122 -0
- package/src/aggregate/types.ts +89 -0
- package/src/aggregate/verifier.ts +230 -0
- package/src/assert-descriptor-self-consistency.ts +70 -0
- package/src/compute-extension-space-apply-path.ts +152 -0
- package/src/concatenate-space-apply-inputs.ts +90 -0
- package/src/detect-space-contract-drift.ts +91 -0
- package/src/emit-contract-space-artefacts.ts +70 -0
- package/src/errors.ts +251 -17
- package/src/exports/aggregate.ts +42 -0
- package/src/exports/errors.ts +8 -0
- package/src/exports/graph.ts +1 -0
- package/src/exports/hash.ts +2 -0
- package/src/exports/invariants.ts +1 -0
- package/src/exports/io.ts +3 -1
- package/src/exports/metadata.ts +1 -0
- package/src/exports/{dag.ts → migration-graph.ts} +3 -2
- package/src/exports/migration.ts +0 -1
- package/src/exports/package.ts +2 -0
- package/src/exports/spaces.ts +49 -0
- package/src/gather-disk-contract-space-state.ts +62 -0
- package/src/graph-ops.ts +57 -30
- package/src/graph.ts +25 -0
- package/src/hash.ts +91 -0
- package/src/invariants.ts +56 -0
- package/src/io.ts +163 -40
- package/src/metadata.ts +1 -0
- package/src/migration-base.ts +97 -56
- package/src/migration-graph.ts +676 -0
- package/src/op-schema.ts +11 -0
- package/src/package.ts +21 -0
- package/src/plan-all-spaces.ts +76 -0
- package/src/read-contract-space-contract.ts +44 -0
- package/src/read-contract-space-head-ref.ts +63 -0
- package/src/space-layout.ts +48 -0
- package/src/verify-contract-spaces.ts +272 -0
- package/dist/attestation-BnzTb0Qp.mjs +0 -65
- package/dist/attestation-BnzTb0Qp.mjs.map +0 -1
- package/dist/errors-BmiSgz1j.mjs +0 -160
- package/dist/errors-BmiSgz1j.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/dag.d.mts +0 -51
- package/dist/exports/dag.d.mts.map +0 -1
- package/dist/exports/dag.mjs +0 -386
- package/dist/exports/dag.mjs.map +0 -1
- package/dist/exports/types.d.mts +0 -35
- package/dist/exports/types.d.mts.map +0 -1
- package/dist/exports/types.mjs +0 -3
- package/dist/io-Cd6GLyjK.mjs +0 -153
- package/dist/io-Cd6GLyjK.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/dag.ts +0 -426
- package/src/exports/attestation.ts +0 -2
- package/src/exports/types.ts +0 -10
- package/src/types.ts +0 -66
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import "../
|
|
2
|
-
import { t as
|
|
3
|
-
import {
|
|
1
|
+
import { T as errorStaleContractBookends, f as errorInvalidOperationEntry } from "../errors-EPL_9p9f.mjs";
|
|
2
|
+
import { t as computeMigrationHash } from "../hash-By50zM_E.mjs";
|
|
3
|
+
import { t as deriveProvidedInvariants } from "../invariants-Duc8f9NM.mjs";
|
|
4
|
+
import { t as MigrationOpSchema } from "../op-schema-D5qkXfEf.mjs";
|
|
4
5
|
import { ifDefined } from "@prisma-next/utils/defined";
|
|
6
|
+
import { type } from "arktype";
|
|
5
7
|
import { realpathSync } from "node:fs";
|
|
6
8
|
import { fileURLToPath } from "node:url";
|
|
7
|
-
|
|
8
9
|
//#region src/migration-base.ts
|
|
9
10
|
const MigrationMetaSchema = type({
|
|
10
|
-
from: "string",
|
|
11
|
+
from: "string > 0 | null",
|
|
11
12
|
to: "string",
|
|
12
|
-
"kind?": "'regular' | 'baseline'",
|
|
13
13
|
"labels?": type("string").array()
|
|
14
14
|
});
|
|
15
15
|
/**
|
|
@@ -17,7 +17,7 @@ const MigrationMetaSchema = type({
|
|
|
17
17
|
*
|
|
18
18
|
* A `Migration` subclass is itself a `MigrationPlan`: CLI commands and the
|
|
19
19
|
* runner can consume it directly via `targetId`, `operations`, `origin`, and
|
|
20
|
-
* `destination`. The
|
|
20
|
+
* `destination`. The metadata-shaped inputs come from `describe()`, which
|
|
21
21
|
* every migration must implement — `migration.json` is required for a
|
|
22
22
|
* migration to be valid.
|
|
23
23
|
*/
|
|
@@ -37,7 +37,7 @@ var Migration = class {
|
|
|
37
37
|
}
|
|
38
38
|
get origin() {
|
|
39
39
|
const from = this.describe().from;
|
|
40
|
-
return from ===
|
|
40
|
+
return from === null ? null : { storageHash: from };
|
|
41
41
|
}
|
|
42
42
|
get destination() {
|
|
43
43
|
return { storageHash: this.describe().to };
|
|
@@ -60,57 +60,80 @@ function isDirectEntrypoint(importMetaUrl) {
|
|
|
60
60
|
return false;
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
|
-
function printMigrationHelp() {
|
|
64
|
-
printHelp();
|
|
65
|
-
}
|
|
66
|
-
function printHelp() {
|
|
67
|
-
process.stdout.write([
|
|
68
|
-
"Usage: node <migration-file> [options]",
|
|
69
|
-
"",
|
|
70
|
-
"Options:",
|
|
71
|
-
" --dry-run Print operations to stdout without writing files",
|
|
72
|
-
" --help Show this help message",
|
|
73
|
-
""
|
|
74
|
-
].join("\n"));
|
|
75
|
-
}
|
|
76
63
|
/**
|
|
77
|
-
* Build the attested
|
|
78
|
-
* operations list, and the previously-scaffolded
|
|
64
|
+
* Build the attested metadata from `describe()`-derived metadata, the
|
|
65
|
+
* operations list, and the previously-scaffolded metadata (if any).
|
|
79
66
|
*
|
|
80
67
|
* When a `migration.json` already exists for this package (the common
|
|
81
68
|
* case: it was scaffolded by `migration plan`), preserve the contract
|
|
82
69
|
* bookends, hints, labels, and `createdAt` set there — those fields are
|
|
83
70
|
* owned by the CLI scaffolder, not the authored class. Only the
|
|
84
|
-
* `describe()`-derived fields (`from`, `to
|
|
85
|
-
* change as the author iterates. When no
|
|
71
|
+
* `describe()`-derived fields (`from`, `to`) and the operations
|
|
72
|
+
* change as the author iterates. When no metadata exists yet (a bare
|
|
86
73
|
* `migration.ts` run from scratch), synthesize a minimal but
|
|
87
|
-
* schema-conformant
|
|
74
|
+
* schema-conformant record so the resulting package can still be read,
|
|
88
75
|
* verified, and applied.
|
|
89
76
|
*
|
|
90
|
-
* The `
|
|
77
|
+
* The `migrationHash` is recomputed against the current metadata + ops so
|
|
91
78
|
* the on-disk artifacts are always fully attested.
|
|
92
79
|
*/
|
|
93
|
-
function
|
|
94
|
-
|
|
80
|
+
function buildAttestedMetadata(meta, ops, existing) {
|
|
81
|
+
assertBookendsMatchMeta(meta, existing);
|
|
82
|
+
const baseMetadata = {
|
|
95
83
|
from: meta.from,
|
|
96
84
|
to: meta.to,
|
|
97
|
-
kind: meta.kind ?? "regular",
|
|
98
85
|
labels: meta.labels ?? existing?.labels ?? [],
|
|
86
|
+
providedInvariants: deriveProvidedInvariants(ops),
|
|
99
87
|
createdAt: existing?.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
100
88
|
fromContract: existing?.fromContract ?? null,
|
|
101
89
|
toContract: existing?.toContract ?? { storage: { storageHash: meta.to } },
|
|
102
90
|
hints: normalizeHints(existing?.hints),
|
|
103
91
|
...ifDefined("authorship", existing?.authorship)
|
|
104
92
|
};
|
|
105
|
-
const
|
|
93
|
+
const migrationHash = computeMigrationHash(baseMetadata, ops);
|
|
106
94
|
return {
|
|
107
|
-
...
|
|
108
|
-
|
|
95
|
+
...baseMetadata,
|
|
96
|
+
migrationHash
|
|
109
97
|
};
|
|
110
98
|
}
|
|
111
99
|
/**
|
|
100
|
+
* Verify each preserved contract bookend in `existing` agrees with the
|
|
101
|
+
* corresponding side of `describe()`'s output. A mismatch indicates the
|
|
102
|
+
* migration's `describe()` was edited after `migration plan` scaffolded
|
|
103
|
+
* the package, leaving a self-inconsistent manifest. Failing fast at
|
|
104
|
+
* write-time turns a silent foot-gun into an actionable diagnostic.
|
|
105
|
+
*
|
|
106
|
+
* Skipped when a side's `existing.<side>Contract` is null/absent (the
|
|
107
|
+
* synthesis path stays open for origin-less initial migrations and for
|
|
108
|
+
* bare `migration.ts` runs from scratch). When a bookend is *present*
|
|
109
|
+
* but its `storage.storageHash` is missing, that's treated as a
|
|
110
|
+
* mismatch — a malformed bookend is not equivalent to "no bookend".
|
|
111
|
+
*
|
|
112
|
+
* This check is paired with TML-2274, which removes `fromContract` /
|
|
113
|
+
* `toContract` from the manifest entirely; once that lands, this
|
|
114
|
+
* function and its error code are deleted.
|
|
115
|
+
*/
|
|
116
|
+
function assertBookendsMatchMeta(meta, existing) {
|
|
117
|
+
if (existing?.fromContract != null) {
|
|
118
|
+
const contractHash = existing.fromContract.storage?.storageHash ?? "";
|
|
119
|
+
if (contractHash !== meta.from) throw errorStaleContractBookends({
|
|
120
|
+
side: "from",
|
|
121
|
+
metaHash: meta.from,
|
|
122
|
+
contractHash
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
if (existing?.toContract != null) {
|
|
126
|
+
const contractHash = existing.toContract.storage?.storageHash ?? "";
|
|
127
|
+
if (contractHash !== meta.to) throw errorStaleContractBookends({
|
|
128
|
+
side: "to",
|
|
129
|
+
metaHash: meta.to,
|
|
130
|
+
contractHash
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
112
135
|
* Project `existing.hints` down to the known `MigrationHints` shape, dropping
|
|
113
|
-
* any legacy keys that may linger in
|
|
136
|
+
* any legacy keys that may linger in metadata scaffolded by older CLI
|
|
114
137
|
* versions (e.g. `planningStrategy`). Picking fields explicitly instead of
|
|
115
138
|
* spreading keeps refreshed `migration.json` files schema-clean regardless
|
|
116
139
|
* of what was on disk before.
|
|
@@ -124,26 +147,30 @@ function normalizeHints(existing) {
|
|
|
124
147
|
}
|
|
125
148
|
/**
|
|
126
149
|
* Pure conversion from a `Migration` instance (plus the previously
|
|
127
|
-
* scaffolded
|
|
150
|
+
* scaffolded metadata, when one exists on disk) to the in-memory
|
|
128
151
|
* artifacts that downstream tooling persists. Owns metadata validation,
|
|
129
|
-
*
|
|
130
|
-
* content-addressed `
|
|
152
|
+
* metadata synthesis/preservation, hint normalization, and the
|
|
153
|
+
* content-addressed `migrationHash` computation, but performs no file I/O
|
|
131
154
|
* — callers handle reads (to source `existing`) and writes (to persist
|
|
132
|
-
* `opsJson` / `
|
|
155
|
+
* `opsJson` / `metadataJson`).
|
|
133
156
|
*/
|
|
134
157
|
function buildMigrationArtifacts(instance, existing) {
|
|
135
158
|
const ops = instance.operations;
|
|
136
159
|
if (!Array.isArray(ops)) throw new Error("operations must be an array");
|
|
160
|
+
for (let index = 0; index < ops.length; index++) {
|
|
161
|
+
const result = MigrationOpSchema(ops[index]);
|
|
162
|
+
if (result instanceof type.errors) throw errorInvalidOperationEntry(index, result.summary);
|
|
163
|
+
}
|
|
137
164
|
const parsed = MigrationMetaSchema(instance.describe());
|
|
138
165
|
if (parsed instanceof type.errors) throw new Error(`describe() returned invalid metadata: ${parsed.summary}`);
|
|
139
|
-
const
|
|
166
|
+
const metadata = buildAttestedMetadata(parsed, ops, existing);
|
|
140
167
|
return {
|
|
141
168
|
opsJson: JSON.stringify(ops, null, 2),
|
|
142
|
-
|
|
143
|
-
|
|
169
|
+
metadata,
|
|
170
|
+
metadataJson: JSON.stringify(metadata, null, 2)
|
|
144
171
|
};
|
|
145
172
|
}
|
|
146
|
-
|
|
147
173
|
//#endregion
|
|
148
|
-
export { Migration, buildMigrationArtifacts, isDirectEntrypoint
|
|
174
|
+
export { Migration, buildMigrationArtifacts, isDirectEntrypoint };
|
|
175
|
+
|
|
149
176
|
//# sourceMappingURL=migration.mjs.map
|
|
@@ -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":[],"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 { errorInvalidOperationEntry, errorStaleContractBookends } from './errors';\nimport { computeMigrationHash } from './hash';\nimport { deriveProvidedInvariants } from './invariants';\nimport type { MigrationHints, MigrationMetadata } from './metadata';\nimport { MigrationOpSchema } from './op-schema';\nimport type { MigrationOps } from './package';\n\nexport interface MigrationMeta {\n readonly from: string | null;\n readonly to: string;\n readonly labels?: readonly string[];\n}\n\n// `from` rejects empty strings to mirror `MigrationMetadataSchema` in\n// `./io.ts`. Without this match, an authored migration could `describe()` with\n// `from: ''` and pass `buildMigrationArtifacts`'s validation, only to have\n// `readMigrationPackage` reject the resulting `migration.json` later — the\n// two validators must agree on the legal value space.\nconst MigrationMetaSchema = type({\n from: 'string > 0 | null',\n to: 'string',\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 return from === null ? 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\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`) 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 assertBookendsMatchMeta(meta, existing);\n\n const baseMetadata: Omit<MigrationMetadata, 'migrationHash'> = {\n from: meta.from,\n to: meta.to,\n labels: meta.labels ?? existing?.labels ?? [],\n providedInvariants: deriveProvidedInvariants(ops),\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 * Verify each preserved contract bookend in `existing` agrees with the\n * corresponding side of `describe()`'s output. A mismatch indicates the\n * migration's `describe()` was edited after `migration plan` scaffolded\n * the package, leaving a self-inconsistent manifest. Failing fast at\n * write-time turns a silent foot-gun into an actionable diagnostic.\n *\n * Skipped when a side's `existing.<side>Contract` is null/absent (the\n * synthesis path stays open for origin-less initial migrations and for\n * bare `migration.ts` runs from scratch). When a bookend is *present*\n * but its `storage.storageHash` is missing, that's treated as a\n * mismatch — a malformed bookend is not equivalent to \"no bookend\".\n *\n * This check is paired with TML-2274, which removes `fromContract` /\n * `toContract` from the manifest entirely; once that lands, this\n * function and its error code are deleted.\n */\nfunction assertBookendsMatchMeta(\n meta: MigrationMeta,\n existing: Partial<MigrationMetadata> | null,\n): void {\n if (existing?.fromContract != null) {\n const contractHash = existing.fromContract.storage?.storageHash ?? '';\n if (contractHash !== meta.from) {\n throw errorStaleContractBookends({\n side: 'from',\n metaHash: meta.from,\n contractHash,\n });\n }\n }\n if (existing?.toContract != null) {\n const contractHash = existing.toContract.storage?.storageHash ?? '';\n if (contractHash !== meta.to) {\n throw errorStaleContractBookends({\n side: 'to',\n metaHash: meta.to,\n contractHash,\n });\n }\n }\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 for (let index = 0; index < ops.length; index++) {\n const result = MigrationOpSchema(ops[index]);\n if (result instanceof type.errors) {\n throw errorInvalidOperationEntry(index, result.summary);\n }\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":";;;;;;;;;AA4BA,MAAM,sBAAsB,KAAK;CAC/B,MAAM;CACN,IAAI;CACJ,WAAW,KAAK,SAAS,CAAC,OAAO;CAClC,CAAC;;;;;;;;;;AAWF,IAAsB,YAAtB,MAKA;;;;;;;;;;CAYE;CAEA,YAAY,OAA4C;EACtD,KAAK,QAAQ;;CAkBf,IAAI,SAAkD;EACpD,MAAM,OAAO,KAAK,UAAU,CAAC;EAC7B,OAAO,SAAS,OAAO,OAAO,EAAE,aAAa,MAAM;;CAGrD,IAAI,cAAgD;EAClD,OAAO,EAAE,aAAa,KAAK,UAAU,CAAC,IAAI;;;;;;;;;;AAW9C,SAAgB,mBAAmB,eAAgC;CACjE,MAAM,eAAe,cAAc,cAAc;CACjD,MAAM,QAAQ,QAAQ,KAAK;CAC3B,IAAI,CAAC,OAAO,OAAO;CACnB,IAAI;EACF,OAAO,aAAa,aAAa,KAAK,aAAa,MAAM;SACnD;EACN,OAAO;;;;;;;;;;;;;;;;;;;;AAsCX,SAAS,sBACP,MACA,KACA,UACmB;CACnB,wBAAwB,MAAM,SAAS;CAEvC,MAAM,eAAyD;EAC7D,MAAM,KAAK;EACX,IAAI,KAAK;EACT,QAAQ,KAAK,UAAU,UAAU,UAAU,EAAE;EAC7C,oBAAoB,yBAAyB,IAAI;EACjD,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;CAC7D,OAAO;EAAE,GAAG;EAAc;EAAe;;;;;;;;;;;;;;;;;;;AAoB3C,SAAS,wBACP,MACA,UACM;CACN,IAAI,UAAU,gBAAgB,MAAM;EAClC,MAAM,eAAe,SAAS,aAAa,SAAS,eAAe;EACnE,IAAI,iBAAiB,KAAK,MACxB,MAAM,2BAA2B;GAC/B,MAAM;GACN,UAAU,KAAK;GACf;GACD,CAAC;;CAGN,IAAI,UAAU,cAAc,MAAM;EAChC,MAAM,eAAe,SAAS,WAAW,SAAS,eAAe;EACjE,IAAI,iBAAiB,KAAK,IACxB,MAAM,2BAA2B;GAC/B,MAAM;GACN,UAAU,KAAK;GACf;GACD,CAAC;;;;;;;;;;AAYR,SAAS,eAAe,UAAsD;CAC5E,OAAO;EACL,MAAM,UAAU,QAAQ,EAAE;EAC1B,SAAS,UAAU,WAAW,EAAE;EAChC,gBAAgB,UAAU,kBAAkB;EAC7C;;;;;;;;;;;AAYH,SAAgB,wBACd,UACA,UACoB;CACpB,MAAM,MAAM,SAAS;CACrB,IAAI,CAAC,MAAM,QAAQ,IAAI,EACrB,MAAM,IAAI,MAAM,8BAA8B;CAGhD,KAAK,IAAI,QAAQ,GAAG,QAAQ,IAAI,QAAQ,SAAS;EAC/C,MAAM,SAAS,kBAAkB,IAAI,OAAO;EAC5C,IAAI,kBAAkB,KAAK,QACzB,MAAM,2BAA2B,OAAO,OAAO,QAAQ;;CAK3D,MAAM,SAAS,oBADU,SAAS,UACQ,CAAC;CAC3C,IAAI,kBAAkB,KAAK,QACzB,MAAM,IAAI,MAAM,yCAAyC,OAAO,UAAU;CAG5E,MAAM,WAAW,sBAAsB,QAAQ,KAAK,SAAS;CAE7D,OAAO;EACL,SAAS,KAAK,UAAU,KAAK,MAAM,EAAE;EACrC;EACA,cAAc,KAAK,UAAU,UAAU,MAAM,EAAE;EAChD"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"refs.d.mts","names":[],"sources":["../../src/refs.ts"],"
|
|
1
|
+
{"version":3,"file":"refs.d.mts","names":[],"sources":["../../src/refs.ts"],"mappings":";UAUiB,QAAA;EAAA,SACN,IAAA;EAAA,SACA,UAAA;AAAA;AAAA,KAGC,IAAA,GAAO,QAAA,CAAS,MAAA,SAAe,QAAA;AAAA,iBAK3B,eAAA,CAAgB,IAAA;AAAA,iBAQhB,gBAAA,CAAiB,KAAA;AAAA,iBAsBX,OAAA,CAAQ,OAAA,UAAiB,IAAA,WAAe,OAAA,CAAQ,QAAA;AAAA,iBAmChD,QAAA,CAAS,OAAA,WAAkB,OAAA,CAAQ,IAAA;AAAA,iBAmDnC,QAAA,CAAS,OAAA,UAAiB,IAAA,UAAc,KAAA,EAAO,QAAA,GAAW,OAAA;AAAA,iBAoB1D,SAAA,CAAU,OAAA,UAAiB,IAAA,WAAe,OAAA;AAAA,iBAsChD,UAAA,CAAW,IAAA,EAAM,IAAA,EAAM,IAAA,WAAe,QAAA"}
|
package/dist/exports/refs.mjs
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { h as errorInvalidRefValue, m as errorInvalidRefName, p as errorInvalidRefFile, t as MigrationToolsError } from "../errors-EPL_9p9f.mjs";
|
|
2
|
+
import { dirname, join, relative } from "pathe";
|
|
2
3
|
import { mkdir, readFile, readdir, rename, rmdir, unlink, writeFile } from "node:fs/promises";
|
|
3
4
|
import { type } from "arktype";
|
|
4
|
-
import { dirname, join, relative } from "pathe";
|
|
5
|
-
|
|
6
5
|
//#region src/refs.ts
|
|
7
6
|
const REF_NAME_PATTERN = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\/[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/;
|
|
8
7
|
const REF_VALUE_PATTERN = /^sha256:(empty|[0-9a-f]{64})$/;
|
|
@@ -143,7 +142,7 @@ function resolveRef(refs, name) {
|
|
|
143
142
|
});
|
|
144
143
|
return refs[name];
|
|
145
144
|
}
|
|
146
|
-
|
|
147
145
|
//#endregion
|
|
148
146
|
export { deleteRef, readRef, readRefs, resolveRef, validateRefName, validateRefValue, writeRef };
|
|
147
|
+
|
|
149
148
|
//# sourceMappingURL=refs.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"refs.mjs","names":["raw: string","parsed: unknown","entries: string[]","refs: Record<string, RefEntry>"],"sources":["../../src/refs.ts"],"sourcesContent":["import { mkdir, readdir, readFile, rename, rmdir, unlink, writeFile } from 'node:fs/promises';\nimport { type } from 'arktype';\nimport { dirname, join, relative } from 'pathe';\nimport {\n errorInvalidRefFile,\n errorInvalidRefName,\n errorInvalidRefValue,\n MigrationToolsError,\n} from './errors';\n\nexport interface RefEntry {\n readonly hash: string;\n readonly invariants: readonly string[];\n}\n\nexport type Refs = Readonly<Record<string, RefEntry>>;\n\nconst REF_NAME_PATTERN = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\/[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/;\nconst REF_VALUE_PATTERN = /^sha256:(empty|[0-9a-f]{64})$/;\n\nexport function validateRefName(name: string): boolean {\n if (name.length === 0) return false;\n if (name.includes('..')) return false;\n if (name.includes('//')) return false;\n if (name.startsWith('.')) return false;\n return REF_NAME_PATTERN.test(name);\n}\n\nexport function validateRefValue(value: string): boolean {\n return REF_VALUE_PATTERN.test(value);\n}\n\nconst RefEntrySchema = type({\n hash: 'string',\n invariants: 'string[]',\n}).narrow((entry, ctx) => {\n if (!validateRefValue(entry.hash))\n return ctx.mustBe(`a valid contract hash (got \"${entry.hash}\")`);\n return true;\n});\n\nfunction refFilePath(refsDir: string, name: string): string {\n return join(refsDir, `${name}.json`);\n}\n\nfunction refNameFromPath(refsDir: string, filePath: string): string {\n const rel = relative(refsDir, filePath);\n return rel.replace(/\\.json$/, '');\n}\n\nexport async function readRef(refsDir: string, name: string): Promise<RefEntry> {\n if (!validateRefName(name)) {\n throw errorInvalidRefName(name);\n }\n\n const filePath = refFilePath(refsDir, name);\n let raw: string;\n try {\n raw = await readFile(filePath, 'utf-8');\n } catch (error) {\n if (error instanceof Error && (error as { code?: string }).code === 'ENOENT') {\n throw new MigrationToolsError('MIGRATION.UNKNOWN_REF', `Unknown ref \"${name}\"`, {\n why: `No ref file found at \"${filePath}\".`,\n fix: `Create the ref with: prisma-next migration ref set ${name} <hash>`,\n details: { refName: name, filePath },\n });\n }\n throw error;\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n throw errorInvalidRefFile(filePath, 'Failed to parse as JSON');\n }\n\n const result = RefEntrySchema(parsed);\n if (result instanceof type.errors) {\n throw errorInvalidRefFile(filePath, result.summary);\n }\n\n return result;\n}\n\nexport async function readRefs(refsDir: string): Promise<Refs> {\n let entries: string[];\n try {\n entries = await readdir(refsDir, { recursive: true, encoding: 'utf-8' });\n } catch (error) {\n if (error instanceof Error && (error as { code?: string }).code === 'ENOENT') {\n return {};\n }\n throw error;\n }\n\n const jsonFiles = entries.filter((entry) => entry.endsWith('.json'));\n const refs: Record<string, RefEntry> = {};\n\n for (const jsonFile of jsonFiles) {\n const filePath = join(refsDir, jsonFile);\n const name = refNameFromPath(refsDir, filePath);\n\n let raw: string;\n try {\n raw = await readFile(filePath, 'utf-8');\n } catch (error) {\n // Tolerate the TOCTOU race between `readdir` and `readFile` (ENOENT) and\n // benign EISDIR if a directory happens to end in `.json`. Anything else\n // (EACCES, EIO, EMFILE, …) is a real failure and propagates so the CLI\n // surfaces it rather than silently dropping the ref.\n const code = error instanceof Error ? (error as { code?: string }).code : undefined;\n if (code === 'ENOENT' || code === 'EISDIR') {\n continue;\n }\n throw error;\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n throw errorInvalidRefFile(filePath, 'Failed to parse as JSON');\n }\n\n const result = RefEntrySchema(parsed);\n if (result instanceof type.errors) {\n throw errorInvalidRefFile(filePath, result.summary);\n }\n\n refs[name] = result;\n }\n\n return refs;\n}\n\nexport async function writeRef(refsDir: string, name: string, entry: RefEntry): Promise<void> {\n if (!validateRefName(name)) {\n throw errorInvalidRefName(name);\n }\n if (!validateRefValue(entry.hash)) {\n throw errorInvalidRefValue(entry.hash);\n }\n\n const filePath = refFilePath(refsDir, name);\n const dir = dirname(filePath);\n await mkdir(dir, { recursive: true });\n\n const tmpPath = join(dir, `.${name.split('/').pop()}.json.${Date.now()}.tmp`);\n await writeFile(\n tmpPath,\n `${JSON.stringify({ hash: entry.hash, invariants: [...entry.invariants] }, null, 2)}\\n`,\n );\n await rename(tmpPath, filePath);\n}\n\nexport async function deleteRef(refsDir: string, name: string): Promise<void> {\n if (!validateRefName(name)) {\n throw errorInvalidRefName(name);\n }\n\n const filePath = refFilePath(refsDir, name);\n try {\n await unlink(filePath);\n } catch (error) {\n if (error instanceof Error && (error as { code?: string }).code === 'ENOENT') {\n throw new MigrationToolsError('MIGRATION.UNKNOWN_REF', `Unknown ref \"${name}\"`, {\n why: `No ref file found at \"${filePath}\".`,\n fix: 'Run `prisma-next migration ref list` to see available refs.',\n details: { refName: name, filePath },\n });\n }\n throw error;\n }\n\n // Clean empty parent directories up to refsDir. Stop walking on the expected\n // \"directory has siblings\" signal (ENOTEMPTY on Linux, EEXIST on some BSDs)\n // and on ENOENT (concurrent removal). Anything else (EACCES, EIO, …) is a\n // real failure and propagates.\n let dir = dirname(filePath);\n while (dir !== refsDir && dir.startsWith(refsDir)) {\n try {\n await rmdir(dir);\n dir = dirname(dir);\n } catch (error) {\n const code = error instanceof Error ? (error as { code?: string }).code : undefined;\n if (code === 'ENOTEMPTY' || code === 'EEXIST' || code === 'ENOENT') {\n break;\n }\n throw error;\n }\n }\n}\n\nexport function resolveRef(refs: Refs, name: string): RefEntry {\n if (!validateRefName(name)) {\n throw errorInvalidRefName(name);\n }\n\n // Object.hasOwn gate: plain-object `refs` would otherwise let\n // `refs['constructor']` return Object.prototype.constructor and bypass the\n // UNKNOWN_REF throw. validateRefName accepts `\"constructor\"` as a name shape.\n if (!Object.hasOwn(refs, name)) {\n throw new MigrationToolsError('MIGRATION.UNKNOWN_REF', `Unknown ref \"${name}\"`, {\n why: `No ref named \"${name}\" exists.`,\n fix: `Available refs: ${Object.keys(refs).join(', ') || '(none)'}. Create a ref with: prisma-next migration ref set ${name} <hash>`,\n details: { refName: name, availableRefs: Object.keys(refs) },\n });\n }\n\n // biome-ignore lint/style/noNonNullAssertion: Object.hasOwn gate above guarantees this is defined\n return refs[name]!;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"refs.mjs","names":[],"sources":["../../src/refs.ts"],"sourcesContent":["import { mkdir, readdir, readFile, rename, rmdir, unlink, writeFile } from 'node:fs/promises';\nimport { type } from 'arktype';\nimport { dirname, join, relative } from 'pathe';\nimport {\n errorInvalidRefFile,\n errorInvalidRefName,\n errorInvalidRefValue,\n MigrationToolsError,\n} from './errors';\n\nexport interface RefEntry {\n readonly hash: string;\n readonly invariants: readonly string[];\n}\n\nexport type Refs = Readonly<Record<string, RefEntry>>;\n\nconst REF_NAME_PATTERN = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\/[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/;\nconst REF_VALUE_PATTERN = /^sha256:(empty|[0-9a-f]{64})$/;\n\nexport function validateRefName(name: string): boolean {\n if (name.length === 0) return false;\n if (name.includes('..')) return false;\n if (name.includes('//')) return false;\n if (name.startsWith('.')) return false;\n return REF_NAME_PATTERN.test(name);\n}\n\nexport function validateRefValue(value: string): boolean {\n return REF_VALUE_PATTERN.test(value);\n}\n\nconst RefEntrySchema = type({\n hash: 'string',\n invariants: 'string[]',\n}).narrow((entry, ctx) => {\n if (!validateRefValue(entry.hash))\n return ctx.mustBe(`a valid contract hash (got \"${entry.hash}\")`);\n return true;\n});\n\nfunction refFilePath(refsDir: string, name: string): string {\n return join(refsDir, `${name}.json`);\n}\n\nfunction refNameFromPath(refsDir: string, filePath: string): string {\n const rel = relative(refsDir, filePath);\n return rel.replace(/\\.json$/, '');\n}\n\nexport async function readRef(refsDir: string, name: string): Promise<RefEntry> {\n if (!validateRefName(name)) {\n throw errorInvalidRefName(name);\n }\n\n const filePath = refFilePath(refsDir, name);\n let raw: string;\n try {\n raw = await readFile(filePath, 'utf-8');\n } catch (error) {\n if (error instanceof Error && (error as { code?: string }).code === 'ENOENT') {\n throw new MigrationToolsError('MIGRATION.UNKNOWN_REF', `Unknown ref \"${name}\"`, {\n why: `No ref file found at \"${filePath}\".`,\n fix: `Create the ref with: prisma-next migration ref set ${name} <hash>`,\n details: { refName: name, filePath },\n });\n }\n throw error;\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n throw errorInvalidRefFile(filePath, 'Failed to parse as JSON');\n }\n\n const result = RefEntrySchema(parsed);\n if (result instanceof type.errors) {\n throw errorInvalidRefFile(filePath, result.summary);\n }\n\n return result;\n}\n\nexport async function readRefs(refsDir: string): Promise<Refs> {\n let entries: string[];\n try {\n entries = await readdir(refsDir, { recursive: true, encoding: 'utf-8' });\n } catch (error) {\n if (error instanceof Error && (error as { code?: string }).code === 'ENOENT') {\n return {};\n }\n throw error;\n }\n\n const jsonFiles = entries.filter((entry) => entry.endsWith('.json'));\n const refs: Record<string, RefEntry> = {};\n\n for (const jsonFile of jsonFiles) {\n const filePath = join(refsDir, jsonFile);\n const name = refNameFromPath(refsDir, filePath);\n\n let raw: string;\n try {\n raw = await readFile(filePath, 'utf-8');\n } catch (error) {\n // Tolerate the TOCTOU race between `readdir` and `readFile` (ENOENT) and\n // benign EISDIR if a directory happens to end in `.json`. Anything else\n // (EACCES, EIO, EMFILE, …) is a real failure and propagates so the CLI\n // surfaces it rather than silently dropping the ref.\n const code = error instanceof Error ? (error as { code?: string }).code : undefined;\n if (code === 'ENOENT' || code === 'EISDIR') {\n continue;\n }\n throw error;\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n throw errorInvalidRefFile(filePath, 'Failed to parse as JSON');\n }\n\n const result = RefEntrySchema(parsed);\n if (result instanceof type.errors) {\n throw errorInvalidRefFile(filePath, result.summary);\n }\n\n refs[name] = result;\n }\n\n return refs;\n}\n\nexport async function writeRef(refsDir: string, name: string, entry: RefEntry): Promise<void> {\n if (!validateRefName(name)) {\n throw errorInvalidRefName(name);\n }\n if (!validateRefValue(entry.hash)) {\n throw errorInvalidRefValue(entry.hash);\n }\n\n const filePath = refFilePath(refsDir, name);\n const dir = dirname(filePath);\n await mkdir(dir, { recursive: true });\n\n const tmpPath = join(dir, `.${name.split('/').pop()}.json.${Date.now()}.tmp`);\n await writeFile(\n tmpPath,\n `${JSON.stringify({ hash: entry.hash, invariants: [...entry.invariants] }, null, 2)}\\n`,\n );\n await rename(tmpPath, filePath);\n}\n\nexport async function deleteRef(refsDir: string, name: string): Promise<void> {\n if (!validateRefName(name)) {\n throw errorInvalidRefName(name);\n }\n\n const filePath = refFilePath(refsDir, name);\n try {\n await unlink(filePath);\n } catch (error) {\n if (error instanceof Error && (error as { code?: string }).code === 'ENOENT') {\n throw new MigrationToolsError('MIGRATION.UNKNOWN_REF', `Unknown ref \"${name}\"`, {\n why: `No ref file found at \"${filePath}\".`,\n fix: 'Run `prisma-next migration ref list` to see available refs.',\n details: { refName: name, filePath },\n });\n }\n throw error;\n }\n\n // Clean empty parent directories up to refsDir. Stop walking on the expected\n // \"directory has siblings\" signal (ENOTEMPTY on Linux, EEXIST on some BSDs)\n // and on ENOENT (concurrent removal). Anything else (EACCES, EIO, …) is a\n // real failure and propagates.\n let dir = dirname(filePath);\n while (dir !== refsDir && dir.startsWith(refsDir)) {\n try {\n await rmdir(dir);\n dir = dirname(dir);\n } catch (error) {\n const code = error instanceof Error ? (error as { code?: string }).code : undefined;\n if (code === 'ENOTEMPTY' || code === 'EEXIST' || code === 'ENOENT') {\n break;\n }\n throw error;\n }\n }\n}\n\nexport function resolveRef(refs: Refs, name: string): RefEntry {\n if (!validateRefName(name)) {\n throw errorInvalidRefName(name);\n }\n\n // Object.hasOwn gate: plain-object `refs` would otherwise let\n // `refs['constructor']` return Object.prototype.constructor and bypass the\n // UNKNOWN_REF throw. validateRefName accepts `\"constructor\"` as a name shape.\n if (!Object.hasOwn(refs, name)) {\n throw new MigrationToolsError('MIGRATION.UNKNOWN_REF', `Unknown ref \"${name}\"`, {\n why: `No ref named \"${name}\" exists.`,\n fix: `Available refs: ${Object.keys(refs).join(', ') || '(none)'}. Create a ref with: prisma-next migration ref set ${name} <hash>`,\n details: { refName: name, availableRefs: Object.keys(refs) },\n });\n }\n\n // biome-ignore lint/style/noNonNullAssertion: Object.hasOwn gate above guarantees this is defined\n return refs[name]!;\n}\n"],"mappings":"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"}
|