@isentinel/eslint-config 6.0.0-beta.2 → 6.0.0-beta.21

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.
@@ -0,0 +1,4827 @@
1
+ import { createRequire } from "node:module";
2
+ import process$1 from "node:process";
3
+ import { getPackageInfoSync, isPackageExists } from "local-pkg";
4
+ import yargs from "yargs";
5
+ import "find-up-simple";
6
+ import fs from "node:fs";
7
+ import path from "node:path";
8
+ import "semver";
9
+ import crypto from "node:crypto";
10
+ import concurrently from "concurrently";
11
+ import { execFileSync, spawn, spawnSync } from "node:child_process";
12
+ import { fileURLToPath } from "node:url";
13
+ import os, { availableParallelism } from "node:os";
14
+ import fileEntryCache from "file-entry-cache";
15
+ //#region \0rolldown/runtime.js
16
+ var __create = Object.create;
17
+ var __defProp = Object.defineProperty;
18
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
19
+ var __getOwnPropNames = Object.getOwnPropertyNames;
20
+ var __getProtoOf = Object.getPrototypeOf;
21
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
22
+ var __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod), cb = null), mod.exports);
23
+ var __copyProps = (to, from, except, desc) => {
24
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
25
+ key = keys[i];
26
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
27
+ get: ((k) => from[k]).bind(null, key),
28
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
29
+ });
30
+ }
31
+ return to;
32
+ };
33
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
34
+ value: mod,
35
+ enumerable: true
36
+ }) : target, mod));
37
+ //#endregion
38
+ //#region package.json
39
+ var version = "6.0.0-beta.21";
40
+ //#endregion
41
+ //#region src/lint-cli/lib/cli/types.ts
42
+ /**
43
+ * User-facing error. When thrown from the runner the message is printed
44
+ * without a stack trace and the process exits non-zero.
45
+ */
46
+ var CliError = class extends Error {
47
+ name = "CliError";
48
+ };
49
+ /**
50
+ * The real TS/JS-family file extensions {@link GLOB_SRC_EXT} encodes (the glob
51
+ * additionally matches non-existent combinations such as `cjsx`). Kept next to
52
+ * the glob so the two are maintained together; consumed by the lint CLI to size
53
+ * its type-aware pass.
54
+ */
55
+ const GLOB_SRC_EXTENSIONS = [
56
+ "ts",
57
+ "tsx",
58
+ "mts",
59
+ "cts",
60
+ "js",
61
+ "jsx",
62
+ "mjs",
63
+ "cjs"
64
+ ];
65
+ /**
66
+ * Every file extension the preset lints by default: the TS/JS family
67
+ * ({@link GLOB_SRC_EXTENSIONS} / {@link GLOB_SRC}) plus the other languages the
68
+ * config enables — JSONC ({@link GLOB_ALL_JSON}), YAML ({@link GLOB_YAML}),
69
+ * TOML ({@link GLOB_TOML}), Markdown ({@link GLOB_MARKDOWN}) and Lua
70
+ * ({@link GLOB_LUA}). Each group is bound to its glob so the list stays in step
71
+ * with the patterns.
72
+ */
73
+ const GLOB_LINTABLE_EXTENSIONS = [
74
+ ...GLOB_SRC_EXTENSIONS,
75
+ "json",
76
+ "jsonc",
77
+ "json5",
78
+ "yaml",
79
+ "yml",
80
+ "toml",
81
+ "md",
82
+ "lua"
83
+ ];
84
+ //#endregion
85
+ //#region src/guards.ts
86
+ /**
87
+ * Internal runtime type guards. Prefer these over `as` assertions so values
88
+ * crossing untyped boundaries (`JSON.parse`, dynamic `import`, plugin objects)
89
+ * are validated at runtime rather than asserted away.
90
+ */
91
+ /**
92
+ * Whether a value is a non-null, non-array object usable as a string-keyed
93
+ * record. Narrows `unknown` without an assertion.
94
+ *
95
+ * @param value - The value to test.
96
+ * @returns Whether the value is a plain object.
97
+ */
98
+ function isRecord(value) {
99
+ return typeof value === "object" && value !== null && !Array.isArray(value);
100
+ }
101
+ /**
102
+ * Whether a value is an array whose every element is a string.
103
+ *
104
+ * @param value - The value to test.
105
+ * @returns Whether the value is a `string` array.
106
+ */
107
+ function isStringArray(value) {
108
+ return Array.isArray(value) && value.every((item) => typeof item === "string");
109
+ }
110
+ createRequire(import.meta.url);
111
+ /**
112
+ * Whether the environment signals a CI run. Treats an unset, empty, `"false"`
113
+ * or `"0"` `CI` value as not-CI, so a deliberately falsy `CI=false` is honoured
114
+ * rather than read as truthy.
115
+ *
116
+ * @param environment - The environment variables to inspect.
117
+ * @returns Whether CI is active.
118
+ */
119
+ function isCi(environment = process$1.env) {
120
+ const value = environment["CI"];
121
+ return value !== void 0 && value !== "" && value !== "false" && value !== "0";
122
+ }
123
+ function isInGitHooksOrLintStaged(environment = process$1.env) {
124
+ return [
125
+ environment["GIT_HOOK"],
126
+ environment["GIT_PARAMS"],
127
+ environment["VSCODE_GIT_COMMAND"],
128
+ environment["npm_lifecycle_script"]?.startsWith("lint-staged")
129
+ ].some(Boolean);
130
+ }
131
+ /**
132
+ * Agent probes, mirroring `unjs/std-env`'s `src/agents.ts` (@50dc652) so the
133
+ * two stay diffable. An entry is either an environment variable that is truthy
134
+ * only inside that agent, or a matcher for the agents that hide in a shared
135
+ * variable. Entries run in std-env's order, one agent per line group.
136
+ *
137
+ * `CLAUDE_CODE_ENTRYPOINT` has no std-env counterpart; it predates this port
138
+ * and stays for the Claude Code versions that set only it.
139
+ */
140
+ const AGENT_CHECKS = [
141
+ "CLAUDECODE",
142
+ "CLAUDE_CODE",
143
+ "CLAUDE_CODE_ENTRYPOINT",
144
+ "REPL_ID",
145
+ "GEMINI_CLI",
146
+ "CODEX_SANDBOX",
147
+ "CODEX_THREAD_ID",
148
+ "OPENCODE",
149
+ environmentMatcher("PATH", /\.pi[/\\]agent/),
150
+ "AUGMENT_AGENT",
151
+ "GOOSE_PROVIDER",
152
+ "JUNIE_DATA",
153
+ "JUNIE_SHIM_PATH",
154
+ environmentMatcher("EDITOR", /devin/),
155
+ "CURSOR_AGENT",
156
+ environmentMatcher("TERM_PROGRAM", /kiro/, { noTty: true })
157
+ ];
158
+ /**
159
+ * Whether the process runs inside an AI coding agent session.
160
+ *
161
+ * `AI_AGENT` forces detection on, matching std-env's explicit override. Git
162
+ * hook and lint-staged runs are excluded even under an agent: those are
163
+ * human-initiated commits whose output a person reads.
164
+ *
165
+ * Not a pure function of `environment` — the kiro check also consults
166
+ * `process.stdout.isTTY` (see {@link environmentMatcher}).
167
+ *
168
+ * @param environment - The environment variables to inspect.
169
+ * @returns Whether an agent session was detected.
170
+ */
171
+ function isInAgentSession(environment = process$1.env) {
172
+ if (isInGitHooksOrLintStaged(environment)) return false;
173
+ if (environment["AI_AGENT"] !== void 0 && environment["AI_AGENT"] !== "") return true;
174
+ return AGENT_CHECKS.some((check) => {
175
+ return typeof check === "string" ? Boolean(environment[check]) : check(environment);
176
+ });
177
+ }
178
+ /**
179
+ * Whether autofixes that rewrite code an agent just wrote should be withheld.
180
+ *
181
+ * Opt-in, and deliberately not tied to {@link isInAgentSession}: merely being
182
+ * an agent is no reason to lint differently — an agent that runs the linter
183
+ * wants the same fixes a human would get. The case this exists for is the
184
+ * wrapper that fixes without anyone reading the diff (an edit hook, a
185
+ * fix-on-save daemon), which knows it is that and can say so.
186
+ *
187
+ * @param environment - The environment variables to inspect.
188
+ * @returns Whether fix suppression was requested.
189
+ */
190
+ function isAgentAutofixDisabled(environment = process$1.env) {
191
+ const value = environment["ESLINT_AGENT_NO_AUTOFIX"];
192
+ return value === "true" || value === "1";
193
+ }
194
+ function isInEditorEnvironment(environment = process$1.env) {
195
+ const explicitValue = environment["ESLINT_IN_EDITOR"];
196
+ if (explicitValue !== void 0) return explicitValue === "true" || explicitValue === "1";
197
+ if (isCi(environment)) return false;
198
+ if (isInGitHooksOrLintStaged(environment)) return false;
199
+ return [
200
+ environment["VSCODE_PID"],
201
+ environment["VSCODE_CWD"],
202
+ environment["JETBRAINS_IDE"],
203
+ environment["VIM"],
204
+ environment["NVIM"]
205
+ ].some(Boolean);
206
+ }
207
+ /**
208
+ * Build a probe that regex-tests one environment variable, for the agents that
209
+ * announce themselves inside a variable a human also sets.
210
+ *
211
+ * `noTty` marks a variable an interactive user shares with the agent (kiro's
212
+ * `TERM_PROGRAM` is set in its terminal too): an attached stdout TTY means a
213
+ * person is watching, so the probe declines.
214
+ *
215
+ * @param name - The environment variable to test.
216
+ * @param pattern - The pattern the value must match.
217
+ * @param options - Probe options.
218
+ * @param options.noTty - Whether an attached stdout TTY disqualifies the match.
219
+ * @returns The probe.
220
+ */
221
+ function environmentMatcher(name, pattern, { noTty = false } = {}) {
222
+ return (environment) => {
223
+ if (noTty && process$1.stdout.isTTY) return false;
224
+ const value = environment[name];
225
+ return value !== void 0 && pattern.test(value);
226
+ };
227
+ }
228
+ //#endregion
229
+ //#region src/lint-cli/lib/cli/parse.ts
230
+ /**
231
+ * Parse an integer that must be at least `min`, returning `undefined` for any
232
+ * value that is missing, non-numeric, fractional or below the bound. The three
233
+ * env-derived numeric knobs (concurrency, files-per-worker, affected-bust
234
+ * threshold) share this parse and keep their differing failure actions
235
+ * (undefined vs throw vs default) at their call sites.
236
+ *
237
+ * @param value - The raw string to parse (usually an environment variable).
238
+ * @param min - The inclusive lower bound the parsed integer must meet.
239
+ * @returns The parsed integer, or `undefined` when it is absent or invalid.
240
+ */
241
+ function parseBoundedInteger(value, min) {
242
+ if (value === void 0) return;
243
+ const parsed = Number(value.trim());
244
+ if (!Number.isInteger(parsed) || parsed < min) return;
245
+ return parsed;
246
+ }
247
+ //#endregion
248
+ //#region src/lint-cli/lib/cli/split-args.ts
249
+ /**
250
+ * Split a raw argument string into tokens, honouring single and double quotes
251
+ * so values such as `--rule "no-console: error"` survive as one argument.
252
+ *
253
+ * @param input - The raw argument string to split.
254
+ * @returns The parsed argument tokens.
255
+ */
256
+ function splitArgs(input) {
257
+ const tokens = [];
258
+ let current = "";
259
+ let quote;
260
+ let hasToken = false;
261
+ for (const char of input) {
262
+ if (quote !== void 0) {
263
+ if (char === quote) quote = void 0;
264
+ else current += char;
265
+ continue;
266
+ }
267
+ if (char === "\"" || char === "'") {
268
+ quote = char;
269
+ hasToken = true;
270
+ continue;
271
+ }
272
+ if (char === " " || char === " " || char === "\n") {
273
+ if (hasToken) {
274
+ tokens.push(current);
275
+ current = "";
276
+ hasToken = false;
277
+ }
278
+ continue;
279
+ }
280
+ current += char;
281
+ hasToken = true;
282
+ }
283
+ if (hasToken) tokens.push(current);
284
+ return tokens;
285
+ }
286
+ //#endregion
287
+ //#region src/lint-cli/lib/cli/options.ts
288
+ /**
289
+ * Parse and validate an `isentinel-lint` argv slice (without the node/bin
290
+ * prefix). Throws {@link CliError} on any invalid combination.
291
+ *
292
+ * `--agents` is tri-state: absent it follows {@link isInAgentSession}, so an
293
+ * agent gets agent-shaped output without passing the flag and `--no-agents`
294
+ * forces it back off.
295
+ *
296
+ * @param argv - The argument slice to parse.
297
+ * @param environment - The process environment (defaults to `process.env`).
298
+ * @returns The parsed and validated options.
299
+ */
300
+ function parseArguments(argv, environment = process$1.env) {
301
+ const separator = argv.indexOf("--");
302
+ const head = separator === -1 ? argv : argv.slice(0, separator);
303
+ const tail = separator === -1 ? [] : argv.slice(separator);
304
+ const eslintExtract = extractValueOption(head, "eslint-args");
305
+ const oxlintExtract = extractValueOption(eslintExtract.rest, "oxlint-args");
306
+ const parsed = yargs([...oxlintExtract.rest, ...tail]).scriptName("isentinel-lint").parserConfiguration({
307
+ "boolean-negation": true,
308
+ "populate--": true
309
+ }).option("eslint", {
310
+ description: "Run only ESLint.",
311
+ type: "boolean"
312
+ }).option("oxlint", {
313
+ description: "Run only oxlint.",
314
+ type: "boolean"
315
+ }).option("fix", {
316
+ description: "Apply fixes (oxlint then ESLint).",
317
+ type: "boolean"
318
+ }).option("agents", {
319
+ description: "Agent-friendly output (default: on in an agent session).",
320
+ type: "boolean"
321
+ }).option("print", {
322
+ description: "Print the composed commands.",
323
+ type: "boolean"
324
+ }).option("cache", {
325
+ default: true,
326
+ description: "Use ESLint's on-disk cache.",
327
+ type: "boolean"
328
+ }).option("oxlint-type-aware", {
329
+ default: true,
330
+ description: "Pass --type-aware to oxlint.",
331
+ type: "boolean"
332
+ }).option("type-aware", {
333
+ choices: [
334
+ "off",
335
+ "only",
336
+ "full"
337
+ ],
338
+ description: "ESLint type-aware mode.",
339
+ type: "string"
340
+ }).option("concurrency", {
341
+ description: "ESLint concurrency override (<n> or off).",
342
+ type: "string"
343
+ }).conflicts("eslint", "oxlint").strictOptions().exitProcess(false).fail((message, error) => {
344
+ throw new CliError(message ?? error?.message ?? "Invalid arguments.");
345
+ }).parseSync();
346
+ const eslintOnly = parsed.eslint === true;
347
+ const oxlintOnly = parsed.oxlint === true;
348
+ const { typeAware } = parsed;
349
+ const fix = parsed.fix === true;
350
+ if (fix && typeAware !== void 0) throw new CliError("Cannot combine --fix with --type-aware; --fix always uses the full config.");
351
+ const eslintArgs = eslintExtract.value !== void 0 ? splitArgs(eslintExtract.value) : [];
352
+ const oxlintArgs = oxlintExtract.value !== void 0 ? splitArgs(oxlintExtract.value) : [];
353
+ const rawPassthrough = parsed["--"];
354
+ const passthrough = Array.isArray(rawPassthrough) ? rawPassthrough.map(String) : [];
355
+ if (passthrough.length > 0) {
356
+ if (eslintOnly === oxlintOnly) throw new CliError("`--` passthrough requires a single tool; use it with --eslint or --oxlint.");
357
+ if (eslintOnly) eslintArgs.push(...passthrough);
358
+ else oxlintArgs.push(...passthrough);
359
+ }
360
+ const paths = parsed._.map(String).filter((value) => value.length > 0);
361
+ return {
362
+ agents: parsed.agents ?? isInAgentSession(environment),
363
+ cache: parsed.cache,
364
+ concurrency: parseConcurrency(parsed.concurrency),
365
+ eslint: eslintOnly,
366
+ eslintArgs,
367
+ fix,
368
+ oxlint: oxlintOnly,
369
+ oxlintArgs,
370
+ oxlintTypeAware: parsed.oxlintTypeAware,
371
+ paths: paths.length > 0 ? paths : ["."],
372
+ print: parsed.print === true,
373
+ typeAware
374
+ };
375
+ }
376
+ /**
377
+ * Pull a single `--name <value>` / `--name=<value>` option out of an argv
378
+ * slice. Done before yargs because yargs refuses dash-prefixed values (for
379
+ * example `--eslint-args "--max-warnings 0"`).
380
+ *
381
+ * @param argv - The argument slice to scan.
382
+ * @param name - The option name (without leading dashes).
383
+ * @returns The extracted value and the remaining arguments.
384
+ */
385
+ function extractValueOption(argv, name) {
386
+ const flag = `--${name}`;
387
+ const inline = `${flag}=`;
388
+ const rest = [];
389
+ let value;
390
+ for (let index = 0; index < argv.length; index += 1) {
391
+ const token = argv[index];
392
+ if (token === void 0) continue;
393
+ if (token === flag) {
394
+ const next = argv[index + 1];
395
+ if (next === void 0) throw new CliError(`Option ${flag} requires a value.`);
396
+ value = next;
397
+ index += 1;
398
+ continue;
399
+ }
400
+ if (token.startsWith(inline)) {
401
+ value = token.slice(inline.length);
402
+ continue;
403
+ }
404
+ rest.push(token);
405
+ }
406
+ return {
407
+ rest,
408
+ value
409
+ };
410
+ }
411
+ function parseConcurrency(value) {
412
+ if (value === void 0) return;
413
+ if (value === "off") return "off";
414
+ const parsed = parseBoundedInteger(value, 1);
415
+ if (parsed === void 0) throw new CliError(`Invalid --concurrency "${value}"; expected a positive integer or "off".`);
416
+ return parsed;
417
+ }
418
+ /**
419
+ * Environment variable a consumer sets to fold its own config branches into the
420
+ * cache key. The env-derived key only sees the branches this preset owns, so a
421
+ * consumer whose `eslint.config.*` varies on anything else — its own
422
+ * `isInAgentSession()` check, a feature flag, an explicit `isAgent` /
423
+ * `isInEditor` / `defaultSeverity` option — must name that branch here or its
424
+ * variants will keep sharing (and overwriting) one cache.
425
+ */
426
+ const CACHE_KEY_OVERRIDE = "ISENTINEL_LINT_CACHE_KEY";
427
+ /**
428
+ * Resolve the cache-variant key for this run. Every input that makes the preset
429
+ * resolve a *different* ESLint config gets its own key, and therefore its own
430
+ * cache file and its own per-cache state.
431
+ *
432
+ * This exists because ESLint stores a `hashOfConfig` per cache entry: two runs
433
+ * whose resolved configs differ by even one rule severity invalidate each
434
+ * other's entries wholesale when they share a cache file. An agent run and a
435
+ * human run alternating against one cache re-lint the whole project in both
436
+ * directions, forever. Splitting the file by variant means nothing is
437
+ * invalidated — the variants simply stop overwriting each other.
438
+ *
439
+ * The key is derived from the environment rather than from the resolved config
440
+ * because hashing the real config costs a full `eslint --print-config`
441
+ * (~4.6s/run), and the key stored in an existing cache describes the *previous*
442
+ * run, which cannot predict this one.
443
+ *
444
+ * @param environment - The process environment to derive the key from.
445
+ * @returns An 8-character hex key identifying the config variant.
446
+ */
447
+ function resolveCacheKey(environment) {
448
+ const parts = [
449
+ isInAgentSession(environment),
450
+ isAgentAutofixDisabled(environment),
451
+ isInEditorEnvironment(environment),
452
+ isCi(environment),
453
+ environment[CACHE_KEY_OVERRIDE] ?? ""
454
+ ];
455
+ return crypto.createHash("sha256").update(parts.join("|")).digest("hex").slice(0, 8);
456
+ }
457
+ /**
458
+ * Describe a run from the process facts it starts with.
459
+ *
460
+ * @param cwd - The working directory.
461
+ * @param environment - The process environment.
462
+ * @param mutate - Whether the run may mutate (false for `--print`).
463
+ * @returns The run context every stage is threaded.
464
+ */
465
+ function resolveRunContext(cwd, environment, mutate) {
466
+ return {
467
+ key: resolveCacheKey(environment),
468
+ ci: isCi(environment),
469
+ cwd,
470
+ environment,
471
+ mutate
472
+ };
473
+ }
474
+ //#endregion
475
+ //#region src/lint-cli/lib/exec/resolve.ts
476
+ /** Memoised {@link resolveLocalBin} results, keyed by `cwd\0name`. */
477
+ const localBinCache = /* @__PURE__ */ new Map();
478
+ /**
479
+ * Resolve the JavaScript entry of a locally installed CLI (eslint / oxlint)
480
+ * from the consumer's `node_modules`, walking up from `cwd`. Returning the JS
481
+ * file lets the caller spawn it with `process.execPath` and no shell, avoiding
482
+ * Windows `.cmd`/`.ps1` quoting hazards. Memoised so the two ESLint passes
483
+ * resolve the same bin once.
484
+ *
485
+ * @param name - The package name to resolve (for example `eslint`).
486
+ * @param cwd - The directory to resolve from.
487
+ * @returns The absolute path to the package's JavaScript entry.
488
+ */
489
+ function resolveLocalBin(name, cwd) {
490
+ const cacheKey = `${cwd}\0${name}`;
491
+ const cached = localBinCache.get(cacheKey);
492
+ if (cached !== void 0) return cached;
493
+ const info = getPackageInfoSync(name, { paths: [cwd] });
494
+ if (info === void 0) throw new CliError(`Could not find "${name}". Install it in this project to run isentinel-lint.`);
495
+ const { bin } = info.packageJson;
496
+ const relative = typeof bin === "string" ? bin : bin?.[name];
497
+ if (relative === void 0) throw new CliError(`Package "${name}" does not declare a "${name}" bin entry.`);
498
+ const resolved = path.resolve(info.rootPath, relative);
499
+ localBinCache.set(cacheKey, resolved);
500
+ return resolved;
501
+ }
502
+ /**
503
+ * Resolve the agent-friendly ESLint formatter shipped alongside this entry
504
+ * (built from `src/formatter-agents.ts` to `formatter-agents.mjs`). Resolved
505
+ * lazily so it is only touched when `--agents` is used.
506
+ *
507
+ * Meaningful only in the bundled layout, where every entry is flattened into
508
+ * `dist/` and this resolves to `dist/formatter-agents.mjs`. Run from source
509
+ * there is no sibling `.mjs` at all, so the path names a file that does not
510
+ * exist — `--agents` is a shipped-package feature and has never worked from a
511
+ * source checkout. Unlike {@link resolveIgnoredHelper} there is no source
512
+ * fallback, because ESLint would have to load the `.ts` formatter itself.
513
+ *
514
+ * @returns The absolute path to the agent ESLint formatter.
515
+ */
516
+ function resolveAgentsFormatter() {
517
+ return fileURLToPath(new URL("./formatter-agents.mjs", import.meta.url));
518
+ }
519
+ /**
520
+ * Resolve the ignored-files helper shipped alongside this entry (built from
521
+ * `src/lint-cli/ignored-child.ts` to `lint-ignored.mjs`).
522
+ *
523
+ * Unlike {@link resolveAgentsFormatter} this falls back to the TypeScript
524
+ * source, because the runner spawns it itself and the fixture tests run from
525
+ * `src` where no bundle exists. That fallback path is relative to *this
526
+ * file's* source location (`lib/exec/`), while the built branch resolves
527
+ * against a flat `dist/` — which is why only the former carries `../../`. The
528
+ * fallback means the tests never exercise the shipped path — a broken or
529
+ * unshipped `dist` entry degrades the runner to "no ignore filtering" rather
530
+ * than failing.
531
+ *
532
+ * @returns The absolute path to the helper module.
533
+ */
534
+ function resolveIgnoredHelper() {
535
+ const built = fileURLToPath(new URL("./lint-ignored.mjs", import.meta.url));
536
+ return fs.existsSync(built) ? built : fileURLToPath(new URL("../../ignored-child.ts", import.meta.url));
537
+ }
538
+ //#endregion
539
+ //#region src/lint-cli/lib/exec/shell.ts
540
+ const SAFE_TOKEN = /^[\w@+=:,./-]+$/;
541
+ /**
542
+ * Render a command as a shell-equivalent line (env prefix + logical binary +
543
+ * arguments) for `--print`. Uses POSIX quoting for stable, cross-platform
544
+ * output.
545
+ *
546
+ * @param command - The child command to render.
547
+ * @returns The shell-equivalent command line.
548
+ */
549
+ function formatCommandLine(command) {
550
+ const environmentPrefix = Object.entries(command.env).flatMap(([key, value]) => value === void 0 ? [] : [`${key}=${quotePosix(value)}`]).join(" ");
551
+ const body = [command.bin, ...command.args].map(quotePosix).join(" ");
552
+ return environmentPrefix.length > 0 ? `${environmentPrefix} ${body}` : body;
553
+ }
554
+ /**
555
+ * Build the command string concurrently runs through a shell. The tool is
556
+ * launched via `node <binJs>` so no `.cmd`/`.ps1` shim quoting is involved.
557
+ *
558
+ * @param nodePath - Absolute path to the Node executable.
559
+ * @param binJsPath - Absolute path to the tool's JavaScript entry.
560
+ * @param args - The tool arguments.
561
+ * @param platform - The platform whose shell quoting rules to apply.
562
+ * @returns The shell command string.
563
+ */
564
+ function buildShellCommand(nodePath, binJsPath, args, platform) {
565
+ const tokens = [
566
+ nodePath,
567
+ binJsPath,
568
+ ...args
569
+ ];
570
+ if (platform === "win32") {
571
+ const offending = tokens.find((token) => token.includes("%"));
572
+ if (offending !== void 0) throw new CliError(`Cannot safely pass "${offending}" to cmd.exe: "%" triggers environment-variable expansion even inside quotes. Remove it, or run the tool directly.`);
573
+ }
574
+ const quote = platform === "win32" ? quoteWindows : quotePosix;
575
+ return tokens.map(quote).join(" ");
576
+ }
577
+ function quotePosix(token) {
578
+ if (token.length > 0 && SAFE_TOKEN.test(token)) return token;
579
+ return `'${token.replaceAll("'", "'\\''")}'`;
580
+ }
581
+ function quoteWindows(token) {
582
+ if (token.length > 0 && SAFE_TOKEN.test(token)) return token;
583
+ let escaped = "";
584
+ let slashes = 0;
585
+ for (const char of token) {
586
+ if (char === "\\") {
587
+ slashes += 1;
588
+ escaped += char;
589
+ continue;
590
+ }
591
+ escaped += char === "\"" ? `${"\\".repeat(slashes)}\\"` : char;
592
+ slashes = 0;
593
+ }
594
+ return `"${escaped}${"\\".repeat(slashes)}"`;
595
+ }
596
+ //#endregion
597
+ //#region src/lint-cli/lib/exec/execute.ts
598
+ /** Prefix colour per child label for `concurrently`; kept visually distinct. */
599
+ const PREFIX_COLOR = {
600
+ eslint: "blue",
601
+ fast: "blue",
602
+ oxc: "magenta",
603
+ typed: "cyan"
604
+ };
605
+ /**
606
+ * Run the composed children and aggregate their exit codes.
607
+ *
608
+ * Sequential when the caller asks for it — `--fix` must not have two children
609
+ * writing the same files at once, and a lone child gains nothing from the
610
+ * concurrently harness. Otherwise every child runs at once. Either way all of
611
+ * them run to completion: an ordinary lint failure in one no longer kills its
612
+ * siblings, so the user keeps every result. The returned code is non-zero when
613
+ * any child exited non-zero.
614
+ *
615
+ * @param commands - The child commands to run.
616
+ * @param cwd - The working directory.
617
+ * @param sequential - Whether to run the children one at a time.
618
+ * @returns The aggregated exit code.
619
+ */
620
+ async function execute(commands, cwd, sequential) {
621
+ return sequential ? runSequential(commands, cwd) : runConcurrent(commands, cwd);
622
+ }
623
+ /**
624
+ * Run every command concurrently to completion and aggregate their exit codes.
625
+ * Unlike the previous `killOthersOn: ["failure"]` behaviour, an ordinary lint
626
+ * failure in one child no longer kills its siblings — each runs to the end so
627
+ * the user keeps every result. The returned code is non-zero when any child
628
+ * exited non-zero.
629
+ *
630
+ * @param commands - The child commands to run.
631
+ * @param cwd - The working directory.
632
+ * @returns The aggregated exit code.
633
+ */
634
+ async function runConcurrent(commands, cwd) {
635
+ const { result } = concurrently(commands.map((command) => {
636
+ return {
637
+ name: command.label,
638
+ command: buildShellCommand(process$1.execPath, resolveLocalBin(command.bin, cwd), command.args, process$1.platform),
639
+ env: command.env,
640
+ prefixColor: PREFIX_COLOR[command.label]
641
+ };
642
+ }), {
643
+ cwd,
644
+ group: true
645
+ });
646
+ try {
647
+ await result;
648
+ return 0;
649
+ } catch {
650
+ return 1;
651
+ }
652
+ }
653
+ async function spawnChild(command, cwd) {
654
+ const binJsPath = resolveLocalBin(command.bin, cwd);
655
+ return new Promise((resolve) => {
656
+ const child = spawn(process$1.execPath, [binJsPath, ...command.args], {
657
+ cwd,
658
+ env: {
659
+ ...process$1.env,
660
+ ...command.env
661
+ },
662
+ stdio: "inherit"
663
+ });
664
+ child.on("error", () => {
665
+ resolve(1);
666
+ });
667
+ child.on("close", (code) => {
668
+ resolve(code ?? 1);
669
+ });
670
+ });
671
+ }
672
+ async function runSequential(commands, cwd) {
673
+ let exitCode = 0;
674
+ for (const command of commands) {
675
+ const code = await spawnChild(command, cwd);
676
+ if (code !== 0) exitCode = code;
677
+ }
678
+ return exitCode;
679
+ }
680
+ //#endregion
681
+ //#region src/lint-cli/lib/plan/command.ts
682
+ /**
683
+ * Compose the oxlint child command.
684
+ *
685
+ * @param options - The parsed CLI options.
686
+ * @param context - The resolved composition context.
687
+ * @returns The oxlint child command.
688
+ */
689
+ function composeOxlintCommand(options, context) {
690
+ const args = [];
691
+ if (options.agents) args.push("--format", "agent");
692
+ if (context.oxlintTypeAware) args.push("--type-aware");
693
+ if (options.fix) args.push("--fix");
694
+ args.push(...options.oxlintArgs, ...context.paths);
695
+ return {
696
+ args,
697
+ bin: "oxlint",
698
+ env: {},
699
+ label: "oxc"
700
+ };
701
+ }
702
+ /**
703
+ * Compose the ESLint child command.
704
+ *
705
+ * @param options - The parsed CLI options.
706
+ * @param context - The resolved composition context.
707
+ * @returns The ESLint child command.
708
+ */
709
+ function composeEslintCommand(options, context) {
710
+ const args = [];
711
+ if (options.cache) args.push("--cache", "--cache-location", context.cacheLocation);
712
+ args.push("--no-warn-ignored", "--concurrency", String(context.concurrency));
713
+ if (options.cache && context.ci) args.push("--cache-strategy", "content");
714
+ if (options.agents) args.push("--format", context.agentsFormatterPath);
715
+ if (options.fix) args.push("--fix");
716
+ args.push(...options.eslintArgs, ...context.paths);
717
+ return {
718
+ args,
719
+ bin: "eslint",
720
+ env: { ESLINT_TYPE_AWARE: context.typeAwareEnv },
721
+ label: context.eslintLabel
722
+ };
723
+ }
724
+ //#endregion
725
+ //#region src/lint-cli/lib/plan/compose.ts
726
+ /** The stderr notice emitted when a lint target resolves outside the cwd. */
727
+ const OUTSIDE_CWD_NOTICE = "isentinel-lint: a lint target resolves outside the working directory; sizing conservatively and not auto-skipping the type-aware pass.\n";
728
+ /**
729
+ * Turn a {@link RunPlan} into child commands. Pure: no I/O and no mutation, so
730
+ * it is safe to run for `--print`.
731
+ *
732
+ * @param runPlan - The planned run.
733
+ * @param options - The parsed CLI options (paths and per-tool args).
734
+ * @returns The composed command plan.
735
+ */
736
+ function compose(runPlan, options) {
737
+ const commands = [];
738
+ const notices = [];
739
+ if (runPlan.oxlintReason !== void 0) notices.push(runPlan.oxlintReason);
740
+ if (runPlan.targetsOutsideCwd) notices.push(OUTSIDE_CWD_NOTICE);
741
+ if (runPlan.oxlint) commands.push(composeOxlintCommand(options, {
742
+ oxlintTypeAware: runPlan.oxlintTypeAware,
743
+ paths: runPlan.oxlintPaths
744
+ }));
745
+ for (const pass of runPlan.passes) {
746
+ if (!pass.shouldRun) {
747
+ if (pass.skipReason !== void 0) notices.push(pass.skipReason);
748
+ continue;
749
+ }
750
+ commands.push(composeEslintCommand(options, {
751
+ agentsFormatterPath: runPlan.agentsFormatterPath,
752
+ cacheLocation: pass.cacheFile,
753
+ ci: runPlan.ci,
754
+ concurrency: pass.concurrency,
755
+ eslintLabel: pass.descriptor.label,
756
+ paths: options.paths,
757
+ typeAwareEnv: pass.descriptor.typeAwareEnv
758
+ }));
759
+ }
760
+ return {
761
+ commands,
762
+ notice: notices.length > 0 ? notices.join("") : void 0
763
+ };
764
+ }
765
+ //#endregion
766
+ //#region src/lint-cli/lib/state.ts
767
+ /**
768
+ * Directory (relative to the project root) where the CLI keeps its persisted
769
+ * state. Lives under `node_modules/.cache` so it is discarded with the
770
+ * dependency tree and never committed.
771
+ */
772
+ const CACHE_DIRECTORY = path.join("node_modules", ".cache", "isentinel-lint");
773
+ /**
774
+ * The directory every state file lives in.
775
+ *
776
+ * @param cwd - The consumer project root.
777
+ * @returns The absolute path to the runner's cache directory.
778
+ */
779
+ function stateDirectory(cwd) {
780
+ return path.resolve(cwd, CACHE_DIRECTORY);
781
+ }
782
+ /**
783
+ * Resolve one state file inside {@link stateDirectory}.
784
+ *
785
+ * @param cwd - The consumer project root.
786
+ * @param name - The state's base name.
787
+ * @param parts - Further hyphen-joined segments, usually the config-variant key
788
+ * from `resolveCacheKey`. State that is consumed once (a stored hash, a
789
+ * drained builder) must carry that key, or the first variant to run absorbs
790
+ * the change on behalf of all of them.
791
+ * @returns The absolute path to the state file.
792
+ */
793
+ function statePath(cwd, name, ...parts) {
794
+ return path.join(stateDirectory(cwd), [name, ...parts].join("-"));
795
+ }
796
+ /**
797
+ * Read a UTF-8 file, or `undefined` when it cannot be read for any reason.
798
+ * Shared by every tolerant read in the CLI: state files, and the config-import
799
+ * closure walk.
800
+ *
801
+ * @param filePath - The file to read.
802
+ * @returns The file's content, or `undefined`.
803
+ */
804
+ function readFileIfPresent(filePath) {
805
+ try {
806
+ return fs.readFileSync(filePath, "utf8");
807
+ } catch {
808
+ return;
809
+ }
810
+ }
811
+ /**
812
+ * Read a state file written by {@link writeState}. Every failure mode — the
813
+ * file is missing, unreadable, malformed, or was written by another
814
+ * {@link STATE_VERSION} — degrades to `undefined`, which every caller treats as
815
+ * "unknown" and recomputes from.
816
+ *
817
+ * @template T - The shape this file's writer stores. Asserted, not checked:
818
+ * only the schema version is verified, exactly as the per-module casts this
819
+ * replaced did.
820
+ * @param filePath - The state file, from {@link statePath}.
821
+ * @returns The stored payload, or `undefined`.
822
+ */
823
+ function readState(filePath) {
824
+ const raw = readFileIfPresent(filePath);
825
+ return raw === void 0 ? void 0 : parseState(raw);
826
+ }
827
+ /**
828
+ * Persist a state payload, creating the cache directory as needed.
829
+ *
830
+ * The write is atomic (temp file plus rename), since `plan()` runs concurrently
831
+ * across packages in a parallel per-package lint setup and a torn file would be
832
+ * read back as "unknown" at best.
833
+ *
834
+ * Best-effort — a failed write leaves the state unknown, so the next run
835
+ * recomputes rather than trusting something stale.
836
+ *
837
+ * @param filePath - The state file, from {@link statePath}.
838
+ * @param data - The payload to store.
839
+ */
840
+ function writeState(filePath, data) {
841
+ const content = `${JSON.stringify({
842
+ data,
843
+ version: 2
844
+ })}\n`;
845
+ const temporary = `${filePath}.${process$1.pid}.tmp`;
846
+ try {
847
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
848
+ fs.writeFileSync(temporary, content);
849
+ fs.renameSync(temporary, filePath);
850
+ } catch {
851
+ try {
852
+ fs.rmSync(temporary, { force: true });
853
+ } catch {}
854
+ }
855
+ }
856
+ /**
857
+ * Compare a value against the stored one and replace it when they differ, the
858
+ * compare-and-swap every hash-drift bust runs: the caller acts on the outcome
859
+ * and never on the stored value itself.
860
+ *
861
+ * The value is consumed by the swap — once stored, every later run reads back
862
+ * `"unchanged"` — which is why hash state is keyed per config variant.
863
+ *
864
+ * A stored value this {@link STATE_VERSION} cannot read counts as `"changed"`,
865
+ * so a CLI upgrade invalidates rather than silently adopts what it finds.
866
+ *
867
+ * @param filePath - The state file, from {@link statePath}.
868
+ * @param value - The value this run computed.
869
+ * @returns How the stored value compared.
870
+ */
871
+ function swapState(filePath, value) {
872
+ const raw = readFileIfPresent(filePath);
873
+ if ((raw === void 0 ? void 0 : parseState(raw)) === value) return "unchanged";
874
+ writeState(filePath, value);
875
+ return raw === void 0 ? "first" : "changed";
876
+ }
877
+ /**
878
+ * Refresh a state file's mtime without rewriting it, so a reader comparing that
879
+ * mtime against another file's still sees the state as fresh. Best-effort: a
880
+ * missing file simply stays missing.
881
+ *
882
+ * @param filePath - The state file, from {@link statePath}.
883
+ */
884
+ function touchState(filePath) {
885
+ try {
886
+ const now = Date.now() / 1e3;
887
+ fs.utimesSync(filePath, now, now);
888
+ } catch {}
889
+ }
890
+ /**
891
+ * Parse one state file's content, or `undefined` when it is malformed or
892
+ * carries another {@link STATE_VERSION}.
893
+ *
894
+ * @template T - The shape this file's writer stores (asserted, not checked).
895
+ * @param raw - The file's content.
896
+ * @returns The stored payload, or `undefined`.
897
+ */
898
+ function parseState(raw) {
899
+ let parsed;
900
+ try {
901
+ parsed = JSON.parse(raw);
902
+ } catch {
903
+ return;
904
+ }
905
+ if (typeof parsed !== "object" || parsed === null) return;
906
+ const envelope = parsed;
907
+ return envelope.version === 2 ? envelope.data : void 0;
908
+ }
909
+ /**
910
+ * Base name of every ESLint cache file the runner manages. Each real file adds
911
+ * a pass suffix and a config-variant key (see {@link cacheFileFor}), so this
912
+ * doubles as the prefix the whole-cache sweep matches on.
913
+ */
914
+ const CACHE_FILE_PREFIX = ".eslintcache";
915
+ /** ESLint cache base name used when no type-aware mode is selected. */
916
+ const CACHE_FILE_DEFAULT = CACHE_FILE_PREFIX;
917
+ /** ESLint cache base name used for `--type-aware=off` (syntactic-only) runs. */
918
+ const CACHE_FILE_FAST = `${CACHE_FILE_PREFIX}-fast`;
919
+ /** ESLint cache base name used for `--type-aware=only` runs. */
920
+ const CACHE_FILE_TYPE_AWARE = `${CACHE_FILE_PREFIX}-typeaware`;
921
+ /** Every ESLint cache base name the runner manages. */
922
+ const ALL_CACHE_FILES = [
923
+ CACHE_FILE_DEFAULT,
924
+ CACHE_FILE_FAST,
925
+ CACHE_FILE_TYPE_AWARE
926
+ ];
927
+ /**
928
+ * Suffix a cache base name with the run's config-variant key. Two runs whose
929
+ * resolved ESLint configs differ get different keys and therefore different
930
+ * files, so neither can invalidate the other's entries via ESLint's per-entry
931
+ * `hashOfConfig`.
932
+ *
933
+ * @param baseName - The pass's cache base name (see {@link ALL_CACHE_FILES}).
934
+ * @param key - The variant key from `resolveCacheKey`.
935
+ * @returns The keyed cache file name, relative to the working directory.
936
+ */
937
+ function cacheFileFor(baseName, key) {
938
+ return `${baseName}-${key}`;
939
+ }
940
+ /**
941
+ * Resolve the affected-set bust threshold, honouring the
942
+ * `LINT_AFFECTED_BUST_THRESHOLD` override.
943
+ *
944
+ * @param environment - The environment variables to read the override from.
945
+ * @returns The resolved threshold.
946
+ */
947
+ function resolveAffectedBustThreshold(environment) {
948
+ return parseBoundedInteger(environment["LINT_AFFECTED_BUST_THRESHOLD"], 0) ?? 1e3;
949
+ }
950
+ //#endregion
951
+ //#region src/lint-cli/lib/cache/bust.ts
952
+ /**
953
+ * The resolved ESLint config's content changed.
954
+ *
955
+ * Deletes all three caches, the fast (syntactic-only) one included: a config
956
+ * change such as a rule-severity flip alters a syntactic lint too. This is why
957
+ * it is evaluated before {@link PACKAGE_RESOLUTION}, which spares that cache.
958
+ */
959
+ const CONFIG_DRIFT = {
960
+ name: "config-hash",
961
+ caches: ALL_CACHE_FILES
962
+ };
963
+ /**
964
+ * The consumer's `package.json` resolution surface changed.
965
+ *
966
+ * Deletes only the two type-aware caches (`.eslintcache-typeaware-<key>` and
967
+ * `.eslintcache-<key>`), leaving `.eslintcache-fast-<key>` intact: a resolution
968
+ * change alters which types an importer sees, and a syntactic lint does not
969
+ * look at types.
970
+ */
971
+ const PACKAGE_RESOLUTION = {
972
+ name: "package-json-hash",
973
+ caches: [CACHE_FILE_DEFAULT, CACHE_FILE_TYPE_AWARE]
974
+ };
975
+ /**
976
+ * Delete this variant's affected caches when the bust's hash changed since the
977
+ * last run. The first run only stores the hash.
978
+ *
979
+ * The state file is keyed per config variant because the stored hash is
980
+ * consumed once: after the first run that sees a new hash, every later run
981
+ * finds `stored === hash` and returns early. A shared state file would let
982
+ * whichever variant ran first absorb the change on behalf of all of them,
983
+ * leaving every cache it did not delete permanently stale with respect to it.
984
+ * Only this variant's cache files are deleted, for the same reason.
985
+ *
986
+ * Takes the hash rather than computing it, so `undefined` unambiguously means
987
+ * "could not be computed" (no config entry point, no resolvable TypeScript, no
988
+ * readable `package.json`) and the check degrades to a no-op — and so the
989
+ * planner can share one config hash between this bust and the ignore-set memo.
990
+ *
991
+ * @param run - The run context.
992
+ * @param bust - Which hash this is, and what it invalidates.
993
+ * @param hash - The hash this run computed, or `undefined` when unavailable.
994
+ * @returns The bust outcome.
995
+ */
996
+ function applyHashBust(run, bust, hash) {
997
+ if (hash === void 0) return {
998
+ busted: false,
999
+ firstRun: false
1000
+ };
1001
+ const swap = swapState(statePath(run.cwd, bust.name, run.key), hash);
1002
+ if (swap !== "changed") return {
1003
+ busted: false,
1004
+ firstRun: swap === "first"
1005
+ };
1006
+ for (const base of bust.caches) fs.rmSync(path.resolve(run.cwd, cacheFileFor(base, run.key)), { force: true });
1007
+ return {
1008
+ busted: true,
1009
+ firstRun: false
1010
+ };
1011
+ }
1012
+ //#endregion
1013
+ //#region src/lint-cli/lib/typescript/load.ts
1014
+ /**
1015
+ * Resolve the consumer's `typescript` and load it lazily. Anchored at `cwd` so
1016
+ * resolution walks the consumer's `node_modules` (typescript is a peer of
1017
+ * typescript-eslint, never a dependency of this package). Using `createRequire`
1018
+ * rather than a static import keeps typescript out of the bundle and off the
1019
+ * load path unless a caller actually needs it.
1020
+ *
1021
+ * @param cwd - The consumer project root to resolve from.
1022
+ * @returns The TypeScript module, or `undefined` when it cannot be resolved.
1023
+ */
1024
+ function loadTypescript(cwd) {
1025
+ try {
1026
+ const required = createRequire(path.join(cwd, "__isentinel-lint__.js"))("typescript");
1027
+ return isTypeScriptModule(required) ? required : void 0;
1028
+ } catch {
1029
+ return;
1030
+ }
1031
+ }
1032
+ /**
1033
+ * Whether a required module is the TypeScript compiler API this module returns.
1034
+ *
1035
+ * @param value - The required module's exports.
1036
+ * @returns Whether the exports expose the TypeScript compiler API.
1037
+ */
1038
+ function isTypeScriptModule(value) {
1039
+ return isRecord(value) && typeof value["createProgram"] === "function";
1040
+ }
1041
+ //#endregion
1042
+ //#region src/lint-cli/lib/cache/config-hash.ts
1043
+ /**
1044
+ * Upper bound on the number of files walked from the config entry points. A
1045
+ * flat-config import graph is a handful of local modules; the cap only guards
1046
+ * against a pathological graph and never trips in practice.
1047
+ */
1048
+ const MAX_CLOSURE_FILES = 500;
1049
+ /**
1050
+ * Hash the content of `eslint.config.*` and its transitive local import
1051
+ * closure. This models the input ESLint keys its per-entry `hashOfConfig` on:
1052
+ * ESLint re-lints every file when the resolved config changes, but the runner's
1053
+ * dirty count only sees the config file (via `CACHE_BUST_PATTERNS`), not the
1054
+ * modules it imports. Hashing content (not mtimes) means a checkout or a
1055
+ * save-without-change never busts.
1056
+ *
1057
+ * The closure is discovered with the consumer's own TypeScript: a lexer
1058
+ * extracts each specifier and `resolveModuleName` resolves it (honouring
1059
+ * tsconfig `paths`/`baseUrl`, extension-less and `index` lookups, and
1060
+ * re-export forwarding). External (`node_modules`) imports are dropped — a
1061
+ * dependency swap is already covered by the lockfile bust and `package-hash`.
1062
+ * Returns `undefined` when `typescript` is unresolvable or no config entry
1063
+ * point exists, so the caller treats the check as a no-op.
1064
+ *
1065
+ * @param cwd - The consumer project root.
1066
+ * @param configFiles - The flat-config entry points (see `RepoFiles.configFiles`).
1067
+ * @returns The hex digest, or `undefined` when unavailable.
1068
+ */
1069
+ function computeConfigHash(cwd, configFiles) {
1070
+ if (configFiles.length === 0) return;
1071
+ const ts = loadTypescript(cwd);
1072
+ if (ts === void 0) return;
1073
+ const closure = discoverConfigClosure(ts, cwd, configFiles);
1074
+ if (closure.length === 0) return;
1075
+ const hash = crypto.createHash("sha256");
1076
+ for (const { content, file } of closure) {
1077
+ hash.update(file);
1078
+ hash.update("\0");
1079
+ hash.update(content);
1080
+ hash.update("\0");
1081
+ }
1082
+ return hash.digest("hex");
1083
+ }
1084
+ /**
1085
+ * Add every not-yet-seen target to the visited set and the work queue.
1086
+ *
1087
+ * @param targets - The candidate import targets.
1088
+ * @param visited - The set of already-seen paths (mutated).
1089
+ * @param queue - The BFS work queue (mutated).
1090
+ */
1091
+ function enqueueUnvisited(targets, visited, queue) {
1092
+ for (const target of targets) {
1093
+ if (visited.has(target)) continue;
1094
+ visited.add(target);
1095
+ queue.push(target);
1096
+ }
1097
+ }
1098
+ /**
1099
+ * Resolve every in-project import found in one file's already-read content.
1100
+ * `node_modules` results and unresolvable specifiers are dropped, so a
1101
+ * dependency swap (covered by the lockfile bust) never enters the closure.
1102
+ *
1103
+ * @param resolver - The shared TypeScript resolution state.
1104
+ * @param file - The absolute path of the importing file.
1105
+ * @param content - The importing file's content.
1106
+ * @returns The absolute, normalized in-project import targets.
1107
+ */
1108
+ function importsOf({ cache, options, ts }, file, content) {
1109
+ const targets = [];
1110
+ const nodeModules = `${path.sep}node_modules${path.sep}`;
1111
+ for (const reference of ts.preProcessFile(content, true, true).importedFiles) {
1112
+ const resolved = ts.resolveModuleName(reference.fileName, file, options, ts.sys, cache).resolvedModule;
1113
+ if (resolved === void 0 || resolved.isExternalLibraryImport === true) continue;
1114
+ const target = path.normalize(resolved.resolvedFileName);
1115
+ if (!target.includes(nodeModules)) targets.push(target);
1116
+ }
1117
+ return targets;
1118
+ }
1119
+ /**
1120
+ * Resolve the consumer's compiler options from the nearest `tsconfig.json` so
1121
+ * `resolveModuleName` honours `paths`/`baseUrl`. Falls back to empty options
1122
+ * (relative and `node_modules` resolution still work) when none is found or it
1123
+ * fails to parse.
1124
+ *
1125
+ * @param ts - The consumer's resolved TypeScript module.
1126
+ * @param cwd - The consumer project root.
1127
+ * @returns The parsed compiler options.
1128
+ */
1129
+ function resolveCompilerOptions(ts, cwd) {
1130
+ const configPath = ts.findConfigFile(cwd, (file) => ts.sys.fileExists(file), "tsconfig.json");
1131
+ if (configPath === void 0) return {};
1132
+ const read = ts.readConfigFile(configPath, (file) => ts.sys.readFile(file));
1133
+ if (read.error !== void 0 || read.config === void 0) return {};
1134
+ const host = {
1135
+ fileExists: (file) => ts.sys.fileExists(file),
1136
+ readDirectory: () => [],
1137
+ readFile: (file) => ts.sys.readFile(file),
1138
+ useCaseSensitiveFileNames: ts.sys.useCaseSensitiveFileNames
1139
+ };
1140
+ return ts.parseJsonConfigFileContent(read.config, host, path.dirname(configPath)).options;
1141
+ }
1142
+ /**
1143
+ * Walk the local import closure of the config entry points. BFS with a
1144
+ * normalized-path visited set; the roots are included so their own content
1145
+ * contributes to the hash. Each file is read exactly once — the content feeds
1146
+ * both import extraction and the hash.
1147
+ *
1148
+ * @param ts - The consumer's resolved TypeScript module.
1149
+ * @param cwd - The consumer project root.
1150
+ * @param roots - The config entry-point paths to start from.
1151
+ * @returns The readable closure files (roots included), each with its content.
1152
+ */
1153
+ function discoverConfigClosure(ts, cwd, roots) {
1154
+ const options = resolveCompilerOptions(ts, cwd);
1155
+ const resolver = {
1156
+ cache: ts.createModuleResolutionCache(cwd, (fileName) => fileName, options),
1157
+ options,
1158
+ ts
1159
+ };
1160
+ const visited = /* @__PURE__ */ new Set();
1161
+ const queue = [];
1162
+ enqueueUnvisited(roots.map((root) => path.normalize(root)), visited, queue);
1163
+ const files = [];
1164
+ while (queue.length > 0 && visited.size <= MAX_CLOSURE_FILES) {
1165
+ const file = queue.shift();
1166
+ if (file === void 0) break;
1167
+ const content = readFileIfPresent(file);
1168
+ if (content === void 0) continue;
1169
+ files.push({
1170
+ content,
1171
+ file
1172
+ });
1173
+ enqueueUnvisited(importsOf(resolver, file, content), visited, queue);
1174
+ }
1175
+ return files;
1176
+ }
1177
+ //#endregion
1178
+ //#region src/lint-cli/lib/paths.ts
1179
+ /**
1180
+ * Rewrite a path to forward slashes. The shared separator-splitting primitive
1181
+ * that both the cache-key `normalizePath` and the file walk build on, so there
1182
+ * is a single place that translates OS-native separators to POSIX.
1183
+ *
1184
+ * @param value - The path to rewrite.
1185
+ * @returns The path with every separator as a forward slash.
1186
+ */
1187
+ function toPosix(value) {
1188
+ return value.split(path.sep).join("/");
1189
+ }
1190
+ //#endregion
1191
+ //#region src/lint-cli/lib/cache/entries.ts
1192
+ /**
1193
+ * Compute the newest modification time across the given files. Callers hash the
1194
+ * cache-bust set once per run and compare each pass's cache mtime against the
1195
+ * result, rather than re-reading every bust file's mtime per pass.
1196
+ *
1197
+ * @param files - Absolute paths to stat.
1198
+ * @returns The newest mtime in milliseconds, or `undefined` when none exist.
1199
+ */
1200
+ function maxMtimeMs(files) {
1201
+ let newest;
1202
+ for (const file of files) {
1203
+ const mtime = safeMtimeMs(file);
1204
+ if (mtime !== void 0 && (newest === void 0 || mtime > newest)) newest = mtime;
1205
+ }
1206
+ return newest;
1207
+ }
1208
+ /**
1209
+ * Whether the cache file is older than the newest cache-bust modification. A
1210
+ * missing cache file (or no bust files) returns false: the caller already
1211
+ * treats an absent cache as "everything is dirty".
1212
+ *
1213
+ * @param cacheFilePath - The ESLint cache file to compare against.
1214
+ * @param newestBustMtimeMs - The newest bust-file mtime (see {@link maxMtimeMs}).
1215
+ * @returns Whether the cache is stale.
1216
+ */
1217
+ function isCacheStale(cacheFilePath, newestBustMtimeMs) {
1218
+ if (newestBustMtimeMs === void 0) return false;
1219
+ const cacheMtime = safeMtimeMs(cacheFilePath);
1220
+ if (cacheMtime === void 0) return false;
1221
+ return newestBustMtimeMs > cacheMtime;
1222
+ }
1223
+ /**
1224
+ * Delete the individually stale cache files in the working directory.
1225
+ *
1226
+ * Deliberately per-file rather than all-or-nothing: variants this run did not
1227
+ * select still sit on disk, and {@link isCacheStale} reports a missing file as
1228
+ * fresh. An all-or-nothing gate over only the selected passes therefore lets an
1229
+ * unselected-but-stale variant survive a config edit, then wipes every fresh
1230
+ * variant the next time that stale one is selected — the same mutual
1231
+ * invalidation the variant split exists to remove, relocated to the config-edit
1232
+ * path.
1233
+ *
1234
+ * @param cwd - The working directory containing the cache files.
1235
+ * @param newestBustMtimeMs - The newest bust-file mtime (see {@link maxMtimeMs}).
1236
+ * @returns The absolute paths deleted.
1237
+ */
1238
+ function sweepStaleCaches(cwd, newestBustMtimeMs) {
1239
+ const removed = [];
1240
+ for (const cacheFilePath of listCacheFiles(cwd)) {
1241
+ if (!isCacheStale(cacheFilePath, newestBustMtimeMs)) continue;
1242
+ removeCacheFile(cacheFilePath);
1243
+ removed.push(cacheFilePath);
1244
+ }
1245
+ return removed;
1246
+ }
1247
+ /**
1248
+ * Normalize a path for cache-key comparison: absolute, forward-slash and
1249
+ * lower-cased. TypeScript emits forward-slash paths while ESLint keys the cache
1250
+ * with OS-native ones, and Windows paths are case-insensitive — this collapses
1251
+ * all of those into a single comparable form.
1252
+ *
1253
+ * @param filePath - The path to normalize.
1254
+ * @returns The canonical key.
1255
+ */
1256
+ function normalizePath(filePath) {
1257
+ return toPosix(path.resolve(filePath)).toLowerCase();
1258
+ }
1259
+ /**
1260
+ * Open an ESLint cache for reuse, or `undefined` when the file is missing (the
1261
+ * caller then treats every target file as dirty). The returned handle backs
1262
+ * both {@link DirtyCache.getUpdatedFiles} and {@link DirtyCache.removeEntries}
1263
+ * so a pass parses the cache once instead of twice.
1264
+ *
1265
+ * @param cacheFilePath - The ESLint cache file to open.
1266
+ * @param useChecksum - Compare by content checksum instead of metadata.
1267
+ * @returns The loaded cache, or `undefined` when the file does not exist.
1268
+ */
1269
+ function openCache(cacheFilePath, useChecksum) {
1270
+ if (!fs.existsSync(cacheFilePath)) return;
1271
+ const cache = fileEntryCache.createFromFile(cacheFilePath, useChecksum);
1272
+ return {
1273
+ getUpdatedFiles: (files) => cache.getUpdatedFiles(files),
1274
+ removeEntries: (files) => removeEntriesFrom(cache, files)
1275
+ };
1276
+ }
1277
+ function safeMtimeMs(filePath) {
1278
+ try {
1279
+ return fs.statSync(filePath).mtimeMs;
1280
+ } catch {
1281
+ return;
1282
+ }
1283
+ }
1284
+ /**
1285
+ * List every ESLint cache file present in the working directory, matched by
1286
+ * prefix rather than by exact name: each pass's cache carries a config-variant
1287
+ * key suffix, so the set on disk is open-ended and an exact-name list would
1288
+ * miss (and therefore leak) every variant but the current run's.
1289
+ *
1290
+ * @param cwd - The working directory containing the cache files.
1291
+ * @returns Absolute paths to the cache files found.
1292
+ */
1293
+ function listCacheFiles(cwd) {
1294
+ let entries;
1295
+ try {
1296
+ entries = fs.readdirSync(cwd);
1297
+ } catch {
1298
+ return [];
1299
+ }
1300
+ return entries.filter((entry) => entry.startsWith(CACHE_FILE_PREFIX)).map((entry) => path.resolve(cwd, entry));
1301
+ }
1302
+ function removeCacheFile(cacheFilePath) {
1303
+ try {
1304
+ fs.rmSync(cacheFilePath, { force: true });
1305
+ } catch {}
1306
+ }
1307
+ function removeEntriesFrom(cache, files) {
1308
+ const keyByNormalized = /* @__PURE__ */ new Map();
1309
+ for (const key of cache.cache.keys()) keyByNormalized.set(normalizePath(key), key);
1310
+ let removed = 0;
1311
+ for (const file of files) {
1312
+ const key = keyByNormalized.get(normalizePath(file));
1313
+ if (key !== void 0) {
1314
+ cache.removeEntry(key);
1315
+ removed += 1;
1316
+ }
1317
+ }
1318
+ if (removed > 0) cache.cache.save(true);
1319
+ return removed;
1320
+ }
1321
+ //#endregion
1322
+ //#region src/lint-cli/lib/files/workspace.ts
1323
+ /**
1324
+ * Markers that identify a repository or pnpm-workspace root. `.git` may be a
1325
+ * directory (normal clone) or a file (a worktree pointer); `existsSync` accepts
1326
+ * both.
1327
+ */
1328
+ const WORKSPACE_ROOT_MARKERS = [".git", "pnpm-workspace.yaml"];
1329
+ /**
1330
+ * Walk up from `cwd` to the nearest directory that looks like a repository or
1331
+ * pnpm-workspace root (contains `.git` or `pnpm-workspace.yaml`). Returns `cwd`
1332
+ * unchanged when it is itself the root, or when no marker is found before the
1333
+ * filesystem root — in both cases there is no ancestor to fold in.
1334
+ *
1335
+ * @param cwd - The directory to walk up from.
1336
+ * @returns The workspace root, or `cwd` when there is no distinct ancestor root.
1337
+ */
1338
+ function findWorkspaceRoot(cwd) {
1339
+ let current = cwd;
1340
+ for (;;) {
1341
+ for (const marker of WORKSPACE_ROOT_MARKERS) if (fs.existsSync(path.join(current, marker))) return current;
1342
+ const parent = path.dirname(current);
1343
+ if (parent === current) return cwd;
1344
+ current = parent;
1345
+ }
1346
+ }
1347
+ //#endregion
1348
+ //#region src/lint-cli/lib/cache/package-hash.ts
1349
+ /**
1350
+ * Root `package.json` fields whose edits can change the types a consumer's
1351
+ * importers see (resolution surface + dependency versions). A change to any of
1352
+ * these must invalidate the type-aware caches; unrelated edits (`scripts`,
1353
+ * `version`, metadata) must not. `pnpm` (overrides/patchedDependencies) and
1354
+ * `optionalDependencies` can silently swap a resolved version too.
1355
+ */
1356
+ const RESOLUTION_FIELDS = [
1357
+ "exports",
1358
+ "imports",
1359
+ "main",
1360
+ "module",
1361
+ "types",
1362
+ "typesVersions",
1363
+ "dependencies",
1364
+ "devDependencies",
1365
+ "peerDependencies",
1366
+ "optionalDependencies",
1367
+ "pnpm"
1368
+ ];
1369
+ /**
1370
+ * Hash the resolution-relevant fields of the consumer's `package.json` as
1371
+ * sorted, stable JSON. When `cwd` sits in a workspace whose root differs, the
1372
+ * root `package.json`'s resolution fields fold into the same digest — a hoisted
1373
+ * root dependency bump changes the types a sub-package sees even though its own
1374
+ * `package.json` text is untouched. Returns `undefined` when there is no
1375
+ * readable/parseable local `package.json` (the caller then treats the check as
1376
+ * a no-op).
1377
+ *
1378
+ * @param cwd - The consumer project root.
1379
+ * @returns The hex digest, or `undefined` when unavailable.
1380
+ */
1381
+ function computePackageJsonHash(cwd) {
1382
+ const local = resolutionSubset(cwd);
1383
+ if (local === void 0) return;
1384
+ const combined = { local };
1385
+ const root = findWorkspaceRoot(cwd);
1386
+ if (root !== cwd) {
1387
+ const rootSubset = resolutionSubset(root);
1388
+ if (rootSubset !== void 0) combined["root"] = rootSubset;
1389
+ }
1390
+ return crypto.createHash("sha256").update(stableStringify(combined)).digest("hex");
1391
+ }
1392
+ /**
1393
+ * Read a directory's `package.json` and project it down to the resolution
1394
+ * fields, or `undefined` when it is absent or unparseable.
1395
+ *
1396
+ * @param directory - The directory whose `package.json` to read.
1397
+ * @returns The resolution-field subset, or `undefined`.
1398
+ */
1399
+ function resolutionSubset(directory) {
1400
+ const raw = readFileIfPresent(path.join(directory, "package.json"));
1401
+ if (raw === void 0) return;
1402
+ let parsed;
1403
+ try {
1404
+ parsed = JSON.parse(raw);
1405
+ } catch {
1406
+ return;
1407
+ }
1408
+ if (!isRecord(parsed)) return;
1409
+ const subset = {};
1410
+ for (const field of RESOLUTION_FIELDS) if (Object.hasOwn(parsed, field)) subset[field] = parsed[field];
1411
+ return subset;
1412
+ }
1413
+ /**
1414
+ * Serialize a value to JSON with object keys sorted at every depth so the
1415
+ * digest is insensitive to key ordering.
1416
+ *
1417
+ * @param value - The value to stringify.
1418
+ * @returns The stable JSON string.
1419
+ */
1420
+ function stableStringify(value) {
1421
+ if (Array.isArray(value)) return `[${value.map((item) => stableStringify(item)).join(",")}]`;
1422
+ if (isRecord(value)) return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${stableStringify(value[key])}`).join(",")}}`;
1423
+ return JSON.stringify(value);
1424
+ }
1425
+ //#endregion
1426
+ //#region node_modules/.pnpm/picomatch@4.0.5/node_modules/picomatch/lib/constants.js
1427
+ var require_constants = /* @__PURE__ */ __commonJSMin(((exports, module) => {
1428
+ const WIN_SLASH = "\\\\/";
1429
+ const WIN_NO_SLASH = `[^${WIN_SLASH}]`;
1430
+ const DEFAULT_MAX_EXTGLOB_RECURSION = 0;
1431
+ /**
1432
+ * Posix glob regex
1433
+ */
1434
+ const DOT_LITERAL = "\\.";
1435
+ const PLUS_LITERAL = "\\+";
1436
+ const QMARK_LITERAL = "\\?";
1437
+ const SLASH_LITERAL = "\\/";
1438
+ const ONE_CHAR = "(?=.)";
1439
+ const QMARK = "[^/]";
1440
+ const END_ANCHOR = `(?:${SLASH_LITERAL}|$)`;
1441
+ const START_ANCHOR = `(?:^|${SLASH_LITERAL})`;
1442
+ const DOTS_SLASH = `${DOT_LITERAL}{1,2}${END_ANCHOR}`;
1443
+ const POSIX_CHARS = {
1444
+ DOT_LITERAL,
1445
+ PLUS_LITERAL,
1446
+ QMARK_LITERAL,
1447
+ SLASH_LITERAL,
1448
+ ONE_CHAR,
1449
+ QMARK,
1450
+ END_ANCHOR,
1451
+ DOTS_SLASH,
1452
+ NO_DOT: `(?!${DOT_LITERAL})`,
1453
+ NO_DOTS: `(?!${START_ANCHOR}${DOTS_SLASH})`,
1454
+ NO_DOT_SLASH: `(?!${DOT_LITERAL}{0,1}${END_ANCHOR})`,
1455
+ NO_DOTS_SLASH: `(?!${DOTS_SLASH})`,
1456
+ QMARK_NO_DOT: `[^.${SLASH_LITERAL}]`,
1457
+ STAR: `${QMARK}*?`,
1458
+ START_ANCHOR,
1459
+ SEP: "/"
1460
+ };
1461
+ /**
1462
+ * Windows glob regex
1463
+ */
1464
+ const WINDOWS_CHARS = {
1465
+ ...POSIX_CHARS,
1466
+ SLASH_LITERAL: `[${WIN_SLASH}]`,
1467
+ QMARK: WIN_NO_SLASH,
1468
+ STAR: `${WIN_NO_SLASH}*?`,
1469
+ DOTS_SLASH: `${DOT_LITERAL}{1,2}(?:[${WIN_SLASH}]|$)`,
1470
+ NO_DOT: `(?!${DOT_LITERAL})`,
1471
+ NO_DOTS: `(?!(?:^|[${WIN_SLASH}])${DOT_LITERAL}{1,2}(?:[${WIN_SLASH}]|$))`,
1472
+ NO_DOT_SLASH: `(?!${DOT_LITERAL}{0,1}(?:[${WIN_SLASH}]|$))`,
1473
+ NO_DOTS_SLASH: `(?!${DOT_LITERAL}{1,2}(?:[${WIN_SLASH}]|$))`,
1474
+ QMARK_NO_DOT: `[^.${WIN_SLASH}]`,
1475
+ START_ANCHOR: `(?:^|[${WIN_SLASH}])`,
1476
+ END_ANCHOR: `(?:[${WIN_SLASH}]|$)`,
1477
+ SEP: "\\"
1478
+ };
1479
+ module.exports = {
1480
+ DEFAULT_MAX_EXTGLOB_RECURSION,
1481
+ MAX_LENGTH: 1024 * 64,
1482
+ POSIX_REGEX_SOURCE: {
1483
+ __proto__: null,
1484
+ alnum: "a-zA-Z0-9",
1485
+ alpha: "a-zA-Z",
1486
+ ascii: "\\x00-\\x7F",
1487
+ blank: " \\t",
1488
+ cntrl: "\\x00-\\x1F\\x7F",
1489
+ digit: "0-9",
1490
+ graph: "\\x21-\\x7E",
1491
+ lower: "a-z",
1492
+ print: "\\x20-\\x7E ",
1493
+ punct: "\\-!\"#$%&'()\\*+,./:;<=>?@[\\]^_`{|}~",
1494
+ space: " \\t\\r\\n\\v\\f",
1495
+ upper: "A-Z",
1496
+ word: "A-Za-z0-9_",
1497
+ xdigit: "A-Fa-f0-9"
1498
+ },
1499
+ REGEX_BACKSLASH: /\\(?![*+?^${}(|)[\]])/g,
1500
+ REGEX_NON_SPECIAL_CHARS: /^[^@![\].,$*+?^{}()|\\/]+/,
1501
+ REGEX_SPECIAL_CHARS: /[-*+?.^${}(|)[\]]/,
1502
+ REGEX_SPECIAL_CHARS_BACKREF: /(\\?)((\W)(\3*))/g,
1503
+ REGEX_SPECIAL_CHARS_GLOBAL: /([-*+?.^${}(|)[\]])/g,
1504
+ REGEX_REMOVE_BACKSLASH: /(?:\[.*?[^\\]\]|\\(?=.))/g,
1505
+ REPLACEMENTS: {
1506
+ __proto__: null,
1507
+ "***": "*",
1508
+ "**/**": "**",
1509
+ "**/**/**": "**"
1510
+ },
1511
+ CHAR_0: 48,
1512
+ CHAR_9: 57,
1513
+ CHAR_UPPERCASE_A: 65,
1514
+ CHAR_LOWERCASE_A: 97,
1515
+ CHAR_UPPERCASE_Z: 90,
1516
+ CHAR_LOWERCASE_Z: 122,
1517
+ CHAR_LEFT_PARENTHESES: 40,
1518
+ CHAR_RIGHT_PARENTHESES: 41,
1519
+ CHAR_ASTERISK: 42,
1520
+ CHAR_AMPERSAND: 38,
1521
+ CHAR_AT: 64,
1522
+ CHAR_BACKWARD_SLASH: 92,
1523
+ CHAR_CARRIAGE_RETURN: 13,
1524
+ CHAR_CIRCUMFLEX_ACCENT: 94,
1525
+ CHAR_COLON: 58,
1526
+ CHAR_COMMA: 44,
1527
+ CHAR_DOT: 46,
1528
+ CHAR_DOUBLE_QUOTE: 34,
1529
+ CHAR_EQUAL: 61,
1530
+ CHAR_EXCLAMATION_MARK: 33,
1531
+ CHAR_FORM_FEED: 12,
1532
+ CHAR_FORWARD_SLASH: 47,
1533
+ CHAR_GRAVE_ACCENT: 96,
1534
+ CHAR_HASH: 35,
1535
+ CHAR_HYPHEN_MINUS: 45,
1536
+ CHAR_LEFT_ANGLE_BRACKET: 60,
1537
+ CHAR_LEFT_CURLY_BRACE: 123,
1538
+ CHAR_LEFT_SQUARE_BRACKET: 91,
1539
+ CHAR_LINE_FEED: 10,
1540
+ CHAR_NO_BREAK_SPACE: 160,
1541
+ CHAR_PERCENT: 37,
1542
+ CHAR_PLUS: 43,
1543
+ CHAR_QUESTION_MARK: 63,
1544
+ CHAR_RIGHT_ANGLE_BRACKET: 62,
1545
+ CHAR_RIGHT_CURLY_BRACE: 125,
1546
+ CHAR_RIGHT_SQUARE_BRACKET: 93,
1547
+ CHAR_SEMICOLON: 59,
1548
+ CHAR_SINGLE_QUOTE: 39,
1549
+ CHAR_SPACE: 32,
1550
+ CHAR_TAB: 9,
1551
+ CHAR_UNDERSCORE: 95,
1552
+ CHAR_VERTICAL_LINE: 124,
1553
+ CHAR_ZERO_WIDTH_NOBREAK_SPACE: 65279,
1554
+ /**
1555
+ * Create EXTGLOB_CHARS
1556
+ */
1557
+ extglobChars(chars) {
1558
+ return {
1559
+ "!": {
1560
+ type: "negate",
1561
+ open: "(?:(?!(?:",
1562
+ close: `))${chars.STAR})`
1563
+ },
1564
+ "?": {
1565
+ type: "qmark",
1566
+ open: "(?:",
1567
+ close: ")?"
1568
+ },
1569
+ "+": {
1570
+ type: "plus",
1571
+ open: "(?:",
1572
+ close: ")+"
1573
+ },
1574
+ "*": {
1575
+ type: "star",
1576
+ open: "(?:",
1577
+ close: ")*"
1578
+ },
1579
+ "@": {
1580
+ type: "at",
1581
+ open: "(?:",
1582
+ close: ")"
1583
+ }
1584
+ };
1585
+ },
1586
+ /**
1587
+ * Create GLOB_CHARS
1588
+ */
1589
+ globChars(win32) {
1590
+ return win32 === true ? WINDOWS_CHARS : POSIX_CHARS;
1591
+ }
1592
+ };
1593
+ }));
1594
+ //#endregion
1595
+ //#region node_modules/.pnpm/picomatch@4.0.5/node_modules/picomatch/lib/utils.js
1596
+ var require_utils = /* @__PURE__ */ __commonJSMin(((exports) => {
1597
+ const { REGEX_BACKSLASH, REGEX_REMOVE_BACKSLASH, REGEX_SPECIAL_CHARS, REGEX_SPECIAL_CHARS_GLOBAL } = require_constants();
1598
+ exports.isObject = (val) => val !== null && typeof val === "object" && !Array.isArray(val);
1599
+ exports.hasRegexChars = (str) => REGEX_SPECIAL_CHARS.test(str);
1600
+ exports.isRegexChar = (str) => str.length === 1 && exports.hasRegexChars(str);
1601
+ exports.escapeRegex = (str) => str.replace(REGEX_SPECIAL_CHARS_GLOBAL, "\\$1");
1602
+ exports.toPosixSlashes = (str) => str.replace(REGEX_BACKSLASH, "/");
1603
+ exports.isWindows = () => {
1604
+ if (typeof navigator !== "undefined" && navigator.platform) {
1605
+ const platform = navigator.platform.toLowerCase();
1606
+ return platform === "win32" || platform === "windows";
1607
+ }
1608
+ if (typeof process !== "undefined" && process.platform) return process.platform === "win32";
1609
+ return false;
1610
+ };
1611
+ exports.removeBackslashes = (str) => {
1612
+ return str.replace(REGEX_REMOVE_BACKSLASH, (match) => {
1613
+ return match === "\\" ? "" : match;
1614
+ });
1615
+ };
1616
+ exports.escapeLast = (input, char, lastIdx) => {
1617
+ const idx = input.lastIndexOf(char, lastIdx);
1618
+ if (idx === -1) return input;
1619
+ if (input[idx - 1] === "\\") return exports.escapeLast(input, char, idx - 1);
1620
+ return `${input.slice(0, idx)}\\${input.slice(idx)}`;
1621
+ };
1622
+ exports.removePrefix = (input, state = {}) => {
1623
+ let output = input;
1624
+ if (output.startsWith("./")) {
1625
+ output = output.slice(2);
1626
+ state.prefix = "./";
1627
+ }
1628
+ return output;
1629
+ };
1630
+ exports.wrapOutput = (input, state = {}, options = {}) => {
1631
+ let output = `${options.contains ? "" : "^"}(?:${input})${options.contains ? "" : "$"}`;
1632
+ if (state.negated === true) output = `(?:^(?!${output}).*$)`;
1633
+ return output;
1634
+ };
1635
+ exports.basename = (path, { windows } = {}) => {
1636
+ const segs = path.split(windows ? /[\\/]/ : "/");
1637
+ const last = segs[segs.length - 1];
1638
+ if (last === "") return segs[segs.length - 2];
1639
+ return last;
1640
+ };
1641
+ }));
1642
+ //#endregion
1643
+ //#region node_modules/.pnpm/picomatch@4.0.5/node_modules/picomatch/lib/scan.js
1644
+ var require_scan = /* @__PURE__ */ __commonJSMin(((exports, module) => {
1645
+ const utils = require_utils();
1646
+ const { CHAR_ASTERISK, CHAR_AT, CHAR_BACKWARD_SLASH, CHAR_COMMA, CHAR_DOT, CHAR_EXCLAMATION_MARK, CHAR_FORWARD_SLASH, CHAR_LEFT_CURLY_BRACE, CHAR_LEFT_PARENTHESES, CHAR_LEFT_SQUARE_BRACKET, CHAR_PLUS, CHAR_QUESTION_MARK, CHAR_RIGHT_CURLY_BRACE, CHAR_RIGHT_PARENTHESES, CHAR_RIGHT_SQUARE_BRACKET } = require_constants();
1647
+ const isPathSeparator = (code) => {
1648
+ return code === CHAR_FORWARD_SLASH || code === CHAR_BACKWARD_SLASH;
1649
+ };
1650
+ const depth = (token) => {
1651
+ if (token.isPrefix !== true) token.depth = token.isGlobstar ? Infinity : 1;
1652
+ };
1653
+ /**
1654
+ * Quickly scans a glob pattern and returns an object with a handful of
1655
+ * useful properties, like `isGlob`, `path` (the leading non-glob, if it exists),
1656
+ * `glob` (the actual pattern), `negated` (true if the path starts with `!` but not
1657
+ * with `!(`) and `negatedExtglob` (true if the path starts with `!(`).
1658
+ *
1659
+ * ```js
1660
+ * const pm = require('picomatch');
1661
+ * console.log(pm.scan('foo/bar/*.js'));
1662
+ * { isGlob: true, input: 'foo/bar/*.js', base: 'foo/bar', glob: '*.js' }
1663
+ * ```
1664
+ * @param {String} `str`
1665
+ * @param {Object} `options`
1666
+ * @return {Object} Returns an object with tokens and regex source string.
1667
+ * @api public
1668
+ */
1669
+ const scan = (input, options) => {
1670
+ const opts = options || {};
1671
+ const length = input.length - 1;
1672
+ const scanToEnd = opts.parts === true || opts.scanToEnd === true;
1673
+ const slashes = [];
1674
+ const tokens = [];
1675
+ const parts = [];
1676
+ let str = input;
1677
+ let index = -1;
1678
+ let start = 0;
1679
+ let lastIndex = 0;
1680
+ let isBrace = false;
1681
+ let isBracket = false;
1682
+ let isGlob = false;
1683
+ let isExtglob = false;
1684
+ let isGlobstar = false;
1685
+ let braceEscaped = false;
1686
+ let backslashes = false;
1687
+ let negated = false;
1688
+ let negatedExtglob = false;
1689
+ let finished = false;
1690
+ let braces = 0;
1691
+ let prev;
1692
+ let code;
1693
+ let token = {
1694
+ value: "",
1695
+ depth: 0,
1696
+ isGlob: false
1697
+ };
1698
+ const eos = () => index >= length;
1699
+ const peek = () => str.charCodeAt(index + 1);
1700
+ const advance = () => {
1701
+ prev = code;
1702
+ return str.charCodeAt(++index);
1703
+ };
1704
+ while (index < length) {
1705
+ code = advance();
1706
+ let next;
1707
+ if (code === CHAR_BACKWARD_SLASH) {
1708
+ backslashes = token.backslashes = true;
1709
+ code = advance();
1710
+ if (code === CHAR_LEFT_CURLY_BRACE) braceEscaped = true;
1711
+ continue;
1712
+ }
1713
+ if (braceEscaped === true || code === CHAR_LEFT_CURLY_BRACE) {
1714
+ braces++;
1715
+ while (eos() !== true && (code = advance())) {
1716
+ if (code === CHAR_BACKWARD_SLASH) {
1717
+ backslashes = token.backslashes = true;
1718
+ advance();
1719
+ continue;
1720
+ }
1721
+ if (code === CHAR_LEFT_CURLY_BRACE) {
1722
+ braces++;
1723
+ continue;
1724
+ }
1725
+ if (braceEscaped !== true && code === CHAR_DOT && (code = advance()) === CHAR_DOT) {
1726
+ isBrace = token.isBrace = true;
1727
+ isGlob = token.isGlob = true;
1728
+ finished = true;
1729
+ if (scanToEnd === true) continue;
1730
+ break;
1731
+ }
1732
+ if (braceEscaped !== true && code === CHAR_COMMA) {
1733
+ isBrace = token.isBrace = true;
1734
+ isGlob = token.isGlob = true;
1735
+ finished = true;
1736
+ if (scanToEnd === true) continue;
1737
+ break;
1738
+ }
1739
+ if (code === CHAR_RIGHT_CURLY_BRACE) {
1740
+ braces--;
1741
+ if (braces === 0) {
1742
+ braceEscaped = false;
1743
+ isBrace = token.isBrace = true;
1744
+ finished = true;
1745
+ break;
1746
+ }
1747
+ }
1748
+ }
1749
+ if (scanToEnd === true) continue;
1750
+ break;
1751
+ }
1752
+ if (code === CHAR_FORWARD_SLASH) {
1753
+ slashes.push(index);
1754
+ tokens.push(token);
1755
+ token = {
1756
+ value: "",
1757
+ depth: 0,
1758
+ isGlob: false
1759
+ };
1760
+ if (finished === true) continue;
1761
+ if (prev === CHAR_DOT && index === start + 1) {
1762
+ start += 2;
1763
+ continue;
1764
+ }
1765
+ lastIndex = index + 1;
1766
+ continue;
1767
+ }
1768
+ if (opts.noext !== true) {
1769
+ if ((code === CHAR_PLUS || code === CHAR_AT || code === CHAR_ASTERISK || code === CHAR_QUESTION_MARK || code === CHAR_EXCLAMATION_MARK) === true && peek() === CHAR_LEFT_PARENTHESES) {
1770
+ isGlob = token.isGlob = true;
1771
+ isExtglob = token.isExtglob = true;
1772
+ finished = true;
1773
+ if (code === CHAR_EXCLAMATION_MARK && index === start) negatedExtglob = true;
1774
+ if (scanToEnd === true) {
1775
+ while (eos() !== true && (code = advance())) {
1776
+ if (code === CHAR_BACKWARD_SLASH) {
1777
+ backslashes = token.backslashes = true;
1778
+ code = advance();
1779
+ continue;
1780
+ }
1781
+ if (code === CHAR_RIGHT_PARENTHESES) {
1782
+ isGlob = token.isGlob = true;
1783
+ finished = true;
1784
+ break;
1785
+ }
1786
+ }
1787
+ continue;
1788
+ }
1789
+ break;
1790
+ }
1791
+ }
1792
+ if (code === CHAR_ASTERISK) {
1793
+ if (prev === CHAR_ASTERISK) isGlobstar = token.isGlobstar = true;
1794
+ isGlob = token.isGlob = true;
1795
+ finished = true;
1796
+ if (scanToEnd === true) continue;
1797
+ break;
1798
+ }
1799
+ if (code === CHAR_QUESTION_MARK) {
1800
+ isGlob = token.isGlob = true;
1801
+ finished = true;
1802
+ if (scanToEnd === true) continue;
1803
+ break;
1804
+ }
1805
+ if (code === CHAR_LEFT_SQUARE_BRACKET) {
1806
+ while (eos() !== true && (next = advance())) {
1807
+ if (next === CHAR_BACKWARD_SLASH) {
1808
+ backslashes = token.backslashes = true;
1809
+ advance();
1810
+ continue;
1811
+ }
1812
+ if (next === CHAR_RIGHT_SQUARE_BRACKET) {
1813
+ isBracket = token.isBracket = true;
1814
+ isGlob = token.isGlob = true;
1815
+ finished = true;
1816
+ break;
1817
+ }
1818
+ }
1819
+ if (scanToEnd === true) continue;
1820
+ break;
1821
+ }
1822
+ if (opts.nonegate !== true && code === CHAR_EXCLAMATION_MARK && index === start) {
1823
+ negated = token.negated = true;
1824
+ start++;
1825
+ continue;
1826
+ }
1827
+ if (opts.noparen !== true && code === CHAR_LEFT_PARENTHESES) {
1828
+ isGlob = token.isGlob = true;
1829
+ if (scanToEnd === true) {
1830
+ while (eos() !== true && (code = advance())) {
1831
+ if (code === CHAR_LEFT_PARENTHESES) {
1832
+ backslashes = token.backslashes = true;
1833
+ code = advance();
1834
+ continue;
1835
+ }
1836
+ if (code === CHAR_RIGHT_PARENTHESES) {
1837
+ finished = true;
1838
+ break;
1839
+ }
1840
+ }
1841
+ continue;
1842
+ }
1843
+ break;
1844
+ }
1845
+ if (isGlob === true) {
1846
+ finished = true;
1847
+ if (scanToEnd === true) continue;
1848
+ break;
1849
+ }
1850
+ }
1851
+ if (opts.noext === true) {
1852
+ isExtglob = false;
1853
+ isGlob = false;
1854
+ }
1855
+ let base = str;
1856
+ let prefix = "";
1857
+ let glob = "";
1858
+ if (start > 0) {
1859
+ prefix = str.slice(0, start);
1860
+ str = str.slice(start);
1861
+ lastIndex -= start;
1862
+ }
1863
+ if (base && isGlob === true && lastIndex > 0) {
1864
+ base = str.slice(0, lastIndex);
1865
+ glob = str.slice(lastIndex);
1866
+ } else if (isGlob === true) {
1867
+ base = "";
1868
+ glob = str;
1869
+ } else base = str;
1870
+ if (base && base !== "" && base !== "/" && base !== str) {
1871
+ if (isPathSeparator(base.charCodeAt(base.length - 1))) base = base.slice(0, -1);
1872
+ }
1873
+ if (opts.unescape === true) {
1874
+ if (glob) glob = utils.removeBackslashes(glob);
1875
+ if (base && backslashes === true) base = utils.removeBackslashes(base);
1876
+ }
1877
+ const state = {
1878
+ prefix,
1879
+ input,
1880
+ start,
1881
+ base,
1882
+ glob,
1883
+ isBrace,
1884
+ isBracket,
1885
+ isGlob,
1886
+ isExtglob,
1887
+ isGlobstar,
1888
+ negated,
1889
+ negatedExtglob
1890
+ };
1891
+ if (opts.tokens === true) {
1892
+ state.maxDepth = 0;
1893
+ if (!isPathSeparator(code)) tokens.push(token);
1894
+ state.tokens = tokens;
1895
+ }
1896
+ if (opts.parts === true || opts.tokens === true) {
1897
+ let prevIndex;
1898
+ for (let idx = 0; idx < slashes.length; idx++) {
1899
+ const n = prevIndex ? prevIndex + 1 : start;
1900
+ const i = slashes[idx];
1901
+ const value = input.slice(n, i);
1902
+ if (opts.tokens) {
1903
+ if (idx === 0 && start !== 0) {
1904
+ tokens[idx].isPrefix = true;
1905
+ tokens[idx].value = prefix;
1906
+ } else tokens[idx].value = value;
1907
+ depth(tokens[idx]);
1908
+ state.maxDepth += tokens[idx].depth;
1909
+ }
1910
+ if (idx !== 0 || value !== "") parts.push(value);
1911
+ prevIndex = i;
1912
+ }
1913
+ if (prevIndex && prevIndex + 1 < input.length) {
1914
+ const value = input.slice(prevIndex + 1);
1915
+ parts.push(value);
1916
+ if (opts.tokens) {
1917
+ tokens[tokens.length - 1].value = value;
1918
+ depth(tokens[tokens.length - 1]);
1919
+ state.maxDepth += tokens[tokens.length - 1].depth;
1920
+ }
1921
+ }
1922
+ state.slashes = slashes;
1923
+ state.parts = parts;
1924
+ }
1925
+ return state;
1926
+ };
1927
+ module.exports = scan;
1928
+ }));
1929
+ //#endregion
1930
+ //#region node_modules/.pnpm/picomatch@4.0.5/node_modules/picomatch/lib/parse.js
1931
+ var require_parse = /* @__PURE__ */ __commonJSMin(((exports, module) => {
1932
+ const constants = require_constants();
1933
+ const utils = require_utils();
1934
+ /**
1935
+ * Constants
1936
+ */
1937
+ const { MAX_LENGTH, POSIX_REGEX_SOURCE, REGEX_NON_SPECIAL_CHARS, REGEX_SPECIAL_CHARS_BACKREF, REPLACEMENTS } = constants;
1938
+ /**
1939
+ * Helpers
1940
+ */
1941
+ const expandRange = (args, options) => {
1942
+ if (typeof options.expandRange === "function") return options.expandRange(...args, options);
1943
+ args.sort();
1944
+ const value = `[${args.join("-")}]`;
1945
+ try {
1946
+ new RegExp(value);
1947
+ } catch (ex) {
1948
+ return args.map((v) => utils.escapeRegex(v)).join("..");
1949
+ }
1950
+ return value;
1951
+ };
1952
+ /**
1953
+ * Create the message for a syntax error
1954
+ */
1955
+ const syntaxError = (type, char) => {
1956
+ return `Missing ${type}: "${char}" - use "\\\\${char}" to match literal characters`;
1957
+ };
1958
+ const splitTopLevel = (input) => {
1959
+ const parts = [];
1960
+ let bracket = 0;
1961
+ let paren = 0;
1962
+ let quote = 0;
1963
+ let value = "";
1964
+ let escaped = false;
1965
+ for (const ch of input) {
1966
+ if (escaped === true) {
1967
+ value += ch;
1968
+ escaped = false;
1969
+ continue;
1970
+ }
1971
+ if (ch === "\\") {
1972
+ value += ch;
1973
+ escaped = true;
1974
+ continue;
1975
+ }
1976
+ if (ch === "\"") {
1977
+ quote = quote === 1 ? 0 : 1;
1978
+ value += ch;
1979
+ continue;
1980
+ }
1981
+ if (quote === 0) {
1982
+ if (ch === "[") bracket++;
1983
+ else if (ch === "]" && bracket > 0) bracket--;
1984
+ else if (bracket === 0) {
1985
+ if (ch === "(") paren++;
1986
+ else if (ch === ")" && paren > 0) paren--;
1987
+ else if (ch === "|" && paren === 0) {
1988
+ parts.push(value);
1989
+ value = "";
1990
+ continue;
1991
+ }
1992
+ }
1993
+ }
1994
+ value += ch;
1995
+ }
1996
+ parts.push(value);
1997
+ return parts;
1998
+ };
1999
+ const isPlainBranch = (branch) => {
2000
+ let escaped = false;
2001
+ for (const ch of branch) {
2002
+ if (escaped === true) {
2003
+ escaped = false;
2004
+ continue;
2005
+ }
2006
+ if (ch === "\\") {
2007
+ escaped = true;
2008
+ continue;
2009
+ }
2010
+ if (/[?*+@!()[\]{}]/.test(ch)) return false;
2011
+ }
2012
+ return true;
2013
+ };
2014
+ const normalizeSimpleBranch = (branch) => {
2015
+ let value = branch.trim();
2016
+ let changed = true;
2017
+ while (changed === true) {
2018
+ changed = false;
2019
+ if (/^@\([^\\()[\]{}|]+\)$/.test(value)) {
2020
+ value = value.slice(2, -1);
2021
+ changed = true;
2022
+ }
2023
+ }
2024
+ if (!isPlainBranch(value)) return;
2025
+ return value.replace(/\\(.)/g, "$1");
2026
+ };
2027
+ const hasRepeatedCharPrefixOverlap = (branches) => {
2028
+ const values = branches.map(normalizeSimpleBranch).filter(Boolean);
2029
+ for (let i = 0; i < values.length; i++) for (let j = i + 1; j < values.length; j++) {
2030
+ const a = values[i];
2031
+ const b = values[j];
2032
+ const char = a[0];
2033
+ if (!char || a !== char.repeat(a.length) || b !== char.repeat(b.length)) continue;
2034
+ if (a === b || a.startsWith(b) || b.startsWith(a)) return true;
2035
+ }
2036
+ return false;
2037
+ };
2038
+ const parseRepeatedExtglob = (pattern, requireEnd = true) => {
2039
+ if (pattern[0] !== "+" && pattern[0] !== "*" || pattern[1] !== "(") return;
2040
+ let bracket = 0;
2041
+ let paren = 0;
2042
+ let quote = 0;
2043
+ let escaped = false;
2044
+ for (let i = 1; i < pattern.length; i++) {
2045
+ const ch = pattern[i];
2046
+ if (escaped === true) {
2047
+ escaped = false;
2048
+ continue;
2049
+ }
2050
+ if (ch === "\\") {
2051
+ escaped = true;
2052
+ continue;
2053
+ }
2054
+ if (ch === "\"") {
2055
+ quote = quote === 1 ? 0 : 1;
2056
+ continue;
2057
+ }
2058
+ if (quote === 1) continue;
2059
+ if (ch === "[") {
2060
+ bracket++;
2061
+ continue;
2062
+ }
2063
+ if (ch === "]" && bracket > 0) {
2064
+ bracket--;
2065
+ continue;
2066
+ }
2067
+ if (bracket > 0) continue;
2068
+ if (ch === "(") {
2069
+ paren++;
2070
+ continue;
2071
+ }
2072
+ if (ch === ")") {
2073
+ paren--;
2074
+ if (paren === 0) {
2075
+ if (requireEnd === true && i !== pattern.length - 1) return;
2076
+ return {
2077
+ type: pattern[0],
2078
+ body: pattern.slice(2, i),
2079
+ end: i
2080
+ };
2081
+ }
2082
+ }
2083
+ }
2084
+ };
2085
+ const buildCharClassStar = (chars) => {
2086
+ return `${chars.length === 1 ? utils.escapeRegex(chars[0]) : `[${chars.map((ch) => utils.escapeRegex(ch)).join("")}]`}*`;
2087
+ };
2088
+ const getStarExtglobSequenceChars = (pattern) => {
2089
+ let index = 0;
2090
+ const chars = [];
2091
+ while (index < pattern.length) {
2092
+ const match = parseRepeatedExtglob(pattern.slice(index), false);
2093
+ if (!match || match.type !== "*") return;
2094
+ const branches = splitTopLevel(match.body).map((branch) => branch.trim());
2095
+ if (branches.length !== 1) return;
2096
+ const branch = normalizeSimpleBranch(branches[0]);
2097
+ if (!branch || branch.length !== 1) return;
2098
+ chars.push(branch);
2099
+ index += match.end + 1;
2100
+ }
2101
+ if (chars.length < 1) return;
2102
+ return chars;
2103
+ };
2104
+ const repeatedExtglobRecursion = (pattern) => {
2105
+ let depth = 0;
2106
+ let value = pattern.trim();
2107
+ let match = parseRepeatedExtglob(value);
2108
+ while (match) {
2109
+ depth++;
2110
+ value = match.body.trim();
2111
+ match = parseRepeatedExtglob(value);
2112
+ }
2113
+ return depth;
2114
+ };
2115
+ const analyzeRepeatedExtglob = (body, options) => {
2116
+ if (options.maxExtglobRecursion === false) return { risky: false };
2117
+ const max = typeof options.maxExtglobRecursion === "number" ? options.maxExtglobRecursion : constants.DEFAULT_MAX_EXTGLOB_RECURSION;
2118
+ const branches = splitTopLevel(body).map((branch) => branch.trim());
2119
+ if (branches.length > 1) {
2120
+ if (branches.some((branch) => branch === "") || branches.some((branch) => /^[*?]+$/.test(branch)) || hasRepeatedCharPrefixOverlap(branches)) return { risky: true };
2121
+ }
2122
+ const safeChars = [];
2123
+ let sawStarSequence = false;
2124
+ let combinable = true;
2125
+ for (const branch of branches) {
2126
+ const chars = getStarExtglobSequenceChars(branch);
2127
+ if (chars) {
2128
+ sawStarSequence = true;
2129
+ safeChars.push(...chars);
2130
+ continue;
2131
+ }
2132
+ const literal = normalizeSimpleBranch(branch);
2133
+ if (literal && literal.length === 1) {
2134
+ safeChars.push(literal);
2135
+ continue;
2136
+ }
2137
+ combinable = false;
2138
+ if (repeatedExtglobRecursion(branch) > max) return { risky: true };
2139
+ }
2140
+ if (sawStarSequence) return combinable ? {
2141
+ risky: true,
2142
+ safeOutput: buildCharClassStar([...new Set(safeChars)])
2143
+ } : { risky: true };
2144
+ return { risky: false };
2145
+ };
2146
+ /**
2147
+ * Parse the given input string.
2148
+ * @param {String} input
2149
+ * @param {Object} options
2150
+ * @return {Object}
2151
+ */
2152
+ const parse = (input, options) => {
2153
+ if (typeof input !== "string") throw new TypeError("Expected a string");
2154
+ input = REPLACEMENTS[input] || input;
2155
+ const opts = { ...options };
2156
+ const max = typeof opts.maxLength === "number" ? Math.min(MAX_LENGTH, opts.maxLength) : MAX_LENGTH;
2157
+ let len = input.length;
2158
+ if (len > max) throw new SyntaxError(`Input length: ${len}, exceeds maximum allowed length: ${max}`);
2159
+ const bos = {
2160
+ type: "bos",
2161
+ value: "",
2162
+ output: opts.prepend || ""
2163
+ };
2164
+ const tokens = [bos];
2165
+ const capture = opts.capture ? "" : "?:";
2166
+ const PLATFORM_CHARS = constants.globChars(opts.windows);
2167
+ const EXTGLOB_CHARS = constants.extglobChars(PLATFORM_CHARS);
2168
+ const { DOT_LITERAL, PLUS_LITERAL, SLASH_LITERAL, ONE_CHAR, DOTS_SLASH, NO_DOT, NO_DOT_SLASH, NO_DOTS_SLASH, QMARK, QMARK_NO_DOT, STAR, START_ANCHOR } = PLATFORM_CHARS;
2169
+ const globstar = (opts) => {
2170
+ return `(${capture}(?:(?!${START_ANCHOR}${opts.dot ? DOTS_SLASH : DOT_LITERAL}).)*?)`;
2171
+ };
2172
+ const nodot = opts.dot ? "" : NO_DOT;
2173
+ const qmarkNoDot = opts.dot ? QMARK : QMARK_NO_DOT;
2174
+ let star = opts.bash === true ? globstar(opts) : STAR;
2175
+ if (opts.capture) star = `(${star})`;
2176
+ if (typeof opts.noext === "boolean") opts.noextglob = opts.noext;
2177
+ const state = {
2178
+ input,
2179
+ index: -1,
2180
+ start: 0,
2181
+ dot: opts.dot === true,
2182
+ consumed: "",
2183
+ output: "",
2184
+ prefix: "",
2185
+ backtrack: false,
2186
+ negated: false,
2187
+ brackets: 0,
2188
+ braces: 0,
2189
+ parens: 0,
2190
+ quotes: 0,
2191
+ globstar: false,
2192
+ tokens
2193
+ };
2194
+ input = utils.removePrefix(input, state);
2195
+ len = input.length;
2196
+ const extglobs = [];
2197
+ const braces = [];
2198
+ const stack = [];
2199
+ let prev = bos;
2200
+ let value;
2201
+ /**
2202
+ * Tokenizing helpers
2203
+ */
2204
+ const eos = () => state.index === len - 1;
2205
+ const peek = state.peek = (n = 1) => input[state.index + n];
2206
+ const advance = state.advance = () => input[++state.index] || "";
2207
+ const remaining = () => input.slice(state.index + 1);
2208
+ const consume = (value = "", num = 0) => {
2209
+ state.consumed += value;
2210
+ state.index += num;
2211
+ };
2212
+ const append = (token) => {
2213
+ state.output += token.output != null ? token.output : token.value;
2214
+ consume(token.value);
2215
+ };
2216
+ const negate = () => {
2217
+ let count = 1;
2218
+ while (peek() === "!" && (peek(2) !== "(" || peek(3) === "?")) {
2219
+ advance();
2220
+ state.start++;
2221
+ count++;
2222
+ }
2223
+ if (count % 2 === 0) return false;
2224
+ state.negated = true;
2225
+ state.start++;
2226
+ return true;
2227
+ };
2228
+ const increment = (type) => {
2229
+ state[type]++;
2230
+ stack.push(type);
2231
+ };
2232
+ const decrement = (type) => {
2233
+ state[type]--;
2234
+ stack.pop();
2235
+ };
2236
+ /**
2237
+ * Push tokens onto the tokens array. This helper speeds up
2238
+ * tokenizing by 1) helping us avoid backtracking as much as possible,
2239
+ * and 2) helping us avoid creating extra tokens when consecutive
2240
+ * characters are plain text. This improves performance and simplifies
2241
+ * lookbehinds.
2242
+ */
2243
+ const push = (tok) => {
2244
+ if (prev.type === "globstar") {
2245
+ const isBrace = state.braces > 0 && (tok.type === "comma" || tok.type === "brace");
2246
+ const isExtglob = tok.extglob === true || extglobs.length && (tok.type === "pipe" || tok.type === "paren");
2247
+ if (tok.type !== "slash" && tok.type !== "paren" && !isBrace && !isExtglob) {
2248
+ state.output = state.output.slice(0, -prev.output.length);
2249
+ prev.type = "star";
2250
+ prev.value = "*";
2251
+ prev.output = star;
2252
+ state.output += prev.output;
2253
+ }
2254
+ }
2255
+ if (extglobs.length && tok.type !== "paren") extglobs[extglobs.length - 1].inner += tok.value;
2256
+ if (tok.value || tok.output) append(tok);
2257
+ if (prev && prev.type === "text" && tok.type === "text") {
2258
+ prev.output = (prev.output || prev.value) + tok.value;
2259
+ prev.value += tok.value;
2260
+ return;
2261
+ }
2262
+ tok.prev = prev;
2263
+ tokens.push(tok);
2264
+ prev = tok;
2265
+ };
2266
+ const extglobOpen = (type, value) => {
2267
+ const token = {
2268
+ ...EXTGLOB_CHARS[value],
2269
+ conditions: 1,
2270
+ inner: ""
2271
+ };
2272
+ token.prev = prev;
2273
+ token.parens = state.parens;
2274
+ token.output = state.output;
2275
+ token.startIndex = state.index;
2276
+ token.tokensIndex = tokens.length;
2277
+ const output = (opts.capture ? "(" : "") + token.open;
2278
+ increment("parens");
2279
+ push({
2280
+ type,
2281
+ value,
2282
+ output: state.output ? "" : ONE_CHAR
2283
+ });
2284
+ push({
2285
+ type: "paren",
2286
+ extglob: true,
2287
+ value: advance(),
2288
+ output
2289
+ });
2290
+ extglobs.push(token);
2291
+ };
2292
+ const extglobClose = (token) => {
2293
+ const literal = input.slice(token.startIndex, state.index + 1);
2294
+ const body = input.slice(token.startIndex + 2, state.index);
2295
+ const analysis = analyzeRepeatedExtglob(body, opts);
2296
+ if ((token.type === "plus" || token.type === "star") && analysis.risky) {
2297
+ const safeOutput = analysis.safeOutput ? (token.output ? "" : ONE_CHAR) + (opts.capture ? `(${analysis.safeOutput})` : analysis.safeOutput) : void 0;
2298
+ const open = tokens[token.tokensIndex];
2299
+ open.type = "text";
2300
+ open.value = literal;
2301
+ open.output = safeOutput || utils.escapeRegex(literal);
2302
+ for (let i = token.tokensIndex + 1; i < tokens.length; i++) {
2303
+ tokens[i].value = "";
2304
+ tokens[i].output = "";
2305
+ delete tokens[i].suffix;
2306
+ }
2307
+ state.output = token.output + open.output;
2308
+ state.backtrack = true;
2309
+ push({
2310
+ type: "paren",
2311
+ extglob: true,
2312
+ value,
2313
+ output: ""
2314
+ });
2315
+ decrement("parens");
2316
+ return;
2317
+ }
2318
+ let output = token.close + (opts.capture ? ")" : "");
2319
+ let rest;
2320
+ if (token.type === "negate") {
2321
+ let extglobStar = star;
2322
+ if (token.inner && token.inner.length > 1 && token.inner.includes("/")) extglobStar = globstar(opts);
2323
+ if (extglobStar !== star || eos() || /^\)+$/.test(remaining())) output = token.close = `)$))${extglobStar}`;
2324
+ if (token.inner.includes("*") && (rest = remaining()) && /^\.[^\\/.]+$/.test(rest)) output = token.close = `)${parse(rest, {
2325
+ ...options,
2326
+ fastpaths: false
2327
+ }).output})${extglobStar})`;
2328
+ if (token.prev.type === "bos") state.negatedExtglob = true;
2329
+ }
2330
+ push({
2331
+ type: "paren",
2332
+ extglob: true,
2333
+ value,
2334
+ output
2335
+ });
2336
+ decrement("parens");
2337
+ };
2338
+ /**
2339
+ * Fast paths
2340
+ */
2341
+ if (opts.fastpaths !== false && !/(^[*!]|[/()[\]{}"])/.test(input)) {
2342
+ let backslashes = false;
2343
+ let output = input.replace(REGEX_SPECIAL_CHARS_BACKREF, (m, esc, chars, first, rest, index) => {
2344
+ if (first === "\\") {
2345
+ backslashes = true;
2346
+ return m;
2347
+ }
2348
+ if (first === "?") {
2349
+ if (esc) return esc + first + (rest ? QMARK.repeat(rest.length) : "");
2350
+ if (index === 0) return qmarkNoDot + (rest ? QMARK.repeat(rest.length) : "");
2351
+ return QMARK.repeat(chars.length);
2352
+ }
2353
+ if (first === ".") return DOT_LITERAL.repeat(chars.length);
2354
+ if (first === "*") {
2355
+ if (esc) return esc + first + (rest ? star : "");
2356
+ return star;
2357
+ }
2358
+ return esc ? m : `\\${m}`;
2359
+ });
2360
+ if (backslashes === true) if (opts.unescape === true) output = output.replace(/\\/g, "");
2361
+ else output = output.replace(/\\+/g, (m) => {
2362
+ return m.length % 2 === 0 ? "\\\\" : m ? "\\" : "";
2363
+ });
2364
+ if (output === input && opts.contains === true) {
2365
+ state.output = input;
2366
+ return state;
2367
+ }
2368
+ state.output = utils.wrapOutput(output, state, options);
2369
+ return state;
2370
+ }
2371
+ /**
2372
+ * Tokenize input until we reach end-of-string
2373
+ */
2374
+ while (!eos()) {
2375
+ value = advance();
2376
+ if (value === "\0") continue;
2377
+ /**
2378
+ * Escaped characters
2379
+ */
2380
+ if (value === "\\") {
2381
+ const next = peek();
2382
+ if (next === "/" && opts.bash !== true) continue;
2383
+ if (next === "." || next === ";") continue;
2384
+ if (!next) {
2385
+ value += "\\";
2386
+ push({
2387
+ type: "text",
2388
+ value
2389
+ });
2390
+ continue;
2391
+ }
2392
+ const match = /^\\+/.exec(remaining());
2393
+ let slashes = 0;
2394
+ if (match && match[0].length > 2) {
2395
+ slashes = match[0].length;
2396
+ state.index += slashes;
2397
+ if (slashes % 2 !== 0) value += "\\";
2398
+ }
2399
+ if (opts.unescape === true) value = advance();
2400
+ else value += advance();
2401
+ if (state.brackets === 0) {
2402
+ push({
2403
+ type: "text",
2404
+ value
2405
+ });
2406
+ continue;
2407
+ }
2408
+ }
2409
+ /**
2410
+ * If we're inside a regex character class, continue
2411
+ * until we reach the closing bracket.
2412
+ */
2413
+ if (state.brackets > 0 && (value !== "]" || prev.value === "[" || prev.value === "[^")) {
2414
+ if (opts.posix !== false && value === ":") {
2415
+ const inner = prev.value.slice(1);
2416
+ if (inner.includes("[")) {
2417
+ prev.posix = true;
2418
+ if (inner.includes(":")) {
2419
+ const idx = prev.value.lastIndexOf("[");
2420
+ const pre = prev.value.slice(0, idx);
2421
+ const rest = prev.value.slice(idx + 2);
2422
+ const posix = POSIX_REGEX_SOURCE[rest];
2423
+ if (posix) {
2424
+ prev.value = pre + posix;
2425
+ state.backtrack = true;
2426
+ advance();
2427
+ if (!bos.output && tokens.indexOf(prev) === 1) bos.output = ONE_CHAR;
2428
+ continue;
2429
+ }
2430
+ }
2431
+ }
2432
+ }
2433
+ if (value === "[" && peek() !== ":" || value === "-" && peek() === "]") value = `\\${value}`;
2434
+ if (value === "]" && (prev.value === "[" || prev.value === "[^")) value = `\\${value}`;
2435
+ if (opts.posix === true && value === "!" && prev.value === "[") value = "^";
2436
+ prev.value += value;
2437
+ append({ value });
2438
+ continue;
2439
+ }
2440
+ /**
2441
+ * If we're inside a quoted string, continue
2442
+ * until we reach the closing double quote.
2443
+ */
2444
+ if (state.quotes === 1 && value !== "\"") {
2445
+ value = utils.escapeRegex(value);
2446
+ prev.value += value;
2447
+ append({ value });
2448
+ continue;
2449
+ }
2450
+ /**
2451
+ * Double quotes
2452
+ */
2453
+ if (value === "\"") {
2454
+ state.quotes = state.quotes === 1 ? 0 : 1;
2455
+ if (opts.keepQuotes === true) push({
2456
+ type: "text",
2457
+ value
2458
+ });
2459
+ continue;
2460
+ }
2461
+ /**
2462
+ * Parentheses
2463
+ */
2464
+ if (value === "(") {
2465
+ increment("parens");
2466
+ push({
2467
+ type: "paren",
2468
+ value
2469
+ });
2470
+ continue;
2471
+ }
2472
+ if (value === ")") {
2473
+ if (state.parens === 0 && opts.strictBrackets === true) throw new SyntaxError(syntaxError("opening", "("));
2474
+ const extglob = extglobs[extglobs.length - 1];
2475
+ if (extglob && state.parens === extglob.parens + 1) {
2476
+ extglobClose(extglobs.pop());
2477
+ continue;
2478
+ }
2479
+ push({
2480
+ type: "paren",
2481
+ value,
2482
+ output: state.parens ? ")" : "\\)"
2483
+ });
2484
+ decrement("parens");
2485
+ continue;
2486
+ }
2487
+ /**
2488
+ * Square brackets
2489
+ */
2490
+ if (value === "[") {
2491
+ if (opts.nobracket === true || !remaining().includes("]")) {
2492
+ if (opts.nobracket !== true && opts.strictBrackets === true) throw new SyntaxError(syntaxError("closing", "]"));
2493
+ value = `\\${value}`;
2494
+ } else increment("brackets");
2495
+ push({
2496
+ type: "bracket",
2497
+ value
2498
+ });
2499
+ continue;
2500
+ }
2501
+ if (value === "]") {
2502
+ if (opts.nobracket === true || prev && prev.type === "bracket" && prev.value.length === 1) {
2503
+ push({
2504
+ type: "text",
2505
+ value,
2506
+ output: `\\${value}`
2507
+ });
2508
+ continue;
2509
+ }
2510
+ if (state.brackets === 0) {
2511
+ if (opts.strictBrackets === true) throw new SyntaxError(syntaxError("opening", "["));
2512
+ push({
2513
+ type: "text",
2514
+ value,
2515
+ output: `\\${value}`
2516
+ });
2517
+ continue;
2518
+ }
2519
+ decrement("brackets");
2520
+ const prevValue = prev.value.slice(1);
2521
+ if (prev.posix !== true && prevValue[0] === "^" && !prevValue.includes("/")) value = `/${value}`;
2522
+ prev.value += value;
2523
+ append({ value });
2524
+ if (opts.literalBrackets === false || utils.hasRegexChars(prevValue)) continue;
2525
+ const escaped = utils.escapeRegex(prev.value);
2526
+ state.output = state.output.slice(0, -prev.value.length);
2527
+ if (opts.literalBrackets === true) {
2528
+ state.output += escaped;
2529
+ prev.value = escaped;
2530
+ continue;
2531
+ }
2532
+ prev.value = `(${capture}${escaped}|${prev.value})`;
2533
+ state.output += prev.value;
2534
+ continue;
2535
+ }
2536
+ /**
2537
+ * Braces
2538
+ */
2539
+ if (value === "{" && opts.nobrace !== true) {
2540
+ increment("braces");
2541
+ const open = {
2542
+ type: "brace",
2543
+ value,
2544
+ output: "(",
2545
+ outputIndex: state.output.length,
2546
+ tokensIndex: state.tokens.length
2547
+ };
2548
+ braces.push(open);
2549
+ push(open);
2550
+ continue;
2551
+ }
2552
+ if (value === "}") {
2553
+ const brace = braces[braces.length - 1];
2554
+ if (opts.nobrace === true || !brace) {
2555
+ push({
2556
+ type: "text",
2557
+ value,
2558
+ output: value
2559
+ });
2560
+ continue;
2561
+ }
2562
+ let output = ")";
2563
+ if (brace.dots === true) {
2564
+ const arr = tokens.slice();
2565
+ const range = [];
2566
+ for (let i = arr.length - 1; i >= 0; i--) {
2567
+ tokens.pop();
2568
+ if (arr[i].type === "brace") break;
2569
+ if (arr[i].type !== "dots") range.unshift(arr[i].value);
2570
+ }
2571
+ output = expandRange(range, opts);
2572
+ state.backtrack = true;
2573
+ }
2574
+ if (brace.comma !== true && brace.dots !== true) {
2575
+ const out = state.output.slice(0, brace.outputIndex);
2576
+ const toks = state.tokens.slice(brace.tokensIndex);
2577
+ brace.value = brace.output = "\\{";
2578
+ value = output = "\\}";
2579
+ state.output = out;
2580
+ for (const t of toks) state.output += t.output || t.value;
2581
+ }
2582
+ push({
2583
+ type: "brace",
2584
+ value,
2585
+ output
2586
+ });
2587
+ decrement("braces");
2588
+ braces.pop();
2589
+ continue;
2590
+ }
2591
+ /**
2592
+ * Pipes
2593
+ */
2594
+ if (value === "|") {
2595
+ if (extglobs.length > 0) extglobs[extglobs.length - 1].conditions++;
2596
+ push({
2597
+ type: "text",
2598
+ value
2599
+ });
2600
+ continue;
2601
+ }
2602
+ /**
2603
+ * Commas
2604
+ */
2605
+ if (value === ",") {
2606
+ let output = value;
2607
+ const brace = braces[braces.length - 1];
2608
+ if (brace && stack[stack.length - 1] === "braces") {
2609
+ brace.comma = true;
2610
+ output = "|";
2611
+ }
2612
+ push({
2613
+ type: "comma",
2614
+ value,
2615
+ output
2616
+ });
2617
+ continue;
2618
+ }
2619
+ /**
2620
+ * Slashes
2621
+ */
2622
+ if (value === "/") {
2623
+ if (prev.type === "dot" && state.index === state.start + 1) {
2624
+ state.start = state.index + 1;
2625
+ state.consumed = "";
2626
+ state.output = "";
2627
+ tokens.pop();
2628
+ prev = bos;
2629
+ continue;
2630
+ }
2631
+ push({
2632
+ type: "slash",
2633
+ value,
2634
+ output: SLASH_LITERAL
2635
+ });
2636
+ continue;
2637
+ }
2638
+ /**
2639
+ * Dots
2640
+ */
2641
+ if (value === ".") {
2642
+ if (state.braces > 0 && prev.type === "dot") {
2643
+ if (prev.value === ".") prev.output = DOT_LITERAL;
2644
+ const brace = braces[braces.length - 1];
2645
+ prev.type = "dots";
2646
+ prev.output += value;
2647
+ prev.value += value;
2648
+ brace.dots = true;
2649
+ continue;
2650
+ }
2651
+ if (state.braces + state.parens === 0 && prev.type !== "bos" && prev.type !== "slash") {
2652
+ push({
2653
+ type: "text",
2654
+ value,
2655
+ output: DOT_LITERAL
2656
+ });
2657
+ continue;
2658
+ }
2659
+ push({
2660
+ type: "dot",
2661
+ value,
2662
+ output: DOT_LITERAL
2663
+ });
2664
+ continue;
2665
+ }
2666
+ /**
2667
+ * Question marks
2668
+ */
2669
+ if (value === "?") {
2670
+ if (!(prev && prev.value === "(") && opts.noextglob !== true && peek() === "(" && peek(2) !== "?") {
2671
+ extglobOpen("qmark", value);
2672
+ continue;
2673
+ }
2674
+ if (prev && prev.type === "paren") {
2675
+ const next = peek();
2676
+ let output = value;
2677
+ if (prev.value === "(" && !/[!=<:]/.test(next) || next === "<" && !/<([!=]|\w+>)/.test(remaining())) output = `\\${value}`;
2678
+ push({
2679
+ type: "text",
2680
+ value,
2681
+ output
2682
+ });
2683
+ continue;
2684
+ }
2685
+ if (opts.dot !== true && (prev.type === "slash" || prev.type === "bos")) {
2686
+ push({
2687
+ type: "qmark",
2688
+ value,
2689
+ output: QMARK_NO_DOT
2690
+ });
2691
+ continue;
2692
+ }
2693
+ push({
2694
+ type: "qmark",
2695
+ value,
2696
+ output: QMARK
2697
+ });
2698
+ continue;
2699
+ }
2700
+ /**
2701
+ * Exclamation
2702
+ */
2703
+ if (value === "!") {
2704
+ if (opts.noextglob !== true && peek() === "(") {
2705
+ if (peek(2) !== "?" || !/[!=<:]/.test(peek(3))) {
2706
+ extglobOpen("negate", value);
2707
+ continue;
2708
+ }
2709
+ }
2710
+ if (opts.nonegate !== true && state.index === 0) {
2711
+ negate();
2712
+ continue;
2713
+ }
2714
+ }
2715
+ /**
2716
+ * Plus
2717
+ */
2718
+ if (value === "+") {
2719
+ if (opts.noextglob !== true && peek() === "(" && peek(2) !== "?") {
2720
+ extglobOpen("plus", value);
2721
+ continue;
2722
+ }
2723
+ if (prev && prev.value === "(" || opts.regex === false) {
2724
+ push({
2725
+ type: "plus",
2726
+ value,
2727
+ output: PLUS_LITERAL
2728
+ });
2729
+ continue;
2730
+ }
2731
+ if (prev && (prev.type === "bracket" || prev.type === "paren" || prev.type === "brace") || state.parens > 0) {
2732
+ push({
2733
+ type: "plus",
2734
+ value
2735
+ });
2736
+ continue;
2737
+ }
2738
+ push({
2739
+ type: "plus",
2740
+ value: PLUS_LITERAL
2741
+ });
2742
+ continue;
2743
+ }
2744
+ /**
2745
+ * Plain text
2746
+ */
2747
+ if (value === "@") {
2748
+ if (opts.noextglob !== true && peek() === "(" && peek(2) !== "?") {
2749
+ push({
2750
+ type: "at",
2751
+ extglob: true,
2752
+ value,
2753
+ output: ""
2754
+ });
2755
+ continue;
2756
+ }
2757
+ push({
2758
+ type: "text",
2759
+ value
2760
+ });
2761
+ continue;
2762
+ }
2763
+ /**
2764
+ * Plain text
2765
+ */
2766
+ if (value !== "*") {
2767
+ if (value === "$" || value === "^") value = `\\${value}`;
2768
+ const match = REGEX_NON_SPECIAL_CHARS.exec(remaining());
2769
+ if (match) {
2770
+ value += match[0];
2771
+ state.index += match[0].length;
2772
+ }
2773
+ push({
2774
+ type: "text",
2775
+ value
2776
+ });
2777
+ continue;
2778
+ }
2779
+ /**
2780
+ * Stars
2781
+ */
2782
+ if (prev && (prev.type === "globstar" || prev.star === true)) {
2783
+ prev.type = "star";
2784
+ prev.star = true;
2785
+ prev.value += value;
2786
+ prev.output = star;
2787
+ state.backtrack = true;
2788
+ state.globstar = true;
2789
+ consume(value);
2790
+ continue;
2791
+ }
2792
+ let rest = remaining();
2793
+ if (opts.noextglob !== true && /^\([^?]/.test(rest)) {
2794
+ extglobOpen("star", value);
2795
+ continue;
2796
+ }
2797
+ if (prev.type === "star") {
2798
+ if (opts.noglobstar === true) {
2799
+ consume(value);
2800
+ continue;
2801
+ }
2802
+ const prior = prev.prev;
2803
+ const before = prior.prev;
2804
+ const isStart = prior.type === "slash" || prior.type === "bos";
2805
+ const afterStar = before && (before.type === "star" || before.type === "globstar");
2806
+ if (opts.bash === true && (!isStart || rest[0] && rest[0] !== "/")) {
2807
+ push({
2808
+ type: "star",
2809
+ value,
2810
+ output: ""
2811
+ });
2812
+ continue;
2813
+ }
2814
+ const isBrace = state.braces > 0 && (prior.type === "comma" || prior.type === "brace");
2815
+ const isExtglob = extglobs.length && (prior.type === "pipe" || prior.type === "paren");
2816
+ if (!isStart && prior.type !== "paren" && !isBrace && !isExtglob) {
2817
+ push({
2818
+ type: "star",
2819
+ value,
2820
+ output: ""
2821
+ });
2822
+ continue;
2823
+ }
2824
+ while (rest.slice(0, 3) === "/**") {
2825
+ const after = input[state.index + 4];
2826
+ if (after && after !== "/") break;
2827
+ rest = rest.slice(3);
2828
+ consume("/**", 3);
2829
+ }
2830
+ if (prior.type === "bos" && eos()) {
2831
+ prev.type = "globstar";
2832
+ prev.value += value;
2833
+ prev.output = globstar(opts);
2834
+ state.output = prev.output;
2835
+ state.globstar = true;
2836
+ consume(value);
2837
+ continue;
2838
+ }
2839
+ if (prior.type === "slash" && prior.prev.type !== "bos" && !afterStar && eos()) {
2840
+ state.output = state.output.slice(0, -(prior.output + prev.output).length);
2841
+ prior.output = `(?:${prior.output}`;
2842
+ prev.type = "globstar";
2843
+ prev.output = globstar(opts) + (opts.strictSlashes ? ")" : "|$)");
2844
+ prev.value += value;
2845
+ state.globstar = true;
2846
+ state.output += prior.output + prev.output;
2847
+ consume(value);
2848
+ continue;
2849
+ }
2850
+ if (prior.type === "slash" && prior.prev.type !== "bos" && rest[0] === "/") {
2851
+ const end = rest[1] !== void 0 ? "|$" : "";
2852
+ state.output = state.output.slice(0, -(prior.output + prev.output).length);
2853
+ prior.output = `(?:${prior.output}`;
2854
+ prev.type = "globstar";
2855
+ prev.output = `${globstar(opts)}${SLASH_LITERAL}|${SLASH_LITERAL}${end})`;
2856
+ prev.value += value;
2857
+ state.output += prior.output + prev.output;
2858
+ state.globstar = true;
2859
+ consume(value + advance());
2860
+ push({
2861
+ type: "slash",
2862
+ value: "/",
2863
+ output: ""
2864
+ });
2865
+ continue;
2866
+ }
2867
+ if (prior.type === "bos" && rest[0] === "/") {
2868
+ prev.type = "globstar";
2869
+ prev.value += value;
2870
+ prev.output = `(?:^|${SLASH_LITERAL}|${globstar(opts)}${SLASH_LITERAL})`;
2871
+ state.output = prev.output;
2872
+ state.globstar = true;
2873
+ consume(value + advance());
2874
+ push({
2875
+ type: "slash",
2876
+ value: "/",
2877
+ output: ""
2878
+ });
2879
+ continue;
2880
+ }
2881
+ state.output = state.output.slice(0, -prev.output.length);
2882
+ prev.type = "globstar";
2883
+ prev.output = globstar(opts);
2884
+ prev.value += value;
2885
+ state.output += prev.output;
2886
+ state.globstar = true;
2887
+ consume(value);
2888
+ continue;
2889
+ }
2890
+ const token = {
2891
+ type: "star",
2892
+ value,
2893
+ output: star
2894
+ };
2895
+ if (opts.bash === true) {
2896
+ token.output = ".*?";
2897
+ if (prev.type === "bos" || prev.type === "slash") token.output = nodot + token.output;
2898
+ push(token);
2899
+ continue;
2900
+ }
2901
+ if (prev && (prev.type === "bracket" || prev.type === "paren") && opts.regex === true) {
2902
+ token.output = value;
2903
+ push(token);
2904
+ continue;
2905
+ }
2906
+ if (state.index === state.start || prev.type === "slash" || prev.type === "dot") {
2907
+ if (prev.type === "dot") {
2908
+ state.output += NO_DOT_SLASH;
2909
+ prev.output += NO_DOT_SLASH;
2910
+ } else if (opts.dot === true) {
2911
+ state.output += NO_DOTS_SLASH;
2912
+ prev.output += NO_DOTS_SLASH;
2913
+ } else {
2914
+ state.output += nodot;
2915
+ prev.output += nodot;
2916
+ }
2917
+ if (peek() !== "*") {
2918
+ state.output += ONE_CHAR;
2919
+ prev.output += ONE_CHAR;
2920
+ }
2921
+ }
2922
+ push(token);
2923
+ }
2924
+ while (state.brackets > 0) {
2925
+ if (opts.strictBrackets === true) throw new SyntaxError(syntaxError("closing", "]"));
2926
+ state.output = utils.escapeLast(state.output, "[");
2927
+ decrement("brackets");
2928
+ }
2929
+ while (state.parens > 0) {
2930
+ if (opts.strictBrackets === true) throw new SyntaxError(syntaxError("closing", ")"));
2931
+ state.output = utils.escapeLast(state.output, "(");
2932
+ decrement("parens");
2933
+ }
2934
+ while (state.braces > 0) {
2935
+ if (opts.strictBrackets === true) throw new SyntaxError(syntaxError("closing", "}"));
2936
+ state.output = utils.escapeLast(state.output, "{");
2937
+ decrement("braces");
2938
+ }
2939
+ if (opts.strictSlashes !== true && (prev.type === "star" || prev.type === "bracket")) push({
2940
+ type: "maybe_slash",
2941
+ value: "",
2942
+ output: `${SLASH_LITERAL}?`
2943
+ });
2944
+ if (state.backtrack === true) {
2945
+ state.output = "";
2946
+ for (const token of state.tokens) {
2947
+ state.output += token.output != null ? token.output : token.value;
2948
+ if (token.suffix) state.output += token.suffix;
2949
+ }
2950
+ }
2951
+ return state;
2952
+ };
2953
+ /**
2954
+ * Fast paths for creating regular expressions for common glob patterns.
2955
+ * This can significantly speed up processing and has very little downside
2956
+ * impact when none of the fast paths match.
2957
+ */
2958
+ parse.fastpaths = (input, options) => {
2959
+ const opts = { ...options };
2960
+ const max = typeof opts.maxLength === "number" ? Math.min(MAX_LENGTH, opts.maxLength) : MAX_LENGTH;
2961
+ const len = input.length;
2962
+ if (len > max) throw new SyntaxError(`Input length: ${len}, exceeds maximum allowed length: ${max}`);
2963
+ input = REPLACEMENTS[input] || input;
2964
+ const { DOT_LITERAL, SLASH_LITERAL, ONE_CHAR, DOTS_SLASH, NO_DOT, NO_DOTS, NO_DOTS_SLASH, STAR, START_ANCHOR } = constants.globChars(opts.windows);
2965
+ const nodot = opts.dot ? NO_DOTS : NO_DOT;
2966
+ const slashDot = opts.dot ? NO_DOTS_SLASH : NO_DOT;
2967
+ const capture = opts.capture ? "" : "?:";
2968
+ const state = {
2969
+ negated: false,
2970
+ prefix: ""
2971
+ };
2972
+ let star = opts.bash === true ? ".*?" : STAR;
2973
+ if (opts.capture) star = `(${star})`;
2974
+ const globstar = (opts) => {
2975
+ if (opts.noglobstar === true) return star;
2976
+ return `(${capture}(?:(?!${START_ANCHOR}${opts.dot ? DOTS_SLASH : DOT_LITERAL}).)*?)`;
2977
+ };
2978
+ const create = (str) => {
2979
+ switch (str) {
2980
+ case "*": return `${nodot}${ONE_CHAR}${star}`;
2981
+ case ".*": return `${DOT_LITERAL}${ONE_CHAR}${star}`;
2982
+ case "*.*": return `${nodot}${star}${DOT_LITERAL}${ONE_CHAR}${star}`;
2983
+ case "*/*": return `${nodot}${star}${SLASH_LITERAL}${ONE_CHAR}${slashDot}${star}`;
2984
+ case "**": return nodot + globstar(opts);
2985
+ case "**/*": return `(?:${nodot}${globstar(opts)}${SLASH_LITERAL})?${slashDot}${ONE_CHAR}${star}`;
2986
+ case "**/*.*": return `(?:${nodot}${globstar(opts)}${SLASH_LITERAL})?${slashDot}${star}${DOT_LITERAL}${ONE_CHAR}${star}`;
2987
+ case "**/.*": return `(?:${nodot}${globstar(opts)}${SLASH_LITERAL})?${DOT_LITERAL}${ONE_CHAR}${star}`;
2988
+ default: {
2989
+ const match = /^(.*?)\.(\w+)$/.exec(str);
2990
+ if (!match) return;
2991
+ const source = create(match[1]);
2992
+ if (!source) return;
2993
+ return source + DOT_LITERAL + match[2];
2994
+ }
2995
+ }
2996
+ };
2997
+ let source = create(utils.removePrefix(input, state));
2998
+ if (source && opts.strictSlashes !== true) source += `${SLASH_LITERAL}?`;
2999
+ return source;
3000
+ };
3001
+ module.exports = parse;
3002
+ }));
3003
+ //#endregion
3004
+ //#region node_modules/.pnpm/picomatch@4.0.5/node_modules/picomatch/lib/picomatch.js
3005
+ var require_picomatch$1 = /* @__PURE__ */ __commonJSMin(((exports, module) => {
3006
+ const scan = require_scan();
3007
+ const parse = require_parse();
3008
+ const utils = require_utils();
3009
+ const constants = require_constants();
3010
+ const isObject = (val) => val && typeof val === "object" && !Array.isArray(val);
3011
+ /**
3012
+ * Creates a matcher function from one or more glob patterns. The
3013
+ * returned function takes a string to match as its first argument,
3014
+ * and returns true if the string is a match. The returned matcher
3015
+ * function also takes a boolean as the second argument that, when true,
3016
+ * returns an object with additional information.
3017
+ *
3018
+ * ```js
3019
+ * const picomatch = require('picomatch');
3020
+ * // picomatch(glob[, options]);
3021
+ *
3022
+ * const isMatch = picomatch('*.!(*a)');
3023
+ * console.log(isMatch('a.a')); //=> false
3024
+ * console.log(isMatch('a.b')); //=> true
3025
+ *
3026
+ * // For environments without `node.js`, `picomatch/posix` provides you a dependency-free matcher, without automatic OS detection.
3027
+ * const picomatch = require('picomatch/posix');
3028
+ * // the same API, defaulting to posix paths
3029
+ * const isMatch = picomatch('a/*');
3030
+ * console.log(isMatch('a\\b')); //=> false
3031
+ * console.log(isMatch('a/b')); //=> true
3032
+ *
3033
+ * // you can still configure the matcher function to accept windows paths
3034
+ * const isMatch = picomatch('a/*', { options: windows });
3035
+ * console.log(isMatch('a\\b')); //=> true
3036
+ * console.log(isMatch('a/b')); //=> true
3037
+ * ```
3038
+ * @name picomatch
3039
+ * @param {String|Array} `globs` One or more glob patterns.
3040
+ * @param {Object=} `options`
3041
+ * @return {Function=} Returns a matcher function.
3042
+ * @api public
3043
+ */
3044
+ const picomatch = (glob, options, returnState = false) => {
3045
+ if (Array.isArray(glob)) {
3046
+ const fns = glob.map((input) => picomatch(input, options, returnState));
3047
+ const arrayMatcher = (str) => {
3048
+ for (const isMatch of fns) {
3049
+ const state = isMatch(str);
3050
+ if (state) return state;
3051
+ }
3052
+ return false;
3053
+ };
3054
+ return arrayMatcher;
3055
+ }
3056
+ const isState = isObject(glob) && glob.tokens && glob.input;
3057
+ if (glob === "" || typeof glob !== "string" && !isState) throw new TypeError("Expected pattern to be a non-empty string");
3058
+ const opts = options || {};
3059
+ const posix = opts.windows;
3060
+ const regex = isState ? picomatch.compileRe(glob, options) : picomatch.makeRe(glob, options, false, true);
3061
+ const state = regex.state;
3062
+ delete regex.state;
3063
+ let isIgnored = () => false;
3064
+ if (opts.ignore) {
3065
+ const ignoreOpts = {
3066
+ ...options,
3067
+ ignore: null,
3068
+ onMatch: null,
3069
+ onResult: null
3070
+ };
3071
+ isIgnored = picomatch(opts.ignore, ignoreOpts, returnState);
3072
+ }
3073
+ const matcher = (input, returnObject = false) => {
3074
+ const { isMatch, match, output } = picomatch.test(input, regex, options, {
3075
+ glob,
3076
+ posix
3077
+ });
3078
+ const result = {
3079
+ glob,
3080
+ state,
3081
+ regex,
3082
+ posix,
3083
+ input,
3084
+ output,
3085
+ match,
3086
+ isMatch
3087
+ };
3088
+ if (typeof opts.onResult === "function") opts.onResult(result);
3089
+ if (isMatch === false) {
3090
+ result.isMatch = false;
3091
+ return returnObject ? result : false;
3092
+ }
3093
+ if (isIgnored(input)) {
3094
+ if (typeof opts.onIgnore === "function") opts.onIgnore(result);
3095
+ result.isMatch = false;
3096
+ return returnObject ? result : false;
3097
+ }
3098
+ if (typeof opts.onMatch === "function") opts.onMatch(result);
3099
+ return returnObject ? result : true;
3100
+ };
3101
+ if (returnState) matcher.state = state;
3102
+ return matcher;
3103
+ };
3104
+ /**
3105
+ * Test `input` with the given `regex`. This is used by the main
3106
+ * `picomatch()` function to test the input string.
3107
+ *
3108
+ * ```js
3109
+ * const picomatch = require('picomatch');
3110
+ * // picomatch.test(input, regex[, options]);
3111
+ *
3112
+ * console.log(picomatch.test('foo/bar', /^(?:([^/]*?)\/([^/]*?))$/));
3113
+ * // { isMatch: true, match: [ 'foo/', 'foo', 'bar' ], output: 'foo/bar' }
3114
+ * ```
3115
+ * @param {String} `input` String to test.
3116
+ * @param {RegExp} `regex`
3117
+ * @return {Object} Returns an object with matching info.
3118
+ * @api public
3119
+ */
3120
+ picomatch.test = (input, regex, options, { glob, posix } = {}) => {
3121
+ if (typeof input !== "string") throw new TypeError("Expected input to be a string");
3122
+ if (input === "") return {
3123
+ isMatch: false,
3124
+ output: ""
3125
+ };
3126
+ const opts = options || {};
3127
+ const format = opts.format || (posix ? utils.toPosixSlashes : null);
3128
+ let match = input === glob;
3129
+ let output = match && format ? format(input) : input;
3130
+ if (match === false) {
3131
+ output = format ? format(input) : input;
3132
+ match = output === glob;
3133
+ }
3134
+ if (match === false || opts.capture === true) if (opts.matchBase === true || opts.basename === true) match = picomatch.matchBase(input, regex, options, posix);
3135
+ else match = regex.exec(output);
3136
+ return {
3137
+ isMatch: Boolean(match),
3138
+ match,
3139
+ output
3140
+ };
3141
+ };
3142
+ /**
3143
+ * Match the basename of a filepath.
3144
+ *
3145
+ * ```js
3146
+ * const picomatch = require('picomatch');
3147
+ * // picomatch.matchBase(input, glob[, options]);
3148
+ * console.log(picomatch.matchBase('foo/bar.js', '*.js'); // true
3149
+ * ```
3150
+ * @param {String} `input` String to test.
3151
+ * @param {RegExp|String} `glob` Glob pattern or regex created by [.makeRe](#makeRe).
3152
+ * @return {Boolean}
3153
+ * @api public
3154
+ */
3155
+ picomatch.matchBase = (input, glob, options, posix = options && options.windows) => {
3156
+ return (glob instanceof RegExp ? glob : picomatch.makeRe(glob, options)).test(utils.basename(input, { windows: posix }));
3157
+ };
3158
+ /**
3159
+ * Returns true if **any** of the given glob `patterns` match the specified `string`.
3160
+ *
3161
+ * ```js
3162
+ * const picomatch = require('picomatch');
3163
+ * // picomatch.isMatch(string, patterns[, options]);
3164
+ *
3165
+ * console.log(picomatch.isMatch('a.a', ['b.*', '*.a'])); //=> true
3166
+ * console.log(picomatch.isMatch('a.a', 'b.*')); //=> false
3167
+ * ```
3168
+ * @param {String|Array} str The string to test.
3169
+ * @param {String|Array} patterns One or more glob patterns to use for matching.
3170
+ * @param {Object} [options] See available [options](#options).
3171
+ * @return {Boolean} Returns true if any patterns match `str`
3172
+ * @api public
3173
+ */
3174
+ picomatch.isMatch = (str, patterns, options) => picomatch(patterns, options)(str);
3175
+ /**
3176
+ * Parse a glob pattern to create the source string for a regular
3177
+ * expression.
3178
+ *
3179
+ * ```js
3180
+ * const picomatch = require('picomatch');
3181
+ * const result = picomatch.parse(pattern[, options]);
3182
+ * ```
3183
+ * @param {String} `pattern`
3184
+ * @param {Object} `options`
3185
+ * @return {Object} Returns an object with useful properties and output to be used as a regex source string.
3186
+ * @api public
3187
+ */
3188
+ picomatch.parse = (pattern, options) => {
3189
+ if (Array.isArray(pattern)) return pattern.map((p) => picomatch.parse(p, options));
3190
+ return parse(pattern, {
3191
+ ...options,
3192
+ fastpaths: false
3193
+ });
3194
+ };
3195
+ /**
3196
+ * Scan a glob pattern to separate the pattern into segments.
3197
+ *
3198
+ * ```js
3199
+ * const picomatch = require('picomatch');
3200
+ * // picomatch.scan(input[, options]);
3201
+ *
3202
+ * const result = picomatch.scan('!./foo/*.js');
3203
+ * console.log(result);
3204
+ * { prefix: '!./',
3205
+ * input: '!./foo/*.js',
3206
+ * start: 3,
3207
+ * base: 'foo',
3208
+ * glob: '*.js',
3209
+ * isBrace: false,
3210
+ * isBracket: false,
3211
+ * isGlob: true,
3212
+ * isExtglob: false,
3213
+ * isGlobstar: false,
3214
+ * negated: true }
3215
+ * ```
3216
+ * @param {String} `input` Glob pattern to scan.
3217
+ * @param {Object} `options`
3218
+ * @return {Object} Returns an object with
3219
+ * @api public
3220
+ */
3221
+ picomatch.scan = (input, options) => scan(input, options);
3222
+ /**
3223
+ * Compile a regular expression from the `state` object returned by the
3224
+ * [parse()](#parse) method.
3225
+ *
3226
+ * ```js
3227
+ * const picomatch = require('picomatch');
3228
+ * const state = picomatch.parse('*.js');
3229
+ * // picomatch.compileRe(state[, options]);
3230
+ *
3231
+ * console.log(picomatch.compileRe(state));
3232
+ * //=> /^(?:(?!\.)(?=.)[^/]*?\.js)$/
3233
+ * ```
3234
+ * @param {Object} `state`
3235
+ * @param {Object} `options`
3236
+ * @param {Boolean} `returnOutput` Intended for implementors, this argument allows you to return the raw output from the parser.
3237
+ * @param {Boolean} `returnState` Adds the state to a `state` property on the returned regex. Useful for implementors and debugging.
3238
+ * @return {RegExp}
3239
+ * @api public
3240
+ */
3241
+ picomatch.compileRe = (state, options, returnOutput = false, returnState = false) => {
3242
+ if (returnOutput === true) return state.output;
3243
+ const opts = options || {};
3244
+ const prepend = opts.contains ? "" : "^";
3245
+ const append = opts.contains ? "" : "$";
3246
+ let source = `${prepend}(?:${state.output})${append}`;
3247
+ if (state && state.negated === true) source = `^(?!${source}).*$`;
3248
+ const regex = picomatch.toRegex(source, options);
3249
+ if (returnState === true) regex.state = state;
3250
+ return regex;
3251
+ };
3252
+ /**
3253
+ * Create a regular expression from a parsed glob pattern.
3254
+ *
3255
+ * ```js
3256
+ * const picomatch = require('picomatch');
3257
+ * // picomatch.makeRe(state[, options]);
3258
+ *
3259
+ * const result = picomatch.makeRe('*.js');
3260
+ * console.log(result);
3261
+ * //=> /^(?:(?!\.)(?=.)[^/]*?\.js)$/
3262
+ * ```
3263
+ * @param {String} `state` The object returned from the `.parse` method.
3264
+ * @param {Object} `options`
3265
+ * @param {Boolean} `returnOutput` Implementors may use this argument to return the compiled output, instead of a regular expression. This is not exposed on the options to prevent end-users from mutating the result.
3266
+ * @param {Boolean} `returnState` Implementors may use this argument to return the state from the parsed glob with the returned regular expression.
3267
+ * @return {RegExp} Returns a regex created from the given pattern.
3268
+ * @api public
3269
+ */
3270
+ picomatch.makeRe = (input, options = {}, returnOutput = false, returnState = false) => {
3271
+ if (!input || typeof input !== "string") throw new TypeError("Expected a non-empty string");
3272
+ let parsed = {
3273
+ negated: false,
3274
+ fastpaths: true
3275
+ };
3276
+ if (options.fastpaths !== false && (input[0] === "." || input[0] === "*")) parsed.output = parse.fastpaths(input, options);
3277
+ if (!parsed.output) parsed = parse(input, options);
3278
+ return picomatch.compileRe(parsed, options, returnOutput, returnState);
3279
+ };
3280
+ /**
3281
+ * Create a regular expression from the given regex source string.
3282
+ *
3283
+ * ```js
3284
+ * const picomatch = require('picomatch');
3285
+ * // picomatch.toRegex(source[, options]);
3286
+ *
3287
+ * const { output } = picomatch.parse('*.js');
3288
+ * console.log(picomatch.toRegex(output));
3289
+ * //=> /^(?:(?!\.)(?=.)[^/]*?\.js)$/
3290
+ * ```
3291
+ * @param {String} `source` Regular expression source string.
3292
+ * @param {Object} `options`
3293
+ * @return {RegExp}
3294
+ * @api public
3295
+ */
3296
+ picomatch.toRegex = (source, options) => {
3297
+ try {
3298
+ const opts = options || {};
3299
+ return new RegExp(source, opts.flags || (opts.nocase ? "i" : ""));
3300
+ } catch (err) {
3301
+ if (options && options.debug === true) throw err;
3302
+ return /$^/;
3303
+ }
3304
+ };
3305
+ /**
3306
+ * Picomatch constants.
3307
+ * @return {Object}
3308
+ */
3309
+ picomatch.constants = constants;
3310
+ /**
3311
+ * Expose "picomatch"
3312
+ */
3313
+ module.exports = picomatch;
3314
+ }));
3315
+ //#endregion
3316
+ //#region src/lint-cli/lib/files/constants.ts
3317
+ var import_picomatch = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((exports, module) => {
3318
+ const pico = require_picomatch$1();
3319
+ const utils = require_utils();
3320
+ function picomatch(glob, options, returnState = false) {
3321
+ if (options && (options.windows === null || options.windows === void 0)) options = {
3322
+ ...options,
3323
+ windows: utils.isWindows()
3324
+ };
3325
+ return pico(glob, options, returnState);
3326
+ }
3327
+ Object.assign(picomatch, pico);
3328
+ module.exports = picomatch;
3329
+ })))(), 1);
3330
+ /** Matches a flat-config entry-point basename (`eslint.config.*`). */
3331
+ const ESLINT_CONFIG_FILE_PATTERN = /^eslint\.config\./;
3332
+ /**
3333
+ * Glob patterns whose modification invalidates every ESLint cache. A bare
3334
+ * `*` never crosses a path separator, so single-segment patterns (for example
3335
+ * `*.config.*` or the lockfiles) only match root-level files, while
3336
+ * `**` patterns match at any depth.
3337
+ */
3338
+ const CACHE_BUST_PATTERNS = [
3339
+ "eslint.config.*",
3340
+ "*.config.*",
3341
+ "**/tsconfig*.json",
3342
+ ".oxlintrc*",
3343
+ ".prettierrc*",
3344
+ "pnpm-lock.yaml",
3345
+ "package-lock.json",
3346
+ "yarn.lock",
3347
+ "bun.lock",
3348
+ "bun.lockb"
3349
+ ];
3350
+ //#endregion
3351
+ //#region src/lint-cli/lib/files/collect.ts
3352
+ const ANCESTOR_BUST_PATTERNS = CACHE_BUST_PATTERNS.map((pattern) => {
3353
+ return pattern.startsWith("**/") ? pattern.slice(3) : pattern;
3354
+ });
3355
+ const IGNORED_WALK_DIRECTORIES = /* @__PURE__ */ new Set([
3356
+ ".git",
3357
+ ".next",
3358
+ ".turbo",
3359
+ "build",
3360
+ "coverage",
3361
+ "dist",
3362
+ "node_modules",
3363
+ "out"
3364
+ ]);
3365
+ const LINTABLE_EXTENSION_SET = new Set(GLOB_LINTABLE_EXTENSIONS.map((extension) => `.${extension}`));
3366
+ const TYPE_AWARE_EXTENSION_SET = new Set(GLOB_SRC_EXTENSIONS.map((extension) => `.${extension}`));
3367
+ /**
3368
+ * Collect every file list a run needs from a single whole-project
3369
+ * `git ls-files` (honouring `.gitignore`), rather than re-spawning git for each
3370
+ * pass. The one scan feeds three filters: the cache-bust set (config files,
3371
+ * tsconfigs, lockfiles — always whole-project), the lintable files restricted
3372
+ * to `targets`, and their type-aware subset.
3373
+ *
3374
+ * @param cwd - The project root to scan.
3375
+ * @param targets - The lint target paths that restrict the lintable set.
3376
+ * @returns The cache-bust, lintable and type-aware file lists.
3377
+ */
3378
+ function collectRepoFiles(cwd, targets) {
3379
+ const relatives = listFiles(cwd, ["."]);
3380
+ const isBustFile = (0, import_picomatch.default)([...CACHE_BUST_PATTERNS], { dot: true });
3381
+ const normalizedTargets = targets.map((target) => normalizeTarget(target, cwd));
3382
+ const targetsOutsideCwd = normalizedTargets.some((target) => isOutsideCwd(target));
3383
+ const isWithinTargets = matchTargets(normalizedTargets);
3384
+ const bustFiles = collectAncestorBustFiles(cwd);
3385
+ const lintable = [];
3386
+ const typeAware = [];
3387
+ for (const relative of relatives) {
3388
+ if (isBustFile(relative)) bustFiles.push(path.resolve(cwd, relative));
3389
+ if (isWithinTargets(relative) && hasLintableExtension(relative)) {
3390
+ const absolute = path.resolve(cwd, relative);
3391
+ lintable.push(absolute);
3392
+ if (isTypeAwareFile(relative)) typeAware.push(absolute);
3393
+ }
3394
+ }
3395
+ return {
3396
+ bustFiles,
3397
+ configFiles: bustFiles.filter(isConfigEntryPoint),
3398
+ lintable,
3399
+ targetsOutsideCwd,
3400
+ typeAware
3401
+ };
3402
+ }
3403
+ /**
3404
+ * Drop the files ESLint declines to lint from the *target* lists, leaving the
3405
+ * rest of the listing untouched.
3406
+ *
3407
+ * Only the `lintable` and `typeAware` lists are lint targets. The `bustFiles`
3408
+ * and `configFiles` lists are whole-project cache-bust inputs — a tsconfig or
3409
+ * lockfile is never linted, so filtering them by "would ESLint lint this" would
3410
+ * empty them. Any list added to {@link RepoFiles} later has to make that same
3411
+ * choice here, which is why this lives beside the type rather than at the call
3412
+ * site.
3413
+ *
3414
+ * @param files - The collected repository files.
3415
+ * @param ignored - The ignored set from `resolveIgnoredFiles` (normalized keys).
3416
+ * @returns The listing with its target lists filtered.
3417
+ */
3418
+ function withoutIgnored(files, ignored) {
3419
+ return {
3420
+ ...files,
3421
+ lintable: retained(files.lintable, ignored),
3422
+ typeAware: retained(files.typeAware, ignored)
3423
+ };
3424
+ }
3425
+ /**
3426
+ * Restrict explicit lint targets to the ones oxlint could lint. Oxlint only
3427
+ * handles the TS/JS family, and exits non-zero with "No files found to lint"
3428
+ * when every path it was handed resolves to nothing — so a hook run over a
3429
+ * `package.json`-only change would fail on a file oxlint was never going to
3430
+ * lint. A path is kept when its extension is type-aware, when it has no
3431
+ * extension, or when it is an existing directory (either may hold TS/JS files).
3432
+ *
3433
+ * Surviving targets are then dropped when oxlint's own ignore matching would
3434
+ * skip them (see {@link gitIgnoredTargets}): a hook stages a `.gitignore`d yet
3435
+ * git-tracked file (a committed generated `*.d.ts`, say), which passes the
3436
+ * extension test but is the exact all-ignored set oxlint exits non-zero on.
3437
+ *
3438
+ * @param cwd - The working directory to resolve targets against.
3439
+ * @param targets - The explicit lint target paths.
3440
+ * @returns The subset oxlint should receive.
3441
+ */
3442
+ function oxlintTargets(cwd, targets) {
3443
+ const candidates = targets.filter((target) => {
3444
+ const extension = path.extname(target).toLowerCase();
3445
+ if (extension === "" || TYPE_AWARE_EXTENSION_SET.has(extension)) return true;
3446
+ try {
3447
+ return fs.statSync(path.resolve(cwd, target)).isDirectory();
3448
+ } catch {
3449
+ return false;
3450
+ }
3451
+ });
3452
+ if (candidates.length === 0) return candidates;
3453
+ const ignored = gitIgnoredTargets(cwd, candidates);
3454
+ return candidates.filter((target) => !ignored.has(target));
3455
+ }
3456
+ /**
3457
+ * Whether an absolute path is a flat-config entry point.
3458
+ *
3459
+ * @param file - The absolute path to test.
3460
+ * @returns True when the basename matches `eslint.config.*`.
3461
+ */
3462
+ function isConfigEntryPoint(file) {
3463
+ return ESLINT_CONFIG_FILE_PATTERN.test(path.basename(file));
3464
+ }
3465
+ /**
3466
+ * Scan the single-segment cache-bust candidates in each directory from `cwd`'s
3467
+ * parent up to and including the workspace root. A sub-package `git ls-files`
3468
+ * only lists files under `cwd`, so a hoisted root lockfile, tsconfig or ESLint
3469
+ * config change would otherwise never bust the caches. Returns absolute paths
3470
+ * (fine for mtime comparison); empty when `cwd` is itself the root.
3471
+ *
3472
+ * @param cwd - The project (sub-package) root to walk up from.
3473
+ * @returns Absolute paths of the ancestor cache-bust files.
3474
+ */
3475
+ function collectAncestorBustFiles(cwd) {
3476
+ const root = findWorkspaceRoot(cwd);
3477
+ if (root === cwd) return [];
3478
+ const isBustName = (0, import_picomatch.default)([...ANCESTOR_BUST_PATTERNS], { dot: true });
3479
+ const found = [];
3480
+ let current = path.dirname(cwd);
3481
+ for (;;) {
3482
+ let entries = [];
3483
+ try {
3484
+ entries = fs.readdirSync(current, { withFileTypes: true });
3485
+ } catch {
3486
+ entries = [];
3487
+ }
3488
+ for (const entry of entries) if (entry.isFile() && isBustName(entry.name)) found.push(path.join(current, entry.name));
3489
+ const parent = path.dirname(current);
3490
+ if (current === root || parent === current) break;
3491
+ current = parent;
3492
+ }
3493
+ return found;
3494
+ }
3495
+ /**
3496
+ * Reduce a raw target to the cwd-relative posix form the listing is keyed by.
3497
+ * Absolute targets (including Windows drive paths) are relativized against
3498
+ * `cwd`; `./` prefixes and trailing slashes are stripped. A target that
3499
+ * resolves to `cwd` itself becomes `""` (match-all). Targets outside `cwd` keep
3500
+ * their `..`-prefixed relative form so {@link isOutsideCwd} can flag them.
3501
+ *
3502
+ * @param target - The raw lint target path.
3503
+ * @param cwd - The working directory to relativize against.
3504
+ * @returns The normalized cwd-relative posix target.
3505
+ */
3506
+ function normalizeTarget(target, cwd) {
3507
+ let value = path.isAbsolute(target) ? toPosix(path.relative(cwd, target)) : toPosix(target);
3508
+ if (value.startsWith("./")) value = value.slice(2);
3509
+ while (value.endsWith("/")) value = value.slice(0, -1);
3510
+ return value;
3511
+ }
3512
+ /**
3513
+ * Whether a normalized target lies outside `cwd`. A cwd-relative listing never
3514
+ * starts with `..`, so any `..`-prefixed target can never match — its files are
3515
+ * invisible to the dirty count.
3516
+ *
3517
+ * @param normalizedTarget - A target already run through {@link normalizeTarget}.
3518
+ * @returns True when the target escapes `cwd`.
3519
+ */
3520
+ function isOutsideCwd(normalizedTarget) {
3521
+ return normalizedTarget === ".." || normalizedTarget.startsWith("../");
3522
+ }
3523
+ /**
3524
+ * Build a predicate for git-pathspec-style target membership: `.` matches
3525
+ * everything, otherwise a relative path matches when it equals a target or sits
3526
+ * beneath one. Faithful to `git ls-files -- <target>` for the plain directory
3527
+ * and file targets consumers pass.
3528
+ *
3529
+ * @param normalized - The lint target paths, already normalized against cwd.
3530
+ * @returns A predicate testing whether a relative posix path is a target.
3531
+ */
3532
+ function matchTargets(normalized) {
3533
+ if (normalized.some((target) => target === "" || target === ".")) return () => true;
3534
+ return (relative) => {
3535
+ return normalized.some((target) => relative === target || relative.startsWith(`${target}/`));
3536
+ };
3537
+ }
3538
+ function isTypeAwareFile(filePath) {
3539
+ return TYPE_AWARE_EXTENSION_SET.has(path.extname(filePath).toLowerCase());
3540
+ }
3541
+ function hasLintableExtension(filePath) {
3542
+ return LINTABLE_EXTENSION_SET.has(path.extname(filePath).toLowerCase());
3543
+ }
3544
+ function gitListFiles(cwd, pathSpecs) {
3545
+ try {
3546
+ return execFileSync("git", [
3547
+ "ls-files",
3548
+ "--cached",
3549
+ "--others",
3550
+ "--exclude-standard",
3551
+ "--",
3552
+ ...pathSpecs
3553
+ ], {
3554
+ cwd,
3555
+ encoding: "utf8",
3556
+ maxBuffer: 64 * 1024 * 1024
3557
+ }).split("\n").filter((line) => line.length > 0);
3558
+ } catch {
3559
+ return;
3560
+ }
3561
+ }
3562
+ function walkDirectory(root, current, accumulator) {
3563
+ let entries;
3564
+ try {
3565
+ entries = fs.readdirSync(current, { withFileTypes: true });
3566
+ } catch {
3567
+ return;
3568
+ }
3569
+ for (const entry of entries) {
3570
+ const entryPath = path.join(current, entry.name);
3571
+ if (entry.isDirectory()) {
3572
+ if (!IGNORED_WALK_DIRECTORIES.has(entry.name)) walkDirectory(root, entryPath, accumulator);
3573
+ continue;
3574
+ }
3575
+ if (entry.isFile()) accumulator.push(toPosix(path.relative(root, entryPath)));
3576
+ }
3577
+ }
3578
+ function walkFallback(cwd, targets) {
3579
+ const files = [];
3580
+ for (const target of targets) {
3581
+ const absolute = path.resolve(cwd, target);
3582
+ let stat;
3583
+ try {
3584
+ stat = fs.statSync(absolute);
3585
+ } catch {
3586
+ continue;
3587
+ }
3588
+ if (stat.isDirectory()) walkDirectory(cwd, absolute, files);
3589
+ else if (stat.isFile()) files.push(toPosix(path.relative(cwd, absolute)));
3590
+ }
3591
+ return files;
3592
+ }
3593
+ function listFiles(cwd, targets) {
3594
+ return gitListFiles(cwd, targets) ?? walkFallback(cwd, targets);
3595
+ }
3596
+ function retained(files, ignored) {
3597
+ return files.filter((file) => !ignored.has(normalizePath(file)));
3598
+ }
3599
+ /**
3600
+ * The subset of `targets` oxlint's own ignore matching would skip, echoed back
3601
+ * verbatim by `git check-ignore`. Empty when git cannot answer (outside a repo,
3602
+ * git absent) or when nothing matches — either way the caller drops nothing,
3603
+ * the behaviour before this existed.
3604
+ *
3605
+ * Oxlint matches `.gitignore` rules through the `ignore` crate, which never
3606
+ * consults git's index — so a file that is `.gitignore`d yet git-*tracked* is
3607
+ * ignored by oxlint all the same. `--no-index` is the flag that makes git
3608
+ * agree: without it, git reports a tracked file as not-ignored and we keep
3609
+ * handing oxlint a path it refuses. `check-ignore` exits 1 (printing nothing)
3610
+ * when no path matches, which `execFileSync` raises as a throw —
3611
+ * indistinguishable here from git being absent, and correctly so: both mean
3612
+ * "drop nothing"..
3613
+ *
3614
+ * @param cwd - The working directory to resolve targets against.
3615
+ * @param targets - The candidate target paths, passed verbatim on stdin.
3616
+ * @returns The ignored subset, matched against the original target strings.
3617
+ */
3618
+ function gitIgnoredTargets(cwd, targets) {
3619
+ try {
3620
+ const output = execFileSync("git", [
3621
+ "check-ignore",
3622
+ "--no-index",
3623
+ "--stdin"
3624
+ ], {
3625
+ cwd,
3626
+ encoding: "utf8",
3627
+ input: targets.join("\n"),
3628
+ maxBuffer: 64 * 1024 * 1024,
3629
+ stdio: [
3630
+ "pipe",
3631
+ "pipe",
3632
+ "ignore"
3633
+ ]
3634
+ });
3635
+ return new Set(output.split("\n").map((line) => line.trim()).filter(Boolean));
3636
+ } catch {
3637
+ return /* @__PURE__ */ new Set();
3638
+ }
3639
+ }
3640
+ //#endregion
3641
+ //#region src/lint-cli/lib/exec/eslint-install.ts
3642
+ /**
3643
+ * Locating the ESLint installation a run's config is resolved against.
3644
+ *
3645
+ * The runner and its ignore helper both need it and must agree: the helper
3646
+ * serializes the config's match patterns out of one ESLint's config loader, and
3647
+ * the runner evaluates them with that same ESLint's matcher. Resolving them
3648
+ * separately would let the two drift onto different installations, and a
3649
+ * matcher that disagrees with the one that produced the patterns is exactly the
3650
+ * failure this feature cannot have.
3651
+ */
3652
+ /**
3653
+ * A synthetic basename for `createRequire`, which resolves relative to a file
3654
+ * rather than a directory. Spelled so it can never collide with a real consumer
3655
+ * module.
3656
+ */
3657
+ const RESOLVE_ANCHOR = "__isentinel-lint__.js";
3658
+ /**
3659
+ * Resolve the ESLint the consumer's config will be linted with: their own,
3660
+ * resolved from `cwd`, falling back to the one resolvable from this file (the
3661
+ * hoisted peer dependency) when `cwd` has no `node_modules` of its own — the
3662
+ * case in the fixture-based tests.
3663
+ *
3664
+ * @param cwd - The consumer project root.
3665
+ * @returns The package root and a require anchored in it.
3666
+ * @throws {Error} When ESLint cannot be resolved from either location.
3667
+ */
3668
+ function resolveEslintInstall(cwd) {
3669
+ let packageJson;
3670
+ try {
3671
+ packageJson = createRequire(path.join(cwd, RESOLVE_ANCHOR)).resolve("eslint/package.json");
3672
+ } catch {
3673
+ packageJson = createRequire(import.meta.url).resolve("eslint/package.json");
3674
+ }
3675
+ return {
3676
+ requireFrom: createRequire(packageJson),
3677
+ root: path.dirname(packageJson)
3678
+ };
3679
+ }
3680
+ //#endregion
3681
+ //#region src/lint-cli/lib/files/ignored-predicate.ts
3682
+ /**
3683
+ * The serialized form of a resolved config's match patterns, and the in-process
3684
+ * evaluation of it. Produced by {@link file://./ignored-child.ts}, consumed by
3685
+ * {@link file://./ignored.ts}.
3686
+ *
3687
+ * The patterns are evaluated with the same `@eslint/config-array` the
3688
+ * consumer's ESLint uses, not a re-implementation: the matcher is the part
3689
+ * that has to agree exactly, since a file wrongly classified as ignored is
3690
+ * dropped from the dirty count and can skip a typed pass that had work to do.
3691
+ */
3692
+ /**
3693
+ * Classify lint targets against a stored payload.
3694
+ *
3695
+ * An `"answers"` payload only knows the targets it was computed from, so
3696
+ * anything else is reported not-ignored — the safe direction, which over-counts
3697
+ * dirty files rather than skipping work. A `"predicate"` payload knows the
3698
+ * config itself and classifies any path, including files added since it was
3699
+ * stored.
3700
+ *
3701
+ * A path counts as ignored when its status is anything but `"matched"`: a file
3702
+ * no config's `files` covers is `"unconfigured"` rather than `"ignored"`, and
3703
+ * ESLint declines to lint it just the same.
3704
+ *
3705
+ * @param cwd - The consumer project root, used to resolve the matcher.
3706
+ * @param payload - The stored payload.
3707
+ * @param targets - The absolute target paths to classify.
3708
+ * @returns The ignored subset of `targets`, or `undefined` when the payload
3709
+ * could not be evaluated.
3710
+ */
3711
+ function classifyIgnored(cwd, payload, targets) {
3712
+ if (payload.mode === "answers") {
3713
+ const wanted = new Set(targets);
3714
+ return payload.ignored.filter((file) => wanted.has(file));
3715
+ }
3716
+ try {
3717
+ const { ConfigArray } = loadConfigArrayModule(cwd);
3718
+ const configArray = new ConfigArray(payload.entries, { basePath: payload.basePath });
3719
+ configArray.normalizeSync();
3720
+ return targets.filter((target) => configArray.getConfigStatus(target) !== "matched");
3721
+ } catch {
3722
+ return;
3723
+ }
3724
+ }
3725
+ /**
3726
+ * Whether a required module exposes the `ConfigArray` constructor this module
3727
+ * uses.
3728
+ *
3729
+ * @param value - The required module's exports.
3730
+ * @returns Whether the exports carry a `ConfigArray` constructor.
3731
+ */
3732
+ function isConfigArrayModule(value) {
3733
+ return isRecord(value) && typeof value["ConfigArray"] === "function";
3734
+ }
3735
+ /**
3736
+ * Load the matcher out of the consumer's own ESLint installation, so the
3737
+ * patterns are evaluated by the same version that produced them.
3738
+ *
3739
+ * @param cwd - The consumer project root.
3740
+ * @returns The `@eslint/config-array` module namespace.
3741
+ * @throws {Error} When neither ESLint nor its config-array can be resolved.
3742
+ */
3743
+ function loadConfigArrayModule(cwd) {
3744
+ const required = resolveEslintInstall(cwd).requireFrom("@eslint/config-array");
3745
+ if (!isConfigArrayModule(required)) throw new Error("@eslint/config-array did not export a ConfigArray constructor");
3746
+ return required;
3747
+ }
3748
+ //#endregion
3749
+ //#region src/lint-cli/lib/files/ignored.ts
3750
+ /**
3751
+ * Whether a value is a serializable predicate-entry array. The child writes it,
3752
+ * so its element shape is trusted to its array-ness — a wrong element throws
3753
+ * where the payload is used, and that degrades to no filtering all the same.
3754
+ *
3755
+ * @param value - The value to test.
3756
+ * @returns Whether the value is an array of {@link PredicateEntry}.
3757
+ */
3758
+ function isPredicateEntryArray(value) {
3759
+ return Array.isArray(value);
3760
+ }
3761
+ /** Reused for every miss, so callers never allocate on the no-op path. */
3762
+ const EMPTY = /* @__PURE__ */ new Set();
3763
+ /**
3764
+ * Resolve the persisted ignore-set file for a config variant, keyed by the same
3765
+ * variant as the config hash it stores.
3766
+ *
3767
+ * @param run - The run context.
3768
+ * @returns The absolute path to the stored ignore-set file.
3769
+ */
3770
+ function ignoredStatePath(run) {
3771
+ return statePath(run.cwd, "ignored", run.key);
3772
+ }
3773
+ /**
3774
+ * The lint targets ESLint declines to lint, as {@link normalizePath} keys, so
3775
+ * the runner can drop them from its dirty count.
3776
+ *
3777
+ * The file lists come from `git ls-files` filtered by extension and know
3778
+ * nothing of the config's own `ignores`. ESLint writes no cache entry for a
3779
+ * file it never lints, so every consumer-ignored file is reported dirty on
3780
+ * every run, forever — which floors the type-aware pass's dirty count above
3781
+ * zero and makes its auto-skip unreachable.
3782
+ *
3783
+ * Asking ESLint itself is the only correct answer, but loading a consumer's
3784
+ * flat config costs several seconds. It is therefore memoised against the same
3785
+ * config hash that drives the cache-drift bust: the answer can only change when
3786
+ * the resolved config changes. That recompute is a blocking cost on the run
3787
+ * that pays it, but it is a run whose caches the drift bust just deleted, so
3788
+ * every file re-lints anyway — and the ignore set still sizes that re-lint's
3789
+ * workers, which is why it is computed then rather than deferred.
3790
+ *
3791
+ * What is memoised is the config's *patterns*, not its answers about one
3792
+ * target list. Storing answers made the memo a partial function over a file
3793
+ * list that kept moving: every file added after the last config change was
3794
+ * absent from the set, counted not-ignored, and floored the typed pass's dirty
3795
+ * count above zero forever. Patterns depend on the config alone, which is what
3796
+ * the key already says, so new files classify with no helper spawn at all.
3797
+ *
3798
+ * The answers are still stored, but now as a cache *derived* from the patterns
3799
+ * rather than as the source of truth: a target the cache does not cover is
3800
+ * matched against the patterns rather than assumed not-ignored, which is the
3801
+ * whole difference. It exists because that match costs ~300µs per path, so a
3802
+ * few thousand targets would otherwise add a second to every run — the cost the
3803
+ * memo exists to avoid, charged a little at a time.
3804
+ *
3805
+ * A config whose `files`/`ignores` hold function matchers cannot be
3806
+ * serialized; the helper falls back to answering per target, and that payload
3807
+ * keeps the old residual — targets absent from it read as not-ignored, which
3808
+ * over-counts dirty files rather than skipping work.
3809
+ *
3810
+ * @param run - The run context.
3811
+ * @param configHash - The config hash for this run, or `undefined` when
3812
+ * unavailable (the memo is then unusable and no filtering happens).
3813
+ * @param targets - Every lintable target, absolute (see `RepoFiles.lintable`).
3814
+ * @returns The ignored subset of `targets`, or an empty set when unavailable.
3815
+ */
3816
+ function resolveIgnoredFiles(run, configHash, targets) {
3817
+ if (configHash === void 0 || targets.length === 0) return EMPTY;
3818
+ const { cwd, mutate } = run;
3819
+ const stateFile = ignoredStatePath(run);
3820
+ const stored = readState(stateFile);
3821
+ const fresh = stored?.hash === configHash ? stored : void 0;
3822
+ let payload = fresh?.payload;
3823
+ if (payload === void 0) {
3824
+ if (!mutate) return EMPTY;
3825
+ payload = queryIgnoredFiles(cwd, targets);
3826
+ if (payload === void 0) return EMPTY;
3827
+ }
3828
+ const classified = classifyTargets(cwd, payload, targets, fresh?.classified);
3829
+ if (classified === void 0) return EMPTY;
3830
+ if (mutate && !sameClassification(classified, fresh?.classified)) writeState(stateFile, {
3831
+ classified,
3832
+ hash: configHash,
3833
+ payload
3834
+ });
3835
+ return new Set(classified.ignored);
3836
+ }
3837
+ /**
3838
+ * Split the targets by whether ESLint would lint them, asking the payload only
3839
+ * about the ones the stored classification does not already cover — which after
3840
+ * the first run means only the files added since it.
3841
+ *
3842
+ * The result covers exactly the current targets, so storing it also drops the
3843
+ * entries for files that have gone away.
3844
+ *
3845
+ * @param cwd - The consumer project root.
3846
+ * @param payload - The patterns (or answers) to classify against.
3847
+ * @param targets - The absolute target paths this run cares about.
3848
+ * @param cached - The stored classification, when one applies to this payload.
3849
+ * @returns The split, or `undefined` when the payload could not be evaluated.
3850
+ */
3851
+ function classifyTargets(cwd, payload, targets, cached) {
3852
+ const known = /* @__PURE__ */ new Map();
3853
+ const cachedIgnored = cached?.ignored ?? [];
3854
+ const cachedLinted = cached?.linted ?? [];
3855
+ for (const file of cachedIgnored) known.set(file, true);
3856
+ for (const file of cachedLinted) known.set(file, false);
3857
+ const byKey = new Map(targets.map((target) => [normalizePath(target), target]));
3858
+ const unknown = [...byKey].filter(([key]) => !known.has(key));
3859
+ if (unknown.length > 0) {
3860
+ const ignored = classifyIgnored(cwd, payload, unknown.map(([, target]) => target));
3861
+ if (ignored === void 0) return;
3862
+ const ignoredKeys = new Set(ignored.map((file) => normalizePath(file)));
3863
+ for (const [key] of unknown) known.set(key, ignoredKeys.has(key));
3864
+ }
3865
+ const classified = {
3866
+ ignored: [],
3867
+ linted: []
3868
+ };
3869
+ for (const key of byKey.keys()) (known.get(key) === true ? classified.ignored : classified.linted).push(key);
3870
+ return classified;
3871
+ }
3872
+ /**
3873
+ * Whether a classification is the one already on disk, so an unchanged run can
3874
+ * skip rewriting it.
3875
+ *
3876
+ * @param classified - The classification this run computed.
3877
+ * @param cached - The stored classification, when one applies.
3878
+ * @returns True when the two cover the same targets.
3879
+ */
3880
+ function sameClassification(classified, cached) {
3881
+ return cached !== void 0 && cached.ignored.length === classified.ignored.length && cached.linted.length === classified.linted.length;
3882
+ }
3883
+ /**
3884
+ * Spawn the helper and read back its payload. Every failure mode (no
3885
+ * resolvable ESLint, a config that throws, a malformed result) degrades to
3886
+ * `undefined`, which the caller treats as "no filtering" — the behaviour
3887
+ * before this existed.
3888
+ *
3889
+ * The result comes back through a scratch file rather than stdout: loading a
3890
+ * consumer's config evaluates their plugins, and anything one of those prints
3891
+ * would land in the middle of the JSON.
3892
+ *
3893
+ * @param cwd - The consumer project root.
3894
+ * @param targets - The target files the fallback path classifies.
3895
+ * @returns The helper's payload, or `undefined` when the query failed.
3896
+ */
3897
+ function queryIgnoredFiles(cwd, targets) {
3898
+ const outFile = path.join(os.tmpdir(), `isentinel-lint-ignored-${process$1.pid}.json`);
3899
+ try {
3900
+ execFileSync(process$1.execPath, [
3901
+ resolveIgnoredHelper(),
3902
+ cwd,
3903
+ outFile
3904
+ ], {
3905
+ cwd,
3906
+ input: JSON.stringify(targets),
3907
+ maxBuffer: 64 * 1024 * 1024,
3908
+ stdio: [
3909
+ "pipe",
3910
+ "ignore",
3911
+ "ignore"
3912
+ ]
3913
+ });
3914
+ const parsed = JSON.parse(fs.readFileSync(outFile, "utf8"));
3915
+ if (!isRecord(parsed)) return;
3916
+ const { basePath, entries, ignored, mode } = parsed;
3917
+ if (mode === "answers" && isStringArray(ignored)) return {
3918
+ ignored,
3919
+ mode
3920
+ };
3921
+ if (mode === "predicate" && typeof basePath === "string" && isPredicateEntryArray(entries)) return {
3922
+ basePath,
3923
+ entries,
3924
+ mode
3925
+ };
3926
+ return;
3927
+ } catch {
3928
+ return;
3929
+ } finally {
3930
+ fs.rmSync(outFile, { force: true });
3931
+ }
3932
+ }
3933
+ //#endregion
3934
+ //#region src/hybrid-status.ts
3935
+ /**
3936
+ * Absolute path to the hybrid-status file for a project root.
3937
+ *
3938
+ * @param cwd - The project root.
3939
+ * @returns The absolute status-file path.
3940
+ */
3941
+ function hybridStatusPath(cwd) {
3942
+ return statePath(cwd, "hybrid-status");
3943
+ }
3944
+ /**
3945
+ * Read the persisted hybrid status, or `undefined` when the file is missing,
3946
+ * unreadable or malformed (the CLI then treats the status as unknown).
3947
+ *
3948
+ * @param cwd - The project root.
3949
+ * @returns The parsed status, or `undefined`.
3950
+ */
3951
+ function readHybridStatus(cwd) {
3952
+ const stored = readState(hybridStatusPath(cwd));
3953
+ return typeof stored?.oxlint === "boolean" ? stored : void 0;
3954
+ }
3955
+ /**
3956
+ * Passively record whether the resolved ESLint config runs in hybrid mode.
3957
+ * Called from the factory on every config evaluation, so an unchanged status is
3958
+ * touched rather than rewritten: the file must not churn as editors and both
3959
+ * lint passes re-evaluate the config, yet its mtime must stay ahead of the
3960
+ * config's or the CLI re-runs its ~3s probe every lint. Every failure is
3961
+ * swallowed — config evaluation must never throw for this, and the CLI treats a
3962
+ * missing or stale file as "unknown" and re-probes.
3963
+ *
3964
+ * Skipped entirely when `node_modules` is absent (nothing installed, so no
3965
+ * cache home and no CLI to read it).
3966
+ *
3967
+ * @param cwd - The project root.
3968
+ * @param oxlint - Whether the config enabled hybrid mode.
3969
+ */
3970
+ function writeHybridStatus(cwd, oxlint) {
3971
+ let installed;
3972
+ try {
3973
+ installed = fs.existsSync(path.resolve(cwd, "node_modules"));
3974
+ } catch {
3975
+ return;
3976
+ }
3977
+ if (!installed) return;
3978
+ const filePath = hybridStatusPath(cwd);
3979
+ if (readHybridStatus(cwd)?.oxlint === oxlint) {
3980
+ touchState(filePath);
3981
+ return;
3982
+ }
3983
+ writeState(filePath, { oxlint });
3984
+ }
3985
+ //#endregion
3986
+ //#region src/lint-cli/lib/hybrid/probe.ts
3987
+ /**
3988
+ * Read the hybrid marker from `eslint --print-config` stdout. Config evaluation
3989
+ * can print non-JSON before the payload (plugin/editor-detection logs), so the
3990
+ * JSON object is isolated (first `{` to last `}`) before parsing. Returns
3991
+ * `undefined` when no JSON object is present or it fails to parse.
3992
+ *
3993
+ * @param stdout - The raw `--print-config` stdout.
3994
+ * @returns The probed status, or `undefined` when unparseable.
3995
+ */
3996
+ function parseHybridPrintConfig(stdout) {
3997
+ const first = stdout.indexOf("{");
3998
+ const last = stdout.lastIndexOf("}");
3999
+ if (first === -1 || last < first) return;
4000
+ try {
4001
+ const config = JSON.parse(stdout.slice(first, last + 1));
4002
+ const settings = isRecord(config) ? config["settings"] : void 0;
4003
+ return { oxlint: isRecord(settings) && settings["isentinel/oxlint"] === true };
4004
+ } catch {
4005
+ return;
4006
+ }
4007
+ }
4008
+ /**
4009
+ * The real prober: spawn the resolved local ESLint with `--print-config` and
4010
+ * read the hybrid marker from the merged `settings`.
4011
+ *
4012
+ * @param cwd - The project root.
4013
+ * @param target - A file whose resolved config carries the marker.
4014
+ * @returns The probed status, or `undefined` on any failure.
4015
+ */
4016
+ function probeHybridConfig(cwd, target) {
4017
+ let binJs;
4018
+ try {
4019
+ binJs = resolveLocalBin("eslint", cwd);
4020
+ } catch {
4021
+ return;
4022
+ }
4023
+ const result = spawnSync(process$1.execPath, [
4024
+ binJs,
4025
+ "--print-config",
4026
+ target
4027
+ ], {
4028
+ cwd,
4029
+ encoding: "utf8",
4030
+ maxBuffer: 64 * 1024 * 1024
4031
+ });
4032
+ if (result.status !== 0 || typeof result.stdout !== "string") return;
4033
+ return parseHybridPrintConfig(result.stdout);
4034
+ }
4035
+ //#endregion
4036
+ //#region src/lint-cli/lib/hybrid/gate.ts
4037
+ /**
4038
+ * The stderr warning emitted when the resolved ESLint config is not hybrid, so
4039
+ * running oxlint too would double-lint every mapped rule. Oxlint is dropped.
4040
+ */
4041
+ const NON_HYBRID_WARNING = "isentinel-lint: the ESLint config does not enable hybrid mode (`oxlint: true` or `oxlint: \"native\"`), so oxlint would re-run rules ESLint already checks. Running ESLint only; enable hybrid mode in your config or pass --oxlint to run oxlint explicitly.\n";
4042
+ /**
4043
+ * The stderr warning emitted when the hybrid status cannot be determined (the
4044
+ * probe failed). The run fails open: both engines run, as before.
4045
+ */
4046
+ const HYBRID_UNKNOWN_WARNING = "isentinel-lint: could not determine whether the ESLint config enables hybrid mode; running both engines.\n";
4047
+ /**
4048
+ * Decide whether the oxlint child runs. When both engines would run (default
4049
+ * mode or `--fix`, i.e. Neither `--eslint` nor `--oxlint`), the ESLint config
4050
+ * must be hybrid or oxlint would double-lint every mapped rule; a non-hybrid
4051
+ * config drops oxlint with a warning. Explicit single-tool runs skip the check
4052
+ * entirely and keep today's behaviour.
4053
+ *
4054
+ * The hybrid status is trusted from the on-disk `hybrid-status` file when it is
4055
+ * at least as new as the ESLint config; otherwise the resolved config is
4056
+ * actively probed (unless `mutate` is false, e.g. `--print`, which never probes
4057
+ * and assumes hybrid). A probe failure fails open: both engines run.
4058
+ *
4059
+ * @param run - The run context.
4060
+ * @param input - The assembled plan-phase inputs.
4061
+ * @param probe - The config prober (injected in tests).
4062
+ * @returns The oxlint run decision.
4063
+ */
4064
+ function resolveOxlintRun(run, input, probe = probeHybridConfig) {
4065
+ if (!input.runOxlint || !input.runEslint) return {
4066
+ reason: void 0,
4067
+ run: input.runOxlint
4068
+ };
4069
+ const status = resolveHybridStatus(run, input, probe);
4070
+ if (status === void 0) return {
4071
+ reason: HYBRID_UNKNOWN_WARNING,
4072
+ run: true
4073
+ };
4074
+ if (!status.oxlint) return {
4075
+ reason: NON_HYBRID_WARNING,
4076
+ run: false
4077
+ };
4078
+ return {
4079
+ reason: void 0,
4080
+ run: true
4081
+ };
4082
+ }
4083
+ /**
4084
+ * Read the cached hybrid status only when it is at least as new as the ESLint
4085
+ * config; a stale or missing file yields `undefined` (the caller then probes).
4086
+ *
4087
+ * Known limitation: freshness tracks `eslint.config.*` mtimes only, so toggling
4088
+ * hybrid mode from a module the config *imports* (rather than the config file
4089
+ * itself) is trusted from the cache for one more run before the factory's
4090
+ * passive write corrects it — it self-heals on the next invocation.
4091
+ *
4092
+ * @param cwd - The project root.
4093
+ * @param configMtime - The newest ESLint-config mtime, or `undefined` when none.
4094
+ * @returns The fresh cached status, or `undefined`.
4095
+ */
4096
+ function readFreshHybridStatus(cwd, configMtime) {
4097
+ const status = readHybridStatus(cwd);
4098
+ if (status === void 0) return;
4099
+ if (configMtime === void 0) return status;
4100
+ const statusMtime = maxMtimeMs([hybridStatusPath(cwd)]);
4101
+ if (statusMtime === void 0 || statusMtime < configMtime) return;
4102
+ return status;
4103
+ }
4104
+ /**
4105
+ * Resolve the hybrid status, trusting a fresh cache file and otherwise probing.
4106
+ * Returns `undefined` only when a probe was attempted and failed.
4107
+ *
4108
+ * @param run - The run context.
4109
+ * @param input - The assembled plan-phase inputs.
4110
+ * @param probe - The config prober.
4111
+ * @returns The hybrid status, or `undefined` when a probe failed.
4112
+ */
4113
+ function resolveHybridStatus({ cwd, mutate }, { files }, probe) {
4114
+ const cached = readFreshHybridStatus(cwd, maxMtimeMs(files.configFiles));
4115
+ if (cached !== void 0) return cached;
4116
+ if (!mutate) return { oxlint: true };
4117
+ const target = files.typeAware[0] ?? files.configFiles[0];
4118
+ if (target === void 0) return;
4119
+ const probed = probe(cwd, target);
4120
+ if (probed === void 0) return;
4121
+ writeHybridStatus(cwd, probed.oxlint);
4122
+ return probed;
4123
+ }
4124
+ /**
4125
+ * Derive the worker limits from environment overrides, falling back to the
4126
+ * default files-per-worker and a share of the available parallelism.
4127
+ *
4128
+ * A type-aware worker costs a fixed TypeScript program build plus roughly
4129
+ * 10-20ms per file. The build is not a constant: `projectService` only builds
4130
+ * the projects covering the files a worker was given, so splitting the run
4131
+ * splits the build too — but each split still repeats the per-project floor,
4132
+ * which is what keeps the sweet spot near 300 files per worker rather than
4133
+ * ESLint's syntax-tuned `auto`.
4134
+ *
4135
+ * Locally the cap is a quarter of the CPUs: the fast and typed passes run as
4136
+ * siblings alongside oxlint (and usually an editor), so each pass only gets a
4137
+ * share. In CI the run collapses to a single full pass with nothing to reserve
4138
+ * for, and a 4-core CI runner sweep (3.1k-file repo) measured monotonic
4139
+ * improvement all the way to workers = cores — 200s single-process against 53s
4140
+ * at 4 workers — so CI uses the full parallelism instead.
4141
+ *
4142
+ * @param environment - The environment variables to read overrides from.
4143
+ * @param availableParallelism - The number of available CPUs.
4144
+ * @param ci - Whether the run is in CI (single pass, idle machine).
4145
+ * @returns The resolved worker limits.
4146
+ */
4147
+ function resolveWorkerLimits(environment, availableParallelism, ci) {
4148
+ const filesPerWorker = parsePositiveInteger(environment["FILES_PER_WORKER"]) ?? 300;
4149
+ const explicit = parsePositiveInteger(environment["LINT_MAX_WORKERS"]);
4150
+ const maxWorkers = explicit ?? (ci ? availableParallelism : Math.floor(availableParallelism / 4));
4151
+ return {
4152
+ filesPerWorker,
4153
+ maxWorkers,
4154
+ typedMaxWorkers: explicit ?? Math.min(maxWorkers, 6)
4155
+ };
4156
+ }
4157
+ /**
4158
+ * Resolve the fast pass's files-per-worker, honouring the
4159
+ * `FAST_FILES_PER_WORKER` override. The fast pass lints each file syntactically
4160
+ * in isolation, so its break-even worker size is far higher than the type-aware
4161
+ * pass (see {@link DEFAULT_FAST_FILES_PER_WORKER}).
4162
+ *
4163
+ * @param environment - The environment variables to read the override from.
4164
+ * @returns The resolved fast-pass files-per-worker.
4165
+ */
4166
+ function resolveFastFilesPerWorker(environment) {
4167
+ return parsePositiveInteger(environment["FAST_FILES_PER_WORKER"]) ?? 800;
4168
+ }
4169
+ /**
4170
+ * Compute ESLint's `--concurrency` value. Returns `"off"` when a single worker
4171
+ * (or fewer) would be used, since parallelism only pays off past that point.
4172
+ *
4173
+ * @param input - The dirty count and worker limits.
4174
+ * @returns The worker count, or `"off"` to disable parallelism.
4175
+ */
4176
+ function computeWorkerCount({ dirtyCount, filesPerWorker, maxWorkers }) {
4177
+ if (dirtyCount <= 0 || filesPerWorker <= 0 || maxWorkers <= 0) return "off";
4178
+ const workers = Math.min(Math.ceil(dirtyCount / filesPerWorker), maxWorkers);
4179
+ if (workers < 2) return "off";
4180
+ return workers;
4181
+ }
4182
+ function parsePositiveInteger(value) {
4183
+ return parseBoundedInteger(value, 1);
4184
+ }
4185
+ //#endregion
4186
+ //#region src/lint-cli/lib/plan/passes.ts
4187
+ /**
4188
+ * The syntactic-only fast pass (`ESLINT_TYPE_AWARE=off`, `.eslintcache-fast`).
4189
+ */
4190
+ const FAST_PASS = {
4191
+ cacheFileBase: CACHE_FILE_FAST,
4192
+ filesPerWorker: (_limits, environment) => resolveFastFilesPerWorker(environment),
4193
+ invalidation: "none",
4194
+ label: "fast",
4195
+ typeAwareEnv: "off",
4196
+ typeAwareOnly: false
4197
+ };
4198
+ /** The type-aware pass (`ESLINT_TYPE_AWARE=only`, `.eslintcache-typeaware`). */
4199
+ const TYPED_PASS = {
4200
+ cacheFileBase: CACHE_FILE_TYPE_AWARE,
4201
+ filesPerWorker: (limits) => limits.filesPerWorker,
4202
+ invalidation: "only",
4203
+ label: "typed",
4204
+ typeAwareEnv: "only",
4205
+ typeAwareOnly: true
4206
+ };
4207
+ /** The full-config pass (env unset, `.eslintcache`): CI, `--fix`, `=full`. */
4208
+ const FULL_PASS = {
4209
+ cacheFileBase: CACHE_FILE_DEFAULT,
4210
+ filesPerWorker: (limits) => limits.filesPerWorker,
4211
+ invalidation: "full",
4212
+ label: "eslint",
4213
+ typeAwareEnv: void 0,
4214
+ typeAwareOnly: false
4215
+ };
4216
+ /**
4217
+ * Select the ESLint passes for the resolved mode. An explicit `--type-aware`
4218
+ * always wins: `=full` (and `--fix`) collapse to the single full pass, while
4219
+ * `=off`/`=only` run their one pass even in CI. Only when no mode is given does
4220
+ * CI change the default — collapsing the concurrent two-pass split to one full
4221
+ * pass; a local default run keeps the split (the typed pass may later be
4222
+ * skipped). CI's `--cache-strategy content` is applied by the command composer
4223
+ * to whichever pass runs, independently of this selection.
4224
+ *
4225
+ * @param options - The parsed CLI options.
4226
+ * @param ci - Whether the run is in CI.
4227
+ * @returns The pass descriptors to plan, in run order.
4228
+ */
4229
+ function selectPasses(options, ci) {
4230
+ if (options.fix || options.typeAware === "full") return [FULL_PASS];
4231
+ if (options.typeAware === "off") return [FAST_PASS];
4232
+ if (options.typeAware === "only") return [TYPED_PASS];
4233
+ if (ci) return [FULL_PASS];
4234
+ return [FAST_PASS, TYPED_PASS];
4235
+ }
4236
+ /**
4237
+ * The worker cap for a pass. `invalidation` already records whether a pass
4238
+ * builds a TypeScript program — the fast pass runs no builder precisely because
4239
+ * it builds none — so it doubles as the cap selector rather than restating the
4240
+ * same fact per descriptor.
4241
+ *
4242
+ * @param descriptor - The pass being sized.
4243
+ * @param limits - The shared worker limits.
4244
+ * @returns The worker cap for this pass.
4245
+ */
4246
+ function maxWorkersFor(descriptor, limits) {
4247
+ return descriptor.invalidation === "none" ? limits.maxWorkers : limits.typedMaxWorkers;
4248
+ }
4249
+ //#endregion
4250
+ //#region src/lint-cli/lib/typescript/affected.ts
4251
+ const warned = /* @__PURE__ */ new Set();
4252
+ /**
4253
+ * Compute the set of files whose type-aware lint results may have changed since
4254
+ * the previous run, using TypeScript's builder API. The builder does a native
4255
+ * shape-hash BFS: it recomputes each dependent's emitted-`.d.ts` shape hash and
4256
+ * stops propagating where shapes stabilise, so an implementation-only edit
4257
+ * invalidates nothing downstream while an exported-type change invalidates its
4258
+ * transitive importers. Files that `affectsGlobalScope` invalidate everything.
4259
+ *
4260
+ * Returns `undefined` when the builder path is skipped or fails (no tsconfig,
4261
+ * `typescript` unresolvable, parse/build error) — callers then lint without
4262
+ * invalidation. Never throws.
4263
+ *
4264
+ * @param run - The run context.
4265
+ * @param mode - The active ESLint type-aware mode.
4266
+ * @returns The affected result, or `undefined` when skipped.
4267
+ */
4268
+ function computeAffectedFiles({ key, cwd }, mode) {
4269
+ const ts = loadTypescript(cwd);
4270
+ if (ts === void 0) {
4271
+ warnOnce("typescript is not resolvable; skipping type-aware cache invalidation");
4272
+ return;
4273
+ }
4274
+ const configPath = ts.findConfigFile(cwd, (file) => ts.sys.fileExists(file), "tsconfig.json");
4275
+ if (configPath === void 0) {
4276
+ warnOnce("no tsconfig.json found; skipping type-aware cache invalidation");
4277
+ return;
4278
+ }
4279
+ try {
4280
+ const { entryReadable, projects } = collectProjects(ts, configPath);
4281
+ if (projects.length === 0) {
4282
+ if (entryReadable) warnOnce(`no TypeScript files resolved from ${path.basename(configPath)}; skipping type-aware cache invalidation`);
4283
+ return;
4284
+ }
4285
+ fs.mkdirSync(stateDirectory(cwd), { recursive: true });
4286
+ const affected = /* @__PURE__ */ new Set();
4287
+ let warmProjects = 0;
4288
+ for (const project of projects) {
4289
+ const result = runBuilder({
4290
+ buildInfoPath: builderStatePath(cwd, mode, key, project.id),
4291
+ project,
4292
+ ts
4293
+ });
4294
+ warmProjects += result.firstRun ? 0 : 1;
4295
+ for (const file of result.affected) affected.add(file);
4296
+ }
4297
+ return {
4298
+ affected,
4299
+ firstRun: warmProjects === 0
4300
+ };
4301
+ } catch (err) {
4302
+ warnOnce(`type-aware cache invalidation failed: ${err instanceof Error ? err.message : String(err)}`);
4303
+ return;
4304
+ }
4305
+ }
4306
+ /**
4307
+ * Resolve the builder incremental-state (`.tsbuildinfo`) file for a mode and
4308
+ * config variant.
4309
+ *
4310
+ * The variant key is part of the path because this state is drained
4311
+ * destructively: {@link computeAffectedFiles} consumes the affected set and
4312
+ * advances the buildinfo, and the caller removes those files from *one* cache.
4313
+ * Sharing one buildinfo across variants would let an agent run advance the
4314
+ * state while only its own cache was invalidated; the next human run would
4315
+ * then see an empty affected set with stale entries still in its own warm
4316
+ * cache — and since those files' mtimes never changed, the typed pass would
4317
+ * auto-skip and report stale diagnostics that ESLint's `hashOfConfig` cannot
4318
+ * catch.
4319
+ *
4320
+ * @param cwd - The consumer project root.
4321
+ * @param mode - The active ESLint type-aware mode (never `"off"` here).
4322
+ * @param key - The config-variant key from `resolveCacheKey`.
4323
+ * @param projectId - {@link projectDigest} of the project's tsconfig. Every
4324
+ * project is suffixed, including the entry one: a solution's members each
4325
+ * need their own state, and an unsuffixed special case for the entry config
4326
+ * would make `${key}` and `${key}-${digest}` ambiguous to parse back.
4327
+ * @returns The absolute path to the mode's buildinfo file.
4328
+ */
4329
+ function builderStatePath(cwd, mode, key, projectId) {
4330
+ return statePath(cwd, "tsbuildinfo", mode === "only" ? "typeaware" : "full", key, projectId);
4331
+ }
4332
+ /**
4333
+ * Emit a warning at most once per distinct message. Keyed by message rather
4334
+ * than a single global flag so per-project degradation (one unreadable
4335
+ * reference among many) stays reportable instead of being swallowed by an
4336
+ * earlier, unrelated warning.
4337
+ *
4338
+ * @param message - The warning text, also its dedupe key.
4339
+ */
4340
+ function warnOnce(message) {
4341
+ if (warned.has(message)) return;
4342
+ warned.add(message);
4343
+ process$1.stderr.write(`isentinel-lint: ${message}\n`);
4344
+ }
4345
+ /**
4346
+ * Derive a project's buildinfo discriminator from its canonical config path.
4347
+ * Hashed rather than slugified so nested paths stay short and two configs that
4348
+ * would sanitise to the same slug cannot collide.
4349
+ *
4350
+ * @param canonicalPath - The project's canonical tsconfig path.
4351
+ * @returns A filename-safe hex digest.
4352
+ */
4353
+ function projectDigest(canonicalPath) {
4354
+ return crypto.createHash("sha256").update(canonicalPath).digest("hex").slice(0, 8);
4355
+ }
4356
+ /**
4357
+ * Walk the project-reference graph from an entry tsconfig, collecting every
4358
+ * project that owns files.
4359
+ *
4360
+ * `parseJsonConfigFileContent` does not follow `references`, so a
4361
+ * solution-style tsconfig (`files: []`, `include: []`) resolves to zero file
4362
+ * names on its own. Recursing gives referenced-project consumers real
4363
+ * cross-file invalidation instead of a silent no-op. References nest — a
4364
+ * referenced project may itself be solution-style — so this recurses rather
4365
+ * than reading one level, and the visited set guards against reference cycles
4366
+ * and diamond graphs.
4367
+ *
4368
+ * A referenced config that cannot be read is warned about and skipped rather
4369
+ * than failing the whole walk: one broken reference should degrade invalidation
4370
+ * for its own files, not for every sibling project.
4371
+ *
4372
+ * @param ts - The resolved TypeScript module.
4373
+ * @param entryPath - The tsconfig to start from.
4374
+ * @returns The file-owning projects and whether the entry config parsed.
4375
+ */
4376
+ function collectProjects(ts, entryPath) {
4377
+ const projects = [];
4378
+ const seen = /* @__PURE__ */ new Set();
4379
+ let entryReadable = true;
4380
+ /**
4381
+ * Canonical form of a config path, for cycle detection and digesting.
4382
+ * TypeScript reaches the same file through differently-cased paths on a
4383
+ * case-insensitive filesystem, so fold case only there. Folding
4384
+ * unconditionally would collapse two genuinely distinct sibling configs
4385
+ * on a case-sensitive filesystem into one — dropping all but the first
4386
+ * from the walk and colliding their buildinfo digests.
4387
+ *
4388
+ * @param configPath - The path to canonicalize.
4389
+ * @returns The canonical form.
4390
+ */
4391
+ function canonical(configPath) {
4392
+ const resolved = toPosix(path.resolve(configPath));
4393
+ return ts.sys.useCaseSensitiveFileNames ? resolved : resolved.toLowerCase();
4394
+ }
4395
+ /**
4396
+ * Visit one config, recording it when it owns files and recursing into its
4397
+ * references.
4398
+ *
4399
+ * @param configPath - The tsconfig to resolve at this step.
4400
+ */
4401
+ function walk(configPath) {
4402
+ const key = canonical(configPath);
4403
+ if (seen.has(key)) return;
4404
+ seen.add(key);
4405
+ const configFile = ts.readConfigFile(configPath, (file) => ts.sys.readFile(file));
4406
+ if (configFile.error !== void 0) {
4407
+ warnOnce(`${configPath} could not be read; its files skip type-aware invalidation`);
4408
+ if (configPath === entryPath) entryReadable = false;
4409
+ return;
4410
+ }
4411
+ const parsed = ts.parseJsonConfigFileContent(configFile.config, ts.sys, path.dirname(configPath));
4412
+ if (parsed.fileNames.length > 0) projects.push({
4413
+ id: projectDigest(key),
4414
+ fileNames: parsed.fileNames,
4415
+ options: parsed.options
4416
+ });
4417
+ const references = parsed.projectReferences ?? [];
4418
+ for (const reference of references) walk(ts.resolveProjectReferencePath(reference));
4419
+ }
4420
+ walk(entryPath);
4421
+ return {
4422
+ entryReadable,
4423
+ projects
4424
+ };
4425
+ }
4426
+ /**
4427
+ * Add a file to the affected set when it is an in-project file. Library
4428
+ * declarations and dependencies live under `node_modules` and are never in the
4429
+ * ESLint cache, so removing them would be a no-op and they would only inflate
4430
+ * the escape-valve threshold.
4431
+ *
4432
+ * @param fileName - The TypeScript file name (forward-slash absolute).
4433
+ * @param into - The accumulating affected set.
4434
+ */
4435
+ function addProjectFile(fileName, into) {
4436
+ const normalized = path.normalize(fileName);
4437
+ if (normalized.includes(`${path.sep}node_modules${path.sep}`)) return;
4438
+ into.add(normalized);
4439
+ }
4440
+ /**
4441
+ * Fold one affected target into the set. The builder yields a `SourceFile`
4442
+ * for a normal affected file, or the whole `Program` when a change affects
4443
+ * global scope (ambient/augmentation) — in which case every source file is
4444
+ * affected.
4445
+ *
4446
+ * @param target - The affected `Program` or `SourceFile`.
4447
+ * @param into - The accumulating affected set.
4448
+ */
4449
+ function collectAffected(target, into) {
4450
+ if ("getSourceFiles" in target) {
4451
+ for (const sourceFile of target.getSourceFiles()) addProjectFile(sourceFile.fileName, into);
4452
+ return;
4453
+ }
4454
+ addProjectFile(target.fileName, into);
4455
+ }
4456
+ /**
4457
+ * Persist the builder's incremental state by emitting ONLY the buildinfo.
4458
+ * `program.emit` walks remaining affected files and writes the `.tsbuildinfo`;
4459
+ * the writeFile callback swallows every other output so no JS/`.d.ts` reaches
4460
+ * the consumer's tree.
4461
+ *
4462
+ * The emit is what computes each file's real declaration signature, so it
4463
+ * cannot be swapped for the cheaper `emitBuildInfo`: without it the shape hash
4464
+ * degrades to a source-text hash and every implementation-only edit invalidates
4465
+ * all its importers.
4466
+ *
4467
+ * @param builder - The drained builder program.
4468
+ * @param buildInfoPath - The variant's buildinfo file.
4469
+ */
4470
+ function persistBuilderState(builder, buildInfoPath) {
4471
+ const normalizedBuildInfo = path.normalize(buildInfoPath);
4472
+ builder.emit(void 0, (fileName, data) => {
4473
+ if (path.normalize(fileName) === normalizedBuildInfo) fs.writeFileSync(fileName, data);
4474
+ });
4475
+ }
4476
+ /**
4477
+ * Drive the incremental builder: read prior state, drain the affected set
4478
+ * without reporting diagnostics, then persist updated state.
4479
+ *
4480
+ * @param build - The project, its state file, and the run's shared handles.
4481
+ * @returns The affected result.
4482
+ */
4483
+ function runBuilder({ buildInfoPath, project, ts }) {
4484
+ const firstRun = !fs.existsSync(buildInfoPath);
4485
+ const options = {
4486
+ ...project.options,
4487
+ composite: false,
4488
+ declaration: true,
4489
+ declarationMap: false,
4490
+ emitDeclarationOnly: false,
4491
+ incremental: true,
4492
+ noEmit: false,
4493
+ tsBuildInfoFile: buildInfoPath
4494
+ };
4495
+ const host = ts.createIncrementalCompilerHost(options, ts.sys);
4496
+ const oldProgram = ts.readBuilderProgram(options, host);
4497
+ const builder = ts.createEmitAndSemanticDiagnosticsBuilderProgram(project.fileNames, options, host, oldProgram);
4498
+ const affected = /* @__PURE__ */ new Set();
4499
+ let touched = false;
4500
+ let next = builder.getSemanticDiagnosticsOfNextAffectedFile();
4501
+ while (next !== void 0) {
4502
+ touched = true;
4503
+ collectAffected(next.affected, affected);
4504
+ next = builder.getSemanticDiagnosticsOfNextAffectedFile();
4505
+ }
4506
+ if (touched) persistBuilderState(builder, buildInfoPath);
4507
+ return {
4508
+ affected,
4509
+ firstRun
4510
+ };
4511
+ }
4512
+ //#endregion
4513
+ //#region src/lint-cli/lib/cache/invalidation.ts
4514
+ /**
4515
+ * Fold TypeScript builder-based invalidation into the ESLint cache. Computes
4516
+ * the affected set (files whose type-aware results may have changed because a
4517
+ * file they import changed), then either:
4518
+ *
4519
+ * - persists state only, when this is the builder's first run (its affected set
4520
+ * is meaningless — everything is "affected");
4521
+ * - deletes the mode cache wholesale, when the affected set exceeds the bust
4522
+ * threshold (surgical removal stops paying off);
4523
+ * - surgically removes the affected files that are lint targets and not already
4524
+ * dirty by mtime/checksum.
4525
+ *
4526
+ * Never throws: a skipped/failed builder yields a no-op outcome.
4527
+ *
4528
+ * @param run - The run context.
4529
+ * @param request - The invalidation inputs.
4530
+ * @returns The invalidation outcome.
4531
+ */
4532
+ function applyTypeAwareInvalidation(run, { alreadyDirty, cache, cacheLocation, mode, targetFiles }) {
4533
+ const result = computeAffectedFiles(run, mode);
4534
+ if (result === void 0) return {
4535
+ busted: false,
4536
+ firstRun: false,
4537
+ invalidated: [],
4538
+ skipped: true
4539
+ };
4540
+ if (result.firstRun) return {
4541
+ busted: false,
4542
+ firstRun: true,
4543
+ invalidated: [],
4544
+ skipped: false
4545
+ };
4546
+ const targets = /* @__PURE__ */ new Set();
4547
+ for (const file of targetFiles) targets.add(normalizePath(file));
4548
+ const affectedTargets = [];
4549
+ for (const affected of result.affected) {
4550
+ const normalized = normalizePath(affected);
4551
+ if (targets.has(normalized)) affectedTargets.push(normalized);
4552
+ }
4553
+ const threshold = resolveAffectedBustThreshold(run.environment);
4554
+ if (affectedTargets.length > threshold) {
4555
+ fs.rmSync(cacheLocation, { force: true });
4556
+ return {
4557
+ busted: true,
4558
+ firstRun: false,
4559
+ invalidated: [],
4560
+ skipped: false
4561
+ };
4562
+ }
4563
+ const invalidated = affectedTargets.filter((target) => !alreadyDirty.has(target));
4564
+ cache?.removeEntries(invalidated);
4565
+ return {
4566
+ busted: false,
4567
+ firstRun: false,
4568
+ invalidated,
4569
+ skipped: false
4570
+ };
4571
+ }
4572
+ //#endregion
4573
+ //#region src/lint-cli/lib/plan/sizing.ts
4574
+ /** The stderr notice emitted when the type-aware pass is skipped. */
4575
+ const TYPED_SKIP_NOTICE = "isentinel-lint: skipping the type-aware ESLint pass; no type-relevant files changed since the last run.\n";
4576
+ /**
4577
+ * Size every selected pass: count the files each will re-lint, resolve its
4578
+ * concurrency, and decide whether it runs at all.
4579
+ *
4580
+ * The dirty count is the whole point. It routes on whether the run may mutate:
4581
+ * a real run clears stale caches and folds TypeScript builder invalidation into
4582
+ * the count, while `--print` only reports what is already dirty by
4583
+ * mtime/checksum and touches nothing. Callers see neither path — they get the
4584
+ * planned passes.
4585
+ *
4586
+ * @param descriptors - The passes to size, in run order.
4587
+ * @param run - The run context.
4588
+ * @param inputs - The file lists, limits and bust results to size against.
4589
+ * @returns The planned passes, in the same order.
4590
+ */
4591
+ function sizePasses(descriptors, run, inputs) {
4592
+ const multiPass = descriptors.length > 1;
4593
+ return descriptors.map((descriptor) => sizePass(descriptor, {
4594
+ ...inputs,
4595
+ multiPass,
4596
+ run
4597
+ }));
4598
+ }
4599
+ /**
4600
+ * Dirty count for a real run: clear the cache wholesale when stale, then fold
4601
+ * TS builder invalidation in, reusing a single loaded cache for the dirty query
4602
+ * and the surgical entry removal.
4603
+ *
4604
+ * @param descriptor - The pass being sized.
4605
+ * @param cacheLocation - The resolved cache file path.
4606
+ * @param targetFiles - The candidate files for this pass.
4607
+ * @param context - The shared sizing inputs.
4608
+ * @returns The number of dirty files.
4609
+ */
4610
+ function mutatingDirtyCount(descriptor, cacheLocation, targetFiles, { clearedCaches, run }) {
4611
+ if (clearedCaches.has(cacheLocation)) return targetFiles.length;
4612
+ const cache = openCache(cacheLocation, run.ci);
4613
+ const dirty = new Set((cache?.getUpdatedFiles(targetFiles) ?? targetFiles).map((file) => normalizePath(file)));
4614
+ if (descriptor.invalidation !== "none") {
4615
+ const outcome = applyTypeAwareInvalidation(run, {
4616
+ alreadyDirty: dirty,
4617
+ cache,
4618
+ cacheLocation,
4619
+ mode: descriptor.invalidation === "only" ? "only" : void 0,
4620
+ targetFiles
4621
+ });
4622
+ if (outcome.busted) return targetFiles.length;
4623
+ for (const file of outcome.invalidated) dirty.add(file);
4624
+ }
4625
+ return dirty.size;
4626
+ }
4627
+ /**
4628
+ * Dirty count for `--print`: reflect cache staleness but never delete it, and
4629
+ * never run the builder. Only the mtime/checksum-dirty files count.
4630
+ *
4631
+ * @param cacheLocation - The resolved cache file path.
4632
+ * @param targetFiles - The candidate files for this pass.
4633
+ * @param context - The shared sizing inputs.
4634
+ * @returns The number of dirty files.
4635
+ */
4636
+ function readOnlyDirtyCount(cacheLocation, targetFiles, { newestBustMtime, run }) {
4637
+ if (isCacheStale(cacheLocation, newestBustMtime)) return targetFiles.length;
4638
+ return (openCache(cacheLocation, run.ci)?.getUpdatedFiles(targetFiles) ?? targetFiles).length;
4639
+ }
4640
+ /**
4641
+ * Count the files a pass will re-lint. Routes on `mutate`: the mutating path
4642
+ * clears stale caches and folds builder invalidation into the count (reusing
4643
+ * one loaded cache for the dirty query and the surgical removal); the read-only
4644
+ * path only reports the mtime/checksum-dirty files.
4645
+ *
4646
+ * @param descriptor - The pass being sized.
4647
+ * @param cacheFile - The pass's keyed cache file name.
4648
+ * @param context - The shared sizing inputs.
4649
+ * @returns The number of dirty files.
4650
+ */
4651
+ function passDirtyCount(descriptor, cacheFile, context) {
4652
+ const targetFiles = descriptor.typeAwareOnly ? context.files.typeAware : context.files.lintable;
4653
+ if (!context.options.cache) return targetFiles.length;
4654
+ const cacheLocation = path.resolve(context.run.cwd, cacheFile);
4655
+ return context.run.mutate ? mutatingDirtyCount(descriptor, cacheLocation, targetFiles, context) : readOnlyDirtyCount(cacheLocation, targetFiles, context);
4656
+ }
4657
+ /**
4658
+ * Size one pass: count its dirty files, resolve concurrency and decide whether
4659
+ * it runs. The default-mode type-aware pass is skipped when nothing
4660
+ * type-relevant is dirty; an explicit single-pass mode never skips.
4661
+ *
4662
+ * @param descriptor - The pass being sized.
4663
+ * @param context - The shared sizing inputs.
4664
+ * @returns The planned pass.
4665
+ */
4666
+ function sizePass(descriptor, context) {
4667
+ const cacheFile = cacheFileFor(descriptor.cacheFileBase, context.run.key);
4668
+ const dirtyCount = passDirtyCount(descriptor, cacheFile, context);
4669
+ const conservative = context.files.targetsOutsideCwd;
4670
+ const filesPerWorker = descriptor.filesPerWorker(context.limits, context.run.environment);
4671
+ const maxWorkers = maxWorkersFor(descriptor, context.limits);
4672
+ const sizingDirtyCount = conservative ? maxWorkers * filesPerWorker : dirtyCount;
4673
+ const concurrency = context.options.concurrency ?? computeWorkerCount({
4674
+ dirtyCount: sizingDirtyCount,
4675
+ filesPerWorker,
4676
+ maxWorkers
4677
+ });
4678
+ if (context.run.mutate && context.multiPass && descriptor === TYPED_PASS && !conservative && dirtyCount === 0) return {
4679
+ cacheFile,
4680
+ concurrency,
4681
+ descriptor,
4682
+ shouldRun: false,
4683
+ skipReason: TYPED_SKIP_NOTICE
4684
+ };
4685
+ return {
4686
+ cacheFile,
4687
+ concurrency,
4688
+ descriptor,
4689
+ shouldRun: true,
4690
+ skipReason: void 0
4691
+ };
4692
+ }
4693
+ //#endregion
4694
+ //#region src/lint-cli/lib/plan/plan.ts
4695
+ /**
4696
+ * Plan the run: collect the repo file list once, apply the package.json and
4697
+ * mtime busts and TypeScript builder invalidation, size each pass and decide
4698
+ * which run. All I/O and mutation happen here, exactly once. When `mutate` is
4699
+ * false (`--print`) the whole mutation step is skipped — no builder, no cache
4700
+ * deletion, no state writes and no auto-skip — while still sizing from the
4701
+ * on-disk caches. The returned value is plain data.
4702
+ *
4703
+ * @param options - The parsed CLI options.
4704
+ * @param run - The run context (cwd, variant key, environment, mutate).
4705
+ * @returns The run plan.
4706
+ */
4707
+ function plan(options, run) {
4708
+ const { ci, cwd, environment, mutate } = run;
4709
+ const runEslint = !options.oxlint;
4710
+ const oxlintTypeAware = resolveOxlintTypeAware(options);
4711
+ const agentsFormatterPath = options.agents ? resolveAgentsFormatter() : "";
4712
+ const oxlintPaths = oxlintTargets(cwd, options.paths);
4713
+ const runOxlint = !options.eslint && oxlintPaths.length > 0;
4714
+ if (!runEslint) return {
4715
+ agentsFormatterPath,
4716
+ ci,
4717
+ oxlint: runOxlint,
4718
+ oxlintPaths,
4719
+ oxlintReason: void 0,
4720
+ oxlintTypeAware,
4721
+ passes: [],
4722
+ targetsOutsideCwd: false
4723
+ };
4724
+ const descriptors = selectPasses(options, ci);
4725
+ const files = collectRepoFiles(cwd, options.paths);
4726
+ const newestBustMtime = maxMtimeMs(files.bustFiles);
4727
+ const limits = resolveWorkerLimits(environment, availableParallelism(), ci);
4728
+ const oxlintDecision = resolveOxlintRun(run, {
4729
+ files,
4730
+ runEslint,
4731
+ runOxlint
4732
+ });
4733
+ const canMutateCaches = mutate && options.cache;
4734
+ const hasTypeAwarePass = descriptors.some((descriptor) => descriptor.invalidation !== "none");
4735
+ const configHash = options.cache ? computeConfigHash(cwd, files.configFiles) : void 0;
4736
+ if (canMutateCaches) applyHashBust(run, CONFIG_DRIFT, configHash);
4737
+ if (canMutateCaches && hasTypeAwarePass) applyHashBust(run, PACKAGE_RESOLUTION, computePackageJsonHash(cwd));
4738
+ const passes = sizePasses(descriptors, run, {
4739
+ clearedCaches: new Set(canMutateCaches ? sweepStaleCaches(cwd, newestBustMtime) : []),
4740
+ files: withoutIgnored(files, resolveIgnoredFiles(run, configHash, files.lintable)),
4741
+ limits,
4742
+ newestBustMtime,
4743
+ options
4744
+ });
4745
+ return {
4746
+ agentsFormatterPath,
4747
+ ci,
4748
+ oxlint: oxlintDecision.run,
4749
+ oxlintPaths,
4750
+ oxlintReason: oxlintDecision.reason,
4751
+ oxlintTypeAware,
4752
+ passes,
4753
+ targetsOutsideCwd: files.targetsOutsideCwd
4754
+ };
4755
+ }
4756
+ function resolveOxlintTypeAware(options) {
4757
+ return !options.eslint && options.oxlintTypeAware && options.typeAware !== "off";
4758
+ }
4759
+ //#endregion
4760
+ //#region src/lint-cli/lib/run.ts
4761
+ /**
4762
+ * Parse, validate, compose and run the hybrid oxlint + ESLint invocation.
4763
+ *
4764
+ * @param argv - The argument slice (without the node/bin prefix).
4765
+ * @param cwd - The working directory (defaults to `process.cwd()`; injected in tests).
4766
+ * @param environment - The process environment (defaults to `process.env`).
4767
+ * @returns The process exit code.
4768
+ * @rejects {CliError} When the arguments are invalid or a tool is missing.
4769
+ */
4770
+ async function runLint(argv, cwd = process$1.cwd(), environment = process$1.env) {
4771
+ const options = parseArguments(argv, environment);
4772
+ const { commands, notice } = compose(plan(options, resolveRunContext(cwd, environment, !options.print)), options);
4773
+ if (options.print) {
4774
+ for (const command of commands) process$1.stdout.write(`${formatCommandLine(command)}\n`);
4775
+ return 0;
4776
+ }
4777
+ if (commands.some((command) => command.bin === "oxlint" && command.args.includes("--type-aware")) && !isPackageExists("oxlint-tsgolint", { paths: [cwd] })) throw new CliError("oxlint-tsgolint is not installed, so oxlint cannot run type-aware rules. Install oxlint-tsgolint, or pass --no-oxlint-type-aware to skip type-aware linting.");
4778
+ if (notice !== void 0) process$1.stderr.write(notice);
4779
+ return execute(commands, cwd, options.fix || commands.length <= 1);
4780
+ }
4781
+ //#endregion
4782
+ //#region src/lint-cli/index.ts
4783
+ const HELP = `isentinel-lint [flags] [paths...]
4784
+
4785
+ Runs oxlint and ESLint together, sizing ESLint's --concurrency from how many
4786
+ files actually need re-linting and managing per-mode caches.
4787
+
4788
+ Flags:
4789
+ --eslint Run only ESLint.
4790
+ --oxlint Run only oxlint.
4791
+ --fix Apply fixes: oxlint --fix then eslint --fix.
4792
+ --agents, --no-agents Emit agent-friendly output. On by default when an AI
4793
+ agent session is detected (AI_AGENT, CLAUDECODE, ...).
4794
+ --type-aware=off|only|full
4795
+ Force a single ESLint pass. Default runs the fast and
4796
+ type-aware passes concurrently; full is the escape hatch.
4797
+ --no-oxlint-type-aware Skip oxlint's type-aware rules (no tsgolint needed).
4798
+ --no-cache Disable ESLint's cache.
4799
+ --concurrency <n|off> Override the concurrency heuristic.
4800
+ --eslint-args "<args>" Extra arguments for ESLint.
4801
+ --oxlint-args "<args>" Extra arguments for oxlint.
4802
+ --print Print the composed commands without running them.
4803
+ -- <args> Forward args to the single selected tool.
4804
+ -h, --help Show this help.
4805
+ -v, --version Show the version.
4806
+ `;
4807
+ async function main() {
4808
+ const argv = process$1.argv.slice(2);
4809
+ if (argv.includes("--help") || argv.includes("-h")) {
4810
+ process$1.stdout.write(`${HELP}\n`);
4811
+ return 0;
4812
+ }
4813
+ if (argv.includes("--version") || argv.includes("-v")) {
4814
+ process$1.stdout.write(`${version}\n`);
4815
+ return 0;
4816
+ }
4817
+ return runLint(argv);
4818
+ }
4819
+ main().then((code) => {
4820
+ process$1.exitCode = code;
4821
+ }).catch((err) => {
4822
+ if (err instanceof CliError) console.error(err.message);
4823
+ else console.error(err);
4824
+ process$1.exitCode = 1;
4825
+ });
4826
+ //#endregion
4827
+ export {};