@hublo/sentinel 1.1.0-alpha.5 → 1.1.0-alpha.6
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 +1 -1
- package/dist/{chunk-NAUACDXT.js → chunk-U2NAKQXR.js} +320 -163
- 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
|
@@ -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.
|
|
@@ -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,8 +3019,8 @@ 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
3026
|
function presetOwnedKeys(config, permitted) {
|
|
@@ -2874,12 +3029,12 @@ function presetOwnedKeys(config, permitted) {
|
|
|
2874
3029
|
}
|
|
2875
3030
|
|
|
2876
3031
|
// src/core/config/resolve-config-target.ts
|
|
2877
|
-
import { existsSync as existsSync11, readFileSync as
|
|
2878
|
-
import { join as
|
|
3032
|
+
import { existsSync as existsSync11, readFileSync as readFileSync9 } from "fs";
|
|
3033
|
+
import { join as join12 } from "path";
|
|
2879
3034
|
function readExtends(absolutePath) {
|
|
2880
3035
|
let parsed;
|
|
2881
3036
|
try {
|
|
2882
|
-
parsed = parseJsonc(
|
|
3037
|
+
parsed = parseJsonc(readFileSync9(absolutePath, "utf8"), absolutePath);
|
|
2883
3038
|
} catch {
|
|
2884
3039
|
return [];
|
|
2885
3040
|
}
|
|
@@ -2893,7 +3048,7 @@ function resolveConfigTarget(moduleDir, { candidates, markers, fallback }) {
|
|
|
2893
3048
|
let existing;
|
|
2894
3049
|
let existingExtendsSomething = false;
|
|
2895
3050
|
for (const candidate of candidates) {
|
|
2896
|
-
const absolutePath =
|
|
3051
|
+
const absolutePath = join12(moduleDir, candidate);
|
|
2897
3052
|
if (!existsSync11(absolutePath)) continue;
|
|
2898
3053
|
const chain = readExtends(absolutePath);
|
|
2899
3054
|
if (existing === void 0) {
|
|
@@ -2927,12 +3082,12 @@ var NOT_ADOPTED2 = (configFile, unreadable = null) => ({
|
|
|
2927
3082
|
});
|
|
2928
3083
|
function readAdoption(cwd, options) {
|
|
2929
3084
|
const target = resolveConfigTarget(cwd, options);
|
|
2930
|
-
if (target.reason === "none" || !existsSync12(
|
|
3085
|
+
if (target.reason === "none" || !existsSync12(join13(cwd, target.path))) {
|
|
2931
3086
|
return NOT_ADOPTED2(target.reason === "none" ? null : target.path);
|
|
2932
3087
|
}
|
|
2933
3088
|
let parsed;
|
|
2934
3089
|
try {
|
|
2935
|
-
parsed = parseJsonc(
|
|
3090
|
+
parsed = parseJsonc(readFileSync10(join13(cwd, target.path), "utf8"), target.path);
|
|
2936
3091
|
} catch (error) {
|
|
2937
3092
|
const reason = error instanceof Error ? error.message : String(error);
|
|
2938
3093
|
return NOT_ADOPTED2(target.path, `${target.path} could not be parsed (${reason})`);
|
|
@@ -2967,14 +3122,14 @@ function readLintAdoption(cwd) {
|
|
|
2967
3122
|
|
|
2968
3123
|
// src/roles/lint/resolve-oxlint.ts
|
|
2969
3124
|
import { existsSync as existsSync13 } from "fs";
|
|
2970
|
-
import { createRequire as
|
|
2971
|
-
import { delimiter as delimiter2, dirname as
|
|
3125
|
+
import { createRequire as createRequire3 } from "module";
|
|
3126
|
+
import { delimiter as delimiter2, dirname as dirname5, join as join14 } from "path";
|
|
2972
3127
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
2973
3128
|
var PACKAGE_OF = {
|
|
2974
3129
|
oxlint: "oxlint",
|
|
2975
3130
|
tsgolint: "oxlint-tsgolint"
|
|
2976
3131
|
};
|
|
2977
|
-
var require3 =
|
|
3132
|
+
var require3 = createRequire3(import.meta.url);
|
|
2978
3133
|
function resolveOxlint(cwd, name = "oxlint") {
|
|
2979
3134
|
return resolveBin(cwd, name) ?? binFromOwnInstall(PACKAGE_OF[name], name);
|
|
2980
3135
|
}
|
|
@@ -2984,7 +3139,7 @@ function canRunTypeAware(cwd) {
|
|
|
2984
3139
|
function oxlintPath(cwd, env) {
|
|
2985
3140
|
const shim = tsgolintShim(cwd);
|
|
2986
3141
|
if (!shim) return env.PATH;
|
|
2987
|
-
return [
|
|
3142
|
+
return [dirname5(shim), env.PATH].filter(Boolean).join(delimiter2);
|
|
2988
3143
|
}
|
|
2989
3144
|
function tsgolintShim(cwd) {
|
|
2990
3145
|
const fromModule = resolveBin(cwd, "tsgolint");
|
|
@@ -2992,17 +3147,17 @@ function tsgolintShim(cwd) {
|
|
|
2992
3147
|
const candidates = [];
|
|
2993
3148
|
for (const owner of ["oxlint-tsgolint", "oxlint"]) {
|
|
2994
3149
|
try {
|
|
2995
|
-
const packageDir =
|
|
2996
|
-
candidates.push(
|
|
2997
|
-
candidates.push(
|
|
3150
|
+
const packageDir = dirname5(require3.resolve(`${owner}/package.json`));
|
|
3151
|
+
candidates.push(join14(packageDir, "node_modules", ".bin", "tsgolint"));
|
|
3152
|
+
candidates.push(join14(packageDir, "..", ".bin", "tsgolint"));
|
|
2998
3153
|
} catch {
|
|
2999
3154
|
}
|
|
3000
3155
|
}
|
|
3001
3156
|
try {
|
|
3002
|
-
const ownRoot =
|
|
3003
|
-
candidates.push(
|
|
3157
|
+
const ownRoot = dirname5(require3.resolve("@hublo/sentinel/package.json"));
|
|
3158
|
+
candidates.push(join14(ownRoot, "node_modules", ".bin", "tsgolint"));
|
|
3004
3159
|
} catch {
|
|
3005
|
-
candidates.push(resolveBin(
|
|
3160
|
+
candidates.push(resolveBin(dirname5(fileURLToPath2(import.meta.url)), "tsgolint") ?? "");
|
|
3006
3161
|
}
|
|
3007
3162
|
return candidates.find((candidate) => candidate !== "" && existsSync13(candidate));
|
|
3008
3163
|
}
|
|
@@ -3011,12 +3166,12 @@ function oxlintSearchPath(cwd) {
|
|
|
3011
3166
|
}
|
|
3012
3167
|
|
|
3013
3168
|
// src/roles/lint/adapters/oxlint/plan.ts
|
|
3014
|
-
import { existsSync as existsSync15, readFileSync as
|
|
3015
|
-
import { join as
|
|
3169
|
+
import { existsSync as existsSync15, readFileSync as readFileSync13 } from "fs";
|
|
3170
|
+
import { join as join17 } from "path";
|
|
3016
3171
|
|
|
3017
3172
|
// src/roles/lint/eslint-ignores.ts
|
|
3018
|
-
import { existsSync as existsSync14, readFileSync as
|
|
3019
|
-
import { join as
|
|
3173
|
+
import { existsSync as existsSync14, readFileSync as readFileSync11 } from "fs";
|
|
3174
|
+
import { join as join15 } from "path";
|
|
3020
3175
|
var IGNORE_BLOCKS = [/\bignores\s*:\s*\[([^\]]*)\]/g, /\bglobalIgnores\s*\(\s*\[([^\]]*)\]/g];
|
|
3021
3176
|
var STRING_LITERAL = /['"`]([^'"`]+)['"`]/g;
|
|
3022
3177
|
var OPAQUE_SOURCE = /\b(includeIgnoreFile)\s*\([^)]*\)/g;
|
|
@@ -3029,11 +3184,11 @@ function readRootEslintIgnores(root) {
|
|
|
3029
3184
|
return { patterns: patterns.filter((pattern) => pattern.startsWith("**/")), unresolved };
|
|
3030
3185
|
}
|
|
3031
3186
|
function readEslintIgnores(cwd) {
|
|
3032
|
-
const config = ESLINT_CONFIG_FILES.map((name) =>
|
|
3187
|
+
const config = ESLINT_CONFIG_FILES.map((name) => join15(cwd, name)).find((path) => existsSync14(path));
|
|
3033
3188
|
if (!config) return { patterns: [], unresolved: [] };
|
|
3034
3189
|
let source;
|
|
3035
3190
|
try {
|
|
3036
|
-
source =
|
|
3191
|
+
source = readFileSync11(config, "utf8");
|
|
3037
3192
|
} catch {
|
|
3038
3193
|
return { patterns: [], unresolved: [] };
|
|
3039
3194
|
}
|
|
@@ -3087,8 +3242,8 @@ function lintPresetFor(preset) {
|
|
|
3087
3242
|
}
|
|
3088
3243
|
|
|
3089
3244
|
// src/roles/lint/rename-suppressions.ts
|
|
3090
|
-
import { readdirSync as readdirSync2, readFileSync as
|
|
3091
|
-
import { join as
|
|
3245
|
+
import { readdirSync as readdirSync2, readFileSync as readFileSync12, statSync } from "fs";
|
|
3246
|
+
import { join as join16, relative as relative2 } from "path";
|
|
3092
3247
|
var SOURCE_EXTENSIONS = [
|
|
3093
3248
|
".ts",
|
|
3094
3249
|
".tsx",
|
|
@@ -3135,7 +3290,7 @@ function* sourceFiles(dir) {
|
|
|
3135
3290
|
return;
|
|
3136
3291
|
}
|
|
3137
3292
|
for (const entry of entries) {
|
|
3138
|
-
const full =
|
|
3293
|
+
const full = join16(dir, entry);
|
|
3139
3294
|
let isDirectory;
|
|
3140
3295
|
try {
|
|
3141
3296
|
isDirectory = statSync(full).isDirectory();
|
|
@@ -3155,7 +3310,7 @@ function findSuppressionRenames(cwd, renames) {
|
|
|
3155
3310
|
for (const file of sourceFiles(cwd)) {
|
|
3156
3311
|
let content;
|
|
3157
3312
|
try {
|
|
3158
|
-
content =
|
|
3313
|
+
content = readFileSync12(file, "utf8");
|
|
3159
3314
|
} catch {
|
|
3160
3315
|
continue;
|
|
3161
3316
|
}
|
|
@@ -3228,7 +3383,7 @@ function plan(context) {
|
|
|
3228
3383
|
if (!hasLintPreset(context.preset)) {
|
|
3229
3384
|
return {
|
|
3230
3385
|
operations: [],
|
|
3231
|
-
|
|
3386
|
+
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
3387
|
};
|
|
3233
3388
|
}
|
|
3234
3389
|
const current = presetOfVariant(presetNameFromPath(readLintAdoption(context.cwd).preset));
|
|
@@ -3273,7 +3428,7 @@ function plan(context) {
|
|
|
3273
3428
|
keys: removableDeps
|
|
3274
3429
|
});
|
|
3275
3430
|
}
|
|
3276
|
-
const eslintConfigs = ESLINT_CONFIG_FILES.filter((name) => existsSync15(
|
|
3431
|
+
const eslintConfigs = ESLINT_CONFIG_FILES.filter((name) => existsSync15(join17(context.cwd, name)));
|
|
3277
3432
|
for (const name of eslintConfigs) operations.push({ kind: "delete", path: name });
|
|
3278
3433
|
operations.push(
|
|
3279
3434
|
...nxTargetOperations({
|
|
@@ -3368,7 +3523,7 @@ function lintScripts(cwd) {
|
|
|
3368
3523
|
function committedIgnorePatterns(cwd) {
|
|
3369
3524
|
try {
|
|
3370
3525
|
const parsed = parseJsonc(
|
|
3371
|
-
|
|
3526
|
+
readFileSync13(join17(cwd, LINT_CONFIG_FILE), "utf8"),
|
|
3372
3527
|
LINT_CONFIG_FILE
|
|
3373
3528
|
);
|
|
3374
3529
|
return Array.isArray(parsed.ignorePatterns) ? parsed.ignorePatterns.filter((entry) => typeof entry === "string") : [];
|
|
@@ -3380,7 +3535,7 @@ function moduleEslintDependencies(cwd) {
|
|
|
3380
3535
|
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
3536
|
let manifest;
|
|
3382
3537
|
try {
|
|
3383
|
-
manifest = JSON.parse(
|
|
3538
|
+
manifest = JSON.parse(readFileSync13(join17(cwd, "package.json"), "utf8"));
|
|
3384
3539
|
} catch {
|
|
3385
3540
|
return [];
|
|
3386
3541
|
}
|
|
@@ -3479,7 +3634,7 @@ var OxlintAdapter = class extends BaseAdapter {
|
|
|
3479
3634
|
this.fixPass(ctx, oxlint, env);
|
|
3480
3635
|
}
|
|
3481
3636
|
async run(ctx) {
|
|
3482
|
-
if (!existsSync16(
|
|
3637
|
+
if (!existsSync16(join18(ctx.cwd, LINT_CONFIG_FILE))) {
|
|
3483
3638
|
process.stderr.write(
|
|
3484
3639
|
`sentinel lint(oxlint): no ${LINT_CONFIG_FILE} in this module; run \`sentinel --init --lint\` to adopt.
|
|
3485
3640
|
`
|
|
@@ -3572,9 +3727,9 @@ var OxlintAdapter = class extends BaseAdapter {
|
|
|
3572
3727
|
if (target) return 0;
|
|
3573
3728
|
let total = 0;
|
|
3574
3729
|
try {
|
|
3575
|
-
const stub = JSON.parse(
|
|
3730
|
+
const stub = JSON.parse(readFileSync14(join18(cwd, LINT_CONFIG_FILE), "utf8"));
|
|
3576
3731
|
for (const entry of stub.extends ?? []) {
|
|
3577
|
-
const preset = JSON.parse(
|
|
3732
|
+
const preset = JSON.parse(readFileSync14(join18(cwd, entry), "utf8"));
|
|
3578
3733
|
total += Object.keys(preset.rules ?? {}).length;
|
|
3579
3734
|
}
|
|
3580
3735
|
} catch {
|
|
@@ -3642,12 +3797,12 @@ var OxlintAdapter = class extends BaseAdapter {
|
|
|
3642
3797
|
unresolvedPreset(cwd) {
|
|
3643
3798
|
let parsed;
|
|
3644
3799
|
try {
|
|
3645
|
-
parsed = JSON.parse(
|
|
3800
|
+
parsed = JSON.parse(readFileSync14(join18(cwd, LINT_CONFIG_FILE), "utf8"));
|
|
3646
3801
|
} catch {
|
|
3647
3802
|
return void 0;
|
|
3648
3803
|
}
|
|
3649
3804
|
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(
|
|
3805
|
+
return targets.find((target) => !existsSync16(join18(cwd, target)));
|
|
3651
3806
|
}
|
|
3652
3807
|
/** Announce what is not enforced, so reduced coverage is never silent. */
|
|
3653
3808
|
announceDisabled(preset) {
|
|
@@ -3679,8 +3834,8 @@ function registerLint() {
|
|
|
3679
3834
|
|
|
3680
3835
|
// src/roles/typescript/adapters/tsc/tsc.adapter.ts
|
|
3681
3836
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
3682
|
-
import { existsSync as
|
|
3683
|
-
import { createRequire as
|
|
3837
|
+
import { existsSync as existsSync17, readFileSync as readFileSync16 } from "fs";
|
|
3838
|
+
import { createRequire as createRequire4 } from "module";
|
|
3684
3839
|
import { join as join20 } from "path";
|
|
3685
3840
|
|
|
3686
3841
|
// src/roles/typescript/presets/base.json
|
|
@@ -3844,86 +3999,6 @@ function readTsconfigAdoption(cwd) {
|
|
|
3844
3999
|
import { readFileSync as readFileSync15 } from "fs";
|
|
3845
4000
|
import { join as join19 } from "path";
|
|
3846
4001
|
|
|
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
4002
|
// src/roles/typescript/typecheck-script.ts
|
|
3928
4003
|
var SENTINEL_TYPECHECK_COMMAND = "sentinel --run --typescript";
|
|
3929
4004
|
var TYPECHECK_SCRIPT_NAME = "typecheck";
|
|
@@ -3964,7 +4039,7 @@ function planAdoption(context) {
|
|
|
3964
4039
|
if (!hasShippedPreset(context.preset)) {
|
|
3965
4040
|
return {
|
|
3966
4041
|
operations: [],
|
|
3967
|
-
|
|
4042
|
+
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
4043
|
};
|
|
3969
4044
|
}
|
|
3970
4045
|
const current = declaredPreset(context.cwd);
|
|
@@ -3974,10 +4049,6 @@ function planAdoption(context) {
|
|
|
3974
4049
|
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
4050
|
};
|
|
3976
4051
|
}
|
|
3977
|
-
const contradiction = presetContradiction(context.preset, context.cwd);
|
|
3978
|
-
if (contradiction !== void 0) {
|
|
3979
|
-
return { operations: [], blocked: contradiction };
|
|
3980
|
-
}
|
|
3981
4052
|
if (!hasSourceFiles(context.cwd, TYPECHECKABLE_EXTENSIONS)) {
|
|
3982
4053
|
return {
|
|
3983
4054
|
operations: [],
|
|
@@ -4156,7 +4227,7 @@ var TscAdapter = class extends BaseAdapter {
|
|
|
4156
4227
|
);
|
|
4157
4228
|
if (preset === void 0) return void 0;
|
|
4158
4229
|
try {
|
|
4159
|
-
|
|
4230
|
+
createRequire4(join20(cwd, "noop.js")).resolve(preset);
|
|
4160
4231
|
return void 0;
|
|
4161
4232
|
} catch {
|
|
4162
4233
|
return preset;
|
|
@@ -4261,7 +4332,7 @@ var TscAdapter = class extends BaseAdapter {
|
|
|
4261
4332
|
* check.
|
|
4262
4333
|
*/
|
|
4263
4334
|
typecheckTarget(cwd) {
|
|
4264
|
-
if (
|
|
4335
|
+
if (existsSync17(join20(cwd, "tsconfig.json"))) return "tsconfig.json";
|
|
4265
4336
|
const target = resolveTsconfigTarget(cwd);
|
|
4266
4337
|
return target.reason === "none" ? null : target.path;
|
|
4267
4338
|
}
|
|
@@ -4451,7 +4522,7 @@ function replaceLines(current, replacements) {
|
|
|
4451
4522
|
}
|
|
4452
4523
|
|
|
4453
4524
|
// src/core/apply-plan.ts
|
|
4454
|
-
import { existsSync as
|
|
4525
|
+
import { existsSync as existsSync18, readFileSync as readFileSync17, renameSync, rmSync, writeFileSync as writeFileSync2 } from "fs";
|
|
4455
4526
|
import { resolve as resolve3, sep } from "path";
|
|
4456
4527
|
import { applyEdits, findNodeAtLocation, modify, parseTree } from "jsonc-parser";
|
|
4457
4528
|
|
|
@@ -4470,7 +4541,7 @@ function resolveWithinRoot(cwd, relativePath) {
|
|
|
4470
4541
|
return absolutePath;
|
|
4471
4542
|
}
|
|
4472
4543
|
function readIfExists(absolutePath) {
|
|
4473
|
-
return
|
|
4544
|
+
return existsSync18(absolutePath) ? readFileSync17(absolutePath, "utf8") : void 0;
|
|
4474
4545
|
}
|
|
4475
4546
|
function* leaves(value, prefix = []) {
|
|
4476
4547
|
for (const [key, keyValue] of Object.entries(value)) {
|
|
@@ -4587,6 +4658,86 @@ function applyPlan(cwd, plan2) {
|
|
|
4587
4658
|
return changed.map((file) => ({ path: file.path, deleted: Boolean(file.deleted) }));
|
|
4588
4659
|
}
|
|
4589
4660
|
|
|
4661
|
+
// src/core/config/preset-evidence.ts
|
|
4662
|
+
import { existsSync as existsSync19, readdirSync as readdirSync3, readFileSync as readFileSync18 } from "fs";
|
|
4663
|
+
import { join as join21 } from "path";
|
|
4664
|
+
var PATH_SIGNALS = [
|
|
4665
|
+
{
|
|
4666
|
+
preset: "nest",
|
|
4667
|
+
pattern: /(^|\/)apps\/nest\//,
|
|
4668
|
+
evidence: "it lives under apps/nest"
|
|
4669
|
+
}
|
|
4670
|
+
];
|
|
4671
|
+
var DEPENDENCY_SIGNALS = [
|
|
4672
|
+
{ preset: "react", pattern: /^(react|react-dom|@types\/react)$|^eslint-plugin-react/ },
|
|
4673
|
+
{ preset: "svelte", pattern: /^svelte$|^@sveltejs\// },
|
|
4674
|
+
{ preset: "nest", pattern: /^@nestjs\// }
|
|
4675
|
+
];
|
|
4676
|
+
function dependencyNames(cwd) {
|
|
4677
|
+
const path = join21(cwd, "package.json");
|
|
4678
|
+
if (!existsSync19(path)) return [];
|
|
4679
|
+
try {
|
|
4680
|
+
const manifest = parseJsonc(
|
|
4681
|
+
readFileSync18(path, "utf8"),
|
|
4682
|
+
path
|
|
4683
|
+
);
|
|
4684
|
+
return [
|
|
4685
|
+
...Object.keys(manifest.dependencies ?? {}),
|
|
4686
|
+
...Object.keys(manifest.devDependencies ?? {})
|
|
4687
|
+
];
|
|
4688
|
+
} catch {
|
|
4689
|
+
return [];
|
|
4690
|
+
}
|
|
4691
|
+
}
|
|
4692
|
+
function declaresJsx(cwd) {
|
|
4693
|
+
let entries;
|
|
4694
|
+
try {
|
|
4695
|
+
entries = readdirSync3(cwd).filter(
|
|
4696
|
+
(name) => name.startsWith("tsconfig") && name.endsWith(".json")
|
|
4697
|
+
);
|
|
4698
|
+
} catch {
|
|
4699
|
+
return false;
|
|
4700
|
+
}
|
|
4701
|
+
for (const name of entries) {
|
|
4702
|
+
try {
|
|
4703
|
+
const config = parseJsonc(
|
|
4704
|
+
readFileSync18(join21(cwd, name), "utf8"),
|
|
4705
|
+
name
|
|
4706
|
+
);
|
|
4707
|
+
if (config.compilerOptions?.jsx !== void 0) return true;
|
|
4708
|
+
} catch {
|
|
4709
|
+
}
|
|
4710
|
+
}
|
|
4711
|
+
return false;
|
|
4712
|
+
}
|
|
4713
|
+
function presetSignals(cwd) {
|
|
4714
|
+
const signals = [];
|
|
4715
|
+
const dependencies = dependencyNames(cwd);
|
|
4716
|
+
for (const { preset, pattern } of DEPENDENCY_SIGNALS) {
|
|
4717
|
+
const hit = dependencies.find((name) => pattern.test(name));
|
|
4718
|
+
if (hit) signals.push({ preset, evidence: `it depends on "${hit}"` });
|
|
4719
|
+
}
|
|
4720
|
+
if (declaresJsx(cwd) && !signals.some((signal) => signal.preset === "react")) {
|
|
4721
|
+
signals.push({ preset: "react", evidence: 'its tsconfig sets "jsx"' });
|
|
4722
|
+
}
|
|
4723
|
+
const location = cwd.replaceAll("\\", "/");
|
|
4724
|
+
for (const { preset, pattern, evidence } of PATH_SIGNALS) {
|
|
4725
|
+
if (pattern.test(location) && !signals.some((signal) => signal.preset === preset)) {
|
|
4726
|
+
signals.push({ preset, evidence });
|
|
4727
|
+
}
|
|
4728
|
+
}
|
|
4729
|
+
return signals;
|
|
4730
|
+
}
|
|
4731
|
+
function presetContradiction(declared, cwd) {
|
|
4732
|
+
const signals = presetSignals(cwd);
|
|
4733
|
+
if (signals.length === 0) return void 0;
|
|
4734
|
+
if (signals.some((signal) => signal.preset === declared)) return void 0;
|
|
4735
|
+
const [first] = signals;
|
|
4736
|
+
if (!first) return void 0;
|
|
4737
|
+
const evidence = signals.map((signal) => signal.evidence).join(", and ");
|
|
4738
|
+
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.`;
|
|
4739
|
+
}
|
|
4740
|
+
|
|
4590
4741
|
// src/core/dispatch.ts
|
|
4591
4742
|
function resolveFlavour(opts) {
|
|
4592
4743
|
if (opts.preset) return opts.preset;
|
|
@@ -4646,6 +4797,12 @@ async function dispatch(opts) {
|
|
|
4646
4797
|
const detected = resolveFlavour(opts);
|
|
4647
4798
|
const adapter = resolve(opts.target, detected, opts.runner);
|
|
4648
4799
|
const preset = opts.preset ?? adapter.declaredPreset?.(opts.cwd) ?? detected;
|
|
4800
|
+
const contradiction = presetContradiction(preset, opts.cwd);
|
|
4801
|
+
if (contradiction !== void 0) {
|
|
4802
|
+
process.stderr.write(`sentinel (${opts.target}): ${contradiction}
|
|
4803
|
+
`);
|
|
4804
|
+
return 1;
|
|
4805
|
+
}
|
|
4649
4806
|
const context = { cwd: opts.cwd, preset };
|
|
4650
4807
|
const plan2 = await adapter.plan(context);
|
|
4651
4808
|
if (plan2.blocked) {
|
|
@@ -4694,8 +4851,8 @@ export {
|
|
|
4694
4851
|
WORKSPACE_ROOT_MARKER,
|
|
4695
4852
|
findWorkspaceRoot,
|
|
4696
4853
|
ensureWorkspacePrep,
|
|
4697
|
-
resolveBin,
|
|
4698
4854
|
palette,
|
|
4855
|
+
resolveBin,
|
|
4699
4856
|
VERBS,
|
|
4700
4857
|
TARGETS,
|
|
4701
4858
|
PRESET_NAMES,
|
|
@@ -4704,4 +4861,4 @@ export {
|
|
|
4704
4861
|
detectFramework,
|
|
4705
4862
|
dispatch
|
|
4706
4863
|
};
|
|
4707
|
-
//# sourceMappingURL=chunk-
|
|
4864
|
+
//# sourceMappingURL=chunk-U2NAKQXR.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.6",
|
|
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",
|