@prisma-next/cli 0.5.0-dev.67 → 0.5.0-dev.68
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/cli.mjs.map +1 -1
- package/dist/client-0ZX24FXF.mjs +1398 -0
- package/dist/client-0ZX24FXF.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 +2 -2
- package/dist/commands/db-sign.mjs +2 -2
- 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.d.mts.map +1 -1
- package/dist/commands/db-verify.mjs +1 -320
- package/dist/commands/migration-apply.mjs +11 -11
- package/dist/commands/migration-apply.mjs.map +1 -1
- package/dist/commands/migration-new.mjs +5 -5
- package/dist/commands/migration-new.mjs.map +1 -1
- package/dist/commands/migration-plan.d.mts.map +1 -1
- package/dist/commands/migration-plan.mjs +1 -344
- package/dist/commands/migration-ref.d.mts +1 -1
- package/dist/commands/migration-ref.mjs +1 -1
- package/dist/commands/migration-show.d.mts +1 -1
- package/dist/commands/migration-show.d.mts.map +1 -1
- package/dist/commands/migration-show.mjs +8 -7
- package/dist/commands/migration-show.mjs.map +1 -1
- package/dist/commands/migration-status.mjs +1 -1
- package/dist/{contract-emit-B0nGrDtk.mjs → contract-emit-DkMqO7f2.mjs} +2 -2
- package/dist/{contract-emit-B0nGrDtk.mjs.map → contract-emit-DkMqO7f2.mjs.map} +1 -1
- package/dist/{contract-infer-BjMJaOOa.mjs → contract-infer-BDKAE0B0.mjs} +3 -3
- package/dist/{contract-infer-BjMJaOOa.mjs.map → contract-infer-BDKAE0B0.mjs.map} +1 -1
- package/dist/db-verify-B4TdDKOI.mjs +403 -0
- package/dist/db-verify-B4TdDKOI.mjs.map +1 -0
- package/dist/exports/control-api.d.mts +201 -3
- package/dist/exports/control-api.d.mts.map +1 -1
- package/dist/exports/control-api.mjs +2 -2
- package/dist/exports/index.d.mts.map +1 -1
- package/dist/exports/index.mjs +17 -17
- package/dist/exports/index.mjs.map +1 -1
- package/dist/exports/init-output.mjs +1 -1
- package/dist/{init-C3qdc0Sh.mjs → init-Deo7U8_U.mjs} +2 -2
- package/dist/{init-C3qdc0Sh.mjs.map → init-Deo7U8_U.mjs.map} +1 -1
- package/dist/{inspect-live-schema-33rxnu0K.mjs → inspect-live-schema-BAgQMYpD.mjs} +3 -3
- package/dist/{inspect-live-schema-33rxnu0K.mjs.map → inspect-live-schema-BAgQMYpD.mjs.map} +1 -1
- package/dist/{migration-command-scaffold-DQo4R0XT.mjs → migration-command-scaffold-B8J702Uh.mjs} +3 -3
- package/dist/{migration-command-scaffold-DQo4R0XT.mjs.map → migration-command-scaffold-B8J702Uh.mjs.map} +1 -1
- package/dist/migration-plan-BcKNnTM7.mjs +530 -0
- package/dist/migration-plan-BcKNnTM7.mjs.map +1 -0
- package/dist/{migration-status-C_2FSkbf.mjs → migration-status-CjwB2of-.mjs} +6 -6
- package/dist/{migration-status-C_2FSkbf.mjs.map → migration-status-CjwB2of-.mjs.map} +1 -1
- package/dist/{output-BTgnZ5c_.mjs → output-DnjfCC_u.mjs} +1 -1
- package/dist/{output-BTgnZ5c_.mjs.map → output-DnjfCC_u.mjs.map} +1 -1
- package/dist/{result-handler-C0QeiqKO.mjs → result-handler-DWb1rFS-.mjs} +18 -3
- package/dist/result-handler-DWb1rFS-.mjs.map +1 -0
- package/package.json +15 -15
- package/src/commands/db-init.ts +13 -3
- package/src/commands/db-update.ts +7 -3
- package/src/commands/db-verify.ts +47 -15
- package/src/commands/init/index.ts +1 -1
- package/src/commands/init/init.ts +2 -2
- package/src/commands/migration-apply.ts +9 -9
- package/src/commands/migration-new.ts +4 -4
- package/src/commands/migration-plan.ts +66 -9
- package/src/commands/migration-show.ts +7 -5
- package/src/commands/migration-status.ts +3 -3
- package/src/control-api/client.ts +42 -0
- package/src/control-api/operations/db-apply-aggregate.ts +446 -0
- package/src/control-api/operations/db-init.ts +51 -258
- package/src/control-api/operations/db-update.ts +66 -188
- package/src/control-api/operations/db-verify.ts +342 -0
- package/src/control-api/types.ts +56 -0
- package/src/exports/control-api.ts +13 -2
- package/src/load-ts-contract.ts +28 -26
- package/src/utils/combine-schema-results.ts +84 -0
- package/src/utils/command-helpers.ts +24 -2
- package/src/utils/contract-space-aggregate-loader.ts +236 -0
- package/src/utils/contract-space-extension-migrations-pass.ts +120 -0
- package/src/utils/contract-space-migrate-pass.ts +156 -0
- package/dist/client-CW1hcUtM.mjs +0 -1025
- package/dist/client-CW1hcUtM.mjs.map +0 -1
- package/dist/commands/db-verify.mjs.map +0 -1
- package/dist/commands/migration-plan.mjs.map +0 -1
- package/dist/result-handler-C0QeiqKO.mjs.map +0 -1
- /package/dist/{cli-errors-BWn943z2.d.mts → cli-errors-QH8kf-C2.d.mts} +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"output-
|
|
1
|
+
{"version":3,"file":"output-DnjfCC_u.mjs","names":[],"sources":["../src/commands/init/output.ts"],"sourcesContent":["import { type } from 'arktype';\nimport type { GlobalFlags } from '../../utils/global-flags';\nimport type { TerminalUI } from '../../utils/terminal-ui';\n\n/**\n * arktype schema for the structured success document `init --json` writes\n * to stdout (FR1.5). The same shape backs the human-readable outro\n * renderer (FR10), so the two output modes carry identical information.\n *\n * `target` is normalised to the user-facing flag value (`mongodb` rather\n * than the internal `mongo`) so consumers can round-trip the document\n * straight into a follow-up `--target` invocation.\n *\n * The `ok: true` literal is the documented success/error discriminator —\n * see [Style Guide § JSON Semantics](../../../../../../../docs/CLI%20Style%20Guide.md#json-semantics).\n * Error envelopes (`CliErrorEnvelope`) carry `ok: false` so consumers can\n * branch with `if (doc.ok)` without inspecting the rest of the structure.\n */\nexport const InitOutputSchema = type({\n ok: 'true',\n target: \"'postgres'|'mongodb'\",\n authoring: \"'psl'|'typescript'\",\n schemaPath: 'string',\n filesWritten: 'string[]',\n /**\n * FR9.1 — files removed from disk during this run. Populated only on\n * re-init when previously-emitted contract artefacts (`contract.json`,\n * `contract.d.ts`, `start-/end-contract.*`, `ops.json`,\n * `migration.json`) were left behind by an earlier run. Empty on a\n * green-field init.\n */\n filesDeleted: 'string[]',\n packagesInstalled: {\n skipped: 'boolean',\n deps: 'string[]',\n devDeps: 'string[]',\n },\n contractEmitted: 'boolean',\n nextSteps: 'string[]',\n warnings: 'string[]',\n});\n\nexport type InitOutput = typeof InitOutputSchema.infer;\n\n/**\n * Serialises the output document for `--json`. Sorted keys are not enforced\n * — `JSON.stringify` preserves insertion order, and the schema field order\n * is the documented order, which matches what users will see when they\n * `jq .` the result.\n */\nexport function formatInitJson(output: InitOutput): string {\n return JSON.stringify(output, null, 2);\n}\n\n/**\n * Renders the human-readable outro on stderr (FR10.1). Re-uses the same\n * data structure as the JSON output so the two stay in lock-step.\n *\n * Warnings come before \"Next steps\" because they describe state the user\n * needs to be aware of before acting on the next-steps list.\n */\nexport function renderInitOutro(ui: TerminalUI, output: InitOutput, flags: GlobalFlags): void {\n if (flags.quiet || flags.json) {\n return;\n }\n\n for (const warning of output.warnings) {\n ui.warn(warning);\n }\n\n const lines: string[] = [];\n lines.push(`Target: ${output.target}`);\n lines.push(`Authoring: ${output.authoring}`);\n lines.push(`Schema: ${output.schemaPath}`);\n lines.push('');\n lines.push('Files written:');\n for (const file of output.filesWritten) {\n lines.push(` • ${file}`);\n }\n\n if (output.filesDeleted.length > 0) {\n lines.push('');\n lines.push('Files deleted (stale contract artefacts):');\n for (const file of output.filesDeleted) {\n lines.push(` • ${file}`);\n }\n }\n\n if (!output.packagesInstalled.skipped) {\n lines.push('');\n lines.push('Packages installed:');\n for (const dep of output.packagesInstalled.deps) {\n lines.push(` • ${dep}`);\n }\n for (const dep of output.packagesInstalled.devDeps) {\n lines.push(` • ${dep} (dev)`);\n }\n }\n\n lines.push('');\n lines.push('Next steps:');\n for (const step of output.nextSteps) {\n lines.push(` ${step}`);\n }\n\n ui.note(lines.join('\\n'), 'Done');\n}\n\n/**\n * Builds the `nextSteps` array from the resolved scaffold state. Steps are\n * ordered by the workflow a user needs to follow: configure connection →\n * (emit if not yet done) → run a starter query → docs / agent skill.\n *\n * The strings are stable and human-readable; agents wanting to act on them\n * should match on substrings (e.g. \"DATABASE_URL\") rather than exact text,\n * since copy may evolve.\n */\nexport function buildNextSteps(options: {\n readonly target: 'postgres' | 'mongodb';\n readonly contractEmitted: boolean;\n readonly emitCommand: string;\n readonly schemaPath: string;\n}): string[] {\n const steps: string[] = [];\n steps.push('1. Set DATABASE_URL in your environment (export it or add it to .env).');\n if (!options.contractEmitted) {\n steps.push(`2. Emit the contract: \\`${options.emitCommand}\\``);\n steps.push(`3. Edit your schema at ${options.schemaPath}, then re-run the emit command.`);\n steps.push(\n '4. Open prisma-next.md for a quick reference on how to write your first typed query.',\n );\n steps.push(\n '5. The .agents/skills/prisma-next/SKILL.md file is wired up for AI-coding agents in this project.',\n );\n } else {\n steps.push(\n `2. Edit your schema at ${options.schemaPath}, then re-run \\`${options.emitCommand}\\`.`,\n );\n steps.push(\n '3. Open prisma-next.md for a quick reference on how to write your first typed query.',\n );\n steps.push(\n '4. The .agents/skills/prisma-next/SKILL.md file is wired up for AI-coding agents in this project.',\n );\n }\n return steps;\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAkBA,MAAa,mBAAmB,KAAK;CACnC,IAAI;CACJ,QAAQ;CACR,WAAW;CACX,YAAY;CACZ,cAAc;;;;;;;;CAQd,cAAc;CACd,mBAAmB;EACjB,SAAS;EACT,MAAM;EACN,SAAS;EACV;CACD,iBAAiB;CACjB,WAAW;CACX,UAAU;CACX,CAAC;;;;;;;AAUF,SAAgB,eAAe,QAA4B;CACzD,OAAO,KAAK,UAAU,QAAQ,MAAM,EAAE;;;;;;;;;AAUxC,SAAgB,gBAAgB,IAAgB,QAAoB,OAA0B;CAC5F,IAAI,MAAM,SAAS,MAAM,MACvB;CAGF,KAAK,MAAM,WAAW,OAAO,UAC3B,GAAG,KAAK,QAAQ;CAGlB,MAAM,QAAkB,EAAE;CAC1B,MAAM,KAAK,cAAc,OAAO,SAAS;CACzC,MAAM,KAAK,cAAc,OAAO,YAAY;CAC5C,MAAM,KAAK,cAAc,OAAO,aAAa;CAC7C,MAAM,KAAK,GAAG;CACd,MAAM,KAAK,iBAAiB;CAC5B,KAAK,MAAM,QAAQ,OAAO,cACxB,MAAM,KAAK,OAAO,OAAO;CAG3B,IAAI,OAAO,aAAa,SAAS,GAAG;EAClC,MAAM,KAAK,GAAG;EACd,MAAM,KAAK,4CAA4C;EACvD,KAAK,MAAM,QAAQ,OAAO,cACxB,MAAM,KAAK,OAAO,OAAO;;CAI7B,IAAI,CAAC,OAAO,kBAAkB,SAAS;EACrC,MAAM,KAAK,GAAG;EACd,MAAM,KAAK,sBAAsB;EACjC,KAAK,MAAM,OAAO,OAAO,kBAAkB,MACzC,MAAM,KAAK,OAAO,MAAM;EAE1B,KAAK,MAAM,OAAO,OAAO,kBAAkB,SACzC,MAAM,KAAK,OAAO,IAAI,QAAQ;;CAIlC,MAAM,KAAK,GAAG;CACd,MAAM,KAAK,cAAc;CACzB,KAAK,MAAM,QAAQ,OAAO,WACxB,MAAM,KAAK,KAAK,OAAO;CAGzB,GAAG,KAAK,MAAM,KAAK,KAAK,EAAE,OAAO;;;;;;;;;;;AAYnC,SAAgB,eAAe,SAKlB;CACX,MAAM,QAAkB,EAAE;CAC1B,MAAM,KAAK,yEAAyE;CACpF,IAAI,CAAC,QAAQ,iBAAiB;EAC5B,MAAM,KAAK,2BAA2B,QAAQ,YAAY,IAAI;EAC9D,MAAM,KAAK,0BAA0B,QAAQ,WAAW,iCAAiC;EACzF,MAAM,KACJ,uFACD;EACD,MAAM,KACJ,oGACD;QACI;EACL,MAAM,KACJ,0BAA0B,QAAQ,WAAW,kBAAkB,QAAQ,YAAY,KACpF;EACD,MAAM,KACJ,uFACD;EACD,MAAM,KACJ,oGACD;;CAEH,OAAO"}
|
|
@@ -5,6 +5,7 @@ import { readFile } from "node:fs/promises";
|
|
|
5
5
|
import { hasMigrations } from "@prisma-next/framework-components/control";
|
|
6
6
|
import { readMigrationsDir } from "@prisma-next/migration-tools/io";
|
|
7
7
|
import { reconstructGraph } from "@prisma-next/migration-tools/migration-graph";
|
|
8
|
+
import { APP_SPACE_ID as APP_SPACE_ID$1, spaceMigrationDirectory } from "@prisma-next/migration-tools/spaces";
|
|
8
9
|
import { blue, bold, cyan, dim, green, magenta } from "colorette";
|
|
9
10
|
import wrapAnsi from "wrap-ansi";
|
|
10
11
|
import stringWidth from "string-width";
|
|
@@ -473,15 +474,29 @@ function resolveContractPath(config) {
|
|
|
473
474
|
/**
|
|
474
475
|
* Resolves the migrations directory and config path from CLI options.
|
|
475
476
|
* Shared by migration-apply, migration-plan, and migration-status.
|
|
477
|
+
*
|
|
478
|
+
* - `migrationsDir` is the project's top-level `migrations/` directory
|
|
479
|
+
* (the root that the aggregate loader walks for every contract space).
|
|
480
|
+
* - `appMigrationsDir` is the app subspace directory under it
|
|
481
|
+
* (`<migrationsDir>/<APP_SPACE_ID>/`). Every per-app reader / writer
|
|
482
|
+
* (`migration new`, `migration plan`, `migration apply`,
|
|
483
|
+
* `migration status`, `migration show`, `migration ref`) operates on
|
|
484
|
+
* this directory. Extensions own their own `migrations/<spaceId>/`.
|
|
485
|
+
* - `refsDir` is the app's refs directory (`<appMigrationsDir>/refs/`).
|
|
486
|
+
* The framework does not maintain refs at the migrations root.
|
|
476
487
|
*/
|
|
477
488
|
function resolveMigrationPaths(configOption, config) {
|
|
478
489
|
const configPath = configOption ? relative(process.cwd(), resolve(configOption)) : "prisma-next.config.ts";
|
|
479
490
|
const migrationsDir = resolve(configOption ? resolve(configOption, "..") : process.cwd(), config.migrations?.dir ?? "migrations");
|
|
491
|
+
const migrationsRelative = relative(process.cwd(), migrationsDir);
|
|
492
|
+
const appMigrationsDir = spaceMigrationDirectory(migrationsDir, APP_SPACE_ID$1);
|
|
480
493
|
return {
|
|
481
494
|
configPath,
|
|
482
495
|
migrationsDir,
|
|
483
|
-
migrationsRelative
|
|
484
|
-
|
|
496
|
+
migrationsRelative,
|
|
497
|
+
appMigrationsDir,
|
|
498
|
+
appMigrationsRelative: relative(process.cwd(), appMigrationsDir),
|
|
499
|
+
refsDir: resolve(appMigrationsDir, "refs")
|
|
485
500
|
};
|
|
486
501
|
}
|
|
487
502
|
function collectDeclaredInvariants(graph) {
|
|
@@ -642,4 +657,4 @@ function handleResult(result, flags, ui, onSuccess) {
|
|
|
642
657
|
//#endregion
|
|
643
658
|
export { formatCommandHelp as _, loadMigrationPackages as a, formatSuccessMessage as b, resolveContractPath as c, setCommandDescriptions as d, setCommandExamples as f, parseGlobalFlags as g, toStructuralEdge as h, getTargetMigrations as i, resolveMigrationPaths as l, toPathDecisionResult as m, addGlobalOptions as n, maskConnectionUrl as o, targetSupportsMigrations as p, collectDeclaredInvariants as r, readContractEnvelope as s, handleResult as t, sanitizeErrorMessage as u, formatRootHelp as v, formatStyledHeader as y };
|
|
644
659
|
|
|
645
|
-
//# sourceMappingURL=result-handler-
|
|
660
|
+
//# sourceMappingURL=result-handler-DWb1rFS-.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"result-handler-DWb1rFS-.mjs","names":["LEFT_COLUMN_WIDTH","createPrismaNextBadge","formatHeaderLine","formatReadMoreLine","APP_SPACE_ID"],"sources":["../src/utils/formatters/styled.ts","../src/utils/formatters/help.ts","../src/utils/global-flags.ts","../src/utils/command-helpers.ts","../src/utils/result-handler.ts"],"sourcesContent":["import { blue, bold, cyan, green } from 'colorette';\nimport type { Command } from 'commander';\nimport stringWidth from 'string-width';\nimport stripAnsi from 'strip-ansi';\n\nimport type { GlobalFlags } from '../global-flags';\nimport { createColorFormatter, formatDim } from './helpers';\n\n// ============================================================================\n// Styled Output Formatters\n// ============================================================================\n\n/**\n * Fixed width for left column in help output.\n */\nconst LEFT_COLUMN_WIDTH = 20;\n\n/**\n * Creates an arrow segment badge with green background and white text.\n * Body: green background with white \"prisma-next\" text\n * Tip: dark grey arrow pointing right (Powerline separator)\n */\nfunction createPrismaNextBadge(useColor: boolean): string {\n if (!useColor) {\n return 'prisma-next';\n }\n return bold('prisma-next');\n}\n\n/**\n * Creates a padding function.\n */\nfunction createPadFunction(): (s: string, w: number) => string {\n return (s: string, w: number) => s + ' '.repeat(Math.max(0, w - s.length));\n}\n\n/**\n * Formats a header line: brand + operation + intent\n */\nfunction formatHeaderLine(options: {\n readonly brand: string;\n readonly operation: string;\n readonly intent: string;\n}): string {\n if (options.operation) {\n return `${options.brand} ${options.operation} → ${options.intent}`;\n }\n return `${options.brand} ${options.intent}`;\n}\n\n/**\n * Formats a \"Read more\" URL line.\n * The \"Read more\" label is in default color (not cyan), and the URL is blue.\n */\nfunction formatReadMoreLine(options: {\n readonly url: string;\n readonly maxLabelWidth: number;\n readonly useColor: boolean;\n readonly formatDimText: (text: string) => string;\n}): string {\n const pad = createPadFunction();\n const labelPadded = pad('Read more', options.maxLabelWidth);\n // Label is default color (not cyan)\n const valueColored = options.useColor ? blue(options.url) : options.url;\n return `${options.formatDimText('│')} ${labelPadded} ${valueColored}`;\n}\n\n/**\n * Pads text to a fixed width, accounting for ANSI escape codes.\n * Uses string-width to measure the actual display width.\n */\nexport function padToFixedWidth(text: string, width: number): string {\n const actualWidth = stringWidth(text);\n const padding = Math.max(0, width - actualWidth);\n return text + ' '.repeat(padding);\n}\n\n/**\n * Renders a command tree structure.\n * Handles both single-level (subcommands of a command) and multi-level (top-level commands with subcommands) trees.\n */\nexport function renderCommandTree(options: {\n readonly commands: readonly Command[];\n readonly useColor: boolean;\n readonly formatDimText: (text: string) => string;\n readonly hasItemsAfter: boolean;\n readonly continuationPrefix?: string;\n}): string[] {\n const { commands, useColor, formatDimText, hasItemsAfter, continuationPrefix } = options;\n const lines: string[] = [];\n\n if (commands.length === 0) {\n return lines;\n }\n\n // Format each command\n for (let i = 0; i < commands.length; i++) {\n const cmd = commands[i];\n if (!cmd) continue;\n\n const subcommands = cmd.commands.filter((subcmd) => !subcmd.name().startsWith('_'));\n const isLastCommand = i === commands.length - 1;\n\n if (subcommands.length > 0) {\n // Command with subcommands - show command name, then tree-structured subcommands\n const treeChar = isLastCommand && !hasItemsAfter ? formatDimText('└') : formatDimText('├');\n // For top-level command, pad name to fixed width (accounting for \"| |-- \" = 5 chars)\n const treePrefix = `${treeChar}─ `;\n const treePrefixWidth = stringWidth(stripAnsi(treePrefix));\n const remainingWidth = LEFT_COLUMN_WIDTH - treePrefixWidth;\n const commandNamePadded = padToFixedWidth(cmd.name(), remainingWidth);\n const commandNameColored = useColor ? cyan(commandNamePadded) : commandNamePadded;\n lines.push(`${formatDimText('│')} ${treePrefix}${commandNameColored}`);\n\n for (let j = 0; j < subcommands.length; j++) {\n const subcmd = subcommands[j];\n if (!subcmd) continue;\n\n const isLastSubcommand = j === subcommands.length - 1;\n const shortDescription = subcmd.description() || '';\n\n // Use tree characters: -- for last subcommand, |-- for others\n const treeChar = isLastSubcommand ? '└' : '├';\n const continuation =\n continuationPrefix ??\n (isLastCommand && isLastSubcommand && !hasItemsAfter ? ' ' : formatDimText('│'));\n // For subcommands, account for \"| | -- \" = 7 chars (or \"| -- \" = 6 chars if continuation is space)\n const continuationStr = continuation === ' ' ? ' ' : continuation;\n const subTreePrefix = `${continuationStr} ${formatDimText(treeChar)}─ `;\n const subTreePrefixWidth = stringWidth(stripAnsi(subTreePrefix));\n const subRemainingWidth = LEFT_COLUMN_WIDTH - subTreePrefixWidth;\n const subcommandNamePadded = padToFixedWidth(subcmd.name(), subRemainingWidth);\n const subcommandNameColored = useColor ? cyan(subcommandNamePadded) : subcommandNamePadded;\n lines.push(\n `${formatDimText('│')} ${subTreePrefix}${subcommandNameColored} ${shortDescription}`,\n );\n }\n } else {\n // Standalone command - show command name and description on same line\n const treeChar = isLastCommand && !hasItemsAfter ? formatDimText('└') : formatDimText('├');\n const treePrefix = `${treeChar}─ `;\n const treePrefixWidth = stringWidth(stripAnsi(treePrefix));\n const remainingWidth = LEFT_COLUMN_WIDTH - treePrefixWidth;\n const commandNamePadded = padToFixedWidth(cmd.name(), remainingWidth);\n const commandNameColored = useColor ? cyan(commandNamePadded) : commandNamePadded;\n const shortDescription = cmd.description() || '';\n lines.push(`${formatDimText('│')} ${treePrefix}${commandNameColored} ${shortDescription}`);\n }\n }\n\n return lines;\n}\n\n/**\n * Formats the header in the new experimental visual style.\n * This header appears at the start of command output, showing the operation,\n * intent, documentation link, and parameters.\n */\nexport function formatStyledHeader(options: {\n readonly command: string;\n readonly description: string;\n readonly url?: string;\n readonly details: ReadonlyArray<{ readonly label: string; readonly value: string }>;\n readonly flags: GlobalFlags;\n}): string {\n const lines: string[] = [];\n const useColor = options.flags.color !== false;\n const formatDimText = (text: string) => formatDim(useColor, text);\n\n // Header: arrow + operation badge + intent\n const brand = createPrismaNextBadge(useColor);\n // Use full command path (e.g., \"contract emit\" not just \"emit\")\n const operation = useColor ? bold(options.command) : options.command;\n const intent = formatDimText(options.description);\n lines.push(formatHeaderLine({ brand, operation, intent }));\n lines.push(formatDimText('│')); // Vertical line separator between command and params\n\n // Format details using fixed left column width (same style as help text options)\n for (const detail of options.details) {\n // Add colon to label, then pad to fixed width using padToFixedWidth for ANSI-aware padding\n const labelWithColon = `${detail.label}:`;\n const labelPadded = padToFixedWidth(labelWithColon, LEFT_COLUMN_WIDTH);\n const labelColored = useColor ? cyan(labelPadded) : labelPadded;\n lines.push(`${formatDimText('│')} ${labelColored} ${detail.value}`);\n }\n\n // Add \"Read more\" URL if present (same style as help text)\n if (options.url) {\n lines.push(formatDimText('│')); // Separator line before \"Read more\"\n lines.push(\n formatReadMoreLine({\n url: options.url,\n maxLabelWidth: LEFT_COLUMN_WIDTH,\n useColor,\n formatDimText,\n }),\n );\n }\n\n lines.push(formatDimText('└'));\n\n return `${lines.join('\\n')}\\n`;\n}\n\n/**\n * Formats a success message in the styled output format.\n */\nexport function formatSuccessMessage(flags: GlobalFlags): string {\n const useColor = flags.color !== false;\n const formatGreen = createColorFormatter(useColor, green);\n return `${formatGreen('✔')} Success`;\n}\n","import { blue, bold, cyan, dim, green, magenta } from 'colorette';\nimport type { Command } from 'commander';\nimport wrapAnsi from 'wrap-ansi';\n\nimport { getCommandExamples, getLongDescription } from '../command-helpers';\nimport type { GlobalFlags } from '../global-flags';\nimport { formatDim } from './helpers';\nimport { padToFixedWidth, renderCommandTree } from './styled';\n\n// ============================================================================\n// Help Output Formatters\n// ============================================================================\n\n/**\n * Fixed width for left column in help output.\n * Must match the value in styled.ts.\n */\nconst LEFT_COLUMN_WIDTH = 20;\n\n/**\n * Minimum width for right column wrapping in help output.\n */\nconst RIGHT_COLUMN_MIN_WIDTH = 40;\n\n/**\n * Maximum width for right column wrapping in help output (when terminal is wide enough).\n */\nconst RIGHT_COLUMN_MAX_WIDTH = 90;\n\n/**\n * Gets the terminal width, or returns a default if not available.\n */\nfunction getTerminalWidth(): number {\n // Help text goes to stderr, so prefer stderr columns. Fall back to stdout, then CLI_WIDTH env.\n const terminalWidth = process.stderr.columns || process.stdout.columns;\n const envWidth = Number.parseInt(process.env['CLI_WIDTH'] || '', 10);\n return terminalWidth || (Number.isFinite(envWidth) ? envWidth : 80);\n}\n\n/**\n * Calculates the available width for the right column based on terminal width.\n */\nfunction calculateRightColumnWidth(): number {\n const terminalWidth = getTerminalWidth();\n const availableWidth = terminalWidth - 2 - LEFT_COLUMN_WIDTH - 2;\n return Math.max(RIGHT_COLUMN_MIN_WIDTH, Math.min(availableWidth, RIGHT_COLUMN_MAX_WIDTH));\n}\n\n/**\n * Creates the CLI brand badge.\n */\nfunction createPrismaNextBadge(useColor: boolean): string {\n return useColor ? bold('prisma-next') : 'prisma-next';\n}\n\n/**\n * Formats a header line: brand + operation + intent\n */\nfunction formatHeaderLine(options: {\n readonly brand: string;\n readonly operation: string;\n readonly intent: string;\n}): string {\n if (options.operation) {\n return `${options.brand} ${options.operation} → ${options.intent}`;\n }\n return `${options.brand} ${options.intent}`;\n}\n\n/**\n * Wraps text to fit within a specified width using wrap-ansi.\n */\nfunction wrapTextAnsi(text: string, width: number): string[] {\n const wrapped = wrapAnsi(text, width, { hard: false, trim: true });\n return wrapped.split('\\n');\n}\n\n/**\n * Formats a default value as \"default: <value>\" with dimming.\n */\nfunction formatDefaultValue(value: unknown, useColor: boolean): string {\n const valueStr = String(value);\n const defaultText = `default: ${valueStr}`;\n return useColor ? dim(defaultText) : defaultText;\n}\n\n/**\n * Formats a \"Read more\" URL line.\n */\nfunction formatReadMoreLine(options: {\n readonly url: string;\n readonly maxLabelWidth: number;\n readonly useColor: boolean;\n readonly formatDimText: (text: string) => string;\n}): string {\n const labelPadded = `Read more${' '.repeat(Math.max(0, options.maxLabelWidth - 'Read more'.length))}`;\n const valueColored = options.useColor ? blue(options.url) : options.url;\n return `${options.formatDimText('│')} ${labelPadded} ${valueColored}`;\n}\n\n/**\n * Formats multiline description with \"Prisma Next\" in green.\n */\nfunction formatMultilineDescription(options: {\n readonly descriptionLines: readonly string[];\n readonly useColor: boolean;\n readonly formatDimText: (text: string) => string;\n}): string[] {\n const lines: string[] = [];\n const formatGreen = (text: string) => (options.useColor ? green(text) : text);\n\n const rightColumnWidth = calculateRightColumnWidth();\n const totalWidth = 2 + LEFT_COLUMN_WIDTH + 2 + rightColumnWidth;\n const wrapWidth = totalWidth - 2;\n\n for (const descLine of options.descriptionLines) {\n const formattedLine = descLine.replace(/Prisma Next/g, (match) => formatGreen(match));\n const wrappedLines = wrapTextAnsi(formattedLine, wrapWidth);\n for (const wrappedLine of wrappedLines) {\n lines.push(`${options.formatDimText('│')} ${wrappedLine}`);\n }\n }\n return lines;\n}\n\n/**\n * Maps command paths to their documentation URLs.\n */\nfunction getCommandDocsUrl(commandPath: string): string | undefined {\n const docsMap: Record<string, string> = {\n 'contract emit': 'https://pris.ly/contract-emit',\n 'contract infer': 'https://pris.ly/contract-infer',\n 'db schema': 'https://pris.ly/db-schema',\n 'db verify': 'https://pris.ly/db-verify',\n 'db update': 'https://pris.ly/db-update',\n 'migration plan': 'https://pris.ly/migration-plan',\n 'migration apply': 'https://pris.ly/migration-apply',\n 'migration show': 'https://pris.ly/migration-show',\n 'migration status': 'https://pris.ly/migration-status',\n };\n return docsMap[commandPath];\n}\n\n/**\n * Builds the full command path from a command and its parents.\n */\nfunction buildCommandPath(command: Command): string {\n const parts: string[] = [];\n let current: Command | undefined = command;\n while (current && current.name() !== 'prisma-next') {\n parts.unshift(current.name());\n current = current.parent ?? undefined;\n }\n return parts.join(' ');\n}\n\n/**\n * Formats help output for a command using the styled format.\n */\nexport function formatCommandHelp(options: {\n readonly command: Command;\n readonly flags: GlobalFlags;\n}): string {\n const { command, flags } = options;\n const lines: string[] = [];\n const useColor = flags.color !== false;\n const formatDimText = (text: string) => formatDim(useColor, text);\n\n // Build full command path (e.g., \"db verify\")\n const commandPath = buildCommandPath(command);\n const shortDescription = command.description() || '';\n const longDescription = getLongDescription(command);\n\n // Include positional arguments in the header line\n const argsSuffix = command.registeredArguments\n .map((arg) => (arg.required ? `<${arg.name()}>` : `[${arg.name()}]`))\n .join(' ');\n const brand = createPrismaNextBadge(useColor);\n const commandWithArgs = argsSuffix ? `${commandPath} ${argsSuffix}` : commandPath;\n const operation = useColor ? bold(commandWithArgs) : commandWithArgs;\n const intent = formatDimText(shortDescription);\n lines.push(formatHeaderLine({ brand, operation, intent }));\n lines.push(formatDimText('│'));\n\n // Extract options and format them\n const optionsList = command.options.map((opt) => {\n const description = opt.description || '';\n // Commander.js stores default value in defaultValue property\n const defaultValue = (opt as { defaultValue?: unknown }).defaultValue;\n return { flags: opt.flags, description, defaultValue };\n });\n\n // Extract subcommands if any\n const subcommands = command.commands.filter((cmd) => !cmd.name().startsWith('_'));\n\n // Format subcommands as a tree if present\n if (subcommands.length > 0) {\n const hasItemsAfter = optionsList.length > 0;\n const treeLines = renderCommandTree({\n commands: subcommands,\n useColor,\n formatDimText,\n hasItemsAfter,\n });\n lines.push(...treeLines);\n }\n\n // Add separator between subcommands and options if both exist\n if (subcommands.length > 0 && optionsList.length > 0) {\n lines.push(formatDimText('│'));\n }\n\n // Format options with fixed width, wrapping, and default values\n if (optionsList.length > 0) {\n for (const opt of optionsList) {\n // Format flag with fixed 30-char width\n const flagsPadded = padToFixedWidth(opt.flags, LEFT_COLUMN_WIDTH);\n let flagsColored = flagsPadded;\n if (useColor) {\n // Color placeholders in magenta, then wrap in cyan\n flagsColored = flagsPadded.replace(/(<[^>]+>)/g, (match: string) => magenta(match));\n flagsColored = cyan(flagsColored);\n }\n\n // Wrap description based on terminal width\n const rightColumnWidth = calculateRightColumnWidth();\n const wrappedDescription = wrapTextAnsi(opt.description, rightColumnWidth);\n\n // First line: flag + first line of description\n lines.push(`${formatDimText('│')} ${flagsColored} ${wrappedDescription[0] || ''}`);\n\n // Continuation lines: empty label (30 spaces) + wrapped lines\n for (let i = 1; i < wrappedDescription.length; i++) {\n const emptyLabel = ' '.repeat(LEFT_COLUMN_WIDTH);\n lines.push(`${formatDimText('│')} ${emptyLabel} ${wrappedDescription[i] || ''}`);\n }\n\n // Default value line (if present)\n if (opt.defaultValue !== undefined) {\n const emptyLabel = ' '.repeat(LEFT_COLUMN_WIDTH);\n const defaultText = formatDefaultValue(opt.defaultValue, useColor);\n lines.push(`${formatDimText('│')} ${emptyLabel} ${defaultText}`);\n }\n }\n }\n\n // Add docs URL if available (with separator line before it)\n const docsUrl = getCommandDocsUrl(commandPath);\n if (docsUrl) {\n lines.push(formatDimText('│')); // Separator line between params and docs\n lines.push(\n formatReadMoreLine({\n url: docsUrl,\n maxLabelWidth: LEFT_COLUMN_WIDTH,\n useColor,\n formatDimText,\n }),\n );\n }\n\n // Examples (copy-pastable)\n const examples = getCommandExamples(command);\n if (examples && examples.length > 0) {\n lines.push(formatDimText('│'));\n lines.push(`${formatDimText('│')} ${formatDimText('Examples:')}`);\n for (const example of examples) {\n lines.push(`${formatDimText('│')} ${useColor ? dim('$') : '$'} ${example}`);\n }\n }\n\n // Multi-line description (if present) - shown after all other content\n if (longDescription) {\n lines.push(formatDimText('│'));\n const descriptionLines = longDescription.split('\\n').filter((line) => line.trim().length > 0);\n lines.push(...formatMultilineDescription({ descriptionLines, useColor, formatDimText }));\n }\n\n lines.push(formatDimText('└'));\n\n return `${lines.join('\\n')}\\n`;\n}\n\n/**\n * Formats help output for the root program using the styled format.\n */\nexport function formatRootHelp(options: {\n readonly program: Command;\n readonly flags: GlobalFlags;\n}): string {\n const { program, flags } = options;\n const lines: string[] = [];\n const useColor = flags.color !== false;\n const formatDimText = (text: string) => formatDim(useColor, text);\n\n // Header: \"prisma-next -> Manage your data layer\"\n const brand = createPrismaNextBadge(useColor);\n const shortDescription = 'Manage your data layer';\n const intent = formatDimText(shortDescription);\n lines.push(formatHeaderLine({ brand, operation: '', intent }));\n lines.push(formatDimText('│')); // Vertical line separator after header\n\n // Extract top-level commands (exclude hidden commands starting with '_' and the 'help' command)\n const topLevelCommands = program.commands.filter(\n (cmd) => !cmd.name().startsWith('_') && cmd.name() !== 'help',\n );\n\n // Extract global options (needed to determine if last command)\n const globalOptions = program.options.map((opt) => {\n const description = opt.description || '';\n // Commander.js stores default value in defaultValue property\n const defaultValue = (opt as { defaultValue?: unknown }).defaultValue;\n return { flags: opt.flags, description, defaultValue };\n });\n\n // Build command tree\n if (topLevelCommands.length > 0) {\n const hasItemsAfter = globalOptions.length > 0;\n const treeLines = renderCommandTree({\n commands: topLevelCommands,\n useColor,\n formatDimText,\n hasItemsAfter,\n });\n lines.push(...treeLines);\n }\n\n // Add separator between commands and options if both exist\n if (topLevelCommands.length > 0 && globalOptions.length > 0) {\n lines.push(formatDimText('│'));\n }\n\n // Format global options with fixed width, wrapping, and default values\n if (globalOptions.length > 0) {\n for (const opt of globalOptions) {\n // Format flag with fixed 30-char width\n const flagsPadded = padToFixedWidth(opt.flags, LEFT_COLUMN_WIDTH);\n let flagsColored = flagsPadded;\n if (useColor) {\n // Color placeholders in magenta, then wrap in cyan\n flagsColored = flagsPadded.replace(/(<[^>]+>)/g, (match: string) => magenta(match));\n flagsColored = cyan(flagsColored);\n }\n\n // Wrap description based on terminal width\n const rightColumnWidth = calculateRightColumnWidth();\n const wrappedDescription = wrapTextAnsi(opt.description, rightColumnWidth);\n\n // First line: flag + first line of description\n lines.push(`${formatDimText('│')} ${flagsColored} ${wrappedDescription[0] || ''}`);\n\n // Continuation lines: empty label (30 spaces) + wrapped lines\n for (let i = 1; i < wrappedDescription.length; i++) {\n const emptyLabel = ' '.repeat(LEFT_COLUMN_WIDTH);\n lines.push(`${formatDimText('│')} ${emptyLabel} ${wrappedDescription[i] || ''}`);\n }\n\n // Default value line (if present)\n if (opt.defaultValue !== undefined) {\n const emptyLabel = ' '.repeat(LEFT_COLUMN_WIDTH);\n const defaultText = formatDefaultValue(opt.defaultValue, useColor);\n lines.push(`${formatDimText('│')} ${emptyLabel} ${defaultText}`);\n }\n }\n }\n\n // Multi-line description (white, not dimmed, with \"Prisma Next\" in green) - shown at bottom\n const formatGreen = (text: string) => (useColor ? green(text) : text);\n const descriptionLines = [\n `Use ${formatGreen('Prisma Next')} to define your data layer as a contract. Sign your database and application with the same contract to guarantee compatibility. Plan and apply migrations to safely evolve your schema.`,\n ];\n if (descriptionLines.length > 0) {\n lines.push(formatDimText('│')); // Separator line before description\n lines.push(...formatMultilineDescription({ descriptionLines, useColor, formatDimText }));\n }\n\n lines.push(formatDimText('└'));\n\n return `${lines.join('\\n')}\\n`;\n}\n","export interface GlobalFlags {\n readonly json?: boolean;\n readonly quiet?: boolean;\n readonly verbose?: number; // 0, 1, or 2\n readonly color?: boolean;\n readonly interactive?: boolean;\n readonly yes?: boolean;\n}\n\n/**\n * Common options parsed by Commander.js for every command.\n * Extend this for command-specific options instead of duplicating these fields.\n */\nexport interface CommonCommandOptions {\n readonly json?: string | boolean;\n readonly quiet?: boolean;\n readonly q?: boolean;\n readonly verbose?: boolean;\n readonly v?: boolean;\n readonly trace?: boolean;\n readonly color?: boolean;\n readonly 'no-color'?: boolean;\n readonly interactive?: boolean;\n readonly 'no-interactive'?: boolean;\n readonly yes?: boolean;\n readonly y?: boolean;\n}\n\n/**\n * Parses global flags from CLI options.\n * Handles verbosity flags (-v, --trace), JSON output, quiet mode, color,\n * interactivity (--interactive/--no-interactive), and auto-accept (-y/--yes).\n */\nexport function parseGlobalFlags(options: CommonCommandOptions): GlobalFlags {\n const flags: {\n json?: boolean;\n quiet?: boolean;\n verbose?: number;\n color?: boolean;\n interactive?: boolean;\n yes?: boolean;\n } = {};\n\n // JSON output: explicit --json flag or auto-detect piped stdout (Unix convention)\n if (options.json || !process.stdout.isTTY) {\n flags.json = true;\n }\n\n // Quiet mode\n if (options.quiet || options.q) {\n flags.quiet = true;\n }\n\n // Verbosity: -v = 1, --trace = 2\n // Env toggles: PRISMA_NEXT_TRACE=1 ≅ --trace, PRISMA_NEXT_DEBUG=1 ≅ -v\n if (options.trace || process.env['PRISMA_NEXT_TRACE'] === '1') {\n flags.verbose = 2;\n } else if (options.verbose || options.v || process.env['PRISMA_NEXT_DEBUG'] === '1') {\n flags.verbose = 1;\n } else {\n flags.verbose = 0;\n }\n\n // Color: respect NO_COLOR env var, --color/--no-color flags\n // When JSON output is enabled, disable color to ensure clean JSON output\n if (process.env['NO_COLOR'] || flags.json) {\n flags.color = false;\n } else if (options['no-color']) {\n flags.color = false;\n } else if (options.color !== undefined) {\n flags.color = options.color;\n } else {\n // Default: enable color if TTY\n flags.color = process.stdout.isTTY && !process.env['CI'];\n }\n\n // Interactivity: --interactive/--no-interactive\n // Default: interactive when stdout is a TTY\n if (options['no-interactive']) {\n flags.interactive = false;\n } else if (options.interactive !== undefined) {\n flags.interactive = options.interactive;\n } else {\n flags.interactive = !!process.stdout.isTTY;\n }\n\n // Auto-accept prompts: -y/--yes\n if (options.yes || options.y) {\n flags.yes = true;\n }\n\n return flags as GlobalFlags;\n}\n","import { readFile } from 'node:fs/promises';\nimport type { ControlTargetDescriptor } from '@prisma-next/framework-components/control';\nimport { hasMigrations } from '@prisma-next/framework-components/control';\nimport type { NoInvariantPathStructuralEdge } from '@prisma-next/migration-tools/errors';\nimport type { MigrationEdge, MigrationGraph } from '@prisma-next/migration-tools/graph';\nimport { readMigrationsDir } from '@prisma-next/migration-tools/io';\nimport type { PathDecision } from '@prisma-next/migration-tools/migration-graph';\nimport { reconstructGraph } from '@prisma-next/migration-tools/migration-graph';\nimport type { OnDiskMigrationPackage } from '@prisma-next/migration-tools/package';\nimport { APP_SPACE_ID, spaceMigrationDirectory } from '@prisma-next/migration-tools/spaces';\nimport { ifDefined } from '@prisma-next/utils/defined';\nimport type { Command } from 'commander';\nimport { relative, resolve } from 'pathe';\nimport { formatCommandHelp } from './formatters/help';\nimport type { CommonCommandOptions } from './global-flags';\nimport { parseGlobalFlags } from './global-flags';\n\nconst longDescriptions = new WeakMap<Command, string>();\nconst commandExamples = new WeakMap<Command, readonly string[]>();\n\n/**\n * Sets both short and long descriptions for a command.\n * The short description is used in command trees and headers.\n * The long description is shown at the bottom of help output.\n */\nexport function setCommandDescriptions(\n command: Command,\n shortDescription: string,\n longDescription?: string,\n): Command {\n command.description(shortDescription);\n if (longDescription) {\n longDescriptions.set(command, longDescription);\n }\n return command;\n}\n\n/**\n * Sets copy-pastable examples for a command, shown in help text.\n */\nexport function setCommandExamples(command: Command, examples: readonly string[]): Command {\n commandExamples.set(command, examples);\n return command;\n}\n\n/**\n * Gets the long description from a command if it was set via setCommandDescriptions.\n */\nexport function getLongDescription(command: Command): string | undefined {\n return longDescriptions.get(command);\n}\n\n/**\n * Gets examples from a command if set via setCommandExamples.\n */\nexport function getCommandExamples(command: Command): readonly string[] | undefined {\n return commandExamples.get(command);\n}\n\n/**\n * Shared CLI options interface for migration commands (db init, db update).\n * These are the Commander.js parsed options common to both commands.\n */\nexport interface MigrationCommandOptions extends CommonCommandOptions {\n readonly db?: string;\n readonly config?: string;\n readonly dryRun?: boolean;\n}\n\n/**\n * Resolves the absolute path to contract.json from the config.\n * Centralises the fallback logic shared by every command that reads the contract.\n */\nexport function resolveContractPath(config: { contract?: { output?: string } }): string {\n return config.contract?.output\n ? resolve(config.contract.output)\n : resolve('src/prisma/contract.json');\n}\n\n/**\n * Resolves the migrations directory and config path from CLI options.\n * Shared by migration-apply, migration-plan, and migration-status.\n *\n * - `migrationsDir` is the project's top-level `migrations/` directory\n * (the root that the aggregate loader walks for every contract space).\n * - `appMigrationsDir` is the app subspace directory under it\n * (`<migrationsDir>/<APP_SPACE_ID>/`). Every per-app reader / writer\n * (`migration new`, `migration plan`, `migration apply`,\n * `migration status`, `migration show`, `migration ref`) operates on\n * this directory. Extensions own their own `migrations/<spaceId>/`.\n * - `refsDir` is the app's refs directory (`<appMigrationsDir>/refs/`).\n * The framework does not maintain refs at the migrations root.\n */\nexport function resolveMigrationPaths(\n configOption: string | undefined,\n config: { migrations?: { dir?: string } },\n): {\n configPath: string;\n migrationsDir: string;\n migrationsRelative: string;\n appMigrationsDir: string;\n appMigrationsRelative: string;\n refsDir: string;\n} {\n const configPath = configOption\n ? relative(process.cwd(), resolve(configOption))\n : 'prisma-next.config.ts';\n const migrationsDir = resolve(\n configOption ? resolve(configOption, '..') : process.cwd(),\n config.migrations?.dir ?? 'migrations',\n );\n const migrationsRelative = relative(process.cwd(), migrationsDir);\n const appMigrationsDir = spaceMigrationDirectory(migrationsDir, APP_SPACE_ID);\n const appMigrationsRelative = relative(process.cwd(), appMigrationsDir);\n const refsDir = resolve(appMigrationsDir, 'refs');\n return {\n configPath,\n migrationsDir,\n migrationsRelative,\n appMigrationsDir,\n appMigrationsRelative,\n refsDir,\n };\n}\n\n/**\n * Slim representation of a PathDecision for CLI JSON output.\n * Strips internal fields (createdAt, labels) from path entries.\n */\nexport interface PathDecisionResult {\n readonly fromHash: string;\n readonly toHash: string;\n readonly alternativeCount: number;\n readonly tieBreakReasons: readonly string[];\n readonly refName?: string;\n readonly requiredInvariants: readonly string[];\n readonly satisfiedInvariants: readonly string[];\n readonly selectedPath: readonly {\n readonly dirName: string;\n readonly migrationHash: string;\n readonly from: string;\n readonly to: string;\n readonly invariants: readonly string[];\n }[];\n}\n\nexport function collectDeclaredInvariants(graph: MigrationGraph): ReadonlySet<string> {\n const declared = new Set<string>();\n for (const edges of graph.forwardChain.values()) {\n for (const edge of edges) {\n for (const inv of edge.invariants) {\n declared.add(inv);\n }\n }\n }\n return declared;\n}\n\n/**\n * Maps a `MigrationEdge` to the structural-edge shape used in the\n * `MIGRATION.NO_INVARIANT_PATH` error envelope. Shared between\n * `migration apply` and `migration status` so both commands surface\n * the same JSON wire shape when an invariant-aware route is unsatisfiable.\n */\nexport function toStructuralEdge(edge: MigrationEdge): NoInvariantPathStructuralEdge {\n return {\n dirName: edge.dirName,\n migrationHash: edge.migrationHash,\n from: edge.from,\n to: edge.to,\n invariants: edge.invariants,\n };\n}\n\n/**\n * Maps a PathDecision to the slim CLI output representation.\n */\nexport function toPathDecisionResult(decision: PathDecision): PathDecisionResult {\n return {\n fromHash: decision.fromHash,\n toHash: decision.toHash,\n alternativeCount: decision.alternativeCount,\n tieBreakReasons: decision.tieBreakReasons,\n requiredInvariants: decision.requiredInvariants ?? [],\n satisfiedInvariants: decision.satisfiedInvariants ?? [],\n ...ifDefined('refName', decision.refName),\n selectedPath: decision.selectedPath.map((entry) => ({\n dirName: entry.dirName,\n migrationHash: entry.migrationHash,\n from: entry.from,\n to: entry.to,\n invariants: entry.invariants,\n })),\n };\n}\n\nexport function targetSupportsMigrations(target: ControlTargetDescriptor<string, string>): boolean {\n return hasMigrations(target);\n}\n\nexport function getTargetMigrations(target: ControlTargetDescriptor<string, string>) {\n return hasMigrations(target) ? target.migrations : undefined;\n}\n\n/**\n * Reads the migrations directory and builds the migration graph from all\n * packages. Throws on I/O or graph errors — callers handle error mapping.\n *\n * Every on-disk package is content-addressed (`migrationHash` is always a\n * string); there is no draft state to filter out.\n */\nexport async function loadMigrationPackages(migrationsDir: string): Promise<{\n bundles: readonly OnDiskMigrationPackage[];\n graph: MigrationGraph;\n}> {\n const bundles = await readMigrationsDir(migrationsDir);\n const graph = reconstructGraph(bundles);\n return { bundles, graph };\n}\n\n/**\n * The subset of the emitted contract.json that the framework layer can\n * safely type. The emitter adds these fields on top of the family-specific\n * storage/models/relations. Other fields exist in the JSON but are opaque\n * at this layer — the index signature preserves them for downstream\n * consumers that operate at the family level (e.g., the control client).\n */\nexport interface ContractEnvelope {\n readonly storageHash: string;\n readonly schemaVersion: string;\n readonly target: string;\n readonly targetFamily: string;\n readonly profileHash?: string;\n readonly [key: string]: unknown;\n}\n\n/**\n * Reads and parses contract.json, validating the framework-level envelope\n * fields (storageHash, schemaVersion, target, targetFamily).\n *\n * Family-specific validation (storage structure, codec mappings, etc.)\n * happens downstream in the control client via the family instance.\n */\nexport async function readContractEnvelope(config: {\n contract?: { output?: string };\n}): Promise<ContractEnvelope> {\n const contractPath = resolveContractPath(config);\n const content = await readFile(contractPath, 'utf-8');\n const json = JSON.parse(content) as Record<string, unknown>;\n\n const { schemaVersion, target, targetFamily, profileHash } = json;\n const storage = json['storage'] as Record<string, unknown> | undefined;\n const storageHash = storage?.['storageHash'];\n\n if (typeof storageHash !== 'string') {\n throw new Error(\n `Contract at ${relative(process.cwd(), contractPath)} is missing a valid storage.storageHash. Run \\`prisma-next contract emit\\` to regenerate.`,\n );\n }\n if (typeof schemaVersion !== 'string') {\n throw new Error(\n `Contract at ${relative(process.cwd(), contractPath)} is missing schemaVersion.`,\n );\n }\n if (typeof target !== 'string') {\n throw new Error(`Contract at ${relative(process.cwd(), contractPath)} is missing target.`);\n }\n if (typeof targetFamily !== 'string') {\n throw new Error(\n `Contract at ${relative(process.cwd(), contractPath)} is missing targetFamily.`,\n );\n }\n\n return {\n ...json,\n storageHash,\n schemaVersion,\n target,\n targetFamily,\n ...(typeof profileHash === 'string' ? { profileHash } : {}),\n };\n}\n\n/**\n * Masks credentials in a database connection URL.\n * Handles standard URLs (username + password + query params) and libpq-style key=value strings.\n */\nexport function maskConnectionUrl(url: string): string {\n try {\n const parsed = new URL(url);\n if (parsed.username) {\n parsed.username = '****';\n }\n if (parsed.password) {\n parsed.password = '****';\n }\n // Also mask password in query parameters (e.g., ?password=secret, ?sslpassword=secret)\n for (const key of [...parsed.searchParams.keys()]) {\n if (/password/i.test(key)) {\n parsed.searchParams.set(key, '****');\n }\n }\n return parsed.toString();\n } catch {\n // Fallback for libpq-style key=value connection strings (e.g., \"host=localhost password=secret user=admin\")\n return url\n .replace(/password\\s*=\\s*\\S+/gi, 'password=****')\n .replace(/user\\s*=\\s*\\S+/gi, 'user=****');\n }\n}\n\n/**\n * Strips raw connection URL fragments from an error message to prevent credential leakage.\n * Call this before surfacing driver errors to the user.\n */\nexport function sanitizeErrorMessage(message: string, connectionUrl?: string): string {\n if (!connectionUrl) {\n return message;\n }\n try {\n const parsed = new URL(connectionUrl);\n // Replace the full URL (with and without trailing slash)\n let sanitized = message;\n sanitized = sanitized.replaceAll(connectionUrl, maskConnectionUrl(connectionUrl));\n // Also replace the password and username individually if they appear\n if (parsed.password) {\n sanitized = sanitized.replaceAll(parsed.password, '****');\n }\n if (parsed.username) {\n sanitized = sanitized.replaceAll(parsed.username, '****');\n }\n return sanitized;\n } catch {\n // For libpq-style strings, mask password and user values in the message\n return message\n .replace(/password\\s*=\\s*\\S+/gi, 'password=****')\n .replace(/user\\s*=\\s*\\S+/gi, 'user=****');\n }\n}\n\n/**\n * Registers the global CLI options shared by every command:\n * --json, -q/--quiet, -v/--verbose, --trace, --color, --no-color,\n * --interactive, --no-interactive, -y/--yes.\n *\n * Also sets up the styled help formatter.\n */\nexport function addGlobalOptions(command: Command): Command {\n return command\n .configureHelp({\n formatHelp: (cmd) => {\n const flags = parseGlobalFlags({});\n return formatCommandHelp({ command: cmd, flags });\n },\n })\n .option('--json', 'Output as JSON')\n .option('-q, --quiet', 'Quiet mode: errors only')\n .option('-v, --verbose', 'Verbose output: debug info, timings')\n .option('--trace', 'Trace output: deep internals, stack traces')\n .option('--color', 'Force color output')\n .option('--no-color', 'Disable color output')\n .option('--interactive', 'Force interactive mode')\n .option('--no-interactive', 'Disable interactive prompts')\n .option('-y, --yes', 'Auto-accept prompts');\n}\n","import type { Result } from '@prisma-next/utils/result';\nimport type { CliStructuredError } from './cli-errors';\nimport { formatErrorJson, formatErrorOutput } from './formatters/errors';\nimport type { GlobalFlags } from './global-flags';\nimport type { TerminalUI } from './terminal-ui';\n\n/**\n * Processes a CLI command result, handling both success and error cases.\n * Formats output appropriately and returns the exit code.\n * Never throws - returns exit code for commands to use with process.exit().\n *\n * Error output:\n * - JSON mode: JSON error to stdout (piped) via ui.output(), human sees nothing on stderr.\n * - Interactive: human-readable error to stderr.\n */\nexport function handleResult<T>(\n result: Result<T, CliStructuredError>,\n flags: GlobalFlags,\n ui: TerminalUI,\n onSuccess?: (value: T) => void,\n): number {\n if (result.ok) {\n if (onSuccess) {\n onSuccess(result.value);\n }\n return 0;\n }\n\n // Convert to CLI envelope\n const envelope = result.failure.toEnvelope();\n\n if (flags.json) {\n // JSON error → stdout only\n ui.output(formatErrorJson(envelope));\n } else {\n // Human-readable error → stderr\n ui.error(formatErrorOutput(envelope, flags));\n }\n\n // Infer exit code from error domain: CLI errors = 2, RUN errors = 1\n const exitCode = result.failure.domain === 'CLI' ? 2 : 1;\n return exitCode;\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prisma-next/cli",
|
|
3
|
-
"version": "0.5.0-dev.
|
|
3
|
+
"version": "0.5.0-dev.68",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"clipanion": "4.0.0-rc.4",
|
|
20
20
|
"closest-match": "^1.3.3",
|
|
21
21
|
"colorette": "^2.0.20",
|
|
22
|
-
"commander": "^
|
|
22
|
+
"commander": "^14.0.3",
|
|
23
23
|
"esbuild": "^0.28.0",
|
|
24
24
|
"jsonc-parser": "^3.3.1",
|
|
25
25
|
"package-manager-detector": "^1.6.0",
|
|
@@ -27,27 +27,27 @@
|
|
|
27
27
|
"string-width": "^8.2.1",
|
|
28
28
|
"strip-ansi": "^7.2.0",
|
|
29
29
|
"wrap-ansi": "^10.0.0",
|
|
30
|
-
"@prisma-next/config": "0.5.0-dev.
|
|
31
|
-
"@prisma-next/
|
|
32
|
-
"@prisma-next/
|
|
33
|
-
"@prisma-next/errors": "0.5.0-dev.
|
|
34
|
-
"@prisma-next/framework-components": "0.5.0-dev.
|
|
35
|
-
"@prisma-next/migration-tools": "0.5.0-dev.
|
|
36
|
-
"@prisma-next/
|
|
37
|
-
"@prisma-next/
|
|
30
|
+
"@prisma-next/config": "0.5.0-dev.68",
|
|
31
|
+
"@prisma-next/emitter": "0.5.0-dev.68",
|
|
32
|
+
"@prisma-next/contract": "0.5.0-dev.68",
|
|
33
|
+
"@prisma-next/errors": "0.5.0-dev.68",
|
|
34
|
+
"@prisma-next/framework-components": "0.5.0-dev.68",
|
|
35
|
+
"@prisma-next/migration-tools": "0.5.0-dev.68",
|
|
36
|
+
"@prisma-next/utils": "0.5.0-dev.68",
|
|
37
|
+
"@prisma-next/psl-printer": "0.5.0-dev.68"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"@types/node": "24.10.4",
|
|
41
41
|
"tsdown": "0.22.0",
|
|
42
42
|
"typescript": "5.9.3",
|
|
43
43
|
"vitest": "4.1.5",
|
|
44
|
-
"@prisma-next/sql-contract
|
|
45
|
-
"@prisma-next/sql-contract-ts": "0.5.0-dev.
|
|
46
|
-
"@prisma-next/sql-
|
|
44
|
+
"@prisma-next/sql-contract": "0.5.0-dev.68",
|
|
45
|
+
"@prisma-next/sql-contract-ts": "0.5.0-dev.68",
|
|
46
|
+
"@prisma-next/sql-operations": "0.5.0-dev.68",
|
|
47
|
+
"@prisma-next/sql-runtime": "0.5.0-dev.68",
|
|
47
48
|
"@prisma-next/test-utils": "0.0.1",
|
|
48
|
-
"@prisma-next/sql-
|
|
49
|
+
"@prisma-next/sql-contract-emitter": "0.5.0-dev.68",
|
|
49
50
|
"@prisma-next/tsconfig": "0.0.0",
|
|
50
|
-
"@prisma-next/sql-contract": "0.5.0-dev.67",
|
|
51
51
|
"@prisma-next/tsdown": "0.0.0"
|
|
52
52
|
},
|
|
53
53
|
"exports": {
|
package/src/commands/db-init.ts
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
} from '../utils/cli-errors';
|
|
14
14
|
import type { MigrationCommandOptions } from '../utils/command-helpers';
|
|
15
15
|
import {
|
|
16
|
+
resolveMigrationPaths,
|
|
16
17
|
sanitizeErrorMessage,
|
|
17
18
|
setCommandDescriptions,
|
|
18
19
|
setCommandExamples,
|
|
@@ -111,14 +112,23 @@ async function executeDbInitCommand(
|
|
|
111
112
|
if (!ctxResult.ok) {
|
|
112
113
|
return ctxResult;
|
|
113
114
|
}
|
|
114
|
-
const { client, contractJson, dbConnection, onProgress, contractPathAbsolute } =
|
|
115
|
+
const { client, config, contractJson, dbConnection, onProgress, contractPathAbsolute } =
|
|
116
|
+
ctxResult.value;
|
|
117
|
+
|
|
118
|
+
// The aggregate loader (loader → planner → runner pipeline) catches
|
|
119
|
+
// layout / drift / disjointness violations on its own; the legacy
|
|
120
|
+
// per-space precheck + marker-check helpers are no longer needed at
|
|
121
|
+
// this surface. Marker-vs-on-disk drift surfaces through the planner's
|
|
122
|
+
// graph-walk strategy.
|
|
123
|
+
const { migrationsDir } = resolveMigrationPaths(options.config, config);
|
|
115
124
|
|
|
116
125
|
try {
|
|
117
|
-
|
|
126
|
+
await client.connect(dbConnection);
|
|
127
|
+
|
|
118
128
|
const result = await client.dbInit({
|
|
119
129
|
contract: contractJson,
|
|
120
130
|
mode: options.dryRun ? 'plan' : 'apply',
|
|
121
|
-
|
|
131
|
+
migrationsDir,
|
|
122
132
|
onProgress,
|
|
123
133
|
});
|
|
124
134
|
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
} from '../utils/cli-errors';
|
|
15
15
|
import type { MigrationCommandOptions } from '../utils/command-helpers';
|
|
16
16
|
import {
|
|
17
|
+
resolveMigrationPaths,
|
|
17
18
|
sanitizeErrorMessage,
|
|
18
19
|
setCommandDescriptions,
|
|
19
20
|
setCommandExamples,
|
|
@@ -80,14 +81,17 @@ async function executeDbUpdateCommand(
|
|
|
80
81
|
if (!ctxResult.ok) {
|
|
81
82
|
return ctxResult;
|
|
82
83
|
}
|
|
83
|
-
const { client, contractJson, dbConnection, onProgress, contractPathAbsolute } =
|
|
84
|
+
const { client, config, contractJson, dbConnection, onProgress, contractPathAbsolute } =
|
|
85
|
+
ctxResult.value;
|
|
86
|
+
const { migrationsDir } = resolveMigrationPaths(options.config, config);
|
|
84
87
|
|
|
85
88
|
try {
|
|
86
|
-
|
|
89
|
+
await client.connect(dbConnection);
|
|
90
|
+
|
|
87
91
|
const result = await client.dbUpdate({
|
|
88
92
|
contract: contractJson,
|
|
89
93
|
mode: options.dryRun ? 'plan' : 'apply',
|
|
90
|
-
|
|
94
|
+
migrationsDir,
|
|
91
95
|
...(flags.yes ? { acceptDataLoss: true } : {}),
|
|
92
96
|
onProgress,
|
|
93
97
|
});
|
|
@@ -27,10 +27,12 @@ import {
|
|
|
27
27
|
errorTargetMismatch,
|
|
28
28
|
errorUnexpected,
|
|
29
29
|
} from '../utils/cli-errors';
|
|
30
|
+
import { combineSchemaResults } from '../utils/combine-schema-results';
|
|
30
31
|
import {
|
|
31
32
|
addGlobalOptions,
|
|
32
33
|
maskConnectionUrl,
|
|
33
34
|
resolveContractPath,
|
|
35
|
+
resolveMigrationPaths,
|
|
34
36
|
setCommandDescriptions,
|
|
35
37
|
setCommandExamples,
|
|
36
38
|
} from '../utils/command-helpers';
|
|
@@ -346,22 +348,42 @@ async function executeDbVerifyCommand(
|
|
|
346
348
|
const setupResult = await resolveVerifySetup(paths, options, mode);
|
|
347
349
|
if (!setupResult.ok) return setupResult;
|
|
348
350
|
const { contractJson, dbConnection, contractPathAbsolute } = setupResult.value;
|
|
351
|
+
const { migrationsDir } = resolveMigrationPaths(options.config, setupResult.value.config);
|
|
349
352
|
|
|
350
353
|
const client = createVerifyClient(setupResult.value);
|
|
351
354
|
const onProgress = createProgressAdapter({ ui, flags });
|
|
352
355
|
|
|
353
356
|
try {
|
|
357
|
+
// Single-contract marker verification preserved for the existing
|
|
358
|
+
// marker / target / hash failure surface (`PN-RUN-3001/3002/3003`).
|
|
359
|
+
// The aggregate verifier (run below for the per-space marker /
|
|
360
|
+
// schema checks) does not duplicate this: it concerns itself with
|
|
361
|
+
// marker-vs-on-disk and orphan-marker drift, not the
|
|
362
|
+
// hash-mismatch-against-the-app-contract lane that today's
|
|
363
|
+
// `client.verify` covers.
|
|
354
364
|
const verifyResult = await client.verify({
|
|
355
365
|
contract: contractJson,
|
|
356
366
|
connection: dbConnection,
|
|
357
367
|
onProgress,
|
|
358
368
|
});
|
|
359
369
|
|
|
360
|
-
// If verification failed, map to CLI structured error
|
|
361
370
|
if (!verifyResult.ok) {
|
|
362
371
|
return notOk(mapVerifyFailure(verifyResult));
|
|
363
372
|
}
|
|
364
373
|
|
|
374
|
+
// Aggregate verifier (loader → verifier pipeline). Runs the layout
|
|
375
|
+
// precheck, marker-aware per-space verifier, and (full mode only)
|
|
376
|
+
// per-space pre-projected schema verification (closes F23).
|
|
377
|
+
const aggregateResult = await client.dbVerify({
|
|
378
|
+
contract: contractJson,
|
|
379
|
+
migrationsDir,
|
|
380
|
+
strict: options.strict ?? false,
|
|
381
|
+
skipSchema: mode === 'marker-only',
|
|
382
|
+
skipMarker: false,
|
|
383
|
+
onProgress,
|
|
384
|
+
});
|
|
385
|
+
if (!aggregateResult.ok) return notOk(aggregateResult.failure);
|
|
386
|
+
|
|
365
387
|
if (mode === 'marker-only') {
|
|
366
388
|
return ok({
|
|
367
389
|
ok: true,
|
|
@@ -381,14 +403,13 @@ async function executeDbVerifyCommand(
|
|
|
381
403
|
});
|
|
382
404
|
}
|
|
383
405
|
|
|
384
|
-
const
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
return notOk(schemaVerifyResult);
|
|
406
|
+
const combined = combineSchemaResults(
|
|
407
|
+
aggregateResult.value.schemaResults,
|
|
408
|
+
aggregateResult.value.appSpaceId,
|
|
409
|
+
options.strict ?? false,
|
|
410
|
+
);
|
|
411
|
+
if (!combined.ok) {
|
|
412
|
+
return notOk(combined);
|
|
392
413
|
}
|
|
393
414
|
|
|
394
415
|
return ok({
|
|
@@ -401,9 +422,9 @@ async function executeDbVerifyCommand(
|
|
|
401
422
|
...ifDefined('missingCodecs', verifyResult.missingCodecs),
|
|
402
423
|
...ifDefined('codecCoverageSkipped', verifyResult.codecCoverageSkipped),
|
|
403
424
|
schema: {
|
|
404
|
-
summary:
|
|
405
|
-
counts:
|
|
406
|
-
strict:
|
|
425
|
+
summary: combined.summary,
|
|
426
|
+
counts: combined.schema.counts,
|
|
427
|
+
strict: combined.meta?.strict ?? false,
|
|
407
428
|
},
|
|
408
429
|
meta: {
|
|
409
430
|
...(verifyResult.meta ?? {}),
|
|
@@ -429,19 +450,30 @@ async function executeDbSchemaOnlyVerifyCommand(
|
|
|
429
450
|
const setupResult = await resolveVerifySetup(paths, options, 'schema-only');
|
|
430
451
|
if (!setupResult.ok) return setupResult;
|
|
431
452
|
const { contractJson, dbConnection, contractPathAbsolute } = setupResult.value;
|
|
453
|
+
const { migrationsDir } = resolveMigrationPaths(options.config, setupResult.value.config);
|
|
432
454
|
|
|
433
455
|
const client = createVerifyClient(setupResult.value);
|
|
434
456
|
const onProgress = createProgressAdapter({ ui, flags });
|
|
435
457
|
|
|
436
458
|
try {
|
|
437
|
-
|
|
459
|
+
await client.connect(dbConnection);
|
|
460
|
+
const aggregateResult = await client.dbVerify({
|
|
438
461
|
contract: contractJson,
|
|
462
|
+
migrationsDir,
|
|
439
463
|
strict: options.strict ?? false,
|
|
440
|
-
|
|
464
|
+
skipSchema: false,
|
|
465
|
+
skipMarker: true,
|
|
441
466
|
onProgress,
|
|
442
467
|
});
|
|
468
|
+
if (!aggregateResult.ok) return notOk(aggregateResult.failure);
|
|
443
469
|
|
|
444
|
-
return ok(
|
|
470
|
+
return ok(
|
|
471
|
+
combineSchemaResults(
|
|
472
|
+
aggregateResult.value.schemaResults,
|
|
473
|
+
aggregateResult.value.appSpaceId,
|
|
474
|
+
options.strict ?? false,
|
|
475
|
+
),
|
|
476
|
+
);
|
|
445
477
|
} catch (error) {
|
|
446
478
|
return wrapVerifyError(error, contractPathAbsolute, 'db verify --schema-only');
|
|
447
479
|
} finally {
|
|
@@ -113,7 +113,7 @@ export function createInitCommand(): Command {
|
|
|
113
113
|
* we honour it (e.g. testing flows where stdin is stubbed).
|
|
114
114
|
*
|
|
115
115
|
* Exported so callers and tests can derive the same value without
|
|
116
|
-
* touching `process` globals
|
|
116
|
+
* touching `process` globals.
|
|
117
117
|
*/
|
|
118
118
|
export function deriveCanPrompt(opts: {
|
|
119
119
|
readonly flagsInteractive: boolean | undefined;
|
|
@@ -569,7 +569,7 @@ async function runInstall(ctx: {
|
|
|
569
569
|
/**
|
|
570
570
|
* FR2.1 — set when the user already declares `@types/node` directly in
|
|
571
571
|
* `dependencies` or `devDependencies`. We then skip adding it so a
|
|
572
|
-
*
|
|
572
|
+
* locked major (e.g. `^18` for a Node 18 runtime) survives `init`
|
|
573
573
|
* unchanged. Transitive presence is intentionally ignored: detecting
|
|
574
574
|
* it requires lockfile introspection and the realistic clobber risk
|
|
575
575
|
* is the direct-pin case.
|
|
@@ -585,7 +585,7 @@ async function runInstall(ctx: {
|
|
|
585
585
|
// Pin it as a devDep rather than relying on a transitive resolution
|
|
586
586
|
// through `dotenv` (whose types bundle is internal and not guaranteed
|
|
587
587
|
// across versions). Skip when the user already declares `@types/node`
|
|
588
|
-
// directly so a
|
|
588
|
+
// directly so a locked major (e.g. `^18` for a Node 18 runtime) is
|
|
589
589
|
// preserved. Listed last so the install log still leads with the
|
|
590
590
|
// user-relevant `prisma-next` line.
|
|
591
591
|
const devDeps = hasTypesNode ? ['prisma-next'] : ['prisma-next', '@types/node'];
|
|
@@ -110,7 +110,7 @@ async function executeMigrationApplyCommand(
|
|
|
110
110
|
startTime: number,
|
|
111
111
|
): Promise<Result<MigrationApplyResult, CliStructuredErrorType>> {
|
|
112
112
|
const config = await loadConfig(options.config);
|
|
113
|
-
const { configPath,
|
|
113
|
+
const { configPath, appMigrationsDir, appMigrationsRelative, refsDir } = resolveMigrationPaths(
|
|
114
114
|
options.config,
|
|
115
115
|
config,
|
|
116
116
|
);
|
|
@@ -173,7 +173,7 @@ async function executeMigrationApplyCommand(
|
|
|
173
173
|
if (!flags.json && !flags.quiet) {
|
|
174
174
|
const details: Array<{ label: string; value: string }> = [
|
|
175
175
|
{ label: 'config', value: configPath },
|
|
176
|
-
{ label: 'migrations', value:
|
|
176
|
+
{ label: 'migrations', value: appMigrationsRelative },
|
|
177
177
|
];
|
|
178
178
|
if (typeof dbConnection === 'string') {
|
|
179
179
|
details.push({
|
|
@@ -197,7 +197,7 @@ async function executeMigrationApplyCommand(
|
|
|
197
197
|
// Read migrations and build migration chain model (offline — no DB needed)
|
|
198
198
|
let migrations: Awaited<ReturnType<typeof loadMigrationPackages>>;
|
|
199
199
|
try {
|
|
200
|
-
migrations = await loadMigrationPackages(
|
|
200
|
+
migrations = await loadMigrationPackages(appMigrationsDir);
|
|
201
201
|
} catch (error) {
|
|
202
202
|
if (MigrationToolsError.is(error)) {
|
|
203
203
|
return notOk(mapMigrationToolsError(error));
|
|
@@ -248,9 +248,9 @@ async function executeMigrationApplyCommand(
|
|
|
248
248
|
if (marker?.storageHash) {
|
|
249
249
|
return notOk(
|
|
250
250
|
errorRuntime('Database has state but no migrations exist', {
|
|
251
|
-
why: `The database marker hash "${marker.storageHash}" exists but no migrations were found in ${
|
|
251
|
+
why: `The database marker hash "${marker.storageHash}" exists but no migrations were found in ${appMigrationsRelative}`,
|
|
252
252
|
fix: 'Ensure the migrations directory is correct. If the database was managed with `db init` or `db update`, run `prisma-next db sign` to update the marker.',
|
|
253
|
-
meta: { markerHash: marker.storageHash, migrationsDir:
|
|
253
|
+
meta: { markerHash: marker.storageHash, migrationsDir: appMigrationsRelative },
|
|
254
254
|
}),
|
|
255
255
|
);
|
|
256
256
|
}
|
|
@@ -258,9 +258,9 @@ async function executeMigrationApplyCommand(
|
|
|
258
258
|
if (destinationHash !== EMPTY_CONTRACT_HASH) {
|
|
259
259
|
return notOk(
|
|
260
260
|
errorRuntime('Current contract has no planned migrations', {
|
|
261
|
-
why: `No migrations were found in ${
|
|
261
|
+
why: `No migrations were found in ${appMigrationsRelative}, but current contract hash is "${destinationHash}"`,
|
|
262
262
|
fix: 'Run `prisma-next migration plan` to create a migration for the current contract.',
|
|
263
|
-
meta: { destinationHash, migrationsDir:
|
|
263
|
+
meta: { destinationHash, migrationsDir: appMigrationsRelative },
|
|
264
264
|
}),
|
|
265
265
|
);
|
|
266
266
|
}
|
|
@@ -295,7 +295,7 @@ async function executeMigrationApplyCommand(
|
|
|
295
295
|
if (markerHash !== undefined && !migrations.graph.nodes.has(markerHash)) {
|
|
296
296
|
return notOk(
|
|
297
297
|
errorRuntime('Database marker does not match any known migration', {
|
|
298
|
-
why: `The database marker hash "${markerHash}" is not found in the migration history at ${
|
|
298
|
+
why: `The database marker hash "${markerHash}" is not found in the migration history at ${appMigrationsRelative}`,
|
|
299
299
|
fix: 'Ensure the migrations directory matches this database. If the database was managed with `db init` or `db update`, run `prisma-next db sign` to update the marker.',
|
|
300
300
|
meta: { markerHash, knownNodes: [...migrations.graph.nodes] },
|
|
301
301
|
}),
|
|
@@ -305,7 +305,7 @@ async function executeMigrationApplyCommand(
|
|
|
305
305
|
if (!migrations.graph.nodes.has(destinationHash)) {
|
|
306
306
|
return notOk(
|
|
307
307
|
errorRuntime('Current contract has no planned migration path', {
|
|
308
|
-
why: `Current contract hash "${destinationHash}" is not present in the migration history at ${
|
|
308
|
+
why: `Current contract hash "${destinationHash}" is not present in the migration history at ${appMigrationsRelative}`,
|
|
309
309
|
fix: 'Run `prisma-next migration plan` to create a migration for the current contract, then re-run apply.',
|
|
310
310
|
meta: { destinationHash, knownNodes: [...migrations.graph.nodes] },
|
|
311
311
|
}),
|
|
@@ -70,7 +70,7 @@ async function executeMigrationNewCommand(
|
|
|
70
70
|
options: MigrationNewOptions,
|
|
71
71
|
): Promise<Result<MigrationNewResult, CliStructuredError>> {
|
|
72
72
|
const config = await loadConfig(options.config);
|
|
73
|
-
const {
|
|
73
|
+
const { appMigrationsDir, appMigrationsRelative } = resolveMigrationPaths(options.config, config);
|
|
74
74
|
|
|
75
75
|
const contractPathAbsolute = resolveContractPath(config);
|
|
76
76
|
|
|
@@ -120,7 +120,7 @@ async function executeMigrationNewCommand(
|
|
|
120
120
|
let fromContractSourceDir: string | null = null;
|
|
121
121
|
|
|
122
122
|
try {
|
|
123
|
-
const packages = await readMigrationsDir(
|
|
123
|
+
const packages = await readMigrationsDir(appMigrationsDir);
|
|
124
124
|
|
|
125
125
|
if (packages.length > 0) {
|
|
126
126
|
const graph = reconstructGraph(packages);
|
|
@@ -130,7 +130,7 @@ async function executeMigrationNewCommand(
|
|
|
130
130
|
if (!match) {
|
|
131
131
|
return notOk(
|
|
132
132
|
errorRuntime('Starting contract not found', {
|
|
133
|
-
why: `No migration with to hash matching "${options.from}" exists in ${
|
|
133
|
+
why: `No migration with to hash matching "${options.from}" exists in ${appMigrationsRelative}`,
|
|
134
134
|
fix: 'Check that the --from hash matches a known migration target hash.',
|
|
135
135
|
}),
|
|
136
136
|
);
|
|
@@ -171,7 +171,7 @@ async function executeMigrationNewCommand(
|
|
|
171
171
|
const timestamp = new Date();
|
|
172
172
|
const slug = options.name ?? 'migration';
|
|
173
173
|
const dirName = formatMigrationDirName(timestamp, slug);
|
|
174
|
-
const packageDir = join(
|
|
174
|
+
const packageDir = join(appMigrationsDir, dirName);
|
|
175
175
|
|
|
176
176
|
// `migration new` scaffolds an empty `migration.ts` for the user to
|
|
177
177
|
// fill, so we attest over `ops: []`. Re-running self-emit after the
|