@hublo/sentinel 1.1.7 → 1.2.0-alpha.1
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 +5 -4
- package/dist/bin/sentinel.js +20 -4
- package/dist/{chunk-W73ISYMG.js → chunk-FLJ2QNFF.js} +1227 -430
- package/dist/chunk-XVDOQ3G3.js +39 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2 -1
- package/dist/roles/build/react-app.d.ts +112 -0
- package/dist/roles/build/react-app.js +126 -0
- package/lint/nest.json +358 -0
- package/lint/node.json +52 -0
- package/lint/react-lib.json +376 -0
- package/lint/react.json +474 -0
- package/lint/svelte.json +209 -0
- package/lint/tools.json +197 -0
- package/oxlint/nest.json +2 -355
- package/oxlint/node.json +3 -50
- package/oxlint/react-lib.json +2 -373
- package/oxlint/react.json +2 -471
- package/oxlint/svelte.json +3 -207
- package/oxlint/tools.json +3 -195
- package/package.json +21 -2
|
@@ -1,5 +1,21 @@
|
|
|
1
|
+
import {
|
|
2
|
+
REACT_APP_DEFAULTS,
|
|
3
|
+
REQUIREMENTS,
|
|
4
|
+
isPlainObject
|
|
5
|
+
} from "./chunk-XVDOQ3G3.js";
|
|
6
|
+
|
|
1
7
|
// src/core/registry.ts
|
|
2
8
|
var adapters = [];
|
|
9
|
+
var PresetUnsupportedError = class extends Error {
|
|
10
|
+
constructor(target, preset) {
|
|
11
|
+
super(`No adapter for target "${target}" handles preset "${preset}".`);
|
|
12
|
+
this.target = target;
|
|
13
|
+
this.preset = preset;
|
|
14
|
+
this.name = "PresetUnsupportedError";
|
|
15
|
+
}
|
|
16
|
+
target;
|
|
17
|
+
preset;
|
|
18
|
+
};
|
|
3
19
|
var defaultRunner = {
|
|
4
20
|
// Filled in by tool branches, e.g. lint: 'eslint', typescript: 'tsc'.
|
|
5
21
|
};
|
|
@@ -12,6 +28,14 @@ function setDefaultRunner(target, runner) {
|
|
|
12
28
|
function all() {
|
|
13
29
|
return adapters;
|
|
14
30
|
}
|
|
31
|
+
function declaredPresetFor(target, cwd) {
|
|
32
|
+
for (const adapter of adapters) {
|
|
33
|
+
if (adapter.target !== target) continue;
|
|
34
|
+
const declared = adapter.declaredPreset?.(cwd);
|
|
35
|
+
if (declared !== void 0) return declared;
|
|
36
|
+
}
|
|
37
|
+
return void 0;
|
|
38
|
+
}
|
|
15
39
|
function availableTargets() {
|
|
16
40
|
return [...new Set(adapters.map((a) => a.target))];
|
|
17
41
|
}
|
|
@@ -24,7 +48,7 @@ function resolve(target, preset, runner) {
|
|
|
24
48
|
}
|
|
25
49
|
const candidates = preset ? forTarget.filter((a) => a.appliesTo(preset)) : forTarget;
|
|
26
50
|
if (candidates.length === 0) {
|
|
27
|
-
throw new
|
|
51
|
+
throw new PresetUnsupportedError(target, preset);
|
|
28
52
|
}
|
|
29
53
|
const wanted = runner ?? defaultRunner[target];
|
|
30
54
|
const available = candidates.map((a) => a.runner).join(", ");
|
|
@@ -117,11 +141,16 @@ var TARGETS = [
|
|
|
117
141
|
"format",
|
|
118
142
|
"typescript",
|
|
119
143
|
"build",
|
|
144
|
+
"dev",
|
|
120
145
|
"test",
|
|
121
146
|
"static-analysis",
|
|
122
147
|
"runtime-analysis",
|
|
123
148
|
"arch"
|
|
124
149
|
];
|
|
150
|
+
var LONG_RUNNING_TARGETS = ["dev"];
|
|
151
|
+
var SWEEPABLE_TARGETS = TARGETS.filter(
|
|
152
|
+
(target) => !LONG_RUNNING_TARGETS.includes(target)
|
|
153
|
+
);
|
|
125
154
|
var PRESET_NAMES = ["react", "nest", "svelte", "node", "tools"];
|
|
126
155
|
|
|
127
156
|
// src/shared/color.ts
|
|
@@ -138,14 +167,67 @@ function palette(stream) {
|
|
|
138
167
|
};
|
|
139
168
|
}
|
|
140
169
|
|
|
141
|
-
// src/roles/
|
|
170
|
+
// src/roles/build/adapters/vite/vite-role.adapter.ts
|
|
142
171
|
import { spawnSync } from "child_process";
|
|
143
|
-
|
|
144
|
-
|
|
172
|
+
|
|
173
|
+
// src/core/config/tool-args.ts
|
|
174
|
+
import { existsSync as existsSync2 } from "fs";
|
|
175
|
+
import { isAbsolute, resolve as resolve2 } from "path";
|
|
176
|
+
function splitToolArgs(cwd, toolArgs = [], { valueFlags = [] } = {}) {
|
|
177
|
+
const takesValue = new Set(valueFlags);
|
|
178
|
+
const options = [];
|
|
179
|
+
const paths = [];
|
|
180
|
+
let previousTakesValue = false;
|
|
181
|
+
for (const arg of toolArgs) {
|
|
182
|
+
const looksLikeOption = arg.startsWith("-");
|
|
183
|
+
const target = isAbsolute(arg) ? arg : resolve2(cwd, arg);
|
|
184
|
+
if (!previousTakesValue && !looksLikeOption && existsSync2(target)) paths.push(arg);
|
|
185
|
+
else options.push(arg);
|
|
186
|
+
previousTakesValue = !arg.includes("=") && takesValue.has(arg);
|
|
187
|
+
}
|
|
188
|
+
return { options, paths };
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// src/shared/resolve-bin.ts
|
|
192
|
+
import { existsSync as existsSync3 } from "fs";
|
|
193
|
+
import { createRequire } from "module";
|
|
194
|
+
import { delimiter, dirname as dirname2, join as join2 } from "path";
|
|
195
|
+
var require2 = createRequire(import.meta.url);
|
|
196
|
+
function resolveBin(fromDir, name) {
|
|
197
|
+
let dir = fromDir;
|
|
198
|
+
for (; ; ) {
|
|
199
|
+
const candidate = join2(dir, "node_modules", ".bin", name);
|
|
200
|
+
if (existsSync3(candidate)) return candidate;
|
|
201
|
+
const parent = dirname2(dir);
|
|
202
|
+
if (parent === dir) return void 0;
|
|
203
|
+
dir = parent;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
function binFromOwnInstall(packageName, binName) {
|
|
207
|
+
try {
|
|
208
|
+
const manifest = require2.resolve(`${packageName}/package.json`);
|
|
209
|
+
const bin = require2(manifest).bin;
|
|
210
|
+
const relative4 = typeof bin === "string" ? bin : bin?.[binName];
|
|
211
|
+
if (!relative4) return void 0;
|
|
212
|
+
const executable = join2(dirname2(manifest), relative4);
|
|
213
|
+
return existsSync3(executable) ? executable : void 0;
|
|
214
|
+
} catch {
|
|
215
|
+
return void 0;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
function binSearchPath(cwd) {
|
|
219
|
+
return [`${cwd}${delimiter}node_modules/.bin (and parents)`, "sentinel's own install"].join(
|
|
220
|
+
" then "
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// src/roles/build/plan.ts
|
|
225
|
+
import { existsSync as existsSync7, readFileSync as readFileSync5 } from "fs";
|
|
226
|
+
import { join as join7 } from "path";
|
|
145
227
|
|
|
146
228
|
// src/core/config/existing-command.ts
|
|
147
229
|
import { readFileSync as readFileSync3 } from "fs";
|
|
148
|
-
import { join as
|
|
230
|
+
import { join as join4 } from "path";
|
|
149
231
|
|
|
150
232
|
// src/shared/jsonc.ts
|
|
151
233
|
import { parse, printParseErrorCode } from "jsonc-parser";
|
|
@@ -160,11 +242,11 @@ function parseJsonc(text, source = "config") {
|
|
|
160
242
|
}
|
|
161
243
|
|
|
162
244
|
// src/core/config/manifest.ts
|
|
163
|
-
import { existsSync as
|
|
164
|
-
import { basename, join as
|
|
245
|
+
import { existsSync as existsSync4, readFileSync as readFileSync2 } from "fs";
|
|
246
|
+
import { basename, join as join3 } from "path";
|
|
165
247
|
function moduleScripts(cwd) {
|
|
166
248
|
try {
|
|
167
|
-
const pkg = JSON.parse(readFileSync2(
|
|
249
|
+
const pkg = JSON.parse(readFileSync2(join3(cwd, "package.json"), "utf8"));
|
|
168
250
|
return pkg.scripts ?? {};
|
|
169
251
|
} catch {
|
|
170
252
|
return {};
|
|
@@ -172,7 +254,7 @@ function moduleScripts(cwd) {
|
|
|
172
254
|
}
|
|
173
255
|
function moduleName(cwd) {
|
|
174
256
|
try {
|
|
175
|
-
const pkg = JSON.parse(readFileSync2(
|
|
257
|
+
const pkg = JSON.parse(readFileSync2(join3(cwd, "package.json"), "utf8"));
|
|
176
258
|
return pkg.name;
|
|
177
259
|
} catch {
|
|
178
260
|
return void 0;
|
|
@@ -192,7 +274,7 @@ function manifestOperation(cwd, scripts) {
|
|
|
192
274
|
scripts,
|
|
193
275
|
devDependencies: { [own.name]: isSelfAdoption(cwd) ? "link:." : own.version }
|
|
194
276
|
};
|
|
195
|
-
if (
|
|
277
|
+
if (existsSync4(join3(cwd, "package.json"))) {
|
|
196
278
|
return { kind: "merge-json", path: "package.json", value };
|
|
197
279
|
}
|
|
198
280
|
return {
|
|
@@ -207,7 +289,7 @@ function nxTargetCommand(cwd, target) {
|
|
|
207
289
|
let project;
|
|
208
290
|
try {
|
|
209
291
|
project = parseJsonc(
|
|
210
|
-
readFileSync3(
|
|
292
|
+
readFileSync3(join4(cwd, "project.json"), "utf8"),
|
|
211
293
|
"project.json"
|
|
212
294
|
);
|
|
213
295
|
} catch {
|
|
@@ -237,9 +319,699 @@ function existingCommand(cwd, target) {
|
|
|
237
319
|
return fromTarget.ranInModule ? fromTarget.command : rootedForScript(fromTarget.command);
|
|
238
320
|
}
|
|
239
321
|
|
|
322
|
+
// src/core/config/nx-target.ts
|
|
323
|
+
import { existsSync as existsSync5 } from "fs";
|
|
324
|
+
import { join as join5 } from "path";
|
|
325
|
+
function nxTargetOperations(options) {
|
|
326
|
+
const { cwd, targets } = options;
|
|
327
|
+
const names = Object.keys(targets);
|
|
328
|
+
if (names.length === 0) return [];
|
|
329
|
+
const operations = [];
|
|
330
|
+
if (existsSync5(join5(cwd, "project.json"))) {
|
|
331
|
+
operations.push({
|
|
332
|
+
kind: "remove-json-keys",
|
|
333
|
+
path: "project.json",
|
|
334
|
+
keys: names.map((name) => ["targets", name])
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
operations.push({
|
|
338
|
+
kind: "merge-json",
|
|
339
|
+
path: "package.json",
|
|
340
|
+
value: { nx: { targets } }
|
|
341
|
+
});
|
|
342
|
+
return operations;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// src/core/config/tool-script.ts
|
|
346
|
+
var SEGMENT_SEPARATOR = /(\s*(?:&&|\|\||;|&|\|)\s*)/;
|
|
347
|
+
function isSeparatorAt(index) {
|
|
348
|
+
return index % 2 === 1;
|
|
349
|
+
}
|
|
350
|
+
function invokes(segment, binary) {
|
|
351
|
+
return new RegExp(String.raw`(^|[\s/])${binary}(\s|$)`).test(segment.trim());
|
|
352
|
+
}
|
|
353
|
+
function isSentinelSegment(segment, roleFlag) {
|
|
354
|
+
const runsSentinel = invokes(segment, "sentinel") || segment.includes("bin/sentinel.js");
|
|
355
|
+
return runsSentinel && new RegExp(String.raw`(^|\s)${roleFlag}(\s|$)`).test(segment);
|
|
356
|
+
}
|
|
357
|
+
function composeToolScript(existing, options) {
|
|
358
|
+
const { command } = options;
|
|
359
|
+
if (!existing || existing.trim() === "") return command;
|
|
360
|
+
const { roleFlag } = options;
|
|
361
|
+
const isReplaceable = (segment) => options.replaces(segment) || roleFlag !== void 0 && isSentinelSegment(segment, roleFlag);
|
|
362
|
+
const parts = existing.split(SEGMENT_SEPARATOR);
|
|
363
|
+
const commands = parts.filter((_, index) => !isSeparatorAt(index));
|
|
364
|
+
if (!commands.some(isReplaceable)) return existing;
|
|
365
|
+
let replacedOnce = false;
|
|
366
|
+
const rebuilt = parts.map((part, index) => {
|
|
367
|
+
if (isSeparatorAt(index) || !isReplaceable(part)) return part;
|
|
368
|
+
if (replacedOnce) return null;
|
|
369
|
+
replacedOnce = true;
|
|
370
|
+
return options.keepFix && /(^|\s)--fix(\s|$)/.test(part) ? `${command} --fix` : command;
|
|
371
|
+
});
|
|
372
|
+
const kept = [];
|
|
373
|
+
for (let index = 0; index < rebuilt.length; index += 1) {
|
|
374
|
+
const part = rebuilt[index];
|
|
375
|
+
if (part === null) {
|
|
376
|
+
if (kept.length > 0) kept.pop();
|
|
377
|
+
continue;
|
|
378
|
+
}
|
|
379
|
+
kept.push(part);
|
|
380
|
+
}
|
|
381
|
+
return kept.join("").trim();
|
|
382
|
+
}
|
|
383
|
+
function keepsOtherCommands(script, sentinelCommand) {
|
|
384
|
+
return script.split(SEGMENT_SEPARATOR).filter((_, index) => !isSeparatorAt(index)).some((segment) => segment.trim() !== "" && segment.trim() !== sentinelCommand);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// src/roles/build/config-policy.ts
|
|
388
|
+
var BUILD_CONFIG_FILES = [
|
|
389
|
+
"vite.config.ts",
|
|
390
|
+
"vite.config.mts",
|
|
391
|
+
"vite.config.js",
|
|
392
|
+
"vite.config.mjs"
|
|
393
|
+
];
|
|
394
|
+
var BUILD_PRESET_SPECIFIER = "@hublo/sentinel/build/react";
|
|
395
|
+
var BUILD_SCRIPT_NAME = "build";
|
|
396
|
+
var SENTINEL_BUILD_COMMAND = "sentinel --run --build";
|
|
397
|
+
var DEV_SCRIPT_NAME = "serve";
|
|
398
|
+
var SENTINEL_DEV_COMMAND = "sentinel --run --dev";
|
|
399
|
+
function buildTarget() {
|
|
400
|
+
return {
|
|
401
|
+
[BUILD_SCRIPT_NAME]: {
|
|
402
|
+
cache: true,
|
|
403
|
+
inputs: ["default", "^default", ...BUILD_CONFIG_FILES.map((name) => `{projectRoot}/${name}`)]
|
|
404
|
+
}
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
var SENTINEL_OWNED_BUILD_PACKAGES = [
|
|
408
|
+
"vite",
|
|
409
|
+
"@vitejs/plugin-react",
|
|
410
|
+
"@tailwindcss/vite",
|
|
411
|
+
"vite-plugin-svgr",
|
|
412
|
+
"nitro"
|
|
413
|
+
];
|
|
414
|
+
|
|
415
|
+
// src/roles/build/dev-script.ts
|
|
416
|
+
var CHAIN = " -- ";
|
|
417
|
+
var VITE_TOKEN = /(^|\s|\/)vite(\s|$)/;
|
|
418
|
+
var DEV_SUBCOMMANDS = /* @__PURE__ */ new Set(["dev", "serve"]);
|
|
419
|
+
function appOptions(rest) {
|
|
420
|
+
const tokens = (rest ?? "").trim().split(/\s+/).filter(Boolean);
|
|
421
|
+
const kept = tokens.filter((token, at2) => !(at2 === 0 && DEV_SUBCOMMANDS.has(token)));
|
|
422
|
+
return kept.join(" ");
|
|
423
|
+
}
|
|
424
|
+
function composeDevScript(existing, command) {
|
|
425
|
+
if (existing === void 0 || existing.trim() === "") return command;
|
|
426
|
+
const chunks = existing.trim().split(CHAIN);
|
|
427
|
+
const at2 = chunks.map((chunk) => VITE_TOKEN.test(chunk)).lastIndexOf(true);
|
|
428
|
+
if (at2 === -1) return existing;
|
|
429
|
+
const rest = (chunks[at2] ?? "").replace(/^.*?(^|\s|\/)vite(\s|$)/, "");
|
|
430
|
+
const options = appOptions(rest);
|
|
431
|
+
chunks[at2] = options === "" ? command : `${command} -- ${options}`;
|
|
432
|
+
return chunks.join(CHAIN);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// src/roles/build/read-adoption.ts
|
|
436
|
+
import { existsSync as existsSync6, readFileSync as readFileSync4 } from "fs";
|
|
437
|
+
import { join as join6 } from "path";
|
|
438
|
+
var NOT_ADOPTED = (configFile, unreadable = null) => ({
|
|
439
|
+
configFile,
|
|
440
|
+
preset: null,
|
|
441
|
+
adopted: false,
|
|
442
|
+
conformant: false,
|
|
443
|
+
drift: [],
|
|
444
|
+
ownDeclarations: [],
|
|
445
|
+
unreadable
|
|
446
|
+
});
|
|
447
|
+
function buildConfigFile(cwd) {
|
|
448
|
+
return BUILD_CONFIG_FILES.find((name) => existsSync6(join6(cwd, name)));
|
|
449
|
+
}
|
|
450
|
+
function importsSpecifier(source, specifier) {
|
|
451
|
+
const escaped = specifier.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
452
|
+
return new RegExp(`(?:from|import)\\s*\\(?\\s*['"\`]${escaped}(?:/[^'"\`]*)?['"\`]`).test(source);
|
|
453
|
+
}
|
|
454
|
+
function importsPreset(source) {
|
|
455
|
+
return importsSpecifier(source, BUILD_PRESET_SPECIFIER);
|
|
456
|
+
}
|
|
457
|
+
var REACT_PLUGIN_SPECIFIERS = ["@vitejs/plugin-react", "@tanstack/react-start"];
|
|
458
|
+
function declaredBuildPreset(cwd) {
|
|
459
|
+
const source = readBuildConfigSource(cwd);
|
|
460
|
+
if (source === void 0) return void 0;
|
|
461
|
+
return REACT_PLUGIN_SPECIFIERS.some((specifier) => importsSpecifier(source, specifier)) ? "react" : void 0;
|
|
462
|
+
}
|
|
463
|
+
function readBuildConfigSource(cwd) {
|
|
464
|
+
const configFile = buildConfigFile(cwd);
|
|
465
|
+
if (!configFile) return void 0;
|
|
466
|
+
try {
|
|
467
|
+
return readFileSync4(join6(cwd, configFile), "utf8");
|
|
468
|
+
} catch {
|
|
469
|
+
return void 0;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
function readBuildAdoption(cwd) {
|
|
473
|
+
const configFile = buildConfigFile(cwd);
|
|
474
|
+
if (!configFile) return NOT_ADOPTED(null);
|
|
475
|
+
let source;
|
|
476
|
+
try {
|
|
477
|
+
source = readFileSync4(join6(cwd, configFile), "utf8");
|
|
478
|
+
} catch (error) {
|
|
479
|
+
return NOT_ADOPTED(configFile, error instanceof Error ? error.message : String(error));
|
|
480
|
+
}
|
|
481
|
+
if (!importsPreset(source)) return NOT_ADOPTED(configFile);
|
|
482
|
+
const manifest = readProjectPackageJson(cwd);
|
|
483
|
+
const declared = { ...manifest.dependencies, ...manifest.devDependencies };
|
|
484
|
+
const ownDeclarations = SENTINEL_OWNED_BUILD_PACKAGES.filter((name) => name in declared).map(
|
|
485
|
+
(name) => ({ name, version: declared[name] })
|
|
486
|
+
);
|
|
487
|
+
return {
|
|
488
|
+
configFile,
|
|
489
|
+
preset: "react",
|
|
490
|
+
adopted: true,
|
|
491
|
+
conformant: ownDeclarations.length === 0,
|
|
492
|
+
drift: ownDeclarations.map(
|
|
493
|
+
({ name, version }) => `declares ${name}@${version} of its own; sentinel owns it, and two copies in one build give the plugins a different Vite than the one running them`
|
|
494
|
+
),
|
|
495
|
+
ownDeclarations,
|
|
496
|
+
unreadable: null
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// src/roles/build/requirements.ts
|
|
501
|
+
function atLeast(declared, needed) {
|
|
502
|
+
const parse2 = (value) => value.replace(/^[\^~>=<\s]+/, "").split("-")[0].split(".").map((part) => Number.parseInt(part, 10) || 0);
|
|
503
|
+
const left = parse2(declared);
|
|
504
|
+
const right = parse2(needed);
|
|
505
|
+
for (let at2 = 0; at2 < Math.max(left.length, right.length); at2++) {
|
|
506
|
+
const a = left[at2] ?? 0;
|
|
507
|
+
const b = right[at2] ?? 0;
|
|
508
|
+
if (a !== b) return a > b;
|
|
509
|
+
}
|
|
510
|
+
return true;
|
|
511
|
+
}
|
|
512
|
+
function unmetRequirements(preset, declared) {
|
|
513
|
+
const entry = REQUIREMENTS[preset];
|
|
514
|
+
if (!entry) return [];
|
|
515
|
+
return Object.entries(entry.requires).filter(([name, needed]) => {
|
|
516
|
+
const have = declared[name];
|
|
517
|
+
return have === void 0 || !atLeast(have, needed);
|
|
518
|
+
}).map(([name, needed]) => ({ name, declared: declared[name], needed }));
|
|
519
|
+
}
|
|
520
|
+
function describeUnmet(preset, unmet) {
|
|
521
|
+
const entry = REQUIREMENTS[preset];
|
|
522
|
+
const list = unmet.map(
|
|
523
|
+
({ name, declared, needed }) => `${name} ${declared === void 0 ? "is not declared here" : `is at ${declared}`}, needs >= ${needed}`
|
|
524
|
+
).join("; ");
|
|
525
|
+
return `the build role does not apply to this module yet: ${list}. ${entry?.why ?? ""}. Nothing was written. Raise those versions, check the module still builds and its tests still pass, then re-run \`sentinel --init --build\`.`;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// src/roles/build/plan.ts
|
|
529
|
+
function scaffold() {
|
|
530
|
+
return `import { defineConfig, reactApp } from '${BUILD_PRESET_SPECIFIER}'
|
|
531
|
+
import { tanstackStart } from '@tanstack/react-start/plugin/vite'
|
|
532
|
+
import { tanstackRouter } from '@tanstack/router-plugin/vite'
|
|
533
|
+
import path from 'node:path'
|
|
534
|
+
|
|
535
|
+
/*
|
|
536
|
+
* Composition comes from sentinel: which plugins run under test and which under a real build,
|
|
537
|
+
* the order they run in, and how the env is read. The values below are this app's own.
|
|
538
|
+
*
|
|
539
|
+
* TanStack is passed IN rather than imported by sentinel, because the app codes against it
|
|
540
|
+
* directly. \`alias\` is passed through verbatim and never read, so nothing in it can be lost.
|
|
541
|
+
* Anything sentinel does not set goes in \`overrides\`, which merges over the preset last.
|
|
542
|
+
*/
|
|
543
|
+
export default defineConfig(({ mode }) =>
|
|
544
|
+
reactApp({
|
|
545
|
+
root: __dirname,
|
|
546
|
+
mode,
|
|
547
|
+
base: '/',
|
|
548
|
+
port: 3000,
|
|
549
|
+
// Explicit, NOT port + 1: the three apps in this repo disagree, one of them goes down.
|
|
550
|
+
hmrPort: 3001,
|
|
551
|
+
router: {
|
|
552
|
+
routesDirectory: path.resolve(__dirname, 'src/routes'),
|
|
553
|
+
generatedRouteTree: path.resolve(__dirname, 'src/routeTree.gen.ts'),
|
|
554
|
+
},
|
|
555
|
+
tanstack: { start: tanstackStart, router: tanstackRouter },
|
|
556
|
+
alias: [],
|
|
557
|
+
}),
|
|
558
|
+
)
|
|
559
|
+
|
|
560
|
+
// If this app reads environment variables, use \`appEnv(__dirname, mode)\` rather than Vite's
|
|
561
|
+
// \`loadEnv\`: it applies the production -> prd normalisation this repo deploys under, which
|
|
562
|
+
// every app was otherwise retyping.
|
|
563
|
+
`;
|
|
564
|
+
}
|
|
565
|
+
function ownedBuildDependencies(cwd) {
|
|
566
|
+
const manifest = readProjectPackageJson(cwd);
|
|
567
|
+
const keys = [];
|
|
568
|
+
for (const section of ["dependencies", "devDependencies"]) {
|
|
569
|
+
const declared = manifest[section];
|
|
570
|
+
if (!declared) continue;
|
|
571
|
+
for (const name of SENTINEL_OWNED_BUILD_PACKAGES) {
|
|
572
|
+
if (name in declared) keys.push([section, name]);
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
return keys;
|
|
576
|
+
}
|
|
577
|
+
function invokesVite(segment) {
|
|
578
|
+
return /(^|\s|\/)vite(\s|$)/.test(segment.trim());
|
|
579
|
+
}
|
|
580
|
+
function buildScripts(cwd) {
|
|
581
|
+
const own = selfCommand(cwd, "--run --build");
|
|
582
|
+
const scripts = {
|
|
583
|
+
[BUILD_SCRIPT_NAME]: composeToolScript(existingCommand(cwd, BUILD_SCRIPT_NAME), {
|
|
584
|
+
command: own ?? SENTINEL_BUILD_COMMAND,
|
|
585
|
+
replaces: invokesVite,
|
|
586
|
+
// Only meaningful for the self case: it lets a re-init correct a sentinel invocation
|
|
587
|
+
// whose FORM is wrong, without matching another role's script.
|
|
588
|
+
roleFlag: own === void 0 ? void 0 : "--build"
|
|
589
|
+
})
|
|
590
|
+
};
|
|
591
|
+
const existingDev = existingCommand(cwd, DEV_SCRIPT_NAME);
|
|
592
|
+
if (existingDev !== void 0) {
|
|
593
|
+
scripts[DEV_SCRIPT_NAME] = composeDevScript(
|
|
594
|
+
existingDev,
|
|
595
|
+
selfCommand(cwd, "--run --dev") ?? SENTINEL_DEV_COMMAND
|
|
596
|
+
);
|
|
597
|
+
}
|
|
598
|
+
return scripts;
|
|
599
|
+
}
|
|
600
|
+
function plan(context) {
|
|
601
|
+
const manifest = readProjectPackageJson(context.cwd);
|
|
602
|
+
const unmet = unmetRequirements(context.preset, {
|
|
603
|
+
...manifest.dependencies,
|
|
604
|
+
...manifest.devDependencies
|
|
605
|
+
});
|
|
606
|
+
if (unmet.length > 0) {
|
|
607
|
+
return { operations: [], skipped: describeUnmet(context.preset, unmet) };
|
|
608
|
+
}
|
|
609
|
+
if (declaredBuildPreset(context.cwd) === void 0 && buildConfigFile(context.cwd) !== void 0) {
|
|
610
|
+
return {
|
|
611
|
+
operations: [],
|
|
612
|
+
skipped: `this module has a Vite config, but it does not build a React app (no @vitejs/plugin-react or @tanstack/react-start in it). The build role ships a React preset only.`
|
|
613
|
+
};
|
|
614
|
+
}
|
|
615
|
+
const notes = [];
|
|
616
|
+
const operations = [];
|
|
617
|
+
const configFile = buildConfigFile(context.cwd);
|
|
618
|
+
if (configFile === void 0) {
|
|
619
|
+
operations.push({ kind: "write", path: BUILD_CONFIG_FILES[0], contents: scaffold() });
|
|
620
|
+
notes.push(
|
|
621
|
+
`wrote ${BUILD_CONFIG_FILES[0]} as a starting point. Fill in this app's own values (base, ports, routes); sentinel owns the composition, not the data.`
|
|
622
|
+
);
|
|
623
|
+
} else if (!readsPreset(context.cwd, configFile)) {
|
|
624
|
+
notes.push(
|
|
625
|
+
`${configFile} is this app's own, so it was left alone. To adopt: import \`reactApp\` from \`${BUILD_PRESET_SPECIFIER}\` and pass it this app's values, keeping \`alias\` and your own plugins verbatim. \`sentinel --inspect --build\` reports what the resolved config departs from and what it adds, before and after.`
|
|
626
|
+
);
|
|
627
|
+
}
|
|
628
|
+
operations.push(manifestOperation(context.cwd, buildScripts(context.cwd)));
|
|
629
|
+
operations.push(...nxTargetOperations({ cwd: context.cwd, targets: buildTarget() }));
|
|
630
|
+
const owned = ownedBuildDependencies(context.cwd);
|
|
631
|
+
if (owned.length > 0) {
|
|
632
|
+
operations.push({ kind: "remove-json-keys", path: "package.json", keys: owned });
|
|
633
|
+
notes.push(
|
|
634
|
+
`removed ${owned.map(([, name]) => name).join(", ")} from this module: sentinel owns them now, and two copies of Vite in one build give the plugins a different Vite than the one running them.`
|
|
635
|
+
);
|
|
636
|
+
}
|
|
637
|
+
return { operations, notes };
|
|
638
|
+
}
|
|
639
|
+
function readsPreset(cwd, configFile) {
|
|
640
|
+
if (!existsSync7(join7(cwd, configFile))) return false;
|
|
641
|
+
try {
|
|
642
|
+
return readFileSync5(join7(cwd, configFile), "utf8").includes(BUILD_PRESET_SPECIFIER);
|
|
643
|
+
} catch {
|
|
644
|
+
return false;
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
// src/roles/build/resolve-vite.ts
|
|
649
|
+
function resolveViteWith(cwd) {
|
|
650
|
+
const own = binFromOwnInstall("vite", "vite");
|
|
651
|
+
if (own) return { bin: own, origin: "sentinel" };
|
|
652
|
+
const module_ = resolveBin(cwd, "vite");
|
|
653
|
+
return module_ ? { bin: module_, origin: "module" } : { bin: void 0, origin: "none" };
|
|
654
|
+
}
|
|
655
|
+
function viteOrigin(cwd) {
|
|
656
|
+
return resolveViteWith(cwd).origin;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
// src/roles/build/adapters/vite/vite-role.adapter.ts
|
|
660
|
+
var VITE_VALUE_FLAGS = [
|
|
661
|
+
"--config",
|
|
662
|
+
"-c",
|
|
663
|
+
"--mode",
|
|
664
|
+
"-m",
|
|
665
|
+
"--outDir",
|
|
666
|
+
"--logLevel",
|
|
667
|
+
"--base",
|
|
668
|
+
"--host",
|
|
669
|
+
"--port"
|
|
670
|
+
];
|
|
671
|
+
var ViteRoleAdapter = class extends BaseAdapter {
|
|
672
|
+
runner = "vite";
|
|
673
|
+
/**
|
|
674
|
+
* The presets this role has something to SAY about, which is wider than the ones it serves.
|
|
675
|
+
*
|
|
676
|
+
* React it serves. Svelte it does not, yet, and the difference matters to a developer: the
|
|
677
|
+
* plugin caps at Vite 6 until its major 7, which needs `svelte ^5.46.4`, so the role applies
|
|
678
|
+
* once the module moves. `plan` reports that chain and SKIPS, which it can only do if
|
|
679
|
+
* resolution let it run at all — filtering here would give "no adapter for preset svelte",
|
|
680
|
+
* which tells nobody what to do about it.
|
|
681
|
+
*
|
|
682
|
+
* Nest, node and tools are genuinely declined: there is nothing to bundle, and no version
|
|
683
|
+
* would change that.
|
|
684
|
+
*/
|
|
685
|
+
appliesTo(preset) {
|
|
686
|
+
return preset === "react" || preset === "svelte";
|
|
687
|
+
}
|
|
688
|
+
/**
|
|
689
|
+
* `react`, read from the module's Vite config rather than from its dependencies.
|
|
690
|
+
*
|
|
691
|
+
* Without this the role is unreachable in practice: the toolchain is declared at the ROOT,
|
|
692
|
+
* so every front app detects as `node` and `appliesTo` filters the adapter out — including
|
|
693
|
+
* during `sentinel --inspect` with no target, which never passes `--preset`.
|
|
694
|
+
*/
|
|
695
|
+
declaredPreset(cwd) {
|
|
696
|
+
return declaredBuildPreset(cwd);
|
|
697
|
+
}
|
|
698
|
+
/**
|
|
699
|
+
* The same plan for both targets, deliberately.
|
|
700
|
+
*
|
|
701
|
+
* `build` and `serve` are one app's two calls into one toolchain, so adopting one without the
|
|
702
|
+
* other leaves the module half-migrated in a way nothing reports. The operations are
|
|
703
|
+
* idempotent, so `--init --build` and `--init --dev` are interchangeable rather than additive.
|
|
704
|
+
*/
|
|
705
|
+
plan(context) {
|
|
706
|
+
return plan(context);
|
|
707
|
+
}
|
|
708
|
+
async status(ctx) {
|
|
709
|
+
const { adopted, preset, conformant, drift, unreadable } = readBuildAdoption(ctx.cwd);
|
|
710
|
+
return { adopted, preset, conformant, drift, unreadable };
|
|
711
|
+
}
|
|
712
|
+
/**
|
|
713
|
+
* Adoption, the binary, and the warning about which copy of Vite is running.
|
|
714
|
+
*
|
|
715
|
+
* A module that has not adopted is reported and PASSES, the same stance every other role
|
|
716
|
+
* takes: `--run` with no named target sweeps every wired target across the workspace, so
|
|
717
|
+
* failing here would exit non-zero on every module that has not migrated, which is most of
|
|
718
|
+
* them.
|
|
719
|
+
*/
|
|
720
|
+
prepare(ctx) {
|
|
721
|
+
if (!readBuildAdoption(ctx.cwd).adopted) {
|
|
722
|
+
this.say(
|
|
723
|
+
`no sentinel preset in this module's Vite config; run \`sentinel --init --build\` to adopt.`
|
|
724
|
+
);
|
|
725
|
+
return { kind: "stop", result: { ok: true, code: 0 } };
|
|
726
|
+
}
|
|
727
|
+
const { bin, origin } = resolveViteWith(ctx.cwd);
|
|
728
|
+
if (!bin) {
|
|
729
|
+
this.say(
|
|
730
|
+
`could not find the vite binary (looked in ${binSearchPath(ctx.cwd)}). Run \`pnpm install\` in the module.`
|
|
731
|
+
);
|
|
732
|
+
return { kind: "stop", result: { ok: false, code: 1 } };
|
|
733
|
+
}
|
|
734
|
+
if (origin === "module") {
|
|
735
|
+
this.say(
|
|
736
|
+
`using the MODULE's vite, not sentinel's. An adopted config gets its plugins from sentinel, and two copies of Vite fail in ways that never mention a version. Remove vite from this module's package.json.`
|
|
737
|
+
);
|
|
738
|
+
}
|
|
739
|
+
return { kind: "ready", vite: bin };
|
|
740
|
+
}
|
|
741
|
+
/**
|
|
742
|
+
* Run Vite with the caller's own arguments after `--`, and report what happened.
|
|
743
|
+
*
|
|
744
|
+
* `whenSignalled` is the only difference between the two targets here, and it is a real one:
|
|
745
|
+
* a build killed by a signal did not produce a bundle, while a dev server killed by Ctrl-C
|
|
746
|
+
* did exactly what the developer asked.
|
|
747
|
+
*/
|
|
748
|
+
spawnVite(ctx, vite, argv, whenSignalled) {
|
|
749
|
+
const { options, paths } = splitToolArgs(ctx.cwd, ctx.toolArgs, {
|
|
750
|
+
valueFlags: VITE_VALUE_FLAGS
|
|
751
|
+
});
|
|
752
|
+
const result = spawnSync(vite, [...argv, ...options, ...paths], {
|
|
753
|
+
cwd: ctx.cwd,
|
|
754
|
+
stdio: "inherit"
|
|
755
|
+
});
|
|
756
|
+
if (result.error) {
|
|
757
|
+
this.say(`could not run vite (${result.error.message})`);
|
|
758
|
+
return { ok: false, code: 1 };
|
|
759
|
+
}
|
|
760
|
+
const code = result.status ?? whenSignalled;
|
|
761
|
+
return { ok: code === 0, code };
|
|
762
|
+
}
|
|
763
|
+
/** One prefix for every message this role emits, so they cannot drift apart. */
|
|
764
|
+
say(message) {
|
|
765
|
+
process.stderr.write(`sentinel ${this.label}(vite): ${message}
|
|
766
|
+
`);
|
|
767
|
+
}
|
|
768
|
+
};
|
|
769
|
+
|
|
770
|
+
// src/roles/build/adapters/vite/vite-dev.adapter.ts
|
|
771
|
+
var ViteDevAdapter = class extends ViteRoleAdapter {
|
|
772
|
+
target = "dev";
|
|
773
|
+
get label() {
|
|
774
|
+
return "dev";
|
|
775
|
+
}
|
|
776
|
+
/**
|
|
777
|
+
* Start the dev server. It does not return until stopped, so there is no verdict to report
|
|
778
|
+
* beyond the exit code the developer's own Ctrl-C produces.
|
|
779
|
+
*
|
|
780
|
+
* No `prebuild` here, unlike `--run --build`: the artefact this repo generates before a build
|
|
781
|
+
* is produced by the watcher that WRAPS this command (`run-with-runtime-artifact-watch`), and
|
|
782
|
+
* running it again would race the watcher that is about to own the file.
|
|
783
|
+
*
|
|
784
|
+
* No subcommand either. `vite`, `vite dev` and `vite serve` all start the server, and the
|
|
785
|
+
* bare form is the one every version accepts.
|
|
786
|
+
*/
|
|
787
|
+
async run(ctx) {
|
|
788
|
+
const ready = this.prepare(ctx);
|
|
789
|
+
if (ready.kind === "stop") return ready.result;
|
|
790
|
+
return this.spawnVite(ctx, ready.vite, [], 0);
|
|
791
|
+
}
|
|
792
|
+
};
|
|
793
|
+
|
|
794
|
+
// src/roles/build/owned-paths.ts
|
|
795
|
+
var PRESET_OWNED_KEYS = [
|
|
796
|
+
"base",
|
|
797
|
+
"root",
|
|
798
|
+
"define",
|
|
799
|
+
"server",
|
|
800
|
+
"build",
|
|
801
|
+
"nitro",
|
|
802
|
+
"resolve",
|
|
803
|
+
"plugins"
|
|
804
|
+
];
|
|
805
|
+
function describeDepartures(config, defaults) {
|
|
806
|
+
const departures = [];
|
|
807
|
+
const build = asRecord(config.build);
|
|
808
|
+
const target = build?.target;
|
|
809
|
+
if (typeof target === "string" && target !== defaults.target) {
|
|
810
|
+
departures.push({
|
|
811
|
+
rule: "build.target",
|
|
812
|
+
reason: `${target}, where the convention is ${defaults.target}`
|
|
813
|
+
});
|
|
814
|
+
}
|
|
815
|
+
const server = asRecord(config.server);
|
|
816
|
+
const hosts = server?.allowedHosts;
|
|
817
|
+
if (Array.isArray(hosts) && !sameStrings(hosts, defaults.allowedHosts)) {
|
|
818
|
+
departures.push({
|
|
819
|
+
rule: "server.allowedHosts",
|
|
820
|
+
reason: `${JSON.stringify(hosts)}, where the convention is ${JSON.stringify(defaults.allowedHosts)}`
|
|
821
|
+
});
|
|
822
|
+
}
|
|
823
|
+
return departures;
|
|
824
|
+
}
|
|
825
|
+
function appAdditions(config) {
|
|
826
|
+
const owned = new Set(PRESET_OWNED_KEYS);
|
|
827
|
+
return Object.keys(config).filter((key) => !owned.has(key)).sort();
|
|
828
|
+
}
|
|
829
|
+
function asRecord(value) {
|
|
830
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
831
|
+
}
|
|
832
|
+
function sameStrings(a, b) {
|
|
833
|
+
return a.length === b.length && a.every((value, at2) => value === b[at2]);
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
// src/roles/build/prerequisite.ts
|
|
837
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
838
|
+
var PREBUILD_SCRIPT_NAME = `pre${BUILD_SCRIPT_NAME}`;
|
|
839
|
+
function insideBuildScript(env = process.env) {
|
|
840
|
+
return env.npm_lifecycle_event === BUILD_SCRIPT_NAME;
|
|
841
|
+
}
|
|
842
|
+
function runPrerequisite(cwd, env = process.env) {
|
|
843
|
+
const scripts = moduleScripts(cwd);
|
|
844
|
+
const script = scripts[PREBUILD_SCRIPT_NAME];
|
|
845
|
+
if (script === void 0 || script.trim() === "") return { kind: "none" };
|
|
846
|
+
if (insideBuildScript(env)) return { kind: "already-run" };
|
|
847
|
+
const result = spawnSync2("pnpm", ["run", PREBUILD_SCRIPT_NAME], { cwd, stdio: "inherit" });
|
|
848
|
+
if (result.error) {
|
|
849
|
+
return { kind: "failed", code: 1, reason: result.error.message };
|
|
850
|
+
}
|
|
851
|
+
const code = result.status ?? 1;
|
|
852
|
+
if (code !== 0) {
|
|
853
|
+
return {
|
|
854
|
+
kind: "failed",
|
|
855
|
+
code,
|
|
856
|
+
reason: `\`pnpm run ${PREBUILD_SCRIPT_NAME}\` exited ${code}`
|
|
857
|
+
};
|
|
858
|
+
}
|
|
859
|
+
return { kind: "ran" };
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
// src/roles/build/resolve-config.ts
|
|
863
|
+
async function resolveBuildConfig(cwd) {
|
|
864
|
+
try {
|
|
865
|
+
const { loadConfigFromFile } = await import("vite");
|
|
866
|
+
const loaded = await loadConfigFromFile(
|
|
867
|
+
{ command: "build", mode: "production" },
|
|
868
|
+
void 0,
|
|
869
|
+
cwd,
|
|
870
|
+
"silent"
|
|
871
|
+
);
|
|
872
|
+
if (!loaded) return { kind: "failed", reason: "Vite found no config file in this module" };
|
|
873
|
+
return { kind: "loaded", config: loaded.config, from: loaded.path };
|
|
874
|
+
} catch (error) {
|
|
875
|
+
return { kind: "failed", reason: error instanceof Error ? error.message : String(error) };
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
// src/roles/build/adapters/vite/vite.adapter.ts
|
|
880
|
+
var ViteAdapter = class extends ViteRoleAdapter {
|
|
881
|
+
target = "build";
|
|
882
|
+
get label() {
|
|
883
|
+
return "build";
|
|
884
|
+
}
|
|
885
|
+
/**
|
|
886
|
+
* Build the module.
|
|
887
|
+
*
|
|
888
|
+
* Adoption, the binary and the two-copies warning are `prepare`'s, shared with `--dev`. What
|
|
889
|
+
* is this target's own is the `prebuild` step and the `build` subcommand.
|
|
890
|
+
*/
|
|
891
|
+
async run(ctx) {
|
|
892
|
+
const ready = this.prepare(ctx);
|
|
893
|
+
if (ready.kind === "stop") return ready.result;
|
|
894
|
+
const prerequisite = runPrerequisite(ctx.cwd);
|
|
895
|
+
if (prerequisite.kind === "failed") {
|
|
896
|
+
this.say(
|
|
897
|
+
`the ${PREBUILD_SCRIPT_NAME} step failed, so the build was not started: ${prerequisite.reason}`
|
|
898
|
+
);
|
|
899
|
+
return { ok: false, code: prerequisite.code };
|
|
900
|
+
}
|
|
901
|
+
return this.spawnVite(ctx, ready.vite, ["build"], 1);
|
|
902
|
+
}
|
|
903
|
+
/**
|
|
904
|
+
* What this module builds with, without building it.
|
|
905
|
+
*
|
|
906
|
+
* `--inspect` reads the committed config for every role, and the moment `--build` was wired
|
|
907
|
+
* it joined the sweep — so leaving this to throw would have broken `sentinel --inspect` on
|
|
908
|
+
* every React module, for a role that had only just arrived. Cheap and honest is the bar.
|
|
909
|
+
*
|
|
910
|
+
* `vite` is the interesting field and the one nothing else reports: an adopted config takes
|
|
911
|
+
* its plugins from sentinel, so a module answering `module` here is one build away from the
|
|
912
|
+
* two-copies failure, and this is where that is visible before it happens.
|
|
913
|
+
*
|
|
914
|
+
* The resolved config comes from Vite's own loader rather than from parsing the file. See
|
|
915
|
+
* `resolve-config` for why that is the only honest answer here, and what it costs.
|
|
916
|
+
*/
|
|
917
|
+
async inspect(ctx) {
|
|
918
|
+
const adoption = readBuildAdoption(ctx.cwd);
|
|
919
|
+
const base = {
|
|
920
|
+
runner: "vite",
|
|
921
|
+
configFile: adoption.configFile,
|
|
922
|
+
...adoption.unreadable ? { unreadable: adoption.unreadable } : {},
|
|
923
|
+
vite: viteOrigin(ctx.cwd),
|
|
924
|
+
// Rendered as a labelled block by the shared renderer, the same shape the lint role's
|
|
925
|
+
// parked rules and the format role's overrides already use.
|
|
926
|
+
overrides: adoption.ownDeclarations.map(({ name, version }) => ({
|
|
927
|
+
rule: name,
|
|
928
|
+
reason: `declared by this module at ${version}, where sentinel owns it`
|
|
929
|
+
})),
|
|
930
|
+
prerequisite: moduleScripts(ctx.cwd)[PREBUILD_SCRIPT_NAME] ?? null
|
|
931
|
+
};
|
|
932
|
+
if (adoption.configFile === null) return base;
|
|
933
|
+
const resolved = await resolveBuildConfig(ctx.cwd);
|
|
934
|
+
if (resolved.kind === "failed") {
|
|
935
|
+
return { ...base, configError: resolved.reason };
|
|
936
|
+
}
|
|
937
|
+
const config = resolved.config;
|
|
938
|
+
return {
|
|
939
|
+
...base,
|
|
940
|
+
// What the app departs from, and what it adds, are different facts. A DEPARTURE is a
|
|
941
|
+
// disagreement with an opinion the preset holds; an ADDITION is the app needing
|
|
942
|
+
// something the preset never claimed, which is the preset working rather than being
|
|
943
|
+
// worked around.
|
|
944
|
+
departures: describeDepartures(config, REACT_APP_DEFAULTS),
|
|
945
|
+
additions: appAdditions(config),
|
|
946
|
+
resolved: {
|
|
947
|
+
base: at(config, "base") ?? null,
|
|
948
|
+
target: at(config, "build", "target") ?? null,
|
|
949
|
+
sourcemap: at(config, "build", "sourcemap") ?? null,
|
|
950
|
+
port: at(config, "server", "port") ?? null,
|
|
951
|
+
// The one number nothing else reports and that no formula predicts: career goes DOWN
|
|
952
|
+
// to 9998 where the others go up, which is why it is data rather than `port + 1`.
|
|
953
|
+
hmrPort: at(config, "server", "hmr", "port") ?? null,
|
|
954
|
+
// Counted, not listed. It is 56-67% of every config today, and printing 296 entries
|
|
955
|
+
// would bury everything above it. The count is what tells you whether it moved.
|
|
956
|
+
aliases: countOf(at(config, "resolve", "alias")),
|
|
957
|
+
// Flattened without a depth limit: Vite lets a plugin be an arbitrarily nested array,
|
|
958
|
+
// and a magic number here would silently undercount the day someone nests one deeper.
|
|
959
|
+
plugins: countOf(at(config, "plugins"))
|
|
960
|
+
}
|
|
961
|
+
};
|
|
962
|
+
}
|
|
963
|
+
/**
|
|
964
|
+
* Adoption as data, WITHOUT building.
|
|
965
|
+
*
|
|
966
|
+
* Deliberately not "run the build and attach metrics". `--report` is what a migration
|
|
967
|
+
* dashboard calls across the whole workspace, and a bundler is the one tool here where doing
|
|
968
|
+
* the real work costs minutes per module rather than seconds. A report that nobody can
|
|
969
|
+
* afford to run is a report nobody runs.
|
|
970
|
+
*
|
|
971
|
+
* Bundle size is the metric this will eventually want, and it needs a build to produce. It
|
|
972
|
+
* belongs behind an explicit opt-in rather than in the default sweep, and the ticket parks
|
|
973
|
+
* it for exactly that reason.
|
|
974
|
+
*/
|
|
975
|
+
async report(ctx) {
|
|
976
|
+
const adoption = readBuildAdoption(ctx.cwd);
|
|
977
|
+
return {
|
|
978
|
+
ok: true,
|
|
979
|
+
code: 0,
|
|
980
|
+
metrics: {
|
|
981
|
+
vite: viteOrigin(ctx.cwd),
|
|
982
|
+
ownBuildDependencies: adoption.ownDeclarations.length
|
|
983
|
+
}
|
|
984
|
+
};
|
|
985
|
+
}
|
|
986
|
+
};
|
|
987
|
+
function at(config, ...path) {
|
|
988
|
+
let value = config;
|
|
989
|
+
for (const key of path) {
|
|
990
|
+
if (typeof value !== "object" || value === null) return void 0;
|
|
991
|
+
value = value[key];
|
|
992
|
+
}
|
|
993
|
+
return value;
|
|
994
|
+
}
|
|
995
|
+
function countOf(value) {
|
|
996
|
+
return Array.isArray(value) ? value.flat(Number.POSITIVE_INFINITY).length : 0;
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
// src/roles/build/register.ts
|
|
1000
|
+
function registerBuild() {
|
|
1001
|
+
register(new ViteAdapter());
|
|
1002
|
+
setDefaultRunner("build", "vite");
|
|
1003
|
+
register(new ViteDevAdapter());
|
|
1004
|
+
setDefaultRunner("dev", "vite");
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
// src/roles/format/adapters/oxfmt/oxfmt.adapter.ts
|
|
1008
|
+
import { spawnSync as spawnSync3 } from "child_process";
|
|
1009
|
+
import { existsSync as existsSync12, readFileSync as readFileSync11 } from "fs";
|
|
1010
|
+
import { join as join14 } from "path";
|
|
1011
|
+
|
|
240
1012
|
// src/core/config/has-source.ts
|
|
241
1013
|
import { readdirSync } from "fs";
|
|
242
|
-
import { extname, join as
|
|
1014
|
+
import { extname, join as join8, relative } from "path";
|
|
243
1015
|
var SKIP_DIRECTORIES = /* @__PURE__ */ new Set([
|
|
244
1016
|
"node_modules",
|
|
245
1017
|
"dist",
|
|
@@ -290,7 +1062,7 @@ function hasSourceFiles(cwd, extensions) {
|
|
|
290
1062
|
}
|
|
291
1063
|
for (const entry of entries) {
|
|
292
1064
|
if (entry.isDirectory()) {
|
|
293
|
-
if (!SKIP_DIRECTORIES.has(entry.name)) queue.push(
|
|
1065
|
+
if (!SKIP_DIRECTORIES.has(entry.name)) queue.push(join8(dir, entry.name));
|
|
294
1066
|
continue;
|
|
295
1067
|
}
|
|
296
1068
|
if (wanted.has(extname(entry.name))) return true;
|
|
@@ -298,46 +1070,34 @@ function hasSourceFiles(cwd, extensions) {
|
|
|
298
1070
|
}
|
|
299
1071
|
return false;
|
|
300
1072
|
}
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
1073
|
+
function listSourceFiles(cwd, extensions) {
|
|
1074
|
+
const wanted = new Set(extensions);
|
|
1075
|
+
const queue = [cwd];
|
|
1076
|
+
const found = [];
|
|
1077
|
+
while (queue.length > 0) {
|
|
1078
|
+
const dir = queue.pop();
|
|
1079
|
+
let entries;
|
|
1080
|
+
try {
|
|
1081
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
1082
|
+
} catch {
|
|
1083
|
+
continue;
|
|
1084
|
+
}
|
|
1085
|
+
for (const entry of entries) {
|
|
1086
|
+
const full = join8(dir, entry.name);
|
|
1087
|
+
if (entry.isDirectory()) {
|
|
1088
|
+
if (!SKIP_DIRECTORIES.has(entry.name)) queue.push(full);
|
|
1089
|
+
continue;
|
|
1090
|
+
}
|
|
1091
|
+
if (wanted.has(extname(entry.name))) found.push(relative(cwd, full));
|
|
1092
|
+
}
|
|
316
1093
|
}
|
|
317
|
-
|
|
318
|
-
kind: "merge-json",
|
|
319
|
-
path: "package.json",
|
|
320
|
-
value: { nx: { targets } }
|
|
321
|
-
});
|
|
322
|
-
return operations;
|
|
1094
|
+
return found;
|
|
323
1095
|
}
|
|
324
1096
|
|
|
325
|
-
// src/core/
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
const takesValue = new Set(valueFlags);
|
|
330
|
-
const options = [];
|
|
331
|
-
const paths = [];
|
|
332
|
-
let previousTakesValue = false;
|
|
333
|
-
for (const arg of toolArgs) {
|
|
334
|
-
const looksLikeOption = arg.startsWith("-");
|
|
335
|
-
const target = isAbsolute(arg) ? arg : resolve2(cwd, arg);
|
|
336
|
-
if (!previousTakesValue && !looksLikeOption && existsSync4(target)) paths.push(arg);
|
|
337
|
-
else options.push(arg);
|
|
338
|
-
previousTakesValue = !arg.includes("=") && takesValue.has(arg);
|
|
339
|
-
}
|
|
340
|
-
return { options, paths };
|
|
1097
|
+
// src/core/named-scope.ts
|
|
1098
|
+
var TOLERATE_EMPTY_FLAG = "--no-error-on-unmatched-pattern";
|
|
1099
|
+
function scopeFlags(paths) {
|
|
1100
|
+
return paths.length > 0 ? [TOLERATE_EMPTY_FLAG] : [];
|
|
341
1101
|
}
|
|
342
1102
|
|
|
343
1103
|
// src/core/settings.ts
|
|
@@ -345,8 +1105,8 @@ var WORKSPACE_ROOT_MARKER = "nx.json";
|
|
|
345
1105
|
var DEFAULT_MAX_DIAGNOSTICS = 100;
|
|
346
1106
|
|
|
347
1107
|
// src/core/workspace-prep.ts
|
|
348
|
-
import { existsSync as
|
|
349
|
-
import { dirname as
|
|
1108
|
+
import { existsSync as existsSync8, readFileSync as readFileSync6, writeFileSync } from "fs";
|
|
1109
|
+
import { dirname as dirname3, join as join9, relative as relative2 } from "path";
|
|
350
1110
|
var OVERRIDE_KEY = "i18next>typescript";
|
|
351
1111
|
var NATIVE_TS_ALIAS = "@typescript/native";
|
|
352
1112
|
var WORKSPACE_YAML = "pnpm-workspace.yaml";
|
|
@@ -355,8 +1115,8 @@ var LIST_ITEM = /^(\s*)-\s+(.*?)\s*$/;
|
|
|
355
1115
|
function findWorkspaceRoot(startDir) {
|
|
356
1116
|
let dir = startDir;
|
|
357
1117
|
for (; ; ) {
|
|
358
|
-
if (
|
|
359
|
-
const parent =
|
|
1118
|
+
if (existsSync8(join9(dir, WORKSPACE_ROOT_MARKER))) return dir;
|
|
1119
|
+
const parent = dirname3(dir);
|
|
360
1120
|
if (parent === dir) return void 0;
|
|
361
1121
|
dir = parent;
|
|
362
1122
|
}
|
|
@@ -368,9 +1128,9 @@ function declaredNativeTs(pkg) {
|
|
|
368
1128
|
return version || void 0;
|
|
369
1129
|
}
|
|
370
1130
|
function ensureI18nextSingleton(root, dryRun) {
|
|
371
|
-
const pkgPath =
|
|
372
|
-
if (!
|
|
373
|
-
const pkg = JSON.parse(
|
|
1131
|
+
const pkgPath = join9(root, "package.json");
|
|
1132
|
+
if (!existsSync8(pkgPath)) return void 0;
|
|
1133
|
+
const pkg = JSON.parse(readFileSync6(pkgPath, "utf8"));
|
|
374
1134
|
const want = declaredNativeTs(pkg);
|
|
375
1135
|
if (!want) return void 0;
|
|
376
1136
|
const have = pkg.pnpm?.overrides?.[OVERRIDE_KEY];
|
|
@@ -383,10 +1143,10 @@ function ensureI18nextSingleton(root, dryRun) {
|
|
|
383
1143
|
return `pinned ${OVERRIDE_KEY} to ${want} in package.json (i18next singleton)`;
|
|
384
1144
|
}
|
|
385
1145
|
function ensureReleaseAgeAllowList(root, dryRun) {
|
|
386
|
-
const yamlPath =
|
|
387
|
-
if (!
|
|
1146
|
+
const yamlPath = join9(root, WORKSPACE_YAML);
|
|
1147
|
+
if (!existsSync8(yamlPath)) return void 0;
|
|
388
1148
|
const own = readOwnPackage().name;
|
|
389
|
-
const lines =
|
|
1149
|
+
const lines = readFileSync6(yamlPath, "utf8").split("\n");
|
|
390
1150
|
const keyIdx = lines.findIndex((line) => line.replace(/\s+$/, "") === `${RELEASE_AGE_KEY}:`);
|
|
391
1151
|
if (keyIdx === -1) return void 0;
|
|
392
1152
|
let lastItemIdx = keyIdx;
|
|
@@ -419,13 +1179,13 @@ var ROOT_PRETTIER_CONFIGS = [
|
|
|
419
1179
|
"prettier.config.mjs"
|
|
420
1180
|
];
|
|
421
1181
|
function ensureFormatterExclusion(root, moduleDir, dryRun) {
|
|
422
|
-
const rel =
|
|
1182
|
+
const rel = relative2(root, moduleDir).replaceAll("\\", "/");
|
|
423
1183
|
if (rel === "" || rel.startsWith("..")) return void 0;
|
|
424
|
-
const ignorePath =
|
|
425
|
-
const hasPrettier =
|
|
1184
|
+
const ignorePath = join9(root, PRETTIER_IGNORE);
|
|
1185
|
+
const hasPrettier = existsSync8(ignorePath) || ROOT_PRETTIER_CONFIGS.some((name) => existsSync8(join9(root, name)));
|
|
426
1186
|
if (!hasPrettier) return void 0;
|
|
427
1187
|
const pattern = `/${rel}/`;
|
|
428
|
-
const existing =
|
|
1188
|
+
const existing = existsSync8(ignorePath) ? readFileSync6(ignorePath, "utf8") : "";
|
|
429
1189
|
const lines = existing.split("\n");
|
|
430
1190
|
if (lines.some((line) => line.trim().replace(/\/$/, "") === pattern.replace(/\/$/, ""))) {
|
|
431
1191
|
return void 0;
|
|
@@ -461,10 +1221,10 @@ function ensureWorkspacePrep(opts) {
|
|
|
461
1221
|
function inspectWorkspacePrep(root) {
|
|
462
1222
|
const entries = [];
|
|
463
1223
|
let pkg = {};
|
|
464
|
-
const pkgPath =
|
|
465
|
-
if (
|
|
1224
|
+
const pkgPath = join9(root, "package.json");
|
|
1225
|
+
if (existsSync8(pkgPath)) {
|
|
466
1226
|
try {
|
|
467
|
-
pkg = JSON.parse(
|
|
1227
|
+
pkg = JSON.parse(readFileSync6(pkgPath, "utf8"));
|
|
468
1228
|
} catch {
|
|
469
1229
|
pkg = {};
|
|
470
1230
|
}
|
|
@@ -478,9 +1238,9 @@ function inspectWorkspacePrep(root) {
|
|
|
478
1238
|
});
|
|
479
1239
|
}
|
|
480
1240
|
const own = readOwnPackage().name;
|
|
481
|
-
const yamlPath =
|
|
482
|
-
if (
|
|
483
|
-
const yaml =
|
|
1241
|
+
const yamlPath = join9(root, WORKSPACE_YAML);
|
|
1242
|
+
if (existsSync8(yamlPath)) {
|
|
1243
|
+
const yaml = readFileSync6(yamlPath, "utf8");
|
|
484
1244
|
const listed = new RegExp(`^\\s*-\\s*['"]?${own.replace("/", "\\/")}['"]?\\s*$`, "m").test(yaml);
|
|
485
1245
|
if (listed) {
|
|
486
1246
|
const gated = new RegExp(`^\\s*minimumReleaseAge\\s*:`, "m").test(yaml);
|
|
@@ -493,39 +1253,6 @@ function inspectWorkspacePrep(root) {
|
|
|
493
1253
|
return entries;
|
|
494
1254
|
}
|
|
495
1255
|
|
|
496
|
-
// src/shared/resolve-bin.ts
|
|
497
|
-
import { existsSync as existsSync6 } from "fs";
|
|
498
|
-
import { createRequire } from "module";
|
|
499
|
-
import { delimiter, dirname as dirname3, join as join7 } from "path";
|
|
500
|
-
var require2 = createRequire(import.meta.url);
|
|
501
|
-
function resolveBin(fromDir, name) {
|
|
502
|
-
let dir = fromDir;
|
|
503
|
-
for (; ; ) {
|
|
504
|
-
const candidate = join7(dir, "node_modules", ".bin", name);
|
|
505
|
-
if (existsSync6(candidate)) return candidate;
|
|
506
|
-
const parent = dirname3(dir);
|
|
507
|
-
if (parent === dir) return void 0;
|
|
508
|
-
dir = parent;
|
|
509
|
-
}
|
|
510
|
-
}
|
|
511
|
-
function binFromOwnInstall(packageName, binName) {
|
|
512
|
-
try {
|
|
513
|
-
const manifest = require2.resolve(`${packageName}/package.json`);
|
|
514
|
-
const bin = require2(manifest).bin;
|
|
515
|
-
const relative3 = typeof bin === "string" ? bin : bin?.[binName];
|
|
516
|
-
if (!relative3) return void 0;
|
|
517
|
-
const executable = join7(dirname3(manifest), relative3);
|
|
518
|
-
return existsSync6(executable) ? executable : void 0;
|
|
519
|
-
} catch {
|
|
520
|
-
return void 0;
|
|
521
|
-
}
|
|
522
|
-
}
|
|
523
|
-
function binSearchPath(cwd) {
|
|
524
|
-
return [`${cwd}${delimiter}node_modules/.bin (and parents)`, "sentinel's own install"].join(
|
|
525
|
-
" then "
|
|
526
|
-
);
|
|
527
|
-
}
|
|
528
|
-
|
|
529
1256
|
// src/roles/format/config-policy.ts
|
|
530
1257
|
var FORMAT_CONFIG_FILE = ".oxfmtrc.json";
|
|
531
1258
|
var FORMAT_SCRIPT_NAME = "format";
|
|
@@ -572,48 +1299,6 @@ function formatTargets() {
|
|
|
572
1299
|
};
|
|
573
1300
|
}
|
|
574
1301
|
|
|
575
|
-
// src/core/config/tool-script.ts
|
|
576
|
-
var SEGMENT_SEPARATOR = /(\s*(?:&&|\|\||;|&|\|)\s*)/;
|
|
577
|
-
function isSeparatorAt(index) {
|
|
578
|
-
return index % 2 === 1;
|
|
579
|
-
}
|
|
580
|
-
function invokes(segment, binary) {
|
|
581
|
-
return new RegExp(String.raw`(^|[\s/])${binary}(\s|$)`).test(segment.trim());
|
|
582
|
-
}
|
|
583
|
-
function isSentinelSegment(segment, roleFlag) {
|
|
584
|
-
const runsSentinel = invokes(segment, "sentinel") || segment.includes("bin/sentinel.js");
|
|
585
|
-
return runsSentinel && new RegExp(String.raw`(^|\s)${roleFlag}(\s|$)`).test(segment);
|
|
586
|
-
}
|
|
587
|
-
function composeToolScript(existing, options) {
|
|
588
|
-
const { command } = options;
|
|
589
|
-
if (!existing || existing.trim() === "") return command;
|
|
590
|
-
const { roleFlag } = options;
|
|
591
|
-
const isReplaceable = (segment) => options.replaces(segment) || roleFlag !== void 0 && isSentinelSegment(segment, roleFlag);
|
|
592
|
-
const parts = existing.split(SEGMENT_SEPARATOR);
|
|
593
|
-
const commands = parts.filter((_, index) => !isSeparatorAt(index));
|
|
594
|
-
if (!commands.some(isReplaceable)) return existing;
|
|
595
|
-
let replacedOnce = false;
|
|
596
|
-
const rebuilt = parts.map((part, index) => {
|
|
597
|
-
if (isSeparatorAt(index) || !isReplaceable(part)) return part;
|
|
598
|
-
if (replacedOnce) return null;
|
|
599
|
-
replacedOnce = true;
|
|
600
|
-
return options.keepFix && /(^|\s)--fix(\s|$)/.test(part) ? `${command} --fix` : command;
|
|
601
|
-
});
|
|
602
|
-
const kept = [];
|
|
603
|
-
for (let index = 0; index < rebuilt.length; index += 1) {
|
|
604
|
-
const part = rebuilt[index];
|
|
605
|
-
if (part === null) {
|
|
606
|
-
if (kept.length > 0) kept.pop();
|
|
607
|
-
continue;
|
|
608
|
-
}
|
|
609
|
-
kept.push(part);
|
|
610
|
-
}
|
|
611
|
-
return kept.join("").trim();
|
|
612
|
-
}
|
|
613
|
-
function keepsOtherCommands(script, sentinelCommand) {
|
|
614
|
-
return script.split(SEGMENT_SEPARATOR).filter((_, index) => !isSeparatorAt(index)).some((segment) => segment.trim() !== "" && segment.trim() !== sentinelCommand);
|
|
615
|
-
}
|
|
616
|
-
|
|
617
1302
|
// src/roles/format/format-script.ts
|
|
618
1303
|
var SENTINEL_FORMAT_COMMAND = "sentinel --run --format";
|
|
619
1304
|
function isPrettierSegment(segment) {
|
|
@@ -642,8 +1327,8 @@ function writesWhenRewritten(name, command) {
|
|
|
642
1327
|
var CHECK_SCRIPT_NAMES = /* @__PURE__ */ new Set(["lint", "format", "check", "test", "typecheck", "verify"]);
|
|
643
1328
|
|
|
644
1329
|
// src/roles/format/inherited-ignores.ts
|
|
645
|
-
import { existsSync as
|
|
646
|
-
import { join as
|
|
1330
|
+
import { existsSync as existsSync9, readFileSync as readFileSync7 } from "fs";
|
|
1331
|
+
import { join as join10 } from "path";
|
|
647
1332
|
var ROOT_IGNORE_FILE = ".prettierignore";
|
|
648
1333
|
function isPattern(line) {
|
|
649
1334
|
const trimmed = line.trim();
|
|
@@ -658,11 +1343,11 @@ function toModulePattern(pattern) {
|
|
|
658
1343
|
}
|
|
659
1344
|
function inheritedIgnorePatterns(workspaceRoot) {
|
|
660
1345
|
if (!workspaceRoot) return [];
|
|
661
|
-
const path =
|
|
662
|
-
if (!
|
|
1346
|
+
const path = join10(workspaceRoot, ROOT_IGNORE_FILE);
|
|
1347
|
+
if (!existsSync9(path)) return [];
|
|
663
1348
|
let contents;
|
|
664
1349
|
try {
|
|
665
|
-
contents =
|
|
1350
|
+
contents = readFileSync7(path, "utf8");
|
|
666
1351
|
} catch {
|
|
667
1352
|
return [];
|
|
668
1353
|
}
|
|
@@ -749,13 +1434,13 @@ function formatPresetFor(preset) {
|
|
|
749
1434
|
}
|
|
750
1435
|
|
|
751
1436
|
// src/roles/format/prettier-config.ts
|
|
752
|
-
import { existsSync as
|
|
753
|
-
import { join as
|
|
1437
|
+
import { existsSync as existsSync10, readFileSync as readFileSync9 } from "fs";
|
|
1438
|
+
import { join as join12 } from "path";
|
|
754
1439
|
|
|
755
1440
|
// src/roles/format/resolve-oxfmt.ts
|
|
756
|
-
import { readFileSync as
|
|
1441
|
+
import { readFileSync as readFileSync8 } from "fs";
|
|
757
1442
|
import { createRequire as createRequire2 } from "module";
|
|
758
|
-
import { dirname as dirname4, join as
|
|
1443
|
+
import { dirname as dirname4, join as join11 } from "path";
|
|
759
1444
|
function resolveOxfmt(cwd) {
|
|
760
1445
|
return resolveBin(cwd, "oxfmt") ?? binFromOwnInstall("oxfmt", "oxfmt");
|
|
761
1446
|
}
|
|
@@ -784,8 +1469,8 @@ function configSchema() {
|
|
|
784
1469
|
function readConfigSchema() {
|
|
785
1470
|
try {
|
|
786
1471
|
const manifest = createRequire2(import.meta.url).resolve("oxfmt/package.json");
|
|
787
|
-
const schemaPath =
|
|
788
|
-
return JSON.parse(
|
|
1472
|
+
const schemaPath = join11(dirname4(manifest), "configuration_schema.json");
|
|
1473
|
+
return JSON.parse(readFileSync8(schemaPath, "utf8"));
|
|
789
1474
|
} catch {
|
|
790
1475
|
return void 0;
|
|
791
1476
|
}
|
|
@@ -816,14 +1501,14 @@ function toOxfmtOverrides(value) {
|
|
|
816
1501
|
return { overrides, unresolved };
|
|
817
1502
|
}
|
|
818
1503
|
function readPrettierSettings(cwd) {
|
|
819
|
-
const file = PRETTIER_CONFIG_FILES.find((name) =>
|
|
1504
|
+
const file = PRETTIER_CONFIG_FILES.find((name) => existsSync10(join12(cwd, name)));
|
|
820
1505
|
if (!file) return { options: {}, unresolved: [] };
|
|
821
1506
|
if (/\.(js|cjs|mjs)$/.test(file)) {
|
|
822
1507
|
return { options: {}, file, unresolved: [`${file} is JavaScript, so it was not executed`] };
|
|
823
1508
|
}
|
|
824
1509
|
let parsed;
|
|
825
1510
|
try {
|
|
826
|
-
parsed = parseJsonc(
|
|
1511
|
+
parsed = parseJsonc(readFileSync9(join12(cwd, file), "utf8"), file);
|
|
827
1512
|
} catch {
|
|
828
1513
|
return { options: {}, file, unresolved: [`${file} could not be parsed`] };
|
|
829
1514
|
}
|
|
@@ -853,9 +1538,9 @@ function readPrettierSettings(cwd) {
|
|
|
853
1538
|
}
|
|
854
1539
|
|
|
855
1540
|
// src/roles/format/read-adoption.ts
|
|
856
|
-
import { existsSync as
|
|
857
|
-
import { join as
|
|
858
|
-
var
|
|
1541
|
+
import { existsSync as existsSync11, readFileSync as readFileSync10 } from "fs";
|
|
1542
|
+
import { join as join13 } from "path";
|
|
1543
|
+
var NOT_ADOPTED2 = (configFile, unreadable = null) => ({
|
|
859
1544
|
configFile,
|
|
860
1545
|
preset: null,
|
|
861
1546
|
adopted: false,
|
|
@@ -870,19 +1555,19 @@ function sameValue(a, b) {
|
|
|
870
1555
|
return JSON.stringify(a) === JSON.stringify(b);
|
|
871
1556
|
}
|
|
872
1557
|
function readFormatAdoption(cwd) {
|
|
873
|
-
const path =
|
|
874
|
-
if (!
|
|
1558
|
+
const path = join13(cwd, FORMAT_CONFIG_FILE);
|
|
1559
|
+
if (!existsSync11(path)) return NOT_ADOPTED2(null);
|
|
875
1560
|
let parsed;
|
|
876
1561
|
try {
|
|
877
|
-
parsed = parseJsonc(
|
|
1562
|
+
parsed = parseJsonc(readFileSync10(path, "utf8"), FORMAT_CONFIG_FILE);
|
|
878
1563
|
} catch (error) {
|
|
879
1564
|
const reason = error instanceof Error ? error.message : String(error);
|
|
880
|
-
return
|
|
1565
|
+
return NOT_ADOPTED2(FORMAT_CONFIG_FILE, `${FORMAT_CONFIG_FILE} could not be parsed (${reason})`);
|
|
881
1566
|
}
|
|
882
1567
|
const provenance = parsed[PROVENANCE_KEY];
|
|
883
|
-
if (!provenance || typeof provenance.preset !== "string") return
|
|
1568
|
+
if (!provenance || typeof provenance.preset !== "string") return NOT_ADOPTED2(FORMAT_CONFIG_FILE);
|
|
884
1569
|
if (!hasFormatPreset(provenance.preset)) {
|
|
885
|
-
return
|
|
1570
|
+
return NOT_ADOPTED2(
|
|
886
1571
|
FORMAT_CONFIG_FILE,
|
|
887
1572
|
`${FORMAT_CONFIG_FILE} declares the format preset "${provenance.preset}", which sentinel does not ship (shipped: ${FORMAT_PRESETS.join(", ")}). Re-run \`sentinel --init --format\`.`
|
|
888
1573
|
);
|
|
@@ -999,7 +1684,7 @@ var OxfmtAdapter = class extends BaseAdapter {
|
|
|
999
1684
|
manifestOperation(context.cwd, this.formatScripts(context.cwd))
|
|
1000
1685
|
];
|
|
1001
1686
|
const prettierConfigs = PRETTIER_CONFIG_FILES.filter(
|
|
1002
|
-
(name) =>
|
|
1687
|
+
(name) => existsSync12(join14(context.cwd, name))
|
|
1003
1688
|
);
|
|
1004
1689
|
for (const name of prettierConfigs) operations.push({ kind: "delete", path: name });
|
|
1005
1690
|
const removableDeps = this.modulePrettierDependencies(context.cwd);
|
|
@@ -1058,7 +1743,7 @@ var OxfmtAdapter = class extends BaseAdapter {
|
|
|
1058
1743
|
async afterInit(ctx) {
|
|
1059
1744
|
const oxfmt = resolveOxfmt(ctx.cwd);
|
|
1060
1745
|
if (!oxfmt) return;
|
|
1061
|
-
const pass =
|
|
1746
|
+
const pass = spawnSync3(oxfmt, ["--write", "."], { cwd: ctx.cwd, encoding: "utf8" });
|
|
1062
1747
|
process.stderr.write(` ${palette(process.stderr).dim(FORMATTER_DIFFERENCES)}
|
|
1063
1748
|
`);
|
|
1064
1749
|
if (pass.status === 0) return;
|
|
@@ -1121,10 +1806,14 @@ var OxfmtAdapter = class extends BaseAdapter {
|
|
|
1121
1806
|
const { options, paths } = splitToolArgs(ctx.cwd, ctx.toolArgs, {
|
|
1122
1807
|
valueFlags: OXFMT_VALUE_FLAGS
|
|
1123
1808
|
});
|
|
1124
|
-
const result =
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1809
|
+
const result = spawnSync3(
|
|
1810
|
+
oxfmt,
|
|
1811
|
+
[mode, ...scopeFlags(paths), ...options, ...paths.length > 0 ? paths : ["."]],
|
|
1812
|
+
{
|
|
1813
|
+
cwd: ctx.cwd,
|
|
1814
|
+
stdio: "inherit"
|
|
1815
|
+
}
|
|
1816
|
+
);
|
|
1128
1817
|
if (result.error) {
|
|
1129
1818
|
process.stderr.write(
|
|
1130
1819
|
`sentinel format(oxfmt): could not run oxfmt (${result.error.message})
|
|
@@ -1199,7 +1888,7 @@ var OxfmtAdapter = class extends BaseAdapter {
|
|
|
1199
1888
|
);
|
|
1200
1889
|
return { ok: false, code: 1, metrics: { ...base, unformatted: null } };
|
|
1201
1890
|
}
|
|
1202
|
-
const result =
|
|
1891
|
+
const result = spawnSync3(oxfmt, ["--list-different", "."], { cwd: ctx.cwd, encoding: "utf8" });
|
|
1203
1892
|
if (result.error) {
|
|
1204
1893
|
process.stderr.write(
|
|
1205
1894
|
`sentinel format(oxfmt): could not run oxfmt (${result.error.message})
|
|
@@ -1235,7 +1924,7 @@ var OxfmtAdapter = class extends BaseAdapter {
|
|
|
1235
1924
|
modulePrettierDependencies(cwd) {
|
|
1236
1925
|
const isPrettierPackage = (name) => name === "prettier" || name.startsWith("prettier-plugin-") || name.startsWith("@prettier/");
|
|
1237
1926
|
try {
|
|
1238
|
-
const manifest = JSON.parse(
|
|
1927
|
+
const manifest = JSON.parse(readFileSync11(join14(cwd, "package.json"), "utf8"));
|
|
1239
1928
|
return Object.keys(manifest.devDependencies ?? {}).filter(isPrettierPackage).map((name) => ["devDependencies", name]);
|
|
1240
1929
|
} catch {
|
|
1241
1930
|
return [];
|
|
@@ -1315,30 +2004,139 @@ function registerFormat() {
|
|
|
1315
2004
|
setDefaultRunner("format", "oxfmt");
|
|
1316
2005
|
}
|
|
1317
2006
|
|
|
1318
|
-
// src/roles/lint/adapters/oxlint/oxlint.adapter.ts
|
|
1319
|
-
import { spawnSync as
|
|
1320
|
-
import { existsSync as
|
|
1321
|
-
import { join as
|
|
1322
|
-
|
|
1323
|
-
// src/core/config/deferred-rules.ts
|
|
1324
|
-
function deferredRuleNames(rules) {
|
|
1325
|
-
return rules.map((entry) => entry.rule);
|
|
2007
|
+
// src/roles/lint/adapters/oxlint/oxlint.adapter.ts
|
|
2008
|
+
import { spawnSync as spawnSync4 } from "child_process";
|
|
2009
|
+
import { existsSync as existsSync20, readFileSync as readFileSync19, rmSync, writeFileSync as writeFileSync2 } from "fs";
|
|
2010
|
+
import { join as join24 } from "path";
|
|
2011
|
+
|
|
2012
|
+
// src/core/config/deferred-rules.ts
|
|
2013
|
+
function deferredRuleNames(rules) {
|
|
2014
|
+
return rules.map((entry) => entry.rule);
|
|
2015
|
+
}
|
|
2016
|
+
|
|
2017
|
+
// src/core/fix-report.ts
|
|
2018
|
+
import { statSync } from "fs";
|
|
2019
|
+
import { join as join15 } from "path";
|
|
2020
|
+
var CONFIG_REFUSED = /failed to parse .*config|invalid config file/i;
|
|
2021
|
+
function readFailure(output) {
|
|
2022
|
+
const lines = output.split("\n").map((line2) => line2.trim());
|
|
2023
|
+
if (!lines.some((line2) => CONFIG_REFUSED.test(line2))) return void 0;
|
|
2024
|
+
const detail = lines.find((line2) => /invalid config file/i.test(line2));
|
|
2025
|
+
const line = (detail ?? lines.find((entry) => entry !== "") ?? "").replace(/^x\s*/, "");
|
|
2026
|
+
return line.split(/:\s*Failed to parse config/i)[0] ?? line;
|
|
2027
|
+
}
|
|
2028
|
+
function fingerprint(path) {
|
|
2029
|
+
try {
|
|
2030
|
+
const stat = statSync(path);
|
|
2031
|
+
return `${stat.size}:${stat.mtimeMs}`;
|
|
2032
|
+
} catch {
|
|
2033
|
+
return void 0;
|
|
2034
|
+
}
|
|
2035
|
+
}
|
|
2036
|
+
function snapshotFiles(cwd, extensions) {
|
|
2037
|
+
const snapshot = /* @__PURE__ */ new Map();
|
|
2038
|
+
for (const file of listSourceFiles(cwd, extensions)) {
|
|
2039
|
+
const mark = fingerprint(join15(cwd, file));
|
|
2040
|
+
if (mark !== void 0) snapshot.set(file, mark);
|
|
2041
|
+
}
|
|
2042
|
+
return snapshot;
|
|
2043
|
+
}
|
|
2044
|
+
function changedSince(cwd, before, extensions) {
|
|
2045
|
+
const changed = [];
|
|
2046
|
+
for (const file of listSourceFiles(cwd, extensions)) {
|
|
2047
|
+
const now = fingerprint(join15(cwd, file));
|
|
2048
|
+
if (now === void 0) continue;
|
|
2049
|
+
if (before.get(file) !== now) changed.push(file);
|
|
2050
|
+
}
|
|
2051
|
+
return changed.sort();
|
|
2052
|
+
}
|
|
2053
|
+
var NAMED_IN_REPORT = 5;
|
|
2054
|
+
function describeFixOutcome(outcome, toolLabel) {
|
|
2055
|
+
if (outcome.failure !== void 0) {
|
|
2056
|
+
return `the ${toolLabel} autofix could not run, so nothing was fixed and this module has not been checked: ${outcome.failure}`;
|
|
2057
|
+
}
|
|
2058
|
+
if (outcome.changed.length === 0) return `the ${toolLabel} autofix changed nothing`;
|
|
2059
|
+
const named = outcome.changed.slice(0, NAMED_IN_REPORT).join(", ");
|
|
2060
|
+
const rest = outcome.changed.length - NAMED_IN_REPORT;
|
|
2061
|
+
const listed = rest > 0 ? `${named} and ${rest} more` : named;
|
|
2062
|
+
return `the ${toolLabel} autofix rewrote ${outcome.changed.length} file${outcome.changed.length === 1 ? "" : "s"}: ${listed}. Review the diff before committing: rules this module used to have switched off are enforced from now on.`;
|
|
2063
|
+
}
|
|
2064
|
+
|
|
2065
|
+
// src/roles/lint/module-baseline.ts
|
|
2066
|
+
import { existsSync as existsSync13, readFileSync as readFileSync12 } from "fs";
|
|
2067
|
+
import { join as join16 } from "path";
|
|
2068
|
+
var LINT_BASELINE_FILE = ".oxlintrc.baseline.json";
|
|
2069
|
+
var LINT_MEASURE_FILE = ".oxlintrc.measure.json";
|
|
2070
|
+
var LINT_BASELINE_SPECIFIER = `./${LINT_BASELINE_FILE}`;
|
|
2071
|
+
function holdsFrom(errorCounts) {
|
|
2072
|
+
return [...errorCounts].map(([rule, count]) => ({ rule, count })).sort((left, right) => right.count - left.count || left.rule.localeCompare(right.rule));
|
|
2073
|
+
}
|
|
2074
|
+
function renderBaseline(holds, version) {
|
|
2075
|
+
const entries = holds.map(({ rule }) => ` ${JSON.stringify(rule)}: "warn"`).join(",\n");
|
|
2076
|
+
return [
|
|
2077
|
+
"{",
|
|
2078
|
+
` // Written by @hublo/sentinel@${version}. Do not edit by hand: \`sentinel --init --lint\``,
|
|
2079
|
+
" // regenerates it from a fresh measurement, and a rule you have since fixed drops out",
|
|
2080
|
+
" // and returns to `error` on its own.",
|
|
2081
|
+
" //",
|
|
2082
|
+
" // These rules are held at `warn` because this module ALREADY violated them when it",
|
|
2083
|
+
" // adopted. They still run and still report, so the build cannot fail on them.",
|
|
2084
|
+
" //",
|
|
2085
|
+
" // The hold is per RULE, not per occurrence: oxlint cannot freeze a specific list of",
|
|
2086
|
+
" // violations, so a NEW violation of one of these rules is covered too, and will only",
|
|
2087
|
+
" // warn. That is the cost of adopting without a red build, and the reason to clear this",
|
|
2088
|
+
" // file rather than live with it.",
|
|
2089
|
+
" //",
|
|
2090
|
+
" // `sentinel --inspect --lint` lists what is held; `sentinel --run --lint --json` counts",
|
|
2091
|
+
" // what is left, per rule. Fix them and re-run `--init --lint` to get the severity back.",
|
|
2092
|
+
' "rules": {',
|
|
2093
|
+
entries,
|
|
2094
|
+
" }",
|
|
2095
|
+
"}",
|
|
2096
|
+
""
|
|
2097
|
+
].join("\n");
|
|
2098
|
+
}
|
|
2099
|
+
function extendsWithBaseline(current, hasHolds) {
|
|
2100
|
+
const withoutBaseline = current.filter((entry) => entry !== LINT_BASELINE_SPECIFIER);
|
|
2101
|
+
return hasHolds ? [...withoutBaseline, LINT_BASELINE_SPECIFIER] : withoutBaseline;
|
|
2102
|
+
}
|
|
2103
|
+
function describeHolds(holds) {
|
|
2104
|
+
if (holds.length === 0) return "";
|
|
2105
|
+
const total = holds.reduce((sum, hold) => sum + hold.count, 0);
|
|
2106
|
+
const listed = holds.slice(0, 5).map(({ rule, count }) => `${rule} (${count})`).join(", ");
|
|
2107
|
+
const rest = holds.length > 5 ? `, and ${holds.length - 5} more` : "";
|
|
2108
|
+
return `held ${holds.length} rule(s) at \`warn\` for this module, covering ${total} violation(s) that were already there: ${listed}${rest}. They are written to ${LINT_BASELINE_FILE}, they still report, and they return to \`error\` once fixed and re-initialized`;
|
|
2109
|
+
}
|
|
2110
|
+
function heldRules(cwd) {
|
|
2111
|
+
const path = join16(cwd, LINT_BASELINE_FILE);
|
|
2112
|
+
if (!existsSync13(path)) return [];
|
|
2113
|
+
try {
|
|
2114
|
+
const parsed = parseJsonc(
|
|
2115
|
+
readFileSync12(path, "utf8"),
|
|
2116
|
+
LINT_BASELINE_FILE
|
|
2117
|
+
);
|
|
2118
|
+
return Object.keys(parsed.rules ?? {});
|
|
2119
|
+
} catch {
|
|
2120
|
+
return [];
|
|
2121
|
+
}
|
|
1326
2122
|
}
|
|
1327
2123
|
|
|
1328
2124
|
// src/roles/lint/config-policy.ts
|
|
1329
2125
|
var LINT_CONFIG_FILE = ".oxlintrc.json";
|
|
1330
2126
|
var LINT_SCRIPT_NAME = "lint";
|
|
1331
|
-
var PRESET_DIR = "./node_modules/@hublo/sentinel/
|
|
2127
|
+
var PRESET_DIR = "./node_modules/@hublo/sentinel/lint";
|
|
1332
2128
|
function presetPath(preset) {
|
|
1333
2129
|
return `${PRESET_DIR}/${preset}.json`;
|
|
1334
2130
|
}
|
|
1335
2131
|
function isSentinelPreset(entry) {
|
|
1336
|
-
return
|
|
2132
|
+
return /@hublo\/sentinel\/(lint|oxlint)\//.test(entry);
|
|
1337
2133
|
}
|
|
1338
2134
|
function extendsWithPreset(current, variant) {
|
|
1339
2135
|
const own = presetPath(variant);
|
|
1340
|
-
const others = current.filter((entry) => entry !== own && !isSentinelPreset(entry));
|
|
1341
|
-
|
|
2136
|
+
const others = [...new Set(current.filter((entry) => entry !== own && !isSentinelPreset(entry)))];
|
|
2137
|
+
const baseline = others.filter((entry) => entry === LINT_BASELINE_SPECIFIER);
|
|
2138
|
+
const rest = others.filter((entry) => entry !== LINT_BASELINE_SPECIFIER);
|
|
2139
|
+
return [own, ...rest, ...baseline];
|
|
1342
2140
|
}
|
|
1343
2141
|
function presetVariant(preset, cwd) {
|
|
1344
2142
|
const normalised = cwd.replaceAll("\\", "/");
|
|
@@ -1347,7 +2145,7 @@ function presetVariant(preset, cwd) {
|
|
|
1347
2145
|
function presetOfVariant(variant) {
|
|
1348
2146
|
return variant === "react-lib" ? "react" : variant;
|
|
1349
2147
|
}
|
|
1350
|
-
var SENTINEL_LINT_PRESET = /@hublo\/sentinel\/oxlint\/([a-z-]+)\.json$/;
|
|
2148
|
+
var SENTINEL_LINT_PRESET = /@hublo\/sentinel\/(?:lint|oxlint)\/([a-z-]+)\.json$/;
|
|
1351
2149
|
function presetNameFromPath(preset) {
|
|
1352
2150
|
return preset ? SENTINEL_LINT_PRESET.exec(preset)?.[1] ?? void 0 : void 0;
|
|
1353
2151
|
}
|
|
@@ -1362,46 +2160,6 @@ function lintTarget() {
|
|
|
1362
2160
|
return { cache: true, inputs: ["default", "^default", `{projectRoot}/${LINT_CONFIG_FILE}`] };
|
|
1363
2161
|
}
|
|
1364
2162
|
|
|
1365
|
-
// src/roles/lint/presets/base.json
|
|
1366
|
-
var base_default2 = {
|
|
1367
|
-
rules: {
|
|
1368
|
-
"no-var": "error",
|
|
1369
|
-
"prefer-const": [
|
|
1370
|
-
"error",
|
|
1371
|
-
{
|
|
1372
|
-
destructuring: "any",
|
|
1373
|
-
ignoreReadBeforeAssign: false
|
|
1374
|
-
}
|
|
1375
|
-
],
|
|
1376
|
-
"prefer-rest-params": "error",
|
|
1377
|
-
"prefer-spread": "error",
|
|
1378
|
-
"typescript/ban-ts-comment": "error",
|
|
1379
|
-
"typescript/no-array-constructor": "error",
|
|
1380
|
-
"typescript/no-duplicate-enum-values": "error",
|
|
1381
|
-
"typescript/no-empty-object-type": "error",
|
|
1382
|
-
"typescript/no-extra-non-null-assertion": "error",
|
|
1383
|
-
"typescript/no-misused-new": "error",
|
|
1384
|
-
"typescript/no-namespace": "error",
|
|
1385
|
-
"typescript/no-non-null-asserted-optional-chain": "error",
|
|
1386
|
-
"typescript/no-this-alias": "error",
|
|
1387
|
-
"typescript/no-unnecessary-type-constraint": "error",
|
|
1388
|
-
"typescript/no-unsafe-declaration-merging": "error",
|
|
1389
|
-
"typescript/no-unsafe-function-type": "error",
|
|
1390
|
-
"typescript/no-unused-expressions": [
|
|
1391
|
-
"error",
|
|
1392
|
-
{
|
|
1393
|
-
allowShortCircuit: false,
|
|
1394
|
-
allowTaggedTemplates: false,
|
|
1395
|
-
allowTernary: false
|
|
1396
|
-
}
|
|
1397
|
-
],
|
|
1398
|
-
"typescript/no-wrapper-object-types": "error",
|
|
1399
|
-
"typescript/prefer-as-const": "error",
|
|
1400
|
-
"typescript/prefer-namespace-keyword": "error",
|
|
1401
|
-
"typescript/triple-slash-reference": "error"
|
|
1402
|
-
}
|
|
1403
|
-
};
|
|
1404
|
-
|
|
1405
2163
|
// src/roles/lint/presets/nest.json
|
|
1406
2164
|
var nest_default = {
|
|
1407
2165
|
plugins: ["typescript", "jest", "import", "unicorn", "oxc", "node"],
|
|
@@ -1741,6 +2499,7 @@ var nest_default = {
|
|
|
1741
2499
|
"no-invalid-this": "no oxlint equivalent, and hosting does not help: the rule crashes on every file under oxlint's plugin API (sourceCode.getJSDocComment is not implemented). Its coverage moves to TypeScript's noImplicitThis, which the sentinel TypeScript role manages.",
|
|
1742
2500
|
"no-multi-spaces": "formatting, and the formatter owns it. It moves to oxfmt, which is deliberately the LAST step of the migration, so nothing enforces it in between.",
|
|
1743
2501
|
"no-octal": "no oxlint equivalent",
|
|
2502
|
+
"no-restricted-syntax": "no oxlint equivalent, and unlike the react tier this preset hosts no house-rule plugin, so architectural guards written as AST selectors are not enforced at all. They belong in sentinel's `hublo` plugin, next to the frontend ones: tell us which guards your service relies on. A per-module copy of a house rule drifts, which is the problem sentinel exists to remove.",
|
|
1744
2503
|
"no-trailing-spaces": "formatting, and the formatter owns it. It moves to oxfmt, which is deliberately the LAST step of the migration, so nothing enforces it in between.",
|
|
1745
2504
|
semi: "formatting, and the formatter owns it. It moves to oxfmt, which is deliberately the LAST step of the migration, so nothing enforces it in between.",
|
|
1746
2505
|
"template-curly-spacing": "formatting, and the formatter owns it. It moves to oxfmt, which is deliberately the LAST step of the migration, so nothing enforces it in between.",
|
|
@@ -2694,10 +3453,51 @@ var react_default = {
|
|
|
2694
3453
|
uncovered: {
|
|
2695
3454
|
"import/order": "import ordering is FORMATTING, and the format role owns it: oxfmt's `sortImports` reproduces exactly these groups (builtin, external, internal, parent/index, sibling, alphabetical, blank line between) and sorts a file completely in ONE pass, because it rewrites the whole import block. A lint fixer cannot: it emits text edits over overlapping ranges, so this rule needed several runs to finish (five imports took four passes, measured) and sentinel deliberately runs autofix once. Enforced by `pnpm run format`, not by the linter, which is also why `eslint-plugin-import` is no longer hosted at all: its only other rule, no-duplicates, is native in oxlint.",
|
|
2696
3455
|
"no-octal": "no oxlint equivalent",
|
|
3456
|
+
"no-restricted-syntax": "no oxlint equivalent as configuration, so the eight AST selectors this repo used are shipped as real rules in the local `hublo` plugin instead, which this tier hosts (`../plugins/hublo.js`). Any selector NOT in that plugin is not enforced here; propose it and it joins the plugin rather than a per-module copy.",
|
|
2697
3457
|
semi: "formatting, and the formatter owns it. It moves to oxfmt, which is deliberately the LAST step of the migration, so nothing enforces it in between."
|
|
2698
3458
|
}
|
|
2699
3459
|
};
|
|
2700
3460
|
|
|
3461
|
+
// src/roles/lint/presets/shared.json
|
|
3462
|
+
var shared_default = {
|
|
3463
|
+
rules: {
|
|
3464
|
+
"no-var": "error",
|
|
3465
|
+
"prefer-const": [
|
|
3466
|
+
"error",
|
|
3467
|
+
{
|
|
3468
|
+
destructuring: "any",
|
|
3469
|
+
ignoreReadBeforeAssign: false
|
|
3470
|
+
}
|
|
3471
|
+
],
|
|
3472
|
+
"prefer-rest-params": "error",
|
|
3473
|
+
"prefer-spread": "error",
|
|
3474
|
+
"typescript/ban-ts-comment": "error",
|
|
3475
|
+
"typescript/no-array-constructor": "error",
|
|
3476
|
+
"typescript/no-duplicate-enum-values": "error",
|
|
3477
|
+
"typescript/no-empty-object-type": "error",
|
|
3478
|
+
"typescript/no-extra-non-null-assertion": "error",
|
|
3479
|
+
"typescript/no-misused-new": "error",
|
|
3480
|
+
"typescript/no-namespace": "error",
|
|
3481
|
+
"typescript/no-non-null-asserted-optional-chain": "error",
|
|
3482
|
+
"typescript/no-this-alias": "error",
|
|
3483
|
+
"typescript/no-unnecessary-type-constraint": "error",
|
|
3484
|
+
"typescript/no-unsafe-declaration-merging": "error",
|
|
3485
|
+
"typescript/no-unsafe-function-type": "error",
|
|
3486
|
+
"typescript/no-unused-expressions": [
|
|
3487
|
+
"error",
|
|
3488
|
+
{
|
|
3489
|
+
allowShortCircuit: false,
|
|
3490
|
+
allowTaggedTemplates: false,
|
|
3491
|
+
allowTernary: false
|
|
3492
|
+
}
|
|
3493
|
+
],
|
|
3494
|
+
"typescript/no-wrapper-object-types": "error",
|
|
3495
|
+
"typescript/prefer-as-const": "error",
|
|
3496
|
+
"typescript/prefer-namespace-keyword": "error",
|
|
3497
|
+
"typescript/triple-slash-reference": "error"
|
|
3498
|
+
}
|
|
3499
|
+
};
|
|
3500
|
+
|
|
2701
3501
|
// src/roles/lint/presets/svelte.json
|
|
2702
3502
|
var svelte_default = {
|
|
2703
3503
|
plugins: ["typescript", "oxc"],
|
|
@@ -3036,7 +3836,7 @@ var tools_default = {
|
|
|
3036
3836
|
};
|
|
3037
3837
|
|
|
3038
3838
|
// src/roles/lint/preset-data.ts
|
|
3039
|
-
var
|
|
3839
|
+
var SHARED_RULES = shared_default.rules;
|
|
3040
3840
|
var PRESET_FILES = {
|
|
3041
3841
|
nest: nest_default,
|
|
3042
3842
|
node: node_default,
|
|
@@ -3058,15 +3858,18 @@ function hasLintPreset(preset) {
|
|
|
3058
3858
|
function presetFile(preset) {
|
|
3059
3859
|
return VARIANT_FILES[preset] ?? PRESET_FILES[preset] ?? node_default;
|
|
3060
3860
|
}
|
|
3061
|
-
function
|
|
3062
|
-
return
|
|
3861
|
+
function sharedRules() {
|
|
3862
|
+
return SHARED_RULES;
|
|
3063
3863
|
}
|
|
3064
3864
|
function presetRules(preset) {
|
|
3065
3865
|
return presetFile(preset).rules;
|
|
3066
3866
|
}
|
|
3867
|
+
function uncoveredFor(preset) {
|
|
3868
|
+
return Object.entries(presetFile(preset).uncovered ?? {}).map(([rule, reason]) => ({ rule, reason })).sort((left, right) => left.rule.localeCompare(right.rule));
|
|
3869
|
+
}
|
|
3067
3870
|
|
|
3068
3871
|
// src/roles/lint/rule-data.ts
|
|
3069
|
-
var
|
|
3872
|
+
var sharedRules2 = sharedRules();
|
|
3070
3873
|
function isDecision(value) {
|
|
3071
3874
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3072
3875
|
}
|
|
@@ -3093,7 +3896,7 @@ function validate(preset, rules) {
|
|
|
3093
3896
|
}
|
|
3094
3897
|
}
|
|
3095
3898
|
function layered(preset) {
|
|
3096
|
-
const base =
|
|
3899
|
+
const base = sharedRules();
|
|
3097
3900
|
const layer = presetRules(preset);
|
|
3098
3901
|
validate("base", base);
|
|
3099
3902
|
validate(preset, layer);
|
|
@@ -3132,63 +3935,14 @@ function downgradedRulesFor(preset) {
|
|
|
3132
3935
|
}
|
|
3133
3936
|
|
|
3134
3937
|
// src/roles/lint/extra-layers.ts
|
|
3135
|
-
import { existsSync as
|
|
3136
|
-
import { isAbsolute as isAbsolute2, join as
|
|
3137
|
-
|
|
3138
|
-
// src/roles/lint/module-baseline.ts
|
|
3139
|
-
var LINT_BASELINE_FILE = ".oxlintrc.baseline.json";
|
|
3140
|
-
var LINT_MEASURE_FILE = ".oxlintrc.measure.json";
|
|
3141
|
-
var LINT_BASELINE_SPECIFIER = `./${LINT_BASELINE_FILE}`;
|
|
3142
|
-
function holdsFrom(errorCounts) {
|
|
3143
|
-
return [...errorCounts].map(([rule, count]) => ({ rule, count })).sort((left, right) => right.count - left.count || left.rule.localeCompare(right.rule));
|
|
3144
|
-
}
|
|
3145
|
-
function renderBaseline(holds, version) {
|
|
3146
|
-
const entries = holds.map(({ rule }) => ` ${JSON.stringify(rule)}: "warn"`).join(",\n");
|
|
3147
|
-
return [
|
|
3148
|
-
"{",
|
|
3149
|
-
` // Written by @hublo/sentinel@${version}. Do not edit by hand: \`sentinel --init --lint\``,
|
|
3150
|
-
" // regenerates it from a fresh measurement, and a rule you have since fixed drops out",
|
|
3151
|
-
" // and returns to `error` on its own.",
|
|
3152
|
-
" //",
|
|
3153
|
-
" // These rules are held at `warn` because this module ALREADY violated them when it",
|
|
3154
|
-
" // adopted. They still run and still report, so the build cannot fail on them.",
|
|
3155
|
-
" //",
|
|
3156
|
-
" // The hold is per RULE, not per occurrence: oxlint cannot freeze a specific list of",
|
|
3157
|
-
" // violations, so a NEW violation of one of these rules is covered too, and will only",
|
|
3158
|
-
" // warn. That is the cost of adopting without a red build, and the reason to clear this",
|
|
3159
|
-
" // file rather than live with it.",
|
|
3160
|
-
" //",
|
|
3161
|
-
" // `sentinel --inspect --lint` counts what is left. Fix them and re-run `--init --lint`",
|
|
3162
|
-
" // to get the preset severity back.",
|
|
3163
|
-
' "rules": {',
|
|
3164
|
-
entries,
|
|
3165
|
-
" }",
|
|
3166
|
-
"}",
|
|
3167
|
-
""
|
|
3168
|
-
].join("\n");
|
|
3169
|
-
}
|
|
3170
|
-
function extendsWithBaseline(current, hasHolds) {
|
|
3171
|
-
const withoutBaseline = current.filter((entry) => entry !== LINT_BASELINE_SPECIFIER);
|
|
3172
|
-
return hasHolds ? [...withoutBaseline, LINT_BASELINE_SPECIFIER] : withoutBaseline;
|
|
3173
|
-
}
|
|
3174
|
-
function describeHolds(holds) {
|
|
3175
|
-
if (holds.length === 0) return "";
|
|
3176
|
-
const total = holds.reduce((sum, hold) => sum + hold.count, 0);
|
|
3177
|
-
const listed = holds.slice(0, 5).map(({ rule, count }) => `${rule} (${count})`).join(", ");
|
|
3178
|
-
const rest = holds.length > 5 ? `, and ${holds.length - 5} more` : "";
|
|
3179
|
-
return `held ${holds.length} rule(s) at \`warn\` for this module, covering ${total} violation(s) that were already there: ${listed}${rest}. They are written to ${LINT_BASELINE_FILE}, they still report, and they return to \`error\` once fixed and re-initialized`;
|
|
3180
|
-
}
|
|
3181
|
-
|
|
3182
|
-
// src/roles/lint/extra-layers.ts
|
|
3183
|
-
function isSentinelPreset2(entry) {
|
|
3184
|
-
return entry.includes("@hublo/sentinel/oxlint/");
|
|
3185
|
-
}
|
|
3938
|
+
import { existsSync as existsSync14, readFileSync as readFileSync13 } from "fs";
|
|
3939
|
+
import { isAbsolute as isAbsolute2, join as join17, resolve as resolve3 } from "path";
|
|
3186
3940
|
function ruleCount(cwd, specifier) {
|
|
3187
3941
|
const path = isAbsolute2(specifier) ? specifier : resolve3(cwd, specifier);
|
|
3188
|
-
if (!
|
|
3942
|
+
if (!existsSync14(path)) return void 0;
|
|
3189
3943
|
try {
|
|
3190
3944
|
const parsed = parseJsonc(
|
|
3191
|
-
|
|
3945
|
+
readFileSync13(path, "utf8"),
|
|
3192
3946
|
specifier
|
|
3193
3947
|
);
|
|
3194
3948
|
return Object.keys(parsed.rules ?? {}).length;
|
|
@@ -3197,7 +3951,7 @@ function ruleCount(cwd, specifier) {
|
|
|
3197
3951
|
}
|
|
3198
3952
|
}
|
|
3199
3953
|
function extraLayers(cwd, extendsList) {
|
|
3200
|
-
return extendsList.filter((entry) => !
|
|
3954
|
+
return extendsList.filter((entry) => !isSentinelPreset(entry) && entry !== LINT_BASELINE_SPECIFIER).map((entry) => {
|
|
3201
3955
|
const count = ruleCount(cwd, entry);
|
|
3202
3956
|
return {
|
|
3203
3957
|
rule: entry,
|
|
@@ -3208,7 +3962,7 @@ function extraLayers(cwd, extendsList) {
|
|
|
3208
3962
|
function committedExtendsList(cwd, configFile) {
|
|
3209
3963
|
try {
|
|
3210
3964
|
const parsed = parseJsonc(
|
|
3211
|
-
|
|
3965
|
+
readFileSync13(join17(cwd, configFile), "utf8"),
|
|
3212
3966
|
configFile
|
|
3213
3967
|
);
|
|
3214
3968
|
return Array.isArray(parsed.extends) ? parsed.extends.filter((entry) => typeof entry === "string") : [];
|
|
@@ -3267,8 +4021,8 @@ function errorCountsByConfigRule(stdout) {
|
|
|
3267
4021
|
}
|
|
3268
4022
|
|
|
3269
4023
|
// src/core/config/read-adoption.ts
|
|
3270
|
-
import { existsSync as
|
|
3271
|
-
import { join as
|
|
4024
|
+
import { existsSync as existsSync16, readFileSync as readFileSync15 } from "fs";
|
|
4025
|
+
import { join as join19 } from "path";
|
|
3272
4026
|
|
|
3273
4027
|
// src/core/config/owned-keys.ts
|
|
3274
4028
|
function presetOwnedKeys(config, permitted, presetSets) {
|
|
@@ -3283,12 +4037,12 @@ function localOnlyKeys(config, permitted, presetSets) {
|
|
|
3283
4037
|
}
|
|
3284
4038
|
|
|
3285
4039
|
// src/core/config/resolve-config-target.ts
|
|
3286
|
-
import { existsSync as
|
|
3287
|
-
import { join as
|
|
4040
|
+
import { existsSync as existsSync15, readFileSync as readFileSync14 } from "fs";
|
|
4041
|
+
import { join as join18 } from "path";
|
|
3288
4042
|
function readExtends(absolutePath) {
|
|
3289
4043
|
let parsed;
|
|
3290
4044
|
try {
|
|
3291
|
-
parsed = parseJsonc(
|
|
4045
|
+
parsed = parseJsonc(readFileSync14(absolutePath, "utf8"), absolutePath);
|
|
3292
4046
|
} catch {
|
|
3293
4047
|
return [];
|
|
3294
4048
|
}
|
|
@@ -3302,8 +4056,8 @@ function resolveConfigTarget(moduleDir, { candidates, markers, fallback }) {
|
|
|
3302
4056
|
let existing;
|
|
3303
4057
|
let existingExtendsSomething = false;
|
|
3304
4058
|
for (const candidate of candidates) {
|
|
3305
|
-
const absolutePath =
|
|
3306
|
-
if (!
|
|
4059
|
+
const absolutePath = join18(moduleDir, candidate);
|
|
4060
|
+
if (!existsSync15(absolutePath)) continue;
|
|
3307
4061
|
const chain = readExtends(absolutePath);
|
|
3308
4062
|
if (existing === void 0) {
|
|
3309
4063
|
existing = candidate;
|
|
@@ -3326,7 +4080,7 @@ function normaliseExtends(value) {
|
|
|
3326
4080
|
}
|
|
3327
4081
|
return [];
|
|
3328
4082
|
}
|
|
3329
|
-
var
|
|
4083
|
+
var NOT_ADOPTED3 = (configFile, unreadable = null) => ({
|
|
3330
4084
|
configFile,
|
|
3331
4085
|
preset: null,
|
|
3332
4086
|
adopted: false,
|
|
@@ -3336,18 +4090,18 @@ var NOT_ADOPTED2 = (configFile, unreadable = null) => ({
|
|
|
3336
4090
|
});
|
|
3337
4091
|
function readAdoption(cwd, options) {
|
|
3338
4092
|
const target = resolveConfigTarget(cwd, options);
|
|
3339
|
-
if (target.reason === "none" || !
|
|
3340
|
-
return
|
|
4093
|
+
if (target.reason === "none" || !existsSync16(join19(cwd, target.path))) {
|
|
4094
|
+
return NOT_ADOPTED3(target.reason === "none" ? null : target.path);
|
|
3341
4095
|
}
|
|
3342
4096
|
let parsed;
|
|
3343
4097
|
try {
|
|
3344
|
-
parsed = parseJsonc(
|
|
4098
|
+
parsed = parseJsonc(readFileSync15(join19(cwd, target.path), "utf8"), target.path);
|
|
3345
4099
|
} catch (error) {
|
|
3346
4100
|
const reason = error instanceof Error ? error.message : String(error);
|
|
3347
|
-
return
|
|
4101
|
+
return NOT_ADOPTED3(target.path, `${target.path} could not be parsed (${reason})`);
|
|
3348
4102
|
}
|
|
3349
4103
|
const preset = normaliseExtends(parsed.extends).find((entry) => options.presetPattern.test(entry)) ?? null;
|
|
3350
|
-
if (preset === null) return
|
|
4104
|
+
if (preset === null) return NOT_ADOPTED3(target.path);
|
|
3351
4105
|
const settings = options.settingsKey === null ? parsed : parsed[options.settingsKey];
|
|
3352
4106
|
const drift = presetOwnedKeys(settings, options.permitted, options.presetOwns?.(preset));
|
|
3353
4107
|
return {
|
|
@@ -3375,9 +4129,9 @@ function readLintAdoption(cwd) {
|
|
|
3375
4129
|
}
|
|
3376
4130
|
|
|
3377
4131
|
// src/roles/lint/resolve-oxlint.ts
|
|
3378
|
-
import { existsSync as
|
|
4132
|
+
import { existsSync as existsSync17 } from "fs";
|
|
3379
4133
|
import { createRequire as createRequire3 } from "module";
|
|
3380
|
-
import { delimiter as delimiter2, dirname as dirname5, join as
|
|
4134
|
+
import { delimiter as delimiter2, dirname as dirname5, join as join20 } from "path";
|
|
3381
4135
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
3382
4136
|
var PACKAGE_OF = {
|
|
3383
4137
|
oxlint: "oxlint",
|
|
@@ -3402,30 +4156,30 @@ function tsgolintShim(cwd) {
|
|
|
3402
4156
|
for (const owner of ["oxlint-tsgolint", "oxlint"]) {
|
|
3403
4157
|
try {
|
|
3404
4158
|
const packageDir = dirname5(require3.resolve(`${owner}/package.json`));
|
|
3405
|
-
candidates.push(
|
|
3406
|
-
candidates.push(
|
|
4159
|
+
candidates.push(join20(packageDir, "node_modules", ".bin", "tsgolint"));
|
|
4160
|
+
candidates.push(join20(packageDir, "..", ".bin", "tsgolint"));
|
|
3407
4161
|
} catch {
|
|
3408
4162
|
}
|
|
3409
4163
|
}
|
|
3410
4164
|
try {
|
|
3411
4165
|
const ownRoot = dirname5(require3.resolve("@hublo/sentinel/package.json"));
|
|
3412
|
-
candidates.push(
|
|
4166
|
+
candidates.push(join20(ownRoot, "node_modules", ".bin", "tsgolint"));
|
|
3413
4167
|
} catch {
|
|
3414
4168
|
candidates.push(resolveBin(dirname5(fileURLToPath2(import.meta.url)), "tsgolint") ?? "");
|
|
3415
4169
|
}
|
|
3416
|
-
return candidates.find((candidate) => candidate !== "" &&
|
|
4170
|
+
return candidates.find((candidate) => candidate !== "" && existsSync17(candidate));
|
|
3417
4171
|
}
|
|
3418
4172
|
function oxlintSearchPath(cwd) {
|
|
3419
4173
|
return binSearchPath(cwd);
|
|
3420
4174
|
}
|
|
3421
4175
|
|
|
3422
4176
|
// src/roles/lint/adapters/oxlint/plan.ts
|
|
3423
|
-
import { existsSync as
|
|
3424
|
-
import { join as
|
|
4177
|
+
import { existsSync as existsSync19, readFileSync as readFileSync18 } from "fs";
|
|
4178
|
+
import { join as join23 } from "path";
|
|
3425
4179
|
|
|
3426
4180
|
// src/roles/lint/eslint-ignores.ts
|
|
3427
|
-
import { existsSync as
|
|
3428
|
-
import { join as
|
|
4181
|
+
import { existsSync as existsSync18, readFileSync as readFileSync16 } from "fs";
|
|
4182
|
+
import { join as join21 } from "path";
|
|
3429
4183
|
var IGNORE_BLOCKS = [/\bignores\s*:\s*\[([^\]]*)\]/g, /\bglobalIgnores\s*\(\s*\[([^\]]*)\]/g];
|
|
3430
4184
|
var STRING_LITERAL = /['"`]([^'"`]+)['"`]/g;
|
|
3431
4185
|
var OPAQUE_SOURCE = /\b(includeIgnoreFile)\s*\([^)]*\)/g;
|
|
@@ -3438,11 +4192,11 @@ function readRootEslintIgnores(root) {
|
|
|
3438
4192
|
return { patterns: patterns.filter((pattern) => pattern.startsWith("**/")), unresolved };
|
|
3439
4193
|
}
|
|
3440
4194
|
function readEslintIgnores(cwd) {
|
|
3441
|
-
const config = ESLINT_CONFIG_FILES.map((name) =>
|
|
4195
|
+
const config = ESLINT_CONFIG_FILES.map((name) => join21(cwd, name)).find((path) => existsSync18(path));
|
|
3442
4196
|
if (!config) return { patterns: [], unresolved: [] };
|
|
3443
4197
|
let source;
|
|
3444
4198
|
try {
|
|
3445
|
-
source =
|
|
4199
|
+
source = readFileSync16(config, "utf8");
|
|
3446
4200
|
} catch {
|
|
3447
4201
|
return { patterns: [], unresolved: [] };
|
|
3448
4202
|
}
|
|
@@ -3496,8 +4250,8 @@ function lintPresetFor(preset) {
|
|
|
3496
4250
|
}
|
|
3497
4251
|
|
|
3498
4252
|
// src/roles/lint/rename-suppressions.ts
|
|
3499
|
-
import { readdirSync as readdirSync2, readFileSync as
|
|
3500
|
-
import { join as
|
|
4253
|
+
import { readdirSync as readdirSync2, readFileSync as readFileSync17, statSync as statSync2 } from "fs";
|
|
4254
|
+
import { join as join22, relative as relative3 } from "path";
|
|
3501
4255
|
var SOURCE_EXTENSIONS = [
|
|
3502
4256
|
".ts",
|
|
3503
4257
|
".tsx",
|
|
@@ -3544,10 +4298,10 @@ function* sourceFiles(dir) {
|
|
|
3544
4298
|
return;
|
|
3545
4299
|
}
|
|
3546
4300
|
for (const entry of entries) {
|
|
3547
|
-
const full =
|
|
4301
|
+
const full = join22(dir, entry);
|
|
3548
4302
|
let isDirectory;
|
|
3549
4303
|
try {
|
|
3550
|
-
isDirectory =
|
|
4304
|
+
isDirectory = statSync2(full).isDirectory();
|
|
3551
4305
|
} catch {
|
|
3552
4306
|
continue;
|
|
3553
4307
|
}
|
|
@@ -3564,7 +4318,7 @@ function findSuppressionRenames(cwd, renames) {
|
|
|
3564
4318
|
for (const file of sourceFiles(cwd)) {
|
|
3565
4319
|
let content;
|
|
3566
4320
|
try {
|
|
3567
|
-
content =
|
|
4321
|
+
content = readFileSync17(file, "utf8");
|
|
3568
4322
|
} catch {
|
|
3569
4323
|
continue;
|
|
3570
4324
|
}
|
|
@@ -3588,7 +4342,7 @@ function findSuppressionRenames(cwd, renames) {
|
|
|
3588
4342
|
}
|
|
3589
4343
|
if (hits.length === 0) return;
|
|
3590
4344
|
found.push({
|
|
3591
|
-
file:
|
|
4345
|
+
file: relative3(cwd, file),
|
|
3592
4346
|
line: index + 1,
|
|
3593
4347
|
from: line,
|
|
3594
4348
|
to: line.replace(rules, renamed.join(", ")),
|
|
@@ -3643,7 +4397,7 @@ function summariseByPlugin(rules) {
|
|
|
3643
4397
|
}
|
|
3644
4398
|
return [...counts.entries()].sort((left, right) => right[1] - left[1]).map(([plugin, count]) => `${count} ${plugin}`).join(", ");
|
|
3645
4399
|
}
|
|
3646
|
-
function
|
|
4400
|
+
function plan2(context) {
|
|
3647
4401
|
if (!hasLintPreset(context.preset)) {
|
|
3648
4402
|
return {
|
|
3649
4403
|
operations: [],
|
|
@@ -3692,7 +4446,7 @@ function plan(context) {
|
|
|
3692
4446
|
keys: removableDeps
|
|
3693
4447
|
});
|
|
3694
4448
|
}
|
|
3695
|
-
const eslintConfigs = ESLINT_CONFIG_FILES.filter((name) =>
|
|
4449
|
+
const eslintConfigs = ESLINT_CONFIG_FILES.filter((name) => existsSync19(join23(context.cwd, name)));
|
|
3696
4450
|
for (const name of eslintConfigs) operations.push({ kind: "delete", path: name });
|
|
3697
4451
|
operations.push(
|
|
3698
4452
|
...nxTargetOperations({
|
|
@@ -3790,7 +4544,7 @@ function lintScripts(cwd) {
|
|
|
3790
4544
|
function committedExtends(cwd) {
|
|
3791
4545
|
try {
|
|
3792
4546
|
const parsed = parseJsonc(
|
|
3793
|
-
|
|
4547
|
+
readFileSync18(join23(cwd, LINT_CONFIG_FILE), "utf8"),
|
|
3794
4548
|
LINT_CONFIG_FILE
|
|
3795
4549
|
);
|
|
3796
4550
|
return Array.isArray(parsed.extends) ? parsed.extends.filter((entry) => typeof entry === "string") : [];
|
|
@@ -3801,7 +4555,7 @@ function committedExtends(cwd) {
|
|
|
3801
4555
|
function committedIgnorePatterns(cwd) {
|
|
3802
4556
|
try {
|
|
3803
4557
|
const parsed = parseJsonc(
|
|
3804
|
-
|
|
4558
|
+
readFileSync18(join23(cwd, LINT_CONFIG_FILE), "utf8"),
|
|
3805
4559
|
LINT_CONFIG_FILE
|
|
3806
4560
|
);
|
|
3807
4561
|
return Array.isArray(parsed.ignorePatterns) ? parsed.ignorePatterns.filter((entry) => typeof entry === "string") : [];
|
|
@@ -3813,7 +4567,7 @@ function moduleEslintDependencies(cwd) {
|
|
|
3813
4567
|
const isEslintPackage = (name) => name === "eslint" || name === "@types/eslint" || name === "typescript-eslint" || name.startsWith("@typescript-eslint/") || name.startsWith("eslint-plugin-") || name.startsWith("eslint-config-") || name.startsWith("@eslint/");
|
|
3814
4568
|
let manifest;
|
|
3815
4569
|
try {
|
|
3816
|
-
manifest = JSON.parse(
|
|
4570
|
+
manifest = JSON.parse(readFileSync18(join23(cwd, "package.json"), "utf8"));
|
|
3817
4571
|
} catch {
|
|
3818
4572
|
return [];
|
|
3819
4573
|
}
|
|
@@ -3872,23 +4626,37 @@ var OxlintAdapter = class extends BaseAdapter {
|
|
|
3872
4626
|
* lines. The adapter stays the contract with the engine.
|
|
3873
4627
|
*/
|
|
3874
4628
|
plan(context) {
|
|
3875
|
-
return
|
|
4629
|
+
return plan2(context);
|
|
3876
4630
|
}
|
|
3877
4631
|
declaredPreset(cwd) {
|
|
3878
4632
|
return presetOfVariant(presetNameFromPath(readLintAdoption(cwd).preset));
|
|
3879
4633
|
}
|
|
3880
4634
|
/**
|
|
3881
|
-
* The autofix pass
|
|
3882
|
-
*
|
|
4635
|
+
* The autofix pass, and what it did.
|
|
4636
|
+
*
|
|
4637
|
+
* Output is captured rather than streamed: the fix run is noise, and the verification run
|
|
4638
|
+
* that follows is the one a developer reads. But captured is not the same as discarded, and
|
|
4639
|
+
* it used to be discarded, which is why a pass that never ran looked exactly like one that
|
|
4640
|
+
* had nothing to do.
|
|
4641
|
+
*
|
|
4642
|
+
* The exit status cannot be the signal, and neither can the stream. Measured on oxlint: a
|
|
4643
|
+
* healthy run on a module with remaining violations and a run whose `extends` path resolves
|
|
4644
|
+
* nowhere BOTH exit 1 and both write to stdout. One module was checked and one was not, and
|
|
4645
|
+
* nothing in the process result separates them, so the only signal is what the tool said.
|
|
3883
4646
|
*/
|
|
3884
4647
|
fixPass(ctx, oxlint, env) {
|
|
3885
|
-
const
|
|
4648
|
+
const before = snapshotFiles(ctx.cwd, LINTABLE_EXTENSIONS);
|
|
4649
|
+
const result = spawnSync4(oxlint, ["-c", LINT_CONFIG_FILE, "--fix", "."], {
|
|
3886
4650
|
cwd: ctx.cwd,
|
|
3887
4651
|
encoding: "utf8",
|
|
3888
4652
|
env,
|
|
3889
4653
|
maxBuffer: CAPTURE_MAX_BUFFER
|
|
3890
4654
|
});
|
|
3891
|
-
return result.
|
|
4655
|
+
if (result.error) return { changed: [], failure: result.error.message };
|
|
4656
|
+
const failure = readFailure(`${result.stdout ?? ""}
|
|
4657
|
+
${result.stderr ?? ""}`);
|
|
4658
|
+
if (failure !== void 0) return { changed: [], failure };
|
|
4659
|
+
return { changed: changedSince(ctx.cwd, before, LINTABLE_EXTENSIONS), failure: void 0 };
|
|
3892
4660
|
}
|
|
3893
4661
|
/** The module's current `lint` script, so adoption replaces only the eslint part of it. */
|
|
3894
4662
|
/**
|
|
@@ -3911,7 +4679,8 @@ var OxlintAdapter = class extends BaseAdapter {
|
|
|
3911
4679
|
const oxlint = resolveOxlint(ctx.cwd);
|
|
3912
4680
|
if (!oxlint) return;
|
|
3913
4681
|
const env = { ...process.env, PATH: oxlintPath(ctx.cwd, process.env) };
|
|
3914
|
-
this.fixPass(ctx, oxlint, env)
|
|
4682
|
+
process.stderr.write(` ${describeFixOutcome(this.fixPass(ctx, oxlint, env), "lint")}
|
|
4683
|
+
`);
|
|
3915
4684
|
this.writeModuleBaseline(ctx, oxlint, env);
|
|
3916
4685
|
}
|
|
3917
4686
|
/**
|
|
@@ -3932,20 +4701,20 @@ var OxlintAdapter = class extends BaseAdapter {
|
|
|
3932
4701
|
* build the developer can see, rather than a silent half-adoption they cannot.
|
|
3933
4702
|
*/
|
|
3934
4703
|
writeModuleBaseline(ctx, oxlint, env) {
|
|
3935
|
-
const configPath =
|
|
3936
|
-
const baselinePath =
|
|
3937
|
-
if (!
|
|
4704
|
+
const configPath = join24(ctx.cwd, LINT_CONFIG_FILE);
|
|
4705
|
+
const baselinePath = join24(ctx.cwd, LINT_BASELINE_FILE);
|
|
4706
|
+
if (!existsSync20(configPath)) return;
|
|
3938
4707
|
let config;
|
|
3939
4708
|
try {
|
|
3940
4709
|
config = parseJsonc(
|
|
3941
|
-
|
|
4710
|
+
readFileSync19(configPath, "utf8"),
|
|
3942
4711
|
LINT_CONFIG_FILE
|
|
3943
4712
|
);
|
|
3944
4713
|
} catch {
|
|
3945
4714
|
return;
|
|
3946
4715
|
}
|
|
3947
4716
|
const current = Array.isArray(config.extends) ? config.extends : [];
|
|
3948
|
-
const measurePath =
|
|
4717
|
+
const measurePath = join24(ctx.cwd, LINT_MEASURE_FILE);
|
|
3949
4718
|
let measured;
|
|
3950
4719
|
try {
|
|
3951
4720
|
writeFileSync2(
|
|
@@ -3953,7 +4722,7 @@ var OxlintAdapter = class extends BaseAdapter {
|
|
|
3953
4722
|
`${JSON.stringify({ ...config, extends: extendsWithBaseline(current, false) }, null, 2)}
|
|
3954
4723
|
`
|
|
3955
4724
|
);
|
|
3956
|
-
measured =
|
|
4725
|
+
measured = spawnSync4(
|
|
3957
4726
|
oxlint,
|
|
3958
4727
|
[
|
|
3959
4728
|
"-c",
|
|
@@ -3986,14 +4755,14 @@ var OxlintAdapter = class extends BaseAdapter {
|
|
|
3986
4755
|
} else {
|
|
3987
4756
|
rmSync(baselinePath, { force: true });
|
|
3988
4757
|
}
|
|
3989
|
-
if (next.length !== current.length || next.some((entry,
|
|
4758
|
+
if (next.length !== current.length || next.some((entry, at2) => entry !== current[at2])) {
|
|
3990
4759
|
config.extends = next;
|
|
3991
4760
|
writeFileSync2(configPath, `${JSON.stringify(config, null, 2)}
|
|
3992
4761
|
`);
|
|
3993
4762
|
}
|
|
3994
4763
|
}
|
|
3995
4764
|
async run(ctx) {
|
|
3996
|
-
if (!
|
|
4765
|
+
if (!existsSync20(join24(ctx.cwd, LINT_CONFIG_FILE))) {
|
|
3997
4766
|
process.stderr.write(
|
|
3998
4767
|
`sentinel lint(oxlint): no ${LINT_CONFIG_FILE} in this module; run \`sentinel --init --lint\` to adopt.
|
|
3999
4768
|
`
|
|
@@ -4038,9 +4807,10 @@ var OxlintAdapter = class extends BaseAdapter {
|
|
|
4038
4807
|
valueFlags: OXLINT_VALUE_FLAGS
|
|
4039
4808
|
});
|
|
4040
4809
|
const targets = paths.length > 0 ? paths : ["."];
|
|
4810
|
+
const emptyScope = scopeFlags(paths);
|
|
4041
4811
|
const lint = (extra) => {
|
|
4042
|
-
const passed = [...extra, ...passedOptions];
|
|
4043
|
-
const result =
|
|
4812
|
+
const passed = [...extra, ...emptyScope, ...passedOptions];
|
|
4813
|
+
const result = spawnSync4(oxlint, ["-c", LINT_CONFIG_FILE, ...passed, ...targets], {
|
|
4044
4814
|
cwd: ctx.cwd,
|
|
4045
4815
|
stdio: "inherit",
|
|
4046
4816
|
env
|
|
@@ -4078,6 +4848,33 @@ var OxlintAdapter = class extends BaseAdapter {
|
|
|
4078
4848
|
rule: entry.rule,
|
|
4079
4849
|
reason: entry.reason
|
|
4080
4850
|
})),
|
|
4851
|
+
// A FOURTH state, and the only one that was declared and never reported: rules oxlint
|
|
4852
|
+
// cannot run at all, so they can be neither enforced nor disabled. Each preset has
|
|
4853
|
+
// carried this list since the Svelte work, and nothing read it outside a test.
|
|
4854
|
+
//
|
|
4855
|
+
// The gap that made it matter: `no-restricted-syntax` has no oxlint equivalent, so two
|
|
4856
|
+
// architectural guards a Nest service relied on vanished at adoption with nothing said.
|
|
4857
|
+
// The adopter found it himself and rebuilt them as a local plugin. The next one might
|
|
4858
|
+
// not, which is the whole argument for saying it out loud.
|
|
4859
|
+
uncovered: uncoveredFor(presetVariant(ctx.preset, ctx.cwd)).map((entry) => ({
|
|
4860
|
+
rule: entry.rule,
|
|
4861
|
+
reason: entry.reason
|
|
4862
|
+
})),
|
|
4863
|
+
// What this module HOLDS: the rules its own baseline lowers to `warn`.
|
|
4864
|
+
//
|
|
4865
|
+
// Distinct from `downgraded` just above, which is the PRESET's doing and identical for
|
|
4866
|
+
// every module on it. These are this module's own debt, and leaving them out was a real
|
|
4867
|
+
// gap: `--inspect` could tell you the preset holds 6 rules while saying nothing about
|
|
4868
|
+
// the 3 your module holds on top, so nobody could see their own debt without opening a
|
|
4869
|
+
// file that says "do not edit by hand".
|
|
4870
|
+
//
|
|
4871
|
+
// Listed, not counted. The list is free — it is the committed file — while a count means
|
|
4872
|
+
// running oxlint, which this verb deliberately does not do (that is what keeps a
|
|
4873
|
+
// 441-module sweep at ~8s per role). `--run --lint --json` carries the per-rule counts.
|
|
4874
|
+
held: heldRules(ctx.cwd).map((rule) => ({
|
|
4875
|
+
rule,
|
|
4876
|
+
reason: `held at \`warn\` by this module's ${LINT_BASELINE_FILE}, because it already violated it when it adopted. \`--run --lint --json\` counts what is left.`
|
|
4877
|
+
})),
|
|
4081
4878
|
// What this module enforces BEYOND the preset. Not drift, and not covered by anything
|
|
4082
4879
|
// else here: the stub still holds only `extends` and `ignorePatterns`, so a module with
|
|
4083
4880
|
// a team layer reports `conformant=true drift=[]` and used to say nothing at all about
|
|
@@ -4113,7 +4910,7 @@ var OxlintAdapter = class extends BaseAdapter {
|
|
|
4113
4910
|
let stub;
|
|
4114
4911
|
try {
|
|
4115
4912
|
stub = parseJsonc(
|
|
4116
|
-
|
|
4913
|
+
readFileSync19(join24(cwd, LINT_CONFIG_FILE), "utf8"),
|
|
4117
4914
|
LINT_CONFIG_FILE
|
|
4118
4915
|
);
|
|
4119
4916
|
} catch {
|
|
@@ -4123,7 +4920,7 @@ var OxlintAdapter = class extends BaseAdapter {
|
|
|
4123
4920
|
for (const entry of stub.extends ?? []) {
|
|
4124
4921
|
try {
|
|
4125
4922
|
const preset = parseJsonc(
|
|
4126
|
-
|
|
4923
|
+
readFileSync19(join24(cwd, entry), "utf8"),
|
|
4127
4924
|
entry
|
|
4128
4925
|
);
|
|
4129
4926
|
for (const rule of Object.keys(preset.rules ?? {})) names.add(rule);
|
|
@@ -4140,7 +4937,7 @@ var OxlintAdapter = class extends BaseAdapter {
|
|
|
4140
4937
|
const oxlint = resolveOxlint(ctx.cwd);
|
|
4141
4938
|
if (!oxlint) return { ok: false, code: 1, metrics: { error: "oxlint not found" } };
|
|
4142
4939
|
const typeAware = canRunTypeAware(ctx.cwd) ? ["--type-aware"] : [];
|
|
4143
|
-
const result =
|
|
4940
|
+
const result = spawnSync4(oxlint, ["-c", LINT_CONFIG_FILE, ...typeAware, "-f", "json", "."], {
|
|
4144
4941
|
cwd: ctx.cwd,
|
|
4145
4942
|
encoding: "utf8",
|
|
4146
4943
|
env: { ...process.env, PATH: oxlintPath(ctx.cwd, process.env) },
|
|
@@ -4193,14 +4990,14 @@ var OxlintAdapter = class extends BaseAdapter {
|
|
|
4193
4990
|
let parsed;
|
|
4194
4991
|
try {
|
|
4195
4992
|
parsed = parseJsonc(
|
|
4196
|
-
|
|
4993
|
+
readFileSync19(join24(cwd, LINT_CONFIG_FILE), "utf8"),
|
|
4197
4994
|
LINT_CONFIG_FILE
|
|
4198
4995
|
);
|
|
4199
4996
|
} catch {
|
|
4200
4997
|
return void 0;
|
|
4201
4998
|
}
|
|
4202
4999
|
const targets = Array.isArray(parsed.extends) ? parsed.extends.filter((entry) => typeof entry === "string") : typeof parsed.extends === "string" ? [parsed.extends] : [];
|
|
4203
|
-
return targets.find((target) => !
|
|
5000
|
+
return targets.find((target) => !existsSync20(join24(cwd, target)));
|
|
4204
5001
|
}
|
|
4205
5002
|
/** Announce what is not enforced, so reduced coverage is never silent. */
|
|
4206
5003
|
announceDisabled(preset) {
|
|
@@ -4231,36 +5028,10 @@ function registerLint() {
|
|
|
4231
5028
|
}
|
|
4232
5029
|
|
|
4233
5030
|
// src/roles/typescript/adapters/tsc/tsc.adapter.ts
|
|
4234
|
-
import { spawnSync as
|
|
4235
|
-
import { existsSync as
|
|
5031
|
+
import { spawnSync as spawnSync5 } from "child_process";
|
|
5032
|
+
import { existsSync as existsSync21, readFileSync as readFileSync21 } from "fs";
|
|
4236
5033
|
import { createRequire as createRequire4 } from "module";
|
|
4237
|
-
import { join as
|
|
4238
|
-
|
|
4239
|
-
// src/roles/typescript/presets/base.json
|
|
4240
|
-
var base_default3 = {
|
|
4241
|
-
$schema: "https://json.schemastore.org/tsconfig",
|
|
4242
|
-
compilerOptions: {
|
|
4243
|
-
strict: true,
|
|
4244
|
-
noFallthroughCasesInSwitch: true,
|
|
4245
|
-
forceConsistentCasingInFileNames: true,
|
|
4246
|
-
esModuleInterop: true,
|
|
4247
|
-
skipLibCheck: true
|
|
4248
|
-
},
|
|
4249
|
-
deferred: {
|
|
4250
|
-
noImplicitAny: {
|
|
4251
|
-
phase: 2,
|
|
4252
|
-
reason: "the implicit-any migration (TS70xx)"
|
|
4253
|
-
},
|
|
4254
|
-
noUnusedLocals: {
|
|
4255
|
-
phase: 2,
|
|
4256
|
-
reason: "unused-local cleanup (TS6133)"
|
|
4257
|
-
},
|
|
4258
|
-
noUnusedParameters: {
|
|
4259
|
-
phase: 2,
|
|
4260
|
-
reason: "unused-parameter cleanup (TS6133)"
|
|
4261
|
-
}
|
|
4262
|
-
}
|
|
4263
|
-
};
|
|
5034
|
+
import { join as join26 } from "path";
|
|
4264
5035
|
|
|
4265
5036
|
// src/roles/typescript/presets/nest.json
|
|
4266
5037
|
var nest_default2 = {
|
|
@@ -4311,15 +5082,41 @@ var react_default2 = {
|
|
|
4311
5082
|
}
|
|
4312
5083
|
};
|
|
4313
5084
|
|
|
5085
|
+
// src/roles/typescript/presets/shared.json
|
|
5086
|
+
var shared_default2 = {
|
|
5087
|
+
$schema: "https://json.schemastore.org/tsconfig",
|
|
5088
|
+
compilerOptions: {
|
|
5089
|
+
strict: true,
|
|
5090
|
+
noFallthroughCasesInSwitch: true,
|
|
5091
|
+
forceConsistentCasingInFileNames: true,
|
|
5092
|
+
esModuleInterop: true,
|
|
5093
|
+
skipLibCheck: true
|
|
5094
|
+
},
|
|
5095
|
+
deferred: {
|
|
5096
|
+
noImplicitAny: {
|
|
5097
|
+
phase: 2,
|
|
5098
|
+
reason: "the implicit-any migration (TS70xx)"
|
|
5099
|
+
},
|
|
5100
|
+
noUnusedLocals: {
|
|
5101
|
+
phase: 2,
|
|
5102
|
+
reason: "unused-local cleanup (TS6133)"
|
|
5103
|
+
},
|
|
5104
|
+
noUnusedParameters: {
|
|
5105
|
+
phase: 2,
|
|
5106
|
+
reason: "unused-parameter cleanup (TS6133)"
|
|
5107
|
+
}
|
|
5108
|
+
}
|
|
5109
|
+
};
|
|
5110
|
+
|
|
4314
5111
|
// src/roles/typescript/preset-data.ts
|
|
4315
|
-
var
|
|
5112
|
+
var SHARED = shared_default2;
|
|
4316
5113
|
var LAYERS = {
|
|
4317
5114
|
nest: nest_default2,
|
|
4318
5115
|
node: node_default2,
|
|
4319
5116
|
react: react_default2
|
|
4320
5117
|
};
|
|
4321
5118
|
var SHIPPED_PRESETS = Object.keys(LAYERS).sort();
|
|
4322
|
-
var DEFERRED_RULES = Object.entries(
|
|
5119
|
+
var DEFERRED_RULES = Object.entries(SHARED.deferred ?? {}).map(([rule, entry]) => ({ rule, phase: entry.phase, reason: entry.reason })).sort((left, right) => left.rule.localeCompare(right.rule));
|
|
4323
5120
|
function validate2() {
|
|
4324
5121
|
for (const { rule, phase, reason } of DEFERRED_RULES) {
|
|
4325
5122
|
if (!Number.isInteger(phase) || phase < 2) {
|
|
@@ -4332,9 +5129,9 @@ function validate2() {
|
|
|
4332
5129
|
`deferred rule "${rule}" has no reason; --inspect reports it and would say nothing`
|
|
4333
5130
|
);
|
|
4334
5131
|
}
|
|
4335
|
-
if (rule in
|
|
5132
|
+
if (rule in SHARED.compilerOptions) {
|
|
4336
5133
|
throw new Error(
|
|
4337
|
-
`"${rule}" is deferred and also set by the
|
|
5134
|
+
`"${rule}" is deferred and also set by the shared layer; it would be announced as not enforced while being enforced`
|
|
4338
5135
|
);
|
|
4339
5136
|
}
|
|
4340
5137
|
for (const [preset, layer] of Object.entries(LAYERS)) {
|
|
@@ -4354,8 +5151,8 @@ function tsPresetFor(preset) {
|
|
|
4354
5151
|
const layer = LAYERS[preset];
|
|
4355
5152
|
if (!layer) throw new Error(`no TypeScript preset ships for preset "${preset}"`);
|
|
4356
5153
|
return {
|
|
4357
|
-
...
|
|
4358
|
-
compilerOptions: { ...
|
|
5154
|
+
...SHARED.$schema === void 0 ? {} : { $schema: SHARED.$schema },
|
|
5155
|
+
compilerOptions: { ...SHARED.compilerOptions, ...layer.compilerOptions }
|
|
4359
5156
|
};
|
|
4360
5157
|
}
|
|
4361
5158
|
|
|
@@ -4398,7 +5195,7 @@ function resolveTsconfigTarget(moduleDir) {
|
|
|
4398
5195
|
}
|
|
4399
5196
|
|
|
4400
5197
|
// src/roles/typescript/read-adoption.ts
|
|
4401
|
-
var SENTINEL_PRESET = /^@hublo\/sentinel\/tsconfig\/[a-z-]+$/;
|
|
5198
|
+
var SENTINEL_PRESET = /^@hublo\/sentinel\/(?:typescript|tsconfig)\/[a-z-]+$/;
|
|
4402
5199
|
function readTsconfigAdoption(cwd) {
|
|
4403
5200
|
return readAdoption(cwd, {
|
|
4404
5201
|
candidates: TSCONFIG_CANDIDATES,
|
|
@@ -4415,8 +5212,8 @@ function readTsconfigAdoption(cwd) {
|
|
|
4415
5212
|
}
|
|
4416
5213
|
|
|
4417
5214
|
// src/roles/typescript/adapters/tsc/plan.ts
|
|
4418
|
-
import { readFileSync as
|
|
4419
|
-
import { join as
|
|
5215
|
+
import { readFileSync as readFileSync20 } from "fs";
|
|
5216
|
+
import { join as join25 } from "path";
|
|
4420
5217
|
|
|
4421
5218
|
// src/roles/typescript/typecheck-script.ts
|
|
4422
5219
|
var SENTINEL_TYPECHECK_COMMAND = "sentinel --run --typescript";
|
|
@@ -4450,9 +5247,11 @@ function typecheckScripts(cwd) {
|
|
|
4450
5247
|
[TYPECHECK_SCRIPT_NAME]: composeTypecheckScript(existingCommand(cwd, TYPECHECK_SCRIPT_NAME))
|
|
4451
5248
|
};
|
|
4452
5249
|
}
|
|
5250
|
+
var SENTINEL_TS_PRESET = /@hublo\/sentinel\/(?:typescript|tsconfig)\//;
|
|
4453
5251
|
function composeExtends(current, preset) {
|
|
4454
5252
|
const chain = typeof current === "string" ? [current] : Array.isArray(current) ? current.filter((entry) => typeof entry === "string") : [];
|
|
4455
|
-
|
|
5253
|
+
const withoutOwn = chain.filter((entry) => entry !== preset && !SENTINEL_TS_PRESET.test(entry));
|
|
5254
|
+
return [...withoutOwn, preset];
|
|
4456
5255
|
}
|
|
4457
5256
|
function declaredPreset(cwd) {
|
|
4458
5257
|
const { preset } = readTsconfigAdoption(cwd);
|
|
@@ -4481,7 +5280,7 @@ function planAdoption(context) {
|
|
|
4481
5280
|
};
|
|
4482
5281
|
}
|
|
4483
5282
|
const target = resolveTsconfigTarget(context.cwd);
|
|
4484
|
-
const preset = `@hublo/sentinel/
|
|
5283
|
+
const preset = `@hublo/sentinel/typescript/${context.preset}`;
|
|
4485
5284
|
const addScript = manifestOperation(context.cwd, typecheckScripts(context.cwd));
|
|
4486
5285
|
const nxTargets = nxTargetOperations({
|
|
4487
5286
|
cwd: context.cwd,
|
|
@@ -4507,7 +5306,7 @@ function planAdoption(context) {
|
|
|
4507
5306
|
};
|
|
4508
5307
|
}
|
|
4509
5308
|
const existing = parseJsonc(
|
|
4510
|
-
|
|
5309
|
+
readFileSync20(join25(context.cwd, target.path), "utf8"),
|
|
4511
5310
|
target.path
|
|
4512
5311
|
);
|
|
4513
5312
|
const extendsChain = composeExtends(existing.extends, preset);
|
|
@@ -4545,7 +5344,7 @@ function planAdoption(context) {
|
|
|
4545
5344
|
|
|
4546
5345
|
// src/roles/typescript/adapters/tsc/tsc.adapter.ts
|
|
4547
5346
|
var SENTINEL_PACKAGE = "@hublo/sentinel";
|
|
4548
|
-
var
|
|
5347
|
+
var SENTINEL_PRESET_SCOPES = [`${SENTINEL_PACKAGE}/typescript/`, `${SENTINEL_PACKAGE}/tsconfig/`];
|
|
4549
5348
|
var DIAGNOSTIC_RE = /^(.+?)\((\d+),(\d+)\): error (TS\d+): (.+)$/;
|
|
4550
5349
|
function parseDiagnostics(output) {
|
|
4551
5350
|
const diagnostics = [];
|
|
@@ -4645,7 +5444,7 @@ var TscAdapter = class _TscAdapter extends BaseAdapter {
|
|
|
4645
5444
|
let chain;
|
|
4646
5445
|
try {
|
|
4647
5446
|
const parsed = parseJsonc(
|
|
4648
|
-
|
|
5447
|
+
readFileSync21(join26(cwd, target.path), "utf8"),
|
|
4649
5448
|
target.path
|
|
4650
5449
|
);
|
|
4651
5450
|
chain = parsed.extends;
|
|
@@ -4654,11 +5453,11 @@ var TscAdapter = class _TscAdapter extends BaseAdapter {
|
|
|
4654
5453
|
}
|
|
4655
5454
|
const entries = typeof chain === "string" ? [chain] : Array.isArray(chain) ? chain : [];
|
|
4656
5455
|
const preset = entries.find(
|
|
4657
|
-
(entry) => typeof entry === "string" && entry.startsWith(
|
|
5456
|
+
(entry) => typeof entry === "string" && SENTINEL_PRESET_SCOPES.some((scope) => entry.startsWith(scope))
|
|
4658
5457
|
);
|
|
4659
5458
|
if (preset === void 0) return void 0;
|
|
4660
5459
|
try {
|
|
4661
|
-
createRequire4(
|
|
5460
|
+
createRequire4(join26(cwd, "noop.js")).resolve(preset);
|
|
4662
5461
|
return void 0;
|
|
4663
5462
|
} catch {
|
|
4664
5463
|
return preset;
|
|
@@ -4699,7 +5498,7 @@ var TscAdapter = class _TscAdapter extends BaseAdapter {
|
|
|
4699
5498
|
return { ok: false, code: 1 };
|
|
4700
5499
|
}
|
|
4701
5500
|
if (options.length > 0) return this.runWithOptions(ctx, tsc, config);
|
|
4702
|
-
const result =
|
|
5501
|
+
const result = spawnSync5(tsc, ["-b", config], { cwd: ctx.cwd, stdio: "inherit" });
|
|
4703
5502
|
if (result.error) {
|
|
4704
5503
|
process.stderr.write(
|
|
4705
5504
|
`sentinel typescript(tsc): could not run tsc (${result.error.message}); is TypeScript installed in the module?
|
|
@@ -4734,7 +5533,7 @@ var TscAdapter = class _TscAdapter extends BaseAdapter {
|
|
|
4734
5533
|
let worst = 0;
|
|
4735
5534
|
for (const project of this.referencedProjects(ctx.cwd, config)) {
|
|
4736
5535
|
const args = ["-p", project, "--noEmit", "--composite", "false", ...ctx.toolArgs ?? []];
|
|
4737
|
-
const result =
|
|
5536
|
+
const result = spawnSync5(tsc, args, { cwd: ctx.cwd, stdio: "inherit" });
|
|
4738
5537
|
if (result.error) {
|
|
4739
5538
|
process.stderr.write(
|
|
4740
5539
|
`sentinel typescript(tsc): could not run tsc (${result.error.message}); is TypeScript installed in the module?
|
|
@@ -4754,7 +5553,7 @@ var TscAdapter = class _TscAdapter extends BaseAdapter {
|
|
|
4754
5553
|
referencedProjects(cwd, config) {
|
|
4755
5554
|
try {
|
|
4756
5555
|
const parsed = parseJsonc(
|
|
4757
|
-
|
|
5556
|
+
readFileSync21(join26(cwd, config), "utf8"),
|
|
4758
5557
|
config
|
|
4759
5558
|
);
|
|
4760
5559
|
const referenced = (parsed.references ?? []).map((reference) => reference.path).filter((path) => typeof path === "string" && path.length > 0);
|
|
@@ -4770,7 +5569,7 @@ var TscAdapter = class _TscAdapter extends BaseAdapter {
|
|
|
4770
5569
|
* check.
|
|
4771
5570
|
*/
|
|
4772
5571
|
typecheckTarget(cwd) {
|
|
4773
|
-
if (
|
|
5572
|
+
if (existsSync21(join26(cwd, "tsconfig.json"))) return "tsconfig.json";
|
|
4774
5573
|
const target = resolveTsconfigTarget(cwd);
|
|
4775
5574
|
return target.reason === "none" ? null : target.path;
|
|
4776
5575
|
}
|
|
@@ -4823,7 +5622,7 @@ var TscAdapter = class _TscAdapter extends BaseAdapter {
|
|
|
4823
5622
|
return { ok: true, code: 0, metrics: _TscAdapter.NOTHING_TO_REPORT };
|
|
4824
5623
|
}
|
|
4825
5624
|
const tsc = resolveBin(ctx.cwd, "tsc") ?? "tsc";
|
|
4826
|
-
const result =
|
|
5625
|
+
const result = spawnSync5(tsc, ["-b", config], { cwd: ctx.cwd, encoding: "utf8" });
|
|
4827
5626
|
if (result.error) {
|
|
4828
5627
|
process.stderr.write(
|
|
4829
5628
|
`sentinel typescript(tsc): could not run tsc (${result.error.message}); is TypeScript installed in the module?
|
|
@@ -4866,7 +5665,7 @@ var TscAdapter = class _TscAdapter extends BaseAdapter {
|
|
|
4866
5665
|
* errors in the output mean the rule must be on.
|
|
4867
5666
|
*/
|
|
4868
5667
|
noImplicitAnyEnabled(cwd, tsc, config, mainOutput) {
|
|
4869
|
-
const shown =
|
|
5668
|
+
const shown = spawnSync5(tsc, ["-p", config, "--showConfig"], { cwd, encoding: "utf8" });
|
|
4870
5669
|
if (shown.status === 0 && shown.stdout) {
|
|
4871
5670
|
try {
|
|
4872
5671
|
const co = parseJsonc(
|
|
@@ -4892,6 +5691,7 @@ function registerAdapters() {
|
|
|
4892
5691
|
registerTypescript();
|
|
4893
5692
|
registerLint();
|
|
4894
5693
|
registerFormat();
|
|
5694
|
+
registerBuild();
|
|
4895
5695
|
}
|
|
4896
5696
|
|
|
4897
5697
|
// src/core/detect-framework.ts
|
|
@@ -4973,16 +5773,9 @@ function replaceLines(current, replacements) {
|
|
|
4973
5773
|
}
|
|
4974
5774
|
|
|
4975
5775
|
// src/core/apply-plan.ts
|
|
4976
|
-
import { existsSync as
|
|
5776
|
+
import { existsSync as existsSync22, readFileSync as readFileSync22, renameSync, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
4977
5777
|
import { resolve as resolve4, sep } from "path";
|
|
4978
5778
|
import { applyEdits, findNodeAtLocation, modify, parseTree } from "jsonc-parser";
|
|
4979
|
-
|
|
4980
|
-
// src/shared/deep-merge.ts
|
|
4981
|
-
function isPlainObject(value) {
|
|
4982
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4983
|
-
}
|
|
4984
|
-
|
|
4985
|
-
// src/core/apply-plan.ts
|
|
4986
5779
|
function resolveWithinRoot(cwd, relativePath) {
|
|
4987
5780
|
const root = resolve4(cwd);
|
|
4988
5781
|
const absolutePath = resolve4(root, relativePath);
|
|
@@ -4992,7 +5785,7 @@ function resolveWithinRoot(cwd, relativePath) {
|
|
|
4992
5785
|
return absolutePath;
|
|
4993
5786
|
}
|
|
4994
5787
|
function readIfExists(absolutePath) {
|
|
4995
|
-
return
|
|
5788
|
+
return existsSync22(absolutePath) ? readFileSync22(absolutePath, "utf8") : void 0;
|
|
4996
5789
|
}
|
|
4997
5790
|
function* leaves(value, prefix = []) {
|
|
4998
5791
|
for (const [key, keyValue] of Object.entries(value)) {
|
|
@@ -5073,9 +5866,9 @@ function applyOperationTo(current, operation) {
|
|
|
5073
5866
|
}
|
|
5074
5867
|
}
|
|
5075
5868
|
}
|
|
5076
|
-
function preparePlan(cwd,
|
|
5869
|
+
function preparePlan(cwd, plan3) {
|
|
5077
5870
|
const prepared = /* @__PURE__ */ new Map();
|
|
5078
|
-
for (const operation of
|
|
5871
|
+
for (const operation of plan3.operations) {
|
|
5079
5872
|
const absolutePath = resolveWithinRoot(cwd, operation.path);
|
|
5080
5873
|
const existing = prepared.get(operation.path);
|
|
5081
5874
|
const before = existing?.before ?? readIfExists(absolutePath) ?? "";
|
|
@@ -5097,8 +5890,8 @@ function writeFileAtomic(absolutePath, contents) {
|
|
|
5097
5890
|
writeFileSync3(tempPath, contents);
|
|
5098
5891
|
renameSync(tempPath, absolutePath);
|
|
5099
5892
|
}
|
|
5100
|
-
function applyPlan(cwd,
|
|
5101
|
-
const changed = preparePlan(cwd,
|
|
5893
|
+
function applyPlan(cwd, plan3) {
|
|
5894
|
+
const changed = preparePlan(cwd, plan3).filter((file) => file.before !== file.after);
|
|
5102
5895
|
for (const file of changed) {
|
|
5103
5896
|
if (file.deleted) {
|
|
5104
5897
|
rmSync2(file.absolutePath, { force: true });
|
|
@@ -5110,8 +5903,8 @@ function applyPlan(cwd, plan2) {
|
|
|
5110
5903
|
}
|
|
5111
5904
|
|
|
5112
5905
|
// src/core/config/preset-evidence.ts
|
|
5113
|
-
import { existsSync as
|
|
5114
|
-
import { join as
|
|
5906
|
+
import { existsSync as existsSync23, readdirSync as readdirSync3, readFileSync as readFileSync23 } from "fs";
|
|
5907
|
+
import { join as join27 } from "path";
|
|
5115
5908
|
var PATH_SIGNALS = [
|
|
5116
5909
|
{
|
|
5117
5910
|
preset: "nest",
|
|
@@ -5125,11 +5918,11 @@ var DEPENDENCY_SIGNALS = [
|
|
|
5125
5918
|
{ preset: "nest", pattern: /^@nestjs\// }
|
|
5126
5919
|
];
|
|
5127
5920
|
function dependencyNames(cwd) {
|
|
5128
|
-
const path =
|
|
5129
|
-
if (!
|
|
5921
|
+
const path = join27(cwd, "package.json");
|
|
5922
|
+
if (!existsSync23(path)) return [];
|
|
5130
5923
|
try {
|
|
5131
5924
|
const manifest = parseJsonc(
|
|
5132
|
-
|
|
5925
|
+
readFileSync23(path, "utf8"),
|
|
5133
5926
|
path
|
|
5134
5927
|
);
|
|
5135
5928
|
return [
|
|
@@ -5152,7 +5945,7 @@ function declaresJsx(cwd) {
|
|
|
5152
5945
|
for (const name of entries) {
|
|
5153
5946
|
try {
|
|
5154
5947
|
const config = parseJsonc(
|
|
5155
|
-
|
|
5948
|
+
readFileSync23(join27(cwd, name), "utf8"),
|
|
5156
5949
|
name
|
|
5157
5950
|
);
|
|
5158
5951
|
if (config.compilerOptions?.jsx !== void 0) return true;
|
|
@@ -5203,14 +5996,14 @@ function resolveFlavour(opts) {
|
|
|
5203
5996
|
}
|
|
5204
5997
|
return detection.preset;
|
|
5205
5998
|
}
|
|
5206
|
-
function previewPlan(opts,
|
|
5207
|
-
const changed = preparePlan(opts.cwd,
|
|
5999
|
+
function previewPlan(opts, plan3) {
|
|
6000
|
+
const changed = preparePlan(opts.cwd, plan3).filter((file) => file.before !== file.after);
|
|
5208
6001
|
if (opts.json) {
|
|
5209
6002
|
process.stdout.write(
|
|
5210
6003
|
JSON.stringify(
|
|
5211
6004
|
{
|
|
5212
6005
|
dryRun: true,
|
|
5213
|
-
notes:
|
|
6006
|
+
notes: plan3.notes ?? [],
|
|
5214
6007
|
files: changed.map(({ path, before, after, deleted }) => ({
|
|
5215
6008
|
path,
|
|
5216
6009
|
action: deleted ? "delete" : before.length === 0 ? "create" : "update",
|
|
@@ -5225,7 +6018,7 @@ function previewPlan(opts, plan2) {
|
|
|
5225
6018
|
return 0;
|
|
5226
6019
|
}
|
|
5227
6020
|
process.stderr.write(" dry run: no files written\n");
|
|
5228
|
-
for (const note of
|
|
6021
|
+
for (const note of plan3.notes ?? []) process.stderr.write(` ${note}
|
|
5229
6022
|
`);
|
|
5230
6023
|
if (changed.length === 0) {
|
|
5231
6024
|
process.stderr.write(" nothing to change\n");
|
|
@@ -5246,7 +6039,8 @@ async function dispatch(opts) {
|
|
|
5246
6039
|
throw new Error(`dispatch handles --init only; --${opts.verb} routes through analyse()`);
|
|
5247
6040
|
}
|
|
5248
6041
|
const detected = resolveFlavour(opts);
|
|
5249
|
-
const
|
|
6042
|
+
const resolutionPreset = opts.preset ?? declaredPresetFor(opts.target, opts.cwd) ?? detected;
|
|
6043
|
+
const adapter = resolve(opts.target, resolutionPreset, opts.runner);
|
|
5250
6044
|
const preset = opts.preset ?? adapter.declaredPreset?.(opts.cwd) ?? detected;
|
|
5251
6045
|
if (opts.preset !== void 0) {
|
|
5252
6046
|
const contradiction = presetContradiction(opts.preset, opts.cwd);
|
|
@@ -5268,25 +6062,25 @@ async function dispatch(opts) {
|
|
|
5268
6062
|
}
|
|
5269
6063
|
}
|
|
5270
6064
|
const context = { cwd: opts.cwd, preset: effective };
|
|
5271
|
-
const
|
|
5272
|
-
if (
|
|
5273
|
-
process.stderr.write(`sentinel (${opts.target}): ${
|
|
6065
|
+
const plan3 = await adapter.plan(context);
|
|
6066
|
+
if (plan3.blocked) {
|
|
6067
|
+
process.stderr.write(`sentinel (${opts.target}): ${plan3.blocked}
|
|
5274
6068
|
`);
|
|
5275
6069
|
return 1;
|
|
5276
6070
|
}
|
|
5277
|
-
if (
|
|
5278
|
-
process.stderr.write(` ${palette(process.stderr).dim(`${opts.target}: ${
|
|
6071
|
+
if (plan3.skipped) {
|
|
6072
|
+
process.stderr.write(` ${palette(process.stderr).dim(`${opts.target}: ${plan3.skipped}`)}
|
|
5279
6073
|
`);
|
|
5280
6074
|
return 0;
|
|
5281
6075
|
}
|
|
5282
6076
|
if (opts.dryRun) {
|
|
5283
|
-
return previewPlan(opts,
|
|
6077
|
+
return previewPlan(opts, plan3);
|
|
5284
6078
|
}
|
|
5285
|
-
for (const change of applyPlan(opts.cwd,
|
|
6079
|
+
for (const change of applyPlan(opts.cwd, plan3)) {
|
|
5286
6080
|
process.stderr.write(` ${change.deleted ? "removed" : "wrote"} ${change.path}
|
|
5287
6081
|
`);
|
|
5288
6082
|
}
|
|
5289
|
-
for (const note of
|
|
6083
|
+
for (const note of plan3.notes ?? []) process.stderr.write(` ${note}
|
|
5290
6084
|
`);
|
|
5291
6085
|
if (adapter.afterInit) {
|
|
5292
6086
|
process.stderr.write(` fixing what ${opts.target} can fix automatically...
|
|
@@ -5303,27 +6097,30 @@ async function dispatch(opts) {
|
|
|
5303
6097
|
}
|
|
5304
6098
|
|
|
5305
6099
|
export {
|
|
6100
|
+
PresetUnsupportedError,
|
|
5306
6101
|
register,
|
|
5307
6102
|
setDefaultRunner,
|
|
5308
6103
|
all,
|
|
6104
|
+
declaredPresetFor,
|
|
5309
6105
|
availableTargets,
|
|
5310
6106
|
resolve,
|
|
5311
6107
|
BaseAdapter,
|
|
6108
|
+
resolveBin,
|
|
5312
6109
|
readOwnVersion,
|
|
5313
6110
|
readProjectPackageJson,
|
|
5314
6111
|
readNxProjectName,
|
|
5315
6112
|
VERBS,
|
|
5316
6113
|
TARGETS,
|
|
6114
|
+
SWEEPABLE_TARGETS,
|
|
5317
6115
|
PRESET_NAMES,
|
|
5318
6116
|
WORKSPACE_ROOT_MARKER,
|
|
5319
6117
|
findWorkspaceRoot,
|
|
5320
6118
|
ensureWorkspacePrep,
|
|
5321
6119
|
inspectWorkspacePrep,
|
|
5322
6120
|
palette,
|
|
5323
|
-
resolveBin,
|
|
5324
6121
|
registerAdapters,
|
|
5325
6122
|
describeFramework,
|
|
5326
6123
|
detectFramework,
|
|
5327
6124
|
dispatch
|
|
5328
6125
|
};
|
|
5329
|
-
//# sourceMappingURL=chunk-
|
|
6126
|
+
//# sourceMappingURL=chunk-FLJ2QNFF.js.map
|