@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,159 @@
1
+ import "./chunk-DGUM43GV.js";
2
+
3
+ // src/commands/audit-log.ts
4
+ import { Command } from "commander";
5
+ import { writeFile } from "fs/promises";
6
+ import { auditLog, compare, deploy } from "@sdt-tools/core";
7
+ function auditLogCommand() {
8
+ const cmd = new Command("audit-log");
9
+ cmd.description(
10
+ "Export deploy history to enterprise audit sinks (file / syslog / Splunk HEC / Datadog Logs)."
11
+ );
12
+ cmd.command("emit").description(
13
+ "Read .sdt/history/*.json (deploys) + .sdt/history/compare/*.json (compares) and emit records to the chosen sink. `--sink file|syslog` emit lines; `--sink splunk|datadog` POST HTTP."
14
+ ).requiredOption("--sink <kind>", "One of: 'file' | 'syslog' | 'splunk' | 'datadog' | 'otlp'.").option(
15
+ "--workspace <path>",
16
+ "Workspace root containing .sdt/history. Default cwd.",
17
+ process.cwd()
18
+ ).option("-o, --out <path>", "Output path (file/syslog sinks). Default stdout.").option("--splunk-url <url>", "Splunk HEC endpoint URL (splunk sink).").option("--splunk-token <tok>", "Splunk HEC token (splunk sink).").option("--splunk-index <idx>", "Splunk index (optional).").option("--splunk-source <src>", "Splunk source field (default: sdt).").option("--datadog-site <site>", "Datadog site (e.g. 'datadoghq.com').").option("--datadog-api-key <key>", "Datadog API key.").option("--datadog-tags <tags>", "Datadog ddtags string (e.g. env:prod,team:data).").option("--datadog-hostname <h>", "Datadog hostname attribute.").option("--syslog-host <h>", "Syslog hostname field (default: localhost).").option("--syslog-app <name>", "Syslog app-name field (default: sdt).").option("--syslog-facility <n>", "Syslog facility (0-23, default 1).").option("--otlp-url <url>", "OTLP/HTTP logs endpoint, e.g. https://otelcol.example/v1/logs.").option(
19
+ "--otlp-header <kv...>",
20
+ "OTLP extra header (repeatable), e.g. --otlp-header authorization=Bearer\\ <tok>."
21
+ ).option("--otlp-service-name <name>", "OTLP resource service.name (default: 'sdt').").option("--otlp-service-version <v>", "OTLP resource service.version.").option(
22
+ "--dry-run",
23
+ "For splunk/datadog sinks, print the prepared HTTP request without POSTing.",
24
+ false
25
+ ).action(async (opts) => {
26
+ const records = await deploy.loadDeployHistory(opts.workspace);
27
+ const compareRecords = await compare.loadCompareHistory(opts.workspace);
28
+ const events = auditLog.toAuditEvents(records, compareRecords);
29
+ if (events.length === 0) {
30
+ process.stderr.write(
31
+ `No deploy-history records (or compare-history records) found under ${opts.workspace}/.sdt/history
32
+ `
33
+ );
34
+ return;
35
+ }
36
+ switch (opts.sink) {
37
+ case "file": {
38
+ const lines = events.map((e) => auditLog.formatJsonLine(e)).join("");
39
+ await emitLines(lines, opts.out);
40
+ process.stderr.write(`Emitted ${events.length} JSONL audit record(s).
41
+ `);
42
+ return;
43
+ }
44
+ case "syslog": {
45
+ const facility = opts.syslogFacility !== void 0 ? Number(opts.syslogFacility) : void 0;
46
+ const syslogOpts = {
47
+ ...opts.syslogHost !== void 0 ? { hostname: opts.syslogHost } : {},
48
+ ...opts.syslogApp !== void 0 ? { appName: opts.syslogApp } : {},
49
+ ...facility !== void 0 && Number.isFinite(facility) ? { facility } : {}
50
+ };
51
+ const lines = events.map((e) => auditLog.formatSyslogLine(e, syslogOpts)).join("\n") + "\n";
52
+ await emitLines(lines, opts.out);
53
+ process.stderr.write(`Emitted ${events.length} RFC 5424 syslog line(s).
54
+ `);
55
+ return;
56
+ }
57
+ case "splunk": {
58
+ if (!opts.splunkUrl || !opts.splunkToken) {
59
+ throw new Error("--sink splunk requires both --splunk-url and --splunk-token.");
60
+ }
61
+ const req = auditLog.buildSplunkHecRequest(events, {
62
+ url: opts.splunkUrl,
63
+ token: opts.splunkToken,
64
+ ...opts.splunkIndex !== void 0 ? { index: opts.splunkIndex } : {},
65
+ ...opts.splunkSource !== void 0 ? { source: opts.splunkSource } : {}
66
+ });
67
+ if (opts.dryRun) {
68
+ await emitLines(formatRequestPreview(req), opts.out);
69
+ } else {
70
+ await postRequest(req);
71
+ process.stderr.write(`Posted ${events.length} audit record(s) to Splunk HEC.
72
+ `);
73
+ }
74
+ return;
75
+ }
76
+ case "otlp": {
77
+ if (!opts.otlpUrl) {
78
+ throw new Error("--sink otlp requires --otlp-url.");
79
+ }
80
+ const headerEntries = parseOtlpHeaders(opts.otlpHeader);
81
+ const otlpOpts = {
82
+ url: opts.otlpUrl,
83
+ ...Object.keys(headerEntries).length > 0 ? { headers: headerEntries } : {},
84
+ ...opts.otlpServiceName !== void 0 ? { serviceName: opts.otlpServiceName } : {},
85
+ ...opts.otlpServiceVersion !== void 0 ? { serviceVersion: opts.otlpServiceVersion } : {}
86
+ };
87
+ const req = auditLog.buildOtlpLogsRequest(events, otlpOpts);
88
+ if (opts.dryRun) {
89
+ await emitLines(formatRequestPreview(req), opts.out);
90
+ } else {
91
+ await postRequest(req);
92
+ process.stderr.write(`Posted ${events.length} audit record(s) to OTLP.
93
+ `);
94
+ }
95
+ return;
96
+ }
97
+ case "datadog": {
98
+ if (!opts.datadogSite || !opts.datadogApiKey) {
99
+ throw new Error("--sink datadog requires both --datadog-site and --datadog-api-key.");
100
+ }
101
+ const req = auditLog.buildDatadogLogsRequest(events, {
102
+ site: opts.datadogSite,
103
+ apiKey: opts.datadogApiKey,
104
+ ...opts.datadogTags !== void 0 ? { ddtags: opts.datadogTags } : {},
105
+ ...opts.datadogHostname !== void 0 ? { hostname: opts.datadogHostname } : {}
106
+ });
107
+ if (opts.dryRun) {
108
+ await emitLines(formatRequestPreview(req), opts.out);
109
+ } else {
110
+ await postRequest(req);
111
+ process.stderr.write(`Posted ${events.length} audit record(s) to Datadog Logs.
112
+ `);
113
+ }
114
+ return;
115
+ }
116
+ default:
117
+ throw new Error(`Unknown --sink value: ${String(opts.sink)}.`);
118
+ }
119
+ });
120
+ return cmd;
121
+ }
122
+ function parseOtlpHeaders(raw) {
123
+ const out = {};
124
+ if (!raw) return out;
125
+ for (const entry of raw) {
126
+ const eq = entry.indexOf("=");
127
+ if (eq <= 0) {
128
+ throw new Error(`--otlp-header expects key=value, got: ${entry}`);
129
+ }
130
+ out[entry.slice(0, eq)] = entry.slice(eq + 1);
131
+ }
132
+ return out;
133
+ }
134
+ async function emitLines(body, out) {
135
+ if (out) {
136
+ await writeFile(out, body, "utf8");
137
+ } else {
138
+ process.stdout.write(body);
139
+ }
140
+ }
141
+ function formatRequestPreview(req) {
142
+ const headers = Object.entries(req.headers).map(([k, v]) => `${k}: ${v}`).join("\n");
143
+ return `${req.method} ${req.url}
144
+ ${headers}
145
+
146
+ ${req.body}
147
+ `;
148
+ }
149
+ async function postRequest(req) {
150
+ const res = await fetch(req.url, { method: req.method, headers: req.headers, body: req.body });
151
+ if (!res.ok) {
152
+ const text = await res.text();
153
+ throw new Error(`Audit sink POST failed: ${res.status} ${res.statusText} ${text}`);
154
+ }
155
+ }
156
+ export {
157
+ auditLogCommand
158
+ };
159
+ //# sourceMappingURL=audit-log-QZFH7LUX.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/audit-log.ts"],"sourcesContent":["/**\n * `sdt audit-log emit` — export deploy + compare history records to\n * enterprise audit sinks. Reads `.sdt/history/*.json` (deploys) and\n * `.sdt/history/compare/*.json` (compares, AUDITCMP.1), normalises each\n * record via `@sdt-tools/core/auditLog.toAuditEvents`, then writes / POSTs\n * based on the `--sink` selector.\n */\nimport { Command } from 'commander';\nimport { writeFile } from 'node:fs/promises';\nimport { auditLog, compare, deploy } from '@sdt-tools/core';\n\ntype Sink = 'file' | 'syslog' | 'splunk' | 'datadog' | 'otlp';\n\nexport function auditLogCommand(): Command {\n const cmd = new Command('audit-log');\n cmd.description(\n 'Export deploy history to enterprise audit sinks (file / syslog / Splunk HEC / Datadog Logs).',\n );\n\n cmd\n .command('emit')\n .description(\n 'Read .sdt/history/*.json (deploys) + .sdt/history/compare/*.json (compares) and emit records to the chosen sink. ' +\n '`--sink file|syslog` emit lines; `--sink splunk|datadog` POST HTTP.',\n )\n .requiredOption('--sink <kind>', \"One of: 'file' | 'syslog' | 'splunk' | 'datadog' | 'otlp'.\")\n .option(\n '--workspace <path>',\n 'Workspace root containing .sdt/history. Default cwd.',\n process.cwd(),\n )\n .option('-o, --out <path>', 'Output path (file/syslog sinks). Default stdout.')\n .option('--splunk-url <url>', 'Splunk HEC endpoint URL (splunk sink).')\n .option('--splunk-token <tok>', 'Splunk HEC token (splunk sink).')\n .option('--splunk-index <idx>', 'Splunk index (optional).')\n .option('--splunk-source <src>', 'Splunk source field (default: sdt).')\n .option('--datadog-site <site>', \"Datadog site (e.g. 'datadoghq.com').\")\n .option('--datadog-api-key <key>', 'Datadog API key.')\n .option('--datadog-tags <tags>', 'Datadog ddtags string (e.g. env:prod,team:data).')\n .option('--datadog-hostname <h>', 'Datadog hostname attribute.')\n .option('--syslog-host <h>', 'Syslog hostname field (default: localhost).')\n .option('--syslog-app <name>', 'Syslog app-name field (default: sdt).')\n .option('--syslog-facility <n>', 'Syslog facility (0-23, default 1).')\n .option('--otlp-url <url>', 'OTLP/HTTP logs endpoint, e.g. https://otelcol.example/v1/logs.')\n .option(\n '--otlp-header <kv...>',\n 'OTLP extra header (repeatable), e.g. --otlp-header authorization=Bearer\\\\ <tok>.',\n )\n .option('--otlp-service-name <name>', \"OTLP resource service.name (default: 'sdt').\")\n .option('--otlp-service-version <v>', 'OTLP resource service.version.')\n .option(\n '--dry-run',\n 'For splunk/datadog sinks, print the prepared HTTP request without POSTing.',\n false,\n )\n .action(async (opts: SinkOptions) => {\n const records = await deploy.loadDeployHistory(opts.workspace);\n const compareRecords = await compare.loadCompareHistory(opts.workspace);\n const events = auditLog.toAuditEvents(records, compareRecords);\n if (events.length === 0) {\n process.stderr.write(\n `No deploy-history records (or compare-history records) found under ${opts.workspace}/.sdt/history\\n`,\n );\n return;\n }\n switch (opts.sink) {\n case 'file': {\n const lines = events.map((e) => auditLog.formatJsonLine(e)).join('');\n await emitLines(lines, opts.out);\n process.stderr.write(`Emitted ${events.length} JSONL audit record(s).\\n`);\n return;\n }\n case 'syslog': {\n const facility =\n opts.syslogFacility !== undefined ? Number(opts.syslogFacility) : undefined;\n const syslogOpts: auditLog.SyslogFormatOptions = {\n ...(opts.syslogHost !== undefined ? { hostname: opts.syslogHost } : {}),\n ...(opts.syslogApp !== undefined ? { appName: opts.syslogApp } : {}),\n ...(facility !== undefined && Number.isFinite(facility) ? { facility } : {}),\n };\n const lines =\n events.map((e) => auditLog.formatSyslogLine(e, syslogOpts)).join('\\n') + '\\n';\n await emitLines(lines, opts.out);\n process.stderr.write(`Emitted ${events.length} RFC 5424 syslog line(s).\\n`);\n return;\n }\n case 'splunk': {\n if (!opts.splunkUrl || !opts.splunkToken) {\n throw new Error('--sink splunk requires both --splunk-url and --splunk-token.');\n }\n const req = auditLog.buildSplunkHecRequest(events, {\n url: opts.splunkUrl,\n token: opts.splunkToken,\n ...(opts.splunkIndex !== undefined ? { index: opts.splunkIndex } : {}),\n ...(opts.splunkSource !== undefined ? { source: opts.splunkSource } : {}),\n });\n if (opts.dryRun) {\n await emitLines(formatRequestPreview(req), opts.out);\n } else {\n await postRequest(req);\n process.stderr.write(`Posted ${events.length} audit record(s) to Splunk HEC.\\n`);\n }\n return;\n }\n case 'otlp': {\n if (!opts.otlpUrl) {\n throw new Error('--sink otlp requires --otlp-url.');\n }\n const headerEntries = parseOtlpHeaders(opts.otlpHeader);\n const otlpOpts: auditLog.OtlpLogsOptions = {\n url: opts.otlpUrl,\n ...(Object.keys(headerEntries).length > 0 ? { headers: headerEntries } : {}),\n ...(opts.otlpServiceName !== undefined ? { serviceName: opts.otlpServiceName } : {}),\n ...(opts.otlpServiceVersion !== undefined\n ? { serviceVersion: opts.otlpServiceVersion }\n : {}),\n };\n const req = auditLog.buildOtlpLogsRequest(events, otlpOpts);\n if (opts.dryRun) {\n await emitLines(formatRequestPreview(req), opts.out);\n } else {\n await postRequest(req);\n process.stderr.write(`Posted ${events.length} audit record(s) to OTLP.\\n`);\n }\n return;\n }\n case 'datadog': {\n if (!opts.datadogSite || !opts.datadogApiKey) {\n throw new Error('--sink datadog requires both --datadog-site and --datadog-api-key.');\n }\n const req = auditLog.buildDatadogLogsRequest(events, {\n site: opts.datadogSite,\n apiKey: opts.datadogApiKey,\n ...(opts.datadogTags !== undefined ? { ddtags: opts.datadogTags } : {}),\n ...(opts.datadogHostname !== undefined ? { hostname: opts.datadogHostname } : {}),\n });\n if (opts.dryRun) {\n await emitLines(formatRequestPreview(req), opts.out);\n } else {\n await postRequest(req);\n process.stderr.write(`Posted ${events.length} audit record(s) to Datadog Logs.\\n`);\n }\n return;\n }\n default:\n throw new Error(`Unknown --sink value: ${String((opts as { sink: string }).sink)}.`);\n }\n });\n\n return cmd;\n}\n\ninterface SinkOptions {\n sink: Sink;\n workspace: string;\n out?: string;\n splunkUrl?: string;\n splunkToken?: string;\n splunkIndex?: string;\n splunkSource?: string;\n datadogSite?: string;\n datadogApiKey?: string;\n datadogTags?: string;\n datadogHostname?: string;\n syslogHost?: string;\n syslogApp?: string;\n syslogFacility?: string;\n otlpUrl?: string;\n otlpHeader?: string[];\n otlpServiceName?: string;\n otlpServiceVersion?: string;\n dryRun: boolean;\n}\n\nfunction parseOtlpHeaders(raw: string[] | undefined): Record<string, string> {\n const out: Record<string, string> = {};\n if (!raw) return out;\n for (const entry of raw) {\n const eq = entry.indexOf('=');\n if (eq <= 0) {\n throw new Error(`--otlp-header expects key=value, got: ${entry}`);\n }\n out[entry.slice(0, eq)] = entry.slice(eq + 1);\n }\n return out;\n}\n\nasync function emitLines(body: string, out?: string): Promise<void> {\n if (out) {\n await writeFile(out, body, 'utf8');\n } else {\n process.stdout.write(body);\n }\n}\n\nfunction formatRequestPreview(req: auditLog.HttpSinkRequest): string {\n const headers = Object.entries(req.headers)\n .map(([k, v]) => `${k}: ${v}`)\n .join('\\n');\n return `${req.method} ${req.url}\\n${headers}\\n\\n${req.body}\\n`;\n}\n\nasync function postRequest(req: auditLog.HttpSinkRequest): Promise<void> {\n const res = await fetch(req.url, { method: req.method, headers: req.headers, body: req.body });\n if (!res.ok) {\n const text = await res.text();\n throw new Error(`Audit sink POST failed: ${res.status} ${res.statusText} ${text}`);\n }\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,76 @@
1
+ import "./chunk-DGUM43GV.js";
2
+
3
+ // src/commands/backlog.ts
4
+ import { promises as fs } from "fs";
5
+ import path from "path";
6
+ import { Command } from "commander";
7
+ import { backlogScore } from "@sdt-tools/core";
8
+ var DEFAULT_BACKLOG_CANDIDATES = [
9
+ path.join("docs", "BACKLOG.md"),
10
+ path.join("..", "docs", "BACKLOG.md"),
11
+ path.join("..", "..", "docs", "BACKLOG.md"),
12
+ path.join("..", "..", "..", "docs", "BACKLOG.md")
13
+ ];
14
+ function backlogCommand() {
15
+ const cmd = new Command("backlog");
16
+ cmd.description("Local-compute backlog tooling. Pure parse + score; no warehouse contact.");
17
+ cmd.command("score").description(
18
+ "Score docs/BACKLOG.md per the /next formula. Default: JSON to stdout. Use --explain-scores for a human-readable breakdown."
19
+ ).option("--source <path>", "Override the BACKLOG.md path.").option("--format <fmt>", "json | markdown (default json).", "json").option("--explain-scores", "Append per-item formula breakdown.", false).option("--no-cluster-bias", "Skip the cluster-closing bias subtraction.").action(async (opts) => {
20
+ const sourcePath = await resolveBacklogPath(opts.source);
21
+ const markdown = await fs.readFile(sourcePath, "utf8");
22
+ const applyClusterClosingBias = opts.clusterBias !== false;
23
+ const scored = backlogScore.scoreBacklog(markdown, { applyClusterClosingBias });
24
+ const fmt = String(opts.format ?? "json").toLowerCase();
25
+ if (fmt === "json") {
26
+ process.stdout.write(JSON.stringify(scored, null, 2) + "\n");
27
+ if (opts.explainScores) {
28
+ for (const item of scored) {
29
+ process.stderr.write("\n" + backlogScore.formatScoreExplanation(item) + "\n");
30
+ }
31
+ }
32
+ return;
33
+ }
34
+ if (fmt === "markdown") {
35
+ emitMarkdown(scored, Boolean(opts.explainScores));
36
+ return;
37
+ }
38
+ throw new Error(`Unknown --format: ${opts.format}. Use json | markdown.`);
39
+ });
40
+ return cmd;
41
+ }
42
+ async function resolveBacklogPath(override) {
43
+ if (override) {
44
+ return path.resolve(override);
45
+ }
46
+ for (const rel of DEFAULT_BACKLOG_CANDIDATES) {
47
+ const candidate = path.resolve(rel);
48
+ try {
49
+ await fs.access(candidate);
50
+ return candidate;
51
+ } catch {
52
+ }
53
+ }
54
+ throw new Error(
55
+ "BACKLOG.md not found in CWD or any parent up to depth 3. Pass --source <path> to override."
56
+ );
57
+ }
58
+ function emitMarkdown(scored, explain) {
59
+ process.stdout.write("| id | cluster | complexity | risk | paired | closes | score |\n");
60
+ process.stdout.write("|---|---|---|---|---|---|---|\n");
61
+ for (const item of scored) {
62
+ process.stdout.write(
63
+ `| ${item.id} | ${item.cluster} | ${item.complexity ?? "?"} | ${item.risk} | ${item.paired ? "Y" : ""} | ${item.closesCluster ? "\u2605" : ""} | ${item.score ?? "?"} |
64
+ `
65
+ );
66
+ }
67
+ if (explain) {
68
+ for (const item of scored) {
69
+ process.stdout.write("\n" + backlogScore.formatScoreExplanation(item) + "\n");
70
+ }
71
+ }
72
+ }
73
+ export {
74
+ backlogCommand
75
+ };
76
+ //# sourceMappingURL=backlog-V2YUIQDL.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/backlog.ts"],"sourcesContent":["/**\n * `sdt backlog score` — pure local-compute backlog scorer (LOCAL.1).\n *\n * Walks the project's `docs/BACKLOG.md` (or `--source <path>`), applies\n * the deterministic `/next` scoring formula, and emits a stable JSON\n * array of `{id, title, family, cluster, status, tier, model, score,\n * complexity, risk, paired, closesCluster, dependsOn, area}`. The\n * `/next` propose phase reads this instead of re-deriving scores\n * in-band — saves ~5k tokens per call.\n *\n * Flags:\n * --source <path> Override the default BACKLOG.md location.\n * --format json|markdown Output format (default json).\n * --explain-scores Append per-item formula breakdown (markdown).\n * --no-cluster-bias Skip the cluster-closing bias subtraction.\n *\n * Mirrors `Databricks/packages/cli/src/commands/backlog.ts`.\n */\nimport { promises as fs } from 'node:fs';\nimport path from 'node:path';\nimport { Command } from 'commander';\nimport { backlogScore } from '@sdt-tools/core';\n\nconst DEFAULT_BACKLOG_CANDIDATES = [\n path.join('docs', 'BACKLOG.md'),\n path.join('..', 'docs', 'BACKLOG.md'),\n path.join('..', '..', 'docs', 'BACKLOG.md'),\n path.join('..', '..', '..', 'docs', 'BACKLOG.md'),\n];\n\nexport function backlogCommand(): Command {\n const cmd = new Command('backlog');\n cmd.description('Local-compute backlog tooling. Pure parse + score; no warehouse contact.');\n\n cmd\n .command('score')\n .description(\n 'Score docs/BACKLOG.md per the /next formula. Default: JSON to stdout. Use --explain-scores for a human-readable breakdown.',\n )\n .option('--source <path>', 'Override the BACKLOG.md path.')\n .option('--format <fmt>', 'json | markdown (default json).', 'json')\n .option('--explain-scores', 'Append per-item formula breakdown.', false)\n .option('--no-cluster-bias', 'Skip the cluster-closing bias subtraction.')\n .action(async (opts) => {\n const sourcePath = await resolveBacklogPath(opts.source);\n const markdown = await fs.readFile(sourcePath, 'utf8');\n const applyClusterClosingBias = opts.clusterBias !== false;\n const scored = backlogScore.scoreBacklog(markdown, { applyClusterClosingBias });\n\n const fmt = String(opts.format ?? 'json').toLowerCase();\n if (fmt === 'json') {\n process.stdout.write(JSON.stringify(scored, null, 2) + '\\n');\n if (opts.explainScores) {\n for (const item of scored) {\n process.stderr.write('\\n' + backlogScore.formatScoreExplanation(item) + '\\n');\n }\n }\n return;\n }\n if (fmt === 'markdown') {\n emitMarkdown(scored, Boolean(opts.explainScores));\n return;\n }\n throw new Error(`Unknown --format: ${opts.format}. Use json | markdown.`);\n });\n\n return cmd;\n}\n\nasync function resolveBacklogPath(override: string | undefined): Promise<string> {\n if (override) {\n return path.resolve(override);\n }\n for (const rel of DEFAULT_BACKLOG_CANDIDATES) {\n const candidate = path.resolve(rel);\n try {\n await fs.access(candidate);\n return candidate;\n } catch {\n // Not at this depth — try the next.\n }\n }\n throw new Error(\n 'BACKLOG.md not found in CWD or any parent up to depth 3. Pass --source <path> to override.',\n );\n}\n\nfunction emitMarkdown(scored: readonly backlogScore.ScoredBacklogItem[], explain: boolean): void {\n process.stdout.write('| id | cluster | complexity | risk | paired | closes | score |\\n');\n process.stdout.write('|---|---|---|---|---|---|---|\\n');\n for (const item of scored) {\n process.stdout.write(\n `| ${item.id} | ${item.cluster} | ${item.complexity ?? '?'} | ${item.risk} | ${item.paired ? 'Y' : ''} | ${item.closesCluster ? '★' : ''} | ${item.score ?? '?'} |\\n`,\n );\n }\n if (explain) {\n for (const item of scored) {\n process.stdout.write('\\n' + backlogScore.formatScoreExplanation(item) + '\\n');\n }\n }\n}\n"],"mappings":";;;AAkBA,SAAS,YAAY,UAAU;AAC/B,OAAO,UAAU;AACjB,SAAS,eAAe;AACxB,SAAS,oBAAoB;AAE7B,IAAM,6BAA6B;AAAA,EACjC,KAAK,KAAK,QAAQ,YAAY;AAAA,EAC9B,KAAK,KAAK,MAAM,QAAQ,YAAY;AAAA,EACpC,KAAK,KAAK,MAAM,MAAM,QAAQ,YAAY;AAAA,EAC1C,KAAK,KAAK,MAAM,MAAM,MAAM,QAAQ,YAAY;AAClD;AAEO,SAAS,iBAA0B;AACxC,QAAM,MAAM,IAAI,QAAQ,SAAS;AACjC,MAAI,YAAY,0EAA0E;AAE1F,MACG,QAAQ,OAAO,EACf;AAAA,IACC;AAAA,EACF,EACC,OAAO,mBAAmB,+BAA+B,EACzD,OAAO,kBAAkB,mCAAmC,MAAM,EAClE,OAAO,oBAAoB,sCAAsC,KAAK,EACtE,OAAO,qBAAqB,4CAA4C,EACxE,OAAO,OAAO,SAAS;AACtB,UAAM,aAAa,MAAM,mBAAmB,KAAK,MAAM;AACvD,UAAM,WAAW,MAAM,GAAG,SAAS,YAAY,MAAM;AACrD,UAAM,0BAA0B,KAAK,gBAAgB;AACrD,UAAM,SAAS,aAAa,aAAa,UAAU,EAAE,wBAAwB,CAAC;AAE9E,UAAM,MAAM,OAAO,KAAK,UAAU,MAAM,EAAE,YAAY;AACtD,QAAI,QAAQ,QAAQ;AAClB,cAAQ,OAAO,MAAM,KAAK,UAAU,QAAQ,MAAM,CAAC,IAAI,IAAI;AAC3D,UAAI,KAAK,eAAe;AACtB,mBAAW,QAAQ,QAAQ;AACzB,kBAAQ,OAAO,MAAM,OAAO,aAAa,uBAAuB,IAAI,IAAI,IAAI;AAAA,QAC9E;AAAA,MACF;AACA;AAAA,IACF;AACA,QAAI,QAAQ,YAAY;AACtB,mBAAa,QAAQ,QAAQ,KAAK,aAAa,CAAC;AAChD;AAAA,IACF;AACA,UAAM,IAAI,MAAM,qBAAqB,KAAK,MAAM,wBAAwB;AAAA,EAC1E,CAAC;AAEH,SAAO;AACT;AAEA,eAAe,mBAAmB,UAA+C;AAC/E,MAAI,UAAU;AACZ,WAAO,KAAK,QAAQ,QAAQ;AAAA,EAC9B;AACA,aAAW,OAAO,4BAA4B;AAC5C,UAAM,YAAY,KAAK,QAAQ,GAAG;AAClC,QAAI;AACF,YAAM,GAAG,OAAO,SAAS;AACzB,aAAO;AAAA,IACT,QAAQ;AAAA,IAER;AAAA,EACF;AACA,QAAM,IAAI;AAAA,IACR;AAAA,EACF;AACF;AAEA,SAAS,aAAa,QAAmD,SAAwB;AAC/F,UAAQ,OAAO,MAAM,kEAAkE;AACvF,UAAQ,OAAO,MAAM,iCAAiC;AACtD,aAAW,QAAQ,QAAQ;AACzB,YAAQ,OAAO;AAAA,MACb,KAAK,KAAK,EAAE,MAAM,KAAK,OAAO,MAAM,KAAK,cAAc,GAAG,MAAM,KAAK,IAAI,MAAM,KAAK,SAAS,MAAM,EAAE,MAAM,KAAK,gBAAgB,WAAM,EAAE,MAAM,KAAK,SAAS,GAAG;AAAA;AAAA,IACjK;AAAA,EACF;AACA,MAAI,SAAS;AACX,eAAW,QAAQ,QAAQ;AACzB,cAAQ,OAAO,MAAM,OAAO,aAAa,uBAAuB,IAAI,IAAI,IAAI;AAAA,IAC9E;AAAA,EACF;AACF;","names":[]}
@@ -0,0 +1,111 @@
1
+ import "./chunk-DGUM43GV.js";
2
+
3
+ // src/commands/bisect.ts
4
+ import { execFile } from "child_process";
5
+ import { promisify } from "util";
6
+ import { realpathSync } from "fs";
7
+ import path from "path";
8
+ import { Command } from "commander";
9
+ import { loadProject, objectFilePath } from "@sdt-tools/core/project";
10
+ var execFileP = promisify(execFile);
11
+ function parseFqn(raw) {
12
+ const parts = raw.split(".").map((p) => p.replace(/^"(.*)"$/, "$1").trim()).filter(Boolean);
13
+ if (parts.length === 3) return { database: parts[0], schema: parts[1], name: parts[2] };
14
+ if (parts.length === 2) return { schema: parts[0], name: parts[1] };
15
+ return { name: parts[0] ?? raw };
16
+ }
17
+ async function runGit(cwd, args) {
18
+ const { stdout } = await execFileP("git", args, { cwd, maxBuffer: 16 * 1024 * 1024 });
19
+ return stdout;
20
+ }
21
+ function canonical(p) {
22
+ try {
23
+ return realpathSync.native(p);
24
+ } catch {
25
+ return p;
26
+ }
27
+ }
28
+ function bisectCommand() {
29
+ const cmd = new Command("bisect");
30
+ cmd.description("Find the commit where a specific FQN first changed in the project tree.").argument("<fqn>", "Fully-qualified name (database.schema.name).").option("-p, --project <path>", "Path to the .sdtproj file.", ".").option("--object-type <type>", "Object type. Default: TABLE.", "TABLE").option("--good <ref>", "Known-good git ref. Default: project root commit.").option("--bad <ref>", "Known-bad git ref. Default: HEAD.", "HEAD").option("--format <fmt>", "Output: table | json.", "table").action(async (fqnArg, opts) => {
31
+ const fqn = parseFqn(fqnArg);
32
+ const objectType = String(opts.objectType).toUpperCase();
33
+ const projectPath = path.resolve(String(opts.project));
34
+ let projectRoot;
35
+ try {
36
+ const loaded = await loadProject(projectPath);
37
+ projectRoot = loaded.rootDir;
38
+ } catch {
39
+ projectRoot = path.dirname(projectPath);
40
+ }
41
+ projectRoot = canonical(projectRoot);
42
+ let targetPath;
43
+ try {
44
+ targetPath = objectFilePath(projectRoot, objectType, {
45
+ ...fqn.database ? { database: fqn.database } : {},
46
+ ...fqn.schema ? { schema: fqn.schema } : {},
47
+ name: fqn.name
48
+ });
49
+ } catch (err) {
50
+ throw new Error(
51
+ `Could not resolve a file path for ${objectType} ${fqnArg}: ${err instanceof Error ? err.message : String(err)}`
52
+ );
53
+ }
54
+ const repoRoot = canonical(
55
+ (await runGit(projectRoot, ["rev-parse", "--show-toplevel"])).trim()
56
+ );
57
+ const relPath = path.relative(repoRoot, targetPath).split(path.sep).join("/");
58
+ const range = String(
59
+ opts.good ? `${String(opts.good)}..${String(opts.bad)}` : String(opts.bad)
60
+ );
61
+ const logArgs = [
62
+ "log",
63
+ "--follow",
64
+ "--format=%H%x09%ad%x09%an%x09%s",
65
+ "--date=short",
66
+ range,
67
+ "--",
68
+ relPath
69
+ ];
70
+ let raw;
71
+ try {
72
+ raw = await runGit(repoRoot, logArgs);
73
+ } catch (err) {
74
+ throw new Error(
75
+ `git log failed for ${relPath}: ${err instanceof Error ? err.message : String(err)}. Is the project tracked in git? Does the file exist at the path?`
76
+ );
77
+ }
78
+ const entries = raw.split("\n").filter((l) => l.trim()).map((line) => {
79
+ const [sha, date, author, subject] = line.split(" ");
80
+ return { sha: sha ?? "", date: date ?? "", author: author ?? "", subject: subject ?? "" };
81
+ });
82
+ if (opts.format === "json") {
83
+ console.log(JSON.stringify({ fqn: fqnArg, objectType, path: relPath, entries }, null, 2));
84
+ return;
85
+ }
86
+ if (entries.length === 0) {
87
+ console.log(`No commits found for ${objectType} ${fqnArg} (path: ${relPath}).`);
88
+ return;
89
+ }
90
+ console.log(`Commits touching ${objectType} ${fqnArg}`);
91
+ console.log(` path: ${relPath}`);
92
+ console.log(` range: ${range}`);
93
+ console.log("");
94
+ console.log(" SHA DATE AUTHOR SUBJECT");
95
+ console.log(
96
+ " ---------- ---------- ------------------ -----------------------------------"
97
+ );
98
+ for (let i = 0; i < entries.length; i++) {
99
+ const e = entries[i];
100
+ const marker = i === entries.length - 1 ? " \u25C0 first-change" : "";
101
+ console.log(
102
+ ` ${e.sha.slice(0, 10)} ${e.date} ${e.author.padEnd(18).slice(0, 18)} ${e.subject}${marker}`
103
+ );
104
+ }
105
+ });
106
+ return cmd;
107
+ }
108
+ export {
109
+ bisectCommand
110
+ };
111
+ //# sourceMappingURL=bisect-GEVYAVL5.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/bisect.ts"],"sourcesContent":["/**\n * `sdt bisect <fqn>` — find the commit where a specific Snowflake object\n * first changed in a git-tracked `.sdtproj` project tree.\n *\n * Mirrors `Databricks/packages/cli/src/commands/bisect.ts` byte-for-byte\n * in flow; only the project loader + object-type defaults are Snowflake-\n * flavored.\n */\nimport { execFile } from 'node:child_process';\nimport { promisify } from 'node:util';\nimport { realpathSync } from 'node:fs';\nimport path from 'node:path';\nimport { Command } from 'commander';\nimport { loadProject, objectFilePath } from '@sdt-tools/core/project';\nimport type { SnowflakeObjectType } from '@sdt-tools/core/model';\n\nconst execFileP = promisify(execFile);\n\nfunction parseFqn(raw: string): { database?: string; schema?: string; name: string } {\n const parts = raw\n .split('.')\n .map((p) => p.replace(/^\"(.*)\"$/, '$1').trim())\n .filter(Boolean);\n if (parts.length === 3) return { database: parts[0]!, schema: parts[1]!, name: parts[2]! };\n if (parts.length === 2) return { schema: parts[0]!, name: parts[1]! };\n return { name: parts[0] ?? raw };\n}\n\nasync function runGit(cwd: string, args: string[]): Promise<string> {\n const { stdout } = await execFileP('git', args, { cwd, maxBuffer: 16 * 1024 * 1024 });\n return stdout;\n}\n\n/**\n * Canonicalize a path so the two sides of the `path.relative(repoRoot,\n * targetPath)` call below stay in the same form. Without this, a Windows\n * 8.3 short name (`C:\\Users\\RUNNER~1\\...`) on one side and the long name\n * (`...\\runneradmin\\...`) that `git rev-parse --show-toplevel` returns on\n * the other produce an out-of-repo `../../RUNNER~1/...` pathspec that git\n * rejects as \"outside repository\". Also collapses macOS `/var` →\n * `/private/var` symlinks. Falls back to the input if the path is absent.\n */\nfunction canonical(p: string): string {\n try {\n return realpathSync.native(p);\n } catch {\n return p;\n }\n}\n\nexport function bisectCommand(): Command {\n const cmd = new Command('bisect');\n cmd\n .description('Find the commit where a specific FQN first changed in the project tree.')\n .argument('<fqn>', 'Fully-qualified name (database.schema.name).')\n .option('-p, --project <path>', 'Path to the .sdtproj file.', '.')\n .option('--object-type <type>', 'Object type. Default: TABLE.', 'TABLE')\n .option('--good <ref>', 'Known-good git ref. Default: project root commit.')\n .option('--bad <ref>', 'Known-bad git ref. Default: HEAD.', 'HEAD')\n .option('--format <fmt>', 'Output: table | json.', 'table')\n .action(async (fqnArg: string, opts) => {\n const fqn = parseFqn(fqnArg);\n const objectType = String(opts.objectType).toUpperCase() as SnowflakeObjectType;\n const projectPath = path.resolve(String(opts.project));\n let projectRoot: string;\n try {\n const loaded = await loadProject(projectPath);\n projectRoot = loaded.rootDir;\n } catch {\n projectRoot = path.dirname(projectPath);\n }\n projectRoot = canonical(projectRoot);\n let targetPath: string;\n try {\n targetPath = objectFilePath(projectRoot, objectType, {\n ...(fqn.database ? { database: fqn.database } : {}),\n ...(fqn.schema ? { schema: fqn.schema } : {}),\n name: fqn.name,\n });\n } catch (err) {\n throw new Error(\n `Could not resolve a file path for ${objectType} ${fqnArg}: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n const repoRoot = canonical(\n (await runGit(projectRoot, ['rev-parse', '--show-toplevel'])).trim(),\n );\n const relPath = path.relative(repoRoot, targetPath).split(path.sep).join('/');\n const range = String(\n opts.good ? `${String(opts.good)}..${String(opts.bad)}` : String(opts.bad),\n );\n const logArgs = [\n 'log',\n '--follow',\n '--format=%H%x09%ad%x09%an%x09%s',\n '--date=short',\n range,\n '--',\n relPath,\n ];\n let raw: string;\n try {\n raw = await runGit(repoRoot, logArgs);\n } catch (err) {\n throw new Error(\n `git log failed for ${relPath}: ${err instanceof Error ? err.message : String(err)}. ` +\n `Is the project tracked in git? Does the file exist at the path?`,\n );\n }\n const entries = raw\n .split('\\n')\n .filter((l) => l.trim())\n .map((line) => {\n const [sha, date, author, subject] = line.split('\\t');\n return { sha: sha ?? '', date: date ?? '', author: author ?? '', subject: subject ?? '' };\n });\n if (opts.format === 'json') {\n console.log(JSON.stringify({ fqn: fqnArg, objectType, path: relPath, entries }, null, 2));\n return;\n }\n if (entries.length === 0) {\n console.log(`No commits found for ${objectType} ${fqnArg} (path: ${relPath}).`);\n return;\n }\n console.log(`Commits touching ${objectType} ${fqnArg}`);\n console.log(` path: ${relPath}`);\n console.log(` range: ${range}`);\n console.log('');\n console.log(' SHA DATE AUTHOR SUBJECT');\n console.log(\n ' ---------- ---------- ------------------ -----------------------------------',\n );\n for (let i = 0; i < entries.length; i++) {\n const e = entries[i]!;\n const marker = i === entries.length - 1 ? ' ◀ first-change' : '';\n console.log(\n ` ${e.sha.slice(0, 10)} ${e.date} ${e.author.padEnd(18).slice(0, 18)} ${e.subject}${marker}`,\n );\n }\n });\n return cmd;\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,107 @@
1
+ import "./chunk-DGUM43GV.js";
2
+
3
+ // src/commands/bookmarks.ts
4
+ import { promises as fs } from "fs";
5
+ import os from "os";
6
+ import path from "path";
7
+ import { Command } from "commander";
8
+ import { bookmarks } from "@sdt-tools/core";
9
+ var DEFAULT_STORE = path.join(os.homedir(), ".sdt", "bookmarks.json");
10
+ function bookmarksCommand() {
11
+ const cmd = new Command("bookmarks");
12
+ cmd.description("Save fast-access pointers to queries, files, or named sections.");
13
+ cmd.option("--store <path>", "Override the bookmarks JSON path.", DEFAULT_STORE);
14
+ cmd.command("add <name>").description("Add a bookmark.").requiredOption("--type <type>", "Target type: query | file | section.").requiredOption("--target <target>", "Target payload (SQL text, file path, or section URI).").option("--label <label>", "Display label (defaults to name).").action(async (name, opts) => {
15
+ const storePath = resolveStorePath(cmd.opts().store);
16
+ const store = await loadStore(storePath);
17
+ const created = store.add({
18
+ name,
19
+ type: opts.type,
20
+ target: opts.target,
21
+ label: opts.label
22
+ });
23
+ await saveStore(storePath, store);
24
+ process.stdout.write(`Added bookmark '${created.name}' (${created.type}).
25
+ `);
26
+ });
27
+ cmd.command("remove <name>").description("Remove a bookmark by name (case-insensitive).").action(async (name) => {
28
+ const storePath = resolveStorePath(cmd.opts().store);
29
+ const store = await loadStore(storePath);
30
+ const removed = store.remove(name);
31
+ if (!removed) {
32
+ throw new Error(`No bookmark named '${name}'.`);
33
+ }
34
+ await saveStore(storePath, store);
35
+ process.stdout.write(`Removed bookmark '${name}'.
36
+ `);
37
+ });
38
+ cmd.command("list").description("List every bookmark.").option("--format <fmt>", "text | json (default text).", "text").action(async (opts) => {
39
+ const storePath = resolveStorePath(cmd.opts().store);
40
+ const store = await loadStore(storePath);
41
+ const list = store.list();
42
+ const fmt = String(opts.format ?? "text").toLowerCase();
43
+ if (fmt === "json") {
44
+ process.stdout.write(JSON.stringify(list, null, 2) + "\n");
45
+ return;
46
+ }
47
+ if (fmt !== "text") {
48
+ throw new Error(`Unknown --format: ${opts.format}. Use text | json.`);
49
+ }
50
+ if (list.length === 0) {
51
+ process.stdout.write(
52
+ "No bookmarks. Add one with `sdt bookmarks add <name> --type ... --target ...`.\n"
53
+ );
54
+ return;
55
+ }
56
+ for (const b of list) {
57
+ const label = b.label ?? b.name;
58
+ process.stdout.write(
59
+ `${b.name} [${b.type}] \u2192 ${b.target}${label !== b.name ? ` (${label})` : ""}
60
+ `
61
+ );
62
+ }
63
+ });
64
+ cmd.command("get <name>").description("Look up a single bookmark by name.").option("--format <fmt>", "text | json (default json).", "json").action(async (name, opts) => {
65
+ const storePath = resolveStorePath(cmd.opts().store);
66
+ const store = await loadStore(storePath);
67
+ const found = store.findByName(name);
68
+ if (found === void 0) {
69
+ throw new Error(`No bookmark named '${name}'.`);
70
+ }
71
+ const fmt = String(opts.format ?? "json").toLowerCase();
72
+ if (fmt === "json") {
73
+ process.stdout.write(JSON.stringify(found, null, 2) + "\n");
74
+ return;
75
+ }
76
+ if (fmt !== "text") {
77
+ throw new Error(`Unknown --format: ${opts.format}. Use text | json.`);
78
+ }
79
+ process.stdout.write(`${found.name} [${found.type}] \u2192 ${found.target}
80
+ `);
81
+ });
82
+ return cmd;
83
+ }
84
+ function resolveStorePath(override) {
85
+ return path.resolve(override ?? DEFAULT_STORE);
86
+ }
87
+ async function loadStore(storePath) {
88
+ try {
89
+ const raw = await fs.readFile(storePath, "utf8");
90
+ const parsed = JSON.parse(raw);
91
+ return bookmarks.BookmarkStore.deserialize(parsed);
92
+ } catch (err) {
93
+ if (err.code === "ENOENT") {
94
+ return new bookmarks.BookmarkStore();
95
+ }
96
+ throw err;
97
+ }
98
+ }
99
+ async function saveStore(storePath, store) {
100
+ const dir = path.dirname(storePath);
101
+ await fs.mkdir(dir, { recursive: true });
102
+ await fs.writeFile(storePath, JSON.stringify(store.serialize(), null, 2) + "\n", "utf8");
103
+ }
104
+ export {
105
+ bookmarksCommand
106
+ };
107
+ //# sourceMappingURL=bookmarks-57LKS7P6.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/bookmarks.ts"],"sourcesContent":["/**\n * `sdt bookmarks` — operator-facing CRUD over saved bookmarks (AUTH.6).\n *\n * Persists to `~/.sdt/bookmarks.json` (override with `--store <path>`).\n * Composes with the pure `@sdt-tools/core/bookmarks` substrate — this file\n * is the disk-I/O boundary; the substrate stays pure.\n *\n * Subcommands:\n * sdt bookmarks add <name> --type query|file|section --target <s> [--label <s>]\n * sdt bookmarks remove <name>\n * sdt bookmarks list [--format text|json]\n * sdt bookmarks get <name> [--format text|json]\n *\n * Mirrors `Databricks/packages/cli/src/commands/bookmarks.ts`.\n */\nimport { promises as fs } from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { Command } from 'commander';\nimport { bookmarks } from '@sdt-tools/core';\n\nconst DEFAULT_STORE = path.join(os.homedir(), '.sdt', 'bookmarks.json');\n\nexport function bookmarksCommand(): Command {\n const cmd = new Command('bookmarks');\n cmd.description('Save fast-access pointers to queries, files, or named sections.');\n cmd.option('--store <path>', 'Override the bookmarks JSON path.', DEFAULT_STORE);\n\n cmd\n .command('add <name>')\n .description('Add a bookmark.')\n .requiredOption('--type <type>', 'Target type: query | file | section.')\n .requiredOption('--target <target>', 'Target payload (SQL text, file path, or section URI).')\n .option('--label <label>', 'Display label (defaults to name).')\n .action(async (name: string, opts) => {\n const storePath = resolveStorePath(cmd.opts().store);\n const store = await loadStore(storePath);\n const created = store.add({\n name,\n type: opts.type as bookmarks.BookmarkType,\n target: opts.target,\n label: opts.label,\n });\n await saveStore(storePath, store);\n process.stdout.write(`Added bookmark '${created.name}' (${created.type}).\\n`);\n });\n\n cmd\n .command('remove <name>')\n .description('Remove a bookmark by name (case-insensitive).')\n .action(async (name: string) => {\n const storePath = resolveStorePath(cmd.opts().store);\n const store = await loadStore(storePath);\n const removed = store.remove(name);\n if (!removed) {\n throw new Error(`No bookmark named '${name}'.`);\n }\n await saveStore(storePath, store);\n process.stdout.write(`Removed bookmark '${name}'.\\n`);\n });\n\n cmd\n .command('list')\n .description('List every bookmark.')\n .option('--format <fmt>', 'text | json (default text).', 'text')\n .action(async (opts) => {\n const storePath = resolveStorePath(cmd.opts().store);\n const store = await loadStore(storePath);\n const list = store.list();\n const fmt = String(opts.format ?? 'text').toLowerCase();\n if (fmt === 'json') {\n process.stdout.write(JSON.stringify(list, null, 2) + '\\n');\n return;\n }\n if (fmt !== 'text') {\n throw new Error(`Unknown --format: ${opts.format}. Use text | json.`);\n }\n if (list.length === 0) {\n process.stdout.write(\n 'No bookmarks. Add one with `sdt bookmarks add <name> --type ... --target ...`.\\n',\n );\n return;\n }\n for (const b of list) {\n const label = b.label ?? b.name;\n process.stdout.write(\n `${b.name} [${b.type}] → ${b.target}${label !== b.name ? ` (${label})` : ''}\\n`,\n );\n }\n });\n\n cmd\n .command('get <name>')\n .description('Look up a single bookmark by name.')\n .option('--format <fmt>', 'text | json (default json).', 'json')\n .action(async (name: string, opts) => {\n const storePath = resolveStorePath(cmd.opts().store);\n const store = await loadStore(storePath);\n const found = store.findByName(name);\n if (found === undefined) {\n throw new Error(`No bookmark named '${name}'.`);\n }\n const fmt = String(opts.format ?? 'json').toLowerCase();\n if (fmt === 'json') {\n process.stdout.write(JSON.stringify(found, null, 2) + '\\n');\n return;\n }\n if (fmt !== 'text') {\n throw new Error(`Unknown --format: ${opts.format}. Use text | json.`);\n }\n process.stdout.write(`${found.name} [${found.type}] → ${found.target}\\n`);\n });\n\n return cmd;\n}\n\nfunction resolveStorePath(override: string | undefined): string {\n return path.resolve(override ?? DEFAULT_STORE);\n}\n\nasync function loadStore(storePath: string): Promise<bookmarks.BookmarkStore> {\n try {\n const raw = await fs.readFile(storePath, 'utf8');\n const parsed: unknown = JSON.parse(raw);\n return bookmarks.BookmarkStore.deserialize(parsed);\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') {\n return new bookmarks.BookmarkStore();\n }\n throw err;\n }\n}\n\nasync function saveStore(storePath: string, store: bookmarks.BookmarkStore): Promise<void> {\n const dir = path.dirname(storePath);\n await fs.mkdir(dir, { recursive: true });\n await fs.writeFile(storePath, JSON.stringify(store.serialize(), null, 2) + '\\n', 'utf8');\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,88 @@
1
+ import "./chunk-DGUM43GV.js";
2
+
3
+ // src/commands/branch.ts
4
+ import { promises as fs } from "fs";
5
+ import path from "path";
6
+ import { Command } from "commander";
7
+ import { getProfile, SnowflakeConnection } from "@sdt-tools/core/connection";
8
+ function branchCommand() {
9
+ const cmd = new Command("branch").description(
10
+ "PlanetScale-style branching via Snowflake zero-copy clone. create / drop / list."
11
+ );
12
+ cmd.command("create <name>").description("Create a zero-copy clone of <from-database> named <name>.").requiredOption("--from <database>", "Base database to clone.").option(
13
+ "--connection <profile>",
14
+ "Connection profile to execute against. Omit to emit SQL only."
15
+ ).option(
16
+ "-o, --out <path>",
17
+ "Write the generated SQL to a file. Defaults to stdout when --connection is omitted."
18
+ ).action(async (name, opts) => {
19
+ const branchDb = String(name);
20
+ const fromDb = String(opts.from);
21
+ const sql = `-- Branch ${branchDb} = zero-copy clone of ${fromDb}
22
+ CREATE OR REPLACE DATABASE ${branchDb} CLONE ${fromDb};
23
+ `;
24
+ await runOrEmit(sql, opts);
25
+ });
26
+ cmd.command("drop <name>").description("Drop a branch database. Irreversible.").option(
27
+ "--connection <profile>",
28
+ "Connection profile to execute against. Omit to emit SQL only."
29
+ ).option("-o, --out <path>", "Write the generated SQL to a file. Defaults to stdout.").action(async (name, opts) => {
30
+ const sql = `-- Drop branch ${name}
31
+ DROP DATABASE IF EXISTS ${name};
32
+ `;
33
+ await runOrEmit(sql, opts);
34
+ });
35
+ cmd.command("list").description("List branches (databases whose names suggest a branch).").requiredOption("--connection <profile>", "Connection profile to query.").option("--pattern <like>", "SHOW DATABASES LIKE pattern. Defaults to all databases.", "%").action(async (opts) => {
36
+ const profile = await getProfile(String(opts.connection));
37
+ const conn = new SnowflakeConnection(profile);
38
+ try {
39
+ await conn.connect();
40
+ const rows = await conn.query(
41
+ `SHOW DATABASES LIKE '${String(opts.pattern).replace(/'/g, "''")}'`
42
+ );
43
+ const list = Array.isArray(rows) ? rows : [];
44
+ for (const r of list) {
45
+ const rec = r;
46
+ const name = rec.name ?? rec.NAME;
47
+ const origin = rec.origin ?? rec.ORIGIN ?? "";
48
+ if (!name) continue;
49
+ console.log(` ${String(name).padEnd(40)} ${origin ? `origin=${origin}` : ""}`);
50
+ }
51
+ console.log("");
52
+ console.log(`${list.length} database(s) found.`);
53
+ } finally {
54
+ await conn.disconnect();
55
+ }
56
+ });
57
+ return cmd;
58
+ }
59
+ async function runOrEmit(sql, opts) {
60
+ if (opts.connection) {
61
+ const profile = await getProfile(String(opts.connection));
62
+ const conn = new SnowflakeConnection(profile);
63
+ try {
64
+ await conn.connect();
65
+ for (const stmt of sql.split(";").map((s) => s.trim()).filter(Boolean)) {
66
+ if (stmt.startsWith("--")) continue;
67
+ console.error(`> ${stmt.split("\n")[0]}`);
68
+ await conn.query(stmt);
69
+ }
70
+ console.error("Done.");
71
+ } finally {
72
+ await conn.disconnect();
73
+ }
74
+ return;
75
+ }
76
+ if (opts.out) {
77
+ const p = path.resolve(String(opts.out));
78
+ await fs.mkdir(path.dirname(p), { recursive: true });
79
+ await fs.writeFile(p, sql, "utf8");
80
+ console.error(`Wrote ${p}.`);
81
+ return;
82
+ }
83
+ process.stdout.write(sql);
84
+ }
85
+ export {
86
+ branchCommand
87
+ };
88
+ //# sourceMappingURL=branch-W2MGMPSH.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/branch.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';\n\n/**\n * `sdt branch` — PlanetScale-style branching for Snowflake. A branch\n * is a Snowflake **zero-copy clone** of a base database — instant to\n * create, free until you start writing to it, and disposable when\n * you're done. The deploy-request workflow becomes:\n *\n * sdt branch create feature-x --from PROD_DB\n * # … iterate against feature-x using sdt publish --apply …\n * sdt compare … (preview the diff between the branch and PROD)\n * sdt branch drop feature-x\n *\n * No competing Snowflake schema tool has this. Atlas + Liquibase\n * lack the zero-copy substrate; Bytebase tries to fit a generic\n * branching workflow on top of generic SQL.\n *\n * Subcommands:\n * create <name> --from <db> [--connection <profile>]\n * drop <name> [--connection <profile>]\n * list --connection <profile>\n *\n * `--connection` makes the command live; without it the SQL is\n * emitted to stdout for the user to run manually.\n */\nexport function branchCommand(): Command {\n const cmd = new Command('branch').description(\n 'PlanetScale-style branching via Snowflake zero-copy clone. create / drop / list.',\n );\n\n cmd\n .command('create <name>')\n .description('Create a zero-copy clone of <from-database> named <name>.')\n .requiredOption('--from <database>', 'Base database to clone.')\n .option(\n '--connection <profile>',\n 'Connection profile to execute against. Omit to emit SQL only.',\n )\n .option(\n '-o, --out <path>',\n 'Write the generated SQL to a file. Defaults to stdout when --connection is omitted.',\n )\n .action(async (name, opts) => {\n const branchDb = String(name);\n const fromDb = String(opts.from);\n const sql = `-- Branch ${branchDb} = zero-copy clone of ${fromDb}\\nCREATE OR REPLACE DATABASE ${branchDb} CLONE ${fromDb};\\n`;\n await runOrEmit(sql, opts);\n });\n\n cmd\n .command('drop <name>')\n .description('Drop a branch database. Irreversible.')\n .option(\n '--connection <profile>',\n 'Connection profile to execute against. Omit to emit SQL only.',\n )\n .option('-o, --out <path>', 'Write the generated SQL to a file. Defaults to stdout.')\n .action(async (name, opts) => {\n const sql = `-- Drop branch ${name}\\nDROP DATABASE IF EXISTS ${name};\\n`;\n await runOrEmit(sql, opts);\n });\n\n cmd\n .command('list')\n .description('List branches (databases whose names suggest a branch).')\n .requiredOption('--connection <profile>', 'Connection profile to query.')\n .option('--pattern <like>', 'SHOW DATABASES LIKE pattern. Defaults to all databases.', '%')\n .action(async (opts) => {\n const profile = await getProfile(String(opts.connection));\n const conn = new SnowflakeConnection(profile);\n try {\n await conn.connect();\n const rows = await conn.query(\n `SHOW DATABASES LIKE '${String(opts.pattern).replace(/'/g, \"''\")}'`,\n );\n const list = Array.isArray(rows) ? rows : [];\n for (const r of list) {\n const rec = r as Record<string, unknown>;\n const name = rec.name ?? rec.NAME;\n const origin = rec.origin ?? rec.ORIGIN ?? '';\n if (!name) continue;\n console.log(` ${String(name).padEnd(40)} ${origin ? `origin=${origin}` : ''}`);\n }\n console.log('');\n console.log(`${list.length} database(s) found.`);\n } finally {\n await conn.disconnect();\n }\n });\n\n return cmd;\n}\n\nasync function runOrEmit(\n sql: string,\n opts: { connection?: unknown; out?: unknown },\n): Promise<void> {\n if (opts.connection) {\n const profile = await getProfile(String(opts.connection));\n const conn = new SnowflakeConnection(profile);\n try {\n await conn.connect();\n for (const stmt of sql\n .split(';')\n .map((s) => s.trim())\n .filter(Boolean)) {\n if (stmt.startsWith('--')) continue;\n console.error(`> ${stmt.split('\\n')[0]}`);\n await conn.query(stmt);\n }\n console.error('Done.');\n } finally {\n await conn.disconnect();\n }\n return;\n }\n if (opts.out) {\n const p = path.resolve(String(opts.out));\n await fs.mkdir(path.dirname(p), { recursive: true });\n await fs.writeFile(p, sql, 'utf8');\n console.error(`Wrote ${p}.`);\n return;\n }\n process.stdout.write(sql);\n}\n"],"mappings":"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","names":[]}