@ddt-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-YNMKVJCD.js +87 -0
- package/dist/advise-tests-YNMKVJCD.js.map +1 -0
- package/dist/ai-NTNPYEKZ.js +86 -0
- package/dist/ai-NTNPYEKZ.js.map +1 -0
- package/dist/anonymize-LERTWUQO.js +139 -0
- package/dist/anonymize-LERTWUQO.js.map +1 -0
- package/dist/approval-GGZGKIU4.js +73 -0
- package/dist/approval-GGZGKIU4.js.map +1 -0
- package/dist/approval-chain-GWJKZHVU.js +118 -0
- package/dist/approval-chain-GWJKZHVU.js.map +1 -0
- package/dist/audit-log-2PH55BU4.js +159 -0
- package/dist/audit-log-2PH55BU4.js.map +1 -0
- package/dist/backlog-QNXGOUF4.js +76 -0
- package/dist/backlog-QNXGOUF4.js.map +1 -0
- package/dist/bisect-W3XKKRWG.js +111 -0
- package/dist/bisect-W3XKKRWG.js.map +1 -0
- package/dist/bookmarks-XVOGXGMC.js +107 -0
- package/dist/bookmarks-XVOGXGMC.js.map +1 -0
- package/dist/branch-S3I2IJGQ.js +103 -0
- package/dist/branch-S3I2IJGQ.js.map +1 -0
- package/dist/build-MP3JQEFO.js +20 -0
- package/dist/build-MP3JQEFO.js.map +1 -0
- package/dist/catalog-3J3NFNXP.js +137 -0
- package/dist/catalog-3J3NFNXP.js.map +1 -0
- package/dist/changelog-ZQAH3ULB.js +216 -0
- package/dist/changelog-ZQAH3ULB.js.map +1 -0
- package/dist/chunk-2FT6HXKS.js +55 -0
- package/dist/chunk-2FT6HXKS.js.map +1 -0
- package/dist/chunk-DGUM43GV.js +11 -0
- package/dist/chunk-DGUM43GV.js.map +1 -0
- package/dist/chunk-DL3V7UJ2.js +25 -0
- package/dist/chunk-DL3V7UJ2.js.map +1 -0
- package/dist/chunk-VM2H4LAO.js +15 -0
- package/dist/chunk-VM2H4LAO.js.map +1 -0
- package/dist/chunk-XFXG347C.js +40 -0
- package/dist/chunk-XFXG347C.js.map +1 -0
- package/dist/cli.js +504 -19402
- package/dist/cli.js.map +1 -1
- package/dist/compare-IOEATL6G.js +435 -0
- package/dist/compare-IOEATL6G.js.map +1 -0
- package/dist/compare-profiles-H33CXZPD.js +219 -0
- package/dist/compare-profiles-H33CXZPD.js.map +1 -0
- package/dist/completion-ZSNCQKJ2.js +89 -0
- package/dist/completion-ZSNCQKJ2.js.map +1 -0
- package/dist/connection-CDGVEFUC.js +148 -0
- package/dist/connection-CDGVEFUC.js.map +1 -0
- package/dist/cost-estimate-S2MKHT2H.js +321 -0
- package/dist/cost-estimate-S2MKHT2H.js.map +1 -0
- package/dist/data-compare-46ZI7KHL.js +128 -0
- package/dist/data-compare-46ZI7KHL.js.map +1 -0
- package/dist/data-fit-WGEPLD5S.js +127 -0
- package/dist/data-fit-WGEPLD5S.js.map +1 -0
- package/dist/deploy-status-4H5KJFRC.js +58 -0
- package/dist/deploy-status-4H5KJFRC.js.map +1 -0
- package/dist/design-ILX3ZSWW.js +135 -0
- package/dist/design-ILX3ZSWW.js.map +1 -0
- package/dist/diagnose-WPUL67E4.js +150 -0
- package/dist/diagnose-WPUL67E4.js.map +1 -0
- package/dist/discover-DEO2R5T6.js +78 -0
- package/dist/discover-DEO2R5T6.js.map +1 -0
- package/dist/docs-QNY3MUVO.js +183 -0
- package/dist/docs-QNY3MUVO.js.map +1 -0
- package/dist/drift-FDRNPWQA.js +233 -0
- package/dist/drift-FDRNPWQA.js.map +1 -0
- package/dist/drift-gate-6BWWWMHW.js +103 -0
- package/dist/drift-gate-6BWWWMHW.js.map +1 -0
- package/dist/error-lookup-4R3Y4RBC.js +56 -0
- package/dist/error-lookup-4R3Y4RBC.js.map +1 -0
- package/dist/errorReporting-LX6WT4JH.js +109 -0
- package/dist/errorReporting-LX6WT4JH.js.map +1 -0
- package/dist/exec-JOLH5LPT.js +122 -0
- package/dist/exec-JOLH5LPT.js.map +1 -0
- package/dist/explain-NS26WE2Y.js +189 -0
- package/dist/explain-NS26WE2Y.js.map +1 -0
- package/dist/explorer-GSYYYOAL.js +58 -0
- package/dist/explorer-GSYYYOAL.js.map +1 -0
- package/dist/extract-4LWEZG4O.js +152 -0
- package/dist/extract-4LWEZG4O.js.map +1 -0
- package/dist/features-KQV4OFIZ.js +54 -0
- package/dist/features-KQV4OFIZ.js.map +1 -0
- package/dist/feedback-CBLGXUEG.js +158 -0
- package/dist/feedback-CBLGXUEG.js.map +1 -0
- package/dist/find-SMXRCZ76.js +176 -0
- package/dist/find-SMXRCZ76.js.map +1 -0
- package/dist/format-HMGG6MY3.js +277 -0
- package/dist/format-HMGG6MY3.js.map +1 -0
- package/dist/generate-W7VLBDLI.js +160 -0
- package/dist/generate-W7VLBDLI.js.map +1 -0
- package/dist/graph-YYL5UYCJ.js +168 -0
- package/dist/graph-YYL5UYCJ.js.map +1 -0
- package/dist/history-GDRFP4PG.js +184 -0
- package/dist/history-GDRFP4PG.js.map +1 -0
- package/dist/hosts-DRFZTMIJ.js +45 -0
- package/dist/hosts-DRFZTMIJ.js.map +1 -0
- package/dist/impact-A4NU6CB2.js +63 -0
- package/dist/impact-A4NU6CB2.js.map +1 -0
- package/dist/import-EGOVKTLX.js +29 -0
- package/dist/import-EGOVKTLX.js.map +1 -0
- package/dist/import-script-R5RXPDH6.js +79 -0
- package/dist/import-script-R5RXPDH6.js.map +1 -0
- package/dist/index.cjs +11 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +8 -2
- package/dist/index.js.map +1 -1
- package/dist/init-EAOGNGXI.js +54 -0
- package/dist/init-EAOGNGXI.js.map +1 -0
- package/dist/install-hooks-G3Y5LVXK.js +109 -0
- package/dist/install-hooks-G3Y5LVXK.js.map +1 -0
- package/dist/license-Z5YSC7XQ.js +43 -0
- package/dist/license-Z5YSC7XQ.js.map +1 -0
- package/dist/lineage-C5CGVP36.js +555 -0
- package/dist/lineage-C5CGVP36.js.map +1 -0
- package/dist/lint-AQFPZ3WG.js +144 -0
- package/dist/lint-AQFPZ3WG.js.map +1 -0
- package/dist/mcp-6ZXOAF7S.js +343 -0
- package/dist/mcp-6ZXOAF7S.js.map +1 -0
- package/dist/migrate-from-dbt-K4ELOWUD.js +156 -0
- package/dist/migrate-from-dbt-K4ELOWUD.js.map +1 -0
- package/dist/migrate-platform-E7VZFPO5.js +91 -0
- package/dist/migrate-platform-E7VZFPO5.js.map +1 -0
- package/dist/optimize-WUJ5ZN5Y.js +109 -0
- package/dist/optimize-WUJ5ZN5Y.js.map +1 -0
- package/dist/perf-UULZSREY.js +200 -0
- package/dist/perf-UULZSREY.js.map +1 -0
- package/dist/pii-QHU32VML.js +146 -0
- package/dist/pii-QHU32VML.js.map +1 -0
- package/dist/pilot-BR6GVK32.js +29 -0
- package/dist/pilot-BR6GVK32.js.map +1 -0
- package/dist/pr-comment-2FOA3EXG.js +81 -0
- package/dist/pr-comment-2FOA3EXG.js.map +1 -0
- package/dist/preview-XNY422OU.js +46 -0
- package/dist/preview-XNY422OU.js.map +1 -0
- package/dist/profile-SQTBNKYS.js +98 -0
- package/dist/profile-SQTBNKYS.js.map +1 -0
- package/dist/promote-FSGUPIPD.js +417 -0
- package/dist/promote-FSGUPIPD.js.map +1 -0
- package/dist/publish-HLP3XHM5.js +766 -0
- package/dist/publish-HLP3XHM5.js.map +1 -0
- package/dist/purge-Y5IOTXKA.js +56 -0
- package/dist/purge-Y5IOTXKA.js.map +1 -0
- package/dist/query-log-SDDGMJLJ.js +112 -0
- package/dist/query-log-SDDGMJLJ.js.map +1 -0
- package/dist/refactor-TC7S43F2.js +5809 -0
- package/dist/refactor-TC7S43F2.js.map +1 -0
- package/dist/refresh-MDJYOYV5.js +39 -0
- package/dist/refresh-MDJYOYV5.js.map +1 -0
- package/dist/replay-E4664A5K.js +118 -0
- package/dist/replay-E4664A5K.js.map +1 -0
- package/dist/revert-QWQWCJJB.js +111 -0
- package/dist/revert-QWQWCJJB.js.map +1 -0
- package/dist/review-7CAVLD67.js +164 -0
- package/dist/review-7CAVLD67.js.map +1 -0
- package/dist/rollback-suggest-C6D5YFCA.js +79 -0
- package/dist/rollback-suggest-C6D5YFCA.js.map +1 -0
- package/dist/safer-alternative-QR4QEFUV.js +84 -0
- package/dist/safer-alternative-QR4QEFUV.js.map +1 -0
- package/dist/safety-OFWUFLK4.js +165 -0
- package/dist/safety-OFWUFLK4.js.map +1 -0
- package/dist/savings-MEBE4TXI.js +95 -0
- package/dist/savings-MEBE4TXI.js.map +1 -0
- package/dist/scan-secrets-XCUBMLHL.js +54 -0
- package/dist/scan-secrets-XCUBMLHL.js.map +1 -0
- package/dist/schema-7JZIG6QR.js +447 -0
- package/dist/schema-7JZIG6QR.js.map +1 -0
- package/dist/script-BMYVBHFR.js +167 -0
- package/dist/script-BMYVBHFR.js.map +1 -0
- package/dist/search-TA3C3AZT.js +151 -0
- package/dist/search-TA3C3AZT.js.map +1 -0
- package/dist/seed-W4Q3L2IU.js +101 -0
- package/dist/seed-W4Q3L2IU.js.map +1 -0
- package/dist/sketch-6B2V6FJV.js +83 -0
- package/dist/sketch-6B2V6FJV.js.map +1 -0
- package/dist/snapshot-YMVS322L.js +171 -0
- package/dist/snapshot-YMVS322L.js.map +1 -0
- package/dist/snippets-EVTN63OU.js +74 -0
- package/dist/snippets-EVTN63OU.js.map +1 -0
- package/dist/standards-FGJW3CQL.js +238 -0
- package/dist/standards-FGJW3CQL.js.map +1 -0
- package/dist/suggest-V3LVIFZ5.js +44 -0
- package/dist/suggest-V3LVIFZ5.js.map +1 -0
- package/dist/suggest-constraints-EX2FCWOQ.js +154 -0
- package/dist/suggest-constraints-EX2FCWOQ.js.map +1 -0
- package/dist/suite-YTQ3CNX5.js +85 -0
- package/dist/suite-YTQ3CNX5.js.map +1 -0
- package/dist/telemetry-KOIY3NEQ.js +90 -0
- package/dist/telemetry-KOIY3NEQ.js.map +1 -0
- package/dist/template-MUJ6X6LN.js +396 -0
- package/dist/template-MUJ6X6LN.js.map +1 -0
- package/dist/test-XFSQHR2S.js +169 -0
- package/dist/test-XFSQHR2S.js.map +1 -0
- package/dist/trial-GFTGYCR3.js +31 -0
- package/dist/trial-GFTGYCR3.js.map +1 -0
- package/dist/validate-LFDEZFFH.js +107 -0
- package/dist/validate-LFDEZFFH.js.map +1 -0
- package/dist/verify-KRDYOJCR.js +76 -0
- package/dist/verify-KRDYOJCR.js.map +1 -0
- package/dist/watch-FSG23RR3.js +80 -0
- package/dist/watch-FSG23RR3.js.map +1 -0
- package/dist/xcompare-U4TXTTIR.js +87 -0
- package/dist/xcompare-U4TXTTIR.js.map +1 -0
- package/package.json +2 -2
- package/dist/cli.cjs +0 -19298
- package/dist/cli.cjs.map +0 -1
- package/dist/cli.d.cts +0 -1
- package/dist/cli.d.ts +0 -1
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import "./chunk-DGUM43GV.js";
|
|
2
|
+
|
|
3
|
+
// src/commands/suggest-constraints.ts
|
|
4
|
+
import { promises as fs } from "fs";
|
|
5
|
+
import path from "path";
|
|
6
|
+
import { Command } from "commander";
|
|
7
|
+
import { ai, suggestConstraints as core, refactoring } from "@ddt-tools/core";
|
|
8
|
+
var CONFIDENCE_RANK = { low: 0, medium: 1, high: 2 };
|
|
9
|
+
function suggestConstraintsCommand() {
|
|
10
|
+
const cmd = new Command("suggest-constraints");
|
|
11
|
+
cmd.description(
|
|
12
|
+
"Suggest PK/UK/FK/CHECK candidates for a profiled table. Reads a TableProfile JSON (from `ddt profile`) and emits suggestions with confidence tiers + rationale."
|
|
13
|
+
).argument("<fqn>", "Fully-qualified table name (must match the profile.fqn).").requiredOption("--profile <path>", "JSON file containing the TableProfile to analyze.").option(
|
|
14
|
+
"--parents <path>",
|
|
15
|
+
"JSON file with parent PK directory: [{ fqn, columns }]. Enables FK candidates."
|
|
16
|
+
).option("--include-low", "Include low-confidence suggestions. Default: filtered out.", false).option(
|
|
17
|
+
"--default-enforced",
|
|
18
|
+
"Treat default platform enforcement as true (Lakebase semantics).",
|
|
19
|
+
false
|
|
20
|
+
).option(
|
|
21
|
+
"--ai",
|
|
22
|
+
"After heuristic scoring, re-rank by business plausibility via the configured AI provider. Requires `ddt ai` configured.",
|
|
23
|
+
false
|
|
24
|
+
).option(
|
|
25
|
+
"--ai-context <text>",
|
|
26
|
+
'Optional one-line context about the table (e.g. "customer events from the e-commerce app") to ground the AI re-ranker.'
|
|
27
|
+
).option(
|
|
28
|
+
"--apply-to <refactor-json-path>",
|
|
29
|
+
"Append included suggestions to a refactor.json file as ADD_CONSTRAINT ops. The file is created if missing; the existing operations array is preserved."
|
|
30
|
+
).option(
|
|
31
|
+
"--apply-min <tier>",
|
|
32
|
+
"Minimum confidence tier to apply (low | medium | high). Default: medium.",
|
|
33
|
+
"medium"
|
|
34
|
+
).option("--format <fmt>", "Output format: json | markdown. Default markdown.", "markdown").option("-o, --output <path>", "Write output to a file instead of stdout.").action(async (fqn, opts) => {
|
|
35
|
+
await runSuggestConstraints(fqn, opts);
|
|
36
|
+
});
|
|
37
|
+
return cmd;
|
|
38
|
+
}
|
|
39
|
+
async function runSuggestConstraints(fqn, opts) {
|
|
40
|
+
const profilePath = String(opts.profile);
|
|
41
|
+
const profileJson = await fs.readFile(path.resolve(profilePath), "utf8");
|
|
42
|
+
const tableProfile = JSON.parse(profileJson);
|
|
43
|
+
if (!tableProfile.fqn || !Array.isArray(tableProfile.columns)) {
|
|
44
|
+
throw new Error(`--profile file is not a TableProfile (missing fqn / columns).`);
|
|
45
|
+
}
|
|
46
|
+
if (tableProfile.fqn !== fqn) {
|
|
47
|
+
console.error(
|
|
48
|
+
`profile fqn "${tableProfile.fqn}" does not match argument "${fqn}". Using the argument.`
|
|
49
|
+
);
|
|
50
|
+
tableProfile.fqn = fqn;
|
|
51
|
+
}
|
|
52
|
+
let parentPks = [];
|
|
53
|
+
if (opts.parents) {
|
|
54
|
+
const parentsJson = await fs.readFile(path.resolve(String(opts.parents)), "utf8");
|
|
55
|
+
const parsed = JSON.parse(parentsJson);
|
|
56
|
+
if (!Array.isArray(parsed)) {
|
|
57
|
+
throw new Error(`--parents file must be a JSON array of { fqn, columns } entries.`);
|
|
58
|
+
}
|
|
59
|
+
parentPks = parsed;
|
|
60
|
+
}
|
|
61
|
+
let suggestions = core.suggestConstraints(tableProfile, {
|
|
62
|
+
parentPks,
|
|
63
|
+
includeLow: opts.includeLow === true,
|
|
64
|
+
defaultEnforced: opts.defaultEnforced === true
|
|
65
|
+
});
|
|
66
|
+
if (opts.ai === true && suggestions.length > 0) {
|
|
67
|
+
try {
|
|
68
|
+
suggestions = await core.rerankWithAi(suggestions, {
|
|
69
|
+
completeFn: async (prompt) => {
|
|
70
|
+
const reply = await ai.complete([{ role: "user", content: prompt }], {
|
|
71
|
+
feature: "suggest-constraints.rerank"
|
|
72
|
+
});
|
|
73
|
+
return reply.text;
|
|
74
|
+
},
|
|
75
|
+
context: typeof opts.aiContext === "string" ? opts.aiContext : void 0
|
|
76
|
+
});
|
|
77
|
+
} catch (err) {
|
|
78
|
+
console.error(
|
|
79
|
+
`--ai re-rank failed; keeping heuristic ordering: ${err instanceof Error ? err.message : String(err)}`
|
|
80
|
+
);
|
|
81
|
+
console.error("Run `ddt ai status` to verify your AI provider is configured.");
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
if (opts.applyTo) {
|
|
85
|
+
const applied = await applyToRefactorLog(
|
|
86
|
+
suggestions,
|
|
87
|
+
String(opts.applyTo),
|
|
88
|
+
fqn,
|
|
89
|
+
String(opts.applyMin ?? "medium").toLowerCase(),
|
|
90
|
+
opts.ai === true ? "ai-rerank" : "heuristic"
|
|
91
|
+
);
|
|
92
|
+
console.error(
|
|
93
|
+
`--apply-to: appended ${applied} suggestion(s) as ADD_CONSTRAINT ops to ${opts.applyTo} (filtered by min confidence ${opts.applyMin ?? "medium"}).`
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
const format = String(opts.format ?? "markdown").toLowerCase();
|
|
97
|
+
const output = format === "json" ? JSON.stringify(suggestions, null, 2) : core.renderSuggestionsMarkdown(suggestions);
|
|
98
|
+
if (opts.output) {
|
|
99
|
+
const outPath = path.resolve(String(opts.output));
|
|
100
|
+
await fs.writeFile(outPath, output, "utf8");
|
|
101
|
+
console.error(`suggest-constraints: wrote ${suggestions.length} suggestion(s) to ${outPath}`);
|
|
102
|
+
} else {
|
|
103
|
+
process.stdout.write(output + (output.endsWith("\n") ? "" : "\n"));
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
async function applyToRefactorLog(suggestions, refactorJsonPath, fqn, minConfidence, source) {
|
|
107
|
+
const fqnParts = fqn.split(".").filter(Boolean);
|
|
108
|
+
if (fqnParts.length < 1) {
|
|
109
|
+
throw new Error(`--apply-to: fqn "${fqn}" cannot be parsed`);
|
|
110
|
+
}
|
|
111
|
+
const objectRef = {
|
|
112
|
+
objectType: "MANAGED_TABLE",
|
|
113
|
+
name: fqnParts[fqnParts.length - 1],
|
|
114
|
+
...fqnParts.length >= 2 ? { schema: fqnParts[fqnParts.length - 2] } : {},
|
|
115
|
+
...fqnParts.length >= 3 ? { database: fqnParts[fqnParts.length - 3] } : {}
|
|
116
|
+
};
|
|
117
|
+
const absPath = path.resolve(refactorJsonPath);
|
|
118
|
+
let log = await refactoring.loadRefactorLog(absPath);
|
|
119
|
+
const minRank = CONFIDENCE_RANK[minConfidence] ?? CONFIDENCE_RANK.medium;
|
|
120
|
+
let appended = 0;
|
|
121
|
+
for (const s of suggestions) {
|
|
122
|
+
if ((CONFIDENCE_RANK[s.confidence] ?? 0) < minRank) continue;
|
|
123
|
+
const details = {
|
|
124
|
+
constraintKind: s.kind,
|
|
125
|
+
columns: s.columns,
|
|
126
|
+
fromSuggester: true,
|
|
127
|
+
suggestionSource: source,
|
|
128
|
+
...s.references ? { referencedFqn: parseFqn(s.references.fqn), referencedColumns: s.references.columns } : {},
|
|
129
|
+
...s.predicate ? { expression: s.predicate } : {}
|
|
130
|
+
};
|
|
131
|
+
log = refactoring.appendOperation(log, {
|
|
132
|
+
type: "ADD_CONSTRAINT",
|
|
133
|
+
object: objectRef,
|
|
134
|
+
details
|
|
135
|
+
});
|
|
136
|
+
appended++;
|
|
137
|
+
}
|
|
138
|
+
await refactoring.saveRefactorLog(absPath, log);
|
|
139
|
+
return appended;
|
|
140
|
+
}
|
|
141
|
+
function parseFqn(s) {
|
|
142
|
+
const parts = s.split(".").filter(Boolean);
|
|
143
|
+
const name = parts[parts.length - 1];
|
|
144
|
+
return {
|
|
145
|
+
name,
|
|
146
|
+
...parts.length >= 2 ? { schema: parts[parts.length - 2] } : {},
|
|
147
|
+
...parts.length >= 3 ? { database: parts[parts.length - 3] } : {}
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
export {
|
|
151
|
+
runSuggestConstraints,
|
|
152
|
+
suggestConstraintsCommand
|
|
153
|
+
};
|
|
154
|
+
//# sourceMappingURL=suggest-constraints-EX2FCWOQ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/suggest-constraints.ts"],"sourcesContent":["/**\n * `ddt suggest-constraints <fqn>` — item 5 of the constraint-enforcement\n * initiative. Mirrors `sdt suggest-constraints`.\n */\nimport { promises as fs } from 'fs';\nimport path from 'path';\nimport { Command } from 'commander';\nimport type { profile as profileNs } from '@ddt-tools/core';\nimport { ai, suggestConstraints as core, refactoring } from '@ddt-tools/core';\n\ntype Confidence = 'low' | 'medium' | 'high';\nconst CONFIDENCE_RANK: Record<Confidence, number> = { low: 0, medium: 1, high: 2 };\n\nexport function suggestConstraintsCommand(): Command {\n const cmd = new Command('suggest-constraints');\n cmd\n .description(\n 'Suggest PK/UK/FK/CHECK candidates for a profiled table. Reads a TableProfile JSON ' +\n '(from `ddt profile`) and emits suggestions with confidence tiers + rationale.',\n )\n .argument('<fqn>', 'Fully-qualified table name (must match the profile.fqn).')\n .requiredOption('--profile <path>', 'JSON file containing the TableProfile to analyze.')\n .option(\n '--parents <path>',\n 'JSON file with parent PK directory: [{ fqn, columns }]. Enables FK candidates.',\n )\n .option('--include-low', 'Include low-confidence suggestions. Default: filtered out.', false)\n .option(\n '--default-enforced',\n 'Treat default platform enforcement as true (Lakebase semantics).',\n false,\n )\n .option(\n '--ai',\n 'After heuristic scoring, re-rank by business plausibility via the configured AI provider. Requires `ddt ai` configured.',\n false,\n )\n .option(\n '--ai-context <text>',\n 'Optional one-line context about the table (e.g. \"customer events from the e-commerce app\") to ground the AI re-ranker.',\n )\n .option(\n '--apply-to <refactor-json-path>',\n 'Append included suggestions to a refactor.json file as ADD_CONSTRAINT ops. The file is created if missing; the existing operations array is preserved.',\n )\n .option(\n '--apply-min <tier>',\n 'Minimum confidence tier to apply (low | medium | high). Default: medium.',\n 'medium',\n )\n .option('--format <fmt>', 'Output format: json | markdown. Default markdown.', 'markdown')\n .option('-o, --output <path>', 'Write output to a file instead of stdout.')\n .action(async (fqn: string, opts: Record<string, unknown>) => {\n await runSuggestConstraints(fqn, opts);\n });\n return cmd;\n}\n\nexport async function runSuggestConstraints(\n fqn: string,\n opts: Record<string, unknown>,\n): Promise<void> {\n const profilePath = String(opts.profile);\n const profileJson = await fs.readFile(path.resolve(profilePath), 'utf8');\n const tableProfile = JSON.parse(profileJson) as profileNs.TableProfile;\n if (!tableProfile.fqn || !Array.isArray(tableProfile.columns)) {\n throw new Error(`--profile file is not a TableProfile (missing fqn / columns).`);\n }\n if (tableProfile.fqn !== fqn) {\n console.error(\n `profile fqn \"${tableProfile.fqn}\" does not match argument \"${fqn}\". Using the argument.`,\n );\n tableProfile.fqn = fqn;\n }\n\n let parentPks: Array<{ fqn: string; columns: string[] }> = [];\n if (opts.parents) {\n const parentsJson = await fs.readFile(path.resolve(String(opts.parents)), 'utf8');\n const parsed = JSON.parse(parentsJson);\n if (!Array.isArray(parsed)) {\n throw new Error(`--parents file must be a JSON array of { fqn, columns } entries.`);\n }\n parentPks = parsed;\n }\n\n let suggestions: core.ConstraintSuggestion[] = core.suggestConstraints(tableProfile, {\n parentPks,\n includeLow: opts.includeLow === true,\n defaultEnforced: opts.defaultEnforced === true,\n });\n\n if (opts.ai === true && suggestions.length > 0) {\n try {\n suggestions = await core.rerankWithAi(suggestions, {\n completeFn: async (prompt: string) => {\n const reply = await ai.complete([{ role: 'user', content: prompt }], {\n feature: 'suggest-constraints.rerank',\n });\n return reply.text;\n },\n context: typeof opts.aiContext === 'string' ? opts.aiContext : undefined,\n });\n } catch (err) {\n console.error(\n `--ai re-rank failed; keeping heuristic ordering: ${err instanceof Error ? err.message : String(err)}`,\n );\n console.error('Run `ddt ai status` to verify your AI provider is configured.');\n }\n }\n\n if (opts.applyTo) {\n const applied = await applyToRefactorLog(\n suggestions,\n String(opts.applyTo),\n fqn,\n String(opts.applyMin ?? 'medium').toLowerCase() as Confidence,\n opts.ai === true ? 'ai-rerank' : 'heuristic',\n );\n console.error(\n `--apply-to: appended ${applied} suggestion(s) as ADD_CONSTRAINT ops to ${opts.applyTo} (filtered by min confidence ${opts.applyMin ?? 'medium'}).`,\n );\n }\n\n const format = String(opts.format ?? 'markdown').toLowerCase();\n const output =\n format === 'json'\n ? JSON.stringify(suggestions, null, 2)\n : core.renderSuggestionsMarkdown(suggestions);\n\n if (opts.output) {\n const outPath = path.resolve(String(opts.output));\n await fs.writeFile(outPath, output, 'utf8');\n console.error(`suggest-constraints: wrote ${suggestions.length} suggestion(s) to ${outPath}`);\n } else {\n process.stdout.write(output + (output.endsWith('\\n') ? '' : '\\n'));\n }\n}\n\nasync function applyToRefactorLog(\n suggestions: readonly core.ConstraintSuggestion[],\n refactorJsonPath: string,\n fqn: string,\n minConfidence: Confidence,\n source: 'heuristic' | 'ai-rerank',\n): Promise<number> {\n const fqnParts = fqn.split('.').filter(Boolean);\n if (fqnParts.length < 1) {\n throw new Error(`--apply-to: fqn \"${fqn}\" cannot be parsed`);\n }\n const objectRef = {\n objectType: 'MANAGED_TABLE' as const,\n name: fqnParts[fqnParts.length - 1]!,\n ...(fqnParts.length >= 2 ? { schema: fqnParts[fqnParts.length - 2]! } : {}),\n ...(fqnParts.length >= 3 ? { database: fqnParts[fqnParts.length - 3]! } : {}),\n };\n\n const absPath = path.resolve(refactorJsonPath);\n let log = await refactoring.loadRefactorLog(absPath);\n const minRank = CONFIDENCE_RANK[minConfidence] ?? CONFIDENCE_RANK.medium;\n let appended = 0;\n for (const s of suggestions) {\n if ((CONFIDENCE_RANK[s.confidence as Confidence] ?? 0) < minRank) continue;\n const details: refactoring.AddConstraintOp['details'] = {\n constraintKind: s.kind,\n columns: s.columns,\n fromSuggester: true,\n suggestionSource: source,\n ...(s.references\n ? { referencedFqn: parseFqn(s.references.fqn), referencedColumns: s.references.columns }\n : {}),\n ...(s.predicate ? { expression: s.predicate } : {}),\n };\n log = refactoring.appendOperation(log, {\n type: 'ADD_CONSTRAINT',\n object: objectRef,\n details,\n });\n appended++;\n }\n await refactoring.saveRefactorLog(absPath, log);\n return appended;\n}\n\nfunction parseFqn(s: string): { database?: string; schema?: string; name: string } {\n const parts = s.split('.').filter(Boolean);\n const name = parts[parts.length - 1]!;\n return {\n name,\n ...(parts.length >= 2 ? { schema: parts[parts.length - 2]! } : {}),\n ...(parts.length >= 3 ? { database: parts[parts.length - 3]! } : {}),\n };\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import "./chunk-DGUM43GV.js";
|
|
2
|
+
|
|
3
|
+
// src/commands/suite.ts
|
|
4
|
+
import path from "path";
|
|
5
|
+
import { Command } from "commander";
|
|
6
|
+
import {
|
|
7
|
+
loadSuite,
|
|
8
|
+
newSuiteTemplate,
|
|
9
|
+
saveSuite,
|
|
10
|
+
validateSuite,
|
|
11
|
+
license as licenseNs
|
|
12
|
+
} from "@ddt-tools/core";
|
|
13
|
+
async function warnIfMissingLicense(feature) {
|
|
14
|
+
const lic = await licenseNs.loadLicense();
|
|
15
|
+
const r = licenseNs.checkProFeature(lic, feature);
|
|
16
|
+
if (r.warning) console.error(`[Pro] ${r.warning}`);
|
|
17
|
+
}
|
|
18
|
+
function suiteCommand() {
|
|
19
|
+
const cmd = new Command("suite").description(
|
|
20
|
+
"Manage a .ddtsuite \u2014 a collection of .ddtproj projects deploying together."
|
|
21
|
+
);
|
|
22
|
+
cmd.addCommand(suiteInitCommand());
|
|
23
|
+
cmd.addCommand(suiteValidateCommand());
|
|
24
|
+
return cmd;
|
|
25
|
+
}
|
|
26
|
+
function suiteInitCommand() {
|
|
27
|
+
const cmd = new Command("init");
|
|
28
|
+
cmd.description("Scaffold a new .ddtsuite file from a list of project paths.").requiredOption("--out <path>", "Where to write the new .ddtsuite file.").requiredOption("--name <suite-name>", "Suite display name.").option(
|
|
29
|
+
"--project <alias=path>",
|
|
30
|
+
"Add a project to the suite (repeatable). Format: alias=path/to/Project.ddtproj",
|
|
31
|
+
(value, prev) => {
|
|
32
|
+
const m = /^([A-Za-z_][\w-]*)=(.+)$/.exec(value);
|
|
33
|
+
if (!m) throw new Error(`Bad --project value "${value}". Expected alias=path.`);
|
|
34
|
+
return [...prev, { alias: m[1], path: m[2] }];
|
|
35
|
+
},
|
|
36
|
+
[]
|
|
37
|
+
).action(async (opts) => {
|
|
38
|
+
await warnIfMissingLicense("suite");
|
|
39
|
+
const projects = opts.project;
|
|
40
|
+
if (projects.length === 0) {
|
|
41
|
+
throw new Error("At least one --project alias=path is required.");
|
|
42
|
+
}
|
|
43
|
+
const outPath = path.resolve(String(opts.out));
|
|
44
|
+
const suite = newSuiteTemplate(String(opts.name), projects);
|
|
45
|
+
await saveSuite(outPath, suite);
|
|
46
|
+
console.log(`Wrote suite: ${outPath} (${projects.length} project(s))`);
|
|
47
|
+
});
|
|
48
|
+
return cmd;
|
|
49
|
+
}
|
|
50
|
+
function suiteValidateCommand() {
|
|
51
|
+
const cmd = new Command("validate");
|
|
52
|
+
cmd.description("Validate a .ddtsuite: ownership conflicts, refs, cycles, deploy order.").argument("<suite>", "Path to a .ddtsuite file.").option("--json", "Emit machine-readable JSON.", false).action(async (suiteArg, opts) => {
|
|
53
|
+
await warnIfMissingLicense("suite");
|
|
54
|
+
const loaded = await loadSuite(suiteArg);
|
|
55
|
+
const result = validateSuite(loaded);
|
|
56
|
+
if (opts.json) {
|
|
57
|
+
process.stdout.write(JSON.stringify(result, null, 2) + "\n");
|
|
58
|
+
} else {
|
|
59
|
+
printSuiteValidation(loaded.suite.suiteName, result.findings, result.deployOrder);
|
|
60
|
+
}
|
|
61
|
+
const errors = result.findings.filter((f) => f.severity === "ERROR").length;
|
|
62
|
+
if (errors > 0) process.exitCode = 2;
|
|
63
|
+
});
|
|
64
|
+
return cmd;
|
|
65
|
+
}
|
|
66
|
+
function printSuiteValidation(suiteName, findings, deployOrder) {
|
|
67
|
+
console.log(`Suite: ${suiteName}`);
|
|
68
|
+
const errors = findings.filter((f) => f.severity === "ERROR");
|
|
69
|
+
const warnings = findings.filter((f) => f.severity === "WARNING");
|
|
70
|
+
const infos = findings.filter((f) => f.severity === "INFO");
|
|
71
|
+
console.log(` ${errors.length} error(s), ${warnings.length} warning(s), ${infos.length} info`);
|
|
72
|
+
for (const f of [...errors, ...warnings, ...infos]) {
|
|
73
|
+
const tag = f.severity === "ERROR" ? "ERROR" : f.severity === "WARNING" ? "WARN " : "INFO ";
|
|
74
|
+
console.log(` [${tag}] ${f.code}: ${f.message}`);
|
|
75
|
+
}
|
|
76
|
+
if (deployOrder.length > 0) {
|
|
77
|
+
console.log(` Deploy order: ${deployOrder.join(" \u2192 ")}`);
|
|
78
|
+
} else {
|
|
79
|
+
console.error(` Suite is undeployable (cycle or invalid order).`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
export {
|
|
83
|
+
suiteCommand
|
|
84
|
+
};
|
|
85
|
+
//# sourceMappingURL=suite-YTQ3CNX5.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/suite.ts"],"sourcesContent":["import path from 'node:path';\nimport { Command } from 'commander';\nimport {\n loadSuite,\n newSuiteTemplate,\n saveSuite,\n validateSuite,\n license as licenseNs,\n type SuiteFinding,\n} from '@ddt-tools/core';\n\n/**\n * Surface a Pro-tier warning (warn-mode v0.1 — never blocks).\n * Both `suite init` and `suite validate` are Pro features.\n */\nasync function warnIfMissingLicense(feature: licenseNs.ProFeature): Promise<void> {\n const lic = await licenseNs.loadLicense();\n const r = licenseNs.checkProFeature(lic, feature);\n if (r.warning) console.error(`[Pro] ${r.warning}`);\n}\n\n/**\n * `ddt suite` — manage and validate a `.ddtsuite` file, the collection of\n * `.ddtproj` projects that compose into one Databricks target.\n *\n * Subcommands:\n * init — scaffold a new .ddtsuite from a list of project paths\n * validate — run the 5 suite-level checks (ownership, refs, cycles,\n * coverage, deploy order)\n *\n * Pro-tier feature.\n */\nexport function suiteCommand(): Command {\n const cmd = new Command('suite').description(\n 'Manage a .ddtsuite — a collection of .ddtproj projects deploying together.',\n );\n cmd.addCommand(suiteInitCommand());\n cmd.addCommand(suiteValidateCommand());\n return cmd;\n}\n\nfunction suiteInitCommand(): Command {\n const cmd = new Command('init');\n cmd\n .description('Scaffold a new .ddtsuite file from a list of project paths.')\n .requiredOption('--out <path>', 'Where to write the new .ddtsuite file.')\n .requiredOption('--name <suite-name>', 'Suite display name.')\n .option(\n '--project <alias=path>',\n 'Add a project to the suite (repeatable). Format: alias=path/to/Project.ddtproj',\n (value: string, prev: Array<{ alias: string; path: string }>) => {\n const m = /^([A-Za-z_][\\w-]*)=(.+)$/.exec(value);\n if (!m) throw new Error(`Bad --project value \"${value}\". Expected alias=path.`);\n return [...prev, { alias: m[1]!, path: m[2]! }];\n },\n [] as Array<{ alias: string; path: string }>,\n )\n .action(async (opts) => {\n await warnIfMissingLicense('suite');\n const projects = opts.project as Array<{ alias: string; path: string }>;\n if (projects.length === 0) {\n throw new Error('At least one --project alias=path is required.');\n }\n const outPath = path.resolve(String(opts.out));\n const suite = newSuiteTemplate(String(opts.name), projects);\n await saveSuite(outPath, suite);\n console.log(`Wrote suite: ${outPath} (${projects.length} project(s))`);\n });\n return cmd;\n}\n\nfunction suiteValidateCommand(): Command {\n const cmd = new Command('validate');\n cmd\n .description('Validate a .ddtsuite: ownership conflicts, refs, cycles, deploy order.')\n .argument('<suite>', 'Path to a .ddtsuite file.')\n .option('--json', 'Emit machine-readable JSON.', false)\n .action(async (suiteArg: string, opts) => {\n await warnIfMissingLicense('suite');\n const loaded = await loadSuite(suiteArg);\n const result = validateSuite(loaded);\n if (opts.json) {\n process.stdout.write(JSON.stringify(result, null, 2) + '\\n');\n } else {\n printSuiteValidation(loaded.suite.suiteName, result.findings, result.deployOrder);\n }\n const errors = result.findings.filter((f) => f.severity === 'ERROR').length;\n if (errors > 0) process.exitCode = 2;\n });\n return cmd;\n}\n\nfunction printSuiteValidation(\n suiteName: string,\n findings: SuiteFinding[],\n deployOrder: string[],\n): void {\n console.log(`Suite: ${suiteName}`);\n const errors = findings.filter((f) => f.severity === 'ERROR');\n const warnings = findings.filter((f) => f.severity === 'WARNING');\n const infos = findings.filter((f) => f.severity === 'INFO');\n console.log(` ${errors.length} error(s), ${warnings.length} warning(s), ${infos.length} info`);\n for (const f of [...errors, ...warnings, ...infos]) {\n const tag = f.severity === 'ERROR' ? 'ERROR' : f.severity === 'WARNING' ? 'WARN ' : 'INFO ';\n console.log(` [${tag}] ${f.code}: ${f.message}`);\n }\n if (deployOrder.length > 0) {\n console.log(` Deploy order: ${deployOrder.join(' → ')}`);\n } else {\n console.error(` Suite is undeployable (cycle or invalid order).`);\n }\n}\n"],"mappings":";;;AAAA,OAAO,UAAU;AACjB,SAAS,eAAe;AACxB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,WAAW;AAAA,OAEN;AAMP,eAAe,qBAAqB,SAA8C;AAChF,QAAM,MAAM,MAAM,UAAU,YAAY;AACxC,QAAM,IAAI,UAAU,gBAAgB,KAAK,OAAO;AAChD,MAAI,EAAE,QAAS,SAAQ,MAAM,SAAS,EAAE,OAAO,EAAE;AACnD;AAaO,SAAS,eAAwB;AACtC,QAAM,MAAM,IAAI,QAAQ,OAAO,EAAE;AAAA,IAC/B;AAAA,EACF;AACA,MAAI,WAAW,iBAAiB,CAAC;AACjC,MAAI,WAAW,qBAAqB,CAAC;AACrC,SAAO;AACT;AAEA,SAAS,mBAA4B;AACnC,QAAM,MAAM,IAAI,QAAQ,MAAM;AAC9B,MACG,YAAY,6DAA6D,EACzE,eAAe,gBAAgB,wCAAwC,EACvE,eAAe,uBAAuB,qBAAqB,EAC3D;AAAA,IACC;AAAA,IACA;AAAA,IACA,CAAC,OAAe,SAAiD;AAC/D,YAAM,IAAI,2BAA2B,KAAK,KAAK;AAC/C,UAAI,CAAC,EAAG,OAAM,IAAI,MAAM,wBAAwB,KAAK,yBAAyB;AAC9E,aAAO,CAAC,GAAG,MAAM,EAAE,OAAO,EAAE,CAAC,GAAI,MAAM,EAAE,CAAC,EAAG,CAAC;AAAA,IAChD;AAAA,IACA,CAAC;AAAA,EACH,EACC,OAAO,OAAO,SAAS;AACtB,UAAM,qBAAqB,OAAO;AAClC,UAAM,WAAW,KAAK;AACtB,QAAI,SAAS,WAAW,GAAG;AACzB,YAAM,IAAI,MAAM,gDAAgD;AAAA,IAClE;AACA,UAAM,UAAU,KAAK,QAAQ,OAAO,KAAK,GAAG,CAAC;AAC7C,UAAM,QAAQ,iBAAiB,OAAO,KAAK,IAAI,GAAG,QAAQ;AAC1D,UAAM,UAAU,SAAS,KAAK;AAC9B,YAAQ,IAAI,gBAAgB,OAAO,KAAK,SAAS,MAAM,cAAc;AAAA,EACvE,CAAC;AACH,SAAO;AACT;AAEA,SAAS,uBAAgC;AACvC,QAAM,MAAM,IAAI,QAAQ,UAAU;AAClC,MACG,YAAY,wEAAwE,EACpF,SAAS,WAAW,2BAA2B,EAC/C,OAAO,UAAU,+BAA+B,KAAK,EACrD,OAAO,OAAO,UAAkB,SAAS;AACxC,UAAM,qBAAqB,OAAO;AAClC,UAAM,SAAS,MAAM,UAAU,QAAQ;AACvC,UAAM,SAAS,cAAc,MAAM;AACnC,QAAI,KAAK,MAAM;AACb,cAAQ,OAAO,MAAM,KAAK,UAAU,QAAQ,MAAM,CAAC,IAAI,IAAI;AAAA,IAC7D,OAAO;AACL,2BAAqB,OAAO,MAAM,WAAW,OAAO,UAAU,OAAO,WAAW;AAAA,IAClF;AACA,UAAM,SAAS,OAAO,SAAS,OAAO,CAAC,MAAM,EAAE,aAAa,OAAO,EAAE;AACrE,QAAI,SAAS,EAAG,SAAQ,WAAW;AAAA,EACrC,CAAC;AACH,SAAO;AACT;AAEA,SAAS,qBACP,WACA,UACA,aACM;AACN,UAAQ,IAAI,UAAU,SAAS,EAAE;AACjC,QAAM,SAAS,SAAS,OAAO,CAAC,MAAM,EAAE,aAAa,OAAO;AAC5D,QAAM,WAAW,SAAS,OAAO,CAAC,MAAM,EAAE,aAAa,SAAS;AAChE,QAAM,QAAQ,SAAS,OAAO,CAAC,MAAM,EAAE,aAAa,MAAM;AAC1D,UAAQ,IAAI,KAAK,OAAO,MAAM,cAAc,SAAS,MAAM,gBAAgB,MAAM,MAAM,OAAO;AAC9F,aAAW,KAAK,CAAC,GAAG,QAAQ,GAAG,UAAU,GAAG,KAAK,GAAG;AAClD,UAAM,MAAM,EAAE,aAAa,UAAU,UAAU,EAAE,aAAa,YAAY,UAAU;AACpF,YAAQ,IAAI,MAAM,GAAG,KAAK,EAAE,IAAI,KAAK,EAAE,OAAO,EAAE;AAAA,EAClD;AACA,MAAI,YAAY,SAAS,GAAG;AAC1B,YAAQ,IAAI,mBAAmB,YAAY,KAAK,UAAK,CAAC,EAAE;AAAA,EAC1D,OAAO;AACL,YAAQ,MAAM,mDAAmD;AAAA,EACnE;AACF;","names":[]}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import "./chunk-DGUM43GV.js";
|
|
2
|
+
|
|
3
|
+
// src/commands/telemetry.ts
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
import { errorReport, telemetry } from "@ddt-tools/core";
|
|
6
|
+
function telemetryCommand() {
|
|
7
|
+
const cmd = new Command("telemetry").description(
|
|
8
|
+
"Manage opt-in telemetry. Off by default; local-only by default."
|
|
9
|
+
);
|
|
10
|
+
cmd.command("on").description("Enable telemetry. By default events stay local; pass --endpoint to also upload.").option("--endpoint <url>", "Optional HTTPS upload endpoint.").action(async (opts) => {
|
|
11
|
+
const cfg = {
|
|
12
|
+
enabled: true,
|
|
13
|
+
enabledAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
14
|
+
...opts.endpoint ? { endpoint: String(opts.endpoint) } : {}
|
|
15
|
+
};
|
|
16
|
+
await telemetry.saveConfig(cfg);
|
|
17
|
+
await errorReport.writeConsent("on");
|
|
18
|
+
console.log("Telemetry: ON.");
|
|
19
|
+
console.log("Automatic error reporting enabled.");
|
|
20
|
+
console.log(`Events written to ${telemetry.telemetryDir()}.`);
|
|
21
|
+
if (cfg.endpoint) {
|
|
22
|
+
console.log(
|
|
23
|
+
`Will batch-upload to ${cfg.endpoint}. Run \`ddt telemetry upload\` to push pending events.`
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
cmd.command("upload").description("POST any not-yet-uploaded local events to the configured --endpoint.").action(async () => {
|
|
28
|
+
const cfg = await telemetry.loadConfig();
|
|
29
|
+
const r = await telemetry.uploadPendingEvents(cfg);
|
|
30
|
+
if (r.skipped) {
|
|
31
|
+
console.log(`Upload skipped: ${r.reason}.`);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
if (r.uploaded === 0) {
|
|
35
|
+
console.log("No new events to upload.");
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
console.log(
|
|
39
|
+
`Uploaded ${r.uploaded} event(s) to ${cfg.endpoint}.${r.reason ? " (" + r.reason + ")" : ""}`
|
|
40
|
+
);
|
|
41
|
+
});
|
|
42
|
+
cmd.command("off").description("Disable telemetry. Local events are NOT wiped \u2014 run `clear` for that.").action(async () => {
|
|
43
|
+
await telemetry.saveConfig({ enabled: false });
|
|
44
|
+
await errorReport.writeConsent("off");
|
|
45
|
+
console.log("Telemetry: OFF.");
|
|
46
|
+
console.log("Automatic error reporting disabled.");
|
|
47
|
+
console.warn(errorReport.CONSENT_WARNING);
|
|
48
|
+
});
|
|
49
|
+
cmd.command("show").description("Print recent telemetry events (newest first).").option("--limit <n>", "Show this many events.", "20").action(async (opts) => {
|
|
50
|
+
const events = await telemetry.listEvents(parseInt(String(opts.limit), 10));
|
|
51
|
+
const cfg = await telemetry.loadConfig();
|
|
52
|
+
console.log(
|
|
53
|
+
`Telemetry: ${cfg.enabled ? "ON" : "OFF"}${cfg.endpoint ? " (endpoint=" + cfg.endpoint + ")" : ""}`
|
|
54
|
+
);
|
|
55
|
+
console.log(`Storage: ${telemetry.telemetryDir()}`);
|
|
56
|
+
console.log(`Events: ${events.length}`);
|
|
57
|
+
console.log("");
|
|
58
|
+
for (const e of events) {
|
|
59
|
+
console.log(
|
|
60
|
+
` ${e.ts} ${e.command.padEnd(12)} ${e.platform.padEnd(11)} ${e.result.padEnd(8)} ${e.duration_ms}ms`
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
cmd.command("clear").description("Wipe every locally-recorded telemetry event.").action(async () => {
|
|
65
|
+
await telemetry.clearEvents();
|
|
66
|
+
console.log("Telemetry: cleared all local events.");
|
|
67
|
+
});
|
|
68
|
+
cmd.command("config").description("Print the telemetry config + automatic error-reporting state.").action(async () => {
|
|
69
|
+
const cfg = await telemetry.loadConfig();
|
|
70
|
+
console.log(JSON.stringify(cfg, null, 2));
|
|
71
|
+
const consent = await errorReport.readConsent();
|
|
72
|
+
const reportingEnabled = errorReport.isErrorReportingEnabled(consent.consent);
|
|
73
|
+
const spooled = await errorReport.listSpooled();
|
|
74
|
+
console.log(
|
|
75
|
+
`Error reporting: ${consent.consent} (sending ${reportingEnabled ? "enabled" : "disabled"})`
|
|
76
|
+
);
|
|
77
|
+
if (spooled.length > 0) {
|
|
78
|
+
const occurrences = spooled.reduce((total, record) => total + record.count, 0);
|
|
79
|
+
console.log(
|
|
80
|
+
`Locally captured errors: ${spooled.length} distinct (${occurrences} occurrences) awaiting ${reportingEnabled ? "upload" : "consent"}.`
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
if (!reportingEnabled) console.log(` ${errorReport.CONSENT_WARNING}`);
|
|
84
|
+
});
|
|
85
|
+
return cmd;
|
|
86
|
+
}
|
|
87
|
+
export {
|
|
88
|
+
telemetryCommand
|
|
89
|
+
};
|
|
90
|
+
//# sourceMappingURL=telemetry-KOIY3NEQ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/telemetry.ts"],"sourcesContent":["import { Command } from 'commander';\nimport { errorReport, telemetry } from '@ddt-tools/core';\n\n/**\n * `ddt telemetry` — opt-in pseudonymous usage analytics AND automatic error\n * reporting (ERR.2: one consent governs both). Off by default.\n *\n * Subcommands:\n * on [--endpoint <url>] enable, optionally with an upload endpoint\n * off disable + stop recording\n * show print the last N recorded events\n * clear wipe local events\n * config print current config + error-reporting state\n */\nexport function telemetryCommand(): Command {\n const cmd = new Command('telemetry').description(\n 'Manage opt-in telemetry. Off by default; local-only by default.',\n );\n\n cmd\n .command('on')\n .description('Enable telemetry. By default events stay local; pass --endpoint to also upload.')\n .option('--endpoint <url>', 'Optional HTTPS upload endpoint.')\n .action(async (opts) => {\n const cfg: telemetry.TelemetryConfig = {\n enabled: true,\n enabledAt: new Date().toISOString(),\n ...(opts.endpoint ? { endpoint: String(opts.endpoint) } : {}),\n };\n await telemetry.saveConfig(cfg);\n // ERR.2 — one consent governs telemetry + automatic error reporting.\n await errorReport.writeConsent('on');\n console.log('Telemetry: ON.');\n console.log('Automatic error reporting enabled.');\n console.log(`Events written to ${telemetry.telemetryDir()}.`);\n if (cfg.endpoint) {\n console.log(\n `Will batch-upload to ${cfg.endpoint}. Run \\`ddt telemetry upload\\` to push pending events.`,\n );\n }\n });\n\n cmd\n .command('upload')\n .description('POST any not-yet-uploaded local events to the configured --endpoint.')\n .action(async () => {\n const cfg = await telemetry.loadConfig();\n const r = await telemetry.uploadPendingEvents(cfg);\n if (r.skipped) {\n console.log(`Upload skipped: ${r.reason}.`);\n return;\n }\n if (r.uploaded === 0) {\n console.log('No new events to upload.');\n return;\n }\n console.log(\n `Uploaded ${r.uploaded} event(s) to ${cfg.endpoint}.${r.reason ? ' (' + r.reason + ')' : ''}`,\n );\n });\n\n cmd\n .command('off')\n .description('Disable telemetry. Local events are NOT wiped — run `clear` for that.')\n .action(async () => {\n await telemetry.saveConfig({ enabled: false });\n // ERR.2 — disabling telemetry also opts out of automatic error reporting.\n await errorReport.writeConsent('off');\n console.log('Telemetry: OFF.');\n console.log('Automatic error reporting disabled.');\n console.warn(errorReport.CONSENT_WARNING);\n });\n\n cmd\n .command('show')\n .description('Print recent telemetry events (newest first).')\n .option('--limit <n>', 'Show this many events.', '20')\n .action(async (opts) => {\n const events = await telemetry.listEvents(parseInt(String(opts.limit), 10));\n const cfg = await telemetry.loadConfig();\n console.log(\n `Telemetry: ${cfg.enabled ? 'ON' : 'OFF'}${cfg.endpoint ? ' (endpoint=' + cfg.endpoint + ')' : ''}`,\n );\n console.log(`Storage: ${telemetry.telemetryDir()}`);\n console.log(`Events: ${events.length}`);\n console.log('');\n for (const e of events) {\n console.log(\n ` ${e.ts} ${e.command.padEnd(12)} ${e.platform.padEnd(11)} ${e.result.padEnd(8)} ${e.duration_ms}ms`,\n );\n }\n });\n\n cmd\n .command('clear')\n .description('Wipe every locally-recorded telemetry event.')\n .action(async () => {\n await telemetry.clearEvents();\n console.log('Telemetry: cleared all local events.');\n });\n\n cmd\n .command('config')\n .description('Print the telemetry config + automatic error-reporting state.')\n .action(async () => {\n const cfg = await telemetry.loadConfig();\n console.log(JSON.stringify(cfg, null, 2));\n\n // ERR.2 — error-reporting consent + local spool state.\n const consent = await errorReport.readConsent();\n const reportingEnabled = errorReport.isErrorReportingEnabled(consent.consent);\n const spooled = await errorReport.listSpooled();\n console.log(\n `Error reporting: ${consent.consent} (sending ${reportingEnabled ? 'enabled' : 'disabled'})`,\n );\n if (spooled.length > 0) {\n const occurrences = spooled.reduce((total, record) => total + record.count, 0);\n console.log(\n `Locally captured errors: ${spooled.length} distinct (${occurrences} occurrences) awaiting ${reportingEnabled ? 'upload' : 'consent'}.`,\n );\n }\n if (!reportingEnabled) console.log(` ${errorReport.CONSENT_WARNING}`);\n });\n\n return cmd;\n}\n"],"mappings":"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","names":[]}
|