@sdt-tools/cli 0.2.0 → 0.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/advise-tests-6DRSZMBL.js +87 -0
- package/dist/advise-tests-6DRSZMBL.js.map +1 -0
- package/dist/ai-G4MJWHTM.js +89 -0
- package/dist/ai-G4MJWHTM.js.map +1 -0
- package/dist/anonymize-QR6JGXA7.js +123 -0
- package/dist/anonymize-QR6JGXA7.js.map +1 -0
- package/dist/approval-YVHYTV53.js +73 -0
- package/dist/approval-YVHYTV53.js.map +1 -0
- package/dist/approval-chain-54KKJZS3.js +120 -0
- package/dist/approval-chain-54KKJZS3.js.map +1 -0
- package/dist/audit-log-QZFH7LUX.js +159 -0
- package/dist/audit-log-QZFH7LUX.js.map +1 -0
- package/dist/backlog-V2YUIQDL.js +76 -0
- package/dist/backlog-V2YUIQDL.js.map +1 -0
- package/dist/bisect-GEVYAVL5.js +111 -0
- package/dist/bisect-GEVYAVL5.js.map +1 -0
- package/dist/bookmarks-57LKS7P6.js +107 -0
- package/dist/bookmarks-57LKS7P6.js.map +1 -0
- package/dist/branch-W2MGMPSH.js +88 -0
- package/dist/branch-W2MGMPSH.js.map +1 -0
- package/dist/build-VNIQFKSP.js +23 -0
- package/dist/build-VNIQFKSP.js.map +1 -0
- package/dist/catalog-JLB5VCEV.js +137 -0
- package/dist/catalog-JLB5VCEV.js.map +1 -0
- package/dist/changelog-M7XGDYSY.js +220 -0
- package/dist/changelog-M7XGDYSY.js.map +1 -0
- package/dist/chunk-DGUM43GV.js +11 -0
- package/dist/chunk-DGUM43GV.js.map +1 -0
- package/dist/chunk-EWXM4KJN.js +25 -0
- package/dist/chunk-EWXM4KJN.js.map +1 -0
- package/dist/chunk-JP2EZLR5.js +50 -0
- package/dist/chunk-JP2EZLR5.js.map +1 -0
- package/dist/chunk-VM2H4LAO.js +15 -0
- package/dist/chunk-VM2H4LAO.js.map +1 -0
- package/dist/chunk-ZWY4ZRHL.js +44 -0
- package/dist/chunk-ZWY4ZRHL.js.map +1 -0
- package/dist/cli.js +506 -19014
- package/dist/cli.js.map +1 -1
- package/dist/compare-5O6UTWPJ.js +405 -0
- package/dist/compare-5O6UTWPJ.js.map +1 -0
- package/dist/compare-profiles-7ZSNIW7B.js +218 -0
- package/dist/compare-profiles-7ZSNIW7B.js.map +1 -0
- package/dist/completion-I5U5VVAX.js +82 -0
- package/dist/completion-I5U5VVAX.js.map +1 -0
- package/dist/connection-SYTH4V53.js +110 -0
- package/dist/connection-SYTH4V53.js.map +1 -0
- package/dist/cost-estimate-TJDDH6TO.js +328 -0
- package/dist/cost-estimate-TJDDH6TO.js.map +1 -0
- package/dist/data-compare-UK2UXAS3.js +134 -0
- package/dist/data-compare-UK2UXAS3.js.map +1 -0
- package/dist/data-fit-Q45ENBRL.js +125 -0
- package/dist/data-fit-Q45ENBRL.js.map +1 -0
- package/dist/deploy-status-UUHKVDTI.js +58 -0
- package/dist/deploy-status-UUHKVDTI.js.map +1 -0
- package/dist/design-PO6UPBL7.js +138 -0
- package/dist/design-PO6UPBL7.js.map +1 -0
- package/dist/diagnose-6IFMELFR.js +145 -0
- package/dist/diagnose-6IFMELFR.js.map +1 -0
- package/dist/discover-A7OSZAHK.js +78 -0
- package/dist/discover-A7OSZAHK.js.map +1 -0
- package/dist/docs-CVRKGUSW.js +177 -0
- package/dist/docs-CVRKGUSW.js.map +1 -0
- package/dist/drift-XDA3BDYN.js +226 -0
- package/dist/drift-XDA3BDYN.js.map +1 -0
- package/dist/drift-gate-V7QSIOGZ.js +94 -0
- package/dist/drift-gate-V7QSIOGZ.js.map +1 -0
- package/dist/error-lookup-7ZWCZJ44.js +56 -0
- package/dist/error-lookup-7ZWCZJ44.js.map +1 -0
- package/dist/errorReporting-ZRNJ3VW7.js +109 -0
- package/dist/errorReporting-ZRNJ3VW7.js.map +1 -0
- package/dist/exec-PKBHLI7T.js +121 -0
- package/dist/exec-PKBHLI7T.js.map +1 -0
- package/dist/explain-LWKJOTL7.js +192 -0
- package/dist/explain-LWKJOTL7.js.map +1 -0
- package/dist/explorer-QOVM6VBD.js +61 -0
- package/dist/explorer-QOVM6VBD.js.map +1 -0
- package/dist/export-IYYBZ5HE.js +42 -0
- package/dist/export-IYYBZ5HE.js.map +1 -0
- package/dist/extract-VMMVRQVT.js +102 -0
- package/dist/extract-VMMVRQVT.js.map +1 -0
- package/dist/features-LE6BDZ2S.js +59 -0
- package/dist/features-LE6BDZ2S.js.map +1 -0
- package/dist/feedback-M7DM2EQC.js +161 -0
- package/dist/feedback-M7DM2EQC.js.map +1 -0
- package/dist/find-EME2JG2I.js +176 -0
- package/dist/find-EME2JG2I.js.map +1 -0
- package/dist/format-TRLWLMGS.js +141 -0
- package/dist/format-TRLWLMGS.js.map +1 -0
- package/dist/generate-6NAZGZDV.js +152 -0
- package/dist/generate-6NAZGZDV.js.map +1 -0
- package/dist/graph-QNQDAUO7.js +161 -0
- package/dist/graph-QNQDAUO7.js.map +1 -0
- package/dist/history-RONA7ZTI.js +199 -0
- package/dist/history-RONA7ZTI.js.map +1 -0
- package/dist/hosts-YBXY2ZG5.js +49 -0
- package/dist/hosts-YBXY2ZG5.js.map +1 -0
- package/dist/impact-T2JSANHS.js +59 -0
- package/dist/impact-T2JSANHS.js.map +1 -0
- package/dist/import-AELYLY6A.js +32 -0
- package/dist/import-AELYLY6A.js.map +1 -0
- package/dist/index.cjs +36 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +60 -25
- package/dist/index.js.map +1 -1
- package/dist/init-SWRRJMGI.js +57 -0
- package/dist/init-SWRRJMGI.js.map +1 -0
- package/dist/install-hooks-6SIAGTAF.js +109 -0
- package/dist/install-hooks-6SIAGTAF.js.map +1 -0
- package/dist/license-OAF22PLZ.js +46 -0
- package/dist/license-OAF22PLZ.js.map +1 -0
- package/dist/lineage-EW66XJ6O.js +552 -0
- package/dist/lineage-EW66XJ6O.js.map +1 -0
- package/dist/lint-FQ2OTYTQ.js +143 -0
- package/dist/lint-FQ2OTYTQ.js.map +1 -0
- package/dist/mcp-3QI4TH4N.js +344 -0
- package/dist/mcp-3QI4TH4N.js.map +1 -0
- package/dist/migrate-from-dbt-JVTXPWKQ.js +156 -0
- package/dist/migrate-from-dbt-JVTXPWKQ.js.map +1 -0
- package/dist/migrate-platform-NTRTOGNR.js +91 -0
- package/dist/migrate-platform-NTRTOGNR.js.map +1 -0
- package/dist/optimize-CJYWMAWA.js +105 -0
- package/dist/optimize-CJYWMAWA.js.map +1 -0
- package/dist/perf-LL2CPCJF.js +205 -0
- package/dist/perf-LL2CPCJF.js.map +1 -0
- package/dist/pii-FBDRDQ2E.js +136 -0
- package/dist/pii-FBDRDQ2E.js.map +1 -0
- package/dist/pilot-CCQERKPH.js +29 -0
- package/dist/pilot-CCQERKPH.js.map +1 -0
- package/dist/pr-comment-S5FF4QRX.js +79 -0
- package/dist/pr-comment-S5FF4QRX.js.map +1 -0
- package/dist/preview-5U4YVCRM.js +47 -0
- package/dist/preview-5U4YVCRM.js.map +1 -0
- package/dist/profile-7VC57KD2.js +101 -0
- package/dist/profile-7VC57KD2.js.map +1 -0
- package/dist/promote-AASEFTIA.js +408 -0
- package/dist/promote-AASEFTIA.js.map +1 -0
- package/dist/publish-Y2J56K4Y.js +715 -0
- package/dist/publish-Y2J56K4Y.js.map +1 -0
- package/dist/purge-QMXZKCMD.js +57 -0
- package/dist/purge-QMXZKCMD.js.map +1 -0
- package/dist/query-log-6OM4GI7W.js +112 -0
- package/dist/query-log-6OM4GI7W.js.map +1 -0
- package/dist/refactor-LTZQLJ35.js +5799 -0
- package/dist/refactor-LTZQLJ35.js.map +1 -0
- package/dist/refresh-4TY2AGOU.js +38 -0
- package/dist/refresh-4TY2AGOU.js.map +1 -0
- package/dist/replay-OOC25FZN.js +117 -0
- package/dist/replay-OOC25FZN.js.map +1 -0
- package/dist/revert-ODMUVJW6.js +110 -0
- package/dist/revert-ODMUVJW6.js.map +1 -0
- package/dist/review-XXPWOBFP.js +158 -0
- package/dist/review-XXPWOBFP.js.map +1 -0
- package/dist/rollback-suggest-6G2HEKFR.js +79 -0
- package/dist/rollback-suggest-6G2HEKFR.js.map +1 -0
- package/dist/safer-alternative-QFVNLG3L.js +89 -0
- package/dist/safer-alternative-QFVNLG3L.js.map +1 -0
- package/dist/safety-7QWRSUEZ.js +168 -0
- package/dist/safety-7QWRSUEZ.js.map +1 -0
- package/dist/savings-RHIXP6IT.js +95 -0
- package/dist/savings-RHIXP6IT.js.map +1 -0
- package/dist/scan-secrets-5YCQ4UCU.js +54 -0
- package/dist/scan-secrets-5YCQ4UCU.js.map +1 -0
- package/dist/schema-CIZXCQD2.js +429 -0
- package/dist/schema-CIZXCQD2.js.map +1 -0
- package/dist/script-K7CIN2P6.js +153 -0
- package/dist/script-K7CIN2P6.js.map +1 -0
- package/dist/search-BUZ5NXZZ.js +151 -0
- package/dist/search-BUZ5NXZZ.js.map +1 -0
- package/dist/seed-76QAK276.js +96 -0
- package/dist/seed-76QAK276.js.map +1 -0
- package/dist/sketch-PTLKDIK3.js +88 -0
- package/dist/sketch-PTLKDIK3.js.map +1 -0
- package/dist/snapshot-XLPR2OZ5.js +177 -0
- package/dist/snapshot-XLPR2OZ5.js.map +1 -0
- package/dist/snippets-EK4DK5CN.js +74 -0
- package/dist/snippets-EK4DK5CN.js.map +1 -0
- package/dist/standards-7T2UY6DD.js +241 -0
- package/dist/standards-7T2UY6DD.js.map +1 -0
- package/dist/suggest-VGRYSAR6.js +39 -0
- package/dist/suggest-VGRYSAR6.js.map +1 -0
- package/dist/suggest-constraints-MY5WKUHA.js +160 -0
- package/dist/suggest-constraints-MY5WKUHA.js.map +1 -0
- package/dist/suite-TRNGZWQM.js +88 -0
- package/dist/suite-TRNGZWQM.js.map +1 -0
- package/dist/telemetry-3U2QLA2S.js +75 -0
- package/dist/telemetry-3U2QLA2S.js.map +1 -0
- package/dist/template-ZERIXVXF.js +403 -0
- package/dist/template-ZERIXVXF.js.map +1 -0
- package/dist/test-5M2ED3WT.js +169 -0
- package/dist/test-5M2ED3WT.js.map +1 -0
- package/dist/trial-U732FONV.js +31 -0
- package/dist/trial-U732FONV.js.map +1 -0
- package/dist/validate-T6D2WCOK.js +106 -0
- package/dist/validate-T6D2WCOK.js.map +1 -0
- package/dist/verify-KXVASEEG.js +76 -0
- package/dist/verify-KXVASEEG.js.map +1 -0
- package/dist/watch-I6K4BNMA.js +80 -0
- package/dist/watch-I6K4BNMA.js.map +1 -0
- package/dist/xcompare-TPFLQO6W.js +87 -0
- package/dist/xcompare-TPFLQO6W.js.map +1 -0
- package/package.json +2 -2
- package/dist/cli.cjs +0 -19040
- package/dist/cli.cjs.map +0 -1
- package/dist/cli.d.cts +0 -1
- package/dist/cli.d.ts +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/test.ts"],"sourcesContent":["import { promises as fs } from 'node:fs';\nimport path from 'node:path';\nimport { Command } from 'commander';\nimport { project, testFramework } from '@sdt-tools/core';\nimport { getProfile, SnowflakeConnection } from '@sdt-tools/core/connection';\nimport { logger } from '../util/logger.js';\n\n/**\n * `sdt test` — declarative test framework. Author tests in YAML\n * alongside DDL; `sdt test render` compiles them to SQL assertions;\n * `sdt test list` enumerates them.\n *\n * dbt-test parity. Free tier — every team can author tests and run\n * them in CI without a license.\n */\nexport function testCommand(): Command {\n const cmd = new Command('test').description(\n 'Declarative tests (unique / not_null / accepted_values / relationships / expression) compiled from YAML to SQL.',\n );\n\n cmd\n .command('list')\n .description('Enumerate test files and their test counts.')\n .requiredOption('-p, --project <path>', 'Path to the .sdtproj file.')\n .action(async (opts) => {\n const loaded = await project.loadProject(String(opts.project));\n const files = await testFramework.discoverTests(loaded.rootDir);\n if (files.length === 0) {\n console.log(\n 'No tests found. Add files under <project>/tests/<db>/<schema>/<table>.test.yaml.',\n );\n return;\n }\n for (const f of files) {\n console.log(` ${f.fqn.padEnd(40)} ${String(f.tests.length).padStart(3)} test(s)`);\n for (const t of f.tests) {\n const cols =\n 'columns' in t\n ? `(${(t.columns ?? []).join(', ')})`\n : 'column' in t\n ? `(${t.column})`\n : '';\n console.log(` - ${t.kind}${cols}${t.name ? ` ${t.name}` : ''}`);\n }\n }\n console.log('');\n console.log(\n `${files.length} file(s), ${files.reduce((acc, f) => acc + f.tests.length, 0)} test(s) total.`,\n );\n });\n\n cmd\n .command('render')\n .description('Compile every test YAML into SQL assertions. 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 .action(async (opts) => {\n const loaded = await project.loadProject(String(opts.project));\n const files = await testFramework.discoverTests(loaded.rootDir);\n const sql = testFramework.renderAllTests(files);\n if (opts.out) {\n const out = path.resolve(String(opts.out));\n await fs.mkdir(path.dirname(out), { recursive: true });\n await fs.writeFile(out, sql, 'utf8');\n console.error(\n `Wrote ${out} (${sql.length} bytes, ${files.reduce((a, f) => a + f.tests.length, 0)} test(s)).`,\n );\n } else {\n process.stdout.write(sql);\n }\n });\n\n cmd\n .command('run')\n .description(\n 'Execute every test assertion against a live Snowflake account and report pass/fail. ' +\n 'Exits 2 when any error-severity test fails. Pro tier — composes DCM item 5 (data-quality expectations) ' +\n 'with the existing dbt-test parity framework.',\n )\n .requiredOption('-p, --project <path>', 'Path to the .sdtproj file.')\n .requiredOption('-c, --connection <profile>', 'Connection profile name.')\n .option(\n '--select <pattern>',\n 'Filter tests to files whose FQN matches a glob pattern. E.g. \"mydb.public.*\".',\n )\n .option('--fail-fast', 'Stop on the first error-severity failure.', false)\n .option(\n '--sample <n>',\n 'Cap failing-row samples captured per test. Default 10. 0 to skip.',\n '10',\n )\n .option(\n '--update-snapshots',\n 'TEST.3: overwrite every saved snapshot under .sdt/tests/__snapshots__/ with the live rows. Use when intentional schema change makes drift expected.',\n false,\n )\n .option('--format <fmt>', 'Output format: text | json. Default text.', 'text')\n .option('-o, --out <path>', 'Write report to a file instead of stdout.')\n .action(async (opts) => {\n const loaded = await project.loadProject(String(opts.project));\n let files = await testFramework.discoverTests(loaded.rootDir);\n if (opts.select) {\n const re = globToRegex(String(opts.select));\n files = files.filter((f) => re.test(f.fqn));\n }\n if (files.length === 0) {\n console.log(\n 'No tests found. Add files under <project>/tests/<db>/<schema>/<table>.test.yaml.',\n );\n return;\n }\n const profile = await getProfile(String(opts.connection));\n const conn = new SnowflakeConnection(profile);\n logger.step(`test run: connecting to ${profile.account} as ${profile.auth.username}…`);\n await conn.connect();\n const snapshotsDir = path.join(loaded.rootDir, '.sdt', 'tests', '__snapshots__');\n const snapshotStorage: testFramework.SnapshotStorage = {\n async read(name) {\n try {\n return await fs.readFile(path.join(snapshotsDir, `${name}.json`), 'utf8');\n } catch (e) {\n if ((e as NodeJS.ErrnoException).code === 'ENOENT') return null;\n throw e;\n }\n },\n async write(name, body) {\n await fs.mkdir(snapshotsDir, { recursive: true });\n await fs.writeFile(path.join(snapshotsDir, `${name}.json`), body, 'utf8');\n },\n };\n let report;\n try {\n report = await testFramework.runTestFiles(files, conn, {\n failFast: opts.failFast === true,\n failingRowSampleCap: Number(opts.sample ?? '10'),\n snapshotStorage,\n updateSnapshots: opts.updateSnapshots === true,\n });\n } finally {\n await conn.disconnect().catch(() => undefined);\n }\n const output =\n String(opts.format).toLowerCase() === 'json'\n ? testFramework.renderJsonReport(report)\n : testFramework.renderTextReport(report);\n if (opts.out) {\n const out = path.resolve(String(opts.out));\n await fs.mkdir(path.dirname(out), { recursive: true });\n await fs.writeFile(out, output, 'utf8');\n logger.info(\n `Wrote ${out} — ${report.summary.pass} pass / ${report.summary.fail} fail / ${report.summary.error} error.`,\n );\n } else {\n process.stdout.write(output);\n }\n if (report.summary.blocking) {\n process.exitCode = 2;\n }\n });\n\n cmd\n .command('coverage')\n .description(\n 'Report which project objects have at least one declarative test (TEST.4). ' +\n 'Offline; no account contact. Emits a Markdown table (default) or JSON.',\n )\n .requiredOption('-p, --project <path>', 'Path to the .sdtproj file.')\n .option('--format <fmt>', 'Output format: text | json. Default text.', 'text')\n .option('-o, --out <path>', 'Write report to a file instead of stdout.')\n .action(async (opts) => {\n const loaded = await project.loadProject(String(opts.project));\n const files = await testFramework.discoverTests(loaded.rootDir);\n const model = await project.parseProjectModel(loaded);\n const testable = new Set<string>([\n 'TABLE',\n 'EXTERNAL_TABLE',\n 'ICEBERG_TABLE',\n 'HYBRID_TABLE',\n 'DYNAMIC_TABLE',\n 'EVENT_TABLE',\n 'VIEW',\n 'MATERIALIZED_VIEW',\n ]);\n const fqns: string[] = [];\n for (const obj of model) {\n if (!testable.has(obj.objectType)) continue;\n const db = obj.fqn.database;\n const sc = obj.fqn.schema;\n if (!db || !sc) continue;\n fqns.push(`${db}.${sc}.${obj.fqn.name}`);\n }\n const report = testFramework.computeTestCoverage(files, fqns);\n const output =\n String(opts.format).toLowerCase() === 'json'\n ? testFramework.renderTestCoverageJson(report)\n : testFramework.renderTestCoverageMarkdown(report);\n if (opts.out) {\n const out = path.resolve(String(opts.out));\n await fs.mkdir(path.dirname(out), { recursive: true });\n await fs.writeFile(out, output, 'utf8');\n logger.info(\n `Wrote ${out} — ${report.withTests}/${report.total} object(s) covered (${report.coveragePct}%).`,\n );\n } else {\n process.stdout.write(output + '\\n');\n }\n });\n\n return cmd;\n}\n\n/** Convert a simple glob pattern (`*` wildcard, `.` literal) to a RegExp. */\nfunction globToRegex(pattern: string): RegExp {\n const escaped = pattern.replace(/[.+^${}()|[\\]\\\\]/g, '\\\\$&').replace(/\\*/g, '.*');\n return new RegExp(`^${escaped}$`, 'i');\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import {
|
|
2
|
+
logger
|
|
3
|
+
} from "./chunk-VM2H4LAO.js";
|
|
4
|
+
import "./chunk-DGUM43GV.js";
|
|
5
|
+
|
|
6
|
+
// src/commands/trial.ts
|
|
7
|
+
import { Command } from "commander";
|
|
8
|
+
import { license } from "@sdt-tools/core";
|
|
9
|
+
function trialCommand() {
|
|
10
|
+
const cmd = new Command("trial").description(
|
|
11
|
+
"Manage the SDT 30-day Pro trial (no account required)."
|
|
12
|
+
);
|
|
13
|
+
cmd.command("start").description("Begin a 30-day Pro trial.").action(async () => {
|
|
14
|
+
const record = await license.startTrial();
|
|
15
|
+
logger.info(`Pro trial started. Expires on ${record.expiresAt.slice(0, 10)}.`);
|
|
16
|
+
logger.info("Run `sdt trial status` to check remaining time.");
|
|
17
|
+
});
|
|
18
|
+
cmd.command("status").description("Show current trial state.").action(async () => {
|
|
19
|
+
const status = await license.getTrialStatus();
|
|
20
|
+
logger.info(license.describeTrialStatus(status));
|
|
21
|
+
});
|
|
22
|
+
cmd.command("reset").description("Reset trial state (dev/test only \u2014 requires SDT_DEV_RESET_TRIAL=1).").action(async () => {
|
|
23
|
+
await license.resetTrial();
|
|
24
|
+
logger.info("Trial state cleared.");
|
|
25
|
+
});
|
|
26
|
+
return cmd;
|
|
27
|
+
}
|
|
28
|
+
export {
|
|
29
|
+
trialCommand
|
|
30
|
+
};
|
|
31
|
+
//# sourceMappingURL=trial-U732FONV.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/trial.ts"],"sourcesContent":["/**\n * `sdt trial start|status|reset` — manage the local 30-day Pro trial.\n * Mirrors `ddt trial` in shape (PIL.1 + PIL.4).\n */\nimport { Command } from 'commander';\nimport { license } from '@sdt-tools/core';\nimport { logger } from '../util/logger.js';\n\nexport function trialCommand(): Command {\n const cmd = new Command('trial').description(\n 'Manage the SDT 30-day Pro trial (no account required).',\n );\n\n cmd\n .command('start')\n .description('Begin a 30-day Pro trial.')\n .action(async () => {\n const record = await license.startTrial();\n logger.info(`Pro trial started. Expires on ${record.expiresAt.slice(0, 10)}.`);\n logger.info('Run `sdt trial status` to check remaining time.');\n });\n\n cmd\n .command('status')\n .description('Show current trial state.')\n .action(async () => {\n const status = await license.getTrialStatus();\n logger.info(license.describeTrialStatus(status));\n });\n\n cmd\n .command('reset')\n .description('Reset trial state (dev/test only — requires SDT_DEV_RESET_TRIAL=1).')\n .action(async () => {\n await license.resetTrial();\n logger.info('Trial state cleared.');\n });\n\n return cmd;\n}\n"],"mappings":";;;;;;AAIA,SAAS,eAAe;AACxB,SAAS,eAAe;AAGjB,SAAS,eAAwB;AACtC,QAAM,MAAM,IAAI,QAAQ,OAAO,EAAE;AAAA,IAC/B;AAAA,EACF;AAEA,MACG,QAAQ,OAAO,EACf,YAAY,2BAA2B,EACvC,OAAO,YAAY;AAClB,UAAM,SAAS,MAAM,QAAQ,WAAW;AACxC,WAAO,KAAK,iCAAiC,OAAO,UAAU,MAAM,GAAG,EAAE,CAAC,GAAG;AAC7E,WAAO,KAAK,iDAAiD;AAAA,EAC/D,CAAC;AAEH,MACG,QAAQ,QAAQ,EAChB,YAAY,2BAA2B,EACvC,OAAO,YAAY;AAClB,UAAM,SAAS,MAAM,QAAQ,eAAe;AAC5C,WAAO,KAAK,QAAQ,oBAAoB,MAAM,CAAC;AAAA,EACjD,CAAC;AAEH,MACG,QAAQ,OAAO,EACf,YAAY,0EAAqE,EACjF,OAAO,YAAY;AAClB,UAAM,QAAQ,WAAW;AACzB,WAAO,KAAK,sBAAsB;AAAA,EACpC,CAAC;AAEH,SAAO;AACT;","names":[]}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import {
|
|
2
|
+
attachRelatedOptions
|
|
3
|
+
} from "./chunk-EWXM4KJN.js";
|
|
4
|
+
import {
|
|
5
|
+
logger
|
|
6
|
+
} from "./chunk-VM2H4LAO.js";
|
|
7
|
+
import "./chunk-DGUM43GV.js";
|
|
8
|
+
|
|
9
|
+
// src/commands/validate.ts
|
|
10
|
+
import { promises as fs } from "fs";
|
|
11
|
+
import path from "path";
|
|
12
|
+
import { Command } from "commander";
|
|
13
|
+
import {
|
|
14
|
+
catalog,
|
|
15
|
+
project,
|
|
16
|
+
validate as validateNs,
|
|
17
|
+
variables as variablesNs
|
|
18
|
+
} from "@sdt-tools/core";
|
|
19
|
+
function validateCommand() {
|
|
20
|
+
const cmd = new Command("validate");
|
|
21
|
+
cmd.description(
|
|
22
|
+
"Validate a .sdtproj \u2014 schema check; optionally resolve every object reference (--references)."
|
|
23
|
+
).requiredOption("-p, --project <path>", "Path to .sdtproj").option(
|
|
24
|
+
"--references",
|
|
25
|
+
"Run the build-time semantic resolver \u2014 flag dangling/external/temp/dynamic references."
|
|
26
|
+
).option(
|
|
27
|
+
"--columns",
|
|
28
|
+
"[--references] Also resolve every column reference. ERROR on missing column on a known table. Phase 2 of the SSDT-build-equivalent resolver."
|
|
29
|
+
).option(
|
|
30
|
+
"--min-severity <s>",
|
|
31
|
+
"[--references] Filter findings: error | warning | info. Default info.",
|
|
32
|
+
"info"
|
|
33
|
+
).option("--format <fmt>", "[--references] table | json. Default table.", "table").option("-o, --out <path>", "[--references] Write report to file instead of stdout.").option(
|
|
34
|
+
"--check-variables",
|
|
35
|
+
"VARSYNTAX.4 \u2014 refuse if any `$(VAR)` reference in a tracked `.sql` file is not declared in built-ins / deploymentProfiles[*].variables. Exits 1 when undeclared references are found."
|
|
36
|
+
).action(async (opts) => {
|
|
37
|
+
const loaded = await project.loadProject(String(opts.project));
|
|
38
|
+
const files = await project.discoverObjectFiles(loaded);
|
|
39
|
+
logger.success(`Project ${loaded.project.name} v${loaded.project.version} is valid.`);
|
|
40
|
+
logger.dim(
|
|
41
|
+
` Scope: ${loaded.project.scope.type}${loaded.project.scope.database ? "/" + loaded.project.scope.database : ""}`
|
|
42
|
+
);
|
|
43
|
+
logger.dim(` Files: ${files.length}`);
|
|
44
|
+
logger.dim(` Profiles: ${Object.keys(loaded.project.deploymentProfiles ?? {}).length}`);
|
|
45
|
+
if (opts.checkVariables) {
|
|
46
|
+
const projRoot = path.dirname(path.resolve(String(opts.project)));
|
|
47
|
+
const { results } = await catalog.mapPool(
|
|
48
|
+
files,
|
|
49
|
+
async (f) => {
|
|
50
|
+
const relPath = path.relative(projRoot, f).split(path.sep).join("/");
|
|
51
|
+
const content = await fs.readFile(f, "utf8");
|
|
52
|
+
return { path: relPath, content };
|
|
53
|
+
},
|
|
54
|
+
{ concurrency: 16, stopOnError: true }
|
|
55
|
+
);
|
|
56
|
+
const fileContents = results.filter(
|
|
57
|
+
(r) => r !== void 0
|
|
58
|
+
);
|
|
59
|
+
const profilesBlock = loaded.project.deploymentProfiles ?? {};
|
|
60
|
+
const profileVariableKeys = Object.values(profilesBlock).map(
|
|
61
|
+
(p) => Object.keys(p.variables ?? {})
|
|
62
|
+
);
|
|
63
|
+
const report2 = variablesNs.checkVariableReferences({
|
|
64
|
+
files: fileContents,
|
|
65
|
+
deploymentProfileVariables: profileVariableKeys
|
|
66
|
+
});
|
|
67
|
+
if (report2.undeclared.length > 0) {
|
|
68
|
+
const msg = variablesNs.formatCheckVariablesReport(report2);
|
|
69
|
+
process.stdout.write(msg + "\n");
|
|
70
|
+
process.exitCode = 1;
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
logger.dim(` Variables: ${report2.references.length} reference(s), all declared.`);
|
|
74
|
+
}
|
|
75
|
+
if (!opts.references) return;
|
|
76
|
+
logger.dim("");
|
|
77
|
+
logger.dim("Resolving references\u2026");
|
|
78
|
+
const model = await project.parseProjectModel(loaded);
|
|
79
|
+
const report = validateNs.validateProject(model, {
|
|
80
|
+
source: String(opts.project),
|
|
81
|
+
minSeverity: opts.minSeverity,
|
|
82
|
+
columns: Boolean(opts.columns)
|
|
83
|
+
});
|
|
84
|
+
const fmt = String(opts.format ?? "table").toLowerCase();
|
|
85
|
+
const payload = fmt === "json" ? JSON.stringify(report, null, 2) : validateNs.formatBuildReport(report);
|
|
86
|
+
if (opts.out) {
|
|
87
|
+
const out = path.resolve(String(opts.out));
|
|
88
|
+
await fs.mkdir(path.dirname(out), { recursive: true });
|
|
89
|
+
await fs.writeFile(out, payload + (payload.endsWith("\n") ? "" : "\n"), "utf8");
|
|
90
|
+
logger.dim(`Wrote ${out} (${payload.length} bytes, ${report.findings.length} finding(s)).`);
|
|
91
|
+
} else {
|
|
92
|
+
process.stdout.write(payload + (payload.endsWith("\n") ? "" : "\n"));
|
|
93
|
+
}
|
|
94
|
+
if (report.totals.error > 0) process.exitCode = 1;
|
|
95
|
+
});
|
|
96
|
+
attachRelatedOptions(cmd, [
|
|
97
|
+
"compare.ignoreCase",
|
|
98
|
+
"compare.ignoreComments",
|
|
99
|
+
"compare.ignoreFormattingDifferences"
|
|
100
|
+
]);
|
|
101
|
+
return cmd;
|
|
102
|
+
}
|
|
103
|
+
export {
|
|
104
|
+
validateCommand
|
|
105
|
+
};
|
|
106
|
+
//# sourceMappingURL=validate-T6D2WCOK.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/validate.ts"],"sourcesContent":["import { promises as fs } from 'node:fs';\nimport path from 'node:path';\nimport { Command } from 'commander';\nimport {\n catalog,\n project,\n validate as validateNs,\n variables as variablesNs,\n} from '@sdt-tools/core';\nimport { logger } from '../util/logger.js';\nimport { attachRelatedOptions } from '../util/help-catalog.js';\n\n/**\n * `sdt validate` — schema-shape sanity check on a `.sdtproj`. Default\n * mode confirms the project file parses, scope resolves, and object\n * files discover successfully. The `--references` flag adds the\n * Microsoft-SSDT-style build-time semantic resolution: every object reference in every\n * project body is classified as resolved / dangling / external /\n * temporary / dynamic, with warnings for unresolved references.\n *\n * Column-level resolution is Phase 2 (BACKLOG-tracked).\n */\nexport function validateCommand(): Command {\n const cmd = new Command('validate');\n cmd\n .description(\n 'Validate a .sdtproj — schema check; optionally resolve every object reference (--references).',\n )\n .requiredOption('-p, --project <path>', 'Path to .sdtproj')\n .option(\n '--references',\n 'Run the build-time semantic resolver — flag dangling/external/temp/dynamic references.',\n )\n .option(\n '--columns',\n '[--references] Also resolve every column reference. ERROR on missing column on a known table. Phase 2 of the SSDT-build-equivalent resolver.',\n )\n .option(\n '--min-severity <s>',\n '[--references] Filter findings: error | warning | info. Default info.',\n 'info',\n )\n .option('--format <fmt>', '[--references] table | json. Default table.', 'table')\n .option('-o, --out <path>', '[--references] Write report to file instead of stdout.')\n .option(\n '--check-variables',\n 'VARSYNTAX.4 — refuse if any `$(VAR)` reference in a tracked `.sql` file is not declared in built-ins / deploymentProfiles[*].variables. Exits 1 when undeclared references are found.',\n )\n .action(async (opts) => {\n const loaded = await project.loadProject(String(opts.project));\n const files = await project.discoverObjectFiles(loaded);\n logger.success(`Project ${loaded.project.name} v${loaded.project.version} is valid.`);\n logger.dim(\n ` Scope: ${loaded.project.scope.type}${loaded.project.scope.database ? '/' + loaded.project.scope.database : ''}`,\n );\n logger.dim(` Files: ${files.length}`);\n logger.dim(` Profiles: ${Object.keys(loaded.project.deploymentProfiles ?? {}).length}`);\n\n if (opts.checkVariables) {\n const projRoot = path.dirname(path.resolve(String(opts.project)));\n // RH4.3 — bounded-concurrency reads (cap FDs on large projects).\n // stopOnError preserves the prior Promise.all reject-on-first-error\n // semantics; results stay dense + in input order.\n const { results } = await catalog.mapPool(\n files,\n async (f) => {\n const relPath = path.relative(projRoot, f).split(path.sep).join('/');\n const content = await fs.readFile(f, 'utf8');\n return { path: relPath, content };\n },\n { concurrency: 16, stopOnError: true },\n );\n const fileContents = results.filter(\n (r): r is { path: string; content: string } => r !== undefined,\n );\n const profilesBlock = loaded.project.deploymentProfiles ?? {};\n const profileVariableKeys = Object.values(profilesBlock).map((p) =>\n Object.keys(p.variables ?? {}),\n );\n const report = variablesNs.checkVariableReferences({\n files: fileContents,\n deploymentProfileVariables: profileVariableKeys,\n });\n if (report.undeclared.length > 0) {\n const msg = variablesNs.formatCheckVariablesReport(report);\n process.stdout.write(msg + '\\n');\n process.exitCode = 1;\n return;\n }\n logger.dim(` Variables: ${report.references.length} reference(s), all declared.`);\n }\n\n if (!opts.references) return;\n\n logger.dim('');\n logger.dim('Resolving references…');\n const model = await project.parseProjectModel(loaded);\n const report = validateNs.validateProject(model, {\n source: String(opts.project),\n minSeverity: opts.minSeverity as validateNs.BuildSeverity,\n columns: Boolean(opts.columns),\n });\n\n const fmt = String(opts.format ?? 'table').toLowerCase();\n const payload =\n fmt === 'json' ? JSON.stringify(report, null, 2) : validateNs.formatBuildReport(report);\n if (opts.out) {\n const out = path.resolve(String(opts.out));\n await fs.mkdir(path.dirname(out), { recursive: true });\n await fs.writeFile(out, payload + (payload.endsWith('\\n') ? '' : '\\n'), 'utf8');\n logger.dim(`Wrote ${out} (${payload.length} bytes, ${report.findings.length} finding(s)).`);\n } else {\n process.stdout.write(payload + (payload.endsWith('\\n') ? '' : '\\n'));\n }\n\n // Non-zero exit when at least one ERROR-severity finding fires so\n // `sdt validate --references` can gate a CI step.\n if (report.totals.error > 0) process.exitCode = 1;\n });\n attachRelatedOptions(cmd, [\n 'compare.ignoreCase',\n 'compare.ignoreComments',\n 'compare.ignoreFormattingDifferences',\n ]);\n return cmd;\n}\n"],"mappings":"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","names":["report"]}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import "./chunk-DGUM43GV.js";
|
|
2
|
+
|
|
3
|
+
// src/commands/verify.ts
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
import { pac } from "@sdt-tools/core";
|
|
6
|
+
function verifyCommand() {
|
|
7
|
+
const cmd = new Command("verify");
|
|
8
|
+
cmd.description("Recompute checksums on a .sdtpac and confirm every object matches the manifest.").requiredOption("--pac <path>", "Path to the .sdtpac to verify.").option("--json", "Emit JSON instead of human-readable output.", false).action(async (opts) => {
|
|
9
|
+
const contents = await pac.readPac(String(opts.pac));
|
|
10
|
+
const expected = contents.checksums;
|
|
11
|
+
const actual = pac.computeChecksums(contents.model);
|
|
12
|
+
const missing = [];
|
|
13
|
+
const mismatched = [];
|
|
14
|
+
const orphaned = [];
|
|
15
|
+
const expectedIds = new Set(Object.keys(expected));
|
|
16
|
+
const actualIds = new Set(Object.keys(actual));
|
|
17
|
+
for (const id of expectedIds) {
|
|
18
|
+
if (!actualIds.has(id)) {
|
|
19
|
+
missing.push(id);
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
if (expected[id] !== actual[id]) {
|
|
23
|
+
mismatched.push({ id, expected: expected[id], actual: actual[id] });
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
for (const id of actualIds) {
|
|
27
|
+
if (!expectedIds.has(id)) orphaned.push(id);
|
|
28
|
+
}
|
|
29
|
+
const ok = missing.length === 0 && mismatched.length === 0 && orphaned.length === 0;
|
|
30
|
+
if (opts.json) {
|
|
31
|
+
console.log(
|
|
32
|
+
JSON.stringify(
|
|
33
|
+
{ ok, missing, mismatched, orphaned, objectCount: contents.model.length },
|
|
34
|
+
null,
|
|
35
|
+
2
|
|
36
|
+
)
|
|
37
|
+
);
|
|
38
|
+
} else {
|
|
39
|
+
console.log(`Pac: ${opts.pac}`);
|
|
40
|
+
console.log(
|
|
41
|
+
`Project: ${contents.manifest.projectName} v${contents.manifest.projectVersion}`
|
|
42
|
+
);
|
|
43
|
+
console.log(
|
|
44
|
+
`Built: ${contents.manifest.builtAt} by ${contents.manifest.builtBy.name} ${contents.manifest.builtBy.version}`
|
|
45
|
+
);
|
|
46
|
+
console.log(`Objects: ${contents.model.length}`);
|
|
47
|
+
console.log("");
|
|
48
|
+
if (ok) {
|
|
49
|
+
console.log("\u2713 verify OK \u2014 every checksum matches.");
|
|
50
|
+
} else {
|
|
51
|
+
console.log("\u2717 verify FAILED");
|
|
52
|
+
if (missing.length > 0) {
|
|
53
|
+
console.log(` missing checksums (${missing.length}):`);
|
|
54
|
+
for (const id of missing) console.log(` ${id}`);
|
|
55
|
+
}
|
|
56
|
+
if (mismatched.length > 0) {
|
|
57
|
+
console.log(` mismatched (${mismatched.length}):`);
|
|
58
|
+
for (const m of mismatched)
|
|
59
|
+
console.log(
|
|
60
|
+
` ${m.id} expected=${m.expected.slice(0, 12)}\u2026 actual=${m.actual.slice(0, 12)}\u2026`
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
if (orphaned.length > 0) {
|
|
64
|
+
console.log(` orphaned (in model but not in checksums) (${orphaned.length}):`);
|
|
65
|
+
for (const id of orphaned) console.log(` ${id}`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (!ok) process.exitCode = 1;
|
|
70
|
+
});
|
|
71
|
+
return cmd;
|
|
72
|
+
}
|
|
73
|
+
export {
|
|
74
|
+
verifyCommand
|
|
75
|
+
};
|
|
76
|
+
//# sourceMappingURL=verify-KXVASEEG.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/verify.ts"],"sourcesContent":["import { Command } from 'commander';\nimport { pac } from '@sdt-tools/core';\n\n/**\n * `sdt verify <pac>` — recompute checksums and confirm the .sdtpac is\n * intact. Catches tampering / corruption / partial-extraction\n * accidents. Sets up the Enterprise-tier signing chain — a signed pac\n * is `verify` + `signature-check` together.\n */\nexport function verifyCommand(): Command {\n const cmd = new Command('verify');\n cmd\n .description('Recompute checksums on a .sdtpac and confirm every object matches the manifest.')\n .requiredOption('--pac <path>', 'Path to the .sdtpac to verify.')\n .option('--json', 'Emit JSON instead of human-readable output.', false)\n .action(async (opts) => {\n const contents = await pac.readPac(String(opts.pac));\n const expected = contents.checksums;\n const actual = pac.computeChecksums(contents.model);\n\n const missing: string[] = [];\n const mismatched: Array<{ id: string; expected: string; actual: string }> = [];\n const orphaned: string[] = [];\n\n const expectedIds = new Set(Object.keys(expected));\n const actualIds = new Set(Object.keys(actual));\n for (const id of expectedIds) {\n if (!actualIds.has(id)) {\n missing.push(id);\n continue;\n }\n if (expected[id] !== actual[id]) {\n mismatched.push({ id, expected: expected[id]!, actual: actual[id]! });\n }\n }\n for (const id of actualIds) {\n if (!expectedIds.has(id)) orphaned.push(id);\n }\n\n const ok = missing.length === 0 && mismatched.length === 0 && orphaned.length === 0;\n if (opts.json) {\n console.log(\n JSON.stringify(\n { ok, missing, mismatched, orphaned, objectCount: contents.model.length },\n null,\n 2,\n ),\n );\n } else {\n console.log(`Pac: ${opts.pac}`);\n console.log(\n `Project: ${contents.manifest.projectName} v${contents.manifest.projectVersion}`,\n );\n console.log(\n `Built: ${contents.manifest.builtAt} by ${contents.manifest.builtBy.name} ${contents.manifest.builtBy.version}`,\n );\n console.log(`Objects: ${contents.model.length}`);\n console.log('');\n if (ok) {\n console.log('✓ verify OK — every checksum matches.');\n } else {\n console.log('✗ verify FAILED');\n if (missing.length > 0) {\n console.log(` missing checksums (${missing.length}):`);\n for (const id of missing) console.log(` ${id}`);\n }\n if (mismatched.length > 0) {\n console.log(` mismatched (${mismatched.length}):`);\n for (const m of mismatched)\n console.log(\n ` ${m.id} expected=${m.expected.slice(0, 12)}… actual=${m.actual.slice(0, 12)}…`,\n );\n }\n if (orphaned.length > 0) {\n console.log(` orphaned (in model but not in checksums) (${orphaned.length}):`);\n for (const id of orphaned) console.log(` ${id}`);\n }\n }\n }\n if (!ok) process.exitCode = 1;\n });\n return cmd;\n}\n"],"mappings":";;;AAAA,SAAS,eAAe;AACxB,SAAS,WAAW;AAQb,SAAS,gBAAyB;AACvC,QAAM,MAAM,IAAI,QAAQ,QAAQ;AAChC,MACG,YAAY,iFAAiF,EAC7F,eAAe,gBAAgB,gCAAgC,EAC/D,OAAO,UAAU,+CAA+C,KAAK,EACrE,OAAO,OAAO,SAAS;AACtB,UAAM,WAAW,MAAM,IAAI,QAAQ,OAAO,KAAK,GAAG,CAAC;AACnD,UAAM,WAAW,SAAS;AAC1B,UAAM,SAAS,IAAI,iBAAiB,SAAS,KAAK;AAElD,UAAM,UAAoB,CAAC;AAC3B,UAAM,aAAsE,CAAC;AAC7E,UAAM,WAAqB,CAAC;AAE5B,UAAM,cAAc,IAAI,IAAI,OAAO,KAAK,QAAQ,CAAC;AACjD,UAAM,YAAY,IAAI,IAAI,OAAO,KAAK,MAAM,CAAC;AAC7C,eAAW,MAAM,aAAa;AAC5B,UAAI,CAAC,UAAU,IAAI,EAAE,GAAG;AACtB,gBAAQ,KAAK,EAAE;AACf;AAAA,MACF;AACA,UAAI,SAAS,EAAE,MAAM,OAAO,EAAE,GAAG;AAC/B,mBAAW,KAAK,EAAE,IAAI,UAAU,SAAS,EAAE,GAAI,QAAQ,OAAO,EAAE,EAAG,CAAC;AAAA,MACtE;AAAA,IACF;AACA,eAAW,MAAM,WAAW;AAC1B,UAAI,CAAC,YAAY,IAAI,EAAE,EAAG,UAAS,KAAK,EAAE;AAAA,IAC5C;AAEA,UAAM,KAAK,QAAQ,WAAW,KAAK,WAAW,WAAW,KAAK,SAAS,WAAW;AAClF,QAAI,KAAK,MAAM;AACb,cAAQ;AAAA,QACN,KAAK;AAAA,UACH,EAAE,IAAI,SAAS,YAAY,UAAU,aAAa,SAAS,MAAM,OAAO;AAAA,UACxE;AAAA,UACA;AAAA,QACF;AAAA,MACF;AAAA,IACF,OAAO;AACL,cAAQ,IAAI,cAAc,KAAK,GAAG,EAAE;AACpC,cAAQ;AAAA,QACN,cAAc,SAAS,SAAS,WAAW,KAAK,SAAS,SAAS,cAAc;AAAA,MAClF;AACA,cAAQ;AAAA,QACN,cAAc,SAAS,SAAS,OAAO,OAAO,SAAS,SAAS,QAAQ,IAAI,IAAI,SAAS,SAAS,QAAQ,OAAO;AAAA,MACnH;AACA,cAAQ,IAAI,cAAc,SAAS,MAAM,MAAM,EAAE;AACjD,cAAQ,IAAI,EAAE;AACd,UAAI,IAAI;AACN,gBAAQ,IAAI,iDAAuC;AAAA,MACrD,OAAO;AACL,gBAAQ,IAAI,sBAAiB;AAC7B,YAAI,QAAQ,SAAS,GAAG;AACtB,kBAAQ,IAAI,wBAAwB,QAAQ,MAAM,IAAI;AACtD,qBAAW,MAAM,QAAS,SAAQ,IAAI,OAAO,EAAE,EAAE;AAAA,QACnD;AACA,YAAI,WAAW,SAAS,GAAG;AACzB,kBAAQ,IAAI,iBAAiB,WAAW,MAAM,IAAI;AAClD,qBAAW,KAAK;AACd,oBAAQ;AAAA,cACN,OAAO,EAAE,EAAE,cAAc,EAAE,SAAS,MAAM,GAAG,EAAE,CAAC,iBAAY,EAAE,OAAO,MAAM,GAAG,EAAE,CAAC;AAAA,YACnF;AAAA,QACJ;AACA,YAAI,SAAS,SAAS,GAAG;AACvB,kBAAQ,IAAI,+CAA+C,SAAS,MAAM,IAAI;AAC9E,qBAAW,MAAM,SAAU,SAAQ,IAAI,OAAO,EAAE,EAAE;AAAA,QACpD;AAAA,MACF;AAAA,IACF;AACA,QAAI,CAAC,GAAI,SAAQ,WAAW;AAAA,EAC9B,CAAC;AACH,SAAO;AACT;","names":[]}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import "./chunk-DGUM43GV.js";
|
|
2
|
+
|
|
3
|
+
// src/commands/watch.ts
|
|
4
|
+
import { promises as fs } from "fs";
|
|
5
|
+
import path from "path";
|
|
6
|
+
import { Command } from "commander";
|
|
7
|
+
import { platformWatch } from "@sdt-tools/core";
|
|
8
|
+
var DEFAULT_SOURCE = "snowflake";
|
|
9
|
+
function watchCommand() {
|
|
10
|
+
const cmd = new Command("watch");
|
|
11
|
+
cmd.description(
|
|
12
|
+
"Platform-feature drift detector. Diffs release-notes markdown against a cached snapshot."
|
|
13
|
+
);
|
|
14
|
+
cmd.command("bulletin").description(
|
|
15
|
+
"Compose a monthly bulletin: new drift (needs BACKLOG action), new covered, removed. Reads --prior snapshot JSON + --current release-notes markdown."
|
|
16
|
+
).requiredOption("--current <path>", "Path to current release-notes markdown.").option("--prior <path>", "Path to prior snapshot JSON (defaults to empty prior).").option("--rules <path>", "Path to cluster-match rules JSON (ClusterMatchRule[]).").option("--overrides <path>", "Path to release-note-id \u2192 cluster override JSON.").option("--source <label>", "Source label written into a saved snapshot.", DEFAULT_SOURCE).option("--format <fmt>", "json | markdown (default markdown).", "markdown").option("--save-snapshot <path>", "Also write the parsed current set as a fresh snapshot.").action(async (opts) => {
|
|
17
|
+
const currentMarkdown = await fs.readFile(path.resolve(opts.current), "utf8");
|
|
18
|
+
const currentNotes = platformWatch.parseReleaseNotesMarkdown(currentMarkdown);
|
|
19
|
+
const priorNotes = opts.prior ? await readPriorSnapshot(path.resolve(opts.prior)) : [];
|
|
20
|
+
const rules = opts.rules ? await readClusterRules(path.resolve(opts.rules)) : [];
|
|
21
|
+
const overrides = opts.overrides ? await readOverrides(path.resolve(opts.overrides)) : void 0;
|
|
22
|
+
const bulletin = platformWatch.buildMonthlyBulletin({
|
|
23
|
+
prior: priorNotes,
|
|
24
|
+
current: currentNotes,
|
|
25
|
+
rules,
|
|
26
|
+
overrides
|
|
27
|
+
});
|
|
28
|
+
const fmt = String(opts.format ?? "markdown").toLowerCase();
|
|
29
|
+
if (fmt === "json") {
|
|
30
|
+
process.stdout.write(JSON.stringify(bulletin, null, 2) + "\n");
|
|
31
|
+
} else if (fmt === "markdown") {
|
|
32
|
+
process.stdout.write(platformWatch.renderBulletinMarkdown(bulletin) + "\n");
|
|
33
|
+
} else {
|
|
34
|
+
throw new Error(`Unknown --format: ${opts.format}. Use json | markdown.`);
|
|
35
|
+
}
|
|
36
|
+
if (opts.saveSnapshot) {
|
|
37
|
+
const snapshot = platformWatch.serializeReleaseNotesSnapshot(
|
|
38
|
+
String(opts.source ?? DEFAULT_SOURCE),
|
|
39
|
+
currentNotes
|
|
40
|
+
);
|
|
41
|
+
await fs.writeFile(
|
|
42
|
+
path.resolve(opts.saveSnapshot),
|
|
43
|
+
JSON.stringify(snapshot, null, 2) + "\n",
|
|
44
|
+
"utf8"
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
return cmd;
|
|
49
|
+
}
|
|
50
|
+
async function readPriorSnapshot(filePath) {
|
|
51
|
+
const raw = await fs.readFile(filePath, "utf8");
|
|
52
|
+
const parsed = JSON.parse(raw);
|
|
53
|
+
const snapshot = platformWatch.deserializeReleaseNotesSnapshot(parsed);
|
|
54
|
+
if (snapshot === null) {
|
|
55
|
+
throw new Error(
|
|
56
|
+
`Prior snapshot at ${filePath} is malformed or carries an unsupported version. Pass a fresh --prior or omit the flag.`
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
return snapshot.notes;
|
|
60
|
+
}
|
|
61
|
+
async function readClusterRules(filePath) {
|
|
62
|
+
const raw = await fs.readFile(filePath, "utf8");
|
|
63
|
+
const parsed = JSON.parse(raw);
|
|
64
|
+
if (!Array.isArray(parsed)) {
|
|
65
|
+
throw new Error(`--rules at ${filePath} must be a JSON array of ClusterMatchRule.`);
|
|
66
|
+
}
|
|
67
|
+
return parsed;
|
|
68
|
+
}
|
|
69
|
+
async function readOverrides(filePath) {
|
|
70
|
+
const raw = await fs.readFile(filePath, "utf8");
|
|
71
|
+
const parsed = JSON.parse(raw);
|
|
72
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
73
|
+
throw new Error(`--overrides at ${filePath} must be a JSON object of {id: cluster}.`);
|
|
74
|
+
}
|
|
75
|
+
return parsed;
|
|
76
|
+
}
|
|
77
|
+
export {
|
|
78
|
+
watchCommand
|
|
79
|
+
};
|
|
80
|
+
//# sourceMappingURL=watch-I6K4BNMA.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/watch.ts"],"sourcesContent":["/**\n * `sdt watch` — platform-feature drift bulletin (WATCH.2-paired).\n *\n * Wires the shipped `@sdt-tools/core/platformWatch` substrate trio (WATCH.1\n * parse+diff+persist, WATCH.5 coverage, WATCH.4 bulletin) into a host-\n * level CLI: read prior snapshot JSON + current release-notes markdown\n * from disk, run `buildMonthlyBulletin`, emit JSON or rendered Markdown.\n * Optional `--save-snapshot` writes the parsed current set to disk so\n * the next run has a fresh prior.\n *\n * Pure file I/O — the substrate stays I/O-free; the CLI owns the disk\n * boundary per the substrate's design contract.\n *\n * Mirrors `Databricks/packages/cli/src/commands/watch.ts`.\n */\nimport { promises as fs } from 'node:fs';\nimport path from 'node:path';\nimport { Command } from 'commander';\nimport { platformWatch } from '@sdt-tools/core';\n\nconst DEFAULT_SOURCE = 'snowflake';\n\nexport function watchCommand(): Command {\n const cmd = new Command('watch');\n cmd.description(\n 'Platform-feature drift detector. Diffs release-notes markdown against a cached snapshot.',\n );\n\n cmd\n .command('bulletin')\n .description(\n 'Compose a monthly bulletin: new drift (needs BACKLOG action), new covered, removed. Reads --prior snapshot JSON + --current release-notes markdown.',\n )\n .requiredOption('--current <path>', 'Path to current release-notes markdown.')\n .option('--prior <path>', 'Path to prior snapshot JSON (defaults to empty prior).')\n .option('--rules <path>', 'Path to cluster-match rules JSON (ClusterMatchRule[]).')\n .option('--overrides <path>', 'Path to release-note-id → cluster override JSON.')\n .option('--source <label>', 'Source label written into a saved snapshot.', DEFAULT_SOURCE)\n .option('--format <fmt>', 'json | markdown (default markdown).', 'markdown')\n .option('--save-snapshot <path>', 'Also write the parsed current set as a fresh snapshot.')\n .action(async (opts) => {\n const currentMarkdown = await fs.readFile(path.resolve(opts.current), 'utf8');\n const currentNotes = platformWatch.parseReleaseNotesMarkdown(currentMarkdown);\n\n const priorNotes = opts.prior ? await readPriorSnapshot(path.resolve(opts.prior)) : [];\n const rules = opts.rules ? await readClusterRules(path.resolve(opts.rules)) : [];\n const overrides = opts.overrides\n ? await readOverrides(path.resolve(opts.overrides))\n : undefined;\n\n const bulletin = platformWatch.buildMonthlyBulletin({\n prior: priorNotes,\n current: currentNotes,\n rules,\n overrides,\n });\n\n const fmt = String(opts.format ?? 'markdown').toLowerCase();\n if (fmt === 'json') {\n process.stdout.write(JSON.stringify(bulletin, null, 2) + '\\n');\n } else if (fmt === 'markdown') {\n process.stdout.write(platformWatch.renderBulletinMarkdown(bulletin) + '\\n');\n } else {\n throw new Error(`Unknown --format: ${opts.format}. Use json | markdown.`);\n }\n\n if (opts.saveSnapshot) {\n const snapshot = platformWatch.serializeReleaseNotesSnapshot(\n String(opts.source ?? DEFAULT_SOURCE),\n currentNotes,\n );\n await fs.writeFile(\n path.resolve(opts.saveSnapshot),\n JSON.stringify(snapshot, null, 2) + '\\n',\n 'utf8',\n );\n }\n });\n\n return cmd;\n}\n\nasync function readPriorSnapshot(filePath: string): Promise<readonly platformWatch.ReleaseNote[]> {\n const raw = await fs.readFile(filePath, 'utf8');\n const parsed: unknown = JSON.parse(raw);\n const snapshot = platformWatch.deserializeReleaseNotesSnapshot(parsed);\n if (snapshot === null) {\n throw new Error(\n `Prior snapshot at ${filePath} is malformed or carries an unsupported version. Pass a fresh --prior or omit the flag.`,\n );\n }\n return snapshot.notes;\n}\n\nasync function readClusterRules(\n filePath: string,\n): Promise<readonly platformWatch.ClusterMatchRule[]> {\n const raw = await fs.readFile(filePath, 'utf8');\n const parsed: unknown = JSON.parse(raw);\n if (!Array.isArray(parsed)) {\n throw new Error(`--rules at ${filePath} must be a JSON array of ClusterMatchRule.`);\n }\n return parsed as platformWatch.ClusterMatchRule[];\n}\n\nasync function readOverrides(filePath: string): Promise<platformWatch.ReleaseNoteOverride> {\n const raw = await fs.readFile(filePath, 'utf8');\n const parsed: unknown = JSON.parse(raw);\n if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {\n throw new Error(`--overrides at ${filePath} must be a JSON object of {id: cluster}.`);\n }\n return parsed as platformWatch.ReleaseNoteOverride;\n}\n"],"mappings":";;;AAeA,SAAS,YAAY,UAAU;AAC/B,OAAO,UAAU;AACjB,SAAS,eAAe;AACxB,SAAS,qBAAqB;AAE9B,IAAM,iBAAiB;AAEhB,SAAS,eAAwB;AACtC,QAAM,MAAM,IAAI,QAAQ,OAAO;AAC/B,MAAI;AAAA,IACF;AAAA,EACF;AAEA,MACG,QAAQ,UAAU,EAClB;AAAA,IACC;AAAA,EACF,EACC,eAAe,oBAAoB,yCAAyC,EAC5E,OAAO,kBAAkB,wDAAwD,EACjF,OAAO,kBAAkB,wDAAwD,EACjF,OAAO,sBAAsB,uDAAkD,EAC/E,OAAO,oBAAoB,+CAA+C,cAAc,EACxF,OAAO,kBAAkB,uCAAuC,UAAU,EAC1E,OAAO,0BAA0B,wDAAwD,EACzF,OAAO,OAAO,SAAS;AACtB,UAAM,kBAAkB,MAAM,GAAG,SAAS,KAAK,QAAQ,KAAK,OAAO,GAAG,MAAM;AAC5E,UAAM,eAAe,cAAc,0BAA0B,eAAe;AAE5E,UAAM,aAAa,KAAK,QAAQ,MAAM,kBAAkB,KAAK,QAAQ,KAAK,KAAK,CAAC,IAAI,CAAC;AACrF,UAAM,QAAQ,KAAK,QAAQ,MAAM,iBAAiB,KAAK,QAAQ,KAAK,KAAK,CAAC,IAAI,CAAC;AAC/E,UAAM,YAAY,KAAK,YACnB,MAAM,cAAc,KAAK,QAAQ,KAAK,SAAS,CAAC,IAChD;AAEJ,UAAM,WAAW,cAAc,qBAAqB;AAAA,MAClD,OAAO;AAAA,MACP,SAAS;AAAA,MACT;AAAA,MACA;AAAA,IACF,CAAC;AAED,UAAM,MAAM,OAAO,KAAK,UAAU,UAAU,EAAE,YAAY;AAC1D,QAAI,QAAQ,QAAQ;AAClB,cAAQ,OAAO,MAAM,KAAK,UAAU,UAAU,MAAM,CAAC,IAAI,IAAI;AAAA,IAC/D,WAAW,QAAQ,YAAY;AAC7B,cAAQ,OAAO,MAAM,cAAc,uBAAuB,QAAQ,IAAI,IAAI;AAAA,IAC5E,OAAO;AACL,YAAM,IAAI,MAAM,qBAAqB,KAAK,MAAM,wBAAwB;AAAA,IAC1E;AAEA,QAAI,KAAK,cAAc;AACrB,YAAM,WAAW,cAAc;AAAA,QAC7B,OAAO,KAAK,UAAU,cAAc;AAAA,QACpC;AAAA,MACF;AACA,YAAM,GAAG;AAAA,QACP,KAAK,QAAQ,KAAK,YAAY;AAAA,QAC9B,KAAK,UAAU,UAAU,MAAM,CAAC,IAAI;AAAA,QACpC;AAAA,MACF;AAAA,IACF;AAAA,EACF,CAAC;AAEH,SAAO;AACT;AAEA,eAAe,kBAAkB,UAAiE;AAChG,QAAM,MAAM,MAAM,GAAG,SAAS,UAAU,MAAM;AAC9C,QAAM,SAAkB,KAAK,MAAM,GAAG;AACtC,QAAM,WAAW,cAAc,gCAAgC,MAAM;AACrE,MAAI,aAAa,MAAM;AACrB,UAAM,IAAI;AAAA,MACR,qBAAqB,QAAQ;AAAA,IAC/B;AAAA,EACF;AACA,SAAO,SAAS;AAClB;AAEA,eAAe,iBACb,UACoD;AACpD,QAAM,MAAM,MAAM,GAAG,SAAS,UAAU,MAAM;AAC9C,QAAM,SAAkB,KAAK,MAAM,GAAG;AACtC,MAAI,CAAC,MAAM,QAAQ,MAAM,GAAG;AAC1B,UAAM,IAAI,MAAM,cAAc,QAAQ,4CAA4C;AAAA,EACpF;AACA,SAAO;AACT;AAEA,eAAe,cAAc,UAA8D;AACzF,QAAM,MAAM,MAAM,GAAG,SAAS,UAAU,MAAM;AAC9C,QAAM,SAAkB,KAAK,MAAM,GAAG;AACtC,MAAI,WAAW,QAAQ,OAAO,WAAW,YAAY,MAAM,QAAQ,MAAM,GAAG;AAC1E,UAAM,IAAI,MAAM,kBAAkB,QAAQ,0CAA0C;AAAA,EACtF;AACA,SAAO;AACT;","names":[]}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import {
|
|
2
|
+
logger
|
|
3
|
+
} from "./chunk-VM2H4LAO.js";
|
|
4
|
+
import "./chunk-DGUM43GV.js";
|
|
5
|
+
|
|
6
|
+
// src/commands/xcompare.ts
|
|
7
|
+
import { Command } from "commander";
|
|
8
|
+
import { promises as fs } from "fs";
|
|
9
|
+
import {
|
|
10
|
+
classifyCrossPlatformDeploy,
|
|
11
|
+
dispatchAll,
|
|
12
|
+
filterAssessmentByTier,
|
|
13
|
+
formatCrossPlatformAssessment,
|
|
14
|
+
isCrossPlatformDeployBlocked,
|
|
15
|
+
isXpmTier
|
|
16
|
+
} from "@sdt-tools/core/migrate";
|
|
17
|
+
function xcompareCommand() {
|
|
18
|
+
const cmd = new Command("xcompare-ir");
|
|
19
|
+
cmd.description(
|
|
20
|
+
"Cross-platform IR compare (Snowflake \u2194 Databricks). Maps source IRs through XPM-Deep substrate; emits tier-gated safety assessment. IR-level only (live-warehouse compare is a follow-up)."
|
|
21
|
+
);
|
|
22
|
+
cmd.requiredOption("--source <path>", "JSON file with an array of XpmObjectIr source objects.").option("--tier <tier>", "License tier: free | pro | team | enterprise.", "free").option("--format <format>", "Output format: markdown | json.", "markdown").option("--allow-destructive", "Permit DESTRUCTIVE verdicts (exit 0 instead of 2).", false).option(
|
|
23
|
+
"--allow-unmappable",
|
|
24
|
+
"Permit UNRECOVERABLE verdicts (exit 0 instead of 2). Implies --allow-destructive.",
|
|
25
|
+
false
|
|
26
|
+
).action(
|
|
27
|
+
async (opts) => {
|
|
28
|
+
if (!isXpmTier(opts.tier)) {
|
|
29
|
+
logger.error(
|
|
30
|
+
`Unknown --tier "${opts.tier}". Use one of: free | pro | team | enterprise.`
|
|
31
|
+
);
|
|
32
|
+
process.exitCode = 1;
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const tier = opts.tier;
|
|
36
|
+
const format = (opts.format ?? "markdown").toLowerCase();
|
|
37
|
+
if (format !== "markdown" && format !== "json") {
|
|
38
|
+
logger.error(`Unknown --format "${opts.format}". Use markdown | json.`);
|
|
39
|
+
process.exitCode = 1;
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
let raw;
|
|
43
|
+
try {
|
|
44
|
+
raw = await fs.readFile(opts.source, "utf-8");
|
|
45
|
+
} catch (e) {
|
|
46
|
+
logger.error(`Cannot read --source file '${opts.source}': ${e.message}`);
|
|
47
|
+
process.exitCode = 1;
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
let parsed;
|
|
51
|
+
try {
|
|
52
|
+
parsed = JSON.parse(raw);
|
|
53
|
+
} catch (e) {
|
|
54
|
+
logger.error(`--source file is not valid JSON: ${e.message}`);
|
|
55
|
+
process.exitCode = 1;
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
if (!Array.isArray(parsed)) {
|
|
59
|
+
logger.error(
|
|
60
|
+
`--source file must contain a JSON array of XpmObjectIr objects (got ${typeof parsed}).`
|
|
61
|
+
);
|
|
62
|
+
process.exitCode = 1;
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
const sources = parsed;
|
|
66
|
+
const dispatched = dispatchAll(sources);
|
|
67
|
+
const assessment = classifyCrossPlatformDeploy(dispatched);
|
|
68
|
+
const tierFiltered = filterAssessmentByTier(assessment, tier);
|
|
69
|
+
if (format === "json") {
|
|
70
|
+
process.stdout.write(JSON.stringify(tierFiltered, null, 2) + "\n");
|
|
71
|
+
} else {
|
|
72
|
+
process.stdout.write(formatCrossPlatformAssessment(tierFiltered) + "\n");
|
|
73
|
+
}
|
|
74
|
+
if (isCrossPlatformDeployBlocked(assessment, {
|
|
75
|
+
destructiveOverride: opts.allowDestructive || opts.allowUnmappable,
|
|
76
|
+
unmappableOverride: opts.allowUnmappable
|
|
77
|
+
})) {
|
|
78
|
+
process.exitCode = 2;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
);
|
|
82
|
+
return cmd;
|
|
83
|
+
}
|
|
84
|
+
export {
|
|
85
|
+
xcompareCommand
|
|
86
|
+
};
|
|
87
|
+
//# sourceMappingURL=xcompare-TPFLQO6W.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/xcompare.ts"],"sourcesContent":["/**\n * `sdt xcompare-ir` — Cross-platform IR compare (XPM.31b).\n *\n * Reads a JSON file of source-platform canonical IRs (typically the\n * Snowflake side; reverse-direction inputs are also accepted), runs\n * every IR through the XPM.31a dispatcher, rolls findings up with the\n * XPM.30 cross-platform safety classifier, applies the XPM.32 tier\n * gate, and prints the result.\n *\n * NOTE: this is the IR-level entry point — it does NOT connect to a\n * live warehouse. Live cross-platform compare against a `<ddt-profile>`\n * is a follow-up that requires both halves of the engine to share\n * extractor state (deferred until paid-warehouse access is available\n * for live integration testing).\n *\n * Usage:\n *\n * sdt xcompare-ir --source schema.json \\\n * --tier pro \\\n * --format markdown\n *\n * sdt xcompare-ir --source schema.json \\\n * --tier enterprise \\\n * --format json \\\n * --allow-destructive --allow-unmappable\n *\n * Input JSON shape: array of `XpmObjectIr` (see `@xpm/migrate/canonicalIr`).\n *\n * Exit codes:\n * 0 — assessment is OK / INFO / DESTRUCTIVE-with-override / UNRECOVERABLE-with-override.\n * 2 — assessment is DESTRUCTIVE without --allow-destructive, or\n * UNRECOVERABLE without --allow-unmappable.\n * 1 — invalid input / file not found / parse error.\n */\nimport { Command } from 'commander';\nimport { promises as fs } from 'node:fs';\nimport {\n classifyCrossPlatformDeploy,\n dispatchAll,\n filterAssessmentByTier,\n formatCrossPlatformAssessment,\n isCrossPlatformDeployBlocked,\n isXpmTier,\n type XpmTier,\n} from '@sdt-tools/core/migrate';\nimport type { XpmObjectIr } from '@sdt-tools/core/migrate';\nimport { logger } from '../util/logger.js';\n\nexport function xcompareCommand(): Command {\n const cmd = new Command('xcompare-ir');\n cmd.description(\n 'Cross-platform IR compare (Snowflake ↔ Databricks). Maps source IRs through XPM-Deep substrate; emits tier-gated safety assessment. IR-level only (live-warehouse compare is a follow-up).',\n );\n\n cmd\n .requiredOption('--source <path>', 'JSON file with an array of XpmObjectIr source objects.')\n .option('--tier <tier>', 'License tier: free | pro | team | enterprise.', 'free')\n .option('--format <format>', 'Output format: markdown | json.', 'markdown')\n .option('--allow-destructive', 'Permit DESTRUCTIVE verdicts (exit 0 instead of 2).', false)\n .option(\n '--allow-unmappable',\n 'Permit UNRECOVERABLE verdicts (exit 0 instead of 2). Implies --allow-destructive.',\n false,\n )\n .action(\n async (opts: {\n source: string;\n tier: string;\n format: string;\n allowDestructive: boolean;\n allowUnmappable: boolean;\n }) => {\n if (!isXpmTier(opts.tier)) {\n logger.error(\n `Unknown --tier \"${opts.tier}\". Use one of: free | pro | team | enterprise.`,\n );\n process.exitCode = 1;\n return;\n }\n const tier: XpmTier = opts.tier;\n const format = (opts.format ?? 'markdown').toLowerCase();\n if (format !== 'markdown' && format !== 'json') {\n logger.error(`Unknown --format \"${opts.format}\". Use markdown | json.`);\n process.exitCode = 1;\n return;\n }\n\n let raw: string;\n try {\n raw = await fs.readFile(opts.source, 'utf-8');\n } catch (e) {\n logger.error(`Cannot read --source file '${opts.source}': ${(e as Error).message}`);\n process.exitCode = 1;\n return;\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch (e) {\n logger.error(`--source file is not valid JSON: ${(e as Error).message}`);\n process.exitCode = 1;\n return;\n }\n\n if (!Array.isArray(parsed)) {\n logger.error(\n `--source file must contain a JSON array of XpmObjectIr objects (got ${typeof parsed}).`,\n );\n process.exitCode = 1;\n return;\n }\n const sources = parsed as readonly XpmObjectIr[];\n\n const dispatched = dispatchAll(sources);\n const assessment = classifyCrossPlatformDeploy(dispatched);\n const tierFiltered = filterAssessmentByTier(assessment, tier);\n\n if (format === 'json') {\n process.stdout.write(JSON.stringify(tierFiltered, null, 2) + '\\n');\n } else {\n process.stdout.write(formatCrossPlatformAssessment(tierFiltered) + '\\n');\n }\n\n if (\n isCrossPlatformDeployBlocked(assessment, {\n destructiveOverride: opts.allowDestructive || opts.allowUnmappable,\n unmappableOverride: opts.allowUnmappable,\n })\n ) {\n process.exitCode = 2;\n }\n },\n );\n\n return cmd;\n}\n"],"mappings":";;;;;;AAkCA,SAAS,eAAe;AACxB,SAAS,YAAY,UAAU;AAC/B;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AAIA,SAAS,kBAA2B;AACzC,QAAM,MAAM,IAAI,QAAQ,aAAa;AACrC,MAAI;AAAA,IACF;AAAA,EACF;AAEA,MACG,eAAe,mBAAmB,wDAAwD,EAC1F,OAAO,iBAAiB,iDAAiD,MAAM,EAC/E,OAAO,qBAAqB,mCAAmC,UAAU,EACzE,OAAO,uBAAuB,sDAAsD,KAAK,EACzF;AAAA,IACC;AAAA,IACA;AAAA,IACA;AAAA,EACF,EACC;AAAA,IACC,OAAO,SAMD;AACJ,UAAI,CAAC,UAAU,KAAK,IAAI,GAAG;AACzB,eAAO;AAAA,UACL,mBAAmB,KAAK,IAAI;AAAA,QAC9B;AACA,gBAAQ,WAAW;AACnB;AAAA,MACF;AACA,YAAM,OAAgB,KAAK;AAC3B,YAAM,UAAU,KAAK,UAAU,YAAY,YAAY;AACvD,UAAI,WAAW,cAAc,WAAW,QAAQ;AAC9C,eAAO,MAAM,qBAAqB,KAAK,MAAM,yBAAyB;AACtE,gBAAQ,WAAW;AACnB;AAAA,MACF;AAEA,UAAI;AACJ,UAAI;AACF,cAAM,MAAM,GAAG,SAAS,KAAK,QAAQ,OAAO;AAAA,MAC9C,SAAS,GAAG;AACV,eAAO,MAAM,8BAA8B,KAAK,MAAM,MAAO,EAAY,OAAO,EAAE;AAClF,gBAAQ,WAAW;AACnB;AAAA,MACF;AAEA,UAAI;AACJ,UAAI;AACF,iBAAS,KAAK,MAAM,GAAG;AAAA,MACzB,SAAS,GAAG;AACV,eAAO,MAAM,oCAAqC,EAAY,OAAO,EAAE;AACvE,gBAAQ,WAAW;AACnB;AAAA,MACF;AAEA,UAAI,CAAC,MAAM,QAAQ,MAAM,GAAG;AAC1B,eAAO;AAAA,UACL,uEAAuE,OAAO,MAAM;AAAA,QACtF;AACA,gBAAQ,WAAW;AACnB;AAAA,MACF;AACA,YAAM,UAAU;AAEhB,YAAM,aAAa,YAAY,OAAO;AACtC,YAAM,aAAa,4BAA4B,UAAU;AACzD,YAAM,eAAe,uBAAuB,YAAY,IAAI;AAE5D,UAAI,WAAW,QAAQ;AACrB,gBAAQ,OAAO,MAAM,KAAK,UAAU,cAAc,MAAM,CAAC,IAAI,IAAI;AAAA,MACnE,OAAO;AACL,gBAAQ,OAAO,MAAM,8BAA8B,YAAY,IAAI,IAAI;AAAA,MACzE;AAEA,UACE,6BAA6B,YAAY;AAAA,QACvC,qBAAqB,KAAK,oBAAoB,KAAK;AAAA,QACnD,oBAAoB,KAAK;AAAA,MAC3B,CAAC,GACD;AACA,gBAAQ,WAAW;AAAA,MACrB;AAAA,IACF;AAAA,EACF;AAEF,SAAO;AACT;","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sdt-tools/cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.5",
|
|
4
4
|
"description": "SDT command-line interface. `sdt` lets you extract, build, compare, and deploy Snowflake schemas.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Varol Consulting LLC",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"commander": "^14.0.0",
|
|
30
30
|
"ora": "^9.4.0",
|
|
31
31
|
"zod": "^4.4.3",
|
|
32
|
-
"@sdt-tools/core": "0.2.
|
|
32
|
+
"@sdt-tools/core": "0.2.5"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"@types/node": "^25.9.1",
|