@prisma-next/cli 0.4.0-dev.9 → 0.5.0-dev.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.
Files changed (116) hide show
  1. package/README.md +26 -18
  2. package/dist/cli-errors-C0JhVj0c.d.mts +4 -0
  3. package/dist/cli-errors-DHq6GQGu.mjs +5 -0
  4. package/dist/cli.mjs +7 -18
  5. package/dist/cli.mjs.map +1 -1
  6. package/dist/{client-CJxHfhze.mjs → client-TG7rbCWT.mjs} +7 -6
  7. package/dist/{client-CJxHfhze.mjs.map → client-TG7rbCWT.mjs.map} +1 -1
  8. package/dist/commands/contract-emit.d.mts.map +1 -1
  9. package/dist/commands/contract-emit.mjs +2 -7
  10. package/dist/commands/contract-infer.mjs +2 -8
  11. package/dist/commands/db-init.mjs +6 -7
  12. package/dist/commands/db-init.mjs.map +1 -1
  13. package/dist/commands/db-schema.mjs +4 -7
  14. package/dist/commands/db-schema.mjs.map +1 -1
  15. package/dist/commands/db-sign.mjs +5 -6
  16. package/dist/commands/db-sign.mjs.map +1 -1
  17. package/dist/commands/db-update.mjs +6 -7
  18. package/dist/commands/db-update.mjs.map +1 -1
  19. package/dist/commands/db-verify.mjs +6 -7
  20. package/dist/commands/db-verify.mjs.map +1 -1
  21. package/dist/commands/migration-apply.d.mts +1 -1
  22. package/dist/commands/migration-apply.d.mts.map +1 -1
  23. package/dist/commands/migration-apply.mjs +33 -25
  24. package/dist/commands/migration-apply.mjs.map +1 -1
  25. package/dist/commands/migration-new.d.mts.map +1 -1
  26. package/dist/commands/migration-new.mjs +47 -22
  27. package/dist/commands/migration-new.mjs.map +1 -1
  28. package/dist/commands/migration-plan.d.mts +6 -1
  29. package/dist/commands/migration-plan.d.mts.map +1 -1
  30. package/dist/commands/migration-plan.mjs +92 -69
  31. package/dist/commands/migration-plan.mjs.map +1 -1
  32. package/dist/commands/migration-ref.d.mts +1 -1
  33. package/dist/commands/migration-ref.mjs +4 -4
  34. package/dist/commands/migration-show.d.mts +2 -2
  35. package/dist/commands/migration-show.d.mts.map +1 -1
  36. package/dist/commands/migration-show.mjs +9 -14
  37. package/dist/commands/migration-show.mjs.map +1 -1
  38. package/dist/commands/migration-status.d.mts +4 -5
  39. package/dist/commands/migration-status.d.mts.map +1 -1
  40. package/dist/commands/migration-status.mjs +2 -7
  41. package/dist/config-loader-_W4T21X1.mjs +90 -0
  42. package/dist/config-loader-_W4T21X1.mjs.map +1 -0
  43. package/dist/config-loader.d.mts.map +1 -1
  44. package/dist/config-loader.mjs +1 -1
  45. package/dist/{contract-emit-gpJNLGs7.mjs → contract-emit-CNYyzJwF.mjs} +18 -14
  46. package/dist/contract-emit-CNYyzJwF.mjs.map +1 -0
  47. package/dist/{contract-emit-CKig_Lra.mjs → contract-emit-CQfj7xJn.mjs} +24 -20
  48. package/dist/contract-emit-CQfj7xJn.mjs.map +1 -0
  49. package/dist/contract-emit-fhNwwhkQ.mjs +4 -0
  50. package/dist/{contract-infer-BDJgg7Xb.mjs → contract-infer-BP3DrGgz.mjs} +3 -3
  51. package/dist/{contract-infer-BDJgg7Xb.mjs.map → contract-infer-BP3DrGgz.mjs.map} +1 -1
  52. package/dist/exports/control-api.d.mts +2 -2
  53. package/dist/exports/control-api.d.mts.map +1 -1
  54. package/dist/exports/control-api.mjs +3 -5
  55. package/dist/exports/index.mjs +2 -7
  56. package/dist/exports/index.mjs.map +1 -1
  57. package/dist/{framework-components-Bsr1GaIj.mjs → framework-components-DfZKQBQ2.mjs} +2 -2
  58. package/dist/{framework-components-Bsr1GaIj.mjs.map → framework-components-DfZKQBQ2.mjs.map} +1 -1
  59. package/dist/{init-DZWvhEP0.mjs → init-CQfo_4Ro.mjs} +2 -2
  60. package/dist/{init-DZWvhEP0.mjs.map → init-CQfo_4Ro.mjs.map} +1 -1
  61. package/dist/{inspect-live-schema-ChqrALmw.mjs → inspect-live-schema-DWzf4Q_m.mjs} +5 -5
  62. package/dist/{inspect-live-schema-ChqrALmw.mjs.map → inspect-live-schema-DWzf4Q_m.mjs.map} +1 -1
  63. package/dist/migration-cli.d.mts +50 -0
  64. package/dist/migration-cli.d.mts.map +1 -0
  65. package/dist/migration-cli.mjs +184 -0
  66. package/dist/migration-cli.mjs.map +1 -0
  67. package/dist/{migration-command-scaffold-B0oH_hyB.mjs → migration-command-scaffold-CLMD302g.mjs} +6 -6
  68. package/dist/{migration-command-scaffold-B0oH_hyB.mjs.map → migration-command-scaffold-CLMD302g.mjs.map} +1 -1
  69. package/dist/{migration-status-CPamfEPj.mjs → migration-status-B0HLF7So.mjs} +18 -34
  70. package/dist/migration-status-B0HLF7So.mjs.map +1 -0
  71. package/dist/{migrations-BIsjFjSV.mjs → migrations-B0dOQlk0.mjs} +4 -15
  72. package/dist/migrations-B0dOQlk0.mjs.map +1 -0
  73. package/dist/{result-handler-AFK4hxyX.mjs → result-handler-CIyu0Pdt.mjs} +22 -11
  74. package/dist/result-handler-CIyu0Pdt.mjs.map +1 -0
  75. package/dist/{validate-contract-deps-DBH6iTAD.mjs → validate-contract-deps-esa-VQ0h.mjs} +1 -1
  76. package/dist/{validate-contract-deps-DBH6iTAD.mjs.map → validate-contract-deps-esa-VQ0h.mjs.map} +1 -1
  77. package/dist/{verify-C56CuQc7.mjs → verify-BxiVp50b.mjs} +2 -2
  78. package/dist/{verify-C56CuQc7.mjs.map → verify-BxiVp50b.mjs.map} +1 -1
  79. package/package.json +19 -19
  80. package/src/cli.ts +1 -5
  81. package/src/commands/contract-emit.ts +9 -10
  82. package/src/commands/migration-apply.ts +34 -23
  83. package/src/commands/migration-new.ts +39 -17
  84. package/src/commands/migration-plan.ts +119 -104
  85. package/src/commands/migration-show.ts +6 -16
  86. package/src/commands/migration-status.ts +14 -34
  87. package/src/config-loader.ts +35 -29
  88. package/src/config-path-validation.ts +75 -0
  89. package/src/control-api/client.ts +2 -1
  90. package/src/control-api/operations/contract-emit.ts +24 -23
  91. package/src/control-api/types.ts +1 -1
  92. package/src/migration-cli.ts +254 -0
  93. package/src/utils/cli-errors.ts +1 -0
  94. package/src/utils/command-helpers.ts +15 -19
  95. package/src/utils/formatters/graph-migration-mapper.ts +5 -14
  96. package/src/utils/formatters/help.ts +0 -1
  97. package/src/utils/formatters/migrations.ts +2 -29
  98. package/dist/cli-errors-BUuJr6py.mjs +0 -5
  99. package/dist/cli-errors-Dic2eADK.d.mts +0 -4
  100. package/dist/commands/migration-emit.d.mts +0 -38
  101. package/dist/commands/migration-emit.d.mts.map +0 -1
  102. package/dist/commands/migration-emit.mjs +0 -81
  103. package/dist/commands/migration-emit.mjs.map +0 -1
  104. package/dist/config-loader-C4VXKl8f.mjs +0 -43
  105. package/dist/config-loader-C4VXKl8f.mjs.map +0 -1
  106. package/dist/contract-emit-CKig_Lra.mjs.map +0 -1
  107. package/dist/contract-emit-CU-SYNe4.mjs +0 -6
  108. package/dist/contract-emit-gpJNLGs7.mjs.map +0 -1
  109. package/dist/migration-emit-Du4DBMqz.mjs +0 -125
  110. package/dist/migration-emit-Du4DBMqz.mjs.map +0 -1
  111. package/dist/migration-status-CPamfEPj.mjs.map +0 -1
  112. package/dist/migrations-BIsjFjSV.mjs.map +0 -1
  113. package/dist/result-handler-AFK4hxyX.mjs.map +0 -1
  114. package/src/commands/migration-emit.ts +0 -134
  115. package/src/lib/migration-emit.ts +0 -125
  116. package/src/lib/migration-strategy.ts +0 -49
@@ -1 +1 @@
1
- {"version":3,"file":"migration-apply.mjs","names":["destinationHash: string","refName: string | undefined","details: Array<{ label: string; value: string }>","migrations: MigrationBundleSet","pendingMigrations: MigrationApplyStep[]"],"sources":["../../src/commands/migration-apply.ts"],"sourcesContent":["import { EMPTY_CONTRACT_HASH } from '@prisma-next/migration-tools/constants';\nimport { findPathWithDecision } from '@prisma-next/migration-tools/dag';\nimport { readRefs, resolveRef } from '@prisma-next/migration-tools/refs';\nimport type { AttestedMigrationBundle } from '@prisma-next/migration-tools/types';\nimport { MigrationToolsError } from '@prisma-next/migration-tools/types';\nimport { notOk, ok, type Result } from '@prisma-next/utils/result';\nimport { Command } from 'commander';\n\nimport { loadConfig } from '../config-loader';\nimport { createControlClient } from '../control-api/client';\nimport type { MigrationApplyFailure, MigrationApplyStep } from '../control-api/types';\nimport {\n CliStructuredError,\n type CliStructuredError as CliStructuredErrorType,\n errorDatabaseConnectionRequired,\n errorDriverRequired,\n errorRuntime,\n errorTargetMigrationNotSupported,\n errorUnexpected,\n} from '../utils/cli-errors';\nimport {\n addGlobalOptions,\n loadAllBundles,\n type MigrationBundleSet,\n maskConnectionUrl,\n readContractEnvelope,\n resolveMigrationPaths,\n setCommandDescriptions,\n setCommandExamples,\n targetSupportsMigrations,\n toPathDecisionResult,\n} from '../utils/command-helpers';\nimport { formatMigrationApplyCommandOutput } from '../utils/formatters/migrations';\nimport { formatStyledHeader } from '../utils/formatters/styled';\nimport type { CommonCommandOptions } from '../utils/global-flags';\nimport { type GlobalFlags, parseGlobalFlags } from '../utils/global-flags';\nimport { handleResult } from '../utils/result-handler';\nimport { TerminalUI } from '../utils/terminal-ui';\n\ninterface MigrationApplyCommandOptions extends CommonCommandOptions {\n readonly db?: string;\n readonly config?: string;\n readonly ref?: string;\n}\n\nexport interface MigrationApplyResult {\n readonly ok: boolean;\n readonly migrationsApplied: number;\n readonly migrationsTotal: number;\n readonly markerHash: string;\n readonly applied: readonly {\n readonly dirName: string;\n readonly from: string;\n readonly to: string;\n readonly operationsExecuted: number;\n }[];\n readonly summary: string;\n readonly pathDecision?: {\n readonly fromHash: string;\n readonly toHash: string;\n readonly alternativeCount: number;\n readonly tieBreakReasons: readonly string[];\n readonly refName?: string;\n readonly selectedPath: readonly {\n readonly dirName: string;\n readonly migrationId: string | null;\n readonly from: string;\n readonly to: string;\n }[];\n };\n readonly timings: {\n readonly total: number;\n };\n}\n\nfunction mapMigrationToolsError(error: unknown): CliStructuredErrorType {\n if (MigrationToolsError.is(error)) {\n return errorRuntime(error.message, {\n why: error.why,\n fix: error.fix,\n meta: { code: error.code, ...(error.details ?? {}) },\n });\n }\n return errorUnexpected(error instanceof Error ? error.message : String(error), {\n why: `Unexpected error during migration apply: ${error instanceof Error ? error.message : String(error)}`,\n });\n}\n\nfunction mapApplyFailure(failure: MigrationApplyFailure): CliStructuredErrorType {\n return errorRuntime(failure.summary, {\n why: failure.why ?? 'Migration runner failed',\n fix: 'Fix the issue and re-run `prisma-next migration apply` — previously applied migrations are preserved.',\n meta: failure.meta ?? {},\n });\n}\n\nfunction packageToStep(pkg: AttestedMigrationBundle): MigrationApplyStep {\n return {\n dirName: pkg.dirName,\n from: pkg.manifest.from,\n to: pkg.manifest.to,\n toContract: pkg.manifest.toContract,\n operations: pkg.ops,\n };\n}\n\nasync function executeMigrationApplyCommand(\n options: MigrationApplyCommandOptions,\n flags: GlobalFlags,\n ui: TerminalUI,\n startTime: number,\n): Promise<Result<MigrationApplyResult, CliStructuredErrorType>> {\n const config = await loadConfig(options.config);\n const { configPath, migrationsDir, migrationsRelative, refsPath } = resolveMigrationPaths(\n options.config,\n config,\n );\n\n const dbConnection = options.db ?? config.db?.connection;\n if (!dbConnection) {\n return notOk(\n errorDatabaseConnectionRequired({\n why: `Database connection is required for migration apply (set db.connection in ${configPath}, or pass --db <url>)`,\n commandName: 'migration apply',\n }),\n );\n }\n\n if (!config.driver) {\n return notOk(\n errorDriverRequired({\n why: 'Config.driver is required for migration apply',\n }),\n );\n }\n\n if (!targetSupportsMigrations(config.target)) {\n return notOk(\n errorTargetMigrationNotSupported({\n why: `Target \"${config.target.id}\" does not support migrations`,\n }),\n );\n }\n\n let destinationHash: string;\n let refName: string | undefined;\n\n if (options.ref) {\n refName = options.ref;\n try {\n const refs = await readRefs(refsPath);\n destinationHash = resolveRef(refs, refName);\n } catch (error) {\n if (MigrationToolsError.is(error)) {\n return notOk(mapMigrationToolsError(error));\n }\n throw error;\n }\n } else {\n try {\n const envelope = await readContractEnvelope(config);\n destinationHash = envelope.storageHash;\n } catch (error) {\n return notOk(\n errorRuntime('Current contract is unavailable', {\n why: `Failed to read contract: ${error instanceof Error ? error.message : String(error)}`,\n fix: 'Run `prisma-next contract emit` to generate a valid contract.json, then retry apply.',\n }),\n );\n }\n }\n\n if (!flags.json && !flags.quiet) {\n const details: Array<{ label: string; value: string }> = [\n { label: 'config', value: configPath },\n { label: 'migrations', value: migrationsRelative },\n ];\n if (typeof dbConnection === 'string') {\n details.push({\n label: 'database',\n value: maskConnectionUrl(dbConnection),\n });\n }\n if (refName) {\n details.push({ label: 'ref', value: refName });\n }\n const header = formatStyledHeader({\n command: 'migration apply',\n description: 'Apply planned migrations to the database',\n url: 'https://pris.ly/migration-apply',\n details,\n flags,\n });\n ui.stderr(header);\n }\n\n // Read migrations and build migration chain model (offline — no DB needed)\n let migrations: MigrationBundleSet;\n try {\n migrations = await loadAllBundles(migrationsDir);\n if (migrations.drafts.length > 0 && !flags.quiet) {\n ui.warn(\n `${migrations.drafts.length} draft migration(s) found: ${migrations.drafts.map((d) => d.dirName).join(', ')}. ` +\n \"Run 'prisma-next migration emit --dir <path>' to attest before applying.\",\n );\n }\n } catch (error) {\n if (MigrationToolsError.is(error)) {\n return notOk(mapMigrationToolsError(error));\n }\n throw error;\n }\n\n const client = createControlClient({\n family: config.family,\n target: config.target,\n adapter: config.adapter,\n driver: config.driver,\n extensionPacks: config.extensionPacks ?? [],\n });\n\n try {\n await client.connect(dbConnection);\n const marker = await client.readMarker();\n\n // --- No attested migrations on disk ---\n if (migrations.attested.length === 0) {\n if (marker?.storageHash) {\n return notOk(\n errorRuntime('Database has state but no migrations exist', {\n why: `The database marker hash \"${marker.storageHash}\" exists but no attested migrations were found in ${migrationsRelative}`,\n fix: 'Ensure the migrations directory is correct. If the database was managed with `db init` or `db update`, run `prisma-next db sign` to update the marker.',\n meta: { markerHash: marker.storageHash, migrationsDir: migrationsRelative },\n }),\n );\n }\n // Non-empty contract + no migrations = user needs to plan first.\n if (destinationHash !== EMPTY_CONTRACT_HASH) {\n return notOk(\n errorRuntime('Current contract has no planned migrations', {\n why: `No attested migrations were found in ${migrationsRelative}, but current contract hash is \"${destinationHash}\"`,\n fix: 'Run `prisma-next migration plan` to create an attested migration for the current contract.',\n meta: { destinationHash, migrationsDir: migrationsRelative },\n }),\n );\n }\n // Empty contract + no migrations = nothing to do.\n return ok({\n ok: true,\n migrationsApplied: 0,\n migrationsTotal: 0,\n markerHash: EMPTY_CONTRACT_HASH,\n applied: [],\n summary: 'No attested migrations found',\n timings: { total: Date.now() - startTime },\n });\n }\n\n // --- Validate marker state ---\n\n // The empty sentinel should never appear in a real marker row — if it does,\n // the marker was corrupted and replaying all migrations would be dangerous.\n if (marker?.storageHash === EMPTY_CONTRACT_HASH) {\n return notOk(\n errorRuntime('Database marker contains the empty sentinel hash', {\n why: `The marker row exists but contains the empty sentinel value \"${EMPTY_CONTRACT_HASH}\". This should never happen — the marker should contain the hash of the last applied contract.`,\n fix: 'The marker is corrupted. Run `prisma-next db sign` to overwrite it with the correct contract hash, or drop and recreate the database.',\n meta: { markerHash: EMPTY_CONTRACT_HASH },\n }),\n );\n }\n\n const markerHash = marker?.storageHash;\n\n if (markerHash !== undefined && !migrations.graph.nodes.has(markerHash)) {\n return notOk(\n errorRuntime('Database marker does not match any known migration', {\n why: `The database marker hash \"${markerHash}\" is not found in the migration history at ${migrationsRelative}`,\n fix: 'Ensure the migrations directory matches this database. If the database was managed with `db init` or `db update`, run `prisma-next db sign` to update the marker.',\n meta: { markerHash, knownNodes: [...migrations.graph.nodes] },\n }),\n );\n }\n\n if (!migrations.graph.nodes.has(destinationHash)) {\n const matchingDraft = migrations.drafts.find((d) => d.manifest.to === destinationHash);\n return notOk(\n errorRuntime('Current contract has no planned migration path', {\n why: matchingDraft\n ? `A draft migration exists at \"${matchingDraft.dirName}\" but has not been attested`\n : `Current contract hash \"${destinationHash}\" is not present in the migration history at ${migrationsRelative}`,\n fix: matchingDraft\n ? `Run 'prisma-next migration emit --dir \"${migrationsRelative}/${matchingDraft.dirName}\"' to attest, then re-run apply.`\n : 'Run `prisma-next migration plan` to create a migration for the current contract, then re-run apply.',\n meta: { destinationHash, knownNodes: [...migrations.graph.nodes] },\n }),\n );\n }\n\n // --- Resolve path and apply ---\n\n // \"No marker\" means the database is fresh — start from the empty contract hash.\n const originHash = markerHash ?? EMPTY_CONTRACT_HASH;\n\n const decision = findPathWithDecision(migrations.graph, originHash, destinationHash, refName);\n if (!decision) {\n return notOk(\n errorRuntime('No migration path from current state to target', {\n why: `Cannot find a path from \"${originHash}\" to target \"${destinationHash}\"`,\n fix: 'Check the migration history for gaps or inconsistencies.',\n meta: { markerHash: originHash, destinationHash },\n }),\n );\n }\n\n const pendingPath = decision.selectedPath;\n const pathDecision = toPathDecisionResult(decision);\n\n if (pendingPath.length === 0) {\n return ok({\n ok: true,\n migrationsApplied: 0,\n migrationsTotal: 0,\n markerHash: originHash,\n applied: [],\n summary: 'Already up to date',\n pathDecision,\n timings: { total: Date.now() - startTime },\n });\n }\n\n const bundleByDir = new Map(migrations.attested.map((b) => [b.dirName, b]));\n const pendingMigrations: MigrationApplyStep[] = [];\n for (const migration of pendingPath) {\n const pkg = bundleByDir.get(migration.dirName);\n if (!pkg) {\n return notOk(\n errorRuntime(`Migration package not found: ${migration.dirName}`, {\n why: `The migration directory for path segment ${migration.from} → ${migration.to} was not found`,\n fix: 'Ensure all migration directories are present and intact.',\n }),\n );\n }\n pendingMigrations.push(packageToStep(pkg));\n }\n\n if (!flags.quiet && !flags.json) {\n for (const migration of pendingMigrations) {\n ui.step(`Pending ${migration.dirName}`);\n }\n }\n\n const applyResult = await client.migrationApply({\n originHash,\n destinationHash,\n pendingMigrations,\n });\n\n if (!applyResult.ok) {\n return notOk(mapApplyFailure(applyResult.failure));\n }\n\n const { value } = applyResult;\n\n return ok({\n ok: true,\n migrationsApplied: value.migrationsApplied,\n migrationsTotal: pendingPath.length,\n markerHash: value.markerHash,\n applied: value.applied,\n summary: value.summary,\n pathDecision,\n timings: { total: Date.now() - startTime },\n });\n } catch (error) {\n if (CliStructuredError.is(error)) {\n return notOk(error);\n }\n return notOk(\n errorUnexpected(error instanceof Error ? error.message : String(error), {\n why: `Unexpected error during migration apply: ${error instanceof Error ? error.message : String(error)}`,\n }),\n );\n } finally {\n await client.close();\n }\n}\n\nexport function createMigrationApplyCommand(): Command {\n const command = new Command('apply');\n setCommandDescriptions(\n command,\n 'Apply planned migrations to the database',\n 'Applies previously planned migrations (created by `migration plan`) to a live database.\\n' +\n 'Compares the database marker against the migration history to determine which\\n' +\n 'migrations are pending, then executes them sequentially. Each migration runs\\n' +\n 'in its own transaction. Does not plan new migrations — run `migration plan` first.',\n );\n setCommandExamples(command, ['prisma-next migration apply --db $DATABASE_URL']);\n addGlobalOptions(command)\n .option('--db <url>', 'Database connection string')\n .option('--config <path>', 'Path to prisma-next.config.ts')\n .option('--ref <name>', 'Target ref name from migrations/refs/')\n .action(async (options: MigrationApplyCommandOptions) => {\n const flags = parseGlobalFlags(options);\n const startTime = Date.now();\n\n const ui = new TerminalUI({\n color: flags.color,\n interactive: flags.interactive,\n });\n\n const result = await executeMigrationApplyCommand(options, flags, ui, startTime);\n\n const exitCode = handleResult(result, flags, ui, (applyResult) => {\n if (flags.json) {\n ui.output(JSON.stringify(applyResult, null, 2));\n } else if (!flags.quiet) {\n ui.log(formatMigrationApplyCommandOutput(applyResult, flags));\n }\n });\n\n process.exit(exitCode);\n });\n\n return command;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"migration-apply.mjs","names":["destinationHash: string","refName: string | undefined","details: Array<{ label: string; value: string }>","migrations: MigrationBundleSet","pendingMigrations: MigrationApplyStep[]"],"sources":["../../src/commands/migration-apply.ts"],"sourcesContent":["import { verifyMigrationBundle } from '@prisma-next/migration-tools/attestation';\nimport { EMPTY_CONTRACT_HASH } from '@prisma-next/migration-tools/constants';\nimport { findPathWithDecision } from '@prisma-next/migration-tools/dag';\nimport { readRefs, resolveRef } from '@prisma-next/migration-tools/refs';\nimport type { MigrationBundle } from '@prisma-next/migration-tools/types';\nimport { MigrationToolsError } from '@prisma-next/migration-tools/types';\nimport { notOk, ok, type Result } from '@prisma-next/utils/result';\nimport { Command } from 'commander';\n\nimport { loadConfig } from '../config-loader';\nimport { createControlClient } from '../control-api/client';\nimport type { MigrationApplyFailure, MigrationApplyStep } from '../control-api/types';\nimport {\n CliStructuredError,\n type CliStructuredError as CliStructuredErrorType,\n errorDatabaseConnectionRequired,\n errorDriverRequired,\n errorRuntime,\n errorTargetMigrationNotSupported,\n errorUnexpected,\n} from '../utils/cli-errors';\nimport {\n addGlobalOptions,\n loadAllBundles,\n type MigrationBundleSet,\n maskConnectionUrl,\n readContractEnvelope,\n resolveMigrationPaths,\n setCommandDescriptions,\n setCommandExamples,\n targetSupportsMigrations,\n toPathDecisionResult,\n} from '../utils/command-helpers';\nimport { formatMigrationApplyCommandOutput } from '../utils/formatters/migrations';\nimport { formatStyledHeader } from '../utils/formatters/styled';\nimport type { CommonCommandOptions } from '../utils/global-flags';\nimport { type GlobalFlags, parseGlobalFlags } from '../utils/global-flags';\nimport { handleResult } from '../utils/result-handler';\nimport { TerminalUI } from '../utils/terminal-ui';\n\ninterface MigrationApplyCommandOptions extends CommonCommandOptions {\n readonly db?: string;\n readonly config?: string;\n readonly ref?: string;\n}\n\nexport interface MigrationApplyResult {\n readonly ok: boolean;\n readonly migrationsApplied: number;\n readonly migrationsTotal: number;\n readonly markerHash: string;\n readonly applied: readonly {\n readonly dirName: string;\n readonly from: string;\n readonly to: string;\n readonly operationsExecuted: number;\n }[];\n readonly summary: string;\n readonly pathDecision?: {\n readonly fromHash: string;\n readonly toHash: string;\n readonly alternativeCount: number;\n readonly tieBreakReasons: readonly string[];\n readonly refName?: string;\n readonly selectedPath: readonly {\n readonly dirName: string;\n readonly migrationId: string;\n readonly from: string;\n readonly to: string;\n }[];\n };\n readonly timings: {\n readonly total: number;\n };\n}\n\nfunction mapMigrationToolsError(error: unknown): CliStructuredErrorType {\n if (MigrationToolsError.is(error)) {\n return errorRuntime(error.message, {\n why: error.why,\n fix: error.fix,\n meta: { code: error.code, ...(error.details ?? {}) },\n });\n }\n return errorUnexpected(error instanceof Error ? error.message : String(error), {\n why: `Unexpected error during migration apply: ${error instanceof Error ? error.message : String(error)}`,\n });\n}\n\nfunction mapApplyFailure(failure: MigrationApplyFailure): CliStructuredErrorType {\n return errorRuntime(failure.summary, {\n why: failure.why ?? 'Migration runner failed',\n fix: 'Fix the issue and re-run `prisma-next migration apply` — previously applied migrations are preserved.',\n meta: failure.meta ?? {},\n });\n}\n\nfunction packageToStep(pkg: MigrationBundle): MigrationApplyStep {\n return {\n dirName: pkg.dirName,\n from: pkg.manifest.from,\n to: pkg.manifest.to,\n toContract: pkg.manifest.toContract,\n operations: pkg.ops,\n };\n}\n\nasync function executeMigrationApplyCommand(\n options: MigrationApplyCommandOptions,\n flags: GlobalFlags,\n ui: TerminalUI,\n startTime: number,\n): Promise<Result<MigrationApplyResult, CliStructuredErrorType>> {\n const config = await loadConfig(options.config);\n const { configPath, migrationsDir, migrationsRelative, refsPath } = resolveMigrationPaths(\n options.config,\n config,\n );\n\n const dbConnection = options.db ?? config.db?.connection;\n if (!dbConnection) {\n return notOk(\n errorDatabaseConnectionRequired({\n why: `Database connection is required for migration apply (set db.connection in ${configPath}, or pass --db <url>)`,\n commandName: 'migration apply',\n }),\n );\n }\n\n if (!config.driver) {\n return notOk(\n errorDriverRequired({\n why: 'Config.driver is required for migration apply',\n }),\n );\n }\n\n if (!targetSupportsMigrations(config.target)) {\n return notOk(\n errorTargetMigrationNotSupported({\n why: `Target \"${config.target.id}\" does not support migrations`,\n }),\n );\n }\n\n let destinationHash: string;\n let refName: string | undefined;\n\n if (options.ref) {\n refName = options.ref;\n try {\n const refs = await readRefs(refsPath);\n destinationHash = resolveRef(refs, refName);\n } catch (error) {\n if (MigrationToolsError.is(error)) {\n return notOk(mapMigrationToolsError(error));\n }\n throw error;\n }\n } else {\n try {\n const envelope = await readContractEnvelope(config);\n destinationHash = envelope.storageHash;\n } catch (error) {\n return notOk(\n errorRuntime('Current contract is unavailable', {\n why: `Failed to read contract: ${error instanceof Error ? error.message : String(error)}`,\n fix: 'Run `prisma-next contract emit` to generate a valid contract.json, then retry apply.',\n }),\n );\n }\n }\n\n if (!flags.json && !flags.quiet) {\n const details: Array<{ label: string; value: string }> = [\n { label: 'config', value: configPath },\n { label: 'migrations', value: migrationsRelative },\n ];\n if (typeof dbConnection === 'string') {\n details.push({\n label: 'database',\n value: maskConnectionUrl(dbConnection),\n });\n }\n if (refName) {\n details.push({ label: 'ref', value: refName });\n }\n const header = formatStyledHeader({\n command: 'migration apply',\n description: 'Apply planned migrations to the database',\n url: 'https://pris.ly/migration-apply',\n details,\n flags,\n });\n ui.stderr(header);\n }\n\n // Read migrations and build migration chain model (offline — no DB needed)\n let migrations: MigrationBundleSet;\n try {\n migrations = await loadAllBundles(migrationsDir);\n } catch (error) {\n if (MigrationToolsError.is(error)) {\n return notOk(mapMigrationToolsError(error));\n }\n throw error;\n }\n\n // Defense in depth: re-hash every bundle and confirm the recorded\n // `migrationId` matches the on-disk `(manifest, ops)`. Catches FS\n // corruption, partial writes, and post-emit hand edits before we\n // start touching the database.\n for (const bundle of migrations.bundles) {\n const verified = verifyMigrationBundle(bundle);\n if (!verified.ok) {\n return notOk(\n errorRuntime(`Migration package is corrupt: ${bundle.dirName}`, {\n why: `Stored migrationId \"${verified.storedMigrationId}\" does not match the recomputed hash \"${verified.computedMigrationId}\" for ${migrationsRelative}/${bundle.dirName}. The migration.json or ops.json has been edited or partially written since emit.`,\n fix: `Re-emit the package by running \\`node \"${migrationsRelative}/${bundle.dirName}/migration.ts\"\\`, or restore the directory from version control.`,\n meta: {\n dirName: bundle.dirName,\n storedMigrationId: verified.storedMigrationId,\n computedMigrationId: verified.computedMigrationId,\n },\n }),\n );\n }\n }\n\n const client = createControlClient({\n family: config.family,\n target: config.target,\n adapter: config.adapter,\n driver: config.driver,\n extensionPacks: config.extensionPacks ?? [],\n });\n\n try {\n await client.connect(dbConnection);\n const marker = await client.readMarker();\n\n // --- No migrations on disk ---\n if (migrations.bundles.length === 0) {\n if (marker?.storageHash) {\n return notOk(\n errorRuntime('Database has state but no migrations exist', {\n why: `The database marker hash \"${marker.storageHash}\" exists but no migrations were found in ${migrationsRelative}`,\n fix: 'Ensure the migrations directory is correct. If the database was managed with `db init` or `db update`, run `prisma-next db sign` to update the marker.',\n meta: { markerHash: marker.storageHash, migrationsDir: migrationsRelative },\n }),\n );\n }\n // Non-empty contract + no migrations = user needs to plan first.\n if (destinationHash !== EMPTY_CONTRACT_HASH) {\n return notOk(\n errorRuntime('Current contract has no planned migrations', {\n why: `No migrations were found in ${migrationsRelative}, but current contract hash is \"${destinationHash}\"`,\n fix: 'Run `prisma-next migration plan` to create a migration for the current contract.',\n meta: { destinationHash, migrationsDir: migrationsRelative },\n }),\n );\n }\n // Empty contract + no migrations = nothing to do.\n return ok({\n ok: true,\n migrationsApplied: 0,\n migrationsTotal: 0,\n markerHash: EMPTY_CONTRACT_HASH,\n applied: [],\n summary: 'No migrations found',\n timings: { total: Date.now() - startTime },\n });\n }\n\n // --- Validate marker state ---\n\n // The empty sentinel should never appear in a real marker row — if it does,\n // the marker was corrupted and replaying all migrations would be dangerous.\n if (marker?.storageHash === EMPTY_CONTRACT_HASH) {\n return notOk(\n errorRuntime('Database marker contains the empty sentinel hash', {\n why: `The marker row exists but contains the empty sentinel value \"${EMPTY_CONTRACT_HASH}\". This should never happen — the marker should contain the hash of the last applied contract.`,\n fix: 'The marker is corrupted. Run `prisma-next db sign` to overwrite it with the correct contract hash, or drop and recreate the database.',\n meta: { markerHash: EMPTY_CONTRACT_HASH },\n }),\n );\n }\n\n const markerHash = marker?.storageHash;\n\n if (markerHash !== undefined && !migrations.graph.nodes.has(markerHash)) {\n return notOk(\n errorRuntime('Database marker does not match any known migration', {\n why: `The database marker hash \"${markerHash}\" is not found in the migration history at ${migrationsRelative}`,\n fix: 'Ensure the migrations directory matches this database. If the database was managed with `db init` or `db update`, run `prisma-next db sign` to update the marker.',\n meta: { markerHash, knownNodes: [...migrations.graph.nodes] },\n }),\n );\n }\n\n if (!migrations.graph.nodes.has(destinationHash)) {\n return notOk(\n errorRuntime('Current contract has no planned migration path', {\n why: `Current contract hash \"${destinationHash}\" is not present in the migration history at ${migrationsRelative}`,\n fix: 'Run `prisma-next migration plan` to create a migration for the current contract, then re-run apply.',\n meta: { destinationHash, knownNodes: [...migrations.graph.nodes] },\n }),\n );\n }\n\n // --- Resolve path and apply ---\n\n // \"No marker\" means the database is fresh — start from the empty contract hash.\n const originHash = markerHash ?? EMPTY_CONTRACT_HASH;\n\n const decision = findPathWithDecision(migrations.graph, originHash, destinationHash, refName);\n if (!decision) {\n return notOk(\n errorRuntime('No migration path from current state to target', {\n why: `Cannot find a path from \"${originHash}\" to target \"${destinationHash}\"`,\n fix: 'Check the migration history for gaps or inconsistencies.',\n meta: { markerHash: originHash, destinationHash },\n }),\n );\n }\n\n const pendingPath = decision.selectedPath;\n const pathDecision = toPathDecisionResult(decision);\n\n if (pendingPath.length === 0) {\n return ok({\n ok: true,\n migrationsApplied: 0,\n migrationsTotal: 0,\n markerHash: originHash,\n applied: [],\n summary: 'Already up to date',\n pathDecision,\n timings: { total: Date.now() - startTime },\n });\n }\n\n const bundleByDir = new Map(migrations.bundles.map((b) => [b.dirName, b]));\n const pendingMigrations: MigrationApplyStep[] = [];\n for (const migration of pendingPath) {\n const pkg = bundleByDir.get(migration.dirName);\n if (!pkg) {\n return notOk(\n errorRuntime(`Migration package not found: ${migration.dirName}`, {\n why: `The migration directory for path segment ${migration.from} → ${migration.to} was not found`,\n fix: 'Ensure all migration directories are present and intact.',\n }),\n );\n }\n pendingMigrations.push(packageToStep(pkg));\n }\n\n if (!flags.quiet && !flags.json) {\n for (const migration of pendingMigrations) {\n ui.step(`Pending ${migration.dirName}`);\n }\n }\n\n const applyResult = await client.migrationApply({\n originHash,\n destinationHash,\n pendingMigrations,\n });\n\n if (!applyResult.ok) {\n return notOk(mapApplyFailure(applyResult.failure));\n }\n\n const { value } = applyResult;\n\n return ok({\n ok: true,\n migrationsApplied: value.migrationsApplied,\n migrationsTotal: pendingPath.length,\n markerHash: value.markerHash,\n applied: value.applied,\n summary: value.summary,\n pathDecision,\n timings: { total: Date.now() - startTime },\n });\n } catch (error) {\n if (CliStructuredError.is(error)) {\n return notOk(error);\n }\n return notOk(\n errorUnexpected(error instanceof Error ? error.message : String(error), {\n why: `Unexpected error during migration apply: ${error instanceof Error ? error.message : String(error)}`,\n }),\n );\n } finally {\n await client.close();\n }\n}\n\nexport function createMigrationApplyCommand(): Command {\n const command = new Command('apply');\n setCommandDescriptions(\n command,\n 'Apply planned migrations to the database',\n 'Applies previously planned migrations (created by `migration plan`) to a live database.\\n' +\n 'Compares the database marker against the migration history to determine which\\n' +\n 'migrations are pending, then executes them sequentially. Each migration runs\\n' +\n 'in its own transaction. Does not plan new migrations — run `migration plan` first.',\n );\n setCommandExamples(command, ['prisma-next migration apply --db $DATABASE_URL']);\n addGlobalOptions(command)\n .option('--db <url>', 'Database connection string')\n .option('--config <path>', 'Path to prisma-next.config.ts')\n .option('--ref <name>', 'Target ref name from migrations/refs/')\n .action(async (options: MigrationApplyCommandOptions) => {\n const flags = parseGlobalFlags(options);\n const startTime = Date.now();\n\n const ui = new TerminalUI({\n color: flags.color,\n interactive: flags.interactive,\n });\n\n const result = await executeMigrationApplyCommand(options, flags, ui, startTime);\n\n const exitCode = handleResult(result, flags, ui, (applyResult) => {\n if (flags.json) {\n ui.output(JSON.stringify(applyResult, null, 2));\n } else if (!flags.quiet) {\n ui.log(formatMigrationApplyCommandOutput(applyResult, flags));\n }\n });\n\n process.exit(exitCode);\n });\n\n return command;\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"migration-new.d.mts","names":[],"sources":["../../src/commands/migration-new.ts"],"sourcesContent":[],"mappings":";;;;iBAoPgB,yBAAA,CAAA,GAA6B"}
1
+ {"version":3,"file":"migration-new.d.mts","names":[],"sources":["../../src/commands/migration-new.ts"],"sourcesContent":[],"mappings":";;;;iBA0QgB,yBAAA,CAAA,GAA6B"}
@@ -1,17 +1,19 @@
1
- import { t as loadConfig } from "../config-loader-C4VXKl8f.mjs";
2
- import { g as errorTargetMigrationNotSupported, h as errorRuntime, t as CliStructuredError, v as errorUnexpected } from "../cli-errors-BUuJr6py.mjs";
3
- import { t as assertFrameworkComponentsCompatible } from "../framework-components-Bsr1GaIj.mjs";
4
- import { _ as formatStyledHeader, c as resolveMigrationPaths, d as setCommandExamples, m as parseGlobalFlags, n as addGlobalOptions, r as getTargetMigrations, t as handleResult, u as setCommandDescriptions } from "../result-handler-AFK4hxyX.mjs";
1
+ import { t as loadConfig } from "../config-loader-_W4T21X1.mjs";
2
+ import { _ as errorUnexpected, h as errorTargetMigrationNotSupported, m as errorRuntime, t as CliStructuredError } from "../cli-errors-DHq6GQGu.mjs";
3
+ import { t as assertFrameworkComponentsCompatible } from "../framework-components-DfZKQBQ2.mjs";
4
+ import { _ as formatStyledHeader, c as resolveMigrationPaths, d as setCommandExamples, m as parseGlobalFlags, n as addGlobalOptions, r as getTargetMigrations, t as handleResult, u as setCommandDescriptions } from "../result-handler-CIyu0Pdt.mjs";
5
5
  import { t as TerminalUI } from "../terminal-ui-C5k88MmW.mjs";
6
6
  import { Command } from "commander";
7
7
  import { readFileSync } from "node:fs";
8
+ import { getEmittedArtifactPaths } from "@prisma-next/emitter";
8
9
  import { notOk, ok } from "@prisma-next/utils/result";
9
10
  import { join, relative, resolve } from "pathe";
10
11
  import { createControlStack } from "@prisma-next/framework-components/control";
11
12
  import { EMPTY_CONTRACT_HASH } from "@prisma-next/migration-tools/constants";
12
13
  import { findLatestMigration, reconstructGraph } from "@prisma-next/migration-tools/dag";
13
- import { copyContractToMigrationDir, formatMigrationDirName, readMigrationsDir, writeMigrationPackage } from "@prisma-next/migration-tools/io";
14
- import { MigrationToolsError, isAttested } from "@prisma-next/migration-tools/types";
14
+ import { copyFilesWithRename, formatMigrationDirName, readMigrationsDir, writeMigrationPackage } from "@prisma-next/migration-tools/io";
15
+ import { computeMigrationId } from "@prisma-next/migration-tools/attestation";
16
+ import { MigrationToolsError } from "@prisma-next/migration-tools/types";
15
17
  import { writeMigrationTs } from "@prisma-next/migration-tools/migration-ts";
16
18
 
17
19
  //#region src/commands/migration-new.ts
@@ -19,8 +21,7 @@ import { writeMigrationTs } from "@prisma-next/migration-tools/migration-ts";
19
21
  * `migration new` — scaffolds a migration package with a `migration.ts` file
20
22
  * for manual authoring.
21
23
  *
22
- * Both descriptor-flow (Postgres) and class-flow (Mongo) targets go through
23
- * the same path here: the planner's `emptyMigration(context)` returns a
24
+ * The planner's `emptyMigration(context)` returns a
24
25
  * `MigrationPlanWithAuthoringSurface`, whose `renderTypeScript()` produces
25
26
  * the target-appropriate empty stub. The CLI writes the returned source
26
27
  * verbatim.
@@ -56,24 +57,29 @@ async function executeMigrationNewCommand(options) {
56
57
  }));
57
58
  let fromContract = null;
58
59
  let fromHash = EMPTY_CONTRACT_HASH;
60
+ let fromContractSourceDir = null;
59
61
  try {
60
- const attested = (await readMigrationsDir(migrationsDir)).filter(isAttested);
61
- if (attested.length > 0) {
62
- const graph = reconstructGraph(attested);
62
+ const packages = await readMigrationsDir(migrationsDir);
63
+ if (packages.length > 0) {
64
+ const graph = reconstructGraph(packages);
63
65
  if (options.from) {
64
- const match = attested.find((p) => p.manifest.to.startsWith(options.from));
66
+ const match = packages.find((p) => p.manifest.to.startsWith(options.from));
65
67
  if (!match) return notOk(errorRuntime("Starting contract not found", {
66
68
  why: `No migration with to hash matching "${options.from}" exists in ${migrationsRelative}`,
67
69
  fix: "Check that the --from hash matches a known migration target hash."
68
70
  }));
69
71
  fromHash = match.manifest.to;
70
72
  fromContract = match.manifest.toContract;
73
+ fromContractSourceDir = match.dirPath;
71
74
  } else {
72
75
  const latestMigration = findLatestMigration(graph);
73
76
  if (latestMigration) {
74
77
  fromHash = latestMigration.to;
75
- const leafPkg = attested.find((p) => p.manifest.migrationId === latestMigration.migrationId);
76
- if (leafPkg) fromContract = leafPkg.manifest.toContract;
78
+ const leafPkg = packages.find((p) => p.manifest.migrationId === latestMigration.migrationId);
79
+ if (leafPkg) {
80
+ fromContract = leafPkg.manifest.toContract;
81
+ fromContractSourceDir = leafPkg.dirPath;
82
+ }
77
83
  }
78
84
  }
79
85
  }
@@ -91,22 +97,24 @@ async function executeMigrationNewCommand(options) {
91
97
  }));
92
98
  const timestamp = /* @__PURE__ */ new Date();
93
99
  const packageDir = join(migrationsDir, formatMigrationDirName(timestamp, options.name ?? "migration"));
94
- const manifest = {
100
+ const baseManifest = {
95
101
  from: fromHash,
96
102
  to: toStorageHash,
97
- migrationId: null,
98
103
  kind: "regular",
99
104
  fromContract,
100
105
  toContract: toContractJson,
101
106
  hints: {
102
107
  used: [],
103
108
  applied: [],
104
- plannerVersion: "1.0.0",
105
- planningStrategy: "manual"
109
+ plannerVersion: "1.0.0"
106
110
  },
107
111
  labels: [],
108
112
  createdAt: timestamp.toISOString()
109
113
  };
114
+ const manifest = {
115
+ ...baseManifest,
116
+ migrationId: computeMigrationId(baseManifest, [])
117
+ };
110
118
  const migrations = getTargetMigrations(config.target);
111
119
  if (!migrations) return notOk(errorTargetMigrationNotSupported({ why: `Target "${config.target.targetId}" does not support migrations` }));
112
120
  try {
@@ -116,12 +124,29 @@ async function executeMigrationNewCommand(options) {
116
124
  ...config.extensionPacks ?? []
117
125
  ]);
118
126
  await writeMigrationPackage(packageDir, manifest, []);
119
- await copyContractToMigrationDir(packageDir, contractPathAbsolute);
127
+ const destinationArtifacts = getEmittedArtifactPaths(contractPathAbsolute);
128
+ await copyFilesWithRename(packageDir, [{
129
+ sourcePath: destinationArtifacts.jsonPath,
130
+ destName: "end-contract.json"
131
+ }, {
132
+ sourcePath: destinationArtifacts.dtsPath,
133
+ destName: "end-contract.d.ts"
134
+ }]);
135
+ if (fromContractSourceDir !== null) {
136
+ const sourceArtifacts = getEmittedArtifactPaths(join(fromContractSourceDir, "end-contract.json"));
137
+ await copyFilesWithRename(packageDir, [{
138
+ sourcePath: sourceArtifacts.jsonPath,
139
+ destName: "start-contract.json"
140
+ }, {
141
+ sourcePath: sourceArtifacts.dtsPath,
142
+ destName: "start-contract.d.ts"
143
+ }]);
144
+ }
120
145
  const stack = createControlStack(config);
121
146
  const familyInstance = config.family.create(stack);
122
147
  await writeMigrationTs(packageDir, migrations.createPlanner(familyInstance).emptyMigration({
123
148
  packageDir,
124
- contractJsonPath: join(packageDir, "contract.json"),
149
+ contractJsonPath: join(packageDir, "end-contract.json"),
125
150
  fromHash,
126
151
  toHash: toStorageHash
127
152
  }).renderTypeScript());
@@ -139,7 +164,7 @@ async function executeMigrationNewCommand(options) {
139
164
  }
140
165
  function createMigrationNewCommand() {
141
166
  const command = new Command("new");
142
- setCommandDescriptions(command, "Scaffold a new migration for manual authoring", "Creates a migration package with a migration.ts file for manual authoring.\nWrite operation descriptors and data transforms in migration.ts, then run\n`migration emit` to resolve and attest the package.");
167
+ setCommandDescriptions(command, "Scaffold a new migration for manual authoring", "Creates a migration package with a migration.ts file for manual authoring.\nWrite the migration body in migration.ts, then run the file with Node\n(`node migration.ts`) to self-emit ops.json and attest the package.");
143
168
  setCommandExamples(command, ["prisma-next migration new --name split-name", "prisma-next migration new --name custom-fk --from sha256:abc..."]);
144
169
  addGlobalOptions(command).option("--name <slug>", "Migration name (used in directory name)").option("--from <hash>", "Starting contract hash (default: latest migration target)").option("--config <path>", "Path to prisma-next.config.ts").action(async (options) => {
145
170
  const flags = parseGlobalFlags(options);
@@ -162,7 +187,7 @@ function createMigrationNewCommand() {
162
187
  ui.output(`\nScaffolded migration at ${value.dir}`);
163
188
  ui.output(` from: ${value.from}`);
164
189
  ui.output(` to: ${value.to}`);
165
- ui.output(`\nEdit migration.ts, then run \`prisma-next migration emit --dir "${value.dir}"\` to attest.`);
190
+ ui.output(`\nEdit migration.ts, then run it directly (\`node "${value.dir}/migration.ts"\`) to self-emit and attest.`);
166
191
  }
167
192
  });
168
193
  process.exit(exitCode);
@@ -1 +1 @@
1
- {"version":3,"file":"migration-new.mjs","names":["contractJsonContent: string","toContractJson: Contract","fromContract: Contract | null","fromHash: string","manifest: MigrationManifest"],"sources":["../../src/commands/migration-new.ts"],"sourcesContent":["/**\n * `migration new` — scaffolds a migration package with a `migration.ts` file\n * for manual authoring.\n *\n * Both descriptor-flow (Postgres) and class-flow (Mongo) targets go through\n * the same path here: the planner's `emptyMigration(context)` returns a\n * `MigrationPlanWithAuthoringSurface`, whose `renderTypeScript()` produces\n * the target-appropriate empty stub. The CLI writes the returned source\n * verbatim.\n */\n\nimport { readFileSync } from 'node:fs';\nimport type { Contract } from '@prisma-next/contract/types';\nimport { createControlStack } from '@prisma-next/framework-components/control';\nimport { EMPTY_CONTRACT_HASH } from '@prisma-next/migration-tools/constants';\nimport { findLatestMigration, reconstructGraph } from '@prisma-next/migration-tools/dag';\nimport {\n copyContractToMigrationDir,\n formatMigrationDirName,\n readMigrationsDir,\n writeMigrationPackage,\n} from '@prisma-next/migration-tools/io';\nimport { writeMigrationTs } from '@prisma-next/migration-tools/migration-ts';\nimport type { MigrationManifest } from '@prisma-next/migration-tools/types';\nimport { isAttested, MigrationToolsError } from '@prisma-next/migration-tools/types';\nimport { notOk, ok, type Result } from '@prisma-next/utils/result';\nimport { Command } from 'commander';\nimport { join, relative, resolve } from 'pathe';\nimport { loadConfig } from '../config-loader';\nimport {\n CliStructuredError,\n errorRuntime,\n errorTargetMigrationNotSupported,\n errorUnexpected,\n} from '../utils/cli-errors';\nimport {\n addGlobalOptions,\n getTargetMigrations,\n resolveMigrationPaths,\n setCommandDescriptions,\n setCommandExamples,\n} from '../utils/command-helpers';\nimport { formatStyledHeader } from '../utils/formatters/styled';\nimport { assertFrameworkComponentsCompatible } from '../utils/framework-components';\nimport type { CommonCommandOptions } from '../utils/global-flags';\nimport { parseGlobalFlags } from '../utils/global-flags';\nimport { handleResult } from '../utils/result-handler';\nimport { TerminalUI } from '../utils/terminal-ui';\n\ninterface MigrationNewOptions extends CommonCommandOptions {\n readonly name?: string;\n readonly from?: string;\n readonly config?: string;\n}\n\ninterface MigrationNewResult {\n readonly ok: true;\n readonly dir: string;\n readonly from: string;\n readonly to: string;\n readonly summary: string;\n}\n\nasync function executeMigrationNewCommand(\n options: MigrationNewOptions,\n): Promise<Result<MigrationNewResult, CliStructuredError>> {\n const config = await loadConfig(options.config);\n const { migrationsDir, migrationsRelative } = resolveMigrationPaths(options.config, config);\n\n const contractPath = config.contract?.output ?? 'contract.json';\n const contractPathAbsolute = resolve(\n options.config ? resolve(options.config, '..') : process.cwd(),\n contractPath,\n );\n\n let contractJsonContent: string;\n try {\n contractJsonContent = readFileSync(contractPathAbsolute, 'utf-8');\n } catch (error) {\n if (error instanceof Error && (error as { code?: string }).code === 'ENOENT') {\n return notOk(\n errorRuntime(`Contract file not found at ${contractPathAbsolute}`, {\n why: `Contract file not found at ${contractPathAbsolute}`,\n fix: 'Run `prisma-next contract emit` first to generate the contract',\n }),\n );\n }\n throw error;\n }\n\n let toContractJson: Contract;\n try {\n toContractJson = JSON.parse(contractJsonContent) as Contract;\n } catch (error) {\n return notOk(\n errorRuntime('Contract JSON is invalid', {\n why: `Failed to parse ${contractPathAbsolute}: ${error instanceof Error ? error.message : String(error)}`,\n fix: 'Run `prisma-next contract emit` to regenerate the contract',\n }),\n );\n }\n\n const toStorageHash = (\n (toContractJson as unknown as Record<string, unknown>)['storage'] as\n | Record<string, unknown>\n | undefined\n )?.['storageHash'] as string | undefined;\n if (!toStorageHash) {\n return notOk(\n errorRuntime('Contract is missing storageHash', {\n why: `Contract at ${contractPathAbsolute} has no storageHash`,\n fix: 'Run `prisma-next contract emit` to regenerate the contract',\n }),\n );\n }\n\n let fromContract: Contract | null = null;\n let fromHash: string = EMPTY_CONTRACT_HASH;\n\n try {\n const packages = await readMigrationsDir(migrationsDir);\n const attested = packages.filter(isAttested);\n\n if (attested.length > 0) {\n const graph = reconstructGraph(attested);\n\n if (options.from) {\n const match = attested.find((p) => p.manifest.to.startsWith(options.from!));\n if (!match) {\n return notOk(\n errorRuntime('Starting contract not found', {\n why: `No migration with to hash matching \"${options.from}\" exists in ${migrationsRelative}`,\n fix: 'Check that the --from hash matches a known migration target hash.',\n }),\n );\n }\n fromHash = match.manifest.to;\n fromContract = match.manifest.toContract;\n } else {\n const latestMigration = findLatestMigration(graph);\n if (latestMigration) {\n fromHash = latestMigration.to;\n const leafPkg = attested.find(\n (p) => p.manifest.migrationId === latestMigration.migrationId,\n );\n if (leafPkg) {\n fromContract = leafPkg.manifest.toContract;\n }\n }\n }\n }\n } catch (error) {\n if (MigrationToolsError.is(error)) {\n return notOk(\n errorRuntime(error.message, {\n why: error.why,\n fix: error.fix,\n meta: { code: error.code },\n }),\n );\n }\n throw error;\n }\n\n if (fromHash === toStorageHash) {\n return notOk(\n errorRuntime('No changes detected', {\n why: 'The from and to contract hashes are identical — there is nothing to migrate.',\n fix: 'Change the contract and run `prisma-next contract emit` before creating a new migration.',\n }),\n );\n }\n\n const timestamp = new Date();\n const slug = options.name ?? 'migration';\n const dirName = formatMigrationDirName(timestamp, slug);\n const packageDir = join(migrationsDir, dirName);\n\n const manifest: MigrationManifest = {\n from: fromHash,\n to: toStorageHash,\n migrationId: null,\n kind: 'regular',\n fromContract,\n toContract: toContractJson,\n hints: {\n used: [],\n applied: [],\n plannerVersion: '1.0.0',\n planningStrategy: 'manual',\n },\n labels: [],\n createdAt: timestamp.toISOString(),\n };\n\n const migrations = getTargetMigrations(config.target);\n if (!migrations) {\n return notOk(\n errorTargetMigrationNotSupported({\n why: `Target \"${config.target.targetId}\" does not support migrations`,\n }),\n );\n }\n\n try {\n assertFrameworkComponentsCompatible(config.family.familyId, config.target.targetId, [\n config.target,\n config.adapter,\n ...(config.extensionPacks ?? []),\n ]);\n\n await writeMigrationPackage(packageDir, manifest, []);\n await copyContractToMigrationDir(packageDir, contractPathAbsolute);\n\n const stack = createControlStack(config);\n const familyInstance = config.family.create(stack);\n const planner = migrations.createPlanner(familyInstance);\n const emptyPlan = planner.emptyMigration({\n packageDir,\n contractJsonPath: join(packageDir, 'contract.json'),\n fromHash,\n toHash: toStorageHash,\n });\n await writeMigrationTs(packageDir, emptyPlan.renderTypeScript());\n\n return ok({\n ok: true as const,\n dir: relative(process.cwd(), packageDir),\n from: fromHash,\n to: toStorageHash,\n summary: `Scaffolded migration at ${relative(process.cwd(), packageDir)}`,\n });\n } catch (error) {\n if (CliStructuredError.is(error)) {\n return notOk(error);\n }\n return notOk(\n errorUnexpected(error instanceof Error ? error.message : String(error), {\n why: `Failed to scaffold migration: ${error instanceof Error ? error.message : String(error)}`,\n }),\n );\n }\n}\n\nexport function createMigrationNewCommand(): Command {\n const command = new Command('new');\n setCommandDescriptions(\n command,\n 'Scaffold a new migration for manual authoring',\n 'Creates a migration package with a migration.ts file for manual authoring.\\n' +\n 'Write operation descriptors and data transforms in migration.ts, then run\\n' +\n '`migration emit` to resolve and attest the package.',\n );\n setCommandExamples(command, [\n 'prisma-next migration new --name split-name',\n 'prisma-next migration new --name custom-fk --from sha256:abc...',\n ]);\n addGlobalOptions(command)\n .option('--name <slug>', 'Migration name (used in directory name)')\n .option('--from <hash>', 'Starting contract hash (default: latest migration target)')\n .option('--config <path>', 'Path to prisma-next.config.ts')\n .action(async (options: MigrationNewOptions) => {\n const flags = parseGlobalFlags(options);\n const ui = new TerminalUI({ color: flags.color, interactive: flags.interactive });\n\n if (!flags.json && !flags.quiet) {\n const header = formatStyledHeader({\n command: 'migration new',\n description: 'Scaffold a new migration',\n details: [],\n flags,\n });\n ui.stderr(header);\n }\n\n const result = await executeMigrationNewCommand(options);\n\n const exitCode = handleResult(result, flags, ui, (value) => {\n if (flags.json) {\n ui.output(JSON.stringify(value, null, 2));\n } else if (!flags.quiet) {\n ui.output(`\\nScaffolded migration at ${value.dir}`);\n ui.output(` from: ${value.from}`);\n ui.output(` to: ${value.to}`);\n ui.output(\n `\\nEdit migration.ts, then run \\`prisma-next migration emit --dir \"${value.dir}\"\\` to attest.`,\n );\n }\n });\n\n process.exit(exitCode);\n });\n\n return command;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"migration-new.mjs","names":["contractJsonContent: string","toContractJson: Contract","fromContract: Contract | null","fromHash: string","fromContractSourceDir: string | null","baseManifest: Omit<MigrationManifest, 'migrationId'>","manifest: MigrationManifest"],"sources":["../../src/commands/migration-new.ts"],"sourcesContent":["/**\n * `migration new` — scaffolds a migration package with a `migration.ts` file\n * for manual authoring.\n *\n * The planner's `emptyMigration(context)` returns a\n * `MigrationPlanWithAuthoringSurface`, whose `renderTypeScript()` produces\n * the target-appropriate empty stub. The CLI writes the returned source\n * verbatim.\n */\n\nimport { readFileSync } from 'node:fs';\nimport type { Contract } from '@prisma-next/contract/types';\nimport { getEmittedArtifactPaths } from '@prisma-next/emitter';\nimport { createControlStack } from '@prisma-next/framework-components/control';\nimport { computeMigrationId } from '@prisma-next/migration-tools/attestation';\nimport { EMPTY_CONTRACT_HASH } from '@prisma-next/migration-tools/constants';\nimport { findLatestMigration, reconstructGraph } from '@prisma-next/migration-tools/dag';\nimport {\n copyFilesWithRename,\n formatMigrationDirName,\n readMigrationsDir,\n writeMigrationPackage,\n} from '@prisma-next/migration-tools/io';\nimport { writeMigrationTs } from '@prisma-next/migration-tools/migration-ts';\nimport type { MigrationManifest } from '@prisma-next/migration-tools/types';\nimport { MigrationToolsError } from '@prisma-next/migration-tools/types';\nimport { notOk, ok, type Result } from '@prisma-next/utils/result';\nimport { Command } from 'commander';\nimport { join, relative, resolve } from 'pathe';\nimport { loadConfig } from '../config-loader';\nimport {\n CliStructuredError,\n errorRuntime,\n errorTargetMigrationNotSupported,\n errorUnexpected,\n} from '../utils/cli-errors';\nimport {\n addGlobalOptions,\n getTargetMigrations,\n resolveMigrationPaths,\n setCommandDescriptions,\n setCommandExamples,\n} from '../utils/command-helpers';\nimport { formatStyledHeader } from '../utils/formatters/styled';\nimport { assertFrameworkComponentsCompatible } from '../utils/framework-components';\nimport type { CommonCommandOptions } from '../utils/global-flags';\nimport { parseGlobalFlags } from '../utils/global-flags';\nimport { handleResult } from '../utils/result-handler';\nimport { TerminalUI } from '../utils/terminal-ui';\n\ninterface MigrationNewOptions extends CommonCommandOptions {\n readonly name?: string;\n readonly from?: string;\n readonly config?: string;\n}\n\ninterface MigrationNewResult {\n readonly ok: true;\n readonly dir: string;\n readonly from: string;\n readonly to: string;\n readonly summary: string;\n}\n\nasync function executeMigrationNewCommand(\n options: MigrationNewOptions,\n): Promise<Result<MigrationNewResult, CliStructuredError>> {\n const config = await loadConfig(options.config);\n const { migrationsDir, migrationsRelative } = resolveMigrationPaths(options.config, config);\n\n const contractPath = config.contract?.output ?? 'contract.json';\n const contractPathAbsolute = resolve(\n options.config ? resolve(options.config, '..') : process.cwd(),\n contractPath,\n );\n\n let contractJsonContent: string;\n try {\n contractJsonContent = readFileSync(contractPathAbsolute, 'utf-8');\n } catch (error) {\n if (error instanceof Error && (error as { code?: string }).code === 'ENOENT') {\n return notOk(\n errorRuntime(`Contract file not found at ${contractPathAbsolute}`, {\n why: `Contract file not found at ${contractPathAbsolute}`,\n fix: 'Run `prisma-next contract emit` first to generate the contract',\n }),\n );\n }\n throw error;\n }\n\n let toContractJson: Contract;\n try {\n toContractJson = JSON.parse(contractJsonContent) as Contract;\n } catch (error) {\n return notOk(\n errorRuntime('Contract JSON is invalid', {\n why: `Failed to parse ${contractPathAbsolute}: ${error instanceof Error ? error.message : String(error)}`,\n fix: 'Run `prisma-next contract emit` to regenerate the contract',\n }),\n );\n }\n\n const toStorageHash = (\n (toContractJson as unknown as Record<string, unknown>)['storage'] as\n | Record<string, unknown>\n | undefined\n )?.['storageHash'] as string | undefined;\n if (!toStorageHash) {\n return notOk(\n errorRuntime('Contract is missing storageHash', {\n why: `Contract at ${contractPathAbsolute} has no storageHash`,\n fix: 'Run `prisma-next contract emit` to regenerate the contract',\n }),\n );\n }\n\n let fromContract: Contract | null = null;\n let fromHash: string = EMPTY_CONTRACT_HASH;\n let fromContractSourceDir: string | null = null;\n\n try {\n const packages = await readMigrationsDir(migrationsDir);\n\n if (packages.length > 0) {\n const graph = reconstructGraph(packages);\n\n if (options.from) {\n const match = packages.find((p) => p.manifest.to.startsWith(options.from!));\n if (!match) {\n return notOk(\n errorRuntime('Starting contract not found', {\n why: `No migration with to hash matching \"${options.from}\" exists in ${migrationsRelative}`,\n fix: 'Check that the --from hash matches a known migration target hash.',\n }),\n );\n }\n fromHash = match.manifest.to;\n fromContract = match.manifest.toContract;\n fromContractSourceDir = match.dirPath;\n } else {\n const latestMigration = findLatestMigration(graph);\n if (latestMigration) {\n fromHash = latestMigration.to;\n const leafPkg = packages.find(\n (p) => p.manifest.migrationId === latestMigration.migrationId,\n );\n if (leafPkg) {\n fromContract = leafPkg.manifest.toContract;\n fromContractSourceDir = leafPkg.dirPath;\n }\n }\n }\n }\n } catch (error) {\n if (MigrationToolsError.is(error)) {\n return notOk(\n errorRuntime(error.message, {\n why: error.why,\n fix: error.fix,\n meta: { code: error.code },\n }),\n );\n }\n throw error;\n }\n\n if (fromHash === toStorageHash) {\n return notOk(\n errorRuntime('No changes detected', {\n why: 'The from and to contract hashes are identical — there is nothing to migrate.',\n fix: 'Change the contract and run `prisma-next contract emit` before creating a new migration.',\n }),\n );\n }\n\n const timestamp = new Date();\n const slug = options.name ?? 'migration';\n const dirName = formatMigrationDirName(timestamp, slug);\n const packageDir = join(migrationsDir, dirName);\n\n // `migration new` scaffolds an empty `migration.ts` for the user to\n // fill, so we attest over `ops: []`. Re-running self-emit after the\n // user adds operations will produce a different `migrationId` (over\n // the real ops). This is intentional — there is no on-disk draft.\n const baseManifest: Omit<MigrationManifest, 'migrationId'> = {\n from: fromHash,\n to: toStorageHash,\n kind: 'regular',\n fromContract,\n toContract: toContractJson,\n hints: {\n used: [],\n applied: [],\n plannerVersion: '1.0.0',\n },\n labels: [],\n createdAt: timestamp.toISOString(),\n };\n const manifest: MigrationManifest = {\n ...baseManifest,\n migrationId: computeMigrationId(baseManifest, []),\n };\n\n const migrations = getTargetMigrations(config.target);\n if (!migrations) {\n return notOk(\n errorTargetMigrationNotSupported({\n why: `Target \"${config.target.targetId}\" does not support migrations`,\n }),\n );\n }\n\n try {\n assertFrameworkComponentsCompatible(config.family.familyId, config.target.targetId, [\n config.target,\n config.adapter,\n ...(config.extensionPacks ?? []),\n ]);\n\n await writeMigrationPackage(packageDir, manifest, []);\n const destinationArtifacts = getEmittedArtifactPaths(contractPathAbsolute);\n await copyFilesWithRename(packageDir, [\n { sourcePath: destinationArtifacts.jsonPath, destName: 'end-contract.json' },\n { sourcePath: destinationArtifacts.dtsPath, destName: 'end-contract.d.ts' },\n ]);\n if (fromContractSourceDir !== null) {\n const sourceArtifacts = getEmittedArtifactPaths(\n join(fromContractSourceDir, 'end-contract.json'),\n );\n await copyFilesWithRename(packageDir, [\n { sourcePath: sourceArtifacts.jsonPath, destName: 'start-contract.json' },\n { sourcePath: sourceArtifacts.dtsPath, destName: 'start-contract.d.ts' },\n ]);\n }\n\n const stack = createControlStack(config);\n const familyInstance = config.family.create(stack);\n const planner = migrations.createPlanner(familyInstance);\n const emptyPlan = planner.emptyMigration({\n packageDir,\n contractJsonPath: join(packageDir, 'end-contract.json'),\n fromHash,\n toHash: toStorageHash,\n });\n await writeMigrationTs(packageDir, emptyPlan.renderTypeScript());\n\n return ok({\n ok: true as const,\n dir: relative(process.cwd(), packageDir),\n from: fromHash,\n to: toStorageHash,\n summary: `Scaffolded migration at ${relative(process.cwd(), packageDir)}`,\n });\n } catch (error) {\n if (CliStructuredError.is(error)) {\n return notOk(error);\n }\n return notOk(\n errorUnexpected(error instanceof Error ? error.message : String(error), {\n why: `Failed to scaffold migration: ${error instanceof Error ? error.message : String(error)}`,\n }),\n );\n }\n}\n\nexport function createMigrationNewCommand(): Command {\n const command = new Command('new');\n setCommandDescriptions(\n command,\n 'Scaffold a new migration for manual authoring',\n 'Creates a migration package with a migration.ts file for manual authoring.\\n' +\n 'Write the migration body in migration.ts, then run the file with Node\\n' +\n '(`node migration.ts`) to self-emit ops.json and attest the package.',\n );\n setCommandExamples(command, [\n 'prisma-next migration new --name split-name',\n 'prisma-next migration new --name custom-fk --from sha256:abc...',\n ]);\n addGlobalOptions(command)\n .option('--name <slug>', 'Migration name (used in directory name)')\n .option('--from <hash>', 'Starting contract hash (default: latest migration target)')\n .option('--config <path>', 'Path to prisma-next.config.ts')\n .action(async (options: MigrationNewOptions) => {\n const flags = parseGlobalFlags(options);\n const ui = new TerminalUI({ color: flags.color, interactive: flags.interactive });\n\n if (!flags.json && !flags.quiet) {\n const header = formatStyledHeader({\n command: 'migration new',\n description: 'Scaffold a new migration',\n details: [],\n flags,\n });\n ui.stderr(header);\n }\n\n const result = await executeMigrationNewCommand(options);\n\n const exitCode = handleResult(result, flags, ui, (value) => {\n if (flags.json) {\n ui.output(JSON.stringify(value, null, 2));\n } else if (!flags.quiet) {\n ui.output(`\\nScaffolded migration at ${value.dir}`);\n ui.output(` from: ${value.from}`);\n ui.output(` to: ${value.to}`);\n ui.output(\n `\\nEdit migration.ts, then run it directly (\\`node \"${value.dir}/migration.ts\"\\`) to self-emit and attest.`,\n );\n }\n });\n\n process.exit(exitCode);\n });\n\n return command;\n}\n"],"mappings":"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"}
@@ -7,7 +7,6 @@ interface MigrationPlanResult {
7
7
  readonly noOp: boolean;
8
8
  readonly from: string;
9
9
  readonly to: string;
10
- readonly migrationId?: string;
11
10
  readonly dir?: string;
12
11
  readonly operations: readonly {
13
12
  readonly id: string;
@@ -16,6 +15,12 @@ interface MigrationPlanResult {
16
15
  }[];
17
16
  readonly sql?: readonly string[];
18
17
  readonly summary: string;
18
+ /**
19
+ * When true, `migration.ts` was written but contains unfilled
20
+ * `placeholder(...)` calls. The user must edit the file and then run
21
+ * `node migration.ts` to self-emit `ops.json` / `migration.json`.
22
+ */
23
+ readonly pendingPlaceholders?: boolean;
19
24
  readonly timings: {
20
25
  readonly total: number;
21
26
  };
@@ -1 +1 @@
1
- {"version":3,"file":"migration-plan.d.mts","names":[],"sources":["../../src/commands/migration-plan.ts"],"sourcesContent":[],"mappings":";;;;UAmDiB,mBAAA;;EAAA,SAAA,IAAA,EAAA,OAAmB;EAoVpB,SAAA,IAAA,EAAA,MAAA;EA4GJ,SAAA,EAAA,EAAA,MAAA;EAcI,SAAA,WAAA,CAAA,EAAA,MAAqB;EACjB,SAAA,GAAA,CAAA,EAAA,MAAA;EAEV,SAAA,UAAA,EAAA,SAAA;IAAG,SAAA,EAAA,EAAA,MAAA;IAAV,SAAA,KAAA,EAAA,MAAA;IAAM,SAAA,cAAA,EAAA,MAAA;;;;;;;;iBA7HO,0BAAA,CAAA,GAA8B;KA4GlC,uBAAA;;;;;;;;;;;;;;;;iBAcI;;;;qBACI,sBAEjB,OAAO,GAAG"}
1
+ {"version":3,"file":"migration-plan.d.mts","names":[],"sources":["../../src/commands/migration-plan.ts"],"sourcesContent":[],"mappings":";;;;UAsDiB,mBAAA;;EAAA,SAAA,IAAA,EAAA,OAAmB;EA8UpB,SAAA,IAAA,EAAA,MAAA;EA8HJ,SAAA,EAAA,EAAA,MAAA;EAcI,SAAA,GAAA,CAAA,EAAA,MAAA;EACI,SAAA,UAAA,EAAA,SAAA;IAEV,SAAA,EAAA,EAAA,MAAA;IAAG,SAAA,KAAA,EAAA,MAAA;IAAV,SAAA,cAAA,EAAA,MAAA;EAAM,CAAA,EAAA;;;;;;;;;;;;;iBA/IO,0BAAA,CAAA,GAA8B;KA8HlC,uBAAA;;;;;;;;;;;;;;;;iBAcI;;;;qBACI,sBAEjB,OAAO,GAAG"}
@@ -1,18 +1,19 @@
1
- import { t as loadConfig } from "../config-loader-C4VXKl8f.mjs";
2
- import { a as errorContractValidationFailed, g as errorTargetMigrationNotSupported, h as errorRuntime, l as errorFileNotFound, p as errorMigrationPlanningFailed, t as CliStructuredError, v as errorUnexpected } from "../cli-errors-BUuJr6py.mjs";
3
- import { t as assertFrameworkComponentsCompatible } from "../framework-components-Bsr1GaIj.mjs";
1
+ import { t as loadConfig } from "../config-loader-_W4T21X1.mjs";
2
+ import { _ as errorUnexpected, a as errorContractValidationFailed, f as errorMigrationPlanningFailed, h as errorTargetMigrationNotSupported, l as errorFileNotFound, m as errorRuntime, t as CliStructuredError } from "../cli-errors-DHq6GQGu.mjs";
3
+ import { t as assertFrameworkComponentsCompatible } from "../framework-components-DfZKQBQ2.mjs";
4
4
  import { t as extractSqlDdl } from "../extract-sql-ddl-DDMX-9mz.mjs";
5
- import { _ as formatStyledHeader, c as resolveMigrationPaths, d as setCommandExamples, i as loadAllBundles, m as parseGlobalFlags, n as addGlobalOptions, r as getTargetMigrations, s as resolveContractPath, t as handleResult, u as setCommandDescriptions } from "../result-handler-AFK4hxyX.mjs";
5
+ import { _ as formatStyledHeader, c as resolveMigrationPaths, d as setCommandExamples, i as loadAllBundles, m as parseGlobalFlags, n as addGlobalOptions, r as getTargetMigrations, s as resolveContractPath, t as handleResult, u as setCommandDescriptions } from "../result-handler-CIyu0Pdt.mjs";
6
6
  import { t as TerminalUI } from "../terminal-ui-C5k88MmW.mjs";
7
- import { n as migrationStrategy, t as emitMigration } from "../migration-emit-Du4DBMqz.mjs";
8
7
  import { Command } from "commander";
8
+ import { getEmittedArtifactPaths } from "@prisma-next/emitter";
9
9
  import { notOk, ok } from "@prisma-next/utils/result";
10
10
  import { join, relative } from "pathe";
11
11
  import { createControlStack } from "@prisma-next/framework-components/control";
12
12
  import { EMPTY_CONTRACT_HASH } from "@prisma-next/migration-tools/constants";
13
13
  import { readFile } from "node:fs/promises";
14
14
  import { findLatestMigration } from "@prisma-next/migration-tools/dag";
15
- import { copyContractToMigrationDir, formatMigrationDirName, writeMigrationPackage } from "@prisma-next/migration-tools/io";
15
+ import { copyFilesWithRename, formatMigrationDirName, writeMigrationPackage } from "@prisma-next/migration-tools/io";
16
+ import { computeMigrationId } from "@prisma-next/migration-tools/attestation";
16
17
  import { MigrationToolsError } from "@prisma-next/migration-tools/types";
17
18
  import { writeMigrationTs } from "@prisma-next/migration-tools/migration-ts";
18
19
 
@@ -87,13 +88,9 @@ async function executeMigrationPlanCommand(options, flags, ui, startTime) {
87
88
  const toStorageHash = rawStorageHash;
88
89
  let fromContract = null;
89
90
  let fromHash = EMPTY_CONTRACT_HASH;
91
+ let fromContractSourceDir = null;
90
92
  try {
91
- const { attested: bundles, drafts, graph } = await loadAllBundles(migrationsDir);
92
- const existingDraft = drafts.find((d) => d.manifest.to === toStorageHash);
93
- if (existingDraft) return notOk(errorRuntime("A draft migration to this contract already exists", {
94
- why: `Draft migration at "${existingDraft.dirName}" already targets ${toStorageHash}`,
95
- fix: `Run 'prisma-next migration emit --dir ${migrationsRelative}/${existingDraft.dirName}' to attest it, or delete it and re-plan.`
96
- }));
93
+ const { bundles, graph } = await loadAllBundles(migrationsDir);
97
94
  if (options.from) {
98
95
  const resolved = resolveBundleByPrefix(bundles, options.from);
99
96
  if (!resolved.ok) {
@@ -108,12 +105,16 @@ async function executeMigrationPlanCommand(options, flags, ui, startTime) {
108
105
  }
109
106
  fromHash = resolved.value.manifest.to;
110
107
  fromContract = resolved.value.manifest.toContract;
108
+ fromContractSourceDir = resolved.value.dirPath;
111
109
  } else {
112
110
  const latestMigration = findLatestMigration(graph);
113
111
  if (latestMigration) {
114
112
  fromHash = latestMigration.to;
115
113
  const leafPkg = bundles.find((p) => p.manifest.migrationId === latestMigration.migrationId);
116
- if (leafPkg) fromContract = leafPkg.manifest.toContract;
114
+ if (leafPkg) {
115
+ fromContract = leafPkg.manifest.toContract;
116
+ fromContractSourceDir = leafPkg.dirPath;
117
+ }
117
118
  }
118
119
  }
119
120
  } catch (error) {
@@ -136,93 +137,103 @@ async function executeMigrationPlanCommand(options, flags, ui, startTime) {
136
137
  config.adapter,
137
138
  ...config.extensionPacks ?? []
138
139
  ]);
139
- const strategy = migrationStrategy(migrations, config.target.targetId);
140
140
  const timestamp = /* @__PURE__ */ new Date();
141
141
  const packageDir = join(migrationsDir, formatMigrationDirName(timestamp, options.name ?? "migration"));
142
- const manifest = {
142
+ const baseManifest = {
143
143
  from: fromHash,
144
144
  to: toStorageHash,
145
- migrationId: null,
146
145
  kind: "regular",
147
146
  fromContract,
148
147
  toContract: toContractJson,
149
148
  hints: {
150
149
  used: [],
151
150
  applied: [],
152
- plannerVersion: "2.0.0",
153
- planningStrategy: strategy === "descriptor" ? "descriptors" : "class-based"
151
+ plannerVersion: "2.0.0"
154
152
  },
155
153
  labels: [],
156
154
  createdAt: timestamp.toISOString()
157
155
  };
158
- const scaffoldContext = {
159
- packageDir,
160
- contractJsonPath: contractPathAbsolute,
161
- fromHash,
162
- toHash: toStorageHash
163
- };
164
156
  try {
165
- let migrationTsContent;
166
- if (strategy === "descriptor") {
167
- if (!migrations.planWithDescriptors || !migrations.renderDescriptorTypeScript) throw errorTargetMigrationNotSupported({ why: `Target "${config.target.targetId}" advertises descriptor flow but is missing required hooks` });
168
- const descriptorResult = migrations.planWithDescriptors({
169
- fromContract,
170
- toContract: toContractJson,
171
- frameworkComponents
172
- });
173
- if (!descriptorResult.ok) return notOk(errorMigrationPlanningFailed({ conflicts: descriptorResult.conflicts }));
174
- if (descriptorResult.descriptors.length === 0) return notOk(errorMigrationPlanningFailed({ conflicts: [{
175
- kind: "unsupportedChange",
176
- summary: "Contract changed but planner produced no operations. This indicates unsupported or ignored changes."
177
- }] }));
178
- migrationTsContent = migrations.renderDescriptorTypeScript(descriptorResult.descriptors, scaffoldContext);
179
- } else {
180
- const stack = createControlStack(config);
181
- const familyInstance = config.family.create(stack);
182
- const planner = migrations.createPlanner(familyInstance);
183
- const fromSchema = migrations.contractToSchema(fromContract, frameworkComponents);
184
- const plannerResult = planner.plan({
185
- contract: toContractJson,
186
- schema: fromSchema,
187
- policy: { allowedOperationClasses: [
188
- "additive",
189
- "widening",
190
- "destructive",
191
- "data"
192
- ] },
193
- fromHash,
194
- frameworkComponents
195
- });
196
- if (plannerResult.kind === "failure") return notOk(errorMigrationPlanningFailed({ conflicts: plannerResult.conflicts }));
197
- if (plannerResult.plan.operations.length === 0) return notOk(errorMigrationPlanningFailed({ conflicts: [{
157
+ const stack = createControlStack(config);
158
+ const familyInstance = config.family.create(stack);
159
+ const planner = migrations.createPlanner(familyInstance);
160
+ const fromSchema = migrations.contractToSchema(fromContract, frameworkComponents);
161
+ const plannerResult = planner.plan({
162
+ contract: toContractJson,
163
+ schema: fromSchema,
164
+ policy: { allowedOperationClasses: [
165
+ "additive",
166
+ "widening",
167
+ "destructive",
168
+ "data"
169
+ ] },
170
+ fromHash,
171
+ fromContract,
172
+ frameworkComponents
173
+ });
174
+ if (plannerResult.kind === "failure") return notOk(errorMigrationPlanningFailed({ conflicts: plannerResult.conflicts }));
175
+ let plannedOps = [];
176
+ let hasPlaceholders = false;
177
+ try {
178
+ plannedOps = plannerResult.plan.operations;
179
+ if (plannedOps.length === 0) return notOk(errorMigrationPlanningFailed({ conflicts: [{
198
180
  kind: "unsupportedChange",
199
181
  summary: "Contract changed but planner produced no operations. This indicates unsupported or ignored changes."
200
182
  }] }));
201
- migrationTsContent = plannerResult.plan.renderTypeScript();
183
+ } catch (e) {
184
+ if (CliStructuredError.is(e) && e.domain === "MIG" && e.code === "2001") hasPlaceholders = true;
185
+ else throw e;
186
+ }
187
+ const migrationTsContent = plannerResult.plan.renderTypeScript();
188
+ const opsForWrite = hasPlaceholders ? [] : plannedOps;
189
+ await writeMigrationPackage(packageDir, {
190
+ ...baseManifest,
191
+ migrationId: computeMigrationId(baseManifest, opsForWrite)
192
+ }, opsForWrite);
193
+ const destinationArtifacts = getEmittedArtifactPaths(contractPathAbsolute);
194
+ await copyFilesWithRename(packageDir, [{
195
+ sourcePath: destinationArtifacts.jsonPath,
196
+ destName: "end-contract.json"
197
+ }, {
198
+ sourcePath: destinationArtifacts.dtsPath,
199
+ destName: "end-contract.d.ts"
200
+ }]);
201
+ if (fromContractSourceDir !== null) {
202
+ const sourceArtifacts = getEmittedArtifactPaths(join(fromContractSourceDir, "end-contract.json"));
203
+ await copyFilesWithRename(packageDir, [{
204
+ sourcePath: sourceArtifacts.jsonPath,
205
+ destName: "start-contract.json"
206
+ }, {
207
+ sourcePath: sourceArtifacts.dtsPath,
208
+ destName: "start-contract.d.ts"
209
+ }]);
202
210
  }
203
- await writeMigrationPackage(packageDir, manifest, []);
204
- await copyContractToMigrationDir(packageDir, contractPathAbsolute);
205
211
  await writeMigrationTs(packageDir, migrationTsContent);
206
- const { operations, migrationId } = await emitMigration(packageDir, {
207
- targetId: config.target.targetId,
208
- migrations,
209
- frameworkComponents
212
+ if (hasPlaceholders) return ok({
213
+ ok: true,
214
+ noOp: false,
215
+ from: fromHash,
216
+ to: toStorageHash,
217
+ dir: relative(process.cwd(), packageDir),
218
+ operations: [],
219
+ pendingPlaceholders: true,
220
+ summary: "Planned migration with placeholder(s) — edit migration.ts then run `node migration.ts` to self-emit",
221
+ timings: { total: Date.now() - startTime }
210
222
  });
211
- const sql = extractSqlDdl(operations);
223
+ const sql = extractSqlDdl(plannedOps);
212
224
  return ok({
213
225
  ok: true,
214
226
  noOp: false,
215
227
  from: fromHash,
216
228
  to: toStorageHash,
217
- migrationId,
218
229
  dir: relative(process.cwd(), packageDir),
219
- operations: operations.map((op) => ({
230
+ operations: plannedOps.map((op) => ({
220
231
  id: op.id,
221
232
  label: op.label,
222
233
  operationClass: op.operationClass
223
234
  })),
224
235
  sql,
225
- summary: `Planned ${operations.length} operation(s)`,
236
+ summary: `Planned ${plannedOps.length} operation(s)`,
226
237
  timings: { total: Date.now() - startTime }
227
238
  });
228
239
  } catch (error) {
@@ -260,6 +271,17 @@ function formatMigrationPlanOutput(result, flags) {
260
271
  lines.push(dim_(` to: ${result.to}`));
261
272
  return lines.join("\n");
262
273
  }
274
+ if (result.pendingPlaceholders) {
275
+ lines.push(`${yellow_("⚠")} ${result.summary}`);
276
+ lines.push("");
277
+ lines.push(dim_(`from: ${result.from}`));
278
+ lines.push(dim_(`to: ${result.to}`));
279
+ if (result.dir) lines.push(dim_(`dir: ${result.dir}`));
280
+ lines.push("");
281
+ lines.push("Open migration.ts and replace each `placeholder(...)` call with your actual query.");
282
+ lines.push(`Then run: ${green_(`node ${result.dir ?? "<dir>"}/migration.ts`)}`);
283
+ return lines.join("\n");
284
+ }
263
285
  lines.push(`${green_("✔")} ${result.summary}`);
264
286
  lines.push("");
265
287
  if (result.operations.length > 0) {
@@ -278,8 +300,9 @@ function formatMigrationPlanOutput(result, flags) {
278
300
  }
279
301
  lines.push(dim_(`from: ${result.from}`));
280
302
  lines.push(dim_(`to: ${result.to}`));
281
- if (result.migrationId) lines.push(dim_(`migrationId: ${result.migrationId}`));
282
303
  if (result.dir) lines.push(dim_(`dir: ${result.dir}`));
304
+ lines.push("");
305
+ lines.push(`Next: ${green_(`node ${result.dir ?? "<dir>"}/migration.ts`)} to emit ops.json and attest migrationId before running ${green_("prisma-next migration apply")}.`);
283
306
  if (result.sql && result.sql.length > 0) {
284
307
  lines.push("");
285
308
  lines.push(dim_("DDL preview"));