vibecodedshit 0.0.0-stage → 0.2.0
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/LICENSE +95 -0
- package/README.md +64 -2
- package/assets/fonts/NotoEmoji-OFL.txt +93 -0
- package/assets/fonts/NotoEmoji-Poop.ttf +0 -0
- package/assets/fonts/SpaceGrotesk-Bold.ttf +0 -0
- package/assets/fonts/SpaceGrotesk-OFL.txt +93 -0
- package/dist/bin.js +8 -0
- package/dist/card/previous.js +38 -0
- package/dist/card/render.js +71 -0
- package/dist/cli.js +121 -0
- package/dist/commands/card.js +69 -0
- package/dist/commands/common.js +1 -0
- package/dist/commands/diff.js +54 -0
- package/dist/commands/footer.js +25 -0
- package/dist/commands/report.js +49 -0
- package/dist/commands/route.js +62 -0
- package/dist/commands/submit.js +125 -0
- package/dist/commands/summary.js +77 -0
- package/dist/diff/compare.js +73 -0
- package/dist/diff/conditions.js +87 -0
- package/dist/diff/format.js +23 -0
- package/dist/exec/docker.js +158 -0
- package/dist/exec/findings.js +73 -0
- package/dist/exec/plan.js +50 -0
- package/dist/exec/reports.js +90 -0
- package/dist/exec/roots.js +49 -0
- package/dist/exec/run.js +182 -0
- package/dist/exec/steps/build.js +45 -0
- package/dist/exec/steps/coverage-install.js +12 -0
- package/dist/exec/steps/install.js +41 -0
- package/dist/exec/steps/knip.js +28 -0
- package/dist/exec/steps/lint.js +25 -0
- package/dist/exec/steps/lockfiles.js +25 -0
- package/dist/exec/steps/test.js +17 -0
- package/dist/exec/steps/typecheck.js +40 -0
- package/dist/exec/test-reports/coverage-py.js +23 -0
- package/dist/exec/test-reports/coverage-ranges.js +65 -0
- package/dist/exec/test-reports/coverage-summary.js +23 -0
- package/dist/exec/test-reports/jest-json.js +31 -0
- package/dist/exec/test-reports/junit.js +24 -0
- package/dist/exec/test-reports/mocha-json.js +18 -0
- package/dist/exec/test-reports/vitest-json.js +5 -0
- package/dist/exec/test-runners/detect.js +99 -0
- package/dist/exec/test-runners/index.js +6 -0
- package/dist/exec/test-runners/jest.js +27 -0
- package/dist/exec/test-runners/mocha.js +28 -0
- package/dist/exec/test-runners/node-test.js +24 -0
- package/dist/exec/test-runners/pytest.js +18 -0
- package/dist/exec/test-runners/script.js +48 -0
- package/dist/exec/test-runners/spec.js +1 -0
- package/dist/exec/test-runners/vitest.js +28 -0
- package/dist/exec/tests.js +120 -0
- package/dist/exec/types.js +1 -0
- package/dist/manifest/package-json.js +36 -0
- package/dist/manifest/python.js +106 -0
- package/dist/measure/d3/index.js +45 -0
- package/dist/measure/d3/presets.js +100 -0
- package/dist/measure/d3/py/adapter.js +67 -0
- package/dist/measure/d3/py/config.js +80 -0
- package/dist/measure/d3/py/contracts.js +57 -0
- package/dist/measure/d3/py/domain.js +57 -0
- package/dist/measure/d3/py/graph.js +214 -0
- package/dist/measure/d3/py/interpreter.js +31 -0
- package/dist/measure/d3/py/layers.js +120 -0
- package/dist/measure/d3/py/roots.js +58 -0
- package/dist/measure/d3/py/types.js +1 -0
- package/dist/measure/d3/scc.js +64 -0
- package/dist/measure/d3/ts/adapter.js +371 -0
- package/dist/measure/d3/ts/classify.js +107 -0
- package/dist/measure/d3/ts/config.js +318 -0
- package/dist/measure/d3/ts/domain.js +23 -0
- package/dist/measure/d3/ts/rules.js +65 -0
- package/dist/measure/d3/types.js +5 -0
- package/dist/measure/deps/cvss.js +66 -0
- package/dist/measure/deps/declared.js +195 -0
- package/dist/measure/deps/files.js +40 -0
- package/dist/measure/deps/lockfiles.js +308 -0
- package/dist/measure/deps/osv.js +122 -0
- package/dist/measure/deps/registry.js +142 -0
- package/dist/measure/deps/resolved.js +78 -0
- package/dist/measure/deps/units.js +5 -0
- package/dist/measure/eslint/health-plugin.js +19 -0
- package/dist/measure/ops/docs-commands.js +73 -0
- package/dist/measure/ops/docs-resolve.js +217 -0
- package/dist/measure/ops/entry-points.js +49 -0
- package/dist/measure/ops/env-example.js +20 -0
- package/dist/measure/ops/env-reads.js +46 -0
- package/dist/measure/ops/runtime.js +73 -0
- package/dist/measure/p1-1.js +8 -0
- package/dist/measure/p1-2.js +8 -0
- package/dist/measure/p1-3.js +8 -0
- package/dist/measure/p1-4.js +41 -0
- package/dist/measure/p1-5.js +59 -0
- package/dist/measure/p2-1.js +8 -0
- package/dist/measure/p2-2.js +44 -0
- package/dist/measure/p2-3.js +81 -0
- package/dist/measure/p2-4.js +288 -0
- package/dist/measure/p2-5.js +8 -0
- package/dist/measure/p3-1.js +16 -0
- package/dist/measure/p3-2.js +26 -0
- package/dist/measure/p3-3.js +37 -0
- package/dist/measure/p3-4.js +26 -0
- package/dist/measure/p3-5.js +26 -0
- package/dist/measure/p4-1.js +184 -0
- package/dist/measure/p4-2.js +27 -0
- package/dist/measure/p4-3.js +106 -0
- package/dist/measure/p4-4.js +309 -0
- package/dist/measure/p5-1.js +117 -0
- package/dist/measure/p5-2.js +80 -0
- package/dist/measure/p5-3.js +21 -0
- package/dist/measure/p5-4.js +161 -0
- package/dist/measure/p5-5.js +158 -0
- package/dist/measure/p6-1.js +20 -0
- package/dist/measure/p6-2.js +20 -0
- package/dist/measure/p6-3.js +40 -0
- package/dist/measure/p6-4.js +31 -0
- package/dist/measure/p6-5.js +60 -0
- package/dist/measure/p6-6.js +54 -0
- package/dist/measure/py/typecheck-config.js +95 -0
- package/dist/measure/registry.js +31 -0
- package/dist/measure/safety/py-tests.js +124 -0
- package/dist/measure/safety/ts-barrels.js +68 -0
- package/dist/measure/safety/ts-cases.js +111 -0
- package/dist/measure/safety/ts-credits.js +101 -0
- package/dist/measure/safety/ts-imports.js +63 -0
- package/dist/measure/safety/ts-resolve.js +43 -0
- package/dist/measure/ts/directives.js +37 -0
- package/dist/measure/ts/exports.js +60 -0
- package/dist/measure/ts/tsconfig.js +179 -0
- package/dist/measure/types.js +1 -0
- package/dist/measure/units.js +41 -0
- package/dist/methodology/load.js +159 -0
- package/dist/methodology/schema.js +105 -0
- package/dist/python/deadcode.js +27 -0
- package/dist/python/env.js +13 -0
- package/dist/python/helper.js +24 -0
- package/dist/python/imports.js +40 -0
- package/dist/python/nox.js +16 -0
- package/dist/python/scope.js +35 -0
- package/dist/python/tests.js +19 -0
- package/dist/python/typing.js +27 -0
- package/dist/report/escape.js +5 -0
- package/dist/report/format.js +75 -0
- package/dist/report/render.js +213 -0
- package/dist/report/roadmap.js +58 -0
- package/dist/report/scares.js +83 -0
- package/dist/report/style.js +27 -0
- package/dist/report-only/hotspots.js +48 -0
- package/dist/route/check.js +37 -0
- package/dist/route/files.js +74 -0
- package/dist/route/order.js +22 -0
- package/dist/route/select.js +114 -0
- package/dist/route/write.js +13 -0
- package/dist/run/read.js +89 -0
- package/dist/run/recompute.js +19 -0
- package/dist/run/scan.js +196 -0
- package/dist/run/schema.js +1 -0
- package/dist/run/score-run.js +42 -0
- package/dist/run/write.js +4 -0
- package/dist/scope/build.js +173 -0
- package/dist/scope/packages.js +87 -0
- package/dist/scope/rules.js +21 -0
- package/dist/scope/tracked.js +34 -0
- package/dist/scope/ts-lines.js +63 -0
- package/dist/scope/types.js +7 -0
- package/dist/scoring/aggregate.js +38 -0
- package/dist/scoring/pool.js +65 -0
- package/dist/scoring/score.js +81 -0
- package/dist/scoring/what-if.js +38 -0
- package/dist/scripts/checks.js +53 -0
- package/dist/scripts/ci.js +139 -0
- package/dist/scripts/entry-points/find.js +31 -0
- package/dist/scripts/entry-points/index.js +18 -0
- package/dist/scripts/entry-points/justfile.js +62 -0
- package/dist/scripts/entry-points/makefile.js +66 -0
- package/dist/scripts/entry-points/nox.js +29 -0
- package/dist/scripts/entry-points/package-json.js +21 -0
- package/dist/scripts/entry-points/pyproject.js +73 -0
- package/dist/scripts/entry-points/tox.js +130 -0
- package/dist/scripts/expand.js +300 -0
- package/dist/scripts/triggers.js +151 -0
- package/dist/scripts/types.js +1 -0
- package/dist/submit/constants.js +9 -0
- package/dist/submit/payload.js +42 -0
- package/dist/submit/repo-identity.js +46 -0
- package/dist/submit/send.js +29 -0
- package/dist/tools/catalog.js +46 -0
- package/dist/tools/image.js +180 -0
- package/dist/tools/kit.js +71 -0
- package/dist/tools/knip.js +169 -0
- package/dist/tools/outcome.js +21 -0
- package/dist/tools/resolve.js +66 -0
- package/dist/tools/workcopy.js +27 -0
- package/dist/util/ini.js +37 -0
- package/dist/util/pep440.js +163 -0
- package/dist/util/toml.js +27 -0
- package/docker/Dockerfile +142 -0
- package/docker/bin/health-capture +5 -0
- package/docker/bin/health-coverage-install +5 -0
- package/docker/bin/health-eslint +66 -0
- package/docker/bin/health-pytest +10 -0
- package/docker/bin/health-tsc +10 -0
- package/docker/bin/health-venv +11 -0
- package/docker/config/eslint.config.mjs +4 -0
- package/docker/config/eslint.rules.mjs +65 -0
- package/docker/config/eslint.untyped.config.mjs +4 -0
- package/docker/config/knip.json +1 -0
- package/docker/config/ruff.toml +7 -0
- package/methodology/LICENSE +396 -0
- package/methodology/v1.0/METHODOLOGY.md +1176 -0
- package/methodology/v1.0/dimensions/1-safety-net.yaml +240 -0
- package/methodology/v1.0/dimensions/2-type-static-rigor.yaml +289 -0
- package/methodology/v1.0/dimensions/3-architecture-boundaries.yaml +266 -0
- package/methodology/v1.0/dimensions/4-complexity-maintainability.yaml +192 -0
- package/methodology/v1.0/dimensions/5-dependency-security-hygiene.yaml +223 -0
- package/methodology/v1.0/dimensions/6-operability-feedback-loop.yaml +279 -0
- package/methodology/v1.0/methodology.yaml +242 -0
- package/methodology/v1.0/presets/django.yaml +43 -0
- package/methodology/v1.0/presets/express.yaml +47 -0
- package/methodology/v1.0/presets/fastapi.yaml +52 -0
- package/methodology/v1.0/presets/generic.yaml +41 -0
- package/methodology/v1.0/presets/nextjs.yaml +50 -0
- package/methodology/v1.0/scope.yaml +115 -0
- package/package.json +61 -4
- package/python/dead_code_helper.py +265 -0
- package/python/env_helper.py +71 -0
- package/python/graph_helper.py +277 -0
- package/python/imports_helper.py +132 -0
- package/python/nox_helper.py +79 -0
- package/python/scope_helper.py +96 -0
- package/python/tests_helper.py +171 -0
- package/python/typing_helper.py +235 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { copyFile, mkdir, mkdtemp, rm } from 'node:fs/promises';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
/**
|
|
6
|
+
* Copy these working-tree files (same relative paths) into a new temp directory.
|
|
7
|
+
* Tools that read their own config run there, so no repository config can change a score.
|
|
8
|
+
* A path missing from the working tree (tracked but deleted, or a broken symlink) is skipped.
|
|
9
|
+
*/
|
|
10
|
+
export async function createWorkCopy(repoPath, relPaths) {
|
|
11
|
+
const dir = await mkdtemp(path.join(tmpdir(), 'health-work-'));
|
|
12
|
+
try {
|
|
13
|
+
for (const rel of relPaths) {
|
|
14
|
+
const src = path.join(repoPath, rel);
|
|
15
|
+
if (!existsSync(src))
|
|
16
|
+
continue;
|
|
17
|
+
const dest = path.join(dir, rel);
|
|
18
|
+
await mkdir(path.dirname(dest), { recursive: true });
|
|
19
|
+
await copyFile(src, dest);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
catch (err) {
|
|
23
|
+
await rm(dir, { recursive: true, force: true });
|
|
24
|
+
throw err;
|
|
25
|
+
}
|
|
26
|
+
return { dir, cleanup: () => rm(dir, { recursive: true, force: true }) };
|
|
27
|
+
}
|
package/dist/util/ini.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal INI reader for mypy's `mypy.ini`, `.mypy.ini` and `setup.cfg`: sections, `key = value`
|
|
3
|
+
* and `key: value`, `;` and `#` comment lines, indented continuation lines, no interpolation.
|
|
4
|
+
* Keys before any section go to the section "".
|
|
5
|
+
*/
|
|
6
|
+
export function parseIni(text) {
|
|
7
|
+
const sections = {};
|
|
8
|
+
let current = '';
|
|
9
|
+
let lastKey;
|
|
10
|
+
for (const rawLine of text.split(/\r\n|\r|\n/)) {
|
|
11
|
+
if (rawLine.trim() === '') {
|
|
12
|
+
lastKey = undefined;
|
|
13
|
+
continue;
|
|
14
|
+
}
|
|
15
|
+
if (/^\s*[;#]/.test(rawLine))
|
|
16
|
+
continue;
|
|
17
|
+
if (/^\s/.test(rawLine) && lastKey !== undefined) {
|
|
18
|
+
const section = (sections[current] ??= {});
|
|
19
|
+
section[lastKey] = `${section[lastKey] ?? ''}\n${rawLine.trim()}`;
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
const header = /^\s*\[([^\]]*)\]/.exec(rawLine);
|
|
23
|
+
if (header) {
|
|
24
|
+
current = (header[1] ?? '').trim();
|
|
25
|
+
sections[current] ??= {};
|
|
26
|
+
lastKey = undefined;
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
const pair = /^\s*([^=:]+?)\s*[=:]\s*(.*?)\s*$/.exec(rawLine);
|
|
30
|
+
if (pair?.[1] !== undefined) {
|
|
31
|
+
const section = (sections[current] ??= {});
|
|
32
|
+
lastKey = pair[1];
|
|
33
|
+
section[lastKey] = pair[2] ?? '';
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return sections;
|
|
37
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/** The part of PEP 440 that dependency checks need: versions, ordering and specifiers. */
|
|
2
|
+
const VERSION = /^\s*v?(?:(\d+)!)?(\d+(?:\.\d+)*)(?:[-_.]?(a|alpha|b|beta|c|rc|pre|preview)[-_.]?(\d+)?)?(?:-(\d+)|[-_.]?(post|rev|r)[-_.]?(\d+)?)?(?:[-_.]?(dev)[-_.]?(\d+)?)?(?:\+([a-z0-9]+(?:[-_.][a-z0-9]+)*))?\s*$/;
|
|
3
|
+
export function parseVersion(text) {
|
|
4
|
+
const m = VERSION.exec(text.toLowerCase());
|
|
5
|
+
if (!m)
|
|
6
|
+
return undefined;
|
|
7
|
+
const version = { epoch: Number(m[1] ?? 0), release: (m[2] ?? '0').split('.').map(Number) };
|
|
8
|
+
if (m[3] !== undefined) {
|
|
9
|
+
const label = m[3];
|
|
10
|
+
const kind = label === 'a' || label === 'alpha' ? 'a' : label === 'b' || label === 'beta' ? 'b' : 'rc';
|
|
11
|
+
version.pre = { kind, n: Number(m[4] ?? 0) };
|
|
12
|
+
}
|
|
13
|
+
if (m[5] !== undefined)
|
|
14
|
+
version.post = Number(m[5]);
|
|
15
|
+
else if (m[6] !== undefined)
|
|
16
|
+
version.post = Number(m[7] ?? 0);
|
|
17
|
+
if (m[8] !== undefined)
|
|
18
|
+
version.dev = Number(m[9] ?? 0);
|
|
19
|
+
if (m[10] !== undefined)
|
|
20
|
+
version.local = m[10];
|
|
21
|
+
return version;
|
|
22
|
+
}
|
|
23
|
+
function cmpNumbers(a, b) {
|
|
24
|
+
const length = Math.max(a.length, b.length);
|
|
25
|
+
for (let i = 0; i < length; i += 1) {
|
|
26
|
+
const diff = (a[i] ?? 0) - (b[i] ?? 0);
|
|
27
|
+
if (diff !== 0)
|
|
28
|
+
return diff < 0 ? -1 : 1;
|
|
29
|
+
}
|
|
30
|
+
return 0;
|
|
31
|
+
}
|
|
32
|
+
const PRE_RANK = { a: 0, b: 1, rc: 2 };
|
|
33
|
+
/** Sort key for the pre/post/dev part, following the PEP 440 ordering rules. */
|
|
34
|
+
function phase(v) {
|
|
35
|
+
const pre = v.pre ? PRE_RANK[v.pre.kind] : v.post === undefined && v.dev !== undefined ? -1 : 3;
|
|
36
|
+
const preN = v.pre ? v.pre.n : 0;
|
|
37
|
+
const post = v.post === undefined ? -1 : v.post;
|
|
38
|
+
const dev = v.dev === undefined ? Number.POSITIVE_INFINITY : v.dev;
|
|
39
|
+
return [pre, preN, post, dev, 0];
|
|
40
|
+
}
|
|
41
|
+
export function compareVersions(a, b) {
|
|
42
|
+
if (a.epoch !== b.epoch)
|
|
43
|
+
return a.epoch < b.epoch ? -1 : 1;
|
|
44
|
+
const release = cmpNumbers(a.release, b.release);
|
|
45
|
+
if (release !== 0)
|
|
46
|
+
return release;
|
|
47
|
+
const pa = phase(a);
|
|
48
|
+
const pb = phase(b);
|
|
49
|
+
for (let i = 0; i < pa.length; i += 1) {
|
|
50
|
+
const x = pa[i] ?? 0;
|
|
51
|
+
const y = pb[i] ?? 0;
|
|
52
|
+
if (x !== y)
|
|
53
|
+
return x < y ? -1 : 1;
|
|
54
|
+
}
|
|
55
|
+
const la = a.local ?? '';
|
|
56
|
+
const lb = b.local ?? '';
|
|
57
|
+
return la === lb ? 0 : la < lb ? -1 : 1;
|
|
58
|
+
}
|
|
59
|
+
/** A stable release: no pre-release and no dev part (post releases are stable). */
|
|
60
|
+
export function isStable(v) {
|
|
61
|
+
return v.pre === undefined && v.dev === undefined;
|
|
62
|
+
}
|
|
63
|
+
function stripLocal(v) {
|
|
64
|
+
return { epoch: v.epoch, release: v.release, pre: v.pre, post: v.post, dev: v.dev };
|
|
65
|
+
}
|
|
66
|
+
function parseClauses(specifier) {
|
|
67
|
+
const clauses = [];
|
|
68
|
+
for (const part of specifier.split(',')) {
|
|
69
|
+
const trimmed = part.trim();
|
|
70
|
+
if (trimmed === '')
|
|
71
|
+
continue;
|
|
72
|
+
const m = /^(===|==|!=|<=|>=|~=|<|>)\s*(.+)$/.exec(trimmed);
|
|
73
|
+
if (!m?.[1] || !m[2])
|
|
74
|
+
return undefined;
|
|
75
|
+
clauses.push({ op: m[1], version: m[2].trim() });
|
|
76
|
+
}
|
|
77
|
+
return clauses;
|
|
78
|
+
}
|
|
79
|
+
function clauseHolds(version, raw, clause) {
|
|
80
|
+
const wildcard = clause.version.endsWith('.*');
|
|
81
|
+
const target = parseVersion(wildcard ? clause.version.slice(0, -2) : clause.version);
|
|
82
|
+
if (!target)
|
|
83
|
+
return false;
|
|
84
|
+
const a = stripLocal(version);
|
|
85
|
+
switch (clause.op) {
|
|
86
|
+
case '===':
|
|
87
|
+
return raw.trim().toLowerCase() === clause.version.toLowerCase();
|
|
88
|
+
case '==':
|
|
89
|
+
case '!=': {
|
|
90
|
+
let equal;
|
|
91
|
+
if (wildcard) {
|
|
92
|
+
equal = a.epoch === target.epoch && target.release.every((n, i) => (a.release[i] ?? 0) === n);
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
equal = compareVersions(target.local ? version : a, target) === 0;
|
|
96
|
+
}
|
|
97
|
+
return clause.op === '==' ? equal : !equal;
|
|
98
|
+
}
|
|
99
|
+
case '<=':
|
|
100
|
+
return compareVersions(a, target) <= 0;
|
|
101
|
+
case '>=':
|
|
102
|
+
return compareVersions(a, target) >= 0;
|
|
103
|
+
case '<':
|
|
104
|
+
return compareVersions(a, target) < 0 && !(target.pre === undefined && a.pre !== undefined && cmpNumbers(a.release, target.release) === 0);
|
|
105
|
+
case '>':
|
|
106
|
+
return compareVersions(a, target) > 0 && !(target.post === undefined && a.post !== undefined && cmpNumbers(a.release, target.release) === 0);
|
|
107
|
+
case '~=': {
|
|
108
|
+
if (target.release.length < 2)
|
|
109
|
+
return false;
|
|
110
|
+
const prefix = target.release.slice(0, -1);
|
|
111
|
+
if (compareVersions(a, target) < 0)
|
|
112
|
+
return false;
|
|
113
|
+
return a.epoch === target.epoch && prefix.every((n, i) => (a.release[i] ?? 0) === n);
|
|
114
|
+
}
|
|
115
|
+
default:
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
/** Is this text a well-formed specifier set ('' is valid and means any version)? */
|
|
120
|
+
export function isValidSpecifier(specifier) {
|
|
121
|
+
return parseClauses(specifier) !== undefined;
|
|
122
|
+
}
|
|
123
|
+
/** Does `version` satisfy a PEP 440 specifier set? An empty specifier accepts everything. */
|
|
124
|
+
export function satisfies(version, specifier) {
|
|
125
|
+
const parsed = parseVersion(version);
|
|
126
|
+
if (!parsed)
|
|
127
|
+
return false;
|
|
128
|
+
const clauses = parseClauses(specifier);
|
|
129
|
+
if (!clauses)
|
|
130
|
+
return false;
|
|
131
|
+
return clauses.every((clause) => clauseHolds(parsed, version, clause));
|
|
132
|
+
}
|
|
133
|
+
/** `^1.2.3` and `~1.2.3` (Poetry) to a PEP 440 specifier set; other constraints pass through. */
|
|
134
|
+
export function poetryToPep440(constraint) {
|
|
135
|
+
const text = constraint.trim();
|
|
136
|
+
if (text === '' || text === '*')
|
|
137
|
+
return '';
|
|
138
|
+
return text
|
|
139
|
+
.split(',')
|
|
140
|
+
.map((part) => part.trim())
|
|
141
|
+
.map((part) => {
|
|
142
|
+
const m = /^([\^~])\s*(\d+(?:\.\d+)*)/.exec(part);
|
|
143
|
+
if (!m?.[2])
|
|
144
|
+
return /^\d/.test(part) ? `==${part}` : part;
|
|
145
|
+
const nums = m[2].split('.').map(Number);
|
|
146
|
+
let upper;
|
|
147
|
+
if (m[1] === '^') {
|
|
148
|
+
const index = nums.findIndex((n) => n !== 0);
|
|
149
|
+
const at = index === -1 ? nums.length - 1 : index;
|
|
150
|
+
upper = nums.slice(0, at + 1);
|
|
151
|
+
upper[at] = (upper[at] ?? 0) + 1;
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
upper = nums.length === 1 ? [(nums[0] ?? 0) + 1] : [nums[0] ?? 0, (nums[1] ?? 0) + 1];
|
|
155
|
+
}
|
|
156
|
+
return `>=${nums.join('.')},<${upper.join('.')}`;
|
|
157
|
+
})
|
|
158
|
+
.join(',');
|
|
159
|
+
}
|
|
160
|
+
/** PEP 503 name normalization. */
|
|
161
|
+
export function normalizeName(name) {
|
|
162
|
+
return name.toLowerCase().replace(/[-_.]+/g, '-');
|
|
163
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { parse } from 'smol-toml';
|
|
3
|
+
/** A missing file or invalid TOML gives undefined; the caller adds the warning for invalid TOML. */
|
|
4
|
+
export function readToml(absPath) {
|
|
5
|
+
let text;
|
|
6
|
+
try {
|
|
7
|
+
text = readFileSync(absPath, 'utf8');
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
return undefined;
|
|
11
|
+
}
|
|
12
|
+
try {
|
|
13
|
+
return parse(text);
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/** Parse TOML text; invalid TOML gives undefined. */
|
|
20
|
+
export function parseTomlText(text) {
|
|
21
|
+
try {
|
|
22
|
+
return parse(text);
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# health:1.0, the pinned runner for the execution layer and, in full runs, every static tool. Build: npm run image:build
|
|
2
|
+
# Every tool is pinned by an exact version below. The base image is pinned by digest.
|
|
3
|
+
ARG NODE_IMAGE=node:22.20.0-bookworm@sha256:915acd9e9b885ead0c620e27e37c81b74c226e0e1c8177f37a60217b6eabb0d7
|
|
4
|
+
FROM ${NODE_IMAGE}
|
|
5
|
+
|
|
6
|
+
ARG NPM_VERSION=11.21.0
|
|
7
|
+
ARG PNPM_VERSION=12.9.1
|
|
8
|
+
ARG YARN_VERSION=4.18.1
|
|
9
|
+
ARG BUN_VERSION=1.4.2
|
|
10
|
+
ARG TYPESCRIPT_VERSION=5.9.3
|
|
11
|
+
ARG ESLINT_VERSION=9.39.5
|
|
12
|
+
ARG TYPESCRIPT_ESLINT_VERSION=8.71.0
|
|
13
|
+
ARG ESLINT_PLUGIN_REACT_HOOKS_VERSION=7.1.1
|
|
14
|
+
ARG PYRIGHT_VERSION=1.1.414
|
|
15
|
+
ARG UV_VERSION=0.12.23
|
|
16
|
+
ARG PYTHON_VERSION=3.12.15
|
|
17
|
+
ARG POETRY_VERSION=2.5.1
|
|
18
|
+
ARG PDM_VERSION=2.29.2
|
|
19
|
+
ARG PIPENV_VERSION=2026.8.0
|
|
20
|
+
ARG RUFF_VERSION=0.16.10
|
|
21
|
+
ARG MYPY_VERSION=2.4.0
|
|
22
|
+
ARG C8_VERSION=12.0.0
|
|
23
|
+
# Same version as the host's knip in package.json, so preview and full runs report the same issues.
|
|
24
|
+
ARG KNIP_VERSION=6.39.0
|
|
25
|
+
ARG JSCPD_VERSION=5.4.0
|
|
26
|
+
ARG COMPLEXIPY_VERSION=8.0.1
|
|
27
|
+
ARG VULTURE_VERSION=2.16
|
|
28
|
+
ARG DEPTRY_VERSION=0.25.1
|
|
29
|
+
ARG IMPORT_LINTER_VERSION=2.15
|
|
30
|
+
ARG GRIMP_VERSION=3.17
|
|
31
|
+
ARG GITLEAKS_VERSION=8.30.1
|
|
32
|
+
ARG GITLEAKS_SHA256_AMD64=551f6fc83ea457d62a0d98237cbad105af8d557003051f41f3e7ca7b3f2470eb
|
|
33
|
+
ARG GITLEAKS_SHA256_ARM64=e4a487ee7ccd7d3a7f7ec08657610aa3606637dab924210b3aee62570fb4b080
|
|
34
|
+
ARG OSV_SCANNER_VERSION=2.7.0
|
|
35
|
+
ARG OSV_SCANNER_SHA256_AMD64=160c6253e2922e9e0a6a9771c5c4c40a4b3c6090b34a3cdd5e80c96cc2e4218b
|
|
36
|
+
ARG OSV_SCANNER_SHA256_ARM64=4061ef4c021ed3c47f203b818f337e351336cc5cd67c0274fc36bd1dfc728dd7
|
|
37
|
+
ARG PYTEST_VERSION=9.1.1
|
|
38
|
+
ARG PYTEST_COV_VERSION=7.1.0
|
|
39
|
+
ARG COVERAGE_VERSION=7.16.2
|
|
40
|
+
ARG GO_VERSION=1.27.1
|
|
41
|
+
ARG GO_SHA256_AMD64=63d339f0da5ab53635a56f2490a7984dfe12dfcff22ad749f63edaf590168445
|
|
42
|
+
ARG GO_SHA256_ARM64=3450b45a3f9ee8568792736a5c5e70a1f2e9b36c35a8f74958c03e51d7d92bec
|
|
43
|
+
# Set by BuildKit. Redeclared here so the stage can see it; empty on a legacy builder.
|
|
44
|
+
ARG TARGETARCH
|
|
45
|
+
|
|
46
|
+
ENV DEBIAN_FRONTEND=noninteractive
|
|
47
|
+
RUN apt-get update \
|
|
48
|
+
&& apt-get install -y --no-install-recommends git ca-certificates curl build-essential \
|
|
49
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
50
|
+
|
|
51
|
+
# Node tools. Yarn is Berry because the methodology installs with `yarn install --immutable`.
|
|
52
|
+
# The base image ships Yarn 1 at /usr/local/bin/yarn, which blocks the Berry install, so it is removed first.
|
|
53
|
+
# npm is pinned past the base image's 10.9: its peer resolution crashes on vitest 4 (`reading 'edgesOut'`).
|
|
54
|
+
RUN rm -rf /usr/local/bin/yarn /usr/local/bin/yarnpkg /opt/yarn-v* \
|
|
55
|
+
&& npm install -g npm@${NPM_VERSION} \
|
|
56
|
+
&& npm install -g \
|
|
57
|
+
pnpm@${PNPM_VERSION} \
|
|
58
|
+
@yarnpkg/cli-dist@${YARN_VERSION} \
|
|
59
|
+
bun@${BUN_VERSION} \
|
|
60
|
+
typescript@${TYPESCRIPT_VERSION} \
|
|
61
|
+
eslint@${ESLINT_VERSION} \
|
|
62
|
+
pyright@${PYRIGHT_VERSION} \
|
|
63
|
+
c8@${C8_VERSION} \
|
|
64
|
+
knip@${KNIP_VERSION} \
|
|
65
|
+
jscpd@${JSCPD_VERSION}
|
|
66
|
+
|
|
67
|
+
# The pinned lint ruleset and its plugins live in /opt/health, never in a scanned repository.
|
|
68
|
+
# type=module because bin/health-eslint is extensionless ESM; npm 11's `npm init -y` writes "type": "commonjs",
|
|
69
|
+
# under which node exits 0 on it without running it.
|
|
70
|
+
WORKDIR /opt/health
|
|
71
|
+
RUN npm init -y >/dev/null \
|
|
72
|
+
&& npm pkg set type=module \
|
|
73
|
+
&& npm install --save-exact \
|
|
74
|
+
eslint@${ESLINT_VERSION} \
|
|
75
|
+
typescript@${TYPESCRIPT_VERSION} \
|
|
76
|
+
typescript-eslint@${TYPESCRIPT_ESLINT_VERSION} \
|
|
77
|
+
eslint-plugin-react-hooks@${ESLINT_PLUGIN_REACT_HOOKS_VERSION}
|
|
78
|
+
COPY config/ /opt/health/
|
|
79
|
+
COPY bin/ /opt/health/bin/
|
|
80
|
+
RUN chmod +x /opt/health/bin/*
|
|
81
|
+
|
|
82
|
+
# Python 3.12 from uv's pinned builds, then the Python tools. Steps run as the host user, so the tool environments
|
|
83
|
+
# live in /opt/uv-tools: uv's default is under /root, which no other user can enter.
|
|
84
|
+
ENV UV_PYTHON_INSTALL_DIR=/opt/python UV_TOOL_DIR=/opt/uv-tools UV_TOOL_BIN_DIR=/usr/local/bin UV_LINK_MODE=copy POETRY_VIRTUALENVS_IN_PROJECT=1 PIPENV_VENV_IN_PROJECT=1
|
|
85
|
+
RUN curl -LsSf https://astral.sh/uv/${UV_VERSION}/install.sh | sh \
|
|
86
|
+
&& mv /root/.local/bin/uv /root/.local/bin/uvx /usr/local/bin/ \
|
|
87
|
+
&& uv python install ${PYTHON_VERSION} \
|
|
88
|
+
&& ln -s "$(uv python find ${PYTHON_VERSION})" /usr/local/bin/python \
|
|
89
|
+
&& ln -s /usr/local/bin/python /usr/local/bin/python3 \
|
|
90
|
+
&& uv tool install --python ${PYTHON_VERSION} poetry==${POETRY_VERSION} \
|
|
91
|
+
&& uv tool install --python ${PYTHON_VERSION} pdm==${PDM_VERSION} \
|
|
92
|
+
&& uv tool install --python ${PYTHON_VERSION} pipenv==${PIPENV_VERSION} \
|
|
93
|
+
&& uv tool install --python ${PYTHON_VERSION} ruff==${RUFF_VERSION} \
|
|
94
|
+
&& uv tool install --python ${PYTHON_VERSION} mypy==${MYPY_VERSION}
|
|
95
|
+
|
|
96
|
+
# The static tools of a full run (4.1, 4.4, 5.5 and D3 for Python). import-linter's environment also holds grimp,
|
|
97
|
+
# which python/graph_helper.py imports; the helper runs with /opt/uv-tools/import-linter/bin/python.
|
|
98
|
+
RUN uv tool install --python ${PYTHON_VERSION} complexipy==${COMPLEXIPY_VERSION} \
|
|
99
|
+
&& uv tool install --python ${PYTHON_VERSION} vulture==${VULTURE_VERSION} \
|
|
100
|
+
&& uv tool install --python ${PYTHON_VERSION} deptry==${DEPTRY_VERSION} \
|
|
101
|
+
&& uv tool install --python ${PYTHON_VERSION} import-linter==${IMPORT_LINTER_VERSION} --with grimp==${GRIMP_VERSION}
|
|
102
|
+
|
|
103
|
+
# pytest, pytest-cov and coverage as wheels, for repositories whose environment has none (the test step has no network). `health-pytest` installs them offline.
|
|
104
|
+
RUN uv venv /tmp/wheel-venv --python ${PYTHON_VERSION} \
|
|
105
|
+
&& uv pip install --python /tmp/wheel-venv/bin/python pip \
|
|
106
|
+
&& /tmp/wheel-venv/bin/python -m pip download -d /opt/health/wheels pytest==${PYTEST_VERSION} pytest-cov==${PYTEST_COV_VERSION} coverage==${COVERAGE_VERSION} \
|
|
107
|
+
&& rm -rf /tmp/wheel-venv
|
|
108
|
+
|
|
109
|
+
# Go, pinned by checksum for amd64 and arm64. No parameter uses it yet.
|
|
110
|
+
# TARGETARCH is empty without BuildKit, so it falls back to the Debian architecture name.
|
|
111
|
+
RUN arch="${TARGETARCH:-$(dpkg --print-architecture)}" \
|
|
112
|
+
&& case "${arch}" in \
|
|
113
|
+
amd64) go_sha256="${GO_SHA256_AMD64}" ;; \
|
|
114
|
+
arm64) go_sha256="${GO_SHA256_ARM64}" ;; \
|
|
115
|
+
*) echo "unsupported architecture for Go: ${arch}" >&2; exit 1 ;; \
|
|
116
|
+
esac \
|
|
117
|
+
&& curl -fsSL -o /tmp/go.tgz "https://go.dev/dl/go${GO_VERSION}.linux-${arch}.tar.gz" \
|
|
118
|
+
&& echo "${go_sha256} /tmp/go.tgz" | sha256sum -c - \
|
|
119
|
+
&& tar -C /usr/local -xzf /tmp/go.tgz \
|
|
120
|
+
&& rm /tmp/go.tgz
|
|
121
|
+
|
|
122
|
+
# gitleaks (5.4) and osv-scanner (5.1), release binaries pinned by checksum for amd64 and arm64.
|
|
123
|
+
RUN arch="${TARGETARCH:-$(dpkg --print-architecture)}" \
|
|
124
|
+
&& case "${arch}" in \
|
|
125
|
+
amd64) gitleaks_arch=x64; gitleaks_sha256="${GITLEAKS_SHA256_AMD64}"; osv_sha256="${OSV_SCANNER_SHA256_AMD64}" ;; \
|
|
126
|
+
arm64) gitleaks_arch=arm64; gitleaks_sha256="${GITLEAKS_SHA256_ARM64}"; osv_sha256="${OSV_SCANNER_SHA256_ARM64}" ;; \
|
|
127
|
+
*) echo "unsupported architecture for gitleaks and osv-scanner: ${arch}" >&2; exit 1 ;; \
|
|
128
|
+
esac \
|
|
129
|
+
&& curl -fsSL -o /tmp/gitleaks.tgz "https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_${gitleaks_arch}.tar.gz" \
|
|
130
|
+
&& echo "${gitleaks_sha256} /tmp/gitleaks.tgz" | sha256sum -c - \
|
|
131
|
+
&& tar -C /usr/local/bin -xzf /tmp/gitleaks.tgz gitleaks \
|
|
132
|
+
&& rm /tmp/gitleaks.tgz \
|
|
133
|
+
&& curl -fsSL -o /usr/local/bin/osv-scanner "https://github.com/google/osv-scanner/releases/download/v${OSV_SCANNER_VERSION}/osv-scanner_linux_${arch}" \
|
|
134
|
+
&& echo "${osv_sha256} /usr/local/bin/osv-scanner" | sha256sum -c - \
|
|
135
|
+
&& chmod +x /usr/local/bin/osv-scanner
|
|
136
|
+
|
|
137
|
+
ENV PATH=/opt/health/bin:/usr/local/go/bin:$PATH
|
|
138
|
+
WORKDIR /work
|
|
139
|
+
|
|
140
|
+
# Bumped whenever the set of tools a full run needs from the image changes. A full scan refuses an image whose
|
|
141
|
+
# label differs from IMAGE_REVISION in src/exec/docker.ts, so an image built before the change is rebuilt.
|
|
142
|
+
LABEL dev.vibecodedshit.health.revision="2"
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# health-coverage-install: install @vitest/coverage-v8 at the installed vitest version, into the work copy only.
|
|
3
|
+
[ -f node_modules/@vitest/coverage-v8/package.json ] && exit 0
|
|
4
|
+
version=$(node -p "require('vitest/package.json').version") || exit 1
|
|
5
|
+
exec npm install --no-save --no-package-lock --ignore-scripts "@vitest/coverage-v8@$version"
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// health-eslint <eslint args...>: run eslint, then relint without type information the files the project service
|
|
3
|
+
// could not place in any tsconfig ("was not found by the project service"). Those files get the core rules and
|
|
4
|
+
// @typescript-eslint/no-unused-vars; their fatal entry is replaced by the untyped result. A real syntax error still
|
|
5
|
+
// reports one fatal message in either pass. Prints the merged JSON report on stdout, exits like eslint (0 clean, 1 findings).
|
|
6
|
+
import { spawnSync } from 'node:child_process';
|
|
7
|
+
import { readFileSync } from 'node:fs';
|
|
8
|
+
|
|
9
|
+
const UNTYPED_CONFIG = '/opt/health/eslint.untyped.config.mjs';
|
|
10
|
+
const MAX_BUFFER = 512 * 1024 * 1024;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Node's default heap (about 2 GB) is too small for typed linting of a large repository, well inside the
|
|
14
|
+
* container's memory limit: eslint then dies with "heap out of memory" (exit 2). The heap gets 75% of the
|
|
15
|
+
* container's cgroup limit (the methodology's runner.memory_gb), or 6 GB when the limit cannot be read.
|
|
16
|
+
*/
|
|
17
|
+
function heapMegabytes() {
|
|
18
|
+
try {
|
|
19
|
+
const limit = Number(readFileSync('/sys/fs/cgroup/memory.max', 'utf8').trim());
|
|
20
|
+
if (Number.isFinite(limit) && limit > 0) return Math.floor((limit / 1024 / 1024) * 0.75);
|
|
21
|
+
} catch {
|
|
22
|
+
// No cgroup v2 limit to read.
|
|
23
|
+
}
|
|
24
|
+
return 6144;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const env = { ...process.env, NODE_OPTIONS: `${process.env.NODE_OPTIONS ?? ''} --max-old-space-size=${heapMegabytes()}`.trim() };
|
|
28
|
+
|
|
29
|
+
function eslint(args) {
|
|
30
|
+
return spawnSync('eslint', args, { encoding: 'utf8', maxBuffer: MAX_BUFFER, stdio: ['ignore', 'pipe', 'inherit'], env });
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function parse(text) {
|
|
34
|
+
try {
|
|
35
|
+
const doc = JSON.parse(text);
|
|
36
|
+
return Array.isArray(doc) ? doc : null;
|
|
37
|
+
} catch {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const args = process.argv.slice(2);
|
|
43
|
+
const first = eslint(args);
|
|
44
|
+
const report = parse(first.stdout ?? '');
|
|
45
|
+
if (report === null || (first.status !== 0 && first.status !== 1)) {
|
|
46
|
+
process.stdout.write(first.stdout ?? '');
|
|
47
|
+
process.exit(first.status ?? 2);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const outsideProject = (entry) =>
|
|
51
|
+
entry.messages?.length === 1 && entry.messages[0].fatal === true && /project service/.test(entry.messages[0].message ?? '');
|
|
52
|
+
const stray = report.filter(outsideProject);
|
|
53
|
+
|
|
54
|
+
let merged = report;
|
|
55
|
+
if (stray.length > 0) {
|
|
56
|
+
const second = eslint(['-c', UNTYPED_CONFIG, '-f', 'json', '--no-error-on-unmatched-pattern', ...stray.map((entry) => entry.filePath)]);
|
|
57
|
+
const relinted = parse(second.stdout ?? '');
|
|
58
|
+
if (relinted === null || (second.status !== 0 && second.status !== 1)) {
|
|
59
|
+
process.stdout.write(first.stdout ?? '');
|
|
60
|
+
process.exit(second.status ?? 2);
|
|
61
|
+
}
|
|
62
|
+
merged = [...report.filter((entry) => !outsideProject(entry)), ...relinted];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
process.stdout.write(JSON.stringify(merged));
|
|
66
|
+
process.exit(merged.some((entry) => entry.messages?.length > 0) ? 1 : 0);
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# health-pytest <pytest args...>: python -m pytest in the repository's .venv. When the environment has no
|
|
3
|
+
# pytest or pytest-cov, they are installed offline from the wheels baked into the image (exit 127 if that fails).
|
|
4
|
+
exec health-venv sh -c '
|
|
5
|
+
python -c "import pytest, pytest_cov" 2>/dev/null || {
|
|
6
|
+
[ -n "$VIRTUAL_ENV" ] || { python -m venv .venv && . .venv/bin/activate; } || exit 127
|
|
7
|
+
pip install --no-index --find-links /opt/health/wheels pytest pytest-cov >/dev/null || exit 127
|
|
8
|
+
}
|
|
9
|
+
exec python -m pytest "$@"
|
|
10
|
+
' sh "$@"
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# health-tsc <tsc args...>: the repository's installed tsc (nearest node_modules/.bin/tsc), else the image's.
|
|
3
|
+
d="$PWD"
|
|
4
|
+
while [ "$d" != "/" ]; do
|
|
5
|
+
if [ -x "$d/node_modules/.bin/tsc" ]; then
|
|
6
|
+
exec "$d/node_modules/.bin/tsc" "$@"
|
|
7
|
+
fi
|
|
8
|
+
d=$(dirname "$d")
|
|
9
|
+
done
|
|
10
|
+
exec tsc "$@"
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# health-venv <command...>: run the command with the nearest .venv (current directory or above) activated.
|
|
3
|
+
d="$PWD"
|
|
4
|
+
while [ "$d" != "/" ]; do
|
|
5
|
+
if [ -f "$d/.venv/bin/activate" ]; then
|
|
6
|
+
. "$d/.venv/bin/activate"
|
|
7
|
+
break
|
|
8
|
+
fi
|
|
9
|
+
d=$(dirname "$d")
|
|
10
|
+
done
|
|
11
|
+
exec "$@"
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// The methodology's 2.5 ESLint ruleset, shared by eslint.config.mjs (typed) and eslint.untyped.config.mjs (no type information).
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
import tseslint from 'typescript-eslint';
|
|
4
|
+
import reactHooks from 'eslint-plugin-react-hooks';
|
|
5
|
+
|
|
6
|
+
function dependsOnReact() {
|
|
7
|
+
try {
|
|
8
|
+
const pkg = JSON.parse(readFileSync('package.json', 'utf8'));
|
|
9
|
+
return Boolean({ ...pkg.dependencies, ...pkg.devDependencies, ...pkg.peerDependencies }.react);
|
|
10
|
+
} catch {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const core = {
|
|
16
|
+
'no-unreachable': 'error',
|
|
17
|
+
'no-dupe-keys': 'error',
|
|
18
|
+
'no-self-compare': 'error',
|
|
19
|
+
'no-unsafe-finally': 'error',
|
|
20
|
+
'no-fallthrough': 'error',
|
|
21
|
+
'no-cond-assign': 'error',
|
|
22
|
+
'use-isnan': 'error',
|
|
23
|
+
'valid-typeof': 'error',
|
|
24
|
+
'no-unsafe-optional-chaining': 'error',
|
|
25
|
+
'no-dupe-else-if': 'error',
|
|
26
|
+
'no-duplicate-case': 'error',
|
|
27
|
+
'no-self-assign': 'error',
|
|
28
|
+
'getter-return': 'error',
|
|
29
|
+
'no-async-promise-executor': 'error',
|
|
30
|
+
'no-constant-binary-expression': 'error',
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const typed = ['**/*.ts', '**/*.tsx', '**/*.mts', '**/*.cts'];
|
|
34
|
+
|
|
35
|
+
/** `typeAware: false` is the second pass for TypeScript files outside every tsconfig: no projectService, no type-aware rules. */
|
|
36
|
+
export function buildConfig({ typeAware }) {
|
|
37
|
+
return [
|
|
38
|
+
{ ignores: ['**/node_modules/**', '**/dist/**', '**/build/**'] },
|
|
39
|
+
{
|
|
40
|
+
files: ['**/*.{js,jsx,mjs,cjs,ts,tsx,mts,cts}'],
|
|
41
|
+
languageOptions: { parser: tseslint.parser, parserOptions: { ecmaFeatures: { jsx: true } } },
|
|
42
|
+
linterOptions: { reportUnusedDisableDirectives: 'off', noInlineConfig: true },
|
|
43
|
+
rules: { ...core, 'no-unused-vars': 'error' },
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
files: typed,
|
|
47
|
+
...(typeAware ? { languageOptions: { parserOptions: { projectService: true, tsconfigRootDir: process.cwd() } } } : {}),
|
|
48
|
+
plugins: { '@typescript-eslint': tseslint.plugin },
|
|
49
|
+
rules: {
|
|
50
|
+
'no-unused-vars': 'off',
|
|
51
|
+
'@typescript-eslint/no-unused-vars': 'error',
|
|
52
|
+
...(typeAware
|
|
53
|
+
? {
|
|
54
|
+
'@typescript-eslint/no-floating-promises': 'error',
|
|
55
|
+
'@typescript-eslint/no-misused-promises': 'error',
|
|
56
|
+
'@typescript-eslint/await-thenable': 'error',
|
|
57
|
+
}
|
|
58
|
+
: {}),
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
...(dependsOnReact()
|
|
62
|
+
? [{ files: ['**/*.{js,jsx,ts,tsx}'], plugins: { 'react-hooks': reactHooks }, rules: { 'react-hooks/rules-of-hooks': 'error' } }]
|
|
63
|
+
: []),
|
|
64
|
+
];
|
|
65
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# The methodology's 2.5 Python ruleset: F (Pyflakes), B (flake8-bugbear), E9 (syntax and IO errors).
|
|
2
|
+
# Used with `ruff check --config`, so a repository's own ruff config is never read.
|
|
3
|
+
target-version = "py312"
|
|
4
|
+
line-length = 88
|
|
5
|
+
|
|
6
|
+
[lint]
|
|
7
|
+
select = ["F", "B", "E9"]
|