greenbump 0.8.0 → 0.9.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.
Files changed (109) hide show
  1. package/README.md +58 -15
  2. package/README.zh-CN.md +37 -4
  3. package/dist/action/github.test.js +37 -0
  4. package/dist/action/github.test.js.map +1 -0
  5. package/dist/action/main.js +19 -4
  6. package/dist/action/main.js.map +1 -1
  7. package/dist/agent/fixer.js +201 -25
  8. package/dist/agent/fixer.js.map +1 -1
  9. package/dist/agent/fixer.test.js +221 -0
  10. package/dist/agent/fixer.test.js.map +1 -1
  11. package/dist/benchmarks/run.js +0 -0
  12. package/dist/benchmarks/run.js.map +1 -0
  13. package/dist/cli/interactive.js +46 -11
  14. package/dist/cli/interactive.js.map +1 -1
  15. package/dist/cli.js +35 -7
  16. package/dist/cli.js.map +1 -1
  17. package/dist/engine/analyzers/changelog-parser.js +19 -0
  18. package/dist/engine/analyzers/changelog-parser.js.map +1 -1
  19. package/dist/engine/batch.js +54 -10
  20. package/dist/engine/batch.js.map +1 -1
  21. package/dist/engine/cache/manager.js +19 -1
  22. package/dist/engine/cache/manager.js.map +1 -1
  23. package/dist/engine/cache/manager.test.js +17 -0
  24. package/dist/engine/cache/manager.test.js.map +1 -1
  25. package/dist/engine/change-detector.js +52 -23
  26. package/dist/engine/change-detector.js.map +1 -1
  27. package/dist/engine/change-detector.test.js +72 -0
  28. package/dist/engine/change-detector.test.js.map +1 -0
  29. package/dist/engine/changelog.js +22 -2
  30. package/dist/engine/changelog.js.map +1 -1
  31. package/dist/engine/changelog.test.js +31 -0
  32. package/dist/engine/changelog.test.js.map +1 -0
  33. package/dist/engine/checks.js +10 -4
  34. package/dist/engine/checks.js.map +1 -1
  35. package/dist/engine/context/optimizer.js +14 -44
  36. package/dist/engine/context/optimizer.js.map +1 -1
  37. package/dist/engine/context/optimizer.test.js +23 -1
  38. package/dist/engine/context/optimizer.test.js.map +1 -1
  39. package/dist/engine/ecosystems/cargo.js +2 -1
  40. package/dist/engine/ecosystems/cargo.js.map +1 -1
  41. package/dist/engine/ecosystems/conan.js +2 -1
  42. package/dist/engine/ecosystems/conan.js.map +1 -1
  43. package/dist/engine/ecosystems/elm.js +2 -1
  44. package/dist/engine/ecosystems/elm.js.map +1 -1
  45. package/dist/engine/ecosystems/gradle.js +2 -1
  46. package/dist/engine/ecosystems/gradle.js.map +1 -1
  47. package/dist/engine/ecosystems/http.js +18 -0
  48. package/dist/engine/ecosystems/http.js.map +1 -0
  49. package/dist/engine/ecosystems/maven.js +2 -1
  50. package/dist/engine/ecosystems/maven.js.map +1 -1
  51. package/dist/engine/ecosystems/swiftpm.js +2 -1
  52. package/dist/engine/ecosystems/swiftpm.js.map +1 -1
  53. package/dist/engine/exec.js +37 -7
  54. package/dist/engine/exec.js.map +1 -1
  55. package/dist/engine/exec.test.js +36 -0
  56. package/dist/engine/exec.test.js.map +1 -0
  57. package/dist/engine/fixer/patterns.js +55 -18
  58. package/dist/engine/fixer/patterns.js.map +1 -1
  59. package/dist/engine/fixer/patterns.test.js +95 -1
  60. package/dist/engine/fixer/patterns.test.js.map +1 -1
  61. package/dist/engine/git.js +38 -16
  62. package/dist/engine/git.js.map +1 -1
  63. package/dist/engine/git.test.js +51 -1
  64. package/dist/engine/git.test.js.map +1 -1
  65. package/dist/engine/perf/metrics.js +44 -57
  66. package/dist/engine/perf/metrics.js.map +1 -1
  67. package/dist/engine/run.js +120 -12
  68. package/dist/engine/run.js.map +1 -1
  69. package/dist/engine/run.test.js +65 -1
  70. package/dist/engine/run.test.js.map +1 -1
  71. package/dist/engine/sandbox/docker-manager.js +52 -26
  72. package/dist/engine/sandbox/docker-manager.js.map +1 -1
  73. package/dist/engine/sandbox/template-generator.js +0 -4
  74. package/dist/engine/sandbox/template-generator.js.map +1 -1
  75. package/dist/engine/sandbox/template-generator.test.js +16 -0
  76. package/dist/engine/sandbox/template-generator.test.js.map +1 -0
  77. package/dist/engine/scan/benchmark-crosscheck.test.js +116 -0
  78. package/dist/engine/scan/benchmark-crosscheck.test.js.map +1 -0
  79. package/dist/engine/scan/detect.js +96 -0
  80. package/dist/engine/scan/detect.js.map +1 -0
  81. package/dist/engine/scan/impact.js +128 -0
  82. package/dist/engine/scan/impact.js.map +1 -0
  83. package/dist/engine/scan/impact.test.js +217 -0
  84. package/dist/engine/scan/impact.test.js.map +1 -0
  85. package/dist/engine/scan/indexer.js +129 -0
  86. package/dist/engine/scan/indexer.js.map +1 -0
  87. package/dist/engine/scan/indexer.test.js +149 -0
  88. package/dist/engine/scan/indexer.test.js.map +1 -0
  89. package/dist/engine/scan/probes.js +92 -0
  90. package/dist/engine/scan/probes.js.map +1 -0
  91. package/dist/engine/scan/probes.test.js +100 -0
  92. package/dist/engine/scan/probes.test.js.map +1 -0
  93. package/dist/engine/verify.js +41 -58
  94. package/dist/engine/verify.js.map +1 -1
  95. package/dist/engine/verify.test.js +49 -0
  96. package/dist/engine/verify.test.js.map +1 -0
  97. package/dist/format.js +81 -0
  98. package/dist/format.js.map +1 -1
  99. package/dist/format.test.js +8 -0
  100. package/dist/format.test.js.map +1 -1
  101. package/dist/pr.test.js +48 -0
  102. package/dist/pr.test.js.map +1 -0
  103. package/package.json +4 -4
  104. package/dist/engine/dep-graph.js +0 -101
  105. package/dist/engine/dep-graph.js.map +0 -1
  106. package/dist/engine/staged-fix.js +0 -108
  107. package/dist/engine/staged-fix.js.map +0 -1
  108. package/dist/engine/stages.js +0 -125
  109. package/dist/engine/stages.js.map +0 -1
@@ -0,0 +1,217 @@
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, dirname } from "node:path";
6
+ import { buildScanReport } from "./impact.js";
7
+ import { WORKSPACE_ROOT } from "../workspace.js";
8
+ import { getCache, resetCacheForTests } from "../cache/manager.js";
9
+ const DEFAULT_PKG = { name: "bench", private: true, dependencies: {} };
10
+ function outdated(overrides) {
11
+ return {
12
+ name: "zod",
13
+ current: "3.23.8",
14
+ wanted: "3.24.0",
15
+ latest: "4.0.5",
16
+ workspacePath: WORKSPACE_ROOT,
17
+ ...overrides,
18
+ };
19
+ }
20
+ /**
21
+ * Build a temp project plus an isolated cache dir. Seeds the digest cache via
22
+ * the same `digest::pkg--from--to` key the real digest writer uses.
23
+ */
24
+ async function withScanEnv(files, fn, seedDigest) {
25
+ const dir = await mkdtemp(join(tmpdir(), "greenbump-impact-"));
26
+ const cacheDir = await mkdtemp(join(tmpdir(), "greenbump-impact-cache-"));
27
+ const prevCache = process.env.GREENBUMP_CACHE_DIR;
28
+ process.env.GREENBUMP_CACHE_DIR = cacheDir;
29
+ resetCacheForTests();
30
+ for (const [path, content] of Object.entries(files)) {
31
+ await mkdir(dirname(join(dir, path)), { recursive: true });
32
+ await writeFile(join(dir, path), content, "utf8");
33
+ }
34
+ if (seedDigest) {
35
+ const cache = getCache();
36
+ await cache.init();
37
+ await cache.setLlmFix(`digest::${seedDigest.name}--${seedDigest.from}--${seedDigest.to}`, {
38
+ edits: { __digest__: JSON.stringify(seedDigest.changes) },
39
+ hits: 0,
40
+ model: "stub",
41
+ });
42
+ }
43
+ try {
44
+ await fn(dir);
45
+ }
46
+ finally {
47
+ if (prevCache === undefined)
48
+ delete process.env.GREENBUMP_CACHE_DIR;
49
+ else
50
+ process.env.GREENBUMP_CACHE_DIR = prevCache;
51
+ resetCacheForTests();
52
+ await rm(cacheDir, { recursive: true, force: true });
53
+ await rm(dir, { recursive: true, force: true });
54
+ }
55
+ }
56
+ test("buildScanReport: codemod tier reports file/line impact with no cache and no LLM", async () => {
57
+ await withScanEnv({
58
+ "package.json": JSON.stringify(DEFAULT_PKG),
59
+ "src/validate.js": 'const r = schema.safeParse(d);\nreturn r.error.errors[0].message;\n',
60
+ }, async (dir) => {
61
+ const report = await buildScanReport(dir, [outdated({})]);
62
+ assert.equal(report.schemaVersion, 1);
63
+ assert.equal(report.summary.depsScanned, 1);
64
+ const det = report.detections[0];
65
+ assert.equal(det.name, "zod");
66
+ assert.equal(det.kind, "codemod");
67
+ assert.equal(det.status, "ok");
68
+ const errors = det.changes.find((c) => c.description.includes("error.issues"));
69
+ assert.ok(errors, "expected the error.errors codemod to be reported");
70
+ assert.equal(errors.matches, 1);
71
+ assert.equal(errors.fileGroups.length, 1);
72
+ assert.equal(errors.fileGroups[0].file, join("src", "validate.js"));
73
+ assert.equal(errors.fileGroups[0].locations[0].line, 2);
74
+ assert.equal(det.filesMatched, 1);
75
+ assert.equal(det.locationsMatched, 1);
76
+ });
77
+ });
78
+ test("buildScanReport: wildcard codemods are not misattributed to an unrelated dep", async () => {
79
+ await withScanEnv({
80
+ "package.json": JSON.stringify(DEFAULT_PKG),
81
+ "test.js": 'assert.equal(a, b);\nconst r = z.safeParse(x);\nr.error.errors;\n',
82
+ }, async (dir) => {
83
+ const report = await buildScanReport(dir, [outdated({})]);
84
+ const det = report.detections[0];
85
+ // `assert.equal` is a `package: "*"` codemod — it must not appear as a zod change.
86
+ assert.ok(!det.changes.some((c) => c.description.includes("assert.equal")), "wildcard codemod leaked into the zod detection");
87
+ assert.ok(!det.changes.some((c) => c.description.includes("util.is")));
88
+ });
89
+ });
90
+ test("buildScanReport: an upgrade with no matching codemod and no cache is reported unknown", async () => {
91
+ await withScanEnv({ "package.json": JSON.stringify(DEFAULT_PKG), "a.js": "whatever();\n" }, async (dir) => {
92
+ const report = await buildScanReport(dir, [
93
+ outdated({ name: "left-pad", current: "1.0.0", latest: "2.0.0" }),
94
+ ]);
95
+ const det = report.detections[0];
96
+ assert.equal(det.status, "unknown");
97
+ assert.equal(det.changes.length, 0);
98
+ assert.equal(det.displayedImpact, false);
99
+ assert.equal(report.summary.depsWithKnownBreaking, 0);
100
+ });
101
+ });
102
+ test("buildScanReport: no cache hit means no filesystem-wide guessing (never calls an LLM)", async () => {
103
+ await withScanEnv({ "package.json": JSON.stringify(DEFAULT_PKG), "a.js": "err.error.errors;\n" }, async (dir) => {
104
+ // No cached digest for left-pad: the zod-only codemod must not fire for it,
105
+ // and nothing else may invent a result.
106
+ const report = await buildScanReport(dir, [
107
+ outdated({ name: "left-pad", current: "1.0.0", latest: "2.0.0" }),
108
+ ]);
109
+ assert.equal(report.detections[0].status, "unknown");
110
+ assert.deepEqual(report.detections[0].changes, []);
111
+ });
112
+ });
113
+ test("buildScanReport: reads a seeded digest from cache and maps it to project locations", async () => {
114
+ await withScanEnv({
115
+ "package.json": JSON.stringify(DEFAULT_PKG),
116
+ "src/api.js": "const r = client.oldMethod();\n",
117
+ }, async (dir) => {
118
+ const report = await buildScanReport(dir, [
119
+ outdated({ name: "some-lib", current: "1.0.0", latest: "2.0.0" }),
120
+ ]);
121
+ const det = report.detections[0];
122
+ assert.equal(det.kind, "digest");
123
+ assert.equal(det.status, "ok");
124
+ const change = det.changes.find((c) => c.description.includes("oldMethod"));
125
+ assert.ok(change, "expected the cached digest change to be reported");
126
+ assert.equal(change.source, "changelog-cache");
127
+ assert.equal(change.matches, 1);
128
+ assert.equal(change.fileGroups[0].file, join("src", "api.js"));
129
+ assert.equal(change.fileGroups[0].locations[0].line, 1);
130
+ }, {
131
+ name: "some-lib",
132
+ from: "1.0.0",
133
+ to: "2.0.0",
134
+ changes: [
135
+ {
136
+ description: "oldMethod was replaced by newMethod",
137
+ oldApi: "client.oldMethod()",
138
+ newApi: "client.newMethod()",
139
+ severity: "high",
140
+ },
141
+ ],
142
+ });
143
+ });
144
+ test("buildScanReport: --no-cache ignores a seeded digest and falls back to codemods", async () => {
145
+ await withScanEnv({
146
+ "package.json": JSON.stringify(DEFAULT_PKG),
147
+ "src/api.js": "const r = client.oldMethod();\n",
148
+ }, async (dir) => {
149
+ const report = await buildScanReport(dir, [outdated({ name: "some-lib", current: "1.0.0", latest: "2.0.0" })], { noCache: true });
150
+ const det = report.detections[0];
151
+ assert.equal(det.status, "unknown");
152
+ assert.deepEqual(det.changes, []);
153
+ }, {
154
+ name: "some-lib",
155
+ from: "1.0.0",
156
+ to: "2.0.0",
157
+ changes: [
158
+ { description: "oldMethod gone", oldApi: "client.oldMethod()", severity: "high" },
159
+ ],
160
+ });
161
+ });
162
+ test("buildScanReport: breaking changes that touch nothing report zero impact, not an error", async () => {
163
+ await withScanEnv({ "package.json": JSON.stringify(DEFAULT_PKG), "src/clean.js": "const x = 1;\n" }, async (dir) => {
164
+ const report = await buildScanReport(dir, [outdated({})]);
165
+ const det = report.detections[0];
166
+ // zod's codemods are known for this upgrade path...
167
+ assert.ok(det.changes.length > 0);
168
+ // ...but none of them appear in this codebase.
169
+ assert.equal(det.filesMatched, 0);
170
+ assert.equal(det.locationsMatched, 0);
171
+ assert.equal(det.displayedImpact, false);
172
+ // Status stays "ok": we *know* the breaking changes, they just don't apply.
173
+ assert.equal(det.status, "ok");
174
+ });
175
+ });
176
+ test("buildScanReport: aggregates a multi-dependency summary", async () => {
177
+ await withScanEnv({
178
+ "package.json": JSON.stringify(DEFAULT_PKG),
179
+ "src/a.js": "r.error.errors;\n",
180
+ "src/b.js": "<Switch>\n",
181
+ }, async (dir) => {
182
+ const report = await buildScanReport(dir, [
183
+ outdated({}),
184
+ outdated({ name: "react-router-dom", current: "5.3.4", latest: "6.30.0" }),
185
+ outdated({ name: "left-pad", current: "1.0.0", latest: "2.0.0" }),
186
+ ]);
187
+ assert.equal(report.summary.depsScanned, 3);
188
+ assert.equal(report.summary.depsWithKnownBreaking, 2); // zod + react-router-dom
189
+ assert.equal(report.summary.filesAffected, 2);
190
+ assert.equal(report.summary.locationsAffected, 2);
191
+ assert.equal(report.detections.length, 3);
192
+ });
193
+ });
194
+ test("buildScanReport: lists file creations for a scaffolding codemod", async () => {
195
+ await withScanEnv({ "package.json": JSON.stringify(DEFAULT_PKG), ".eslintrc.js": "module.exports = {};\n" }, async (dir) => {
196
+ const report = await buildScanReport(dir, [
197
+ outdated({ name: "eslint", current: "8.57.0", latest: "9.0.0" }),
198
+ ]);
199
+ const det = report.detections[0];
200
+ assert.ok(det.fileCreations?.some((f) => f.path === "eslint.config.js"));
201
+ });
202
+ });
203
+ test("buildScanReport: workspace path is carried through to the detection", async () => {
204
+ await withScanEnv({ "package.json": JSON.stringify(DEFAULT_PKG), "src/a.js": "r.error.errors;\n" }, async (dir) => {
205
+ const report = await buildScanReport(dir, [outdated({ workspacePath: "packages/api" })]);
206
+ assert.equal(report.detections[0].workspacePath, "packages/api");
207
+ });
208
+ });
209
+ test("buildScanReport: empty outdated list produces an empty, well-formed report", async () => {
210
+ await withScanEnv({ "package.json": JSON.stringify(DEFAULT_PKG) }, async (dir) => {
211
+ const report = await buildScanReport(dir, []);
212
+ assert.equal(report.summary.depsScanned, 0);
213
+ assert.equal(report.summary.filesAffected, 0);
214
+ assert.deepEqual(report.detections, []);
215
+ });
216
+ });
217
+ //# sourceMappingURL=impact.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"impact.test.js","sourceRoot":"","sources":["../../../src/engine/scan/impact.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,129 @@
1
+ import { readFile, readdir, stat } from "node:fs/promises";
2
+ import { join, relative } from "node:path";
3
+ import { isSensitivePath } from "../../agent/guard.js";
4
+ import { REQUIRES_IMPORT_PREFIX, escapeRegExpLiteral } from "./probes.js";
5
+ /**
6
+ * Single-pass file walker for impact analysis.
7
+ *
8
+ * Scans the project tree once, checking every line against every probe.
9
+ * Optimized for "find which files/lines match any of these patterns" —
10
+ * the inverse of `searchCode` (which is "find lines matching this one pattern").
11
+ */
12
+ const IGNORE_DIRS = new Set(["node_modules", ".git", "dist", "build", ".next", "coverage", ".turbo"]);
13
+ const SEARCHABLE_EXT = new Set([
14
+ ".js", ".jsx", ".mjs", ".cjs", ".ts", ".tsx", ".mts", ".cts",
15
+ ".json", ".vue", ".svelte",
16
+ ]);
17
+ const MAX_FILE_SIZE = 1_000_000;
18
+ /**
19
+ * Scan the project once, finding all matches for all probes.
20
+ *
21
+ * Returns hits grouped by probe key, plus a set of probe keys that hit the
22
+ * per-probe cap (indicating their match count is a lower bound).
23
+ */
24
+ export async function scanOnce(cwd, probes, opts) {
25
+ const hits = [];
26
+ const truncatedProbes = new Set();
27
+ const probeHitsCount = new Map();
28
+ // Compile each probe's regex once, not once per line per probe. A pattern
29
+ // that fails to compile falls back to a literal match (same policy as the
30
+ // fix loop's search_code tool).
31
+ const compiled = [];
32
+ for (const probe of probes) {
33
+ probeHitsCount.set(probe.key, 0);
34
+ let re;
35
+ try {
36
+ re = new RegExp(probe.pattern, "g");
37
+ }
38
+ catch {
39
+ re = new RegExp(escapeRegExpLiteral(probe.pattern), "g");
40
+ }
41
+ compiled.push({ probe, re });
42
+ }
43
+ async function walk(dir) {
44
+ let entries;
45
+ try {
46
+ entries = await readdir(dir, { withFileTypes: true });
47
+ }
48
+ catch {
49
+ return;
50
+ }
51
+ for (const e of entries) {
52
+ if (IGNORE_DIRS.has(e.name))
53
+ continue;
54
+ const abs = join(dir, e.name);
55
+ if (e.isDirectory()) {
56
+ await walk(abs);
57
+ continue;
58
+ }
59
+ const dot = e.name.lastIndexOf(".");
60
+ if (dot < 0 || !SEARCHABLE_EXT.has(e.name.slice(dot)))
61
+ continue;
62
+ if (isSensitivePath(e.name))
63
+ continue;
64
+ let content;
65
+ try {
66
+ const s = await stat(abs);
67
+ if (s.size > MAX_FILE_SIZE)
68
+ continue;
69
+ content = await readFile(abs, "utf8");
70
+ }
71
+ catch {
72
+ continue;
73
+ }
74
+ const rel = relative(cwd, abs);
75
+ // Which packages this file imports — resolves the bare-identifier gate
76
+ // (`__requiresImport:<pkg>`) for probes in this same file, so the file
77
+ // is read exactly once.
78
+ const importsHere = new Set();
79
+ for (const m of content.matchAll(/(?:from|require\()\s*['"]([^'"]+)['"]/g)) {
80
+ importsHere.add(m[1]);
81
+ }
82
+ const lines = content.split("\n");
83
+ for (const { probe, re } of compiled) {
84
+ if ((probeHitsCount.get(probe.key) ?? 0) >= opts.perProbeCap) {
85
+ truncatedProbes.add(probe.key);
86
+ continue;
87
+ }
88
+ // File-level gates: substring filter, or import scope for bare identifiers.
89
+ if (probe.fileFilter) {
90
+ if (probe.fileFilter.startsWith(REQUIRES_IMPORT_PREFIX)) {
91
+ const pkg = probe.fileFilter.slice(REQUIRES_IMPORT_PREFIX.length);
92
+ if (!importsHere.has(pkg))
93
+ continue;
94
+ }
95
+ else if (!rel.includes(probe.fileFilter)) {
96
+ continue;
97
+ }
98
+ }
99
+ for (let i = 0; i < lines.length; i++) {
100
+ const line = lines[i];
101
+ re.lastIndex = 0;
102
+ let match;
103
+ while ((match = re.exec(line)) !== null) {
104
+ hits.push({
105
+ probeKey: probe.key,
106
+ file: rel,
107
+ line: i + 1,
108
+ excerpt: line.trim().slice(0, 200),
109
+ });
110
+ const n = (probeHitsCount.get(probe.key) ?? 0) + 1;
111
+ probeHitsCount.set(probe.key, n);
112
+ if (n >= opts.perProbeCap) {
113
+ truncatedProbes.add(probe.key);
114
+ break;
115
+ }
116
+ // Zero-width match guard — otherwise exec() would never advance.
117
+ if (match.index === re.lastIndex)
118
+ re.lastIndex++;
119
+ }
120
+ if ((probeHitsCount.get(probe.key) ?? 0) >= opts.perProbeCap)
121
+ break;
122
+ }
123
+ }
124
+ }
125
+ }
126
+ await walk(cwd);
127
+ return { hits, truncatedProbes };
128
+ }
129
+ //# sourceMappingURL=indexer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"indexer.js","sourceRoot":"","sources":["../../../src/engine/scan/indexer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAC3D,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,EAAE,sBAAsB,EAAE,mBAAmB,EAAc,MAAM,aAAa,CAAC;AAEtF;;;;;;GAMG;AAEH,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,CAAC,cAAc,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,QAAQ,CAAC,CAAC,CAAC;AACtG,MAAM,cAAc,GAAG,IAAI,GAAG,CAAC;IAC7B,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;IAC5D,OAAO,EAAE,MAAM,EAAE,SAAS;CAC3B,CAAC,CAAC;AACH,MAAM,aAAa,GAAG,SAAS,CAAC;AAShC;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,QAAQ,CAC5B,GAAW,EACX,MAAe,EACf,IAA6B;IAE7B,MAAM,IAAI,GAAa,EAAE,CAAC;IAC1B,MAAM,eAAe,GAAG,IAAI,GAAG,EAAU,CAAC;IAC1C,MAAM,cAAc,GAAG,IAAI,GAAG,EAAkB,CAAC;IAEjD,0EAA0E;IAC1E,0EAA0E;IAC1E,gCAAgC;IAChC,MAAM,QAAQ,GAAwC,EAAE,CAAC;IACzD,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,cAAc,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;QACjC,IAAI,EAAU,CAAC;QACf,IAAI,CAAC;YACH,EAAE,GAAG,IAAI,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;QACtC,CAAC;QAAC,MAAM,CAAC;YACP,EAAE,GAAG,IAAI,MAAM,CAAC,mBAAmB,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,GAAG,CAAC,CAAC;QAC3D,CAAC;QACD,QAAQ,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,CAAC;IAC/B,CAAC;IAED,KAAK,UAAU,IAAI,CAAC,GAAW;QAC7B,IAAI,OAAO,CAAC;QACZ,IAAI,CAAC;YACH,OAAO,GAAG,MAAM,OAAO,CAAC,GAAG,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;QACxD,CAAC;QAAC,MAAM,CAAC;YACP,OAAO;QACT,CAAC;QACD,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;YACxB,IAAI,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC;gBAAE,SAAS;YACtC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;YAC9B,IAAI,CAAC,CAAC,WAAW,EAAE,EAAE,CAAC;gBACpB,MAAM,IAAI,CAAC,GAAG,CAAC,CAAC;gBAChB,SAAS;YACX,CAAC;YACD,MAAM,GAAG,GAAG,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;YACpC,IAAI,GAAG,GAAG,CAAC,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;gBAAE,SAAS;YAChE,IAAI,eAAe,CAAC,CAAC,CAAC,IAAI,CAAC;gBAAE,SAAS;YAEtC,IAAI,OAAe,CAAC;YACpB,IAAI,CAAC;gBACH,MAAM,CAAC,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,CAAC;gBAC1B,IAAI,CAAC,CAAC,IAAI,GAAG,aAAa;oBAAE,SAAS;gBACrC,OAAO,GAAG,MAAM,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;YACxC,CAAC;YAAC,MAAM,CAAC;gBACP,SAAS;YACX,CAAC;YAED,MAAM,GAAG,GAAG,QAAQ,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;YAE/B,uEAAuE;YACvE,uEAAuE;YACvE,wBAAwB;YACxB,MAAM,WAAW,GAAG,IAAI,GAAG,EAAU,CAAC;YACtC,KAAK,MAAM,CAAC,IAAI,OAAO,CAAC,QAAQ,CAAC,wCAAwC,CAAC,EAAE,CAAC;gBAC3E,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YACxB,CAAC;YAED,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YAClC,KAAK,MAAM,EAAE,KAAK,EAAE,EAAE,EAAE,IAAI,QAAQ,EAAE,CAAC;gBACrC,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;oBAC7D,eAAe,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;oBAC/B,SAAS;gBACX,CAAC;gBAED,4EAA4E;gBAC5E,IAAI,KAAK,CAAC,UAAU,EAAE,CAAC;oBACrB,IAAI,KAAK,CAAC,UAAU,CAAC,UAAU,CAAC,sBAAsB,CAAC,EAAE,CAAC;wBACxD,MAAM,GAAG,GAAG,KAAK,CAAC,UAAU,CAAC,KAAK,CAAC,sBAAsB,CAAC,MAAM,CAAC,CAAC;wBAClE,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC;4BAAE,SAAS;oBACtC,CAAC;yBAAM,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,UAAU,CAAC,EAAE,CAAC;wBAC3C,SAAS;oBACX,CAAC;gBACH,CAAC;gBAED,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;oBACtC,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;oBACtB,EAAE,CAAC,SAAS,GAAG,CAAC,CAAC;oBACjB,IAAI,KAA6B,CAAC;oBAClC,OAAO,CAAC,KAAK,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;wBACxC,IAAI,CAAC,IAAI,CAAC;4BACR,QAAQ,EAAE,KAAK,CAAC,GAAG;4BACnB,IAAI,EAAE,GAAG;4BACT,IAAI,EAAE,CAAC,GAAG,CAAC;4BACX,OAAO,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC;yBACnC,CAAC,CAAC;wBACH,MAAM,CAAC,GAAG,CAAC,cAAc,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;wBACnD,cAAc,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;wBACjC,IAAI,CAAC,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;4BAC1B,eAAe,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;4BAC/B,MAAM;wBACR,CAAC;wBACD,iEAAiE;wBACjE,IAAI,KAAK,CAAC,KAAK,KAAK,EAAE,CAAC,SAAS;4BAAE,EAAE,CAAC,SAAS,EAAE,CAAC;oBACnD,CAAC;oBACD,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,IAAI,IAAI,CAAC,WAAW;wBAAE,MAAM;gBACtE,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;IAED,MAAM,IAAI,CAAC,GAAG,CAAC,CAAC;IAEhB,OAAO,EAAE,IAAI,EAAE,eAAe,EAAE,CAAC;AACnC,CAAC"}
@@ -0,0 +1,149 @@
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, dirname } from "node:path";
6
+ import { scanOnce } from "./indexer.js";
7
+ import { REQUIRES_IMPORT_PREFIX } from "./probes.js";
8
+ async function withProject(files, fn) {
9
+ const dir = await mkdtemp(join(tmpdir(), "greenbump-scan-"));
10
+ for (const [path, content] of Object.entries(files)) {
11
+ await mkdir(dirname(join(dir, path)), { recursive: true });
12
+ await writeFile(join(dir, path), content, "utf8");
13
+ }
14
+ try {
15
+ await fn(dir);
16
+ }
17
+ finally {
18
+ await rm(dir, { recursive: true, force: true });
19
+ }
20
+ }
21
+ function probe(pattern, key = "k0", fileFilter) {
22
+ return { key, pattern, refs: [{ depName: "pkg", changeIndex: 0 }], ...(fileFilter ? { fileFilter } : {}) };
23
+ }
24
+ test("scanOnce: reports file and 1-based line for each match", async () => {
25
+ await withProject({ "src/a.js": "line one\noldApi()\nline three\n" }, async (dir) => {
26
+ const { hits } = await scanOnce(dir, [probe("oldApi")], { perProbeCap: 200 });
27
+ assert.equal(hits.length, 1);
28
+ assert.equal(hits[0].file, join("src", "a.js"));
29
+ assert.equal(hits[0].line, 2);
30
+ assert.equal(hits[0].excerpt, "oldApi()");
31
+ });
32
+ });
33
+ test("scanOnce: finds multiple matches across files", async () => {
34
+ await withProject({
35
+ "src/a.js": "oldApi()\n",
36
+ "src/nested/b.ts": "const x = oldApi;\n",
37
+ }, async (dir) => {
38
+ const { hits } = await scanOnce(dir, [probe("oldApi")], { perProbeCap: 200 });
39
+ const files = hits.map((h) => h.file).sort();
40
+ assert.deepEqual(files, [join("src", "a.js"), join("src", "nested", "b.ts")]);
41
+ });
42
+ });
43
+ test("scanOnce: counts every occurrence on one line", async () => {
44
+ await withProject({ "a.js": "oldApi(); oldApi();\n" }, async (dir) => {
45
+ const { hits } = await scanOnce(dir, [probe("oldApi")], { perProbeCap: 200 });
46
+ assert.equal(hits.length, 2);
47
+ });
48
+ });
49
+ test("scanOnce: caps matches per probe and flags the probe as truncated", async () => {
50
+ const manyLines = Array.from({ length: 50 }, () => "oldApi();").join("\n");
51
+ await withProject({ "a.js": manyLines }, async (dir) => {
52
+ const { hits, truncatedProbes } = await scanOnce(dir, [probe("oldApi")], { perProbeCap: 10 });
53
+ assert.equal(hits.length, 10);
54
+ assert.ok(truncatedProbes.has("k0"));
55
+ });
56
+ });
57
+ test("scanOnce: a probe under its cap is not marked truncated", async () => {
58
+ await withProject({ "a.js": "oldApi();\n" }, async (dir) => {
59
+ const { truncatedProbes } = await scanOnce(dir, [probe("oldApi")], { perProbeCap: 10 });
60
+ assert.equal(truncatedProbes.size, 0);
61
+ });
62
+ });
63
+ test("scanOnce: matches are attributed to the right probe when several are active", async () => {
64
+ await withProject({ "a.js": "alpha();\nbeta();\n" }, async (dir) => {
65
+ const { hits } = await scanOnce(dir, [probe("alpha", "k-alpha"), probe("beta", "k-beta")], { perProbeCap: 200 });
66
+ assert.equal(hits.length, 2);
67
+ const alpha = hits.find((h) => h.excerpt.startsWith("alpha"));
68
+ const beta = hits.find((h) => h.excerpt.startsWith("beta"));
69
+ assert.equal(alpha?.probeKey, "k-alpha");
70
+ assert.equal(beta?.probeKey, "k-beta");
71
+ });
72
+ });
73
+ test("scanOnce: skips node_modules and other ignored directories", async () => {
74
+ await withProject({
75
+ "src/a.js": "oldApi();\n",
76
+ "node_modules/dep/index.js": "oldApi();\n",
77
+ "dist/bundle.js": "oldApi();\n",
78
+ }, async (dir) => {
79
+ const { hits } = await scanOnce(dir, [probe("oldApi")], { perProbeCap: 200 });
80
+ assert.equal(hits.length, 1);
81
+ assert.equal(hits[0].file, join("src", "a.js"));
82
+ });
83
+ });
84
+ test("scanOnce: never reports matches from secrets files", async () => {
85
+ await withProject({
86
+ "src/a.js": "oldApi();\n",
87
+ ".env": "SECRET=oldApi\n",
88
+ "secrets.json": '{"k":"oldApi"}\n',
89
+ "credentials.json": '{"k":"oldApi"}\n',
90
+ }, async (dir) => {
91
+ const { hits } = await scanOnce(dir, [probe("oldApi")], { perProbeCap: 200 });
92
+ assert.equal(hits.length, 1);
93
+ assert.equal(hits[0].file, join("src", "a.js"));
94
+ });
95
+ });
96
+ test("scanOnce: only searches known source extensions", async () => {
97
+ await withProject({ "a.js": "oldApi();\n", "notes.txt": "oldApi();\n", "image.bin": "oldApi();\n" }, async (dir) => {
98
+ const { hits } = await scanOnce(dir, [probe("oldApi")], { perProbeCap: 200 });
99
+ assert.deepEqual(hits.map((h) => h.file), ["a.js"]);
100
+ });
101
+ });
102
+ test("scanOnce: fileFilter restricts matches to matching paths", async () => {
103
+ await withProject({ "src/a.js": "oldApi();\n", "test/b.js": "oldApi();\n" }, async (dir) => {
104
+ const { hits } = await scanOnce(dir, [probe("oldApi", "k0", "test")], { perProbeCap: 200 });
105
+ assert.deepEqual(hits.map((h) => h.file), [join("test", "b.js")]);
106
+ });
107
+ });
108
+ test("scanOnce: import gate limits bare-identifier probes to importing files", async () => {
109
+ await withProject({
110
+ "imports.js": "const { describe } = require('vitest');\ndescribe('x', () => {});\n",
111
+ "unrelated.js": "describe('this is not vitest');\n",
112
+ }, async (dir) => {
113
+ const { hits } = await scanOnce(dir, [probe("describe", "k0", `${REQUIRES_IMPORT_PREFIX}vitest`)], { perProbeCap: 200 });
114
+ // Both occurrences live in imports.js; unrelated.js must contribute none.
115
+ assert.deepEqual(hits.map((h) => h.file), ["imports.js", "imports.js"]);
116
+ assert.deepEqual(hits.map((h) => h.line), [1, 2]);
117
+ });
118
+ });
119
+ test("scanOnce: import gate also matches ESM import statements", async () => {
120
+ await withProject({
121
+ "esm.mjs": 'import { describe } from "vitest";\ndescribe("x");\n',
122
+ "other.mjs": 'describe("nope");\n',
123
+ }, async (dir) => {
124
+ const { hits } = await scanOnce(dir, [probe("describe", "k0", `${REQUIRES_IMPORT_PREFIX}vitest`)], { perProbeCap: 200 });
125
+ assert.ok(hits.every((h) => h.file === "esm.mjs"));
126
+ assert.equal(hits.length, 2);
127
+ });
128
+ });
129
+ test("scanOnce: an invalid regex falls back to a literal match instead of throwing", async () => {
130
+ await withProject({ "a.js": "a[b\n" }, async (dir) => {
131
+ // Unbalanced bracket is not a valid regex — must not throw.
132
+ const { hits } = await scanOnce(dir, [probe("a[b")], { perProbeCap: 200 });
133
+ assert.equal(hits.length, 1);
134
+ });
135
+ });
136
+ test("scanOnce: no probes means no hits (and no crash)", async () => {
137
+ await withProject({ "a.js": "anything\n" }, async (dir) => {
138
+ const { hits, truncatedProbes } = await scanOnce(dir, [], { perProbeCap: 200 });
139
+ assert.deepEqual(hits, []);
140
+ assert.equal(truncatedProbes.size, 0);
141
+ });
142
+ });
143
+ test("scanOnce: empty directory yields no hits", async () => {
144
+ await withProject({}, async (dir) => {
145
+ const { hits } = await scanOnce(dir, [probe("oldApi")], { perProbeCap: 200 });
146
+ assert.deepEqual(hits, []);
147
+ });
148
+ });
149
+ //# sourceMappingURL=indexer.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"indexer.test.js","sourceRoot":"","sources":["../../../src/engine/scan/indexer.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,92 @@
1
+ import { builtinCodemods, codemodMatchesRange, parseMajor } from "../fixer/patterns.js";
2
+ /**
3
+ * Prefix marking a probe whose matches are only valid inside files that
4
+ * import a given package. Carried in `Probe.fileFilter` so the indexer can
5
+ * resolve it per file without a second pass.
6
+ */
7
+ export const REQUIRES_IMPORT_PREFIX = "__requiresImport:";
8
+ /**
9
+ * Generate search probes from a breaking change.
10
+ *
11
+ * Strategy:
12
+ * 1. Primary probe = oldApi.split("(")[0].trim(), if length >= 4
13
+ * 2. If primary is a bare identifier (no `.`, `(`, quotes), mark for import-scoped search
14
+ * 3. If primary too short, extract longest `.identifier` member and mark approx
15
+ */
16
+ export function probesFromChange(change, depName, changeIndex) {
17
+ const probes = [];
18
+ const oldApi = change.oldApi;
19
+ // Primary probe: everything before the first `(`
20
+ const primary = oldApi.split("(")[0].trim();
21
+ if (primary.length >= 4) {
22
+ // Check if it's a bare identifier (no dots, parens, quotes, spaces)
23
+ const isBareIdentifier = /^[a-zA-Z_$][\w$]*$/.test(primary);
24
+ probes.push({
25
+ key: `${depName}:${changeIndex}:primary`,
26
+ pattern: escapeRegExpLiteral(primary),
27
+ approx: false,
28
+ refs: [{ depName, changeIndex }],
29
+ // Bare identifiers like `describe` are too noisy — only search in files
30
+ // that import the package (resolved per file by the indexer).
31
+ ...(isBareIdentifier ? { fileFilter: `${REQUIRES_IMPORT_PREFIX}${depName}` } : {}),
32
+ });
33
+ return probes;
34
+ }
35
+ // Fallback: extract the longest `.identifier` member
36
+ const members = oldApi.match(/\.[a-zA-Z_$][\w$]*/g) || [];
37
+ if (members.length > 0) {
38
+ const longest = members.reduce((a, b) => (a.length > b.length ? a : b));
39
+ probes.push({
40
+ key: `${depName}:${changeIndex}:member`,
41
+ pattern: escapeRegExpLiteral(longest),
42
+ approx: true,
43
+ refs: [{ depName, changeIndex }],
44
+ });
45
+ }
46
+ return probes;
47
+ }
48
+ /** Generate probes from a codemod (uses its transform.find pattern). */
49
+ export function probesFromCodemod(codemod, depName, changeIndex) {
50
+ if (!codemod.transform)
51
+ return [];
52
+ return [
53
+ {
54
+ key: `${depName}:${changeIndex}:codemod`,
55
+ pattern: codemod.transform.find,
56
+ fileFilter: codemod.transform.fileFilter,
57
+ approx: false,
58
+ refs: [{ depName, changeIndex }],
59
+ },
60
+ ];
61
+ }
62
+ /**
63
+ * Packages whose upgrades can plausibly break the generic Node-runtime
64
+ * codemods (`package: "*"` in the registry, e.g. `util.isArray` removal).
65
+ * Those codemods are error-gated in the fix loop — they only fire when the
66
+ * failure output matches — but `--scan` has no error output, so without this
67
+ * gate they would be attributed to *every* outdated dependency (a zod bump
68
+ * reported as breaking `assert.equal`, which is simply wrong).
69
+ */
70
+ const NODE_RUNTIME_PACKAGES = new Set(["node", "nodejs", "@types/node"]);
71
+ /** Find codemods that apply to a given package upgrade path. */
72
+ export function codemodsForUpgrade(packageName, fromVersion, toVersion) {
73
+ const fromMajor = parseMajor(fromVersion);
74
+ const toMajor = parseMajor(toVersion);
75
+ return builtinCodemods().filter((c) => {
76
+ if (c.package === "*") {
77
+ // Generic runtime codemods only apply to Node-runtime upgrades.
78
+ if (!NODE_RUNTIME_PACKAGES.has(packageName))
79
+ return false;
80
+ }
81
+ else if (c.package !== packageName) {
82
+ return false;
83
+ }
84
+ // Shared with the fix loop so the two can't drift.
85
+ return codemodMatchesRange(c.versionRange, fromMajor, toMajor);
86
+ });
87
+ }
88
+ /** Escape a string for use as a literal in a RegExp. */
89
+ export function escapeRegExpLiteral(s) {
90
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
91
+ }
92
+ //# sourceMappingURL=probes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"probes.js","sourceRoot":"","sources":["../../../src/engine/scan/probes.ts"],"names":[],"mappings":"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"}