@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.
Files changed (205) hide show
  1. package/dist/advise-tests-6DRSZMBL.js +87 -0
  2. package/dist/advise-tests-6DRSZMBL.js.map +1 -0
  3. package/dist/ai-G4MJWHTM.js +89 -0
  4. package/dist/ai-G4MJWHTM.js.map +1 -0
  5. package/dist/anonymize-QR6JGXA7.js +123 -0
  6. package/dist/anonymize-QR6JGXA7.js.map +1 -0
  7. package/dist/approval-YVHYTV53.js +73 -0
  8. package/dist/approval-YVHYTV53.js.map +1 -0
  9. package/dist/approval-chain-54KKJZS3.js +120 -0
  10. package/dist/approval-chain-54KKJZS3.js.map +1 -0
  11. package/dist/audit-log-QZFH7LUX.js +159 -0
  12. package/dist/audit-log-QZFH7LUX.js.map +1 -0
  13. package/dist/backlog-V2YUIQDL.js +76 -0
  14. package/dist/backlog-V2YUIQDL.js.map +1 -0
  15. package/dist/bisect-GEVYAVL5.js +111 -0
  16. package/dist/bisect-GEVYAVL5.js.map +1 -0
  17. package/dist/bookmarks-57LKS7P6.js +107 -0
  18. package/dist/bookmarks-57LKS7P6.js.map +1 -0
  19. package/dist/branch-W2MGMPSH.js +88 -0
  20. package/dist/branch-W2MGMPSH.js.map +1 -0
  21. package/dist/build-VNIQFKSP.js +23 -0
  22. package/dist/build-VNIQFKSP.js.map +1 -0
  23. package/dist/catalog-JLB5VCEV.js +137 -0
  24. package/dist/catalog-JLB5VCEV.js.map +1 -0
  25. package/dist/changelog-M7XGDYSY.js +220 -0
  26. package/dist/changelog-M7XGDYSY.js.map +1 -0
  27. package/dist/chunk-DGUM43GV.js +11 -0
  28. package/dist/chunk-DGUM43GV.js.map +1 -0
  29. package/dist/chunk-EWXM4KJN.js +25 -0
  30. package/dist/chunk-EWXM4KJN.js.map +1 -0
  31. package/dist/chunk-JP2EZLR5.js +50 -0
  32. package/dist/chunk-JP2EZLR5.js.map +1 -0
  33. package/dist/chunk-VM2H4LAO.js +15 -0
  34. package/dist/chunk-VM2H4LAO.js.map +1 -0
  35. package/dist/chunk-ZWY4ZRHL.js +44 -0
  36. package/dist/chunk-ZWY4ZRHL.js.map +1 -0
  37. package/dist/cli.js +506 -19014
  38. package/dist/cli.js.map +1 -1
  39. package/dist/compare-5O6UTWPJ.js +405 -0
  40. package/dist/compare-5O6UTWPJ.js.map +1 -0
  41. package/dist/compare-profiles-7ZSNIW7B.js +218 -0
  42. package/dist/compare-profiles-7ZSNIW7B.js.map +1 -0
  43. package/dist/completion-I5U5VVAX.js +82 -0
  44. package/dist/completion-I5U5VVAX.js.map +1 -0
  45. package/dist/connection-SYTH4V53.js +110 -0
  46. package/dist/connection-SYTH4V53.js.map +1 -0
  47. package/dist/cost-estimate-TJDDH6TO.js +328 -0
  48. package/dist/cost-estimate-TJDDH6TO.js.map +1 -0
  49. package/dist/data-compare-UK2UXAS3.js +134 -0
  50. package/dist/data-compare-UK2UXAS3.js.map +1 -0
  51. package/dist/data-fit-Q45ENBRL.js +125 -0
  52. package/dist/data-fit-Q45ENBRL.js.map +1 -0
  53. package/dist/deploy-status-UUHKVDTI.js +58 -0
  54. package/dist/deploy-status-UUHKVDTI.js.map +1 -0
  55. package/dist/design-PO6UPBL7.js +138 -0
  56. package/dist/design-PO6UPBL7.js.map +1 -0
  57. package/dist/diagnose-6IFMELFR.js +145 -0
  58. package/dist/diagnose-6IFMELFR.js.map +1 -0
  59. package/dist/discover-A7OSZAHK.js +78 -0
  60. package/dist/discover-A7OSZAHK.js.map +1 -0
  61. package/dist/docs-CVRKGUSW.js +177 -0
  62. package/dist/docs-CVRKGUSW.js.map +1 -0
  63. package/dist/drift-XDA3BDYN.js +226 -0
  64. package/dist/drift-XDA3BDYN.js.map +1 -0
  65. package/dist/drift-gate-V7QSIOGZ.js +94 -0
  66. package/dist/drift-gate-V7QSIOGZ.js.map +1 -0
  67. package/dist/error-lookup-7ZWCZJ44.js +56 -0
  68. package/dist/error-lookup-7ZWCZJ44.js.map +1 -0
  69. package/dist/errorReporting-ZRNJ3VW7.js +109 -0
  70. package/dist/errorReporting-ZRNJ3VW7.js.map +1 -0
  71. package/dist/exec-PKBHLI7T.js +121 -0
  72. package/dist/exec-PKBHLI7T.js.map +1 -0
  73. package/dist/explain-LWKJOTL7.js +192 -0
  74. package/dist/explain-LWKJOTL7.js.map +1 -0
  75. package/dist/explorer-QOVM6VBD.js +61 -0
  76. package/dist/explorer-QOVM6VBD.js.map +1 -0
  77. package/dist/export-IYYBZ5HE.js +42 -0
  78. package/dist/export-IYYBZ5HE.js.map +1 -0
  79. package/dist/extract-VMMVRQVT.js +102 -0
  80. package/dist/extract-VMMVRQVT.js.map +1 -0
  81. package/dist/features-LE6BDZ2S.js +59 -0
  82. package/dist/features-LE6BDZ2S.js.map +1 -0
  83. package/dist/feedback-M7DM2EQC.js +161 -0
  84. package/dist/feedback-M7DM2EQC.js.map +1 -0
  85. package/dist/find-EME2JG2I.js +176 -0
  86. package/dist/find-EME2JG2I.js.map +1 -0
  87. package/dist/format-TRLWLMGS.js +141 -0
  88. package/dist/format-TRLWLMGS.js.map +1 -0
  89. package/dist/generate-6NAZGZDV.js +152 -0
  90. package/dist/generate-6NAZGZDV.js.map +1 -0
  91. package/dist/graph-QNQDAUO7.js +161 -0
  92. package/dist/graph-QNQDAUO7.js.map +1 -0
  93. package/dist/history-RONA7ZTI.js +199 -0
  94. package/dist/history-RONA7ZTI.js.map +1 -0
  95. package/dist/hosts-YBXY2ZG5.js +49 -0
  96. package/dist/hosts-YBXY2ZG5.js.map +1 -0
  97. package/dist/impact-T2JSANHS.js +59 -0
  98. package/dist/impact-T2JSANHS.js.map +1 -0
  99. package/dist/import-AELYLY6A.js +32 -0
  100. package/dist/import-AELYLY6A.js.map +1 -0
  101. package/dist/index.cjs +36 -6
  102. package/dist/index.cjs.map +1 -1
  103. package/dist/index.js +60 -25
  104. package/dist/index.js.map +1 -1
  105. package/dist/init-SWRRJMGI.js +57 -0
  106. package/dist/init-SWRRJMGI.js.map +1 -0
  107. package/dist/install-hooks-6SIAGTAF.js +109 -0
  108. package/dist/install-hooks-6SIAGTAF.js.map +1 -0
  109. package/dist/license-OAF22PLZ.js +46 -0
  110. package/dist/license-OAF22PLZ.js.map +1 -0
  111. package/dist/lineage-EW66XJ6O.js +552 -0
  112. package/dist/lineage-EW66XJ6O.js.map +1 -0
  113. package/dist/lint-FQ2OTYTQ.js +143 -0
  114. package/dist/lint-FQ2OTYTQ.js.map +1 -0
  115. package/dist/mcp-3QI4TH4N.js +344 -0
  116. package/dist/mcp-3QI4TH4N.js.map +1 -0
  117. package/dist/migrate-from-dbt-JVTXPWKQ.js +156 -0
  118. package/dist/migrate-from-dbt-JVTXPWKQ.js.map +1 -0
  119. package/dist/migrate-platform-NTRTOGNR.js +91 -0
  120. package/dist/migrate-platform-NTRTOGNR.js.map +1 -0
  121. package/dist/optimize-CJYWMAWA.js +105 -0
  122. package/dist/optimize-CJYWMAWA.js.map +1 -0
  123. package/dist/perf-LL2CPCJF.js +205 -0
  124. package/dist/perf-LL2CPCJF.js.map +1 -0
  125. package/dist/pii-FBDRDQ2E.js +136 -0
  126. package/dist/pii-FBDRDQ2E.js.map +1 -0
  127. package/dist/pilot-CCQERKPH.js +29 -0
  128. package/dist/pilot-CCQERKPH.js.map +1 -0
  129. package/dist/pr-comment-S5FF4QRX.js +79 -0
  130. package/dist/pr-comment-S5FF4QRX.js.map +1 -0
  131. package/dist/preview-5U4YVCRM.js +47 -0
  132. package/dist/preview-5U4YVCRM.js.map +1 -0
  133. package/dist/profile-7VC57KD2.js +101 -0
  134. package/dist/profile-7VC57KD2.js.map +1 -0
  135. package/dist/promote-AASEFTIA.js +408 -0
  136. package/dist/promote-AASEFTIA.js.map +1 -0
  137. package/dist/publish-Y2J56K4Y.js +715 -0
  138. package/dist/publish-Y2J56K4Y.js.map +1 -0
  139. package/dist/purge-QMXZKCMD.js +57 -0
  140. package/dist/purge-QMXZKCMD.js.map +1 -0
  141. package/dist/query-log-6OM4GI7W.js +112 -0
  142. package/dist/query-log-6OM4GI7W.js.map +1 -0
  143. package/dist/refactor-LTZQLJ35.js +5799 -0
  144. package/dist/refactor-LTZQLJ35.js.map +1 -0
  145. package/dist/refresh-4TY2AGOU.js +38 -0
  146. package/dist/refresh-4TY2AGOU.js.map +1 -0
  147. package/dist/replay-OOC25FZN.js +117 -0
  148. package/dist/replay-OOC25FZN.js.map +1 -0
  149. package/dist/revert-ODMUVJW6.js +110 -0
  150. package/dist/revert-ODMUVJW6.js.map +1 -0
  151. package/dist/review-XXPWOBFP.js +158 -0
  152. package/dist/review-XXPWOBFP.js.map +1 -0
  153. package/dist/rollback-suggest-6G2HEKFR.js +79 -0
  154. package/dist/rollback-suggest-6G2HEKFR.js.map +1 -0
  155. package/dist/safer-alternative-QFVNLG3L.js +89 -0
  156. package/dist/safer-alternative-QFVNLG3L.js.map +1 -0
  157. package/dist/safety-7QWRSUEZ.js +168 -0
  158. package/dist/safety-7QWRSUEZ.js.map +1 -0
  159. package/dist/savings-RHIXP6IT.js +95 -0
  160. package/dist/savings-RHIXP6IT.js.map +1 -0
  161. package/dist/scan-secrets-5YCQ4UCU.js +54 -0
  162. package/dist/scan-secrets-5YCQ4UCU.js.map +1 -0
  163. package/dist/schema-CIZXCQD2.js +429 -0
  164. package/dist/schema-CIZXCQD2.js.map +1 -0
  165. package/dist/script-K7CIN2P6.js +153 -0
  166. package/dist/script-K7CIN2P6.js.map +1 -0
  167. package/dist/search-BUZ5NXZZ.js +151 -0
  168. package/dist/search-BUZ5NXZZ.js.map +1 -0
  169. package/dist/seed-76QAK276.js +96 -0
  170. package/dist/seed-76QAK276.js.map +1 -0
  171. package/dist/sketch-PTLKDIK3.js +88 -0
  172. package/dist/sketch-PTLKDIK3.js.map +1 -0
  173. package/dist/snapshot-XLPR2OZ5.js +177 -0
  174. package/dist/snapshot-XLPR2OZ5.js.map +1 -0
  175. package/dist/snippets-EK4DK5CN.js +74 -0
  176. package/dist/snippets-EK4DK5CN.js.map +1 -0
  177. package/dist/standards-7T2UY6DD.js +241 -0
  178. package/dist/standards-7T2UY6DD.js.map +1 -0
  179. package/dist/suggest-VGRYSAR6.js +39 -0
  180. package/dist/suggest-VGRYSAR6.js.map +1 -0
  181. package/dist/suggest-constraints-MY5WKUHA.js +160 -0
  182. package/dist/suggest-constraints-MY5WKUHA.js.map +1 -0
  183. package/dist/suite-TRNGZWQM.js +88 -0
  184. package/dist/suite-TRNGZWQM.js.map +1 -0
  185. package/dist/telemetry-3U2QLA2S.js +75 -0
  186. package/dist/telemetry-3U2QLA2S.js.map +1 -0
  187. package/dist/template-ZERIXVXF.js +403 -0
  188. package/dist/template-ZERIXVXF.js.map +1 -0
  189. package/dist/test-5M2ED3WT.js +169 -0
  190. package/dist/test-5M2ED3WT.js.map +1 -0
  191. package/dist/trial-U732FONV.js +31 -0
  192. package/dist/trial-U732FONV.js.map +1 -0
  193. package/dist/validate-T6D2WCOK.js +106 -0
  194. package/dist/validate-T6D2WCOK.js.map +1 -0
  195. package/dist/verify-KXVASEEG.js +76 -0
  196. package/dist/verify-KXVASEEG.js.map +1 -0
  197. package/dist/watch-I6K4BNMA.js +80 -0
  198. package/dist/watch-I6K4BNMA.js.map +1 -0
  199. package/dist/xcompare-TPFLQO6W.js +87 -0
  200. package/dist/xcompare-TPFLQO6W.js.map +1 -0
  201. package/package.json +2 -2
  202. package/dist/cli.cjs +0 -19040
  203. package/dist/cli.cjs.map +0 -1
  204. package/dist/cli.d.cts +0 -1
  205. 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()).action(async (opts) => {
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("<source>", "Left side of the comparison").argument("<target>", "Right side of the comparison").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(
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 (sourceArg, targetArg, opts) => {
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 fileContents = await Promise.all(
1579
- files.map(async (f) => {
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(