@hublo/sentinel 1.2.0-alpha.0 → 1.2.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/sentinel.js +2 -2
- package/dist/{chunk-TWL6T237.js → chunk-FLJ2QNFF.js} +621 -378
- package/dist/chunk-XVDOQ3G3.js +39 -0
- package/dist/index.js +2 -2
- package/dist/roles/build/react-app.d.ts +3 -7
- package/dist/roles/build/react-app.js +25 -10
- package/package.json +1 -1
- package/dist/chunk-XSCDJZDY.js +0 -26
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
REACT_APP_DEFAULTS,
|
|
3
|
+
REQUIREMENTS,
|
|
3
4
|
isPlainObject
|
|
4
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-XVDOQ3G3.js";
|
|
5
6
|
|
|
6
7
|
// src/core/registry.ts
|
|
7
8
|
var adapters = [];
|
|
@@ -133,20 +134,6 @@ function readNxProjectName(dir) {
|
|
|
133
134
|
}
|
|
134
135
|
}
|
|
135
136
|
|
|
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
137
|
// src/core/domain.ts
|
|
151
138
|
var VERBS = ["run", "inspect", "init", "migrate", "report", "status"];
|
|
152
139
|
var TARGETS = [
|
|
@@ -166,7 +153,21 @@ var SWEEPABLE_TARGETS = TARGETS.filter(
|
|
|
166
153
|
);
|
|
167
154
|
var PRESET_NAMES = ["react", "nest", "svelte", "node", "tools"];
|
|
168
155
|
|
|
169
|
-
// src/
|
|
156
|
+
// src/shared/color.ts
|
|
157
|
+
import { styleText } from "util";
|
|
158
|
+
function palette(stream) {
|
|
159
|
+
const paint = (format, text) => styleText(format, text, { stream });
|
|
160
|
+
return {
|
|
161
|
+
ok: (text) => paint(["green", "bold"], text),
|
|
162
|
+
fail: (text) => paint(["red", "bold"], text),
|
|
163
|
+
warn: (text) => paint("yellow", text),
|
|
164
|
+
strong: (text) => paint("bold", text),
|
|
165
|
+
dim: (text) => paint("dim", text),
|
|
166
|
+
accent: (text) => paint("cyan", text)
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// src/roles/build/adapters/vite/vite-role.adapter.ts
|
|
170
171
|
import { spawnSync } from "child_process";
|
|
171
172
|
|
|
172
173
|
// src/core/config/tool-args.ts
|
|
@@ -206,9 +207,9 @@ function binFromOwnInstall(packageName, binName) {
|
|
|
206
207
|
try {
|
|
207
208
|
const manifest = require2.resolve(`${packageName}/package.json`);
|
|
208
209
|
const bin = require2(manifest).bin;
|
|
209
|
-
const
|
|
210
|
-
if (!
|
|
211
|
-
const executable = join2(dirname2(manifest),
|
|
210
|
+
const relative4 = typeof bin === "string" ? bin : bin?.[binName];
|
|
211
|
+
if (!relative4) return void 0;
|
|
212
|
+
const executable = join2(dirname2(manifest), relative4);
|
|
212
213
|
return existsSync3(executable) ? executable : void 0;
|
|
213
214
|
} catch {
|
|
214
215
|
return void 0;
|
|
@@ -399,7 +400,7 @@ function buildTarget() {
|
|
|
399
400
|
return {
|
|
400
401
|
[BUILD_SCRIPT_NAME]: {
|
|
401
402
|
cache: true,
|
|
402
|
-
inputs: ["default", "^default", `{projectRoot}/${
|
|
403
|
+
inputs: ["default", "^default", ...BUILD_CONFIG_FILES.map((name) => `{projectRoot}/${name}`)]
|
|
403
404
|
}
|
|
404
405
|
};
|
|
405
406
|
}
|
|
@@ -417,17 +418,17 @@ var VITE_TOKEN = /(^|\s|\/)vite(\s|$)/;
|
|
|
417
418
|
var DEV_SUBCOMMANDS = /* @__PURE__ */ new Set(["dev", "serve"]);
|
|
418
419
|
function appOptions(rest) {
|
|
419
420
|
const tokens = (rest ?? "").trim().split(/\s+/).filter(Boolean);
|
|
420
|
-
const kept = tokens.filter((token,
|
|
421
|
+
const kept = tokens.filter((token, at2) => !(at2 === 0 && DEV_SUBCOMMANDS.has(token)));
|
|
421
422
|
return kept.join(" ");
|
|
422
423
|
}
|
|
423
424
|
function composeDevScript(existing, command) {
|
|
424
425
|
if (existing === void 0 || existing.trim() === "") return command;
|
|
425
426
|
const chunks = existing.trim().split(CHAIN);
|
|
426
|
-
const
|
|
427
|
-
if (
|
|
428
|
-
const rest = (chunks[
|
|
427
|
+
const at2 = chunks.map((chunk) => VITE_TOKEN.test(chunk)).lastIndexOf(true);
|
|
428
|
+
if (at2 === -1) return existing;
|
|
429
|
+
const rest = (chunks[at2] ?? "").replace(/^.*?(^|\s|\/)vite(\s|$)/, "");
|
|
429
430
|
const options = appOptions(rest);
|
|
430
|
-
chunks[
|
|
431
|
+
chunks[at2] = options === "" ? command : `${command} -- ${options}`;
|
|
431
432
|
return chunks.join(CHAIN);
|
|
432
433
|
}
|
|
433
434
|
|
|
@@ -446,17 +447,24 @@ var NOT_ADOPTED = (configFile, unreadable = null) => ({
|
|
|
446
447
|
function buildConfigFile(cwd) {
|
|
447
448
|
return BUILD_CONFIG_FILES.find((name) => existsSync6(join6(cwd, name)));
|
|
448
449
|
}
|
|
450
|
+
function importsSpecifier(source, specifier) {
|
|
451
|
+
const escaped = specifier.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
452
|
+
return new RegExp(`(?:from|import)\\s*\\(?\\s*['"\`]${escaped}(?:/[^'"\`]*)?['"\`]`).test(source);
|
|
453
|
+
}
|
|
449
454
|
function importsPreset(source) {
|
|
450
|
-
|
|
451
|
-
return new RegExp(`(?:from|import)\\s*\\(?\\s*['"\`]${specifier}['"\`]`).test(source);
|
|
455
|
+
return importsSpecifier(source, BUILD_PRESET_SPECIFIER);
|
|
452
456
|
}
|
|
453
457
|
var REACT_PLUGIN_SPECIFIERS = ["@vitejs/plugin-react", "@tanstack/react-start"];
|
|
454
458
|
function declaredBuildPreset(cwd) {
|
|
459
|
+
const source = readBuildConfigSource(cwd);
|
|
460
|
+
if (source === void 0) return void 0;
|
|
461
|
+
return REACT_PLUGIN_SPECIFIERS.some((specifier) => importsSpecifier(source, specifier)) ? "react" : void 0;
|
|
462
|
+
}
|
|
463
|
+
function readBuildConfigSource(cwd) {
|
|
455
464
|
const configFile = buildConfigFile(cwd);
|
|
456
465
|
if (!configFile) return void 0;
|
|
457
466
|
try {
|
|
458
|
-
|
|
459
|
-
return REACT_PLUGIN_SPECIFIERS.some((specifier) => source.includes(specifier)) ? "react" : void 0;
|
|
467
|
+
return readFileSync4(join6(cwd, configFile), "utf8");
|
|
460
468
|
} catch {
|
|
461
469
|
return void 0;
|
|
462
470
|
}
|
|
@@ -489,6 +497,34 @@ function readBuildAdoption(cwd) {
|
|
|
489
497
|
};
|
|
490
498
|
}
|
|
491
499
|
|
|
500
|
+
// src/roles/build/requirements.ts
|
|
501
|
+
function atLeast(declared, needed) {
|
|
502
|
+
const parse2 = (value) => value.replace(/^[\^~>=<\s]+/, "").split("-")[0].split(".").map((part) => Number.parseInt(part, 10) || 0);
|
|
503
|
+
const left = parse2(declared);
|
|
504
|
+
const right = parse2(needed);
|
|
505
|
+
for (let at2 = 0; at2 < Math.max(left.length, right.length); at2++) {
|
|
506
|
+
const a = left[at2] ?? 0;
|
|
507
|
+
const b = right[at2] ?? 0;
|
|
508
|
+
if (a !== b) return a > b;
|
|
509
|
+
}
|
|
510
|
+
return true;
|
|
511
|
+
}
|
|
512
|
+
function unmetRequirements(preset, declared) {
|
|
513
|
+
const entry = REQUIREMENTS[preset];
|
|
514
|
+
if (!entry) return [];
|
|
515
|
+
return Object.entries(entry.requires).filter(([name, needed]) => {
|
|
516
|
+
const have = declared[name];
|
|
517
|
+
return have === void 0 || !atLeast(have, needed);
|
|
518
|
+
}).map(([name, needed]) => ({ name, declared: declared[name], needed }));
|
|
519
|
+
}
|
|
520
|
+
function describeUnmet(preset, unmet) {
|
|
521
|
+
const entry = REQUIREMENTS[preset];
|
|
522
|
+
const list = unmet.map(
|
|
523
|
+
({ name, declared, needed }) => `${name} ${declared === void 0 ? "is not declared here" : `is at ${declared}`}, needs >= ${needed}`
|
|
524
|
+
).join("; ");
|
|
525
|
+
return `the build role does not apply to this module yet: ${list}. ${entry?.why ?? ""}. Nothing was written. Raise those versions, check the module still builds and its tests still pass, then re-run \`sentinel --init --build\`.`;
|
|
526
|
+
}
|
|
527
|
+
|
|
492
528
|
// src/roles/build/plan.ts
|
|
493
529
|
function scaffold() {
|
|
494
530
|
return `import { defineConfig, reactApp } from '${BUILD_PRESET_SPECIFIER}'
|
|
@@ -497,8 +533,8 @@ import { tanstackRouter } from '@tanstack/router-plugin/vite'
|
|
|
497
533
|
import path from 'node:path'
|
|
498
534
|
|
|
499
535
|
/*
|
|
500
|
-
* Composition comes from sentinel:
|
|
501
|
-
*
|
|
536
|
+
* Composition comes from sentinel: which plugins run under test and which under a real build,
|
|
537
|
+
* the order they run in, and how the env is read. The values below are this app's own.
|
|
502
538
|
*
|
|
503
539
|
* TanStack is passed IN rather than imported by sentinel, because the app codes against it
|
|
504
540
|
* directly. \`alias\` is passed through verbatim and never read, so nothing in it can be lost.
|
|
@@ -520,6 +556,10 @@ export default defineConfig(({ mode }) =>
|
|
|
520
556
|
alias: [],
|
|
521
557
|
}),
|
|
522
558
|
)
|
|
559
|
+
|
|
560
|
+
// If this app reads environment variables, use \`appEnv(__dirname, mode)\` rather than Vite's
|
|
561
|
+
// \`loadEnv\`: it applies the production -> prd normalisation this repo deploys under, which
|
|
562
|
+
// every app was otherwise retyping.
|
|
523
563
|
`;
|
|
524
564
|
}
|
|
525
565
|
function ownedBuildDependencies(cwd) {
|
|
@@ -558,10 +598,18 @@ function buildScripts(cwd) {
|
|
|
558
598
|
return scripts;
|
|
559
599
|
}
|
|
560
600
|
function plan(context) {
|
|
601
|
+
const manifest = readProjectPackageJson(context.cwd);
|
|
602
|
+
const unmet = unmetRequirements(context.preset, {
|
|
603
|
+
...manifest.dependencies,
|
|
604
|
+
...manifest.devDependencies
|
|
605
|
+
});
|
|
606
|
+
if (unmet.length > 0) {
|
|
607
|
+
return { operations: [], skipped: describeUnmet(context.preset, unmet) };
|
|
608
|
+
}
|
|
561
609
|
if (declaredBuildPreset(context.cwd) === void 0 && buildConfigFile(context.cwd) !== void 0) {
|
|
562
610
|
return {
|
|
563
611
|
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
|
|
612
|
+
skipped: `this module has a Vite config, but it does not build a React app (no @vitejs/plugin-react or @tanstack/react-start in it). The build role ships a React preset only.`
|
|
565
613
|
};
|
|
566
614
|
}
|
|
567
615
|
const notes = [];
|
|
@@ -598,83 +646,150 @@ function readsPreset(cwd, configFile) {
|
|
|
598
646
|
}
|
|
599
647
|
|
|
600
648
|
// src/roles/build/resolve-vite.ts
|
|
601
|
-
function
|
|
602
|
-
|
|
649
|
+
function resolveViteWith(cwd) {
|
|
650
|
+
const own = binFromOwnInstall("vite", "vite");
|
|
651
|
+
if (own) return { bin: own, origin: "sentinel" };
|
|
652
|
+
const module_ = resolveBin(cwd, "vite");
|
|
653
|
+
return module_ ? { bin: module_, origin: "module" } : { bin: void 0, origin: "none" };
|
|
603
654
|
}
|
|
604
655
|
function viteOrigin(cwd) {
|
|
605
|
-
|
|
606
|
-
return resolveBin(cwd, "vite") ? "module" : "none";
|
|
656
|
+
return resolveViteWith(cwd).origin;
|
|
607
657
|
}
|
|
608
658
|
|
|
609
|
-
// src/roles/build/adapters/vite/vite-
|
|
610
|
-
var
|
|
611
|
-
|
|
659
|
+
// src/roles/build/adapters/vite/vite-role.adapter.ts
|
|
660
|
+
var VITE_VALUE_FLAGS = [
|
|
661
|
+
"--config",
|
|
662
|
+
"-c",
|
|
663
|
+
"--mode",
|
|
664
|
+
"-m",
|
|
665
|
+
"--outDir",
|
|
666
|
+
"--logLevel",
|
|
667
|
+
"--base",
|
|
668
|
+
"--host",
|
|
669
|
+
"--port"
|
|
670
|
+
];
|
|
671
|
+
var ViteRoleAdapter = class extends BaseAdapter {
|
|
612
672
|
runner = "vite";
|
|
673
|
+
/**
|
|
674
|
+
* The presets this role has something to SAY about, which is wider than the ones it serves.
|
|
675
|
+
*
|
|
676
|
+
* React it serves. Svelte it does not, yet, and the difference matters to a developer: the
|
|
677
|
+
* plugin caps at Vite 6 until its major 7, which needs `svelte ^5.46.4`, so the role applies
|
|
678
|
+
* once the module moves. `plan` reports that chain and SKIPS, which it can only do if
|
|
679
|
+
* resolution let it run at all — filtering here would give "no adapter for preset svelte",
|
|
680
|
+
* which tells nobody what to do about it.
|
|
681
|
+
*
|
|
682
|
+
* Nest, node and tools are genuinely declined: there is nothing to bundle, and no version
|
|
683
|
+
* would change that.
|
|
684
|
+
*/
|
|
613
685
|
appliesTo(preset) {
|
|
614
|
-
return preset === "react";
|
|
686
|
+
return preset === "react" || preset === "svelte";
|
|
615
687
|
}
|
|
688
|
+
/**
|
|
689
|
+
* `react`, read from the module's Vite config rather than from its dependencies.
|
|
690
|
+
*
|
|
691
|
+
* Without this the role is unreachable in practice: the toolchain is declared at the ROOT,
|
|
692
|
+
* so every front app detects as `node` and `appliesTo` filters the adapter out — including
|
|
693
|
+
* during `sentinel --inspect` with no target, which never passes `--preset`.
|
|
694
|
+
*/
|
|
616
695
|
declaredPreset(cwd) {
|
|
617
696
|
return declaredBuildPreset(cwd);
|
|
618
697
|
}
|
|
619
698
|
/**
|
|
620
|
-
* The same plan
|
|
699
|
+
* The same plan for both targets, deliberately.
|
|
621
700
|
*
|
|
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.
|
|
701
|
+
* `build` and `serve` are one app's two calls into one toolchain, so adopting one without the
|
|
702
|
+
* other leaves the module half-migrated in a way nothing reports. The operations are
|
|
703
|
+
* idempotent, so `--init --build` and `--init --dev` are interchangeable rather than additive.
|
|
626
704
|
*/
|
|
627
705
|
plan(context) {
|
|
628
706
|
return plan(context);
|
|
629
707
|
}
|
|
708
|
+
async status(ctx) {
|
|
709
|
+
const { adopted, preset, conformant, drift, unreadable } = readBuildAdoption(ctx.cwd);
|
|
710
|
+
return { adopted, preset, conformant, drift, unreadable };
|
|
711
|
+
}
|
|
630
712
|
/**
|
|
631
|
-
*
|
|
632
|
-
* beyond the exit code the developer's own Ctrl-C produces.
|
|
713
|
+
* Adoption, the binary, and the warning about which copy of Vite is running.
|
|
633
714
|
*
|
|
634
|
-
*
|
|
635
|
-
*
|
|
636
|
-
*
|
|
715
|
+
* A module that has not adopted is reported and PASSES, the same stance every other role
|
|
716
|
+
* takes: `--run` with no named target sweeps every wired target across the workspace, so
|
|
717
|
+
* failing here would exit non-zero on every module that has not migrated, which is most of
|
|
718
|
+
* them.
|
|
637
719
|
*/
|
|
638
|
-
|
|
720
|
+
prepare(ctx) {
|
|
639
721
|
if (!readBuildAdoption(ctx.cwd).adopted) {
|
|
640
|
-
|
|
641
|
-
`
|
|
642
|
-
`
|
|
722
|
+
this.say(
|
|
723
|
+
`no sentinel preset in this module's Vite config; run \`sentinel --init --build\` to adopt.`
|
|
643
724
|
);
|
|
644
|
-
return { ok: true, code: 0 };
|
|
725
|
+
return { kind: "stop", result: { ok: true, code: 0 } };
|
|
645
726
|
}
|
|
646
|
-
const
|
|
647
|
-
if (!
|
|
648
|
-
|
|
649
|
-
`
|
|
650
|
-
`
|
|
727
|
+
const { bin, origin } = resolveViteWith(ctx.cwd);
|
|
728
|
+
if (!bin) {
|
|
729
|
+
this.say(
|
|
730
|
+
`could not find the vite binary (looked in ${binSearchPath(ctx.cwd)}). Run \`pnpm install\` in the module.`
|
|
651
731
|
);
|
|
652
|
-
return { ok: false, code: 1 };
|
|
732
|
+
return { kind: "stop", result: { ok: false, code: 1 } };
|
|
653
733
|
}
|
|
654
|
-
if (
|
|
655
|
-
|
|
656
|
-
`
|
|
657
|
-
`
|
|
734
|
+
if (origin === "module") {
|
|
735
|
+
this.say(
|
|
736
|
+
`using the MODULE's vite, not sentinel's. An adopted config gets its plugins from sentinel, and two copies of Vite fail in ways that never mention a version. Remove vite from this module's package.json.`
|
|
658
737
|
);
|
|
659
738
|
}
|
|
660
|
-
|
|
661
|
-
|
|
739
|
+
return { kind: "ready", vite: bin };
|
|
740
|
+
}
|
|
741
|
+
/**
|
|
742
|
+
* Run Vite with the caller's own arguments after `--`, and report what happened.
|
|
743
|
+
*
|
|
744
|
+
* `whenSignalled` is the only difference between the two targets here, and it is a real one:
|
|
745
|
+
* a build killed by a signal did not produce a bundle, while a dev server killed by Ctrl-C
|
|
746
|
+
* did exactly what the developer asked.
|
|
747
|
+
*/
|
|
748
|
+
spawnVite(ctx, vite, argv, whenSignalled) {
|
|
749
|
+
const { options, paths } = splitToolArgs(ctx.cwd, ctx.toolArgs, {
|
|
750
|
+
valueFlags: VITE_VALUE_FLAGS
|
|
751
|
+
});
|
|
752
|
+
const result = spawnSync(vite, [...argv, ...options, ...paths], {
|
|
753
|
+
cwd: ctx.cwd,
|
|
754
|
+
stdio: "inherit"
|
|
755
|
+
});
|
|
662
756
|
if (result.error) {
|
|
663
|
-
|
|
664
|
-
`);
|
|
757
|
+
this.say(`could not run vite (${result.error.message})`);
|
|
665
758
|
return { ok: false, code: 1 };
|
|
666
759
|
}
|
|
667
|
-
const code = result.status ??
|
|
760
|
+
const code = result.status ?? whenSignalled;
|
|
668
761
|
return { ok: code === 0, code };
|
|
669
762
|
}
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
763
|
+
/** One prefix for every message this role emits, so they cannot drift apart. */
|
|
764
|
+
say(message) {
|
|
765
|
+
process.stderr.write(`sentinel ${this.label}(vite): ${message}
|
|
766
|
+
`);
|
|
673
767
|
}
|
|
674
768
|
};
|
|
675
769
|
|
|
676
|
-
// src/roles/build/adapters/vite/vite.adapter.ts
|
|
677
|
-
|
|
770
|
+
// src/roles/build/adapters/vite/vite-dev.adapter.ts
|
|
771
|
+
var ViteDevAdapter = class extends ViteRoleAdapter {
|
|
772
|
+
target = "dev";
|
|
773
|
+
get label() {
|
|
774
|
+
return "dev";
|
|
775
|
+
}
|
|
776
|
+
/**
|
|
777
|
+
* Start the dev server. It does not return until stopped, so there is no verdict to report
|
|
778
|
+
* beyond the exit code the developer's own Ctrl-C produces.
|
|
779
|
+
*
|
|
780
|
+
* No `prebuild` here, unlike `--run --build`: the artefact this repo generates before a build
|
|
781
|
+
* is produced by the watcher that WRAPS this command (`run-with-runtime-artifact-watch`), and
|
|
782
|
+
* running it again would race the watcher that is about to own the file.
|
|
783
|
+
*
|
|
784
|
+
* No subcommand either. `vite`, `vite dev` and `vite serve` all start the server, and the
|
|
785
|
+
* bare form is the one every version accepts.
|
|
786
|
+
*/
|
|
787
|
+
async run(ctx) {
|
|
788
|
+
const ready = this.prepare(ctx);
|
|
789
|
+
if (ready.kind === "stop") return ready.result;
|
|
790
|
+
return this.spawnVite(ctx, ready.vite, [], 0);
|
|
791
|
+
}
|
|
792
|
+
};
|
|
678
793
|
|
|
679
794
|
// src/roles/build/owned-paths.ts
|
|
680
795
|
var PRESET_OWNED_KEYS = [
|
|
@@ -683,6 +798,7 @@ var PRESET_OWNED_KEYS = [
|
|
|
683
798
|
"define",
|
|
684
799
|
"server",
|
|
685
800
|
"build",
|
|
801
|
+
"nitro",
|
|
686
802
|
"resolve",
|
|
687
803
|
"plugins"
|
|
688
804
|
];
|
|
@@ -714,7 +830,7 @@ function asRecord(value) {
|
|
|
714
830
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
715
831
|
}
|
|
716
832
|
function sameStrings(a, b) {
|
|
717
|
-
return a.length === b.length && a.every((value,
|
|
833
|
+
return a.length === b.length && a.every((value, at2) => value === b[at2]);
|
|
718
834
|
}
|
|
719
835
|
|
|
720
836
|
// src/roles/build/prerequisite.ts
|
|
@@ -761,83 +877,28 @@ async function resolveBuildConfig(cwd) {
|
|
|
761
877
|
}
|
|
762
878
|
|
|
763
879
|
// src/roles/build/adapters/vite/vite.adapter.ts
|
|
764
|
-
var
|
|
765
|
-
var ViteAdapter = class extends BaseAdapter {
|
|
880
|
+
var ViteAdapter = class extends ViteRoleAdapter {
|
|
766
881
|
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);
|
|
882
|
+
get label() {
|
|
883
|
+
return "build";
|
|
787
884
|
}
|
|
788
885
|
/**
|
|
789
886
|
* Build the module.
|
|
790
887
|
*
|
|
791
|
-
*
|
|
792
|
-
*
|
|
793
|
-
* failing here would exit non-zero on every module that has not migrated, which is most of
|
|
794
|
-
* them.
|
|
888
|
+
* Adoption, the binary and the two-copies warning are `prepare`'s, shared with `--dev`. What
|
|
889
|
+
* is this target's own is the `prebuild` step and the `build` subcommand.
|
|
795
890
|
*/
|
|
796
891
|
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
|
-
}
|
|
892
|
+
const ready = this.prepare(ctx);
|
|
893
|
+
if (ready.kind === "stop") return ready.result;
|
|
805
894
|
const prerequisite = runPrerequisite(ctx.cwd);
|
|
806
895
|
if (prerequisite.kind === "failed") {
|
|
807
|
-
|
|
808
|
-
`
|
|
809
|
-
`
|
|
896
|
+
this.say(
|
|
897
|
+
`the ${PREBUILD_SCRIPT_NAME} step failed, so the build was not started: ${prerequisite.reason}`
|
|
810
898
|
);
|
|
811
899
|
return { ok: false, code: prerequisite.code };
|
|
812
900
|
}
|
|
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 };
|
|
901
|
+
return this.spawnVite(ctx, ready.vite, ["build"], 1);
|
|
841
902
|
}
|
|
842
903
|
/**
|
|
843
904
|
* What this module builds with, without building it.
|
|
@@ -883,17 +944,19 @@ var ViteAdapter = class extends BaseAdapter {
|
|
|
883
944
|
departures: describeDepartures(config, REACT_APP_DEFAULTS),
|
|
884
945
|
additions: appAdditions(config),
|
|
885
946
|
resolved: {
|
|
886
|
-
base: config
|
|
887
|
-
target: config
|
|
888
|
-
sourcemap: config
|
|
889
|
-
port: config
|
|
947
|
+
base: at(config, "base") ?? null,
|
|
948
|
+
target: at(config, "build", "target") ?? null,
|
|
949
|
+
sourcemap: at(config, "build", "sourcemap") ?? null,
|
|
950
|
+
port: at(config, "server", "port") ?? null,
|
|
890
951
|
// The one number nothing else reports and that no formula predicts: career goes DOWN
|
|
891
952
|
// to 9998 where the others go up, which is why it is data rather than `port + 1`.
|
|
892
|
-
hmrPort: config
|
|
953
|
+
hmrPort: at(config, "server", "hmr", "port") ?? null,
|
|
893
954
|
// Counted, not listed. It is 56-67% of every config today, and printing 296 entries
|
|
894
955
|
// would bury everything above it. The count is what tells you whether it moved.
|
|
895
|
-
aliases:
|
|
896
|
-
|
|
956
|
+
aliases: countOf(at(config, "resolve", "alias")),
|
|
957
|
+
// Flattened without a depth limit: Vite lets a plugin be an arbitrarily nested array,
|
|
958
|
+
// and a magic number here would silently undercount the day someone nests one deeper.
|
|
959
|
+
plugins: countOf(at(config, "plugins"))
|
|
897
960
|
}
|
|
898
961
|
};
|
|
899
962
|
}
|
|
@@ -920,11 +983,18 @@ var ViteAdapter = class extends BaseAdapter {
|
|
|
920
983
|
}
|
|
921
984
|
};
|
|
922
985
|
}
|
|
923
|
-
async status(ctx) {
|
|
924
|
-
const { adopted, preset, conformant, drift, unreadable } = readBuildAdoption(ctx.cwd);
|
|
925
|
-
return { adopted, preset, conformant, drift, unreadable };
|
|
926
|
-
}
|
|
927
986
|
};
|
|
987
|
+
function at(config, ...path) {
|
|
988
|
+
let value = config;
|
|
989
|
+
for (const key of path) {
|
|
990
|
+
if (typeof value !== "object" || value === null) return void 0;
|
|
991
|
+
value = value[key];
|
|
992
|
+
}
|
|
993
|
+
return value;
|
|
994
|
+
}
|
|
995
|
+
function countOf(value) {
|
|
996
|
+
return Array.isArray(value) ? value.flat(Number.POSITIVE_INFINITY).length : 0;
|
|
997
|
+
}
|
|
928
998
|
|
|
929
999
|
// src/roles/build/register.ts
|
|
930
1000
|
function registerBuild() {
|
|
@@ -935,13 +1005,13 @@ function registerBuild() {
|
|
|
935
1005
|
}
|
|
936
1006
|
|
|
937
1007
|
// src/roles/format/adapters/oxfmt/oxfmt.adapter.ts
|
|
938
|
-
import { spawnSync as
|
|
1008
|
+
import { spawnSync as spawnSync3 } from "child_process";
|
|
939
1009
|
import { existsSync as existsSync12, readFileSync as readFileSync11 } from "fs";
|
|
940
1010
|
import { join as join14 } from "path";
|
|
941
1011
|
|
|
942
1012
|
// src/core/config/has-source.ts
|
|
943
1013
|
import { readdirSync } from "fs";
|
|
944
|
-
import { extname, join as join8 } from "path";
|
|
1014
|
+
import { extname, join as join8, relative } from "path";
|
|
945
1015
|
var SKIP_DIRECTORIES = /* @__PURE__ */ new Set([
|
|
946
1016
|
"node_modules",
|
|
947
1017
|
"dist",
|
|
@@ -1000,6 +1070,35 @@ function hasSourceFiles(cwd, extensions) {
|
|
|
1000
1070
|
}
|
|
1001
1071
|
return false;
|
|
1002
1072
|
}
|
|
1073
|
+
function listSourceFiles(cwd, extensions) {
|
|
1074
|
+
const wanted = new Set(extensions);
|
|
1075
|
+
const queue = [cwd];
|
|
1076
|
+
const found = [];
|
|
1077
|
+
while (queue.length > 0) {
|
|
1078
|
+
const dir = queue.pop();
|
|
1079
|
+
let entries;
|
|
1080
|
+
try {
|
|
1081
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
1082
|
+
} catch {
|
|
1083
|
+
continue;
|
|
1084
|
+
}
|
|
1085
|
+
for (const entry of entries) {
|
|
1086
|
+
const full = join8(dir, entry.name);
|
|
1087
|
+
if (entry.isDirectory()) {
|
|
1088
|
+
if (!SKIP_DIRECTORIES.has(entry.name)) queue.push(full);
|
|
1089
|
+
continue;
|
|
1090
|
+
}
|
|
1091
|
+
if (wanted.has(extname(entry.name))) found.push(relative(cwd, full));
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
return found;
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
// src/core/named-scope.ts
|
|
1098
|
+
var TOLERATE_EMPTY_FLAG = "--no-error-on-unmatched-pattern";
|
|
1099
|
+
function scopeFlags(paths) {
|
|
1100
|
+
return paths.length > 0 ? [TOLERATE_EMPTY_FLAG] : [];
|
|
1101
|
+
}
|
|
1003
1102
|
|
|
1004
1103
|
// src/core/settings.ts
|
|
1005
1104
|
var WORKSPACE_ROOT_MARKER = "nx.json";
|
|
@@ -1007,7 +1106,7 @@ var DEFAULT_MAX_DIAGNOSTICS = 100;
|
|
|
1007
1106
|
|
|
1008
1107
|
// src/core/workspace-prep.ts
|
|
1009
1108
|
import { existsSync as existsSync8, readFileSync as readFileSync6, writeFileSync } from "fs";
|
|
1010
|
-
import { dirname as dirname3, join as join9, relative } from "path";
|
|
1109
|
+
import { dirname as dirname3, join as join9, relative as relative2 } from "path";
|
|
1011
1110
|
var OVERRIDE_KEY = "i18next>typescript";
|
|
1012
1111
|
var NATIVE_TS_ALIAS = "@typescript/native";
|
|
1013
1112
|
var WORKSPACE_YAML = "pnpm-workspace.yaml";
|
|
@@ -1080,7 +1179,7 @@ var ROOT_PRETTIER_CONFIGS = [
|
|
|
1080
1179
|
"prettier.config.mjs"
|
|
1081
1180
|
];
|
|
1082
1181
|
function ensureFormatterExclusion(root, moduleDir, dryRun) {
|
|
1083
|
-
const rel =
|
|
1182
|
+
const rel = relative2(root, moduleDir).replaceAll("\\", "/");
|
|
1084
1183
|
if (rel === "" || rel.startsWith("..")) return void 0;
|
|
1085
1184
|
const ignorePath = join9(root, PRETTIER_IGNORE);
|
|
1086
1185
|
const hasPrettier = existsSync8(ignorePath) || ROOT_PRETTIER_CONFIGS.some((name) => existsSync8(join9(root, name)));
|
|
@@ -1527,6 +1626,22 @@ var OxfmtAdapter = class extends BaseAdapter {
|
|
|
1527
1626
|
appliesTo(_preset) {
|
|
1528
1627
|
return true;
|
|
1529
1628
|
}
|
|
1629
|
+
/**
|
|
1630
|
+
* The preset this module COMMITTED to, read from the `$sentinel` block it writes.
|
|
1631
|
+
*
|
|
1632
|
+
* Every other role implements this and format did not, so it was the only one answering from
|
|
1633
|
+
* dependency detection alone. On an adopted Nest module whose own `.oxfmtrc.json` records
|
|
1634
|
+
* `"preset": "nest"`, `--inspect` reported `flavour=node` — the module knew, and the role did
|
|
1635
|
+
* not pass it on. Same command, four rows, two different answers about what the module is.
|
|
1636
|
+
*
|
|
1637
|
+
* It reads the RECORD rather than re-deriving: the block is what `--init` wrote, so it is the
|
|
1638
|
+
* module's declaration, and a re-derivation from hoisted dependencies is exactly the wrong
|
|
1639
|
+
* answer this exists to avoid.
|
|
1640
|
+
*/
|
|
1641
|
+
declaredPreset(cwd) {
|
|
1642
|
+
const declared = readFormatAdoption(cwd).preset;
|
|
1643
|
+
return declared !== null && PRESET_NAMES.includes(declared) ? declared : void 0;
|
|
1644
|
+
}
|
|
1530
1645
|
plan(context) {
|
|
1531
1646
|
if (!hasSourceFiles(context.cwd, FORMATTABLE_EXTENSIONS)) {
|
|
1532
1647
|
return {
|
|
@@ -1628,7 +1743,7 @@ var OxfmtAdapter = class extends BaseAdapter {
|
|
|
1628
1743
|
async afterInit(ctx) {
|
|
1629
1744
|
const oxfmt = resolveOxfmt(ctx.cwd);
|
|
1630
1745
|
if (!oxfmt) return;
|
|
1631
|
-
const pass =
|
|
1746
|
+
const pass = spawnSync3(oxfmt, ["--write", "."], { cwd: ctx.cwd, encoding: "utf8" });
|
|
1632
1747
|
process.stderr.write(` ${palette(process.stderr).dim(FORMATTER_DIFFERENCES)}
|
|
1633
1748
|
`);
|
|
1634
1749
|
if (pass.status === 0) return;
|
|
@@ -1691,10 +1806,14 @@ var OxfmtAdapter = class extends BaseAdapter {
|
|
|
1691
1806
|
const { options, paths } = splitToolArgs(ctx.cwd, ctx.toolArgs, {
|
|
1692
1807
|
valueFlags: OXFMT_VALUE_FLAGS
|
|
1693
1808
|
});
|
|
1694
|
-
const result =
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1809
|
+
const result = spawnSync3(
|
|
1810
|
+
oxfmt,
|
|
1811
|
+
[mode, ...scopeFlags(paths), ...options, ...paths.length > 0 ? paths : ["."]],
|
|
1812
|
+
{
|
|
1813
|
+
cwd: ctx.cwd,
|
|
1814
|
+
stdio: "inherit"
|
|
1815
|
+
}
|
|
1816
|
+
);
|
|
1698
1817
|
if (result.error) {
|
|
1699
1818
|
process.stderr.write(
|
|
1700
1819
|
`sentinel format(oxfmt): could not run oxfmt (${result.error.message})
|
|
@@ -1769,7 +1888,7 @@ var OxfmtAdapter = class extends BaseAdapter {
|
|
|
1769
1888
|
);
|
|
1770
1889
|
return { ok: false, code: 1, metrics: { ...base, unformatted: null } };
|
|
1771
1890
|
}
|
|
1772
|
-
const result =
|
|
1891
|
+
const result = spawnSync3(oxfmt, ["--list-different", "."], { cwd: ctx.cwd, encoding: "utf8" });
|
|
1773
1892
|
if (result.error) {
|
|
1774
1893
|
process.stderr.write(
|
|
1775
1894
|
`sentinel format(oxfmt): could not run oxfmt (${result.error.message})
|
|
@@ -1886,15 +2005,122 @@ function registerFormat() {
|
|
|
1886
2005
|
}
|
|
1887
2006
|
|
|
1888
2007
|
// src/roles/lint/adapters/oxlint/oxlint.adapter.ts
|
|
1889
|
-
import { spawnSync as
|
|
1890
|
-
import { existsSync as
|
|
1891
|
-
import { join as
|
|
2008
|
+
import { spawnSync as spawnSync4 } from "child_process";
|
|
2009
|
+
import { existsSync as existsSync20, readFileSync as readFileSync19, rmSync, writeFileSync as writeFileSync2 } from "fs";
|
|
2010
|
+
import { join as join24 } from "path";
|
|
1892
2011
|
|
|
1893
2012
|
// src/core/config/deferred-rules.ts
|
|
1894
2013
|
function deferredRuleNames(rules) {
|
|
1895
2014
|
return rules.map((entry) => entry.rule);
|
|
1896
2015
|
}
|
|
1897
2016
|
|
|
2017
|
+
// src/core/fix-report.ts
|
|
2018
|
+
import { statSync } from "fs";
|
|
2019
|
+
import { join as join15 } from "path";
|
|
2020
|
+
var CONFIG_REFUSED = /failed to parse .*config|invalid config file/i;
|
|
2021
|
+
function readFailure(output) {
|
|
2022
|
+
const lines = output.split("\n").map((line2) => line2.trim());
|
|
2023
|
+
if (!lines.some((line2) => CONFIG_REFUSED.test(line2))) return void 0;
|
|
2024
|
+
const detail = lines.find((line2) => /invalid config file/i.test(line2));
|
|
2025
|
+
const line = (detail ?? lines.find((entry) => entry !== "") ?? "").replace(/^x\s*/, "");
|
|
2026
|
+
return line.split(/:\s*Failed to parse config/i)[0] ?? line;
|
|
2027
|
+
}
|
|
2028
|
+
function fingerprint(path) {
|
|
2029
|
+
try {
|
|
2030
|
+
const stat = statSync(path);
|
|
2031
|
+
return `${stat.size}:${stat.mtimeMs}`;
|
|
2032
|
+
} catch {
|
|
2033
|
+
return void 0;
|
|
2034
|
+
}
|
|
2035
|
+
}
|
|
2036
|
+
function snapshotFiles(cwd, extensions) {
|
|
2037
|
+
const snapshot = /* @__PURE__ */ new Map();
|
|
2038
|
+
for (const file of listSourceFiles(cwd, extensions)) {
|
|
2039
|
+
const mark = fingerprint(join15(cwd, file));
|
|
2040
|
+
if (mark !== void 0) snapshot.set(file, mark);
|
|
2041
|
+
}
|
|
2042
|
+
return snapshot;
|
|
2043
|
+
}
|
|
2044
|
+
function changedSince(cwd, before, extensions) {
|
|
2045
|
+
const changed = [];
|
|
2046
|
+
for (const file of listSourceFiles(cwd, extensions)) {
|
|
2047
|
+
const now = fingerprint(join15(cwd, file));
|
|
2048
|
+
if (now === void 0) continue;
|
|
2049
|
+
if (before.get(file) !== now) changed.push(file);
|
|
2050
|
+
}
|
|
2051
|
+
return changed.sort();
|
|
2052
|
+
}
|
|
2053
|
+
var NAMED_IN_REPORT = 5;
|
|
2054
|
+
function describeFixOutcome(outcome, toolLabel) {
|
|
2055
|
+
if (outcome.failure !== void 0) {
|
|
2056
|
+
return `the ${toolLabel} autofix could not run, so nothing was fixed and this module has not been checked: ${outcome.failure}`;
|
|
2057
|
+
}
|
|
2058
|
+
if (outcome.changed.length === 0) return `the ${toolLabel} autofix changed nothing`;
|
|
2059
|
+
const named = outcome.changed.slice(0, NAMED_IN_REPORT).join(", ");
|
|
2060
|
+
const rest = outcome.changed.length - NAMED_IN_REPORT;
|
|
2061
|
+
const listed = rest > 0 ? `${named} and ${rest} more` : named;
|
|
2062
|
+
return `the ${toolLabel} autofix rewrote ${outcome.changed.length} file${outcome.changed.length === 1 ? "" : "s"}: ${listed}. Review the diff before committing: rules this module used to have switched off are enforced from now on.`;
|
|
2063
|
+
}
|
|
2064
|
+
|
|
2065
|
+
// src/roles/lint/module-baseline.ts
|
|
2066
|
+
import { existsSync as existsSync13, readFileSync as readFileSync12 } from "fs";
|
|
2067
|
+
import { join as join16 } from "path";
|
|
2068
|
+
var LINT_BASELINE_FILE = ".oxlintrc.baseline.json";
|
|
2069
|
+
var LINT_MEASURE_FILE = ".oxlintrc.measure.json";
|
|
2070
|
+
var LINT_BASELINE_SPECIFIER = `./${LINT_BASELINE_FILE}`;
|
|
2071
|
+
function holdsFrom(errorCounts) {
|
|
2072
|
+
return [...errorCounts].map(([rule, count]) => ({ rule, count })).sort((left, right) => right.count - left.count || left.rule.localeCompare(right.rule));
|
|
2073
|
+
}
|
|
2074
|
+
function renderBaseline(holds, version) {
|
|
2075
|
+
const entries = holds.map(({ rule }) => ` ${JSON.stringify(rule)}: "warn"`).join(",\n");
|
|
2076
|
+
return [
|
|
2077
|
+
"{",
|
|
2078
|
+
` // Written by @hublo/sentinel@${version}. Do not edit by hand: \`sentinel --init --lint\``,
|
|
2079
|
+
" // regenerates it from a fresh measurement, and a rule you have since fixed drops out",
|
|
2080
|
+
" // and returns to `error` on its own.",
|
|
2081
|
+
" //",
|
|
2082
|
+
" // These rules are held at `warn` because this module ALREADY violated them when it",
|
|
2083
|
+
" // adopted. They still run and still report, so the build cannot fail on them.",
|
|
2084
|
+
" //",
|
|
2085
|
+
" // The hold is per RULE, not per occurrence: oxlint cannot freeze a specific list of",
|
|
2086
|
+
" // violations, so a NEW violation of one of these rules is covered too, and will only",
|
|
2087
|
+
" // warn. That is the cost of adopting without a red build, and the reason to clear this",
|
|
2088
|
+
" // file rather than live with it.",
|
|
2089
|
+
" //",
|
|
2090
|
+
" // `sentinel --inspect --lint` lists what is held; `sentinel --run --lint --json` counts",
|
|
2091
|
+
" // what is left, per rule. Fix them and re-run `--init --lint` to get the severity back.",
|
|
2092
|
+
' "rules": {',
|
|
2093
|
+
entries,
|
|
2094
|
+
" }",
|
|
2095
|
+
"}",
|
|
2096
|
+
""
|
|
2097
|
+
].join("\n");
|
|
2098
|
+
}
|
|
2099
|
+
function extendsWithBaseline(current, hasHolds) {
|
|
2100
|
+
const withoutBaseline = current.filter((entry) => entry !== LINT_BASELINE_SPECIFIER);
|
|
2101
|
+
return hasHolds ? [...withoutBaseline, LINT_BASELINE_SPECIFIER] : withoutBaseline;
|
|
2102
|
+
}
|
|
2103
|
+
function describeHolds(holds) {
|
|
2104
|
+
if (holds.length === 0) return "";
|
|
2105
|
+
const total = holds.reduce((sum, hold) => sum + hold.count, 0);
|
|
2106
|
+
const listed = holds.slice(0, 5).map(({ rule, count }) => `${rule} (${count})`).join(", ");
|
|
2107
|
+
const rest = holds.length > 5 ? `, and ${holds.length - 5} more` : "";
|
|
2108
|
+
return `held ${holds.length} rule(s) at \`warn\` for this module, covering ${total} violation(s) that were already there: ${listed}${rest}. They are written to ${LINT_BASELINE_FILE}, they still report, and they return to \`error\` once fixed and re-initialized`;
|
|
2109
|
+
}
|
|
2110
|
+
function heldRules(cwd) {
|
|
2111
|
+
const path = join16(cwd, LINT_BASELINE_FILE);
|
|
2112
|
+
if (!existsSync13(path)) return [];
|
|
2113
|
+
try {
|
|
2114
|
+
const parsed = parseJsonc(
|
|
2115
|
+
readFileSync12(path, "utf8"),
|
|
2116
|
+
LINT_BASELINE_FILE
|
|
2117
|
+
);
|
|
2118
|
+
return Object.keys(parsed.rules ?? {});
|
|
2119
|
+
} catch {
|
|
2120
|
+
return [];
|
|
2121
|
+
}
|
|
2122
|
+
}
|
|
2123
|
+
|
|
1898
2124
|
// src/roles/lint/config-policy.ts
|
|
1899
2125
|
var LINT_CONFIG_FILE = ".oxlintrc.json";
|
|
1900
2126
|
var LINT_SCRIPT_NAME = "lint";
|
|
@@ -1907,8 +2133,10 @@ function isSentinelPreset(entry) {
|
|
|
1907
2133
|
}
|
|
1908
2134
|
function extendsWithPreset(current, variant) {
|
|
1909
2135
|
const own = presetPath(variant);
|
|
1910
|
-
const others = current.filter((entry) => entry !== own && !isSentinelPreset(entry));
|
|
1911
|
-
|
|
2136
|
+
const others = [...new Set(current.filter((entry) => entry !== own && !isSentinelPreset(entry)))];
|
|
2137
|
+
const baseline = others.filter((entry) => entry === LINT_BASELINE_SPECIFIER);
|
|
2138
|
+
const rest = others.filter((entry) => entry !== LINT_BASELINE_SPECIFIER);
|
|
2139
|
+
return [own, ...rest, ...baseline];
|
|
1912
2140
|
}
|
|
1913
2141
|
function presetVariant(preset, cwd) {
|
|
1914
2142
|
const normalised = cwd.replaceAll("\\", "/");
|
|
@@ -1932,46 +2160,6 @@ function lintTarget() {
|
|
|
1932
2160
|
return { cache: true, inputs: ["default", "^default", `{projectRoot}/${LINT_CONFIG_FILE}`] };
|
|
1933
2161
|
}
|
|
1934
2162
|
|
|
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
2163
|
// src/roles/lint/presets/nest.json
|
|
1976
2164
|
var nest_default = {
|
|
1977
2165
|
plugins: ["typescript", "jest", "import", "unicorn", "oxc", "node"],
|
|
@@ -2311,6 +2499,7 @@ var nest_default = {
|
|
|
2311
2499
|
"no-invalid-this": "no oxlint equivalent, and hosting does not help: the rule crashes on every file under oxlint's plugin API (sourceCode.getJSDocComment is not implemented). Its coverage moves to TypeScript's noImplicitThis, which the sentinel TypeScript role manages.",
|
|
2312
2500
|
"no-multi-spaces": "formatting, and the formatter owns it. It moves to oxfmt, which is deliberately the LAST step of the migration, so nothing enforces it in between.",
|
|
2313
2501
|
"no-octal": "no oxlint equivalent",
|
|
2502
|
+
"no-restricted-syntax": "no oxlint equivalent, and unlike the react tier this preset hosts no house-rule plugin, so architectural guards written as AST selectors are not enforced at all. They belong in sentinel's `hublo` plugin, next to the frontend ones: tell us which guards your service relies on. A per-module copy of a house rule drifts, which is the problem sentinel exists to remove.",
|
|
2314
2503
|
"no-trailing-spaces": "formatting, and the formatter owns it. It moves to oxfmt, which is deliberately the LAST step of the migration, so nothing enforces it in between.",
|
|
2315
2504
|
semi: "formatting, and the formatter owns it. It moves to oxfmt, which is deliberately the LAST step of the migration, so nothing enforces it in between.",
|
|
2316
2505
|
"template-curly-spacing": "formatting, and the formatter owns it. It moves to oxfmt, which is deliberately the LAST step of the migration, so nothing enforces it in between.",
|
|
@@ -3264,10 +3453,51 @@ var react_default = {
|
|
|
3264
3453
|
uncovered: {
|
|
3265
3454
|
"import/order": "import ordering is FORMATTING, and the format role owns it: oxfmt's `sortImports` reproduces exactly these groups (builtin, external, internal, parent/index, sibling, alphabetical, blank line between) and sorts a file completely in ONE pass, because it rewrites the whole import block. A lint fixer cannot: it emits text edits over overlapping ranges, so this rule needed several runs to finish (five imports took four passes, measured) and sentinel deliberately runs autofix once. Enforced by `pnpm run format`, not by the linter, which is also why `eslint-plugin-import` is no longer hosted at all: its only other rule, no-duplicates, is native in oxlint.",
|
|
3266
3455
|
"no-octal": "no oxlint equivalent",
|
|
3456
|
+
"no-restricted-syntax": "no oxlint equivalent as configuration, so the eight AST selectors this repo used are shipped as real rules in the local `hublo` plugin instead, which this tier hosts (`../plugins/hublo.js`). Any selector NOT in that plugin is not enforced here; propose it and it joins the plugin rather than a per-module copy.",
|
|
3267
3457
|
semi: "formatting, and the formatter owns it. It moves to oxfmt, which is deliberately the LAST step of the migration, so nothing enforces it in between."
|
|
3268
3458
|
}
|
|
3269
3459
|
};
|
|
3270
3460
|
|
|
3461
|
+
// src/roles/lint/presets/shared.json
|
|
3462
|
+
var shared_default = {
|
|
3463
|
+
rules: {
|
|
3464
|
+
"no-var": "error",
|
|
3465
|
+
"prefer-const": [
|
|
3466
|
+
"error",
|
|
3467
|
+
{
|
|
3468
|
+
destructuring: "any",
|
|
3469
|
+
ignoreReadBeforeAssign: false
|
|
3470
|
+
}
|
|
3471
|
+
],
|
|
3472
|
+
"prefer-rest-params": "error",
|
|
3473
|
+
"prefer-spread": "error",
|
|
3474
|
+
"typescript/ban-ts-comment": "error",
|
|
3475
|
+
"typescript/no-array-constructor": "error",
|
|
3476
|
+
"typescript/no-duplicate-enum-values": "error",
|
|
3477
|
+
"typescript/no-empty-object-type": "error",
|
|
3478
|
+
"typescript/no-extra-non-null-assertion": "error",
|
|
3479
|
+
"typescript/no-misused-new": "error",
|
|
3480
|
+
"typescript/no-namespace": "error",
|
|
3481
|
+
"typescript/no-non-null-asserted-optional-chain": "error",
|
|
3482
|
+
"typescript/no-this-alias": "error",
|
|
3483
|
+
"typescript/no-unnecessary-type-constraint": "error",
|
|
3484
|
+
"typescript/no-unsafe-declaration-merging": "error",
|
|
3485
|
+
"typescript/no-unsafe-function-type": "error",
|
|
3486
|
+
"typescript/no-unused-expressions": [
|
|
3487
|
+
"error",
|
|
3488
|
+
{
|
|
3489
|
+
allowShortCircuit: false,
|
|
3490
|
+
allowTaggedTemplates: false,
|
|
3491
|
+
allowTernary: false
|
|
3492
|
+
}
|
|
3493
|
+
],
|
|
3494
|
+
"typescript/no-wrapper-object-types": "error",
|
|
3495
|
+
"typescript/prefer-as-const": "error",
|
|
3496
|
+
"typescript/prefer-namespace-keyword": "error",
|
|
3497
|
+
"typescript/triple-slash-reference": "error"
|
|
3498
|
+
}
|
|
3499
|
+
};
|
|
3500
|
+
|
|
3271
3501
|
// src/roles/lint/presets/svelte.json
|
|
3272
3502
|
var svelte_default = {
|
|
3273
3503
|
plugins: ["typescript", "oxc"],
|
|
@@ -3606,7 +3836,7 @@ var tools_default = {
|
|
|
3606
3836
|
};
|
|
3607
3837
|
|
|
3608
3838
|
// src/roles/lint/preset-data.ts
|
|
3609
|
-
var
|
|
3839
|
+
var SHARED_RULES = shared_default.rules;
|
|
3610
3840
|
var PRESET_FILES = {
|
|
3611
3841
|
nest: nest_default,
|
|
3612
3842
|
node: node_default,
|
|
@@ -3628,15 +3858,18 @@ function hasLintPreset(preset) {
|
|
|
3628
3858
|
function presetFile(preset) {
|
|
3629
3859
|
return VARIANT_FILES[preset] ?? PRESET_FILES[preset] ?? node_default;
|
|
3630
3860
|
}
|
|
3631
|
-
function
|
|
3632
|
-
return
|
|
3861
|
+
function sharedRules() {
|
|
3862
|
+
return SHARED_RULES;
|
|
3633
3863
|
}
|
|
3634
3864
|
function presetRules(preset) {
|
|
3635
3865
|
return presetFile(preset).rules;
|
|
3636
3866
|
}
|
|
3867
|
+
function uncoveredFor(preset) {
|
|
3868
|
+
return Object.entries(presetFile(preset).uncovered ?? {}).map(([rule, reason]) => ({ rule, reason })).sort((left, right) => left.rule.localeCompare(right.rule));
|
|
3869
|
+
}
|
|
3637
3870
|
|
|
3638
3871
|
// src/roles/lint/rule-data.ts
|
|
3639
|
-
var
|
|
3872
|
+
var sharedRules2 = sharedRules();
|
|
3640
3873
|
function isDecision(value) {
|
|
3641
3874
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3642
3875
|
}
|
|
@@ -3663,7 +3896,7 @@ function validate(preset, rules) {
|
|
|
3663
3896
|
}
|
|
3664
3897
|
}
|
|
3665
3898
|
function layered(preset) {
|
|
3666
|
-
const base =
|
|
3899
|
+
const base = sharedRules();
|
|
3667
3900
|
const layer = presetRules(preset);
|
|
3668
3901
|
validate("base", base);
|
|
3669
3902
|
validate(preset, layer);
|
|
@@ -3702,60 +3935,14 @@ function downgradedRulesFor(preset) {
|
|
|
3702
3935
|
}
|
|
3703
3936
|
|
|
3704
3937
|
// 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
|
|
3938
|
+
import { existsSync as existsSync14, readFileSync as readFileSync13 } from "fs";
|
|
3939
|
+
import { isAbsolute as isAbsolute2, join as join17, resolve as resolve3 } from "path";
|
|
3753
3940
|
function ruleCount(cwd, specifier) {
|
|
3754
3941
|
const path = isAbsolute2(specifier) ? specifier : resolve3(cwd, specifier);
|
|
3755
|
-
if (!
|
|
3942
|
+
if (!existsSync14(path)) return void 0;
|
|
3756
3943
|
try {
|
|
3757
3944
|
const parsed = parseJsonc(
|
|
3758
|
-
|
|
3945
|
+
readFileSync13(path, "utf8"),
|
|
3759
3946
|
specifier
|
|
3760
3947
|
);
|
|
3761
3948
|
return Object.keys(parsed.rules ?? {}).length;
|
|
@@ -3775,7 +3962,7 @@ function extraLayers(cwd, extendsList) {
|
|
|
3775
3962
|
function committedExtendsList(cwd, configFile) {
|
|
3776
3963
|
try {
|
|
3777
3964
|
const parsed = parseJsonc(
|
|
3778
|
-
|
|
3965
|
+
readFileSync13(join17(cwd, configFile), "utf8"),
|
|
3779
3966
|
configFile
|
|
3780
3967
|
);
|
|
3781
3968
|
return Array.isArray(parsed.extends) ? parsed.extends.filter((entry) => typeof entry === "string") : [];
|
|
@@ -3834,8 +4021,8 @@ function errorCountsByConfigRule(stdout) {
|
|
|
3834
4021
|
}
|
|
3835
4022
|
|
|
3836
4023
|
// src/core/config/read-adoption.ts
|
|
3837
|
-
import { existsSync as
|
|
3838
|
-
import { join as
|
|
4024
|
+
import { existsSync as existsSync16, readFileSync as readFileSync15 } from "fs";
|
|
4025
|
+
import { join as join19 } from "path";
|
|
3839
4026
|
|
|
3840
4027
|
// src/core/config/owned-keys.ts
|
|
3841
4028
|
function presetOwnedKeys(config, permitted, presetSets) {
|
|
@@ -3850,12 +4037,12 @@ function localOnlyKeys(config, permitted, presetSets) {
|
|
|
3850
4037
|
}
|
|
3851
4038
|
|
|
3852
4039
|
// src/core/config/resolve-config-target.ts
|
|
3853
|
-
import { existsSync as
|
|
3854
|
-
import { join as
|
|
4040
|
+
import { existsSync as existsSync15, readFileSync as readFileSync14 } from "fs";
|
|
4041
|
+
import { join as join18 } from "path";
|
|
3855
4042
|
function readExtends(absolutePath) {
|
|
3856
4043
|
let parsed;
|
|
3857
4044
|
try {
|
|
3858
|
-
parsed = parseJsonc(
|
|
4045
|
+
parsed = parseJsonc(readFileSync14(absolutePath, "utf8"), absolutePath);
|
|
3859
4046
|
} catch {
|
|
3860
4047
|
return [];
|
|
3861
4048
|
}
|
|
@@ -3869,8 +4056,8 @@ function resolveConfigTarget(moduleDir, { candidates, markers, fallback }) {
|
|
|
3869
4056
|
let existing;
|
|
3870
4057
|
let existingExtendsSomething = false;
|
|
3871
4058
|
for (const candidate of candidates) {
|
|
3872
|
-
const absolutePath =
|
|
3873
|
-
if (!
|
|
4059
|
+
const absolutePath = join18(moduleDir, candidate);
|
|
4060
|
+
if (!existsSync15(absolutePath)) continue;
|
|
3874
4061
|
const chain = readExtends(absolutePath);
|
|
3875
4062
|
if (existing === void 0) {
|
|
3876
4063
|
existing = candidate;
|
|
@@ -3903,12 +4090,12 @@ var NOT_ADOPTED3 = (configFile, unreadable = null) => ({
|
|
|
3903
4090
|
});
|
|
3904
4091
|
function readAdoption(cwd, options) {
|
|
3905
4092
|
const target = resolveConfigTarget(cwd, options);
|
|
3906
|
-
if (target.reason === "none" || !
|
|
4093
|
+
if (target.reason === "none" || !existsSync16(join19(cwd, target.path))) {
|
|
3907
4094
|
return NOT_ADOPTED3(target.reason === "none" ? null : target.path);
|
|
3908
4095
|
}
|
|
3909
4096
|
let parsed;
|
|
3910
4097
|
try {
|
|
3911
|
-
parsed = parseJsonc(
|
|
4098
|
+
parsed = parseJsonc(readFileSync15(join19(cwd, target.path), "utf8"), target.path);
|
|
3912
4099
|
} catch (error) {
|
|
3913
4100
|
const reason = error instanceof Error ? error.message : String(error);
|
|
3914
4101
|
return NOT_ADOPTED3(target.path, `${target.path} could not be parsed (${reason})`);
|
|
@@ -3942,9 +4129,9 @@ function readLintAdoption(cwd) {
|
|
|
3942
4129
|
}
|
|
3943
4130
|
|
|
3944
4131
|
// src/roles/lint/resolve-oxlint.ts
|
|
3945
|
-
import { existsSync as
|
|
4132
|
+
import { existsSync as existsSync17 } from "fs";
|
|
3946
4133
|
import { createRequire as createRequire3 } from "module";
|
|
3947
|
-
import { delimiter as delimiter2, dirname as dirname5, join as
|
|
4134
|
+
import { delimiter as delimiter2, dirname as dirname5, join as join20 } from "path";
|
|
3948
4135
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
3949
4136
|
var PACKAGE_OF = {
|
|
3950
4137
|
oxlint: "oxlint",
|
|
@@ -3969,30 +4156,30 @@ function tsgolintShim(cwd) {
|
|
|
3969
4156
|
for (const owner of ["oxlint-tsgolint", "oxlint"]) {
|
|
3970
4157
|
try {
|
|
3971
4158
|
const packageDir = dirname5(require3.resolve(`${owner}/package.json`));
|
|
3972
|
-
candidates.push(
|
|
3973
|
-
candidates.push(
|
|
4159
|
+
candidates.push(join20(packageDir, "node_modules", ".bin", "tsgolint"));
|
|
4160
|
+
candidates.push(join20(packageDir, "..", ".bin", "tsgolint"));
|
|
3974
4161
|
} catch {
|
|
3975
4162
|
}
|
|
3976
4163
|
}
|
|
3977
4164
|
try {
|
|
3978
4165
|
const ownRoot = dirname5(require3.resolve("@hublo/sentinel/package.json"));
|
|
3979
|
-
candidates.push(
|
|
4166
|
+
candidates.push(join20(ownRoot, "node_modules", ".bin", "tsgolint"));
|
|
3980
4167
|
} catch {
|
|
3981
4168
|
candidates.push(resolveBin(dirname5(fileURLToPath2(import.meta.url)), "tsgolint") ?? "");
|
|
3982
4169
|
}
|
|
3983
|
-
return candidates.find((candidate) => candidate !== "" &&
|
|
4170
|
+
return candidates.find((candidate) => candidate !== "" && existsSync17(candidate));
|
|
3984
4171
|
}
|
|
3985
4172
|
function oxlintSearchPath(cwd) {
|
|
3986
4173
|
return binSearchPath(cwd);
|
|
3987
4174
|
}
|
|
3988
4175
|
|
|
3989
4176
|
// src/roles/lint/adapters/oxlint/plan.ts
|
|
3990
|
-
import { existsSync as
|
|
3991
|
-
import { join as
|
|
4177
|
+
import { existsSync as existsSync19, readFileSync as readFileSync18 } from "fs";
|
|
4178
|
+
import { join as join23 } from "path";
|
|
3992
4179
|
|
|
3993
4180
|
// src/roles/lint/eslint-ignores.ts
|
|
3994
|
-
import { existsSync as
|
|
3995
|
-
import { join as
|
|
4181
|
+
import { existsSync as existsSync18, readFileSync as readFileSync16 } from "fs";
|
|
4182
|
+
import { join as join21 } from "path";
|
|
3996
4183
|
var IGNORE_BLOCKS = [/\bignores\s*:\s*\[([^\]]*)\]/g, /\bglobalIgnores\s*\(\s*\[([^\]]*)\]/g];
|
|
3997
4184
|
var STRING_LITERAL = /['"`]([^'"`]+)['"`]/g;
|
|
3998
4185
|
var OPAQUE_SOURCE = /\b(includeIgnoreFile)\s*\([^)]*\)/g;
|
|
@@ -4005,11 +4192,11 @@ function readRootEslintIgnores(root) {
|
|
|
4005
4192
|
return { patterns: patterns.filter((pattern) => pattern.startsWith("**/")), unresolved };
|
|
4006
4193
|
}
|
|
4007
4194
|
function readEslintIgnores(cwd) {
|
|
4008
|
-
const config = ESLINT_CONFIG_FILES.map((name) =>
|
|
4195
|
+
const config = ESLINT_CONFIG_FILES.map((name) => join21(cwd, name)).find((path) => existsSync18(path));
|
|
4009
4196
|
if (!config) return { patterns: [], unresolved: [] };
|
|
4010
4197
|
let source;
|
|
4011
4198
|
try {
|
|
4012
|
-
source =
|
|
4199
|
+
source = readFileSync16(config, "utf8");
|
|
4013
4200
|
} catch {
|
|
4014
4201
|
return { patterns: [], unresolved: [] };
|
|
4015
4202
|
}
|
|
@@ -4063,8 +4250,8 @@ function lintPresetFor(preset) {
|
|
|
4063
4250
|
}
|
|
4064
4251
|
|
|
4065
4252
|
// src/roles/lint/rename-suppressions.ts
|
|
4066
|
-
import { readdirSync as readdirSync2, readFileSync as
|
|
4067
|
-
import { join as
|
|
4253
|
+
import { readdirSync as readdirSync2, readFileSync as readFileSync17, statSync as statSync2 } from "fs";
|
|
4254
|
+
import { join as join22, relative as relative3 } from "path";
|
|
4068
4255
|
var SOURCE_EXTENSIONS = [
|
|
4069
4256
|
".ts",
|
|
4070
4257
|
".tsx",
|
|
@@ -4111,10 +4298,10 @@ function* sourceFiles(dir) {
|
|
|
4111
4298
|
return;
|
|
4112
4299
|
}
|
|
4113
4300
|
for (const entry of entries) {
|
|
4114
|
-
const full =
|
|
4301
|
+
const full = join22(dir, entry);
|
|
4115
4302
|
let isDirectory;
|
|
4116
4303
|
try {
|
|
4117
|
-
isDirectory =
|
|
4304
|
+
isDirectory = statSync2(full).isDirectory();
|
|
4118
4305
|
} catch {
|
|
4119
4306
|
continue;
|
|
4120
4307
|
}
|
|
@@ -4131,7 +4318,7 @@ function findSuppressionRenames(cwd, renames) {
|
|
|
4131
4318
|
for (const file of sourceFiles(cwd)) {
|
|
4132
4319
|
let content;
|
|
4133
4320
|
try {
|
|
4134
|
-
content =
|
|
4321
|
+
content = readFileSync17(file, "utf8");
|
|
4135
4322
|
} catch {
|
|
4136
4323
|
continue;
|
|
4137
4324
|
}
|
|
@@ -4155,7 +4342,7 @@ function findSuppressionRenames(cwd, renames) {
|
|
|
4155
4342
|
}
|
|
4156
4343
|
if (hits.length === 0) return;
|
|
4157
4344
|
found.push({
|
|
4158
|
-
file:
|
|
4345
|
+
file: relative3(cwd, file),
|
|
4159
4346
|
line: index + 1,
|
|
4160
4347
|
from: line,
|
|
4161
4348
|
to: line.replace(rules, renamed.join(", ")),
|
|
@@ -4259,7 +4446,7 @@ function plan2(context) {
|
|
|
4259
4446
|
keys: removableDeps
|
|
4260
4447
|
});
|
|
4261
4448
|
}
|
|
4262
|
-
const eslintConfigs = ESLINT_CONFIG_FILES.filter((name) =>
|
|
4449
|
+
const eslintConfigs = ESLINT_CONFIG_FILES.filter((name) => existsSync19(join23(context.cwd, name)));
|
|
4263
4450
|
for (const name of eslintConfigs) operations.push({ kind: "delete", path: name });
|
|
4264
4451
|
operations.push(
|
|
4265
4452
|
...nxTargetOperations({
|
|
@@ -4357,7 +4544,7 @@ function lintScripts(cwd) {
|
|
|
4357
4544
|
function committedExtends(cwd) {
|
|
4358
4545
|
try {
|
|
4359
4546
|
const parsed = parseJsonc(
|
|
4360
|
-
|
|
4547
|
+
readFileSync18(join23(cwd, LINT_CONFIG_FILE), "utf8"),
|
|
4361
4548
|
LINT_CONFIG_FILE
|
|
4362
4549
|
);
|
|
4363
4550
|
return Array.isArray(parsed.extends) ? parsed.extends.filter((entry) => typeof entry === "string") : [];
|
|
@@ -4368,7 +4555,7 @@ function committedExtends(cwd) {
|
|
|
4368
4555
|
function committedIgnorePatterns(cwd) {
|
|
4369
4556
|
try {
|
|
4370
4557
|
const parsed = parseJsonc(
|
|
4371
|
-
|
|
4558
|
+
readFileSync18(join23(cwd, LINT_CONFIG_FILE), "utf8"),
|
|
4372
4559
|
LINT_CONFIG_FILE
|
|
4373
4560
|
);
|
|
4374
4561
|
return Array.isArray(parsed.ignorePatterns) ? parsed.ignorePatterns.filter((entry) => typeof entry === "string") : [];
|
|
@@ -4380,7 +4567,7 @@ function moduleEslintDependencies(cwd) {
|
|
|
4380
4567
|
const isEslintPackage = (name) => name === "eslint" || name === "@types/eslint" || name === "typescript-eslint" || name.startsWith("@typescript-eslint/") || name.startsWith("eslint-plugin-") || name.startsWith("eslint-config-") || name.startsWith("@eslint/");
|
|
4381
4568
|
let manifest;
|
|
4382
4569
|
try {
|
|
4383
|
-
manifest = JSON.parse(
|
|
4570
|
+
manifest = JSON.parse(readFileSync18(join23(cwd, "package.json"), "utf8"));
|
|
4384
4571
|
} catch {
|
|
4385
4572
|
return [];
|
|
4386
4573
|
}
|
|
@@ -4445,17 +4632,31 @@ var OxlintAdapter = class extends BaseAdapter {
|
|
|
4445
4632
|
return presetOfVariant(presetNameFromPath(readLintAdoption(cwd).preset));
|
|
4446
4633
|
}
|
|
4447
4634
|
/**
|
|
4448
|
-
* The autofix pass
|
|
4449
|
-
*
|
|
4635
|
+
* The autofix pass, and what it did.
|
|
4636
|
+
*
|
|
4637
|
+
* Output is captured rather than streamed: the fix run is noise, and the verification run
|
|
4638
|
+
* that follows is the one a developer reads. But captured is not the same as discarded, and
|
|
4639
|
+
* it used to be discarded, which is why a pass that never ran looked exactly like one that
|
|
4640
|
+
* had nothing to do.
|
|
4641
|
+
*
|
|
4642
|
+
* The exit status cannot be the signal, and neither can the stream. Measured on oxlint: a
|
|
4643
|
+
* healthy run on a module with remaining violations and a run whose `extends` path resolves
|
|
4644
|
+
* nowhere BOTH exit 1 and both write to stdout. One module was checked and one was not, and
|
|
4645
|
+
* nothing in the process result separates them, so the only signal is what the tool said.
|
|
4450
4646
|
*/
|
|
4451
4647
|
fixPass(ctx, oxlint, env) {
|
|
4452
|
-
const
|
|
4648
|
+
const before = snapshotFiles(ctx.cwd, LINTABLE_EXTENSIONS);
|
|
4649
|
+
const result = spawnSync4(oxlint, ["-c", LINT_CONFIG_FILE, "--fix", "."], {
|
|
4453
4650
|
cwd: ctx.cwd,
|
|
4454
4651
|
encoding: "utf8",
|
|
4455
4652
|
env,
|
|
4456
4653
|
maxBuffer: CAPTURE_MAX_BUFFER
|
|
4457
4654
|
});
|
|
4458
|
-
return result.
|
|
4655
|
+
if (result.error) return { changed: [], failure: result.error.message };
|
|
4656
|
+
const failure = readFailure(`${result.stdout ?? ""}
|
|
4657
|
+
${result.stderr ?? ""}`);
|
|
4658
|
+
if (failure !== void 0) return { changed: [], failure };
|
|
4659
|
+
return { changed: changedSince(ctx.cwd, before, LINTABLE_EXTENSIONS), failure: void 0 };
|
|
4459
4660
|
}
|
|
4460
4661
|
/** The module's current `lint` script, so adoption replaces only the eslint part of it. */
|
|
4461
4662
|
/**
|
|
@@ -4478,7 +4679,8 @@ var OxlintAdapter = class extends BaseAdapter {
|
|
|
4478
4679
|
const oxlint = resolveOxlint(ctx.cwd);
|
|
4479
4680
|
if (!oxlint) return;
|
|
4480
4681
|
const env = { ...process.env, PATH: oxlintPath(ctx.cwd, process.env) };
|
|
4481
|
-
this.fixPass(ctx, oxlint, env)
|
|
4682
|
+
process.stderr.write(` ${describeFixOutcome(this.fixPass(ctx, oxlint, env), "lint")}
|
|
4683
|
+
`);
|
|
4482
4684
|
this.writeModuleBaseline(ctx, oxlint, env);
|
|
4483
4685
|
}
|
|
4484
4686
|
/**
|
|
@@ -4499,20 +4701,20 @@ var OxlintAdapter = class extends BaseAdapter {
|
|
|
4499
4701
|
* build the developer can see, rather than a silent half-adoption they cannot.
|
|
4500
4702
|
*/
|
|
4501
4703
|
writeModuleBaseline(ctx, oxlint, env) {
|
|
4502
|
-
const configPath =
|
|
4503
|
-
const baselinePath =
|
|
4504
|
-
if (!
|
|
4704
|
+
const configPath = join24(ctx.cwd, LINT_CONFIG_FILE);
|
|
4705
|
+
const baselinePath = join24(ctx.cwd, LINT_BASELINE_FILE);
|
|
4706
|
+
if (!existsSync20(configPath)) return;
|
|
4505
4707
|
let config;
|
|
4506
4708
|
try {
|
|
4507
4709
|
config = parseJsonc(
|
|
4508
|
-
|
|
4710
|
+
readFileSync19(configPath, "utf8"),
|
|
4509
4711
|
LINT_CONFIG_FILE
|
|
4510
4712
|
);
|
|
4511
4713
|
} catch {
|
|
4512
4714
|
return;
|
|
4513
4715
|
}
|
|
4514
4716
|
const current = Array.isArray(config.extends) ? config.extends : [];
|
|
4515
|
-
const measurePath =
|
|
4717
|
+
const measurePath = join24(ctx.cwd, LINT_MEASURE_FILE);
|
|
4516
4718
|
let measured;
|
|
4517
4719
|
try {
|
|
4518
4720
|
writeFileSync2(
|
|
@@ -4520,7 +4722,7 @@ var OxlintAdapter = class extends BaseAdapter {
|
|
|
4520
4722
|
`${JSON.stringify({ ...config, extends: extendsWithBaseline(current, false) }, null, 2)}
|
|
4521
4723
|
`
|
|
4522
4724
|
);
|
|
4523
|
-
measured =
|
|
4725
|
+
measured = spawnSync4(
|
|
4524
4726
|
oxlint,
|
|
4525
4727
|
[
|
|
4526
4728
|
"-c",
|
|
@@ -4553,14 +4755,14 @@ var OxlintAdapter = class extends BaseAdapter {
|
|
|
4553
4755
|
} else {
|
|
4554
4756
|
rmSync(baselinePath, { force: true });
|
|
4555
4757
|
}
|
|
4556
|
-
if (next.length !== current.length || next.some((entry,
|
|
4758
|
+
if (next.length !== current.length || next.some((entry, at2) => entry !== current[at2])) {
|
|
4557
4759
|
config.extends = next;
|
|
4558
4760
|
writeFileSync2(configPath, `${JSON.stringify(config, null, 2)}
|
|
4559
4761
|
`);
|
|
4560
4762
|
}
|
|
4561
4763
|
}
|
|
4562
4764
|
async run(ctx) {
|
|
4563
|
-
if (!
|
|
4765
|
+
if (!existsSync20(join24(ctx.cwd, LINT_CONFIG_FILE))) {
|
|
4564
4766
|
process.stderr.write(
|
|
4565
4767
|
`sentinel lint(oxlint): no ${LINT_CONFIG_FILE} in this module; run \`sentinel --init --lint\` to adopt.
|
|
4566
4768
|
`
|
|
@@ -4605,9 +4807,10 @@ var OxlintAdapter = class extends BaseAdapter {
|
|
|
4605
4807
|
valueFlags: OXLINT_VALUE_FLAGS
|
|
4606
4808
|
});
|
|
4607
4809
|
const targets = paths.length > 0 ? paths : ["."];
|
|
4810
|
+
const emptyScope = scopeFlags(paths);
|
|
4608
4811
|
const lint = (extra) => {
|
|
4609
|
-
const passed = [...extra, ...passedOptions];
|
|
4610
|
-
const result =
|
|
4812
|
+
const passed = [...extra, ...emptyScope, ...passedOptions];
|
|
4813
|
+
const result = spawnSync4(oxlint, ["-c", LINT_CONFIG_FILE, ...passed, ...targets], {
|
|
4611
4814
|
cwd: ctx.cwd,
|
|
4612
4815
|
stdio: "inherit",
|
|
4613
4816
|
env
|
|
@@ -4645,6 +4848,33 @@ var OxlintAdapter = class extends BaseAdapter {
|
|
|
4645
4848
|
rule: entry.rule,
|
|
4646
4849
|
reason: entry.reason
|
|
4647
4850
|
})),
|
|
4851
|
+
// A FOURTH state, and the only one that was declared and never reported: rules oxlint
|
|
4852
|
+
// cannot run at all, so they can be neither enforced nor disabled. Each preset has
|
|
4853
|
+
// carried this list since the Svelte work, and nothing read it outside a test.
|
|
4854
|
+
//
|
|
4855
|
+
// The gap that made it matter: `no-restricted-syntax` has no oxlint equivalent, so two
|
|
4856
|
+
// architectural guards a Nest service relied on vanished at adoption with nothing said.
|
|
4857
|
+
// The adopter found it himself and rebuilt them as a local plugin. The next one might
|
|
4858
|
+
// not, which is the whole argument for saying it out loud.
|
|
4859
|
+
uncovered: uncoveredFor(presetVariant(ctx.preset, ctx.cwd)).map((entry) => ({
|
|
4860
|
+
rule: entry.rule,
|
|
4861
|
+
reason: entry.reason
|
|
4862
|
+
})),
|
|
4863
|
+
// What this module HOLDS: the rules its own baseline lowers to `warn`.
|
|
4864
|
+
//
|
|
4865
|
+
// Distinct from `downgraded` just above, which is the PRESET's doing and identical for
|
|
4866
|
+
// every module on it. These are this module's own debt, and leaving them out was a real
|
|
4867
|
+
// gap: `--inspect` could tell you the preset holds 6 rules while saying nothing about
|
|
4868
|
+
// the 3 your module holds on top, so nobody could see their own debt without opening a
|
|
4869
|
+
// file that says "do not edit by hand".
|
|
4870
|
+
//
|
|
4871
|
+
// Listed, not counted. The list is free — it is the committed file — while a count means
|
|
4872
|
+
// running oxlint, which this verb deliberately does not do (that is what keeps a
|
|
4873
|
+
// 441-module sweep at ~8s per role). `--run --lint --json` carries the per-rule counts.
|
|
4874
|
+
held: heldRules(ctx.cwd).map((rule) => ({
|
|
4875
|
+
rule,
|
|
4876
|
+
reason: `held at \`warn\` by this module's ${LINT_BASELINE_FILE}, because it already violated it when it adopted. \`--run --lint --json\` counts what is left.`
|
|
4877
|
+
})),
|
|
4648
4878
|
// What this module enforces BEYOND the preset. Not drift, and not covered by anything
|
|
4649
4879
|
// else here: the stub still holds only `extends` and `ignorePatterns`, so a module with
|
|
4650
4880
|
// a team layer reports `conformant=true drift=[]` and used to say nothing at all about
|
|
@@ -4680,7 +4910,7 @@ var OxlintAdapter = class extends BaseAdapter {
|
|
|
4680
4910
|
let stub;
|
|
4681
4911
|
try {
|
|
4682
4912
|
stub = parseJsonc(
|
|
4683
|
-
|
|
4913
|
+
readFileSync19(join24(cwd, LINT_CONFIG_FILE), "utf8"),
|
|
4684
4914
|
LINT_CONFIG_FILE
|
|
4685
4915
|
);
|
|
4686
4916
|
} catch {
|
|
@@ -4690,7 +4920,7 @@ var OxlintAdapter = class extends BaseAdapter {
|
|
|
4690
4920
|
for (const entry of stub.extends ?? []) {
|
|
4691
4921
|
try {
|
|
4692
4922
|
const preset = parseJsonc(
|
|
4693
|
-
|
|
4923
|
+
readFileSync19(join24(cwd, entry), "utf8"),
|
|
4694
4924
|
entry
|
|
4695
4925
|
);
|
|
4696
4926
|
for (const rule of Object.keys(preset.rules ?? {})) names.add(rule);
|
|
@@ -4707,7 +4937,7 @@ var OxlintAdapter = class extends BaseAdapter {
|
|
|
4707
4937
|
const oxlint = resolveOxlint(ctx.cwd);
|
|
4708
4938
|
if (!oxlint) return { ok: false, code: 1, metrics: { error: "oxlint not found" } };
|
|
4709
4939
|
const typeAware = canRunTypeAware(ctx.cwd) ? ["--type-aware"] : [];
|
|
4710
|
-
const result =
|
|
4940
|
+
const result = spawnSync4(oxlint, ["-c", LINT_CONFIG_FILE, ...typeAware, "-f", "json", "."], {
|
|
4711
4941
|
cwd: ctx.cwd,
|
|
4712
4942
|
encoding: "utf8",
|
|
4713
4943
|
env: { ...process.env, PATH: oxlintPath(ctx.cwd, process.env) },
|
|
@@ -4760,14 +4990,14 @@ var OxlintAdapter = class extends BaseAdapter {
|
|
|
4760
4990
|
let parsed;
|
|
4761
4991
|
try {
|
|
4762
4992
|
parsed = parseJsonc(
|
|
4763
|
-
|
|
4993
|
+
readFileSync19(join24(cwd, LINT_CONFIG_FILE), "utf8"),
|
|
4764
4994
|
LINT_CONFIG_FILE
|
|
4765
4995
|
);
|
|
4766
4996
|
} catch {
|
|
4767
4997
|
return void 0;
|
|
4768
4998
|
}
|
|
4769
4999
|
const targets = Array.isArray(parsed.extends) ? parsed.extends.filter((entry) => typeof entry === "string") : typeof parsed.extends === "string" ? [parsed.extends] : [];
|
|
4770
|
-
return targets.find((target) => !
|
|
5000
|
+
return targets.find((target) => !existsSync20(join24(cwd, target)));
|
|
4771
5001
|
}
|
|
4772
5002
|
/** Announce what is not enforced, so reduced coverage is never silent. */
|
|
4773
5003
|
announceDisabled(preset) {
|
|
@@ -4798,36 +5028,10 @@ function registerLint() {
|
|
|
4798
5028
|
}
|
|
4799
5029
|
|
|
4800
5030
|
// src/roles/typescript/adapters/tsc/tsc.adapter.ts
|
|
4801
|
-
import { spawnSync as
|
|
4802
|
-
import { existsSync as
|
|
5031
|
+
import { spawnSync as spawnSync5 } from "child_process";
|
|
5032
|
+
import { existsSync as existsSync21, readFileSync as readFileSync21 } from "fs";
|
|
4803
5033
|
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
|
-
};
|
|
5034
|
+
import { join as join26 } from "path";
|
|
4831
5035
|
|
|
4832
5036
|
// src/roles/typescript/presets/nest.json
|
|
4833
5037
|
var nest_default2 = {
|
|
@@ -4878,15 +5082,41 @@ var react_default2 = {
|
|
|
4878
5082
|
}
|
|
4879
5083
|
};
|
|
4880
5084
|
|
|
5085
|
+
// src/roles/typescript/presets/shared.json
|
|
5086
|
+
var shared_default2 = {
|
|
5087
|
+
$schema: "https://json.schemastore.org/tsconfig",
|
|
5088
|
+
compilerOptions: {
|
|
5089
|
+
strict: true,
|
|
5090
|
+
noFallthroughCasesInSwitch: true,
|
|
5091
|
+
forceConsistentCasingInFileNames: true,
|
|
5092
|
+
esModuleInterop: true,
|
|
5093
|
+
skipLibCheck: true
|
|
5094
|
+
},
|
|
5095
|
+
deferred: {
|
|
5096
|
+
noImplicitAny: {
|
|
5097
|
+
phase: 2,
|
|
5098
|
+
reason: "the implicit-any migration (TS70xx)"
|
|
5099
|
+
},
|
|
5100
|
+
noUnusedLocals: {
|
|
5101
|
+
phase: 2,
|
|
5102
|
+
reason: "unused-local cleanup (TS6133)"
|
|
5103
|
+
},
|
|
5104
|
+
noUnusedParameters: {
|
|
5105
|
+
phase: 2,
|
|
5106
|
+
reason: "unused-parameter cleanup (TS6133)"
|
|
5107
|
+
}
|
|
5108
|
+
}
|
|
5109
|
+
};
|
|
5110
|
+
|
|
4881
5111
|
// src/roles/typescript/preset-data.ts
|
|
4882
|
-
var
|
|
5112
|
+
var SHARED = shared_default2;
|
|
4883
5113
|
var LAYERS = {
|
|
4884
5114
|
nest: nest_default2,
|
|
4885
5115
|
node: node_default2,
|
|
4886
5116
|
react: react_default2
|
|
4887
5117
|
};
|
|
4888
5118
|
var SHIPPED_PRESETS = Object.keys(LAYERS).sort();
|
|
4889
|
-
var DEFERRED_RULES = Object.entries(
|
|
5119
|
+
var DEFERRED_RULES = Object.entries(SHARED.deferred ?? {}).map(([rule, entry]) => ({ rule, phase: entry.phase, reason: entry.reason })).sort((left, right) => left.rule.localeCompare(right.rule));
|
|
4890
5120
|
function validate2() {
|
|
4891
5121
|
for (const { rule, phase, reason } of DEFERRED_RULES) {
|
|
4892
5122
|
if (!Number.isInteger(phase) || phase < 2) {
|
|
@@ -4899,9 +5129,9 @@ function validate2() {
|
|
|
4899
5129
|
`deferred rule "${rule}" has no reason; --inspect reports it and would say nothing`
|
|
4900
5130
|
);
|
|
4901
5131
|
}
|
|
4902
|
-
if (rule in
|
|
5132
|
+
if (rule in SHARED.compilerOptions) {
|
|
4903
5133
|
throw new Error(
|
|
4904
|
-
`"${rule}" is deferred and also set by the
|
|
5134
|
+
`"${rule}" is deferred and also set by the shared layer; it would be announced as not enforced while being enforced`
|
|
4905
5135
|
);
|
|
4906
5136
|
}
|
|
4907
5137
|
for (const [preset, layer] of Object.entries(LAYERS)) {
|
|
@@ -4921,8 +5151,8 @@ function tsPresetFor(preset) {
|
|
|
4921
5151
|
const layer = LAYERS[preset];
|
|
4922
5152
|
if (!layer) throw new Error(`no TypeScript preset ships for preset "${preset}"`);
|
|
4923
5153
|
return {
|
|
4924
|
-
...
|
|
4925
|
-
compilerOptions: { ...
|
|
5154
|
+
...SHARED.$schema === void 0 ? {} : { $schema: SHARED.$schema },
|
|
5155
|
+
compilerOptions: { ...SHARED.compilerOptions, ...layer.compilerOptions }
|
|
4926
5156
|
};
|
|
4927
5157
|
}
|
|
4928
5158
|
|
|
@@ -4982,8 +5212,8 @@ function readTsconfigAdoption(cwd) {
|
|
|
4982
5212
|
}
|
|
4983
5213
|
|
|
4984
5214
|
// src/roles/typescript/adapters/tsc/plan.ts
|
|
4985
|
-
import { readFileSync as
|
|
4986
|
-
import { join as
|
|
5215
|
+
import { readFileSync as readFileSync20 } from "fs";
|
|
5216
|
+
import { join as join25 } from "path";
|
|
4987
5217
|
|
|
4988
5218
|
// src/roles/typescript/typecheck-script.ts
|
|
4989
5219
|
var SENTINEL_TYPECHECK_COMMAND = "sentinel --run --typescript";
|
|
@@ -5076,7 +5306,7 @@ function planAdoption(context) {
|
|
|
5076
5306
|
};
|
|
5077
5307
|
}
|
|
5078
5308
|
const existing = parseJsonc(
|
|
5079
|
-
|
|
5309
|
+
readFileSync20(join25(context.cwd, target.path), "utf8"),
|
|
5080
5310
|
target.path
|
|
5081
5311
|
);
|
|
5082
5312
|
const extendsChain = composeExtends(existing.extends, preset);
|
|
@@ -5214,7 +5444,7 @@ var TscAdapter = class _TscAdapter extends BaseAdapter {
|
|
|
5214
5444
|
let chain;
|
|
5215
5445
|
try {
|
|
5216
5446
|
const parsed = parseJsonc(
|
|
5217
|
-
|
|
5447
|
+
readFileSync21(join26(cwd, target.path), "utf8"),
|
|
5218
5448
|
target.path
|
|
5219
5449
|
);
|
|
5220
5450
|
chain = parsed.extends;
|
|
@@ -5227,7 +5457,7 @@ var TscAdapter = class _TscAdapter extends BaseAdapter {
|
|
|
5227
5457
|
);
|
|
5228
5458
|
if (preset === void 0) return void 0;
|
|
5229
5459
|
try {
|
|
5230
|
-
createRequire4(
|
|
5460
|
+
createRequire4(join26(cwd, "noop.js")).resolve(preset);
|
|
5231
5461
|
return void 0;
|
|
5232
5462
|
} catch {
|
|
5233
5463
|
return preset;
|
|
@@ -5268,7 +5498,7 @@ var TscAdapter = class _TscAdapter extends BaseAdapter {
|
|
|
5268
5498
|
return { ok: false, code: 1 };
|
|
5269
5499
|
}
|
|
5270
5500
|
if (options.length > 0) return this.runWithOptions(ctx, tsc, config);
|
|
5271
|
-
const result =
|
|
5501
|
+
const result = spawnSync5(tsc, ["-b", config], { cwd: ctx.cwd, stdio: "inherit" });
|
|
5272
5502
|
if (result.error) {
|
|
5273
5503
|
process.stderr.write(
|
|
5274
5504
|
`sentinel typescript(tsc): could not run tsc (${result.error.message}); is TypeScript installed in the module?
|
|
@@ -5303,7 +5533,7 @@ var TscAdapter = class _TscAdapter extends BaseAdapter {
|
|
|
5303
5533
|
let worst = 0;
|
|
5304
5534
|
for (const project of this.referencedProjects(ctx.cwd, config)) {
|
|
5305
5535
|
const args = ["-p", project, "--noEmit", "--composite", "false", ...ctx.toolArgs ?? []];
|
|
5306
|
-
const result =
|
|
5536
|
+
const result = spawnSync5(tsc, args, { cwd: ctx.cwd, stdio: "inherit" });
|
|
5307
5537
|
if (result.error) {
|
|
5308
5538
|
process.stderr.write(
|
|
5309
5539
|
`sentinel typescript(tsc): could not run tsc (${result.error.message}); is TypeScript installed in the module?
|
|
@@ -5323,7 +5553,7 @@ var TscAdapter = class _TscAdapter extends BaseAdapter {
|
|
|
5323
5553
|
referencedProjects(cwd, config) {
|
|
5324
5554
|
try {
|
|
5325
5555
|
const parsed = parseJsonc(
|
|
5326
|
-
|
|
5556
|
+
readFileSync21(join26(cwd, config), "utf8"),
|
|
5327
5557
|
config
|
|
5328
5558
|
);
|
|
5329
5559
|
const referenced = (parsed.references ?? []).map((reference) => reference.path).filter((path) => typeof path === "string" && path.length > 0);
|
|
@@ -5339,7 +5569,7 @@ var TscAdapter = class _TscAdapter extends BaseAdapter {
|
|
|
5339
5569
|
* check.
|
|
5340
5570
|
*/
|
|
5341
5571
|
typecheckTarget(cwd) {
|
|
5342
|
-
if (
|
|
5572
|
+
if (existsSync21(join26(cwd, "tsconfig.json"))) return "tsconfig.json";
|
|
5343
5573
|
const target = resolveTsconfigTarget(cwd);
|
|
5344
5574
|
return target.reason === "none" ? null : target.path;
|
|
5345
5575
|
}
|
|
@@ -5392,7 +5622,7 @@ var TscAdapter = class _TscAdapter extends BaseAdapter {
|
|
|
5392
5622
|
return { ok: true, code: 0, metrics: _TscAdapter.NOTHING_TO_REPORT };
|
|
5393
5623
|
}
|
|
5394
5624
|
const tsc = resolveBin(ctx.cwd, "tsc") ?? "tsc";
|
|
5395
|
-
const result =
|
|
5625
|
+
const result = spawnSync5(tsc, ["-b", config], { cwd: ctx.cwd, encoding: "utf8" });
|
|
5396
5626
|
if (result.error) {
|
|
5397
5627
|
process.stderr.write(
|
|
5398
5628
|
`sentinel typescript(tsc): could not run tsc (${result.error.message}); is TypeScript installed in the module?
|
|
@@ -5435,7 +5665,7 @@ var TscAdapter = class _TscAdapter extends BaseAdapter {
|
|
|
5435
5665
|
* errors in the output mean the rule must be on.
|
|
5436
5666
|
*/
|
|
5437
5667
|
noImplicitAnyEnabled(cwd, tsc, config, mainOutput) {
|
|
5438
|
-
const shown =
|
|
5668
|
+
const shown = spawnSync5(tsc, ["-p", config, "--showConfig"], { cwd, encoding: "utf8" });
|
|
5439
5669
|
if (shown.status === 0 && shown.stdout) {
|
|
5440
5670
|
try {
|
|
5441
5671
|
const co = parseJsonc(
|
|
@@ -5543,7 +5773,7 @@ function replaceLines(current, replacements) {
|
|
|
5543
5773
|
}
|
|
5544
5774
|
|
|
5545
5775
|
// src/core/apply-plan.ts
|
|
5546
|
-
import { existsSync as
|
|
5776
|
+
import { existsSync as existsSync22, readFileSync as readFileSync22, renameSync, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
5547
5777
|
import { resolve as resolve4, sep } from "path";
|
|
5548
5778
|
import { applyEdits, findNodeAtLocation, modify, parseTree } from "jsonc-parser";
|
|
5549
5779
|
function resolveWithinRoot(cwd, relativePath) {
|
|
@@ -5555,7 +5785,7 @@ function resolveWithinRoot(cwd, relativePath) {
|
|
|
5555
5785
|
return absolutePath;
|
|
5556
5786
|
}
|
|
5557
5787
|
function readIfExists(absolutePath) {
|
|
5558
|
-
return
|
|
5788
|
+
return existsSync22(absolutePath) ? readFileSync22(absolutePath, "utf8") : void 0;
|
|
5559
5789
|
}
|
|
5560
5790
|
function* leaves(value, prefix = []) {
|
|
5561
5791
|
for (const [key, keyValue] of Object.entries(value)) {
|
|
@@ -5673,8 +5903,8 @@ function applyPlan(cwd, plan3) {
|
|
|
5673
5903
|
}
|
|
5674
5904
|
|
|
5675
5905
|
// src/core/config/preset-evidence.ts
|
|
5676
|
-
import { existsSync as
|
|
5677
|
-
import { join as
|
|
5906
|
+
import { existsSync as existsSync23, readdirSync as readdirSync3, readFileSync as readFileSync23 } from "fs";
|
|
5907
|
+
import { join as join27 } from "path";
|
|
5678
5908
|
var PATH_SIGNALS = [
|
|
5679
5909
|
{
|
|
5680
5910
|
preset: "nest",
|
|
@@ -5688,11 +5918,11 @@ var DEPENDENCY_SIGNALS = [
|
|
|
5688
5918
|
{ preset: "nest", pattern: /^@nestjs\// }
|
|
5689
5919
|
];
|
|
5690
5920
|
function dependencyNames(cwd) {
|
|
5691
|
-
const path =
|
|
5692
|
-
if (!
|
|
5921
|
+
const path = join27(cwd, "package.json");
|
|
5922
|
+
if (!existsSync23(path)) return [];
|
|
5693
5923
|
try {
|
|
5694
5924
|
const manifest = parseJsonc(
|
|
5695
|
-
|
|
5925
|
+
readFileSync23(path, "utf8"),
|
|
5696
5926
|
path
|
|
5697
5927
|
);
|
|
5698
5928
|
return [
|
|
@@ -5715,7 +5945,7 @@ function declaresJsx(cwd) {
|
|
|
5715
5945
|
for (const name of entries) {
|
|
5716
5946
|
try {
|
|
5717
5947
|
const config = parseJsonc(
|
|
5718
|
-
|
|
5948
|
+
readFileSync23(join27(cwd, name), "utf8"),
|
|
5719
5949
|
name
|
|
5720
5950
|
);
|
|
5721
5951
|
if (config.compilerOptions?.jsx !== void 0) return true;
|
|
@@ -5812,13 +6042,26 @@ async function dispatch(opts) {
|
|
|
5812
6042
|
const resolutionPreset = opts.preset ?? declaredPresetFor(opts.target, opts.cwd) ?? detected;
|
|
5813
6043
|
const adapter = resolve(opts.target, resolutionPreset, opts.runner);
|
|
5814
6044
|
const preset = opts.preset ?? adapter.declaredPreset?.(opts.cwd) ?? detected;
|
|
5815
|
-
|
|
5816
|
-
|
|
5817
|
-
|
|
6045
|
+
if (opts.preset !== void 0) {
|
|
6046
|
+
const contradiction = presetContradiction(opts.preset, opts.cwd);
|
|
6047
|
+
if (contradiction !== void 0) {
|
|
6048
|
+
process.stderr.write(`sentinel (${opts.target}): ${contradiction}
|
|
5818
6049
|
`);
|
|
5819
|
-
|
|
6050
|
+
return 1;
|
|
6051
|
+
}
|
|
5820
6052
|
}
|
|
5821
|
-
|
|
6053
|
+
let effective = preset;
|
|
6054
|
+
if (opts.preset === void 0 && adapter.declaredPreset?.(opts.cwd) === void 0) {
|
|
6055
|
+
const [signal] = presetSignals(opts.cwd);
|
|
6056
|
+
if (signal !== void 0 && signal.preset !== preset) {
|
|
6057
|
+
effective = signal.preset;
|
|
6058
|
+
process.stderr.write(
|
|
6059
|
+
` detected "${preset}" from dependencies, but ${signal.evidence}, so adopting as "${signal.preset}". Pass --preset to override.
|
|
6060
|
+
`
|
|
6061
|
+
);
|
|
6062
|
+
}
|
|
6063
|
+
}
|
|
6064
|
+
const context = { cwd: opts.cwd, preset: effective };
|
|
5822
6065
|
const plan3 = await adapter.plan(context);
|
|
5823
6066
|
if (plan3.blocked) {
|
|
5824
6067
|
process.stderr.write(`sentinel (${opts.target}): ${plan3.blocked}
|
|
@@ -5866,18 +6109,18 @@ export {
|
|
|
5866
6109
|
readOwnVersion,
|
|
5867
6110
|
readProjectPackageJson,
|
|
5868
6111
|
readNxProjectName,
|
|
6112
|
+
VERBS,
|
|
6113
|
+
TARGETS,
|
|
6114
|
+
SWEEPABLE_TARGETS,
|
|
6115
|
+
PRESET_NAMES,
|
|
5869
6116
|
WORKSPACE_ROOT_MARKER,
|
|
5870
6117
|
findWorkspaceRoot,
|
|
5871
6118
|
ensureWorkspacePrep,
|
|
5872
6119
|
inspectWorkspacePrep,
|
|
5873
6120
|
palette,
|
|
5874
|
-
VERBS,
|
|
5875
|
-
TARGETS,
|
|
5876
|
-
SWEEPABLE_TARGETS,
|
|
5877
|
-
PRESET_NAMES,
|
|
5878
6121
|
registerAdapters,
|
|
5879
6122
|
describeFramework,
|
|
5880
6123
|
detectFramework,
|
|
5881
6124
|
dispatch
|
|
5882
6125
|
};
|
|
5883
|
-
//# sourceMappingURL=chunk-
|
|
6126
|
+
//# sourceMappingURL=chunk-FLJ2QNFF.js.map
|