@sdt-tools/cli 0.2.0 → 0.2.6
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-6DRSZMBL.js +87 -0
- package/dist/advise-tests-6DRSZMBL.js.map +1 -0
- package/dist/ai-G4MJWHTM.js +89 -0
- package/dist/ai-G4MJWHTM.js.map +1 -0
- package/dist/anonymize-QR6JGXA7.js +123 -0
- package/dist/anonymize-QR6JGXA7.js.map +1 -0
- package/dist/approval-YVHYTV53.js +73 -0
- package/dist/approval-YVHYTV53.js.map +1 -0
- package/dist/approval-chain-54KKJZS3.js +120 -0
- package/dist/approval-chain-54KKJZS3.js.map +1 -0
- package/dist/audit-log-QZFH7LUX.js +159 -0
- package/dist/audit-log-QZFH7LUX.js.map +1 -0
- package/dist/backlog-V2YUIQDL.js +76 -0
- package/dist/backlog-V2YUIQDL.js.map +1 -0
- package/dist/bisect-GEVYAVL5.js +111 -0
- package/dist/bisect-GEVYAVL5.js.map +1 -0
- package/dist/bookmarks-57LKS7P6.js +107 -0
- package/dist/bookmarks-57LKS7P6.js.map +1 -0
- package/dist/branch-W2MGMPSH.js +88 -0
- package/dist/branch-W2MGMPSH.js.map +1 -0
- package/dist/build-VNIQFKSP.js +23 -0
- package/dist/build-VNIQFKSP.js.map +1 -0
- package/dist/catalog-JLB5VCEV.js +137 -0
- package/dist/catalog-JLB5VCEV.js.map +1 -0
- package/dist/changelog-M7XGDYSY.js +220 -0
- package/dist/changelog-M7XGDYSY.js.map +1 -0
- package/dist/chunk-DGUM43GV.js +11 -0
- package/dist/chunk-DGUM43GV.js.map +1 -0
- package/dist/chunk-EWXM4KJN.js +25 -0
- package/dist/chunk-EWXM4KJN.js.map +1 -0
- package/dist/chunk-JP2EZLR5.js +50 -0
- package/dist/chunk-JP2EZLR5.js.map +1 -0
- package/dist/chunk-VM2H4LAO.js +15 -0
- package/dist/chunk-VM2H4LAO.js.map +1 -0
- package/dist/chunk-ZWY4ZRHL.js +44 -0
- package/dist/chunk-ZWY4ZRHL.js.map +1 -0
- package/dist/cli.js +511 -19014
- package/dist/cli.js.map +1 -1
- package/dist/compare-5O6UTWPJ.js +405 -0
- package/dist/compare-5O6UTWPJ.js.map +1 -0
- package/dist/compare-profiles-7ZSNIW7B.js +218 -0
- package/dist/compare-profiles-7ZSNIW7B.js.map +1 -0
- package/dist/completion-I5U5VVAX.js +82 -0
- package/dist/completion-I5U5VVAX.js.map +1 -0
- package/dist/connection-GNTZDHXF.js +133 -0
- package/dist/connection-GNTZDHXF.js.map +1 -0
- package/dist/cost-estimate-TJDDH6TO.js +328 -0
- package/dist/cost-estimate-TJDDH6TO.js.map +1 -0
- package/dist/data-compare-UK2UXAS3.js +134 -0
- package/dist/data-compare-UK2UXAS3.js.map +1 -0
- package/dist/data-fit-Q45ENBRL.js +125 -0
- package/dist/data-fit-Q45ENBRL.js.map +1 -0
- package/dist/deploy-status-UUHKVDTI.js +58 -0
- package/dist/deploy-status-UUHKVDTI.js.map +1 -0
- package/dist/design-PO6UPBL7.js +138 -0
- package/dist/design-PO6UPBL7.js.map +1 -0
- package/dist/diagnose-6IFMELFR.js +145 -0
- package/dist/diagnose-6IFMELFR.js.map +1 -0
- package/dist/discover-A7OSZAHK.js +78 -0
- package/dist/discover-A7OSZAHK.js.map +1 -0
- package/dist/docs-CVRKGUSW.js +177 -0
- package/dist/docs-CVRKGUSW.js.map +1 -0
- package/dist/drift-XDA3BDYN.js +226 -0
- package/dist/drift-XDA3BDYN.js.map +1 -0
- package/dist/drift-gate-V7QSIOGZ.js +94 -0
- package/dist/drift-gate-V7QSIOGZ.js.map +1 -0
- package/dist/error-lookup-7ZWCZJ44.js +56 -0
- package/dist/error-lookup-7ZWCZJ44.js.map +1 -0
- package/dist/errorReporting-AQXKKGZH.js +109 -0
- package/dist/errorReporting-AQXKKGZH.js.map +1 -0
- package/dist/exec-PKBHLI7T.js +121 -0
- package/dist/exec-PKBHLI7T.js.map +1 -0
- package/dist/explain-LWKJOTL7.js +192 -0
- package/dist/explain-LWKJOTL7.js.map +1 -0
- package/dist/explorer-QOVM6VBD.js +61 -0
- package/dist/explorer-QOVM6VBD.js.map +1 -0
- package/dist/export-IYYBZ5HE.js +42 -0
- package/dist/export-IYYBZ5HE.js.map +1 -0
- package/dist/extract-VMMVRQVT.js +102 -0
- package/dist/extract-VMMVRQVT.js.map +1 -0
- package/dist/features-LE6BDZ2S.js +59 -0
- package/dist/features-LE6BDZ2S.js.map +1 -0
- package/dist/feedback-M7DM2EQC.js +161 -0
- package/dist/feedback-M7DM2EQC.js.map +1 -0
- package/dist/find-EME2JG2I.js +176 -0
- package/dist/find-EME2JG2I.js.map +1 -0
- package/dist/format-TRLWLMGS.js +141 -0
- package/dist/format-TRLWLMGS.js.map +1 -0
- package/dist/generate-6NAZGZDV.js +152 -0
- package/dist/generate-6NAZGZDV.js.map +1 -0
- package/dist/graph-QNQDAUO7.js +161 -0
- package/dist/graph-QNQDAUO7.js.map +1 -0
- package/dist/history-RONA7ZTI.js +199 -0
- package/dist/history-RONA7ZTI.js.map +1 -0
- package/dist/hosts-YBXY2ZG5.js +49 -0
- package/dist/hosts-YBXY2ZG5.js.map +1 -0
- package/dist/impact-T2JSANHS.js +59 -0
- package/dist/impact-T2JSANHS.js.map +1 -0
- package/dist/import-AELYLY6A.js +32 -0
- package/dist/import-AELYLY6A.js.map +1 -0
- package/dist/import-script-2OF5BI6A.js +83 -0
- package/dist/import-script-2OF5BI6A.js.map +1 -0
- package/dist/index.cjs +71 -12
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +95 -31
- package/dist/index.js.map +1 -1
- package/dist/init-SWRRJMGI.js +57 -0
- package/dist/init-SWRRJMGI.js.map +1 -0
- package/dist/install-hooks-6SIAGTAF.js +109 -0
- package/dist/install-hooks-6SIAGTAF.js.map +1 -0
- package/dist/license-OAF22PLZ.js +46 -0
- package/dist/license-OAF22PLZ.js.map +1 -0
- package/dist/lineage-EW66XJ6O.js +552 -0
- package/dist/lineage-EW66XJ6O.js.map +1 -0
- package/dist/lint-FQ2OTYTQ.js +143 -0
- package/dist/lint-FQ2OTYTQ.js.map +1 -0
- package/dist/mcp-SARDMCDV.js +344 -0
- package/dist/mcp-SARDMCDV.js.map +1 -0
- package/dist/migrate-from-dbt-JVTXPWKQ.js +156 -0
- package/dist/migrate-from-dbt-JVTXPWKQ.js.map +1 -0
- package/dist/migrate-platform-NTRTOGNR.js +91 -0
- package/dist/migrate-platform-NTRTOGNR.js.map +1 -0
- package/dist/optimize-CJYWMAWA.js +105 -0
- package/dist/optimize-CJYWMAWA.js.map +1 -0
- package/dist/perf-LL2CPCJF.js +205 -0
- package/dist/perf-LL2CPCJF.js.map +1 -0
- package/dist/pii-FBDRDQ2E.js +136 -0
- package/dist/pii-FBDRDQ2E.js.map +1 -0
- package/dist/pilot-CCQERKPH.js +29 -0
- package/dist/pilot-CCQERKPH.js.map +1 -0
- package/dist/pr-comment-S5FF4QRX.js +79 -0
- package/dist/pr-comment-S5FF4QRX.js.map +1 -0
- package/dist/preview-5U4YVCRM.js +47 -0
- package/dist/preview-5U4YVCRM.js.map +1 -0
- package/dist/profile-7VC57KD2.js +101 -0
- package/dist/profile-7VC57KD2.js.map +1 -0
- package/dist/promote-AASEFTIA.js +408 -0
- package/dist/promote-AASEFTIA.js.map +1 -0
- package/dist/publish-UMVIWH6H.js +721 -0
- package/dist/publish-UMVIWH6H.js.map +1 -0
- package/dist/purge-QMXZKCMD.js +57 -0
- package/dist/purge-QMXZKCMD.js.map +1 -0
- package/dist/query-log-6OM4GI7W.js +112 -0
- package/dist/query-log-6OM4GI7W.js.map +1 -0
- package/dist/refactor-LTZQLJ35.js +5799 -0
- package/dist/refactor-LTZQLJ35.js.map +1 -0
- package/dist/refresh-4TY2AGOU.js +38 -0
- package/dist/refresh-4TY2AGOU.js.map +1 -0
- package/dist/replay-OOC25FZN.js +117 -0
- package/dist/replay-OOC25FZN.js.map +1 -0
- package/dist/revert-ODMUVJW6.js +110 -0
- package/dist/revert-ODMUVJW6.js.map +1 -0
- package/dist/review-XXPWOBFP.js +158 -0
- package/dist/review-XXPWOBFP.js.map +1 -0
- package/dist/rollback-suggest-6G2HEKFR.js +79 -0
- package/dist/rollback-suggest-6G2HEKFR.js.map +1 -0
- package/dist/safer-alternative-QFVNLG3L.js +89 -0
- package/dist/safer-alternative-QFVNLG3L.js.map +1 -0
- package/dist/safety-7QWRSUEZ.js +168 -0
- package/dist/safety-7QWRSUEZ.js.map +1 -0
- package/dist/savings-RHIXP6IT.js +95 -0
- package/dist/savings-RHIXP6IT.js.map +1 -0
- package/dist/scan-secrets-5YCQ4UCU.js +54 -0
- package/dist/scan-secrets-5YCQ4UCU.js.map +1 -0
- package/dist/schema-CIZXCQD2.js +429 -0
- package/dist/schema-CIZXCQD2.js.map +1 -0
- package/dist/script-K7CIN2P6.js +153 -0
- package/dist/script-K7CIN2P6.js.map +1 -0
- package/dist/search-BUZ5NXZZ.js +151 -0
- package/dist/search-BUZ5NXZZ.js.map +1 -0
- package/dist/seed-76QAK276.js +96 -0
- package/dist/seed-76QAK276.js.map +1 -0
- package/dist/sketch-PTLKDIK3.js +88 -0
- package/dist/sketch-PTLKDIK3.js.map +1 -0
- package/dist/snapshot-XLPR2OZ5.js +177 -0
- package/dist/snapshot-XLPR2OZ5.js.map +1 -0
- package/dist/snippets-EK4DK5CN.js +74 -0
- package/dist/snippets-EK4DK5CN.js.map +1 -0
- package/dist/standards-7T2UY6DD.js +241 -0
- package/dist/standards-7T2UY6DD.js.map +1 -0
- package/dist/suggest-VGRYSAR6.js +39 -0
- package/dist/suggest-VGRYSAR6.js.map +1 -0
- package/dist/suggest-constraints-MY5WKUHA.js +160 -0
- package/dist/suggest-constraints-MY5WKUHA.js.map +1 -0
- package/dist/suite-TRNGZWQM.js +88 -0
- package/dist/suite-TRNGZWQM.js.map +1 -0
- package/dist/telemetry-3U2QLA2S.js +75 -0
- package/dist/telemetry-3U2QLA2S.js.map +1 -0
- package/dist/template-ZERIXVXF.js +403 -0
- package/dist/template-ZERIXVXF.js.map +1 -0
- package/dist/test-5M2ED3WT.js +169 -0
- package/dist/test-5M2ED3WT.js.map +1 -0
- package/dist/trial-U732FONV.js +31 -0
- package/dist/trial-U732FONV.js.map +1 -0
- package/dist/validate-T6D2WCOK.js +106 -0
- package/dist/validate-T6D2WCOK.js.map +1 -0
- package/dist/verify-KXVASEEG.js +76 -0
- package/dist/verify-KXVASEEG.js.map +1 -0
- package/dist/watch-I6K4BNMA.js +80 -0
- package/dist/watch-I6K4BNMA.js.map +1 -0
- package/dist/xcompare-TPFLQO6W.js +87 -0
- package/dist/xcompare-TPFLQO6W.js.map +1 -0
- package/package.json +2 -2
- package/dist/cli.cjs +0 -19040
- package/dist/cli.cjs.map +0 -1
- package/dist/cli.d.cts +0 -1
- package/dist/cli.d.ts +0 -1
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import {
|
|
2
|
+
logger
|
|
3
|
+
} from "./chunk-VM2H4LAO.js";
|
|
4
|
+
import "./chunk-DGUM43GV.js";
|
|
5
|
+
|
|
6
|
+
// src/commands/history.ts
|
|
7
|
+
import { promises as fs } from "fs";
|
|
8
|
+
import path from "path";
|
|
9
|
+
import { Command } from "commander";
|
|
10
|
+
import { getProfile, SnowflakeConnection } from "@sdt-tools/core/connection";
|
|
11
|
+
import { SnowflakeQueryHistoryReader } from "@sdt-tools/core/queryHistory";
|
|
12
|
+
function historyCommand() {
|
|
13
|
+
const cmd = new Command("history");
|
|
14
|
+
cmd.description(
|
|
15
|
+
"List deploy manifests (default) or query the live Snowflake QUERY_HISTORY views via --last/--query/--verify."
|
|
16
|
+
).option(
|
|
17
|
+
"--dir <path>",
|
|
18
|
+
"Directory containing *.json manifests (manifest mode).",
|
|
19
|
+
"./.sdt/history"
|
|
20
|
+
).option(
|
|
21
|
+
"--limit <n>",
|
|
22
|
+
"Manifest mode: most-recent N entries. Live mode: max rows returned.",
|
|
23
|
+
"20"
|
|
24
|
+
).option("--json", "Emit JSON instead of human-readable table.", false).option(
|
|
25
|
+
"--full-sql",
|
|
26
|
+
"Include full SQL text in the output (default: truncated to 120 chars).",
|
|
27
|
+
false
|
|
28
|
+
).option(
|
|
29
|
+
"--connection <profile>",
|
|
30
|
+
"Connection profile (required for --last / --query / --verify)."
|
|
31
|
+
).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 QUERY_ID.").option(
|
|
32
|
+
"--verify <tag>",
|
|
33
|
+
"Live mode: read all queries tagged with this string. Use the auto-generated tag from `sdt publish --apply` for deploy verification."
|
|
34
|
+
).option("--since <iso>", "Live mode: only include entries after this ISO 8601 timestamp.").option(
|
|
35
|
+
"--view <kind>",
|
|
36
|
+
"Live mode: which view to query \u2014 'session' | 'user' | 'account'. Default 'session'.",
|
|
37
|
+
"session"
|
|
38
|
+
).action(
|
|
39
|
+
async (opts) => {
|
|
40
|
+
const isLive = opts.last !== void 0 || opts.query !== void 0 || opts.verify !== void 0;
|
|
41
|
+
if (isLive) {
|
|
42
|
+
await runLive(opts);
|
|
43
|
+
} else {
|
|
44
|
+
await runManifests(opts);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
);
|
|
48
|
+
return cmd;
|
|
49
|
+
}
|
|
50
|
+
async function runManifests(opts) {
|
|
51
|
+
const dir = path.resolve(String(opts.dir ?? "./.sdt/history"));
|
|
52
|
+
let files;
|
|
53
|
+
try {
|
|
54
|
+
const entries2 = await fs.readdir(dir);
|
|
55
|
+
files = entries2.filter((f) => f.endsWith(".json")).map((f) => path.join(dir, f));
|
|
56
|
+
} catch (err) {
|
|
57
|
+
if (err.code === "ENOENT") {
|
|
58
|
+
console.error(
|
|
59
|
+
`No history directory at ${dir}. Pass --dir <path> or pipe deploys to write manifests there.`
|
|
60
|
+
);
|
|
61
|
+
process.exitCode = 1;
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
throw err;
|
|
65
|
+
}
|
|
66
|
+
const entries = [];
|
|
67
|
+
for (const f of files) {
|
|
68
|
+
try {
|
|
69
|
+
const raw = await fs.readFile(f, "utf8");
|
|
70
|
+
const m = JSON.parse(raw);
|
|
71
|
+
const stat = await fs.stat(f);
|
|
72
|
+
entries.push({ path: f, mtime: stat.mtime, manifest: m });
|
|
73
|
+
} catch {
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
entries.sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
|
|
77
|
+
const limit = parseInt(String(opts.limit ?? 20), 10);
|
|
78
|
+
const visible = entries.slice(0, limit > 0 ? limit : entries.length);
|
|
79
|
+
if (opts.json) {
|
|
80
|
+
console.log(
|
|
81
|
+
JSON.stringify(
|
|
82
|
+
visible.map((e) => ({ path: e.path, mtime: e.mtime.toISOString(), ...e.manifest })),
|
|
83
|
+
null,
|
|
84
|
+
2
|
|
85
|
+
)
|
|
86
|
+
);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
if (visible.length === 0) {
|
|
90
|
+
console.log(`No manifests found in ${dir}.`);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
console.log(`History: ${visible.length} of ${entries.length} deploys at ${dir}`);
|
|
94
|
+
console.log("");
|
|
95
|
+
console.log(
|
|
96
|
+
" DEPLOYED AT ACCOUNT FINAL STATE STEPS"
|
|
97
|
+
);
|
|
98
|
+
console.log(
|
|
99
|
+
" \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"
|
|
100
|
+
);
|
|
101
|
+
for (const e of visible) {
|
|
102
|
+
const m = e.manifest;
|
|
103
|
+
const when = m.deployedAt ? new Date(m.deployedAt).toISOString().slice(0, 19).replace("T", " ") : e.mtime.toISOString().slice(0, 19).replace("T", " ");
|
|
104
|
+
const host = (m.account ?? "?").padEnd(34).slice(0, 34);
|
|
105
|
+
const state = (m.finalState ?? "?").padEnd(18).slice(0, 18);
|
|
106
|
+
const succ = m.steps?.filter((s) => s.status === "SUCCESS").length ?? 0;
|
|
107
|
+
const tot = m.steps?.length ?? 0;
|
|
108
|
+
console.log(` ${when} ${host} ${state} ${succ}/${tot}`);
|
|
109
|
+
if (opts.fullSql) {
|
|
110
|
+
for (const s of m.steps ?? []) {
|
|
111
|
+
if (s.forwardSql) console.log(` [forward] ${s.fqn ?? ""}
|
|
112
|
+
${indent(s.forwardSql, 8)}`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
console.log("");
|
|
117
|
+
console.log(
|
|
118
|
+
`Pass --json for machine-readable output, --limit <n> to control depth, --full-sql to expand each step.`
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
async function runLive(opts) {
|
|
122
|
+
if (!opts.connection) {
|
|
123
|
+
logger.error("Live history mode requires --connection <profile>.");
|
|
124
|
+
process.exitCode = 1;
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
const profile = await getProfile(String(opts.connection));
|
|
128
|
+
const conn = new SnowflakeConnection(profile);
|
|
129
|
+
await conn.connect();
|
|
130
|
+
try {
|
|
131
|
+
const view = String(opts.view ?? "session").toLowerCase();
|
|
132
|
+
const reader = new SnowflakeQueryHistoryReader(conn, { defaultView: view });
|
|
133
|
+
const limit = Math.max(1, parseInt(String(opts.limit ?? "20"), 10) || 20);
|
|
134
|
+
const since = opts.since ? new Date(opts.since) : void 0;
|
|
135
|
+
let entries = [];
|
|
136
|
+
if (opts.query) {
|
|
137
|
+
const e = await reader.readById(String(opts.query));
|
|
138
|
+
entries = e ? [e] : [];
|
|
139
|
+
} else if (opts.verify) {
|
|
140
|
+
entries = await reader.readByTag(String(opts.verify), {
|
|
141
|
+
...since ? { since } : {},
|
|
142
|
+
limit
|
|
143
|
+
});
|
|
144
|
+
} else {
|
|
145
|
+
const n = typeof opts.last === "string" ? parseInt(opts.last, 10) || limit : limit;
|
|
146
|
+
entries = await reader.readByUser(profile.auth.username ?? "", {
|
|
147
|
+
...since ? { since } : {},
|
|
148
|
+
limit: n
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
if (opts.json) {
|
|
152
|
+
process.stdout.write(JSON.stringify(entries, null, 2) + "\n");
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
renderEntries(entries, Boolean(opts.fullSql));
|
|
156
|
+
} finally {
|
|
157
|
+
await conn.disconnect();
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
function renderEntries(entries, fullSql) {
|
|
161
|
+
if (entries.length === 0) {
|
|
162
|
+
console.log("No matching queries found.");
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
console.log(`${entries.length} quer${entries.length === 1 ? "y" : "ies"}:`);
|
|
166
|
+
console.log("");
|
|
167
|
+
console.log(" STARTED STATUS DURATION USER QUERY_ID");
|
|
168
|
+
console.log(
|
|
169
|
+
" \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"
|
|
170
|
+
);
|
|
171
|
+
for (const e of entries) {
|
|
172
|
+
const when = e.startedAt.slice(0, 19).replace("T", " ");
|
|
173
|
+
const status = e.status.toUpperCase().padEnd(8);
|
|
174
|
+
const dur = `${e.durationMs}ms`.padEnd(8);
|
|
175
|
+
const user = (e.user ?? "?").padEnd(20).slice(0, 20);
|
|
176
|
+
console.log(` ${when} ${status} ${dur} ${user} ${e.queryId}`);
|
|
177
|
+
const sql = fullSql ? e.sqlText : truncate(e.sqlText, 120);
|
|
178
|
+
if (sql)
|
|
179
|
+
console.log(
|
|
180
|
+
` ${sql.split("\n").map((l) => l.trim()).filter(Boolean).join(" ")}`
|
|
181
|
+
);
|
|
182
|
+
if (e.queryTag) console.log(` tag: ${e.queryTag}`);
|
|
183
|
+
if (e.errorMessage) console.log(` ERROR: ${e.errorMessage}`);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
function indent(text, n) {
|
|
187
|
+
const pad = " ".repeat(n);
|
|
188
|
+
return text.split("\n").map((l) => pad + l).join("\n");
|
|
189
|
+
}
|
|
190
|
+
function truncate(s, max) {
|
|
191
|
+
if (!s) return "";
|
|
192
|
+
const collapsed = s.replace(/\s+/g, " ").trim();
|
|
193
|
+
if (collapsed.length <= max) return collapsed;
|
|
194
|
+
return collapsed.slice(0, max - 1) + "\u2026";
|
|
195
|
+
}
|
|
196
|
+
export {
|
|
197
|
+
historyCommand
|
|
198
|
+
};
|
|
199
|
+
//# sourceMappingURL=history-RONA7ZTI.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 { getProfile, SnowflakeConnection } from '@sdt-tools/core/connection';\nimport { SnowflakeQueryHistoryReader } from '@sdt-tools/core/queryHistory';\nimport { logger } from '../util/logger.js';\nimport type { QueryHistoryEntry } from '@sdt-tools/core/queryHistory';\n\n/**\n * `sdt history` — two modes.\n *\n * 1. **Manifest mode** (default) — read deploy manifests from a\n * directory (written by `sdt publish --apply --manifest <path>`)\n * and show a chronological summary.\n *\n * 2. **Live mode** (triggered by `--last`, `--query`, or `--verify`)\n * — query the Snowflake account's QUERY_HISTORY views via the\n * SnowflakeQueryHistoryReader. Requires `--connection <profile>`.\n */\ninterface Manifest {\n version: 1;\n deployedAt?: string;\n account?: string;\n warehouse?: 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 the live Snowflake QUERY_HISTORY views via --last/--query/--verify.',\n )\n // Manifest mode\n .option(\n '--dir <path>',\n 'Directory containing *.json manifests (manifest mode).',\n './.sdt/history',\n )\n .option(\n '--limit <n>',\n 'Manifest mode: most-recent N entries. Live mode: max rows returned.',\n '20',\n )\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 // Live mode\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 QUERY_ID.')\n .option(\n '--verify <tag>',\n 'Live mode: read all queries tagged with this string. Use the auto-generated tag from `sdt publish --apply` for deploy verification.',\n )\n .option('--since <iso>', 'Live mode: only include entries after this ISO 8601 timestamp.')\n .option(\n '--view <kind>',\n \"Live mode: which view to query — 'session' | 'user' | 'account'. Default 'session'.\",\n 'session',\n )\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 view?: string;\n }) => {\n const isLive =\n opts.last !== undefined || opts.query !== undefined || opts.verify !== undefined;\n if (isLive) {\n await runLive(opts);\n } else {\n await runManifests(opts);\n }\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 ?? './.sdt/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 ACCOUNT 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.account ?? '?').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 view?: string;\n limit?: string;\n json?: boolean;\n fullSql?: boolean;\n}): Promise<void> {\n if (!opts.connection) {\n logger.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 = new SnowflakeConnection(profile);\n await conn.connect();\n try {\n const view = String(opts.view ?? 'session').toLowerCase() as 'session' | 'user' | 'account';\n const reader = new SnowflakeQueryHistoryReader(conn, { defaultView: view });\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 // --last [n]\n const n = typeof opts.last === 'string' ? parseInt(opts.last, 10) || limit : limit;\n // Reuse readByUser with the current user; if that's not set on the\n // reader, the underlying view still respects the current session's\n // user via INFORMATION_SCHEMA semantics.\n entries = await reader.readByUser(profile.auth.username ?? '', {\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 QUERY_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) => 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":"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","names":["entries"]}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import {
|
|
2
|
+
logger
|
|
3
|
+
} from "./chunk-VM2H4LAO.js";
|
|
4
|
+
import "./chunk-DGUM43GV.js";
|
|
5
|
+
|
|
6
|
+
// src/commands/hosts.ts
|
|
7
|
+
import { Command } from "commander";
|
|
8
|
+
import { hosts as hostsApi } from "@sdt-tools/core";
|
|
9
|
+
function hostsCommand() {
|
|
10
|
+
const cmd = new Command("hosts");
|
|
11
|
+
cmd.description(
|
|
12
|
+
"Browse every host SDT plugs into \u2014 CLI, VS Code, MCP agents, GitHub Actions, dbt, Airflow, etc. Shows supported versions and the host-specific tailoring notes."
|
|
13
|
+
);
|
|
14
|
+
cmd.command("list").description("List every host adapter with its status and version range.").action(() => {
|
|
15
|
+
const rows = hostsApi.listHosts();
|
|
16
|
+
const nameWidth = Math.max(...rows.map((h) => h.name.length), 4);
|
|
17
|
+
let lastKind = "";
|
|
18
|
+
for (const host of rows) {
|
|
19
|
+
if (host.kind !== lastKind) {
|
|
20
|
+
if (lastKind !== "") process.stdout.write("\n");
|
|
21
|
+
process.stdout.write(`\u2500\u2500 ${host.kind.toUpperCase()} \u2500\u2500
|
|
22
|
+
`);
|
|
23
|
+
lastKind = host.kind;
|
|
24
|
+
}
|
|
25
|
+
const pad = (s, w) => s + " ".repeat(Math.max(0, w - s.length));
|
|
26
|
+
process.stdout.write(
|
|
27
|
+
` ${pad(host.name, nameWidth)} ${hostsApi.hostSummaryLine(host).split(" \xB7 ")[1]}
|
|
28
|
+
`
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
process.stdout.write(
|
|
32
|
+
"\nRun `sdt hosts show <id>` for tailoring notes. See docs/HOST_INTEGRATIONS.md for the per-host playbook.\n"
|
|
33
|
+
);
|
|
34
|
+
});
|
|
35
|
+
cmd.command("show <id>").description("Print supported versions, idioms, and tailoring notes for one host.").action((id) => {
|
|
36
|
+
const host = hostsApi.getHost(id);
|
|
37
|
+
if (!host) {
|
|
38
|
+
logger.error(`Unknown host id: "${id}". Run \`sdt hosts list\` to see all ids.`);
|
|
39
|
+
process.exitCode = 1;
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
process.stdout.write(hostsApi.hostDetailMessage(host) + "\n");
|
|
43
|
+
});
|
|
44
|
+
return cmd;
|
|
45
|
+
}
|
|
46
|
+
export {
|
|
47
|
+
hostsCommand
|
|
48
|
+
};
|
|
49
|
+
//# sourceMappingURL=hosts-YBXY2ZG5.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/hosts.ts"],"sourcesContent":["/**\n * `sdt hosts` — browse every host SDT plugs into, with version-support\n * + tailoring notes. Companion to `sdt features`: features tell you\n * what we do; hosts tell you where you can do it.\n *\n * Subcommands:\n * sdt hosts list Tier-ordered table of every host with status.\n * sdt hosts show <id> Full tailoring notes + supported versions.\n */\nimport { Command } from 'commander';\nimport { hosts as hostsApi } from '@sdt-tools/core';\nimport { logger } from '../util/logger.js';\n\nexport function hostsCommand(): Command {\n const cmd = new Command('hosts');\n cmd.description(\n 'Browse every host SDT plugs into — CLI, VS Code, MCP agents, GitHub Actions, 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 process.stdout.write(\n ` ${pad(host.name, nameWidth)} ${hostsApi.hostSummaryLine(host).split(' · ')[1]}\\n`,\n );\n }\n process.stdout.write(\n '\\nRun `sdt 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 logger.error(`Unknown host id: \"${id}\". Run \\`sdt 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":";;;;;;AASA,SAAS,eAAe;AACxB,SAAS,SAAS,gBAAgB;AAG3B,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,cAAQ,OAAO;AAAA,QACb,KAAK,IAAI,KAAK,MAAM,SAAS,CAAC,KAAK,SAAS,gBAAgB,IAAI,EAAE,MAAM,QAAK,EAAE,CAAC,CAAC;AAAA;AAAA,MACnF;AAAA,IACF;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,aAAO,MAAM,qBAAqB,EAAE,2CAA2C;AAC/E,cAAQ,WAAW;AACnB;AAAA,IACF;AACA,YAAQ,OAAO,MAAM,SAAS,kBAAkB,IAAI,IAAI,IAAI;AAAA,EAC9D,CAAC;AAEH,SAAO;AACT;","names":[]}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import {
|
|
2
|
+
attachExplainFlag,
|
|
3
|
+
runExplain
|
|
4
|
+
} from "./chunk-ZWY4ZRHL.js";
|
|
5
|
+
import "./chunk-VM2H4LAO.js";
|
|
6
|
+
import "./chunk-DGUM43GV.js";
|
|
7
|
+
|
|
8
|
+
// src/commands/impact.ts
|
|
9
|
+
import { promises as fs } from "fs";
|
|
10
|
+
import path from "path";
|
|
11
|
+
import { Command } from "commander";
|
|
12
|
+
import { pac, project, review } from "@sdt-tools/core";
|
|
13
|
+
function impactCommand() {
|
|
14
|
+
const cmd = new Command("impact");
|
|
15
|
+
cmd.description(
|
|
16
|
+
"Single-FQN blast-radius: who feeds it, who reads from it, what findings apply to it."
|
|
17
|
+
).argument("<fqn>", "Fully-qualified name to analyze (e.g. ANALYTICS.GOLD.CUSTOMER).").requiredOption("--source <path>", ".sdtproj or .sdtpac to analyze.").option("-o, --out <path>", "Output file path. Defaults to stdout.").action(async (fqn, opts) => {
|
|
18
|
+
const sourcePath = String(opts.source);
|
|
19
|
+
const model = await loadModel(sourcePath);
|
|
20
|
+
const md = review.renderImpactReport(model, String(fqn), { source: sourcePath });
|
|
21
|
+
await emit(md, opts.out);
|
|
22
|
+
await runExplain(
|
|
23
|
+
{
|
|
24
|
+
feature: "impact.explain",
|
|
25
|
+
systemPrompt: "You are a senior Snowflake DBA 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."
|
|
26
|
+
},
|
|
27
|
+
opts,
|
|
28
|
+
() => `Impact report for ${fqn} follows:
|
|
29
|
+
|
|
30
|
+
${md}
|
|
31
|
+
|
|
32
|
+
Explain the blast radius and what the engineer should pay attention to.`
|
|
33
|
+
);
|
|
34
|
+
});
|
|
35
|
+
attachExplainFlag(cmd);
|
|
36
|
+
return cmd;
|
|
37
|
+
}
|
|
38
|
+
async function loadModel(sourcePath) {
|
|
39
|
+
if (sourcePath.endsWith(".sdtpac")) {
|
|
40
|
+
const c = await pac.readPac(sourcePath);
|
|
41
|
+
return c.model;
|
|
42
|
+
}
|
|
43
|
+
const loaded = await project.loadProject(sourcePath);
|
|
44
|
+
return await project.parseProjectModel(loaded);
|
|
45
|
+
}
|
|
46
|
+
async function emit(payload, out) {
|
|
47
|
+
if (out) {
|
|
48
|
+
const p = path.resolve(String(out));
|
|
49
|
+
await fs.mkdir(path.dirname(p), { recursive: true });
|
|
50
|
+
await fs.writeFile(p, payload + (payload.endsWith("\n") ? "" : "\n"), "utf8");
|
|
51
|
+
console.error(`Wrote ${p} (${payload.length} bytes).`);
|
|
52
|
+
} else {
|
|
53
|
+
process.stdout.write(payload + (payload.endsWith("\n") ? "" : "\n"));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
export {
|
|
57
|
+
impactCommand
|
|
58
|
+
};
|
|
59
|
+
//# sourceMappingURL=impact-T2JSANHS.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 { pac, project, review } from '@sdt-tools/core';\nimport { attachExplainFlag, runExplain } from '../util/ai-explain.js';\n\n/**\n * `sdt impact <fqn>` — single-FQN blast-radius answer. \"If I change\n * this, what breaks? What feeds it? What pending diagnostics\n * touch it?\" Composition of `@sdt-tools/core/lineage` (upstream/downstream)\n * and `@sdt-tools/core/diagnostics` (findings filtered to the FQN).\n *\n * Output is Markdown by default; `--format json` returns a structured\n * payload for CI integration.\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. ANALYTICS.GOLD.CUSTOMER).')\n .requiredOption('--source <path>', '.sdtproj or .sdtpac 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 Snowflake DBA 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) {\n if (sourcePath.endsWith('.sdtpac')) {\n const c = await pac.readPac(sourcePath);\n return c.model;\n }\n const loaded = await project.loadProject(sourcePath);\n return await project.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,SAAS,KAAK,SAAS,cAAc;AAY9B,SAAS,gBAAyB;AACvC,QAAM,MAAM,IAAI,QAAQ,QAAQ;AAChC,MACG;AAAA,IACC;AAAA,EACF,EACC,SAAS,SAAS,iEAAiE,EACnF,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,YAAoB;AAC3C,MAAI,WAAW,SAAS,SAAS,GAAG;AAClC,UAAM,IAAI,MAAM,IAAI,QAAQ,UAAU;AACtC,WAAO,EAAE;AAAA,EACX;AACA,QAAM,SAAS,MAAM,QAAQ,YAAY,UAAU;AACnD,SAAO,MAAM,QAAQ,kBAAkB,MAAM;AAC/C;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,32 @@
|
|
|
1
|
+
import {
|
|
2
|
+
logger
|
|
3
|
+
} from "./chunk-VM2H4LAO.js";
|
|
4
|
+
import "./chunk-DGUM43GV.js";
|
|
5
|
+
|
|
6
|
+
// src/commands/import.ts
|
|
7
|
+
import { Command } from "commander";
|
|
8
|
+
import { importers as importersApi } from "@sdt-tools/core";
|
|
9
|
+
function importCommand() {
|
|
10
|
+
return new Command("import").description("Convert artifacts from other tools into an SDT project.").requiredOption(
|
|
11
|
+
"--from <source>",
|
|
12
|
+
"Source format (schemachange, snowddl, sqlpackage-dacpac, snowflake-account, snowflake-dcm, terraform-state, sql-files)"
|
|
13
|
+
).requiredOption("--source-path <path>", "Source artifact path (file or directory)").requiredOption("--output <dir>", "Output directory for the generated SDT project").option("--connection <profile>", "Connection profile (required for snowflake-account)").action(
|
|
14
|
+
async (opts) => {
|
|
15
|
+
const source = opts.from;
|
|
16
|
+
const importer = importersApi.getImporter(source);
|
|
17
|
+
const result = await importer.import({
|
|
18
|
+
sourcePath: opts.sourcePath,
|
|
19
|
+
outputDir: opts.output,
|
|
20
|
+
connectionProfile: opts.connection
|
|
21
|
+
});
|
|
22
|
+
logger.info(
|
|
23
|
+
`Imported ${result.filesCreated} files. Project written to ${result.projectPath}`
|
|
24
|
+
);
|
|
25
|
+
for (const w of result.warnings) logger.warn(w);
|
|
26
|
+
}
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
export {
|
|
30
|
+
importCommand
|
|
31
|
+
};
|
|
32
|
+
//# sourceMappingURL=import-AELYLY6A.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/import.ts"],"sourcesContent":["/**\n * `sdt import` — convert artifacts from other tools into an `.sdtproj`.\n *\n * Sources (all v0.x stubs):\n * - schemachange (V*.sql / R__*.sql tree)\n * - snowddl (YAML config tree)\n * - sqlpackage-dacpac (SQL Server `.dacpac`)\n * - snowflake-account (live account → project, like `extract` + skeleton)\n * - terraform-state (`.tfstate` from Snowflake-Labs/snowflake provider)\n * - sql-files (flat dir of `.sql`)\n */\nimport { Command } from 'commander';\n\nimport { importers as importersApi } from '@sdt-tools/core';\nimport { logger } from '../util/logger.js';\n\nexport function importCommand(): Command {\n return new Command('import')\n .description('Convert artifacts from other tools into an SDT project.')\n .requiredOption(\n '--from <source>',\n 'Source format (schemachange, snowddl, sqlpackage-dacpac, snowflake-account, snowflake-dcm, terraform-state, sql-files)',\n )\n .requiredOption('--source-path <path>', 'Source artifact path (file or directory)')\n .requiredOption('--output <dir>', 'Output directory for the generated SDT project')\n .option('--connection <profile>', 'Connection profile (required for snowflake-account)')\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 logger.info(\n `Imported ${result.filesCreated} files. Project written to ${result.projectPath}`,\n );\n for (const w of result.warnings) logger.warn(w);\n },\n );\n}\n"],"mappings":";;;;;;AAWA,SAAS,eAAe;AAExB,SAAS,aAAa,oBAAoB;AAGnC,SAAS,gBAAyB;AACvC,SAAO,IAAI,QAAQ,QAAQ,EACxB,YAAY,yDAAyD,EACrE;AAAA,IACC;AAAA,IACA;AAAA,EACF,EACC,eAAe,wBAAwB,0CAA0C,EACjF,eAAe,kBAAkB,gDAAgD,EACjF,OAAO,0BAA0B,qDAAqD,EACtF;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,aAAO;AAAA,QACL,YAAY,OAAO,YAAY,8BAA8B,OAAO,WAAW;AAAA,MACjF;AACA,iBAAW,KAAK,OAAO,SAAU,QAAO,KAAK,CAAC;AAAA,IAChD;AAAA,EACF;AACJ;","names":[]}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import "./chunk-DGUM43GV.js";
|
|
2
|
+
|
|
3
|
+
// src/commands/import-script.ts
|
|
4
|
+
import { promises as fs } from "fs";
|
|
5
|
+
import path from "path";
|
|
6
|
+
import { Command } from "commander";
|
|
7
|
+
import {
|
|
8
|
+
importScript,
|
|
9
|
+
loadProject,
|
|
10
|
+
parseScript
|
|
11
|
+
} from "@sdt-tools/core/project";
|
|
12
|
+
function importScriptCommand() {
|
|
13
|
+
const cmd = new Command("import-script");
|
|
14
|
+
cmd.description(
|
|
15
|
+
"Parse a SQL script and write each DDL statement into the .sdtproj tree under its canonical folder."
|
|
16
|
+
).requiredOption("--script <path>", "Path to the SQL script to import.").requiredOption("-p, --project <path>", "Path to the .sdtproj file.").option("--dry-run", "Report what would be written without touching disk.", false).option("--force", "Overwrite existing files (default refuses on conflict).", false).option(
|
|
17
|
+
"--ignore-errors",
|
|
18
|
+
"Import classifiable statements even when others have errors. Default refuses on any error.",
|
|
19
|
+
false
|
|
20
|
+
).action(async (opts) => {
|
|
21
|
+
const scriptPath = path.resolve(String(opts.script));
|
|
22
|
+
const projectPath = path.resolve(String(opts.project));
|
|
23
|
+
const sql = await fs.readFile(scriptPath, "utf8");
|
|
24
|
+
const parsed = parseScript(sql);
|
|
25
|
+
printParseReport(scriptPath, parsed);
|
|
26
|
+
if (parsed.totalErrors > 0 && !opts.ignoreErrors) {
|
|
27
|
+
console.error(
|
|
28
|
+
`
|
|
29
|
+
Refusing to import: ${parsed.totalErrors} parse error(s) above. Fix the script and re-run, or pass --ignore-errors to import the clean statements.`
|
|
30
|
+
);
|
|
31
|
+
process.exitCode = 2;
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
const loaded = await loadProject(projectPath);
|
|
35
|
+
const result = await importScript(parsed, loaded, {
|
|
36
|
+
dryRun: !!opts.dryRun,
|
|
37
|
+
force: !!opts.force
|
|
38
|
+
});
|
|
39
|
+
console.log("");
|
|
40
|
+
const verb = opts.dryRun ? "would write" : "wrote";
|
|
41
|
+
for (const item of result.imported) {
|
|
42
|
+
const rel = path.relative(loaded.rootDir, item.targetPath);
|
|
43
|
+
console.log(
|
|
44
|
+
` ${verb} ${item.statement.objectType} ${qualified(item.statement)} \u2192 ${rel}`
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
for (const item of result.skipped) {
|
|
48
|
+
const head = item.statement.objectType && item.statement.fqn ? `${item.statement.objectType} ${qualified(item.statement)}` : `<unclassified statement at line ${item.statement.startLine}>`;
|
|
49
|
+
console.log(` skip ${head} \u2014 ${item.reason}`);
|
|
50
|
+
}
|
|
51
|
+
console.log("");
|
|
52
|
+
console.log(
|
|
53
|
+
`Summary: ${result.imported.length} ${verb}, ${result.skipped.length} skipped, ${parsed.totalErrors} errors, ${parsed.totalWarnings} warnings.`
|
|
54
|
+
);
|
|
55
|
+
if (result.imported.length === 0 && result.skipped.length > 0) {
|
|
56
|
+
process.exitCode = 1;
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
return cmd;
|
|
60
|
+
}
|
|
61
|
+
function qualified(stmt) {
|
|
62
|
+
if (!stmt.fqn) return "<no fqn>";
|
|
63
|
+
return [stmt.fqn.database, stmt.fqn.schema, stmt.fqn.name].filter(Boolean).join(".");
|
|
64
|
+
}
|
|
65
|
+
function printParseReport(scriptPath, parsed) {
|
|
66
|
+
console.log(`Parsed ${parsed.statements.length} statement(s) from ${scriptPath}.`);
|
|
67
|
+
if (parsed.totalErrors === 0 && parsed.totalWarnings === 0) {
|
|
68
|
+
console.log("All statements classified cleanly.");
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
for (const stmt of parsed.statements) {
|
|
72
|
+
if (stmt.errors.length === 0 && stmt.warnings.length === 0) continue;
|
|
73
|
+
const head = stmt.objectType && stmt.fqn ? `${stmt.objectType} ${qualified(stmt)}` : "<unclassified>";
|
|
74
|
+
console.log(`
|
|
75
|
+
[line ${stmt.startLine}\u2013${stmt.endLine}] ${head}`);
|
|
76
|
+
for (const e of stmt.errors) console.log(` ERROR: ${e}`);
|
|
77
|
+
for (const w of stmt.warnings) console.log(` warning: ${w}`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
export {
|
|
81
|
+
importScriptCommand
|
|
82
|
+
};
|
|
83
|
+
//# sourceMappingURL=import-script-2OF5BI6A.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/import-script.ts"],"sourcesContent":["import { promises as fs } from 'node:fs';\nimport path from 'node:path';\nimport { Command } from 'commander';\nimport {\n importScript,\n loadProject,\n parseScript,\n type ParsedStatement,\n} from '@sdt-tools/core/project';\n\n/**\n * `sdt import-script` — parse a SQL script and apply each DDL statement to a\n * `.sdtproj` tree, writing each into the canonical folder for its object\n * type (`databases/<db>/schemas/<sch>/<type-folder>/<name>.sql`).\n *\n * This is the script→tree splitter. For converting another tool's artifacts\n * (schemachange / SnowDDL / dacpac / dbt / Terraform / …) into a fresh\n * project, use `sdt import` instead. Byte-aligned with `ddt import-script`.\n *\n * Default mode is safe:\n * - Parses the whole script first; refuses to write anything if any\n * statement has hard errors. The user reviews the report, fixes the\n * script, and re-runs.\n * - Refuses to overwrite existing files. Pass `--force` to clobber.\n * - `--dry-run` shows the plan without writing.\n */\nexport function importScriptCommand(): Command {\n const cmd = new Command('import-script');\n cmd\n .description(\n 'Parse a SQL script and write each DDL statement into the .sdtproj tree under its canonical folder.',\n )\n .requiredOption('--script <path>', 'Path to the SQL script to import.')\n .requiredOption('-p, --project <path>', 'Path to the .sdtproj file.')\n .option('--dry-run', 'Report what would be written without touching disk.', false)\n .option('--force', 'Overwrite existing files (default refuses on conflict).', false)\n .option(\n '--ignore-errors',\n 'Import classifiable statements even when others have errors. Default refuses on any error.',\n false,\n )\n .action(async (opts) => {\n const scriptPath = path.resolve(String(opts.script));\n const projectPath = path.resolve(String(opts.project));\n const sql = await fs.readFile(scriptPath, 'utf8');\n const parsed = parseScript(sql);\n\n // Up-front parse report — surfaces every error/warning verbatim.\n printParseReport(scriptPath, parsed);\n\n if (parsed.totalErrors > 0 && !opts.ignoreErrors) {\n console.error(\n `\\nRefusing to import: ${parsed.totalErrors} parse error(s) above. ` +\n `Fix the script and re-run, or pass --ignore-errors to import the clean statements.`,\n );\n process.exitCode = 2;\n return;\n }\n\n const loaded = await loadProject(projectPath);\n const result = await importScript(parsed, loaded, {\n dryRun: !!opts.dryRun,\n force: !!opts.force,\n });\n\n console.log('');\n const verb = opts.dryRun ? 'would write' : 'wrote';\n for (const item of result.imported) {\n const rel = path.relative(loaded.rootDir, item.targetPath);\n console.log(\n ` ${verb} ${item.statement.objectType} ${qualified(item.statement)} → ${rel}`,\n );\n }\n for (const item of result.skipped) {\n const head =\n item.statement.objectType && item.statement.fqn\n ? `${item.statement.objectType} ${qualified(item.statement)}`\n : `<unclassified statement at line ${item.statement.startLine}>`;\n console.log(` skip ${head} — ${item.reason}`);\n }\n console.log('');\n console.log(\n `Summary: ${result.imported.length} ${verb}, ${result.skipped.length} skipped, ` +\n `${parsed.totalErrors} errors, ${parsed.totalWarnings} warnings.`,\n );\n\n if (result.imported.length === 0 && result.skipped.length > 0) {\n process.exitCode = 1;\n }\n });\n return cmd;\n}\n\nfunction qualified(stmt: ParsedStatement): string {\n if (!stmt.fqn) return '<no fqn>';\n return [stmt.fqn.database, stmt.fqn.schema, stmt.fqn.name].filter(Boolean).join('.');\n}\n\nfunction printParseReport(scriptPath: string, parsed: ReturnType<typeof parseScript>): void {\n console.log(`Parsed ${parsed.statements.length} statement(s) from ${scriptPath}.`);\n if (parsed.totalErrors === 0 && parsed.totalWarnings === 0) {\n console.log('All statements classified cleanly.');\n return;\n }\n for (const stmt of parsed.statements) {\n if (stmt.errors.length === 0 && stmt.warnings.length === 0) continue;\n const head =\n stmt.objectType && stmt.fqn ? `${stmt.objectType} ${qualified(stmt)}` : '<unclassified>';\n console.log(`\\n[line ${stmt.startLine}–${stmt.endLine}] ${head}`);\n for (const e of stmt.errors) console.log(` ERROR: ${e}`);\n for (const w of stmt.warnings) console.log(` warning: ${w}`);\n }\n}\n"],"mappings":"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","names":[]}
|