@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,168 @@
|
|
|
1
|
+
import {
|
|
2
|
+
attachExplainFlag,
|
|
3
|
+
runExplain
|
|
4
|
+
} from "./chunk-XFXG347C.js";
|
|
5
|
+
import "./chunk-DGUM43GV.js";
|
|
6
|
+
|
|
7
|
+
// src/commands/graph.ts
|
|
8
|
+
import { promises as fs } from "fs";
|
|
9
|
+
import path from "path";
|
|
10
|
+
import { Command } from "commander";
|
|
11
|
+
import {
|
|
12
|
+
buildDependencyGraph,
|
|
13
|
+
diffDependencyGraphs,
|
|
14
|
+
loadProject,
|
|
15
|
+
pac,
|
|
16
|
+
parseProjectModel,
|
|
17
|
+
renderGraphDot,
|
|
18
|
+
renderGraphMermaid
|
|
19
|
+
} from "@ddt-tools/core";
|
|
20
|
+
function graphCommand() {
|
|
21
|
+
const cmd = new Command("graph");
|
|
22
|
+
cmd.description("Build an object-dependency DAG and emit it as Mermaid or DOT.").requiredOption("--source <path>", ".ddtproj or .ddtpac to analyze.").option("-o, --out <path>", "Output file path. Defaults to stdout.").option(
|
|
23
|
+
"--format <fmt>",
|
|
24
|
+
"mermaid | dot | md (Mermaid wrapped in a markdown fenced block)",
|
|
25
|
+
"mermaid"
|
|
26
|
+
).option("--json", "Emit the raw { nodes, edges } JSON instead of a renderer.", false).option(
|
|
27
|
+
"--compare-to <path>",
|
|
28
|
+
"A second .ddtproj or .ddtpac. When set, emit added / removed / changed nodes + edges (the DAG delta) instead of the static graph."
|
|
29
|
+
).action(async (opts) => {
|
|
30
|
+
const model = await loadModel(String(opts.source));
|
|
31
|
+
const graph = buildDependencyGraph(model);
|
|
32
|
+
let diff;
|
|
33
|
+
if (opts.compareTo) {
|
|
34
|
+
const otherModel = await loadModel(String(opts.compareTo));
|
|
35
|
+
const otherGraph = buildDependencyGraph(otherModel);
|
|
36
|
+
diff = diffDependencyGraphs(otherGraph, graph);
|
|
37
|
+
}
|
|
38
|
+
const fmt = String(opts.format ?? "mermaid").toLowerCase();
|
|
39
|
+
let payload;
|
|
40
|
+
if (opts.json) {
|
|
41
|
+
payload = JSON.stringify(diff ?? graph, null, 2);
|
|
42
|
+
} else if (diff) {
|
|
43
|
+
payload = renderDiffMarkdown(diff);
|
|
44
|
+
} else if (fmt === "mermaid") {
|
|
45
|
+
payload = renderGraphMermaid(graph);
|
|
46
|
+
} else if (fmt === "dot") {
|
|
47
|
+
payload = renderGraphDot(graph);
|
|
48
|
+
} else if (fmt === "md") {
|
|
49
|
+
payload = `# Schema dependency graph
|
|
50
|
+
|
|
51
|
+
${graph.nodes.length} node(s), ${graph.edges.length} edge(s).
|
|
52
|
+
|
|
53
|
+
\`\`\`mermaid
|
|
54
|
+
${renderGraphMermaid(graph)}
|
|
55
|
+
\`\`\`
|
|
56
|
+
`;
|
|
57
|
+
} else {
|
|
58
|
+
throw new Error(`Unknown --format: ${fmt}. Use mermaid | dot | md.`);
|
|
59
|
+
}
|
|
60
|
+
if (opts.out) {
|
|
61
|
+
const out = path.resolve(String(opts.out));
|
|
62
|
+
await fs.mkdir(path.dirname(out), { recursive: true });
|
|
63
|
+
await fs.writeFile(out, payload + (payload.endsWith("\n") ? "" : "\n"), "utf8");
|
|
64
|
+
console.error(
|
|
65
|
+
`Wrote ${out} (${payload.length} bytes, ${graph.nodes.length} nodes, ${graph.edges.length} edges).`
|
|
66
|
+
);
|
|
67
|
+
} else {
|
|
68
|
+
process.stdout.write(payload + (payload.endsWith("\n") ? "" : "\n"));
|
|
69
|
+
}
|
|
70
|
+
await runExplain(
|
|
71
|
+
{
|
|
72
|
+
feature: "graph.explain",
|
|
73
|
+
systemPrompt: diff ? "You are a data-platform architect narrating a dependency-graph DELTA between two snapshots. Walk through added / removed / changed nodes and edges in plain English. Call out structural shifts (new sources, dropped consumers, type changes) and likely-but-not-yet-broken downstream consequences." : "You are a data-platform architect explaining a dependency graph. Describe the dominant data-flow shape, point out hot or fragile nodes, and recommend any decomposition the graph suggests."
|
|
74
|
+
},
|
|
75
|
+
opts,
|
|
76
|
+
() => diff ? buildDiffPrompt(diff) : buildGraphPrompt(graph)
|
|
77
|
+
);
|
|
78
|
+
});
|
|
79
|
+
attachExplainFlag(cmd);
|
|
80
|
+
return cmd;
|
|
81
|
+
}
|
|
82
|
+
function renderDiffMarkdown(d) {
|
|
83
|
+
const lines = ["# Dependency-graph delta", ""];
|
|
84
|
+
lines.push(
|
|
85
|
+
`**Summary**: ${d.summary.nodeDelta >= 0 ? "+" : ""}${d.summary.nodeDelta} nodes, ${d.summary.edgeDelta >= 0 ? "+" : ""}${d.summary.edgeDelta} edges. ${d.addedNodes.length} added node(s), ${d.removedNodes.length} removed node(s), ${d.changedNodes.length} changed node(s).`
|
|
86
|
+
);
|
|
87
|
+
lines.push("");
|
|
88
|
+
if (d.addedNodes.length > 0) {
|
|
89
|
+
lines.push("## Added nodes");
|
|
90
|
+
for (const n of d.addedNodes) lines.push(`- \`${n.fqn}\` (${n.objectType})`);
|
|
91
|
+
lines.push("");
|
|
92
|
+
}
|
|
93
|
+
if (d.removedNodes.length > 0) {
|
|
94
|
+
lines.push("## Removed nodes");
|
|
95
|
+
for (const n of d.removedNodes) lines.push(`- \`${n.fqn}\` (${n.objectType})`);
|
|
96
|
+
lines.push("");
|
|
97
|
+
}
|
|
98
|
+
if (d.changedNodes.length > 0) {
|
|
99
|
+
lines.push("## Changed nodes (objectType)");
|
|
100
|
+
for (const c of d.changedNodes) lines.push(`- \`${c.fqn}\`: ${c.before} \u2192 ${c.after}`);
|
|
101
|
+
lines.push("");
|
|
102
|
+
}
|
|
103
|
+
if (d.addedEdges.length > 0) {
|
|
104
|
+
lines.push("## Added edges");
|
|
105
|
+
for (const e of d.addedEdges) lines.push(`- \`${e.from}\` \u2192 \`${e.to}\` (${e.kind})`);
|
|
106
|
+
lines.push("");
|
|
107
|
+
}
|
|
108
|
+
if (d.removedEdges.length > 0) {
|
|
109
|
+
lines.push("## Removed edges");
|
|
110
|
+
for (const e of d.removedEdges) lines.push(`- \`${e.from}\` \u2192 \`${e.to}\` (${e.kind})`);
|
|
111
|
+
}
|
|
112
|
+
return lines.join("\n");
|
|
113
|
+
}
|
|
114
|
+
function buildGraphPrompt(g) {
|
|
115
|
+
const sample = g.edges.slice(0, 40).map((e) => ` - ${e.from} -> ${e.to}`);
|
|
116
|
+
return [
|
|
117
|
+
`Dependency graph: ${g.nodes.length} nodes, ${g.edges.length} edges.`,
|
|
118
|
+
"",
|
|
119
|
+
"Edges (up to 40):",
|
|
120
|
+
...sample,
|
|
121
|
+
"",
|
|
122
|
+
"Narrate this graph in plain English."
|
|
123
|
+
].join("\n");
|
|
124
|
+
}
|
|
125
|
+
function buildDiffPrompt(d) {
|
|
126
|
+
const lines = [];
|
|
127
|
+
lines.push(
|
|
128
|
+
`Graph delta: ${d.summary.nodeDelta >= 0 ? "+" : ""}${d.summary.nodeDelta} nodes, ${d.summary.edgeDelta >= 0 ? "+" : ""}${d.summary.edgeDelta} edges.`
|
|
129
|
+
);
|
|
130
|
+
if (d.addedNodes.length > 0) {
|
|
131
|
+
lines.push("", `Added nodes (${d.addedNodes.length}, up to 20):`);
|
|
132
|
+
for (const n of d.addedNodes.slice(0, 20)) lines.push(` + ${n.fqn} (${n.objectType})`);
|
|
133
|
+
}
|
|
134
|
+
if (d.removedNodes.length > 0) {
|
|
135
|
+
lines.push("", `Removed nodes (${d.removedNodes.length}, up to 20):`);
|
|
136
|
+
for (const n of d.removedNodes.slice(0, 20)) lines.push(` - ${n.fqn} (${n.objectType})`);
|
|
137
|
+
}
|
|
138
|
+
if (d.changedNodes.length > 0) {
|
|
139
|
+
lines.push("", `Changed nodes (${d.changedNodes.length}):`);
|
|
140
|
+
for (const c of d.changedNodes.slice(0, 20))
|
|
141
|
+
lines.push(` ~ ${c.fqn}: ${c.before} -> ${c.after}`);
|
|
142
|
+
}
|
|
143
|
+
if (d.addedEdges.length > 0) {
|
|
144
|
+
lines.push("", `Added edges (${d.addedEdges.length}, up to 20):`);
|
|
145
|
+
for (const e of d.addedEdges.slice(0, 20)) lines.push(` + ${e.from} -> ${e.to}`);
|
|
146
|
+
}
|
|
147
|
+
if (d.removedEdges.length > 0) {
|
|
148
|
+
lines.push("", `Removed edges (${d.removedEdges.length}, up to 20):`);
|
|
149
|
+
for (const e of d.removedEdges.slice(0, 20)) lines.push(` - ${e.from} -> ${e.to}`);
|
|
150
|
+
}
|
|
151
|
+
lines.push(
|
|
152
|
+
"",
|
|
153
|
+
"Narrate this delta in plain English. Highlight structural shifts and likely downstream consequences."
|
|
154
|
+
);
|
|
155
|
+
return lines.join("\n");
|
|
156
|
+
}
|
|
157
|
+
async function loadModel(sourcePath) {
|
|
158
|
+
if (sourcePath.endsWith(".ddtpac")) {
|
|
159
|
+
const c = await pac.readPac(sourcePath);
|
|
160
|
+
return c.model;
|
|
161
|
+
}
|
|
162
|
+
const loaded = await loadProject(sourcePath);
|
|
163
|
+
return await parseProjectModel(loaded);
|
|
164
|
+
}
|
|
165
|
+
export {
|
|
166
|
+
graphCommand
|
|
167
|
+
};
|
|
168
|
+
//# sourceMappingURL=graph-YYL5UYCJ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/graph.ts"],"sourcesContent":["import { promises as fs } from 'node:fs';\nimport path from 'node:path';\nimport { Command } from 'commander';\nimport {\n buildDependencyGraph,\n diffDependencyGraphs,\n loadProject,\n pac,\n parseProjectModel,\n renderGraphDot,\n renderGraphMermaid,\n type DatabricksObject,\n type DependencyGraph,\n type DependencyGraphDiff,\n} from '@ddt-tools/core';\nimport { attachExplainFlag, runExplain } from '../util/ai-explain.js';\n\n/**\n * `ddt graph` — emit an object-dependency DAG (Mermaid or DOT) from a\n * `.ddtproj` or `.ddtpac`. Lets reviewers see blast radius before a\n * drop. Inspired by DBeaver's \"Object References\" panel — but at the\n * cross-object level rather than column level.\n *\n * With `--compare-to <other-source>`, diffs the two graphs and (when\n * `--explain` is on) narrates the DAG changes in plain English — the\n * AI Phase 6 \"lineage explanation\" surface.\n */\nexport function graphCommand(): Command {\n const cmd = new Command('graph');\n cmd\n .description('Build an object-dependency DAG and emit it as Mermaid or DOT.')\n .requiredOption('--source <path>', '.ddtproj or .ddtpac to analyze.')\n .option('-o, --out <path>', 'Output file path. Defaults to stdout.')\n .option(\n '--format <fmt>',\n 'mermaid | dot | md (Mermaid wrapped in a markdown fenced block)',\n 'mermaid',\n )\n .option('--json', 'Emit the raw { nodes, edges } JSON instead of a renderer.', false)\n .option(\n '--compare-to <path>',\n 'A second .ddtproj or .ddtpac. When set, emit added / removed / changed nodes + edges (the DAG delta) instead of the static graph.',\n )\n .action(async (opts) => {\n const model = await loadModel(String(opts.source));\n const graph = buildDependencyGraph(model);\n\n let diff: DependencyGraphDiff | undefined;\n if (opts.compareTo) {\n const otherModel = await loadModel(String(opts.compareTo));\n const otherGraph = buildDependencyGraph(otherModel);\n diff = diffDependencyGraphs(otherGraph, graph);\n }\n\n const fmt = String(opts.format ?? 'mermaid').toLowerCase();\n let payload: string;\n if (opts.json) {\n payload = JSON.stringify(diff ?? graph, null, 2);\n } else if (diff) {\n payload = renderDiffMarkdown(diff);\n } else if (fmt === 'mermaid') {\n payload = renderGraphMermaid(graph);\n } else if (fmt === 'dot') {\n payload = renderGraphDot(graph);\n } else if (fmt === 'md') {\n payload = `# Schema dependency graph\\n\\n${graph.nodes.length} node(s), ${graph.edges.length} edge(s).\\n\\n\\`\\`\\`mermaid\\n${renderGraphMermaid(graph)}\\n\\`\\`\\`\\n`;\n } else {\n throw new Error(`Unknown --format: ${fmt}. Use mermaid | dot | md.`);\n }\n\n if (opts.out) {\n const out = path.resolve(String(opts.out));\n await fs.mkdir(path.dirname(out), { recursive: true });\n await fs.writeFile(out, payload + (payload.endsWith('\\n') ? '' : '\\n'), 'utf8');\n console.error(\n `Wrote ${out} (${payload.length} bytes, ${graph.nodes.length} nodes, ${graph.edges.length} edges).`,\n );\n } else {\n process.stdout.write(payload + (payload.endsWith('\\n') ? '' : '\\n'));\n }\n\n await runExplain(\n {\n feature: 'graph.explain',\n systemPrompt: diff\n ? 'You are a data-platform architect narrating a dependency-graph DELTA between two snapshots. Walk through added / removed / changed nodes and edges in plain English. Call out structural shifts (new sources, dropped consumers, type changes) and likely-but-not-yet-broken downstream consequences.'\n : 'You are a data-platform architect explaining a dependency graph. Describe the dominant data-flow shape, point out hot or fragile nodes, and recommend any decomposition the graph suggests.',\n },\n opts as { explain?: boolean },\n () => (diff ? buildDiffPrompt(diff) : buildGraphPrompt(graph)),\n );\n });\n attachExplainFlag(cmd);\n return cmd;\n}\n\nfunction renderDiffMarkdown(d: DependencyGraphDiff): string {\n const lines: string[] = ['# Dependency-graph delta', ''];\n lines.push(\n `**Summary**: ${d.summary.nodeDelta >= 0 ? '+' : ''}${d.summary.nodeDelta} nodes, ` +\n `${d.summary.edgeDelta >= 0 ? '+' : ''}${d.summary.edgeDelta} edges. ` +\n `${d.addedNodes.length} added node(s), ${d.removedNodes.length} removed node(s), ` +\n `${d.changedNodes.length} changed node(s).`,\n );\n lines.push('');\n if (d.addedNodes.length > 0) {\n lines.push('## Added nodes');\n for (const n of d.addedNodes) lines.push(`- \\`${n.fqn}\\` (${n.objectType})`);\n lines.push('');\n }\n if (d.removedNodes.length > 0) {\n lines.push('## Removed nodes');\n for (const n of d.removedNodes) lines.push(`- \\`${n.fqn}\\` (${n.objectType})`);\n lines.push('');\n }\n if (d.changedNodes.length > 0) {\n lines.push('## Changed nodes (objectType)');\n for (const c of d.changedNodes) lines.push(`- \\`${c.fqn}\\`: ${c.before} → ${c.after}`);\n lines.push('');\n }\n if (d.addedEdges.length > 0) {\n lines.push('## Added edges');\n for (const e of d.addedEdges) lines.push(`- \\`${e.from}\\` → \\`${e.to}\\` (${e.kind})`);\n lines.push('');\n }\n if (d.removedEdges.length > 0) {\n lines.push('## Removed edges');\n for (const e of d.removedEdges) lines.push(`- \\`${e.from}\\` → \\`${e.to}\\` (${e.kind})`);\n }\n return lines.join('\\n');\n}\n\nfunction buildGraphPrompt(g: DependencyGraph): string {\n const sample = g.edges.slice(0, 40).map((e) => ` - ${e.from} -> ${e.to}`);\n return [\n `Dependency graph: ${g.nodes.length} nodes, ${g.edges.length} edges.`,\n '',\n 'Edges (up to 40):',\n ...sample,\n '',\n 'Narrate this graph in plain English.',\n ].join('\\n');\n}\n\nfunction buildDiffPrompt(d: DependencyGraphDiff): string {\n const lines: string[] = [];\n lines.push(\n `Graph delta: ${d.summary.nodeDelta >= 0 ? '+' : ''}${d.summary.nodeDelta} nodes, ` +\n `${d.summary.edgeDelta >= 0 ? '+' : ''}${d.summary.edgeDelta} edges.`,\n );\n if (d.addedNodes.length > 0) {\n lines.push('', `Added nodes (${d.addedNodes.length}, up to 20):`);\n for (const n of d.addedNodes.slice(0, 20)) lines.push(` + ${n.fqn} (${n.objectType})`);\n }\n if (d.removedNodes.length > 0) {\n lines.push('', `Removed nodes (${d.removedNodes.length}, up to 20):`);\n for (const n of d.removedNodes.slice(0, 20)) lines.push(` - ${n.fqn} (${n.objectType})`);\n }\n if (d.changedNodes.length > 0) {\n lines.push('', `Changed nodes (${d.changedNodes.length}):`);\n for (const c of d.changedNodes.slice(0, 20))\n lines.push(` ~ ${c.fqn}: ${c.before} -> ${c.after}`);\n }\n if (d.addedEdges.length > 0) {\n lines.push('', `Added edges (${d.addedEdges.length}, up to 20):`);\n for (const e of d.addedEdges.slice(0, 20)) lines.push(` + ${e.from} -> ${e.to}`);\n }\n if (d.removedEdges.length > 0) {\n lines.push('', `Removed edges (${d.removedEdges.length}, up to 20):`);\n for (const e of d.removedEdges.slice(0, 20)) lines.push(` - ${e.from} -> ${e.to}`);\n }\n lines.push(\n '',\n 'Narrate this delta in plain English. Highlight structural shifts and likely downstream consequences.',\n );\n return lines.join('\\n');\n}\n\nasync function loadModel(sourcePath: string): Promise<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"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import "./chunk-DGUM43GV.js";
|
|
2
|
+
|
|
3
|
+
// src/commands/history.ts
|
|
4
|
+
import { promises as fs } from "fs";
|
|
5
|
+
import path from "path";
|
|
6
|
+
import { Command } from "commander";
|
|
7
|
+
import { createConnection, getProfile, queryHistory } from "@ddt-tools/core";
|
|
8
|
+
function historyCommand() {
|
|
9
|
+
const cmd = new Command("history");
|
|
10
|
+
cmd.description(
|
|
11
|
+
"List deploy manifests (default) or query live system.query.* via --last/--query/--verify."
|
|
12
|
+
).option(
|
|
13
|
+
"--dir <path>",
|
|
14
|
+
"Directory containing *.json manifests (manifest mode).",
|
|
15
|
+
"./.ddt/history"
|
|
16
|
+
).option("--limit <n>", "Manifest mode: most-recent N entries. Live mode: max rows.", "20").option("--json", "Emit JSON instead of human-readable table.", false).option(
|
|
17
|
+
"--full-sql",
|
|
18
|
+
"Include full SQL text in the output (default truncated to 120 chars).",
|
|
19
|
+
false
|
|
20
|
+
).option(
|
|
21
|
+
"--connection <profile>",
|
|
22
|
+
"Connection profile (required for --last / --query / --verify)."
|
|
23
|
+
).option("--last [n]", "Live mode: show the N most-recent queries. Default 20.").option("--query <id>", "Live mode: look up a single query by its statement_id.").option(
|
|
24
|
+
"--verify <tag>",
|
|
25
|
+
"Live mode: read all queries tagged with this string. Use the `DDT-TAG` auto-tag from `ddt publish --apply`."
|
|
26
|
+
).option("--since <iso>", "Live mode: only include entries after this ISO 8601 timestamp.").action(
|
|
27
|
+
async (opts) => {
|
|
28
|
+
const isLive = opts.last !== void 0 || opts.query !== void 0 || opts.verify !== void 0;
|
|
29
|
+
if (isLive) await runLive(opts);
|
|
30
|
+
else await runManifests(opts);
|
|
31
|
+
}
|
|
32
|
+
);
|
|
33
|
+
return cmd;
|
|
34
|
+
}
|
|
35
|
+
async function runManifests(opts) {
|
|
36
|
+
const dir = path.resolve(String(opts.dir ?? "./.ddt/history"));
|
|
37
|
+
let files;
|
|
38
|
+
try {
|
|
39
|
+
const entries2 = await fs.readdir(dir);
|
|
40
|
+
files = entries2.filter((f) => f.endsWith(".json")).map((f) => path.join(dir, f));
|
|
41
|
+
} catch (err) {
|
|
42
|
+
if (err.code === "ENOENT") {
|
|
43
|
+
console.error(
|
|
44
|
+
`No history directory at ${dir}. Pass --dir <path> or pipe deploys to write manifests there.`
|
|
45
|
+
);
|
|
46
|
+
process.exitCode = 1;
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
throw err;
|
|
50
|
+
}
|
|
51
|
+
const entries = [];
|
|
52
|
+
for (const f of files) {
|
|
53
|
+
try {
|
|
54
|
+
const raw = await fs.readFile(f, "utf8");
|
|
55
|
+
const m = JSON.parse(raw);
|
|
56
|
+
const stat = await fs.stat(f);
|
|
57
|
+
entries.push({ path: f, mtime: stat.mtime, manifest: m });
|
|
58
|
+
} catch {
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
entries.sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
|
|
62
|
+
const limit = parseInt(String(opts.limit ?? 20), 10);
|
|
63
|
+
const visible = entries.slice(0, limit > 0 ? limit : entries.length);
|
|
64
|
+
if (opts.json) {
|
|
65
|
+
console.log(
|
|
66
|
+
JSON.stringify(
|
|
67
|
+
visible.map((e) => ({ path: e.path, mtime: e.mtime.toISOString(), ...e.manifest })),
|
|
68
|
+
null,
|
|
69
|
+
2
|
|
70
|
+
)
|
|
71
|
+
);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
if (visible.length === 0) {
|
|
75
|
+
console.log(`No manifests found in ${dir}.`);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
console.log(`History: ${visible.length} of ${entries.length} deploys at ${dir}`);
|
|
79
|
+
console.log("");
|
|
80
|
+
console.log(
|
|
81
|
+
" DEPLOYED AT WORKSPACE FINAL STATE STEPS"
|
|
82
|
+
);
|
|
83
|
+
console.log(
|
|
84
|
+
" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500"
|
|
85
|
+
);
|
|
86
|
+
for (const e of visible) {
|
|
87
|
+
const m = e.manifest;
|
|
88
|
+
const when = m.deployedAt ? new Date(m.deployedAt).toISOString().slice(0, 19).replace("T", " ") : e.mtime.toISOString().slice(0, 19).replace("T", " ");
|
|
89
|
+
const host = (m.workspaceHost ?? "?").padEnd(34).slice(0, 34);
|
|
90
|
+
const state = (m.finalState ?? "?").padEnd(18).slice(0, 18);
|
|
91
|
+
const succ = m.steps?.filter((s) => s.status === "SUCCESS").length ?? 0;
|
|
92
|
+
const tot = m.steps?.length ?? 0;
|
|
93
|
+
console.log(` ${when} ${host} ${state} ${succ}/${tot}`);
|
|
94
|
+
if (opts.fullSql) {
|
|
95
|
+
for (const s of m.steps ?? []) {
|
|
96
|
+
if (s.forwardSql) console.log(` [forward] ${s.fqn ?? ""}
|
|
97
|
+
${indent(s.forwardSql, 8)}`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
console.log("");
|
|
102
|
+
console.log(
|
|
103
|
+
`Pass --json for machine-readable output, --limit <n> to control depth, --full-sql to expand each step.`
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
async function runLive(opts) {
|
|
107
|
+
if (!opts.connection) {
|
|
108
|
+
console.error("Live history mode requires --connection <profile>.");
|
|
109
|
+
process.exitCode = 1;
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
const profile = await getProfile(String(opts.connection));
|
|
113
|
+
const conn = createConnection(profile);
|
|
114
|
+
await conn.connect();
|
|
115
|
+
try {
|
|
116
|
+
const reader = new queryHistory.DatabricksQueryHistoryReader(conn);
|
|
117
|
+
const limit = Math.max(1, parseInt(String(opts.limit ?? "20"), 10) || 20);
|
|
118
|
+
const since = opts.since ? new Date(opts.since) : void 0;
|
|
119
|
+
let entries = [];
|
|
120
|
+
if (opts.query) {
|
|
121
|
+
const e = await reader.readById(String(opts.query));
|
|
122
|
+
entries = e ? [e] : [];
|
|
123
|
+
} else if (opts.verify) {
|
|
124
|
+
entries = await reader.readByTag(String(opts.verify), {
|
|
125
|
+
...since ? { since } : {},
|
|
126
|
+
limit
|
|
127
|
+
});
|
|
128
|
+
} else {
|
|
129
|
+
const n = typeof opts.last === "string" ? parseInt(opts.last, 10) || limit : limit;
|
|
130
|
+
const user = profile.auth.user ?? profile.auth.principalEmail ?? "";
|
|
131
|
+
entries = await reader.readByUser(user, {
|
|
132
|
+
...since ? { since } : {},
|
|
133
|
+
limit: n
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
if (opts.json) {
|
|
137
|
+
process.stdout.write(JSON.stringify(entries, null, 2) + "\n");
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
renderEntries(entries, Boolean(opts.fullSql));
|
|
141
|
+
} finally {
|
|
142
|
+
await conn.disconnect();
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
function renderEntries(entries, fullSql) {
|
|
146
|
+
if (entries.length === 0) {
|
|
147
|
+
console.log("No matching queries found.");
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
console.log(`${entries.length} quer${entries.length === 1 ? "y" : "ies"}:`);
|
|
151
|
+
console.log("");
|
|
152
|
+
console.log(" STARTED STATUS DURATION USER STATEMENT_ID");
|
|
153
|
+
console.log(
|
|
154
|
+
" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"
|
|
155
|
+
);
|
|
156
|
+
for (const e of entries) {
|
|
157
|
+
const when = e.startedAt.slice(0, 19).replace("T", " ");
|
|
158
|
+
const status = e.status.toUpperCase().padEnd(8);
|
|
159
|
+
const dur = `${e.durationMs}ms`.padEnd(8);
|
|
160
|
+
const user = (e.user ?? "?").padEnd(20).slice(0, 20);
|
|
161
|
+
console.log(` ${when} ${status} ${dur} ${user} ${e.queryId}`);
|
|
162
|
+
const sql = fullSql ? e.sqlText : truncate(e.sqlText, 120);
|
|
163
|
+
if (sql)
|
|
164
|
+
console.log(
|
|
165
|
+
` ${sql.split("\n").map((l) => l.trim()).filter(Boolean).join(" ")}`
|
|
166
|
+
);
|
|
167
|
+
if (e.queryTag) console.log(` tag: ${e.queryTag}`);
|
|
168
|
+
if (e.errorMessage) console.log(` ERROR: ${e.errorMessage}`);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
function indent(text, n) {
|
|
172
|
+
const pad = " ".repeat(n);
|
|
173
|
+
return text.split("\n").map((l) => pad + l).join("\n");
|
|
174
|
+
}
|
|
175
|
+
function truncate(s, max) {
|
|
176
|
+
if (!s) return "";
|
|
177
|
+
const collapsed = s.replace(/\s+/g, " ").trim();
|
|
178
|
+
if (collapsed.length <= max) return collapsed;
|
|
179
|
+
return collapsed.slice(0, max - 1) + "\u2026";
|
|
180
|
+
}
|
|
181
|
+
export {
|
|
182
|
+
historyCommand
|
|
183
|
+
};
|
|
184
|
+
//# sourceMappingURL=history-GDRFP4PG.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/history.ts"],"sourcesContent":["import { promises as fs } from 'node:fs';\nimport path from 'node:path';\nimport { Command } from 'commander';\nimport { createConnection, getProfile, queryHistory } from '@ddt-tools/core';\n\ntype QueryHistoryEntry = queryHistory.QueryHistoryEntry;\n\n/**\n * `ddt history` — two modes.\n *\n * 1. **Manifest mode** (default) — read deploy manifests from a\n * directory and show a chronological summary.\n *\n * 2. **Live mode** (triggered by `--last`, `--query`, or `--verify`)\n * — query the workspace `system.query.*` tables via the\n * DatabricksQueryHistoryReader. Requires `--connection <profile>`.\n *\n * Mirrors `sdt history`.\n */\ninterface Manifest {\n version: 1;\n deployedAt?: string;\n workspaceHost?: string;\n finalState?: string;\n failedStepId?: string | null;\n steps?: Array<{\n status?: string;\n objectType?: string;\n fqn?: string;\n forwardSql?: string;\n reverseSql?: string;\n }>;\n}\n\nexport function historyCommand(): Command {\n const cmd = new Command('history');\n cmd\n .description(\n 'List deploy manifests (default) or query live system.query.* via --last/--query/--verify.',\n )\n .option(\n '--dir <path>',\n 'Directory containing *.json manifests (manifest mode).',\n './.ddt/history',\n )\n .option('--limit <n>', 'Manifest mode: most-recent N entries. Live mode: max rows.', '20')\n .option('--json', 'Emit JSON instead of human-readable table.', false)\n .option(\n '--full-sql',\n 'Include full SQL text in the output (default truncated to 120 chars).',\n false,\n )\n .option(\n '--connection <profile>',\n 'Connection profile (required for --last / --query / --verify).',\n )\n .option('--last [n]', 'Live mode: show the N most-recent queries. Default 20.')\n .option('--query <id>', 'Live mode: look up a single query by its statement_id.')\n .option(\n '--verify <tag>',\n 'Live mode: read all queries tagged with this string. Use the `DDT-TAG` auto-tag from `ddt publish --apply`.',\n )\n .option('--since <iso>', 'Live mode: only include entries after this ISO 8601 timestamp.')\n .action(\n async (opts: {\n dir?: string;\n limit?: string;\n json?: boolean;\n fullSql?: boolean;\n connection?: string;\n last?: boolean | string;\n query?: string;\n verify?: string;\n since?: string;\n }) => {\n const isLive =\n opts.last !== undefined || opts.query !== undefined || opts.verify !== undefined;\n if (isLive) await runLive(opts);\n else await runManifests(opts);\n },\n );\n return cmd;\n}\n\nasync function runManifests(opts: {\n dir?: string;\n limit?: string;\n json?: boolean;\n fullSql?: boolean;\n}): Promise<void> {\n const dir = path.resolve(String(opts.dir ?? './.ddt/history'));\n let files: string[];\n try {\n const entries = await fs.readdir(dir);\n files = entries.filter((f) => f.endsWith('.json')).map((f) => path.join(dir, f));\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') {\n console.error(\n `No history directory at ${dir}. Pass --dir <path> or pipe deploys to write manifests there.`,\n );\n process.exitCode = 1;\n return;\n }\n throw err;\n }\n\n const entries: Array<{ path: string; mtime: Date; manifest: Manifest }> = [];\n for (const f of files) {\n try {\n const raw = await fs.readFile(f, 'utf8');\n const m = JSON.parse(raw) as Manifest;\n const stat = await fs.stat(f);\n entries.push({ path: f, mtime: stat.mtime, manifest: m });\n } catch {\n // skip files that don't parse\n }\n }\n entries.sort((a, b) => b.mtime.getTime() - a.mtime.getTime());\n\n const limit = parseInt(String(opts.limit ?? 20), 10);\n const visible = entries.slice(0, limit > 0 ? limit : entries.length);\n\n if (opts.json) {\n console.log(\n JSON.stringify(\n visible.map((e) => ({ path: e.path, mtime: e.mtime.toISOString(), ...e.manifest })),\n null,\n 2,\n ),\n );\n return;\n }\n\n if (visible.length === 0) {\n console.log(`No manifests found in ${dir}.`);\n return;\n }\n console.log(`History: ${visible.length} of ${entries.length} deploys at ${dir}`);\n console.log('');\n console.log(\n ' DEPLOYED AT WORKSPACE FINAL STATE STEPS',\n );\n console.log(\n ' ─────────────────── ────────────────────────────────── ────────────────── ─────',\n );\n for (const e of visible) {\n const m = e.manifest;\n const when = m.deployedAt\n ? new Date(m.deployedAt).toISOString().slice(0, 19).replace('T', ' ')\n : e.mtime.toISOString().slice(0, 19).replace('T', ' ');\n const host = (m.workspaceHost ?? '?').padEnd(34).slice(0, 34);\n const state = (m.finalState ?? '?').padEnd(18).slice(0, 18);\n const succ = m.steps?.filter((s) => s.status === 'SUCCESS').length ?? 0;\n const tot = m.steps?.length ?? 0;\n console.log(` ${when} ${host} ${state} ${succ}/${tot}`);\n if (opts.fullSql) {\n for (const s of m.steps ?? []) {\n if (s.forwardSql) console.log(` [forward] ${s.fqn ?? ''}\\n${indent(s.forwardSql, 8)}`);\n }\n }\n }\n console.log('');\n console.log(\n `Pass --json for machine-readable output, --limit <n> to control depth, --full-sql to expand each step.`,\n );\n}\n\nasync function runLive(opts: {\n connection?: string;\n last?: boolean | string;\n query?: string;\n verify?: string;\n since?: string;\n limit?: string;\n json?: boolean;\n fullSql?: boolean;\n}): Promise<void> {\n if (!opts.connection) {\n console.error('Live history mode requires --connection <profile>.');\n process.exitCode = 1;\n return;\n }\n const profile = await getProfile(String(opts.connection));\n const conn = createConnection(profile);\n await conn.connect();\n try {\n const reader = new queryHistory.DatabricksQueryHistoryReader(conn);\n const limit = Math.max(1, parseInt(String(opts.limit ?? '20'), 10) || 20);\n const since = opts.since ? new Date(opts.since) : undefined;\n\n let entries: QueryHistoryEntry[] = [];\n if (opts.query) {\n const e = await reader.readById(String(opts.query));\n entries = e ? [e] : [];\n } else if (opts.verify) {\n entries = await reader.readByTag(String(opts.verify), {\n ...(since ? { since } : {}),\n limit,\n });\n } else {\n const n = typeof opts.last === 'string' ? parseInt(opts.last, 10) || limit : limit;\n const user =\n (profile.auth as { user?: string; principalEmail?: string }).user ??\n (profile.auth as { principalEmail?: string }).principalEmail ??\n '';\n entries = await reader.readByUser(user, {\n ...(since ? { since } : {}),\n limit: n,\n });\n }\n\n if (opts.json) {\n process.stdout.write(JSON.stringify(entries, null, 2) + '\\n');\n return;\n }\n renderEntries(entries, Boolean(opts.fullSql));\n } finally {\n await conn.disconnect();\n }\n}\n\nfunction renderEntries(entries: readonly QueryHistoryEntry[], fullSql: boolean): void {\n if (entries.length === 0) {\n console.log('No matching queries found.');\n return;\n }\n console.log(`${entries.length} quer${entries.length === 1 ? 'y' : 'ies'}:`);\n console.log('');\n console.log(' STARTED STATUS DURATION USER STATEMENT_ID');\n console.log(\n ' ─────────────────── ──────── ──────── ───────────────────── ────────────────────────────',\n );\n for (const e of entries) {\n const when = e.startedAt.slice(0, 19).replace('T', ' ');\n const status = e.status.toUpperCase().padEnd(8);\n const dur = `${e.durationMs}ms`.padEnd(8);\n const user = (e.user ?? '?').padEnd(20).slice(0, 20);\n console.log(` ${when} ${status} ${dur} ${user} ${e.queryId}`);\n const sql = fullSql ? e.sqlText : truncate(e.sqlText, 120);\n if (sql)\n console.log(\n ` ${sql\n .split('\\n')\n .map((l: string) => l.trim())\n .filter(Boolean)\n .join(' ')}`,\n );\n if (e.queryTag) console.log(` tag: ${e.queryTag}`);\n if (e.errorMessage) console.log(` ERROR: ${e.errorMessage}`);\n }\n}\n\nfunction indent(text: string, n: number): string {\n const pad = ' '.repeat(n);\n return text\n .split('\\n')\n .map((l) => pad + l)\n .join('\\n');\n}\n\nfunction truncate(s: string, max: number): string {\n if (!s) return '';\n const collapsed = s.replace(/\\s+/g, ' ').trim();\n if (collapsed.length <= max) return collapsed;\n return collapsed.slice(0, max - 1) + '…';\n}\n"],"mappings":";;;AAAA,SAAS,YAAY,UAAU;AAC/B,OAAO,UAAU;AACjB,SAAS,eAAe;AACxB,SAAS,kBAAkB,YAAY,oBAAoB;AA+BpD,SAAS,iBAA0B;AACxC,QAAM,MAAM,IAAI,QAAQ,SAAS;AACjC,MACG;AAAA,IACC;AAAA,EACF,EACC;AAAA,IACC;AAAA,IACA;AAAA,IACA;AAAA,EACF,EACC,OAAO,eAAe,8DAA8D,IAAI,EACxF,OAAO,UAAU,8CAA8C,KAAK,EACpE;AAAA,IACC;AAAA,IACA;AAAA,IACA;AAAA,EACF,EACC;AAAA,IACC;AAAA,IACA;AAAA,EACF,EACC,OAAO,cAAc,wDAAwD,EAC7E,OAAO,gBAAgB,wDAAwD,EAC/E;AAAA,IACC;AAAA,IACA;AAAA,EACF,EACC,OAAO,iBAAiB,gEAAgE,EACxF;AAAA,IACC,OAAO,SAUD;AACJ,YAAM,SACJ,KAAK,SAAS,UAAa,KAAK,UAAU,UAAa,KAAK,WAAW;AACzE,UAAI,OAAQ,OAAM,QAAQ,IAAI;AAAA,UACzB,OAAM,aAAa,IAAI;AAAA,IAC9B;AAAA,EACF;AACF,SAAO;AACT;AAEA,eAAe,aAAa,MAKV;AAChB,QAAM,MAAM,KAAK,QAAQ,OAAO,KAAK,OAAO,gBAAgB,CAAC;AAC7D,MAAI;AACJ,MAAI;AACF,UAAMA,WAAU,MAAM,GAAG,QAAQ,GAAG;AACpC,YAAQA,SAAQ,OAAO,CAAC,MAAM,EAAE,SAAS,OAAO,CAAC,EAAE,IAAI,CAAC,MAAM,KAAK,KAAK,KAAK,CAAC,CAAC;AAAA,EACjF,SAAS,KAAK;AACZ,QAAK,IAA8B,SAAS,UAAU;AACpD,cAAQ;AAAA,QACN,2BAA2B,GAAG;AAAA,MAChC;AACA,cAAQ,WAAW;AACnB;AAAA,IACF;AACA,UAAM;AAAA,EACR;AAEA,QAAM,UAAoE,CAAC;AAC3E,aAAW,KAAK,OAAO;AACrB,QAAI;AACF,YAAM,MAAM,MAAM,GAAG,SAAS,GAAG,MAAM;AACvC,YAAM,IAAI,KAAK,MAAM,GAAG;AACxB,YAAM,OAAO,MAAM,GAAG,KAAK,CAAC;AAC5B,cAAQ,KAAK,EAAE,MAAM,GAAG,OAAO,KAAK,OAAO,UAAU,EAAE,CAAC;AAAA,IAC1D,QAAQ;AAAA,IAER;AAAA,EACF;AACA,UAAQ,KAAK,CAAC,GAAG,MAAM,EAAE,MAAM,QAAQ,IAAI,EAAE,MAAM,QAAQ,CAAC;AAE5D,QAAM,QAAQ,SAAS,OAAO,KAAK,SAAS,EAAE,GAAG,EAAE;AACnD,QAAM,UAAU,QAAQ,MAAM,GAAG,QAAQ,IAAI,QAAQ,QAAQ,MAAM;AAEnE,MAAI,KAAK,MAAM;AACb,YAAQ;AAAA,MACN,KAAK;AAAA,QACH,QAAQ,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,OAAO,EAAE,MAAM,YAAY,GAAG,GAAG,EAAE,SAAS,EAAE;AAAA,QAClF;AAAA,QACA;AAAA,MACF;AAAA,IACF;AACA;AAAA,EACF;AAEA,MAAI,QAAQ,WAAW,GAAG;AACxB,YAAQ,IAAI,yBAAyB,GAAG,GAAG;AAC3C;AAAA,EACF;AACA,UAAQ,IAAI,YAAY,QAAQ,MAAM,OAAO,QAAQ,MAAM,eAAe,GAAG,EAAE;AAC/E,UAAQ,IAAI,EAAE;AACd,UAAQ;AAAA,IACN;AAAA,EACF;AACA,UAAQ;AAAA,IACN;AAAA,EACF;AACA,aAAW,KAAK,SAAS;AACvB,UAAM,IAAI,EAAE;AACZ,UAAM,OAAO,EAAE,aACX,IAAI,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,GAAG,EAAE,EAAE,QAAQ,KAAK,GAAG,IAClE,EAAE,MAAM,YAAY,EAAE,MAAM,GAAG,EAAE,EAAE,QAAQ,KAAK,GAAG;AACvD,UAAM,QAAQ,EAAE,iBAAiB,KAAK,OAAO,EAAE,EAAE,MAAM,GAAG,EAAE;AAC5D,UAAM,SAAS,EAAE,cAAc,KAAK,OAAO,EAAE,EAAE,MAAM,GAAG,EAAE;AAC1D,UAAM,OAAO,EAAE,OAAO,OAAO,CAAC,MAAM,EAAE,WAAW,SAAS,EAAE,UAAU;AACtE,UAAM,MAAM,EAAE,OAAO,UAAU;AAC/B,YAAQ,IAAI,KAAK,IAAI,KAAK,IAAI,KAAK,KAAK,KAAK,IAAI,IAAI,GAAG,EAAE;AAC1D,QAAI,KAAK,SAAS;AAChB,iBAAW,KAAK,EAAE,SAAS,CAAC,GAAG;AAC7B,YAAI,EAAE,WAAY,SAAQ,IAAI,mBAAmB,EAAE,OAAO,EAAE;AAAA,EAAK,OAAO,EAAE,YAAY,CAAC,CAAC,EAAE;AAAA,MAC5F;AAAA,IACF;AAAA,EACF;AACA,UAAQ,IAAI,EAAE;AACd,UAAQ;AAAA,IACN;AAAA,EACF;AACF;AAEA,eAAe,QAAQ,MASL;AAChB,MAAI,CAAC,KAAK,YAAY;AACpB,YAAQ,MAAM,oDAAoD;AAClE,YAAQ,WAAW;AACnB;AAAA,EACF;AACA,QAAM,UAAU,MAAM,WAAW,OAAO,KAAK,UAAU,CAAC;AACxD,QAAM,OAAO,iBAAiB,OAAO;AACrC,QAAM,KAAK,QAAQ;AACnB,MAAI;AACF,UAAM,SAAS,IAAI,aAAa,6BAA6B,IAAI;AACjE,UAAM,QAAQ,KAAK,IAAI,GAAG,SAAS,OAAO,KAAK,SAAS,IAAI,GAAG,EAAE,KAAK,EAAE;AACxE,UAAM,QAAQ,KAAK,QAAQ,IAAI,KAAK,KAAK,KAAK,IAAI;AAElD,QAAI,UAA+B,CAAC;AACpC,QAAI,KAAK,OAAO;AACd,YAAM,IAAI,MAAM,OAAO,SAAS,OAAO,KAAK,KAAK,CAAC;AAClD,gBAAU,IAAI,CAAC,CAAC,IAAI,CAAC;AAAA,IACvB,WAAW,KAAK,QAAQ;AACtB,gBAAU,MAAM,OAAO,UAAU,OAAO,KAAK,MAAM,GAAG;AAAA,QACpD,GAAI,QAAQ,EAAE,MAAM,IAAI,CAAC;AAAA,QACzB;AAAA,MACF,CAAC;AAAA,IACH,OAAO;AACL,YAAM,IAAI,OAAO,KAAK,SAAS,WAAW,SAAS,KAAK,MAAM,EAAE,KAAK,QAAQ;AAC7E,YAAM,OACH,QAAQ,KAAoD,QAC5D,QAAQ,KAAqC,kBAC9C;AACF,gBAAU,MAAM,OAAO,WAAW,MAAM;AAAA,QACtC,GAAI,QAAQ,EAAE,MAAM,IAAI,CAAC;AAAA,QACzB,OAAO;AAAA,MACT,CAAC;AAAA,IACH;AAEA,QAAI,KAAK,MAAM;AACb,cAAQ,OAAO,MAAM,KAAK,UAAU,SAAS,MAAM,CAAC,IAAI,IAAI;AAC5D;AAAA,IACF;AACA,kBAAc,SAAS,QAAQ,KAAK,OAAO,CAAC;AAAA,EAC9C,UAAE;AACA,UAAM,KAAK,WAAW;AAAA,EACxB;AACF;AAEA,SAAS,cAAc,SAAuC,SAAwB;AACpF,MAAI,QAAQ,WAAW,GAAG;AACxB,YAAQ,IAAI,4BAA4B;AACxC;AAAA,EACF;AACA,UAAQ,IAAI,GAAG,QAAQ,MAAM,QAAQ,QAAQ,WAAW,IAAI,MAAM,KAAK,GAAG;AAC1E,UAAQ,IAAI,EAAE;AACd,UAAQ,IAAI,+EAA+E;AAC3F,UAAQ;AAAA,IACN;AAAA,EACF;AACA,aAAW,KAAK,SAAS;AACvB,UAAM,OAAO,EAAE,UAAU,MAAM,GAAG,EAAE,EAAE,QAAQ,KAAK,GAAG;AACtD,UAAM,SAAS,EAAE,OAAO,YAAY,EAAE,OAAO,CAAC;AAC9C,UAAM,MAAM,GAAG,EAAE,UAAU,KAAK,OAAO,CAAC;AACxC,UAAM,QAAQ,EAAE,QAAQ,KAAK,OAAO,EAAE,EAAE,MAAM,GAAG,EAAE;AACnD,YAAQ,IAAI,KAAK,IAAI,KAAK,MAAM,KAAK,GAAG,KAAK,IAAI,KAAK,EAAE,OAAO,EAAE;AACjE,UAAM,MAAM,UAAU,EAAE,UAAU,SAAS,EAAE,SAAS,GAAG;AACzD,QAAI;AACF,cAAQ;AAAA,QACN,SAAS,IACN,MAAM,IAAI,EACV,IAAI,CAAC,MAAc,EAAE,KAAK,CAAC,EAC3B,OAAO,OAAO,EACd,KAAK,GAAG,CAAC;AAAA,MACd;AACF,QAAI,EAAE,SAAU,SAAQ,IAAI,cAAc,EAAE,QAAQ,EAAE;AACtD,QAAI,EAAE,aAAc,SAAQ,IAAI,gBAAgB,EAAE,YAAY,EAAE;AAAA,EAClE;AACF;AAEA,SAAS,OAAO,MAAc,GAAmB;AAC/C,QAAM,MAAM,IAAI,OAAO,CAAC;AACxB,SAAO,KACJ,MAAM,IAAI,EACV,IAAI,CAAC,MAAM,MAAM,CAAC,EAClB,KAAK,IAAI;AACd;AAEA,SAAS,SAAS,GAAW,KAAqB;AAChD,MAAI,CAAC,EAAG,QAAO;AACf,QAAM,YAAY,EAAE,QAAQ,QAAQ,GAAG,EAAE,KAAK;AAC9C,MAAI,UAAU,UAAU,IAAK,QAAO;AACpC,SAAO,UAAU,MAAM,GAAG,MAAM,CAAC,IAAI;AACvC;","names":["entries"]}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import "./chunk-DGUM43GV.js";
|
|
2
|
+
|
|
3
|
+
// src/commands/hosts.ts
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
import { hosts as hostsApi } from "@ddt-tools/core";
|
|
6
|
+
function hostsCommand() {
|
|
7
|
+
const cmd = new Command("hosts");
|
|
8
|
+
cmd.description(
|
|
9
|
+
"Browse every host DDT plugs into \u2014 CLI, VS Code, MCP agents, Databricks Workflows, dbt, Airflow, etc. Shows supported versions and the host-specific tailoring notes."
|
|
10
|
+
);
|
|
11
|
+
cmd.command("list").description("List every host adapter with its status and version range.").action(() => {
|
|
12
|
+
const rows = hostsApi.listHosts();
|
|
13
|
+
const nameWidth = Math.max(...rows.map((h) => h.name.length), 4);
|
|
14
|
+
let lastKind = "";
|
|
15
|
+
for (const host of rows) {
|
|
16
|
+
if (host.kind !== lastKind) {
|
|
17
|
+
if (lastKind !== "") process.stdout.write("\n");
|
|
18
|
+
process.stdout.write(`\u2500\u2500 ${host.kind.toUpperCase()} \u2500\u2500
|
|
19
|
+
`);
|
|
20
|
+
lastKind = host.kind;
|
|
21
|
+
}
|
|
22
|
+
const pad = (s, w) => s + " ".repeat(Math.max(0, w - s.length));
|
|
23
|
+
const versions = hostsApi.hostSummaryLine(host).split(" \xB7 ")[1] ?? "";
|
|
24
|
+
process.stdout.write(` ${pad(host.name, nameWidth)} ${versions}
|
|
25
|
+
`);
|
|
26
|
+
}
|
|
27
|
+
process.stdout.write(
|
|
28
|
+
"\nRun `ddt hosts show <id>` for tailoring notes. See docs/HOST_INTEGRATIONS.md for the per-host playbook.\n"
|
|
29
|
+
);
|
|
30
|
+
});
|
|
31
|
+
cmd.command("show <id>").description("Print supported versions, idioms, and tailoring notes for one host.").action((id) => {
|
|
32
|
+
const host = hostsApi.getHost(id);
|
|
33
|
+
if (!host) {
|
|
34
|
+
console.error(`Unknown host id: "${id}". Run \`ddt hosts list\` to see all ids.`);
|
|
35
|
+
process.exitCode = 1;
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
process.stdout.write(hostsApi.hostDetailMessage(host) + "\n");
|
|
39
|
+
});
|
|
40
|
+
return cmd;
|
|
41
|
+
}
|
|
42
|
+
export {
|
|
43
|
+
hostsCommand
|
|
44
|
+
};
|
|
45
|
+
//# sourceMappingURL=hosts-DRFZTMIJ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/hosts.ts"],"sourcesContent":["/**\n * `ddt hosts` — browse every host DDT plugs into, with version-support\n * + tailoring notes. Companion to `ddt features`: features tell you\n * what we do; hosts tell you where you can do it.\n */\nimport { Command } from 'commander';\nimport { hosts as hostsApi } from '@ddt-tools/core';\n\nexport function hostsCommand(): Command {\n const cmd = new Command('hosts');\n cmd.description(\n 'Browse every host DDT plugs into — CLI, VS Code, MCP agents, Databricks Workflows, dbt, Airflow, etc. Shows supported versions and the host-specific tailoring notes.',\n );\n\n cmd\n .command('list')\n .description('List every host adapter with its status and version range.')\n .action(() => {\n const rows = hostsApi.listHosts();\n const nameWidth = Math.max(...rows.map((h) => h.name.length), 4);\n let lastKind = '';\n for (const host of rows) {\n if (host.kind !== lastKind) {\n if (lastKind !== '') process.stdout.write('\\n');\n process.stdout.write(`── ${host.kind.toUpperCase()} ──\\n`);\n lastKind = host.kind;\n }\n const pad = (s: string, w: number): string => s + ' '.repeat(Math.max(0, w - s.length));\n const versions = hostsApi.hostSummaryLine(host).split(' · ')[1] ?? '';\n process.stdout.write(` ${pad(host.name, nameWidth)} ${versions}\\n`);\n }\n process.stdout.write(\n '\\nRun `ddt hosts show <id>` for tailoring notes. See docs/HOST_INTEGRATIONS.md for the per-host playbook.\\n',\n );\n });\n\n cmd\n .command('show <id>')\n .description('Print supported versions, idioms, and tailoring notes for one host.')\n .action((id: string) => {\n const host = hostsApi.getHost(id);\n if (!host) {\n console.error(`Unknown host id: \"${id}\". Run \\`ddt hosts list\\` to see all ids.`);\n process.exitCode = 1;\n return;\n }\n process.stdout.write(hostsApi.hostDetailMessage(host) + '\\n');\n });\n\n return cmd;\n}\n"],"mappings":";;;AAKA,SAAS,eAAe;AACxB,SAAS,SAAS,gBAAgB;AAE3B,SAAS,eAAwB;AACtC,QAAM,MAAM,IAAI,QAAQ,OAAO;AAC/B,MAAI;AAAA,IACF;AAAA,EACF;AAEA,MACG,QAAQ,MAAM,EACd,YAAY,4DAA4D,EACxE,OAAO,MAAM;AACZ,UAAM,OAAO,SAAS,UAAU;AAChC,UAAM,YAAY,KAAK,IAAI,GAAG,KAAK,IAAI,CAAC,MAAM,EAAE,KAAK,MAAM,GAAG,CAAC;AAC/D,QAAI,WAAW;AACf,eAAW,QAAQ,MAAM;AACvB,UAAI,KAAK,SAAS,UAAU;AAC1B,YAAI,aAAa,GAAI,SAAQ,OAAO,MAAM,IAAI;AAC9C,gBAAQ,OAAO,MAAM,gBAAM,KAAK,KAAK,YAAY,CAAC;AAAA,CAAO;AACzD,mBAAW,KAAK;AAAA,MAClB;AACA,YAAM,MAAM,CAAC,GAAW,MAAsB,IAAI,IAAI,OAAO,KAAK,IAAI,GAAG,IAAI,EAAE,MAAM,CAAC;AACtF,YAAM,WAAW,SAAS,gBAAgB,IAAI,EAAE,MAAM,QAAK,EAAE,CAAC,KAAK;AACnE,cAAQ,OAAO,MAAM,KAAK,IAAI,KAAK,MAAM,SAAS,CAAC,KAAK,QAAQ;AAAA,CAAI;AAAA,IACtE;AACA,YAAQ,OAAO;AAAA,MACb;AAAA,IACF;AAAA,EACF,CAAC;AAEH,MACG,QAAQ,WAAW,EACnB,YAAY,qEAAqE,EACjF,OAAO,CAAC,OAAe;AACtB,UAAM,OAAO,SAAS,QAAQ,EAAE;AAChC,QAAI,CAAC,MAAM;AACT,cAAQ,MAAM,qBAAqB,EAAE,2CAA2C;AAChF,cAAQ,WAAW;AACnB;AAAA,IACF;AACA,YAAQ,OAAO,MAAM,SAAS,kBAAkB,IAAI,IAAI,IAAI;AAAA,EAC9D,CAAC;AAEH,SAAO;AACT;","names":[]}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import {
|
|
2
|
+
attachExplainFlag,
|
|
3
|
+
runExplain
|
|
4
|
+
} from "./chunk-XFXG347C.js";
|
|
5
|
+
import "./chunk-DGUM43GV.js";
|
|
6
|
+
|
|
7
|
+
// src/commands/impact.ts
|
|
8
|
+
import { promises as fs } from "fs";
|
|
9
|
+
import path from "path";
|
|
10
|
+
import { Command } from "commander";
|
|
11
|
+
import {
|
|
12
|
+
loadProject,
|
|
13
|
+
pac,
|
|
14
|
+
parseProjectModel,
|
|
15
|
+
review
|
|
16
|
+
} from "@ddt-tools/core";
|
|
17
|
+
function impactCommand() {
|
|
18
|
+
const cmd = new Command("impact");
|
|
19
|
+
cmd.description(
|
|
20
|
+
"Single-FQN blast-radius: who feeds it, who reads from it, what findings apply to it."
|
|
21
|
+
).argument("<fqn>", "Fully-qualified name to analyze (e.g. main.gold.orders).").requiredOption("--source <path>", ".ddtproj or .ddtpac to analyze.").option("-o, --out <path>", "Output file path. Defaults to stdout.").action(async (fqn, opts) => {
|
|
22
|
+
const sourcePath = String(opts.source);
|
|
23
|
+
const model = await loadModel(sourcePath);
|
|
24
|
+
const md = review.renderImpactReport(model, String(fqn), { source: sourcePath });
|
|
25
|
+
await emit(md, opts.out);
|
|
26
|
+
await runExplain(
|
|
27
|
+
{
|
|
28
|
+
feature: "impact.explain",
|
|
29
|
+
systemPrompt: "You are a senior Databricks data engineer explaining an impact analysis to a developer who is about to change one object. Be very direct about the blast radius and what they should test before merging."
|
|
30
|
+
},
|
|
31
|
+
opts,
|
|
32
|
+
() => `Impact report for ${fqn} follows:
|
|
33
|
+
|
|
34
|
+
${md}
|
|
35
|
+
|
|
36
|
+
Explain the blast radius and what the engineer should pay attention to.`
|
|
37
|
+
);
|
|
38
|
+
});
|
|
39
|
+
attachExplainFlag(cmd);
|
|
40
|
+
return cmd;
|
|
41
|
+
}
|
|
42
|
+
async function loadModel(sourcePath) {
|
|
43
|
+
if (sourcePath.endsWith(".ddtpac")) {
|
|
44
|
+
const c = await pac.readPac(sourcePath);
|
|
45
|
+
return c.model;
|
|
46
|
+
}
|
|
47
|
+
const loaded = await loadProject(sourcePath);
|
|
48
|
+
return await parseProjectModel(loaded);
|
|
49
|
+
}
|
|
50
|
+
async function emit(payload, out) {
|
|
51
|
+
if (out) {
|
|
52
|
+
const p = path.resolve(String(out));
|
|
53
|
+
await fs.mkdir(path.dirname(p), { recursive: true });
|
|
54
|
+
await fs.writeFile(p, payload + (payload.endsWith("\n") ? "" : "\n"), "utf8");
|
|
55
|
+
console.error(`Wrote ${p} (${payload.length} bytes).`);
|
|
56
|
+
} else {
|
|
57
|
+
process.stdout.write(payload + (payload.endsWith("\n") ? "" : "\n"));
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
export {
|
|
61
|
+
impactCommand
|
|
62
|
+
};
|
|
63
|
+
//# sourceMappingURL=impact-A4NU6CB2.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/impact.ts"],"sourcesContent":["import { promises as fs } from 'node:fs';\nimport path from 'node:path';\nimport { Command } from 'commander';\nimport {\n loadProject,\n pac,\n parseProjectModel,\n review,\n type DatabricksObject,\n} from '@ddt-tools/core';\nimport { attachExplainFlag, runExplain } from '../util/ai-explain.js';\n\n/**\n * `ddt impact <fqn>` — single-FQN blast-radius. Mirrors `sdt impact`.\n */\nexport function impactCommand(): Command {\n const cmd = new Command('impact');\n cmd\n .description(\n 'Single-FQN blast-radius: who feeds it, who reads from it, what findings apply to it.',\n )\n .argument('<fqn>', 'Fully-qualified name to analyze (e.g. main.gold.orders).')\n .requiredOption('--source <path>', '.ddtproj or .ddtpac to analyze.')\n .option('-o, --out <path>', 'Output file path. Defaults to stdout.')\n .action(async (fqn: string, opts: { source: string; out?: string; explain?: boolean }) => {\n const sourcePath = String(opts.source);\n const model = await loadModel(sourcePath);\n const md = review.renderImpactReport(model, String(fqn), { source: sourcePath });\n await emit(md, opts.out);\n await runExplain(\n {\n feature: 'impact.explain',\n systemPrompt:\n 'You are a senior Databricks data engineer explaining an impact analysis to a developer who is about to change one object. Be very direct about the blast radius and what they should test before merging.',\n },\n opts,\n () =>\n `Impact report for ${fqn} follows:\\n\\n${md}\\n\\nExplain the blast radius and what the engineer should pay attention to.`,\n );\n });\n attachExplainFlag(cmd);\n return cmd;\n}\n\nasync function loadModel(sourcePath: string): Promise<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);\n}\n\nasync function emit(payload: string, out: unknown): Promise<void> {\n if (out) {\n const p = path.resolve(String(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} (${payload.length} bytes).`);\n } else {\n process.stdout.write(payload + (payload.endsWith('\\n') ? '' : '\\n'));\n }\n}\n"],"mappings":";;;;;;;AAAA,SAAS,YAAY,UAAU;AAC/B,OAAO,UAAU;AACjB,SAAS,eAAe;AACxB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AAMA,SAAS,gBAAyB;AACvC,QAAM,MAAM,IAAI,QAAQ,QAAQ;AAChC,MACG;AAAA,IACC;AAAA,EACF,EACC,SAAS,SAAS,0DAA0D,EAC5E,eAAe,mBAAmB,iCAAiC,EACnE,OAAO,oBAAoB,uCAAuC,EAClE,OAAO,OAAO,KAAa,SAA8D;AACxF,UAAM,aAAa,OAAO,KAAK,MAAM;AACrC,UAAM,QAAQ,MAAM,UAAU,UAAU;AACxC,UAAM,KAAK,OAAO,mBAAmB,OAAO,OAAO,GAAG,GAAG,EAAE,QAAQ,WAAW,CAAC;AAC/E,UAAM,KAAK,IAAI,KAAK,GAAG;AACvB,UAAM;AAAA,MACJ;AAAA,QACE,SAAS;AAAA,QACT,cACE;AAAA,MACJ;AAAA,MACA;AAAA,MACA,MACE,qBAAqB,GAAG;AAAA;AAAA,EAAgB,EAAE;AAAA;AAAA;AAAA,IAC9C;AAAA,EACF,CAAC;AACH,oBAAkB,GAAG;AACrB,SAAO;AACT;AAEA,eAAe,UAAU,YAAiD;AACxE,MAAI,WAAW,SAAS,SAAS,GAAG;AAClC,UAAM,IAAI,MAAM,IAAI,QAAQ,UAAU;AACtC,WAAO,EAAE;AAAA,EACX;AACA,QAAM,SAAS,MAAM,YAAY,UAAU;AAC3C,SAAO,MAAM,kBAAkB,MAAM;AACvC;AAEA,eAAe,KAAK,SAAiB,KAA6B;AAChE,MAAI,KAAK;AACP,UAAM,IAAI,KAAK,QAAQ,OAAO,GAAG,CAAC;AAClC,UAAM,GAAG,MAAM,KAAK,QAAQ,CAAC,GAAG,EAAE,WAAW,KAAK,CAAC;AACnD,UAAM,GAAG,UAAU,GAAG,WAAW,QAAQ,SAAS,IAAI,IAAI,KAAK,OAAO,MAAM;AAC5E,YAAQ,MAAM,SAAS,CAAC,KAAK,QAAQ,MAAM,UAAU;AAAA,EACvD,OAAO;AACL,YAAQ,OAAO,MAAM,WAAW,QAAQ,SAAS,IAAI,IAAI,KAAK,KAAK;AAAA,EACrE;AACF;","names":[]}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import "./chunk-DGUM43GV.js";
|
|
2
|
+
|
|
3
|
+
// src/commands/import.ts
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
import { importers as importersApi } from "@ddt-tools/core";
|
|
6
|
+
function importCommand() {
|
|
7
|
+
return new Command("import").description("Convert artifacts from other tools into a DDT project.").requiredOption(
|
|
8
|
+
"--from <source>",
|
|
9
|
+
"Source format (liquibase, flyway, dbt, terraform-state, sql-files, cross-engine-ddl, databricks-workspace)"
|
|
10
|
+
).requiredOption("--source-path <path>", "Source artifact path (file or directory)").requiredOption("--output <dir>", "Output directory for the generated DDT project").option("--connection <profile>", "Connection profile (required for databricks-workspace)").action(
|
|
11
|
+
async (opts) => {
|
|
12
|
+
const source = opts.from;
|
|
13
|
+
const importer = importersApi.getImporter(source);
|
|
14
|
+
const result = await importer.import({
|
|
15
|
+
sourcePath: opts.sourcePath,
|
|
16
|
+
outputDir: opts.output,
|
|
17
|
+
connectionProfile: opts.connection
|
|
18
|
+
});
|
|
19
|
+
console.log(
|
|
20
|
+
`Imported ${result.filesCreated} files. Project written to ${result.projectPath}`
|
|
21
|
+
);
|
|
22
|
+
for (const w of result.warnings) console.warn(w);
|
|
23
|
+
}
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
export {
|
|
27
|
+
importCommand
|
|
28
|
+
};
|
|
29
|
+
//# sourceMappingURL=import-EGOVKTLX.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/import.ts"],"sourcesContent":["/**\n * `ddt import` — convert artifacts from other tools into a `.ddtproj`.\n *\n * Sources (per the DDT importer registry):\n * - liquibase (changelog XML/YAML/SQL)\n * - flyway (versioned / repeatable migration tree)\n * - dbt (compiled manifest.json / project)\n * - terraform-state (`.tfstate` from the Databricks provider)\n * - sql-files (flat dir of `.sql`)\n * - cross-engine-ddl (foreign-dialect DDL, best-effort translation)\n * - databricks-workspace (live workspace → project, needs --connection)\n *\n * To split a single SQL script into an existing project tree instead, use\n * `ddt import-script`. Byte-aligned with `sdt import`.\n */\nimport { Command } from 'commander';\n\nimport { importers as importersApi } from '@ddt-tools/core';\n\nexport function importCommand(): Command {\n return new Command('import')\n .description('Convert artifacts from other tools into a DDT project.')\n .requiredOption(\n '--from <source>',\n 'Source format (liquibase, flyway, dbt, terraform-state, sql-files, cross-engine-ddl, databricks-workspace)',\n )\n .requiredOption('--source-path <path>', 'Source artifact path (file or directory)')\n .requiredOption('--output <dir>', 'Output directory for the generated DDT project')\n .option('--connection <profile>', 'Connection profile (required for databricks-workspace)')\n .action(\n async (opts: { from: string; sourcePath: string; output: string; connection?: string }) => {\n const source = opts.from as Parameters<typeof importersApi.getImporter>[0];\n const importer = importersApi.getImporter(source);\n const result = await importer.import({\n sourcePath: opts.sourcePath,\n outputDir: opts.output,\n connectionProfile: opts.connection,\n });\n console.log(\n `Imported ${result.filesCreated} files. Project written to ${result.projectPath}`,\n );\n for (const w of result.warnings) console.warn(w);\n },\n );\n}\n"],"mappings":";;;AAeA,SAAS,eAAe;AAExB,SAAS,aAAa,oBAAoB;AAEnC,SAAS,gBAAyB;AACvC,SAAO,IAAI,QAAQ,QAAQ,EACxB,YAAY,wDAAwD,EACpE;AAAA,IACC;AAAA,IACA;AAAA,EACF,EACC,eAAe,wBAAwB,0CAA0C,EACjF,eAAe,kBAAkB,gDAAgD,EACjF,OAAO,0BAA0B,wDAAwD,EACzF;AAAA,IACC,OAAO,SAAoF;AACzF,YAAM,SAAS,KAAK;AACpB,YAAM,WAAW,aAAa,YAAY,MAAM;AAChD,YAAM,SAAS,MAAM,SAAS,OAAO;AAAA,QACnC,YAAY,KAAK;AAAA,QACjB,WAAW,KAAK;AAAA,QAChB,mBAAmB,KAAK;AAAA,MAC1B,CAAC;AACD,cAAQ;AAAA,QACN,YAAY,OAAO,YAAY,8BAA8B,OAAO,WAAW;AAAA,MACjF;AACA,iBAAW,KAAK,OAAO,SAAU,SAAQ,KAAK,CAAC;AAAA,IACjD;AAAA,EACF;AACJ;","names":[]}
|