@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,555 @@
|
|
|
1
|
+
import {
|
|
2
|
+
attachExplainFlag,
|
|
3
|
+
runExplain
|
|
4
|
+
} from "./chunk-XFXG347C.js";
|
|
5
|
+
import "./chunk-DGUM43GV.js";
|
|
6
|
+
|
|
7
|
+
// src/commands/lineage.ts
|
|
8
|
+
import { promises as fs } from "fs";
|
|
9
|
+
import { randomUUID } from "crypto";
|
|
10
|
+
import path from "path";
|
|
11
|
+
import { Command } from "commander";
|
|
12
|
+
import {
|
|
13
|
+
loadProject,
|
|
14
|
+
pac,
|
|
15
|
+
parseProjectModel,
|
|
16
|
+
lineage,
|
|
17
|
+
columnLineage
|
|
18
|
+
} from "@ddt-tools/core";
|
|
19
|
+
var VALID_OL_EVENT_TYPES = /* @__PURE__ */ new Set([
|
|
20
|
+
"START",
|
|
21
|
+
"RUNNING",
|
|
22
|
+
"COMPLETE",
|
|
23
|
+
"ABORT",
|
|
24
|
+
"FAIL",
|
|
25
|
+
"OTHER"
|
|
26
|
+
]);
|
|
27
|
+
function validateEventType(raw) {
|
|
28
|
+
const upper = raw.toUpperCase();
|
|
29
|
+
if (!VALID_OL_EVENT_TYPES.has(upper)) {
|
|
30
|
+
throw new Error(
|
|
31
|
+
`--emit-openlineage-event-type ${JSON.stringify(raw)} is not a valid OpenLineage event type. Use one of: ${[...VALID_OL_EVENT_TYPES].join(", ")}.`
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
return upper;
|
|
35
|
+
}
|
|
36
|
+
var AUTO_MODE_THRESHOLD = 10;
|
|
37
|
+
function resolveAutoMode(mode, eventCount) {
|
|
38
|
+
if (mode !== "auto") return mode;
|
|
39
|
+
return eventCount <= AUTO_MODE_THRESHOLD ? "per-event" : "batch";
|
|
40
|
+
}
|
|
41
|
+
function validateMode(raw) {
|
|
42
|
+
const lower = raw.toLowerCase();
|
|
43
|
+
if (lower !== "per-event" && lower !== "batch" && lower !== "auto") {
|
|
44
|
+
throw new Error(
|
|
45
|
+
`--emit-openlineage-mode ${JSON.stringify(raw)} is not valid. Use one of: per-event, batch, auto.`
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
return lower;
|
|
49
|
+
}
|
|
50
|
+
function validateEventsOutFormat(raw) {
|
|
51
|
+
const lower = raw.toLowerCase();
|
|
52
|
+
if (lower !== "json" && lower !== "jsonl") {
|
|
53
|
+
throw new Error(
|
|
54
|
+
`--emit-openlineage-events-out-format ${JSON.stringify(raw)} is not valid. Use one of: json, jsonl.`
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
return lower;
|
|
58
|
+
}
|
|
59
|
+
function validateEventTime(raw) {
|
|
60
|
+
const parsed = new Date(raw);
|
|
61
|
+
if (Number.isNaN(parsed.getTime())) {
|
|
62
|
+
throw new Error(
|
|
63
|
+
`--emit-openlineage-event-time ${JSON.stringify(raw)} is not a valid ISO 8601 timestamp. Examples: 2026-05-18T20:00:00Z, 2026-05-18T16:00:00-04:00.`
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
return parsed.toISOString();
|
|
67
|
+
}
|
|
68
|
+
function parseHeaderArgs(args, stderr = () => {
|
|
69
|
+
}) {
|
|
70
|
+
const out = {};
|
|
71
|
+
if (!args || args.length === 0) return out;
|
|
72
|
+
for (const raw of args) {
|
|
73
|
+
const eq = raw.indexOf("=");
|
|
74
|
+
if (eq <= 0) {
|
|
75
|
+
stderr(
|
|
76
|
+
`Ignoring malformed --emit-openlineage-header value ${JSON.stringify(raw)} (expected name=value).
|
|
77
|
+
`
|
|
78
|
+
);
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
const name = raw.slice(0, eq).trim();
|
|
82
|
+
const value = raw.slice(eq + 1);
|
|
83
|
+
if (!name) {
|
|
84
|
+
stderr(`Ignoring --emit-openlineage-header with empty name.
|
|
85
|
+
`);
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
out[name] = value;
|
|
89
|
+
}
|
|
90
|
+
return out;
|
|
91
|
+
}
|
|
92
|
+
async function emitOpenLineageFromCli(inputs, deps = {}) {
|
|
93
|
+
const stdout = deps.stdout ?? ((c) => process.stdout.write(c));
|
|
94
|
+
const stderr = deps.stderr ?? ((c) => process.stderr.write(c));
|
|
95
|
+
const readFile = deps.readFile ?? ((p) => fs.readFile(p, "utf8"));
|
|
96
|
+
const writeFile = deps.writeFile ?? (async (p, data) => {
|
|
97
|
+
await fs.mkdir(path.dirname(p), { recursive: true });
|
|
98
|
+
await fs.writeFile(p, data, "utf8");
|
|
99
|
+
});
|
|
100
|
+
const env = deps.env ?? process.env;
|
|
101
|
+
const now = deps.now ?? (() => /* @__PURE__ */ new Date());
|
|
102
|
+
const uuid = deps.uuid ?? (() => randomUUID());
|
|
103
|
+
let loaded;
|
|
104
|
+
const configPath = inputs.configPath ?? columnLineage.DEFAULT_OPENLINEAGE_CONFIG_FILE_REL;
|
|
105
|
+
try {
|
|
106
|
+
const raw = await readFile(configPath);
|
|
107
|
+
const json = JSON.parse(raw);
|
|
108
|
+
const parsed = columnLineage.parseOpenLineageConfig(json);
|
|
109
|
+
if (!parsed.config) {
|
|
110
|
+
const lines = parsed.errors.map((e) => ` - ${e.path}: ${e.message}`);
|
|
111
|
+
stderr(`Failed to parse ${configPath}:
|
|
112
|
+
${lines.join("\n")}
|
|
113
|
+
`);
|
|
114
|
+
return {
|
|
115
|
+
ok: false,
|
|
116
|
+
events: 0,
|
|
117
|
+
error: { code: "CONFIG_PARSE_FAILED", message: `${configPath} did not validate` }
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
loaded = parsed.config;
|
|
121
|
+
} catch (err) {
|
|
122
|
+
const code = err.code;
|
|
123
|
+
if (code !== "ENOENT") {
|
|
124
|
+
stderr(`Failed reading ${configPath}: ${err.message}
|
|
125
|
+
`);
|
|
126
|
+
return {
|
|
127
|
+
ok: false,
|
|
128
|
+
events: 0,
|
|
129
|
+
error: { code: "CONFIG_READ_FAILED", message: err.message }
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
const overrides = {};
|
|
134
|
+
if (inputs.endpointFlag) overrides.endpoint = inputs.endpointFlag;
|
|
135
|
+
if (inputs.namespace) overrides.datasetNamespace = inputs.namespace;
|
|
136
|
+
if (inputs.jobNamespace) overrides.jobNamespace = inputs.jobNamespace;
|
|
137
|
+
if (inputs.job) overrides.jobName = inputs.job;
|
|
138
|
+
if (inputs.mode && inputs.mode !== "auto") overrides.mode = inputs.mode;
|
|
139
|
+
if (typeof inputs.timeoutMs === "number" && Number.isFinite(inputs.timeoutMs) && inputs.timeoutMs > 0) {
|
|
140
|
+
overrides.timeoutMs = Math.floor(inputs.timeoutMs);
|
|
141
|
+
}
|
|
142
|
+
if (inputs.token) overrides.bearerToken = inputs.token;
|
|
143
|
+
if (inputs.producer && inputs.producer.trim().length > 0)
|
|
144
|
+
overrides.producer = inputs.producer.trim();
|
|
145
|
+
const cliHeaders = parseHeaderArgs(inputs.headerArgs, stderr);
|
|
146
|
+
if (Object.keys(cliHeaders).length > 0) overrides.headers = cliHeaders;
|
|
147
|
+
const merged = columnLineage.mergeOpenLineageCliOverrides(loaded, overrides);
|
|
148
|
+
const validated = columnLineage.validateOpenLineageConfig(merged);
|
|
149
|
+
if (!validated.ok) {
|
|
150
|
+
const lines = validated.errors.map((e) => ` - ${e.path}: ${e.message}`);
|
|
151
|
+
stderr(`OpenLineage emit refused:
|
|
152
|
+
${lines.join("\n")}
|
|
153
|
+
`);
|
|
154
|
+
return {
|
|
155
|
+
ok: false,
|
|
156
|
+
events: 0,
|
|
157
|
+
error: { code: "CONFIG_INCOMPLETE", message: validated.errors[0].message }
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
const resolved = columnLineage.resolveEnvPlaceholders(validated.resolved, env);
|
|
161
|
+
if (!resolved.ok) {
|
|
162
|
+
const lines = resolved.missing.map((m) => ` - ${m.path}: missing env var ${m.envKey}`);
|
|
163
|
+
stderr(`OpenLineage env placeholders unresolved:
|
|
164
|
+
${lines.join("\n")}
|
|
165
|
+
`);
|
|
166
|
+
return {
|
|
167
|
+
ok: false,
|
|
168
|
+
events: 0,
|
|
169
|
+
error: { code: "ENV_UNRESOLVED", message: resolved.missing[0].envKey }
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
const final = resolved.resolved;
|
|
173
|
+
const jobName = final.jobName ?? inputs.sourceHint ?? "ddt-lineage";
|
|
174
|
+
const eventType = inputs.eventType ?? "COMPLETE";
|
|
175
|
+
const events = columnLineage.buildOpenLineageEvents(inputs.dag, {
|
|
176
|
+
runId: inputs.runId ?? uuid(),
|
|
177
|
+
jobName,
|
|
178
|
+
jobNamespace: final.jobNamespace ?? columnLineage.DEFAULT_OPENLINEAGE_JOB_NAMESPACE,
|
|
179
|
+
...final.datasetNamespace ? { datasetNamespace: final.datasetNamespace } : {},
|
|
180
|
+
...final.producer ? { producer: final.producer } : {},
|
|
181
|
+
eventTime: inputs.eventTime ?? now().toISOString(),
|
|
182
|
+
eventType
|
|
183
|
+
});
|
|
184
|
+
if (inputs.eventsOutPath) {
|
|
185
|
+
const format = inputs.eventsOutFormat ?? "json";
|
|
186
|
+
const payload = format === "jsonl" ? events.map((e) => JSON.stringify(e)).join("\n") + "\n" : JSON.stringify({ events }, null, 2) + "\n";
|
|
187
|
+
await writeFile(inputs.eventsOutPath, payload);
|
|
188
|
+
stderr(
|
|
189
|
+
`OpenLineage: wrote ${events.length} event(s) to ${inputs.eventsOutPath} (${format}).
|
|
190
|
+
`
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
if (typeof inputs.minEvents === "number" && Number.isFinite(inputs.minEvents) && inputs.minEvents > 0 && events.length < inputs.minEvents) {
|
|
194
|
+
stderr(
|
|
195
|
+
`OpenLineage: built ${events.length} event(s), below --emit-openlineage-min-events=${inputs.minEvents}; refusing to emit.
|
|
196
|
+
`
|
|
197
|
+
);
|
|
198
|
+
return {
|
|
199
|
+
ok: false,
|
|
200
|
+
events: events.length,
|
|
201
|
+
error: {
|
|
202
|
+
code: "MIN_EVENTS_NOT_MET",
|
|
203
|
+
message: `built ${events.length} events, expected >= ${inputs.minEvents}`
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
if (inputs.dryRun) {
|
|
208
|
+
stdout(JSON.stringify({ events }, null, 2) + "\n");
|
|
209
|
+
return { ok: true, events: events.length };
|
|
210
|
+
}
|
|
211
|
+
if (events.length === 0 && inputs.skipEmpty) {
|
|
212
|
+
stderr(
|
|
213
|
+
"OpenLineage: 0 events to emit (DAG empty) and --emit-openlineage-skip-empty set; skipping POST.\n"
|
|
214
|
+
);
|
|
215
|
+
return { ok: true, events: 0, succeeded: 0, failed: 0 };
|
|
216
|
+
}
|
|
217
|
+
const concreteMode = inputs.mode === "auto" ? resolveAutoMode("auto", events.length) : final.mode ?? "per-event";
|
|
218
|
+
if (inputs.mode === "auto") {
|
|
219
|
+
stderr(
|
|
220
|
+
`OpenLineage: --emit-openlineage-mode=auto resolved to ${concreteMode} (${events.length} event(s), threshold=${AUTO_MODE_THRESHOLD}).
|
|
221
|
+
`
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
const result = await columnLineage.emitOpenLineageEvents(events, final.endpoint, {
|
|
225
|
+
...final.bearerToken ? { bearerToken: final.bearerToken } : {},
|
|
226
|
+
...final.headers ? { headers: final.headers } : {},
|
|
227
|
+
mode: concreteMode,
|
|
228
|
+
...typeof final.timeoutMs === "number" ? { timeoutMs: final.timeoutMs } : {},
|
|
229
|
+
...deps.fetchImpl ? { fetchImpl: deps.fetchImpl } : {}
|
|
230
|
+
});
|
|
231
|
+
stderr(
|
|
232
|
+
`OpenLineage: ${result.succeeded}/${result.totalEvents} events delivered to ${final.endpoint} (${result.failed} failed).
|
|
233
|
+
`
|
|
234
|
+
);
|
|
235
|
+
return {
|
|
236
|
+
ok: result.failed === 0,
|
|
237
|
+
events: result.totalEvents,
|
|
238
|
+
succeeded: result.succeeded,
|
|
239
|
+
failed: result.failed
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
function lineageCommand() {
|
|
243
|
+
const cmd = new Command("lineage");
|
|
244
|
+
cmd.description("Extract data-flow lineage from a .ddtproj, .ddtpac, or dbt manifest.json.").option("--source <path>", ".ddtproj or .ddtpac to analyze.").option(
|
|
245
|
+
"--dbt-manifest <path>",
|
|
246
|
+
"Extract column-level lineage from a dbt target/manifest.json (or project root)."
|
|
247
|
+
).option(
|
|
248
|
+
"--fqn <name>",
|
|
249
|
+
"Focus on this fully-qualified name; restrict the graph to its upstream + downstream slice."
|
|
250
|
+
).option(
|
|
251
|
+
"--direction <kind>",
|
|
252
|
+
"When --fqn is set, restrict to upstream | downstream | both. Default both.",
|
|
253
|
+
"both"
|
|
254
|
+
).option(
|
|
255
|
+
"--depth <n>",
|
|
256
|
+
"Max traversal depth from --fqn. Default unbounded.",
|
|
257
|
+
(v) => parseInt(v, 10)
|
|
258
|
+
).option("--format <fmt>", "mermaid | dot | json | markdown. Default mermaid.", "mermaid").option("--columns", "Include best-effort column-level lineage for view SELECT lists.", false).option(
|
|
259
|
+
"--column-dag",
|
|
260
|
+
"Build a true per-column DAG via the CLL.1/2/3 substrate. Walks every VIEW / MATERIALIZED_VIEW / STREAMING_TABLE body in the project model, extracts column-level lineage, assembles the DAG, and emits Mermaid (default) or JSON. Composes with --fqn (focus to one object) and --out.",
|
|
261
|
+
false
|
|
262
|
+
).option("--max-edges <n>", "Truncate large graphs at this edge count.", (v) => parseInt(v, 10)).option("-o, --out <path>", "Output file path. Defaults to stdout.").option(
|
|
263
|
+
"--emit-openlineage [endpoint]",
|
|
264
|
+
"CLL.7-followup: emit the column DAG as OpenLineage RunEvents to the given collector URL. When the flag is given without a value, the endpoint from .ddt/lineage.json is used. Requires --column-dag (or --dbt-manifest)."
|
|
265
|
+
).option(
|
|
266
|
+
"--emit-openlineage-config <path>",
|
|
267
|
+
"Override the config-file location (default .ddt/lineage.json)."
|
|
268
|
+
).option(
|
|
269
|
+
"--emit-openlineage-namespace <ns>",
|
|
270
|
+
"Override the OpenLineage dataset namespace (default databricks)."
|
|
271
|
+
).option(
|
|
272
|
+
"--emit-openlineage-job-namespace <ns>",
|
|
273
|
+
"Override the OpenLineage job.namespace (default ddt). Groups jobs across deployments in Marquez/OpenMetadata."
|
|
274
|
+
).option(
|
|
275
|
+
"--emit-openlineage-job <name>",
|
|
276
|
+
"Override the OpenLineage job.name (default derived from --source)."
|
|
277
|
+
).option(
|
|
278
|
+
"--emit-openlineage-mode <mode>",
|
|
279
|
+
"OpenLineage emit mode: per-event (default) | batch | auto. `auto` resolves to per-event when the DAG has \u226410 events (easier to debug in Marquez UI) and batch when >10 (amortizes HTTP overhead)."
|
|
280
|
+
).option(
|
|
281
|
+
"--emit-openlineage-timeout-ms <ms>",
|
|
282
|
+
"Override per-request timeout (positive integer, default 10000ms).",
|
|
283
|
+
(v) => parseInt(v, 10)
|
|
284
|
+
).option(
|
|
285
|
+
"--emit-openlineage-token <token>",
|
|
286
|
+
"Override bearer token (supports env:VAR_NAME for indirection)."
|
|
287
|
+
).option(
|
|
288
|
+
"--emit-openlineage-header <k=v>",
|
|
289
|
+
"Extra HTTP header attached to every OpenLineage POST (repeatable). Format: name=value. Values support env:VAR_NAME for indirection.",
|
|
290
|
+
(val, prev) => [...prev, val],
|
|
291
|
+
[]
|
|
292
|
+
).option(
|
|
293
|
+
"--emit-openlineage-producer <url>",
|
|
294
|
+
"Override the OpenLineage producer URL (identifies the emitting tool to the collector). Default: https://github.com/GVOrganization/SDT."
|
|
295
|
+
).option(
|
|
296
|
+
"--emit-openlineage-event-type <type>",
|
|
297
|
+
"OpenLineage event type: COMPLETE (default), START, RUNNING, ABORT, FAIL, or OTHER. Use START + COMPLETE pairs (sharing --emit-openlineage-run-id) for run-lifecycle modelling."
|
|
298
|
+
).option(
|
|
299
|
+
"--emit-openlineage-event-time <iso>",
|
|
300
|
+
'Override the OpenLineage eventTime (ISO 8601). Default: now() at emit time. Use for backfill \u2014 replay historical pipeline runs into Marquez/OpenMetadata with their original timestamps instead of "now".'
|
|
301
|
+
).option(
|
|
302
|
+
"--emit-openlineage-run-id <id>",
|
|
303
|
+
"Stable runId (UUID v4). Default: generated per invocation."
|
|
304
|
+
).option(
|
|
305
|
+
"--emit-openlineage-dry-run",
|
|
306
|
+
"Build OpenLineage events but do not POST \u2014 print the events JSON to stdout instead.",
|
|
307
|
+
false
|
|
308
|
+
).option(
|
|
309
|
+
"--emit-openlineage-events-out <path>",
|
|
310
|
+
"Also write the built events JSON to <path> (audit / replay). Independent of --dry-run; combined yields write-only-no-POST."
|
|
311
|
+
).option(
|
|
312
|
+
"--emit-openlineage-events-out-format <fmt>",
|
|
313
|
+
"Output format for --emit-openlineage-events-out: json (default, `{events: [...]}` wrapper) or jsonl (newline-delimited, one event per line). Use jsonl for streaming-ingest pipelines (Vector, Fluent Bit, OpenTelemetry collectors, Databricks Auto Loader)."
|
|
314
|
+
).option(
|
|
315
|
+
"--emit-openlineage-skip-empty",
|
|
316
|
+
"Skip the POST when the built events array is empty (default behavior is to still hit the collector with 0 events in batch mode). Useful in CI when lineage extraction may produce an empty DAG and noisy 0-event traffic is undesirable.",
|
|
317
|
+
false
|
|
318
|
+
).option(
|
|
319
|
+
"--emit-openlineage-min-events <n>",
|
|
320
|
+
"CI assertion: refuse to emit (exit 2) when the built events count is below this threshold. Catches silent lineage gaps after a refactor that bypasses column-lineage extraction. Skip-empty composes naturally \u2014 set min-events=1 to force every run to produce \u22651 event.",
|
|
321
|
+
(v) => parseInt(v, 10)
|
|
322
|
+
).action(async (opts) => {
|
|
323
|
+
if (opts.emitOpenlineage && !opts.columnDag && !opts.dbtManifest) {
|
|
324
|
+
process.stderr.write(
|
|
325
|
+
"--emit-openlineage requires --column-dag (CLL.2 DAG) or --dbt-manifest. The default FQN-level lineage path has no column-level facet to emit. Re-run with --column-dag --source <path> or --dbt-manifest <path>.\n"
|
|
326
|
+
);
|
|
327
|
+
process.exitCode = 2;
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
if (opts.columnDag) {
|
|
331
|
+
if (!opts.source) throw new Error("--column-dag requires --source <path>.");
|
|
332
|
+
const model2 = await loadModel(String(opts.source));
|
|
333
|
+
const inputs = collectColumnLineageInputs(model2);
|
|
334
|
+
const dag = columnLineage.buildColumnLineageDag(inputs);
|
|
335
|
+
let renderedDag = dag;
|
|
336
|
+
if (opts.fqn) {
|
|
337
|
+
const focus2 = String(opts.fqn);
|
|
338
|
+
const direction2 = String(opts.direction ?? "both").toLowerCase();
|
|
339
|
+
renderedDag = columnLineage.sliceColumnDag(dag, focus2, direction2);
|
|
340
|
+
}
|
|
341
|
+
const fmt2 = String(opts.format ?? "mermaid").toLowerCase();
|
|
342
|
+
if (!["mermaid", "json"].includes(fmt2)) {
|
|
343
|
+
throw new Error(`--column-dag only supports --format mermaid|json (got ${fmt2}).`);
|
|
344
|
+
}
|
|
345
|
+
const payload2 = fmt2 === "json" ? JSON.stringify(columnLineage.renderDagJson(renderedDag), null, 2) : columnLineage.renderDagMermaid(renderedDag);
|
|
346
|
+
if (opts.out) {
|
|
347
|
+
const out = path.resolve(String(opts.out));
|
|
348
|
+
await fs.mkdir(path.dirname(out), { recursive: true });
|
|
349
|
+
await fs.writeFile(out, payload2 + "\n", "utf8");
|
|
350
|
+
console.error(
|
|
351
|
+
`Wrote ${out} (${renderedDag.nodes.size} nodes, ${renderedDag.edges.length} edges).`
|
|
352
|
+
);
|
|
353
|
+
} else if (!opts.emitOpenlineage) {
|
|
354
|
+
process.stdout.write(payload2 + "\n");
|
|
355
|
+
}
|
|
356
|
+
if (opts.emitOpenlineage) {
|
|
357
|
+
const endpointFlag = typeof opts.emitOpenlineage === "string" && opts.emitOpenlineage.length > 0 ? String(opts.emitOpenlineage) : void 0;
|
|
358
|
+
const sourceHint = opts.source ? path.basename(String(opts.source)).replace(/\.(ddtproj|ddtpac)$/i, "") : void 0;
|
|
359
|
+
const outcome = await emitOpenLineageFromCli({
|
|
360
|
+
dag: renderedDag,
|
|
361
|
+
...endpointFlag ? { endpointFlag } : {},
|
|
362
|
+
...opts.emitOpenlineageConfig ? { configPath: String(opts.emitOpenlineageConfig) } : {},
|
|
363
|
+
...opts.emitOpenlineageNamespace ? { namespace: String(opts.emitOpenlineageNamespace) } : {},
|
|
364
|
+
...opts.emitOpenlineageJobNamespace ? { jobNamespace: String(opts.emitOpenlineageJobNamespace) } : {},
|
|
365
|
+
...opts.emitOpenlineageJob ? { job: String(opts.emitOpenlineageJob) } : {},
|
|
366
|
+
...opts.emitOpenlineageMode ? { mode: validateMode(String(opts.emitOpenlineageMode)) } : {},
|
|
367
|
+
...opts.emitOpenlineageEventType ? { eventType: validateEventType(String(opts.emitOpenlineageEventType)) } : {},
|
|
368
|
+
...opts.emitOpenlineageEventTime ? { eventTime: validateEventTime(String(opts.emitOpenlineageEventTime)) } : {},
|
|
369
|
+
...typeof opts.emitOpenlineageTimeoutMs === "number" ? { timeoutMs: opts.emitOpenlineageTimeoutMs } : {},
|
|
370
|
+
...opts.emitOpenlineageToken ? { token: String(opts.emitOpenlineageToken) } : {},
|
|
371
|
+
...opts.emitOpenlineageProducer ? { producer: String(opts.emitOpenlineageProducer) } : {},
|
|
372
|
+
...Array.isArray(opts.emitOpenlineageHeader) && opts.emitOpenlineageHeader.length > 0 ? { headerArgs: opts.emitOpenlineageHeader } : {},
|
|
373
|
+
...opts.emitOpenlineageRunId ? { runId: String(opts.emitOpenlineageRunId) } : {},
|
|
374
|
+
...opts.emitOpenlineageDryRun ? { dryRun: true } : {},
|
|
375
|
+
...opts.emitOpenlineageEventsOut ? { eventsOutPath: String(opts.emitOpenlineageEventsOut) } : {},
|
|
376
|
+
...opts.emitOpenlineageEventsOutFormat ? {
|
|
377
|
+
eventsOutFormat: validateEventsOutFormat(
|
|
378
|
+
String(opts.emitOpenlineageEventsOutFormat)
|
|
379
|
+
)
|
|
380
|
+
} : {},
|
|
381
|
+
...opts.emitOpenlineageSkipEmpty ? { skipEmpty: true } : {},
|
|
382
|
+
...typeof opts.emitOpenlineageMinEvents === "number" && opts.emitOpenlineageMinEvents > 0 ? { minEvents: opts.emitOpenlineageMinEvents } : {},
|
|
383
|
+
...sourceHint ? { sourceHint } : {}
|
|
384
|
+
});
|
|
385
|
+
if (!outcome.ok) {
|
|
386
|
+
process.exitCode = 2;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
if (opts.dbtManifest) {
|
|
392
|
+
const result = await columnLineage.extractDbtManifestLineage(String(opts.dbtManifest));
|
|
393
|
+
const fmt2 = String(opts.format ?? "mermaid").toLowerCase();
|
|
394
|
+
const payload2 = fmt2 === "json" ? columnLineage.renderDbtLineageJson(result) : columnLineage.renderDbtLineageMermaid(result);
|
|
395
|
+
if (result.skipped.length > 0) {
|
|
396
|
+
for (const s of result.skipped) console.error(`Skipped ${s.nodeId}: ${s.reason}`);
|
|
397
|
+
}
|
|
398
|
+
if (opts.out) {
|
|
399
|
+
const out = path.resolve(String(opts.out));
|
|
400
|
+
await fs.mkdir(path.dirname(out), { recursive: true });
|
|
401
|
+
await fs.writeFile(out, payload2 + "\n", "utf8");
|
|
402
|
+
console.error(
|
|
403
|
+
`Wrote ${out} (${result.models.length} models, ${result.skipped.length} skipped).`
|
|
404
|
+
);
|
|
405
|
+
} else if (!opts.emitOpenlineage) {
|
|
406
|
+
process.stdout.write(payload2 + "\n");
|
|
407
|
+
}
|
|
408
|
+
if (opts.emitOpenlineage) {
|
|
409
|
+
const dag = columnLineage.buildColumnLineageDag(
|
|
410
|
+
result.models.map((m) => ({ objectFqn: m.fqn, lineage: m.lineage }))
|
|
411
|
+
);
|
|
412
|
+
const endpointFlag = typeof opts.emitOpenlineage === "string" && opts.emitOpenlineage.length > 0 ? String(opts.emitOpenlineage) : void 0;
|
|
413
|
+
const sourceHint = `dbt:${path.basename(String(opts.dbtManifest)).replace(/\.json$/i, "")}`;
|
|
414
|
+
const outcome = await emitOpenLineageFromCli({
|
|
415
|
+
dag,
|
|
416
|
+
...endpointFlag ? { endpointFlag } : {},
|
|
417
|
+
...opts.emitOpenlineageConfig ? { configPath: String(opts.emitOpenlineageConfig) } : {},
|
|
418
|
+
...opts.emitOpenlineageNamespace ? { namespace: String(opts.emitOpenlineageNamespace) } : {},
|
|
419
|
+
...opts.emitOpenlineageJobNamespace ? { jobNamespace: String(opts.emitOpenlineageJobNamespace) } : {},
|
|
420
|
+
...opts.emitOpenlineageJob ? { job: String(opts.emitOpenlineageJob) } : {},
|
|
421
|
+
...opts.emitOpenlineageMode ? { mode: validateMode(String(opts.emitOpenlineageMode)) } : {},
|
|
422
|
+
...opts.emitOpenlineageEventType ? { eventType: validateEventType(String(opts.emitOpenlineageEventType)) } : {},
|
|
423
|
+
...opts.emitOpenlineageEventTime ? { eventTime: validateEventTime(String(opts.emitOpenlineageEventTime)) } : {},
|
|
424
|
+
...typeof opts.emitOpenlineageTimeoutMs === "number" ? { timeoutMs: opts.emitOpenlineageTimeoutMs } : {},
|
|
425
|
+
...opts.emitOpenlineageToken ? { token: String(opts.emitOpenlineageToken) } : {},
|
|
426
|
+
...opts.emitOpenlineageProducer ? { producer: String(opts.emitOpenlineageProducer) } : {},
|
|
427
|
+
...Array.isArray(opts.emitOpenlineageHeader) && opts.emitOpenlineageHeader.length > 0 ? { headerArgs: opts.emitOpenlineageHeader } : {},
|
|
428
|
+
...opts.emitOpenlineageRunId ? { runId: String(opts.emitOpenlineageRunId) } : {},
|
|
429
|
+
...opts.emitOpenlineageDryRun ? { dryRun: true } : {},
|
|
430
|
+
...opts.emitOpenlineageEventsOut ? { eventsOutPath: String(opts.emitOpenlineageEventsOut) } : {},
|
|
431
|
+
...opts.emitOpenlineageEventsOutFormat ? {
|
|
432
|
+
eventsOutFormat: validateEventsOutFormat(
|
|
433
|
+
String(opts.emitOpenlineageEventsOutFormat)
|
|
434
|
+
)
|
|
435
|
+
} : {},
|
|
436
|
+
...opts.emitOpenlineageSkipEmpty ? { skipEmpty: true } : {},
|
|
437
|
+
...typeof opts.emitOpenlineageMinEvents === "number" && opts.emitOpenlineageMinEvents > 0 ? { minEvents: opts.emitOpenlineageMinEvents } : {},
|
|
438
|
+
sourceHint
|
|
439
|
+
});
|
|
440
|
+
if (!outcome.ok) {
|
|
441
|
+
process.exitCode = 2;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
if (!opts.source) throw new Error("Either --source or --dbt-manifest is required.");
|
|
447
|
+
const model = await loadModel(String(opts.source));
|
|
448
|
+
const fullGraph = lineage.buildLineageGraph(model);
|
|
449
|
+
const direction = String(opts.direction ?? "both").toLowerCase();
|
|
450
|
+
let graph = fullGraph;
|
|
451
|
+
let focus;
|
|
452
|
+
if (opts.fqn) {
|
|
453
|
+
focus = String(opts.fqn);
|
|
454
|
+
const depth = typeof opts.depth === "number" ? opts.depth : Infinity;
|
|
455
|
+
if (direction === "upstream") {
|
|
456
|
+
const keep = /* @__PURE__ */ new Set([focus, ...lineage.upstreamOf(fullGraph, focus, depth)]);
|
|
457
|
+
graph = subsetGraph(fullGraph, keep);
|
|
458
|
+
} else if (direction === "downstream") {
|
|
459
|
+
const keep = /* @__PURE__ */ new Set([focus, ...lineage.downstreamOf(fullGraph, focus, depth)]);
|
|
460
|
+
graph = subsetGraph(fullGraph, keep);
|
|
461
|
+
} else {
|
|
462
|
+
graph = lineage.sliceAround(fullGraph, focus, depth);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
const fmt = String(opts.format ?? "mermaid").toLowerCase();
|
|
466
|
+
if (!["mermaid", "dot", "json", "markdown"].includes(fmt)) {
|
|
467
|
+
throw new Error(`Unknown --format: ${fmt}. Use mermaid | dot | json | markdown.`);
|
|
468
|
+
}
|
|
469
|
+
const renderOpts = {
|
|
470
|
+
...focus ? { focus } : {},
|
|
471
|
+
...typeof opts.maxEdges === "number" ? { maxEdges: opts.maxEdges } : {},
|
|
472
|
+
...opts.columns ? { includeColumns: true } : {}
|
|
473
|
+
};
|
|
474
|
+
const payload = lineage.renderLineage(graph, fmt, renderOpts);
|
|
475
|
+
if (opts.out) {
|
|
476
|
+
const out = path.resolve(String(opts.out));
|
|
477
|
+
await fs.mkdir(path.dirname(out), { recursive: true });
|
|
478
|
+
await fs.writeFile(out, payload + (payload.endsWith("\n") ? "" : "\n"), "utf8");
|
|
479
|
+
console.error(
|
|
480
|
+
`Wrote ${out} (${payload.length} bytes, ${graph.nodes.length} nodes, ${graph.edges.length} edges).`
|
|
481
|
+
);
|
|
482
|
+
} else {
|
|
483
|
+
process.stdout.write(payload + (payload.endsWith("\n") ? "" : "\n"));
|
|
484
|
+
}
|
|
485
|
+
await runExplain(
|
|
486
|
+
{
|
|
487
|
+
feature: "lineage.explain",
|
|
488
|
+
systemPrompt: "You are a Databricks data-platform architect explaining a lineage graph. Describe the dominant data-flow shape, point out hot or fragile nodes, and recommend any decomposition / sharding the graph suggests."
|
|
489
|
+
},
|
|
490
|
+
opts,
|
|
491
|
+
() => {
|
|
492
|
+
const focused = focus ? ` (focused on ${focus}, direction=${direction})` : "";
|
|
493
|
+
const summary = `Lineage graph${focused}: ${graph.nodes.length} nodes, ${graph.edges.length} edges.`;
|
|
494
|
+
const sample = graph.edges.slice(0, 40).map((e) => ` - ${e.from} ${e.kind === "READS_FROM" ? "->" : "=>"} ${e.to}`);
|
|
495
|
+
return [
|
|
496
|
+
summary,
|
|
497
|
+
"",
|
|
498
|
+
"Edges (up to 40):",
|
|
499
|
+
...sample,
|
|
500
|
+
"",
|
|
501
|
+
"Narrate this graph in plain English."
|
|
502
|
+
].join("\n");
|
|
503
|
+
}
|
|
504
|
+
);
|
|
505
|
+
});
|
|
506
|
+
attachExplainFlag(cmd);
|
|
507
|
+
return cmd;
|
|
508
|
+
}
|
|
509
|
+
function subsetGraph(graph, keep) {
|
|
510
|
+
return {
|
|
511
|
+
nodes: graph.nodes.filter((n) => keep.has(n.fqn)),
|
|
512
|
+
edges: graph.edges.filter((e) => keep.has(e.from) && keep.has(e.to))
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
async function loadModel(sourcePath) {
|
|
516
|
+
if (sourcePath.endsWith(".ddtpac")) {
|
|
517
|
+
const c = await pac.readPac(sourcePath);
|
|
518
|
+
return c.model;
|
|
519
|
+
}
|
|
520
|
+
const loaded = await loadProject(sourcePath);
|
|
521
|
+
return await parseProjectModel(loaded);
|
|
522
|
+
}
|
|
523
|
+
function collectColumnLineageInputs(model) {
|
|
524
|
+
const inputs = [];
|
|
525
|
+
const matchTypes = /* @__PURE__ */ new Set(["VIEW", "MATERIALIZED_VIEW", "STREAMING_TABLE"]);
|
|
526
|
+
for (const obj of model) {
|
|
527
|
+
if (!matchTypes.has(obj.objectType)) continue;
|
|
528
|
+
const query = obj.query;
|
|
529
|
+
if (typeof query !== "string" || query.length === 0) continue;
|
|
530
|
+
const fqn = formatFqn(obj.fqn);
|
|
531
|
+
const lineage2 = columnLineage.extractColumnLineage(query);
|
|
532
|
+
inputs.push({ objectFqn: fqn, lineage: lineage2 });
|
|
533
|
+
}
|
|
534
|
+
return inputs;
|
|
535
|
+
}
|
|
536
|
+
function formatFqn(fqn) {
|
|
537
|
+
if (typeof fqn === "string") return fqn;
|
|
538
|
+
if (fqn && typeof fqn === "object") {
|
|
539
|
+
const f = fqn;
|
|
540
|
+
return [f.catalog ?? f.database, f.schema, f.name].filter(Boolean).join(".");
|
|
541
|
+
}
|
|
542
|
+
return "<unknown>";
|
|
543
|
+
}
|
|
544
|
+
export {
|
|
545
|
+
AUTO_MODE_THRESHOLD,
|
|
546
|
+
emitOpenLineageFromCli,
|
|
547
|
+
lineageCommand,
|
|
548
|
+
parseHeaderArgs,
|
|
549
|
+
resolveAutoMode,
|
|
550
|
+
validateEventTime,
|
|
551
|
+
validateEventType,
|
|
552
|
+
validateEventsOutFormat,
|
|
553
|
+
validateMode
|
|
554
|
+
};
|
|
555
|
+
//# sourceMappingURL=lineage-C5CGVP36.js.map
|