@prisma-next/cli 0.3.0-dev.6 → 0.3.0-dev.64

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 (180) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +314 -80
  3. package/dist/cli-errors-JlPTsazx.mjs +3 -0
  4. package/dist/cli.d.mts +1 -0
  5. package/dist/cli.js +1 -2376
  6. package/dist/cli.mjs +203 -0
  7. package/dist/cli.mjs.map +1 -0
  8. package/dist/client-PimzSD1f.mjs +981 -0
  9. package/dist/client-PimzSD1f.mjs.map +1 -0
  10. package/dist/commands/contract-emit.d.mts +7 -0
  11. package/dist/commands/contract-emit.d.mts.map +1 -0
  12. package/dist/commands/contract-emit.mjs +151 -0
  13. package/dist/commands/contract-emit.mjs.map +1 -0
  14. package/dist/commands/db-init.d.mts +7 -0
  15. package/dist/commands/db-init.d.mts.map +1 -0
  16. package/dist/commands/db-init.mjs +134 -0
  17. package/dist/commands/db-init.mjs.map +1 -0
  18. package/dist/commands/db-introspect.d.mts +7 -0
  19. package/dist/commands/db-introspect.d.mts.map +1 -0
  20. package/dist/commands/db-introspect.mjs +118 -0
  21. package/dist/commands/db-introspect.mjs.map +1 -0
  22. package/dist/commands/db-schema-verify.d.mts +7 -0
  23. package/dist/commands/db-schema-verify.d.mts.map +1 -0
  24. package/dist/commands/db-schema-verify.mjs +120 -0
  25. package/dist/commands/db-schema-verify.mjs.map +1 -0
  26. package/dist/commands/db-sign.d.mts +7 -0
  27. package/dist/commands/db-sign.d.mts.map +1 -0
  28. package/dist/commands/db-sign.mjs +142 -0
  29. package/dist/commands/db-sign.mjs.map +1 -0
  30. package/dist/commands/db-update.d.mts +7 -0
  31. package/dist/commands/db-update.d.mts.map +1 -0
  32. package/dist/commands/db-update.mjs +123 -0
  33. package/dist/commands/db-update.mjs.map +1 -0
  34. package/dist/commands/db-verify.d.mts +7 -0
  35. package/dist/commands/db-verify.d.mts.map +1 -0
  36. package/dist/commands/db-verify.mjs +133 -0
  37. package/dist/commands/db-verify.mjs.map +1 -0
  38. package/dist/commands/migration-apply.d.mts +23 -0
  39. package/dist/commands/migration-apply.d.mts.map +1 -0
  40. package/dist/commands/migration-apply.mjs +250 -0
  41. package/dist/commands/migration-apply.mjs.map +1 -0
  42. package/dist/commands/migration-plan.d.mts +25 -0
  43. package/dist/commands/migration-plan.d.mts.map +1 -0
  44. package/dist/commands/migration-plan.mjs +266 -0
  45. package/dist/commands/migration-plan.mjs.map +1 -0
  46. package/dist/commands/migration-show.d.mts +28 -0
  47. package/dist/commands/migration-show.d.mts.map +1 -0
  48. package/dist/commands/migration-show.mjs +138 -0
  49. package/dist/commands/migration-show.mjs.map +1 -0
  50. package/dist/commands/migration-status.d.mts +35 -0
  51. package/dist/commands/migration-status.d.mts.map +1 -0
  52. package/dist/commands/migration-status.mjs +260 -0
  53. package/dist/commands/migration-status.mjs.map +1 -0
  54. package/dist/commands/migration-verify.d.mts +16 -0
  55. package/dist/commands/migration-verify.d.mts.map +1 -0
  56. package/dist/commands/migration-verify.mjs +86 -0
  57. package/dist/commands/migration-verify.mjs.map +1 -0
  58. package/dist/config-loader-PPf4CtDj.mjs +43 -0
  59. package/dist/config-loader-PPf4CtDj.mjs.map +1 -0
  60. package/dist/{config-loader.d.ts → config-loader.d.mts} +8 -3
  61. package/dist/config-loader.d.mts.map +1 -0
  62. package/dist/config-loader.mjs +3 -0
  63. package/dist/exports/config-types.d.mts +2 -0
  64. package/dist/exports/config-types.mjs +3 -0
  65. package/dist/exports/control-api.d.mts +621 -0
  66. package/dist/exports/control-api.d.mts.map +1 -0
  67. package/dist/exports/control-api.mjs +98 -0
  68. package/dist/exports/control-api.mjs.map +1 -0
  69. package/dist/{load-ts-contract.d.ts → exports/index.d.mts} +10 -5
  70. package/dist/exports/index.d.mts.map +1 -0
  71. package/dist/exports/index.mjs +135 -0
  72. package/dist/exports/index.mjs.map +1 -0
  73. package/dist/extract-sql-ddl-BmlKvk4o.mjs +26 -0
  74. package/dist/extract-sql-ddl-BmlKvk4o.mjs.map +1 -0
  75. package/dist/framework-components-CjV_jD8f.mjs +59 -0
  76. package/dist/framework-components-CjV_jD8f.mjs.map +1 -0
  77. package/dist/migration-command-scaffold-DfY_F3ev.mjs +97 -0
  78. package/dist/migration-command-scaffold-DfY_F3ev.mjs.map +1 -0
  79. package/dist/progress-adapter-DENrzF6I.mjs +49 -0
  80. package/dist/progress-adapter-DENrzF6I.mjs.map +1 -0
  81. package/dist/result-handler-iA9JtUC7.mjs +1186 -0
  82. package/dist/result-handler-iA9JtUC7.mjs.map +1 -0
  83. package/package.json +75 -38
  84. package/src/cli.ts +43 -0
  85. package/src/commands/contract-emit.ts +221 -111
  86. package/src/commands/db-init.ts +217 -426
  87. package/src/commands/db-introspect.ts +148 -185
  88. package/src/commands/db-schema-verify.ts +162 -149
  89. package/src/commands/db-sign.ts +215 -202
  90. package/src/commands/db-update.ts +220 -0
  91. package/src/commands/db-verify.ts +193 -156
  92. package/src/commands/migration-apply.ts +431 -0
  93. package/src/commands/migration-plan.ts +446 -0
  94. package/src/commands/migration-show.ts +255 -0
  95. package/src/commands/migration-status.ts +436 -0
  96. package/src/commands/migration-verify.ts +151 -0
  97. package/src/config-loader.ts +13 -3
  98. package/src/control-api/client.ts +605 -0
  99. package/src/control-api/errors.ts +9 -0
  100. package/src/control-api/operations/contract-emit.ts +161 -0
  101. package/src/control-api/operations/db-init.ts +286 -0
  102. package/src/control-api/operations/db-update.ts +221 -0
  103. package/src/control-api/operations/extract-sql-ddl.ts +47 -0
  104. package/src/control-api/operations/migration-apply.ts +195 -0
  105. package/src/control-api/operations/migration-helpers.ts +49 -0
  106. package/src/control-api/types.ts +687 -0
  107. package/src/exports/config-types.ts +3 -3
  108. package/src/exports/control-api.ts +53 -0
  109. package/src/load-ts-contract.ts +16 -11
  110. package/src/utils/cli-errors.ts +3 -1
  111. package/src/utils/command-helpers.ts +92 -3
  112. package/src/utils/framework-components.ts +11 -30
  113. package/src/utils/migration-command-scaffold.ts +190 -0
  114. package/src/utils/output.ts +363 -25
  115. package/src/utils/progress-adapter.ts +86 -0
  116. package/dist/chunk-464LNZCE.js +0 -134
  117. package/dist/chunk-464LNZCE.js.map +0 -1
  118. package/dist/chunk-BZMBKEEQ.js +0 -997
  119. package/dist/chunk-BZMBKEEQ.js.map +0 -1
  120. package/dist/chunk-HWYQOCAJ.js +0 -47
  121. package/dist/chunk-HWYQOCAJ.js.map +0 -1
  122. package/dist/chunk-ZKYEJROM.js +0 -94
  123. package/dist/chunk-ZKYEJROM.js.map +0 -1
  124. package/dist/cli.d.ts +0 -2
  125. package/dist/cli.d.ts.map +0 -1
  126. package/dist/cli.js.map +0 -1
  127. package/dist/commands/contract-emit.d.ts +0 -3
  128. package/dist/commands/contract-emit.d.ts.map +0 -1
  129. package/dist/commands/contract-emit.js +0 -9
  130. package/dist/commands/contract-emit.js.map +0 -1
  131. package/dist/commands/db-init.d.ts +0 -3
  132. package/dist/commands/db-init.d.ts.map +0 -1
  133. package/dist/commands/db-init.js +0 -341
  134. package/dist/commands/db-init.js.map +0 -1
  135. package/dist/commands/db-introspect.d.ts +0 -3
  136. package/dist/commands/db-introspect.d.ts.map +0 -1
  137. package/dist/commands/db-introspect.js +0 -190
  138. package/dist/commands/db-introspect.js.map +0 -1
  139. package/dist/commands/db-schema-verify.d.ts +0 -3
  140. package/dist/commands/db-schema-verify.d.ts.map +0 -1
  141. package/dist/commands/db-schema-verify.js +0 -164
  142. package/dist/commands/db-schema-verify.js.map +0 -1
  143. package/dist/commands/db-sign.d.ts +0 -3
  144. package/dist/commands/db-sign.d.ts.map +0 -1
  145. package/dist/commands/db-sign.js +0 -199
  146. package/dist/commands/db-sign.js.map +0 -1
  147. package/dist/commands/db-verify.d.ts +0 -3
  148. package/dist/commands/db-verify.d.ts.map +0 -1
  149. package/dist/commands/db-verify.js +0 -173
  150. package/dist/commands/db-verify.js.map +0 -1
  151. package/dist/config-loader.d.ts.map +0 -1
  152. package/dist/config-loader.js +0 -7
  153. package/dist/config-loader.js.map +0 -1
  154. package/dist/exports/config-types.d.ts +0 -3
  155. package/dist/exports/config-types.d.ts.map +0 -1
  156. package/dist/exports/config-types.js +0 -6
  157. package/dist/exports/config-types.js.map +0 -1
  158. package/dist/exports/index.d.ts +0 -4
  159. package/dist/exports/index.d.ts.map +0 -1
  160. package/dist/exports/index.js +0 -175
  161. package/dist/exports/index.js.map +0 -1
  162. package/dist/load-ts-contract.d.ts.map +0 -1
  163. package/dist/utils/action.d.ts +0 -16
  164. package/dist/utils/action.d.ts.map +0 -1
  165. package/dist/utils/cli-errors.d.ts +0 -7
  166. package/dist/utils/cli-errors.d.ts.map +0 -1
  167. package/dist/utils/command-helpers.d.ts +0 -12
  168. package/dist/utils/command-helpers.d.ts.map +0 -1
  169. package/dist/utils/framework-components.d.ts +0 -81
  170. package/dist/utils/framework-components.d.ts.map +0 -1
  171. package/dist/utils/global-flags.d.ts +0 -25
  172. package/dist/utils/global-flags.d.ts.map +0 -1
  173. package/dist/utils/output.d.ts +0 -142
  174. package/dist/utils/output.d.ts.map +0 -1
  175. package/dist/utils/result-handler.d.ts +0 -15
  176. package/dist/utils/result-handler.d.ts.map +0 -1
  177. package/dist/utils/spinner.d.ts +0 -29
  178. package/dist/utils/spinner.d.ts.map +0 -1
  179. package/src/utils/action.ts +0 -43
  180. package/src/utils/spinner.ts +0 -67
@@ -0,0 +1,98 @@
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 "../framework-components-CjV_jD8f.mjs";
4
+ import { t as createControlClient } from "../client-PimzSD1f.mjs";
5
+ import { dirname, isAbsolute, join, resolve } from "pathe";
6
+ import { createControlPlaneStack } from "@prisma-next/core-control-plane/stack";
7
+ import { ifDefined } from "@prisma-next/utils/defined";
8
+ import { mkdir, writeFile } from "node:fs/promises";
9
+ import { abortable } from "@prisma-next/utils/abortable";
10
+
11
+ //#region src/control-api/operations/contract-emit.ts
12
+ function isRecord(value) {
13
+ return typeof value === "object" && value !== null;
14
+ }
15
+ function isAbortError(error) {
16
+ return isRecord(error) && typeof error["name"] === "string" && error["name"] === "AbortError";
17
+ }
18
+ function isProviderFailureLike(value) {
19
+ return isRecord(value) && typeof value["summary"] === "string" && Array.isArray(value["diagnostics"]);
20
+ }
21
+ /**
22
+ * Executes the contract emit operation.
23
+ *
24
+ * This is an offline operation that:
25
+ * 1. Loads the Prisma Next config from the specified path
26
+ * 2. Resolves the contract source from config
27
+ * 3. Creates a control plane stack and family instance
28
+ * 4. Emits contract artifacts (JSON and DTS)
29
+ * 5. Writes files to the paths specified in config
30
+ *
31
+ * Supports AbortSignal for cancellation, enabling "last change wins" behavior.
32
+ *
33
+ * @param options - Options including configPath and optional signal
34
+ * @returns File paths and hashes of emitted artifacts
35
+ * @throws If config loading fails, contract is invalid, or file I/O fails
36
+ * @throws signal.reason if cancelled via AbortSignal (typically DOMException with name 'AbortError')
37
+ */
38
+ async function executeContractEmit(options) {
39
+ const { configPath, signal = new AbortController().signal } = options;
40
+ const unlessAborted = abortable(signal);
41
+ const config = await unlessAborted(loadConfig(configPath));
42
+ if (!config.contract) throw errorContractConfigMissing({ why: "Config.contract is required for emit. Define it in your config: contract: { source: ..., output: ... }" });
43
+ const contractConfig = config.contract;
44
+ if (!contractConfig.output) throw errorContractConfigMissing({ why: "Contract config must have output path. This should not happen if defineConfig() was used." });
45
+ if (!contractConfig.output.endsWith(".json")) throw errorContractConfigMissing({ why: "Contract config output path must end with .json (e.g., \"src/prisma/contract.json\")" });
46
+ if (typeof contractConfig.source !== "function") throw errorContractConfigMissing({ why: "Contract config must include a valid source provider function" });
47
+ const configDir = dirname(resolve(configPath));
48
+ const outputJsonPath = isAbsolute(contractConfig.output) ? contractConfig.output : join(configDir, contractConfig.output);
49
+ const outputDtsPath = `${outputJsonPath.slice(0, -5)}.d.ts`;
50
+ let providerResult;
51
+ try {
52
+ providerResult = await unlessAborted(contractConfig.source());
53
+ } catch (error) {
54
+ if (signal.aborted || isAbortError(error)) throw error;
55
+ throw errorRuntime("Failed to resolve contract source", {
56
+ why: error instanceof Error ? error.message : String(error),
57
+ fix: "Ensure contract.source resolves to ok(contractIR) or returns structured diagnostics."
58
+ });
59
+ }
60
+ if (!isRecord(providerResult) || typeof providerResult.ok !== "boolean") throw errorRuntime("Failed to resolve contract source", {
61
+ why: "Contract source provider returned malformed result shape.",
62
+ fix: "Ensure contract.source resolves to ok(contractIR) or notOk({ summary, diagnostics })."
63
+ });
64
+ if (providerResult.ok && !("value" in providerResult)) throw errorRuntime("Failed to resolve contract source", {
65
+ why: "Contract source provider returned malformed success result: missing value.",
66
+ fix: "Ensure contract.source success payload is ok(contractIR)."
67
+ });
68
+ if (!providerResult.ok && !isProviderFailureLike(providerResult.failure)) throw errorRuntime("Failed to resolve contract source", {
69
+ why: "Contract source provider returned malformed failure result: expected summary and diagnostics.",
70
+ fix: "Ensure contract.source failure payload is notOk({ summary, diagnostics, meta? })."
71
+ });
72
+ if (!providerResult.ok) throw errorRuntime("Failed to resolve contract source", {
73
+ why: providerResult.failure.summary,
74
+ fix: "Fix contract source diagnostics and return ok(contractIR).",
75
+ meta: {
76
+ diagnostics: providerResult.failure.diagnostics,
77
+ ...ifDefined("providerMeta", providerResult.failure.meta)
78
+ }
79
+ });
80
+ const stack = createControlPlaneStack(config);
81
+ const emitResult = await unlessAborted(config.family.create(stack).emitContract({ contractIR: providerResult.value }));
82
+ await unlessAborted(mkdir(dirname(outputJsonPath), { recursive: true }));
83
+ await unlessAborted(writeFile(outputJsonPath, emitResult.contractJson, "utf-8"));
84
+ await unlessAborted(writeFile(outputDtsPath, emitResult.contractDts, "utf-8"));
85
+ return {
86
+ storageHash: emitResult.storageHash,
87
+ ...ifDefined("executionHash", emitResult.executionHash),
88
+ profileHash: emitResult.profileHash,
89
+ files: {
90
+ json: outputJsonPath,
91
+ dts: outputDtsPath
92
+ }
93
+ };
94
+ }
95
+
96
+ //#endregion
97
+ export { createControlClient, executeContractEmit };
98
+ //# sourceMappingURL=control-api.mjs.map
@@ -0,0 +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,6 +1,9 @@
1
- import type { ContractIR } from '@prisma-next/contract/ir';
2
- export interface LoadTsContractOptions {
3
- readonly allowlist?: ReadonlyArray<string>;
1
+ import { createContractEmitCommand } from "../commands/contract-emit.mjs";
2
+ import { ContractIR } from "@prisma-next/contract/ir";
3
+
4
+ //#region src/load-ts-contract.d.ts
5
+ interface LoadTsContractOptions {
6
+ readonly allowlist?: ReadonlyArray<string>;
4
7
  }
5
8
  /**
6
9
  * Loads a contract from a TypeScript file and returns it as ContractIR.
@@ -17,5 +20,7 @@ export interface LoadTsContractOptions {
17
20
  * @returns The contract as ContractIR (should already be normalized)
18
21
  * @throws Error if the contract cannot be loaded or is not JSON-serializable
19
22
  */
20
- export declare function loadContractFromTs(entryPath: string, options?: LoadTsContractOptions): Promise<ContractIR>;
21
- //# sourceMappingURL=load-ts-contract.d.ts.map
23
+ declare function loadContractFromTs(entryPath: string, options?: LoadTsContractOptions): Promise<ContractIR>;
24
+ //#endregion
25
+ export { type LoadTsContractOptions, createContractEmitCommand, loadContractFromTs };
26
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/load-ts-contract.ts"],"sourcesContent":[],"mappings":";;;;UAOiB,qBAAA;uBACM;;AADvB;AA0GA;;;;;;;;;;;;;;iBAAsB,kBAAA,8BAEV,wBACT,QAAQ"}
@@ -0,0 +1,135 @@
1
+ import "../config-loader-PPf4CtDj.mjs";
2
+ import "../cli-errors-JlPTsazx.mjs";
3
+ import "../framework-components-CjV_jD8f.mjs";
4
+ import "../client-PimzSD1f.mjs";
5
+ import "../result-handler-iA9JtUC7.mjs";
6
+ import "../progress-adapter-DENrzF6I.mjs";
7
+ import { createContractEmitCommand } from "../commands/contract-emit.mjs";
8
+ import { existsSync, unlinkSync, writeFileSync } from "node:fs";
9
+ import { join } from "node:path";
10
+ import { tmpdir } from "node:os";
11
+ import { build } from "esbuild";
12
+
13
+ //#region src/load-ts-contract.ts
14
+ const DEFAULT_ALLOWLIST = ["@prisma-next/*"];
15
+ function isAllowedImport(importPath, allowlist) {
16
+ for (const pattern of allowlist) if (pattern.endsWith("/*")) {
17
+ const prefix = pattern.slice(0, -2);
18
+ if (importPath === prefix || importPath.startsWith(`${prefix}/`)) return true;
19
+ } else if (importPath === pattern) return true;
20
+ return false;
21
+ }
22
+ function validatePurity(value) {
23
+ if (typeof value !== "object" || value === null) return;
24
+ const path = /* @__PURE__ */ new WeakSet();
25
+ function check(value$1) {
26
+ if (value$1 === null || typeof value$1 !== "object") return;
27
+ if (path.has(value$1)) throw new Error("Contract export contains circular references");
28
+ path.add(value$1);
29
+ try {
30
+ for (const key in value$1) {
31
+ const descriptor = Object.getOwnPropertyDescriptor(value$1, key);
32
+ if (descriptor && (descriptor.get || descriptor.set)) throw new Error(`Contract export contains getter/setter at key "${key}"`);
33
+ if (descriptor && typeof descriptor.value === "function") throw new Error(`Contract export contains function at key "${key}"`);
34
+ check(value$1[key]);
35
+ }
36
+ } finally {
37
+ path.delete(value$1);
38
+ }
39
+ }
40
+ try {
41
+ check(value);
42
+ JSON.stringify(value);
43
+ } catch (error) {
44
+ if (error instanceof Error) {
45
+ if (error.message.includes("getter") || error.message.includes("circular")) throw error;
46
+ throw new Error(`Contract export is not JSON-serializable: ${error.message}`);
47
+ }
48
+ throw new Error("Contract export is not JSON-serializable");
49
+ }
50
+ }
51
+ function createImportAllowlistPlugin(allowlist, entryPath) {
52
+ return {
53
+ name: "import-allowlist",
54
+ setup(build$1) {
55
+ build$1.onResolve({ filter: /.*/ }, (args) => {
56
+ if (args.kind === "entry-point") return;
57
+ if (args.path.startsWith(".") || args.path.startsWith("/")) return;
58
+ if ((args.importer === entryPath || args.importer === "<stdin>") && !isAllowedImport(args.path, allowlist)) return {
59
+ path: args.path,
60
+ external: true
61
+ };
62
+ });
63
+ }
64
+ };
65
+ }
66
+ /**
67
+ * Loads a contract from a TypeScript file and returns it as ContractIR.
68
+ *
69
+ * **Responsibility: Parsing Only**
70
+ * This function loads and parses a TypeScript contract file. It does NOT normalize the contract.
71
+ * The contract should already be normalized if it was built using the contract builder.
72
+ *
73
+ * Normalization must happen in the contract builder when the contract is created.
74
+ * This function only validates that the contract is JSON-serializable and returns it as-is.
75
+ *
76
+ * @param entryPath - Path to the TypeScript contract file
77
+ * @param options - Optional configuration (import allowlist)
78
+ * @returns The contract as ContractIR (should already be normalized)
79
+ * @throws Error if the contract cannot be loaded or is not JSON-serializable
80
+ */
81
+ async function loadContractFromTs(entryPath, options) {
82
+ const allowlist = options?.allowlist ?? DEFAULT_ALLOWLIST;
83
+ if (!existsSync(entryPath)) throw new Error(`Contract file not found: ${entryPath}`);
84
+ const tempFile = join(tmpdir(), `prisma-next-contract-${Date.now()}-${Math.random().toString(36).slice(2)}.mjs`);
85
+ try {
86
+ const result = await build({
87
+ entryPoints: [entryPath],
88
+ bundle: true,
89
+ format: "esm",
90
+ platform: "node",
91
+ target: "es2022",
92
+ outfile: tempFile,
93
+ write: false,
94
+ metafile: true,
95
+ plugins: [createImportAllowlistPlugin(allowlist, entryPath)],
96
+ logLevel: "error"
97
+ });
98
+ if (result.errors.length > 0) {
99
+ const errorMessages = result.errors.map((e) => e.text).join("\n");
100
+ throw new Error(`Failed to bundle contract file: ${errorMessages}`);
101
+ }
102
+ if (!result.outputFiles || result.outputFiles.length === 0) throw new Error("No output files generated from bundling");
103
+ const disallowedImports = [];
104
+ if (result.metafile) {
105
+ const inputs = result.metafile.inputs;
106
+ for (const [, inputData] of Object.entries(inputs)) {
107
+ const imports = inputData.imports || [];
108
+ for (const imp of imports) if (imp.external && !imp.path.startsWith(".") && !imp.path.startsWith("/") && !isAllowedImport(imp.path, allowlist)) disallowedImports.push(imp.path);
109
+ }
110
+ }
111
+ if (disallowedImports.length > 0) throw new Error(`Disallowed imports detected. Only imports matching the allowlist are permitted:\n Allowlist: ${allowlist.join(", ")}\n Disallowed imports: ${disallowedImports.join(", ")}\n\nOnly @prisma-next/* packages are allowed in contract files.`);
112
+ const bundleContent = result.outputFiles[0]?.text;
113
+ if (bundleContent === void 0) throw new Error("Bundle content is undefined");
114
+ writeFileSync(tempFile, bundleContent, "utf-8");
115
+ const module = await import(`file://${tempFile}`);
116
+ unlinkSync(tempFile);
117
+ let contract;
118
+ if (module.default !== void 0) contract = module.default;
119
+ else if (module.contract !== void 0) contract = module.contract;
120
+ else throw new Error(`Contract file must export a contract as default export or named export 'contract'. Found exports: ${Object.keys(module).join(", ") || "none"}`);
121
+ if (typeof contract !== "object" || contract === null) throw new Error(`Contract export must be an object, got ${typeof contract}`);
122
+ validatePurity(contract);
123
+ return contract;
124
+ } catch (error) {
125
+ try {
126
+ if (tempFile) unlinkSync(tempFile);
127
+ } catch {}
128
+ if (error instanceof Error) throw error;
129
+ throw new Error(`Failed to load contract from ${entryPath}: ${String(error)}`);
130
+ }
131
+ }
132
+
133
+ //#endregion
134
+ export { createContractEmitCommand, loadContractFromTs };
135
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":["value","disallowedImports: string[]","contract: unknown"],"sources":["../../src/load-ts-contract.ts"],"sourcesContent":["import { existsSync, unlinkSync, writeFileSync } from 'node:fs';\nimport { tmpdir } from 'node:os';\nimport { join } from 'node:path';\nimport type { ContractIR } from '@prisma-next/contract/ir';\nimport type { Plugin } from 'esbuild';\nimport { build } from 'esbuild';\n\nexport interface LoadTsContractOptions {\n readonly allowlist?: ReadonlyArray<string>;\n}\n\nconst DEFAULT_ALLOWLIST = ['@prisma-next/*'];\n\nfunction isAllowedImport(importPath: string, allowlist: ReadonlyArray<string>): boolean {\n for (const pattern of allowlist) {\n if (pattern.endsWith('/*')) {\n const prefix = pattern.slice(0, -2);\n if (importPath === prefix || importPath.startsWith(`${prefix}/`)) {\n return true;\n }\n } else if (importPath === pattern) {\n return true;\n }\n }\n return false;\n}\n\nfunction validatePurity(value: unknown): void {\n if (typeof value !== 'object' || value === null) {\n return;\n }\n\n const path = new WeakSet();\n\n function check(value: unknown): void {\n if (value === null || typeof value !== 'object') {\n return;\n }\n\n if (path.has(value)) {\n throw new Error('Contract export contains circular references');\n }\n path.add(value);\n\n try {\n for (const key in value) {\n const descriptor = Object.getOwnPropertyDescriptor(value, key);\n if (descriptor && (descriptor.get || descriptor.set)) {\n throw new Error(`Contract export contains getter/setter at key \"${key}\"`);\n }\n if (descriptor && typeof descriptor.value === 'function') {\n throw new Error(`Contract export contains function at key \"${key}\"`);\n }\n check((value as Record<string, unknown>)[key]);\n }\n } finally {\n path.delete(value);\n }\n }\n\n try {\n check(value);\n JSON.stringify(value);\n } catch (error) {\n if (error instanceof Error) {\n if (error.message.includes('getter') || error.message.includes('circular')) {\n throw error;\n }\n throw new Error(`Contract export is not JSON-serializable: ${error.message}`);\n }\n throw new Error('Contract export is not JSON-serializable');\n }\n}\n\nfunction createImportAllowlistPlugin(allowlist: ReadonlyArray<string>, entryPath: string): Plugin {\n return {\n name: 'import-allowlist',\n setup(build) {\n build.onResolve({ filter: /.*/ }, (args) => {\n if (args.kind === 'entry-point') {\n return undefined;\n }\n if (args.path.startsWith('.') || args.path.startsWith('/')) {\n return undefined;\n }\n const isFromEntryPoint = args.importer === entryPath || args.importer === '<stdin>';\n if (isFromEntryPoint && !isAllowedImport(args.path, allowlist)) {\n return {\n path: args.path,\n external: true,\n };\n }\n return undefined;\n });\n },\n };\n}\n\n/**\n * Loads a contract from a TypeScript file and returns it as ContractIR.\n *\n * **Responsibility: Parsing Only**\n * This function loads and parses a TypeScript contract file. It does NOT normalize the contract.\n * The contract should already be normalized if it was built using the contract builder.\n *\n * Normalization must happen in the contract builder when the contract is created.\n * This function only validates that the contract is JSON-serializable and returns it as-is.\n *\n * @param entryPath - Path to the TypeScript contract file\n * @param options - Optional configuration (import allowlist)\n * @returns The contract as ContractIR (should already be normalized)\n * @throws Error if the contract cannot be loaded or is not JSON-serializable\n */\nexport async function loadContractFromTs(\n entryPath: string,\n options?: LoadTsContractOptions,\n): Promise<ContractIR> {\n const allowlist = options?.allowlist ?? DEFAULT_ALLOWLIST;\n\n if (!existsSync(entryPath)) {\n throw new Error(`Contract file not found: ${entryPath}`);\n }\n\n const tempFile = join(\n tmpdir(),\n `prisma-next-contract-${Date.now()}-${Math.random().toString(36).slice(2)}.mjs`,\n );\n\n try {\n const result = await build({\n entryPoints: [entryPath],\n bundle: true,\n format: 'esm',\n platform: 'node',\n target: 'es2022',\n outfile: tempFile,\n write: false,\n metafile: true,\n plugins: [createImportAllowlistPlugin(allowlist, entryPath)],\n logLevel: 'error',\n });\n\n if (result.errors.length > 0) {\n const errorMessages = result.errors.map((e: { text: string }) => e.text).join('\\n');\n throw new Error(`Failed to bundle contract file: ${errorMessages}`);\n }\n\n if (!result.outputFiles || result.outputFiles.length === 0) {\n throw new Error('No output files generated from bundling');\n }\n\n const disallowedImports: string[] = [];\n if (result.metafile) {\n const inputs = result.metafile.inputs;\n for (const [, inputData] of Object.entries(inputs)) {\n const imports =\n (inputData as { imports?: Array<{ path: string; external?: boolean }> }).imports || [];\n for (const imp of imports) {\n if (\n imp.external &&\n !imp.path.startsWith('.') &&\n !imp.path.startsWith('/') &&\n !isAllowedImport(imp.path, allowlist)\n ) {\n disallowedImports.push(imp.path);\n }\n }\n }\n }\n\n if (disallowedImports.length > 0) {\n throw new Error(\n `Disallowed imports detected. Only imports matching the allowlist are permitted:\\n Allowlist: ${allowlist.join(', ')}\\n Disallowed imports: ${disallowedImports.join(', ')}\\n\\nOnly @prisma-next/* packages are allowed in contract files.`,\n );\n }\n\n const bundleContent = result.outputFiles[0]?.text;\n if (bundleContent === undefined) {\n throw new Error('Bundle content is undefined');\n }\n writeFileSync(tempFile, bundleContent, 'utf-8');\n\n const module = (await import(`file://${tempFile}`)) as {\n default?: unknown;\n contract?: unknown;\n };\n unlinkSync(tempFile);\n\n let contract: unknown;\n\n if (module.default !== undefined) {\n contract = module.default;\n } else if (module.contract !== undefined) {\n contract = module.contract;\n } else {\n throw new Error(\n `Contract file must export a contract as default export or named export 'contract'. Found exports: ${Object.keys(module as Record<string, unknown>).join(', ') || 'none'}`,\n );\n }\n\n if (typeof contract !== 'object' || contract === null) {\n throw new Error(`Contract export must be an object, got ${typeof contract}`);\n }\n\n validatePurity(contract);\n\n return contract as ContractIR;\n } catch (error) {\n try {\n if (tempFile) {\n unlinkSync(tempFile);\n }\n } catch {\n // Ignore cleanup errors\n }\n\n if (error instanceof Error) {\n throw error;\n }\n throw new Error(`Failed to load contract from ${entryPath}: ${String(error)}`);\n }\n}\n"],"mappings":";;;;;;;;;;;;;AAWA,MAAM,oBAAoB,CAAC,iBAAiB;AAE5C,SAAS,gBAAgB,YAAoB,WAA2C;AACtF,MAAK,MAAM,WAAW,UACpB,KAAI,QAAQ,SAAS,KAAK,EAAE;EAC1B,MAAM,SAAS,QAAQ,MAAM,GAAG,GAAG;AACnC,MAAI,eAAe,UAAU,WAAW,WAAW,GAAG,OAAO,GAAG,CAC9D,QAAO;YAEA,eAAe,QACxB,QAAO;AAGX,QAAO;;AAGT,SAAS,eAAe,OAAsB;AAC5C,KAAI,OAAO,UAAU,YAAY,UAAU,KACzC;CAGF,MAAM,uBAAO,IAAI,SAAS;CAE1B,SAAS,MAAM,SAAsB;AACnC,MAAIA,YAAU,QAAQ,OAAOA,YAAU,SACrC;AAGF,MAAI,KAAK,IAAIA,QAAM,CACjB,OAAM,IAAI,MAAM,+CAA+C;AAEjE,OAAK,IAAIA,QAAM;AAEf,MAAI;AACF,QAAK,MAAM,OAAOA,SAAO;IACvB,MAAM,aAAa,OAAO,yBAAyBA,SAAO,IAAI;AAC9D,QAAI,eAAe,WAAW,OAAO,WAAW,KAC9C,OAAM,IAAI,MAAM,kDAAkD,IAAI,GAAG;AAE3E,QAAI,cAAc,OAAO,WAAW,UAAU,WAC5C,OAAM,IAAI,MAAM,6CAA6C,IAAI,GAAG;AAEtE,UAAOA,QAAkC,KAAK;;YAExC;AACR,QAAK,OAAOA,QAAM;;;AAItB,KAAI;AACF,QAAM,MAAM;AACZ,OAAK,UAAU,MAAM;UACd,OAAO;AACd,MAAI,iBAAiB,OAAO;AAC1B,OAAI,MAAM,QAAQ,SAAS,SAAS,IAAI,MAAM,QAAQ,SAAS,WAAW,CACxE,OAAM;AAER,SAAM,IAAI,MAAM,6CAA6C,MAAM,UAAU;;AAE/E,QAAM,IAAI,MAAM,2CAA2C;;;AAI/D,SAAS,4BAA4B,WAAkC,WAA2B;AAChG,QAAO;EACL,MAAM;EACN,MAAM,SAAO;AACX,WAAM,UAAU,EAAE,QAAQ,MAAM,GAAG,SAAS;AAC1C,QAAI,KAAK,SAAS,cAChB;AAEF,QAAI,KAAK,KAAK,WAAW,IAAI,IAAI,KAAK,KAAK,WAAW,IAAI,CACxD;AAGF,SADyB,KAAK,aAAa,aAAa,KAAK,aAAa,cAClD,CAAC,gBAAgB,KAAK,MAAM,UAAU,CAC5D,QAAO;KACL,MAAM,KAAK;KACX,UAAU;KACX;KAGH;;EAEL;;;;;;;;;;;;;;;;;AAkBH,eAAsB,mBACpB,WACA,SACqB;CACrB,MAAM,YAAY,SAAS,aAAa;AAExC,KAAI,CAAC,WAAW,UAAU,CACxB,OAAM,IAAI,MAAM,4BAA4B,YAAY;CAG1D,MAAM,WAAW,KACf,QAAQ,EACR,wBAAwB,KAAK,KAAK,CAAC,GAAG,KAAK,QAAQ,CAAC,SAAS,GAAG,CAAC,MAAM,EAAE,CAAC,MAC3E;AAED,KAAI;EACF,MAAM,SAAS,MAAM,MAAM;GACzB,aAAa,CAAC,UAAU;GACxB,QAAQ;GACR,QAAQ;GACR,UAAU;GACV,QAAQ;GACR,SAAS;GACT,OAAO;GACP,UAAU;GACV,SAAS,CAAC,4BAA4B,WAAW,UAAU,CAAC;GAC5D,UAAU;GACX,CAAC;AAEF,MAAI,OAAO,OAAO,SAAS,GAAG;GAC5B,MAAM,gBAAgB,OAAO,OAAO,KAAK,MAAwB,EAAE,KAAK,CAAC,KAAK,KAAK;AACnF,SAAM,IAAI,MAAM,mCAAmC,gBAAgB;;AAGrE,MAAI,CAAC,OAAO,eAAe,OAAO,YAAY,WAAW,EACvD,OAAM,IAAI,MAAM,0CAA0C;EAG5D,MAAMC,oBAA8B,EAAE;AACtC,MAAI,OAAO,UAAU;GACnB,MAAM,SAAS,OAAO,SAAS;AAC/B,QAAK,MAAM,GAAG,cAAc,OAAO,QAAQ,OAAO,EAAE;IAClD,MAAM,UACH,UAAwE,WAAW,EAAE;AACxF,SAAK,MAAM,OAAO,QAChB,KACE,IAAI,YACJ,CAAC,IAAI,KAAK,WAAW,IAAI,IACzB,CAAC,IAAI,KAAK,WAAW,IAAI,IACzB,CAAC,gBAAgB,IAAI,MAAM,UAAU,CAErC,mBAAkB,KAAK,IAAI,KAAK;;;AAMxC,MAAI,kBAAkB,SAAS,EAC7B,OAAM,IAAI,MACR,iGAAiG,UAAU,KAAK,KAAK,CAAC,0BAA0B,kBAAkB,KAAK,KAAK,CAAC,iEAC9K;EAGH,MAAM,gBAAgB,OAAO,YAAY,IAAI;AAC7C,MAAI,kBAAkB,OACpB,OAAM,IAAI,MAAM,8BAA8B;AAEhD,gBAAc,UAAU,eAAe,QAAQ;EAE/C,MAAM,SAAU,MAAM,OAAO,UAAU;AAIvC,aAAW,SAAS;EAEpB,IAAIC;AAEJ,MAAI,OAAO,YAAY,OACrB,YAAW,OAAO;WACT,OAAO,aAAa,OAC7B,YAAW,OAAO;MAElB,OAAM,IAAI,MACR,qGAAqG,OAAO,KAAK,OAAkC,CAAC,KAAK,KAAK,IAAI,SACnK;AAGH,MAAI,OAAO,aAAa,YAAY,aAAa,KAC/C,OAAM,IAAI,MAAM,0CAA0C,OAAO,WAAW;AAG9E,iBAAe,SAAS;AAExB,SAAO;UACA,OAAO;AACd,MAAI;AACF,OAAI,SACF,YAAW,SAAS;UAEhB;AAIR,MAAI,iBAAiB,MACnB,OAAM;AAER,QAAM,IAAI,MAAM,gCAAgC,UAAU,IAAI,OAAO,MAAM,GAAG"}
@@ -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"}
@@ -0,0 +1,97 @@
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";
6
+ import { notOk, ok } from "@prisma-next/utils/result";
7
+ import { relative, resolve } from "node:path";
8
+ import { readFile } from "node:fs/promises";
9
+
10
+ //#region src/utils/migration-command-scaffold.ts
11
+ /**
12
+ * Prepares the shared context for migration commands (db init, db update).
13
+ *
14
+ * Handles: config loading, contract file reading, JSON parsing, connection resolution,
15
+ * driver/migration-support validation, client creation, and header output.
16
+ *
17
+ * Returns a Result with either the resolved context or a structured error.
18
+ */
19
+ async function prepareMigrationContext(options, flags, descriptor) {
20
+ const config = await loadConfig(options.config);
21
+ const configPath = options.config ? relative(process.cwd(), resolve(options.config)) : "prisma-next.config.ts";
22
+ const contractPathAbsolute = resolveContractPath(config);
23
+ const contractPath = relative(process.cwd(), contractPathAbsolute);
24
+ if (flags.json !== "object" && !flags.quiet) {
25
+ const details = [{
26
+ label: "config",
27
+ value: configPath
28
+ }, {
29
+ label: "contract",
30
+ value: contractPath
31
+ }];
32
+ if (options.db) details.push({
33
+ label: "database",
34
+ value: maskConnectionUrl(options.db)
35
+ });
36
+ if (options.plan) details.push({
37
+ label: "mode",
38
+ value: "plan (dry run)"
39
+ });
40
+ const header = formatStyledHeader({
41
+ command: descriptor.commandName,
42
+ description: descriptor.description,
43
+ url: descriptor.url,
44
+ details,
45
+ flags
46
+ });
47
+ console.log(header);
48
+ }
49
+ let contractJsonContent;
50
+ try {
51
+ contractJsonContent = await readFile(contractPathAbsolute, "utf-8");
52
+ } catch (error) {
53
+ if (error instanceof Error && error.code === "ENOENT") return notOk(errorFileNotFound(contractPathAbsolute, {
54
+ why: `Contract file not found at ${contractPathAbsolute}`,
55
+ fix: `Run \`prisma-next contract emit\` to generate ${contractPath}, or update \`config.contract.output\` in ${configPath}`
56
+ }));
57
+ return notOk(errorUnexpected(error instanceof Error ? error.message : String(error), { why: `Failed to read contract file: ${error instanceof Error ? error.message : String(error)}` }));
58
+ }
59
+ let contractJson;
60
+ try {
61
+ contractJson = JSON.parse(contractJsonContent);
62
+ } catch (error) {
63
+ return notOk(errorContractValidationFailed(`Contract JSON is invalid: ${error instanceof Error ? error.message : String(error)}`, { where: { path: contractPathAbsolute } }));
64
+ }
65
+ const dbConnection = options.db ?? config.db?.connection;
66
+ if (!dbConnection) return notOk(errorDatabaseConnectionRequired({ why: `Database connection is required for ${descriptor.commandName} (set db.connection in ${configPath}, or pass --db <url>)` }));
67
+ if (!config.driver) return notOk(errorDriverRequired({ why: `Config.driver is required for ${descriptor.commandName}` }));
68
+ if (!config.target.migrations) return notOk(errorTargetMigrationNotSupported({ why: `Target "${config.target.id}" does not support migrations` }));
69
+ const client = createControlClient({
70
+ family: config.family,
71
+ target: config.target,
72
+ adapter: config.adapter,
73
+ driver: config.driver,
74
+ extensionPacks: config.extensionPacks ?? []
75
+ });
76
+ const onProgress = createProgressAdapter({ flags });
77
+ return ok({
78
+ client,
79
+ contractJson,
80
+ dbConnection,
81
+ onProgress,
82
+ configPath,
83
+ contractPath,
84
+ contractPathAbsolute,
85
+ config
86
+ });
87
+ }
88
+ /**
89
+ * Registers the shared CLI options for migration commands (db init, db update).
90
+ */
91
+ function addMigrationCommandOptions(command) {
92
+ return command.option("--db <url>", "Database connection string").option("--config <path>", "Path to prisma-next.config.ts").option("--plan", "Preview planned operations without applying", false).option("--json [format]", "Output as JSON (object)", false).option("-q, --quiet", "Quiet mode: errors only").option("-v, --verbose", "Verbose output: debug info, timings").option("-vv, --trace", "Trace output: deep internals, stack traces").option("--timestamps", "Add timestamps to output").option("--color", "Force color output").option("--no-color", "Disable color output");
93
+ }
94
+
95
+ //#endregion
96
+ export { prepareMigrationContext as n, addMigrationCommandOptions as t };
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"}