@xenos1996/usa 2.0.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/LICENSE +21 -0
- package/README.md +335 -0
- package/USA.md +597 -0
- package/dist/bootstrap/catalog.d.ts +46 -0
- package/dist/bootstrap/catalog.d.ts.map +1 -0
- package/dist/bootstrap/catalog.js +312 -0
- package/dist/bootstrap/catalog.js.map +1 -0
- package/dist/bootstrap/index.d.ts +36 -0
- package/dist/bootstrap/index.d.ts.map +1 -0
- package/dist/bootstrap/index.js +177 -0
- package/dist/bootstrap/index.js.map +1 -0
- package/dist/cli.d.ts +5 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +706 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +7 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +88 -0
- package/dist/config.js.map +1 -0
- package/dist/detect/index.d.ts +58 -0
- package/dist/detect/index.d.ts.map +1 -0
- package/dist/detect/index.js +329 -0
- package/dist/detect/index.js.map +1 -0
- package/dist/engine/audit.d.ts +32 -0
- package/dist/engine/audit.d.ts.map +1 -0
- package/dist/engine/audit.js +366 -0
- package/dist/engine/audit.js.map +1 -0
- package/dist/engine/diff.d.ts +24 -0
- package/dist/engine/diff.d.ts.map +1 -0
- package/dist/engine/diff.js +136 -0
- package/dist/engine/diff.js.map +1 -0
- package/dist/engine/evaluate.d.ts +19 -0
- package/dist/engine/evaluate.d.ts.map +1 -0
- package/dist/engine/evaluate.js +377 -0
- package/dist/engine/evaluate.js.map +1 -0
- package/dist/engine/gate.d.ts +19 -0
- package/dist/engine/gate.d.ts.map +1 -0
- package/dist/engine/gate.js +47 -0
- package/dist/engine/gate.js.map +1 -0
- package/dist/engine/loader.d.ts +37 -0
- package/dist/engine/loader.d.ts.map +1 -0
- package/dist/engine/loader.js +388 -0
- package/dist/engine/loader.js.map +1 -0
- package/dist/engine/maturity.d.ts +20 -0
- package/dist/engine/maturity.d.ts.map +1 -0
- package/dist/engine/maturity.js +157 -0
- package/dist/engine/maturity.js.map +1 -0
- package/dist/engine/score.d.ts +19 -0
- package/dist/engine/score.d.ts.map +1 -0
- package/dist/engine/score.js +141 -0
- package/dist/engine/score.js.map +1 -0
- package/dist/engine/sections.d.ts +14 -0
- package/dist/engine/sections.d.ts.map +1 -0
- package/dist/engine/sections.js +50 -0
- package/dist/engine/sections.js.map +1 -0
- package/dist/evolution/benchmark.d.ts +12 -0
- package/dist/evolution/benchmark.d.ts.map +1 -0
- package/dist/evolution/benchmark.js +107 -0
- package/dist/evolution/benchmark.js.map +1 -0
- package/dist/evolution/capability.d.ts +22 -0
- package/dist/evolution/capability.d.ts.map +1 -0
- package/dist/evolution/capability.js +69 -0
- package/dist/evolution/capability.js.map +1 -0
- package/dist/evolution/coverage.d.ts +15 -0
- package/dist/evolution/coverage.d.ts.map +1 -0
- package/dist/evolution/coverage.js +65 -0
- package/dist/evolution/coverage.js.map +1 -0
- package/dist/evolution/gap.d.ts +10 -0
- package/dist/evolution/gap.d.ts.map +1 -0
- package/dist/evolution/gap.js +52 -0
- package/dist/evolution/gap.js.map +1 -0
- package/dist/evolution/propose.d.ts +50 -0
- package/dist/evolution/propose.d.ts.map +1 -0
- package/dist/evolution/propose.js +188 -0
- package/dist/evolution/propose.js.map +1 -0
- package/dist/evolution/queue.d.ts +64 -0
- package/dist/evolution/queue.d.ts.map +1 -0
- package/dist/evolution/queue.js +192 -0
- package/dist/evolution/queue.js.map +1 -0
- package/dist/evolution/release.d.ts +11 -0
- package/dist/evolution/release.d.ts.map +1 -0
- package/dist/evolution/release.js +55 -0
- package/dist/evolution/release.js.map +1 -0
- package/dist/evolution/run.d.ts +88 -0
- package/dist/evolution/run.d.ts.map +1 -0
- package/dist/evolution/run.js +258 -0
- package/dist/evolution/run.js.map +1 -0
- package/dist/evolution/schedule.d.ts +59 -0
- package/dist/evolution/schedule.d.ts.map +1 -0
- package/dist/evolution/schedule.js +77 -0
- package/dist/evolution/schedule.js.map +1 -0
- package/dist/evolution/types.d.ts +174 -0
- package/dist/evolution/types.d.ts.map +1 -0
- package/dist/evolution/types.js +8 -0
- package/dist/evolution/types.js.map +1 -0
- package/dist/index.d.ts +36 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +30 -0
- package/dist/index.js.map +1 -0
- package/dist/learn/index.d.ts +27 -0
- package/dist/learn/index.d.ts.map +1 -0
- package/dist/learn/index.js +196 -0
- package/dist/learn/index.js.map +1 -0
- package/dist/report/markdown.d.ts +16 -0
- package/dist/report/markdown.d.ts.map +1 -0
- package/dist/report/markdown.js +488 -0
- package/dist/report/markdown.js.map +1 -0
- package/dist/snapshot/index.d.ts +55 -0
- package/dist/snapshot/index.d.ts.map +1 -0
- package/dist/snapshot/index.js +76 -0
- package/dist/snapshot/index.js.map +1 -0
- package/dist/store/index.d.ts +31 -0
- package/dist/store/index.d.ts.map +1 -0
- package/dist/store/index.js +69 -0
- package/dist/store/index.js.map +1 -0
- package/dist/types.d.ts +287 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +16 -0
- package/dist/types.js.map +1 -0
- package/dist/util/glob.d.ts +11 -0
- package/dist/util/glob.d.ts.map +1 -0
- package/dist/util/glob.js +86 -0
- package/dist/util/glob.js.map +1 -0
- package/dist/util/project.d.ts +90 -0
- package/dist/util/project.d.ts.map +1 -0
- package/dist/util/project.js +636 -0
- package/dist/util/project.js.map +1 -0
- package/dist/util/yaml.d.ts +28 -0
- package/dist/util/yaml.d.ts.map +1 -0
- package/dist/util/yaml.js +48 -0
- package/dist/util/yaml.js.map +1 -0
- package/package.json +94 -0
- package/rules/core/architecture.yaml +190 -0
- package/rules/core/cicd.yaml +212 -0
- package/rules/core/code-quality.yaml +261 -0
- package/rules/core/dependencies.yaml +132 -0
- package/rules/core/documentation.yaml +119 -0
- package/rules/core/future-readiness.yaml +91 -0
- package/rules/core/release.yaml +131 -0
- package/rules/core/repo.yaml +313 -0
- package/rules/core/security.yaml +496 -0
- package/rules/core/supply-chain.yaml +357 -0
- package/rules/core/testing.yaml +266 -0
- package/rules/detectors.yaml +2028 -0
- package/rules/index.yaml +42 -0
- package/rules/profiles/maturity.yaml +135 -0
- package/rules/stacks/ai-era.yaml +193 -0
- package/rules/stacks/api-backend.yaml +121 -0
- package/rules/stacks/cli.yaml +105 -0
- package/rules/stacks/compliance.yaml +157 -0
- package/rules/stacks/containers.yaml +124 -0
- package/rules/stacks/data.yaml +165 -0
- package/rules/stacks/go.yaml +134 -0
- package/rules/stacks/iac.yaml +128 -0
- package/rules/stacks/jvm.yaml +150 -0
- package/rules/stacks/ml-ai.yaml +140 -0
- package/rules/stacks/mobile.yaml +121 -0
- package/rules/stacks/node-typescript.yaml +166 -0
- package/rules/stacks/python.yaml +175 -0
- package/rules/stacks/rust.yaml +129 -0
- package/rules/stacks/solidity.yaml +139 -0
- package/rules/stacks/swift.yaml +100 -0
- package/rules/stacks/web-frontend.yaml +159 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,706 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
5
|
+
import { runAudit } from './engine/audit.js';
|
|
6
|
+
import { loadRulePacks, loadPackFile } from './engine/loader.js';
|
|
7
|
+
import { loadDetectorFile } from './detect/index.js';
|
|
8
|
+
import { Project } from './util/project.js';
|
|
9
|
+
import { detect } from './detect/index.js';
|
|
10
|
+
import { renderMarkdown, parseTrailer } from './report/markdown.js';
|
|
11
|
+
import { diffReports } from './engine/diff.js';
|
|
12
|
+
import { bootstrapPacks, writeBootstrapPacks } from './bootstrap/index.js';
|
|
13
|
+
import { EXAMPLE_CONFIG, loadConfig } from './config.js';
|
|
14
|
+
import { evaluateGate } from './engine/gate.js';
|
|
15
|
+
import { learnFromReport, renderSuggestions } from './learn/index.js';
|
|
16
|
+
import { Store } from './store/index.js';
|
|
17
|
+
import { runEvolutionCycle } from './evolution/run.js';
|
|
18
|
+
import { capabilityFromPack } from './evolution/capability.js';
|
|
19
|
+
const HERE = path.dirname(fileURLToPath(import.meta.url));
|
|
20
|
+
const DEFAULT_RULES_DIR = path.resolve(HERE, '..', 'rules');
|
|
21
|
+
const VERSION = readVersion();
|
|
22
|
+
const SEVERITY_ORDER = ['CRITICAL', 'HIGH', 'MEDIUM', 'LOW', 'FUTURE'];
|
|
23
|
+
const DEPTHS = ['quick', 'standard', 'deep'];
|
|
24
|
+
const MATURITIES = ['prototype', 'mvp', 'beta', 'production', 'legacy'];
|
|
25
|
+
function parseArgs(argv) {
|
|
26
|
+
const args = { _: [] };
|
|
27
|
+
for (let i = 0; i < argv.length; i++) {
|
|
28
|
+
const tok = argv[i];
|
|
29
|
+
if (!tok.startsWith('--')) {
|
|
30
|
+
args._.push(tok);
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
const body = tok.slice(2);
|
|
34
|
+
if (body.includes('=')) {
|
|
35
|
+
const [k, ...rest] = body.split('=');
|
|
36
|
+
args[k] = rest.join('=');
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
const next = argv[i + 1];
|
|
40
|
+
if (next === undefined || next.startsWith('--')) {
|
|
41
|
+
args[body] = true;
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
args[body] = next;
|
|
45
|
+
i++;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return args;
|
|
49
|
+
}
|
|
50
|
+
function str(args, key, fallback) {
|
|
51
|
+
const v = args[key];
|
|
52
|
+
return typeof v === 'string' ? v : fallback;
|
|
53
|
+
}
|
|
54
|
+
function bool(args, key) {
|
|
55
|
+
return args[key] === true || args[key] === 'true';
|
|
56
|
+
}
|
|
57
|
+
function list(args, key) {
|
|
58
|
+
const v = args[key];
|
|
59
|
+
if (Array.isArray(v))
|
|
60
|
+
return v;
|
|
61
|
+
if (typeof v === 'string')
|
|
62
|
+
return v
|
|
63
|
+
.split(',')
|
|
64
|
+
.map((s) => s.trim())
|
|
65
|
+
.filter(Boolean);
|
|
66
|
+
return [];
|
|
67
|
+
}
|
|
68
|
+
/* -------------------------------------------------------------------- main -- */
|
|
69
|
+
const COMMANDS = {
|
|
70
|
+
audit: cmdAudit,
|
|
71
|
+
detect: cmdDetect,
|
|
72
|
+
rules: cmdRules,
|
|
73
|
+
explain: cmdExplain,
|
|
74
|
+
diff: cmdDiff,
|
|
75
|
+
init: cmdInit,
|
|
76
|
+
bootstrap: cmdBootstrap,
|
|
77
|
+
learn: cmdLearn,
|
|
78
|
+
evolve: cmdEvolve,
|
|
79
|
+
};
|
|
80
|
+
export function main(argv) {
|
|
81
|
+
const args = parseArgs(argv);
|
|
82
|
+
if (bool(args, 'help')) {
|
|
83
|
+
console.log(HELP);
|
|
84
|
+
return 0;
|
|
85
|
+
}
|
|
86
|
+
if (bool(args, 'version')) {
|
|
87
|
+
console.log(`usa ${VERSION}`);
|
|
88
|
+
return 0;
|
|
89
|
+
}
|
|
90
|
+
const cmd = (args._[0] ?? 'audit');
|
|
91
|
+
const run = COMMANDS[cmd];
|
|
92
|
+
if (run)
|
|
93
|
+
return run(args);
|
|
94
|
+
switch (cmd) {
|
|
95
|
+
case 'version':
|
|
96
|
+
case '--version':
|
|
97
|
+
console.log(`usa ${VERSION}`);
|
|
98
|
+
return 0;
|
|
99
|
+
case 'help':
|
|
100
|
+
case '--help':
|
|
101
|
+
case '-h':
|
|
102
|
+
console.log(HELP);
|
|
103
|
+
return 0;
|
|
104
|
+
default:
|
|
105
|
+
console.error(`Unknown command: ${cmd}\n`);
|
|
106
|
+
console.error(HELP);
|
|
107
|
+
return 2;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
function parseCountFlag(v) {
|
|
111
|
+
if (v === undefined)
|
|
112
|
+
return undefined;
|
|
113
|
+
const n = Number(v);
|
|
114
|
+
return Number.isFinite(n) ? n : NaN;
|
|
115
|
+
}
|
|
116
|
+
function readAuditOptions(args) {
|
|
117
|
+
return {
|
|
118
|
+
target: args._[1] ?? '.',
|
|
119
|
+
rulesDir: path.resolve(str(args, 'rules-dir', DEFAULT_RULES_DIR) ?? DEFAULT_RULES_DIR),
|
|
120
|
+
depth: str(args, 'depth', 'standard') ?? 'standard',
|
|
121
|
+
profileArg: str(args, 'profile', 'auto') ?? 'auto',
|
|
122
|
+
out: str(args, 'out', 'AUDIT.md') ?? 'AUDIT.md',
|
|
123
|
+
failOn: (str(args, 'fail-on', 'none') ?? 'none').toLowerCase(),
|
|
124
|
+
quiet: bool(args, 'quiet'),
|
|
125
|
+
maxFiles: parseCountFlag(str(args, 'max-files')),
|
|
126
|
+
maxBytes: parseCountFlag(str(args, 'max-bytes')),
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
function validateAuditOptions(o) {
|
|
130
|
+
if (!DEPTHS.includes(o.depth))
|
|
131
|
+
return `--depth must be one of ${DEPTHS.join('|')}`;
|
|
132
|
+
if (o.profileArg !== 'auto' && !MATURITIES.includes(o.profileArg)) {
|
|
133
|
+
return `--profile must be auto or one of ${MATURITIES.join('|')}`;
|
|
134
|
+
}
|
|
135
|
+
if (o.maxFiles !== undefined && !(o.maxFiles > 0))
|
|
136
|
+
return '--max-files must be a positive number';
|
|
137
|
+
if (o.maxBytes !== undefined && !(o.maxBytes > 0))
|
|
138
|
+
return '--max-bytes must be a positive number';
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
function cmdAudit(args) {
|
|
142
|
+
const o = readAuditOptions(args);
|
|
143
|
+
const invalid = validateAuditOptions(o);
|
|
144
|
+
if (invalid) {
|
|
145
|
+
console.error(invalid);
|
|
146
|
+
return 2;
|
|
147
|
+
}
|
|
148
|
+
if (!fs.existsSync(o.target)) {
|
|
149
|
+
console.error(`Target path does not exist: ${o.target}`);
|
|
150
|
+
return 2;
|
|
151
|
+
}
|
|
152
|
+
let config;
|
|
153
|
+
try {
|
|
154
|
+
config = loadConfig(o.target, str(args, 'config'));
|
|
155
|
+
}
|
|
156
|
+
catch (err) {
|
|
157
|
+
console.error(err.message);
|
|
158
|
+
return 2;
|
|
159
|
+
}
|
|
160
|
+
if (config.facts)
|
|
161
|
+
config.facts = [...config.facts, ...list(args, 'fact')];
|
|
162
|
+
const { report, warnings, profile } = runAudit({
|
|
163
|
+
target: o.target,
|
|
164
|
+
rulesDir: o.rulesDir,
|
|
165
|
+
depth: o.depth,
|
|
166
|
+
profile: o.profileArg,
|
|
167
|
+
config,
|
|
168
|
+
allowCommands: bool(args, 'allow-commands'),
|
|
169
|
+
usaVersion: VERSION,
|
|
170
|
+
includePacks: list(args, 'include'),
|
|
171
|
+
excludePacks: list(args, 'exclude'),
|
|
172
|
+
maxFiles: o.maxFiles,
|
|
173
|
+
maxBytes: o.maxBytes,
|
|
174
|
+
});
|
|
175
|
+
for (const w of warnings)
|
|
176
|
+
console.error(`warning: ${w}`);
|
|
177
|
+
const markdown = renderMarkdown(report, profile);
|
|
178
|
+
fs.writeFileSync(o.out, markdown, 'utf8');
|
|
179
|
+
if (!o.quiet) {
|
|
180
|
+
console.log(summaryLine(report));
|
|
181
|
+
console.log(` report → ${o.out}`);
|
|
182
|
+
}
|
|
183
|
+
return evaluateGate(report, o.failOn, o.quiet);
|
|
184
|
+
}
|
|
185
|
+
function summaryLine(report) {
|
|
186
|
+
const s = report.score.severityCounts;
|
|
187
|
+
const parts = SEVERITY_ORDER.filter((k) => s[k] > 0)
|
|
188
|
+
.map((k) => `${k.toLowerCase()} ${s[k]}`)
|
|
189
|
+
.join(' · ');
|
|
190
|
+
return (`usa ${report.score.overall}/100 (${report.detection.maturity}) — ` +
|
|
191
|
+
`${report.score.counts.PASS} passed, ` +
|
|
192
|
+
(parts ? `open: ${parts}` : 'no open findings') +
|
|
193
|
+
`, ${report.score.counts.UNKNOWN} to review` +
|
|
194
|
+
` (${report.score.automationCoverage}% verified automatically)`);
|
|
195
|
+
}
|
|
196
|
+
/* ----------------------------------------------------------------- detect -- */
|
|
197
|
+
function cmdDetect(args) {
|
|
198
|
+
const target = args._[1] ?? '.';
|
|
199
|
+
const rulesDir = path.resolve(str(args, 'rules-dir', DEFAULT_RULES_DIR) ?? DEFAULT_RULES_DIR);
|
|
200
|
+
const config = loadConfig(target, str(args, 'config'));
|
|
201
|
+
const project = new Project(target, config.ignore ?? []);
|
|
202
|
+
const git = project.gitInfo();
|
|
203
|
+
const detection = detect(project, loadDetectorFile(rulesDir), git, [
|
|
204
|
+
...list(args, 'fact'),
|
|
205
|
+
...(config.facts ?? []),
|
|
206
|
+
]);
|
|
207
|
+
const grouped = groupFlagsByNamespace(detection.facts.flags);
|
|
208
|
+
console.log(`# Detection: ${path.resolve(target)}`);
|
|
209
|
+
console.log();
|
|
210
|
+
console.log(`maturity: ${detection.maturity}`);
|
|
211
|
+
for (const [ns, values] of [...grouped.entries()].sort()) {
|
|
212
|
+
console.log(`${ns}: ${[...new Set(values)].sort().join(', ')}`);
|
|
213
|
+
}
|
|
214
|
+
console.log();
|
|
215
|
+
console.log('metrics:');
|
|
216
|
+
for (const [k, v] of Object.entries(detection.facts.metrics))
|
|
217
|
+
console.log(` ${k}: ${v}`);
|
|
218
|
+
console.log();
|
|
219
|
+
console.log('maturity signals:');
|
|
220
|
+
for (const s of detection.maturitySignals)
|
|
221
|
+
console.log(` ${s}`);
|
|
222
|
+
return 0;
|
|
223
|
+
}
|
|
224
|
+
function groupFlagsByNamespace(flags) {
|
|
225
|
+
const grouped = new Map();
|
|
226
|
+
for (const f of flags) {
|
|
227
|
+
const [ns, value] = f.split(':');
|
|
228
|
+
const key = value === undefined ? 'other' : ns;
|
|
229
|
+
const list2 = grouped.get(key) ?? [];
|
|
230
|
+
list2.push(value === undefined ? f : value);
|
|
231
|
+
grouped.set(key, list2);
|
|
232
|
+
}
|
|
233
|
+
return grouped;
|
|
234
|
+
}
|
|
235
|
+
/* ------------------------------------------------------------------ rules -- */
|
|
236
|
+
function cmdRules(args) {
|
|
237
|
+
const rulesDir = path.resolve(str(args, 'rules-dir', DEFAULT_RULES_DIR) ?? DEFAULT_RULES_DIR);
|
|
238
|
+
const { packs } = loadRulePacks(rulesDir);
|
|
239
|
+
const filter = str(args, 'section');
|
|
240
|
+
let total = 0;
|
|
241
|
+
for (const pack of packs) {
|
|
242
|
+
const rules = filter ? pack.rules.filter((r) => r.section === filter) : pack.rules;
|
|
243
|
+
if (filter && rules.length === 0)
|
|
244
|
+
continue;
|
|
245
|
+
console.log(`\n## ${pack.id} — ${pack.title} (${rules.length} rules)`);
|
|
246
|
+
if (pack.description)
|
|
247
|
+
console.log(` ${pack.description}`);
|
|
248
|
+
for (const r of rules) {
|
|
249
|
+
total++;
|
|
250
|
+
console.log(` ${r.id.padEnd(10)} ${r.severity.padEnd(8)} ${r.ruleClass.padEnd(16)} ${r.title}`);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
console.log(`\n${total} rule(s) across ${packs.length} pack(s).`);
|
|
254
|
+
return 0;
|
|
255
|
+
}
|
|
256
|
+
/* ---------------------------------------------------------------- explain -- */
|
|
257
|
+
function cmdExplain(args) {
|
|
258
|
+
const id = args._[1];
|
|
259
|
+
if (!id) {
|
|
260
|
+
console.error('Usage: usa explain <RULE-ID>');
|
|
261
|
+
return 2;
|
|
262
|
+
}
|
|
263
|
+
const rulesDir = path.resolve(str(args, 'rules-dir', DEFAULT_RULES_DIR) ?? DEFAULT_RULES_DIR);
|
|
264
|
+
const { packs } = loadRulePacks(rulesDir);
|
|
265
|
+
for (const pack of packs) {
|
|
266
|
+
const rule = pack.rules.find((r) => r.id.toLowerCase() === id.toLowerCase());
|
|
267
|
+
if (!rule)
|
|
268
|
+
continue;
|
|
269
|
+
printRuleDetail(pack.id, rule);
|
|
270
|
+
return 0;
|
|
271
|
+
}
|
|
272
|
+
console.error(`Rule not found: ${id}`);
|
|
273
|
+
return 1;
|
|
274
|
+
}
|
|
275
|
+
function printRuleDetail(packId, rule) {
|
|
276
|
+
console.log(`# ${rule.id} — ${rule.title}`);
|
|
277
|
+
console.log();
|
|
278
|
+
console.log(`pack : ${packId}`);
|
|
279
|
+
console.log(`section : ${rule.section} ${rule.sectionTitle ?? ''}`);
|
|
280
|
+
console.log(`severity : ${rule.severity} (when violated)`);
|
|
281
|
+
console.log(`class : ${rule.ruleClass}`);
|
|
282
|
+
console.log(`weight : ${rule.weight ?? 'default'}`);
|
|
283
|
+
console.log(`depths : ${rule.depths?.join(', ') ?? 'all'}`);
|
|
284
|
+
console.log(`check : ${JSON.stringify(rule.check)}`);
|
|
285
|
+
printRuleOptional(rule);
|
|
286
|
+
}
|
|
287
|
+
function printRuleOptional(rule) {
|
|
288
|
+
if (rule.appliesWhen)
|
|
289
|
+
console.log(`applies : ${JSON.stringify(rule.appliesWhen)}`);
|
|
290
|
+
if (rule.why)
|
|
291
|
+
console.log(`\nwhy:\n ${rule.why}`);
|
|
292
|
+
if (rule.evidence)
|
|
293
|
+
console.log(`\nevidence:\n ${rule.evidence}`);
|
|
294
|
+
if (rule.remediation)
|
|
295
|
+
console.log(`\nfix:\n ${rule.remediation}`);
|
|
296
|
+
if (rule.references?.length)
|
|
297
|
+
console.log(`\nreferences:\n ${rule.references.join('\n ')}`);
|
|
298
|
+
}
|
|
299
|
+
/* ------------------------------------------------------------------- diff -- */
|
|
300
|
+
function cmdDiff(args) {
|
|
301
|
+
const beforePath = args._[1];
|
|
302
|
+
const afterPath = args._[2];
|
|
303
|
+
if (!beforePath || !afterPath) {
|
|
304
|
+
console.error('Usage: usa diff <before.md> <after.md> [--out DIFF.md]');
|
|
305
|
+
return 2;
|
|
306
|
+
}
|
|
307
|
+
const readTrailer = (file) => {
|
|
308
|
+
const text = fs.readFileSync(file, 'utf8');
|
|
309
|
+
return parseTrailer(text) ?? text; // tolerate a raw YAML trailer file
|
|
310
|
+
};
|
|
311
|
+
try {
|
|
312
|
+
const md = diffReports(readTrailer(beforePath), readTrailer(afterPath));
|
|
313
|
+
const out = str(args, 'out');
|
|
314
|
+
if (out)
|
|
315
|
+
fs.writeFileSync(out, md, 'utf8');
|
|
316
|
+
console.log(md);
|
|
317
|
+
return 0;
|
|
318
|
+
}
|
|
319
|
+
catch (err) {
|
|
320
|
+
console.error(err.message);
|
|
321
|
+
return 2;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
/* -------------------------------------------------------------- bootstrap -- */
|
|
325
|
+
function cmdBootstrap(args) {
|
|
326
|
+
const target = args._[1] ?? '.';
|
|
327
|
+
const rulesDir = path.resolve(str(args, 'rules-dir', DEFAULT_RULES_DIR) ?? DEFAULT_RULES_DIR);
|
|
328
|
+
const config = loadConfig(target, str(args, 'config'));
|
|
329
|
+
const project = new Project(target, config.ignore ?? []);
|
|
330
|
+
const git = project.gitInfo();
|
|
331
|
+
const detection = detect(project, loadDetectorFile(rulesDir), git, [
|
|
332
|
+
...list(args, 'fact'),
|
|
333
|
+
...(config.facts ?? []),
|
|
334
|
+
]);
|
|
335
|
+
const outcome = bootstrapPacks(detection.facts);
|
|
336
|
+
if (outcome.packs.length === 0) {
|
|
337
|
+
console.log('No uncovered stacks: every detected language already has a pack.');
|
|
338
|
+
return 0;
|
|
339
|
+
}
|
|
340
|
+
const out = str(args, 'out');
|
|
341
|
+
if (!out) {
|
|
342
|
+
printBootstrap(outcome);
|
|
343
|
+
return 0;
|
|
344
|
+
}
|
|
345
|
+
try {
|
|
346
|
+
writeBootstrapPacks(outcome, out);
|
|
347
|
+
}
|
|
348
|
+
catch (err) {
|
|
349
|
+
console.error(err.message);
|
|
350
|
+
return 2;
|
|
351
|
+
}
|
|
352
|
+
console.log(`wrote ${outcome.packs.length} pack(s) to ${out}`);
|
|
353
|
+
for (const note of outcome.notes)
|
|
354
|
+
console.log(`- ${note}`);
|
|
355
|
+
return 0;
|
|
356
|
+
}
|
|
357
|
+
function printBootstrap(outcome) {
|
|
358
|
+
for (const pack of outcome.packs) {
|
|
359
|
+
console.log(`--- ${pack.filename} ---`);
|
|
360
|
+
console.log(pack.yaml);
|
|
361
|
+
}
|
|
362
|
+
for (const note of outcome.notes)
|
|
363
|
+
console.log(`- ${note}`);
|
|
364
|
+
}
|
|
365
|
+
/* ------------------------------------------------------------------- init -- */
|
|
366
|
+
function cmdInit(args) {
|
|
367
|
+
const target = args._[1] ?? '.';
|
|
368
|
+
fs.mkdirSync(target, { recursive: true });
|
|
369
|
+
const configPath = path.join(target, '.usa.yaml');
|
|
370
|
+
if (fs.existsSync(configPath)) {
|
|
371
|
+
console.error(`${configPath} already exists — leaving it alone.`);
|
|
372
|
+
}
|
|
373
|
+
else {
|
|
374
|
+
fs.writeFileSync(configPath, EXAMPLE_CONFIG, 'utf8');
|
|
375
|
+
console.log(`created ${configPath}`);
|
|
376
|
+
}
|
|
377
|
+
const workflowDir = path.join(target, '.github', 'workflows');
|
|
378
|
+
const workflowPath = path.join(workflowDir, 'usa.yml');
|
|
379
|
+
if (fs.existsSync(workflowPath)) {
|
|
380
|
+
console.error(`${workflowPath} already exists — leaving it alone.`);
|
|
381
|
+
}
|
|
382
|
+
else {
|
|
383
|
+
fs.mkdirSync(workflowDir, { recursive: true });
|
|
384
|
+
fs.writeFileSync(workflowPath, WORKFLOW_TEMPLATE, 'utf8');
|
|
385
|
+
console.log(`created ${workflowPath}`);
|
|
386
|
+
}
|
|
387
|
+
console.log('\nNext: run `npx usa audit .` or `usa audit . --depth standard`.');
|
|
388
|
+
return 0;
|
|
389
|
+
}
|
|
390
|
+
/* ------------------------------------------------------------------ learn -- */
|
|
391
|
+
function cmdLearn(args) {
|
|
392
|
+
const reportPath = args._[1];
|
|
393
|
+
if (!reportPath) {
|
|
394
|
+
console.error('Usage: usa learn <report.md> [--out <file>] [--min-severity MEDIUM]');
|
|
395
|
+
return 2;
|
|
396
|
+
}
|
|
397
|
+
if (!fs.existsSync(reportPath)) {
|
|
398
|
+
console.error(`Report file not found: ${reportPath}`);
|
|
399
|
+
return 2;
|
|
400
|
+
}
|
|
401
|
+
const outFile = str(args, 'out', 'learn-suggestions.yaml') ?? 'learn-suggestions.yaml';
|
|
402
|
+
const minSeverity = (str(args, 'min-severity', 'MEDIUM') ?? 'MEDIUM').toUpperCase();
|
|
403
|
+
const allowed = ['CRITICAL', 'HIGH', 'MEDIUM', 'LOW', 'FUTURE'];
|
|
404
|
+
if (!allowed.includes(minSeverity)) {
|
|
405
|
+
console.error(`--min-severity must be one of: ${allowed.join(', ')}`);
|
|
406
|
+
return 2;
|
|
407
|
+
}
|
|
408
|
+
const options = { reportPath, out: outFile, minSeverity };
|
|
409
|
+
try {
|
|
410
|
+
const suggestions = learnFromReport(options);
|
|
411
|
+
if (suggestions.length === 0) {
|
|
412
|
+
console.log(`No actionable suggestions found in ${reportPath}.`);
|
|
413
|
+
return 0;
|
|
414
|
+
}
|
|
415
|
+
const yaml = renderSuggestions(suggestions);
|
|
416
|
+
fs.writeFileSync(outFile, yaml, 'utf8');
|
|
417
|
+
console.log(`Wrote ${suggestions.length} suggestion(s) to ${outFile}`);
|
|
418
|
+
console.log(' Review the rules, test them, then register in rules/index.yaml.');
|
|
419
|
+
return 0;
|
|
420
|
+
}
|
|
421
|
+
catch (err) {
|
|
422
|
+
console.error(err.message);
|
|
423
|
+
return 2;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
/* ----------------------------------------------------------------- evolve -- */
|
|
427
|
+
function cmdEvolve(args) {
|
|
428
|
+
const target = args._[1] ?? '.';
|
|
429
|
+
const rulesDir = path.resolve(str(args, 'rules-dir', DEFAULT_RULES_DIR) ?? DEFAULT_RULES_DIR);
|
|
430
|
+
if (!fs.existsSync(target)) {
|
|
431
|
+
console.error(`Target path does not exist: ${target}`);
|
|
432
|
+
return 2;
|
|
433
|
+
}
|
|
434
|
+
const storeDir = str(args, 'store');
|
|
435
|
+
const store = storeDir ? new Store(storeDir) : undefined;
|
|
436
|
+
const candidateCapability = loadEvolveCandidate(args);
|
|
437
|
+
const cases = loadEvolveCases(args, candidateCapability);
|
|
438
|
+
const learnReportPath = loadEvolveLearn(args);
|
|
439
|
+
const proposeFromGaps = bool(args, 'propose') && !candidateCapability && !learnReportPath;
|
|
440
|
+
const allGaps = bool(args, 'all-gaps');
|
|
441
|
+
const result = runEvolutionCycle({
|
|
442
|
+
target,
|
|
443
|
+
rulesDir,
|
|
444
|
+
engineVersion: VERSION,
|
|
445
|
+
candidateCapability,
|
|
446
|
+
benchmarkCases: cases,
|
|
447
|
+
store,
|
|
448
|
+
learnReportPath,
|
|
449
|
+
learnMinSeverity: str(args, 'min-severity', 'MEDIUM'),
|
|
450
|
+
proposeFromGaps,
|
|
451
|
+
allGaps,
|
|
452
|
+
onUnproposable: (gap, reason) => {
|
|
453
|
+
console.error(`warning: ${gap.id} not proposable (${reason})`);
|
|
454
|
+
},
|
|
455
|
+
});
|
|
456
|
+
printEvolutionResult(result);
|
|
457
|
+
return 0;
|
|
458
|
+
}
|
|
459
|
+
/** Resolve `--learn <report.md>` to an existing path, or undefined. */
|
|
460
|
+
function loadEvolveLearn(args) {
|
|
461
|
+
const reportPath = str(args, 'learn');
|
|
462
|
+
if (!reportPath)
|
|
463
|
+
return undefined;
|
|
464
|
+
if (!fs.existsSync(reportPath)) {
|
|
465
|
+
console.error(`warning: learn report not found (${reportPath}) — ignoring --learn`);
|
|
466
|
+
return undefined;
|
|
467
|
+
}
|
|
468
|
+
return reportPath;
|
|
469
|
+
}
|
|
470
|
+
function loadEvolveCandidate(args) {
|
|
471
|
+
const candidateFile = str(args, 'candidate');
|
|
472
|
+
if (!candidateFile)
|
|
473
|
+
return undefined;
|
|
474
|
+
const { pack, warnings } = loadPackFile(candidateFile);
|
|
475
|
+
for (const w of warnings)
|
|
476
|
+
console.error(`warning: ${w}`);
|
|
477
|
+
if (!pack) {
|
|
478
|
+
console.error(`Could not load candidate pack: ${candidateFile}`);
|
|
479
|
+
return undefined;
|
|
480
|
+
}
|
|
481
|
+
return capabilityFromPack(pack, { createdBy: 'cli' });
|
|
482
|
+
}
|
|
483
|
+
function loadEvolveCases(args, candidateCapability) {
|
|
484
|
+
const benchDir = str(args, 'bench-dir');
|
|
485
|
+
const cases = benchDir ? loadBenchCases(benchDir) : [];
|
|
486
|
+
if (candidateCapability && cases.length === 0) {
|
|
487
|
+
console.error('warning: no benchmark cases supplied (--bench-dir). The release decision will be vacuous.');
|
|
488
|
+
}
|
|
489
|
+
return cases;
|
|
490
|
+
}
|
|
491
|
+
function loadBenchCases(dir) {
|
|
492
|
+
if (!fs.existsSync(dir))
|
|
493
|
+
return [];
|
|
494
|
+
const cases = [];
|
|
495
|
+
for (const entry of fs.readdirSync(dir)) {
|
|
496
|
+
if (!entry.endsWith('.json'))
|
|
497
|
+
continue;
|
|
498
|
+
try {
|
|
499
|
+
const raw = JSON.parse(fs.readFileSync(path.join(dir, entry), 'utf8'));
|
|
500
|
+
if (raw && raw.id && Array.isArray(raw.expected) && raw.fixture)
|
|
501
|
+
cases.push(raw);
|
|
502
|
+
}
|
|
503
|
+
catch {
|
|
504
|
+
console.error(`warning: could not read benchmark case ${entry}`);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
return cases;
|
|
508
|
+
}
|
|
509
|
+
function printEvolutionResult(result) {
|
|
510
|
+
console.log(`# Evolution: ${result.snapshot.root}`);
|
|
511
|
+
console.log();
|
|
512
|
+
console.log(`snapshot : ${result.snapshot.id}`);
|
|
513
|
+
printCoverage('before', result.before.coverage);
|
|
514
|
+
printGaps(result.before.gaps);
|
|
515
|
+
if (!result.candidate) {
|
|
516
|
+
console.log();
|
|
517
|
+
if (result.proposed.length > 0) {
|
|
518
|
+
console.log(`proposed : ${result.proposed.length} candidate(s) (none benchmarked)`);
|
|
519
|
+
printProposed(result.proposed);
|
|
520
|
+
}
|
|
521
|
+
else {
|
|
522
|
+
console.log('No candidate capability supplied (--candidate <pack.yaml>).');
|
|
523
|
+
console.log('This was the deterministic BEFORE half of the loop only.');
|
|
524
|
+
console.log('Pass --propose to auto-propose a candidate from the gaps.');
|
|
525
|
+
}
|
|
526
|
+
printQueue(result.queue);
|
|
527
|
+
printSchedule(result.schedule);
|
|
528
|
+
return;
|
|
529
|
+
}
|
|
530
|
+
const release = result.candidate.release;
|
|
531
|
+
console.log();
|
|
532
|
+
console.log(`candidate : ${result.candidate.capabilityId} (gaps: ${result.candidate.gapIds.join(', ')})`);
|
|
533
|
+
if (result.proposed.length > 0)
|
|
534
|
+
printProposed(result.proposed);
|
|
535
|
+
if (result.learnedSuggestions !== undefined) {
|
|
536
|
+
console.log(`learned : ${result.learnedSuggestions} suggestion(s) from report`);
|
|
537
|
+
}
|
|
538
|
+
if (release) {
|
|
539
|
+
console.log(`release decision: ${release.decision}`);
|
|
540
|
+
console.log(` precision ${release.precision.toFixed(3)} · recall ${release.recall.toFixed(3)} · regressions ${release.regressions}`);
|
|
541
|
+
for (const r of release.reasons)
|
|
542
|
+
console.log(` - ${r}`);
|
|
543
|
+
}
|
|
544
|
+
if (result.after) {
|
|
545
|
+
console.log();
|
|
546
|
+
printCoverage('after', result.after.coverage);
|
|
547
|
+
const d = result.after.delta;
|
|
548
|
+
console.log(`delta : coverage +${d.coverageDelta}pt · ${d.findingsAdded.length} finding(s) added · gap reduced: ${d.gapReduced}`);
|
|
549
|
+
if (d.findingsAdded.length)
|
|
550
|
+
console.log(` new findings: ${d.findingsAdded.join(', ')}`);
|
|
551
|
+
}
|
|
552
|
+
if (result.queue) {
|
|
553
|
+
console.log(`queue : ${result.queue.open} open · ${result.queue.closed} closed · ${result.queue.total} total`);
|
|
554
|
+
}
|
|
555
|
+
printSchedule(result.schedule);
|
|
556
|
+
}
|
|
557
|
+
function printSchedule(schedule) {
|
|
558
|
+
if (!schedule)
|
|
559
|
+
return;
|
|
560
|
+
for (const c of schedule.candidates) {
|
|
561
|
+
const blocked = c.blockedReason ? ` · blocked: ${c.blockedReason}` : '';
|
|
562
|
+
console.log(`schedule : ${c.capabilityId} → ${c.outcome} ` +
|
|
563
|
+
`(precision ${c.release.precision.toFixed(3)} · recall ${c.release.recall.toFixed(3)} · ` +
|
|
564
|
+
`regressions ${c.release.regressions}, closed: ${c.closedGapIds.length} gap(s))${blocked}`);
|
|
565
|
+
}
|
|
566
|
+
console.log(`schedule : ${schedule.attempted} attempted · ${schedule.released} released · ${schedule.rejected} rejected`);
|
|
567
|
+
}
|
|
568
|
+
function printQueue(queue) {
|
|
569
|
+
if (!queue)
|
|
570
|
+
return;
|
|
571
|
+
console.log(`queue : ${queue.open} open · ${queue.closed} closed · ${queue.total} total`);
|
|
572
|
+
}
|
|
573
|
+
function printCoverage(label, cov) {
|
|
574
|
+
console.log(`${label.padEnd(16)}: language coverage ${cov.languageCoverage}% · automation ${cov.automationCoverage}%`);
|
|
575
|
+
if (cov.unsupportedLanguages.length) {
|
|
576
|
+
console.log(` unsupported : ${cov.unsupportedLanguages.join(', ')}`);
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
function printGaps(gaps) {
|
|
580
|
+
if (gaps.length === 0) {
|
|
581
|
+
console.log('gaps : none');
|
|
582
|
+
return;
|
|
583
|
+
}
|
|
584
|
+
console.log(`gaps : ${gaps.length}`);
|
|
585
|
+
for (const g of gaps)
|
|
586
|
+
console.log(` - ${g.id} [${g.priority}] ${g.requiredCapability}`);
|
|
587
|
+
}
|
|
588
|
+
function printProposed(proposed) {
|
|
589
|
+
for (const c of proposed) {
|
|
590
|
+
const langs = c.capability.languages?.join(', ') ?? 'unknown';
|
|
591
|
+
const rules = c.capability.pack?.rules.length ?? 0;
|
|
592
|
+
console.log(` proposed : ${c.id} (${rules} rule(s), langs: ${langs}) → gap ${c.gapIds.join(', ')} [${c.status}]`);
|
|
593
|
+
}
|
|
594
|
+
console.log(' review required — proposals are unreviewed bootstrap packs, never registered.');
|
|
595
|
+
}
|
|
596
|
+
/* ------------------------------------------------------------------ utils -- */
|
|
597
|
+
function readVersion() {
|
|
598
|
+
const candidates = [
|
|
599
|
+
path.resolve(HERE, '..', 'package.json'),
|
|
600
|
+
path.resolve(HERE, '..', '..', 'package.json'),
|
|
601
|
+
];
|
|
602
|
+
for (const c of candidates) {
|
|
603
|
+
try {
|
|
604
|
+
const pkg = JSON.parse(fs.readFileSync(c, 'utf8'));
|
|
605
|
+
if (pkg.version)
|
|
606
|
+
return pkg.version;
|
|
607
|
+
}
|
|
608
|
+
catch {
|
|
609
|
+
/* fall through */
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
return '0.0.0';
|
|
613
|
+
}
|
|
614
|
+
const HELP = `
|
|
615
|
+
usa — Universal Software Auditor
|
|
616
|
+
|
|
617
|
+
usa audit [path] Audit a project and write a Markdown report
|
|
618
|
+
usa detect [path] Print the auto-detected facts and maturity
|
|
619
|
+
usa rules [--section S2] List all loaded rule packs and rules
|
|
620
|
+
usa explain <RULE-ID> Show everything about one rule
|
|
621
|
+
usa diff <before> <after> Compare two previously generated reports
|
|
622
|
+
usa init [path] Scaffold .usa.yaml + a GitHub Actions workflow
|
|
623
|
+
usa bootstrap [path] Propose rule packs for stacks USA cannot audit yet
|
|
624
|
+
usa learn <report.md> Generate suggested rules from audit findings
|
|
625
|
+
usa evolve [path] Run the audit → gap → candidate → release loop
|
|
626
|
+
|
|
627
|
+
evolve options
|
|
628
|
+
--store <dir> Persist audit runs/results (content-addressed store)
|
|
629
|
+
--candidate <file> A candidate capability pack (YAML) to benchmark and release
|
|
630
|
+
--propose Auto-propose a candidate from the gaps (bootstrap catalog)
|
|
631
|
+
--all-gaps Evaluate every proposable candidate (one per open gap)
|
|
632
|
+
--learn <report.md> Propose a candidate from a report's open findings (usa learn)
|
|
633
|
+
--min-severity <s> Min severity for --learn (default MEDIUM)
|
|
634
|
+
--bench-dir <dir> Directory of benchmark case *.json files
|
|
635
|
+
--rules-dir <dir> Rule pack directory (default bundled rules/)
|
|
636
|
+
|
|
637
|
+
learn options
|
|
638
|
+
--out <file> Output YAML file (default learn-suggestions.yaml)
|
|
639
|
+
--min-severity <s> Minimum severity to consider (CRITICAL|HIGH|MEDIUM|LOW|FUTURE, default MEDIUM)
|
|
640
|
+
|
|
641
|
+
audit options
|
|
642
|
+
--out <file> Report path (default AUDIT.md)
|
|
643
|
+
--depth <level> quick | standard | deep (default standard)
|
|
644
|
+
--profile <stage> auto | prototype | mvp | beta | production | legacy
|
|
645
|
+
--rules-dir <dir> Rule pack directory (default bundled rules/)
|
|
646
|
+
--config <file> Explicit .usa.yaml location
|
|
647
|
+
--include <packs> Force these packs on (comma separated)
|
|
648
|
+
--exclude <packs> Force these packs off
|
|
649
|
+
--fact <ns:value> Assert a fact detection missed, e.g. --fact has:database
|
|
650
|
+
--allow-commands Run \`command:\` checks (shells out; off by default)
|
|
651
|
+
--fail-on <sev> Exit 1 on findings >= sev: critical|high|medium|low|none
|
|
652
|
+
--quiet Only errors
|
|
653
|
+
--max-files <n> Index at most n files (overrides config; default 60000)
|
|
654
|
+
--max-bytes <n> Skip files larger than n bytes (overrides config; default 2 MiB)
|
|
655
|
+
|
|
656
|
+
bootstrap options
|
|
657
|
+
--out <file|dir> Write pack files instead of printing (default: print)
|
|
658
|
+
|
|
659
|
+
examples
|
|
660
|
+
usa audit . --depth deep
|
|
661
|
+
usa bootstrap ~/code/legacy-php-app --out /tmp/packs
|
|
662
|
+
usa audit ../api --profile production --fail-on high
|
|
663
|
+
usa audit . --out reports/audit-$(date +%F).md
|
|
664
|
+
usa learn AUDIT.md --out swift-suggestions.yaml
|
|
665
|
+
`.trim();
|
|
666
|
+
const WORKFLOW_TEMPLATE = `# USA — Universal Software Auditor
|
|
667
|
+
# Runs on every PR and pushes a Markdown summary you can read in the Actions UI.
|
|
668
|
+
name: USA Audit
|
|
669
|
+
|
|
670
|
+
on:
|
|
671
|
+
pull_request:
|
|
672
|
+
push:
|
|
673
|
+
branches: [master]
|
|
674
|
+
workflow_dispatch:
|
|
675
|
+
inputs:
|
|
676
|
+
depth:
|
|
677
|
+
description: Audit depth
|
|
678
|
+
required: false
|
|
679
|
+
default: standard
|
|
680
|
+
type: choice
|
|
681
|
+
options: [quick, standard, deep]
|
|
682
|
+
|
|
683
|
+
permissions:
|
|
684
|
+
contents: read
|
|
685
|
+
`;
|
|
686
|
+
// ---- main entry point ----
|
|
687
|
+
// Run only when this file is the entry point. Comparing `import.meta.url` to
|
|
688
|
+
// `file://${process.argv[1]}` breaks under npm's bin symlinks: argv[1] is the
|
|
689
|
+
// `.bin/usa` symlink while import.meta.url is the real dist/cli.js path, so
|
|
690
|
+
// the guard never matches and the CLI exits silently. Resolve argv[1] to its
|
|
691
|
+
// real path first (and guard the fs call so a non-file argv never throws).
|
|
692
|
+
function isEntryPoint(argv1 = process.argv[1], thisUrl = import.meta.url) {
|
|
693
|
+
if (!argv1)
|
|
694
|
+
return false;
|
|
695
|
+
try {
|
|
696
|
+
return thisUrl === pathToFileURL(fs.realpathSync(argv1)).href;
|
|
697
|
+
}
|
|
698
|
+
catch {
|
|
699
|
+
return false;
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
export { isEntryPoint };
|
|
703
|
+
if (isEntryPoint()) {
|
|
704
|
+
process.exit(main(process.argv.slice(2)));
|
|
705
|
+
}
|
|
706
|
+
//# sourceMappingURL=cli.js.map
|