@prisma-next/cli 0.4.0-dev.9 → 0.4.1
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 +31 -18
- package/dist/cli-errors-CznZA5-d.mjs +5 -0
- package/dist/cli.mjs +8 -19
- package/dist/cli.mjs.map +1 -1
- package/dist/{client-CJxHfhze.mjs → client-DGKrciLM.mjs} +9 -8
- package/dist/{client-CJxHfhze.mjs.map → client-DGKrciLM.mjs.map} +1 -1
- package/dist/commands/contract-emit.d.mts.map +1 -1
- package/dist/commands/contract-emit.mjs +2 -7
- package/dist/commands/contract-infer.mjs +2 -8
- package/dist/commands/db-init.mjs +7 -8
- package/dist/commands/db-init.mjs.map +1 -1
- package/dist/commands/db-schema.mjs +5 -8
- package/dist/commands/db-schema.mjs.map +1 -1
- package/dist/commands/db-sign.mjs +7 -8
- package/dist/commands/db-sign.mjs.map +1 -1
- package/dist/commands/db-update.mjs +7 -8
- package/dist/commands/db-update.mjs.map +1 -1
- package/dist/commands/db-verify.mjs +8 -9
- package/dist/commands/db-verify.mjs.map +1 -1
- package/dist/commands/migration-apply.d.mts +1 -1
- package/dist/commands/migration-apply.d.mts.map +1 -1
- package/dist/commands/migration-apply.mjs +34 -26
- package/dist/commands/migration-apply.mjs.map +1 -1
- package/dist/commands/migration-new.d.mts.map +1 -1
- package/dist/commands/migration-new.mjs +48 -23
- package/dist/commands/migration-new.mjs.map +1 -1
- package/dist/commands/migration-plan.d.mts +6 -1
- package/dist/commands/migration-plan.d.mts.map +1 -1
- package/dist/commands/migration-plan.mjs +94 -71
- package/dist/commands/migration-plan.mjs.map +1 -1
- package/dist/commands/migration-ref.d.mts +1 -1
- package/dist/commands/migration-ref.mjs +5 -5
- package/dist/commands/migration-show.d.mts +2 -2
- package/dist/commands/migration-show.d.mts.map +1 -1
- package/dist/commands/migration-show.mjs +11 -16
- package/dist/commands/migration-show.mjs.map +1 -1
- package/dist/commands/migration-status.d.mts +4 -5
- package/dist/commands/migration-status.d.mts.map +1 -1
- package/dist/commands/migration-status.mjs +2 -7
- package/dist/config-loader-_xQZsw0i.mjs +90 -0
- package/dist/config-loader-_xQZsw0i.mjs.map +1 -0
- package/dist/config-loader.d.mts.map +1 -1
- package/dist/config-loader.mjs +1 -1
- package/dist/contract-emit-304WZtZJ.mjs +4 -0
- package/dist/{contract-emit-CKig_Lra.mjs → contract-emit-DgeWdonT.mjs} +25 -21
- package/dist/contract-emit-DgeWdonT.mjs.map +1 -0
- package/dist/{contract-emit-gpJNLGs7.mjs → contract-emit-mU1_B_m9.mjs} +18 -14
- package/dist/contract-emit-mU1_B_m9.mjs.map +1 -0
- package/dist/{contract-enrichment-CGW6mm-E.mjs → contract-enrichment-BV4KpbNW.mjs} +1 -1
- package/dist/{contract-enrichment-CGW6mm-E.mjs.map → contract-enrichment-BV4KpbNW.mjs.map} +1 -1
- package/dist/{contract-infer-BDJgg7Xb.mjs → contract-infer-CUbiWGX0.mjs} +4 -4
- package/dist/{contract-infer-BDJgg7Xb.mjs.map → contract-infer-CUbiWGX0.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 +4 -6
- package/dist/exports/index.mjs +2 -7
- package/dist/exports/index.mjs.map +1 -1
- package/dist/{extract-operation-statements-DZUJNmL3.mjs → extract-operation-statements-DWWFz1PK.mjs} +2 -2
- package/dist/{extract-operation-statements-DZUJNmL3.mjs.map → extract-operation-statements-DWWFz1PK.mjs.map} +1 -1
- package/dist/{extract-sql-ddl-DDMX-9mz.mjs → extract-sql-ddl-7zn_AFS8.mjs} +1 -1
- package/dist/{extract-sql-ddl-DDMX-9mz.mjs.map → extract-sql-ddl-7zn_AFS8.mjs.map} +1 -1
- package/dist/{framework-components-Bsr1GaIj.mjs → framework-components-B__p--vT.mjs} +2 -2
- package/dist/{framework-components-Bsr1GaIj.mjs.map → framework-components-B__p--vT.mjs.map} +1 -1
- package/dist/{init-DZWvhEP0.mjs → init-DRquYpPa.mjs} +3 -3
- package/dist/{init-DZWvhEP0.mjs.map → init-DRquYpPa.mjs.map} +1 -1
- package/dist/{inspect-live-schema-ChqrALmw.mjs → inspect-live-schema-wIYBTdL3.mjs} +6 -6
- package/dist/{inspect-live-schema-ChqrALmw.mjs.map → inspect-live-schema-wIYBTdL3.mjs.map} +1 -1
- package/dist/{migration-command-scaffold-B0oH_hyB.mjs → migration-command-scaffold-BC73xQSo.mjs} +7 -7
- package/dist/{migration-command-scaffold-B0oH_hyB.mjs.map → migration-command-scaffold-BC73xQSo.mjs.map} +1 -1
- package/dist/{migration-status-CPamfEPj.mjs → migration-status-CXBbScH5.mjs} +19 -35
- package/dist/migration-status-CXBbScH5.mjs.map +1 -0
- package/dist/{migrations-BIsjFjSV.mjs → migrations-DYRAjiVh.mjs} +4 -15
- package/dist/migrations-DYRAjiVh.mjs.map +1 -0
- package/dist/{progress-adapter-B-YvmcDu.mjs → progress-adapter-Bwouy73-.mjs} +1 -1
- package/dist/{progress-adapter-B-YvmcDu.mjs.map → progress-adapter-Bwouy73-.mjs.map} +1 -1
- package/dist/{result-handler-AFK4hxyX.mjs → result-handler-CGohaH1o.mjs} +22 -11
- package/dist/result-handler-CGohaH1o.mjs.map +1 -0
- package/dist/{terminal-ui-C5k88MmW.mjs → terminal-ui-BuPXVRFY.mjs} +1 -1
- package/dist/{terminal-ui-C5k88MmW.mjs.map → terminal-ui-BuPXVRFY.mjs.map} +1 -1
- package/dist/{validate-contract-deps-DBH6iTAD.mjs → validate-contract-deps-DZqv9m7H.mjs} +1 -1
- package/dist/{validate-contract-deps-DBH6iTAD.mjs.map → validate-contract-deps-DZqv9m7H.mjs.map} +1 -1
- package/dist/{verify-C56CuQc7.mjs → verify-Cm2UFuZA.mjs} +2 -2
- package/dist/{verify-C56CuQc7.mjs.map → verify-Cm2UFuZA.mjs.map} +1 -1
- package/package.json +16 -20
- package/src/cli.ts +1 -5
- package/src/commands/contract-emit.ts +9 -10
- package/src/commands/migration-apply.ts +34 -23
- package/src/commands/migration-new.ts +39 -17
- package/src/commands/migration-plan.ts +119 -104
- package/src/commands/migration-show.ts +6 -16
- package/src/commands/migration-status.ts +14 -34
- package/src/config-loader.ts +35 -29
- package/src/config-path-validation.ts +75 -0
- package/src/control-api/client.ts +2 -1
- package/src/control-api/operations/contract-emit.ts +24 -23
- package/src/control-api/types.ts +1 -1
- package/src/utils/command-helpers.ts +15 -19
- package/src/utils/formatters/graph-migration-mapper.ts +5 -14
- package/src/utils/formatters/help.ts +0 -1
- package/src/utils/formatters/migrations.ts +2 -29
- package/dist/cli-errors-BUuJr6py.mjs +0 -5
- package/dist/commands/migration-emit.d.mts +0 -38
- package/dist/commands/migration-emit.d.mts.map +0 -1
- package/dist/commands/migration-emit.mjs +0 -81
- package/dist/commands/migration-emit.mjs.map +0 -1
- package/dist/config-loader-C4VXKl8f.mjs +0 -43
- package/dist/config-loader-C4VXKl8f.mjs.map +0 -1
- package/dist/contract-emit-CKig_Lra.mjs.map +0 -1
- package/dist/contract-emit-CU-SYNe4.mjs +0 -6
- package/dist/contract-emit-gpJNLGs7.mjs.map +0 -1
- package/dist/migration-emit-Du4DBMqz.mjs +0 -125
- package/dist/migration-emit-Du4DBMqz.mjs.map +0 -1
- package/dist/migration-status-CPamfEPj.mjs.map +0 -1
- package/dist/migrations-BIsjFjSV.mjs.map +0 -1
- package/dist/result-handler-AFK4hxyX.mjs.map +0 -1
- package/src/commands/migration-emit.ts +0 -134
- package/src/lib/migration-emit.ts +0 -125
- package/src/lib/migration-strategy.ts +0 -49
- /package/dist/{cli-errors-Dic2eADK.d.mts → cli-errors-z37sV3eR.d.mts} +0 -0
|
@@ -2,7 +2,7 @@ import type { MigrationPlanOperation } from '@prisma-next/framework-components/c
|
|
|
2
2
|
import { findLatestMigration, reconstructGraph } from '@prisma-next/migration-tools/dag';
|
|
3
3
|
import { readMigrationPackage, readMigrationsDir } from '@prisma-next/migration-tools/io';
|
|
4
4
|
import type { MigrationBundle } from '@prisma-next/migration-tools/types';
|
|
5
|
-
import {
|
|
5
|
+
import { MigrationToolsError } from '@prisma-next/migration-tools/types';
|
|
6
6
|
import { notOk, ok, type Result } from '@prisma-next/utils/result';
|
|
7
7
|
import { Command } from 'commander';
|
|
8
8
|
import { relative, resolve } from 'pathe';
|
|
@@ -31,7 +31,7 @@ export interface MigrationShowResult {
|
|
|
31
31
|
readonly dirPath: string;
|
|
32
32
|
readonly from: string;
|
|
33
33
|
readonly to: string;
|
|
34
|
-
readonly migrationId: string
|
|
34
|
+
readonly migrationId: string;
|
|
35
35
|
readonly kind: string;
|
|
36
36
|
readonly createdAt: string;
|
|
37
37
|
readonly operations: readonly {
|
|
@@ -52,8 +52,7 @@ export function resolveByHashPrefix(
|
|
|
52
52
|
prefix: string,
|
|
53
53
|
): Result<MigrationBundle, CliStructuredError> {
|
|
54
54
|
const normalizedPrefix = prefix.startsWith('sha256:') ? prefix : `sha256:${prefix}`;
|
|
55
|
-
const
|
|
56
|
-
const matches = attested.filter((p) => p.manifest.migrationId!.startsWith(normalizedPrefix));
|
|
55
|
+
const matches = packages.filter((p) => p.manifest.migrationId.startsWith(normalizedPrefix));
|
|
57
56
|
|
|
58
57
|
if (matches.length === 1) {
|
|
59
58
|
return ok(matches[0]!);
|
|
@@ -62,7 +61,7 @@ export function resolveByHashPrefix(
|
|
|
62
61
|
if (matches.length === 0) {
|
|
63
62
|
return notOk(
|
|
64
63
|
errorRuntime('No migration found matching prefix', {
|
|
65
|
-
why: `No
|
|
64
|
+
why: `No migration has a migrationId starting with "${normalizedPrefix}"`,
|
|
66
65
|
fix: 'Run `prisma-next migration show` (no argument) to see the latest migration, or check the migrations directory for available packages.',
|
|
67
66
|
}),
|
|
68
67
|
);
|
|
@@ -132,16 +131,7 @@ async function executeMigrationShowCommand(
|
|
|
132
131
|
if (!resolved.ok) return resolved;
|
|
133
132
|
pkg = resolved.value;
|
|
134
133
|
} else {
|
|
135
|
-
const
|
|
136
|
-
if (attested.length === 0) {
|
|
137
|
-
return notOk(
|
|
138
|
-
errorRuntime('No attested migrations found', {
|
|
139
|
-
why: `All migrations in ${migrationsRelative} are drafts (migrationId: null)`,
|
|
140
|
-
fix: 'Run `prisma-next migration emit --dir <path>` to attest a draft migration.',
|
|
141
|
-
}),
|
|
142
|
-
);
|
|
143
|
-
}
|
|
144
|
-
const graph = reconstructGraph(attested);
|
|
134
|
+
const graph = reconstructGraph(allPackages);
|
|
145
135
|
const latestMigration = findLatestMigration(graph);
|
|
146
136
|
if (!latestMigration) {
|
|
147
137
|
return notOk(
|
|
@@ -151,7 +141,7 @@ async function executeMigrationShowCommand(
|
|
|
151
141
|
}),
|
|
152
142
|
);
|
|
153
143
|
}
|
|
154
|
-
const leafPkg =
|
|
144
|
+
const leafPkg = allPackages.find(
|
|
155
145
|
(p) => p.manifest.migrationId === latestMigration.migrationId,
|
|
156
146
|
);
|
|
157
147
|
if (!leafPkg) {
|
|
@@ -8,8 +8,7 @@ import {
|
|
|
8
8
|
import type { Refs } from '@prisma-next/migration-tools/refs';
|
|
9
9
|
import { readRefs, resolveRef } from '@prisma-next/migration-tools/refs';
|
|
10
10
|
import type {
|
|
11
|
-
|
|
12
|
-
DraftMigrationBundle,
|
|
11
|
+
MigrationBundle,
|
|
13
12
|
MigrationChainEntry,
|
|
14
13
|
MigrationGraph,
|
|
15
14
|
} from '@prisma-next/migration-tools/types';
|
|
@@ -62,7 +61,7 @@ export interface MigrationStatusEntry {
|
|
|
62
61
|
readonly dirName: string;
|
|
63
62
|
readonly from: string;
|
|
64
63
|
readonly to: string;
|
|
65
|
-
readonly migrationId: string
|
|
64
|
+
readonly migrationId: string;
|
|
66
65
|
readonly operationCount: number;
|
|
67
66
|
readonly operationSummary: string;
|
|
68
67
|
readonly hasDestructive: boolean;
|
|
@@ -87,7 +86,7 @@ export interface MigrationStatusResult {
|
|
|
87
86
|
readonly refName?: string;
|
|
88
87
|
readonly selectedPath: readonly {
|
|
89
88
|
readonly dirName: string;
|
|
90
|
-
readonly migrationId: string
|
|
89
|
+
readonly migrationId: string;
|
|
91
90
|
readonly from: string;
|
|
92
91
|
readonly to: string;
|
|
93
92
|
}[];
|
|
@@ -95,8 +94,7 @@ export interface MigrationStatusResult {
|
|
|
95
94
|
readonly summary: string;
|
|
96
95
|
readonly diagnostics: readonly StatusDiagnostic[];
|
|
97
96
|
readonly graph?: MigrationGraph;
|
|
98
|
-
readonly bundles?: readonly
|
|
99
|
-
readonly drafts?: readonly DraftMigrationBundle[];
|
|
97
|
+
readonly bundles?: readonly MigrationBundle[];
|
|
100
98
|
readonly edgeStatuses?: readonly EdgeStatus[];
|
|
101
99
|
readonly activeRefHash?: string;
|
|
102
100
|
readonly activeRefName?: string;
|
|
@@ -227,7 +225,7 @@ export function deriveEdgeStatuses(
|
|
|
227
225
|
*/
|
|
228
226
|
function buildMigrationEntries(
|
|
229
227
|
chain: readonly MigrationChainEntry[],
|
|
230
|
-
packages: readonly
|
|
228
|
+
packages: readonly MigrationBundle[],
|
|
231
229
|
mode: 'online' | 'offline',
|
|
232
230
|
markerHash: string | undefined,
|
|
233
231
|
edgeStatuses?: readonly EdgeStatus[],
|
|
@@ -436,11 +434,10 @@ async function executeMigrationStatusCommand(
|
|
|
436
434
|
});
|
|
437
435
|
}
|
|
438
436
|
|
|
439
|
-
let
|
|
440
|
-
let drafts: readonly DraftMigrationBundle[];
|
|
437
|
+
let bundles: readonly MigrationBundle[];
|
|
441
438
|
let graph: MigrationGraph;
|
|
442
439
|
try {
|
|
443
|
-
({
|
|
440
|
+
({ bundles, graph } = await loadAllBundles(migrationsDir));
|
|
444
441
|
} catch (error) {
|
|
445
442
|
if (MigrationToolsError.is(error)) {
|
|
446
443
|
return notOk(
|
|
@@ -454,18 +451,7 @@ async function executeMigrationStatusCommand(
|
|
|
454
451
|
);
|
|
455
452
|
}
|
|
456
453
|
|
|
457
|
-
if (
|
|
458
|
-
diagnostics.push({
|
|
459
|
-
code: 'MIGRATION.DRAFTS',
|
|
460
|
-
severity: 'warn',
|
|
461
|
-
message: `${drafts.length} draft migration(s) found: ${drafts.map((d) => d.dirName).join(', ')}`,
|
|
462
|
-
hints: [
|
|
463
|
-
"Run 'prisma-next migration emit --dir <path>' to attest draft migrations before applying",
|
|
464
|
-
],
|
|
465
|
-
});
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
if (attested.length === 0) {
|
|
454
|
+
if (bundles.length === 0) {
|
|
469
455
|
if (contractHash !== EMPTY_CONTRACT_HASH) {
|
|
470
456
|
diagnostics.push({
|
|
471
457
|
code: 'CONTRACT.AHEAD',
|
|
@@ -576,7 +562,7 @@ async function executeMigrationStatusCommand(
|
|
|
576
562
|
migrations: [],
|
|
577
563
|
targetHash: EMPTY_CONTRACT_HASH,
|
|
578
564
|
contractHash,
|
|
579
|
-
summary: `${
|
|
565
|
+
summary: `${bundles.length} migration(s) on disk`,
|
|
580
566
|
diagnostics,
|
|
581
567
|
markerHash,
|
|
582
568
|
...(statusRefs.length > 0 ? { refs: statusRefs } : {}),
|
|
@@ -616,12 +602,12 @@ async function executeMigrationStatusCommand(
|
|
|
616
602
|
migrations: [],
|
|
617
603
|
targetHash: EMPTY_CONTRACT_HASH,
|
|
618
604
|
contractHash,
|
|
619
|
-
summary: `${
|
|
605
|
+
summary: `${bundles.length} migration(s) on disk`,
|
|
620
606
|
diagnostics,
|
|
621
607
|
...ifDefined('markerHash', markerHash),
|
|
622
608
|
...(statusRefs.length > 0 ? { refs: statusRefs } : {}),
|
|
623
609
|
graph,
|
|
624
|
-
bundles
|
|
610
|
+
bundles,
|
|
625
611
|
diverged: true,
|
|
626
612
|
});
|
|
627
613
|
}
|
|
@@ -638,7 +624,7 @@ async function executeMigrationStatusCommand(
|
|
|
638
624
|
}
|
|
639
625
|
|
|
640
626
|
const edgeStatuses = deriveEdgeStatuses(graph, targetHash, contractHash, markerHash, mode);
|
|
641
|
-
const entries = buildMigrationEntries(chain,
|
|
627
|
+
const entries = buildMigrationEntries(chain, bundles, mode, markerHash, edgeStatuses);
|
|
642
628
|
|
|
643
629
|
const pendingCount = edgeStatuses.filter((e) => e.status === 'pending').length;
|
|
644
630
|
const appliedCount = edgeStatuses.filter((e) => e.status === 'applied').length;
|
|
@@ -646,7 +632,7 @@ async function executeMigrationStatusCommand(
|
|
|
646
632
|
let summary: string;
|
|
647
633
|
if (mode === 'online') {
|
|
648
634
|
if (markerHash !== undefined && !graph.nodes.has(markerHash) && markerHash === contractHash) {
|
|
649
|
-
summary = `${
|
|
635
|
+
summary = `${bundles.length} migration(s) on disk`;
|
|
650
636
|
} else if (activeRefHash && markerHash !== undefined) {
|
|
651
637
|
summary = summarizeRefDistance(graph, markerHash, activeRefHash, activeRefName!);
|
|
652
638
|
} else if (pendingCount === 0) {
|
|
@@ -706,8 +692,7 @@ async function executeMigrationStatusCommand(
|
|
|
706
692
|
...(statusRefs.length > 0 ? { refs: statusRefs } : {}),
|
|
707
693
|
...ifDefined('pathDecision', pathDecision),
|
|
708
694
|
graph,
|
|
709
|
-
bundles
|
|
710
|
-
...(drafts.length > 0 ? { drafts } : {}),
|
|
695
|
+
bundles,
|
|
711
696
|
edgeStatuses,
|
|
712
697
|
...ifDefined('activeRefHash', activeRefHash),
|
|
713
698
|
...ifDefined('activeRefName', activeRefName),
|
|
@@ -768,11 +753,6 @@ export function createMigrationStatusCommand(): Command {
|
|
|
768
753
|
activeRefHash: statusResult.activeRefHash,
|
|
769
754
|
activeRefName: statusResult.activeRefName,
|
|
770
755
|
edgeStatuses: statusResult.edgeStatuses,
|
|
771
|
-
draftEdges: statusResult.drafts?.map((d) => ({
|
|
772
|
-
from: d.manifest.from,
|
|
773
|
-
to: d.manifest.to,
|
|
774
|
-
dirName: d.dirName,
|
|
775
|
-
})),
|
|
776
756
|
});
|
|
777
757
|
|
|
778
758
|
const graphToRender =
|
package/src/config-loader.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { dirname, resolve } from 'node:path';
|
|
2
1
|
import type { PrismaNextConfig } from '@prisma-next/config/config-types';
|
|
3
2
|
import { ConfigValidationError, validateConfig } from '@prisma-next/config/config-validation';
|
|
4
3
|
import {
|
|
@@ -6,7 +5,41 @@ import {
|
|
|
6
5
|
errorConfigValidation,
|
|
7
6
|
errorUnexpected,
|
|
8
7
|
} from '@prisma-next/errors/control';
|
|
8
|
+
import { ifDefined } from '@prisma-next/utils/defined';
|
|
9
9
|
import { loadConfig as loadConfigC12 } from 'c12';
|
|
10
|
+
import { dirname, resolve } from 'pathe';
|
|
11
|
+
import { finalizeConfig } from './config-path-validation';
|
|
12
|
+
|
|
13
|
+
async function loadValidatedConfig(configPath?: string): Promise<PrismaNextConfig> {
|
|
14
|
+
const cwd = process.cwd();
|
|
15
|
+
const resolvedConfigPath = configPath ? resolve(cwd, configPath) : undefined;
|
|
16
|
+
const configCwd = resolvedConfigPath ? dirname(resolvedConfigPath) : cwd;
|
|
17
|
+
|
|
18
|
+
const result = await loadConfigC12<PrismaNextConfig>({
|
|
19
|
+
name: 'prisma-next',
|
|
20
|
+
...ifDefined('configFile', resolvedConfigPath),
|
|
21
|
+
cwd: configCwd,
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
// When a specific config file was requested, verify it was actually loaded
|
|
25
|
+
// (c12 falls back to searching by name if the specified file doesn't exist)
|
|
26
|
+
if (resolvedConfigPath && result.configFile !== resolvedConfigPath) {
|
|
27
|
+
throw errorConfigFileNotFound(resolvedConfigPath);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Check if config is missing or empty (c12 may return empty object when file doesn't exist)
|
|
31
|
+
if (!result.config || Object.keys(result.config).length === 0) {
|
|
32
|
+
// Use c12's configFile if available, otherwise use explicit configPath, otherwise omit path
|
|
33
|
+
const displayPath = result.configFile || resolvedConfigPath || configPath;
|
|
34
|
+
throw errorConfigFileNotFound(displayPath);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Validate config structure
|
|
38
|
+
validateConfig(result.config);
|
|
39
|
+
|
|
40
|
+
const loadedConfigDir = result.configFile ? dirname(result.configFile) : configCwd;
|
|
41
|
+
return finalizeConfig(result.config, loadedConfigDir);
|
|
42
|
+
}
|
|
10
43
|
|
|
11
44
|
/**
|
|
12
45
|
* Loads the Prisma Next config from a TypeScript file.
|
|
@@ -19,34 +52,7 @@ import { loadConfig as loadConfigC12 } from 'c12';
|
|
|
19
52
|
*/
|
|
20
53
|
export async function loadConfig(configPath?: string): Promise<PrismaNextConfig> {
|
|
21
54
|
try {
|
|
22
|
-
|
|
23
|
-
// Resolve config path to absolute path and set cwd to config directory when path is provided
|
|
24
|
-
const resolvedConfigPath = configPath ? resolve(cwd, configPath) : undefined;
|
|
25
|
-
const configCwd = resolvedConfigPath ? dirname(resolvedConfigPath) : cwd;
|
|
26
|
-
|
|
27
|
-
const result = await loadConfigC12<PrismaNextConfig>({
|
|
28
|
-
name: 'prisma-next',
|
|
29
|
-
...(resolvedConfigPath ? { configFile: resolvedConfigPath } : {}),
|
|
30
|
-
cwd: configCwd,
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
// When a specific config file was requested, verify it was actually loaded
|
|
34
|
-
// (c12 falls back to searching by name if the specified file doesn't exist)
|
|
35
|
-
if (resolvedConfigPath && result.configFile !== resolvedConfigPath) {
|
|
36
|
-
throw errorConfigFileNotFound(resolvedConfigPath);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// Check if config is missing or empty (c12 may return empty object when file doesn't exist)
|
|
40
|
-
if (!result.config || Object.keys(result.config).length === 0) {
|
|
41
|
-
// Use c12's configFile if available, otherwise use explicit configPath, otherwise omit path
|
|
42
|
-
const displayPath = result.configFile || resolvedConfigPath || configPath;
|
|
43
|
-
throw errorConfigFileNotFound(displayPath);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// Validate config structure
|
|
47
|
-
validateConfig(result.config);
|
|
48
|
-
|
|
49
|
-
return result.config;
|
|
55
|
+
return await loadValidatedConfig(configPath);
|
|
50
56
|
} catch (error) {
|
|
51
57
|
if (error instanceof ConfigValidationError) {
|
|
52
58
|
throw errorConfigValidation(error.field, {
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type ContractSourceProvider,
|
|
3
|
+
normalizeContractConfig,
|
|
4
|
+
type PrismaNextConfig,
|
|
5
|
+
} from '@prisma-next/config/config-types';
|
|
6
|
+
import { ConfigValidationError } from '@prisma-next/config/config-validation';
|
|
7
|
+
import { getEmittedArtifactPaths } from '@prisma-next/emitter';
|
|
8
|
+
import { resolve } from 'pathe';
|
|
9
|
+
|
|
10
|
+
function throwValidation(field: string, why: string): never {
|
|
11
|
+
throw new ConfigValidationError(field, why);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function finalizeContractSource(
|
|
15
|
+
source: ContractSourceProvider,
|
|
16
|
+
configDir: string,
|
|
17
|
+
): ContractSourceProvider {
|
|
18
|
+
const resolvedInputs = source.inputs?.map((input) => resolve(configDir, input));
|
|
19
|
+
if (resolvedInputs === undefined) {
|
|
20
|
+
return source;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return {
|
|
24
|
+
...source,
|
|
25
|
+
inputs: resolvedInputs,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function validateNoOutputsAreInputs(
|
|
30
|
+
inputs: readonly string[] | undefined,
|
|
31
|
+
output: string | undefined,
|
|
32
|
+
): void {
|
|
33
|
+
if (inputs === undefined || output === undefined) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
let emittedArtifactPaths: ReturnType<typeof getEmittedArtifactPaths>;
|
|
38
|
+
try {
|
|
39
|
+
emittedArtifactPaths = getEmittedArtifactPaths(output);
|
|
40
|
+
} catch (error) {
|
|
41
|
+
throwValidation('contract.output', error instanceof Error ? error.message : String(error));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const emittedPaths = new Set([emittedArtifactPaths.jsonPath, emittedArtifactPaths.dtsPath]);
|
|
45
|
+
|
|
46
|
+
for (const input of inputs) {
|
|
47
|
+
if (emittedPaths.has(input)) {
|
|
48
|
+
throwValidation(
|
|
49
|
+
'contract.source.inputs[]',
|
|
50
|
+
'Config.contract.source.inputs must not include emitted artifact paths derived from contract.output',
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function finalizeConfig(config: PrismaNextConfig, configDir: string): PrismaNextConfig {
|
|
57
|
+
if (!config.contract) {
|
|
58
|
+
return config;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const contract = normalizeContractConfig(config.contract);
|
|
62
|
+
const source = finalizeContractSource(contract.source, configDir);
|
|
63
|
+
const output = resolve(configDir, contract.output);
|
|
64
|
+
|
|
65
|
+
validateNoOutputsAreInputs(source.inputs, output);
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
...config,
|
|
69
|
+
contract: {
|
|
70
|
+
...contract,
|
|
71
|
+
source,
|
|
72
|
+
output,
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
}
|
|
@@ -496,8 +496,9 @@ class ControlClientImpl implements ControlClient {
|
|
|
496
496
|
authoringContributions: stack.authoringContributions,
|
|
497
497
|
codecLookup: stack.codecLookup,
|
|
498
498
|
controlMutationDefaults: stack.controlMutationDefaults,
|
|
499
|
+
resolvedInputs: contractConfig.source.inputs ?? [],
|
|
499
500
|
};
|
|
500
|
-
const providerResult = await contractConfig.
|
|
501
|
+
const providerResult = await contractConfig.source.load(sourceContext);
|
|
501
502
|
if (!providerResult.ok) {
|
|
502
503
|
onProgress?.({
|
|
503
504
|
action: 'emit',
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { mkdir, writeFile } from 'node:fs/promises';
|
|
2
2
|
import type { Contract } from '@prisma-next/contract/types';
|
|
3
|
-
import { emit } from '@prisma-next/emitter';
|
|
3
|
+
import { emit, getEmittedArtifactPaths } from '@prisma-next/emitter';
|
|
4
4
|
import { createControlStack } from '@prisma-next/framework-components/control';
|
|
5
5
|
import { abortable } from '@prisma-next/utils/abortable';
|
|
6
6
|
import { ifDefined } from '@prisma-next/utils/defined';
|
|
7
|
-
import { dirname
|
|
7
|
+
import { dirname } from 'pathe';
|
|
8
8
|
import { loadConfig } from '../../config-loader';
|
|
9
9
|
import { errorContractConfigMissing, errorRuntime } from '../../utils/cli-errors';
|
|
10
10
|
import { assertFrameworkComponentsCompatible } from '../../utils/framework-components';
|
|
@@ -72,27 +72,23 @@ export async function executeContractEmit(
|
|
|
72
72
|
why: 'Contract config must have output path. This should not happen if defineConfig() was used.',
|
|
73
73
|
});
|
|
74
74
|
}
|
|
75
|
-
|
|
75
|
+
|
|
76
|
+
// Validate source exists and is callable
|
|
77
|
+
if (typeof contractConfig.source?.load !== 'function') {
|
|
76
78
|
throw errorContractConfigMissing({
|
|
77
|
-
why: 'Contract config
|
|
79
|
+
why: 'Contract config must include a valid source provider object',
|
|
78
80
|
});
|
|
79
81
|
}
|
|
80
82
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
+
let outputPaths: ReturnType<typeof getEmittedArtifactPaths>;
|
|
84
|
+
try {
|
|
85
|
+
outputPaths = getEmittedArtifactPaths(contractConfig.output);
|
|
86
|
+
} catch (error) {
|
|
83
87
|
throw errorContractConfigMissing({
|
|
84
|
-
why:
|
|
88
|
+
why: error instanceof Error ? error.message : String(error),
|
|
85
89
|
});
|
|
86
90
|
}
|
|
87
|
-
|
|
88
|
-
// Normalize configPath and resolve artifact paths relative to config file directory
|
|
89
|
-
const normalizedConfigPath = resolve(configPath);
|
|
90
|
-
const configDir = dirname(normalizedConfigPath);
|
|
91
|
-
const outputJsonPath = isAbsolute(contractConfig.output)
|
|
92
|
-
? contractConfig.output
|
|
93
|
-
: join(configDir, contractConfig.output);
|
|
94
|
-
// Colocate .d.ts with .json (contract.json → contract.d.ts)
|
|
95
|
-
const outputDtsPath = `${outputJsonPath.slice(0, -5)}.d.ts`;
|
|
91
|
+
const { jsonPath: outputJsonPath, dtsPath: outputDtsPath } = outputPaths;
|
|
96
92
|
|
|
97
93
|
const stack = createControlStack(config);
|
|
98
94
|
|
|
@@ -102,39 +98,40 @@ export async function executeContractEmit(
|
|
|
102
98
|
authoringContributions: stack.authoringContributions,
|
|
103
99
|
codecLookup: stack.codecLookup,
|
|
104
100
|
controlMutationDefaults: stack.controlMutationDefaults,
|
|
101
|
+
resolvedInputs: contractConfig.source.inputs ?? [],
|
|
105
102
|
};
|
|
106
103
|
|
|
107
|
-
let providerResult: Awaited<ReturnType<typeof contractConfig.source>>;
|
|
104
|
+
let providerResult: Awaited<ReturnType<typeof contractConfig.source.load>>;
|
|
108
105
|
try {
|
|
109
|
-
providerResult = await unlessAborted(contractConfig.source(sourceContext));
|
|
106
|
+
providerResult = await unlessAborted(contractConfig.source.load(sourceContext));
|
|
110
107
|
} catch (error) {
|
|
111
108
|
if (signal.aborted || isAbortError(error)) {
|
|
112
109
|
throw error;
|
|
113
110
|
}
|
|
114
111
|
throw errorRuntime('Failed to resolve contract source', {
|
|
115
112
|
why: error instanceof Error ? error.message : String(error),
|
|
116
|
-
fix: 'Ensure contract.source resolves to ok(Contract) or returns structured diagnostics.',
|
|
113
|
+
fix: 'Ensure contract.source.load resolves to ok(Contract) or returns structured diagnostics.',
|
|
117
114
|
});
|
|
118
115
|
}
|
|
119
116
|
|
|
120
117
|
if (!isRecord(providerResult) || typeof providerResult.ok !== 'boolean') {
|
|
121
118
|
throw errorRuntime('Failed to resolve contract source', {
|
|
122
119
|
why: 'Contract source provider returned malformed result shape.',
|
|
123
|
-
fix: 'Ensure contract.source resolves to ok(Contract) or notOk({ summary, diagnostics }).',
|
|
120
|
+
fix: 'Ensure contract.source.load resolves to ok(Contract) or notOk({ summary, diagnostics }).',
|
|
124
121
|
});
|
|
125
122
|
}
|
|
126
123
|
|
|
127
124
|
if (providerResult.ok && !('value' in providerResult)) {
|
|
128
125
|
throw errorRuntime('Failed to resolve contract source', {
|
|
129
126
|
why: 'Contract source provider returned malformed success result: missing value.',
|
|
130
|
-
fix: 'Ensure contract.source success payload is ok(Contract).',
|
|
127
|
+
fix: 'Ensure contract.source.load success payload is ok(Contract).',
|
|
131
128
|
});
|
|
132
129
|
}
|
|
133
130
|
|
|
134
131
|
if (!providerResult.ok && !isProviderFailureLike(providerResult.failure)) {
|
|
135
132
|
throw errorRuntime('Failed to resolve contract source', {
|
|
136
133
|
why: 'Contract source provider returned malformed failure result: expected summary and diagnostics.',
|
|
137
|
-
fix: 'Ensure contract.source failure payload is notOk({ summary, diagnostics, meta? }).',
|
|
134
|
+
fix: 'Ensure contract.source.load failure payload is notOk({ summary, diagnostics, meta? }).',
|
|
138
135
|
});
|
|
139
136
|
}
|
|
140
137
|
|
|
@@ -160,7 +157,11 @@ export async function executeContractEmit(
|
|
|
160
157
|
const enrichedIR = enrichContract(providerResult.value as Contract, frameworkComponents);
|
|
161
158
|
|
|
162
159
|
familyInstance.validateContract(enrichedIR);
|
|
163
|
-
const emitResult = await unlessAborted(
|
|
160
|
+
const emitResult = await unlessAborted(
|
|
161
|
+
emit(enrichedIR, stack, config.family.emission, {
|
|
162
|
+
outputJsonPath: outputJsonPath,
|
|
163
|
+
}),
|
|
164
|
+
);
|
|
164
165
|
|
|
165
166
|
// Create directory if needed and write files (both colocated)
|
|
166
167
|
await unlessAborted(mkdir(dirname(outputJsonPath), { recursive: true }));
|
package/src/control-api/types.ts
CHANGED
|
@@ -248,7 +248,7 @@ export interface EmitContractConfig {
|
|
|
248
248
|
/**
|
|
249
249
|
* Contract source provider.
|
|
250
250
|
*/
|
|
251
|
-
readonly
|
|
251
|
+
readonly source: ContractSourceProvider;
|
|
252
252
|
/**
|
|
253
253
|
* Output path for contract.json.
|
|
254
254
|
* The .d.ts types file will be colocated (e.g., contract.json → contract.d.ts).
|
|
@@ -4,12 +4,7 @@ import { hasMigrations } from '@prisma-next/framework-components/control';
|
|
|
4
4
|
import type { PathDecision } from '@prisma-next/migration-tools/dag';
|
|
5
5
|
import { reconstructGraph } from '@prisma-next/migration-tools/dag';
|
|
6
6
|
import { readMigrationsDir } from '@prisma-next/migration-tools/io';
|
|
7
|
-
import type {
|
|
8
|
-
AttestedMigrationBundle,
|
|
9
|
-
DraftMigrationBundle,
|
|
10
|
-
MigrationGraph,
|
|
11
|
-
} from '@prisma-next/migration-tools/types';
|
|
12
|
-
import { isAttested, isDraft } from '@prisma-next/migration-tools/types';
|
|
7
|
+
import type { MigrationBundle, MigrationGraph } from '@prisma-next/migration-tools/types';
|
|
13
8
|
import { ifDefined } from '@prisma-next/utils/defined';
|
|
14
9
|
import type { Command } from 'commander';
|
|
15
10
|
import { relative, resolve } from 'pathe';
|
|
@@ -150,32 +145,33 @@ export function getTargetMigrations(target: ControlTargetDescriptor<string, stri
|
|
|
150
145
|
}
|
|
151
146
|
|
|
152
147
|
/**
|
|
153
|
-
* Reads the migrations directory
|
|
154
|
-
*
|
|
155
|
-
*
|
|
148
|
+
* Reads the migrations directory and builds the migration graph from all
|
|
149
|
+
* bundles. Throws on I/O or graph errors — callers handle error mapping.
|
|
150
|
+
*
|
|
151
|
+
* Every on-disk bundle is content-addressed (`migrationId` is always a
|
|
152
|
+
* string); there is no draft state to filter out.
|
|
156
153
|
*/
|
|
157
154
|
export async function loadMigrationBundles(migrationsDir: string): Promise<{
|
|
158
|
-
bundles: readonly
|
|
155
|
+
bundles: readonly MigrationBundle[];
|
|
159
156
|
graph: MigrationGraph;
|
|
160
157
|
}> {
|
|
161
|
-
const
|
|
162
|
-
const bundles = allBundles.filter(isAttested);
|
|
158
|
+
const bundles = await readMigrationsDir(migrationsDir);
|
|
163
159
|
const graph = reconstructGraph(bundles);
|
|
164
160
|
return { bundles, graph };
|
|
165
161
|
}
|
|
166
162
|
|
|
167
163
|
export interface MigrationBundleSet {
|
|
168
|
-
readonly
|
|
169
|
-
readonly drafts: readonly DraftMigrationBundle[];
|
|
164
|
+
readonly bundles: readonly MigrationBundle[];
|
|
170
165
|
readonly graph: MigrationGraph;
|
|
171
166
|
}
|
|
172
167
|
|
|
168
|
+
/**
|
|
169
|
+
* Alias of `loadMigrationBundles` retained for naming-clarity in commands
|
|
170
|
+
* that previously needed both attested and draft splits. With the
|
|
171
|
+
* collapse of the draft state, both helpers do the same thing.
|
|
172
|
+
*/
|
|
173
173
|
export async function loadAllBundles(migrationsDir: string): Promise<MigrationBundleSet> {
|
|
174
|
-
|
|
175
|
-
const attested = all.filter(isAttested);
|
|
176
|
-
const drafts = all.filter(isDraft);
|
|
177
|
-
const graph = reconstructGraph(attested);
|
|
178
|
-
return { attested, drafts, graph };
|
|
174
|
+
return loadMigrationBundles(migrationsDir);
|
|
179
175
|
}
|
|
180
176
|
|
|
181
177
|
/**
|
|
@@ -39,12 +39,6 @@ export interface EdgeStatus {
|
|
|
39
39
|
readonly status: EdgeStatusKind;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
export interface DraftEdge {
|
|
43
|
-
readonly from: string;
|
|
44
|
-
readonly to: string;
|
|
45
|
-
readonly dirName: string;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
42
|
export interface MigrationGraphInput {
|
|
49
43
|
readonly graph: MigrationGraph;
|
|
50
44
|
readonly mode: 'online' | 'offline';
|
|
@@ -58,8 +52,6 @@ export interface MigrationGraphInput {
|
|
|
58
52
|
* icons (✓/⧗) are baked into edge labels. Undefined in offline mode.
|
|
59
53
|
*/
|
|
60
54
|
readonly edgeStatuses?: readonly EdgeStatus[] | undefined;
|
|
61
|
-
/** Draft migrations to render as dashed edges. */
|
|
62
|
-
readonly draftEdges?: readonly DraftEdge[] | undefined;
|
|
63
55
|
}
|
|
64
56
|
|
|
65
57
|
export interface MigrationRenderInput {
|
|
@@ -204,7 +196,9 @@ export function migrationGraphToRenderInput(input: MigrationGraphInput): Migrati
|
|
|
204
196
|
spineTargetHash = lastEdge?.to ?? EMPTY_CONTRACT_HASH;
|
|
205
197
|
}
|
|
206
198
|
|
|
207
|
-
// Contract not in
|
|
199
|
+
// Contract not in the migration graph — connect from spine target with a
|
|
200
|
+
// dashed edge so the user can see the gap (contract has changed but no
|
|
201
|
+
// migration has been planned yet).
|
|
208
202
|
if (contractHash !== EMPTY_CONTRACT_HASH && !graph.nodes.has(contractHash)) {
|
|
209
203
|
const contractMarkers: NodeMarker[] = [];
|
|
210
204
|
if (mode === 'online' && markerHash === contractHash) {
|
|
@@ -216,13 +210,10 @@ export function migrationGraphToRenderInput(input: MigrationGraphInput): Migrati
|
|
|
216
210
|
markers: contractMarkers,
|
|
217
211
|
});
|
|
218
212
|
|
|
219
|
-
|
|
220
|
-
const fromHash = matchingDraft?.from ?? spineTargetHash;
|
|
221
|
-
if (graph.nodes.has(fromHash) || fromHash === spineTargetHash) {
|
|
213
|
+
if (graph.nodes.has(spineTargetHash) || spineTargetHash === EMPTY_CONTRACT_HASH) {
|
|
222
214
|
edgeList.push({
|
|
223
|
-
from: toShortId(
|
|
215
|
+
from: toShortId(spineTargetHash),
|
|
224
216
|
to: shortHash(contractHash),
|
|
225
|
-
...ifDefined('label', matchingDraft ? `${matchingDraft.dirName} [draft]` : undefined),
|
|
226
217
|
style: 'dashed',
|
|
227
218
|
});
|
|
228
219
|
}
|
|
@@ -137,7 +137,6 @@ function getCommandDocsUrl(commandPath: string): string | undefined {
|
|
|
137
137
|
'migration apply': 'https://pris.ly/migration-apply',
|
|
138
138
|
'migration show': 'https://pris.ly/migration-show',
|
|
139
139
|
'migration status': 'https://pris.ly/migration-status',
|
|
140
|
-
'migration emit': 'https://pris.ly/migration-emit',
|
|
141
140
|
};
|
|
142
141
|
return docsMap[commandPath];
|
|
143
142
|
}
|
|
@@ -136,10 +136,6 @@ export interface MigrationApplyCommandOutputResult {
|
|
|
136
136
|
};
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
-
export interface MigrationEmitCommandOutputResult {
|
|
140
|
-
readonly migrationId: string;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
139
|
export function formatMigrationApplyCommandOutput(
|
|
144
140
|
result: MigrationApplyCommandOutputResult,
|
|
145
141
|
flags: GlobalFlags,
|
|
@@ -182,31 +178,12 @@ export function formatMigrationApplyCommandOutput(
|
|
|
182
178
|
return lines.join('\n');
|
|
183
179
|
}
|
|
184
180
|
|
|
185
|
-
export function formatMigrationEmitCommandOutput(
|
|
186
|
-
result: MigrationEmitCommandOutputResult,
|
|
187
|
-
flags: GlobalFlags,
|
|
188
|
-
): string {
|
|
189
|
-
if (flags.quiet) {
|
|
190
|
-
return '';
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
const lines: string[] = [];
|
|
194
|
-
const useColor = flags.color !== false;
|
|
195
|
-
const formatGreen = createColorFormatter(useColor, green);
|
|
196
|
-
const formatDimText = (text: string) => formatDim(useColor, text);
|
|
197
|
-
|
|
198
|
-
lines.push(`${formatGreen('✔')} Emitted ops.json and attested migration`);
|
|
199
|
-
lines.push(formatDimText(` migrationId: ${result.migrationId}`));
|
|
200
|
-
|
|
201
|
-
return lines.join('\n');
|
|
202
|
-
}
|
|
203
|
-
|
|
204
181
|
interface MigrationShowResult {
|
|
205
182
|
readonly dirName: string;
|
|
206
183
|
readonly dirPath: string;
|
|
207
184
|
readonly from: string;
|
|
208
185
|
readonly to: string;
|
|
209
|
-
readonly migrationId: string
|
|
186
|
+
readonly migrationId: string;
|
|
210
187
|
readonly kind: string;
|
|
211
188
|
readonly createdAt: string;
|
|
212
189
|
readonly operations: readonly {
|
|
@@ -234,11 +211,7 @@ export function formatMigrationShowOutput(result: MigrationShowResult, flags: Gl
|
|
|
234
211
|
lines.push(`${formatDimText(` kind: ${result.kind}`)}`);
|
|
235
212
|
lines.push(`${formatDimText(` from: ${result.from}`)}`);
|
|
236
213
|
lines.push(`${formatDimText(` to: ${result.to}`)}`);
|
|
237
|
-
|
|
238
|
-
lines.push(`${formatDimText(` migrationId: ${result.migrationId}`)}`);
|
|
239
|
-
} else {
|
|
240
|
-
lines.push(`${formatYellow(' migrationId: (draft — not yet attested)')}`);
|
|
241
|
-
}
|
|
214
|
+
lines.push(`${formatDimText(` migrationId: ${result.migrationId}`)}`);
|
|
242
215
|
lines.push(`${formatDimText(` created: ${result.createdAt}`)}`);
|
|
243
216
|
|
|
244
217
|
lines.push('');
|