@hublo/sentinel 1.1.5 → 1.1.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/dist/bin/sentinel.js +28 -2
- package/dist/{chunk-RLVLDC5H.js → chunk-W73ISYMG.js} +251 -102
- package/dist/index.js +1 -1
- package/oxlint/nest.json +1 -1
- package/oxlint/react-lib.json +1 -1
- package/oxlint/react.json +1 -1
- package/package.json +1 -1
package/dist/bin/sentinel.js
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
dispatch,
|
|
10
10
|
ensureWorkspacePrep,
|
|
11
11
|
findWorkspaceRoot,
|
|
12
|
+
inspectWorkspacePrep,
|
|
12
13
|
palette,
|
|
13
14
|
readNxProjectName,
|
|
14
15
|
readOwnVersion,
|
|
@@ -16,7 +17,7 @@ import {
|
|
|
16
17
|
registerAdapters,
|
|
17
18
|
resolve,
|
|
18
19
|
resolveBin
|
|
19
|
-
} from "../chunk-
|
|
20
|
+
} from "../chunk-W73ISYMG.js";
|
|
20
21
|
|
|
21
22
|
// bin/sentinel.ts
|
|
22
23
|
import { program } from "commander";
|
|
@@ -438,6 +439,10 @@ function renderStatusSummary(items, p) {
|
|
|
438
439
|
}
|
|
439
440
|
|
|
440
441
|
// src/cli/run-verb.ts
|
|
442
|
+
function workspacePrep(cwd2) {
|
|
443
|
+
const root = findWorkspaceRoot(cwd2);
|
|
444
|
+
return root ? inspectWorkspacePrep(root) : [];
|
|
445
|
+
}
|
|
441
446
|
async function runVerb(ctx) {
|
|
442
447
|
const { modules, scope } = resolveContext(ctx.cwd, { module: ctx.module, ci: ctx.ci });
|
|
443
448
|
const out = palette(process.stdout);
|
|
@@ -482,7 +487,15 @@ async function runVerb(ctx) {
|
|
|
482
487
|
});
|
|
483
488
|
const summary = generateSummaries(results, ctx.targets);
|
|
484
489
|
if (ctx.json) {
|
|
485
|
-
const base = ctx.verb === "status" || ctx.verb === "inspect" ? {
|
|
490
|
+
const base = ctx.verb === "status" || ctx.verb === "inspect" ? {
|
|
491
|
+
...summary,
|
|
492
|
+
coverage: statusCoverage(summary.results),
|
|
493
|
+
// The root-level changes sentinel maintains, each with its justification checked
|
|
494
|
+
// NOW rather than described in a comment. They are the only thing sentinel does
|
|
495
|
+
// that nobody can see afterwards, and every one of them is temporary. See
|
|
496
|
+
// `inspectWorkspacePrep`.
|
|
497
|
+
...ctx.verb === "inspect" ? { workspace: workspacePrep(ctx.cwd) } : {}
|
|
498
|
+
} : summary;
|
|
486
499
|
const payload = ctx.toolArgs.length > 0 ? { ...base, toolArgs: ctx.toolArgs } : base;
|
|
487
500
|
process.stdout.write(JSON.stringify(payload, null, 2) + "\n");
|
|
488
501
|
} else if (ctx.verb === "status" || ctx.verb === "inspect" && modules.length > 1) {
|
|
@@ -493,6 +506,19 @@ async function runVerb(ctx) {
|
|
|
493
506
|
process.stdout.write(renderSummary(item, out) + "\n");
|
|
494
507
|
}
|
|
495
508
|
}
|
|
509
|
+
if (!ctx.json && ctx.verb === "inspect") {
|
|
510
|
+
const prep = workspacePrep(ctx.cwd);
|
|
511
|
+
if (prep.length > 0) {
|
|
512
|
+
process.stdout.write(`
|
|
513
|
+
sentinel maintains these at the workspace root:
|
|
514
|
+
`);
|
|
515
|
+
for (const entry of prep) {
|
|
516
|
+
process.stdout.write(` ${out.strong(entry.rule)}
|
|
517
|
+
${entry.reason}
|
|
518
|
+
`);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
}
|
|
496
522
|
process.stderr.write(
|
|
497
523
|
err.dim(` ${modules.length} module(s) [${scope}] in ${Date.now() - started}ms
|
|
498
524
|
`)
|
|
@@ -110,20 +110,6 @@ function readNxProjectName(dir) {
|
|
|
110
110
|
}
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
-
// src/shared/color.ts
|
|
114
|
-
import { styleText } from "util";
|
|
115
|
-
function palette(stream) {
|
|
116
|
-
const paint = (format, text) => styleText(format, text, { stream });
|
|
117
|
-
return {
|
|
118
|
-
ok: (text) => paint(["green", "bold"], text),
|
|
119
|
-
fail: (text) => paint(["red", "bold"], text),
|
|
120
|
-
warn: (text) => paint("yellow", text),
|
|
121
|
-
strong: (text) => paint("bold", text),
|
|
122
|
-
dim: (text) => paint("dim", text),
|
|
123
|
-
accent: (text) => paint("cyan", text)
|
|
124
|
-
};
|
|
125
|
-
}
|
|
126
|
-
|
|
127
113
|
// src/core/domain.ts
|
|
128
114
|
var VERBS = ["run", "inspect", "init", "migrate", "report", "status"];
|
|
129
115
|
var TARGETS = [
|
|
@@ -138,6 +124,20 @@ var TARGETS = [
|
|
|
138
124
|
];
|
|
139
125
|
var PRESET_NAMES = ["react", "nest", "svelte", "node", "tools"];
|
|
140
126
|
|
|
127
|
+
// src/shared/color.ts
|
|
128
|
+
import { styleText } from "util";
|
|
129
|
+
function palette(stream) {
|
|
130
|
+
const paint = (format, text) => styleText(format, text, { stream });
|
|
131
|
+
return {
|
|
132
|
+
ok: (text) => paint(["green", "bold"], text),
|
|
133
|
+
fail: (text) => paint(["red", "bold"], text),
|
|
134
|
+
warn: (text) => paint("yellow", text),
|
|
135
|
+
strong: (text) => paint("bold", text),
|
|
136
|
+
dim: (text) => paint("dim", text),
|
|
137
|
+
accent: (text) => paint("cyan", text)
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
141
|
// src/roles/format/adapters/oxfmt/oxfmt.adapter.ts
|
|
142
142
|
import { spawnSync } from "child_process";
|
|
143
143
|
import { existsSync as existsSync10, readFileSync as readFileSync9 } from "fs";
|
|
@@ -458,6 +458,40 @@ function ensureWorkspacePrep(opts) {
|
|
|
458
458
|
opts.formattedModule === void 0 ? void 0 : ensureFormatterExclusion(opts.root, opts.formattedModule, dryRun)
|
|
459
459
|
].filter((message) => message !== void 0);
|
|
460
460
|
}
|
|
461
|
+
function inspectWorkspacePrep(root) {
|
|
462
|
+
const entries = [];
|
|
463
|
+
let pkg = {};
|
|
464
|
+
const pkgPath = join6(root, "package.json");
|
|
465
|
+
if (existsSync5(pkgPath)) {
|
|
466
|
+
try {
|
|
467
|
+
pkg = JSON.parse(readFileSync4(pkgPath, "utf8"));
|
|
468
|
+
} catch {
|
|
469
|
+
pkg = {};
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
const pinned = pkg.pnpm?.overrides?.[OVERRIDE_KEY];
|
|
473
|
+
if (pinned !== void 0) {
|
|
474
|
+
const stillForks = declaredNativeTs(pkg);
|
|
475
|
+
entries.push({
|
|
476
|
+
rule: `package.json \u2192 pnpm.overrides.${OVERRIDE_KEY}`,
|
|
477
|
+
reason: stillForks === void 0 ? `pinned to ${pinned}, but the root no longer declares ${NATIVE_TS_ALIAS}, so nothing forks any more. This override is now dead config and can be removed.` : `pinned to ${pinned}, matching the second TypeScript the root declares (${NATIVE_TS_ALIAS}). It re-resolves i18next for EVERY module that depends on it, not only adopted ones. It retires with the second TypeScript.`
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
const own = readOwnPackage().name;
|
|
481
|
+
const yamlPath = join6(root, WORKSPACE_YAML);
|
|
482
|
+
if (existsSync5(yamlPath)) {
|
|
483
|
+
const yaml = readFileSync4(yamlPath, "utf8");
|
|
484
|
+
const listed = new RegExp(`^\\s*-\\s*['"]?${own.replace("/", "\\/")}['"]?\\s*$`, "m").test(yaml);
|
|
485
|
+
if (listed) {
|
|
486
|
+
const gated = new RegExp(`^\\s*minimumReleaseAge\\s*:`, "m").test(yaml);
|
|
487
|
+
entries.push({
|
|
488
|
+
rule: `${WORKSPACE_YAML} \u2192 ${RELEASE_AGE_KEY}: ${own}`,
|
|
489
|
+
reason: gated ? `${own} is exempt from this workspace's release-age gate, so a freshly published version installs instead of being held. That is a standing exemption from a supply-chain control: the exposure is one bump wide, since the version is pinned and the lockfile carries its integrity, so a re-publish of the same version does not pass. Retire it once a bump can simply wait out the window.` : `${own} is allow-listed, but this workspace no longer sets minimumReleaseAge, so there is no gate to be exempt from. This entry is now dead config and can be removed.`
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
return entries;
|
|
494
|
+
}
|
|
461
495
|
|
|
462
496
|
// src/shared/resolve-bin.ts
|
|
463
497
|
import { existsSync as existsSync6 } from "fs";
|
|
@@ -514,6 +548,7 @@ var PERMITTED_LOCAL_KEYS = [
|
|
|
514
548
|
"experimentalOperatorPosition"
|
|
515
549
|
];
|
|
516
550
|
var ALWAYS_LOCAL_KEYS = ["ignorePatterns", "overrides"];
|
|
551
|
+
var FORMAT_DEFAULT_IGNORES = ["**/*.toml"];
|
|
517
552
|
var PRETTIER_CONFIG_FILES = [
|
|
518
553
|
".prettierrc",
|
|
519
554
|
".prettierrc.json",
|
|
@@ -906,6 +941,22 @@ var OxfmtAdapter = class extends BaseAdapter {
|
|
|
906
941
|
appliesTo(_preset) {
|
|
907
942
|
return true;
|
|
908
943
|
}
|
|
944
|
+
/**
|
|
945
|
+
* The preset this module COMMITTED to, read from the `$sentinel` block it writes.
|
|
946
|
+
*
|
|
947
|
+
* Every other role implements this and format did not, so it was the only one answering from
|
|
948
|
+
* dependency detection alone. On an adopted Nest module whose own `.oxfmtrc.json` records
|
|
949
|
+
* `"preset": "nest"`, `--inspect` reported `flavour=node` — the module knew, and the role did
|
|
950
|
+
* not pass it on. Same command, four rows, two different answers about what the module is.
|
|
951
|
+
*
|
|
952
|
+
* It reads the RECORD rather than re-deriving: the block is what `--init` wrote, so it is the
|
|
953
|
+
* module's declaration, and a re-derivation from hoisted dependencies is exactly the wrong
|
|
954
|
+
* answer this exists to avoid.
|
|
955
|
+
*/
|
|
956
|
+
declaredPreset(cwd) {
|
|
957
|
+
const declared = readFormatAdoption(cwd).preset;
|
|
958
|
+
return declared !== null && PRESET_NAMES.includes(declared) ? declared : void 0;
|
|
959
|
+
}
|
|
909
960
|
plan(context) {
|
|
910
961
|
if (!hasSourceFiles(context.cwd, FORMATTABLE_EXTENSIONS)) {
|
|
911
962
|
return {
|
|
@@ -926,7 +977,9 @@ var OxfmtAdapter = class extends BaseAdapter {
|
|
|
926
977
|
const localKeys = Object.keys(local).filter((key) => !ALWAYS_LOCAL_KEYS.includes(key)).sort();
|
|
927
978
|
const inherited = inheritedIgnorePatterns(findWorkspaceRoot(context.cwd));
|
|
928
979
|
const declaredIgnores = Array.isArray(local.ignorePatterns) ? local.ignorePatterns.filter((v) => typeof v === "string") : [];
|
|
929
|
-
const ignorePatterns = [
|
|
980
|
+
const ignorePatterns = [
|
|
981
|
+
.../* @__PURE__ */ new Set([...declaredIgnores, ...inherited, ...FORMAT_DEFAULT_IGNORES])
|
|
982
|
+
].sort();
|
|
930
983
|
if (ignorePatterns.length > 0) local.ignorePatterns = ignorePatterns;
|
|
931
984
|
const unchanged = existing.adopted && existing.conformant;
|
|
932
985
|
const version = unchanged && existing.presetVersion ? existing.presetVersion : own.version;
|
|
@@ -1264,8 +1317,8 @@ function registerFormat() {
|
|
|
1264
1317
|
|
|
1265
1318
|
// src/roles/lint/adapters/oxlint/oxlint.adapter.ts
|
|
1266
1319
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
1267
|
-
import { existsSync as
|
|
1268
|
-
import { join as
|
|
1320
|
+
import { existsSync as existsSync17, readFileSync as readFileSync16, rmSync, writeFileSync as writeFileSync2 } from "fs";
|
|
1321
|
+
import { join as join20 } from "path";
|
|
1269
1322
|
|
|
1270
1323
|
// src/core/config/deferred-rules.ts
|
|
1271
1324
|
function deferredRuleNames(rules) {
|
|
@@ -1279,6 +1332,14 @@ var PRESET_DIR = "./node_modules/@hublo/sentinel/oxlint";
|
|
|
1279
1332
|
function presetPath(preset) {
|
|
1280
1333
|
return `${PRESET_DIR}/${preset}.json`;
|
|
1281
1334
|
}
|
|
1335
|
+
function isSentinelPreset(entry) {
|
|
1336
|
+
return entry.includes("@hublo/sentinel/oxlint/");
|
|
1337
|
+
}
|
|
1338
|
+
function extendsWithPreset(current, variant) {
|
|
1339
|
+
const own = presetPath(variant);
|
|
1340
|
+
const others = current.filter((entry) => entry !== own && !isSentinelPreset(entry));
|
|
1341
|
+
return [own, ...new Set(others)];
|
|
1342
|
+
}
|
|
1282
1343
|
function presetVariant(preset, cwd) {
|
|
1283
1344
|
const normalised = cwd.replaceAll("\\", "/");
|
|
1284
1345
|
return preset === "react" && /(^|\/)libs\//.test(normalised) ? "react-lib" : preset;
|
|
@@ -1298,7 +1359,7 @@ var ESLINT_CONFIG_FILES = [
|
|
|
1298
1359
|
"eslint.config.ts"
|
|
1299
1360
|
];
|
|
1300
1361
|
function lintTarget() {
|
|
1301
|
-
return { cache: true, inputs: ["default", `{projectRoot}/${LINT_CONFIG_FILE}`] };
|
|
1362
|
+
return { cache: true, inputs: ["default", "^default", `{projectRoot}/${LINT_CONFIG_FILE}`] };
|
|
1302
1363
|
}
|
|
1303
1364
|
|
|
1304
1365
|
// src/roles/lint/presets/base.json
|
|
@@ -1642,8 +1703,8 @@ var nest_default = {
|
|
|
1642
1703
|
},
|
|
1643
1704
|
"typescript/return-await": {
|
|
1644
1705
|
severity: "warn",
|
|
1645
|
-
reason: "replaces the deprecated core `no-return-await`, which both modules set to error but which was never actually reporting: the nx lint target expanded to 2 files of 2888.
|
|
1646
|
-
options: ["
|
|
1706
|
+
reason: "replaces the deprecated core `no-return-await`, which both modules set to error but which was never actually reporting: the nx lint target expanded to 2 files of 2888. `in-try-catch` rather than `never`, and the planning adoption is what settled it. `never` forces `return await f()` to become `const x = await f(); return x` even inside a `try`, where the await is REQUIRED for the catch to fire: it produced 10 such edits across 15 modules, every one of them inside a try/catch, and every one of them a rewrite that made the code longer without making it safer. Worse, the obvious reading of the rule is to delete the `await`, which silently stops the catch from running. `in-try-catch` is what typescript-eslint recommends and flags none of those 10. Held at warn so adoption does not turn a green module red, and raised to error once the remaining findings are fixed.",
|
|
1707
|
+
options: ["in-try-catch"]
|
|
1647
1708
|
},
|
|
1648
1709
|
"typescript/switch-exhaustiveness-check": [
|
|
1649
1710
|
"error",
|
|
@@ -2034,8 +2095,8 @@ var react_lib_default = {
|
|
|
2034
2095
|
},
|
|
2035
2096
|
"typescript/return-await": {
|
|
2036
2097
|
severity: "warn",
|
|
2037
|
-
reason: "replaces the deprecated core `no-return-await
|
|
2038
|
-
options: ["
|
|
2098
|
+
reason: "replaces the deprecated core `no-return-await`, which both modules set to error but which was never actually reporting: the nx lint target expanded to 2 files of 2888. `in-try-catch` rather than `never`, and the planning adoption is what settled it. `never` forces `return await f()` to become `const x = await f(); return x` even inside a `try`, where the await is REQUIRED for the catch to fire: it produced 10 such edits across 15 modules, every one of them inside a try/catch, and every one of them a rewrite that made the code longer without making it safer. Worse, the obvious reading of the rule is to delete the `await`, which silently stops the catch from running. `in-try-catch` is what typescript-eslint recommends and flags none of those 10. Held at warn so adoption does not turn a green module red, and raised to error once the remaining findings are fixed.",
|
|
2099
|
+
options: ["in-try-catch"]
|
|
2039
2100
|
},
|
|
2040
2101
|
"use-isnan": [
|
|
2041
2102
|
"error",
|
|
@@ -2608,8 +2669,8 @@ var react_default = {
|
|
|
2608
2669
|
"typescript/restrict-template-expressions": "error",
|
|
2609
2670
|
"typescript/return-await": {
|
|
2610
2671
|
severity: "warn",
|
|
2611
|
-
reason: "replaces the deprecated core `no-return-await`, which both modules set to error but which was never actually reporting: the nx lint target expanded to 2 files of 2888.
|
|
2612
|
-
options: ["
|
|
2672
|
+
reason: "replaces the deprecated core `no-return-await`, which both modules set to error but which was never actually reporting: the nx lint target expanded to 2 files of 2888. `in-try-catch` rather than `never`, and the planning adoption is what settled it. `never` forces `return await f()` to become `const x = await f(); return x` even inside a `try`, where the await is REQUIRED for the catch to fire: it produced 10 such edits across 15 modules, every one of them inside a try/catch, and every one of them a rewrite that made the code longer without making it safer. Worse, the obvious reading of the rule is to delete the `await`, which silently stops the catch from running. `in-try-catch` is what typescript-eslint recommends and flags none of those 10. Held at warn so adoption does not turn a green module red, and raised to error once the remaining findings are fixed.",
|
|
2673
|
+
options: ["in-try-catch"]
|
|
2613
2674
|
},
|
|
2614
2675
|
"typescript/switch-exhaustiveness-check": {
|
|
2615
2676
|
severity: "warn",
|
|
@@ -3070,6 +3131,10 @@ function downgradedRulesFor(preset) {
|
|
|
3070
3131
|
return downgradedFor(preset);
|
|
3071
3132
|
}
|
|
3072
3133
|
|
|
3134
|
+
// src/roles/lint/extra-layers.ts
|
|
3135
|
+
import { existsSync as existsSync11, readFileSync as readFileSync10 } from "fs";
|
|
3136
|
+
import { isAbsolute as isAbsolute2, join as join13, resolve as resolve3 } from "path";
|
|
3137
|
+
|
|
3073
3138
|
// src/roles/lint/module-baseline.ts
|
|
3074
3139
|
var LINT_BASELINE_FILE = ".oxlintrc.baseline.json";
|
|
3075
3140
|
var LINT_MEASURE_FILE = ".oxlintrc.measure.json";
|
|
@@ -3078,10 +3143,7 @@ function holdsFrom(errorCounts) {
|
|
|
3078
3143
|
return [...errorCounts].map(([rule, count]) => ({ rule, count })).sort((left, right) => right.count - left.count || left.rule.localeCompare(right.rule));
|
|
3079
3144
|
}
|
|
3080
3145
|
function renderBaseline(holds, version) {
|
|
3081
|
-
const entries = holds.map(
|
|
3082
|
-
({ rule, count }) => ` // ${count} violation${count === 1 ? "" : "s"} when this module adopted
|
|
3083
|
-
${JSON.stringify(rule)}: "warn"`
|
|
3084
|
-
).join(",\n");
|
|
3146
|
+
const entries = holds.map(({ rule }) => ` ${JSON.stringify(rule)}: "warn"`).join(",\n");
|
|
3085
3147
|
return [
|
|
3086
3148
|
"{",
|
|
3087
3149
|
` // Written by @hublo/sentinel@${version}. Do not edit by hand: \`sentinel --init --lint\``,
|
|
@@ -3089,9 +3151,15 @@ function renderBaseline(holds, version) {
|
|
|
3089
3151
|
" // and returns to `error` on its own.",
|
|
3090
3152
|
" //",
|
|
3091
3153
|
" // These rules are held at `warn` because this module ALREADY violated them when it",
|
|
3092
|
-
" // adopted. They still run and still report
|
|
3093
|
-
" //
|
|
3094
|
-
" //
|
|
3154
|
+
" // adopted. They still run and still report, so the build cannot fail on them.",
|
|
3155
|
+
" //",
|
|
3156
|
+
" // The hold is per RULE, not per occurrence: oxlint cannot freeze a specific list of",
|
|
3157
|
+
" // violations, so a NEW violation of one of these rules is covered too, and will only",
|
|
3158
|
+
" // warn. That is the cost of adopting without a red build, and the reason to clear this",
|
|
3159
|
+
" // file rather than live with it.",
|
|
3160
|
+
" //",
|
|
3161
|
+
" // `sentinel --inspect --lint` counts what is left. Fix them and re-run `--init --lint`",
|
|
3162
|
+
" // to get the preset severity back.",
|
|
3095
3163
|
' "rules": {',
|
|
3096
3164
|
entries,
|
|
3097
3165
|
" }",
|
|
@@ -3111,6 +3179,44 @@ function describeHolds(holds) {
|
|
|
3111
3179
|
return `held ${holds.length} rule(s) at \`warn\` for this module, covering ${total} violation(s) that were already there: ${listed}${rest}. They are written to ${LINT_BASELINE_FILE}, they still report, and they return to \`error\` once fixed and re-initialized`;
|
|
3112
3180
|
}
|
|
3113
3181
|
|
|
3182
|
+
// src/roles/lint/extra-layers.ts
|
|
3183
|
+
function isSentinelPreset2(entry) {
|
|
3184
|
+
return entry.includes("@hublo/sentinel/oxlint/");
|
|
3185
|
+
}
|
|
3186
|
+
function ruleCount(cwd, specifier) {
|
|
3187
|
+
const path = isAbsolute2(specifier) ? specifier : resolve3(cwd, specifier);
|
|
3188
|
+
if (!existsSync11(path)) return void 0;
|
|
3189
|
+
try {
|
|
3190
|
+
const parsed = parseJsonc(
|
|
3191
|
+
readFileSync10(path, "utf8"),
|
|
3192
|
+
specifier
|
|
3193
|
+
);
|
|
3194
|
+
return Object.keys(parsed.rules ?? {}).length;
|
|
3195
|
+
} catch {
|
|
3196
|
+
return void 0;
|
|
3197
|
+
}
|
|
3198
|
+
}
|
|
3199
|
+
function extraLayers(cwd, extendsList) {
|
|
3200
|
+
return extendsList.filter((entry) => !isSentinelPreset2(entry) && entry !== LINT_BASELINE_SPECIFIER).map((entry) => {
|
|
3201
|
+
const count = ruleCount(cwd, entry);
|
|
3202
|
+
return {
|
|
3203
|
+
rule: entry,
|
|
3204
|
+
reason: count === void 0 ? "this module extends it, but it could not be read from here" : `${count} rule(s) on top of the preset, applied after it`
|
|
3205
|
+
};
|
|
3206
|
+
});
|
|
3207
|
+
}
|
|
3208
|
+
function committedExtendsList(cwd, configFile) {
|
|
3209
|
+
try {
|
|
3210
|
+
const parsed = parseJsonc(
|
|
3211
|
+
readFileSync10(join13(cwd, configFile), "utf8"),
|
|
3212
|
+
configFile
|
|
3213
|
+
);
|
|
3214
|
+
return Array.isArray(parsed.extends) ? parsed.extends.filter((entry) => typeof entry === "string") : [];
|
|
3215
|
+
} catch {
|
|
3216
|
+
return [];
|
|
3217
|
+
}
|
|
3218
|
+
}
|
|
3219
|
+
|
|
3114
3220
|
// src/roles/lint/parse-diagnostics.ts
|
|
3115
3221
|
function ruleId(code) {
|
|
3116
3222
|
const inner = /\(([^)]+)\)/.exec(code);
|
|
@@ -3161,8 +3267,8 @@ function errorCountsByConfigRule(stdout) {
|
|
|
3161
3267
|
}
|
|
3162
3268
|
|
|
3163
3269
|
// src/core/config/read-adoption.ts
|
|
3164
|
-
import { existsSync as
|
|
3165
|
-
import { join as
|
|
3270
|
+
import { existsSync as existsSync13, readFileSync as readFileSync12 } from "fs";
|
|
3271
|
+
import { join as join15 } from "path";
|
|
3166
3272
|
|
|
3167
3273
|
// src/core/config/owned-keys.ts
|
|
3168
3274
|
function presetOwnedKeys(config, permitted, presetSets) {
|
|
@@ -3177,12 +3283,12 @@ function localOnlyKeys(config, permitted, presetSets) {
|
|
|
3177
3283
|
}
|
|
3178
3284
|
|
|
3179
3285
|
// src/core/config/resolve-config-target.ts
|
|
3180
|
-
import { existsSync as
|
|
3181
|
-
import { join as
|
|
3286
|
+
import { existsSync as existsSync12, readFileSync as readFileSync11 } from "fs";
|
|
3287
|
+
import { join as join14 } from "path";
|
|
3182
3288
|
function readExtends(absolutePath) {
|
|
3183
3289
|
let parsed;
|
|
3184
3290
|
try {
|
|
3185
|
-
parsed = parseJsonc(
|
|
3291
|
+
parsed = parseJsonc(readFileSync11(absolutePath, "utf8"), absolutePath);
|
|
3186
3292
|
} catch {
|
|
3187
3293
|
return [];
|
|
3188
3294
|
}
|
|
@@ -3196,8 +3302,8 @@ function resolveConfigTarget(moduleDir, { candidates, markers, fallback }) {
|
|
|
3196
3302
|
let existing;
|
|
3197
3303
|
let existingExtendsSomething = false;
|
|
3198
3304
|
for (const candidate of candidates) {
|
|
3199
|
-
const absolutePath =
|
|
3200
|
-
if (!
|
|
3305
|
+
const absolutePath = join14(moduleDir, candidate);
|
|
3306
|
+
if (!existsSync12(absolutePath)) continue;
|
|
3201
3307
|
const chain = readExtends(absolutePath);
|
|
3202
3308
|
if (existing === void 0) {
|
|
3203
3309
|
existing = candidate;
|
|
@@ -3230,12 +3336,12 @@ var NOT_ADOPTED2 = (configFile, unreadable = null) => ({
|
|
|
3230
3336
|
});
|
|
3231
3337
|
function readAdoption(cwd, options) {
|
|
3232
3338
|
const target = resolveConfigTarget(cwd, options);
|
|
3233
|
-
if (target.reason === "none" || !
|
|
3339
|
+
if (target.reason === "none" || !existsSync13(join15(cwd, target.path))) {
|
|
3234
3340
|
return NOT_ADOPTED2(target.reason === "none" ? null : target.path);
|
|
3235
3341
|
}
|
|
3236
3342
|
let parsed;
|
|
3237
3343
|
try {
|
|
3238
|
-
parsed = parseJsonc(
|
|
3344
|
+
parsed = parseJsonc(readFileSync12(join15(cwd, target.path), "utf8"), target.path);
|
|
3239
3345
|
} catch (error) {
|
|
3240
3346
|
const reason = error instanceof Error ? error.message : String(error);
|
|
3241
3347
|
return NOT_ADOPTED2(target.path, `${target.path} could not be parsed (${reason})`);
|
|
@@ -3269,9 +3375,9 @@ function readLintAdoption(cwd) {
|
|
|
3269
3375
|
}
|
|
3270
3376
|
|
|
3271
3377
|
// src/roles/lint/resolve-oxlint.ts
|
|
3272
|
-
import { existsSync as
|
|
3378
|
+
import { existsSync as existsSync14 } from "fs";
|
|
3273
3379
|
import { createRequire as createRequire3 } from "module";
|
|
3274
|
-
import { delimiter as delimiter2, dirname as dirname5, join as
|
|
3380
|
+
import { delimiter as delimiter2, dirname as dirname5, join as join16 } from "path";
|
|
3275
3381
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
3276
3382
|
var PACKAGE_OF = {
|
|
3277
3383
|
oxlint: "oxlint",
|
|
@@ -3296,30 +3402,30 @@ function tsgolintShim(cwd) {
|
|
|
3296
3402
|
for (const owner of ["oxlint-tsgolint", "oxlint"]) {
|
|
3297
3403
|
try {
|
|
3298
3404
|
const packageDir = dirname5(require3.resolve(`${owner}/package.json`));
|
|
3299
|
-
candidates.push(
|
|
3300
|
-
candidates.push(
|
|
3405
|
+
candidates.push(join16(packageDir, "node_modules", ".bin", "tsgolint"));
|
|
3406
|
+
candidates.push(join16(packageDir, "..", ".bin", "tsgolint"));
|
|
3301
3407
|
} catch {
|
|
3302
3408
|
}
|
|
3303
3409
|
}
|
|
3304
3410
|
try {
|
|
3305
3411
|
const ownRoot = dirname5(require3.resolve("@hublo/sentinel/package.json"));
|
|
3306
|
-
candidates.push(
|
|
3412
|
+
candidates.push(join16(ownRoot, "node_modules", ".bin", "tsgolint"));
|
|
3307
3413
|
} catch {
|
|
3308
3414
|
candidates.push(resolveBin(dirname5(fileURLToPath2(import.meta.url)), "tsgolint") ?? "");
|
|
3309
3415
|
}
|
|
3310
|
-
return candidates.find((candidate) => candidate !== "" &&
|
|
3416
|
+
return candidates.find((candidate) => candidate !== "" && existsSync14(candidate));
|
|
3311
3417
|
}
|
|
3312
3418
|
function oxlintSearchPath(cwd) {
|
|
3313
3419
|
return binSearchPath(cwd);
|
|
3314
3420
|
}
|
|
3315
3421
|
|
|
3316
3422
|
// src/roles/lint/adapters/oxlint/plan.ts
|
|
3317
|
-
import { existsSync as
|
|
3318
|
-
import { join as
|
|
3423
|
+
import { existsSync as existsSync16, readFileSync as readFileSync15 } from "fs";
|
|
3424
|
+
import { join as join19 } from "path";
|
|
3319
3425
|
|
|
3320
3426
|
// src/roles/lint/eslint-ignores.ts
|
|
3321
|
-
import { existsSync as
|
|
3322
|
-
import { join as
|
|
3427
|
+
import { existsSync as existsSync15, readFileSync as readFileSync13 } from "fs";
|
|
3428
|
+
import { join as join17 } from "path";
|
|
3323
3429
|
var IGNORE_BLOCKS = [/\bignores\s*:\s*\[([^\]]*)\]/g, /\bglobalIgnores\s*\(\s*\[([^\]]*)\]/g];
|
|
3324
3430
|
var STRING_LITERAL = /['"`]([^'"`]+)['"`]/g;
|
|
3325
3431
|
var OPAQUE_SOURCE = /\b(includeIgnoreFile)\s*\([^)]*\)/g;
|
|
@@ -3332,11 +3438,11 @@ function readRootEslintIgnores(root) {
|
|
|
3332
3438
|
return { patterns: patterns.filter((pattern) => pattern.startsWith("**/")), unresolved };
|
|
3333
3439
|
}
|
|
3334
3440
|
function readEslintIgnores(cwd) {
|
|
3335
|
-
const config = ESLINT_CONFIG_FILES.map((name) =>
|
|
3441
|
+
const config = ESLINT_CONFIG_FILES.map((name) => join17(cwd, name)).find((path) => existsSync15(path));
|
|
3336
3442
|
if (!config) return { patterns: [], unresolved: [] };
|
|
3337
3443
|
let source;
|
|
3338
3444
|
try {
|
|
3339
|
-
source =
|
|
3445
|
+
source = readFileSync13(config, "utf8");
|
|
3340
3446
|
} catch {
|
|
3341
3447
|
return { patterns: [], unresolved: [] };
|
|
3342
3448
|
}
|
|
@@ -3390,8 +3496,8 @@ function lintPresetFor(preset) {
|
|
|
3390
3496
|
}
|
|
3391
3497
|
|
|
3392
3498
|
// src/roles/lint/rename-suppressions.ts
|
|
3393
|
-
import { readdirSync as readdirSync2, readFileSync as
|
|
3394
|
-
import { join as
|
|
3499
|
+
import { readdirSync as readdirSync2, readFileSync as readFileSync14, statSync } from "fs";
|
|
3500
|
+
import { join as join18, relative as relative2 } from "path";
|
|
3395
3501
|
var SOURCE_EXTENSIONS = [
|
|
3396
3502
|
".ts",
|
|
3397
3503
|
".tsx",
|
|
@@ -3438,7 +3544,7 @@ function* sourceFiles(dir) {
|
|
|
3438
3544
|
return;
|
|
3439
3545
|
}
|
|
3440
3546
|
for (const entry of entries) {
|
|
3441
|
-
const full =
|
|
3547
|
+
const full = join18(dir, entry);
|
|
3442
3548
|
let isDirectory;
|
|
3443
3549
|
try {
|
|
3444
3550
|
isDirectory = statSync(full).isDirectory();
|
|
@@ -3458,7 +3564,7 @@ function findSuppressionRenames(cwd, renames) {
|
|
|
3458
3564
|
for (const file of sourceFiles(cwd)) {
|
|
3459
3565
|
let content;
|
|
3460
3566
|
try {
|
|
3461
|
-
content =
|
|
3567
|
+
content = readFileSync14(file, "utf8");
|
|
3462
3568
|
} catch {
|
|
3463
3569
|
continue;
|
|
3464
3570
|
}
|
|
@@ -3498,7 +3604,17 @@ var DEFAULT_IGNORE_PATTERNS = [
|
|
|
3498
3604
|
"**/dist/**",
|
|
3499
3605
|
"**/node_modules/**",
|
|
3500
3606
|
"**/coverage/**",
|
|
3501
|
-
|
|
3607
|
+
// `**/*.mock.ts` used to sit here and has been removed. It was the only entry in this list
|
|
3608
|
+
// with no reason written next to it, and it does not meet the bar the paragraph above sets:
|
|
3609
|
+
// a mock is not build output, not vendored and not generated. It is hand-written TypeScript
|
|
3610
|
+
// that ships with the tests, and lint rules have as much to say about it as about any other
|
|
3611
|
+
// source file. Ignoring it quietly narrowed what an adopted module checks — career lost
|
|
3612
|
+
// `src/test/router.mock.ts`, which its previous ESLint config did lint.
|
|
3613
|
+
//
|
|
3614
|
+
// Measured before removing, because this is the one change that can turn a green module red:
|
|
3615
|
+
// career has 1 such file and it reports nothing; the 15 planning modules have none. 222 exist
|
|
3616
|
+
// repo-wide, and modules adopting later meet them through the baseline, which is exactly what
|
|
3617
|
+
// the baseline is for.
|
|
3502
3618
|
// Tool configuration is not source. A repo's shared ESLint config ignores these, and every
|
|
3503
3619
|
// module inherits that; adoption replaces the config chain, so without them the first thing
|
|
3504
3620
|
// an adopted module lints is its own jest setup file. Found on libs/front/components, where
|
|
@@ -3561,7 +3677,7 @@ function plan(context) {
|
|
|
3561
3677
|
].filter((pattern) => !defaults.has(bare(pattern)));
|
|
3562
3678
|
const carried = [.../* @__PURE__ */ new Set([...DEFAULT_IGNORE_PATTERNS, ...moduleOwn])];
|
|
3563
3679
|
const stub = {
|
|
3564
|
-
extends:
|
|
3680
|
+
extends: extendsWithPreset(committedExtends(context.cwd), variant),
|
|
3565
3681
|
...carried.length > 0 ? { ignorePatterns: carried } : {}
|
|
3566
3682
|
};
|
|
3567
3683
|
const operations = [
|
|
@@ -3576,7 +3692,7 @@ function plan(context) {
|
|
|
3576
3692
|
keys: removableDeps
|
|
3577
3693
|
});
|
|
3578
3694
|
}
|
|
3579
|
-
const eslintConfigs = ESLINT_CONFIG_FILES.filter((name) =>
|
|
3695
|
+
const eslintConfigs = ESLINT_CONFIG_FILES.filter((name) => existsSync16(join19(context.cwd, name)));
|
|
3580
3696
|
for (const name of eslintConfigs) operations.push({ kind: "delete", path: name });
|
|
3581
3697
|
operations.push(
|
|
3582
3698
|
...nxTargetOperations({
|
|
@@ -3671,10 +3787,21 @@ function lintScripts(cwd) {
|
|
|
3671
3787
|
}
|
|
3672
3788
|
return rewritten;
|
|
3673
3789
|
}
|
|
3790
|
+
function committedExtends(cwd) {
|
|
3791
|
+
try {
|
|
3792
|
+
const parsed = parseJsonc(
|
|
3793
|
+
readFileSync15(join19(cwd, LINT_CONFIG_FILE), "utf8"),
|
|
3794
|
+
LINT_CONFIG_FILE
|
|
3795
|
+
);
|
|
3796
|
+
return Array.isArray(parsed.extends) ? parsed.extends.filter((entry) => typeof entry === "string") : [];
|
|
3797
|
+
} catch {
|
|
3798
|
+
return [];
|
|
3799
|
+
}
|
|
3800
|
+
}
|
|
3674
3801
|
function committedIgnorePatterns(cwd) {
|
|
3675
3802
|
try {
|
|
3676
3803
|
const parsed = parseJsonc(
|
|
3677
|
-
|
|
3804
|
+
readFileSync15(join19(cwd, LINT_CONFIG_FILE), "utf8"),
|
|
3678
3805
|
LINT_CONFIG_FILE
|
|
3679
3806
|
);
|
|
3680
3807
|
return Array.isArray(parsed.ignorePatterns) ? parsed.ignorePatterns.filter((entry) => typeof entry === "string") : [];
|
|
@@ -3686,7 +3813,7 @@ function moduleEslintDependencies(cwd) {
|
|
|
3686
3813
|
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/");
|
|
3687
3814
|
let manifest;
|
|
3688
3815
|
try {
|
|
3689
|
-
manifest = JSON.parse(
|
|
3816
|
+
manifest = JSON.parse(readFileSync15(join19(cwd, "package.json"), "utf8"));
|
|
3690
3817
|
} catch {
|
|
3691
3818
|
return [];
|
|
3692
3819
|
}
|
|
@@ -3805,20 +3932,20 @@ var OxlintAdapter = class extends BaseAdapter {
|
|
|
3805
3932
|
* build the developer can see, rather than a silent half-adoption they cannot.
|
|
3806
3933
|
*/
|
|
3807
3934
|
writeModuleBaseline(ctx, oxlint, env) {
|
|
3808
|
-
const configPath =
|
|
3809
|
-
const baselinePath =
|
|
3810
|
-
if (!
|
|
3935
|
+
const configPath = join20(ctx.cwd, LINT_CONFIG_FILE);
|
|
3936
|
+
const baselinePath = join20(ctx.cwd, LINT_BASELINE_FILE);
|
|
3937
|
+
if (!existsSync17(configPath)) return;
|
|
3811
3938
|
let config;
|
|
3812
3939
|
try {
|
|
3813
3940
|
config = parseJsonc(
|
|
3814
|
-
|
|
3941
|
+
readFileSync16(configPath, "utf8"),
|
|
3815
3942
|
LINT_CONFIG_FILE
|
|
3816
3943
|
);
|
|
3817
3944
|
} catch {
|
|
3818
3945
|
return;
|
|
3819
3946
|
}
|
|
3820
3947
|
const current = Array.isArray(config.extends) ? config.extends : [];
|
|
3821
|
-
const measurePath =
|
|
3948
|
+
const measurePath = join20(ctx.cwd, LINT_MEASURE_FILE);
|
|
3822
3949
|
let measured;
|
|
3823
3950
|
try {
|
|
3824
3951
|
writeFileSync2(
|
|
@@ -3866,7 +3993,7 @@ var OxlintAdapter = class extends BaseAdapter {
|
|
|
3866
3993
|
}
|
|
3867
3994
|
}
|
|
3868
3995
|
async run(ctx) {
|
|
3869
|
-
if (!
|
|
3996
|
+
if (!existsSync17(join20(ctx.cwd, LINT_CONFIG_FILE))) {
|
|
3870
3997
|
process.stderr.write(
|
|
3871
3998
|
`sentinel lint(oxlint): no ${LINT_CONFIG_FILE} in this module; run \`sentinel --init --lint\` to adopt.
|
|
3872
3999
|
`
|
|
@@ -3950,7 +4077,12 @@ var OxlintAdapter = class extends BaseAdapter {
|
|
|
3950
4077
|
downgraded: downgradedRulesFor(presetVariant(ctx.preset, ctx.cwd)).map((entry) => ({
|
|
3951
4078
|
rule: entry.rule,
|
|
3952
4079
|
reason: entry.reason
|
|
3953
|
-
}))
|
|
4080
|
+
})),
|
|
4081
|
+
// What this module enforces BEYOND the preset. Not drift, and not covered by anything
|
|
4082
|
+
// else here: the stub still holds only `extends` and `ignorePatterns`, so a module with
|
|
4083
|
+
// a team layer reports `conformant=true drift=[]` and used to say nothing at all about
|
|
4084
|
+
// the extra rules it runs. See `extra-layers` for why that state is legitimate.
|
|
4085
|
+
layers: extraLayers(ctx.cwd, committedExtendsList(ctx.cwd, LINT_CONFIG_FILE))
|
|
3954
4086
|
};
|
|
3955
4087
|
}
|
|
3956
4088
|
/** How many rules the extended preset enforces, read from the preset on disk. */
|
|
@@ -3981,7 +4113,7 @@ var OxlintAdapter = class extends BaseAdapter {
|
|
|
3981
4113
|
let stub;
|
|
3982
4114
|
try {
|
|
3983
4115
|
stub = parseJsonc(
|
|
3984
|
-
|
|
4116
|
+
readFileSync16(join20(cwd, LINT_CONFIG_FILE), "utf8"),
|
|
3985
4117
|
LINT_CONFIG_FILE
|
|
3986
4118
|
);
|
|
3987
4119
|
} catch {
|
|
@@ -3991,7 +4123,7 @@ var OxlintAdapter = class extends BaseAdapter {
|
|
|
3991
4123
|
for (const entry of stub.extends ?? []) {
|
|
3992
4124
|
try {
|
|
3993
4125
|
const preset = parseJsonc(
|
|
3994
|
-
|
|
4126
|
+
readFileSync16(join20(cwd, entry), "utf8"),
|
|
3995
4127
|
entry
|
|
3996
4128
|
);
|
|
3997
4129
|
for (const rule of Object.keys(preset.rules ?? {})) names.add(rule);
|
|
@@ -4061,14 +4193,14 @@ var OxlintAdapter = class extends BaseAdapter {
|
|
|
4061
4193
|
let parsed;
|
|
4062
4194
|
try {
|
|
4063
4195
|
parsed = parseJsonc(
|
|
4064
|
-
|
|
4196
|
+
readFileSync16(join20(cwd, LINT_CONFIG_FILE), "utf8"),
|
|
4065
4197
|
LINT_CONFIG_FILE
|
|
4066
4198
|
);
|
|
4067
4199
|
} catch {
|
|
4068
4200
|
return void 0;
|
|
4069
4201
|
}
|
|
4070
4202
|
const targets = Array.isArray(parsed.extends) ? parsed.extends.filter((entry) => typeof entry === "string") : typeof parsed.extends === "string" ? [parsed.extends] : [];
|
|
4071
|
-
return targets.find((target) => !
|
|
4203
|
+
return targets.find((target) => !existsSync17(join20(cwd, target)));
|
|
4072
4204
|
}
|
|
4073
4205
|
/** Announce what is not enforced, so reduced coverage is never silent. */
|
|
4074
4206
|
announceDisabled(preset) {
|
|
@@ -4100,9 +4232,9 @@ function registerLint() {
|
|
|
4100
4232
|
|
|
4101
4233
|
// src/roles/typescript/adapters/tsc/tsc.adapter.ts
|
|
4102
4234
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
4103
|
-
import { existsSync as
|
|
4235
|
+
import { existsSync as existsSync18, readFileSync as readFileSync18 } from "fs";
|
|
4104
4236
|
import { createRequire as createRequire4 } from "module";
|
|
4105
|
-
import { join as
|
|
4237
|
+
import { join as join22 } from "path";
|
|
4106
4238
|
|
|
4107
4239
|
// src/roles/typescript/presets/base.json
|
|
4108
4240
|
var base_default3 = {
|
|
@@ -4283,8 +4415,8 @@ function readTsconfigAdoption(cwd) {
|
|
|
4283
4415
|
}
|
|
4284
4416
|
|
|
4285
4417
|
// src/roles/typescript/adapters/tsc/plan.ts
|
|
4286
|
-
import { readFileSync as
|
|
4287
|
-
import { join as
|
|
4418
|
+
import { readFileSync as readFileSync17 } from "fs";
|
|
4419
|
+
import { join as join21 } from "path";
|
|
4288
4420
|
|
|
4289
4421
|
// src/roles/typescript/typecheck-script.ts
|
|
4290
4422
|
var SENTINEL_TYPECHECK_COMMAND = "sentinel --run --typescript";
|
|
@@ -4306,7 +4438,10 @@ function keepsForeignChecker(existing) {
|
|
|
4306
4438
|
|
|
4307
4439
|
// src/roles/typescript/adapters/tsc/plan.ts
|
|
4308
4440
|
var INSTALL_NOTE = "run `pnpm install` to fetch @hublo/sentinel (added to the module devDependencies) so `extends` and the typecheck script resolve";
|
|
4309
|
-
var TYPECHECK_TARGET = {
|
|
4441
|
+
var TYPECHECK_TARGET = {
|
|
4442
|
+
cache: true,
|
|
4443
|
+
inputs: ["default", "^default", "{projectRoot}/tsconfig.json"]
|
|
4444
|
+
};
|
|
4310
4445
|
function typecheckScripts(cwd) {
|
|
4311
4446
|
return {
|
|
4312
4447
|
// Reads the nx target too, not only the npm script: five modules in this repo keep a
|
|
@@ -4372,7 +4507,7 @@ function planAdoption(context) {
|
|
|
4372
4507
|
};
|
|
4373
4508
|
}
|
|
4374
4509
|
const existing = parseJsonc(
|
|
4375
|
-
|
|
4510
|
+
readFileSync17(join21(context.cwd, target.path), "utf8"),
|
|
4376
4511
|
target.path
|
|
4377
4512
|
);
|
|
4378
4513
|
const extendsChain = composeExtends(existing.extends, preset);
|
|
@@ -4510,7 +4645,7 @@ var TscAdapter = class _TscAdapter extends BaseAdapter {
|
|
|
4510
4645
|
let chain;
|
|
4511
4646
|
try {
|
|
4512
4647
|
const parsed = parseJsonc(
|
|
4513
|
-
|
|
4648
|
+
readFileSync18(join22(cwd, target.path), "utf8"),
|
|
4514
4649
|
target.path
|
|
4515
4650
|
);
|
|
4516
4651
|
chain = parsed.extends;
|
|
@@ -4523,7 +4658,7 @@ var TscAdapter = class _TscAdapter extends BaseAdapter {
|
|
|
4523
4658
|
);
|
|
4524
4659
|
if (preset === void 0) return void 0;
|
|
4525
4660
|
try {
|
|
4526
|
-
createRequire4(
|
|
4661
|
+
createRequire4(join22(cwd, "noop.js")).resolve(preset);
|
|
4527
4662
|
return void 0;
|
|
4528
4663
|
} catch {
|
|
4529
4664
|
return preset;
|
|
@@ -4619,7 +4754,7 @@ var TscAdapter = class _TscAdapter extends BaseAdapter {
|
|
|
4619
4754
|
referencedProjects(cwd, config) {
|
|
4620
4755
|
try {
|
|
4621
4756
|
const parsed = parseJsonc(
|
|
4622
|
-
|
|
4757
|
+
readFileSync18(join22(cwd, config), "utf8"),
|
|
4623
4758
|
config
|
|
4624
4759
|
);
|
|
4625
4760
|
const referenced = (parsed.references ?? []).map((reference) => reference.path).filter((path) => typeof path === "string" && path.length > 0);
|
|
@@ -4635,7 +4770,7 @@ var TscAdapter = class _TscAdapter extends BaseAdapter {
|
|
|
4635
4770
|
* check.
|
|
4636
4771
|
*/
|
|
4637
4772
|
typecheckTarget(cwd) {
|
|
4638
|
-
if (
|
|
4773
|
+
if (existsSync18(join22(cwd, "tsconfig.json"))) return "tsconfig.json";
|
|
4639
4774
|
const target = resolveTsconfigTarget(cwd);
|
|
4640
4775
|
return target.reason === "none" ? null : target.path;
|
|
4641
4776
|
}
|
|
@@ -4838,8 +4973,8 @@ function replaceLines(current, replacements) {
|
|
|
4838
4973
|
}
|
|
4839
4974
|
|
|
4840
4975
|
// src/core/apply-plan.ts
|
|
4841
|
-
import { existsSync as
|
|
4842
|
-
import { resolve as
|
|
4976
|
+
import { existsSync as existsSync19, readFileSync as readFileSync19, renameSync, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
4977
|
+
import { resolve as resolve4, sep } from "path";
|
|
4843
4978
|
import { applyEdits, findNodeAtLocation, modify, parseTree } from "jsonc-parser";
|
|
4844
4979
|
|
|
4845
4980
|
// src/shared/deep-merge.ts
|
|
@@ -4849,15 +4984,15 @@ function isPlainObject(value) {
|
|
|
4849
4984
|
|
|
4850
4985
|
// src/core/apply-plan.ts
|
|
4851
4986
|
function resolveWithinRoot(cwd, relativePath) {
|
|
4852
|
-
const root =
|
|
4853
|
-
const absolutePath =
|
|
4987
|
+
const root = resolve4(cwd);
|
|
4988
|
+
const absolutePath = resolve4(root, relativePath);
|
|
4854
4989
|
if (absolutePath !== root && !absolutePath.startsWith(root + sep)) {
|
|
4855
4990
|
throw new Error(`Refusing to write outside the module root: "${relativePath}".`);
|
|
4856
4991
|
}
|
|
4857
4992
|
return absolutePath;
|
|
4858
4993
|
}
|
|
4859
4994
|
function readIfExists(absolutePath) {
|
|
4860
|
-
return
|
|
4995
|
+
return existsSync19(absolutePath) ? readFileSync19(absolutePath, "utf8") : void 0;
|
|
4861
4996
|
}
|
|
4862
4997
|
function* leaves(value, prefix = []) {
|
|
4863
4998
|
for (const [key, keyValue] of Object.entries(value)) {
|
|
@@ -4975,8 +5110,8 @@ function applyPlan(cwd, plan2) {
|
|
|
4975
5110
|
}
|
|
4976
5111
|
|
|
4977
5112
|
// src/core/config/preset-evidence.ts
|
|
4978
|
-
import { existsSync as
|
|
4979
|
-
import { join as
|
|
5113
|
+
import { existsSync as existsSync20, readdirSync as readdirSync3, readFileSync as readFileSync20 } from "fs";
|
|
5114
|
+
import { join as join23 } from "path";
|
|
4980
5115
|
var PATH_SIGNALS = [
|
|
4981
5116
|
{
|
|
4982
5117
|
preset: "nest",
|
|
@@ -4990,11 +5125,11 @@ var DEPENDENCY_SIGNALS = [
|
|
|
4990
5125
|
{ preset: "nest", pattern: /^@nestjs\// }
|
|
4991
5126
|
];
|
|
4992
5127
|
function dependencyNames(cwd) {
|
|
4993
|
-
const path =
|
|
4994
|
-
if (!
|
|
5128
|
+
const path = join23(cwd, "package.json");
|
|
5129
|
+
if (!existsSync20(path)) return [];
|
|
4995
5130
|
try {
|
|
4996
5131
|
const manifest = parseJsonc(
|
|
4997
|
-
|
|
5132
|
+
readFileSync20(path, "utf8"),
|
|
4998
5133
|
path
|
|
4999
5134
|
);
|
|
5000
5135
|
return [
|
|
@@ -5017,7 +5152,7 @@ function declaresJsx(cwd) {
|
|
|
5017
5152
|
for (const name of entries) {
|
|
5018
5153
|
try {
|
|
5019
5154
|
const config = parseJsonc(
|
|
5020
|
-
|
|
5155
|
+
readFileSync20(join23(cwd, name), "utf8"),
|
|
5021
5156
|
name
|
|
5022
5157
|
);
|
|
5023
5158
|
if (config.compilerOptions?.jsx !== void 0) return true;
|
|
@@ -5113,13 +5248,26 @@ async function dispatch(opts) {
|
|
|
5113
5248
|
const detected = resolveFlavour(opts);
|
|
5114
5249
|
const adapter = resolve(opts.target, detected, opts.runner);
|
|
5115
5250
|
const preset = opts.preset ?? adapter.declaredPreset?.(opts.cwd) ?? detected;
|
|
5116
|
-
|
|
5117
|
-
|
|
5118
|
-
|
|
5251
|
+
if (opts.preset !== void 0) {
|
|
5252
|
+
const contradiction = presetContradiction(opts.preset, opts.cwd);
|
|
5253
|
+
if (contradiction !== void 0) {
|
|
5254
|
+
process.stderr.write(`sentinel (${opts.target}): ${contradiction}
|
|
5119
5255
|
`);
|
|
5120
|
-
|
|
5256
|
+
return 1;
|
|
5257
|
+
}
|
|
5258
|
+
}
|
|
5259
|
+
let effective = preset;
|
|
5260
|
+
if (opts.preset === void 0 && adapter.declaredPreset?.(opts.cwd) === void 0) {
|
|
5261
|
+
const [signal] = presetSignals(opts.cwd);
|
|
5262
|
+
if (signal !== void 0 && signal.preset !== preset) {
|
|
5263
|
+
effective = signal.preset;
|
|
5264
|
+
process.stderr.write(
|
|
5265
|
+
` detected "${preset}" from dependencies, but ${signal.evidence}, so adopting as "${signal.preset}". Pass --preset to override.
|
|
5266
|
+
`
|
|
5267
|
+
);
|
|
5268
|
+
}
|
|
5121
5269
|
}
|
|
5122
|
-
const context = { cwd: opts.cwd, preset };
|
|
5270
|
+
const context = { cwd: opts.cwd, preset: effective };
|
|
5123
5271
|
const plan2 = await adapter.plan(context);
|
|
5124
5272
|
if (plan2.blocked) {
|
|
5125
5273
|
process.stderr.write(`sentinel (${opts.target}): ${plan2.blocked}
|
|
@@ -5164,17 +5312,18 @@ export {
|
|
|
5164
5312
|
readOwnVersion,
|
|
5165
5313
|
readProjectPackageJson,
|
|
5166
5314
|
readNxProjectName,
|
|
5315
|
+
VERBS,
|
|
5316
|
+
TARGETS,
|
|
5317
|
+
PRESET_NAMES,
|
|
5167
5318
|
WORKSPACE_ROOT_MARKER,
|
|
5168
5319
|
findWorkspaceRoot,
|
|
5169
5320
|
ensureWorkspacePrep,
|
|
5321
|
+
inspectWorkspacePrep,
|
|
5170
5322
|
palette,
|
|
5171
5323
|
resolveBin,
|
|
5172
|
-
VERBS,
|
|
5173
|
-
TARGETS,
|
|
5174
|
-
PRESET_NAMES,
|
|
5175
5324
|
registerAdapters,
|
|
5176
5325
|
describeFramework,
|
|
5177
5326
|
detectFramework,
|
|
5178
5327
|
dispatch
|
|
5179
5328
|
};
|
|
5180
|
-
//# sourceMappingURL=chunk-
|
|
5329
|
+
//# sourceMappingURL=chunk-W73ISYMG.js.map
|
package/dist/index.js
CHANGED
package/oxlint/nest.json
CHANGED
package/oxlint/react-lib.json
CHANGED
package/oxlint/react.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hublo/sentinel",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.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",
|