@isentinel/eslint-config 6.1.0 → 6.3.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/README.md +106 -0
- package/dist/cli.mjs +231 -30
- package/dist/index.d.mts +55 -34
- package/dist/index.mjs +873 -209
- package/dist/lint-cli.mjs +234 -114
- package/dist/oxfmt.d.mts +132 -0
- package/dist/oxfmt.mjs +750 -0
- package/dist/oxlint.d.mts +14 -0
- package/dist/oxlint.mjs +417 -63
- package/package.json +8 -3
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
|
|
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.
|
|
45
|
+
var version = "6.3.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
|
|
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
|
|
838
|
-
fs
|
|
839
|
-
fs
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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,
|
|
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
|
|
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
|
|
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
|
|
5904
|
-
fs
|
|
5990
|
+
fs.writeFileSync(temporary, data);
|
|
5991
|
+
fs.renameSync(temporary, fileName);
|
|
5905
5992
|
} catch (err) {
|
|
5906
|
-
fs
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
6349
|
-
|
|
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
|