greenbump 0.5.0 → 0.7.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/README.md +696 -66
- package/README.zh-CN.md +539 -100
- package/dist/agent/fixer.js +165 -12
- package/dist/agent/fixer.js.map +1 -1
- package/dist/agent/fixer.test.js +410 -0
- package/dist/agent/fixer.test.js.map +1 -0
- package/dist/cli/interactive.js +81 -0
- package/dist/cli/interactive.js.map +1 -0
- package/dist/cli.js +59 -0
- package/dist/cli.js.map +1 -1
- package/dist/engine/analyzers/changelog-parser.js +121 -0
- package/dist/engine/analyzers/changelog-parser.js.map +1 -0
- package/dist/engine/batch.js +24 -0
- package/dist/engine/batch.js.map +1 -1
- package/dist/engine/batch.test.js +59 -0
- package/dist/engine/batch.test.js.map +1 -0
- package/dist/engine/cache/manager.js +165 -0
- package/dist/engine/cache/manager.js.map +1 -0
- package/dist/engine/cache/manager.test.js +75 -0
- package/dist/engine/cache/manager.test.js.map +1 -0
- package/dist/engine/checks.test.js +92 -0
- package/dist/engine/checks.test.js.map +1 -0
- package/dist/engine/context/optimizer.js +189 -0
- package/dist/engine/context/optimizer.js.map +1 -0
- package/dist/engine/context/optimizer.test.js +81 -0
- package/dist/engine/context/optimizer.test.js.map +1 -0
- package/dist/engine/ecosystems/parsers.test.js +255 -0
- package/dist/engine/ecosystems/parsers.test.js.map +1 -0
- package/dist/engine/fixer/patterns.js +597 -0
- package/dist/engine/fixer/patterns.js.map +1 -0
- package/dist/engine/fixer/patterns.test.js +316 -0
- package/dist/engine/fixer/patterns.test.js.map +1 -0
- package/dist/engine/git.test.js +111 -0
- package/dist/engine/git.test.js.map +1 -0
- package/dist/engine/run.js +66 -1
- package/dist/engine/run.js.map +1 -1
- package/dist/engine/run.test.js +47 -0
- package/dist/engine/run.test.js.map +1 -0
- package/dist/engine/stats/recorder.js +48 -0
- package/dist/engine/stats/recorder.js.map +1 -0
- package/dist/engine/stats/recorder.test.js +71 -0
- package/dist/engine/stats/recorder.test.js.map +1 -0
- package/dist/engine/stats/report.js +157 -0
- package/dist/engine/stats/report.js.map +1 -0
- package/dist/engine/stats/report.test.js +83 -0
- package/dist/engine/stats/report.test.js.map +1 -0
- package/dist/engine/verifiers/ast-analyzer.js +212 -0
- package/dist/engine/verifiers/ast-analyzer.js.map +1 -0
- package/dist/engine/verifiers/ast-analyzer.test.js +96 -0
- package/dist/engine/verifiers/ast-analyzer.test.js.map +1 -0
- package/dist/engine/workspace.test.js +95 -0
- package/dist/engine/workspace.test.js.map +1 -0
- package/dist/exitcode.test.js +62 -0
- package/dist/exitcode.test.js.map +1 -0
- package/dist/format.js +15 -0
- package/dist/format.js.map +1 -1
- package/dist/report.test.js +56 -0
- package/dist/report.test.js.map +1 -0
- package/package.json +7 -4
- package/CHANGELOG.md +0 -136
- package/CONTRIBUTING.md +0 -52
- package/SECURITY.md +0 -38
- package/action-dist/index.cjs +0 -20730
- package/action.yml +0 -76
- package/competitive-analysis.md +0 -304
- package/examples/greenbump.yml +0 -37
- package/examples/legacy-repo/package.json +0 -12
- package/examples/legacy-repo/test.js +0 -17
- package/examples/multi-file-repo/models/todo.js +0 -8
- package/examples/multi-file-repo/models/user.js +0 -11
- package/examples/multi-file-repo/package.json +0 -12
- package/examples/multi-file-repo/services/session.js +0 -9
- package/examples/multi-file-repo/test.js +0 -19
- package/optimization-roadmap.md +0 -772
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { test } from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { mkdtemp, writeFile, rm } from "node:fs/promises";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { runChecks } from "./checks.js";
|
|
7
|
+
async function withTmpDir(fn) {
|
|
8
|
+
const dir = await mkdtemp(join(tmpdir(), "greenbump-checks-"));
|
|
9
|
+
try {
|
|
10
|
+
await fn(dir);
|
|
11
|
+
}
|
|
12
|
+
finally {
|
|
13
|
+
await rm(dir, { recursive: true, force: true });
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
async function writePkg(dir, scripts) {
|
|
17
|
+
await writeFile(join(dir, "package.json"), JSON.stringify({ name: "tmp", version: "1.0.0", scripts }), "utf8");
|
|
18
|
+
}
|
|
19
|
+
test("runChecks: unverifiable when the adapter has no build or test script", async () => {
|
|
20
|
+
await withTmpDir(async (dir) => {
|
|
21
|
+
await writePkg(dir, {});
|
|
22
|
+
const r = await runChecks("npm", dir);
|
|
23
|
+
assert.equal(r.unverifiable, true);
|
|
24
|
+
assert.equal(r.ok, true);
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
test("runChecks: ok when build and test scripts both succeed", async () => {
|
|
28
|
+
await withTmpDir(async (dir) => {
|
|
29
|
+
await writePkg(dir, { build: "node -e \"process.exit(0)\"", test: "node -e \"process.exit(0)\"" });
|
|
30
|
+
const r = await runChecks("npm", dir);
|
|
31
|
+
assert.equal(r.ok, true);
|
|
32
|
+
assert.equal(r.unverifiable, false);
|
|
33
|
+
assert.equal(r.failedStep, undefined);
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
test("runChecks: stops at build failure without running test", async () => {
|
|
37
|
+
await withTmpDir(async (dir) => {
|
|
38
|
+
await writePkg(dir, {
|
|
39
|
+
build: "node -e \"console.error('boom'); process.exit(1)\"",
|
|
40
|
+
test: "node -e \"require('fs').writeFileSync('ran-test','1')\"",
|
|
41
|
+
});
|
|
42
|
+
const r = await runChecks("npm", dir);
|
|
43
|
+
assert.equal(r.ok, false);
|
|
44
|
+
assert.equal(r.failedStep, "build");
|
|
45
|
+
assert.match(r.output, /boom/);
|
|
46
|
+
await assert.rejects(async () => {
|
|
47
|
+
const { stat } = await import("node:fs/promises");
|
|
48
|
+
await stat(join(dir, "ran-test"));
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
test("runChecks: build passes, test fails — reports test as the failed step", async () => {
|
|
53
|
+
await withTmpDir(async (dir) => {
|
|
54
|
+
await writePkg(dir, {
|
|
55
|
+
build: "node -e \"process.exit(0)\"",
|
|
56
|
+
test: "node -e \"console.error('test failed'); process.exit(1)\"",
|
|
57
|
+
});
|
|
58
|
+
const r = await runChecks("npm", dir);
|
|
59
|
+
assert.equal(r.ok, false);
|
|
60
|
+
assert.equal(r.failedStep, "test");
|
|
61
|
+
assert.match(r.output, /test failed/);
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
test("runChecks: --build-cmd/--test-cmd overrides win over the adapter's own scripts", async () => {
|
|
65
|
+
await withTmpDir(async (dir) => {
|
|
66
|
+
await writePkg(dir, { build: "node -e \"process.exit(1)\"" });
|
|
67
|
+
const r = await runChecks("npm", dir, { buildCmd: "node -e \"process.exit(0)\"" });
|
|
68
|
+
assert.equal(r.ok, true);
|
|
69
|
+
assert.equal(r.unverifiable, false);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
test("runChecks: truncates output that exceeds the max size, keeping the tail", async () => {
|
|
73
|
+
await withTmpDir(async (dir) => {
|
|
74
|
+
// Write >16_000 chars of stdout before failing, then check only the end survives.
|
|
75
|
+
// Use process.exitCode (not process.exit()) so Node drains stdout before exiting —
|
|
76
|
+
// stdout writes to a pipe are asynchronous, and a forced process.exit() right after
|
|
77
|
+
// thousands of console.log calls can truncate the buffer before it's flushed. That
|
|
78
|
+
// race is invisible on a local TTY but reliably bites in CI's piped, headless stdio.
|
|
79
|
+
// MARKER_END also goes through console.log (stdout), not console.error (stderr) —
|
|
80
|
+
// exec.ts appends to `combined` from two independent stream 'data' listeners, so
|
|
81
|
+
// interleaving order across stdout/stderr is not guaranteed; keeping the marker on
|
|
82
|
+
// the same stream as the 2000 lines guarantees it lands after them.
|
|
83
|
+
const script = "for (let i = 0; i < 2000; i++) console.log('line-' + i); console.log('MARKER_END'); process.exitCode = 1;";
|
|
84
|
+
await writePkg(dir, { build: `node -e "${script.replace(/"/g, '\\"')}"` });
|
|
85
|
+
const r = await runChecks("npm", dir);
|
|
86
|
+
assert.equal(r.ok, false);
|
|
87
|
+
assert.match(r.output, /MARKER_END/);
|
|
88
|
+
assert.ok(r.output.length <= 16_000 + 50);
|
|
89
|
+
assert.doesNotMatch(r.output, /line-0\b/);
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
//# sourceMappingURL=checks.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"checks.test.js","sourceRoot":"","sources":["../../src/engine/checks.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { readFile, readdir, stat } from "node:fs/promises";
|
|
2
|
+
import { join, relative } from "node:path";
|
|
3
|
+
/**
|
|
4
|
+
* Context optimizer: shrink what we send to the LLM.
|
|
5
|
+
*
|
|
6
|
+
* Two strategies:
|
|
7
|
+
* 1. Trim the failure output — keep error lines, drop passing-test noise,
|
|
8
|
+
* stack frames in node_modules, and repeated warnings.
|
|
9
|
+
* 2. Identify candidate files — parse the error output for file paths so the
|
|
10
|
+
* fix agent gets pointed at the right files immediately instead of
|
|
11
|
+
* exploring (every exploration round costs input tokens).
|
|
12
|
+
*/
|
|
13
|
+
const NOISE_PATTERNS = [
|
|
14
|
+
/^\s*at .*node_modules.*$/, // stack frames inside dependencies
|
|
15
|
+
/^\s*at .*internal\//, // node internal frames
|
|
16
|
+
/npm warn/i,
|
|
17
|
+
/deprecated.*\(https?:\/\//i,
|
|
18
|
+
/^\s*✓/, // passing test lines
|
|
19
|
+
/^\s*PASS /,
|
|
20
|
+
/Test Suites:.*passed/i,
|
|
21
|
+
/Tests:.*passed/i,
|
|
22
|
+
];
|
|
23
|
+
const MAX_FAILURE_CHARS = 4_000;
|
|
24
|
+
const MAX_FILE_LINE = 300;
|
|
25
|
+
/**
|
|
26
|
+
* Compress failure output: keep the informative parts (errors, file refs,
|
|
27
|
+
* first stack frame per error), drop the rest. Preserves the error's
|
|
28
|
+
* "signature" — the part that determines what the fix should be.
|
|
29
|
+
*/
|
|
30
|
+
export function trimFailureOutput(output) {
|
|
31
|
+
const lines = output.split("\n");
|
|
32
|
+
const kept = [];
|
|
33
|
+
let seenErrors = 0;
|
|
34
|
+
for (const line of lines) {
|
|
35
|
+
if (NOISE_PATTERNS.some((p) => p.test(line)))
|
|
36
|
+
continue;
|
|
37
|
+
const isError = /error|fail|✗|✘|cannot|not a function|not defined|TypeError|ReferenceError|SyntaxError/i.test(line);
|
|
38
|
+
if (isError)
|
|
39
|
+
seenErrors++;
|
|
40
|
+
// After the first couple of errors, stack-trace lines become noise
|
|
41
|
+
const isStackFrame = /^\s*at\s/.test(line);
|
|
42
|
+
if (isStackFrame && seenErrors > 2)
|
|
43
|
+
continue;
|
|
44
|
+
kept.push(line.length > MAX_FILE_LINE ? line.slice(0, MAX_FILE_LINE) + "…" : line);
|
|
45
|
+
if (kept.join("\n").length > MAX_FAILURE_CHARS)
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
let result = kept.join("\n");
|
|
49
|
+
if (output.length > result.length) {
|
|
50
|
+
result += `\n… (trimmed ${output.length - result.length} chars of passing/noisy output)`;
|
|
51
|
+
}
|
|
52
|
+
return result;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Extract file paths referenced by the failure output — these are the
|
|
56
|
+
* files most likely to need edits. Returned as project-relative paths,
|
|
57
|
+
* deduplicated, existing files only.
|
|
58
|
+
*/
|
|
59
|
+
export async function extractCandidateFiles(failureOutput, cwd) {
|
|
60
|
+
const found = new Set();
|
|
61
|
+
// Match path:line:col references (TS/JS errors), stack frames, webpack errors
|
|
62
|
+
const patterns = [
|
|
63
|
+
/([^\s():]+\.(?:ts|tsx|js|jsx|mjs|cjs|vue|svelte)):\d+:\d+/g,
|
|
64
|
+
/at\s+(?:\w+\s+)?\(([^():\s]+\.(?:ts|tsx|js|jsx|mjs|cjs)):\d+:\d+\)/g,
|
|
65
|
+
/at\s+([^():\s]+\.(?:ts|tsx|js|jsx|mjs|cjs)):\d+:\d+/g,
|
|
66
|
+
/ERROR in ([^\s]+\.(?:ts|tsx|js|jsx|vue|svelte))/g,
|
|
67
|
+
];
|
|
68
|
+
for (const pattern of patterns) {
|
|
69
|
+
let m;
|
|
70
|
+
while ((m = pattern.exec(failureOutput)) !== null) {
|
|
71
|
+
const p = normalizePath(m[1], cwd);
|
|
72
|
+
if (p)
|
|
73
|
+
found.add(p);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
// Verify the files actually exist in the project
|
|
77
|
+
const existing = [];
|
|
78
|
+
for (const f of found) {
|
|
79
|
+
try {
|
|
80
|
+
const s = await stat(join(cwd, f));
|
|
81
|
+
if (s.isFile() && s.size < 200_000)
|
|
82
|
+
existing.push(f);
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
// not a real project file — skip
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return existing.slice(0, 20); // cap to avoid flooding the context
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Find test files related to candidate source files, so the agent can
|
|
92
|
+
* see expectations without searching.
|
|
93
|
+
*/
|
|
94
|
+
export async function findRelatedTests(candidateFiles, cwd) {
|
|
95
|
+
const tests = new Set();
|
|
96
|
+
for (const f of candidateFiles) {
|
|
97
|
+
// src/foo.ts → src/foo.test.ts, tests/foo.test.ts, __tests__/foo.test.ts
|
|
98
|
+
const base = f.replace(/\.(ts|tsx|js|jsx|mjs|cjs|vue|svelte)$/, "");
|
|
99
|
+
const candidates = [
|
|
100
|
+
`${base}.test.ts`, `${base}.test.tsx`, `${base}.test.js`, `${base}.test.jsx`,
|
|
101
|
+
`${base}.spec.ts`, `${base}.spec.tsx`, `${base}.spec.js`, `${base}.spec.jsx`,
|
|
102
|
+
];
|
|
103
|
+
for (const c of candidates) {
|
|
104
|
+
try {
|
|
105
|
+
await stat(join(cwd, c));
|
|
106
|
+
tests.add(c);
|
|
107
|
+
}
|
|
108
|
+
catch { /* doesn't exist */ }
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return [...tests].slice(0, 10);
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Read candidate files (truncated) to include as upfront context.
|
|
115
|
+
* Returns a map of path → content.
|
|
116
|
+
*/
|
|
117
|
+
export async function readCandidateContents(files, cwd, maxCharsPerFile = 8_000) {
|
|
118
|
+
const out = new Map();
|
|
119
|
+
for (const f of files) {
|
|
120
|
+
try {
|
|
121
|
+
let content = await readFile(join(cwd, f), "utf8");
|
|
122
|
+
if (content.length > maxCharsPerFile) {
|
|
123
|
+
content = content.slice(0, maxCharsPerFile) + "\n… (file truncated)";
|
|
124
|
+
}
|
|
125
|
+
out.set(f, content);
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
// unreadable — skip
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return out;
|
|
132
|
+
}
|
|
133
|
+
/** Estimate token count for a string (~4 chars/token for code+prose mix). */
|
|
134
|
+
export function estimateTokens(text) {
|
|
135
|
+
return Math.ceil(text.length / 4);
|
|
136
|
+
}
|
|
137
|
+
/** Build the "files likely involved" hint block for the fix prompt. */
|
|
138
|
+
export function buildCandidateHint(candidateFiles, relatedTests) {
|
|
139
|
+
if (candidateFiles.length === 0 && relatedTests.length === 0)
|
|
140
|
+
return "";
|
|
141
|
+
const parts = ["\n\nFiles referenced by the failure output (start here):"];
|
|
142
|
+
for (const f of candidateFiles)
|
|
143
|
+
parts.push(`- ${f}`);
|
|
144
|
+
if (relatedTests.length > 0) {
|
|
145
|
+
parts.push("\nRelated test files:");
|
|
146
|
+
for (const t of relatedTests)
|
|
147
|
+
parts.push(`- ${t}`);
|
|
148
|
+
}
|
|
149
|
+
return parts.join("\n");
|
|
150
|
+
}
|
|
151
|
+
function normalizePath(p, cwd) {
|
|
152
|
+
// Strip common prefixes to get a project-relative path
|
|
153
|
+
let cleaned = p
|
|
154
|
+
.replace(/^file:\/\//, "")
|
|
155
|
+
.replace(/\\/g, "/");
|
|
156
|
+
if (cleaned.startsWith(cwd)) {
|
|
157
|
+
cleaned = relative(cwd, cleaned);
|
|
158
|
+
}
|
|
159
|
+
// Drop absolute paths that aren't under cwd
|
|
160
|
+
if (cleaned.startsWith("/"))
|
|
161
|
+
return null;
|
|
162
|
+
// Drop anything escaping the project
|
|
163
|
+
if (cleaned.startsWith(".."))
|
|
164
|
+
return null;
|
|
165
|
+
// Drop node_modules references — we don't fix dependency internals
|
|
166
|
+
if (cleaned.includes("node_modules/"))
|
|
167
|
+
return null;
|
|
168
|
+
return cleaned;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Scan the project root for the most likely source dirs, so the candidate
|
|
172
|
+
* search can stay focused (cheap heuristic, avoids walking node_modules).
|
|
173
|
+
*/
|
|
174
|
+
export async function guessSourceDirs(cwd) {
|
|
175
|
+
const candidates = ["src", "lib", "app", "packages", "source", "test", "tests", "__tests__"];
|
|
176
|
+
const found = [];
|
|
177
|
+
try {
|
|
178
|
+
const entries = await readdir(cwd, { withFileTypes: true });
|
|
179
|
+
for (const e of entries) {
|
|
180
|
+
if (e.isDirectory() && candidates.includes(e.name))
|
|
181
|
+
found.push(e.name);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
catch {
|
|
185
|
+
// unreadable root — return empty
|
|
186
|
+
}
|
|
187
|
+
return found.length > 0 ? found : ["."];
|
|
188
|
+
}
|
|
189
|
+
//# sourceMappingURL=optimizer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"optimizer.js","sourceRoot":"","sources":["../../../src/engine/context/optimizer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { test } from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { mkdtemp, writeFile, rm, mkdir } from "node:fs/promises";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { trimFailureOutput, extractCandidateFiles, estimateTokens, buildCandidateHint, } from "./optimizer.js";
|
|
7
|
+
test("trimFailureOutput: drops passing-test noise, keeps errors", () => {
|
|
8
|
+
const output = [
|
|
9
|
+
" ✓ renders header",
|
|
10
|
+
" ✓ renders footer",
|
|
11
|
+
" ✗ renders body",
|
|
12
|
+
"TypeError: ReactDOM.render is not a function",
|
|
13
|
+
" at render (src/index.tsx:5:3)",
|
|
14
|
+
" at Object.<anonymous> (node_modules/react-dom/cjs/index.js:12:1)",
|
|
15
|
+
"Tests: 2 passed, 1 failed",
|
|
16
|
+
].join("\n");
|
|
17
|
+
const trimmed = trimFailureOutput(output);
|
|
18
|
+
assert.ok(trimmed.includes("ReactDOM.render is not a function"));
|
|
19
|
+
assert.ok(!trimmed.includes("✓ renders header"));
|
|
20
|
+
assert.ok(!trimmed.includes("node_modules"));
|
|
21
|
+
assert.ok(!trimmed.includes("Tests: 2 passed"));
|
|
22
|
+
});
|
|
23
|
+
test("trimFailureOutput: caps total size", () => {
|
|
24
|
+
const huge = "error line\n".repeat(5000);
|
|
25
|
+
const trimmed = trimFailureOutput(huge);
|
|
26
|
+
assert.ok(trimmed.length < 5000);
|
|
27
|
+
});
|
|
28
|
+
test("extractCandidateFiles: pulls project-relative paths from TS errors", async () => {
|
|
29
|
+
const dir = await mkdtemp(join(tmpdir(), "greenbump-ctx-"));
|
|
30
|
+
await mkdir(join(dir, "src"), { recursive: true });
|
|
31
|
+
await writeFile(join(dir, "src", "index.tsx"), "content", "utf8");
|
|
32
|
+
try {
|
|
33
|
+
const files = await extractCandidateFiles("src/index.tsx:5:3 - error TS2339: Property 'render' does not exist", dir);
|
|
34
|
+
assert.deepEqual(files, ["src/index.tsx"]);
|
|
35
|
+
}
|
|
36
|
+
finally {
|
|
37
|
+
await rm(dir, { recursive: true, force: true });
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
test("extractCandidateFiles: ignores node_modules and absolute paths outside project", async () => {
|
|
41
|
+
const dir = await mkdtemp(join(tmpdir(), "greenbump-ctx-"));
|
|
42
|
+
await mkdir(join(dir, "src"), { recursive: true });
|
|
43
|
+
await writeFile(join(dir, "src", "index.tsx"), "content", "utf8");
|
|
44
|
+
try {
|
|
45
|
+
const files = await extractCandidateFiles([
|
|
46
|
+
"at render (/project/node_modules/react-dom/index.js:5:3)",
|
|
47
|
+
"at render (/totally/other/place/file.ts:1:1)",
|
|
48
|
+
"src/index.tsx:5:3 - error TS2339",
|
|
49
|
+
].join("\n"), dir);
|
|
50
|
+
assert.deepEqual(files, ["src/index.tsx"]);
|
|
51
|
+
}
|
|
52
|
+
finally {
|
|
53
|
+
await rm(dir, { recursive: true, force: true });
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
test("extractCandidateFiles: only returns files that exist", async () => {
|
|
57
|
+
const dir = await mkdtemp(join(tmpdir(), "greenbump-ctx-"));
|
|
58
|
+
await mkdir(join(dir, "src"), { recursive: true });
|
|
59
|
+
await writeFile(join(dir, "src", "real.tsx"), "content", "utf8");
|
|
60
|
+
try {
|
|
61
|
+
const files = await extractCandidateFiles(["src/real.tsx:1:1 - error", "src/imaginary.tsx:2:2 - error"].join("\n"), dir);
|
|
62
|
+
assert.deepEqual(files, ["src/real.tsx"]);
|
|
63
|
+
}
|
|
64
|
+
finally {
|
|
65
|
+
await rm(dir, { recursive: true, force: true });
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
test("estimateTokens: roughly 4 chars per token", () => {
|
|
69
|
+
assert.equal(estimateTokens(""), 0);
|
|
70
|
+
assert.equal(estimateTokens("abcd"), 1);
|
|
71
|
+
assert.equal(estimateTokens("a".repeat(100)), 25);
|
|
72
|
+
});
|
|
73
|
+
test("buildCandidateHint: empty when no candidates", () => {
|
|
74
|
+
assert.equal(buildCandidateHint([], []), "");
|
|
75
|
+
});
|
|
76
|
+
test("buildCandidateHint: lists files and tests", () => {
|
|
77
|
+
const hint = buildCandidateHint(["src/index.tsx"], ["src/index.test.tsx"]);
|
|
78
|
+
assert.ok(hint.includes("src/index.tsx"));
|
|
79
|
+
assert.ok(hint.includes("src/index.test.tsx"));
|
|
80
|
+
});
|
|
81
|
+
//# sourceMappingURL=optimizer.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"optimizer.test.js","sourceRoot":"","sources":["../../../src/engine/context/optimizer.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import { test } from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { parseYarnOutdated } from "./yarn.js";
|
|
4
|
+
import { parseGoListOutdated } from "./go.js";
|
|
5
|
+
import { parseComposerOutdated } from "./composer.js";
|
|
6
|
+
import { parsePubOutdated } from "./pub.js";
|
|
7
|
+
import { parseNugetOutdated } from "./nuget.js";
|
|
8
|
+
import { parseCargoToml } from "./cargo.js";
|
|
9
|
+
import { parsePomXml } from "./maven.js";
|
|
10
|
+
import { parseGradleBuildFile } from "./gradle.js";
|
|
11
|
+
import { parseBundlerOutdated } from "./bundler.js";
|
|
12
|
+
import { parsePoetryOutdated } from "./poetry.js";
|
|
13
|
+
import { parseCocoapodsOutdated } from "./cocoapods.js";
|
|
14
|
+
import { parseMixOutdated } from "./mix.js";
|
|
15
|
+
import { parseConanfileTxt } from "./conan.js";
|
|
16
|
+
import { parseNpmOutdated } from "./npm.js";
|
|
17
|
+
import { parsePnpmOutdated } from "./pnpm.js";
|
|
18
|
+
import { parsePipOutdated } from "./pip.js";
|
|
19
|
+
import { parseUvOutdated } from "./uv.js";
|
|
20
|
+
import { parsePipenvOutdated } from "./pipenv.js";
|
|
21
|
+
import { parseElmDirectDeps } from "./elm.js";
|
|
22
|
+
import { parsePackageResolved, parsePackageSwiftDeps } from "./swiftpm.js";
|
|
23
|
+
test("yarn: parses the ndjson table row, skips non-table lines", () => {
|
|
24
|
+
const stdout = [
|
|
25
|
+
JSON.stringify({ type: "info", data: "unrelated" }),
|
|
26
|
+
JSON.stringify({
|
|
27
|
+
type: "table",
|
|
28
|
+
data: {
|
|
29
|
+
head: ["Package", "Current", "Wanted", "Latest", "Package Type", "URL"],
|
|
30
|
+
body: [
|
|
31
|
+
["lodash", "4.17.20", "4.17.21", "4.17.21", "dependencies", ""],
|
|
32
|
+
["left-pad", "1.0.0", "1.0.0", "1.0.0", "dependencies", ""],
|
|
33
|
+
],
|
|
34
|
+
},
|
|
35
|
+
}),
|
|
36
|
+
].join("\n");
|
|
37
|
+
const result = parseYarnOutdated(stdout);
|
|
38
|
+
assert.deepEqual(result, [{ name: "lodash", current: "4.17.20", wanted: "", latest: "4.17.21" }]);
|
|
39
|
+
});
|
|
40
|
+
test("go: parses ndjson module stream, skips main/indirect/no-update", () => {
|
|
41
|
+
const stdout = [
|
|
42
|
+
JSON.stringify({ Path: "example.com/app", Main: true }),
|
|
43
|
+
JSON.stringify({ Path: "example.com/indirect", Version: "v1.0.0", Indirect: true, Update: { Version: "v1.1.0" } }),
|
|
44
|
+
JSON.stringify({ Path: "example.com/nodiff", Version: "v1.0.0", Update: { Version: "v1.0.0" } }),
|
|
45
|
+
JSON.stringify({ Path: "example.com/direct", Version: "v1.0.0", Update: { Version: "v1.2.0" } }),
|
|
46
|
+
].join("\n");
|
|
47
|
+
const result = parseGoListOutdated(stdout);
|
|
48
|
+
assert.deepEqual(result, [{ name: "example.com/direct", current: "v1.0.0", wanted: "", latest: "v1.2.0" }]);
|
|
49
|
+
});
|
|
50
|
+
test("composer: parses `composer outdated --format json`", () => {
|
|
51
|
+
const stdout = JSON.stringify({
|
|
52
|
+
installed: [
|
|
53
|
+
{ name: "monolog/monolog", version: "2.0.0", latest: "3.5.0" },
|
|
54
|
+
{ name: "psr/log", version: "1.1.4", latest: "1.1.4" },
|
|
55
|
+
],
|
|
56
|
+
});
|
|
57
|
+
const result = parseComposerOutdated(stdout);
|
|
58
|
+
assert.deepEqual(result, [{ name: "monolog/monolog", current: "2.0.0", wanted: "", latest: "3.5.0" }]);
|
|
59
|
+
});
|
|
60
|
+
test("pub: parses `dart pub outdated --json`", () => {
|
|
61
|
+
const stdout = JSON.stringify({
|
|
62
|
+
packages: [
|
|
63
|
+
{ package: "http", current: { version: "0.13.0" }, latest: { version: "1.2.0" } },
|
|
64
|
+
{ package: "path", current: { version: "1.8.0" }, latest: { version: "1.8.0" } },
|
|
65
|
+
],
|
|
66
|
+
});
|
|
67
|
+
const result = parsePubOutdated(stdout);
|
|
68
|
+
assert.deepEqual(result, [{ name: "http", current: "0.13.0", wanted: "", latest: "1.2.0" }]);
|
|
69
|
+
});
|
|
70
|
+
test("nuget: parses `dotnet list package --outdated` table", () => {
|
|
71
|
+
const stdout = [
|
|
72
|
+
"Project 'App' has the following updates to its packages",
|
|
73
|
+
" [net8.0]:",
|
|
74
|
+
" Top-level Package Requested Resolved Latest",
|
|
75
|
+
" > Newtonsoft.Json 13.0.1 13.0.1 13.0.3",
|
|
76
|
+
].join("\n");
|
|
77
|
+
const result = parseNugetOutdated(stdout);
|
|
78
|
+
assert.deepEqual(result, [{ name: "Newtonsoft.Json", current: "13.0.1", wanted: "13.0.1", latest: "13.0.3" }]);
|
|
79
|
+
});
|
|
80
|
+
test("cargo: parses [dependencies] section of Cargo.toml", () => {
|
|
81
|
+
const raw = [
|
|
82
|
+
"[package]",
|
|
83
|
+
'name = "app"',
|
|
84
|
+
"",
|
|
85
|
+
"[dependencies]",
|
|
86
|
+
'serde = "1.0"',
|
|
87
|
+
'tokio = "^1.28"',
|
|
88
|
+
"",
|
|
89
|
+
"[dev-dependencies]",
|
|
90
|
+
'criterion = "0.5"',
|
|
91
|
+
].join("\n");
|
|
92
|
+
const result = parseCargoToml(raw);
|
|
93
|
+
assert.deepEqual([...result.entries()], [["serde", "1.0"], ["tokio", "1.28"]]);
|
|
94
|
+
});
|
|
95
|
+
test("maven: parses <dependency> blocks from pom.xml", () => {
|
|
96
|
+
const raw = `<project>
|
|
97
|
+
<dependencies>
|
|
98
|
+
<dependency>
|
|
99
|
+
<groupId>org.apache.commons</groupId>
|
|
100
|
+
<artifactId>commons-lang3</artifactId>
|
|
101
|
+
<version>3.12.0</version>
|
|
102
|
+
</dependency>
|
|
103
|
+
</dependencies>
|
|
104
|
+
</project>`;
|
|
105
|
+
const result = parsePomXml(raw);
|
|
106
|
+
assert.deepEqual(result, [{ name: "org.apache.commons:commons-lang3", version: "3.12.0" }]);
|
|
107
|
+
});
|
|
108
|
+
test("gradle: parses implementation(...) dependency declarations", () => {
|
|
109
|
+
const raw = `dependencies {
|
|
110
|
+
implementation("com.google.guava:guava:31.1-jre")
|
|
111
|
+
testImplementation 'junit:junit:4.13.2'
|
|
112
|
+
}`;
|
|
113
|
+
const result = parseGradleBuildFile(raw);
|
|
114
|
+
assert.deepEqual(result, [
|
|
115
|
+
{ group: "com.google.guava", artifact: "guava", version: "31.1-jre" },
|
|
116
|
+
{ group: "junit", artifact: "junit", version: "4.13.2" },
|
|
117
|
+
]);
|
|
118
|
+
});
|
|
119
|
+
test("bundler: parses `bundle outdated` gem lines", () => {
|
|
120
|
+
const stdout = [
|
|
121
|
+
"Fetching gem metadata...",
|
|
122
|
+
"Gem Current Latest Requested Groups",
|
|
123
|
+
"* rails (newest 7.1.2, installed 7.0.4, requested ~> 7.0) in groups \"default\"",
|
|
124
|
+
"* rake (newest 13.1.0, installed 13.1.0)",
|
|
125
|
+
].join("\n");
|
|
126
|
+
const result = parseBundlerOutdated(stdout);
|
|
127
|
+
assert.deepEqual(result, [{ name: "rails", current: "7.0.4", wanted: "", latest: "7.1.2" }]);
|
|
128
|
+
});
|
|
129
|
+
test("poetry: parses `poetry show -o` plain-text table", () => {
|
|
130
|
+
const stdout = ["requests 2.28.0 2.31.0 HTTP library", "click 8.1.3 8.1.3 CLI toolkit"].join("\n");
|
|
131
|
+
const result = parsePoetryOutdated(stdout);
|
|
132
|
+
assert.deepEqual(result, [{ name: "requests", current: "2.28.0", wanted: "", latest: "2.31.0" }]);
|
|
133
|
+
});
|
|
134
|
+
test("cocoapods: parses `pod outdated` list lines, using the (latest version) suffix", () => {
|
|
135
|
+
// The value after "->" is only what the Podfile's constraint resolves
|
|
136
|
+
// to (often == current for an exact pin, as with AFNetworking below) —
|
|
137
|
+
// the true latest is only in the "(latest version X)" suffix.
|
|
138
|
+
const stdout = [
|
|
139
|
+
"The following pod updates are available:",
|
|
140
|
+
"- AFNetworking 3.2.1 -> 3.2.1 (latest version 4.0.1)",
|
|
141
|
+
"- Alamofire 5.6.0 -> 5.6.0 (latest version 5.6.0)",
|
|
142
|
+
].join("\n");
|
|
143
|
+
const result = parseCocoapodsOutdated(stdout);
|
|
144
|
+
assert.deepEqual(result, [{ name: "AFNetworking", current: "3.2.1", wanted: "", latest: "4.0.1" }]);
|
|
145
|
+
});
|
|
146
|
+
test("mix: parses `mix hex.outdated` table rows, including the Only column", () => {
|
|
147
|
+
// Real `mix hex.outdated` output always has the "Only" column; it's blank
|
|
148
|
+
// for deps with no `only:` restriction (e.g. jason below).
|
|
149
|
+
const stdout = [
|
|
150
|
+
"Dependency Only Current Latest Status ",
|
|
151
|
+
"ex_doc dev 0.29.0 0.40.3 Update not possible ",
|
|
152
|
+
"jason 1.2.0 1.4.5 Update not possible ",
|
|
153
|
+
"",
|
|
154
|
+
"Run `mix hex.outdated APP` to see requirements for a specific dependency.",
|
|
155
|
+
].join("\n");
|
|
156
|
+
const result = parseMixOutdated(stdout);
|
|
157
|
+
assert.deepEqual(result, [
|
|
158
|
+
{ name: "ex_doc", current: "0.29.0", wanted: "", latest: "0.40.3" },
|
|
159
|
+
{ name: "jason", current: "1.2.0", wanted: "", latest: "1.4.5" },
|
|
160
|
+
]);
|
|
161
|
+
});
|
|
162
|
+
test("conan: parses [requires] section of conanfile.txt", () => {
|
|
163
|
+
const raw = ["[requires]", "zlib/1.2.13", "boost/1.83.0", "", "[generators]", "CMakeDeps"].join("\n");
|
|
164
|
+
const result = parseConanfileTxt(raw);
|
|
165
|
+
assert.deepEqual(result, [
|
|
166
|
+
{ name: "zlib", version: "1.2.13" },
|
|
167
|
+
{ name: "boost", version: "1.83.0" },
|
|
168
|
+
]);
|
|
169
|
+
});
|
|
170
|
+
test("npm: parses `npm outdated --json` output, dropping entries already at latest", () => {
|
|
171
|
+
const data = {
|
|
172
|
+
lodash: { current: "4.17.20", latest: "4.17.21" },
|
|
173
|
+
axios: { current: "1.3.0", latest: "1.3.0" },
|
|
174
|
+
};
|
|
175
|
+
const result = parseNpmOutdated(data);
|
|
176
|
+
assert.deepEqual(result, [{ name: "lodash", current: "4.17.20", wanted: "", latest: "4.17.21" }]);
|
|
177
|
+
});
|
|
178
|
+
test("npm: null data (non-JSON stdout) yields empty result", () => {
|
|
179
|
+
assert.deepEqual(parseNpmOutdated(null), []);
|
|
180
|
+
});
|
|
181
|
+
test("pnpm: parses `pnpm outdated --format json`, tolerating missing current/latest", () => {
|
|
182
|
+
const data = {
|
|
183
|
+
lodash: { current: "4.17.20", latest: "4.17.21" },
|
|
184
|
+
// pnpm can omit fields for workspace-linked packages; guard against undefined.
|
|
185
|
+
weird: {},
|
|
186
|
+
};
|
|
187
|
+
const result = parsePnpmOutdated(data);
|
|
188
|
+
assert.deepEqual(result, [{ name: "lodash", current: "4.17.20", wanted: "", latest: "4.17.21" }]);
|
|
189
|
+
});
|
|
190
|
+
test("pnpm: null data yields empty result", () => {
|
|
191
|
+
assert.deepEqual(parsePnpmOutdated(null), []);
|
|
192
|
+
});
|
|
193
|
+
test("pip: parses `pip list --outdated --format json`", () => {
|
|
194
|
+
const data = [
|
|
195
|
+
{ name: "requests", version: "2.28.0", latest_version: "2.31.0" },
|
|
196
|
+
{ name: "flask", version: "2.0.0", latest_version: "2.3.3" },
|
|
197
|
+
];
|
|
198
|
+
const result = parsePipOutdated(data);
|
|
199
|
+
assert.deepEqual(result, [
|
|
200
|
+
{ name: "requests", current: "2.28.0", wanted: "", latest: "2.31.0" },
|
|
201
|
+
{ name: "flask", current: "2.0.0", wanted: "", latest: "2.3.3" },
|
|
202
|
+
]);
|
|
203
|
+
});
|
|
204
|
+
test("pip: null data yields empty result", () => {
|
|
205
|
+
assert.deepEqual(parsePipOutdated(null), []);
|
|
206
|
+
});
|
|
207
|
+
test("uv: parses `uv pip list --outdated --format json` (same shape as pip)", () => {
|
|
208
|
+
const data = [{ name: "requests", version: "2.28.0", latest_version: "2.31.0" }];
|
|
209
|
+
const result = parseUvOutdated(data);
|
|
210
|
+
assert.deepEqual(result, [{ name: "requests", current: "2.28.0", wanted: "", latest: "2.31.0" }]);
|
|
211
|
+
});
|
|
212
|
+
test("uv: null data yields empty result", () => {
|
|
213
|
+
assert.deepEqual(parseUvOutdated(null), []);
|
|
214
|
+
});
|
|
215
|
+
test("pipenv: parses `pipenv run pip list --outdated --format json`", () => {
|
|
216
|
+
const data = [{ name: "django", version: "4.0.0", latest_version: "4.2.5" }];
|
|
217
|
+
const result = parsePipenvOutdated(data);
|
|
218
|
+
assert.deepEqual(result, [{ name: "django", current: "4.0.0", wanted: "", latest: "4.2.5" }]);
|
|
219
|
+
});
|
|
220
|
+
test("pipenv: null data yields empty result", () => {
|
|
221
|
+
assert.deepEqual(parsePipenvOutdated(null), []);
|
|
222
|
+
});
|
|
223
|
+
test("elm: extracts direct dependencies from elm.json, ignoring indirect deps", () => {
|
|
224
|
+
const json = {
|
|
225
|
+
dependencies: {
|
|
226
|
+
direct: { "elm/core": "1.0.5", "elm/json": "1.1.3" },
|
|
227
|
+
indirect: { "elm/bytes": "1.0.8" },
|
|
228
|
+
},
|
|
229
|
+
};
|
|
230
|
+
assert.deepEqual(parseElmDirectDeps(json), { "elm/core": "1.0.5", "elm/json": "1.1.3" });
|
|
231
|
+
});
|
|
232
|
+
test("elm: missing dependencies field yields empty object", () => {
|
|
233
|
+
assert.deepEqual(parseElmDirectDeps({}), {});
|
|
234
|
+
});
|
|
235
|
+
test("swiftpm: parses Package.resolved pins into a name-to-version map", () => {
|
|
236
|
+
const raw = JSON.stringify({
|
|
237
|
+
pins: [
|
|
238
|
+
{ identity: "swift-log", state: { version: "1.5.3" } },
|
|
239
|
+
{ identity: "no-version-pin", state: {} },
|
|
240
|
+
],
|
|
241
|
+
});
|
|
242
|
+
assert.deepEqual(parsePackageResolved(raw), { "swift-log": "1.5.3" });
|
|
243
|
+
});
|
|
244
|
+
test("swiftpm: extracts .package(url:, from:) declarations and joins with resolved versions", () => {
|
|
245
|
+
const manifest = [
|
|
246
|
+
".package(url: \"https://github.com/apple/swift-log.git\", from: \"1.4.0\")",
|
|
247
|
+
".package(url: \"https://github.com/apple/swift-nio.git\", from: \"2.0.0\")",
|
|
248
|
+
].join("\n");
|
|
249
|
+
const resolved = { "swift-log": "1.5.3" }; // swift-nio has no resolved pin (e.g. not yet fetched)
|
|
250
|
+
const result = parsePackageSwiftDeps(manifest, resolved);
|
|
251
|
+
assert.deepEqual(result, [
|
|
252
|
+
{ name: "swift-log", current: "1.5.3", url: "https://github.com/apple/swift-log.git" },
|
|
253
|
+
]);
|
|
254
|
+
});
|
|
255
|
+
//# sourceMappingURL=parsers.test.js.map
|