@prisma-next/cli 0.12.0 → 0.13.0-dev.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/cli.mjs +180 -163
- package/dist/cli.mjs.map +1 -1
- package/dist/{client-KgJorIvG.mjs → client-CJzuo5wX.mjs} +222 -107
- package/dist/client-CJzuo5wX.mjs.map +1 -0
- package/dist/{command-helpers-Bbw1GbwL.mjs → command-helpers-DGMvGBeX.mjs} +318 -25
- package/dist/command-helpers-DGMvGBeX.mjs.map +1 -0
- 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.map +1 -1
- package/dist/commands/contract-infer.mjs +1 -1
- package/dist/commands/db-init.d.mts.map +1 -1
- package/dist/commands/db-init.mjs +4 -5
- package/dist/commands/db-init.mjs.map +1 -1
- package/dist/commands/db-schema.d.mts.map +1 -1
- package/dist/commands/db-schema.mjs +3 -3
- package/dist/commands/db-schema.mjs.map +1 -1
- package/dist/commands/db-sign.d.mts.map +1 -1
- package/dist/commands/db-sign.mjs +6 -6
- package/dist/commands/db-sign.mjs.map +1 -1
- package/dist/commands/db-update.d.mts.map +1 -1
- package/dist/commands/db-update.mjs +10 -7
- package/dist/commands/db-update.mjs.map +1 -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 +37 -3
- package/dist/commands/migrate.d.mts.map +1 -1
- package/dist/commands/migrate.mjs +298 -12
- package/dist/commands/migrate.mjs.map +1 -1
- package/dist/commands/migration-check.d.mts +55 -13
- package/dist/commands/migration-check.d.mts.map +1 -1
- package/dist/commands/migration-check.mjs +3 -2
- package/dist/commands/migration-graph.d.mts +17 -8
- package/dist/commands/migration-graph.d.mts.map +1 -1
- package/dist/commands/migration-graph.mjs +185 -2
- package/dist/commands/migration-graph.mjs.map +1 -0
- package/dist/commands/migration-list.d.mts +26 -27
- package/dist/commands/migration-list.d.mts.map +1 -1
- package/dist/commands/migration-list.mjs +2 -190
- package/dist/commands/migration-log.d.mts +9 -19
- package/dist/commands/migration-log.d.mts.map +1 -1
- package/dist/commands/migration-log.mjs +1 -137
- package/dist/commands/migration-new.d.mts.map +1 -1
- package/dist/commands/migration-new.mjs +6 -5
- package/dist/commands/migration-new.mjs.map +1 -1
- package/dist/commands/migration-plan.d.mts +1 -1
- package/dist/commands/migration-plan.d.mts.map +1 -1
- package/dist/commands/migration-plan.mjs +1 -1
- package/dist/commands/migration-show.d.mts +17 -21
- package/dist/commands/migration-show.d.mts.map +1 -1
- package/dist/commands/migration-show.mjs +24 -36
- package/dist/commands/migration-show.mjs.map +1 -1
- package/dist/commands/migration-status.d.mts +42 -144
- package/dist/commands/migration-status.d.mts.map +1 -1
- package/dist/commands/migration-status.mjs +3 -759
- package/dist/commands/ref.d.mts +1 -1
- package/dist/commands/ref.d.mts.map +1 -1
- package/dist/commands/ref.mjs +4 -4
- package/dist/commands/ref.mjs.map +1 -1
- package/dist/commands/telemetry/index.d.mts +7 -0
- package/dist/commands/telemetry/index.d.mts.map +1 -0
- package/dist/commands/telemetry/index.mjs +2 -0
- package/dist/{config-loader-B6sJjXTv.mjs → config-loader-p9JMrekQ.mjs} +1 -1
- package/dist/{config-loader-B6sJjXTv.mjs.map → config-loader-p9JMrekQ.mjs.map} +1 -1
- package/dist/config-loader.mjs +1 -1
- package/dist/{contract-at-errors-BxP-TOMl.mjs → contract-at-errors-CFXsstzm.mjs} +2 -2
- package/dist/{contract-at-errors-BxP-TOMl.mjs.map → contract-at-errors-CFXsstzm.mjs.map} +1 -1
- package/dist/{contract-emit-DxcGl4Uq.mjs → contract-emit-B_qriF8B.mjs} +5 -5
- package/dist/{contract-emit-DxcGl4Uq.mjs.map → contract-emit-B_qriF8B.mjs.map} +1 -1
- package/dist/{contract-emit-D-4jrNve.mjs → contract-emit-C8HmtboH.mjs} +12 -7
- package/dist/contract-emit-C8HmtboH.mjs.map +1 -0
- package/dist/{contract-enrichment-a0V5Y_mL.mjs → contract-enrichment-gn9sWbPw.mjs} +1 -1
- package/dist/{contract-enrichment-a0V5Y_mL.mjs.map → contract-enrichment-gn9sWbPw.mjs.map} +1 -1
- package/dist/{contract-infer-D8uEbJuu.mjs → contract-infer-BYT_ra_U.mjs} +5 -5
- package/dist/contract-infer-BYT_ra_U.mjs.map +1 -0
- package/dist/{contract-space-aggregate-loader-DvZwdkrr.mjs → contract-space-aggregate-loader-ClI1KN6d.mjs} +5 -5
- package/dist/{contract-space-aggregate-loader-DvZwdkrr.mjs.map → contract-space-aggregate-loader-ClI1KN6d.mjs.map} +1 -1
- package/dist/{db-verify-v_vUKXTU.mjs → db-verify-C24FKhb7.mjs} +6 -6
- package/dist/{db-verify-v_vUKXTU.mjs.map → db-verify-C24FKhb7.mjs.map} +1 -1
- package/dist/exports/control-api.d.mts +5 -3
- package/dist/exports/control-api.d.mts.map +1 -1
- package/dist/exports/control-api.mjs +3 -3
- package/dist/exports/index.mjs +1 -1
- package/dist/exports/index.mjs.map +1 -1
- package/dist/exports/init-output.d.mts +1 -3
- package/dist/exports/init-output.d.mts.map +1 -1
- package/dist/exports/init-output.mjs +1 -1
- package/dist/{extension-pack-inputs-IDvjRCi3.mjs → extension-pack-inputs-1ySHqxKG.mjs} +1 -1
- package/dist/{extension-pack-inputs-IDvjRCi3.mjs.map → extension-pack-inputs-1ySHqxKG.mjs.map} +1 -1
- package/dist/{framework-components-fYXjz_in.mjs → framework-components-YVQHhPH7.mjs} +2 -2
- package/dist/{framework-components-fYXjz_in.mjs.map → framework-components-YVQHhPH7.mjs.map} +1 -1
- package/dist/{global-flags-DEHjV8_s.d.mts → global-flags-BpoOYtNZ.d.mts} +1 -1
- package/dist/{global-flags-DEHjV8_s.d.mts.map → global-flags-BpoOYtNZ.d.mts.map} +1 -1
- package/dist/{init-Cv9UzWL5.mjs → init-0HwB-Vh8.mjs} +5 -58
- package/dist/init-0HwB-Vh8.mjs.map +1 -0
- package/dist/{inspect-live-schema-C6ohV_oQ.mjs → inspect-live-schema-DF6IwcDl.mjs} +7 -5
- package/dist/inspect-live-schema-DF6IwcDl.mjs.map +1 -0
- package/dist/migration-check-VwM8xCZV.mjs +574 -0
- package/dist/migration-check-VwM8xCZV.mjs.map +1 -0
- package/dist/migration-cli.mjs +1 -1
- package/dist/migration-cli.mjs.map +1 -1
- package/dist/{migration-command-scaffold-CjvwO6at.mjs → migration-command-scaffold-DA-Lhx6o.mjs} +5 -5
- package/dist/{migration-command-scaffold-CjvwO6at.mjs.map → migration-command-scaffold-DA-Lhx6o.mjs.map} +1 -1
- package/dist/migration-graph-command-render-CEez7YUK.mjs +1960 -0
- package/dist/migration-graph-command-render-CEez7YUK.mjs.map +1 -0
- package/dist/migration-list-DlJJ_38Z.mjs +230 -0
- package/dist/migration-list-DlJJ_38Z.mjs.map +1 -0
- package/dist/migration-log-CG0qQAFm.mjs +222 -0
- package/dist/migration-log-CG0qQAFm.mjs.map +1 -0
- package/dist/migration-path-target-Ce6OZImp.mjs +38 -0
- package/dist/migration-path-target-Ce6OZImp.mjs.map +1 -0
- package/dist/{migration-plan-9DJ7q7_z.mjs → migration-plan-z5Ing-TD.mjs} +9 -8
- package/dist/migration-plan-z5Ing-TD.mjs.map +1 -0
- package/dist/migration-status-CD-LC2Ip.mjs +447 -0
- package/dist/migration-status-CD-LC2Ip.mjs.map +1 -0
- package/dist/{output-B60Gw5fu.mjs → output-mEQ74_nd.mjs} +1 -1
- package/dist/{output-B60Gw5fu.mjs.map → output-mEQ74_nd.mjs.map} +1 -1
- package/dist/{progress-adapter-C644QK8l.mjs → progress-adapter-CjAeTxY_.mjs} +1 -1
- package/dist/{progress-adapter-C644QK8l.mjs.map → progress-adapter-CjAeTxY_.mjs.map} +1 -1
- package/dist/{ref-advancement-DUZqsue6.mjs → ref-advancement-BkXlikCA.mjs} +1 -1
- package/dist/{ref-advancement-DUZqsue6.mjs.map → ref-advancement-BkXlikCA.mjs.map} +1 -1
- package/dist/schemas-CeGMYFYX.d.mts +191 -0
- package/dist/schemas-CeGMYFYX.d.mts.map +1 -0
- package/dist/schemas-KhXMzNA_.mjs +112 -0
- package/dist/schemas-KhXMzNA_.mjs.map +1 -0
- package/dist/telemetry-BIM4beEO.mjs +122 -0
- package/dist/telemetry-BIM4beEO.mjs.map +1 -0
- package/dist/{terminal-ui-5Y6mrg93.d.mts → terminal-ui-DGRNFWna.d.mts} +1 -1
- package/dist/terminal-ui-DGRNFWna.d.mts.map +1 -0
- package/dist/{types-Dt_SfqFm.d.mts → types-C_tYiJYx.d.mts} +53 -31
- package/dist/types-C_tYiJYx.d.mts.map +1 -0
- package/dist/{verify-DCA9Sldu.mjs → verify-DcOYZ1tH.mjs} +2 -2
- package/dist/{verify-DCA9Sldu.mjs.map → verify-DcOYZ1tH.mjs.map} +1 -1
- package/package.json +26 -22
- package/src/cli.ts +5 -0
- package/src/commands/contract-infer.ts +2 -2
- package/src/commands/db-update.ts +7 -1
- package/src/commands/init/index.ts +6 -35
- package/src/commands/init/init.ts +1 -14
- package/src/commands/init/inputs.ts +0 -75
- package/src/commands/inspect-live-schema.ts +10 -0
- package/src/commands/json/schemas.ts +195 -0
- package/src/commands/migrate.ts +527 -8
- package/src/commands/migration-check.ts +469 -134
- package/src/commands/migration-graph.ts +164 -91
- package/src/commands/migration-list.ts +72 -39
- package/src/commands/migration-log.ts +52 -102
- package/src/commands/migration-new.ts +2 -1
- package/src/commands/migration-plan.ts +2 -1
- package/src/commands/migration-show.ts +31 -66
- package/src/commands/migration-status-overlay.ts +61 -0
- package/src/commands/migration-status.ts +458 -1066
- package/src/commands/telemetry/index.ts +107 -0
- package/src/commands/telemetry/status.ts +67 -0
- package/src/control-api/client.ts +70 -9
- package/src/control-api/operations/contract-emit.ts +22 -2
- package/src/control-api/operations/db-init.ts +6 -3
- package/src/control-api/operations/{db-apply.ts → db-run.ts} +55 -14
- package/src/control-api/operations/db-update.ts +7 -4
- package/src/control-api/operations/db-verify.ts +15 -5
- package/src/control-api/operations/{migration-apply.ts → migrate.ts} +181 -80
- package/src/control-api/operations/{apply.ts → run-migration.ts} +33 -27
- package/src/control-api/types.ts +56 -29
- package/src/utils/cli-errors.ts +70 -2
- package/src/utils/formatters/errors.ts +11 -0
- package/src/utils/formatters/migration-graph-command-render.ts +239 -0
- package/src/utils/formatters/migration-graph-grid-layout.ts +1134 -0
- package/src/utils/formatters/migration-graph-labels.ts +408 -0
- package/src/utils/formatters/migration-graph-model.ts +103 -0
- package/src/utils/formatters/migration-graph-occlusion-render.ts +258 -0
- package/src/utils/formatters/migration-graph-rows.ts +128 -15
- package/src/utils/formatters/migration-graph-space-render.ts +188 -0
- package/src/utils/formatters/migration-list-data-column.ts +4 -91
- package/src/utils/formatters/migration-list-graph-topology.ts +72 -94
- package/src/utils/formatters/migration-list-render.ts +135 -71
- package/src/utils/formatters/migration-list-styler.ts +46 -5
- package/src/utils/formatters/migration-list-types.ts +5 -21
- package/src/utils/formatters/migration-log-table.ts +205 -0
- package/src/utils/formatters/migrations.ts +33 -11
- package/src/utils/global-flags.ts +35 -0
- package/src/utils/integrity-violation-to-check-failure.ts +28 -19
- package/src/utils/legend.ts +38 -0
- package/src/utils/migration-path-target.ts +60 -0
- package/src/utils/telemetry.ts +68 -32
- package/dist/client-KgJorIvG.mjs.map +0 -1
- package/dist/command-helpers-Bbw1GbwL.mjs.map +0 -1
- package/dist/commands/migration-list.mjs.map +0 -1
- package/dist/commands/migration-log.mjs.map +0 -1
- package/dist/commands/migration-status.mjs.map +0 -1
- package/dist/contract-emit-D-4jrNve.mjs.map +0 -1
- package/dist/contract-infer-D8uEbJuu.mjs.map +0 -1
- package/dist/graph-render-rFAqZujX.mjs +0 -1081
- package/dist/graph-render-rFAqZujX.mjs.map +0 -1
- package/dist/init-Cv9UzWL5.mjs.map +0 -1
- package/dist/inspect-live-schema-C6ohV_oQ.mjs.map +0 -1
- package/dist/migration-check-BiBJoYYW.mjs +0 -341
- package/dist/migration-check-BiBJoYYW.mjs.map +0 -1
- package/dist/migration-graph-D7DVUElV.mjs +0 -1232
- package/dist/migration-graph-D7DVUElV.mjs.map +0 -1
- package/dist/migration-list-styler-BRwF4-gy.mjs +0 -399
- package/dist/migration-list-styler-BRwF4-gy.mjs.map +0 -1
- package/dist/migration-plan-9DJ7q7_z.mjs.map +0 -1
- package/dist/migration-types-D2FW63pr.d.mts +0 -15
- package/dist/migration-types-D2FW63pr.d.mts.map +0 -1
- package/dist/migrations-Cv2jxNNK.mjs +0 -228
- package/dist/migrations-Cv2jxNNK.mjs.map +0 -1
- package/dist/terminal-ui-5Y6mrg93.d.mts.map +0 -1
- package/dist/types-Dt_SfqFm.d.mts.map +0 -1
- package/src/utils/formatters/graph-migration-mapper.ts +0 -235
- package/src/utils/formatters/graph-render.ts +0 -1323
- package/src/utils/formatters/graph-types.ts +0 -120
- package/src/utils/formatters/migration-graph-layout.ts +0 -1119
- package/src/utils/formatters/migration-graph-tree-render.ts +0 -459
|
@@ -1,6 +1,39 @@
|
|
|
1
1
|
import { bold, cyan, cyanBright, dim, green, yellow } from 'colorette';
|
|
2
2
|
import { IDENTITY_MIGRATION_LIST_STYLER, type MigrationListStyler } from './migration-list-render';
|
|
3
3
|
|
|
4
|
+
export type MigrationListStylerWithMarkers = MigrationListStyler & {
|
|
5
|
+
markers(names: readonly string[]): string;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
function hasMarkersFormatter(
|
|
9
|
+
styler: MigrationListStyler,
|
|
10
|
+
): styler is MigrationListStylerWithMarkers {
|
|
11
|
+
return 'markers' in styler && typeof styler.markers === 'function';
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function styleMarkerName(name: string): string {
|
|
15
|
+
return name === CONTRACT_MARKER_NAME ? bold(green(name)) : green(name);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function plainMarkers(names: readonly string[]): string {
|
|
19
|
+
return names.map((name) => `@${name}`).join(' ');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function formatContractNodeOverlays(
|
|
23
|
+
styler: MigrationListStyler,
|
|
24
|
+
markers: readonly string[],
|
|
25
|
+
refs: readonly string[],
|
|
26
|
+
): string {
|
|
27
|
+
const parts: string[] = [];
|
|
28
|
+
if (markers.length > 0) {
|
|
29
|
+
parts.push(hasMarkersFormatter(styler) ? styler.markers(markers) : plainMarkers(markers));
|
|
30
|
+
}
|
|
31
|
+
if (refs.length > 0) {
|
|
32
|
+
parts.push(styler.refs(refs));
|
|
33
|
+
}
|
|
34
|
+
return parts.join(' ');
|
|
35
|
+
}
|
|
36
|
+
|
|
4
37
|
/**
|
|
5
38
|
* The current contract overlay marker. Unlike user refs, this names the user's
|
|
6
39
|
* declared desired state — the implicit base/target for `plan` / `migrate` —
|
|
@@ -10,7 +43,7 @@ import { IDENTITY_MIGRATION_LIST_STYLER, type MigrationListStyler } from './migr
|
|
|
10
43
|
export const CONTRACT_MARKER_NAME = 'contract';
|
|
11
44
|
|
|
12
45
|
function styleRefName(name: string): string {
|
|
13
|
-
return
|
|
46
|
+
return green(name);
|
|
14
47
|
}
|
|
15
48
|
|
|
16
49
|
/**
|
|
@@ -29,17 +62,21 @@ function styleRefName(name: string): string {
|
|
|
29
62
|
* - `glyph` (`→` / `⟲` / `∅`): dim
|
|
30
63
|
* - `lane` (graph gutter lines `│` and fan/join connectors `├─┐` / `├─┘`): dim
|
|
31
64
|
* - `invariants` (`{...}`): yellow
|
|
32
|
-
* - `
|
|
33
|
-
* green-bold (the
|
|
65
|
+
* - `markers` (`@contract @db`): green; the `contract` desired-state marker is
|
|
66
|
+
* green-bold (`db` is plain green); the `@` sigil is applied to each name
|
|
67
|
+
* - `refs` (`(...)`): green (the active ref is bolded separately by the tree styler)
|
|
34
68
|
* - `spaceHeading` (`<spaceId>:`): bold
|
|
35
69
|
* - `summary`: dim
|
|
36
70
|
* - `emptyState`: dim
|
|
37
71
|
*/
|
|
38
72
|
export function createAnsiMigrationListStyler(opts: {
|
|
39
73
|
readonly useColor: boolean;
|
|
40
|
-
}):
|
|
74
|
+
}): MigrationListStylerWithMarkers {
|
|
41
75
|
if (!opts.useColor) {
|
|
42
|
-
return
|
|
76
|
+
return {
|
|
77
|
+
...IDENTITY_MIGRATION_LIST_STYLER,
|
|
78
|
+
markers: plainMarkers,
|
|
79
|
+
};
|
|
43
80
|
}
|
|
44
81
|
return {
|
|
45
82
|
// Kind glyphs stay bright in both flat and graph views; lanes carry the dim gutter.
|
|
@@ -50,6 +87,10 @@ export function createAnsiMigrationListStyler(opts: {
|
|
|
50
87
|
glyph: (text) => dim(text),
|
|
51
88
|
lane: (text) => dim(text),
|
|
52
89
|
invariants: (ids) => yellow(`{${ids.join(', ')}}`),
|
|
90
|
+
markers: (names) => {
|
|
91
|
+
const sigil = green('@');
|
|
92
|
+
return names.map((name) => sigil + styleMarkerName(name)).join(' ');
|
|
93
|
+
},
|
|
53
94
|
refs: (names) => {
|
|
54
95
|
const open = green('(');
|
|
55
96
|
const close = green(')');
|
|
@@ -1,21 +1,5 @@
|
|
|
1
|
-
export
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
readonly operationCount: number;
|
|
7
|
-
readonly createdAt: string;
|
|
8
|
-
readonly refs: readonly string[];
|
|
9
|
-
readonly providedInvariants: readonly string[];
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export interface MigrationSpaceListEntry {
|
|
13
|
-
readonly spaceId: string;
|
|
14
|
-
readonly migrations: readonly MigrationListEntry[];
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export interface MigrationListResult {
|
|
18
|
-
readonly ok: true;
|
|
19
|
-
readonly spaces: readonly MigrationSpaceListEntry[];
|
|
20
|
-
readonly summary: string;
|
|
21
|
-
}
|
|
1
|
+
export type {
|
|
2
|
+
MigrationEntry as MigrationListEntry,
|
|
3
|
+
MigrationListResult,
|
|
4
|
+
MigrationSpaceListEntry,
|
|
5
|
+
} from '../../commands/json/schemas';
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import type { LedgerEntryRecord } from '@prisma-next/contract/types';
|
|
2
|
+
import stringWidth from 'string-width';
|
|
3
|
+
import type { GlyphMode } from '../glyph-mode';
|
|
4
|
+
import {
|
|
5
|
+
abbreviateContractHash,
|
|
6
|
+
migrationListEmptySource,
|
|
7
|
+
migrationListForwardArrow,
|
|
8
|
+
} from './migration-list-data-column';
|
|
9
|
+
import { IDENTITY_MIGRATION_LIST_STYLER, type MigrationListStyler } from './migration-list-render';
|
|
10
|
+
|
|
11
|
+
export type LedgerTimestampMode = 'local' | 'utc' | 'iso';
|
|
12
|
+
|
|
13
|
+
export interface RenderMigrationLogTableOptions {
|
|
14
|
+
readonly utc?: boolean;
|
|
15
|
+
readonly styler?: MigrationListStyler;
|
|
16
|
+
readonly glyphMode?: GlyphMode;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface SerializedLedgerEntryRecord {
|
|
20
|
+
readonly space: string;
|
|
21
|
+
readonly name: string;
|
|
22
|
+
readonly hash: string;
|
|
23
|
+
readonly fromContract: string | null;
|
|
24
|
+
readonly toContract: string;
|
|
25
|
+
readonly appliedAt: string;
|
|
26
|
+
readonly operationCount: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const HEADING_APPLIED_AT = 'Applied at';
|
|
30
|
+
const HEADING_SPACE = 'Space';
|
|
31
|
+
const HEADING_MIGRATION = 'Migration';
|
|
32
|
+
const HEADING_CHANGE = 'Change';
|
|
33
|
+
const HEADING_OPS = 'Ops';
|
|
34
|
+
const COLUMN_SEPARATOR = ' ';
|
|
35
|
+
const DIVIDER_CHAR = '─';
|
|
36
|
+
const ASCII_DIVIDER_CHAR = '-';
|
|
37
|
+
|
|
38
|
+
export function sortLedgerEntries(entries: readonly LedgerEntryRecord[]): LedgerEntryRecord[] {
|
|
39
|
+
return [...entries].sort((left, right) => {
|
|
40
|
+
const timeDiff = left.appliedAt.getTime() - right.appliedAt.getTime();
|
|
41
|
+
if (timeDiff !== 0) {
|
|
42
|
+
return timeDiff;
|
|
43
|
+
}
|
|
44
|
+
const spaceDiff = left.space.localeCompare(right.space);
|
|
45
|
+
if (spaceDiff !== 0) {
|
|
46
|
+
return spaceDiff;
|
|
47
|
+
}
|
|
48
|
+
return left.migrationName.localeCompare(right.migrationName);
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function pad2(value: number): string {
|
|
53
|
+
return String(value).padStart(2, '0');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function formatLedgerAppliedAt(date: Date, mode: LedgerTimestampMode): string {
|
|
57
|
+
if (mode === 'iso') {
|
|
58
|
+
return date.toISOString();
|
|
59
|
+
}
|
|
60
|
+
if (mode === 'utc') {
|
|
61
|
+
return `${date.getUTCFullYear()}-${pad2(date.getUTCMonth() + 1)}-${pad2(date.getUTCDate())} ${pad2(date.getUTCHours())}:${pad2(date.getUTCMinutes())}:${pad2(date.getUTCSeconds())}Z`;
|
|
62
|
+
}
|
|
63
|
+
const offsetMinutes = -date.getTimezoneOffset();
|
|
64
|
+
const sign = offsetMinutes >= 0 ? '+' : '-';
|
|
65
|
+
const absoluteOffset = Math.abs(offsetMinutes);
|
|
66
|
+
const offsetHours = pad2(Math.floor(absoluteOffset / 60));
|
|
67
|
+
const offsetMins = pad2(absoluteOffset % 60);
|
|
68
|
+
return `${date.getFullYear()}-${pad2(date.getMonth() + 1)}-${pad2(date.getDate())} ${pad2(date.getHours())}:${pad2(date.getMinutes())}:${pad2(date.getSeconds())} ${sign}${offsetHours}:${offsetMins}`;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function formatHashEndpoint(hash: string | null, glyphMode: GlyphMode = 'unicode'): string {
|
|
72
|
+
if (hash === null) {
|
|
73
|
+
return migrationListEmptySource(glyphMode);
|
|
74
|
+
}
|
|
75
|
+
return abbreviateContractHash(hash);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function formatHashTransition(
|
|
79
|
+
from: string | null,
|
|
80
|
+
to: string,
|
|
81
|
+
glyphMode: GlyphMode = 'unicode',
|
|
82
|
+
): string {
|
|
83
|
+
return `${formatHashEndpoint(from, glyphMode)} ${migrationListForwardArrow(glyphMode)} ${abbreviateContractHash(to)}`;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function styleHashTransition(
|
|
87
|
+
from: string | null,
|
|
88
|
+
to: string,
|
|
89
|
+
styler: MigrationListStyler,
|
|
90
|
+
glyphMode: GlyphMode = 'unicode',
|
|
91
|
+
): string {
|
|
92
|
+
const fromPart =
|
|
93
|
+
from === null
|
|
94
|
+
? styler.glyph(migrationListEmptySource(glyphMode))
|
|
95
|
+
: styler.sourceHash(abbreviateContractHash(from));
|
|
96
|
+
const arrow = styler.glyph(migrationListForwardArrow(glyphMode));
|
|
97
|
+
const dest = styler.destHash(abbreviateContractHash(to));
|
|
98
|
+
return `${fromPart} ${arrow} ${dest}`;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function padVisible(text: string, targetWidth: number): string {
|
|
102
|
+
const padding = Math.max(0, targetWidth - stringWidth(text));
|
|
103
|
+
return text + ' '.repeat(padding);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function columnWidth(values: readonly string[]): number {
|
|
107
|
+
return values.reduce((max, value) => Math.max(max, stringWidth(value)), 0);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function padDividerCell(valueWidth: number, dividerChar: string): string {
|
|
111
|
+
return dividerChar.repeat(valueWidth + 2);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function padTextCell(value: string, valueWidth: number): string {
|
|
115
|
+
return ` ${padVisible(value, valueWidth)} `;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function padOpsCell(value: string, valueWidth: number): string {
|
|
119
|
+
const padding = Math.max(0, valueWidth - stringWidth(value));
|
|
120
|
+
return ` ${' '.repeat(padding)}${value} `;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export function renderMigrationLogTable(
|
|
124
|
+
entries: readonly LedgerEntryRecord[],
|
|
125
|
+
options: RenderMigrationLogTableOptions = {},
|
|
126
|
+
): string {
|
|
127
|
+
const sorted = sortLedgerEntries(entries);
|
|
128
|
+
if (sorted.length === 0) {
|
|
129
|
+
return '';
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const styler = options.styler ?? IDENTITY_MIGRATION_LIST_STYLER;
|
|
133
|
+
const glyphMode = options.glyphMode ?? 'unicode';
|
|
134
|
+
const dividerChar = glyphMode === 'ascii' ? ASCII_DIVIDER_CHAR : DIVIDER_CHAR;
|
|
135
|
+
const showSpace = new Set(sorted.map((entry) => entry.space)).size > 1;
|
|
136
|
+
const timestampMode: LedgerTimestampMode = options.utc ? 'utc' : 'local';
|
|
137
|
+
const rows = sorted.map((entry) => ({
|
|
138
|
+
appliedAt: formatLedgerAppliedAt(entry.appliedAt, timestampMode),
|
|
139
|
+
space: entry.space,
|
|
140
|
+
migrationName: entry.migrationName,
|
|
141
|
+
transition: formatHashTransition(entry.from, entry.to, glyphMode),
|
|
142
|
+
ops: `${entry.operationCount} ops`,
|
|
143
|
+
from: entry.from,
|
|
144
|
+
to: entry.to,
|
|
145
|
+
}));
|
|
146
|
+
|
|
147
|
+
const appliedAtWidth = columnWidth([HEADING_APPLIED_AT, ...rows.map((row) => row.appliedAt)]);
|
|
148
|
+
const spaceWidth = showSpace ? columnWidth([HEADING_SPACE, ...rows.map((row) => row.space)]) : 0;
|
|
149
|
+
const nameWidth = columnWidth([HEADING_MIGRATION, ...rows.map((row) => row.migrationName)]);
|
|
150
|
+
const transitionWidth = columnWidth([HEADING_CHANGE, ...rows.map((row) => row.transition)]);
|
|
151
|
+
const opsWidth = columnWidth([HEADING_OPS, ...rows.map((row) => row.ops)]);
|
|
152
|
+
|
|
153
|
+
const headingParts = [padTextCell(HEADING_APPLIED_AT, appliedAtWidth)];
|
|
154
|
+
if (showSpace) {
|
|
155
|
+
headingParts.push(padTextCell(HEADING_SPACE, spaceWidth));
|
|
156
|
+
}
|
|
157
|
+
headingParts.push(
|
|
158
|
+
padTextCell(HEADING_MIGRATION, nameWidth),
|
|
159
|
+
padTextCell(HEADING_CHANGE, transitionWidth),
|
|
160
|
+
padOpsCell(HEADING_OPS, opsWidth),
|
|
161
|
+
);
|
|
162
|
+
const heading = headingParts.join(COLUMN_SEPARATOR);
|
|
163
|
+
|
|
164
|
+
const dividerParts = [padDividerCell(appliedAtWidth, dividerChar)];
|
|
165
|
+
if (showSpace) {
|
|
166
|
+
dividerParts.push(padDividerCell(spaceWidth, dividerChar));
|
|
167
|
+
}
|
|
168
|
+
dividerParts.push(
|
|
169
|
+
padDividerCell(nameWidth, dividerChar),
|
|
170
|
+
padDividerCell(transitionWidth, dividerChar),
|
|
171
|
+
padDividerCell(opsWidth, dividerChar),
|
|
172
|
+
);
|
|
173
|
+
const divider = dividerParts.map((cell) => styler.summary(cell)).join(COLUMN_SEPARATOR);
|
|
174
|
+
|
|
175
|
+
const dataRows = rows.map((row) => {
|
|
176
|
+
const parts = [padTextCell(row.appliedAt, appliedAtWidth)];
|
|
177
|
+
if (showSpace) {
|
|
178
|
+
parts.push(padTextCell(row.space, spaceWidth));
|
|
179
|
+
}
|
|
180
|
+
parts.push(
|
|
181
|
+
padTextCell(styler.dirName(row.migrationName), nameWidth),
|
|
182
|
+
padTextCell(styleHashTransition(row.from, row.to, styler, glyphMode), transitionWidth),
|
|
183
|
+
padOpsCell(row.ops, opsWidth),
|
|
184
|
+
);
|
|
185
|
+
return parts.join(COLUMN_SEPARATOR);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
return [heading, divider, ...dataRows].join('\n');
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export function serializeLedgerEntriesForJson(
|
|
192
|
+
entries: readonly LedgerEntryRecord[],
|
|
193
|
+
): SerializedLedgerEntryRecord[] {
|
|
194
|
+
return sortLedgerEntries(entries).map((entry) => ({
|
|
195
|
+
space: entry.space,
|
|
196
|
+
name: entry.migrationName,
|
|
197
|
+
hash: entry.migrationHash,
|
|
198
|
+
fromContract: entry.from,
|
|
199
|
+
toContract: entry.to,
|
|
200
|
+
appliedAt: formatLedgerAppliedAt(entry.appliedAt, 'iso'),
|
|
201
|
+
operationCount: entry.operationCount,
|
|
202
|
+
}));
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export const MIGRATION_LOG_EMPTY_MESSAGE = 'No migrations have been applied to this database.';
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
MigrationPlannerConflict,
|
|
3
|
+
OperationPreview,
|
|
4
|
+
} from '@prisma-next/framework-components/control';
|
|
2
5
|
import { cyan, green, yellow } from 'colorette';
|
|
3
6
|
|
|
4
7
|
import type { PerSpaceExecutionEntry } from '../../control-api/types';
|
|
@@ -85,11 +88,24 @@ export interface MigrationCommandResult {
|
|
|
85
88
|
readonly advancedRef?: { readonly name: string; readonly hash: string } | null;
|
|
86
89
|
readonly plannedAdvanceRef?: { readonly name: string; readonly hash: string } | null;
|
|
87
90
|
readonly summary: string;
|
|
91
|
+
readonly warnings?: readonly MigrationPlannerConflict[];
|
|
88
92
|
readonly timings: {
|
|
89
93
|
readonly total: number;
|
|
90
94
|
};
|
|
91
95
|
}
|
|
92
96
|
|
|
97
|
+
export function formatPlannerWarningsBlock(
|
|
98
|
+
warnings: readonly MigrationPlannerConflict[],
|
|
99
|
+
useColor: boolean,
|
|
100
|
+
): readonly string[] {
|
|
101
|
+
const formatDimText = (text: string) => formatDim(useColor, text);
|
|
102
|
+
const lines: string[] = ['', 'Warnings:'];
|
|
103
|
+
for (const warning of warnings) {
|
|
104
|
+
lines.push(` ${formatDimText(`- ${warning.summary}`)}`);
|
|
105
|
+
}
|
|
106
|
+
return lines;
|
|
107
|
+
}
|
|
108
|
+
|
|
93
109
|
/**
|
|
94
110
|
* Render the shared per-space execution block consumed by the `db init`
|
|
95
111
|
* / `db update` / `migrate` summaries. Always shows: space
|
|
@@ -165,6 +181,10 @@ export function formatMigrationPlanOutput(
|
|
|
165
181
|
lines.push(`${formatGreen('✔')} Planned ${operationCount} operation(s)`);
|
|
166
182
|
}
|
|
167
183
|
|
|
184
|
+
if (result.warnings && result.warnings.length > 0) {
|
|
185
|
+
lines.push(...formatPlannerWarningsBlock(result.warnings, useColor));
|
|
186
|
+
}
|
|
187
|
+
|
|
168
188
|
const formatYellow = createColorFormatter(useColor, yellow);
|
|
169
189
|
|
|
170
190
|
// Per-space breakdown takes precedence over the flat ops tree when
|
|
@@ -315,11 +335,10 @@ export function formatMigrationApplyCommandOutput(
|
|
|
315
335
|
}
|
|
316
336
|
|
|
317
337
|
interface MigrationShowPresent {
|
|
318
|
-
readonly
|
|
319
|
-
readonly
|
|
320
|
-
readonly
|
|
321
|
-
readonly
|
|
322
|
-
readonly migrationHash: string;
|
|
338
|
+
readonly name: string;
|
|
339
|
+
readonly fromContract: string | null;
|
|
340
|
+
readonly toContract: string;
|
|
341
|
+
readonly hash: string;
|
|
323
342
|
readonly createdAt: string;
|
|
324
343
|
readonly operations: readonly {
|
|
325
344
|
readonly id: string;
|
|
@@ -327,7 +346,6 @@ interface MigrationShowPresent {
|
|
|
327
346
|
readonly operationClass: string;
|
|
328
347
|
}[];
|
|
329
348
|
readonly preview: OperationPreview;
|
|
330
|
-
readonly summary: string;
|
|
331
349
|
}
|
|
332
350
|
|
|
333
351
|
interface MigrationShowResult {
|
|
@@ -340,10 +358,10 @@ function formatSpaceShowBlock(space: MigrationShowPresent, useColor: boolean): r
|
|
|
340
358
|
const formatDimText = (text: string) => formatDim(useColor, text);
|
|
341
359
|
|
|
342
360
|
const lines: string[] = [];
|
|
343
|
-
lines.push(`${formatGreen('✔')} ${space.
|
|
344
|
-
lines.push(`${formatDimText(` from: ${space.
|
|
345
|
-
lines.push(`${formatDimText(` to: ${space.
|
|
346
|
-
lines.push(`${formatDimText(`
|
|
361
|
+
lines.push(`${formatGreen('✔')} ${space.name}`);
|
|
362
|
+
lines.push(`${formatDimText(` from: ${space.fromContract ?? '(baseline)'}`)}`);
|
|
363
|
+
lines.push(`${formatDimText(` to: ${space.toContract}`)}`);
|
|
364
|
+
lines.push(`${formatDimText(` hash: ${space.hash}`)}`);
|
|
347
365
|
lines.push(`${formatDimText(` created: ${space.createdAt}`)}`);
|
|
348
366
|
|
|
349
367
|
lines.push('');
|
|
@@ -427,6 +445,10 @@ export function formatMigrationApplyOutput(
|
|
|
427
445
|
lines.push(`${formatGreen('✔')} Applied ${executed} operation(s)`);
|
|
428
446
|
}
|
|
429
447
|
|
|
448
|
+
if (result.warnings && result.warnings.length > 0) {
|
|
449
|
+
lines.push(...formatPlannerWarningsBlock(result.warnings, useColor));
|
|
450
|
+
}
|
|
451
|
+
|
|
430
452
|
// Per-space breakdown — replaces the single ambiguous `Signature:`
|
|
431
453
|
// line with a per-space marker + ops listing.
|
|
432
454
|
if (result.perSpace && result.perSpace.length > 0) {
|
|
@@ -180,3 +180,38 @@ export function parseGlobalFlags(options: CommonCommandOptions): GlobalFlags {
|
|
|
180
180
|
|
|
181
181
|
return flags as GlobalFlags;
|
|
182
182
|
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Bridges the two TTY checks (stdout via `flags`, stdin via
|
|
186
|
+
* `process.stdin.isTTY`) into the `canPrompt` boolean the interactive
|
|
187
|
+
* `init` flow consumes.
|
|
188
|
+
*
|
|
189
|
+
* Per the [Style Guide § Interactivity](../../../../../../../docs/CLI%20Style%20Guide.md#interactivity):
|
|
190
|
+
*
|
|
191
|
+
* - `flags.interactive` governs *decoration* (TerminalUI, intro/outro,
|
|
192
|
+
* spinners) and is derived from stdout-TTY by `parseGlobalFlags`,
|
|
193
|
+
* honouring `--interactive` / `--no-interactive`.
|
|
194
|
+
* - Prompting additionally requires a stdin TTY — closing stdin is a
|
|
195
|
+
* common signal in CI / agent environments even when stdout stays
|
|
196
|
+
* attached.
|
|
197
|
+
* - `--interactive` is the explicit override: when the user passes it,
|
|
198
|
+
* we honour it (e.g. testing flows where stdin is stubbed).
|
|
199
|
+
*
|
|
200
|
+
* Single source of truth for the interactive-prompt decision: both the
|
|
201
|
+
* `init` action handler and the preAction telemetry bridge derive
|
|
202
|
+
* prompt-eligibility from this helper so they cannot drift. Lives in
|
|
203
|
+
* `global-flags` (alongside `parseGlobalFlags`) to keep
|
|
204
|
+
* `utils/telemetry` and `commands/init/index` free of an import cycle.
|
|
205
|
+
*
|
|
206
|
+
* Exported so callers and tests can derive the same value without
|
|
207
|
+
* touching `process` globals.
|
|
208
|
+
*/
|
|
209
|
+
export function deriveCanPrompt(opts: {
|
|
210
|
+
readonly flagsInteractive: boolean | undefined;
|
|
211
|
+
readonly optionInteractive: boolean | undefined;
|
|
212
|
+
readonly stdinIsTTY: boolean;
|
|
213
|
+
}): boolean {
|
|
214
|
+
if (opts.optionInteractive === true) return true;
|
|
215
|
+
if (opts.flagsInteractive === false) return false;
|
|
216
|
+
return opts.stdinIsTTY;
|
|
217
|
+
}
|
|
@@ -1,12 +1,8 @@
|
|
|
1
1
|
import type { IntegrityViolation } from '@prisma-next/migration-tools/aggregate';
|
|
2
2
|
import { join, relative } from 'pathe';
|
|
3
|
+
import type { CheckFailure } from '../commands/json/schemas';
|
|
3
4
|
|
|
4
|
-
export
|
|
5
|
-
readonly pnCode: string;
|
|
6
|
-
readonly where: string;
|
|
7
|
-
readonly why: string;
|
|
8
|
-
readonly fix: string;
|
|
9
|
-
}
|
|
5
|
+
export type { CheckFailure } from '../commands/json/schemas';
|
|
10
6
|
|
|
11
7
|
function migrationPathRelative(dirPath: string): string {
|
|
12
8
|
return relative(process.cwd(), dirPath);
|
|
@@ -34,7 +30,8 @@ export function integrityViolationToCheckFailure(
|
|
|
34
30
|
switch (violation.kind) {
|
|
35
31
|
case 'hashMismatch':
|
|
36
32
|
return {
|
|
37
|
-
|
|
33
|
+
space: violation.spaceId,
|
|
34
|
+
code: 'PN-MIG-CHECK-001',
|
|
38
35
|
where: migrationFileRelative(
|
|
39
36
|
join(migrationsDir, violation.spaceId, violation.dirName),
|
|
40
37
|
'migration.json',
|
|
@@ -44,84 +41,96 @@ export function integrityViolationToCheckFailure(
|
|
|
44
41
|
};
|
|
45
42
|
case 'providedInvariantsMismatch':
|
|
46
43
|
return {
|
|
47
|
-
|
|
44
|
+
space: violation.spaceId,
|
|
45
|
+
code: 'PN-MIG-CHECK-002',
|
|
48
46
|
where: packageRelative(violation.spaceId, violation.dirName),
|
|
49
47
|
why: `Migration "${violation.dirName}" providedInvariants in migration.json disagrees with ops.json.`,
|
|
50
48
|
fix: 'Re-emit the migration package so migration.json and ops.json agree.',
|
|
51
49
|
};
|
|
52
50
|
case 'packageUnloadable':
|
|
53
51
|
return {
|
|
54
|
-
|
|
52
|
+
space: violation.spaceId,
|
|
53
|
+
code: 'PN-MIG-CHECK-002',
|
|
55
54
|
where: packageRelative(violation.spaceId, violation.dirName),
|
|
56
55
|
why: `Migration "${violation.dirName}" could not be loaded: ${violation.detail}`,
|
|
57
56
|
fix: 'Re-emit the migration package or restore from version control.',
|
|
58
57
|
};
|
|
59
58
|
case 'sameSourceAndTarget':
|
|
60
59
|
return {
|
|
61
|
-
|
|
60
|
+
space: violation.spaceId,
|
|
61
|
+
code: 'PN-MIG-CHECK-007',
|
|
62
62
|
where: packageRelative(violation.spaceId, violation.dirName),
|
|
63
63
|
why: `Migration "${violation.dirName}" in space "${violation.spaceId}" has source equal to target (${violation.hash}) with no data invariant — a true no-op self-edge.`,
|
|
64
64
|
fix: 'Add a data operation if this self-edge was meant to carry a data invariant, or delete the migration if it is a true no-op.',
|
|
65
65
|
};
|
|
66
66
|
case 'orphanSpaceDir':
|
|
67
67
|
return {
|
|
68
|
-
|
|
68
|
+
space: violation.spaceId,
|
|
69
|
+
code: 'PN-MIG-CHECK-008',
|
|
69
70
|
where: spaceRelative(violation.spaceId),
|
|
70
71
|
why: `Contract-space directory "${violation.spaceId}" exists on disk but no extension declares it.`,
|
|
71
72
|
fix: 'Remove the orphan directory, or declare the extension in `extensionPacks`.',
|
|
72
73
|
};
|
|
73
74
|
case 'declaredButUnmigrated':
|
|
74
75
|
return {
|
|
75
|
-
|
|
76
|
+
space: violation.spaceId,
|
|
77
|
+
code: 'PN-MIG-CHECK-009',
|
|
76
78
|
where: spaceRelative(violation.spaceId),
|
|
77
79
|
why: `Extension "${violation.spaceId}" is declared in \`extensionPacks\` but has no on-disk migrations directory.`,
|
|
78
80
|
fix: 'Re-emit the extension contract-space artefacts with `prisma-next contract emit` and migration planning, or remove the extension from `extensionPacks` if it is unused.',
|
|
79
81
|
};
|
|
80
82
|
case 'headRefMissing':
|
|
81
83
|
return {
|
|
82
|
-
|
|
84
|
+
space: violation.spaceId,
|
|
85
|
+
code: 'PN-MIG-CHECK-010',
|
|
83
86
|
where: refRelative(violation.spaceId, 'head'),
|
|
84
87
|
why: `Head ref \`refs/head.json\` is missing for contract space "${violation.spaceId}".`,
|
|
85
88
|
fix: 'Re-emit the contract-space migrations and head ref artefacts, or restore `refs/head.json` from version control.',
|
|
86
89
|
};
|
|
87
90
|
case 'headRefNotInGraph':
|
|
88
91
|
return {
|
|
89
|
-
|
|
92
|
+
space: violation.spaceId,
|
|
93
|
+
code: 'PN-MIG-CHECK-011',
|
|
90
94
|
where: refRelative(violation.spaceId, 'head'),
|
|
91
95
|
why: `Head ref ${violation.hash} for contract space "${violation.spaceId}" is not present in its migration graph.`,
|
|
92
96
|
fix: 'Re-emit the contract space migrations, or restore the missing migration package.',
|
|
93
97
|
};
|
|
94
98
|
case 'refUnreadable':
|
|
95
99
|
return {
|
|
96
|
-
|
|
100
|
+
space: violation.spaceId,
|
|
101
|
+
code: 'PN-MIG-CHECK-012',
|
|
97
102
|
where: refRelative(violation.spaceId, violation.refName),
|
|
98
103
|
why: `Ref "${violation.refName}" for contract space "${violation.spaceId}" is unreadable: ${violation.detail}`,
|
|
99
104
|
fix: 'Repair or remove the corrupt ref file.',
|
|
100
105
|
};
|
|
101
106
|
case 'targetMismatch':
|
|
102
107
|
return {
|
|
103
|
-
|
|
108
|
+
space: violation.spaceId,
|
|
109
|
+
code: 'PN-MIG-CHECK-013',
|
|
104
110
|
where: spaceRelative(violation.spaceId),
|
|
105
111
|
why: `Contract space "${violation.spaceId}" targets "${violation.actual}" but the project targets "${violation.expected}".`,
|
|
106
112
|
fix: 'Update the extension to target the configured database, or change the project target.',
|
|
107
113
|
};
|
|
108
114
|
case 'disjointness':
|
|
109
115
|
return {
|
|
110
|
-
|
|
116
|
+
space: 'app',
|
|
117
|
+
code: 'PN-MIG-CHECK-014',
|
|
111
118
|
where: migrationPathRelative(migrationsDir),
|
|
112
119
|
why: `Storage element "${violation.element}" is claimed by multiple contract spaces: ${violation.claimedBy.join(', ')}.`,
|
|
113
120
|
fix: 'Update the contracts so each storage element is owned by exactly one contract space.',
|
|
114
121
|
};
|
|
115
122
|
case 'contractUnreadable':
|
|
116
123
|
return {
|
|
117
|
-
|
|
124
|
+
space: violation.spaceId,
|
|
125
|
+
code: 'PN-MIG-CHECK-015',
|
|
118
126
|
where: migrationFileRelative(join(migrationsDir, violation.spaceId), 'contract.json'),
|
|
119
127
|
why: `Contract for space "${violation.spaceId}" is unreadable: ${violation.detail}`,
|
|
120
128
|
fix: 'Re-emit the extension contract artefacts, or fix the descriptor producing the invalid contract.',
|
|
121
129
|
};
|
|
122
130
|
case 'duplicateMigrationHash':
|
|
123
131
|
return {
|
|
124
|
-
|
|
132
|
+
space: violation.spaceId,
|
|
133
|
+
code: 'PN-MIG-CHECK-016',
|
|
125
134
|
where: spaceRelative(violation.spaceId),
|
|
126
135
|
why: `Multiple migrations in space "${violation.spaceId}" share migrationHash "${violation.migrationHash}" (${violation.dirNames.join(', ')}).`,
|
|
127
136
|
fix: 'Re-emit one of the conflicting packages so each migrationHash is unique.',
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { notOk, ok, type Result } from '@prisma-next/utils/result';
|
|
2
|
+
import { type CliStructuredError, errorLegendHumanOnly } from './cli-errors';
|
|
3
|
+
import type { GlobalFlags } from './global-flags';
|
|
4
|
+
|
|
5
|
+
export interface LegendCliOptions {
|
|
6
|
+
readonly legend?: boolean;
|
|
7
|
+
readonly dot?: boolean;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* The legend is decoration printed alongside the command header on stderr, so
|
|
12
|
+
* it is suppressed for the machine-readable / silent paths (`--json`, `--dot`,
|
|
13
|
+
* `--quiet`) exactly as the header is.
|
|
14
|
+
*/
|
|
15
|
+
export function shouldShowLegend(options: LegendCliOptions, flags: GlobalFlags): boolean {
|
|
16
|
+
return (
|
|
17
|
+
options.legend === true && options.dot !== true && flags.json !== true && flags.quiet !== true
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function validateLegendOptions(
|
|
22
|
+
options: LegendCliOptions,
|
|
23
|
+
flags: GlobalFlags,
|
|
24
|
+
): Result<void, CliStructuredError> {
|
|
25
|
+
if (options.legend !== true) {
|
|
26
|
+
return ok(undefined);
|
|
27
|
+
}
|
|
28
|
+
if (flags.json === true) {
|
|
29
|
+
return notOk(errorLegendHumanOnly('--json'));
|
|
30
|
+
}
|
|
31
|
+
if (flags.quiet === true) {
|
|
32
|
+
return notOk(errorLegendHumanOnly('--quiet'));
|
|
33
|
+
}
|
|
34
|
+
if (options.dot === true) {
|
|
35
|
+
return notOk(errorLegendHumanOnly('--dot'));
|
|
36
|
+
}
|
|
37
|
+
return ok(undefined);
|
|
38
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { OnDiskMigrationPackage } from '@prisma-next/migration-tools/package';
|
|
2
|
+
import { notOk, ok, type Result } from '@prisma-next/utils/result';
|
|
3
|
+
import { isAbsolute, relative, resolve } from 'pathe';
|
|
4
|
+
import { type CliStructuredError, errorRuntime } from './cli-errors';
|
|
5
|
+
|
|
6
|
+
export function looksLikePath(target: string): boolean {
|
|
7
|
+
return target.includes('/') || target.includes('\\');
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function resolveAppTargetPath(
|
|
11
|
+
target: string,
|
|
12
|
+
appMigrationsDir: string,
|
|
13
|
+
appMigrationsRelative: string,
|
|
14
|
+
): Result<string, CliStructuredError> {
|
|
15
|
+
const targetPath = resolve(target);
|
|
16
|
+
const relativeToApp = relative(appMigrationsDir, targetPath);
|
|
17
|
+
const isOutsideAppDir =
|
|
18
|
+
relativeToApp === '' ||
|
|
19
|
+
relativeToApp === '.' ||
|
|
20
|
+
relativeToApp.startsWith('..') ||
|
|
21
|
+
isAbsolute(relativeToApp);
|
|
22
|
+
if (isOutsideAppDir) {
|
|
23
|
+
return notOk(
|
|
24
|
+
errorRuntime('Target must point to an app-space migration', {
|
|
25
|
+
why: `Expected a path under ${appMigrationsRelative}, got ${target}`,
|
|
26
|
+
fix: 'Pass an app-space migration directory or use a hash prefix.',
|
|
27
|
+
}),
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
return ok(targetPath);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Resolve a filesystem-path target to the migration dir that contains it,
|
|
35
|
+
* searching each in-scope space's `migrationsDir`. A path is explicit, so
|
|
36
|
+
* it can belong to at most one space — returns the first match, or `null`
|
|
37
|
+
* when the path falls outside every space dir.
|
|
38
|
+
*/
|
|
39
|
+
export function resolveTargetPathAcrossSpaces(
|
|
40
|
+
target: string,
|
|
41
|
+
spaces: ReadonlyArray<{ readonly migrationsDir: string }>,
|
|
42
|
+
): string | null {
|
|
43
|
+
const targetPath = resolve(target);
|
|
44
|
+
for (const space of spaces) {
|
|
45
|
+
const rel = relative(space.migrationsDir, targetPath);
|
|
46
|
+
const isOutside = rel === '' || rel === '.' || rel.startsWith('..') || isAbsolute(rel);
|
|
47
|
+
if (!isOutside) {
|
|
48
|
+
return targetPath;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function findPackageByDirPath(
|
|
55
|
+
packages: readonly OnDiskMigrationPackage[],
|
|
56
|
+
resolvedDirPath: string,
|
|
57
|
+
): OnDiskMigrationPackage | undefined {
|
|
58
|
+
const normalized = resolve(resolvedDirPath);
|
|
59
|
+
return packages.find((p) => resolve(p.dirPath) === normalized);
|
|
60
|
+
}
|