greenbump 0.4.0 → 0.6.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 (78) hide show
  1. package/README.md +673 -66
  2. package/README.zh-CN.md +525 -100
  3. package/dist/agent/fixer.js +161 -12
  4. package/dist/agent/fixer.js.map +1 -1
  5. package/dist/agent/fixer.test.js +410 -0
  6. package/dist/agent/fixer.test.js.map +1 -0
  7. package/dist/cli/interactive.js +81 -0
  8. package/dist/cli/interactive.js.map +1 -0
  9. package/dist/cli.js +53 -0
  10. package/dist/cli.js.map +1 -1
  11. package/dist/engine/analyzers/changelog-parser.js +121 -0
  12. package/dist/engine/analyzers/changelog-parser.js.map +1 -0
  13. package/dist/engine/batch.test.js +59 -0
  14. package/dist/engine/batch.test.js.map +1 -0
  15. package/dist/engine/cache/manager.js +165 -0
  16. package/dist/engine/cache/manager.js.map +1 -0
  17. package/dist/engine/cache/manager.test.js +75 -0
  18. package/dist/engine/cache/manager.test.js.map +1 -0
  19. package/dist/engine/checks.test.js +92 -0
  20. package/dist/engine/checks.test.js.map +1 -0
  21. package/dist/engine/context/optimizer.js +189 -0
  22. package/dist/engine/context/optimizer.js.map +1 -0
  23. package/dist/engine/context/optimizer.test.js +81 -0
  24. package/dist/engine/context/optimizer.test.js.map +1 -0
  25. package/dist/engine/ecosystems/parsers.test.js +255 -0
  26. package/dist/engine/ecosystems/parsers.test.js.map +1 -0
  27. package/dist/engine/fixer/patterns.js +356 -0
  28. package/dist/engine/fixer/patterns.js.map +1 -0
  29. package/dist/engine/fixer/patterns.test.js +124 -0
  30. package/dist/engine/fixer/patterns.test.js.map +1 -0
  31. package/dist/engine/git.js +1 -0
  32. package/dist/engine/git.js.map +1 -1
  33. package/dist/engine/git.test.js +111 -0
  34. package/dist/engine/git.test.js.map +1 -0
  35. package/dist/engine/perf/metrics.js +171 -0
  36. package/dist/engine/perf/metrics.js.map +1 -0
  37. package/dist/engine/perf/regression.js +115 -0
  38. package/dist/engine/perf/regression.js.map +1 -0
  39. package/dist/engine/run.js +93 -2
  40. package/dist/engine/run.js.map +1 -1
  41. package/dist/engine/run.test.js +47 -0
  42. package/dist/engine/run.test.js.map +1 -0
  43. package/dist/engine/sandbox/docker-manager.js +215 -0
  44. package/dist/engine/sandbox/docker-manager.js.map +1 -0
  45. package/dist/engine/sandbox/orchestrator.js +107 -0
  46. package/dist/engine/sandbox/orchestrator.js.map +1 -0
  47. package/dist/engine/sandbox/service-detector.js +147 -0
  48. package/dist/engine/sandbox/service-detector.js.map +1 -0
  49. package/dist/engine/sandbox/template-generator.js +241 -0
  50. package/dist/engine/sandbox/template-generator.js.map +1 -0
  51. package/dist/engine/verifiers/ast-analyzer.js +212 -0
  52. package/dist/engine/verifiers/ast-analyzer.js.map +1 -0
  53. package/dist/engine/verifiers/ast-analyzer.test.js +96 -0
  54. package/dist/engine/verifiers/ast-analyzer.test.js.map +1 -0
  55. package/dist/engine/workspace.test.js +95 -0
  56. package/dist/engine/workspace.test.js.map +1 -0
  57. package/dist/exitcode.test.js +62 -0
  58. package/dist/exitcode.test.js.map +1 -0
  59. package/dist/format.js +15 -0
  60. package/dist/format.js.map +1 -1
  61. package/dist/report.test.js +56 -0
  62. package/dist/report.test.js.map +1 -0
  63. package/package.json +7 -4
  64. package/CHANGELOG.md +0 -93
  65. package/CONTRIBUTING.md +0 -52
  66. package/SECURITY.md +0 -38
  67. package/action-dist/index.cjs +0 -19935
  68. package/action.yml +0 -76
  69. package/competitive-analysis.md +0 -304
  70. package/examples/greenbump.yml +0 -37
  71. package/examples/legacy-repo/package.json +0 -12
  72. package/examples/legacy-repo/test.js +0 -17
  73. package/examples/multi-file-repo/models/todo.js +0 -8
  74. package/examples/multi-file-repo/models/user.js +0 -11
  75. package/examples/multi-file-repo/package.json +0 -12
  76. package/examples/multi-file-repo/services/session.js +0 -9
  77. package/examples/multi-file-repo/test.js +0 -19
  78. package/optimization-roadmap.md +0 -772
@@ -0,0 +1,356 @@
1
+ import { readFile, writeFile } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import { getCache } from "../cache/manager.js";
4
+ /**
5
+ * Tiered fix strategies, ordered by cost:
6
+ *
7
+ * Level 1: REGEX — built-in codemod patterns for well-known upgrades ($0)
8
+ * Level 2: RULE — learned patterns from cache, applied by template ($0)
9
+ * Level 3: CACHED — full cached LLM fix for an identical failure ($0)
10
+ * Level 4: LLM — actual model call (paid, last resort)
11
+ *
12
+ * The first three tiers run before any LLM call. On a typical minor/patch
13
+ * upgrade (renamed export, moved module), tier 1-2 resolves the fix with
14
+ * zero tokens spent.
15
+ */
16
+ export var FixTier;
17
+ (function (FixTier) {
18
+ FixTier[FixTier["REGEX"] = 1] = "REGEX";
19
+ FixTier[FixTier["RULE"] = 2] = "RULE";
20
+ FixTier[FixTier["CACHED"] = 3] = "CACHED";
21
+ FixTier[FixTier["LLM"] = 4] = "LLM";
22
+ })(FixTier || (FixTier = {}));
23
+ const BUILTIN_CODEMODS = [
24
+ // ── React 18 → 19 ──
25
+ {
26
+ package: "react-dom",
27
+ versionRange: { fromMajor: 18, toMajor: 19 },
28
+ errorMatch: "ReactDOM.render is not a function",
29
+ description: "ReactDOM.render → createRoot (React 19)",
30
+ transform: {
31
+ find: "ReactDOM\\.render\\(\\s*([\\s\\S]*?),\\s*(document\\.[a-zA-Z]+\\([^)]*\\)[^)]*?)\\s*\\)",
32
+ replace: "createRoot($2).render($1)",
33
+ },
34
+ imports: {
35
+ remove: "import ReactDOM from 'react-dom'",
36
+ add: "import { createRoot } from 'react-dom/client'",
37
+ },
38
+ },
39
+ {
40
+ package: "react-dom",
41
+ versionRange: { fromMajor: 18, toMajor: 19 },
42
+ errorMatch: "ReactDOM.render is not a function",
43
+ description: "ReactDOM.render → createRoot (double-quoted imports)",
44
+ transform: {
45
+ find: "ReactDOM\\.render\\(\\s*([\\s\\S]*?),\\s*(document\\.[a-zA-Z]+\\([^)]*\\)[^)]*?)\\s*\\)",
46
+ replace: "createRoot($2).render($1)",
47
+ },
48
+ imports: {
49
+ remove: 'import ReactDOM from "react-dom"',
50
+ add: 'import { createRoot } from "react-dom/client"',
51
+ },
52
+ },
53
+ {
54
+ package: "react-dom",
55
+ versionRange: { fromMajor: 18, toMajor: 19 },
56
+ errorMatch: "hydrate is not a function",
57
+ description: "ReactDOM.hydrate → hydrateRoot (React 19)",
58
+ transform: {
59
+ find: "ReactDOM\\.hydrate\\(\\s*([\\s\\S]*?),\\s*(document\\.[a-zA-Z]+\\([^)]*\\)[^)]*?)\\s*\\)",
60
+ replace: "hydrateRoot($2, $1)",
61
+ },
62
+ imports: {
63
+ remove: "import ReactDOM from 'react-dom'",
64
+ add: "import { hydrateRoot } from 'react-dom/client'",
65
+ },
66
+ },
67
+ // ── Vue 2 → 3 ──
68
+ {
69
+ package: "vue",
70
+ versionRange: { fromMajor: 2, toMajor: 3 },
71
+ errorMatch: "Vue is not a constructor",
72
+ description: "new Vue() → createApp() (Vue 3)",
73
+ transform: {
74
+ find: "new Vue\\(([^)]*)\\)",
75
+ replace: "createApp($1)",
76
+ },
77
+ imports: {
78
+ remove: "import Vue from 'vue'",
79
+ add: "import { createApp } from 'vue'",
80
+ },
81
+ },
82
+ // ── Node.js assert style ──
83
+ {
84
+ package: "*",
85
+ errorMatch: "assert.equal is deprecated",
86
+ description: "assert.equal → assert.strictEqual",
87
+ transform: {
88
+ find: "assert\\.equal\\(",
89
+ replace: "assert.strictEqual(",
90
+ fileFilter: "test",
91
+ },
92
+ },
93
+ // ── Jest → Vitest common patterns ──
94
+ {
95
+ package: "vitest",
96
+ errorMatch: "jest is not defined",
97
+ description: "jest.fn() → vi.fn() (Vitest)",
98
+ transform: {
99
+ find: "\\bjest\\.",
100
+ replace: "vi.",
101
+ },
102
+ imports: {
103
+ remove: "",
104
+ add: "import { vi } from 'vitest'",
105
+ },
106
+ },
107
+ ];
108
+ /**
109
+ * Level 1: try built-in codemods. Returns applied=true if any codemod
110
+ * matched the error AND modified at least one file.
111
+ */
112
+ export async function tryBuiltinCodemods(ctx) {
113
+ const fromMajor = parseMajor(ctx.fromVersion);
114
+ const toMajor = parseMajor(ctx.toVersion);
115
+ const candidates = BUILTIN_CODEMODS.filter((c) => {
116
+ if (c.package !== "*" && c.package !== ctx.packageName)
117
+ return false;
118
+ if (!new RegExp(c.errorMatch, "i").test(ctx.failureOutput))
119
+ return false;
120
+ if (c.versionRange) {
121
+ if (c.versionRange.fromMajor !== undefined && fromMajor !== c.versionRange.fromMajor)
122
+ return false;
123
+ if (c.versionRange.toMajor !== undefined && toMajor !== c.versionRange.toMajor)
124
+ return false;
125
+ }
126
+ return true;
127
+ });
128
+ if (candidates.length === 0) {
129
+ return noFix(FixTier.REGEX);
130
+ }
131
+ const editedFiles = [];
132
+ const descriptions = [];
133
+ for (const codemod of candidates) {
134
+ for (const file of ctx.candidateFiles) {
135
+ if (codemod.transform.fileFilter && !file.includes(codemod.transform.fileFilter))
136
+ continue;
137
+ const abs = join(ctx.cwd, file);
138
+ let content;
139
+ try {
140
+ content = await readFile(abs, "utf8");
141
+ }
142
+ catch {
143
+ continue;
144
+ }
145
+ let updated = content;
146
+ try {
147
+ updated = updated.replace(new RegExp(codemod.transform.find, "g"), codemod.transform.replace);
148
+ }
149
+ catch {
150
+ continue;
151
+ }
152
+ if (codemod.imports) {
153
+ const { remove, add } = codemod.imports;
154
+ if (remove && updated.includes(remove)) {
155
+ updated = updated.replace(remove, add);
156
+ }
157
+ else if (add && !updated.includes(add) && updated !== content) {
158
+ // Transform applied but import line wasn't there in expected form —
159
+ // prepend the new import at the top.
160
+ updated = add + "\n" + updated;
161
+ }
162
+ }
163
+ if (updated !== content) {
164
+ await writeFile(abs, updated, "utf8");
165
+ editedFiles.push(file);
166
+ }
167
+ }
168
+ if (editedFiles.length > 0)
169
+ descriptions.push(codemod.description);
170
+ }
171
+ return {
172
+ applied: editedFiles.length > 0,
173
+ tier: FixTier.REGEX,
174
+ editedFiles,
175
+ description: descriptions.join("; ") || "no codemod matched",
176
+ tokensUsed: { inputTokens: 0, outputTokens: 0 },
177
+ };
178
+ }
179
+ /**
180
+ * Level 2: try learned patterns from the local cache (patterns that worked
181
+ * in previous runs of greenbump, either here or on other projects).
182
+ */
183
+ export async function tryLearnedPatterns(ctx) {
184
+ const cache = getCache();
185
+ await cache.init();
186
+ // Try each error line as a signature — the cache is keyed by
187
+ // (package, hash(error signature)) so we probe with the first few
188
+ // distinctive lines of the failure.
189
+ const signatures = extractErrorSignatures(ctx.failureOutput);
190
+ for (const sig of signatures) {
191
+ const pattern = await cache.getFixPattern(ctx.packageName, sig);
192
+ if (!pattern)
193
+ continue;
194
+ const editedFiles = [];
195
+ let allApplied = true;
196
+ for (const [file, edit] of Object.entries(pattern.edits)) {
197
+ const abs = join(ctx.cwd, file);
198
+ let content;
199
+ try {
200
+ content = await readFile(abs, "utf8");
201
+ }
202
+ catch {
203
+ allApplied = false;
204
+ continue;
205
+ }
206
+ let updated;
207
+ if (typeof edit === "string") {
208
+ updated = edit; // whole-file replacement
209
+ }
210
+ else {
211
+ try {
212
+ updated = content.replace(new RegExp(edit.find, "g"), edit.replace);
213
+ }
214
+ catch {
215
+ allApplied = false;
216
+ continue;
217
+ }
218
+ }
219
+ const importFix = pattern.importFixes?.[file];
220
+ if (importFix?.remove && updated.includes(importFix.remove)) {
221
+ updated = updated.replace(importFix.remove, importFix.add ?? "");
222
+ }
223
+ if (updated !== content) {
224
+ await writeFile(abs, updated, "utf8");
225
+ editedFiles.push(file);
226
+ }
227
+ }
228
+ if (editedFiles.length > 0 && allApplied) {
229
+ pattern.hits++;
230
+ await cache.setFixPattern(ctx.packageName, sig, pattern);
231
+ return {
232
+ applied: true,
233
+ tier: FixTier.RULE,
234
+ editedFiles,
235
+ description: `learned pattern: ${pattern.description}`,
236
+ tokensUsed: { inputTokens: 0, outputTokens: 0 },
237
+ };
238
+ }
239
+ if (editedFiles.length === 0) {
240
+ pattern.misses++;
241
+ await cache.setFixPattern(ctx.packageName, sig, pattern);
242
+ }
243
+ }
244
+ return noFix(FixTier.RULE);
245
+ }
246
+ /**
247
+ * Level 3: try a fully cached LLM fix for an identical failure context.
248
+ * The context key covers (package, versions, failure output hash) so this
249
+ * only hits when the exact same failure has been fixed before.
250
+ */
251
+ export async function tryCachedLlmFix(ctx, contextKey) {
252
+ const cache = getCache();
253
+ await cache.init();
254
+ const cached = await cache.getLlmFix(contextKey);
255
+ if (!cached)
256
+ return noFix(FixTier.CACHED);
257
+ const editedFiles = [];
258
+ for (const [file, content] of Object.entries(cached.edits)) {
259
+ const abs = join(ctx.cwd, file);
260
+ try {
261
+ await writeFile(abs, content, "utf8");
262
+ editedFiles.push(file);
263
+ }
264
+ catch {
265
+ // file path doesn't exist in this project — the cached fix doesn't
266
+ // apply cleanly, bail rather than write partial state
267
+ return noFix(FixTier.CACHED);
268
+ }
269
+ }
270
+ cached.hits++;
271
+ await cache.setLlmFix(contextKey, cached);
272
+ return {
273
+ applied: true,
274
+ tier: FixTier.CACHED,
275
+ editedFiles,
276
+ description: `cached fix (used ${cached.hits}x before, from ${cached.model})`,
277
+ tokensUsed: { inputTokens: 0, outputTokens: 0 },
278
+ };
279
+ }
280
+ /**
281
+ * Record a successful LLM fix as a learned pattern + full cached fix,
282
+ * so future identical failures cost $0.
283
+ */
284
+ export async function learnFromSuccessfulFix(ctx, contextKey, editedFiles, model) {
285
+ const cache = getCache();
286
+ await cache.init();
287
+ // Store the full fix (file contents after edit)
288
+ const edits = {};
289
+ for (const file of editedFiles) {
290
+ try {
291
+ edits[file] = await readFile(join(ctx.cwd, file), "utf8");
292
+ }
293
+ catch {
294
+ // skip unreadable files
295
+ }
296
+ }
297
+ if (Object.keys(edits).length > 0) {
298
+ const existing = await cache.getLlmFix(contextKey);
299
+ await cache.setLlmFix(contextKey, {
300
+ edits,
301
+ hits: (existing?.hits ?? 0),
302
+ model,
303
+ });
304
+ }
305
+ }
306
+ /** Build the cache context key for a failure — stable across projects. */
307
+ export function buildContextKey(packageName, from, to, failureOutput) {
308
+ // Normalize the failure output: strip paths, line numbers, timings —
309
+ // keep only the error essence so identical errors across projects collide.
310
+ const normalized = failureOutput
311
+ .replace(/[^\s]*node_modules[^\s]*/g, "<pkg>")
312
+ .replace(/[A-Za-z]:?[\\/][^\s:;)]+/g, "<path>")
313
+ .replace(/:\d+:\d+/g, "")
314
+ .replace(/\d+ms/g, "<time>")
315
+ .replace(/\s+/g, " ")
316
+ .trim()
317
+ .slice(0, 2000);
318
+ return `${packageName}@${from}->${to}::${normalized}`;
319
+ }
320
+ // ── Helpers ────────────────────────────────────────────────────────────────
321
+ function parseMajor(version) {
322
+ const m = version.replace(/^[~^>=\s]*/, "").match(/^(\d+)/);
323
+ return m ? parseInt(m[1], 10) : 0;
324
+ }
325
+ /** Extract distinctive error lines to use as cache probe signatures. */
326
+ function extractErrorSignatures(failureOutput) {
327
+ const lines = failureOutput.split("\n");
328
+ const sigs = [];
329
+ for (const line of lines) {
330
+ const t = line.trim();
331
+ // Keep lines that look like actual error messages
332
+ if (/error|failed|is not a function|is not defined|cannot find|deprecated/i.test(t)
333
+ && t.length > 15 && t.length < 300) {
334
+ sigs.push(t);
335
+ if (sigs.length >= 5)
336
+ break;
337
+ }
338
+ }
339
+ return sigs;
340
+ }
341
+ function noFix(tier) {
342
+ return {
343
+ applied: false,
344
+ tier,
345
+ editedFiles: [],
346
+ description: "no applicable pattern",
347
+ tokensUsed: { inputTokens: 0, outputTokens: 0 },
348
+ };
349
+ }
350
+ /** List built-in codemods (for --list-codemods CLI / docs). */
351
+ export function listBuiltinCodemods() {
352
+ return BUILTIN_CODEMODS
353
+ .map((c) => ` ${c.package}${c.versionRange ? ` (${c.versionRange.fromMajor}→${c.versionRange.toMajor})` : ""}: ${c.description}`)
354
+ .join("\n");
355
+ }
356
+ //# sourceMappingURL=patterns.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"patterns.js","sourceRoot":"","sources":["../../../src/engine/fixer/patterns.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAE/C;;;;;;;;;;;GAWG;AAEH,MAAM,CAAN,IAAY,OAKX;AALD,WAAY,OAAO;IACjB,uCAAS,CAAA;IACT,qCAAQ,CAAA;IACR,yCAAU,CAAA;IACV,mCAAO,CAAA;AACT,CAAC,EALW,OAAO,KAAP,OAAO,QAKlB;AAoCD,MAAM,gBAAgB,GAAc;IAClC,sBAAsB;IACtB;QACE,OAAO,EAAE,WAAW;QACpB,YAAY,EAAE,EAAE,SAAS,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE;QAC5C,UAAU,EAAE,mCAAmC;QAC/C,WAAW,EAAE,yCAAyC;QACtD,SAAS,EAAE;YACT,IAAI,EAAE,yFAAyF;YAC/F,OAAO,EAAE,2BAA2B;SACrC;QACD,OAAO,EAAE;YACP,MAAM,EAAE,kCAAkC;YAC1C,GAAG,EAAE,+CAA+C;SACrD;KACF;IACD;QACE,OAAO,EAAE,WAAW;QACpB,YAAY,EAAE,EAAE,SAAS,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE;QAC5C,UAAU,EAAE,mCAAmC;QAC/C,WAAW,EAAE,sDAAsD;QACnE,SAAS,EAAE;YACT,IAAI,EAAE,yFAAyF;YAC/F,OAAO,EAAE,2BAA2B;SACrC;QACD,OAAO,EAAE;YACP,MAAM,EAAE,kCAAkC;YAC1C,GAAG,EAAE,+CAA+C;SACrD;KACF;IACD;QACE,OAAO,EAAE,WAAW;QACpB,YAAY,EAAE,EAAE,SAAS,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE;QAC5C,UAAU,EAAE,2BAA2B;QACvC,WAAW,EAAE,2CAA2C;QACxD,SAAS,EAAE;YACT,IAAI,EAAE,0FAA0F;YAChG,OAAO,EAAE,qBAAqB;SAC/B;QACD,OAAO,EAAE;YACP,MAAM,EAAE,kCAAkC;YAC1C,GAAG,EAAE,gDAAgD;SACtD;KACF;IAED,kBAAkB;IAClB;QACE,OAAO,EAAE,KAAK;QACd,YAAY,EAAE,EAAE,SAAS,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE;QAC1C,UAAU,EAAE,0BAA0B;QACtC,WAAW,EAAE,iCAAiC;QAC9C,SAAS,EAAE;YACT,IAAI,EAAE,sBAAsB;YAC5B,OAAO,EAAE,eAAe;SACzB;QACD,OAAO,EAAE;YACP,MAAM,EAAE,uBAAuB;YAC/B,GAAG,EAAE,iCAAiC;SACvC;KACF;IAED,6BAA6B;IAC7B;QACE,OAAO,EAAE,GAAG;QACZ,UAAU,EAAE,4BAA4B;QACxC,WAAW,EAAE,mCAAmC;QAChD,SAAS,EAAE;YACT,IAAI,EAAE,mBAAmB;YACzB,OAAO,EAAE,qBAAqB;YAC9B,UAAU,EAAE,MAAM;SACnB;KACF;IAED,sCAAsC;IACtC;QACE,OAAO,EAAE,QAAQ;QACjB,UAAU,EAAE,qBAAqB;QACjC,WAAW,EAAE,8BAA8B;QAC3C,SAAS,EAAE;YACT,IAAI,EAAE,YAAY;YAClB,OAAO,EAAE,KAAK;SACf;QACD,OAAO,EAAE;YACP,MAAM,EAAE,EAAE;YACV,GAAG,EAAE,6BAA6B;SACnC;KACF;CACF,CAAC;AAcF;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,GAAsB;IAC7D,MAAM,SAAS,GAAG,UAAU,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;IAC9C,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;IAE1C,MAAM,UAAU,GAAG,gBAAgB,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE;QAC/C,IAAI,CAAC,CAAC,OAAO,KAAK,GAAG,IAAI,CAAC,CAAC,OAAO,KAAK,GAAG,CAAC,WAAW;YAAE,OAAO,KAAK,CAAC;QACrE,IAAI,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,aAAa,CAAC;YAAE,OAAO,KAAK,CAAC;QACzE,IAAI,CAAC,CAAC,YAAY,EAAE,CAAC;YACnB,IAAI,CAAC,CAAC,YAAY,CAAC,SAAS,KAAK,SAAS,IAAI,SAAS,KAAK,CAAC,CAAC,YAAY,CAAC,SAAS;gBAAE,OAAO,KAAK,CAAC;YACnG,IAAI,CAAC,CAAC,YAAY,CAAC,OAAO,KAAK,SAAS,IAAI,OAAO,KAAK,CAAC,CAAC,YAAY,CAAC,OAAO;gBAAE,OAAO,KAAK,CAAC;QAC/F,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC,CAAC,CAAC;IAEH,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC5B,OAAO,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IAC9B,CAAC;IAED,MAAM,WAAW,GAAa,EAAE,CAAC;IACjC,MAAM,YAAY,GAAa,EAAE,CAAC;IAElC,KAAK,MAAM,OAAO,IAAI,UAAU,EAAE,CAAC;QACjC,KAAK,MAAM,IAAI,IAAI,GAAG,CAAC,cAAc,EAAE,CAAC;YACtC,IAAI,OAAO,CAAC,SAAS,CAAC,UAAU,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,SAAS,CAAC,UAAU,CAAC;gBAAE,SAAS;YAE3F,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;YAChC,IAAI,OAAe,CAAC;YACpB,IAAI,CAAC;gBACH,OAAO,GAAG,MAAM,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;YACxC,CAAC;YAAC,MAAM,CAAC;gBACP,SAAS;YACX,CAAC;YAED,IAAI,OAAO,GAAG,OAAO,CAAC;YACtB,IAAI,CAAC;gBACH,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,IAAI,EAAE,GAAG,CAAC,EAAE,OAAO,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;YAChG,CAAC;YAAC,MAAM,CAAC;gBACP,SAAS;YACX,CAAC;YAED,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;gBACpB,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC;gBACxC,IAAI,MAAM,IAAI,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;oBACvC,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;gBACzC,CAAC;qBAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,OAAO,KAAK,OAAO,EAAE,CAAC;oBAChE,oEAAoE;oBACpE,qCAAqC;oBACrC,OAAO,GAAG,GAAG,GAAG,IAAI,GAAG,OAAO,CAAC;gBACjC,CAAC;YACH,CAAC;YAED,IAAI,OAAO,KAAK,OAAO,EAAE,CAAC;gBACxB,MAAM,SAAS,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;gBACtC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACzB,CAAC;QACH,CAAC;QACD,IAAI,WAAW,CAAC,MAAM,GAAG,CAAC;YAAE,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;IACrE,CAAC;IAED,OAAO;QACL,OAAO,EAAE,WAAW,CAAC,MAAM,GAAG,CAAC;QAC/B,IAAI,EAAE,OAAO,CAAC,KAAK;QACnB,WAAW;QACX,WAAW,EAAE,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,oBAAoB;QAC5D,UAAU,EAAE,EAAE,WAAW,EAAE,CAAC,EAAE,YAAY,EAAE,CAAC,EAAE;KAChD,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,GAAsB;IAC7D,MAAM,KAAK,GAAG,QAAQ,EAAE,CAAC;IACzB,MAAM,KAAK,CAAC,IAAI,EAAE,CAAC;IAEnB,6DAA6D;IAC7D,kEAAkE;IAClE,oCAAoC;IACpC,MAAM,UAAU,GAAG,sBAAsB,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;IAE7D,KAAK,MAAM,GAAG,IAAI,UAAU,EAAE,CAAC;QAC7B,MAAM,OAAO,GAAG,MAAM,KAAK,CAAC,aAAa,CAAC,GAAG,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC;QAChE,IAAI,CAAC,OAAO;YAAE,SAAS;QAEvB,MAAM,WAAW,GAAa,EAAE,CAAC;QACjC,IAAI,UAAU,GAAG,IAAI,CAAC;QAEtB,KAAK,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;YACzD,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;YAChC,IAAI,OAAe,CAAC;YACpB,IAAI,CAAC;gBACH,OAAO,GAAG,MAAM,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;YACxC,CAAC;YAAC,MAAM,CAAC;gBACP,UAAU,GAAG,KAAK,CAAC;gBACnB,SAAS;YACX,CAAC;YAED,IAAI,OAAe,CAAC;YACpB,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;gBAC7B,OAAO,GAAG,IAAI,CAAC,CAAC,yBAAyB;YAC3C,CAAC;iBAAM,CAAC;gBACN,IAAI,CAAC;oBACH,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;gBACtE,CAAC;gBAAC,MAAM,CAAC;oBACP,UAAU,GAAG,KAAK,CAAC;oBACnB,SAAS;gBACX,CAAC;YACH,CAAC;YAED,MAAM,SAAS,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,IAAI,CAAC,CAAC;YAC9C,IAAI,SAAS,EAAE,MAAM,IAAI,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC;gBAC5D,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,SAAS,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,IAAI,EAAE,CAAC,CAAC;YACnE,CAAC;YAED,IAAI,OAAO,KAAK,OAAO,EAAE,CAAC;gBACxB,MAAM,SAAS,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;gBACtC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACzB,CAAC;QACH,CAAC;QAED,IAAI,WAAW,CAAC,MAAM,GAAG,CAAC,IAAI,UAAU,EAAE,CAAC;YACzC,OAAO,CAAC,IAAI,EAAE,CAAC;YACf,MAAM,KAAK,CAAC,aAAa,CAAC,GAAG,CAAC,WAAW,EAAE,GAAG,EAAE,OAAO,CAAC,CAAC;YACzD,OAAO;gBACL,OAAO,EAAE,IAAI;gBACb,IAAI,EAAE,OAAO,CAAC,IAAI;gBAClB,WAAW;gBACX,WAAW,EAAE,oBAAoB,OAAO,CAAC,WAAW,EAAE;gBACtD,UAAU,EAAE,EAAE,WAAW,EAAE,CAAC,EAAE,YAAY,EAAE,CAAC,EAAE;aAChD,CAAC;QACJ,CAAC;QAED,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC7B,OAAO,CAAC,MAAM,EAAE,CAAC;YACjB,MAAM,KAAK,CAAC,aAAa,CAAC,GAAG,CAAC,WAAW,EAAE,GAAG,EAAE,OAAO,CAAC,CAAC;QAC3D,CAAC;IACH,CAAC;IAED,OAAO,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;AAC7B,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,GAAsB,EACtB,UAAkB;IAElB,MAAM,KAAK,GAAG,QAAQ,EAAE,CAAC;IACzB,MAAM,KAAK,CAAC,IAAI,EAAE,CAAC;IAEnB,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC;IACjD,IAAI,CAAC,MAAM;QAAE,OAAO,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IAE1C,MAAM,WAAW,GAAa,EAAE,CAAC;IACjC,KAAK,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;QAC3D,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;QAChC,IAAI,CAAC;YACH,MAAM,SAAS,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;YACtC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACzB,CAAC;QAAC,MAAM,CAAC;YACP,mEAAmE;YACnE,sDAAsD;YACtD,OAAO,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QAC/B,CAAC;IACH,CAAC;IAED,MAAM,CAAC,IAAI,EAAE,CAAC;IACd,MAAM,KAAK,CAAC,SAAS,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;IAE1C,OAAO;QACL,OAAO,EAAE,IAAI;QACb,IAAI,EAAE,OAAO,CAAC,MAAM;QACpB,WAAW;QACX,WAAW,EAAE,oBAAoB,MAAM,CAAC,IAAI,kBAAkB,MAAM,CAAC,KAAK,GAAG;QAC7E,UAAU,EAAE,EAAE,WAAW,EAAE,CAAC,EAAE,YAAY,EAAE,CAAC,EAAE;KAChD,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAC1C,GAAsB,EACtB,UAAkB,EAClB,WAAqB,EACrB,KAAa;IAEb,MAAM,KAAK,GAAG,QAAQ,EAAE,CAAC;IACzB,MAAM,KAAK,CAAC,IAAI,EAAE,CAAC;IAEnB,gDAAgD;IAChD,MAAM,KAAK,GAA2B,EAAE,CAAC;IACzC,KAAK,MAAM,IAAI,IAAI,WAAW,EAAE,CAAC;QAC/B,IAAI,CAAC;YACH,KAAK,CAAC,IAAI,CAAC,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,CAAC;QAC5D,CAAC;QAAC,MAAM,CAAC;YACP,wBAAwB;QAC1B,CAAC;IACH,CAAC;IACD,IAAI,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAClC,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC;QACnD,MAAM,KAAK,CAAC,SAAS,CAAC,UAAU,EAAE;YAChC,KAAK;YACL,IAAI,EAAE,CAAC,QAAQ,EAAE,IAAI,IAAI,CAAC,CAAC;YAC3B,KAAK;SACN,CAAC,CAAC;IACL,CAAC;AACH,CAAC;AAED,0EAA0E;AAC1E,MAAM,UAAU,eAAe,CAC7B,WAAmB,EACnB,IAAY,EACZ,EAAU,EACV,aAAqB;IAErB,qEAAqE;IACrE,2EAA2E;IAC3E,MAAM,UAAU,GAAG,aAAa;SAC7B,OAAO,CAAC,2BAA2B,EAAE,OAAO,CAAC;SAC7C,OAAO,CAAC,2BAA2B,EAAE,QAAQ,CAAC;SAC9C,OAAO,CAAC,WAAW,EAAE,EAAE,CAAC;SACxB,OAAO,CAAC,QAAQ,EAAE,QAAQ,CAAC;SAC3B,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC;SACpB,IAAI,EAAE;SACN,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;IAClB,OAAO,GAAG,WAAW,IAAI,IAAI,KAAK,EAAE,KAAK,UAAU,EAAE,CAAC;AACxD,CAAC;AAED,8EAA8E;AAE9E,SAAS,UAAU,CAAC,OAAe;IACjC,MAAM,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;IAC5D,OAAO,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACpC,CAAC;AAED,wEAAwE;AACxE,SAAS,sBAAsB,CAAC,aAAqB;IACnD,MAAM,KAAK,GAAG,aAAa,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACxC,MAAM,IAAI,GAAa,EAAE,CAAC;IAC1B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,CAAC,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QACtB,kDAAkD;QAClD,IAAI,uEAAuE,CAAC,IAAI,CAAC,CAAC,CAAC;eAC5E,CAAC,CAAC,MAAM,GAAG,EAAE,IAAI,CAAC,CAAC,MAAM,GAAG,GAAG,EAAE,CAAC;YACvC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YACb,IAAI,IAAI,CAAC,MAAM,IAAI,CAAC;gBAAE,MAAM;QAC9B,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,KAAK,CAAC,IAAa;IAC1B,OAAO;QACL,OAAO,EAAE,KAAK;QACd,IAAI;QACJ,WAAW,EAAE,EAAE;QACf,WAAW,EAAE,uBAAuB;QACpC,UAAU,EAAE,EAAE,WAAW,EAAE,CAAC,EAAE,YAAY,EAAE,CAAC,EAAE;KAChD,CAAC;AACJ,CAAC;AAED,+DAA+D;AAC/D,MAAM,UAAU,mBAAmB;IACjC,OAAO,gBAAgB;SACpB,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,OAAO,GAAG,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,YAAY,CAAC,SAAS,IAAI,CAAC,CAAC,YAAY,CAAC,OAAO,GAAG,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,WAAW,EAAE,CAAC;SACjI,IAAI,CAAC,IAAI,CAAC,CAAC;AAChB,CAAC"}
@@ -0,0 +1,124 @@
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 { readFile } from "node:fs/promises";
5
+ import { tmpdir } from "node:os";
6
+ import { join, dirname } from "node:path";
7
+ import { tryBuiltinCodemods, buildContextKey, FixTier, } from "./patterns.js";
8
+ async function withProject(files, fn) {
9
+ const dir = await mkdtemp(join(tmpdir(), "greenbump-patterns-"));
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
+ const reactApp = `import React from 'react';
22
+ import ReactDOM from 'react-dom';
23
+ import App from './App';
24
+
25
+ ReactDOM.render(
26
+ <App />,
27
+ document.getElementById('root')
28
+ );
29
+ `;
30
+ test("codemods: React 18→19 ReactDOM.render is rewritten to createRoot", async () => {
31
+ await withProject({ "src/index.tsx": reactApp }, async (dir) => {
32
+ const result = await tryBuiltinCodemods({
33
+ cwd: dir,
34
+ packageName: "react-dom",
35
+ fromVersion: "18.3.1",
36
+ toVersion: "19.2.0",
37
+ failureOutput: "TypeError: ReactDOM.render is not a function",
38
+ candidateFiles: ["src/index.tsx"],
39
+ });
40
+ assert.equal(result.applied, true);
41
+ assert.equal(result.tier, FixTier.REGEX);
42
+ assert.deepEqual(result.editedFiles, ["src/index.tsx"]);
43
+ assert.equal(result.tokensUsed.inputTokens, 0);
44
+ const fixed = await readFile(join(dir, "src/index.tsx"), "utf8");
45
+ assert.ok(fixed.includes("createRoot("));
46
+ assert.ok(fixed.includes("from 'react-dom/client'"));
47
+ assert.ok(!fixed.includes("ReactDOM.render("));
48
+ });
49
+ });
50
+ test("codemods: non-matching error output leaves files untouched", async () => {
51
+ await withProject({ "src/index.tsx": reactApp }, async (dir) => {
52
+ const result = await tryBuiltinCodemods({
53
+ cwd: dir,
54
+ packageName: "react-dom",
55
+ fromVersion: "18.3.1",
56
+ toVersion: "19.2.0",
57
+ failureOutput: "Error: something entirely different",
58
+ candidateFiles: ["src/index.tsx"],
59
+ });
60
+ assert.equal(result.applied, false);
61
+ const unchanged = await readFile(join(dir, "src/index.tsx"), "utf8");
62
+ assert.equal(unchanged, reactApp);
63
+ });
64
+ });
65
+ test("codemods: wrong package does not apply the react codemod", async () => {
66
+ await withProject({ "src/index.tsx": reactApp }, async (dir) => {
67
+ const result = await tryBuiltinCodemods({
68
+ cwd: dir,
69
+ packageName: "lodash",
70
+ fromVersion: "4.0.0",
71
+ toVersion: "5.0.0",
72
+ failureOutput: "TypeError: ReactDOM.render is not a function",
73
+ candidateFiles: ["src/index.tsx"],
74
+ });
75
+ assert.equal(result.applied, false);
76
+ });
77
+ });
78
+ test("codemods: wrong major version range does not apply", async () => {
79
+ await withProject({ "src/index.tsx": reactApp }, async (dir) => {
80
+ const result = await tryBuiltinCodemods({
81
+ cwd: dir,
82
+ packageName: "react-dom",
83
+ fromVersion: "17.0.2",
84
+ toVersion: "18.3.1",
85
+ failureOutput: "TypeError: ReactDOM.render is not a function",
86
+ candidateFiles: ["src/index.tsx"],
87
+ });
88
+ assert.equal(result.applied, false);
89
+ });
90
+ });
91
+ test("codemods: Vue 2→3 new Vue() is rewritten to createApp()", async () => {
92
+ const vueApp = `import Vue from 'vue';
93
+ import App from './App.vue';
94
+
95
+ new Vue({
96
+ render: h => h(App),
97
+ }).$mount('#app');
98
+ `;
99
+ await withProject({ "src/main.js": vueApp }, async (dir) => {
100
+ const result = await tryBuiltinCodemods({
101
+ cwd: dir,
102
+ packageName: "vue",
103
+ fromVersion: "2.7.0",
104
+ toVersion: "3.4.0",
105
+ failureOutput: "TypeError: Vue is not a constructor",
106
+ candidateFiles: ["src/main.js"],
107
+ });
108
+ assert.equal(result.applied, true);
109
+ const fixed = await readFile(join(dir, "src/main.js"), "utf8");
110
+ assert.ok(fixed.includes("createApp("));
111
+ assert.ok(!fixed.includes("new Vue("));
112
+ });
113
+ });
114
+ test("buildContextKey: identical errors across different paths collide", () => {
115
+ const key1 = buildContextKey("react", "18.0.0", "19.0.0", "TypeError: ReactDOM.render is not a function\n at /home/alice/project/src/index.tsx:5:3");
116
+ const key2 = buildContextKey("react", "18.0.0", "19.0.0", "TypeError: ReactDOM.render is not a function\n at /home/bob/other/src/main.tsx:12:7");
117
+ assert.equal(key1, key2);
118
+ });
119
+ test("buildContextKey: different errors produce different keys", () => {
120
+ const key1 = buildContextKey("react", "18.0.0", "19.0.0", "TypeError: foo is not a function");
121
+ const key2 = buildContextKey("react", "18.0.0", "19.0.0", "TypeError: bar is not a function");
122
+ assert.notEqual(key1, key2);
123
+ });
124
+ //# sourceMappingURL=patterns.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"patterns.test.js","sourceRoot":"","sources":["../../../src/engine/fixer/patterns.test.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,5 @@
1
1
  import { exec } from "./exec.js";
2
+ export { exec };
2
3
  export async function isGitRepo(cwd) {
3
4
  const r = await exec("git", ["rev-parse", "--is-inside-work-tree"], { cwd });
4
5
  return r.code === 0 && r.stdout.trim() === "true";
@@ -1 +1 @@
1
- {"version":3,"file":"git.js","sourceRoot":"","sources":["../../src/engine/git.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"git.js","sourceRoot":"","sources":["../../src/engine/git.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,111 @@
1
+ import { test } from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { mkdtemp, writeFile, rm, appendFile } from "node:fs/promises";
4
+ import { tmpdir } from "node:os";
5
+ import { join } from "node:path";
6
+ import { exec } from "./exec.js";
7
+ import { isGitRepo, isTreeClean, currentBranch, createBranch, checkout, commitAll, diffStat, fullDiff, changedFiles, } from "./git.js";
8
+ async function withRepo(fn) {
9
+ const dir = await mkdtemp(join(tmpdir(), "greenbump-git-"));
10
+ try {
11
+ await exec("git", ["init", "-q"], { cwd: dir });
12
+ // Never touch the machine's global git config — set identity locally only.
13
+ await exec("git", ["config", "user.email", "test@greenbump.dev"], { cwd: dir });
14
+ await exec("git", ["config", "user.name", "greenbump test"], { cwd: dir });
15
+ await fn(dir);
16
+ }
17
+ finally {
18
+ await rm(dir, { recursive: true, force: true });
19
+ }
20
+ }
21
+ /** Repo with one committed file, so HEAD is a real ref (not "unborn"). */
22
+ async function withInitializedRepo(fn) {
23
+ await withRepo(async (dir) => {
24
+ await writeFile(join(dir, "README.md"), "hello\n", "utf8");
25
+ await exec("git", ["add", "-A"], { cwd: dir });
26
+ await exec("git", ["commit", "-q", "-m", "init"], { cwd: dir });
27
+ await fn(dir);
28
+ });
29
+ }
30
+ test("isGitRepo: true inside a git repo, false in a plain directory", async () => {
31
+ await withRepo(async (dir) => {
32
+ assert.equal(await isGitRepo(dir), true);
33
+ });
34
+ const plain = await mkdtemp(join(tmpdir(), "greenbump-plain-"));
35
+ try {
36
+ assert.equal(await isGitRepo(plain), false);
37
+ }
38
+ finally {
39
+ await rm(plain, { recursive: true, force: true });
40
+ }
41
+ });
42
+ test("isTreeClean: true right after commit, false with an untracked or modified file", async () => {
43
+ await withInitializedRepo(async (dir) => {
44
+ assert.equal(await isTreeClean(dir), true);
45
+ await writeFile(join(dir, "untracked.txt"), "x", "utf8");
46
+ assert.equal(await isTreeClean(dir), false);
47
+ await exec("git", ["add", "-A"], { cwd: dir });
48
+ await exec("git", ["commit", "-q", "-m", "add untracked"], { cwd: dir });
49
+ assert.equal(await isTreeClean(dir), true);
50
+ await appendFile(join(dir, "README.md"), "more\n");
51
+ assert.equal(await isTreeClean(dir), false);
52
+ });
53
+ });
54
+ test("currentBranch + createBranch: creates and switches to a new branch", async () => {
55
+ await withInitializedRepo(async (dir) => {
56
+ const before = await currentBranch(dir);
57
+ assert.ok(before.length > 0);
58
+ await createBranch(dir, "greenbump/lodash-4.17.21");
59
+ const after = await currentBranch(dir);
60
+ assert.equal(after, "greenbump/lodash-4.17.21");
61
+ assert.notEqual(after, before);
62
+ });
63
+ });
64
+ test("checkout: switches HEAD back to an existing branch", async () => {
65
+ await withInitializedRepo(async (dir) => {
66
+ const original = await currentBranch(dir);
67
+ await createBranch(dir, "feature-x");
68
+ assert.equal(await currentBranch(dir), "feature-x");
69
+ await checkout(dir, original);
70
+ assert.equal(await currentBranch(dir), original);
71
+ });
72
+ });
73
+ test("commitAll: stages and commits pending changes, including untracked files", async () => {
74
+ await withInitializedRepo(async (dir) => {
75
+ await writeFile(join(dir, "new-file.txt"), "content", "utf8");
76
+ await appendFile(join(dir, "README.md"), "more\n");
77
+ assert.equal(await isTreeClean(dir), false);
78
+ await commitAll(dir, "chore(deps): bump lodash 4.17.20 → 4.17.21");
79
+ assert.equal(await isTreeClean(dir), true);
80
+ const log = await exec("git", ["log", "-1", "--pretty=%s"], { cwd: dir });
81
+ assert.equal(log.stdout.trim(), "chore(deps): bump lodash 4.17.20 → 4.17.21");
82
+ });
83
+ });
84
+ test("diffStat + fullDiff + changedFiles: report changes made since a ref", async () => {
85
+ await withInitializedRepo(async (dir) => {
86
+ const base = (await exec("git", ["rev-parse", "HEAD"], { cwd: dir })).stdout.trim();
87
+ await writeFile(join(dir, "src.js"), "export const x = 1;\n", "utf8");
88
+ await appendFile(join(dir, "README.md"), "changed\n");
89
+ await commitAll(dir, "add src.js and edit README");
90
+ const files = await changedFiles(dir, base);
91
+ assert.deepEqual([...files].sort(), ["README.md", "src.js"]);
92
+ const stat = await diffStat(dir, base);
93
+ assert.match(stat, /README\.md/);
94
+ assert.match(stat, /src\.js/);
95
+ const diff = await fullDiff(dir, base);
96
+ assert.match(diff, /\+export const x = 1;/);
97
+ assert.match(diff, /\+changed/);
98
+ });
99
+ });
100
+ test("commitAll: --no-verify means a failing pre-commit hook does not block the commit", async () => {
101
+ await withInitializedRepo(async (dir) => {
102
+ const hooksDir = join(dir, ".git", "hooks");
103
+ await writeFile(join(hooksDir, "pre-commit"), "#!/bin/sh\nexit 1\n", { mode: 0o755 });
104
+ await writeFile(join(dir, "another.txt"), "x", "utf8");
105
+ await commitAll(dir, "should succeed despite failing hook");
106
+ assert.equal(await isTreeClean(dir), true);
107
+ const log = await exec("git", ["log", "-1", "--pretty=%s"], { cwd: dir });
108
+ assert.equal(log.stdout.trim(), "should succeed despite failing hook");
109
+ });
110
+ });
111
+ //# sourceMappingURL=git.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git.test.js","sourceRoot":"","sources":["../../src/engine/git.test.ts"],"names":[],"mappings":"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"}