prisma-next 0.5.0-dev.74 → 0.5.0-dev.76
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.
- package/dist/cli.mjs +8 -8
- package/dist/{client-0ZX24FXF.mjs → client-qVH-rEgd.mjs} +433 -236
- package/dist/client-qVH-rEgd.mjs.map +1 -0
- package/dist/{result-handler-DWb1rFS-.mjs → command-helpers-BeZHkxV8.mjs} +22 -24
- package/dist/command-helpers-BeZHkxV8.mjs.map +1 -0
- package/dist/commands/contract-emit.mjs +1 -1
- package/dist/commands/contract-infer.mjs +1 -1
- package/dist/commands/db-init.d.mts.map +1 -1
- package/dist/commands/db-init.mjs +7 -5
- package/dist/commands/db-init.mjs.map +1 -1
- package/dist/commands/db-schema.mjs +5 -4
- package/dist/commands/db-schema.mjs.map +1 -1
- package/dist/commands/db-sign.mjs +6 -5
- package/dist/commands/db-sign.mjs.map +1 -1
- package/dist/commands/db-update.d.mts.map +1 -1
- package/dist/commands/db-update.mjs +7 -5
- package/dist/commands/db-update.mjs.map +1 -1
- package/dist/commands/db-verify.mjs +1 -1
- package/dist/commands/migration-apply.d.mts +29 -17
- package/dist/commands/migration-apply.d.mts.map +1 -1
- package/dist/commands/migration-apply.mjs +35 -129
- package/dist/commands/migration-apply.mjs.map +1 -1
- package/dist/commands/migration-new.mjs +4 -3
- package/dist/commands/migration-new.mjs.map +1 -1
- package/dist/commands/migration-plan.d.mts +19 -1
- package/dist/commands/migration-plan.d.mts.map +1 -1
- package/dist/commands/migration-plan.mjs +2 -2
- package/dist/commands/migration-ref.d.mts +1 -1
- package/dist/commands/migration-ref.mjs +3 -2
- package/dist/commands/migration-ref.mjs.map +1 -1
- package/dist/commands/migration-show.d.mts +1 -1
- package/dist/commands/migration-show.mjs +5 -4
- package/dist/commands/migration-show.mjs.map +1 -1
- package/dist/commands/migration-status.d.mts +104 -1
- package/dist/commands/migration-status.d.mts.map +1 -1
- package/dist/commands/migration-status.mjs +2 -2
- package/dist/{contract-emit-DkMqO7f2.mjs → contract-emit-9DBda5Ou.mjs} +7 -5
- package/dist/{contract-emit-DkMqO7f2.mjs.map → contract-emit-9DBda5Ou.mjs.map} +1 -1
- package/dist/{contract-emit-B3ChISB_.mjs → contract-emit-B77TsJqf.mjs} +4 -15
- package/dist/{contract-emit-B3ChISB_.mjs.map → contract-emit-B77TsJqf.mjs.map} +1 -1
- package/dist/{contract-enrichment-CF6ogEJ_.mjs → contract-enrichment-Dani0mMW.mjs} +1 -1
- package/dist/{contract-enrichment-CF6ogEJ_.mjs.map → contract-enrichment-Dani0mMW.mjs.map} +1 -1
- package/dist/{contract-infer-BDKAE0B0.mjs → contract-infer-BK9YFGEG.mjs} +5 -4
- package/dist/{contract-infer-BDKAE0B0.mjs.map → contract-infer-BK9YFGEG.mjs.map} +1 -1
- package/dist/{db-verify-B4TdDKOI.mjs → db-verify-C0y1PCO2.mjs} +7 -6
- package/dist/{db-verify-B4TdDKOI.mjs.map → db-verify-C0y1PCO2.mjs.map} +1 -1
- package/dist/exports/control-api.d.mts +3 -746
- package/dist/exports/control-api.d.mts.map +1 -1
- package/dist/exports/control-api.mjs +3 -3
- package/dist/exports/index.mjs +1 -1
- package/dist/exports/init-output.mjs +1 -1
- package/dist/extension-pack-inputs-C7xgE-vv.mjs +74 -0
- package/dist/extension-pack-inputs-C7xgE-vv.mjs.map +1 -0
- package/dist/{framework-components-gwAHl7ml.mjs → framework-components-ChqVUxR-.mjs} +1 -1
- package/dist/{framework-components-gwAHl7ml.mjs.map → framework-components-ChqVUxR-.mjs.map} +1 -1
- package/dist/global-flags-Icqpxk23.d.mts +12 -0
- package/dist/global-flags-Icqpxk23.d.mts.map +1 -0
- package/dist/helpers-eqdN8tH6.mjs +25 -0
- package/dist/helpers-eqdN8tH6.mjs.map +1 -0
- package/dist/{init-Deo7U8_U.mjs → init-CoDVPvQ4.mjs} +4 -4
- package/dist/{init-Deo7U8_U.mjs.map → init-CoDVPvQ4.mjs.map} +1 -1
- package/dist/{inspect-live-schema-BAgQMYpD.mjs → inspect-live-schema-CWYxGKlb.mjs} +4 -4
- package/dist/{inspect-live-schema-BAgQMYpD.mjs.map → inspect-live-schema-CWYxGKlb.mjs.map} +1 -1
- package/dist/{migration-command-scaffold-B8J702Uh.mjs → migration-command-scaffold-B5dORFEv.mjs} +4 -4
- package/dist/{migration-command-scaffold-B8J702Uh.mjs.map → migration-command-scaffold-B5dORFEv.mjs.map} +1 -1
- package/dist/{migration-plan-BcKNnTM7.mjs → migration-plan-C6lVaHsO.mjs} +47 -23
- package/dist/migration-plan-C6lVaHsO.mjs.map +1 -0
- package/dist/{migration-status-CjwB2of-.mjs → migration-status-CZ-D5k7k.mjs} +161 -7
- package/dist/migration-status-CZ-D5k7k.mjs.map +1 -0
- package/dist/{migrations-CIK94AJf.mjs → migrations-D_UJnpuW.mjs} +67 -24
- package/dist/migrations-D_UJnpuW.mjs.map +1 -0
- package/dist/{output-DnjfCC_u.mjs → output-B16Kefzx.mjs} +1 -1
- package/dist/{output-DnjfCC_u.mjs.map → output-B16Kefzx.mjs.map} +1 -1
- package/dist/{progress-adapter-xASh41wr.mjs → progress-adapter-DFfvZcYL.mjs} +1 -1
- package/dist/{progress-adapter-xASh41wr.mjs.map → progress-adapter-DFfvZcYL.mjs.map} +1 -1
- package/dist/result-handler-rmPVKIP2.mjs +25 -0
- package/dist/result-handler-rmPVKIP2.mjs.map +1 -0
- package/dist/rolldown-runtime-twds-ZHy.mjs +14 -0
- package/dist/{terminal-ui-zaRDhJnP.mjs → terminal-ui-C_hFNbAn.mjs} +3 -23
- package/dist/terminal-ui-C_hFNbAn.mjs.map +1 -0
- package/dist/types-D7x-IFLO.d.mts +858 -0
- package/dist/types-D7x-IFLO.d.mts.map +1 -0
- package/dist/{verify-BEIa9638.mjs → verify-CiwNWM9N.mjs} +2 -2
- package/dist/{verify-BEIa9638.mjs.map → verify-CiwNWM9N.mjs.map} +1 -1
- package/package.json +10 -10
- package/dist/client-0ZX24FXF.mjs.map +0 -1
- package/dist/migration-plan-BcKNnTM7.mjs.map +0 -1
- package/dist/migration-status-CjwB2of-.mjs.map +0 -1
- package/dist/migrations-CIK94AJf.mjs.map +0 -1
- package/dist/result-handler-DWb1rFS-.mjs.map +0 -1
- package/dist/terminal-ui-zaRDhJnP.mjs.map +0 -1
- /package/dist/{cli-errors-QH8kf-C2.d.mts → cli-errors-B9OBbled.d.mts} +0 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { t as loadConfig } from "./config-loader-B6sJjXTv.mjs";
|
|
2
2
|
import { _ as errorUnexpected, c as errorDriverRequired, o as errorDatabaseConnectionRequired, t as CliStructuredError } from "./cli-errors-D3_sMh2K.mjs";
|
|
3
|
-
import { o as maskConnectionUrl, u as sanitizeErrorMessage, y as formatStyledHeader } from "./
|
|
4
|
-
import { t as createProgressAdapter } from "./progress-adapter-
|
|
5
|
-
import { t as createControlClient } from "./client-
|
|
3
|
+
import { o as maskConnectionUrl, u as sanitizeErrorMessage, y as formatStyledHeader } from "./command-helpers-BeZHkxV8.mjs";
|
|
4
|
+
import { t as createProgressAdapter } from "./progress-adapter-DFfvZcYL.mjs";
|
|
5
|
+
import { t as createControlClient } from "./client-qVH-rEgd.mjs";
|
|
6
6
|
import { notOk, ok } from "@prisma-next/utils/result";
|
|
7
7
|
import { relative, resolve } from "pathe";
|
|
8
8
|
//#region src/commands/inspect-live-schema.ts
|
|
@@ -87,4 +87,4 @@ async function inspectLiveSchema(options, flags, ui, startTime, context) {
|
|
|
87
87
|
//#endregion
|
|
88
88
|
export { inspectLiveSchema as t };
|
|
89
89
|
|
|
90
|
-
//# sourceMappingURL=inspect-live-schema-
|
|
90
|
+
//# sourceMappingURL=inspect-live-schema-CWYxGKlb.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"inspect-live-schema-
|
|
1
|
+
{"version":3,"file":"inspect-live-schema-CWYxGKlb.mjs","names":[],"sources":["../src/commands/inspect-live-schema.ts"],"sourcesContent":["import type { CoreSchemaView } from '@prisma-next/framework-components/control';\nimport type { PslDocumentAst } from '@prisma-next/framework-components/psl-ast';\nimport { notOk, ok, type Result } from '@prisma-next/utils/result';\nimport { relative, resolve } from 'pathe';\nimport { loadConfig } from '../config-loader';\nimport { createControlClient } from '../control-api/client';\nimport {\n CliStructuredError,\n errorDatabaseConnectionRequired,\n errorDriverRequired,\n errorUnexpected,\n} from '../utils/cli-errors';\nimport { maskConnectionUrl, sanitizeErrorMessage } from '../utils/command-helpers';\nimport { formatStyledHeader } from '../utils/formatters/styled';\nimport type { CommonCommandOptions, GlobalFlags } from '../utils/global-flags';\nimport { createProgressAdapter } from '../utils/progress-adapter';\nimport type { TerminalUI } from '../utils/terminal-ui';\n\nexport interface InspectLiveSchemaOptions extends CommonCommandOptions {\n readonly db?: string;\n readonly config?: string;\n}\n\ninterface InspectLiveSchemaContext {\n readonly commandName: string;\n readonly description: string;\n readonly url: string;\n}\n\ntype LoadedCliConfig = Awaited<ReturnType<typeof loadConfig>>;\n\nexport interface InspectLiveSchemaResult {\n readonly config: LoadedCliConfig;\n readonly schema: unknown;\n readonly schemaView: CoreSchemaView | undefined;\n /**\n * PSL AST inferred from the introspected schema, when the configured family\n * implements `PslContractInferCapable`. `undefined` for families that do not\n * support inference (e.g. Mongo today).\n */\n readonly pslContractAst: PslDocumentAst | undefined;\n readonly target: {\n readonly familyId: string;\n readonly id: string;\n };\n readonly meta: {\n readonly configPath?: string;\n readonly dbUrl?: string;\n };\n readonly timings: {\n readonly total: number;\n };\n}\n\nexport async function inspectLiveSchema(\n options: InspectLiveSchemaOptions,\n flags: GlobalFlags,\n ui: TerminalUI,\n startTime: number,\n context: InspectLiveSchemaContext,\n): Promise<Result<InspectLiveSchemaResult, CliStructuredError>> {\n let config: LoadedCliConfig;\n try {\n config = await loadConfig(options.config);\n } catch (error) {\n if (CliStructuredError.is(error)) {\n return notOk(error);\n }\n\n return notOk(\n errorUnexpected(error instanceof Error ? error.message : String(error), {\n why: 'Failed to load config',\n }),\n );\n }\n\n const configPath = options.config\n ? relative(process.cwd(), resolve(options.config))\n : 'prisma-next.config.ts';\n\n if (!flags.json && !flags.quiet) {\n const details: Array<{ label: string; value: string }> = [\n { label: 'config', value: configPath },\n ];\n\n if (options.db) {\n details.push({ label: 'database', value: maskConnectionUrl(options.db) });\n } else if (config.db?.connection && typeof config.db.connection === 'string') {\n details.push({ label: 'database', value: maskConnectionUrl(config.db.connection) });\n }\n\n ui.stderr(\n formatStyledHeader({\n command: context.commandName,\n description: context.description,\n url: context.url,\n details,\n flags,\n }),\n );\n }\n\n const dbConnection = options.db ?? config.db?.connection;\n if (!dbConnection) {\n return notOk(\n errorDatabaseConnectionRequired({\n why: `Database connection is required for ${context.commandName} (set db.connection in ${configPath}, or pass --db <url>)`,\n commandName: context.commandName,\n }),\n );\n }\n\n if (!config.driver) {\n return notOk(\n errorDriverRequired({\n why: `Config.driver is required for ${context.commandName}`,\n }),\n );\n }\n\n const client = createControlClient({\n family: config.family,\n target: config.target,\n adapter: config.adapter,\n driver: config.driver,\n extensionPacks: config.extensionPacks ?? [],\n });\n const onProgress = createProgressAdapter({ ui, flags });\n\n try {\n const schema = await client.introspect({\n connection: dbConnection,\n onProgress,\n });\n const schemaView = client.toSchemaView(schema);\n const pslContractAst = client.inferPslContract(schema);\n\n const dbUrl = typeof dbConnection === 'string' ? maskConnectionUrl(dbConnection) : undefined;\n\n return ok({\n config,\n schema,\n schemaView,\n pslContractAst,\n target: {\n familyId: config.family.familyId,\n id: config.target.targetId,\n },\n meta: {\n configPath,\n ...(dbUrl ? { dbUrl } : {}),\n },\n timings: {\n total: Date.now() - startTime,\n },\n });\n } catch (error) {\n if (CliStructuredError.is(error)) {\n return notOk(error);\n }\n\n const rawMessage = error instanceof Error ? error.message : String(error);\n const safeMessage = sanitizeErrorMessage(\n rawMessage,\n typeof dbConnection === 'string' ? dbConnection : undefined,\n );\n return notOk(\n errorUnexpected(safeMessage, {\n why: `Unexpected error during ${context.commandName}: ${safeMessage}`,\n }),\n );\n } finally {\n await client.close();\n }\n}\n"],"mappings":";;;;;;;;AAsDA,eAAsB,kBACpB,SACA,OACA,IACA,WACA,SAC8D;CAC9D,IAAI;CACJ,IAAI;EACF,SAAS,MAAM,WAAW,QAAQ,OAAO;UAClC,OAAO;EACd,IAAI,mBAAmB,GAAG,MAAM,EAC9B,OAAO,MAAM,MAAM;EAGrB,OAAO,MACL,gBAAgB,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM,EAAE,EACtE,KAAK,yBACN,CAAC,CACH;;CAGH,MAAM,aAAa,QAAQ,SACvB,SAAS,QAAQ,KAAK,EAAE,QAAQ,QAAQ,OAAO,CAAC,GAChD;CAEJ,IAAI,CAAC,MAAM,QAAQ,CAAC,MAAM,OAAO;EAC/B,MAAM,UAAmD,CACvD;GAAE,OAAO;GAAU,OAAO;GAAY,CACvC;EAED,IAAI,QAAQ,IACV,QAAQ,KAAK;GAAE,OAAO;GAAY,OAAO,kBAAkB,QAAQ,GAAG;GAAE,CAAC;OACpE,IAAI,OAAO,IAAI,cAAc,OAAO,OAAO,GAAG,eAAe,UAClE,QAAQ,KAAK;GAAE,OAAO;GAAY,OAAO,kBAAkB,OAAO,GAAG,WAAW;GAAE,CAAC;EAGrF,GAAG,OACD,mBAAmB;GACjB,SAAS,QAAQ;GACjB,aAAa,QAAQ;GACrB,KAAK,QAAQ;GACb;GACA;GACD,CAAC,CACH;;CAGH,MAAM,eAAe,QAAQ,MAAM,OAAO,IAAI;CAC9C,IAAI,CAAC,cACH,OAAO,MACL,gCAAgC;EAC9B,KAAK,uCAAuC,QAAQ,YAAY,yBAAyB,WAAW;EACpG,aAAa,QAAQ;EACtB,CAAC,CACH;CAGH,IAAI,CAAC,OAAO,QACV,OAAO,MACL,oBAAoB,EAClB,KAAK,iCAAiC,QAAQ,eAC/C,CAAC,CACH;CAGH,MAAM,SAAS,oBAAoB;EACjC,QAAQ,OAAO;EACf,QAAQ,OAAO;EACf,SAAS,OAAO;EAChB,QAAQ,OAAO;EACf,gBAAgB,OAAO,kBAAkB,EAAE;EAC5C,CAAC;CACF,MAAM,aAAa,sBAAsB;EAAE;EAAI;EAAO,CAAC;CAEvD,IAAI;EACF,MAAM,SAAS,MAAM,OAAO,WAAW;GACrC,YAAY;GACZ;GACD,CAAC;EACF,MAAM,aAAa,OAAO,aAAa,OAAO;EAC9C,MAAM,iBAAiB,OAAO,iBAAiB,OAAO;EAEtD,MAAM,QAAQ,OAAO,iBAAiB,WAAW,kBAAkB,aAAa,GAAG,KAAA;EAEnF,OAAO,GAAG;GACR;GACA;GACA;GACA;GACA,QAAQ;IACN,UAAU,OAAO,OAAO;IACxB,IAAI,OAAO,OAAO;IACnB;GACD,MAAM;IACJ;IACA,GAAI,QAAQ,EAAE,OAAO,GAAG,EAAE;IAC3B;GACD,SAAS,EACP,OAAO,KAAK,KAAK,GAAG,WACrB;GACF,CAAC;UACK,OAAO;EACd,IAAI,mBAAmB,GAAG,MAAM,EAC9B,OAAO,MAAM,MAAM;EAIrB,MAAM,cAAc,qBADD,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM,EAGvE,OAAO,iBAAiB,WAAW,eAAe,KAAA,EACnD;EACD,OAAO,MACL,gBAAgB,aAAa,EAC3B,KAAK,2BAA2B,QAAQ,YAAY,IAAI,eACzD,CAAC,CACH;WACO;EACR,MAAM,OAAO,OAAO"}
|
package/dist/{migration-command-scaffold-B8J702Uh.mjs → migration-command-scaffold-B5dORFEv.mjs}
RENAMED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { t as loadConfig } from "./config-loader-B6sJjXTv.mjs";
|
|
2
2
|
import { _ as errorUnexpected, a as errorContractValidationFailed, c as errorDriverRequired, h as errorTargetMigrationNotSupported, l as errorFileNotFound, o as errorDatabaseConnectionRequired } from "./cli-errors-D3_sMh2K.mjs";
|
|
3
|
-
import { c as resolveContractPath,
|
|
4
|
-
import { t as createProgressAdapter } from "./progress-adapter-
|
|
5
|
-
import { t as createControlClient } from "./client-
|
|
3
|
+
import { c as resolveContractPath, o as maskConnectionUrl, t as addGlobalOptions, y as formatStyledHeader } from "./command-helpers-BeZHkxV8.mjs";
|
|
4
|
+
import { t as createProgressAdapter } from "./progress-adapter-DFfvZcYL.mjs";
|
|
5
|
+
import { t as createControlClient } from "./client-qVH-rEgd.mjs";
|
|
6
6
|
import { notOk, ok } from "@prisma-next/utils/result";
|
|
7
7
|
import { readFile } from "node:fs/promises";
|
|
8
8
|
import { hasMigrations } from "@prisma-next/framework-components/control";
|
|
@@ -101,4 +101,4 @@ function addMigrationCommandOptions(command) {
|
|
|
101
101
|
//#endregion
|
|
102
102
|
export { prepareMigrationContext as n, addMigrationCommandOptions as t };
|
|
103
103
|
|
|
104
|
-
//# sourceMappingURL=migration-command-scaffold-
|
|
104
|
+
//# sourceMappingURL=migration-command-scaffold-B5dORFEv.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"migration-command-scaffold-
|
|
1
|
+
{"version":3,"file":"migration-command-scaffold-B5dORFEv.mjs","names":[],"sources":["../src/utils/migration-command-scaffold.ts"],"sourcesContent":["import { readFile } from 'node:fs/promises';\nimport { relative, resolve } from 'node:path';\nimport { hasMigrations } from '@prisma-next/framework-components/control';\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 { addGlobalOptions, maskConnectionUrl, resolveContractPath } from './command-helpers';\nimport { formatStyledHeader } from './formatters/styled';\nimport type { GlobalFlags } from './global-flags';\nimport { createProgressAdapter } from './progress-adapter';\nimport type { TerminalUI } from './terminal-ui';\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 dryRun?: boolean },\n flags: GlobalFlags,\n ui: TerminalUI,\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 to stderr (decoration)\n if (!flags.json && !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.dryRun) {\n details.push({ label: 'mode', value: '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 ui.stderr(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 commandName: descriptor.commandName,\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 if (!hasMigrations(config.target)) {\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({ ui, 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 addGlobalOptions(command);\n return command\n .option('--db <url>', 'Database connection string')\n .option('--config <path>', 'Path to prisma-next.config.ts')\n .option('--dry-run', 'Preview planned operations without applying', false);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAuDA,eAAsB,wBACpB,SACA,OACA,IACA,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;CAGlE,IAAI,CAAC,MAAM,QAAQ,CAAC,MAAM,OAAO;EAC/B,MAAM,UAAmD,CACvD;GAAE,OAAO;GAAU,OAAO;GAAY,EACtC;GAAE,OAAO;GAAY,OAAO;GAAc,CAC3C;EACD,IAAI,QAAQ,IACV,QAAQ,KAAK;GAAE,OAAO;GAAY,OAAO,kBAAkB,QAAQ,GAAG;GAAE,CAAC;EAE3E,IAAI,QAAQ,QACV,QAAQ,KAAK;GAAE,OAAO;GAAQ,OAAO;GAAW,CAAC;EAEnD,MAAM,SAAS,mBAAmB;GAChC,SAAS,WAAW;GACpB,aAAa,WAAW;GACxB,KAAK,WAAW;GAChB;GACA;GACD,CAAC;EACF,GAAG,OAAO,OAAO;;CAInB,IAAI;CACJ,IAAI;EACF,sBAAsB,MAAM,SAAS,sBAAsB,QAAQ;UAC5D,OAAO;EACd,IAAI,iBAAiB,SAAU,MAA4B,SAAS,UAClE,OAAO,MACL,kBAAkB,sBAAsB;GACtC,KAAK,8BAA8B;GACnC,KAAK,iDAAiD,aAAa,4CAA4C;GAChH,CAAC,CACH;EAEH,OAAO,MACL,gBAAgB,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM,EAAE,EACtE,KAAK,iCAAiC,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM,IAC7F,CAAC,CACH;;CAIH,IAAI;CACJ,IAAI;EACF,eAAe,KAAK,MAAM,oBAAoB;UACvC,OAAO;EACd,OAAO,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;CAC9C,IAAI,CAAC,cACH,OAAO,MACL,gCAAgC;EAC9B,KAAK,uCAAuC,WAAW,YAAY,yBAAyB,WAAW;EACvG,aAAa,WAAW;EACzB,CAAC,CACH;CAIH,IAAI,CAAC,OAAO,QACV,OAAO,MACL,oBAAoB,EAAE,KAAK,iCAAiC,WAAW,eAAe,CAAC,CACxF;CAGH,IAAI,CAAC,cAAc,OAAO,OAAO,EAC/B,OAAO,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;EAAI;EAAO,CAAC;CAEvD,OAAO,GAAG;EACR;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACD,CAAC;;;;;AAMJ,SAAgB,2BAA2B,SAA2B;CACpE,iBAAiB,QAAQ;CACzB,OAAO,QACJ,OAAO,cAAc,6BAA6B,CAClD,OAAO,mBAAmB,gCAAgC,CAC1D,OAAO,aAAa,+CAA+C,MAAM"}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { t as loadConfig } from "./config-loader-B6sJjXTv.mjs";
|
|
2
2
|
import { _ as errorUnexpected, a as errorContractValidationFailed, f as errorMigrationPlanningFailed, h as errorTargetMigrationNotSupported, l as errorFileNotFound, m as errorRuntime, t as CliStructuredError, v as mapMigrationToolsError } from "./cli-errors-D3_sMh2K.mjs";
|
|
3
|
-
import { t as assertFrameworkComponentsCompatible } from "./framework-components-
|
|
4
|
-
import { t as
|
|
5
|
-
import {
|
|
3
|
+
import { t as assertFrameworkComponentsCompatible } from "./framework-components-ChqVUxR-.mjs";
|
|
4
|
+
import { a as loadMigrationPackages, c as resolveContractPath, d as setCommandDescriptions, f as setCommandExamples, g as parseGlobalFlags, i as getTargetMigrations, l as resolveMigrationPaths, t as addGlobalOptions, y as formatStyledHeader } from "./command-helpers-BeZHkxV8.mjs";
|
|
5
|
+
import { t as TerminalUI } from "./terminal-ui-C_hFNbAn.mjs";
|
|
6
|
+
import { t as handleResult } from "./result-handler-rmPVKIP2.mjs";
|
|
7
|
+
import { i as toMigratePassInputs, n as toExtensionInputs, r as toExtensionMigrationsInputs } from "./extension-pack-inputs-C7xgE-vv.mjs";
|
|
6
8
|
import { Command } from "commander";
|
|
7
9
|
import { getEmittedArtifactPaths } from "@prisma-next/emitter";
|
|
8
10
|
import { notOk, ok } from "@prisma-next/utils/result";
|
|
@@ -269,28 +271,17 @@ async function executeMigrationPlanCommand(options, flags, ui, startTime) {
|
|
|
269
271
|
const message = error instanceof Error ? error.message : String(error);
|
|
270
272
|
return notOk(errorUnexpected(message, { why: `Unexpected error while loading migrations: ${message}` }));
|
|
271
273
|
}
|
|
274
|
+
const canonicalExtensionInputs = toExtensionInputs(config.extensionPacks ?? []);
|
|
272
275
|
const migratePass = await runContractSpaceMigratePass({
|
|
273
276
|
migrationsDir,
|
|
274
|
-
extensionPacks: (
|
|
275
|
-
const cs = pack.contractSpace;
|
|
276
|
-
return cs !== void 0 ? {
|
|
277
|
-
id: pack.id,
|
|
278
|
-
contractSpace: cs
|
|
279
|
-
} : { id: pack.id };
|
|
280
|
-
})
|
|
277
|
+
extensionPacks: toMigratePassInputs(canonicalExtensionInputs)
|
|
281
278
|
});
|
|
282
279
|
if (!flags.json && !flags.quiet) {
|
|
283
280
|
for (const drift of migratePass.drifts) if (drift.kind === "drift") ui.stderr(formatContractSpaceDriftWarning(drift));
|
|
284
281
|
}
|
|
285
282
|
const extensionMigrationsResult = await runContractSpaceExtensionMigrationsPass({
|
|
286
283
|
migrationsDir,
|
|
287
|
-
extensionPacks: (
|
|
288
|
-
const cs = pack.contractSpace;
|
|
289
|
-
return cs !== void 0 ? {
|
|
290
|
-
id: pack.id,
|
|
291
|
-
contractSpace: cs
|
|
292
|
-
} : { id: pack.id };
|
|
293
|
-
})
|
|
284
|
+
extensionPacks: toExtensionMigrationsInputs(canonicalExtensionInputs)
|
|
294
285
|
});
|
|
295
286
|
if (!flags.json && !flags.quiet) for (const entry of extensionMigrationsResult.emitted) ui.step(`Emitted ${entry.spaceId}/${entry.dirName}`);
|
|
296
287
|
if (fromHash === toStorageHash) return ok({
|
|
@@ -299,6 +290,7 @@ async function executeMigrationPlanCommand(options, flags, ui, startTime) {
|
|
|
299
290
|
from: fromHash,
|
|
300
291
|
to: toStorageHash,
|
|
301
292
|
operations: [],
|
|
293
|
+
emittedExtensionDirs: extensionMigrationsResult.emitted,
|
|
302
294
|
summary: "No changes detected between contracts",
|
|
303
295
|
timings: { total: Date.now() - startTime }
|
|
304
296
|
});
|
|
@@ -391,6 +383,7 @@ async function executeMigrationPlanCommand(options, flags, ui, startTime) {
|
|
|
391
383
|
to: toStorageHash,
|
|
392
384
|
dir: relative(process.cwd(), packageDir),
|
|
393
385
|
operations: [],
|
|
386
|
+
emittedExtensionDirs: extensionMigrationsResult.emitted,
|
|
394
387
|
pendingPlaceholders: true,
|
|
395
388
|
summary: "Planned migration with placeholder(s) — edit migration.ts then run `node migration.ts` to self-emit",
|
|
396
389
|
timings: { total: Date.now() - startTime }
|
|
@@ -407,8 +400,9 @@ async function executeMigrationPlanCommand(options, flags, ui, startTime) {
|
|
|
407
400
|
label: op.label,
|
|
408
401
|
operationClass: op.operationClass
|
|
409
402
|
})),
|
|
403
|
+
emittedExtensionDirs: extensionMigrationsResult.emitted,
|
|
410
404
|
...preview !== void 0 ? { preview } : {},
|
|
411
|
-
summary:
|
|
405
|
+
summary: buildPlanSummary(plannedOps.length, extensionMigrationsResult.emitted.length),
|
|
412
406
|
timings: { total: Date.now() - startTime }
|
|
413
407
|
});
|
|
414
408
|
} catch (error) {
|
|
@@ -437,16 +431,44 @@ function createMigrationPlanCommand() {
|
|
|
437
431
|
});
|
|
438
432
|
return command;
|
|
439
433
|
}
|
|
434
|
+
/**
|
|
435
|
+
* Compose the success-line summary so the cross-space side effect
|
|
436
|
+
* (extension-space migration packages materialised on disk during
|
|
437
|
+
* this `plan` run) is visible in the top line — not just in the
|
|
438
|
+
* step log above it.
|
|
439
|
+
*
|
|
440
|
+
* Example outputs:
|
|
441
|
+
* - `Planned 3 operation(s)` (app-space-only project)
|
|
442
|
+
* - `Planned 3 operation(s); materialised 1 extension-space migration` (one extension)
|
|
443
|
+
* - `Planned 3 operation(s); materialised 2 extension-space migrations` (two extensions)
|
|
444
|
+
*
|
|
445
|
+
* Locks AC3 at the summary-line level: a reader of the success line
|
|
446
|
+
* can tell that something happened beyond the app space.
|
|
447
|
+
*/
|
|
448
|
+
function buildPlanSummary(plannedOpsCount, emittedExtensionDirsCount) {
|
|
449
|
+
const base = `Planned ${plannedOpsCount} operation(s)`;
|
|
450
|
+
if (emittedExtensionDirsCount === 0) return base;
|
|
451
|
+
return `${base}; materialised ${emittedExtensionDirsCount} ${emittedExtensionDirsCount === 1 ? "extension-space migration" : "extension-space migrations"}`;
|
|
452
|
+
}
|
|
440
453
|
function formatMigrationPlanOutput(result, flags) {
|
|
441
454
|
const lines = [];
|
|
442
455
|
const useColor = flags.color !== false;
|
|
443
456
|
const green_ = useColor ? (s) => `\x1b[32m${s}\x1b[0m` : (s) => s;
|
|
444
457
|
const yellow_ = useColor ? (s) => `\x1b[33m${s}\x1b[0m` : (s) => s;
|
|
445
458
|
const dim_ = useColor ? (s) => `\x1b[2m${s}\x1b[0m` : (s) => s;
|
|
459
|
+
function appendEmittedExtensions() {
|
|
460
|
+
if (result.emittedExtensionDirs.length === 0) return;
|
|
461
|
+
lines.push("");
|
|
462
|
+
lines.push(dim_("Emitted extension migrations:"));
|
|
463
|
+
for (const entry of result.emittedExtensionDirs) lines.push(dim_(` ${entry.spaceId} → migrations/${entry.spaceId}/${entry.dirName}`));
|
|
464
|
+
lines.push("");
|
|
465
|
+
lines.push(`Next: review the extension migrations above, then run ${green_("prisma-next migration apply")}.`);
|
|
466
|
+
}
|
|
446
467
|
if (result.noOp) {
|
|
447
468
|
lines.push(`${green_("✔")} No changes detected`);
|
|
448
469
|
lines.push(dim_(` from: ${result.from}`));
|
|
449
470
|
lines.push(dim_(` to: ${result.to}`));
|
|
471
|
+
appendEmittedExtensions();
|
|
450
472
|
return lines.join("\n");
|
|
451
473
|
}
|
|
452
474
|
if (result.pendingPlaceholders) {
|
|
@@ -458,6 +480,7 @@ function formatMigrationPlanOutput(result, flags) {
|
|
|
458
480
|
lines.push("");
|
|
459
481
|
lines.push("Open migration.ts and replace each `placeholder(...)` call with your actual query.");
|
|
460
482
|
lines.push(`Then run: ${green_(`node ${result.dir ?? "<dir>"}/migration.ts`)}`);
|
|
483
|
+
appendEmittedExtensions();
|
|
461
484
|
return lines.join("\n");
|
|
462
485
|
}
|
|
463
486
|
lines.push(`${green_("✔")} ${result.summary}`);
|
|
@@ -467,8 +490,8 @@ function formatMigrationPlanOutput(result, flags) {
|
|
|
467
490
|
for (let i = 0; i < result.operations.length; i++) {
|
|
468
491
|
const op = result.operations[i];
|
|
469
492
|
const treeChar = i === result.operations.length - 1 ? "└" : "├";
|
|
470
|
-
const
|
|
471
|
-
lines.push(`${dim_(treeChar)}─ ${op.label}
|
|
493
|
+
const destructiveMarker = op.operationClass === "destructive" ? ` ${yellow_("(destructive)")}` : "";
|
|
494
|
+
lines.push(`${dim_(treeChar)}─ ${op.label}${destructiveMarker}`);
|
|
472
495
|
}
|
|
473
496
|
if (result.operations.some((op) => op.operationClass === "destructive")) {
|
|
474
497
|
lines.push("");
|
|
@@ -478,7 +501,8 @@ function formatMigrationPlanOutput(result, flags) {
|
|
|
478
501
|
}
|
|
479
502
|
lines.push(dim_(`from: ${result.from}`));
|
|
480
503
|
lines.push(dim_(`to: ${result.to}`));
|
|
481
|
-
if (result.dir) lines.push(dim_(`
|
|
504
|
+
if (result.dir) lines.push(dim_(`App space → ${result.dir}`));
|
|
505
|
+
for (const entry of result.emittedExtensionDirs) lines.push(dim_(`Extension space ${entry.spaceId} → migrations/${entry.spaceId}/${entry.dirName}`));
|
|
482
506
|
lines.push("");
|
|
483
507
|
lines.push(`Next: review ${green_(result.dir ?? "<dir>")} if needed, then run ${green_("prisma-next migration apply")}.`);
|
|
484
508
|
if (result.preview && result.preview.statements.length > 0) {
|
|
@@ -525,6 +549,6 @@ function resolveBundleByPrefix(bundles, needle) {
|
|
|
525
549
|
return notOk({ reason: "not-found" });
|
|
526
550
|
}
|
|
527
551
|
//#endregion
|
|
528
|
-
export {
|
|
552
|
+
export { formatMigrationPlanOutput as n, resolveBundleByPrefix as r, createMigrationPlanCommand as t };
|
|
529
553
|
|
|
530
|
-
//# sourceMappingURL=migration-plan-
|
|
554
|
+
//# sourceMappingURL=migration-plan-C6lVaHsO.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"migration-plan-C6lVaHsO.mjs","names":[],"sources":["../src/utils/contract-space-extension-migrations-pass.ts","../src/utils/contract-space-migrate-pass.ts","../src/commands/migration-plan.ts"],"sourcesContent":["import { materialiseExtensionMigrationPackageIfMissing } from '@prisma-next/migration-tools/io';\nimport type { MigrationMetadata } from '@prisma-next/migration-tools/metadata';\nimport type { MigrationOps } from '@prisma-next/migration-tools/package';\nimport {\n planAllSpaces,\n type SpacePlanOutput,\n spaceMigrationDirectory,\n} from '@prisma-next/migration-tools/spaces';\n\n/**\n * In-memory authored migration package shipped by an extension descriptor.\n * Mirrors `MigrationPackage` from `@prisma-next/migration-tools/io`\n * (the on-disk shape minus `dirPath`); redeclared structurally here so\n * the CLI helper does not couple to the SQL family's `ExtensionMigrationPackage`\n * type — any family that ships pre-built migration packages can pass them\n * through unchanged.\n */\nexport interface DescriptorMigrationPackage {\n readonly dirName: string;\n readonly metadata: MigrationMetadata;\n readonly ops: MigrationOps;\n}\n\n/**\n * Minimal descriptor view consumed by the migration-materialisation pass.\n * Mirrors {@link import('./contract-space-migrate-pass').MigrateExtensionInput}\n * but adds the `migrations` field — the canonical set of pre-built\n * migration packages the extension ships.\n */\nexport interface ExtensionMigrationsExtensionInput {\n readonly id: string;\n readonly contractSpace?: {\n readonly contractJson: unknown;\n readonly migrations: readonly DescriptorMigrationPackage[];\n readonly headRef: { readonly hash: string; readonly invariants: readonly string[] };\n };\n}\n\nexport interface ContractSpaceExtensionMigrationsPassInputs {\n readonly migrationsDir: string;\n readonly extensionPacks: ReadonlyArray<ExtensionMigrationsExtensionInput>;\n}\n\nexport interface ContractSpaceExtensionMigrationsPassResult {\n readonly emitted: readonly { readonly spaceId: string; readonly dirName: string }[];\n readonly skipped: readonly { readonly spaceId: string; readonly dirName: string }[];\n}\n\n/**\n * Materialise an extension's pre-built migration packages onto disk\n * under `migrations/<spaceId>/<dirName>/` for every package that does\n * not yet exist there.\n *\n * Helper-location pattern — the per-space \"planner\" for extension\n * spaces is a no-op that just returns the descriptor's `migrations`\n * verbatim; the value `planAllSpaces` brings to this consumer site is\n * **deterministic ordering** (alphabetical by spaceId) and\n * **duplicate-spaceId detection**. The actual write is performed via\n * `materialiseMigrationPackage` per package.\n *\n * Idempotent: an existing `migrations/<spaceId>/<dirName>/` is left\n * untouched and reported in `result.skipped` — the helper never\n * overwrites authored migration content, ensuring re-running\n * `migrate` does not corrupt or churn extension migration packages.\n *\n * Per-space artefacts (`contract.json`, `contract.d.ts`,\n * `refs/head.json`) are emitted by\n * {@link import('./contract-space-migrate-pass').runContractSpaceMigratePass}\n * separately — they cover the head-pointer side of the ledger. This\n * helper covers the migration-graph side.\n */\nexport async function runContractSpaceExtensionMigrationsPass(\n inputs: ContractSpaceExtensionMigrationsPassInputs,\n): Promise<ContractSpaceExtensionMigrationsPassResult> {\n const planInputs = inputs.extensionPacks\n .filter(\n (\n pack,\n ): pack is ExtensionMigrationsExtensionInput & {\n contractSpace: NonNullable<ExtensionMigrationsExtensionInput['contractSpace']>;\n } => pack.contractSpace !== undefined,\n )\n .map((pack) => ({\n spaceId: pack.id,\n priorContract: null,\n newContract: pack.contractSpace.contractJson,\n __migrations: pack.contractSpace.migrations,\n }));\n\n // Threading the descriptor's pre-built migrations into the\n // `planAllSpaces` callback by piggybacking on the input shape.\n // The framework helper is generic over the per-space planner output;\n // here the \"planner\" is a no-op that returns the descriptor's\n // `migrations` array. The benefit of routing through `planAllSpaces`\n // is duplicate-spaceId detection + alphabetical ordering — failures\n // there throw `MIGRATION.DUPLICATE_SPACE_ID` before any write.\n const planned: readonly SpacePlanOutput<DescriptorMigrationPackage>[] = planAllSpaces(\n planInputs,\n (input) =>\n (input as typeof input & { readonly __migrations: readonly DescriptorMigrationPackage[] })\n .__migrations,\n );\n\n const emitted: { spaceId: string; dirName: string }[] = [];\n const skipped: { spaceId: string; dirName: string }[] = [];\n\n for (const space of planned) {\n const spaceDir = spaceMigrationDirectory(inputs.migrationsDir, space.spaceId);\n for (const pkg of space.migrationPackages) {\n const { written } = await materialiseExtensionMigrationPackageIfMissing(spaceDir, pkg);\n if (written) {\n emitted.push({ spaceId: space.spaceId, dirName: pkg.dirName });\n } else {\n skipped.push({ spaceId: space.spaceId, dirName: pkg.dirName });\n }\n }\n }\n\n return { emitted, skipped };\n}\n","import {\n detectSpaceContractDrift,\n emitContractSpaceArtefacts,\n readContractSpaceHeadRef,\n type SpaceContractDriftResult,\n} from '@prisma-next/migration-tools/spaces';\n\n/**\n * Minimal descriptor view consumed by the migrate-time per-space pass.\n *\n * The CLI receives descriptors typed against the SQL family (or any other\n * family in the future); this helper only needs the structural shape of\n * `contractSpace`, so it accepts an `unknown`-typed `contractJson` and\n * a structurally-typed `headRef`. SQL-family callers pass the same\n * `Contract<SqlStorage>` value through unchanged — `emitContractSpaceArtefacts`\n * already serialises through `canonicalizeJson` and is framework-neutral.\n *\n * @see specs/framework-mechanism.spec.md § 3 — Per-space helper location.\n */\nexport interface MigrateExtensionInput {\n readonly id: string;\n readonly contractSpace?: {\n readonly contractJson: unknown;\n readonly headRef: { readonly hash: string; readonly invariants: readonly string[] };\n };\n}\n\n/**\n * Inputs needed to compose the migrate-time per-space pass at the CLI\n * surface — typically called once after the app-space migration package\n * has been written, regardless of whether the app-space had structural\n * changes (an extension bump alone should still re-pin its artefacts).\n */\nexport interface ContractSpaceMigratePassInputs {\n readonly migrationsDir: string;\n readonly extensionPacks: ReadonlyArray<MigrateExtensionInput>;\n}\n\nexport interface ContractSpaceMigratePassResult {\n readonly drifts: readonly SpaceContractDriftResult[];\n readonly emittedSpaceIds: readonly string[];\n}\n\n/**\n * Run drift detection + on-disk artefact emission for every loaded\n * extension space at `migrate` time.\n *\n * Per sub-spec § 3:\n *\n * - For each declared extension that exposes a `contractSpace`:\n * - Read the on-disk head hash from `migrations/<spaceId>/refs/head.json`\n * (returns `null` on first emit).\n * - Compare against the descriptor's `headRef.hash` via\n * `detectSpaceContractDrift`. The `kind` discriminant decides whether\n * the user sees a warning (`drift`), a no-op silent emit (`firstEmit`,\n * `noDrift`), or nothing at all.\n * - Always re-emit the on-disk artefacts (`contract.json`, `contract.d.ts`,\n * `refs/head.json`). The framework owns these files and the helper is\n * idempotent.\n *\n * Drift warnings are returned to the caller for formatting (TerminalUI,\n * structured-output envelope, etc.) — the helper does not print directly,\n * keeping it framework-neutral and unit-testable.\n *\n * Extension migration packages (the descriptor's pre-canned `migrations`\n * array → `migrations/<spaceId>/<dirName>/`) are intentionally not\n * materialised here — that interaction will be wired in a follow-on round\n * once the runner-side single-tx slice (sub-spec § 6) is in place.\n * On-disk artefacts are sufficient to lock the drift-warning behaviour\n * and the always-on re-emit AC for R2.\n *\n * @see specs/framework-mechanism.spec.md § 3 — Drift detection (T1.9).\n */\nexport async function runContractSpaceMigratePass(\n inputs: ContractSpaceMigratePassInputs,\n): Promise<ContractSpaceMigratePassResult> {\n const drifts: SpaceContractDriftResult[] = [];\n const emittedSpaceIds: string[] = [];\n\n for (const pack of inputs.extensionPacks) {\n if (pack.contractSpace === undefined) continue;\n const { contractJson, headRef } = pack.contractSpace;\n\n const onDiskHeadRef = await readContractSpaceHeadRef(inputs.migrationsDir, pack.id);\n const drift = detectSpaceContractDrift(pack.id, {\n descriptorHash: headRef.hash,\n priorHeadHash: onDiskHeadRef?.hash ?? null,\n });\n drifts.push(drift);\n\n await emitContractSpaceArtefacts(inputs.migrationsDir, pack.id, {\n contract: contractJson,\n contractDts: buildPlaceholderContractDts(pack.id),\n headRef: { hash: headRef.hash, invariants: headRef.invariants },\n });\n emittedSpaceIds.push(pack.id);\n }\n\n return { drifts, emittedSpaceIds };\n}\n\n/**\n * Format the user-facing drift warning for a single space. Callers\n * funnel this through their preferred output channel (TerminalUI line,\n * structured-output envelope `warnings[]`, etc.).\n *\n * Locks AM7 — drift warning surfaces the extension name and the diff\n * direction (descriptor → on-disk head).\n */\nexport function formatContractSpaceDriftWarning(drift: SpaceContractDriftResult): string {\n if (drift.kind !== 'drift') {\n throw new Error(`formatContractSpaceDriftWarning called with non-drift result: ${drift.kind}`);\n }\n return (\n `Contract-space drift detected for \"${drift.spaceId}\": descriptor hash ` +\n `${drift.descriptorHash} differs from on-disk head hash ${drift.priorHeadHash ?? '<none>'}. ` +\n `The on-disk artefacts under migrations/${drift.spaceId}/ will be refreshed to match the descriptor.`\n );\n}\n\n/**\n * Placeholder `.d.ts` content for an extension space's on-disk mirror.\n *\n * Rendering a fully-typed `.d.ts` for an extension contract requires the\n * SQL-family renderer with the codec / typemap registry threaded\n * through; that integration is tracked under sub-spec Open Question 3\n * (see `projects/extension-contract-spaces/specs/framework-mechanism.spec.md`).\n *\n * Until that ships, the on-disk `.d.ts` is a `@ts-nocheck` stub. The\n * spec gap closing alongside the typed renderer is **AC2 / AC14**\n * (byte-equivalence of per-space artefacts under `migrate`):\n * a placeholder cannot be byte-equal to a fully-rendered `.d.ts` from\n * the same descriptor, so AC2 / AC14 are PARTIAL today and become\n * fully-PASS once OQ3 closes.\n *\n * Scheduled to close in **M3** (cipherstash editor tooling) — that's\n * the milestone where the typed renderer gets its first real\n * extension-space consumer and the byte-equivalence guarantee is\n * practically required.\n */\nfunction buildPlaceholderContractDts(spaceId: string): string {\n return [\n '// @ts-nocheck',\n '/**',\n ` * Placeholder \\`.d.ts\\` for extension space \"${spaceId}\".`,\n ' *',\n ' * The framework re-emits this file on every `migrate` run alongside',\n ' * `contract.json` and `refs/head.json`. A typed `.d.ts` rendering',\n \" * pass for extension contracts is tracked under the project's open\",\n ' * questions; until that ships, consumers should import',\n ' * `contract.json` directly with `validateContract<…>(…)`.',\n ' */',\n 'export {};',\n '',\n ].join('\\n');\n}\n","import { readFile } from 'node:fs/promises';\nimport type { Contract } from '@prisma-next/contract/types';\nimport { getEmittedArtifactPaths } from '@prisma-next/emitter';\nimport {\n APP_SPACE_ID,\n createControlStack,\n hasOperationPreview,\n type MigrationPlanOperation,\n type OperationPreview,\n} from '@prisma-next/framework-components/control';\nimport { MigrationToolsError } from '@prisma-next/migration-tools/errors';\nimport { computeMigrationHash } from '@prisma-next/migration-tools/hash';\nimport { deriveProvidedInvariants } from '@prisma-next/migration-tools/invariants';\nimport {\n copyFilesWithRename,\n formatMigrationDirName,\n writeMigrationPackage,\n} from '@prisma-next/migration-tools/io';\nimport type { MigrationMetadata } from '@prisma-next/migration-tools/metadata';\nimport { findLatestMigration } from '@prisma-next/migration-tools/migration-graph';\nimport { writeMigrationTs } from '@prisma-next/migration-tools/migration-ts';\nimport { notOk, ok, type Result } from '@prisma-next/utils/result';\nimport { Command } from 'commander';\nimport { join, relative } from 'pathe';\nimport { loadConfig } from '../config-loader';\nimport {\n type CliErrorConflict,\n CliStructuredError,\n errorContractValidationFailed,\n errorFileNotFound,\n errorMigrationPlanningFailed,\n errorRuntime,\n errorTargetMigrationNotSupported,\n errorUnexpected,\n mapMigrationToolsError,\n} from '../utils/cli-errors';\nimport {\n addGlobalOptions,\n getTargetMigrations,\n loadMigrationPackages,\n resolveContractPath,\n resolveMigrationPaths,\n setCommandDescriptions,\n setCommandExamples,\n} from '../utils/command-helpers';\nimport { runContractSpaceExtensionMigrationsPass } from '../utils/contract-space-extension-migrations-pass';\nimport {\n formatContractSpaceDriftWarning,\n runContractSpaceMigratePass,\n} from '../utils/contract-space-migrate-pass';\nimport {\n toExtensionInputs,\n toExtensionMigrationsInputs,\n toMigratePassInputs,\n} from '../utils/extension-pack-inputs';\nimport { formatStyledHeader } from '../utils/formatters/styled';\nimport { assertFrameworkComponentsCompatible } from '../utils/framework-components';\nimport type { CommonCommandOptions } from '../utils/global-flags';\nimport { type GlobalFlags, parseGlobalFlags } from '../utils/global-flags';\nimport { handleResult } from '../utils/result-handler';\nimport { TerminalUI } from '../utils/terminal-ui';\n\ninterface MigrationPlanOptions extends CommonCommandOptions {\n readonly config?: string;\n readonly name?: string;\n readonly from?: string;\n}\n\nexport interface MigrationPlanResult {\n readonly ok: boolean;\n readonly noOp: boolean;\n readonly from: string | null;\n readonly to: string;\n readonly dir?: string;\n /**\n * Extension-space migration packages materialised onto disk during this\n * `plan` run. Each entry names a `migrations/<spaceId>/<dirName>/`\n * tree the framework wrote alongside the app-space migration directory.\n * Empty when the project has no extension packs declaring a contract\n * space, or when every extension-space package is already on disk.\n *\n * Surfacing these in the result (rather than only via `ui.step` log\n * lines) makes the cross-space side effect explicit to JSON consumers\n * and the success-summary renderer — the same multi-space side effect\n * that `migration apply` will replay.\n */\n readonly emittedExtensionDirs: readonly { readonly spaceId: string; readonly dirName: string }[];\n readonly operations: readonly {\n readonly id: string;\n readonly label: string;\n readonly operationClass: string;\n }[];\n /**\n * Family-agnostic textual preview of the migration plan operations.\n * Replaces the previous `sql?: readonly string[]` field; consumers should\n * read `result.preview?.statements`.\n */\n readonly preview?: OperationPreview;\n readonly summary: string;\n /**\n * When true, `migration.ts` was written but contains unfilled\n * `placeholder(...)` calls. The user must edit the file and then run\n * `node migration.ts` to self-emit `ops.json` / `migration.json`.\n */\n readonly pendingPlaceholders?: boolean;\n readonly timings: {\n readonly total: number;\n };\n}\n\nasync function executeMigrationPlanCommand(\n options: MigrationPlanOptions,\n flags: GlobalFlags,\n ui: TerminalUI,\n startTime: number,\n): Promise<Result<MigrationPlanResult, CliStructuredError>> {\n const config = await loadConfig(options.config);\n const { configPath, migrationsDir, appMigrationsDir, appMigrationsRelative } =\n resolveMigrationPaths(options.config, config);\n\n const contractPathAbsolute = resolveContractPath(config);\n const contractPath = relative(process.cwd(), contractPathAbsolute);\n\n if (!flags.json && !flags.quiet) {\n const details: Array<{ label: string; value: string }> = [\n { label: 'config', value: configPath },\n { label: 'contract', value: contractPath },\n { label: 'migrations', value: appMigrationsRelative },\n ];\n if (options.from) {\n details.push({ label: 'from', value: options.from });\n }\n if (options.name) {\n details.push({ label: 'name', value: options.name });\n }\n const header = formatStyledHeader({\n command: 'migration plan',\n description: 'Plan a migration from contract changes',\n url: 'https://pris.ly/migration-plan',\n details,\n flags,\n });\n ui.stderr(header);\n }\n\n // Load contract file (the \"to\" contract)\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 let toContractJson: Contract;\n try {\n toContractJson = JSON.parse(contractJsonContent) as Contract;\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 const rawStorageHash = toContractJson.storage?.storageHash;\n if (typeof rawStorageHash !== 'string') {\n return notOk(\n errorContractValidationFailed('Contract is missing storageHash', {\n where: { path: contractPathAbsolute },\n }),\n );\n }\n const toStorageHash = rawStorageHash;\n\n // Read existing migrations and determine \"from\" contract\n let fromContract: Contract | null = null;\n let fromHash: string | null = null;\n let fromContractSourceDir: string | null = null;\n\n try {\n const { bundles, graph } = await loadMigrationPackages(appMigrationsDir);\n\n if (options.from) {\n const resolved = resolveBundleByPrefix(bundles, options.from);\n if (!resolved.ok) {\n const f = resolved.failure;\n return notOk(\n f.reason === 'ambiguous'\n ? errorRuntime('Multiple matching migrations found', {\n why: `Prefix \"${options.from}\" matches ${f.count} migrations in ${appMigrationsRelative}`,\n fix: 'Provide a longer prefix to disambiguate, or omit --from to use the latest migration target.',\n })\n : errorRuntime('Starting contract not found', {\n why: `No migration with to hash matching \"${options.from}\" exists in ${appMigrationsRelative}`,\n fix: 'Check that the --from hash matches a known migration target hash, or omit --from to use the latest migration target.',\n }),\n );\n }\n fromHash = resolved.value.metadata.to;\n fromContract = resolved.value.metadata.toContract;\n fromContractSourceDir = resolved.value.dirPath;\n } else {\n const latestMigration = findLatestMigration(graph);\n if (latestMigration) {\n fromHash = latestMigration.to;\n const leafPkg = bundles.find(\n (p) => p.metadata.migrationHash === latestMigration.migrationHash,\n );\n if (leafPkg) {\n fromContract = leafPkg.metadata.toContract;\n fromContractSourceDir = leafPkg.dirPath;\n }\n }\n }\n } catch (error) {\n if (MigrationToolsError.is(error)) {\n return notOk(mapMigrationToolsError(error));\n }\n // Wrap unexpected (non-MigrationToolsError) failures from the migration\n // load phase in a structured CLI envelope. Letting them throw would\n // bypass `handleResult()` and crash the command — see CLI structured-\n // errors guideline (CliStructuredError + Result pattern).\n const message = error instanceof Error ? error.message : String(error);\n return notOk(\n errorUnexpected(message, {\n why: `Unexpected error while loading migrations: ${message}`,\n }),\n );\n }\n\n // Per-space migrate pass: drift detection + on-disk artefact emission for\n // every loaded extension that exposes a `contractSpace`. Runs *before*\n // the app-space no-op check so that an extension bump alone (with no\n // structural app-space change) still re-pins extension artefacts on\n // disk. Drift warnings are non-fatal — the on-disk artefacts are refreshed\n // and the user is notified that the bump is being captured.\n // Single descriptor-import boundary: every consumer of `extensionPacks`\n // goes through `toExtensionInputs` + a per-consumer adapter. AC11.\n const canonicalExtensionInputs = toExtensionInputs(config.extensionPacks ?? []);\n const migratePass = await runContractSpaceMigratePass({\n migrationsDir,\n extensionPacks: toMigratePassInputs(canonicalExtensionInputs),\n });\n if (!flags.json && !flags.quiet) {\n for (const drift of migratePass.drifts) {\n if (drift.kind === 'drift') {\n ui.stderr(formatContractSpaceDriftWarning(drift));\n }\n }\n }\n\n // Materialise descriptor-shipped migration packages onto disk under\n // `migrations/<spaceId>/<dirName>/` for any package not yet present.\n // Idempotent (existing dirs are left untouched).\n // Uses `planAllSpaces` for deterministic ordering + duplicate-spaceId\n // detection.\n const extensionMigrationsResult = await runContractSpaceExtensionMigrationsPass({\n migrationsDir,\n extensionPacks: toExtensionMigrationsInputs(canonicalExtensionInputs),\n });\n if (!flags.json && !flags.quiet) {\n for (const entry of extensionMigrationsResult.emitted) {\n ui.step(`Emitted ${entry.spaceId}/${entry.dirName}`);\n }\n }\n\n // Check for no-op (same hash means no changes)\n if (fromHash === toStorageHash) {\n const result: MigrationPlanResult = {\n ok: true,\n noOp: true,\n from: fromHash,\n to: toStorageHash,\n operations: [],\n emittedExtensionDirs: extensionMigrationsResult.emitted,\n summary: 'No changes detected between contracts',\n timings: { total: Date.now() - startTime },\n };\n return ok(result);\n }\n\n // Check target supports migrations\n const migrations = getTargetMigrations(config.target);\n if (!migrations) {\n return notOk(\n errorTargetMigrationNotSupported({\n why: `Target \"${config.target.id}\" does not support migrations`,\n }),\n );\n }\n const frameworkComponents = assertFrameworkComponentsCompatible(\n config.family.familyId,\n config.target.targetId,\n [config.target, config.adapter, ...(config.extensionPacks ?? [])],\n );\n\n // Build manifest and write migration package\n const timestamp = new Date();\n const slug = options.name ?? 'migration';\n const dirName = formatMigrationDirName(timestamp, slug);\n const packageDir = join(appMigrationsDir, dirName);\n\n const baseMetadata: Omit<MigrationMetadata, 'migrationHash' | 'providedInvariants'> = {\n from: fromHash,\n to: toStorageHash,\n fromContract,\n toContract: toContractJson,\n hints: {\n used: [],\n applied: [],\n plannerVersion: '2.0.0',\n },\n labels: [],\n createdAt: timestamp.toISOString(),\n };\n\n try {\n const stack = createControlStack(config);\n const familyInstance = config.family.create(stack);\n const planner = migrations.createPlanner(familyInstance);\n const fromSchema = migrations.contractToSchema(fromContract, frameworkComponents);\n const plannerResult = planner.plan({\n contract: toContractJson,\n schema: fromSchema,\n policy: { allowedOperationClasses: ['additive', 'widening', 'destructive', 'data'] },\n fromContract,\n frameworkComponents,\n spaceId: APP_SPACE_ID,\n });\n if (plannerResult.kind === 'failure') {\n return notOk(\n errorMigrationPlanningFailed({\n conflicts: plannerResult.conflicts as readonly CliErrorConflict[],\n }),\n );\n }\n\n // Accessing .operations triggers toOp() on each call. If any call\n // is a DataTransformCall with an unfilled placeholder stub, toOp()\n // throws PN-MIG-2001. We catch that here so the migration can still\n // be scaffolded with `ops: []`; the user fills the placeholder, then\n // re-runs `node migration.ts` to attest with the real ops.\n let plannedOps: readonly MigrationPlanOperation[] = [];\n let hasPlaceholders = false;\n try {\n plannedOps = plannerResult.plan.operations;\n if (plannedOps.length === 0) {\n return notOk(\n errorMigrationPlanningFailed({\n conflicts: [\n {\n kind: 'unsupportedChange',\n summary:\n 'Contract changed but planner produced no operations. ' +\n 'This indicates unsupported or ignored changes.',\n },\n ],\n }),\n );\n }\n } catch (e) {\n if (CliStructuredError.is(e) && e.domain === 'MIG' && e.code === '2001') {\n hasPlaceholders = true;\n } else {\n throw e;\n }\n }\n\n const migrationTsContent = plannerResult.plan.renderTypeScript();\n\n // Always-attest: compute migrationHash over (metadata, ops). When\n // placeholders blocked lowering, ops is `[]` and the hash is computed\n // over the empty list — re-emitting after the user fills the placeholder\n // produces a different hash (over the real ops). This is intentional;\n // there is no on-disk \"draft\" state.\n const opsForWrite = hasPlaceholders ? [] : plannedOps;\n const metadataWithInvariants: Omit<MigrationMetadata, 'migrationHash'> = {\n ...baseMetadata,\n providedInvariants: deriveProvidedInvariants(opsForWrite),\n };\n const metadata: MigrationMetadata = {\n ...metadataWithInvariants,\n migrationHash: computeMigrationHash(metadataWithInvariants, opsForWrite),\n };\n\n await writeMigrationPackage(packageDir, metadata, opsForWrite);\n const destinationArtifacts = getEmittedArtifactPaths(contractPathAbsolute);\n await copyFilesWithRename(packageDir, [\n { sourcePath: destinationArtifacts.jsonPath, destName: 'end-contract.json' },\n { sourcePath: destinationArtifacts.dtsPath, destName: 'end-contract.d.ts' },\n ]);\n if (fromContractSourceDir !== null) {\n const sourceArtifacts = getEmittedArtifactPaths(\n join(fromContractSourceDir, 'end-contract.json'),\n );\n await copyFilesWithRename(packageDir, [\n { sourcePath: sourceArtifacts.jsonPath, destName: 'start-contract.json' },\n { sourcePath: sourceArtifacts.dtsPath, destName: 'start-contract.d.ts' },\n ]);\n }\n await writeMigrationTs(packageDir, migrationTsContent);\n\n if (hasPlaceholders) {\n const result: MigrationPlanResult = {\n ok: true,\n noOp: false,\n from: fromHash,\n to: toStorageHash,\n dir: relative(process.cwd(), packageDir),\n operations: [],\n emittedExtensionDirs: extensionMigrationsResult.emitted,\n pendingPlaceholders: true,\n summary:\n 'Planned migration with placeholder(s) — edit migration.ts then run `node migration.ts` to self-emit',\n timings: { total: Date.now() - startTime },\n };\n return ok(result);\n }\n\n const preview = hasOperationPreview(familyInstance)\n ? familyInstance.toOperationPreview(plannedOps)\n : undefined;\n const result: MigrationPlanResult = {\n ok: true,\n noOp: false,\n from: fromHash,\n to: toStorageHash,\n dir: relative(process.cwd(), packageDir),\n operations: plannedOps.map((op) => ({\n id: op.id,\n label: op.label,\n operationClass: op.operationClass,\n })),\n emittedExtensionDirs: extensionMigrationsResult.emitted,\n ...(preview !== undefined ? { preview } : {}),\n summary: buildPlanSummary(plannedOps.length, extensionMigrationsResult.emitted.length),\n timings: { total: Date.now() - startTime },\n };\n return ok(result);\n } catch (error) {\n if (CliStructuredError.is(error)) {\n return notOk(error);\n }\n if (MigrationToolsError.is(error)) {\n return notOk(mapMigrationToolsError(error));\n }\n const message = error instanceof Error ? error.message : String(error);\n return notOk(\n errorUnexpected(message, {\n why: `Unexpected error during migration plan: ${message}`,\n }),\n );\n }\n}\n\nexport function createMigrationPlanCommand(): Command {\n const command = new Command('plan');\n setCommandDescriptions(\n command,\n 'Plan a migration from contract changes',\n 'Compares the emitted contract against the latest on-disk migration state and\\n' +\n 'produces a new migration package with the required operations. No database\\n' +\n 'connection is needed — this is a fully offline operation.',\n );\n setCommandExamples(command, [\n 'prisma-next migration plan',\n 'prisma-next migration plan --name add-users-table',\n ]);\n addGlobalOptions(command)\n .option('--config <path>', 'Path to prisma-next.config.ts')\n .option('--name <slug>', 'Name slug for the migration directory', 'migration')\n .option('--from <hash>', 'Explicit starting contract hash (overrides latest migration target)')\n .action(async (options: MigrationPlanOptions) => {\n const flags = parseGlobalFlags(options);\n const startTime = Date.now();\n\n const ui = new TerminalUI({ color: flags.color, interactive: flags.interactive });\n const result = await executeMigrationPlanCommand(options, flags, ui, startTime);\n\n const exitCode = handleResult(result, flags, ui, (planResult) => {\n if (flags.json) {\n ui.output(JSON.stringify(planResult, null, 2));\n } else if (!flags.quiet) {\n ui.log(formatMigrationPlanOutput(planResult, flags));\n }\n });\n\n process.exit(exitCode);\n });\n\n return command;\n}\n\n/**\n * Compose the success-line summary so the cross-space side effect\n * (extension-space migration packages materialised on disk during\n * this `plan` run) is visible in the top line — not just in the\n * step log above it.\n *\n * Example outputs:\n * - `Planned 3 operation(s)` (app-space-only project)\n * - `Planned 3 operation(s); materialised 1 extension-space migration` (one extension)\n * - `Planned 3 operation(s); materialised 2 extension-space migrations` (two extensions)\n *\n * Locks AC3 at the summary-line level: a reader of the success line\n * can tell that something happened beyond the app space.\n */\nfunction buildPlanSummary(plannedOpsCount: number, emittedExtensionDirsCount: number): string {\n const base = `Planned ${plannedOpsCount} operation(s)`;\n if (emittedExtensionDirsCount === 0) return base;\n const noun =\n emittedExtensionDirsCount === 1 ? 'extension-space migration' : 'extension-space migrations';\n return `${base}; materialised ${emittedExtensionDirsCount} ${noun}`;\n}\n\nexport function formatMigrationPlanOutput(result: MigrationPlanResult, flags: GlobalFlags): string {\n const lines: string[] = [];\n const useColor = flags.color !== false;\n\n const green_ = useColor ? (s: string) => `\\x1b[32m${s}\\x1b[0m` : (s: string) => s;\n const yellow_ = useColor ? (s: string) => `\\x1b[33m${s}\\x1b[0m` : (s: string) => s;\n const dim_ = useColor ? (s: string) => `\\x1b[2m${s}\\x1b[0m` : (s: string) => s;\n\n // Renders the extension-space materialisation block + canonical apply-step\n // hint shared by the no-op, placeholder, and full-plan branches. The app\n // space short-circuits do not skip it: an extension-only bump emits new\n // `migrations/<spaceId>/<dirName>/` directories on disk that the user\n // still has to apply, so the success line must surface them.\n function appendEmittedExtensions(): void {\n if (result.emittedExtensionDirs.length === 0) return;\n lines.push('');\n lines.push(dim_('Emitted extension migrations:'));\n for (const entry of result.emittedExtensionDirs) {\n lines.push(dim_(` ${entry.spaceId} → migrations/${entry.spaceId}/${entry.dirName}`));\n }\n lines.push('');\n lines.push(\n `Next: review the extension migrations above, then run ${green_('prisma-next migration apply')}.`,\n );\n }\n\n if (result.noOp) {\n lines.push(`${green_('✔')} No changes detected`);\n lines.push(dim_(` from: ${result.from}`));\n lines.push(dim_(` to: ${result.to}`));\n appendEmittedExtensions();\n return lines.join('\\n');\n }\n\n if (result.pendingPlaceholders) {\n lines.push(`${yellow_('⚠')} ${result.summary}`);\n lines.push('');\n lines.push(dim_(`from: ${result.from}`));\n lines.push(dim_(`to: ${result.to}`));\n if (result.dir) {\n lines.push(dim_(`dir: ${result.dir}`));\n }\n lines.push('');\n lines.push(\n 'Open migration.ts and replace each `placeholder(...)` call with your actual query.',\n );\n lines.push(`Then run: ${green_(`node ${result.dir ?? '<dir>'}/migration.ts`)}`);\n appendEmittedExtensions();\n return lines.join('\\n');\n }\n\n lines.push(`${green_('✔')} ${result.summary}`);\n lines.push('');\n\n if (result.operations.length > 0) {\n lines.push(dim_('│'));\n for (let i = 0; i < result.operations.length; i++) {\n const op = result.operations[i]!;\n const isLast = i === result.operations.length - 1;\n const treeChar = isLast ? '└' : '├';\n // operationClass tag is intentionally NOT inlined per spec:\n // a destructive footer warning still surfaces below this list.\n const destructiveMarker =\n op.operationClass === 'destructive' ? ` ${yellow_('(destructive)')}` : '';\n lines.push(`${dim_(treeChar)}─ ${op.label}${destructiveMarker}`);\n }\n\n const hasDestructive = result.operations.some((op) => op.operationClass === 'destructive');\n if (hasDestructive) {\n lines.push('');\n lines.push(\n `${yellow_('⚠')} This migration contains destructive operations that may cause data loss.`,\n );\n }\n lines.push('');\n }\n\n lines.push(dim_(`from: ${result.from}`));\n lines.push(dim_(`to: ${result.to}`));\n if (result.dir) {\n lines.push(dim_(`App space → ${result.dir}`));\n }\n // Per-space block: surface the extension-space directories materialised\n // alongside the app-space migration. Without this block the cross-space\n // side effect is invisible in the success summary (e2e finding F1).\n for (const entry of result.emittedExtensionDirs) {\n lines.push(\n dim_(`Extension space ${entry.spaceId} → migrations/${entry.spaceId}/${entry.dirName}`),\n );\n }\n\n lines.push('');\n // The \"Next:\" hint always points at the canonical apply path\n // (`prisma-next migration apply`) regardless of how many spaces\n // were materialised — `db update` is a dev-time convenience, not\n // the canonical replay step.\n lines.push(\n `Next: review ${green_(result.dir ?? '<dir>')} if needed, then run ${green_('prisma-next migration apply')}.`,\n );\n\n if (result.preview && result.preview.statements.length > 0) {\n // The non-empty length is already guaranteed by the surrounding check, so\n // a plain `every` here is equivalent to the helper in formatters/migrations.ts.\n const allSql = result.preview.statements.every((s) => s.language === 'sql');\n lines.push('');\n lines.push(dim_(allSql ? 'DDL preview' : 'Operation preview'));\n lines.push('');\n for (const statement of result.preview.statements) {\n const trimmed = statement.text.trim();\n if (!trimmed) continue;\n const line = statement.language === 'sql' && !trimmed.endsWith(';') ? `${trimmed};` : trimmed;\n lines.push(line);\n }\n }\n\n if (flags.verbose && result.timings) {\n lines.push('');\n lines.push(dim_(`Total time: ${result.timings.total}ms`));\n }\n\n return lines.join('\\n');\n}\n\nexport type PrefixResolutionFailure =\n | { reason: 'ambiguous'; count: number }\n | { reason: 'not-found' };\n\n/**\n * Resolve a migration package by **target contract hash** (`metadata.to`)\n * using exact match or prefix match.\n *\n * Note: matches `metadata.to` (the contract hash this migration produces),\n * not `metadata.migrationHash` (the package's content-addressed identity).\n * Tries exact match first, then prefix match (auto-prepending `sha256:` when\n * the needle omits the scheme). Returns the matched package on success, or a\n * discriminated failure indicating whether the prefix was ambiguous or simply\n * not found.\n *\n * @internal Exported for testing only.\n */\nexport function resolveBundleByPrefix<T extends { metadata: { to: string } }>(\n bundles: readonly T[],\n needle: string,\n): Result<T, PrefixResolutionFailure> {\n const exact = bundles.find((p) => p.metadata.to === needle);\n if (exact) return ok(exact);\n\n const prefixWithScheme = needle.startsWith('sha256:') ? needle : `sha256:${needle}`;\n const candidates = bundles.filter((p) => p.metadata.to.startsWith(prefixWithScheme));\n\n if (candidates.length === 1) return ok(candidates[0]!);\n if (candidates.length > 1) return notOk({ reason: 'ambiguous', count: candidates.length });\n return notOk({ reason: 'not-found' });\n}\n"],"mappings":"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"}
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import { t as loadConfig } from "./config-loader-B6sJjXTv.mjs";
|
|
2
2
|
import { _ as errorUnexpected, m as errorRuntime, v as mapMigrationToolsError } from "./cli-errors-D3_sMh2K.mjs";
|
|
3
|
-
import { t as
|
|
4
|
-
import {
|
|
5
|
-
import { t as
|
|
3
|
+
import { a as loadMigrationPackages, d as setCommandDescriptions, f as setCommandExamples, g as parseGlobalFlags, h as toStructuralEdge, l as resolveMigrationPaths, m as toPathDecisionResult, n as collectDeclaredInvariants, o as maskConnectionUrl, s as readContractEnvelope, t as addGlobalOptions, y as formatStyledHeader } from "./command-helpers-BeZHkxV8.mjs";
|
|
4
|
+
import { t as TerminalUI } from "./terminal-ui-C_hFNbAn.mjs";
|
|
5
|
+
import { t as handleResult } from "./result-handler-rmPVKIP2.mjs";
|
|
6
|
+
import { a as buildContractSpaceAggregate, t as createControlClient } from "./client-qVH-rEgd.mjs";
|
|
6
7
|
import { Command } from "commander";
|
|
7
8
|
import { ifDefined } from "@prisma-next/utils/defined";
|
|
8
9
|
import { notOk, ok } from "@prisma-next/utils/result";
|
|
10
|
+
import { createControlStack } from "@prisma-next/framework-components/control";
|
|
9
11
|
import { findPath, findPathWithDecision, findReachableLeaves } from "@prisma-next/migration-tools/migration-graph";
|
|
10
12
|
import { bold, cyan, dim, magenta, yellow } from "colorette";
|
|
13
|
+
import { graphWalkStrategy } from "@prisma-next/migration-tools/aggregate";
|
|
11
14
|
import { EMPTY_CONTRACT_HASH } from "@prisma-next/migration-tools/constants";
|
|
12
15
|
import { MigrationToolsError, errorNoInvariantPath, errorUnknownInvariant } from "@prisma-next/migration-tools/errors";
|
|
13
16
|
import { readRefs, resolveRef } from "@prisma-next/migration-tools/refs";
|
|
@@ -1086,6 +1089,22 @@ function isLinearGraph(graph) {
|
|
|
1086
1089
|
}
|
|
1087
1090
|
//#endregion
|
|
1088
1091
|
//#region src/commands/migration-status.ts
|
|
1092
|
+
/**
|
|
1093
|
+
* Sum per-space `pendingCount` into a cross-space total, but only when
|
|
1094
|
+
* every loaded space reports a defined `pendingCount`. Returns
|
|
1095
|
+
* `undefined` if any space is on the marker-unknown / offline path
|
|
1096
|
+
* (where `pendingCount` is intentionally absent), so JSON consumers can
|
|
1097
|
+
* distinguish "no pending" from "unknown".
|
|
1098
|
+
*/
|
|
1099
|
+
function computeTotalPendingAcrossSpaces(spaces) {
|
|
1100
|
+
if (spaces.length === 0) return void 0;
|
|
1101
|
+
let total = 0;
|
|
1102
|
+
for (const s of spaces) {
|
|
1103
|
+
if (s.pendingCount === void 0) return void 0;
|
|
1104
|
+
total += s.pendingCount;
|
|
1105
|
+
}
|
|
1106
|
+
return total;
|
|
1107
|
+
}
|
|
1089
1108
|
function summarizeOps(ops) {
|
|
1090
1109
|
if (ops.length === 0) return {
|
|
1091
1110
|
summary: "0 ops",
|
|
@@ -1236,9 +1255,94 @@ function determineLimit(opts) {
|
|
|
1236
1255
|
if (Number.isNaN(parsed)) return DEFAULT_LIMIT;
|
|
1237
1256
|
return parsed;
|
|
1238
1257
|
}
|
|
1258
|
+
/**
|
|
1259
|
+
* Build the aggregate enumeration of contract spaces for the status
|
|
1260
|
+
* output. Loads the aggregate from disk (lossy on failure — extension
|
|
1261
|
+
* spaces are simply omitted, the existing single-space app behaviour
|
|
1262
|
+
* keeps working), reads per-space marker rows when online, and uses
|
|
1263
|
+
* {@link graphWalkStrategy} to compute each space's pending count.
|
|
1264
|
+
*
|
|
1265
|
+
* Sub-spec § `migration status` semantics — the aggregate-walking
|
|
1266
|
+
* version reports per-space marker + pending state alongside the
|
|
1267
|
+
* cross-space totals.
|
|
1268
|
+
*/
|
|
1269
|
+
async function loadAggregateStatusSpaces(args) {
|
|
1270
|
+
const loaded = await buildContractSpaceAggregate({
|
|
1271
|
+
targetId: args.targetId,
|
|
1272
|
+
migrationsDir: args.migrationsDir,
|
|
1273
|
+
appContract: args.validateContract(args.appContractRaw),
|
|
1274
|
+
extensionPacks: args.extensionPacks,
|
|
1275
|
+
validateContract: args.validateContract
|
|
1276
|
+
});
|
|
1277
|
+
if (!loaded.ok) return [];
|
|
1278
|
+
const aggregate = loaded.value;
|
|
1279
|
+
const orderedMembers = [...aggregate.extensions, aggregate.app];
|
|
1280
|
+
const rows = [];
|
|
1281
|
+
for (const member of orderedMembers) {
|
|
1282
|
+
const liveMarker = args.markersBySpace?.get(member.spaceId) ?? null;
|
|
1283
|
+
const isApp = member.spaceId === aggregate.app.spaceId;
|
|
1284
|
+
if (member.migrations.graph.nodes.size === 0) {
|
|
1285
|
+
rows.push({
|
|
1286
|
+
spaceId: member.spaceId,
|
|
1287
|
+
kind: isApp ? "app" : "extension",
|
|
1288
|
+
headHash: member.headRef.hash,
|
|
1289
|
+
...args.markersBySpace !== null ? {
|
|
1290
|
+
markerHash: liveMarker?.storageHash ?? null,
|
|
1291
|
+
status: member.headRef.hash === EMPTY_CONTRACT_HASH ? "up-to-date" : "never-planned",
|
|
1292
|
+
pendingCount: 0
|
|
1293
|
+
} : {}
|
|
1294
|
+
});
|
|
1295
|
+
continue;
|
|
1296
|
+
}
|
|
1297
|
+
if (args.markersBySpace === null) {
|
|
1298
|
+
rows.push({
|
|
1299
|
+
spaceId: member.spaceId,
|
|
1300
|
+
kind: isApp ? "app" : "extension",
|
|
1301
|
+
headHash: member.headRef.hash
|
|
1302
|
+
});
|
|
1303
|
+
continue;
|
|
1304
|
+
}
|
|
1305
|
+
const walked = graphWalkStrategy({
|
|
1306
|
+
aggregateTargetId: aggregate.targetId,
|
|
1307
|
+
member,
|
|
1308
|
+
currentMarker: liveMarker
|
|
1309
|
+
});
|
|
1310
|
+
let pendingCount = 0;
|
|
1311
|
+
let status;
|
|
1312
|
+
if (walked.kind === "ok") {
|
|
1313
|
+
pendingCount = walked.result.plan.operations.length;
|
|
1314
|
+
if (liveMarker === null) status = pendingCount === 0 ? "no-marker" : "pending";
|
|
1315
|
+
else status = pendingCount === 0 ? "up-to-date" : "pending";
|
|
1316
|
+
} else status = "unreachable";
|
|
1317
|
+
rows.push({
|
|
1318
|
+
spaceId: member.spaceId,
|
|
1319
|
+
kind: isApp ? "app" : "extension",
|
|
1320
|
+
headHash: member.headRef.hash,
|
|
1321
|
+
markerHash: liveMarker?.storageHash ?? null,
|
|
1322
|
+
pendingCount,
|
|
1323
|
+
...status ? { status } : {}
|
|
1324
|
+
});
|
|
1325
|
+
}
|
|
1326
|
+
return rows;
|
|
1327
|
+
}
|
|
1328
|
+
/**
|
|
1329
|
+
* Read the raw contract.json bytes from disk for the aggregate
|
|
1330
|
+
* loader. Returns `null` if the file is missing or unparseable —
|
|
1331
|
+
* the existing `readContractEnvelope` path will report the same
|
|
1332
|
+
* problem via a status diagnostic, no need to double-surface.
|
|
1333
|
+
*/
|
|
1334
|
+
async function loadContractRawSafely(config) {
|
|
1335
|
+
try {
|
|
1336
|
+
const path = (await import("./command-helpers-BeZHkxV8.mjs").then((n) => n.r)).resolveContractPath(config);
|
|
1337
|
+
const raw = await (await import("node:fs/promises")).readFile(path, "utf-8");
|
|
1338
|
+
return JSON.parse(raw);
|
|
1339
|
+
} catch {
|
|
1340
|
+
return null;
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1239
1343
|
async function executeMigrationStatusCommand(options, flags, ui) {
|
|
1240
1344
|
const config = await loadConfig(options.config);
|
|
1241
|
-
const { configPath, appMigrationsDir, appMigrationsRelative, refsDir } = resolveMigrationPaths(options.config, config);
|
|
1345
|
+
const { configPath, appMigrationsDir, appMigrationsRelative, migrationsDir, refsDir } = resolveMigrationPaths(options.config, config);
|
|
1242
1346
|
const dbConnection = options.db ?? config.db?.connection;
|
|
1243
1347
|
const hasDriver = !!config.driver;
|
|
1244
1348
|
let activeRefName;
|
|
@@ -1349,6 +1453,7 @@ async function executeMigrationStatusCommand(options, flags, ui) {
|
|
|
1349
1453
|
let markerHash;
|
|
1350
1454
|
let markerInvariants = [];
|
|
1351
1455
|
let mode = "offline";
|
|
1456
|
+
let allMarkers = null;
|
|
1352
1457
|
if (dbConnection && hasDriver) {
|
|
1353
1458
|
const client = createControlClient({
|
|
1354
1459
|
family: config.family,
|
|
@@ -1363,12 +1468,36 @@ async function executeMigrationStatusCommand(options, flags, ui) {
|
|
|
1363
1468
|
markerHash = marker?.storageHash;
|
|
1364
1469
|
markerInvariants = marker?.invariants ?? [];
|
|
1365
1470
|
mode = "online";
|
|
1471
|
+
try {
|
|
1472
|
+
allMarkers = await client.readAllMarkers();
|
|
1473
|
+
} catch {
|
|
1474
|
+
allMarkers = null;
|
|
1475
|
+
}
|
|
1366
1476
|
} catch {
|
|
1367
1477
|
if (!flags.json && !flags.quiet) ui.warn("Could not connect to database — showing offline status");
|
|
1368
1478
|
} finally {
|
|
1369
1479
|
await client.close();
|
|
1370
1480
|
}
|
|
1371
1481
|
}
|
|
1482
|
+
const contractRawForAggregate = await loadContractRawSafely(config);
|
|
1483
|
+
let aggregateSpaces = [];
|
|
1484
|
+
if (contractRawForAggregate !== null) {
|
|
1485
|
+
const stack = createControlStack(config);
|
|
1486
|
+
const familyInstance = config.family.create(stack);
|
|
1487
|
+
try {
|
|
1488
|
+
aggregateSpaces = await loadAggregateStatusSpaces({
|
|
1489
|
+
targetId: config.target.targetId,
|
|
1490
|
+
migrationsDir,
|
|
1491
|
+
appContractRaw: contractRawForAggregate,
|
|
1492
|
+
extensionPacks: config.extensionPacks ?? [],
|
|
1493
|
+
validateContract: (json) => familyInstance.validateContract(json),
|
|
1494
|
+
markersBySpace: allMarkers
|
|
1495
|
+
});
|
|
1496
|
+
} catch {
|
|
1497
|
+
aggregateSpaces = [];
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1500
|
+
const totalPendingAcrossSpaces = computeTotalPendingAcrossSpaces(aggregateSpaces);
|
|
1372
1501
|
if (activeRefEntry && activeRefEntry.invariants.length > 0) {
|
|
1373
1502
|
const declared = collectDeclaredInvariants(graph);
|
|
1374
1503
|
const known = new Set(declared);
|
|
@@ -1520,7 +1649,9 @@ async function executeMigrationStatusCommand(options, flags, ui) {
|
|
|
1520
1649
|
bundles,
|
|
1521
1650
|
edgeStatuses,
|
|
1522
1651
|
...ifDefined("activeRefHash", activeRefHash),
|
|
1523
|
-
...ifDefined("activeRefName", activeRefName)
|
|
1652
|
+
...ifDefined("activeRefName", activeRefName),
|
|
1653
|
+
spaces: aggregateSpaces,
|
|
1654
|
+
...ifDefined("totalPendingAcrossSpaces", totalPendingAcrossSpaces)
|
|
1524
1655
|
});
|
|
1525
1656
|
}
|
|
1526
1657
|
function createMigrationStatusCommand() {
|
|
@@ -1599,8 +1730,31 @@ function formatStatusSummary(result, colorize) {
|
|
|
1599
1730
|
lines.push(`${c(yellow, "⚠")} ${diag.message}`);
|
|
1600
1731
|
for (const hint of diag.hints) lines.push(` ${c(dim, hint)}`);
|
|
1601
1732
|
}
|
|
1733
|
+
if (result.spaces?.some((s) => s.kind === "extension")) {
|
|
1734
|
+
const total = result.totalPendingAcrossSpaces ?? 0;
|
|
1735
|
+
lines.push("");
|
|
1736
|
+
lines.push(c(dim, "spaces"));
|
|
1737
|
+
for (const space of result.spaces) lines.push(formatSpaceLine(space, c));
|
|
1738
|
+
if (total > 0) {
|
|
1739
|
+
lines.push("");
|
|
1740
|
+
lines.push(`${c(yellow, "⧗")} ${total} pending migration(s) across ${result.spaces.length} space(s) — run 'prisma-next migration apply' to apply`);
|
|
1741
|
+
}
|
|
1742
|
+
}
|
|
1602
1743
|
return lines.join("\n");
|
|
1603
1744
|
}
|
|
1745
|
+
function formatSpaceLine(space, c) {
|
|
1746
|
+
const glyph = (() => {
|
|
1747
|
+
if (space.status === "up-to-date" || space.status === "no-marker") return c(cyan, "✓");
|
|
1748
|
+
if (space.status === "pending") return c(yellow, "⧗");
|
|
1749
|
+
if (space.status === "unreachable" || space.status === "never-planned") return c(magenta, "✗");
|
|
1750
|
+
return " ";
|
|
1751
|
+
})();
|
|
1752
|
+
const tag = space.kind === "app" ? "[app]" : "[ext]";
|
|
1753
|
+
const head = space.headHash.slice(0, 8);
|
|
1754
|
+
const marker = space.markerHash === void 0 ? "(unknown)" : space.markerHash === null ? "(no marker)" : space.markerHash.slice(0, 8);
|
|
1755
|
+
const pending = space.pendingCount === void 0 ? "" : space.pendingCount === 0 ? c(dim, " (up to date)") : c(yellow, ` (${space.pendingCount} pending)`);
|
|
1756
|
+
return ` ${glyph} ${c(dim, tag)} ${space.spaceId} → head ${c(dim, head)}, marker ${c(dim, marker)}${pending}`;
|
|
1757
|
+
}
|
|
1604
1758
|
function formatInvariantList(ids) {
|
|
1605
1759
|
return ids.length === 0 ? "(none)" : ids.join(", ");
|
|
1606
1760
|
}
|
|
@@ -1613,6 +1767,6 @@ function summarizeRefDistance(graph, markerHash, refHash, refName) {
|
|
|
1613
1767
|
return `No path between database marker and ref "${refName}" target`;
|
|
1614
1768
|
}
|
|
1615
1769
|
//#endregion
|
|
1616
|
-
export {
|
|
1770
|
+
export { loadAggregateStatusSpaces as a, formatStatusSummary as i, createMigrationStatusCommand as n, deriveEdgeStatuses as r, computeTotalPendingAcrossSpaces as t };
|
|
1617
1771
|
|
|
1618
|
-
//# sourceMappingURL=migration-status-
|
|
1772
|
+
//# sourceMappingURL=migration-status-CZ-D5k7k.mjs.map
|