@prisma-next/cli 0.11.0-dev.46 → 0.11.0-dev.48

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 (105) hide show
  1. package/dist/cli.mjs +9 -9
  2. package/dist/{client-a5NJce0-.mjs → client-5uvDppD8.mjs} +20 -18
  3. package/dist/client-5uvDppD8.mjs.map +1 -0
  4. package/dist/{command-helpers-yLuA78TP.mjs → command-helpers-CI8P5Xyd.mjs} +4 -4
  5. package/dist/{command-helpers-yLuA78TP.mjs.map → command-helpers-CI8P5Xyd.mjs.map} +1 -1
  6. package/dist/commands/contract-emit.mjs +1 -1
  7. package/dist/commands/contract-infer.mjs +1 -1
  8. package/dist/commands/db-init.mjs +5 -5
  9. package/dist/commands/db-schema.mjs +3 -3
  10. package/dist/commands/db-sign.mjs +3 -3
  11. package/dist/commands/db-update.mjs +5 -5
  12. package/dist/commands/db-verify.mjs +1 -1
  13. package/dist/commands/migrate.d.mts +1 -1
  14. package/dist/commands/migrate.d.mts.map +1 -1
  15. package/dist/commands/migrate.mjs +42 -37
  16. package/dist/commands/migrate.mjs.map +1 -1
  17. package/dist/commands/migration-check.d.mts +4 -3
  18. package/dist/commands/migration-check.d.mts.map +1 -1
  19. package/dist/commands/migration-check.mjs +1 -279
  20. package/dist/commands/migration-graph.d.mts +1 -1
  21. package/dist/commands/migration-graph.mjs +2 -2
  22. package/dist/commands/migration-list.d.mts +2 -2
  23. package/dist/commands/migration-list.mjs +1 -1
  24. package/dist/commands/migration-log.mjs +2 -2
  25. package/dist/commands/migration-new.d.mts.map +1 -1
  26. package/dist/commands/migration-new.mjs +32 -30
  27. package/dist/commands/migration-new.mjs.map +1 -1
  28. package/dist/commands/migration-plan.d.mts +1 -1
  29. package/dist/commands/migration-plan.mjs +1 -1
  30. package/dist/commands/migration-show.d.mts +4 -55
  31. package/dist/commands/migration-show.d.mts.map +1 -1
  32. package/dist/commands/migration-show.mjs +62 -152
  33. package/dist/commands/migration-show.mjs.map +1 -1
  34. package/dist/commands/migration-status.d.mts +5 -40
  35. package/dist/commands/migration-status.d.mts.map +1 -1
  36. package/dist/commands/migration-status.mjs +91 -64
  37. package/dist/commands/migration-status.mjs.map +1 -1
  38. package/dist/commands/ref.d.mts +1 -1
  39. package/dist/commands/ref.mjs +2 -2
  40. package/dist/{contract-emit-FtDVFs2Q.mjs → contract-emit-DmBG2Nnc.mjs} +2 -2
  41. package/dist/{contract-emit-FtDVFs2Q.mjs.map → contract-emit-DmBG2Nnc.mjs.map} +1 -1
  42. package/dist/{contract-infer-CVMuoJKk.mjs → contract-infer-BSWFKgI1.mjs} +3 -3
  43. package/dist/{contract-infer-CVMuoJKk.mjs.map → contract-infer-BSWFKgI1.mjs.map} +1 -1
  44. package/dist/contract-space-aggregate-loader-CVHGuA35.mjs +170 -0
  45. package/dist/contract-space-aggregate-loader-CVHGuA35.mjs.map +1 -0
  46. package/dist/{db-verify-B00o3LuC.mjs → db-verify-BzpwFyLg.mjs} +4 -4
  47. package/dist/{db-verify-B00o3LuC.mjs.map → db-verify-BzpwFyLg.mjs.map} +1 -1
  48. package/dist/exports/control-api.d.mts +1 -1
  49. package/dist/exports/control-api.mjs +1 -1
  50. package/dist/exports/index.mjs +1 -1
  51. package/dist/exports/init-output.mjs +1 -1
  52. package/dist/extension-pack-inputs-BiY86HbQ.mjs +62 -0
  53. package/dist/extension-pack-inputs-BiY86HbQ.mjs.map +1 -0
  54. package/dist/{global-flags-Dvibm2yu.d.mts → global-flags-DWsQ6SSI.d.mts} +1 -1
  55. package/dist/{global-flags-Dvibm2yu.d.mts.map → global-flags-DWsQ6SSI.d.mts.map} +1 -1
  56. package/dist/{graph-render-DJVv0_uf.mjs → graph-render-D2FnLpuK.mjs} +1 -1
  57. package/dist/{graph-render-DJVv0_uf.mjs.map → graph-render-D2FnLpuK.mjs.map} +1 -1
  58. package/dist/{init-BKgB6EKw.mjs → init-DEssiJ8j.mjs} +3 -3
  59. package/dist/{init-BKgB6EKw.mjs.map → init-DEssiJ8j.mjs.map} +1 -1
  60. package/dist/{inspect-live-schema-BXUd6RfS.mjs → inspect-live-schema-DlBM84nh.mjs} +3 -3
  61. package/dist/{inspect-live-schema-BXUd6RfS.mjs.map → inspect-live-schema-DlBM84nh.mjs.map} +1 -1
  62. package/dist/migration-check-CzLbAqIQ.mjs +341 -0
  63. package/dist/migration-check-CzLbAqIQ.mjs.map +1 -0
  64. package/dist/{migration-command-scaffold-3l3EdmSD.mjs → migration-command-scaffold-Bp8UHnvJ.mjs} +3 -3
  65. package/dist/{migration-command-scaffold-3l3EdmSD.mjs.map → migration-command-scaffold-Bp8UHnvJ.mjs.map} +1 -1
  66. package/dist/{migration-list-DopkAG7L.mjs → migration-list-C2xnaYsT.mjs} +2 -2
  67. package/dist/{migration-list-DopkAG7L.mjs.map → migration-list-C2xnaYsT.mjs.map} +1 -1
  68. package/dist/{migration-list-graph-render-C-daUZLU.d.mts → migration-list-graph-render-DKw1AT-e.d.mts} +1 -1
  69. package/dist/migration-list-graph-render-DKw1AT-e.d.mts.map +1 -0
  70. package/dist/{migration-plan-BHoeET4O.mjs → migration-plan-BLvOmNCu.mjs} +6 -5
  71. package/dist/{migration-plan-BHoeET4O.mjs.map → migration-plan-BLvOmNCu.mjs.map} +1 -1
  72. package/dist/{migration-types-BXWvz12q.d.mts → migration-types-q64xAI_J.d.mts} +1 -1
  73. package/dist/{migration-types-BXWvz12q.d.mts.map → migration-types-q64xAI_J.d.mts.map} +1 -1
  74. package/dist/{migrations-D-UCOGtk.mjs → migrations-vzQt9LI2.mjs} +3 -13
  75. package/dist/migrations-vzQt9LI2.mjs.map +1 -0
  76. package/dist/{output-CUIdfYo5.mjs → output-B60Gw5fu.mjs} +1 -1
  77. package/dist/{output-CUIdfYo5.mjs.map → output-B60Gw5fu.mjs.map} +1 -1
  78. package/dist/{ref-advancement-CHJ_8HxQ.mjs → ref-advancement-DRh5Nquq.mjs} +1 -1
  79. package/dist/{ref-advancement-CHJ_8HxQ.mjs.map → ref-advancement-DRh5Nquq.mjs.map} +1 -1
  80. package/dist/{types-UWB2-rrw.d.mts → types-CEtm6v6a.d.mts} +1 -8
  81. package/dist/types-CEtm6v6a.d.mts.map +1 -0
  82. package/dist/{verify-9gDJz6cm.mjs → verify-ktSRQvIS.mjs} +2 -2
  83. package/dist/{verify-9gDJz6cm.mjs.map → verify-ktSRQvIS.mjs.map} +1 -1
  84. package/package.json +18 -18
  85. package/src/commands/migrate.ts +52 -42
  86. package/src/commands/migration-check.ts +43 -83
  87. package/src/commands/migration-new.ts +44 -42
  88. package/src/commands/migration-plan.ts +2 -2
  89. package/src/commands/migration-show.ts +65 -284
  90. package/src/commands/migration-status.ts +131 -99
  91. package/src/control-api/client.ts +0 -1
  92. package/src/control-api/operations/db-verify.ts +9 -5
  93. package/src/control-api/operations/migration-apply.ts +11 -19
  94. package/src/control-api/types.ts +0 -7
  95. package/src/utils/command-helpers.ts +8 -3
  96. package/src/utils/contract-space-aggregate-loader.ts +221 -117
  97. package/src/utils/formatters/migrations.ts +4 -38
  98. package/src/utils/integrity-violation-to-check-failure.ts +130 -0
  99. package/dist/client-a5NJce0-.mjs.map +0 -1
  100. package/dist/commands/migration-check.mjs.map +0 -1
  101. package/dist/contract-space-aggregate-loader-EVU3n9YE.mjs +0 -160
  102. package/dist/contract-space-aggregate-loader-EVU3n9YE.mjs.map +0 -1
  103. package/dist/migration-list-graph-render-C-daUZLU.d.mts.map +0 -1
  104. package/dist/migrations-D-UCOGtk.mjs.map +0 -1
  105. package/dist/types-UWB2-rrw.d.mts.map +0 -1
@@ -1,6 +1,11 @@
1
1
  import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
2
- import { MigrationToolsError } from '@prisma-next/migration-tools/errors';
2
+ import { readFile } from 'node:fs/promises';
3
+ import { createControlStack } from '@prisma-next/framework-components/control';
4
+ import type { IntegrityViolation } from '@prisma-next/migration-tools/aggregate';
5
+ import { loadContractSpaceAggregate } from '@prisma-next/migration-tools/aggregate';
3
6
  import { verifyMigrationHash } from '@prisma-next/migration-tools/hash';
7
+ import { readMigrationsDir } from '@prisma-next/migration-tools/io';
8
+ import { reconstructGraph } from '@prisma-next/migration-tools/migration-graph';
4
9
  import type { OnDiskMigrationPackage } from '@prisma-next/migration-tools/package';
5
10
  import { parseMigrationRef } from '@prisma-next/migration-tools/ref-resolution';
6
11
  import { readRefs } from '@prisma-next/migration-tools/refs';
@@ -9,15 +14,20 @@ import { join, relative } from 'pathe';
9
14
  import { loadConfig } from '../config-loader';
10
15
  import {
11
16
  addGlobalOptions,
12
- loadMigrationPackages,
17
+ resolveContractPath,
13
18
  resolveMigrationPaths,
14
19
  setCommandDescriptions,
15
20
  setCommandExamples,
16
21
  setCommandSeeAlso,
17
22
  } from '../utils/command-helpers';
23
+ import { toDeclaredExtensionsFromRaw } from '../utils/extension-pack-inputs';
18
24
  import { formatStyledHeader } from '../utils/formatters/styled';
19
25
  import type { CommonCommandOptions } from '../utils/global-flags';
20
26
  import { type GlobalFlags, parseGlobalFlagsOrExit } from '../utils/global-flags';
27
+ import {
28
+ type CheckFailure,
29
+ integrityViolationToCheckFailure,
30
+ } from '../utils/integrity-violation-to-check-failure';
21
31
  import { createTerminalUI, type TerminalUI } from '../utils/terminal-ui';
22
32
  import { INTEGRITY_FAILED, OK, PRECONDITION } from './migration-check/exit-codes';
23
33
 
@@ -25,12 +35,7 @@ interface MigrationCheckOptions extends CommonCommandOptions {
25
35
  readonly config?: string;
26
36
  }
27
37
 
28
- export interface CheckFailure {
29
- readonly pnCode: string;
30
- readonly where: string;
31
- readonly why: string;
32
- readonly fix: string;
33
- }
38
+ export type { CheckFailure } from '../utils/integrity-violation-to-check-failure';
34
39
 
35
40
  export interface MigrationCheckResult {
36
41
  readonly ok: boolean;
@@ -38,11 +43,6 @@ export interface MigrationCheckResult {
38
43
  readonly summary: string;
39
44
  }
40
45
 
41
- /**
42
- * Canonical user-facing locator for a check failure: the cwd-relative path
43
- * to the migration package directory. Surfacing the same shape across every
44
- * PN code means `--json` consumers can branch uniformly on `where`.
45
- */
46
46
  function migrationPathRelative(dirPath: string): string {
47
47
  return relative(process.cwd(), dirPath);
48
48
  }
@@ -63,21 +63,6 @@ function checkFileExists(dirPath: string, dirName: string, fileName: string): Ch
63
63
  return null;
64
64
  }
65
65
 
66
- /**
67
- * Within-migration snapshot-consistency check (PN-MIG-CHECK-005).
68
- *
69
- * Compares the migration's stored `metadata.to` against the `storageHash`
70
- * recorded in its on-disk `end-contract.json` snapshot. The two values are
71
- * independent on-disk records of the same fact (the migration's destination
72
- * contract); drift between them indicates the package is internally
73
- * corrupt. Cross-migration consistency (one migration's end-contract.json
74
- * agreeing with the next migration's start-contract.json) is a separate
75
- * check that requires shadow execution and is deferred to
76
- * `migration preflight`.
77
- *
78
- * Shared between the graph-wide and per-migration code paths so both report
79
- * the same failure for the same on-disk state.
80
- */
81
66
  function checkSnapshotConsistency(pkg: OnDiskMigrationPackage): CheckFailure | null {
82
67
  const endContractPath = join(pkg.dirPath, 'end-contract.json');
83
68
  if (!existsSync(endContractPath)) return null;
@@ -104,6 +89,27 @@ function checkSnapshotConsistency(pkg: OnDiskMigrationPackage): CheckFailure | n
104
89
  return null;
105
90
  }
106
91
 
92
+ async function loadAggregateIntegrityViolations(
93
+ config: Awaited<ReturnType<typeof loadConfig>>,
94
+ migrationsDir: string,
95
+ ): Promise<readonly IntegrityViolation[]> {
96
+ try {
97
+ const contractJsonContent = await readFile(resolveContractPath(config), 'utf-8');
98
+ const familyInstance = config.family.create(createControlStack(config));
99
+ const declaredExtensions = toDeclaredExtensionsFromRaw(config.extensionPacks ?? []);
100
+
101
+ const parsedAppContract: unknown = JSON.parse(contractJsonContent);
102
+ const aggregate = await loadContractSpaceAggregate({
103
+ migrationsDir,
104
+ deserializeContract: (json: unknown) => familyInstance.deserializeContract(json),
105
+ appContract: familyInstance.deserializeContract(parsedAppContract),
106
+ });
107
+ return aggregate.checkIntegrity({ declaredExtensions, checkContracts: true });
108
+ } catch {
109
+ return [];
110
+ }
111
+ }
112
+
107
113
  async function executeMigrationCheckCommand(
108
114
  target: string | undefined,
109
115
  options: MigrationCheckOptions,
@@ -111,10 +117,8 @@ async function executeMigrationCheckCommand(
111
117
  ui: TerminalUI,
112
118
  ): Promise<{ result: MigrationCheckResult; exitCode: number }> {
113
119
  const config = await loadConfig(options.config);
114
- const { configPath, appMigrationsDir, appMigrationsRelative, refsDir } = resolveMigrationPaths(
115
- options.config,
116
- config,
117
- );
120
+ const { configPath, migrationsDir, appMigrationsDir, appMigrationsRelative, refsDir } =
121
+ resolveMigrationPaths(options.config, config);
118
122
 
119
123
  if (!flags.json && !flags.quiet) {
120
124
  const details: Array<{ label: string; value: string }> = [
@@ -135,37 +139,9 @@ async function executeMigrationCheckCommand(
135
139
 
136
140
  const failures: CheckFailure[] = [];
137
141
 
138
- let bundles: Awaited<ReturnType<typeof loadMigrationPackages>>['bundles'];
139
- let graph: Awaited<ReturnType<typeof loadMigrationPackages>>['graph'];
140
- try {
141
- const loaded = await loadMigrationPackages(appMigrationsDir);
142
- bundles = loaded.bundles;
143
- graph = loaded.graph;
144
- } catch (error) {
145
- if (MigrationToolsError.is(error)) {
146
- const pnCode =
147
- error.code === 'MIGRATION.HASH_MISMATCH' ? 'PN-MIG-CHECK-001' : 'PN-MIG-CHECK-002';
148
- // Normalise to a cwd-relative path. `error.details.dir` is absolute
149
- // (the migration-tools layer doesn't know the caller's cwd); the
150
- // `filePath` fallback is also absolute. Surfacing the relative form
151
- // matches the rest of the command's `where` shape and keeps `--json`
152
- // consumers from having to special-case the bootstrap-failure path.
153
- const rawWhere =
154
- (error.details?.['dir'] as string) ?? (error.details?.['filePath'] as string) ?? null;
155
- const where = rawWhere ? relative(process.cwd(), rawWhere) : 'unknown';
156
- failures.push({
157
- pnCode,
158
- where,
159
- why: error.why,
160
- fix: error.fix,
161
- });
162
- return {
163
- result: { ok: false, failures, summary: `${failures.length} integrity failure(s)` },
164
- exitCode: INTEGRITY_FAILED,
165
- };
166
- }
167
- throw error;
168
- }
142
+ const loaded = await readMigrationsDir(appMigrationsDir);
143
+ const bundles: readonly OnDiskMigrationPackage[] = loaded.packages;
144
+ const graph = reconstructGraph(bundles);
169
145
 
170
146
  if (existsSync(appMigrationsDir)) {
171
147
  const loadedDirNames = new Set(bundles.map((p) => p.dirName));
@@ -236,29 +212,9 @@ async function executeMigrationCheckCommand(
236
212
  });
237
213
  }
238
214
 
239
- // PN-MIG-CHECK-005 must fire per-migration as well as graph-wide; both
240
- // call sites delegate to the shared helper so the same on-disk drift
241
- // produces the same failure regardless of how the user invoked check.
242
215
  const snapshotFailure = checkSnapshotConsistency(matchedPkg);
243
216
  if (snapshotFailure) failures.push(snapshotFailure);
244
217
  } else {
245
- for (const pkg of bundles) {
246
- for (const f of ['migration.json', 'ops.json']) {
247
- const fail = checkFileExists(pkg.dirPath, pkg.dirName, f);
248
- if (fail) failures.push(fail);
249
- }
250
-
251
- const verification = verifyMigrationHash(pkg);
252
- if (!verification.ok) {
253
- failures.push({
254
- pnCode: 'PN-MIG-CHECK-001',
255
- where: migrationFileRelative(pkg.dirPath, 'migration.json'),
256
- why: `Stored hash ${verification.storedHash} does not match recomputed hash ${verification.computedHash}`,
257
- fix: 'Re-emit the migration package or restore from version control.',
258
- });
259
- }
260
- }
261
-
262
218
  for (const pkg of bundles) {
263
219
  const snapshotFailure = checkSnapshotConsistency(pkg);
264
220
  if (snapshotFailure) failures.push(snapshotFailure);
@@ -295,6 +251,10 @@ async function executeMigrationCheckCommand(
295
251
  } catch {
296
252
  // Refs unreadable — skip ref checks
297
253
  }
254
+
255
+ for (const violation of await loadAggregateIntegrityViolations(config, migrationsDir)) {
256
+ failures.push(integrityViolationToCheckFailure(violation, migrationsDir));
257
+ }
298
258
  }
299
259
 
300
260
  if (failures.length === 0) {
@@ -12,19 +12,15 @@ import { readFile } from 'node:fs/promises';
12
12
  import type { Contract } from '@prisma-next/contract/types';
13
13
  import { getEmittedArtifactPaths } from '@prisma-next/emitter';
14
14
  import { APP_SPACE_ID, createControlStack } from '@prisma-next/framework-components/control';
15
- import { MigrationToolsError } from '@prisma-next/migration-tools/errors';
15
+ import { loadContractSpaceAggregate } from '@prisma-next/migration-tools/aggregate';
16
16
  import { computeMigrationHash } from '@prisma-next/migration-tools/hash';
17
17
  import {
18
18
  copyFilesWithRename,
19
19
  formatMigrationDirName,
20
- readMigrationsDir,
21
20
  writeMigrationPackage,
22
21
  } from '@prisma-next/migration-tools/io';
23
22
  import type { MigrationMetadata } from '@prisma-next/migration-tools/metadata';
24
- import {
25
- findLatestMigration,
26
- reconstructGraph,
27
- } from '@prisma-next/migration-tools/migration-graph';
23
+ import { findLatestMigration } from '@prisma-next/migration-tools/migration-graph';
28
24
  import { writeMigrationTs } from '@prisma-next/migration-tools/migration-ts';
29
25
  import { notOk, ok, type Result } from '@prisma-next/utils/result';
30
26
  import { Command } from 'commander';
@@ -36,7 +32,6 @@ import {
36
32
  errorRuntime,
37
33
  errorTargetMigrationNotSupported,
38
34
  errorUnexpected,
39
- mapMigrationToolsError,
40
35
  } from '../utils/cli-errors';
41
36
  import {
42
37
  addGlobalOptions,
@@ -46,6 +41,7 @@ import {
46
41
  setCommandDescriptions,
47
42
  setCommandExamples,
48
43
  } from '../utils/command-helpers';
44
+ import { refusePackageCorruptionOnAggregate } from '../utils/contract-space-aggregate-loader';
49
45
  import { formatStyledHeader } from '../utils/formatters/styled';
50
46
  import { assertFrameworkComponentsCompatible } from '../utils/framework-components';
51
47
  import type { CommonCommandOptions } from '../utils/global-flags';
@@ -71,7 +67,10 @@ async function executeMigrationNewCommand(
71
67
  options: MigrationNewOptions,
72
68
  ): Promise<Result<MigrationNewResult, CliStructuredError>> {
73
69
  const config = await loadConfig(options.config);
74
- const { appMigrationsDir, appMigrationsRelative } = resolveMigrationPaths(options.config, config);
70
+ const { migrationsDir, appMigrationsDir, appMigrationsRelative } = resolveMigrationPaths(
71
+ options.config,
72
+ config,
73
+ );
75
74
 
76
75
  // Construct the family instance up-front so the on-disk contract read
77
76
  // below crosses the serializer seam (`familyInstance.deserializeContract`)
@@ -98,7 +97,8 @@ async function executeMigrationNewCommand(
98
97
 
99
98
  let toContract: Contract;
100
99
  try {
101
- toContract = familyInstance.deserializeContract(JSON.parse(contractJsonContent) as unknown);
100
+ const parsedContract: unknown = JSON.parse(contractJsonContent);
101
+ toContract = familyInstance.deserializeContract(parsedContract);
102
102
  } catch (error) {
103
103
  return notOk(
104
104
  errorRuntime('Contract JSON is invalid', {
@@ -118,45 +118,47 @@ async function executeMigrationNewCommand(
118
118
  );
119
119
  }
120
120
 
121
- let fromHash: string | null = null;
122
- let fromContractSourceDir: string | null = null;
121
+ const aggregate = await loadContractSpaceAggregate({
122
+ migrationsDir,
123
+ deserializeContract: (json) => familyInstance.deserializeContract(json),
124
+ appContract: toContract,
125
+ });
126
+ const packageCorruptionFailure = refusePackageCorruptionOnAggregate(aggregate);
127
+ if (packageCorruptionFailure) {
128
+ return notOk(packageCorruptionFailure);
129
+ }
123
130
 
124
- try {
125
- const packages = await readMigrationsDir(appMigrationsDir);
131
+ const packages = aggregate.app.packages;
132
+ const graph = aggregate.app.graph();
126
133
 
127
- if (packages.length > 0) {
128
- const graph = reconstructGraph(packages);
134
+ let fromHash: string | null = null;
135
+ let fromContractSourceDir: string | null = null;
129
136
 
130
- if (options.from) {
131
- const match = packages.find((p) => p.metadata.to.startsWith(options.from!));
132
- if (!match) {
133
- return notOk(
134
- errorRuntime('Starting contract not found', {
135
- why: `No migration with to hash matching "${options.from}" exists in ${appMigrationsRelative}`,
136
- fix: 'Check that the --from hash matches a known migration target hash.',
137
- }),
138
- );
139
- }
140
- fromHash = match.metadata.to;
141
- fromContractSourceDir = match.dirPath;
142
- } else {
143
- const latestMigration = findLatestMigration(graph);
144
- if (latestMigration) {
145
- fromHash = latestMigration.to;
146
- const leafPkg = packages.find(
147
- (p) => p.metadata.migrationHash === latestMigration.migrationHash,
148
- );
149
- if (leafPkg) {
150
- fromContractSourceDir = leafPkg.dirPath;
151
- }
137
+ if (packages.length > 0) {
138
+ if (options.from) {
139
+ const match = packages.find((p) => p.metadata.to.startsWith(options.from!));
140
+ if (!match) {
141
+ return notOk(
142
+ errorRuntime('Starting contract not found', {
143
+ why: `No migration with to hash matching "${options.from}" exists in ${appMigrationsRelative}`,
144
+ fix: 'Check that the --from hash matches a known migration target hash.',
145
+ }),
146
+ );
147
+ }
148
+ fromHash = match.metadata.to;
149
+ fromContractSourceDir = match.dirPath;
150
+ } else {
151
+ const latestMigration = findLatestMigration(graph);
152
+ if (latestMigration) {
153
+ fromHash = latestMigration.to;
154
+ const leafPkg = packages.find(
155
+ (p) => p.metadata.migrationHash === latestMigration.migrationHash,
156
+ );
157
+ if (leafPkg) {
158
+ fromContractSourceDir = leafPkg.dirPath;
152
159
  }
153
160
  }
154
161
  }
155
- } catch (error) {
156
- if (MigrationToolsError.is(error)) {
157
- return notOk(mapMigrationToolsError(error));
158
- }
159
- throw error;
160
162
  }
161
163
 
162
164
  if (fromHash === toStorageHash && !options.from) {
@@ -576,7 +576,7 @@ async function executeMigrationPlanCommand(
576
576
  planner,
577
577
  migrations,
578
578
  frameworkComponents,
579
- aggregate.app.contract,
579
+ aggregate.app.contract(),
580
580
  fromContract,
581
581
  aggregate.app.spaceId,
582
582
  );
@@ -653,7 +653,7 @@ async function executeMigrationPlanCommand(
653
653
  planner,
654
654
  migrations,
655
655
  frameworkComponents,
656
- aggregate.app.contract,
656
+ aggregate.app.contract(),
657
657
  fromContract,
658
658
  aggregate.app.spaceId,
659
659
  );