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,139 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { parse } from 'yaml';
|
|
3
|
+
import { readTracked, trackedPaths } from '../scope/tracked.js';
|
|
4
|
+
import { triggersFor } from './triggers.js';
|
|
5
|
+
/** The five CI files of methodology 3.1 `ci_files`. */
|
|
6
|
+
function ciFormatOf(file) {
|
|
7
|
+
if (/^\.github\/workflows\/[^/]+\.ya?ml$/.test(file))
|
|
8
|
+
return 'github';
|
|
9
|
+
if (file === '.gitlab-ci.yml')
|
|
10
|
+
return 'gitlab';
|
|
11
|
+
if (file === '.circleci/config.yml')
|
|
12
|
+
return 'circleci';
|
|
13
|
+
if (file === 'bitbucket-pipelines.yml')
|
|
14
|
+
return 'bitbucket';
|
|
15
|
+
if (file === 'azure-pipelines.yml')
|
|
16
|
+
return 'azure';
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
const isObj = (value) => value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
20
|
+
function strings(value) {
|
|
21
|
+
if (typeof value === 'string')
|
|
22
|
+
return [value];
|
|
23
|
+
if (Array.isArray(value))
|
|
24
|
+
return value.flatMap(strings);
|
|
25
|
+
return [];
|
|
26
|
+
}
|
|
27
|
+
function workingDir(value) {
|
|
28
|
+
return typeof value === 'string' && value.trim() !== '' ? path.posix.normalize(value.trim().replace(/^\.\//, '')) : '.';
|
|
29
|
+
}
|
|
30
|
+
function withCd(command, dir) {
|
|
31
|
+
const match = /^\s*cd\s+([^\s&;|"'$`]+)\s*&&/.exec(command);
|
|
32
|
+
return match?.[1] ? path.posix.normalize(path.posix.join(dir, match[1])) : dir;
|
|
33
|
+
}
|
|
34
|
+
function collect(doc, file, format, repoPath, out) {
|
|
35
|
+
const triggers = new Map();
|
|
36
|
+
const push = (job, command, dir, triggerKey = job) => {
|
|
37
|
+
if (command.trim() === '')
|
|
38
|
+
return;
|
|
39
|
+
const key = triggerKey;
|
|
40
|
+
if (!triggers.has(key))
|
|
41
|
+
triggers.set(key, triggersFor(format, doc, key, repoPath));
|
|
42
|
+
out.push({ file, format, job, command, dir: withCd(command, dir), triggers: triggers.get(key) });
|
|
43
|
+
};
|
|
44
|
+
if (!isObj(doc))
|
|
45
|
+
return;
|
|
46
|
+
if (format === 'github' || format === 'circleci') {
|
|
47
|
+
const jobs = isObj(doc.jobs) ? doc.jobs : {};
|
|
48
|
+
for (const [name, job] of Object.entries(jobs)) {
|
|
49
|
+
if (!isObj(job))
|
|
50
|
+
continue;
|
|
51
|
+
const defaults = isObj(job.defaults) && isObj(job.defaults.run) ? workingDir(job.defaults.run['working-directory']) : '.';
|
|
52
|
+
const steps = Array.isArray(job.steps) ? job.steps : [];
|
|
53
|
+
for (const step of steps) {
|
|
54
|
+
if (format === 'github') {
|
|
55
|
+
if (!isObj(step) || typeof step.run !== 'string')
|
|
56
|
+
continue;
|
|
57
|
+
push(name, step.run, step['working-directory'] === undefined ? defaults : workingDir(step['working-directory']));
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
const run = isObj(step) ? step.run : undefined;
|
|
61
|
+
const command = typeof run === 'string' ? run : isObj(run) && typeof run.command === 'string' ? run.command : undefined;
|
|
62
|
+
if (command !== undefined)
|
|
63
|
+
push(name, command, '.');
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
else if (format === 'gitlab') {
|
|
69
|
+
for (const [name, value] of Object.entries(doc)) {
|
|
70
|
+
if (!isObj(value))
|
|
71
|
+
continue;
|
|
72
|
+
for (const key of ['before_script', 'script', 'after_script'])
|
|
73
|
+
for (const command of strings(value[key]))
|
|
74
|
+
push(name, command, '.');
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
else if (format === 'bitbucket') {
|
|
78
|
+
const walk = (node, name, section) => {
|
|
79
|
+
if (Array.isArray(node))
|
|
80
|
+
node.forEach((child) => walk(child, name, section));
|
|
81
|
+
else if (isObj(node)) {
|
|
82
|
+
for (const [key, child] of Object.entries(node)) {
|
|
83
|
+
if (key === 'script')
|
|
84
|
+
for (const command of strings(child))
|
|
85
|
+
push(name, command, '.', section);
|
|
86
|
+
else
|
|
87
|
+
walk(child, key === 'name' || typeof child !== 'object' ? name : key, section);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
for (const [section, child] of Object.entries(isObj(doc.pipelines) ? doc.pipelines : {}))
|
|
92
|
+
walk(child, section, section);
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
const walk = (node, name) => {
|
|
96
|
+
if (Array.isArray(node))
|
|
97
|
+
node.forEach((child) => walk(child, name));
|
|
98
|
+
else if (isObj(node)) {
|
|
99
|
+
for (const [key, child] of Object.entries(node)) {
|
|
100
|
+
if (key === 'steps' && Array.isArray(child)) {
|
|
101
|
+
for (const step of child) {
|
|
102
|
+
if (!isObj(step))
|
|
103
|
+
continue;
|
|
104
|
+
for (const field of ['script', 'bash', 'pwsh', 'powershell']) {
|
|
105
|
+
if (typeof step[field] === 'string')
|
|
106
|
+
push(name, step[field], workingDir(step.workingDirectory), '');
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
else
|
|
111
|
+
walk(child, typeof node.job === 'string' ? node.job : name);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
walk(doc, 'steps');
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
/** Every command of the repository's CI files. Invalid YAML is skipped with a warning. */
|
|
119
|
+
export function ciCommands(repoPath, warnings = []) {
|
|
120
|
+
const out = [];
|
|
121
|
+
for (const file of trackedPaths(repoPath)) {
|
|
122
|
+
const format = ciFormatOf(file);
|
|
123
|
+
if (!format)
|
|
124
|
+
continue;
|
|
125
|
+
const text = readTracked(repoPath, file);
|
|
126
|
+
if (text === null)
|
|
127
|
+
continue;
|
|
128
|
+
let doc;
|
|
129
|
+
try {
|
|
130
|
+
doc = parse(text);
|
|
131
|
+
}
|
|
132
|
+
catch (err) {
|
|
133
|
+
warnings.push(`${file}: invalid YAML, skipped (${err instanceof Error ? err.message.split('\n')[0] : String(err)})`);
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
collect(doc, file, format, repoPath, out);
|
|
137
|
+
}
|
|
138
|
+
return out;
|
|
139
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { readTracked } from '../../scope/tracked.js';
|
|
3
|
+
/** The first of `names` that is tracked in `dir`, or in the nearest ancestor when `ancestors` is set. */
|
|
4
|
+
export function findFile(repoPath, dir, names, ancestors) {
|
|
5
|
+
for (let current = path.posix.normalize(dir === '' ? '.' : dir);; current = path.posix.dirname(current)) {
|
|
6
|
+
for (const name of names) {
|
|
7
|
+
const file = path.posix.join(current, name);
|
|
8
|
+
const text = readTracked(repoPath, file);
|
|
9
|
+
if (text !== null)
|
|
10
|
+
return { file, text };
|
|
11
|
+
}
|
|
12
|
+
if (!ancestors || current === '.' || current === '' || current.startsWith('..'))
|
|
13
|
+
return undefined;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
/** Join backslash-continued lines. */
|
|
17
|
+
export function joinContinuations(lines) {
|
|
18
|
+
const out = [];
|
|
19
|
+
let pending = '';
|
|
20
|
+
for (const line of lines) {
|
|
21
|
+
if (line.endsWith('\\')) {
|
|
22
|
+
pending += `${line.slice(0, -1).trimEnd()} `;
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
out.push((pending + line).trim());
|
|
26
|
+
pending = '';
|
|
27
|
+
}
|
|
28
|
+
if (pending.trim() !== '')
|
|
29
|
+
out.push(pending.trim());
|
|
30
|
+
return out;
|
|
31
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { justfileEntryPoints } from './justfile.js';
|
|
2
|
+
import { makefileEntryPoints } from './makefile.js';
|
|
3
|
+
import { noxEntryPoints } from './nox.js';
|
|
4
|
+
import { packageJsonEntryPoints } from './package-json.js';
|
|
5
|
+
import { pyprojectEntryPoints } from './pyproject.js';
|
|
6
|
+
import { toxEntryPoints } from './tox.js';
|
|
7
|
+
/** Explicit list. */
|
|
8
|
+
export const entryPointKinds = [
|
|
9
|
+
packageJsonEntryPoints,
|
|
10
|
+
makefileEntryPoints,
|
|
11
|
+
justfileEntryPoints,
|
|
12
|
+
toxEntryPoints,
|
|
13
|
+
noxEntryPoints,
|
|
14
|
+
pyprojectEntryPoints,
|
|
15
|
+
];
|
|
16
|
+
export function entryPointKind(id) {
|
|
17
|
+
return entryPointKinds.find((kind) => kind.id === id);
|
|
18
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { findFile, joinContinuations } from './find.js';
|
|
2
|
+
const JUSTFILES = ['justfile', 'Justfile', '.justfile'];
|
|
3
|
+
/** Line based: `name [params...]: [dependencies]`, indented lines are the body. `alias`, `set`, `import`, `mod`, assignments and attributes are ignored. */
|
|
4
|
+
function parseJustfile(text) {
|
|
5
|
+
const recipes = new Map();
|
|
6
|
+
const lines = text.replace(/\r\n/g, '\n').split('\n');
|
|
7
|
+
let current;
|
|
8
|
+
let shebang = false;
|
|
9
|
+
for (const line of lines) {
|
|
10
|
+
if (/^[ \t]+\S/.test(line)) {
|
|
11
|
+
if (!current)
|
|
12
|
+
continue;
|
|
13
|
+
const trimmed = line.trim();
|
|
14
|
+
if (current.body.length === 0 && trimmed.startsWith('#!')) {
|
|
15
|
+
shebang = true;
|
|
16
|
+
current.body.push('');
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
if (shebang)
|
|
20
|
+
current.body[0] = `${current.body[0] ?? ''}\n${trimmed}`.replace(/^\n/, '');
|
|
21
|
+
else
|
|
22
|
+
current.body.push(trimmed.replace(/^[@-]+\s*/, ''));
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
if (line.trim() === '')
|
|
26
|
+
continue;
|
|
27
|
+
current = undefined;
|
|
28
|
+
shebang = false;
|
|
29
|
+
if (/^\s*#/.test(line) || /^\s*\[/.test(line) || /^(alias|set|import|mod|export)\b/.test(line))
|
|
30
|
+
continue;
|
|
31
|
+
const match = /^@?([A-Za-z_][\w-]*)([^:=]*?):(?!=)\s*(.*)$/.exec(line);
|
|
32
|
+
if (!match?.[1])
|
|
33
|
+
continue;
|
|
34
|
+
const dependencies = (match[3] ?? '')
|
|
35
|
+
.replace(/#.*$/, '')
|
|
36
|
+
.split(/&&|\s+/)
|
|
37
|
+
.map((d) => d.replace(/^\(|\)$/g, '').trim())
|
|
38
|
+
.filter((d) => d !== '' && /^[A-Za-z_][\w-]*$/.test(d));
|
|
39
|
+
current = { dependencies, body: [] };
|
|
40
|
+
recipes.set(match[1], current);
|
|
41
|
+
}
|
|
42
|
+
for (const recipe of recipes.values())
|
|
43
|
+
recipe.body = joinContinuations(recipe.body).filter((l) => l !== '');
|
|
44
|
+
return recipes;
|
|
45
|
+
}
|
|
46
|
+
/** `justfile`, `Justfile` or `.justfile` in `dir`, else the nearest ancestor. Dependencies are expanded like Makefile prerequisites. */
|
|
47
|
+
export const justfileEntryPoints = {
|
|
48
|
+
id: 'justfile',
|
|
49
|
+
list(repoPath, dir) {
|
|
50
|
+
const found = findFile(repoPath, dir, JUSTFILES, false);
|
|
51
|
+
return found ? [...parseJustfile(found.text).keys()].map((name) => ({ name, invoke: `just ${name}` })) : [];
|
|
52
|
+
},
|
|
53
|
+
lookup(repoPath, dir, name) {
|
|
54
|
+
const found = findFile(repoPath, dir, JUSTFILES, true);
|
|
55
|
+
if (!found)
|
|
56
|
+
return undefined;
|
|
57
|
+
const recipes = parseJustfile(found.text);
|
|
58
|
+
const key = name === '@default' ? [...recipes.keys()][0] : name;
|
|
59
|
+
const recipe = key === undefined ? undefined : recipes.get(key);
|
|
60
|
+
return recipe ? { file: found.file, bodies: [...recipe.dependencies.map((d) => `just ${d}`), ...recipe.body] } : undefined;
|
|
61
|
+
},
|
|
62
|
+
};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { readTracked } from '../../scope/tracked.js';
|
|
3
|
+
const MAKEFILE_NAMES = ['Makefile', 'makefile', 'GNUmakefile'];
|
|
4
|
+
/** Line based: `name [name...]: [prerequisites]` and tab-indented recipe lines. */
|
|
5
|
+
export function parseMakefile(text) {
|
|
6
|
+
const targets = new Map();
|
|
7
|
+
const lines = text.replace(/\r\n/g, '\n').split('\n');
|
|
8
|
+
let current = [];
|
|
9
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
10
|
+
let line = lines[i] ?? '';
|
|
11
|
+
if (line.startsWith('\t')) {
|
|
12
|
+
while (line.endsWith('\\') && i + 1 < lines.length) {
|
|
13
|
+
i += 1;
|
|
14
|
+
line = `${line.slice(0, -1).trimEnd()} ${(lines[i] ?? '').trim()}`;
|
|
15
|
+
}
|
|
16
|
+
const command = line.trim().replace(/^[@\-+]+\s*/, '');
|
|
17
|
+
if (command !== '')
|
|
18
|
+
for (const target of current)
|
|
19
|
+
target.recipe.push(command);
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
current = [];
|
|
23
|
+
const match = /^([^\s#:=][^:=#]*?)\s*:(?![:=])\s*([^#]*)/.exec(line);
|
|
24
|
+
if (!match?.[1] || /^(include|-include|ifeq|ifneq|ifdef|ifndef|else|endif|define|endef|export|override)\b/.test(line))
|
|
25
|
+
continue;
|
|
26
|
+
const names = match[1].split(/\s+/).filter((n) => n !== '' && !n.includes('%') && !n.includes('$') && n !== '.PHONY');
|
|
27
|
+
const prerequisites = (match[2] ?? '').split(/\s+/).filter((n) => n !== '' && !n.includes('$') && !n.includes('%'));
|
|
28
|
+
for (const name of names) {
|
|
29
|
+
const target = targets.get(name) ?? { prerequisites: [], recipe: [] };
|
|
30
|
+
target.prerequisites.push(...prerequisites);
|
|
31
|
+
targets.set(name, target);
|
|
32
|
+
current.push(target);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return targets;
|
|
36
|
+
}
|
|
37
|
+
/** Targets of the Makefile in `dir`. A prerequisite is expanded like a call to `make <prerequisite>`. */
|
|
38
|
+
export const makefileEntryPoints = {
|
|
39
|
+
id: 'makefile',
|
|
40
|
+
list(repoPath, dir) {
|
|
41
|
+
const base = dir === '' ? '.' : dir;
|
|
42
|
+
for (const candidate of MAKEFILE_NAMES) {
|
|
43
|
+
const text = readTracked(repoPath, path.posix.join(base, candidate));
|
|
44
|
+
if (text === null)
|
|
45
|
+
continue;
|
|
46
|
+
return [...parseMakefile(text).keys()]
|
|
47
|
+
.filter((name) => !name.startsWith('.') && !name.includes('%'))
|
|
48
|
+
.map((name) => ({ name, invoke: `make ${name}` }));
|
|
49
|
+
}
|
|
50
|
+
return [];
|
|
51
|
+
},
|
|
52
|
+
lookup(repoPath, dir, name) {
|
|
53
|
+
const base = dir === '' ? '.' : dir;
|
|
54
|
+
for (const candidate of MAKEFILE_NAMES) {
|
|
55
|
+
const file = path.posix.join(base, candidate);
|
|
56
|
+
const text = readTracked(repoPath, file);
|
|
57
|
+
if (text === null)
|
|
58
|
+
continue;
|
|
59
|
+
const target = parseMakefile(text).get(name);
|
|
60
|
+
if (!target)
|
|
61
|
+
return undefined;
|
|
62
|
+
return { file, bodies: [...target.prerequisites.map((p) => `make ${p}`), ...target.recipe] };
|
|
63
|
+
}
|
|
64
|
+
return undefined;
|
|
65
|
+
},
|
|
66
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { noxSessions } from '../../python/nox.js';
|
|
3
|
+
import { trackedPaths } from '../../scope/tracked.js';
|
|
4
|
+
function noxfileIn(repoPath, dir) {
|
|
5
|
+
const file = path.posix.join(dir === '' ? '.' : dir, 'noxfile.py');
|
|
6
|
+
return trackedPaths(repoPath).includes(file) ? file : undefined;
|
|
7
|
+
}
|
|
8
|
+
/** `noxfile.py` in `dir`. A call is `nox [-s <sessions>]`; without `-s` every session runs. */
|
|
9
|
+
export const noxEntryPoints = {
|
|
10
|
+
id: 'nox',
|
|
11
|
+
fileFor: noxfileIn,
|
|
12
|
+
list(repoPath, dir) {
|
|
13
|
+
const file = noxfileIn(repoPath, dir);
|
|
14
|
+
const sessions = file ? noxSessions(repoPath, file) : undefined;
|
|
15
|
+
return (sessions ?? []).map((s) => ({ name: s.name, invoke: `nox -s ${s.name}` }));
|
|
16
|
+
},
|
|
17
|
+
lookup(repoPath, dir, name) {
|
|
18
|
+
const file = noxfileIn(repoPath, dir);
|
|
19
|
+
const sessions = file ? noxSessions(repoPath, file) : undefined;
|
|
20
|
+
if (!file || !sessions)
|
|
21
|
+
return undefined;
|
|
22
|
+
if (name === '@all')
|
|
23
|
+
return { file, bodies: sessions.flatMap((s) => s.commands) };
|
|
24
|
+
const session = sessions.find((s) => s.name === name);
|
|
25
|
+
if (!session)
|
|
26
|
+
return undefined;
|
|
27
|
+
return { file, bodies: [...session.notify.map((n) => `nox -s ${n}`), ...session.commands] };
|
|
28
|
+
},
|
|
29
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { readPackageJson } from '../../manifest/package-json.js';
|
|
3
|
+
/** `scripts` of the `package.json` in `dir`, else the nearest ancestor within the repository. */
|
|
4
|
+
export const packageJsonEntryPoints = {
|
|
5
|
+
id: 'package-json',
|
|
6
|
+
list(repoPath, dir) {
|
|
7
|
+
const manifest = readPackageJson(repoPath, dir);
|
|
8
|
+
return Object.keys(manifest?.scripts ?? {}).map((name) => ({ name, invoke: `npm run ${name}` }));
|
|
9
|
+
},
|
|
10
|
+
lookup(repoPath, dir, name) {
|
|
11
|
+
for (let current = path.posix.normalize(dir === '' ? '.' : dir);; current = path.posix.dirname(current)) {
|
|
12
|
+
const manifest = readPackageJson(repoPath, current);
|
|
13
|
+
if (manifest) {
|
|
14
|
+
const body = manifest.scripts[name];
|
|
15
|
+
return body === undefined ? undefined : { file: path.posix.join(current, 'package.json'), bodies: [body] };
|
|
16
|
+
}
|
|
17
|
+
if (current === '.' || current === '' || current.startsWith('..'))
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { parseTomlText } from '../../util/toml.js';
|
|
2
|
+
import { findFile } from './find.js';
|
|
3
|
+
const isObj = (value) => value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
4
|
+
const cmdText = (value) => (Array.isArray(value) ? [strs(value).join(' ')] : strs(value));
|
|
5
|
+
const strs = (value) => (Array.isArray(value) ? value.filter((v) => typeof v === 'string') : typeof value === 'string' ? [value] : []);
|
|
6
|
+
const sub = (value, ...keys) => {
|
|
7
|
+
let current = value;
|
|
8
|
+
for (const key of keys)
|
|
9
|
+
current = isObj(current) ? current[key] : undefined;
|
|
10
|
+
return isObj(current) ? current : {};
|
|
11
|
+
};
|
|
12
|
+
function pdm(doc) {
|
|
13
|
+
return Object.entries(sub(doc, 'tool', 'pdm', 'scripts')).map(([name, value]) => {
|
|
14
|
+
const bodies = typeof value === 'string' ? [value] : isObj(value) ? [...cmdText(value.cmd), ...strs(value.shell), ...strs(value.composite).map((c) => `pdm run ${c}`)] : [];
|
|
15
|
+
return { key: `pdm:${name}`, invoke: `pdm run ${name}`, bodies };
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
function hatch(doc) {
|
|
19
|
+
const out = [];
|
|
20
|
+
for (const [env, table] of Object.entries(sub(doc, 'tool', 'hatch', 'envs'))) {
|
|
21
|
+
const scripts = isObj(table) && isObj(table.scripts) ? table.scripts : {};
|
|
22
|
+
for (const [name, value] of Object.entries(scripts)) {
|
|
23
|
+
const bodies = strs(value).map((c) => (Object.hasOwn(scripts, c.trim().split(/\s+/)[0] ?? '') ? `hatch run ${env}:${c.trim().split(/\s+/)[0]}` : c));
|
|
24
|
+
out.push({ key: `hatch:${env}:${name}`, invoke: `hatch run ${env}:${name}`, bodies });
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return out;
|
|
28
|
+
}
|
|
29
|
+
function rye(doc) {
|
|
30
|
+
return Object.entries(sub(doc, 'tool', 'rye', 'scripts')).map(([name, value]) => {
|
|
31
|
+
const bodies = typeof value === 'string' ? [value] : isObj(value) ? [...cmdText(value.cmd), ...strs(value.shell), ...strs(value.chain).map((c) => `rye run ${c}`)] : [];
|
|
32
|
+
return { key: `rye:${name}`, invoke: `rye run ${name}`, bodies };
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
function poe(doc) {
|
|
36
|
+
const tasks = sub(doc, 'tool', 'poe', 'tasks');
|
|
37
|
+
const items = (list) => (Array.isArray(list) ? list : []).flatMap((item) => (typeof item === 'string' ? [Object.hasOwn(tasks, item.trim().split(/\s+/)[0] ?? '') ? `poe ${item}` : item] : isObj(item) ? [...cmdText(item.cmd), ...strs(item.shell)] : []));
|
|
38
|
+
return Object.entries(tasks).map(([name, value]) => {
|
|
39
|
+
const bodies = typeof value === 'string' ? [value] : isObj(value) ? [...cmdText(value.cmd), ...strs(value.shell), ...items(value.sequence), ...items(value.composite)] : [];
|
|
40
|
+
return { key: `poe:${name}`, invoke: `poe ${name}`, bodies };
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
function taskipy(doc) {
|
|
44
|
+
return Object.entries(sub(doc, 'tool', 'taskipy', 'tasks')).map(([name, value]) => ({
|
|
45
|
+
key: `taskipy:${name}`,
|
|
46
|
+
invoke: `task ${name}`,
|
|
47
|
+
bodies: typeof value === 'string' ? [value] : isObj(value) ? strs(value.cmd) : [],
|
|
48
|
+
}));
|
|
49
|
+
}
|
|
50
|
+
/** `[project.scripts]` and `[tool.poetry.scripts]` targets are `module:callable`: the entry exists and runs no recognised tool. */
|
|
51
|
+
function scripts(doc) {
|
|
52
|
+
const names = [...Object.keys(sub(doc, 'project', 'scripts')), ...Object.keys(sub(doc, 'tool', 'poetry', 'scripts'))];
|
|
53
|
+
return [...new Set(names)].map((name) => ({ key: `scripts:${name}`, invoke: `uv run ${name}`, bodies: [] }));
|
|
54
|
+
}
|
|
55
|
+
function entries(repoPath, dir) {
|
|
56
|
+
const found = findFile(repoPath, dir, ['pyproject.toml'], false);
|
|
57
|
+
const doc = found ? parseTomlText(found.text) : undefined;
|
|
58
|
+
if (!found || !doc)
|
|
59
|
+
return undefined;
|
|
60
|
+
return { file: found.file, entries: [...scripts(doc), ...pdm(doc), ...hatch(doc), ...rye(doc), ...poe(doc), ...taskipy(doc)] };
|
|
61
|
+
}
|
|
62
|
+
/** Script tables of the `pyproject.toml` in `dir`. Lookup names are the namespaced keys (`pdm:lint`, `hatch:default:test`). */
|
|
63
|
+
export const pyprojectEntryPoints = {
|
|
64
|
+
id: 'pyproject',
|
|
65
|
+
list(repoPath, dir) {
|
|
66
|
+
return (entries(repoPath, dir)?.entries ?? []).map((e) => ({ name: e.key, invoke: e.invoke }));
|
|
67
|
+
},
|
|
68
|
+
lookup(repoPath, dir, name) {
|
|
69
|
+
const read = entries(repoPath, dir);
|
|
70
|
+
const entry = read?.entries.find((e) => e.key === name);
|
|
71
|
+
return read && entry ? { file: read.file, bodies: entry.bodies } : undefined;
|
|
72
|
+
},
|
|
73
|
+
};
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { parseIni } from '../../util/ini.js';
|
|
2
|
+
import { parseTomlText } from '../../util/toml.js';
|
|
3
|
+
import { findFile } from './find.js';
|
|
4
|
+
const isObj = (value) => value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
5
|
+
/** `py{311,312}-lint` becomes `py311-lint` and `py312-lint`. */
|
|
6
|
+
function expandBraces(text) {
|
|
7
|
+
const match = /^(.*?)\{([^{}]*)\}(.*)$/.exec(text);
|
|
8
|
+
if (!match)
|
|
9
|
+
return [text];
|
|
10
|
+
const [, head = '', set = '', tail = ''] = match;
|
|
11
|
+
return set.split(',').flatMap((item) => expandBraces(`${head}${item.trim()}${tail}`));
|
|
12
|
+
}
|
|
13
|
+
function lines(value) {
|
|
14
|
+
return (value ?? '')
|
|
15
|
+
.split('\n')
|
|
16
|
+
.map((l) => l.trim())
|
|
17
|
+
.filter((l) => l !== '' && !l.startsWith('#'));
|
|
18
|
+
}
|
|
19
|
+
/** Split on commas and newlines outside braces, then expand brace sets. */
|
|
20
|
+
function envList(value) {
|
|
21
|
+
const parts = [];
|
|
22
|
+
let depth = 0;
|
|
23
|
+
let current = '';
|
|
24
|
+
for (const ch of value ?? '') {
|
|
25
|
+
if (ch === '{')
|
|
26
|
+
depth += 1;
|
|
27
|
+
else if (ch === '}')
|
|
28
|
+
depth -= 1;
|
|
29
|
+
if ((ch === ',' || ch === '\n') && depth <= 0) {
|
|
30
|
+
parts.push(current);
|
|
31
|
+
current = '';
|
|
32
|
+
}
|
|
33
|
+
else
|
|
34
|
+
current += ch;
|
|
35
|
+
}
|
|
36
|
+
parts.push(current);
|
|
37
|
+
return parts.map((e) => e.trim()).filter((e) => e !== '').flatMap(expandBraces);
|
|
38
|
+
}
|
|
39
|
+
function fromIni(file, text) {
|
|
40
|
+
const ini = parseIni(text);
|
|
41
|
+
const section = (name) => {
|
|
42
|
+
const s = ini[name] ?? {};
|
|
43
|
+
return { commandsPre: lines(s.commands_pre), commands: lines(s.commands), commandsPost: lines(s.commands_post) };
|
|
44
|
+
};
|
|
45
|
+
const envs = new Map();
|
|
46
|
+
for (const name of Object.keys(ini))
|
|
47
|
+
if (name.startsWith('testenv:'))
|
|
48
|
+
envs.set(name.slice('testenv:'.length), section(name));
|
|
49
|
+
return { file, envlist: envList(ini.tox?.envlist), base: section('testenv'), envs };
|
|
50
|
+
}
|
|
51
|
+
function commandText(value) {
|
|
52
|
+
if (typeof value === 'string')
|
|
53
|
+
return [value];
|
|
54
|
+
if (Array.isArray(value))
|
|
55
|
+
return value.map((v) => (Array.isArray(v) ? v.filter((x) => typeof x === 'string').join(' ') : typeof v === 'string' ? v : '')).filter((v) => v !== '');
|
|
56
|
+
return [];
|
|
57
|
+
}
|
|
58
|
+
function fromToml(file, doc) {
|
|
59
|
+
const table = file.endsWith('pyproject.toml') ? (isObj(doc.tool) && isObj(doc.tool.tox) ? doc.tool.tox : undefined) : doc;
|
|
60
|
+
if (!table)
|
|
61
|
+
return undefined;
|
|
62
|
+
if (typeof table.legacy_tox_ini === 'string')
|
|
63
|
+
return fromIni(file, table.legacy_tox_ini);
|
|
64
|
+
const base = { commands: commandText(isObj(table.env_run_base) ? table.env_run_base.commands : undefined) };
|
|
65
|
+
const envs = new Map();
|
|
66
|
+
const rawEnvs = isObj(table.env) ? table.env : {};
|
|
67
|
+
for (const [name, value] of Object.entries(rawEnvs))
|
|
68
|
+
if (isObj(value))
|
|
69
|
+
envs.set(name, { commands: commandText(value.commands) });
|
|
70
|
+
const envlist = Array.isArray(table.env_list) ? table.env_list.filter((e) => typeof e === 'string').flatMap(expandBraces) : [];
|
|
71
|
+
return { file, envlist, base, envs };
|
|
72
|
+
}
|
|
73
|
+
function readToxConfig(repoPath, dir) {
|
|
74
|
+
const ini = findFile(repoPath, dir, ['tox.ini'], false);
|
|
75
|
+
if (ini)
|
|
76
|
+
return fromIni(ini.file, ini.text);
|
|
77
|
+
for (const name of ['tox.toml', 'pyproject.toml']) {
|
|
78
|
+
const found = findFile(repoPath, dir, [name], false);
|
|
79
|
+
const doc = found ? parseTomlText(found.text) : undefined;
|
|
80
|
+
const config = found && doc ? fromToml(found.file, doc) : undefined;
|
|
81
|
+
if (config)
|
|
82
|
+
return config;
|
|
83
|
+
}
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
86
|
+
/** `{envpython} -m` becomes `python -m`; `{posargs}`, `{toxinidir}` and other `{...}` fields are dropped. */
|
|
87
|
+
function substitute(command, factors) {
|
|
88
|
+
let text = command.trim();
|
|
89
|
+
const factor = /^([A-Za-z0-9_.!,-]+):\s+(.*)$/.exec(text);
|
|
90
|
+
if (factor?.[1] && factor[2] !== undefined) {
|
|
91
|
+
const wanted = factor[1].split(',').some((f) => (f.startsWith('!') ? !factors.includes(f.slice(1)) : factors.includes(f)));
|
|
92
|
+
if (!wanted)
|
|
93
|
+
return undefined;
|
|
94
|
+
text = factor[2];
|
|
95
|
+
}
|
|
96
|
+
text = text.replace(/\{envpython\}/g, 'python').replace(/\{[^{}]*\}/g, '').replace(/^[-!]+\s*/, '').replace(/\s+/g, ' ').trim();
|
|
97
|
+
return text === '' ? undefined : text;
|
|
98
|
+
}
|
|
99
|
+
function envBodies(config, name) {
|
|
100
|
+
const factors = name.split('-');
|
|
101
|
+
const section = config.envs.get(name) ?? factors.map((f) => config.envs.get(f)).find((e) => e !== undefined);
|
|
102
|
+
const known = section !== undefined || config.envlist.includes(name) || name === '@base';
|
|
103
|
+
if (!known)
|
|
104
|
+
return undefined;
|
|
105
|
+
const pick = (key) => section?.[key] ?? config.base[key] ?? [];
|
|
106
|
+
return [...pick('commandsPre'), ...pick('commands'), ...pick('commandsPost')].flatMap((c) => substitute(c, factors) ?? []);
|
|
107
|
+
}
|
|
108
|
+
/** `tox.ini`, `tox.toml` or `[tool.tox]` in `dir`. A call is `tox [-e <envs>]`. */
|
|
109
|
+
export const toxEntryPoints = {
|
|
110
|
+
id: 'tox',
|
|
111
|
+
fileFor: (repoPath, dir) => readToxConfig(repoPath, dir)?.file,
|
|
112
|
+
list(repoPath, dir) {
|
|
113
|
+
const config = readToxConfig(repoPath, dir);
|
|
114
|
+
if (!config)
|
|
115
|
+
return [];
|
|
116
|
+
const names = config.envlist.length > 0 ? config.envlist : [...config.envs.keys()];
|
|
117
|
+
return [...new Set(names)].map((name) => ({ name, invoke: `tox -e ${name}` }));
|
|
118
|
+
},
|
|
119
|
+
lookup(repoPath, dir, name) {
|
|
120
|
+
const config = readToxConfig(repoPath, dir);
|
|
121
|
+
if (!config)
|
|
122
|
+
return undefined;
|
|
123
|
+
if (name === '@default') {
|
|
124
|
+
const names = config.envlist.length > 0 ? config.envlist : ['@base'];
|
|
125
|
+
return { file: config.file, bodies: names.flatMap((n) => envBodies(config, n) ?? []) };
|
|
126
|
+
}
|
|
127
|
+
const bodies = envBodies(config, name);
|
|
128
|
+
return bodies === undefined ? undefined : { file: config.file, bodies };
|
|
129
|
+
},
|
|
130
|
+
};
|