@prisma-next/cli 0.4.2 → 0.4.4
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/README.md +56 -21
- package/dist/cli-errors-By1iVE3z.mjs +34 -0
- package/dist/cli-errors-By1iVE3z.mjs.map +1 -0
- package/dist/{cli-errors-BFYgBH3L.d.mts → cli-errors-DDeVsP2Y.d.mts} +1 -0
- package/dist/cli.mjs +19 -16
- package/dist/cli.mjs.map +1 -1
- package/dist/{client-CrsnY58k.mjs → client-1JqqkiC7.mjs} +45 -20
- package/dist/client-1JqqkiC7.mjs.map +1 -0
- package/dist/commands/contract-emit.d.mts.map +1 -1
- package/dist/commands/contract-emit.mjs +7 -7
- package/dist/commands/contract-infer.d.mts.map +1 -1
- package/dist/commands/contract-infer.mjs +8 -8
- package/dist/commands/db-init.d.mts.map +1 -1
- package/dist/commands/db-init.mjs +11 -10
- package/dist/commands/db-init.mjs.map +1 -1
- package/dist/commands/db-schema.mjs +8 -8
- package/dist/commands/db-sign.mjs +8 -8
- package/dist/commands/db-update.mjs +10 -10
- package/dist/commands/db-update.mjs.map +1 -1
- package/dist/commands/db-verify.mjs +10 -10
- package/dist/commands/migration-apply.d.mts +5 -2
- package/dist/commands/migration-apply.d.mts.map +1 -1
- package/dist/commands/migration-apply.mjs +56 -57
- package/dist/commands/migration-apply.mjs.map +1 -1
- package/dist/commands/migration-new.d.mts.map +1 -1
- package/dist/commands/migration-new.mjs +26 -32
- package/dist/commands/migration-new.mjs.map +1 -1
- package/dist/commands/migration-plan.d.mts +14 -5
- package/dist/commands/migration-plan.d.mts.map +1 -1
- package/dist/commands/migration-plan.mjs +45 -48
- package/dist/commands/migration-plan.mjs.map +1 -1
- package/dist/commands/migration-ref.d.mts +1 -1
- package/dist/commands/migration-ref.d.mts.map +1 -1
- package/dist/commands/migration-ref.mjs +6 -10
- package/dist/commands/migration-ref.mjs.map +1 -1
- package/dist/commands/migration-show.d.mts +13 -7
- package/dist/commands/migration-show.d.mts.map +1 -1
- package/dist/commands/migration-show.mjs +28 -29
- package/dist/commands/migration-show.mjs.map +1 -1
- package/dist/commands/migration-status.d.mts +23 -5
- package/dist/commands/migration-status.d.mts.map +1 -1
- package/dist/commands/migration-status.mjs +8 -8
- package/dist/{config-loader-C25b63rJ.mjs → config-loader-ih8ViDb_.mjs} +2 -2
- package/dist/config-loader-ih8ViDb_.mjs.map +1 -0
- package/dist/config-loader.mjs +1 -1
- package/dist/contract-emit-DS5NzZh2.mjs +6 -0
- package/dist/contract-emit-RZBWzkop.mjs +329 -0
- package/dist/contract-emit-RZBWzkop.mjs.map +1 -0
- package/dist/contract-emit-rt_Nmdwq.mjs +150 -0
- package/dist/contract-emit-rt_Nmdwq.mjs.map +1 -0
- package/dist/{contract-enrichment-CAOELa-H.mjs → contract-enrichment-4Ptgw3Pe.mjs} +1 -1
- package/dist/{contract-enrichment-CAOELa-H.mjs.map → contract-enrichment-4Ptgw3Pe.mjs.map} +1 -1
- package/dist/{contract-infer-D9cC3rJm.mjs → contract-infer-Cf5J2wVg.mjs} +11 -19
- package/dist/contract-infer-Cf5J2wVg.mjs.map +1 -0
- package/dist/exports/control-api.d.mts +86 -21
- package/dist/exports/control-api.d.mts.map +1 -1
- package/dist/exports/control-api.mjs +7 -7
- package/dist/exports/index.mjs +8 -8
- package/dist/{framework-components-Cr--XBKy.mjs → framework-components-Bgcre3Z6.mjs} +2 -2
- package/dist/{framework-components-Cr--XBKy.mjs.map → framework-components-Bgcre3Z6.mjs.map} +1 -1
- package/dist/{init-m8x0UoPY.mjs → init-DAbQMxIR.mjs} +5 -5
- package/dist/{init-m8x0UoPY.mjs.map → init-DAbQMxIR.mjs.map} +1 -1
- package/dist/{inspect-live-schema-yrHAvG71.mjs → inspect-live-schema-LWtXfxm_.mjs} +9 -9
- package/dist/inspect-live-schema-LWtXfxm_.mjs.map +1 -0
- package/dist/migration-cli.d.mts +41 -11
- package/dist/migration-cli.d.mts.map +1 -1
- package/dist/migration-cli.mjs +308 -84
- package/dist/migration-cli.mjs.map +1 -1
- package/dist/{migration-command-scaffold-B3B09et6.mjs → migration-command-scaffold-CU452v9h.mjs} +7 -7
- package/dist/{migration-command-scaffold-B3B09et6.mjs.map → migration-command-scaffold-CU452v9h.mjs.map} +1 -1
- package/dist/{migration-status-DUMiH8_G.mjs → migration-status-DoPrFIOQ.mjs} +114 -57
- package/dist/migration-status-DoPrFIOQ.mjs.map +1 -0
- package/dist/{migrations-Bo5WtTla.mjs → migrations-MEoKMiV5.mjs} +42 -21
- package/dist/migrations-MEoKMiV5.mjs.map +1 -0
- package/dist/{progress-adapter-DvQWB1nK.mjs → progress-adapter-DgRGldpT.mjs} +1 -1
- package/dist/{progress-adapter-DvQWB1nK.mjs.map → progress-adapter-DgRGldpT.mjs.map} +1 -1
- package/dist/{result-handler-Ba3zWQsI.mjs → result-handler-Ch6hVnOo.mjs} +34 -19
- package/dist/{result-handler-Ba3zWQsI.mjs.map → result-handler-Ch6hVnOo.mjs.map} +1 -1
- package/dist/{terminal-ui-C3ZLwQxK.mjs → terminal-ui-u2YgKghu.mjs} +1 -1
- package/dist/{terminal-ui-C3ZLwQxK.mjs.map → terminal-ui-u2YgKghu.mjs.map} +1 -1
- package/dist/{verify-Bkycc-Tf.mjs → verify-BT9tgCOH.mjs} +2 -2
- package/dist/{verify-Bkycc-Tf.mjs.map → verify-BT9tgCOH.mjs.map} +1 -1
- package/package.json +18 -17
- package/src/cli.ts +32 -6
- package/src/commands/contract-emit.ts +67 -163
- package/src/commands/contract-infer.ts +7 -20
- package/src/commands/db-init.ts +1 -0
- package/src/commands/db-update.ts +1 -1
- package/src/commands/inspect-live-schema.ts +10 -5
- package/src/commands/migration-apply.ts +84 -63
- package/src/commands/migration-new.ts +28 -34
- package/src/commands/migration-plan.ts +80 -56
- package/src/commands/migration-ref.ts +8 -7
- package/src/commands/migration-show.ts +53 -36
- package/src/commands/migration-status.ts +194 -58
- package/src/config-path-validation.ts +0 -1
- package/src/control-api/client.ts +21 -0
- package/src/control-api/operations/contract-emit.ts +198 -115
- package/src/control-api/operations/db-init.ts +10 -6
- package/src/control-api/operations/db-update.ts +10 -6
- package/src/control-api/operations/migration-apply.ts +30 -9
- package/src/control-api/types.ts +69 -7
- package/src/exports/control-api.ts +2 -1
- package/src/migration-cli.ts +445 -122
- package/src/utils/cli-errors.ts +49 -2
- package/src/utils/command-helpers.ts +45 -23
- package/src/utils/emit-queue.ts +26 -0
- package/src/utils/formatters/graph-migration-mapper.ts +7 -3
- package/src/utils/formatters/migrations.ts +62 -26
- package/src/utils/publish-contract-artifact-pair.ts +134 -0
- package/dist/cli-errors-Cd79vmTH.mjs +0 -5
- package/dist/client-CrsnY58k.mjs.map +0 -1
- package/dist/config-loader-C25b63rJ.mjs.map +0 -1
- package/dist/contract-emit-DxgyXrqV.mjs +0 -6
- package/dist/contract-emit-NJ01hiiv.mjs +0 -195
- package/dist/contract-emit-NJ01hiiv.mjs.map +0 -1
- package/dist/contract-emit-V5SSitUT.mjs +0 -122
- package/dist/contract-emit-V5SSitUT.mjs.map +0 -1
- package/dist/contract-infer-D9cC3rJm.mjs.map +0 -1
- package/dist/extract-operation-statements-DsFfxXVZ.mjs +0 -13
- package/dist/extract-operation-statements-DsFfxXVZ.mjs.map +0 -1
- package/dist/extract-sql-ddl-D9UbZDyz.mjs +0 -26
- package/dist/extract-sql-ddl-D9UbZDyz.mjs.map +0 -1
- package/dist/inspect-live-schema-yrHAvG71.mjs.map +0 -1
- package/dist/migration-status-DUMiH8_G.mjs.map +0 -1
- package/dist/migrations-Bo5WtTla.mjs.map +0 -1
- package/dist/validate-contract-deps-B_Cs29TL.mjs +0 -37
- package/dist/validate-contract-deps-B_Cs29TL.mjs.map +0 -1
- package/src/control-api/operations/extract-operation-statements.ts +0 -14
- package/src/control-api/operations/extract-sql-ddl.ts +0 -47
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"migration-cli.mjs","names":["configPath: string | undefined","raw: string"],"sources":["../src/migration-cli.ts"],"sourcesContent":["/**\n * The migration-file CLI interface: the actor invoked when the author runs\n * `node migration.ts` directly.\n *\n * Naming: this is *not* a \"migration runner\" in the apply-time sense. The\n * apply-time runner is the thing `prisma-next migration apply` uses to\n * execute migration JSON ops against a database. `MigrationCLI` is the\n * tiny CLI surface owned by an authored `migration.ts` file: parse the\n * file's argv, load the project's `prisma-next.config.ts`, assemble a\n * `ControlStack`, instantiate the migration class, and serialize.\n *\n * The user authors a migration class, then calls\n * `MigrationCLI.run(import.meta.url, MigrationClass)` at module scope\n * after the class definition. When the file is invoked as a node\n * entrypoint (`node migration.ts`), the CLI:\n *\n * 1. Detects whether the file is the direct entrypoint (no-op when imported).\n * 2. Parses CLI args (`--help`, `--dry-run`, `--config <path>`).\n * 3. Loads the project's `prisma-next.config.ts` via the same `loadConfig`\n * the CLI commands use, walking up from the migration file's directory.\n * 4. Probe-instantiates the migration class without a stack so it can read\n * `targetId` and verify it matches `config.target.targetId`\n * (`PN-MIG-2006` on mismatch) before any stack-driven adapter\n * construction runs.\n * 5. Assembles a `ControlStack` from the loaded config descriptors and\n * constructs the migration with that stack.\n * 6. Reads any previously-scaffolded `migration.json`, then calls\n * `buildMigrationArtifacts` from `@prisma-next/migration-tools` to\n * produce in-memory `ops.json` + `migration.json` content. Persists\n * the result to disk (or prints in dry-run mode).\n *\n * File I/O lives here, in `@prisma-next/cli`: this is the only place\n * that legitimately combines config loading, stack assembly, and\n * on-disk persistence. `@prisma-next/migration-tools` owns the pure\n * conversion from a `Migration` instance to artifact strings; `Migration`\n * stays a pure abstract class.\n */\n\nimport { readFileSync, writeFileSync } from 'node:fs';\nimport { fileURLToPath } from 'node:url';\nimport { CliStructuredError, errorMigrationCliInvalidConfigArg } from '@prisma-next/errors/control';\nimport { errorMigrationTargetMismatch } from '@prisma-next/errors/migration';\nimport { createControlStack } from '@prisma-next/framework-components/control';\nimport {\n buildMigrationArtifacts,\n isDirectEntrypoint,\n type Migration,\n printMigrationHelp,\n} from '@prisma-next/migration-tools/migration';\nimport type { MigrationManifest } from '@prisma-next/migration-tools/types';\nimport { dirname, join } from 'pathe';\nimport { loadConfig } from './config-loader';\n\n/**\n * Constructor shape accepted by `MigrationCLI.run`. `Migration` subclasses\n * accept an optional `ControlStack` in their constructor (each subclass\n * narrows the stack to its own family/target generics); the CLI always\n * passes one assembled from the loaded config. We use a rest-args `any[]`\n * constructor signature so that subclass constructors with narrower\n * parameter types remain assignable - constructor type compatibility in\n * TS is contravariant in the parameter, and a wider `unknown` parameter\n * on the alias side would reject any narrower subclass signature.\n *\n * The CLI only ever passes one argument (`new MigrationClass(stack)`);\n * the rest-arity is purely a type-compatibility concession for subclass\n * constructors that declare narrower parameter types, not an extension\n * point for additional construction arguments.\n */\n// biome-ignore lint/suspicious/noExplicitAny: see JSDoc - rest args with any are the idiomatic TS pattern for accepting arbitrary subclass constructor signatures\nexport type MigrationConstructor = new (...args: any[]) => Migration;\n\ninterface ParsedArgs {\n readonly help: boolean;\n readonly dryRun: boolean;\n readonly configPath: string | undefined;\n}\n\n/**\n * Parse the subset of `process.argv` that `MigrationCLI.run` cares about.\n * Recognised flags: `--help`, `--dry-run`, `--config <path>` /\n * `--config=<path>`. Unknown flags are ignored to keep the surface\n * forgiving for ad-hoc tooling that wraps a migration file.\n *\n * Throws `errorMigrationCliInvalidConfigArg` (`PN-CLI-4012`) when\n * `--config` is missing its path argument or is followed by another flag\n * (e.g. `--config --dry-run`); silently consuming the next flag would\n * either drop dry-run handling or serialize against the wrong project.\n *\n * NOTE: this hand-rolled parser is a known wart, tracked separately by\n * TML-2318 (\"Migration CLI: replace handrolled arg parser with shared\n * CLI library\"). Until that lands the surface is intentionally tiny.\n */\nfunction parseArgs(argv: readonly string[]): ParsedArgs {\n let help = false;\n let dryRun = false;\n let configPath: string | undefined;\n\n for (let i = 0; i < argv.length; i++) {\n const arg = argv[i]!;\n if (arg === '--help' || arg === '-h') {\n help = true;\n } else if (arg === '--dry-run') {\n dryRun = true;\n } else if (arg === '--config') {\n const next = argv[i + 1];\n if (next === undefined) {\n throw errorMigrationCliInvalidConfigArg();\n }\n if (next.startsWith('-')) {\n throw errorMigrationCliInvalidConfigArg({ nextToken: next });\n }\n configPath = next;\n i++;\n } else if (arg.startsWith('--config=')) {\n configPath = arg.slice('--config='.length);\n }\n }\n\n return { help, dryRun, configPath };\n}\n\n/**\n * The CLI surface invoked by an authored `migration.ts` file. Exposed as\n * a class with a static `run` method (rather than a free function) to\n * give the concept a stable identity in the ubiquitous language: this is\n * the \"migration-file CLI\", distinct from the apply-time runner that\n * executes migration JSON ops.\n *\n * Currently a single static method. Future surface (e.g. a programmatic\n * `MigrationCLI.serializeOnly(...)` for tests, or extra subcommands) can\n * land here without changing the import shape used by every authored\n * migration.\n */\n// biome-ignore lint/complexity/noStaticOnlyClass: see JSDoc - intentional class facade for the migration-file CLI surface; future methods will share state derived from argv/config.\nexport class MigrationCLI {\n /**\n * Orchestrates a class-flow `migration.ts` script run. Awaitable:\n * callers may `await MigrationCLI.run(...)` to surface async failures\n * from config loading, but the typical usage pattern (top-level call\n * after the class definition) does not require awaiting because\n * node's module evaluation keeps the promise alive until completion.\n *\n * Any throwable inside this function must surface through the internal\n * try/catch — script callers do not await, so an unhandled rejection\n * would silently exit 0. Treat the try/catch as load-bearing for the\n * no-await usage pattern.\n */\n static async run(importMetaUrl: string, MigrationClass: MigrationConstructor): Promise<void> {\n if (!importMetaUrl) return;\n if (!isDirectEntrypoint(importMetaUrl)) return;\n\n try {\n const args = parseArgs(process.argv.slice(2));\n\n if (args.help) {\n printMigrationHelp();\n return;\n }\n\n const migrationFile = fileURLToPath(importMetaUrl);\n const migrationDir = dirname(migrationFile);\n\n const config = await loadConfig(args.configPath);\n\n // Probe-instantiate without a stack so we can read `targetId` before\n // any target-specific constructor side effects (e.g.\n // `PostgresMigration`'s `stack.adapter.create(stack)`) run. Concrete\n // subclasses are required to accept the no-arg form; the abstract\n // `Migration` constructor declares `stack?` and target subclasses\n // (Postgres, Mongo) propagate that optionality. This makes the\n // target-mismatch guard fail fast with `PN-MIG-2006` before any\n // stack-driven adapter construction begins, even if the wrong-target\n // adapter's `create` would otherwise succeed and silently misshapen\n // the stored adapter cast.\n const probe = new MigrationClass();\n\n if (probe.targetId !== config.target.targetId) {\n throw errorMigrationTargetMismatch({\n migrationTargetId: probe.targetId,\n configTargetId: config.target.targetId,\n });\n }\n\n const stack = createControlStack(config);\n const instance = new MigrationClass(stack);\n\n serializeMigrationToDisk(instance, migrationDir, args.dryRun);\n } catch (err) {\n if (CliStructuredError.is(err)) {\n process.stderr.write(`${err.message}: ${err.why}\\n`);\n } else {\n process.stderr.write(`${err instanceof Error ? err.message : String(err)}\\n`);\n }\n process.exitCode = 1;\n }\n }\n}\n\n/**\n * Read a previously-scaffolded `migration.json` from disk, returning\n * `null` when the file is missing or unparseable. The CLI feeds this into\n * `buildMigrationArtifacts` so the pure builder can preserve fields owned\n * by `migration plan` (contract bookends, hints, labels, `createdAt`)\n * across re-emits.\n */\nfunction readExistingManifest(manifestPath: string): Partial<MigrationManifest> | null {\n let raw: string;\n try {\n raw = readFileSync(manifestPath, 'utf-8');\n } catch {\n return null;\n }\n try {\n return JSON.parse(raw) as Partial<MigrationManifest>;\n } catch {\n return null;\n }\n}\n\n/**\n * Persist a migration instance's artifacts to `migrationDir`. In\n * `dryRun` mode the artifacts are printed to stdout (with the same\n * `--- migration.json --- / --- ops.json ---` framing the legacy\n * `serializeMigration` helper used) and no files are written. Otherwise\n * `ops.json` and `migration.json` are written next to `migration.ts` and\n * a confirmation line is printed.\n *\n * File I/O lives in the CLI rather than `@prisma-next/migration-tools`\n * so the migration-tools package stays focused on the pure\n * `Migration` → in-memory artifact conversion. The CLI is the only\n * legitimate site for combining config loading, stack assembly, and\n * filesystem persistence.\n */\nfunction serializeMigrationToDisk(\n instance: Migration,\n migrationDir: string,\n dryRun: boolean,\n): void {\n const manifestPath = join(migrationDir, 'migration.json');\n const existing = readExistingManifest(manifestPath);\n const { opsJson, manifestJson } = buildMigrationArtifacts(instance, existing);\n\n if (dryRun) {\n process.stdout.write(`--- migration.json ---\\n${manifestJson}\\n`);\n process.stdout.write('--- ops.json ---\\n');\n process.stdout.write(`${opsJson}\\n`);\n return;\n }\n\n writeFileSync(join(migrationDir, 'ops.json'), opsJson);\n writeFileSync(manifestPath, manifestJson);\n\n process.stdout.write(`Wrote ops.json + migration.json to ${migrationDir}\\n`);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4FA,SAAS,UAAU,MAAqC;CACtD,IAAI,OAAO;CACX,IAAI,SAAS;CACb,IAAIA;AAEJ,MAAK,IAAI,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;EACpC,MAAM,MAAM,KAAK;AACjB,MAAI,QAAQ,YAAY,QAAQ,KAC9B,QAAO;WACE,QAAQ,YACjB,UAAS;WACA,QAAQ,YAAY;GAC7B,MAAM,OAAO,KAAK,IAAI;AACtB,OAAI,SAAS,OACX,OAAM,mCAAmC;AAE3C,OAAI,KAAK,WAAW,IAAI,CACtB,OAAM,kCAAkC,EAAE,WAAW,MAAM,CAAC;AAE9D,gBAAa;AACb;aACS,IAAI,WAAW,YAAY,CACpC,cAAa,IAAI,MAAM,EAAmB;;AAI9C,QAAO;EAAE;EAAM;EAAQ;EAAY;;;;;;;;;;;;;;AAgBrC,IAAa,eAAb,MAA0B;;;;;;;;;;;;;CAaxB,aAAa,IAAI,eAAuB,gBAAqD;AAC3F,MAAI,CAAC,cAAe;AACpB,MAAI,CAAC,mBAAmB,cAAc,CAAE;AAExC,MAAI;GACF,MAAM,OAAO,UAAU,QAAQ,KAAK,MAAM,EAAE,CAAC;AAE7C,OAAI,KAAK,MAAM;AACb,wBAAoB;AACpB;;GAIF,MAAM,eAAe,QADC,cAAc,cAAc,CACP;GAE3C,MAAM,SAAS,MAAM,WAAW,KAAK,WAAW;GAYhD,MAAM,QAAQ,IAAI,gBAAgB;AAElC,OAAI,MAAM,aAAa,OAAO,OAAO,SACnC,OAAM,6BAA6B;IACjC,mBAAmB,MAAM;IACzB,gBAAgB,OAAO,OAAO;IAC/B,CAAC;AAMJ,4BAFiB,IAAI,eADP,mBAAmB,OAAO,CACE,EAEP,cAAc,KAAK,OAAO;WACtD,KAAK;AACZ,OAAI,mBAAmB,GAAG,IAAI,CAC5B,SAAQ,OAAO,MAAM,GAAG,IAAI,QAAQ,IAAI,IAAI,IAAI,IAAI;OAEpD,SAAQ,OAAO,MAAM,GAAG,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,CAAC,IAAI;AAE/E,WAAQ,WAAW;;;;;;;;;;;AAYzB,SAAS,qBAAqB,cAAyD;CACrF,IAAIC;AACJ,KAAI;AACF,QAAM,aAAa,cAAc,QAAQ;SACnC;AACN,SAAO;;AAET,KAAI;AACF,SAAO,KAAK,MAAM,IAAI;SAChB;AACN,SAAO;;;;;;;;;;;;;;;;;AAkBX,SAAS,yBACP,UACA,cACA,QACM;CACN,MAAM,eAAe,KAAK,cAAc,iBAAiB;CAEzD,MAAM,EAAE,SAAS,iBAAiB,wBAAwB,UADzC,qBAAqB,aAAa,CAC0B;AAE7E,KAAI,QAAQ;AACV,UAAQ,OAAO,MAAM,2BAA2B,aAAa,IAAI;AACjE,UAAQ,OAAO,MAAM,qBAAqB;AAC1C,UAAQ,OAAO,MAAM,GAAG,QAAQ,IAAI;AACpC;;AAGF,eAAc,KAAK,cAAc,WAAW,EAAE,QAAQ;AACtD,eAAc,cAAc,aAAa;AAEzC,SAAQ,OAAO,MAAM,sCAAsC,aAAa,IAAI"}
|
|
1
|
+
{"version":3,"file":"migration-cli.mjs","names":["KNOWN_FLAGS: readonly string[]","parsed: MigrationFileCommand","raw: string"],"sources":["../src/migration-cli.ts"],"sourcesContent":["/**\n * The migration-file CLI interface: the actor invoked when the author runs\n * `node migration.ts` directly.\n *\n * Naming: this is *not* a \"migration runner\" in the apply-time sense. The\n * apply-time runner is the thing `prisma-next migration apply` uses to\n * execute migration JSON ops against a database. `MigrationCLI` is the\n * tiny CLI surface owned by an authored `migration.ts` file: parse the\n * file's argv, load the project's `prisma-next.config.ts`, assemble a\n * `ControlStack`, instantiate the migration class, and serialize.\n *\n * The user authors a migration class, then calls\n * `MigrationCLI.run(import.meta.url, MigrationClass)` at module scope\n * after the class definition. When the file is invoked as a node\n * entrypoint (`node migration.ts`), the CLI:\n *\n * 1. Detects whether the file is the direct entrypoint (no-op when imported).\n * 2. Parses CLI args (`--help`, `--dry-run`, `--config <path>`) via\n * [clipanion](https://github.com/arcanis/clipanion).\n * 3. Loads the project's `prisma-next.config.ts` via the same `loadConfig`\n * the CLI commands use, walking up from the migration file's directory.\n * 4. Probe-instantiates the migration class without a stack so it can read\n * `targetId` and verify it matches `config.target.targetId`\n * (`PN-MIG-2006` on mismatch) before any stack-driven adapter\n * construction runs.\n * 5. Assembles a `ControlStack` from the loaded config descriptors and\n * constructs the migration with that stack.\n * 6. Reads any previously-scaffolded `migration.json`, then calls\n * `buildMigrationArtifacts` from `@prisma-next/migration-tools` to\n * produce in-memory `ops.json` + `migration.json` content. Persists\n * the result to disk (or prints in dry-run mode).\n *\n * File I/O lives here, in `@prisma-next/cli`: this is the only place\n * that legitimately combines config loading, stack assembly, and\n * on-disk persistence. `@prisma-next/migration-tools` owns the pure\n * conversion from a `Migration` instance to artifact strings; `Migration`\n * stays a pure abstract class.\n *\n * Parser library: clipanion (chosen over Commander/citty/`node:util.parseArgs`\n * for its in-process testability and runtime-agnostic execution surface; see\n * `docs/architecture docs/research/commander-friction-points.md` for the\n * evaluation rubric and the durable rationale that drove the choice).\n */\n\nimport { readFileSync, realpathSync, writeFileSync } from 'node:fs';\nimport type { Writable } from 'node:stream';\nimport { fileURLToPath } from 'node:url';\nimport {\n CliStructuredError,\n errorMigrationCliInvalidConfigArg,\n errorMigrationCliUnknownFlag,\n} from '@prisma-next/errors/control';\nimport { errorMigrationTargetMismatch } from '@prisma-next/errors/migration';\nimport { createControlStack } from '@prisma-next/framework-components/control';\nimport { errorInvalidJson, MigrationToolsError } from '@prisma-next/migration-tools/errors';\nimport type { MigrationMetadata } from '@prisma-next/migration-tools/metadata';\nimport { buildMigrationArtifacts, type Migration } from '@prisma-next/migration-tools/migration';\nimport { Cli, Command, Option, UsageError } from 'clipanion';\nimport { dirname, join } from 'pathe';\nimport { loadConfig } from './config-loader';\n\n/**\n * Constructor shape accepted by `MigrationCLI.run`. `Migration` subclasses\n * accept an optional `ControlStack` in their constructor (each subclass\n * narrows the stack to its own family/target generics); the CLI always\n * passes one assembled from the loaded config. We use a rest-args `any[]`\n * constructor signature so that subclass constructors with narrower\n * parameter types remain assignable - constructor type compatibility in\n * TS is contravariant in the parameter, and a wider `unknown` parameter\n * on the alias side would reject any narrower subclass signature.\n *\n * The CLI only ever passes one argument (`new MigrationClass(stack)`);\n * the rest-arity is purely a type-compatibility concession for subclass\n * constructors that declare narrower parameter types, not an extension\n * point for additional construction arguments.\n */\n// biome-ignore lint/suspicious/noExplicitAny: see JSDoc - rest args with any are the idiomatic TS pattern for accepting arbitrary subclass constructor signatures\nexport type MigrationConstructor = new (...args: any[]) => Migration;\n\n/**\n * Stream surface accepted by `MigrationCLI.run`'s `options.stdout` /\n * `options.stderr`. Aliases node's `Writable` because clipanion's\n * `BaseContext.stdout`/`stderr` are typed as `Writable`, and the CLI\n * forwards the injected streams into clipanion's context.\n *\n * `process.stdout` and `process.stderr` are `Writable`-shaped, so the\n * default-fallback path remains a no-op for existing two-argument\n * callers like `MigrationCLI.run(import.meta.url, MyMigration)`.\n *\n * Tests inject a `Writable` subclass that captures chunks for\n * assertions.\n */\nexport type MigrationCliWritable = Writable;\n\n/**\n * Flags exposed by the migration-file CLI.\n *\n * Must stay in sync with the `Option` declarations on\n * `MigrationFileCommand` below. This list is rendered in the\n * `errorMigrationCliUnknownFlag` envelope's `fix` text and `meta`,\n * so order matters for user-visible output (declaration order is the\n * order users see when they run `--help`).\n */\nconst KNOWN_FLAGS: readonly string[] = ['--help', '--dry-run', '--config'];\n\n/**\n * The clipanion command that owns the migration-file CLI's option\n * declarations. The class is internal — `MigrationCLI.run` is the\n * stable public surface. Adding a flag here automatically updates\n * `--help` rendering and the `KNOWN_FLAGS` list (the latter must be\n * updated in tandem).\n */\nclass MigrationFileCommand extends Command {\n static override paths = [Command.Default];\n\n static override usage = Command.Usage({\n description: 'Self-emit ops.json and migration.json from a class-flow migration',\n details: `\n Loads the project's prisma-next.config.ts, assembles a ControlStack\n from the configured target/adapter/extensions, and serializes the\n migration's operations + metadata next to this file.\n `,\n examples: [\n ['Self-emit ops.json + migration.json next to migration.ts', '$0'],\n ['Preview without writing files', '$0 --dry-run'],\n ['Use a non-default config path', '$0 --config ./custom.config.ts'],\n ],\n });\n\n dryRun = Option.Boolean('--dry-run', false, {\n description: 'Print operations to stdout without writing files',\n });\n\n config = Option.String('--config', {\n description: 'Path to prisma-next.config.ts',\n });\n\n /**\n * Unused: orchestration runs inside `MigrationCLI.run` so error\n * routing stays under our control (clipanion's `cli.run` writes\n * error output to `context.stdout`, but our contract requires\n * structured errors on stderr). `cli.process` is used to parse\n * argv into a populated `MigrationFileCommand` instance whose\n * fields drive the orchestration directly.\n */\n override async execute(): Promise<number> {\n return 0;\n }\n}\n\n/**\n * The CLI surface invoked by an authored `migration.ts` file. Exposed as\n * a class with a static `run` method (rather than a free function) to\n * give the concept a stable identity in the ubiquitous language: this is\n * the \"migration-file CLI\", distinct from the apply-time runner that\n * executes migration JSON ops.\n *\n * Currently a single static method. Future surface (e.g. a programmatic\n * `MigrationCLI.serializeOnly(...)` for tests, or extra subcommands) can\n * land here without changing the import shape used by every authored\n * migration.\n */\n// biome-ignore lint/complexity/noStaticOnlyClass: see JSDoc - intentional class facade for the migration-file CLI surface; future methods will share state derived from argv/config.\nexport class MigrationCLI {\n /**\n * Orchestrates a class-flow `migration.ts` script run.\n *\n * The third argument is the in-process testability surface: callers\n * (and tests) may inject `argv`, `stdout`, and `stderr` instead of\n * relying on `process.argv` / `process.stdout` / `process.stderr`.\n * Each option defaults to its `process` global when omitted, so\n * existing two-argument call sites\n * (`MigrationCLI.run(import.meta.url, MyMigration)`) continue to\n * compile and behave identically.\n *\n * Returns the exit code so the caller can branch on it. Also writes\n * the same code to `process.exitCode` so script-style callers that\n * don't await the return value still surface a non-zero exit when\n * something fails.\n *\n * Exit codes:\n * - 0 — success, or `--help`, or imported-not-entrypoint no-op.\n * - 1 — runtime/orchestration error (config not found, target\n * mismatch, etc.).\n * - 2 — usage error (unknown flag, malformed `--config`). Aligns\n * with `docs/CLI Style Guide.md` § Exit Codes.\n */\n static async run(\n importMetaUrl: string,\n MigrationClass: MigrationConstructor,\n options: {\n readonly argv?: readonly string[];\n readonly stdout?: MigrationCliWritable;\n readonly stderr?: MigrationCliWritable;\n } = {},\n ): Promise<number> {\n if (!importMetaUrl) {\n return 0;\n }\n\n const argv = options.argv ?? process.argv;\n const stdout = options.stdout ?? process.stdout;\n const stderr = options.stderr ?? process.stderr;\n\n if (!isDirectEntrypoint(importMetaUrl, argv)) {\n return 0;\n }\n\n const exitCode = await orchestrate(importMetaUrl, MigrationClass, {\n argv,\n stdout,\n stderr,\n });\n // Preserve any pre-existing non-zero `process.exitCode` set by code\n // running alongside `MigrationCLI.run` (an unhandled rejection\n // upstream, an explicit `process.exitCode = N` from another\n // module). Overwriting it with our success would mask the upstream\n // failure for script-style callers that don't await the return\n // value. Failures we return here are still surfaced — non-zero\n // codes always win over the prior status — but successes never\n // clear it.\n if (exitCode !== 0 || !process.exitCode) {\n process.exitCode = exitCode;\n }\n return exitCode;\n }\n}\n\n/**\n * Argv-aware variant of the entrypoint guard. The shared\n * `@prisma-next/migration-tools` helper of the same name reads\n * `process.argv[1]` directly, which doesn't compose with the new\n * in-process testability surface (tests inject `argv` without mutating\n * the process global). Inlined here so the migration-file CLI's check\n * follows the injected `argv[1]` consistently.\n */\nfunction isDirectEntrypoint(importMetaUrl: string, argv: readonly string[]): boolean {\n const argv1 = argv[1];\n if (!argv1) {\n return false;\n }\n try {\n return realpathSync(fileURLToPath(importMetaUrl)) === realpathSync(argv1);\n } catch {\n return false;\n }\n}\n\n/**\n * Argv-and-stream-driven orchestration body. Pulled out of the static\n * method so the entrypoint guard / process-default plumbing stays\n * separate from the parse + load + serialize steps.\n */\nasync function orchestrate(\n importMetaUrl: string,\n MigrationClass: MigrationConstructor,\n ctx: {\n readonly argv: readonly string[];\n readonly stdout: MigrationCliWritable;\n readonly stderr: MigrationCliWritable;\n },\n): Promise<number> {\n const cli = Cli.from([MigrationFileCommand], {\n binaryName: 'migration.ts',\n binaryLabel: 'Migration file CLI',\n });\n\n const input = ctx.argv.slice(2);\n\n // Pre-scan for malformed `--config` (no value, or value-shaped-as-flag)\n // before delegating to clipanion. The legacy parser surfaced both as\n // `errorMigrationCliInvalidConfigArg` (`PN-CLI-4012`); pre-scanning\n // here keeps that contract independent of how clipanion classifies\n // the error internally (it variably throws `UnknownSyntaxError` or\n // accepts the flag-shaped token as the value depending on what other\n // options are registered).\n const configError = detectInvalidConfig(input);\n if (configError) {\n writeStructuredError(ctx.stderr, configError);\n return 2;\n }\n\n let parsed: MigrationFileCommand;\n try {\n const command = cli.process({\n input: [...input],\n context: { stdout: ctx.stdout, stderr: ctx.stderr },\n });\n if (!(command instanceof MigrationFileCommand)) {\n // The only registered command class is `MigrationFileCommand`;\n // any other concrete type indicates clipanion emitted its\n // built-in `HelpCommand`. Render usage directly so we don't\n // depend on calling `cli.run` (which routes errors to stdout —\n // wrong stream for our contract).\n ctx.stdout.write(cli.usage(MigrationFileCommand, { detailed: true }));\n return 0;\n }\n parsed = command;\n } catch (err) {\n return renderParseError(err, input, ctx.stderr);\n }\n\n if (parsed.help) {\n ctx.stdout.write(cli.usage(MigrationFileCommand, { detailed: true }));\n return 0;\n }\n\n try {\n await runMigration(importMetaUrl, MigrationClass, parsed, ctx);\n return 0;\n } catch (err) {\n if (CliStructuredError.is(err)) {\n writeStructuredError(ctx.stderr, err);\n } else if (MigrationToolsError.is(err)) {\n // Migration-tools errors (e.g. `errorInvalidJson` thrown by\n // `readExistingMetadata` when migration.json is malformed) carry\n // their own `code`/`why`/`fix` shape. Render them with the same\n // visual structure as `CliStructuredError` so consumers grepping\n // for `MIGRATION.<CODE>` see consistent output across surfaces.\n const fix = err.fix ? `\\n${err.fix}` : '';\n ctx.stderr.write(`${err.code}: ${err.message}\\n${err.why}${fix}\\n`);\n } else {\n ctx.stderr.write(`${err instanceof Error ? err.message : String(err)}\\n`);\n }\n return 1;\n }\n}\n\n/**\n * Returns an `errorMigrationCliInvalidConfigArg` envelope when `input`\n * contains a malformed `--config`:\n *\n * - `--config` as the last token (no value follows).\n * - `--config <flag>` where `<flag>` starts with `-` (silently\n * consuming the next flag would either drop the flag or serialize\n * against the wrong project).\n * - `--config <empty>` where the value is the empty string. Shells\n * expand `--config \"\"` (or `--config \"$UNSET_VAR\"`) into a real\n * empty argv token; treating that as a usage error here surfaces\n * `PN-CLI-4012` instead of a less actionable loader error on an\n * empty path.\n * - `--config=` (the equals form with an empty value). Same shape as\n * the empty-string case above; the user expressed intent to override\n * the config path but the override is empty.\n *\n * `--config=<value>` and `--config <value>` with a non-empty value are\n * both valid (and the equals form's value is allowed to start with\n * `-` — the `=` makes the binding explicit).\n */\nfunction detectInvalidConfig(input: readonly string[]): CliStructuredError | null {\n for (let i = 0; i < input.length; i++) {\n const token = input[i];\n if (token === '--config') {\n const next = input[i + 1];\n if (next === undefined || next === '') {\n return errorMigrationCliInvalidConfigArg();\n }\n if (next.startsWith('-')) {\n return errorMigrationCliInvalidConfigArg({ nextToken: next });\n }\n continue;\n }\n if (token === '--config=') {\n return errorMigrationCliInvalidConfigArg();\n }\n }\n return null;\n}\n\n/**\n * Translate clipanion's parse-time errors into the project's structured\n * error envelopes.\n *\n * - `UnknownSyntaxError` covers both unknown flags (`--frobnicate`) and\n * the bare-trailing `--config` case (where arity-1 needs a value but\n * none was supplied). Distinguished by inspecting the input array.\n * - `UsageError` covers schema/validator failures from typanion. None\n * of the migration-file CLI's options have validators today, but we\n * still translate it as a usage error (exit 2) for forward-compat.\n * - Anything else re-throws — caller's outer catch will surface it as\n * exit 1 (runtime error).\n */\nfunction renderParseError(\n err: unknown,\n input: readonly string[],\n stderr: MigrationCliWritable,\n): number {\n if (isUnknownSyntaxError(err)) {\n const flag = findOffendingFlag(input);\n writeStructuredError(stderr, errorMigrationCliUnknownFlag({ flag, knownFlags: KNOWN_FLAGS }));\n return 2;\n }\n if (err instanceof UsageError) {\n // typanion validator failures and similar usage errors. None of\n // the migration-file CLI's options have validators today, so this\n // branch is forward-compat scaffolding — kept so that a future\n // option declaration with a validator routes through the same PN\n // envelope path rather than escaping as exit 1.\n writeStructuredError(stderr, errorMigrationCliInvalidConfigArg({ nextToken: err.message }));\n return 2;\n }\n throw err;\n}\n\n/**\n * Duck-type check for clipanion's `UnknownSyntaxError`: the class is\n * thrown by the parser but is not re-exported from the package's main\n * entry (only `UsageError` is — see clipanion's `advanced/index.d.ts`).\n * Identified by `name === 'UnknownSyntaxError'` and the\n * `clipanion.type === 'none'` discriminator that clipanion's\n * `ErrorWithMeta` interface guarantees.\n */\nfunction isUnknownSyntaxError(err: unknown): err is Error {\n if (!(err instanceof Error) || err.name !== 'UnknownSyntaxError') {\n return false;\n }\n // clipanion's `ErrorWithMeta` interface guarantees a `clipanion` field with\n // a `type` discriminator on every error it throws. Read it via a structural\n // shape rather than importing the class (it's not re-exported from the\n // package main).\n const meta = (err as { clipanion?: { type?: string } }).clipanion;\n return typeof meta === 'object' && meta !== null && meta.type === 'none';\n}\n\n/**\n * Best-effort: pull the first input token that doesn't match a known\n * flag. Falls back to the first token when we can't pinpoint it. The\n * returned name is rendered into the user-visible PN-CLI-4013 envelope\n * (`Unknown flag \\`<name>\\``) and round-tripped via `meta.flag` so\n * agent consumers can render their own \"did you mean\" suggestions.\n */\nfunction findOffendingFlag(input: readonly string[]): string {\n for (const token of input) {\n if (!token.startsWith('-')) {\n continue;\n }\n const head = token.split('=', 1)[0] ?? token;\n if (!KNOWN_FLAGS.includes(head) && head !== '-h') {\n return head;\n }\n }\n return input[0] ?? '';\n}\n\n/**\n * Write a `CliStructuredError` envelope to the given stream. Format\n * matches the legacy hand-rolled writer (`message: why`) so the rest of\n * the project's error rendering stays consistent across surfaces. The\n * full PN code (`PN-<domain>-<code>`) is included so consumers can\n * grep for stable identifiers.\n */\nfunction writeStructuredError(stream: MigrationCliWritable, err: CliStructuredError): void {\n const envelope = err.toEnvelope();\n const why = envelope.why ?? envelope.summary;\n const fix = envelope.fix ? `\\n${envelope.fix}` : '';\n stream.write(`${envelope.code}: ${envelope.summary}\\n${why}${fix}\\n`);\n}\n\n/**\n * Read a previously-scaffolded `migration.json` from disk, returning\n * `null` when the file is missing and throwing `MIGRATION.INVALID_JSON`\n * when the file is present but cannot be parsed as JSON. The CLI feeds\n * this into `buildMigrationArtifacts` so the pure builder can preserve\n * fields owned by `migration plan` (contract bookends, hints, labels,\n * `createdAt`) across re-emits.\n *\n * Author-time path: this loader still does not verify the manifest hash\n * or schema — that is the apply-time loader's job. Hash mismatch is the\n * *expected* outcome of a re-author (the developer's source changes\n * invalidate the prior hash by construction), and verification here\n * would block legitimate regenerations. Syntactic JSON-parse failure,\n * however, is now surfaced rather than swallowed: a malformed\n * `migration.json` indicates either a hand-edit gone wrong or partial\n * write, and silently rebuilding from `describe()` would discard the\n * user's on-disk content (preserved bookends, hints, labels,\n * `createdAt`) without any indication something was wrong on disk.\n * Apply-time consumers always route through the verifying\n * `readMigrationPackage` in `@prisma-next/migration-tools/io` instead.\n */\nfunction readExistingMetadata(metadataPath: string): Partial<MigrationMetadata> | null {\n let raw: string;\n try {\n raw = readFileSync(metadataPath, 'utf-8');\n } catch {\n return null;\n }\n try {\n return JSON.parse(raw) as Partial<MigrationMetadata>;\n } catch (e) {\n throw errorInvalidJson(metadataPath, e instanceof Error ? e.message : String(e));\n }\n}\n\n/**\n * Persist a migration instance's artifacts to `migrationDir`. In\n * `dryRun` mode the artifacts are printed to stdout (with the same\n * `--- migration.json --- / --- ops.json ---` framing the legacy\n * `serializeMigration` helper used) and no files are written. Otherwise\n * `ops.json` and `migration.json` are written next to `migration.ts` and\n * a confirmation line is printed.\n *\n * File I/O lives in the CLI rather than `@prisma-next/migration-tools`\n * so the migration-tools package stays focused on the pure\n * `Migration` → in-memory artifact conversion. The CLI is the only\n * legitimate site for combining config loading, stack assembly, and\n * filesystem persistence.\n */\nfunction serializeMigrationToDisk(\n instance: Migration,\n migrationDir: string,\n dryRun: boolean,\n stdout: MigrationCliWritable,\n): void {\n const metadataPath = join(migrationDir, 'migration.json');\n const existing = readExistingMetadata(metadataPath);\n const { opsJson, metadataJson } = buildMigrationArtifacts(instance, existing);\n\n if (dryRun) {\n stdout.write(`--- migration.json ---\\n${metadataJson}\\n`);\n stdout.write('--- ops.json ---\\n');\n stdout.write(`${opsJson}\\n`);\n return;\n }\n\n writeFileSync(join(migrationDir, 'ops.json'), opsJson);\n writeFileSync(metadataPath, metadataJson);\n\n stdout.write(`Wrote ops.json + migration.json to ${migrationDir}\\n`);\n}\n\n/**\n * Inner orchestration: load config, probe-construct the migration,\n * verify target, assemble the stack, construct with the stack, persist.\n *\n * Throws `CliStructuredError` for known failure modes (config not\n * found, target mismatch); the outer `orchestrate` translates those to\n * exit 1.\n */\nasync function runMigration(\n importMetaUrl: string,\n MigrationClass: MigrationConstructor,\n parsed: MigrationFileCommand,\n ctx: {\n readonly stdout: MigrationCliWritable;\n readonly stderr: MigrationCliWritable;\n },\n): Promise<void> {\n const migrationFile = fileURLToPath(importMetaUrl);\n const migrationDir = dirname(migrationFile);\n\n const config = await loadConfig(parsed.config);\n\n // Probe-instantiate without a stack so we can read `targetId` before\n // any target-specific constructor side effects (e.g.\n // `PostgresMigration`'s `stack.adapter.create(stack)`) run. Concrete\n // subclasses are required to accept the no-arg form; the abstract\n // `Migration` constructor declares `stack?` and target subclasses\n // (Postgres, Mongo) propagate that optionality. This makes the\n // target-mismatch guard fail fast with `PN-MIG-2006` before any\n // stack-driven adapter construction begins, even if the wrong-target\n // adapter's `create` would otherwise succeed and silently misshapen\n // the stored adapter cast.\n const probe = new MigrationClass();\n\n if (probe.targetId !== config.target.targetId) {\n throw errorMigrationTargetMismatch({\n migrationTargetId: probe.targetId,\n configTargetId: config.target.targetId,\n });\n }\n\n const stack = createControlStack(config);\n const instance = new MigrationClass(stack);\n\n serializeMigrationToDisk(instance, migrationDir, parsed.dryRun, ctx.stdout);\n void ctx.stderr;\n}\n"],"mappings":"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"}
|
package/dist/{migration-command-scaffold-B3B09et6.mjs → migration-command-scaffold-CU452v9h.mjs}
RENAMED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { t as loadConfig } from "./config-loader-
|
|
2
|
-
import { _ as errorUnexpected, a as errorContractValidationFailed, c as errorDriverRequired, h as errorTargetMigrationNotSupported, l as errorFileNotFound, o as errorDatabaseConnectionRequired } from "./cli-errors-
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { t as
|
|
1
|
+
import { t as loadConfig } from "./config-loader-ih8ViDb_.mjs";
|
|
2
|
+
import { _ as errorUnexpected, a as errorContractValidationFailed, c as errorDriverRequired, h as errorTargetMigrationNotSupported, l as errorFileNotFound, o as errorDatabaseConnectionRequired } from "./cli-errors-By1iVE3z.mjs";
|
|
3
|
+
import { c as resolveContractPath, n as addGlobalOptions, o as maskConnectionUrl, y as formatStyledHeader } from "./result-handler-Ch6hVnOo.mjs";
|
|
4
|
+
import { t as createProgressAdapter } from "./progress-adapter-DgRGldpT.mjs";
|
|
5
|
+
import { t as createControlClient } from "./client-1JqqkiC7.mjs";
|
|
6
6
|
import { notOk, ok } from "@prisma-next/utils/result";
|
|
7
|
-
import { hasMigrations } from "@prisma-next/framework-components/control";
|
|
8
7
|
import { readFile } from "node:fs/promises";
|
|
8
|
+
import { hasMigrations } from "@prisma-next/framework-components/control";
|
|
9
9
|
import { relative, resolve } from "node:path";
|
|
10
10
|
|
|
11
11
|
//#region src/utils/migration-command-scaffold.ts
|
|
@@ -102,4 +102,4 @@ function addMigrationCommandOptions(command) {
|
|
|
102
102
|
|
|
103
103
|
//#endregion
|
|
104
104
|
export { prepareMigrationContext as n, addMigrationCommandOptions as t };
|
|
105
|
-
//# sourceMappingURL=migration-command-scaffold-
|
|
105
|
+
//# sourceMappingURL=migration-command-scaffold-CU452v9h.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"migration-command-scaffold-
|
|
1
|
+
{"version":3,"file":"migration-command-scaffold-CU452v9h.mjs","names":["details: Array<{ label: string; value: string }>","contractJsonContent: string","contractJson: Record<string, unknown>"],"sources":["../src/utils/migration-command-scaffold.ts"],"sourcesContent":["import { readFile } from 'node:fs/promises';\nimport { relative, resolve } from 'node:path';\nimport { 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;AAGlE,KAAI,CAAC,MAAM,QAAQ,CAAC,MAAM,OAAO;EAC/B,MAAMA,UAAmD,CACvD;GAAE,OAAO;GAAU,OAAO;GAAY,EACtC;GAAE,OAAO;GAAY,OAAO;GAAc,CAC3C;AACD,MAAI,QAAQ,GACV,SAAQ,KAAK;GAAE,OAAO;GAAY,OAAO,kBAAkB,QAAQ,GAAG;GAAE,CAAC;AAE3E,MAAI,QAAQ,OACV,SAAQ,KAAK;GAAE,OAAO;GAAQ,OAAO;GAAW,CAAC;EAEnD,MAAM,SAAS,mBAAmB;GAChC,SAAS,WAAW;GACpB,aAAa,WAAW;GACxB,KAAK,WAAW;GAChB;GACA;GACD,CAAC;AACF,KAAG,OAAO,OAAO;;CAInB,IAAIC;AACJ,KAAI;AACF,wBAAsB,MAAM,SAAS,sBAAsB,QAAQ;UAC5D,OAAO;AACd,MAAI,iBAAiB,SAAU,MAA4B,SAAS,SAClE,QAAO,MACL,kBAAkB,sBAAsB;GACtC,KAAK,8BAA8B;GACnC,KAAK,iDAAiD,aAAa,4CAA4C;GAChH,CAAC,CACH;AAEH,SAAO,MACL,gBAAgB,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM,EAAE,EACtE,KAAK,iCAAiC,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM,IAC7F,CAAC,CACH;;CAIH,IAAIC;AACJ,KAAI;AACF,iBAAe,KAAK,MAAM,oBAAoB;UACvC,OAAO;AACd,SAAO,MACL,8BACE,6BAA6B,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM,IACnF,EAAE,OAAO,EAAE,MAAM,sBAAsB,EAAE,CAC1C,CACF;;CAIH,MAAM,eAAe,QAAQ,MAAM,OAAO,IAAI;AAC9C,KAAI,CAAC,aACH,QAAO,MACL,gCAAgC;EAC9B,KAAK,uCAAuC,WAAW,YAAY,yBAAyB,WAAW;EACvG,aAAa,WAAW;EACzB,CAAC,CACH;AAIH,KAAI,CAAC,OAAO,OACV,QAAO,MACL,oBAAoB,EAAE,KAAK,iCAAiC,WAAW,eAAe,CAAC,CACxF;AAGH,KAAI,CAAC,cAAc,OAAO,OAAO,CAC/B,QAAO,MACL,iCAAiC,EAC/B,KAAK,WAAW,OAAO,OAAO,GAAG,gCAClC,CAAC,CACH;CAIH,MAAM,SAAS,oBAAoB;EACjC,QAAQ,OAAO;EACf,QAAQ,OAAO;EACf,SAAS,OAAO;EAChB,QAAQ,OAAO;EACf,gBAAgB,OAAO,kBAAkB,EAAE;EAC5C,CAAC;CAGF,MAAM,aAAa,sBAAsB;EAAE;EAAI;EAAO,CAAC;AAEvD,QAAO,GAAG;EACR;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACD,CAAC;;;;;AAMJ,SAAgB,2BAA2B,SAA2B;AACpE,kBAAiB,QAAQ;AACzB,QAAO,QACJ,OAAO,cAAc,6BAA6B,CAClD,OAAO,mBAAmB,gCAAgC,CAC1D,OAAO,aAAa,+CAA+C,MAAM"}
|
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
import { t as loadConfig } from "./config-loader-
|
|
2
|
-
import { _ as errorUnexpected, m as errorRuntime } from "./cli-errors-
|
|
3
|
-
import { t as
|
|
4
|
-
import { t as
|
|
5
|
-
import {
|
|
1
|
+
import { t as loadConfig } from "./config-loader-ih8ViDb_.mjs";
|
|
2
|
+
import { _ as errorUnexpected, m as errorRuntime, v as mapMigrationToolsError } from "./cli-errors-By1iVE3z.mjs";
|
|
3
|
+
import { t as TerminalUI } from "./terminal-ui-u2YgKghu.mjs";
|
|
4
|
+
import { a as loadMigrationPackages, d as setCommandDescriptions, f as setCommandExamples, g as parseGlobalFlags, h as toStructuralEdge, l as resolveMigrationPaths, m as toPathDecisionResult, n as addGlobalOptions, o as maskConnectionUrl, r as collectDeclaredInvariants, s as readContractEnvelope, t as handleResult, y as formatStyledHeader } from "./result-handler-Ch6hVnOo.mjs";
|
|
5
|
+
import { t as createControlClient } from "./client-1JqqkiC7.mjs";
|
|
6
6
|
import { Command } from "commander";
|
|
7
|
-
import { notOk, ok } from "@prisma-next/utils/result";
|
|
8
7
|
import { ifDefined } from "@prisma-next/utils/defined";
|
|
9
|
-
import {
|
|
10
|
-
import { findPath, findPathWithDecision, findReachableLeaves } from "@prisma-next/migration-tools/
|
|
8
|
+
import { notOk, ok } from "@prisma-next/utils/result";
|
|
9
|
+
import { findPath, findPathWithDecision, findReachableLeaves } from "@prisma-next/migration-tools/migration-graph";
|
|
11
10
|
import { bold, cyan, dim, magenta, yellow } from "colorette";
|
|
11
|
+
import { EMPTY_CONTRACT_HASH } from "@prisma-next/migration-tools/constants";
|
|
12
|
+
import { MigrationToolsError, errorNoInvariantPath, errorUnknownInvariant } from "@prisma-next/migration-tools/errors";
|
|
12
13
|
import { readRefs, resolveRef } from "@prisma-next/migration-tools/refs";
|
|
13
|
-
import { MigrationToolsError } from "@prisma-next/migration-tools/types";
|
|
14
14
|
import dagre from "@dagrejs/dagre";
|
|
15
15
|
|
|
16
16
|
//#region src/utils/formatters/graph-types.ts
|
|
@@ -100,7 +100,8 @@ function migrationGraphToRenderInput(input) {
|
|
|
100
100
|
for (const [, entries] of graph.forwardChain) for (const entry of entries) {
|
|
101
101
|
const status = statusByDirName.get(entry.dirName);
|
|
102
102
|
const icon = status ? STATUS_ICON[status] : "";
|
|
103
|
-
const
|
|
103
|
+
const invariantsSuffix = entry.invariants.length > 0 ? ` provides [${entry.invariants.map((id) => JSON.stringify(id)).join(", ")}]` : "";
|
|
104
|
+
const label = `${entry.dirName}${icon}${invariantsSuffix}`;
|
|
104
105
|
edgeList.push({
|
|
105
106
|
from: toShortId(entry.from),
|
|
106
107
|
to: toShortId(entry.to),
|
|
@@ -1194,7 +1195,7 @@ function buildMigrationEntries(chain, packages, mode, markerHash, edgeStatuses)
|
|
|
1194
1195
|
dirName: migration.dirName,
|
|
1195
1196
|
from: migration.from,
|
|
1196
1197
|
to: migration.to,
|
|
1197
|
-
|
|
1198
|
+
migrationHash: migration.migrationHash,
|
|
1198
1199
|
operationCount: ops.length,
|
|
1199
1200
|
operationSummary: summary,
|
|
1200
1201
|
hasDestructive,
|
|
@@ -1246,30 +1247,25 @@ async function executeMigrationStatusCommand(options, flags, ui) {
|
|
|
1246
1247
|
const hasDriver = !!config.driver;
|
|
1247
1248
|
let activeRefName;
|
|
1248
1249
|
let activeRefHash;
|
|
1250
|
+
let activeRefEntry;
|
|
1249
1251
|
let allRefs = {};
|
|
1250
1252
|
try {
|
|
1251
1253
|
allRefs = await readRefs(refsDir);
|
|
1252
1254
|
} catch (error) {
|
|
1253
|
-
if (MigrationToolsError.is(error)) return notOk(
|
|
1254
|
-
why: error.why,
|
|
1255
|
-
fix: error.fix,
|
|
1256
|
-
meta: { code: error.code }
|
|
1257
|
-
}));
|
|
1255
|
+
if (MigrationToolsError.is(error)) return notOk(mapMigrationToolsError(error));
|
|
1258
1256
|
throw error;
|
|
1259
1257
|
}
|
|
1260
1258
|
if (options.ref) {
|
|
1261
1259
|
activeRefName = options.ref;
|
|
1262
1260
|
try {
|
|
1263
|
-
|
|
1261
|
+
activeRefEntry = resolveRef(allRefs, activeRefName);
|
|
1262
|
+
activeRefHash = activeRefEntry.hash;
|
|
1264
1263
|
} catch (error) {
|
|
1265
|
-
if (MigrationToolsError.is(error)) return notOk(
|
|
1266
|
-
why: error.why,
|
|
1267
|
-
fix: error.fix,
|
|
1268
|
-
meta: { code: error.code }
|
|
1269
|
-
}));
|
|
1264
|
+
if (MigrationToolsError.is(error)) return notOk(mapMigrationToolsError(error));
|
|
1270
1265
|
throw error;
|
|
1271
1266
|
}
|
|
1272
1267
|
}
|
|
1268
|
+
const requiredInvariants = [...activeRefEntry?.invariants ?? []].sort();
|
|
1273
1269
|
const statusRefs = Object.entries(allRefs).map(([name, entry]) => ({
|
|
1274
1270
|
name,
|
|
1275
1271
|
hash: entry.hash,
|
|
@@ -1291,6 +1287,10 @@ async function executeMigrationStatusCommand(options, flags, ui) {
|
|
|
1291
1287
|
label: "ref",
|
|
1292
1288
|
value: activeRefName
|
|
1293
1289
|
});
|
|
1290
|
+
if (activeRefEntry && activeRefEntry.invariants.length > 0) details.push({
|
|
1291
|
+
label: "required",
|
|
1292
|
+
value: formatInvariantList(activeRefEntry.invariants)
|
|
1293
|
+
});
|
|
1294
1294
|
const header = formatStyledHeader({
|
|
1295
1295
|
command: "migration status",
|
|
1296
1296
|
description: "Show migration history and applied status",
|
|
@@ -1314,13 +1314,9 @@ async function executeMigrationStatusCommand(options, flags, ui) {
|
|
|
1314
1314
|
let bundles;
|
|
1315
1315
|
let graph;
|
|
1316
1316
|
try {
|
|
1317
|
-
({bundles, graph} = await
|
|
1317
|
+
({bundles, graph} = await loadMigrationPackages(migrationsDir));
|
|
1318
1318
|
} catch (error) {
|
|
1319
|
-
if (MigrationToolsError.is(error)) return notOk(
|
|
1320
|
-
why: error.why,
|
|
1321
|
-
fix: error.fix,
|
|
1322
|
-
meta: { code: error.code }
|
|
1323
|
-
}));
|
|
1319
|
+
if (MigrationToolsError.is(error)) return notOk(mapMigrationToolsError(error));
|
|
1324
1320
|
return notOk(errorUnexpected(error instanceof Error ? error.message : String(error), { why: `Failed to read migrations directory: ${error instanceof Error ? error.message : String(error)}` }));
|
|
1325
1321
|
}
|
|
1326
1322
|
if (bundles.length === 0) {
|
|
@@ -1337,7 +1333,8 @@ async function executeMigrationStatusCommand(options, flags, ui) {
|
|
|
1337
1333
|
targetHash: EMPTY_CONTRACT_HASH,
|
|
1338
1334
|
contractHash,
|
|
1339
1335
|
summary: "No migrations found",
|
|
1340
|
-
diagnostics
|
|
1336
|
+
diagnostics,
|
|
1337
|
+
requiredInvariants
|
|
1341
1338
|
});
|
|
1342
1339
|
}
|
|
1343
1340
|
let targetHash;
|
|
@@ -1354,6 +1351,7 @@ async function executeMigrationStatusCommand(options, flags, ui) {
|
|
|
1354
1351
|
});
|
|
1355
1352
|
}
|
|
1356
1353
|
let markerHash;
|
|
1354
|
+
let markerInvariants = [];
|
|
1357
1355
|
let mode = "offline";
|
|
1358
1356
|
if (dbConnection && hasDriver) {
|
|
1359
1357
|
const client = createControlClient({
|
|
@@ -1365,7 +1363,9 @@ async function executeMigrationStatusCommand(options, flags, ui) {
|
|
|
1365
1363
|
});
|
|
1366
1364
|
try {
|
|
1367
1365
|
await client.connect(dbConnection);
|
|
1368
|
-
|
|
1366
|
+
const marker = await client.readMarker();
|
|
1367
|
+
markerHash = marker?.storageHash;
|
|
1368
|
+
markerInvariants = marker?.invariants ?? [];
|
|
1369
1369
|
mode = "online";
|
|
1370
1370
|
} catch {
|
|
1371
1371
|
if (!flags.json && !flags.quiet) ui.warn("Could not connect to database — showing offline status");
|
|
@@ -1373,6 +1373,17 @@ async function executeMigrationStatusCommand(options, flags, ui) {
|
|
|
1373
1373
|
await client.close();
|
|
1374
1374
|
}
|
|
1375
1375
|
}
|
|
1376
|
+
if (activeRefEntry && activeRefEntry.invariants.length > 0) {
|
|
1377
|
+
const declared = collectDeclaredInvariants(graph);
|
|
1378
|
+
const known = new Set(declared);
|
|
1379
|
+
if (mode === "online") for (const id of markerInvariants) known.add(id);
|
|
1380
|
+
const unknown = activeRefEntry.invariants.filter((id) => !known.has(id));
|
|
1381
|
+
if (unknown.length > 0) return notOk(mapMigrationToolsError(errorUnknownInvariant({
|
|
1382
|
+
...ifDefined("refName", activeRefName),
|
|
1383
|
+
unknown,
|
|
1384
|
+
declared: [...declared].sort()
|
|
1385
|
+
})));
|
|
1386
|
+
}
|
|
1376
1387
|
if (mode === "online" && markerHash !== void 0 && !graph.nodes.has(markerHash) && markerHash !== contractHash) {
|
|
1377
1388
|
const hints = [];
|
|
1378
1389
|
if (graph.nodes.has(contractHash)) hints.push("Run 'prisma-next db sign' to overwrite the marker if the database already matches the contract", "Run 'prisma-next db update' to push the current contract to the database", "Run 'prisma-next contract infer' to make your contract match the database", "Run 'prisma-next db verify' to inspect the database state");
|
|
@@ -1392,6 +1403,7 @@ async function executeMigrationStatusCommand(options, flags, ui) {
|
|
|
1392
1403
|
summary: `${bundles.length} migration(s) on disk`,
|
|
1393
1404
|
diagnostics,
|
|
1394
1405
|
markerHash,
|
|
1406
|
+
requiredInvariants,
|
|
1395
1407
|
...statusRefs.length > 0 ? { refs: statusRefs } : {}
|
|
1396
1408
|
});
|
|
1397
1409
|
}
|
|
@@ -1416,6 +1428,7 @@ async function executeMigrationStatusCommand(options, flags, ui) {
|
|
|
1416
1428
|
summary: `${bundles.length} migration(s) on disk`,
|
|
1417
1429
|
diagnostics,
|
|
1418
1430
|
...ifDefined("markerHash", markerHash),
|
|
1431
|
+
requiredInvariants,
|
|
1419
1432
|
...statusRefs.length > 0 ? { refs: statusRefs } : {},
|
|
1420
1433
|
graph,
|
|
1421
1434
|
bundles,
|
|
@@ -1430,35 +1443,68 @@ async function executeMigrationStatusCommand(options, flags, ui) {
|
|
|
1430
1443
|
const entries = buildMigrationEntries(chain, bundles, mode, markerHash, edgeStatuses);
|
|
1431
1444
|
const pendingCount = edgeStatuses.filter((e) => e.status === "pending").length;
|
|
1432
1445
|
const appliedCount = edgeStatuses.filter((e) => e.status === "applied").length;
|
|
1446
|
+
let appliedInvariants;
|
|
1447
|
+
let missingInvariants;
|
|
1448
|
+
let effectiveRequired = /* @__PURE__ */ new Set();
|
|
1449
|
+
if (mode === "online") {
|
|
1450
|
+
const markerSet = new Set(markerInvariants);
|
|
1451
|
+
effectiveRequired = new Set(requiredInvariants.filter((id) => !markerSet.has(id)));
|
|
1452
|
+
appliedInvariants = requiredInvariants.filter((id) => markerSet.has(id));
|
|
1453
|
+
missingInvariants = [...effectiveRequired].sort();
|
|
1454
|
+
}
|
|
1455
|
+
const hasInvariantWork = effectiveRequired.size > 0;
|
|
1456
|
+
const missingList = [...effectiveRequired].sort().join(", ");
|
|
1433
1457
|
let summary;
|
|
1434
1458
|
if (mode === "online") if (markerHash !== void 0 && !graph.nodes.has(markerHash) && markerHash === contractHash) summary = `${bundles.length} migration(s) on disk`;
|
|
1435
|
-
else if (activeRefHash && markerHash !== void 0)
|
|
1436
|
-
|
|
1459
|
+
else if (activeRefHash && markerHash !== void 0) {
|
|
1460
|
+
const distance = summarizeRefDistance(graph, markerHash, activeRefHash, activeRefName);
|
|
1461
|
+
summary = hasInvariantWork ? `${distance} — missing invariant(s): ${missingList}` : distance;
|
|
1462
|
+
} else if (pendingCount === 0 && !hasInvariantWork) summary = `Database is up to date (${appliedCount} migration${appliedCount !== 1 ? "s" : ""} applied)`;
|
|
1463
|
+
else if (pendingCount === 0 && hasInvariantWork) summary = `Missing invariant(s): ${missingList} — run 'prisma-next migration apply --ref ${activeRefName ?? "<ref>"}' to apply`;
|
|
1437
1464
|
else if (markerHash === void 0) summary = `${pendingCount} pending migration(s) — database has no marker`;
|
|
1438
1465
|
else summary = `${pendingCount} pending migration(s) — run 'prisma-next migration apply' to apply`;
|
|
1439
1466
|
else summary = `${entries.length} migration(s) on disk`;
|
|
1440
|
-
if (mode === "online") if (markerHash !== void 0 && !graph.nodes.has(markerHash) && markerHash === contractHash) diagnostics.push({
|
|
1441
|
-
code: "MIGRATION.MARKER_NOT_IN_HISTORY",
|
|
1442
|
-
severity: "warn",
|
|
1443
|
-
message: "Database matches the current contract but was updated directly (not via migration apply)",
|
|
1444
|
-
hints: ["Run 'prisma-next migration plan' to plan a migration to your current contract"]
|
|
1445
|
-
});
|
|
1446
|
-
else if (pendingCount > 0) diagnostics.push({
|
|
1447
|
-
code: "MIGRATION.DATABASE_BEHIND",
|
|
1448
|
-
severity: "info",
|
|
1449
|
-
message: `${pendingCount} migration(s) pending`,
|
|
1450
|
-
hints: ["Run 'prisma-next migration apply' to apply pending migrations"]
|
|
1451
|
-
});
|
|
1452
|
-
else diagnostics.push({
|
|
1453
|
-
code: "MIGRATION.UP_TO_DATE",
|
|
1454
|
-
severity: "info",
|
|
1455
|
-
message: "Database is up to date",
|
|
1456
|
-
hints: []
|
|
1457
|
-
});
|
|
1458
1467
|
let pathDecision;
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1468
|
+
let routingUnreachable = false;
|
|
1469
|
+
if (mode === "online") {
|
|
1470
|
+
const outcome = findPathWithDecision(graph, markerHash ?? EMPTY_CONTRACT_HASH, targetHash, {
|
|
1471
|
+
...ifDefined("refName", activeRefName),
|
|
1472
|
+
required: effectiveRequired
|
|
1473
|
+
});
|
|
1474
|
+
if (outcome.kind === "ok") pathDecision = toPathDecisionResult(outcome.decision);
|
|
1475
|
+
else if (outcome.kind === "unsatisfiable") return notOk(mapMigrationToolsError(errorNoInvariantPath({
|
|
1476
|
+
...ifDefined("refName", activeRefName),
|
|
1477
|
+
required: [...effectiveRequired].sort(),
|
|
1478
|
+
missing: outcome.missing,
|
|
1479
|
+
structuralPath: outcome.structuralPath.map(toStructuralEdge)
|
|
1480
|
+
})));
|
|
1481
|
+
else routingUnreachable = true;
|
|
1482
|
+
}
|
|
1483
|
+
if (mode === "online") {
|
|
1484
|
+
if (markerHash !== void 0 && !graph.nodes.has(markerHash) && markerHash === contractHash) diagnostics.push({
|
|
1485
|
+
code: "MIGRATION.MARKER_NOT_IN_HISTORY",
|
|
1486
|
+
severity: "warn",
|
|
1487
|
+
message: "Database matches the current contract but was updated directly (not via migration apply)",
|
|
1488
|
+
hints: ["Run 'prisma-next migration plan' to plan a migration to your current contract"]
|
|
1489
|
+
});
|
|
1490
|
+
else if (pendingCount > 0) diagnostics.push({
|
|
1491
|
+
code: "MIGRATION.DATABASE_BEHIND",
|
|
1492
|
+
severity: "info",
|
|
1493
|
+
message: `${pendingCount} migration(s) pending`,
|
|
1494
|
+
hints: ["Run 'prisma-next migration apply' to apply pending migrations"]
|
|
1495
|
+
});
|
|
1496
|
+
else if (hasInvariantWork) diagnostics.push({
|
|
1497
|
+
code: "MIGRATION.INVARIANTS_PENDING",
|
|
1498
|
+
severity: "info",
|
|
1499
|
+
message: `Missing required invariant(s): ${missingList}`,
|
|
1500
|
+
hints: [`Run 'prisma-next migration apply --ref ${activeRefName ?? "<ref>"}' to apply a path that covers the required invariants`]
|
|
1501
|
+
});
|
|
1502
|
+
else if (!routingUnreachable) diagnostics.push({
|
|
1503
|
+
code: "MIGRATION.UP_TO_DATE",
|
|
1504
|
+
severity: "info",
|
|
1505
|
+
message: "Database is up to date",
|
|
1506
|
+
hints: []
|
|
1507
|
+
});
|
|
1462
1508
|
}
|
|
1463
1509
|
return ok({
|
|
1464
1510
|
ok: true,
|
|
@@ -1469,6 +1515,9 @@ async function executeMigrationStatusCommand(options, flags, ui) {
|
|
|
1469
1515
|
summary,
|
|
1470
1516
|
diagnostics,
|
|
1471
1517
|
...ifDefined("markerHash", markerHash),
|
|
1518
|
+
requiredInvariants,
|
|
1519
|
+
...ifDefined("appliedInvariants", appliedInvariants),
|
|
1520
|
+
...ifDefined("missingInvariants", missingInvariants),
|
|
1472
1521
|
...statusRefs.length > 0 ? { refs: statusRefs } : {},
|
|
1473
1522
|
...ifDefined("pathDecision", pathDecision),
|
|
1474
1523
|
graph,
|
|
@@ -1490,7 +1539,7 @@ function createMigrationStatusCommand() {
|
|
|
1490
1539
|
});
|
|
1491
1540
|
const exitCode = handleResult(await executeMigrationStatusCommand(options, flags, ui), flags, ui, (statusResult) => {
|
|
1492
1541
|
if (flags.json) {
|
|
1493
|
-
const { graph:
|
|
1542
|
+
const { graph: _graph, bundles: _bundles, edgeStatuses: _edgeStatuses, activeRefHash: _activeRefHash, activeRefName: _activeRefName, diverged: _diverged, ...jsonResult } = statusResult;
|
|
1494
1543
|
ui.output(JSON.stringify(jsonResult, null, 2));
|
|
1495
1544
|
} else if (!flags.quiet) {
|
|
1496
1545
|
const colorize = flags.color !== false;
|
|
@@ -1540,10 +1589,15 @@ function formatStatusSummary(result, colorize) {
|
|
|
1540
1589
|
const hasUnknown = result.migrations.some((e) => e.status === "unknown");
|
|
1541
1590
|
const pendingCount = result.migrations.filter((e) => e.status === "pending").length;
|
|
1542
1591
|
const hasWarnings = result.diagnostics?.some((d) => d.severity === "warn") ?? false;
|
|
1592
|
+
const hasInvariantPending = result.diagnostics?.some((d) => d.code === "MIGRATION.INVARIANTS_PENDING") ?? false;
|
|
1543
1593
|
if (result.mode === "online") if (hasUnknown || hasWarnings) lines.push(`${c(yellow, "⚠")} ${result.summary}`);
|
|
1544
|
-
else if (pendingCount === 0) lines.push(`${c(cyan, "✔")} ${result.summary}`);
|
|
1594
|
+
else if (pendingCount === 0 && !hasInvariantPending) lines.push(`${c(cyan, "✔")} ${result.summary}`);
|
|
1545
1595
|
else lines.push(`${c(yellow, "⧗")} ${result.summary}`);
|
|
1546
1596
|
else lines.push(result.summary);
|
|
1597
|
+
if (result.requiredInvariants.length > 0) if (result.appliedInvariants !== void 0 && result.missingInvariants !== void 0) {
|
|
1598
|
+
lines.push(`${c(dim, "applied ")}${formatInvariantList(result.appliedInvariants)}`);
|
|
1599
|
+
lines.push(`${c(dim, "missing ")}${formatInvariantList(result.missingInvariants)}`);
|
|
1600
|
+
} else lines.push(`${c(dim, "applied ")}(unknown — connect a database to evaluate)`);
|
|
1547
1601
|
const warnings = result.diagnostics?.filter((d) => d.severity === "warn") ?? [];
|
|
1548
1602
|
for (const diag of warnings) {
|
|
1549
1603
|
lines.push(`${c(yellow, "⚠")} ${diag.message}`);
|
|
@@ -1551,6 +1605,9 @@ function formatStatusSummary(result, colorize) {
|
|
|
1551
1605
|
}
|
|
1552
1606
|
return lines.join("\n");
|
|
1553
1607
|
}
|
|
1608
|
+
function formatInvariantList(ids) {
|
|
1609
|
+
return ids.length === 0 ? "(none)" : ids.join(", ");
|
|
1610
|
+
}
|
|
1554
1611
|
function summarizeRefDistance(graph, markerHash, refHash, refName) {
|
|
1555
1612
|
if (markerHash === refHash) return `At ref "${refName}" target`;
|
|
1556
1613
|
const pathToRef = findPath(graph, markerHash, refHash);
|
|
@@ -1561,5 +1618,5 @@ function summarizeRefDistance(graph, markerHash, refHash, refName) {
|
|
|
1561
1618
|
}
|
|
1562
1619
|
|
|
1563
1620
|
//#endregion
|
|
1564
|
-
export { deriveEdgeStatuses as n, createMigrationStatusCommand as t };
|
|
1565
|
-
//# sourceMappingURL=migration-status-
|
|
1621
|
+
export { deriveEdgeStatuses as n, formatStatusSummary as r, createMigrationStatusCommand as t };
|
|
1622
|
+
//# sourceMappingURL=migration-status-DoPrFIOQ.mjs.map
|