@prisma-next/cli 0.12.0-dev.66 → 0.12.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 +5 -5
- package/dist/commands/migrate.d.mts.map +1 -1
- package/dist/commands/migrate.mjs +17 -11
- package/dist/commands/migrate.mjs.map +1 -1
- package/dist/commands/migration-check.mjs +1 -1
- package/dist/commands/migration-graph.mjs +2 -2
- package/dist/commands/migration-graph.mjs.map +1 -1
- package/dist/commands/migration-list.mjs +1 -1
- package/dist/commands/migration-log.mjs +1 -1
- package/dist/commands/migration-status.d.mts.map +1 -1
- package/dist/commands/migration-status.mjs +1 -1
- package/dist/{migration-check-VwM8xCZV.mjs → migration-check-soB5uZEQ.mjs} +1 -2
- package/dist/{migration-check-VwM8xCZV.mjs.map → migration-check-soB5uZEQ.mjs.map} +1 -1
- package/dist/migration-graph-command-render-BAOzyYF6.mjs +1822 -0
- package/dist/migration-graph-command-render-BAOzyYF6.mjs.map +1 -0
- package/dist/{migration-list-CyLslAtv.mjs → migration-list-CihF6w5z.mjs} +2 -2
- package/dist/migration-list-CihF6w5z.mjs.map +1 -0
- package/dist/{migration-log-BYt18y2H.mjs → migration-log-B75IArji.mjs} +2 -2
- package/dist/{migration-log-BYt18y2H.mjs.map → migration-log-B75IArji.mjs.map} +1 -1
- package/dist/{migration-status-B5GzQYe3.mjs → migration-status-Di82DGvo.mjs} +3 -4
- package/dist/migration-status-Di82DGvo.mjs.map +1 -0
- package/package.json +19 -18
- package/src/commands/migrate.ts +35 -26
- package/src/commands/migration-graph.ts +1 -1
- package/src/commands/migration-list.ts +1 -1
- package/src/commands/migration-status-overlay.ts +1 -1
- package/src/commands/migration-status.ts +4 -2
- package/src/utils/formatters/migration-graph-command-render.ts +239 -0
- package/src/utils/formatters/migration-graph-grid-layout.ts +857 -0
- package/src/utils/formatters/migration-graph-labels.ts +406 -0
- package/src/utils/formatters/migration-graph-model.ts +94 -0
- package/src/utils/formatters/migration-graph-occlusion-render.ts +245 -0
- package/src/utils/formatters/migration-graph-space-render.ts +73 -33
- package/src/utils/formatters/migration-list-render.ts +1 -1
- package/dist/migration-graph-space-render-Cpg0ql8v.mjs +0 -2370
- package/dist/migration-graph-space-render-Cpg0ql8v.mjs.map +0 -1
- package/dist/migration-list-CyLslAtv.mjs.map +0 -1
- package/dist/migration-status-B5GzQYe3.mjs.map +0 -1
- package/src/utils/formatters/migration-graph-lane-colors.ts +0 -194
- package/src/utils/formatters/migration-graph-layout.ts +0 -1308
- package/src/utils/formatters/migration-graph-tree-render.ts +0 -1337
|
@@ -2,9 +2,8 @@ import { t as loadConfig } from "./config-loader-p9JMrekQ.mjs";
|
|
|
2
2
|
import { A as formatStyledHeader, F as CliStructuredError, _ as createTerminalUI, a as readContractEnvelope, ct as errorUnexpected, d as setCommandSeeAlso, dt as requireLiveDatabase, g as parseGlobalFlagsOrExit, i as maskConnectionUrl, l as setCommandDescriptions, lt as mapMigrationToolsError, n as collectDeclaredInvariants, p as toStructuralEdge, s as resolveMigrationPaths, t as addGlobalOptions, u as setCommandExamples, ut as mapRefResolutionError, y as handleResult } from "./command-helpers-DGMvGBeX.mjs";
|
|
3
3
|
import { t as createControlClient } from "./client-CJzuo5wX.mjs";
|
|
4
4
|
import { n as buildReadAggregate, o as refusePackageCorruptionOnAggregate, r as loadContractRawSafely } from "./contract-space-aggregate-loader-ClI1KN6d.mjs";
|
|
5
|
-
import {
|
|
6
|
-
import { c as validateLegendOptions, i as migrationSpaceListEntriesFromAggregate, o as runMigrationList, r as listRefsByContractHash, s as shouldShowLegend } from "./migration-list-
|
|
7
|
-
import "./schemas-KhXMzNA_.mjs";
|
|
5
|
+
import { a as renderMigrationGraphLegend, f as indentMigrationGraphTreeBlock, l as computeGlobalMaxDirNameWidth, p as renderMigrationGraphSpaceTree, u as computeGlobalMaxEdgeTreePrefixWidth } from "./migration-graph-command-render-BAOzyYF6.mjs";
|
|
6
|
+
import { c as validateLegendOptions, i as migrationSpaceListEntriesFromAggregate, o as runMigrationList, r as listRefsByContractHash, s as shouldShowLegend } from "./migration-list-CihF6w5z.mjs";
|
|
8
7
|
import { Command } from "commander";
|
|
9
8
|
import { ifDefined } from "@prisma-next/utils/defined";
|
|
10
9
|
import { notOk, ok } from "@prisma-next/utils/result";
|
|
@@ -444,4 +443,4 @@ function createMigrationStatusCommand() {
|
|
|
444
443
|
//#endregion
|
|
445
444
|
export { formatStatusHumanOutput as a, executeMigrationStatusCommand as i, buildStatusHeadline as n, formatStatusSummary as o, createMigrationStatusCommand as r, buildNoPathSummary as t };
|
|
446
445
|
|
|
447
|
-
//# sourceMappingURL=migration-status-
|
|
446
|
+
//# sourceMappingURL=migration-status-Di82DGvo.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"migration-status-Di82DGvo.mjs","names":[],"sources":["../src/commands/migration-status-overlay.ts","../src/commands/migration-status.ts"],"sourcesContent":["import type { MigrationGraph } from '@prisma-next/migration-tools/graph';\nimport { findPath } from '@prisma-next/migration-tools/migration-graph';\nimport type { MigrationEdgeAnnotation } from '../utils/formatters/migration-graph-labels';\n\nexport interface DeriveStatusEdgeAnnotationsInput {\n readonly graph: MigrationGraph;\n readonly targetHash: string;\n readonly originHash: string;\n readonly appliedMigrationHashes: ReadonlySet<string>;\n readonly showAppliedOverlay: boolean;\n}\n\nexport function deriveStatusEdgeAnnotations(\n input: DeriveStatusEdgeAnnotationsInput,\n): ReadonlyMap<string, MigrationEdgeAnnotation> {\n const annotations = new Map<string, MigrationEdgeAnnotation>();\n\n if (input.showAppliedOverlay) {\n for (const edge of input.graph.migrationByHash.values()) {\n if (input.appliedMigrationHashes.has(edge.migrationHash)) {\n annotations.set(edge.migrationHash, { status: 'applied' });\n }\n }\n }\n\n if (!input.graph.nodes.has(input.originHash)) {\n return annotations;\n }\n\n const pendingPath = findPath(input.graph, input.originHash, input.targetHash);\n if (!pendingPath) {\n return annotations;\n }\n\n for (const edge of pendingPath) {\n if (input.appliedMigrationHashes.has(edge.migrationHash)) {\n continue;\n }\n const existing = annotations.get(edge.migrationHash);\n if (existing?.status === 'applied') {\n continue;\n }\n annotations.set(edge.migrationHash, { status: 'pending' });\n }\n\n return annotations;\n}\n\nexport function appliedHashesFromLedger(\n ledgerEntries: ReadonlyArray<{ readonly migrationHash: string }>,\n): ReadonlySet<string> {\n return new Set(ledgerEntries.map((entry) => entry.migrationHash));\n}\n\nexport function statusForMigrationHash(\n migrationHash: string,\n annotations: ReadonlyMap<string, MigrationEdgeAnnotation>,\n): 'applied' | 'pending' | null {\n const status = annotations.get(migrationHash)?.status;\n return status ?? null;\n}\n","import type { LedgerEntryRecord } from '@prisma-next/contract/types';\nimport type {\n ContractMarkerRecordLike,\n ContractSpaceMember,\n} from '@prisma-next/migration-tools/aggregate';\nimport { EMPTY_CONTRACT_HASH } from '@prisma-next/migration-tools/constants';\nimport {\n errorNoInvariantPath,\n errorUnknownInvariant,\n MigrationToolsError,\n} from '@prisma-next/migration-tools/errors';\nimport { findPath, findPathWithDecision } from '@prisma-next/migration-tools/migration-graph';\nimport { parseContractRef } from '@prisma-next/migration-tools/ref-resolution';\nimport type { RefEntry, Refs } from '@prisma-next/migration-tools/refs';\nimport { readRefs } from '@prisma-next/migration-tools/refs';\nimport { ifDefined } from '@prisma-next/utils/defined';\nimport { notOk, ok, type Result } from '@prisma-next/utils/result';\nimport { dim, yellow } from 'colorette';\nimport { Command } from 'commander';\nimport { loadConfig } from '../config-loader';\nimport { createControlClient } from '../control-api/client';\nimport {\n CliStructuredError,\n errorUnexpected,\n mapMigrationToolsError,\n mapRefResolutionError,\n requireLiveDatabase,\n} from '../utils/cli-errors';\nimport {\n addGlobalOptions,\n collectDeclaredInvariants,\n maskConnectionUrl,\n readContractEnvelope,\n resolveMigrationPaths,\n setCommandDescriptions,\n setCommandExamples,\n setCommandSeeAlso,\n toStructuralEdge,\n} from '../utils/command-helpers';\nimport {\n buildReadAggregate,\n loadContractRawSafely,\n refusePackageCorruptionOnAggregate,\n} from '../utils/contract-space-aggregate-loader';\nimport {\n type MigrationEdgeAnnotation,\n renderMigrationGraphLegend,\n} from '../utils/formatters/migration-graph-labels';\nimport {\n computeGlobalMaxDirNameWidth,\n computeGlobalMaxEdgeTreePrefixWidth,\n indentMigrationGraphTreeBlock,\n renderMigrationGraphSpaceTree,\n} from '../utils/formatters/migration-graph-space-render';\nimport type { MigrationListEntry } from '../utils/formatters/migration-list-types';\nimport { formatStyledHeader } from '../utils/formatters/styled';\nimport type { CommonCommandOptions } from '../utils/global-flags';\nimport { type GlobalFlags, parseGlobalFlagsOrExit } from '../utils/global-flags';\nimport { shouldShowLegend, validateLegendOptions } from '../utils/legend';\nimport { handleResult } from '../utils/result-handler';\nimport { createTerminalUI, type TerminalUI } from '../utils/terminal-ui';\nimport type {\n MigrationStatusEntry,\n MigrationStatusResult,\n MigrationStatusSpace,\n StatusDiagnosticJson,\n} from './json/schemas';\nimport { migrationStatusJsonResultSchema } from './json/schemas';\nimport {\n listRefsByContractHash,\n migrationSpaceListEntriesFromAggregate,\n runMigrationList,\n} from './migration-list';\nimport {\n appliedHashesFromLedger,\n deriveStatusEdgeAnnotations,\n statusForMigrationHash,\n} from './migration-status-overlay';\n\nexport type { StatusRef } from '../utils/migration-types';\nexport type {\n MigrationStatusEntry,\n MigrationStatusResult,\n MigrationStatusSpace,\n StatusDiagnosticJson,\n};\nexport { migrationStatusJsonResultSchema };\n\nexport interface MigrationStatusOptions extends CommonCommandOptions {\n readonly db?: string;\n readonly config?: string;\n readonly to?: string;\n readonly from?: string;\n readonly space?: string;\n readonly legend?: boolean;\n readonly ascii?: boolean;\n}\n\nexport interface MigrationStatusTreeSection {\n readonly space: string;\n readonly tree: string;\n readonly showHeading: boolean;\n}\n\ninterface MigrationStatusCommandResult {\n readonly ok: true;\n readonly spaces: readonly MigrationStatusSpace[];\n readonly summary: string;\n readonly diagnostics: readonly StatusDiagnosticJson[];\n readonly treeSections: readonly MigrationStatusTreeSection[];\n}\n\nfunction shortDisplayHash(hash: string): string {\n const stripped = hash.startsWith('sha256:') ? hash.slice(7) : hash;\n return stripped.slice(0, 12);\n}\n\nfunction resolveTarget(contractHash: string, activeRefHash: string | undefined): string {\n return activeRefHash ?? contractHash;\n}\n\nfunction buildStatusMigrations(\n listMigrations: readonly MigrationListEntry[],\n annotations: ReadonlyMap<string, MigrationEdgeAnnotation>,\n): readonly MigrationStatusEntry[] {\n return listMigrations.map((migration) => ({\n ...migration,\n status: statusForMigrationHash(migration.hash, annotations),\n }));\n}\n\nfunction renderSpaceTree(args: {\n readonly member: ContractSpaceMember;\n readonly liveContractHash: string;\n readonly migrations: readonly MigrationListEntry[];\n readonly markerHash: string | undefined;\n readonly showDbMarker: boolean;\n readonly statusOverlay: ReadonlyMap<string, MigrationEdgeAnnotation>;\n readonly colorize: boolean;\n readonly glyphMode: 'unicode' | 'ascii';\n readonly isAppSpace: boolean;\n readonly globalMaxEdgeTreePrefixWidth?: number;\n readonly globalMaxDirNameWidth?: number;\n}): string {\n const graph = args.member.graph();\n if (graph.nodes.size === 0) {\n return '';\n }\n return renderMigrationGraphSpaceTree({\n graph,\n migrations: args.migrations,\n liveContractHash: args.liveContractHash,\n refsByHash: listRefsByContractHash(args.member),\n statusOverlayByHash: args.statusOverlay,\n colorize: args.colorize,\n glyphMode: args.glyphMode,\n isAppSpace: args.isAppSpace,\n ...(args.showDbMarker && args.markerHash !== undefined ? { dbHash: args.markerHash } : {}),\n ...(args.globalMaxEdgeTreePrefixWidth !== undefined\n ? { globalMaxEdgeTreePrefixWidth: args.globalMaxEdgeTreePrefixWidth }\n : {}),\n ...(args.globalMaxDirNameWidth !== undefined\n ? { globalMaxDirNameWidth: args.globalMaxDirNameWidth }\n : {}),\n });\n}\n\nfunction countPending(migrations: readonly MigrationStatusEntry[]): number {\n return migrations.filter((m) => m.status === 'pending').length;\n}\n\nexport function buildNoPathSummary(args: {\n readonly markerHash: string | undefined;\n readonly targetHash: string;\n readonly explicitTarget: boolean;\n readonly refName: string | undefined;\n}): string {\n const markerPart =\n args.markerHash !== undefined\n ? `the database state (${shortDisplayHash(args.markerHash)})`\n : 'the database state';\n const targetShort = shortDisplayHash(args.targetHash);\n if (!args.explicitTarget) {\n return `No migration path from ${markerPart} to the application's contract (${targetShort}). Run \\`prisma-next migration plan --name <name>\\` to author one.`;\n }\n const targetLabel =\n args.refName !== undefined\n ? `the target (${targetShort} via \\`${args.refName}\\`)`\n : `the target (${targetShort})`;\n return `No migration path from ${markerPart} to ${targetLabel}. Run \\`prisma-next migration plan --name <name>\\` to author one, or pass \\`--to <contract>\\` to pick a reachable target.`;\n}\n\nexport function buildStatusHeadline(args: {\n readonly pendingCount: number;\n readonly targetHash: string;\n readonly markerDiverged: boolean;\n readonly markerHash: string | undefined;\n}): string {\n if (args.markerDiverged && args.markerHash !== undefined) {\n return `Database marker ${shortDisplayHash(args.markerHash)} is not in the on-disk migration graph`;\n }\n if (args.pendingCount === 0) {\n return 'Up to date';\n }\n return `${args.pendingCount} pending — run \\`prisma-next migrate --to ${shortDisplayHash(args.targetHash)}\\``;\n}\n\nexport function formatStatusSummary(\n result: MigrationStatusCommandResult,\n colorize: boolean,\n): string {\n const c = (fn: (s: string) => string, s: string) => (colorize ? fn(s) : s);\n const lines: string[] = [];\n const pendingTotal = result.spaces.reduce(\n (sum, space) => sum + countPending(space.migrations),\n 0,\n );\n const hasDivergence = result.diagnostics.some(\n (d) => d.code === 'MIGRATION.MARKER_NOT_IN_HISTORY',\n );\n if (hasDivergence || pendingTotal > 0) {\n lines.push(c(yellow, result.summary));\n } else {\n lines.push(result.summary);\n }\n const missingInvariantsDiagnostic = result.diagnostics.find(\n (d) => d.code === 'MIGRATION.MISSING_INVARIANTS',\n );\n if (missingInvariantsDiagnostic !== undefined) {\n lines.push(c(dim, missingInvariantsDiagnostic.message));\n }\n return lines.join('\\n');\n}\n\nexport function formatStatusHumanOutput(\n result: MigrationStatusCommandResult,\n colorize: boolean,\n): string {\n const sections: string[] = [];\n for (const section of result.treeSections) {\n if (section.showHeading) {\n sections.push(`${section.space}:`);\n }\n if (section.tree.length > 0) {\n sections.push(section.tree);\n } else {\n sections.push('(no migrations)');\n }\n sections.push('');\n }\n sections.push(formatStatusSummary(result, colorize));\n return sections.join('\\n').trimEnd();\n}\n\nasync function readMarkersAndLedgers(args: {\n readonly client: ReturnType<typeof createControlClient>;\n readonly spaceIds: readonly string[];\n}): Promise<{\n readonly markersBySpace: ReadonlyMap<string, ContractMarkerRecordLike>;\n readonly ledgersBySpace: ReadonlyMap<string, readonly LedgerEntryRecord[]>;\n}> {\n const markersBySpace = new Map<string, ContractMarkerRecordLike>();\n const all = await args.client.readAllMarkers();\n for (const [spaceId, marker] of all) {\n markersBySpace.set(spaceId, marker);\n }\n const ledgersBySpace = new Map<string, readonly LedgerEntryRecord[]>();\n for (const spaceId of args.spaceIds) {\n ledgersBySpace.set(spaceId, await args.client.readLedger(spaceId));\n }\n return { markersBySpace, ledgersBySpace };\n}\n\nexport async function executeMigrationStatusCommand(\n options: MigrationStatusOptions,\n flags: GlobalFlags,\n ui: TerminalUI,\n): Promise<Result<MigrationStatusCommandResult, CliStructuredError>> {\n const config = await loadConfig(options.config);\n const { configPath, migrationsDir, migrationsRelative, refsDir } = resolveMigrationPaths(\n options.config,\n config,\n );\n\n const dbConnection = options.db ?? config.db?.connection;\n const hasDriver = !!config.driver;\n const usingFromOverride = options.from !== undefined;\n\n if (!usingFromOverride) {\n const missingDb = requireLiveDatabase({\n dbConnection,\n hasDriver,\n why: 'migration status needs a database connection to read the marker and ledger (or pass --from for offline path preview)',\n retryCommand: 'prisma-next migration status --from <contract>',\n });\n if (missingDb) {\n return notOk(missingDb);\n }\n }\n\n let allRefs: Refs = {};\n try {\n allRefs = await readRefs(refsDir);\n } catch (error) {\n if (MigrationToolsError.is(error)) {\n return notOk(mapMigrationToolsError(error));\n }\n throw error;\n }\n\n const diagnostics: StatusDiagnosticJson[] = [];\n let contractHash: string = EMPTY_CONTRACT_HASH;\n try {\n const envelope = await readContractEnvelope(config);\n contractHash = envelope.storageHash;\n } catch (error) {\n diagnostics.push({\n code: 'CONTRACT.UNREADABLE',\n severity: 'warn',\n message: `Could not read contract: ${error instanceof Error ? error.message : 'unknown error'}`,\n hints: [\"Run 'prisma-next contract emit' to generate a valid contract\"],\n });\n }\n\n const loaded = await buildReadAggregate(config, { migrationsDir });\n if (!loaded.ok) {\n return notOk(loaded.failure);\n }\n\n const { aggregate } = loaded.value;\n const contractRawForAggregate = await loadContractRawSafely(config);\n if (contractRawForAggregate !== null) {\n const corruptionFailure = refusePackageCorruptionOnAggregate(aggregate);\n if (corruptionFailure) {\n return notOk(corruptionFailure);\n }\n }\n const appGraph = aggregate.app.graph();\n\n let activeRefHash: string | undefined;\n let activeRefName: string | undefined;\n let activeRefEntry: RefEntry | undefined;\n let fromOverrideHash: string | undefined;\n\n if (options.to) {\n const refResult = parseContractRef(options.to, { graph: appGraph, refs: allRefs });\n if (!refResult.ok) {\n return notOk(mapRefResolutionError(refResult.failure));\n }\n activeRefHash = refResult.value.hash;\n if (refResult.value.provenance.kind === 'ref') {\n activeRefName = refResult.value.provenance.refName;\n activeRefEntry = allRefs[activeRefName];\n }\n }\n\n if (options.from) {\n const fromResult = parseContractRef(options.from, { graph: appGraph, refs: allRefs });\n if (!fromResult.ok) {\n return notOk(mapRefResolutionError(fromResult.failure));\n }\n fromOverrideHash = fromResult.value.hash;\n }\n\n const requiredInvariants: readonly string[] = [...(activeRefEntry?.invariants ?? [])].sort();\n\n if (!flags.json && !flags.quiet) {\n const details: Array<{ label: string; value: string }> = [\n { label: 'config', value: configPath },\n { label: 'migrations', value: migrationsRelative },\n ];\n if (dbConnection && hasDriver) {\n details.push({ label: 'database', value: maskConnectionUrl(String(dbConnection)) });\n }\n if (activeRefName) {\n details.push({ label: 'ref', value: activeRefName });\n }\n if (options.from) {\n details.push({ label: 'from', value: options.from });\n }\n if (options.space) {\n details.push({ label: 'space', value: options.space });\n }\n const header = formatStyledHeader({\n command: 'migration status',\n description: 'Show migration history and applied status',\n details,\n flags,\n });\n ui.stderr(header);\n if (shouldShowLegend(options, flags)) {\n ui.stderr(\n renderMigrationGraphLegend({\n colorize: flags.color !== false,\n glyphMode: ui.resolveGlyphMode(options.ascii === true),\n }),\n );\n ui.stderr('');\n }\n }\n\n const listSpaces = await migrationSpaceListEntriesFromAggregate(aggregate, migrationsDir);\n const listResult = runMigrationList({\n spaces: listSpaces,\n ...ifDefined('spaceFilter', options.space),\n });\n if (!listResult.ok) {\n return listResult;\n }\n\n const scopedSpaces = listResult.value.spaces;\n const showSpaceHeadings = scopedSpaces.length > 1;\n\n let markersBySpace = new Map<string, ContractMarkerRecordLike>();\n let ledgersBySpace = new Map<string, readonly LedgerEntryRecord[]>();\n let connected = false;\n\n if (dbConnection && hasDriver && !usingFromOverride) {\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 try {\n await client.connect(dbConnection);\n connected = true;\n const read = await readMarkersAndLedgers({\n client,\n spaceIds: scopedSpaces.map((s) => s.space),\n });\n markersBySpace = new Map(read.markersBySpace);\n ledgersBySpace = new Map(read.ledgersBySpace);\n } catch (error) {\n if (CliStructuredError.is(error)) {\n return notOk(error);\n }\n return notOk(\n errorUnexpected(error instanceof Error ? error.message : String(error), {\n why: `Failed to read database state: ${error instanceof Error ? error.message : String(error)}`,\n }),\n );\n } finally {\n await client.close();\n }\n }\n\n if (activeRefEntry && activeRefEntry.invariants.length > 0 && connected) {\n const declared = collectDeclaredInvariants(appGraph);\n const markerInvariants = markersBySpace.get(aggregate.app.spaceId)?.invariants ?? [];\n const known = new Set<string>(declared);\n for (const id of markerInvariants) known.add(id);\n const unknown = activeRefEntry.invariants.filter((id) => !known.has(id));\n if (unknown.length > 0) {\n return notOk(\n mapMigrationToolsError(\n errorUnknownInvariant({\n ...ifDefined('refName', activeRefName),\n unknown,\n declared: [...declared].sort(),\n }),\n ),\n );\n }\n }\n\n const showAppliedOverlay = connected && !usingFromOverride;\n const showDbMarker = connected && !usingFromOverride;\n const glyphMode = ui.resolveGlyphMode(options.ascii === true);\n const colorize = flags.color !== false;\n\n const statusSpaces: MigrationStatusSpace[] = [];\n const treeSections: MigrationStatusTreeSection[] = [];\n let markerDiverged = false;\n let markerCannotReachTarget = false;\n let headlineTargetHash = activeRefHash ?? contractHash;\n let totalPending = 0;\n\n const globalLayoutInputs = showSpaceHeadings\n ? scopedSpaces\n .filter((spaceEntry) => spaceEntry.migrations.length > 0)\n .map((spaceEntry) => ({\n graph: aggregate.space(spaceEntry.space)!.graph(),\n liveContractHash: contractHash,\n }))\n : [];\n const globalMaxEdgeTreePrefixWidth =\n globalLayoutInputs.length > 0\n ? computeGlobalMaxEdgeTreePrefixWidth(globalLayoutInputs)\n : undefined;\n const globalMaxDirNameWidth =\n globalLayoutInputs.length > 0 ? computeGlobalMaxDirNameWidth(globalLayoutInputs) : undefined;\n\n for (const spaceEntry of scopedSpaces) {\n const member = aggregate.space(spaceEntry.space);\n if (member === undefined) {\n continue;\n }\n const graph = member.graph();\n const spaceContractHash = member.contract().storage.storageHash;\n const targetHash = resolveTarget(spaceContractHash, activeRefHash);\n if (spaceEntry.space === aggregate.app.spaceId) {\n headlineTargetHash = targetHash;\n }\n\n const markerRecord = markersBySpace.get(spaceEntry.space);\n const markerHash = usingFromOverride\n ? fromOverrideHash\n : (markerRecord?.storageHash ?? undefined);\n const originHash = markerHash ?? EMPTY_CONTRACT_HASH;\n const markerInGraph =\n markerHash === undefined || graph.nodes.has(markerHash) || markerHash === spaceContractHash;\n if (\n connected &&\n !usingFromOverride &&\n markerInGraph &&\n originHash !== targetHash &&\n findPath(graph, originHash, targetHash) === null\n ) {\n markerCannotReachTarget = true;\n }\n\n if (connected && !usingFromOverride && markerHash !== undefined && !markerInGraph) {\n markerDiverged = true;\n diagnostics.push({\n code: 'MIGRATION.MARKER_NOT_IN_HISTORY',\n severity: 'warn',\n message:\n 'Database was updated outside the migration system (marker does not match any migration)',\n hints: [\n \"Run 'prisma-next db sign' to overwrite the marker if the database already matches the contract\",\n \"Run 'prisma-next db update' to push the current contract to the database\",\n ],\n });\n }\n\n const ledger = ledgersBySpace.get(spaceEntry.space) ?? [];\n const appliedHashes = showAppliedOverlay ? appliedHashesFromLedger(ledger) : new Set<string>();\n\n const annotations = deriveStatusEdgeAnnotations({\n graph,\n targetHash,\n originHash,\n appliedMigrationHashes: appliedHashes,\n showAppliedOverlay,\n });\n const isAppSpace = spaceEntry.space === aggregate.app.spaceId;\n const tree = renderSpaceTree({\n member,\n liveContractHash: contractHash,\n migrations: spaceEntry.migrations,\n markerHash,\n showDbMarker,\n statusOverlay: annotations,\n colorize,\n glyphMode,\n isAppSpace,\n ...(globalMaxEdgeTreePrefixWidth !== undefined ? { globalMaxEdgeTreePrefixWidth } : {}),\n ...(globalMaxDirNameWidth !== undefined ? { globalMaxDirNameWidth } : {}),\n });\n const migrations = buildStatusMigrations(spaceEntry.migrations, annotations);\n const pending = countPending(migrations);\n totalPending += pending;\n\n statusSpaces.push({\n space: spaceEntry.space,\n currentContract: markerHash ?? null,\n targetContract: targetHash,\n migrations: [...migrations],\n });\n const displayTree =\n showSpaceHeadings && tree.length > 0 ? indentMigrationGraphTreeBlock(tree, ' ') : tree;\n treeSections.push({\n space: spaceEntry.space,\n tree: displayTree,\n showHeading: showSpaceHeadings,\n });\n }\n\n if (connected && requiredInvariants.length > 0) {\n const markerInvariants = markersBySpace.get(aggregate.app.spaceId)?.invariants ?? [];\n const markerSet = new Set(markerInvariants);\n const missing = requiredInvariants.filter((id) => !markerSet.has(id));\n if (missing.length > 0) {\n diagnostics.push({\n code: 'MIGRATION.MISSING_INVARIANTS',\n ...ifDefined('ref', activeRefName),\n invariants: missing,\n message: `missing invariant(s): ${missing.join(', ')}`,\n });\n if (activeRefHash !== undefined) {\n const originHash =\n markersBySpace.get(aggregate.app.spaceId)?.storageHash ?? EMPTY_CONTRACT_HASH;\n const outcome = findPathWithDecision(appGraph, originHash, activeRefHash, {\n ...ifDefined('refName', activeRefName),\n required: new Set(missing),\n });\n if (outcome.kind === 'unsatisfiable') {\n return notOk(\n mapMigrationToolsError(\n errorNoInvariantPath({\n ...ifDefined('refName', activeRefName),\n required: [...missing].sort(),\n missing: outcome.missing,\n structuralPath: outcome.structuralPath.map(toStructuralEdge),\n }),\n ),\n );\n }\n }\n }\n }\n\n const appMarkerHash = markersBySpace.get(aggregate.app.spaceId)?.storageHash;\n const summary = markerCannotReachTarget\n ? buildNoPathSummary({\n markerHash: appMarkerHash,\n targetHash: headlineTargetHash,\n explicitTarget: options.to !== undefined,\n refName: activeRefName,\n })\n : buildStatusHeadline({\n pendingCount: totalPending,\n targetHash: headlineTargetHash,\n markerDiverged,\n markerHash: appMarkerHash,\n });\n\n if (scopedSpaces.every((s) => s.migrations.length === 0)) {\n return ok({\n ok: true,\n spaces: statusSpaces,\n summary: 'No migrations found',\n diagnostics,\n treeSections,\n });\n }\n\n return ok({\n ok: true,\n spaces: statusSpaces,\n summary,\n diagnostics,\n treeSections,\n });\n}\n\nexport function createMigrationStatusCommand(): Command {\n const command = new Command('status');\n setCommandDescriptions(\n command,\n 'Show migration path and pending status',\n 'Shows which migrations are pending between the database marker and\\n' +\n 'the target contract. Requires a database connection.\\n' +\n 'Pass --from for an offline path preview without a database.\\n' +\n 'Use `migration graph` for topology, `migration log` for history,\\n' +\n 'and `migration list` for on-disk enumeration.',\n );\n setCommandExamples(command, [\n 'prisma-next migration status --db $DATABASE_URL',\n 'prisma-next migration status --to production --db $DATABASE_URL',\n 'prisma-next migration status --from sha256:abc --to production',\n 'prisma-next migration status --from sha256:abc --to production --json',\n 'prisma-next migration status --ascii --from sha256:abc --to production',\n 'prisma-next migration status --legend --from sha256:abc --to production',\n ]);\n setCommandSeeAlso(command, [\n { verb: 'migration log', oneLiner: 'Show executed migration history' },\n { verb: 'migration list', oneLiner: 'List on-disk migrations' },\n { verb: 'migration graph', oneLiner: 'Show the migration graph topology' },\n { verb: 'migration show', oneLiner: 'Display migration package contents' },\n ]);\n addGlobalOptions(command)\n .option('--db <url>', 'Database connection string')\n .option('--config <path>', 'Path to prisma-next.config.ts')\n .option('--space <id>', 'Narrow output to a single contract space')\n .option(\n '--to <contract>',\n 'Target contract reference (hash, prefix, ref name, migration dir name, <dir>^, or ./path)',\n )\n .option(\n '--from <contract>',\n 'Origin contract reference; same grammar as --to. Supplying --from switches to offline path computation.',\n )\n .option('--legend', 'Print a key for the tree glyphs and lane colors')\n .option('--ascii', 'Use ASCII glyphs (pipe-friendly)')\n .action(async (options: MigrationStatusOptions) => {\n const flags = parseGlobalFlagsOrExit(options);\n const ui = createTerminalUI(flags);\n\n const legendValidation = validateLegendOptions(options, flags);\n if (!legendValidation.ok) {\n process.exit(handleResult(legendValidation, flags, ui));\n }\n\n const result = await executeMigrationStatusCommand(options, flags, ui);\n\n const exitCode = handleResult(result, flags, ui, (statusResult) => {\n if (flags.json) {\n const jsonResult: MigrationStatusResult = {\n ok: true,\n spaces: [...statusResult.spaces],\n summary: statusResult.summary,\n diagnostics: [...statusResult.diagnostics],\n };\n ui.output(JSON.stringify(jsonResult, null, 2));\n } else if (!flags.quiet) {\n ui.output(formatStatusHumanOutput(statusResult, flags.color !== false));\n }\n });\n\n process.exit(exitCode);\n });\n\n return command;\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.12.0-dev.
|
|
3
|
+
"version": "0.12.0-dev.68",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -9,15 +9,15 @@
|
|
|
9
9
|
},
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"@clack/prompts": "^1.4.0",
|
|
12
|
-
"@prisma-next/config": "0.12.0-dev.
|
|
13
|
-
"@prisma-next/contract": "0.12.0-dev.
|
|
14
|
-
"@prisma-next/emitter": "0.12.0-dev.
|
|
15
|
-
"@prisma-next/errors": "0.12.0-dev.
|
|
16
|
-
"@prisma-next/framework-components": "0.12.0-dev.
|
|
17
|
-
"@prisma-next/migration-tools": "0.12.0-dev.
|
|
18
|
-
"@prisma-next/psl-printer": "0.12.0-dev.
|
|
19
|
-
"@prisma-next/cli-telemetry": "0.12.0-dev.
|
|
20
|
-
"@prisma-next/utils": "0.12.0-dev.
|
|
12
|
+
"@prisma-next/config": "0.12.0-dev.68",
|
|
13
|
+
"@prisma-next/contract": "0.12.0-dev.68",
|
|
14
|
+
"@prisma-next/emitter": "0.12.0-dev.68",
|
|
15
|
+
"@prisma-next/errors": "0.12.0-dev.68",
|
|
16
|
+
"@prisma-next/framework-components": "0.12.0-dev.68",
|
|
17
|
+
"@prisma-next/migration-tools": "0.12.0-dev.68",
|
|
18
|
+
"@prisma-next/psl-printer": "0.12.0-dev.68",
|
|
19
|
+
"@prisma-next/cli-telemetry": "0.12.0-dev.68",
|
|
20
|
+
"@prisma-next/utils": "0.12.0-dev.68",
|
|
21
21
|
"arktype": "^2.2.0",
|
|
22
22
|
"c12": "^3.3.4",
|
|
23
23
|
"ci-info": "^4.3.1",
|
|
@@ -34,14 +34,14 @@
|
|
|
34
34
|
"wrap-ansi": "^10.0.0"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
|
-
"@prisma-next/sql-contract": "0.12.0-dev.
|
|
38
|
-
"@prisma-next/sql-contract-emitter": "0.12.0-dev.
|
|
39
|
-
"@prisma-next/sql-contract-ts": "0.12.0-dev.
|
|
40
|
-
"@prisma-next/sql-operations": "0.12.0-dev.
|
|
41
|
-
"@prisma-next/sql-runtime": "0.12.0-dev.
|
|
42
|
-
"@prisma-next/test-utils": "0.12.0-dev.
|
|
43
|
-
"@prisma-next/tsconfig": "0.12.0-dev.
|
|
44
|
-
"@prisma-next/tsdown": "0.12.0-dev.
|
|
37
|
+
"@prisma-next/sql-contract": "0.12.0-dev.68",
|
|
38
|
+
"@prisma-next/sql-contract-emitter": "0.12.0-dev.68",
|
|
39
|
+
"@prisma-next/sql-contract-ts": "0.12.0-dev.68",
|
|
40
|
+
"@prisma-next/sql-operations": "0.12.0-dev.68",
|
|
41
|
+
"@prisma-next/sql-runtime": "0.12.0-dev.68",
|
|
42
|
+
"@prisma-next/test-utils": "0.12.0-dev.68",
|
|
43
|
+
"@prisma-next/tsconfig": "0.12.0-dev.68",
|
|
44
|
+
"@prisma-next/tsdown": "0.12.0-dev.68",
|
|
45
45
|
"@types/node": "25.9.1",
|
|
46
46
|
"tsdown": "0.22.1",
|
|
47
47
|
"typescript": "5.9.3",
|
|
@@ -173,6 +173,7 @@
|
|
|
173
173
|
"lint": "biome check . --error-on-warnings",
|
|
174
174
|
"lint:fix": "biome check --write .",
|
|
175
175
|
"lint:fix:unsafe": "biome check --write --unsafe .",
|
|
176
|
+
"gallery": "tsx scripts/gallery.ts",
|
|
176
177
|
"record": "tsx scripts/record.ts",
|
|
177
178
|
"clean": "rm -rf dist dist-tsc dist-tsc-prod coverage .tmp-output"
|
|
178
179
|
}
|
package/src/commands/migrate.ts
CHANGED
|
@@ -52,16 +52,21 @@ import {
|
|
|
52
52
|
refuseContractSpaceIntegrity,
|
|
53
53
|
} from '../utils/contract-space-aggregate-loader';
|
|
54
54
|
import { toDeclaredExtensionsFromRaw } from '../utils/extension-pack-inputs';
|
|
55
|
-
import { buildMigrationGraphLayout } from '../utils/formatters/migration-graph-layout';
|
|
56
|
-
import { buildMigrationGraphRows } from '../utils/formatters/migration-graph-rows';
|
|
57
|
-
import { indentMigrationGraphTreeBlock } from '../utils/formatters/migration-graph-space-render';
|
|
58
|
-
import type { MigrationEdgeAnnotation } from '../utils/formatters/migration-graph-tree-render';
|
|
59
55
|
import {
|
|
60
|
-
|
|
61
|
-
|
|
56
|
+
computeLabelColumn,
|
|
57
|
+
computeMaxDirNameWidth,
|
|
58
|
+
renderMigrationGraphCommand,
|
|
59
|
+
} from '../utils/formatters/migration-graph-command-render';
|
|
60
|
+
import { buildGrid } from '../utils/formatters/migration-graph-grid-layout';
|
|
61
|
+
import {
|
|
62
62
|
formatOnPathMigrationRow,
|
|
63
|
-
|
|
64
|
-
} from '../utils/formatters/migration-graph-
|
|
63
|
+
type MigrationEdgeAnnotation,
|
|
64
|
+
} from '../utils/formatters/migration-graph-labels';
|
|
65
|
+
import { buildMigrationGraphRows } from '../utils/formatters/migration-graph-rows';
|
|
66
|
+
import {
|
|
67
|
+
highlightFromEdgeAnnotations,
|
|
68
|
+
indentMigrationGraphTreeBlock,
|
|
69
|
+
} from '../utils/formatters/migration-graph-space-render';
|
|
65
70
|
import { formatMigrationApplyCommandOutput } from '../utils/formatters/migrations';
|
|
66
71
|
import { formatStyledHeader } from '../utils/formatters/styled';
|
|
67
72
|
import type { CommonCommandOptions } from '../utils/global-flags';
|
|
@@ -444,20 +449,27 @@ async function executeMigrateShowCommand(
|
|
|
444
449
|
const isApp = member.spaceId === aggregate.app.spaceId;
|
|
445
450
|
const memberGraph = member.graph();
|
|
446
451
|
const rowModel = buildMigrationGraphRows(memberGraph, isApp ? { contractHash } : {});
|
|
447
|
-
const
|
|
448
|
-
|
|
452
|
+
const edgeAnnotations = new Map<string, MigrationEdgeAnnotation>();
|
|
453
|
+
for (const edge of memberGraph.migrationByHash.values()) {
|
|
454
|
+
edgeAnnotations.set(edge.migrationHash, {
|
|
455
|
+
pathHighlight: onPathHashes.has(edge.migrationHash) ? 'on-path' : 'off-path',
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
// The on-path migration set lifts to focus mode so the chosen route draws
|
|
459
|
+
// green/continuous; off-path lanes dim. Rows, gutter, and labels all come
|
|
460
|
+
// from this one grid.
|
|
461
|
+
const grid = buildGrid(rowModel, {}, highlightFromEdgeAnnotations(edgeAnnotations));
|
|
462
|
+
return { member, isApp, memberGraph, rowModel, grid, edgeAnnotations };
|
|
449
463
|
});
|
|
450
464
|
|
|
451
|
-
// Global max across all space
|
|
452
|
-
const
|
|
465
|
+
// Global max across all space grids so every section's labels share columns.
|
|
466
|
+
const globalLabelColumn =
|
|
453
467
|
memberLayouts.length > 1
|
|
454
|
-
? Math.max(
|
|
455
|
-
...memberLayouts.map(({ layout }) => computeMaxEdgeTreePrefixWidthForLayout(layout)),
|
|
456
|
-
)
|
|
468
|
+
? Math.max(...memberLayouts.map(({ grid }) => computeLabelColumn(grid, 'unicode')))
|
|
457
469
|
: undefined;
|
|
458
470
|
const globalMaxDirNameWidthFromLayouts =
|
|
459
471
|
memberLayouts.length > 1
|
|
460
|
-
? Math.max(...memberLayouts.map(({
|
|
472
|
+
? Math.max(...memberLayouts.map(({ rowModel }) => computeMaxDirNameWidth(rowModel)))
|
|
461
473
|
: undefined;
|
|
462
474
|
// The run-list name column width must be at least as wide as the global tree dirName
|
|
463
475
|
// width so that tree sections and the list align at the hash column.
|
|
@@ -470,28 +482,25 @@ async function executeMigrateShowCommand(
|
|
|
470
482
|
? Math.max(globalMaxDirNameWidthFromLayouts, runListMaxFromMigrations)
|
|
471
483
|
: undefined;
|
|
472
484
|
runListDirNameWidth = globalMaxDirNameWidth ?? runListMaxFromMigrations;
|
|
473
|
-
runListLeftPad =
|
|
485
|
+
runListLeftPad = globalLabelColumn;
|
|
474
486
|
|
|
475
487
|
// Render each space section with globally computed widths.
|
|
476
488
|
const showSpaceHeadings = allMembers.length > 1;
|
|
477
489
|
const sections: string[] = [];
|
|
478
|
-
for (const { member, isApp,
|
|
479
|
-
const edgeAnnotations = new Map<string, MigrationEdgeAnnotation>();
|
|
480
|
-
for (const edge of memberGraph.migrationByHash.values()) {
|
|
481
|
-
edgeAnnotations.set(edge.migrationHash, {
|
|
482
|
-
pathHighlight: onPathHashes.has(edge.migrationHash) ? 'on-path' : 'off-path',
|
|
483
|
-
});
|
|
484
|
-
}
|
|
490
|
+
for (const { member, isApp, rowModel, grid, edgeAnnotations } of memberLayouts) {
|
|
485
491
|
const liveMarker = markerBySpace.get(member.spaceId) ?? null;
|
|
486
492
|
const liveMarkerHash = liveMarker?.storageHash ?? EMPTY_CONTRACT_HASH;
|
|
487
|
-
const tree =
|
|
493
|
+
const tree = renderMigrationGraphCommand({
|
|
494
|
+
grid,
|
|
495
|
+
rowModel,
|
|
488
496
|
contractHash,
|
|
489
497
|
isAppSpace: isApp,
|
|
490
498
|
...(needsLiveMarker ? { dbHash: liveMarkerHash } : {}),
|
|
491
499
|
refsByHash: listRefsByContractHash(member),
|
|
492
500
|
edgeAnnotationsByHash: edgeAnnotations,
|
|
493
501
|
colorize,
|
|
494
|
-
|
|
502
|
+
glyphMode: 'unicode',
|
|
503
|
+
...(globalLabelColumn !== undefined ? { globalLabelColumn } : {}),
|
|
495
504
|
...(globalMaxDirNameWidth !== undefined ? { globalMaxDirNameWidth } : {}),
|
|
496
505
|
});
|
|
497
506
|
if (tree.length === 0) continue;
|
|
@@ -13,13 +13,13 @@ import {
|
|
|
13
13
|
setCommandSeeAlso,
|
|
14
14
|
} from '../utils/command-helpers';
|
|
15
15
|
import { buildReadAggregate } from '../utils/contract-space-aggregate-loader';
|
|
16
|
+
import { renderMigrationGraphLegend } from '../utils/formatters/migration-graph-labels';
|
|
16
17
|
import {
|
|
17
18
|
computeGlobalMaxDirNameWidth,
|
|
18
19
|
computeGlobalMaxEdgeTreePrefixWidth,
|
|
19
20
|
indentMigrationGraphTreeBlock,
|
|
20
21
|
renderMigrationGraphSpaceTree,
|
|
21
22
|
} from '../utils/formatters/migration-graph-space-render';
|
|
22
|
-
import { renderMigrationGraphLegend } from '../utils/formatters/migration-graph-tree-render';
|
|
23
23
|
import { formatStyledHeader } from '../utils/formatters/styled';
|
|
24
24
|
import type { CommonCommandOptions } from '../utils/global-flags';
|
|
25
25
|
import { type GlobalFlags, parseGlobalFlagsOrExit } from '../utils/global-flags';
|
|
@@ -27,7 +27,7 @@ import {
|
|
|
27
27
|
setCommandSeeAlso,
|
|
28
28
|
} from '../utils/command-helpers';
|
|
29
29
|
import { buildReadAggregate } from '../utils/contract-space-aggregate-loader';
|
|
30
|
-
import { renderMigrationGraphLegend } from '../utils/formatters/migration-graph-
|
|
30
|
+
import { renderMigrationGraphLegend } from '../utils/formatters/migration-graph-labels';
|
|
31
31
|
import { renderMigrationListWithStyle } from '../utils/formatters/migration-list-render';
|
|
32
32
|
import { createAnsiMigrationListStyler } from '../utils/formatters/migration-list-styler';
|
|
33
33
|
import type {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { MigrationGraph } from '@prisma-next/migration-tools/graph';
|
|
2
2
|
import { findPath } from '@prisma-next/migration-tools/migration-graph';
|
|
3
|
-
import type { MigrationEdgeAnnotation } from '../utils/formatters/migration-graph-
|
|
3
|
+
import type { MigrationEdgeAnnotation } from '../utils/formatters/migration-graph-labels';
|
|
4
4
|
|
|
5
5
|
export interface DeriveStatusEdgeAnnotationsInput {
|
|
6
6
|
readonly graph: MigrationGraph;
|
|
@@ -42,14 +42,16 @@ import {
|
|
|
42
42
|
loadContractRawSafely,
|
|
43
43
|
refusePackageCorruptionOnAggregate,
|
|
44
44
|
} from '../utils/contract-space-aggregate-loader';
|
|
45
|
+
import {
|
|
46
|
+
type MigrationEdgeAnnotation,
|
|
47
|
+
renderMigrationGraphLegend,
|
|
48
|
+
} from '../utils/formatters/migration-graph-labels';
|
|
45
49
|
import {
|
|
46
50
|
computeGlobalMaxDirNameWidth,
|
|
47
51
|
computeGlobalMaxEdgeTreePrefixWidth,
|
|
48
52
|
indentMigrationGraphTreeBlock,
|
|
49
53
|
renderMigrationGraphSpaceTree,
|
|
50
54
|
} from '../utils/formatters/migration-graph-space-render';
|
|
51
|
-
import type { MigrationEdgeAnnotation } from '../utils/formatters/migration-graph-tree-render';
|
|
52
|
-
import { renderMigrationGraphLegend } from '../utils/formatters/migration-graph-tree-render';
|
|
53
55
|
import type { MigrationListEntry } from '../utils/formatters/migration-list-types';
|
|
54
56
|
import { formatStyledHeader } from '../utils/formatters/styled';
|
|
55
57
|
import type { CommonCommandOptions } from '../utils/global-flags';
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Command graph renderer: composes the gutter (from the grid) with per-row labels.
|
|
3
|
+
*
|
|
4
|
+
* Pipeline: buildMigrationGraphRows → buildGrid → renderMigrationGraphCommand
|
|
5
|
+
*
|
|
6
|
+
* Each grid row is classified by its cells: a node row gets a contract label;
|
|
7
|
+
* a migration arrow row gets a migration label; connector rows get no label.
|
|
8
|
+
* Label format and styling live in `./migration-graph-labels`.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { EMPTY_CONTRACT_HASH } from '@prisma-next/migration-tools/constants';
|
|
12
|
+
import { ifDefined } from '@prisma-next/utils/defined';
|
|
13
|
+
import stringWidth from 'string-width';
|
|
14
|
+
import type { GlyphMode } from '../glyph-mode';
|
|
15
|
+
import {
|
|
16
|
+
formatMigrationLabel,
|
|
17
|
+
formatNodeLabel,
|
|
18
|
+
type MigrationEdgeAnnotation,
|
|
19
|
+
type MigrationGraphLabelOptions,
|
|
20
|
+
} from './migration-graph-labels';
|
|
21
|
+
import type { Cell, CellLine, Grid } from './migration-graph-model';
|
|
22
|
+
import { renderGridRow } from './migration-graph-occlusion-render';
|
|
23
|
+
import type { ClassifiedEdge, MigrationGraphRowModel } from './migration-graph-rows';
|
|
24
|
+
import type { MigrationListStyler } from './migration-list-render';
|
|
25
|
+
|
|
26
|
+
const LABEL_GAP = 2;
|
|
27
|
+
const MIN_HASH_DATA_COLUMN = 25;
|
|
28
|
+
|
|
29
|
+
export interface RenderMigrationGraphCommandInput {
|
|
30
|
+
readonly grid: Grid;
|
|
31
|
+
readonly rowModel: MigrationGraphRowModel;
|
|
32
|
+
readonly colorize: boolean;
|
|
33
|
+
readonly glyphMode: GlyphMode;
|
|
34
|
+
readonly refsByHash?: ReadonlyMap<string, readonly string[]>;
|
|
35
|
+
readonly edgeAnnotationsByHash?: ReadonlyMap<string, MigrationEdgeAnnotation>;
|
|
36
|
+
readonly dbHash?: string;
|
|
37
|
+
readonly contractHash?: string;
|
|
38
|
+
readonly isAppSpace?: boolean;
|
|
39
|
+
readonly activeRefName?: string;
|
|
40
|
+
readonly styler?: MigrationListStyler;
|
|
41
|
+
/** Cross-space override for the gutter→label column (max gutter width). */
|
|
42
|
+
readonly globalLabelColumn?: number;
|
|
43
|
+
/** Cross-space override for the migration-name column width. */
|
|
44
|
+
readonly globalMaxDirNameWidth?: number;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
// Row classification — derive each grid row's identity from its own cells.
|
|
49
|
+
// ---------------------------------------------------------------------------
|
|
50
|
+
|
|
51
|
+
type RowIdentity =
|
|
52
|
+
| { readonly kind: 'node'; readonly contractHash: string }
|
|
53
|
+
| { readonly kind: 'migration'; readonly edge: ClassifiedEdge; readonly lane: number }
|
|
54
|
+
| { readonly kind: 'none' };
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Classify a grid row by its own cells:
|
|
58
|
+
* - a cell carrying a NodeRef → node row (contract label);
|
|
59
|
+
* - a cell whose top line is an arrow ({up}/{down}/self-loop) → migration row;
|
|
60
|
+
* - otherwise → no label.
|
|
61
|
+
*
|
|
62
|
+
* A migration's arrow appears in exactly one grid row (the forward `↑` row, the
|
|
63
|
+
* adjacent-rollback `↓` row, or the self-loop `⟲` row), so each migration gets
|
|
64
|
+
* exactly one label, on the row that draws its arrow.
|
|
65
|
+
*
|
|
66
|
+
* Two distinct migrations with identical content (same from/to/ops) hash to the
|
|
67
|
+
* SAME migration hash, so the arrow line is matched on BOTH its hash and its
|
|
68
|
+
* `dirName` (which the LineRef carries per-row) — otherwise both rows would
|
|
69
|
+
* resolve to one edge and the other migration's name would be lost.
|
|
70
|
+
*/
|
|
71
|
+
function classifyRow(
|
|
72
|
+
row: readonly Cell[],
|
|
73
|
+
edgesByHash: ReadonlyMap<string, readonly ClassifiedEdge[]>,
|
|
74
|
+
): RowIdentity {
|
|
75
|
+
for (const cell of row) {
|
|
76
|
+
if (cell.node !== undefined) {
|
|
77
|
+
return { kind: 'node', contractHash: cell.node.contractHash };
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
for (const cell of row) {
|
|
81
|
+
const arrow = arrowLine(cell);
|
|
82
|
+
if (arrow === undefined) continue;
|
|
83
|
+
const candidates = edgesByHash.get(arrow.line.migrationHash) ?? [];
|
|
84
|
+
const edge = candidates.find((e) => e.dirName === arrow.line.dirName) ?? candidates[0];
|
|
85
|
+
if (edge !== undefined) return { kind: 'migration', edge, lane: arrow.line.lane };
|
|
86
|
+
}
|
|
87
|
+
return { kind: 'none' };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Return the cell's arrow line if it carries one — a self-loop, or a line whose
|
|
92
|
+
* directions are exactly `{up}` or `{down}` (the migration-direction arrows).
|
|
93
|
+
* Connector/corner/vertical lines are not arrows and yield `undefined`.
|
|
94
|
+
*/
|
|
95
|
+
function arrowLine(cell: Cell): CellLine | undefined {
|
|
96
|
+
for (const line of cell.lines) {
|
|
97
|
+
if (line.selfLoop === true) return line;
|
|
98
|
+
if (line.landingArrow === true) continue;
|
|
99
|
+
const dirs = line.directions;
|
|
100
|
+
if (dirs.size !== 1) continue;
|
|
101
|
+
if (dirs.has('up') || dirs.has('down')) return line;
|
|
102
|
+
}
|
|
103
|
+
return undefined;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// ---------------------------------------------------------------------------
|
|
107
|
+
// Node path-highlight resolution (focus mode).
|
|
108
|
+
// ---------------------------------------------------------------------------
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Resolve each contract's path-highlight role from the edges incident on it.
|
|
112
|
+
* On-path wins: a contract touched by any on-path edge is on-path. Empty unless
|
|
113
|
+
* focus-mode annotations are present.
|
|
114
|
+
*/
|
|
115
|
+
function resolveNodeHighlights(
|
|
116
|
+
rowModel: MigrationGraphRowModel,
|
|
117
|
+
edgeAnnotationsByHash: ReadonlyMap<string, MigrationEdgeAnnotation> | undefined,
|
|
118
|
+
): Map<string, 'on-path' | 'off-path'> {
|
|
119
|
+
const result = new Map<string, 'on-path' | 'off-path'>();
|
|
120
|
+
if (edgeAnnotationsByHash === undefined) return result;
|
|
121
|
+
for (const edge of rowModel.edges) {
|
|
122
|
+
const highlight = edgeAnnotationsByHash.get(edge.migrationHash)?.pathHighlight;
|
|
123
|
+
if (highlight === undefined) continue;
|
|
124
|
+
for (const hash of [edge.from, edge.to]) {
|
|
125
|
+
if (hash === EMPTY_CONTRACT_HASH) continue;
|
|
126
|
+
if (result.get(hash) !== 'on-path') result.set(hash, highlight);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return result;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// ---------------------------------------------------------------------------
|
|
133
|
+
// Width helpers
|
|
134
|
+
// ---------------------------------------------------------------------------
|
|
135
|
+
|
|
136
|
+
function maxDirNameLength(edges: readonly ClassifiedEdge[]): number {
|
|
137
|
+
let max = 0;
|
|
138
|
+
for (const edge of edges) max = Math.max(max, edge.dirName.length);
|
|
139
|
+
return max;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* The label column for a render: the widest gutter (visible width) across every
|
|
144
|
+
* row, plus the label gap. Labels begin here so they line up regardless of how
|
|
145
|
+
* deep the lane structure runs on any one row. A cross-space override widens it
|
|
146
|
+
* so sibling space sections share one column.
|
|
147
|
+
*/
|
|
148
|
+
export function computeLabelColumn(grid: Grid, glyphMode: GlyphMode): number {
|
|
149
|
+
let maxGutter = 0;
|
|
150
|
+
for (const row of grid) {
|
|
151
|
+
const gutter = renderGridRow(row, { colorize: false, glyphMode });
|
|
152
|
+
maxGutter = Math.max(maxGutter, stringWidth(gutter));
|
|
153
|
+
}
|
|
154
|
+
return maxGutter + LABEL_GAP;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export function computeMaxDirNameWidth(rowModel: MigrationGraphRowModel): number {
|
|
158
|
+
return maxDirNameLength(rowModel.edges);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function padVisible(text: string, targetWidth: number): string {
|
|
162
|
+
const padding = Math.max(0, targetWidth - stringWidth(text));
|
|
163
|
+
return text + ' '.repeat(padding);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const ANSI_ESCAPE = '\x1b';
|
|
167
|
+
|
|
168
|
+
function trimTrailingWhitespace(line: string): string {
|
|
169
|
+
const trailingSpaceBeforeReset = new RegExp(`[\\t ]+((?:${ANSI_ESCAPE}\\[[0-9;]*m)+)$`);
|
|
170
|
+
return line.replace(trailingSpaceBeforeReset, '$1').replace(/\s+$/, '');
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// ---------------------------------------------------------------------------
|
|
174
|
+
// Render
|
|
175
|
+
// ---------------------------------------------------------------------------
|
|
176
|
+
|
|
177
|
+
export function renderMigrationGraphCommand(input: RenderMigrationGraphCommandInput): string {
|
|
178
|
+
const { grid, rowModel } = input;
|
|
179
|
+
const glyphMode = input.glyphMode;
|
|
180
|
+
|
|
181
|
+
// Edges grouped by hash — a list, not a single entry, because two distinct
|
|
182
|
+
// migrations with identical content collide on one hash. classifyRow then
|
|
183
|
+
// disambiguates by the row's own dirName.
|
|
184
|
+
const edgesByHash = new Map<string, ClassifiedEdge[]>();
|
|
185
|
+
for (const edge of rowModel.edges) {
|
|
186
|
+
const bucket = edgesByHash.get(edge.migrationHash);
|
|
187
|
+
if (bucket) bucket.push(edge);
|
|
188
|
+
else edgesByHash.set(edge.migrationHash, [edge]);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const labelOpts: MigrationGraphLabelOptions = {
|
|
192
|
+
colorize: input.colorize,
|
|
193
|
+
glyphMode,
|
|
194
|
+
...ifDefined('refsByHash', input.refsByHash),
|
|
195
|
+
...ifDefined('edgeAnnotationsByHash', input.edgeAnnotationsByHash),
|
|
196
|
+
...ifDefined('dbHash', input.dbHash),
|
|
197
|
+
...ifDefined('contractHash', input.contractHash),
|
|
198
|
+
...ifDefined('isAppSpace', input.isAppSpace),
|
|
199
|
+
...ifDefined('activeRefName', input.activeRefName),
|
|
200
|
+
...ifDefined('styler', input.styler),
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
const nodeHighlights = resolveNodeHighlights(rowModel, input.edgeAnnotationsByHash);
|
|
204
|
+
|
|
205
|
+
const labelColumn = input.globalLabelColumn ?? computeLabelColumn(grid, glyphMode);
|
|
206
|
+
const maxDirNameLen = input.globalMaxDirNameWidth ?? maxDirNameLength(rowModel.edges);
|
|
207
|
+
// The migration-name column is at least wide enough to push the `from → to`
|
|
208
|
+
// hash column to MIN_HASH_DATA_COLUMN, matching the historical layout.
|
|
209
|
+
const dirNameWidth = Math.max(maxDirNameLen + LABEL_GAP, MIN_HASH_DATA_COLUMN - labelColumn);
|
|
210
|
+
|
|
211
|
+
const lines: string[] = [];
|
|
212
|
+
for (const row of grid) {
|
|
213
|
+
const gutter = renderGridRow(row, { colorize: input.colorize, glyphMode });
|
|
214
|
+
const identity = classifyRow(row, edgesByHash);
|
|
215
|
+
|
|
216
|
+
if (identity.kind === 'none') {
|
|
217
|
+
// Connector / pass-through / back-arc rows carry no label. A wholly empty
|
|
218
|
+
// grid row (the blank line between disjoint components) renders as a blank.
|
|
219
|
+
lines.push(trimTrailingWhitespace(gutter));
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const gutterPad = padVisible(gutter, labelColumn);
|
|
224
|
+
if (identity.kind === 'node') {
|
|
225
|
+
const label = formatNodeLabel(
|
|
226
|
+
identity.contractHash,
|
|
227
|
+
labelOpts,
|
|
228
|
+
nodeHighlights.get(identity.contractHash),
|
|
229
|
+
);
|
|
230
|
+
lines.push(trimTrailingWhitespace(label.length === 0 ? gutter : `${gutterPad}${label}`));
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const label = formatMigrationLabel(identity.edge, dirNameWidth, labelOpts, identity.lane);
|
|
235
|
+
lines.push(trimTrailingWhitespace(`${gutterPad}${label}`));
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return lines.join('\n');
|
|
239
|
+
}
|