@hublo/sentinel 1.5.0-alpha.2 → 1.5.0-alpha.20
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 +8 -6
- package/dist/{chunk-Z7L4FGKP.js → chunk-AMCGMLPS.js} +2 -2
- package/dist/chunk-AYEOGJQR.js +169 -0
- package/dist/chunk-AYEOGJQR.js.map +1 -0
- package/dist/{chunk-E3MYVYEZ.js → chunk-KLEXJFJ6.js} +1 -1
- package/dist/{chunk-KMKQDGI6.js → chunk-YQRDKWSH.js} +31 -2
- package/dist/chunk-YQRDKWSH.js.map +1 -0
- package/dist/{chunk-AHA7LGGA.js → chunk-ZN32JGFP.js} +1920 -254
- package/dist/index.js +4 -4
- package/dist/roles/static-analysis/config.d.ts +3 -3
- package/dist/roles/static-analysis/config.js +1 -1
- package/dist/roles/test/setup/jest-parity.js +2 -2
- package/dist/roles/test/setup/workspace-entry.js +2 -2
- package/dist/{validate-STUK2AGG.js → validate-AWWMBSH5.js} +2 -2
- package/docs/static-analysis-study.md +416 -2
- package/docs/typescript-traces.md +157 -4
- package/package.json +20 -2
- package/dist/chunk-GJGZRXM3.js +0 -92
- package/dist/chunk-GJGZRXM3.js.map +0 -1
- package/dist/chunk-KMKQDGI6.js.map +0 -1
- /package/dist/{chunk-Z7L4FGKP.js.map → chunk-AMCGMLPS.js.map} +0 -0
|
@@ -235,10 +235,10 @@ $ sentinel --report --json 2>/dev/null
|
|
|
235
235
|
"format",
|
|
236
236
|
"typescript",
|
|
237
237
|
"build",
|
|
238
|
-
"test"
|
|
238
|
+
"test",
|
|
239
|
+
"static-analysis"
|
|
239
240
|
],
|
|
240
241
|
"skipped": [
|
|
241
|
-
"static-analysis",
|
|
242
242
|
"runtime-analysis",
|
|
243
243
|
"arch"
|
|
244
244
|
],
|
|
@@ -309,6 +309,16 @@ $ sentinel --report --json 2>/dev/null
|
|
|
309
309
|
"preset": null,
|
|
310
310
|
"conformant": false
|
|
311
311
|
},
|
|
312
|
+
{
|
|
313
|
+
"project": "svelte-app",
|
|
314
|
+
"target": "static-analysis",
|
|
315
|
+
"flavour": "svelte",
|
|
316
|
+
"ok": true,
|
|
317
|
+
"status": "ok",
|
|
318
|
+
"adopted": false,
|
|
319
|
+
"preset": null,
|
|
320
|
+
"conformant": false
|
|
321
|
+
},
|
|
312
322
|
{
|
|
313
323
|
"project": "bff-admin",
|
|
314
324
|
"target": "lint",
|
|
@@ -375,6 +385,16 @@ $ sentinel --report --json 2>/dev/null
|
|
|
375
385
|
"preset": null,
|
|
376
386
|
"conformant": false
|
|
377
387
|
},
|
|
388
|
+
{
|
|
389
|
+
"project": "bff-admin",
|
|
390
|
+
"target": "static-analysis",
|
|
391
|
+
"flavour": "nest",
|
|
392
|
+
"ok": true,
|
|
393
|
+
"status": "ok",
|
|
394
|
+
"adopted": false,
|
|
395
|
+
"preset": null,
|
|
396
|
+
"conformant": false
|
|
397
|
+
},
|
|
378
398
|
{
|
|
379
399
|
"project": "react-app",
|
|
380
400
|
"target": "lint",
|
|
@@ -441,6 +461,16 @@ $ sentinel --report --json 2>/dev/null
|
|
|
441
461
|
"preset": null,
|
|
442
462
|
"conformant": false
|
|
443
463
|
},
|
|
464
|
+
{
|
|
465
|
+
"project": "react-app",
|
|
466
|
+
"target": "static-analysis",
|
|
467
|
+
"flavour": "react",
|
|
468
|
+
"ok": true,
|
|
469
|
+
"status": "ok",
|
|
470
|
+
"adopted": false,
|
|
471
|
+
"preset": null,
|
|
472
|
+
"conformant": false
|
|
473
|
+
},
|
|
444
474
|
{
|
|
445
475
|
"project": "utils",
|
|
446
476
|
"target": "lint",
|
|
@@ -494,6 +524,16 @@ $ sentinel --report --json 2>/dev/null
|
|
|
494
524
|
"adopted": false,
|
|
495
525
|
"preset": null,
|
|
496
526
|
"conformant": false
|
|
527
|
+
},
|
|
528
|
+
{
|
|
529
|
+
"project": "utils",
|
|
530
|
+
"target": "static-analysis",
|
|
531
|
+
"flavour": "node",
|
|
532
|
+
"ok": true,
|
|
533
|
+
"status": "ok",
|
|
534
|
+
"adopted": false,
|
|
535
|
+
"preset": null,
|
|
536
|
+
"conformant": false
|
|
497
537
|
}
|
|
498
538
|
]
|
|
499
539
|
}
|
|
@@ -525,10 +565,10 @@ $ sentinel --report --all --json 2>/dev/null
|
|
|
525
565
|
"format",
|
|
526
566
|
"typescript",
|
|
527
567
|
"build",
|
|
528
|
-
"test"
|
|
568
|
+
"test",
|
|
569
|
+
"static-analysis"
|
|
529
570
|
],
|
|
530
571
|
"skipped": [
|
|
531
|
-
"static-analysis",
|
|
532
572
|
"runtime-analysis",
|
|
533
573
|
"arch"
|
|
534
574
|
],
|
|
@@ -599,6 +639,16 @@ $ sentinel --report --all --json 2>/dev/null
|
|
|
599
639
|
"preset": null,
|
|
600
640
|
"conformant": false
|
|
601
641
|
},
|
|
642
|
+
{
|
|
643
|
+
"project": "svelte-app",
|
|
644
|
+
"target": "static-analysis",
|
|
645
|
+
"flavour": "svelte",
|
|
646
|
+
"ok": true,
|
|
647
|
+
"status": "ok",
|
|
648
|
+
"adopted": false,
|
|
649
|
+
"preset": null,
|
|
650
|
+
"conformant": false
|
|
651
|
+
},
|
|
602
652
|
{
|
|
603
653
|
"project": "bff-admin",
|
|
604
654
|
"target": "lint",
|
|
@@ -665,6 +715,16 @@ $ sentinel --report --all --json 2>/dev/null
|
|
|
665
715
|
"preset": null,
|
|
666
716
|
"conformant": false
|
|
667
717
|
},
|
|
718
|
+
{
|
|
719
|
+
"project": "bff-admin",
|
|
720
|
+
"target": "static-analysis",
|
|
721
|
+
"flavour": "nest",
|
|
722
|
+
"ok": true,
|
|
723
|
+
"status": "ok",
|
|
724
|
+
"adopted": false,
|
|
725
|
+
"preset": null,
|
|
726
|
+
"conformant": false
|
|
727
|
+
},
|
|
668
728
|
{
|
|
669
729
|
"project": "react-app",
|
|
670
730
|
"target": "lint",
|
|
@@ -731,6 +791,16 @@ $ sentinel --report --all --json 2>/dev/null
|
|
|
731
791
|
"preset": null,
|
|
732
792
|
"conformant": false
|
|
733
793
|
},
|
|
794
|
+
{
|
|
795
|
+
"project": "react-app",
|
|
796
|
+
"target": "static-analysis",
|
|
797
|
+
"flavour": "react",
|
|
798
|
+
"ok": true,
|
|
799
|
+
"status": "ok",
|
|
800
|
+
"adopted": false,
|
|
801
|
+
"preset": null,
|
|
802
|
+
"conformant": false
|
|
803
|
+
},
|
|
734
804
|
{
|
|
735
805
|
"project": "utils",
|
|
736
806
|
"target": "lint",
|
|
@@ -784,6 +854,16 @@ $ sentinel --report --all --json 2>/dev/null
|
|
|
784
854
|
"adopted": false,
|
|
785
855
|
"preset": null,
|
|
786
856
|
"conformant": false
|
|
857
|
+
},
|
|
858
|
+
{
|
|
859
|
+
"project": "utils",
|
|
860
|
+
"target": "static-analysis",
|
|
861
|
+
"flavour": "node",
|
|
862
|
+
"ok": true,
|
|
863
|
+
"status": "ok",
|
|
864
|
+
"adopted": false,
|
|
865
|
+
"preset": null,
|
|
866
|
+
"conformant": false
|
|
787
867
|
}
|
|
788
868
|
]
|
|
789
869
|
}
|
|
@@ -1076,6 +1156,78 @@ $ sentinel --init --dry-run
|
|
|
1076
1156
|
build: no Vite config in this module, so there is nothing to point at sentinel. This role moves an existing config's imports; it does not introduce a bundler. If this module builds another way (38 services here build through `@nx/webpack`), the build role does not apply to it.
|
|
1077
1157
|
test: this module has no test and no jest leftovers, so there is nothing to do here.
|
|
1078
1158
|
dry run: no files written
|
|
1159
|
+
added the three `static-analysis` scripts and static-analysis.config.mjs; declare this module's entry points and journeys there, everything else comes from the role
|
|
1160
|
+
|
|
1161
|
+
create static-analysis.config.mjs
|
|
1162
|
+
+ import { defineStaticAnalysis } from '@hublo/sentinel/static-analysis'
|
|
1163
|
+
+
|
|
1164
|
+
+ export default defineStaticAnalysis({
|
|
1165
|
+
+ root: import.meta.dirname,
|
|
1166
|
+
+
|
|
1167
|
+
+ // Files that RUN although nothing imports them: they are where reachability starts. Added to
|
|
1168
|
+
+ // the patterns sentinel ships, never replacing them.
|
|
1169
|
+
+ entry: [],
|
|
1170
|
+
+
|
|
1171
|
+
+ // Named product journeys, as { 'the name product uses': ['src/some/path/**'] }. Paths are
|
|
1172
|
+
+ // relative to this module, because one run measures one module.
|
|
1173
|
+
+ journeys: {},
|
|
1174
|
+
+
|
|
1175
|
+
+ // ---------------------------------------------------------------------------------------------
|
|
1176
|
+
+ // What this module can move, with the role's value today. A setting you leave out keeps that
|
|
1177
|
+
+ // value; a setting you write REPLACES it, including inside a list. Anything you set is printed
|
|
1178
|
+
+ // under "measured with" in the report and marked `(module)`, because two modules measured with
|
|
1179
|
+
+ // different settings can no longer be compared.
|
|
1180
|
+
+ //
|
|
1181
|
+
+ // ignore 19 patterns
|
|
1182
|
+
+ // languages javascript, typescript, jsx, tsx, css, scss
|
|
1183
|
+
+ // duplication.minTokens 50
|
|
1184
|
+
+ // duplication.minLines 5
|
|
1185
|
+
+ // duplication.kinds exact
|
|
1186
|
+
+ // duplication.tests 3 patterns
|
|
1187
|
+
+ // deadCode.minConfidence 60
|
|
1188
|
+
+ // deadCode.categories unused-file, unused-export, unused-symbol, unused-import
|
|
1189
|
+
+ // graph.resolution 1
|
|
1190
|
+
+ //
|
|
1191
|
+
+ // A list shown as a count is not copyable, and replacing one means writing back what you keep,
|
|
1192
|
+
+ // so read the values in full with:
|
|
1193
|
+
+ //
|
|
1194
|
+
+ // pnpm exec sentinel --inspect --static-analysis --json
|
|
1195
|
+
+ //
|
|
1196
|
+
+ // tools: sentinel's own passthrough to the tool underneath; a module never names the vendor
|
|
1197
|
+
+ //
|
|
1198
|
+
+ // overrides: {
|
|
1199
|
+
+ // duplication: { minTokens: 100 },
|
|
1200
|
+
+ // },
|
|
1201
|
+
+ })
|
|
1202
|
+
|
|
1203
|
+
update package.json
|
|
1204
|
+
- { "name": "bff-admin", "dependencies": { "@nestjs/core": "^10" } }
|
|
1205
|
+
+ {
|
|
1206
|
+
+ "name": "bff-admin",
|
|
1207
|
+
+ "dependencies": {
|
|
1208
|
+
+ "@nestjs/core": "^10"
|
|
1209
|
+
+ },
|
|
1210
|
+
+ "scripts": {
|
|
1211
|
+
+ "static-analysis": "sentinel --run --static-analysis",
|
|
1212
|
+
+ "static-analysis:json": "sentinel --run --static-analysis --output json",
|
|
1213
|
+
+ "static-analysis:html": "sentinel --run --static-analysis --output html"
|
|
1214
|
+
+ },
|
|
1215
|
+
+ "devDependencies": {
|
|
1216
|
+
+ "@hublo/sentinel": "<version>"
|
|
1217
|
+
+ },
|
|
1218
|
+
+ "nx": {
|
|
1219
|
+
+ "targets": {
|
|
1220
|
+
+ "static-analysis": {
|
|
1221
|
+
+ "cache": true,
|
|
1222
|
+
+ "inputs": [
|
|
1223
|
+
+ "default",
|
|
1224
|
+
+ "{projectRoot}/static-analysis.config.mjs"
|
|
1225
|
+
+ ]
|
|
1226
|
+
+ }
|
|
1227
|
+
+ }
|
|
1228
|
+
+ }
|
|
1229
|
+
+ }
|
|
1230
|
+
dry run: no files written
|
|
1079
1231
|
materialized the nest format preset into .oxfmtrc.json (@hublo/sentinel@<version>). oxfmt has no `extends`, so the values live here; `sentinel --status --format` reports modules left behind by a preset change
|
|
1080
1232
|
|
|
1081
1233
|
create .oxfmtrc.json
|
|
@@ -1150,6 +1302,7 @@ $ sentinel --init --dry-run
|
|
|
1150
1302
|
+ }
|
|
1151
1303
|
+ }
|
|
1152
1304
|
+ }
|
|
1305
|
+
dry run: would ignore static-analysis-report/ in the root .gitignore
|
|
1153
1306
|
# exit: 0
|
|
1154
1307
|
```
|
|
1155
1308
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hublo/sentinel",
|
|
3
|
-
"version": "1.5.0-alpha.
|
|
3
|
+
"version": "1.5.0-alpha.20",
|
|
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",
|
|
@@ -120,6 +120,14 @@
|
|
|
120
120
|
"eslint-plugin-react-refresh": "0.4.19",
|
|
121
121
|
"eslint-plugin-storybook": "10.3.5",
|
|
122
122
|
"eslint-plugin-styled-components-a11y": "2.2.0",
|
|
123
|
+
"graphology": "0.26.0",
|
|
124
|
+
"graphology-communities-louvain": "2.0.2",
|
|
125
|
+
"graphology-components": "1.5.4",
|
|
126
|
+
"graphology-layout": "0.6.1",
|
|
127
|
+
"graphology-layout-forceatlas2": "0.10.1",
|
|
128
|
+
"graphology-metrics": "2.4.2",
|
|
129
|
+
"graphology-svg": "0.1.3",
|
|
130
|
+
"graphology-types": "0.24.8",
|
|
123
131
|
"html-validate": "^11.16.0",
|
|
124
132
|
"jscpd": "5.4.0",
|
|
125
133
|
"jsdom": "^24.1.3",
|
|
@@ -160,6 +168,13 @@
|
|
|
160
168
|
},
|
|
161
169
|
"format:fix": {
|
|
162
170
|
"cache": false
|
|
171
|
+
},
|
|
172
|
+
"static-analysis": {
|
|
173
|
+
"cache": true,
|
|
174
|
+
"inputs": [
|
|
175
|
+
"default",
|
|
176
|
+
"{projectRoot}/static-analysis.config.mjs"
|
|
177
|
+
]
|
|
163
178
|
}
|
|
164
179
|
}
|
|
165
180
|
},
|
|
@@ -190,6 +205,9 @@
|
|
|
190
205
|
"docs:traces": "pnpm build && bash scripts/gen-traces.sh",
|
|
191
206
|
"docs:traces:check": "pnpm build && bash scripts/gen-traces.sh --check",
|
|
192
207
|
"format:fix": "node dist/bin/sentinel.js --run --format --fix",
|
|
193
|
-
"bench": "node scripts/bench.mjs"
|
|
208
|
+
"bench": "node scripts/bench.mjs",
|
|
209
|
+
"static-analysis": "sentinel --run --static-analysis",
|
|
210
|
+
"static-analysis:json": "sentinel --run --static-analysis --output json",
|
|
211
|
+
"static-analysis:html": "sentinel --run --static-analysis --output html"
|
|
194
212
|
}
|
|
195
213
|
}
|
package/dist/chunk-GJGZRXM3.js
DELETED
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
// src/shared/deep-merge.ts
|
|
2
|
-
function isPlainObject(value) {
|
|
3
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
// src/roles/static-analysis/presets/shared.json
|
|
7
|
-
var shared_default = {
|
|
8
|
-
entry: ["**/*.integration-spec.ts", "**/*.steps.ts"],
|
|
9
|
-
ignore: [
|
|
10
|
-
"**/node_modules/**",
|
|
11
|
-
"**/dist/**",
|
|
12
|
-
"**/build/**",
|
|
13
|
-
"**/coverage/**",
|
|
14
|
-
"**/.next/**",
|
|
15
|
-
"**/static-analysis-report/**",
|
|
16
|
-
"**/__mocks__/**",
|
|
17
|
-
"**/mocks/**",
|
|
18
|
-
"**/_mock_/**",
|
|
19
|
-
"**/**Mock**/**",
|
|
20
|
-
"**/__fixtures__/**",
|
|
21
|
-
"**/fixtures/**",
|
|
22
|
-
"**/public/**",
|
|
23
|
-
"**/.husky/**",
|
|
24
|
-
"**/.vscode/**",
|
|
25
|
-
"**/.idea/**",
|
|
26
|
-
"**/.github/**",
|
|
27
|
-
"**/*.d.ts",
|
|
28
|
-
"**/*.gen.ts",
|
|
29
|
-
"**/*.generated.ts"
|
|
30
|
-
],
|
|
31
|
-
languages: ["javascript", "typescript", "jsx", "tsx", "css", "scss"],
|
|
32
|
-
journeys: {},
|
|
33
|
-
duplication: { minTokens: 50, minLines: 5, kinds: ["exact"] },
|
|
34
|
-
deadCode: {
|
|
35
|
-
minConfidence: 60,
|
|
36
|
-
categories: ["unused-file", "unused-export", "unused-symbol", "unused-import"]
|
|
37
|
-
},
|
|
38
|
-
thresholds: { duplicatedLinesPercent: 5, deadCodePercent: 5 },
|
|
39
|
-
tools: { jscpd: {} }
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
// src/roles/static-analysis/preset-data.ts
|
|
43
|
-
var REPORT_DIR = "static-analysis-report";
|
|
44
|
-
var defaults = shared_default;
|
|
45
|
-
|
|
46
|
-
// src/roles/static-analysis/configs/define.ts
|
|
47
|
-
function mergeInto(base, patch) {
|
|
48
|
-
if (!isPlainObject(patch)) return base;
|
|
49
|
-
if (!isPlainObject(base)) return patch;
|
|
50
|
-
const out = { ...base };
|
|
51
|
-
for (const [key, value] of Object.entries(patch)) {
|
|
52
|
-
out[key] = isPlainObject(value) ? mergeInto(out[key], value) : value;
|
|
53
|
-
}
|
|
54
|
-
return out;
|
|
55
|
-
}
|
|
56
|
-
function leafPaths(patch, prefix = "") {
|
|
57
|
-
if (!isPlainObject(patch)) return prefix ? [prefix] : [];
|
|
58
|
-
return Object.entries(patch).flatMap(
|
|
59
|
-
([key, value]) => leafPaths(value, prefix ? `${prefix}.${key}` : key)
|
|
60
|
-
);
|
|
61
|
-
}
|
|
62
|
-
function unique(values) {
|
|
63
|
-
return [...new Set(values)];
|
|
64
|
-
}
|
|
65
|
-
function withoutVendorKeys(overrides) {
|
|
66
|
-
if (!isPlainObject(overrides) || !("tools" in overrides)) return overrides;
|
|
67
|
-
const { tools: _vendor, ...rest } = overrides;
|
|
68
|
-
process.stderr.write(
|
|
69
|
-
"sentinel static-analysis: ignoring `overrides.tools` from this module. The tool underneath the role is not part of a module's surface; if a setting is missing, it needs a key of its own.\n"
|
|
70
|
-
);
|
|
71
|
-
return rest;
|
|
72
|
-
}
|
|
73
|
-
function defineStaticAnalysis(input = {}) {
|
|
74
|
-
const declared = {
|
|
75
|
-
...defaults,
|
|
76
|
-
entry: unique([...defaults.entry, ...input.entry ?? []]),
|
|
77
|
-
journeys: { ...defaults.journeys, ...input.journeys ?? {} }
|
|
78
|
-
};
|
|
79
|
-
const overrides = withoutVendorKeys(input.overrides);
|
|
80
|
-
return {
|
|
81
|
-
...mergeInto(declared, overrides),
|
|
82
|
-
root: input.root ?? null,
|
|
83
|
-
moved: leafPaths(overrides).sort()
|
|
84
|
-
};
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
export {
|
|
88
|
-
isPlainObject,
|
|
89
|
-
REPORT_DIR,
|
|
90
|
-
defineStaticAnalysis
|
|
91
|
-
};
|
|
92
|
-
//# sourceMappingURL=chunk-GJGZRXM3.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/shared/deep-merge.ts","../src/roles/static-analysis/presets/shared.json","../src/roles/static-analysis/preset-data.ts","../src/roles/static-analysis/configs/define.ts"],"sourcesContent":["/**\n * Is this a plain JSON object?\n *\n * The file is named for a `deepMerge` that no longer exists. It was written when the engine\n * merged JSON in memory and wrote the result; that approach was replaced by editing the\n * document IN PLACE with jsonc-parser, so a project's comments, key order and formatting\n * survive a `merge-json` op. Nothing has called `deepMerge` since, except its own recursion,\n * so it went.\n *\n * What remains is the predicate the in-place merge still needs, to decide whether a value is\n * a branch to walk into or a leaf to set.\n */\n\n/** True for a mergeable plain object (not null, not an array). */\nexport function isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n","{\n \"entry\": [\"**/*.integration-spec.ts\", \"**/*.steps.ts\"],\n \"ignore\": [\n \"**/node_modules/**\",\n \"**/dist/**\",\n \"**/build/**\",\n \"**/coverage/**\",\n \"**/.next/**\",\n \"**/static-analysis-report/**\",\n \"**/__mocks__/**\",\n \"**/mocks/**\",\n \"**/_mock_/**\",\n \"**/**Mock**/**\",\n \"**/__fixtures__/**\",\n \"**/fixtures/**\",\n \"**/public/**\",\n \"**/.husky/**\",\n \"**/.vscode/**\",\n \"**/.idea/**\",\n \"**/.github/**\",\n \"**/*.d.ts\",\n \"**/*.gen.ts\",\n \"**/*.generated.ts\"\n ],\n \"languages\": [\"javascript\", \"typescript\", \"jsx\", \"tsx\", \"css\", \"scss\"],\n \"journeys\": {},\n \"duplication\": { \"minTokens\": 50, \"minLines\": 5, \"kinds\": [\"exact\"] },\n \"deadCode\": {\n \"minConfidence\": 60,\n \"categories\": [\"unused-file\", \"unused-export\", \"unused-symbol\", \"unused-import\"]\n },\n \"thresholds\": { \"duplicatedLinesPercent\": 5, \"deadCodePercent\": 5 },\n \"tools\": { \"jscpd\": {} }\n}\n","/**\n * The static analysis preset: the role's settings, as data.\n *\n * One shared layer and no per-stack preset yet, because nothing measured so far varies by stack:\n * duplication, dead code and complexity are the same questions in Nest and in React. The split\n * that is already foreseen is `LCOM4`, which is meaningless on a functional front end and real on\n * nest's 16 568 classes; when it arrives it becomes `nest.json` beside this layer, which is why\n * the values live here as JSON rather than inline in the code.\n *\n * ## Why these values are the role's and not the module's\n *\n * A team declares FACTS about its own domain (which journeys exist, which files are entry points);\n * it does not declare the values a number is judged by. Two files with the same measurement\n * getting different verdicts in two modules would make every figure incomparable, and the first\n * thing this role has to be able to say is that a number means the same thing everywhere.\n *\n * That is a default, not a wall: a module CAN move any of them (see `configs/define.ts`), and when\n * it does the report prints `(module)` next to the value. The seam exists from the first commit\n * because retrofitting one later means rewriting every module's file.\n *\n * Both thresholds are a STARTING POINT and will be calibrated from the distribution the first full\n * run produces, the same commitment a preset rule carries in `CONTRIBUTING.md`. For scale,\n * sentinel's own package measures 1.4% duplicated lines and 1.3% dead code.\n *\n * ## Where each value comes from, so nobody has to guess later\n *\n * - `entry` is the single most consequential line in the file. Measured on\n * `apps/nest/microservices/network` with jscpd 5.4.0: with nothing declared the dead-code pass\n * reports **33.4%** of lines dead (79 657 lines, 232 files, 13 entry points); with these two\n * patterns declared it reports **1.0%** (2 462 lines, 207 entry points). The cause is a naming\n * convention and not a bug: our integration tests are `foo.integration-spec.ts` with a HYPHEN,\n * so they do not end in `.spec.ts` and the tool's own test-file detection never sees them, and\n * the 115 `.steps.ts` BDD definitions are tests by no convention it knows. A module ADDS to\n * this list rather than replacing it, precisely so no team can lose these two by declaring a\n * third.\n *\n * - `languages` is this role's name for what the tool calls `format`, a word that already names\n * another sentinel role and means something else there. Stating it has a measured cost either\n * way: on `apps/front/console`, naming `typescript` alone scans 300 of 523 files because `.tsx`\n * is a SEPARATE language to the tool, naming all four scans 522 (the one left out being\n * `styles.css`), and naming nothing scans every one of its 224 languages, json and markdown\n * included. Stylesheets are in deliberately: measured on `libs/front` they duplicate at 9.64%\n * against 3.50% for the code, a signal that only survives because the report is broken down per\n * language.\n *\n * - `duplication.minTokens` and `minLines` are the detection window, and the window decides what\n * is INVISIBLE: a fragment shorter than it is not reported at all, so a reader comparing two\n * runs has to know both values. They are the tool's own defaults, written out rather than\n * inherited for that reason.\n *\n * - `duplication.kinds` is `exact` alone, and that is a correctness constraint rather than\n * caution. Measured: with a second kind enabled, `duplicatedLines` adds up overlapping clones.\n * Two 9-line files reported 13 duplicated lines out of 18, 72%, because an `exact` clone of\n * lines 1-4 and a `similar` clone of lines 1-9 containing it were both counted; the real extent\n * is 9 of 18, 50%. The tool's own answer is to cap the display at 100%, which does not make a\n * threshold meaningful. Opening another kind therefore requires computing the share ourselves\n * from the UNION of the line ranges.\n *\n * - `deadCode.minConfidence` is the tool's own default of 60. It scores every finding and names\n * what lowered it (a file calling `eval`, an unrecognised decorator, a name appearing in a\n * string literal, a file in the scan that did not parse). Measured on sentinel: 60 yields 20\n * files, 10 exports and 22 symbols; 90 yields 18 files; 100 yields nothing.\n *\n * - `deadCode.categories` leaves out `unused-member` deliberately: without type information\n * `x.render()` could reach any `render` in the project, which the tool itself calls its least\n * certain rule.\n *\n * - `ignore` excludes generated, vendored and fixture code, which duplicates by nature and buries\n * the clones a developer can act on. It is load-bearing rather than tidy: measured on this\n * package, scanning the module root with no ignore list reports 18 186 files and 32 153 clones,\n * **31 969** of them touching `node_modules`; with the list, 408 files and zero.\n *\n * - `tools.jscpd` is passed to the tool verbatim, and it is **sentinel's own** escape hatch, not a\n * module's: `defineStaticAnalysis` excludes it from what a module can override. Everything above\n * uses OUR key names, translated on the way out, so that replacing the tool one day does not\n * oblige a single module to edit its config file, and a module only ever knows the ROLE. This\n * block is where that guarantee stops, which is why it stays on this side and why it is empty:\n * the first thing to put in it would be an option a single preset needs, not a workaround.\n */\nimport sharedData from './presets/shared.json'\n\n/**\n * Where the json and html reports are written, relative to the module.\n *\n * Named for the role rather than left on the tool's default `report/`, and added to `.gitignore`\n * by `--init`. Both halves matter: the format role once shipped a `lint/` directory that nothing\n * ignored, and every adopting module grew an untracked directory nobody could explain.\n */\nexport const REPORT_DIR = 'static-analysis-report'\n\nexport type StaticDefaults = {\n entry: string[]\n ignore: string[]\n languages: string[]\n journeys: Record<string, string[]>\n duplication: { minTokens: number; minLines: number; kinds: string[] }\n deadCode: { minConfidence: number; categories: string[] }\n thresholds: { duplicatedLinesPercent: number; deadCodePercent: number }\n tools: { jscpd: Record<string, unknown> }\n}\n\n/** The role's settings. Read-only here; `defineStaticAnalysis` resolves a module against them. */\nexport const defaults: StaticDefaults = sharedData as StaticDefaults\n","/**\n * `defineStaticAnalysis`: what a module imports to declare its own static analysis settings.\n *\n * ```js\n * // static-analysis.config.mjs\n * import { defineStaticAnalysis } from '@hublo/sentinel/static-analysis'\n *\n * export default defineStaticAnalysis({\n * root: import.meta.dirname,\n * entry: ['**\\/*.steps.ts'],\n * journeys: { planning: ['src/planning/**'] },\n * })\n * ```\n *\n * ## Why a function and not `{ ...baseConfig, ... }`\n *\n * A spread is SHALLOW, so a module writing `deadCode: { minConfidence: 80 }` would replace the\n * whole block and silently lose `categories`. And the flattening happens in the module's file,\n * before this package ever sees the object, so nothing here could tell \"set one key, keep the\n * rest\" from \"set one key, drop the rest\". A function receives the module's INTENT, the keys it\n * actually passed, and merges them here. Second reason: the defaults are never copied into a\n * module, so changing one reaches every module at the next release instead of leaving 108 stale\n * copies behind.\n *\n * Same shape as `reactTestConfig` in the test role, for the same reasons.\n *\n * ## Two semantics, one obvious and one deliberate\n *\n * `entry` and `journeys` are FACTS a team declares, and they ADD to what this role ships: no\n * module can lose `**\\/*.integration-spec.ts` by declaring a third pattern, which matters\n * because that one pattern is the difference between 33.4% and 1.0% of dead code on a real\n * service (see `defaults.ts`).\n *\n * `overrides` REPLACES, including those lists. It is the seam for the day a team has a real\n * reason, it is not documented anywhere a developer would meet it, and every path it sets is\n * recorded in `moved` so the report can print `(module)` next to the value. Without that trace\n * two modules stop being comparable and nobody knows. It reaches every setting EXCEPT the vendor\n * passthrough, for the reason stated on the field itself.\n */\nimport { isPlainObject } from '../../../shared/deep-merge.js'\nimport { defaults, type StaticDefaults } from '../preset-data.js'\n\ntype DeepPartial<T> = { [K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K] }\n\n/** What a module's config file passes. Everything is optional, including the file itself. */\nexport interface StaticAnalysisInput {\n /** The module's own directory, normally `import.meta.dirname`. */\n root?: string\n /** Files that run although nothing imports them. ADDED to the shipped patterns. */\n entry?: string[]\n /** Named journeys, as `{ name: [globs] }`. MERGED by name with any shipped ones. */\n journeys?: Record<string, string[]>\n /**\n * The seam. Replaces whatever it names, and every path it sets lands in `moved`.\n *\n * ⛔ `tools` is excluded, and the reason is the whole point of translating the vocabulary: a\n * module knows the ROLE, not the tool underneath it. A key named after the vendor would mean\n * nothing to the person reading their own config file, and showing it would invite them to learn\n * a name we are working to keep out of their world. The passthrough still exists, on sentinel's\n * side, for a preset that needs a vendor option; a module that genuinely needs one gets a key in\n * OUR vocabulary, which is the right friction because it goes through a review instead of\n * spreading silently.\n */\n overrides?: DeepPartial<Omit<StaticDefaults, 'tools'>>\n}\n\nexport interface ResolvedStaticConfig extends StaticDefaults {\n /** The module's directory, or null when its config file did not say. */\n root: string | null\n /**\n * Dotted paths the module set through `overrides`, so the report can mark them. Sorted, so\n * two runs on the same config produce the same report.\n */\n moved: string[]\n}\n\n/**\n * Merge `patch` into `base`, recursively, patch winning.\n *\n * Not the `deepMerge` this repo once had: that one existed for WRITING a module's json and was\n * replaced by an in-place jsonc edit, which is why only `isPlainObject` survived. This is the\n * read side, which has no file to preserve and only has to resolve two objects into one.\n *\n * Arrays are replaced, never concatenated. Additive behaviour is a decision about a specific\n * key, which `defineStaticAnalysis` makes explicitly for `entry`, rather than a property of the\n * merge: a merge that silently concatenates cannot express \"replace this list\", and a team\n * reaching for `overrides` is asking for exactly that.\n */\nfunction mergeInto<T>(base: T, patch: unknown): T {\n if (!isPlainObject(patch)) return base\n if (!isPlainObject(base)) return patch as T\n const out: Record<string, unknown> = { ...base }\n for (const [key, value] of Object.entries(patch)) {\n out[key] = isPlainObject(value) ? mergeInto(out[key], value) : value\n }\n return out as T\n}\n\n/** Every leaf path in `patch`, dotted, so a reader can be told what the module decided. */\nfunction leafPaths(patch: unknown, prefix = ''): string[] {\n if (!isPlainObject(patch)) return prefix ? [prefix] : []\n return Object.entries(patch).flatMap(([key, value]) =>\n leafPaths(value, prefix ? `${prefix}.${key}` : key),\n )\n}\n\n/** The same list with duplicates dropped, keeping the first occurrence's order. */\nfunction unique(values: string[]): string[] {\n return [...new Set(values)]\n}\n\n/**\n * Drop the vendor passthrough from what a module sent.\n *\n * ⚠️ Enforced here and not by the type, because the type cannot: a module's config file is `.mjs`,\n * so nothing type-checks it, and `Omit` on the input would be documentation that compiles away. A\n * `tools` key written there is dropped and NAMED on stderr rather than ignored, so a developer who\n * found the key somewhere learns why it did nothing instead of concluding the role is broken.\n */\nfunction withoutVendorKeys(overrides: unknown): unknown {\n if (!isPlainObject(overrides) || !('tools' in overrides)) return overrides\n const { tools: _vendor, ...rest } = overrides\n process.stderr.write(\n 'sentinel static-analysis: ignoring `overrides.tools` from this module. The tool underneath ' +\n \"the role is not part of a module's surface; if a setting is missing, it needs a key of \" +\n 'its own.\\n',\n )\n return rest\n}\n\n/** Resolve a module's declaration against this role's defaults. */\nexport function defineStaticAnalysis(input: StaticAnalysisInput = {}): ResolvedStaticConfig {\n const declared: StaticDefaults = {\n ...defaults,\n entry: unique([...defaults.entry, ...(input.entry ?? [])]),\n journeys: { ...defaults.journeys, ...(input.journeys ?? {}) },\n }\n const overrides = withoutVendorKeys(input.overrides)\n\n return {\n ...mergeInto(declared, overrides),\n root: input.root ?? null,\n moved: leafPaths(overrides).sort(),\n }\n}\n"],"mappings":";AAcO,SAAS,cAAc,OAAkD;AAC9E,SAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK;AAC5E;;;AChBA;AAAA,EACE,OAAS,CAAC,4BAA4B,eAAe;AAAA,EACrD,QAAU;AAAA,IACR;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAAA,EACA,WAAa,CAAC,cAAc,cAAc,OAAO,OAAO,OAAO,MAAM;AAAA,EACrE,UAAY,CAAC;AAAA,EACb,aAAe,EAAE,WAAa,IAAI,UAAY,GAAG,OAAS,CAAC,OAAO,EAAE;AAAA,EACpE,UAAY;AAAA,IACV,eAAiB;AAAA,IACjB,YAAc,CAAC,eAAe,iBAAiB,iBAAiB,eAAe;AAAA,EACjF;AAAA,EACA,YAAc,EAAE,wBAA0B,GAAG,iBAAmB,EAAE;AAAA,EAClE,OAAS,EAAE,OAAS,CAAC,EAAE;AACzB;;;ACuDO,IAAM,aAAa;AAcnB,IAAM,WAA2B;;;ACdxC,SAAS,UAAa,MAAS,OAAmB;AAChD,MAAI,CAAC,cAAc,KAAK,EAAG,QAAO;AAClC,MAAI,CAAC,cAAc,IAAI,EAAG,QAAO;AACjC,QAAM,MAA+B,EAAE,GAAG,KAAK;AAC/C,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,KAAK,GAAG;AAChD,QAAI,GAAG,IAAI,cAAc,KAAK,IAAI,UAAU,IAAI,GAAG,GAAG,KAAK,IAAI;AAAA,EACjE;AACA,SAAO;AACT;AAGA,SAAS,UAAU,OAAgB,SAAS,IAAc;AACxD,MAAI,CAAC,cAAc,KAAK,EAAG,QAAO,SAAS,CAAC,MAAM,IAAI,CAAC;AACvD,SAAO,OAAO,QAAQ,KAAK,EAAE;AAAA,IAAQ,CAAC,CAAC,KAAK,KAAK,MAC/C,UAAU,OAAO,SAAS,GAAG,MAAM,IAAI,GAAG,KAAK,GAAG;AAAA,EACpD;AACF;AAGA,SAAS,OAAO,QAA4B;AAC1C,SAAO,CAAC,GAAG,IAAI,IAAI,MAAM,CAAC;AAC5B;AAUA,SAAS,kBAAkB,WAA6B;AACtD,MAAI,CAAC,cAAc,SAAS,KAAK,EAAE,WAAW,WAAY,QAAO;AACjE,QAAM,EAAE,OAAO,SAAS,GAAG,KAAK,IAAI;AACpC,UAAQ,OAAO;AAAA,IACb;AAAA,EAGF;AACA,SAAO;AACT;AAGO,SAAS,qBAAqB,QAA6B,CAAC,GAAyB;AAC1F,QAAM,WAA2B;AAAA,IAC/B,GAAG;AAAA,IACH,OAAO,OAAO,CAAC,GAAG,SAAS,OAAO,GAAI,MAAM,SAAS,CAAC,CAAE,CAAC;AAAA,IACzD,UAAU,EAAE,GAAG,SAAS,UAAU,GAAI,MAAM,YAAY,CAAC,EAAG;AAAA,EAC9D;AACA,QAAM,YAAY,kBAAkB,MAAM,SAAS;AAEnD,SAAO;AAAA,IACL,GAAG,UAAU,UAAU,SAAS;AAAA,IAChC,MAAM,MAAM,QAAQ;AAAA,IACpB,OAAO,UAAU,SAAS,EAAE,KAAK;AAAA,EACnC;AACF;","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/shared/package-json.ts","../src/core/settings.ts","../src/core/workspace-prep.ts"],"sourcesContent":["/**\n * package.json helpers, shared by the CLI and the engine.\n */\nimport { existsSync, readFileSync } from 'node:fs'\nimport { basename, dirname, join } from 'node:path'\nimport { fileURLToPath } from 'node:url'\n\n/**\n * sentinel's OWN version, for `--version`. We must NOT use\n * `process.env.npm_package_version`, that is the version of whatever script\n * invoked us (an app's package.json, or nothing). Instead walk up from this\n * module to the nearest package.json, which is always sentinel's own, whether\n * running from source or the bundled dist.\n */\n/**\n * sentinel's OWN name + version, read by walking up from this module to the nearest\n * package.json. Used both for `--version` and to pin the exact dependency `--init`\n * writes into an adopted module, so a module always references the version that adopted it.\n */\nexport function readOwnPackage(): { name: string; version: string; bin?: string } {\n let dir = dirname(fileURLToPath(import.meta.url))\n for (;;) {\n const pkgPath = join(dir, 'package.json')\n if (existsSync(pkgPath)) {\n try {\n const pkg = JSON.parse(readFileSync(pkgPath, 'utf8')) as {\n name?: string\n version?: string\n bin?: string | Record<string, string>\n }\n if (typeof pkg.version === 'string') {\n // The bin PATH, not the command name: a package that adopts itself has to invoke\n // its own build output, because the `.bin` shim does not exist yet (see below).\n const bin = typeof pkg.bin === 'string' ? pkg.bin : Object.values(pkg.bin ?? {})[0]\n return { name: pkg.name ?? '@hublo/sentinel', version: pkg.version, bin }\n }\n } catch {\n // Malformed package.json; keep walking up.\n }\n }\n const parent = dirname(dir)\n if (parent === dir) return { name: '@hublo/sentinel', version: '0.0.0' } // filesystem root\n dir = parent\n }\n}\n\nexport function readOwnVersion(): string {\n return readOwnPackage().version\n}\n\n/**\n * Read a module's package.json for framework detection; tolerant of a missing or\n * malformed file (returns `{}`, so detection falls through to the `node` default).\n */\nexport function readProjectPackageJson(dir: string): {\n /** The package name, which is also what nx calls the project when there is no `project.json`. */\n name?: string\n dependencies?: Record<string, string>\n devDependencies?: Record<string, string>\n /**\n * How the module's `.js`/`.ts` files are loaded. It decides whether a Vite config can import\n * an ESM-only package at all, which is why the build role reads it before rewriting one.\n */\n type?: string\n /**\n * What nx alone needs per target, which every role writes here and none could read back.\n *\n * Typed loosely on purpose: this reader is shared by every role and each one knows the shape of\n * its own target. Declared at all because a role that WRITES a block and then has to cast to\n * read it is one refactor away from reading a key it never wrote.\n */\n nx?: { targets?: Record<string, Record<string, unknown>> }\n} {\n const path = join(dir, 'package.json')\n if (!existsSync(path)) return {}\n try {\n return JSON.parse(readFileSync(path, 'utf8')) as Record<string, never>\n } catch {\n process.stderr.write(`sentinel: could not parse ${path}; ignoring for detection.\\n`)\n return {}\n }\n}\n\n/**\n * The nx project name for a module, read from its `project.json`, or undefined if\n * there is none/unreadable. Used when `--init` has to scaffold a `package.json`\n * for a module that only has a `project.json` (common for nx apps/services): the\n * scaffolded file borrows this name so it is a valid, uniquely-named workspace\n * package. Reusable by any tool that scaffolds, not just TypeScript.\n */\nexport function readNxProjectName(dir: string): string | undefined {\n const path = join(dir, 'project.json')\n if (!existsSync(path)) return undefined\n try {\n const parsed = JSON.parse(readFileSync(path, 'utf8')) as { name?: unknown }\n return typeof parsed.name === 'string' ? parsed.name : undefined\n } catch {\n return undefined\n }\n}\n\n/**\n * What nx calls this module, resolved the way nx itself resolves it.\n *\n * The precedence is `project.json`, then `package.json`, then the directory, and only the\n * first and last of those were being consulted. A module with no `project.json` therefore\n * answered with its DIRECTORY while nx answered with its package name, and the two disagree\n * in the real repo: `apps/front/hr-management` is `front-hr-management` to nx, as is\n * `apps/front/storybook-app`.\n *\n * That gap is not cosmetic, because this name is what `--module` is matched against. The\n * `serve` script `--init` writes carries `--module <name>`, so a module in that shape was\n * adopted with a script naming a project the workspace does not contain, and `pnpm run serve`\n * failed the day it was adopted with nothing having reported it.\n *\n * One function so the two ends cannot drift again: whoever WRITES the name and whoever\n * RESOLVES it now read the same source.\n */\nexport function moduleName(dir: string): string {\n const declared = readProjectPackageJson(dir).name\n return readNxProjectName(dir) ?? (declared === undefined ? basename(dir) : declared)\n}\n","/**\n * Tunable settings: the knobs you would actually change. The fixed vocabulary and\n * its types live in `domain.ts`.\n *\n * Note: the preset is DETECTED from a module's dependencies (see\n * `detect-framework.ts`), deterministically, `node` when no framework dep is present.\n * `--preset` overrides the detection (recommended for `--init`, since hoisted deps\n * can make detection return `node`). The detection table lives in `detect-framework.ts`.\n */\n\n/** A file that marks a workspace root; sentinel refuses to operate there. */\nexport const WORKSPACE_ROOT_MARKER = 'nx.json'\n\n/**\n * How many structured diagnostics a report embeds per module, when the caller does not say.\n *\n * A cap, not a limit on what is counted: the totals stay exact and `diagnosticsTruncated`\n * says when rows were dropped, so a bounded envelope never reads as a clean module. Shared\n * by every role, because a consumer reading `--json` across roles should not have to learn\n * a different cap for each one. `--max-diagnostics 0` means no cap.\n */\nexport const DEFAULT_MAX_DIAGNOSTICS = 100\n","/**\n * Workspace-level preparation that `--init` applies once, at the workspace root, so an\n * adopted module actually installs and resolves cleanly. `--init` writes a module's own\n * files; this is the small, deliberate exception where it also touches the root, because\n * \"set this module up\" is not honest if the workspace it lives in cannot then install or\n * resolve it.\n *\n * Kept intentionally narrow (YAGNI): it applies the specific fixes a workspace's OWN\n * config shows it needs, and is a no-op everywhere else. It is NOT a speculative dedupe\n * engine, when a second, different case appears we generalize from real cases instead of\n * predicting them. The two fixes today:\n *\n * - i18next singleton override. If the root declares a second TypeScript via the\n * `@typescript/native` alias (a repo mid-migration running two TypeScripts), adopting a\n * module re-resolves pnpm and i18next, which carries an optional type-only `typescript`\n * peer, forks into two peer-keyed variants. That forks react-i18next into two React\n * contexts and breaks translation-singleton tests. Pinning i18next's `typescript` peer\n * to the declared TS collapses it to one. Read from the root's own declaration, so it\n * tracks the version and disappears (no-op) once the migration removes the second TS.\n * Written to `pnpm-workspace.yaml`, the only place current pnpm reads overrides from.\n *\n * - release-age allow-list. If the root uses pnpm's `minimumReleaseAgeExclude` gate, a\n * freshly published sentinel is younger than the gate and will not install; allow-list\n * it. Only touched when the gate is actually configured.\n *\n * - root formatter exclusion. A module that has adopted the FORMAT role is formatted by\n * oxfmt, from its own config. The root Prettier does not know that and still owns every\n * file in the repo, so `nx format:write` or `prettier --write .` from the root reformats\n * the module back to the ROOT's style, and the next `pnpm run format` in the module\n * reverses it. Two formatters fighting over the same files, with whichever ran last\n * winning, is the exact state adoption removes INSIDE a module, and it has to be removed\n * at the root too. So the module is excluded in the root `.prettierignore` as it adopts,\n * and the root's own config is otherwise untouched: it still serves every module that has\n * not migrated.\n *\n * All three edits are idempotent and defensive: absent the trigger (no second TypeScript, no\n * release-age gate, no root Prettier) each is skipped, so the step is safe on any workspace.\n */\nimport { existsSync, readFileSync, writeFileSync } from 'node:fs'\nimport { dirname, join, relative } from 'node:path'\n\nimport { readOwnPackage } from '../shared/package-json.js'\n\nimport { WORKSPACE_ROOT_MARKER } from './settings.js'\n\nconst OVERRIDE_KEY = 'i18next>typescript'\nconst NATIVE_TS_ALIAS = '@typescript/native' // `npm:typescript@^7.x`: a workspace's second, real TS\nconst WORKSPACE_YAML = 'pnpm-workspace.yaml'\nconst RELEASE_AGE_KEY = 'minimumReleaseAgeExclude'\nconst LIST_ITEM = /^(\\s*)-\\s+(.*?)\\s*$/\nconst MAP_ENTRY = /^(\\s+)(.*?):\\s+(.*?)\\s*$/\n\ninterface RootPackageJson {\n dependencies?: Record<string, string>\n devDependencies?: Record<string, string>\n pnpm?: { overrides?: Record<string, string> }\n}\n\n/** Walk up from a module directory to the workspace root (the dir holding the marker). */\nexport function findWorkspaceRoot(startDir: string): string | undefined {\n let dir = startDir\n for (;;) {\n if (existsSync(join(dir, WORKSPACE_ROOT_MARKER))) return dir\n const parent = dirname(dir)\n if (parent === dir) return undefined\n dir = parent\n }\n}\n\n/**\n * The TypeScript version to pin i18next's peer to, read from the root's own\n * `@typescript/native` alias (`npm:typescript@^7.0.2` -> `7.0.2`). Returns undefined when\n * the alias is absent (no second TypeScript, so no fork to fix).\n */\nfunction declaredNativeTs(pkg: RootPackageJson): string | undefined {\n const spec = pkg.dependencies?.[NATIVE_TS_ALIAS] ?? pkg.devDependencies?.[NATIVE_TS_ALIAS]\n if (!spec) return undefined\n const version = spec.slice(spec.lastIndexOf('@') + 1).replace(/^[\\^~>=<\\s]+/, '')\n return version || undefined\n}\n\n/**\n * Ensure the i18next singleton override matches the declared second TypeScript. No-op when\n * the workspace runs a single TypeScript (no `@typescript/native`). Returns a message when\n * it changed (or would change, under dryRun), else undefined.\n *\n * The override is written to `pnpm-workspace.yaml`, never to `package.json#pnpm`: pnpm 11\n * stopped reading the `pnpm` field and does so SILENTLY, with no warning and no deprecation\n * (pnpm/pnpm#11536), so an override left in `package.json` is simply not applied. The yaml is\n * read by pnpm 10 and 11 alike (https://pnpm.io/settings), which is why there is one target\n * here rather than a version-dependent fallback.\n */\nfunction ensureI18nextSingleton(root: string, dryRun: boolean): string | undefined {\n const pkgPath = join(root, 'package.json')\n if (!existsSync(pkgPath)) return undefined\n const pkg = JSON.parse(readFileSync(pkgPath, 'utf8')) as RootPackageJson\n const want = declaredNativeTs(pkg)\n if (!want) return undefined // single TypeScript: nothing forks\n\n const yamlPath = join(root, WORKSPACE_YAML)\n if (!existsSync(yamlPath)) return undefined // no workspace file: nowhere pnpm would read it\n const lines = readFileSync(yamlPath, 'utf8').split('\\n')\n const entry = findOverrideEntry(lines)\n if (entry.valueIdx !== undefined && entry.value === want) return undefined // already pinned\n if (dryRun) return `would pin ${OVERRIDE_KEY} to ${want} in ${WORKSPACE_YAML} (i18next singleton)`\n\n applyOverride(lines, entry, want)\n writeFileSync(yamlPath, lines.join('\\n'))\n return `pinned ${OVERRIDE_KEY} to ${want} in ${WORKSPACE_YAML} (i18next singleton)`\n}\n\n/** Where the override sits in the yaml, if at all, plus where a new one would be inserted. */\ninterface OverrideEntry {\n /** Index of the `OVERRIDE_KEY` line, when the key is already declared. */\n valueIdx?: number\n /** Its current value, unquoted. */\n value?: string\n /** Index of the `overrides:` key line, when the block exists. */\n blockIdx?: number\n /** Index of the block's last entry, so an insertion keeps the block contiguous. */\n lastEntryIdx?: number\n /** Indentation the block's entries use. */\n indent: string\n}\n\n/**\n * Locate the `overrides:` block and the i18next key inside it. A targeted text read rather\n * than a yaml parse, for the same reason `ensureReleaseAgeAllowList` does it: this file is\n * hand-maintained and round-tripping it through a parser would drop its comments and ordering.\n */\nfunction findOverrideEntry(lines: string[]): OverrideEntry {\n const blockIdx = lines.findIndex((line) => line.replace(/\\s+$/, '') === 'overrides:')\n if (blockIdx === -1) return { indent: ' ' }\n\n let lastEntryIdx = blockIdx\n let indent = ' '\n for (let i = blockIdx + 1; i < lines.length; i++) {\n const match = lines[i]?.match(MAP_ENTRY)\n if (!match) break // dedent or blank line: the block ended\n indent = match[1] ?? indent\n lastEntryIdx = i\n if ((match[2] ?? '').replace(/^['\"]|['\"]$/g, '') === OVERRIDE_KEY) {\n return {\n valueIdx: i,\n value: (match[3] ?? '').replace(/^['\"]|['\"]$/g, ''),\n blockIdx,\n lastEntryIdx,\n indent,\n }\n }\n }\n return { blockIdx, lastEntryIdx, indent }\n}\n\n/** Update the key in place, append it to an existing block, or open a block at the end. */\nfunction applyOverride(lines: string[], entry: OverrideEntry, want: string): void {\n const line = `${entry.indent}'${OVERRIDE_KEY}': ${want}`\n if (entry.valueIdx !== undefined) {\n lines[entry.valueIdx] = line\n return\n }\n if (entry.lastEntryIdx !== undefined) {\n lines.splice(entry.lastEntryIdx + 1, 0, line)\n return\n }\n // No `overrides:` block at all: open one, keeping a single trailing newline.\n while (lines.length > 0 && lines[lines.length - 1] === '') lines.pop()\n lines.push('overrides:', line, '')\n}\n\n/**\n * Ensure sentinel is allow-listed under `minimumReleaseAgeExclude` so a fresh publish\n * installs. No-op when the workspace has no such gate. Targeted text edit so comments and\n * formatting are preserved. Returns a message when it changed (or would), else undefined.\n */\nfunction ensureReleaseAgeAllowList(root: string, dryRun: boolean): string | undefined {\n const yamlPath = join(root, WORKSPACE_YAML)\n if (!existsSync(yamlPath)) return undefined\n const own = readOwnPackage().name\n const lines = readFileSync(yamlPath, 'utf8').split('\\n')\n const keyIdx = lines.findIndex((line) => line.replace(/\\s+$/, '') === `${RELEASE_AGE_KEY}:`)\n if (keyIdx === -1) return undefined // no release-age gate in use\n\n let lastItemIdx = keyIdx\n let indent = ' '\n for (let i = keyIdx + 1; i < lines.length; i++) {\n const match = lines[i]?.match(LIST_ITEM)\n if (!match) break\n indent = match[1] ?? indent\n lastItemIdx = i\n if ((match[2] ?? '').replace(/^['\"]|['\"]$/g, '') === own) return undefined // already listed\n }\n if (dryRun) return `would allow-list ${own} under ${RELEASE_AGE_KEY}`\n\n lines.splice(lastItemIdx + 1, 0, `${indent}- '${own}'`)\n writeFileSync(yamlPath, lines.join('\\n'))\n return `allow-listed ${own} under ${RELEASE_AGE_KEY} in ${WORKSPACE_YAML}`\n}\n\n/** The root ignore file Prettier reads, and the section sentinel owns inside it. */\nconst PRETTIER_IGNORE = '.prettierignore'\nconst EXCLUSION_HEADER = '# @hublo/sentinel: modules formatted by oxfmt, not by the root Prettier'\n\n/** Root Prettier configs. Their presence is what makes an exclusion meaningful. */\nconst ROOT_PRETTIER_CONFIGS = [\n '.prettierrc',\n '.prettierrc.json',\n '.prettierrc.json5',\n '.prettierrc.yml',\n '.prettierrc.yaml',\n '.prettierrc.js',\n '.prettierrc.cjs',\n '.prettierrc.mjs',\n 'prettier.config.js',\n 'prettier.config.cjs',\n 'prettier.config.mjs',\n]\n\n/**\n * Exclude a module that now formats itself from the ROOT Prettier.\n *\n * Written as a directory pattern anchored at the root (`/apps/front/x/`), which is what\n * gitignore syntax means by \"this exact directory\", so a module cannot accidentally exclude a\n * same-named directory somewhere else.\n *\n * Skipped entirely when the root has no Prettier at all: there is then nothing to conflict\n * with, and creating an ignore file for a tool the workspace does not use would be noise.\n */\nfunction ensureFormatterExclusion(\n root: string,\n moduleDir: string,\n dryRun: boolean,\n): string | undefined {\n const rel = relative(root, moduleDir).replaceAll('\\\\', '/')\n if (rel === '' || rel.startsWith('..')) return undefined // the root itself, or outside it\n\n const ignorePath = join(root, PRETTIER_IGNORE)\n const hasPrettier =\n existsSync(ignorePath) || ROOT_PRETTIER_CONFIGS.some((name) => existsSync(join(root, name)))\n if (!hasPrettier) return undefined\n\n const pattern = `/${rel}/`\n const existing = existsSync(ignorePath) ? readFileSync(ignorePath, 'utf8') : ''\n const lines = existing.split('\\n')\n // Already excluded, in whichever spelling a human may have used.\n if (lines.some((line) => line.trim().replace(/\\/$/, '') === pattern.replace(/\\/$/, ''))) {\n return undefined\n }\n if (dryRun) return `would exclude ${rel} from the root Prettier (${PRETTIER_IGNORE})`\n\n const headerIdx = lines.findIndex((line) => line.trim() === EXCLUSION_HEADER)\n if (headerIdx === -1) {\n const prefix = existing.trim() === '' ? '' : `${existing.replace(/\\n*$/, '')}\\n\\n`\n writeFileSync(ignorePath, `${prefix}${EXCLUSION_HEADER}\\n${pattern}\\n`)\n } else {\n // Keep the managed section together and sorted, so the list stays readable as it grows\n // to 150 entries and a diff shows one added line rather than a reshuffle.\n let end = headerIdx + 1\n while (end < lines.length && lines[end]?.trim().startsWith('/')) end += 1\n const managed = [...lines.slice(headerIdx + 1, end), pattern].sort()\n writeFileSync(\n ignorePath,\n [...lines.slice(0, headerIdx + 1), ...managed, ...lines.slice(end)].join('\\n'),\n )\n }\n return `excluded ${rel} from the root Prettier (${PRETTIER_IGNORE}): it formats itself now`\n}\n\n/**\n * Apply every workspace-level fix the given root needs. Returns the human-readable\n * messages for what changed (empty when nothing did). Under dryRun nothing is written and\n * the messages describe what would change.\n *\n * `formattedModule` is the module that just adopted the FORMAT role, when one did. Absent\n * for a lint-only or TypeScript-only `--init`, because a module that has not adopted the\n * formatter must keep being formatted by the root.\n */\nexport function ensureWorkspacePrep(opts: {\n root: string\n dryRun?: boolean\n formattedModule?: string\n}): string[] {\n const dryRun = Boolean(opts.dryRun)\n return [\n ensureI18nextSingleton(opts.root, dryRun),\n ensureReleaseAgeAllowList(opts.root, dryRun),\n opts.formattedModule === undefined\n ? undefined\n : ensureFormatterExclusion(opts.root, opts.formattedModule, dryRun),\n ].filter((message): message is string => message !== undefined)\n}\n\n/**\n * One standing change sentinel has made to the workspace ROOT, and whether it is still earned.\n *\n * This exists because the three root writes are the only thing sentinel does that nobody can\n * see afterwards. `--init` announces each one on the run that makes it, and then it is a line\n * in someone else's file forever. Two consequences already bit us.\n *\n * The `i18next>typescript` override re-resolved `apps/front/console` from a PR that touched no\n * file under it, so CODEOWNERS pulled nobody and its owners learned about it afterwards.\n *\n * And every one of them is TEMPORARY with its removal condition written in prose. That failed\n * three times in one week: `SENTINEL_TAG=alpha` in the migration-page workflow kept building\n * with a pre-release long after 1.1.0 went stable and reported `Format 0/439` for 15 adopted\n * modules; ADR 0042 and its RFC both sat mid-flight past their deadline; and this release-age\n * exclusion is still waiting on \"once sentinel's release cadence settles\". Nothing failed when\n * any of them expired, because a comment cannot fail.\n *\n * So each entry carries the condition that JUSTIFIES it, evaluated now rather than described.\n * A trigger that has gone away makes the entry dead config, and dead config is something a\n * command can say out loud instead of something a reader has to notice.\n *\n * Read-only, and offline: it inspects the same files `--init` writes and nothing else. What it\n * deliberately does NOT claim is anything needing the npm registry — whether a published\n * version is old enough to clear the release-age gate is not knowable from the repo, so it is\n * stated as the standing exemption it is rather than guessed at.\n */\nexport interface WorkspacePrepEntry {\n /** `rule` to match the shape the shared renderer already prints as a labelled block. */\n rule: string\n /** What it does, whether its trigger still holds, and what would retire it. */\n reason: string\n}\n\n/** The root-level changes sentinel maintains here, each with its justification checked now. */\nexport function inspectWorkspacePrep(root: string): WorkspacePrepEntry[] {\n const entries: WorkspacePrepEntry[] = []\n // Tolerant, unlike the write path above, and the difference is deliberate. `--init` should\n // fail loudly on a root it cannot parse, because it is about to edit it. `--inspect` is a\n // read verb that sweeps 441 modules, and one malformed root file must not take the whole\n // sweep down: the modules still have honest answers to give. Caught by its own test.\n let pkg: RootPackageJson = {}\n const pkgPath = join(root, 'package.json')\n if (existsSync(pkgPath)) {\n try {\n pkg = JSON.parse(readFileSync(pkgPath, 'utf8')) as RootPackageJson\n } catch {\n pkg = {}\n }\n }\n\n const yamlPath = join(root, WORKSPACE_YAML)\n const yaml = existsSync(yamlPath) ? readFileSync(yamlPath, 'utf8') : undefined\n\n const pinned = yaml === undefined ? undefined : findOverrideEntry(yaml.split('\\n')).value\n if (pinned !== undefined) {\n const stillForks = declaredNativeTs(pkg)\n entries.push({\n rule: `${WORKSPACE_YAML} → overrides.${OVERRIDE_KEY}`,\n reason:\n stillForks === undefined\n ? `pinned to ${pinned}, but the root no longer declares ${NATIVE_TS_ALIAS}, so nothing ` +\n `forks any more. This override is now dead config and can be removed.`\n : `pinned to ${pinned}, matching the second TypeScript the root declares ` +\n `(${NATIVE_TS_ALIAS}). It re-resolves i18next for EVERY module that depends on it, ` +\n `not only adopted ones. It retires with the second TypeScript.`,\n })\n }\n\n // A copy left in `package.json#pnpm` by an older sentinel, or by hand. Reported because it\n // is INERT rather than redundant: pnpm 11 dropped the `pnpm` field with no warning\n // (pnpm/pnpm#11536), so it reads as protection that is not applied.\n const legacy = pkg.pnpm?.overrides?.[OVERRIDE_KEY]\n if (legacy !== undefined) {\n entries.push({\n rule: `package.json → pnpm.overrides.${OVERRIDE_KEY}`,\n reason:\n `pinned to ${legacy}, but pnpm 11 no longer reads the \\`pnpm\\` field of package.json ` +\n `and says nothing about it, so this override is not applied. Move it to ` +\n `${WORKSPACE_YAML} under \\`overrides:\\`, which pnpm 10 and 11 both read.`,\n })\n }\n\n const own = readOwnPackage().name\n if (yaml !== undefined) {\n const listed = new RegExp(`^\\\\s*-\\\\s*['\"]?${own.replace('/', '\\\\/')}['\"]?\\\\s*$`, 'm').test(yaml)\n if (listed) {\n const gated = new RegExp(`^\\\\s*minimumReleaseAge\\\\s*:`, 'm').test(yaml)\n entries.push({\n rule: `${WORKSPACE_YAML} → ${RELEASE_AGE_KEY}: ${own}`,\n reason: gated\n ? `${own} is exempt from this workspace's release-age gate, so a freshly published ` +\n `version installs instead of being held. That is a standing exemption from a ` +\n `supply-chain control: the exposure is one bump wide, since the version is pinned ` +\n `and the lockfile carries its integrity, so a re-publish of the same version does ` +\n `not pass. Retire it once a bump can simply wait out the window.`\n : `${own} is allow-listed, but this workspace no longer sets minimumReleaseAge, so ` +\n `there is no gate to be exempt from. This entry is now dead config and can be removed.`,\n })\n }\n }\n\n return entries\n}\n"],"mappings":"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","names":["existsSync","readFileSync","dirname","join","existsSync","join","dirname","readFileSync"]}
|
|
File without changes
|