@eduardbar/drift 1.3.0 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.gga +50 -0
- package/.github/actions/drift-review/README.md +60 -0
- package/.github/actions/drift-review/action.yml +131 -0
- package/.github/actions/drift-scan/README.md +28 -32
- package/.github/actions/drift-scan/action.yml +78 -14
- package/.github/workflows/review-pr.yml +34 -41
- package/AGENTS.md +75 -251
- package/CHANGELOG.md +28 -0
- package/README.md +148 -41
- package/dist/benchmark.d.ts +1 -1
- package/dist/benchmark.js +71 -52
- package/dist/cli.js +243 -8
- package/dist/config.js +16 -2
- package/dist/diff.js +42 -50
- package/dist/doctor.d.ts +5 -0
- package/dist/doctor.js +133 -0
- package/dist/format.d.ts +17 -0
- package/dist/format.js +45 -0
- package/dist/guard-types.d.ts +57 -0
- package/dist/guard-types.js +2 -0
- package/dist/guard.d.ts +14 -0
- package/dist/guard.js +239 -0
- package/dist/index.d.ts +10 -3
- package/dist/index.js +4 -1
- package/dist/init.d.ts +15 -0
- package/dist/init.js +273 -0
- package/dist/map-cycles.d.ts +2 -0
- package/dist/map-cycles.js +34 -0
- package/dist/map-svg.d.ts +19 -0
- package/dist/map-svg.js +97 -0
- package/dist/map.js +78 -138
- package/dist/metrics.js +70 -55
- package/dist/output-metadata.d.ts +13 -0
- package/dist/output-metadata.js +17 -0
- package/dist/plugins-capabilities.d.ts +4 -0
- package/dist/plugins-capabilities.js +21 -0
- package/dist/plugins-messages.d.ts +10 -0
- package/dist/plugins-messages.js +16 -0
- package/dist/plugins-rules.d.ts +9 -0
- package/dist/plugins-rules.js +137 -0
- package/dist/plugins.d.ts +1 -1
- package/dist/plugins.js +45 -142
- package/dist/reporter-constants.d.ts +16 -0
- package/dist/reporter-constants.js +39 -0
- package/dist/reporter.d.ts +3 -3
- package/dist/reporter.js +35 -55
- package/dist/review.d.ts +2 -1
- package/dist/review.js +2 -1
- package/dist/rules/phase3-configurable.js +23 -15
- package/dist/saas/constants.d.ts +15 -0
- package/dist/saas/constants.js +48 -0
- package/dist/saas/dashboard.d.ts +8 -0
- package/dist/saas/dashboard.js +132 -0
- package/dist/saas/errors.d.ts +19 -0
- package/dist/saas/errors.js +37 -0
- package/dist/saas/helpers.d.ts +21 -0
- package/dist/saas/helpers.js +110 -0
- package/dist/saas/ingest.d.ts +3 -0
- package/dist/saas/ingest.js +249 -0
- package/dist/saas/organization.d.ts +5 -0
- package/dist/saas/organization.js +82 -0
- package/dist/saas/plan-change.d.ts +10 -0
- package/dist/saas/plan-change.js +15 -0
- package/dist/saas/store.d.ts +21 -0
- package/dist/saas/store.js +159 -0
- package/dist/saas/types.d.ts +191 -0
- package/dist/saas/types.js +2 -0
- package/dist/saas.d.ts +8 -218
- package/dist/saas.js +7 -761
- package/dist/sarif.d.ts +74 -0
- package/dist/sarif.js +122 -0
- package/dist/trust-advanced.d.ts +14 -0
- package/dist/trust-advanced.js +65 -0
- package/dist/trust-kpi-fs.d.ts +3 -0
- package/dist/trust-kpi-fs.js +141 -0
- package/dist/trust-kpi-parse.d.ts +7 -0
- package/dist/trust-kpi-parse.js +186 -0
- package/dist/trust-kpi-types.d.ts +16 -0
- package/dist/trust-kpi-types.js +2 -0
- package/dist/trust-kpi.d.ts +1 -3
- package/dist/trust-kpi.js +6 -266
- package/dist/trust-policy.d.ts +32 -0
- package/dist/trust-policy.js +160 -0
- package/dist/trust-render.d.ts +9 -0
- package/dist/trust-render.js +54 -0
- package/dist/trust-scoring.d.ts +9 -0
- package/dist/trust-scoring.js +208 -0
- package/dist/trust.d.ts +4 -32
- package/dist/trust.js +29 -432
- package/dist/types/app.d.ts +30 -0
- package/dist/types/app.js +2 -0
- package/dist/types/config.d.ts +25 -0
- package/dist/types/config.js +2 -0
- package/dist/types/core.d.ts +100 -0
- package/dist/types/core.js +2 -0
- package/dist/types/diff.d.ts +55 -0
- package/dist/types/diff.js +2 -0
- package/dist/types/plugin.d.ts +41 -0
- package/dist/types/plugin.js +2 -0
- package/dist/types/trust.d.ts +120 -0
- package/dist/types/trust.js +2 -0
- package/dist/types.d.ts +8 -365
- package/docs/release-notes-draft.md +40 -0
- package/docs/rules-catalog.md +49 -0
- package/docs/trust-core-release-checklist.md +37 -5
- package/package.json +3 -2
- package/packages/vscode-drift/src/code-actions.ts +1 -1
- package/schemas/drift-ai-output.v1.json +162 -0
- package/schemas/drift-report.v1.json +151 -0
- package/schemas/drift-trust.v1.json +131 -0
- package/scripts/smoke-repo.mjs +394 -0
- package/src/benchmark.ts +75 -53
- package/src/cli.ts +285 -13
- package/src/config.ts +19 -2
- package/src/diff.ts +57 -48
- package/src/doctor.ts +173 -0
- package/src/format.ts +81 -0
- package/src/guard-types.ts +64 -0
- package/src/guard.ts +324 -0
- package/src/index.ts +35 -0
- package/src/init.ts +298 -0
- package/src/map-cycles.ts +38 -0
- package/src/map-svg.ts +124 -0
- package/src/map.ts +111 -142
- package/src/metrics.ts +78 -59
- package/src/output-metadata.ts +30 -0
- package/src/plugins-capabilities.ts +36 -0
- package/src/plugins-messages.ts +35 -0
- package/src/plugins-rules.ts +296 -0
- package/src/plugins.ts +76 -283
- package/src/reporter-constants.ts +46 -0
- package/src/reporter.ts +64 -65
- package/src/review.ts +4 -2
- package/src/rules/phase3-configurable.ts +39 -26
- package/src/saas/constants.ts +56 -0
- package/src/saas/dashboard.ts +172 -0
- package/src/saas/errors.ts +45 -0
- package/src/saas/helpers.ts +140 -0
- package/src/saas/ingest.ts +278 -0
- package/src/saas/organization.ts +99 -0
- package/src/saas/plan-change.ts +19 -0
- package/src/saas/store.ts +172 -0
- package/src/saas/types.ts +216 -0
- package/src/saas.ts +49 -1031
- package/src/sarif.ts +232 -0
- package/src/trust-advanced.ts +99 -0
- package/src/trust-kpi-fs.ts +169 -0
- package/src/trust-kpi-parse.ts +219 -0
- package/src/trust-kpi-types.ts +19 -0
- package/src/trust-kpi.ts +8 -316
- package/src/trust-policy.ts +246 -0
- package/src/trust-render.ts +61 -0
- package/src/trust-scoring.ts +231 -0
- package/src/trust.ts +62 -576
- package/src/types/app.ts +30 -0
- package/src/types/config.ts +27 -0
- package/src/types/core.ts +105 -0
- package/src/types/diff.ts +61 -0
- package/src/types/plugin.ts +46 -0
- package/src/types/trust.ts +134 -0
- package/src/types.ts +78 -409
- package/tests/cli-sarif.test.ts +92 -0
- package/tests/format.test.ts +157 -0
- package/tests/new-features.test.ts +10 -2
- package/tests/phase1-init-doctor-guard.test.ts +199 -0
- package/tests/sarif.test.ts +160 -0
- package/tests/trust-kpi.test.ts +31 -4
- package/tests/trust.test.ts +18 -0
package/dist/cli.js
CHANGED
|
@@ -14,6 +14,7 @@ import { printConsole, printDiff } from './printer.js';
|
|
|
14
14
|
import { loadConfig } from './config.js';
|
|
15
15
|
import { extractFilesAtRef, cleanupTempDir } from './git.js';
|
|
16
16
|
import { computeDiff } from './diff.js';
|
|
17
|
+
import { runGuard } from './guard.js';
|
|
17
18
|
import { generateHtmlReport } from './report.js';
|
|
18
19
|
import { generateBadge } from './badge.js';
|
|
19
20
|
import { emitCIAnnotations, printCISummary } from './ci.js';
|
|
@@ -24,6 +25,11 @@ import { generateArchitectureMap } from './map.js';
|
|
|
24
25
|
import { changeOrganizationPlan, generateSaasDashboardHtml, getOrganizationEffectiveLimits, getOrganizationUsageSnapshot, getSaasSummary, ingestSnapshotFromReport, listOrganizationPlanChanges, } from './saas.js';
|
|
25
26
|
import { buildTrustReport, explainTrustGatePolicy, formatTrustGatePolicyExplanation, formatTrustJson, renderTrustOutput, shouldFailTrustGate, normalizeMergeRiskLevel, MERGE_RISK_ORDER, detectBranchName, } from './trust.js';
|
|
26
27
|
import { computeTrustKpis, formatTrustKpiConsole, formatTrustKpiJson } from './trust-kpi.js';
|
|
28
|
+
import { runBenchmarkCli } from './benchmark.js';
|
|
29
|
+
import { runInit, INIT_PRESETS } from './init.js';
|
|
30
|
+
import { runDoctor } from './doctor.js';
|
|
31
|
+
import { resolveOutputFormat } from './format.js';
|
|
32
|
+
import { toSarif, diffToSarif } from './sarif.js';
|
|
27
33
|
const program = new Command();
|
|
28
34
|
function parseOptionalPositiveInt(rawValue, flagName) {
|
|
29
35
|
if (rawValue == null)
|
|
@@ -51,6 +57,77 @@ function addResourceOptions(command) {
|
|
|
51
57
|
.option('--max-file-size-kb <n>', 'Skip files above this size and report diagnostics')
|
|
52
58
|
.option('--with-semantic-duplication', 'Keep semantic-duplication rule enabled in low-memory mode');
|
|
53
59
|
}
|
|
60
|
+
function parseOptionalNumber(rawValue, flagName) {
|
|
61
|
+
if (rawValue == null)
|
|
62
|
+
return undefined;
|
|
63
|
+
const value = Number(rawValue);
|
|
64
|
+
if (!Number.isFinite(value)) {
|
|
65
|
+
throw new Error(`${flagName} must be a valid number`);
|
|
66
|
+
}
|
|
67
|
+
return value;
|
|
68
|
+
}
|
|
69
|
+
function parseBySeverity(rawValue) {
|
|
70
|
+
if (rawValue == null)
|
|
71
|
+
return undefined;
|
|
72
|
+
const spec = rawValue.trim();
|
|
73
|
+
if (!spec) {
|
|
74
|
+
throw new Error('--by-severity must not be empty. Expected format: error=0,warning=2,info=5');
|
|
75
|
+
}
|
|
76
|
+
const thresholds = {};
|
|
77
|
+
const seen = new Set();
|
|
78
|
+
for (const segment of spec.split(',')) {
|
|
79
|
+
const pair = segment.trim();
|
|
80
|
+
if (!pair)
|
|
81
|
+
continue;
|
|
82
|
+
const equalIndex = pair.indexOf('=');
|
|
83
|
+
if (equalIndex <= 0 || equalIndex === pair.length - 1) {
|
|
84
|
+
throw new Error(`Invalid --by-severity entry '${pair}'. Expected key=value (e.g. warning=2).`);
|
|
85
|
+
}
|
|
86
|
+
const key = pair.slice(0, equalIndex).trim().toLowerCase();
|
|
87
|
+
const rawThreshold = pair.slice(equalIndex + 1).trim();
|
|
88
|
+
if (key !== 'error' && key !== 'warning' && key !== 'info') {
|
|
89
|
+
throw new Error(`Invalid --by-severity key '${key}'. Allowed keys: error, warning, info.`);
|
|
90
|
+
}
|
|
91
|
+
if (seen.has(key)) {
|
|
92
|
+
throw new Error(`Duplicate --by-severity key '${key}'.`);
|
|
93
|
+
}
|
|
94
|
+
const threshold = Number(rawThreshold);
|
|
95
|
+
if (!Number.isFinite(threshold)) {
|
|
96
|
+
throw new Error(`Invalid --by-severity value for '${key}': '${rawThreshold}'. Must be a valid number.`);
|
|
97
|
+
}
|
|
98
|
+
const severityKey = key;
|
|
99
|
+
thresholds[severityKey] = threshold;
|
|
100
|
+
seen.add(severityKey);
|
|
101
|
+
}
|
|
102
|
+
if (seen.size === 0) {
|
|
103
|
+
throw new Error('--by-severity must include at least one threshold. Example: error=0,warning=2');
|
|
104
|
+
}
|
|
105
|
+
return thresholds;
|
|
106
|
+
}
|
|
107
|
+
function formatSigned(value) {
|
|
108
|
+
return value > 0 ? `+${value}` : `${value}`;
|
|
109
|
+
}
|
|
110
|
+
function printGuardSummary(result) {
|
|
111
|
+
const modeLabel = result.mode === 'diff' ? `diff (${result.baseRef ?? 'unknown base'})` : 'baseline';
|
|
112
|
+
const statusLabel = result.passed ? 'PASS' : 'FAIL';
|
|
113
|
+
process.stdout.write('\n');
|
|
114
|
+
process.stdout.write(`Guard mode: ${modeLabel}\n`);
|
|
115
|
+
process.stdout.write(`Result: ${statusLabel}\n`);
|
|
116
|
+
process.stdout.write(`Score delta: ${formatSigned(result.metrics.scoreDelta)}\n`);
|
|
117
|
+
process.stdout.write(`Total issues delta: ${formatSigned(result.metrics.totalIssuesDelta)}\n`);
|
|
118
|
+
process.stdout.write(`Severity delta: error=${formatSigned(result.metrics.severityDelta.error)}, warning=${formatSigned(result.metrics.severityDelta.warning)}, info=${formatSigned(result.metrics.severityDelta.info)}\n`);
|
|
119
|
+
if (result.mode === 'baseline' && result.baselinePath) {
|
|
120
|
+
process.stdout.write(`Baseline file: ${result.baselinePath}\n`);
|
|
121
|
+
}
|
|
122
|
+
if (result.checks.length === 0) {
|
|
123
|
+
process.stdout.write('Checks: none configured\n');
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
process.stdout.write('Checks:\n');
|
|
127
|
+
for (const check of result.checks) {
|
|
128
|
+
process.stdout.write(` - [${check.passed ? 'PASS' : 'FAIL'}] ${check.id}: ${check.message} (actual=${check.actual}, limit=${check.limit})\n`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
54
131
|
function parseTrustGateOverrides(options) {
|
|
55
132
|
const cliMinTrust = options.minTrust ? Number(options.minTrust) : undefined;
|
|
56
133
|
if (options.minTrust && Number.isNaN(cliMinTrust)) {
|
|
@@ -95,6 +172,7 @@ addResourceOptions(program
|
|
|
95
172
|
.command('scan [path]', { isDefault: true })
|
|
96
173
|
.description('Scan a directory for vibe coding drift')
|
|
97
174
|
.option('-o, --output <file>', 'Write report to a Markdown file')
|
|
175
|
+
.option('--format <type>', 'Output format: console|json|markdown|ai|sarif')
|
|
98
176
|
.option('--json', 'Output raw JSON report')
|
|
99
177
|
.option('--ai', 'Output AI-optimized JSON for LLM consumption')
|
|
100
178
|
.option('--fix', 'Show fix suggestions for each issue')
|
|
@@ -106,15 +184,33 @@ addResourceOptions(program
|
|
|
106
184
|
const files = analyzeProject(resolvedPath, config, resolveAnalysisOptions(options));
|
|
107
185
|
process.stderr.write(` Found ${files.length} TypeScript file(s)\n\n`);
|
|
108
186
|
const report = buildReport(resolvedPath, files);
|
|
109
|
-
|
|
187
|
+
const format = resolveOutputFormat({
|
|
188
|
+
command: 'scan',
|
|
189
|
+
format: options.format,
|
|
190
|
+
supported: ['console', 'json', 'markdown', 'ai', 'sarif'],
|
|
191
|
+
legacyAliases: [
|
|
192
|
+
{ flag: 'json', used: options.json, mapsTo: 'json' },
|
|
193
|
+
{ flag: 'ai', used: options.ai, mapsTo: 'ai' },
|
|
194
|
+
],
|
|
195
|
+
onWarning: (message) => process.stderr.write(`${message}\n`),
|
|
196
|
+
});
|
|
197
|
+
if (format === 'sarif') {
|
|
198
|
+
process.stdout.write(`${JSON.stringify(toSarif(report), null, 2)}\n`);
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
if (format === 'ai') {
|
|
110
202
|
const aiOutput = formatAIOutput(report);
|
|
111
203
|
process.stdout.write(JSON.stringify(aiOutput, null, 2));
|
|
112
204
|
return;
|
|
113
205
|
}
|
|
114
|
-
if (
|
|
206
|
+
if (format === 'json') {
|
|
115
207
|
process.stdout.write(JSON.stringify(report, null, 2));
|
|
116
208
|
return;
|
|
117
209
|
}
|
|
210
|
+
if (format === 'markdown') {
|
|
211
|
+
process.stdout.write(`${formatMarkdown(report)}\n`);
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
118
214
|
printConsole(report, { showFix: options.fix });
|
|
119
215
|
if (options.output) {
|
|
120
216
|
const md = formatMarkdown(report);
|
|
@@ -128,9 +224,31 @@ addResourceOptions(program
|
|
|
128
224
|
process.exit(1);
|
|
129
225
|
}
|
|
130
226
|
}));
|
|
227
|
+
program
|
|
228
|
+
.command('init')
|
|
229
|
+
.description('Initialize drift configuration with presets and scaffolding')
|
|
230
|
+
.option('--preset <type>', `Scaffold config with preset: ${INIT_PRESETS.join(', ')}`)
|
|
231
|
+
.option('--ci', 'Generate GitHub Actions workflow for drift review')
|
|
232
|
+
.option('--baseline', 'Create drift-baseline.json with current project score')
|
|
233
|
+
.action(async (options) => {
|
|
234
|
+
const projectRoot = resolve('.');
|
|
235
|
+
try {
|
|
236
|
+
await runInit(projectRoot, {
|
|
237
|
+
preset: options.preset,
|
|
238
|
+
ci: options.ci,
|
|
239
|
+
baseline: options.baseline,
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
catch (err) {
|
|
243
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
244
|
+
process.stderr.write(`\n Error: ${message}\n\n`);
|
|
245
|
+
process.exit(1);
|
|
246
|
+
}
|
|
247
|
+
});
|
|
131
248
|
addResourceOptions(program
|
|
132
249
|
.command('diff [ref]')
|
|
133
250
|
.description('Compare current state against a git ref (default: HEAD~1)')
|
|
251
|
+
.option('--format <type>', 'Output format: console|json|markdown|ai|sarif')
|
|
134
252
|
.option('--json', 'Output raw JSON diff')
|
|
135
253
|
.action(async (ref, options) => {
|
|
136
254
|
const baseRef = ref ?? 'HEAD~1';
|
|
@@ -139,6 +257,13 @@ addResourceOptions(program
|
|
|
139
257
|
let tempDir;
|
|
140
258
|
try {
|
|
141
259
|
process.stderr.write(`\nComputing diff: HEAD vs ${baseRef}...\n\n`);
|
|
260
|
+
const format = resolveOutputFormat({
|
|
261
|
+
command: 'diff',
|
|
262
|
+
format: options.format,
|
|
263
|
+
supported: ['console', 'json', 'sarif'],
|
|
264
|
+
legacyAliases: [{ flag: 'json', used: options.json, mapsTo: 'json' }],
|
|
265
|
+
onWarning: (message) => process.stderr.write(`${message}\n`),
|
|
266
|
+
});
|
|
142
267
|
// Scan current state
|
|
143
268
|
const config = await loadConfig(projectPath);
|
|
144
269
|
const currentFiles = analyzeProject(projectPath, config, analysisOptions);
|
|
@@ -157,7 +282,10 @@ addResourceOptions(program
|
|
|
157
282
|
})),
|
|
158
283
|
};
|
|
159
284
|
const diff = computeDiff(remappedBase, currentReport, baseRef);
|
|
160
|
-
if (
|
|
285
|
+
if (format === 'sarif') {
|
|
286
|
+
process.stdout.write(`${JSON.stringify(diffToSarif(diff), null, 2)}\n`);
|
|
287
|
+
}
|
|
288
|
+
else if (format === 'json') {
|
|
161
289
|
process.stdout.write(JSON.stringify(diff, null, 2) + '\n');
|
|
162
290
|
}
|
|
163
291
|
else {
|
|
@@ -174,21 +302,81 @@ addResourceOptions(program
|
|
|
174
302
|
cleanupTempDir(tempDir);
|
|
175
303
|
}
|
|
176
304
|
}));
|
|
305
|
+
addResourceOptions(program
|
|
306
|
+
.command('guard [path]')
|
|
307
|
+
.description('Evaluate drift guard thresholds against diff or baseline')
|
|
308
|
+
.option('--base <ref>', 'Git base ref for diff guard mode')
|
|
309
|
+
.option('--baseline <file>', 'Baseline file path (default: drift-baseline.json)')
|
|
310
|
+
.option('--budget <n>', 'Allowed score delta budget')
|
|
311
|
+
.option('--by-severity <spec>', 'Severity thresholds: error=0,warning=2,info=5')
|
|
312
|
+
.option('--json', 'Output raw JSON guard result')
|
|
313
|
+
.action(async (targetPath, options) => {
|
|
314
|
+
try {
|
|
315
|
+
const resolvedPath = resolve(targetPath ?? '.');
|
|
316
|
+
const budget = parseOptionalNumber(options.budget, '--budget');
|
|
317
|
+
const bySeverity = parseBySeverity(options.bySeverity);
|
|
318
|
+
const result = await runGuard(resolvedPath, {
|
|
319
|
+
baseRef: options.base,
|
|
320
|
+
baselinePath: options.baseline,
|
|
321
|
+
budget,
|
|
322
|
+
bySeverity,
|
|
323
|
+
analysis: resolveAnalysisOptions(options),
|
|
324
|
+
});
|
|
325
|
+
if (options.json) {
|
|
326
|
+
process.stdout.write(JSON.stringify(result, null, 2) + '\n');
|
|
327
|
+
}
|
|
328
|
+
else {
|
|
329
|
+
printGuardSummary(result);
|
|
330
|
+
}
|
|
331
|
+
if (!result.passed) {
|
|
332
|
+
process.exit(1);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
catch (err) {
|
|
336
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
337
|
+
process.stderr.write(`\n Error: ${message}\n\n`);
|
|
338
|
+
process.exit(1);
|
|
339
|
+
}
|
|
340
|
+
}));
|
|
341
|
+
program
|
|
342
|
+
.command('benchmark')
|
|
343
|
+
.description('Run benchmark harness for scan/review/trust commands')
|
|
344
|
+
.allowUnknownOption(true)
|
|
345
|
+
.action(async () => {
|
|
346
|
+
await runBenchmarkCli(process.argv.slice(3));
|
|
347
|
+
});
|
|
177
348
|
program
|
|
178
349
|
.command('review')
|
|
179
350
|
.description('Review drift against a base ref and output PR markdown')
|
|
180
351
|
.option('--base <ref>', 'Git base ref to compare against', 'origin/main')
|
|
352
|
+
.option('--format <type>', 'Output format: console|json|markdown|ai|sarif')
|
|
181
353
|
.option('--json', 'Output structured review JSON')
|
|
182
354
|
.option('--comment', 'Output markdown comment body')
|
|
183
355
|
.option('--fail-on <n>', 'Exit with code 1 if score delta is >= n')
|
|
184
356
|
.action(async (options) => {
|
|
185
357
|
try {
|
|
186
358
|
const review = await generateReview(resolve('.'), options.base);
|
|
187
|
-
|
|
359
|
+
const format = resolveOutputFormat({
|
|
360
|
+
command: 'review',
|
|
361
|
+
format: options.format,
|
|
362
|
+
supported: ['console', 'json', 'markdown', 'sarif'],
|
|
363
|
+
legacyAliases: [
|
|
364
|
+
{ flag: 'json', used: options.json, mapsTo: 'json' },
|
|
365
|
+
{ flag: 'comment', used: options.comment, mapsTo: 'markdown' },
|
|
366
|
+
],
|
|
367
|
+
onWarning: (message) => process.stderr.write(`${message}\n`),
|
|
368
|
+
});
|
|
369
|
+
if (format === 'sarif') {
|
|
370
|
+
process.stdout.write(`${JSON.stringify(diffToSarif(review.diff), null, 2)}\n`);
|
|
371
|
+
}
|
|
372
|
+
else if (format === 'json') {
|
|
188
373
|
process.stdout.write(JSON.stringify(review, null, 2) + '\n');
|
|
189
374
|
}
|
|
375
|
+
else if (format === 'markdown') {
|
|
376
|
+
process.stdout.write(`${review.markdown}\n`);
|
|
377
|
+
}
|
|
190
378
|
else {
|
|
191
|
-
process.stdout.write(
|
|
379
|
+
process.stdout.write(`${review.summary}\n\n${review.markdown}\n`);
|
|
192
380
|
}
|
|
193
381
|
const failOn = options.failOn ? Number(options.failOn) : undefined;
|
|
194
382
|
if (typeof failOn === 'number' && !Number.isNaN(failOn) && review.totalDelta >= failOn) {
|
|
@@ -205,6 +393,7 @@ addResourceOptions(program
|
|
|
205
393
|
.command('trust [path]')
|
|
206
394
|
.description('Compute merge trust baseline from drift signals')
|
|
207
395
|
.option('--base <ref>', 'Git base ref for diff-aware trust scoring')
|
|
396
|
+
.option('--format <type>', 'Output format: console|json|markdown|ai|sarif')
|
|
208
397
|
.option('--json', 'Output structured trust JSON')
|
|
209
398
|
.option('--markdown', 'Output trust report as markdown (PR comment ready)')
|
|
210
399
|
.option('-o, --output <file>', 'Write trust output to file')
|
|
@@ -284,7 +473,22 @@ addResourceOptions(program
|
|
|
284
473
|
snapshots,
|
|
285
474
|
},
|
|
286
475
|
});
|
|
287
|
-
const
|
|
476
|
+
const format = resolveOutputFormat({
|
|
477
|
+
command: 'trust',
|
|
478
|
+
format: options.format,
|
|
479
|
+
supported: ['console', 'json', 'markdown', 'sarif'],
|
|
480
|
+
legacyAliases: [
|
|
481
|
+
{ flag: 'json', used: options.json, mapsTo: 'json' },
|
|
482
|
+
{ flag: 'markdown', used: options.markdown, mapsTo: 'markdown' },
|
|
483
|
+
],
|
|
484
|
+
onWarning: (message) => process.stderr.write(`${message}\n`),
|
|
485
|
+
});
|
|
486
|
+
const rendered = format === 'sarif'
|
|
487
|
+
? `${JSON.stringify(toSarif(report), null, 2)}\n`
|
|
488
|
+
: `${renderTrustOutput(trust, {
|
|
489
|
+
json: format === 'json',
|
|
490
|
+
markdown: format === 'markdown',
|
|
491
|
+
})}\n`;
|
|
288
492
|
process.stdout.write(rendered);
|
|
289
493
|
if (options.output) {
|
|
290
494
|
const outPath = resolve(options.output);
|
|
@@ -378,6 +582,20 @@ program
|
|
|
378
582
|
process.exit(1);
|
|
379
583
|
}
|
|
380
584
|
});
|
|
585
|
+
program
|
|
586
|
+
.command('doctor')
|
|
587
|
+
.description('Run project environment diagnostics')
|
|
588
|
+
.option('--json', 'Output structured doctor JSON')
|
|
589
|
+
.action(async (opts) => {
|
|
590
|
+
try {
|
|
591
|
+
await runDoctor(process.cwd(), { json: opts.json });
|
|
592
|
+
}
|
|
593
|
+
catch (err) {
|
|
594
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
595
|
+
process.stderr.write(`\n Error: ${message}\n\n`);
|
|
596
|
+
process.exitCode = 1;
|
|
597
|
+
}
|
|
598
|
+
});
|
|
381
599
|
program
|
|
382
600
|
.command('kpi <path>')
|
|
383
601
|
.description('Aggregate trust KPIs from trust JSON artifacts')
|
|
@@ -442,14 +660,31 @@ addResourceOptions(program
|
|
|
442
660
|
addResourceOptions(program
|
|
443
661
|
.command('ci [path]')
|
|
444
662
|
.description('Emit GitHub Actions annotations and step summary')
|
|
663
|
+
.option('--format <type>', 'Output format: console|json|markdown|ai|sarif')
|
|
664
|
+
.option('--json', 'Output raw JSON report (legacy alias for --format json)')
|
|
445
665
|
.option('--min-score <n>', 'Exit with code 1 if overall score exceeds this threshold', '0')
|
|
446
666
|
.action(async (targetPath, options) => {
|
|
447
667
|
const resolvedPath = resolve(targetPath ?? '.');
|
|
448
668
|
const config = await loadConfig(resolvedPath);
|
|
449
669
|
const files = analyzeProject(resolvedPath, config, resolveAnalysisOptions(options));
|
|
450
670
|
const report = buildReport(resolvedPath, files);
|
|
451
|
-
|
|
452
|
-
|
|
671
|
+
const format = resolveOutputFormat({
|
|
672
|
+
command: 'ci',
|
|
673
|
+
format: options.format,
|
|
674
|
+
supported: ['console', 'json', 'sarif'],
|
|
675
|
+
legacyAliases: [{ flag: 'json', used: options.json, mapsTo: 'json' }],
|
|
676
|
+
onWarning: (message) => process.stderr.write(`${message}\n`),
|
|
677
|
+
});
|
|
678
|
+
if (format === 'sarif') {
|
|
679
|
+
process.stdout.write(`${JSON.stringify(toSarif(report), null, 2)}\n`);
|
|
680
|
+
}
|
|
681
|
+
else if (format === 'json') {
|
|
682
|
+
process.stdout.write(JSON.stringify(report, null, 2) + '\n');
|
|
683
|
+
}
|
|
684
|
+
else {
|
|
685
|
+
emitCIAnnotations(report);
|
|
686
|
+
printCISummary(report);
|
|
687
|
+
}
|
|
453
688
|
const minScore = Number(options.minScore);
|
|
454
689
|
if (minScore > 0 && report.totalScore > minScore) {
|
|
455
690
|
process.exit(1);
|
package/dist/config.js
CHANGED
|
@@ -1,6 +1,19 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs';
|
|
2
2
|
import { join, resolve } from 'node:path';
|
|
3
3
|
import { pathToFileURL } from 'node:url';
|
|
4
|
+
function normalizeLegacyConfig(config) {
|
|
5
|
+
if (config.modules !== undefined) {
|
|
6
|
+
return config;
|
|
7
|
+
}
|
|
8
|
+
const legacyModules = config.moduleBoundaries ?? config.boundaries;
|
|
9
|
+
if (!legacyModules || legacyModules.length === 0) {
|
|
10
|
+
return config;
|
|
11
|
+
}
|
|
12
|
+
return {
|
|
13
|
+
...config,
|
|
14
|
+
modules: legacyModules,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
4
17
|
/**
|
|
5
18
|
* Load drift.config.ts / .js / .json from the given project root.
|
|
6
19
|
* Returns undefined if no config file is found.
|
|
@@ -23,13 +36,14 @@ export async function loadConfig(projectRoot) {
|
|
|
23
36
|
const ext = candidate.split('.').pop();
|
|
24
37
|
if (ext === 'json') {
|
|
25
38
|
const { readFileSync } = await import('node:fs');
|
|
26
|
-
|
|
39
|
+
const rawConfig = JSON.parse(readFileSync(candidate, 'utf-8'));
|
|
40
|
+
return normalizeLegacyConfig(rawConfig);
|
|
27
41
|
}
|
|
28
42
|
// .ts / .js — dynamic import via file URL
|
|
29
43
|
const fileUrl = pathToFileURL(resolve(candidate)).href;
|
|
30
44
|
const mod = await import(fileUrl);
|
|
31
45
|
const config = mod.default ?? mod;
|
|
32
|
-
return config;
|
|
46
|
+
return normalizeLegacyConfig(config);
|
|
33
47
|
}
|
|
34
48
|
catch { // drift-ignore
|
|
35
49
|
// drift-ignore: catch-swallow — config is optional; load failure is non-fatal
|
package/dist/diff.js
CHANGED
|
@@ -8,6 +8,43 @@ function normalizeIssueText(value) {
|
|
|
8
8
|
.replace(/\s+/g, ' ')
|
|
9
9
|
.trim();
|
|
10
10
|
}
|
|
11
|
+
const SNIPPET_PREFIX_LENGTH = 80;
|
|
12
|
+
function strictIssueKey(i) {
|
|
13
|
+
return `${i.rule}:${i.line}:${i.column}`;
|
|
14
|
+
}
|
|
15
|
+
function normalizedIssueKey(i) {
|
|
16
|
+
const normalizedMessage = normalizeIssueText(i.message);
|
|
17
|
+
const normalizedSnippetPrefix = normalizeIssueText(i.snippet).slice(0, SNIPPET_PREFIX_LENGTH);
|
|
18
|
+
return `${i.rule}:${i.severity}:${i.line}:${normalizedMessage}:${normalizedSnippetPrefix}`;
|
|
19
|
+
}
|
|
20
|
+
function buildIssueIndex(issues, getKey, skip) {
|
|
21
|
+
const index = new Map();
|
|
22
|
+
for (const [idx, issue] of issues.entries()) {
|
|
23
|
+
if (skip?.has(idx))
|
|
24
|
+
continue;
|
|
25
|
+
const key = getKey(issue);
|
|
26
|
+
const bucket = index.get(key);
|
|
27
|
+
if (bucket)
|
|
28
|
+
bucket.push(idx);
|
|
29
|
+
else
|
|
30
|
+
index.set(key, [idx]);
|
|
31
|
+
}
|
|
32
|
+
return index;
|
|
33
|
+
}
|
|
34
|
+
function matchIssues(currentIssues, index, state, getKey) {
|
|
35
|
+
for (const [currentIndex, issue] of currentIssues.entries()) {
|
|
36
|
+
if (state.matchedCurrentIndexes.has(currentIndex))
|
|
37
|
+
continue;
|
|
38
|
+
const bucket = index.get(getKey(issue));
|
|
39
|
+
if (!bucket || bucket.length === 0)
|
|
40
|
+
continue;
|
|
41
|
+
const matchedIndex = bucket.shift();
|
|
42
|
+
if (matchedIndex === undefined)
|
|
43
|
+
continue;
|
|
44
|
+
state.matchedBaseIndexes.add(matchedIndex);
|
|
45
|
+
state.matchedCurrentIndexes.add(currentIndex);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
11
48
|
/**
|
|
12
49
|
* Compute the diff between two DriftReports.
|
|
13
50
|
*
|
|
@@ -26,58 +63,13 @@ function computeFileDiff(filePath, baseFile, currentFile) {
|
|
|
26
63
|
const scoreDelta = scoreAfter - scoreBefore;
|
|
27
64
|
const baseIssues = baseFile?.issues ?? [];
|
|
28
65
|
const currentIssues = currentFile?.issues ?? [];
|
|
29
|
-
const strictIssueKey = (i) => `${i.rule}:${i.line}:${i.column}`;
|
|
30
|
-
const normalizedIssueKey = (i) => {
|
|
31
|
-
const normalizedMessage = normalizeIssueText(i.message);
|
|
32
|
-
const normalizedSnippetPrefix = normalizeIssueText(i.snippet).slice(0, 80);
|
|
33
|
-
return `${i.rule}:${i.severity}:${i.line}:${normalizedMessage}:${normalizedSnippetPrefix}`;
|
|
34
|
-
};
|
|
35
66
|
const matchedBaseIndexes = new Set();
|
|
36
67
|
const matchedCurrentIndexes = new Set();
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
bucket.push(index);
|
|
43
|
-
else
|
|
44
|
-
baseStrictIndex.set(key, [index]);
|
|
45
|
-
}
|
|
46
|
-
for (const [currentIndex, issue] of currentIssues.entries()) {
|
|
47
|
-
const key = strictIssueKey(issue);
|
|
48
|
-
const bucket = baseStrictIndex.get(key);
|
|
49
|
-
if (!bucket || bucket.length === 0)
|
|
50
|
-
continue;
|
|
51
|
-
const matchedIndex = bucket.shift();
|
|
52
|
-
if (matchedIndex === undefined)
|
|
53
|
-
continue;
|
|
54
|
-
matchedBaseIndexes.add(matchedIndex);
|
|
55
|
-
matchedCurrentIndexes.add(currentIndex);
|
|
56
|
-
}
|
|
57
|
-
const baseNormalizedIndex = new Map();
|
|
58
|
-
for (const [index, issue] of baseIssues.entries()) {
|
|
59
|
-
if (matchedBaseIndexes.has(index))
|
|
60
|
-
continue;
|
|
61
|
-
const key = normalizedIssueKey(issue);
|
|
62
|
-
const bucket = baseNormalizedIndex.get(key);
|
|
63
|
-
if (bucket)
|
|
64
|
-
bucket.push(index);
|
|
65
|
-
else
|
|
66
|
-
baseNormalizedIndex.set(key, [index]);
|
|
67
|
-
}
|
|
68
|
-
for (const [currentIndex, issue] of currentIssues.entries()) {
|
|
69
|
-
if (matchedCurrentIndexes.has(currentIndex))
|
|
70
|
-
continue;
|
|
71
|
-
const key = normalizedIssueKey(issue);
|
|
72
|
-
const bucket = baseNormalizedIndex.get(key);
|
|
73
|
-
if (!bucket || bucket.length === 0)
|
|
74
|
-
continue;
|
|
75
|
-
const matchedIndex = bucket.shift();
|
|
76
|
-
if (matchedIndex === undefined)
|
|
77
|
-
continue;
|
|
78
|
-
matchedBaseIndexes.add(matchedIndex);
|
|
79
|
-
matchedCurrentIndexes.add(currentIndex);
|
|
80
|
-
}
|
|
68
|
+
const matchState = { matchedBaseIndexes, matchedCurrentIndexes };
|
|
69
|
+
const baseStrictIndex = buildIssueIndex(baseIssues, strictIssueKey);
|
|
70
|
+
matchIssues(currentIssues, baseStrictIndex, matchState, strictIssueKey);
|
|
71
|
+
const baseNormalizedIndex = buildIssueIndex(baseIssues, normalizedIssueKey, matchedBaseIndexes);
|
|
72
|
+
matchIssues(currentIssues, baseNormalizedIndex, matchState, normalizedIssueKey);
|
|
81
73
|
const newIssues = currentIssues.filter((_, index) => !matchedCurrentIndexes.has(index));
|
|
82
74
|
const resolvedIssues = baseIssues.filter((_, index) => !matchedBaseIndexes.has(index));
|
|
83
75
|
if (scoreDelta !== 0 || newIssues.length > 0 || resolvedIssues.length > 0) {
|
package/dist/doctor.d.ts
ADDED
package/dist/doctor.js
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import kleur from 'kleur';
|
|
4
|
+
const SOURCE_EXTENSIONS = new Set(['.ts', '.tsx', '.js', '.jsx']);
|
|
5
|
+
const IGNORED_DIRECTORIES = new Set(['node_modules', '.git', 'dist', '.next', 'coverage']);
|
|
6
|
+
const DECIMAL_RADIX = 10;
|
|
7
|
+
const MIN_SUPPORTED_NODE_MAJOR = 18;
|
|
8
|
+
const LOW_MEMORY_SOURCE_FILE_THRESHOLD = 500;
|
|
9
|
+
const DRIFT_CONFIG_CANDIDATES = [
|
|
10
|
+
'drift.config.ts',
|
|
11
|
+
'drift.config.js',
|
|
12
|
+
'drift.config.mjs',
|
|
13
|
+
'drift.config.cjs',
|
|
14
|
+
'drift.config.json',
|
|
15
|
+
];
|
|
16
|
+
function parseNodeMajor(version) {
|
|
17
|
+
const parsed = Number.parseInt(version.replace(/^v/, '').split('.')[0] ?? '0', DECIMAL_RADIX);
|
|
18
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
19
|
+
}
|
|
20
|
+
function detectDriftConfig(projectPath) {
|
|
21
|
+
for (const candidate of DRIFT_CONFIG_CANDIDATES) {
|
|
22
|
+
if (existsSync(join(projectPath, candidate))) {
|
|
23
|
+
return candidate;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
function countSourceFiles(projectPath) {
|
|
29
|
+
let total = 0;
|
|
30
|
+
const stack = [projectPath];
|
|
31
|
+
while (stack.length > 0) {
|
|
32
|
+
const currentDir = stack.pop();
|
|
33
|
+
if (!currentDir)
|
|
34
|
+
continue;
|
|
35
|
+
const entries = readdirSync(currentDir, { withFileTypes: true });
|
|
36
|
+
for (const entry of entries) {
|
|
37
|
+
if (entry.isDirectory() && !IGNORED_DIRECTORIES.has(entry.name)) {
|
|
38
|
+
stack.push(join(currentDir, entry.name));
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
if (entry.isDirectory()) {
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
if (!entry.isFile())
|
|
45
|
+
continue;
|
|
46
|
+
const lastDot = entry.name.lastIndexOf('.');
|
|
47
|
+
if (lastDot === -1)
|
|
48
|
+
continue;
|
|
49
|
+
const extension = entry.name.slice(lastDot);
|
|
50
|
+
if (SOURCE_EXTENSIONS.has(extension)) {
|
|
51
|
+
total += 1;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return total;
|
|
56
|
+
}
|
|
57
|
+
function buildDoctorReport(projectPath) {
|
|
58
|
+
const nodeMajor = parseNodeMajor(process.version);
|
|
59
|
+
const packageJsonPath = join(projectPath, 'package.json');
|
|
60
|
+
const packageJsonFound = existsSync(packageJsonPath);
|
|
61
|
+
let esm = false;
|
|
62
|
+
if (packageJsonFound) {
|
|
63
|
+
const parsed = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
|
|
64
|
+
esm = parsed.type === 'module';
|
|
65
|
+
}
|
|
66
|
+
const sourceFilesCount = countSourceFiles(projectPath);
|
|
67
|
+
return {
|
|
68
|
+
targetPath: projectPath,
|
|
69
|
+
node: {
|
|
70
|
+
version: process.version,
|
|
71
|
+
major: nodeMajor,
|
|
72
|
+
supported: nodeMajor >= MIN_SUPPORTED_NODE_MAJOR,
|
|
73
|
+
},
|
|
74
|
+
project: {
|
|
75
|
+
packageJsonFound,
|
|
76
|
+
esm,
|
|
77
|
+
tsconfigFound: existsSync(join(projectPath, 'tsconfig.json')),
|
|
78
|
+
sourceFilesCount,
|
|
79
|
+
lowMemorySuggested: sourceFilesCount > LOW_MEMORY_SOURCE_FILE_THRESHOLD,
|
|
80
|
+
driftConfigFile: detectDriftConfig(projectPath),
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
function printConsoleReport(report) {
|
|
85
|
+
const icons = {
|
|
86
|
+
check: kleur.green('✓'),
|
|
87
|
+
warn: kleur.yellow('⚠'),
|
|
88
|
+
error: kleur.red('✗'),
|
|
89
|
+
info: kleur.cyan('ℹ'),
|
|
90
|
+
};
|
|
91
|
+
process.stdout.write('\n');
|
|
92
|
+
process.stdout.write(`${kleur.bold().white('drift doctor')} ${kleur.gray('- environment diagnostics')}\n\n`);
|
|
93
|
+
const nodeStatus = report.node.supported
|
|
94
|
+
? `${icons.check} ${kleur.green('Node runtime supported')}`
|
|
95
|
+
: `${icons.warn} ${kleur.yellow('Node runtime below recommended minimum (>=18)')}`;
|
|
96
|
+
process.stdout.write(`${nodeStatus} ${kleur.gray(`(${report.node.version})`)}\n`);
|
|
97
|
+
if (report.project.packageJsonFound) {
|
|
98
|
+
process.stdout.write(`${icons.check} package.json found\n`);
|
|
99
|
+
process.stdout.write(`${icons.info} ESM mode: ${report.project.esm ? kleur.green('yes') : kleur.yellow('no')}\n`);
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
process.stdout.write(`${icons.warn} package.json not found\n`);
|
|
103
|
+
process.stdout.write(`${icons.info} ESM mode: ${kleur.gray('unknown')}\n`);
|
|
104
|
+
}
|
|
105
|
+
if (report.project.tsconfigFound) {
|
|
106
|
+
process.stdout.write(`${icons.check} tsconfig.json found\n`);
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
process.stdout.write(`${icons.warn} tsconfig.json not found\n`);
|
|
110
|
+
}
|
|
111
|
+
process.stdout.write(`${icons.info} Source files (.ts/.tsx/.js/.jsx): ${report.project.sourceFilesCount}\n`);
|
|
112
|
+
if (report.project.lowMemorySuggested) {
|
|
113
|
+
process.stdout.write(`${icons.warn} Large codebase detected, consider ${kleur.bold('--low-memory')}\n`);
|
|
114
|
+
}
|
|
115
|
+
if (report.project.driftConfigFile) {
|
|
116
|
+
process.stdout.write(`${icons.check} Drift config: ${report.project.driftConfigFile}\n`);
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
process.stdout.write(`${icons.warn} Drift config not found (drift.config.*)\n`);
|
|
120
|
+
}
|
|
121
|
+
process.stdout.write('\n');
|
|
122
|
+
}
|
|
123
|
+
export async function runDoctor(projectPath, options) {
|
|
124
|
+
const report = buildDoctorReport(projectPath);
|
|
125
|
+
if (options?.json) {
|
|
126
|
+
process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
printConsoleReport(report);
|
|
130
|
+
}
|
|
131
|
+
return 0;
|
|
132
|
+
}
|
|
133
|
+
//# sourceMappingURL=doctor.js.map
|
package/dist/format.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
declare const UNIFIED_FORMAT_VALUES: readonly ["console", "json", "markdown", "ai", "sarif"];
|
|
2
|
+
type UnifiedOutputFormat = (typeof UNIFIED_FORMAT_VALUES)[number];
|
|
3
|
+
type LegacyAlias = {
|
|
4
|
+
flag: string;
|
|
5
|
+
used?: boolean;
|
|
6
|
+
mapsTo: UnifiedOutputFormat;
|
|
7
|
+
};
|
|
8
|
+
interface ResolveOutputFormatOptions {
|
|
9
|
+
command: string;
|
|
10
|
+
format?: string;
|
|
11
|
+
supported: readonly UnifiedOutputFormat[];
|
|
12
|
+
legacyAliases?: LegacyAlias[];
|
|
13
|
+
onWarning?: (message: string) => void;
|
|
14
|
+
}
|
|
15
|
+
export declare function resolveOutputFormat(options: ResolveOutputFormatOptions): UnifiedOutputFormat;
|
|
16
|
+
export {};
|
|
17
|
+
//# sourceMappingURL=format.d.ts.map
|