@sdt-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-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 +506 -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-SYTH4V53.js +110 -0
- package/dist/connection-SYTH4V53.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-ZRNJ3VW7.js +109 -0
- package/dist/errorReporting-ZRNJ3VW7.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/index.cjs +36 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +60 -25
- 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-3QI4TH4N.js +344 -0
- package/dist/mcp-3QI4TH4N.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-Y2J56K4Y.js +715 -0
- package/dist/publish-Y2J56K4Y.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":[]}
|
package/dist/index.cjs
CHANGED
|
@@ -86,7 +86,10 @@ var logger = {
|
|
|
86
86
|
// src/commands/init.ts
|
|
87
87
|
function initCommand() {
|
|
88
88
|
const cmd = new import_commander.Command("init");
|
|
89
|
-
cmd.description("Initialize a new SDT project in the current directory.").option("--name <name>", "Project name", "NewSnowflakeProject").option("--scope <scope>", "Project scope: account | database | schema", "database").option("--db <database>", "Database name (required for database/schema scope)").option("--schema <schema>", "Schema name (required for schema scope)").option("--dir <dir>", "Target directory (default: cwd)", process.cwd()).
|
|
89
|
+
cmd.description("Initialize a new SDT project in the current directory.").option("--name <name>", "Project name", "NewSnowflakeProject").option("--scope <scope>", "Project scope: account | database | schema", "database").option("--db <database>", "Database name (required for database/schema scope)").option("--schema <schema>", "Schema name (required for schema scope)").option("--dir <dir>", "Target directory (default: cwd)", process.cwd()).option(
|
|
90
|
+
"--force",
|
|
91
|
+
"Overwrite an existing <name>.sdtproj in the target directory. Without this flag, init refuses to clobber an existing project file so a re-run never silently discards your project configuration."
|
|
92
|
+
).action(async (opts) => {
|
|
90
93
|
const scopeType = String(opts.scope);
|
|
91
94
|
if (!["account", "database", "schema"].includes(scopeType)) {
|
|
92
95
|
throw new Error(`Invalid --scope: ${opts.scope}. Use account | database | schema.`);
|
|
@@ -104,6 +107,15 @@ function initCommand() {
|
|
|
104
107
|
await import_node_fs.promises.mkdir(root, { recursive: true });
|
|
105
108
|
const project10 = (0, import_project.newProjectTemplate)(String(opts.name), scope);
|
|
106
109
|
const projectPath = import_node_path.default.join(root, `${project10.name}.sdtproj`);
|
|
110
|
+
if (!opts.force) {
|
|
111
|
+
try {
|
|
112
|
+
await import_node_fs.promises.access(projectPath);
|
|
113
|
+
logger.error(`${projectPath} already exists; pass --force to overwrite.`);
|
|
114
|
+
process.exitCode = 1;
|
|
115
|
+
return;
|
|
116
|
+
} catch {
|
|
117
|
+
}
|
|
118
|
+
}
|
|
107
119
|
await (0, import_project.saveProject)(projectPath, project10);
|
|
108
120
|
const seedFolders = ["databases", "scripts/pre", "scripts/post"];
|
|
109
121
|
for (const f of seedFolders) await import_node_fs.promises.mkdir(import_node_path.default.join(root, f), { recursive: true });
|
|
@@ -380,7 +392,13 @@ function compareCommand() {
|
|
|
380
392
|
const cmd = new import_commander5.Command("compare");
|
|
381
393
|
cmd.description(
|
|
382
394
|
"Compare two schemas. Sources may be .sdtproj, .sdtpac, or snowflake://<profile>[/db[/schema]]."
|
|
383
|
-
).argument("
|
|
395
|
+
).argument("[source]", "Left side of the comparison (or use --source)").argument("[target]", "Right side of the comparison (or use --target)").option(
|
|
396
|
+
"--source <path>",
|
|
397
|
+
"Left side of the comparison (flag form of the positional source arg)"
|
|
398
|
+
).option(
|
|
399
|
+
"--target <path>",
|
|
400
|
+
"Right side of the comparison (flag form of the positional target arg)"
|
|
401
|
+
).option("-o, --output <path>", "Write JSON result to this path").option("--format <format>", "Output format: json | summary | markdown", "summary").option("--ignore-case", "Treat unquoted identifiers case-insensitively", false).option(
|
|
384
402
|
"--no-slice",
|
|
385
403
|
"Disable the source project's Slice (if it has one). Default: a project's slice is applied automatically."
|
|
386
404
|
).option(
|
|
@@ -405,7 +423,14 @@ function compareCommand() {
|
|
|
405
423
|
).option(
|
|
406
424
|
"--no-history",
|
|
407
425
|
"Skip writing the compare-history audit record (AUDITCMP.1). Default: every compare run writes a record to `.sdt/history/compare/`, exportable via `sdt audit-log emit`."
|
|
408
|
-
).action(async (
|
|
426
|
+
).action(async (sourceArgPos, targetArgPos, opts) => {
|
|
427
|
+
const sourceArg = opts.source ? String(opts.source) : sourceArgPos ? String(sourceArgPos) : "";
|
|
428
|
+
const targetArg = opts.target ? String(opts.target) : targetArgPos ? String(targetArgPos) : "";
|
|
429
|
+
if (!sourceArg || !targetArg) {
|
|
430
|
+
throw new Error(
|
|
431
|
+
"compare needs a source and a target \u2014 pass them positionally (`sdt compare <source> <target>`) or via `--source`/`--target`."
|
|
432
|
+
);
|
|
433
|
+
}
|
|
409
434
|
const nameMapping = await buildMappingFromOptions(opts);
|
|
410
435
|
const source = await resolveSource(String(sourceArg));
|
|
411
436
|
const target = await resolveSource(String(targetArg));
|
|
@@ -1575,12 +1600,17 @@ function validateCommand() {
|
|
|
1575
1600
|
logger.dim(` Profiles: ${Object.keys(loaded.project.deploymentProfiles ?? {}).length}`);
|
|
1576
1601
|
if (opts.checkVariables) {
|
|
1577
1602
|
const projRoot = import_node_path6.default.dirname(import_node_path6.default.resolve(String(opts.project)));
|
|
1578
|
-
const
|
|
1579
|
-
files
|
|
1603
|
+
const { results } = await import_core6.catalog.mapPool(
|
|
1604
|
+
files,
|
|
1605
|
+
async (f) => {
|
|
1580
1606
|
const relPath = import_node_path6.default.relative(projRoot, f).split(import_node_path6.default.sep).join("/");
|
|
1581
1607
|
const content = await import_node_fs6.promises.readFile(f, "utf8");
|
|
1582
1608
|
return { path: relPath, content };
|
|
1583
|
-
}
|
|
1609
|
+
},
|
|
1610
|
+
{ concurrency: 16, stopOnError: true }
|
|
1611
|
+
);
|
|
1612
|
+
const fileContents = results.filter(
|
|
1613
|
+
(r) => r !== void 0
|
|
1584
1614
|
);
|
|
1585
1615
|
const profilesBlock = loaded.project.deploymentProfiles ?? {};
|
|
1586
1616
|
const profileVariableKeys = Object.values(profilesBlock).map(
|