@sdt-tools/cli 0.2.0 → 0.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (207) 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 +511 -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-GNTZDHXF.js +133 -0
  46. package/dist/connection-GNTZDHXF.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-AQXKKGZH.js +109 -0
  70. package/dist/errorReporting-AQXKKGZH.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/import-script-2OF5BI6A.js +83 -0
  102. package/dist/import-script-2OF5BI6A.js.map +1 -0
  103. package/dist/index.cjs +71 -12
  104. package/dist/index.cjs.map +1 -1
  105. package/dist/index.js +95 -31
  106. package/dist/index.js.map +1 -1
  107. package/dist/init-SWRRJMGI.js +57 -0
  108. package/dist/init-SWRRJMGI.js.map +1 -0
  109. package/dist/install-hooks-6SIAGTAF.js +109 -0
  110. package/dist/install-hooks-6SIAGTAF.js.map +1 -0
  111. package/dist/license-OAF22PLZ.js +46 -0
  112. package/dist/license-OAF22PLZ.js.map +1 -0
  113. package/dist/lineage-EW66XJ6O.js +552 -0
  114. package/dist/lineage-EW66XJ6O.js.map +1 -0
  115. package/dist/lint-FQ2OTYTQ.js +143 -0
  116. package/dist/lint-FQ2OTYTQ.js.map +1 -0
  117. package/dist/mcp-SARDMCDV.js +344 -0
  118. package/dist/mcp-SARDMCDV.js.map +1 -0
  119. package/dist/migrate-from-dbt-JVTXPWKQ.js +156 -0
  120. package/dist/migrate-from-dbt-JVTXPWKQ.js.map +1 -0
  121. package/dist/migrate-platform-NTRTOGNR.js +91 -0
  122. package/dist/migrate-platform-NTRTOGNR.js.map +1 -0
  123. package/dist/optimize-CJYWMAWA.js +105 -0
  124. package/dist/optimize-CJYWMAWA.js.map +1 -0
  125. package/dist/perf-LL2CPCJF.js +205 -0
  126. package/dist/perf-LL2CPCJF.js.map +1 -0
  127. package/dist/pii-FBDRDQ2E.js +136 -0
  128. package/dist/pii-FBDRDQ2E.js.map +1 -0
  129. package/dist/pilot-CCQERKPH.js +29 -0
  130. package/dist/pilot-CCQERKPH.js.map +1 -0
  131. package/dist/pr-comment-S5FF4QRX.js +79 -0
  132. package/dist/pr-comment-S5FF4QRX.js.map +1 -0
  133. package/dist/preview-5U4YVCRM.js +47 -0
  134. package/dist/preview-5U4YVCRM.js.map +1 -0
  135. package/dist/profile-7VC57KD2.js +101 -0
  136. package/dist/profile-7VC57KD2.js.map +1 -0
  137. package/dist/promote-AASEFTIA.js +408 -0
  138. package/dist/promote-AASEFTIA.js.map +1 -0
  139. package/dist/publish-UMVIWH6H.js +721 -0
  140. package/dist/publish-UMVIWH6H.js.map +1 -0
  141. package/dist/purge-QMXZKCMD.js +57 -0
  142. package/dist/purge-QMXZKCMD.js.map +1 -0
  143. package/dist/query-log-6OM4GI7W.js +112 -0
  144. package/dist/query-log-6OM4GI7W.js.map +1 -0
  145. package/dist/refactor-LTZQLJ35.js +5799 -0
  146. package/dist/refactor-LTZQLJ35.js.map +1 -0
  147. package/dist/refresh-4TY2AGOU.js +38 -0
  148. package/dist/refresh-4TY2AGOU.js.map +1 -0
  149. package/dist/replay-OOC25FZN.js +117 -0
  150. package/dist/replay-OOC25FZN.js.map +1 -0
  151. package/dist/revert-ODMUVJW6.js +110 -0
  152. package/dist/revert-ODMUVJW6.js.map +1 -0
  153. package/dist/review-XXPWOBFP.js +158 -0
  154. package/dist/review-XXPWOBFP.js.map +1 -0
  155. package/dist/rollback-suggest-6G2HEKFR.js +79 -0
  156. package/dist/rollback-suggest-6G2HEKFR.js.map +1 -0
  157. package/dist/safer-alternative-QFVNLG3L.js +89 -0
  158. package/dist/safer-alternative-QFVNLG3L.js.map +1 -0
  159. package/dist/safety-7QWRSUEZ.js +168 -0
  160. package/dist/safety-7QWRSUEZ.js.map +1 -0
  161. package/dist/savings-RHIXP6IT.js +95 -0
  162. package/dist/savings-RHIXP6IT.js.map +1 -0
  163. package/dist/scan-secrets-5YCQ4UCU.js +54 -0
  164. package/dist/scan-secrets-5YCQ4UCU.js.map +1 -0
  165. package/dist/schema-CIZXCQD2.js +429 -0
  166. package/dist/schema-CIZXCQD2.js.map +1 -0
  167. package/dist/script-K7CIN2P6.js +153 -0
  168. package/dist/script-K7CIN2P6.js.map +1 -0
  169. package/dist/search-BUZ5NXZZ.js +151 -0
  170. package/dist/search-BUZ5NXZZ.js.map +1 -0
  171. package/dist/seed-76QAK276.js +96 -0
  172. package/dist/seed-76QAK276.js.map +1 -0
  173. package/dist/sketch-PTLKDIK3.js +88 -0
  174. package/dist/sketch-PTLKDIK3.js.map +1 -0
  175. package/dist/snapshot-XLPR2OZ5.js +177 -0
  176. package/dist/snapshot-XLPR2OZ5.js.map +1 -0
  177. package/dist/snippets-EK4DK5CN.js +74 -0
  178. package/dist/snippets-EK4DK5CN.js.map +1 -0
  179. package/dist/standards-7T2UY6DD.js +241 -0
  180. package/dist/standards-7T2UY6DD.js.map +1 -0
  181. package/dist/suggest-VGRYSAR6.js +39 -0
  182. package/dist/suggest-VGRYSAR6.js.map +1 -0
  183. package/dist/suggest-constraints-MY5WKUHA.js +160 -0
  184. package/dist/suggest-constraints-MY5WKUHA.js.map +1 -0
  185. package/dist/suite-TRNGZWQM.js +88 -0
  186. package/dist/suite-TRNGZWQM.js.map +1 -0
  187. package/dist/telemetry-3U2QLA2S.js +75 -0
  188. package/dist/telemetry-3U2QLA2S.js.map +1 -0
  189. package/dist/template-ZERIXVXF.js +403 -0
  190. package/dist/template-ZERIXVXF.js.map +1 -0
  191. package/dist/test-5M2ED3WT.js +169 -0
  192. package/dist/test-5M2ED3WT.js.map +1 -0
  193. package/dist/trial-U732FONV.js +31 -0
  194. package/dist/trial-U732FONV.js.map +1 -0
  195. package/dist/validate-T6D2WCOK.js +106 -0
  196. package/dist/validate-T6D2WCOK.js.map +1 -0
  197. package/dist/verify-KXVASEEG.js +76 -0
  198. package/dist/verify-KXVASEEG.js.map +1 -0
  199. package/dist/watch-I6K4BNMA.js +80 -0
  200. package/dist/watch-I6K4BNMA.js.map +1 -0
  201. package/dist/xcompare-TPFLQO6W.js +87 -0
  202. package/dist/xcompare-TPFLQO6W.js.map +1 -0
  203. package/package.json +2 -2
  204. package/dist/cli.cjs +0 -19040
  205. package/dist/cli.cjs.map +0 -1
  206. package/dist/cli.d.cts +0 -1
  207. package/dist/cli.d.ts +0 -1
@@ -0,0 +1,153 @@
1
+ import {
2
+ addMappingFlags,
3
+ buildMappingFromOptions
4
+ } from "./chunk-JP2EZLR5.js";
5
+ import "./chunk-DGUM43GV.js";
6
+
7
+ // src/commands/script.ts
8
+ import { promises as fs } from "fs";
9
+ import path from "path";
10
+ import { Command } from "commander";
11
+ import {
12
+ CompareEngine,
13
+ PacSource,
14
+ ProjectSource
15
+ } from "@sdt-tools/core/compare";
16
+ import { ScriptGenerator, colorizeMigrationScript } from "@sdt-tools/core/deploy";
17
+ import { mergeDeployOptions, loadProject } from "@sdt-tools/core/project";
18
+ import { readPac } from "@sdt-tools/core/pac";
19
+ import { safety } from "@sdt-tools/core";
20
+ function scriptCommand() {
21
+ const cmd = new Command("script");
22
+ cmd.description(
23
+ "Generate a deploy SQL script for source \u2192 target. Always offline \u2014 does not touch the account."
24
+ ).requiredOption("--source <path>", "Source: .sdtproj or .sdtpac (the desired state).").requiredOption("--target <path>", "Target: .sdtproj or .sdtpac (the current state).").option("-o, --out <path>", "Write the script to <path>. Defaults to stdout.").option("--format <fmt>", "sql | json", "sql").option("--variables <kv>", "Comma-separated KEY=VALUE pairs for $(VAR) substitution.").option(
25
+ "--profile <name>",
26
+ "VARSYNTAX.5 \u2014 look up `deploymentProfiles[<name>].variables` from the --source artifact (`.sdtproj` reads `project.deploymentProfiles`; `.sdtpac` reads `manifest.deploymentProfiles`) and substitute `$(VAR)` references at script-emission time. `--variables` overrides on key collision."
27
+ ).option("--ignore-case", "Compare object FQNs case-insensitively.", false).option("--banner <text>", "Banner line prepended to the script.").option(
28
+ "--color <mode>",
29
+ "Color stdout: auto | always | never. Honors NO_COLOR / SDT_NO_COLOR env vars in auto mode. Files written via -o are never colorized.",
30
+ "auto"
31
+ );
32
+ addMappingFlags(cmd);
33
+ cmd.action(async (opts) => {
34
+ const format = String(opts.format ?? "sql").toLowerCase();
35
+ if (format !== "sql" && format !== "json") {
36
+ throw new Error(`Unknown --format: ${opts.format}. Use sql or json.`);
37
+ }
38
+ const nameMapping = await buildMappingFromOptions(opts);
39
+ const source = await sourceFor(String(opts.source), "source");
40
+ const target = await sourceFor(String(opts.target), "target");
41
+ const engine = new CompareEngine();
42
+ const result = await engine.compare(source, target, {
43
+ ignoreCase: !!opts.ignoreCase,
44
+ ...nameMapping ? { nameMapping } : {}
45
+ });
46
+ const deployment = mergeDeployOptions().deployment;
47
+ const cliVariables = parseVariables(opts.variables);
48
+ let profileVariables;
49
+ let ctxProjectName;
50
+ let ctxProjectVersion;
51
+ const sourcePath = String(opts.source);
52
+ if (opts.profile || sourcePath.endsWith(".sdtproj") || sourcePath.endsWith(".sdtpac")) {
53
+ try {
54
+ if (sourcePath.endsWith(".sdtpac")) {
55
+ const srcPac = await readPac(sourcePath);
56
+ ctxProjectName = srcPac.manifest.projectName;
57
+ ctxProjectVersion = srcPac.manifest.projectVersion;
58
+ if (opts.profile) {
59
+ const block = srcPac.manifest.deploymentProfiles?.[String(opts.profile)];
60
+ if (!block) {
61
+ const available = Object.keys(srcPac.manifest.deploymentProfiles ?? {});
62
+ throw new Error(
63
+ `--profile ${String(opts.profile)}: no deploymentProfile by that name in the --source pac manifest. ` + (available.length === 0 ? "The pac carries no deploymentProfiles (was it built before VARSYNTAX.2, or does the .sdtproj declare any?)." : `Available: ${available.join(", ")}.`)
64
+ );
65
+ }
66
+ if (block.variables) profileVariables = { ...block.variables };
67
+ }
68
+ } else if (sourcePath.endsWith(".sdtproj")) {
69
+ const loaded = await loadProject(sourcePath);
70
+ ctxProjectName = loaded.project.name;
71
+ ctxProjectVersion = loaded.project.version;
72
+ if (opts.profile) {
73
+ const block = loaded.project.deploymentProfiles?.[String(opts.profile)];
74
+ if (!block) {
75
+ const available = Object.keys(loaded.project.deploymentProfiles ?? {});
76
+ throw new Error(
77
+ `--profile ${String(opts.profile)}: no deploymentProfile by that name in the --source .sdtproj. ` + (available.length === 0 ? "The project declares no deploymentProfiles." : `Available: ${available.join(", ")}.`)
78
+ );
79
+ }
80
+ if (block.variables) profileVariables = { ...block.variables };
81
+ }
82
+ }
83
+ } catch (e) {
84
+ if (opts.profile) throw e;
85
+ }
86
+ }
87
+ const variables = profileVariables || cliVariables ? { ...profileVariables ?? {}, ...cliVariables ?? {} } : void 0;
88
+ const generator = new ScriptGenerator();
89
+ const script = generator.generate(result, {
90
+ deployment,
91
+ ...variables ? { variables } : {},
92
+ ...opts.banner ? { banner: String(opts.banner) } : {},
93
+ context: {
94
+ ...opts.profile ? { profile: String(opts.profile) } : {},
95
+ ...ctxProjectName ? { projectName: ctxProjectName } : {},
96
+ ...ctxProjectVersion ? { projectVersion: ctxProjectVersion } : {}
97
+ }
98
+ });
99
+ const assessment = safety.assess(result);
100
+ let payload;
101
+ if (format === "json") {
102
+ payload = JSON.stringify(
103
+ {
104
+ source: result.source,
105
+ target: result.target,
106
+ summary: { ...result.summary, ...script.summary },
107
+ safety: assessment,
108
+ statements: script.statements
109
+ },
110
+ null,
111
+ 2
112
+ );
113
+ } else {
114
+ const header = [
115
+ `-- ${assessment.blocked ? "BLOCKED: " + assessment.blockReason : "Generated by sdt script"}`,
116
+ `-- unrecoverable=${assessment.unrecoverable.length} destructive=${assessment.destructive.length} expensive=${assessment.expensive.length} warnings=${assessment.warnings.length}`,
117
+ ""
118
+ ].join("\n");
119
+ payload = header + script.sql;
120
+ }
121
+ if (opts.out) {
122
+ const outPath = path.resolve(String(opts.out));
123
+ await fs.mkdir(path.dirname(outPath), { recursive: true });
124
+ await fs.writeFile(outPath, payload + (payload.endsWith("\n") ? "" : "\n"), "utf8");
125
+ console.error(`Wrote ${outPath} (${payload.length} bytes).`);
126
+ } else {
127
+ const colorMode = opts.color ?? "auto";
128
+ const rendered = format === "sql" ? colorizeMigrationScript(payload, { mode: colorMode }) : payload;
129
+ process.stdout.write(rendered);
130
+ if (!rendered.endsWith("\n")) process.stdout.write("\n");
131
+ }
132
+ if (assessment.blocked) {
133
+ process.exitCode = 2;
134
+ }
135
+ });
136
+ return cmd;
137
+ }
138
+ async function sourceFor(filePath, label) {
139
+ return filePath.endsWith(".sdtpac") ? new PacSource(filePath, label) : new ProjectSource(filePath, label);
140
+ }
141
+ function parseVariables(raw) {
142
+ if (!raw) return void 0;
143
+ const out = {};
144
+ for (const pair of String(raw).split(",")) {
145
+ const [k, v] = pair.split("=");
146
+ if (k && v !== void 0) out[k.trim()] = v.trim();
147
+ }
148
+ return out;
149
+ }
150
+ export {
151
+ scriptCommand
152
+ };
153
+ //# sourceMappingURL=script-K7CIN2P6.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/script.ts"],"sourcesContent":["import { promises as fs } from 'node:fs';\nimport path from 'node:path';\nimport { Command } from 'commander';\nimport {\n CompareEngine,\n PacSource,\n ProjectSource,\n type CompareSource,\n} from '@sdt-tools/core/compare';\nimport { ScriptGenerator, colorizeMigrationScript } from '@sdt-tools/core/deploy';\nimport { mergeDeployOptions, loadProject } from '@sdt-tools/core/project';\nimport { readPac } from '@sdt-tools/core/pac';\nimport { safety } from '@sdt-tools/core';\nimport type { DeploymentProfile } from '@sdt-tools/core/project';\nimport { addMappingFlags, buildMappingFromOptions } from '../util/mapping.js';\n\n/**\n * `sdt script` — generate a deploy SQL script for source → target. Mirrors\n * SqlPackage `/Action:Script` / SSDT's \"Generate Script\" affordance. Always\n * offline — does not touch a live Snowflake account.\n *\n * Output formats:\n * - sql (default): human-readable migration script with banner comments\n * - json: raw CompareResult + safety assessment + per-statement\n * metadata, suitable for CI gates or UI rendering\n *\n * Mirrors `ddt script`. To compare against a live account, use\n * `sdt compare` or `sdt publish`.\n */\nexport function scriptCommand(): Command {\n const cmd = new Command('script');\n cmd\n .description(\n 'Generate a deploy SQL script for source → target. Always offline — does not touch the account.',\n )\n .requiredOption('--source <path>', 'Source: .sdtproj or .sdtpac (the desired state).')\n .requiredOption('--target <path>', 'Target: .sdtproj or .sdtpac (the current state).')\n .option('-o, --out <path>', 'Write the script to <path>. Defaults to stdout.')\n .option('--format <fmt>', 'sql | json', 'sql')\n .option('--variables <kv>', 'Comma-separated KEY=VALUE pairs for $(VAR) substitution.')\n .option(\n '--profile <name>',\n 'VARSYNTAX.5 — look up `deploymentProfiles[<name>].variables` from the --source artifact (`.sdtproj` reads `project.deploymentProfiles`; `.sdtpac` reads `manifest.deploymentProfiles`) and substitute `$(VAR)` references at script-emission time. `--variables` overrides on key collision.',\n )\n .option('--ignore-case', 'Compare object FQNs case-insensitively.', false)\n .option('--banner <text>', 'Banner line prepended to the script.')\n .option(\n '--color <mode>',\n 'Color stdout: auto | always | never. Honors NO_COLOR / SDT_NO_COLOR env vars in auto mode. Files written via -o are never colorized.',\n 'auto',\n );\n addMappingFlags(cmd);\n cmd.action(async (opts) => {\n const format = String(opts.format ?? 'sql').toLowerCase();\n if (format !== 'sql' && format !== 'json') {\n throw new Error(`Unknown --format: ${opts.format}. Use sql or json.`);\n }\n const nameMapping = await buildMappingFromOptions(opts);\n const source = await sourceFor(String(opts.source), 'source');\n const target = await sourceFor(String(opts.target), 'target');\n const engine = new CompareEngine();\n const result = await engine.compare(source, target, {\n ignoreCase: !!opts.ignoreCase,\n ...(nameMapping ? { nameMapping } : {}),\n });\n\n const deployment = mergeDeployOptions().deployment;\n const cliVariables = parseVariables(opts.variables);\n\n // VARSYNTAX.5 — resolve --profile <name> from the --source artifact's\n // deploymentProfiles bag (if present), merge with --variables CLI\n // override (CLI wins on key collision). Also gather the project context\n // (name / version) so VARSYNTAX.3 built-ins (`$(SDT_PROFILE)`,\n // `$(SDT_PROJECT_NAME)`, `$(SDT_PROJECT_VERSION)`, …) auto-populate.\n let profileVariables: Record<string, string> | undefined;\n let ctxProjectName: string | undefined;\n let ctxProjectVersion: string | undefined;\n const sourcePath = String(opts.source);\n if (opts.profile || sourcePath.endsWith('.sdtproj') || sourcePath.endsWith('.sdtpac')) {\n try {\n if (sourcePath.endsWith('.sdtpac')) {\n const srcPac = await readPac(sourcePath);\n ctxProjectName = srcPac.manifest.projectName;\n ctxProjectVersion = srcPac.manifest.projectVersion;\n if (opts.profile) {\n const block: DeploymentProfile | undefined =\n srcPac.manifest.deploymentProfiles?.[String(opts.profile)];\n if (!block) {\n const available = Object.keys(srcPac.manifest.deploymentProfiles ?? {});\n throw new Error(\n `--profile ${String(opts.profile)}: no deploymentProfile by that name in the --source pac manifest. ` +\n (available.length === 0\n ? 'The pac carries no deploymentProfiles (was it built before VARSYNTAX.2, or does the .sdtproj declare any?).'\n : `Available: ${available.join(', ')}.`),\n );\n }\n if (block.variables) profileVariables = { ...block.variables };\n }\n } else if (sourcePath.endsWith('.sdtproj')) {\n const loaded = await loadProject(sourcePath);\n ctxProjectName = loaded.project.name;\n ctxProjectVersion = loaded.project.version;\n if (opts.profile) {\n const block: DeploymentProfile | undefined =\n loaded.project.deploymentProfiles?.[String(opts.profile)];\n if (!block) {\n const available = Object.keys(loaded.project.deploymentProfiles ?? {});\n throw new Error(\n `--profile ${String(opts.profile)}: no deploymentProfile by that name in the --source .sdtproj. ` +\n (available.length === 0\n ? 'The project declares no deploymentProfiles.'\n : `Available: ${available.join(', ')}.`),\n );\n }\n if (block.variables) profileVariables = { ...block.variables };\n }\n }\n } catch (e) {\n // If the user passed --profile, hard-fail. Otherwise best-effort:\n // built-ins stay defaulted on a partial / unreadable source.\n if (opts.profile) throw e;\n }\n }\n const variables =\n profileVariables || cliVariables\n ? { ...(profileVariables ?? {}), ...(cliVariables ?? {}) }\n : undefined;\n\n const generator = new ScriptGenerator();\n const script = generator.generate(result, {\n deployment,\n ...(variables ? { variables } : {}),\n ...(opts.banner ? { banner: String(opts.banner) } : {}),\n context: {\n ...(opts.profile ? { profile: String(opts.profile) } : {}),\n ...(ctxProjectName ? { projectName: ctxProjectName } : {}),\n ...(ctxProjectVersion ? { projectVersion: ctxProjectVersion } : {}),\n },\n });\n const assessment = safety.assess(result);\n\n let payload: string;\n if (format === 'json') {\n payload = JSON.stringify(\n {\n source: result.source,\n target: result.target,\n summary: { ...result.summary, ...script.summary },\n safety: assessment,\n statements: script.statements,\n },\n null,\n 2,\n );\n } else {\n const header = [\n `-- ${assessment.blocked ? 'BLOCKED: ' + assessment.blockReason : 'Generated by sdt script'}`,\n `-- unrecoverable=${assessment.unrecoverable.length} destructive=${assessment.destructive.length} expensive=${assessment.expensive.length} warnings=${assessment.warnings.length}`,\n '',\n ].join('\\n');\n payload = header + script.sql;\n }\n\n if (opts.out) {\n const outPath = path.resolve(String(opts.out));\n await fs.mkdir(path.dirname(outPath), { recursive: true });\n await fs.writeFile(outPath, payload + (payload.endsWith('\\n') ? '' : '\\n'), 'utf8');\n console.error(`Wrote ${outPath} (${payload.length} bytes).`);\n } else {\n // Colorize stdout for SQL only — JSON stays raw so downstream tools can\n // parse it. ANSI escapes are stripped when stdout is not a TTY (auto mode).\n const colorMode = (opts.color as 'auto' | 'always' | 'never' | undefined) ?? 'auto';\n const rendered =\n format === 'sql' ? colorizeMigrationScript(payload, { mode: colorMode }) : payload;\n process.stdout.write(rendered);\n if (!rendered.endsWith('\\n')) process.stdout.write('\\n');\n }\n if (assessment.blocked) {\n process.exitCode = 2;\n }\n });\n return cmd;\n}\n\nasync function sourceFor(filePath: string, label: string): Promise<CompareSource> {\n return filePath.endsWith('.sdtpac')\n ? new PacSource(filePath, label)\n : new ProjectSource(filePath, label);\n}\n\nfunction parseVariables(raw: unknown): Record<string, string> | undefined {\n if (!raw) return undefined;\n const out: Record<string, string> = {};\n for (const pair of String(raw).split(',')) {\n const [k, v] = pair.split('=');\n if (k && v !== undefined) out[k.trim()] = v.trim();\n }\n return out;\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,151 @@
1
+ import "./chunk-DGUM43GV.js";
2
+
3
+ // src/commands/search.ts
4
+ import { Command } from "commander";
5
+ import { promises as fs } from "fs";
6
+ import os from "os";
7
+ import path from "path";
8
+ import { catalog } from "@sdt-tools/core";
9
+ function buildDriftReport(results) {
10
+ const allFqns = /* @__PURE__ */ new Set();
11
+ const byFqn = /* @__PURE__ */ new Map();
12
+ for (const r of results) {
13
+ if (r.error) continue;
14
+ for (const m of r.matches) {
15
+ allFqns.add(m.fqn);
16
+ if (!byFqn.has(m.fqn)) byFqn.set(m.fqn, /* @__PURE__ */ new Set());
17
+ byFqn.get(m.fqn).add(r.profile);
18
+ }
19
+ }
20
+ const successProfiles = results.filter((r) => !r.error).map((r) => r.profile);
21
+ const driftLines = [];
22
+ for (const fqn of allFqns) {
23
+ const present = byFqn.get(fqn);
24
+ if (present.size < successProfiles.length) {
25
+ const absent = successProfiles.filter((p) => !present.has(p));
26
+ driftLines.push(
27
+ ` DRIFT ${fqn} [present: ${[...present].join(", ")} absent: ${absent.join(", ")}]`
28
+ );
29
+ }
30
+ }
31
+ return driftLines.join("\n");
32
+ }
33
+ function renderText(results, showDrift) {
34
+ const lines = [];
35
+ for (const r of results) {
36
+ if (r.error) {
37
+ lines.push(`[${r.profile}] ERROR: ${r.error}`);
38
+ continue;
39
+ }
40
+ if (r.matches.length === 0) {
41
+ lines.push(`[${r.profile}] no matches`);
42
+ } else {
43
+ for (const m of r.matches) {
44
+ lines.push(`[${r.profile}] ${m.fqn} (${m.objectType})`);
45
+ }
46
+ }
47
+ }
48
+ if (showDrift) {
49
+ const drift = buildDriftReport(results);
50
+ if (drift) {
51
+ lines.push("");
52
+ lines.push("--- Drift detected ---");
53
+ lines.push(drift);
54
+ }
55
+ }
56
+ return lines.join("\n");
57
+ }
58
+ async function loadProfileNames(profilesOpt, profilesDir) {
59
+ if (profilesOpt.toLowerCase() !== "all") {
60
+ return profilesOpt.split(",").map((p) => p.trim()).filter(Boolean);
61
+ }
62
+ try {
63
+ const entries = await fs.readdir(profilesDir);
64
+ return entries.filter((e) => e.endsWith(".json") || e.endsWith(".toml")).map((e) => path.basename(e, path.extname(e)));
65
+ } catch {
66
+ return [];
67
+ }
68
+ }
69
+ async function defaultSearchFn(profile, pattern, opts) {
70
+ const root = opts.root ?? process.cwd();
71
+ const cache = new catalog.CatalogCache({ root, connection: profile });
72
+ const snapshot = await cache.get();
73
+ if (snapshot.databases.length === 0) {
74
+ throw new Error(
75
+ `No catalog cache for profile "${profile}" at ${cache.path}. Run \`sdt catalog refresh --connection ${profile} --root ${root}\` or \`sdt extract --connection ${profile} --output ${root} --write-catalog-cache\` first.`
76
+ );
77
+ }
78
+ const needle = pattern.toLowerCase();
79
+ const typeFilter = opts.objectType ? opts.objectType.toUpperCase() : void 0;
80
+ const matches = [];
81
+ for (const db of snapshot.databases) {
82
+ for (const schema of db.schemas) {
83
+ for (const obj of schema.objects) {
84
+ if (typeFilter && obj.objectType !== typeFilter) continue;
85
+ const name = obj.name.toLowerCase();
86
+ const hit = opts.exact ? name === needle : name.includes(needle);
87
+ if (hit) {
88
+ matches.push({
89
+ profile,
90
+ fqn: `${obj.database}.${obj.schema}.${obj.name}`,
91
+ objectType: obj.objectType
92
+ });
93
+ }
94
+ }
95
+ }
96
+ }
97
+ return matches;
98
+ }
99
+ function searchCommand(searchFn = defaultSearchFn) {
100
+ const cmd = new Command("search");
101
+ cmd.description("Find objects matching a name pattern across one or more connection profiles.").argument("<pattern>", "Name pattern to search for (case-insensitive substring).").requiredOption(
102
+ "--profiles <list|all>",
103
+ 'Comma-separated profile names, or "all" to scan every configured profile.'
104
+ ).option("--exact", "Exact name match instead of substring.", false).option("--type <objectType>", "Filter results to a specific object type (e.g. TABLE, VIEW).").option("--format <fmt>", "text | json (default text).", "text").option(
105
+ "--profiles-dir <dir>",
106
+ "Directory to scan when --profiles all is used.",
107
+ path.join(os.homedir(), ".sdt", "connections")
108
+ ).option(
109
+ "--root <dir>",
110
+ "Project / cache root containing the .sdt/cache directory. Default: cwd.",
111
+ process.cwd()
112
+ ).option("--no-drift", "Suppress the drift section in text output.").action(async (pattern, opts) => {
113
+ const profiles = await loadProfileNames(opts.profiles, opts.profilesDir);
114
+ if (profiles.length === 0) {
115
+ throw new Error(
116
+ opts.profiles.toLowerCase() === "all" ? `No profiles found in ${opts.profilesDir}. Run \`sdt connection add\` first.` : "--profiles must list at least one profile."
117
+ );
118
+ }
119
+ const results = await Promise.all(
120
+ profiles.map(async (p) => {
121
+ try {
122
+ const matches = await searchFn(p, pattern, {
123
+ exact: opts.exact,
124
+ objectType: opts.type,
125
+ root: opts.root
126
+ });
127
+ return { profile: p, matches };
128
+ } catch (err) {
129
+ return {
130
+ profile: p,
131
+ matches: [],
132
+ error: err instanceof Error ? err.message : String(err)
133
+ };
134
+ }
135
+ })
136
+ );
137
+ const fmt = String(opts.format ?? "text").toLowerCase();
138
+ if (fmt === "json") {
139
+ process.stdout.write(JSON.stringify(results, null, 2) + "\n");
140
+ return;
141
+ }
142
+ if (fmt !== "text") throw new Error(`Unknown --format: ${opts.format}. Use text | json.`);
143
+ const text = renderText(results, opts.drift !== false);
144
+ process.stdout.write(text ? text + "\n" : "No results.\n");
145
+ });
146
+ return cmd;
147
+ }
148
+ export {
149
+ searchCommand
150
+ };
151
+ //# sourceMappingURL=search-BUZ5NXZZ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/search.ts"],"sourcesContent":["/**\n * `sdt search <pattern> --profiles <list|all>` — AUTH.4.\n *\n * Finds objects whose name matches a pattern across one or more connection\n * profiles. Surfaces presence/absence drift: if an object exists in some\n * profiles but not others, those profiles are flagged.\n *\n * The search is case-insensitive substring match by default; --exact\n * switches to exact name match; --type filters by objectType.\n *\n * Mirrors `Databricks/packages/cli/src/commands/search.ts`.\n */\nimport { Command } from 'commander';\nimport { promises as fs } from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { catalog } from '@sdt-tools/core';\n\nexport interface SearchMatch {\n profile: string;\n fqn: string;\n objectType: string;\n}\n\nexport interface SearchResult {\n profile: string;\n matches: SearchMatch[];\n error?: string;\n}\n\nexport interface SearchFnOptions {\n exact: boolean;\n objectType?: string;\n /** Project / cache root. Defaults to `process.cwd()`. */\n root?: string;\n}\n\nexport type SearchFn = (\n profile: string,\n pattern: string,\n opts: SearchFnOptions,\n) => Promise<SearchMatch[]>;\n\nfunction buildDriftReport(results: SearchResult[]): string {\n const allFqns = new Set<string>();\n const byFqn = new Map<string, Set<string>>();\n for (const r of results) {\n if (r.error) continue;\n for (const m of r.matches) {\n allFqns.add(m.fqn);\n if (!byFqn.has(m.fqn)) byFqn.set(m.fqn, new Set());\n byFqn.get(m.fqn)!.add(r.profile);\n }\n }\n const successProfiles = results.filter((r) => !r.error).map((r) => r.profile);\n const driftLines: string[] = [];\n for (const fqn of allFqns) {\n const present = byFqn.get(fqn)!;\n if (present.size < successProfiles.length) {\n const absent = successProfiles.filter((p) => !present.has(p));\n driftLines.push(\n ` DRIFT ${fqn} [present: ${[...present].join(', ')} absent: ${absent.join(', ')}]`,\n );\n }\n }\n return driftLines.join('\\n');\n}\n\nfunction renderText(results: SearchResult[], showDrift: boolean): string {\n const lines: string[] = [];\n for (const r of results) {\n if (r.error) {\n lines.push(`[${r.profile}] ERROR: ${r.error}`);\n continue;\n }\n if (r.matches.length === 0) {\n lines.push(`[${r.profile}] no matches`);\n } else {\n for (const m of r.matches) {\n lines.push(`[${r.profile}] ${m.fqn} (${m.objectType})`);\n }\n }\n }\n if (showDrift) {\n const drift = buildDriftReport(results);\n if (drift) {\n lines.push('');\n lines.push('--- Drift detected ---');\n lines.push(drift);\n }\n }\n return lines.join('\\n');\n}\n\nasync function loadProfileNames(profilesOpt: string, profilesDir: string): Promise<string[]> {\n if (profilesOpt.toLowerCase() !== 'all') {\n return profilesOpt\n .split(',')\n .map((p) => p.trim())\n .filter(Boolean);\n }\n // Scan profiles directory for known profile files\n try {\n const entries = await fs.readdir(profilesDir);\n return entries\n .filter((e) => e.endsWith('.json') || e.endsWith('.toml'))\n .map((e) => path.basename(e, path.extname(e)));\n } catch {\n return [];\n }\n}\n\n/**\n * Wire to the on-disk catalog cache (`<root>/.sdt/cache/<profile>/catalog.msgpack`\n * written by `sdt catalog refresh` / `sdt extract --write-catalog-cache`).\n *\n * 1. Open `catalog.CatalogCache({root, connection: profile})`.\n * 2. Read the snapshot — `get()` returns an empty snapshot when the\n * file is absent / corrupt / version-mismatched.\n * 3. If the snapshot is empty, surface an actionable error naming\n * the expected file path and the two commands that populate it.\n * 4. Walk every (database, schema, object), filter by `--type`, then\n * match the name (case-insensitive substring by default, exact\n * when `--exact` is set), and return `SearchMatch[]` with the\n * fully-qualified `database.schema.name`.\n *\n * Injectable for tests via the `searchFn` parameter on `searchCommand`.\n */\nasync function defaultSearchFn(\n profile: string,\n pattern: string,\n opts: SearchFnOptions,\n): Promise<SearchMatch[]> {\n const root = opts.root ?? process.cwd();\n const cache = new catalog.CatalogCache({ root, connection: profile });\n const snapshot = await cache.get();\n if (snapshot.databases.length === 0) {\n throw new Error(\n `No catalog cache for profile \"${profile}\" at ${cache.path}. ` +\n `Run \\`sdt catalog refresh --connection ${profile} --root ${root}\\` ` +\n `or \\`sdt extract --connection ${profile} --output ${root} --write-catalog-cache\\` first.`,\n );\n }\n const needle = pattern.toLowerCase();\n const typeFilter = opts.objectType ? opts.objectType.toUpperCase() : undefined;\n const matches: SearchMatch[] = [];\n for (const db of snapshot.databases) {\n for (const schema of db.schemas) {\n for (const obj of schema.objects) {\n if (typeFilter && obj.objectType !== typeFilter) continue;\n const name = obj.name.toLowerCase();\n const hit = opts.exact ? name === needle : name.includes(needle);\n if (hit) {\n matches.push({\n profile,\n fqn: `${obj.database}.${obj.schema}.${obj.name}`,\n objectType: obj.objectType,\n });\n }\n }\n }\n }\n return matches;\n}\n\nexport function searchCommand(searchFn: SearchFn = defaultSearchFn): Command {\n const cmd = new Command('search');\n cmd\n .description('Find objects matching a name pattern across one or more connection profiles.')\n .argument('<pattern>', 'Name pattern to search for (case-insensitive substring).')\n .requiredOption(\n '--profiles <list|all>',\n 'Comma-separated profile names, or \"all\" to scan every configured profile.',\n )\n .option('--exact', 'Exact name match instead of substring.', false)\n .option('--type <objectType>', 'Filter results to a specific object type (e.g. TABLE, VIEW).')\n .option('--format <fmt>', 'text | json (default text).', 'text')\n .option(\n '--profiles-dir <dir>',\n 'Directory to scan when --profiles all is used.',\n path.join(os.homedir(), '.sdt', 'connections'),\n )\n .option(\n '--root <dir>',\n 'Project / cache root containing the .sdt/cache directory. Default: cwd.',\n process.cwd(),\n )\n .option('--no-drift', 'Suppress the drift section in text output.')\n .action(async (pattern: string, opts) => {\n const profiles = await loadProfileNames(opts.profiles as string, opts.profilesDir as string);\n if (profiles.length === 0) {\n throw new Error(\n opts.profiles.toLowerCase() === 'all'\n ? `No profiles found in ${opts.profilesDir}. Run \\`sdt connection add\\` first.`\n : '--profiles must list at least one profile.',\n );\n }\n\n const results: SearchResult[] = await Promise.all(\n profiles.map(async (p) => {\n try {\n const matches = await searchFn(p, pattern, {\n exact: opts.exact as boolean,\n objectType: opts.type as string | undefined,\n root: opts.root as string,\n });\n return { profile: p, matches };\n } catch (err) {\n return {\n profile: p,\n matches: [],\n error: err instanceof Error ? err.message : String(err),\n };\n }\n }),\n );\n\n const fmt = String(opts.format ?? 'text').toLowerCase();\n if (fmt === 'json') {\n process.stdout.write(JSON.stringify(results, null, 2) + '\\n');\n return;\n }\n if (fmt !== 'text') throw new Error(`Unknown --format: ${opts.format}. Use text | json.`);\n const text = renderText(results, opts.drift !== false);\n process.stdout.write(text ? text + '\\n' : 'No results.\\n');\n });\n return cmd;\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,96 @@
1
+ import "./chunk-DGUM43GV.js";
2
+
3
+ // src/commands/seed.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
+ import { SnowflakeExecutor } from "@sdt-tools/core/deploy";
9
+ import { discoverSeeds, loadProject, renderSeedMerge } from "@sdt-tools/core/project";
10
+ function seedCommand() {
11
+ const cmd = new Command("seed").description(
12
+ "Reference / dimension data seeds. Declare static rows next to DDL; engine generates MERGEs."
13
+ );
14
+ cmd.command("list").description("Enumerate seed files under <project>/seeds/.").requiredOption("-p, --project <path>", "Path to the .sdtproj file.").action(async (opts) => {
15
+ const loaded = await loadProject(String(opts.project));
16
+ const seeds = await discoverSeeds(loaded.rootDir);
17
+ if (seeds.length === 0) {
18
+ console.log(
19
+ "No seeds found. Add files under <project>/seeds/<database>/<schema>/<table>.{json,csv}."
20
+ );
21
+ return;
22
+ }
23
+ for (const s of seeds) {
24
+ console.log(
25
+ ` ${s.database}.${s.schema}.${s.table.padEnd(28)} ${String(s.rows.length).padStart(5)} rows keys=[${s.keys.join(", ")}]`
26
+ );
27
+ }
28
+ console.log("");
29
+ console.log(`${seeds.length} seed(s).`);
30
+ });
31
+ cmd.command("render").description("Emit MERGE statements for every seed file. Offline; no account contact.").requiredOption("-p, --project <path>", "Path to the .sdtproj file.").option("-o, --out <path>", "Write to file. Defaults to stdout.").option(
32
+ "--delete-not-in-source",
33
+ "Prepend a DELETE so target-only rows are removed (Snowflake equivalent of WHEN NOT MATCHED BY SOURCE). Default off (safer).",
34
+ false
35
+ ).action(async (opts) => {
36
+ const loaded = await loadProject(String(opts.project));
37
+ const seeds = await discoverSeeds(loaded.rootDir);
38
+ const renderOpts = { deleteNotInSource: !!opts.deleteNotInSource };
39
+ const sql = seeds.map((s) => renderSeedMerge(s, renderOpts)).join("\n\n") + "\n";
40
+ if (opts.out) {
41
+ const outPath = path.resolve(String(opts.out));
42
+ await fs.mkdir(path.dirname(outPath), { recursive: true });
43
+ await fs.writeFile(outPath, sql, "utf8");
44
+ console.error(`Wrote ${outPath} (${sql.length} bytes, ${seeds.length} seed(s)).`);
45
+ } else {
46
+ process.stdout.write(sql);
47
+ }
48
+ });
49
+ cmd.command("apply").description("Execute every seed MERGE against a connection profile.").requiredOption("-p, --project <path>", "Path to the .sdtproj file.").requiredOption("--connection <name>", "Connection profile to apply against.").requiredOption("--yes", "Explicit confirmation. Required because apply writes data.").option("--dry-run", "Print the MERGEs without executing.", false).option(
50
+ "--delete-not-in-source",
51
+ "Prepend a DELETE so target-only rows are removed (Snowflake equivalent of WHEN NOT MATCHED BY SOURCE). Default off (safer).",
52
+ false
53
+ ).action(async (opts) => {
54
+ const loaded = await loadProject(String(opts.project));
55
+ const seeds = await discoverSeeds(loaded.rootDir);
56
+ if (seeds.length === 0) {
57
+ console.log("No seeds found. Nothing to apply.");
58
+ return;
59
+ }
60
+ const renderOpts = { deleteNotInSource: !!opts.deleteNotInSource };
61
+ if (opts.dryRun) {
62
+ for (const s of seeds) console.log(renderSeedMerge(s, renderOpts) + "\n");
63
+ return;
64
+ }
65
+ const profile = await getProfile(String(opts.connection));
66
+ const conn = new SnowflakeConnection(profile);
67
+ let succeeded = 0;
68
+ let failed = 0;
69
+ try {
70
+ await conn.connect();
71
+ const exec = new SnowflakeExecutor(conn);
72
+ for (const s of seeds) {
73
+ process.stdout.write(`\u25B6 ${s.database}.${s.schema}.${s.table} (${s.rows.length} rows) \u2026`);
74
+ const t0 = Date.now();
75
+ try {
76
+ await exec.execute(renderSeedMerge(s, renderOpts));
77
+ console.log(` \u2713 (${Date.now() - t0}ms)`);
78
+ succeeded++;
79
+ } catch (err) {
80
+ console.log(` \u2717 ${err instanceof Error ? err.message : String(err)}`);
81
+ failed++;
82
+ }
83
+ }
84
+ } finally {
85
+ await conn.disconnect();
86
+ }
87
+ console.log("");
88
+ console.log(`Summary: ${succeeded} succeeded, ${failed} failed.`);
89
+ if (failed > 0) process.exitCode = 1;
90
+ });
91
+ return cmd;
92
+ }
93
+ export {
94
+ seedCommand
95
+ };
96
+ //# sourceMappingURL=seed-76QAK276.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/seed.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 { SnowflakeExecutor } from '@sdt-tools/core/deploy';\nimport { discoverSeeds, loadProject, renderSeedMerge } from '@sdt-tools/core/project';\n\n/**\n * `sdt seed` — declare static reference rows next to DDL; engine\n * generates MERGE statements to keep them in sync.\n *\n * Mirrors `Databricks/packages/cli/src/commands/seed.ts`. Subcommands:\n * list enumerate seed files + row counts\n * render -p <project> emit MERGEs to stdout (offline)\n * apply -p <project> -c <conn> execute the MERGEs against an account\n */\nexport function seedCommand(): Command {\n const cmd = new Command('seed').description(\n 'Reference / dimension data seeds. Declare static rows next to DDL; engine generates MERGEs.',\n );\n\n cmd\n .command('list')\n .description('Enumerate seed files under <project>/seeds/.')\n .requiredOption('-p, --project <path>', 'Path to the .sdtproj file.')\n .action(async (opts) => {\n const loaded = await loadProject(String(opts.project));\n const seeds = await discoverSeeds(loaded.rootDir);\n if (seeds.length === 0) {\n console.log(\n 'No seeds found. Add files under <project>/seeds/<database>/<schema>/<table>.{json,csv}.',\n );\n return;\n }\n for (const s of seeds) {\n console.log(\n ` ${s.database}.${s.schema}.${s.table.padEnd(28)} ${String(s.rows.length).padStart(5)} rows keys=[${s.keys.join(', ')}]`,\n );\n }\n console.log('');\n console.log(`${seeds.length} seed(s).`);\n });\n\n cmd\n .command('render')\n .description('Emit MERGE statements for every seed file. Offline; no account contact.')\n .requiredOption('-p, --project <path>', 'Path to the .sdtproj file.')\n .option('-o, --out <path>', 'Write to file. Defaults to stdout.')\n .option(\n '--delete-not-in-source',\n 'Prepend a DELETE so target-only rows are removed (Snowflake equivalent of WHEN NOT MATCHED BY SOURCE). Default off (safer).',\n false,\n )\n .action(async (opts) => {\n const loaded = await loadProject(String(opts.project));\n const seeds = await discoverSeeds(loaded.rootDir);\n const renderOpts = { deleteNotInSource: !!opts.deleteNotInSource };\n const sql = seeds.map((s) => renderSeedMerge(s, renderOpts)).join('\\n\\n') + '\\n';\n if (opts.out) {\n const outPath = path.resolve(String(opts.out));\n await fs.mkdir(path.dirname(outPath), { recursive: true });\n await fs.writeFile(outPath, sql, 'utf8');\n console.error(`Wrote ${outPath} (${sql.length} bytes, ${seeds.length} seed(s)).`);\n } else {\n process.stdout.write(sql);\n }\n });\n\n cmd\n .command('apply')\n .description('Execute every seed MERGE against a connection profile.')\n .requiredOption('-p, --project <path>', 'Path to the .sdtproj file.')\n .requiredOption('--connection <name>', 'Connection profile to apply against.')\n .requiredOption('--yes', 'Explicit confirmation. Required because apply writes data.')\n .option('--dry-run', 'Print the MERGEs without executing.', false)\n .option(\n '--delete-not-in-source',\n 'Prepend a DELETE so target-only rows are removed (Snowflake equivalent of WHEN NOT MATCHED BY SOURCE). Default off (safer).',\n false,\n )\n .action(async (opts) => {\n const loaded = await loadProject(String(opts.project));\n const seeds = await discoverSeeds(loaded.rootDir);\n if (seeds.length === 0) {\n console.log('No seeds found. Nothing to apply.');\n return;\n }\n const renderOpts = { deleteNotInSource: !!opts.deleteNotInSource };\n if (opts.dryRun) {\n for (const s of seeds) console.log(renderSeedMerge(s, renderOpts) + '\\n');\n return;\n }\n const profile = await getProfile(String(opts.connection));\n const conn = new SnowflakeConnection(profile);\n let succeeded = 0;\n let failed = 0;\n try {\n await conn.connect();\n const exec = new SnowflakeExecutor(conn);\n for (const s of seeds) {\n process.stdout.write(`▶ ${s.database}.${s.schema}.${s.table} (${s.rows.length} rows) …`);\n const t0 = Date.now();\n try {\n await exec.execute(renderSeedMerge(s, renderOpts));\n console.log(` ✓ (${Date.now() - t0}ms)`);\n succeeded++;\n } catch (err) {\n console.log(` ✗ ${err instanceof Error ? err.message : String(err)}`);\n failed++;\n }\n }\n } finally {\n await conn.disconnect();\n }\n console.log('');\n console.log(`Summary: ${succeeded} succeeded, ${failed} failed.`);\n if (failed > 0) process.exitCode = 1;\n });\n\n return cmd;\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,88 @@
1
+ import {
2
+ logger
3
+ } from "./chunk-VM2H4LAO.js";
4
+ import "./chunk-DGUM43GV.js";
5
+
6
+ // src/commands/sketch.ts
7
+ import { promises as fs } from "fs";
8
+ import { Command } from "commander";
9
+ import { ai, objectSketch } from "@sdt-tools/core";
10
+ var KINDS = [
11
+ "table",
12
+ "view",
13
+ "materialized-view",
14
+ "stream",
15
+ "task",
16
+ "dynamic-table",
17
+ "procedure",
18
+ "function",
19
+ "sequence"
20
+ ];
21
+ function sketchCommand() {
22
+ const cmd = new Command("sketch");
23
+ cmd.description(
24
+ "AI-assist: scaffold idiomatic Snowflake DDL from a prose description. Output always carries a REVIEW BEFORE DEPLOY header."
25
+ ).argument("<kind>", `Object kind: ${KINDS.join(" | ")}`).requiredOption("--description <text>", 'Free-form description. Use "-" to read from stdin.').option("--target <fqn>", "Target FQN (e.g. ANALYTICS.PUBLIC.ORDERS).").option("--context <text>", 'Optional additional context (e.g. "use role MERGE_OPS").').option("--out <path>", "Output file. Default stdout.").option("--format <fmt>", "Output format: text | json. Default text.", "text").option(
26
+ "--ai-max-spend <usd>",
27
+ "Refuse the call if today's estimated spend \u2265 this (USD). 0 = no cap.",
28
+ "0"
29
+ ).action(async (kindArg, opts) => {
30
+ const kind = String(kindArg).toLowerCase();
31
+ if (!KINDS.includes(kind)) {
32
+ throw new Error(`Unknown kind "${kindArg}". Use one of: ${KINDS.join(" | ")}`);
33
+ }
34
+ const description = String(opts.description) === "-" ? await readStdin() : String(opts.description);
35
+ const targetFqn = opts.target ? splitFqn(String(opts.target)) : void 0;
36
+ const result = await objectSketch.sketchToDdl(
37
+ {
38
+ description,
39
+ objectKind: kind,
40
+ targetFqn,
41
+ additionalContext: opts.context ? String(opts.context) : void 0
42
+ },
43
+ {
44
+ completeFn: async (prompt) => {
45
+ const r = await ai.complete([{ role: "user", content: prompt }], {
46
+ feature: "object-sketch",
47
+ maxSpendUsd: Number(opts.aiMaxSpend ?? "0") || 0
48
+ });
49
+ return r.text;
50
+ }
51
+ }
52
+ );
53
+ const output = String(opts.format).toLowerCase() === "json" ? JSON.stringify({ ...result, rawModelText: void 0 }, null, 2) : result.generatedSql;
54
+ if (opts.out) {
55
+ await fs.writeFile(String(opts.out), output, "utf8");
56
+ logger.success(`Wrote ${String(opts.out)} (${output.length} bytes)`);
57
+ } else {
58
+ console.log(output);
59
+ }
60
+ if (result.assumptions.length > 0) {
61
+ logger.dim("");
62
+ logger.dim("Model assumptions:");
63
+ for (const a of result.assumptions) logger.dim(` - ${a}`);
64
+ }
65
+ if (result.parseFailed) {
66
+ logger.warn("Model output could not be parsed \u2014 see the raw text via --format json.");
67
+ }
68
+ });
69
+ return cmd;
70
+ }
71
+ function splitFqn(fqn) {
72
+ const parts = fqn.split(".");
73
+ if (parts.length === 1) return { name: parts[0] };
74
+ if (parts.length === 2) return { schema: parts[0], name: parts[1] };
75
+ if (parts.length === 3) return { database: parts[0], schema: parts[1], name: parts[2] };
76
+ throw new Error(`Invalid --target "${fqn}": expected 1, 2, or 3 dot-separated parts.`);
77
+ }
78
+ async function readStdin() {
79
+ const chunks = [];
80
+ for await (const chunk of process.stdin) {
81
+ chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
82
+ }
83
+ return Buffer.concat(chunks).toString("utf8");
84
+ }
85
+ export {
86
+ sketchCommand
87
+ };
88
+ //# sourceMappingURL=sketch-PTLKDIK3.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/sketch.ts"],"sourcesContent":["/**\n * `sdt sketch <kind>` — generate idiomatic Snowflake DDL from a free-form\n * prose description via the configured AI provider.\n *\n * Composes `@sdt-tools/core/objectSketch.sketchToDdl` with `@sdt-tools/core/ai.complete`.\n * Output is always wrapped in a \"REVIEW BEFORE DEPLOY\" header so the user\n * never confuses model output with hand-authored DDL.\n *\n * Inputs:\n * <kind> table | view | materialized-view | stream | task\n * | dynamic-table | procedure | function | sequence\n * --description <text> Free-form description (or \"-\" to read from stdin)\n * --target <fqn> Optional target FQN (e.g. ANALYTICS.PUBLIC.ORDERS)\n * --context <text> Optional additional context\n * --out <path> Optional output file. Default: stdout.\n * --format <fmt> text | json. Default text (just the DDL).\n */\nimport { promises as fs } from 'node:fs';\nimport { Command } from 'commander';\nimport { ai, objectSketch } from '@sdt-tools/core';\nimport { logger } from '../util/logger.js';\n\nconst KINDS = [\n 'table',\n 'view',\n 'materialized-view',\n 'stream',\n 'task',\n 'dynamic-table',\n 'procedure',\n 'function',\n 'sequence',\n] as const;\n\ntype Kind = (typeof KINDS)[number];\n\nexport function sketchCommand(): Command {\n const cmd = new Command('sketch');\n cmd\n .description(\n 'AI-assist: scaffold idiomatic Snowflake DDL from a prose description. Output always carries a REVIEW BEFORE DEPLOY header.',\n )\n .argument('<kind>', `Object kind: ${KINDS.join(' | ')}`)\n .requiredOption('--description <text>', 'Free-form description. Use \"-\" to read from stdin.')\n .option('--target <fqn>', 'Target FQN (e.g. ANALYTICS.PUBLIC.ORDERS).')\n .option('--context <text>', 'Optional additional context (e.g. \"use role MERGE_OPS\").')\n .option('--out <path>', 'Output file. Default stdout.')\n .option('--format <fmt>', 'Output format: text | json. Default text.', 'text')\n .option(\n '--ai-max-spend <usd>',\n \"Refuse the call if today's estimated spend ≥ this (USD). 0 = no cap.\",\n '0',\n )\n .action(async (kindArg, opts) => {\n const kind = String(kindArg).toLowerCase() as Kind;\n if (!KINDS.includes(kind)) {\n throw new Error(`Unknown kind \"${kindArg}\". Use one of: ${KINDS.join(' | ')}`);\n }\n\n const description =\n String(opts.description) === '-' ? await readStdin() : String(opts.description);\n const targetFqn = opts.target ? splitFqn(String(opts.target)) : undefined;\n\n const result = await objectSketch.sketchToDdl(\n {\n description,\n objectKind: kind,\n targetFqn,\n additionalContext: opts.context ? String(opts.context) : undefined,\n },\n {\n completeFn: async (prompt) => {\n const r = await ai.complete([{ role: 'user', content: prompt }], {\n feature: 'object-sketch',\n maxSpendUsd: Number(opts.aiMaxSpend ?? '0') || 0,\n });\n return r.text;\n },\n },\n );\n\n const output =\n String(opts.format).toLowerCase() === 'json'\n ? JSON.stringify({ ...result, rawModelText: undefined } /* strip raw for JSON */, null, 2)\n : result.generatedSql;\n\n if (opts.out) {\n await fs.writeFile(String(opts.out), output, 'utf8');\n logger.success(`Wrote ${String(opts.out)} (${output.length} bytes)`);\n } else {\n console.log(output);\n }\n\n // Surface assumptions on stderr so they're visible even when stdout\n // is redirected into a project file.\n if (result.assumptions.length > 0) {\n logger.dim('');\n logger.dim('Model assumptions:');\n for (const a of result.assumptions) logger.dim(` - ${a}`);\n }\n if (result.parseFailed) {\n logger.warn('Model output could not be parsed — see the raw text via --format json.');\n }\n });\n return cmd;\n}\n\nfunction splitFqn(fqn: string): { database?: string; schema?: string; name: string } {\n const parts = fqn.split('.');\n if (parts.length === 1) return { name: parts[0]! };\n if (parts.length === 2) return { schema: parts[0], name: parts[1]! };\n if (parts.length === 3) return { database: parts[0], schema: parts[1], name: parts[2]! };\n throw new Error(`Invalid --target \"${fqn}\": expected 1, 2, or 3 dot-separated parts.`);\n}\n\nasync function readStdin(): Promise<string> {\n const chunks: Buffer[] = [];\n for await (const chunk of process.stdin) {\n chunks.push(typeof chunk === 'string' ? Buffer.from(chunk) : (chunk as Buffer));\n }\n return Buffer.concat(chunks).toString('utf8');\n}\n"],"mappings":"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","names":[]}