@hublo/sentinel 1.1.0-alpha.5 → 1.1.0-alpha.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -5
- package/dist/bin/sentinel.js +17 -6
- package/dist/{chunk-NAUACDXT.js → chunk-FA6N4XCP.js} +386 -178
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -158,11 +158,11 @@ pnpm dlx @hublo/sentinel@<exact-version> --inspect --typescript --module <name>
|
|
|
158
158
|
|
|
159
159
|
Every check is described by three layers:
|
|
160
160
|
|
|
161
|
-
| Layer | Flag | What it is | Examples
|
|
162
|
-
| -------------------- | --------------------------- | ------------------------------------------- |
|
|
163
|
-
| **target** (role) | `--lint`, `--typescript`, … | the _kind_ of check, stable | `lint` `format` `typescript` `build` `test` `static-analysis` `runtime-analysis`
|
|
164
|
-
| **runner** (adapter) | `--runner=<tool>` | the _tool_ behind the target, swappable | lint: `eslint`/`biome`/`oxlint` · types: `tsc`/`tsgo` · build: `vite` · test: `vitest`
|
|
165
|
-
| **preset** (preset) | detected / `--preset` | the _variant_ per stack (strict by default) | `react` `nest` `node` `svelte` (svelte: lint
|
|
161
|
+
| Layer | Flag | What it is | Examples |
|
|
162
|
+
| -------------------- | --------------------------- | ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
163
|
+
| **target** (role) | `--lint`, `--typescript`, … | the _kind_ of check, stable | `lint` `format` `typescript` `build` `test` `static-analysis` `runtime-analysis` |
|
|
164
|
+
| **runner** (adapter) | `--runner=<tool>` | the _tool_ behind the target, swappable | lint: `eslint`/`biome`/`oxlint` · types: `tsc`/`tsgo` · build: `vite` · test: `vitest` |
|
|
165
|
+
| **preset** (preset) | detected / `--preset` | the _variant_ per stack (strict by default) | `react` `nest` `node` `svelte` (svelte: lint + format; no tsconfig preset yet, so the TypeScript role stands aside on those modules and the other two adopt) |
|
|
166
166
|
|
|
167
167
|
A run is `target × runner × preset`, e.g. `sentinel --run --lint --runner=eslint` from the `host-admin` dir.
|
|
168
168
|
|
package/dist/bin/sentinel.js
CHANGED
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
registerAdapters,
|
|
17
17
|
resolve,
|
|
18
18
|
resolveBin
|
|
19
|
-
} from "../chunk-
|
|
19
|
+
} from "../chunk-FA6N4XCP.js";
|
|
20
20
|
|
|
21
21
|
// bin/sentinel.ts
|
|
22
22
|
import { program } from "commander";
|
|
@@ -223,6 +223,14 @@ async function analyse(params) {
|
|
|
223
223
|
toolArgs: params.toolArgs
|
|
224
224
|
};
|
|
225
225
|
try {
|
|
226
|
+
let adoption;
|
|
227
|
+
if (params.verb === "run" || params.verb === "report") {
|
|
228
|
+
try {
|
|
229
|
+
adoption = await adapter.status(ctx);
|
|
230
|
+
} catch {
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
const adoptionFields = adoption ? { adopted: adoption.adopted, preset: adoption.preset, conformant: adoption.conformant } : {};
|
|
226
234
|
if (params.verb === "run") {
|
|
227
235
|
if (params.modules.length > 1) {
|
|
228
236
|
const err = palette(process.stderr);
|
|
@@ -239,7 +247,7 @@ async function analyse(params) {
|
|
|
239
247
|
flavour: effectiveFlavour,
|
|
240
248
|
ok: result.ok,
|
|
241
249
|
status: result.ok ? "ok" : "failed",
|
|
242
|
-
data: {}
|
|
250
|
+
data: { ...adoptionFields }
|
|
243
251
|
});
|
|
244
252
|
worstCode = Math.max(worstCode, result.code);
|
|
245
253
|
} else if (params.verb === "report") {
|
|
@@ -250,12 +258,15 @@ async function analyse(params) {
|
|
|
250
258
|
flavour: effectiveFlavour,
|
|
251
259
|
ok: result.ok,
|
|
252
260
|
status: result.ok ? "ok" : "failed",
|
|
253
|
-
|
|
261
|
+
// Engine fields LAST, so they WIN. Spreading them first let a role's own metrics
|
|
262
|
+
// override them, which is the disagreement this was written to remove: the point
|
|
263
|
+
// is that adoption has exactly one source, not that it has a default.
|
|
264
|
+
data: { ...result.metrics ?? {}, ...adoptionFields }
|
|
254
265
|
});
|
|
255
266
|
worstCode = Math.max(worstCode, result.code);
|
|
256
267
|
} else if (params.verb === "inspect") {
|
|
257
|
-
const [config,
|
|
258
|
-
const ok = !
|
|
268
|
+
const [config, adoption2] = await Promise.all([adapter.inspect(ctx), adapter.status(ctx)]);
|
|
269
|
+
const ok = !adoption2.unreadable && (!adoption2.adopted || adoption2.conformant);
|
|
259
270
|
results.push({
|
|
260
271
|
project: module.name,
|
|
261
272
|
target,
|
|
@@ -265,7 +276,7 @@ async function analyse(params) {
|
|
|
265
276
|
// `inspect` is typed as `unknown` (each role shapes its own detail), so the
|
|
266
277
|
// merge is guarded rather than assumed: a role returning a non-object still
|
|
267
278
|
// gets its adoption fields instead of throwing here.
|
|
268
|
-
data: config && typeof config === "object" ? { ...config, ...
|
|
279
|
+
data: config && typeof config === "object" ? { ...config, ...adoption2 } : { ...adoption2 }
|
|
269
280
|
});
|
|
270
281
|
if (!ok) worstCode = Math.max(worstCode, 1);
|
|
271
282
|
} else {
|
|
@@ -140,8 +140,8 @@ var PRESET_NAMES = ["react", "nest", "svelte", "node", "tools"];
|
|
|
140
140
|
|
|
141
141
|
// src/roles/format/adapters/oxfmt/oxfmt.adapter.ts
|
|
142
142
|
import { spawnSync } from "child_process";
|
|
143
|
-
import { existsSync as existsSync10, readFileSync as
|
|
144
|
-
import { join as
|
|
143
|
+
import { existsSync as existsSync10, readFileSync as readFileSync8 } from "fs";
|
|
144
|
+
import { join as join11 } from "path";
|
|
145
145
|
|
|
146
146
|
// src/core/config/has-source.ts
|
|
147
147
|
import { readdirSync } from "fs";
|
|
@@ -548,6 +548,12 @@ function invokesFormat(script) {
|
|
|
548
548
|
function keepsOtherCommands2(script, command) {
|
|
549
549
|
return keepsOtherCommands(script, command ?? SENTINEL_FORMAT_COMMAND);
|
|
550
550
|
}
|
|
551
|
+
function writesWhenRewritten(name, command) {
|
|
552
|
+
if (/[:-](fix|write)$/.test(name)) return true;
|
|
553
|
+
if (CHECK_SCRIPT_NAMES.has(name) || /[:-]check$/.test(name)) return false;
|
|
554
|
+
return /--write\b/.test(command);
|
|
555
|
+
}
|
|
556
|
+
var CHECK_SCRIPT_NAMES = /* @__PURE__ */ new Set(["lint", "format", "check", "test", "typecheck", "verify"]);
|
|
551
557
|
|
|
552
558
|
// src/roles/format/inherited-ignores.ts
|
|
553
559
|
import { existsSync as existsSync7, readFileSync as readFileSync4 } from "fs";
|
|
@@ -578,6 +584,42 @@ function inheritedIgnorePatterns(workspaceRoot) {
|
|
|
578
584
|
return [...new Set(carried)].sort();
|
|
579
585
|
}
|
|
580
586
|
|
|
587
|
+
// src/roles/format/oxfmt-diagnostics.ts
|
|
588
|
+
var MESSAGE = /^\s*[x\u00d7]\s+(.+?)\s*$/;
|
|
589
|
+
var LOCATION = /[,\u256d][-\u2500]\[([^\]]+?):(\d+):\d+\]/;
|
|
590
|
+
var ANSI = /\u001b\[[0-9;]*m/g;
|
|
591
|
+
function plain(output) {
|
|
592
|
+
return output.replace(ANSI, "");
|
|
593
|
+
}
|
|
594
|
+
function unparseableFiles(output) {
|
|
595
|
+
const diagnostics = [];
|
|
596
|
+
let pending;
|
|
597
|
+
for (const raw of plain(output).split("\n")) {
|
|
598
|
+
const location = LOCATION.exec(raw);
|
|
599
|
+
if (location && pending) {
|
|
600
|
+
diagnostics.push({
|
|
601
|
+
file: location[1] ?? "",
|
|
602
|
+
line: Number(location[2] ?? 0),
|
|
603
|
+
message: pending
|
|
604
|
+
});
|
|
605
|
+
pending = void 0;
|
|
606
|
+
continue;
|
|
607
|
+
}
|
|
608
|
+
const message = MESSAGE.exec(raw);
|
|
609
|
+
if (message) pending = message[1];
|
|
610
|
+
}
|
|
611
|
+
return diagnostics;
|
|
612
|
+
}
|
|
613
|
+
function oxfmtSaid(output) {
|
|
614
|
+
const lines = plain(output).split("\n");
|
|
615
|
+
const diagnostic = lines.find((line) => MESSAGE.test(line));
|
|
616
|
+
if (diagnostic) return MESSAGE.exec(diagnostic)?.[1] ?? diagnostic.trim();
|
|
617
|
+
return [...lines].reverse().find((line) => line.trim().length > 0)?.trim() ?? "no output";
|
|
618
|
+
}
|
|
619
|
+
function needsSvelteCompiler(output) {
|
|
620
|
+
return /svelte\/compiler/.test(output);
|
|
621
|
+
}
|
|
622
|
+
|
|
581
623
|
// src/roles/format/presets/base.json
|
|
582
624
|
var base_default = {
|
|
583
625
|
printWidth: 80,
|
|
@@ -594,17 +636,21 @@ var base_default = {
|
|
|
594
636
|
};
|
|
595
637
|
|
|
596
638
|
// src/roles/format/preset-data.ts
|
|
597
|
-
var FORMAT_PRESETS = ["base"];
|
|
639
|
+
var FORMAT_PRESETS = ["base", "svelte"];
|
|
598
640
|
function hasFormatPreset(name) {
|
|
599
641
|
return FORMAT_PRESETS.includes(name);
|
|
600
642
|
}
|
|
601
|
-
function
|
|
602
|
-
return
|
|
643
|
+
function formatPresetName(declared) {
|
|
644
|
+
return declared === "svelte" ? "svelte" : "base";
|
|
645
|
+
}
|
|
646
|
+
function formatPresetFor(preset) {
|
|
647
|
+
const base = base_default;
|
|
648
|
+
return formatPresetName(preset) === "svelte" ? { ...base, svelte: true } : base;
|
|
603
649
|
}
|
|
604
650
|
|
|
605
651
|
// src/roles/format/prettier-config.ts
|
|
606
|
-
import { existsSync as existsSync8, readFileSync as
|
|
607
|
-
import { join as
|
|
652
|
+
import { existsSync as existsSync8, readFileSync as readFileSync6 } from "fs";
|
|
653
|
+
import { join as join9 } from "path";
|
|
608
654
|
|
|
609
655
|
// src/shared/jsonc.ts
|
|
610
656
|
import { parse, printParseErrorCode } from "jsonc-parser";
|
|
@@ -618,17 +664,78 @@ function parseJsonc(text, source = "config") {
|
|
|
618
664
|
return value;
|
|
619
665
|
}
|
|
620
666
|
|
|
667
|
+
// src/roles/format/resolve-oxfmt.ts
|
|
668
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
669
|
+
import { createRequire as createRequire2 } from "module";
|
|
670
|
+
import { dirname as dirname4, join as join8 } from "path";
|
|
671
|
+
function resolveOxfmt(cwd) {
|
|
672
|
+
return resolveBin(cwd, "oxfmt") ?? binFromOwnInstall("oxfmt", "oxfmt");
|
|
673
|
+
}
|
|
674
|
+
function oxfmtRejects(key, value) {
|
|
675
|
+
const schema = configSchema();
|
|
676
|
+
const property = schema?.properties?.[key];
|
|
677
|
+
if (!property) return false;
|
|
678
|
+
const definition = resolveRef(schema, property);
|
|
679
|
+
if (Array.isArray(definition.enum)) return !definition.enum.includes(value);
|
|
680
|
+
if (definition.type === "boolean") return typeof value !== "boolean";
|
|
681
|
+
if (definition.type === "integer" || definition.type === "number")
|
|
682
|
+
return typeof value !== "number";
|
|
683
|
+
return false;
|
|
684
|
+
}
|
|
685
|
+
function resolveRef(schema, node) {
|
|
686
|
+
const ref = node.$ref ?? node.allOf?.[0]?.$ref;
|
|
687
|
+
const name = ref?.startsWith("#/definitions/") ? ref.slice("#/definitions/".length) : void 0;
|
|
688
|
+
return (name ? schema.definitions?.[name] : void 0) ?? node;
|
|
689
|
+
}
|
|
690
|
+
var cachedSchema;
|
|
691
|
+
function configSchema() {
|
|
692
|
+
if (cachedSchema !== void 0) return cachedSchema ?? void 0;
|
|
693
|
+
cachedSchema = readConfigSchema() ?? null;
|
|
694
|
+
return cachedSchema ?? void 0;
|
|
695
|
+
}
|
|
696
|
+
function readConfigSchema() {
|
|
697
|
+
try {
|
|
698
|
+
const manifest = createRequire2(import.meta.url).resolve("oxfmt/package.json");
|
|
699
|
+
const schemaPath = join8(dirname4(manifest), "configuration_schema.json");
|
|
700
|
+
return JSON.parse(readFileSync5(schemaPath, "utf8"));
|
|
701
|
+
} catch {
|
|
702
|
+
return void 0;
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
|
|
621
706
|
// src/roles/format/prettier-config.ts
|
|
622
707
|
var UNSUPPORTED = /* @__PURE__ */ new Set(["plugins", "parser", "filepath", "rangeStart", "rangeEnd"]);
|
|
708
|
+
function toOxfmtOverrides(value) {
|
|
709
|
+
if (!Array.isArray(value)) return { overrides: [], unresolved: [] };
|
|
710
|
+
const overrides = [];
|
|
711
|
+
const unresolved = [];
|
|
712
|
+
for (const entry of value) {
|
|
713
|
+
if (typeof entry !== "object" || entry === null) continue;
|
|
714
|
+
const { files, options } = entry;
|
|
715
|
+
const patterns = typeof files === "string" ? [files] : Array.isArray(files) ? files : [];
|
|
716
|
+
if (patterns.length === 0) continue;
|
|
717
|
+
const kept = {};
|
|
718
|
+
for (const [key, option] of Object.entries(options ?? {})) {
|
|
719
|
+
const where = `overrides[${patterns.join(", ")}].${key}`;
|
|
720
|
+
if (UNSUPPORTED.has(key)) unresolved.push(where);
|
|
721
|
+
else if (!PERMITTED_LOCAL_KEYS.includes(key)) continue;
|
|
722
|
+
else if (oxfmtRejects(key, option)) {
|
|
723
|
+
unresolved.push(`${where}: ${JSON.stringify(option)} (oxfmt does not accept that value)`);
|
|
724
|
+
} else kept[key] = option;
|
|
725
|
+
}
|
|
726
|
+
if (Object.keys(kept).length > 0) overrides.push({ files: patterns, options: kept });
|
|
727
|
+
}
|
|
728
|
+
return { overrides, unresolved };
|
|
729
|
+
}
|
|
623
730
|
function readPrettierSettings(cwd) {
|
|
624
|
-
const file = PRETTIER_CONFIG_FILES.find((name) => existsSync8(
|
|
731
|
+
const file = PRETTIER_CONFIG_FILES.find((name) => existsSync8(join9(cwd, name)));
|
|
625
732
|
if (!file) return { options: {}, unresolved: [] };
|
|
626
733
|
if (/\.(js|cjs|mjs)$/.test(file)) {
|
|
627
734
|
return { options: {}, file, unresolved: [`${file} is JavaScript, so it was not executed`] };
|
|
628
735
|
}
|
|
629
736
|
let parsed;
|
|
630
737
|
try {
|
|
631
|
-
parsed = parseJsonc(
|
|
738
|
+
parsed = parseJsonc(readFileSync6(join9(cwd, file), "utf8"), file);
|
|
632
739
|
} catch {
|
|
633
740
|
return { options: {}, file, unresolved: [`${file} could not be parsed`] };
|
|
634
741
|
}
|
|
@@ -636,10 +743,21 @@ function readPrettierSettings(cwd) {
|
|
|
636
743
|
const unresolved = [];
|
|
637
744
|
for (const [key, value] of Object.entries(parsed)) {
|
|
638
745
|
if (UNSUPPORTED.has(key)) {
|
|
639
|
-
|
|
746
|
+
const named = key === "plugins" && Array.isArray(value) ? value.map((plugin) => `plugins: ${String(plugin)}`) : [key];
|
|
747
|
+
unresolved.push(...named.map((entry) => `${entry} (oxfmt has no equivalent)`));
|
|
748
|
+
continue;
|
|
749
|
+
}
|
|
750
|
+
if (key === "overrides") {
|
|
751
|
+
const translated = toOxfmtOverrides(value);
|
|
752
|
+
if (translated.overrides.length > 0) options.overrides = translated.overrides;
|
|
753
|
+
unresolved.push(...translated.unresolved.map((entry) => `${entry} (oxfmt has no equivalent)`));
|
|
640
754
|
continue;
|
|
641
755
|
}
|
|
642
756
|
if (PERMITTED_LOCAL_KEYS.includes(key) || ALWAYS_LOCAL_KEYS.includes(key)) {
|
|
757
|
+
if (oxfmtRejects(key, value)) {
|
|
758
|
+
unresolved.push(`${key}: ${JSON.stringify(value)} (oxfmt does not accept that value)`);
|
|
759
|
+
continue;
|
|
760
|
+
}
|
|
643
761
|
options[key] = value;
|
|
644
762
|
}
|
|
645
763
|
}
|
|
@@ -647,8 +765,8 @@ function readPrettierSettings(cwd) {
|
|
|
647
765
|
}
|
|
648
766
|
|
|
649
767
|
// src/roles/format/read-adoption.ts
|
|
650
|
-
import { existsSync as existsSync9, readFileSync as
|
|
651
|
-
import { join as
|
|
768
|
+
import { existsSync as existsSync9, readFileSync as readFileSync7 } from "fs";
|
|
769
|
+
import { join as join10 } from "path";
|
|
652
770
|
var NOT_ADOPTED = (configFile, unreadable = null) => ({
|
|
653
771
|
configFile,
|
|
654
772
|
preset: null,
|
|
@@ -663,11 +781,11 @@ function sameValue(a, b) {
|
|
|
663
781
|
return JSON.stringify(a) === JSON.stringify(b);
|
|
664
782
|
}
|
|
665
783
|
function readFormatAdoption(cwd) {
|
|
666
|
-
const path =
|
|
784
|
+
const path = join10(cwd, FORMAT_CONFIG_FILE);
|
|
667
785
|
if (!existsSync9(path)) return NOT_ADOPTED(null);
|
|
668
786
|
let parsed;
|
|
669
787
|
try {
|
|
670
|
-
parsed = parseJsonc(
|
|
788
|
+
parsed = parseJsonc(readFileSync7(path, "utf8"), FORMAT_CONFIG_FILE);
|
|
671
789
|
} catch (error) {
|
|
672
790
|
const reason = error instanceof Error ? error.message : String(error);
|
|
673
791
|
return NOT_ADOPTED(FORMAT_CONFIG_FILE, `${FORMAT_CONFIG_FILE} could not be parsed (${reason})`);
|
|
@@ -709,13 +827,8 @@ function readFormatAdoption(cwd) {
|
|
|
709
827
|
};
|
|
710
828
|
}
|
|
711
829
|
|
|
712
|
-
// src/roles/format/resolve-oxfmt.ts
|
|
713
|
-
function resolveOxfmt(cwd) {
|
|
714
|
-
return resolveBin(cwd, "oxfmt") ?? binFromOwnInstall("oxfmt", "oxfmt");
|
|
715
|
-
}
|
|
716
|
-
|
|
717
830
|
// src/roles/format/adapters/oxfmt/oxfmt.adapter.ts
|
|
718
|
-
var
|
|
831
|
+
var DEFAULT_PRESET = "base";
|
|
719
832
|
var OXFMT_VALUE_FLAGS = ["-c", "--config", "--ignore-path", "--threads", "--stdin-filepath"];
|
|
720
833
|
var OxfmtAdapter = class extends BaseAdapter {
|
|
721
834
|
target = "format";
|
|
@@ -732,7 +845,8 @@ var OxfmtAdapter = class extends BaseAdapter {
|
|
|
732
845
|
};
|
|
733
846
|
}
|
|
734
847
|
const own = readOwnPackage();
|
|
735
|
-
const
|
|
848
|
+
const presetName = formatPresetName(context.preset);
|
|
849
|
+
const preset = formatPresetFor(presetName);
|
|
736
850
|
const existing = readFormatAdoption(context.cwd);
|
|
737
851
|
const prettier = readPrettierSettings(context.cwd);
|
|
738
852
|
const local = existing.adopted ? { ...existing.local } : { ...prettier.options };
|
|
@@ -748,7 +862,7 @@ var OxfmtAdapter = class extends BaseAdapter {
|
|
|
748
862
|
const unchanged = existing.adopted && existing.conformant;
|
|
749
863
|
const version = unchanged && existing.presetVersion ? existing.presetVersion : own.version;
|
|
750
864
|
const config = {
|
|
751
|
-
[PROVENANCE_KEY]: { preset:
|
|
865
|
+
[PROVENANCE_KEY]: { preset: presetName, version, local: localKeys },
|
|
752
866
|
...preset,
|
|
753
867
|
...Object.fromEntries(
|
|
754
868
|
[...localKeys, ...ALWAYS_LOCAL_KEYS].flatMap(
|
|
@@ -763,7 +877,7 @@ var OxfmtAdapter = class extends BaseAdapter {
|
|
|
763
877
|
manifestOperation(context.cwd, this.formatScripts(context.cwd))
|
|
764
878
|
];
|
|
765
879
|
const prettierConfigs = PRETTIER_CONFIG_FILES.filter(
|
|
766
|
-
(name) => existsSync10(
|
|
880
|
+
(name) => existsSync10(join11(context.cwd, name))
|
|
767
881
|
);
|
|
768
882
|
for (const name of prettierConfigs) operations.push({ kind: "delete", path: name });
|
|
769
883
|
const removableDeps = this.modulePrettierDependencies(context.cwd);
|
|
@@ -772,7 +886,7 @@ var OxfmtAdapter = class extends BaseAdapter {
|
|
|
772
886
|
}
|
|
773
887
|
operations.push(...nxTargetOperations({ cwd: context.cwd, targets: formatTargets() }));
|
|
774
888
|
const notes = [
|
|
775
|
-
`materialized the ${
|
|
889
|
+
`materialized the ${presetName} format preset into ${FORMAT_CONFIG_FILE} (${own.name}@${own.version}). oxfmt has no \`extends\`, so the values live here; \`sentinel --status --format\` reports modules left behind by a preset change`
|
|
776
890
|
];
|
|
777
891
|
if (localKeys.length > 0) {
|
|
778
892
|
notes.push(
|
|
@@ -784,9 +898,12 @@ var OxfmtAdapter = class extends BaseAdapter {
|
|
|
784
898
|
`carried ${inherited.length} ignore pattern(s) from the workspace .prettierignore (${inherited.join(", ")}). oxfmt reads that file from the CURRENT directory and sentinel runs it in this module, so without them adoption would start formatting files the repo has always excluded`
|
|
785
899
|
);
|
|
786
900
|
}
|
|
787
|
-
|
|
901
|
+
const lost = prettier.unresolved.filter(
|
|
902
|
+
(entry) => !(presetName === "svelte" && /svelte/i.test(entry))
|
|
903
|
+
);
|
|
904
|
+
if (lost.length > 0) {
|
|
788
905
|
notes.push(
|
|
789
|
-
`COULD NOT carry ${
|
|
906
|
+
`COULD NOT carry ${lost.join("; ")}. Check it and add what you need to ${FORMAT_CONFIG_FILE}, declaring the key in \`${PROVENANCE_KEY}.local\` so a re-init keeps it`
|
|
790
907
|
);
|
|
791
908
|
}
|
|
792
909
|
if (prettierConfigs.length > 0) {
|
|
@@ -819,7 +936,45 @@ var OxfmtAdapter = class extends BaseAdapter {
|
|
|
819
936
|
async afterInit(ctx) {
|
|
820
937
|
const oxfmt = resolveOxfmt(ctx.cwd);
|
|
821
938
|
if (!oxfmt) return;
|
|
822
|
-
spawnSync(oxfmt, ["--write", "."], { cwd: ctx.cwd, encoding: "utf8" });
|
|
939
|
+
const pass = spawnSync(oxfmt, ["--write", "."], { cwd: ctx.cwd, encoding: "utf8" });
|
|
940
|
+
if (pass.status === 0) return;
|
|
941
|
+
const output = `${pass.stdout ?? ""}${pass.stderr ?? ""}`;
|
|
942
|
+
const unparseable = unparseableFiles(output);
|
|
943
|
+
const warn = palette(process.stderr).warn;
|
|
944
|
+
if (unparseable.length > 0) {
|
|
945
|
+
process.stderr.write(
|
|
946
|
+
warn(
|
|
947
|
+
` ${unparseable.length} file(s) oxfmt cannot parse, so they were NOT formatted and \`pnpm run ${FORMAT_SCRIPT_NAME}\` fails until they are fixed:
|
|
948
|
+
`
|
|
949
|
+
)
|
|
950
|
+
);
|
|
951
|
+
for (const { file, line, message } of unparseable) {
|
|
952
|
+
process.stderr.write(warn(` ${file}:${line} ${message}
|
|
953
|
+
`));
|
|
954
|
+
}
|
|
955
|
+
process.stderr.write(
|
|
956
|
+
palette(process.stderr).dim(
|
|
957
|
+
` These are syntax errors Prettier recovered from and oxfmt refuses. They are the module's, not the migration's: fix them (usually one line), or exclude them in \`ignorePatterns\`.
|
|
958
|
+
`
|
|
959
|
+
)
|
|
960
|
+
);
|
|
961
|
+
return;
|
|
962
|
+
}
|
|
963
|
+
if (needsSvelteCompiler(output)) {
|
|
964
|
+
process.stderr.write(
|
|
965
|
+
warn(
|
|
966
|
+
` .svelte files are not formatted yet: oxfmt loads the Svelte compiler from this module, and it is not installed here. Run \`pnpm install\`, then \`pnpm run ${FORMAT_SCRIPT_NAME}:fix\`.
|
|
967
|
+
`
|
|
968
|
+
)
|
|
969
|
+
);
|
|
970
|
+
return;
|
|
971
|
+
}
|
|
972
|
+
process.stderr.write(
|
|
973
|
+
warn(
|
|
974
|
+
` the format pass did not finish, so this module is not formatted yet: ${oxfmtSaid(output)}
|
|
975
|
+
`
|
|
976
|
+
)
|
|
977
|
+
);
|
|
823
978
|
}
|
|
824
979
|
/** Check formatting; `--fix` writes. */
|
|
825
980
|
async run(ctx) {
|
|
@@ -870,7 +1025,7 @@ var OxfmtAdapter = class extends BaseAdapter {
|
|
|
870
1025
|
*/
|
|
871
1026
|
async inspect(ctx) {
|
|
872
1027
|
const adoption = readFormatAdoption(ctx.cwd);
|
|
873
|
-
const preset = formatPresetFor(adoption.preset ??
|
|
1028
|
+
const preset = formatPresetFor(adoption.preset ?? DEFAULT_PRESET);
|
|
874
1029
|
const options = { ...preset, ...adoption.local };
|
|
875
1030
|
const describe = (value) => typeof value === "string" ? value : JSON.stringify(value);
|
|
876
1031
|
return {
|
|
@@ -881,7 +1036,7 @@ var OxfmtAdapter = class extends BaseAdapter {
|
|
|
881
1036
|
options,
|
|
882
1037
|
overrides: Object.entries(adoption.local).filter(([key]) => !ALWAYS_LOCAL_KEYS.includes(key)).map(([key, value]) => ({
|
|
883
1038
|
rule: key,
|
|
884
|
-
reason: key in preset ? `${describe(value)}, where the ${adoption.preset ??
|
|
1039
|
+
reason: key in preset ? `${describe(value)}, where the ${adoption.preset ?? DEFAULT_PRESET} preset says ${describe(preset[key])}` : `${describe(value)}, which the preset does not set`
|
|
885
1040
|
})),
|
|
886
1041
|
// Which files this module does not format at all. A long list here is worth a look:
|
|
887
1042
|
// it is the quiet way a module stops being covered.
|
|
@@ -902,7 +1057,7 @@ var OxfmtAdapter = class extends BaseAdapter {
|
|
|
902
1057
|
*/
|
|
903
1058
|
async report(ctx) {
|
|
904
1059
|
const adoption = readFormatAdoption(ctx.cwd);
|
|
905
|
-
const base = {
|
|
1060
|
+
const base = {};
|
|
906
1061
|
if (!adoption.adopted) return { ok: true, code: 0, metrics: { ...base, unformatted: 0 } };
|
|
907
1062
|
const oxfmt = resolveOxfmt(ctx.cwd);
|
|
908
1063
|
if (!oxfmt) {
|
|
@@ -948,7 +1103,7 @@ var OxfmtAdapter = class extends BaseAdapter {
|
|
|
948
1103
|
modulePrettierDependencies(cwd) {
|
|
949
1104
|
const isPrettierPackage = (name) => name === "prettier" || name.startsWith("prettier-plugin-") || name.startsWith("@prettier/");
|
|
950
1105
|
try {
|
|
951
|
-
const manifest = JSON.parse(
|
|
1106
|
+
const manifest = JSON.parse(readFileSync8(join11(cwd, "package.json"), "utf8"));
|
|
952
1107
|
return Object.keys(manifest.devDependencies ?? {}).filter(isPrettierPackage).map((name) => ["devDependencies", name]);
|
|
953
1108
|
} catch {
|
|
954
1109
|
return [];
|
|
@@ -981,7 +1136,7 @@ var OxfmtAdapter = class extends BaseAdapter {
|
|
|
981
1136
|
// script too and rewrote it into a second format run.
|
|
982
1137
|
ownCommand !== void 0 && /\bsentinel\b/.test(command) && /--format\b/.test(command);
|
|
983
1138
|
if (!needsRewrite) continue;
|
|
984
|
-
rewritten[name] = compose(command,
|
|
1139
|
+
rewritten[name] = compose(command, writesWhenRewritten(name, command));
|
|
985
1140
|
}
|
|
986
1141
|
return this.joinFormatIntoLint(scripts, rewritten, ownCommand);
|
|
987
1142
|
}
|
|
@@ -1028,8 +1183,8 @@ function registerFormat() {
|
|
|
1028
1183
|
|
|
1029
1184
|
// src/roles/lint/adapters/oxlint/oxlint.adapter.ts
|
|
1030
1185
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
1031
|
-
import { existsSync as existsSync16, readFileSync as
|
|
1032
|
-
import { join as
|
|
1186
|
+
import { existsSync as existsSync16, readFileSync as readFileSync14 } from "fs";
|
|
1187
|
+
import { join as join18 } from "path";
|
|
1033
1188
|
|
|
1034
1189
|
// src/core/config/deferred-rules.ts
|
|
1035
1190
|
function deferredRuleNames(rules) {
|
|
@@ -2864,22 +3019,28 @@ function parseOxlintDiagnostics(stdout) {
|
|
|
2864
3019
|
}
|
|
2865
3020
|
|
|
2866
3021
|
// src/core/config/read-adoption.ts
|
|
2867
|
-
import { existsSync as existsSync12, readFileSync as
|
|
2868
|
-
import { join as
|
|
3022
|
+
import { existsSync as existsSync12, readFileSync as readFileSync10 } from "fs";
|
|
3023
|
+
import { join as join13 } from "path";
|
|
2869
3024
|
|
|
2870
3025
|
// src/core/config/owned-keys.ts
|
|
2871
|
-
function presetOwnedKeys(config, permitted) {
|
|
3026
|
+
function presetOwnedKeys(config, permitted, presetSets) {
|
|
3027
|
+
if (!config) return [];
|
|
3028
|
+
return Object.keys(config).filter(
|
|
3029
|
+
(key) => !permitted.includes(key) && (presetSets === void 0 || presetSets.includes(key))
|
|
3030
|
+
);
|
|
3031
|
+
}
|
|
3032
|
+
function localOnlyKeys(config, permitted, presetSets) {
|
|
2872
3033
|
if (!config) return [];
|
|
2873
|
-
return Object.keys(config).filter((key) => !permitted.includes(key));
|
|
3034
|
+
return Object.keys(config).filter((key) => !permitted.includes(key) && !presetSets.includes(key));
|
|
2874
3035
|
}
|
|
2875
3036
|
|
|
2876
3037
|
// src/core/config/resolve-config-target.ts
|
|
2877
|
-
import { existsSync as existsSync11, readFileSync as
|
|
2878
|
-
import { join as
|
|
3038
|
+
import { existsSync as existsSync11, readFileSync as readFileSync9 } from "fs";
|
|
3039
|
+
import { join as join12 } from "path";
|
|
2879
3040
|
function readExtends(absolutePath) {
|
|
2880
3041
|
let parsed;
|
|
2881
3042
|
try {
|
|
2882
|
-
parsed = parseJsonc(
|
|
3043
|
+
parsed = parseJsonc(readFileSync9(absolutePath, "utf8"), absolutePath);
|
|
2883
3044
|
} catch {
|
|
2884
3045
|
return [];
|
|
2885
3046
|
}
|
|
@@ -2893,7 +3054,7 @@ function resolveConfigTarget(moduleDir, { candidates, markers, fallback }) {
|
|
|
2893
3054
|
let existing;
|
|
2894
3055
|
let existingExtendsSomething = false;
|
|
2895
3056
|
for (const candidate of candidates) {
|
|
2896
|
-
const absolutePath =
|
|
3057
|
+
const absolutePath = join12(moduleDir, candidate);
|
|
2897
3058
|
if (!existsSync11(absolutePath)) continue;
|
|
2898
3059
|
const chain = readExtends(absolutePath);
|
|
2899
3060
|
if (existing === void 0) {
|
|
@@ -2927,12 +3088,12 @@ var NOT_ADOPTED2 = (configFile, unreadable = null) => ({
|
|
|
2927
3088
|
});
|
|
2928
3089
|
function readAdoption(cwd, options) {
|
|
2929
3090
|
const target = resolveConfigTarget(cwd, options);
|
|
2930
|
-
if (target.reason === "none" || !existsSync12(
|
|
3091
|
+
if (target.reason === "none" || !existsSync12(join13(cwd, target.path))) {
|
|
2931
3092
|
return NOT_ADOPTED2(target.reason === "none" ? null : target.path);
|
|
2932
3093
|
}
|
|
2933
3094
|
let parsed;
|
|
2934
3095
|
try {
|
|
2935
|
-
parsed = parseJsonc(
|
|
3096
|
+
parsed = parseJsonc(readFileSync10(join13(cwd, target.path), "utf8"), target.path);
|
|
2936
3097
|
} catch (error) {
|
|
2937
3098
|
const reason = error instanceof Error ? error.message : String(error);
|
|
2938
3099
|
return NOT_ADOPTED2(target.path, `${target.path} could not be parsed (${reason})`);
|
|
@@ -2940,7 +3101,7 @@ function readAdoption(cwd, options) {
|
|
|
2940
3101
|
const preset = normaliseExtends(parsed.extends).find((entry) => options.presetPattern.test(entry)) ?? null;
|
|
2941
3102
|
if (preset === null) return NOT_ADOPTED2(target.path);
|
|
2942
3103
|
const settings = options.settingsKey === null ? parsed : parsed[options.settingsKey];
|
|
2943
|
-
const drift = presetOwnedKeys(settings, options.permitted);
|
|
3104
|
+
const drift = presetOwnedKeys(settings, options.permitted, options.presetOwns?.(preset));
|
|
2944
3105
|
return {
|
|
2945
3106
|
configFile: target.path,
|
|
2946
3107
|
preset,
|
|
@@ -2967,14 +3128,14 @@ function readLintAdoption(cwd) {
|
|
|
2967
3128
|
|
|
2968
3129
|
// src/roles/lint/resolve-oxlint.ts
|
|
2969
3130
|
import { existsSync as existsSync13 } from "fs";
|
|
2970
|
-
import { createRequire as
|
|
2971
|
-
import { delimiter as delimiter2, dirname as
|
|
3131
|
+
import { createRequire as createRequire3 } from "module";
|
|
3132
|
+
import { delimiter as delimiter2, dirname as dirname5, join as join14 } from "path";
|
|
2972
3133
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
2973
3134
|
var PACKAGE_OF = {
|
|
2974
3135
|
oxlint: "oxlint",
|
|
2975
3136
|
tsgolint: "oxlint-tsgolint"
|
|
2976
3137
|
};
|
|
2977
|
-
var require3 =
|
|
3138
|
+
var require3 = createRequire3(import.meta.url);
|
|
2978
3139
|
function resolveOxlint(cwd, name = "oxlint") {
|
|
2979
3140
|
return resolveBin(cwd, name) ?? binFromOwnInstall(PACKAGE_OF[name], name);
|
|
2980
3141
|
}
|
|
@@ -2984,7 +3145,7 @@ function canRunTypeAware(cwd) {
|
|
|
2984
3145
|
function oxlintPath(cwd, env) {
|
|
2985
3146
|
const shim = tsgolintShim(cwd);
|
|
2986
3147
|
if (!shim) return env.PATH;
|
|
2987
|
-
return [
|
|
3148
|
+
return [dirname5(shim), env.PATH].filter(Boolean).join(delimiter2);
|
|
2988
3149
|
}
|
|
2989
3150
|
function tsgolintShim(cwd) {
|
|
2990
3151
|
const fromModule = resolveBin(cwd, "tsgolint");
|
|
@@ -2992,17 +3153,17 @@ function tsgolintShim(cwd) {
|
|
|
2992
3153
|
const candidates = [];
|
|
2993
3154
|
for (const owner of ["oxlint-tsgolint", "oxlint"]) {
|
|
2994
3155
|
try {
|
|
2995
|
-
const packageDir =
|
|
2996
|
-
candidates.push(
|
|
2997
|
-
candidates.push(
|
|
3156
|
+
const packageDir = dirname5(require3.resolve(`${owner}/package.json`));
|
|
3157
|
+
candidates.push(join14(packageDir, "node_modules", ".bin", "tsgolint"));
|
|
3158
|
+
candidates.push(join14(packageDir, "..", ".bin", "tsgolint"));
|
|
2998
3159
|
} catch {
|
|
2999
3160
|
}
|
|
3000
3161
|
}
|
|
3001
3162
|
try {
|
|
3002
|
-
const ownRoot =
|
|
3003
|
-
candidates.push(
|
|
3163
|
+
const ownRoot = dirname5(require3.resolve("@hublo/sentinel/package.json"));
|
|
3164
|
+
candidates.push(join14(ownRoot, "node_modules", ".bin", "tsgolint"));
|
|
3004
3165
|
} catch {
|
|
3005
|
-
candidates.push(resolveBin(
|
|
3166
|
+
candidates.push(resolveBin(dirname5(fileURLToPath2(import.meta.url)), "tsgolint") ?? "");
|
|
3006
3167
|
}
|
|
3007
3168
|
return candidates.find((candidate) => candidate !== "" && existsSync13(candidate));
|
|
3008
3169
|
}
|
|
@@ -3011,12 +3172,12 @@ function oxlintSearchPath(cwd) {
|
|
|
3011
3172
|
}
|
|
3012
3173
|
|
|
3013
3174
|
// src/roles/lint/adapters/oxlint/plan.ts
|
|
3014
|
-
import { existsSync as existsSync15, readFileSync as
|
|
3015
|
-
import { join as
|
|
3175
|
+
import { existsSync as existsSync15, readFileSync as readFileSync13 } from "fs";
|
|
3176
|
+
import { join as join17 } from "path";
|
|
3016
3177
|
|
|
3017
3178
|
// src/roles/lint/eslint-ignores.ts
|
|
3018
|
-
import { existsSync as existsSync14, readFileSync as
|
|
3019
|
-
import { join as
|
|
3179
|
+
import { existsSync as existsSync14, readFileSync as readFileSync11 } from "fs";
|
|
3180
|
+
import { join as join15 } from "path";
|
|
3020
3181
|
var IGNORE_BLOCKS = [/\bignores\s*:\s*\[([^\]]*)\]/g, /\bglobalIgnores\s*\(\s*\[([^\]]*)\]/g];
|
|
3021
3182
|
var STRING_LITERAL = /['"`]([^'"`]+)['"`]/g;
|
|
3022
3183
|
var OPAQUE_SOURCE = /\b(includeIgnoreFile)\s*\([^)]*\)/g;
|
|
@@ -3029,11 +3190,11 @@ function readRootEslintIgnores(root) {
|
|
|
3029
3190
|
return { patterns: patterns.filter((pattern) => pattern.startsWith("**/")), unresolved };
|
|
3030
3191
|
}
|
|
3031
3192
|
function readEslintIgnores(cwd) {
|
|
3032
|
-
const config = ESLINT_CONFIG_FILES.map((name) =>
|
|
3193
|
+
const config = ESLINT_CONFIG_FILES.map((name) => join15(cwd, name)).find((path) => existsSync14(path));
|
|
3033
3194
|
if (!config) return { patterns: [], unresolved: [] };
|
|
3034
3195
|
let source;
|
|
3035
3196
|
try {
|
|
3036
|
-
source =
|
|
3197
|
+
source = readFileSync11(config, "utf8");
|
|
3037
3198
|
} catch {
|
|
3038
3199
|
return { patterns: [], unresolved: [] };
|
|
3039
3200
|
}
|
|
@@ -3087,8 +3248,8 @@ function lintPresetFor(preset) {
|
|
|
3087
3248
|
}
|
|
3088
3249
|
|
|
3089
3250
|
// src/roles/lint/rename-suppressions.ts
|
|
3090
|
-
import { readdirSync as readdirSync2, readFileSync as
|
|
3091
|
-
import { join as
|
|
3251
|
+
import { readdirSync as readdirSync2, readFileSync as readFileSync12, statSync } from "fs";
|
|
3252
|
+
import { join as join16, relative as relative2 } from "path";
|
|
3092
3253
|
var SOURCE_EXTENSIONS = [
|
|
3093
3254
|
".ts",
|
|
3094
3255
|
".tsx",
|
|
@@ -3135,7 +3296,7 @@ function* sourceFiles(dir) {
|
|
|
3135
3296
|
return;
|
|
3136
3297
|
}
|
|
3137
3298
|
for (const entry of entries) {
|
|
3138
|
-
const full =
|
|
3299
|
+
const full = join16(dir, entry);
|
|
3139
3300
|
let isDirectory;
|
|
3140
3301
|
try {
|
|
3141
3302
|
isDirectory = statSync(full).isDirectory();
|
|
@@ -3155,7 +3316,7 @@ function findSuppressionRenames(cwd, renames) {
|
|
|
3155
3316
|
for (const file of sourceFiles(cwd)) {
|
|
3156
3317
|
let content;
|
|
3157
3318
|
try {
|
|
3158
|
-
content =
|
|
3319
|
+
content = readFileSync12(file, "utf8");
|
|
3159
3320
|
} catch {
|
|
3160
3321
|
continue;
|
|
3161
3322
|
}
|
|
@@ -3228,7 +3389,7 @@ function plan(context) {
|
|
|
3228
3389
|
if (!hasLintPreset(context.preset)) {
|
|
3229
3390
|
return {
|
|
3230
3391
|
operations: [],
|
|
3231
|
-
|
|
3392
|
+
skipped: `sentinel does not ship a lint preset named "${context.preset}" yet (shipped: ${LINT_PRESETS.join(", ")}). Nothing was written; the module keeps its current linter and the other roles adopt normally.`
|
|
3232
3393
|
};
|
|
3233
3394
|
}
|
|
3234
3395
|
const current = presetOfVariant(presetNameFromPath(readLintAdoption(context.cwd).preset));
|
|
@@ -3273,7 +3434,7 @@ function plan(context) {
|
|
|
3273
3434
|
keys: removableDeps
|
|
3274
3435
|
});
|
|
3275
3436
|
}
|
|
3276
|
-
const eslintConfigs = ESLINT_CONFIG_FILES.filter((name) => existsSync15(
|
|
3437
|
+
const eslintConfigs = ESLINT_CONFIG_FILES.filter((name) => existsSync15(join17(context.cwd, name)));
|
|
3277
3438
|
for (const name of eslintConfigs) operations.push({ kind: "delete", path: name });
|
|
3278
3439
|
operations.push(
|
|
3279
3440
|
...nxTargetOperations({
|
|
@@ -3368,7 +3529,7 @@ function lintScripts(cwd) {
|
|
|
3368
3529
|
function committedIgnorePatterns(cwd) {
|
|
3369
3530
|
try {
|
|
3370
3531
|
const parsed = parseJsonc(
|
|
3371
|
-
|
|
3532
|
+
readFileSync13(join17(cwd, LINT_CONFIG_FILE), "utf8"),
|
|
3372
3533
|
LINT_CONFIG_FILE
|
|
3373
3534
|
);
|
|
3374
3535
|
return Array.isArray(parsed.ignorePatterns) ? parsed.ignorePatterns.filter((entry) => typeof entry === "string") : [];
|
|
@@ -3380,7 +3541,7 @@ function moduleEslintDependencies(cwd) {
|
|
|
3380
3541
|
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/");
|
|
3381
3542
|
let manifest;
|
|
3382
3543
|
try {
|
|
3383
|
-
manifest = JSON.parse(
|
|
3544
|
+
manifest = JSON.parse(readFileSync13(join17(cwd, "package.json"), "utf8"));
|
|
3384
3545
|
} catch {
|
|
3385
3546
|
return [];
|
|
3386
3547
|
}
|
|
@@ -3479,7 +3640,7 @@ var OxlintAdapter = class extends BaseAdapter {
|
|
|
3479
3640
|
this.fixPass(ctx, oxlint, env);
|
|
3480
3641
|
}
|
|
3481
3642
|
async run(ctx) {
|
|
3482
|
-
if (!existsSync16(
|
|
3643
|
+
if (!existsSync16(join18(ctx.cwd, LINT_CONFIG_FILE))) {
|
|
3483
3644
|
process.stderr.write(
|
|
3484
3645
|
`sentinel lint(oxlint): no ${LINT_CONFIG_FILE} in this module; run \`sentinel --init --lint\` to adopt.
|
|
3485
3646
|
`
|
|
@@ -3572,9 +3733,9 @@ var OxlintAdapter = class extends BaseAdapter {
|
|
|
3572
3733
|
if (target) return 0;
|
|
3573
3734
|
let total = 0;
|
|
3574
3735
|
try {
|
|
3575
|
-
const stub = JSON.parse(
|
|
3736
|
+
const stub = JSON.parse(readFileSync14(join18(cwd, LINT_CONFIG_FILE), "utf8"));
|
|
3576
3737
|
for (const entry of stub.extends ?? []) {
|
|
3577
|
-
const preset = JSON.parse(
|
|
3738
|
+
const preset = JSON.parse(readFileSync14(join18(cwd, entry), "utf8"));
|
|
3578
3739
|
total += Object.keys(preset.rules ?? {}).length;
|
|
3579
3740
|
}
|
|
3580
3741
|
} catch {
|
|
@@ -3585,10 +3746,9 @@ var OxlintAdapter = class extends BaseAdapter {
|
|
|
3585
3746
|
/** Lint health for `--report`. Deepened in a later step; today it is the run outcome. */
|
|
3586
3747
|
async report(ctx) {
|
|
3587
3748
|
const adoption = readLintAdoption(ctx.cwd);
|
|
3588
|
-
if (!adoption.adopted) return { ok: true, code: 0, metrics: {
|
|
3749
|
+
if (!adoption.adopted) return { ok: true, code: 0, metrics: {} };
|
|
3589
3750
|
const oxlint = resolveOxlint(ctx.cwd);
|
|
3590
|
-
if (!oxlint)
|
|
3591
|
-
return { ok: false, code: 1, metrics: { adopted: true, error: "oxlint not found" } };
|
|
3751
|
+
if (!oxlint) return { ok: false, code: 1, metrics: { error: "oxlint not found" } };
|
|
3592
3752
|
const typeAware = canRunTypeAware(ctx.cwd) ? ["--type-aware"] : [];
|
|
3593
3753
|
const result = spawnSync2(oxlint, ["-c", LINT_CONFIG_FILE, ...typeAware, "-f", "json", "."], {
|
|
3594
3754
|
cwd: ctx.cwd,
|
|
@@ -3596,7 +3756,7 @@ var OxlintAdapter = class extends BaseAdapter {
|
|
|
3596
3756
|
env: { ...process.env, PATH: oxlintPath(ctx.cwd, process.env) }
|
|
3597
3757
|
});
|
|
3598
3758
|
if (result.error) {
|
|
3599
|
-
return { ok: false, code: 1, metrics: {
|
|
3759
|
+
return { ok: false, code: 1, metrics: { error: result.error.message } };
|
|
3600
3760
|
}
|
|
3601
3761
|
const diagnostics = parseOxlintDiagnostics(result.stdout ?? "");
|
|
3602
3762
|
const errors = diagnostics.filter((entry) => entry.severity === "error").length;
|
|
@@ -3614,7 +3774,6 @@ var OxlintAdapter = class extends BaseAdapter {
|
|
|
3614
3774
|
ok: errors === 0,
|
|
3615
3775
|
code: errors === 0 ? 0 : 1,
|
|
3616
3776
|
metrics: {
|
|
3617
|
-
adopted: true,
|
|
3618
3777
|
errors,
|
|
3619
3778
|
warnings,
|
|
3620
3779
|
rules,
|
|
@@ -3642,12 +3801,12 @@ var OxlintAdapter = class extends BaseAdapter {
|
|
|
3642
3801
|
unresolvedPreset(cwd) {
|
|
3643
3802
|
let parsed;
|
|
3644
3803
|
try {
|
|
3645
|
-
parsed = JSON.parse(
|
|
3804
|
+
parsed = JSON.parse(readFileSync14(join18(cwd, LINT_CONFIG_FILE), "utf8"));
|
|
3646
3805
|
} catch {
|
|
3647
3806
|
return void 0;
|
|
3648
3807
|
}
|
|
3649
3808
|
const targets = Array.isArray(parsed.extends) ? parsed.extends.filter((entry) => typeof entry === "string") : typeof parsed.extends === "string" ? [parsed.extends] : [];
|
|
3650
|
-
return targets.find((target) => !existsSync16(
|
|
3809
|
+
return targets.find((target) => !existsSync16(join18(cwd, target)));
|
|
3651
3810
|
}
|
|
3652
3811
|
/** Announce what is not enforced, so reduced coverage is never silent. */
|
|
3653
3812
|
announceDisabled(preset) {
|
|
@@ -3679,8 +3838,8 @@ function registerLint() {
|
|
|
3679
3838
|
|
|
3680
3839
|
// src/roles/typescript/adapters/tsc/tsc.adapter.ts
|
|
3681
3840
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
3682
|
-
import { existsSync as
|
|
3683
|
-
import { createRequire as
|
|
3841
|
+
import { existsSync as existsSync17, readFileSync as readFileSync16 } from "fs";
|
|
3842
|
+
import { createRequire as createRequire4 } from "module";
|
|
3684
3843
|
import { join as join20 } from "path";
|
|
3685
3844
|
|
|
3686
3845
|
// src/roles/typescript/presets/base.json
|
|
@@ -3797,6 +3956,14 @@ validate2();
|
|
|
3797
3956
|
function hasShippedPreset(preset) {
|
|
3798
3957
|
return preset in LAYERS;
|
|
3799
3958
|
}
|
|
3959
|
+
function tsPresetFor(preset) {
|
|
3960
|
+
const layer = LAYERS[preset];
|
|
3961
|
+
if (!layer) throw new Error(`no TypeScript preset ships for preset "${preset}"`);
|
|
3962
|
+
return {
|
|
3963
|
+
...BASE.$schema === void 0 ? {} : { $schema: BASE.$schema },
|
|
3964
|
+
compilerOptions: { ...BASE.compilerOptions, ...layer.compilerOptions }
|
|
3965
|
+
};
|
|
3966
|
+
}
|
|
3800
3967
|
|
|
3801
3968
|
// src/roles/typescript/phased-rules.ts
|
|
3802
3969
|
function deferredRuleNames2() {
|
|
@@ -3811,8 +3978,17 @@ var PERMITTED_COMPILER_OPTIONS = [
|
|
|
3811
3978
|
"outDir",
|
|
3812
3979
|
"tsBuildInfoFile"
|
|
3813
3980
|
];
|
|
3814
|
-
function
|
|
3815
|
-
|
|
3981
|
+
function presetCompilerOptionKeys(preset) {
|
|
3982
|
+
if (preset === void 0 || !hasShippedPreset(preset)) return void 0;
|
|
3983
|
+
return Object.keys(tsPresetFor(preset).compilerOptions);
|
|
3984
|
+
}
|
|
3985
|
+
function presetOwnedKeys2(compilerOptions, preset) {
|
|
3986
|
+
return presetOwnedKeys(compilerOptions, PERMITTED_COMPILER_OPTIONS, presetCompilerOptionKeys(preset));
|
|
3987
|
+
}
|
|
3988
|
+
function localOnlyCompilerOptions(compilerOptions, preset) {
|
|
3989
|
+
const sets = presetCompilerOptionKeys(preset);
|
|
3990
|
+
if (sets === void 0) return [];
|
|
3991
|
+
return localOnlyKeys(compilerOptions, PERMITTED_COMPILER_OPTIONS, sets);
|
|
3816
3992
|
}
|
|
3817
3993
|
|
|
3818
3994
|
// src/roles/typescript/resolve-tsconfig-target.ts
|
|
@@ -3836,7 +4012,11 @@ function readTsconfigAdoption(cwd) {
|
|
|
3836
4012
|
fallback: TSCONFIG_FALLBACK,
|
|
3837
4013
|
presetPattern: SENTINEL_PRESET,
|
|
3838
4014
|
settingsKey: "compilerOptions",
|
|
3839
|
-
permitted: PERMITTED_COMPILER_OPTIONS
|
|
4015
|
+
permitted: PERMITTED_COMPILER_OPTIONS,
|
|
4016
|
+
// Drift is measured against what the preset SETS, not against the allowlist alone: see
|
|
4017
|
+
// `presetOwnedKeys`. Without this a module overriding the workspace base reads as
|
|
4018
|
+
// non-conformant forever, since re-running `--init` would not (and should not) fix it.
|
|
4019
|
+
presetOwns: (path) => presetCompilerOptionKeys(path.slice(path.lastIndexOf("/") + 1)) ?? []
|
|
3840
4020
|
});
|
|
3841
4021
|
}
|
|
3842
4022
|
|
|
@@ -3844,86 +4024,6 @@ function readTsconfigAdoption(cwd) {
|
|
|
3844
4024
|
import { readFileSync as readFileSync15 } from "fs";
|
|
3845
4025
|
import { join as join19 } from "path";
|
|
3846
4026
|
|
|
3847
|
-
// src/core/config/preset-evidence.ts
|
|
3848
|
-
import { existsSync as existsSync17, readdirSync as readdirSync3, readFileSync as readFileSync14 } from "fs";
|
|
3849
|
-
import { join as join18 } from "path";
|
|
3850
|
-
var PATH_SIGNALS = [
|
|
3851
|
-
{
|
|
3852
|
-
preset: "nest",
|
|
3853
|
-
pattern: /(^|\/)apps\/nest\//,
|
|
3854
|
-
evidence: "it lives under apps/nest"
|
|
3855
|
-
}
|
|
3856
|
-
];
|
|
3857
|
-
var DEPENDENCY_SIGNALS = [
|
|
3858
|
-
{ preset: "react", pattern: /^(react|react-dom|@types\/react)$|^eslint-plugin-react/ },
|
|
3859
|
-
{ preset: "svelte", pattern: /^svelte$|^@sveltejs\// },
|
|
3860
|
-
{ preset: "nest", pattern: /^@nestjs\// }
|
|
3861
|
-
];
|
|
3862
|
-
function dependencyNames(cwd) {
|
|
3863
|
-
const path = join18(cwd, "package.json");
|
|
3864
|
-
if (!existsSync17(path)) return [];
|
|
3865
|
-
try {
|
|
3866
|
-
const manifest = parseJsonc(
|
|
3867
|
-
readFileSync14(path, "utf8"),
|
|
3868
|
-
path
|
|
3869
|
-
);
|
|
3870
|
-
return [
|
|
3871
|
-
...Object.keys(manifest.dependencies ?? {}),
|
|
3872
|
-
...Object.keys(manifest.devDependencies ?? {})
|
|
3873
|
-
];
|
|
3874
|
-
} catch {
|
|
3875
|
-
return [];
|
|
3876
|
-
}
|
|
3877
|
-
}
|
|
3878
|
-
function declaresJsx(cwd) {
|
|
3879
|
-
let entries;
|
|
3880
|
-
try {
|
|
3881
|
-
entries = readdirSync3(cwd).filter(
|
|
3882
|
-
(name) => name.startsWith("tsconfig") && name.endsWith(".json")
|
|
3883
|
-
);
|
|
3884
|
-
} catch {
|
|
3885
|
-
return false;
|
|
3886
|
-
}
|
|
3887
|
-
for (const name of entries) {
|
|
3888
|
-
try {
|
|
3889
|
-
const config = parseJsonc(
|
|
3890
|
-
readFileSync14(join18(cwd, name), "utf8"),
|
|
3891
|
-
name
|
|
3892
|
-
);
|
|
3893
|
-
if (config.compilerOptions?.jsx !== void 0) return true;
|
|
3894
|
-
} catch {
|
|
3895
|
-
}
|
|
3896
|
-
}
|
|
3897
|
-
return false;
|
|
3898
|
-
}
|
|
3899
|
-
function presetSignals(cwd) {
|
|
3900
|
-
const signals = [];
|
|
3901
|
-
const dependencies = dependencyNames(cwd);
|
|
3902
|
-
for (const { preset, pattern } of DEPENDENCY_SIGNALS) {
|
|
3903
|
-
const hit = dependencies.find((name) => pattern.test(name));
|
|
3904
|
-
if (hit) signals.push({ preset, evidence: `it depends on "${hit}"` });
|
|
3905
|
-
}
|
|
3906
|
-
if (declaresJsx(cwd) && !signals.some((signal) => signal.preset === "react")) {
|
|
3907
|
-
signals.push({ preset: "react", evidence: 'its tsconfig sets "jsx"' });
|
|
3908
|
-
}
|
|
3909
|
-
const location = cwd.replaceAll("\\", "/");
|
|
3910
|
-
for (const { preset, pattern, evidence } of PATH_SIGNALS) {
|
|
3911
|
-
if (pattern.test(location) && !signals.some((signal) => signal.preset === preset)) {
|
|
3912
|
-
signals.push({ preset, evidence });
|
|
3913
|
-
}
|
|
3914
|
-
}
|
|
3915
|
-
return signals;
|
|
3916
|
-
}
|
|
3917
|
-
function presetContradiction(declared, cwd) {
|
|
3918
|
-
const signals = presetSignals(cwd);
|
|
3919
|
-
if (signals.length === 0) return void 0;
|
|
3920
|
-
if (signals.some((signal) => signal.preset === declared)) return void 0;
|
|
3921
|
-
const [first] = signals;
|
|
3922
|
-
if (!first) return void 0;
|
|
3923
|
-
const evidence = signals.map((signal) => signal.evidence).join(", and ");
|
|
3924
|
-
return `--preset ${declared} does not match this module: ${evidence}, which makes it "${first.preset}". Nothing was written. Re-run with --preset ${first.preset}, or adopt from the module you actually meant.`;
|
|
3925
|
-
}
|
|
3926
|
-
|
|
3927
4027
|
// src/roles/typescript/typecheck-script.ts
|
|
3928
4028
|
var SENTINEL_TYPECHECK_COMMAND = "sentinel --run --typescript";
|
|
3929
4029
|
var TYPECHECK_SCRIPT_NAME = "typecheck";
|
|
@@ -3964,7 +4064,7 @@ function planAdoption(context) {
|
|
|
3964
4064
|
if (!hasShippedPreset(context.preset)) {
|
|
3965
4065
|
return {
|
|
3966
4066
|
operations: [],
|
|
3967
|
-
|
|
4067
|
+
skipped: `sentinel does not ship a TypeScript preset named "${context.preset}" yet (shipped: ${SHIPPED_PRESETS.join(", ")}). Nothing was written, and the module keeps its own tsconfig; the other roles adopt normally.`
|
|
3968
4068
|
};
|
|
3969
4069
|
}
|
|
3970
4070
|
const current = declaredPreset(context.cwd);
|
|
@@ -3974,10 +4074,6 @@ function planAdoption(context) {
|
|
|
3974
4074
|
blocked: `this module is already adopted as "${current}", and --preset says "${context.preset}". Nothing was written. If the change is deliberate, remove the sentinel preset from the tsconfig \`extends\` chain and re-run; otherwise re-run with --preset ${current}.`
|
|
3975
4075
|
};
|
|
3976
4076
|
}
|
|
3977
|
-
const contradiction = presetContradiction(context.preset, context.cwd);
|
|
3978
|
-
if (contradiction !== void 0) {
|
|
3979
|
-
return { operations: [], blocked: contradiction };
|
|
3980
|
-
}
|
|
3981
4077
|
if (!hasSourceFiles(context.cwd, TYPECHECKABLE_EXTENSIONS)) {
|
|
3982
4078
|
return {
|
|
3983
4079
|
operations: [],
|
|
@@ -4015,7 +4111,8 @@ function planAdoption(context) {
|
|
|
4015
4111
|
target.path
|
|
4016
4112
|
);
|
|
4017
4113
|
const extendsChain = composeExtends(existing.extends, preset);
|
|
4018
|
-
const drift = presetOwnedKeys2(existing.compilerOptions);
|
|
4114
|
+
const drift = presetOwnedKeys2(existing.compilerOptions, context.preset);
|
|
4115
|
+
const keptLocal = localOnlyCompilerOptions(existing.compilerOptions, context.preset);
|
|
4019
4116
|
const operations = [
|
|
4020
4117
|
{ kind: "merge-json", path: target.path, value: { extends: extendsChain } }
|
|
4021
4118
|
];
|
|
@@ -4030,6 +4127,11 @@ function planAdoption(context) {
|
|
|
4030
4127
|
});
|
|
4031
4128
|
notes.push(`stripped preset-owned compilerOptions: ${drift.join(", ")}`);
|
|
4032
4129
|
}
|
|
4130
|
+
if (keptLocal.length > 0) {
|
|
4131
|
+
notes.push(
|
|
4132
|
+
`KEPT this module's own compilerOptions (${keptLocal.join(", ")}): the preset does not set them, so they are yours rather than drift. Removing them would not hand the decision to the preset, it would hand it to the workspace tsconfig this module extends`
|
|
4133
|
+
);
|
|
4134
|
+
}
|
|
4033
4135
|
operations.push(addScript, ...nxTargets);
|
|
4034
4136
|
const existingCheck = moduleScripts(context.cwd)[TYPECHECK_SCRIPT_NAME];
|
|
4035
4137
|
if (keepsForeignChecker(existingCheck)) {
|
|
@@ -4062,7 +4164,7 @@ function parseDiagnostics(output) {
|
|
|
4062
4164
|
return diagnostics;
|
|
4063
4165
|
}
|
|
4064
4166
|
var PHASED_STRICTNESS_WARNING = `sentinel typescript: phase 1 (non-breaking) \u2014 deferred: ${deferredRuleNames2().join(", ")}. Enabled centrally in a later wave; run \`sentinel --inspect --typescript\` for the list, or \`sentinel --run --typescript -- --noImplicitAny\` to measure what one would cost today.`;
|
|
4065
|
-
var TscAdapter = class extends BaseAdapter {
|
|
4167
|
+
var TscAdapter = class _TscAdapter extends BaseAdapter {
|
|
4066
4168
|
target = "typescript";
|
|
4067
4169
|
runner = "tsc";
|
|
4068
4170
|
/**
|
|
@@ -4156,7 +4258,7 @@ var TscAdapter = class extends BaseAdapter {
|
|
|
4156
4258
|
);
|
|
4157
4259
|
if (preset === void 0) return void 0;
|
|
4158
4260
|
try {
|
|
4159
|
-
|
|
4261
|
+
createRequire4(join20(cwd, "noop.js")).resolve(preset);
|
|
4160
4262
|
return void 0;
|
|
4161
4263
|
} catch {
|
|
4162
4264
|
return preset;
|
|
@@ -4170,6 +4272,13 @@ var TscAdapter = class extends BaseAdapter {
|
|
|
4170
4272
|
);
|
|
4171
4273
|
return { ok: true, code: 0 };
|
|
4172
4274
|
}
|
|
4275
|
+
if (!readTsconfigAdoption(ctx.cwd).adopted) {
|
|
4276
|
+
process.stderr.write(
|
|
4277
|
+
`sentinel typescript(tsc): no sentinel preset in this module's tsconfig; run \`sentinel --init --typescript\` to adopt.
|
|
4278
|
+
`
|
|
4279
|
+
);
|
|
4280
|
+
return { ok: true, code: 0 };
|
|
4281
|
+
}
|
|
4173
4282
|
const missing = this.missingPresetPackage(ctx.cwd);
|
|
4174
4283
|
if (missing) {
|
|
4175
4284
|
process.stderr.write(
|
|
@@ -4261,7 +4370,7 @@ var TscAdapter = class extends BaseAdapter {
|
|
|
4261
4370
|
* check.
|
|
4262
4371
|
*/
|
|
4263
4372
|
typecheckTarget(cwd) {
|
|
4264
|
-
if (
|
|
4373
|
+
if (existsSync17(join20(cwd, "tsconfig.json"))) return "tsconfig.json";
|
|
4265
4374
|
const target = resolveTsconfigTarget(cwd);
|
|
4266
4375
|
return target.reason === "none" ? null : target.path;
|
|
4267
4376
|
}
|
|
@@ -4295,10 +4404,23 @@ var TscAdapter = class extends BaseAdapter {
|
|
|
4295
4404
|
* 7006/7031/7053/… ), the signal that drives the noImplicitAny migration. No
|
|
4296
4405
|
* tsconfig is a clean, empty report.
|
|
4297
4406
|
*/
|
|
4407
|
+
/**
|
|
4408
|
+
* The metrics for a module this role has nothing to say about. Every KEY the full path
|
|
4409
|
+
* emits, so a consumer never sees a row whose shape depends on whether the module adopted.
|
|
4410
|
+
*/
|
|
4411
|
+
static NOTHING_TO_REPORT = {
|
|
4412
|
+
errors: 0,
|
|
4413
|
+
implicitAny: "deferred",
|
|
4414
|
+
diagnostics: [],
|
|
4415
|
+
diagnosticsTruncated: false
|
|
4416
|
+
};
|
|
4298
4417
|
async report(ctx) {
|
|
4299
4418
|
const config = this.typecheckTarget(ctx.cwd);
|
|
4300
4419
|
if (!config) {
|
|
4301
|
-
return { ok: true, code: 0, metrics:
|
|
4420
|
+
return { ok: true, code: 0, metrics: _TscAdapter.NOTHING_TO_REPORT };
|
|
4421
|
+
}
|
|
4422
|
+
if (!readTsconfigAdoption(ctx.cwd).adopted) {
|
|
4423
|
+
return { ok: true, code: 0, metrics: _TscAdapter.NOTHING_TO_REPORT };
|
|
4302
4424
|
}
|
|
4303
4425
|
const tsc = resolveBin(ctx.cwd, "tsc") ?? "tsc";
|
|
4304
4426
|
const result = spawnSync3(tsc, ["-b", config], { cwd: ctx.cwd, encoding: "utf8" });
|
|
@@ -4451,7 +4573,7 @@ function replaceLines(current, replacements) {
|
|
|
4451
4573
|
}
|
|
4452
4574
|
|
|
4453
4575
|
// src/core/apply-plan.ts
|
|
4454
|
-
import { existsSync as
|
|
4576
|
+
import { existsSync as existsSync18, readFileSync as readFileSync17, renameSync, rmSync, writeFileSync as writeFileSync2 } from "fs";
|
|
4455
4577
|
import { resolve as resolve3, sep } from "path";
|
|
4456
4578
|
import { applyEdits, findNodeAtLocation, modify, parseTree } from "jsonc-parser";
|
|
4457
4579
|
|
|
@@ -4470,7 +4592,7 @@ function resolveWithinRoot(cwd, relativePath) {
|
|
|
4470
4592
|
return absolutePath;
|
|
4471
4593
|
}
|
|
4472
4594
|
function readIfExists(absolutePath) {
|
|
4473
|
-
return
|
|
4595
|
+
return existsSync18(absolutePath) ? readFileSync17(absolutePath, "utf8") : void 0;
|
|
4474
4596
|
}
|
|
4475
4597
|
function* leaves(value, prefix = []) {
|
|
4476
4598
|
for (const [key, keyValue] of Object.entries(value)) {
|
|
@@ -4587,6 +4709,86 @@ function applyPlan(cwd, plan2) {
|
|
|
4587
4709
|
return changed.map((file) => ({ path: file.path, deleted: Boolean(file.deleted) }));
|
|
4588
4710
|
}
|
|
4589
4711
|
|
|
4712
|
+
// src/core/config/preset-evidence.ts
|
|
4713
|
+
import { existsSync as existsSync19, readdirSync as readdirSync3, readFileSync as readFileSync18 } from "fs";
|
|
4714
|
+
import { join as join21 } from "path";
|
|
4715
|
+
var PATH_SIGNALS = [
|
|
4716
|
+
{
|
|
4717
|
+
preset: "nest",
|
|
4718
|
+
pattern: /(^|\/)apps\/nest\//,
|
|
4719
|
+
evidence: "it lives under apps/nest"
|
|
4720
|
+
}
|
|
4721
|
+
];
|
|
4722
|
+
var DEPENDENCY_SIGNALS = [
|
|
4723
|
+
{ preset: "react", pattern: /^(react|react-dom|@types\/react)$|^eslint-plugin-react/ },
|
|
4724
|
+
{ preset: "svelte", pattern: /^svelte$|^@sveltejs\// },
|
|
4725
|
+
{ preset: "nest", pattern: /^@nestjs\// }
|
|
4726
|
+
];
|
|
4727
|
+
function dependencyNames(cwd) {
|
|
4728
|
+
const path = join21(cwd, "package.json");
|
|
4729
|
+
if (!existsSync19(path)) return [];
|
|
4730
|
+
try {
|
|
4731
|
+
const manifest = parseJsonc(
|
|
4732
|
+
readFileSync18(path, "utf8"),
|
|
4733
|
+
path
|
|
4734
|
+
);
|
|
4735
|
+
return [
|
|
4736
|
+
...Object.keys(manifest.dependencies ?? {}),
|
|
4737
|
+
...Object.keys(manifest.devDependencies ?? {})
|
|
4738
|
+
];
|
|
4739
|
+
} catch {
|
|
4740
|
+
return [];
|
|
4741
|
+
}
|
|
4742
|
+
}
|
|
4743
|
+
function declaresJsx(cwd) {
|
|
4744
|
+
let entries;
|
|
4745
|
+
try {
|
|
4746
|
+
entries = readdirSync3(cwd).filter(
|
|
4747
|
+
(name) => name.startsWith("tsconfig") && name.endsWith(".json")
|
|
4748
|
+
);
|
|
4749
|
+
} catch {
|
|
4750
|
+
return false;
|
|
4751
|
+
}
|
|
4752
|
+
for (const name of entries) {
|
|
4753
|
+
try {
|
|
4754
|
+
const config = parseJsonc(
|
|
4755
|
+
readFileSync18(join21(cwd, name), "utf8"),
|
|
4756
|
+
name
|
|
4757
|
+
);
|
|
4758
|
+
if (config.compilerOptions?.jsx !== void 0) return true;
|
|
4759
|
+
} catch {
|
|
4760
|
+
}
|
|
4761
|
+
}
|
|
4762
|
+
return false;
|
|
4763
|
+
}
|
|
4764
|
+
function presetSignals(cwd) {
|
|
4765
|
+
const signals = [];
|
|
4766
|
+
const dependencies = dependencyNames(cwd);
|
|
4767
|
+
for (const { preset, pattern } of DEPENDENCY_SIGNALS) {
|
|
4768
|
+
const hit = dependencies.find((name) => pattern.test(name));
|
|
4769
|
+
if (hit) signals.push({ preset, evidence: `it depends on "${hit}"` });
|
|
4770
|
+
}
|
|
4771
|
+
if (declaresJsx(cwd) && !signals.some((signal) => signal.preset === "react")) {
|
|
4772
|
+
signals.push({ preset: "react", evidence: 'its tsconfig sets "jsx"' });
|
|
4773
|
+
}
|
|
4774
|
+
const location = cwd.replaceAll("\\", "/");
|
|
4775
|
+
for (const { preset, pattern, evidence } of PATH_SIGNALS) {
|
|
4776
|
+
if (pattern.test(location) && !signals.some((signal) => signal.preset === preset)) {
|
|
4777
|
+
signals.push({ preset, evidence });
|
|
4778
|
+
}
|
|
4779
|
+
}
|
|
4780
|
+
return signals;
|
|
4781
|
+
}
|
|
4782
|
+
function presetContradiction(declared, cwd) {
|
|
4783
|
+
const signals = presetSignals(cwd);
|
|
4784
|
+
if (signals.length === 0) return void 0;
|
|
4785
|
+
if (signals.some((signal) => signal.preset === declared)) return void 0;
|
|
4786
|
+
const [first] = signals;
|
|
4787
|
+
if (!first) return void 0;
|
|
4788
|
+
const evidence = signals.map((signal) => signal.evidence).join(", and ");
|
|
4789
|
+
return `--preset ${declared} does not match this module: ${evidence}, which makes it "${first.preset}". Nothing was written. Re-run with --preset ${first.preset}, or adopt from the module you actually meant.`;
|
|
4790
|
+
}
|
|
4791
|
+
|
|
4590
4792
|
// src/core/dispatch.ts
|
|
4591
4793
|
function resolveFlavour(opts) {
|
|
4592
4794
|
if (opts.preset) return opts.preset;
|
|
@@ -4646,6 +4848,12 @@ async function dispatch(opts) {
|
|
|
4646
4848
|
const detected = resolveFlavour(opts);
|
|
4647
4849
|
const adapter = resolve(opts.target, detected, opts.runner);
|
|
4648
4850
|
const preset = opts.preset ?? adapter.declaredPreset?.(opts.cwd) ?? detected;
|
|
4851
|
+
const contradiction = presetContradiction(preset, opts.cwd);
|
|
4852
|
+
if (contradiction !== void 0) {
|
|
4853
|
+
process.stderr.write(`sentinel (${opts.target}): ${contradiction}
|
|
4854
|
+
`);
|
|
4855
|
+
return 1;
|
|
4856
|
+
}
|
|
4649
4857
|
const context = { cwd: opts.cwd, preset };
|
|
4650
4858
|
const plan2 = await adapter.plan(context);
|
|
4651
4859
|
if (plan2.blocked) {
|
|
@@ -4694,8 +4902,8 @@ export {
|
|
|
4694
4902
|
WORKSPACE_ROOT_MARKER,
|
|
4695
4903
|
findWorkspaceRoot,
|
|
4696
4904
|
ensureWorkspacePrep,
|
|
4697
|
-
resolveBin,
|
|
4698
4905
|
palette,
|
|
4906
|
+
resolveBin,
|
|
4699
4907
|
VERBS,
|
|
4700
4908
|
TARGETS,
|
|
4701
4909
|
PRESET_NAMES,
|
|
@@ -4704,4 +4912,4 @@ export {
|
|
|
4704
4912
|
detectFramework,
|
|
4705
4913
|
dispatch
|
|
4706
4914
|
};
|
|
4707
|
-
//# sourceMappingURL=chunk-
|
|
4915
|
+
//# sourceMappingURL=chunk-FA6N4XCP.js.map
|
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hublo/sentinel",
|
|
3
|
-
"version": "1.1.0-alpha.
|
|
3
|
+
"version": "1.1.0-alpha.7",
|
|
4
4
|
"description": "One CLI that guards code health across Hublo repos: shared lint/typescript/build/test presets, static & dynamic analysis, and architecture checks.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|