vibecodedshit 0.0.0-stage → 0.3.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 +73 -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 +11 -0
- package/dist/card/previous.js +38 -0
- package/dist/card/render.js +71 -0
- package/dist/cli.js +138 -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 +28 -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/interactive/after-scan.js +121 -0
- package/dist/interactive/docker-setup.js +97 -0
- package/dist/interactive/prompt.js +17 -0
- package/dist/interactive/system.js +10 -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 +10 -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,300 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { ciCommands } from './ci.js';
|
|
3
|
+
import { entryPointKind } from './entry-points/index.js';
|
|
4
|
+
const SKIPPED_WORDS = new Set(['sudo', 'time', 'exec', 'env', 'command', 'nohup']);
|
|
5
|
+
export const RUNNER_PREFIXES = [
|
|
6
|
+
['pnpm', 'exec'], ['pnpm', 'dlx'], ['yarn', 'exec'], ['yarn', 'dlx'], ['uv', 'run'], ['poetry', 'run'], ['pdm', 'run'],
|
|
7
|
+
['hatch', 'run'], ['pipx', 'run'], ['python', '-m'], ['python3', '-m'], ['npx'], ['bunx'], ['yarn'], ['pnpm'],
|
|
8
|
+
];
|
|
9
|
+
/** Split on newline, `&&`, `||`, `;` and `|` outside quotes. */
|
|
10
|
+
export function splitSimple(text) {
|
|
11
|
+
const out = [];
|
|
12
|
+
let current = '';
|
|
13
|
+
let quote = null;
|
|
14
|
+
for (let i = 0; i < text.length; i += 1) {
|
|
15
|
+
const ch = text[i];
|
|
16
|
+
if (quote !== null) {
|
|
17
|
+
if (ch === quote)
|
|
18
|
+
quote = null;
|
|
19
|
+
current += ch;
|
|
20
|
+
}
|
|
21
|
+
else if (ch === '"' || ch === "'") {
|
|
22
|
+
quote = ch;
|
|
23
|
+
current += ch;
|
|
24
|
+
}
|
|
25
|
+
else if (ch === '\\' && text[i + 1] === '\n') {
|
|
26
|
+
i += 1;
|
|
27
|
+
}
|
|
28
|
+
else if ((ch === '&' && text[i + 1] === '&') || (ch === '|' && text[i + 1] === '|')) {
|
|
29
|
+
out.push({ text: current, sep: ch + ch });
|
|
30
|
+
current = '';
|
|
31
|
+
i += 1;
|
|
32
|
+
}
|
|
33
|
+
else if (ch === ';' || ch === '|' || ch === '\n') {
|
|
34
|
+
out.push({ text: current, sep: ch });
|
|
35
|
+
current = '';
|
|
36
|
+
}
|
|
37
|
+
else
|
|
38
|
+
current += ch;
|
|
39
|
+
}
|
|
40
|
+
out.push({ text: current, sep: '' });
|
|
41
|
+
return out.filter((s) => s.text.trim() !== '');
|
|
42
|
+
}
|
|
43
|
+
export function tokenize(text) {
|
|
44
|
+
const tokens = [];
|
|
45
|
+
const re = /"([^"]*)"|'([^']*)'|(\S+)/g;
|
|
46
|
+
for (let m = re.exec(text); m !== null; m = re.exec(text))
|
|
47
|
+
tokens.push(m[1] ?? m[2] ?? m[3] ?? '');
|
|
48
|
+
return tokens;
|
|
49
|
+
}
|
|
50
|
+
function stripWrappers(tokens) {
|
|
51
|
+
let rest = tokens;
|
|
52
|
+
for (;;) {
|
|
53
|
+
const first = rest[0];
|
|
54
|
+
if (first === undefined)
|
|
55
|
+
return rest;
|
|
56
|
+
if (/^[A-Za-z_][A-Za-z0-9_]*=/.test(first) || SKIPPED_WORDS.has(first)) {
|
|
57
|
+
rest = rest.slice(1);
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
return rest;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
function stripRunners(tokens) {
|
|
64
|
+
let rest = tokens;
|
|
65
|
+
for (;;) {
|
|
66
|
+
const prefix = RUNNER_PREFIXES.find((p) => p.every((word, i) => rest[i] === word));
|
|
67
|
+
if (!prefix)
|
|
68
|
+
return rest;
|
|
69
|
+
rest = rest.slice(prefix.length);
|
|
70
|
+
while (rest[0]?.startsWith('-'))
|
|
71
|
+
rest = rest.slice(1);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/** Flags that take a value, for the runners whose first plain argument is the script or target name. */
|
|
75
|
+
const VALUE_FLAGS = new Set(['--prefix', '-C', '--cwd', '--filter', '-F', '--workspace', '--directory']);
|
|
76
|
+
function joinDir(dir, next) {
|
|
77
|
+
return next === undefined ? dir : path.posix.normalize(path.posix.join(dir, next));
|
|
78
|
+
}
|
|
79
|
+
function detectPackageManager(base, tokens, dir) {
|
|
80
|
+
let callDir = dir;
|
|
81
|
+
const args = [];
|
|
82
|
+
for (let i = 1; i < tokens.length; i += 1) {
|
|
83
|
+
const token = tokens[i];
|
|
84
|
+
if (token === '--prefix' || token === '-C' || token === '--cwd') {
|
|
85
|
+
callDir = joinDir(dir, tokens[i + 1]);
|
|
86
|
+
i += 1;
|
|
87
|
+
}
|
|
88
|
+
else if (token.startsWith('--prefix='))
|
|
89
|
+
callDir = joinDir(dir, token.slice(9));
|
|
90
|
+
else if (VALUE_FLAGS.has(token))
|
|
91
|
+
i += 1;
|
|
92
|
+
else if (token.startsWith('-'))
|
|
93
|
+
continue;
|
|
94
|
+
else
|
|
95
|
+
args.push(token);
|
|
96
|
+
}
|
|
97
|
+
let name;
|
|
98
|
+
let fallback = false;
|
|
99
|
+
if (base === 'npm') {
|
|
100
|
+
if (args[0] === 'run' || args[0] === 'run-script')
|
|
101
|
+
name = args[1];
|
|
102
|
+
else if (args[0] === 'test' || args[0] === 'start')
|
|
103
|
+
name = args[0];
|
|
104
|
+
}
|
|
105
|
+
else if (base === 'bun') {
|
|
106
|
+
if (args[0] === 'run')
|
|
107
|
+
name = args[1];
|
|
108
|
+
}
|
|
109
|
+
else if (args[0] === 'run')
|
|
110
|
+
name = args[1];
|
|
111
|
+
else if (args[0] !== undefined && !(base === 'pnpm' && (args[0] === 'exec' || args[0] === 'dlx')) && !(base === 'yarn' && (args[0] === 'exec' || args[0] === 'dlx'))) {
|
|
112
|
+
name = args[0];
|
|
113
|
+
fallback = true;
|
|
114
|
+
}
|
|
115
|
+
if (name === undefined)
|
|
116
|
+
return undefined;
|
|
117
|
+
return { runner: base, kind: 'package-json', names: [name], dir: callDir, prePost: base === 'npm' || base === 'yarn', fallback };
|
|
118
|
+
}
|
|
119
|
+
function detectMake(base, tokens, dir) {
|
|
120
|
+
let callDir = dir;
|
|
121
|
+
const targets = [];
|
|
122
|
+
for (let i = 1; i < tokens.length; i += 1) {
|
|
123
|
+
const token = tokens[i];
|
|
124
|
+
if (token === '-C' || token === '--directory') {
|
|
125
|
+
callDir = joinDir(dir, tokens[i + 1]);
|
|
126
|
+
i += 1;
|
|
127
|
+
}
|
|
128
|
+
else if (token === '-f' || token === '--file')
|
|
129
|
+
i += 1;
|
|
130
|
+
else if (token.startsWith('-') || token.includes('='))
|
|
131
|
+
continue;
|
|
132
|
+
else
|
|
133
|
+
targets.push(token);
|
|
134
|
+
}
|
|
135
|
+
if (targets.length === 0)
|
|
136
|
+
return undefined;
|
|
137
|
+
return { runner: base, kind: 'makefile', names: targets, dir: callDir, prePost: false, fallback: false };
|
|
138
|
+
}
|
|
139
|
+
function detectJust(tokens, dir) {
|
|
140
|
+
let callDir = dir;
|
|
141
|
+
const names = [];
|
|
142
|
+
for (let i = 1; i < tokens.length; i += 1) {
|
|
143
|
+
const token = tokens[i];
|
|
144
|
+
if (token === '-d' || token === '--working-directory') {
|
|
145
|
+
callDir = joinDir(dir, tokens[i + 1]);
|
|
146
|
+
i += 1;
|
|
147
|
+
}
|
|
148
|
+
else if (token === '-f' || token === '--justfile')
|
|
149
|
+
i += 1;
|
|
150
|
+
else if (token.startsWith('--justfile=') || token.startsWith('--working-directory='))
|
|
151
|
+
continue;
|
|
152
|
+
else if (token.startsWith('-') || token.includes('='))
|
|
153
|
+
continue;
|
|
154
|
+
else
|
|
155
|
+
names.push(token);
|
|
156
|
+
}
|
|
157
|
+
return { runner: 'just', kind: 'justfile', names: names.length === 0 ? ['@default'] : names, dir: callDir, prePost: false, fallback: false };
|
|
158
|
+
}
|
|
159
|
+
function detectTox(tokens, dir) {
|
|
160
|
+
const names = [];
|
|
161
|
+
for (let i = 1; i < tokens.length; i += 1) {
|
|
162
|
+
const token = tokens[i];
|
|
163
|
+
if (token === '--')
|
|
164
|
+
break;
|
|
165
|
+
if (token === '-e') {
|
|
166
|
+
names.push(...(tokens[i + 1] ?? '').split(','));
|
|
167
|
+
i += 1;
|
|
168
|
+
}
|
|
169
|
+
else if (token.startsWith('-e') && token.length > 2)
|
|
170
|
+
names.push(...token.slice(2).split(','));
|
|
171
|
+
}
|
|
172
|
+
return { runner: 'tox', kind: 'tox', names: names.length === 0 ? ['@default'] : names.filter((n) => n !== ''), dir, prePost: false, fallback: false };
|
|
173
|
+
}
|
|
174
|
+
function detectNox(tokens, dir) {
|
|
175
|
+
const names = [];
|
|
176
|
+
for (let i = 1; i < tokens.length; i += 1) {
|
|
177
|
+
const token = tokens[i];
|
|
178
|
+
if (token === '--')
|
|
179
|
+
break;
|
|
180
|
+
if (token === '-s' || token === '--session' || token === '-e' || token === '--sessions') {
|
|
181
|
+
while (tokens[i + 1] !== undefined && !tokens[i + 1].startsWith('-')) {
|
|
182
|
+
names.push(...tokens[i + 1].split(','));
|
|
183
|
+
i += 1;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return { runner: 'nox', kind: 'nox', names: names.length === 0 ? ['@all'] : names.filter((n) => n !== ''), dir, prePost: false, fallback: false };
|
|
188
|
+
}
|
|
189
|
+
/** `uv run <n>`, `poetry run <n>`, `pdm run <n>`, `hatch run [<env>:]<n>`, `rye run <n>`, `poe <n>` and `task <n>` name a script of a pyproject table. */
|
|
190
|
+
function detectPyproject(tokens, dir) {
|
|
191
|
+
const [first, second] = tokens;
|
|
192
|
+
const rest = (from) => tokens.slice(from).find((t) => !t.startsWith('-'));
|
|
193
|
+
const make = (runner, key, name, fallback) => name === undefined ? undefined : { runner, kind: 'pyproject', names: [key(name)], dir, prePost: false, fallback };
|
|
194
|
+
if ((first === 'uv' || first === 'poetry') && second === 'run')
|
|
195
|
+
return make(first, (n) => `scripts:${n}`, rest(2), true);
|
|
196
|
+
if (first === 'pdm' && second === 'run')
|
|
197
|
+
return make(first, (n) => `pdm:${n}`, rest(2), true);
|
|
198
|
+
if (first === 'hatch' && second === 'run')
|
|
199
|
+
return make(first, (n) => (n.includes(':') ? `hatch:${n}` : `hatch:default:${n}`), rest(2), true);
|
|
200
|
+
if (first === 'rye' && second === 'run')
|
|
201
|
+
return make(first, (n) => `rye:${n}`, rest(2), false);
|
|
202
|
+
if (first === 'poe')
|
|
203
|
+
return make(first, (n) => `poe:${n}`, rest(1), false);
|
|
204
|
+
if (first === 'task')
|
|
205
|
+
return make(first, (n) => `taskipy:${n}`, rest(1), false);
|
|
206
|
+
return undefined;
|
|
207
|
+
}
|
|
208
|
+
function detectCall(tokens, dir) {
|
|
209
|
+
const first = tokens[0];
|
|
210
|
+
if (first === undefined)
|
|
211
|
+
return undefined;
|
|
212
|
+
const base = path.posix.basename(first);
|
|
213
|
+
if (base === 'npm' || base === 'yarn' || base === 'pnpm' || base === 'bun')
|
|
214
|
+
return detectPackageManager(base, tokens, dir);
|
|
215
|
+
if (base === 'make' || first === '$(MAKE)' || first === '${MAKE}')
|
|
216
|
+
return detectMake('make', tokens, dir);
|
|
217
|
+
if (base === 'just')
|
|
218
|
+
return detectJust(tokens, dir);
|
|
219
|
+
if (base === 'tox')
|
|
220
|
+
return detectTox(tokens, dir);
|
|
221
|
+
if (base === 'nox')
|
|
222
|
+
return detectNox(tokens, dir);
|
|
223
|
+
return detectPyproject(tokens, dir);
|
|
224
|
+
}
|
|
225
|
+
function expandText(repoPath, text, frame, maxDepth, out) {
|
|
226
|
+
let dir = frame.dir;
|
|
227
|
+
for (const simple of splitSimple(text)) {
|
|
228
|
+
const tokens = stripWrappers(tokenize(simple.text));
|
|
229
|
+
if (tokens.length === 0)
|
|
230
|
+
continue;
|
|
231
|
+
if (tokens[0] === 'cd' && tokens[1] !== undefined && !/[$`*?]/.test(tokens[1])) {
|
|
232
|
+
if (simple.sep === '&&')
|
|
233
|
+
dir = path.posix.normalize(path.posix.join(dir, tokens[1]));
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
const call = detectCall(tokens, dir);
|
|
237
|
+
if (call && follow(repoPath, call, tokens, frame, maxDepth, out))
|
|
238
|
+
continue;
|
|
239
|
+
const stripped = stripRunners(tokens);
|
|
240
|
+
const [tool, ...argv] = stripped;
|
|
241
|
+
out.push({ tool: tool === undefined ? null : path.posix.basename(tool), argv, file: frame.file, chain: frame.chain, depth: frame.chain.length });
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
/** Replace a call by what it runs. False when the caller should read the command another way (runner prefix removed). */
|
|
245
|
+
function follow(repoPath, call, tokens, frame, maxDepth, out) {
|
|
246
|
+
const kind = entryPointKind(call.kind);
|
|
247
|
+
const plain = () => {
|
|
248
|
+
if (call.fallback && frame.chain.length < maxDepth)
|
|
249
|
+
return false;
|
|
250
|
+
const readable = kind?.fileFor === undefined ? undefined : kind.fileFor(repoPath, call.dir) !== undefined;
|
|
251
|
+
out.push({
|
|
252
|
+
tool: call.runner,
|
|
253
|
+
argv: tokens.slice(1),
|
|
254
|
+
file: frame.file,
|
|
255
|
+
chain: frame.chain,
|
|
256
|
+
depth: frame.chain.length,
|
|
257
|
+
...(readable === undefined ? {} : { readable }),
|
|
258
|
+
});
|
|
259
|
+
return true;
|
|
260
|
+
};
|
|
261
|
+
if (!kind || frame.chain.length >= maxDepth)
|
|
262
|
+
return plain();
|
|
263
|
+
let followed = false;
|
|
264
|
+
for (const name of call.names) {
|
|
265
|
+
const variants = call.prePost ? [`pre${name}`, name, `post${name}`] : [name];
|
|
266
|
+
for (const variant of variants) {
|
|
267
|
+
const found = kind.lookup(repoPath, call.dir, variant);
|
|
268
|
+
const key = found ? `${found.file}:${variant}` : '';
|
|
269
|
+
if (!found || frame.seen.has(key))
|
|
270
|
+
continue;
|
|
271
|
+
followed = true;
|
|
272
|
+
const next = {
|
|
273
|
+
dir: path.posix.dirname(found.file),
|
|
274
|
+
file: found.file,
|
|
275
|
+
chain: [...frame.chain, key],
|
|
276
|
+
seen: new Set([...frame.seen, key]),
|
|
277
|
+
};
|
|
278
|
+
for (const body of found.bodies)
|
|
279
|
+
expandText(repoPath, body, next, maxDepth, out);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
return followed || plain();
|
|
283
|
+
}
|
|
284
|
+
/** The commands a text runs, calls to scripts and make targets replaced by what they run (up to `maxDepth` bodies deep, default 3). */
|
|
285
|
+
export function expandCommand(repoPath, text, from, opts = {}) {
|
|
286
|
+
const out = [];
|
|
287
|
+
expandText(repoPath, text, { dir: from.dir, file: from.file, chain: [], seen: new Set() }, opts.maxDepth ?? 3, out);
|
|
288
|
+
return out;
|
|
289
|
+
}
|
|
290
|
+
/** Every CI command whose expansion reaches one of `tools`. */
|
|
291
|
+
export function ciInvokes(repoPath, tools, opts = {}) {
|
|
292
|
+
const hits = [];
|
|
293
|
+
for (const command of ciCommands(repoPath, opts.warnings)) {
|
|
294
|
+
const resolved = expandCommand(repoPath, command.command, { dir: command.dir, file: command.file }, opts);
|
|
295
|
+
const match = resolved.find((r) => r.tool !== null && tools.includes(r.tool));
|
|
296
|
+
if (match)
|
|
297
|
+
hits.push({ file: command.file, job: command.job, chain: match.chain });
|
|
298
|
+
}
|
|
299
|
+
return hits;
|
|
300
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
const isObj = (value) => value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
4
|
+
const list = (value) => (typeof value === 'string' ? [value] : Array.isArray(value) ? value.filter((v) => typeof v === 'string') : []);
|
|
5
|
+
const defaultCache = new Map();
|
|
6
|
+
/** The target of `origin/HEAD` with `origin/` removed (when present), plus `main` and `master`. */
|
|
7
|
+
function defaultBranchNames(repoPath) {
|
|
8
|
+
const key = path.resolve(repoPath);
|
|
9
|
+
const cached = defaultCache.get(key);
|
|
10
|
+
if (cached)
|
|
11
|
+
return cached;
|
|
12
|
+
const result = spawnSync('git', ['-C', key, 'symbolic-ref', '--short', 'refs/remotes/origin/HEAD'], { encoding: 'utf8' });
|
|
13
|
+
const head = result.error || result.status !== 0 ? '' : result.stdout.trim().replace(/^origin\//, '');
|
|
14
|
+
const names = [...new Set([...(head === '' ? [] : [head]), 'main', 'master'])];
|
|
15
|
+
defaultCache.set(key, names);
|
|
16
|
+
return names;
|
|
17
|
+
}
|
|
18
|
+
export function resetDefaultBranchCache() {
|
|
19
|
+
defaultCache.clear();
|
|
20
|
+
}
|
|
21
|
+
const yes = (why) => ({ qualifies: true, why });
|
|
22
|
+
const no = (why) => ({ qualifies: false, why });
|
|
23
|
+
function github(doc, defaults) {
|
|
24
|
+
const on = doc.on ?? doc.true;
|
|
25
|
+
const events = new Map();
|
|
26
|
+
if (typeof on === 'string')
|
|
27
|
+
events.set(on, null);
|
|
28
|
+
else if (Array.isArray(on))
|
|
29
|
+
for (const e of list(on))
|
|
30
|
+
events.set(e, null);
|
|
31
|
+
else if (isObj(on))
|
|
32
|
+
for (const [event, value] of Object.entries(on))
|
|
33
|
+
events.set(event, value);
|
|
34
|
+
for (const event of ['pull_request', 'pull_request_target'])
|
|
35
|
+
if (events.has(event))
|
|
36
|
+
return yes(event);
|
|
37
|
+
if (events.has('push')) {
|
|
38
|
+
const push = events.get('push');
|
|
39
|
+
return pushFilter(isObj(push) ? push : {}, defaults);
|
|
40
|
+
}
|
|
41
|
+
return no(events.size === 0 ? 'no triggers' : `only ${[...events.keys()].join(', ')}`);
|
|
42
|
+
}
|
|
43
|
+
function pushFilter(push, defaults) {
|
|
44
|
+
if (push['branches-ignore'] !== undefined)
|
|
45
|
+
return yes('push, branches-ignore');
|
|
46
|
+
if (push.branches !== undefined) {
|
|
47
|
+
const branches = list(push.branches);
|
|
48
|
+
return branches.length === 0 || branches.some((b) => !defaults.includes(b)) ? yes('push to non-default branches') : no('push limited to the default branch');
|
|
49
|
+
}
|
|
50
|
+
if (push.tags !== undefined || push['tags-ignore'] !== undefined)
|
|
51
|
+
return no('push of tags only');
|
|
52
|
+
return yes('push, no filter');
|
|
53
|
+
}
|
|
54
|
+
function limitedToDefault(expression, defaults) {
|
|
55
|
+
if (/merge_request_event|CI_MERGE_REQUEST/.test(expression))
|
|
56
|
+
return false;
|
|
57
|
+
const names = defaults.map((d) => d.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|');
|
|
58
|
+
return new RegExp(`^\\s*\\$CI_(?:COMMIT_BRANCH|COMMIT_REF_NAME)\\s*==\\s*(["']?)(?:\\$CI_DEFAULT_BRANCH|${names})\\1\\s*$`).test(expression);
|
|
59
|
+
}
|
|
60
|
+
function gitlab(doc, jobName, defaults) {
|
|
61
|
+
const job = doc[jobName];
|
|
62
|
+
if (!isObj(job))
|
|
63
|
+
return yes('not a job table');
|
|
64
|
+
if (Array.isArray(job.rules)) {
|
|
65
|
+
for (const rule of job.rules) {
|
|
66
|
+
if (!isObj(rule))
|
|
67
|
+
continue;
|
|
68
|
+
if (typeof rule.if === 'string') {
|
|
69
|
+
if (rule.when === 'never')
|
|
70
|
+
continue;
|
|
71
|
+
if (!limitedToDefault(rule.if, defaults))
|
|
72
|
+
return yes('rule not limited to the default branch');
|
|
73
|
+
}
|
|
74
|
+
else if (rule.when !== 'never')
|
|
75
|
+
return yes('rule without a condition');
|
|
76
|
+
}
|
|
77
|
+
return no('rules limited to the default branch');
|
|
78
|
+
}
|
|
79
|
+
const only = job.only;
|
|
80
|
+
if (only !== undefined) {
|
|
81
|
+
const refs = isObj(only) ? list(only.refs) : list(only);
|
|
82
|
+
if (refs.some((r) => r === 'merge_requests' || r === 'branches'))
|
|
83
|
+
return yes('only merge_requests or branches');
|
|
84
|
+
return refs.length === 0 || refs.some((r) => !defaults.includes(r)) ? yes('only other refs') : no('only the default branch');
|
|
85
|
+
}
|
|
86
|
+
return yes('no rules or only: every branch push');
|
|
87
|
+
}
|
|
88
|
+
function circleci(doc, jobName, defaults) {
|
|
89
|
+
if (!isObj(doc.workflows))
|
|
90
|
+
return yes('no workflows key');
|
|
91
|
+
for (const [name, workflow] of Object.entries(doc.workflows)) {
|
|
92
|
+
if (!isObj(workflow))
|
|
93
|
+
continue;
|
|
94
|
+
const triggers = Array.isArray(workflow.triggers) ? workflow.triggers : [];
|
|
95
|
+
if (triggers.some((t) => isObj(t) && t.schedule !== undefined))
|
|
96
|
+
continue;
|
|
97
|
+
for (const entry of Array.isArray(workflow.jobs) ? workflow.jobs : []) {
|
|
98
|
+
const [key, value] = typeof entry === 'string' ? [entry, null] : isObj(entry) ? (Object.entries(entry)[0] ?? ['', null]) : ['', null];
|
|
99
|
+
const alias = isObj(value) && typeof value.name === 'string' ? value.name : key;
|
|
100
|
+
if (key !== jobName && alias !== jobName)
|
|
101
|
+
continue;
|
|
102
|
+
const only = isObj(value) && isObj(value.filters) && isObj(value.filters.branches) ? list(value.filters.branches.only) : [];
|
|
103
|
+
if (only.length === 0 || only.some((b) => !defaults.includes(b)))
|
|
104
|
+
return yes(`workflow ${name}`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return no('in no qualifying workflow');
|
|
108
|
+
}
|
|
109
|
+
function bitbucket(section, doc, defaults) {
|
|
110
|
+
if (section === 'default' || section === 'pull-requests')
|
|
111
|
+
return yes(section);
|
|
112
|
+
if (section === 'branches') {
|
|
113
|
+
const pipelines = isObj(doc.pipelines) ? doc.pipelines : {};
|
|
114
|
+
const keys = isObj(pipelines.branches) ? Object.keys(pipelines.branches) : [];
|
|
115
|
+
return keys.length === 0 || keys.some((k) => !defaults.includes(k)) ? yes('branches other than the default') : no('branches limited to the default branch');
|
|
116
|
+
}
|
|
117
|
+
return no(`${section} pipelines`);
|
|
118
|
+
}
|
|
119
|
+
function azure(doc, defaults) {
|
|
120
|
+
const pr = doc.pr;
|
|
121
|
+
if (pr !== undefined && pr !== 'none' && pr !== null)
|
|
122
|
+
return yes('pr trigger');
|
|
123
|
+
const trigger = doc.trigger;
|
|
124
|
+
if (trigger === undefined)
|
|
125
|
+
return yes('no trigger: CI on all branches');
|
|
126
|
+
if (trigger === 'none')
|
|
127
|
+
return no('trigger: none');
|
|
128
|
+
const include = isObj(trigger) ? (isObj(trigger.branches) ? list(trigger.branches.include) : list(trigger.include)) : list(trigger);
|
|
129
|
+
return include.length === 0 || include.some((b) => !defaults.includes(b)) ? yes('trigger on other branches') : no('trigger limited to the default branch');
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Whether a CI job runs on pull requests, merge requests or non-default pushes. A job is excluded only when it is positively
|
|
133
|
+
* identified as not triggered that way; a filter that cannot be read statically qualifies.
|
|
134
|
+
*/
|
|
135
|
+
export function triggersFor(format, doc, jobName, repoPath) {
|
|
136
|
+
if (!isObj(doc))
|
|
137
|
+
return yes('unreadable');
|
|
138
|
+
const defaults = defaultBranchNames(repoPath);
|
|
139
|
+
switch (format) {
|
|
140
|
+
case 'github':
|
|
141
|
+
return github(doc, defaults);
|
|
142
|
+
case 'gitlab':
|
|
143
|
+
return gitlab(doc, jobName, [...defaults, '$CI_DEFAULT_BRANCH']);
|
|
144
|
+
case 'circleci':
|
|
145
|
+
return circleci(doc, jobName, defaults);
|
|
146
|
+
case 'bitbucket':
|
|
147
|
+
return bitbucket(jobName, doc, defaults);
|
|
148
|
+
case 'azure':
|
|
149
|
+
return azure(doc, defaults);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export const SUBMIT_URL = 'https://app.vibecodedshit.com/api/submissions';
|
|
2
|
+
export const PRIVACY_URL = 'https://app.vibecodedshit.com/privacy';
|
|
3
|
+
export const QUOTE_URL = 'https://app.vibecodedshit.com/api/quote-requests';
|
|
4
|
+
/** The server rejects larger bodies. */
|
|
5
|
+
export const MAX_PAYLOAD_BYTES = 65536;
|
|
6
|
+
/** The server rejects longer summaries. */
|
|
7
|
+
export const MAX_SUMMARY_CHARS = 4000;
|
|
8
|
+
export const SEND_TIMEOUT_MS = 15000;
|
|
9
|
+
export const EMAIL_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
10
|
+
export const MAX_EMAIL_CHARS = 254;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export class PayloadError extends Error {
|
|
2
|
+
constructor(message) {
|
|
3
|
+
super(message);
|
|
4
|
+
this.name = 'PayloadError';
|
|
5
|
+
}
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Build the payload from an allowlist of run.json fields, never by spreading the run.
|
|
9
|
+
* `units`, `breakdown`, `repo`, `warnings`, reasons, `scope` and `ignoreSet` hold paths or local information and are left out.
|
|
10
|
+
* Values are copied unchanged; nothing is computed or rounded.
|
|
11
|
+
*/
|
|
12
|
+
export function buildPayload({ run, repoHash, repoName, summary, email, newsletter }) {
|
|
13
|
+
const digest = run.tools.image?.digest;
|
|
14
|
+
if (!run.health)
|
|
15
|
+
throw new PayloadError('run.json has no health score.');
|
|
16
|
+
if (!digest)
|
|
17
|
+
throw new PayloadError('run.json has no execution image digest (tools.image.digest).');
|
|
18
|
+
const dimensions = run.dimensions.map((d) => {
|
|
19
|
+
if (d.score === null)
|
|
20
|
+
throw new PayloadError(`dimension ${d.id} has no score.`);
|
|
21
|
+
return { id: d.id, score: d.score };
|
|
22
|
+
});
|
|
23
|
+
return {
|
|
24
|
+
repoHash,
|
|
25
|
+
...(repoName === undefined ? {} : { repoName }),
|
|
26
|
+
methodologyVersion: run.methodology.version,
|
|
27
|
+
imageDigest: digest,
|
|
28
|
+
asOf: run.asOf,
|
|
29
|
+
health: run.health.displayed,
|
|
30
|
+
band: run.health.band,
|
|
31
|
+
dimensions,
|
|
32
|
+
parameters: run.parameters.map((p) => ({
|
|
33
|
+
id: p.id,
|
|
34
|
+
status: p.status,
|
|
35
|
+
...(p.raw === undefined || p.raw === null ? {} : { raw: p.raw }),
|
|
36
|
+
...(p.score === undefined ? {} : { score: p.score }),
|
|
37
|
+
})),
|
|
38
|
+
...(summary === undefined ? {} : { summary }),
|
|
39
|
+
email,
|
|
40
|
+
...(newsletter ? { newsletter: true } : {}),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
export class RepoIdentityError extends Error {
|
|
4
|
+
constructor() {
|
|
5
|
+
super('cannot determine the repository identity (run.json repo.path is missing or not a git repository)');
|
|
6
|
+
this.name = 'RepoIdentityError';
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Reduce a remote URL to `host/owner/repo`: no scheme, credentials, port, `.git` suffix or trailing slash, lowercase.
|
|
11
|
+
* The SSH and HTTPS forms of one remote give the same string.
|
|
12
|
+
*/
|
|
13
|
+
export function normalizeRemote(url) {
|
|
14
|
+
const trimmed = url.trim();
|
|
15
|
+
const schemed = /^[a-z][a-z0-9+.-]*:\/\/(?:[^@/]*@)?([^/:]+)(?::\d+)?\/(.+)$/i.exec(trimmed);
|
|
16
|
+
const scp = /^(?:[^@/:]+@)?([^:/]+):(.+)$/.exec(trimmed);
|
|
17
|
+
const match = schemed ?? scp;
|
|
18
|
+
const joined = match ? `${match[1]}/${match[2]}` : trimmed;
|
|
19
|
+
return joined.replace(/\/+$/, '').replace(/\.git$/i, '').replace(/\/+$/, '').toLowerCase();
|
|
20
|
+
}
|
|
21
|
+
const sha256 = (text) => createHash('sha256').update(text, 'utf8').digest('hex');
|
|
22
|
+
export async function repoIdentity(repoPath, git) {
|
|
23
|
+
if (!repoPath)
|
|
24
|
+
throw new RepoIdentityError();
|
|
25
|
+
const remotes = await git(repoPath, ['remote']);
|
|
26
|
+
if (remotes === null)
|
|
27
|
+
throw new RepoIdentityError();
|
|
28
|
+
const names = remotes.split('\n').map((n) => n.trim()).filter(Boolean);
|
|
29
|
+
const remote = names.includes('origin') ? 'origin' : names[0];
|
|
30
|
+
if (remote !== undefined) {
|
|
31
|
+
const url = (await git(repoPath, ['remote', 'get-url', remote]))?.trim();
|
|
32
|
+
if (url) {
|
|
33
|
+
const normalised = normalizeRemote(url);
|
|
34
|
+
return { hash: sha256(normalised), name: normalised.split('/').slice(1).join('/') || normalised };
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
const roots = await git(repoPath, ['rev-list', '--max-parents=0', 'HEAD']);
|
|
38
|
+
const root = roots
|
|
39
|
+
?.split('\n')
|
|
40
|
+
.map((l) => l.trim().toLowerCase())
|
|
41
|
+
.filter(Boolean)
|
|
42
|
+
.sort()[0];
|
|
43
|
+
if (!root)
|
|
44
|
+
throw new RepoIdentityError();
|
|
45
|
+
return { hash: sha256(root), name: path.basename(repoPath) };
|
|
46
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { SEND_TIMEOUT_MS, SUBMIT_URL } from './constants.js';
|
|
2
|
+
function readRank(body) {
|
|
3
|
+
try {
|
|
4
|
+
const data = JSON.parse(body);
|
|
5
|
+
if (typeof data.rank !== 'number' || typeof data.total !== 'number')
|
|
6
|
+
return undefined;
|
|
7
|
+
return { rank: data.rank, total: data.total, top10Threshold: typeof data.top10Threshold === 'number' ? data.top10Threshold : null };
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
return undefined;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export async function sendPayload(payload, fetchImpl) {
|
|
14
|
+
try {
|
|
15
|
+
const response = await fetchImpl(SUBMIT_URL, {
|
|
16
|
+
method: 'POST',
|
|
17
|
+
headers: { 'content-type': 'application/json' },
|
|
18
|
+
body: JSON.stringify(payload),
|
|
19
|
+
signal: AbortSignal.timeout(SEND_TIMEOUT_MS),
|
|
20
|
+
});
|
|
21
|
+
const body = await response.text();
|
|
22
|
+
if (response.ok)
|
|
23
|
+
return { ok: true, rank: readRank(body) };
|
|
24
|
+
return { ok: false, message: `Server answered ${response.status}: ${body.slice(0, 300)}` };
|
|
25
|
+
}
|
|
26
|
+
catch (err) {
|
|
27
|
+
return { ok: false, message: `Could not send: ${err instanceof Error ? err.message : String(err)}` };
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export const catalog = {
|
|
2
|
+
jscpd: { kind: 'npm', pkg: 'jscpd', install: 'npm install (jscpd is a dependency of health)' },
|
|
3
|
+
knip: { kind: 'npm', pkg: 'knip', install: 'npm install (knip is a dependency of health)' },
|
|
4
|
+
complexipy: {
|
|
5
|
+
kind: 'path',
|
|
6
|
+
bin: 'complexipy',
|
|
7
|
+
versionArgs: ['--version'],
|
|
8
|
+
versionPattern: /(\d+\.\d+\.\d+\S*)/,
|
|
9
|
+
install: 'pipx install complexipy (or uv tool install complexipy)',
|
|
10
|
+
},
|
|
11
|
+
vulture: {
|
|
12
|
+
kind: 'path',
|
|
13
|
+
bin: 'vulture',
|
|
14
|
+
versionArgs: ['--version'],
|
|
15
|
+
versionPattern: /(\d+\.\d+\S*)/,
|
|
16
|
+
install: 'pipx install vulture (or uv tool install vulture)',
|
|
17
|
+
},
|
|
18
|
+
deptry: {
|
|
19
|
+
kind: 'path',
|
|
20
|
+
bin: 'deptry',
|
|
21
|
+
versionArgs: ['--version'],
|
|
22
|
+
versionPattern: /(\d+\.\d+\.\d+\S*)/,
|
|
23
|
+
install: 'pipx install deptry (or uv tool install deptry)',
|
|
24
|
+
},
|
|
25
|
+
'osv-scanner': {
|
|
26
|
+
kind: 'path',
|
|
27
|
+
bin: 'osv-scanner',
|
|
28
|
+
versionArgs: ['--version'],
|
|
29
|
+
versionPattern: /(\d+\.\d+\.\d+\S*)/,
|
|
30
|
+
install: 'brew install osv-scanner, or https://github.com/google/osv-scanner/releases',
|
|
31
|
+
},
|
|
32
|
+
gitleaks: {
|
|
33
|
+
kind: 'path',
|
|
34
|
+
bin: 'gitleaks',
|
|
35
|
+
versionArgs: ['version'],
|
|
36
|
+
versionPattern: /(\d+\.\d+\.\d+\S*)/,
|
|
37
|
+
install: 'brew install gitleaks, or https://github.com/gitleaks/gitleaks/releases',
|
|
38
|
+
},
|
|
39
|
+
'import-linter': {
|
|
40
|
+
kind: 'path',
|
|
41
|
+
bin: 'lint-imports',
|
|
42
|
+
versionArgs: ['--version'],
|
|
43
|
+
versionPattern: /import-linter (\d+(?:\.\d+)+)/,
|
|
44
|
+
install: 'pipx install import-linter (or uv tool install import-linter)',
|
|
45
|
+
},
|
|
46
|
+
};
|