@prisma-next/cli 0.10.0-dev.2 → 0.10.0-dev.21
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 +150 -10
- package/dist/cli.mjs.map +1 -1
- package/dist/{command-helpers-D3vL5yi8.mjs → command-helpers-Dvgul7UA.mjs} +3 -3
- package/dist/command-helpers-Dvgul7UA.mjs.map +1 -0
- package/dist/commands/contract-emit.mjs +1 -1
- package/dist/commands/contract-infer.mjs +1 -1
- package/dist/commands/db-init.mjs +4 -4
- package/dist/commands/db-schema.mjs +4 -4
- package/dist/commands/db-sign.mjs +3 -3
- package/dist/commands/db-update.mjs +4 -4
- package/dist/commands/db-verify.mjs +1 -1
- package/dist/commands/migrate.mjs +3 -3
- package/dist/commands/migration-check.mjs +1 -1
- package/dist/commands/migration-graph.mjs +2 -2
- package/dist/commands/migration-list.mjs +2 -2
- package/dist/commands/migration-log.mjs +2 -2
- package/dist/commands/migration-new.mjs +2 -2
- package/dist/commands/migration-plan.mjs +1 -1
- package/dist/commands/migration-show.mjs +3 -3
- package/dist/commands/migration-status.mjs +3 -3
- package/dist/commands/ref.mjs +2 -2
- package/dist/{contract-emit-C3STUIBg.mjs → contract-emit-BDBzHlaC.mjs} +4 -4
- package/dist/{contract-emit-C3STUIBg.mjs.map → contract-emit-BDBzHlaC.mjs.map} +1 -1
- package/dist/{contract-infer-Cnj8G1E2.mjs → contract-infer-Dm8pBZMR.mjs} +4 -4
- package/dist/{contract-infer-Cnj8G1E2.mjs.map → contract-infer-Dm8pBZMR.mjs.map} +1 -1
- package/dist/{db-verify-D7cyH_zz.mjs → db-verify-CW8DR5Ei.mjs} +4 -4
- package/dist/{db-verify-D7cyH_zz.mjs.map → db-verify-CW8DR5Ei.mjs.map} +1 -1
- package/dist/{errors-Cw6kyTyV.mjs → errors-BYAXmyRJ.mjs} +2 -2
- package/dist/{errors-Cw6kyTyV.mjs.map → errors-BYAXmyRJ.mjs.map} +1 -1
- package/dist/exports/index.mjs +2 -2
- package/dist/global-flags-DGmw6Kqg.d.mts.map +1 -1
- package/dist/{init-DBRWZlFU.mjs → init-n7JaaCgD.mjs} +447 -367
- package/dist/init-n7JaaCgD.mjs.map +1 -0
- package/dist/{inspect-live-schema-CWLK_lgs.mjs → inspect-live-schema-iETRZ_59.mjs} +2 -2
- package/dist/{inspect-live-schema-CWLK_lgs.mjs.map → inspect-live-schema-iETRZ_59.mjs.map} +1 -1
- package/dist/is-ci-YyvQBBke.mjs +44 -0
- package/dist/is-ci-YyvQBBke.mjs.map +1 -0
- package/dist/{migration-command-scaffold-CmXXC1UZ.mjs → migration-command-scaffold-BlgVj_Pn.mjs} +2 -2
- package/dist/{migration-command-scaffold-CmXXC1UZ.mjs.map → migration-command-scaffold-BlgVj_Pn.mjs.map} +1 -1
- package/dist/{migration-plan-CHyUlBV0.mjs → migration-plan-BSzcWsvm.mjs} +3 -3
- package/dist/{migration-plan-CHyUlBV0.mjs.map → migration-plan-BSzcWsvm.mjs.map} +1 -1
- package/dist/{migrations-DyUf5lTt.mjs → migrations-CgANWI0w.mjs} +2 -2
- package/dist/{migrations-DyUf5lTt.mjs.map → migrations-CgANWI0w.mjs.map} +1 -1
- package/dist/output-B60Gw5fu.mjs.map +1 -1
- package/dist/{result-handler-Bm_6dDYg.mjs → result-handler-CG3vVoKf.mjs} +2 -2
- package/dist/{result-handler-Bm_6dDYg.mjs.map → result-handler-CG3vVoKf.mjs.map} +1 -1
- package/dist/{verify-D7ypCCe6.mjs → verify-nlzO0uIY.mjs} +2 -2
- package/dist/{verify-D7ypCCe6.mjs.map → verify-nlzO0uIY.mjs.map} +1 -1
- package/package.json +19 -17
- package/src/cli.ts +42 -0
- package/src/commands/init/errors.ts +33 -2
- package/src/commands/init/index.ts +11 -3
- package/src/commands/init/init.ts +37 -27
- package/src/commands/init/inputs.ts +82 -5
- package/src/commands/init/output.ts +1 -1
- package/src/commands/init/{agent-skill-install.ts → skill-install.ts} +6 -6
- package/src/commands/init/templates/env.ts +8 -1
- package/src/utils/global-flags.ts +6 -2
- package/src/utils/is-ci.ts +18 -0
- package/src/utils/telemetry.ts +141 -0
- package/dist/command-helpers-D3vL5yi8.mjs.map +0 -1
- package/dist/helpers-eqdN8tH6.mjs +0 -25
- package/dist/helpers-eqdN8tH6.mjs.map +0 -1
- package/dist/init-DBRWZlFU.mjs.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"verify-D7ypCCe6.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-nlzO0uIY.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.10.0-dev.
|
|
3
|
+
"version": "0.10.0-dev.21",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -14,16 +14,18 @@
|
|
|
14
14
|
"dependencies": {
|
|
15
15
|
"@clack/prompts": "^1.3.0",
|
|
16
16
|
"@dagrejs/dagre": "^3.0.0",
|
|
17
|
-
"@prisma-next/config": "0.10.0-dev.
|
|
18
|
-
"@prisma-next/contract": "0.10.0-dev.
|
|
19
|
-
"@prisma-next/emitter": "0.10.0-dev.
|
|
20
|
-
"@prisma-next/errors": "0.10.0-dev.
|
|
21
|
-
"@prisma-next/framework-components": "0.10.0-dev.
|
|
22
|
-
"@prisma-next/migration-tools": "0.10.0-dev.
|
|
23
|
-
"@prisma-next/psl-printer": "0.10.0-dev.
|
|
24
|
-
"@prisma-next/
|
|
17
|
+
"@prisma-next/config": "0.10.0-dev.21",
|
|
18
|
+
"@prisma-next/contract": "0.10.0-dev.21",
|
|
19
|
+
"@prisma-next/emitter": "0.10.0-dev.21",
|
|
20
|
+
"@prisma-next/errors": "0.10.0-dev.21",
|
|
21
|
+
"@prisma-next/framework-components": "0.10.0-dev.21",
|
|
22
|
+
"@prisma-next/migration-tools": "0.10.0-dev.21",
|
|
23
|
+
"@prisma-next/psl-printer": "0.10.0-dev.21",
|
|
24
|
+
"@prisma-next/cli-telemetry": "0.10.0-dev.21",
|
|
25
|
+
"@prisma-next/utils": "0.10.0-dev.21",
|
|
25
26
|
"arktype": "^2.2.0",
|
|
26
27
|
"c12": "^3.3.4",
|
|
28
|
+
"ci-info": "^4.3.1",
|
|
27
29
|
"clipanion": "4.0.0-rc.4",
|
|
28
30
|
"closest-match": "^1.3.3",
|
|
29
31
|
"colorette": "^2.0.20",
|
|
@@ -37,14 +39,14 @@
|
|
|
37
39
|
"wrap-ansi": "^10.0.0"
|
|
38
40
|
},
|
|
39
41
|
"devDependencies": {
|
|
40
|
-
"@prisma-next/sql-contract": "0.10.0-dev.
|
|
41
|
-
"@prisma-next/sql-contract-emitter": "0.10.0-dev.
|
|
42
|
-
"@prisma-next/sql-contract-ts": "0.10.0-dev.
|
|
43
|
-
"@prisma-next/sql-operations": "0.10.0-dev.
|
|
44
|
-
"@prisma-next/sql-runtime": "0.10.0-dev.
|
|
45
|
-
"@prisma-next/test-utils": "0.10.0-dev.
|
|
46
|
-
"@prisma-next/tsconfig": "0.10.0-dev.
|
|
47
|
-
"@prisma-next/tsdown": "0.10.0-dev.
|
|
42
|
+
"@prisma-next/sql-contract": "0.10.0-dev.21",
|
|
43
|
+
"@prisma-next/sql-contract-emitter": "0.10.0-dev.21",
|
|
44
|
+
"@prisma-next/sql-contract-ts": "0.10.0-dev.21",
|
|
45
|
+
"@prisma-next/sql-operations": "0.10.0-dev.21",
|
|
46
|
+
"@prisma-next/sql-runtime": "0.10.0-dev.21",
|
|
47
|
+
"@prisma-next/test-utils": "0.10.0-dev.21",
|
|
48
|
+
"@prisma-next/tsconfig": "0.10.0-dev.21",
|
|
49
|
+
"@prisma-next/tsdown": "0.10.0-dev.21",
|
|
48
50
|
"@types/node": "24.10.4",
|
|
49
51
|
"tsdown": "0.22.0",
|
|
50
52
|
"typescript": "5.9.3",
|
package/src/cli.ts
CHANGED
|
@@ -27,6 +27,7 @@ import { setCommandDescriptions } from './utils/command-helpers';
|
|
|
27
27
|
import { formatCommandHelp, formatRootHelp } from './utils/formatters/help';
|
|
28
28
|
import { parseGlobalFlags } from './utils/global-flags';
|
|
29
29
|
import { suggestCommands } from './utils/suggest-command';
|
|
30
|
+
import { fireTelemetryFromPreAction } from './utils/telemetry';
|
|
30
31
|
|
|
31
32
|
/**
|
|
32
33
|
* Lookup table mapping removed subcommands to their replacement verbs.
|
|
@@ -68,6 +69,21 @@ const program = new Command();
|
|
|
68
69
|
|
|
69
70
|
program.name('prisma-next').description('Prisma Next CLI').version(packageJson.version);
|
|
70
71
|
|
|
72
|
+
// Telemetry hook — fires at command start, before the action body
|
|
73
|
+
// runs. Synchronous by construction: `fireTelemetryFromPreAction`
|
|
74
|
+
// resolves gates (cheap), then `fork()`s the detached sender. The
|
|
75
|
+
// fork is enqueued before the action body runs at all, so the child
|
|
76
|
+
// survives even when the action throws synchronously. The try/catch
|
|
77
|
+
// is defence-in-depth — `runTelemetry` already swallows every failure
|
|
78
|
+
// mode internally and returns an outcome instead of throwing.
|
|
79
|
+
program.hook('preAction', (_thisCommand, actionCommand) => {
|
|
80
|
+
try {
|
|
81
|
+
fireTelemetryFromPreAction(actionCommand);
|
|
82
|
+
} catch {
|
|
83
|
+
// defence-in-depth — runTelemetry already swallows internally.
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
|
|
71
87
|
// Override version option description to match capitalization style
|
|
72
88
|
const versionOption = program.options.find((opt) => opt.flags.includes('--version'));
|
|
73
89
|
if (versionOption) {
|
|
@@ -302,6 +318,32 @@ program.addCommand(dbCommand);
|
|
|
302
318
|
program.addCommand(migrationCommand);
|
|
303
319
|
program.addCommand(refCommand);
|
|
304
320
|
|
|
321
|
+
// Test-only hidden command used by `cli-telemetry`'s `cli-e2e.test.ts`
|
|
322
|
+
// to verify that telemetry still lands when a CLI command crashes
|
|
323
|
+
// mid-execution. The preAction hook is synchronous and `fork()`s the
|
|
324
|
+
// detached sender before this action body runs; the small sleep
|
|
325
|
+
// gives the IPC `child.send()` a tick to flush before the throw
|
|
326
|
+
// triggers commander's `exitOverride` and `process.exit(1)`. Hidden
|
|
327
|
+
// from help; underscore prefix marks it as internal. Doesn't depend
|
|
328
|
+
// on any project state, so it runs in any tempdir.
|
|
329
|
+
//
|
|
330
|
+
// Gated behind `PRISMA_NEXT_ENABLE_TEST_COMMANDS=1` so the command is
|
|
331
|
+
// not even registered (and therefore not invocable) in shipped
|
|
332
|
+
// binaries. `hidden: true` only filters the help output; without this
|
|
333
|
+
// env gate the command would still be callable from production. The
|
|
334
|
+
// e2e suite sets the env var when it spawns the CLI.
|
|
335
|
+
const TELEMETRY_CRASH_TEST_SLEEP_MS = 200;
|
|
336
|
+
if (process.env['PRISMA_NEXT_ENABLE_TEST_COMMANDS'] === '1') {
|
|
337
|
+
const telemetryCrashTestCommand = new Command('__telemetry-crash-test')
|
|
338
|
+
.description('Internal: deliberately throw for the telemetry e2e suite.')
|
|
339
|
+
.action(async () => {
|
|
340
|
+
await new Promise((settle) => setTimeout(settle, TELEMETRY_CRASH_TEST_SLEEP_MS));
|
|
341
|
+
throw new Error('__telemetry-crash-test: intentional crash for e2e coverage');
|
|
342
|
+
});
|
|
343
|
+
telemetryCrashTestCommand.configureHelp({ visibleCommands: () => [] });
|
|
344
|
+
program.addCommand(telemetryCrashTestCommand, { hidden: true });
|
|
345
|
+
}
|
|
346
|
+
|
|
305
347
|
// Create help command
|
|
306
348
|
const helpCommand = new Command('help')
|
|
307
349
|
.description('Show usage instructions')
|
|
@@ -69,6 +69,37 @@ export function errorInitInvalidFlagValue(options: {
|
|
|
69
69
|
});
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
+
/**
|
|
73
|
+
* `--authoring` and `--schema-path` disagree on file extension (e.g. PSL
|
|
74
|
+
* authoring with a `.ts` path). Surfaces before any scaffold files are
|
|
75
|
+
* written so the project tree stays untouched.
|
|
76
|
+
*/
|
|
77
|
+
export function errorInitAuthoringSchemaPathMismatch(options: {
|
|
78
|
+
readonly authoring: 'psl' | 'typescript';
|
|
79
|
+
readonly schemaPath: string;
|
|
80
|
+
readonly actualExtension: string;
|
|
81
|
+
readonly expectedExtension: string;
|
|
82
|
+
}): CliStructuredError {
|
|
83
|
+
const expectedAuthoring = options.expectedExtension === '.ts' ? 'typescript' : 'psl';
|
|
84
|
+
return new CliStructuredError('5014', 'Authoring and schema path do not match', {
|
|
85
|
+
domain: 'CLI',
|
|
86
|
+
why:
|
|
87
|
+
`\`--authoring ${options.authoring}\` requires a schema file ending in ${options.expectedExtension}, ` +
|
|
88
|
+
`but \`--schema-path ${options.schemaPath}\` ends in ${options.actualExtension}.`,
|
|
89
|
+
fix:
|
|
90
|
+
`Use a matching pair, for example \`--authoring ${expectedAuthoring} --schema-path <path>${options.expectedExtension}\`, ` +
|
|
91
|
+
'or change `--authoring` to match the path you supplied. ' +
|
|
92
|
+
'You can also omit `--schema-path` to use the default for the chosen authoring.',
|
|
93
|
+
docsUrl: 'https://prisma-next.dev/docs/cli/init',
|
|
94
|
+
meta: {
|
|
95
|
+
authoring: options.authoring,
|
|
96
|
+
schemaPath: options.schemaPath,
|
|
97
|
+
actualExtension: options.actualExtension,
|
|
98
|
+
expectedExtension: options.expectedExtension,
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
72
103
|
/**
|
|
73
104
|
* The user cancelled an interactive prompt (Ctrl-C, escape, declined a
|
|
74
105
|
* selection). Distinct from `errorInitReinitNeedsForce` because that path
|
|
@@ -238,7 +269,7 @@ export function errorInitEmitFailed(options: {
|
|
|
238
269
|
}
|
|
239
270
|
|
|
240
271
|
/**
|
|
241
|
-
* The project-level
|
|
272
|
+
* The project-level skills install (`npx skills add
|
|
242
273
|
* prisma/prisma-next#v<version>`) failed after a successful dependency
|
|
243
274
|
* install + emit. The project's scaffold remains on disk; the user
|
|
244
275
|
* can either fix the underlying issue (network, registry, PATH) and
|
|
@@ -260,7 +291,7 @@ export function errorInitSkillInstallFailed(options: {
|
|
|
260
291
|
'Either:\n' +
|
|
261
292
|
` - Re-run \`prisma-next init --no-skill${options.filesWritten.length > 0 ? ' --force' : ''}\` to skip the skill install for this run, or\n` +
|
|
262
293
|
` - Fix the underlying issue (network, npm registry, \`npx skills\` on PATH) and install manually:\n ${options.skillInstallCommand}`,
|
|
263
|
-
docsUrl: 'https://prisma-next.dev/docs/cli/init#
|
|
294
|
+
docsUrl: 'https://prisma-next.dev/docs/cli/init#skills',
|
|
264
295
|
meta: {
|
|
265
296
|
filesWritten: options.filesWritten,
|
|
266
297
|
skillInstallCommand: options.skillInstallCommand,
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
setCommandExamples,
|
|
6
6
|
} from '../../utils/command-helpers';
|
|
7
7
|
import { type CommonCommandOptions, parseGlobalFlags } from '../../utils/global-flags';
|
|
8
|
+
import { fireTelemetryAfterInitConsent } from '../../utils/telemetry';
|
|
8
9
|
import {
|
|
9
10
|
INIT_EXIT_EMIT_FAILED,
|
|
10
11
|
INIT_EXIT_INSTALL_FAILED,
|
|
@@ -63,7 +64,7 @@ export function createInitCommand(): Command {
|
|
|
63
64
|
'prisma-next init --yes --target mongodb --authoring typescript --json',
|
|
64
65
|
'prisma-next init --yes --force --target postgres --authoring psl # overwrite an existing scaffold',
|
|
65
66
|
'prisma-next init --no-install # skip pnpm/npm install + emit',
|
|
66
|
-
'prisma-next init --no-skill # skip the
|
|
67
|
+
'prisma-next init --no-skill # skip the skills install (air-gapped / restricted env)',
|
|
67
68
|
]);
|
|
68
69
|
|
|
69
70
|
return addGlobalOptions(command)
|
|
@@ -91,7 +92,7 @@ export function createInitCommand(): Command {
|
|
|
91
92
|
'--no-skill',
|
|
92
93
|
'Skip Prisma Next skills install (air-gapped CI, restricted registries, etc.)',
|
|
93
94
|
)
|
|
94
|
-
.action(async (options: InitCommandOptions) => {
|
|
95
|
+
.action(async (options: InitCommandOptions, actionCommand: Command) => {
|
|
95
96
|
const { runInit } = await import('./init');
|
|
96
97
|
const flags = parseGlobalFlags(options);
|
|
97
98
|
const canPrompt = deriveCanPrompt({
|
|
@@ -99,7 +100,14 @@ export function createInitCommand(): Command {
|
|
|
99
100
|
optionInteractive: options.interactive,
|
|
100
101
|
stdinIsTTY: Boolean(process.stdin.isTTY),
|
|
101
102
|
});
|
|
102
|
-
const exitCode = await runInit(process.cwd(), {
|
|
103
|
+
const exitCode = await runInit(process.cwd(), {
|
|
104
|
+
options,
|
|
105
|
+
flags,
|
|
106
|
+
canPrompt,
|
|
107
|
+
afterFirstTelemetryConsent: (inputs) => {
|
|
108
|
+
fireTelemetryAfterInitConsent(actionCommand, { databaseTarget: inputs.target });
|
|
109
|
+
},
|
|
110
|
+
});
|
|
103
111
|
process.exit(exitCode);
|
|
104
112
|
});
|
|
105
113
|
}
|
|
@@ -7,13 +7,6 @@ import { CliStructuredError } from '../../utils/cli-errors';
|
|
|
7
7
|
import { formatErrorJson, formatErrorOutput } from '../../utils/formatters/errors';
|
|
8
8
|
import type { GlobalFlags } from '../../utils/global-flags';
|
|
9
9
|
import { TerminalUI } from '../../utils/terminal-ui';
|
|
10
|
-
import {
|
|
11
|
-
DEFAULT_AGENT_SKILL_SOURCES,
|
|
12
|
-
formatClaudeSkillInstallCommand,
|
|
13
|
-
formatSkillInstallCommand,
|
|
14
|
-
LEGACY_SKILL_FILE,
|
|
15
|
-
runProjectLevelSkillInstall,
|
|
16
|
-
} from './agent-skill-install';
|
|
17
10
|
import {
|
|
18
11
|
detectPackageManager,
|
|
19
12
|
formatAddArgs,
|
|
@@ -56,6 +49,13 @@ import {
|
|
|
56
49
|
} from './output';
|
|
57
50
|
import { type ProbeOutcome, type ProbeOverrides, probeServerVersion } from './probe-db';
|
|
58
51
|
import { findStaleArtefacts, removeDependency } from './reinit-cleanup';
|
|
52
|
+
import {
|
|
53
|
+
DEFAULT_SKILL_SOURCES,
|
|
54
|
+
formatClaudeSkillInstallCommand,
|
|
55
|
+
formatSkillInstallCommand,
|
|
56
|
+
LEGACY_SKILL_FILE,
|
|
57
|
+
runProjectLevelSkillInstall,
|
|
58
|
+
} from './skill-install';
|
|
59
59
|
import { configFile, dbFile, starterSchema, targetPackageName } from './templates/code-templates';
|
|
60
60
|
import { envExampleContent, envFileContent, MIN_SERVER_VERSION } from './templates/env';
|
|
61
61
|
import { quickReferenceMd } from './templates/quick-reference';
|
|
@@ -81,11 +81,11 @@ interface InstallReport {
|
|
|
81
81
|
readonly warnings: readonly string[];
|
|
82
82
|
/**
|
|
83
83
|
* The package manager that actually ran. Equal to the detected `pm`
|
|
84
|
-
* on the common path; differs when the
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
84
|
+
* on the common path; differs when the pnpm → npm fallback fired, in
|
|
85
|
+
* which case it's `'npm'`. Threaded into the skills install so the
|
|
86
|
+
* runner stays consistent with the install we just ran — re-trying
|
|
87
|
+
* through `pnpm dlx` when `pnpm install` just failed for
|
|
88
|
+
* workspace/catalog reasons would fail again for the same reason.
|
|
89
89
|
*/
|
|
90
90
|
readonly effectivePm: PackageManager;
|
|
91
91
|
}
|
|
@@ -95,7 +95,7 @@ interface InstallReport {
|
|
|
95
95
|
* structured CLI errors raised at every phase (input resolution, install,
|
|
96
96
|
* emit) and renders them via the same UI surface as success output
|
|
97
97
|
* (`--json` to stdout, human to stderr). Exit codes follow the documented
|
|
98
|
-
* stable set in `./exit-codes.ts`
|
|
98
|
+
* stable set in `./exit-codes.ts` and the
|
|
99
99
|
* [Style Guide § Exit Codes](../../../../../../../docs/CLI%20Style%20Guide.md#exit-codes).
|
|
100
100
|
*
|
|
101
101
|
* Layered for testability: the action handler in `./index.ts` is
|
|
@@ -113,6 +113,11 @@ export async function runInit(
|
|
|
113
113
|
* mode) — see [Style Guide § Interactivity](../../../../../../../docs/CLI%20Style%20Guide.md#interactivity).
|
|
114
114
|
*/
|
|
115
115
|
readonly canPrompt: boolean;
|
|
116
|
+
/**
|
|
117
|
+
* Called once after the first affirmative telemetry consent is persisted.
|
|
118
|
+
* Failures are swallowed by the caller; init success must not depend on telemetry.
|
|
119
|
+
*/
|
|
120
|
+
readonly afterFirstTelemetryConsent?: (inputs: ResolvedInitInputs) => void | Promise<void>;
|
|
116
121
|
/**
|
|
117
122
|
* FR8.3 — test-only seam for the optional database version probe.
|
|
118
123
|
* Production callers omit this; tests inject stub `probePostgres` /
|
|
@@ -124,7 +129,7 @@ export async function runInit(
|
|
|
124
129
|
readonly probeOverrides?: ProbeOverrides;
|
|
125
130
|
},
|
|
126
131
|
): Promise<number> {
|
|
127
|
-
const { options, flags, canPrompt, probeOverrides } = runOptions;
|
|
132
|
+
const { options, flags, canPrompt, probeOverrides, afterFirstTelemetryConsent } = runOptions;
|
|
128
133
|
const ui = new TerminalUI({ color: flags.color, interactive: flags.interactive });
|
|
129
134
|
const warnings: string[] = [];
|
|
130
135
|
const filesWritten: string[] = [];
|
|
@@ -144,6 +149,14 @@ export async function runInit(
|
|
|
144
149
|
throw error;
|
|
145
150
|
}
|
|
146
151
|
|
|
152
|
+
if (inputs.enableTelemetry === true && afterFirstTelemetryConsent !== undefined) {
|
|
153
|
+
try {
|
|
154
|
+
await afterFirstTelemetryConsent(inputs);
|
|
155
|
+
} catch {
|
|
156
|
+
// telemetry is best-effort and must never affect init
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
147
160
|
const pm = await detectPackageManager(baseDir);
|
|
148
161
|
const pkgRun = formatRunCommand(pm, 'prisma-next', '').trimEnd();
|
|
149
162
|
|
|
@@ -445,13 +458,13 @@ export async function runInit(
|
|
|
445
458
|
// framework relies on. A project-level failure is fatal
|
|
446
459
|
// (`INIT_EXIT_SKILL_INSTALL_FAILED`).
|
|
447
460
|
//
|
|
448
|
-
// Runs after install + emit
|
|
449
|
-
//
|
|
450
|
-
//
|
|
451
|
-
//
|
|
452
|
-
// `--no-
|
|
453
|
-
//
|
|
454
|
-
const manualProjectSkillCommands =
|
|
461
|
+
// Runs after the install + emit phase when those steps are enabled,
|
|
462
|
+
// but is not coupled to them: the skills are pulled directly from
|
|
463
|
+
// the Prisma Next GitHub repo and do not require `node_modules`.
|
|
464
|
+
// `--no-install` therefore skips only dependency installation and
|
|
465
|
+
// contract emission; `--no-skill` is the explicit escape hatch for
|
|
466
|
+
// skipping skills.
|
|
467
|
+
const manualProjectSkillCommands = DEFAULT_SKILL_SOURCES.flatMap((source) => [
|
|
455
468
|
formatSkillInstallCommand(install.effectivePm, source),
|
|
456
469
|
formatClaudeSkillInstallCommand(install.effectivePm, source),
|
|
457
470
|
]);
|
|
@@ -459,11 +472,7 @@ export async function runInit(
|
|
|
459
472
|
let skillRegistered = false;
|
|
460
473
|
if (!inputs.installProjectSkill) {
|
|
461
474
|
warnings.push(
|
|
462
|
-
`Skipped Prisma Next
|
|
463
|
-
);
|
|
464
|
-
} else if (install.skipped) {
|
|
465
|
-
warnings.push(
|
|
466
|
-
`Skipped Prisma Next agent-skill install because --no-install was passed. After you run install manually, install the skills with: ${manualProjectSkillSummary}`,
|
|
475
|
+
`Skipped Prisma Next skills install (--no-skill). To install the skills later, run: ${manualProjectSkillSummary}`,
|
|
467
476
|
);
|
|
468
477
|
} else {
|
|
469
478
|
const spinner = ui.spinner();
|
|
@@ -588,6 +597,7 @@ export function exitCodeForError(error: { readonly code: string }): number {
|
|
|
588
597
|
case '5010': // invalid manifest (malformed package.json) — precondition
|
|
589
598
|
case '5011': // invalid tsconfig (unparseable JSONC) — precondition
|
|
590
599
|
case '5012': // probe failed under --strict-probe — precondition
|
|
600
|
+
case '5014': // --authoring / --schema-path extension mismatch — precondition
|
|
591
601
|
return INIT_EXIT_PRECONDITION;
|
|
592
602
|
case '5006': // user aborted interactive prompt
|
|
593
603
|
return INIT_EXIT_USER_ABORTED;
|
|
@@ -597,7 +607,7 @@ export function exitCodeForError(error: { readonly code: string }): number {
|
|
|
597
607
|
return INIT_EXIT_EMIT_FAILED;
|
|
598
608
|
case '5009': // invalid output document — internal bug in prisma-next
|
|
599
609
|
return INIT_EXIT_INTERNAL_ERROR;
|
|
600
|
-
case '5013': //
|
|
610
|
+
case '5013': // skill install failed
|
|
601
611
|
return INIT_EXIT_SKILL_INSTALL_FAILED;
|
|
602
612
|
default:
|
|
603
613
|
// Any unexpected code is treated as an internal bug rather than
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from 'node:fs';
|
|
2
2
|
import * as clack from '@clack/prompts';
|
|
3
|
+
import { readUserConfig, resolveGating, writeUserConfig } from '@prisma-next/cli-telemetry';
|
|
3
4
|
import { extname, join, normalize } from 'pathe';
|
|
4
5
|
import type { GlobalFlags } from '../../utils/global-flags';
|
|
6
|
+
import { isCI } from '../../utils/is-ci';
|
|
5
7
|
import {
|
|
8
|
+
errorInitAuthoringSchemaPathMismatch,
|
|
6
9
|
errorInitInvalidFlagValue,
|
|
7
10
|
errorInitMissingFlags,
|
|
8
11
|
errorInitReinitNeedsForce,
|
|
@@ -68,12 +71,24 @@ export interface ResolvedInitInputs {
|
|
|
68
71
|
* is added separately via the install step.
|
|
69
72
|
*/
|
|
70
73
|
readonly removePreviousFacade: string | null;
|
|
74
|
+
/**
|
|
75
|
+
* Telemetry consent answer recorded during this `init` run, or `null`
|
|
76
|
+
* when no prompt was shown. The prompt fires only on the
|
|
77
|
+
* canPrompt + !autoAcceptPrompts + no env/CI opt-out +
|
|
78
|
+
* `enableTelemetry === undefined` intersection; outside that window
|
|
79
|
+
* the field is `null` and the stored preference (if any) stays
|
|
80
|
+
* unchanged. The answer has already been persisted to
|
|
81
|
+
* `$XDG_CONFIG_HOME/prisma-next/config.json` by
|
|
82
|
+
* the time `runInit` sees this value — it's surfaced here purely so
|
|
83
|
+
* the post-init summary can mention what the user chose.
|
|
84
|
+
*/
|
|
85
|
+
readonly enableTelemetry: boolean | null;
|
|
71
86
|
/**
|
|
72
87
|
* Whether to run `npx skills add prisma/prisma-next#v<version>` at the
|
|
73
88
|
* project level after install + emit. True by default; `--no-skill`
|
|
74
89
|
* sets it to `false`. The skill is always project-level (never
|
|
75
90
|
* user-level / global) so its version stays locked to the project's
|
|
76
|
-
* Prisma Next version — see `
|
|
91
|
+
* Prisma Next version — see `skill-install.ts`.
|
|
77
92
|
*/
|
|
78
93
|
readonly installProjectSkill: boolean;
|
|
79
94
|
}
|
|
@@ -91,6 +106,12 @@ const AUTHORING_VALUES: ReadonlyMap<string, AuthoringId> = new Map([
|
|
|
91
106
|
['ts', 'typescript'],
|
|
92
107
|
]);
|
|
93
108
|
|
|
109
|
+
export const TELEMETRY_CONSENT_MESSAGE = [
|
|
110
|
+
'Help us prioritize features by sharing anonymous CLI usage data?',
|
|
111
|
+
'The telemetry implementation is open source and fully transparent.',
|
|
112
|
+
'(packages/1-framework/3-tooling/cli-telemetry and apps/telemetry-backend).',
|
|
113
|
+
].join(' ');
|
|
114
|
+
|
|
94
115
|
/**
|
|
95
116
|
* Resolves every required input for `runInit`. In interactive mode, missing
|
|
96
117
|
* inputs are prompted via clack; in non-interactive mode, missing required
|
|
@@ -176,6 +197,11 @@ export async function resolveInitInputs(ctx: {
|
|
|
176
197
|
autoAcceptPrompts,
|
|
177
198
|
});
|
|
178
199
|
|
|
200
|
+
const enableTelemetry = await resolveTelemetryConsent({
|
|
201
|
+
canPrompt,
|
|
202
|
+
autoAcceptPrompts,
|
|
203
|
+
});
|
|
204
|
+
|
|
179
205
|
// Skill-install gating. `--no-skill` (commander parses
|
|
180
206
|
// `options.skill === false`) is the only escape hatch; otherwise
|
|
181
207
|
// project-level install is unconditional. The skill is always
|
|
@@ -193,10 +219,60 @@ export async function resolveInitInputs(ctx: {
|
|
|
193
219
|
strictProbe: Boolean(options.strictProbe),
|
|
194
220
|
reinit,
|
|
195
221
|
removePreviousFacade,
|
|
222
|
+
enableTelemetry,
|
|
196
223
|
installProjectSkill,
|
|
197
224
|
};
|
|
198
225
|
}
|
|
199
226
|
|
|
227
|
+
/**
|
|
228
|
+
* The interactive telemetry consent prompt. Shown as the last
|
|
229
|
+
* question of the `init` sequence iff:
|
|
230
|
+
* 1. `canPrompt === true` (interactive stdin / stdout combo),
|
|
231
|
+
* 2. `autoAcceptPrompts === false` (the user did not pass `--yes`),
|
|
232
|
+
* 3. neither telemetry env opt-out is active,
|
|
233
|
+
* 4. the process is not running in CI,
|
|
234
|
+
* 5. the stored `enableTelemetry` value is `undefined` (the user
|
|
235
|
+
* has never been asked, or skipped the prompt previously).
|
|
236
|
+
*
|
|
237
|
+
* Outside that intersection the function returns `null` and leaves the
|
|
238
|
+
* stored preference untouched. Inside it, the user's answer is
|
|
239
|
+
* persisted via `writeUserConfig({ enableTelemetry })` before this
|
|
240
|
+
* function returns; on an affirmative answer `writeUserConfig`
|
|
241
|
+
* generates and stores the v4 `installationId` in the same write.
|
|
242
|
+
*
|
|
243
|
+
* The wording names CLI usage data and points to the open-source
|
|
244
|
+
* client/backend paths. Default value is `true` to match precedent
|
|
245
|
+
* and to make the consent answer a single keystroke once it's been
|
|
246
|
+
* disclosed.
|
|
247
|
+
*/
|
|
248
|
+
async function resolveTelemetryConsent(opts: {
|
|
249
|
+
readonly canPrompt: boolean;
|
|
250
|
+
readonly autoAcceptPrompts: boolean;
|
|
251
|
+
}): Promise<boolean | null> {
|
|
252
|
+
if (!opts.canPrompt || opts.autoAcceptPrompts || isCI()) {
|
|
253
|
+
return null;
|
|
254
|
+
}
|
|
255
|
+
const config = readUserConfig();
|
|
256
|
+
const gating = resolveGating({ env: process.env, config });
|
|
257
|
+
if (!gating.enabled && gating.reason === 'env-override') {
|
|
258
|
+
return null;
|
|
259
|
+
}
|
|
260
|
+
const stored = config.enableTelemetry;
|
|
261
|
+
if (stored !== undefined) {
|
|
262
|
+
return null;
|
|
263
|
+
}
|
|
264
|
+
const result = await clack.confirm({
|
|
265
|
+
message: TELEMETRY_CONSENT_MESSAGE,
|
|
266
|
+
initialValue: true,
|
|
267
|
+
output: process.stderr,
|
|
268
|
+
});
|
|
269
|
+
if (clack.isCancel(result)) {
|
|
270
|
+
throw errorInitUserAborted();
|
|
271
|
+
}
|
|
272
|
+
writeUserConfig({ enableTelemetry: Boolean(result) });
|
|
273
|
+
return Boolean(result);
|
|
274
|
+
}
|
|
275
|
+
|
|
200
276
|
async function resolveWriteEnv(opts: {
|
|
201
277
|
readonly flag: boolean | undefined;
|
|
202
278
|
readonly canPrompt: boolean;
|
|
@@ -376,10 +452,11 @@ function validateSchemaPath(value: string, authoring: AuthoringId): string {
|
|
|
376
452
|
const ext = extname(trimmed).toLowerCase();
|
|
377
453
|
const expected = authoring === 'typescript' ? '.ts' : '.prisma';
|
|
378
454
|
if (ext !== expected) {
|
|
379
|
-
throw
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
455
|
+
throw errorInitAuthoringSchemaPathMismatch({
|
|
456
|
+
authoring,
|
|
457
|
+
schemaPath: trimmed,
|
|
458
|
+
actualExtension: ext.length > 0 ? ext : '(none)',
|
|
459
|
+
expectedExtension: expected,
|
|
383
460
|
});
|
|
384
461
|
}
|
|
385
462
|
return normalize(trimmed);
|