@prisma-next/cli 0.12.0-dev.39 → 0.12.0-dev.40
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 +12 -12
- package/dist/{client-CcqChq9N.mjs → client-BNdG504y.mjs} +4 -4
- package/dist/{client-CcqChq9N.mjs.map → client-BNdG504y.mjs.map} +1 -1
- package/dist/{command-helpers-DK_5ItoJ.mjs → command-helpers-xvg9oq4T.mjs} +21 -3
- 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 -4
- package/dist/commands/db-schema.mjs +3 -3
- package/dist/commands/db-sign.mjs +4 -4
- package/dist/commands/db-update.mjs +5 -5
- package/dist/commands/db-verify.mjs +1 -1
- package/dist/commands/migrate.mjs +5 -6
- 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 +13 -1
- package/dist/commands/migration-graph.d.mts.map +1 -1
- package/dist/commands/migration-graph.mjs +15 -16
- package/dist/commands/migration-graph.mjs.map +1 -1
- package/dist/commands/migration-list.mjs +1 -1
- package/dist/commands/migration-log.d.mts +18 -1
- package/dist/commands/migration-log.d.mts.map +1 -1
- package/dist/commands/migration-log.mjs +1 -1
- package/dist/commands/migration-new.mjs +3 -3
- 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 +12 -23
- package/dist/commands/migration-show.mjs.map +1 -1
- package/dist/commands/migration-status.d.mts.map +1 -1
- package/dist/commands/migration-status.mjs +1 -1
- package/dist/commands/ref.mjs +3 -3
- package/dist/commands/telemetry/index.mjs +1 -1
- package/dist/{contract-at-errors-DG3kjgoz.mjs → contract-at-errors-Wj3u4Xco.mjs} +2 -2
- package/dist/{contract-at-errors-DG3kjgoz.mjs.map → contract-at-errors-Wj3u4Xco.mjs.map} +1 -1
- package/dist/{contract-emit-C0Bs0VRj.mjs → contract-emit-COg18szA.mjs} +3 -3
- package/dist/{contract-emit-C0Bs0VRj.mjs.map → contract-emit-COg18szA.mjs.map} +1 -1
- package/dist/{contract-emit-BO0l6fnT.mjs → contract-emit-KyJNQK5-.mjs} +3 -3
- package/dist/{contract-emit-BO0l6fnT.mjs.map → contract-emit-KyJNQK5-.mjs.map} +1 -1
- package/dist/{contract-infer-DaHH7CGX.mjs → contract-infer-IEp0227u.mjs} +3 -3
- package/dist/{contract-infer-DaHH7CGX.mjs.map → contract-infer-IEp0227u.mjs.map} +1 -1
- package/dist/{contract-space-aggregate-loader-Dbr3-jHF.mjs → contract-space-aggregate-loader-BdRPfM3Q.mjs} +62 -4
- package/dist/{contract-space-aggregate-loader-Dbr3-jHF.mjs.map → contract-space-aggregate-loader-BdRPfM3Q.mjs.map} +1 -1
- package/dist/{db-verify-DppLAhYT.mjs → db-verify-C9k5KAyI.mjs} +4 -4
- package/dist/{db-verify-DppLAhYT.mjs.map → db-verify-C9k5KAyI.mjs.map} +1 -1
- package/dist/exports/control-api.mjs +2 -2
- package/dist/exports/index.mjs +1 -1
- package/dist/{framework-components-CxOVKAAh.mjs → framework-components-Be4inY3I.mjs} +2 -2
- package/dist/{framework-components-CxOVKAAh.mjs.map → framework-components-Be4inY3I.mjs.map} +1 -1
- package/dist/{init-R272pxux.mjs → init-BIxw3l7t.mjs} +3 -3
- package/dist/{init-R272pxux.mjs.map → init-BIxw3l7t.mjs.map} +1 -1
- package/dist/{inspect-live-schema-CfOSPCmR.mjs → inspect-live-schema-DXUFGQDe.mjs} +3 -3
- package/dist/{inspect-live-schema-CfOSPCmR.mjs.map → inspect-live-schema-DXUFGQDe.mjs.map} +1 -1
- package/dist/{migration-check-Dc0cOhKH.mjs → migration-check-CUavU7U9.mjs} +236 -88
- package/dist/migration-check-CUavU7U9.mjs.map +1 -0
- package/dist/{migration-command-scaffold-CIxCqBXa.mjs → migration-command-scaffold-omgKpt3K.mjs} +3 -3
- package/dist/{migration-command-scaffold-CIxCqBXa.mjs.map → migration-command-scaffold-omgKpt3K.mjs.map} +1 -1
- package/dist/{migration-graph-space-render-dmLLWift.mjs → migration-graph-space-render-ByJ83gxp.mjs} +2 -2
- package/dist/{migration-graph-space-render-dmLLWift.mjs.map → migration-graph-space-render-ByJ83gxp.mjs.map} +1 -1
- package/dist/{migration-list-C5sXrl0U.mjs → migration-list-jK6QeczE.mjs} +4 -4
- package/dist/{migration-list-C5sXrl0U.mjs.map → migration-list-jK6QeczE.mjs.map} +1 -1
- package/dist/{migration-log-CwP41Cke.mjs → migration-log-CW0EjxSr.mjs} +39 -27
- package/dist/migration-log-CW0EjxSr.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-CeTjQOIG.mjs → migration-plan-NHdlUwPG.mjs} +5 -6
- package/dist/{migration-plan-CeTjQOIG.mjs.map → migration-plan-NHdlUwPG.mjs.map} +1 -1
- package/dist/{migration-status-GMRiiLFP.mjs → migration-status-GZ6XfbWs.mjs} +21 -14
- package/dist/migration-status-GZ6XfbWs.mjs.map +1 -0
- package/dist/{ref-advancement-V1o-9LVK.mjs → ref-advancement-CJY9zOv7.mjs} +1 -1
- package/dist/{ref-advancement-V1o-9LVK.mjs.map → ref-advancement-CJY9zOv7.mjs.map} +1 -1
- package/dist/{telemetry-S-NGi9U6.mjs → telemetry-DQP0BvKv.mjs} +2 -2
- package/dist/{telemetry-S-NGi9U6.mjs.map → telemetry-DQP0BvKv.mjs.map} +1 -1
- package/dist/{verify-BdI-BgYi.mjs → verify-tvHRBBVP.mjs} +2 -2
- package/dist/{verify-BdI-BgYi.mjs.map → verify-tvHRBBVP.mjs.map} +1 -1
- package/package.json +18 -18
- package/src/commands/migration-check.ts +340 -117
- package/src/commands/migration-graph.ts +31 -19
- package/src/commands/migration-log.ts +33 -16
- package/src/commands/migration-show.ts +10 -38
- package/src/commands/migration-status.ts +19 -11
- package/src/utils/cli-errors.ts +28 -0
- package/src/utils/formatters/migration-log-table.ts +27 -17
- package/src/utils/migration-path-target.ts +39 -0
- package/dist/command-helpers-DK_5ItoJ.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/migration-check-Dc0cOhKH.mjs.map +0 -1
- package/dist/migration-log-CwP41Cke.mjs.map +0 -1
- package/dist/migration-status-GMRiiLFP.mjs.map +0 -1
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import type { LedgerEntryRecord } from '@prisma-next/contract/types';
|
|
2
2
|
import { MigrationToolsError } from '@prisma-next/migration-tools/errors';
|
|
3
|
+
import { ifDefined } from '@prisma-next/utils/defined';
|
|
3
4
|
import { notOk, ok, type Result } from '@prisma-next/utils/result';
|
|
4
5
|
import { Command } from 'commander';
|
|
5
6
|
import { loadConfig } from '../config-loader';
|
|
6
7
|
import { createControlClient } from '../control-api/client';
|
|
7
8
|
import {
|
|
8
9
|
CliStructuredError,
|
|
9
|
-
errorDatabaseConnectionRequired,
|
|
10
|
-
errorDriverRequired,
|
|
11
10
|
errorUnexpected,
|
|
12
11
|
mapMigrationToolsError,
|
|
12
|
+
requireLiveDatabase,
|
|
13
13
|
} from '../utils/cli-errors';
|
|
14
14
|
import {
|
|
15
15
|
addGlobalOptions,
|
|
@@ -24,6 +24,7 @@ import { createAnsiMigrationListStyler } from '../utils/formatters/migration-lis
|
|
|
24
24
|
import {
|
|
25
25
|
MIGRATION_LOG_EMPTY_MESSAGE,
|
|
26
26
|
renderMigrationLogTable,
|
|
27
|
+
type SerializedLedgerEntryRecord,
|
|
27
28
|
serializeLedgerEntriesForJson,
|
|
28
29
|
} from '../utils/formatters/migration-log-table';
|
|
29
30
|
import { formatStyledHeader } from '../utils/formatters/styled';
|
|
@@ -36,6 +37,12 @@ interface MigrationLogOptions extends CommonCommandOptions {
|
|
|
36
37
|
readonly db?: string;
|
|
37
38
|
readonly config?: string;
|
|
38
39
|
readonly utc?: boolean;
|
|
40
|
+
readonly ascii?: boolean;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface MigrationLogResult {
|
|
44
|
+
readonly ok: true;
|
|
45
|
+
readonly entries: readonly SerializedLedgerEntryRecord[];
|
|
39
46
|
}
|
|
40
47
|
|
|
41
48
|
export async function executeMigrationLogCommand(
|
|
@@ -47,16 +54,14 @@ export async function executeMigrationLogCommand(
|
|
|
47
54
|
const { configPath } = resolveMigrationPaths(options.config, config);
|
|
48
55
|
|
|
49
56
|
const dbConnection = options.db ?? config.db?.connection;
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
if (!config.driver) {
|
|
59
|
-
return notOk(errorDriverRequired({ why: 'Config.driver is required for migration log' }));
|
|
57
|
+
const missingDb = requireLiveDatabase({
|
|
58
|
+
dbConnection,
|
|
59
|
+
hasDriver: !!config.driver,
|
|
60
|
+
why: `migration log needs a database connection and driver to read the ledger (set db.connection in ${configPath}, or pass --db <url>)`,
|
|
61
|
+
commandName: 'migration log',
|
|
62
|
+
});
|
|
63
|
+
if (missingDb) {
|
|
64
|
+
return notOk(missingDb);
|
|
60
65
|
}
|
|
61
66
|
if (!targetSupportsMigrations(config.target)) {
|
|
62
67
|
return notOk(errorUnexpected('Target does not support migrations'));
|
|
@@ -81,7 +86,7 @@ export async function executeMigrationLogCommand(
|
|
|
81
86
|
family: config.family,
|
|
82
87
|
target: config.target,
|
|
83
88
|
adapter: config.adapter,
|
|
84
|
-
driver
|
|
89
|
+
...ifDefined('driver', config.driver),
|
|
85
90
|
extensionPacks: config.extensionPacks ?? [],
|
|
86
91
|
});
|
|
87
92
|
|
|
@@ -108,7 +113,8 @@ export function createMigrationLogCommand(): Command {
|
|
|
108
113
|
command,
|
|
109
114
|
'Show executed migration history',
|
|
110
115
|
'Reads the database ledger and displays every applied migration edge\n' +
|
|
111
|
-
'in chronological order, including rollbacks and re-applies
|
|
116
|
+
'in chronological order, including rollbacks and re-applies, merged\n' +
|
|
117
|
+
'across all contract spaces. Requires a database connection.',
|
|
112
118
|
);
|
|
113
119
|
setCommandExamples(command, [
|
|
114
120
|
'prisma-next migration log --db $DATABASE_URL',
|
|
@@ -125,19 +131,30 @@ export function createMigrationLogCommand(): Command {
|
|
|
125
131
|
.option('--db <url>', 'Database connection string')
|
|
126
132
|
.option('--config <path>', 'Path to prisma-next.config.ts')
|
|
127
133
|
.option('--utc', 'Render human timestamps in UTC instead of local time')
|
|
134
|
+
.option('--ascii', 'Use ASCII glyphs (pipe-friendly)')
|
|
128
135
|
.action(async (options: MigrationLogOptions) => {
|
|
129
136
|
const flags = parseGlobalFlagsOrExit(options);
|
|
130
137
|
const ui = createTerminalUI(flags);
|
|
131
138
|
const result = await executeMigrationLogCommand(options, flags, ui);
|
|
132
139
|
const exitCode = handleResult(result, flags, ui, (entries) => {
|
|
133
140
|
if (flags.json) {
|
|
134
|
-
|
|
141
|
+
const result: MigrationLogResult = {
|
|
142
|
+
ok: true,
|
|
143
|
+
entries: serializeLedgerEntriesForJson(entries),
|
|
144
|
+
};
|
|
145
|
+
ui.output(JSON.stringify(result, null, 2));
|
|
135
146
|
} else if (!flags.quiet) {
|
|
136
147
|
if (entries.length === 0) {
|
|
137
148
|
ui.output(MIGRATION_LOG_EMPTY_MESSAGE);
|
|
138
149
|
} else {
|
|
139
150
|
const styler = createAnsiMigrationListStyler({ useColor: ui.useColor });
|
|
140
|
-
ui.output(
|
|
151
|
+
ui.output(
|
|
152
|
+
renderMigrationLogTable(entries, {
|
|
153
|
+
utc: options.utc === true,
|
|
154
|
+
styler,
|
|
155
|
+
glyphMode: ui.resolveGlyphMode(options.ascii === true),
|
|
156
|
+
}),
|
|
157
|
+
);
|
|
141
158
|
}
|
|
142
159
|
}
|
|
143
160
|
});
|
|
@@ -13,7 +13,7 @@ import { castAs } from '@prisma-next/utils/casts';
|
|
|
13
13
|
import { ifDefined } from '@prisma-next/utils/defined';
|
|
14
14
|
import { notOk, ok, type Result } from '@prisma-next/utils/result';
|
|
15
15
|
import { Command } from 'commander';
|
|
16
|
-
import {
|
|
16
|
+
import { relative } from 'pathe';
|
|
17
17
|
import { loadConfig } from '../config-loader';
|
|
18
18
|
import { createControlClient } from '../control-api/client';
|
|
19
19
|
import {
|
|
@@ -36,6 +36,11 @@ import { formatMigrationShowOutput } from '../utils/formatters/migrations';
|
|
|
36
36
|
import { formatStyledHeader } from '../utils/formatters/styled';
|
|
37
37
|
import type { CommonCommandOptions } from '../utils/global-flags';
|
|
38
38
|
import { type GlobalFlags, parseGlobalFlagsOrExit } from '../utils/global-flags';
|
|
39
|
+
import {
|
|
40
|
+
findPackageByDirPath,
|
|
41
|
+
looksLikePath,
|
|
42
|
+
resolveAppTargetPath,
|
|
43
|
+
} from '../utils/migration-path-target';
|
|
39
44
|
import { handleResult } from '../utils/result-handler';
|
|
40
45
|
import { createTerminalUI, type TerminalUI } from '../utils/terminal-ui';
|
|
41
46
|
|
|
@@ -65,33 +70,6 @@ export interface MigrationShowResult {
|
|
|
65
70
|
readonly migration: MigrationShowPresent;
|
|
66
71
|
}
|
|
67
72
|
|
|
68
|
-
function looksLikePath(target: string): boolean {
|
|
69
|
-
return target.includes('/') || target.includes('\\');
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
export function resolveAppTargetPath(
|
|
73
|
-
target: string,
|
|
74
|
-
appMigrationsDir: string,
|
|
75
|
-
appMigrationsRelative: string,
|
|
76
|
-
): Result<string, CliStructuredError> {
|
|
77
|
-
const targetPath = resolve(target);
|
|
78
|
-
const relativeToApp = relative(appMigrationsDir, targetPath);
|
|
79
|
-
const isOutsideAppDir =
|
|
80
|
-
relativeToApp === '' ||
|
|
81
|
-
relativeToApp === '.' ||
|
|
82
|
-
relativeToApp.startsWith('..') ||
|
|
83
|
-
isAbsolute(relativeToApp);
|
|
84
|
-
if (isOutsideAppDir) {
|
|
85
|
-
return notOk(
|
|
86
|
-
errorRuntime('Target must point to an app-space migration', {
|
|
87
|
-
why: `Expected a path under ${appMigrationsRelative}, got ${target}`,
|
|
88
|
-
fix: 'Pass an app-space migration directory or use a hash prefix.',
|
|
89
|
-
}),
|
|
90
|
-
);
|
|
91
|
-
}
|
|
92
|
-
return ok(targetPath);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
73
|
function pkgToPresent(
|
|
96
74
|
spaceId: string,
|
|
97
75
|
pkg: OnDiskMigrationPackage,
|
|
@@ -117,14 +95,6 @@ function pkgToPresent(
|
|
|
117
95
|
};
|
|
118
96
|
}
|
|
119
97
|
|
|
120
|
-
function findPackageByDirPath(
|
|
121
|
-
packages: readonly OnDiskMigrationPackage[],
|
|
122
|
-
resolvedDirPath: string,
|
|
123
|
-
): OnDiskMigrationPackage | undefined {
|
|
124
|
-
const normalized = resolve(resolvedDirPath);
|
|
125
|
-
return packages.find((p) => resolve(p.dirPath) === normalized);
|
|
126
|
-
}
|
|
127
|
-
|
|
128
98
|
async function executeMigrationShowCommand(
|
|
129
99
|
target: string,
|
|
130
100
|
options: MigrationShowOptions,
|
|
@@ -260,11 +230,13 @@ export function createMigrationShowCommand(): Command {
|
|
|
260
230
|
command,
|
|
261
231
|
'Display migration package contents',
|
|
262
232
|
'Shows the operations, statement preview, and metadata for one app-space migration.\n' +
|
|
263
|
-
'Accepts a directory path, directory name, or hash prefix
|
|
233
|
+
'Accepts a directory path, directory name, or hash prefix.\n' +
|
|
234
|
+
'Offline — does not consult the database.',
|
|
264
235
|
);
|
|
265
236
|
setCommandExamples(command, [
|
|
266
237
|
'prisma-next migration show 20260101_100000_add_user',
|
|
267
238
|
'prisma-next migration show sha256:a1b2c3',
|
|
239
|
+
'prisma-next migration show 20260101_100000_add_user --json',
|
|
268
240
|
]);
|
|
269
241
|
setCommandSeeAlso(command, [
|
|
270
242
|
{ verb: 'migration status', oneLiner: 'Show migration path and pending status' },
|
|
@@ -273,7 +245,7 @@ export function createMigrationShowCommand(): Command {
|
|
|
273
245
|
{ verb: 'migration graph', oneLiner: 'Show the migration graph topology' },
|
|
274
246
|
]);
|
|
275
247
|
addGlobalOptions(command)
|
|
276
|
-
.argument('<target>', 'Migration reference: directory name, hash/prefix, or path')
|
|
248
|
+
.argument('<target>', 'Migration reference: directory name, hash/prefix, ref, or path')
|
|
277
249
|
.option('--config <path>', 'Path to prisma-next.config.ts')
|
|
278
250
|
.action(async (target: string, options: MigrationShowOptions) => {
|
|
279
251
|
const flags = parseGlobalFlagsOrExit(options);
|
|
@@ -21,10 +21,10 @@ import { loadConfig } from '../config-loader';
|
|
|
21
21
|
import { createControlClient } from '../control-api/client';
|
|
22
22
|
import {
|
|
23
23
|
CliStructuredError,
|
|
24
|
-
errorDatabaseConnectionRequired,
|
|
25
24
|
errorUnexpected,
|
|
26
25
|
mapMigrationToolsError,
|
|
27
26
|
mapRefResolutionError,
|
|
27
|
+
requireLiveDatabase,
|
|
28
28
|
} from '../utils/cli-errors';
|
|
29
29
|
import {
|
|
30
30
|
addGlobalOptions,
|
|
@@ -76,6 +76,7 @@ interface MigrationStatusOptions extends CommonCommandOptions {
|
|
|
76
76
|
readonly from?: string;
|
|
77
77
|
readonly space?: string;
|
|
78
78
|
readonly legend?: boolean;
|
|
79
|
+
readonly ascii?: boolean;
|
|
79
80
|
}
|
|
80
81
|
|
|
81
82
|
export interface MigrationStatusMigrationEntry extends MigrationListEntry {
|
|
@@ -271,13 +272,16 @@ async function executeMigrationStatusCommand(
|
|
|
271
272
|
const hasDriver = !!config.driver;
|
|
272
273
|
const usingFromOverride = options.from !== undefined;
|
|
273
274
|
|
|
274
|
-
if (!usingFromOverride
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
);
|
|
275
|
+
if (!usingFromOverride) {
|
|
276
|
+
const missingDb = requireLiveDatabase({
|
|
277
|
+
dbConnection,
|
|
278
|
+
hasDriver,
|
|
279
|
+
why: 'migration status needs a database connection to read the marker and ledger (or pass --from for offline path preview)',
|
|
280
|
+
retryCommand: 'prisma-next migration status --from <contract>',
|
|
281
|
+
});
|
|
282
|
+
if (missingDb) {
|
|
283
|
+
return notOk(missingDb);
|
|
284
|
+
}
|
|
281
285
|
}
|
|
282
286
|
|
|
283
287
|
let allRefs: Refs = {};
|
|
@@ -374,7 +378,7 @@ async function executeMigrationStatusCommand(
|
|
|
374
378
|
ui.stderr(
|
|
375
379
|
renderMigrationGraphLegend({
|
|
376
380
|
colorize: flags.color !== false,
|
|
377
|
-
glyphMode: ui.resolveGlyphMode(
|
|
381
|
+
glyphMode: ui.resolveGlyphMode(options.ascii === true),
|
|
378
382
|
}),
|
|
379
383
|
);
|
|
380
384
|
ui.stderr('');
|
|
@@ -449,7 +453,7 @@ async function executeMigrationStatusCommand(
|
|
|
449
453
|
|
|
450
454
|
const showAppliedOverlay = connected && !usingFromOverride;
|
|
451
455
|
const showDbMarker = connected && !usingFromOverride;
|
|
452
|
-
const glyphMode = ui.resolveGlyphMode(
|
|
456
|
+
const glyphMode = ui.resolveGlyphMode(options.ascii === true);
|
|
453
457
|
const colorize = flags.color !== false;
|
|
454
458
|
|
|
455
459
|
const statusSpaces: MigrationStatusSpaceResult[] = [];
|
|
@@ -630,7 +634,8 @@ export function createMigrationStatusCommand(): Command {
|
|
|
630
634
|
command,
|
|
631
635
|
'Show migration path and pending status',
|
|
632
636
|
'Shows which migrations are pending between the database marker and\n' +
|
|
633
|
-
'the target contract. Requires a database connection
|
|
637
|
+
'the target contract. Requires a database connection.\n' +
|
|
638
|
+
'Pass --from for an offline path preview without a database.\n' +
|
|
634
639
|
'Use `migration graph` for topology, `migration log` for history,\n' +
|
|
635
640
|
'and `migration list` for on-disk enumeration.',
|
|
636
641
|
);
|
|
@@ -638,6 +643,8 @@ export function createMigrationStatusCommand(): Command {
|
|
|
638
643
|
'prisma-next migration status --db $DATABASE_URL',
|
|
639
644
|
'prisma-next migration status --to production --db $DATABASE_URL',
|
|
640
645
|
'prisma-next migration status --from sha256:abc --to production',
|
|
646
|
+
'prisma-next migration status --from sha256:abc --to production --json',
|
|
647
|
+
'prisma-next migration status --ascii --from sha256:abc --to production',
|
|
641
648
|
'prisma-next migration status --legend --from sha256:abc --to production',
|
|
642
649
|
]);
|
|
643
650
|
setCommandSeeAlso(command, [
|
|
@@ -659,6 +666,7 @@ export function createMigrationStatusCommand(): Command {
|
|
|
659
666
|
'Origin contract reference; same grammar as --to. Supplying --from switches to offline path computation.',
|
|
660
667
|
)
|
|
661
668
|
.option('--legend', 'Print a key for the tree glyphs and lane colors')
|
|
669
|
+
.option('--ascii', 'Use ASCII glyphs (pipe-friendly)')
|
|
662
670
|
.action(async (options: MigrationStatusOptions) => {
|
|
663
671
|
const flags = parseGlobalFlagsOrExit(options);
|
|
664
672
|
const ui = createTerminalUI(flags);
|
package/src/utils/cli-errors.ts
CHANGED
|
@@ -28,6 +28,7 @@ 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 { ifDefined } from '@prisma-next/utils/defined';
|
|
31
32
|
import type { MigrateFailure } from '../control-api/types';
|
|
32
33
|
|
|
33
34
|
export {
|
|
@@ -342,6 +343,33 @@ export function mapMigrationToolsError(error: MigrationToolsError): CliStructure
|
|
|
342
343
|
});
|
|
343
344
|
}
|
|
344
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
|
+
|
|
345
373
|
/**
|
|
346
374
|
* Maps a `RefResolutionError` from the contract/migration reference
|
|
347
375
|
* resolver into a CLI structured error envelope.
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import type { LedgerEntryRecord } from '@prisma-next/contract/types';
|
|
2
2
|
import stringWidth from 'string-width';
|
|
3
|
+
import type { GlyphMode } from '../glyph-mode';
|
|
3
4
|
import {
|
|
4
5
|
abbreviateContractHash,
|
|
5
|
-
|
|
6
|
-
|
|
6
|
+
migrationListEmptySource,
|
|
7
|
+
migrationListForwardArrow,
|
|
7
8
|
} from './migration-list-data-column';
|
|
8
9
|
import { IDENTITY_MIGRATION_LIST_STYLER, type MigrationListStyler } from './migration-list-render';
|
|
9
10
|
|
|
@@ -12,6 +13,7 @@ export type LedgerTimestampMode = 'local' | 'utc' | 'iso';
|
|
|
12
13
|
export interface RenderMigrationLogTableOptions {
|
|
13
14
|
readonly utc?: boolean;
|
|
14
15
|
readonly styler?: MigrationListStyler;
|
|
16
|
+
readonly glyphMode?: GlyphMode;
|
|
15
17
|
}
|
|
16
18
|
|
|
17
19
|
export interface SerializedLedgerEntryRecord {
|
|
@@ -31,6 +33,7 @@ const HEADING_CHANGE = 'Change';
|
|
|
31
33
|
const HEADING_OPS = 'Ops';
|
|
32
34
|
const COLUMN_SEPARATOR = ' ';
|
|
33
35
|
const DIVIDER_CHAR = '─';
|
|
36
|
+
const ASCII_DIVIDER_CHAR = '-';
|
|
34
37
|
|
|
35
38
|
export function sortLedgerEntries(entries: readonly LedgerEntryRecord[]): LedgerEntryRecord[] {
|
|
36
39
|
return [...entries].sort((left, right) => {
|
|
@@ -65,27 +68,32 @@ export function formatLedgerAppliedAt(date: Date, mode: LedgerTimestampMode): st
|
|
|
65
68
|
return `${date.getFullYear()}-${pad2(date.getMonth() + 1)}-${pad2(date.getDate())} ${pad2(date.getHours())}:${pad2(date.getMinutes())}:${pad2(date.getSeconds())} ${sign}${offsetHours}:${offsetMins}`;
|
|
66
69
|
}
|
|
67
70
|
|
|
68
|
-
export function formatHashEndpoint(hash: string | null): string {
|
|
71
|
+
export function formatHashEndpoint(hash: string | null, glyphMode: GlyphMode = 'unicode'): string {
|
|
69
72
|
if (hash === null) {
|
|
70
|
-
return
|
|
73
|
+
return migrationListEmptySource(glyphMode);
|
|
71
74
|
}
|
|
72
75
|
return abbreviateContractHash(hash);
|
|
73
76
|
}
|
|
74
77
|
|
|
75
|
-
export function formatHashTransition(
|
|
76
|
-
|
|
78
|
+
export function formatHashTransition(
|
|
79
|
+
from: string | null,
|
|
80
|
+
to: string,
|
|
81
|
+
glyphMode: GlyphMode = 'unicode',
|
|
82
|
+
): string {
|
|
83
|
+
return `${formatHashEndpoint(from, glyphMode)} ${migrationListForwardArrow(glyphMode)} ${abbreviateContractHash(to)}`;
|
|
77
84
|
}
|
|
78
85
|
|
|
79
86
|
export function styleHashTransition(
|
|
80
87
|
from: string | null,
|
|
81
88
|
to: string,
|
|
82
89
|
styler: MigrationListStyler,
|
|
90
|
+
glyphMode: GlyphMode = 'unicode',
|
|
83
91
|
): string {
|
|
84
92
|
const fromPart =
|
|
85
93
|
from === null
|
|
86
|
-
? styler.glyph(
|
|
94
|
+
? styler.glyph(migrationListEmptySource(glyphMode))
|
|
87
95
|
: styler.sourceHash(abbreviateContractHash(from));
|
|
88
|
-
const arrow = styler.glyph(
|
|
96
|
+
const arrow = styler.glyph(migrationListForwardArrow(glyphMode));
|
|
89
97
|
const dest = styler.destHash(abbreviateContractHash(to));
|
|
90
98
|
return `${fromPart} ${arrow} ${dest}`;
|
|
91
99
|
}
|
|
@@ -99,8 +107,8 @@ function columnWidth(values: readonly string[]): number {
|
|
|
99
107
|
return values.reduce((max, value) => Math.max(max, stringWidth(value)), 0);
|
|
100
108
|
}
|
|
101
109
|
|
|
102
|
-
function padDividerCell(valueWidth: number): string {
|
|
103
|
-
return
|
|
110
|
+
function padDividerCell(valueWidth: number, dividerChar: string): string {
|
|
111
|
+
return dividerChar.repeat(valueWidth + 2);
|
|
104
112
|
}
|
|
105
113
|
|
|
106
114
|
function padTextCell(value: string, valueWidth: number): string {
|
|
@@ -122,13 +130,15 @@ export function renderMigrationLogTable(
|
|
|
122
130
|
}
|
|
123
131
|
|
|
124
132
|
const styler = options.styler ?? IDENTITY_MIGRATION_LIST_STYLER;
|
|
133
|
+
const glyphMode = options.glyphMode ?? 'unicode';
|
|
134
|
+
const dividerChar = glyphMode === 'ascii' ? ASCII_DIVIDER_CHAR : DIVIDER_CHAR;
|
|
125
135
|
const showSpace = new Set(sorted.map((entry) => entry.space)).size > 1;
|
|
126
136
|
const timestampMode: LedgerTimestampMode = options.utc ? 'utc' : 'local';
|
|
127
137
|
const rows = sorted.map((entry) => ({
|
|
128
138
|
appliedAt: formatLedgerAppliedAt(entry.appliedAt, timestampMode),
|
|
129
139
|
space: entry.space,
|
|
130
140
|
migrationName: entry.migrationName,
|
|
131
|
-
transition: formatHashTransition(entry.from, entry.to),
|
|
141
|
+
transition: formatHashTransition(entry.from, entry.to, glyphMode),
|
|
132
142
|
ops: `${entry.operationCount} ops`,
|
|
133
143
|
from: entry.from,
|
|
134
144
|
to: entry.to,
|
|
@@ -151,14 +161,14 @@ export function renderMigrationLogTable(
|
|
|
151
161
|
);
|
|
152
162
|
const heading = headingParts.join(COLUMN_SEPARATOR);
|
|
153
163
|
|
|
154
|
-
const dividerParts = [padDividerCell(appliedAtWidth)];
|
|
164
|
+
const dividerParts = [padDividerCell(appliedAtWidth, dividerChar)];
|
|
155
165
|
if (showSpace) {
|
|
156
|
-
dividerParts.push(padDividerCell(spaceWidth));
|
|
166
|
+
dividerParts.push(padDividerCell(spaceWidth, dividerChar));
|
|
157
167
|
}
|
|
158
168
|
dividerParts.push(
|
|
159
|
-
padDividerCell(nameWidth),
|
|
160
|
-
padDividerCell(transitionWidth),
|
|
161
|
-
padDividerCell(opsWidth),
|
|
169
|
+
padDividerCell(nameWidth, dividerChar),
|
|
170
|
+
padDividerCell(transitionWidth, dividerChar),
|
|
171
|
+
padDividerCell(opsWidth, dividerChar),
|
|
162
172
|
);
|
|
163
173
|
const divider = dividerParts.map((cell) => styler.summary(cell)).join(COLUMN_SEPARATOR);
|
|
164
174
|
|
|
@@ -169,7 +179,7 @@ export function renderMigrationLogTable(
|
|
|
169
179
|
}
|
|
170
180
|
parts.push(
|
|
171
181
|
padTextCell(styler.dirName(row.migrationName), nameWidth),
|
|
172
|
-
padTextCell(styleHashTransition(row.from, row.to, styler), transitionWidth),
|
|
182
|
+
padTextCell(styleHashTransition(row.from, row.to, styler, glyphMode), transitionWidth),
|
|
173
183
|
padOpsCell(row.ops, opsWidth),
|
|
174
184
|
);
|
|
175
185
|
return parts.join(COLUMN_SEPARATOR);
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { OnDiskMigrationPackage } from '@prisma-next/migration-tools/package';
|
|
2
|
+
import { notOk, ok, type Result } from '@prisma-next/utils/result';
|
|
3
|
+
import { isAbsolute, relative, resolve } from 'pathe';
|
|
4
|
+
import { type CliStructuredError, errorRuntime } from './cli-errors';
|
|
5
|
+
|
|
6
|
+
export function looksLikePath(target: string): boolean {
|
|
7
|
+
return target.includes('/') || target.includes('\\');
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function resolveAppTargetPath(
|
|
11
|
+
target: string,
|
|
12
|
+
appMigrationsDir: string,
|
|
13
|
+
appMigrationsRelative: string,
|
|
14
|
+
): Result<string, CliStructuredError> {
|
|
15
|
+
const targetPath = resolve(target);
|
|
16
|
+
const relativeToApp = relative(appMigrationsDir, targetPath);
|
|
17
|
+
const isOutsideAppDir =
|
|
18
|
+
relativeToApp === '' ||
|
|
19
|
+
relativeToApp === '.' ||
|
|
20
|
+
relativeToApp.startsWith('..') ||
|
|
21
|
+
isAbsolute(relativeToApp);
|
|
22
|
+
if (isOutsideAppDir) {
|
|
23
|
+
return notOk(
|
|
24
|
+
errorRuntime('Target must point to an app-space migration', {
|
|
25
|
+
why: `Expected a path under ${appMigrationsRelative}, got ${target}`,
|
|
26
|
+
fix: 'Pass an app-space migration directory or use a hash prefix.',
|
|
27
|
+
}),
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
return ok(targetPath);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function findPackageByDirPath(
|
|
34
|
+
packages: readonly OnDiskMigrationPackage[],
|
|
35
|
+
resolvedDirPath: string,
|
|
36
|
+
): OnDiskMigrationPackage | undefined {
|
|
37
|
+
const normalized = resolve(resolvedDirPath);
|
|
38
|
+
return packages.find((p) => resolve(p.dirPath) === normalized);
|
|
39
|
+
}
|