@prisma-next/cli 0.5.0-dev.7 → 0.5.0-dev.71
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 +56 -21
- package/dist/cli-errors-D3_sMh2K.mjs +33 -0
- package/dist/cli-errors-D3_sMh2K.mjs.map +1 -0
- package/dist/cli-errors-QH8kf-C2.d.mts +3 -0
- package/dist/cli.mjs +16 -78
- package/dist/cli.mjs.map +1 -1
- package/dist/client-0ZX24FXF.mjs +1398 -0
- package/dist/client-0ZX24FXF.mjs.map +1 -0
- package/dist/commands/contract-emit.d.mts.map +1 -1
- package/dist/commands/contract-emit.mjs +2 -4
- package/dist/commands/contract-infer.d.mts.map +1 -1
- package/dist/commands/contract-infer.mjs +2 -4
- package/dist/commands/db-init.d.mts.map +1 -1
- package/dist/commands/db-init.mjs +14 -13
- package/dist/commands/db-init.mjs.map +1 -1
- package/dist/commands/db-schema.d.mts.map +1 -1
- package/dist/commands/db-schema.mjs +5 -7
- package/dist/commands/db-schema.mjs.map +1 -1
- package/dist/commands/db-sign.d.mts.map +1 -1
- package/dist/commands/db-sign.mjs +8 -9
- package/dist/commands/db-sign.mjs.map +1 -1
- package/dist/commands/db-update.d.mts.map +1 -1
- package/dist/commands/db-update.mjs +13 -13
- package/dist/commands/db-update.mjs.map +1 -1
- package/dist/commands/db-verify.d.mts.map +1 -1
- package/dist/commands/db-verify.mjs +1 -321
- package/dist/commands/migration-apply.d.mts +5 -2
- package/dist/commands/migration-apply.d.mts.map +1 -1
- package/dist/commands/migration-apply.mjs +64 -66
- package/dist/commands/migration-apply.mjs.map +1 -1
- package/dist/commands/migration-new.d.mts +0 -1
- package/dist/commands/migration-new.d.mts.map +1 -1
- package/dist/commands/migration-new.mjs +33 -40
- package/dist/commands/migration-new.mjs.map +1 -1
- package/dist/commands/migration-plan.d.mts +14 -5
- package/dist/commands/migration-plan.d.mts.map +1 -1
- package/dist/commands/migration-plan.mjs +1 -347
- package/dist/commands/migration-ref.d.mts +1 -1
- package/dist/commands/migration-ref.d.mts.map +1 -1
- package/dist/commands/migration-ref.mjs +7 -12
- package/dist/commands/migration-ref.mjs.map +1 -1
- package/dist/commands/migration-show.d.mts +13 -7
- package/dist/commands/migration-show.d.mts.map +1 -1
- package/dist/commands/migration-show.mjs +34 -36
- package/dist/commands/migration-show.mjs.map +1 -1
- package/dist/commands/migration-status.d.mts +23 -5
- package/dist/commands/migration-status.d.mts.map +1 -1
- package/dist/commands/migration-status.mjs +2 -4
- package/dist/{config-loader-C25b63rJ.mjs → config-loader-B6sJjXTv.mjs} +3 -5
- package/dist/config-loader-B6sJjXTv.mjs.map +1 -0
- package/dist/config-loader.d.mts +0 -1
- package/dist/config-loader.d.mts.map +1 -1
- package/dist/config-loader.mjs +2 -3
- package/dist/contract-emit-B3ChISB_.mjs +338 -0
- package/dist/contract-emit-B3ChISB_.mjs.map +1 -0
- package/dist/contract-emit-DkMqO7f2.mjs +148 -0
- package/dist/contract-emit-DkMqO7f2.mjs.map +1 -0
- package/dist/{contract-enrichment-CAOELa-H.mjs → contract-enrichment-CF6ogEJ_.mjs} +4 -6
- package/dist/contract-enrichment-CF6ogEJ_.mjs.map +1 -0
- package/dist/{contract-infer-D9cC3rJm.mjs → contract-infer-BDKAE0B0.mjs} +12 -22
- package/dist/contract-infer-BDKAE0B0.mjs.map +1 -0
- package/dist/db-verify-B4TdDKOI.mjs +403 -0
- package/dist/db-verify-B4TdDKOI.mjs.map +1 -0
- package/dist/exports/config-types.mjs +1 -2
- package/dist/exports/control-api.d.mts +287 -29
- package/dist/exports/control-api.d.mts.map +1 -1
- package/dist/exports/control-api.mjs +4 -6
- package/dist/exports/index.d.mts.map +1 -1
- package/dist/exports/index.mjs +28 -30
- package/dist/exports/index.mjs.map +1 -1
- package/dist/exports/init-output.d.mts +2 -4
- package/dist/exports/init-output.d.mts.map +1 -1
- package/dist/exports/init-output.mjs +2 -3
- package/dist/{framework-components-Cr--XBKy.mjs → framework-components-gwAHl7ml.mjs} +3 -4
- package/dist/{framework-components-Cr--XBKy.mjs.map → framework-components-gwAHl7ml.mjs.map} +1 -1
- package/dist/{init-C5220SY9.mjs → init-Deo7U8_U.mjs} +26 -35
- package/dist/init-Deo7U8_U.mjs.map +1 -0
- package/dist/{inspect-live-schema-yrHAvG71.mjs → inspect-live-schema-BAgQMYpD.mjs} +10 -11
- package/dist/inspect-live-schema-BAgQMYpD.mjs.map +1 -0
- package/dist/migration-cli.d.mts +41 -12
- package/dist/migration-cli.d.mts.map +1 -1
- package/dist/migration-cli.mjs +309 -86
- package/dist/migration-cli.mjs.map +1 -1
- package/dist/{migration-command-scaffold-B3B09et6.mjs → migration-command-scaffold-B8J702Uh.mjs} +8 -9
- package/dist/migration-command-scaffold-B8J702Uh.mjs.map +1 -0
- package/dist/migration-plan-BcKNnTM7.mjs +530 -0
- package/dist/migration-plan-BcKNnTM7.mjs.map +1 -0
- package/dist/{migration-status-DUMiH8_G.mjs → migration-status-CjwB2of-.mjs} +117 -64
- package/dist/migration-status-CjwB2of-.mjs.map +1 -0
- package/dist/{migrations-Bo5WtTla.mjs → migrations-CIK94AJf.mjs} +43 -23
- package/dist/migrations-CIK94AJf.mjs.map +1 -0
- package/dist/{output-BpcQrnnq.mjs → output-DnjfCC_u.mjs} +9 -3
- package/dist/output-DnjfCC_u.mjs.map +1 -0
- package/dist/{progress-adapter-DvQWB1nK.mjs → progress-adapter-xASh41wr.mjs} +2 -2
- package/dist/{progress-adapter-DvQWB1nK.mjs.map → progress-adapter-xASh41wr.mjs.map} +1 -1
- package/dist/{result-handler-Ba3zWQsI.mjs → result-handler-DWb1rFS-.mjs} +52 -27
- package/dist/result-handler-DWb1rFS-.mjs.map +1 -0
- package/dist/{terminal-ui-C3ZLwQxK.mjs → terminal-ui-zaRDhJnP.mjs} +2 -6
- package/dist/{terminal-ui-C3ZLwQxK.mjs.map → terminal-ui-zaRDhJnP.mjs.map} +1 -1
- package/dist/{verify-Bkycc-Tf.mjs → verify-BEIa9638.mjs} +3 -4
- package/dist/verify-BEIa9638.mjs.map +1 -0
- package/package.json +28 -26
- package/src/cli.ts +32 -6
- package/src/commands/contract-emit.ts +67 -163
- package/src/commands/contract-infer.ts +7 -20
- package/src/commands/db-init.ts +14 -3
- package/src/commands/db-update.ts +8 -4
- package/src/commands/db-verify.ts +47 -15
- package/src/commands/init/index.ts +1 -1
- package/src/commands/init/init.ts +2 -2
- package/src/commands/init/templates/code-templates.ts +12 -4
- package/src/commands/inspect-live-schema.ts +10 -5
- package/src/commands/migration-apply.ts +92 -71
- package/src/commands/migration-new.ts +42 -45
- package/src/commands/migration-plan.ts +147 -64
- package/src/commands/migration-ref.ts +8 -7
- package/src/commands/migration-show.ts +60 -41
- package/src/commands/migration-status.ts +196 -60
- package/src/config-path-validation.ts +0 -1
- package/src/control-api/client.ts +69 -1
- package/src/control-api/contract-enrichment.ts +6 -4
- package/src/control-api/operations/contract-emit.ts +198 -115
- package/src/control-api/operations/db-apply-aggregate.ts +446 -0
- package/src/control-api/operations/db-init.ts +51 -253
- package/src/control-api/operations/db-update.ts +66 -183
- package/src/control-api/operations/db-verify.ts +342 -0
- package/src/control-api/operations/migration-apply.ts +37 -9
- package/src/control-api/types.ts +125 -7
- package/src/exports/control-api.ts +15 -3
- package/src/load-ts-contract.ts +28 -26
- package/src/migration-cli.ts +445 -122
- package/src/utils/cli-errors.ts +49 -2
- package/src/utils/combine-schema-results.ts +84 -0
- package/src/utils/command-helpers.ts +69 -25
- package/src/utils/contract-space-aggregate-loader.ts +236 -0
- package/src/utils/contract-space-extension-migrations-pass.ts +120 -0
- package/src/utils/contract-space-migrate-pass.ts +156 -0
- package/src/utils/emit-queue.ts +26 -0
- package/src/utils/formatters/graph-migration-mapper.ts +7 -3
- package/src/utils/formatters/migrations.ts +62 -26
- package/src/utils/publish-contract-artifact-pair.ts +134 -0
- package/dist/cli-errors-BFYgBH3L.d.mts +0 -4
- package/dist/cli-errors-Cd79vmTH.mjs +0 -5
- package/dist/client-CrsnY58k.mjs +0 -997
- package/dist/client-CrsnY58k.mjs.map +0 -1
- package/dist/commands/db-verify.mjs.map +0 -1
- package/dist/commands/migration-plan.mjs.map +0 -1
- package/dist/config-loader-C25b63rJ.mjs.map +0 -1
- package/dist/contract-emit--feXyNd7.mjs +0 -4
- package/dist/contract-emit-NJ01hiiv.mjs +0 -195
- package/dist/contract-emit-NJ01hiiv.mjs.map +0 -1
- package/dist/contract-emit-V5SSitUT.mjs +0 -122
- package/dist/contract-emit-V5SSitUT.mjs.map +0 -1
- package/dist/contract-enrichment-CAOELa-H.mjs.map +0 -1
- package/dist/contract-infer-D9cC3rJm.mjs.map +0 -1
- package/dist/extract-operation-statements-DsFfxXVZ.mjs +0 -13
- package/dist/extract-operation-statements-DsFfxXVZ.mjs.map +0 -1
- package/dist/extract-sql-ddl-D9UbZDyz.mjs +0 -26
- package/dist/extract-sql-ddl-D9UbZDyz.mjs.map +0 -1
- package/dist/init-C5220SY9.mjs.map +0 -1
- package/dist/inspect-live-schema-yrHAvG71.mjs.map +0 -1
- package/dist/migration-command-scaffold-B3B09et6.mjs.map +0 -1
- package/dist/migration-status-DUMiH8_G.mjs.map +0 -1
- package/dist/migrations-Bo5WtTla.mjs.map +0 -1
- package/dist/output-BpcQrnnq.mjs.map +0 -1
- package/dist/result-handler-Ba3zWQsI.mjs.map +0 -1
- package/dist/validate-contract-deps-B_Cs29TL.mjs +0 -37
- package/dist/validate-contract-deps-B_Cs29TL.mjs.map +0 -1
- package/dist/verify-Bkycc-Tf.mjs.map +0 -1
- package/src/control-api/operations/extract-operation-statements.ts +0 -14
- package/src/control-api/operations/extract-sql-ddl.ts +0 -47
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import {
|
|
2
|
+
detectSpaceContractDrift,
|
|
3
|
+
emitContractSpaceArtefacts,
|
|
4
|
+
readContractSpaceHeadRef,
|
|
5
|
+
type SpaceContractDriftResult,
|
|
6
|
+
} from '@prisma-next/migration-tools/spaces';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Minimal descriptor view consumed by the migrate-time per-space pass.
|
|
10
|
+
*
|
|
11
|
+
* The CLI receives descriptors typed against the SQL family (or any other
|
|
12
|
+
* family in the future); this helper only needs the structural shape of
|
|
13
|
+
* `contractSpace`, so it accepts an `unknown`-typed `contractJson` and
|
|
14
|
+
* a structurally-typed `headRef`. SQL-family callers pass the same
|
|
15
|
+
* `Contract<SqlStorage>` value through unchanged — `emitContractSpaceArtefacts`
|
|
16
|
+
* already serialises through `canonicalizeJson` and is framework-neutral.
|
|
17
|
+
*
|
|
18
|
+
* @see specs/framework-mechanism.spec.md § 3 — Per-space helper location.
|
|
19
|
+
*/
|
|
20
|
+
export interface MigrateExtensionInput {
|
|
21
|
+
readonly id: string;
|
|
22
|
+
readonly contractSpace?: {
|
|
23
|
+
readonly contractJson: unknown;
|
|
24
|
+
readonly headRef: { readonly hash: string; readonly invariants: readonly string[] };
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Inputs needed to compose the migrate-time per-space pass at the CLI
|
|
30
|
+
* surface — typically called once after the app-space migration package
|
|
31
|
+
* has been written, regardless of whether the app-space had structural
|
|
32
|
+
* changes (an extension bump alone should still re-pin its artefacts).
|
|
33
|
+
*/
|
|
34
|
+
export interface ContractSpaceMigratePassInputs {
|
|
35
|
+
readonly migrationsDir: string;
|
|
36
|
+
readonly extensionPacks: ReadonlyArray<MigrateExtensionInput>;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface ContractSpaceMigratePassResult {
|
|
40
|
+
readonly drifts: readonly SpaceContractDriftResult[];
|
|
41
|
+
readonly emittedSpaceIds: readonly string[];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Run drift detection + on-disk artefact emission for every loaded
|
|
46
|
+
* extension space at `migrate` time.
|
|
47
|
+
*
|
|
48
|
+
* Per sub-spec § 3:
|
|
49
|
+
*
|
|
50
|
+
* - For each declared extension that exposes a `contractSpace`:
|
|
51
|
+
* - Read the on-disk head hash from `migrations/<spaceId>/refs/head.json`
|
|
52
|
+
* (returns `null` on first emit).
|
|
53
|
+
* - Compare against the descriptor's `headRef.hash` via
|
|
54
|
+
* `detectSpaceContractDrift`. The `kind` discriminant decides whether
|
|
55
|
+
* the user sees a warning (`drift`), a no-op silent emit (`firstEmit`,
|
|
56
|
+
* `noDrift`), or nothing at all.
|
|
57
|
+
* - Always re-emit the on-disk artefacts (`contract.json`, `contract.d.ts`,
|
|
58
|
+
* `refs/head.json`). The framework owns these files and the helper is
|
|
59
|
+
* idempotent.
|
|
60
|
+
*
|
|
61
|
+
* Drift warnings are returned to the caller for formatting (TerminalUI,
|
|
62
|
+
* structured-output envelope, etc.) — the helper does not print directly,
|
|
63
|
+
* keeping it framework-neutral and unit-testable.
|
|
64
|
+
*
|
|
65
|
+
* Extension migration packages (the descriptor's pre-canned `migrations`
|
|
66
|
+
* array → `migrations/<spaceId>/<dirName>/`) are intentionally not
|
|
67
|
+
* materialised here — that interaction will be wired in a follow-on round
|
|
68
|
+
* once the runner-side single-tx slice (sub-spec § 6) is in place.
|
|
69
|
+
* On-disk artefacts are sufficient to lock the drift-warning behaviour
|
|
70
|
+
* and the always-on re-emit AC for R2.
|
|
71
|
+
*
|
|
72
|
+
* @see specs/framework-mechanism.spec.md § 3 — Drift detection (T1.9).
|
|
73
|
+
*/
|
|
74
|
+
export async function runContractSpaceMigratePass(
|
|
75
|
+
inputs: ContractSpaceMigratePassInputs,
|
|
76
|
+
): Promise<ContractSpaceMigratePassResult> {
|
|
77
|
+
const drifts: SpaceContractDriftResult[] = [];
|
|
78
|
+
const emittedSpaceIds: string[] = [];
|
|
79
|
+
|
|
80
|
+
for (const pack of inputs.extensionPacks) {
|
|
81
|
+
if (pack.contractSpace === undefined) continue;
|
|
82
|
+
const { contractJson, headRef } = pack.contractSpace;
|
|
83
|
+
|
|
84
|
+
const onDiskHeadRef = await readContractSpaceHeadRef(inputs.migrationsDir, pack.id);
|
|
85
|
+
const drift = detectSpaceContractDrift(pack.id, {
|
|
86
|
+
descriptorHash: headRef.hash,
|
|
87
|
+
priorHeadHash: onDiskHeadRef?.hash ?? null,
|
|
88
|
+
});
|
|
89
|
+
drifts.push(drift);
|
|
90
|
+
|
|
91
|
+
await emitContractSpaceArtefacts(inputs.migrationsDir, pack.id, {
|
|
92
|
+
contract: contractJson,
|
|
93
|
+
contractDts: buildPlaceholderContractDts(pack.id),
|
|
94
|
+
headRef: { hash: headRef.hash, invariants: headRef.invariants },
|
|
95
|
+
});
|
|
96
|
+
emittedSpaceIds.push(pack.id);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return { drifts, emittedSpaceIds };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Format the user-facing drift warning for a single space. Callers
|
|
104
|
+
* funnel this through their preferred output channel (TerminalUI line,
|
|
105
|
+
* structured-output envelope `warnings[]`, etc.).
|
|
106
|
+
*
|
|
107
|
+
* Locks AM7 — drift warning surfaces the extension name and the diff
|
|
108
|
+
* direction (descriptor → on-disk head).
|
|
109
|
+
*/
|
|
110
|
+
export function formatContractSpaceDriftWarning(drift: SpaceContractDriftResult): string {
|
|
111
|
+
if (drift.kind !== 'drift') {
|
|
112
|
+
throw new Error(`formatContractSpaceDriftWarning called with non-drift result: ${drift.kind}`);
|
|
113
|
+
}
|
|
114
|
+
return (
|
|
115
|
+
`Contract-space drift detected for "${drift.spaceId}": descriptor hash ` +
|
|
116
|
+
`${drift.descriptorHash} differs from on-disk head hash ${drift.priorHeadHash ?? '<none>'}. ` +
|
|
117
|
+
`The on-disk artefacts under migrations/${drift.spaceId}/ will be refreshed to match the descriptor.`
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Placeholder `.d.ts` content for an extension space's on-disk mirror.
|
|
123
|
+
*
|
|
124
|
+
* Rendering a fully-typed `.d.ts` for an extension contract requires the
|
|
125
|
+
* SQL-family renderer with the codec / typemap registry threaded
|
|
126
|
+
* through; that integration is tracked under sub-spec Open Question 3
|
|
127
|
+
* (see `projects/extension-contract-spaces/specs/framework-mechanism.spec.md`).
|
|
128
|
+
*
|
|
129
|
+
* Until that ships, the on-disk `.d.ts` is a `@ts-nocheck` stub. The
|
|
130
|
+
* spec gap closing alongside the typed renderer is **AC2 / AC14**
|
|
131
|
+
* (byte-equivalence of per-space artefacts under `migrate`):
|
|
132
|
+
* a placeholder cannot be byte-equal to a fully-rendered `.d.ts` from
|
|
133
|
+
* the same descriptor, so AC2 / AC14 are PARTIAL today and become
|
|
134
|
+
* fully-PASS once OQ3 closes.
|
|
135
|
+
*
|
|
136
|
+
* Scheduled to close in **M3** (cipherstash editor tooling) — that's
|
|
137
|
+
* the milestone where the typed renderer gets its first real
|
|
138
|
+
* extension-space consumer and the byte-equivalence guarantee is
|
|
139
|
+
* practically required.
|
|
140
|
+
*/
|
|
141
|
+
function buildPlaceholderContractDts(spaceId: string): string {
|
|
142
|
+
return [
|
|
143
|
+
'// @ts-nocheck',
|
|
144
|
+
'/**',
|
|
145
|
+
` * Placeholder \`.d.ts\` for extension space "${spaceId}".`,
|
|
146
|
+
' *',
|
|
147
|
+
' * The framework re-emits this file on every `migrate` run alongside',
|
|
148
|
+
' * `contract.json` and `refs/head.json`. A typed `.d.ts` rendering',
|
|
149
|
+
" * pass for extension contracts is tracked under the project's open",
|
|
150
|
+
' * questions; until that ships, consumers should import',
|
|
151
|
+
' * `contract.json` directly with `validateContract<…>(…)`.',
|
|
152
|
+
' */',
|
|
153
|
+
'export {};',
|
|
154
|
+
'',
|
|
155
|
+
].join('\n');
|
|
156
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-output FIFO queue for `executeContractEmit`.
|
|
3
|
+
*
|
|
4
|
+
* Ensures that at most one emit (load → resolve source → emit bytes → publish)
|
|
5
|
+
* runs per output JSON path at a time. Concurrent calls for the same path
|
|
6
|
+
* line up behind the in-flight one and run in submission order; the user-visible
|
|
7
|
+
* outcome is "last submission wins on disk" without any supersession bookkeeping.
|
|
8
|
+
*
|
|
9
|
+
* Long-lived hosts (Vite dev server, watch CLIs) must call `disposeEmitQueue`
|
|
10
|
+
* when they stop publishing to a path, otherwise the module-global `Map`
|
|
11
|
+
* accumulates one entry per unique output path for the lifetime of the process.
|
|
12
|
+
*/
|
|
13
|
+
const emitQueues = new Map<string, Promise<unknown>>();
|
|
14
|
+
|
|
15
|
+
export function queueEmitByOutput<T>(outputJsonPath: string, action: () => Promise<T>): Promise<T> {
|
|
16
|
+
const previous = emitQueues.get(outputJsonPath) ?? Promise.resolve();
|
|
17
|
+
// Continue regardless of the previous task's outcome — a failed emit must not
|
|
18
|
+
// block subsequent ones. The current task's outcome propagates via `next`.
|
|
19
|
+
const next = previous.then(action, action);
|
|
20
|
+
emitQueues.set(outputJsonPath, next);
|
|
21
|
+
return next;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function disposeEmitQueue(outputJsonPath: string): void {
|
|
25
|
+
emitQueues.delete(outputJsonPath);
|
|
26
|
+
}
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* Maps MigrationGraph + status info to the generic graph renderer types.
|
|
3
3
|
*/
|
|
4
4
|
import { EMPTY_CONTRACT_HASH } from '@prisma-next/migration-tools/constants';
|
|
5
|
-
import {
|
|
6
|
-
import
|
|
5
|
+
import type { MigrationGraph } from '@prisma-next/migration-tools/graph';
|
|
6
|
+
import { findPath } from '@prisma-next/migration-tools/migration-graph';
|
|
7
7
|
import { ifDefined } from '@prisma-next/utils/defined';
|
|
8
8
|
|
|
9
9
|
import type { StatusRef } from '../migration-types';
|
|
@@ -106,7 +106,11 @@ export function migrationGraphToRenderInput(input: MigrationGraphInput): Migrati
|
|
|
106
106
|
for (const entry of entries) {
|
|
107
107
|
const status = statusByDirName.get(entry.dirName);
|
|
108
108
|
const icon = status ? STATUS_ICON[status] : '';
|
|
109
|
-
const
|
|
109
|
+
const invariantsSuffix =
|
|
110
|
+
entry.invariants.length > 0
|
|
111
|
+
? ` provides [${entry.invariants.map((id) => JSON.stringify(id)).join(', ')}]`
|
|
112
|
+
: '';
|
|
113
|
+
const label = `${entry.dirName}${icon}${invariantsSuffix}`;
|
|
110
114
|
|
|
111
115
|
edgeList.push({
|
|
112
116
|
from: toShortId(entry.from),
|
|
@@ -1,8 +1,41 @@
|
|
|
1
|
+
import type { OperationPreview } from '@prisma-next/framework-components/control';
|
|
1
2
|
import { green, yellow } from 'colorette';
|
|
2
3
|
|
|
3
4
|
import type { GlobalFlags } from '../global-flags';
|
|
4
5
|
import { createColorFormatter, formatDim, isVerbose } from './helpers';
|
|
5
6
|
|
|
7
|
+
/**
|
|
8
|
+
* Render a single statement of an `OperationPreview` for the human-readable
|
|
9
|
+
* preview block. SQL statements get a trailing `;` if missing — matches the
|
|
10
|
+
* legacy `string[]`-based renderer byte-for-byte (per spec OQ-4). Other
|
|
11
|
+
* languages (`'mongodb-shell'`) render verbatim.
|
|
12
|
+
*/
|
|
13
|
+
function renderPreviewStatement(text: string, language: string): string | undefined {
|
|
14
|
+
const trimmed = text.trim();
|
|
15
|
+
if (!trimmed) return undefined;
|
|
16
|
+
if (language === 'sql') {
|
|
17
|
+
return trimmed.endsWith(';') ? trimmed : `${trimmed};`;
|
|
18
|
+
}
|
|
19
|
+
return trimmed;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Choose the header label for a preview block. SQL-only previews keep the
|
|
24
|
+
* legacy `DDL preview` label (preserves CLI byte-identity for SQL targets per
|
|
25
|
+
* spec OQ-4); previews from any other family — or a mix that includes any
|
|
26
|
+
* non-SQL language — use the family-agnostic `Operation preview` label.
|
|
27
|
+
*
|
|
28
|
+
* An empty `statements` array deliberately renders as `Operation preview`
|
|
29
|
+
* rather than `DDL preview`: `Array.prototype.every` is vacuously true for
|
|
30
|
+
* empty arrays, but we have no evidence the preview is SQL-only when no
|
|
31
|
+
* statements are present, so the family-agnostic label is the safer default.
|
|
32
|
+
*/
|
|
33
|
+
export function previewBlockHeader(preview: OperationPreview): string {
|
|
34
|
+
const allSql =
|
|
35
|
+
preview.statements.length > 0 && preview.statements.every((s) => s.language === 'sql');
|
|
36
|
+
return allSql ? 'DDL preview' : 'Operation preview';
|
|
37
|
+
}
|
|
38
|
+
|
|
6
39
|
// ============================================================================
|
|
7
40
|
// Migration Command Output Formatters (shared by db init and db update)
|
|
8
41
|
// ============================================================================
|
|
@@ -24,7 +57,12 @@ export interface MigrationCommandResult {
|
|
|
24
57
|
readonly label: string;
|
|
25
58
|
readonly operationClass: string;
|
|
26
59
|
}[];
|
|
27
|
-
|
|
60
|
+
/**
|
|
61
|
+
* Family-agnostic textual preview of the planned operations. Replaces the
|
|
62
|
+
* previous `sql?: readonly string[]`. Consumers should read
|
|
63
|
+
* `plan.preview?.statements`.
|
|
64
|
+
*/
|
|
65
|
+
readonly preview?: OperationPreview;
|
|
28
66
|
};
|
|
29
67
|
readonly execution?: {
|
|
30
68
|
readonly operationsPlanned: number;
|
|
@@ -92,20 +130,20 @@ export function formatMigrationPlanOutput(
|
|
|
92
130
|
lines.push(`${formatDimText(`Destination hash: ${result.plan.destination.storageHash}`)}`);
|
|
93
131
|
}
|
|
94
132
|
|
|
95
|
-
//
|
|
96
|
-
const
|
|
97
|
-
if (
|
|
133
|
+
// Statement preview (any family that implements OperationPreviewCapable)
|
|
134
|
+
const preview = result.plan?.preview;
|
|
135
|
+
if (preview) {
|
|
98
136
|
lines.push('');
|
|
99
|
-
lines.push(`${formatDimText(
|
|
100
|
-
if (
|
|
101
|
-
lines.push(`${formatDimText('No
|
|
137
|
+
lines.push(`${formatDimText(previewBlockHeader(preview))}`);
|
|
138
|
+
if (preview.statements.length === 0) {
|
|
139
|
+
lines.push(`${formatDimText('No operations.')}`);
|
|
102
140
|
} else {
|
|
103
141
|
lines.push('');
|
|
104
|
-
for (const statement of
|
|
105
|
-
const
|
|
106
|
-
if (
|
|
107
|
-
|
|
108
|
-
|
|
142
|
+
for (const statement of preview.statements) {
|
|
143
|
+
const rendered = renderPreviewStatement(statement.text, statement.language);
|
|
144
|
+
if (rendered) {
|
|
145
|
+
lines.push(rendered);
|
|
146
|
+
}
|
|
109
147
|
}
|
|
110
148
|
}
|
|
111
149
|
}
|
|
@@ -181,17 +219,16 @@ export function formatMigrationApplyCommandOutput(
|
|
|
181
219
|
interface MigrationShowResult {
|
|
182
220
|
readonly dirName: string;
|
|
183
221
|
readonly dirPath: string;
|
|
184
|
-
readonly from: string;
|
|
222
|
+
readonly from: string | null;
|
|
185
223
|
readonly to: string;
|
|
186
|
-
readonly
|
|
187
|
-
readonly kind: string;
|
|
224
|
+
readonly migrationHash: string;
|
|
188
225
|
readonly createdAt: string;
|
|
189
226
|
readonly operations: readonly {
|
|
190
227
|
readonly id: string;
|
|
191
228
|
readonly label: string;
|
|
192
229
|
readonly operationClass: string;
|
|
193
230
|
}[];
|
|
194
|
-
readonly
|
|
231
|
+
readonly preview: OperationPreview;
|
|
195
232
|
readonly summary: string;
|
|
196
233
|
}
|
|
197
234
|
|
|
@@ -208,10 +245,9 @@ export function formatMigrationShowOutput(result: MigrationShowResult, flags: Gl
|
|
|
208
245
|
const formatDimText = (text: string) => formatDim(useColor, text);
|
|
209
246
|
|
|
210
247
|
lines.push(`${formatGreen('✔')} ${result.dirName}`);
|
|
211
|
-
lines.push(`${formatDimText(`
|
|
212
|
-
lines.push(`${formatDimText(` from: ${result.from}`)}`);
|
|
248
|
+
lines.push(`${formatDimText(` from: ${result.from ?? '(baseline)'}`)}`);
|
|
213
249
|
lines.push(`${formatDimText(` to: ${result.to}`)}`);
|
|
214
|
-
lines.push(`${formatDimText(`
|
|
250
|
+
lines.push(`${formatDimText(` migrationHash: ${result.migrationHash}`)}`);
|
|
215
251
|
lines.push(`${formatDimText(` created: ${result.createdAt}`)}`);
|
|
216
252
|
|
|
217
253
|
lines.push('');
|
|
@@ -239,15 +275,15 @@ export function formatMigrationShowOutput(result: MigrationShowResult, flags: Gl
|
|
|
239
275
|
}
|
|
240
276
|
}
|
|
241
277
|
|
|
242
|
-
if (result.
|
|
278
|
+
if (result.preview.statements.length > 0) {
|
|
243
279
|
lines.push('');
|
|
244
|
-
lines.push(`${formatDimText(
|
|
280
|
+
lines.push(`${formatDimText(previewBlockHeader(result.preview))}`);
|
|
245
281
|
lines.push('');
|
|
246
|
-
for (const statement of result.
|
|
247
|
-
const
|
|
248
|
-
if (
|
|
249
|
-
|
|
250
|
-
|
|
282
|
+
for (const statement of result.preview.statements) {
|
|
283
|
+
const rendered = renderPreviewStatement(statement.text, statement.language);
|
|
284
|
+
if (rendered) {
|
|
285
|
+
lines.push(rendered);
|
|
286
|
+
}
|
|
251
287
|
}
|
|
252
288
|
}
|
|
253
289
|
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { readFile, rename, rm, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { basename, dirname, join } from 'pathe';
|
|
3
|
+
|
|
4
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
5
|
+
return typeof value === 'object' && value !== null;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function createTempArtifactPath(path: string, publicationToken: string, phase: string): string {
|
|
9
|
+
return join(dirname(path), `.${basename(path)}.${process.pid}.${publicationToken}.${phase}.tmp`);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
type PreviousArtifact = { readonly content: string } | 'remove';
|
|
13
|
+
|
|
14
|
+
async function readExistingArtifact(path: string): Promise<PreviousArtifact> {
|
|
15
|
+
try {
|
|
16
|
+
return { content: await readFile(path, 'utf-8') };
|
|
17
|
+
} catch (error) {
|
|
18
|
+
if (isRecord(error) && error['code'] === 'ENOENT') {
|
|
19
|
+
return 'remove';
|
|
20
|
+
}
|
|
21
|
+
throw error;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async function restoreArtifact(
|
|
26
|
+
path: string,
|
|
27
|
+
previous: PreviousArtifact,
|
|
28
|
+
publicationToken: string,
|
|
29
|
+
): Promise<void> {
|
|
30
|
+
if (previous === 'remove') {
|
|
31
|
+
await rm(path, { force: true });
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const restorePath = createTempArtifactPath(path, publicationToken, 'rollback');
|
|
36
|
+
await writeFile(restorePath, previous.content, 'utf-8');
|
|
37
|
+
try {
|
|
38
|
+
await rename(restorePath, path);
|
|
39
|
+
} finally {
|
|
40
|
+
await rm(restorePath, { force: true });
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
interface PublishEntry {
|
|
45
|
+
readonly tempPath: string;
|
|
46
|
+
readonly outputPath: string;
|
|
47
|
+
readonly previous: PreviousArtifact;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function withRollbackFailureCause(error: unknown, rollbackFailures: readonly unknown[]): Error {
|
|
51
|
+
const rollbackCause = new AggregateError(
|
|
52
|
+
rollbackFailures,
|
|
53
|
+
'Failed to restore published artifacts',
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
if (error instanceof Error) {
|
|
57
|
+
Object.defineProperty(error, 'cause', {
|
|
58
|
+
value: rollbackCause,
|
|
59
|
+
configurable: true,
|
|
60
|
+
writable: true,
|
|
61
|
+
});
|
|
62
|
+
return error;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return new Error(String(error), { cause: rollbackCause });
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async function publishPairWithRollback(
|
|
69
|
+
entries: readonly PublishEntry[],
|
|
70
|
+
publicationToken: string,
|
|
71
|
+
): Promise<void> {
|
|
72
|
+
const replaced: PublishEntry[] = [];
|
|
73
|
+
try {
|
|
74
|
+
for (const entry of entries) {
|
|
75
|
+
await rename(entry.tempPath, entry.outputPath);
|
|
76
|
+
replaced.push(entry);
|
|
77
|
+
}
|
|
78
|
+
} catch (error) {
|
|
79
|
+
const rollbackResults = await Promise.allSettled(
|
|
80
|
+
replaced.map((entry) => restoreArtifact(entry.outputPath, entry.previous, publicationToken)),
|
|
81
|
+
);
|
|
82
|
+
const rollbackFailures = rollbackResults.flatMap((result) =>
|
|
83
|
+
result.status === 'rejected' ? [result.reason] : [],
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
if (rollbackFailures.length > 0) {
|
|
87
|
+
throw withRollbackFailureCause(error, rollbackFailures);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
throw error;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export async function publishContractArtifactPair({
|
|
95
|
+
outputJsonPath,
|
|
96
|
+
outputDtsPath,
|
|
97
|
+
contractJson,
|
|
98
|
+
contractDts,
|
|
99
|
+
publicationToken,
|
|
100
|
+
beforePublish,
|
|
101
|
+
}: {
|
|
102
|
+
readonly outputJsonPath: string;
|
|
103
|
+
readonly outputDtsPath: string;
|
|
104
|
+
readonly contractJson: string;
|
|
105
|
+
readonly contractDts: string;
|
|
106
|
+
readonly publicationToken: string;
|
|
107
|
+
readonly beforePublish?: () => Promise<boolean> | boolean;
|
|
108
|
+
}): Promise<boolean> {
|
|
109
|
+
const tempJsonPath = createTempArtifactPath(outputJsonPath, publicationToken, 'next');
|
|
110
|
+
const tempDtsPath = createTempArtifactPath(outputDtsPath, publicationToken, 'next');
|
|
111
|
+
|
|
112
|
+
try {
|
|
113
|
+
await writeFile(tempJsonPath, contractJson, 'utf-8');
|
|
114
|
+
await writeFile(tempDtsPath, contractDts, 'utf-8');
|
|
115
|
+
|
|
116
|
+
if ((await beforePublish?.()) === false) {
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const previousJson = await readExistingArtifact(outputJsonPath);
|
|
121
|
+
const previousDts = await readExistingArtifact(outputDtsPath);
|
|
122
|
+
|
|
123
|
+
await publishPairWithRollback(
|
|
124
|
+
[
|
|
125
|
+
{ tempPath: tempDtsPath, outputPath: outputDtsPath, previous: previousDts },
|
|
126
|
+
{ tempPath: tempJsonPath, outputPath: outputJsonPath, previous: previousJson },
|
|
127
|
+
],
|
|
128
|
+
publicationToken,
|
|
129
|
+
);
|
|
130
|
+
return true;
|
|
131
|
+
} finally {
|
|
132
|
+
await Promise.allSettled([rm(tempJsonPath, { force: true }), rm(tempDtsPath, { force: true })]);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
import { CliStructuredError as CliStructuredError$1, errorConfigValidation as errorConfigValidation$1, errorContractConfigMissing as errorContractConfigMissing$1, errorContractValidationFailed, errorDatabaseConnectionRequired, errorDriverRequired, errorFileNotFound, errorMigrationPlanningFailed, errorTargetMigrationNotSupported, errorUnexpected as errorUnexpected$1 } from "@prisma-next/errors/control";
|
|
2
|
-
import { ERROR_CODE_DESTRUCTIVE_CHANGES, errorDestructiveChanges, errorHashMismatch, errorMarkerMissing, errorRunnerFailed, errorRuntime as errorRuntime$1, errorTargetMismatch } from "@prisma-next/errors/execution";
|
|
3
|
-
import "@prisma-next/errors/migration";
|
|
4
|
-
|
|
5
|
-
export { errorUnexpected$1 as _, errorContractValidationFailed as a, errorDriverRequired as c, errorMarkerMissing as d, errorMigrationPlanningFailed as f, errorTargetMismatch as g, errorTargetMigrationNotSupported as h, errorContractConfigMissing$1 as i, errorFileNotFound as l, errorRuntime$1 as m, ERROR_CODE_DESTRUCTIVE_CHANGES as n, errorDatabaseConnectionRequired as o, errorRunnerFailed as p, errorConfigValidation$1 as r, errorDestructiveChanges as s, CliStructuredError$1 as t, errorHashMismatch as u };
|