@prisma-next/cli 0.12.0-dev.3 → 0.12.0-dev.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.mjs +180 -163
- package/dist/cli.mjs.map +1 -1
- package/dist/{client-KgJorIvG.mjs → client-xeWpMlq1.mjs} +39 -15
- package/dist/client-xeWpMlq1.mjs.map +1 -0
- package/dist/{command-helpers-Bbw1GbwL.mjs → command-helpers-DK_5ItoJ.mjs} +284 -24
- package/dist/command-helpers-DK_5ItoJ.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 +1 -1
- package/dist/commands/migrate.mjs +5 -6
- package/dist/commands/migrate.mjs.map +1 -1
- package/dist/commands/migration-check.mjs +1 -1
- package/dist/commands/migration-graph.d.mts +13 -7
- package/dist/commands/migration-graph.d.mts.map +1 -1
- package/dist/commands/migration-graph.mjs +171 -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 +6 -18
- 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 -1
- package/dist/commands/migration-show.mjs +3 -4
- 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-DG3kjgoz.mjs} +2 -2
- package/dist/{contract-at-errors-BxP-TOMl.mjs.map → contract-at-errors-DG3kjgoz.mjs.map} +1 -1
- package/dist/{contract-emit-D-4jrNve.mjs → contract-emit-BO0l6fnT.mjs} +3 -3
- package/dist/{contract-emit-D-4jrNve.mjs.map → contract-emit-BO0l6fnT.mjs.map} +1 -1
- package/dist/{contract-emit-DxcGl4Uq.mjs → contract-emit-C0Bs0VRj.mjs} +3 -3
- package/dist/{contract-emit-DxcGl4Uq.mjs.map → contract-emit-C0Bs0VRj.mjs.map} +1 -1
- package/dist/{contract-infer-D8uEbJuu.mjs → contract-infer-2wtPflGH.mjs} +3 -3
- package/dist/{contract-infer-D8uEbJuu.mjs.map → contract-infer-2wtPflGH.mjs.map} +1 -1
- package/dist/{contract-space-aggregate-loader-DvZwdkrr.mjs → contract-space-aggregate-loader-Dbr3-jHF.mjs} +4 -4
- package/dist/{contract-space-aggregate-loader-DvZwdkrr.mjs.map → contract-space-aggregate-loader-Dbr3-jHF.mjs.map} +1 -1
- package/dist/{db-verify-v_vUKXTU.mjs → db-verify-CxHiSiTG.mjs} +4 -4
- package/dist/{db-verify-v_vUKXTU.mjs.map → db-verify-CxHiSiTG.mjs.map} +1 -1
- package/dist/exports/control-api.d.mts +1 -1
- 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-CxOVKAAh.mjs} +2 -2
- package/dist/{framework-components-fYXjz_in.mjs.map → framework-components-CxOVKAAh.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-R272pxux.mjs} +5 -58
- package/dist/init-R272pxux.mjs.map +1 -0
- package/dist/{inspect-live-schema-C6ohV_oQ.mjs → inspect-live-schema-RekOwfi5.mjs} +3 -3
- package/dist/{inspect-live-schema-C6ohV_oQ.mjs.map → inspect-live-schema-RekOwfi5.mjs.map} +1 -1
- package/dist/{migration-check-BiBJoYYW.mjs → migration-check-Dc0cOhKH.mjs} +2 -2
- package/dist/{migration-check-BiBJoYYW.mjs.map → migration-check-Dc0cOhKH.mjs.map} +1 -1
- package/dist/{migration-command-scaffold-CjvwO6at.mjs → migration-command-scaffold-ApB3NxWY.mjs} +3 -3
- package/dist/{migration-command-scaffold-CjvwO6at.mjs.map → migration-command-scaffold-ApB3NxWY.mjs.map} +1 -1
- package/dist/migration-graph-space-render-dmLLWift.mjs +1966 -0
- package/dist/migration-graph-space-render-dmLLWift.mjs.map +1 -0
- package/dist/migration-list-C5sXrl0U.mjs +228 -0
- package/dist/migration-list-C5sXrl0U.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-DD_vCbYW.mjs +203 -0
- package/dist/migration-log-DD_vCbYW.mjs.map +1 -0
- package/dist/{migration-plan-9DJ7q7_z.mjs → migration-plan-CeTjQOIG.mjs} +5 -5
- package/dist/{migration-plan-9DJ7q7_z.mjs.map → migration-plan-CeTjQOIG.mjs.map} +1 -1
- package/dist/migration-status-qV8ctwPy.mjs +432 -0
- package/dist/migration-status-qV8ctwPy.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-V1o-9LVK.mjs} +1 -1
- package/dist/{ref-advancement-DUZqsue6.mjs.map → ref-advancement-V1o-9LVK.mjs.map} +1 -1
- package/dist/telemetry-S-NGi9U6.mjs +122 -0
- package/dist/telemetry-S-NGi9U6.mjs.map +1 -0
- package/dist/{types-Dt_SfqFm.d.mts → types-Mh7mdPHM.d.mts} +13 -2
- package/dist/types-Mh7mdPHM.d.mts.map +1 -0
- package/dist/{verify-DCA9Sldu.mjs → verify-BdI-BgYi.mjs} +2 -2
- package/dist/{verify-DCA9Sldu.mjs.map → verify-BdI-BgYi.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/migration-graph.ts +143 -82
- package/src/commands/migration-list.ts +55 -25
- package/src/commands/migration-log.ts +23 -89
- package/src/commands/migration-status-overlay.ts +61 -0
- package/src/commands/migration-status.ts +431 -1055
- package/src/commands/telemetry/index.ts +107 -0
- package/src/commands/telemetry/status.ts +67 -0
- package/src/control-api/client.ts +11 -1
- package/src/control-api/operations/apply.ts +1 -0
- package/src/control-api/operations/db-apply.ts +24 -1
- package/src/control-api/operations/migration-apply.ts +10 -3
- package/src/control-api/types.ts +16 -1
- package/src/utils/cli-errors.ts +17 -0
- 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 +190 -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/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/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-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
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { userConfigPath, writeUserConfig } from '@prisma-next/cli-telemetry';
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import {
|
|
4
|
+
addGlobalOptions,
|
|
5
|
+
setCommandDescriptions,
|
|
6
|
+
setCommandExamples,
|
|
7
|
+
} from '../../utils/command-helpers';
|
|
8
|
+
import { formatCommandHelp } from '../../utils/formatters/help';
|
|
9
|
+
import {
|
|
10
|
+
type CommonCommandOptions,
|
|
11
|
+
parseGlobalFlags,
|
|
12
|
+
parseGlobalFlagsOrExit,
|
|
13
|
+
} from '../../utils/global-flags';
|
|
14
|
+
import { isCI } from '../../utils/is-ci';
|
|
15
|
+
import { createTerminalUI } from '../../utils/terminal-ui';
|
|
16
|
+
import { formatTelemetryStatusLines, resolveTelemetryStatus } from './status';
|
|
17
|
+
|
|
18
|
+
function createTelemetryStatusCommand(): Command {
|
|
19
|
+
const command = new Command('status');
|
|
20
|
+
setCommandDescriptions(
|
|
21
|
+
command,
|
|
22
|
+
'Show whether anonymous CLI telemetry is enabled and why',
|
|
23
|
+
'Reports whether telemetry is currently enabled or disabled and the reason\n' +
|
|
24
|
+
'(default-on, stored opt-out, environment opt-out, or CI), the path to your\n' +
|
|
25
|
+
'user-level config file, and whether an installation ID has been stored.\n' +
|
|
26
|
+
'Read-only: never sends an event, never mints an ID, never writes anything.',
|
|
27
|
+
);
|
|
28
|
+
return addGlobalOptions(command).action((options: CommonCommandOptions) => {
|
|
29
|
+
const flags = parseGlobalFlagsOrExit(options);
|
|
30
|
+
const ui = createTerminalUI(flags);
|
|
31
|
+
const status = resolveTelemetryStatus({ env: process.env, inCI: isCI() });
|
|
32
|
+
if (flags.json) {
|
|
33
|
+
ui.output(JSON.stringify(status));
|
|
34
|
+
} else {
|
|
35
|
+
for (const line of formatTelemetryStatusLines(status)) {
|
|
36
|
+
ui.output(line);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
process.exit(0);
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function createTelemetryEnableCommand(): Command {
|
|
44
|
+
const command = new Command('enable');
|
|
45
|
+
setCommandDescriptions(
|
|
46
|
+
command,
|
|
47
|
+
'Enable anonymous CLI telemetry',
|
|
48
|
+
'Stores "enableTelemetry": true in your user-level config and mints an\n' +
|
|
49
|
+
'installation ID if one is not already stored.',
|
|
50
|
+
);
|
|
51
|
+
return addGlobalOptions(command).action((options: CommonCommandOptions) => {
|
|
52
|
+
const flags = parseGlobalFlagsOrExit(options);
|
|
53
|
+
writeUserConfig({ enableTelemetry: true });
|
|
54
|
+
const ui = createTerminalUI(flags);
|
|
55
|
+
if (flags.json) {
|
|
56
|
+
ui.output(JSON.stringify({ enableTelemetry: true, configPath: userConfigPath() }));
|
|
57
|
+
} else {
|
|
58
|
+
ui.output(`Telemetry enabled. Preference stored in ${userConfigPath()}.`);
|
|
59
|
+
}
|
|
60
|
+
process.exit(0);
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function createTelemetryDisableCommand(): Command {
|
|
65
|
+
const command = new Command('disable');
|
|
66
|
+
setCommandDescriptions(
|
|
67
|
+
command,
|
|
68
|
+
'Disable anonymous CLI telemetry',
|
|
69
|
+
'Stores "enableTelemetry": false in your user-level config. No installation\n' +
|
|
70
|
+
'ID is minted and no event is sent.',
|
|
71
|
+
);
|
|
72
|
+
return addGlobalOptions(command).action((options: CommonCommandOptions) => {
|
|
73
|
+
const flags = parseGlobalFlagsOrExit(options);
|
|
74
|
+
writeUserConfig({ enableTelemetry: false });
|
|
75
|
+
const ui = createTerminalUI(flags);
|
|
76
|
+
if (flags.json) {
|
|
77
|
+
ui.output(JSON.stringify({ enableTelemetry: false, configPath: userConfigPath() }));
|
|
78
|
+
} else {
|
|
79
|
+
ui.output(`Telemetry disabled. Preference stored in ${userConfigPath()}.`);
|
|
80
|
+
}
|
|
81
|
+
process.exit(0);
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function createTelemetryCommand(): Command {
|
|
86
|
+
const command = new Command('telemetry');
|
|
87
|
+
setCommandDescriptions(
|
|
88
|
+
command,
|
|
89
|
+
'Inspect and change anonymous CLI telemetry',
|
|
90
|
+
'Show telemetry status, or enable / disable anonymous CLI usage data.\n' +
|
|
91
|
+
'Telemetry is on by default (opt-out); see https://prisma-next.dev/docs/cli/telemetry\n' +
|
|
92
|
+
'for what is collected and why.',
|
|
93
|
+
);
|
|
94
|
+
setCommandExamples(command, [
|
|
95
|
+
'prisma-next telemetry status',
|
|
96
|
+
'prisma-next telemetry disable',
|
|
97
|
+
'prisma-next telemetry enable',
|
|
98
|
+
]);
|
|
99
|
+
command.configureHelp({
|
|
100
|
+
formatHelp: (cmd) => formatCommandHelp({ command: cmd, flags: parseGlobalFlags({}) }),
|
|
101
|
+
subcommandDescription: () => '',
|
|
102
|
+
});
|
|
103
|
+
command.addCommand(createTelemetryStatusCommand());
|
|
104
|
+
command.addCommand(createTelemetryEnableCommand());
|
|
105
|
+
command.addCommand(createTelemetryDisableCommand());
|
|
106
|
+
return command;
|
|
107
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { readUserConfig, resolveGating, userConfigPath } from '@prisma-next/cli-telemetry';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Why telemetry resolves the way it does, in the order the CLI's
|
|
5
|
+
* `resolveTelemetryGate` evaluates: CI hard-disables first, then the env
|
|
6
|
+
* opt-outs, then the stored `enableTelemetry`, then the opt-out default.
|
|
7
|
+
*/
|
|
8
|
+
export type TelemetryStatusReason =
|
|
9
|
+
| 'ci'
|
|
10
|
+
| 'env-opt-out'
|
|
11
|
+
| 'stored-opt-out'
|
|
12
|
+
| 'stored-opt-in'
|
|
13
|
+
| 'default-on';
|
|
14
|
+
|
|
15
|
+
export interface TelemetryStatus {
|
|
16
|
+
readonly enabled: boolean;
|
|
17
|
+
readonly reason: TelemetryStatusReason;
|
|
18
|
+
readonly configPath: string;
|
|
19
|
+
readonly installationIdStored: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Resolves the same gate the runtime uses (CI check + `resolveGating`) and
|
|
24
|
+
* projects it into a user-facing status. Pure read: never mints, never
|
|
25
|
+
* writes. The `installationId` value itself is never surfaced — only its
|
|
26
|
+
* presence — so `status` discloses nothing identifying.
|
|
27
|
+
*/
|
|
28
|
+
export function resolveTelemetryStatus(inputs: {
|
|
29
|
+
readonly env: Readonly<Record<string, string | undefined>>;
|
|
30
|
+
readonly inCI: boolean;
|
|
31
|
+
}): TelemetryStatus {
|
|
32
|
+
const config = readUserConfig();
|
|
33
|
+
const configPath = userConfigPath();
|
|
34
|
+
const installationIdStored =
|
|
35
|
+
typeof config.installationId === 'string' && config.installationId.length > 0;
|
|
36
|
+
|
|
37
|
+
if (inputs.inCI) {
|
|
38
|
+
return { enabled: false, reason: 'ci', configPath, installationIdStored };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const gating = resolveGating({ env: inputs.env, config });
|
|
42
|
+
if (!gating.enabled) {
|
|
43
|
+
const reason: TelemetryStatusReason =
|
|
44
|
+
gating.reason === 'env-override' ? 'env-opt-out' : 'stored-opt-out';
|
|
45
|
+
return { enabled: false, reason, configPath, installationIdStored };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const reason: TelemetryStatusReason =
|
|
49
|
+
config.enableTelemetry === true ? 'stored-opt-in' : 'default-on';
|
|
50
|
+
return { enabled: true, reason, configPath, installationIdStored };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const REASON_EXPLANATION: Record<TelemetryStatusReason, string> = {
|
|
54
|
+
ci: 'CI environment detected — telemetry is hard-disabled.',
|
|
55
|
+
'env-opt-out': 'an environment opt-out is set (DO_NOT_TRACK / PRISMA_NEXT_DISABLE_TELEMETRY).',
|
|
56
|
+
'stored-opt-out': '"enableTelemetry": false is stored in your config.',
|
|
57
|
+
'stored-opt-in': '"enableTelemetry": true is stored in your config.',
|
|
58
|
+
'default-on': 'no explicit choice is stored, so the opt-out default applies.',
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export function formatTelemetryStatusLines(status: TelemetryStatus): string[] {
|
|
62
|
+
return [
|
|
63
|
+
`Telemetry is ${status.enabled ? 'enabled' : 'disabled'}: ${REASON_EXPLANATION[status.reason]}`,
|
|
64
|
+
`Config file: ${status.configPath}`,
|
|
65
|
+
`Installation ID: ${status.installationIdStored ? 'stored' : 'not stored'}`,
|
|
66
|
+
];
|
|
67
|
+
}
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
Contract,
|
|
3
|
+
ContractMarkerRecord,
|
|
4
|
+
LedgerEntryRecord,
|
|
5
|
+
} from '@prisma-next/contract/types';
|
|
2
6
|
import { emit as emitContractArtifacts } from '@prisma-next/emitter';
|
|
3
7
|
import type { TargetBoundComponentDescriptor } from '@prisma-next/framework-components/components';
|
|
4
8
|
import type {
|
|
@@ -447,6 +451,12 @@ class ControlClientImpl implements ControlClient {
|
|
|
447
451
|
return familyInstance.readAllMarkers({ driver });
|
|
448
452
|
}
|
|
449
453
|
|
|
454
|
+
/** Reads the per-migration journal; omit `space` to return every space. */
|
|
455
|
+
async readLedger(space?: string): Promise<readonly LedgerEntryRecord[]> {
|
|
456
|
+
const { driver, familyInstance } = await this.ensureConnected();
|
|
457
|
+
return familyInstance.readLedger({ driver, ...ifDefined('space', space) });
|
|
458
|
+
}
|
|
459
|
+
|
|
450
460
|
async migrationApply(options: MigrationApplyOptions): Promise<MigrationApplyResult> {
|
|
451
461
|
const { onProgress } = options;
|
|
452
462
|
await this.connectWithProgress(options.connection, 'migrationApply', onProgress);
|
|
@@ -141,6 +141,7 @@ export async function applyMigration<TFamilyId extends string, TTargetId extends
|
|
|
141
141
|
destinationContract: r.entry.destinationContract,
|
|
142
142
|
policy,
|
|
143
143
|
frameworkComponents,
|
|
144
|
+
migrationEdges: r.entry.migrationEdges,
|
|
144
145
|
// Per-space post-apply schema verification is non-strict: each
|
|
145
146
|
// space's `destinationContract` describes only its own slice; a
|
|
146
147
|
// strict verifier would treat every other space's tables as
|
|
@@ -5,6 +5,7 @@ import type {
|
|
|
5
5
|
ControlExtensionDescriptor,
|
|
6
6
|
ControlFamilyInstance,
|
|
7
7
|
MigrationOperationPolicy,
|
|
8
|
+
MigrationPlannerConflict,
|
|
8
9
|
MigrationPlanOperation,
|
|
9
10
|
OperationPreview,
|
|
10
11
|
TargetMigrationsCapability,
|
|
@@ -32,7 +33,12 @@ import type {
|
|
|
32
33
|
OnControlProgress,
|
|
33
34
|
PerSpaceExecutionEntry,
|
|
34
35
|
} from '../types';
|
|
35
|
-
import {
|
|
36
|
+
import {
|
|
37
|
+
applyMigration,
|
|
38
|
+
buildPerSpaceBreakdown,
|
|
39
|
+
collectOrdered,
|
|
40
|
+
type OrderedResolution,
|
|
41
|
+
} from './apply';
|
|
36
42
|
import { stripOperations } from './migration-helpers';
|
|
37
43
|
|
|
38
44
|
/**
|
|
@@ -175,6 +181,7 @@ export async function executeApply<TFamilyId extends string, TTargetId extends s
|
|
|
175
181
|
onProgress?.({ action, kind: 'spanEnd', spanId: SPAN_IDS.plan, outcome: 'ok' });
|
|
176
182
|
|
|
177
183
|
const orderedResolutions = collectOrdered(planResult.value.applyOrder, planResult.value.perSpace);
|
|
184
|
+
const plannerWarnings = aggregatePlannerWarnings(orderedResolutions);
|
|
178
185
|
|
|
179
186
|
// The destination's structural shape comes from the app's plan — its
|
|
180
187
|
// `destination` is the storage hash users see in CLI output.
|
|
@@ -202,6 +209,7 @@ export async function executeApply<TFamilyId extends string, TTargetId extends s
|
|
|
202
209
|
preview,
|
|
203
210
|
perSpace,
|
|
204
211
|
summary,
|
|
212
|
+
...ifDefined('warnings', plannerWarnings),
|
|
205
213
|
});
|
|
206
214
|
}
|
|
207
215
|
|
|
@@ -228,6 +236,7 @@ export async function executeApply<TFamilyId extends string, TTargetId extends s
|
|
|
228
236
|
summary: applied.failure.summary,
|
|
229
237
|
...ifDefined('why', applied.failure.why),
|
|
230
238
|
meta: applied.failure.meta,
|
|
239
|
+
...ifDefined('warnings', plannerWarnings),
|
|
231
240
|
});
|
|
232
241
|
}
|
|
233
242
|
|
|
@@ -246,9 +255,17 @@ export async function executeApply<TFamilyId extends string, TTargetId extends s
|
|
|
246
255
|
operationsExecuted: applied.value.totalOpsExecuted,
|
|
247
256
|
perSpace: applied.value.perSpace,
|
|
248
257
|
summary,
|
|
258
|
+
...ifDefined('warnings', plannerWarnings),
|
|
249
259
|
});
|
|
250
260
|
}
|
|
251
261
|
|
|
262
|
+
function aggregatePlannerWarnings(
|
|
263
|
+
orderedResolutions: readonly OrderedResolution[],
|
|
264
|
+
): readonly MigrationPlannerConflict[] | undefined {
|
|
265
|
+
const warnings = orderedResolutions.flatMap((r) => r.entry.warnings ?? []);
|
|
266
|
+
return warnings.length > 0 ? warnings : undefined;
|
|
267
|
+
}
|
|
268
|
+
|
|
252
269
|
/**
|
|
253
270
|
* Compare the live `_prisma_marker` rows against the aggregate's
|
|
254
271
|
* declared members. Any marker row whose `space` is not a member of
|
|
@@ -339,6 +356,7 @@ function wrapPlanResult(args: {
|
|
|
339
356
|
readonly preview: OperationPreview | undefined;
|
|
340
357
|
readonly perSpace: readonly PerSpaceExecutionEntry[];
|
|
341
358
|
readonly summary: string;
|
|
359
|
+
readonly warnings?: readonly MigrationPlannerConflict[];
|
|
342
360
|
}): DbInitResult | DbUpdateResult {
|
|
343
361
|
const success: DbInitSuccess | DbUpdateSuccess = {
|
|
344
362
|
mode: 'plan',
|
|
@@ -352,6 +370,7 @@ function wrapPlanResult(args: {
|
|
|
352
370
|
},
|
|
353
371
|
perSpace: args.perSpace,
|
|
354
372
|
summary: args.summary,
|
|
373
|
+
...ifDefined('warnings', args.warnings),
|
|
355
374
|
};
|
|
356
375
|
return ok(success);
|
|
357
376
|
}
|
|
@@ -363,6 +382,7 @@ function wrapApplyResult(args: {
|
|
|
363
382
|
readonly operationsExecuted: number;
|
|
364
383
|
readonly perSpace: readonly PerSpaceExecutionEntry[];
|
|
365
384
|
readonly summary: string;
|
|
385
|
+
readonly warnings?: readonly MigrationPlannerConflict[];
|
|
366
386
|
}): DbInitResult | DbUpdateResult {
|
|
367
387
|
const success: DbInitSuccess | DbUpdateSuccess = {
|
|
368
388
|
mode: 'apply',
|
|
@@ -380,6 +400,7 @@ function wrapApplyResult(args: {
|
|
|
380
400
|
: { storageHash: args.destination.storageHash },
|
|
381
401
|
perSpace: args.perSpace,
|
|
382
402
|
summary: args.summary,
|
|
403
|
+
...ifDefined('warnings', args.warnings),
|
|
383
404
|
};
|
|
384
405
|
return ok(success);
|
|
385
406
|
}
|
|
@@ -388,6 +409,7 @@ function buildRunnerFailure(args: {
|
|
|
388
409
|
readonly summary: string;
|
|
389
410
|
readonly why?: string;
|
|
390
411
|
readonly meta: Record<string, unknown>;
|
|
412
|
+
readonly warnings?: readonly MigrationPlannerConflict[];
|
|
391
413
|
}): DbInitResult | DbUpdateResult {
|
|
392
414
|
const failure: DbInitFailure | DbUpdateFailure = {
|
|
393
415
|
code: 'RUNNER_FAILED',
|
|
@@ -395,6 +417,7 @@ function buildRunnerFailure(args: {
|
|
|
395
417
|
why: args.why,
|
|
396
418
|
meta: args.meta,
|
|
397
419
|
conflicts: undefined,
|
|
420
|
+
...ifDefined('warnings', args.warnings),
|
|
398
421
|
};
|
|
399
422
|
return notOk(failure) as DbInitResult | DbUpdateResult;
|
|
400
423
|
}
|
|
@@ -7,6 +7,7 @@ import type {
|
|
|
7
7
|
TargetMigrationsCapability,
|
|
8
8
|
} from '@prisma-next/framework-components/control';
|
|
9
9
|
import {
|
|
10
|
+
buildSynthMigrationEdge,
|
|
10
11
|
type ContractMarkerRecordLike,
|
|
11
12
|
type ContractSpaceAggregate,
|
|
12
13
|
type ContractSpaceMember,
|
|
@@ -319,7 +320,7 @@ export async function executeMigrationApply<TFamilyId extends string, TTargetId
|
|
|
319
320
|
includeMarkers: true,
|
|
320
321
|
});
|
|
321
322
|
const totalMigrationsApplied = applied.value.orderedResolutions.reduce(
|
|
322
|
-
(sum, r) => sum +
|
|
323
|
+
(sum, r) => sum + r.entry.migrationEdges.length,
|
|
323
324
|
0,
|
|
324
325
|
);
|
|
325
326
|
const summary = `Applied ${totalMigrationsApplied} migration(s) (${applied.value.totalOpsExecuted} operation(s)) across ${orderedAll.length} contract space(s)`;
|
|
@@ -361,7 +362,13 @@ function buildAtHeadResolution(args: {
|
|
|
361
362
|
displayOps: [],
|
|
362
363
|
destinationContract: member.contract(),
|
|
363
364
|
strategy: 'graph-walk',
|
|
364
|
-
migrationEdges: [
|
|
365
|
+
migrationEdges: [
|
|
366
|
+
buildSynthMigrationEdge({
|
|
367
|
+
currentMarkerStorageHash: liveMarker?.storageHash,
|
|
368
|
+
destinationStorageHash: targetHash,
|
|
369
|
+
operationCount: 0,
|
|
370
|
+
}),
|
|
371
|
+
],
|
|
365
372
|
};
|
|
366
373
|
}
|
|
367
374
|
|
|
@@ -404,7 +411,7 @@ function buildSuccess(args: BuildSuccessArgs): MigrationApplySuccess {
|
|
|
404
411
|
// JSON-shape consumers (e.g. `parsed.applied.length` in integration
|
|
405
412
|
// tests). The aggregate per-space breakdown lives on `perSpace[]`.
|
|
406
413
|
const applied = args.orderedResolutions.flatMap((r) => {
|
|
407
|
-
const edges = r.entry.migrationEdges
|
|
414
|
+
const edges = r.entry.migrationEdges;
|
|
408
415
|
return edges.map((edge) => ({
|
|
409
416
|
spaceId: r.spaceId,
|
|
410
417
|
dirName: edge.dirName,
|
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,
|
|
@@ -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
|
|
|
@@ -876,6 +884,13 @@ export interface ControlClient {
|
|
|
876
884
|
*/
|
|
877
885
|
readAllMarkers(): Promise<ReadonlyMap<string, ContractMarkerRecord>>;
|
|
878
886
|
|
|
887
|
+
/**
|
|
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
|
+
|
|
879
894
|
/**
|
|
880
895
|
* Applies pre-planned on-disk migrations to the database.
|
|
881
896
|
* Each migration runs in its own transaction with full execution checks.
|
package/src/utils/cli-errors.ts
CHANGED
|
@@ -108,6 +108,23 @@ export function errorRefSetEmptySentinel(hash: string): CliStructuredError {
|
|
|
108
108
|
});
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
+
/**
|
|
112
|
+
* `--legend` was combined with a machine-readable or silent output flag.
|
|
113
|
+
* The legend is human-only decoration on stderr.
|
|
114
|
+
*/
|
|
115
|
+
export function errorLegendHumanOnly(
|
|
116
|
+
conflictingFlag: '--json' | '--dot' | '--quiet',
|
|
117
|
+
): CliStructuredError {
|
|
118
|
+
return errorRuntime('`--legend` is only available for human-readable output', {
|
|
119
|
+
why: `\`--legend\` prints a glyph key to stderr and cannot be combined with ${conflictingFlag}.`,
|
|
120
|
+
fix: `Omit ${conflictingFlag} to print the legend alongside the tree, or omit --legend when using ${conflictingFlag}.`,
|
|
121
|
+
meta: {
|
|
122
|
+
code: 'MIGRATION.LEGEND_HUMAN_ONLY',
|
|
123
|
+
conflictingFlag,
|
|
124
|
+
},
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
111
128
|
/**
|
|
112
129
|
* `--space <id>` was given a value that doesn't satisfy the contract-space
|
|
113
130
|
* naming rule (`[a-z][a-z0-9_-]{0,63}` per `isValidSpaceId`). Fires before
|
|
@@ -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
|
+
}
|