@prisma-next/cli 0.5.0-dev.9 → 0.5.0
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 +60 -25
- package/dist/cli-errors-B9OBbled.d.mts +3 -0
- package/dist/cli-errors-D3_sMh2K.mjs +33 -0
- package/dist/cli-errors-D3_sMh2K.mjs.map +1 -0
- package/dist/cli.mjs +16 -78
- package/dist/cli.mjs.map +1 -1
- package/dist/client-qVH-rEgd.mjs +1595 -0
- package/dist/client-qVH-rEgd.mjs.map +1 -0
- package/dist/{result-handler-Ba3zWQsI.mjs → command-helpers-BeZHkxV8.mjs} +70 -47
- package/dist/command-helpers-BeZHkxV8.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 +16 -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 +6 -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 +9 -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 +15 -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 +28 -13
- package/dist/commands/migration-apply.d.mts.map +1 -1
- package/dist/commands/migration-apply.mjs +55 -151
- 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 +34 -40
- package/dist/commands/migration-new.mjs.map +1 -1
- package/dist/commands/migration-plan.d.mts +33 -6
- package/dist/commands/migration-plan.d.mts.map +1 -1
- package/dist/commands/migration-plan.mjs +2 -348
- 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 +8 -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 +35 -36
- package/dist/commands/migration-show.mjs.map +1 -1
- package/dist/commands/migration-status.d.mts +126 -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-9DBda5Ou.mjs +150 -0
- package/dist/contract-emit-9DBda5Ou.mjs.map +1 -0
- package/dist/contract-emit-B77TsJqf.mjs +327 -0
- package/dist/contract-emit-B77TsJqf.mjs.map +1 -0
- package/dist/{contract-enrichment-CAOELa-H.mjs → contract-enrichment-Dani0mMW.mjs} +4 -6
- package/dist/contract-enrichment-Dani0mMW.mjs.map +1 -0
- package/dist/{contract-infer-D9cC3rJm.mjs → contract-infer-BK9YFGEG.mjs} +13 -22
- package/dist/contract-infer-BK9YFGEG.mjs.map +1 -0
- package/dist/db-verify-C0y1PCO2.mjs +404 -0
- package/dist/db-verify-C0y1PCO2.mjs.map +1 -0
- package/dist/exports/config-types.mjs +1 -2
- package/dist/exports/control-api.d.mts +101 -586
- 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/extension-pack-inputs-C7xgE-vv.mjs +74 -0
- package/dist/extension-pack-inputs-C7xgE-vv.mjs.map +1 -0
- package/dist/{framework-components-Cr--XBKy.mjs → framework-components-ChqVUxR-.mjs} +3 -4
- package/dist/{framework-components-Cr--XBKy.mjs.map → framework-components-ChqVUxR-.mjs.map} +1 -1
- package/dist/global-flags-Icqpxk23.d.mts +12 -0
- package/dist/global-flags-Icqpxk23.d.mts.map +1 -0
- package/dist/helpers-eqdN8tH6.mjs +25 -0
- package/dist/helpers-eqdN8tH6.mjs.map +1 -0
- package/dist/{init-C5220SY9.mjs → init-DETSgw3h.mjs} +40 -49
- package/dist/init-DETSgw3h.mjs.map +1 -0
- package/dist/{inspect-live-schema-yrHAvG71.mjs → inspect-live-schema-CWYxGKlb.mjs} +10 -11
- package/dist/inspect-live-schema-CWYxGKlb.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-B5dORFEv.mjs} +8 -9
- package/dist/migration-command-scaffold-B5dORFEv.mjs.map +1 -0
- package/dist/migration-plan-C6lVaHsO.mjs +554 -0
- package/dist/migration-plan-C6lVaHsO.mjs.map +1 -0
- package/dist/{migration-status-DUMiH8_G.mjs → migration-status-CZ-D5k7k.mjs} +272 -65
- package/dist/migration-status-CZ-D5k7k.mjs.map +1 -0
- package/dist/migrations-D_UJnpuW.mjs +216 -0
- package/dist/migrations-D_UJnpuW.mjs.map +1 -0
- package/dist/{output-BpcQrnnq.mjs → output-B16Kefzx.mjs} +9 -3
- package/dist/output-B16Kefzx.mjs.map +1 -0
- package/dist/{progress-adapter-DvQWB1nK.mjs → progress-adapter-DFfvZcYL.mjs} +2 -2
- package/dist/{progress-adapter-DvQWB1nK.mjs.map → progress-adapter-DFfvZcYL.mjs.map} +1 -1
- package/dist/result-handler-rmPVKIP2.mjs +25 -0
- package/dist/result-handler-rmPVKIP2.mjs.map +1 -0
- package/dist/rolldown-runtime-twds-ZHy.mjs +14 -0
- package/dist/{terminal-ui-C3ZLwQxK.mjs → terminal-ui-C_hFNbAn.mjs} +4 -28
- package/dist/terminal-ui-C_hFNbAn.mjs.map +1 -0
- package/dist/types-D7x-IFLO.d.mts +858 -0
- package/dist/types-D7x-IFLO.d.mts.map +1 -0
- package/dist/{verify-Bkycc-Tf.mjs → verify-CiwNWM9N.mjs} +3 -4
- package/dist/verify-CiwNWM9N.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 +15 -3
- package/src/commands/db-update.ts +9 -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 +26 -18
- package/src/commands/inspect-live-schema.ts +10 -5
- package/src/commands/migration-apply.ts +114 -212
- package/src/commands/migration-new.ts +42 -45
- package/src/commands/migration-plan.ts +212 -72
- package/src/commands/migration-ref.ts +8 -7
- package/src/commands/migration-show.ts +60 -41
- package/src/commands/migration-status.ts +483 -64
- package/src/config-path-validation.ts +0 -1
- package/src/control-api/client.ts +85 -5
- package/src/control-api/contract-enrichment.ts +6 -4
- package/src/control-api/operations/apply-aggregate.ts +290 -0
- package/src/control-api/operations/contract-emit.ts +198 -115
- package/src/control-api/operations/db-apply-aggregate.ts +397 -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 +424 -131
- package/src/control-api/types.ts +280 -29
- 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 +204 -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/extension-pack-inputs.ts +170 -0
- package/src/utils/formatters/graph-migration-mapper.ts +7 -3
- package/src/utils/formatters/migrations.ts +197 -61
- 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 +0 -153
- 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/terminal-ui-C3ZLwQxK.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
|
@@ -1,8 +1,42 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { OperationPreview } from '@prisma-next/framework-components/control';
|
|
2
|
+
import { cyan, green, yellow } from 'colorette';
|
|
2
3
|
|
|
4
|
+
import type { AggregatePerSpaceExecutionEntry } from '../../control-api/types';
|
|
3
5
|
import type { GlobalFlags } from '../global-flags';
|
|
4
6
|
import { createColorFormatter, formatDim, isVerbose } from './helpers';
|
|
5
7
|
|
|
8
|
+
/**
|
|
9
|
+
* Render a single statement of an `OperationPreview` for the human-readable
|
|
10
|
+
* preview block. SQL statements get a trailing `;` if missing — matches the
|
|
11
|
+
* legacy `string[]`-based renderer byte-for-byte (per spec OQ-4). Other
|
|
12
|
+
* languages (`'mongodb-shell'`) render verbatim.
|
|
13
|
+
*/
|
|
14
|
+
function renderPreviewStatement(text: string, language: string): string | undefined {
|
|
15
|
+
const trimmed = text.trim();
|
|
16
|
+
if (!trimmed) return undefined;
|
|
17
|
+
if (language === 'sql') {
|
|
18
|
+
return trimmed.endsWith(';') ? trimmed : `${trimmed};`;
|
|
19
|
+
}
|
|
20
|
+
return trimmed;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Choose the header label for a preview block. SQL-only previews keep the
|
|
25
|
+
* legacy `DDL preview` label (preserves CLI byte-identity for SQL targets per
|
|
26
|
+
* spec OQ-4); previews from any other family — or a mix that includes any
|
|
27
|
+
* non-SQL language — use the family-agnostic `Operation preview` label.
|
|
28
|
+
*
|
|
29
|
+
* An empty `statements` array deliberately renders as `Operation preview`
|
|
30
|
+
* rather than `DDL preview`: `Array.prototype.every` is vacuously true for
|
|
31
|
+
* empty arrays, but we have no evidence the preview is SQL-only when no
|
|
32
|
+
* statements are present, so the family-agnostic label is the safer default.
|
|
33
|
+
*/
|
|
34
|
+
export function previewBlockHeader(preview: OperationPreview): string {
|
|
35
|
+
const allSql =
|
|
36
|
+
preview.statements.length > 0 && preview.statements.every((s) => s.language === 'sql');
|
|
37
|
+
return allSql ? 'DDL preview' : 'Operation preview';
|
|
38
|
+
}
|
|
39
|
+
|
|
6
40
|
// ============================================================================
|
|
7
41
|
// Migration Command Output Formatters (shared by db init and db update)
|
|
8
42
|
// ============================================================================
|
|
@@ -24,7 +58,12 @@ export interface MigrationCommandResult {
|
|
|
24
58
|
readonly label: string;
|
|
25
59
|
readonly operationClass: string;
|
|
26
60
|
}[];
|
|
27
|
-
|
|
61
|
+
/**
|
|
62
|
+
* Family-agnostic textual preview of the planned operations. Replaces the
|
|
63
|
+
* previous `sql?: readonly string[]`. Consumers should read
|
|
64
|
+
* `plan.preview?.statements`.
|
|
65
|
+
*/
|
|
66
|
+
readonly preview?: OperationPreview;
|
|
28
67
|
};
|
|
29
68
|
readonly execution?: {
|
|
30
69
|
readonly operationsPlanned: number;
|
|
@@ -34,12 +73,67 @@ export interface MigrationCommandResult {
|
|
|
34
73
|
readonly storageHash: string;
|
|
35
74
|
readonly profileHash?: string;
|
|
36
75
|
};
|
|
76
|
+
/**
|
|
77
|
+
* Per-space execution breakdown in canonical schedule order
|
|
78
|
+
* (extensions alphabetically, then app). Surfaces per-space markers
|
|
79
|
+
* + ops grouped by space; closes F1 / F4 / F7 from the M6
|
|
80
|
+
* verification doc. See {@link AggregatePerSpaceExecutionEntry}.
|
|
81
|
+
*/
|
|
82
|
+
readonly perSpace?: ReadonlyArray<AggregatePerSpaceExecutionEntry>;
|
|
37
83
|
readonly summary: string;
|
|
38
84
|
readonly timings: {
|
|
39
85
|
readonly total: number;
|
|
40
86
|
};
|
|
41
87
|
}
|
|
42
88
|
|
|
89
|
+
/**
|
|
90
|
+
* Render the shared per-space execution block consumed by the `db init`
|
|
91
|
+
* / `db update` / `migration apply` summaries (M6 sub-spec § Output
|
|
92
|
+
* shape contract). Always shows: space label (`Extension space: <id>`
|
|
93
|
+
* or `App space`) → per-op lines under each space → per-space marker
|
|
94
|
+
* hash (when known).
|
|
95
|
+
*
|
|
96
|
+
* `mode` controls the marker label phrasing — `'apply'` shows
|
|
97
|
+
* `marker → <hash>` (post-apply), `'plan'` omits the marker line
|
|
98
|
+
* entirely (no marker has been written yet).
|
|
99
|
+
*/
|
|
100
|
+
export function formatPerSpaceBlock(
|
|
101
|
+
perSpace: ReadonlyArray<AggregatePerSpaceExecutionEntry>,
|
|
102
|
+
mode: 'plan' | 'apply',
|
|
103
|
+
useColor: boolean,
|
|
104
|
+
): readonly string[] {
|
|
105
|
+
const formatYellow = createColorFormatter(useColor, yellow);
|
|
106
|
+
const formatCyan = createColorFormatter(useColor, cyan);
|
|
107
|
+
const formatDimText = (text: string) => formatDim(useColor, text);
|
|
108
|
+
|
|
109
|
+
const lines: string[] = [];
|
|
110
|
+
for (let s = 0; s < perSpace.length; s++) {
|
|
111
|
+
const space = perSpace[s]!;
|
|
112
|
+
if (s > 0) lines.push('');
|
|
113
|
+
const header =
|
|
114
|
+
space.kind === 'app'
|
|
115
|
+
? formatCyan('App space')
|
|
116
|
+
: formatCyan(`Extension space: ${space.spaceId}`);
|
|
117
|
+
lines.push(header);
|
|
118
|
+
if (space.operations.length === 0) {
|
|
119
|
+
lines.push(` ${formatDimText('(no operations)')}`);
|
|
120
|
+
} else {
|
|
121
|
+
for (let i = 0; i < space.operations.length; i++) {
|
|
122
|
+
const op = space.operations[i]!;
|
|
123
|
+
const isLast = i === space.operations.length - 1;
|
|
124
|
+
const treeChar = isLast ? '└' : '├';
|
|
125
|
+
const destructiveMarker =
|
|
126
|
+
op.operationClass === 'destructive' ? ` ${formatYellow('(destructive)')}` : '';
|
|
127
|
+
lines.push(` ${formatDimText(treeChar)}─ ${op.label}${destructiveMarker}`);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
if (mode === 'apply' && space.marker) {
|
|
131
|
+
lines.push(` ${formatDimText(`marker: ${space.marker.storageHash}`)}`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return lines;
|
|
135
|
+
}
|
|
136
|
+
|
|
43
137
|
/**
|
|
44
138
|
* Formats human-readable output for migration commands (db init, db update) in plan mode.
|
|
45
139
|
*/
|
|
@@ -59,22 +153,43 @@ export function formatMigrationPlanOutput(
|
|
|
59
153
|
|
|
60
154
|
// Plan summary
|
|
61
155
|
const operationCount = result.plan?.operations.length ?? 0;
|
|
62
|
-
|
|
156
|
+
const spaceCount = result.perSpace?.length ?? 0;
|
|
157
|
+
if (spaceCount > 0) {
|
|
158
|
+
lines.push(
|
|
159
|
+
`${formatGreen('✔')} Planned ${operationCount} operation(s) across ${spaceCount} contract space${spaceCount === 1 ? '' : 's'}`,
|
|
160
|
+
);
|
|
161
|
+
} else {
|
|
162
|
+
lines.push(`${formatGreen('✔')} Planned ${operationCount} operation(s)`);
|
|
163
|
+
}
|
|
63
164
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
165
|
+
const formatYellow = createColorFormatter(useColor, yellow);
|
|
166
|
+
|
|
167
|
+
// Per-space breakdown takes precedence over the flat ops tree when
|
|
168
|
+
// the aggregate flow surfaced one (M6 sub-spec § Output shape contract).
|
|
169
|
+
if (result.perSpace && result.perSpace.length > 0) {
|
|
170
|
+
lines.push('');
|
|
171
|
+
lines.push(...formatPerSpaceBlock(result.perSpace, 'plan', useColor));
|
|
172
|
+
const hasDestructive = result.perSpace.some((s) =>
|
|
173
|
+
s.operations.some((op) => op.operationClass === 'destructive'),
|
|
174
|
+
);
|
|
175
|
+
if (hasDestructive) {
|
|
176
|
+
lines.push('');
|
|
177
|
+
lines.push(
|
|
178
|
+
`${formatYellow('⚠')} This migration contains destructive operations that may cause data loss.`,
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
} else if (result.plan?.operations && result.plan.operations.length > 0) {
|
|
182
|
+
// Single-space fallback (no aggregate breakdown). Same flat tree
|
|
183
|
+
// we've always rendered.
|
|
67
184
|
lines.push(`${formatDimText('│')}`);
|
|
68
185
|
for (let i = 0; i < result.plan.operations.length; i++) {
|
|
69
186
|
const op = result.plan.operations[i];
|
|
70
187
|
if (!op) continue;
|
|
71
188
|
const isLast = i === result.plan.operations.length - 1;
|
|
72
189
|
const treeChar = isLast ? '└' : '├';
|
|
73
|
-
const
|
|
74
|
-
op.operationClass === 'destructive'
|
|
75
|
-
|
|
76
|
-
: formatDimText(`[${op.operationClass}]`);
|
|
77
|
-
lines.push(`${formatDimText(treeChar)}─ ${op.label} ${opClassLabel}`);
|
|
190
|
+
const destructiveMarker =
|
|
191
|
+
op.operationClass === 'destructive' ? ` ${formatYellow('(destructive)')}` : '';
|
|
192
|
+
lines.push(`${formatDimText(treeChar)}─ ${op.label}${destructiveMarker}`);
|
|
78
193
|
}
|
|
79
194
|
|
|
80
195
|
const hasDestructive = result.plan.operations.some((op) => op.operationClass === 'destructive');
|
|
@@ -92,20 +207,20 @@ export function formatMigrationPlanOutput(
|
|
|
92
207
|
lines.push(`${formatDimText(`Destination hash: ${result.plan.destination.storageHash}`)}`);
|
|
93
208
|
}
|
|
94
209
|
|
|
95
|
-
//
|
|
96
|
-
const
|
|
97
|
-
if (
|
|
210
|
+
// Statement preview (any family that implements OperationPreviewCapable)
|
|
211
|
+
const preview = result.plan?.preview;
|
|
212
|
+
if (preview) {
|
|
98
213
|
lines.push('');
|
|
99
|
-
lines.push(`${formatDimText(
|
|
100
|
-
if (
|
|
101
|
-
lines.push(`${formatDimText('No
|
|
214
|
+
lines.push(`${formatDimText(previewBlockHeader(preview))}`);
|
|
215
|
+
if (preview.statements.length === 0) {
|
|
216
|
+
lines.push(`${formatDimText('No operations.')}`);
|
|
102
217
|
} else {
|
|
103
218
|
lines.push('');
|
|
104
|
-
for (const statement of
|
|
105
|
-
const
|
|
106
|
-
if (
|
|
107
|
-
|
|
108
|
-
|
|
219
|
+
for (const statement of preview.statements) {
|
|
220
|
+
const rendered = renderPreviewStatement(statement.text, statement.language);
|
|
221
|
+
if (rendered) {
|
|
222
|
+
lines.push(rendered);
|
|
223
|
+
}
|
|
109
224
|
}
|
|
110
225
|
}
|
|
111
226
|
}
|
|
@@ -127,10 +242,20 @@ export interface MigrationApplyCommandOutputResult {
|
|
|
127
242
|
readonly migrationsApplied: number;
|
|
128
243
|
readonly markerHash: string;
|
|
129
244
|
readonly applied: readonly {
|
|
130
|
-
readonly
|
|
245
|
+
readonly spaceId: string;
|
|
246
|
+
readonly dirName?: string;
|
|
247
|
+
readonly migrationHash?: string;
|
|
248
|
+
readonly from?: string;
|
|
249
|
+
readonly to?: string;
|
|
131
250
|
readonly operationsExecuted: number;
|
|
132
251
|
}[];
|
|
133
252
|
readonly summary: string;
|
|
253
|
+
/**
|
|
254
|
+
* Per-space breakdown in canonical schedule order (extensions
|
|
255
|
+
* alphabetically, then app). Always present for the aggregate-walking
|
|
256
|
+
* `migration apply` command.
|
|
257
|
+
*/
|
|
258
|
+
readonly perSpace: readonly AggregatePerSpaceExecutionEntry[];
|
|
134
259
|
readonly timings?: {
|
|
135
260
|
readonly total: number;
|
|
136
261
|
};
|
|
@@ -149,26 +274,17 @@ export function formatMigrationApplyCommandOutput(
|
|
|
149
274
|
const formatGreen = createColorFormatter(useColor, green);
|
|
150
275
|
const formatDimText = (text: string) => formatDim(useColor, text);
|
|
151
276
|
|
|
152
|
-
if (result.migrationsApplied === 0) {
|
|
153
|
-
lines.push(`${formatGreen('✔')} ${result.summary}`);
|
|
154
|
-
lines.push(formatDimText(` marker: ${result.markerHash}`));
|
|
155
|
-
return lines.join('\n');
|
|
156
|
-
}
|
|
157
|
-
|
|
158
277
|
lines.push(`${formatGreen('✔')} ${result.summary}`);
|
|
159
|
-
lines.push('');
|
|
160
278
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
const
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
`${formatDimText(treeChar)}─ ${migration.dirName} ${formatDimText(`[${migration.operationsExecuted} op(s)]`)}`,
|
|
167
|
-
);
|
|
279
|
+
if (result.perSpace.length > 0) {
|
|
280
|
+
lines.push('');
|
|
281
|
+
for (const line of formatPerSpaceBlock(result.perSpace, 'apply', useColor)) {
|
|
282
|
+
lines.push(line);
|
|
283
|
+
}
|
|
168
284
|
}
|
|
169
285
|
|
|
170
286
|
lines.push('');
|
|
171
|
-
lines.push(formatDimText(
|
|
287
|
+
lines.push(formatDimText('Next: prisma-next migration status'));
|
|
172
288
|
|
|
173
289
|
if (isVerbose(flags, 1) && result.timings) {
|
|
174
290
|
lines.push('');
|
|
@@ -181,17 +297,16 @@ export function formatMigrationApplyCommandOutput(
|
|
|
181
297
|
interface MigrationShowResult {
|
|
182
298
|
readonly dirName: string;
|
|
183
299
|
readonly dirPath: string;
|
|
184
|
-
readonly from: string;
|
|
300
|
+
readonly from: string | null;
|
|
185
301
|
readonly to: string;
|
|
186
|
-
readonly
|
|
187
|
-
readonly kind: string;
|
|
302
|
+
readonly migrationHash: string;
|
|
188
303
|
readonly createdAt: string;
|
|
189
304
|
readonly operations: readonly {
|
|
190
305
|
readonly id: string;
|
|
191
306
|
readonly label: string;
|
|
192
307
|
readonly operationClass: string;
|
|
193
308
|
}[];
|
|
194
|
-
readonly
|
|
309
|
+
readonly preview: OperationPreview;
|
|
195
310
|
readonly summary: string;
|
|
196
311
|
}
|
|
197
312
|
|
|
@@ -208,10 +323,9 @@ export function formatMigrationShowOutput(result: MigrationShowResult, flags: Gl
|
|
|
208
323
|
const formatDimText = (text: string) => formatDim(useColor, text);
|
|
209
324
|
|
|
210
325
|
lines.push(`${formatGreen('✔')} ${result.dirName}`);
|
|
211
|
-
lines.push(`${formatDimText(`
|
|
212
|
-
lines.push(`${formatDimText(` from: ${result.from}`)}`);
|
|
326
|
+
lines.push(`${formatDimText(` from: ${result.from ?? '(baseline)'}`)}`);
|
|
213
327
|
lines.push(`${formatDimText(` to: ${result.to}`)}`);
|
|
214
|
-
lines.push(`${formatDimText(`
|
|
328
|
+
lines.push(`${formatDimText(` migrationHash: ${result.migrationHash}`)}`);
|
|
215
329
|
lines.push(`${formatDimText(` created: ${result.createdAt}`)}`);
|
|
216
330
|
|
|
217
331
|
lines.push('');
|
|
@@ -223,11 +337,9 @@ export function formatMigrationShowOutput(result: MigrationShowResult, flags: Gl
|
|
|
223
337
|
const op = result.operations[i]!;
|
|
224
338
|
const isLast = i === result.operations.length - 1;
|
|
225
339
|
const treeChar = isLast ? '└' : '├';
|
|
226
|
-
const
|
|
227
|
-
op.operationClass === 'destructive'
|
|
228
|
-
|
|
229
|
-
: formatDimText(`[${op.operationClass}]`);
|
|
230
|
-
lines.push(`${formatDimText(treeChar)}─ ${op.label} ${opClassLabel}`);
|
|
340
|
+
const destructiveMarker =
|
|
341
|
+
op.operationClass === 'destructive' ? ` ${formatYellow('(destructive)')}` : '';
|
|
342
|
+
lines.push(`${formatDimText(treeChar)}─ ${op.label}${destructiveMarker}`);
|
|
231
343
|
}
|
|
232
344
|
|
|
233
345
|
const hasDestructive = result.operations.some((op) => op.operationClass === 'destructive');
|
|
@@ -239,15 +351,15 @@ export function formatMigrationShowOutput(result: MigrationShowResult, flags: Gl
|
|
|
239
351
|
}
|
|
240
352
|
}
|
|
241
353
|
|
|
242
|
-
if (result.
|
|
354
|
+
if (result.preview.statements.length > 0) {
|
|
243
355
|
lines.push('');
|
|
244
|
-
lines.push(`${formatDimText(
|
|
356
|
+
lines.push(`${formatDimText(previewBlockHeader(result.preview))}`);
|
|
245
357
|
lines.push('');
|
|
246
|
-
for (const statement of result.
|
|
247
|
-
const
|
|
248
|
-
if (
|
|
249
|
-
|
|
250
|
-
|
|
358
|
+
for (const statement of result.preview.statements) {
|
|
359
|
+
const rendered = renderPreviewStatement(statement.text, statement.language);
|
|
360
|
+
if (rendered) {
|
|
361
|
+
lines.push(rendered);
|
|
362
|
+
}
|
|
251
363
|
}
|
|
252
364
|
}
|
|
253
365
|
|
|
@@ -274,15 +386,39 @@ export function formatMigrationApplyOutput(
|
|
|
274
386
|
if (result.ok) {
|
|
275
387
|
// Success summary
|
|
276
388
|
const executed = result.execution?.operationsExecuted ?? 0;
|
|
389
|
+
const spaceCount = result.perSpace?.length ?? 0;
|
|
390
|
+
|
|
277
391
|
if (executed === 0) {
|
|
278
|
-
|
|
392
|
+
const acrossClause =
|
|
393
|
+
spaceCount > 0 ? ` across ${spaceCount} contract space${spaceCount === 1 ? '' : 's'}` : '';
|
|
394
|
+
lines.push(`${formatGreen('✔')} Database already matches contract${acrossClause}`);
|
|
395
|
+
} else if (spaceCount > 0) {
|
|
396
|
+
lines.push(
|
|
397
|
+
`${formatGreen('✔')} Applied ${executed} operation(s) across ${spaceCount} contract space${spaceCount === 1 ? '' : 's'}`,
|
|
398
|
+
);
|
|
279
399
|
} else {
|
|
280
400
|
lines.push(`${formatGreen('✔')} Applied ${executed} operation(s)`);
|
|
281
401
|
}
|
|
282
402
|
|
|
283
|
-
//
|
|
284
|
-
|
|
285
|
-
|
|
403
|
+
// Per-space breakdown — replaces the single ambiguous `Signature:`
|
|
404
|
+
// line per M6 sub-spec § Output shape contract / AC4 / AC5.
|
|
405
|
+
if (result.perSpace && result.perSpace.length > 0) {
|
|
406
|
+
lines.push('');
|
|
407
|
+
lines.push(...formatPerSpaceBlock(result.perSpace, 'apply', useColor));
|
|
408
|
+
lines.push('');
|
|
409
|
+
lines.push(
|
|
410
|
+
formatDimText(
|
|
411
|
+
`Run 'prisma-next migration status' to confirm ${
|
|
412
|
+
spaceCount === 1 ? 'the space is' : 'all spaces are'
|
|
413
|
+
} up to date.`,
|
|
414
|
+
),
|
|
415
|
+
);
|
|
416
|
+
} else if (result.marker) {
|
|
417
|
+
// Single-space fallback (no aggregate breakdown surfaced — e.g.
|
|
418
|
+
// older callers / non-aggregate code paths). Renamed from
|
|
419
|
+
// `Signature` to `App-space marker` per AC4 — when only one
|
|
420
|
+
// marker is observable, name what it covers explicitly.
|
|
421
|
+
lines.push(`${formatDimText(` App-space marker: ${result.marker.storageHash}`)}`);
|
|
286
422
|
if (result.marker.profileHash) {
|
|
287
423
|
lines.push(`${formatDimText(` Profile hash: ${result.marker.profileHash}`)}`);
|
|
288
424
|
}
|
|
@@ -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 };
|