@prisma-next/cli 0.11.0-dev.24 → 0.11.0-dev.25
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-errors-Bw2GlweY.mjs → cli-errors-DFF1LlfU.mjs} +42 -2
- package/dist/cli-errors-DFF1LlfU.mjs.map +1 -0
- package/dist/cli.mjs +8 -8
- package/dist/{client-DePN5ZzP.mjs → client-a5NJce0-.mjs} +4 -4
- package/dist/{client-DePN5ZzP.mjs.map → client-a5NJce0-.mjs.map} +1 -1
- package/dist/{command-helpers-137k3Zbi.mjs → command-helpers-BnqwTptC.mjs} +2 -2
- package/dist/{command-helpers-137k3Zbi.mjs.map → command-helpers-BnqwTptC.mjs.map} +1 -1
- package/dist/commands/contract-emit.mjs +1 -1
- package/dist/commands/contract-infer.mjs +1 -1
- package/dist/commands/db-init.mjs +5 -5
- package/dist/commands/db-schema.mjs +3 -3
- package/dist/commands/db-sign.mjs +4 -4
- package/dist/commands/db-update.mjs +5 -5
- package/dist/commands/db-verify.mjs +1 -1
- package/dist/commands/migrate.d.mts +1 -1
- package/dist/commands/migrate.mjs +4 -4
- package/dist/commands/migration-check.mjs +1 -1
- package/dist/commands/migration-graph.d.mts +1 -1
- package/dist/commands/migration-graph.mjs +2 -2
- package/dist/commands/migration-list.d.mts +57 -13
- package/dist/commands/migration-list.d.mts.map +1 -1
- package/dist/commands/migration-list.mjs +2 -102
- package/dist/commands/migration-log.mjs +3 -3
- package/dist/commands/migration-new.mjs +3 -3
- package/dist/commands/migration-plan.d.mts +1 -1
- package/dist/commands/migration-plan.mjs +1 -1
- package/dist/commands/migration-show.d.mts +1 -1
- package/dist/commands/migration-show.mjs +5 -5
- package/dist/commands/migration-status.d.mts +1 -1
- package/dist/commands/migration-status.mjs +5 -5
- package/dist/commands/ref.d.mts +1 -1
- package/dist/commands/ref.mjs +2 -2
- package/dist/{contract-emit-D3lPKeRC.mjs → contract-emit-DYBHfZqL.mjs} +3 -3
- package/dist/{contract-emit-D3lPKeRC.mjs.map → contract-emit-DYBHfZqL.mjs.map} +1 -1
- package/dist/{contract-emit-BBLRbwOP.mjs → contract-emit-aFcOi3aw.mjs} +4 -4
- package/dist/{contract-emit-BBLRbwOP.mjs.map → contract-emit-aFcOi3aw.mjs.map} +1 -1
- package/dist/{contract-infer-BLAYDLqC.mjs → contract-infer-BpJeg-Eu.mjs} +3 -3
- package/dist/{contract-infer-BLAYDLqC.mjs.map → contract-infer-BpJeg-Eu.mjs.map} +1 -1
- package/dist/{contract-space-aggregate-loader-CjVyPKxG.mjs → contract-space-aggregate-loader-EVU3n9YE.mjs} +2 -2
- package/dist/{contract-space-aggregate-loader-CjVyPKxG.mjs.map → contract-space-aggregate-loader-EVU3n9YE.mjs.map} +1 -1
- package/dist/{db-verify-6sapsnWU.mjs → db-verify-CxtdGiL3.mjs} +5 -5
- package/dist/{db-verify-6sapsnWU.mjs.map → db-verify-CxtdGiL3.mjs.map} +1 -1
- package/dist/exports/control-api.d.mts +1 -1
- package/dist/exports/control-api.mjs +2 -2
- package/dist/exports/index.mjs +1 -1
- package/dist/exports/init-output.mjs +1 -1
- package/dist/{framework-components-Bexd0f4E.mjs → framework-components-DTcjouhS.mjs} +2 -2
- package/dist/{framework-components-Bexd0f4E.mjs.map → framework-components-DTcjouhS.mjs.map} +1 -1
- package/dist/{global-flags-CLwNZnki.d.mts → global-flags-CdE7M0d9.d.mts} +1 -1
- package/dist/{global-flags-CLwNZnki.d.mts.map → global-flags-CdE7M0d9.d.mts.map} +1 -1
- package/dist/{init-Ba-AZ0zh.mjs → init-eGkSo7hi.mjs} +5 -5
- package/dist/{init-Ba-AZ0zh.mjs.map → init-eGkSo7hi.mjs.map} +1 -1
- package/dist/{inspect-live-schema-B_xGH5G6.mjs → inspect-live-schema-B1GCyjAJ.mjs} +4 -4
- package/dist/{inspect-live-schema-B_xGH5G6.mjs.map → inspect-live-schema-B1GCyjAJ.mjs.map} +1 -1
- package/dist/{migration-command-scaffold-Efyi26SZ.mjs → migration-command-scaffold-CNdZl60X.mjs} +4 -4
- package/dist/{migration-command-scaffold-Efyi26SZ.mjs.map → migration-command-scaffold-CNdZl60X.mjs.map} +1 -1
- package/dist/migration-list-CnYiHrNV.mjs +288 -0
- package/dist/migration-list-CnYiHrNV.mjs.map +1 -0
- package/dist/{migration-plan-C1blDFs-.mjs → migration-plan-BnmP_yNz.mjs} +5 -5
- package/dist/{migration-plan-C1blDFs-.mjs.map → migration-plan-BnmP_yNz.mjs.map} +1 -1
- package/dist/{migration-types-DOvz-F0g.d.mts → migration-types-BXWvz12q.d.mts} +1 -1
- package/dist/{migration-types-DOvz-F0g.d.mts.map → migration-types-BXWvz12q.d.mts.map} +1 -1
- package/dist/{migrations-B8Fjf_zG.mjs → migrations-C7YTBnLy.mjs} +2 -2
- package/dist/{migrations-B8Fjf_zG.mjs.map → migrations-C7YTBnLy.mjs.map} +1 -1
- package/dist/{output-BlsrGMEF.mjs → output-CUIdfYo5.mjs} +1 -1
- package/dist/{output-BlsrGMEF.mjs.map → output-CUIdfYo5.mjs.map} +1 -1
- package/dist/{types-CKdsR3HE.d.mts → types-UWB2-rrw.d.mts} +1 -1
- package/dist/{types-CKdsR3HE.d.mts.map → types-UWB2-rrw.d.mts.map} +1 -1
- package/dist/{verify-8e6M7ZXH.mjs → verify-DX4RQwq4.mjs} +2 -2
- package/dist/{verify-8e6M7ZXH.mjs.map → verify-DX4RQwq4.mjs.map} +1 -1
- package/package.json +18 -18
- package/src/commands/migration-list.ts +145 -74
- package/src/utils/cli-errors.ts +51 -0
- package/src/utils/formatters/migration-list-render.ts +171 -0
- package/src/utils/formatters/migration-list-styler.ts +56 -0
- package/dist/cli-errors-Bw2GlweY.mjs.map +0 -1
- package/dist/commands/migration-list.mjs.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"verify-8e6M7ZXH.mjs","names":[],"sources":["../src/utils/formatters/verify.ts"],"sourcesContent":["import type {\n CoreSchemaView,\n IntrospectSchemaResult,\n SchemaTreeNode,\n SchemaVerificationNode,\n SignDatabaseResult,\n VerifyDatabaseResult,\n VerifyDatabaseSchemaResult,\n} from '@prisma-next/framework-components/control';\nimport { ifDefined } from '@prisma-next/utils/defined';\nimport { bold, cyan, dim, green, magenta, red, yellow } from 'colorette';\nimport type { GlobalFlags } from '../global-flags';\nimport { createColorFormatter, formatDim, isVerbose } from './helpers';\n\n// ============================================================================\n// Verify Output Formatters\n// ============================================================================\n\nexport interface DbVerifyCommandSuccessResult {\n readonly ok: true;\n readonly mode: 'full' | 'marker-only';\n readonly summary: string;\n readonly contract: VerifyDatabaseResult['contract'];\n readonly marker?: VerifyDatabaseResult['marker'];\n readonly target: VerifyDatabaseResult['target'];\n readonly missingCodecs?: VerifyDatabaseResult['missingCodecs'];\n readonly codecCoverageSkipped?: VerifyDatabaseResult['codecCoverageSkipped'];\n readonly schema?: {\n readonly summary: string;\n readonly counts: VerifyDatabaseSchemaResult['schema']['counts'];\n readonly strict: boolean;\n };\n readonly warning?: string;\n readonly meta?:\n | (NonNullable<VerifyDatabaseResult['meta']> & {\n readonly schemaVerification: 'performed' | 'skipped';\n })\n | {\n readonly schemaVerification: 'performed' | 'skipped';\n };\n readonly timings: {\n readonly total: number;\n };\n}\n\n/**\n * Formats human-readable output for database verify.\n */\nexport function formatVerifyOutput(\n result: DbVerifyCommandSuccessResult,\n flags: GlobalFlags,\n): string {\n if (flags.quiet) {\n return '';\n }\n\n const lines: string[] = [];\n\n const useColor = flags.color !== false;\n const formatGreen = createColorFormatter(useColor, green);\n const formatYellow = createColorFormatter(useColor, yellow);\n const formatDimText = (text: string) => formatDim(useColor, text);\n const verificationMode =\n result.mode === 'full'\n ? `marker + schema${result.schema?.strict ? ' (strict)' : ' (tolerant)'}`\n : 'marker only (--marker-only)';\n\n lines.push(`${formatGreen('✔')} ${result.summary}`);\n lines.push(`${formatDimText(` verification: ${verificationMode}`)}`);\n lines.push(`${formatDimText(` storageHash: ${result.contract.storageHash}`)}`);\n if (result.contract.profileHash) {\n lines.push(`${formatDimText(` profileHash: ${result.contract.profileHash}`)}`);\n }\n if (result.mode === 'full' && result.schema && isVerbose(flags, 1)) {\n lines.push(\n `${formatDimText(` schema: pass=${result.schema.counts.pass} warn=${result.schema.counts.warn} fail=${result.schema.counts.fail}`)}`,\n );\n }\n if (result.warning) {\n lines.push('');\n lines.push(`${formatYellow('⚠')} ${result.warning}`);\n }\n\n if (isVerbose(flags, 1)) {\n if (result.codecCoverageSkipped) {\n lines.push(\n `${formatDimText(' Codec coverage check skipped (helper returned no supported types)')}`,\n );\n }\n lines.push(`${formatDimText(` Total time: ${result.timings.total}ms`)}`);\n }\n\n return lines.join('\\n');\n}\n\n/**\n * Formats JSON output for database verify.\n */\nexport function formatVerifyJson(result: DbVerifyCommandSuccessResult): string {\n const output = {\n ok: result.ok,\n summary: result.summary,\n mode: result.mode,\n contract: result.contract,\n ...ifDefined('marker', result.marker),\n target: result.target,\n ...ifDefined('missingCodecs', result.missingCodecs),\n ...ifDefined('codecCoverageSkipped', result.codecCoverageSkipped),\n ...ifDefined('schema', result.schema),\n ...ifDefined('warning', result.warning),\n ...ifDefined('meta', result.meta),\n timings: result.timings,\n };\n\n return JSON.stringify(output, null, 2);\n}\n\n/**\n * Formats JSON output for database introspection.\n */\nexport function formatIntrospectJson(result: IntrospectSchemaResult<unknown>): string {\n return JSON.stringify(result, null, 2);\n}\n\n/**\n * Renders a schema tree structure from CoreSchemaView.\n * Matches the style of renderSchemaVerificationTree for consistency.\n */\nfunction renderSchemaTree(\n node: SchemaTreeNode,\n flags: GlobalFlags,\n options: {\n readonly isLast: boolean;\n readonly prefix: string;\n readonly useColor: boolean;\n readonly formatDimText: (text: string) => string;\n readonly isRoot?: boolean;\n },\n): string[] {\n const { isLast, prefix, useColor, formatDimText, isRoot = false } = options;\n const lines: string[] = [];\n\n // Format node label with color based on kind (matching schema-verify style)\n let formattedLabel: string = node.label;\n\n if (useColor) {\n switch (node.kind) {\n case 'root':\n formattedLabel = bold(node.label);\n break;\n case 'entity': {\n // Parse \"table tableName\" format - color \"table\" dim, tableName cyan\n const tableMatch = node.label.match(/^table\\s+(.+)$/);\n if (tableMatch?.[1]) {\n const tableName = tableMatch[1];\n formattedLabel = `${dim('table')} ${cyan(tableName)}`;\n } else {\n // Fallback: color entire label with cyan\n formattedLabel = cyan(node.label);\n }\n break;\n }\n case 'collection': {\n // \"columns\" grouping node - dim the label\n formattedLabel = dim(node.label);\n break;\n }\n case 'field': {\n // Parse column name format: \"columnName: typeDisplay (nullability)\"\n // Color code: column name (cyan), type (default), nullability (dim)\n const columnMatch = node.label.match(/^([^:]+):\\s*(.+)$/);\n if (columnMatch?.[1] && columnMatch[2]) {\n const columnName = columnMatch[1];\n const rest = columnMatch[2];\n // Parse rest: \"typeDisplay (nullability)\"\n const typeMatch = rest.match(/^([^\\s(]+)\\s*(\\([^)]+\\))$/);\n if (typeMatch?.[1] && typeMatch[2]) {\n const typeDisplay = typeMatch[1];\n const nullability = typeMatch[2];\n formattedLabel = `${cyan(columnName)}: ${typeDisplay} ${dim(nullability)}`;\n } else {\n // Fallback if format doesn't match\n formattedLabel = `${cyan(columnName)}: ${rest}`;\n }\n } else {\n formattedLabel = node.label;\n }\n break;\n }\n case 'index': {\n // Parse index/unique constraint/primary key formats\n // \"primary key: columnName\" -> dim \"primary key\", cyan columnName\n const pkMatch = node.label.match(/^primary key:\\s*(.+)$/);\n if (pkMatch?.[1]) {\n const columnNames = pkMatch[1];\n formattedLabel = `${dim('primary key')}: ${cyan(columnNames)}`;\n } else {\n // \"unique name\" -> dim \"unique\", cyan \"name\"\n const uniqueMatch = node.label.match(/^unique\\s+(.+)$/);\n if (uniqueMatch?.[1]) {\n const name = uniqueMatch[1];\n formattedLabel = `${dim('unique')} ${cyan(name)}`;\n } else {\n // \"index name\" or \"unique index name\" -> dim label prefix, cyan name\n const indexMatch = node.label.match(/^(unique\\s+)?index\\s+(.+)$/);\n if (indexMatch?.[2]) {\n const indexPrefix = indexMatch[1] ? `${dim('unique')} ` : '';\n const name = indexMatch[2];\n formattedLabel = `${indexPrefix}${dim('index')} ${cyan(name)}`;\n } else {\n formattedLabel = dim(node.label);\n }\n }\n }\n break;\n }\n case 'dependency': {\n // Parse extension message formats similar to schema-verify\n // \"extensionName extension is enabled\" -> cyan extensionName, dim rest\n const extMatch = node.label.match(/^([^\\s]+)\\s+(extension is enabled)$/);\n if (extMatch?.[1] && extMatch[2]) {\n const extName = extMatch[1];\n const rest = extMatch[2];\n formattedLabel = `${cyan(extName)} ${dim(rest)}`;\n } else {\n // Fallback: color entire label with magenta\n formattedLabel = magenta(node.label);\n }\n break;\n }\n default:\n formattedLabel = node.label;\n break;\n }\n }\n\n // Root node renders without tree characters or prefix\n if (isRoot) {\n lines.push(formattedLabel);\n } else {\n const treeChar = isLast ? '└' : '├';\n const treePrefix = `${formatDimText(treeChar)}─ `;\n lines.push(`${prefix}${treePrefix}${formattedLabel}`);\n }\n\n // Render children if present\n if (node.children && node.children.length > 0) {\n const childPrefix = isRoot ? '' : `${prefix}${isLast ? ' ' : `${formatDimText('│')} `}`;\n for (let i = 0; i < node.children.length; i++) {\n const child = node.children[i];\n if (!child) continue;\n const isLastChild = i === node.children.length - 1;\n const childLines = renderSchemaTree(child, flags, {\n isLast: isLastChild,\n prefix: childPrefix,\n useColor,\n formatDimText,\n isRoot: false,\n });\n lines.push(...childLines);\n }\n }\n\n return lines;\n}\n\n/**\n * Formats human-readable output for database introspection.\n */\nexport function formatIntrospectOutput(\n result: IntrospectSchemaResult<unknown>,\n schemaView: CoreSchemaView | undefined,\n flags: GlobalFlags,\n): string {\n if (flags.quiet) {\n return '';\n }\n\n const lines: string[] = [];\n\n const useColor = flags.color !== false;\n const formatDimText = (text: string) => formatDim(useColor, text);\n\n if (schemaView) {\n // Render tree structure - root node is special (no tree characters)\n const treeLines = renderSchemaTree(schemaView.root, flags, {\n isLast: true,\n prefix: '',\n useColor,\n formatDimText,\n isRoot: true,\n });\n lines.push(...treeLines);\n } else {\n // Fallback: print summary when toSchemaView is not available\n lines.push(`✔ ${result.summary}`);\n if (isVerbose(flags, 1)) {\n lines.push(` Target: ${result.target.familyId}/${result.target.id}`);\n if (result.meta?.dbUrl) {\n lines.push(` Database: ${result.meta.dbUrl}`);\n }\n }\n }\n\n // Add timings in verbose mode\n if (isVerbose(flags, 1)) {\n lines.push(`${formatDimText(` Total time: ${result.timings.total}ms`)}`);\n }\n\n return lines.join('\\n');\n}\n\n/**\n * Renders a schema verification tree structure from SchemaVerificationNode.\n * Similar to renderSchemaTree but for verification nodes with status-based colors and glyphs.\n */\nfunction renderSchemaVerificationTree(\n node: SchemaVerificationNode,\n flags: GlobalFlags,\n options: {\n readonly isLast: boolean;\n readonly prefix: string;\n readonly useColor: boolean;\n readonly formatDimText: (text: string) => string;\n readonly isRoot?: boolean;\n },\n): string[] {\n const { isLast, prefix, useColor, formatDimText, isRoot = false } = options;\n const lines: string[] = [];\n\n // Format status glyph and color based on status\n let statusGlyph = '';\n let statusColor: (text: string) => string = (text) => text;\n if (useColor) {\n switch (node.status) {\n case 'pass':\n statusGlyph = '✔';\n statusColor = green;\n break;\n case 'warn':\n statusGlyph = '⚠';\n statusColor = (text) => (useColor ? yellow(text) : text);\n break;\n case 'fail':\n statusGlyph = '✖';\n statusColor = red;\n break;\n }\n } else {\n switch (node.status) {\n case 'pass':\n statusGlyph = '✔';\n break;\n case 'warn':\n statusGlyph = '⚠';\n break;\n case 'fail':\n statusGlyph = '✖';\n break;\n }\n }\n\n // Format node label with color based on kind\n // For column nodes, we need to parse the name to color code different parts\n let labelColor: (text: string) => string = (text) => text;\n let formattedLabel: string = node.name;\n\n if (useColor) {\n switch (node.kind) {\n case 'contract':\n case 'schema':\n labelColor = bold;\n formattedLabel = labelColor(node.name);\n break;\n case 'table': {\n // Parse \"table tableName\" format - color \"table\" dim, tableName cyan\n const tableMatch = node.name.match(/^table\\s+(.+)$/);\n if (tableMatch?.[1]) {\n const tableName = tableMatch[1];\n formattedLabel = `${dim('table')} ${cyan(tableName)}`;\n } else {\n formattedLabel = dim(node.name);\n }\n break;\n }\n case 'columns':\n labelColor = dim;\n formattedLabel = labelColor(node.name);\n break;\n case 'column': {\n // Parse column name format: \"columnName: contractType -> nativeType (nullability)\"\n // Color code: column name (cyan), contract type (default), native type (dim), nullability (dim)\n const columnMatch = node.name.match(/^([^:]+):\\s*(.+)$/);\n if (columnMatch?.[1] && columnMatch[2]) {\n const columnName = columnMatch[1];\n const rest = columnMatch[2];\n // Parse rest: \"contractType -> nativeType (nullability)\"\n // Match contract type (can contain /, @, etc.), arrow, native type, then nullability in parentheses\n const typeMatch = rest.match(/^([^\\s→]+)\\s*→\\s*([^\\s(]+)\\s*(\\([^)]+\\))$/);\n if (typeMatch?.[1] && typeMatch[2] && typeMatch[3]) {\n const contractType = typeMatch[1];\n const nativeType = typeMatch[2];\n const nullability = typeMatch[3];\n formattedLabel = `${cyan(columnName)}: ${contractType} → ${dim(nativeType)} ${dim(nullability)}`;\n } else {\n // Fallback if format doesn't match (e.g., no native type or no nullability)\n formattedLabel = `${cyan(columnName)}: ${rest}`;\n }\n } else {\n formattedLabel = node.name;\n }\n break;\n }\n case 'type':\n case 'nullability':\n labelColor = (text) => text; // Default color\n formattedLabel = labelColor(node.name);\n break;\n case 'primaryKey': {\n // Parse \"primary key: columnName\" format - color \"primary key\" dim, columnName cyan\n const pkMatch = node.name.match(/^primary key:\\s*(.+)$/);\n if (pkMatch?.[1]) {\n const columnNames = pkMatch[1];\n formattedLabel = `${dim('primary key')}: ${cyan(columnNames)}`;\n } else {\n formattedLabel = dim(node.name);\n }\n break;\n }\n case 'foreignKey':\n case 'unique':\n case 'index':\n labelColor = dim;\n formattedLabel = labelColor(node.name);\n break;\n case 'dependency': {\n // Parse specific extension message formats\n // \"database is postgres\" -> dim \"database is\", cyan \"postgres\"\n const dbMatch = node.name.match(/^database is\\s+(.+)$/);\n if (dbMatch?.[1]) {\n const dbName = dbMatch[1];\n formattedLabel = `${dim('database is')} ${cyan(dbName)}`;\n } else {\n // \"vector extension is enabled\" -> dim everything except extension name\n // Match pattern: \"extensionName extension is enabled\"\n const extMatch = node.name.match(/^([^\\s]+)\\s+(extension is enabled)$/);\n if (extMatch?.[1] && extMatch[2]) {\n const extName = extMatch[1];\n const rest = extMatch[2];\n formattedLabel = `${cyan(extName)} ${dim(rest)}`;\n } else {\n // Fallback: color entire name with magenta\n labelColor = magenta;\n formattedLabel = labelColor(node.name);\n }\n }\n break;\n }\n default:\n formattedLabel = node.name;\n break;\n }\n } else {\n formattedLabel = node.name;\n }\n\n const statusGlyphColored = statusColor(statusGlyph);\n\n // Build the label with optional message for failure/warn nodes\n let nodeLabel = formattedLabel;\n if (\n (node.status === 'fail' || node.status === 'warn') &&\n node.message &&\n node.message.length > 0\n ) {\n // Always show message for failure/warn nodes - it provides crucial context\n // For parent nodes, the message summarizes child failures\n // For leaf nodes, the message explains the specific issue\n const messageText = formatDimText(`(${node.message})`);\n nodeLabel = `${formattedLabel} ${messageText}`;\n }\n\n // Root node renders without tree characters or | prefix\n // Root node renders without tree characters or prefix\n if (isRoot) {\n lines.push(`${statusGlyphColored} ${nodeLabel}`);\n } else {\n const treeChar = isLast ? '└' : '├';\n const treePrefix = `${formatDimText(treeChar)}─ `;\n lines.push(`${prefix}${treePrefix}${statusGlyphColored} ${nodeLabel}`);\n }\n\n // Render children if present\n if (node.children && node.children.length > 0) {\n const childPrefix = isRoot ? '' : `${prefix}${isLast ? ' ' : `${formatDimText('│')} `}`;\n for (let i = 0; i < node.children.length; i++) {\n const child = node.children[i];\n if (!child) continue;\n const isLastChild = i === node.children.length - 1;\n const childLines = renderSchemaVerificationTree(child, flags, {\n isLast: isLastChild,\n prefix: childPrefix,\n useColor,\n formatDimText,\n isRoot: false,\n });\n lines.push(...childLines);\n }\n }\n\n return lines;\n}\n\n/**\n * Formats human-readable output for database schema verification.\n */\nexport function formatSchemaVerifyOutput(\n result: VerifyDatabaseSchemaResult,\n flags: GlobalFlags,\n): string {\n if (flags.quiet) {\n return '';\n }\n\n const lines: string[] = [];\n\n const useColor = flags.color !== false;\n const formatGreen = createColorFormatter(useColor, green);\n const formatRed = createColorFormatter(useColor, red);\n const formatDimText = (text: string) => formatDim(useColor, text);\n\n // Render verification tree first\n const treeLines = renderSchemaVerificationTree(result.schema.root, flags, {\n isLast: true,\n prefix: '',\n useColor,\n formatDimText,\n isRoot: true,\n });\n lines.push(...treeLines);\n\n // Add counts and timings in verbose mode\n if (isVerbose(flags, 1)) {\n lines.push(`${formatDimText(` Total time: ${result.timings.total}ms`)}`);\n lines.push(\n `${formatDimText(` pass=${result.schema.counts.pass} warn=${result.schema.counts.warn} fail=${result.schema.counts.fail}`)}`,\n );\n }\n\n // Blank line before summary\n lines.push('');\n\n // Summary line at the end: summary with status glyph\n if (result.ok) {\n lines.push(`${formatGreen('✔')} ${result.summary}`);\n } else {\n const codeText = result.code ? ` (${result.code})` : '';\n lines.push(`${formatRed('✖')} ${result.summary}${codeText}`);\n }\n\n return lines.join('\\n');\n}\n\n/**\n * Formats JSON output for database schema verification.\n */\nexport function formatSchemaVerifyJson(result: VerifyDatabaseSchemaResult): string {\n return JSON.stringify(result, null, 2);\n}\n\n// ============================================================================\n// Sign Output Formatters\n// ============================================================================\n\n/**\n * Formats human-readable output for database sign.\n */\nexport function formatSignOutput(result: SignDatabaseResult, flags: GlobalFlags): string {\n if (flags.quiet) {\n return '';\n }\n\n const lines: string[] = [];\n\n const useColor = flags.color !== false;\n const formatGreen = createColorFormatter(useColor, green);\n const formatDimText = (text: string) => formatDim(useColor, text);\n\n if (result.ok) {\n // Main success message in white (not dimmed)\n lines.push(`${formatGreen('✔')} Database signed`);\n\n // Show from -> to hashes with clear labels\n const previousHash = result.marker.previous?.storageHash ?? 'none';\n const currentHash = result.contract.storageHash;\n\n lines.push(`${formatDimText(` from: ${previousHash}`)}`);\n lines.push(`${formatDimText(` to: ${currentHash}`)}`);\n\n if (isVerbose(flags, 1)) {\n if (result.contract.profileHash) {\n lines.push(`${formatDimText(` profileHash: ${result.contract.profileHash}`)}`);\n }\n if (result.marker.previous?.profileHash) {\n lines.push(\n `${formatDimText(` previous profileHash: ${result.marker.previous.profileHash}`)}`,\n );\n }\n lines.push(`${formatDimText(` Total time: ${result.timings.total}ms`)}`);\n }\n }\n\n return lines.join('\\n');\n}\n\n/**\n * Formats JSON output for database sign.\n */\nexport function formatSignJson(result: SignDatabaseResult): string {\n return JSON.stringify(result, null, 2);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"verify-DX4RQwq4.mjs","names":[],"sources":["../src/utils/formatters/verify.ts"],"sourcesContent":["import type {\n CoreSchemaView,\n IntrospectSchemaResult,\n SchemaTreeNode,\n SchemaVerificationNode,\n SignDatabaseResult,\n VerifyDatabaseResult,\n VerifyDatabaseSchemaResult,\n} from '@prisma-next/framework-components/control';\nimport { ifDefined } from '@prisma-next/utils/defined';\nimport { bold, cyan, dim, green, magenta, red, yellow } from 'colorette';\nimport type { GlobalFlags } from '../global-flags';\nimport { createColorFormatter, formatDim, isVerbose } from './helpers';\n\n// ============================================================================\n// Verify Output Formatters\n// ============================================================================\n\nexport interface DbVerifyCommandSuccessResult {\n readonly ok: true;\n readonly mode: 'full' | 'marker-only';\n readonly summary: string;\n readonly contract: VerifyDatabaseResult['contract'];\n readonly marker?: VerifyDatabaseResult['marker'];\n readonly target: VerifyDatabaseResult['target'];\n readonly missingCodecs?: VerifyDatabaseResult['missingCodecs'];\n readonly codecCoverageSkipped?: VerifyDatabaseResult['codecCoverageSkipped'];\n readonly schema?: {\n readonly summary: string;\n readonly counts: VerifyDatabaseSchemaResult['schema']['counts'];\n readonly strict: boolean;\n };\n readonly warning?: string;\n readonly meta?:\n | (NonNullable<VerifyDatabaseResult['meta']> & {\n readonly schemaVerification: 'performed' | 'skipped';\n })\n | {\n readonly schemaVerification: 'performed' | 'skipped';\n };\n readonly timings: {\n readonly total: number;\n };\n}\n\n/**\n * Formats human-readable output for database verify.\n */\nexport function formatVerifyOutput(\n result: DbVerifyCommandSuccessResult,\n flags: GlobalFlags,\n): string {\n if (flags.quiet) {\n return '';\n }\n\n const lines: string[] = [];\n\n const useColor = flags.color !== false;\n const formatGreen = createColorFormatter(useColor, green);\n const formatYellow = createColorFormatter(useColor, yellow);\n const formatDimText = (text: string) => formatDim(useColor, text);\n const verificationMode =\n result.mode === 'full'\n ? `marker + schema${result.schema?.strict ? ' (strict)' : ' (tolerant)'}`\n : 'marker only (--marker-only)';\n\n lines.push(`${formatGreen('✔')} ${result.summary}`);\n lines.push(`${formatDimText(` verification: ${verificationMode}`)}`);\n lines.push(`${formatDimText(` storageHash: ${result.contract.storageHash}`)}`);\n if (result.contract.profileHash) {\n lines.push(`${formatDimText(` profileHash: ${result.contract.profileHash}`)}`);\n }\n if (result.mode === 'full' && result.schema && isVerbose(flags, 1)) {\n lines.push(\n `${formatDimText(` schema: pass=${result.schema.counts.pass} warn=${result.schema.counts.warn} fail=${result.schema.counts.fail}`)}`,\n );\n }\n if (result.warning) {\n lines.push('');\n lines.push(`${formatYellow('⚠')} ${result.warning}`);\n }\n\n if (isVerbose(flags, 1)) {\n if (result.codecCoverageSkipped) {\n lines.push(\n `${formatDimText(' Codec coverage check skipped (helper returned no supported types)')}`,\n );\n }\n lines.push(`${formatDimText(` Total time: ${result.timings.total}ms`)}`);\n }\n\n return lines.join('\\n');\n}\n\n/**\n * Formats JSON output for database verify.\n */\nexport function formatVerifyJson(result: DbVerifyCommandSuccessResult): string {\n const output = {\n ok: result.ok,\n summary: result.summary,\n mode: result.mode,\n contract: result.contract,\n ...ifDefined('marker', result.marker),\n target: result.target,\n ...ifDefined('missingCodecs', result.missingCodecs),\n ...ifDefined('codecCoverageSkipped', result.codecCoverageSkipped),\n ...ifDefined('schema', result.schema),\n ...ifDefined('warning', result.warning),\n ...ifDefined('meta', result.meta),\n timings: result.timings,\n };\n\n return JSON.stringify(output, null, 2);\n}\n\n/**\n * Formats JSON output for database introspection.\n */\nexport function formatIntrospectJson(result: IntrospectSchemaResult<unknown>): string {\n return JSON.stringify(result, null, 2);\n}\n\n/**\n * Renders a schema tree structure from CoreSchemaView.\n * Matches the style of renderSchemaVerificationTree for consistency.\n */\nfunction renderSchemaTree(\n node: SchemaTreeNode,\n flags: GlobalFlags,\n options: {\n readonly isLast: boolean;\n readonly prefix: string;\n readonly useColor: boolean;\n readonly formatDimText: (text: string) => string;\n readonly isRoot?: boolean;\n },\n): string[] {\n const { isLast, prefix, useColor, formatDimText, isRoot = false } = options;\n const lines: string[] = [];\n\n // Format node label with color based on kind (matching schema-verify style)\n let formattedLabel: string = node.label;\n\n if (useColor) {\n switch (node.kind) {\n case 'root':\n formattedLabel = bold(node.label);\n break;\n case 'entity': {\n // Parse \"table tableName\" format - color \"table\" dim, tableName cyan\n const tableMatch = node.label.match(/^table\\s+(.+)$/);\n if (tableMatch?.[1]) {\n const tableName = tableMatch[1];\n formattedLabel = `${dim('table')} ${cyan(tableName)}`;\n } else {\n // Fallback: color entire label with cyan\n formattedLabel = cyan(node.label);\n }\n break;\n }\n case 'collection': {\n // \"columns\" grouping node - dim the label\n formattedLabel = dim(node.label);\n break;\n }\n case 'field': {\n // Parse column name format: \"columnName: typeDisplay (nullability)\"\n // Color code: column name (cyan), type (default), nullability (dim)\n const columnMatch = node.label.match(/^([^:]+):\\s*(.+)$/);\n if (columnMatch?.[1] && columnMatch[2]) {\n const columnName = columnMatch[1];\n const rest = columnMatch[2];\n // Parse rest: \"typeDisplay (nullability)\"\n const typeMatch = rest.match(/^([^\\s(]+)\\s*(\\([^)]+\\))$/);\n if (typeMatch?.[1] && typeMatch[2]) {\n const typeDisplay = typeMatch[1];\n const nullability = typeMatch[2];\n formattedLabel = `${cyan(columnName)}: ${typeDisplay} ${dim(nullability)}`;\n } else {\n // Fallback if format doesn't match\n formattedLabel = `${cyan(columnName)}: ${rest}`;\n }\n } else {\n formattedLabel = node.label;\n }\n break;\n }\n case 'index': {\n // Parse index/unique constraint/primary key formats\n // \"primary key: columnName\" -> dim \"primary key\", cyan columnName\n const pkMatch = node.label.match(/^primary key:\\s*(.+)$/);\n if (pkMatch?.[1]) {\n const columnNames = pkMatch[1];\n formattedLabel = `${dim('primary key')}: ${cyan(columnNames)}`;\n } else {\n // \"unique name\" -> dim \"unique\", cyan \"name\"\n const uniqueMatch = node.label.match(/^unique\\s+(.+)$/);\n if (uniqueMatch?.[1]) {\n const name = uniqueMatch[1];\n formattedLabel = `${dim('unique')} ${cyan(name)}`;\n } else {\n // \"index name\" or \"unique index name\" -> dim label prefix, cyan name\n const indexMatch = node.label.match(/^(unique\\s+)?index\\s+(.+)$/);\n if (indexMatch?.[2]) {\n const indexPrefix = indexMatch[1] ? `${dim('unique')} ` : '';\n const name = indexMatch[2];\n formattedLabel = `${indexPrefix}${dim('index')} ${cyan(name)}`;\n } else {\n formattedLabel = dim(node.label);\n }\n }\n }\n break;\n }\n case 'dependency': {\n // Parse extension message formats similar to schema-verify\n // \"extensionName extension is enabled\" -> cyan extensionName, dim rest\n const extMatch = node.label.match(/^([^\\s]+)\\s+(extension is enabled)$/);\n if (extMatch?.[1] && extMatch[2]) {\n const extName = extMatch[1];\n const rest = extMatch[2];\n formattedLabel = `${cyan(extName)} ${dim(rest)}`;\n } else {\n // Fallback: color entire label with magenta\n formattedLabel = magenta(node.label);\n }\n break;\n }\n default:\n formattedLabel = node.label;\n break;\n }\n }\n\n // Root node renders without tree characters or prefix\n if (isRoot) {\n lines.push(formattedLabel);\n } else {\n const treeChar = isLast ? '└' : '├';\n const treePrefix = `${formatDimText(treeChar)}─ `;\n lines.push(`${prefix}${treePrefix}${formattedLabel}`);\n }\n\n // Render children if present\n if (node.children && node.children.length > 0) {\n const childPrefix = isRoot ? '' : `${prefix}${isLast ? ' ' : `${formatDimText('│')} `}`;\n for (let i = 0; i < node.children.length; i++) {\n const child = node.children[i];\n if (!child) continue;\n const isLastChild = i === node.children.length - 1;\n const childLines = renderSchemaTree(child, flags, {\n isLast: isLastChild,\n prefix: childPrefix,\n useColor,\n formatDimText,\n isRoot: false,\n });\n lines.push(...childLines);\n }\n }\n\n return lines;\n}\n\n/**\n * Formats human-readable output for database introspection.\n */\nexport function formatIntrospectOutput(\n result: IntrospectSchemaResult<unknown>,\n schemaView: CoreSchemaView | undefined,\n flags: GlobalFlags,\n): string {\n if (flags.quiet) {\n return '';\n }\n\n const lines: string[] = [];\n\n const useColor = flags.color !== false;\n const formatDimText = (text: string) => formatDim(useColor, text);\n\n if (schemaView) {\n // Render tree structure - root node is special (no tree characters)\n const treeLines = renderSchemaTree(schemaView.root, flags, {\n isLast: true,\n prefix: '',\n useColor,\n formatDimText,\n isRoot: true,\n });\n lines.push(...treeLines);\n } else {\n // Fallback: print summary when toSchemaView is not available\n lines.push(`✔ ${result.summary}`);\n if (isVerbose(flags, 1)) {\n lines.push(` Target: ${result.target.familyId}/${result.target.id}`);\n if (result.meta?.dbUrl) {\n lines.push(` Database: ${result.meta.dbUrl}`);\n }\n }\n }\n\n // Add timings in verbose mode\n if (isVerbose(flags, 1)) {\n lines.push(`${formatDimText(` Total time: ${result.timings.total}ms`)}`);\n }\n\n return lines.join('\\n');\n}\n\n/**\n * Renders a schema verification tree structure from SchemaVerificationNode.\n * Similar to renderSchemaTree but for verification nodes with status-based colors and glyphs.\n */\nfunction renderSchemaVerificationTree(\n node: SchemaVerificationNode,\n flags: GlobalFlags,\n options: {\n readonly isLast: boolean;\n readonly prefix: string;\n readonly useColor: boolean;\n readonly formatDimText: (text: string) => string;\n readonly isRoot?: boolean;\n },\n): string[] {\n const { isLast, prefix, useColor, formatDimText, isRoot = false } = options;\n const lines: string[] = [];\n\n // Format status glyph and color based on status\n let statusGlyph = '';\n let statusColor: (text: string) => string = (text) => text;\n if (useColor) {\n switch (node.status) {\n case 'pass':\n statusGlyph = '✔';\n statusColor = green;\n break;\n case 'warn':\n statusGlyph = '⚠';\n statusColor = (text) => (useColor ? yellow(text) : text);\n break;\n case 'fail':\n statusGlyph = '✖';\n statusColor = red;\n break;\n }\n } else {\n switch (node.status) {\n case 'pass':\n statusGlyph = '✔';\n break;\n case 'warn':\n statusGlyph = '⚠';\n break;\n case 'fail':\n statusGlyph = '✖';\n break;\n }\n }\n\n // Format node label with color based on kind\n // For column nodes, we need to parse the name to color code different parts\n let labelColor: (text: string) => string = (text) => text;\n let formattedLabel: string = node.name;\n\n if (useColor) {\n switch (node.kind) {\n case 'contract':\n case 'schema':\n labelColor = bold;\n formattedLabel = labelColor(node.name);\n break;\n case 'table': {\n // Parse \"table tableName\" format - color \"table\" dim, tableName cyan\n const tableMatch = node.name.match(/^table\\s+(.+)$/);\n if (tableMatch?.[1]) {\n const tableName = tableMatch[1];\n formattedLabel = `${dim('table')} ${cyan(tableName)}`;\n } else {\n formattedLabel = dim(node.name);\n }\n break;\n }\n case 'columns':\n labelColor = dim;\n formattedLabel = labelColor(node.name);\n break;\n case 'column': {\n // Parse column name format: \"columnName: contractType -> nativeType (nullability)\"\n // Color code: column name (cyan), contract type (default), native type (dim), nullability (dim)\n const columnMatch = node.name.match(/^([^:]+):\\s*(.+)$/);\n if (columnMatch?.[1] && columnMatch[2]) {\n const columnName = columnMatch[1];\n const rest = columnMatch[2];\n // Parse rest: \"contractType -> nativeType (nullability)\"\n // Match contract type (can contain /, @, etc.), arrow, native type, then nullability in parentheses\n const typeMatch = rest.match(/^([^\\s→]+)\\s*→\\s*([^\\s(]+)\\s*(\\([^)]+\\))$/);\n if (typeMatch?.[1] && typeMatch[2] && typeMatch[3]) {\n const contractType = typeMatch[1];\n const nativeType = typeMatch[2];\n const nullability = typeMatch[3];\n formattedLabel = `${cyan(columnName)}: ${contractType} → ${dim(nativeType)} ${dim(nullability)}`;\n } else {\n // Fallback if format doesn't match (e.g., no native type or no nullability)\n formattedLabel = `${cyan(columnName)}: ${rest}`;\n }\n } else {\n formattedLabel = node.name;\n }\n break;\n }\n case 'type':\n case 'nullability':\n labelColor = (text) => text; // Default color\n formattedLabel = labelColor(node.name);\n break;\n case 'primaryKey': {\n // Parse \"primary key: columnName\" format - color \"primary key\" dim, columnName cyan\n const pkMatch = node.name.match(/^primary key:\\s*(.+)$/);\n if (pkMatch?.[1]) {\n const columnNames = pkMatch[1];\n formattedLabel = `${dim('primary key')}: ${cyan(columnNames)}`;\n } else {\n formattedLabel = dim(node.name);\n }\n break;\n }\n case 'foreignKey':\n case 'unique':\n case 'index':\n labelColor = dim;\n formattedLabel = labelColor(node.name);\n break;\n case 'dependency': {\n // Parse specific extension message formats\n // \"database is postgres\" -> dim \"database is\", cyan \"postgres\"\n const dbMatch = node.name.match(/^database is\\s+(.+)$/);\n if (dbMatch?.[1]) {\n const dbName = dbMatch[1];\n formattedLabel = `${dim('database is')} ${cyan(dbName)}`;\n } else {\n // \"vector extension is enabled\" -> dim everything except extension name\n // Match pattern: \"extensionName extension is enabled\"\n const extMatch = node.name.match(/^([^\\s]+)\\s+(extension is enabled)$/);\n if (extMatch?.[1] && extMatch[2]) {\n const extName = extMatch[1];\n const rest = extMatch[2];\n formattedLabel = `${cyan(extName)} ${dim(rest)}`;\n } else {\n // Fallback: color entire name with magenta\n labelColor = magenta;\n formattedLabel = labelColor(node.name);\n }\n }\n break;\n }\n default:\n formattedLabel = node.name;\n break;\n }\n } else {\n formattedLabel = node.name;\n }\n\n const statusGlyphColored = statusColor(statusGlyph);\n\n // Build the label with optional message for failure/warn nodes\n let nodeLabel = formattedLabel;\n if (\n (node.status === 'fail' || node.status === 'warn') &&\n node.message &&\n node.message.length > 0\n ) {\n // Always show message for failure/warn nodes - it provides crucial context\n // For parent nodes, the message summarizes child failures\n // For leaf nodes, the message explains the specific issue\n const messageText = formatDimText(`(${node.message})`);\n nodeLabel = `${formattedLabel} ${messageText}`;\n }\n\n // Root node renders without tree characters or | prefix\n // Root node renders without tree characters or prefix\n if (isRoot) {\n lines.push(`${statusGlyphColored} ${nodeLabel}`);\n } else {\n const treeChar = isLast ? '└' : '├';\n const treePrefix = `${formatDimText(treeChar)}─ `;\n lines.push(`${prefix}${treePrefix}${statusGlyphColored} ${nodeLabel}`);\n }\n\n // Render children if present\n if (node.children && node.children.length > 0) {\n const childPrefix = isRoot ? '' : `${prefix}${isLast ? ' ' : `${formatDimText('│')} `}`;\n for (let i = 0; i < node.children.length; i++) {\n const child = node.children[i];\n if (!child) continue;\n const isLastChild = i === node.children.length - 1;\n const childLines = renderSchemaVerificationTree(child, flags, {\n isLast: isLastChild,\n prefix: childPrefix,\n useColor,\n formatDimText,\n isRoot: false,\n });\n lines.push(...childLines);\n }\n }\n\n return lines;\n}\n\n/**\n * Formats human-readable output for database schema verification.\n */\nexport function formatSchemaVerifyOutput(\n result: VerifyDatabaseSchemaResult,\n flags: GlobalFlags,\n): string {\n if (flags.quiet) {\n return '';\n }\n\n const lines: string[] = [];\n\n const useColor = flags.color !== false;\n const formatGreen = createColorFormatter(useColor, green);\n const formatRed = createColorFormatter(useColor, red);\n const formatDimText = (text: string) => formatDim(useColor, text);\n\n // Render verification tree first\n const treeLines = renderSchemaVerificationTree(result.schema.root, flags, {\n isLast: true,\n prefix: '',\n useColor,\n formatDimText,\n isRoot: true,\n });\n lines.push(...treeLines);\n\n // Add counts and timings in verbose mode\n if (isVerbose(flags, 1)) {\n lines.push(`${formatDimText(` Total time: ${result.timings.total}ms`)}`);\n lines.push(\n `${formatDimText(` pass=${result.schema.counts.pass} warn=${result.schema.counts.warn} fail=${result.schema.counts.fail}`)}`,\n );\n }\n\n // Blank line before summary\n lines.push('');\n\n // Summary line at the end: summary with status glyph\n if (result.ok) {\n lines.push(`${formatGreen('✔')} ${result.summary}`);\n } else {\n const codeText = result.code ? ` (${result.code})` : '';\n lines.push(`${formatRed('✖')} ${result.summary}${codeText}`);\n }\n\n return lines.join('\\n');\n}\n\n/**\n * Formats JSON output for database schema verification.\n */\nexport function formatSchemaVerifyJson(result: VerifyDatabaseSchemaResult): string {\n return JSON.stringify(result, null, 2);\n}\n\n// ============================================================================\n// Sign Output Formatters\n// ============================================================================\n\n/**\n * Formats human-readable output for database sign.\n */\nexport function formatSignOutput(result: SignDatabaseResult, flags: GlobalFlags): string {\n if (flags.quiet) {\n return '';\n }\n\n const lines: string[] = [];\n\n const useColor = flags.color !== false;\n const formatGreen = createColorFormatter(useColor, green);\n const formatDimText = (text: string) => formatDim(useColor, text);\n\n if (result.ok) {\n // Main success message in white (not dimmed)\n lines.push(`${formatGreen('✔')} Database signed`);\n\n // Show from -> to hashes with clear labels\n const previousHash = result.marker.previous?.storageHash ?? 'none';\n const currentHash = result.contract.storageHash;\n\n lines.push(`${formatDimText(` from: ${previousHash}`)}`);\n lines.push(`${formatDimText(` to: ${currentHash}`)}`);\n\n if (isVerbose(flags, 1)) {\n if (result.contract.profileHash) {\n lines.push(`${formatDimText(` profileHash: ${result.contract.profileHash}`)}`);\n }\n if (result.marker.previous?.profileHash) {\n lines.push(\n `${formatDimText(` previous profileHash: ${result.marker.previous.profileHash}`)}`,\n );\n }\n lines.push(`${formatDimText(` Total time: ${result.timings.total}ms`)}`);\n }\n }\n\n return lines.join('\\n');\n}\n\n/**\n * Formats JSON output for database sign.\n */\nexport function formatSignJson(result: SignDatabaseResult): string {\n return JSON.stringify(result, null, 2);\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.11.0-dev.
|
|
3
|
+
"version": "0.11.0-dev.25",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -14,15 +14,15 @@
|
|
|
14
14
|
"dependencies": {
|
|
15
15
|
"@clack/prompts": "^1.4.0",
|
|
16
16
|
"@dagrejs/dagre": "^3.0.0",
|
|
17
|
-
"@prisma-next/config": "0.11.0-dev.
|
|
18
|
-
"@prisma-next/contract": "0.11.0-dev.
|
|
19
|
-
"@prisma-next/emitter": "0.11.0-dev.
|
|
20
|
-
"@prisma-next/errors": "0.11.0-dev.
|
|
21
|
-
"@prisma-next/framework-components": "0.11.0-dev.
|
|
22
|
-
"@prisma-next/migration-tools": "0.11.0-dev.
|
|
23
|
-
"@prisma-next/psl-printer": "0.11.0-dev.
|
|
24
|
-
"@prisma-next/cli-telemetry": "0.11.0-dev.
|
|
25
|
-
"@prisma-next/utils": "0.11.0-dev.
|
|
17
|
+
"@prisma-next/config": "0.11.0-dev.25",
|
|
18
|
+
"@prisma-next/contract": "0.11.0-dev.25",
|
|
19
|
+
"@prisma-next/emitter": "0.11.0-dev.25",
|
|
20
|
+
"@prisma-next/errors": "0.11.0-dev.25",
|
|
21
|
+
"@prisma-next/framework-components": "0.11.0-dev.25",
|
|
22
|
+
"@prisma-next/migration-tools": "0.11.0-dev.25",
|
|
23
|
+
"@prisma-next/psl-printer": "0.11.0-dev.25",
|
|
24
|
+
"@prisma-next/cli-telemetry": "0.11.0-dev.25",
|
|
25
|
+
"@prisma-next/utils": "0.11.0-dev.25",
|
|
26
26
|
"arktype": "^2.2.0",
|
|
27
27
|
"c12": "^3.3.4",
|
|
28
28
|
"ci-info": "^4.3.1",
|
|
@@ -39,14 +39,14 @@
|
|
|
39
39
|
"wrap-ansi": "^10.0.0"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
|
-
"@prisma-next/sql-contract": "0.11.0-dev.
|
|
43
|
-
"@prisma-next/sql-contract-emitter": "0.11.0-dev.
|
|
44
|
-
"@prisma-next/sql-contract-ts": "0.11.0-dev.
|
|
45
|
-
"@prisma-next/sql-operations": "0.11.0-dev.
|
|
46
|
-
"@prisma-next/sql-runtime": "0.11.0-dev.
|
|
47
|
-
"@prisma-next/test-utils": "0.11.0-dev.
|
|
48
|
-
"@prisma-next/tsconfig": "0.11.0-dev.
|
|
49
|
-
"@prisma-next/tsdown": "0.11.0-dev.
|
|
42
|
+
"@prisma-next/sql-contract": "0.11.0-dev.25",
|
|
43
|
+
"@prisma-next/sql-contract-emitter": "0.11.0-dev.25",
|
|
44
|
+
"@prisma-next/sql-contract-ts": "0.11.0-dev.25",
|
|
45
|
+
"@prisma-next/sql-operations": "0.11.0-dev.25",
|
|
46
|
+
"@prisma-next/sql-runtime": "0.11.0-dev.25",
|
|
47
|
+
"@prisma-next/test-utils": "0.11.0-dev.25",
|
|
48
|
+
"@prisma-next/tsconfig": "0.11.0-dev.25",
|
|
49
|
+
"@prisma-next/tsdown": "0.11.0-dev.25",
|
|
50
50
|
"@types/node": "25.6.0",
|
|
51
51
|
"tsdown": "0.22.0",
|
|
52
52
|
"typescript": "5.9.3",
|
|
@@ -1,23 +1,30 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { EMPTY_CONTRACT_HASH } from '@prisma-next/migration-tools/constants';
|
|
1
|
+
import { enumerateMigrationSpaces } from '@prisma-next/migration-tools/enumerate-migration-spaces';
|
|
3
2
|
import { MigrationToolsError } from '@prisma-next/migration-tools/errors';
|
|
4
|
-
import {
|
|
3
|
+
import type {
|
|
4
|
+
MigrationListResult,
|
|
5
|
+
MigrationSpaceListEntry,
|
|
6
|
+
} from '@prisma-next/migration-tools/migration-list-types';
|
|
7
|
+
import { APP_SPACE_ID, isValidSpaceId } from '@prisma-next/migration-tools/spaces';
|
|
8
|
+
import { ifDefined } from '@prisma-next/utils/defined';
|
|
5
9
|
import { notOk, ok, type Result } from '@prisma-next/utils/result';
|
|
6
10
|
import { Command } from 'commander';
|
|
7
11
|
import { loadConfig } from '../config-loader';
|
|
8
12
|
import {
|
|
9
13
|
type CliStructuredError,
|
|
14
|
+
errorInvalidSpaceId,
|
|
15
|
+
errorSpaceNotFound,
|
|
10
16
|
errorUnexpected,
|
|
11
17
|
mapMigrationToolsError,
|
|
12
18
|
} from '../utils/cli-errors';
|
|
13
19
|
import {
|
|
14
20
|
addGlobalOptions,
|
|
15
|
-
loadMigrationPackages,
|
|
16
21
|
resolveMigrationPaths,
|
|
17
22
|
setCommandDescriptions,
|
|
18
23
|
setCommandExamples,
|
|
19
24
|
setCommandSeeAlso,
|
|
20
25
|
} from '../utils/command-helpers';
|
|
26
|
+
import { renderMigrationListWithStyle } from '../utils/formatters/migration-list-render';
|
|
27
|
+
import { createAnsiMigrationListStyler } from '../utils/formatters/migration-list-styler';
|
|
21
28
|
import { formatStyledHeader } from '../utils/formatters/styled';
|
|
22
29
|
import type { CommonCommandOptions } from '../utils/global-flags';
|
|
23
30
|
import { type GlobalFlags, parseGlobalFlagsOrExit } from '../utils/global-flags';
|
|
@@ -26,30 +33,131 @@ import { createTerminalUI, type TerminalUI } from '../utils/terminal-ui';
|
|
|
26
33
|
|
|
27
34
|
interface MigrationListOptions extends CommonCommandOptions {
|
|
28
35
|
readonly config?: string;
|
|
36
|
+
readonly space?: string;
|
|
29
37
|
}
|
|
30
38
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
39
|
+
/**
|
|
40
|
+
* Inputs for {@link runMigrationList} — the pure-ish data-and-policy core
|
|
41
|
+
* of `migration list` that tests exercise directly.
|
|
42
|
+
*
|
|
43
|
+
* The core depends only on the filesystem rooted at `migrationsDir`. It
|
|
44
|
+
* does NOT call `loadConfig`, parse CLI flags, render a styled header,
|
|
45
|
+
* or write to any stream. Output rendering is the caller's concern (the
|
|
46
|
+
* CLI shell renders via {@link renderMigrationList}; JSON callers
|
|
47
|
+
* serialize the {@link MigrationListResult} directly).
|
|
48
|
+
*/
|
|
49
|
+
export interface RunMigrationListInputs {
|
|
50
|
+
/** Absolute path to the project's `migrations/` directory. */
|
|
51
|
+
readonly migrationsDir: string;
|
|
52
|
+
/**
|
|
53
|
+
* Optional contract-space id to narrow the result to a single space.
|
|
54
|
+
* Same validation rules as {@link isValidSpaceId}. When absent, every
|
|
55
|
+
* on-disk space contributes.
|
|
56
|
+
*/
|
|
57
|
+
readonly spaceFilter?: string;
|
|
38
58
|
}
|
|
39
59
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
60
|
+
/**
|
|
61
|
+
* Compute the trailing one-line summary appended below the migration
|
|
62
|
+
* rows. Wording follows the existing CLI's pluralization style ("N
|
|
63
|
+
* migration(s) on disk" for the common single-space path; multi-space
|
|
64
|
+
* adds "across K contract space(s)" so consumers can see the spread).
|
|
65
|
+
*
|
|
66
|
+
* The renderer suppresses the summary line when `totalMigrations === 0`
|
|
67
|
+
* — the empty-state line carries enough information on its own — so
|
|
68
|
+
* this function always returns a string even for the empty-state.
|
|
69
|
+
*/
|
|
70
|
+
function computeSummary(spaces: readonly MigrationSpaceListEntry[]): string {
|
|
71
|
+
const totalMigrations = spaces.reduce((count, space) => count + space.migrations.length, 0);
|
|
72
|
+
if (spaces.length <= 1) {
|
|
73
|
+
return `${totalMigrations} migration(s) on disk`;
|
|
74
|
+
}
|
|
75
|
+
return `${totalMigrations} migration(s) across ${spaces.length} contract space(s)`;
|
|
44
76
|
}
|
|
45
77
|
|
|
78
|
+
/**
|
|
79
|
+
* The unit-testable core of `migration list`. Given an absolute
|
|
80
|
+
* `migrationsDir` and an optional `spaceFilter`, enumerates every
|
|
81
|
+
* on-disk migration (via {@link enumerateMigrationSpaces}), narrows to
|
|
82
|
+
* the requested space if any, and assembles a {@link MigrationListResult}
|
|
83
|
+
* ready for the renderer or JSON serializer.
|
|
84
|
+
*
|
|
85
|
+
* The enumerator is the single source of truth for "what is a contract
|
|
86
|
+
* space": existence, the `--space` candidate-suggestion list, and
|
|
87
|
+
* scoping are all derived from one {@link enumerateMigrationSpaces}
|
|
88
|
+
* traversal. This means the reserved-name exclusion the enumerator
|
|
89
|
+
* applies (e.g. the per-space `refs/` subdirectory) is honoured here for
|
|
90
|
+
* free — a `--space refs` request resolves to `SPACE_NOT_FOUND`, not a
|
|
91
|
+
* synthesized empty-state.
|
|
92
|
+
*
|
|
93
|
+
* Distinct empty-state paths:
|
|
94
|
+
*
|
|
95
|
+
* - `migrations/` missing or contains no valid space directories →
|
|
96
|
+
* synthesizes `[{ spaceId: APP_SPACE_ID, migrations: [] }]` so the
|
|
97
|
+
* renderer's empty-state path can name a directory (spec § Empty-state +
|
|
98
|
+
* the `migrations/` missing edge case).
|
|
99
|
+
* - `--space <id>` on an existing-but-empty space dir → the enumerator
|
|
100
|
+
* surfaces `{ spaceId, migrations: [] }`; `<id>` is in the set, so it
|
|
101
|
+
* scopes to that entry and renders the empty-state the same way.
|
|
102
|
+
* - `--space <id>` on a non-existent (or reserved) space → structured
|
|
103
|
+
* `MIGRATION.SPACE_NOT_FOUND` error (NOT empty-state).
|
|
104
|
+
*
|
|
105
|
+
* Errors caught here:
|
|
106
|
+
*
|
|
107
|
+
* - {@link MigrationToolsError} from the enumerator → mapped through
|
|
108
|
+
* {@link mapMigrationToolsError} so callers see the catalogue code.
|
|
109
|
+
* - Anything else (filesystem etc.) → wrapped via {@link errorUnexpected}.
|
|
110
|
+
*/
|
|
111
|
+
export async function runMigrationList(
|
|
112
|
+
inputs: RunMigrationListInputs,
|
|
113
|
+
): Promise<Result<MigrationListResult, CliStructuredError>> {
|
|
114
|
+
const { migrationsDir, spaceFilter } = inputs;
|
|
115
|
+
|
|
116
|
+
if (spaceFilter !== undefined && !isValidSpaceId(spaceFilter)) {
|
|
117
|
+
return notOk(errorInvalidSpaceId(spaceFilter));
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
let spaces: readonly MigrationSpaceListEntry[];
|
|
121
|
+
try {
|
|
122
|
+
spaces = await enumerateMigrationSpaces({ projectMigrationsDir: migrationsDir });
|
|
123
|
+
} catch (error) {
|
|
124
|
+
if (MigrationToolsError.is(error)) return notOk(mapMigrationToolsError(error));
|
|
125
|
+
return notOk(
|
|
126
|
+
errorUnexpected(error instanceof Error ? error.message : String(error), {
|
|
127
|
+
why: `Failed to enumerate migrations: ${error instanceof Error ? error.message : String(error)}`,
|
|
128
|
+
}),
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (spaceFilter !== undefined && !spaces.some((s) => s.spaceId === spaceFilter)) {
|
|
133
|
+
return notOk(errorSpaceNotFound(spaceFilter, spaces.map((s) => s.spaceId).sort()));
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const scopedSpaces =
|
|
137
|
+
spaceFilter !== undefined ? spaces.filter((s) => s.spaceId === spaceFilter) : spaces;
|
|
138
|
+
|
|
139
|
+
const resultSpaces: readonly MigrationSpaceListEntry[] =
|
|
140
|
+
scopedSpaces.length === 0 ? [{ spaceId: APP_SPACE_ID, migrations: [] }] : scopedSpaces;
|
|
141
|
+
|
|
142
|
+
return ok({
|
|
143
|
+
ok: true,
|
|
144
|
+
spaces: resultSpaces,
|
|
145
|
+
summary: computeSummary(resultSpaces),
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* CLI shell: loads config, resolves paths, prints the styled header on
|
|
151
|
+
* stderr (interactive mode only), and delegates to {@link runMigrationList}.
|
|
152
|
+
* Kept intentionally thin so the unit-testable surface lives in the core.
|
|
153
|
+
*/
|
|
46
154
|
async function executeMigrationListCommand(
|
|
47
155
|
options: MigrationListOptions,
|
|
48
156
|
flags: GlobalFlags,
|
|
49
157
|
ui: TerminalUI,
|
|
50
158
|
): Promise<Result<MigrationListResult, CliStructuredError>> {
|
|
51
159
|
const config = await loadConfig(options.config);
|
|
52
|
-
const { configPath,
|
|
160
|
+
const { configPath, migrationsDir, migrationsRelative } = resolveMigrationPaths(
|
|
53
161
|
options.config,
|
|
54
162
|
config,
|
|
55
163
|
);
|
|
@@ -57,58 +165,20 @@ async function executeMigrationListCommand(
|
|
|
57
165
|
if (!flags.json && !flags.quiet) {
|
|
58
166
|
const header = formatStyledHeader({
|
|
59
167
|
command: 'migration list',
|
|
60
|
-
description: 'List on-disk migrations
|
|
168
|
+
description: 'List on-disk migrations, latest first, per contract space',
|
|
61
169
|
details: [
|
|
62
170
|
{ label: 'config', value: configPath },
|
|
63
|
-
{ label: 'migrations', value:
|
|
171
|
+
{ label: 'migrations', value: migrationsRelative },
|
|
172
|
+
...(options.space !== undefined ? [{ label: 'space', value: options.space }] : []),
|
|
64
173
|
],
|
|
65
174
|
flags,
|
|
66
175
|
});
|
|
67
176
|
ui.stderr(header);
|
|
68
177
|
}
|
|
69
178
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
({ bundles, graph } = await loadMigrationPackages(appMigrationsDir));
|
|
74
|
-
} catch (error) {
|
|
75
|
-
if (MigrationToolsError.is(error)) return notOk(mapMigrationToolsError(error));
|
|
76
|
-
return notOk(
|
|
77
|
-
errorUnexpected(error instanceof Error ? error.message : String(error), {
|
|
78
|
-
why: `Failed to read migrations: ${error instanceof Error ? error.message : String(error)}`,
|
|
79
|
-
}),
|
|
80
|
-
);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
if (bundles.length === 0) {
|
|
84
|
-
return ok({ ok: true, migrations: [], summary: 'No migrations found' });
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
const leaves = [...graph.nodes].filter(
|
|
88
|
-
(n) => !graph.forwardChain.has(n) || graph.forwardChain.get(n)!.length === 0,
|
|
89
|
-
);
|
|
90
|
-
const targetHash =
|
|
91
|
-
leaves.length === 1 ? leaves[0]! : ([...graph.nodes].values().next().value as string);
|
|
92
|
-
const chain = findPath(graph, EMPTY_CONTRACT_HASH, targetHash) ?? [];
|
|
93
|
-
|
|
94
|
-
const pkgByDirName = new Map(bundles.map((p) => [p.dirName, p]));
|
|
95
|
-
const entries: MigrationListEntry[] = chain.map((edge) => {
|
|
96
|
-
const pkg = pkgByDirName.get(edge.dirName);
|
|
97
|
-
const ops = (pkg?.ops ?? []) as readonly MigrationPlanOperation[];
|
|
98
|
-
return {
|
|
99
|
-
dirName: edge.dirName,
|
|
100
|
-
from: edge.from,
|
|
101
|
-
to: edge.to,
|
|
102
|
-
migrationHash: edge.migrationHash,
|
|
103
|
-
operationCount: ops.length,
|
|
104
|
-
createdAt: edge.createdAt,
|
|
105
|
-
};
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
return ok({
|
|
109
|
-
ok: true,
|
|
110
|
-
migrations: entries,
|
|
111
|
-
summary: `${entries.length} migration(s) on disk`,
|
|
179
|
+
return runMigrationList({
|
|
180
|
+
migrationsDir,
|
|
181
|
+
...ifDefined('spaceFilter', options.space),
|
|
112
182
|
});
|
|
113
183
|
}
|
|
114
184
|
|
|
@@ -116,11 +186,19 @@ export function createMigrationListCommand(): Command {
|
|
|
116
186
|
const command = new Command('list');
|
|
117
187
|
setCommandDescriptions(
|
|
118
188
|
command,
|
|
119
|
-
'List on-disk migrations
|
|
120
|
-
'Enumerates
|
|
121
|
-
'
|
|
189
|
+
'List on-disk migrations, latest first, per contract space',
|
|
190
|
+
'Enumerates every on-disk migration under migrations/<space>/ for every\n' +
|
|
191
|
+
'contract space found on disk, latest first. Offline — does not consult\n' +
|
|
192
|
+
'the database. Each row shows source → destination contract hashes\n' +
|
|
193
|
+
'(7-char git-style), the self-edge marker (⟲), any provided invariants\n' +
|
|
194
|
+
'({...}), and refs landing on the destination ((production, db)). Pass\n' +
|
|
195
|
+
'--space <id> to narrow to a single contract space.',
|
|
122
196
|
);
|
|
123
|
-
setCommandExamples(command, [
|
|
197
|
+
setCommandExamples(command, [
|
|
198
|
+
'prisma-next migration list',
|
|
199
|
+
'prisma-next migration list --space app',
|
|
200
|
+
'prisma-next migration list --json',
|
|
201
|
+
]);
|
|
124
202
|
setCommandSeeAlso(command, [
|
|
125
203
|
{ verb: 'migration status', oneLiner: 'Show migration path and pending status' },
|
|
126
204
|
{ verb: 'migration log', oneLiner: 'Show executed migration history' },
|
|
@@ -129,6 +207,7 @@ export function createMigrationListCommand(): Command {
|
|
|
129
207
|
]);
|
|
130
208
|
addGlobalOptions(command)
|
|
131
209
|
.option('--config <path>', 'Path to prisma-next.config.ts')
|
|
210
|
+
.option('--space <id>', 'Narrow output to a single contract space')
|
|
132
211
|
.action(async (options: MigrationListOptions) => {
|
|
133
212
|
const flags = parseGlobalFlagsOrExit(options);
|
|
134
213
|
const ui = createTerminalUI(flags);
|
|
@@ -137,16 +216,8 @@ export function createMigrationListCommand(): Command {
|
|
|
137
216
|
if (flags.json) {
|
|
138
217
|
ui.output(JSON.stringify(listResult, null, 2));
|
|
139
218
|
} else if (!flags.quiet) {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
} else {
|
|
143
|
-
for (const entry of listResult.migrations) {
|
|
144
|
-
ui.log(
|
|
145
|
-
`${entry.dirName} ${entry.migrationHash.slice(0, 16)}… ${entry.operationCount} op(s)`,
|
|
146
|
-
);
|
|
147
|
-
}
|
|
148
|
-
ui.log(`\n${listResult.summary}`);
|
|
149
|
-
}
|
|
219
|
+
const styler = createAnsiMigrationListStyler({ useColor: ui.useColor });
|
|
220
|
+
ui.output(renderMigrationListWithStyle(listResult, styler));
|
|
150
221
|
}
|
|
151
222
|
});
|
|
152
223
|
process.exit(exitCode);
|
package/src/utils/cli-errors.ts
CHANGED
|
@@ -108,6 +108,57 @@ export function errorRefSetEmptySentinel(hash: string): CliStructuredError {
|
|
|
108
108
|
});
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
+
/**
|
|
112
|
+
* `--space <id>` was given a value that doesn't satisfy the contract-space
|
|
113
|
+
* naming rule (`[a-z][a-z0-9_-]{0,63}` per `isValidSpaceId`). Fires before
|
|
114
|
+
* any fs work — the input is syntactically rejected the same way an on-disk
|
|
115
|
+
* directory with that name would be skipped by the enumerator.
|
|
116
|
+
*/
|
|
117
|
+
export function errorInvalidSpaceId(spaceId: string): CliStructuredError {
|
|
118
|
+
return errorRuntime(`Invalid contract space id: ${spaceId}`, {
|
|
119
|
+
why: 'Contract space ids must match [a-z][a-z0-9_-]{0,63} (lowercase, starts with a letter, max 64 characters — the rule applied to every on-disk space directory).',
|
|
120
|
+
fix: 'Pass a space id that matches the directory naming rule, or omit --space to list every space.',
|
|
121
|
+
meta: {
|
|
122
|
+
code: 'MIGRATION.INVALID_SPACE_ID',
|
|
123
|
+
spaceId,
|
|
124
|
+
},
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* `migration list --space <id>` was given a contract-space id that has no
|
|
130
|
+
* directory under `migrations/`. Distinct from "the space exists but is
|
|
131
|
+
* empty" — that path renders the empty-state line and exits 0 per the
|
|
132
|
+
* slice spec § Empty-state. This error fires only when `<projectMigrationsDir>/<spaceId>`
|
|
133
|
+
* does not exist on disk.
|
|
134
|
+
*
|
|
135
|
+
* `availableSpaces` lists the contract-space directory names actually
|
|
136
|
+
* present, sorted lex-asc, so the diagnostic can suggest a near match
|
|
137
|
+
* without making the user reach for `ls`.
|
|
138
|
+
*/
|
|
139
|
+
export function errorSpaceNotFound(
|
|
140
|
+
spaceId: string,
|
|
141
|
+
availableSpaces: readonly string[],
|
|
142
|
+
): CliStructuredError {
|
|
143
|
+
const availableList =
|
|
144
|
+
availableSpaces.length > 0
|
|
145
|
+
? availableSpaces.join(', ')
|
|
146
|
+
: '(none — no contract spaces on disk yet)';
|
|
147
|
+
const fix =
|
|
148
|
+
availableSpaces.length > 0
|
|
149
|
+
? `Pick one of: ${availableList}. Run \`prisma-next migration list\` (no --space) to see every space's migrations.`
|
|
150
|
+
: 'Author a migration with `prisma-next migration new` to create the first contract-space directory.';
|
|
151
|
+
return errorRuntime(`Unknown contract space: ${spaceId}`, {
|
|
152
|
+
why: `No directory named "${spaceId}" exists under the migrations root.`,
|
|
153
|
+
fix,
|
|
154
|
+
meta: {
|
|
155
|
+
code: 'MIGRATION.SPACE_NOT_FOUND',
|
|
156
|
+
spaceId,
|
|
157
|
+
availableSpaces: [...availableSpaces],
|
|
158
|
+
},
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
111
162
|
export function errorRefSetBundleNotFound(hash: string): CliStructuredError {
|
|
112
163
|
return errorRuntime(`No migration bundle matches graph-node hash ${hash}`, {
|
|
113
164
|
why: `The hash is a graph node but no on-disk bundle has metadata.to = ${hash}.`,
|