@prisma-next/cli 0.4.1 → 0.4.3
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 +56 -26
- package/dist/agent-skill-mongo.md +63 -31
- package/dist/agent-skill-postgres.md +1 -1
- package/dist/cli-errors-By1iVE3z.mjs +34 -0
- package/dist/cli-errors-By1iVE3z.mjs.map +1 -0
- package/dist/cli-errors-DDeVsP2Y.d.mts +5 -0
- package/dist/cli.mjs +131 -15
- package/dist/cli.mjs.map +1 -1
- package/dist/{client-DGKrciLM.mjs → client-1JqqkiC7.mjs} +45 -20
- package/dist/client-1JqqkiC7.mjs.map +1 -0
- package/dist/commands/contract-emit.d.mts.map +1 -1
- package/dist/commands/contract-emit.mjs +7 -2
- package/dist/commands/contract-infer.d.mts.map +1 -1
- package/dist/commands/contract-infer.mjs +8 -2
- package/dist/commands/db-init.d.mts.map +1 -1
- package/dist/commands/db-init.mjs +11 -9
- package/dist/commands/db-init.mjs.map +1 -1
- package/dist/commands/db-schema.mjs +8 -5
- package/dist/commands/db-schema.mjs.map +1 -1
- package/dist/commands/db-sign.mjs +8 -7
- package/dist/commands/db-sign.mjs.map +1 -1
- package/dist/commands/db-update.mjs +10 -9
- package/dist/commands/db-update.mjs.map +1 -1
- package/dist/commands/db-verify.mjs +10 -9
- package/dist/commands/db-verify.mjs.map +1 -1
- package/dist/commands/migration-apply.d.mts +5 -2
- package/dist/commands/migration-apply.d.mts.map +1 -1
- package/dist/commands/migration-apply.mjs +57 -57
- package/dist/commands/migration-apply.mjs.map +1 -1
- package/dist/commands/migration-new.d.mts.map +1 -1
- package/dist/commands/migration-new.mjs +26 -32
- package/dist/commands/migration-new.mjs.map +1 -1
- package/dist/commands/migration-plan.d.mts +14 -5
- package/dist/commands/migration-plan.d.mts.map +1 -1
- package/dist/commands/migration-plan.mjs +45 -48
- package/dist/commands/migration-plan.mjs.map +1 -1
- package/dist/commands/migration-ref.d.mts +6 -4
- package/dist/commands/migration-ref.d.mts.map +1 -1
- package/dist/commands/migration-ref.mjs +31 -40
- package/dist/commands/migration-ref.mjs.map +1 -1
- package/dist/commands/migration-show.d.mts +13 -7
- package/dist/commands/migration-show.d.mts.map +1 -1
- package/dist/commands/migration-show.mjs +28 -29
- package/dist/commands/migration-show.mjs.map +1 -1
- package/dist/commands/migration-status.d.mts +23 -5
- package/dist/commands/migration-status.d.mts.map +1 -1
- package/dist/commands/migration-status.mjs +8 -3
- package/dist/{config-loader-_xQZsw0i.mjs → config-loader-ih8ViDb_.mjs} +2 -2
- package/dist/config-loader-ih8ViDb_.mjs.map +1 -0
- package/dist/config-loader.mjs +1 -1
- package/dist/contract-emit-DS5NzZh2.mjs +6 -0
- package/dist/contract-emit-RZBWzkop.mjs +329 -0
- package/dist/contract-emit-RZBWzkop.mjs.map +1 -0
- package/dist/contract-emit-rt_Nmdwq.mjs +150 -0
- package/dist/contract-emit-rt_Nmdwq.mjs.map +1 -0
- package/dist/{contract-enrichment-BV4KpbNW.mjs → contract-enrichment-4Ptgw3Pe.mjs} +1 -1
- package/dist/{contract-enrichment-BV4KpbNW.mjs.map → contract-enrichment-4Ptgw3Pe.mjs.map} +1 -1
- package/dist/{contract-infer-CUbiWGX0.mjs → contract-infer-Cf5J2wVg.mjs} +11 -19
- package/dist/contract-infer-Cf5J2wVg.mjs.map +1 -0
- package/dist/exports/control-api.d.mts +86 -21
- package/dist/exports/control-api.d.mts.map +1 -1
- package/dist/exports/control-api.mjs +7 -5
- package/dist/exports/index.mjs +8 -3
- package/dist/exports/index.mjs.map +1 -1
- package/dist/exports/init-output.d.mts +39 -0
- package/dist/exports/init-output.d.mts.map +1 -0
- package/dist/exports/init-output.mjs +3 -0
- package/dist/{framework-components-B__p--vT.mjs → framework-components-Bgcre3Z6.mjs} +2 -2
- package/dist/{framework-components-B__p--vT.mjs.map → framework-components-Bgcre3Z6.mjs.map} +1 -1
- package/dist/init-DAbQMxIR.mjs +2062 -0
- package/dist/init-DAbQMxIR.mjs.map +1 -0
- package/dist/{inspect-live-schema-wIYBTdL3.mjs → inspect-live-schema-LWtXfxm_.mjs} +9 -9
- package/dist/inspect-live-schema-LWtXfxm_.mjs.map +1 -0
- package/dist/migration-cli.d.mts +80 -0
- package/dist/migration-cli.d.mts.map +1 -0
- package/dist/migration-cli.mjs +408 -0
- package/dist/migration-cli.mjs.map +1 -0
- package/dist/{migration-command-scaffold-BC73xQSo.mjs → migration-command-scaffold-CU452v9h.mjs} +7 -7
- package/dist/{migration-command-scaffold-BC73xQSo.mjs.map → migration-command-scaffold-CU452v9h.mjs.map} +1 -1
- package/dist/{migration-status-CXBbScH5.mjs → migration-status-DoPrFIOQ.mjs} +119 -64
- package/dist/migration-status-DoPrFIOQ.mjs.map +1 -0
- package/dist/{migrations-DYRAjiVh.mjs → migrations-MEoKMiV5.mjs} +42 -21
- package/dist/migrations-MEoKMiV5.mjs.map +1 -0
- package/dist/output-BpcQrnnq.mjs +103 -0
- package/dist/output-BpcQrnnq.mjs.map +1 -0
- package/dist/{progress-adapter-Bwouy73-.mjs → progress-adapter-DgRGldpT.mjs} +1 -1
- package/dist/{progress-adapter-Bwouy73-.mjs.map → progress-adapter-DgRGldpT.mjs.map} +1 -1
- package/dist/quick-reference-mongo.md +34 -13
- package/dist/quick-reference-postgres.md +11 -9
- package/dist/{result-handler-CGohaH1o.mjs → result-handler-Ch6hVnOo.mjs} +36 -94
- package/dist/result-handler-Ch6hVnOo.mjs.map +1 -0
- package/dist/{terminal-ui-BuPXVRFY.mjs → terminal-ui-u2YgKghu.mjs} +76 -2
- package/dist/terminal-ui-u2YgKghu.mjs.map +1 -0
- package/dist/{verify-Cm2UFuZA.mjs → verify-BT9tgCOH.mjs} +2 -2
- package/dist/{verify-Cm2UFuZA.mjs.map → verify-BT9tgCOH.mjs.map} +1 -1
- package/package.json +27 -17
- package/src/cli.ts +32 -6
- package/src/commands/contract-emit.ts +67 -163
- package/src/commands/contract-infer.ts +7 -20
- package/src/commands/db-init.ts +1 -0
- package/src/commands/db-update.ts +1 -1
- package/src/commands/init/detect-pnpm-catalog.ts +141 -0
- package/src/commands/init/errors.ts +254 -0
- package/src/commands/init/exit-codes.ts +62 -0
- package/src/commands/init/hygiene-gitattributes.ts +97 -0
- package/src/commands/init/hygiene-gitignore.ts +48 -0
- package/src/commands/init/hygiene-package-scripts.ts +91 -0
- package/src/commands/init/index.ts +112 -7
- package/src/commands/init/init.ts +766 -144
- package/src/commands/init/inputs.ts +421 -0
- package/src/commands/init/output.ts +147 -0
- package/src/commands/init/probe-db.ts +308 -0
- package/src/commands/init/reinit-cleanup.ts +83 -0
- package/src/commands/init/templates/agent-skill-mongo.md +63 -31
- package/src/commands/init/templates/agent-skill-postgres.md +1 -1
- package/src/commands/init/templates/agent-skill.ts +25 -3
- package/src/commands/init/templates/code-templates.ts +125 -32
- package/src/commands/init/templates/env.ts +80 -0
- package/src/commands/init/templates/quick-reference-mongo.md +34 -13
- package/src/commands/init/templates/quick-reference-postgres.md +11 -9
- package/src/commands/init/templates/quick-reference.ts +42 -3
- package/src/commands/init/templates/tsconfig.ts +167 -5
- package/src/commands/inspect-live-schema.ts +10 -5
- package/src/commands/migration-apply.ts +86 -65
- package/src/commands/migration-new.ts +28 -34
- package/src/commands/migration-plan.ts +80 -56
- package/src/commands/migration-ref.ts +40 -54
- package/src/commands/migration-show.ts +53 -36
- package/src/commands/migration-status.ts +202 -71
- package/src/config-path-validation.ts +0 -1
- package/src/control-api/client.ts +21 -0
- package/src/control-api/operations/contract-emit.ts +198 -115
- package/src/control-api/operations/db-init.ts +10 -6
- package/src/control-api/operations/db-update.ts +10 -6
- package/src/control-api/operations/migration-apply.ts +30 -9
- package/src/control-api/types.ts +69 -7
- package/src/exports/control-api.ts +2 -1
- package/src/exports/init-output.ts +10 -0
- package/src/migration-cli.ts +577 -0
- package/src/utils/cli-errors.ts +50 -2
- package/src/utils/command-helpers.ts +48 -26
- package/src/utils/emit-queue.ts +26 -0
- package/src/utils/formatters/graph-migration-mapper.ts +7 -3
- package/src/utils/formatters/migrations.ts +62 -26
- package/src/utils/publish-contract-artifact-pair.ts +134 -0
- package/dist/cli-errors-CznZA5-d.mjs +0 -5
- package/dist/cli-errors-z37sV3eR.d.mts +0 -4
- package/dist/client-DGKrciLM.mjs.map +0 -1
- package/dist/config-loader-_xQZsw0i.mjs.map +0 -1
- package/dist/contract-emit-304WZtZJ.mjs +0 -4
- package/dist/contract-emit-DgeWdonT.mjs +0 -122
- package/dist/contract-emit-DgeWdonT.mjs.map +0 -1
- package/dist/contract-emit-mU1_B_m9.mjs +0 -195
- package/dist/contract-emit-mU1_B_m9.mjs.map +0 -1
- package/dist/contract-infer-CUbiWGX0.mjs.map +0 -1
- package/dist/extract-operation-statements-DWWFz1PK.mjs +0 -13
- package/dist/extract-operation-statements-DWWFz1PK.mjs.map +0 -1
- package/dist/extract-sql-ddl-7zn_AFS8.mjs +0 -26
- package/dist/extract-sql-ddl-7zn_AFS8.mjs.map +0 -1
- package/dist/init-DRquYpPa.mjs +0 -430
- package/dist/init-DRquYpPa.mjs.map +0 -1
- package/dist/inspect-live-schema-wIYBTdL3.mjs.map +0 -1
- package/dist/migration-status-CXBbScH5.mjs.map +0 -1
- package/dist/migrations-DYRAjiVh.mjs.map +0 -1
- package/dist/result-handler-CGohaH1o.mjs.map +0 -1
- package/dist/terminal-ui-BuPXVRFY.mjs.map +0 -1
- package/dist/validate-contract-deps-DZqv9m7H.mjs +0 -37
- package/dist/validate-contract-deps-DZqv9m7H.mjs.map +0 -1
- package/src/control-api/operations/extract-operation-statements.ts +0 -14
- package/src/control-api/operations/extract-sql-ddl.ts +0 -47
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
import type { MigrationPlanOperation } from '@prisma-next/framework-components/control';
|
|
2
2
|
import { EMPTY_CONTRACT_HASH } from '@prisma-next/migration-tools/constants';
|
|
3
|
+
import {
|
|
4
|
+
errorNoInvariantPath,
|
|
5
|
+
errorUnknownInvariant,
|
|
6
|
+
MigrationToolsError,
|
|
7
|
+
} from '@prisma-next/migration-tools/errors';
|
|
8
|
+
import type { MigrationEdge, MigrationGraph } from '@prisma-next/migration-tools/graph';
|
|
3
9
|
import {
|
|
4
10
|
findPath,
|
|
5
11
|
findPathWithDecision,
|
|
6
12
|
findReachableLeaves,
|
|
7
|
-
} from '@prisma-next/migration-tools/
|
|
8
|
-
import type {
|
|
13
|
+
} from '@prisma-next/migration-tools/migration-graph';
|
|
14
|
+
import type { MigrationPackage } from '@prisma-next/migration-tools/package';
|
|
15
|
+
import type { RefEntry, Refs } from '@prisma-next/migration-tools/refs';
|
|
9
16
|
import { readRefs, resolveRef } from '@prisma-next/migration-tools/refs';
|
|
10
|
-
import type {
|
|
11
|
-
MigrationBundle,
|
|
12
|
-
MigrationChainEntry,
|
|
13
|
-
MigrationGraph,
|
|
14
|
-
} from '@prisma-next/migration-tools/types';
|
|
15
|
-
import { MigrationToolsError } from '@prisma-next/migration-tools/types';
|
|
16
17
|
import { ifDefined } from '@prisma-next/utils/defined';
|
|
17
18
|
import { notOk, ok, type Result } from '@prisma-next/utils/result';
|
|
18
19
|
import { cyan, dim, magenta, yellow } from 'colorette';
|
|
@@ -20,16 +21,23 @@ import { Command } from 'commander';
|
|
|
20
21
|
|
|
21
22
|
import { loadConfig } from '../config-loader';
|
|
22
23
|
import { createControlClient } from '../control-api/client';
|
|
23
|
-
import {
|
|
24
|
+
import {
|
|
25
|
+
type CliStructuredError,
|
|
26
|
+
errorRuntime,
|
|
27
|
+
errorUnexpected,
|
|
28
|
+
mapMigrationToolsError,
|
|
29
|
+
} from '../utils/cli-errors';
|
|
24
30
|
import {
|
|
25
31
|
addGlobalOptions,
|
|
26
|
-
|
|
32
|
+
collectDeclaredInvariants,
|
|
33
|
+
loadMigrationPackages,
|
|
27
34
|
maskConnectionUrl,
|
|
28
35
|
readContractEnvelope,
|
|
29
36
|
resolveMigrationPaths,
|
|
30
37
|
setCommandDescriptions,
|
|
31
38
|
setCommandExamples,
|
|
32
39
|
toPathDecisionResult,
|
|
40
|
+
toStructuralEdge,
|
|
33
41
|
} from '../utils/command-helpers';
|
|
34
42
|
import {
|
|
35
43
|
type EdgeStatus,
|
|
@@ -61,7 +69,7 @@ export interface MigrationStatusEntry {
|
|
|
61
69
|
readonly dirName: string;
|
|
62
70
|
readonly from: string;
|
|
63
71
|
readonly to: string;
|
|
64
|
-
readonly
|
|
72
|
+
readonly migrationHash: string;
|
|
65
73
|
readonly operationCount: number;
|
|
66
74
|
readonly operationSummary: string;
|
|
67
75
|
readonly hasDestructive: boolean;
|
|
@@ -78,23 +86,39 @@ export interface MigrationStatusResult {
|
|
|
78
86
|
readonly targetHash: string;
|
|
79
87
|
readonly contractHash: string;
|
|
80
88
|
readonly refs?: readonly StatusRef[];
|
|
89
|
+
/** Required invariants from the active ref, sorted ascending. Always present (`[]` when no `--ref` or the ref declares none) — knowable offline. */
|
|
90
|
+
readonly requiredInvariants: readonly string[];
|
|
91
|
+
/**
|
|
92
|
+
* Invariants the marker has applied at least once, intersected with
|
|
93
|
+
* `requiredInvariants` for display relevance. JSON consumers see only the
|
|
94
|
+
* subset overlapping the active ref's required set — the full unfiltered
|
|
95
|
+
* marker invariant list lives on `marker.invariants` (control plane) and
|
|
96
|
+
* is not surfaced here. Present only in `mode === 'online'`; absent when
|
|
97
|
+
* offline (the marker is unknown, not empty).
|
|
98
|
+
*/
|
|
99
|
+
readonly appliedInvariants?: readonly string[];
|
|
100
|
+
/** required − applied. Present only in `mode === 'online'`; absent when offline. */
|
|
101
|
+
readonly missingInvariants?: readonly string[];
|
|
81
102
|
readonly pathDecision?: {
|
|
82
103
|
readonly fromHash: string;
|
|
83
104
|
readonly toHash: string;
|
|
84
105
|
readonly alternativeCount: number;
|
|
85
106
|
readonly tieBreakReasons: readonly string[];
|
|
86
107
|
readonly refName?: string;
|
|
108
|
+
readonly requiredInvariants: readonly string[];
|
|
109
|
+
readonly satisfiedInvariants: readonly string[];
|
|
87
110
|
readonly selectedPath: readonly {
|
|
88
111
|
readonly dirName: string;
|
|
89
|
-
readonly
|
|
112
|
+
readonly migrationHash: string;
|
|
90
113
|
readonly from: string;
|
|
91
114
|
readonly to: string;
|
|
115
|
+
readonly invariants: readonly string[];
|
|
92
116
|
}[];
|
|
93
117
|
};
|
|
94
118
|
readonly summary: string;
|
|
95
119
|
readonly diagnostics: readonly StatusDiagnostic[];
|
|
96
120
|
readonly graph?: MigrationGraph;
|
|
97
|
-
readonly bundles?: readonly
|
|
121
|
+
readonly bundles?: readonly MigrationPackage[];
|
|
98
122
|
readonly edgeStatuses?: readonly EdgeStatus[];
|
|
99
123
|
readonly activeRefHash?: string;
|
|
100
124
|
readonly activeRefName?: string;
|
|
@@ -154,7 +178,7 @@ export function deriveEdgeStatuses(
|
|
|
154
178
|
): EdgeStatus[] {
|
|
155
179
|
if (mode === 'offline') return [];
|
|
156
180
|
|
|
157
|
-
const edgeKey = (e:
|
|
181
|
+
const edgeKey = (e: MigrationEdge) => `${e.from}\0${e.to}`;
|
|
158
182
|
|
|
159
183
|
// No marker = empty DB — treat root as the marker (nothing applied, everything pending)
|
|
160
184
|
const effectiveMarker = markerHash ?? EMPTY_CONTRACT_HASH;
|
|
@@ -224,8 +248,8 @@ export function deriveEdgeStatuses(
|
|
|
224
248
|
* @param markerHash — the marker hash from the database, or undefined if no marker row / offline
|
|
225
249
|
*/
|
|
226
250
|
function buildMigrationEntries(
|
|
227
|
-
chain: readonly
|
|
228
|
-
packages: readonly
|
|
251
|
+
chain: readonly MigrationEdge[],
|
|
252
|
+
packages: readonly MigrationPackage[],
|
|
229
253
|
mode: 'online' | 'offline',
|
|
230
254
|
markerHash: string | undefined,
|
|
231
255
|
edgeStatuses?: readonly EdgeStatus[],
|
|
@@ -261,7 +285,7 @@ function buildMigrationEntries(
|
|
|
261
285
|
dirName: migration.dirName,
|
|
262
286
|
from: migration.from,
|
|
263
287
|
to: migration.to,
|
|
264
|
-
|
|
288
|
+
migrationHash: migration.migrationHash,
|
|
265
289
|
operationCount: ops.length,
|
|
266
290
|
operationSummary: summary,
|
|
267
291
|
hasDestructive,
|
|
@@ -294,7 +318,7 @@ function resolveDisplayChain(
|
|
|
294
318
|
graph: MigrationGraph,
|
|
295
319
|
targetHash: string,
|
|
296
320
|
markerHash: string | undefined,
|
|
297
|
-
): readonly
|
|
321
|
+
): readonly MigrationEdge[] | null {
|
|
298
322
|
if (markerHash === undefined) {
|
|
299
323
|
return findPath(graph, EMPTY_CONTRACT_HASH, targetHash);
|
|
300
324
|
}
|
|
@@ -345,7 +369,7 @@ async function executeMigrationStatusCommand(
|
|
|
345
369
|
ui: TerminalUI,
|
|
346
370
|
): Promise<Result<MigrationStatusResult, CliStructuredError>> {
|
|
347
371
|
const config = await loadConfig(options.config);
|
|
348
|
-
const { configPath, migrationsDir, migrationsRelative,
|
|
372
|
+
const { configPath, migrationsDir, migrationsRelative, refsDir } = resolveMigrationPaths(
|
|
349
373
|
options.config,
|
|
350
374
|
config,
|
|
351
375
|
);
|
|
@@ -355,48 +379,35 @@ async function executeMigrationStatusCommand(
|
|
|
355
379
|
|
|
356
380
|
let activeRefName: string | undefined;
|
|
357
381
|
let activeRefHash: string | undefined;
|
|
382
|
+
let activeRefEntry: RefEntry | undefined;
|
|
358
383
|
let allRefs: Refs = {};
|
|
359
384
|
try {
|
|
360
|
-
allRefs = await readRefs(
|
|
385
|
+
allRefs = await readRefs(refsDir);
|
|
361
386
|
} catch (error) {
|
|
362
387
|
if (MigrationToolsError.is(error)) {
|
|
363
|
-
return notOk(
|
|
364
|
-
errorRuntime(error.message, {
|
|
365
|
-
why: error.why,
|
|
366
|
-
fix: error.fix,
|
|
367
|
-
meta: { code: error.code },
|
|
368
|
-
}),
|
|
369
|
-
);
|
|
388
|
+
return notOk(mapMigrationToolsError(error));
|
|
370
389
|
}
|
|
371
390
|
throw error;
|
|
372
391
|
}
|
|
373
392
|
|
|
374
393
|
if (options.ref) {
|
|
375
394
|
activeRefName = options.ref;
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
activeRefHash =
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
} catch (error) {
|
|
383
|
-
if (MigrationToolsError.is(error)) {
|
|
384
|
-
return notOk(
|
|
385
|
-
errorRuntime(error.message, {
|
|
386
|
-
why: error.why,
|
|
387
|
-
fix: error.fix,
|
|
388
|
-
meta: { code: error.code },
|
|
389
|
-
}),
|
|
390
|
-
);
|
|
391
|
-
}
|
|
392
|
-
throw error;
|
|
395
|
+
try {
|
|
396
|
+
activeRefEntry = resolveRef(allRefs, activeRefName);
|
|
397
|
+
activeRefHash = activeRefEntry.hash;
|
|
398
|
+
} catch (error) {
|
|
399
|
+
if (MigrationToolsError.is(error)) {
|
|
400
|
+
return notOk(mapMigrationToolsError(error));
|
|
393
401
|
}
|
|
402
|
+
throw error;
|
|
394
403
|
}
|
|
395
404
|
}
|
|
396
405
|
|
|
397
|
-
const
|
|
406
|
+
const requiredInvariants: readonly string[] = [...(activeRefEntry?.invariants ?? [])].sort();
|
|
407
|
+
|
|
408
|
+
const statusRefs: StatusRef[] = Object.entries(allRefs).map(([name, entry]) => ({
|
|
398
409
|
name,
|
|
399
|
-
hash,
|
|
410
|
+
hash: entry.hash,
|
|
400
411
|
active: name === activeRefName,
|
|
401
412
|
}));
|
|
402
413
|
|
|
@@ -411,6 +422,12 @@ async function executeMigrationStatusCommand(
|
|
|
411
422
|
if (activeRefName) {
|
|
412
423
|
details.push({ label: 'ref', value: activeRefName });
|
|
413
424
|
}
|
|
425
|
+
if (activeRefEntry && activeRefEntry.invariants.length > 0) {
|
|
426
|
+
details.push({
|
|
427
|
+
label: 'required',
|
|
428
|
+
value: formatInvariantList(activeRefEntry.invariants),
|
|
429
|
+
});
|
|
430
|
+
}
|
|
414
431
|
const header = formatStyledHeader({
|
|
415
432
|
command: 'migration status',
|
|
416
433
|
description: 'Show migration history and applied status',
|
|
@@ -434,15 +451,13 @@ async function executeMigrationStatusCommand(
|
|
|
434
451
|
});
|
|
435
452
|
}
|
|
436
453
|
|
|
437
|
-
let bundles: readonly
|
|
454
|
+
let bundles: readonly MigrationPackage[];
|
|
438
455
|
let graph: MigrationGraph;
|
|
439
456
|
try {
|
|
440
|
-
({ bundles, graph } = await
|
|
457
|
+
({ bundles, graph } = await loadMigrationPackages(migrationsDir));
|
|
441
458
|
} catch (error) {
|
|
442
459
|
if (MigrationToolsError.is(error)) {
|
|
443
|
-
return notOk(
|
|
444
|
-
errorRuntime(error.message, { why: error.why, fix: error.fix, meta: { code: error.code } }),
|
|
445
|
-
);
|
|
460
|
+
return notOk(mapMigrationToolsError(error));
|
|
446
461
|
}
|
|
447
462
|
return notOk(
|
|
448
463
|
errorUnexpected(error instanceof Error ? error.message : String(error), {
|
|
@@ -470,6 +485,7 @@ async function executeMigrationStatusCommand(
|
|
|
470
485
|
contractHash,
|
|
471
486
|
summary: 'No migrations found',
|
|
472
487
|
diagnostics,
|
|
488
|
+
requiredInvariants,
|
|
473
489
|
});
|
|
474
490
|
}
|
|
475
491
|
|
|
@@ -497,6 +513,7 @@ async function executeMigrationStatusCommand(
|
|
|
497
513
|
}
|
|
498
514
|
|
|
499
515
|
let markerHash: string | undefined;
|
|
516
|
+
let markerInvariants: readonly string[] = [];
|
|
500
517
|
let mode: 'online' | 'offline' = 'offline';
|
|
501
518
|
|
|
502
519
|
if (dbConnection && hasDriver) {
|
|
@@ -509,7 +526,9 @@ async function executeMigrationStatusCommand(
|
|
|
509
526
|
});
|
|
510
527
|
try {
|
|
511
528
|
await client.connect(dbConnection);
|
|
512
|
-
|
|
529
|
+
const marker = await client.readMarker();
|
|
530
|
+
markerHash = marker?.storageHash;
|
|
531
|
+
markerInvariants = marker?.invariants ?? [];
|
|
513
532
|
mode = 'online';
|
|
514
533
|
} catch {
|
|
515
534
|
if (!flags.json && !flags.quiet) {
|
|
@@ -520,6 +539,32 @@ async function executeMigrationStatusCommand(
|
|
|
520
539
|
}
|
|
521
540
|
}
|
|
522
541
|
|
|
542
|
+
// Pre-check unknown invariants. Online: union the graph's declared
|
|
543
|
+
// invariants with the marker's recorded set so a retired-but-applied
|
|
544
|
+
// invariant doesn't surface as MIGRATION.UNKNOWN_INVARIANT — apply would
|
|
545
|
+
// route fine because marker subtraction empties `effectiveRequired`.
|
|
546
|
+
// Offline: keep the check graph-strict (the marker is unknown, and a
|
|
547
|
+
// missing declarer is the dominant signal we can offer).
|
|
548
|
+
if (activeRefEntry && activeRefEntry.invariants.length > 0) {
|
|
549
|
+
const declared = collectDeclaredInvariants(graph);
|
|
550
|
+
const known = new Set<string>(declared);
|
|
551
|
+
if (mode === 'online') {
|
|
552
|
+
for (const id of markerInvariants) known.add(id);
|
|
553
|
+
}
|
|
554
|
+
const unknown = activeRefEntry.invariants.filter((id) => !known.has(id));
|
|
555
|
+
if (unknown.length > 0) {
|
|
556
|
+
return notOk(
|
|
557
|
+
mapMigrationToolsError(
|
|
558
|
+
errorUnknownInvariant({
|
|
559
|
+
...ifDefined('refName', activeRefName),
|
|
560
|
+
unknown,
|
|
561
|
+
declared: [...declared].sort(),
|
|
562
|
+
}),
|
|
563
|
+
),
|
|
564
|
+
);
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
|
|
523
568
|
// Marker exists but is not in the migration graph and doesn't match the
|
|
524
569
|
// contract hash. The DB is at an unknown state relative to the graph.
|
|
525
570
|
// Bail out early with a clear diagnostic instead of rendering a confusing
|
|
@@ -565,6 +610,7 @@ async function executeMigrationStatusCommand(
|
|
|
565
610
|
summary: `${bundles.length} migration(s) on disk`,
|
|
566
611
|
diagnostics,
|
|
567
612
|
markerHash,
|
|
613
|
+
requiredInvariants,
|
|
568
614
|
...(statusRefs.length > 0 ? { refs: statusRefs } : {}),
|
|
569
615
|
});
|
|
570
616
|
}
|
|
@@ -605,6 +651,7 @@ async function executeMigrationStatusCommand(
|
|
|
605
651
|
summary: `${bundles.length} migration(s) on disk`,
|
|
606
652
|
diagnostics,
|
|
607
653
|
...ifDefined('markerHash', markerHash),
|
|
654
|
+
requiredInvariants,
|
|
608
655
|
...(statusRefs.length > 0 ? { refs: statusRefs } : {}),
|
|
609
656
|
graph,
|
|
610
657
|
bundles,
|
|
@@ -629,14 +676,39 @@ async function executeMigrationStatusCommand(
|
|
|
629
676
|
const pendingCount = edgeStatuses.filter((e) => e.status === 'pending').length;
|
|
630
677
|
const appliedCount = edgeStatuses.filter((e) => e.status === 'applied').length;
|
|
631
678
|
|
|
679
|
+
let appliedInvariants: readonly string[] | undefined;
|
|
680
|
+
let missingInvariants: readonly string[] | undefined;
|
|
681
|
+
let effectiveRequired = new Set<string>();
|
|
682
|
+
if (mode === 'online') {
|
|
683
|
+
// Mirrors `migration-apply.ts`: compute `effectiveRequired = required −
|
|
684
|
+
// marker.invariants` directly, then derive the display fields from it.
|
|
685
|
+
// `appliedInvariants` is the intersection (`required ∩ marker`), which
|
|
686
|
+
// is what JSON consumers see for the active ref; the unfiltered set
|
|
687
|
+
// lives on `marker.invariants`.
|
|
688
|
+
const markerSet = new Set(markerInvariants);
|
|
689
|
+
effectiveRequired = new Set(requiredInvariants.filter((id) => !markerSet.has(id)));
|
|
690
|
+
appliedInvariants = requiredInvariants.filter((id) => markerSet.has(id));
|
|
691
|
+
missingInvariants = [...effectiveRequired].sort();
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
// The marker can match the structural target while still missing required
|
|
695
|
+
// invariants — for example, a self-edge that provides X, applied via a ref
|
|
696
|
+
// declaring X. `pendingCount` (structural) says zero in that case but
|
|
697
|
+
// `effectiveRequired` is non-empty, so up-to-date messaging would mislead.
|
|
698
|
+
const hasInvariantWork = effectiveRequired.size > 0;
|
|
699
|
+
const missingList = [...effectiveRequired].sort().join(', ');
|
|
700
|
+
|
|
632
701
|
let summary: string;
|
|
633
702
|
if (mode === 'online') {
|
|
634
703
|
if (markerHash !== undefined && !graph.nodes.has(markerHash) && markerHash === contractHash) {
|
|
635
704
|
summary = `${bundles.length} migration(s) on disk`;
|
|
636
705
|
} else if (activeRefHash && markerHash !== undefined) {
|
|
637
|
-
|
|
638
|
-
|
|
706
|
+
const distance = summarizeRefDistance(graph, markerHash, activeRefHash, activeRefName!);
|
|
707
|
+
summary = hasInvariantWork ? `${distance} — missing invariant(s): ${missingList}` : distance;
|
|
708
|
+
} else if (pendingCount === 0 && !hasInvariantWork) {
|
|
639
709
|
summary = `Database is up to date (${appliedCount} migration${appliedCount !== 1 ? 's' : ''} applied)`;
|
|
710
|
+
} else if (pendingCount === 0 && hasInvariantWork) {
|
|
711
|
+
summary = `Missing invariant(s): ${missingList} — run 'prisma-next migration apply --ref ${activeRefName ?? '<ref>'}' to apply`;
|
|
640
712
|
} else if (markerHash === undefined) {
|
|
641
713
|
summary = `${pendingCount} pending migration(s) — database has no marker`;
|
|
642
714
|
} else {
|
|
@@ -646,6 +718,37 @@ async function executeMigrationStatusCommand(
|
|
|
646
718
|
summary = `${entries.length} migration(s) on disk`;
|
|
647
719
|
}
|
|
648
720
|
|
|
721
|
+
let pathDecision: MigrationStatusResult['pathDecision'];
|
|
722
|
+
let routingUnreachable = false;
|
|
723
|
+
if (mode === 'online') {
|
|
724
|
+
const originHash = markerHash ?? EMPTY_CONTRACT_HASH;
|
|
725
|
+
const outcome = findPathWithDecision(graph, originHash, targetHash, {
|
|
726
|
+
...ifDefined('refName', activeRefName),
|
|
727
|
+
required: effectiveRequired,
|
|
728
|
+
});
|
|
729
|
+
if (outcome.kind === 'ok') {
|
|
730
|
+
pathDecision = toPathDecisionResult(outcome.decision);
|
|
731
|
+
} else if (outcome.kind === 'unsatisfiable') {
|
|
732
|
+
return notOk(
|
|
733
|
+
mapMigrationToolsError(
|
|
734
|
+
errorNoInvariantPath({
|
|
735
|
+
...ifDefined('refName', activeRefName),
|
|
736
|
+
required: [...effectiveRequired].sort(),
|
|
737
|
+
missing: outcome.missing,
|
|
738
|
+
structuralPath: outcome.structuralPath.map(toStructuralEdge),
|
|
739
|
+
}),
|
|
740
|
+
),
|
|
741
|
+
);
|
|
742
|
+
} else {
|
|
743
|
+
// outcome.kind === 'unreachable' — origin (marker) has no structural
|
|
744
|
+
// path to the active target. `pendingCount` and `hasInvariantWork`
|
|
745
|
+
// both report zero in this case, but emitting MIGRATION.UP_TO_DATE
|
|
746
|
+
// would be wrong: the database simply cannot reach the requested
|
|
747
|
+
// ref/contract from its current state. Suppress UP_TO_DATE below.
|
|
748
|
+
routingUnreachable = true;
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
|
|
649
752
|
if (mode === 'online') {
|
|
650
753
|
if (markerHash !== undefined && !graph.nodes.has(markerHash) && markerHash === contractHash) {
|
|
651
754
|
diagnostics.push({
|
|
@@ -662,7 +765,16 @@ async function executeMigrationStatusCommand(
|
|
|
662
765
|
message: `${pendingCount} migration(s) pending`,
|
|
663
766
|
hints: ["Run 'prisma-next migration apply' to apply pending migrations"],
|
|
664
767
|
});
|
|
665
|
-
} else {
|
|
768
|
+
} else if (hasInvariantWork) {
|
|
769
|
+
diagnostics.push({
|
|
770
|
+
code: 'MIGRATION.INVARIANTS_PENDING',
|
|
771
|
+
severity: 'info',
|
|
772
|
+
message: `Missing required invariant(s): ${missingList}`,
|
|
773
|
+
hints: [
|
|
774
|
+
`Run 'prisma-next migration apply --ref ${activeRefName ?? '<ref>'}' to apply a path that covers the required invariants`,
|
|
775
|
+
],
|
|
776
|
+
});
|
|
777
|
+
} else if (!routingUnreachable) {
|
|
666
778
|
diagnostics.push({
|
|
667
779
|
code: 'MIGRATION.UP_TO_DATE',
|
|
668
780
|
severity: 'info',
|
|
@@ -672,14 +784,6 @@ async function executeMigrationStatusCommand(
|
|
|
672
784
|
}
|
|
673
785
|
}
|
|
674
786
|
|
|
675
|
-
let pathDecision: MigrationStatusResult['pathDecision'];
|
|
676
|
-
if (mode === 'online' && markerHash !== undefined) {
|
|
677
|
-
const decision = findPathWithDecision(graph, markerHash, targetHash, activeRefName);
|
|
678
|
-
if (decision) {
|
|
679
|
-
pathDecision = toPathDecisionResult(decision);
|
|
680
|
-
}
|
|
681
|
-
}
|
|
682
|
-
|
|
683
787
|
const result: MigrationStatusResult = {
|
|
684
788
|
ok: true,
|
|
685
789
|
mode,
|
|
@@ -689,6 +793,9 @@ async function executeMigrationStatusCommand(
|
|
|
689
793
|
summary,
|
|
690
794
|
diagnostics,
|
|
691
795
|
...ifDefined('markerHash', markerHash),
|
|
796
|
+
requiredInvariants,
|
|
797
|
+
...ifDefined('appliedInvariants', appliedInvariants),
|
|
798
|
+
...ifDefined('missingInvariants', missingInvariants),
|
|
692
799
|
...(statusRefs.length > 0 ? { refs: statusRefs } : {}),
|
|
693
800
|
...ifDefined('pathDecision', pathDecision),
|
|
694
801
|
graph,
|
|
@@ -729,13 +836,19 @@ export function createMigrationStatusCommand(): Command {
|
|
|
729
836
|
|
|
730
837
|
const exitCode = handleResult(result, flags, ui, (statusResult) => {
|
|
731
838
|
if (flags.json) {
|
|
839
|
+
// Strip non-JSON-shape fields before emitting. These belong to
|
|
840
|
+
// the in-memory result so the human renderer can avoid
|
|
841
|
+
// recomputing them, but they would either bloat the wire format
|
|
842
|
+
// (graph, bundles, edgeStatuses) or expose internals
|
|
843
|
+
// (activeRefHash, activeRefName, diverged) that consumers should
|
|
844
|
+
// read off `pathDecision` / `refs` instead.
|
|
732
845
|
const {
|
|
733
|
-
graph:
|
|
734
|
-
bundles:
|
|
735
|
-
edgeStatuses:
|
|
736
|
-
activeRefHash:
|
|
737
|
-
activeRefName:
|
|
738
|
-
diverged:
|
|
846
|
+
graph: _graph,
|
|
847
|
+
bundles: _bundles,
|
|
848
|
+
edgeStatuses: _edgeStatuses,
|
|
849
|
+
activeRefHash: _activeRefHash,
|
|
850
|
+
activeRefName: _activeRefName,
|
|
851
|
+
diverged: _diverged,
|
|
739
852
|
...jsonResult
|
|
740
853
|
} = statusResult;
|
|
741
854
|
ui.output(JSON.stringify(jsonResult, null, 2));
|
|
@@ -794,7 +907,7 @@ function formatLegend(colorize: boolean): string {
|
|
|
794
907
|
return c(dim, parts.join(' '));
|
|
795
908
|
}
|
|
796
909
|
|
|
797
|
-
function formatStatusSummary(result: MigrationStatusResult, colorize: boolean): string {
|
|
910
|
+
export function formatStatusSummary(result: MigrationStatusResult, colorize: boolean): string {
|
|
798
911
|
const c = (fn: (s: string) => string, s: string) => (colorize ? fn(s) : s);
|
|
799
912
|
const lines: string[] = [];
|
|
800
913
|
|
|
@@ -802,11 +915,16 @@ function formatStatusSummary(result: MigrationStatusResult, colorize: boolean):
|
|
|
802
915
|
const pendingCount = result.migrations.filter((e) => e.status === 'pending').length;
|
|
803
916
|
|
|
804
917
|
const hasWarnings = result.diagnostics?.some((d) => d.severity === 'warn') ?? false;
|
|
918
|
+
// INVARIANTS_PENDING is filed at severity 'info' (per ADR 208) so the
|
|
919
|
+
// warn-severity check above doesn't see it. It still represents pending
|
|
920
|
+
// work, so it must promote the summary off the success icon.
|
|
921
|
+
const hasInvariantPending =
|
|
922
|
+
result.diagnostics?.some((d) => d.code === 'MIGRATION.INVARIANTS_PENDING') ?? false;
|
|
805
923
|
|
|
806
924
|
if (result.mode === 'online') {
|
|
807
925
|
if (hasUnknown || hasWarnings) {
|
|
808
926
|
lines.push(`${c(yellow, '⚠')} ${result.summary}`);
|
|
809
|
-
} else if (pendingCount === 0) {
|
|
927
|
+
} else if (pendingCount === 0 && !hasInvariantPending) {
|
|
810
928
|
lines.push(`${c(cyan, '✔')} ${result.summary}`);
|
|
811
929
|
} else {
|
|
812
930
|
lines.push(`${c(yellow, '⧗')} ${result.summary}`);
|
|
@@ -815,6 +933,15 @@ function formatStatusSummary(result: MigrationStatusResult, colorize: boolean):
|
|
|
815
933
|
lines.push(result.summary);
|
|
816
934
|
}
|
|
817
935
|
|
|
936
|
+
if (result.requiredInvariants.length > 0) {
|
|
937
|
+
if (result.appliedInvariants !== undefined && result.missingInvariants !== undefined) {
|
|
938
|
+
lines.push(`${c(dim, 'applied ')}${formatInvariantList(result.appliedInvariants)}`);
|
|
939
|
+
lines.push(`${c(dim, 'missing ')}${formatInvariantList(result.missingInvariants)}`);
|
|
940
|
+
} else {
|
|
941
|
+
lines.push(`${c(dim, 'applied ')}(unknown — connect a database to evaluate)`);
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
|
|
818
945
|
const warnings = result.diagnostics?.filter((d) => d.severity === 'warn') ?? [];
|
|
819
946
|
for (const diag of warnings) {
|
|
820
947
|
lines.push(`${c(yellow, '⚠')} ${diag.message}`);
|
|
@@ -826,6 +953,10 @@ function formatStatusSummary(result: MigrationStatusResult, colorize: boolean):
|
|
|
826
953
|
return lines.join('\n');
|
|
827
954
|
}
|
|
828
955
|
|
|
956
|
+
function formatInvariantList(ids: readonly string[]): string {
|
|
957
|
+
return ids.length === 0 ? '(none)' : ids.join(', ');
|
|
958
|
+
}
|
|
959
|
+
|
|
829
960
|
function summarizeRefDistance(
|
|
830
961
|
graph: MigrationGraph,
|
|
831
962
|
markerHash: string,
|
|
@@ -57,7 +57,6 @@ export function finalizeConfig(config: PrismaNextConfig, configDir: string): Pri
|
|
|
57
57
|
if (!config.contract) {
|
|
58
58
|
return config;
|
|
59
59
|
}
|
|
60
|
-
|
|
61
60
|
const contract = normalizeContractConfig(config.contract);
|
|
62
61
|
const source = finalizeContractSource(contract.source, configDir);
|
|
63
62
|
const output = resolve(configDir, contract.output);
|
|
@@ -6,6 +6,8 @@ import type {
|
|
|
6
6
|
ControlFamilyInstance,
|
|
7
7
|
ControlStack,
|
|
8
8
|
CoreSchemaView,
|
|
9
|
+
MigrationPlanOperation,
|
|
10
|
+
OperationPreview,
|
|
9
11
|
SignDatabaseResult,
|
|
10
12
|
VerifyDatabaseResult,
|
|
11
13
|
VerifyDatabaseSchemaResult,
|
|
@@ -13,8 +15,11 @@ import type {
|
|
|
13
15
|
import {
|
|
14
16
|
createControlStack,
|
|
15
17
|
hasMigrations,
|
|
18
|
+
hasOperationPreview,
|
|
19
|
+
hasPslContractInfer,
|
|
16
20
|
hasSchemaView,
|
|
17
21
|
} from '@prisma-next/framework-components/control';
|
|
22
|
+
import type { PslDocumentAst } from '@prisma-next/framework-components/psl-ast';
|
|
18
23
|
import { ifDefined } from '@prisma-next/utils/defined';
|
|
19
24
|
import { notOk, ok } from '@prisma-next/utils/result';
|
|
20
25
|
import { assertFrameworkComponentsCompatible } from '../utils/framework-components';
|
|
@@ -469,6 +474,22 @@ class ControlClientImpl implements ControlClient {
|
|
|
469
474
|
return undefined;
|
|
470
475
|
}
|
|
471
476
|
|
|
477
|
+
inferPslContract(schemaIR: unknown): PslDocumentAst | undefined {
|
|
478
|
+
this.init();
|
|
479
|
+
if (this.familyInstance && hasPslContractInfer(this.familyInstance)) {
|
|
480
|
+
return this.familyInstance.inferPslContract(schemaIR);
|
|
481
|
+
}
|
|
482
|
+
return undefined;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
toOperationPreview(operations: readonly MigrationPlanOperation[]): OperationPreview | undefined {
|
|
486
|
+
this.init();
|
|
487
|
+
if (this.familyInstance && hasOperationPreview(this.familyInstance)) {
|
|
488
|
+
return this.familyInstance.toOperationPreview(operations);
|
|
489
|
+
}
|
|
490
|
+
return undefined;
|
|
491
|
+
}
|
|
492
|
+
|
|
472
493
|
async emit(options: EmitOptions): Promise<EmitResult> {
|
|
473
494
|
const { onProgress, contractConfig } = options;
|
|
474
495
|
|