@prisma-next/cli 0.14.0-dev.49 → 0.14.0-dev.50
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/dist/cli.mjs +13 -13
- package/dist/{client-CWTNqJdj.mjs → client-DL6KE_c0.mjs} +22 -37
- package/dist/client-DL6KE_c0.mjs.map +1 -0
- package/dist/{command-helpers-6cNJZ863.mjs → command-helpers-DMG0RsR1.mjs} +3 -3
- package/dist/command-helpers-DMG0RsR1.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 +4 -4
- package/dist/commands/db-update.mjs +4 -4
- package/dist/commands/db-verify.mjs +1 -1
- package/dist/commands/migrate.d.mts +1 -1
- package/dist/commands/migrate.mjs +6 -6
- 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 +3 -3
- package/dist/commands/migration-list.d.mts +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 +3 -3
- package/dist/commands/migration-plan.d.mts.map +1 -1
- package/dist/commands/migration-plan.mjs +1 -1
- package/dist/commands/migration-show.mjs +3 -3
- 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 +2 -2
- package/dist/commands/telemetry/index.mjs +1 -1
- package/dist/{contract-at-errors-DdxWRjMc.mjs → contract-at-errors-bF4CGDl0.mjs} +2 -2
- package/dist/{contract-at-errors-DdxWRjMc.mjs.map → contract-at-errors-bF4CGDl0.mjs.map} +1 -1
- package/dist/{contract-emit-CMwCj7_m.mjs → contract-emit-BM7oxYIG.mjs} +3 -3
- package/dist/{contract-emit-CMwCj7_m.mjs.map → contract-emit-BM7oxYIG.mjs.map} +1 -1
- package/dist/{contract-emit-klH7zzaV.mjs → contract-emit-DnCG2JaQ.mjs} +3 -3
- package/dist/{contract-emit-klH7zzaV.mjs.map → contract-emit-DnCG2JaQ.mjs.map} +1 -1
- package/dist/{contract-infer-B__56PGb.mjs → contract-infer-Dq9XNQCx.mjs} +3 -3
- package/dist/{contract-infer-B__56PGb.mjs.map → contract-infer-Dq9XNQCx.mjs.map} +1 -1
- package/dist/{contract-space-aggregate-loader-Bu2fjSWV.mjs → contract-space-aggregate-loader-DAa8u6bi.mjs} +2 -2
- package/dist/{contract-space-aggregate-loader-Bu2fjSWV.mjs.map → contract-space-aggregate-loader-DAa8u6bi.mjs.map} +1 -1
- package/dist/{db-verify-DlqHvkkA.mjs → db-verify-BpfJvC8O.mjs} +14 -38
- package/dist/db-verify-BpfJvC8O.mjs.map +1 -0
- package/dist/exports/control-api.d.mts +1 -1
- package/dist/exports/control-api.mjs +2 -2
- package/dist/exports/index.mjs +2 -2
- package/dist/{format-C3XdaDuA.mjs → format-DrUSkdqR.mjs} +2 -2
- package/dist/{format-C3XdaDuA.mjs.map → format-DrUSkdqR.mjs.map} +1 -1
- package/dist/{framework-components-5RljWT7Q.mjs → framework-components-BdmVlRSS.mjs} +2 -2
- package/dist/{framework-components-5RljWT7Q.mjs.map → framework-components-BdmVlRSS.mjs.map} +1 -1
- package/dist/{init-BF3s9NcB.mjs → init-C0YyVoGj.mjs} +3 -3
- package/dist/{init-BF3s9NcB.mjs.map → init-C0YyVoGj.mjs.map} +1 -1
- package/dist/{inspect-live-schema-Dd1Ft0dw.mjs → inspect-live-schema-Bs_0M6z8.mjs} +3 -3
- package/dist/{inspect-live-schema-Dd1Ft0dw.mjs.map → inspect-live-schema-Bs_0M6z8.mjs.map} +1 -1
- package/dist/{migration-check-C9F4RopI.mjs → migration-check-DYxrNfK-.mjs} +4 -5
- package/dist/{migration-check-C9F4RopI.mjs.map → migration-check-DYxrNfK-.mjs.map} +1 -1
- package/dist/{migration-command-scaffold-DW2BpjZW.mjs → migration-command-scaffold-DB5DQLvF.mjs} +3 -3
- package/dist/{migration-command-scaffold-DW2BpjZW.mjs.map → migration-command-scaffold-DB5DQLvF.mjs.map} +1 -1
- package/dist/{migration-list-BOzQzJK4.mjs → migration-list-BX5w9HAn.mjs} +3 -3
- package/dist/{migration-list-BOzQzJK4.mjs.map → migration-list-BX5w9HAn.mjs.map} +1 -1
- package/dist/{migration-log-q50rqMiU.mjs → migration-log-CB1KzVOG.mjs} +3 -3
- package/dist/{migration-log-q50rqMiU.mjs.map → migration-log-CB1KzVOG.mjs.map} +1 -1
- package/dist/{migration-path-target-zK6RG6JY.mjs → migration-path-target-CEFRK2Q7.mjs} +2 -2
- package/dist/{migration-path-target-zK6RG6JY.mjs.map → migration-path-target-CEFRK2Q7.mjs.map} +1 -1
- package/dist/{migration-plan-BwJLPkwV.mjs → migration-plan-DMKl_HnV.mjs} +11 -10
- package/dist/migration-plan-DMKl_HnV.mjs.map +1 -0
- package/dist/{migration-status-RrGUf39y.mjs → migration-status-BeNAmEju.mjs} +5 -6
- package/dist/{migration-status-RrGUf39y.mjs.map → migration-status-BeNAmEju.mjs.map} +1 -1
- package/dist/{telemetry-BckOSUWG.mjs → telemetry-_19qtfXp.mjs} +2 -2
- package/dist/{telemetry-BckOSUWG.mjs.map → telemetry-_19qtfXp.mjs.map} +1 -1
- package/dist/{types-CKgJmH02.d.mts → types-DFoqNIcQ.d.mts} +2 -2
- package/dist/{types-CKgJmH02.d.mts.map → types-DFoqNIcQ.d.mts.map} +1 -1
- package/dist/{verify-DC14_nPu.mjs → verify-Djo-Jep_.mjs} +36 -161
- package/dist/verify-Djo-Jep_.mjs.map +1 -0
- package/package.json +21 -21
- package/src/commands/db-verify.ts +1 -1
- package/src/commands/migrate.ts +4 -4
- package/src/commands/migration-plan.ts +11 -0
- package/src/control-api/operations/db-run.ts +13 -12
- package/src/control-api/operations/db-verify.ts +16 -13
- package/src/control-api/operations/migrate.ts +8 -8
- package/src/control-api/operations/run-migration.ts +5 -4
- package/src/control-api/types.ts +1 -1
- package/src/utils/combine-verify-results.ts +8 -27
- package/src/utils/formatters/errors.ts +12 -4
- package/src/utils/formatters/verify.ts +50 -230
- package/dist/client-CWTNqJdj.mjs.map +0 -1
- package/dist/command-helpers-6cNJZ863.mjs.map +0 -1
- package/dist/db-verify-DlqHvkkA.mjs.map +0 -1
- package/dist/migration-plan-BwJLPkwV.mjs.map +0 -1
- package/dist/verify-DC14_nPu.mjs.map +0 -1
|
@@ -45,10 +45,11 @@ export interface RunMigrationInputs<TFamilyId extends string, TTargetId extends
|
|
|
45
45
|
readonly aggregate: ContractSpaceAggregate;
|
|
46
46
|
/**
|
|
47
47
|
* Per-space plans, keyed by `spaceId`. Produced by either the full
|
|
48
|
-
* {@link planMigration} pipeline (`db init` / `db update` —
|
|
49
|
-
* for the app,
|
|
50
|
-
* {@link
|
|
51
|
-
* for every contract space). Either way, the runner
|
|
48
|
+
* {@link planMigration} pipeline (`db init` / `db update` —
|
|
49
|
+
* `planFromDiff` for the app, `resolveRecordedPath` for extensions) or
|
|
50
|
+
* by direct {@link resolveRecordedPath} calls (`migrate` — resolves
|
|
51
|
+
* the recorded path for every contract space). Either way, the runner
|
|
52
|
+
* consumes the same shape.
|
|
52
53
|
*/
|
|
53
54
|
readonly perSpacePlans: ReadonlyMap<string, PerSpacePlan>;
|
|
54
55
|
/**
|
package/src/control-api/types.ts
CHANGED
|
@@ -545,7 +545,7 @@ export type EmitResult = Result<EmitSuccess, EmitFailure>;
|
|
|
545
545
|
*
|
|
546
546
|
* The control-api operation is responsible for: loading the
|
|
547
547
|
* contract-space aggregate, reading per-space marker rows from the
|
|
548
|
-
* live database, plotting per-space paths via `
|
|
548
|
+
* live database, plotting per-space paths via `resolveRecordedPath`
|
|
549
549
|
* (replay-only — no synth, no introspection), and dispatching
|
|
550
550
|
* through the shared `runMigration` primitive. The CLI command
|
|
551
551
|
* just resolves the descriptor surface (config, refs, contract
|
|
@@ -12,11 +12,11 @@ export interface CombinedVerifyResult {
|
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* Collapse the aggregate verifier's per-space contract-satisfaction results
|
|
15
|
-
* into a single {@link VerifyDatabaseSchemaResult} for the
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
15
|
+
* into a single {@link VerifyDatabaseSchemaResult} for the CLI display
|
|
16
|
+
* surface, and carry the deduplicated unclaimed-elements list alongside it.
|
|
17
|
+
* Concatenates the issue lists across spaces; uses the app space's result as
|
|
18
|
+
* the structural envelope (storage hash, target). Extras are already
|
|
19
|
+
* stripped from each per-space result, so nothing here duplicates an
|
|
20
20
|
* unclaimed element per space.
|
|
21
21
|
*
|
|
22
22
|
* **Unclaimed disposition.** In strict mode a non-empty `unclaimed` list fails
|
|
@@ -50,21 +50,14 @@ export function combineVerifyResults(
|
|
|
50
50
|
let okAll = true;
|
|
51
51
|
let firstFailure: VerifyDatabaseSchemaResult | undefined;
|
|
52
52
|
let issues: VerifyDatabaseSchemaResult['schema']['issues'] = [];
|
|
53
|
-
let
|
|
54
|
-
const counts = { pass: 0, warn: 0, fail: 0, totalNodes: 0 };
|
|
55
|
-
const childRoots: Array<VerifyDatabaseSchemaResult['schema']['root']> = [];
|
|
53
|
+
let warningIssues: VerifyDatabaseSchemaResult['schema']['issues'] = [];
|
|
56
54
|
for (const [, result] of perSpace) {
|
|
57
55
|
if (!result.ok) {
|
|
58
56
|
okAll = false;
|
|
59
57
|
if (firstFailure === undefined) firstFailure = result;
|
|
60
58
|
}
|
|
61
59
|
issues = [...issues, ...result.schema.issues];
|
|
62
|
-
|
|
63
|
-
counts.pass += result.schema.counts.pass;
|
|
64
|
-
counts.warn += result.schema.counts.warn;
|
|
65
|
-
counts.fail += result.schema.counts.fail;
|
|
66
|
-
counts.totalNodes += result.schema.counts.totalNodes;
|
|
67
|
-
childRoots.push(result.schema.root);
|
|
60
|
+
warningIssues = [...warningIssues, ...(result.schema.warnings?.issues ?? [])];
|
|
68
61
|
}
|
|
69
62
|
|
|
70
63
|
const unclaimedFails = strict && unclaimed.length > 0;
|
|
@@ -91,19 +84,7 @@ export function combineVerifyResults(
|
|
|
91
84
|
target: appResult.target,
|
|
92
85
|
schema: {
|
|
93
86
|
issues,
|
|
94
|
-
|
|
95
|
-
root: {
|
|
96
|
-
status: ok ? 'pass' : 'fail',
|
|
97
|
-
kind: 'aggregate',
|
|
98
|
-
name: 'aggregate',
|
|
99
|
-
contractPath: '',
|
|
100
|
-
code: 'AGGREGATE',
|
|
101
|
-
message: ok ? 'Aggregate schema matches' : 'Aggregate schema mismatch',
|
|
102
|
-
expected: undefined,
|
|
103
|
-
actual: undefined,
|
|
104
|
-
children: childRoots,
|
|
105
|
-
},
|
|
106
|
-
counts,
|
|
87
|
+
warnings: { issues: warningIssues },
|
|
107
88
|
},
|
|
108
89
|
meta: { strict },
|
|
109
90
|
timings: { total: 0 },
|
|
@@ -48,9 +48,17 @@ export function formatErrorOutput(error: CliErrorEnvelope, flags: GlobalFlags):
|
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
|
-
// Show issues list if present (always show a short list; show full list when verbose)
|
|
51
|
+
// Show issues list if present (always show a short list; show full list when verbose).
|
|
52
|
+
// `issues` is a shared error-envelope field: PSL interpretation diagnostics stamp
|
|
53
|
+
// `kind` with their diagnostic code (e.g. `PSL_ORPHANED_BACKRELATION_LIST`); schema-diff
|
|
54
|
+
// issues carry no `kind` and stamp `reason` instead (e.g. `not-found`). Prefer whichever
|
|
55
|
+
// the producer supplied.
|
|
52
56
|
if (error.meta?.['issues']) {
|
|
53
|
-
const issues = error.meta['issues'] as readonly {
|
|
57
|
+
const issues = error.meta['issues'] as readonly {
|
|
58
|
+
kind?: string;
|
|
59
|
+
reason?: string;
|
|
60
|
+
message?: string;
|
|
61
|
+
}[];
|
|
54
62
|
if (issues.length > 0) {
|
|
55
63
|
const maxToShow = isVerbose(flags, 1) ? issues.length : Math.min(3, issues.length);
|
|
56
64
|
const header = isVerbose(flags, 1)
|
|
@@ -58,9 +66,9 @@ export function formatErrorOutput(error: CliErrorEnvelope, flags: GlobalFlags):
|
|
|
58
66
|
: ` Issues (showing ${maxToShow} of ${issues.length}):`;
|
|
59
67
|
lines.push(`${formatDimText(header)}`);
|
|
60
68
|
for (const issue of issues.slice(0, maxToShow)) {
|
|
61
|
-
const
|
|
69
|
+
const label = issue.kind ?? issue.reason ?? 'issue';
|
|
62
70
|
const message = issue.message ?? '';
|
|
63
|
-
lines.push(`${formatDimText(` - [${
|
|
71
|
+
lines.push(`${formatDimText(` - [${label}] ${message}`)}`);
|
|
64
72
|
}
|
|
65
73
|
if (!isVerbose(flags, 1) && issues.length > maxToShow) {
|
|
66
74
|
lines.push(`${formatDimText(' Re-run with -v/--verbose to see all issues')}`);
|
|
@@ -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 message (the differ's `path`), prefixed
|
|
25
|
+
* with a human label for why it's flagged. Turning `reason` into a label is
|
|
26
|
+
* this formatter's job, not the differ's — the differ's issue is data
|
|
27
|
+
* (`path` + `reason` + nodes), not prose.
|
|
28
|
+
*/
|
|
29
|
+
function formatIssueMessage(issue: SchemaDiffIssue): string {
|
|
30
|
+
return `${REASON_LABEL[issue.reason]}: ${issue.message}`;
|
|
31
|
+
}
|
|
32
|
+
|
|
15
33
|
// ============================================================================
|
|
16
34
|
// Verify Output Formatters
|
|
17
35
|
// ============================================================================
|
|
@@ -27,8 +45,13 @@ 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
|
};
|
|
33
56
|
/**
|
|
34
57
|
* Live element names no contract space declares. In full success this is
|
|
@@ -77,11 +100,14 @@ export function formatVerifyOutput(
|
|
|
77
100
|
if (result.contract.profileHash) {
|
|
78
101
|
lines.push(`${formatDimText(` profileHash: ${result.contract.profileHash}`)}`);
|
|
79
102
|
}
|
|
80
|
-
if (result.
|
|
81
|
-
lines.push(
|
|
82
|
-
|
|
83
|
-
)
|
|
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
|
+
}
|
|
84
109
|
}
|
|
110
|
+
|
|
85
111
|
if (result.unclaimed && result.unclaimed.length > 0) {
|
|
86
112
|
lines.push('');
|
|
87
113
|
lines.push(formatYellow('Unclaimed elements (declared by no contract):'));
|
|
@@ -139,7 +165,7 @@ export function formatIntrospectJson(result: IntrospectSchemaResult<unknown>): s
|
|
|
139
165
|
|
|
140
166
|
/**
|
|
141
167
|
* Renders a schema tree structure from CoreSchemaView.
|
|
142
|
-
*
|
|
168
|
+
* Status-glyph tree styling shared with the retired verification-tree renderer.
|
|
143
169
|
*/
|
|
144
170
|
function renderSchemaTree(
|
|
145
171
|
node: SchemaTreeNode,
|
|
@@ -325,207 +351,6 @@ export function formatIntrospectOutput(
|
|
|
325
351
|
return lines.join('\n');
|
|
326
352
|
}
|
|
327
353
|
|
|
328
|
-
/**
|
|
329
|
-
* Renders a schema verification tree structure from SchemaVerificationNode.
|
|
330
|
-
* Similar to renderSchemaTree but for verification nodes with status-based colors and glyphs.
|
|
331
|
-
*/
|
|
332
|
-
function renderSchemaVerificationTree(
|
|
333
|
-
node: SchemaVerificationNode,
|
|
334
|
-
flags: GlobalFlags,
|
|
335
|
-
options: {
|
|
336
|
-
readonly isLast: boolean;
|
|
337
|
-
readonly prefix: string;
|
|
338
|
-
readonly useColor: boolean;
|
|
339
|
-
readonly formatDimText: (text: string) => string;
|
|
340
|
-
readonly isRoot?: boolean;
|
|
341
|
-
},
|
|
342
|
-
): string[] {
|
|
343
|
-
const { isLast, prefix, useColor, formatDimText, isRoot = false } = options;
|
|
344
|
-
const lines: string[] = [];
|
|
345
|
-
|
|
346
|
-
// Format status glyph and color based on status
|
|
347
|
-
let statusGlyph = '';
|
|
348
|
-
let statusColor: (text: string) => string = (text) => text;
|
|
349
|
-
if (useColor) {
|
|
350
|
-
switch (node.status) {
|
|
351
|
-
case 'pass':
|
|
352
|
-
statusGlyph = '✔';
|
|
353
|
-
statusColor = green;
|
|
354
|
-
break;
|
|
355
|
-
case 'warn':
|
|
356
|
-
statusGlyph = '⚠';
|
|
357
|
-
statusColor = (text) => (useColor ? yellow(text) : text);
|
|
358
|
-
break;
|
|
359
|
-
case 'fail':
|
|
360
|
-
statusGlyph = '✖';
|
|
361
|
-
statusColor = red;
|
|
362
|
-
break;
|
|
363
|
-
}
|
|
364
|
-
} else {
|
|
365
|
-
switch (node.status) {
|
|
366
|
-
case 'pass':
|
|
367
|
-
statusGlyph = '✔';
|
|
368
|
-
break;
|
|
369
|
-
case 'warn':
|
|
370
|
-
statusGlyph = '⚠';
|
|
371
|
-
break;
|
|
372
|
-
case 'fail':
|
|
373
|
-
statusGlyph = '✖';
|
|
374
|
-
break;
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
// Format node label with color based on kind
|
|
379
|
-
// For column nodes, we need to parse the name to color code different parts
|
|
380
|
-
let labelColor: (text: string) => string = (text) => text;
|
|
381
|
-
let formattedLabel: string = node.name;
|
|
382
|
-
|
|
383
|
-
if (useColor) {
|
|
384
|
-
switch (node.kind) {
|
|
385
|
-
case 'contract':
|
|
386
|
-
case 'schema':
|
|
387
|
-
labelColor = bold;
|
|
388
|
-
formattedLabel = labelColor(node.name);
|
|
389
|
-
break;
|
|
390
|
-
case 'table': {
|
|
391
|
-
// Parse "table tableName" format - color "table" dim, tableName cyan
|
|
392
|
-
const tableMatch = node.name.match(/^table\s+(.+)$/);
|
|
393
|
-
if (tableMatch?.[1]) {
|
|
394
|
-
const tableName = tableMatch[1];
|
|
395
|
-
formattedLabel = `${dim('table')} ${cyan(tableName)}`;
|
|
396
|
-
} else {
|
|
397
|
-
formattedLabel = dim(node.name);
|
|
398
|
-
}
|
|
399
|
-
break;
|
|
400
|
-
}
|
|
401
|
-
case 'columns':
|
|
402
|
-
labelColor = dim;
|
|
403
|
-
formattedLabel = labelColor(node.name);
|
|
404
|
-
break;
|
|
405
|
-
case 'column': {
|
|
406
|
-
// Parse column name format: "columnName: contractType -> nativeType (nullability)"
|
|
407
|
-
// Color code: column name (cyan), contract type (default), native type (dim), nullability (dim)
|
|
408
|
-
const columnMatch = node.name.match(/^([^:]+):\s*(.+)$/);
|
|
409
|
-
if (columnMatch?.[1] && columnMatch[2]) {
|
|
410
|
-
const columnName = columnMatch[1];
|
|
411
|
-
const rest = columnMatch[2];
|
|
412
|
-
// Parse rest: "contractType -> nativeType (nullability)"
|
|
413
|
-
// Match contract type (can contain /, @, etc.), arrow, native type, then nullability in parentheses
|
|
414
|
-
const typeMatch = rest.match(/^([^\s→]+)\s*→\s*([^\s(]+)\s*(\([^)]+\))$/);
|
|
415
|
-
if (typeMatch?.[1] && typeMatch[2] && typeMatch[3]) {
|
|
416
|
-
const contractType = typeMatch[1];
|
|
417
|
-
const nativeType = typeMatch[2];
|
|
418
|
-
const nullability = typeMatch[3];
|
|
419
|
-
formattedLabel = `${cyan(columnName)}: ${contractType} → ${dim(nativeType)} ${dim(nullability)}`;
|
|
420
|
-
} else {
|
|
421
|
-
// Fallback if format doesn't match (e.g., no native type or no nullability)
|
|
422
|
-
formattedLabel = `${cyan(columnName)}: ${rest}`;
|
|
423
|
-
}
|
|
424
|
-
} else {
|
|
425
|
-
formattedLabel = node.name;
|
|
426
|
-
}
|
|
427
|
-
break;
|
|
428
|
-
}
|
|
429
|
-
case 'type':
|
|
430
|
-
case 'nullability':
|
|
431
|
-
labelColor = (text) => text; // Default color
|
|
432
|
-
formattedLabel = labelColor(node.name);
|
|
433
|
-
break;
|
|
434
|
-
case 'primaryKey': {
|
|
435
|
-
// Parse "primary key: columnName" format - color "primary key" dim, columnName cyan
|
|
436
|
-
const pkMatch = node.name.match(/^primary key:\s*(.+)$/);
|
|
437
|
-
if (pkMatch?.[1]) {
|
|
438
|
-
const columnNames = pkMatch[1];
|
|
439
|
-
formattedLabel = `${dim('primary key')}: ${cyan(columnNames)}`;
|
|
440
|
-
} else {
|
|
441
|
-
formattedLabel = dim(node.name);
|
|
442
|
-
}
|
|
443
|
-
break;
|
|
444
|
-
}
|
|
445
|
-
case 'foreignKey':
|
|
446
|
-
case 'unique':
|
|
447
|
-
case 'index':
|
|
448
|
-
labelColor = dim;
|
|
449
|
-
formattedLabel = labelColor(node.name);
|
|
450
|
-
break;
|
|
451
|
-
case 'dependency': {
|
|
452
|
-
// Parse specific extension message formats
|
|
453
|
-
// "database is postgres" -> dim "database is", cyan "postgres"
|
|
454
|
-
const dbMatch = node.name.match(/^database is\s+(.+)$/);
|
|
455
|
-
if (dbMatch?.[1]) {
|
|
456
|
-
const dbName = dbMatch[1];
|
|
457
|
-
formattedLabel = `${dim('database is')} ${cyan(dbName)}`;
|
|
458
|
-
} else {
|
|
459
|
-
// "vector extension is enabled" -> dim everything except extension name
|
|
460
|
-
// Match pattern: "extensionName extension is enabled"
|
|
461
|
-
const extMatch = node.name.match(/^([^\s]+)\s+(extension is enabled)$/);
|
|
462
|
-
if (extMatch?.[1] && extMatch[2]) {
|
|
463
|
-
const extName = extMatch[1];
|
|
464
|
-
const rest = extMatch[2];
|
|
465
|
-
formattedLabel = `${cyan(extName)} ${dim(rest)}`;
|
|
466
|
-
} else {
|
|
467
|
-
// Fallback: color entire name with magenta
|
|
468
|
-
labelColor = magenta;
|
|
469
|
-
formattedLabel = labelColor(node.name);
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
|
-
break;
|
|
473
|
-
}
|
|
474
|
-
default:
|
|
475
|
-
formattedLabel = node.name;
|
|
476
|
-
break;
|
|
477
|
-
}
|
|
478
|
-
} else {
|
|
479
|
-
formattedLabel = node.name;
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
const statusGlyphColored = statusColor(statusGlyph);
|
|
483
|
-
|
|
484
|
-
// Build the label with optional message for failure/warn nodes
|
|
485
|
-
let nodeLabel = formattedLabel;
|
|
486
|
-
if (
|
|
487
|
-
(node.status === 'fail' || node.status === 'warn') &&
|
|
488
|
-
node.message &&
|
|
489
|
-
node.message.length > 0
|
|
490
|
-
) {
|
|
491
|
-
// Always show message for failure/warn nodes - it provides crucial context
|
|
492
|
-
// For parent nodes, the message summarizes child failures
|
|
493
|
-
// For leaf nodes, the message explains the specific issue
|
|
494
|
-
const messageText = formatDimText(`(${node.message})`);
|
|
495
|
-
nodeLabel = `${formattedLabel} ${messageText}`;
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
// Root node renders without tree characters or | prefix
|
|
499
|
-
// Root node renders without tree characters or prefix
|
|
500
|
-
if (isRoot) {
|
|
501
|
-
lines.push(`${statusGlyphColored} ${nodeLabel}`);
|
|
502
|
-
} else {
|
|
503
|
-
const treeChar = isLast ? '└' : '├';
|
|
504
|
-
const treePrefix = `${formatDimText(treeChar)}─ `;
|
|
505
|
-
lines.push(`${prefix}${treePrefix}${statusGlyphColored} ${nodeLabel}`);
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
// Render children if present
|
|
509
|
-
if (node.children && node.children.length > 0) {
|
|
510
|
-
const childPrefix = isRoot ? '' : `${prefix}${isLast ? ' ' : `${formatDimText('│')} `}`;
|
|
511
|
-
for (let i = 0; i < node.children.length; i++) {
|
|
512
|
-
const child = node.children[i];
|
|
513
|
-
if (!child) continue;
|
|
514
|
-
const isLastChild = i === node.children.length - 1;
|
|
515
|
-
const childLines = renderSchemaVerificationTree(child, flags, {
|
|
516
|
-
isLast: isLastChild,
|
|
517
|
-
prefix: childPrefix,
|
|
518
|
-
useColor,
|
|
519
|
-
formatDimText,
|
|
520
|
-
isRoot: false,
|
|
521
|
-
});
|
|
522
|
-
lines.push(...childLines);
|
|
523
|
-
}
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
return lines;
|
|
527
|
-
}
|
|
528
|
-
|
|
529
354
|
/**
|
|
530
355
|
* Formats human-readable output for database schema verification.
|
|
531
356
|
*/
|
|
@@ -546,27 +371,26 @@ export function formatSchemaVerifyOutput(
|
|
|
546
371
|
const formatYellow = createColorFormatter(useColor, yellow);
|
|
547
372
|
const formatDimText = (text: string) => formatDim(useColor, text);
|
|
548
373
|
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
lines.push('');
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
lines.push(` ${formatRed('✖')} ${issue.message}`);
|
|
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}`);
|
|
564
388
|
}
|
|
565
389
|
}
|
|
566
390
|
|
|
567
391
|
if (unclaimed.length > 0) {
|
|
568
392
|
const strict = result.meta?.strict ?? false;
|
|
569
|
-
lines.push('');
|
|
393
|
+
if (lines.length > 0) lines.push('');
|
|
570
394
|
lines.push(
|
|
571
395
|
(strict ? formatRed : formatYellow)('Unclaimed elements (declared by no contract):'),
|
|
572
396
|
);
|
|
@@ -575,18 +399,14 @@ export function formatSchemaVerifyOutput(
|
|
|
575
399
|
}
|
|
576
400
|
}
|
|
577
401
|
|
|
578
|
-
// Add counts and timings in verbose mode
|
|
579
402
|
if (isVerbose(flags, 1)) {
|
|
580
403
|
lines.push(`${formatDimText(` Total time: ${result.timings.total}ms`)}`);
|
|
581
|
-
lines.push(
|
|
582
|
-
`${formatDimText(` pass=${result.schema.counts.pass} warn=${result.schema.counts.warn} fail=${result.schema.counts.fail}`)}`,
|
|
583
|
-
);
|
|
584
404
|
}
|
|
585
405
|
|
|
586
406
|
// Blank line before summary
|
|
587
|
-
lines.push('');
|
|
407
|
+
if (lines.length > 0) lines.push('');
|
|
588
408
|
|
|
589
|
-
// Summary line at the end:
|
|
409
|
+
// Summary line at the end: verdict with status glyph
|
|
590
410
|
if (result.ok) {
|
|
591
411
|
lines.push(`${formatGreen('✔')} ${result.summary}`);
|
|
592
412
|
} else {
|