@ddt-tools/cli 0.2.0 → 0.2.5
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/advise-tests-YNMKVJCD.js +87 -0
- package/dist/advise-tests-YNMKVJCD.js.map +1 -0
- package/dist/ai-NTNPYEKZ.js +86 -0
- package/dist/ai-NTNPYEKZ.js.map +1 -0
- package/dist/anonymize-LERTWUQO.js +139 -0
- package/dist/anonymize-LERTWUQO.js.map +1 -0
- package/dist/approval-GGZGKIU4.js +73 -0
- package/dist/approval-GGZGKIU4.js.map +1 -0
- package/dist/approval-chain-GWJKZHVU.js +118 -0
- package/dist/approval-chain-GWJKZHVU.js.map +1 -0
- package/dist/audit-log-2PH55BU4.js +159 -0
- package/dist/audit-log-2PH55BU4.js.map +1 -0
- package/dist/backlog-QNXGOUF4.js +76 -0
- package/dist/backlog-QNXGOUF4.js.map +1 -0
- package/dist/bisect-W3XKKRWG.js +111 -0
- package/dist/bisect-W3XKKRWG.js.map +1 -0
- package/dist/bookmarks-XVOGXGMC.js +107 -0
- package/dist/bookmarks-XVOGXGMC.js.map +1 -0
- package/dist/branch-S3I2IJGQ.js +103 -0
- package/dist/branch-S3I2IJGQ.js.map +1 -0
- package/dist/build-MP3JQEFO.js +20 -0
- package/dist/build-MP3JQEFO.js.map +1 -0
- package/dist/catalog-3J3NFNXP.js +137 -0
- package/dist/catalog-3J3NFNXP.js.map +1 -0
- package/dist/changelog-ZQAH3ULB.js +216 -0
- package/dist/changelog-ZQAH3ULB.js.map +1 -0
- package/dist/chunk-2FT6HXKS.js +55 -0
- package/dist/chunk-2FT6HXKS.js.map +1 -0
- package/dist/chunk-DGUM43GV.js +11 -0
- package/dist/chunk-DGUM43GV.js.map +1 -0
- package/dist/chunk-DL3V7UJ2.js +25 -0
- package/dist/chunk-DL3V7UJ2.js.map +1 -0
- package/dist/chunk-VM2H4LAO.js +15 -0
- package/dist/chunk-VM2H4LAO.js.map +1 -0
- package/dist/chunk-XFXG347C.js +40 -0
- package/dist/chunk-XFXG347C.js.map +1 -0
- package/dist/cli.js +504 -19402
- package/dist/cli.js.map +1 -1
- package/dist/compare-IOEATL6G.js +435 -0
- package/dist/compare-IOEATL6G.js.map +1 -0
- package/dist/compare-profiles-H33CXZPD.js +219 -0
- package/dist/compare-profiles-H33CXZPD.js.map +1 -0
- package/dist/completion-ZSNCQKJ2.js +89 -0
- package/dist/completion-ZSNCQKJ2.js.map +1 -0
- package/dist/connection-CDGVEFUC.js +148 -0
- package/dist/connection-CDGVEFUC.js.map +1 -0
- package/dist/cost-estimate-S2MKHT2H.js +321 -0
- package/dist/cost-estimate-S2MKHT2H.js.map +1 -0
- package/dist/data-compare-46ZI7KHL.js +128 -0
- package/dist/data-compare-46ZI7KHL.js.map +1 -0
- package/dist/data-fit-WGEPLD5S.js +127 -0
- package/dist/data-fit-WGEPLD5S.js.map +1 -0
- package/dist/deploy-status-4H5KJFRC.js +58 -0
- package/dist/deploy-status-4H5KJFRC.js.map +1 -0
- package/dist/design-ILX3ZSWW.js +135 -0
- package/dist/design-ILX3ZSWW.js.map +1 -0
- package/dist/diagnose-WPUL67E4.js +150 -0
- package/dist/diagnose-WPUL67E4.js.map +1 -0
- package/dist/discover-DEO2R5T6.js +78 -0
- package/dist/discover-DEO2R5T6.js.map +1 -0
- package/dist/docs-QNY3MUVO.js +183 -0
- package/dist/docs-QNY3MUVO.js.map +1 -0
- package/dist/drift-FDRNPWQA.js +233 -0
- package/dist/drift-FDRNPWQA.js.map +1 -0
- package/dist/drift-gate-6BWWWMHW.js +103 -0
- package/dist/drift-gate-6BWWWMHW.js.map +1 -0
- package/dist/error-lookup-4R3Y4RBC.js +56 -0
- package/dist/error-lookup-4R3Y4RBC.js.map +1 -0
- package/dist/errorReporting-LX6WT4JH.js +109 -0
- package/dist/errorReporting-LX6WT4JH.js.map +1 -0
- package/dist/exec-JOLH5LPT.js +122 -0
- package/dist/exec-JOLH5LPT.js.map +1 -0
- package/dist/explain-NS26WE2Y.js +189 -0
- package/dist/explain-NS26WE2Y.js.map +1 -0
- package/dist/explorer-GSYYYOAL.js +58 -0
- package/dist/explorer-GSYYYOAL.js.map +1 -0
- package/dist/extract-4LWEZG4O.js +152 -0
- package/dist/extract-4LWEZG4O.js.map +1 -0
- package/dist/features-KQV4OFIZ.js +54 -0
- package/dist/features-KQV4OFIZ.js.map +1 -0
- package/dist/feedback-CBLGXUEG.js +158 -0
- package/dist/feedback-CBLGXUEG.js.map +1 -0
- package/dist/find-SMXRCZ76.js +176 -0
- package/dist/find-SMXRCZ76.js.map +1 -0
- package/dist/format-HMGG6MY3.js +277 -0
- package/dist/format-HMGG6MY3.js.map +1 -0
- package/dist/generate-W7VLBDLI.js +160 -0
- package/dist/generate-W7VLBDLI.js.map +1 -0
- package/dist/graph-YYL5UYCJ.js +168 -0
- package/dist/graph-YYL5UYCJ.js.map +1 -0
- package/dist/history-GDRFP4PG.js +184 -0
- package/dist/history-GDRFP4PG.js.map +1 -0
- package/dist/hosts-DRFZTMIJ.js +45 -0
- package/dist/hosts-DRFZTMIJ.js.map +1 -0
- package/dist/impact-A4NU6CB2.js +63 -0
- package/dist/impact-A4NU6CB2.js.map +1 -0
- package/dist/import-EGOVKTLX.js +29 -0
- package/dist/import-EGOVKTLX.js.map +1 -0
- package/dist/import-script-R5RXPDH6.js +79 -0
- package/dist/import-script-R5RXPDH6.js.map +1 -0
- package/dist/index.cjs +11 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +8 -2
- package/dist/index.js.map +1 -1
- package/dist/init-EAOGNGXI.js +54 -0
- package/dist/init-EAOGNGXI.js.map +1 -0
- package/dist/install-hooks-G3Y5LVXK.js +109 -0
- package/dist/install-hooks-G3Y5LVXK.js.map +1 -0
- package/dist/license-Z5YSC7XQ.js +43 -0
- package/dist/license-Z5YSC7XQ.js.map +1 -0
- package/dist/lineage-C5CGVP36.js +555 -0
- package/dist/lineage-C5CGVP36.js.map +1 -0
- package/dist/lint-AQFPZ3WG.js +144 -0
- package/dist/lint-AQFPZ3WG.js.map +1 -0
- package/dist/mcp-6ZXOAF7S.js +343 -0
- package/dist/mcp-6ZXOAF7S.js.map +1 -0
- package/dist/migrate-from-dbt-K4ELOWUD.js +156 -0
- package/dist/migrate-from-dbt-K4ELOWUD.js.map +1 -0
- package/dist/migrate-platform-E7VZFPO5.js +91 -0
- package/dist/migrate-platform-E7VZFPO5.js.map +1 -0
- package/dist/optimize-WUJ5ZN5Y.js +109 -0
- package/dist/optimize-WUJ5ZN5Y.js.map +1 -0
- package/dist/perf-UULZSREY.js +200 -0
- package/dist/perf-UULZSREY.js.map +1 -0
- package/dist/pii-QHU32VML.js +146 -0
- package/dist/pii-QHU32VML.js.map +1 -0
- package/dist/pilot-BR6GVK32.js +29 -0
- package/dist/pilot-BR6GVK32.js.map +1 -0
- package/dist/pr-comment-2FOA3EXG.js +81 -0
- package/dist/pr-comment-2FOA3EXG.js.map +1 -0
- package/dist/preview-XNY422OU.js +46 -0
- package/dist/preview-XNY422OU.js.map +1 -0
- package/dist/profile-SQTBNKYS.js +98 -0
- package/dist/profile-SQTBNKYS.js.map +1 -0
- package/dist/promote-FSGUPIPD.js +417 -0
- package/dist/promote-FSGUPIPD.js.map +1 -0
- package/dist/publish-HLP3XHM5.js +766 -0
- package/dist/publish-HLP3XHM5.js.map +1 -0
- package/dist/purge-Y5IOTXKA.js +56 -0
- package/dist/purge-Y5IOTXKA.js.map +1 -0
- package/dist/query-log-SDDGMJLJ.js +112 -0
- package/dist/query-log-SDDGMJLJ.js.map +1 -0
- package/dist/refactor-TC7S43F2.js +5809 -0
- package/dist/refactor-TC7S43F2.js.map +1 -0
- package/dist/refresh-MDJYOYV5.js +39 -0
- package/dist/refresh-MDJYOYV5.js.map +1 -0
- package/dist/replay-E4664A5K.js +118 -0
- package/dist/replay-E4664A5K.js.map +1 -0
- package/dist/revert-QWQWCJJB.js +111 -0
- package/dist/revert-QWQWCJJB.js.map +1 -0
- package/dist/review-7CAVLD67.js +164 -0
- package/dist/review-7CAVLD67.js.map +1 -0
- package/dist/rollback-suggest-C6D5YFCA.js +79 -0
- package/dist/rollback-suggest-C6D5YFCA.js.map +1 -0
- package/dist/safer-alternative-QR4QEFUV.js +84 -0
- package/dist/safer-alternative-QR4QEFUV.js.map +1 -0
- package/dist/safety-OFWUFLK4.js +165 -0
- package/dist/safety-OFWUFLK4.js.map +1 -0
- package/dist/savings-MEBE4TXI.js +95 -0
- package/dist/savings-MEBE4TXI.js.map +1 -0
- package/dist/scan-secrets-XCUBMLHL.js +54 -0
- package/dist/scan-secrets-XCUBMLHL.js.map +1 -0
- package/dist/schema-7JZIG6QR.js +447 -0
- package/dist/schema-7JZIG6QR.js.map +1 -0
- package/dist/script-BMYVBHFR.js +167 -0
- package/dist/script-BMYVBHFR.js.map +1 -0
- package/dist/search-TA3C3AZT.js +151 -0
- package/dist/search-TA3C3AZT.js.map +1 -0
- package/dist/seed-W4Q3L2IU.js +101 -0
- package/dist/seed-W4Q3L2IU.js.map +1 -0
- package/dist/sketch-6B2V6FJV.js +83 -0
- package/dist/sketch-6B2V6FJV.js.map +1 -0
- package/dist/snapshot-YMVS322L.js +171 -0
- package/dist/snapshot-YMVS322L.js.map +1 -0
- package/dist/snippets-EVTN63OU.js +74 -0
- package/dist/snippets-EVTN63OU.js.map +1 -0
- package/dist/standards-FGJW3CQL.js +238 -0
- package/dist/standards-FGJW3CQL.js.map +1 -0
- package/dist/suggest-V3LVIFZ5.js +44 -0
- package/dist/suggest-V3LVIFZ5.js.map +1 -0
- package/dist/suggest-constraints-EX2FCWOQ.js +154 -0
- package/dist/suggest-constraints-EX2FCWOQ.js.map +1 -0
- package/dist/suite-YTQ3CNX5.js +85 -0
- package/dist/suite-YTQ3CNX5.js.map +1 -0
- package/dist/telemetry-KOIY3NEQ.js +90 -0
- package/dist/telemetry-KOIY3NEQ.js.map +1 -0
- package/dist/template-MUJ6X6LN.js +396 -0
- package/dist/template-MUJ6X6LN.js.map +1 -0
- package/dist/test-XFSQHR2S.js +169 -0
- package/dist/test-XFSQHR2S.js.map +1 -0
- package/dist/trial-GFTGYCR3.js +31 -0
- package/dist/trial-GFTGYCR3.js.map +1 -0
- package/dist/validate-LFDEZFFH.js +107 -0
- package/dist/validate-LFDEZFFH.js.map +1 -0
- package/dist/verify-KRDYOJCR.js +76 -0
- package/dist/verify-KRDYOJCR.js.map +1 -0
- package/dist/watch-FSG23RR3.js +80 -0
- package/dist/watch-FSG23RR3.js.map +1 -0
- package/dist/xcompare-U4TXTTIR.js +87 -0
- package/dist/xcompare-U4TXTTIR.js.map +1 -0
- package/package.json +2 -2
- package/dist/cli.cjs +0 -19298
- package/dist/cli.cjs.map +0 -1
- package/dist/cli.d.cts +0 -1
- package/dist/cli.d.ts +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/mcp.ts"],"sourcesContent":["/**\n * `ddt mcp` — Model Context Protocol server (stdio transport).\n *\n * Exposes DDT's read-only operations as MCP tools so AI agents (Claude\n * Desktop, Cursor, Continue, Claude Code, MCP-aware IDE plugins) can\n * call them. Speaks JSON-RPC 2.0 over newline-delimited JSON on stdio.\n *\n * To wire into Claude Desktop / Cursor / etc., add to the MCP config:\n *\n * {\n * \"mcpServers\": {\n * \"ddt\": { \"command\": \"ddt\", \"args\": [\"mcp\"] }\n * }\n * }\n *\n * Tools exposed:\n * - ddt.validate — schema check on a .ddtproj\n * - ddt.compare — pac↔pac diff (offline)\n * - ddt.docs — generate HTML schema docs\n * - ddt.erd — generate Mermaid ER diagram (Markdown)\n * - ddt.safety_assess — classify a diff's safety findings\n * - ddt.narrate_diff — structured payload (diff + safety + prompt\n * scaffold) so the calling agent's LLM can\n * generate a plain-English narration.\n * - ddt.detect_pii — scan a project/pac for likely PII columns.\n * - ddt.suggest_safer — saferAlternatives entries for every\n * UNRECOVERABLE / DESTRUCTIVE finding.\n * - ddt.feature_search — search the DDT feature catalog by keyword.\n *\n * Live-network tools (extract, publish) are deliberately NOT exposed\n * — they require profile credentials and we'd rather an agent not act\n * on those without explicit user invocation.\n */\nimport path from 'node:path';\nimport { Command } from 'commander';\nimport {\n CompareEngine,\n PacSource,\n renderDocsReport,\n renderErdMarkdown,\n loadProject,\n parseProjectModel,\n discoverObjectFiles,\n pac,\n pii,\n safety,\n features,\n type DatabricksObject,\n} from '@ddt-tools/core';\n\ninterface JsonRpcRequest {\n jsonrpc: '2.0';\n id?: number | string | null;\n method: string;\n params?: unknown;\n}\n\ninterface JsonRpcResponse {\n jsonrpc: '2.0';\n id: number | string | null;\n result?: unknown;\n error?: { code: number; message: string; data?: unknown };\n}\n\nconst SERVER_NAME = 'ddt';\nconst SERVER_VERSION = '0.2.5';\nconst PROTOCOL_VERSION = '2024-11-05';\n\ninterface ToolDef {\n name: string;\n description: string;\n inputSchema: Record<string, unknown>;\n handler: (args: Record<string, unknown>) => Promise<string>;\n}\n\nconst TOOLS: ToolDef[] = [\n {\n name: 'ddt.validate',\n description:\n 'Validate a .ddtproj file: parse JSON, check schema with zod, list object files. Returns a JSON summary of project structure.',\n inputSchema: {\n type: 'object',\n properties: {\n projectPath: { type: 'string', description: 'Absolute path to a .ddtproj file.' },\n },\n required: ['projectPath'],\n },\n handler: async (args) => {\n const projectPath = path.resolve(String(args.projectPath));\n const loaded = await loadProject(projectPath);\n const files = await discoverObjectFiles(loaded);\n return JSON.stringify(\n {\n ok: true,\n name: loaded.project.name,\n version: loaded.project.version,\n scope: loaded.project.scope,\n objectFileCount: files.length,\n profiles: Object.keys(loaded.project.deploymentProfiles ?? {}),\n },\n null,\n 2,\n );\n },\n },\n {\n name: 'ddt.compare',\n description:\n 'Compare two .ddtpac files (offline). Returns the diff JSON: added / removed / modified objects, with per-object field changes.',\n inputSchema: {\n type: 'object',\n properties: {\n sourcePac: { type: 'string', description: 'Absolute path to the source .ddtpac.' },\n targetPac: { type: 'string', description: 'Absolute path to the target .ddtpac.' },\n },\n required: ['sourcePac', 'targetPac'],\n },\n handler: async (args) => {\n const source = new PacSource(path.resolve(String(args.sourcePac)));\n const target = new PacSource(path.resolve(String(args.targetPac)));\n const engine = new CompareEngine();\n const result = await engine.compare(source, target);\n return JSON.stringify(result, null, 2);\n },\n },\n {\n name: 'ddt.docs',\n description:\n 'Generate self-contained HTML schema documentation from a .ddtproj or .ddtpac. Returns the HTML body as a string.',\n inputSchema: {\n type: 'object',\n properties: {\n source: { type: 'string', description: 'Absolute path to a .ddtproj or .ddtpac.' },\n title: { type: 'string', description: 'Optional doc title.' },\n },\n required: ['source'],\n },\n handler: async (args) => {\n const model = await loadModel(String(args.source));\n return renderDocsReport(model, {\n title: typeof args.title === 'string' ? args.title : 'Schema docs',\n });\n },\n },\n {\n name: 'ddt.erd',\n description: 'Generate a Mermaid ER diagram (Markdown) from a .ddtproj or .ddtpac.',\n inputSchema: {\n type: 'object',\n properties: {\n source: { type: 'string', description: 'Absolute path to a .ddtproj or .ddtpac.' },\n title: { type: 'string', description: 'Optional diagram title.' },\n },\n required: ['source'],\n },\n handler: async (args) => {\n const model = await loadModel(String(args.source));\n return renderErdMarkdown(\n model,\n typeof args.title === 'string' ? args.title : 'Schema ER diagram',\n );\n },\n },\n {\n name: 'ddt.safety_assess',\n description:\n 'Given two .ddtpac files, run the safety classifier on the diff. Returns UNRECOVERABLE / DESTRUCTIVE / EXPENSIVE / WARNING findings with the gate option each would need.',\n inputSchema: {\n type: 'object',\n properties: {\n sourcePac: { type: 'string', description: 'Source .ddtpac (desired state).' },\n targetPac: { type: 'string', description: 'Target .ddtpac (current state).' },\n },\n required: ['sourcePac', 'targetPac'],\n },\n handler: async (args) => {\n const source = new PacSource(path.resolve(String(args.sourcePac)));\n const target = new PacSource(path.resolve(String(args.targetPac)));\n const engine = new CompareEngine();\n const diff = await engine.compare(source, target);\n const assessment = safety.assess(diff);\n return JSON.stringify(assessment, null, 2);\n },\n },\n {\n name: 'ddt.narrate_diff',\n description:\n \"Build a structured payload (diff summary + per-object changes + safety findings + a system-prompt scaffold) the calling agent's LLM can use to generate a plain-English narration of a schema diff. Returns JSON the agent should compose into its own completion.\",\n inputSchema: {\n type: 'object',\n properties: {\n sourcePac: { type: 'string', description: 'Source .ddtpac (desired state).' },\n targetPac: { type: 'string', description: 'Target .ddtpac (current state).' },\n },\n required: ['sourcePac', 'targetPac'],\n },\n handler: async (args) => {\n const source = new PacSource(path.resolve(String(args.sourcePac)));\n const target = new PacSource(path.resolve(String(args.targetPac)));\n const engine = new CompareEngine();\n const diff = await engine.compare(source, target);\n const assessment = safety.assess(diff);\n const payload = {\n summary: diff.summary,\n objects: diff.objects.map((o) => ({\n kind: o.kind,\n objectType: o.identity.objectType,\n fqn: o.identity.fqn,\n })),\n safety: assessment,\n promptScaffold: {\n system:\n 'You are a senior Databricks UC engineer reviewing a schema migration. ' +\n 'Narrate the change in 2-3 short paragraphs: (1) the intent, ' +\n '(2) the safest order of operations, (3) the risks to watch ' +\n 'for. Be concrete; reference the FQNs.',\n userTemplate: 'Here is the diff JSON and the safety assessment. Generate the narration.',\n },\n };\n return JSON.stringify(payload, null, 2);\n },\n },\n {\n name: 'ddt.detect_pii',\n description:\n 'Scan a project or pac for columns that look like PII (email, phone, ssn, dob, credit-card, national-id, health, ip-address, name, address, generic). Returns each candidate with its category, confidence tier, and the reason the heuristic flagged it.',\n inputSchema: {\n type: 'object',\n properties: {\n source: { type: 'string', description: 'Absolute path to a .ddtproj or .ddtpac.' },\n },\n required: ['source'],\n },\n handler: async (args) => {\n const model = await loadModel(String(args.source));\n const candidates = pii.detectPiiCandidates(model);\n return JSON.stringify({ count: candidates.length, candidates }, null, 2);\n },\n },\n {\n name: 'ddt.suggest_safer',\n description:\n 'For every UNRECOVERABLE / DESTRUCTIVE finding the safety classifier produced on a diff, return the catalog\\'s saferAlternatives entries (e.g. \"instead of DROP TABLE, RENAME with a date suffix and let the retention policy expire it\"). The agent can paste these into a PR comment or CodeLens suggestion.',\n inputSchema: {\n type: 'object',\n properties: {\n sourcePac: { type: 'string', description: 'Source .ddtpac (desired).' },\n targetPac: { type: 'string', description: 'Target .ddtpac (current).' },\n },\n required: ['sourcePac', 'targetPac'],\n },\n handler: async (args) => {\n const source = new PacSource(path.resolve(String(args.sourcePac)));\n const target = new PacSource(path.resolve(String(args.targetPac)));\n const engine = new CompareEngine();\n const diff = await engine.compare(source, target);\n const assessment = safety.assess(diff);\n const dangerous = [...assessment.unrecoverable, ...assessment.destructive];\n const suggestions = dangerous.map((f) => {\n const explanation = safety.explainFinding(f.code);\n return {\n finding: { code: f.code, category: f.category, fqn: f.fqn, reason: f.reason },\n saferAlternatives: explanation?.saferAlternatives ?? [],\n requiredGates: explanation?.requiredGates ?? [],\n };\n });\n return JSON.stringify({ count: suggestions.length, suggestions }, null, 2);\n },\n },\n {\n name: 'ddt.feature_search',\n description:\n 'Search the DDT feature catalog by keyword. Returns features whose id, name, summary, or synonyms match the query. Useful for discovering which tier unlocks a capability or finding the right command for a task.',\n inputSchema: {\n type: 'object',\n properties: {\n query: {\n type: 'string',\n description:\n 'Search term — feature name, keyword, or capability (e.g. \"drift\", \"slice\", \"AI\").',\n },\n },\n required: ['query'],\n },\n handler: async (args) => {\n const q = String(args.query ?? '')\n .toLowerCase()\n .trim();\n const matches = features.DDT_FEATURE_CATALOG.filter(\n (f) =>\n f.id.includes(q) ||\n f.name.toLowerCase().includes(q) ||\n f.summary.toLowerCase().includes(q) ||\n f.synonyms?.some((s) => s.toLowerCase().includes(q)) ||\n f.whenToUse?.toLowerCase().includes(q),\n );\n return JSON.stringify(\n {\n count: matches.length,\n features: matches.map((f) => ({\n id: f.id,\n name: f.name,\n tier: f.tier,\n status: f.status,\n summary: f.summary,\n unlockHow: f.unlockHow,\n useCases: f.useCases,\n relatedFeatures: f.relatedFeatures,\n })),\n },\n null,\n 2,\n );\n },\n },\n];\n\nasync function loadModel(sourcePath: string): Promise<DatabricksObject[]> {\n const abs = path.resolve(sourcePath);\n if (abs.endsWith('.ddtpac')) {\n const pacContents = await pac.readPac(abs);\n return pacContents.model;\n }\n const loaded = await loadProject(abs);\n const parsed = await parseProjectModel(loaded);\n return parsed as DatabricksObject[];\n}\n\nexport function mcpCommand(): Command {\n const cmd = new Command('mcp');\n cmd\n .description(\n 'Start the DDT Model Context Protocol server on stdio (for Claude / Cursor / agents).',\n )\n .action(async () => {\n await runMcpStdio();\n });\n return cmd;\n}\n\nasync function runMcpStdio(): Promise<void> {\n await new Promise<void>((resolve) => {\n let buffer = '';\n process.stdin.setEncoding('utf8');\n process.stdin.on('data', (chunk: string) => {\n buffer += chunk;\n let nl = buffer.indexOf('\\n');\n while (nl !== -1) {\n const line = buffer.slice(0, nl).trim();\n buffer = buffer.slice(nl + 1);\n if (line.length > 0) {\n void handleLine(line);\n }\n nl = buffer.indexOf('\\n');\n }\n });\n process.stdin.on('end', () => resolve());\n process.stdin.on('close', () => resolve());\n });\n}\n\nasync function handleLine(line: string): Promise<void> {\n let req: JsonRpcRequest;\n try {\n req = JSON.parse(line) as JsonRpcRequest;\n } catch (err) {\n send({\n jsonrpc: '2.0',\n id: null,\n error: { code: -32700, message: 'Parse error', data: String(err) },\n });\n return;\n }\n const id = req.id ?? null;\n try {\n const result = await dispatch(req);\n if (id !== null) {\n send({ jsonrpc: '2.0', id, result });\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n send({ jsonrpc: '2.0', id, error: { code: -32603, message } });\n }\n}\n\nasync function dispatch(req: JsonRpcRequest): Promise<unknown> {\n switch (req.method) {\n case 'initialize':\n return {\n protocolVersion: PROTOCOL_VERSION,\n serverInfo: { name: SERVER_NAME, version: SERVER_VERSION },\n capabilities: { tools: {} },\n };\n case 'notifications/initialized':\n return undefined;\n case 'tools/list':\n return {\n tools: TOOLS.map((t) => ({\n name: t.name,\n description: t.description,\n inputSchema: t.inputSchema,\n })),\n };\n case 'tools/call': {\n const params = (req.params ?? {}) as { name?: string; arguments?: Record<string, unknown> };\n const tool = TOOLS.find((t) => t.name === params.name);\n if (!tool) throw new Error(`Unknown tool: ${String(params.name)}`);\n const output = await tool.handler(params.arguments ?? {});\n return { content: [{ type: 'text', text: output }] };\n }\n case 'ping':\n return {};\n default:\n throw new Error(`Method not found: ${req.method}`);\n }\n}\n\nfunction send(msg: JsonRpcResponse): void {\n process.stdout.write(`${JSON.stringify(msg)}\\n`);\n}\n"],"mappings":";;;AAiCA,OAAO,UAAU;AACjB,SAAS,eAAe;AACxB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AAgBP,IAAM,cAAc;AACpB,IAAM,iBAAiB;AACvB,IAAM,mBAAmB;AASzB,IAAM,QAAmB;AAAA,EACvB;AAAA,IACE,MAAM;AAAA,IACN,aACE;AAAA,IACF,aAAa;AAAA,MACX,MAAM;AAAA,MACN,YAAY;AAAA,QACV,aAAa,EAAE,MAAM,UAAU,aAAa,oCAAoC;AAAA,MAClF;AAAA,MACA,UAAU,CAAC,aAAa;AAAA,IAC1B;AAAA,IACA,SAAS,OAAO,SAAS;AACvB,YAAM,cAAc,KAAK,QAAQ,OAAO,KAAK,WAAW,CAAC;AACzD,YAAM,SAAS,MAAM,YAAY,WAAW;AAC5C,YAAM,QAAQ,MAAM,oBAAoB,MAAM;AAC9C,aAAO,KAAK;AAAA,QACV;AAAA,UACE,IAAI;AAAA,UACJ,MAAM,OAAO,QAAQ;AAAA,UACrB,SAAS,OAAO,QAAQ;AAAA,UACxB,OAAO,OAAO,QAAQ;AAAA,UACtB,iBAAiB,MAAM;AAAA,UACvB,UAAU,OAAO,KAAK,OAAO,QAAQ,sBAAsB,CAAC,CAAC;AAAA,QAC/D;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aACE;AAAA,IACF,aAAa;AAAA,MACX,MAAM;AAAA,MACN,YAAY;AAAA,QACV,WAAW,EAAE,MAAM,UAAU,aAAa,uCAAuC;AAAA,QACjF,WAAW,EAAE,MAAM,UAAU,aAAa,uCAAuC;AAAA,MACnF;AAAA,MACA,UAAU,CAAC,aAAa,WAAW;AAAA,IACrC;AAAA,IACA,SAAS,OAAO,SAAS;AACvB,YAAM,SAAS,IAAI,UAAU,KAAK,QAAQ,OAAO,KAAK,SAAS,CAAC,CAAC;AACjE,YAAM,SAAS,IAAI,UAAU,KAAK,QAAQ,OAAO,KAAK,SAAS,CAAC,CAAC;AACjE,YAAM,SAAS,IAAI,cAAc;AACjC,YAAM,SAAS,MAAM,OAAO,QAAQ,QAAQ,MAAM;AAClD,aAAO,KAAK,UAAU,QAAQ,MAAM,CAAC;AAAA,IACvC;AAAA,EACF;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aACE;AAAA,IACF,aAAa;AAAA,MACX,MAAM;AAAA,MACN,YAAY;AAAA,QACV,QAAQ,EAAE,MAAM,UAAU,aAAa,0CAA0C;AAAA,QACjF,OAAO,EAAE,MAAM,UAAU,aAAa,sBAAsB;AAAA,MAC9D;AAAA,MACA,UAAU,CAAC,QAAQ;AAAA,IACrB;AAAA,IACA,SAAS,OAAO,SAAS;AACvB,YAAM,QAAQ,MAAM,UAAU,OAAO,KAAK,MAAM,CAAC;AACjD,aAAO,iBAAiB,OAAO;AAAA,QAC7B,OAAO,OAAO,KAAK,UAAU,WAAW,KAAK,QAAQ;AAAA,MACvD,CAAC;AAAA,IACH;AAAA,EACF;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,aAAa;AAAA,MACX,MAAM;AAAA,MACN,YAAY;AAAA,QACV,QAAQ,EAAE,MAAM,UAAU,aAAa,0CAA0C;AAAA,QACjF,OAAO,EAAE,MAAM,UAAU,aAAa,0BAA0B;AAAA,MAClE;AAAA,MACA,UAAU,CAAC,QAAQ;AAAA,IACrB;AAAA,IACA,SAAS,OAAO,SAAS;AACvB,YAAM,QAAQ,MAAM,UAAU,OAAO,KAAK,MAAM,CAAC;AACjD,aAAO;AAAA,QACL;AAAA,QACA,OAAO,KAAK,UAAU,WAAW,KAAK,QAAQ;AAAA,MAChD;AAAA,IACF;AAAA,EACF;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aACE;AAAA,IACF,aAAa;AAAA,MACX,MAAM;AAAA,MACN,YAAY;AAAA,QACV,WAAW,EAAE,MAAM,UAAU,aAAa,kCAAkC;AAAA,QAC5E,WAAW,EAAE,MAAM,UAAU,aAAa,kCAAkC;AAAA,MAC9E;AAAA,MACA,UAAU,CAAC,aAAa,WAAW;AAAA,IACrC;AAAA,IACA,SAAS,OAAO,SAAS;AACvB,YAAM,SAAS,IAAI,UAAU,KAAK,QAAQ,OAAO,KAAK,SAAS,CAAC,CAAC;AACjE,YAAM,SAAS,IAAI,UAAU,KAAK,QAAQ,OAAO,KAAK,SAAS,CAAC,CAAC;AACjE,YAAM,SAAS,IAAI,cAAc;AACjC,YAAM,OAAO,MAAM,OAAO,QAAQ,QAAQ,MAAM;AAChD,YAAM,aAAa,OAAO,OAAO,IAAI;AACrC,aAAO,KAAK,UAAU,YAAY,MAAM,CAAC;AAAA,IAC3C;AAAA,EACF;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aACE;AAAA,IACF,aAAa;AAAA,MACX,MAAM;AAAA,MACN,YAAY;AAAA,QACV,WAAW,EAAE,MAAM,UAAU,aAAa,kCAAkC;AAAA,QAC5E,WAAW,EAAE,MAAM,UAAU,aAAa,kCAAkC;AAAA,MAC9E;AAAA,MACA,UAAU,CAAC,aAAa,WAAW;AAAA,IACrC;AAAA,IACA,SAAS,OAAO,SAAS;AACvB,YAAM,SAAS,IAAI,UAAU,KAAK,QAAQ,OAAO,KAAK,SAAS,CAAC,CAAC;AACjE,YAAM,SAAS,IAAI,UAAU,KAAK,QAAQ,OAAO,KAAK,SAAS,CAAC,CAAC;AACjE,YAAM,SAAS,IAAI,cAAc;AACjC,YAAM,OAAO,MAAM,OAAO,QAAQ,QAAQ,MAAM;AAChD,YAAM,aAAa,OAAO,OAAO,IAAI;AACrC,YAAM,UAAU;AAAA,QACd,SAAS,KAAK;AAAA,QACd,SAAS,KAAK,QAAQ,IAAI,CAAC,OAAO;AAAA,UAChC,MAAM,EAAE;AAAA,UACR,YAAY,EAAE,SAAS;AAAA,UACvB,KAAK,EAAE,SAAS;AAAA,QAClB,EAAE;AAAA,QACF,QAAQ;AAAA,QACR,gBAAgB;AAAA,UACd,QACE;AAAA,UAIF,cAAc;AAAA,QAChB;AAAA,MACF;AACA,aAAO,KAAK,UAAU,SAAS,MAAM,CAAC;AAAA,IACxC;AAAA,EACF;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aACE;AAAA,IACF,aAAa;AAAA,MACX,MAAM;AAAA,MACN,YAAY;AAAA,QACV,QAAQ,EAAE,MAAM,UAAU,aAAa,0CAA0C;AAAA,MACnF;AAAA,MACA,UAAU,CAAC,QAAQ;AAAA,IACrB;AAAA,IACA,SAAS,OAAO,SAAS;AACvB,YAAM,QAAQ,MAAM,UAAU,OAAO,KAAK,MAAM,CAAC;AACjD,YAAM,aAAa,IAAI,oBAAoB,KAAK;AAChD,aAAO,KAAK,UAAU,EAAE,OAAO,WAAW,QAAQ,WAAW,GAAG,MAAM,CAAC;AAAA,IACzE;AAAA,EACF;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aACE;AAAA,IACF,aAAa;AAAA,MACX,MAAM;AAAA,MACN,YAAY;AAAA,QACV,WAAW,EAAE,MAAM,UAAU,aAAa,4BAA4B;AAAA,QACtE,WAAW,EAAE,MAAM,UAAU,aAAa,4BAA4B;AAAA,MACxE;AAAA,MACA,UAAU,CAAC,aAAa,WAAW;AAAA,IACrC;AAAA,IACA,SAAS,OAAO,SAAS;AACvB,YAAM,SAAS,IAAI,UAAU,KAAK,QAAQ,OAAO,KAAK,SAAS,CAAC,CAAC;AACjE,YAAM,SAAS,IAAI,UAAU,KAAK,QAAQ,OAAO,KAAK,SAAS,CAAC,CAAC;AACjE,YAAM,SAAS,IAAI,cAAc;AACjC,YAAM,OAAO,MAAM,OAAO,QAAQ,QAAQ,MAAM;AAChD,YAAM,aAAa,OAAO,OAAO,IAAI;AACrC,YAAM,YAAY,CAAC,GAAG,WAAW,eAAe,GAAG,WAAW,WAAW;AACzE,YAAM,cAAc,UAAU,IAAI,CAAC,MAAM;AACvC,cAAM,cAAc,OAAO,eAAe,EAAE,IAAI;AAChD,eAAO;AAAA,UACL,SAAS,EAAE,MAAM,EAAE,MAAM,UAAU,EAAE,UAAU,KAAK,EAAE,KAAK,QAAQ,EAAE,OAAO;AAAA,UAC5E,mBAAmB,aAAa,qBAAqB,CAAC;AAAA,UACtD,eAAe,aAAa,iBAAiB,CAAC;AAAA,QAChD;AAAA,MACF,CAAC;AACD,aAAO,KAAK,UAAU,EAAE,OAAO,YAAY,QAAQ,YAAY,GAAG,MAAM,CAAC;AAAA,IAC3E;AAAA,EACF;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aACE;AAAA,IACF,aAAa;AAAA,MACX,MAAM;AAAA,MACN,YAAY;AAAA,QACV,OAAO;AAAA,UACL,MAAM;AAAA,UACN,aACE;AAAA,QACJ;AAAA,MACF;AAAA,MACA,UAAU,CAAC,OAAO;AAAA,IACpB;AAAA,IACA,SAAS,OAAO,SAAS;AACvB,YAAM,IAAI,OAAO,KAAK,SAAS,EAAE,EAC9B,YAAY,EACZ,KAAK;AACR,YAAM,UAAU,SAAS,oBAAoB;AAAA,QAC3C,CAAC,MACC,EAAE,GAAG,SAAS,CAAC,KACf,EAAE,KAAK,YAAY,EAAE,SAAS,CAAC,KAC/B,EAAE,QAAQ,YAAY,EAAE,SAAS,CAAC,KAClC,EAAE,UAAU,KAAK,CAAC,MAAM,EAAE,YAAY,EAAE,SAAS,CAAC,CAAC,KACnD,EAAE,WAAW,YAAY,EAAE,SAAS,CAAC;AAAA,MACzC;AACA,aAAO,KAAK;AAAA,QACV;AAAA,UACE,OAAO,QAAQ;AAAA,UACf,UAAU,QAAQ,IAAI,CAAC,OAAO;AAAA,YAC5B,IAAI,EAAE;AAAA,YACN,MAAM,EAAE;AAAA,YACR,MAAM,EAAE;AAAA,YACR,QAAQ,EAAE;AAAA,YACV,SAAS,EAAE;AAAA,YACX,WAAW,EAAE;AAAA,YACb,UAAU,EAAE;AAAA,YACZ,iBAAiB,EAAE;AAAA,UACrB,EAAE;AAAA,QACJ;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;AAEA,eAAe,UAAU,YAAiD;AACxE,QAAM,MAAM,KAAK,QAAQ,UAAU;AACnC,MAAI,IAAI,SAAS,SAAS,GAAG;AAC3B,UAAM,cAAc,MAAM,IAAI,QAAQ,GAAG;AACzC,WAAO,YAAY;AAAA,EACrB;AACA,QAAM,SAAS,MAAM,YAAY,GAAG;AACpC,QAAM,SAAS,MAAM,kBAAkB,MAAM;AAC7C,SAAO;AACT;AAEO,SAAS,aAAsB;AACpC,QAAM,MAAM,IAAI,QAAQ,KAAK;AAC7B,MACG;AAAA,IACC;AAAA,EACF,EACC,OAAO,YAAY;AAClB,UAAM,YAAY;AAAA,EACpB,CAAC;AACH,SAAO;AACT;AAEA,eAAe,cAA6B;AAC1C,QAAM,IAAI,QAAc,CAAC,YAAY;AACnC,QAAI,SAAS;AACb,YAAQ,MAAM,YAAY,MAAM;AAChC,YAAQ,MAAM,GAAG,QAAQ,CAAC,UAAkB;AAC1C,gBAAU;AACV,UAAI,KAAK,OAAO,QAAQ,IAAI;AAC5B,aAAO,OAAO,IAAI;AAChB,cAAM,OAAO,OAAO,MAAM,GAAG,EAAE,EAAE,KAAK;AACtC,iBAAS,OAAO,MAAM,KAAK,CAAC;AAC5B,YAAI,KAAK,SAAS,GAAG;AACnB,eAAK,WAAW,IAAI;AAAA,QACtB;AACA,aAAK,OAAO,QAAQ,IAAI;AAAA,MAC1B;AAAA,IACF,CAAC;AACD,YAAQ,MAAM,GAAG,OAAO,MAAM,QAAQ,CAAC;AACvC,YAAQ,MAAM,GAAG,SAAS,MAAM,QAAQ,CAAC;AAAA,EAC3C,CAAC;AACH;AAEA,eAAe,WAAW,MAA6B;AACrD,MAAI;AACJ,MAAI;AACF,UAAM,KAAK,MAAM,IAAI;AAAA,EACvB,SAAS,KAAK;AACZ,SAAK;AAAA,MACH,SAAS;AAAA,MACT,IAAI;AAAA,MACJ,OAAO,EAAE,MAAM,QAAQ,SAAS,eAAe,MAAM,OAAO,GAAG,EAAE;AAAA,IACnE,CAAC;AACD;AAAA,EACF;AACA,QAAM,KAAK,IAAI,MAAM;AACrB,MAAI;AACF,UAAM,SAAS,MAAM,SAAS,GAAG;AACjC,QAAI,OAAO,MAAM;AACf,WAAK,EAAE,SAAS,OAAO,IAAI,OAAO,CAAC;AAAA,IACrC;AAAA,EACF,SAAS,KAAK;AACZ,UAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC/D,SAAK,EAAE,SAAS,OAAO,IAAI,OAAO,EAAE,MAAM,QAAQ,QAAQ,EAAE,CAAC;AAAA,EAC/D;AACF;AAEA,eAAe,SAAS,KAAuC;AAC7D,UAAQ,IAAI,QAAQ;AAAA,IAClB,KAAK;AACH,aAAO;AAAA,QACL,iBAAiB;AAAA,QACjB,YAAY,EAAE,MAAM,aAAa,SAAS,eAAe;AAAA,QACzD,cAAc,EAAE,OAAO,CAAC,EAAE;AAAA,MAC5B;AAAA,IACF,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,QACL,OAAO,MAAM,IAAI,CAAC,OAAO;AAAA,UACvB,MAAM,EAAE;AAAA,UACR,aAAa,EAAE;AAAA,UACf,aAAa,EAAE;AAAA,QACjB,EAAE;AAAA,MACJ;AAAA,IACF,KAAK,cAAc;AACjB,YAAM,SAAU,IAAI,UAAU,CAAC;AAC/B,YAAM,OAAO,MAAM,KAAK,CAAC,MAAM,EAAE,SAAS,OAAO,IAAI;AACrD,UAAI,CAAC,KAAM,OAAM,IAAI,MAAM,iBAAiB,OAAO,OAAO,IAAI,CAAC,EAAE;AACjE,YAAM,SAAS,MAAM,KAAK,QAAQ,OAAO,aAAa,CAAC,CAAC;AACxD,aAAO,EAAE,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,OAAO,CAAC,EAAE;AAAA,IACrD;AAAA,IACA,KAAK;AACH,aAAO,CAAC;AAAA,IACV;AACE,YAAM,IAAI,MAAM,qBAAqB,IAAI,MAAM,EAAE;AAAA,EACrD;AACF;AAEA,SAAS,KAAK,KAA4B;AACxC,UAAQ,OAAO,MAAM,GAAG,KAAK,UAAU,GAAG,CAAC;AAAA,CAAI;AACjD;","names":[]}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import {
|
|
2
|
+
logger
|
|
3
|
+
} from "./chunk-VM2H4LAO.js";
|
|
4
|
+
import "./chunk-DGUM43GV.js";
|
|
5
|
+
|
|
6
|
+
// src/commands/migrate-from-dbt.ts
|
|
7
|
+
import path from "path";
|
|
8
|
+
import fs from "fs/promises";
|
|
9
|
+
import { Command } from "commander";
|
|
10
|
+
import {
|
|
11
|
+
parseDbtProjectYaml,
|
|
12
|
+
parseDbtSourcesYaml,
|
|
13
|
+
migrateFromDbtManifest
|
|
14
|
+
} from "@ddt-tools/core/migrate";
|
|
15
|
+
async function readJsonFile(filePath) {
|
|
16
|
+
const raw = await fs.readFile(filePath, "utf-8");
|
|
17
|
+
return JSON.parse(raw);
|
|
18
|
+
}
|
|
19
|
+
async function readTextFile(filePath) {
|
|
20
|
+
try {
|
|
21
|
+
return await fs.readFile(filePath, "utf-8");
|
|
22
|
+
} catch (err) {
|
|
23
|
+
if (err.code === "ENOENT") return void 0;
|
|
24
|
+
throw err;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
async function discoverDbtFiles(rootPath, manifestPathOverride) {
|
|
28
|
+
const projectYamlPath = path.join(rootPath, "dbt_project.yml");
|
|
29
|
+
const projectYamlText = await readTextFile(projectYamlPath);
|
|
30
|
+
if (projectYamlText === void 0) {
|
|
31
|
+
throw new Error(`dbt_project.yml not found at ${projectYamlPath}`);
|
|
32
|
+
}
|
|
33
|
+
const manifestPath = manifestPathOverride ?? path.join(rootPath, "target", "manifest.json");
|
|
34
|
+
const manifestText = await readTextFile(manifestPath);
|
|
35
|
+
if (manifestText === void 0) {
|
|
36
|
+
throw new Error(
|
|
37
|
+
`manifest.json not found at ${manifestPath} \u2014 run "dbt compile" first or pass --manifest`
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
const modelsRoot = path.join(rootPath, "models");
|
|
41
|
+
const sourcesYamlTexts = [];
|
|
42
|
+
try {
|
|
43
|
+
await walkForSourcesYaml(modelsRoot, sourcesYamlTexts);
|
|
44
|
+
} catch {
|
|
45
|
+
}
|
|
46
|
+
return { projectYamlText, projectYamlPath, manifestText, manifestPath, sourcesYamlTexts };
|
|
47
|
+
}
|
|
48
|
+
async function walkForSourcesYaml(dirPath, out) {
|
|
49
|
+
const entries = await fs.readdir(dirPath, { withFileTypes: true });
|
|
50
|
+
for (const entry of entries) {
|
|
51
|
+
const full = path.join(dirPath, entry.name);
|
|
52
|
+
if (entry.isDirectory()) {
|
|
53
|
+
await walkForSourcesYaml(full, out);
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
if (entry.name === "sources.yml" || entry.name === "sources.yaml" || entry.name === "_sources.yml" || entry.name === "_sources.yaml") {
|
|
57
|
+
const text = await fs.readFile(full, "utf-8");
|
|
58
|
+
out.push({ path: full, text });
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function buildMigrateFromDbtReport(inputs) {
|
|
63
|
+
const projectInfo = parseDbtProjectYaml(inputs.projectYamlText);
|
|
64
|
+
let sourcesInfo;
|
|
65
|
+
if (inputs.sourcesYamlTexts.length > 0) {
|
|
66
|
+
const combined = { sources: [], warnings: [] };
|
|
67
|
+
for (const f of inputs.sourcesYamlTexts) {
|
|
68
|
+
const parsed = parseDbtSourcesYaml(f.text);
|
|
69
|
+
combined.sources.push(...parsed.sources);
|
|
70
|
+
combined.warnings.push(...parsed.warnings.map((w) => `${f.path}: ${w}`));
|
|
71
|
+
}
|
|
72
|
+
sourcesInfo = combined;
|
|
73
|
+
}
|
|
74
|
+
const manifest = JSON.parse(inputs.manifestText);
|
|
75
|
+
const inferredDefault = projectInfo.perPath.find((p) => p.database || p.schema);
|
|
76
|
+
const migOpts = {
|
|
77
|
+
identifierCase: inputs.options?.identifierCase ?? "preserve",
|
|
78
|
+
...inputs.options?.defaultDatabase ?? inferredDefault?.database ? { defaultDatabase: inputs.options?.defaultDatabase ?? inferredDefault?.database } : {},
|
|
79
|
+
...inputs.options?.defaultSchema ?? inferredDefault?.schema ? { defaultSchema: inputs.options?.defaultSchema ?? inferredDefault?.schema } : {}
|
|
80
|
+
};
|
|
81
|
+
const migration = migrateFromDbtManifest(manifest, migOpts);
|
|
82
|
+
return {
|
|
83
|
+
projectInfo,
|
|
84
|
+
...sourcesInfo ? { sourcesInfo } : {},
|
|
85
|
+
migration,
|
|
86
|
+
inputs: {
|
|
87
|
+
projectPath: inputs.projectYamlPath,
|
|
88
|
+
manifestPath: inputs.manifestPath,
|
|
89
|
+
sourcesPaths: inputs.sourcesYamlTexts.map((f) => f.path)
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
function migrateFromDbtCommand() {
|
|
94
|
+
const cmd = new Command("migrate");
|
|
95
|
+
cmd.description("Migrate from another tool to SDT (`from-dbt` subcommand only in v1).");
|
|
96
|
+
cmd.command("from-dbt").description(
|
|
97
|
+
"Read a dbt project (dbt_project.yml + target/manifest.json + sources.yml files) and emit a structured migration report (`DbtMigrationResult`) to stdout or --output."
|
|
98
|
+
).argument(
|
|
99
|
+
"<project-path>",
|
|
100
|
+
"Path to the dbt project root (the directory containing dbt_project.yml)."
|
|
101
|
+
).option(
|
|
102
|
+
"--manifest <path>",
|
|
103
|
+
"Override the manifest path. Default: <project-path>/target/manifest.json."
|
|
104
|
+
).option("--output <path>", "Write JSON report to this file. Default: stdout.").option("--identifier-case <case>", "preserve | upper | lower. Default: preserve.", "preserve").option(
|
|
105
|
+
"--default-database <name>",
|
|
106
|
+
"Fallback database when the manifest entry doesn't pin one."
|
|
107
|
+
).option("--default-schema <name>", "Fallback schema when the manifest entry doesn't pin one.").action(
|
|
108
|
+
async (projectPath, options) => {
|
|
109
|
+
const resolved = path.resolve(projectPath);
|
|
110
|
+
let discovered;
|
|
111
|
+
try {
|
|
112
|
+
discovered = await discoverDbtFiles(resolved, options.manifest);
|
|
113
|
+
} catch (err) {
|
|
114
|
+
logger.error(`migrate from-dbt: ${err.message}`);
|
|
115
|
+
process.exitCode = 1;
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
const idCase = options.identifierCase ?? "preserve";
|
|
119
|
+
if (!["preserve", "upper", "lower"].includes(idCase)) {
|
|
120
|
+
logger.error(
|
|
121
|
+
`--identifier-case must be one of preserve | upper | lower (got "${idCase}")`
|
|
122
|
+
);
|
|
123
|
+
process.exitCode = 1;
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
const report = buildMigrateFromDbtReport({
|
|
127
|
+
projectYamlText: discovered.projectYamlText,
|
|
128
|
+
projectYamlPath: discovered.projectYamlPath,
|
|
129
|
+
manifestText: discovered.manifestText,
|
|
130
|
+
manifestPath: discovered.manifestPath,
|
|
131
|
+
sourcesYamlTexts: discovered.sourcesYamlTexts,
|
|
132
|
+
options: {
|
|
133
|
+
identifierCase: idCase,
|
|
134
|
+
...options.defaultDatabase ? { defaultDatabase: options.defaultDatabase } : {},
|
|
135
|
+
...options.defaultSchema ? { defaultSchema: options.defaultSchema } : {}
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
const json = JSON.stringify(report, null, 2);
|
|
139
|
+
if (options.output) {
|
|
140
|
+
await fs.writeFile(options.output, json);
|
|
141
|
+
logger.info(
|
|
142
|
+
`migrate from-dbt: wrote report to ${options.output} \u2014 ${report.migration.models.length} models / ${report.migration.sources.length} sources / ${report.migration.diagnostics.length} diagnostics`
|
|
143
|
+
);
|
|
144
|
+
} else {
|
|
145
|
+
process.stdout.write(json + "\n");
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
);
|
|
149
|
+
return cmd;
|
|
150
|
+
}
|
|
151
|
+
export {
|
|
152
|
+
readJsonFile as _readJsonFile_internalForTests,
|
|
153
|
+
buildMigrateFromDbtReport,
|
|
154
|
+
migrateFromDbtCommand
|
|
155
|
+
};
|
|
156
|
+
//# sourceMappingURL=migrate-from-dbt-K4ELOWUD.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/migrate-from-dbt.ts"],"sourcesContent":["/**\n * DBT.4-followup CLI — `ddt migrate from-dbt <project-path>`\n *\n * Reads a dbt project from disk and emits a structured migration report\n * (JSON to stdout or --output file). Composes the three already-shipped\n * substrates (byte-aligned with SDT side):\n * - `parseDbtProjectYaml` (DBT.4-followup parser shipped iter 25)\n * - `parseDbtSourcesYaml` (DBT.4-followup parser shipped iter 25)\n * - `migrateFromDbtManifest` (DBT.4 substrate shipped iter 18 Opus)\n *\n * The CLI does NOT (yet) attempt to write a full `.ddtproj` + per-object\n * `.sql` files — that's a follow-up step. The current surface produces\n * the structured `DbtMigrationResult` shape an operator can pipe into\n * downstream automation or review by hand.\n *\n * NOT in scope:\n * - Writing `.ddtproj` + `.sql` skeleton on disk (separate followup).\n * - DBT.6 macro translation pass on the compiled SQL (caller can run\n * `translateDbtMacros` separately; we surface the unresolved SQL).\n *\n * Byte-aligned with `Snowflake/packages/cli/src/commands/migrate-from-dbt.ts`.\n */\nimport path from 'node:path';\nimport fs from 'node:fs/promises';\nimport { Command } from 'commander';\nimport {\n parseDbtProjectYaml,\n parseDbtSourcesYaml,\n migrateFromDbtManifest,\n type DbtManifestInput,\n type DbtMigrationOptions,\n type DbtMigrationResult,\n type DbtProjectInfo,\n type DbtSourcesInfo,\n type IdentifierCase,\n} from '@ddt-tools/core/migrate';\nimport { logger } from '../util/logger.js';\n\nexport interface MigrateFromDbtReport {\n projectInfo: DbtProjectInfo;\n sourcesInfo?: DbtSourcesInfo;\n migration: DbtMigrationResult;\n inputs: {\n projectPath: string;\n manifestPath: string;\n sourcesPaths: string[];\n };\n}\n\nasync function readJsonFile(filePath: string): Promise<DbtManifestInput> {\n const raw = await fs.readFile(filePath, 'utf-8');\n return JSON.parse(raw) as DbtManifestInput;\n}\n\nasync function readTextFile(filePath: string): Promise<string | undefined> {\n try {\n return await fs.readFile(filePath, 'utf-8');\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') return undefined;\n throw err;\n }\n}\n\n/**\n * Walk a dbt project directory and load the three input files we know\n * about. Returns paths + raw bytes; the orchestration function uses these.\n *\n * Expected layout (dbt defaults):\n * <root>/\n * dbt_project.yml\n * target/\n * manifest.json\n * models/**\\/_sources.yml (or schema.yml)\n *\n * `manifestPath` is overridable so callers can point at a manifest cached\n * elsewhere (CI, pre-compiled artifact).\n */\nasync function discoverDbtFiles(\n rootPath: string,\n manifestPathOverride?: string,\n): Promise<{\n projectYamlText: string;\n projectYamlPath: string;\n manifestText: string;\n manifestPath: string;\n sourcesYamlTexts: { path: string; text: string }[];\n}> {\n const projectYamlPath = path.join(rootPath, 'dbt_project.yml');\n const projectYamlText = await readTextFile(projectYamlPath);\n if (projectYamlText === undefined) {\n throw new Error(`dbt_project.yml not found at ${projectYamlPath}`);\n }\n\n const manifestPath = manifestPathOverride ?? path.join(rootPath, 'target', 'manifest.json');\n const manifestText = await readTextFile(manifestPath);\n if (manifestText === undefined) {\n throw new Error(\n `manifest.json not found at ${manifestPath} — run \"dbt compile\" first or pass --manifest`,\n );\n }\n\n // Look for sources YAMLs anywhere under models/. Best-effort; the\n // migrator works without these (manifest carries sources too).\n const modelsRoot = path.join(rootPath, 'models');\n const sourcesYamlTexts: { path: string; text: string }[] = [];\n try {\n await walkForSourcesYaml(modelsRoot, sourcesYamlTexts);\n } catch {\n // models/ might not exist (esoteric project layouts); skip silently.\n }\n\n return { projectYamlText, projectYamlPath, manifestText, manifestPath, sourcesYamlTexts };\n}\n\nasync function walkForSourcesYaml(\n dirPath: string,\n out: { path: string; text: string }[],\n): Promise<void> {\n const entries = await fs.readdir(dirPath, { withFileTypes: true });\n for (const entry of entries) {\n const full = path.join(dirPath, entry.name);\n if (entry.isDirectory()) {\n await walkForSourcesYaml(full, out);\n continue;\n }\n if (\n entry.name === 'sources.yml' ||\n entry.name === 'sources.yaml' ||\n entry.name === '_sources.yml' ||\n entry.name === '_sources.yaml'\n ) {\n const text = await fs.readFile(full, 'utf-8');\n out.push({ path: full, text });\n }\n }\n}\n\n/**\n * End-to-end migration orchestrator. Pure given the input bytes — the\n * filesystem walk happens upstream so this remains test-friendly.\n */\nexport function buildMigrateFromDbtReport(inputs: {\n projectYamlText: string;\n projectYamlPath: string;\n manifestText: string;\n manifestPath: string;\n sourcesYamlTexts: { path: string; text: string }[];\n options?: {\n identifierCase?: IdentifierCase;\n defaultDatabase?: string;\n defaultSchema?: string;\n };\n}): MigrateFromDbtReport {\n const projectInfo = parseDbtProjectYaml(inputs.projectYamlText);\n\n // Combine all sources.yml files. The DDT-side / SDT-side parsers are\n // file-scoped; we aggregate at the CLI level.\n let sourcesInfo: DbtSourcesInfo | undefined;\n if (inputs.sourcesYamlTexts.length > 0) {\n const combined: DbtSourcesInfo = { sources: [], warnings: [] };\n for (const f of inputs.sourcesYamlTexts) {\n const parsed = parseDbtSourcesYaml(f.text);\n combined.sources.push(...parsed.sources);\n combined.warnings.push(...parsed.warnings.map((w) => `${f.path}: ${w}`));\n }\n sourcesInfo = combined;\n }\n\n const manifest: DbtManifestInput = JSON.parse(inputs.manifestText) as DbtManifestInput;\n\n // Caller-overrides win; otherwise infer reasonable defaults from\n // project.yml's first per-path entry that has database/schema set.\n const inferredDefault = projectInfo.perPath.find((p) => p.database || p.schema);\n const migOpts: DbtMigrationOptions = {\n identifierCase: inputs.options?.identifierCase ?? 'preserve',\n ...((inputs.options?.defaultDatabase ?? inferredDefault?.database)\n ? { defaultDatabase: inputs.options?.defaultDatabase ?? inferredDefault?.database }\n : {}),\n ...((inputs.options?.defaultSchema ?? inferredDefault?.schema)\n ? { defaultSchema: inputs.options?.defaultSchema ?? inferredDefault?.schema }\n : {}),\n };\n\n const migration = migrateFromDbtManifest(manifest, migOpts);\n\n return {\n projectInfo,\n ...(sourcesInfo ? { sourcesInfo } : {}),\n migration,\n inputs: {\n projectPath: inputs.projectYamlPath,\n manifestPath: inputs.manifestPath,\n sourcesPaths: inputs.sourcesYamlTexts.map((f) => f.path),\n },\n };\n}\n\nexport function migrateFromDbtCommand(): Command {\n const cmd = new Command('migrate');\n cmd.description('Migrate from another tool to SDT (`from-dbt` subcommand only in v1).');\n\n cmd\n .command('from-dbt')\n .description(\n 'Read a dbt project (dbt_project.yml + target/manifest.json + sources.yml files) ' +\n 'and emit a structured migration report (`DbtMigrationResult`) to stdout or --output.',\n )\n .argument(\n '<project-path>',\n 'Path to the dbt project root (the directory containing dbt_project.yml).',\n )\n .option(\n '--manifest <path>',\n 'Override the manifest path. Default: <project-path>/target/manifest.json.',\n )\n .option('--output <path>', 'Write JSON report to this file. Default: stdout.')\n .option('--identifier-case <case>', 'preserve | upper | lower. Default: preserve.', 'preserve')\n .option(\n '--default-database <name>',\n \"Fallback database when the manifest entry doesn't pin one.\",\n )\n .option('--default-schema <name>', \"Fallback schema when the manifest entry doesn't pin one.\")\n .action(\n async (\n projectPath: string,\n options: {\n manifest?: string;\n output?: string;\n identifierCase?: string;\n defaultDatabase?: string;\n defaultSchema?: string;\n },\n ) => {\n const resolved = path.resolve(projectPath);\n let discovered;\n try {\n discovered = await discoverDbtFiles(resolved, options.manifest);\n } catch (err) {\n logger.error(`migrate from-dbt: ${(err as Error).message}`);\n process.exitCode = 1;\n return;\n }\n const idCase = (options.identifierCase ?? 'preserve') as IdentifierCase;\n if (!['preserve', 'upper', 'lower'].includes(idCase)) {\n logger.error(\n `--identifier-case must be one of preserve | upper | lower (got \"${idCase}\")`,\n );\n process.exitCode = 1;\n return;\n }\n const report = buildMigrateFromDbtReport({\n projectYamlText: discovered.projectYamlText,\n projectYamlPath: discovered.projectYamlPath,\n manifestText: discovered.manifestText,\n manifestPath: discovered.manifestPath,\n sourcesYamlTexts: discovered.sourcesYamlTexts,\n options: {\n identifierCase: idCase,\n ...(options.defaultDatabase ? { defaultDatabase: options.defaultDatabase } : {}),\n ...(options.defaultSchema ? { defaultSchema: options.defaultSchema } : {}),\n },\n });\n const json = JSON.stringify(report, null, 2);\n if (options.output) {\n await fs.writeFile(options.output, json);\n logger.info(\n `migrate from-dbt: wrote report to ${options.output} — ${report.migration.models.length} models / ${report.migration.sources.length} sources / ${report.migration.diagnostics.length} diagnostics`,\n );\n } else {\n process.stdout.write(json + '\\n');\n }\n },\n );\n\n return cmd;\n}\n\n// Re-export the orchestrator for direct programmatic / test usage. Note:\n// `readJsonFile` is intentionally not exported (filesystem-coupling stays\n// behind the CLI surface).\nexport { readJsonFile as _readJsonFile_internalForTests };\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import {
|
|
2
|
+
logger
|
|
3
|
+
} from "./chunk-VM2H4LAO.js";
|
|
4
|
+
import "./chunk-DGUM43GV.js";
|
|
5
|
+
|
|
6
|
+
// src/commands/migrate-platform.ts
|
|
7
|
+
import path from "path";
|
|
8
|
+
import fs from "fs/promises";
|
|
9
|
+
import { Command } from "commander";
|
|
10
|
+
import { translateTableDatabricksToSnowflake } from "@ddt-tools/core/migrate";
|
|
11
|
+
async function collectSqlFiles(inputPath) {
|
|
12
|
+
let stat;
|
|
13
|
+
try {
|
|
14
|
+
stat = await fs.stat(inputPath);
|
|
15
|
+
} catch {
|
|
16
|
+
return [];
|
|
17
|
+
}
|
|
18
|
+
if (stat.isFile()) {
|
|
19
|
+
return inputPath.endsWith(".sql") ? [inputPath] : [];
|
|
20
|
+
}
|
|
21
|
+
const entries = await fs.readdir(inputPath, { withFileTypes: true });
|
|
22
|
+
const files = [];
|
|
23
|
+
for (const entry of entries) {
|
|
24
|
+
const full = path.join(inputPath, entry.name);
|
|
25
|
+
if (entry.isDirectory()) {
|
|
26
|
+
files.push(...await collectSqlFiles(full));
|
|
27
|
+
} else if (entry.name.endsWith(".sql")) {
|
|
28
|
+
files.push(full);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return files;
|
|
32
|
+
}
|
|
33
|
+
function migratePlatformCommand() {
|
|
34
|
+
const cmd = new Command("migrate-platform");
|
|
35
|
+
cmd.description(
|
|
36
|
+
"Translate Databricks SQL files to another platform dialect. v1: --to snowflake. Reads .sql files from <path> (file or directory)."
|
|
37
|
+
).argument("<path>", "Source .sql file or directory containing .sql files.").requiredOption("--to <platform>", 'Target platform. Must be "snowflake".').option("--output <dir>", "Write translated files to this directory. Default: stdout.").option(
|
|
38
|
+
"--warn-on-unsupported",
|
|
39
|
+
"Exit with code 1 if any column type cannot be translated.",
|
|
40
|
+
false
|
|
41
|
+
).action(
|
|
42
|
+
async (inputPath, opts) => {
|
|
43
|
+
const target = opts.to.toLowerCase();
|
|
44
|
+
if (target !== "snowflake") {
|
|
45
|
+
logger.error(
|
|
46
|
+
`ddt migrate-platform --to ${opts.to}: unknown target. Only "snowflake" is supported.`
|
|
47
|
+
);
|
|
48
|
+
process.exitCode = 1;
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const resolved = path.resolve(inputPath);
|
|
52
|
+
const root = resolved.endsWith(".ddtproj") ? path.dirname(resolved) : resolved;
|
|
53
|
+
const sqlFiles = await collectSqlFiles(root);
|
|
54
|
+
if (sqlFiles.length === 0) {
|
|
55
|
+
logger.warn(`No .sql files found at ${root}`);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
logger.info(`Translating ${sqlFiles.length} file(s) \u2192 Snowflake\u2026`);
|
|
59
|
+
let anyUnsupported = false;
|
|
60
|
+
for (const file of sqlFiles) {
|
|
61
|
+
const rel = root === file ? path.basename(file) : path.relative(root, file);
|
|
62
|
+
const ddl = await fs.readFile(file, "utf8");
|
|
63
|
+
const result = translateTableDatabricksToSnowflake(ddl);
|
|
64
|
+
if (result.hasUnsupported) anyUnsupported = true;
|
|
65
|
+
for (const w of result.warnings) {
|
|
66
|
+
logger.warn(`[${rel}] ${w}`);
|
|
67
|
+
}
|
|
68
|
+
if (opts.output) {
|
|
69
|
+
const dest = path.join(path.resolve(opts.output), rel);
|
|
70
|
+
await fs.mkdir(path.dirname(dest), { recursive: true });
|
|
71
|
+
await fs.writeFile(dest, result.sql, "utf8");
|
|
72
|
+
logger.success(`Wrote ${dest}`);
|
|
73
|
+
} else {
|
|
74
|
+
process.stdout.write(`-- Source: ${rel}
|
|
75
|
+
${result.sql}
|
|
76
|
+
|
|
77
|
+
`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (opts.warnOnUnsupported && anyUnsupported) {
|
|
81
|
+
logger.error("One or more column types could not be translated (see warnings above).");
|
|
82
|
+
process.exitCode = 1;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
);
|
|
86
|
+
return cmd;
|
|
87
|
+
}
|
|
88
|
+
export {
|
|
89
|
+
migratePlatformCommand
|
|
90
|
+
};
|
|
91
|
+
//# sourceMappingURL=migrate-platform-E7VZFPO5.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/migrate-platform.ts"],"sourcesContent":["/**\n * XPM.7 — `ddt migrate-platform --to snowflake <path>`\n *\n * Translates Databricks SQL files to Snowflake equivalents using the XPM.4\n * table/view emitter. Accepts a single .sql file or a directory of .sql files.\n * Outputs translated DDL to stdout (default) or --output <dir>.\n *\n * Byte-aligned with `Snowflake/packages/cli/src/commands/migrate-platform.ts`.\n */\nimport path from 'node:path';\nimport fs from 'node:fs/promises';\nimport { Command } from 'commander';\nimport { translateTableDatabricksToSnowflake } from '@ddt-tools/core/migrate';\nimport { logger } from '../util/logger.js';\n\nasync function collectSqlFiles(inputPath: string): Promise<string[]> {\n let stat: Awaited<ReturnType<typeof fs.stat>>;\n try {\n stat = await fs.stat(inputPath);\n } catch {\n return [];\n }\n if (stat.isFile()) {\n return inputPath.endsWith('.sql') ? [inputPath] : [];\n }\n const entries = await fs.readdir(inputPath, { withFileTypes: true });\n const files: string[] = [];\n for (const entry of entries) {\n const full = path.join(inputPath, entry.name);\n if (entry.isDirectory()) {\n files.push(...(await collectSqlFiles(full)));\n } else if (entry.name.endsWith('.sql')) {\n files.push(full);\n }\n }\n return files;\n}\n\nexport function migratePlatformCommand(): Command {\n const cmd = new Command('migrate-platform');\n cmd\n .description(\n 'Translate Databricks SQL files to another platform dialect. ' +\n 'v1: --to snowflake. Reads .sql files from <path> (file or directory).',\n )\n .argument('<path>', 'Source .sql file or directory containing .sql files.')\n .requiredOption('--to <platform>', 'Target platform. Must be \"snowflake\".')\n .option('--output <dir>', 'Write translated files to this directory. Default: stdout.')\n .option(\n '--warn-on-unsupported',\n 'Exit with code 1 if any column type cannot be translated.',\n false,\n )\n .action(\n async (\n inputPath: string,\n opts: { to: string; output?: string; warnOnUnsupported: boolean },\n ) => {\n const target = opts.to.toLowerCase();\n if (target !== 'snowflake') {\n logger.error(\n `ddt migrate-platform --to ${opts.to}: unknown target. Only \"snowflake\" is supported.`,\n );\n process.exitCode = 1;\n return;\n }\n\n const resolved = path.resolve(inputPath);\n // If the path is a .ddtproj file, translate its containing directory.\n const root = resolved.endsWith('.ddtproj') ? path.dirname(resolved) : resolved;\n const sqlFiles = await collectSqlFiles(root);\n\n if (sqlFiles.length === 0) {\n logger.warn(`No .sql files found at ${root}`);\n return;\n }\n\n logger.info(`Translating ${sqlFiles.length} file(s) → Snowflake…`);\n let anyUnsupported = false;\n\n for (const file of sqlFiles) {\n // When the user passes a single .sql file, `root === file` and\n // `path.relative(root, file)` is empty — fall back to the basename\n // so the `-- Source:` header shows the filename, and so the --output\n // destination is `output/<basename>` not the output dir path itself.\n const rel = root === file ? path.basename(file) : path.relative(root, file);\n const ddl = await fs.readFile(file, 'utf8');\n const result = translateTableDatabricksToSnowflake(ddl);\n\n if (result.hasUnsupported) anyUnsupported = true;\n for (const w of result.warnings) {\n logger.warn(`[${rel}] ${w}`);\n }\n\n if (opts.output) {\n const dest = path.join(path.resolve(opts.output), rel);\n await fs.mkdir(path.dirname(dest), { recursive: true });\n await fs.writeFile(dest, result.sql, 'utf8');\n logger.success(`Wrote ${dest}`);\n } else {\n process.stdout.write(`-- Source: ${rel}\\n${result.sql}\\n\\n`);\n }\n }\n\n if (opts.warnOnUnsupported && anyUnsupported) {\n logger.error('One or more column types could not be translated (see warnings above).');\n process.exitCode = 1;\n }\n },\n );\n\n return cmd;\n}\n"],"mappings":";;;;;;AASA,OAAO,UAAU;AACjB,OAAO,QAAQ;AACf,SAAS,eAAe;AACxB,SAAS,2CAA2C;AAGpD,eAAe,gBAAgB,WAAsC;AACnE,MAAI;AACJ,MAAI;AACF,WAAO,MAAM,GAAG,KAAK,SAAS;AAAA,EAChC,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACA,MAAI,KAAK,OAAO,GAAG;AACjB,WAAO,UAAU,SAAS,MAAM,IAAI,CAAC,SAAS,IAAI,CAAC;AAAA,EACrD;AACA,QAAM,UAAU,MAAM,GAAG,QAAQ,WAAW,EAAE,eAAe,KAAK,CAAC;AACnE,QAAM,QAAkB,CAAC;AACzB,aAAW,SAAS,SAAS;AAC3B,UAAM,OAAO,KAAK,KAAK,WAAW,MAAM,IAAI;AAC5C,QAAI,MAAM,YAAY,GAAG;AACvB,YAAM,KAAK,GAAI,MAAM,gBAAgB,IAAI,CAAE;AAAA,IAC7C,WAAW,MAAM,KAAK,SAAS,MAAM,GAAG;AACtC,YAAM,KAAK,IAAI;AAAA,IACjB;AAAA,EACF;AACA,SAAO;AACT;AAEO,SAAS,yBAAkC;AAChD,QAAM,MAAM,IAAI,QAAQ,kBAAkB;AAC1C,MACG;AAAA,IACC;AAAA,EAEF,EACC,SAAS,UAAU,sDAAsD,EACzE,eAAe,mBAAmB,uCAAuC,EACzE,OAAO,kBAAkB,4DAA4D,EACrF;AAAA,IACC;AAAA,IACA;AAAA,IACA;AAAA,EACF,EACC;AAAA,IACC,OACE,WACA,SACG;AACH,YAAM,SAAS,KAAK,GAAG,YAAY;AACnC,UAAI,WAAW,aAAa;AAC1B,eAAO;AAAA,UACL,6BAA6B,KAAK,EAAE;AAAA,QACtC;AACA,gBAAQ,WAAW;AACnB;AAAA,MACF;AAEA,YAAM,WAAW,KAAK,QAAQ,SAAS;AAEvC,YAAM,OAAO,SAAS,SAAS,UAAU,IAAI,KAAK,QAAQ,QAAQ,IAAI;AACtE,YAAM,WAAW,MAAM,gBAAgB,IAAI;AAE3C,UAAI,SAAS,WAAW,GAAG;AACzB,eAAO,KAAK,0BAA0B,IAAI,EAAE;AAC5C;AAAA,MACF;AAEA,aAAO,KAAK,eAAe,SAAS,MAAM,iCAAuB;AACjE,UAAI,iBAAiB;AAErB,iBAAW,QAAQ,UAAU;AAK3B,cAAM,MAAM,SAAS,OAAO,KAAK,SAAS,IAAI,IAAI,KAAK,SAAS,MAAM,IAAI;AAC1E,cAAM,MAAM,MAAM,GAAG,SAAS,MAAM,MAAM;AAC1C,cAAM,SAAS,oCAAoC,GAAG;AAEtD,YAAI,OAAO,eAAgB,kBAAiB;AAC5C,mBAAW,KAAK,OAAO,UAAU;AAC/B,iBAAO,KAAK,IAAI,GAAG,KAAK,CAAC,EAAE;AAAA,QAC7B;AAEA,YAAI,KAAK,QAAQ;AACf,gBAAM,OAAO,KAAK,KAAK,KAAK,QAAQ,KAAK,MAAM,GAAG,GAAG;AACrD,gBAAM,GAAG,MAAM,KAAK,QAAQ,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;AACtD,gBAAM,GAAG,UAAU,MAAM,OAAO,KAAK,MAAM;AAC3C,iBAAO,QAAQ,SAAS,IAAI,EAAE;AAAA,QAChC,OAAO;AACL,kBAAQ,OAAO,MAAM,cAAc,GAAG;AAAA,EAAK,OAAO,GAAG;AAAA;AAAA,CAAM;AAAA,QAC7D;AAAA,MACF;AAEA,UAAI,KAAK,qBAAqB,gBAAgB;AAC5C,eAAO,MAAM,wEAAwE;AACrF,gBAAQ,WAAW;AAAA,MACrB;AAAA,IACF;AAAA,EACF;AAEF,SAAO;AACT;","names":[]}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import {
|
|
2
|
+
attachExplainFlag,
|
|
3
|
+
runExplain
|
|
4
|
+
} from "./chunk-XFXG347C.js";
|
|
5
|
+
import "./chunk-DGUM43GV.js";
|
|
6
|
+
|
|
7
|
+
// src/commands/optimize.ts
|
|
8
|
+
import { promises as fs } from "fs";
|
|
9
|
+
import path from "path";
|
|
10
|
+
import { Command } from "commander";
|
|
11
|
+
import {
|
|
12
|
+
loadProject,
|
|
13
|
+
optimize,
|
|
14
|
+
pac,
|
|
15
|
+
parseProjectModel
|
|
16
|
+
} from "@ddt-tools/core";
|
|
17
|
+
function optimizeCommand() {
|
|
18
|
+
const cmd = new Command("optimize");
|
|
19
|
+
cmd.description(
|
|
20
|
+
"Surface ranked optimization recommendations (Z-ORDER, ENUM-shaped strings, missing PKs, streaming-table schedule)."
|
|
21
|
+
).requiredOption("--source <path>", ".ddtproj or .ddtpac to analyze.").option("--format <fmt>", "table | json | markdown. Default table.", "table").option("--severity <level>", "Filter: high | medium | low | info. Default all.", "all").option("-o, --out <path>", "Output file. Defaults to stdout.").action(async (opts) => {
|
|
22
|
+
const model = await loadModel(String(opts.source));
|
|
23
|
+
const report = optimize.optimize(model);
|
|
24
|
+
const severity = String(opts.severity ?? "all").toLowerCase();
|
|
25
|
+
const filtered = severity === "all" ? report.recommendations : report.recommendations.filter((r) => r.severity === severity);
|
|
26
|
+
const fmt = String(opts.format ?? "table").toLowerCase();
|
|
27
|
+
let payload;
|
|
28
|
+
if (fmt === "json") {
|
|
29
|
+
payload = JSON.stringify({ ...report, recommendations: filtered }, null, 2);
|
|
30
|
+
} else if (fmt === "markdown") {
|
|
31
|
+
payload = renderMarkdown(filtered, report.stats.objectsScanned);
|
|
32
|
+
} else {
|
|
33
|
+
payload = renderTable(filtered, report.stats.objectsScanned);
|
|
34
|
+
}
|
|
35
|
+
if (opts.out) {
|
|
36
|
+
const p = path.resolve(String(opts.out));
|
|
37
|
+
await fs.mkdir(path.dirname(p), { recursive: true });
|
|
38
|
+
await fs.writeFile(p, payload + (payload.endsWith("\n") ? "" : "\n"), "utf8");
|
|
39
|
+
console.error(`Wrote ${p}.`);
|
|
40
|
+
} else {
|
|
41
|
+
process.stdout.write(payload + (payload.endsWith("\n") ? "" : "\n"));
|
|
42
|
+
}
|
|
43
|
+
await runExplain(
|
|
44
|
+
{
|
|
45
|
+
feature: "optimize.explain",
|
|
46
|
+
systemPrompt: "You are a Databricks performance engineer. Walk the team through these optimization recommendations: which would have the biggest DBU-cost impact, which are stylistic, which depend on workload characteristics the static scan can't see. Suggest an order to apply them."
|
|
47
|
+
},
|
|
48
|
+
opts,
|
|
49
|
+
() => `Recommendations (JSON):
|
|
50
|
+
|
|
51
|
+
${JSON.stringify(filtered, null, 2)}
|
|
52
|
+
|
|
53
|
+
Narrate for a teammate.`
|
|
54
|
+
);
|
|
55
|
+
});
|
|
56
|
+
attachExplainFlag(cmd);
|
|
57
|
+
return cmd;
|
|
58
|
+
}
|
|
59
|
+
async function loadModel(sourcePath) {
|
|
60
|
+
if (sourcePath.endsWith(".ddtpac")) {
|
|
61
|
+
const c = await pac.readPac(sourcePath);
|
|
62
|
+
return c.model;
|
|
63
|
+
}
|
|
64
|
+
const loaded = await loadProject(sourcePath);
|
|
65
|
+
return await parseProjectModel(loaded);
|
|
66
|
+
}
|
|
67
|
+
function renderTable(recs, scanned) {
|
|
68
|
+
const lines = [];
|
|
69
|
+
lines.push(`Optimize \u2014 ${scanned} object(s) scanned, ${recs.length} recommendation(s).`);
|
|
70
|
+
lines.push("");
|
|
71
|
+
if (recs.length === 0) {
|
|
72
|
+
lines.push(" \u2713 No recommendations \u2014 the model looks healthy by these heuristics.");
|
|
73
|
+
return lines.join("\n");
|
|
74
|
+
}
|
|
75
|
+
const sevW = 8;
|
|
76
|
+
const idW = Math.max(20, ...recs.map((r) => r.id.length));
|
|
77
|
+
const fqnW = Math.max(20, ...recs.map((r) => r.fqn.length));
|
|
78
|
+
for (const r of recs) {
|
|
79
|
+
lines.push(
|
|
80
|
+
` ${r.severity.toUpperCase().padEnd(sevW)} ${r.id.padEnd(idW)} ${r.fqn.padEnd(fqnW)} ${r.summary}`
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
return lines.join("\n");
|
|
84
|
+
}
|
|
85
|
+
function renderMarkdown(recs, scanned) {
|
|
86
|
+
const lines = [];
|
|
87
|
+
lines.push(`# Optimize recommendations`);
|
|
88
|
+
lines.push("");
|
|
89
|
+
lines.push(`**Scanned:** ${scanned} object(s) \u2014 **${recs.length} recommendation(s)**.`);
|
|
90
|
+
lines.push("");
|
|
91
|
+
for (const r of recs) {
|
|
92
|
+
lines.push(`## ${r.severity.toUpperCase()} \xB7 \`${r.id}\` \u2014 \`${r.fqn}\``);
|
|
93
|
+
lines.push("");
|
|
94
|
+
lines.push(r.summary);
|
|
95
|
+
lines.push("");
|
|
96
|
+
lines.push("**Suggestion:**");
|
|
97
|
+
lines.push("```sql");
|
|
98
|
+
lines.push(r.suggestion);
|
|
99
|
+
lines.push("```");
|
|
100
|
+
lines.push("");
|
|
101
|
+
lines.push("**Why:** " + r.rationale);
|
|
102
|
+
lines.push("");
|
|
103
|
+
}
|
|
104
|
+
return lines.join("\n");
|
|
105
|
+
}
|
|
106
|
+
export {
|
|
107
|
+
optimizeCommand
|
|
108
|
+
};
|
|
109
|
+
//# sourceMappingURL=optimize-WUJ5ZN5Y.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/optimize.ts"],"sourcesContent":["/**\n * `ddt optimize` — heuristic optimizer. Walks the model and surfaces\n * ranked recommendations (Z-ORDER, unbounded ENUM-shaped strings,\n * missing PKs, streaming-table refresh schedules). Composes with\n * `--explain` so the configured AI provider can re-rank.\n *\n * Mirrors `sdt optimize`.\n */\nimport { promises as fs } from 'node:fs';\nimport path from 'node:path';\nimport { Command } from 'commander';\nimport {\n loadProject,\n optimize,\n pac,\n parseProjectModel,\n type DatabricksObject,\n} from '@ddt-tools/core';\nimport { attachExplainFlag, runExplain } from '../util/ai-explain.js';\n\nexport function optimizeCommand(): Command {\n const cmd = new Command('optimize');\n cmd\n .description(\n 'Surface ranked optimization recommendations (Z-ORDER, ENUM-shaped strings, missing PKs, streaming-table schedule).',\n )\n .requiredOption('--source <path>', '.ddtproj or .ddtpac to analyze.')\n .option('--format <fmt>', 'table | json | markdown. Default table.', 'table')\n .option('--severity <level>', 'Filter: high | medium | low | info. Default all.', 'all')\n .option('-o, --out <path>', 'Output file. Defaults to stdout.')\n .action(async (opts) => {\n const model = await loadModel(String(opts.source));\n const report = optimize.optimize(model);\n const severity = String(opts.severity ?? 'all').toLowerCase();\n const filtered =\n severity === 'all'\n ? report.recommendations\n : report.recommendations.filter((r) => r.severity === severity);\n\n const fmt = String(opts.format ?? 'table').toLowerCase();\n let payload: string;\n if (fmt === 'json') {\n payload = JSON.stringify({ ...report, recommendations: filtered }, null, 2);\n } else if (fmt === 'markdown') {\n payload = renderMarkdown(filtered, report.stats.objectsScanned);\n } else {\n payload = renderTable(filtered, report.stats.objectsScanned);\n }\n\n if (opts.out) {\n const p = path.resolve(String(opts.out));\n await fs.mkdir(path.dirname(p), { recursive: true });\n await fs.writeFile(p, payload + (payload.endsWith('\\n') ? '' : '\\n'), 'utf8');\n console.error(`Wrote ${p}.`);\n } else {\n process.stdout.write(payload + (payload.endsWith('\\n') ? '' : '\\n'));\n }\n\n await runExplain(\n {\n feature: 'optimize.explain',\n systemPrompt:\n \"You are a Databricks performance engineer. Walk the team through these optimization recommendations: which would have the biggest DBU-cost impact, which are stylistic, which depend on workload characteristics the static scan can't see. Suggest an order to apply them.\",\n },\n opts as { explain?: boolean },\n () =>\n `Recommendations (JSON):\\n\\n${JSON.stringify(filtered, null, 2)}\\n\\nNarrate for a teammate.`,\n );\n });\n attachExplainFlag(cmd);\n return cmd;\n}\n\nasync function loadModel(sourcePath: string): Promise<readonly DatabricksObject[]> {\n if (sourcePath.endsWith('.ddtpac')) {\n const c = await pac.readPac(sourcePath);\n return c.model;\n }\n const loaded = await loadProject(sourcePath);\n return (await parseProjectModel(loaded)) as DatabricksObject[];\n}\n\nfunction renderTable(recs: readonly optimize.OptimizeRecommendation[], scanned: number): string {\n const lines: string[] = [];\n lines.push(`Optimize — ${scanned} object(s) scanned, ${recs.length} recommendation(s).`);\n lines.push('');\n if (recs.length === 0) {\n lines.push(' ✓ No recommendations — the model looks healthy by these heuristics.');\n return lines.join('\\n');\n }\n const sevW = 8;\n const idW = Math.max(20, ...recs.map((r) => r.id.length));\n const fqnW = Math.max(20, ...recs.map((r) => r.fqn.length));\n for (const r of recs) {\n lines.push(\n ` ${r.severity.toUpperCase().padEnd(sevW)} ${r.id.padEnd(idW)} ${r.fqn.padEnd(fqnW)} ${r.summary}`,\n );\n }\n return lines.join('\\n');\n}\n\nfunction renderMarkdown(recs: readonly optimize.OptimizeRecommendation[], scanned: number): string {\n const lines: string[] = [];\n lines.push(`# Optimize recommendations`);\n lines.push('');\n lines.push(`**Scanned:** ${scanned} object(s) — **${recs.length} recommendation(s)**.`);\n lines.push('');\n for (const r of recs) {\n lines.push(`## ${r.severity.toUpperCase()} · \\`${r.id}\\` — \\`${r.fqn}\\``);\n lines.push('');\n lines.push(r.summary);\n lines.push('');\n lines.push('**Suggestion:**');\n lines.push('```sql');\n lines.push(r.suggestion);\n lines.push('```');\n lines.push('');\n lines.push('**Why:** ' + r.rationale);\n lines.push('');\n }\n return lines.join('\\n');\n}\n"],"mappings":"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","names":[]}
|