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