@ddt-tools/cli 0.2.0 → 0.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (203) hide show
  1. package/dist/advise-tests-YNMKVJCD.js +87 -0
  2. package/dist/advise-tests-YNMKVJCD.js.map +1 -0
  3. package/dist/ai-NTNPYEKZ.js +86 -0
  4. package/dist/ai-NTNPYEKZ.js.map +1 -0
  5. package/dist/anonymize-LERTWUQO.js +139 -0
  6. package/dist/anonymize-LERTWUQO.js.map +1 -0
  7. package/dist/approval-GGZGKIU4.js +73 -0
  8. package/dist/approval-GGZGKIU4.js.map +1 -0
  9. package/dist/approval-chain-GWJKZHVU.js +118 -0
  10. package/dist/approval-chain-GWJKZHVU.js.map +1 -0
  11. package/dist/audit-log-2PH55BU4.js +159 -0
  12. package/dist/audit-log-2PH55BU4.js.map +1 -0
  13. package/dist/backlog-QNXGOUF4.js +76 -0
  14. package/dist/backlog-QNXGOUF4.js.map +1 -0
  15. package/dist/bisect-W3XKKRWG.js +111 -0
  16. package/dist/bisect-W3XKKRWG.js.map +1 -0
  17. package/dist/bookmarks-XVOGXGMC.js +107 -0
  18. package/dist/bookmarks-XVOGXGMC.js.map +1 -0
  19. package/dist/branch-S3I2IJGQ.js +103 -0
  20. package/dist/branch-S3I2IJGQ.js.map +1 -0
  21. package/dist/build-MP3JQEFO.js +20 -0
  22. package/dist/build-MP3JQEFO.js.map +1 -0
  23. package/dist/catalog-3J3NFNXP.js +137 -0
  24. package/dist/catalog-3J3NFNXP.js.map +1 -0
  25. package/dist/changelog-ZQAH3ULB.js +216 -0
  26. package/dist/changelog-ZQAH3ULB.js.map +1 -0
  27. package/dist/chunk-2FT6HXKS.js +55 -0
  28. package/dist/chunk-2FT6HXKS.js.map +1 -0
  29. package/dist/chunk-DGUM43GV.js +11 -0
  30. package/dist/chunk-DGUM43GV.js.map +1 -0
  31. package/dist/chunk-DL3V7UJ2.js +25 -0
  32. package/dist/chunk-DL3V7UJ2.js.map +1 -0
  33. package/dist/chunk-VM2H4LAO.js +15 -0
  34. package/dist/chunk-VM2H4LAO.js.map +1 -0
  35. package/dist/chunk-XFXG347C.js +40 -0
  36. package/dist/chunk-XFXG347C.js.map +1 -0
  37. package/dist/cli.js +499 -19402
  38. package/dist/cli.js.map +1 -1
  39. package/dist/compare-P7JOV76O.js +379 -0
  40. package/dist/compare-P7JOV76O.js.map +1 -0
  41. package/dist/compare-profiles-H33CXZPD.js +219 -0
  42. package/dist/compare-profiles-H33CXZPD.js.map +1 -0
  43. package/dist/completion-ZSNCQKJ2.js +89 -0
  44. package/dist/completion-ZSNCQKJ2.js.map +1 -0
  45. package/dist/connection-CDGVEFUC.js +148 -0
  46. package/dist/connection-CDGVEFUC.js.map +1 -0
  47. package/dist/cost-estimate-S2MKHT2H.js +321 -0
  48. package/dist/cost-estimate-S2MKHT2H.js.map +1 -0
  49. package/dist/data-compare-46ZI7KHL.js +128 -0
  50. package/dist/data-compare-46ZI7KHL.js.map +1 -0
  51. package/dist/data-fit-WGEPLD5S.js +127 -0
  52. package/dist/data-fit-WGEPLD5S.js.map +1 -0
  53. package/dist/deploy-status-4H5KJFRC.js +58 -0
  54. package/dist/deploy-status-4H5KJFRC.js.map +1 -0
  55. package/dist/design-ILX3ZSWW.js +135 -0
  56. package/dist/design-ILX3ZSWW.js.map +1 -0
  57. package/dist/diagnose-WPUL67E4.js +150 -0
  58. package/dist/diagnose-WPUL67E4.js.map +1 -0
  59. package/dist/discover-DEO2R5T6.js +78 -0
  60. package/dist/discover-DEO2R5T6.js.map +1 -0
  61. package/dist/docs-QNY3MUVO.js +183 -0
  62. package/dist/docs-QNY3MUVO.js.map +1 -0
  63. package/dist/drift-FDRNPWQA.js +233 -0
  64. package/dist/drift-FDRNPWQA.js.map +1 -0
  65. package/dist/drift-gate-6BWWWMHW.js +103 -0
  66. package/dist/drift-gate-6BWWWMHW.js.map +1 -0
  67. package/dist/error-lookup-4R3Y4RBC.js +56 -0
  68. package/dist/error-lookup-4R3Y4RBC.js.map +1 -0
  69. package/dist/errorReporting-3LPE2IJY.js +109 -0
  70. package/dist/errorReporting-3LPE2IJY.js.map +1 -0
  71. package/dist/exec-JOLH5LPT.js +122 -0
  72. package/dist/exec-JOLH5LPT.js.map +1 -0
  73. package/dist/explain-NS26WE2Y.js +189 -0
  74. package/dist/explain-NS26WE2Y.js.map +1 -0
  75. package/dist/explorer-GSYYYOAL.js +58 -0
  76. package/dist/explorer-GSYYYOAL.js.map +1 -0
  77. package/dist/extract-4LWEZG4O.js +152 -0
  78. package/dist/extract-4LWEZG4O.js.map +1 -0
  79. package/dist/features-KQV4OFIZ.js +54 -0
  80. package/dist/features-KQV4OFIZ.js.map +1 -0
  81. package/dist/feedback-CBLGXUEG.js +158 -0
  82. package/dist/feedback-CBLGXUEG.js.map +1 -0
  83. package/dist/find-SMXRCZ76.js +176 -0
  84. package/dist/find-SMXRCZ76.js.map +1 -0
  85. package/dist/format-HMGG6MY3.js +277 -0
  86. package/dist/format-HMGG6MY3.js.map +1 -0
  87. package/dist/generate-W7VLBDLI.js +160 -0
  88. package/dist/generate-W7VLBDLI.js.map +1 -0
  89. package/dist/graph-YYL5UYCJ.js +168 -0
  90. package/dist/graph-YYL5UYCJ.js.map +1 -0
  91. package/dist/history-GDRFP4PG.js +184 -0
  92. package/dist/history-GDRFP4PG.js.map +1 -0
  93. package/dist/hosts-DRFZTMIJ.js +45 -0
  94. package/dist/hosts-DRFZTMIJ.js.map +1 -0
  95. package/dist/impact-A4NU6CB2.js +63 -0
  96. package/dist/impact-A4NU6CB2.js.map +1 -0
  97. package/dist/import-2RNYDL4E.js +79 -0
  98. package/dist/import-2RNYDL4E.js.map +1 -0
  99. package/dist/index.cjs +11 -5
  100. package/dist/index.cjs.map +1 -1
  101. package/dist/index.js +8 -2
  102. package/dist/index.js.map +1 -1
  103. package/dist/init-EAOGNGXI.js +54 -0
  104. package/dist/init-EAOGNGXI.js.map +1 -0
  105. package/dist/install-hooks-G3Y5LVXK.js +109 -0
  106. package/dist/install-hooks-G3Y5LVXK.js.map +1 -0
  107. package/dist/license-Z5YSC7XQ.js +43 -0
  108. package/dist/license-Z5YSC7XQ.js.map +1 -0
  109. package/dist/lineage-C5CGVP36.js +555 -0
  110. package/dist/lineage-C5CGVP36.js.map +1 -0
  111. package/dist/lint-AQFPZ3WG.js +144 -0
  112. package/dist/lint-AQFPZ3WG.js.map +1 -0
  113. package/dist/mcp-F7FND5X7.js +343 -0
  114. package/dist/mcp-F7FND5X7.js.map +1 -0
  115. package/dist/migrate-from-dbt-K4ELOWUD.js +156 -0
  116. package/dist/migrate-from-dbt-K4ELOWUD.js.map +1 -0
  117. package/dist/migrate-platform-E7VZFPO5.js +91 -0
  118. package/dist/migrate-platform-E7VZFPO5.js.map +1 -0
  119. package/dist/optimize-WUJ5ZN5Y.js +109 -0
  120. package/dist/optimize-WUJ5ZN5Y.js.map +1 -0
  121. package/dist/perf-UULZSREY.js +200 -0
  122. package/dist/perf-UULZSREY.js.map +1 -0
  123. package/dist/pii-QHU32VML.js +146 -0
  124. package/dist/pii-QHU32VML.js.map +1 -0
  125. package/dist/pilot-BR6GVK32.js +29 -0
  126. package/dist/pilot-BR6GVK32.js.map +1 -0
  127. package/dist/pr-comment-2FOA3EXG.js +81 -0
  128. package/dist/pr-comment-2FOA3EXG.js.map +1 -0
  129. package/dist/preview-XNY422OU.js +46 -0
  130. package/dist/preview-XNY422OU.js.map +1 -0
  131. package/dist/profile-SQTBNKYS.js +98 -0
  132. package/dist/profile-SQTBNKYS.js.map +1 -0
  133. package/dist/promote-FSGUPIPD.js +417 -0
  134. package/dist/promote-FSGUPIPD.js.map +1 -0
  135. package/dist/publish-AYCRMCE2.js +739 -0
  136. package/dist/publish-AYCRMCE2.js.map +1 -0
  137. package/dist/purge-Y5IOTXKA.js +56 -0
  138. package/dist/purge-Y5IOTXKA.js.map +1 -0
  139. package/dist/query-log-SDDGMJLJ.js +112 -0
  140. package/dist/query-log-SDDGMJLJ.js.map +1 -0
  141. package/dist/refactor-TC7S43F2.js +5809 -0
  142. package/dist/refactor-TC7S43F2.js.map +1 -0
  143. package/dist/refresh-MDJYOYV5.js +39 -0
  144. package/dist/refresh-MDJYOYV5.js.map +1 -0
  145. package/dist/replay-E4664A5K.js +118 -0
  146. package/dist/replay-E4664A5K.js.map +1 -0
  147. package/dist/revert-QWQWCJJB.js +111 -0
  148. package/dist/revert-QWQWCJJB.js.map +1 -0
  149. package/dist/review-7CAVLD67.js +164 -0
  150. package/dist/review-7CAVLD67.js.map +1 -0
  151. package/dist/rollback-suggest-C6D5YFCA.js +79 -0
  152. package/dist/rollback-suggest-C6D5YFCA.js.map +1 -0
  153. package/dist/safer-alternative-QR4QEFUV.js +84 -0
  154. package/dist/safer-alternative-QR4QEFUV.js.map +1 -0
  155. package/dist/safety-OFWUFLK4.js +165 -0
  156. package/dist/safety-OFWUFLK4.js.map +1 -0
  157. package/dist/savings-MEBE4TXI.js +95 -0
  158. package/dist/savings-MEBE4TXI.js.map +1 -0
  159. package/dist/scan-secrets-XCUBMLHL.js +54 -0
  160. package/dist/scan-secrets-XCUBMLHL.js.map +1 -0
  161. package/dist/schema-7JZIG6QR.js +447 -0
  162. package/dist/schema-7JZIG6QR.js.map +1 -0
  163. package/dist/script-BMYVBHFR.js +167 -0
  164. package/dist/script-BMYVBHFR.js.map +1 -0
  165. package/dist/search-TA3C3AZT.js +151 -0
  166. package/dist/search-TA3C3AZT.js.map +1 -0
  167. package/dist/seed-W4Q3L2IU.js +101 -0
  168. package/dist/seed-W4Q3L2IU.js.map +1 -0
  169. package/dist/sketch-6B2V6FJV.js +83 -0
  170. package/dist/sketch-6B2V6FJV.js.map +1 -0
  171. package/dist/snapshot-YMVS322L.js +171 -0
  172. package/dist/snapshot-YMVS322L.js.map +1 -0
  173. package/dist/snippets-EVTN63OU.js +74 -0
  174. package/dist/snippets-EVTN63OU.js.map +1 -0
  175. package/dist/standards-FGJW3CQL.js +238 -0
  176. package/dist/standards-FGJW3CQL.js.map +1 -0
  177. package/dist/suggest-V3LVIFZ5.js +44 -0
  178. package/dist/suggest-V3LVIFZ5.js.map +1 -0
  179. package/dist/suggest-constraints-EX2FCWOQ.js +154 -0
  180. package/dist/suggest-constraints-EX2FCWOQ.js.map +1 -0
  181. package/dist/suite-YTQ3CNX5.js +85 -0
  182. package/dist/suite-YTQ3CNX5.js.map +1 -0
  183. package/dist/telemetry-KOIY3NEQ.js +90 -0
  184. package/dist/telemetry-KOIY3NEQ.js.map +1 -0
  185. package/dist/template-MUJ6X6LN.js +396 -0
  186. package/dist/template-MUJ6X6LN.js.map +1 -0
  187. package/dist/test-XFSQHR2S.js +169 -0
  188. package/dist/test-XFSQHR2S.js.map +1 -0
  189. package/dist/trial-GFTGYCR3.js +31 -0
  190. package/dist/trial-GFTGYCR3.js.map +1 -0
  191. package/dist/validate-LFDEZFFH.js +107 -0
  192. package/dist/validate-LFDEZFFH.js.map +1 -0
  193. package/dist/verify-KRDYOJCR.js +76 -0
  194. package/dist/verify-KRDYOJCR.js.map +1 -0
  195. package/dist/watch-FSG23RR3.js +80 -0
  196. package/dist/watch-FSG23RR3.js.map +1 -0
  197. package/dist/xcompare-U4TXTTIR.js +87 -0
  198. package/dist/xcompare-U4TXTTIR.js.map +1 -0
  199. package/package.json +2 -2
  200. package/dist/cli.cjs +0 -19298
  201. package/dist/cli.cjs.map +0 -1
  202. package/dist/cli.d.cts +0 -1
  203. package/dist/cli.d.ts +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/compare-profiles.ts"],"sourcesContent":["/**\n * `ddt compare-profiles` — manage saved comparison profiles.\n *\n * A profile is \"Source X (connection|pac|project) → Target Y, with these\n * mapping rules\", persisted to `<cwd>/.ddt/compare-profiles.json`. The\n * eventual VS Code webview reads/writes the same store; this CLI gives\n * power-users the same affordances headlessly.\n *\n * Subcommands:\n * list — list all saved profiles (table or JSON)\n * show <id> — print one profile (table or JSON)\n * save — upsert a profile from a JSON file (--json-file <path>) or stdin\n * remove <id> — delete a profile\n * preview <id> — run `previewMatch` over the saved profile's\n * source + target endpoints and report counts +\n * examples of matched / source-only / target-only\n * FQNs. Catches typo'd schema names before a\n * full extract + diff round-trip.\n *\n * `preview` resolves each endpoint locally:\n * - kind=connection — reads the EE1 catalog cache at\n * `<root>/.ddt/cache/<reference>/catalog.msgpack` (legacy\n * `catalog.json` also accepted). Run\n * `ddt catalog refresh --connection <reference>` first.\n * - kind=pac — reads the `.ddtpac` archive at `reference`.\n * - kind=project — reads the `.ddtproj` at `reference`.\n */\nimport { promises as fs } from 'node:fs';\nimport { Command } from 'commander';\nimport { catalog, compareProfiles, loadProject, pac, parseProjectModel } from '@ddt-tools/core';\nimport type { DatabricksObject, FullyQualifiedName } from '@ddt-tools/core/model';\n\nexport function compareProfilesCommand(): Command {\n const cmd = new Command('compare-profiles');\n cmd.description('Manage saved compare profiles (.ddt/compare-profiles.json).');\n\n cmd\n .command('list')\n .description('List every saved profile.')\n .option('--root <path>', 'Project root. Default cwd.', process.cwd())\n .option('--json', 'Emit JSON instead of a human table.')\n .action(async (opts) => {\n const store = new compareProfiles.CompareProfilesStore({ root: String(opts.root) });\n const all = await store.list();\n if (opts.json) {\n console.log(JSON.stringify(all, null, 2));\n return;\n }\n if (all.length === 0) {\n console.log('(no compare profiles saved yet)');\n console.log(` store: ${store.path}`);\n return;\n }\n console.log(`${all.length} compare profile(s):`);\n for (const p of all) {\n console.log(` ${p.id.padEnd(24)} ${p.name}`);\n console.log(` source: ${p.source.kind}=${p.source.reference}`);\n console.log(` target: ${p.target.kind}=${p.target.reference}`);\n console.log(` mappings: ${p.mappings.length} updated: ${p.updatedAt}`);\n }\n });\n\n cmd\n .command('show')\n .description('Show one profile by id.')\n .argument('<id>', 'Profile id (from `compare-profiles list`).')\n .option('--root <path>', 'Project root. Default cwd.', process.cwd())\n .option('--json', 'Emit JSON instead of a human table.')\n .action(async (id, opts) => {\n const store = new compareProfiles.CompareProfilesStore({ root: String(opts.root) });\n const p = await store.get(String(id));\n if (!p) {\n console.error(\n `No profile with id \"${id}\". Run \\`ddt compare-profiles list\\` to enumerate.`,\n );\n process.exitCode = 1;\n return;\n }\n if (opts.json) {\n console.log(JSON.stringify(p, null, 2));\n return;\n }\n console.log(`Profile: ${p.name} (${p.id})`);\n if (p.description) console.log(` ${p.description}`);\n console.log(\n ` source: ${p.source.kind}=${p.source.reference}${p.source.catalog ? ` cat=${p.source.catalog}` : ''}${p.source.schema ? ` schema=${p.source.schema}` : ''}`,\n );\n console.log(\n ` target: ${p.target.kind}=${p.target.reference}${p.target.catalog ? ` cat=${p.target.catalog}` : ''}${p.target.schema ? ` schema=${p.target.schema}` : ''}`,\n );\n console.log(` case-sensitive: ${p.caseSensitive ? 'yes' : 'no'}`);\n console.log(` rewrite-inside-strings: ${p.rewriteInsideStrings ? 'yes' : 'no'}`);\n console.log(` updatedAt: ${p.updatedAt}`);\n if (p.mappings.length === 0) {\n console.log(' mappings: (none — identity)');\n } else {\n console.log(` mappings (${p.mappings.length}):`);\n for (const m of p.mappings) console.log(` ${m.source} => ${m.target}`);\n }\n });\n\n cmd\n .command('save')\n .description('Upsert a profile from a JSON file or stdin.')\n .option('--root <path>', 'Project root. Default cwd.', process.cwd())\n .option('--json-file <path>', 'Path to a JSON file containing one profile. Use \"-\" for stdin.')\n .action(async (opts) => {\n if (!opts.jsonFile) {\n throw new Error('--json-file is required (use a path or \"-\" for stdin).');\n }\n const raw =\n String(opts.jsonFile) === '-'\n ? await readStdin()\n : await fs.readFile(String(opts.jsonFile), 'utf8');\n const parsed = JSON.parse(raw) as compareProfiles.CompareProfile;\n if (!parsed?.id || !parsed?.name || !parsed?.source || !parsed?.target) {\n throw new Error(\n 'Profile JSON must contain at minimum: id, name, source, target. mappings defaults to [].',\n );\n }\n const store = new compareProfiles.CompareProfilesStore({ root: String(opts.root) });\n const stamped = await store.upsert({ ...parsed, mappings: parsed.mappings ?? [] });\n console.log(`Saved profile \"${stamped.name}\" (${stamped.id})`);\n console.log(` store: ${store.path}`);\n });\n\n cmd\n .command('remove')\n .description('Delete one profile by id.')\n .argument('<id>', 'Profile id (from `compare-profiles list`).')\n .option('--root <path>', 'Project root. Default cwd.', process.cwd())\n .action(async (id, opts) => {\n const store = new compareProfiles.CompareProfilesStore({ root: String(opts.root) });\n const removed = await store.remove(String(id));\n if (removed) {\n console.log(`Removed profile \"${id}\".`);\n } else {\n console.warn(`No profile with id \"${id}\" — nothing to remove.`);\n }\n });\n\n cmd\n .command('preview')\n .description(\n \"Preview which FQNs match between the profile's source and target. Local-only — no workspace round-trip.\",\n )\n .argument('<id>', 'Profile id (from `compare-profiles list`).')\n .option('--root <path>', 'Project root. Default cwd.', process.cwd())\n .option(\n '--examples <n>',\n 'Max example FQNs to show per bucket in human output. Default 5.',\n (v) => parseInt(v, 10),\n 5,\n )\n .option('--json', 'Emit the full PreviewSummary as JSON.')\n .action(async (id, opts) => {\n const store = new compareProfiles.CompareProfilesStore({ root: String(opts.root) });\n const profile = await store.get(String(id));\n if (!profile) {\n console.error(\n `No profile with id \"${id}\". Run \\`ddt compare-profiles list\\` to enumerate.`,\n );\n process.exitCode = 1;\n return;\n }\n\n const root = String(opts.root);\n const source = await resolveEndpointFqns(profile.source, root);\n const target = await resolveEndpointFqns(profile.target, root);\n\n const summary = compareProfiles.previewMatch({\n source: source.fqns,\n target: target.fqns,\n mappings: profile.mappings,\n ...(profile.caseSensitive ? { caseSensitive: true } : {}),\n });\n\n if (opts.json) {\n console.log(\n JSON.stringify(\n {\n profile: { id: profile.id, name: profile.name },\n source: {\n kind: profile.source.kind,\n reference: profile.source.reference,\n count: source.fqns.length,\n },\n target: {\n kind: profile.target.kind,\n reference: profile.target.reference,\n count: target.fqns.length,\n },\n summary,\n },\n null,\n 2,\n ),\n );\n return;\n }\n\n console.log(`Preview: ${profile.name} (${profile.id})`);\n console.log(\n ` source (${profile.source.kind}=${profile.source.reference}): ${source.fqns.length} object(s)${source.note ? ` — ${source.note}` : ''}`,\n );\n console.log(\n ` target (${profile.target.kind}=${profile.target.reference}): ${target.fqns.length} object(s)${target.note ? ` — ${target.note}` : ''}`,\n );\n console.log('');\n console.log(` matched: ${summary.matchedCount}`);\n console.log(` source-only: ${summary.sourceOnlyCount}`);\n console.log(` target-only: ${summary.targetOnlyCount}`);\n\n const exN = Math.max(0, Number(opts.examples));\n printBucket('matched', summary.matched, exN);\n printBucket('source-only', summary.sourceOnly, exN);\n printBucket('target-only', summary.targetOnly, exN);\n\n if (\n summary.matchedCount === 0 &&\n (summary.sourceOnlyCount > 0 || summary.targetOnlyCount > 0)\n ) {\n console.warn(\"No FQNs matched — check the profile's scope and mapping rules.\");\n }\n });\n\n return cmd;\n}\n\ninterface EndpointResolution {\n fqns: FullyQualifiedName[];\n note?: string;\n}\n\nasync function resolveEndpointFqns(\n endpoint: compareProfiles.CompareProfile['source'],\n root: string,\n): Promise<EndpointResolution> {\n if (endpoint.kind === 'connection') {\n const cache = new catalog.CatalogCache({ root, connection: endpoint.reference });\n const snapshot = await cache.get();\n if (snapshot.catalogs.length === 0) {\n return {\n fqns: [],\n note: `empty catalog cache at ${cache.path} — run \\`ddt catalog refresh --connection ${endpoint.reference}\\` first`,\n };\n }\n return { fqns: fqnsFromSnapshot(snapshot, endpoint.catalog, endpoint.schema) };\n }\n if (endpoint.kind === 'pac') {\n const contents = await pac.readPac(endpoint.reference);\n return { fqns: fqnsFromObjects(contents.model, endpoint.catalog, endpoint.schema) };\n }\n // kind === 'project'\n const loaded = await loadProject(endpoint.reference);\n const model = await parseProjectModel(loaded);\n return { fqns: fqnsFromObjects(model, endpoint.catalog, endpoint.schema) };\n}\n\nfunction fqnsFromSnapshot(\n snapshot: catalog.CatalogSnapshot,\n catalogScope: string | undefined,\n schemaScope: string | undefined,\n): FullyQualifiedName[] {\n const sameId = (a: string | undefined, b: string | undefined): boolean =>\n !!a && !!b && a.toUpperCase() === b.toUpperCase();\n const out: FullyQualifiedName[] = [];\n for (const cat of snapshot.catalogs) {\n if (catalogScope && !sameId(cat.catalog, catalogScope)) continue;\n for (const sc of cat.schemas) {\n if (schemaScope && !sameId(sc.schema, schemaScope)) continue;\n for (const obj of sc.objects) {\n // FQN uses `database` for UC's catalog — paired-file convention.\n out.push({ database: obj.catalog, schema: obj.schema, name: obj.name });\n }\n }\n }\n return out;\n}\n\nfunction fqnsFromObjects(\n model: readonly DatabricksObject[],\n catalogScope: string | undefined,\n schemaScope: string | undefined,\n): FullyQualifiedName[] {\n const sameId = (a: string | undefined, b: string | undefined): boolean =>\n !!a && !!b && a.toUpperCase() === b.toUpperCase();\n const out: FullyQualifiedName[] = [];\n for (const obj of model) {\n // FQN uses `database` for UC's catalog — paired-file convention.\n if (catalogScope && !sameId(obj.fqn.database, catalogScope)) continue;\n if (schemaScope && !sameId(obj.fqn.schema, schemaScope)) continue;\n out.push(obj.fqn);\n }\n return out;\n}\n\nfunction printBucket(label: string, items: readonly string[], exampleN: number): void {\n if (items.length === 0) return;\n console.log('');\n console.log(\n ` ${label} examples (showing ${Math.min(exampleN, items.length)} of ${items.length}):`,\n );\n for (const fqn of items.slice(0, exampleN)) console.log(` ${fqn}`);\n}\n\nasync function readStdin(): Promise<string> {\n const chunks: Buffer[] = [];\n for await (const chunk of process.stdin) {\n chunks.push(typeof chunk === 'string' ? Buffer.from(chunk) : (chunk as Buffer));\n }\n return Buffer.concat(chunks).toString('utf8');\n}\n"],"mappings":";;;AA2BA,SAAS,YAAY,UAAU;AAC/B,SAAS,eAAe;AACxB,SAAS,SAAS,iBAAiB,aAAa,KAAK,yBAAyB;AAGvE,SAAS,yBAAkC;AAChD,QAAM,MAAM,IAAI,QAAQ,kBAAkB;AAC1C,MAAI,YAAY,6DAA6D;AAE7E,MACG,QAAQ,MAAM,EACd,YAAY,2BAA2B,EACvC,OAAO,iBAAiB,8BAA8B,QAAQ,IAAI,CAAC,EACnE,OAAO,UAAU,qCAAqC,EACtD,OAAO,OAAO,SAAS;AACtB,UAAM,QAAQ,IAAI,gBAAgB,qBAAqB,EAAE,MAAM,OAAO,KAAK,IAAI,EAAE,CAAC;AAClF,UAAM,MAAM,MAAM,MAAM,KAAK;AAC7B,QAAI,KAAK,MAAM;AACb,cAAQ,IAAI,KAAK,UAAU,KAAK,MAAM,CAAC,CAAC;AACxC;AAAA,IACF;AACA,QAAI,IAAI,WAAW,GAAG;AACpB,cAAQ,IAAI,iCAAiC;AAC7C,cAAQ,IAAI,YAAY,MAAM,IAAI,EAAE;AACpC;AAAA,IACF;AACA,YAAQ,IAAI,GAAG,IAAI,MAAM,sBAAsB;AAC/C,eAAW,KAAK,KAAK;AACnB,cAAQ,IAAI,KAAK,EAAE,GAAG,OAAO,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE;AAC7C,cAAQ,IAAI,eAAe,EAAE,OAAO,IAAI,IAAI,EAAE,OAAO,SAAS,EAAE;AAChE,cAAQ,IAAI,eAAe,EAAE,OAAO,IAAI,IAAI,EAAE,OAAO,SAAS,EAAE;AAChE,cAAQ,IAAI,iBAAiB,EAAE,SAAS,MAAM,gBAAgB,EAAE,SAAS,EAAE;AAAA,IAC7E;AAAA,EACF,CAAC;AAEH,MACG,QAAQ,MAAM,EACd,YAAY,yBAAyB,EACrC,SAAS,QAAQ,4CAA4C,EAC7D,OAAO,iBAAiB,8BAA8B,QAAQ,IAAI,CAAC,EACnE,OAAO,UAAU,qCAAqC,EACtD,OAAO,OAAO,IAAI,SAAS;AAC1B,UAAM,QAAQ,IAAI,gBAAgB,qBAAqB,EAAE,MAAM,OAAO,KAAK,IAAI,EAAE,CAAC;AAClF,UAAM,IAAI,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;AACpC,QAAI,CAAC,GAAG;AACN,cAAQ;AAAA,QACN,uBAAuB,EAAE;AAAA,MAC3B;AACA,cAAQ,WAAW;AACnB;AAAA,IACF;AACA,QAAI,KAAK,MAAM;AACb,cAAQ,IAAI,KAAK,UAAU,GAAG,MAAM,CAAC,CAAC;AACtC;AAAA,IACF;AACA,YAAQ,IAAI,YAAY,EAAE,IAAI,KAAK,EAAE,EAAE,GAAG;AAC1C,QAAI,EAAE,YAAa,SAAQ,IAAI,KAAK,EAAE,WAAW,EAAE;AACnD,YAAQ;AAAA,MACN,cAAc,EAAE,OAAO,IAAI,IAAI,EAAE,OAAO,SAAS,GAAG,EAAE,OAAO,UAAU,QAAQ,EAAE,OAAO,OAAO,KAAK,EAAE,GAAG,EAAE,OAAO,SAAS,WAAW,EAAE,OAAO,MAAM,KAAK,EAAE;AAAA,IAC9J;AACA,YAAQ;AAAA,MACN,cAAc,EAAE,OAAO,IAAI,IAAI,EAAE,OAAO,SAAS,GAAG,EAAE,OAAO,UAAU,QAAQ,EAAE,OAAO,OAAO,KAAK,EAAE,GAAG,EAAE,OAAO,SAAS,WAAW,EAAE,OAAO,MAAM,KAAK,EAAE;AAAA,IAC9J;AACA,YAAQ,IAAI,qBAAqB,EAAE,gBAAgB,QAAQ,IAAI,EAAE;AACjE,YAAQ,IAAI,6BAA6B,EAAE,uBAAuB,QAAQ,IAAI,EAAE;AAChF,YAAQ,IAAI,gBAAgB,EAAE,SAAS,EAAE;AACzC,QAAI,EAAE,SAAS,WAAW,GAAG;AAC3B,cAAQ,IAAI,oCAA+B;AAAA,IAC7C,OAAO;AACL,cAAQ,IAAI,eAAe,EAAE,SAAS,MAAM,IAAI;AAChD,iBAAW,KAAK,EAAE,SAAU,SAAQ,IAAI,OAAO,EAAE,MAAM,SAAS,EAAE,MAAM,EAAE;AAAA,IAC5E;AAAA,EACF,CAAC;AAEH,MACG,QAAQ,MAAM,EACd,YAAY,6CAA6C,EACzD,OAAO,iBAAiB,8BAA8B,QAAQ,IAAI,CAAC,EACnE,OAAO,sBAAsB,gEAAgE,EAC7F,OAAO,OAAO,SAAS;AACtB,QAAI,CAAC,KAAK,UAAU;AAClB,YAAM,IAAI,MAAM,wDAAwD;AAAA,IAC1E;AACA,UAAM,MACJ,OAAO,KAAK,QAAQ,MAAM,MACtB,MAAM,UAAU,IAChB,MAAM,GAAG,SAAS,OAAO,KAAK,QAAQ,GAAG,MAAM;AACrD,UAAM,SAAS,KAAK,MAAM,GAAG;AAC7B,QAAI,CAAC,QAAQ,MAAM,CAAC,QAAQ,QAAQ,CAAC,QAAQ,UAAU,CAAC,QAAQ,QAAQ;AACtE,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AACA,UAAM,QAAQ,IAAI,gBAAgB,qBAAqB,EAAE,MAAM,OAAO,KAAK,IAAI,EAAE,CAAC;AAClF,UAAM,UAAU,MAAM,MAAM,OAAO,EAAE,GAAG,QAAQ,UAAU,OAAO,YAAY,CAAC,EAAE,CAAC;AACjF,YAAQ,IAAI,kBAAkB,QAAQ,IAAI,MAAM,QAAQ,EAAE,GAAG;AAC7D,YAAQ,IAAI,YAAY,MAAM,IAAI,EAAE;AAAA,EACtC,CAAC;AAEH,MACG,QAAQ,QAAQ,EAChB,YAAY,2BAA2B,EACvC,SAAS,QAAQ,4CAA4C,EAC7D,OAAO,iBAAiB,8BAA8B,QAAQ,IAAI,CAAC,EACnE,OAAO,OAAO,IAAI,SAAS;AAC1B,UAAM,QAAQ,IAAI,gBAAgB,qBAAqB,EAAE,MAAM,OAAO,KAAK,IAAI,EAAE,CAAC;AAClF,UAAM,UAAU,MAAM,MAAM,OAAO,OAAO,EAAE,CAAC;AAC7C,QAAI,SAAS;AACX,cAAQ,IAAI,oBAAoB,EAAE,IAAI;AAAA,IACxC,OAAO;AACL,cAAQ,KAAK,uBAAuB,EAAE,6BAAwB;AAAA,IAChE;AAAA,EACF,CAAC;AAEH,MACG,QAAQ,SAAS,EACjB;AAAA,IACC;AAAA,EACF,EACC,SAAS,QAAQ,4CAA4C,EAC7D,OAAO,iBAAiB,8BAA8B,QAAQ,IAAI,CAAC,EACnE;AAAA,IACC;AAAA,IACA;AAAA,IACA,CAAC,MAAM,SAAS,GAAG,EAAE;AAAA,IACrB;AAAA,EACF,EACC,OAAO,UAAU,uCAAuC,EACxD,OAAO,OAAO,IAAI,SAAS;AAC1B,UAAM,QAAQ,IAAI,gBAAgB,qBAAqB,EAAE,MAAM,OAAO,KAAK,IAAI,EAAE,CAAC;AAClF,UAAM,UAAU,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;AAC1C,QAAI,CAAC,SAAS;AACZ,cAAQ;AAAA,QACN,uBAAuB,EAAE;AAAA,MAC3B;AACA,cAAQ,WAAW;AACnB;AAAA,IACF;AAEA,UAAM,OAAO,OAAO,KAAK,IAAI;AAC7B,UAAM,SAAS,MAAM,oBAAoB,QAAQ,QAAQ,IAAI;AAC7D,UAAM,SAAS,MAAM,oBAAoB,QAAQ,QAAQ,IAAI;AAE7D,UAAM,UAAU,gBAAgB,aAAa;AAAA,MAC3C,QAAQ,OAAO;AAAA,MACf,QAAQ,OAAO;AAAA,MACf,UAAU,QAAQ;AAAA,MAClB,GAAI,QAAQ,gBAAgB,EAAE,eAAe,KAAK,IAAI,CAAC;AAAA,IACzD,CAAC;AAED,QAAI,KAAK,MAAM;AACb,cAAQ;AAAA,QACN,KAAK;AAAA,UACH;AAAA,YACE,SAAS,EAAE,IAAI,QAAQ,IAAI,MAAM,QAAQ,KAAK;AAAA,YAC9C,QAAQ;AAAA,cACN,MAAM,QAAQ,OAAO;AAAA,cACrB,WAAW,QAAQ,OAAO;AAAA,cAC1B,OAAO,OAAO,KAAK;AAAA,YACrB;AAAA,YACA,QAAQ;AAAA,cACN,MAAM,QAAQ,OAAO;AAAA,cACrB,WAAW,QAAQ,OAAO;AAAA,cAC1B,OAAO,OAAO,KAAK;AAAA,YACrB;AAAA,YACA;AAAA,UACF;AAAA,UACA;AAAA,UACA;AAAA,QACF;AAAA,MACF;AACA;AAAA,IACF;AAEA,YAAQ,IAAI,YAAY,QAAQ,IAAI,KAAK,QAAQ,EAAE,GAAG;AACtD,YAAQ;AAAA,MACN,aAAa,QAAQ,OAAO,IAAI,IAAI,QAAQ,OAAO,SAAS,MAAM,OAAO,KAAK,MAAM,aAAa,OAAO,OAAO,WAAM,OAAO,IAAI,KAAK,EAAE;AAAA,IACzI;AACA,YAAQ;AAAA,MACN,aAAa,QAAQ,OAAO,IAAI,IAAI,QAAQ,OAAO,SAAS,MAAM,OAAO,KAAK,MAAM,aAAa,OAAO,OAAO,WAAM,OAAO,IAAI,KAAK,EAAE;AAAA,IACzI;AACA,YAAQ,IAAI,EAAE;AACd,YAAQ,IAAI,kBAAkB,QAAQ,YAAY,EAAE;AACpD,YAAQ,IAAI,kBAAkB,QAAQ,eAAe,EAAE;AACvD,YAAQ,IAAI,kBAAkB,QAAQ,eAAe,EAAE;AAEvD,UAAM,MAAM,KAAK,IAAI,GAAG,OAAO,KAAK,QAAQ,CAAC;AAC7C,gBAAY,WAAW,QAAQ,SAAS,GAAG;AAC3C,gBAAY,eAAe,QAAQ,YAAY,GAAG;AAClD,gBAAY,eAAe,QAAQ,YAAY,GAAG;AAElD,QACE,QAAQ,iBAAiB,MACxB,QAAQ,kBAAkB,KAAK,QAAQ,kBAAkB,IAC1D;AACA,cAAQ,KAAK,qEAAgE;AAAA,IAC/E;AAAA,EACF,CAAC;AAEH,SAAO;AACT;AAOA,eAAe,oBACb,UACA,MAC6B;AAC7B,MAAI,SAAS,SAAS,cAAc;AAClC,UAAM,QAAQ,IAAI,QAAQ,aAAa,EAAE,MAAM,YAAY,SAAS,UAAU,CAAC;AAC/E,UAAM,WAAW,MAAM,MAAM,IAAI;AACjC,QAAI,SAAS,SAAS,WAAW,GAAG;AAClC,aAAO;AAAA,QACL,MAAM,CAAC;AAAA,QACP,MAAM,0BAA0B,MAAM,IAAI,kDAA6C,SAAS,SAAS;AAAA,MAC3G;AAAA,IACF;AACA,WAAO,EAAE,MAAM,iBAAiB,UAAU,SAAS,SAAS,SAAS,MAAM,EAAE;AAAA,EAC/E;AACA,MAAI,SAAS,SAAS,OAAO;AAC3B,UAAM,WAAW,MAAM,IAAI,QAAQ,SAAS,SAAS;AACrD,WAAO,EAAE,MAAM,gBAAgB,SAAS,OAAO,SAAS,SAAS,SAAS,MAAM,EAAE;AAAA,EACpF;AAEA,QAAM,SAAS,MAAM,YAAY,SAAS,SAAS;AACnD,QAAM,QAAQ,MAAM,kBAAkB,MAAM;AAC5C,SAAO,EAAE,MAAM,gBAAgB,OAAO,SAAS,SAAS,SAAS,MAAM,EAAE;AAC3E;AAEA,SAAS,iBACP,UACA,cACA,aACsB;AACtB,QAAM,SAAS,CAAC,GAAuB,MACrC,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,YAAY,MAAM,EAAE,YAAY;AAClD,QAAM,MAA4B,CAAC;AACnC,aAAW,OAAO,SAAS,UAAU;AACnC,QAAI,gBAAgB,CAAC,OAAO,IAAI,SAAS,YAAY,EAAG;AACxD,eAAW,MAAM,IAAI,SAAS;AAC5B,UAAI,eAAe,CAAC,OAAO,GAAG,QAAQ,WAAW,EAAG;AACpD,iBAAW,OAAO,GAAG,SAAS;AAE5B,YAAI,KAAK,EAAE,UAAU,IAAI,SAAS,QAAQ,IAAI,QAAQ,MAAM,IAAI,KAAK,CAAC;AAAA,MACxE;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,gBACP,OACA,cACA,aACsB;AACtB,QAAM,SAAS,CAAC,GAAuB,MACrC,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,YAAY,MAAM,EAAE,YAAY;AAClD,QAAM,MAA4B,CAAC;AACnC,aAAW,OAAO,OAAO;AAEvB,QAAI,gBAAgB,CAAC,OAAO,IAAI,IAAI,UAAU,YAAY,EAAG;AAC7D,QAAI,eAAe,CAAC,OAAO,IAAI,IAAI,QAAQ,WAAW,EAAG;AACzD,QAAI,KAAK,IAAI,GAAG;AAAA,EAClB;AACA,SAAO;AACT;AAEA,SAAS,YAAY,OAAe,OAA0B,UAAwB;AACpF,MAAI,MAAM,WAAW,EAAG;AACxB,UAAQ,IAAI,EAAE;AACd,UAAQ;AAAA,IACN,KAAK,KAAK,sBAAsB,KAAK,IAAI,UAAU,MAAM,MAAM,CAAC,OAAO,MAAM,MAAM;AAAA,EACrF;AACA,aAAW,OAAO,MAAM,MAAM,GAAG,QAAQ,EAAG,SAAQ,IAAI,OAAO,GAAG,EAAE;AACtE;AAEA,eAAe,YAA6B;AAC1C,QAAM,SAAmB,CAAC;AAC1B,mBAAiB,SAAS,QAAQ,OAAO;AACvC,WAAO,KAAK,OAAO,UAAU,WAAW,OAAO,KAAK,KAAK,IAAK,KAAgB;AAAA,EAChF;AACA,SAAO,OAAO,OAAO,MAAM,EAAE,SAAS,MAAM;AAC9C;","names":[]}
@@ -0,0 +1,89 @@
1
+ import {
2
+ logger
3
+ } from "./chunk-VM2H4LAO.js";
4
+ import "./chunk-DGUM43GV.js";
5
+
6
+ // src/commands/completion.ts
7
+ import { Command } from "commander";
8
+ function completionCommand() {
9
+ return new Command("completion").description("Emit a shell completion script (bash | zsh | fish | powershell).").argument("<shell>", "bash | zsh | fish | powershell").action((shell) => {
10
+ const cmds = [
11
+ "connection",
12
+ "init",
13
+ "validate",
14
+ "compare",
15
+ "build",
16
+ "publish",
17
+ "script",
18
+ "import",
19
+ "extract",
20
+ "drift",
21
+ "refactor",
22
+ "format",
23
+ "completion",
24
+ "telemetry",
25
+ "license"
26
+ ];
27
+ switch (shell) {
28
+ case "bash":
29
+ console.log(bashScript(cmds));
30
+ return;
31
+ case "zsh":
32
+ console.log(zshScript(cmds));
33
+ return;
34
+ case "fish":
35
+ console.log(fishScript(cmds));
36
+ return;
37
+ case "powershell":
38
+ case "pwsh":
39
+ console.log(powerShellScript(cmds));
40
+ return;
41
+ default:
42
+ logger.error(`Unsupported shell: ${shell}. Use bash | zsh | fish | powershell.`);
43
+ process.exitCode = 1;
44
+ }
45
+ });
46
+ }
47
+ function bashScript(cmds) {
48
+ return `# ddt bash completion. Source from ~/.bashrc or drop in /etc/bash_completion.d/.
49
+ _ddt() {
50
+ local cur prev
51
+ COMPREPLY=()
52
+ cur="\${COMP_WORDS[COMP_CWORD]}"
53
+ prev="\${COMP_WORDS[COMP_CWORD-1]}"
54
+ if [[ $COMP_CWORD -eq 1 ]]; then
55
+ COMPREPLY=( $(compgen -W "${cmds.join(" ")}" -- "$cur") )
56
+ return 0
57
+ fi
58
+ }
59
+ complete -F _ddt ddt
60
+ `;
61
+ }
62
+ function zshScript(cmds) {
63
+ return `#compdef ddt
64
+ _ddt() {
65
+ local -a commands
66
+ commands=(${cmds.map((c) => `'${c}:'`).join(" ")})
67
+ _describe 'command' commands
68
+ }
69
+ _ddt "$@"
70
+ `;
71
+ }
72
+ function fishScript(cmds) {
73
+ return cmds.map((c) => `complete -c ddt -f -n '__fish_use_subcommand' -a '${c}'`).join("\n");
74
+ }
75
+ function powerShellScript(cmds) {
76
+ return `# ddt PowerShell completion. Add to your $PROFILE.
77
+ Register-ArgumentCompleter -Native -CommandName ddt -ScriptBlock {
78
+ param($wordToComplete, $commandAst, $cursorPosition)
79
+ $commands = @(${cmds.map((c) => `'${c}'`).join(", ")})
80
+ $commands | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object {
81
+ [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_)
82
+ }
83
+ }
84
+ `;
85
+ }
86
+ export {
87
+ completionCommand
88
+ };
89
+ //# sourceMappingURL=completion-ZSNCQKJ2.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/completion.ts"],"sourcesContent":["import { Command } from 'commander';\n\nimport { logger } from '../util/logger.js';\n\n/**\n * `ddt completion` — emit a shell completion script for bash / zsh / fish.\n * Pipe into your shell init: `ddt completion zsh > ~/.zsh/completions/_ddt`.\n *\n * Completion content is intentionally minimal — command names + the\n * known top-level flags. Subcommand-aware completion (i.e. completing\n * profile names after `ddt connection test`) would require a separate\n * shell helper that calls `ddt connection list`; out of scope.\n */\nexport function completionCommand(): Command {\n return new Command('completion')\n .description('Emit a shell completion script (bash | zsh | fish | powershell).')\n .argument('<shell>', 'bash | zsh | fish | powershell')\n .action((shell: string) => {\n const cmds = [\n 'connection',\n 'init',\n 'validate',\n 'compare',\n 'build',\n 'publish',\n 'script',\n 'import',\n 'extract',\n 'drift',\n 'refactor',\n 'format',\n 'completion',\n 'telemetry',\n 'license',\n ];\n switch (shell) {\n case 'bash':\n console.log(bashScript(cmds));\n return;\n case 'zsh':\n console.log(zshScript(cmds));\n return;\n case 'fish':\n console.log(fishScript(cmds));\n return;\n case 'powershell':\n case 'pwsh':\n console.log(powerShellScript(cmds));\n return;\n default:\n // RH4.6 — match the SDT side (exitCode + logger.error) rather than\n // throwing, so unknown-shell handling is identical across both CLIs.\n logger.error(`Unsupported shell: ${shell}. Use bash | zsh | fish | powershell.`);\n process.exitCode = 1;\n }\n });\n}\n\nfunction bashScript(cmds: string[]): string {\n return `# ddt bash completion. Source from ~/.bashrc or drop in /etc/bash_completion.d/.\n_ddt() {\n local cur prev\n COMPREPLY=()\n cur=\"\\${COMP_WORDS[COMP_CWORD]}\"\n prev=\"\\${COMP_WORDS[COMP_CWORD-1]}\"\n if [[ $COMP_CWORD -eq 1 ]]; then\n COMPREPLY=( $(compgen -W \"${cmds.join(' ')}\" -- \"$cur\") )\n return 0\n fi\n}\ncomplete -F _ddt ddt\n`;\n}\n\nfunction zshScript(cmds: string[]): string {\n return `#compdef ddt\n_ddt() {\n local -a commands\n commands=(${cmds.map((c) => `'${c}:'`).join(' ')})\n _describe 'command' commands\n}\n_ddt \"$@\"\n`;\n}\n\nfunction fishScript(cmds: string[]): string {\n return cmds.map((c) => `complete -c ddt -f -n '__fish_use_subcommand' -a '${c}'`).join('\\n');\n}\n\nfunction powerShellScript(cmds: string[]): string {\n return `# ddt PowerShell completion. Add to your $PROFILE.\nRegister-ArgumentCompleter -Native -CommandName ddt -ScriptBlock {\n param($wordToComplete, $commandAst, $cursorPosition)\n $commands = @(${cmds.map((c) => `'${c}'`).join(', ')})\n $commands | Where-Object { $_ -like \"$wordToComplete*\" } | ForEach-Object {\n [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_)\n }\n}\n`;\n}\n"],"mappings":";;;;;;AAAA,SAAS,eAAe;AAajB,SAAS,oBAA6B;AAC3C,SAAO,IAAI,QAAQ,YAAY,EAC5B,YAAY,kEAAkE,EAC9E,SAAS,WAAW,gCAAgC,EACpD,OAAO,CAAC,UAAkB;AACzB,UAAM,OAAO;AAAA,MACX;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AACA,YAAQ,OAAO;AAAA,MACb,KAAK;AACH,gBAAQ,IAAI,WAAW,IAAI,CAAC;AAC5B;AAAA,MACF,KAAK;AACH,gBAAQ,IAAI,UAAU,IAAI,CAAC;AAC3B;AAAA,MACF,KAAK;AACH,gBAAQ,IAAI,WAAW,IAAI,CAAC;AAC5B;AAAA,MACF,KAAK;AAAA,MACL,KAAK;AACH,gBAAQ,IAAI,iBAAiB,IAAI,CAAC;AAClC;AAAA,MACF;AAGE,eAAO,MAAM,sBAAsB,KAAK,uCAAuC;AAC/E,gBAAQ,WAAW;AAAA,IACvB;AAAA,EACF,CAAC;AACL;AAEA,SAAS,WAAW,MAAwB;AAC1C,SAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,gCAOuB,KAAK,KAAK,GAAG,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAM9C;AAEA,SAAS,UAAU,MAAwB;AACzC,SAAO;AAAA;AAAA;AAAA,cAGK,KAAK,IAAI,CAAC,MAAM,IAAI,CAAC,IAAI,EAAE,KAAK,GAAG,CAAC;AAAA;AAAA;AAAA;AAAA;AAKlD;AAEA,SAAS,WAAW,MAAwB;AAC1C,SAAO,KAAK,IAAI,CAAC,MAAM,qDAAqD,CAAC,GAAG,EAAE,KAAK,IAAI;AAC7F;AAEA,SAAS,iBAAiB,MAAwB;AAChD,SAAO;AAAA;AAAA;AAAA,kBAGS,KAAK,IAAI,CAAC,MAAM,IAAI,CAAC,GAAG,EAAE,KAAK,IAAI,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAMtD;","names":[]}
@@ -0,0 +1,148 @@
1
+ import "./chunk-DGUM43GV.js";
2
+
3
+ // src/commands/connection.ts
4
+ import { Command } from "commander";
5
+ import {
6
+ createConnection,
7
+ defaultProfilesPath,
8
+ getProfile,
9
+ loadProfiles,
10
+ removeProfile,
11
+ upsertProfile
12
+ } from "@ddt-tools/core";
13
+ function connectionCommand() {
14
+ const cmd = new Command("connection").description("Manage Databricks connection profiles.");
15
+ cmd.command("add [name]").description("Store a new connection profile.").option("--name <name>", "Profile name (flag form of the positional name arg)").option("--host <host>", "Workspace host, e.g. dbc-12345-abcd.cloud.databricks.com").option("--auth <method>", "One of: pat | oauth-m2m | oauth-u2m | azure-ad | google-idc", "pat").option(
16
+ "--token <token>",
17
+ "PAT token \u2014 use env:DATABRICKS_TOKEN to reference an env var, never inline a secret"
18
+ ).option("--client-id <id>", "OAuth M2M / Azure AD client ID").option("--client-secret <secret>", "OAuth M2M / Azure AD client secret").option("--tenant-id <id>", "Azure AD tenant ID").option(
19
+ "--service-account-key-path <path>",
20
+ "Path to a GCP service-account key JSON file (for google-idc auth)"
21
+ ).requiredOption("--warehouse-id <id>", "SQL warehouse ID for DDL operations").option("--catalog <catalog>", "Default Unity Catalog catalog").option("--schema <schema>", "Default schema").action(async (nameArg, opts) => {
22
+ const name = opts.name ? String(opts.name) : nameArg ?? "";
23
+ if (!name) {
24
+ throw new Error(
25
+ "connection add needs a profile name \u2014 pass it positionally (`ddt connection add <name>`) or via `--name`."
26
+ );
27
+ }
28
+ if (!opts.host) throw new Error("--host is required");
29
+ const auth = buildAuth(opts);
30
+ const profile = {
31
+ name,
32
+ platform: "Databricks",
33
+ auth,
34
+ warehouseId: String(opts.warehouseId),
35
+ ...opts.catalog ? { catalog: String(opts.catalog) } : {},
36
+ ...opts.schema ? { schema: String(opts.schema) } : {}
37
+ };
38
+ await upsertProfile(profile);
39
+ console.log(`Saved profile "${name}" \u2192 ${defaultProfilesPath()}`);
40
+ });
41
+ cmd.command("list").description("List configured Databricks profiles.").action(async () => {
42
+ const profiles = await loadProfiles();
43
+ if (profiles.length === 0) {
44
+ console.log(`(no profiles configured at ${defaultProfilesPath()})`);
45
+ return;
46
+ }
47
+ for (const p of profiles) {
48
+ console.log(`${p.name} ${p.auth.method} ${p.auth.host} warehouse=${p.warehouseId}`);
49
+ }
50
+ });
51
+ cmd.command("get <name>").description("Print a profile (secrets are redacted).").action(async (name) => {
52
+ const p = await getProfile(name);
53
+ const redacted = {
54
+ ...p,
55
+ auth: redactAuth(p.auth)
56
+ };
57
+ console.log(JSON.stringify(redacted, null, 2));
58
+ });
59
+ cmd.command("remove <name>").description("Delete a connection profile.").action(async (name) => {
60
+ const removed = await removeProfile(name);
61
+ console.log(removed ? `Removed profile "${name}".` : `No profile "${name}" found.`);
62
+ if (!removed) process.exitCode = 1;
63
+ });
64
+ cmd.command("test <name>").description(
65
+ "Probe a connection profile via the workspace REST API (`/api/2.0/preview/scim/v2/Me`)."
66
+ ).action(async (name) => {
67
+ const profile = await getProfile(name);
68
+ const conn = createConnection(profile);
69
+ try {
70
+ await conn.connect();
71
+ const info = await conn.test();
72
+ console.log(`OK ${name}`);
73
+ console.log(` host: ${profile.auth.host}`);
74
+ console.log(` account: ${info.account}`);
75
+ console.log(` version: ${info.version}`);
76
+ } catch (err) {
77
+ console.error(`FAIL ${name}: ${err instanceof Error ? err.message : String(err)}`);
78
+ process.exitCode = 1;
79
+ } finally {
80
+ await conn.disconnect();
81
+ }
82
+ });
83
+ return cmd;
84
+ }
85
+ function buildAuth(opts) {
86
+ const host = String(opts.host);
87
+ const method = String(opts.auth ?? "pat").toLowerCase();
88
+ switch (method) {
89
+ case "pat":
90
+ case "personal_access_token":
91
+ if (!opts.token) throw new Error("--token is required for PAT auth");
92
+ return { method: "PERSONAL_ACCESS_TOKEN", host, token: String(opts.token) };
93
+ case "oauth-m2m":
94
+ case "oauth_m2m":
95
+ if (!opts.clientId || !opts.clientSecret) {
96
+ throw new Error("--client-id and --client-secret are required for OAuth M2M");
97
+ }
98
+ return {
99
+ method: "OAUTH_M2M",
100
+ host,
101
+ clientId: String(opts.clientId),
102
+ clientSecret: String(opts.clientSecret)
103
+ };
104
+ case "oauth-u2m":
105
+ case "oauth_u2m":
106
+ return { method: "OAUTH_U2M", host };
107
+ case "azure-ad":
108
+ case "azure_ad":
109
+ if (!opts.tenantId || !opts.clientId) {
110
+ throw new Error("--tenant-id and --client-id are required for Azure AD");
111
+ }
112
+ return {
113
+ method: "AZURE_AD",
114
+ host,
115
+ tenantId: String(opts.tenantId),
116
+ clientId: String(opts.clientId),
117
+ ...opts.clientSecret ? { clientSecret: String(opts.clientSecret) } : {}
118
+ };
119
+ case "google-idc":
120
+ case "google_idc":
121
+ if (!opts.serviceAccountKeyPath) {
122
+ throw new Error("--service-account-key-path is required for google-idc auth");
123
+ }
124
+ return {
125
+ method: "GOOGLE_IDC",
126
+ host,
127
+ serviceAccountKeyPath: String(opts.serviceAccountKeyPath)
128
+ };
129
+ default:
130
+ throw new Error(`Unknown --auth value: ${method}`);
131
+ }
132
+ }
133
+ function redactAuth(auth) {
134
+ if (auth.method === "PERSONAL_ACCESS_TOKEN" && auth.token && !auth.token.startsWith("env:")) {
135
+ return { ...auth, token: "<redacted>" };
136
+ }
137
+ if (auth.method === "OAUTH_M2M" && auth.clientSecret && !auth.clientSecret.startsWith("env:")) {
138
+ return { ...auth, clientSecret: "<redacted>" };
139
+ }
140
+ if (auth.method === "AZURE_AD" && auth.clientSecret && !auth.clientSecret.startsWith("env:")) {
141
+ return { ...auth, clientSecret: "<redacted>" };
142
+ }
143
+ return auth;
144
+ }
145
+ export {
146
+ connectionCommand
147
+ };
148
+ //# sourceMappingURL=connection-CDGVEFUC.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/connection.ts"],"sourcesContent":["import { Command } from 'commander';\nimport {\n createConnection,\n defaultProfilesPath,\n getProfile,\n loadProfiles,\n removeProfile,\n upsertProfile,\n type DatabricksConnectionProfile,\n type DatabricksAuth,\n} from '@ddt-tools/core';\n\n/**\n * `ddt connection` — manage Databricks workspace connection profiles.\n *\n * Profiles live at `~/.ddt/profiles.json` (overridable via `$DDT_PROFILES_PATH`).\n * add / list / get / remove are wired today. `test` (probe a profile via\n * the workspace REST API) waits on the OAuth U2M flow before it can land.\n */\nexport function connectionCommand(): Command {\n const cmd = new Command('connection').description('Manage Databricks connection profiles.');\n\n cmd\n .command('add [name]')\n .description('Store a new connection profile.')\n .option('--name <name>', 'Profile name (flag form of the positional name arg)')\n .option('--host <host>', 'Workspace host, e.g. dbc-12345-abcd.cloud.databricks.com')\n .option('--auth <method>', 'One of: pat | oauth-m2m | oauth-u2m | azure-ad | google-idc', 'pat')\n .option(\n '--token <token>',\n 'PAT token — use env:DATABRICKS_TOKEN to reference an env var, never inline a secret',\n )\n .option('--client-id <id>', 'OAuth M2M / Azure AD client ID')\n .option('--client-secret <secret>', 'OAuth M2M / Azure AD client secret')\n .option('--tenant-id <id>', 'Azure AD tenant ID')\n .option(\n '--service-account-key-path <path>',\n 'Path to a GCP service-account key JSON file (for google-idc auth)',\n )\n .requiredOption('--warehouse-id <id>', 'SQL warehouse ID for DDL operations')\n .option('--catalog <catalog>', 'Default Unity Catalog catalog')\n .option('--schema <schema>', 'Default schema')\n .action(async (nameArg: string | undefined, opts) => {\n // Accept the profile name either positionally (`ddt connection add prod`)\n // or via `--name prod` (the flag form `sdt connection add` uses, and what\n // GETTING_STARTED documents canonically). Flag wins when both are given.\n const name = opts.name ? String(opts.name) : (nameArg ?? '');\n if (!name) {\n throw new Error(\n 'connection add needs a profile name — pass it positionally (`ddt connection add <name>`) or via `--name`.',\n );\n }\n if (!opts.host) throw new Error('--host is required');\n const auth = buildAuth(opts);\n const profile: DatabricksConnectionProfile = {\n name,\n platform: 'Databricks',\n auth,\n warehouseId: String(opts.warehouseId),\n ...(opts.catalog ? { catalog: String(opts.catalog) } : {}),\n ...(opts.schema ? { schema: String(opts.schema) } : {}),\n };\n await upsertProfile(profile);\n console.log(`Saved profile \"${name}\" → ${defaultProfilesPath()}`);\n });\n\n cmd\n .command('list')\n .description('List configured Databricks profiles.')\n .action(async () => {\n const profiles = await loadProfiles();\n if (profiles.length === 0) {\n console.log(`(no profiles configured at ${defaultProfilesPath()})`);\n return;\n }\n for (const p of profiles) {\n console.log(`${p.name}\\t${p.auth.method}\\t${p.auth.host}\\twarehouse=${p.warehouseId}`);\n }\n });\n\n cmd\n .command('get <name>')\n .description('Print a profile (secrets are redacted).')\n .action(async (name: string) => {\n const p = await getProfile(name);\n const redacted: DatabricksConnectionProfile = {\n ...p,\n auth: redactAuth(p.auth),\n };\n console.log(JSON.stringify(redacted, null, 2));\n });\n\n cmd\n .command('remove <name>')\n .description('Delete a connection profile.')\n .action(async (name: string) => {\n const removed = await removeProfile(name);\n console.log(removed ? `Removed profile \"${name}\".` : `No profile \"${name}\" found.`);\n if (!removed) process.exitCode = 1;\n });\n\n cmd\n .command('test <name>')\n .description(\n 'Probe a connection profile via the workspace REST API (`/api/2.0/preview/scim/v2/Me`).',\n )\n .action(async (name: string) => {\n const profile = await getProfile(name);\n const conn = createConnection(profile);\n try {\n await conn.connect();\n const info = await conn.test();\n console.log(`OK ${name}`);\n console.log(` host: ${profile.auth.host}`);\n console.log(` account: ${info.account}`);\n console.log(` version: ${info.version}`);\n } catch (err) {\n console.error(`FAIL ${name}: ${err instanceof Error ? err.message : String(err)}`);\n process.exitCode = 1;\n } finally {\n await conn.disconnect();\n }\n });\n\n return cmd;\n}\n\nfunction buildAuth(opts: {\n host?: string;\n auth?: string;\n token?: string;\n clientId?: string;\n clientSecret?: string;\n tenantId?: string;\n serviceAccountKeyPath?: string;\n}): DatabricksAuth {\n const host = String(opts.host);\n const method = String(opts.auth ?? 'pat').toLowerCase();\n switch (method) {\n case 'pat':\n case 'personal_access_token':\n if (!opts.token) throw new Error('--token is required for PAT auth');\n return { method: 'PERSONAL_ACCESS_TOKEN', host, token: String(opts.token) };\n case 'oauth-m2m':\n case 'oauth_m2m':\n if (!opts.clientId || !opts.clientSecret) {\n throw new Error('--client-id and --client-secret are required for OAuth M2M');\n }\n return {\n method: 'OAUTH_M2M',\n host,\n clientId: String(opts.clientId),\n clientSecret: String(opts.clientSecret),\n };\n case 'oauth-u2m':\n case 'oauth_u2m':\n return { method: 'OAUTH_U2M', host };\n case 'azure-ad':\n case 'azure_ad':\n if (!opts.tenantId || !opts.clientId) {\n throw new Error('--tenant-id and --client-id are required for Azure AD');\n }\n return {\n method: 'AZURE_AD',\n host,\n tenantId: String(opts.tenantId),\n clientId: String(opts.clientId),\n ...(opts.clientSecret ? { clientSecret: String(opts.clientSecret) } : {}),\n };\n case 'google-idc':\n case 'google_idc':\n if (!opts.serviceAccountKeyPath) {\n throw new Error('--service-account-key-path is required for google-idc auth');\n }\n return {\n method: 'GOOGLE_IDC',\n host,\n serviceAccountKeyPath: String(opts.serviceAccountKeyPath),\n };\n default:\n throw new Error(`Unknown --auth value: ${method}`);\n }\n}\n\nfunction redactAuth(auth: DatabricksAuth): DatabricksAuth {\n if (auth.method === 'PERSONAL_ACCESS_TOKEN' && auth.token && !auth.token.startsWith('env:')) {\n return { ...auth, token: '<redacted>' };\n }\n if (auth.method === 'OAUTH_M2M' && auth.clientSecret && !auth.clientSecret.startsWith('env:')) {\n return { ...auth, clientSecret: '<redacted>' };\n }\n if (auth.method === 'AZURE_AD' && auth.clientSecret && !auth.clientSecret.startsWith('env:')) {\n return { ...auth, clientSecret: '<redacted>' };\n }\n return auth;\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,321 @@
1
+ import {
2
+ attachExplainFlag,
3
+ runExplain
4
+ } from "./chunk-XFXG347C.js";
5
+ import "./chunk-DGUM43GV.js";
6
+
7
+ // src/commands/cost-estimate.ts
8
+ import { promises as fs } from "fs";
9
+ import path from "path";
10
+ import { Command } from "commander";
11
+ function costEstimateCommand() {
12
+ const cmd = new Command("cost-estimate");
13
+ cmd.description("Heuristic Databricks-DBU estimate for a generated migration script.").requiredOption(
14
+ "--script <path>",
15
+ "Path to a generated migration script (.sql) from `ddt publish`."
16
+ ).option(
17
+ "--warehouse-size <size>",
18
+ "SQL warehouse size: 2X-Small | X-Small | Small | Medium | Large | X-Large | 2X-Large | 3X-Large | 4X-Large.",
19
+ "Small"
20
+ ).option("--format <fmt>", "table | json | markdown. Default table.", "table").option("-o, --out <path>", "Write output to file. Defaults to stdout.").option(
21
+ "--calibrate-from <path>",
22
+ "AI Phase 6 calibration: a JSON file of QueryHistoryEntry[] from prior deploys. Each historic statement is classified against the same cost-classes; classes with \u22653 samples adopt empirical min/max duration in place of the heuristic range."
23
+ ).action(
24
+ async (opts) => {
25
+ const sql = await fs.readFile(path.resolve(String(opts.script)), "utf8");
26
+ const whRaw = String(opts.warehouseSize ?? "Small");
27
+ const wh = whRaw;
28
+ if (!(wh in DBU_PER_HOUR)) {
29
+ throw new Error(
30
+ `Unknown --warehouse-size: ${whRaw}. Use one of ${Object.keys(DBU_PER_HOUR).join(" | ")}.`
31
+ );
32
+ }
33
+ let calibration;
34
+ if (opts.calibrateFrom) {
35
+ const histRaw = await fs.readFile(path.resolve(String(opts.calibrateFrom)), "utf8");
36
+ const parsed = JSON.parse(histRaw);
37
+ const entries = Array.isArray(parsed) ? parsed : Array.isArray(parsed.entries) ? parsed.entries : [];
38
+ calibration = buildCalibration(entries);
39
+ }
40
+ const report = estimateCost(sql, wh, calibration);
41
+ const fmt = String(opts.format ?? "table").toLowerCase();
42
+ let payload;
43
+ if (fmt === "json") {
44
+ payload = JSON.stringify(report, null, 2);
45
+ } else if (fmt === "markdown") {
46
+ payload = renderMarkdown(report);
47
+ } else {
48
+ payload = renderTable(report);
49
+ }
50
+ if (opts.out) {
51
+ const p = path.resolve(String(opts.out));
52
+ await fs.mkdir(path.dirname(p), { recursive: true });
53
+ await fs.writeFile(p, payload + (payload.endsWith("\n") ? "" : "\n"), "utf8");
54
+ console.error(`Wrote ${p}.`);
55
+ } else {
56
+ process.stdout.write(payload + (payload.endsWith("\n") ? "" : "\n"));
57
+ }
58
+ await runExplain(
59
+ {
60
+ feature: "cost-estimate.explain",
61
+ systemPrompt: "You are a Databricks cost engineer. Walk the team through this DBU estimate: what is the dominant cost driver, what does the range mean in practice, and what knobs (warehouse size, Photon, sequencing, off-peak timing) would meaningfully reduce it."
62
+ },
63
+ opts,
64
+ () => `Cost estimate (JSON) follows:
65
+
66
+ ${JSON.stringify(report, null, 2)}
67
+
68
+ Narrate this for a teammate.`
69
+ );
70
+ }
71
+ );
72
+ attachExplainFlag(cmd);
73
+ return cmd;
74
+ }
75
+ var DBU_PER_HOUR = {
76
+ "2X-Small": 4,
77
+ "X-Small": 6,
78
+ Small: 12,
79
+ Medium: 24,
80
+ Large: 40,
81
+ "X-Large": 80,
82
+ "2X-Large": 144,
83
+ "3X-Large": 272,
84
+ "4X-Large": 528
85
+ };
86
+ var COST_CLASSES = [
87
+ {
88
+ id: "table-rebuild",
89
+ description: "CREATE OR REPLACE TABLE or CTAS \u2014 rewrites the entire table",
90
+ minSeconds: 30,
91
+ maxSeconds: 1800,
92
+ test: (s) => /CREATE\s+(?:OR\s+REPLACE\s+)?TABLE\s+.+\s+AS\s+SELECT/i.test(s) || /CREATE\s+OR\s+REPLACE\s+TABLE\b/i.test(s)
93
+ },
94
+ {
95
+ id: "optimize",
96
+ description: "OPTIMIZE / OPTIMIZE \u2026 ZORDER BY \u2014 compact + cluster",
97
+ minSeconds: 60,
98
+ maxSeconds: 3600,
99
+ test: (s) => /^\s*OPTIMIZE\b/i.test(s)
100
+ },
101
+ {
102
+ id: "vacuum",
103
+ description: "VACUUM \u2014 removes old files",
104
+ minSeconds: 30,
105
+ maxSeconds: 600,
106
+ test: (s) => /^\s*VACUUM\b/i.test(s)
107
+ },
108
+ {
109
+ id: "add-column",
110
+ description: "ALTER TABLE ADD COLUMN \u2014 metadata-only",
111
+ minSeconds: 1,
112
+ maxSeconds: 5,
113
+ test: (s) => /ALTER\s+TABLE\b.*\bADD\s+COLUMN\b/i.test(s)
114
+ },
115
+ {
116
+ id: "drop-column",
117
+ description: "ALTER TABLE DROP COLUMN \u2014 metadata-only (DV-enabled)",
118
+ minSeconds: 1,
119
+ maxSeconds: 5,
120
+ test: (s) => /ALTER\s+TABLE\b.*\bDROP\s+COLUMN\b/i.test(s)
121
+ },
122
+ {
123
+ id: "alter-type-narrowing",
124
+ description: "ALTER COLUMN TYPE \u2014 may require rewrite",
125
+ minSeconds: 30,
126
+ maxSeconds: 600,
127
+ test: (s) => /ALTER\s+TABLE\b.*\bALTER\s+COLUMN\b.*\bTYPE\b/i.test(s)
128
+ },
129
+ {
130
+ id: "create-view",
131
+ description: "CREATE OR REPLACE VIEW \u2014 metadata-only",
132
+ minSeconds: 1,
133
+ maxSeconds: 3,
134
+ test: (s) => /CREATE\s+(?:OR\s+REPLACE\s+)?VIEW\b/i.test(s) && !/MATERIALIZED/i.test(s)
135
+ },
136
+ {
137
+ id: "create-mv",
138
+ description: "CREATE MATERIALIZED VIEW \u2014 initial materialisation",
139
+ minSeconds: 60,
140
+ maxSeconds: 3600,
141
+ test: (s) => /CREATE\s+(?:OR\s+REPLACE\s+)?MATERIALIZED\s+VIEW\b/i.test(s)
142
+ },
143
+ {
144
+ id: "create-streaming",
145
+ description: "CREATE STREAMING TABLE \u2014 initial population",
146
+ minSeconds: 60,
147
+ maxSeconds: 1800,
148
+ test: (s) => /CREATE\s+(?:OR\s+REPLACE\s+)?STREAMING\s+TABLE\b/i.test(s)
149
+ },
150
+ {
151
+ id: "create-pipeline",
152
+ description: "CREATE PIPELINE \u2014 DLT initial run",
153
+ minSeconds: 120,
154
+ maxSeconds: 3600,
155
+ test: (s) => /CREATE\s+(?:OR\s+REPLACE\s+)?PIPELINE\b/i.test(s)
156
+ },
157
+ {
158
+ id: "set-tblproperties",
159
+ description: "ALTER TABLE \u2026 SET TBLPROPERTIES \u2014 metadata-only",
160
+ minSeconds: 1,
161
+ maxSeconds: 3,
162
+ test: (s) => /ALTER\s+TABLE\b.*\bSET\s+TBLPROPERTIES\b/i.test(s)
163
+ },
164
+ {
165
+ id: "drop",
166
+ description: "DROP statement \u2014 metadata-only",
167
+ minSeconds: 1,
168
+ maxSeconds: 3,
169
+ test: (s) => /^\s*DROP\s+/i.test(s)
170
+ },
171
+ {
172
+ id: "grant",
173
+ description: "GRANT / REVOKE \u2014 metadata-only",
174
+ minSeconds: 1,
175
+ maxSeconds: 2,
176
+ test: (s) => /^\s*(?:GRANT|REVOKE)\s+/i.test(s)
177
+ },
178
+ {
179
+ id: "comment",
180
+ description: "COMMENT-only statement \u2014 free",
181
+ minSeconds: 0,
182
+ maxSeconds: 0,
183
+ test: (s) => s.replace(/--[^\n]*/g, "").replace(/\/\*[\s\S]*?\*\//g, "").trim().length === 0
184
+ }
185
+ ];
186
+ function buildCalibration(entries) {
187
+ const samples = /* @__PURE__ */ new Map();
188
+ for (const e of entries) {
189
+ const sql = typeof e.sqlText === "string" ? e.sqlText : "";
190
+ const dur = typeof e.durationMs === "number" ? e.durationMs : NaN;
191
+ if (!sql || !isFinite(dur) || dur <= 0) continue;
192
+ const matched = COST_CLASSES.find((c) => c.test(sql));
193
+ if (!matched) continue;
194
+ const arr = samples.get(matched.id) ?? [];
195
+ arr.push(dur / 1e3);
196
+ samples.set(matched.id, arr);
197
+ }
198
+ const perClass = {};
199
+ for (const [id, durs] of samples) {
200
+ if (durs.length < 3) continue;
201
+ durs.sort((a, b) => a - b);
202
+ perClass[id] = {
203
+ sampleSize: durs.length,
204
+ minSeconds: Math.max(1, Math.round(durs[0])),
205
+ maxSeconds: Math.max(1, Math.round(durs[durs.length - 1]))
206
+ };
207
+ }
208
+ return { perClass };
209
+ }
210
+ function estimateCost(sql, warehouseSize, calibration) {
211
+ const statements = splitStatements(sql);
212
+ const classCounts = /* @__PURE__ */ new Map();
213
+ let classifiedStatements = 0;
214
+ let unknownStatements = 0;
215
+ for (const stmt of statements) {
216
+ const matched = COST_CLASSES.find((c) => c.test(stmt));
217
+ if (matched) {
218
+ classCounts.set(matched.id, (classCounts.get(matched.id) ?? 0) + 1);
219
+ classifiedStatements++;
220
+ } else if (stmt.trim().length > 0) {
221
+ unknownStatements++;
222
+ }
223
+ }
224
+ const dbuPerHour = DBU_PER_HOUR[warehouseSize];
225
+ const perClass = COST_CLASSES.filter((c) => (classCounts.get(c.id) ?? 0) > 0).map((c) => {
226
+ const count = classCounts.get(c.id) ?? 0;
227
+ const cal = calibration?.perClass[c.id];
228
+ const minSecondsPerStmt = cal ? cal.minSeconds : c.minSeconds;
229
+ const maxSecondsPerStmt = cal ? cal.maxSeconds : c.maxSeconds;
230
+ const minSeconds = minSecondsPerStmt * count;
231
+ const maxSeconds = maxSecondsPerStmt * count;
232
+ return {
233
+ id: c.id,
234
+ description: c.description,
235
+ count,
236
+ minSeconds,
237
+ maxSeconds,
238
+ minDbu: minSeconds / 3600 * dbuPerHour,
239
+ maxDbu: maxSeconds / 3600 * dbuPerHour,
240
+ ...cal ? { calibratedFromSamples: cal.sampleSize } : {}
241
+ };
242
+ });
243
+ const totals = perClass.reduce(
244
+ (acc, c) => ({
245
+ minSeconds: acc.minSeconds + c.minSeconds,
246
+ maxSeconds: acc.maxSeconds + c.maxSeconds,
247
+ minDbu: acc.minDbu + c.minDbu,
248
+ maxDbu: acc.maxDbu + c.maxDbu
249
+ }),
250
+ { minSeconds: 0, maxSeconds: 0, minDbu: 0, maxDbu: 0 }
251
+ );
252
+ return {
253
+ warehouseSize,
254
+ dbuPerHour,
255
+ totalStatements: statements.length,
256
+ classifiedStatements,
257
+ unknownStatements,
258
+ perClass,
259
+ totals
260
+ };
261
+ }
262
+ function splitStatements(sql) {
263
+ return sql.replace(/\/\*[\s\S]*?\*\//g, "").split(/;\s*$/m).map((s) => s.trim()).filter(Boolean);
264
+ }
265
+ function renderTable(r) {
266
+ const lines = [];
267
+ lines.push(`Cost estimate \u2014 warehouse=${r.warehouseSize} (${r.dbuPerHour} DBU/hour)`);
268
+ lines.push("");
269
+ lines.push(
270
+ ` Statements: ${r.totalStatements} (${r.classifiedStatements} classified, ${r.unknownStatements} unknown).`
271
+ );
272
+ lines.push("");
273
+ const idW = Math.max(20, ...r.perClass.map((c) => c.id.length));
274
+ for (const c of r.perClass) {
275
+ lines.push(
276
+ ` ${c.id.padEnd(idW)} \xD7${String(c.count).padStart(4)} \u2248 ${c.minDbu.toFixed(3)} \u2013 ${c.maxDbu.toFixed(3)} DBU`
277
+ );
278
+ }
279
+ lines.push("");
280
+ lines.push(
281
+ ` TOTAL \u2248 ${r.totals.minDbu.toFixed(3)} \u2013 ${r.totals.maxDbu.toFixed(3)} DBU`
282
+ );
283
+ lines.push(` TOTAL (duration estimate) \u2248 ${r.totals.minSeconds}s \u2013 ${r.totals.maxSeconds}s`);
284
+ lines.push("");
285
+ lines.push(" Note: ranges are heuristic. Actual DBU consumption depends on file count,");
286
+ lines.push(" Z-ORDER state, deletion-vector enablement, and Photon. Treat the upper bound");
287
+ lines.push(" as a pessimistic ceiling, not a likely outcome.");
288
+ return lines.join("\n");
289
+ }
290
+ function renderMarkdown(r) {
291
+ const lines = [];
292
+ lines.push(`# Cost estimate`);
293
+ lines.push("");
294
+ lines.push(`**Warehouse:** ${r.warehouseSize} (${r.dbuPerHour} DBU/hour)`);
295
+ lines.push(
296
+ `**Statements:** ${r.totalStatements} total, ${r.classifiedStatements} classified, ${r.unknownStatements} unknown`
297
+ );
298
+ lines.push("");
299
+ lines.push("## Estimate by statement class");
300
+ lines.push("");
301
+ lines.push("| Class | Count | DBU range |");
302
+ lines.push("|---|---|---|");
303
+ for (const c of r.perClass) {
304
+ lines.push(
305
+ `| \`${c.id}\` (${c.description}) | ${c.count} | ${c.minDbu.toFixed(3)} \u2013 ${c.maxDbu.toFixed(3)} |`
306
+ );
307
+ }
308
+ lines.push("");
309
+ lines.push(
310
+ `**Total:** ${r.totals.minDbu.toFixed(3)} \u2013 ${r.totals.maxDbu.toFixed(3)} DBU (${r.totals.minSeconds}\u2013${r.totals.maxSeconds}s)`
311
+ );
312
+ lines.push("");
313
+ lines.push(
314
+ "> Ranges are heuristic. Actual DBU consumption depends on file count, Z-ORDER state, DV enablement, and Photon."
315
+ );
316
+ return lines.join("\n");
317
+ }
318
+ export {
319
+ costEstimateCommand
320
+ };
321
+ //# sourceMappingURL=cost-estimate-S2MKHT2H.js.map