@prisma-next/cli 0.12.0-dev.4 → 0.12.0-dev.41
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-Dk-zRFuT.mjs} +83 -58
- package/dist/client-Dk-zRFuT.mjs.map +1 -0
- package/dist/{command-helpers-Bbw1GbwL.mjs → command-helpers-xvg9oq4T.mjs} +301 -23
- package/dist/command-helpers-xvg9oq4T.mjs.map +1 -0
- package/dist/commands/contract-emit.mjs +1 -1
- package/dist/commands/contract-infer.mjs +1 -1
- package/dist/commands/db-init.mjs +4 -5
- package/dist/commands/db-init.mjs.map +1 -1
- package/dist/commands/db-schema.mjs +3 -3
- package/dist/commands/db-sign.mjs +4 -4
- 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.mjs +1 -1
- package/dist/commands/migrate.d.mts +2 -2
- package/dist/commands/migrate.d.mts.map +1 -1
- package/dist/commands/migrate.mjs +6 -8
- package/dist/commands/migrate.mjs.map +1 -1
- package/dist/commands/migration-check.d.mts +55 -1
- package/dist/commands/migration-check.d.mts.map +1 -1
- package/dist/commands/migration-check.mjs +2 -2
- package/dist/commands/migration-graph.d.mts +25 -7
- package/dist/commands/migration-graph.d.mts.map +1 -1
- package/dist/commands/migration-graph.mjs +170 -2
- package/dist/commands/migration-graph.mjs.map +1 -0
- package/dist/commands/migration-list.d.mts +24 -26
- 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 +20 -15
- package/dist/commands/migration-log.d.mts.map +1 -1
- package/dist/commands/migration-log.mjs +1 -137
- package/dist/commands/migration-new.mjs +3 -3
- package/dist/commands/migration-plan.d.mts +1 -1
- package/dist/commands/migration-plan.mjs +1 -1
- package/dist/commands/migration-show.d.mts +1 -4
- package/dist/commands/migration-show.d.mts.map +1 -1
- package/dist/commands/migration-show.mjs +13 -25
- package/dist/commands/migration-show.mjs.map +1 -1
- package/dist/commands/migration-status.d.mts +41 -141
- package/dist/commands/migration-status.d.mts.map +1 -1
- package/dist/commands/migration-status.mjs +2 -759
- package/dist/commands/ref.d.mts +1 -1
- package/dist/commands/ref.mjs +3 -3
- 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/{contract-at-errors-BxP-TOMl.mjs → contract-at-errors-Wj3u4Xco.mjs} +2 -2
- package/dist/{contract-at-errors-BxP-TOMl.mjs.map → contract-at-errors-Wj3u4Xco.mjs.map} +1 -1
- package/dist/{contract-emit-DxcGl4Uq.mjs → contract-emit-CZU0UO6M.mjs} +3 -3
- package/dist/{contract-emit-DxcGl4Uq.mjs.map → contract-emit-CZU0UO6M.mjs.map} +1 -1
- package/dist/{contract-emit-D-4jrNve.mjs → contract-emit-FetLZ3jn.mjs} +5 -5
- package/dist/{contract-emit-D-4jrNve.mjs.map → contract-emit-FetLZ3jn.mjs.map} +1 -1
- package/dist/{contract-infer-D8uEbJuu.mjs → contract-infer-3wtOsS_H.mjs} +3 -3
- package/dist/{contract-infer-D8uEbJuu.mjs.map → contract-infer-3wtOsS_H.mjs.map} +1 -1
- package/dist/{contract-space-aggregate-loader-DvZwdkrr.mjs → contract-space-aggregate-loader-BdRPfM3Q.mjs} +63 -5
- package/dist/{contract-space-aggregate-loader-DvZwdkrr.mjs.map → contract-space-aggregate-loader-BdRPfM3Q.mjs.map} +1 -1
- package/dist/{db-verify-v_vUKXTU.mjs → db-verify-BisylXFZ.mjs} +4 -4
- package/dist/{db-verify-v_vUKXTU.mjs.map → db-verify-BisylXFZ.mjs.map} +1 -1
- package/dist/exports/control-api.d.mts +2 -2
- package/dist/exports/control-api.d.mts.map +1 -1
- package/dist/exports/control-api.mjs +2 -2
- package/dist/exports/index.mjs +1 -1
- package/dist/exports/init-output.mjs +1 -1
- package/dist/{framework-components-fYXjz_in.mjs → framework-components-Be4inY3I.mjs} +2 -2
- package/dist/{framework-components-fYXjz_in.mjs.map → framework-components-Be4inY3I.mjs.map} +1 -1
- package/dist/{global-flags-DEHjV8_s.d.mts → global-flags-DG4uY5tV.d.mts} +1 -1
- package/dist/{global-flags-DEHjV8_s.d.mts.map → global-flags-DG4uY5tV.d.mts.map} +1 -1
- package/dist/{init-Cv9UzWL5.mjs → init-BTE2U7lG.mjs} +5 -58
- package/dist/init-BTE2U7lG.mjs.map +1 -0
- package/dist/{inspect-live-schema-C6ohV_oQ.mjs → inspect-live-schema-Cy9Y4wsL.mjs} +3 -3
- package/dist/{inspect-live-schema-C6ohV_oQ.mjs.map → inspect-live-schema-Cy9Y4wsL.mjs.map} +1 -1
- package/dist/{migration-check-BiBJoYYW.mjs → migration-check-CUavU7U9.mjs} +236 -88
- package/dist/migration-check-CUavU7U9.mjs.map +1 -0
- package/dist/{migration-command-scaffold-CjvwO6at.mjs → migration-command-scaffold-BxOxtyJ6.mjs} +3 -3
- package/dist/{migration-command-scaffold-CjvwO6at.mjs.map → migration-command-scaffold-BxOxtyJ6.mjs.map} +1 -1
- package/dist/migration-graph-space-render-ByJ83gxp.mjs +1966 -0
- package/dist/migration-graph-space-render-ByJ83gxp.mjs.map +1 -0
- package/dist/migration-list-jK6QeczE.mjs +228 -0
- package/dist/migration-list-jK6QeczE.mjs.map +1 -0
- package/dist/migration-list-types-DS63IdFd.d.mts +23 -0
- package/dist/migration-list-types-DS63IdFd.d.mts.map +1 -0
- package/dist/migration-log-DWI6dZyi.mjs +215 -0
- package/dist/migration-log-DWI6dZyi.mjs.map +1 -0
- package/dist/migration-path-target-DqcrbOis.mjs +24 -0
- package/dist/migration-path-target-DqcrbOis.mjs.map +1 -0
- package/dist/{migration-plan-9DJ7q7_z.mjs → migration-plan-NHdlUwPG.mjs} +5 -6
- package/dist/{migration-plan-9DJ7q7_z.mjs.map → migration-plan-NHdlUwPG.mjs.map} +1 -1
- package/dist/migration-status-DI6Ldjbo.mjs +439 -0
- package/dist/migration-status-DI6Ldjbo.mjs.map +1 -0
- package/dist/{output-B60Gw5fu.mjs → output-CF_hqzI-.mjs} +1 -1
- package/dist/{output-B60Gw5fu.mjs.map → output-CF_hqzI-.mjs.map} +1 -1
- package/dist/{ref-advancement-DUZqsue6.mjs → ref-advancement-CJY9zOv7.mjs} +1 -1
- package/dist/{ref-advancement-DUZqsue6.mjs.map → ref-advancement-CJY9zOv7.mjs.map} +1 -1
- package/dist/telemetry-DQP0BvKv.mjs +122 -0
- package/dist/telemetry-DQP0BvKv.mjs.map +1 -0
- package/dist/{types-Dt_SfqFm.d.mts → types-Cculk5KV.d.mts} +44 -31
- package/dist/types-Cculk5KV.d.mts.map +1 -0
- package/dist/{verify-DCA9Sldu.mjs → verify-tvHRBBVP.mjs} +2 -2
- package/dist/{verify-DCA9Sldu.mjs.map → verify-tvHRBBVP.mjs.map} +1 -1
- package/package.json +22 -19
- package/src/cli.ts +5 -0
- 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/migrate.ts +6 -6
- package/src/commands/migration-check.ts +340 -117
- package/src/commands/migration-graph.ts +163 -90
- package/src/commands/migration-list.ts +55 -25
- package/src/commands/migration-log.ts +49 -98
- package/src/commands/migration-show.ts +10 -38
- package/src/commands/migration-status-overlay.ts +61 -0
- package/src/commands/migration-status.ts +440 -1056
- package/src/commands/telemetry/index.ts +107 -0
- package/src/commands/telemetry/status.ts +67 -0
- package/src/control-api/client.ts +20 -9
- package/src/control-api/operations/contract-emit.ts +2 -2
- package/src/control-api/operations/db-init.ts +3 -3
- package/src/control-api/operations/{db-apply.ts → db-run.ts} +37 -10
- package/src/control-api/operations/db-update.ts +4 -4
- package/src/control-api/operations/{migration-apply.ts → migrate.ts} +32 -24
- package/src/control-api/operations/{apply.ts → run-migration.ts} +33 -27
- package/src/control-api/types.ts +46 -29
- package/src/utils/cli-errors.ts +47 -2
- package/src/utils/formatters/errors.ts +11 -0
- package/src/utils/formatters/migration-graph-lane-colors.ts +194 -0
- package/src/utils/formatters/migration-graph-layout.ts +51 -7
- package/src/utils/formatters/migration-graph-rows.ts +128 -15
- package/src/utils/formatters/migration-graph-space-render.ts +138 -0
- package/src/utils/formatters/migration-graph-tree-render.ts +405 -77
- package/src/utils/formatters/migration-list-data-column.ts +4 -91
- package/src/utils/formatters/migration-list-graph-topology.ts +68 -90
- package/src/utils/formatters/migration-list-render.ts +122 -70
- package/src/utils/formatters/migration-list-styler.ts +48 -5
- package/src/utils/formatters/migration-log-table.ts +200 -0
- package/src/utils/formatters/migrations.ts +25 -1
- package/src/utils/global-flags.ts +35 -0
- package/src/utils/legend.ts +38 -0
- package/src/utils/migration-path-target.ts +39 -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/extension-pack-inputs-IDvjRCi3.mjs +0 -62
- package/dist/extension-pack-inputs-IDvjRCi3.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/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-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/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
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared runner tail (`runMigration` + `buildPerSpaceBreakdown`/`collectOrdered`).
|
|
3
|
+
* Backs no command directly; consumed by db-run and migrate.
|
|
4
|
+
*/
|
|
5
|
+
|
|
1
6
|
import type { TargetBoundComponentDescriptor } from '@prisma-next/framework-components/components';
|
|
2
7
|
import type {
|
|
3
8
|
ControlDriverInstance,
|
|
@@ -11,32 +16,32 @@ import { notOk, ok, type Result } from '@prisma-next/utils/result';
|
|
|
11
16
|
import type { OnControlProgress, PerSpaceExecutionEntry } from '../types';
|
|
12
17
|
|
|
13
18
|
/**
|
|
14
|
-
* Span id emitted via `onProgress` for the
|
|
19
|
+
* Span id emitted via `onProgress` for the run phase. Stable
|
|
15
20
|
* identifier consumed by the structured-output renderer and by tests.
|
|
16
21
|
*/
|
|
17
|
-
const
|
|
22
|
+
const RUN_SPAN_ID = 'apply' as const;
|
|
18
23
|
|
|
19
24
|
/**
|
|
20
|
-
* Action that originated this
|
|
25
|
+
* Action that originated this run call. Threaded into `OnControlProgress`
|
|
21
26
|
* events so the parent CLI command can attribute the span correctly,
|
|
22
27
|
* and used to compose action-specific summary phrasing.
|
|
23
28
|
*/
|
|
24
|
-
export type
|
|
29
|
+
export type RunAction = 'dbInit' | 'dbUpdate' | 'migrate';
|
|
25
30
|
|
|
26
31
|
/**
|
|
27
|
-
* Failure variant emitted by {@link
|
|
28
|
-
* itself rejects the
|
|
32
|
+
* Failure variant emitted by {@link runMigration} when the runner
|
|
33
|
+
* itself rejects the run. Mirrors the failure shape callers
|
|
29
34
|
* already wrap into their own action-specific failure envelopes
|
|
30
|
-
* (`DbInitFailure`, `DbUpdateFailure`, `
|
|
35
|
+
* (`DbInitFailure`, `DbUpdateFailure`, `MigrateFailure`) so each
|
|
31
36
|
* caller keeps owning its own discriminated failure code.
|
|
32
37
|
*/
|
|
33
|
-
export interface
|
|
38
|
+
export interface RunnerFailure {
|
|
34
39
|
readonly summary: string;
|
|
35
40
|
readonly why?: string;
|
|
36
41
|
readonly meta: Record<string, unknown>;
|
|
37
42
|
}
|
|
38
43
|
|
|
39
|
-
export interface
|
|
44
|
+
export interface RunMigrationInputs<TFamilyId extends string, TTargetId extends string> {
|
|
40
45
|
readonly aggregate: ContractSpaceAggregate;
|
|
41
46
|
/**
|
|
42
47
|
* Per-space plans, keyed by `spaceId`. Produced by either the full
|
|
@@ -62,22 +67,22 @@ export interface ApplyMigrationInputs<TFamilyId extends string, TTargetId extend
|
|
|
62
67
|
>;
|
|
63
68
|
readonly frameworkComponents: ReadonlyArray<TargetBoundComponentDescriptor<TFamilyId, TTargetId>>;
|
|
64
69
|
readonly policy: MigrationOperationPolicy;
|
|
65
|
-
readonly action:
|
|
70
|
+
readonly action: RunAction;
|
|
66
71
|
readonly onProgress?: OnControlProgress;
|
|
67
72
|
}
|
|
68
73
|
|
|
69
74
|
/**
|
|
70
75
|
* Resolved per-space plan in canonical schedule order. Surfaced from
|
|
71
|
-
* {@link
|
|
76
|
+
* {@link runMigration} to callers so each one can build its own
|
|
72
77
|
* action-specific success envelope (e.g. `DbInitSuccess` vs
|
|
73
|
-
* `
|
|
78
|
+
* `MigrateSuccess`) without re-deriving the ordering.
|
|
74
79
|
*/
|
|
75
80
|
export interface OrderedResolution {
|
|
76
81
|
readonly spaceId: string;
|
|
77
82
|
readonly entry: PerSpacePlan;
|
|
78
83
|
}
|
|
79
84
|
|
|
80
|
-
export interface
|
|
85
|
+
export interface RunMigrationValue {
|
|
81
86
|
readonly orderedResolutions: readonly OrderedResolution[];
|
|
82
87
|
readonly totalOpsPlanned: number;
|
|
83
88
|
readonly totalOpsExecuted: number;
|
|
@@ -89,10 +94,10 @@ export interface ApplyMigrationValue {
|
|
|
89
94
|
readonly perSpace: readonly PerSpaceExecutionEntry[];
|
|
90
95
|
}
|
|
91
96
|
|
|
92
|
-
export type
|
|
97
|
+
export type RunMigrationResult = Result<RunMigrationValue, RunnerFailure>;
|
|
93
98
|
|
|
94
99
|
/**
|
|
95
|
-
* Runner-driving tail shared by every
|
|
100
|
+
* Runner-driving tail shared by every run caller — `db init`,
|
|
96
101
|
* `db update`, and `migrate`. Consumes already-resolved per-space
|
|
97
102
|
* plans (the planner-vs-replay distinction is owned by the caller) and
|
|
98
103
|
* dispatches them to the runner in canonical order.
|
|
@@ -107,9 +112,9 @@ export type ApplyMigrationResult = Result<ApplyMigrationValue, ApplyRunnerFailur
|
|
|
107
112
|
* so callers don't have to duplicate it; the `action` field on each
|
|
108
113
|
* progress event is taken from the caller's `action` argument.
|
|
109
114
|
*/
|
|
110
|
-
export async function
|
|
111
|
-
inputs:
|
|
112
|
-
): Promise<
|
|
115
|
+
export async function runMigration<TFamilyId extends string, TTargetId extends string>(
|
|
116
|
+
inputs: RunMigrationInputs<TFamilyId, TTargetId>,
|
|
117
|
+
): Promise<RunMigrationResult> {
|
|
113
118
|
const {
|
|
114
119
|
aggregate,
|
|
115
120
|
perSpacePlans,
|
|
@@ -130,7 +135,7 @@ export async function applyMigration<TFamilyId extends string, TTargetId extends
|
|
|
130
135
|
onProgress?.({
|
|
131
136
|
action,
|
|
132
137
|
kind: 'spanStart',
|
|
133
|
-
spanId:
|
|
138
|
+
spanId: RUN_SPAN_ID,
|
|
134
139
|
label: progressLabelForAction(action),
|
|
135
140
|
});
|
|
136
141
|
|
|
@@ -141,6 +146,7 @@ export async function applyMigration<TFamilyId extends string, TTargetId extends
|
|
|
141
146
|
destinationContract: r.entry.destinationContract,
|
|
142
147
|
policy,
|
|
143
148
|
frameworkComponents,
|
|
149
|
+
migrationEdges: r.entry.migrationEdges,
|
|
144
150
|
// Per-space post-apply schema verification is non-strict: each
|
|
145
151
|
// space's `destinationContract` describes only its own slice; a
|
|
146
152
|
// strict verifier would treat every other space's tables as
|
|
@@ -151,7 +157,7 @@ export async function applyMigration<TFamilyId extends string, TTargetId extends
|
|
|
151
157
|
const runnerResult = await runner.execute({ driver, perSpaceOptions });
|
|
152
158
|
|
|
153
159
|
if (!runnerResult.ok) {
|
|
154
|
-
onProgress?.({ action, kind: 'spanEnd', spanId:
|
|
160
|
+
onProgress?.({ action, kind: 'spanEnd', spanId: RUN_SPAN_ID, outcome: 'error' });
|
|
155
161
|
return notOk({
|
|
156
162
|
summary: runnerResult.failure.summary,
|
|
157
163
|
...ifDefined('why', runnerResult.failure.why),
|
|
@@ -162,7 +168,7 @@ export async function applyMigration<TFamilyId extends string, TTargetId extends
|
|
|
162
168
|
},
|
|
163
169
|
});
|
|
164
170
|
}
|
|
165
|
-
onProgress?.({ action, kind: 'spanEnd', spanId:
|
|
171
|
+
onProgress?.({ action, kind: 'spanEnd', spanId: RUN_SPAN_ID, outcome: 'ok' });
|
|
166
172
|
|
|
167
173
|
const totalOpsPlanned = runnerResult.value.perSpaceResults.reduce(
|
|
168
174
|
(sum, r) => sum + r.value.operationsPlanned,
|
|
@@ -194,7 +200,7 @@ export async function applyMigration<TFamilyId extends string, TTargetId extends
|
|
|
194
200
|
* advances as the last step of each space's transaction) and `false`
|
|
195
201
|
* for plan-mode (no marker has been written yet).
|
|
196
202
|
*
|
|
197
|
-
* Exported alongside {@link
|
|
203
|
+
* Exported alongside {@link runMigration} so plan-mode callers can
|
|
198
204
|
* assemble the same per-space block without going through the runner.
|
|
199
205
|
*/
|
|
200
206
|
export function buildPerSpaceBreakdown(
|
|
@@ -243,18 +249,18 @@ export function collectOrdered(
|
|
|
243
249
|
}
|
|
244
250
|
|
|
245
251
|
/**
|
|
246
|
-
* Action-appropriate label for the `spanStart` event the
|
|
247
|
-
* primitive emits. `
|
|
252
|
+
* Action-appropriate label for the `spanStart` event the run
|
|
253
|
+
* primitive emits. `runMigration` is shared by `db init`, `db update`,
|
|
248
254
|
* and `migrate`; the span label tracks the user-visible action
|
|
249
255
|
* so structured-progress output reads naturally for each surface.
|
|
250
256
|
*/
|
|
251
|
-
export function progressLabelForAction(action:
|
|
257
|
+
export function progressLabelForAction(action: RunAction): string {
|
|
252
258
|
switch (action) {
|
|
253
259
|
case 'dbInit':
|
|
254
260
|
return 'Initialising database across spaces';
|
|
255
261
|
case 'dbUpdate':
|
|
256
262
|
return 'Updating database across spaces';
|
|
257
|
-
case '
|
|
258
|
-
return '
|
|
263
|
+
case 'migrate':
|
|
264
|
+
return 'Running migration plan across spaces';
|
|
259
265
|
}
|
|
260
266
|
}
|
package/src/control-api/types.ts
CHANGED
|
@@ -2,7 +2,11 @@ import type {
|
|
|
2
2
|
ContractSourceDiagnostics,
|
|
3
3
|
ContractSourceProvider,
|
|
4
4
|
} from '@prisma-next/config/config-types';
|
|
5
|
-
import type {
|
|
5
|
+
import type {
|
|
6
|
+
Contract,
|
|
7
|
+
ContractMarkerRecord,
|
|
8
|
+
LedgerEntryRecord,
|
|
9
|
+
} from '@prisma-next/contract/types';
|
|
6
10
|
import type {
|
|
7
11
|
ControlAdapterDescriptor,
|
|
8
12
|
ControlDriverDescriptor,
|
|
@@ -67,7 +71,7 @@ export type ControlActionName =
|
|
|
67
71
|
| 'dbInit'
|
|
68
72
|
| 'dbUpdate'
|
|
69
73
|
| 'dbVerify'
|
|
70
|
-
| '
|
|
74
|
+
| 'migrate'
|
|
71
75
|
| 'verify'
|
|
72
76
|
| 'schemaVerify'
|
|
73
77
|
| 'sign'
|
|
@@ -391,6 +395,7 @@ export interface DbInitSuccess {
|
|
|
391
395
|
*/
|
|
392
396
|
readonly perSpace?: ReadonlyArray<PerSpaceExecutionEntry>;
|
|
393
397
|
readonly summary: string;
|
|
398
|
+
readonly warnings?: ReadonlyArray<MigrationPlannerConflict>;
|
|
394
399
|
}
|
|
395
400
|
|
|
396
401
|
/**
|
|
@@ -406,6 +411,7 @@ export interface DbInitFailure {
|
|
|
406
411
|
readonly summary: string;
|
|
407
412
|
readonly why: string | undefined;
|
|
408
413
|
readonly conflicts: ReadonlyArray<MigrationPlannerConflict> | undefined;
|
|
414
|
+
readonly warnings?: ReadonlyArray<MigrationPlannerConflict>;
|
|
409
415
|
readonly meta: Record<string, unknown> | undefined;
|
|
410
416
|
readonly marker?: {
|
|
411
417
|
readonly storageHash?: string;
|
|
@@ -461,6 +467,7 @@ export interface DbUpdateSuccess {
|
|
|
461
467
|
*/
|
|
462
468
|
readonly perSpace?: ReadonlyArray<PerSpaceExecutionEntry>;
|
|
463
469
|
readonly summary: string;
|
|
470
|
+
readonly warnings?: ReadonlyArray<MigrationPlannerConflict>;
|
|
464
471
|
}
|
|
465
472
|
|
|
466
473
|
/**
|
|
@@ -476,6 +483,7 @@ export interface DbUpdateFailure {
|
|
|
476
483
|
readonly summary: string;
|
|
477
484
|
readonly why: string | undefined;
|
|
478
485
|
readonly conflicts: ReadonlyArray<MigrationPlannerConflict> | undefined;
|
|
486
|
+
readonly warnings?: ReadonlyArray<MigrationPlannerConflict>;
|
|
479
487
|
readonly meta: Record<string, unknown> | undefined;
|
|
480
488
|
}
|
|
481
489
|
|
|
@@ -532,17 +540,17 @@ export type EmitResult = Result<EmitSuccess, EmitFailure>;
|
|
|
532
540
|
// ============================================================================
|
|
533
541
|
|
|
534
542
|
/**
|
|
535
|
-
* Options for the aggregate-walking `
|
|
543
|
+
* Options for the aggregate-walking `migrate` operation.
|
|
536
544
|
*
|
|
537
545
|
* The control-api operation is responsible for: loading the
|
|
538
546
|
* contract-space aggregate, reading per-space marker rows from the
|
|
539
547
|
* live database, plotting per-space paths via `graphWalkStrategy`
|
|
540
548
|
* (replay-only — no synth, no introspection), and dispatching
|
|
541
|
-
* through the shared `
|
|
549
|
+
* through the shared `runMigration` primitive. The CLI command
|
|
542
550
|
* just resolves the descriptor surface (config, refs, contract
|
|
543
551
|
* envelope, app-space migration packages) and hands the inputs in.
|
|
544
552
|
*/
|
|
545
|
-
export interface
|
|
553
|
+
export interface MigrateOptions {
|
|
546
554
|
/** Already-validated app contract (the canonical "where we are heading" hash). */
|
|
547
555
|
readonly contract: unknown;
|
|
548
556
|
/** Migrations root directory (`migrations/` under the project). */
|
|
@@ -567,7 +575,7 @@ export interface MigrationApplyOptions {
|
|
|
567
575
|
*/
|
|
568
576
|
readonly refName?: string;
|
|
569
577
|
/**
|
|
570
|
-
* Database connection. If provided,
|
|
578
|
+
* Database connection. If provided, migrate will connect before executing.
|
|
571
579
|
* If omitted, the client must already be connected.
|
|
572
580
|
*/
|
|
573
581
|
readonly connection?: unknown;
|
|
@@ -611,7 +619,7 @@ export interface MigrationApplyStep {
|
|
|
611
619
|
* Per-space aggregate detail (markers, ops grouped by space) lives
|
|
612
620
|
* on `perSpace[]`; this list is the per-edge view.
|
|
613
621
|
*/
|
|
614
|
-
export interface
|
|
622
|
+
export interface MigrateRanEntry {
|
|
615
623
|
readonly spaceId: string;
|
|
616
624
|
readonly dirName: string;
|
|
617
625
|
readonly migrationHash: string;
|
|
@@ -621,13 +629,13 @@ export interface MigrationApplyAppliedEntry {
|
|
|
621
629
|
}
|
|
622
630
|
|
|
623
631
|
/**
|
|
624
|
-
* Successful
|
|
625
|
-
* (`markerHash` is the **app member's** post-
|
|
632
|
+
* Successful migrate result. Carries both the top-level fields
|
|
633
|
+
* (`markerHash` is the **app member's** post-migrate marker) and the
|
|
626
634
|
* per-space breakdown (`perSpace` — markers / operations in canonical
|
|
627
635
|
* schedule order).
|
|
628
636
|
*/
|
|
629
637
|
/**
|
|
630
|
-
* Path-decision summary for the **app member** post-
|
|
638
|
+
* Path-decision summary for the **app member** post-migrate. Surfaced
|
|
631
639
|
* at the top level (and consumed by the cli-journeys suite, which
|
|
632
640
|
* inspects `requiredInvariants`/`satisfiedInvariants`/
|
|
633
641
|
* `selectedPath` to validate invariant routing).
|
|
@@ -635,7 +643,7 @@ export interface MigrationApplyAppliedEntry {
|
|
|
635
643
|
* Per-space path decisions for extension members are not surfaced —
|
|
636
644
|
* extensions own their own ref/invariant control.
|
|
637
645
|
*/
|
|
638
|
-
export interface
|
|
646
|
+
export interface MigratePathDecision {
|
|
639
647
|
readonly fromHash: string;
|
|
640
648
|
readonly toHash: string;
|
|
641
649
|
readonly alternativeCount: number;
|
|
@@ -652,10 +660,10 @@ export interface MigrationApplyPathDecision {
|
|
|
652
660
|
}[];
|
|
653
661
|
}
|
|
654
662
|
|
|
655
|
-
export interface
|
|
663
|
+
export interface MigrateSuccess {
|
|
656
664
|
readonly migrationsApplied: number;
|
|
657
665
|
readonly markerHash: string;
|
|
658
|
-
readonly applied: readonly
|
|
666
|
+
readonly applied: readonly MigrateRanEntry[];
|
|
659
667
|
readonly summary: string;
|
|
660
668
|
/**
|
|
661
669
|
* Per-space breakdown in canonical schedule order (extensions
|
|
@@ -666,31 +674,31 @@ export interface MigrationApplySuccess {
|
|
|
666
674
|
/**
|
|
667
675
|
* Path-decision data for the app member. Present whenever the
|
|
668
676
|
* graph-walk strategy ran for the app (i.e. always for the
|
|
669
|
-
* aggregate-walking
|
|
677
|
+
* aggregate-walking migrate path). Absent only for the no-op
|
|
670
678
|
* "Already up to date" early return when the app has no plan.
|
|
671
679
|
*/
|
|
672
|
-
readonly pathDecision?:
|
|
680
|
+
readonly pathDecision?: MigratePathDecision;
|
|
673
681
|
}
|
|
674
682
|
|
|
675
683
|
/**
|
|
676
|
-
* Failure codes for
|
|
684
|
+
* Failure codes for migrate operation.
|
|
677
685
|
*/
|
|
678
|
-
export type
|
|
686
|
+
export type MigrateFailureCode = 'RUNNER_FAILED' | 'MIGRATION_PATH_NOT_FOUND';
|
|
679
687
|
|
|
680
688
|
/**
|
|
681
|
-
* Failure details for
|
|
689
|
+
* Failure details for migrate operation.
|
|
682
690
|
*/
|
|
683
|
-
export interface
|
|
684
|
-
readonly code:
|
|
691
|
+
export interface MigrateFailure {
|
|
692
|
+
readonly code: MigrateFailureCode;
|
|
685
693
|
readonly summary: string;
|
|
686
694
|
readonly why: string | undefined;
|
|
687
695
|
readonly meta: Record<string, unknown> | undefined;
|
|
688
696
|
}
|
|
689
697
|
|
|
690
698
|
/**
|
|
691
|
-
* Result type for
|
|
699
|
+
* Result type for migrate operation.
|
|
692
700
|
*/
|
|
693
|
-
export type
|
|
701
|
+
export type MigrateResult = Result<MigrateSuccess, MigrateFailure>;
|
|
694
702
|
|
|
695
703
|
// ============================================================================
|
|
696
704
|
// Standalone Contract Emit Types
|
|
@@ -877,17 +885,26 @@ export interface ControlClient {
|
|
|
877
885
|
readAllMarkers(): Promise<ReadonlyMap<string, ContractMarkerRecord>>;
|
|
878
886
|
|
|
879
887
|
/**
|
|
880
|
-
*
|
|
888
|
+
* Reads the per-migration ledger journal for `space` in apply order.
|
|
889
|
+
* Returns an empty array when the ledger store does not yet exist or
|
|
890
|
+
* has no rows for that space.
|
|
891
|
+
*/
|
|
892
|
+
readLedger(space?: string): Promise<readonly LedgerEntryRecord[]>;
|
|
893
|
+
|
|
894
|
+
/**
|
|
895
|
+
* Advances the database along the migration graph to the target contract.
|
|
881
896
|
* Each migration runs in its own transaction with full execution checks.
|
|
882
|
-
* Resume-safe: re-running after failure picks up from the last
|
|
897
|
+
* Resume-safe: re-running after failure picks up from the last run migration.
|
|
883
898
|
*
|
|
884
|
-
* @param options.
|
|
885
|
-
* @param options.
|
|
886
|
-
* @param options.
|
|
887
|
-
* @
|
|
899
|
+
* @param options.contract - The target contract to migrate to
|
|
900
|
+
* @param options.migrationsDir - Root migrations directory (`migrations/` under the project)
|
|
901
|
+
* @param options.refHash - Optional app-space ref override hash
|
|
902
|
+
* @param options.refInvariants - Required invariants on the user-supplied ref
|
|
903
|
+
* @param options.refName - Resolved name of the user-supplied app-space ref
|
|
904
|
+
* @returns Result pattern: Ok with migration details, NotOk with failure details
|
|
888
905
|
* @throws If not connected, target doesn't support migrations, or infrastructure failure
|
|
889
906
|
*/
|
|
890
|
-
|
|
907
|
+
migrate(options: MigrateOptions): Promise<MigrateResult>;
|
|
891
908
|
|
|
892
909
|
/**
|
|
893
910
|
* Introspects the database schema.
|
package/src/utils/cli-errors.ts
CHANGED
|
@@ -28,7 +28,8 @@ import {
|
|
|
28
28
|
import { errorRuntime } from '@prisma-next/errors/execution';
|
|
29
29
|
import type { MigrationToolsError } from '@prisma-next/migration-tools/errors';
|
|
30
30
|
import type { RefResolutionError } from '@prisma-next/migration-tools/ref-resolution';
|
|
31
|
-
import
|
|
31
|
+
import { ifDefined } from '@prisma-next/utils/defined';
|
|
32
|
+
import type { MigrateFailure } from '../control-api/types';
|
|
32
33
|
|
|
33
34
|
export {
|
|
34
35
|
ERROR_CODE_DESTRUCTIVE_CHANGES,
|
|
@@ -108,6 +109,23 @@ export function errorRefSetEmptySentinel(hash: string): CliStructuredError {
|
|
|
108
109
|
});
|
|
109
110
|
}
|
|
110
111
|
|
|
112
|
+
/**
|
|
113
|
+
* `--legend` was combined with a machine-readable or silent output flag.
|
|
114
|
+
* The legend is human-only decoration on stderr.
|
|
115
|
+
*/
|
|
116
|
+
export function errorLegendHumanOnly(
|
|
117
|
+
conflictingFlag: '--json' | '--dot' | '--quiet',
|
|
118
|
+
): CliStructuredError {
|
|
119
|
+
return errorRuntime('`--legend` is only available for human-readable output', {
|
|
120
|
+
why: `\`--legend\` prints a glyph key to stderr and cannot be combined with ${conflictingFlag}.`,
|
|
121
|
+
fix: `Omit ${conflictingFlag} to print the legend alongside the tree, or omit --legend when using ${conflictingFlag}.`,
|
|
122
|
+
meta: {
|
|
123
|
+
code: 'MIGRATION.LEGEND_HUMAN_ONLY',
|
|
124
|
+
conflictingFlag,
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
111
129
|
/**
|
|
112
130
|
* `--space <id>` was given a value that doesn't satisfy the contract-space
|
|
113
131
|
* naming rule (`[a-z][a-z0-9_-]{0,63}` per `isValidSpaceId`). Fires before
|
|
@@ -250,7 +268,7 @@ export function errorMarkerMismatch(
|
|
|
250
268
|
});
|
|
251
269
|
}
|
|
252
270
|
|
|
253
|
-
export function errorPathUnreachable(failure:
|
|
271
|
+
export function errorPathUnreachable(failure: MigrateFailure): CliStructuredError {
|
|
254
272
|
const meta = failure.meta ?? {};
|
|
255
273
|
const fromHashMeta = typeof meta['fromHash'] === 'string' ? meta['fromHash'] : null;
|
|
256
274
|
// `buildPathNotFoundFailure` uses this sentinel in meta when the live marker is null.
|
|
@@ -325,6 +343,33 @@ export function mapMigrationToolsError(error: MigrationToolsError): CliStructure
|
|
|
325
343
|
});
|
|
326
344
|
}
|
|
327
345
|
|
|
346
|
+
/**
|
|
347
|
+
* Shared "needs a live database" precondition for read verbs that consult the
|
|
348
|
+
* marker/ledger (`migration log`, `migration status`). A command needs both a
|
|
349
|
+
* connection string and a control-plane driver; either missing yields the same
|
|
350
|
+
* `PN-CLI-4005` envelope with `meta.missingFlags` (canonical long-form flags
|
|
351
|
+
* per CLI Style Guide §Errors) so callers can react programmatically. Returns
|
|
352
|
+
* `null` when both are present.
|
|
353
|
+
*/
|
|
354
|
+
export function requireLiveDatabase(args: {
|
|
355
|
+
readonly dbConnection: unknown;
|
|
356
|
+
readonly hasDriver: boolean;
|
|
357
|
+
readonly why: string;
|
|
358
|
+
readonly commandName?: string;
|
|
359
|
+
readonly retryCommand?: string;
|
|
360
|
+
}): CliStructuredError | null {
|
|
361
|
+
if (args.dbConnection && args.hasDriver) {
|
|
362
|
+
return null;
|
|
363
|
+
}
|
|
364
|
+
const missingFlags = args.dbConnection ? [] : ['--db'];
|
|
365
|
+
return errorDatabaseConnectionRequired({
|
|
366
|
+
why: args.why,
|
|
367
|
+
missingFlags,
|
|
368
|
+
...ifDefined('commandName', args.commandName),
|
|
369
|
+
...ifDefined('retryCommand', args.retryCommand),
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
|
|
328
373
|
/**
|
|
329
374
|
* Maps a `RefResolutionError` from the contract/migration reference
|
|
330
375
|
* resolver into a CLI structured error envelope.
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
import type { MigrationPlannerConflict } from '@prisma-next/framework-components/control';
|
|
2
|
+
import { blindCast } from '@prisma-next/utils/casts';
|
|
1
3
|
import { red } from 'colorette';
|
|
2
4
|
|
|
3
5
|
import type { CliErrorConflict, CliErrorEnvelope } from '../cli-errors';
|
|
4
6
|
import type { GlobalFlags } from '../global-flags';
|
|
5
7
|
import { createColorFormatter, formatDim, isVerbose } from './helpers';
|
|
8
|
+
import { formatPlannerWarningsBlock } from './migrations';
|
|
6
9
|
|
|
7
10
|
/**
|
|
8
11
|
* Formats error output for human-readable display.
|
|
@@ -67,6 +70,14 @@ export function formatErrorOutput(error: CliErrorEnvelope, flags: GlobalFlags):
|
|
|
67
70
|
if (error.docsUrl && isVerbose(flags, 1)) {
|
|
68
71
|
lines.push(formatDimText(error.docsUrl));
|
|
69
72
|
}
|
|
73
|
+
const plannerWarnings = error.meta?.['plannerWarnings'];
|
|
74
|
+
if (Array.isArray(plannerWarnings) && plannerWarnings.length > 0) {
|
|
75
|
+
const typedWarnings = blindCast<
|
|
76
|
+
readonly MigrationPlannerConflict[],
|
|
77
|
+
'mapDbUpdateFailure (db-update.ts) writes meta.plannerWarnings as MigrationPlannerConflict[]; meta is typed Record<string, unknown> so the channel erases the element type'
|
|
78
|
+
>(plannerWarnings);
|
|
79
|
+
lines.push(...formatPlannerWarningsBlock(typedWarnings, useColor));
|
|
80
|
+
}
|
|
70
81
|
if (isVerbose(flags, 2) && error.meta) {
|
|
71
82
|
lines.push(`${formatDimText(` Meta: ${JSON.stringify(error.meta, null, 2)}`)}`);
|
|
72
83
|
}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { createColors } from 'colorette';
|
|
2
|
+
import type { StructuralCell } from './migration-graph-layout';
|
|
3
|
+
|
|
4
|
+
export type LaneColorizer = (text: string) => string;
|
|
5
|
+
|
|
6
|
+
const { magenta, cyan, green, yellow, blueBright, red } = createColors({ useColor: true });
|
|
7
|
+
|
|
8
|
+
export const LANE_COLOR_CYCLE: readonly LaneColorizer[] = [
|
|
9
|
+
magenta,
|
|
10
|
+
cyan,
|
|
11
|
+
green,
|
|
12
|
+
yellow,
|
|
13
|
+
blueBright,
|
|
14
|
+
red,
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* The leftmost lane (column 0) renders neutral — no palette hue. Columns ≥ 1
|
|
19
|
+
* rotate through {@link LANE_COLOR_CYCLE}.
|
|
20
|
+
*/
|
|
21
|
+
export const NEUTRAL_LANE_COLUMN = 0;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* The hue for a gutter column. The leftmost lane (column 0) is **neutral** — it
|
|
25
|
+
* has nothing to be told apart from in the common single-lane linear case, so
|
|
26
|
+
* the renderer dims it rather than tinting it; the rotating palette is reserved
|
|
27
|
+
* for columns ≥ 1 (where a second lane exists to distinguish). Callers must dim
|
|
28
|
+
* column 0 themselves; this returns identity for it as a guard. A lane freed and
|
|
29
|
+
* reused by a later branch keeps its column's hue — coloring is by position, not
|
|
30
|
+
* branch identity, exactly like `git log --graph`.
|
|
31
|
+
*/
|
|
32
|
+
export function laneColorForColumn(column: number): LaneColorizer {
|
|
33
|
+
if (column <= NEUTRAL_LANE_COLUMN) {
|
|
34
|
+
return (text) => text;
|
|
35
|
+
}
|
|
36
|
+
const colorizer = LANE_COLOR_CYCLE[(column - 1) % LANE_COLOR_CYCLE.length];
|
|
37
|
+
return colorizer ?? ((text) => text);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Style a structural glyph by its resolved colour column. Column 0 and the
|
|
42
|
+
* neutral sentinel render dim (`dimLane`); columns ≥ 1 take a palette hue.
|
|
43
|
+
*/
|
|
44
|
+
export function stylerForLaneColumn(
|
|
45
|
+
colorColumn: number,
|
|
46
|
+
colorize: boolean,
|
|
47
|
+
dimLane: (text: string) => string,
|
|
48
|
+
): LaneColorizer {
|
|
49
|
+
if (!colorize || colorColumn <= NEUTRAL_LANE_COLUMN) {
|
|
50
|
+
return dimLane;
|
|
51
|
+
}
|
|
52
|
+
return laneColorForColumn(colorColumn);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* The colour-source column for each cell of a row, resolved together because a
|
|
57
|
+
* routed back-arc spans columns and must read as **one hue** rather than a
|
|
58
|
+
* per-column "rainbow".
|
|
59
|
+
*/
|
|
60
|
+
export interface RowArcLaneColors {
|
|
61
|
+
/** Colour column for a cell's structural glyph (lane / spine / arc body). */
|
|
62
|
+
readonly lane: readonly number[];
|
|
63
|
+
/** Colour column for a node arc-pair's connector half (`◂` / `─`). */
|
|
64
|
+
readonly connector: readonly number[];
|
|
65
|
+
/**
|
|
66
|
+
* Colour column for the trailing `─` of a landing tee (`┴─`). The junction
|
|
67
|
+
* (`lane`) keeps its own column; the dash leads into the next converging arc.
|
|
68
|
+
*/
|
|
69
|
+
readonly dash: readonly number[];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Resolve per-cell colour columns for a node/arc row. Scanning right-to-left
|
|
74
|
+
* lets each arc segment inherit the hue of the arc it leads into.
|
|
75
|
+
*/
|
|
76
|
+
export function resolveRowArcLaneColors(cells: readonly StructuralCell[]): RowArcLaneColors {
|
|
77
|
+
const lane = new Array<number>(cells.length);
|
|
78
|
+
const connector = new Array<number>(cells.length);
|
|
79
|
+
const dash = new Array<number>(cells.length);
|
|
80
|
+
let arcCorner = NEUTRAL_LANE_COLUMN;
|
|
81
|
+
let landingAnchor = NEUTRAL_LANE_COLUMN;
|
|
82
|
+
for (let column = cells.length - 1; column >= 0; column--) {
|
|
83
|
+
const cell = cells[column];
|
|
84
|
+
connector[column] = landingAnchor !== NEUTRAL_LANE_COLUMN ? landingAnchor : arcCorner;
|
|
85
|
+
switch (cell?.kind) {
|
|
86
|
+
case 'arc-branch-corner':
|
|
87
|
+
arcCorner = column;
|
|
88
|
+
lane[column] = column;
|
|
89
|
+
dash[column] = column;
|
|
90
|
+
break;
|
|
91
|
+
case 'arc-land-corner':
|
|
92
|
+
arcCorner = column;
|
|
93
|
+
landingAnchor = column;
|
|
94
|
+
lane[column] = column;
|
|
95
|
+
dash[column] = column;
|
|
96
|
+
break;
|
|
97
|
+
case 'arc-branch-tee':
|
|
98
|
+
lane[column] = column;
|
|
99
|
+
dash[column] = column;
|
|
100
|
+
break;
|
|
101
|
+
case 'arc-land-tee':
|
|
102
|
+
lane[column] = column;
|
|
103
|
+
dash[column] = landingAnchor === NEUTRAL_LANE_COLUMN ? column : landingAnchor;
|
|
104
|
+
landingAnchor = column;
|
|
105
|
+
break;
|
|
106
|
+
case 'arc-crossing':
|
|
107
|
+
case 'arc-land-bridge': {
|
|
108
|
+
const served = landingAnchor !== NEUTRAL_LANE_COLUMN ? landingAnchor : arcCorner;
|
|
109
|
+
lane[column] = served;
|
|
110
|
+
dash[column] = served;
|
|
111
|
+
break;
|
|
112
|
+
}
|
|
113
|
+
case 'horizontal-pass':
|
|
114
|
+
lane[column] = arcCorner === NEUTRAL_LANE_COLUMN ? column : arcCorner;
|
|
115
|
+
dash[column] = lane[column] ?? column;
|
|
116
|
+
break;
|
|
117
|
+
case 'node':
|
|
118
|
+
lane[column] = column;
|
|
119
|
+
dash[column] = column;
|
|
120
|
+
arcCorner = NEUTRAL_LANE_COLUMN;
|
|
121
|
+
landingAnchor = NEUTRAL_LANE_COLUMN;
|
|
122
|
+
break;
|
|
123
|
+
default:
|
|
124
|
+
lane[column] = column;
|
|
125
|
+
dash[column] = column;
|
|
126
|
+
arcCorner = NEUTRAL_LANE_COLUMN;
|
|
127
|
+
landingAnchor = NEUTRAL_LANE_COLUMN;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return { lane, connector, dash };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Per-cell colour for a forward branch/merge connector row, split into the
|
|
135
|
+
* cell's junction `glyph` and its trailing `dash`.
|
|
136
|
+
*/
|
|
137
|
+
export interface ConnectorLaneColors {
|
|
138
|
+
/** Colour column for a cell's junction glyph (`├` / `┬` / `┴` / `╮` / `╯`). */
|
|
139
|
+
readonly glyph: readonly number[];
|
|
140
|
+
/** Colour column for a tee's trailing `─` — the branch it leads into. */
|
|
141
|
+
readonly dash: readonly number[];
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Resolve per-cell connector colours. Scanning right-to-left, a corner or an
|
|
146
|
+
* intermediate tee anchors its own lane, but a tee's trailing dash leads into
|
|
147
|
+
* the branch on its right.
|
|
148
|
+
*/
|
|
149
|
+
export function resolveConnectorLaneColors(
|
|
150
|
+
cells: readonly StructuralCell[],
|
|
151
|
+
startLane: number,
|
|
152
|
+
): ConnectorLaneColors {
|
|
153
|
+
const glyph = new Array<number>(cells.length);
|
|
154
|
+
const dash = new Array<number>(cells.length);
|
|
155
|
+
let owner = NEUTRAL_LANE_COLUMN;
|
|
156
|
+
for (let column = cells.length - 1; column >= 0; column--) {
|
|
157
|
+
const cell = cells[column];
|
|
158
|
+
switch (cell?.kind) {
|
|
159
|
+
case 'branch-corner':
|
|
160
|
+
case 'merge-corner':
|
|
161
|
+
owner = column;
|
|
162
|
+
glyph[column] = column;
|
|
163
|
+
dash[column] = column;
|
|
164
|
+
break;
|
|
165
|
+
case 'branch-tee':
|
|
166
|
+
case 'merge-tee':
|
|
167
|
+
if (column === startLane) {
|
|
168
|
+
const served = owner === NEUTRAL_LANE_COLUMN ? column : owner;
|
|
169
|
+
glyph[column] = column;
|
|
170
|
+
dash[column] = served;
|
|
171
|
+
} else {
|
|
172
|
+
dash[column] = owner === NEUTRAL_LANE_COLUMN ? column : owner;
|
|
173
|
+
glyph[column] = column;
|
|
174
|
+
owner = column;
|
|
175
|
+
}
|
|
176
|
+
break;
|
|
177
|
+
case 'arc-crossing':
|
|
178
|
+
glyph[column] = column;
|
|
179
|
+
dash[column] = owner === NEUTRAL_LANE_COLUMN ? column : owner;
|
|
180
|
+
owner = column;
|
|
181
|
+
break;
|
|
182
|
+
case 'horizontal-pass': {
|
|
183
|
+
const served = owner === NEUTRAL_LANE_COLUMN ? column : owner;
|
|
184
|
+
glyph[column] = served;
|
|
185
|
+
dash[column] = served;
|
|
186
|
+
break;
|
|
187
|
+
}
|
|
188
|
+
default:
|
|
189
|
+
glyph[column] = column;
|
|
190
|
+
dash[column] = column;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return { glyph, dash };
|
|
194
|
+
}
|