@sdt-tools/cli 0.2.5 → 0.3.0

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.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/import-script.ts"],"sourcesContent":["import { promises as fs } from 'node:fs';\nimport path from 'node:path';\nimport { Command } from 'commander';\nimport {\n importScript,\n loadProject,\n parseScript,\n type ParsedStatement,\n} from '@sdt-tools/core/project';\n\n/**\n * `sdt import-script` — parse a SQL script and apply each DDL statement to a\n * `.sdtproj` tree, writing each into the canonical folder for its object\n * type (`databases/<db>/schemas/<sch>/<type-folder>/<name>.sql`).\n *\n * This is the script→tree splitter. For converting another tool's artifacts\n * (schemachange / SnowDDL / dacpac / dbt / Terraform / …) into a fresh\n * project, use `sdt import` instead. Byte-aligned with `ddt import-script`.\n *\n * Default mode is safe:\n * - Parses the whole script first; refuses to write anything if any\n * statement has hard errors. The user reviews the report, fixes the\n * script, and re-runs.\n * - Refuses to overwrite existing files. Pass `--force` to clobber.\n * - `--dry-run` shows the plan without writing.\n */\nexport function importScriptCommand(): Command {\n const cmd = new Command('import-script');\n cmd\n .description(\n 'Parse a SQL script and write each DDL statement into the .sdtproj tree under its canonical folder.',\n )\n .requiredOption('--script <path>', 'Path to the SQL script to import.')\n .requiredOption('-p, --project <path>', 'Path to the .sdtproj file.')\n .option('--dry-run', 'Report what would be written without touching disk.', false)\n .option('--force', 'Overwrite existing files (default refuses on conflict).', false)\n .option(\n '--ignore-errors',\n 'Import classifiable statements even when others have errors. Default refuses on any error.',\n false,\n )\n .action(async (opts) => {\n const scriptPath = path.resolve(String(opts.script));\n const projectPath = path.resolve(String(opts.project));\n const sql = await fs.readFile(scriptPath, 'utf8');\n const parsed = parseScript(sql);\n\n // Up-front parse report — surfaces every error/warning verbatim.\n printParseReport(scriptPath, parsed);\n\n if (parsed.totalErrors > 0 && !opts.ignoreErrors) {\n console.error(\n `\\nRefusing to import: ${parsed.totalErrors} parse error(s) above. ` +\n `Fix the script and re-run, or pass --ignore-errors to import the clean statements.`,\n );\n process.exitCode = 2;\n return;\n }\n\n const loaded = await loadProject(projectPath);\n const result = await importScript(parsed, loaded, {\n dryRun: !!opts.dryRun,\n force: !!opts.force,\n });\n\n console.log('');\n const verb = opts.dryRun ? 'would write' : 'wrote';\n for (const item of result.imported) {\n const rel = path.relative(loaded.rootDir, item.targetPath);\n console.log(\n ` ${verb} ${item.statement.objectType} ${qualified(item.statement)} → ${rel}`,\n );\n }\n for (const item of result.skipped) {\n const head =\n item.statement.objectType && item.statement.fqn\n ? `${item.statement.objectType} ${qualified(item.statement)}`\n : `<unclassified statement at line ${item.statement.startLine}>`;\n console.log(` skip ${head} — ${item.reason}`);\n }\n console.log('');\n console.log(\n `Summary: ${result.imported.length} ${verb}, ${result.skipped.length} skipped, ` +\n `${parsed.totalErrors} errors, ${parsed.totalWarnings} warnings.`,\n );\n\n if (result.imported.length === 0 && result.skipped.length > 0) {\n process.exitCode = 1;\n }\n });\n return cmd;\n}\n\nfunction qualified(stmt: ParsedStatement): string {\n if (!stmt.fqn) return '<no fqn>';\n return [stmt.fqn.database, stmt.fqn.schema, stmt.fqn.name].filter(Boolean).join('.');\n}\n\nfunction printParseReport(scriptPath: string, parsed: ReturnType<typeof parseScript>): void {\n console.log(`Parsed ${parsed.statements.length} statement(s) from ${scriptPath}.`);\n if (parsed.totalErrors === 0 && parsed.totalWarnings === 0) {\n console.log('All statements classified cleanly.');\n return;\n }\n for (const stmt of parsed.statements) {\n if (stmt.errors.length === 0 && stmt.warnings.length === 0) continue;\n const head =\n stmt.objectType && stmt.fqn ? `${stmt.objectType} ${qualified(stmt)}` : '<unclassified>';\n console.log(`\\n[line ${stmt.startLine}–${stmt.endLine}] ${head}`);\n for (const e of stmt.errors) console.log(` ERROR: ${e}`);\n for (const w of stmt.warnings) console.log(` warning: ${w}`);\n }\n}\n"],"mappings":"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","names":[]}
package/dist/index.cjs CHANGED
@@ -716,11 +716,14 @@ function splitStatements(sql) {
716
716
  function publishCommand() {
717
717
  const cmd = new import_commander6.Command("publish");
718
718
  cmd.description(
719
- "Compare a .sdtpac to a live Snowflake target and apply (or dry-run) the migration."
719
+ "Compare a .sdtpac (the desired state) to a live Snowflake target and apply (or dry-run) the migration. Shared form with `ddt publish`: `--source <desired> --connection <live-target>`."
720
+ ).option(
721
+ "--source <path>",
722
+ "Path to the desired-state .sdtpac (canonical name; the same flag `ddt publish` uses). Required for a normal publish; omit only with --restore-from-snapshot."
720
723
  ).option(
721
724
  "--pac <path>",
722
- "Path to a built .sdtpac. Required for a normal publish; omit only with --restore-from-snapshot."
723
- ).requiredOption("-c, --connection <profile>", "Connection profile name").option(
725
+ "Back-compat alias for --source (the desired-state .sdtpac). Prefer --source for cross-platform parity with `ddt publish`."
726
+ ).requiredOption("-c, --connection <profile>", "Connection profile name (the live target).").option(
724
727
  "--restore-from-snapshot <batchId>",
725
728
  "Recovery mode: skip the compare step and emit ALTER TABLE \u2026 SWAP WITH against the snapshot batch <batchId> from the registry. Dry-run by default; pass --apply --yes to execute. This is the command printed by TRUST.4's post-deploy-smoke + TRUST.8's restore-hint when a deploy fails \u2014 they tell the operator to run `sdt publish --restore-from-snapshot <id>`."
726
729
  ).option(
@@ -807,12 +810,15 @@ function publishCommand() {
807
810
  await runRestoreFromSnapshot(opts);
808
811
  return;
809
812
  }
810
- if (!opts.pac) {
811
- logger.error("--pac <path> is required (unless --restore-from-snapshot is given).");
813
+ const pacRef = opts.source ?? opts.pac;
814
+ if (!pacRef) {
815
+ logger.error(
816
+ "--source <path> (or its alias --pac) is required (unless --restore-from-snapshot is given)."
817
+ );
812
818
  process.exitCode = 1;
813
819
  return;
814
820
  }
815
- const pacPath = import_node_path4.default.resolve(String(opts.pac));
821
+ const pacPath = import_node_path4.default.resolve(String(pacRef));
816
822
  const pac10 = await (0, import_pac2.readPac)(pacPath);
817
823
  const freshnessMode = opts.freshness ?? "warn";
818
824
  if (freshnessMode !== "skip") {
@@ -1735,6 +1741,11 @@ function connectionCommand() {
1735
1741
  await (0, import_connection5.upsertProfile)(profile);
1736
1742
  logger.success(`Saved profile "${profile.name}".`);
1737
1743
  });
1744
+ cmd.command("get <name>").description("Print a profile (secrets are redacted).").action(async (name) => {
1745
+ const profile = await (0, import_connection5.getProfile)(String(name));
1746
+ const redacted = { ...profile, auth: redactAuth(profile.auth) };
1747
+ logger.info(JSON.stringify(redacted, null, 2));
1748
+ });
1738
1749
  cmd.command("remove").description("Remove a connection profile.").argument("<name>").action(async (name) => {
1739
1750
  const ok = await (0, import_connection5.removeProfile)(String(name));
1740
1751
  if (ok) logger.success(`Removed profile "${name}".`);
@@ -1752,6 +1763,24 @@ function connectionCommand() {
1752
1763
  });
1753
1764
  return cmd;
1754
1765
  }
1766
+ function redactAuth(auth) {
1767
+ const isPlaceholder = (v) => v.startsWith("env:") || v.startsWith("keyring:");
1768
+ switch (auth.method) {
1769
+ case "PASSWORD":
1770
+ case "MFA":
1771
+ return auth.password && !isPlaceholder(auth.password) ? { ...auth, password: "<redacted>" } : auth;
1772
+ case "OAUTH":
1773
+ return auth.token && !isPlaceholder(auth.token) ? { ...auth, token: "<redacted>" } : auth;
1774
+ case "KEY_PAIR":
1775
+ return auth.privateKeyPassphrase && !isPlaceholder(auth.privateKeyPassphrase) ? { ...auth, privateKeyPassphrase: "<redacted>" } : auth;
1776
+ case "EXTERNAL_BROWSER":
1777
+ return auth;
1778
+ default: {
1779
+ const _exhaustive = auth;
1780
+ return _exhaustive;
1781
+ }
1782
+ }
1783
+ }
1755
1784
 
1756
1785
  // src/commands/scaffold.ts
1757
1786
  var import_commander10 = require("commander");