@isentinel/eslint-config 6.1.0 → 6.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/lint-cli.mjs CHANGED
@@ -1,13 +1,14 @@
1
1
  import { createRequire } from "node:module";
2
2
  import process$1 from "node:process";
3
3
  import { isPackageExists } from "local-pkg";
4
- import { Buffer as Buffer$1 } from "node:buffer";
5
- import fs from "node:fs/promises";
6
- import path from "node:path";
7
- import yargs from "yargs";
8
4
  import "find-up-simple";
9
- import fs$1 from "node:fs";
5
+ import "jsonc-eslint-parser";
6
+ import fs from "node:fs";
7
+ import path from "node:path";
10
8
  import "semver";
9
+ import { Buffer as Buffer$1 } from "node:buffer";
10
+ import fs$1 from "node:fs/promises";
11
+ import yargs from "yargs";
11
12
  import crypto from "node:crypto";
12
13
  import ansis from "ansis";
13
14
  import { KillOnSignal, LogError, LogExit, LogOutput, Logger, createConcurrently } from "concurrently";
@@ -41,7 +42,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
41
42
  }) : target, mod));
42
43
  //#endregion
43
44
  //#region package.json
44
- var version = "6.1.0";
45
+ var version = "6.2.0";
45
46
  //#endregion
46
47
  //#region src/lint-cli/lib/cli/types.ts
47
48
  /**
@@ -51,78 +52,6 @@ var version = "6.1.0";
51
52
  var CliError = class extends Error {
52
53
  name = "CliError";
53
54
  };
54
- //#endregion
55
- //#region src/lint-cli/lib/cli/file-list.ts
56
- /** The `--files0-from` value that names stdin rather than a file. */
57
- const STDIN_SOURCE = "-";
58
- /**
59
- * Split a NUL-delimited, UTF-8 path list (the `git ls-files -z` / `find
60
- * -print0` shape) into its paths.
61
- *
62
- * Every path is terminated by a NUL, though the last terminator may be left off
63
- * as GNU's own `--files0-from` allows. Nothing else is interpreted: spaces,
64
- * newlines, quotes, shell metacharacters and leading hyphens all stay part of
65
- * the name. The bytes must be valid UTF-8 — a lossy decode would lint a file
66
- * that is not the one selected — and no entry may be empty, since an empty
67
- * path is never a file and most likely a broken producer.
68
- *
69
- * @param bytes - The raw list.
70
- * @returns The paths, in list order. Empty for an empty list.
71
- */
72
- function decodeFileList(bytes) {
73
- const decoder = new TextDecoder("utf-8", {
74
- fatal: true,
75
- ignoreBOM: true
76
- });
77
- let text;
78
- try {
79
- text = decoder.decode(bytes);
80
- } catch {
81
- throw new CliError("--files0-from: the file list is not valid UTF-8.");
82
- }
83
- if (text.length === 0) return [];
84
- const entries = text.split("\0");
85
- if (entries.at(-1) === "") entries.pop();
86
- const empty = entries.indexOf("");
87
- if (empty !== -1) throw new CliError(`--files0-from: entry ${empty + 1} of the file list is empty; expected NUL-terminated paths.`);
88
- return entries;
89
- }
90
- /**
91
- * Fill the run's paths from its `--files0-from` list, reading it once. Options
92
- * without a list are returned as they are.
93
- *
94
- * An empty list stays empty: the caller treats it as nothing to lint, and it
95
- * must never become the whole-tree default `.`.
96
- *
97
- * @param options - The parsed CLI options.
98
- * @param cwd - The directory a relative list path resolves against.
99
- * @param stdin - The stream `-` reads.
100
- * @returns The options with `paths` set from the list.
101
- * @rejects {CliError} When the list cannot be read or is malformed.
102
- */
103
- async function readFileList(options, cwd, stdin) {
104
- const source = options.filesFrom;
105
- if (source === void 0) return options;
106
- const bytes = source === STDIN_SOURCE ? await readAll(stdin) : await readFile(source, cwd);
107
- return {
108
- ...options,
109
- paths: decodeFileList(bytes)
110
- };
111
- }
112
- async function readFile(source, cwd) {
113
- try {
114
- return await fs.readFile(path.resolve(cwd, source));
115
- } catch (err) {
116
- throw new CliError(`--files0-from: cannot read "${source}": ${err instanceof Error ? err.message : String(err)}`);
117
- }
118
- }
119
- async function readAll(stream) {
120
- const chunks = [];
121
- for await (const chunk of stream) if (typeof chunk === "string") chunks.push(Buffer$1.from(chunk, "utf8"));
122
- else if (chunk instanceof Uint8Array) chunks.push(Buffer$1.from(chunk));
123
- else throw new CliError("--files0-from: stdin produced a non-byte chunk.");
124
- return Buffer$1.concat(chunks);
125
- }
126
55
  /**
127
56
  * The real TS/JS-family file extensions {@link GLOB_SRC_EXT} encodes (the glob
128
57
  * additionally matches non-existent combinations such as `cjsx`). Kept next to
@@ -184,6 +113,10 @@ function isRecord(value) {
184
113
  function isStringArray(value) {
185
114
  return Array.isArray(value) && value.every((item) => typeof item === "string");
186
115
  }
116
+ //#endregion
117
+ //#region src/quiet-warnings.ts
118
+ /** Set in an ESLint child that must not repeat the preset's config warnings. */
119
+ const QUIET_WARNINGS_ENV = "ISENTINEL_QUIET_CONFIG_WARNINGS";
187
120
  createRequire(import.meta.url);
188
121
  /**
189
122
  * Whether the environment signals a CI run. Treats an unset, empty, `"false"`
@@ -302,6 +235,120 @@ function environmentMatcher(name, pattern, { noTty = false } = {}) {
302
235
  return value !== void 0 && pattern.test(value);
303
236
  };
304
237
  }
238
+ /** JSON oxfmt config files oxfmt discovers. */
239
+ const OXFMT_RC_FILES = [".oxfmtrc.json", ".oxfmtrc.jsonc"];
240
+ /**
241
+ * Oxfmt config modules oxfmt discovers. It loads other module extensions only
242
+ * through `--config`, never by search.
243
+ */
244
+ const OXFMT_CONFIG_MODULES = ["oxfmt.config.ts", "oxfmt.config.mts"];
245
+ OXFMT_CONFIG_MODULES[0];
246
+ /** Every file name oxfmt discovers. One directory may hold only one of them. */
247
+ const OXFMT_CONFIG_NAMES = [...OXFMT_RC_FILES, ...OXFMT_CONFIG_MODULES];
248
+ /**
249
+ * List the oxfmt config files in one directory, without walking up.
250
+ *
251
+ * @param directory - Directory to check.
252
+ * @returns The file names found, sorted.
253
+ */
254
+ function oxfmtConfigNamesIn(directory) {
255
+ return OXFMT_CONFIG_NAMES.filter((name) => fs.existsSync(path.join(directory, name))).sort();
256
+ }
257
+ /**
258
+ * Find the oxfmt config the way oxfmt does: walk up from `cwd` to the
259
+ * filesystem root and take the first directory that holds one. Like oxfmt,
260
+ * refuse a directory that holds more than one.
261
+ *
262
+ * @param cwd - Directory to start from.
263
+ * @returns The config file, if any.
264
+ * @throws When one directory holds more than one oxfmt config file.
265
+ */
266
+ function findOxfmtConfig(cwd = process$1.cwd()) {
267
+ let directory = path.resolve(cwd);
268
+ for (;;) {
269
+ const [first, second] = oxfmtConfigNamesIn(directory);
270
+ if (second !== void 0) throw new Error(`[@isentinel/eslint-config] Both '${first}' and '${second}' found in ${directory}. Keep one oxfmt config file.`);
271
+ if (first !== void 0) return {
272
+ isModule: OXFMT_CONFIG_MODULES.some((name) => name === first),
273
+ path: path.join(directory, first)
274
+ };
275
+ const parent = path.dirname(directory);
276
+ if (parent === directory) return;
277
+ directory = parent;
278
+ }
279
+ }
280
+ //#endregion
281
+ //#region src/lint-cli/lib/cli/file-list.ts
282
+ /** The `--files0-from` value that names stdin rather than a file. */
283
+ const STDIN_SOURCE = "-";
284
+ /**
285
+ * Split a NUL-delimited, UTF-8 path list (the `git ls-files -z` / `find
286
+ * -print0` shape) into its paths.
287
+ *
288
+ * Every path is terminated by a NUL, though the last terminator may be left off
289
+ * as GNU's own `--files0-from` allows. Nothing else is interpreted: spaces,
290
+ * newlines, quotes, shell metacharacters and leading hyphens all stay part of
291
+ * the name. The bytes must be valid UTF-8 — a lossy decode would lint a file
292
+ * that is not the one selected — and no entry may be empty, since an empty
293
+ * path is never a file and most likely a broken producer.
294
+ *
295
+ * @param bytes - The raw list.
296
+ * @returns The paths, in list order. Empty for an empty list.
297
+ */
298
+ function decodeFileList(bytes) {
299
+ const decoder = new TextDecoder("utf-8", {
300
+ fatal: true,
301
+ ignoreBOM: true
302
+ });
303
+ let text;
304
+ try {
305
+ text = decoder.decode(bytes);
306
+ } catch {
307
+ throw new CliError("--files0-from: the file list is not valid UTF-8.");
308
+ }
309
+ if (text.length === 0) return [];
310
+ const entries = text.split("\0");
311
+ if (entries.at(-1) === "") entries.pop();
312
+ const empty = entries.indexOf("");
313
+ if (empty !== -1) throw new CliError(`--files0-from: entry ${empty + 1} of the file list is empty; expected NUL-terminated paths.`);
314
+ return entries;
315
+ }
316
+ /**
317
+ * Fill the run's paths from its `--files0-from` list, reading it once. Options
318
+ * without a list are returned as they are.
319
+ *
320
+ * An empty list stays empty: the caller treats it as nothing to lint, and it
321
+ * must never become the whole-tree default `.`.
322
+ *
323
+ * @param options - The parsed CLI options.
324
+ * @param cwd - The directory a relative list path resolves against.
325
+ * @param stdin - The stream `-` reads.
326
+ * @returns The options with `paths` set from the list.
327
+ * @rejects {CliError} When the list cannot be read or is malformed.
328
+ */
329
+ async function readFileList(options, cwd, stdin) {
330
+ const source = options.filesFrom;
331
+ if (source === void 0) return options;
332
+ const bytes = source === STDIN_SOURCE ? await readAll(stdin) : await readFile(source, cwd);
333
+ return {
334
+ ...options,
335
+ paths: decodeFileList(bytes)
336
+ };
337
+ }
338
+ async function readFile(source, cwd) {
339
+ try {
340
+ return await fs$1.readFile(path.resolve(cwd, source));
341
+ } catch (err) {
342
+ throw new CliError(`--files0-from: cannot read "${source}": ${err instanceof Error ? err.message : String(err)}`);
343
+ }
344
+ }
345
+ async function readAll(stream) {
346
+ const chunks = [];
347
+ for await (const chunk of stream) if (typeof chunk === "string") chunks.push(Buffer$1.from(chunk, "utf8"));
348
+ else if (chunk instanceof Uint8Array) chunks.push(Buffer$1.from(chunk));
349
+ else throw new CliError("--files0-from: stdin produced a non-byte chunk.");
350
+ return Buffer$1.concat(chunks);
351
+ }
305
352
  //#endregion
306
353
  //#region src/lint-cli/lib/cli/parse.ts
307
354
  /**
@@ -413,6 +460,10 @@ function parseArguments(argv, environment = process$1.env) {
413
460
  default: true,
414
461
  description: "Use ESLint's on-disk cache.",
415
462
  type: "boolean"
463
+ }).option("oxfmt", {
464
+ default: true,
465
+ description: "Run oxfmt when an oxfmt.config.* file exists.",
466
+ type: "boolean"
416
467
  }).option("oxlint-type-aware", {
417
468
  default: true,
418
469
  description: "Pass --type-aware to oxlint.",
@@ -457,6 +508,7 @@ function parseArguments(argv, environment = process$1.env) {
457
508
  eslintArgs,
458
509
  filesFrom,
459
510
  fix,
511
+ oxfmt: parsed.oxfmt,
460
512
  oxlint: oxlintOnly,
461
513
  oxlintArgs,
462
514
  oxlintTypeAware: parsed.oxlintTypeAware,
@@ -793,7 +845,7 @@ function statePath(cwd, name, ...parts) {
793
845
  */
794
846
  function readFileIfPresent(filePath) {
795
847
  try {
796
- return fs$1.readFileSync(filePath, "utf8");
848
+ return fs.readFileSync(filePath, "utf8");
797
849
  } catch {
798
850
  return;
799
851
  }
@@ -834,12 +886,12 @@ function writeState(filePath, data) {
834
886
  })}\n`;
835
887
  const temporary = `${filePath}.${process$1.pid}.tmp`;
836
888
  try {
837
- fs$1.mkdirSync(path.dirname(filePath), { recursive: true });
838
- fs$1.writeFileSync(temporary, content);
839
- fs$1.renameSync(temporary, filePath);
889
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
890
+ fs.writeFileSync(temporary, content);
891
+ fs.renameSync(temporary, filePath);
840
892
  } catch {
841
893
  try {
842
- fs$1.rmSync(temporary, { force: true });
894
+ fs.rmSync(temporary, { force: true });
843
895
  } catch {}
844
896
  }
845
897
  }
@@ -874,7 +926,7 @@ function swapState(filePath, value) {
874
926
  function touchState(filePath) {
875
927
  try {
876
928
  const now = Date.now() / 1e3;
877
- fs$1.utimesSync(filePath, now, now);
929
+ fs.utimesSync(filePath, now, now);
878
930
  } catch {}
879
931
  }
880
932
  /**
@@ -967,7 +1019,7 @@ function resolveAgentsFormatter() {
967
1019
  */
968
1020
  function resolveIgnoredHelper() {
969
1021
  const built = fileURLToPath(new URL("./lint-ignored.mjs", import.meta.url));
970
- return fs$1.existsSync(built) ? built : fileURLToPath(new URL("../../ignored-child.ts", import.meta.url));
1022
+ return fs.existsSync(built) ? built : fileURLToPath(new URL("../../ignored-child.ts", import.meta.url));
971
1023
  }
972
1024
  /**
973
1025
  * Resolve the engine bootstrap shipped alongside this entry (built from
@@ -983,7 +1035,7 @@ function resolveIgnoredHelper() {
983
1035
  */
984
1036
  function resolveBootstrap() {
985
1037
  const built = fileURLToPath(new URL("./lint-bootstrap.mjs", import.meta.url));
986
- return fs$1.existsSync(built) ? built : fileURLToPath(new URL("../../bootstrap.ts", import.meta.url));
1038
+ return fs.existsSync(built) ? built : fileURLToPath(new URL("../../bootstrap.ts", import.meta.url));
987
1039
  }
988
1040
  /**
989
1041
  * The path a package's manifest declares as the bin of the package's own name,
@@ -1062,6 +1114,7 @@ const PREFIX_COLOR = {
1062
1114
  eslint: "blue",
1063
1115
  fast: "blue",
1064
1116
  oxc: "magenta",
1117
+ oxfmt: "yellow",
1065
1118
  typed: "cyan"
1066
1119
  };
1067
1120
  /**
@@ -1482,6 +1535,35 @@ function composeOxlintCommand(options, context) {
1482
1535
  };
1483
1536
  }
1484
1537
  /**
1538
+ * Compose the oxfmt child command. Oxfmt receives the run's own targets and
1539
+ * applies its config's `ignorePatterns` to them, so a whole-tree run passes
1540
+ * `.` and lets oxfmt walk the tree itself. Like every engine child it is
1541
+ * launched through the bootstrap, so a long file list never reaches the OS
1542
+ * command line..
1543
+ *
1544
+ * @param options - The parsed CLI options.
1545
+ * @param context - The child's settings.
1546
+ * @param context.configPath - The config module, passed as `--config` so oxfmt
1547
+ * uses the file the lint factories read.
1548
+ * @param context.write - Format the files in place rather than check them.
1549
+ * @returns The oxfmt child command.
1550
+ */
1551
+ function composeOxfmtCommand(options, { configPath, write }) {
1552
+ return {
1553
+ args: [
1554
+ write ? "--write" : "--check",
1555
+ "--config",
1556
+ configPath,
1557
+ "--no-error-on-unmatched-pattern",
1558
+ ...pathArguments(options.paths)
1559
+ ],
1560
+ bin: "oxfmt",
1561
+ env: {},
1562
+ label: "oxfmt",
1563
+ watchHeap: false
1564
+ };
1565
+ }
1566
+ /**
1485
1567
  * Compose the ESLint child command.
1486
1568
  *
1487
1569
  * @param options - The parsed CLI options.
@@ -1497,6 +1579,7 @@ function composeEslintCommand(options, context) {
1497
1579
  if (context.fix) args.push("--fix");
1498
1580
  args.push(...options.eslintArgs, ...pathArguments(context.paths));
1499
1581
  const environment = { ESLINT_TYPE_AWARE: context.typeAwareEnv };
1582
+ if (context.quietWarnings === true) environment[QUIET_WARNINGS_ENV] = "1";
1500
1583
  if (context.heap?.nodeOptions !== void 0) environment["NODE_OPTIONS"] = context.heap.nodeOptions;
1501
1584
  return {
1502
1585
  args,
@@ -1553,6 +1636,7 @@ function composePasses(passes, runPlan, options) {
1553
1636
  fix: false,
1554
1637
  heap: pass.heap,
1555
1638
  paths: options.paths,
1639
+ quietWarnings: commands.length > 0,
1556
1640
  typeAwareEnv: pass.descriptor.typeAwareEnv
1557
1641
  }));
1558
1642
  }
@@ -1686,7 +1770,7 @@ function applyHashBust(run, bust, hash) {
1686
1770
  const cleared = [];
1687
1771
  for (const base of bust.caches) {
1688
1772
  const cacheFilePath = path.resolve(run.cwd, cacheFileFor(base, run.key));
1689
- fs$1.rmSync(cacheFilePath, { force: true });
1773
+ fs.rmSync(cacheFilePath, { force: true });
1690
1774
  cleared.push(cacheFilePath);
1691
1775
  }
1692
1776
  return {
@@ -1963,7 +2047,7 @@ function normalizePath(filePath, platform = process$1.platform) {
1963
2047
  * @returns The loaded cache, or `undefined` when the file does not exist.
1964
2048
  */
1965
2049
  function openCache(cacheFilePath, useChecksum) {
1966
- if (!fs$1.existsSync(cacheFilePath)) return;
2050
+ if (!fs.existsSync(cacheFilePath)) return;
1967
2051
  const cache = load(cacheFilePath, useChecksum);
1968
2052
  return {
1969
2053
  filesWithMessages: (files) => filesWithMessagesIn(cache, files),
@@ -1973,7 +2057,7 @@ function openCache(cacheFilePath, useChecksum) {
1973
2057
  }
1974
2058
  function safeMtimeMs(filePath) {
1975
2059
  try {
1976
- return fs$1.statSync(filePath).mtimeMs;
2060
+ return fs.statSync(filePath).mtimeMs;
1977
2061
  } catch {
1978
2062
  return;
1979
2063
  }
@@ -1990,7 +2074,7 @@ function safeMtimeMs(filePath) {
1990
2074
  function listCacheFiles(cwd) {
1991
2075
  let entries;
1992
2076
  try {
1993
- entries = fs$1.readdirSync(cwd);
2077
+ entries = fs.readdirSync(cwd);
1994
2078
  } catch {
1995
2079
  return [];
1996
2080
  }
@@ -1998,7 +2082,7 @@ function listCacheFiles(cwd) {
1998
2082
  }
1999
2083
  function removeCacheFile(cacheFilePath) {
2000
2084
  try {
2001
- fs$1.rmSync(cacheFilePath, { force: true });
2085
+ fs.rmSync(cacheFilePath, { force: true });
2002
2086
  } catch {}
2003
2087
  }
2004
2088
  /**
@@ -4007,7 +4091,7 @@ const WALK_BUDGET = 8192;
4007
4091
  function findWorkspaceRoot(cwd) {
4008
4092
  let current = cwd;
4009
4093
  for (;;) {
4010
- for (const marker of WORKSPACE_ROOT_MARKERS) if (fs$1.existsSync(path.join(current, marker))) return current;
4094
+ for (const marker of WORKSPACE_ROOT_MARKERS) if (fs.existsSync(path.join(current, marker))) return current;
4011
4095
  const parent = path.dirname(current);
4012
4096
  if (parent === current) return cwd;
4013
4097
  current = parent;
@@ -4051,7 +4135,7 @@ function findWorkspaceMembers(root) {
4051
4135
  function walk(directory, depth) {
4052
4136
  let entries;
4053
4137
  try {
4054
- entries = fs$1.readdirSync(directory, { withFileTypes: true });
4138
+ entries = fs.readdirSync(directory, { withFileTypes: true });
4055
4139
  } catch {
4056
4140
  return true;
4057
4141
  }
@@ -4061,7 +4145,7 @@ function findWorkspaceMembers(root) {
4061
4145
  if (budget < 0) return false;
4062
4146
  const absolute = path.join(directory, entry.name);
4063
4147
  const relative = toPosix(path.relative(root, absolute));
4064
- if (isMember(relative) && fs$1.existsSync(path.join(absolute, "package.json"))) members.push(absolute);
4148
+ if (isMember(relative) && fs.existsSync(path.join(absolute, "package.json"))) members.push(absolute);
4065
4149
  if (depth < maxDepth && !walk(absolute, depth + 1)) return false;
4066
4150
  }
4067
4151
  return true;
@@ -4279,6 +4363,8 @@ const CACHE_BUST_PATTERNS = [
4279
4363
  "*.config.*",
4280
4364
  "**/tsconfig*.json",
4281
4365
  ".oxlintrc*",
4366
+ ".oxfmtrc*",
4367
+ "oxfmt.config.*",
4282
4368
  ".prettierrc*",
4283
4369
  "pnpm-lock.yaml",
4284
4370
  "package-lock.json",
@@ -4389,7 +4475,7 @@ function oxlintTargets(cwd, targets) {
4389
4475
  const extension = path.extname(target).toLowerCase();
4390
4476
  if (extension === "" || TYPE_AWARE_EXTENSION_SET.has(extension)) return true;
4391
4477
  try {
4392
- return fs$1.statSync(path.resolve(cwd, target)).isDirectory();
4478
+ return fs.statSync(path.resolve(cwd, target)).isDirectory();
4393
4479
  } catch {
4394
4480
  return false;
4395
4481
  }
@@ -4426,7 +4512,7 @@ function collectAncestorBustFiles(cwd) {
4426
4512
  for (;;) {
4427
4513
  let entries = [];
4428
4514
  try {
4429
- entries = fs$1.readdirSync(current, { withFileTypes: true });
4515
+ entries = fs.readdirSync(current, { withFileTypes: true });
4430
4516
  } catch {
4431
4517
  entries = [];
4432
4518
  }
@@ -4515,7 +4601,7 @@ function gitListFiles(cwd, pathSpecs) {
4515
4601
  function walkDirectory(root, current, accumulator) {
4516
4602
  let entries;
4517
4603
  try {
4518
- entries = fs$1.readdirSync(current, { withFileTypes: true });
4604
+ entries = fs.readdirSync(current, { withFileTypes: true });
4519
4605
  } catch {
4520
4606
  return;
4521
4607
  }
@@ -4534,7 +4620,7 @@ function walkFallback(cwd, targets) {
4534
4620
  const absolute = path.resolve(cwd, target);
4535
4621
  let stat;
4536
4622
  try {
4537
- stat = fs$1.statSync(absolute);
4623
+ stat = fs.statSync(absolute);
4538
4624
  } catch {
4539
4625
  continue;
4540
4626
  }
@@ -4859,7 +4945,7 @@ function queryIgnoredFiles(cwd, targets) {
4859
4945
  "ignore"
4860
4946
  ]
4861
4947
  });
4862
- const parsed = JSON.parse(fs$1.readFileSync(outFile, "utf8"));
4948
+ const parsed = JSON.parse(fs.readFileSync(outFile, "utf8"));
4863
4949
  if (!isRecord(parsed)) return;
4864
4950
  const { basePath, entries, ignored, mode } = parsed;
4865
4951
  if (mode === "answers" && isStringArray(ignored)) return {
@@ -4875,7 +4961,7 @@ function queryIgnoredFiles(cwd, targets) {
4875
4961
  } catch {
4876
4962
  return;
4877
4963
  } finally {
4878
- fs$1.rmSync(outFile, { force: true });
4964
+ fs.rmSync(outFile, { force: true });
4879
4965
  }
4880
4966
  }
4881
4967
  //#endregion
@@ -4918,7 +5004,7 @@ function readHybridStatus(cwd) {
4918
5004
  function writeHybridStatus(cwd, oxlint) {
4919
5005
  let installed;
4920
5006
  try {
4921
- installed = fs$1.existsSync(path.resolve(cwd, "node_modules"));
5007
+ installed = fs.existsSync(path.resolve(cwd, "node_modules"));
4922
5008
  } catch {
4923
5009
  return;
4924
5010
  }
@@ -5100,7 +5186,7 @@ function resolveWorkerLimits(environment, availableParallelism, ci) {
5100
5186
  explicitMaxWorkers: explicit !== void 0,
5101
5187
  filesPerWorker,
5102
5188
  maxWorkers,
5103
- typedMaxWorkers: explicit ?? Math.min(maxWorkers, 6)
5189
+ typedMaxWorkers: explicit ?? Math.min(maxWorkers, 5)
5104
5190
  };
5105
5191
  }
5106
5192
  /**
@@ -5303,6 +5389,7 @@ function planFixChild(passes, run, inputs) {
5303
5389
  dirtyCount: targets.length
5304
5390
  }),
5305
5391
  paths: targets,
5392
+ quietWarnings: true,
5306
5393
  typeAwareEnv: FULL_PASS.typeAwareEnv
5307
5394
  });
5308
5395
  }
@@ -5638,7 +5725,7 @@ function computeAffectedFiles({ key, cwd }, mode) {
5638
5725
  if (entryReadable) warnOnce(`no TypeScript files resolved from ${path.basename(configPath)}; skipping type-aware cache invalidation`);
5639
5726
  return;
5640
5727
  }
5641
- fs$1.mkdirSync(stateDirectory(cwd), { recursive: true });
5728
+ fs.mkdirSync(stateDirectory(cwd), { recursive: true });
5642
5729
  const resolutionGate = computeResolutionGate(cwd);
5643
5730
  const affected = /* @__PURE__ */ new Set();
5644
5731
  let warmProjects = 0;
@@ -5685,7 +5772,7 @@ function computeAffectedFiles({ key, cwd }, mode) {
5685
5772
  function hasBuilderState({ key, cwd }, mode) {
5686
5773
  const prefix = `${path.basename(statePath(cwd, BUILD_INFO_STATE, modeSuffix(mode), key))}-`;
5687
5774
  try {
5688
- return fs$1.readdirSync(stateDirectory(cwd)).some((name) => name.startsWith(prefix) && !name.endsWith(".tmp"));
5775
+ return fs.readdirSync(stateDirectory(cwd)).some((name) => name.startsWith(prefix) && !name.endsWith(".tmp"));
5689
5776
  } catch {
5690
5777
  return false;
5691
5778
  }
@@ -5900,10 +5987,10 @@ function persistBuilderState(builder, buildInfoPath) {
5900
5987
  if (path.normalize(fileName) !== normalizedBuildInfo) return;
5901
5988
  const temporary = `${fileName}.${process$1.pid}.tmp`;
5902
5989
  try {
5903
- fs$1.writeFileSync(temporary, data);
5904
- fs$1.renameSync(temporary, fileName);
5990
+ fs.writeFileSync(temporary, data);
5991
+ fs.renameSync(temporary, fileName);
5905
5992
  } catch (err) {
5906
- fs$1.rmSync(temporary, { force: true });
5993
+ fs.rmSync(temporary, { force: true });
5907
5994
  throw err;
5908
5995
  }
5909
5996
  });
@@ -5918,7 +6005,7 @@ function persistBuilderState(builder, buildInfoPath) {
5918
6005
  */
5919
6006
  function writeGateState(gatePath, gateValue) {
5920
6007
  if (gateValue === void 0) {
5921
- fs$1.rmSync(gatePath, { force: true });
6008
+ fs.rmSync(gatePath, { force: true });
5922
6009
  return;
5923
6010
  }
5924
6011
  writeState(gatePath, gateValue);
@@ -5939,7 +6026,7 @@ function writeGateState(gatePath, gateValue) {
5939
6026
  * @returns The affected result.
5940
6027
  */
5941
6028
  function runBuilder({ buildInfoPath, gatePath, project, resolutionGate, ts }) {
5942
- const firstRun = !fs$1.existsSync(buildInfoPath);
6029
+ const firstRun = !fs.existsSync(buildInfoPath);
5943
6030
  const options = {
5944
6031
  ...project.options,
5945
6032
  composite: false,
@@ -6022,7 +6109,7 @@ function applyTypeAwareInvalidation(run, { alreadyDirty, cache, cacheLocation, m
6022
6109
  }
6023
6110
  const threshold = resolveAffectedBustThreshold(run.environment);
6024
6111
  if (affectedTargets.length > threshold) {
6025
- fs$1.rmSync(cacheLocation, { force: true });
6112
+ fs.rmSync(cacheLocation, { force: true });
6026
6113
  return {
6027
6114
  busted: true,
6028
6115
  firstRun: false,
@@ -6332,21 +6419,51 @@ async function runLint(argv, cwd = process$1.cwd(), environment = process$1.env,
6332
6419
  if (options.paths.length === 0) return 0;
6333
6420
  const staged = plan(options, resolveRunContext(cwd, environment, !options.print));
6334
6421
  const { commands, notice } = compose(staged.eager, options);
6422
+ const oxfmtConfig = findOxfmtModule(options, cwd);
6423
+ const formatCommand = oxfmtConfig === void 0 ? void 0 : composeOxfmtCommand(options, {
6424
+ configPath: oxfmtConfig,
6425
+ write: options.fix
6426
+ });
6335
6427
  if (options.print) {
6336
- for (const command of commands) process$1.stdout.write(`${formatCommandLine(command)}\n`);
6428
+ const printed = formatCommand === void 0 ? commands : [...commands, formatCommand];
6429
+ for (const command of printed) process$1.stdout.write(`${formatCommandLine(command)}\n`);
6337
6430
  for (const pass of staged.eager.passes) if (pass.shouldRun && pass.heap !== void 0) process$1.stderr.write(pass.heap.note);
6338
6431
  return 0;
6339
6432
  }
6340
6433
  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.");
6434
+ if (formatCommand !== void 0 && !isPackageExists("oxfmt", { paths: [cwd] })) throw new CliError("oxfmt is not installed, but this project has an `oxfmt.config.*` file. Install oxfmt, or pass --no-oxfmt to skip formatting.");
6341
6435
  if (notice !== void 0) process$1.stderr.write(notice);
6342
6436
  const { resolveFixChild } = staged;
6343
- if (resolveFixChild === void 0) return (await runChecks(commands, cwd, staged, options)).code;
6437
+ if (resolveFixChild === void 0) return (await runChecks(formatCommand === void 0 ? commands : [...commands, formatCommand], cwd, staged, options)).code;
6344
6438
  const byBin = Object.groupBy(commands, (command) => command.bin);
6345
6439
  const oxlintCode = await execute(byBin.oxlint ?? [], cwd, true);
6346
6440
  const checks = await runChecks(byBin.eslint ?? [], cwd, staged, options);
6347
6441
  const fixChild = resolveFixChild(checks.passes);
6348
- if (fixChild === void 0) return oxlintCode || checks.code;
6349
- return oxlintCode || await execute([fixChild], cwd, true);
6442
+ const lintCode = fixChild === void 0 ? oxlintCode || checks.code : oxlintCode || await execute([fixChild], cwd, true);
6443
+ if (formatCommand === void 0) return lintCode;
6444
+ const formatCode = await execute([formatCommand], cwd, true);
6445
+ return lintCode || formatCode;
6446
+ }
6447
+ /**
6448
+ * The oxfmt config module the run formats with. The oxfmt pass is on when the
6449
+ * project runs oxfmt as its own tool (an `oxfmt.config.ts` or
6450
+ * `oxfmt.config.mts`, found the way oxfmt finds it), unless `--no-oxfmt` or a
6451
+ * single-engine flag (`--eslint`, `--oxlint`) says otherwise.
6452
+ *
6453
+ * @param options - The parsed CLI options.
6454
+ * @param cwd - The working directory.
6455
+ * @returns The config module path, or `undefined` to skip the pass.
6456
+ * @throws {CliError} When one directory holds more than one oxfmt config.
6457
+ */
6458
+ function findOxfmtModule(options, cwd) {
6459
+ if (!options.oxfmt || options.eslint || options.oxlint) return;
6460
+ let config;
6461
+ try {
6462
+ config = findOxfmtConfig(cwd);
6463
+ } catch (err) {
6464
+ throw new CliError(err instanceof Error ? err.message : String(err));
6465
+ }
6466
+ return config?.isModule === true ? config.path : void 0;
6350
6467
  }
6351
6468
  /**
6352
6469
  * Run a run's ESLint check children and report which passes they covered.
@@ -6393,6 +6510,9 @@ Flags:
6393
6510
  --oxlint Run only oxlint.
6394
6511
  --fix Check as usual, then fix only the files the checks
6395
6512
  reported, in one further ESLint child.
6513
+ --no-oxfmt Skip the oxfmt pass. It runs when the project has an
6514
+ oxfmt.config.* file: oxfmt --check alongside the
6515
+ linters, or oxfmt --write last with --fix.
6396
6516
  --agents, --no-agents Emit agent-friendly output. On by default when an AI
6397
6517
  agent session is detected (AI_AGENT, CLAUDECODE, ...).
6398
6518
  --type-aware=off|only|full