@pho9ubenaa/siro 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.mjs CHANGED
@@ -1,215 +1,134 @@
1
1
  #!/usr/bin/env node
2
- import { C as isPM, E as UsageError, O as SiroError, S as SEVERITIES, k as asAbsPath, m as BUILTIN_REPORTER_NAMES, n as lintCommand, p as isNodeError, t as nodeIO, v as version, w as isSeverity, x as PMS } from "./node-io-Cb8nixgE.mjs";
2
+ import { C as SUPPORTED_NODE_RANGE, D as isNodeError, M as UsageError, O as asAbsPath, S as isSeverity, T as assertDirectory, _ as PROJECT_TYPES, b as SEVERITIES, c as DEFAULT_REPORTER_NAME, f as version, j as SiroError, l as JSON_REPORTER_NAME, m as loadConfig, n as lintCommand, s as BUILTIN_REPORTER_NAMES, t as nodeIO, v as isProjectType, w as isSupportedNodeVersion, x as isPM, y as PMS } from "./node-io-BdyNbvzO.mjs";
3
3
  import path from "node:path";
4
4
  import { pathToFileURL } from "node:url";
5
- import { cac } from "cac";
6
-
7
- //#region src/cli/commands.ts
8
- /**
9
- * Canonical list of siro CLI sub-commands. Shared between `cli.ts` (the
10
- * dispatcher / arg parser) and `cli/help.ts` (the help-text renderer) so a
11
- * new command added here propagates to both via the derived `CommandName`
12
- * union — there is no second place to remember to update.
13
- */
14
- const COMMANDS = ["lint", "check"];
15
- const isCommandName = (value) => COMMANDS.some((cmd) => cmd === value);
16
-
17
- //#endregion
18
- //#region src/cli/flags.ts
19
- const ARGS_OFFSET$1 = 2;
20
- /** Flags that consume the following argv token; pre-cac scanners skip that token. */
21
- const VALUE_FLAGS = new Set(Object.entries({
22
- json: { takesValue: false },
23
- pm: { takesValue: true },
24
- reporter: { takesValue: true },
25
- severity: { takesValue: true }
26
- }).filter(([, meta]) => meta.takesValue).map(([name]) => name));
27
- const LINT_FLAGS = [
28
- "pm",
29
- "reporter",
30
- "json",
31
- "severity"
32
- ];
33
- const FLAG_SCOPES = {
34
- check: LINT_FLAGS,
35
- lint: LINT_FLAGS
36
- };
37
- const GLOBAL_FLAGS = ["version", "help"];
38
- const CAC_KEYS = [
39
- "v",
40
- "h",
41
- "--"
42
- ];
43
- const flagsFor = (command) => new Set([
44
- ...FLAG_SCOPES[command],
45
- ...GLOBAL_FLAGS,
46
- ...CAC_KEYS
47
- ]);
48
- const collectAllFlags = () => {
49
- const all = [];
50
- for (const command of COMMANDS) for (const flag of flagsFor(command)) all.push(flag);
51
- return new Set(all);
52
- };
53
- const KNOWN_FLAGS = collectAllFlags();
54
- /**
55
- * Whether a `--name value` flag consumes the following argv token. Only the
56
- * separate-token form of a value-taking flag does (`--name=value` carries its
57
- * own value), AND only when the next token isn't itself a `-`-prefixed flag —
58
- * so `--reporter --help` leaves `--help` for the help/version detectors rather
59
- * than eating it as the reporter value. Shared by both pre-cac scanners so the
60
- * skip predicate stays in one place.
61
- */
62
- const consumesNext = (token, next) => token.startsWith("--") && !token.includes("=") && VALUE_FLAGS.has(token.slice(ARGS_OFFSET$1)) && typeof next !== "undefined" && !next.startsWith("-");
63
-
64
- //#endregion
65
- //#region src/cli/scanners.ts
66
- const STEP$1 = 1;
67
- const isHelpFlag = (token) => token === "--help" || token === "-h";
68
- const processHelpToken = (token, next, state) => {
69
- if (token === "--") return "break";
70
- if (isHelpFlag(token)) state.asksHelp = true;
71
- else if (consumesNext(token, next)) state.index += STEP$1;
72
- else if (!token.startsWith("-") && state.first === "") state.first = token;
73
- return "continue";
74
- };
75
- const scanHelpTokens = (argv) => {
76
- const state = {
77
- asksHelp: false,
78
- first: "",
79
- index: 0
80
- };
81
- for (; state.index < argv.length; state.index += STEP$1) {
82
- const token = argv[state.index];
83
- if (typeof token === "string" && processHelpToken(token, argv[state.index + STEP$1], state) === "break") break;
84
- }
85
- return {
86
- asksHelp: state.asksHelp,
87
- first: state.first || [].pop()
88
- };
89
- };
90
- const resolveTarget = (first) => {
91
- if (typeof first !== "undefined" && isCommandName(first)) return first;
92
- };
93
- /**
94
- * Detect a --help / -h flag in argv before cac sees it (cac would otherwise
95
- * write to console.log itself). Returns the target command (when the first
96
- * positional names one) or 'root' for the top-level help.
97
- */
98
- const detectHelpFlag = (argv) => {
99
- const { asksHelp, first } = scanHelpTokens(argv);
100
- if (!asksHelp) return [].pop();
101
- return { target: resolveTarget(first) };
102
- };
103
- /**
104
- * Detect `--version` / `-v` ahead of cac for the same reason `detectHelpFlag`
105
- * exists. The `--` separator stops the scan so a trailing `--version` is left
106
- * for the (rejected) passthrough handler rather than flipping to the version
107
- * branch — siro forwards to no wrapped tool, so non-empty passthrough is a
108
- * usage error (see parseArgs).
109
- */
110
- const detectVersionFlag = (argv) => {
111
- for (let index = 0; index < argv.length; index += STEP$1) {
112
- const token = argv[index];
113
- if (typeof token === "string") {
114
- if (token === "--") return false;
115
- if (token === "--version" || token === "-v") return true;
116
- if (consumesNext(token, argv[index + STEP$1])) index += STEP$1;
117
- }
118
- }
119
- return false;
120
- };
121
-
122
- //#endregion
5
+ import { parseArgs } from "node:util";
123
6
  //#region src/cli/parsers.ts
124
- const FIRST_INDEX$1 = 0;
125
- const MIN_NODE_MAJOR = 20;
126
- const rejectUnknownFlags = (flags, allowed, scope) => {
127
- for (const key of Object.keys(flags)) if (!allowed.has(key)) {
128
- let where = "";
129
- if (scope) where = ` for '${scope}'`;
130
- throw new UsageError(`Unknown flag${where}: --${key}`);
131
- }
132
- };
133
7
  const parsePmFlag = (raw) => {
134
8
  if (typeof raw === "undefined") return;
135
9
  if (raw === true) throw new UsageError(`--pm requires a value (expected one of: ${PMS.join(", ")})`);
136
10
  if (typeof raw !== "string" || !isPM(raw)) throw new UsageError(`Unknown package manager: ${raw} (expected one of: ${PMS.join(", ")})`);
137
11
  return raw;
138
12
  };
13
+ const parseProjectTypeFlag = (raw) => {
14
+ if (typeof raw === "undefined") return;
15
+ if (raw === true) throw new UsageError(`--project-type requires a value (expected ${PROJECT_TYPES.join("|")})`);
16
+ if (typeof raw === "string" && isProjectType(raw)) return raw;
17
+ throw new UsageError(`Unknown project type: ${String(raw)} (expected ${PROJECT_TYPES.join("|")})`);
18
+ };
139
19
  const parseSeverityFlag = (raw) => {
140
20
  if (typeof raw === "undefined") return;
141
21
  if (raw === true) throw new UsageError(`--severity requires a value (expected ${SEVERITIES.join("|")})`);
142
22
  if (typeof raw !== "string" || !isSeverity(raw)) throw new UsageError(`Invalid severity: ${String(raw)} (expected ${SEVERITIES.join("|")})`);
143
23
  return raw;
144
24
  };
145
- const resolveReporter = (flags) => {
146
- if (flags.reporter === true) throw new UsageError(`--reporter requires a value (expected one of: ${BUILTIN_REPORTER_NAMES.join(", ")}, or a reporter registered via siro.config.ts)`);
147
- if (typeof flags.reporter === "string") {
148
- if (flags.json === true) throw new UsageError("Use either --reporter or --json, not both.");
149
- return flags.reporter;
150
- }
151
- if (flags.json === true) return "json";
152
- return "pretty";
153
- };
154
25
  const ensureNodeVersion = (nodeVersion) => {
155
- const major = Number(nodeVersion.replace(/^v/u, "").split(".")[FIRST_INDEX$1]);
156
- if (Number.isFinite(major) && major < MIN_NODE_MAJOR) throw new UsageError(`Node ${MIN_NODE_MAJOR}+ required (you are on ${nodeVersion}). Upgrade your runtime to use siro.`);
26
+ if (!isSupportedNodeVersion(nodeVersion)) throw new UsageError(`Node ${SUPPORTED_NODE_RANGE} required (you are on ${nodeVersion}). Upgrade your runtime to use siro.`);
157
27
  };
158
-
159
28
  //#endregion
160
- //#region src/cli/cac-bridge.ts
161
- const FIRST_INDEX = 0;
162
- const STEP = 1;
163
- const ARGS_OFFSET = 2;
164
- const buildCli = () => {
165
- const cli = cac("siro");
166
- cli.option("--pm <name>", "Target a specific package manager").option("--reporter <name>", `Reporter (${BUILTIN_REPORTER_NAMES.join("|")})`).option("--json", "Shortcut for --reporter json").option("--severity <level>", `Display/fail threshold (${SEVERITIES.join("|")})`).help().version(version);
167
- for (const name of COMMANDS) {
168
- let desc = `${name} a repository`;
169
- if (name === "check") desc = "Alias of lint";
170
- cli.command(`${name} [cwd]`, desc);
29
+ //#region src/cli/commands.ts
30
+ const COMMANDS = ["lint", "check"];
31
+ const isCommandName = (value) => COMMANDS.some((cmd) => cmd === value);
32
+ //#endregion
33
+ //#region src/cli/parse-args.ts
34
+ const VALUE_FLAGS = new Set([
35
+ "pm",
36
+ "project-type",
37
+ "reporter",
38
+ "severity"
39
+ ]);
40
+ const BOOLEAN_FLAGS = new Set([
41
+ "help",
42
+ "version",
43
+ "json"
44
+ ]);
45
+ const parseCommand = (argv) => {
46
+ const { tokens } = parseArgs({
47
+ args: [...argv],
48
+ options: {
49
+ help: {
50
+ type: "boolean",
51
+ short: "h"
52
+ },
53
+ version: {
54
+ type: "boolean",
55
+ short: "v"
56
+ }
57
+ },
58
+ strict: false,
59
+ tokens: true
60
+ });
61
+ const flags = /* @__PURE__ */ new Map();
62
+ const positionals = [];
63
+ let error;
64
+ for (let index = 0; index < tokens.length; index += 1) {
65
+ const token = tokens[index];
66
+ if (!token) break;
67
+ if (token.kind === "option-terminator") {
68
+ if (index + 1 < tokens.length) error ??= "siro takes no passthrough arguments after `--`.";
69
+ break;
70
+ }
71
+ if (token.kind === "positional") {
72
+ positionals.push(token.value);
73
+ continue;
74
+ }
75
+ if (BOOLEAN_FLAGS.has(token.name)) if (token.value !== void 0) error ??= `Flag ${token.rawName} does not accept a value.`;
76
+ else {
77
+ if (token.name === "json" && flags.has("json")) error ??= "--json must be specified only once.";
78
+ flags.set(token.name, true);
79
+ }
80
+ else if (VALUE_FLAGS.has(token.name)) {
81
+ let value = token.value;
82
+ const next = tokens[index + 1];
83
+ if (value === void 0 && next?.kind === "positional" && next.index === token.index + 1) {
84
+ value = next.value;
85
+ index += 1;
86
+ }
87
+ if (value === void 0 || value === "") error ??= `${token.rawName} requires a value.`;
88
+ else {
89
+ if (flags.has(token.name)) error ??= `${token.rawName} must be specified only once.`;
90
+ flags.set(token.name, value);
91
+ }
92
+ } else error ??= `Unknown flag: ${token.rawName}`;
171
93
  }
172
- return cli;
173
- };
174
- const extractPositionalCwd = (matchedCommand, args) => {
175
- if (matchedCommand) return args[FIRST_INDEX];
176
- return args[STEP];
177
- };
178
- const extractCommandCandidate = (matchedCommand, args) => {
179
- if (matchedCommand) return matchedCommand;
180
- return args[FIRST_INDEX];
181
- };
182
- const extractExtraPositionals = (matchedCommand, args) => {
183
- if (matchedCommand) return args.slice(STEP);
184
- return args.slice(ARGS_OFFSET);
185
- };
186
- const parseCacOutput = (argv) => {
187
- const cli = buildCli();
188
- const parsed = cli.parse([
189
- "node",
190
- "siro",
191
- ...argv
192
- ], { run: false });
193
- const flags = parsed.options;
194
- const matchedCommand = cli.matchedCommandName;
195
- const positionalCwd = extractPositionalCwd(matchedCommand, parsed.args);
94
+ const [command, cwd, ...extra] = positionals;
95
+ if (flags.has("help")) return {
96
+ kind: "help",
97
+ target: command && isCommandName(command) ? command : void 0
98
+ };
99
+ if (flags.has("version")) return { kind: "version" };
100
+ if (error) throw new UsageError(error);
101
+ if (command === void 0) return { kind: "usage" };
102
+ if (command === "init") return {
103
+ kind: "usage",
104
+ reason: "The 'init' command was removed: siro is lint-only. Run `siro lint --reporter json` and review each finding's remediation."
105
+ };
106
+ if (!isCommandName(command)) return {
107
+ kind: "usage",
108
+ reason: `Unknown command: ${command}`
109
+ };
110
+ if (extra.length > 0) throw new UsageError(`Unexpected extra argument: ${extra.join(" ")}`);
111
+ if (flags.has("reporter") && flags.has("json")) throw new UsageError("Invalid reporter selection: use either --reporter or --json.");
112
+ const reporter = flags.get("reporter");
196
113
  return {
197
- commandCandidate: extractCommandCandidate(matchedCommand, parsed.args),
198
- extraPositionals: extractExtraPositionals(matchedCommand, parsed.args),
199
- flags,
200
- matchedCommand,
201
- positionalCwd
114
+ kind: "lint",
115
+ cwd: asAbsPath(path.resolve(cwd ?? process.cwd())),
116
+ pm: parsePmFlag(flags.get("pm")),
117
+ projectType: parseProjectTypeFlag(flags.get("project-type")),
118
+ severity: parseSeverityFlag(flags.get("severity")),
119
+ reporter: typeof reporter === "string" ? reporter : flags.has("json") ? JSON_REPORTER_NAME : DEFAULT_REPORTER_NAME
202
120
  };
203
121
  };
204
-
205
122
  //#endregion
206
123
  //#region src/cli/help.ts
207
124
  const PMS_LIST = PMS.join("|");
208
125
  const REPORTERS_LIST = BUILTIN_REPORTER_NAMES.join("|");
126
+ const PROJECT_TYPES_LIST = PROJECT_TYPES.join("|");
209
127
  const SEVERITIES_LIST = SEVERITIES.join("|");
210
128
  const FLAG_LINES = {
211
129
  json: " --json Shortcut for --reporter json",
212
130
  pm: ` --pm <name> Target a specific package manager (${PMS_LIST})`,
131
+ projectType: ` --project-type <type> Project type (${PROJECT_TYPES_LIST}; default auto)`,
213
132
  reporter: ` --reporter <name> Reporter (${REPORTERS_LIST}; additional reporters can be registered via siro.config.ts)`,
214
133
  severity: ` --severity <level> Show + fail on findings at or above this level (${SEVERITIES_LIST})`
215
134
  };
@@ -224,6 +143,7 @@ const HELP_ROOT = [
224
143
  "",
225
144
  "GLOBAL FLAGS",
226
145
  FLAG_LINES.pm,
146
+ FLAG_LINES.projectType,
227
147
  " --version Print the siro version",
228
148
  " --help Show help for siro or a command",
229
149
  "",
@@ -251,6 +171,7 @@ const HELP_LINT = [
251
171
  "",
252
172
  "FLAGS",
253
173
  FLAG_LINES.pm,
174
+ FLAG_LINES.projectType,
254
175
  FLAG_LINES.reporter,
255
176
  FLAG_LINES.json,
256
177
  FLAG_LINES.severity,
@@ -271,146 +192,63 @@ const renderHelp = (target) => {
271
192
  if (target === "lint" || target === "check") return HELP_LINT;
272
193
  return target;
273
194
  };
274
-
275
195
  //#endregion
276
196
  //#region src/cli.ts
277
- const EMPTY = 0;
278
197
  const EXIT_SUCCESS = 0;
279
198
  const EXIT_USAGE = 2;
280
199
  const EXIT_CRASH = 70;
281
- const rejectPassthrough = (flags) => {
282
- const passthrough = flags["--"];
283
- if (Array.isArray(passthrough) && passthrough.length > EMPTY) throw new UsageError("siro takes no passthrough arguments after `--`.");
284
- };
285
- const resolveCommandCandidate = (commandCandidate) => {
286
- if (typeof commandCandidate === "undefined") return { kind: "usage" };
287
- if (commandCandidate === "init") return {
288
- kind: "usage",
289
- reason: "The 'init' command was removed: siro is lint-only. Run `siro lint --reporter json` and apply each finding's `fix` operations with your editor or an agent skill."
290
- };
291
- if (typeof commandCandidate !== "string" || !isCommandName(commandCandidate)) return {
292
- kind: "usage",
293
- reason: `Unknown command: ${String(commandCandidate)}`
294
- };
295
- return {
296
- kind: "command",
297
- name: commandCandidate
298
- };
299
- };
300
- const SINGLE = 1;
301
- const rejectExtraPositionals = (extraPositionals) => {
302
- if (extraPositionals.length > EMPTY) {
303
- let plural = "";
304
- if (extraPositionals.length > SINGLE) plural = "s";
305
- throw new UsageError(`Unexpected extra argument${plural}: ${extraPositionals.join(" ")}`);
306
- }
307
- };
308
- const resolveCwd = (positionalCwd) => {
309
- let raw = process.cwd();
310
- if (typeof positionalCwd === "string") raw = positionalCwd;
311
- return asAbsPath(path.resolve(raw));
312
- };
313
- const checkPreScanFlags = (argv) => {
314
- const preHelp = detectHelpFlag(argv);
315
- if (typeof preHelp !== "undefined") return {
316
- kind: "help",
317
- target: preHelp.target
318
- };
319
- if (detectVersionFlag(argv)) return { kind: "version" };
320
- };
321
- const buildLintCommand = (flags, positionalCwd, commandCandidate) => {
322
- rejectUnknownFlags(flags, flagsFor(commandCandidate), commandCandidate);
323
- return {
324
- cwd: resolveCwd(positionalCwd),
325
- kind: "lint",
326
- pm: parsePmFlag(flags.pm),
327
- reporter: resolveReporter(flags),
328
- severity: parseSeverityFlag(flags.severity)
329
- };
330
- };
331
- const validateFlags = (flags) => {
332
- rejectUnknownFlags(flags, KNOWN_FLAGS);
333
- rejectPassthrough(flags);
334
- };
335
- const parseArgs = (argv) => {
336
- const preScan = checkPreScanFlags(argv);
337
- if (typeof preScan !== "undefined") return preScan;
338
- const { commandCandidate, extraPositionals, flags, positionalCwd } = parseCacOutput(argv);
339
- validateFlags(flags);
340
- const resolved = resolveCommandCandidate(commandCandidate);
341
- if (resolved.kind === "usage") return resolved;
342
- rejectExtraPositionals(extraPositionals);
343
- return buildLintCommand(flags, positionalCwd, resolved.name);
344
- };
345
- const handleVersion = (io) => {
346
- io.stdout(version);
347
- return Promise.resolve(EXIT_SUCCESS);
348
- };
349
- const handleHelp = (target, io) => {
350
- io.stdout(renderHelp(target));
351
- return Promise.resolve(EXIT_SUCCESS);
352
- };
353
- const handleUsage = (reason, io) => {
354
- if (typeof reason !== "undefined") io.stderr(`${reason}\n`);
355
- io.stderr(renderHelp());
356
- return Promise.resolve(EXIT_USAGE);
357
- };
358
200
  const dispatch = (cmd, io) => {
359
201
  switch (cmd.kind) {
360
- case "version": return handleVersion(io);
361
- case "help": return handleHelp(cmd.target, io);
362
- case "usage": return handleUsage(cmd.reason, io);
363
- case "lint": return lintCommand(cmd, io);
202
+ case "version":
203
+ io.stdout(version);
204
+ return EXIT_SUCCESS;
205
+ case "help":
206
+ io.stdout(renderHelp(cmd.target));
207
+ return EXIT_SUCCESS;
208
+ case "usage":
209
+ if (cmd.reason) io.stderr(`${cmd.reason}\n`);
210
+ io.stderr(renderHelp());
211
+ return EXIT_USAGE;
212
+ case "lint":
213
+ assertDirectory(cmd.cwd);
214
+ return loadConfig(cmd.cwd).then((config) => lintCommand({
215
+ ...cmd,
216
+ config
217
+ }, io));
364
218
  default: throw new Error(`Unhandled command kind: ${String(cmd)}`);
365
219
  }
366
220
  };
367
- const handleAsyncError = (error, io) => {
221
+ const handleError = (error, io) => {
368
222
  if (error instanceof SiroError) {
369
223
  io.stderr(error.message);
370
224
  return error.exitCode;
371
225
  }
372
- if (isNodeError(error) && typeof error.errno === "number") {
226
+ if (isNodeError(error) && "errno" in error && typeof error.errno === "number") {
373
227
  io.stderr(`File system error: ${error.message}`);
374
228
  return EXIT_USAGE;
375
229
  }
376
230
  throw error;
377
231
  };
378
- const handleSyncError = (error, io) => {
379
- if (error instanceof SiroError) {
380
- io.stderr(error.message);
381
- return Promise.resolve(error.exitCode);
382
- }
383
- if (isNodeError(error) && typeof error.errno === "number") {
384
- io.stderr(`File system error: ${error.message}`);
385
- return Promise.resolve(EXIT_USAGE);
386
- }
387
- return Promise.reject(error);
388
- };
389
- const run = (argv, io = nodeIO) => {
232
+ const run = async (argv, io = nodeIO) => {
390
233
  try {
391
234
  ensureNodeVersion(process.versions.node);
392
- return dispatch(parseArgs(argv), io).catch((error) => handleAsyncError(error, io));
235
+ return await dispatch(parseCommand(argv), io);
393
236
  } catch (error) {
394
- return handleSyncError(error, io);
237
+ return handleError(error, io);
395
238
  }
396
239
  };
397
- const onRunFulfilled = (code) => {
398
- process.exitCode = code;
399
- };
400
- const onRunRejected = (error) => {
401
- let errStr = String(error);
402
- if (error instanceof Error) errStr = error.stack ?? error.message;
403
- process.stderr.write(`${errStr}\n`);
404
- process.exitCode = EXIT_CRASH;
240
+ const runMain = async (argv) => {
241
+ try {
242
+ process.exitCode = await run(argv);
243
+ } catch (error) {
244
+ const errStr = error instanceof Error ? error.stack ?? error.message : String(error);
245
+ process.stderr.write(`${errStr}\n`);
246
+ process.exitCode = EXIT_CRASH;
247
+ }
405
248
  };
406
- const ARGV_SKIP = 2;
407
249
  const [, invokedPath] = process.argv;
408
- if (invokedPath && import.meta.url === pathToFileURL(invokedPath).href) try {
409
- onRunFulfilled(await run(process.argv.slice(ARGV_SKIP)));
410
- } catch (error) {
411
- onRunRejected(error);
412
- }
413
-
250
+ if (invokedPath && import.meta.url === pathToFileURL(invokedPath).href) await runMain(process.argv.slice(2));
414
251
  //#endregion
415
- export { run };
252
+ export { run, runMain };
253
+
416
254
  //# sourceMappingURL=cli.mjs.map