@prisma-next/cli 0.14.0 → 0.15.0-dev.1
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 +2 -2
- package/dist/cli.mjs +33 -14
- package/dist/cli.mjs.map +1 -1
- package/dist/{client-CJzuo5wX.mjs → client-DigVI8zX.mjs} +90 -104
- package/dist/client-DigVI8zX.mjs.map +1 -0
- package/dist/{command-helpers-DGMvGBeX.mjs → command-helpers-CUoKYTlG.mjs} +13 -10
- package/dist/command-helpers-CUoKYTlG.mjs.map +1 -0
- package/dist/commands/contract-emit.mjs +1 -1
- package/dist/commands/contract-infer.mjs +1 -1
- package/dist/commands/db-init.mjs +3 -3
- package/dist/commands/db-schema.mjs +3 -3
- package/dist/commands/db-sign.mjs +5 -5
- package/dist/commands/db-sign.mjs.map +1 -1
- package/dist/commands/db-update.mjs +4 -4
- package/dist/commands/db-verify.d.mts.map +1 -1
- package/dist/commands/db-verify.mjs +1 -1
- package/dist/commands/migrate.d.mts +1 -1
- package/dist/commands/migrate.d.mts.map +1 -1
- package/dist/commands/migrate.mjs +37 -38
- package/dist/commands/migrate.mjs.map +1 -1
- package/dist/commands/migration-check.d.mts +1 -1
- package/dist/commands/migration-check.mjs +1 -1
- package/dist/commands/migration-graph.d.mts +1 -1
- package/dist/commands/migration-graph.mjs +9 -9
- package/dist/commands/migration-graph.mjs.map +1 -1
- package/dist/commands/migration-list.d.mts +6 -6
- package/dist/commands/migration-list.d.mts.map +1 -1
- package/dist/commands/migration-list.mjs +1 -1
- package/dist/commands/migration-log.d.mts +1 -1
- package/dist/commands/migration-log.mjs +1 -1
- package/dist/commands/migration-new.mjs +4 -4
- package/dist/commands/migration-new.mjs.map +1 -1
- package/dist/commands/migration-plan.d.mts.map +1 -1
- package/dist/commands/migration-plan.mjs +1 -1
- package/dist/commands/migration-show.mjs +4 -4
- package/dist/commands/migration-show.mjs.map +1 -1
- package/dist/commands/migration-status.d.mts +1 -1
- package/dist/commands/migration-status.mjs +1 -1
- package/dist/commands/ref.d.mts +1 -1
- package/dist/commands/ref.mjs +3 -3
- package/dist/commands/ref.mjs.map +1 -1
- package/dist/commands/telemetry/index.mjs +1 -1
- package/dist/{contract-at-errors-CFXsstzm.mjs → contract-at-errors-D8CNDD7g.mjs} +2 -2
- package/dist/{contract-at-errors-CFXsstzm.mjs.map → contract-at-errors-D8CNDD7g.mjs.map} +1 -1
- package/dist/{contract-emit-C8HmtboH.mjs → contract-emit-sYrqzsdN.mjs} +9 -11
- package/dist/contract-emit-sYrqzsdN.mjs.map +1 -0
- package/dist/{contract-emit-B_qriF8B.mjs → contract-emit-wgqrOXUI.mjs} +6 -6
- package/dist/contract-emit-wgqrOXUI.mjs.map +1 -0
- package/dist/{contract-infer-BYT_ra_U.mjs → contract-infer-6SQnnVj7.mjs} +3 -3
- package/dist/{contract-infer-BYT_ra_U.mjs.map → contract-infer-6SQnnVj7.mjs.map} +1 -1
- package/dist/{contract-space-aggregate-loader-ClI1KN6d.mjs → contract-space-aggregate-loader-DpB4x97a.mjs} +63 -5
- package/dist/contract-space-aggregate-loader-DpB4x97a.mjs.map +1 -0
- package/dist/{db-verify-C24FKhb7.mjs → db-verify-CEsG5Gek.mjs} +57 -61
- package/dist/db-verify-CEsG5Gek.mjs.map +1 -0
- package/dist/exports/control-api.d.mts +3 -3
- package/dist/exports/control-api.d.mts.map +1 -1
- package/dist/exports/control-api.mjs +2 -2
- package/dist/exports/index.d.mts +5 -1
- package/dist/exports/index.d.mts.map +1 -1
- package/dist/exports/index.mjs +4 -3
- package/dist/exports/index.mjs.map +1 -1
- package/dist/format-CjongNb1.mjs +102 -0
- package/dist/format-CjongNb1.mjs.map +1 -0
- package/dist/{framework-components-YVQHhPH7.mjs → framework-components-BbnM1RYm.mjs} +2 -2
- package/dist/{framework-components-YVQHhPH7.mjs.map → framework-components-BbnM1RYm.mjs.map} +1 -1
- package/dist/{init-0HwB-Vh8.mjs → init-D8YFfaD6.mjs} +27 -19
- package/dist/init-D8YFfaD6.mjs.map +1 -0
- package/dist/{inspect-live-schema-DF6IwcDl.mjs → inspect-live-schema-CEt_ztBJ.mjs} +4 -4
- package/dist/inspect-live-schema-CEt_ztBJ.mjs.map +1 -0
- package/dist/{migration-check-VwM8xCZV.mjs → migration-check-ByeQHOhK.mjs} +10 -12
- package/dist/migration-check-ByeQHOhK.mjs.map +1 -0
- package/dist/migration-cli.mjs +1 -1
- package/dist/migration-cli.mjs.map +1 -1
- package/dist/{migration-command-scaffold-DA-Lhx6o.mjs → migration-command-scaffold-CJO3tjiR.mjs} +4 -4
- package/dist/migration-command-scaffold-CJO3tjiR.mjs.map +1 -0
- package/dist/{migration-graph-command-render-CEez7YUK.mjs → migration-graph-command-render-B83xrnNy.mjs} +24 -24
- package/dist/{migration-graph-command-render-CEez7YUK.mjs.map → migration-graph-command-render-B83xrnNy.mjs.map} +1 -1
- package/dist/{migration-list-DlJJ_38Z.mjs → migration-list-DTYM8xvm.mjs} +16 -16
- package/dist/migration-list-DTYM8xvm.mjs.map +1 -0
- package/dist/{migration-log-CG0qQAFm.mjs → migration-log-qZ8LpQIl.mjs} +5 -5
- package/dist/migration-log-qZ8LpQIl.mjs.map +1 -0
- package/dist/{migration-path-target-Ce6OZImp.mjs → migration-path-target-DmNwsDgs.mjs} +2 -2
- package/dist/{migration-path-target-Ce6OZImp.mjs.map → migration-path-target-DmNwsDgs.mjs.map} +1 -1
- package/dist/{migration-plan-W_E8FQOk.mjs → migration-plan-CfPrB80t.mjs} +30 -30
- package/dist/migration-plan-CfPrB80t.mjs.map +1 -0
- package/dist/{migration-status-CD-LC2Ip.mjs → migration-status-DRuS-rP5.mjs} +14 -15
- package/dist/migration-status-DRuS-rP5.mjs.map +1 -0
- package/dist/{telemetry-BIM4beEO.mjs → telemetry-DHBBq5wT.mjs} +2 -2
- package/dist/{telemetry-BIM4beEO.mjs.map → telemetry-DHBBq5wT.mjs.map} +1 -1
- package/dist/{types-C_tYiJYx.d.mts → types-DFoqNIcQ.d.mts} +27 -21
- package/dist/types-DFoqNIcQ.d.mts.map +1 -0
- package/dist/{verify-DcOYZ1tH.mjs → verify-DDCb0R2r.mjs} +57 -159
- package/dist/verify-DDCb0R2r.mjs.map +1 -0
- package/package.json +28 -30
- package/src/cli.ts +7 -0
- package/src/commands/contract-emit.ts +1 -1
- package/src/commands/db-sign.ts +1 -1
- package/src/commands/db-verify.ts +34 -20
- package/src/commands/format.ts +75 -0
- package/src/commands/init/detect-package-manager.ts +8 -4
- package/src/commands/init/hygiene-package-scripts.ts +16 -3
- package/src/commands/inspect-live-schema.ts +1 -1
- package/src/commands/lsp.ts +32 -0
- package/src/commands/migrate.ts +47 -44
- package/src/commands/migration-check.ts +6 -6
- package/src/commands/migration-graph.ts +5 -5
- package/src/commands/migration-list.ts +13 -13
- package/src/commands/migration-log.ts +1 -1
- package/src/commands/migration-new.ts +1 -1
- package/src/commands/migration-plan.ts +15 -4
- package/src/commands/migration-show.ts +1 -1
- package/src/commands/migration-status.ts +10 -10
- package/src/commands/ref.ts +1 -1
- package/src/control-api/client.ts +8 -23
- package/src/control-api/operations/contract-emit.ts +9 -27
- package/src/control-api/operations/db-init.ts +2 -2
- package/src/control-api/operations/db-run.ts +18 -17
- package/src/control-api/operations/db-verify.ts +52 -44
- package/src/control-api/operations/format.ts +98 -0
- package/src/control-api/operations/migrate.ts +72 -72
- package/src/control-api/operations/run-migration.ts +6 -5
- package/src/control-api/types.ts +8 -8
- package/src/exports/index.ts +1 -0
- package/src/migration-cli.ts +1 -1
- package/src/utils/combine-verify-results.ts +94 -0
- package/src/utils/extension-pack-inputs.ts +1 -1
- package/src/utils/formatters/errors.ts +13 -5
- package/src/utils/formatters/verify.ts +88 -227
- package/src/utils/migration-command-scaffold.ts +1 -1
- package/src/utils/plan-resolution.ts +18 -18
- package/dist/client-CJzuo5wX.mjs.map +0 -1
- package/dist/command-helpers-DGMvGBeX.mjs.map +0 -1
- package/dist/config-loader-p9JMrekQ.mjs +0 -88
- package/dist/config-loader-p9JMrekQ.mjs.map +0 -1
- package/dist/config-loader.d.mts +0 -16
- package/dist/config-loader.d.mts.map +0 -1
- package/dist/config-loader.mjs +0 -2
- package/dist/contract-emit-B_qriF8B.mjs.map +0 -1
- package/dist/contract-emit-C8HmtboH.mjs.map +0 -1
- package/dist/contract-space-aggregate-loader-ClI1KN6d.mjs.map +0 -1
- package/dist/db-verify-C24FKhb7.mjs.map +0 -1
- package/dist/extension-pack-inputs-1ySHqxKG.mjs +0 -62
- package/dist/extension-pack-inputs-1ySHqxKG.mjs.map +0 -1
- package/dist/init-0HwB-Vh8.mjs.map +0 -1
- package/dist/inspect-live-schema-DF6IwcDl.mjs.map +0 -1
- package/dist/migration-check-VwM8xCZV.mjs.map +0 -1
- package/dist/migration-command-scaffold-DA-Lhx6o.mjs.map +0 -1
- package/dist/migration-list-DlJJ_38Z.mjs.map +0 -1
- package/dist/migration-log-CG0qQAFm.mjs.map +0 -1
- package/dist/migration-plan-W_E8FQOk.mjs.map +0 -1
- package/dist/migration-status-CD-LC2Ip.mjs.map +0 -1
- package/dist/types-C_tYiJYx.d.mts.map +0 -1
- package/dist/verify-DcOYZ1tH.mjs.map +0 -1
- package/src/config-loader.ts +0 -92
- package/src/config-path-validation.ts +0 -74
- package/src/utils/combine-schema-results.ts +0 -84
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
CoreSchemaView,
|
|
3
|
+
ExpectationFailureReason,
|
|
3
4
|
IntrospectSchemaResult,
|
|
5
|
+
SchemaDiffIssue,
|
|
4
6
|
SchemaTreeNode,
|
|
5
|
-
SchemaVerificationNode,
|
|
6
7
|
SignDatabaseResult,
|
|
7
8
|
VerifyDatabaseResult,
|
|
8
9
|
VerifyDatabaseSchemaResult,
|
|
@@ -12,6 +13,23 @@ import { bold, cyan, dim, green, magenta, red, yellow } from 'colorette';
|
|
|
12
13
|
import type { GlobalFlags } from '../global-flags';
|
|
13
14
|
import { createColorFormatter, formatDim, isVerbose } from './helpers';
|
|
14
15
|
|
|
16
|
+
/** Human-readable label for each failure reason, prefixed onto an issue's message for display. */
|
|
17
|
+
const REASON_LABEL: Record<ExpectationFailureReason, string> = {
|
|
18
|
+
'not-found': 'missing',
|
|
19
|
+
'not-expected': 'extra',
|
|
20
|
+
'not-equal': 'mismatch',
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* The issue's display text: its own path, prefixed with a human label for
|
|
25
|
+
* why it's flagged. Turning `reason` (and the path) into prose is this
|
|
26
|
+
* formatter's job, not the differ's — the differ's issue is data (`path` +
|
|
27
|
+
* `reason` + nodes), not prose.
|
|
28
|
+
*/
|
|
29
|
+
function formatIssueMessage(issue: SchemaDiffIssue): string {
|
|
30
|
+
return `${REASON_LABEL[issue.reason]}: ${issue.path.join('/')}`;
|
|
31
|
+
}
|
|
32
|
+
|
|
15
33
|
// ============================================================================
|
|
16
34
|
// Verify Output Formatters
|
|
17
35
|
// ============================================================================
|
|
@@ -27,9 +45,20 @@ export interface DbVerifyCommandSuccessResult {
|
|
|
27
45
|
readonly codecCoverageSkipped?: VerifyDatabaseResult['codecCoverageSkipped'];
|
|
28
46
|
readonly schema?: {
|
|
29
47
|
readonly summary: string;
|
|
30
|
-
readonly counts: VerifyDatabaseSchemaResult['schema']['counts'];
|
|
31
48
|
readonly strict: boolean;
|
|
49
|
+
/**
|
|
50
|
+
* Warn-graded finding messages (observed-policy drift). Informational —
|
|
51
|
+
* present on a passing verify; the full-mode result summarizes them as a
|
|
52
|
+
* flat message list.
|
|
53
|
+
*/
|
|
54
|
+
readonly warnings?: readonly string[];
|
|
32
55
|
};
|
|
56
|
+
/**
|
|
57
|
+
* Live element names no contract space declares. In full success this is
|
|
58
|
+
* only ever non-empty in lenient mode — strict mode fails on it — and is
|
|
59
|
+
* rendered informationally.
|
|
60
|
+
*/
|
|
61
|
+
readonly unclaimed?: readonly string[];
|
|
33
62
|
readonly warning?: string;
|
|
34
63
|
readonly meta?:
|
|
35
64
|
| (NonNullable<VerifyDatabaseResult['meta']> & {
|
|
@@ -71,11 +100,22 @@ export function formatVerifyOutput(
|
|
|
71
100
|
if (result.contract.profileHash) {
|
|
72
101
|
lines.push(`${formatDimText(` profileHash: ${result.contract.profileHash}`)}`);
|
|
73
102
|
}
|
|
74
|
-
if (result.
|
|
75
|
-
lines.push(
|
|
76
|
-
|
|
77
|
-
)
|
|
103
|
+
if (result.schema?.warnings && result.schema.warnings.length > 0) {
|
|
104
|
+
lines.push('');
|
|
105
|
+
lines.push(formatYellow('Schema warnings:'));
|
|
106
|
+
for (const message of result.schema.warnings) {
|
|
107
|
+
lines.push(` ${formatYellow('⚠')} ${message}`);
|
|
108
|
+
}
|
|
78
109
|
}
|
|
110
|
+
|
|
111
|
+
if (result.unclaimed && result.unclaimed.length > 0) {
|
|
112
|
+
lines.push('');
|
|
113
|
+
lines.push(formatYellow('Unclaimed elements (declared by no contract):'));
|
|
114
|
+
for (const name of result.unclaimed) {
|
|
115
|
+
lines.push(` ${formatYellow('⚠')} ${name}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
79
119
|
if (result.warning) {
|
|
80
120
|
lines.push('');
|
|
81
121
|
lines.push(`${formatYellow('⚠')} ${result.warning}`);
|
|
@@ -107,6 +147,7 @@ export function formatVerifyJson(result: DbVerifyCommandSuccessResult): string {
|
|
|
107
147
|
...ifDefined('missingCodecs', result.missingCodecs),
|
|
108
148
|
...ifDefined('codecCoverageSkipped', result.codecCoverageSkipped),
|
|
109
149
|
...ifDefined('schema', result.schema),
|
|
150
|
+
unclaimed: result.unclaimed ?? [],
|
|
110
151
|
...ifDefined('warning', result.warning),
|
|
111
152
|
...ifDefined('meta', result.meta),
|
|
112
153
|
timings: result.timings,
|
|
@@ -124,7 +165,7 @@ export function formatIntrospectJson(result: IntrospectSchemaResult<unknown>): s
|
|
|
124
165
|
|
|
125
166
|
/**
|
|
126
167
|
* Renders a schema tree structure from CoreSchemaView.
|
|
127
|
-
*
|
|
168
|
+
* Status-glyph tree styling shared with the retired verification-tree renderer.
|
|
128
169
|
*/
|
|
129
170
|
function renderSchemaTree(
|
|
130
171
|
node: SchemaTreeNode,
|
|
@@ -310,213 +351,13 @@ export function formatIntrospectOutput(
|
|
|
310
351
|
return lines.join('\n');
|
|
311
352
|
}
|
|
312
353
|
|
|
313
|
-
/**
|
|
314
|
-
* Renders a schema verification tree structure from SchemaVerificationNode.
|
|
315
|
-
* Similar to renderSchemaTree but for verification nodes with status-based colors and glyphs.
|
|
316
|
-
*/
|
|
317
|
-
function renderSchemaVerificationTree(
|
|
318
|
-
node: SchemaVerificationNode,
|
|
319
|
-
flags: GlobalFlags,
|
|
320
|
-
options: {
|
|
321
|
-
readonly isLast: boolean;
|
|
322
|
-
readonly prefix: string;
|
|
323
|
-
readonly useColor: boolean;
|
|
324
|
-
readonly formatDimText: (text: string) => string;
|
|
325
|
-
readonly isRoot?: boolean;
|
|
326
|
-
},
|
|
327
|
-
): string[] {
|
|
328
|
-
const { isLast, prefix, useColor, formatDimText, isRoot = false } = options;
|
|
329
|
-
const lines: string[] = [];
|
|
330
|
-
|
|
331
|
-
// Format status glyph and color based on status
|
|
332
|
-
let statusGlyph = '';
|
|
333
|
-
let statusColor: (text: string) => string = (text) => text;
|
|
334
|
-
if (useColor) {
|
|
335
|
-
switch (node.status) {
|
|
336
|
-
case 'pass':
|
|
337
|
-
statusGlyph = '✔';
|
|
338
|
-
statusColor = green;
|
|
339
|
-
break;
|
|
340
|
-
case 'warn':
|
|
341
|
-
statusGlyph = '⚠';
|
|
342
|
-
statusColor = (text) => (useColor ? yellow(text) : text);
|
|
343
|
-
break;
|
|
344
|
-
case 'fail':
|
|
345
|
-
statusGlyph = '✖';
|
|
346
|
-
statusColor = red;
|
|
347
|
-
break;
|
|
348
|
-
}
|
|
349
|
-
} else {
|
|
350
|
-
switch (node.status) {
|
|
351
|
-
case 'pass':
|
|
352
|
-
statusGlyph = '✔';
|
|
353
|
-
break;
|
|
354
|
-
case 'warn':
|
|
355
|
-
statusGlyph = '⚠';
|
|
356
|
-
break;
|
|
357
|
-
case 'fail':
|
|
358
|
-
statusGlyph = '✖';
|
|
359
|
-
break;
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
// Format node label with color based on kind
|
|
364
|
-
// For column nodes, we need to parse the name to color code different parts
|
|
365
|
-
let labelColor: (text: string) => string = (text) => text;
|
|
366
|
-
let formattedLabel: string = node.name;
|
|
367
|
-
|
|
368
|
-
if (useColor) {
|
|
369
|
-
switch (node.kind) {
|
|
370
|
-
case 'contract':
|
|
371
|
-
case 'schema':
|
|
372
|
-
labelColor = bold;
|
|
373
|
-
formattedLabel = labelColor(node.name);
|
|
374
|
-
break;
|
|
375
|
-
case 'table': {
|
|
376
|
-
// Parse "table tableName" format - color "table" dim, tableName cyan
|
|
377
|
-
const tableMatch = node.name.match(/^table\s+(.+)$/);
|
|
378
|
-
if (tableMatch?.[1]) {
|
|
379
|
-
const tableName = tableMatch[1];
|
|
380
|
-
formattedLabel = `${dim('table')} ${cyan(tableName)}`;
|
|
381
|
-
} else {
|
|
382
|
-
formattedLabel = dim(node.name);
|
|
383
|
-
}
|
|
384
|
-
break;
|
|
385
|
-
}
|
|
386
|
-
case 'columns':
|
|
387
|
-
labelColor = dim;
|
|
388
|
-
formattedLabel = labelColor(node.name);
|
|
389
|
-
break;
|
|
390
|
-
case 'column': {
|
|
391
|
-
// Parse column name format: "columnName: contractType -> nativeType (nullability)"
|
|
392
|
-
// Color code: column name (cyan), contract type (default), native type (dim), nullability (dim)
|
|
393
|
-
const columnMatch = node.name.match(/^([^:]+):\s*(.+)$/);
|
|
394
|
-
if (columnMatch?.[1] && columnMatch[2]) {
|
|
395
|
-
const columnName = columnMatch[1];
|
|
396
|
-
const rest = columnMatch[2];
|
|
397
|
-
// Parse rest: "contractType -> nativeType (nullability)"
|
|
398
|
-
// Match contract type (can contain /, @, etc.), arrow, native type, then nullability in parentheses
|
|
399
|
-
const typeMatch = rest.match(/^([^\s→]+)\s*→\s*([^\s(]+)\s*(\([^)]+\))$/);
|
|
400
|
-
if (typeMatch?.[1] && typeMatch[2] && typeMatch[3]) {
|
|
401
|
-
const contractType = typeMatch[1];
|
|
402
|
-
const nativeType = typeMatch[2];
|
|
403
|
-
const nullability = typeMatch[3];
|
|
404
|
-
formattedLabel = `${cyan(columnName)}: ${contractType} → ${dim(nativeType)} ${dim(nullability)}`;
|
|
405
|
-
} else {
|
|
406
|
-
// Fallback if format doesn't match (e.g., no native type or no nullability)
|
|
407
|
-
formattedLabel = `${cyan(columnName)}: ${rest}`;
|
|
408
|
-
}
|
|
409
|
-
} else {
|
|
410
|
-
formattedLabel = node.name;
|
|
411
|
-
}
|
|
412
|
-
break;
|
|
413
|
-
}
|
|
414
|
-
case 'type':
|
|
415
|
-
case 'nullability':
|
|
416
|
-
labelColor = (text) => text; // Default color
|
|
417
|
-
formattedLabel = labelColor(node.name);
|
|
418
|
-
break;
|
|
419
|
-
case 'primaryKey': {
|
|
420
|
-
// Parse "primary key: columnName" format - color "primary key" dim, columnName cyan
|
|
421
|
-
const pkMatch = node.name.match(/^primary key:\s*(.+)$/);
|
|
422
|
-
if (pkMatch?.[1]) {
|
|
423
|
-
const columnNames = pkMatch[1];
|
|
424
|
-
formattedLabel = `${dim('primary key')}: ${cyan(columnNames)}`;
|
|
425
|
-
} else {
|
|
426
|
-
formattedLabel = dim(node.name);
|
|
427
|
-
}
|
|
428
|
-
break;
|
|
429
|
-
}
|
|
430
|
-
case 'foreignKey':
|
|
431
|
-
case 'unique':
|
|
432
|
-
case 'index':
|
|
433
|
-
labelColor = dim;
|
|
434
|
-
formattedLabel = labelColor(node.name);
|
|
435
|
-
break;
|
|
436
|
-
case 'dependency': {
|
|
437
|
-
// Parse specific extension message formats
|
|
438
|
-
// "database is postgres" -> dim "database is", cyan "postgres"
|
|
439
|
-
const dbMatch = node.name.match(/^database is\s+(.+)$/);
|
|
440
|
-
if (dbMatch?.[1]) {
|
|
441
|
-
const dbName = dbMatch[1];
|
|
442
|
-
formattedLabel = `${dim('database is')} ${cyan(dbName)}`;
|
|
443
|
-
} else {
|
|
444
|
-
// "vector extension is enabled" -> dim everything except extension name
|
|
445
|
-
// Match pattern: "extensionName extension is enabled"
|
|
446
|
-
const extMatch = node.name.match(/^([^\s]+)\s+(extension is enabled)$/);
|
|
447
|
-
if (extMatch?.[1] && extMatch[2]) {
|
|
448
|
-
const extName = extMatch[1];
|
|
449
|
-
const rest = extMatch[2];
|
|
450
|
-
formattedLabel = `${cyan(extName)} ${dim(rest)}`;
|
|
451
|
-
} else {
|
|
452
|
-
// Fallback: color entire name with magenta
|
|
453
|
-
labelColor = magenta;
|
|
454
|
-
formattedLabel = labelColor(node.name);
|
|
455
|
-
}
|
|
456
|
-
}
|
|
457
|
-
break;
|
|
458
|
-
}
|
|
459
|
-
default:
|
|
460
|
-
formattedLabel = node.name;
|
|
461
|
-
break;
|
|
462
|
-
}
|
|
463
|
-
} else {
|
|
464
|
-
formattedLabel = node.name;
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
const statusGlyphColored = statusColor(statusGlyph);
|
|
468
|
-
|
|
469
|
-
// Build the label with optional message for failure/warn nodes
|
|
470
|
-
let nodeLabel = formattedLabel;
|
|
471
|
-
if (
|
|
472
|
-
(node.status === 'fail' || node.status === 'warn') &&
|
|
473
|
-
node.message &&
|
|
474
|
-
node.message.length > 0
|
|
475
|
-
) {
|
|
476
|
-
// Always show message for failure/warn nodes - it provides crucial context
|
|
477
|
-
// For parent nodes, the message summarizes child failures
|
|
478
|
-
// For leaf nodes, the message explains the specific issue
|
|
479
|
-
const messageText = formatDimText(`(${node.message})`);
|
|
480
|
-
nodeLabel = `${formattedLabel} ${messageText}`;
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
// Root node renders without tree characters or | prefix
|
|
484
|
-
// Root node renders without tree characters or prefix
|
|
485
|
-
if (isRoot) {
|
|
486
|
-
lines.push(`${statusGlyphColored} ${nodeLabel}`);
|
|
487
|
-
} else {
|
|
488
|
-
const treeChar = isLast ? '└' : '├';
|
|
489
|
-
const treePrefix = `${formatDimText(treeChar)}─ `;
|
|
490
|
-
lines.push(`${prefix}${treePrefix}${statusGlyphColored} ${nodeLabel}`);
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
// Render children if present
|
|
494
|
-
if (node.children && node.children.length > 0) {
|
|
495
|
-
const childPrefix = isRoot ? '' : `${prefix}${isLast ? ' ' : `${formatDimText('│')} `}`;
|
|
496
|
-
for (let i = 0; i < node.children.length; i++) {
|
|
497
|
-
const child = node.children[i];
|
|
498
|
-
if (!child) continue;
|
|
499
|
-
const isLastChild = i === node.children.length - 1;
|
|
500
|
-
const childLines = renderSchemaVerificationTree(child, flags, {
|
|
501
|
-
isLast: isLastChild,
|
|
502
|
-
prefix: childPrefix,
|
|
503
|
-
useColor,
|
|
504
|
-
formatDimText,
|
|
505
|
-
isRoot: false,
|
|
506
|
-
});
|
|
507
|
-
lines.push(...childLines);
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
return lines;
|
|
512
|
-
}
|
|
513
|
-
|
|
514
354
|
/**
|
|
515
355
|
* Formats human-readable output for database schema verification.
|
|
516
356
|
*/
|
|
517
357
|
export function formatSchemaVerifyOutput(
|
|
518
358
|
result: VerifyDatabaseSchemaResult,
|
|
519
359
|
flags: GlobalFlags,
|
|
360
|
+
unclaimed: readonly string[] = [],
|
|
520
361
|
): string {
|
|
521
362
|
if (flags.quiet) {
|
|
522
363
|
return '';
|
|
@@ -527,30 +368,45 @@ export function formatSchemaVerifyOutput(
|
|
|
527
368
|
const useColor = flags.color !== false;
|
|
528
369
|
const formatGreen = createColorFormatter(useColor, green);
|
|
529
370
|
const formatRed = createColorFormatter(useColor, red);
|
|
371
|
+
const formatYellow = createColorFormatter(useColor, yellow);
|
|
530
372
|
const formatDimText = (text: string) => formatDim(useColor, text);
|
|
531
373
|
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
374
|
+
const issueMessages = result.schema.issues.map(formatIssueMessage);
|
|
375
|
+
if (issueMessages.length > 0) {
|
|
376
|
+
lines.push(formatRed('Schema issues:'));
|
|
377
|
+
for (const message of issueMessages) {
|
|
378
|
+
lines.push(` ${formatRed('✖')} ${message}`);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
const warningMessages = (result.schema.warnings?.issues ?? []).map(formatIssueMessage);
|
|
383
|
+
if (warningMessages.length > 0) {
|
|
384
|
+
if (lines.length > 0) lines.push('');
|
|
385
|
+
lines.push(formatYellow('Schema warnings:'));
|
|
386
|
+
for (const message of warningMessages) {
|
|
387
|
+
lines.push(` ${formatYellow('⚠')} ${message}`);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
if (unclaimed.length > 0) {
|
|
392
|
+
const strict = result.meta?.strict ?? false;
|
|
393
|
+
if (lines.length > 0) lines.push('');
|
|
545
394
|
lines.push(
|
|
546
|
-
|
|
395
|
+
(strict ? formatRed : formatYellow)('Unclaimed elements (declared by no contract):'),
|
|
547
396
|
);
|
|
397
|
+
for (const name of unclaimed) {
|
|
398
|
+
lines.push(` ${(strict ? formatRed : formatYellow)(strict ? '✖' : '⚠')} ${name}`);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
if (isVerbose(flags, 1)) {
|
|
403
|
+
lines.push(`${formatDimText(` Total time: ${result.timings.total}ms`)}`);
|
|
548
404
|
}
|
|
549
405
|
|
|
550
406
|
// Blank line before summary
|
|
551
|
-
lines.push('');
|
|
407
|
+
if (lines.length > 0) lines.push('');
|
|
552
408
|
|
|
553
|
-
// Summary line at the end:
|
|
409
|
+
// Summary line at the end: verdict with status glyph
|
|
554
410
|
if (result.ok) {
|
|
555
411
|
lines.push(`${formatGreen('✔')} ${result.summary}`);
|
|
556
412
|
} else {
|
|
@@ -562,10 +418,15 @@ export function formatSchemaVerifyOutput(
|
|
|
562
418
|
}
|
|
563
419
|
|
|
564
420
|
/**
|
|
565
|
-
* Formats JSON output for database schema verification.
|
|
421
|
+
* Formats JSON output for database schema verification. The unclaimed-elements
|
|
422
|
+
* list is a top-level field alongside the combined result, reported once for
|
|
423
|
+
* the whole database.
|
|
566
424
|
*/
|
|
567
|
-
export function formatSchemaVerifyJson(
|
|
568
|
-
|
|
425
|
+
export function formatSchemaVerifyJson(
|
|
426
|
+
result: VerifyDatabaseSchemaResult,
|
|
427
|
+
unclaimed: readonly string[] = [],
|
|
428
|
+
): string {
|
|
429
|
+
return JSON.stringify({ ...result, unclaimed }, null, 2);
|
|
569
430
|
}
|
|
570
431
|
|
|
571
432
|
// ============================================================================
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { readFile } from 'node:fs/promises';
|
|
2
2
|
import { relative, resolve } from 'node:path';
|
|
3
|
+
import { loadConfig } from '@prisma-next/config-loader';
|
|
3
4
|
import { hasMigrations } from '@prisma-next/framework-components/control';
|
|
4
5
|
import { notOk, ok, type Result } from '@prisma-next/utils/result';
|
|
5
6
|
import type { Command } from 'commander';
|
|
6
|
-
import { loadConfig } from '../config-loader';
|
|
7
7
|
import { createControlClient } from '../control-api/client';
|
|
8
8
|
import type { ControlClient } from '../control-api/types';
|
|
9
9
|
import {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Contract } from '@prisma-next/contract/types';
|
|
2
|
-
import type {
|
|
2
|
+
import type { AggregateContractSpace } from '@prisma-next/migration-tools/aggregate';
|
|
3
3
|
import { MigrationToolsError } from '@prisma-next/migration-tools/errors';
|
|
4
4
|
import type { MigrationGraph } from '@prisma-next/migration-tools/graph';
|
|
5
5
|
import {
|
|
@@ -45,11 +45,11 @@ export type FromResolution =
|
|
|
45
45
|
|
|
46
46
|
export interface ResolveFromForPlanInput {
|
|
47
47
|
readonly optionsFrom?: string | undefined;
|
|
48
|
-
readonly
|
|
48
|
+
readonly space: AggregateContractSpace;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
function graphIsEmpty(
|
|
52
|
-
return
|
|
51
|
+
function graphIsEmpty(space: AggregateContractSpace): boolean {
|
|
52
|
+
return space.packages.length === 0;
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
function getReachableRefs(
|
|
@@ -98,14 +98,14 @@ type RefContractResolution =
|
|
|
98
98
|
|
|
99
99
|
async function resolveContractRef(
|
|
100
100
|
parsed: ContractRef,
|
|
101
|
-
|
|
101
|
+
space: AggregateContractSpace,
|
|
102
102
|
options?: { readonly explicitLabel?: string; readonly artifactRole?: 'from' | 'to' },
|
|
103
103
|
): Promise<Result<RefContractResolution, CliStructuredError>> {
|
|
104
104
|
const { hash, provenance } = parsed;
|
|
105
105
|
const refName = provenance.kind === 'ref' ? provenance.refName : undefined;
|
|
106
106
|
|
|
107
107
|
try {
|
|
108
|
-
const at = await
|
|
108
|
+
const at = await space.contractAt(hash, refName !== undefined ? { refName } : undefined);
|
|
109
109
|
|
|
110
110
|
if (at.provenance === 'snapshot') {
|
|
111
111
|
return ok({
|
|
@@ -139,7 +139,7 @@ async function resolveFromPolicy(
|
|
|
139
139
|
refs: Refs,
|
|
140
140
|
explicitFromLabel?: string,
|
|
141
141
|
): Promise<Result<FromResolution, CliStructuredError>> {
|
|
142
|
-
const resolution = await resolveContractRef(parsed, input.
|
|
142
|
+
const resolution = await resolveContractRef(parsed, input.space, {
|
|
143
143
|
...(explicitFromLabel !== undefined ? { explicitLabel: explicitFromLabel } : {}),
|
|
144
144
|
artifactRole: 'from',
|
|
145
145
|
});
|
|
@@ -157,7 +157,7 @@ async function resolveFromPolicy(
|
|
|
157
157
|
}
|
|
158
158
|
|
|
159
159
|
const { hash, contract, contractJson, contractDts } = resolution.value;
|
|
160
|
-
if (graphIsEmpty(input.
|
|
160
|
+
if (graphIsEmpty(input.space)) {
|
|
161
161
|
return ok({
|
|
162
162
|
kind: 'auto-baseline',
|
|
163
163
|
fromHash: hash,
|
|
@@ -167,7 +167,7 @@ async function resolveFromPolicy(
|
|
|
167
167
|
});
|
|
168
168
|
}
|
|
169
169
|
|
|
170
|
-
const graph = input.
|
|
170
|
+
const graph = input.space.graph();
|
|
171
171
|
const graphTip = findLatestMigration(graph)?.to ?? null;
|
|
172
172
|
try {
|
|
173
173
|
assertFromIsGraphNode(hash, graph, refs, graphTip);
|
|
@@ -189,9 +189,9 @@ async function resolveFromPolicy(
|
|
|
189
189
|
export async function resolveFromForPlan(
|
|
190
190
|
input: ResolveFromForPlanInput,
|
|
191
191
|
): Promise<Result<FromResolution, CliStructuredError>> {
|
|
192
|
-
const { optionsFrom,
|
|
193
|
-
const graph =
|
|
194
|
-
const refs =
|
|
192
|
+
const { optionsFrom, space } = input;
|
|
193
|
+
const graph = space.graph();
|
|
194
|
+
const refs = space.refs;
|
|
195
195
|
|
|
196
196
|
if (optionsFrom === undefined) {
|
|
197
197
|
const dbRef = refs['db'];
|
|
@@ -208,7 +208,7 @@ export async function resolveFromForPlan(
|
|
|
208
208
|
const refResult = parseContractRef(optionsFrom, { graph, refs });
|
|
209
209
|
if (!refResult.ok) {
|
|
210
210
|
if (looksLikeFullHash(optionsFrom)) {
|
|
211
|
-
const empty = graphIsEmpty(
|
|
211
|
+
const empty = graphIsEmpty(space);
|
|
212
212
|
const graphTip = findLatestMigration(graph)?.to ?? null;
|
|
213
213
|
if (empty) {
|
|
214
214
|
return notOk(errorSnapshotMissing(optionsFrom, { viaRef: false }));
|
|
@@ -222,7 +222,7 @@ export async function resolveFromForPlan(
|
|
|
222
222
|
}
|
|
223
223
|
|
|
224
224
|
export interface ResolveToForPlanInput {
|
|
225
|
-
readonly
|
|
225
|
+
readonly space: AggregateContractSpace;
|
|
226
226
|
}
|
|
227
227
|
|
|
228
228
|
export interface ResolvedContractRef {
|
|
@@ -236,16 +236,16 @@ export async function resolveToForPlan(
|
|
|
236
236
|
optionsTo: string,
|
|
237
237
|
input: ResolveToForPlanInput,
|
|
238
238
|
): Promise<Result<ResolvedContractRef, CliStructuredError>> {
|
|
239
|
-
const {
|
|
240
|
-
const graph =
|
|
241
|
-
const refs =
|
|
239
|
+
const { space } = input;
|
|
240
|
+
const graph = space.graph();
|
|
241
|
+
const refs = space.refs;
|
|
242
242
|
|
|
243
243
|
const refResult = parseContractRef(optionsTo, { graph, refs });
|
|
244
244
|
if (!refResult.ok) {
|
|
245
245
|
return notOk(mapRefResolutionError(refResult.failure));
|
|
246
246
|
}
|
|
247
247
|
|
|
248
|
-
const resolution = await resolveContractRef(refResult.value,
|
|
248
|
+
const resolution = await resolveContractRef(refResult.value, space, {
|
|
249
249
|
explicitLabel: optionsTo,
|
|
250
250
|
artifactRole: 'to',
|
|
251
251
|
});
|