@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.
Files changed (156) hide show
  1. package/README.md +2 -2
  2. package/dist/cli.mjs +33 -14
  3. package/dist/cli.mjs.map +1 -1
  4. package/dist/{client-CJzuo5wX.mjs → client-DigVI8zX.mjs} +90 -104
  5. package/dist/client-DigVI8zX.mjs.map +1 -0
  6. package/dist/{command-helpers-DGMvGBeX.mjs → command-helpers-CUoKYTlG.mjs} +13 -10
  7. package/dist/command-helpers-CUoKYTlG.mjs.map +1 -0
  8. package/dist/commands/contract-emit.mjs +1 -1
  9. package/dist/commands/contract-infer.mjs +1 -1
  10. package/dist/commands/db-init.mjs +3 -3
  11. package/dist/commands/db-schema.mjs +3 -3
  12. package/dist/commands/db-sign.mjs +5 -5
  13. package/dist/commands/db-sign.mjs.map +1 -1
  14. package/dist/commands/db-update.mjs +4 -4
  15. package/dist/commands/db-verify.d.mts.map +1 -1
  16. package/dist/commands/db-verify.mjs +1 -1
  17. package/dist/commands/migrate.d.mts +1 -1
  18. package/dist/commands/migrate.d.mts.map +1 -1
  19. package/dist/commands/migrate.mjs +37 -38
  20. package/dist/commands/migrate.mjs.map +1 -1
  21. package/dist/commands/migration-check.d.mts +1 -1
  22. package/dist/commands/migration-check.mjs +1 -1
  23. package/dist/commands/migration-graph.d.mts +1 -1
  24. package/dist/commands/migration-graph.mjs +9 -9
  25. package/dist/commands/migration-graph.mjs.map +1 -1
  26. package/dist/commands/migration-list.d.mts +6 -6
  27. package/dist/commands/migration-list.d.mts.map +1 -1
  28. package/dist/commands/migration-list.mjs +1 -1
  29. package/dist/commands/migration-log.d.mts +1 -1
  30. package/dist/commands/migration-log.mjs +1 -1
  31. package/dist/commands/migration-new.mjs +4 -4
  32. package/dist/commands/migration-new.mjs.map +1 -1
  33. package/dist/commands/migration-plan.d.mts.map +1 -1
  34. package/dist/commands/migration-plan.mjs +1 -1
  35. package/dist/commands/migration-show.mjs +4 -4
  36. package/dist/commands/migration-show.mjs.map +1 -1
  37. package/dist/commands/migration-status.d.mts +1 -1
  38. package/dist/commands/migration-status.mjs +1 -1
  39. package/dist/commands/ref.d.mts +1 -1
  40. package/dist/commands/ref.mjs +3 -3
  41. package/dist/commands/ref.mjs.map +1 -1
  42. package/dist/commands/telemetry/index.mjs +1 -1
  43. package/dist/{contract-at-errors-CFXsstzm.mjs → contract-at-errors-D8CNDD7g.mjs} +2 -2
  44. package/dist/{contract-at-errors-CFXsstzm.mjs.map → contract-at-errors-D8CNDD7g.mjs.map} +1 -1
  45. package/dist/{contract-emit-C8HmtboH.mjs → contract-emit-sYrqzsdN.mjs} +9 -11
  46. package/dist/contract-emit-sYrqzsdN.mjs.map +1 -0
  47. package/dist/{contract-emit-B_qriF8B.mjs → contract-emit-wgqrOXUI.mjs} +6 -6
  48. package/dist/contract-emit-wgqrOXUI.mjs.map +1 -0
  49. package/dist/{contract-infer-BYT_ra_U.mjs → contract-infer-6SQnnVj7.mjs} +3 -3
  50. package/dist/{contract-infer-BYT_ra_U.mjs.map → contract-infer-6SQnnVj7.mjs.map} +1 -1
  51. package/dist/{contract-space-aggregate-loader-ClI1KN6d.mjs → contract-space-aggregate-loader-DpB4x97a.mjs} +63 -5
  52. package/dist/contract-space-aggregate-loader-DpB4x97a.mjs.map +1 -0
  53. package/dist/{db-verify-C24FKhb7.mjs → db-verify-CEsG5Gek.mjs} +57 -61
  54. package/dist/db-verify-CEsG5Gek.mjs.map +1 -0
  55. package/dist/exports/control-api.d.mts +3 -3
  56. package/dist/exports/control-api.d.mts.map +1 -1
  57. package/dist/exports/control-api.mjs +2 -2
  58. package/dist/exports/index.d.mts +5 -1
  59. package/dist/exports/index.d.mts.map +1 -1
  60. package/dist/exports/index.mjs +4 -3
  61. package/dist/exports/index.mjs.map +1 -1
  62. package/dist/format-CjongNb1.mjs +102 -0
  63. package/dist/format-CjongNb1.mjs.map +1 -0
  64. package/dist/{framework-components-YVQHhPH7.mjs → framework-components-BbnM1RYm.mjs} +2 -2
  65. package/dist/{framework-components-YVQHhPH7.mjs.map → framework-components-BbnM1RYm.mjs.map} +1 -1
  66. package/dist/{init-0HwB-Vh8.mjs → init-D8YFfaD6.mjs} +27 -19
  67. package/dist/init-D8YFfaD6.mjs.map +1 -0
  68. package/dist/{inspect-live-schema-DF6IwcDl.mjs → inspect-live-schema-CEt_ztBJ.mjs} +4 -4
  69. package/dist/inspect-live-schema-CEt_ztBJ.mjs.map +1 -0
  70. package/dist/{migration-check-VwM8xCZV.mjs → migration-check-ByeQHOhK.mjs} +10 -12
  71. package/dist/migration-check-ByeQHOhK.mjs.map +1 -0
  72. package/dist/migration-cli.mjs +1 -1
  73. package/dist/migration-cli.mjs.map +1 -1
  74. package/dist/{migration-command-scaffold-DA-Lhx6o.mjs → migration-command-scaffold-CJO3tjiR.mjs} +4 -4
  75. package/dist/migration-command-scaffold-CJO3tjiR.mjs.map +1 -0
  76. package/dist/{migration-graph-command-render-CEez7YUK.mjs → migration-graph-command-render-B83xrnNy.mjs} +24 -24
  77. package/dist/{migration-graph-command-render-CEez7YUK.mjs.map → migration-graph-command-render-B83xrnNy.mjs.map} +1 -1
  78. package/dist/{migration-list-DlJJ_38Z.mjs → migration-list-DTYM8xvm.mjs} +16 -16
  79. package/dist/migration-list-DTYM8xvm.mjs.map +1 -0
  80. package/dist/{migration-log-CG0qQAFm.mjs → migration-log-qZ8LpQIl.mjs} +5 -5
  81. package/dist/migration-log-qZ8LpQIl.mjs.map +1 -0
  82. package/dist/{migration-path-target-Ce6OZImp.mjs → migration-path-target-DmNwsDgs.mjs} +2 -2
  83. package/dist/{migration-path-target-Ce6OZImp.mjs.map → migration-path-target-DmNwsDgs.mjs.map} +1 -1
  84. package/dist/{migration-plan-W_E8FQOk.mjs → migration-plan-CfPrB80t.mjs} +30 -30
  85. package/dist/migration-plan-CfPrB80t.mjs.map +1 -0
  86. package/dist/{migration-status-CD-LC2Ip.mjs → migration-status-DRuS-rP5.mjs} +14 -15
  87. package/dist/migration-status-DRuS-rP5.mjs.map +1 -0
  88. package/dist/{telemetry-BIM4beEO.mjs → telemetry-DHBBq5wT.mjs} +2 -2
  89. package/dist/{telemetry-BIM4beEO.mjs.map → telemetry-DHBBq5wT.mjs.map} +1 -1
  90. package/dist/{types-C_tYiJYx.d.mts → types-DFoqNIcQ.d.mts} +27 -21
  91. package/dist/types-DFoqNIcQ.d.mts.map +1 -0
  92. package/dist/{verify-DcOYZ1tH.mjs → verify-DDCb0R2r.mjs} +57 -159
  93. package/dist/verify-DDCb0R2r.mjs.map +1 -0
  94. package/package.json +28 -30
  95. package/src/cli.ts +7 -0
  96. package/src/commands/contract-emit.ts +1 -1
  97. package/src/commands/db-sign.ts +1 -1
  98. package/src/commands/db-verify.ts +34 -20
  99. package/src/commands/format.ts +75 -0
  100. package/src/commands/init/detect-package-manager.ts +8 -4
  101. package/src/commands/init/hygiene-package-scripts.ts +16 -3
  102. package/src/commands/inspect-live-schema.ts +1 -1
  103. package/src/commands/lsp.ts +32 -0
  104. package/src/commands/migrate.ts +47 -44
  105. package/src/commands/migration-check.ts +6 -6
  106. package/src/commands/migration-graph.ts +5 -5
  107. package/src/commands/migration-list.ts +13 -13
  108. package/src/commands/migration-log.ts +1 -1
  109. package/src/commands/migration-new.ts +1 -1
  110. package/src/commands/migration-plan.ts +15 -4
  111. package/src/commands/migration-show.ts +1 -1
  112. package/src/commands/migration-status.ts +10 -10
  113. package/src/commands/ref.ts +1 -1
  114. package/src/control-api/client.ts +8 -23
  115. package/src/control-api/operations/contract-emit.ts +9 -27
  116. package/src/control-api/operations/db-init.ts +2 -2
  117. package/src/control-api/operations/db-run.ts +18 -17
  118. package/src/control-api/operations/db-verify.ts +52 -44
  119. package/src/control-api/operations/format.ts +98 -0
  120. package/src/control-api/operations/migrate.ts +72 -72
  121. package/src/control-api/operations/run-migration.ts +6 -5
  122. package/src/control-api/types.ts +8 -8
  123. package/src/exports/index.ts +1 -0
  124. package/src/migration-cli.ts +1 -1
  125. package/src/utils/combine-verify-results.ts +94 -0
  126. package/src/utils/extension-pack-inputs.ts +1 -1
  127. package/src/utils/formatters/errors.ts +13 -5
  128. package/src/utils/formatters/verify.ts +88 -227
  129. package/src/utils/migration-command-scaffold.ts +1 -1
  130. package/src/utils/plan-resolution.ts +18 -18
  131. package/dist/client-CJzuo5wX.mjs.map +0 -1
  132. package/dist/command-helpers-DGMvGBeX.mjs.map +0 -1
  133. package/dist/config-loader-p9JMrekQ.mjs +0 -88
  134. package/dist/config-loader-p9JMrekQ.mjs.map +0 -1
  135. package/dist/config-loader.d.mts +0 -16
  136. package/dist/config-loader.d.mts.map +0 -1
  137. package/dist/config-loader.mjs +0 -2
  138. package/dist/contract-emit-B_qriF8B.mjs.map +0 -1
  139. package/dist/contract-emit-C8HmtboH.mjs.map +0 -1
  140. package/dist/contract-space-aggregate-loader-ClI1KN6d.mjs.map +0 -1
  141. package/dist/db-verify-C24FKhb7.mjs.map +0 -1
  142. package/dist/extension-pack-inputs-1ySHqxKG.mjs +0 -62
  143. package/dist/extension-pack-inputs-1ySHqxKG.mjs.map +0 -1
  144. package/dist/init-0HwB-Vh8.mjs.map +0 -1
  145. package/dist/inspect-live-schema-DF6IwcDl.mjs.map +0 -1
  146. package/dist/migration-check-VwM8xCZV.mjs.map +0 -1
  147. package/dist/migration-command-scaffold-DA-Lhx6o.mjs.map +0 -1
  148. package/dist/migration-list-DlJJ_38Z.mjs.map +0 -1
  149. package/dist/migration-log-CG0qQAFm.mjs.map +0 -1
  150. package/dist/migration-plan-W_E8FQOk.mjs.map +0 -1
  151. package/dist/migration-status-CD-LC2Ip.mjs.map +0 -1
  152. package/dist/types-C_tYiJYx.d.mts.map +0 -1
  153. package/dist/verify-DcOYZ1tH.mjs.map +0 -1
  154. package/src/config-loader.ts +0 -92
  155. package/src/config-path-validation.ts +0 -74
  156. 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.mode === 'full' && result.schema && isVerbose(flags, 1)) {
75
- lines.push(
76
- `${formatDimText(` schema: pass=${result.schema.counts.pass} warn=${result.schema.counts.warn} fail=${result.schema.counts.fail}`)}`,
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
- * Matches the style of renderSchemaVerificationTree for consistency.
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
- // Render verification tree first
533
- const treeLines = renderSchemaVerificationTree(result.schema.root, flags, {
534
- isLast: true,
535
- prefix: '',
536
- useColor,
537
- formatDimText,
538
- isRoot: true,
539
- });
540
- lines.push(...treeLines);
541
-
542
- // Add counts and timings in verbose mode
543
- if (isVerbose(flags, 1)) {
544
- lines.push(`${formatDimText(` Total time: ${result.timings.total}ms`)}`);
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
- `${formatDimText(` pass=${result.schema.counts.pass} warn=${result.schema.counts.warn} fail=${result.schema.counts.fail}`)}`,
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: summary with status glyph
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(result: VerifyDatabaseSchemaResult): string {
568
- return JSON.stringify(result, null, 2);
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 { ContractSpaceMember } from '@prisma-next/migration-tools/aggregate';
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 member: ContractSpaceMember;
48
+ readonly space: AggregateContractSpace;
49
49
  }
50
50
 
51
- function graphIsEmpty(member: ContractSpaceMember): boolean {
52
- return member.packages.length === 0;
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
- member: ContractSpaceMember,
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 member.contractAt(hash, refName !== undefined ? { refName } : undefined);
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.member, {
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.member)) {
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.member.graph();
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, member } = input;
193
- const graph = member.graph();
194
- const refs = member.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(member);
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 member: ContractSpaceMember;
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 { member } = input;
240
- const graph = member.graph();
241
- const refs = member.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, member, {
248
+ const resolution = await resolveContractRef(refResult.value, space, {
249
249
  explicitLabel: optionsTo,
250
250
  artifactRole: 'to',
251
251
  });