@prisma-next/cli 0.8.0-dev.7 → 0.8.0-dev.9

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.
Files changed (140) hide show
  1. package/README.md +7 -8
  2. package/dist/{cli-errors-D3_sMh2K.mjs → cli-errors-CF60g2cG.mjs} +40 -2
  3. package/dist/cli-errors-CF60g2cG.mjs.map +1 -0
  4. package/dist/cli.mjs +66 -18
  5. package/dist/cli.mjs.map +1 -1
  6. package/dist/{client-4d26awB-.mjs → client-XkUw4xD0.mjs} +10 -9
  7. package/dist/client-XkUw4xD0.mjs.map +1 -0
  8. package/dist/{command-helpers-BeZHkxV8.mjs → command-helpers-D3vL5yi8.mjs} +29 -6
  9. package/dist/command-helpers-D3vL5yi8.mjs.map +1 -0
  10. package/dist/commands/contract-emit.mjs +1 -1
  11. package/dist/commands/contract-infer.mjs +1 -1
  12. package/dist/commands/db-init.mjs +7 -7
  13. package/dist/commands/db-schema.mjs +5 -5
  14. package/dist/commands/db-sign.d.mts.map +1 -1
  15. package/dist/commands/db-sign.mjs +67 -25
  16. package/dist/commands/db-sign.mjs.map +1 -1
  17. package/dist/commands/db-update.d.mts.map +1 -1
  18. package/dist/commands/db-update.mjs +37 -9
  19. package/dist/commands/db-update.mjs.map +1 -1
  20. package/dist/commands/db-verify.mjs +1 -1
  21. package/dist/commands/migrate.d.mts +28 -0
  22. package/dist/commands/migrate.d.mts.map +1 -0
  23. package/dist/commands/{migration-apply.mjs → migrate.mjs} +54 -36
  24. package/dist/commands/migrate.mjs.map +1 -0
  25. package/dist/commands/migration-check.d.mts +18 -0
  26. package/dist/commands/migration-check.d.mts.map +1 -0
  27. package/dist/commands/migration-check.mjs +284 -0
  28. package/dist/commands/migration-check.mjs.map +1 -0
  29. package/dist/commands/migration-graph.d.mts +16 -0
  30. package/dist/commands/migration-graph.d.mts.map +1 -0
  31. package/dist/commands/migration-graph.mjs +141 -0
  32. package/dist/commands/migration-graph.mjs.map +1 -0
  33. package/dist/commands/migration-list.d.mts +20 -0
  34. package/dist/commands/migration-list.d.mts.map +1 -0
  35. package/dist/commands/migration-list.mjs +107 -0
  36. package/dist/commands/migration-list.mjs.map +1 -0
  37. package/dist/commands/migration-log.d.mts +21 -0
  38. package/dist/commands/migration-log.d.mts.map +1 -0
  39. package/dist/commands/migration-log.mjs +146 -0
  40. package/dist/commands/migration-log.mjs.map +1 -0
  41. package/dist/commands/migration-new.mjs +5 -5
  42. package/dist/commands/migration-plan.d.mts +2 -2
  43. package/dist/commands/migration-plan.d.mts.map +1 -1
  44. package/dist/commands/migration-plan.mjs +1 -1
  45. package/dist/commands/migration-show.d.mts +1 -1
  46. package/dist/commands/migration-show.d.mts.map +1 -1
  47. package/dist/commands/migration-show.mjs +85 -47
  48. package/dist/commands/migration-show.mjs.map +1 -1
  49. package/dist/commands/migration-status.d.mts +3 -15
  50. package/dist/commands/migration-status.d.mts.map +1 -1
  51. package/dist/commands/migration-status.mjs +732 -1
  52. package/dist/commands/migration-status.mjs.map +1 -0
  53. package/dist/commands/ref.d.mts +34 -0
  54. package/dist/commands/ref.d.mts.map +1 -0
  55. package/dist/commands/{migration-ref.mjs → ref.mjs} +28 -57
  56. package/dist/commands/ref.mjs.map +1 -0
  57. package/dist/{contract-emit-DLc5GYbr.mjs → contract-emit-CgoFk9AU.mjs} +3 -3
  58. package/dist/{contract-emit-DLc5GYbr.mjs.map → contract-emit-CgoFk9AU.mjs.map} +1 -1
  59. package/dist/{contract-emit-BhKR-D9Y.mjs → contract-emit-GpxW5RLe.mjs} +6 -6
  60. package/dist/{contract-emit-BhKR-D9Y.mjs.map → contract-emit-GpxW5RLe.mjs.map} +1 -1
  61. package/dist/{contract-infer-Bnla2kuK.mjs → contract-infer-D8edZOCi.mjs} +5 -5
  62. package/dist/{contract-infer-Bnla2kuK.mjs.map → contract-infer-D8edZOCi.mjs.map} +1 -1
  63. package/dist/{contract-space-aggregate-loader-BrwKK6Q6.mjs → contract-space-aggregate-loader-D68YpuPR.mjs} +3 -3
  64. package/dist/{contract-space-aggregate-loader-BrwKK6Q6.mjs.map → contract-space-aggregate-loader-D68YpuPR.mjs.map} +1 -1
  65. package/dist/{db-verify-DitNxDiE.mjs → db-verify-DtRB9iHJ.mjs} +7 -7
  66. package/dist/{db-verify-DitNxDiE.mjs.map → db-verify-DtRB9iHJ.mjs.map} +1 -1
  67. package/dist/errors-Cw6kyTyV.mjs +56 -0
  68. package/dist/errors-Cw6kyTyV.mjs.map +1 -0
  69. package/dist/exports/control-api.d.mts +1 -1
  70. package/dist/exports/control-api.mjs +2 -2
  71. package/dist/exports/index.mjs +1 -1
  72. package/dist/exports/init-output.mjs +1 -1
  73. package/dist/{framework-components-ChqVUxR-.mjs → framework-components-xFLFpZUO.mjs} +2 -2
  74. package/dist/{framework-components-ChqVUxR-.mjs.map → framework-components-xFLFpZUO.mjs.map} +1 -1
  75. package/dist/{global-flags-Icqpxk23.d.mts → global-flags-DGmw6Kqg.d.mts} +1 -1
  76. package/dist/{global-flags-Icqpxk23.d.mts.map → global-flags-DGmw6Kqg.d.mts.map} +1 -1
  77. package/dist/{migration-status-Do4Ei0i_.mjs → graph-render-eJDcLWny.mjs} +3 -692
  78. package/dist/graph-render-eJDcLWny.mjs.map +1 -0
  79. package/dist/{init-CcDCJMLk.mjs → init-BU2G31T8.mjs} +7 -6
  80. package/dist/{init-CcDCJMLk.mjs.map → init-BU2G31T8.mjs.map} +1 -1
  81. package/dist/{inspect-live-schema-CyzAzPzF.mjs → inspect-live-schema-CPPqCips.mjs} +4 -4
  82. package/dist/{inspect-live-schema-CyzAzPzF.mjs.map → inspect-live-schema-CPPqCips.mjs.map} +1 -1
  83. package/dist/migration-cli.mjs +1 -1
  84. package/dist/migration-cli.mjs.map +1 -1
  85. package/dist/{migration-command-scaffold-Jp1rosw8.mjs → migration-command-scaffold-B_ezTTwX.mjs} +4 -4
  86. package/dist/{migration-command-scaffold-Jp1rosw8.mjs.map → migration-command-scaffold-B_ezTTwX.mjs.map} +1 -1
  87. package/dist/{migration-plan-DTwYi61q.mjs → migration-plan-DWB-NTxH.mjs} +26 -24
  88. package/dist/migration-plan-DWB-NTxH.mjs.map +1 -0
  89. package/dist/migration-types-D2FW63pr.d.mts +15 -0
  90. package/dist/migration-types-D2FW63pr.d.mts.map +1 -0
  91. package/dist/{migrations-CTsyBXCA.mjs → migrations-DyUf5lTt.mjs} +2 -2
  92. package/dist/migrations-DyUf5lTt.mjs.map +1 -0
  93. package/dist/{output-DEg3SSnJ.mjs → output-B60Gw5fu.mjs} +1 -1
  94. package/dist/{output-DEg3SSnJ.mjs.map → output-B60Gw5fu.mjs.map} +1 -1
  95. package/dist/{result-handler-rmPVKIP2.mjs → result-handler-Bm_6dDYg.mjs} +2 -2
  96. package/dist/{result-handler-rmPVKIP2.mjs.map → result-handler-Bm_6dDYg.mjs.map} +1 -1
  97. package/dist/{terminal-ui-C_hFNbAn.mjs → terminal-ui-XtOQsqe9.mjs} +2 -54
  98. package/dist/terminal-ui-XtOQsqe9.mjs.map +1 -0
  99. package/dist/{types-LItU7E4l.d.mts → types-BS_wpjAY.d.mts} +2 -2
  100. package/dist/{types-LItU7E4l.d.mts.map → types-BS_wpjAY.d.mts.map} +1 -1
  101. package/dist/{verify-CiwNWM9N.mjs → verify-D7ypCCe6.mjs} +1 -1
  102. package/dist/{verify-CiwNWM9N.mjs.map → verify-D7ypCCe6.mjs.map} +1 -1
  103. package/package.json +39 -23
  104. package/src/cli.ts +78 -15
  105. package/src/commands/db-sign.ts +102 -32
  106. package/src/commands/db-update.ts +56 -4
  107. package/src/commands/{migration-apply.ts → migrate.ts} +54 -70
  108. package/src/commands/migration-check/exit-codes.ts +3 -0
  109. package/src/commands/migration-check.ts +369 -0
  110. package/src/commands/migration-graph.ts +184 -0
  111. package/src/commands/migration-list.ts +155 -0
  112. package/src/commands/migration-log.ts +218 -0
  113. package/src/commands/migration-plan.ts +29 -22
  114. package/src/commands/migration-show.ts +132 -60
  115. package/src/commands/migration-status.ts +77 -64
  116. package/src/commands/{migration-ref.ts → ref.ts} +32 -86
  117. package/src/control-api/operations/apply-aggregate.ts +4 -4
  118. package/src/control-api/operations/db-apply-aggregate.ts +3 -2
  119. package/src/control-api/operations/migration-apply.ts +4 -3
  120. package/src/migration-cli.ts +1 -1
  121. package/src/utils/cli-errors.ts +37 -0
  122. package/src/utils/command-helpers.ts +28 -3
  123. package/src/utils/contract-space-aggregate-loader.ts +2 -2
  124. package/src/utils/contract-space-seed-phase.ts +1 -1
  125. package/src/utils/formatters/help.ts +12 -2
  126. package/src/utils/formatters/migrations.ts +2 -2
  127. package/dist/cli-errors-D3_sMh2K.mjs.map +0 -1
  128. package/dist/client-4d26awB-.mjs.map +0 -1
  129. package/dist/command-helpers-BeZHkxV8.mjs.map +0 -1
  130. package/dist/commands/migration-apply.d.mts +0 -51
  131. package/dist/commands/migration-apply.d.mts.map +0 -1
  132. package/dist/commands/migration-apply.mjs.map +0 -1
  133. package/dist/commands/migration-ref.d.mts +0 -45
  134. package/dist/commands/migration-ref.d.mts.map +0 -1
  135. package/dist/commands/migration-ref.mjs.map +0 -1
  136. package/dist/migration-plan-DTwYi61q.mjs.map +0 -1
  137. package/dist/migration-status-Do4Ei0i_.mjs.map +0 -1
  138. package/dist/migrations-CTsyBXCA.mjs.map +0 -1
  139. package/dist/terminal-ui-C_hFNbAn.mjs.map +0 -1
  140. /package/dist/{cli-errors-B9OBbled.d.mts → cli-errors-DdcjVLJV.d.mts} +0 -0
@@ -0,0 +1,155 @@
1
+ import type { MigrationPlanOperation } from '@prisma-next/framework-components/control';
2
+ import { EMPTY_CONTRACT_HASH } from '@prisma-next/migration-tools/constants';
3
+ import { MigrationToolsError } from '@prisma-next/migration-tools/errors';
4
+ import { findPath } from '@prisma-next/migration-tools/migration-graph';
5
+ import { notOk, ok, type Result } from '@prisma-next/utils/result';
6
+ import { Command } from 'commander';
7
+ import { loadConfig } from '../config-loader';
8
+ import {
9
+ type CliStructuredError,
10
+ errorUnexpected,
11
+ mapMigrationToolsError,
12
+ } from '../utils/cli-errors';
13
+ import {
14
+ addGlobalOptions,
15
+ loadMigrationPackages,
16
+ resolveMigrationPaths,
17
+ setCommandDescriptions,
18
+ setCommandExamples,
19
+ setCommandSeeAlso,
20
+ } from '../utils/command-helpers';
21
+ import { formatStyledHeader } from '../utils/formatters/styled';
22
+ import type { CommonCommandOptions } from '../utils/global-flags';
23
+ import { type GlobalFlags, parseGlobalFlags } from '../utils/global-flags';
24
+ import { handleResult } from '../utils/result-handler';
25
+ import { TerminalUI } from '../utils/terminal-ui';
26
+
27
+ interface MigrationListOptions extends CommonCommandOptions {
28
+ readonly config?: string;
29
+ }
30
+
31
+ export interface MigrationListEntry {
32
+ readonly dirName: string;
33
+ readonly from: string;
34
+ readonly to: string;
35
+ readonly migrationHash: string;
36
+ readonly operationCount: number;
37
+ readonly createdAt: string;
38
+ }
39
+
40
+ export interface MigrationListResult {
41
+ readonly ok: true;
42
+ readonly migrations: readonly MigrationListEntry[];
43
+ readonly summary: string;
44
+ }
45
+
46
+ async function executeMigrationListCommand(
47
+ options: MigrationListOptions,
48
+ flags: GlobalFlags,
49
+ ui: TerminalUI,
50
+ ): Promise<Result<MigrationListResult, CliStructuredError>> {
51
+ const config = await loadConfig(options.config);
52
+ const { configPath, appMigrationsDir, appMigrationsRelative } = resolveMigrationPaths(
53
+ options.config,
54
+ config,
55
+ );
56
+
57
+ if (!flags.json && !flags.quiet) {
58
+ const header = formatStyledHeader({
59
+ command: 'migration list',
60
+ description: 'List on-disk migrations in topological order',
61
+ details: [
62
+ { label: 'config', value: configPath },
63
+ { label: 'migrations', value: appMigrationsRelative },
64
+ ],
65
+ flags,
66
+ });
67
+ ui.stderr(header);
68
+ }
69
+
70
+ let bundles: Awaited<ReturnType<typeof loadMigrationPackages>>['bundles'];
71
+ let graph: Awaited<ReturnType<typeof loadMigrationPackages>>['graph'];
72
+ try {
73
+ ({ bundles, graph } = await loadMigrationPackages(appMigrationsDir));
74
+ } catch (error) {
75
+ if (MigrationToolsError.is(error)) return notOk(mapMigrationToolsError(error));
76
+ return notOk(
77
+ errorUnexpected(error instanceof Error ? error.message : String(error), {
78
+ why: `Failed to read migrations: ${error instanceof Error ? error.message : String(error)}`,
79
+ }),
80
+ );
81
+ }
82
+
83
+ if (bundles.length === 0) {
84
+ return ok({ ok: true, migrations: [], summary: 'No migrations found' });
85
+ }
86
+
87
+ const leaves = [...graph.nodes].filter(
88
+ (n) => !graph.forwardChain.has(n) || graph.forwardChain.get(n)!.length === 0,
89
+ );
90
+ const targetHash =
91
+ leaves.length === 1 ? leaves[0]! : ([...graph.nodes].values().next().value as string);
92
+ const chain = findPath(graph, EMPTY_CONTRACT_HASH, targetHash) ?? [];
93
+
94
+ const pkgByDirName = new Map(bundles.map((p) => [p.dirName, p]));
95
+ const entries: MigrationListEntry[] = chain.map((edge) => {
96
+ const pkg = pkgByDirName.get(edge.dirName);
97
+ const ops = (pkg?.ops ?? []) as readonly MigrationPlanOperation[];
98
+ return {
99
+ dirName: edge.dirName,
100
+ from: edge.from,
101
+ to: edge.to,
102
+ migrationHash: edge.migrationHash,
103
+ operationCount: ops.length,
104
+ createdAt: edge.createdAt,
105
+ };
106
+ });
107
+
108
+ return ok({
109
+ ok: true,
110
+ migrations: entries,
111
+ summary: `${entries.length} migration(s) on disk`,
112
+ });
113
+ }
114
+
115
+ export function createMigrationListCommand(): Command {
116
+ const command = new Command('list');
117
+ setCommandDescriptions(
118
+ command,
119
+ 'List on-disk migrations in topological order',
120
+ 'Enumerates all migration packages under migrations/<space>/ in\n' +
121
+ 'topological order. Offline — does not consult the database.',
122
+ );
123
+ setCommandExamples(command, ['prisma-next migration list']);
124
+ setCommandSeeAlso(command, [
125
+ { verb: 'migration status', oneLiner: 'Show migration path and pending status' },
126
+ { verb: 'migration log', oneLiner: 'Show executed migration history' },
127
+ { verb: 'migration graph', oneLiner: 'Show the migration graph topology' },
128
+ { verb: 'migration show', oneLiner: 'Display migration package contents' },
129
+ ]);
130
+ addGlobalOptions(command)
131
+ .option('--config <path>', 'Path to prisma-next.config.ts')
132
+ .action(async (options: MigrationListOptions) => {
133
+ const flags = parseGlobalFlags(options);
134
+ const ui = new TerminalUI({ color: flags.color, interactive: flags.interactive });
135
+ const result = await executeMigrationListCommand(options, flags, ui);
136
+ const exitCode = handleResult(result, flags, ui, (listResult) => {
137
+ if (flags.json) {
138
+ ui.output(JSON.stringify(listResult, null, 2));
139
+ } else if (!flags.quiet) {
140
+ if (listResult.migrations.length === 0) {
141
+ ui.log('No migrations found');
142
+ } else {
143
+ for (const entry of listResult.migrations) {
144
+ ui.log(
145
+ `${entry.dirName} ${entry.migrationHash.slice(0, 16)}… ${entry.operationCount} op(s)`,
146
+ );
147
+ }
148
+ ui.log(`\n${listResult.summary}`);
149
+ }
150
+ }
151
+ });
152
+ process.exit(exitCode);
153
+ });
154
+ return command;
155
+ }
@@ -0,0 +1,218 @@
1
+ import type { MigrationPlanOperation } from '@prisma-next/framework-components/control';
2
+ import { EMPTY_CONTRACT_HASH } from '@prisma-next/migration-tools/constants';
3
+ import { MigrationToolsError } from '@prisma-next/migration-tools/errors';
4
+ import { findPath } from '@prisma-next/migration-tools/migration-graph';
5
+ import { notOk, ok, type Result } from '@prisma-next/utils/result';
6
+ import { cyan, dim } from 'colorette';
7
+ import { Command } from 'commander';
8
+ import { loadConfig } from '../config-loader';
9
+ import { createControlClient } from '../control-api/client';
10
+ import {
11
+ type CliStructuredError,
12
+ errorDatabaseConnectionRequired,
13
+ errorDriverRequired,
14
+ errorUnexpected,
15
+ mapMigrationToolsError,
16
+ } from '../utils/cli-errors';
17
+ import {
18
+ addGlobalOptions,
19
+ loadMigrationPackages,
20
+ maskConnectionUrl,
21
+ resolveMigrationPaths,
22
+ setCommandDescriptions,
23
+ setCommandExamples,
24
+ setCommandSeeAlso,
25
+ targetSupportsMigrations,
26
+ } from '../utils/command-helpers';
27
+ import { formatStyledHeader } from '../utils/formatters/styled';
28
+ import type { CommonCommandOptions } from '../utils/global-flags';
29
+ import { type GlobalFlags, parseGlobalFlags } from '../utils/global-flags';
30
+ import { handleResult } from '../utils/result-handler';
31
+ import { TerminalUI } from '../utils/terminal-ui';
32
+
33
+ interface MigrationLogOptions extends CommonCommandOptions {
34
+ readonly db?: string;
35
+ readonly config?: string;
36
+ }
37
+
38
+ export interface MigrationLogEntry {
39
+ readonly dirName: string;
40
+ readonly from: string;
41
+ readonly to: string;
42
+ readonly migrationHash: string;
43
+ readonly operationCount: number;
44
+ readonly createdAt: string;
45
+ }
46
+
47
+ export interface MigrationLogResult {
48
+ readonly ok: true;
49
+ readonly markerHash: string | null;
50
+ readonly applied: readonly MigrationLogEntry[];
51
+ readonly summary: string;
52
+ }
53
+
54
+ async function executeMigrationLogCommand(
55
+ options: MigrationLogOptions,
56
+ flags: GlobalFlags,
57
+ ui: TerminalUI,
58
+ ): Promise<Result<MigrationLogResult, CliStructuredError>> {
59
+ const config = await loadConfig(options.config);
60
+ const { configPath, appMigrationsDir, appMigrationsRelative } = resolveMigrationPaths(
61
+ options.config,
62
+ config,
63
+ );
64
+
65
+ const dbConnection = options.db ?? config.db?.connection;
66
+ if (!dbConnection) {
67
+ return notOk(
68
+ errorDatabaseConnectionRequired({
69
+ why: `Database connection is required for migration log (set db.connection in ${configPath}, or pass --db <url>)`,
70
+ commandName: 'migration log',
71
+ }),
72
+ );
73
+ }
74
+ if (!config.driver) {
75
+ return notOk(errorDriverRequired({ why: 'Config.driver is required for migration log' }));
76
+ }
77
+ if (!targetSupportsMigrations(config.target)) {
78
+ return notOk(errorUnexpected('Target does not support migrations'));
79
+ }
80
+
81
+ if (!flags.json && !flags.quiet) {
82
+ const header = formatStyledHeader({
83
+ command: 'migration log',
84
+ description: 'Show executed migration history',
85
+ details: [
86
+ { label: 'config', value: configPath },
87
+ { label: 'migrations', value: appMigrationsRelative },
88
+ ...(typeof dbConnection === 'string'
89
+ ? [{ label: 'database', value: maskConnectionUrl(dbConnection) }]
90
+ : []),
91
+ ],
92
+ flags,
93
+ });
94
+ ui.stderr(header);
95
+ }
96
+
97
+ let bundles: Awaited<ReturnType<typeof loadMigrationPackages>>['bundles'];
98
+ let graph: Awaited<ReturnType<typeof loadMigrationPackages>>['graph'];
99
+ try {
100
+ ({ bundles, graph } = await loadMigrationPackages(appMigrationsDir));
101
+ } catch (error) {
102
+ if (MigrationToolsError.is(error)) return notOk(mapMigrationToolsError(error));
103
+ return notOk(
104
+ errorUnexpected(error instanceof Error ? error.message : String(error), {
105
+ why: `Failed to read migrations: ${error instanceof Error ? error.message : String(error)}`,
106
+ }),
107
+ );
108
+ }
109
+
110
+ const client = createControlClient({
111
+ family: config.family,
112
+ target: config.target,
113
+ adapter: config.adapter,
114
+ driver: config.driver,
115
+ extensionPacks: config.extensionPacks ?? [],
116
+ });
117
+
118
+ try {
119
+ await client.connect(dbConnection);
120
+ const marker = await client.readMarker();
121
+ const markerHash = marker?.storageHash ?? null;
122
+
123
+ if (!markerHash) {
124
+ return ok({
125
+ ok: true,
126
+ markerHash: null,
127
+ applied: [],
128
+ summary: 'No migrations applied (database has no marker)',
129
+ });
130
+ }
131
+
132
+ const appliedPath = findPath(graph, EMPTY_CONTRACT_HASH, markerHash);
133
+ if (appliedPath === null) {
134
+ return notOk(
135
+ errorUnexpected('Database marker is not reachable from migration history', {
136
+ why: `Marker hash ${markerHash} is not reachable from the root of the on-disk migration graph.`,
137
+ fix: 'The database may have been migrated outside this project. Use `migration status` to inspect the current state.',
138
+ }),
139
+ );
140
+ }
141
+ const pkgByDirName = new Map(bundles.map((p) => [p.dirName, p]));
142
+ const entries: MigrationLogEntry[] = appliedPath.map((edge) => {
143
+ const pkg = pkgByDirName.get(edge.dirName);
144
+ const ops = (pkg?.ops ?? []) as readonly MigrationPlanOperation[];
145
+ return {
146
+ dirName: edge.dirName,
147
+ from: edge.from,
148
+ to: edge.to,
149
+ migrationHash: edge.migrationHash,
150
+ operationCount: ops.length,
151
+ createdAt: edge.createdAt,
152
+ };
153
+ });
154
+
155
+ return ok({
156
+ ok: true,
157
+ markerHash,
158
+ applied: entries,
159
+ summary: `${entries.length} migration(s) applied`,
160
+ });
161
+ } catch (error) {
162
+ if (MigrationToolsError.is(error)) return notOk(mapMigrationToolsError(error));
163
+ return notOk(
164
+ errorUnexpected(error instanceof Error ? error.message : String(error), {
165
+ why: `Failed to read migration log: ${error instanceof Error ? error.message : String(error)}`,
166
+ }),
167
+ );
168
+ } finally {
169
+ await client.close();
170
+ }
171
+ }
172
+
173
+ export function createMigrationLogCommand(): Command {
174
+ const command = new Command('log');
175
+ setCommandDescriptions(
176
+ command,
177
+ 'Show executed migration history',
178
+ 'Reads the database marker and displays the applied migration chain\n' +
179
+ 'from the initial state to the current marker position.',
180
+ );
181
+ setCommandExamples(command, [
182
+ 'prisma-next migration log --db $DATABASE_URL',
183
+ 'prisma-next migration log --json --db $DATABASE_URL',
184
+ ]);
185
+ setCommandSeeAlso(command, [
186
+ { verb: 'migration status', oneLiner: 'Show migration path and pending status' },
187
+ { verb: 'migration list', oneLiner: 'List on-disk migrations' },
188
+ { verb: 'migration graph', oneLiner: 'Show the migration graph topology' },
189
+ { verb: 'migration show', oneLiner: 'Display migration package contents' },
190
+ ]);
191
+ addGlobalOptions(command)
192
+ .option('--db <url>', 'Database connection string')
193
+ .option('--config <path>', 'Path to prisma-next.config.ts')
194
+ .action(async (options: MigrationLogOptions) => {
195
+ const flags = parseGlobalFlags(options);
196
+ const ui = new TerminalUI({ color: flags.color, interactive: flags.interactive });
197
+ const result = await executeMigrationLogCommand(options, flags, ui);
198
+ const exitCode = handleResult(result, flags, ui, (logResult) => {
199
+ if (flags.json) {
200
+ ui.output(JSON.stringify(logResult, null, 2));
201
+ } else if (!flags.quiet) {
202
+ const c = (fn: (s: string) => string, s: string) => (flags.color !== false ? fn(s) : s);
203
+ if (logResult.applied.length === 0) {
204
+ ui.log(logResult.summary);
205
+ } else {
206
+ for (const entry of logResult.applied) {
207
+ ui.log(
208
+ `${c(cyan, '✓')} ${entry.dirName} ${c(dim, entry.migrationHash.slice(0, 16) + '…')} ${entry.operationCount} op(s)`,
209
+ );
210
+ }
211
+ ui.log(`\n${logResult.summary}`);
212
+ }
213
+ }
214
+ });
215
+ process.exit(exitCode);
216
+ });
217
+ return command;
218
+ }
@@ -18,6 +18,8 @@ import {
18
18
  import type { MigrationMetadata } from '@prisma-next/migration-tools/metadata';
19
19
  import { findLatestMigration } from '@prisma-next/migration-tools/migration-graph';
20
20
  import { writeMigrationTs } from '@prisma-next/migration-tools/migration-ts';
21
+ import { parseContractRef } from '@prisma-next/migration-tools/ref-resolution';
22
+ import { readRefs } from '@prisma-next/migration-tools/refs';
21
23
  import { notOk, ok, type Result } from '@prisma-next/utils/result';
22
24
  import { Command } from 'commander';
23
25
  import { join, relative } from 'pathe';
@@ -28,10 +30,10 @@ import {
28
30
  errorContractValidationFailed,
29
31
  errorFileNotFound,
30
32
  errorMigrationPlanningFailed,
31
- errorRuntime,
32
33
  errorTargetMigrationNotSupported,
33
34
  errorUnexpected,
34
35
  mapMigrationToolsError,
36
+ mapRefResolutionError,
35
37
  } from '../utils/cli-errors';
36
38
  import {
37
39
  addGlobalOptions,
@@ -102,7 +104,7 @@ export interface MigrationPlanResult {
102
104
  * Surfacing these in the result (rather than only via `ui.step` log
103
105
  * lines) makes the cross-space side effect explicit to JSON consumers
104
106
  * and the success-summary renderer — the same multi-space side effect
105
- * that `migration apply` will replay.
107
+ * that `migrate` will replay.
106
108
  */
107
109
  readonly emittedExtensionDirs: readonly { readonly spaceId: string; readonly dirName: string }[];
108
110
  readonly operations: readonly {
@@ -214,23 +216,25 @@ async function executeMigrationPlanCommand(
214
216
  const { bundles, graph } = await loadMigrationPackages(appMigrationsDir);
215
217
 
216
218
  if (options.from) {
217
- const resolved = resolveBundleByPrefix(bundles, options.from);
218
- if (!resolved.ok) {
219
- const f = resolved.failure;
219
+ const refs = await readRefs(resolveMigrationPaths(options.config, config).refsDir);
220
+ const refResult = parseContractRef(options.from, { graph, refs });
221
+ if (!refResult.ok) {
222
+ return notOk(mapRefResolutionError(refResult.failure));
223
+ }
224
+ fromHash = refResult.value.hash;
225
+ const matchingBundle = bundles.find((p) => p.metadata.to === fromHash);
226
+ if (!matchingBundle) {
220
227
  return notOk(
221
- f.reason === 'ambiguous'
222
- ? errorRuntime('Multiple matching migrations found', {
223
- why: `Prefix "${options.from}" matches ${f.count} migrations in ${appMigrationsRelative}`,
224
- fix: 'Provide a longer prefix to disambiguate, or omit --from to use the latest migration target.',
225
- })
226
- : errorRuntime('Starting contract not found', {
227
- why: `No migration with to hash matching "${options.from}" exists in ${appMigrationsRelative}`,
228
- fix: 'Check that the --from hash matches a known migration target hash, or omit --from to use the latest migration target.',
229
- }),
228
+ errorUnexpected(
229
+ `No migration bundle found for --from "${options.from}" (resolved hash: ${fromHash})`,
230
+ {
231
+ why: `The ref resolved successfully but no on-disk migration package has an end-contract hash matching ${fromHash}.`,
232
+ fix: 'Provide a ref or hash that corresponds to an existing migration package, or run `migration list` to see available migrations.',
233
+ },
234
+ ),
230
235
  );
231
236
  }
232
- fromHash = resolved.value.metadata.to;
233
- fromContractSourceDir = resolved.value.dirPath;
237
+ fromContractSourceDir = matchingBundle.dirPath;
234
238
  fromContract = await readPredecessorEndContract(fromContractSourceDir);
235
239
  } else {
236
240
  const latestMigration = findLatestMigration(graph);
@@ -523,7 +527,10 @@ export function createMigrationPlanCommand(): Command {
523
527
  addGlobalOptions(command)
524
528
  .option('--config <path>', 'Path to prisma-next.config.ts')
525
529
  .option('--name <slug>', 'Name slug for the migration directory', 'migration')
526
- .option('--from <hash>', 'Explicit starting contract hash (overrides latest migration target)')
530
+ .option(
531
+ '--from <contract>',
532
+ 'Starting contract reference (hash, prefix, ref name, migration dir name, <dir>^, or ./path)',
533
+ )
527
534
  .action(async (options: MigrationPlanOptions) => {
528
535
  const flags = parseGlobalFlags(options);
529
536
  const startTime = Date.now();
@@ -589,7 +596,7 @@ export function formatMigrationPlanOutput(result: MigrationPlanResult, flags: Gl
589
596
  }
590
597
  lines.push('');
591
598
  lines.push(
592
- `Next: review the extension migrations above, then run ${green_('prisma-next migration apply')}.`,
599
+ `Next: review the extension migrations above, then run ${green_('prisma-next migrate')}.`,
593
600
  );
594
601
  }
595
602
 
@@ -660,11 +667,11 @@ export function formatMigrationPlanOutput(result: MigrationPlanResult, flags: Gl
660
667
 
661
668
  lines.push('');
662
669
  // The "Next:" hint always points at the canonical apply path
663
- // (`prisma-next migration apply`) regardless of how many spaces
664
- // were materialised — `db update` is a dev-time convenience, not
665
- // the canonical replay step.
670
+ // (`prisma-next migrate`) regardless of how many spaces were
671
+ // materialised — `db update` is a dev-time convenience, not the
672
+ // canonical replay step.
666
673
  lines.push(
667
- `Next: review ${green_(result.dir ?? '<dir>')} if needed, then run ${green_('prisma-next migration apply')}.`,
674
+ `Next: review ${green_(result.dir ?? '<dir>')} if needed, then run ${green_('prisma-next migrate')}.`,
668
675
  );
669
676
 
670
677
  if (result.preview && result.preview.statements.length > 0) {