@prisma-next/cli 0.3.0-dev.55 → 0.3.0-dev.63

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 (88) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +145 -6
  3. package/dist/cli-errors-JlPTsazx.mjs +3 -0
  4. package/dist/cli.mjs +29 -2
  5. package/dist/cli.mjs.map +1 -1
  6. package/dist/{client-B7f4PZZ1.mjs → client-PimzSD1f.mjs} +155 -82
  7. package/dist/client-PimzSD1f.mjs.map +1 -0
  8. package/dist/commands/contract-emit.mjs +5 -3
  9. package/dist/commands/contract-emit.mjs.map +1 -1
  10. package/dist/commands/db-init.mjs +5 -4
  11. package/dist/commands/db-init.mjs.map +1 -1
  12. package/dist/commands/db-introspect.mjs +5 -3
  13. package/dist/commands/db-introspect.mjs.map +1 -1
  14. package/dist/commands/db-schema-verify.d.mts.map +1 -1
  15. package/dist/commands/db-schema-verify.mjs +6 -4
  16. package/dist/commands/db-schema-verify.mjs.map +1 -1
  17. package/dist/commands/db-sign.d.mts.map +1 -1
  18. package/dist/commands/db-sign.mjs +6 -4
  19. package/dist/commands/db-sign.mjs.map +1 -1
  20. package/dist/commands/db-update.mjs +5 -4
  21. package/dist/commands/db-update.mjs.map +1 -1
  22. package/dist/commands/db-verify.d.mts.map +1 -1
  23. package/dist/commands/db-verify.mjs +6 -4
  24. package/dist/commands/db-verify.mjs.map +1 -1
  25. package/dist/commands/migration-apply.d.mts +23 -0
  26. package/dist/commands/migration-apply.d.mts.map +1 -0
  27. package/dist/commands/migration-apply.mjs +249 -0
  28. package/dist/commands/migration-apply.mjs.map +1 -0
  29. package/dist/commands/migration-plan.d.mts +25 -0
  30. package/dist/commands/migration-plan.d.mts.map +1 -0
  31. package/dist/commands/migration-plan.mjs +266 -0
  32. package/dist/commands/migration-plan.mjs.map +1 -0
  33. package/dist/commands/migration-show.d.mts +28 -0
  34. package/dist/commands/migration-show.d.mts.map +1 -0
  35. package/dist/commands/migration-show.mjs +138 -0
  36. package/dist/commands/migration-show.mjs.map +1 -0
  37. package/dist/commands/migration-status.d.mts +35 -0
  38. package/dist/commands/migration-status.d.mts.map +1 -0
  39. package/dist/commands/migration-status.mjs +259 -0
  40. package/dist/commands/migration-status.mjs.map +1 -0
  41. package/dist/commands/migration-verify.d.mts +16 -0
  42. package/dist/commands/migration-verify.d.mts.map +1 -0
  43. package/dist/commands/migration-verify.mjs +86 -0
  44. package/dist/commands/migration-verify.mjs.map +1 -0
  45. package/dist/{config-loader-DqKf1qSa.mjs → config-loader-PPf4CtDj.mjs} +4 -3
  46. package/dist/config-loader-PPf4CtDj.mjs.map +1 -0
  47. package/dist/config-loader.d.mts +1 -1
  48. package/dist/config-loader.d.mts.map +1 -1
  49. package/dist/config-loader.mjs +1 -1
  50. package/dist/exports/config-types.d.mts +1 -1
  51. package/dist/exports/config-types.mjs +1 -1
  52. package/dist/exports/control-api.d.mts +100 -2
  53. package/dist/exports/control-api.d.mts.map +1 -1
  54. package/dist/exports/control-api.mjs +3 -2
  55. package/dist/exports/control-api.mjs.map +1 -1
  56. package/dist/exports/index.mjs +1 -1
  57. package/dist/extract-sql-ddl-BmlKvk4o.mjs +26 -0
  58. package/dist/extract-sql-ddl-BmlKvk4o.mjs.map +1 -0
  59. package/dist/framework-components-CjV_jD8f.mjs +59 -0
  60. package/dist/framework-components-CjV_jD8f.mjs.map +1 -0
  61. package/dist/{migration-command-scaffold-BELw_do2.mjs → migration-command-scaffold-DfY_F3ev.mjs} +7 -5
  62. package/dist/migration-command-scaffold-DfY_F3ev.mjs.map +1 -0
  63. package/dist/progress-adapter-DENrzF6I.mjs +49 -0
  64. package/dist/progress-adapter-DENrzF6I.mjs.map +1 -0
  65. package/dist/{result-handler-BhmrXIvT.mjs → result-handler-iA9JtUC7.mjs} +158 -51
  66. package/dist/result-handler-iA9JtUC7.mjs.map +1 -0
  67. package/package.json +34 -12
  68. package/src/cli.ts +38 -0
  69. package/src/commands/db-schema-verify.ts +6 -4
  70. package/src/commands/db-sign.ts +6 -4
  71. package/src/commands/db-verify.ts +6 -4
  72. package/src/commands/migration-apply.ts +431 -0
  73. package/src/commands/migration-plan.ts +446 -0
  74. package/src/commands/migration-show.ts +255 -0
  75. package/src/commands/migration-status.ts +436 -0
  76. package/src/commands/migration-verify.ts +151 -0
  77. package/src/config-loader.ts +13 -3
  78. package/src/control-api/client.ts +31 -0
  79. package/src/control-api/operations/migration-apply.ts +195 -0
  80. package/src/control-api/types.ts +113 -1
  81. package/src/exports/config-types.ts +3 -3
  82. package/src/utils/command-helpers.ts +11 -0
  83. package/src/utils/migration-command-scaffold.ts +7 -6
  84. package/src/utils/output.ts +305 -3
  85. package/dist/client-B7f4PZZ1.mjs.map +0 -1
  86. package/dist/config-loader-DqKf1qSa.mjs.map +0 -1
  87. package/dist/migration-command-scaffold-BELw_do2.mjs.map +0 -1
  88. package/dist/result-handler-BhmrXIvT.mjs.map +0 -1
@@ -0,0 +1,86 @@
1
+ import { _ as errorUnexpected, m as errorRuntime } from "../cli-errors-JlPTsazx.mjs";
2
+ import { C as parseGlobalFlags, D as setCommandDescriptions, n as formatCommandHelp, p as formatMigrationVerifyCommandOutput, t as handleResult, y as formatStyledHeader } from "../result-handler-iA9JtUC7.mjs";
3
+ import { Command } from "commander";
4
+ import { notOk, ok } from "@prisma-next/utils/result";
5
+ import { ifDefined } from "@prisma-next/utils/defined";
6
+ import { MigrationToolsError } from "@prisma-next/migration-tools/types";
7
+ import { attestMigration, verifyMigration } from "@prisma-next/migration-tools/attestation";
8
+
9
+ //#region src/commands/migration-verify.ts
10
+ async function executeMigrationVerifyCommand(options, flags) {
11
+ const dir = options.dir;
12
+ if (flags.json !== "object" && !flags.quiet) {
13
+ const header = formatStyledHeader({
14
+ command: "migration verify",
15
+ description: "Verify migration package integrity",
16
+ details: [{
17
+ label: "dir",
18
+ value: dir
19
+ }],
20
+ flags
21
+ });
22
+ console.log(header);
23
+ }
24
+ try {
25
+ const result = await verifyMigration(dir);
26
+ if (result.ok) return ok({
27
+ ok: true,
28
+ status: "verified",
29
+ dir,
30
+ ...ifDefined("migrationId", result.storedMigrationId),
31
+ ...ifDefined("storedMigrationId", result.storedMigrationId),
32
+ ...ifDefined("computedMigrationId", result.computedMigrationId),
33
+ summary: "Migration package verified — migrationId matches"
34
+ });
35
+ if (result.reason === "draft") {
36
+ const migrationId = await attestMigration(dir);
37
+ return ok({
38
+ ok: true,
39
+ status: "attested",
40
+ dir,
41
+ migrationId,
42
+ summary: `Draft migration attested with migrationId: ${migrationId}`
43
+ });
44
+ }
45
+ return notOk(errorRuntime("migrationId mismatch — migration has been modified", {
46
+ why: `stored=${result.storedMigrationId}, computed=${result.computedMigrationId}`,
47
+ fix: "If the change was intentional, set \"migrationId\" to null in migration.json and rerun `migration verify` to re-attest. Otherwise, restore the original migration.",
48
+ meta: {
49
+ storedMigrationId: result.storedMigrationId,
50
+ computedMigrationId: result.computedMigrationId
51
+ }
52
+ }));
53
+ } catch (error) {
54
+ if (MigrationToolsError.is(error)) return notOk(errorRuntime(error.message, {
55
+ why: error.why,
56
+ fix: error.fix,
57
+ meta: {
58
+ code: error.code,
59
+ ...error.details ?? {}
60
+ }
61
+ }));
62
+ return notOk(errorUnexpected(error instanceof Error ? error.message : String(error), { why: `Failed to verify migration: ${error instanceof Error ? error.message : String(error)}` }));
63
+ }
64
+ }
65
+ function createMigrationVerifyCommand() {
66
+ const command = new Command("verify");
67
+ setCommandDescriptions(command, "Verify a migration package migrationId", "Recomputes the content-addressed migrationId for a migration package and compares\nit against the stored value. Draft migrations (migrationId: null) are automatically\nattested.");
68
+ command.configureHelp({ formatHelp: (cmd) => {
69
+ return formatCommandHelp({
70
+ command: cmd,
71
+ flags: parseGlobalFlags({})
72
+ });
73
+ } }).requiredOption("--dir <path>", "Path to the migration package directory").option("--json [format]", "Output as JSON (object)", false).option("-q, --quiet", "Quiet mode: errors only").option("-v, --verbose", "Verbose output").option("-vv, --trace", "Trace output").option("--timestamps", "Add timestamps to output").option("--color", "Force color output").option("--no-color", "Disable color output").action(async (options) => {
74
+ const flags = parseGlobalFlags(options);
75
+ const exitCode = handleResult(await executeMigrationVerifyCommand(options, flags), flags, (verifyResult) => {
76
+ if (flags.json === "object") console.log(JSON.stringify(verifyResult, null, 2));
77
+ else if (!flags.quiet) console.log(formatMigrationVerifyCommandOutput(verifyResult, flags));
78
+ });
79
+ process.exit(exitCode);
80
+ });
81
+ return command;
82
+ }
83
+
84
+ //#endregion
85
+ export { createMigrationVerifyCommand };
86
+ //# sourceMappingURL=migration-verify.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migration-verify.mjs","names":[],"sources":["../../src/commands/migration-verify.ts"],"sourcesContent":["import { attestMigration, verifyMigration } from '@prisma-next/migration-tools/attestation';\nimport { MigrationToolsError } from '@prisma-next/migration-tools/types';\nimport { ifDefined } from '@prisma-next/utils/defined';\nimport { notOk, ok, type Result } from '@prisma-next/utils/result';\nimport { Command } from 'commander';\nimport { type CliStructuredError, errorRuntime, errorUnexpected } from '../utils/cli-errors';\nimport { setCommandDescriptions } from '../utils/command-helpers';\nimport { type GlobalFlags, parseGlobalFlags } from '../utils/global-flags';\nimport {\n formatCommandHelp,\n formatMigrationVerifyCommandOutput,\n formatStyledHeader,\n} from '../utils/output';\nimport { handleResult } from '../utils/result-handler';\n\ninterface MigrationVerifyOptions {\n readonly dir: string;\n readonly json?: string | boolean;\n readonly quiet?: boolean;\n readonly q?: boolean;\n readonly verbose?: boolean;\n readonly v?: boolean;\n readonly vv?: boolean;\n readonly trace?: boolean;\n readonly timestamps?: boolean;\n readonly color?: boolean;\n readonly 'no-color'?: boolean;\n}\n\nexport interface MigrationVerifyResult {\n readonly ok: boolean;\n readonly status: 'verified' | 'attested';\n readonly dir: string;\n readonly migrationId?: string;\n readonly storedMigrationId?: string;\n readonly computedMigrationId?: string;\n readonly summary: string;\n}\n\nasync function executeMigrationVerifyCommand(\n options: MigrationVerifyOptions,\n flags: GlobalFlags,\n): Promise<Result<MigrationVerifyResult, CliStructuredError>> {\n const dir = options.dir;\n\n if (flags.json !== 'object' && !flags.quiet) {\n const header = formatStyledHeader({\n command: 'migration verify',\n description: 'Verify migration package integrity',\n details: [{ label: 'dir', value: dir }],\n flags,\n });\n console.log(header);\n }\n\n try {\n const result = await verifyMigration(dir);\n\n if (result.ok) {\n return ok({\n ok: true,\n status: 'verified',\n dir,\n ...ifDefined('migrationId', result.storedMigrationId),\n ...ifDefined('storedMigrationId', result.storedMigrationId),\n ...ifDefined('computedMigrationId', result.computedMigrationId),\n summary: 'Migration package verified — migrationId matches',\n });\n }\n\n if (result.reason === 'draft') {\n const migrationId = await attestMigration(dir);\n return ok({\n ok: true,\n status: 'attested',\n dir,\n migrationId,\n summary: `Draft migration attested with migrationId: ${migrationId}`,\n });\n }\n\n return notOk(\n errorRuntime('migrationId mismatch — migration has been modified', {\n why: `stored=${result.storedMigrationId}, computed=${result.computedMigrationId}`,\n fix: 'If the change was intentional, set \"migrationId\" to null in migration.json and rerun `migration verify` to re-attest. Otherwise, restore the original migration.',\n meta: {\n storedMigrationId: result.storedMigrationId,\n computedMigrationId: result.computedMigrationId,\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, ...(error.details ?? {}) },\n }),\n );\n }\n return notOk(\n errorUnexpected(error instanceof Error ? error.message : String(error), {\n why: `Failed to verify migration: ${error instanceof Error ? error.message : String(error)}`,\n }),\n );\n }\n}\n\nexport function createMigrationVerifyCommand(): Command {\n const command = new Command('verify');\n setCommandDescriptions(\n command,\n 'Verify a migration package migrationId',\n 'Recomputes the content-addressed migrationId for a migration package and compares\\n' +\n 'it against the stored value. Draft migrations (migrationId: null) are automatically\\n' +\n 'attested.',\n );\n command\n .configureHelp({\n formatHelp: (cmd) => {\n const defaultFlags = parseGlobalFlags({});\n return formatCommandHelp({ command: cmd, flags: defaultFlags });\n },\n })\n .requiredOption('--dir <path>', 'Path to the migration package directory')\n .option('--json [format]', 'Output as JSON (object)', false)\n .option('-q, --quiet', 'Quiet mode: errors only')\n .option('-v, --verbose', 'Verbose output')\n .option('-vv, --trace', 'Trace output')\n .option('--timestamps', 'Add timestamps to output')\n .option('--color', 'Force color output')\n .option('--no-color', 'Disable color output')\n .action(async (options: MigrationVerifyOptions) => {\n const flags = parseGlobalFlags(options);\n\n const result = await executeMigrationVerifyCommand(options, flags);\n\n const exitCode = handleResult(result, flags, (verifyResult) => {\n if (flags.json === 'object') {\n console.log(JSON.stringify(verifyResult, null, 2));\n } else if (!flags.quiet) {\n console.log(formatMigrationVerifyCommandOutput(verifyResult, flags));\n }\n });\n\n process.exit(exitCode);\n });\n\n return command;\n}\n"],"mappings":";;;;;;;;;AAuCA,eAAe,8BACb,SACA,OAC4D;CAC5D,MAAM,MAAM,QAAQ;AAEpB,KAAI,MAAM,SAAS,YAAY,CAAC,MAAM,OAAO;EAC3C,MAAM,SAAS,mBAAmB;GAChC,SAAS;GACT,aAAa;GACb,SAAS,CAAC;IAAE,OAAO;IAAO,OAAO;IAAK,CAAC;GACvC;GACD,CAAC;AACF,UAAQ,IAAI,OAAO;;AAGrB,KAAI;EACF,MAAM,SAAS,MAAM,gBAAgB,IAAI;AAEzC,MAAI,OAAO,GACT,QAAO,GAAG;GACR,IAAI;GACJ,QAAQ;GACR;GACA,GAAG,UAAU,eAAe,OAAO,kBAAkB;GACrD,GAAG,UAAU,qBAAqB,OAAO,kBAAkB;GAC3D,GAAG,UAAU,uBAAuB,OAAO,oBAAoB;GAC/D,SAAS;GACV,CAAC;AAGJ,MAAI,OAAO,WAAW,SAAS;GAC7B,MAAM,cAAc,MAAM,gBAAgB,IAAI;AAC9C,UAAO,GAAG;IACR,IAAI;IACJ,QAAQ;IACR;IACA;IACA,SAAS,8CAA8C;IACxD,CAAC;;AAGJ,SAAO,MACL,aAAa,sDAAsD;GACjE,KAAK,UAAU,OAAO,kBAAkB,aAAa,OAAO;GAC5D,KAAK;GACL,MAAM;IACJ,mBAAmB,OAAO;IAC1B,qBAAqB,OAAO;IAC7B;GACF,CAAC,CACH;UACM,OAAO;AACd,MAAI,oBAAoB,GAAG,MAAM,CAC/B,QAAO,MACL,aAAa,MAAM,SAAS;GAC1B,KAAK,MAAM;GACX,KAAK,MAAM;GACX,MAAM;IAAE,MAAM,MAAM;IAAM,GAAI,MAAM,WAAW,EAAE;IAAG;GACrD,CAAC,CACH;AAEH,SAAO,MACL,gBAAgB,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM,EAAE,EACtE,KAAK,+BAA+B,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM,IAC3F,CAAC,CACH;;;AAIL,SAAgB,+BAAwC;CACtD,MAAM,UAAU,IAAI,QAAQ,SAAS;AACrC,wBACE,SACA,0CACA,oLAGD;AACD,SACG,cAAc,EACb,aAAa,QAAQ;AAEnB,SAAO,kBAAkB;GAAE,SAAS;GAAK,OADpB,iBAAiB,EAAE,CAAC;GACqB,CAAC;IAElE,CAAC,CACD,eAAe,gBAAgB,0CAA0C,CACzE,OAAO,mBAAmB,2BAA2B,MAAM,CAC3D,OAAO,eAAe,0BAA0B,CAChD,OAAO,iBAAiB,iBAAiB,CACzC,OAAO,gBAAgB,eAAe,CACtC,OAAO,gBAAgB,2BAA2B,CAClD,OAAO,WAAW,qBAAqB,CACvC,OAAO,cAAc,uBAAuB,CAC5C,OAAO,OAAO,YAAoC;EACjD,MAAM,QAAQ,iBAAiB,QAAQ;EAIvC,MAAM,WAAW,aAFF,MAAM,8BAA8B,SAAS,MAAM,EAE5B,QAAQ,iBAAiB;AAC7D,OAAI,MAAM,SAAS,SACjB,SAAQ,IAAI,KAAK,UAAU,cAAc,MAAM,EAAE,CAAC;YACzC,CAAC,MAAM,MAChB,SAAQ,IAAI,mCAAmC,cAAc,MAAM,CAAC;IAEtE;AAEF,UAAQ,KAAK,SAAS;GACtB;AAEJ,QAAO"}
@@ -1,6 +1,6 @@
1
- import { errorConfigFileNotFound, errorUnexpected } from "@prisma-next/core-control-plane/errors";
1
+ import { errorConfigFileNotFound, errorConfigValidation, errorUnexpected } from "@prisma-next/core-control-plane/errors";
2
2
  import { dirname, resolve } from "node:path";
3
- import { validateConfig } from "@prisma-next/core-control-plane/config-validation";
3
+ import { ConfigValidationError, validateConfig } from "@prisma-next/config/config-validation";
4
4
  import { loadConfig } from "c12";
5
5
 
6
6
  //#region src/config-loader.ts
@@ -28,6 +28,7 @@ async function loadConfig$1(configPath) {
28
28
  validateConfig(result.config);
29
29
  return result.config;
30
30
  } catch (error) {
31
+ if (error instanceof ConfigValidationError) throw errorConfigValidation(error.field, { why: error.why });
31
32
  if (error instanceof Error && "code" in error && typeof error.code === "string") throw error;
32
33
  if (error instanceof Error) {
33
34
  if (error.message.includes("not found") || error.message.includes("Cannot find") || error.message.includes("ENOENT")) throw errorConfigFileNotFound(configPath ? resolve(process.cwd(), configPath) : void 0, { why: error.message });
@@ -39,4 +40,4 @@ async function loadConfig$1(configPath) {
39
40
 
40
41
  //#endregion
41
42
  export { loadConfig$1 as t };
42
- //# sourceMappingURL=config-loader-DqKf1qSa.mjs.map
43
+ //# sourceMappingURL=config-loader-PPf4CtDj.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-loader-PPf4CtDj.mjs","names":["loadConfig","loadConfigC12"],"sources":["../src/config-loader.ts"],"sourcesContent":["import { dirname, resolve } from 'node:path';\nimport type { PrismaNextConfig } from '@prisma-next/config/config-types';\nimport { ConfigValidationError, validateConfig } from '@prisma-next/config/config-validation';\nimport {\n errorConfigFileNotFound,\n errorConfigValidation,\n errorUnexpected,\n} from '@prisma-next/core-control-plane/errors';\nimport { loadConfig as loadConfigC12 } from 'c12';\n\n/**\n * Loads the Prisma Next config from a TypeScript file.\n * Supports both default export and named export.\n * Uses c12 to automatically handle TypeScript compilation and config file discovery.\n *\n * @param configPath - Optional path to config file. Defaults to `./prisma-next.config.ts` in current directory.\n * @returns The loaded config object.\n * @throws Error if config file doesn't exist or is invalid.\n */\nexport async function loadConfig(configPath?: string): Promise<PrismaNextConfig> {\n try {\n const cwd = process.cwd();\n // Resolve config path to absolute path and set cwd to config directory when path is provided\n const resolvedConfigPath = configPath ? resolve(cwd, configPath) : undefined;\n const configCwd = resolvedConfigPath ? dirname(resolvedConfigPath) : cwd;\n\n const result = await loadConfigC12<PrismaNextConfig>({\n name: 'prisma-next',\n ...(resolvedConfigPath ? { configFile: resolvedConfigPath } : {}),\n cwd: configCwd,\n });\n\n // When a specific config file was requested, verify it was actually loaded\n // (c12 falls back to searching by name if the specified file doesn't exist)\n if (resolvedConfigPath && result.configFile !== resolvedConfigPath) {\n throw errorConfigFileNotFound(resolvedConfigPath);\n }\n\n // Check if config is missing or empty (c12 may return empty object when file doesn't exist)\n if (!result.config || Object.keys(result.config).length === 0) {\n // Use c12's configFile if available, otherwise use explicit configPath, otherwise omit path\n const displayPath = result.configFile || resolvedConfigPath || configPath;\n throw errorConfigFileNotFound(displayPath);\n }\n\n // Validate config structure\n validateConfig(result.config);\n\n return result.config;\n } catch (error) {\n if (error instanceof ConfigValidationError) {\n throw errorConfigValidation(error.field, {\n why: error.why,\n });\n }\n\n // Re-throw structured errors as-is\n if (\n error instanceof Error &&\n 'code' in error &&\n typeof (error as { code: string }).code === 'string'\n ) {\n throw error;\n }\n\n if (error instanceof Error) {\n // Check for file not found errors\n if (\n error.message.includes('not found') ||\n error.message.includes('Cannot find') ||\n error.message.includes('ENOENT')\n ) {\n // Use resolved path if available, otherwise use original configPath\n const displayPath = configPath ? resolve(process.cwd(), configPath) : undefined;\n throw errorConfigFileNotFound(displayPath, {\n why: error.message,\n });\n }\n // For other errors, wrap in unexpected error\n throw errorUnexpected(error.message, {\n why: `Failed to load config: ${error.message}`,\n });\n }\n throw errorUnexpected(String(error));\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;AAmBA,eAAsBA,aAAW,YAAgD;AAC/E,KAAI;EACF,MAAM,MAAM,QAAQ,KAAK;EAEzB,MAAM,qBAAqB,aAAa,QAAQ,KAAK,WAAW,GAAG;EACnE,MAAM,YAAY,qBAAqB,QAAQ,mBAAmB,GAAG;EAErE,MAAM,SAAS,MAAMC,WAAgC;GACnD,MAAM;GACN,GAAI,qBAAqB,EAAE,YAAY,oBAAoB,GAAG,EAAE;GAChE,KAAK;GACN,CAAC;AAIF,MAAI,sBAAsB,OAAO,eAAe,mBAC9C,OAAM,wBAAwB,mBAAmB;AAInD,MAAI,CAAC,OAAO,UAAU,OAAO,KAAK,OAAO,OAAO,CAAC,WAAW,EAG1D,OAAM,wBADc,OAAO,cAAc,sBAAsB,WACrB;AAI5C,iBAAe,OAAO,OAAO;AAE7B,SAAO,OAAO;UACP,OAAO;AACd,MAAI,iBAAiB,sBACnB,OAAM,sBAAsB,MAAM,OAAO,EACvC,KAAK,MAAM,KACZ,CAAC;AAIJ,MACE,iBAAiB,SACjB,UAAU,SACV,OAAQ,MAA2B,SAAS,SAE5C,OAAM;AAGR,MAAI,iBAAiB,OAAO;AAE1B,OACE,MAAM,QAAQ,SAAS,YAAY,IACnC,MAAM,QAAQ,SAAS,cAAc,IACrC,MAAM,QAAQ,SAAS,SAAS,CAIhC,OAAM,wBADc,aAAa,QAAQ,QAAQ,KAAK,EAAE,WAAW,GAAG,QAC3B,EACzC,KAAK,MAAM,SACZ,CAAC;AAGJ,SAAM,gBAAgB,MAAM,SAAS,EACnC,KAAK,0BAA0B,MAAM,WACtC,CAAC;;AAEJ,QAAM,gBAAgB,OAAO,MAAM,CAAC"}
@@ -1,4 +1,4 @@
1
- import { PrismaNextConfig } from "@prisma-next/core-control-plane/config-types";
1
+ import { PrismaNextConfig } from "@prisma-next/config/config-types";
2
2
 
3
3
  //#region src/config-loader.d.ts
4
4
 
@@ -1 +1 @@
1
- {"version":3,"file":"config-loader.d.mts","names":[],"sources":["../src/config-loader.ts"],"sourcesContent":[],"mappings":";;;;;;AAeA;;;;;;;iBAAsB,UAAA,uBAAiC,QAAQ"}
1
+ {"version":3,"file":"config-loader.d.mts","names":[],"sources":["../src/config-loader.ts"],"sourcesContent":[],"mappings":";;;;;;AAmBA;;;;;;;iBAAsB,UAAA,uBAAiC,QAAQ"}
@@ -1,3 +1,3 @@
1
- import { t as loadConfig } from "./config-loader-DqKf1qSa.mjs";
1
+ import { t as loadConfig } from "./config-loader-PPf4CtDj.mjs";
2
2
 
3
3
  export { loadConfig };
@@ -1,2 +1,2 @@
1
- import { ContractConfig, PrismaNextConfig, defineConfig } from "@prisma-next/core-control-plane/config-types";
1
+ import { ContractConfig, PrismaNextConfig, defineConfig } from "@prisma-next/config/config-types";
2
2
  export { type ContractConfig, type PrismaNextConfig, defineConfig };
@@ -1,3 +1,3 @@
1
- import { defineConfig } from "@prisma-next/core-control-plane/config-types";
1
+ import { defineConfig } from "@prisma-next/config/config-types";
2
2
 
3
3
  export { defineConfig };
@@ -1,6 +1,7 @@
1
1
  import { Result } from "@prisma-next/utils/result";
2
- import { ContractSourceDiagnostics, ContractSourceProvider } from "@prisma-next/core-control-plane/config-types";
2
+ import { ContractSourceDiagnostics, ContractSourceProvider } from "@prisma-next/config/config-types";
3
3
  import { ControlAdapterDescriptor, ControlDriverDescriptor, ControlExtensionDescriptor, ControlFamilyDescriptor, ControlPlaneStack, ControlTargetDescriptor, MigrationPlannerConflict, SignDatabaseResult, SignDatabaseResult as SignDatabaseResult$1, VerifyDatabaseResult, VerifyDatabaseResult as VerifyDatabaseResult$1, VerifyDatabaseSchemaResult, VerifyDatabaseSchemaResult as VerifyDatabaseSchemaResult$1 } from "@prisma-next/core-control-plane/types";
4
+ import { ContractMarkerRecord } from "@prisma-next/contract/types";
4
5
  import { CoreSchemaView } from "@prisma-next/core-control-plane/schema-view";
5
6
 
6
7
  //#region src/control-api/types.d.ts
@@ -33,7 +34,7 @@ interface ControlClientOptions {
33
34
  /**
34
35
  * Action names for control-api operations that can emit progress events.
35
36
  */
36
- type ControlActionName = 'dbInit' | 'dbUpdate' | 'verify' | 'schemaVerify' | 'sign' | 'introspect' | 'emit';
37
+ type ControlActionName = 'dbInit' | 'dbUpdate' | 'migrationApply' | 'verify' | 'schemaVerify' | 'sign' | 'introspect' | 'emit';
37
38
  /**
38
39
  * Progress event emitted during control-api operation execution.
39
40
  *
@@ -351,6 +352,84 @@ interface EmitFailure {
351
352
  * Uses Result pattern: success returns EmitSuccess, failure returns EmitFailure.
352
353
  */
353
354
  type EmitResult = Result<EmitSuccess, EmitFailure>;
355
+ /**
356
+ * A pre-planned migration step ready for execution.
357
+ * Contains the manifest metadata and the serialized operations from ops.json.
358
+ */
359
+ interface MigrationApplyStep {
360
+ readonly dirName: string;
361
+ readonly from: string;
362
+ readonly to: string;
363
+ readonly toContract: unknown;
364
+ readonly operations: ReadonlyArray<{
365
+ readonly id: string;
366
+ readonly label: string;
367
+ readonly operationClass: string;
368
+ readonly [key: string]: unknown;
369
+ }>;
370
+ }
371
+ /**
372
+ * Options for the migrationApply operation.
373
+ */
374
+ interface MigrationApplyOptions {
375
+ /**
376
+ * Hash of the database state this apply path starts from.
377
+ * This is resolved by the caller (typically the CLI orchestration layer).
378
+ */
379
+ readonly originHash: string;
380
+ /**
381
+ * Hash of the target contract this apply path must reach.
382
+ * This is resolved by the caller (typically the CLI orchestration layer).
383
+ */
384
+ readonly destinationHash: string;
385
+ /**
386
+ * Ordered list of migrations to execute from originHash to destinationHash.
387
+ * The execution layer does not choose defaults; it only executes this explicit path.
388
+ */
389
+ readonly pendingMigrations: readonly MigrationApplyStep[];
390
+ /**
391
+ * Database connection. If provided, migrationApply will connect before executing.
392
+ * If omitted, the client must already be connected.
393
+ */
394
+ readonly connection?: unknown;
395
+ /** Optional progress callback for observing operation progress */
396
+ readonly onProgress?: OnControlProgress;
397
+ }
398
+ /**
399
+ * Record of a successfully applied migration.
400
+ */
401
+ interface MigrationApplyAppliedEntry {
402
+ readonly dirName: string;
403
+ readonly from: string;
404
+ readonly to: string;
405
+ readonly operationsExecuted: number;
406
+ }
407
+ /**
408
+ * Successful migrationApply result.
409
+ */
410
+ interface MigrationApplySuccess {
411
+ readonly migrationsApplied: number;
412
+ readonly markerHash: string;
413
+ readonly applied: readonly MigrationApplyAppliedEntry[];
414
+ readonly summary: string;
415
+ }
416
+ /**
417
+ * Failure codes for migrationApply operation.
418
+ */
419
+ type MigrationApplyFailureCode = 'RUNNER_FAILED' | 'MIGRATION_PATH_NOT_FOUND';
420
+ /**
421
+ * Failure details for migrationApply operation.
422
+ */
423
+ interface MigrationApplyFailure {
424
+ readonly code: MigrationApplyFailureCode;
425
+ readonly summary: string;
426
+ readonly why: string | undefined;
427
+ readonly meta: Record<string, unknown> | undefined;
428
+ }
429
+ /**
430
+ * Result type for migrationApply operation.
431
+ */
432
+ type MigrationApplyResult = Result<MigrationApplySuccess, MigrationApplyFailure>;
354
433
  /**
355
434
  * Options for the standalone executeContractEmit function.
356
435
  * Used by tooling (e.g., Vite plugin) that needs to emit contracts
@@ -461,6 +540,25 @@ interface ControlClient {
461
540
  * @throws If not connected, target doesn't support migrations, or infrastructure failure
462
541
  */
463
542
  dbUpdate(options: DbUpdateOptions): Promise<DbUpdateResult>;
543
+ /**
544
+ * Reads the contract marker from the database.
545
+ * Returns null if no marker exists (fresh database).
546
+ *
547
+ * @throws If not connected or infrastructure failure
548
+ */
549
+ readMarker(): Promise<ContractMarkerRecord | null>;
550
+ /**
551
+ * Applies pre-planned on-disk migrations to the database.
552
+ * Each migration runs in its own transaction with full execution checks.
553
+ * Resume-safe: re-running after failure picks up from the last applied migration.
554
+ *
555
+ * @param options.originHash - Explicit source hash for the apply path
556
+ * @param options.destinationHash - Explicit destination hash for the apply path
557
+ * @param options.pendingMigrations - Ordered migrations to execute
558
+ * @returns Result pattern: Ok with applied details, NotOk with failure details
559
+ * @throws If not connected, target doesn't support migrations, or infrastructure failure
560
+ */
561
+ migrationApply(options: MigrationApplyOptions): Promise<MigrationApplyResult>;
464
562
  /**
465
563
  * Introspects the database schema.
466
564
  *
@@ -1 +1 @@
1
- {"version":3,"file":"control-api.d.mts","names":[],"sources":["../../src/control-api/types.ts","../../src/control-api/client.ts","../../src/control-api/operations/contract-emit.ts"],"sourcesContent":[],"mappings":"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"}
1
+ {"version":3,"file":"control-api.d.mts","names":[],"sources":["../../src/control-api/types.ts","../../src/control-api/client.ts","../../src/control-api/operations/contract-emit.ts"],"sourcesContent":[],"mappings":"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"}
@@ -1,5 +1,6 @@
1
- import { t as loadConfig } from "../config-loader-DqKf1qSa.mjs";
2
- import { h as errorRuntime, i as errorContractConfigMissing, t as createControlClient } from "../client-B7f4PZZ1.mjs";
1
+ import { t as loadConfig } from "../config-loader-PPf4CtDj.mjs";
2
+ import { m as errorRuntime, r as errorContractConfigMissing } from "../cli-errors-JlPTsazx.mjs";
3
+ import { t as createControlClient } from "../client-PimzSD1f.mjs";
3
4
  import { dirname, isAbsolute, join, resolve } from "pathe";
4
5
  import { createControlPlaneStack } from "@prisma-next/core-control-plane/stack";
5
6
  import { ifDefined } from "@prisma-next/utils/defined";
@@ -1 +1 @@
1
- {"version":3,"file":"control-api.mjs","names":["providerResult: Awaited<ReturnType<typeof contractConfig.source>>"],"sources":["../../src/control-api/operations/contract-emit.ts"],"sourcesContent":["import { mkdir, writeFile } from 'node:fs/promises';\nimport { createControlPlaneStack } from '@prisma-next/core-control-plane/stack';\nimport { abortable } from '@prisma-next/utils/abortable';\nimport { ifDefined } from '@prisma-next/utils/defined';\nimport { dirname, isAbsolute, join, resolve } from 'pathe';\nimport { loadConfig } from '../../config-loader';\nimport { errorContractConfigMissing, errorRuntime } from '../../utils/cli-errors';\nimport type { ContractEmitOptions, ContractEmitResult } from '../types';\n\ninterface ProviderFailureLike {\n readonly summary: string;\n readonly diagnostics: readonly unknown[];\n readonly meta?: unknown;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null;\n}\n\nfunction isAbortError(error: unknown): boolean {\n return isRecord(error) && typeof error['name'] === 'string' && error['name'] === 'AbortError';\n}\n\nfunction isProviderFailureLike(value: unknown): value is ProviderFailureLike {\n return (\n isRecord(value) && typeof value['summary'] === 'string' && Array.isArray(value['diagnostics'])\n );\n}\n\n/**\n * Executes the contract emit operation.\n *\n * This is an offline operation that:\n * 1. Loads the Prisma Next config from the specified path\n * 2. Resolves the contract source from config\n * 3. Creates a control plane stack and family instance\n * 4. Emits contract artifacts (JSON and DTS)\n * 5. Writes files to the paths specified in config\n *\n * Supports AbortSignal for cancellation, enabling \"last change wins\" behavior.\n *\n * @param options - Options including configPath and optional signal\n * @returns File paths and hashes of emitted artifacts\n * @throws If config loading fails, contract is invalid, or file I/O fails\n * @throws signal.reason if cancelled via AbortSignal (typically DOMException with name 'AbortError')\n */\nexport async function executeContractEmit(\n options: ContractEmitOptions,\n): Promise<ContractEmitResult> {\n const { configPath, signal = new AbortController().signal } = options;\n const unlessAborted = abortable(signal);\n\n // Load config using the existing config loader\n const config = await unlessAborted(loadConfig(configPath));\n\n // Validate contract config is present\n if (!config.contract) {\n throw errorContractConfigMissing({\n why: 'Config.contract is required for emit. Define it in your config: contract: { source: ..., output: ... }',\n });\n }\n\n const contractConfig = config.contract;\n\n // Validate output path is present and ends with .json\n if (!contractConfig.output) {\n throw errorContractConfigMissing({\n why: 'Contract config must have output path. This should not happen if defineConfig() was used.',\n });\n }\n if (!contractConfig.output.endsWith('.json')) {\n throw errorContractConfigMissing({\n why: 'Contract config output path must end with .json (e.g., \"src/prisma/contract.json\")',\n });\n }\n\n // Validate source exists and is callable\n if (typeof contractConfig.source !== 'function') {\n throw errorContractConfigMissing({\n why: 'Contract config must include a valid source provider function',\n });\n }\n\n // Normalize configPath and resolve artifact paths relative to config file directory\n const normalizedConfigPath = resolve(configPath);\n const configDir = dirname(normalizedConfigPath);\n const outputJsonPath = isAbsolute(contractConfig.output)\n ? contractConfig.output\n : join(configDir, contractConfig.output);\n // Colocate .d.ts with .json (contract.json → contract.d.ts)\n const outputDtsPath = `${outputJsonPath.slice(0, -5)}.d.ts`;\n\n let providerResult: Awaited<ReturnType<typeof contractConfig.source>>;\n try {\n providerResult = await unlessAborted(contractConfig.source());\n } catch (error) {\n if (signal.aborted || isAbortError(error)) {\n throw error;\n }\n throw errorRuntime('Failed to resolve contract source', {\n why: error instanceof Error ? error.message : String(error),\n fix: 'Ensure contract.source resolves to ok(contractIR) or returns structured diagnostics.',\n });\n }\n\n if (!isRecord(providerResult) || typeof providerResult.ok !== 'boolean') {\n throw errorRuntime('Failed to resolve contract source', {\n why: 'Contract source provider returned malformed result shape.',\n fix: 'Ensure contract.source resolves to ok(contractIR) or notOk({ summary, diagnostics }).',\n });\n }\n\n if (providerResult.ok && !('value' in providerResult)) {\n throw errorRuntime('Failed to resolve contract source', {\n why: 'Contract source provider returned malformed success result: missing value.',\n fix: 'Ensure contract.source success payload is ok(contractIR).',\n });\n }\n\n if (!providerResult.ok && !isProviderFailureLike(providerResult.failure)) {\n throw errorRuntime('Failed to resolve contract source', {\n why: 'Contract source provider returned malformed failure result: expected summary and diagnostics.',\n fix: 'Ensure contract.source failure payload is notOk({ summary, diagnostics, meta? }).',\n });\n }\n\n if (!providerResult.ok) {\n throw errorRuntime('Failed to resolve contract source', {\n why: providerResult.failure.summary,\n fix: 'Fix contract source diagnostics and return ok(contractIR).',\n meta: {\n diagnostics: providerResult.failure.diagnostics,\n ...ifDefined('providerMeta', providerResult.failure.meta),\n },\n });\n }\n\n // Create control plane stack from config\n const stack = createControlPlaneStack(config);\n const familyInstance = config.family.create(stack);\n\n // Emit contract via family instance\n const emitResult = await unlessAborted(\n familyInstance.emitContract({ contractIR: providerResult.value }),\n );\n\n // Create directory if needed and write files (both colocated)\n await unlessAborted(mkdir(dirname(outputJsonPath), { recursive: true }));\n await unlessAborted(writeFile(outputJsonPath, emitResult.contractJson, 'utf-8'));\n await unlessAborted(writeFile(outputDtsPath, emitResult.contractDts, 'utf-8'));\n\n return {\n storageHash: emitResult.storageHash,\n ...ifDefined('executionHash', emitResult.executionHash),\n profileHash: emitResult.profileHash,\n files: {\n json: outputJsonPath,\n dts: outputDtsPath,\n },\n };\n}\n"],"mappings":";;;;;;;;;AAeA,SAAS,SAAS,OAAkD;AAClE,QAAO,OAAO,UAAU,YAAY,UAAU;;AAGhD,SAAS,aAAa,OAAyB;AAC7C,QAAO,SAAS,MAAM,IAAI,OAAO,MAAM,YAAY,YAAY,MAAM,YAAY;;AAGnF,SAAS,sBAAsB,OAA8C;AAC3E,QACE,SAAS,MAAM,IAAI,OAAO,MAAM,eAAe,YAAY,MAAM,QAAQ,MAAM,eAAe;;;;;;;;;;;;;;;;;;;AAqBlG,eAAsB,oBACpB,SAC6B;CAC7B,MAAM,EAAE,YAAY,SAAS,IAAI,iBAAiB,CAAC,WAAW;CAC9D,MAAM,gBAAgB,UAAU,OAAO;CAGvC,MAAM,SAAS,MAAM,cAAc,WAAW,WAAW,CAAC;AAG1D,KAAI,CAAC,OAAO,SACV,OAAM,2BAA2B,EAC/B,KAAK,0GACN,CAAC;CAGJ,MAAM,iBAAiB,OAAO;AAG9B,KAAI,CAAC,eAAe,OAClB,OAAM,2BAA2B,EAC/B,KAAK,6FACN,CAAC;AAEJ,KAAI,CAAC,eAAe,OAAO,SAAS,QAAQ,CAC1C,OAAM,2BAA2B,EAC/B,KAAK,wFACN,CAAC;AAIJ,KAAI,OAAO,eAAe,WAAW,WACnC,OAAM,2BAA2B,EAC/B,KAAK,iEACN,CAAC;CAKJ,MAAM,YAAY,QADW,QAAQ,WAAW,CACD;CAC/C,MAAM,iBAAiB,WAAW,eAAe,OAAO,GACpD,eAAe,SACf,KAAK,WAAW,eAAe,OAAO;CAE1C,MAAM,gBAAgB,GAAG,eAAe,MAAM,GAAG,GAAG,CAAC;CAErD,IAAIA;AACJ,KAAI;AACF,mBAAiB,MAAM,cAAc,eAAe,QAAQ,CAAC;UACtD,OAAO;AACd,MAAI,OAAO,WAAW,aAAa,MAAM,CACvC,OAAM;AAER,QAAM,aAAa,qCAAqC;GACtD,KAAK,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;GAC3D,KAAK;GACN,CAAC;;AAGJ,KAAI,CAAC,SAAS,eAAe,IAAI,OAAO,eAAe,OAAO,UAC5D,OAAM,aAAa,qCAAqC;EACtD,KAAK;EACL,KAAK;EACN,CAAC;AAGJ,KAAI,eAAe,MAAM,EAAE,WAAW,gBACpC,OAAM,aAAa,qCAAqC;EACtD,KAAK;EACL,KAAK;EACN,CAAC;AAGJ,KAAI,CAAC,eAAe,MAAM,CAAC,sBAAsB,eAAe,QAAQ,CACtE,OAAM,aAAa,qCAAqC;EACtD,KAAK;EACL,KAAK;EACN,CAAC;AAGJ,KAAI,CAAC,eAAe,GAClB,OAAM,aAAa,qCAAqC;EACtD,KAAK,eAAe,QAAQ;EAC5B,KAAK;EACL,MAAM;GACJ,aAAa,eAAe,QAAQ;GACpC,GAAG,UAAU,gBAAgB,eAAe,QAAQ,KAAK;GAC1D;EACF,CAAC;CAIJ,MAAM,QAAQ,wBAAwB,OAAO;CAI7C,MAAM,aAAa,MAAM,cAHF,OAAO,OAAO,OAAO,MAAM,CAIjC,aAAa,EAAE,YAAY,eAAe,OAAO,CAAC,CAClE;AAGD,OAAM,cAAc,MAAM,QAAQ,eAAe,EAAE,EAAE,WAAW,MAAM,CAAC,CAAC;AACxE,OAAM,cAAc,UAAU,gBAAgB,WAAW,cAAc,QAAQ,CAAC;AAChF,OAAM,cAAc,UAAU,eAAe,WAAW,aAAa,QAAQ,CAAC;AAE9E,QAAO;EACL,aAAa,WAAW;EACxB,GAAG,UAAU,iBAAiB,WAAW,cAAc;EACvD,aAAa,WAAW;EACxB,OAAO;GACL,MAAM;GACN,KAAK;GACN;EACF"}
1
+ {"version":3,"file":"control-api.mjs","names":["providerResult: Awaited<ReturnType<typeof contractConfig.source>>"],"sources":["../../src/control-api/operations/contract-emit.ts"],"sourcesContent":["import { mkdir, writeFile } from 'node:fs/promises';\nimport { createControlPlaneStack } from '@prisma-next/core-control-plane/stack';\nimport { abortable } from '@prisma-next/utils/abortable';\nimport { ifDefined } from '@prisma-next/utils/defined';\nimport { dirname, isAbsolute, join, resolve } from 'pathe';\nimport { loadConfig } from '../../config-loader';\nimport { errorContractConfigMissing, errorRuntime } from '../../utils/cli-errors';\nimport type { ContractEmitOptions, ContractEmitResult } from '../types';\n\ninterface ProviderFailureLike {\n readonly summary: string;\n readonly diagnostics: readonly unknown[];\n readonly meta?: unknown;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null;\n}\n\nfunction isAbortError(error: unknown): boolean {\n return isRecord(error) && typeof error['name'] === 'string' && error['name'] === 'AbortError';\n}\n\nfunction isProviderFailureLike(value: unknown): value is ProviderFailureLike {\n return (\n isRecord(value) && typeof value['summary'] === 'string' && Array.isArray(value['diagnostics'])\n );\n}\n\n/**\n * Executes the contract emit operation.\n *\n * This is an offline operation that:\n * 1. Loads the Prisma Next config from the specified path\n * 2. Resolves the contract source from config\n * 3. Creates a control plane stack and family instance\n * 4. Emits contract artifacts (JSON and DTS)\n * 5. Writes files to the paths specified in config\n *\n * Supports AbortSignal for cancellation, enabling \"last change wins\" behavior.\n *\n * @param options - Options including configPath and optional signal\n * @returns File paths and hashes of emitted artifacts\n * @throws If config loading fails, contract is invalid, or file I/O fails\n * @throws signal.reason if cancelled via AbortSignal (typically DOMException with name 'AbortError')\n */\nexport async function executeContractEmit(\n options: ContractEmitOptions,\n): Promise<ContractEmitResult> {\n const { configPath, signal = new AbortController().signal } = options;\n const unlessAborted = abortable(signal);\n\n // Load config using the existing config loader\n const config = await unlessAborted(loadConfig(configPath));\n\n // Validate contract config is present\n if (!config.contract) {\n throw errorContractConfigMissing({\n why: 'Config.contract is required for emit. Define it in your config: contract: { source: ..., output: ... }',\n });\n }\n\n const contractConfig = config.contract;\n\n // Validate output path is present and ends with .json\n if (!contractConfig.output) {\n throw errorContractConfigMissing({\n why: 'Contract config must have output path. This should not happen if defineConfig() was used.',\n });\n }\n if (!contractConfig.output.endsWith('.json')) {\n throw errorContractConfigMissing({\n why: 'Contract config output path must end with .json (e.g., \"src/prisma/contract.json\")',\n });\n }\n\n // Validate source exists and is callable\n if (typeof contractConfig.source !== 'function') {\n throw errorContractConfigMissing({\n why: 'Contract config must include a valid source provider function',\n });\n }\n\n // Normalize configPath and resolve artifact paths relative to config file directory\n const normalizedConfigPath = resolve(configPath);\n const configDir = dirname(normalizedConfigPath);\n const outputJsonPath = isAbsolute(contractConfig.output)\n ? contractConfig.output\n : join(configDir, contractConfig.output);\n // Colocate .d.ts with .json (contract.json → contract.d.ts)\n const outputDtsPath = `${outputJsonPath.slice(0, -5)}.d.ts`;\n\n let providerResult: Awaited<ReturnType<typeof contractConfig.source>>;\n try {\n providerResult = await unlessAborted(contractConfig.source());\n } catch (error) {\n if (signal.aborted || isAbortError(error)) {\n throw error;\n }\n throw errorRuntime('Failed to resolve contract source', {\n why: error instanceof Error ? error.message : String(error),\n fix: 'Ensure contract.source resolves to ok(contractIR) or returns structured diagnostics.',\n });\n }\n\n if (!isRecord(providerResult) || typeof providerResult.ok !== 'boolean') {\n throw errorRuntime('Failed to resolve contract source', {\n why: 'Contract source provider returned malformed result shape.',\n fix: 'Ensure contract.source resolves to ok(contractIR) or notOk({ summary, diagnostics }).',\n });\n }\n\n if (providerResult.ok && !('value' in providerResult)) {\n throw errorRuntime('Failed to resolve contract source', {\n why: 'Contract source provider returned malformed success result: missing value.',\n fix: 'Ensure contract.source success payload is ok(contractIR).',\n });\n }\n\n if (!providerResult.ok && !isProviderFailureLike(providerResult.failure)) {\n throw errorRuntime('Failed to resolve contract source', {\n why: 'Contract source provider returned malformed failure result: expected summary and diagnostics.',\n fix: 'Ensure contract.source failure payload is notOk({ summary, diagnostics, meta? }).',\n });\n }\n\n if (!providerResult.ok) {\n throw errorRuntime('Failed to resolve contract source', {\n why: providerResult.failure.summary,\n fix: 'Fix contract source diagnostics and return ok(contractIR).',\n meta: {\n diagnostics: providerResult.failure.diagnostics,\n ...ifDefined('providerMeta', providerResult.failure.meta),\n },\n });\n }\n\n // Create control plane stack from config\n const stack = createControlPlaneStack(config);\n const familyInstance = config.family.create(stack);\n\n // Emit contract via family instance\n const emitResult = await unlessAborted(\n familyInstance.emitContract({ contractIR: providerResult.value }),\n );\n\n // Create directory if needed and write files (both colocated)\n await unlessAborted(mkdir(dirname(outputJsonPath), { recursive: true }));\n await unlessAborted(writeFile(outputJsonPath, emitResult.contractJson, 'utf-8'));\n await unlessAborted(writeFile(outputDtsPath, emitResult.contractDts, 'utf-8'));\n\n return {\n storageHash: emitResult.storageHash,\n ...ifDefined('executionHash', emitResult.executionHash),\n profileHash: emitResult.profileHash,\n files: {\n json: outputJsonPath,\n dts: outputDtsPath,\n },\n };\n}\n"],"mappings":";;;;;;;;;;AAeA,SAAS,SAAS,OAAkD;AAClE,QAAO,OAAO,UAAU,YAAY,UAAU;;AAGhD,SAAS,aAAa,OAAyB;AAC7C,QAAO,SAAS,MAAM,IAAI,OAAO,MAAM,YAAY,YAAY,MAAM,YAAY;;AAGnF,SAAS,sBAAsB,OAA8C;AAC3E,QACE,SAAS,MAAM,IAAI,OAAO,MAAM,eAAe,YAAY,MAAM,QAAQ,MAAM,eAAe;;;;;;;;;;;;;;;;;;;AAqBlG,eAAsB,oBACpB,SAC6B;CAC7B,MAAM,EAAE,YAAY,SAAS,IAAI,iBAAiB,CAAC,WAAW;CAC9D,MAAM,gBAAgB,UAAU,OAAO;CAGvC,MAAM,SAAS,MAAM,cAAc,WAAW,WAAW,CAAC;AAG1D,KAAI,CAAC,OAAO,SACV,OAAM,2BAA2B,EAC/B,KAAK,0GACN,CAAC;CAGJ,MAAM,iBAAiB,OAAO;AAG9B,KAAI,CAAC,eAAe,OAClB,OAAM,2BAA2B,EAC/B,KAAK,6FACN,CAAC;AAEJ,KAAI,CAAC,eAAe,OAAO,SAAS,QAAQ,CAC1C,OAAM,2BAA2B,EAC/B,KAAK,wFACN,CAAC;AAIJ,KAAI,OAAO,eAAe,WAAW,WACnC,OAAM,2BAA2B,EAC/B,KAAK,iEACN,CAAC;CAKJ,MAAM,YAAY,QADW,QAAQ,WAAW,CACD;CAC/C,MAAM,iBAAiB,WAAW,eAAe,OAAO,GACpD,eAAe,SACf,KAAK,WAAW,eAAe,OAAO;CAE1C,MAAM,gBAAgB,GAAG,eAAe,MAAM,GAAG,GAAG,CAAC;CAErD,IAAIA;AACJ,KAAI;AACF,mBAAiB,MAAM,cAAc,eAAe,QAAQ,CAAC;UACtD,OAAO;AACd,MAAI,OAAO,WAAW,aAAa,MAAM,CACvC,OAAM;AAER,QAAM,aAAa,qCAAqC;GACtD,KAAK,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;GAC3D,KAAK;GACN,CAAC;;AAGJ,KAAI,CAAC,SAAS,eAAe,IAAI,OAAO,eAAe,OAAO,UAC5D,OAAM,aAAa,qCAAqC;EACtD,KAAK;EACL,KAAK;EACN,CAAC;AAGJ,KAAI,eAAe,MAAM,EAAE,WAAW,gBACpC,OAAM,aAAa,qCAAqC;EACtD,KAAK;EACL,KAAK;EACN,CAAC;AAGJ,KAAI,CAAC,eAAe,MAAM,CAAC,sBAAsB,eAAe,QAAQ,CACtE,OAAM,aAAa,qCAAqC;EACtD,KAAK;EACL,KAAK;EACN,CAAC;AAGJ,KAAI,CAAC,eAAe,GAClB,OAAM,aAAa,qCAAqC;EACtD,KAAK,eAAe,QAAQ;EAC5B,KAAK;EACL,MAAM;GACJ,aAAa,eAAe,QAAQ;GACpC,GAAG,UAAU,gBAAgB,eAAe,QAAQ,KAAK;GAC1D;EACF,CAAC;CAIJ,MAAM,QAAQ,wBAAwB,OAAO;CAI7C,MAAM,aAAa,MAAM,cAHF,OAAO,OAAO,OAAO,MAAM,CAIjC,aAAa,EAAE,YAAY,eAAe,OAAO,CAAC,CAClE;AAGD,OAAM,cAAc,MAAM,QAAQ,eAAe,EAAE,EAAE,WAAW,MAAM,CAAC,CAAC;AACxE,OAAM,cAAc,UAAU,gBAAgB,WAAW,cAAc,QAAQ,CAAC;AAChF,OAAM,cAAc,UAAU,eAAe,WAAW,aAAa,QAAQ,CAAC;AAE9E,QAAO;EACL,aAAa,WAAW;EACxB,GAAG,UAAU,iBAAiB,WAAW,cAAc;EACvD,aAAa,WAAW;EACxB,OAAO;GACL,MAAM;GACN,KAAK;GACN;EACF"}
@@ -1,4 +1,4 @@
1
- import "../config-loader-DqKf1qSa.mjs";
1
+ import "../config-loader-PPf4CtDj.mjs";
2
2
  import { createContractEmitCommand } from "../commands/contract-emit.mjs";
3
3
  import { existsSync, unlinkSync, writeFileSync } from "node:fs";
4
4
  import { join } from "node:path";
@@ -0,0 +1,26 @@
1
+ //#region src/control-api/operations/extract-sql-ddl.ts
2
+ function isDdlStatement(sqlStatement) {
3
+ const trimmed = sqlStatement.trim().toLowerCase();
4
+ return trimmed.startsWith("create ") || trimmed.startsWith("alter ") || trimmed.startsWith("drop ");
5
+ }
6
+ function hasExecuteSteps(operation) {
7
+ const candidate = operation;
8
+ if (!("execute" in candidate) || !Array.isArray(candidate["execute"])) return false;
9
+ return candidate["execute"].every((step) => typeof step === "object" && step !== null && "sql" in step);
10
+ }
11
+ /**
12
+ * Extracts a best-effort SQL DDL preview for CLI plan output.
13
+ * This helper is presentation-only and is never used to decide migration correctness.
14
+ */
15
+ function extractSqlDdl(operations) {
16
+ const statements = [];
17
+ for (const operation of operations) {
18
+ if (!hasExecuteSteps(operation)) continue;
19
+ for (const step of operation.execute) if (typeof step.sql === "string" && isDdlStatement(step.sql)) statements.push(step.sql.trim());
20
+ }
21
+ return statements;
22
+ }
23
+
24
+ //#endregion
25
+ export { extractSqlDdl as t };
26
+ //# sourceMappingURL=extract-sql-ddl-BmlKvk4o.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extract-sql-ddl-BmlKvk4o.mjs","names":["statements: string[]"],"sources":["../src/control-api/operations/extract-sql-ddl.ts"],"sourcesContent":["import type { MigrationPlanOperation } from '@prisma-next/core-control-plane/types';\n\n/**\n * Shape of an SQL execute step on SqlMigrationPlanOperation.\n * Used for runtime type narrowing without importing the concrete SQL type.\n */\ninterface SqlExecuteStep {\n readonly sql: string;\n}\n\nfunction isDdlStatement(sqlStatement: string): boolean {\n const trimmed = sqlStatement.trim().toLowerCase();\n return (\n trimmed.startsWith('create ') || trimmed.startsWith('alter ') || trimmed.startsWith('drop ')\n );\n}\n\nfunction hasExecuteSteps(\n operation: MigrationPlanOperation,\n): operation is MigrationPlanOperation & { readonly execute: readonly SqlExecuteStep[] } {\n const candidate = operation as unknown as Record<string, unknown>;\n if (!('execute' in candidate) || !Array.isArray(candidate['execute'])) {\n return false;\n }\n return candidate['execute'].every(\n (step: unknown) => typeof step === 'object' && step !== null && 'sql' in step,\n );\n}\n\n/**\n * Extracts a best-effort SQL DDL preview for CLI plan output.\n * This helper is presentation-only and is never used to decide migration correctness.\n */\nexport function extractSqlDdl(operations: readonly MigrationPlanOperation[]): string[] {\n const statements: string[] = [];\n for (const operation of operations) {\n if (!hasExecuteSteps(operation)) {\n continue;\n }\n for (const step of operation.execute) {\n if (typeof step.sql === 'string' && isDdlStatement(step.sql)) {\n statements.push(step.sql.trim());\n }\n }\n }\n return statements;\n}\n"],"mappings":";AAUA,SAAS,eAAe,cAA+B;CACrD,MAAM,UAAU,aAAa,MAAM,CAAC,aAAa;AACjD,QACE,QAAQ,WAAW,UAAU,IAAI,QAAQ,WAAW,SAAS,IAAI,QAAQ,WAAW,QAAQ;;AAIhG,SAAS,gBACP,WACuF;CACvF,MAAM,YAAY;AAClB,KAAI,EAAE,aAAa,cAAc,CAAC,MAAM,QAAQ,UAAU,WAAW,CACnE,QAAO;AAET,QAAO,UAAU,WAAW,OACzB,SAAkB,OAAO,SAAS,YAAY,SAAS,QAAQ,SAAS,KAC1E;;;;;;AAOH,SAAgB,cAAc,YAAyD;CACrF,MAAMA,aAAuB,EAAE;AAC/B,MAAK,MAAM,aAAa,YAAY;AAClC,MAAI,CAAC,gBAAgB,UAAU,CAC7B;AAEF,OAAK,MAAM,QAAQ,UAAU,QAC3B,KAAI,OAAO,KAAK,QAAQ,YAAY,eAAe,KAAK,IAAI,CAC1D,YAAW,KAAK,KAAK,IAAI,MAAM,CAAC;;AAItC,QAAO"}
@@ -0,0 +1,59 @@
1
+ import { n as errorConfigValidation } from "./cli-errors-JlPTsazx.mjs";
2
+ import "@prisma-next/contract/framework-components";
3
+
4
+ //#region src/utils/framework-components.ts
5
+ /**
6
+ * Asserts that all framework components are compatible with the expected family and target.
7
+ *
8
+ * This function validates that each component in the framework components array:
9
+ * - Has kind 'target', 'adapter', 'extension', or 'driver'
10
+ * - Has familyId matching expectedFamilyId
11
+ * - Has targetId matching expectedTargetId
12
+ *
13
+ * This validation happens at the CLI composition boundary, before passing components
14
+ * to typed planner/runner instances. It fills the gap between runtime validation
15
+ * (via `validateConfig()`) and compile-time type enforcement.
16
+ *
17
+ * @param expectedFamilyId - The expected family ID (e.g., 'sql')
18
+ * @param expectedTargetId - The expected target ID (e.g., 'postgres')
19
+ * @param frameworkComponents - Array of framework components to validate
20
+ * @returns The same array typed as TargetBoundComponentDescriptor
21
+ * @throws CliStructuredError if any component is incompatible
22
+ *
23
+ * @example
24
+ * ```ts
25
+ * const config = await loadConfig();
26
+ * const frameworkComponents = [config.target, config.adapter, ...(config.extensionPacks ?? [])];
27
+ *
28
+ * // Validate and type-narrow components before passing to planner
29
+ * const typedComponents = assertFrameworkComponentsCompatible(
30
+ * config.family.familyId,
31
+ * config.target.targetId,
32
+ * frameworkComponents
33
+ * );
34
+ *
35
+ * const planner = target.migrations.createPlanner(familyInstance);
36
+ * planner.plan({ contract, schema, policy, frameworkComponents: typedComponents });
37
+ * ```
38
+ */
39
+ function assertFrameworkComponentsCompatible(expectedFamilyId, expectedTargetId, frameworkComponents) {
40
+ for (let i = 0; i < frameworkComponents.length; i++) {
41
+ const component = frameworkComponents[i];
42
+ if (typeof component !== "object" || component === null) throw errorConfigValidation("frameworkComponents[]", { why: `Framework component at index ${i} must be an object` });
43
+ const record = component;
44
+ if (!Object.hasOwn(record, "kind")) throw errorConfigValidation("frameworkComponents[].kind", { why: `Framework component at index ${i} must have 'kind' property` });
45
+ const kind = record["kind"];
46
+ if (kind !== "target" && kind !== "adapter" && kind !== "extension" && kind !== "driver") throw errorConfigValidation("frameworkComponents[].kind", { why: `Framework component at index ${i} has invalid kind '${String(kind)}' (must be 'target', 'adapter', 'extension', or 'driver')` });
47
+ if (!Object.hasOwn(record, "familyId")) throw errorConfigValidation("frameworkComponents[].familyId", { why: `Framework component at index ${i} (kind: ${String(kind)}) must have 'familyId' property` });
48
+ const familyId = record["familyId"];
49
+ if (familyId !== expectedFamilyId) throw errorConfigValidation("frameworkComponents[].familyId", { why: `Framework component at index ${i} (kind: ${String(kind)}) has familyId '${String(familyId)}' but expected '${expectedFamilyId}'` });
50
+ if (!Object.hasOwn(record, "targetId")) throw errorConfigValidation("frameworkComponents[].targetId", { why: `Framework component at index ${i} (kind: ${String(kind)}) must have 'targetId' property` });
51
+ const targetId = record["targetId"];
52
+ if (targetId !== expectedTargetId) throw errorConfigValidation("frameworkComponents[].targetId", { why: `Framework component at index ${i} (kind: ${String(kind)}) has targetId '${String(targetId)}' but expected '${expectedTargetId}'` });
53
+ }
54
+ return frameworkComponents;
55
+ }
56
+
57
+ //#endregion
58
+ export { assertFrameworkComponentsCompatible as t };
59
+ //# sourceMappingURL=framework-components-CjV_jD8f.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"framework-components-CjV_jD8f.mjs","names":[],"sources":["../src/utils/framework-components.ts"],"sourcesContent":["import {\n checkContractComponentRequirements,\n type TargetBoundComponentDescriptor,\n} from '@prisma-next/contract/framework-components';\nimport type { ContractIR } from '@prisma-next/contract/ir';\nimport type { ControlPlaneStack } from '@prisma-next/core-control-plane/types';\nimport { errorConfigValidation, errorContractMissingExtensionPacks } from './cli-errors';\n\n/**\n * Asserts that all framework components are compatible with the expected family and target.\n *\n * This function validates that each component in the framework components array:\n * - Has kind 'target', 'adapter', 'extension', or 'driver'\n * - Has familyId matching expectedFamilyId\n * - Has targetId matching expectedTargetId\n *\n * This validation happens at the CLI composition boundary, before passing components\n * to typed planner/runner instances. It fills the gap between runtime validation\n * (via `validateConfig()`) and compile-time type enforcement.\n *\n * @param expectedFamilyId - The expected family ID (e.g., 'sql')\n * @param expectedTargetId - The expected target ID (e.g., 'postgres')\n * @param frameworkComponents - Array of framework components to validate\n * @returns The same array typed as TargetBoundComponentDescriptor\n * @throws CliStructuredError if any component is incompatible\n *\n * @example\n * ```ts\n * const config = await loadConfig();\n * const frameworkComponents = [config.target, config.adapter, ...(config.extensionPacks ?? [])];\n *\n * // Validate and type-narrow components before passing to planner\n * const typedComponents = assertFrameworkComponentsCompatible(\n * config.family.familyId,\n * config.target.targetId,\n * frameworkComponents\n * );\n *\n * const planner = target.migrations.createPlanner(familyInstance);\n * planner.plan({ contract, schema, policy, frameworkComponents: typedComponents });\n * ```\n */\nexport function assertFrameworkComponentsCompatible<\n TFamilyId extends string,\n TTargetId extends string,\n>(\n expectedFamilyId: TFamilyId,\n expectedTargetId: TTargetId,\n frameworkComponents: ReadonlyArray<unknown>,\n): ReadonlyArray<TargetBoundComponentDescriptor<TFamilyId, TTargetId>> {\n for (let i = 0; i < frameworkComponents.length; i++) {\n const component = frameworkComponents[i];\n\n // Check that component is an object\n if (typeof component !== 'object' || component === null) {\n throw errorConfigValidation('frameworkComponents[]', {\n why: `Framework component at index ${i} must be an object`,\n });\n }\n\n const record = component as Record<string, unknown>;\n\n // Check kind\n if (!Object.hasOwn(record, 'kind')) {\n throw errorConfigValidation('frameworkComponents[].kind', {\n why: `Framework component at index ${i} must have 'kind' property`,\n });\n }\n\n const kind = record['kind'];\n if (kind !== 'target' && kind !== 'adapter' && kind !== 'extension' && kind !== 'driver') {\n throw errorConfigValidation('frameworkComponents[].kind', {\n why: `Framework component at index ${i} has invalid kind '${String(kind)}' (must be 'target', 'adapter', 'extension', or 'driver')`,\n });\n }\n\n // Check familyId\n if (!Object.hasOwn(record, 'familyId')) {\n throw errorConfigValidation('frameworkComponents[].familyId', {\n why: `Framework component at index ${i} (kind: ${String(kind)}) must have 'familyId' property`,\n });\n }\n\n const familyId = record['familyId'];\n if (familyId !== expectedFamilyId) {\n throw errorConfigValidation('frameworkComponents[].familyId', {\n why: `Framework component at index ${i} (kind: ${String(kind)}) has familyId '${String(familyId)}' but expected '${expectedFamilyId}'`,\n });\n }\n\n // Check targetId\n if (!Object.hasOwn(record, 'targetId')) {\n throw errorConfigValidation('frameworkComponents[].targetId', {\n why: `Framework component at index ${i} (kind: ${String(kind)}) must have 'targetId' property`,\n });\n }\n\n const targetId = record['targetId'];\n if (targetId !== expectedTargetId) {\n throw errorConfigValidation('frameworkComponents[].targetId', {\n why: `Framework component at index ${i} (kind: ${String(kind)}) has targetId '${String(targetId)}' but expected '${expectedTargetId}'`,\n });\n }\n }\n\n // Type assertion is safe because we've validated all components above\n return frameworkComponents as ReadonlyArray<TargetBoundComponentDescriptor<TFamilyId, TTargetId>>;\n}\n\n/**\n * Validates that a contract is compatible with the configured target, adapter,\n * and extension packs. Throws on family/target mismatches or missing extension packs.\n *\n * This check ensures the emitted contract matches the CLI config before running\n * commands that depend on the contract (e.g., db verify, db sign).\n *\n * @param contract - The contract IR to validate (must include targetFamily, target, extensionPacks).\n * @param stack - The control plane stack (target, adapter, driver, extensionPacks).\n *\n * @throws {CliStructuredError} errorConfigValidation when contract.targetFamily or contract.target\n * doesn't match the configured family/target.\n * @throws {CliStructuredError} errorContractMissingExtensionPacks when the contract requires\n * extension packs that are not provided in the config (includes all missing packs in error.meta).\n *\n * @example\n * ```ts\n * import { assertContractRequirementsSatisfied } from './framework-components';\n *\n * const config = await loadConfig();\n * const contractIR = await loadContractJson(config.contract.output);\n * const stack = createControlPlaneStack({ target: config.target, adapter: config.adapter, ... });\n *\n * // Throws if contract is incompatible with config\n * assertContractRequirementsSatisfied({ contract: contractIR, stack });\n * ```\n */\nexport function assertContractRequirementsSatisfied<\n TFamilyId extends string,\n TTargetId extends string,\n>({\n contract,\n stack,\n}: {\n readonly contract: Pick<ContractIR, 'targetFamily' | 'target' | 'extensionPacks'>;\n readonly stack: ControlPlaneStack<TFamilyId, TTargetId>;\n}): void {\n const providedComponentIds = new Set<string>([stack.target.id, stack.adapter.id]);\n for (const extension of stack.extensionPacks) {\n providedComponentIds.add(extension.id);\n }\n\n const result = checkContractComponentRequirements({\n contract,\n expectedTargetFamily: stack.target.familyId,\n expectedTargetId: stack.target.targetId,\n providedComponentIds,\n });\n\n if (result.familyMismatch) {\n throw errorConfigValidation('contract.targetFamily', {\n why: `Contract was emitted for family '${result.familyMismatch.actual}' but CLI config is wired to '${result.familyMismatch.expected}'.`,\n });\n }\n\n if (result.targetMismatch) {\n throw errorConfigValidation('contract.target', {\n why: `Contract target '${result.targetMismatch.actual}' does not match CLI target '${result.targetMismatch.expected}'.`,\n });\n }\n\n if (result.missingExtensionPackIds.length > 0) {\n throw errorContractMissingExtensionPacks({\n missingExtensionPacks: result.missingExtensionPackIds,\n providedComponentIds: [...providedComponentIds],\n });\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0CA,SAAgB,oCAId,kBACA,kBACA,qBACqE;AACrE,MAAK,IAAI,IAAI,GAAG,IAAI,oBAAoB,QAAQ,KAAK;EACnD,MAAM,YAAY,oBAAoB;AAGtC,MAAI,OAAO,cAAc,YAAY,cAAc,KACjD,OAAM,sBAAsB,yBAAyB,EACnD,KAAK,gCAAgC,EAAE,qBACxC,CAAC;EAGJ,MAAM,SAAS;AAGf,MAAI,CAAC,OAAO,OAAO,QAAQ,OAAO,CAChC,OAAM,sBAAsB,8BAA8B,EACxD,KAAK,gCAAgC,EAAE,6BACxC,CAAC;EAGJ,MAAM,OAAO,OAAO;AACpB,MAAI,SAAS,YAAY,SAAS,aAAa,SAAS,eAAe,SAAS,SAC9E,OAAM,sBAAsB,8BAA8B,EACxD,KAAK,gCAAgC,EAAE,qBAAqB,OAAO,KAAK,CAAC,4DAC1E,CAAC;AAIJ,MAAI,CAAC,OAAO,OAAO,QAAQ,WAAW,CACpC,OAAM,sBAAsB,kCAAkC,EAC5D,KAAK,gCAAgC,EAAE,UAAU,OAAO,KAAK,CAAC,kCAC/D,CAAC;EAGJ,MAAM,WAAW,OAAO;AACxB,MAAI,aAAa,iBACf,OAAM,sBAAsB,kCAAkC,EAC5D,KAAK,gCAAgC,EAAE,UAAU,OAAO,KAAK,CAAC,kBAAkB,OAAO,SAAS,CAAC,kBAAkB,iBAAiB,IACrI,CAAC;AAIJ,MAAI,CAAC,OAAO,OAAO,QAAQ,WAAW,CACpC,OAAM,sBAAsB,kCAAkC,EAC5D,KAAK,gCAAgC,EAAE,UAAU,OAAO,KAAK,CAAC,kCAC/D,CAAC;EAGJ,MAAM,WAAW,OAAO;AACxB,MAAI,aAAa,iBACf,OAAM,sBAAsB,kCAAkC,EAC5D,KAAK,gCAAgC,EAAE,UAAU,OAAO,KAAK,CAAC,kBAAkB,OAAO,SAAS,CAAC,kBAAkB,iBAAiB,IACrI,CAAC;;AAKN,QAAO"}
@@ -1,6 +1,8 @@
1
- import { t as loadConfig } from "./config-loader-DqKf1qSa.mjs";
2
- import { a as errorContractValidationFailed, c as errorDriverRequired, g as errorTargetMigrationNotSupported, l as errorFileNotFound, o as errorDatabaseConnectionRequired, t as createControlClient, v as errorUnexpected } from "./client-B7f4PZZ1.mjs";
3
- import { g as formatStyledHeader, n as createProgressAdapter, x as maskConnectionUrl } from "./result-handler-BhmrXIvT.mjs";
1
+ import { t as loadConfig } from "./config-loader-PPf4CtDj.mjs";
2
+ import { _ as errorUnexpected, a as errorDatabaseConnectionRequired, c as errorFileNotFound, h as errorTargetMigrationNotSupported, i as errorContractValidationFailed, s as errorDriverRequired } from "./cli-errors-JlPTsazx.mjs";
3
+ import { t as createControlClient } from "./client-PimzSD1f.mjs";
4
+ import { T as resolveContractPath, w as maskConnectionUrl, y as formatStyledHeader } from "./result-handler-iA9JtUC7.mjs";
5
+ import { t as createProgressAdapter } from "./progress-adapter-DENrzF6I.mjs";
4
6
  import { notOk, ok } from "@prisma-next/utils/result";
5
7
  import { relative, resolve } from "node:path";
6
8
  import { readFile } from "node:fs/promises";
@@ -17,7 +19,7 @@ import { readFile } from "node:fs/promises";
17
19
  async function prepareMigrationContext(options, flags, descriptor) {
18
20
  const config = await loadConfig(options.config);
19
21
  const configPath = options.config ? relative(process.cwd(), resolve(options.config)) : "prisma-next.config.ts";
20
- const contractPathAbsolute = config.contract?.output ? resolve(config.contract.output) : resolve("src/prisma/contract.json");
22
+ const contractPathAbsolute = resolveContractPath(config);
21
23
  const contractPath = relative(process.cwd(), contractPathAbsolute);
22
24
  if (flags.json !== "object" && !flags.quiet) {
23
25
  const details = [{
@@ -92,4 +94,4 @@ function addMigrationCommandOptions(command) {
92
94
 
93
95
  //#endregion
94
96
  export { prepareMigrationContext as n, addMigrationCommandOptions as t };
95
- //# sourceMappingURL=migration-command-scaffold-BELw_do2.mjs.map
97
+ //# sourceMappingURL=migration-command-scaffold-DfY_F3ev.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migration-command-scaffold-DfY_F3ev.mjs","names":["details: Array<{ label: string; value: string }>","contractJsonContent: string","contractJson: Record<string, unknown>"],"sources":["../src/utils/migration-command-scaffold.ts"],"sourcesContent":["import { readFile } from 'node:fs/promises';\nimport { relative, resolve } from 'node:path';\nimport { notOk, ok, type Result } from '@prisma-next/utils/result';\nimport type { Command } from 'commander';\nimport { loadConfig } from '../config-loader';\nimport { createControlClient } from '../control-api/client';\nimport type { ControlClient } from '../control-api/types';\nimport {\n type CliStructuredError,\n errorContractValidationFailed,\n errorDatabaseConnectionRequired,\n errorDriverRequired,\n errorFileNotFound,\n errorTargetMigrationNotSupported,\n errorUnexpected,\n} from './cli-errors';\nimport { maskConnectionUrl, resolveContractPath } from './command-helpers';\nimport type { GlobalFlags } from './global-flags';\nimport { formatStyledHeader } from './output';\nimport { createProgressAdapter } from './progress-adapter';\n\n/**\n * Resolved context for a migration command.\n * Contains everything needed to invoke a control-api operation.\n */\nexport interface MigrationContext {\n readonly client: ControlClient;\n readonly contractJson: Record<string, unknown>;\n readonly dbConnection: unknown;\n readonly onProgress: ReturnType<typeof createProgressAdapter>;\n readonly configPath: string;\n readonly contractPath: string;\n readonly contractPathAbsolute: string;\n readonly config: Awaited<ReturnType<typeof loadConfig>>;\n}\n\n/**\n * Command-specific configuration for the shared scaffold.\n */\nexport interface MigrationCommandDescriptor {\n readonly commandName: string;\n readonly description: string;\n readonly url: string;\n}\n\n/**\n * Prepares the shared context for migration commands (db init, db update).\n *\n * Handles: config loading, contract file reading, JSON parsing, connection resolution,\n * driver/migration-support validation, client creation, and header output.\n *\n * Returns a Result with either the resolved context or a structured error.\n */\nexport async function prepareMigrationContext(\n options: { readonly db?: string; readonly config?: string; readonly plan?: boolean },\n flags: GlobalFlags,\n descriptor: MigrationCommandDescriptor,\n): Promise<Result<MigrationContext, CliStructuredError>> {\n // Load config\n const config = await loadConfig(options.config);\n const configPath = options.config\n ? relative(process.cwd(), resolve(options.config))\n : 'prisma-next.config.ts';\n const contractPathAbsolute = resolveContractPath(config);\n const contractPath = relative(process.cwd(), contractPathAbsolute);\n\n // Output header\n if (flags.json !== 'object' && !flags.quiet) {\n const details: Array<{ label: string; value: string }> = [\n { label: 'config', value: configPath },\n { label: 'contract', value: contractPath },\n ];\n if (options.db) {\n details.push({ label: 'database', value: maskConnectionUrl(options.db) });\n }\n if (options.plan) {\n details.push({ label: 'mode', value: 'plan (dry run)' });\n }\n const header = formatStyledHeader({\n command: descriptor.commandName,\n description: descriptor.description,\n url: descriptor.url,\n details,\n flags,\n });\n console.log(header);\n }\n\n // Load contract file\n let contractJsonContent: string;\n try {\n contractJsonContent = await readFile(contractPathAbsolute, 'utf-8');\n } catch (error) {\n if (error instanceof Error && (error as { code?: string }).code === 'ENOENT') {\n return notOk(\n errorFileNotFound(contractPathAbsolute, {\n why: `Contract file not found at ${contractPathAbsolute}`,\n fix: `Run \\`prisma-next contract emit\\` to generate ${contractPath}, or update \\`config.contract.output\\` in ${configPath}`,\n }),\n );\n }\n return notOk(\n errorUnexpected(error instanceof Error ? error.message : String(error), {\n why: `Failed to read contract file: ${error instanceof Error ? error.message : String(error)}`,\n }),\n );\n }\n\n // Parse contract JSON\n let contractJson: Record<string, unknown>;\n try {\n contractJson = JSON.parse(contractJsonContent) as Record<string, unknown>;\n } catch (error) {\n return notOk(\n errorContractValidationFailed(\n `Contract JSON is invalid: ${error instanceof Error ? error.message : String(error)}`,\n { where: { path: contractPathAbsolute } },\n ),\n );\n }\n\n // Resolve database connection (--db flag or config.db.connection)\n const dbConnection = options.db ?? config.db?.connection;\n if (!dbConnection) {\n return notOk(\n errorDatabaseConnectionRequired({\n why: `Database connection is required for ${descriptor.commandName} (set db.connection in ${configPath}, or pass --db <url>)`,\n }),\n );\n }\n\n // Check for driver\n if (!config.driver) {\n return notOk(\n errorDriverRequired({ why: `Config.driver is required for ${descriptor.commandName}` }),\n );\n }\n\n // Check target supports migrations via optional descriptor capability\n const targetWithMigrations = config.target as typeof config.target & {\n readonly migrations?: unknown;\n };\n if (!targetWithMigrations.migrations) {\n return notOk(\n errorTargetMigrationNotSupported({\n why: `Target \"${config.target.id}\" does not support migrations`,\n }),\n );\n }\n\n // Create control client\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 // Create progress adapter\n const onProgress = createProgressAdapter({ flags });\n\n return ok({\n client,\n contractJson,\n dbConnection,\n onProgress,\n configPath,\n contractPath,\n contractPathAbsolute,\n config,\n });\n}\n\n/**\n * Registers the shared CLI options for migration commands (db init, db update).\n */\nexport function addMigrationCommandOptions(command: Command): Command {\n return command\n .option('--db <url>', 'Database connection string')\n .option('--config <path>', 'Path to prisma-next.config.ts')\n .option('--plan', 'Preview planned operations without applying', false)\n .option('--json [format]', 'Output as JSON (object)', false)\n .option('-q, --quiet', 'Quiet mode: errors only')\n .option('-v, --verbose', 'Verbose output: debug info, timings')\n .option('-vv, --trace', 'Trace output: deep internals, stack traces')\n .option('--timestamps', 'Add timestamps to output')\n .option('--color', 'Force color output')\n .option('--no-color', 'Disable color output');\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAqDA,eAAsB,wBACpB,SACA,OACA,YACuD;CAEvD,MAAM,SAAS,MAAM,WAAW,QAAQ,OAAO;CAC/C,MAAM,aAAa,QAAQ,SACvB,SAAS,QAAQ,KAAK,EAAE,QAAQ,QAAQ,OAAO,CAAC,GAChD;CACJ,MAAM,uBAAuB,oBAAoB,OAAO;CACxD,MAAM,eAAe,SAAS,QAAQ,KAAK,EAAE,qBAAqB;AAGlE,KAAI,MAAM,SAAS,YAAY,CAAC,MAAM,OAAO;EAC3C,MAAMA,UAAmD,CACvD;GAAE,OAAO;GAAU,OAAO;GAAY,EACtC;GAAE,OAAO;GAAY,OAAO;GAAc,CAC3C;AACD,MAAI,QAAQ,GACV,SAAQ,KAAK;GAAE,OAAO;GAAY,OAAO,kBAAkB,QAAQ,GAAG;GAAE,CAAC;AAE3E,MAAI,QAAQ,KACV,SAAQ,KAAK;GAAE,OAAO;GAAQ,OAAO;GAAkB,CAAC;EAE1D,MAAM,SAAS,mBAAmB;GAChC,SAAS,WAAW;GACpB,aAAa,WAAW;GACxB,KAAK,WAAW;GAChB;GACA;GACD,CAAC;AACF,UAAQ,IAAI,OAAO;;CAIrB,IAAIC;AACJ,KAAI;AACF,wBAAsB,MAAM,SAAS,sBAAsB,QAAQ;UAC5D,OAAO;AACd,MAAI,iBAAiB,SAAU,MAA4B,SAAS,SAClE,QAAO,MACL,kBAAkB,sBAAsB;GACtC,KAAK,8BAA8B;GACnC,KAAK,iDAAiD,aAAa,4CAA4C;GAChH,CAAC,CACH;AAEH,SAAO,MACL,gBAAgB,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM,EAAE,EACtE,KAAK,iCAAiC,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM,IAC7F,CAAC,CACH;;CAIH,IAAIC;AACJ,KAAI;AACF,iBAAe,KAAK,MAAM,oBAAoB;UACvC,OAAO;AACd,SAAO,MACL,8BACE,6BAA6B,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM,IACnF,EAAE,OAAO,EAAE,MAAM,sBAAsB,EAAE,CAC1C,CACF;;CAIH,MAAM,eAAe,QAAQ,MAAM,OAAO,IAAI;AAC9C,KAAI,CAAC,aACH,QAAO,MACL,gCAAgC,EAC9B,KAAK,uCAAuC,WAAW,YAAY,yBAAyB,WAAW,wBACxG,CAAC,CACH;AAIH,KAAI,CAAC,OAAO,OACV,QAAO,MACL,oBAAoB,EAAE,KAAK,iCAAiC,WAAW,eAAe,CAAC,CACxF;AAOH,KAAI,CAHyB,OAAO,OAGV,WACxB,QAAO,MACL,iCAAiC,EAC/B,KAAK,WAAW,OAAO,OAAO,GAAG,gCAClC,CAAC,CACH;CAIH,MAAM,SAAS,oBAAoB;EACjC,QAAQ,OAAO;EACf,QAAQ,OAAO;EACf,SAAS,OAAO;EAChB,QAAQ,OAAO;EACf,gBAAgB,OAAO,kBAAkB,EAAE;EAC5C,CAAC;CAGF,MAAM,aAAa,sBAAsB,EAAE,OAAO,CAAC;AAEnD,QAAO,GAAG;EACR;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACD,CAAC;;;;;AAMJ,SAAgB,2BAA2B,SAA2B;AACpE,QAAO,QACJ,OAAO,cAAc,6BAA6B,CAClD,OAAO,mBAAmB,gCAAgC,CAC1D,OAAO,UAAU,+CAA+C,MAAM,CACtE,OAAO,mBAAmB,2BAA2B,MAAM,CAC3D,OAAO,eAAe,0BAA0B,CAChD,OAAO,iBAAiB,sCAAsC,CAC9D,OAAO,gBAAgB,6CAA6C,CACpE,OAAO,gBAAgB,2BAA2B,CAClD,OAAO,WAAW,qBAAqB,CACvC,OAAO,cAAc,uBAAuB"}
@@ -0,0 +1,49 @@
1
+ import ora from "ora";
2
+
3
+ //#region src/utils/progress-adapter.ts
4
+ /**
5
+ * Creates a progress adapter that converts control-api progress events
6
+ * into CLI spinner/progress output.
7
+ *
8
+ * The adapter:
9
+ * - Starts/succeeds spinners for top-level span boundaries
10
+ * - Prints per-operation lines for nested spans (e.g., migration operations under 'apply')
11
+ * - Respects quiet/json/non-TTY flags (no-op in those cases)
12
+ *
13
+ * @param options - Progress adapter configuration
14
+ * @returns An onProgress callback compatible with control-api operations
15
+ */
16
+ function createProgressAdapter(options) {
17
+ const { flags } = options;
18
+ if (!(!flags.quiet && flags.json !== "object" && process.stdout.isTTY)) return () => {};
19
+ const activeSpans = /* @__PURE__ */ new Map();
20
+ return (event) => {
21
+ if (event.kind === "spanStart") {
22
+ if (event.parentSpanId) {
23
+ console.log(` → ${event.label}...`);
24
+ return;
25
+ }
26
+ const spinner = ora({
27
+ text: event.label,
28
+ color: flags.color !== false ? "cyan" : false
29
+ }).start();
30
+ activeSpans.set(event.spanId, {
31
+ spinner,
32
+ startTime: Date.now()
33
+ });
34
+ } else if (event.kind === "spanEnd") {
35
+ const spanState = activeSpans.get(event.spanId);
36
+ if (spanState) {
37
+ const elapsed = Date.now() - spanState.startTime;
38
+ if (event.outcome === "error") spanState.spinner.fail(`${spanState.spinner.text} (failed)`);
39
+ else if (event.outcome === "skipped") spanState.spinner.info(`${spanState.spinner.text} (skipped)`);
40
+ else spanState.spinner.succeed(`${spanState.spinner.text} (${elapsed}ms)`);
41
+ activeSpans.delete(event.spanId);
42
+ }
43
+ }
44
+ };
45
+ }
46
+
47
+ //#endregion
48
+ export { createProgressAdapter as t };
49
+ //# sourceMappingURL=progress-adapter-DENrzF6I.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"progress-adapter-DENrzF6I.mjs","names":[],"sources":["../src/utils/progress-adapter.ts"],"sourcesContent":["import ora from 'ora';\nimport type { ControlProgressEvent, OnControlProgress } from '../control-api/types';\nimport type { GlobalFlags } from './global-flags';\n\n/**\n * Options for creating a progress adapter.\n */\ninterface ProgressAdapterOptions {\n /**\n * Global flags that control progress output behavior (quiet, json, color).\n */\n readonly flags: GlobalFlags;\n}\n\n/**\n * State for tracking active spans in the progress adapter.\n */\ninterface SpanState {\n readonly spinner: ReturnType<typeof ora>;\n readonly startTime: number;\n}\n\n/**\n * Creates a progress adapter that converts control-api progress events\n * into CLI spinner/progress output.\n *\n * The adapter:\n * - Starts/succeeds spinners for top-level span boundaries\n * - Prints per-operation lines for nested spans (e.g., migration operations under 'apply')\n * - Respects quiet/json/non-TTY flags (no-op in those cases)\n *\n * @param options - Progress adapter configuration\n * @returns An onProgress callback compatible with control-api operations\n */\nexport function createProgressAdapter(options: ProgressAdapterOptions): OnControlProgress {\n const { flags } = options;\n\n // Skip progress if quiet, JSON output, or non-TTY\n const shouldShowProgress = !flags.quiet && flags.json !== 'object' && process.stdout.isTTY;\n\n if (!shouldShowProgress) {\n // Return a no-op callback\n return () => {\n // No-op\n };\n }\n\n // Track active spans by spanId\n const activeSpans = new Map<string, SpanState>();\n\n return (event: ControlProgressEvent) => {\n if (event.kind === 'spanStart') {\n // Nested spans (with parentSpanId) are printed as lines, not spinners\n if (event.parentSpanId) {\n console.log(` → ${event.label}...`);\n return;\n }\n\n // Top-level spans get a spinner\n const spinner = ora({\n text: event.label,\n color: flags.color !== false ? 'cyan' : false,\n }).start();\n\n activeSpans.set(event.spanId, {\n spinner,\n startTime: Date.now(),\n });\n } else if (event.kind === 'spanEnd') {\n // Complete the spinner for this span (only top-level spans have spinners)\n const spanState = activeSpans.get(event.spanId);\n if (spanState) {\n const elapsed = Date.now() - spanState.startTime;\n if (event.outcome === 'error') {\n spanState.spinner.fail(`${spanState.spinner.text} (failed)`);\n } else if (event.outcome === 'skipped') {\n spanState.spinner.info(`${spanState.spinner.text} (skipped)`);\n } else {\n spanState.spinner.succeed(`${spanState.spinner.text} (${elapsed}ms)`);\n }\n activeSpans.delete(event.spanId);\n }\n // Nested span ends are no-ops (could log completion if needed)\n }\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;AAkCA,SAAgB,sBAAsB,SAAoD;CACxF,MAAM,EAAE,UAAU;AAKlB,KAAI,EAFuB,CAAC,MAAM,SAAS,MAAM,SAAS,YAAY,QAAQ,OAAO,OAInF,cAAa;CAMf,MAAM,8BAAc,IAAI,KAAwB;AAEhD,SAAQ,UAAgC;AACtC,MAAI,MAAM,SAAS,aAAa;AAE9B,OAAI,MAAM,cAAc;AACtB,YAAQ,IAAI,OAAO,MAAM,MAAM,KAAK;AACpC;;GAIF,MAAM,UAAU,IAAI;IAClB,MAAM,MAAM;IACZ,OAAO,MAAM,UAAU,QAAQ,SAAS;IACzC,CAAC,CAAC,OAAO;AAEV,eAAY,IAAI,MAAM,QAAQ;IAC5B;IACA,WAAW,KAAK,KAAK;IACtB,CAAC;aACO,MAAM,SAAS,WAAW;GAEnC,MAAM,YAAY,YAAY,IAAI,MAAM,OAAO;AAC/C,OAAI,WAAW;IACb,MAAM,UAAU,KAAK,KAAK,GAAG,UAAU;AACvC,QAAI,MAAM,YAAY,QACpB,WAAU,QAAQ,KAAK,GAAG,UAAU,QAAQ,KAAK,WAAW;aACnD,MAAM,YAAY,UAC3B,WAAU,QAAQ,KAAK,GAAG,UAAU,QAAQ,KAAK,YAAY;QAE7D,WAAU,QAAQ,QAAQ,GAAG,UAAU,QAAQ,KAAK,IAAI,QAAQ,KAAK;AAEvE,gBAAY,OAAO,MAAM,OAAO"}