@hublo/sentinel 1.2.0-alpha.0 → 1.2.0-alpha.2
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 +2 -2
- package/dist/bin/sentinel.d.ts +5 -0
- package/dist/bin/sentinel.js +27 -4
- package/dist/{chunk-TWL6T237.js → chunk-SA2RMTVI.js} +886 -528
- package/dist/index.js +1 -2
- package/dist/roles/build/toolchain.d.ts +5 -0
- package/dist/roles/build/toolchain.js +16 -0
- package/package.json +4 -3
- package/dist/chunk-XSCDJZDY.js +0 -26
- package/dist/roles/build/react-app.d.ts +0 -116
- package/dist/roles/build/react-app.js +0 -111
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
REACT_APP_DEFAULTS,
|
|
3
|
-
isPlainObject
|
|
4
|
-
} from "./chunk-XSCDJZDY.js";
|
|
5
|
-
|
|
6
1
|
// src/core/registry.ts
|
|
7
2
|
var adapters = [];
|
|
8
3
|
var PresetUnsupportedError = class extends Error {
|
|
@@ -133,20 +128,6 @@ function readNxProjectName(dir) {
|
|
|
133
128
|
}
|
|
134
129
|
}
|
|
135
130
|
|
|
136
|
-
// src/shared/color.ts
|
|
137
|
-
import { styleText } from "util";
|
|
138
|
-
function palette(stream) {
|
|
139
|
-
const paint = (format, text) => styleText(format, text, { stream });
|
|
140
|
-
return {
|
|
141
|
-
ok: (text) => paint(["green", "bold"], text),
|
|
142
|
-
fail: (text) => paint(["red", "bold"], text),
|
|
143
|
-
warn: (text) => paint("yellow", text),
|
|
144
|
-
strong: (text) => paint("bold", text),
|
|
145
|
-
dim: (text) => paint("dim", text),
|
|
146
|
-
accent: (text) => paint("cyan", text)
|
|
147
|
-
};
|
|
148
|
-
}
|
|
149
|
-
|
|
150
131
|
// src/core/domain.ts
|
|
151
132
|
var VERBS = ["run", "inspect", "init", "migrate", "report", "status"];
|
|
152
133
|
var TARGETS = [
|
|
@@ -166,7 +147,21 @@ var SWEEPABLE_TARGETS = TARGETS.filter(
|
|
|
166
147
|
);
|
|
167
148
|
var PRESET_NAMES = ["react", "nest", "svelte", "node", "tools"];
|
|
168
149
|
|
|
169
|
-
// src/
|
|
150
|
+
// src/shared/color.ts
|
|
151
|
+
import { styleText } from "util";
|
|
152
|
+
function palette(stream) {
|
|
153
|
+
const paint = (format, text) => styleText(format, text, { stream });
|
|
154
|
+
return {
|
|
155
|
+
ok: (text) => paint(["green", "bold"], text),
|
|
156
|
+
fail: (text) => paint(["red", "bold"], text),
|
|
157
|
+
warn: (text) => paint("yellow", text),
|
|
158
|
+
strong: (text) => paint("bold", text),
|
|
159
|
+
dim: (text) => paint("dim", text),
|
|
160
|
+
accent: (text) => paint("cyan", text)
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// src/roles/build/adapters/vite/vite-role.adapter.ts
|
|
170
165
|
import { spawnSync } from "child_process";
|
|
171
166
|
|
|
172
167
|
// src/core/config/tool-args.ts
|
|
@@ -206,9 +201,9 @@ function binFromOwnInstall(packageName, binName) {
|
|
|
206
201
|
try {
|
|
207
202
|
const manifest = require2.resolve(`${packageName}/package.json`);
|
|
208
203
|
const bin = require2(manifest).bin;
|
|
209
|
-
const
|
|
210
|
-
if (!
|
|
211
|
-
const executable = join2(dirname2(manifest),
|
|
204
|
+
const relative4 = typeof bin === "string" ? bin : bin?.[binName];
|
|
205
|
+
if (!relative4) return void 0;
|
|
206
|
+
const executable = join2(dirname2(manifest), relative4);
|
|
212
207
|
return existsSync3(executable) ? executable : void 0;
|
|
213
208
|
} catch {
|
|
214
209
|
return void 0;
|
|
@@ -222,7 +217,7 @@ function binSearchPath(cwd) {
|
|
|
222
217
|
|
|
223
218
|
// src/roles/build/plan.ts
|
|
224
219
|
import { existsSync as existsSync7, readFileSync as readFileSync5 } from "fs";
|
|
225
|
-
import { join as join7 } from "path";
|
|
220
|
+
import { basename as basename2, join as join7 } from "path";
|
|
226
221
|
|
|
227
222
|
// src/core/config/existing-command.ts
|
|
228
223
|
import { readFileSync as readFileSync3 } from "fs";
|
|
@@ -383,7 +378,85 @@ function keepsOtherCommands(script, sentinelCommand) {
|
|
|
383
378
|
return script.split(SEGMENT_SEPARATOR).filter((_, index) => !isSeparatorAt(index)).some((segment) => segment.trim() !== "" && segment.trim() !== sentinelCommand);
|
|
384
379
|
}
|
|
385
380
|
|
|
381
|
+
// src/roles/build/presets/requirements.json
|
|
382
|
+
var requirements_default = {
|
|
383
|
+
svelte: {
|
|
384
|
+
why: "sentinel ships Vite 8, and @sveltejs/vite-plugin-svelte caps at Vite 6 until its major 7",
|
|
385
|
+
requires: {
|
|
386
|
+
"@sveltejs/vite-plugin-svelte": "7.0.0",
|
|
387
|
+
svelte: "5.46.4"
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
};
|
|
391
|
+
|
|
392
|
+
// src/roles/build/presets/toolchain.json
|
|
393
|
+
var toolchain_default = {
|
|
394
|
+
$comment: "The build toolchain sentinel owns. One table, because three places used to hold parts of it: the list to remove from a module's package.json, the list of import specifiers to rewrite, and the map from a default import to the name sentinel re-exports it under. A package added to one and not the others gives a removal with no rewrite, or an import pointing at a package the module no longer declares.",
|
|
395
|
+
owned: [
|
|
396
|
+
{
|
|
397
|
+
package: "vite",
|
|
398
|
+
specifier: "vite",
|
|
399
|
+
named: ["defineConfig", "loadEnv", "mergeConfig"],
|
|
400
|
+
types: [
|
|
401
|
+
"Plugin",
|
|
402
|
+
"PluginOption",
|
|
403
|
+
"SassPreprocessorOptions",
|
|
404
|
+
"UserConfig",
|
|
405
|
+
"UserConfigExport"
|
|
406
|
+
],
|
|
407
|
+
why: "the bundler itself. `defineConfig`, `loadEnv` and `mergeConfig` are what every config in this repo imports from it, and the types travel with them: a config importing PluginOption from a package it no longer declares builds but does not typecheck."
|
|
408
|
+
},
|
|
409
|
+
{
|
|
410
|
+
package: "@vitejs/plugin-react",
|
|
411
|
+
specifier: "@vitejs/plugin-react",
|
|
412
|
+
default: "react",
|
|
413
|
+
why: "the React transform. Bound to the Vite that runs it, so it has to come from the same install."
|
|
414
|
+
},
|
|
415
|
+
{
|
|
416
|
+
package: "@tailwindcss/vite",
|
|
417
|
+
specifier: "@tailwindcss/vite",
|
|
418
|
+
default: "tailwindcss",
|
|
419
|
+
why: "the Tailwind pipeline, identical in all three front apps."
|
|
420
|
+
},
|
|
421
|
+
{
|
|
422
|
+
package: "vite-plugin-svgr",
|
|
423
|
+
specifier: "vite-plugin-svgr",
|
|
424
|
+
default: "svgr",
|
|
425
|
+
why: "SVG as components, identical in all three front apps."
|
|
426
|
+
},
|
|
427
|
+
{
|
|
428
|
+
package: "nitro",
|
|
429
|
+
specifier: "nitro/vite",
|
|
430
|
+
named: ["nitro"],
|
|
431
|
+
why: "the SSR server. Note the package and the import specifier differ, which is exactly the kind of split that made three separate lists disagree."
|
|
432
|
+
}
|
|
433
|
+
],
|
|
434
|
+
add: [
|
|
435
|
+
{
|
|
436
|
+
package: "@hublo/sentinel",
|
|
437
|
+
why: "the module now gets its toolchain from here, so it declares it. Pinned by the installer rather than by us: `--init` writes the dependency, `pnpm install` resolves it."
|
|
438
|
+
}
|
|
439
|
+
],
|
|
440
|
+
$notOwned: {
|
|
441
|
+
"@rolldown/plugin-babel": "declared by career and host-admin themselves, not at the root. It supports LocatorJS, a development convenience gated on LOCATORJS=true, not shared build infrastructure. Removing it would remove a feature from two teams, which is their call and must not ride along inside an adoption.",
|
|
442
|
+
"@locator/babel-jsx": "same, and same owners.",
|
|
443
|
+
"@tanstack/react-start": "a framework the apps code against: 48 source files import it directly.",
|
|
444
|
+
"@tanstack/router-plugin": "generates the app's own route tree.",
|
|
445
|
+
"vite-bundle-analyzer": "at the root at 1.3.7 and imported nowhere, the only mention being a commented-out line in host-admin. Not a candidate to re-export, a candidate to delete from the root."
|
|
446
|
+
}
|
|
447
|
+
};
|
|
448
|
+
|
|
449
|
+
// src/roles/build/preset-data.ts
|
|
450
|
+
var REQUIREMENTS = requirements_default;
|
|
451
|
+
var OWNED_PACKAGES = toolchain_default.owned;
|
|
452
|
+
|
|
386
453
|
// src/roles/build/config-policy.ts
|
|
454
|
+
var COMPANION_CONFIG_FILES = [
|
|
455
|
+
"vitest.config.ts",
|
|
456
|
+
"vitest.config.mts",
|
|
457
|
+
"vitest.config.js",
|
|
458
|
+
"vitest.config.mjs"
|
|
459
|
+
];
|
|
387
460
|
var BUILD_CONFIG_FILES = [
|
|
388
461
|
"vite.config.ts",
|
|
389
462
|
"vite.config.mts",
|
|
@@ -399,17 +472,13 @@ function buildTarget() {
|
|
|
399
472
|
return {
|
|
400
473
|
[BUILD_SCRIPT_NAME]: {
|
|
401
474
|
cache: true,
|
|
402
|
-
inputs: ["default", "^default", `{projectRoot}/${
|
|
475
|
+
inputs: ["default", "^default", ...BUILD_CONFIG_FILES.map((name) => `{projectRoot}/${name}`)]
|
|
403
476
|
}
|
|
404
477
|
};
|
|
405
478
|
}
|
|
406
|
-
var SENTINEL_OWNED_BUILD_PACKAGES =
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
"@tailwindcss/vite",
|
|
410
|
-
"vite-plugin-svgr",
|
|
411
|
-
"nitro"
|
|
412
|
-
];
|
|
479
|
+
var SENTINEL_OWNED_BUILD_PACKAGES = OWNED_PACKAGES.map(
|
|
480
|
+
(entry) => entry.package
|
|
481
|
+
);
|
|
413
482
|
|
|
414
483
|
// src/roles/build/dev-script.ts
|
|
415
484
|
var CHAIN = " -- ";
|
|
@@ -446,17 +515,24 @@ var NOT_ADOPTED = (configFile, unreadable = null) => ({
|
|
|
446
515
|
function buildConfigFile(cwd) {
|
|
447
516
|
return BUILD_CONFIG_FILES.find((name) => existsSync6(join6(cwd, name)));
|
|
448
517
|
}
|
|
518
|
+
function importsSpecifier(source, specifier) {
|
|
519
|
+
const escaped = specifier.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
520
|
+
return new RegExp(`(?:from|import)\\s*\\(?\\s*['"\`]${escaped}(?:/[^'"\`]*)?['"\`]`).test(source);
|
|
521
|
+
}
|
|
449
522
|
function importsPreset(source) {
|
|
450
|
-
|
|
451
|
-
return new RegExp(`(?:from|import)\\s*\\(?\\s*['"\`]${specifier}['"\`]`).test(source);
|
|
523
|
+
return importsSpecifier(source, BUILD_PRESET_SPECIFIER);
|
|
452
524
|
}
|
|
453
525
|
var REACT_PLUGIN_SPECIFIERS = ["@vitejs/plugin-react", "@tanstack/react-start"];
|
|
454
526
|
function declaredBuildPreset(cwd) {
|
|
527
|
+
const source = readBuildConfigSource(cwd);
|
|
528
|
+
if (source === void 0) return void 0;
|
|
529
|
+
return REACT_PLUGIN_SPECIFIERS.some((specifier) => importsSpecifier(source, specifier)) ? "react" : void 0;
|
|
530
|
+
}
|
|
531
|
+
function readBuildConfigSource(cwd) {
|
|
455
532
|
const configFile = buildConfigFile(cwd);
|
|
456
533
|
if (!configFile) return void 0;
|
|
457
534
|
try {
|
|
458
|
-
|
|
459
|
-
return REACT_PLUGIN_SPECIFIERS.some((specifier) => source.includes(specifier)) ? "react" : void 0;
|
|
535
|
+
return readFileSync4(join6(cwd, configFile), "utf8");
|
|
460
536
|
} catch {
|
|
461
537
|
return void 0;
|
|
462
538
|
}
|
|
@@ -489,39 +565,131 @@ function readBuildAdoption(cwd) {
|
|
|
489
565
|
};
|
|
490
566
|
}
|
|
491
567
|
|
|
492
|
-
// src/roles/build/
|
|
493
|
-
function
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
568
|
+
// src/roles/build/requirements.ts
|
|
569
|
+
function atLeast(declared, needed) {
|
|
570
|
+
const parse2 = (value) => value.replace(/^[\^~>=<\s]+/, "").split("-")[0].split(".").map((part) => Number.parseInt(part, 10) || 0);
|
|
571
|
+
const left = parse2(declared);
|
|
572
|
+
const right = parse2(needed);
|
|
573
|
+
for (let at = 0; at < Math.max(left.length, right.length); at++) {
|
|
574
|
+
const a = left[at] ?? 0;
|
|
575
|
+
const b = right[at] ?? 0;
|
|
576
|
+
if (a !== b) return a > b;
|
|
577
|
+
}
|
|
578
|
+
return true;
|
|
579
|
+
}
|
|
580
|
+
function unmetRequirements(preset, declared) {
|
|
581
|
+
const entry = REQUIREMENTS[preset];
|
|
582
|
+
if (!entry) return [];
|
|
583
|
+
return Object.entries(entry.requires).filter(([name, needed]) => {
|
|
584
|
+
const have = declared[name];
|
|
585
|
+
return have === void 0 || !atLeast(have, needed);
|
|
586
|
+
}).map(([name, needed]) => ({ name, declared: declared[name], needed }));
|
|
587
|
+
}
|
|
588
|
+
function describeUnmet(preset, unmet) {
|
|
589
|
+
const entry = REQUIREMENTS[preset];
|
|
590
|
+
const list = unmet.map(
|
|
591
|
+
({ name, declared, needed }) => `${name} ${declared === void 0 ? "is not declared here" : `is at ${declared}`}, needs >= ${needed}`
|
|
592
|
+
).join("; ");
|
|
593
|
+
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\`.`;
|
|
594
|
+
}
|
|
498
595
|
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
}
|
|
522
|
-
)
|
|
523
|
-
|
|
596
|
+
// src/roles/build/rewrite-imports.ts
|
|
597
|
+
import { parseSync } from "oxc-parser";
|
|
598
|
+
var MOVED_SPECIFIERS = new Set(OWNED_PACKAGES.map((entry) => entry.specifier));
|
|
599
|
+
function esmBlocker(fileName, packageType) {
|
|
600
|
+
if (/\.(mts|mjs)$/.test(fileName)) return void 0;
|
|
601
|
+
if (packageType === "module") return void 0;
|
|
602
|
+
return `sentinel is ESM only, and this module does not declare \`"type": "module"\`, so Vite would load ${fileName} as CommonJS and fail to require it. Add \`"type": "module"\` to this module's package.json, or rename the config to \`${fileName.replace(/\.(ts|js)$/, ".m$1")}\`, then run this again.`;
|
|
603
|
+
}
|
|
604
|
+
var DEFAULT_EXPORT_NAMES = Object.fromEntries(
|
|
605
|
+
OWNED_PACKAGES.filter((entry) => entry.default !== void 0).map((entry) => [
|
|
606
|
+
entry.specifier,
|
|
607
|
+
entry.default
|
|
608
|
+
])
|
|
609
|
+
);
|
|
610
|
+
function renderImport(bindings) {
|
|
611
|
+
const render = (list) => list.map((b) => b.exported === b.local ? b.exported : `${b.exported} as ${b.local}`).sort((left, right) => left.localeCompare(right)).join(", ");
|
|
612
|
+
const values = bindings.filter((b) => !b.typeOnly);
|
|
613
|
+
const types = bindings.filter((b) => b.typeOnly);
|
|
614
|
+
const lines = [];
|
|
615
|
+
if (values.length > 0) lines.push(`import { ${render(values)} } from '${BUILD_PRESET_SPECIFIER}'`);
|
|
616
|
+
if (types.length > 0) {
|
|
617
|
+
lines.push(`import type { ${render(types)} } from '${BUILD_PRESET_SPECIFIER}'`);
|
|
618
|
+
}
|
|
619
|
+
return lines.join("\n");
|
|
620
|
+
}
|
|
621
|
+
function rewriteBuildImports(source, fileName = "vite.config.ts") {
|
|
622
|
+
const { program, errors } = parseSync(fileName, source, { sourceType: "module" });
|
|
623
|
+
if (errors.length > 0) {
|
|
624
|
+
return {
|
|
625
|
+
kind: "blocked",
|
|
626
|
+
why: `${fileName} does not parse: ${errors[0]?.message ?? "unknown"}`
|
|
627
|
+
};
|
|
628
|
+
}
|
|
629
|
+
const imports = program.body.filter(
|
|
630
|
+
(node) => node.type === "ImportDeclaration"
|
|
631
|
+
);
|
|
632
|
+
if (imports.some((node) => node.source.value === BUILD_PRESET_SPECIFIER)) {
|
|
633
|
+
return { kind: "unchanged", why: "this config already imports its toolchain from sentinel" };
|
|
634
|
+
}
|
|
635
|
+
const moving = imports.filter((node) => MOVED_SPECIFIERS.has(node.source.value));
|
|
636
|
+
if (moving.length === 0) {
|
|
637
|
+
return {
|
|
638
|
+
kind: "unchanged",
|
|
639
|
+
why: "this config imports none of the packages sentinel owns, so there is nothing to move"
|
|
640
|
+
};
|
|
641
|
+
}
|
|
642
|
+
const bindings = [];
|
|
643
|
+
for (const node of moving) {
|
|
644
|
+
const from = node.source.value;
|
|
645
|
+
const specifiers = node.specifiers ?? [];
|
|
646
|
+
if (specifiers.length === 0) {
|
|
647
|
+
return { kind: "blocked", why: `\`import '${from}'\` has no bindings to move` };
|
|
648
|
+
}
|
|
649
|
+
for (const spec of specifiers) {
|
|
650
|
+
const typeOnly = node.importKind === "type" || spec.importKind === "type";
|
|
651
|
+
if (spec.type === "ImportDefaultSpecifier") {
|
|
652
|
+
const exported = DEFAULT_EXPORT_NAMES[from];
|
|
653
|
+
if (!exported) {
|
|
654
|
+
return {
|
|
655
|
+
kind: "blocked",
|
|
656
|
+
why: `no sentinel export for the default import of \`${from}\``
|
|
657
|
+
};
|
|
658
|
+
}
|
|
659
|
+
bindings.push({ exported, local: spec.local.name, typeOnly });
|
|
660
|
+
continue;
|
|
661
|
+
}
|
|
662
|
+
if (spec.type === "ImportSpecifier") {
|
|
663
|
+
bindings.push({
|
|
664
|
+
exported: spec.imported?.name ?? spec.imported?.value ?? spec.local.name,
|
|
665
|
+
local: spec.local.name,
|
|
666
|
+
typeOnly
|
|
667
|
+
});
|
|
668
|
+
continue;
|
|
669
|
+
}
|
|
670
|
+
return {
|
|
671
|
+
kind: "blocked",
|
|
672
|
+
why: `\`import * as ${spec.local.name} from '${from}'\` cannot be mapped onto sentinel's named exports`
|
|
673
|
+
};
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
const spans = moving.map((node) => ({ start: node.start, end: node.end })).sort((left, right) => left.start - right.start);
|
|
677
|
+
const first = spans[0];
|
|
678
|
+
const rest = spans.slice(1);
|
|
679
|
+
let out = source;
|
|
680
|
+
for (const span of rest.slice().reverse()) {
|
|
681
|
+
const after = out.slice(span.end).match(/^\r?\n/)?.[0].length ?? 0;
|
|
682
|
+
out = out.slice(0, span.start) + out.slice(span.end + after);
|
|
683
|
+
}
|
|
684
|
+
out = out.slice(0, first.start) + renderImport(bindings) + out.slice(first.end);
|
|
685
|
+
return {
|
|
686
|
+
kind: "rewritten",
|
|
687
|
+
source: out,
|
|
688
|
+
moved: [...new Set(moving.map((node) => node.source.value))].sort()
|
|
689
|
+
};
|
|
524
690
|
}
|
|
691
|
+
|
|
692
|
+
// src/roles/build/plan.ts
|
|
525
693
|
function ownedBuildDependencies(cwd) {
|
|
526
694
|
const manifest = readProjectPackageJson(cwd);
|
|
527
695
|
const keys = [];
|
|
@@ -550,32 +718,68 @@ function buildScripts(cwd) {
|
|
|
550
718
|
};
|
|
551
719
|
const existingDev = existingCommand(cwd, DEV_SCRIPT_NAME);
|
|
552
720
|
if (existingDev !== void 0) {
|
|
721
|
+
const own2 = basename2(cwd);
|
|
553
722
|
scripts[DEV_SCRIPT_NAME] = composeDevScript(
|
|
554
723
|
existingDev,
|
|
555
|
-
selfCommand(cwd, "--run --dev") ?? SENTINEL_DEV_COMMAND
|
|
724
|
+
`${selfCommand(cwd, "--run --dev") ?? SENTINEL_DEV_COMMAND} --module ${own2}`
|
|
556
725
|
);
|
|
557
726
|
}
|
|
558
727
|
return scripts;
|
|
559
728
|
}
|
|
560
729
|
function plan(context) {
|
|
730
|
+
const manifest = readProjectPackageJson(context.cwd);
|
|
731
|
+
const unmet = unmetRequirements(context.preset, {
|
|
732
|
+
...manifest.dependencies,
|
|
733
|
+
...manifest.devDependencies
|
|
734
|
+
});
|
|
735
|
+
if (unmet.length > 0) {
|
|
736
|
+
return { operations: [], skipped: describeUnmet(context.preset, unmet) };
|
|
737
|
+
}
|
|
561
738
|
if (declaredBuildPreset(context.cwd) === void 0 && buildConfigFile(context.cwd) !== void 0) {
|
|
562
739
|
return {
|
|
563
740
|
operations: [],
|
|
564
|
-
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
|
|
741
|
+
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.`
|
|
565
742
|
};
|
|
566
743
|
}
|
|
567
744
|
const notes = [];
|
|
568
745
|
const operations = [];
|
|
569
746
|
const configFile = buildConfigFile(context.cwd);
|
|
570
747
|
if (configFile === void 0) {
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
`
|
|
574
|
-
|
|
575
|
-
} else
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
);
|
|
748
|
+
return {
|
|
749
|
+
operations: [],
|
|
750
|
+
skipped: `no Vite config in this module, so there is nothing to point at sentinel. This role moves an existing config's imports; it does not introduce a bundler. If this module builds another way (38 projects here build through \`@nx/webpack\`), the build role does not apply to it.`
|
|
751
|
+
};
|
|
752
|
+
} else {
|
|
753
|
+
const notEsm = esmBlocker(configFile, readProjectPackageJson(context.cwd).type);
|
|
754
|
+
if (notEsm !== void 0) return { operations: [], skipped: notEsm };
|
|
755
|
+
const rewrite = rewriteBuildImports(readConfigSource(context.cwd, configFile), configFile);
|
|
756
|
+
if (rewrite.kind === "blocked") {
|
|
757
|
+
return {
|
|
758
|
+
operations: [],
|
|
759
|
+
skipped: `${configFile} could not be pointed at sentinel, so nothing was written: ${rewrite.why}. Nothing here is broken; this needs a look before the role applies.`
|
|
760
|
+
};
|
|
761
|
+
}
|
|
762
|
+
if (rewrite.kind === "rewritten") {
|
|
763
|
+
operations.push({ kind: "write", path: configFile, contents: rewrite.source });
|
|
764
|
+
}
|
|
765
|
+
for (const name of COMPANION_CONFIG_FILES) {
|
|
766
|
+
const source = readConfigSource(context.cwd, name);
|
|
767
|
+
if (source === "") continue;
|
|
768
|
+
const companion = rewriteBuildImports(source, name);
|
|
769
|
+
if (companion.kind === "rewritten") {
|
|
770
|
+
operations.push({ kind: "write", path: name, contents: companion.source });
|
|
771
|
+
notes.push(`${name} now imports ${companion.moved.join(", ")} from sentinel too.`);
|
|
772
|
+
} else if (companion.kind === "blocked") {
|
|
773
|
+
notes.push(
|
|
774
|
+
`${name} imports the toolchain and could NOT be pointed at sentinel: ${companion.why}. It will stop resolving when the workspace root drops these packages, and it will fail your TESTS rather than your build.`
|
|
775
|
+
);
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
if (rewrite.kind === "rewritten") {
|
|
779
|
+
notes.push(
|
|
780
|
+
`${configFile} now imports ${rewrite.moved.join(", ")} from \`${BUILD_PRESET_SPECIFIER}\`. Only the import lines changed: every attribute, alias and path is untouched, so this build produces what it produced before.`
|
|
781
|
+
);
|
|
782
|
+
}
|
|
579
783
|
}
|
|
580
784
|
operations.push(manifestOperation(context.cwd, buildScripts(context.cwd)));
|
|
581
785
|
operations.push(...nxTargetOperations({ cwd: context.cwd, targets: buildTarget() }));
|
|
@@ -588,134 +792,160 @@ function plan(context) {
|
|
|
588
792
|
}
|
|
589
793
|
return { operations, notes };
|
|
590
794
|
}
|
|
591
|
-
function
|
|
592
|
-
if (!existsSync7(join7(cwd, configFile))) return
|
|
795
|
+
function readConfigSource(cwd, configFile) {
|
|
796
|
+
if (!existsSync7(join7(cwd, configFile))) return "";
|
|
593
797
|
try {
|
|
594
|
-
return readFileSync5(join7(cwd, configFile), "utf8")
|
|
798
|
+
return readFileSync5(join7(cwd, configFile), "utf8");
|
|
595
799
|
} catch {
|
|
596
|
-
return
|
|
800
|
+
return "";
|
|
597
801
|
}
|
|
598
802
|
}
|
|
599
803
|
|
|
600
804
|
// src/roles/build/resolve-vite.ts
|
|
601
|
-
function
|
|
602
|
-
|
|
805
|
+
function resolveViteWith(cwd) {
|
|
806
|
+
const own = binFromOwnInstall("vite", "vite");
|
|
807
|
+
if (own) return { bin: own, origin: "sentinel" };
|
|
808
|
+
const module_ = resolveBin(cwd, "vite");
|
|
809
|
+
return module_ ? { bin: module_, origin: "module" } : { bin: void 0, origin: "none" };
|
|
603
810
|
}
|
|
604
811
|
function viteOrigin(cwd) {
|
|
605
|
-
|
|
606
|
-
return resolveBin(cwd, "vite") ? "module" : "none";
|
|
812
|
+
return resolveViteWith(cwd).origin;
|
|
607
813
|
}
|
|
608
814
|
|
|
609
|
-
// src/roles/build/adapters/vite/vite-
|
|
610
|
-
var
|
|
611
|
-
|
|
815
|
+
// src/roles/build/adapters/vite/vite-role.adapter.ts
|
|
816
|
+
var VITE_VALUE_FLAGS = [
|
|
817
|
+
"--config",
|
|
818
|
+
"-c",
|
|
819
|
+
"--mode",
|
|
820
|
+
"-m",
|
|
821
|
+
"--outDir",
|
|
822
|
+
"--logLevel",
|
|
823
|
+
"--base",
|
|
824
|
+
"--host",
|
|
825
|
+
"--port"
|
|
826
|
+
];
|
|
827
|
+
var ViteRoleAdapter = class extends BaseAdapter {
|
|
612
828
|
runner = "vite";
|
|
829
|
+
/**
|
|
830
|
+
* The presets this role has something to SAY about, which is wider than the ones it serves.
|
|
831
|
+
*
|
|
832
|
+
* React it serves. Svelte it does not, yet, and the difference matters to a developer: the
|
|
833
|
+
* plugin caps at Vite 6 until its major 7, which needs `svelte ^5.46.4`, so the role applies
|
|
834
|
+
* once the module moves. `plan` reports that chain and SKIPS, which it can only do if
|
|
835
|
+
* resolution let it run at all — filtering here would give "no adapter for preset svelte",
|
|
836
|
+
* which tells nobody what to do about it.
|
|
837
|
+
*
|
|
838
|
+
* Nest, node and tools are genuinely declined: there is nothing to bundle, and no version
|
|
839
|
+
* would change that.
|
|
840
|
+
*/
|
|
613
841
|
appliesTo(preset) {
|
|
614
|
-
return preset === "react";
|
|
842
|
+
return preset === "react" || preset === "svelte";
|
|
615
843
|
}
|
|
844
|
+
/**
|
|
845
|
+
* `react`, read from the module's Vite config rather than from its dependencies.
|
|
846
|
+
*
|
|
847
|
+
* Without this the role is unreachable in practice: the toolchain is declared at the ROOT,
|
|
848
|
+
* so every front app detects as `node` and `appliesTo` filters the adapter out — including
|
|
849
|
+
* during `sentinel --inspect` with no target, which never passes `--preset`.
|
|
850
|
+
*/
|
|
616
851
|
declaredPreset(cwd) {
|
|
617
852
|
return declaredBuildPreset(cwd);
|
|
618
853
|
}
|
|
619
854
|
/**
|
|
620
|
-
* The same plan
|
|
855
|
+
* The same plan for both targets, deliberately.
|
|
621
856
|
*
|
|
622
|
-
* `build` and `serve` are one app's two calls into one toolchain, so adopting one without
|
|
623
|
-
*
|
|
624
|
-
* idempotent, so `--init --build` and `--init --dev` are interchangeable rather than
|
|
625
|
-
* additive.
|
|
857
|
+
* `build` and `serve` are one app's two calls into one toolchain, so adopting one without the
|
|
858
|
+
* other leaves the module half-migrated in a way nothing reports. The operations are
|
|
859
|
+
* idempotent, so `--init --build` and `--init --dev` are interchangeable rather than additive.
|
|
626
860
|
*/
|
|
627
861
|
plan(context) {
|
|
628
862
|
return plan(context);
|
|
629
863
|
}
|
|
864
|
+
async status(ctx) {
|
|
865
|
+
const { adopted, preset, conformant, drift, unreadable } = readBuildAdoption(ctx.cwd);
|
|
866
|
+
return { adopted, preset, conformant, drift, unreadable };
|
|
867
|
+
}
|
|
630
868
|
/**
|
|
631
|
-
*
|
|
632
|
-
* beyond the exit code the developer's own Ctrl-C produces.
|
|
869
|
+
* Adoption, the binary, and the warning about which copy of Vite is running.
|
|
633
870
|
*
|
|
634
|
-
*
|
|
635
|
-
*
|
|
636
|
-
*
|
|
871
|
+
* A module that has not adopted is reported and PASSES, the same stance every other role
|
|
872
|
+
* takes: `--run` with no named target sweeps every wired target across the workspace, so
|
|
873
|
+
* failing here would exit non-zero on every module that has not migrated, which is most of
|
|
874
|
+
* them.
|
|
637
875
|
*/
|
|
638
|
-
|
|
876
|
+
prepare(ctx) {
|
|
639
877
|
if (!readBuildAdoption(ctx.cwd).adopted) {
|
|
640
|
-
|
|
641
|
-
`
|
|
642
|
-
`
|
|
878
|
+
this.say(
|
|
879
|
+
`no sentinel preset in this module's Vite config; run \`sentinel --init --build\` to adopt.`
|
|
643
880
|
);
|
|
644
|
-
return { ok: true, code: 0 };
|
|
881
|
+
return { kind: "stop", result: { ok: true, code: 0 } };
|
|
645
882
|
}
|
|
646
|
-
const
|
|
647
|
-
if (!
|
|
648
|
-
|
|
649
|
-
`
|
|
650
|
-
`
|
|
883
|
+
const { bin, origin } = resolveViteWith(ctx.cwd);
|
|
884
|
+
if (!bin) {
|
|
885
|
+
this.say(
|
|
886
|
+
`could not find the vite binary (looked in ${binSearchPath(ctx.cwd)}). Run \`pnpm install\` in the module.`
|
|
651
887
|
);
|
|
652
|
-
return { ok: false, code: 1 };
|
|
888
|
+
return { kind: "stop", result: { ok: false, code: 1 } };
|
|
653
889
|
}
|
|
654
|
-
if (
|
|
655
|
-
|
|
656
|
-
`
|
|
657
|
-
`
|
|
890
|
+
if (origin === "module") {
|
|
891
|
+
this.say(
|
|
892
|
+
`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.`
|
|
658
893
|
);
|
|
659
894
|
}
|
|
660
|
-
|
|
661
|
-
|
|
895
|
+
return { kind: "ready", vite: bin };
|
|
896
|
+
}
|
|
897
|
+
/**
|
|
898
|
+
* Run Vite with the caller's own arguments after `--`, and report what happened.
|
|
899
|
+
*
|
|
900
|
+
* `whenSignalled` is the only difference between the two targets here, and it is a real one:
|
|
901
|
+
* a build killed by a signal did not produce a bundle, while a dev server killed by Ctrl-C
|
|
902
|
+
* did exactly what the developer asked.
|
|
903
|
+
*/
|
|
904
|
+
spawnVite(ctx, vite, argv, whenSignalled) {
|
|
905
|
+
const { options, paths } = splitToolArgs(ctx.cwd, ctx.toolArgs, {
|
|
906
|
+
valueFlags: VITE_VALUE_FLAGS
|
|
907
|
+
});
|
|
908
|
+
const result = spawnSync(vite, [...argv, ...options, ...paths], {
|
|
909
|
+
cwd: ctx.cwd,
|
|
910
|
+
stdio: "inherit"
|
|
911
|
+
});
|
|
662
912
|
if (result.error) {
|
|
663
|
-
|
|
664
|
-
`);
|
|
913
|
+
this.say(`could not run vite (${result.error.message})`);
|
|
665
914
|
return { ok: false, code: 1 };
|
|
666
915
|
}
|
|
667
|
-
const code = result.status ??
|
|
916
|
+
const code = result.status ?? whenSignalled;
|
|
668
917
|
return { ok: code === 0, code };
|
|
669
918
|
}
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
919
|
+
/** One prefix for every message this role emits, so they cannot drift apart. */
|
|
920
|
+
say(message) {
|
|
921
|
+
process.stderr.write(`sentinel ${this.label}(vite): ${message}
|
|
922
|
+
`);
|
|
673
923
|
}
|
|
674
924
|
};
|
|
675
925
|
|
|
676
|
-
// src/roles/build/adapters/vite/vite.adapter.ts
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
"base",
|
|
682
|
-
"root",
|
|
683
|
-
"define",
|
|
684
|
-
"server",
|
|
685
|
-
"build",
|
|
686
|
-
"resolve",
|
|
687
|
-
"plugins"
|
|
688
|
-
];
|
|
689
|
-
function describeDepartures(config, defaults) {
|
|
690
|
-
const departures = [];
|
|
691
|
-
const build = asRecord(config.build);
|
|
692
|
-
const target = build?.target;
|
|
693
|
-
if (typeof target === "string" && target !== defaults.target) {
|
|
694
|
-
departures.push({
|
|
695
|
-
rule: "build.target",
|
|
696
|
-
reason: `${target}, where the convention is ${defaults.target}`
|
|
697
|
-
});
|
|
926
|
+
// src/roles/build/adapters/vite/vite-dev.adapter.ts
|
|
927
|
+
var ViteDevAdapter = class extends ViteRoleAdapter {
|
|
928
|
+
target = "dev";
|
|
929
|
+
get label() {
|
|
930
|
+
return "dev";
|
|
698
931
|
}
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
932
|
+
/**
|
|
933
|
+
* Start the dev server. It does not return until stopped, so there is no verdict to report
|
|
934
|
+
* beyond the exit code the developer's own Ctrl-C produces.
|
|
935
|
+
*
|
|
936
|
+
* No `prebuild` here, unlike `--run --build`: the artefact this repo generates before a build
|
|
937
|
+
* is produced by the watcher that WRAPS this command (`run-with-runtime-artifact-watch`), and
|
|
938
|
+
* running it again would race the watcher that is about to own the file.
|
|
939
|
+
*
|
|
940
|
+
* No subcommand either. `vite`, `vite dev` and `vite serve` all start the server, and the
|
|
941
|
+
* bare form is the one every version accepts.
|
|
942
|
+
*/
|
|
943
|
+
async run(ctx) {
|
|
944
|
+
const ready = this.prepare(ctx);
|
|
945
|
+
if (ready.kind === "stop") return ready.result;
|
|
946
|
+
return this.spawnVite(ctx, ready.vite, [], 0);
|
|
706
947
|
}
|
|
707
|
-
|
|
708
|
-
}
|
|
709
|
-
function appAdditions(config) {
|
|
710
|
-
const owned = new Set(PRESET_OWNED_KEYS);
|
|
711
|
-
return Object.keys(config).filter((key) => !owned.has(key)).sort();
|
|
712
|
-
}
|
|
713
|
-
function asRecord(value) {
|
|
714
|
-
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
715
|
-
}
|
|
716
|
-
function sameStrings(a, b) {
|
|
717
|
-
return a.length === b.length && a.every((value, at) => value === b[at]);
|
|
718
|
-
}
|
|
948
|
+
};
|
|
719
949
|
|
|
720
950
|
// src/roles/build/prerequisite.ts
|
|
721
951
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
@@ -743,122 +973,55 @@ function runPrerequisite(cwd, env = process.env) {
|
|
|
743
973
|
return { kind: "ran" };
|
|
744
974
|
}
|
|
745
975
|
|
|
746
|
-
// src/roles/build/resolve-config.ts
|
|
747
|
-
async function resolveBuildConfig(cwd) {
|
|
748
|
-
try {
|
|
749
|
-
const { loadConfigFromFile } = await import("vite");
|
|
750
|
-
const loaded = await loadConfigFromFile(
|
|
751
|
-
{ command: "build", mode: "production" },
|
|
752
|
-
void 0,
|
|
753
|
-
cwd,
|
|
754
|
-
"silent"
|
|
755
|
-
);
|
|
756
|
-
if (!loaded) return { kind: "failed", reason: "Vite found no config file in this module" };
|
|
757
|
-
return { kind: "loaded", config: loaded.config, from: loaded.path };
|
|
758
|
-
} catch (error) {
|
|
759
|
-
return { kind: "failed", reason: error instanceof Error ? error.message : String(error) };
|
|
760
|
-
}
|
|
761
|
-
}
|
|
762
|
-
|
|
763
976
|
// src/roles/build/adapters/vite/vite.adapter.ts
|
|
764
|
-
var
|
|
765
|
-
var ViteAdapter = class extends BaseAdapter {
|
|
977
|
+
var ViteAdapter = class extends ViteRoleAdapter {
|
|
766
978
|
target = "build";
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
* React only. The two SvelteKit configs in this repo are 34 and 21 lines and share nothing
|
|
770
|
-
* with the React three; claiming them would mean a preset that fits neither.
|
|
771
|
-
*/
|
|
772
|
-
appliesTo(preset) {
|
|
773
|
-
return preset === "react";
|
|
774
|
-
}
|
|
775
|
-
/**
|
|
776
|
-
* `react`, read from the module's Vite config rather than from its dependencies.
|
|
777
|
-
*
|
|
778
|
-
* Without this the role is unreachable in practice: the toolchain is declared at the ROOT,
|
|
779
|
-
* so every front app detects as `node` and `appliesTo` filters the adapter out — including
|
|
780
|
-
* during `sentinel --inspect` with no target, which never passes `--preset`.
|
|
781
|
-
*/
|
|
782
|
-
declaredPreset(cwd) {
|
|
783
|
-
return declaredBuildPreset(cwd);
|
|
784
|
-
}
|
|
785
|
-
plan(context) {
|
|
786
|
-
return plan(context);
|
|
979
|
+
get label() {
|
|
980
|
+
return "build";
|
|
787
981
|
}
|
|
788
982
|
/**
|
|
789
983
|
* Build the module.
|
|
790
984
|
*
|
|
791
|
-
*
|
|
792
|
-
*
|
|
793
|
-
* failing here would exit non-zero on every module that has not migrated, which is most of
|
|
794
|
-
* them.
|
|
985
|
+
* Adoption, the binary and the two-copies warning are `prepare`'s, shared with `--dev`. What
|
|
986
|
+
* is this target's own is the `prebuild` step and the `build` subcommand.
|
|
795
987
|
*/
|
|
796
988
|
async run(ctx) {
|
|
797
|
-
const
|
|
798
|
-
if (
|
|
799
|
-
process.stderr.write(
|
|
800
|
-
`sentinel build(vite): no sentinel preset in this module's Vite config; run \`sentinel --init --build\` to adopt.
|
|
801
|
-
`
|
|
802
|
-
);
|
|
803
|
-
return { ok: true, code: 0 };
|
|
804
|
-
}
|
|
989
|
+
const ready = this.prepare(ctx);
|
|
990
|
+
if (ready.kind === "stop") return ready.result;
|
|
805
991
|
const prerequisite = runPrerequisite(ctx.cwd);
|
|
806
992
|
if (prerequisite.kind === "failed") {
|
|
807
|
-
|
|
808
|
-
`
|
|
809
|
-
`
|
|
993
|
+
this.say(
|
|
994
|
+
`the ${PREBUILD_SCRIPT_NAME} step failed, so the build was not started: ${prerequisite.reason}`
|
|
810
995
|
);
|
|
811
996
|
return { ok: false, code: prerequisite.code };
|
|
812
997
|
}
|
|
813
|
-
|
|
814
|
-
if (!vite) {
|
|
815
|
-
process.stderr.write(
|
|
816
|
-
`sentinel build(vite): could not find the vite binary (looked in ${binSearchPath(ctx.cwd)}). Run \`pnpm install\` in the module.
|
|
817
|
-
`
|
|
818
|
-
);
|
|
819
|
-
return { ok: false, code: 1 };
|
|
820
|
-
}
|
|
821
|
-
if (viteOrigin(ctx.cwd) === "module") {
|
|
822
|
-
process.stderr.write(
|
|
823
|
-
`sentinel build(vite): building with the MODULE's vite, not sentinel's. An adopted config gets its plugins from sentinel, and two copies of Vite in one build fail in ways that never mention a version. Remove vite from this module's package.json.
|
|
824
|
-
`
|
|
825
|
-
);
|
|
826
|
-
}
|
|
827
|
-
const { options, paths } = splitToolArgs(ctx.cwd, ctx.toolArgs, {
|
|
828
|
-
valueFlags: VITE_VALUE_FLAGS
|
|
829
|
-
});
|
|
830
|
-
const result = spawnSync3(vite, ["build", ...options, ...paths], {
|
|
831
|
-
cwd: ctx.cwd,
|
|
832
|
-
stdio: "inherit"
|
|
833
|
-
});
|
|
834
|
-
if (result.error) {
|
|
835
|
-
process.stderr.write(`sentinel build(vite): could not run vite (${result.error.message})
|
|
836
|
-
`);
|
|
837
|
-
return { ok: false, code: 1 };
|
|
838
|
-
}
|
|
839
|
-
const code = result.status ?? 1;
|
|
840
|
-
return { ok: code === 0, code };
|
|
998
|
+
return this.spawnVite(ctx, ready.vite, ["build"], 1);
|
|
841
999
|
}
|
|
842
1000
|
/**
|
|
843
|
-
* What this module builds with,
|
|
1001
|
+
* What this module builds with, WITHOUT loading its config.
|
|
844
1002
|
*
|
|
845
|
-
*
|
|
846
|
-
*
|
|
847
|
-
*
|
|
1003
|
+
* Everything reported here is read from the manifest and from the config's text: whether the
|
|
1004
|
+
* module imports its toolchain from sentinel, which Vite install would answer, what it still
|
|
1005
|
+
* declares of its own, and whether it has a `prebuild` step.
|
|
848
1006
|
*
|
|
849
|
-
*
|
|
850
|
-
*
|
|
851
|
-
*
|
|
1007
|
+
* It used to also load the config through Vite and report where it departed from a preset
|
|
1008
|
+
* and what it added on top. Both were dropped with the preset itself. Adoption changes a
|
|
1009
|
+
* config's IMPORTS and nothing else, so there is no preset the app signed up to and no
|
|
1010
|
+
* distance from it to measure; and computing it meant EXECUTING an app's config, which is
|
|
1011
|
+
* expensive and can fail on its own terms (career throws without `VITE_BRAND_ID`). Reading
|
|
1012
|
+
* a file to report on it must not be able to break.
|
|
852
1013
|
*
|
|
853
|
-
*
|
|
854
|
-
*
|
|
1014
|
+
* They come back with the shared base, when there is again something an app has chosen to
|
|
1015
|
+
* adopt and can therefore depart from.
|
|
855
1016
|
*/
|
|
856
1017
|
async inspect(ctx) {
|
|
857
1018
|
const adoption = readBuildAdoption(ctx.cwd);
|
|
858
|
-
|
|
1019
|
+
return {
|
|
859
1020
|
runner: "vite",
|
|
860
1021
|
configFile: adoption.configFile,
|
|
861
1022
|
...adoption.unreadable ? { unreadable: adoption.unreadable } : {},
|
|
1023
|
+
// The field nothing else reports: an adopted config takes its plugins from sentinel, so
|
|
1024
|
+
// a module answering `module` here is one build away from the two-copies failure.
|
|
862
1025
|
vite: viteOrigin(ctx.cwd),
|
|
863
1026
|
// Rendered as a labelled block by the shared renderer, the same shape the lint role's
|
|
864
1027
|
// parked rules and the format role's overrides already use.
|
|
@@ -868,34 +1031,6 @@ var ViteAdapter = class extends BaseAdapter {
|
|
|
868
1031
|
})),
|
|
869
1032
|
prerequisite: moduleScripts(ctx.cwd)[PREBUILD_SCRIPT_NAME] ?? null
|
|
870
1033
|
};
|
|
871
|
-
if (adoption.configFile === null) return base;
|
|
872
|
-
const resolved = await resolveBuildConfig(ctx.cwd);
|
|
873
|
-
if (resolved.kind === "failed") {
|
|
874
|
-
return { ...base, configError: resolved.reason };
|
|
875
|
-
}
|
|
876
|
-
const config = resolved.config;
|
|
877
|
-
return {
|
|
878
|
-
...base,
|
|
879
|
-
// What the app departs from, and what it adds, are different facts. A DEPARTURE is a
|
|
880
|
-
// disagreement with an opinion the preset holds; an ADDITION is the app needing
|
|
881
|
-
// something the preset never claimed, which is the preset working rather than being
|
|
882
|
-
// worked around.
|
|
883
|
-
departures: describeDepartures(config, REACT_APP_DEFAULTS),
|
|
884
|
-
additions: appAdditions(config),
|
|
885
|
-
resolved: {
|
|
886
|
-
base: config.base ?? null,
|
|
887
|
-
target: config.build?.target ?? null,
|
|
888
|
-
sourcemap: config.build?.sourcemap ?? null,
|
|
889
|
-
port: config.server?.port ?? null,
|
|
890
|
-
// The one number nothing else reports and that no formula predicts: career goes DOWN
|
|
891
|
-
// to 9998 where the others go up, which is why it is data rather than `port + 1`.
|
|
892
|
-
hmrPort: config.server?.hmr?.port ?? null,
|
|
893
|
-
// Counted, not listed. It is 56-67% of every config today, and printing 296 entries
|
|
894
|
-
// would bury everything above it. The count is what tells you whether it moved.
|
|
895
|
-
aliases: Array.isArray(config.resolve?.alias) ? config.resolve.alias.length : 0,
|
|
896
|
-
plugins: Array.isArray(config.plugins) ? config.plugins.flat(9).length : 0
|
|
897
|
-
}
|
|
898
|
-
};
|
|
899
1034
|
}
|
|
900
1035
|
/**
|
|
901
1036
|
* Adoption as data, WITHOUT building.
|
|
@@ -920,10 +1055,6 @@ var ViteAdapter = class extends BaseAdapter {
|
|
|
920
1055
|
}
|
|
921
1056
|
};
|
|
922
1057
|
}
|
|
923
|
-
async status(ctx) {
|
|
924
|
-
const { adopted, preset, conformant, drift, unreadable } = readBuildAdoption(ctx.cwd);
|
|
925
|
-
return { adopted, preset, conformant, drift, unreadable };
|
|
926
|
-
}
|
|
927
1058
|
};
|
|
928
1059
|
|
|
929
1060
|
// src/roles/build/register.ts
|
|
@@ -935,13 +1066,13 @@ function registerBuild() {
|
|
|
935
1066
|
}
|
|
936
1067
|
|
|
937
1068
|
// src/roles/format/adapters/oxfmt/oxfmt.adapter.ts
|
|
938
|
-
import { spawnSync as
|
|
1069
|
+
import { spawnSync as spawnSync3 } from "child_process";
|
|
939
1070
|
import { existsSync as existsSync12, readFileSync as readFileSync11 } from "fs";
|
|
940
1071
|
import { join as join14 } from "path";
|
|
941
1072
|
|
|
942
1073
|
// src/core/config/has-source.ts
|
|
943
1074
|
import { readdirSync } from "fs";
|
|
944
|
-
import { extname, join as join8 } from "path";
|
|
1075
|
+
import { extname, join as join8, relative } from "path";
|
|
945
1076
|
var SKIP_DIRECTORIES = /* @__PURE__ */ new Set([
|
|
946
1077
|
"node_modules",
|
|
947
1078
|
"dist",
|
|
@@ -1000,6 +1131,35 @@ function hasSourceFiles(cwd, extensions) {
|
|
|
1000
1131
|
}
|
|
1001
1132
|
return false;
|
|
1002
1133
|
}
|
|
1134
|
+
function listSourceFiles(cwd, extensions) {
|
|
1135
|
+
const wanted = new Set(extensions);
|
|
1136
|
+
const queue = [cwd];
|
|
1137
|
+
const found = [];
|
|
1138
|
+
while (queue.length > 0) {
|
|
1139
|
+
const dir = queue.pop();
|
|
1140
|
+
let entries;
|
|
1141
|
+
try {
|
|
1142
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
1143
|
+
} catch {
|
|
1144
|
+
continue;
|
|
1145
|
+
}
|
|
1146
|
+
for (const entry of entries) {
|
|
1147
|
+
const full = join8(dir, entry.name);
|
|
1148
|
+
if (entry.isDirectory()) {
|
|
1149
|
+
if (!SKIP_DIRECTORIES.has(entry.name)) queue.push(full);
|
|
1150
|
+
continue;
|
|
1151
|
+
}
|
|
1152
|
+
if (wanted.has(extname(entry.name))) found.push(relative(cwd, full));
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
return found;
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
// src/core/named-scope.ts
|
|
1159
|
+
var TOLERATE_EMPTY_FLAG = "--no-error-on-unmatched-pattern";
|
|
1160
|
+
function scopeFlags(paths) {
|
|
1161
|
+
return paths.length > 0 ? [TOLERATE_EMPTY_FLAG] : [];
|
|
1162
|
+
}
|
|
1003
1163
|
|
|
1004
1164
|
// src/core/settings.ts
|
|
1005
1165
|
var WORKSPACE_ROOT_MARKER = "nx.json";
|
|
@@ -1007,7 +1167,7 @@ var DEFAULT_MAX_DIAGNOSTICS = 100;
|
|
|
1007
1167
|
|
|
1008
1168
|
// src/core/workspace-prep.ts
|
|
1009
1169
|
import { existsSync as existsSync8, readFileSync as readFileSync6, writeFileSync } from "fs";
|
|
1010
|
-
import { dirname as dirname3, join as join9, relative } from "path";
|
|
1170
|
+
import { dirname as dirname3, join as join9, relative as relative2 } from "path";
|
|
1011
1171
|
var OVERRIDE_KEY = "i18next>typescript";
|
|
1012
1172
|
var NATIVE_TS_ALIAS = "@typescript/native";
|
|
1013
1173
|
var WORKSPACE_YAML = "pnpm-workspace.yaml";
|
|
@@ -1080,7 +1240,7 @@ var ROOT_PRETTIER_CONFIGS = [
|
|
|
1080
1240
|
"prettier.config.mjs"
|
|
1081
1241
|
];
|
|
1082
1242
|
function ensureFormatterExclusion(root, moduleDir, dryRun) {
|
|
1083
|
-
const rel =
|
|
1243
|
+
const rel = relative2(root, moduleDir).replaceAll("\\", "/");
|
|
1084
1244
|
if (rel === "" || rel.startsWith("..")) return void 0;
|
|
1085
1245
|
const ignorePath = join9(root, PRETTIER_IGNORE);
|
|
1086
1246
|
const hasPrettier = existsSync8(ignorePath) || ROOT_PRETTIER_CONFIGS.some((name) => existsSync8(join9(root, name)));
|
|
@@ -1527,6 +1687,22 @@ var OxfmtAdapter = class extends BaseAdapter {
|
|
|
1527
1687
|
appliesTo(_preset) {
|
|
1528
1688
|
return true;
|
|
1529
1689
|
}
|
|
1690
|
+
/**
|
|
1691
|
+
* The preset this module COMMITTED to, read from the `$sentinel` block it writes.
|
|
1692
|
+
*
|
|
1693
|
+
* Every other role implements this and format did not, so it was the only one answering from
|
|
1694
|
+
* dependency detection alone. On an adopted Nest module whose own `.oxfmtrc.json` records
|
|
1695
|
+
* `"preset": "nest"`, `--inspect` reported `flavour=node` — the module knew, and the role did
|
|
1696
|
+
* not pass it on. Same command, four rows, two different answers about what the module is.
|
|
1697
|
+
*
|
|
1698
|
+
* It reads the RECORD rather than re-deriving: the block is what `--init` wrote, so it is the
|
|
1699
|
+
* module's declaration, and a re-derivation from hoisted dependencies is exactly the wrong
|
|
1700
|
+
* answer this exists to avoid.
|
|
1701
|
+
*/
|
|
1702
|
+
declaredPreset(cwd) {
|
|
1703
|
+
const declared = readFormatAdoption(cwd).preset;
|
|
1704
|
+
return declared !== null && PRESET_NAMES.includes(declared) ? declared : void 0;
|
|
1705
|
+
}
|
|
1530
1706
|
plan(context) {
|
|
1531
1707
|
if (!hasSourceFiles(context.cwd, FORMATTABLE_EXTENSIONS)) {
|
|
1532
1708
|
return {
|
|
@@ -1628,7 +1804,7 @@ var OxfmtAdapter = class extends BaseAdapter {
|
|
|
1628
1804
|
async afterInit(ctx) {
|
|
1629
1805
|
const oxfmt = resolveOxfmt(ctx.cwd);
|
|
1630
1806
|
if (!oxfmt) return;
|
|
1631
|
-
const pass =
|
|
1807
|
+
const pass = spawnSync3(oxfmt, ["--write", "."], { cwd: ctx.cwd, encoding: "utf8" });
|
|
1632
1808
|
process.stderr.write(` ${palette(process.stderr).dim(FORMATTER_DIFFERENCES)}
|
|
1633
1809
|
`);
|
|
1634
1810
|
if (pass.status === 0) return;
|
|
@@ -1691,10 +1867,14 @@ var OxfmtAdapter = class extends BaseAdapter {
|
|
|
1691
1867
|
const { options, paths } = splitToolArgs(ctx.cwd, ctx.toolArgs, {
|
|
1692
1868
|
valueFlags: OXFMT_VALUE_FLAGS
|
|
1693
1869
|
});
|
|
1694
|
-
const result =
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1870
|
+
const result = spawnSync3(
|
|
1871
|
+
oxfmt,
|
|
1872
|
+
[mode, ...scopeFlags(paths), ...options, ...paths.length > 0 ? paths : ["."]],
|
|
1873
|
+
{
|
|
1874
|
+
cwd: ctx.cwd,
|
|
1875
|
+
stdio: "inherit"
|
|
1876
|
+
}
|
|
1877
|
+
);
|
|
1698
1878
|
if (result.error) {
|
|
1699
1879
|
process.stderr.write(
|
|
1700
1880
|
`sentinel format(oxfmt): could not run oxfmt (${result.error.message})
|
|
@@ -1769,7 +1949,7 @@ var OxfmtAdapter = class extends BaseAdapter {
|
|
|
1769
1949
|
);
|
|
1770
1950
|
return { ok: false, code: 1, metrics: { ...base, unformatted: null } };
|
|
1771
1951
|
}
|
|
1772
|
-
const result =
|
|
1952
|
+
const result = spawnSync3(oxfmt, ["--list-different", "."], { cwd: ctx.cwd, encoding: "utf8" });
|
|
1773
1953
|
if (result.error) {
|
|
1774
1954
|
process.stderr.write(
|
|
1775
1955
|
`sentinel format(oxfmt): could not run oxfmt (${result.error.message})
|
|
@@ -1884,15 +2064,122 @@ function registerFormat() {
|
|
|
1884
2064
|
register(new OxfmtAdapter());
|
|
1885
2065
|
setDefaultRunner("format", "oxfmt");
|
|
1886
2066
|
}
|
|
1887
|
-
|
|
1888
|
-
// src/roles/lint/adapters/oxlint/oxlint.adapter.ts
|
|
1889
|
-
import { spawnSync as
|
|
1890
|
-
import { existsSync as
|
|
1891
|
-
import { join as
|
|
1892
|
-
|
|
1893
|
-
// src/core/config/deferred-rules.ts
|
|
1894
|
-
function deferredRuleNames(rules) {
|
|
1895
|
-
return rules.map((entry) => entry.rule);
|
|
2067
|
+
|
|
2068
|
+
// src/roles/lint/adapters/oxlint/oxlint.adapter.ts
|
|
2069
|
+
import { spawnSync as spawnSync4 } from "child_process";
|
|
2070
|
+
import { existsSync as existsSync20, readFileSync as readFileSync19, rmSync, writeFileSync as writeFileSync2 } from "fs";
|
|
2071
|
+
import { join as join24 } from "path";
|
|
2072
|
+
|
|
2073
|
+
// src/core/config/deferred-rules.ts
|
|
2074
|
+
function deferredRuleNames(rules) {
|
|
2075
|
+
return rules.map((entry) => entry.rule);
|
|
2076
|
+
}
|
|
2077
|
+
|
|
2078
|
+
// src/core/fix-report.ts
|
|
2079
|
+
import { statSync } from "fs";
|
|
2080
|
+
import { join as join15 } from "path";
|
|
2081
|
+
var CONFIG_REFUSED = /failed to parse .*config|invalid config file/i;
|
|
2082
|
+
function readFailure(output) {
|
|
2083
|
+
const lines = output.split("\n").map((line2) => line2.trim());
|
|
2084
|
+
if (!lines.some((line2) => CONFIG_REFUSED.test(line2))) return void 0;
|
|
2085
|
+
const detail = lines.find((line2) => /invalid config file/i.test(line2));
|
|
2086
|
+
const line = (detail ?? lines.find((entry) => entry !== "") ?? "").replace(/^x\s*/, "");
|
|
2087
|
+
return line.split(/:\s*Failed to parse config/i)[0] ?? line;
|
|
2088
|
+
}
|
|
2089
|
+
function fingerprint(path) {
|
|
2090
|
+
try {
|
|
2091
|
+
const stat = statSync(path);
|
|
2092
|
+
return `${stat.size}:${stat.mtimeMs}`;
|
|
2093
|
+
} catch {
|
|
2094
|
+
return void 0;
|
|
2095
|
+
}
|
|
2096
|
+
}
|
|
2097
|
+
function snapshotFiles(cwd, extensions) {
|
|
2098
|
+
const snapshot = /* @__PURE__ */ new Map();
|
|
2099
|
+
for (const file of listSourceFiles(cwd, extensions)) {
|
|
2100
|
+
const mark = fingerprint(join15(cwd, file));
|
|
2101
|
+
if (mark !== void 0) snapshot.set(file, mark);
|
|
2102
|
+
}
|
|
2103
|
+
return snapshot;
|
|
2104
|
+
}
|
|
2105
|
+
function changedSince(cwd, before, extensions) {
|
|
2106
|
+
const changed = [];
|
|
2107
|
+
for (const file of listSourceFiles(cwd, extensions)) {
|
|
2108
|
+
const now = fingerprint(join15(cwd, file));
|
|
2109
|
+
if (now === void 0) continue;
|
|
2110
|
+
if (before.get(file) !== now) changed.push(file);
|
|
2111
|
+
}
|
|
2112
|
+
return changed.sort();
|
|
2113
|
+
}
|
|
2114
|
+
var NAMED_IN_REPORT = 5;
|
|
2115
|
+
function describeFixOutcome(outcome, toolLabel) {
|
|
2116
|
+
if (outcome.failure !== void 0) {
|
|
2117
|
+
return `the ${toolLabel} autofix could not run, so nothing was fixed and this module has not been checked: ${outcome.failure}`;
|
|
2118
|
+
}
|
|
2119
|
+
if (outcome.changed.length === 0) return `the ${toolLabel} autofix changed nothing`;
|
|
2120
|
+
const named = outcome.changed.slice(0, NAMED_IN_REPORT).join(", ");
|
|
2121
|
+
const rest = outcome.changed.length - NAMED_IN_REPORT;
|
|
2122
|
+
const listed = rest > 0 ? `${named} and ${rest} more` : named;
|
|
2123
|
+
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.`;
|
|
2124
|
+
}
|
|
2125
|
+
|
|
2126
|
+
// src/roles/lint/module-baseline.ts
|
|
2127
|
+
import { existsSync as existsSync13, readFileSync as readFileSync12 } from "fs";
|
|
2128
|
+
import { join as join16 } from "path";
|
|
2129
|
+
var LINT_BASELINE_FILE = ".oxlintrc.baseline.json";
|
|
2130
|
+
var LINT_MEASURE_FILE = ".oxlintrc.measure.json";
|
|
2131
|
+
var LINT_BASELINE_SPECIFIER = `./${LINT_BASELINE_FILE}`;
|
|
2132
|
+
function holdsFrom(errorCounts) {
|
|
2133
|
+
return [...errorCounts].map(([rule, count]) => ({ rule, count })).sort((left, right) => right.count - left.count || left.rule.localeCompare(right.rule));
|
|
2134
|
+
}
|
|
2135
|
+
function renderBaseline(holds, version) {
|
|
2136
|
+
const entries = holds.map(({ rule }) => ` ${JSON.stringify(rule)}: "warn"`).join(",\n");
|
|
2137
|
+
return [
|
|
2138
|
+
"{",
|
|
2139
|
+
` // Written by @hublo/sentinel@${version}. Do not edit by hand: \`sentinel --init --lint\``,
|
|
2140
|
+
" // regenerates it from a fresh measurement, and a rule you have since fixed drops out",
|
|
2141
|
+
" // and returns to `error` on its own.",
|
|
2142
|
+
" //",
|
|
2143
|
+
" // These rules are held at `warn` because this module ALREADY violated them when it",
|
|
2144
|
+
" // adopted. They still run and still report, so the build cannot fail on them.",
|
|
2145
|
+
" //",
|
|
2146
|
+
" // The hold is per RULE, not per occurrence: oxlint cannot freeze a specific list of",
|
|
2147
|
+
" // violations, so a NEW violation of one of these rules is covered too, and will only",
|
|
2148
|
+
" // warn. That is the cost of adopting without a red build, and the reason to clear this",
|
|
2149
|
+
" // file rather than live with it.",
|
|
2150
|
+
" //",
|
|
2151
|
+
" // `sentinel --inspect --lint` lists what is held; `sentinel --run --lint --json` counts",
|
|
2152
|
+
" // what is left, per rule. Fix them and re-run `--init --lint` to get the severity back.",
|
|
2153
|
+
' "rules": {',
|
|
2154
|
+
entries,
|
|
2155
|
+
" }",
|
|
2156
|
+
"}",
|
|
2157
|
+
""
|
|
2158
|
+
].join("\n");
|
|
2159
|
+
}
|
|
2160
|
+
function extendsWithBaseline(current, hasHolds) {
|
|
2161
|
+
const withoutBaseline = current.filter((entry) => entry !== LINT_BASELINE_SPECIFIER);
|
|
2162
|
+
return hasHolds ? [...withoutBaseline, LINT_BASELINE_SPECIFIER] : withoutBaseline;
|
|
2163
|
+
}
|
|
2164
|
+
function describeHolds(holds) {
|
|
2165
|
+
if (holds.length === 0) return "";
|
|
2166
|
+
const total = holds.reduce((sum, hold) => sum + hold.count, 0);
|
|
2167
|
+
const listed = holds.slice(0, 5).map(({ rule, count }) => `${rule} (${count})`).join(", ");
|
|
2168
|
+
const rest = holds.length > 5 ? `, and ${holds.length - 5} more` : "";
|
|
2169
|
+
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`;
|
|
2170
|
+
}
|
|
2171
|
+
function heldRules(cwd) {
|
|
2172
|
+
const path = join16(cwd, LINT_BASELINE_FILE);
|
|
2173
|
+
if (!existsSync13(path)) return [];
|
|
2174
|
+
try {
|
|
2175
|
+
const parsed = parseJsonc(
|
|
2176
|
+
readFileSync12(path, "utf8"),
|
|
2177
|
+
LINT_BASELINE_FILE
|
|
2178
|
+
);
|
|
2179
|
+
return Object.keys(parsed.rules ?? {});
|
|
2180
|
+
} catch {
|
|
2181
|
+
return [];
|
|
2182
|
+
}
|
|
1896
2183
|
}
|
|
1897
2184
|
|
|
1898
2185
|
// src/roles/lint/config-policy.ts
|
|
@@ -1907,8 +2194,10 @@ function isSentinelPreset(entry) {
|
|
|
1907
2194
|
}
|
|
1908
2195
|
function extendsWithPreset(current, variant) {
|
|
1909
2196
|
const own = presetPath(variant);
|
|
1910
|
-
const others = current.filter((entry) => entry !== own && !isSentinelPreset(entry));
|
|
1911
|
-
|
|
2197
|
+
const others = [...new Set(current.filter((entry) => entry !== own && !isSentinelPreset(entry)))];
|
|
2198
|
+
const baseline = others.filter((entry) => entry === LINT_BASELINE_SPECIFIER);
|
|
2199
|
+
const rest = others.filter((entry) => entry !== LINT_BASELINE_SPECIFIER);
|
|
2200
|
+
return [own, ...rest, ...baseline];
|
|
1912
2201
|
}
|
|
1913
2202
|
function presetVariant(preset, cwd) {
|
|
1914
2203
|
const normalised = cwd.replaceAll("\\", "/");
|
|
@@ -1932,46 +2221,6 @@ function lintTarget() {
|
|
|
1932
2221
|
return { cache: true, inputs: ["default", "^default", `{projectRoot}/${LINT_CONFIG_FILE}`] };
|
|
1933
2222
|
}
|
|
1934
2223
|
|
|
1935
|
-
// src/roles/lint/presets/base.json
|
|
1936
|
-
var base_default2 = {
|
|
1937
|
-
rules: {
|
|
1938
|
-
"no-var": "error",
|
|
1939
|
-
"prefer-const": [
|
|
1940
|
-
"error",
|
|
1941
|
-
{
|
|
1942
|
-
destructuring: "any",
|
|
1943
|
-
ignoreReadBeforeAssign: false
|
|
1944
|
-
}
|
|
1945
|
-
],
|
|
1946
|
-
"prefer-rest-params": "error",
|
|
1947
|
-
"prefer-spread": "error",
|
|
1948
|
-
"typescript/ban-ts-comment": "error",
|
|
1949
|
-
"typescript/no-array-constructor": "error",
|
|
1950
|
-
"typescript/no-duplicate-enum-values": "error",
|
|
1951
|
-
"typescript/no-empty-object-type": "error",
|
|
1952
|
-
"typescript/no-extra-non-null-assertion": "error",
|
|
1953
|
-
"typescript/no-misused-new": "error",
|
|
1954
|
-
"typescript/no-namespace": "error",
|
|
1955
|
-
"typescript/no-non-null-asserted-optional-chain": "error",
|
|
1956
|
-
"typescript/no-this-alias": "error",
|
|
1957
|
-
"typescript/no-unnecessary-type-constraint": "error",
|
|
1958
|
-
"typescript/no-unsafe-declaration-merging": "error",
|
|
1959
|
-
"typescript/no-unsafe-function-type": "error",
|
|
1960
|
-
"typescript/no-unused-expressions": [
|
|
1961
|
-
"error",
|
|
1962
|
-
{
|
|
1963
|
-
allowShortCircuit: false,
|
|
1964
|
-
allowTaggedTemplates: false,
|
|
1965
|
-
allowTernary: false
|
|
1966
|
-
}
|
|
1967
|
-
],
|
|
1968
|
-
"typescript/no-wrapper-object-types": "error",
|
|
1969
|
-
"typescript/prefer-as-const": "error",
|
|
1970
|
-
"typescript/prefer-namespace-keyword": "error",
|
|
1971
|
-
"typescript/triple-slash-reference": "error"
|
|
1972
|
-
}
|
|
1973
|
-
};
|
|
1974
|
-
|
|
1975
2224
|
// src/roles/lint/presets/nest.json
|
|
1976
2225
|
var nest_default = {
|
|
1977
2226
|
plugins: ["typescript", "jest", "import", "unicorn", "oxc", "node"],
|
|
@@ -2311,6 +2560,7 @@ var nest_default = {
|
|
|
2311
2560
|
"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.",
|
|
2312
2561
|
"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.",
|
|
2313
2562
|
"no-octal": "no oxlint equivalent",
|
|
2563
|
+
"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.",
|
|
2314
2564
|
"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.",
|
|
2315
2565
|
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.",
|
|
2316
2566
|
"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.",
|
|
@@ -3264,10 +3514,51 @@ var react_default = {
|
|
|
3264
3514
|
uncovered: {
|
|
3265
3515
|
"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.",
|
|
3266
3516
|
"no-octal": "no oxlint equivalent",
|
|
3517
|
+
"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.",
|
|
3267
3518
|
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."
|
|
3268
3519
|
}
|
|
3269
3520
|
};
|
|
3270
3521
|
|
|
3522
|
+
// src/roles/lint/presets/shared.json
|
|
3523
|
+
var shared_default = {
|
|
3524
|
+
rules: {
|
|
3525
|
+
"no-var": "error",
|
|
3526
|
+
"prefer-const": [
|
|
3527
|
+
"error",
|
|
3528
|
+
{
|
|
3529
|
+
destructuring: "any",
|
|
3530
|
+
ignoreReadBeforeAssign: false
|
|
3531
|
+
}
|
|
3532
|
+
],
|
|
3533
|
+
"prefer-rest-params": "error",
|
|
3534
|
+
"prefer-spread": "error",
|
|
3535
|
+
"typescript/ban-ts-comment": "error",
|
|
3536
|
+
"typescript/no-array-constructor": "error",
|
|
3537
|
+
"typescript/no-duplicate-enum-values": "error",
|
|
3538
|
+
"typescript/no-empty-object-type": "error",
|
|
3539
|
+
"typescript/no-extra-non-null-assertion": "error",
|
|
3540
|
+
"typescript/no-misused-new": "error",
|
|
3541
|
+
"typescript/no-namespace": "error",
|
|
3542
|
+
"typescript/no-non-null-asserted-optional-chain": "error",
|
|
3543
|
+
"typescript/no-this-alias": "error",
|
|
3544
|
+
"typescript/no-unnecessary-type-constraint": "error",
|
|
3545
|
+
"typescript/no-unsafe-declaration-merging": "error",
|
|
3546
|
+
"typescript/no-unsafe-function-type": "error",
|
|
3547
|
+
"typescript/no-unused-expressions": [
|
|
3548
|
+
"error",
|
|
3549
|
+
{
|
|
3550
|
+
allowShortCircuit: false,
|
|
3551
|
+
allowTaggedTemplates: false,
|
|
3552
|
+
allowTernary: false
|
|
3553
|
+
}
|
|
3554
|
+
],
|
|
3555
|
+
"typescript/no-wrapper-object-types": "error",
|
|
3556
|
+
"typescript/prefer-as-const": "error",
|
|
3557
|
+
"typescript/prefer-namespace-keyword": "error",
|
|
3558
|
+
"typescript/triple-slash-reference": "error"
|
|
3559
|
+
}
|
|
3560
|
+
};
|
|
3561
|
+
|
|
3271
3562
|
// src/roles/lint/presets/svelte.json
|
|
3272
3563
|
var svelte_default = {
|
|
3273
3564
|
plugins: ["typescript", "oxc"],
|
|
@@ -3606,7 +3897,7 @@ var tools_default = {
|
|
|
3606
3897
|
};
|
|
3607
3898
|
|
|
3608
3899
|
// src/roles/lint/preset-data.ts
|
|
3609
|
-
var
|
|
3900
|
+
var SHARED_RULES = shared_default.rules;
|
|
3610
3901
|
var PRESET_FILES = {
|
|
3611
3902
|
nest: nest_default,
|
|
3612
3903
|
node: node_default,
|
|
@@ -3628,15 +3919,18 @@ function hasLintPreset(preset) {
|
|
|
3628
3919
|
function presetFile(preset) {
|
|
3629
3920
|
return VARIANT_FILES[preset] ?? PRESET_FILES[preset] ?? node_default;
|
|
3630
3921
|
}
|
|
3631
|
-
function
|
|
3632
|
-
return
|
|
3922
|
+
function sharedRules() {
|
|
3923
|
+
return SHARED_RULES;
|
|
3633
3924
|
}
|
|
3634
3925
|
function presetRules(preset) {
|
|
3635
3926
|
return presetFile(preset).rules;
|
|
3636
3927
|
}
|
|
3928
|
+
function uncoveredFor(preset) {
|
|
3929
|
+
return Object.entries(presetFile(preset).uncovered ?? {}).map(([rule, reason]) => ({ rule, reason })).sort((left, right) => left.rule.localeCompare(right.rule));
|
|
3930
|
+
}
|
|
3637
3931
|
|
|
3638
3932
|
// src/roles/lint/rule-data.ts
|
|
3639
|
-
var
|
|
3933
|
+
var sharedRules2 = sharedRules();
|
|
3640
3934
|
function isDecision(value) {
|
|
3641
3935
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3642
3936
|
}
|
|
@@ -3663,7 +3957,7 @@ function validate(preset, rules) {
|
|
|
3663
3957
|
}
|
|
3664
3958
|
}
|
|
3665
3959
|
function layered(preset) {
|
|
3666
|
-
const base =
|
|
3960
|
+
const base = sharedRules();
|
|
3667
3961
|
const layer = presetRules(preset);
|
|
3668
3962
|
validate("base", base);
|
|
3669
3963
|
validate(preset, layer);
|
|
@@ -3702,60 +3996,14 @@ function downgradedRulesFor(preset) {
|
|
|
3702
3996
|
}
|
|
3703
3997
|
|
|
3704
3998
|
// src/roles/lint/extra-layers.ts
|
|
3705
|
-
import { existsSync as
|
|
3706
|
-
import { isAbsolute as isAbsolute2, join as
|
|
3707
|
-
|
|
3708
|
-
// src/roles/lint/module-baseline.ts
|
|
3709
|
-
var LINT_BASELINE_FILE = ".oxlintrc.baseline.json";
|
|
3710
|
-
var LINT_MEASURE_FILE = ".oxlintrc.measure.json";
|
|
3711
|
-
var LINT_BASELINE_SPECIFIER = `./${LINT_BASELINE_FILE}`;
|
|
3712
|
-
function holdsFrom(errorCounts) {
|
|
3713
|
-
return [...errorCounts].map(([rule, count]) => ({ rule, count })).sort((left, right) => right.count - left.count || left.rule.localeCompare(right.rule));
|
|
3714
|
-
}
|
|
3715
|
-
function renderBaseline(holds, version) {
|
|
3716
|
-
const entries = holds.map(({ rule }) => ` ${JSON.stringify(rule)}: "warn"`).join(",\n");
|
|
3717
|
-
return [
|
|
3718
|
-
"{",
|
|
3719
|
-
` // Written by @hublo/sentinel@${version}. Do not edit by hand: \`sentinel --init --lint\``,
|
|
3720
|
-
" // regenerates it from a fresh measurement, and a rule you have since fixed drops out",
|
|
3721
|
-
" // and returns to `error` on its own.",
|
|
3722
|
-
" //",
|
|
3723
|
-
" // These rules are held at `warn` because this module ALREADY violated them when it",
|
|
3724
|
-
" // adopted. They still run and still report, so the build cannot fail on them.",
|
|
3725
|
-
" //",
|
|
3726
|
-
" // The hold is per RULE, not per occurrence: oxlint cannot freeze a specific list of",
|
|
3727
|
-
" // violations, so a NEW violation of one of these rules is covered too, and will only",
|
|
3728
|
-
" // warn. That is the cost of adopting without a red build, and the reason to clear this",
|
|
3729
|
-
" // file rather than live with it.",
|
|
3730
|
-
" //",
|
|
3731
|
-
" // `sentinel --inspect --lint` counts what is left. Fix them and re-run `--init --lint`",
|
|
3732
|
-
" // to get the preset severity back.",
|
|
3733
|
-
' "rules": {',
|
|
3734
|
-
entries,
|
|
3735
|
-
" }",
|
|
3736
|
-
"}",
|
|
3737
|
-
""
|
|
3738
|
-
].join("\n");
|
|
3739
|
-
}
|
|
3740
|
-
function extendsWithBaseline(current, hasHolds) {
|
|
3741
|
-
const withoutBaseline = current.filter((entry) => entry !== LINT_BASELINE_SPECIFIER);
|
|
3742
|
-
return hasHolds ? [...withoutBaseline, LINT_BASELINE_SPECIFIER] : withoutBaseline;
|
|
3743
|
-
}
|
|
3744
|
-
function describeHolds(holds) {
|
|
3745
|
-
if (holds.length === 0) return "";
|
|
3746
|
-
const total = holds.reduce((sum, hold) => sum + hold.count, 0);
|
|
3747
|
-
const listed = holds.slice(0, 5).map(({ rule, count }) => `${rule} (${count})`).join(", ");
|
|
3748
|
-
const rest = holds.length > 5 ? `, and ${holds.length - 5} more` : "";
|
|
3749
|
-
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`;
|
|
3750
|
-
}
|
|
3751
|
-
|
|
3752
|
-
// src/roles/lint/extra-layers.ts
|
|
3999
|
+
import { existsSync as existsSync14, readFileSync as readFileSync13 } from "fs";
|
|
4000
|
+
import { isAbsolute as isAbsolute2, join as join17, resolve as resolve3 } from "path";
|
|
3753
4001
|
function ruleCount(cwd, specifier) {
|
|
3754
4002
|
const path = isAbsolute2(specifier) ? specifier : resolve3(cwd, specifier);
|
|
3755
|
-
if (!
|
|
4003
|
+
if (!existsSync14(path)) return void 0;
|
|
3756
4004
|
try {
|
|
3757
4005
|
const parsed = parseJsonc(
|
|
3758
|
-
|
|
4006
|
+
readFileSync13(path, "utf8"),
|
|
3759
4007
|
specifier
|
|
3760
4008
|
);
|
|
3761
4009
|
return Object.keys(parsed.rules ?? {}).length;
|
|
@@ -3775,7 +4023,7 @@ function extraLayers(cwd, extendsList) {
|
|
|
3775
4023
|
function committedExtendsList(cwd, configFile) {
|
|
3776
4024
|
try {
|
|
3777
4025
|
const parsed = parseJsonc(
|
|
3778
|
-
|
|
4026
|
+
readFileSync13(join17(cwd, configFile), "utf8"),
|
|
3779
4027
|
configFile
|
|
3780
4028
|
);
|
|
3781
4029
|
return Array.isArray(parsed.extends) ? parsed.extends.filter((entry) => typeof entry === "string") : [];
|
|
@@ -3834,8 +4082,8 @@ function errorCountsByConfigRule(stdout) {
|
|
|
3834
4082
|
}
|
|
3835
4083
|
|
|
3836
4084
|
// src/core/config/read-adoption.ts
|
|
3837
|
-
import { existsSync as
|
|
3838
|
-
import { join as
|
|
4085
|
+
import { existsSync as existsSync16, readFileSync as readFileSync15 } from "fs";
|
|
4086
|
+
import { join as join19 } from "path";
|
|
3839
4087
|
|
|
3840
4088
|
// src/core/config/owned-keys.ts
|
|
3841
4089
|
function presetOwnedKeys(config, permitted, presetSets) {
|
|
@@ -3850,12 +4098,12 @@ function localOnlyKeys(config, permitted, presetSets) {
|
|
|
3850
4098
|
}
|
|
3851
4099
|
|
|
3852
4100
|
// src/core/config/resolve-config-target.ts
|
|
3853
|
-
import { existsSync as
|
|
3854
|
-
import { join as
|
|
4101
|
+
import { existsSync as existsSync15, readFileSync as readFileSync14 } from "fs";
|
|
4102
|
+
import { join as join18 } from "path";
|
|
3855
4103
|
function readExtends(absolutePath) {
|
|
3856
4104
|
let parsed;
|
|
3857
4105
|
try {
|
|
3858
|
-
parsed = parseJsonc(
|
|
4106
|
+
parsed = parseJsonc(readFileSync14(absolutePath, "utf8"), absolutePath);
|
|
3859
4107
|
} catch {
|
|
3860
4108
|
return [];
|
|
3861
4109
|
}
|
|
@@ -3869,8 +4117,8 @@ function resolveConfigTarget(moduleDir, { candidates, markers, fallback }) {
|
|
|
3869
4117
|
let existing;
|
|
3870
4118
|
let existingExtendsSomething = false;
|
|
3871
4119
|
for (const candidate of candidates) {
|
|
3872
|
-
const absolutePath =
|
|
3873
|
-
if (!
|
|
4120
|
+
const absolutePath = join18(moduleDir, candidate);
|
|
4121
|
+
if (!existsSync15(absolutePath)) continue;
|
|
3874
4122
|
const chain = readExtends(absolutePath);
|
|
3875
4123
|
if (existing === void 0) {
|
|
3876
4124
|
existing = candidate;
|
|
@@ -3903,12 +4151,12 @@ var NOT_ADOPTED3 = (configFile, unreadable = null) => ({
|
|
|
3903
4151
|
});
|
|
3904
4152
|
function readAdoption(cwd, options) {
|
|
3905
4153
|
const target = resolveConfigTarget(cwd, options);
|
|
3906
|
-
if (target.reason === "none" || !
|
|
4154
|
+
if (target.reason === "none" || !existsSync16(join19(cwd, target.path))) {
|
|
3907
4155
|
return NOT_ADOPTED3(target.reason === "none" ? null : target.path);
|
|
3908
4156
|
}
|
|
3909
4157
|
let parsed;
|
|
3910
4158
|
try {
|
|
3911
|
-
parsed = parseJsonc(
|
|
4159
|
+
parsed = parseJsonc(readFileSync15(join19(cwd, target.path), "utf8"), target.path);
|
|
3912
4160
|
} catch (error) {
|
|
3913
4161
|
const reason = error instanceof Error ? error.message : String(error);
|
|
3914
4162
|
return NOT_ADOPTED3(target.path, `${target.path} could not be parsed (${reason})`);
|
|
@@ -3942,9 +4190,9 @@ function readLintAdoption(cwd) {
|
|
|
3942
4190
|
}
|
|
3943
4191
|
|
|
3944
4192
|
// src/roles/lint/resolve-oxlint.ts
|
|
3945
|
-
import { existsSync as
|
|
4193
|
+
import { existsSync as existsSync17 } from "fs";
|
|
3946
4194
|
import { createRequire as createRequire3 } from "module";
|
|
3947
|
-
import { delimiter as delimiter2, dirname as dirname5, join as
|
|
4195
|
+
import { delimiter as delimiter2, dirname as dirname5, join as join20 } from "path";
|
|
3948
4196
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
3949
4197
|
var PACKAGE_OF = {
|
|
3950
4198
|
oxlint: "oxlint",
|
|
@@ -3969,32 +4217,31 @@ function tsgolintShim(cwd) {
|
|
|
3969
4217
|
for (const owner of ["oxlint-tsgolint", "oxlint"]) {
|
|
3970
4218
|
try {
|
|
3971
4219
|
const packageDir = dirname5(require3.resolve(`${owner}/package.json`));
|
|
3972
|
-
candidates.push(
|
|
3973
|
-
candidates.push(
|
|
4220
|
+
candidates.push(join20(packageDir, "node_modules", ".bin", "tsgolint"));
|
|
4221
|
+
candidates.push(join20(packageDir, "..", ".bin", "tsgolint"));
|
|
3974
4222
|
} catch {
|
|
3975
4223
|
}
|
|
3976
4224
|
}
|
|
3977
4225
|
try {
|
|
3978
4226
|
const ownRoot = dirname5(require3.resolve("@hublo/sentinel/package.json"));
|
|
3979
|
-
candidates.push(
|
|
4227
|
+
candidates.push(join20(ownRoot, "node_modules", ".bin", "tsgolint"));
|
|
3980
4228
|
} catch {
|
|
3981
4229
|
candidates.push(resolveBin(dirname5(fileURLToPath2(import.meta.url)), "tsgolint") ?? "");
|
|
3982
4230
|
}
|
|
3983
|
-
return candidates.find((candidate) => candidate !== "" &&
|
|
4231
|
+
return candidates.find((candidate) => candidate !== "" && existsSync17(candidate));
|
|
3984
4232
|
}
|
|
3985
4233
|
function oxlintSearchPath(cwd) {
|
|
3986
4234
|
return binSearchPath(cwd);
|
|
3987
4235
|
}
|
|
3988
4236
|
|
|
3989
4237
|
// src/roles/lint/adapters/oxlint/plan.ts
|
|
3990
|
-
import { existsSync as
|
|
3991
|
-
import { join as
|
|
4238
|
+
import { existsSync as existsSync19, readFileSync as readFileSync18 } from "fs";
|
|
4239
|
+
import { join as join23 } from "path";
|
|
3992
4240
|
|
|
3993
4241
|
// src/roles/lint/eslint-ignores.ts
|
|
3994
|
-
import { existsSync as
|
|
3995
|
-
import { join as
|
|
3996
|
-
|
|
3997
|
-
var STRING_LITERAL = /['"`]([^'"`]+)['"`]/g;
|
|
4242
|
+
import { existsSync as existsSync18, readFileSync as readFileSync16 } from "fs";
|
|
4243
|
+
import { join as join21 } from "path";
|
|
4244
|
+
import { parseSync as parseSync2 } from "oxc-parser";
|
|
3998
4245
|
var OPAQUE_SOURCE = /\b(includeIgnoreFile)\s*\([^)]*\)/g;
|
|
3999
4246
|
function toOxlintPattern(pattern) {
|
|
4000
4247
|
return pattern.replace(/^\.\//, "");
|
|
@@ -4005,24 +4252,71 @@ function readRootEslintIgnores(root) {
|
|
|
4005
4252
|
return { patterns: patterns.filter((pattern) => pattern.startsWith("**/")), unresolved };
|
|
4006
4253
|
}
|
|
4007
4254
|
function readEslintIgnores(cwd) {
|
|
4008
|
-
const config = ESLINT_CONFIG_FILES.map((name) =>
|
|
4255
|
+
const config = ESLINT_CONFIG_FILES.map((name) => join21(cwd, name)).find((path) => existsSync18(path));
|
|
4009
4256
|
if (!config) return { patterns: [], unresolved: [] };
|
|
4010
4257
|
let source;
|
|
4011
4258
|
try {
|
|
4012
|
-
source =
|
|
4259
|
+
source = readFileSync16(config, "utf8");
|
|
4013
4260
|
} catch {
|
|
4014
4261
|
return { patterns: [], unresolved: [] };
|
|
4015
4262
|
}
|
|
4016
4263
|
const patterns = /* @__PURE__ */ new Set();
|
|
4017
|
-
|
|
4018
|
-
|
|
4019
|
-
|
|
4020
|
-
|
|
4264
|
+
const unresolved = new Set([...source.matchAll(OPAQUE_SOURCE)].map((match) => match[0]));
|
|
4265
|
+
const { program, errors } = parseSync2(config, source, { sourceType: "module" });
|
|
4266
|
+
if (errors.length > 0) {
|
|
4267
|
+
return { patterns: [], unresolved: [`${config} could not be parsed`] };
|
|
4268
|
+
}
|
|
4269
|
+
walk(program, (node) => {
|
|
4270
|
+
if (node.type === "CallExpression" && calleeName(node) === "globalIgnores") {
|
|
4271
|
+
for (const literal of stringLiterals(firstArrayArgument(node))) {
|
|
4272
|
+
patterns.add(toOxlintPattern(literal));
|
|
4021
4273
|
}
|
|
4274
|
+
return;
|
|
4275
|
+
}
|
|
4276
|
+
if (node.type !== "ObjectExpression") return;
|
|
4277
|
+
const keys = objectKeys(node);
|
|
4278
|
+
if (!keys.includes("ignores")) return;
|
|
4279
|
+
const values = arrayValues(node, "ignores");
|
|
4280
|
+
if (keys.length > 1) {
|
|
4281
|
+
unresolved.add(
|
|
4282
|
+
`ignores scoped to one config block (with ${keys.filter((k) => k !== "ignores").join(", ")}): ${values.join(", ")} \u2014 exempt from those RULES, not excluded from linting`
|
|
4283
|
+
);
|
|
4284
|
+
return;
|
|
4022
4285
|
}
|
|
4286
|
+
for (const literal of values) patterns.add(toOxlintPattern(literal));
|
|
4287
|
+
});
|
|
4288
|
+
return { patterns: [...patterns], unresolved: [...unresolved] };
|
|
4289
|
+
}
|
|
4290
|
+
function walk(node, visit) {
|
|
4291
|
+
if (!node || typeof node !== "object") return;
|
|
4292
|
+
const current = node;
|
|
4293
|
+
if (typeof current.type === "string") visit(current);
|
|
4294
|
+
for (const key of Object.keys(current)) {
|
|
4295
|
+
const value = current[key];
|
|
4296
|
+
if (Array.isArray(value)) for (const entry of value) walk(entry, visit);
|
|
4297
|
+
else if (value && typeof value === "object") walk(value, visit);
|
|
4023
4298
|
}
|
|
4024
|
-
|
|
4025
|
-
|
|
4299
|
+
}
|
|
4300
|
+
function calleeName(node) {
|
|
4301
|
+
const callee = node.callee;
|
|
4302
|
+
return callee?.type === "Identifier" ? callee.name : void 0;
|
|
4303
|
+
}
|
|
4304
|
+
function firstArrayArgument(node) {
|
|
4305
|
+
const args = node.arguments ?? [];
|
|
4306
|
+
return args.find((argument) => argument?.type === "ArrayExpression");
|
|
4307
|
+
}
|
|
4308
|
+
function stringLiterals(array) {
|
|
4309
|
+
const elements = array?.elements ?? [];
|
|
4310
|
+
return elements.filter((element) => element?.type === "Literal" && typeof element.value === "string").map((element) => element.value);
|
|
4311
|
+
}
|
|
4312
|
+
function objectKeys(node) {
|
|
4313
|
+
const properties = node.properties ?? [];
|
|
4314
|
+
return properties.filter((property) => property?.type === "Property" && !property.computed).map((property) => property.key?.name ?? String(property.key?.value ?? "")).filter(Boolean);
|
|
4315
|
+
}
|
|
4316
|
+
function arrayValues(node, name) {
|
|
4317
|
+
const properties = node.properties ?? [];
|
|
4318
|
+
const found = properties.find((property) => property.key?.name === name);
|
|
4319
|
+
return stringLiterals(found?.value);
|
|
4026
4320
|
}
|
|
4027
4321
|
|
|
4028
4322
|
// src/roles/lint/lint-script.ts
|
|
@@ -4063,8 +4357,8 @@ function lintPresetFor(preset) {
|
|
|
4063
4357
|
}
|
|
4064
4358
|
|
|
4065
4359
|
// src/roles/lint/rename-suppressions.ts
|
|
4066
|
-
import { readdirSync as readdirSync2, readFileSync as
|
|
4067
|
-
import { join as
|
|
4360
|
+
import { readdirSync as readdirSync2, readFileSync as readFileSync17, statSync as statSync2 } from "fs";
|
|
4361
|
+
import { join as join22, relative as relative3 } from "path";
|
|
4068
4362
|
var SOURCE_EXTENSIONS = [
|
|
4069
4363
|
".ts",
|
|
4070
4364
|
".tsx",
|
|
@@ -4111,10 +4405,10 @@ function* sourceFiles(dir) {
|
|
|
4111
4405
|
return;
|
|
4112
4406
|
}
|
|
4113
4407
|
for (const entry of entries) {
|
|
4114
|
-
const full =
|
|
4408
|
+
const full = join22(dir, entry);
|
|
4115
4409
|
let isDirectory;
|
|
4116
4410
|
try {
|
|
4117
|
-
isDirectory =
|
|
4411
|
+
isDirectory = statSync2(full).isDirectory();
|
|
4118
4412
|
} catch {
|
|
4119
4413
|
continue;
|
|
4120
4414
|
}
|
|
@@ -4131,7 +4425,7 @@ function findSuppressionRenames(cwd, renames) {
|
|
|
4131
4425
|
for (const file of sourceFiles(cwd)) {
|
|
4132
4426
|
let content;
|
|
4133
4427
|
try {
|
|
4134
|
-
content =
|
|
4428
|
+
content = readFileSync17(file, "utf8");
|
|
4135
4429
|
} catch {
|
|
4136
4430
|
continue;
|
|
4137
4431
|
}
|
|
@@ -4155,7 +4449,7 @@ function findSuppressionRenames(cwd, renames) {
|
|
|
4155
4449
|
}
|
|
4156
4450
|
if (hits.length === 0) return;
|
|
4157
4451
|
found.push({
|
|
4158
|
-
file:
|
|
4452
|
+
file: relative3(cwd, file),
|
|
4159
4453
|
line: index + 1,
|
|
4160
4454
|
from: line,
|
|
4161
4455
|
to: line.replace(rules, renamed.join(", ")),
|
|
@@ -4259,7 +4553,7 @@ function plan2(context) {
|
|
|
4259
4553
|
keys: removableDeps
|
|
4260
4554
|
});
|
|
4261
4555
|
}
|
|
4262
|
-
const eslintConfigs = ESLINT_CONFIG_FILES.filter((name) =>
|
|
4556
|
+
const eslintConfigs = ESLINT_CONFIG_FILES.filter((name) => existsSync19(join23(context.cwd, name)));
|
|
4263
4557
|
for (const name of eslintConfigs) operations.push({ kind: "delete", path: name });
|
|
4264
4558
|
operations.push(
|
|
4265
4559
|
...nxTargetOperations({
|
|
@@ -4357,7 +4651,7 @@ function lintScripts(cwd) {
|
|
|
4357
4651
|
function committedExtends(cwd) {
|
|
4358
4652
|
try {
|
|
4359
4653
|
const parsed = parseJsonc(
|
|
4360
|
-
|
|
4654
|
+
readFileSync18(join23(cwd, LINT_CONFIG_FILE), "utf8"),
|
|
4361
4655
|
LINT_CONFIG_FILE
|
|
4362
4656
|
);
|
|
4363
4657
|
return Array.isArray(parsed.extends) ? parsed.extends.filter((entry) => typeof entry === "string") : [];
|
|
@@ -4368,7 +4662,7 @@ function committedExtends(cwd) {
|
|
|
4368
4662
|
function committedIgnorePatterns(cwd) {
|
|
4369
4663
|
try {
|
|
4370
4664
|
const parsed = parseJsonc(
|
|
4371
|
-
|
|
4665
|
+
readFileSync18(join23(cwd, LINT_CONFIG_FILE), "utf8"),
|
|
4372
4666
|
LINT_CONFIG_FILE
|
|
4373
4667
|
);
|
|
4374
4668
|
return Array.isArray(parsed.ignorePatterns) ? parsed.ignorePatterns.filter((entry) => typeof entry === "string") : [];
|
|
@@ -4380,7 +4674,7 @@ function moduleEslintDependencies(cwd) {
|
|
|
4380
4674
|
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/");
|
|
4381
4675
|
let manifest;
|
|
4382
4676
|
try {
|
|
4383
|
-
manifest = JSON.parse(
|
|
4677
|
+
manifest = JSON.parse(readFileSync18(join23(cwd, "package.json"), "utf8"));
|
|
4384
4678
|
} catch {
|
|
4385
4679
|
return [];
|
|
4386
4680
|
}
|
|
@@ -4445,17 +4739,31 @@ var OxlintAdapter = class extends BaseAdapter {
|
|
|
4445
4739
|
return presetOfVariant(presetNameFromPath(readLintAdoption(cwd).preset));
|
|
4446
4740
|
}
|
|
4447
4741
|
/**
|
|
4448
|
-
* The autofix pass
|
|
4449
|
-
*
|
|
4742
|
+
* The autofix pass, and what it did.
|
|
4743
|
+
*
|
|
4744
|
+
* Output is captured rather than streamed: the fix run is noise, and the verification run
|
|
4745
|
+
* that follows is the one a developer reads. But captured is not the same as discarded, and
|
|
4746
|
+
* it used to be discarded, which is why a pass that never ran looked exactly like one that
|
|
4747
|
+
* had nothing to do.
|
|
4748
|
+
*
|
|
4749
|
+
* The exit status cannot be the signal, and neither can the stream. Measured on oxlint: a
|
|
4750
|
+
* healthy run on a module with remaining violations and a run whose `extends` path resolves
|
|
4751
|
+
* nowhere BOTH exit 1 and both write to stdout. One module was checked and one was not, and
|
|
4752
|
+
* nothing in the process result separates them, so the only signal is what the tool said.
|
|
4450
4753
|
*/
|
|
4451
4754
|
fixPass(ctx, oxlint, env) {
|
|
4452
|
-
const
|
|
4755
|
+
const before = snapshotFiles(ctx.cwd, LINTABLE_EXTENSIONS);
|
|
4756
|
+
const result = spawnSync4(oxlint, ["-c", LINT_CONFIG_FILE, "--fix", "."], {
|
|
4453
4757
|
cwd: ctx.cwd,
|
|
4454
4758
|
encoding: "utf8",
|
|
4455
4759
|
env,
|
|
4456
4760
|
maxBuffer: CAPTURE_MAX_BUFFER
|
|
4457
4761
|
});
|
|
4458
|
-
return result.
|
|
4762
|
+
if (result.error) return { changed: [], failure: result.error.message };
|
|
4763
|
+
const failure = readFailure(`${result.stdout ?? ""}
|
|
4764
|
+
${result.stderr ?? ""}`);
|
|
4765
|
+
if (failure !== void 0) return { changed: [], failure };
|
|
4766
|
+
return { changed: changedSince(ctx.cwd, before, LINTABLE_EXTENSIONS), failure: void 0 };
|
|
4459
4767
|
}
|
|
4460
4768
|
/** The module's current `lint` script, so adoption replaces only the eslint part of it. */
|
|
4461
4769
|
/**
|
|
@@ -4478,7 +4786,8 @@ var OxlintAdapter = class extends BaseAdapter {
|
|
|
4478
4786
|
const oxlint = resolveOxlint(ctx.cwd);
|
|
4479
4787
|
if (!oxlint) return;
|
|
4480
4788
|
const env = { ...process.env, PATH: oxlintPath(ctx.cwd, process.env) };
|
|
4481
|
-
this.fixPass(ctx, oxlint, env)
|
|
4789
|
+
process.stderr.write(` ${describeFixOutcome(this.fixPass(ctx, oxlint, env), "lint")}
|
|
4790
|
+
`);
|
|
4482
4791
|
this.writeModuleBaseline(ctx, oxlint, env);
|
|
4483
4792
|
}
|
|
4484
4793
|
/**
|
|
@@ -4499,20 +4808,20 @@ var OxlintAdapter = class extends BaseAdapter {
|
|
|
4499
4808
|
* build the developer can see, rather than a silent half-adoption they cannot.
|
|
4500
4809
|
*/
|
|
4501
4810
|
writeModuleBaseline(ctx, oxlint, env) {
|
|
4502
|
-
const configPath =
|
|
4503
|
-
const baselinePath =
|
|
4504
|
-
if (!
|
|
4811
|
+
const configPath = join24(ctx.cwd, LINT_CONFIG_FILE);
|
|
4812
|
+
const baselinePath = join24(ctx.cwd, LINT_BASELINE_FILE);
|
|
4813
|
+
if (!existsSync20(configPath)) return;
|
|
4505
4814
|
let config;
|
|
4506
4815
|
try {
|
|
4507
4816
|
config = parseJsonc(
|
|
4508
|
-
|
|
4817
|
+
readFileSync19(configPath, "utf8"),
|
|
4509
4818
|
LINT_CONFIG_FILE
|
|
4510
4819
|
);
|
|
4511
4820
|
} catch {
|
|
4512
4821
|
return;
|
|
4513
4822
|
}
|
|
4514
4823
|
const current = Array.isArray(config.extends) ? config.extends : [];
|
|
4515
|
-
const measurePath =
|
|
4824
|
+
const measurePath = join24(ctx.cwd, LINT_MEASURE_FILE);
|
|
4516
4825
|
let measured;
|
|
4517
4826
|
try {
|
|
4518
4827
|
writeFileSync2(
|
|
@@ -4520,7 +4829,7 @@ var OxlintAdapter = class extends BaseAdapter {
|
|
|
4520
4829
|
`${JSON.stringify({ ...config, extends: extendsWithBaseline(current, false) }, null, 2)}
|
|
4521
4830
|
`
|
|
4522
4831
|
);
|
|
4523
|
-
measured =
|
|
4832
|
+
measured = spawnSync4(
|
|
4524
4833
|
oxlint,
|
|
4525
4834
|
[
|
|
4526
4835
|
"-c",
|
|
@@ -4560,7 +4869,7 @@ var OxlintAdapter = class extends BaseAdapter {
|
|
|
4560
4869
|
}
|
|
4561
4870
|
}
|
|
4562
4871
|
async run(ctx) {
|
|
4563
|
-
if (!
|
|
4872
|
+
if (!existsSync20(join24(ctx.cwd, LINT_CONFIG_FILE))) {
|
|
4564
4873
|
process.stderr.write(
|
|
4565
4874
|
`sentinel lint(oxlint): no ${LINT_CONFIG_FILE} in this module; run \`sentinel --init --lint\` to adopt.
|
|
4566
4875
|
`
|
|
@@ -4605,9 +4914,10 @@ var OxlintAdapter = class extends BaseAdapter {
|
|
|
4605
4914
|
valueFlags: OXLINT_VALUE_FLAGS
|
|
4606
4915
|
});
|
|
4607
4916
|
const targets = paths.length > 0 ? paths : ["."];
|
|
4917
|
+
const emptyScope = scopeFlags(paths);
|
|
4608
4918
|
const lint = (extra) => {
|
|
4609
|
-
const passed = [...extra, ...passedOptions];
|
|
4610
|
-
const result =
|
|
4919
|
+
const passed = [...extra, ...emptyScope, ...passedOptions];
|
|
4920
|
+
const result = spawnSync4(oxlint, ["-c", LINT_CONFIG_FILE, ...passed, ...targets], {
|
|
4611
4921
|
cwd: ctx.cwd,
|
|
4612
4922
|
stdio: "inherit",
|
|
4613
4923
|
env
|
|
@@ -4645,6 +4955,33 @@ var OxlintAdapter = class extends BaseAdapter {
|
|
|
4645
4955
|
rule: entry.rule,
|
|
4646
4956
|
reason: entry.reason
|
|
4647
4957
|
})),
|
|
4958
|
+
// A FOURTH state, and the only one that was declared and never reported: rules oxlint
|
|
4959
|
+
// cannot run at all, so they can be neither enforced nor disabled. Each preset has
|
|
4960
|
+
// carried this list since the Svelte work, and nothing read it outside a test.
|
|
4961
|
+
//
|
|
4962
|
+
// The gap that made it matter: `no-restricted-syntax` has no oxlint equivalent, so two
|
|
4963
|
+
// architectural guards a Nest service relied on vanished at adoption with nothing said.
|
|
4964
|
+
// The adopter found it himself and rebuilt them as a local plugin. The next one might
|
|
4965
|
+
// not, which is the whole argument for saying it out loud.
|
|
4966
|
+
uncovered: uncoveredFor(presetVariant(ctx.preset, ctx.cwd)).map((entry) => ({
|
|
4967
|
+
rule: entry.rule,
|
|
4968
|
+
reason: entry.reason
|
|
4969
|
+
})),
|
|
4970
|
+
// What this module HOLDS: the rules its own baseline lowers to `warn`.
|
|
4971
|
+
//
|
|
4972
|
+
// Distinct from `downgraded` just above, which is the PRESET's doing and identical for
|
|
4973
|
+
// every module on it. These are this module's own debt, and leaving them out was a real
|
|
4974
|
+
// gap: `--inspect` could tell you the preset holds 6 rules while saying nothing about
|
|
4975
|
+
// the 3 your module holds on top, so nobody could see their own debt without opening a
|
|
4976
|
+
// file that says "do not edit by hand".
|
|
4977
|
+
//
|
|
4978
|
+
// Listed, not counted. The list is free — it is the committed file — while a count means
|
|
4979
|
+
// running oxlint, which this verb deliberately does not do (that is what keeps a
|
|
4980
|
+
// 441-module sweep at ~8s per role). `--run --lint --json` carries the per-rule counts.
|
|
4981
|
+
held: heldRules(ctx.cwd).map((rule) => ({
|
|
4982
|
+
rule,
|
|
4983
|
+
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.`
|
|
4984
|
+
})),
|
|
4648
4985
|
// What this module enforces BEYOND the preset. Not drift, and not covered by anything
|
|
4649
4986
|
// else here: the stub still holds only `extends` and `ignorePatterns`, so a module with
|
|
4650
4987
|
// a team layer reports `conformant=true drift=[]` and used to say nothing at all about
|
|
@@ -4680,7 +5017,7 @@ var OxlintAdapter = class extends BaseAdapter {
|
|
|
4680
5017
|
let stub;
|
|
4681
5018
|
try {
|
|
4682
5019
|
stub = parseJsonc(
|
|
4683
|
-
|
|
5020
|
+
readFileSync19(join24(cwd, LINT_CONFIG_FILE), "utf8"),
|
|
4684
5021
|
LINT_CONFIG_FILE
|
|
4685
5022
|
);
|
|
4686
5023
|
} catch {
|
|
@@ -4690,7 +5027,7 @@ var OxlintAdapter = class extends BaseAdapter {
|
|
|
4690
5027
|
for (const entry of stub.extends ?? []) {
|
|
4691
5028
|
try {
|
|
4692
5029
|
const preset = parseJsonc(
|
|
4693
|
-
|
|
5030
|
+
readFileSync19(join24(cwd, entry), "utf8"),
|
|
4694
5031
|
entry
|
|
4695
5032
|
);
|
|
4696
5033
|
for (const rule of Object.keys(preset.rules ?? {})) names.add(rule);
|
|
@@ -4707,7 +5044,7 @@ var OxlintAdapter = class extends BaseAdapter {
|
|
|
4707
5044
|
const oxlint = resolveOxlint(ctx.cwd);
|
|
4708
5045
|
if (!oxlint) return { ok: false, code: 1, metrics: { error: "oxlint not found" } };
|
|
4709
5046
|
const typeAware = canRunTypeAware(ctx.cwd) ? ["--type-aware"] : [];
|
|
4710
|
-
const result =
|
|
5047
|
+
const result = spawnSync4(oxlint, ["-c", LINT_CONFIG_FILE, ...typeAware, "-f", "json", "."], {
|
|
4711
5048
|
cwd: ctx.cwd,
|
|
4712
5049
|
encoding: "utf8",
|
|
4713
5050
|
env: { ...process.env, PATH: oxlintPath(ctx.cwd, process.env) },
|
|
@@ -4760,14 +5097,14 @@ var OxlintAdapter = class extends BaseAdapter {
|
|
|
4760
5097
|
let parsed;
|
|
4761
5098
|
try {
|
|
4762
5099
|
parsed = parseJsonc(
|
|
4763
|
-
|
|
5100
|
+
readFileSync19(join24(cwd, LINT_CONFIG_FILE), "utf8"),
|
|
4764
5101
|
LINT_CONFIG_FILE
|
|
4765
5102
|
);
|
|
4766
5103
|
} catch {
|
|
4767
5104
|
return void 0;
|
|
4768
5105
|
}
|
|
4769
5106
|
const targets = Array.isArray(parsed.extends) ? parsed.extends.filter((entry) => typeof entry === "string") : typeof parsed.extends === "string" ? [parsed.extends] : [];
|
|
4770
|
-
return targets.find((target) => !
|
|
5107
|
+
return targets.find((target) => !existsSync20(join24(cwd, target)));
|
|
4771
5108
|
}
|
|
4772
5109
|
/** Announce what is not enforced, so reduced coverage is never silent. */
|
|
4773
5110
|
announceDisabled(preset) {
|
|
@@ -4798,36 +5135,10 @@ function registerLint() {
|
|
|
4798
5135
|
}
|
|
4799
5136
|
|
|
4800
5137
|
// src/roles/typescript/adapters/tsc/tsc.adapter.ts
|
|
4801
|
-
import { spawnSync as
|
|
4802
|
-
import { existsSync as
|
|
5138
|
+
import { spawnSync as spawnSync5 } from "child_process";
|
|
5139
|
+
import { existsSync as existsSync21, readFileSync as readFileSync21 } from "fs";
|
|
4803
5140
|
import { createRequire as createRequire4 } from "module";
|
|
4804
|
-
import { join as
|
|
4805
|
-
|
|
4806
|
-
// src/roles/typescript/presets/base.json
|
|
4807
|
-
var base_default3 = {
|
|
4808
|
-
$schema: "https://json.schemastore.org/tsconfig",
|
|
4809
|
-
compilerOptions: {
|
|
4810
|
-
strict: true,
|
|
4811
|
-
noFallthroughCasesInSwitch: true,
|
|
4812
|
-
forceConsistentCasingInFileNames: true,
|
|
4813
|
-
esModuleInterop: true,
|
|
4814
|
-
skipLibCheck: true
|
|
4815
|
-
},
|
|
4816
|
-
deferred: {
|
|
4817
|
-
noImplicitAny: {
|
|
4818
|
-
phase: 2,
|
|
4819
|
-
reason: "the implicit-any migration (TS70xx)"
|
|
4820
|
-
},
|
|
4821
|
-
noUnusedLocals: {
|
|
4822
|
-
phase: 2,
|
|
4823
|
-
reason: "unused-local cleanup (TS6133)"
|
|
4824
|
-
},
|
|
4825
|
-
noUnusedParameters: {
|
|
4826
|
-
phase: 2,
|
|
4827
|
-
reason: "unused-parameter cleanup (TS6133)"
|
|
4828
|
-
}
|
|
4829
|
-
}
|
|
4830
|
-
};
|
|
5141
|
+
import { join as join26 } from "path";
|
|
4831
5142
|
|
|
4832
5143
|
// src/roles/typescript/presets/nest.json
|
|
4833
5144
|
var nest_default2 = {
|
|
@@ -4878,15 +5189,41 @@ var react_default2 = {
|
|
|
4878
5189
|
}
|
|
4879
5190
|
};
|
|
4880
5191
|
|
|
5192
|
+
// src/roles/typescript/presets/shared.json
|
|
5193
|
+
var shared_default2 = {
|
|
5194
|
+
$schema: "https://json.schemastore.org/tsconfig",
|
|
5195
|
+
compilerOptions: {
|
|
5196
|
+
strict: true,
|
|
5197
|
+
noFallthroughCasesInSwitch: true,
|
|
5198
|
+
forceConsistentCasingInFileNames: true,
|
|
5199
|
+
esModuleInterop: true,
|
|
5200
|
+
skipLibCheck: true
|
|
5201
|
+
},
|
|
5202
|
+
deferred: {
|
|
5203
|
+
noImplicitAny: {
|
|
5204
|
+
phase: 2,
|
|
5205
|
+
reason: "the implicit-any migration (TS70xx)"
|
|
5206
|
+
},
|
|
5207
|
+
noUnusedLocals: {
|
|
5208
|
+
phase: 2,
|
|
5209
|
+
reason: "unused-local cleanup (TS6133)"
|
|
5210
|
+
},
|
|
5211
|
+
noUnusedParameters: {
|
|
5212
|
+
phase: 2,
|
|
5213
|
+
reason: "unused-parameter cleanup (TS6133)"
|
|
5214
|
+
}
|
|
5215
|
+
}
|
|
5216
|
+
};
|
|
5217
|
+
|
|
4881
5218
|
// src/roles/typescript/preset-data.ts
|
|
4882
|
-
var
|
|
5219
|
+
var SHARED = shared_default2;
|
|
4883
5220
|
var LAYERS = {
|
|
4884
5221
|
nest: nest_default2,
|
|
4885
5222
|
node: node_default2,
|
|
4886
5223
|
react: react_default2
|
|
4887
5224
|
};
|
|
4888
5225
|
var SHIPPED_PRESETS = Object.keys(LAYERS).sort();
|
|
4889
|
-
var DEFERRED_RULES = Object.entries(
|
|
5226
|
+
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));
|
|
4890
5227
|
function validate2() {
|
|
4891
5228
|
for (const { rule, phase, reason } of DEFERRED_RULES) {
|
|
4892
5229
|
if (!Number.isInteger(phase) || phase < 2) {
|
|
@@ -4899,9 +5236,9 @@ function validate2() {
|
|
|
4899
5236
|
`deferred rule "${rule}" has no reason; --inspect reports it and would say nothing`
|
|
4900
5237
|
);
|
|
4901
5238
|
}
|
|
4902
|
-
if (rule in
|
|
5239
|
+
if (rule in SHARED.compilerOptions) {
|
|
4903
5240
|
throw new Error(
|
|
4904
|
-
`"${rule}" is deferred and also set by the
|
|
5241
|
+
`"${rule}" is deferred and also set by the shared layer; it would be announced as not enforced while being enforced`
|
|
4905
5242
|
);
|
|
4906
5243
|
}
|
|
4907
5244
|
for (const [preset, layer] of Object.entries(LAYERS)) {
|
|
@@ -4921,8 +5258,8 @@ function tsPresetFor(preset) {
|
|
|
4921
5258
|
const layer = LAYERS[preset];
|
|
4922
5259
|
if (!layer) throw new Error(`no TypeScript preset ships for preset "${preset}"`);
|
|
4923
5260
|
return {
|
|
4924
|
-
...
|
|
4925
|
-
compilerOptions: { ...
|
|
5261
|
+
...SHARED.$schema === void 0 ? {} : { $schema: SHARED.$schema },
|
|
5262
|
+
compilerOptions: { ...SHARED.compilerOptions, ...layer.compilerOptions }
|
|
4926
5263
|
};
|
|
4927
5264
|
}
|
|
4928
5265
|
|
|
@@ -4982,8 +5319,8 @@ function readTsconfigAdoption(cwd) {
|
|
|
4982
5319
|
}
|
|
4983
5320
|
|
|
4984
5321
|
// src/roles/typescript/adapters/tsc/plan.ts
|
|
4985
|
-
import { readFileSync as
|
|
4986
|
-
import { join as
|
|
5322
|
+
import { readFileSync as readFileSync20 } from "fs";
|
|
5323
|
+
import { join as join25 } from "path";
|
|
4987
5324
|
|
|
4988
5325
|
// src/roles/typescript/typecheck-script.ts
|
|
4989
5326
|
var SENTINEL_TYPECHECK_COMMAND = "sentinel --run --typescript";
|
|
@@ -5076,7 +5413,7 @@ function planAdoption(context) {
|
|
|
5076
5413
|
};
|
|
5077
5414
|
}
|
|
5078
5415
|
const existing = parseJsonc(
|
|
5079
|
-
|
|
5416
|
+
readFileSync20(join25(context.cwd, target.path), "utf8"),
|
|
5080
5417
|
target.path
|
|
5081
5418
|
);
|
|
5082
5419
|
const extendsChain = composeExtends(existing.extends, preset);
|
|
@@ -5214,7 +5551,7 @@ var TscAdapter = class _TscAdapter extends BaseAdapter {
|
|
|
5214
5551
|
let chain;
|
|
5215
5552
|
try {
|
|
5216
5553
|
const parsed = parseJsonc(
|
|
5217
|
-
|
|
5554
|
+
readFileSync21(join26(cwd, target.path), "utf8"),
|
|
5218
5555
|
target.path
|
|
5219
5556
|
);
|
|
5220
5557
|
chain = parsed.extends;
|
|
@@ -5227,7 +5564,7 @@ var TscAdapter = class _TscAdapter extends BaseAdapter {
|
|
|
5227
5564
|
);
|
|
5228
5565
|
if (preset === void 0) return void 0;
|
|
5229
5566
|
try {
|
|
5230
|
-
createRequire4(
|
|
5567
|
+
createRequire4(join26(cwd, "noop.js")).resolve(preset);
|
|
5231
5568
|
return void 0;
|
|
5232
5569
|
} catch {
|
|
5233
5570
|
return preset;
|
|
@@ -5268,7 +5605,7 @@ var TscAdapter = class _TscAdapter extends BaseAdapter {
|
|
|
5268
5605
|
return { ok: false, code: 1 };
|
|
5269
5606
|
}
|
|
5270
5607
|
if (options.length > 0) return this.runWithOptions(ctx, tsc, config);
|
|
5271
|
-
const result =
|
|
5608
|
+
const result = spawnSync5(tsc, ["-b", config], { cwd: ctx.cwd, stdio: "inherit" });
|
|
5272
5609
|
if (result.error) {
|
|
5273
5610
|
process.stderr.write(
|
|
5274
5611
|
`sentinel typescript(tsc): could not run tsc (${result.error.message}); is TypeScript installed in the module?
|
|
@@ -5303,7 +5640,7 @@ var TscAdapter = class _TscAdapter extends BaseAdapter {
|
|
|
5303
5640
|
let worst = 0;
|
|
5304
5641
|
for (const project of this.referencedProjects(ctx.cwd, config)) {
|
|
5305
5642
|
const args = ["-p", project, "--noEmit", "--composite", "false", ...ctx.toolArgs ?? []];
|
|
5306
|
-
const result =
|
|
5643
|
+
const result = spawnSync5(tsc, args, { cwd: ctx.cwd, stdio: "inherit" });
|
|
5307
5644
|
if (result.error) {
|
|
5308
5645
|
process.stderr.write(
|
|
5309
5646
|
`sentinel typescript(tsc): could not run tsc (${result.error.message}); is TypeScript installed in the module?
|
|
@@ -5323,7 +5660,7 @@ var TscAdapter = class _TscAdapter extends BaseAdapter {
|
|
|
5323
5660
|
referencedProjects(cwd, config) {
|
|
5324
5661
|
try {
|
|
5325
5662
|
const parsed = parseJsonc(
|
|
5326
|
-
|
|
5663
|
+
readFileSync21(join26(cwd, config), "utf8"),
|
|
5327
5664
|
config
|
|
5328
5665
|
);
|
|
5329
5666
|
const referenced = (parsed.references ?? []).map((reference) => reference.path).filter((path) => typeof path === "string" && path.length > 0);
|
|
@@ -5339,7 +5676,7 @@ var TscAdapter = class _TscAdapter extends BaseAdapter {
|
|
|
5339
5676
|
* check.
|
|
5340
5677
|
*/
|
|
5341
5678
|
typecheckTarget(cwd) {
|
|
5342
|
-
if (
|
|
5679
|
+
if (existsSync21(join26(cwd, "tsconfig.json"))) return "tsconfig.json";
|
|
5343
5680
|
const target = resolveTsconfigTarget(cwd);
|
|
5344
5681
|
return target.reason === "none" ? null : target.path;
|
|
5345
5682
|
}
|
|
@@ -5392,7 +5729,7 @@ var TscAdapter = class _TscAdapter extends BaseAdapter {
|
|
|
5392
5729
|
return { ok: true, code: 0, metrics: _TscAdapter.NOTHING_TO_REPORT };
|
|
5393
5730
|
}
|
|
5394
5731
|
const tsc = resolveBin(ctx.cwd, "tsc") ?? "tsc";
|
|
5395
|
-
const result =
|
|
5732
|
+
const result = spawnSync5(tsc, ["-b", config], { cwd: ctx.cwd, encoding: "utf8" });
|
|
5396
5733
|
if (result.error) {
|
|
5397
5734
|
process.stderr.write(
|
|
5398
5735
|
`sentinel typescript(tsc): could not run tsc (${result.error.message}); is TypeScript installed in the module?
|
|
@@ -5435,7 +5772,7 @@ var TscAdapter = class _TscAdapter extends BaseAdapter {
|
|
|
5435
5772
|
* errors in the output mean the rule must be on.
|
|
5436
5773
|
*/
|
|
5437
5774
|
noImplicitAnyEnabled(cwd, tsc, config, mainOutput) {
|
|
5438
|
-
const shown =
|
|
5775
|
+
const shown = spawnSync5(tsc, ["-p", config, "--showConfig"], { cwd, encoding: "utf8" });
|
|
5439
5776
|
if (shown.status === 0 && shown.stdout) {
|
|
5440
5777
|
try {
|
|
5441
5778
|
const co = parseJsonc(
|
|
@@ -5487,7 +5824,7 @@ function detectFramework(packageJson) {
|
|
|
5487
5824
|
}
|
|
5488
5825
|
|
|
5489
5826
|
// src/core/dispatch.ts
|
|
5490
|
-
import { basename as
|
|
5827
|
+
import { basename as basename3 } from "path";
|
|
5491
5828
|
|
|
5492
5829
|
// src/shared/text.ts
|
|
5493
5830
|
function ensureLines(current, lines) {
|
|
@@ -5543,9 +5880,16 @@ function replaceLines(current, replacements) {
|
|
|
5543
5880
|
}
|
|
5544
5881
|
|
|
5545
5882
|
// src/core/apply-plan.ts
|
|
5546
|
-
import { existsSync as
|
|
5883
|
+
import { existsSync as existsSync22, readFileSync as readFileSync22, renameSync, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
5547
5884
|
import { resolve as resolve4, sep } from "path";
|
|
5548
5885
|
import { applyEdits, findNodeAtLocation, modify, parseTree } from "jsonc-parser";
|
|
5886
|
+
|
|
5887
|
+
// src/shared/deep-merge.ts
|
|
5888
|
+
function isPlainObject(value) {
|
|
5889
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5890
|
+
}
|
|
5891
|
+
|
|
5892
|
+
// src/core/apply-plan.ts
|
|
5549
5893
|
function resolveWithinRoot(cwd, relativePath) {
|
|
5550
5894
|
const root = resolve4(cwd);
|
|
5551
5895
|
const absolutePath = resolve4(root, relativePath);
|
|
@@ -5555,7 +5899,7 @@ function resolveWithinRoot(cwd, relativePath) {
|
|
|
5555
5899
|
return absolutePath;
|
|
5556
5900
|
}
|
|
5557
5901
|
function readIfExists(absolutePath) {
|
|
5558
|
-
return
|
|
5902
|
+
return existsSync22(absolutePath) ? readFileSync22(absolutePath, "utf8") : void 0;
|
|
5559
5903
|
}
|
|
5560
5904
|
function* leaves(value, prefix = []) {
|
|
5561
5905
|
for (const [key, keyValue] of Object.entries(value)) {
|
|
@@ -5673,8 +6017,8 @@ function applyPlan(cwd, plan3) {
|
|
|
5673
6017
|
}
|
|
5674
6018
|
|
|
5675
6019
|
// src/core/config/preset-evidence.ts
|
|
5676
|
-
import { existsSync as
|
|
5677
|
-
import { join as
|
|
6020
|
+
import { existsSync as existsSync23, readdirSync as readdirSync3, readFileSync as readFileSync23 } from "fs";
|
|
6021
|
+
import { join as join27 } from "path";
|
|
5678
6022
|
var PATH_SIGNALS = [
|
|
5679
6023
|
{
|
|
5680
6024
|
preset: "nest",
|
|
@@ -5688,11 +6032,11 @@ var DEPENDENCY_SIGNALS = [
|
|
|
5688
6032
|
{ preset: "nest", pattern: /^@nestjs\// }
|
|
5689
6033
|
];
|
|
5690
6034
|
function dependencyNames(cwd) {
|
|
5691
|
-
const path =
|
|
5692
|
-
if (!
|
|
6035
|
+
const path = join27(cwd, "package.json");
|
|
6036
|
+
if (!existsSync23(path)) return [];
|
|
5693
6037
|
try {
|
|
5694
6038
|
const manifest = parseJsonc(
|
|
5695
|
-
|
|
6039
|
+
readFileSync23(path, "utf8"),
|
|
5696
6040
|
path
|
|
5697
6041
|
);
|
|
5698
6042
|
return [
|
|
@@ -5715,7 +6059,7 @@ function declaresJsx(cwd) {
|
|
|
5715
6059
|
for (const name of entries) {
|
|
5716
6060
|
try {
|
|
5717
6061
|
const config = parseJsonc(
|
|
5718
|
-
|
|
6062
|
+
readFileSync23(join27(cwd, name), "utf8"),
|
|
5719
6063
|
name
|
|
5720
6064
|
);
|
|
5721
6065
|
if (config.compilerOptions?.jsx !== void 0) return true;
|
|
@@ -5812,13 +6156,26 @@ async function dispatch(opts) {
|
|
|
5812
6156
|
const resolutionPreset = opts.preset ?? declaredPresetFor(opts.target, opts.cwd) ?? detected;
|
|
5813
6157
|
const adapter = resolve(opts.target, resolutionPreset, opts.runner);
|
|
5814
6158
|
const preset = opts.preset ?? adapter.declaredPreset?.(opts.cwd) ?? detected;
|
|
5815
|
-
|
|
5816
|
-
|
|
5817
|
-
|
|
6159
|
+
if (opts.preset !== void 0) {
|
|
6160
|
+
const contradiction = presetContradiction(opts.preset, opts.cwd);
|
|
6161
|
+
if (contradiction !== void 0) {
|
|
6162
|
+
process.stderr.write(`sentinel (${opts.target}): ${contradiction}
|
|
5818
6163
|
`);
|
|
5819
|
-
|
|
6164
|
+
return 1;
|
|
6165
|
+
}
|
|
6166
|
+
}
|
|
6167
|
+
let effective = preset;
|
|
6168
|
+
if (opts.preset === void 0 && adapter.declaredPreset?.(opts.cwd) === void 0) {
|
|
6169
|
+
const [signal] = presetSignals(opts.cwd);
|
|
6170
|
+
if (signal !== void 0 && signal.preset !== preset) {
|
|
6171
|
+
effective = signal.preset;
|
|
6172
|
+
process.stderr.write(
|
|
6173
|
+
` detected "${preset}" from dependencies, but ${signal.evidence}, so adopting as "${signal.preset}". Pass --preset to override.
|
|
6174
|
+
`
|
|
6175
|
+
);
|
|
6176
|
+
}
|
|
5820
6177
|
}
|
|
5821
|
-
const context = { cwd: opts.cwd, preset };
|
|
6178
|
+
const context = { cwd: opts.cwd, preset: effective };
|
|
5822
6179
|
const plan3 = await adapter.plan(context);
|
|
5823
6180
|
if (plan3.blocked) {
|
|
5824
6181
|
process.stderr.write(`sentinel (${opts.target}): ${plan3.blocked}
|
|
@@ -5843,7 +6200,7 @@ async function dispatch(opts) {
|
|
|
5843
6200
|
process.stderr.write(` fixing what ${opts.target} can fix automatically...
|
|
5844
6201
|
`);
|
|
5845
6202
|
await adapter.afterInit({
|
|
5846
|
-
module:
|
|
6203
|
+
module: basename3(opts.cwd),
|
|
5847
6204
|
cwd: opts.cwd,
|
|
5848
6205
|
preset,
|
|
5849
6206
|
ci: false,
|
|
@@ -5866,18 +6223,19 @@ export {
|
|
|
5866
6223
|
readOwnVersion,
|
|
5867
6224
|
readProjectPackageJson,
|
|
5868
6225
|
readNxProjectName,
|
|
6226
|
+
VERBS,
|
|
6227
|
+
TARGETS,
|
|
6228
|
+
LONG_RUNNING_TARGETS,
|
|
6229
|
+
SWEEPABLE_TARGETS,
|
|
6230
|
+
PRESET_NAMES,
|
|
5869
6231
|
WORKSPACE_ROOT_MARKER,
|
|
5870
6232
|
findWorkspaceRoot,
|
|
5871
6233
|
ensureWorkspacePrep,
|
|
5872
6234
|
inspectWorkspacePrep,
|
|
5873
6235
|
palette,
|
|
5874
|
-
VERBS,
|
|
5875
|
-
TARGETS,
|
|
5876
|
-
SWEEPABLE_TARGETS,
|
|
5877
|
-
PRESET_NAMES,
|
|
5878
6236
|
registerAdapters,
|
|
5879
6237
|
describeFramework,
|
|
5880
6238
|
detectFramework,
|
|
5881
6239
|
dispatch
|
|
5882
6240
|
};
|
|
5883
|
-
//# sourceMappingURL=chunk-
|
|
6241
|
+
//# sourceMappingURL=chunk-SA2RMTVI.js.map
|