unguard 0.16.0 → 0.16.2

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/cli.js CHANGED
@@ -1,207 +1,236 @@
1
- import {
2
- BASELINE_FILENAME,
3
- buildBaseline,
4
- computeExitCode,
5
- executeScan,
6
- isFailOn,
7
- isRulePolicySeverity,
8
- isSeverity,
9
- loadBaseline,
10
- toRulePolicyEntries,
11
- writeBaseline
12
- } from "./chunk-3Y76J2K2.js";
13
- import "./chunk-Z34GRD3S.js";
14
-
15
- // src/cli.ts
16
- import { existsSync, readFileSync, writeFileSync } from "fs";
17
- import { basename, relative, resolve } from "path";
18
- import { parseArgs } from "util";
1
+ import { a as buildBaseline, c as isFailOn, d as toRulePolicyEntries, i as BASELINE_FILENAME, l as isRulePolicySeverity, o as loadBaseline, r as computeExitCode, s as writeBaseline, t as executeScan, u as isSeverity } from "./engine-C4KGqeYw.js";
2
+ import { basename, relative, resolve } from "node:path";
3
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
4
+ import { parseArgs } from "node:util";
19
5
  import pc from "picocolors";
20
- var COMMANDS = /* @__PURE__ */ new Set(["scan", "audit", "baseline"]);
6
+ //#region src/cli.ts
7
+ const COMMANDS = /* @__PURE__ */ new Set([
8
+ "scan",
9
+ "audit",
10
+ "baseline"
11
+ ]);
21
12
  async function main(argv) {
22
- const rawArgs = argv.slice(2);
23
- const first = rawArgs[0];
24
- const command = first !== void 0 && COMMANDS.has(first) ? first : "scan";
25
- const userArgs = first !== void 0 && COMMANDS.has(first) ? rawArgs.slice(1) : rawArgs;
26
- const { values, positionals: cliPaths } = parseArgs({
27
- args: userArgs,
28
- options: {
29
- strict: { type: "boolean", default: false },
30
- filter: { type: "string" },
31
- fix: { type: "boolean", default: false },
32
- format: { type: "string", default: "grouped" },
33
- severity: { type: "string", multiple: true, default: [] },
34
- ignore: { type: "string", multiple: true, default: [] },
35
- rule: { type: "string", multiple: true, default: [] },
36
- config: { type: "string" },
37
- "fail-on": { type: "string" },
38
- concurrency: { type: "string" },
39
- "no-baseline": { type: "boolean", default: false },
40
- "no-cache": { type: "boolean", default: false },
41
- help: { type: "boolean", short: "h", default: false }
42
- },
43
- allowPositionals: true
44
- });
45
- if (values.help) {
46
- printHelp();
47
- return 0;
48
- }
49
- if (values.format !== "grouped" && values.format !== "flat" && values.format !== "json") {
50
- console.error(pc.red(`Invalid --format value "${values.format}". Use grouped, flat, or json.`));
51
- return 1;
52
- }
53
- let config = null;
54
- try {
55
- const configPath = findConfigPath(values.config);
56
- config = configPath ? loadConfig(configPath) : null;
57
- } catch (err) {
58
- return printErrorAndFail(err);
59
- }
60
- const rulePolicyResult = parseRulePolicyArgs(values.rule);
61
- if (!rulePolicyResult.ok) {
62
- console.error(pc.red(rulePolicyResult.message));
63
- return 1;
64
- }
65
- const severityFiltersResult = parseSeverityFilters(values.severity);
66
- if (!severityFiltersResult.ok) {
67
- console.error(pc.red(severityFiltersResult.message));
68
- return 1;
69
- }
70
- const failOnResult = command === "audit" ? resolveFailOn(values["fail-on"], "none") : resolveFailOn(values["fail-on"], "info", config?.failOn);
71
- if (!failOnResult.ok) {
72
- console.error(pc.red(failOnResult.message));
73
- return 1;
74
- }
75
- const concurrencyResult = resolveConcurrency(values.concurrency, config?.concurrency);
76
- if (!concurrencyResult.ok) {
77
- console.error(pc.red(concurrencyResult.message));
78
- return 1;
79
- }
80
- const paths = cliPaths.length > 0 ? cliPaths : config?.paths ?? [];
81
- const ignore = [...config?.ignore ?? [], ...values.ignore];
82
- const rulePolicy = [
83
- ...toRulePolicyEntries(config?.rules),
84
- ...rulePolicyResult.value
85
- ];
86
- const cacheEnabled = values["no-cache"] ? false : config?.cache ?? true;
87
- let baseline = null;
88
- if (command === "scan" && !values["no-baseline"]) {
89
- try {
90
- baseline = loadBaseline(process.cwd());
91
- } catch (err) {
92
- return printErrorAndFail(err);
93
- }
94
- }
95
- const execution = await executeScan({
96
- paths,
97
- mode: command === "audit" ? "audit" : "scan",
98
- strict: values.strict,
99
- rules: values.filter ? [values.filter] : void 0,
100
- ignore: ignore.length > 0 ? ignore : void 0,
101
- rulePolicy: rulePolicy.length > 0 ? rulePolicy : void 0,
102
- overrides: config?.overrides,
103
- showSeverities: severityFiltersResult.value.length > 0 ? severityFiltersResult.value : config?.severity,
104
- failOn: failOnResult.value,
105
- concurrency: concurrencyResult.value,
106
- cache: cacheEnabled,
107
- baseline: baseline ?? void 0
108
- });
109
- if (command === "baseline") {
110
- const data = buildBaseline(execution.visibleDiagnostics, process.cwd());
111
- writeBaseline(data, process.cwd());
112
- console.log(
113
- `Baseline written to ${BASELINE_FILENAME}: ${plural(execution.visibleDiagnostics.length, "known issue")} across ${plural(Object.keys(data.rules).length, "file")}.`
114
- );
115
- return 0;
116
- }
117
- let diagnostics = execution.visibleDiagnostics;
118
- let exitCode = execution.exitCode;
119
- let fixedCount = 0;
120
- let fixedFileCount = 0;
121
- if (values.fix) {
122
- const result = applyFixes(diagnostics);
123
- fixedCount = result.applied;
124
- fixedFileCount = result.fileCount;
125
- diagnostics = diagnostics.filter((d) => !result.fixed.has(d));
126
- exitCode = computeExitCode(diagnostics, failOnResult.value);
127
- }
128
- if (values.format === "json") {
129
- console.log(JSON.stringify(buildJsonReport(diagnostics, execution.fileCount, exitCode, values.fix ? fixedCount : null), null, 2));
130
- return exitCode;
131
- }
132
- if (baseline !== null) {
133
- console.log(pc.dim(`Using ${BASELINE_FILENAME} (run "unguard baseline" to regenerate, --no-baseline to ignore)`));
134
- }
135
- if (values.fix && fixedCount > 0) {
136
- console.log(pc.green(`Applied ${fixedCount} fix${fixedCount === 1 ? "" : "es"} in ${fixedFileCount} file${fixedFileCount === 1 ? "" : "s"}. Re-run unguard to verify.`));
137
- }
138
- if (diagnostics.length === 0) {
139
- console.log(pc.green(`No issues found in ${plural(execution.fileCount, "file")}.`));
140
- return exitCode;
141
- }
142
- if (values.format === "flat") {
143
- printDiagnosticsFlat(diagnostics);
144
- } else {
145
- printDiagnostics(diagnostics);
146
- }
147
- printSummary(diagnostics, execution.fileCount);
148
- return exitCode;
13
+ const rawArgs = argv.slice(2);
14
+ const first = rawArgs[0];
15
+ const command = first !== void 0 && COMMANDS.has(first) ? first : "scan";
16
+ const { values, positionals: cliPaths } = parseArgs({
17
+ args: first !== void 0 && COMMANDS.has(first) ? rawArgs.slice(1) : rawArgs,
18
+ options: {
19
+ strict: {
20
+ type: "boolean",
21
+ default: false
22
+ },
23
+ filter: { type: "string" },
24
+ fix: {
25
+ type: "boolean",
26
+ default: false
27
+ },
28
+ format: {
29
+ type: "string",
30
+ default: "grouped"
31
+ },
32
+ severity: {
33
+ type: "string",
34
+ multiple: true,
35
+ default: []
36
+ },
37
+ ignore: {
38
+ type: "string",
39
+ multiple: true,
40
+ default: []
41
+ },
42
+ rule: {
43
+ type: "string",
44
+ multiple: true,
45
+ default: []
46
+ },
47
+ config: { type: "string" },
48
+ "fail-on": { type: "string" },
49
+ concurrency: { type: "string" },
50
+ "no-baseline": {
51
+ type: "boolean",
52
+ default: false
53
+ },
54
+ "no-cache": {
55
+ type: "boolean",
56
+ default: false
57
+ },
58
+ version: {
59
+ type: "boolean",
60
+ short: "v",
61
+ default: false
62
+ },
63
+ help: {
64
+ type: "boolean",
65
+ short: "h",
66
+ default: false
67
+ }
68
+ },
69
+ allowPositionals: true
70
+ });
71
+ if (values.version) {
72
+ console.log(readVersion());
73
+ return 0;
74
+ }
75
+ if (values.help) {
76
+ printHelp();
77
+ return 0;
78
+ }
79
+ if (values.format !== "grouped" && values.format !== "flat" && values.format !== "json") {
80
+ console.error(pc.red(`Invalid --format value "${values.format}". Use grouped, flat, or json.`));
81
+ return 1;
82
+ }
83
+ let config = null;
84
+ try {
85
+ const configPath = findConfigPath(values.config);
86
+ config = configPath ? loadConfig(configPath) : null;
87
+ } catch (err) {
88
+ return printErrorAndFail(err);
89
+ }
90
+ const rulePolicyResult = parseRulePolicyArgs(values.rule);
91
+ if (!rulePolicyResult.ok) {
92
+ console.error(pc.red(rulePolicyResult.message));
93
+ return 1;
94
+ }
95
+ const severityFiltersResult = parseSeverityFilters(values.severity);
96
+ if (!severityFiltersResult.ok) {
97
+ console.error(pc.red(severityFiltersResult.message));
98
+ return 1;
99
+ }
100
+ const failOnResult = command === "audit" ? resolveFailOn(values["fail-on"], "none") : resolveFailOn(values["fail-on"], "info", config?.failOn);
101
+ if (!failOnResult.ok) {
102
+ console.error(pc.red(failOnResult.message));
103
+ return 1;
104
+ }
105
+ const concurrencyResult = resolveConcurrency(values.concurrency, config?.concurrency);
106
+ if (!concurrencyResult.ok) {
107
+ console.error(pc.red(concurrencyResult.message));
108
+ return 1;
109
+ }
110
+ const paths = cliPaths.length > 0 ? cliPaths : config?.paths ?? [];
111
+ const ignore = [...config?.ignore ?? [], ...values.ignore];
112
+ const rulePolicy = [...toRulePolicyEntries(config?.rules), ...rulePolicyResult.value];
113
+ const cacheEnabled = values["no-cache"] ? false : config?.cache ?? true;
114
+ let baseline = null;
115
+ if (command === "scan" && !values["no-baseline"]) try {
116
+ baseline = loadBaseline(process.cwd());
117
+ } catch (err) {
118
+ return printErrorAndFail(err);
119
+ }
120
+ const execution = await executeScan({
121
+ paths,
122
+ mode: command === "audit" ? "audit" : "scan",
123
+ strict: values.strict,
124
+ rules: values.filter ? [values.filter] : void 0,
125
+ ignore: ignore.length > 0 ? ignore : void 0,
126
+ rulePolicy: rulePolicy.length > 0 ? rulePolicy : void 0,
127
+ overrides: config?.overrides,
128
+ showSeverities: severityFiltersResult.value.length > 0 ? severityFiltersResult.value : config?.severity,
129
+ failOn: failOnResult.value,
130
+ concurrency: concurrencyResult.value,
131
+ cache: cacheEnabled,
132
+ baseline: baseline ?? void 0
133
+ });
134
+ if (command === "baseline") {
135
+ const data = buildBaseline(execution.visibleDiagnostics, process.cwd());
136
+ writeBaseline(data, process.cwd());
137
+ console.log(`Baseline written to ${BASELINE_FILENAME}: ${plural(execution.visibleDiagnostics.length, "known issue")} across ${plural(Object.keys(data.rules).length, "file")}.`);
138
+ return 0;
139
+ }
140
+ let diagnostics = execution.visibleDiagnostics;
141
+ let exitCode = execution.exitCode;
142
+ let fixedCount = 0;
143
+ let fixedFileCount = 0;
144
+ if (values.fix) {
145
+ const result = applyFixes(diagnostics);
146
+ fixedCount = result.applied;
147
+ fixedFileCount = result.fileCount;
148
+ diagnostics = diagnostics.filter((d) => !result.fixed.has(d));
149
+ exitCode = computeExitCode(diagnostics, failOnResult.value);
150
+ }
151
+ if (values.format === "json") {
152
+ console.log(JSON.stringify(buildJsonReport(diagnostics, execution.fileCount, exitCode, values.fix ? fixedCount : null), null, 2));
153
+ return exitCode;
154
+ }
155
+ if (baseline !== null) console.log(pc.dim(`Using ${BASELINE_FILENAME} (run "unguard baseline" to regenerate, --no-baseline to ignore)`));
156
+ if (values.fix && fixedCount > 0) console.log(pc.green(`Applied ${fixedCount} fix${fixedCount === 1 ? "" : "es"} in ${fixedFileCount} file${fixedFileCount === 1 ? "" : "s"}. Re-run unguard to verify.`));
157
+ if (diagnostics.length === 0) {
158
+ console.log(pc.green(`No issues found in ${plural(execution.fileCount, "file")}.`));
159
+ return exitCode;
160
+ }
161
+ if (values.format === "flat") printDiagnosticsFlat(diagnostics);
162
+ else printDiagnostics(diagnostics);
163
+ printSummary(diagnostics, execution.fileCount);
164
+ return exitCode;
149
165
  }
150
166
  function buildJsonReport(diagnostics, fileCount, exitCode, fixedCount) {
151
- return {
152
- diagnostics: diagnostics.map((d) => ({
153
- file: d.file,
154
- line: d.line,
155
- column: d.column,
156
- severity: d.severity,
157
- ruleId: d.ruleId,
158
- message: d.message,
159
- ...d.annotation !== void 0 ? { annotation: d.annotation } : {},
160
- fixable: d.fix !== void 0
161
- })),
162
- fileCount,
163
- exitCode,
164
- ...fixedCount !== null ? { fixedCount } : {}
165
- };
167
+ return {
168
+ diagnostics: diagnostics.map((d) => ({
169
+ file: d.file,
170
+ line: d.line,
171
+ column: d.column,
172
+ severity: d.severity,
173
+ ruleId: d.ruleId,
174
+ message: d.message,
175
+ ...d.annotation !== void 0 ? { annotation: d.annotation } : {},
176
+ fixable: d.fix !== void 0
177
+ })),
178
+ fileCount,
179
+ exitCode,
180
+ ...fixedCount !== null ? { fixedCount } : {}
181
+ };
166
182
  }
183
+ /**
184
+ * Apply fix edits, last-to-first per file so earlier offsets stay valid.
185
+ * Overlapping edits in one pass are skipped — the re-run picks them up.
186
+ */
167
187
  function applyFixes(diagnostics) {
168
- const byFile = groupByFile(diagnostics.filter((d) => d.fix !== void 0));
169
- const fixed = /* @__PURE__ */ new Set();
170
- for (const [file, fileDiagnostics] of byFile) {
171
- const ordered = [...fileDiagnostics].sort((a, b) => (b.fix?.start ?? 0) - (a.fix?.start ?? 0));
172
- let content = readFileSync(file, "utf8");
173
- let lastAppliedStart = Number.POSITIVE_INFINITY;
174
- for (const diagnostic of ordered) {
175
- const fix = diagnostic.fix;
176
- if (fix === void 0) continue;
177
- if (fix.end > lastAppliedStart) continue;
178
- content = content.slice(0, fix.start) + fix.text + content.slice(fix.end);
179
- lastAppliedStart = fix.start;
180
- fixed.add(diagnostic);
181
- }
182
- writeFileSync(file, content, "utf8");
183
- }
184
- return { applied: fixed.size, fileCount: byFile.size, fixed };
188
+ const byFile = groupByFile(diagnostics.filter((d) => d.fix !== void 0));
189
+ const fixed = /* @__PURE__ */ new Set();
190
+ for (const [file, fileDiagnostics] of byFile) {
191
+ const ordered = [...fileDiagnostics].sort((a, b) => (b.fix?.start ?? 0) - (a.fix?.start ?? 0));
192
+ let content = readFileSync(file, "utf8");
193
+ let lastAppliedStart = Number.POSITIVE_INFINITY;
194
+ for (const diagnostic of ordered) {
195
+ const fix = diagnostic.fix;
196
+ if (fix === void 0) continue;
197
+ if (fix.end > lastAppliedStart) continue;
198
+ content = content.slice(0, fix.start) + fix.text + content.slice(fix.end);
199
+ lastAppliedStart = fix.start;
200
+ fixed.add(diagnostic);
201
+ }
202
+ writeFileSync(file, content, "utf8");
203
+ }
204
+ return {
205
+ applied: fixed.size,
206
+ fileCount: byFile.size,
207
+ fixed
208
+ };
185
209
  }
186
210
  function groupByFile(diagnostics) {
187
- const byFile = /* @__PURE__ */ new Map();
188
- for (const diagnostic of diagnostics) {
189
- let list = byFile.get(diagnostic.file);
190
- if (list === void 0) {
191
- list = [];
192
- byFile.set(diagnostic.file, list);
193
- }
194
- list.push(diagnostic);
195
- }
196
- return byFile;
211
+ const byFile = /* @__PURE__ */ new Map();
212
+ for (const diagnostic of diagnostics) {
213
+ let list = byFile.get(diagnostic.file);
214
+ if (list === void 0) {
215
+ list = [];
216
+ byFile.set(diagnostic.file, list);
217
+ }
218
+ list.push(diagnostic);
219
+ }
220
+ return byFile;
197
221
  }
222
+ /** Surface a failure as output + exit code 1 instead of a thrown error. */
198
223
  function printErrorAndFail(err) {
199
- const message = err instanceof Error ? err.message : String(err);
200
- console.error(pc.red(message));
201
- return 1;
224
+ const message = err instanceof Error ? err.message : String(err);
225
+ console.error(pc.red(message));
226
+ return 1;
227
+ }
228
+ function readVersion() {
229
+ const version = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")).version;
230
+ return typeof version === "string" ? version : "unknown";
202
231
  }
203
232
  function printHelp() {
204
- console.log(`unguard: data-shape static analyzer
233
+ console.log(`unguard: data-shape static analyzer
205
234
 
206
235
  Usage:
207
236
  unguard scan [paths...] [options]
@@ -231,6 +260,7 @@ Options:
231
260
  --concurrency <n> Worker threads for tsconfig groups (default: auto, 1 disables)
232
261
  --no-baseline Ignore unguard.baseline.json for this scan
233
262
  --no-cache Disable on-disk diagnostic cache (node_modules/.cache/unguard)
263
+ -v, --version Print version and exit
234
264
  -h, --help Show this help
235
265
 
236
266
  Exit codes:
@@ -251,248 +281,244 @@ Examples:
251
281
  unguard scan src --fail-on=error`);
252
282
  }
253
283
  function printDiagnostics(diagnostics) {
254
- const byFile = groupByFile(diagnostics);
255
- const cwd = `${process.cwd()}/`;
256
- const cwdBase = process.cwd();
257
- const prefixWidth = 2 + 10 + 1 + 7 + 2;
258
- const columns = process.stdout.columns;
259
- const wrapWidth = columns !== void 0 && columns > prefixWidth + 20 ? columns - prefixWidth : null;
260
- for (const [file, fileDiagnostics] of byFile) {
261
- const rel = relative(cwdBase, file);
262
- console.log(pc.underline(rel));
263
- for (const diagnostic of fileDiagnostics) {
264
- const loc = `${diagnostic.line}:${diagnostic.column}`;
265
- const sev = diagnostic.severity === "error" ? pc.red("error".padEnd(7)) : diagnostic.severity === "warning" ? pc.yellow("warning") : pc.blue("info".padEnd(7));
266
- const msg = diagnostic.message.replaceAll(cwd, "");
267
- const head = ` ${pc.dim(loc.padEnd(10))} ${sev} `;
268
- const rule = pc.dim(diagnostic.ruleId);
269
- const annotation = diagnostic.annotation !== void 0 ? ` ${pc.cyan(diagnostic.annotation)}` : "";
270
- if (wrapWidth === null) {
271
- console.log(`${head}${msg} ${rule}${annotation}`);
272
- continue;
273
- }
274
- const lines = wrapWords(msg, wrapWidth);
275
- const tailWidth = diagnostic.ruleId.length + (diagnostic.annotation !== void 0 ? 2 + diagnostic.annotation.length : 0);
276
- const last = lines.length - 1;
277
- const tailFits = (lines[last]?.length ?? 0) + 2 + tailWidth <= wrapWidth;
278
- const indent = " ".repeat(prefixWidth);
279
- for (let i = 0; i < lines.length; i++) {
280
- const start = i === 0 ? head : indent;
281
- const tail = i === last && tailFits ? ` ${rule}${annotation}` : "";
282
- console.log(`${start}${lines[i]}${tail}`);
283
- }
284
- if (!tailFits) console.log(`${indent}${rule}${annotation}`);
285
- }
286
- console.log();
287
- }
284
+ const byFile = groupByFile(diagnostics);
285
+ const cwd = `${process.cwd()}/`;
286
+ const cwdBase = process.cwd();
287
+ const prefixWidth = 22;
288
+ const columns = process.stdout.columns;
289
+ const wrapWidth = columns !== void 0 && columns > 42 ? columns - prefixWidth : null;
290
+ for (const [file, fileDiagnostics] of byFile) {
291
+ const rel = relative(cwdBase, file);
292
+ console.log(pc.underline(rel));
293
+ for (const diagnostic of fileDiagnostics) {
294
+ const loc = `${diagnostic.line}:${diagnostic.column}`;
295
+ const sev = diagnostic.severity === "error" ? pc.red("error".padEnd(7)) : diagnostic.severity === "warning" ? pc.yellow("warning") : pc.blue("info".padEnd(7));
296
+ const msg = diagnostic.message.replaceAll(cwd, "");
297
+ const head = ` ${pc.dim(loc.padEnd(10))} ${sev} `;
298
+ const rule = pc.dim(diagnostic.ruleId);
299
+ const annotation = diagnostic.annotation !== void 0 ? ` ${pc.cyan(diagnostic.annotation)}` : "";
300
+ if (wrapWidth === null) {
301
+ console.log(`${head}${msg} ${rule}${annotation}`);
302
+ continue;
303
+ }
304
+ const lines = wrapWords(msg, wrapWidth);
305
+ const tailWidth = diagnostic.ruleId.length + (diagnostic.annotation !== void 0 ? 2 + diagnostic.annotation.length : 0);
306
+ const last = lines.length - 1;
307
+ const tailFits = (lines[last]?.length ?? 0) + 2 + tailWidth <= wrapWidth;
308
+ const indent = " ".repeat(prefixWidth);
309
+ for (let i = 0; i < lines.length; i++) {
310
+ const start = i === 0 ? head : indent;
311
+ const tail = i === last && tailFits ? ` ${rule}${annotation}` : "";
312
+ console.log(`${start}${lines[i]}${tail}`);
313
+ }
314
+ if (!tailFits) console.log(`${indent}${rule}${annotation}`);
315
+ }
316
+ console.log();
317
+ }
288
318
  }
319
+ /** Greedy word wrap; words longer than the width get their own line unbroken. */
289
320
  function wrapWords(text, width) {
290
- const lines = [];
291
- let line = "";
292
- for (const word of text.split(" ")) {
293
- if (line === "") {
294
- line = word;
295
- } else if (line.length + 1 + word.length > width) {
296
- lines.push(line);
297
- line = word;
298
- } else {
299
- line += ` ${word}`;
300
- }
301
- }
302
- if (line !== "") lines.push(line);
303
- return lines;
321
+ const lines = [];
322
+ let line = "";
323
+ for (const word of text.split(" ")) if (line === "") line = word;
324
+ else if (line.length + 1 + word.length > width) {
325
+ lines.push(line);
326
+ line = word;
327
+ } else line += ` ${word}`;
328
+ if (line !== "") lines.push(line);
329
+ return lines;
304
330
  }
305
331
  function printDiagnosticsFlat(diagnostics) {
306
- const cwdBase = process.cwd();
307
- const cwd = `${cwdBase}/`;
308
- for (const diagnostic of diagnostics) {
309
- const rel = relative(cwdBase, diagnostic.file);
310
- const msg = diagnostic.message.replaceAll(cwd, "");
311
- console.log(`${rel}:${diagnostic.line}:${diagnostic.column} ${diagnostic.severity} [${diagnostic.ruleId}] ${msg}`);
312
- }
332
+ const cwdBase = process.cwd();
333
+ const cwd = `${cwdBase}/`;
334
+ for (const diagnostic of diagnostics) {
335
+ const rel = relative(cwdBase, diagnostic.file);
336
+ const msg = diagnostic.message.replaceAll(cwd, "");
337
+ console.log(`${rel}:${diagnostic.line}:${diagnostic.column} ${diagnostic.severity} [${diagnostic.ruleId}] ${msg}`);
338
+ }
313
339
  }
314
340
  function printSummary(diagnostics, fileCount) {
315
- const counts = /* @__PURE__ */ new Map();
316
- for (const diagnostic of diagnostics) {
317
- const prev = counts.get(diagnostic.ruleId);
318
- counts.set(diagnostic.ruleId, (prev === void 0 ? 0 : prev) + 1);
319
- }
320
- const sorted = [...counts.entries()].sort((a, b) => b[1] - a[1]);
321
- const maxRule = Math.max(...sorted.map(([rule]) => rule.length));
322
- console.log(pc.bold("Summary"));
323
- for (const [rule, count] of sorted) {
324
- console.log(` ${rule.padEnd(maxRule)} ${pc.bold(String(count))}`);
325
- }
326
- console.log(
327
- `
328
- ${pc.bold(String(diagnostics.length))} issue${diagnostics.length === 1 ? "" : "s"} in ${plural(fileCount, "file")}.`
329
- );
341
+ const counts = /* @__PURE__ */ new Map();
342
+ for (const diagnostic of diagnostics) {
343
+ const prev = counts.get(diagnostic.ruleId);
344
+ counts.set(diagnostic.ruleId, (prev === void 0 ? 0 : prev) + 1);
345
+ }
346
+ const sorted = [...counts.entries()].sort((a, b) => b[1] - a[1]);
347
+ const maxRule = Math.max(...sorted.map(([rule]) => rule.length));
348
+ console.log(pc.bold("Summary"));
349
+ for (const [rule, count] of sorted) console.log(` ${rule.padEnd(maxRule)} ${pc.bold(String(count))}`);
350
+ console.log(`\n${pc.bold(String(diagnostics.length))} issue${diagnostics.length === 1 ? "" : "s"} in ${plural(fileCount, "file")}.`);
330
351
  }
331
352
  function plural(count, noun) {
332
- return `${count} ${noun}${count === 1 ? "" : "s"}`;
353
+ return `${count} ${noun}${count === 1 ? "" : "s"}`;
333
354
  }
334
355
  function parseSeverityFilters(values) {
335
- const levels = splitCsv(values);
336
- const parsed = [];
337
- for (const value of levels) {
338
- if (!isSeverity(value)) {
339
- return { ok: false, message: `Invalid --severity value "${value}". Use error, warning, info.` };
340
- }
341
- parsed.push(value);
342
- }
343
- return { ok: true, value: parsed };
356
+ const levels = splitCsv(values);
357
+ const parsed = [];
358
+ for (const value of levels) {
359
+ if (!isSeverity(value)) return {
360
+ ok: false,
361
+ message: `Invalid --severity value "${value}". Use error, warning, info.`
362
+ };
363
+ parsed.push(value);
364
+ }
365
+ return {
366
+ ok: true,
367
+ value: parsed
368
+ };
344
369
  }
345
370
  function parseRulePolicyArgs(values) {
346
- const entries = [];
347
- for (const arg of values) {
348
- const separator = resolveRuleSeparator(arg);
349
- if (separator === null) {
350
- return { ok: false, message: `Invalid --rule value "${arg}". Use <selector>=<off|info|warning|error>.` };
351
- }
352
- const selector = arg.slice(0, separator).trim();
353
- const severity = arg.slice(separator + 1).trim();
354
- if (!selector) {
355
- return { ok: false, message: `Invalid --rule value "${arg}". Rule selector is required.` };
356
- }
357
- if (!isRulePolicySeverity(severity)) {
358
- return { ok: false, message: `Invalid --rule value "${arg}". Severity must be off, info, warning, or error.` };
359
- }
360
- entries.push({ selector, severity });
361
- }
362
- return { ok: true, value: entries };
371
+ const entries = [];
372
+ for (const arg of values) {
373
+ const separator = resolveRuleSeparator(arg);
374
+ if (separator === null) return {
375
+ ok: false,
376
+ message: `Invalid --rule value "${arg}". Use <selector>=<off|info|warning|error>.`
377
+ };
378
+ const selector = arg.slice(0, separator).trim();
379
+ const severity = arg.slice(separator + 1).trim();
380
+ if (!selector) return {
381
+ ok: false,
382
+ message: `Invalid --rule value "${arg}". Rule selector is required.`
383
+ };
384
+ if (!isRulePolicySeverity(severity)) return {
385
+ ok: false,
386
+ message: `Invalid --rule value "${arg}". Severity must be off, info, warning, or error.`
387
+ };
388
+ entries.push({
389
+ selector,
390
+ severity
391
+ });
392
+ }
393
+ return {
394
+ ok: true,
395
+ value: entries
396
+ };
363
397
  }
364
398
  function resolveRuleSeparator(value) {
365
- const equalIndex = value.indexOf("=");
366
- if (equalIndex >= 0) return equalIndex;
367
- const colonIndex = value.lastIndexOf(":");
368
- if (colonIndex <= 0) return null;
369
- return colonIndex;
399
+ const equalIndex = value.indexOf("=");
400
+ if (equalIndex >= 0) return equalIndex;
401
+ const colonIndex = value.lastIndexOf(":");
402
+ if (colonIndex <= 0) return null;
403
+ return colonIndex;
370
404
  }
371
405
  function splitCsv(values) {
372
- return values.flatMap((value) => value.split(",")).map((value) => value.trim()).filter((value) => value.length > 0);
406
+ return values.flatMap((value) => value.split(",")).map((value) => value.trim()).filter((value) => value.length > 0);
373
407
  }
374
408
  function findConfigPath(cliValue) {
375
- if (cliValue) {
376
- const explicitPath = resolve(process.cwd(), cliValue);
377
- if (!existsSync(explicitPath)) throw new Error(`Config file not found: ${cliValue}`);
378
- return explicitPath;
379
- }
380
- const candidates = ["unguard.config.json", ".unguardrc.json"];
381
- for (const candidate of candidates) {
382
- const candidatePath = resolve(process.cwd(), candidate);
383
- if (existsSync(candidatePath)) return candidatePath;
384
- }
385
- return null;
409
+ if (cliValue) {
410
+ const explicitPath = resolve(process.cwd(), cliValue);
411
+ if (!existsSync(explicitPath)) throw new Error(`Config file not found: ${cliValue}`);
412
+ return explicitPath;
413
+ }
414
+ for (const candidate of ["unguard.config.json", ".unguardrc.json"]) {
415
+ const candidatePath = resolve(process.cwd(), candidate);
416
+ if (existsSync(candidatePath)) return candidatePath;
417
+ }
418
+ return null;
386
419
  }
387
420
  function loadConfig(path) {
388
- const text = readFileSync(path, "utf8");
389
- let parsed;
390
- try {
391
- parsed = JSON.parse(text);
392
- } catch (err) {
393
- const message = err instanceof Error ? err.message : String(err);
394
- throw new Error(`Invalid JSON in ${basename(path)}: ${message}`);
395
- }
396
- if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
397
- throw new Error(`Invalid config in ${basename(path)}: expected a JSON object.`);
398
- }
399
- const raw = parsed;
400
- const config = {};
401
- if (raw.paths !== void 0) {
402
- if (!isStringArray(raw.paths)) throw new Error(`Invalid config in ${basename(path)}: "paths" must be a string array.`);
403
- config.paths = raw.paths;
404
- }
405
- if (raw.ignore !== void 0) {
406
- if (!isStringArray(raw.ignore)) throw new Error(`Invalid config in ${basename(path)}: "ignore" must be a string array.`);
407
- config.ignore = raw.ignore;
408
- }
409
- if (raw.failOn !== void 0) {
410
- if (typeof raw.failOn !== "string" || !isFailOn(raw.failOn)) {
411
- throw new Error(`Invalid config in ${basename(path)}: "failOn" must be one of none, error, warning, info.`);
412
- }
413
- config.failOn = raw.failOn;
414
- }
415
- if (raw.severity !== void 0) {
416
- if (!isStringArray(raw.severity) || !raw.severity.every(isSeverity)) {
417
- throw new Error(`Invalid config in ${basename(path)}: "severity" must be an array of error, warning, info.`);
418
- }
419
- config.severity = raw.severity;
420
- }
421
- if (raw.concurrency !== void 0) {
422
- if (typeof raw.concurrency !== "number" || !Number.isInteger(raw.concurrency) || raw.concurrency < 1) {
423
- throw new Error(`Invalid config in ${basename(path)}: "concurrency" must be a positive integer.`);
424
- }
425
- config.concurrency = raw.concurrency;
426
- }
427
- if (raw.cache !== void 0) {
428
- if (typeof raw.cache !== "boolean") {
429
- throw new Error(`Invalid config in ${basename(path)}: "cache" must be a boolean.`);
430
- }
431
- config.cache = raw.cache;
432
- }
433
- if (raw.rules !== void 0) {
434
- config.rules = parseRulesObject(raw.rules, `"rules"`, basename(path));
435
- }
436
- if (raw.overrides !== void 0) {
437
- if (!Array.isArray(raw.overrides)) {
438
- throw new Error(`Invalid config in ${basename(path)}: "overrides" must be an array.`);
439
- }
440
- config.overrides = raw.overrides.map((entry, index) => {
441
- if (typeof entry !== "object" || entry === null || Array.isArray(entry)) {
442
- throw new Error(`Invalid config in ${basename(path)}: overrides[${index}] must be an object.`);
443
- }
444
- const override = entry;
445
- if (!isStringArray(override.files) || override.files.length === 0) {
446
- throw new Error(`Invalid config in ${basename(path)}: overrides[${index}].files must be a non-empty string array.`);
447
- }
448
- return {
449
- files: override.files,
450
- rules: parseRulesObject(override.rules, `overrides[${index}].rules`, basename(path))
451
- };
452
- });
453
- }
454
- return config;
421
+ const text = readFileSync(path, "utf8");
422
+ let parsed;
423
+ try {
424
+ parsed = JSON.parse(text);
425
+ } catch (err) {
426
+ const message = err instanceof Error ? err.message : String(err);
427
+ throw new Error(`Invalid JSON in ${basename(path)}: ${message}`);
428
+ }
429
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) throw new Error(`Invalid config in ${basename(path)}: expected a JSON object.`);
430
+ const raw = parsed;
431
+ const config = {};
432
+ if (raw.paths !== void 0) {
433
+ if (!isStringArray(raw.paths)) throw new Error(`Invalid config in ${basename(path)}: "paths" must be a string array.`);
434
+ config.paths = raw.paths;
435
+ }
436
+ if (raw.ignore !== void 0) {
437
+ if (!isStringArray(raw.ignore)) throw new Error(`Invalid config in ${basename(path)}: "ignore" must be a string array.`);
438
+ config.ignore = raw.ignore;
439
+ }
440
+ if (raw.failOn !== void 0) {
441
+ if (typeof raw.failOn !== "string" || !isFailOn(raw.failOn)) throw new Error(`Invalid config in ${basename(path)}: "failOn" must be one of none, error, warning, info.`);
442
+ config.failOn = raw.failOn;
443
+ }
444
+ if (raw.severity !== void 0) {
445
+ if (!isStringArray(raw.severity) || !raw.severity.every(isSeverity)) throw new Error(`Invalid config in ${basename(path)}: "severity" must be an array of error, warning, info.`);
446
+ config.severity = raw.severity;
447
+ }
448
+ if (raw.concurrency !== void 0) {
449
+ if (typeof raw.concurrency !== "number" || !Number.isInteger(raw.concurrency) || raw.concurrency < 1) throw new Error(`Invalid config in ${basename(path)}: "concurrency" must be a positive integer.`);
450
+ config.concurrency = raw.concurrency;
451
+ }
452
+ if (raw.cache !== void 0) {
453
+ if (typeof raw.cache !== "boolean") throw new Error(`Invalid config in ${basename(path)}: "cache" must be a boolean.`);
454
+ config.cache = raw.cache;
455
+ }
456
+ if (raw.rules !== void 0) config.rules = parseRulesObject(raw.rules, `"rules"`, basename(path));
457
+ if (raw.overrides !== void 0) {
458
+ if (!Array.isArray(raw.overrides)) throw new Error(`Invalid config in ${basename(path)}: "overrides" must be an array.`);
459
+ config.overrides = raw.overrides.map((entry, index) => {
460
+ if (typeof entry !== "object" || entry === null || Array.isArray(entry)) throw new Error(`Invalid config in ${basename(path)}: overrides[${index}] must be an object.`);
461
+ const override = entry;
462
+ if (!isStringArray(override.files) || override.files.length === 0) throw new Error(`Invalid config in ${basename(path)}: overrides[${index}].files must be a non-empty string array.`);
463
+ return {
464
+ files: override.files,
465
+ rules: parseRulesObject(override.rules, `overrides[${index}].rules`, basename(path))
466
+ };
467
+ });
468
+ }
469
+ return config;
455
470
  }
456
471
  function parseRulesObject(value, label, configName) {
457
- if (typeof value !== "object" || value === null || Array.isArray(value)) {
458
- throw new Error(`Invalid config in ${configName}: ${label} must be an object.`);
459
- }
460
- const rules = {};
461
- for (const [selector, severity] of Object.entries(value)) {
462
- if (typeof severity !== "string" || !isRulePolicySeverity(severity)) {
463
- throw new Error(`Invalid config in ${configName}: rule "${selector}" in ${label} must be off, info, warning, or error.`);
464
- }
465
- rules[selector] = severity;
466
- }
467
- return rules;
472
+ if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error(`Invalid config in ${configName}: ${label} must be an object.`);
473
+ const rules = {};
474
+ for (const [selector, severity] of Object.entries(value)) {
475
+ if (typeof severity !== "string" || !isRulePolicySeverity(severity)) throw new Error(`Invalid config in ${configName}: rule "${selector}" in ${label} must be off, info, warning, or error.`);
476
+ rules[selector] = severity;
477
+ }
478
+ return rules;
468
479
  }
469
480
  function isStringArray(value) {
470
- return Array.isArray(value) && value.every((entry) => typeof entry === "string");
481
+ return Array.isArray(value) && value.every((entry) => typeof entry === "string");
471
482
  }
472
483
  function resolveFailOn(cliValue, fallback, configValue) {
473
- const selected = cliValue ?? configValue ?? fallback;
474
- if (!isFailOn(selected)) {
475
- return { ok: false, message: `Invalid --fail-on value "${selected}". Use none, error, warning, or info.` };
476
- }
477
- return { ok: true, value: selected };
484
+ const selected = cliValue ?? configValue ?? fallback;
485
+ if (!isFailOn(selected)) return {
486
+ ok: false,
487
+ message: `Invalid --fail-on value "${selected}". Use none, error, warning, or info.`
488
+ };
489
+ return {
490
+ ok: true,
491
+ value: selected
492
+ };
478
493
  }
479
494
  function resolveConcurrency(cliValue, configValue) {
480
- if (cliValue !== void 0) {
481
- const parsed = Number(cliValue);
482
- if (!Number.isInteger(parsed) || parsed < 1) {
483
- return { ok: false, message: `Invalid --concurrency value "${cliValue}". Use a positive integer.` };
484
- }
485
- return { ok: true, value: parsed };
486
- }
487
- if (configValue !== void 0) {
488
- if (!Number.isInteger(configValue) || configValue < 1) {
489
- return { ok: false, message: "Invalid concurrency in config: must be a positive integer." };
490
- }
491
- return { ok: true, value: configValue };
492
- }
493
- return { ok: true, value: void 0 };
495
+ if (cliValue !== void 0) {
496
+ const parsed = Number(cliValue);
497
+ if (!Number.isInteger(parsed) || parsed < 1) return {
498
+ ok: false,
499
+ message: `Invalid --concurrency value "${cliValue}". Use a positive integer.`
500
+ };
501
+ return {
502
+ ok: true,
503
+ value: parsed
504
+ };
505
+ }
506
+ if (configValue !== void 0) {
507
+ if (!Number.isInteger(configValue) || configValue < 1) return {
508
+ ok: false,
509
+ message: "Invalid concurrency in config: must be a positive integer."
510
+ };
511
+ return {
512
+ ok: true,
513
+ value: configValue
514
+ };
515
+ }
516
+ return {
517
+ ok: true,
518
+ value: void 0
519
+ };
494
520
  }
495
- export {
496
- main
497
- };
521
+ //#endregion
522
+ export { main };
523
+
498
524
  //# sourceMappingURL=cli.js.map