@prisma-next/cli 0.15.0 → 0.16.0-dev.10
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 +3 -4
- package/dist/cli.d.mts +1 -1
- package/dist/cli.mjs +13 -13
- package/dist/{client-DigVI8zX.mjs → client-2kwLMBSf.mjs} +67 -36
- package/dist/client-2kwLMBSf.mjs.map +1 -0
- package/dist/{command-helpers-CUoKYTlG.mjs → command-helpers-CVn3U9Uz.mjs} +32 -8
- package/dist/command-helpers-CVn3U9Uz.mjs.map +1 -0
- package/dist/commands/contract-emit.d.mts +0 -1
- package/dist/commands/contract-emit.d.mts.map +1 -1
- package/dist/commands/contract-emit.mjs +1 -1
- package/dist/commands/contract-infer.d.mts +0 -1
- package/dist/commands/contract-infer.d.mts.map +1 -1
- package/dist/commands/contract-infer.mjs +1 -1
- package/dist/commands/db-init.d.mts +0 -1
- package/dist/commands/db-init.d.mts.map +1 -1
- package/dist/commands/db-init.mjs +8 -7
- package/dist/commands/db-init.mjs.map +1 -1
- package/dist/commands/db-schema.d.mts +0 -1
- package/dist/commands/db-schema.d.mts.map +1 -1
- package/dist/commands/db-schema.mjs +3 -3
- package/dist/commands/db-sign.d.mts +0 -1
- package/dist/commands/db-sign.d.mts.map +1 -1
- package/dist/commands/db-sign.mjs +8 -10
- package/dist/commands/db-sign.mjs.map +1 -1
- package/dist/commands/db-update.d.mts +0 -1
- package/dist/commands/db-update.d.mts.map +1 -1
- package/dist/commands/db-update.mjs +12 -14
- package/dist/commands/db-update.mjs.map +1 -1
- package/dist/commands/db-verify.d.mts +0 -1
- 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 -2
- package/dist/commands/migrate.d.mts.map +1 -1
- package/dist/commands/migrate.mjs +7 -7
- package/dist/commands/migrate.mjs.map +1 -1
- package/dist/commands/migration-check.d.mts +4 -3
- package/dist/commands/migration-check.d.mts.map +1 -1
- package/dist/commands/migration-check.mjs +1 -1
- package/dist/commands/migration-graph.d.mts +2 -3
- package/dist/commands/migration-graph.d.mts.map +1 -1
- package/dist/commands/migration-graph.mjs +3 -3
- package/dist/commands/migration-list.d.mts +2 -3
- 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 +2 -3
- package/dist/commands/migration-log.d.mts.map +1 -1
- package/dist/commands/migration-log.mjs +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 +14 -38
- package/dist/commands/migration-new.mjs.map +1 -1
- package/dist/commands/migration-plan.d.mts +0 -1
- package/dist/commands/migration-plan.d.mts.map +1 -1
- package/dist/commands/migration-plan.mjs +1 -1
- package/dist/commands/migration-show.d.mts +0 -1
- package/dist/commands/migration-show.d.mts.map +1 -1
- package/dist/commands/migration-show.mjs +3 -3
- package/dist/commands/migration-status.d.mts +2 -3
- package/dist/commands/migration-status.d.mts.map +1 -1
- package/dist/commands/migration-status.mjs +1 -1
- package/dist/commands/ref.d.mts +1 -2
- package/dist/commands/ref.d.mts.map +1 -1
- package/dist/commands/ref.mjs +13 -18
- package/dist/commands/ref.mjs.map +1 -1
- package/dist/commands/telemetry/index.d.mts +0 -1
- package/dist/commands/telemetry/index.d.mts.map +1 -1
- package/dist/commands/telemetry/index.mjs +1 -1
- package/dist/contract-at-errors-C-l4-JEY.mjs +36 -0
- package/dist/contract-at-errors-C-l4-JEY.mjs.map +1 -0
- package/dist/{contract-emit-wgqrOXUI.mjs → contract-emit-DzOkTs11.mjs} +3 -3
- package/dist/{contract-emit-wgqrOXUI.mjs.map → contract-emit-DzOkTs11.mjs.map} +1 -1
- package/dist/{contract-emit-sYrqzsdN.mjs → contract-emit-iSMJVs26.mjs} +3 -3
- package/dist/{contract-emit-sYrqzsdN.mjs.map → contract-emit-iSMJVs26.mjs.map} +1 -1
- package/dist/{contract-infer-6SQnnVj7.mjs → contract-infer-BVqworBB.mjs} +3 -3
- package/dist/{contract-infer-6SQnnVj7.mjs.map → contract-infer-BVqworBB.mjs.map} +1 -1
- package/dist/{contract-space-aggregate-loader-DpB4x97a.mjs → contract-space-aggregate-loader-hPVymNLw.mjs} +14 -15
- package/dist/contract-space-aggregate-loader-hPVymNLw.mjs.map +1 -0
- package/dist/{db-verify-CEsG5Gek.mjs → db-verify-BpwCMyWJ.mjs} +6 -7
- package/dist/db-verify-BpwCMyWJ.mjs.map +1 -0
- package/dist/exports/control-api.d.mts +1 -2
- package/dist/exports/control-api.d.mts.map +1 -1
- package/dist/exports/control-api.mjs +2 -2
- package/dist/exports/index.d.mts +0 -1
- package/dist/exports/index.d.mts.map +1 -1
- package/dist/exports/index.mjs +2 -2
- package/dist/exports/init-output.d.mts +1 -0
- package/dist/exports/init-output.d.mts.map +1 -1
- package/dist/exports/init-output.mjs +1 -1
- package/dist/{format-CjongNb1.mjs → format-CFLZv_zU.mjs} +6 -5
- package/dist/format-CFLZv_zU.mjs.map +1 -0
- package/dist/{framework-components-BbnM1RYm.mjs → framework-components-VMQJbAwl.mjs} +2 -2
- package/dist/{framework-components-BbnM1RYm.mjs.map → framework-components-VMQJbAwl.mjs.map} +1 -1
- package/dist/global-flags-BpoOYtNZ.d.mts.map +1 -1
- package/dist/{init-D8YFfaD6.mjs → init-BSgPxVXA.mjs} +85 -90
- package/dist/init-BSgPxVXA.mjs.map +1 -0
- package/dist/{inspect-live-schema-CEt_ztBJ.mjs → inspect-live-schema-DxsD_a4d.mjs} +3 -3
- package/dist/{inspect-live-schema-CEt_ztBJ.mjs.map → inspect-live-schema-DxsD_a4d.mjs.map} +1 -1
- package/dist/{migration-check-ByeQHOhK.mjs → migration-check-DMxPzbHl.mjs} +70 -44
- package/dist/migration-check-DMxPzbHl.mjs.map +1 -0
- package/dist/migration-cli.d.mts +0 -1
- package/dist/migration-cli.d.mts.map +1 -1
- package/dist/migration-cli.mjs +3 -3
- package/dist/migration-cli.mjs.map +1 -1
- package/dist/{migration-command-scaffold-CJO3tjiR.mjs → migration-command-scaffold-DB_i9nBC.mjs} +3 -3
- package/dist/{migration-command-scaffold-CJO3tjiR.mjs.map → migration-command-scaffold-DB_i9nBC.mjs.map} +1 -1
- package/dist/{migration-list-DTYM8xvm.mjs → migration-list-cnSKilYH.mjs} +5 -5
- package/dist/migration-list-cnSKilYH.mjs.map +1 -0
- package/dist/{migration-log-qZ8LpQIl.mjs → migration-log-CEqFiez9.mjs} +3 -3
- package/dist/{migration-log-qZ8LpQIl.mjs.map → migration-log-CEqFiez9.mjs.map} +1 -1
- package/dist/{migration-path-target-DmNwsDgs.mjs → migration-path-target-C0TQuV5n.mjs} +2 -2
- package/dist/{migration-path-target-DmNwsDgs.mjs.map → migration-path-target-C0TQuV5n.mjs.map} +1 -1
- package/dist/{migration-plan-CfPrB80t.mjs → migration-plan-BJHulFZC.mjs} +54 -62
- package/dist/migration-plan-BJHulFZC.mjs.map +1 -0
- package/dist/{migration-status-DRuS-rP5.mjs → migration-status-ugWwGk1V.mjs} +5 -5
- package/dist/{migration-status-DRuS-rP5.mjs.map → migration-status-ugWwGk1V.mjs.map} +1 -1
- package/dist/{output-mEQ74_nd.mjs → output-DWNYqZGc.mjs} +5 -6
- package/dist/output-DWNYqZGc.mjs.map +1 -0
- package/dist/{ref-advancement-BkXlikCA.mjs → ref-advancement-BCz7X7qJ.mjs} +13 -6
- package/dist/ref-advancement-BCz7X7qJ.mjs.map +1 -0
- package/dist/schemas-CeGMYFYX.d.mts.map +1 -1
- package/dist/{telemetry-DHBBq5wT.mjs → telemetry-BEyNMfco.mjs} +2 -2
- package/dist/{telemetry-DHBBq5wT.mjs.map → telemetry-BEyNMfco.mjs.map} +1 -1
- package/dist/{terminal-ui-DGRNFWna.d.mts → terminal-ui-BN4QOsm5.d.mts} +1 -2
- package/dist/terminal-ui-BN4QOsm5.d.mts.map +1 -0
- package/dist/{types-DFoqNIcQ.d.mts → types-D4r_HI9-.d.mts} +8 -4
- package/dist/types-D4r_HI9-.d.mts.map +1 -0
- package/dist/{verify-DDCb0R2r.mjs → verify-C4RazDE1.mjs} +9 -8
- package/dist/verify-C4RazDE1.mjs.map +1 -0
- package/package.json +25 -25
- package/src/commands/db-init.ts +4 -3
- package/src/commands/db-sign.ts +6 -4
- package/src/commands/db-update.ts +15 -7
- package/src/commands/db-verify.ts +2 -3
- package/src/commands/init/errors.ts +62 -58
- package/src/commands/init/hygiene-gitattributes.ts +37 -21
- package/src/commands/init/init.ts +35 -32
- package/src/commands/init/output.ts +4 -5
- package/src/commands/init/reinit-cleanup.ts +16 -17
- package/src/commands/migrate.ts +1 -0
- package/src/commands/migration-check.ts +75 -38
- package/src/commands/migration-list.ts +1 -5
- package/src/commands/migration-new.ts +15 -39
- package/src/commands/migration-plan.ts +56 -78
- package/src/commands/ref.ts +19 -14
- package/src/control-api/operations/db-run.ts +4 -4
- package/src/control-api/operations/db-verify.ts +12 -9
- package/src/control-api/operations/format.ts +4 -3
- package/src/control-api/operations/migrate.ts +70 -26
- package/src/control-api/types.ts +4 -1
- package/src/migration-cli.ts +5 -5
- package/src/utils/cli-errors.ts +28 -7
- package/src/utils/combine-verify-results.ts +1 -1
- package/src/utils/contract-at-errors.ts +13 -22
- package/src/utils/contract-space-aggregate-loader.ts +12 -13
- package/src/utils/contract-space-seed-phase.ts +8 -6
- package/src/utils/formatters/errors.ts +24 -3
- package/src/utils/formatters/verify.ts +7 -6
- package/src/utils/integrity-violation-to-check-failure.ts +14 -14
- package/src/utils/plan-resolution.ts +6 -9
- package/src/utils/ref-advancement.ts +22 -3
- package/src/utils/result-handler.ts +4 -2
- package/dist/client-DigVI8zX.mjs.map +0 -1
- package/dist/command-helpers-CUoKYTlG.mjs.map +0 -1
- package/dist/contract-at-errors-D8CNDD7g.mjs +0 -42
- package/dist/contract-at-errors-D8CNDD7g.mjs.map +0 -1
- package/dist/contract-space-aggregate-loader-DpB4x97a.mjs.map +0 -1
- package/dist/db-verify-CEsG5Gek.mjs.map +0 -1
- package/dist/format-CjongNb1.mjs.map +0 -1
- package/dist/init-D8YFfaD6.mjs.map +0 -1
- package/dist/migration-check-ByeQHOhK.mjs.map +0 -1
- package/dist/migration-list-DTYM8xvm.mjs.map +0 -1
- package/dist/migration-plan-CfPrB80t.mjs.map +0 -1
- package/dist/output-mEQ74_nd.mjs.map +0 -1
- package/dist/ref-advancement-BkXlikCA.mjs.map +0 -1
- package/dist/terminal-ui-DGRNFWna.d.mts.map +0 -1
- package/dist/types-DFoqNIcQ.d.mts.map +0 -1
- package/dist/verify-DDCb0R2r.mjs.map +0 -1
|
@@ -12,6 +12,7 @@ import type {
|
|
|
12
12
|
} from '@prisma-next/framework-components/control';
|
|
13
13
|
import {
|
|
14
14
|
type AggregateContractSpace,
|
|
15
|
+
allStorageElementsExternal,
|
|
15
16
|
buildFabricatedMigrationEdge,
|
|
16
17
|
type ContractMarkerRecordLike,
|
|
17
18
|
type ContractSpaceAggregate,
|
|
@@ -98,13 +99,17 @@ export interface ExecuteMigrateOptions<TFamilyId extends string, TTargetId exten
|
|
|
98
99
|
* 2. Read live marker rows per space (`familyInstance.readAllMarkers`).
|
|
99
100
|
* 3. Per space: `resolveRecordedPath` plots the path from the live
|
|
100
101
|
* marker to `space.headRef.hash` (or `refHash` for the app
|
|
101
|
-
* space when provided).
|
|
102
|
-
*
|
|
102
|
+
* space when provided). An empty-graph space whose elements are ALL
|
|
103
|
+
* externally managed resolves declaratively (marker to head, zero
|
|
104
|
+
* ops), mirroring the db-init aggregate planner: such a space ships
|
|
105
|
+
* no DDL and has nothing to author. Every other empty-graph space
|
|
106
|
+
* fails loudly — "never planned" is a user-error condition for
|
|
107
|
+
* replay.
|
|
103
108
|
* 4. Hand off to {@link runMigration} (the runner-driving tail
|
|
104
109
|
* shared with `db init` / `db update`). Marker advancement is
|
|
105
110
|
* inside the per-space transaction.
|
|
106
111
|
*
|
|
107
|
-
* Encodes the replay-only contract:
|
|
112
|
+
* Encodes the replay-only contract: the app contract space must have an
|
|
108
113
|
* authored migration graph on disk before this operation can advance it.
|
|
109
114
|
*/
|
|
110
115
|
export async function executeMigrate<TFamilyId extends string, TTargetId extends string>(
|
|
@@ -230,8 +235,13 @@ export async function executeMigrate<TFamilyId extends string, TTargetId extends
|
|
|
230
235
|
// plans). Surfaces every loaded space — including at-head empty-
|
|
231
236
|
// graph extensions — in `perSpace[]` so the result reflects the
|
|
232
237
|
// full aggregate, not just the spaces the runner would have touched.
|
|
233
|
-
|
|
234
|
-
|
|
238
|
+
// A zero-op plan still counts as pending when it advances a marker
|
|
239
|
+
// (declared-state resolution for an all-external extension space).
|
|
240
|
+
const hasPendingWork = applyOrder.some((spaceId) => {
|
|
241
|
+
const entry = perSpacePlans.get(spaceId);
|
|
242
|
+
return entry !== undefined && planRequiresExecution(entry);
|
|
243
|
+
});
|
|
244
|
+
if (!hasPendingWork) {
|
|
235
245
|
const ordered = canonicalOrder
|
|
236
246
|
.filter((spaceId) => perSpacePlans.has(spaceId) || atHeadResolutions.has(spaceId))
|
|
237
247
|
.map((spaceId) => {
|
|
@@ -397,6 +407,39 @@ export function planSpacePath({
|
|
|
397
407
|
}),
|
|
398
408
|
};
|
|
399
409
|
}
|
|
410
|
+
// Empty-graph extension space not yet at head: the space ships no
|
|
411
|
+
// migration packages at all. Advancing the marker without migrations
|
|
412
|
+
// (the db-init aggregate planner's declared-state strategy, mirrored
|
|
413
|
+
// here) is valid exclusively when every element the space declares is
|
|
414
|
+
// externally managed — nothing Prisma Next owns exists in such a space
|
|
415
|
+
// (e.g. Supabase's auth/storage), so its declared state needs no
|
|
416
|
+
// migration to be true, and there is no command that could author an
|
|
417
|
+
// edge for it. A space that declares a managed element but ships no
|
|
418
|
+
// migration graph is an authoring bug: it falls through to the
|
|
419
|
+
// never-planned failure, like an app space with no authored graph.
|
|
420
|
+
if (!isAppSpace && allStorageElementsExternal(space.contract())) {
|
|
421
|
+
if (headRef.invariants.length > 0) {
|
|
422
|
+
return {
|
|
423
|
+
kind: 'unsatisfiable',
|
|
424
|
+
spaceId: space.spaceId,
|
|
425
|
+
isAppSpace,
|
|
426
|
+
missing: [...headRef.invariants].sort(),
|
|
427
|
+
targetInvariants: headRef.invariants,
|
|
428
|
+
targetSpace: space,
|
|
429
|
+
liveHash: liveHash ?? EMPTY_CONTRACT_HASH,
|
|
430
|
+
refName: undefined,
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
return {
|
|
434
|
+
kind: 'ok',
|
|
435
|
+
plan: buildAtHeadResolution({
|
|
436
|
+
aggregateTargetId: aggregate.targetId,
|
|
437
|
+
space,
|
|
438
|
+
targetHash,
|
|
439
|
+
liveMarker,
|
|
440
|
+
}),
|
|
441
|
+
};
|
|
442
|
+
}
|
|
400
443
|
return { kind: 'never-planned', spaceId: space.spaceId, targetHash };
|
|
401
444
|
}
|
|
402
445
|
|
|
@@ -434,11 +477,13 @@ export function planSpacePath({
|
|
|
434
477
|
}
|
|
435
478
|
|
|
436
479
|
/**
|
|
437
|
-
* Build a zero-op {@link PerSpacePlan} for an empty-graph
|
|
438
|
-
*
|
|
439
|
-
*
|
|
440
|
-
*
|
|
441
|
-
*
|
|
480
|
+
* Build a zero-op {@link PerSpacePlan} for an empty-graph space —
|
|
481
|
+
* either one whose live marker already matches the target (at-head), or
|
|
482
|
+
* an all-external extension space whose marker must advance to the head
|
|
483
|
+
* ref with no DDL (declared-state). Lets the apply pipeline thread the
|
|
484
|
+
* space through `perSpacePlans` -> `applyOrder` -> the success
|
|
485
|
+
* envelope's `perSpace[]` block so the result reflects every loaded
|
|
486
|
+
* space, even when there is nothing to execute.
|
|
442
487
|
*/
|
|
443
488
|
function buildAtHeadResolution(args: {
|
|
444
489
|
readonly aggregateTargetId: string;
|
|
@@ -469,17 +514,14 @@ function buildAtHeadResolution(args: {
|
|
|
469
514
|
};
|
|
470
515
|
}
|
|
471
516
|
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
total += entry.plan.operations.length;
|
|
481
|
-
}
|
|
482
|
-
return total;
|
|
517
|
+
/**
|
|
518
|
+
* A plan needs the runner when it executes operations or advances the
|
|
519
|
+
* space's marker (a declared-state resolution has zero operations but a
|
|
520
|
+
* destination hash the live marker doesn't carry yet).
|
|
521
|
+
*/
|
|
522
|
+
function planRequiresExecution(entry: PerSpacePlan): boolean {
|
|
523
|
+
if (entry.plan.operations.length > 0) return true;
|
|
524
|
+
return entry.plan.origin?.storageHash !== entry.plan.destination.storageHash;
|
|
483
525
|
}
|
|
484
526
|
|
|
485
527
|
interface BuildSuccessArgs {
|
|
@@ -552,10 +594,12 @@ function buildSuccess(args: BuildSuccessArgs): MigrateSuccess {
|
|
|
552
594
|
}
|
|
553
595
|
|
|
554
596
|
/**
|
|
555
|
-
* Build the `neverPlanned` failure raised when a contract space
|
|
556
|
-
*
|
|
557
|
-
*
|
|
558
|
-
*
|
|
597
|
+
* Build the `neverPlanned` failure raised when a contract space that
|
|
598
|
+
* declares managed storage elements has no on-disk migration graph but
|
|
599
|
+
* migrate was asked to reach a target hash. All-external spaces never reach
|
|
600
|
+
* this: they resolve declaratively (marker advances to the head ref with
|
|
601
|
+
* zero operations). The `why` states only the condition; the recovery
|
|
602
|
+
* sequence is composed by `errorPathUnreachable`'s `fix`.
|
|
559
603
|
*
|
|
560
604
|
* @internal Exported for testing only.
|
|
561
605
|
*/
|
|
@@ -563,7 +607,7 @@ export function buildNeverPlannedFailure(spaceId: string, targetHash: string): M
|
|
|
563
607
|
return {
|
|
564
608
|
code: 'MIGRATION_PATH_NOT_FOUND',
|
|
565
609
|
summary: `No on-disk migrations for contract space "${spaceId}"`,
|
|
566
|
-
why: `migrate is replay-only:
|
|
610
|
+
why: `migrate is replay-only: a contract space that declares managed storage elements must have an authored migration graph on disk. Space "${spaceId}" has no migrations under \`migrations/${spaceId}/\` but its head ref targets "${targetHash}".`,
|
|
567
611
|
meta: { spaceId, target: targetHash, kind: 'neverPlanned' },
|
|
568
612
|
};
|
|
569
613
|
}
|
package/src/control-api/types.ts
CHANGED
|
@@ -402,7 +402,10 @@ export interface DbInitSuccess {
|
|
|
402
402
|
/**
|
|
403
403
|
* Failure codes for dbInit operation.
|
|
404
404
|
*/
|
|
405
|
-
export type DbInitFailureCode =
|
|
405
|
+
export type DbInitFailureCode =
|
|
406
|
+
| 'PLANNING_FAILED'
|
|
407
|
+
| 'MIGRATION.MARKER_ORIGIN_MISMATCH'
|
|
408
|
+
| 'RUNNER_FAILED';
|
|
406
409
|
|
|
407
410
|
/**
|
|
408
411
|
* Failure details for dbInit operation.
|
package/src/migration-cli.ts
CHANGED
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
* the CLI commands use, walking up from the migration file's directory.
|
|
22
22
|
* 4. Probe-instantiates the migration class without a stack so it can read
|
|
23
23
|
* `targetId` and verify it matches `config.target.targetId`
|
|
24
|
-
* (`
|
|
24
|
+
* (`MIGRATION.TARGET_MISMATCH` on mismatch) before any stack-driven adapter
|
|
25
25
|
* construction runs.
|
|
26
26
|
* 5. Assembles a `ControlStack` from the loaded config descriptors and
|
|
27
27
|
* constructs the migration with that stack.
|
|
@@ -269,7 +269,7 @@ async function orchestrate(
|
|
|
269
269
|
|
|
270
270
|
// Pre-scan for malformed `--config` (no value, or value-shaped-as-flag)
|
|
271
271
|
// before delegating to clipanion. The legacy parser surfaced both as
|
|
272
|
-
// `errorMigrationCliInvalidConfigArg` (`
|
|
272
|
+
// `errorMigrationCliInvalidConfigArg` (`CLI.CONFIG_ARG_MISSING_PATH`); pre-scanning
|
|
273
273
|
// here keeps that contract independent of how clipanion classifies
|
|
274
274
|
// the error internally (it variably throws `UnknownSyntaxError` or
|
|
275
275
|
// accepts the flag-shaped token as the value depending on what other
|
|
@@ -337,7 +337,7 @@ async function orchestrate(
|
|
|
337
337
|
* - `--config <empty>` where the value is the empty string. Shells
|
|
338
338
|
* expand `--config ""` (or `--config "$UNSET_VAR"`) into a real
|
|
339
339
|
* empty argv token; treating that as a usage error here surfaces
|
|
340
|
-
* `
|
|
340
|
+
* `CLI.CONFIG_ARG_MISSING_PATH` instead of a less actionable loader error on an
|
|
341
341
|
* empty path.
|
|
342
342
|
* - `--config=` (the equals form with an empty value). Same shape as
|
|
343
343
|
* the empty-string case above; the user expressed intent to override
|
|
@@ -425,7 +425,7 @@ function isUnknownSyntaxError(err: unknown): err is Error {
|
|
|
425
425
|
/**
|
|
426
426
|
* Best-effort: pull the first input token that doesn't match a known
|
|
427
427
|
* flag. Falls back to the first token when we can't pinpoint it. The
|
|
428
|
-
* returned name is rendered into the user-visible
|
|
428
|
+
* returned name is rendered into the user-visible CLI.UNKNOWN_FLAG envelope
|
|
429
429
|
* (`Unknown flag \`<name>\``) and round-tripped via `meta.flag` so
|
|
430
430
|
* agent consumers can render their own "did you mean" suggestions.
|
|
431
431
|
*/
|
|
@@ -556,7 +556,7 @@ async function runMigration(
|
|
|
556
556
|
// subclasses are required to accept the no-arg form; the abstract
|
|
557
557
|
// `Migration` constructor declares `stack?` and target subclasses
|
|
558
558
|
// (Postgres, Mongo) propagate that optionality. This makes the
|
|
559
|
-
// target-mismatch guard fail fast with `
|
|
559
|
+
// target-mismatch guard fail fast with `MIGRATION.TARGET_MISMATCH` before any
|
|
560
560
|
// stack-driven adapter construction begins, even if the wrong-target
|
|
561
561
|
// adapter's `create` would otherwise succeed and silently misshapen
|
|
562
562
|
// the stored adapter cast.
|
package/src/utils/cli-errors.ts
CHANGED
|
@@ -215,22 +215,30 @@ export function errorPlanForgotTheFlag(
|
|
|
215
215
|
});
|
|
216
216
|
}
|
|
217
217
|
|
|
218
|
+
/**
|
|
219
|
+
* `viaRef: true` (the default) mirrors migration-tools' `errorRefNotResolvable`:
|
|
220
|
+
* a ref name with no pointer file, where the fallback hash isn't a graph
|
|
221
|
+
* node either — there's nothing to materialize a contract from.
|
|
222
|
+
* `viaRef: false` is a distinct, ref-independent case: an explicit `--from
|
|
223
|
+
* <hash>` that doesn't name a ref, on an empty migration graph, so there is
|
|
224
|
+
* no graph node and no ref to resolve a contract through.
|
|
225
|
+
*/
|
|
218
226
|
export function errorSnapshotMissing(
|
|
219
227
|
identifier: string,
|
|
220
228
|
options?: { readonly viaRef?: boolean },
|
|
221
229
|
): CliStructuredError {
|
|
222
230
|
const viaRef = options?.viaRef !== false;
|
|
223
231
|
const fix = viaRef
|
|
224
|
-
? `
|
|
225
|
-
: `No contract source exists for hash "${identifier}" on an empty migration graph. Use --from with a ref name
|
|
232
|
+
? `Create the ref with "prisma-next ref set ${identifier} <hash>" (or advance it via "prisma-next db update --advance-ref ${identifier}"), or pass a hash that is a node in the migration graph.`
|
|
233
|
+
: `No contract source exists for hash "${identifier}" on an empty migration graph. Use --from with a ref name (its contract resolves through the snapshot store), or run db update first.`;
|
|
226
234
|
return errorRuntime(
|
|
227
235
|
viaRef
|
|
228
|
-
? `Ref "${identifier}"
|
|
236
|
+
? `Ref "${identifier}" is not resolvable`
|
|
229
237
|
: `No contract source for from-hash "${identifier}"`,
|
|
230
238
|
{
|
|
231
239
|
why: viaRef
|
|
232
|
-
? `Ref "${identifier}"
|
|
233
|
-
: `Hash "${identifier}" is not a graph
|
|
240
|
+
? `Ref "${identifier}" has no pointer file, and the hash being resolved is not a node in the migration graph either.`
|
|
241
|
+
: `Hash "${identifier}" is not a node in the migration graph (the graph is empty), and it does not name a ref either.`,
|
|
234
242
|
fix,
|
|
235
243
|
meta: {
|
|
236
244
|
code: 'MIGRATION.SNAPSHOT_MISSING',
|
|
@@ -287,7 +295,18 @@ export function errorPathUnreachable(failure: MigrateFailure): CliStructuredErro
|
|
|
287
295
|
// Plan-then-apply recovery. The planner destination is the missing edge's
|
|
288
296
|
// target; `migration plan --to` (built for arbitrary targets) makes this a
|
|
289
297
|
// real command, so the diagnostic that sends you here is now honest.
|
|
298
|
+
//
|
|
299
|
+
// Never-planned spaces have an EMPTY migration graph, and contract-ref
|
|
300
|
+
// resolution only resolves full hashes against graph nodes — a
|
|
301
|
+
// `--to <hash>` remediation would reject its own input. `migration plan`
|
|
302
|
+
// without `--to` targets the working contract (the same contract the app
|
|
303
|
+
// space's synthesized head ref carries), so the bare form is the one that
|
|
304
|
+
// runs verbatim.
|
|
305
|
+
const neverPlanned = meta['kind'] === 'neverPlanned';
|
|
290
306
|
const planCommand = (() => {
|
|
307
|
+
if (neverPlanned) {
|
|
308
|
+
return 'prisma-next migration plan --name <slug>';
|
|
309
|
+
}
|
|
291
310
|
if (planFromHash !== null && targetHash !== null) {
|
|
292
311
|
return `prisma-next migration plan --from ${planFromHash} --to ${targetHash} --name <slug>`;
|
|
293
312
|
}
|
|
@@ -300,7 +319,9 @@ export function errorPathUnreachable(failure: MigrateFailure): CliStructuredErro
|
|
|
300
319
|
return 'prisma-next migration plan';
|
|
301
320
|
})();
|
|
302
321
|
const applyCommand =
|
|
303
|
-
targetHash !== null
|
|
322
|
+
targetHash !== null && !neverPlanned
|
|
323
|
+
? `prisma-next migrate --to ${targetHash}`
|
|
324
|
+
: 'prisma-next migrate';
|
|
304
325
|
return errorRuntime(failure.summary, {
|
|
305
326
|
why:
|
|
306
327
|
failure.why ??
|
|
@@ -347,7 +368,7 @@ export function mapMigrationToolsError(error: MigrationToolsError): CliStructure
|
|
|
347
368
|
* Shared "needs a live database" precondition for read verbs that consult the
|
|
348
369
|
* marker/ledger (`migration log`, `migration status`). A command needs both a
|
|
349
370
|
* connection string and a control-plane driver; either missing yields the same
|
|
350
|
-
* `
|
|
371
|
+
* `CONFIG.DB_CONNECTION_REQUIRED` envelope with `meta.missingFlags` (canonical long-form flags
|
|
351
372
|
* per CLI Style Guide §Errors) so callers can react programmatically. Returns
|
|
352
373
|
* `null` when both are present.
|
|
353
374
|
*/
|
|
@@ -78,7 +78,7 @@ export function combineVerifyResults(
|
|
|
78
78
|
return {
|
|
79
79
|
result: {
|
|
80
80
|
ok,
|
|
81
|
-
...(ok ? {} : { code: appResult.code ?? '
|
|
81
|
+
...(ok ? {} : { code: appResult.code ?? 'CONTRACT.MARKER_REQUIRED' }),
|
|
82
82
|
summary,
|
|
83
83
|
contract: appResult.contract,
|
|
84
84
|
target: appResult.target,
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { MigrationToolsError } from '@prisma-next/migration-tools/errors';
|
|
2
2
|
import { notOk, type Result } from '@prisma-next/utils/result';
|
|
3
|
-
import { join } from 'pathe';
|
|
4
3
|
import {
|
|
5
4
|
CliStructuredError,
|
|
6
5
|
errorContractValidationFailed,
|
|
@@ -16,7 +15,7 @@ export function mapContractAtError(
|
|
|
16
15
|
): Result<never, CliStructuredError> {
|
|
17
16
|
if (MigrationToolsError.is(error)) {
|
|
18
17
|
switch (error.code) {
|
|
19
|
-
case 'MIGRATION.
|
|
18
|
+
case 'MIGRATION.REF_NOT_RESOLVABLE': {
|
|
20
19
|
const refName =
|
|
21
20
|
typeof error.details?.['refName'] === 'string'
|
|
22
21
|
? error.details['refName']
|
|
@@ -27,18 +26,13 @@ export function mapContractAtError(
|
|
|
27
26
|
}
|
|
28
27
|
case 'MIGRATION.CONTRACT_DESERIALIZATION_FAILED': {
|
|
29
28
|
const filePath =
|
|
30
|
-
typeof error.details?.['filePath'] === 'string'
|
|
31
|
-
? error.details['filePath']
|
|
32
|
-
: 'ref-snapshot';
|
|
29
|
+
typeof error.details?.['filePath'] === 'string' ? error.details['filePath'] : 'unknown';
|
|
33
30
|
const message =
|
|
34
31
|
typeof error.details?.['message'] === 'string' ? error.details['message'] : error.message;
|
|
35
|
-
const isRefSnapshot = filePath.endsWith('.contract.json');
|
|
36
32
|
return notOk(
|
|
37
33
|
errorContractValidationFailed(
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
: `Predecessor contract at ${filePath} failed to deserialize: ${message}`,
|
|
41
|
-
{ where: { path: isRefSnapshot ? 'ref-snapshot' : filePath } },
|
|
34
|
+
`Predecessor contract at ${filePath} failed to deserialize: ${message}`,
|
|
35
|
+
{ where: { path: filePath } },
|
|
42
36
|
),
|
|
43
37
|
);
|
|
44
38
|
}
|
|
@@ -66,22 +60,19 @@ export function mapContractAtError(
|
|
|
66
60
|
fix: error.fix,
|
|
67
61
|
}),
|
|
68
62
|
);
|
|
69
|
-
case 'MIGRATION.
|
|
70
|
-
const
|
|
71
|
-
typeof error.details?.['
|
|
72
|
-
|
|
73
|
-
|
|
63
|
+
case 'MIGRATION.CONTRACT_SNAPSHOT_MISSING': {
|
|
64
|
+
const expectedPath =
|
|
65
|
+
typeof error.details?.['expectedPath'] === 'string'
|
|
66
|
+
? error.details['expectedPath']
|
|
67
|
+
: 'migrations/snapshots/';
|
|
74
68
|
const role = options?.artifactRole ?? 'from';
|
|
75
69
|
return notOk(
|
|
76
|
-
errorFileNotFound(
|
|
70
|
+
errorFileNotFound(expectedPath, {
|
|
77
71
|
why:
|
|
78
72
|
role === 'to'
|
|
79
|
-
? `Target migration is missing its
|
|
80
|
-
: `Predecessor migration is missing its
|
|
81
|
-
fix:
|
|
82
|
-
role === 'to'
|
|
83
|
-
? 'Re-emit the target migration so its sibling `end-contract.json` / `end-contract.d.ts` are restored, then re-run this command.'
|
|
84
|
-
: 'Re-emit the predecessor migration (`prisma-next migration plan` from its source) so its sibling `end-contract.json` is restored, then re-run this command.',
|
|
73
|
+
? `Target migration is missing its contract snapshot at ${expectedPath}`
|
|
74
|
+
: `Predecessor migration is missing its contract snapshot at ${expectedPath}`,
|
|
75
|
+
fix: 'Restore migrations/snapshots/ from version control, or re-run the command that produced this migration to regenerate its snapshot.',
|
|
85
76
|
}),
|
|
86
77
|
);
|
|
87
78
|
}
|
|
@@ -20,7 +20,7 @@ import { toDeclaredExtensionsFromRaw } from './extension-pack-inputs';
|
|
|
20
20
|
|
|
21
21
|
const CONTRACT_SPACES_DOCS_URL = 'https://pris.ly/contract-spaces';
|
|
22
22
|
|
|
23
|
-
function
|
|
23
|
+
function contractSpaceViolationError(
|
|
24
24
|
summary: string,
|
|
25
25
|
options: {
|
|
26
26
|
readonly why: string;
|
|
@@ -28,8 +28,7 @@ function contractSpaceError5002(
|
|
|
28
28
|
readonly violations: readonly IntegrityViolation[];
|
|
29
29
|
},
|
|
30
30
|
): CliStructuredError {
|
|
31
|
-
return new CliStructuredError('
|
|
32
|
-
domain: 'MIG',
|
|
31
|
+
return new CliStructuredError('MIGRATION.CONTRACT_SPACE_VIOLATION', summary, {
|
|
33
32
|
why: options.why,
|
|
34
33
|
fix: options.fix,
|
|
35
34
|
docsUrl: CONTRACT_SPACES_DOCS_URL,
|
|
@@ -38,7 +37,7 @@ function contractSpaceError5002(
|
|
|
38
37
|
}
|
|
39
38
|
|
|
40
39
|
/**
|
|
41
|
-
* Build the `
|
|
40
|
+
* Build the `MIGRATION.CONTRACT_SPACE_VIOLATION` structured-error envelope for a contract-space
|
|
42
41
|
* target mismatch. Shared between the declared-extension precheck (the
|
|
43
42
|
* descriptor's configured target disagrees with the project target) and
|
|
44
43
|
* the on-disk-contract check surfaced by `checkIntegrity`.
|
|
@@ -48,7 +47,7 @@ function targetMismatchError(
|
|
|
48
47
|
expected: string,
|
|
49
48
|
actual: string,
|
|
50
49
|
): CliStructuredError {
|
|
51
|
-
return
|
|
50
|
+
return contractSpaceViolationError(`Contract-space target mismatch for "${spaceId}"`, {
|
|
52
51
|
why: `Space "${spaceId}" targets "${actual}" but the project's adapter targets "${expected}".`,
|
|
53
52
|
fix: 'Update the extension descriptor to target the configured database, or change the project adapter.',
|
|
54
53
|
violations: [{ kind: 'targetMismatch', spaceId, expected, actual }],
|
|
@@ -89,9 +88,10 @@ function describeIntegrityViolation(violation: IntegrityViolation): string {
|
|
|
89
88
|
/**
|
|
90
89
|
* Map the integrity violations `checkIntegrity` reports into a single
|
|
91
90
|
* CLI structured-error envelope, preserving the error codes the prior
|
|
92
|
-
* throw-on-load loader emitted: `
|
|
93
|
-
* `
|
|
94
|
-
*
|
|
91
|
+
* throw-on-load loader emitted: `MIGRATION.CONTRACT_SPACE_LAYOUT_VIOLATION`
|
|
92
|
+
* (layout drift, bundled) and `MIGRATION.CONTRACT_SPACE_VIOLATION` (target /
|
|
93
|
+
* disjointness / contract-validation / structural integrity). Returns
|
|
94
|
+
* `null` when there is nothing to refuse on.
|
|
95
95
|
*
|
|
96
96
|
* Precedence reproduces the prior loader's first-failure ordering:
|
|
97
97
|
* layout drift first (every offence bundled into one envelope), then
|
|
@@ -113,8 +113,7 @@ export function mapIntegrityViolations(
|
|
|
113
113
|
layout.length === 1
|
|
114
114
|
? 'Contract-space layout violation detected'
|
|
115
115
|
: `Contract-space layout violations detected (${layout.length})`;
|
|
116
|
-
return new CliStructuredError('
|
|
117
|
-
domain: 'MIG',
|
|
116
|
+
return new CliStructuredError('MIGRATION.CONTRACT_SPACE_LAYOUT_VIOLATION', summary, {
|
|
118
117
|
why: `The on-disk \`migrations/\` directory and your \`extensionPacks\` declaration are not in agreement.\n${lines.join('\n')}`,
|
|
119
118
|
fix: 'Declare the extension in `extensionPacks` and re-emit its contract-space artefacts, or remove the orphan `migrations/<space>` directory.',
|
|
120
119
|
docsUrl: CONTRACT_SPACES_DOCS_URL,
|
|
@@ -133,7 +132,7 @@ export function mapIntegrityViolations(
|
|
|
133
132
|
|
|
134
133
|
const disjointness = violations.find((v) => v.kind === 'disjointness');
|
|
135
134
|
if (disjointness && disjointness.kind === 'disjointness') {
|
|
136
|
-
return
|
|
135
|
+
return contractSpaceViolationError(
|
|
137
136
|
`Contract-space disjointness violation: storage element "${disjointness.element}" claimed by multiple spaces`,
|
|
138
137
|
{
|
|
139
138
|
why: `Spaces ${disjointness.claimedBy.map((s) => `"${s}"`).join(', ')} all claim the storage element "${disjointness.element}". Each storage element must be owned by exactly one contract space.`,
|
|
@@ -145,7 +144,7 @@ export function mapIntegrityViolations(
|
|
|
145
144
|
|
|
146
145
|
const contractUnreadable = violations.find((v) => v.kind === 'contractUnreadable');
|
|
147
146
|
if (contractUnreadable && contractUnreadable.kind === 'contractUnreadable') {
|
|
148
|
-
return
|
|
147
|
+
return contractSpaceViolationError(
|
|
149
148
|
`Contract-space contract validation failed for "${contractUnreadable.spaceId}"`,
|
|
150
149
|
{
|
|
151
150
|
why: contractUnreadable.detail,
|
|
@@ -160,7 +159,7 @@ export function mapIntegrityViolations(
|
|
|
160
159
|
// is still computed; the gate just renders one envelope).
|
|
161
160
|
const structural = violations[0]!;
|
|
162
161
|
const spaceId = 'spaceId' in structural ? structural.spaceId : '*';
|
|
163
|
-
return
|
|
162
|
+
return contractSpaceViolationError(`Contract-space integrity failure for "${spaceId}"`, {
|
|
164
163
|
why: describeIntegrityViolation(structural),
|
|
165
164
|
fix: 'Re-emit the affected migration package(s) or restore the on-disk `migrations/` directory from version control.',
|
|
166
165
|
violations: [structural],
|
|
@@ -2,7 +2,7 @@ import { materialiseExtensionMigrationPackageIfMissing } from '@prisma-next/migr
|
|
|
2
2
|
import type { MigrationMetadata } from '@prisma-next/migration-tools/metadata';
|
|
3
3
|
import type { MigrationOps } from '@prisma-next/migration-tools/package';
|
|
4
4
|
import {
|
|
5
|
-
|
|
5
|
+
emitContractSpaceArtifacts,
|
|
6
6
|
planAllSpaces,
|
|
7
7
|
readContractSpaceHeadRef,
|
|
8
8
|
type SpacePlanOutput,
|
|
@@ -53,7 +53,7 @@ export interface ContractSpaceSeedPhaseInputs {
|
|
|
53
53
|
* - `action: 'unchanged'` — the on-disk head already matched the
|
|
54
54
|
* descriptor and no new migration packages needed to be written.
|
|
55
55
|
*
|
|
56
|
-
* Either way, the
|
|
56
|
+
* Either way, the artifacts (`contract.json`, `contract.d.ts`,
|
|
57
57
|
* `refs/head.json`) are re-emitted: the framework owns those files and
|
|
58
58
|
* makes the re-emit observably idempotent at the byte level.
|
|
59
59
|
*/
|
|
@@ -75,9 +75,11 @@ export interface ContractSpaceSeedPhaseResult {
|
|
|
75
75
|
* For every extension that exposes a `contractSpace`:
|
|
76
76
|
*
|
|
77
77
|
* 1. Read the on-disk head ref (returns `null` on first emit).
|
|
78
|
-
* 2.
|
|
79
|
-
*
|
|
80
|
-
*
|
|
78
|
+
* 2. Write the head contract into the migrations-root snapshot store
|
|
79
|
+
* (write-if-absent, keyed by hash) and unconditionally re-emit
|
|
80
|
+
* `refs/head.json`, via {@link emitContractSpaceArtifacts}. The
|
|
81
|
+
* framework owns `refs/head.json`; re-emit is the contract for that
|
|
82
|
+
* file. The snapshot itself is only written once per distinct hash.
|
|
81
83
|
* 3. Materialise any descriptor-shipped migration packages not yet on
|
|
82
84
|
* disk via {@link materialiseExtensionMigrationPackageIfMissing}.
|
|
83
85
|
* Existing packages are left untouched (by-existence skip).
|
|
@@ -144,7 +146,7 @@ export async function runContractSpaceSeedPhase(
|
|
|
144
146
|
const onDiskHeadRef = await readContractSpaceHeadRef(inputs.migrationsDir, space.spaceId);
|
|
145
147
|
const priorHash = onDiskHeadRef?.hash ?? null;
|
|
146
148
|
|
|
147
|
-
await
|
|
149
|
+
await emitContractSpaceArtifacts(inputs.migrationsDir, space.spaceId, {
|
|
148
150
|
contract: descriptor.contractJson,
|
|
149
151
|
contractDts: buildPlaceholderContractDts(space.spaceId),
|
|
150
152
|
headRef: { hash: descriptor.headRef.hash, invariants: descriptor.headRef.invariants },
|
|
@@ -7,6 +7,25 @@ import type { GlobalFlags } from '../global-flags';
|
|
|
7
7
|
import { createColorFormatter, formatDim, isVerbose } from './helpers';
|
|
8
8
|
import { formatPlannerWarningsBlock } from './migrations';
|
|
9
9
|
|
|
10
|
+
/**
|
|
11
|
+
* The display label for a schema-diff issue in the shared error envelope,
|
|
12
|
+
* derived from which sides are present: expected-only is a missing object,
|
|
13
|
+
* actual-only an extra one, both a mismatch. `undefined` when the entry is not
|
|
14
|
+
* a schema-diff issue (it carries neither side) so the caller can fall through
|
|
15
|
+
* to its generic label.
|
|
16
|
+
*/
|
|
17
|
+
function schemaDiffIssueLabel(issue: {
|
|
18
|
+
readonly expected?: unknown;
|
|
19
|
+
readonly actual?: unknown;
|
|
20
|
+
}): 'missing' | 'extra' | 'mismatch' | undefined {
|
|
21
|
+
const hasExpected = issue.expected !== undefined;
|
|
22
|
+
const hasActual = issue.actual !== undefined;
|
|
23
|
+
if (hasExpected && hasActual) return 'mismatch';
|
|
24
|
+
if (hasExpected) return 'missing';
|
|
25
|
+
if (hasActual) return 'extra';
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
|
|
10
29
|
/**
|
|
11
30
|
* Formats error output for human-readable display.
|
|
12
31
|
*/
|
|
@@ -51,13 +70,15 @@ export function formatErrorOutput(error: CliErrorEnvelope, flags: GlobalFlags):
|
|
|
51
70
|
// Show issues list if present (always show a short list; show full list when verbose).
|
|
52
71
|
// `issues` is a shared error-envelope field: PSL interpretation diagnostics stamp
|
|
53
72
|
// `kind` and `message` (their diagnostic code and prose); schema-diff issues
|
|
54
|
-
// (`SchemaDiffIssue`) carry no `message` and stamp `
|
|
73
|
+
// (`SchemaDiffIssue`) carry no `message` and stamp `path` plus the
|
|
74
|
+
// `expected`/`actual` presence a label is derived from.
|
|
55
75
|
if (error.meta?.['issues']) {
|
|
56
76
|
const issues = error.meta['issues'] as readonly {
|
|
57
77
|
kind?: string;
|
|
58
|
-
reason?: string;
|
|
59
78
|
message?: string;
|
|
60
79
|
path?: readonly string[];
|
|
80
|
+
expected?: unknown;
|
|
81
|
+
actual?: unknown;
|
|
61
82
|
}[];
|
|
62
83
|
if (issues.length > 0) {
|
|
63
84
|
const maxToShow = isVerbose(flags, 1) ? issues.length : Math.min(3, issues.length);
|
|
@@ -66,7 +87,7 @@ export function formatErrorOutput(error: CliErrorEnvelope, flags: GlobalFlags):
|
|
|
66
87
|
: ` Issues (showing ${maxToShow} of ${issues.length}):`;
|
|
67
88
|
lines.push(`${formatDimText(header)}`);
|
|
68
89
|
for (const issue of issues.slice(0, maxToShow)) {
|
|
69
|
-
const label = issue.kind ?? issue
|
|
90
|
+
const label = issue.kind ?? schemaDiffIssueLabel(issue) ?? 'issue';
|
|
70
91
|
const message = issue.message ?? issue.path?.join('/') ?? '';
|
|
71
92
|
lines.push(`${formatDimText(` - [${label}] ${message}`)}`);
|
|
72
93
|
}
|
|
@@ -8,13 +8,14 @@ import type {
|
|
|
8
8
|
VerifyDatabaseResult,
|
|
9
9
|
VerifyDatabaseSchemaResult,
|
|
10
10
|
} from '@prisma-next/framework-components/control';
|
|
11
|
+
import { issueOutcome } from '@prisma-next/framework-components/control';
|
|
11
12
|
import { ifDefined } from '@prisma-next/utils/defined';
|
|
12
13
|
import { bold, cyan, dim, green, magenta, red, yellow } from 'colorette';
|
|
13
14
|
import type { GlobalFlags } from '../global-flags';
|
|
14
15
|
import { createColorFormatter, formatDim, isVerbose } from './helpers';
|
|
15
16
|
|
|
16
|
-
/** Human-readable label for each
|
|
17
|
-
const
|
|
17
|
+
/** Human-readable label for each outcome, prefixed onto an issue's message for display. */
|
|
18
|
+
const OUTCOME_LABEL: Record<ExpectationFailureReason, string> = {
|
|
18
19
|
'not-found': 'missing',
|
|
19
20
|
'not-expected': 'extra',
|
|
20
21
|
'not-equal': 'mismatch',
|
|
@@ -22,12 +23,12 @@ const REASON_LABEL: Record<ExpectationFailureReason, string> = {
|
|
|
22
23
|
|
|
23
24
|
/**
|
|
24
25
|
* The issue's display text: its own path, prefixed with a human label for
|
|
25
|
-
* why it's flagged. Turning
|
|
26
|
-
* formatter's job, not the differ's — the differ's issue is
|
|
27
|
-
* `
|
|
26
|
+
* why it's flagged. Turning the presence-derived outcome (and the path) into
|
|
27
|
+
* prose is this formatter's job, not the differ's — the differ's issue is
|
|
28
|
+
* data (`path` + nodes), not prose.
|
|
28
29
|
*/
|
|
29
30
|
function formatIssueMessage(issue: SchemaDiffIssue): string {
|
|
30
|
-
return `${
|
|
31
|
+
return `${OUTCOME_LABEL[issueOutcome(issue)]}: ${issue.path.join('/')}`;
|
|
31
32
|
}
|
|
32
33
|
|
|
33
34
|
// ============================================================================
|