greenbump 0.7.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.
- package/README.md +73 -15
- package/README.zh-CN.md +37 -4
- package/dist/action/github.test.js +37 -0
- package/dist/action/github.test.js.map +1 -0
- package/dist/action/main.js +19 -4
- package/dist/action/main.js.map +1 -1
- package/dist/agent/calllog.js +23 -0
- package/dist/agent/calllog.js.map +1 -0
- package/dist/agent/calllog.test.js +77 -0
- package/dist/agent/calllog.test.js.map +1 -0
- package/dist/agent/fixer.js +246 -29
- package/dist/agent/fixer.js.map +1 -1
- package/dist/agent/fixer.test.js +299 -0
- package/dist/agent/fixer.test.js.map +1 -1
- package/dist/agent/guard.js +37 -0
- package/dist/agent/guard.js.map +1 -0
- package/dist/agent/guard.test.js +48 -0
- package/dist/agent/guard.test.js.map +1 -0
- package/dist/agent/provider-anthropic.js +2 -2
- package/dist/agent/provider-anthropic.js.map +1 -1
- package/dist/agent/provider-openai.js +2 -2
- package/dist/agent/provider-openai.js.map +1 -1
- package/dist/benchmarks/run.js +0 -0
- package/dist/benchmarks/run.js.map +1 -0
- package/dist/cli/interactive.js +46 -11
- package/dist/cli/interactive.js.map +1 -1
- package/dist/cli.js +37 -7
- package/dist/cli.js.map +1 -1
- package/dist/engine/analyzers/changelog-parser.js +51 -2
- package/dist/engine/analyzers/changelog-parser.js.map +1 -1
- package/dist/engine/batch.js +54 -10
- package/dist/engine/batch.js.map +1 -1
- package/dist/engine/cache/manager.js +19 -1
- package/dist/engine/cache/manager.js.map +1 -1
- package/dist/engine/cache/manager.test.js +17 -0
- package/dist/engine/cache/manager.test.js.map +1 -1
- package/dist/engine/change-detector.js +52 -23
- package/dist/engine/change-detector.js.map +1 -1
- package/dist/engine/change-detector.test.js +72 -0
- package/dist/engine/change-detector.test.js.map +1 -0
- package/dist/engine/changelog.js +22 -2
- package/dist/engine/changelog.js.map +1 -1
- package/dist/engine/changelog.test.js +31 -0
- package/dist/engine/changelog.test.js.map +1 -0
- package/dist/engine/checks.js +10 -4
- package/dist/engine/checks.js.map +1 -1
- package/dist/engine/context/optimizer.js +14 -44
- package/dist/engine/context/optimizer.js.map +1 -1
- package/dist/engine/context/optimizer.test.js +23 -1
- package/dist/engine/context/optimizer.test.js.map +1 -1
- package/dist/engine/ecosystems/cargo.js +2 -1
- package/dist/engine/ecosystems/cargo.js.map +1 -1
- package/dist/engine/ecosystems/conan.js +2 -1
- package/dist/engine/ecosystems/conan.js.map +1 -1
- package/dist/engine/ecosystems/elm.js +2 -1
- package/dist/engine/ecosystems/elm.js.map +1 -1
- package/dist/engine/ecosystems/gradle.js +2 -1
- package/dist/engine/ecosystems/gradle.js.map +1 -1
- package/dist/engine/ecosystems/http.js +18 -0
- package/dist/engine/ecosystems/http.js.map +1 -0
- package/dist/engine/ecosystems/maven.js +2 -1
- package/dist/engine/ecosystems/maven.js.map +1 -1
- package/dist/engine/ecosystems/swiftpm.js +2 -1
- package/dist/engine/ecosystems/swiftpm.js.map +1 -1
- package/dist/engine/exec.js +37 -7
- package/dist/engine/exec.js.map +1 -1
- package/dist/engine/exec.test.js +36 -0
- package/dist/engine/exec.test.js.map +1 -0
- package/dist/engine/fixer/patterns.js +55 -18
- package/dist/engine/fixer/patterns.js.map +1 -1
- package/dist/engine/fixer/patterns.test.js +95 -1
- package/dist/engine/fixer/patterns.test.js.map +1 -1
- package/dist/engine/git.js +38 -16
- package/dist/engine/git.js.map +1 -1
- package/dist/engine/git.test.js +51 -1
- package/dist/engine/git.test.js.map +1 -1
- package/dist/engine/perf/metrics.js +44 -57
- package/dist/engine/perf/metrics.js.map +1 -1
- package/dist/engine/run.js +141 -15
- package/dist/engine/run.js.map +1 -1
- package/dist/engine/run.test.js +65 -1
- package/dist/engine/run.test.js.map +1 -1
- package/dist/engine/sandbox/docker-manager.js +52 -26
- package/dist/engine/sandbox/docker-manager.js.map +1 -1
- package/dist/engine/sandbox/template-generator.js +0 -4
- package/dist/engine/sandbox/template-generator.js.map +1 -1
- package/dist/engine/sandbox/template-generator.test.js +16 -0
- package/dist/engine/sandbox/template-generator.test.js.map +1 -0
- package/dist/engine/scan/benchmark-crosscheck.test.js +116 -0
- package/dist/engine/scan/benchmark-crosscheck.test.js.map +1 -0
- package/dist/engine/scan/detect.js +96 -0
- package/dist/engine/scan/detect.js.map +1 -0
- package/dist/engine/scan/impact.js +128 -0
- package/dist/engine/scan/impact.js.map +1 -0
- package/dist/engine/scan/impact.test.js +217 -0
- package/dist/engine/scan/impact.test.js.map +1 -0
- package/dist/engine/scan/indexer.js +129 -0
- package/dist/engine/scan/indexer.js.map +1 -0
- package/dist/engine/scan/indexer.test.js +149 -0
- package/dist/engine/scan/indexer.test.js.map +1 -0
- package/dist/engine/scan/probes.js +92 -0
- package/dist/engine/scan/probes.js.map +1 -0
- package/dist/engine/scan/probes.test.js +100 -0
- package/dist/engine/scan/probes.test.js.map +1 -0
- package/dist/engine/verify.js +41 -58
- package/dist/engine/verify.js.map +1 -1
- package/dist/engine/verify.test.js +49 -0
- package/dist/engine/verify.test.js.map +1 -0
- package/dist/format.js +96 -0
- package/dist/format.js.map +1 -1
- package/dist/format.test.js +53 -0
- package/dist/format.test.js.map +1 -0
- package/dist/pr.test.js +48 -0
- package/dist/pr.test.js.map +1 -0
- package/package.json +4 -4
- package/dist/engine/dep-graph.js +0 -101
- package/dist/engine/dep-graph.js.map +0 -1
- package/dist/engine/staged-fix.js +0 -108
- package/dist/engine/staged-fix.js.map +0 -1
- package/dist/engine/stages.js +0 -125
- package/dist/engine/stages.js.map +0 -1
package/README.md
CHANGED
|
@@ -133,7 +133,7 @@ then re-ran your test suite to confirm the fix — all inside the fix loop shown
|
|
|
133
133
|
- 🐳 **Docker sandbox mode** (`--sandbox`) - Run tests in clean containers
|
|
134
134
|
- 🗄️ **Database integration testing** - Auto-start PostgreSQL, MySQL, Redis, MongoDB
|
|
135
135
|
- 📊 **Performance regression detection** - Track build time, bundle size, memory usage
|
|
136
|
-
- 🎯
|
|
136
|
+
- 🎯 Extra assurance on top of your local build/test run — the sandbox reruns the same checks in a clean container
|
|
137
137
|
|
|
138
138
|
### 🌍 Multi-Ecosystem Support
|
|
139
139
|
- **20+ ecosystems**: npm, pip, cargo, maven, poetry, gradle, and more
|
|
@@ -143,6 +143,8 @@ then re-ran your test suite to confirm the fix — all inside the fix loop shown
|
|
|
143
143
|
### 🛡️ Safe by Default
|
|
144
144
|
- **Git branch isolation** - Never touches your main branch
|
|
145
145
|
- **No auto-merge** - Always requires human review
|
|
146
|
+
- **Hard tool guardrails** - The agent physically cannot write dependency manifests/lockfiles or read secrets (`.env`, keys, credentials) — enforced in the tool layer, not just the prompt
|
|
147
|
+
- **Clear rollback** - A failed fix prints the exact commands to discard the branch and partial edits
|
|
146
148
|
- **Bounded cost** - `--max-rounds` caps token spend
|
|
147
149
|
- **Bring your own key** - You control costs, nothing phones home
|
|
148
150
|
|
|
@@ -167,7 +169,7 @@ then re-ran your test suite to confirm the fix — all inside the fix loop shown
|
|
|
167
169
|
- ✅ Multi-ecosystem support
|
|
168
170
|
- ✅ Actual code fixing
|
|
169
171
|
- ✅ Model flexibility
|
|
170
|
-
- ✅
|
|
172
|
+
- ✅ Isolated verification (optional sandbox + database services)
|
|
171
173
|
|
|
172
174
|
---
|
|
173
175
|
|
|
@@ -194,10 +196,50 @@ npx greenbump eslint --to 9.15.0
|
|
|
194
196
|
# Let greenbump pick the most-outdated dependency
|
|
195
197
|
npx greenbump
|
|
196
198
|
|
|
197
|
-
#
|
|
199
|
+
# Preview upgrade impact — deterministic, no API key, no LLM calls
|
|
198
200
|
npx greenbump --scan
|
|
199
201
|
```
|
|
200
202
|
|
|
203
|
+
### Deterministic preview: `--scan`
|
|
204
|
+
|
|
205
|
+
`--scan` answers one question **without spending a token**: *if I upgrade these
|
|
206
|
+
dependencies, which of my files break?*
|
|
207
|
+
|
|
208
|
+
```bash
|
|
209
|
+
npx greenbump --scan
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
```
|
|
213
|
+
🌱 greenbump scan — 1 outdated dependency, 1 with known breaking changes
|
|
214
|
+
|
|
215
|
+
zod 3.23.8 → 4.6.7 [breaking: 2 changes — 1 files, 1 locations]
|
|
216
|
+
1. [high] error.errors → error.issues (Zod 4) (builtin-codemod)
|
|
217
|
+
fix: .error.issues
|
|
218
|
+
src/validate.js:6: return r.error.errors[0].message;
|
|
219
|
+
→ 1 location in 1 file
|
|
220
|
+
2. [high] z.string().email()/.url()/.uuid() → z.email()/z.url()/z.uuid() (Zod 4)
|
|
221
|
+
→ 0 locations in 0 files
|
|
222
|
+
|
|
223
|
+
Summary: 1 dependency with breaking changes touch 1 file, 1 location.
|
|
224
|
+
Deterministic preview: no LLM calls, no network — sources: built-in codemods, changelog cache.
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
Notes on what you're seeing:
|
|
228
|
+
|
|
229
|
+
- **No API key required.** Scan reads its built-in codemod registry and your local
|
|
230
|
+
changelog cache only. It never calls a model and never touches the network.
|
|
231
|
+
- **The count that matters is "how many touch your code"**, not how many exist. Breaking
|
|
232
|
+
changes listed with `0 locations` do not affect this repository.
|
|
233
|
+
- **`unknown` means unknown.** If a dependency has no built-in codemod and no cached
|
|
234
|
+
analysis, scan says so instead of guessing. Run a real upgrade once
|
|
235
|
+
(`npx greenbump <dep>`) and its changelog digest is cached — the next `--scan` is
|
|
236
|
+
free forever.
|
|
237
|
+
- **`--scan --json`** emits the same data as structured JSON (`schemaVersion: 1`) for CI
|
|
238
|
+
or dashboards.
|
|
239
|
+
- **`--scan --no-impact`** restores the plain outdated-dependency list.
|
|
240
|
+
- Results are **file:line precise** and read-only: nothing is modified, nothing is
|
|
241
|
+
committed.
|
|
242
|
+
|
|
201
243
|
### Advanced Usage
|
|
202
244
|
|
|
203
245
|
```bash
|
|
@@ -232,7 +274,8 @@ npx greenbump react react-dom --group react-upgrade
|
|
|
232
274
|
| `--to <version>` | Target version (default: `latest`). |
|
|
233
275
|
| `--ecosystem <id>` | Dependency ecosystem (`npm`, `poetry`, `cargo`, `maven`, …). Auto-detected if omitted. |
|
|
234
276
|
| `--list-ecosystems` | List every supported ecosystem and exit. |
|
|
235
|
-
| `--scan` |
|
|
277
|
+
| `--scan` | Preview upgrade impact without upgrading: lists outdated dependencies **and** which files/lines each known breaking change would touch. Deterministic — no LLM, no API key, no network. |
|
|
278
|
+
| `--no-impact` | With `--scan`, skip impact analysis and just list outdated dependencies. |
|
|
236
279
|
| `--all` | Upgrade every outdated dependency found. |
|
|
237
280
|
| `--group <name>` | Combine multiple deps into one branch/PR. |
|
|
238
281
|
|
|
@@ -249,6 +292,7 @@ npx greenbump react react-dom --group react-upgrade
|
|
|
249
292
|
|---|---|
|
|
250
293
|
| `--provider <name>` | Model provider preset (`anthropic`, `openai`, `deepseek`, `groq`, …). |
|
|
251
294
|
| `--model <model>` | Model id for the fix agent (default: per provider). |
|
|
295
|
+
| `--digest-model <model>` | Cheaper model id for utility tasks (changelog digest); default: same as `--model`. |
|
|
252
296
|
| `--list-providers` | List built-in provider presets and exit. |
|
|
253
297
|
| `--max-rounds <n>` | Cap fix-loop rounds / token spend (default: `15`). |
|
|
254
298
|
| `--max-tokens <n>` | Hard cap on total tokens spent; stops and flags for review on overrun. |
|
|
@@ -274,7 +318,7 @@ npx greenbump react react-dom --group react-upgrade
|
|
|
274
318
|
| `--cache-stats` | Show cache entry count, size, and per-category breakdown. |
|
|
275
319
|
| `--cache-clear [category]` | Clear cached entries (`changelogs`, `llm-fixes`, `patterns`; all if omitted). |
|
|
276
320
|
| `--stats [days]` | Show a usage/cost-savings report for the last N days (default 30) and exit. |
|
|
277
|
-
| `--json` | With `--stats`, print the raw JSON
|
|
321
|
+
| `--json` | With `--stats` or `--scan`, print the raw JSON output instead of the formatted report. |
|
|
278
322
|
|
|
279
323
|
#### How the tiered fix strategy keeps costs down
|
|
280
324
|
|
|
@@ -283,8 +327,8 @@ When an upgrade breaks the build, greenbump tries four escalating fix tiers —
|
|
|
283
327
|
| Tier | Strategy | Cost |
|
|
284
328
|
|---|---|---|
|
|
285
329
|
| 1 | **Builtin codemods** — regex transforms for well-known breakages (React 18→19, Express 4→5, Zod 3→4, React Router 5→6, Mongoose, Lodash, Node util.\*, …) | 0 tokens |
|
|
286
|
-
| 2 | **Learned patterns** —
|
|
287
|
-
| 3 | **Cached LLM fixes** —
|
|
330
|
+
| 2 | **Learned patterns** — extension point for hand-seeded fix rules (not auto-populated yet; see note below) | 0 tokens |
|
|
331
|
+
| 3 | **Cached LLM fixes** — replays a previous fix only when the failure context AND the candidate files' contents both match | 0 tokens |
|
|
288
332
|
| 4 | **LLM fix loop** — the full agent, only when tiers 1–3 miss | paid |
|
|
289
333
|
|
|
290
334
|
24 builtin codemods ship out of the box (`greenbump --list-codemods` for the full list) —
|
|
@@ -294,7 +338,10 @@ edit, so tier 1 never silently misapplies a risky change.
|
|
|
294
338
|
|
|
295
339
|
Verified end-to-end: Express 4→5 (`app.del`→`app.delete`) and Zod 3→4 (`error.errors`→
|
|
296
340
|
`error.issues`) are both fixed by tier 1 with **0 input / 0 output tokens**. Successful
|
|
297
|
-
LLM fixes are
|
|
341
|
+
LLM fixes are stored in the tier-3 cache, so a repeat of the *same* failure on the *same*
|
|
342
|
+
code is free too. (Tier 2 exists as an extension point for hand-seeded patterns — the
|
|
343
|
+
auto-distillation of LLM fixes into generic rules is not implemented yet, and the tier
|
|
344
|
+
table above says so.)
|
|
298
345
|
|
|
299
346
|
#### See what it's actually saving you
|
|
300
347
|
|
|
@@ -310,10 +357,23 @@ override with `GREENBUMP_STATS_DIR`) — no network calls, nothing leaves your m
|
|
|
310
357
|
spent, and an *estimated* dollar figure saved by the free tiers (clearly labeled as an
|
|
311
358
|
estimate, not a bill).
|
|
312
359
|
|
|
313
|
-
|
|
360
|
+
Every individual LLM call is also recorded to `~/.greenbump/llm-calls.jsonl` with its
|
|
361
|
+
feature (`fix-loop` / `changelog-digest`), model, input/output tokens, latency, retry
|
|
362
|
+
count, and success/failure — so cost is attributable per task, not just per run.
|
|
363
|
+
|
|
364
|
+
#### Model routing and output caps
|
|
365
|
+
|
|
366
|
+
Not every task needs the strong model. The changelog digest is a simple extraction
|
|
367
|
+
task: route it to a cheaper model with `--digest-model` (e.g.
|
|
368
|
+
`greenbump react --digest-model gpt-4o-mini`) while the fix agent keeps `--model`.
|
|
369
|
+
Each call has a bounded output cap (8000 tokens for the fix agent, 2000 for the
|
|
370
|
+
digest), and the digest result is cached per upgrade path so it's paid at most once.
|
|
371
|
+
|
|
372
|
+
### Git & Isolation
|
|
314
373
|
|
|
315
374
|
| Flag | Description |
|
|
316
375
|
|---|---|
|
|
376
|
+
| `--worktree` | Run the upgrade in an isolated temporary git worktree to keep the primary directory clean. |
|
|
317
377
|
| `--no-git` | Operate in place instead of on a new branch. |
|
|
318
378
|
| `--pr-body` | Print a ready-to-paste PR body. |
|
|
319
379
|
| `--report-file <path>` | Write a JSON report of the run(s) to this path. |
|
|
@@ -409,7 +469,6 @@ graph TB
|
|
|
409
469
|
subgraph "Verification Layer"
|
|
410
470
|
STATIC[Static Analysis<br/>TypeScript + ESLint]
|
|
411
471
|
CHANGE[Change Detection<br/>Test mods, large deletions]
|
|
412
|
-
GRAPH[Dependency Graph<br/>Multi-stage fixes]
|
|
413
472
|
end
|
|
414
473
|
|
|
415
474
|
subgraph "Sandbox Layer (Optional)"
|
|
@@ -442,8 +501,7 @@ graph TB
|
|
|
442
501
|
TOOLS --> CHECK
|
|
443
502
|
|
|
444
503
|
STATIC --> CHANGE
|
|
445
|
-
CHANGE -->
|
|
446
|
-
GRAPH --> DOCKER
|
|
504
|
+
CHANGE --> DOCKER
|
|
447
505
|
|
|
448
506
|
DOCKER --> SERVICES
|
|
449
507
|
SERVICES --> CONTAINER
|
|
@@ -467,7 +525,7 @@ graph TB
|
|
|
467
525
|
| **Isolation** | Git Branch Manager, Baseline Runner | Isolate work on feature branch, verify starting state |
|
|
468
526
|
| **Upgrade** | Package Manager, Verification Runner | Install new version and run checks |
|
|
469
527
|
| **AI Fix** | LLM Agent, Tools, Changelog Fetcher | Automatically fix breaking changes with AI |
|
|
470
|
-
| **Verification** | Static Analysis, Change Detection
|
|
528
|
+
| **Verification** | Static Analysis, Change Detection | Multi-layer validation (types, lints, suspicious changes) |
|
|
471
529
|
| **Sandbox** | Docker Builder, Service Manager, Container Runner | Optional isolated testing with database services |
|
|
472
530
|
| **Performance** | Performance Tracker, Regression Detector | Optional performance monitoring and regression alerts |
|
|
473
531
|
| **Output** | Git Commit, PR Body Generator | Commit changes and generate PR description |
|
|
@@ -633,7 +691,7 @@ flowchart LR
|
|
|
633
691
|
```
|
|
634
692
|
|
|
635
693
|
Each layer is independently swappable: pick any ecosystem adapter, any model provider, and
|
|
636
|
-
opt into the sandbox
|
|
694
|
+
opt into the sandbox when you want the checks rerun in a clean container.
|
|
637
695
|
|
|
638
696
|
---
|
|
639
697
|
|
|
@@ -749,7 +807,7 @@ Full example with all inputs: [examples/greenbump.yml](examples/greenbump.yml)
|
|
|
749
807
|
- [x] **v0.1.0**: Core fix loop + multi-ecosystem support
|
|
750
808
|
- [x] **v0.2.0**: Batch upgrades (`--all`), changelog integration
|
|
751
809
|
- [x] **v0.3.0**: Static analysis (TypeScript + ESLint) + change detection
|
|
752
|
-
- [x] **v0.4.0**:
|
|
810
|
+
- [x] **v0.4.0**: AST verification + changelog digest
|
|
753
811
|
- [x] **v0.5.0**: Docker sandbox + database testing + performance regression detection
|
|
754
812
|
- [ ] **v0.6.0**: Web UI for monitoring upgrades
|
|
755
813
|
- [ ] **v0.7.0**: Upgrade impact prediction (before running)
|
package/README.zh-CN.md
CHANGED
|
@@ -115,7 +115,7 @@ React 19 移除了 `ReactDOM.render`。下面是 greenbump 智能体为修复该
|
|
|
115
115
|
- 🐳 **Docker 沙箱模式**(`--sandbox`)— 在干净的容器中运行测试
|
|
116
116
|
- 🗄️ **数据库集成测试** — 自动启动 PostgreSQL、MySQL、Redis、MongoDB
|
|
117
117
|
- 📊 **性能回归检测** — 追踪构建时间、包体积、内存占用
|
|
118
|
-
- 🎯 **
|
|
118
|
+
- 🎯 **可复现 benchmark** — 6/6 无 LLM 用例通过(5 个 codemod 修复 + 1 个无误报),LLM 场景需自带 API key 实测(`npm run bench`,见 `benchmarks/`)
|
|
119
119
|
|
|
120
120
|
### 🌍 多生态系统支持
|
|
121
121
|
- **20+ 生态系统**:npm、pip、cargo、maven、poetry、gradle 等
|
|
@@ -177,10 +177,42 @@ npx greenbump eslint --to 9.15.0
|
|
|
177
177
|
# 不指定依赖,让 greenbump 自动选择最过期的一个
|
|
178
178
|
npx greenbump
|
|
179
179
|
|
|
180
|
-
#
|
|
180
|
+
# 预览升级影响 —— 确定性分析,不需要 API key,不调用大模型
|
|
181
181
|
npx greenbump --scan
|
|
182
182
|
```
|
|
183
183
|
|
|
184
|
+
### 确定性预览:`--scan`
|
|
185
|
+
|
|
186
|
+
`--scan` 用**零成本**回答一个问题:*如果升级这些依赖,我的哪些文件会炸?*
|
|
187
|
+
|
|
188
|
+
```bash
|
|
189
|
+
npx greenbump --scan
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
```
|
|
193
|
+
🌱 greenbump scan — 1 outdated dependency, 1 with known breaking changes
|
|
194
|
+
|
|
195
|
+
zod 3.23.8 → 4.6.7 [breaking: 2 changes — 1 files, 1 locations]
|
|
196
|
+
1. [high] error.errors → error.issues (Zod 4) (builtin-codemod)
|
|
197
|
+
fix: .error.issues
|
|
198
|
+
src/validate.js:6: return r.error.errors[0].message;
|
|
199
|
+
→ 1 location in 1 file
|
|
200
|
+
2. [high] z.string().email()/.url()/.uuid() → z.email()/z.url()/z.uuid() (Zod 4)
|
|
201
|
+
→ 0 locations in 0 files
|
|
202
|
+
|
|
203
|
+
Summary: 1 dependency with breaking changes touch 1 file, 1 location.
|
|
204
|
+
Deterministic preview: no LLM calls, no network — sources: built-in codemods, changelog cache.
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
几个要点:
|
|
208
|
+
|
|
209
|
+
- **不需要 API key。** scan 只读取内置 codemod 库和本地 changelog 缓存,不调用任何模型,也不联网。
|
|
210
|
+
- **真正有意义的数字是「影响你几处」,不是「总共多少个」。** 标注 `0 locations` 的破坏性变更对你的仓库没有影响。
|
|
211
|
+
- **`unknown` 就是「不知道」。** 如果某个依赖既没有内置 codemod、也没有缓存分析结果,scan 会如实说明,而不是瞎猜。跑一次真实升级(`npx greenbump <依赖名>`)就会把 changelog 解析结果缓存下来,之后 `--scan` 永久免费。
|
|
212
|
+
- **`--scan --json`** 输出结构化 JSON(`schemaVersion: 1`),方便接入 CI 或看板。
|
|
213
|
+
- **`--scan --no-impact`** 恢复成纯过时依赖列表。
|
|
214
|
+
- 结果是**精确到 file:line** 的,且完全只读:不改文件、不提交。
|
|
215
|
+
|
|
184
216
|
### 进阶用法
|
|
185
217
|
|
|
186
218
|
```bash
|
|
@@ -215,7 +247,8 @@ npx greenbump react react-dom --group react-upgrade
|
|
|
215
247
|
| `--to <version>` | 目标版本(默认:`latest`)。 |
|
|
216
248
|
| `--ecosystem <id>` | 依赖生态系统(`npm`、`poetry`、`cargo`、`maven` 等)。省略时自动检测。 |
|
|
217
249
|
| `--list-ecosystems` | 列出所有支持的生态系统并退出。 |
|
|
218
|
-
| `--scan` |
|
|
250
|
+
| `--scan` | 预览升级影响(不执行升级):列出过期依赖,**并**指出每个已知破坏性变更会触及哪些文件/哪些行。确定性分析——不调用大模型、不需要 API key、不联网。 |
|
|
251
|
+
| `--no-impact` | 配合 `--scan`,跳过影响面分析,仅列出过期依赖。 |
|
|
219
252
|
| `--all` | 升级所有检测到的过期依赖。 |
|
|
220
253
|
| `--group <name>` | 将多个依赖合并到同一个分支/PR。 |
|
|
221
254
|
|
|
@@ -257,7 +290,7 @@ npx greenbump react react-dom --group react-upgrade
|
|
|
257
290
|
| `--cache-stats` | 显示缓存条目数、体积及分类统计。 |
|
|
258
291
|
| `--cache-clear [category]` | 清除缓存条目(`changelogs`、`llm-fixes`、`patterns`;省略则全部清除)。 |
|
|
259
292
|
| `--stats [days]` | 显示最近 N 天(默认 30 天)的用量与节省报告并退出。 |
|
|
260
|
-
| `--json` | 配合 `--stats` 使用,输出原始 JSON 而非格式化报告。 |
|
|
293
|
+
| `--json` | 配合 `--stats` 或 `--scan` 使用,输出原始 JSON 而非格式化报告。 |
|
|
261
294
|
|
|
262
295
|
#### 分层修复策略如何降低成本
|
|
263
296
|
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { test } from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { mkdtemp, readFile, rm } from "node:fs/promises";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { getInput, setOutput } from "./github.js";
|
|
7
|
+
test("getInput: reads INPUT_* env vars (GitHub uppercases but keeps dashes) and trims", () => {
|
|
8
|
+
process.env["INPUT_ANTHROPIC-API-KEY"] = " sk-test ";
|
|
9
|
+
try {
|
|
10
|
+
assert.equal(getInput("anthropic-api-key"), "sk-test");
|
|
11
|
+
assert.equal(getInput("missing-input"), "");
|
|
12
|
+
}
|
|
13
|
+
finally {
|
|
14
|
+
delete process.env["INPUT_ANTHROPIC-API-KEY"];
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
test("setOutput: writes heredoc format to GITHUB_OUTPUT", async () => {
|
|
18
|
+
const dir = await mkdtemp(join(tmpdir(), "greenbump-ghout-"));
|
|
19
|
+
const file = join(dir, "output");
|
|
20
|
+
process.env.GITHUB_OUTPUT = file;
|
|
21
|
+
try {
|
|
22
|
+
setOutput("status", "pr-opened");
|
|
23
|
+
setOutput("pr-url", "https://example.com/pr/1");
|
|
24
|
+
const content = await readFile(file, "utf8");
|
|
25
|
+
assert.match(content, /status<<ghadelimiter_status\npr-opened\nghadelimiter_status\n/);
|
|
26
|
+
assert.match(content, /pr-url<<ghadelimiter_pr-url\nhttps:\/\/example\.com\/pr\/1\nghadelimiter_pr-url\n/);
|
|
27
|
+
}
|
|
28
|
+
finally {
|
|
29
|
+
delete process.env.GITHUB_OUTPUT;
|
|
30
|
+
await rm(dir, { recursive: true, force: true });
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
test("setOutput: without GITHUB_OUTPUT it is a no-op", () => {
|
|
34
|
+
delete process.env.GITHUB_OUTPUT;
|
|
35
|
+
setOutput("status", "anything"); // must not throw
|
|
36
|
+
});
|
|
37
|
+
//# sourceMappingURL=github.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"github.test.js","sourceRoot":"","sources":["../../src/action/github.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,kBAAkB,CAAC;AACzD,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAElD,IAAI,CAAC,iFAAiF,EAAE,GAAG,EAAE;IAC3F,OAAO,CAAC,GAAG,CAAC,yBAAyB,CAAC,GAAG,aAAa,CAAC;IACvD,IAAI,CAAC;QACH,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,mBAAmB,CAAC,EAAE,SAAS,CAAC,CAAC;QACvD,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,eAAe,CAAC,EAAE,EAAE,CAAC,CAAC;IAC9C,CAAC;YAAS,CAAC;QACT,OAAO,OAAO,CAAC,GAAG,CAAC,yBAAyB,CAAC,CAAC;IAChD,CAAC;AACH,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,mDAAmD,EAAE,KAAK,IAAI,EAAE;IACnE,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,kBAAkB,CAAC,CAAC,CAAC;IAC9D,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;IACjC,OAAO,CAAC,GAAG,CAAC,aAAa,GAAG,IAAI,CAAC;IACjC,IAAI,CAAC;QACH,SAAS,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;QACjC,SAAS,CAAC,QAAQ,EAAE,0BAA0B,CAAC,CAAC;QAChD,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QAC7C,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,+DAA+D,CAAC,CAAC;QACvF,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,mFAAmF,CAAC,CAAC;IAC7G,CAAC;YAAS,CAAC;QACT,OAAO,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC;QACjC,MAAM,EAAE,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAClD,CAAC;AACH,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,gDAAgD,EAAE,GAAG,EAAE;IAC1D,OAAO,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC;IACjC,SAAS,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC,CAAC,iBAAiB;AACpD,CAAC,CAAC,CAAC"}
|
package/dist/action/main.js
CHANGED
|
@@ -64,13 +64,21 @@ async function main() {
|
|
|
64
64
|
setOutput("status", summary.fixed ? "no-changes" : "unfixed");
|
|
65
65
|
return;
|
|
66
66
|
}
|
|
67
|
-
// configure a bot identity and push the branch
|
|
67
|
+
// configure a bot identity and push the branch.
|
|
68
|
+
// The token goes in an auth header, NOT the remote URL: git's error output
|
|
69
|
+
// echoes the remote URL on failure, and a URL-embedded token would land in
|
|
70
|
+
// the workflow log. (It still transits argv on the runner — acceptable on
|
|
71
|
+
// ephemeral single-tenant runners; the log leak was the real risk.)
|
|
68
72
|
await exec("git", ["config", "user.name", "greenbump[bot]"], { cwd });
|
|
69
73
|
await exec("git", ["config", "user.email", "greenbump@users.noreply.github.com"], { cwd });
|
|
70
|
-
const remote = `https://
|
|
71
|
-
const push = await exec("git", [
|
|
74
|
+
const remote = `https://github.com/${repoSlug}.git`;
|
|
75
|
+
const push = await exec("git", [
|
|
76
|
+
"-c", `http.https://github.com/.extraheader=AUTHORIZATION: bearer ${token}`,
|
|
77
|
+
"push", "--force", remote, `HEAD:${summary.branch}`,
|
|
78
|
+
], { cwd });
|
|
72
79
|
if (push.code !== 0) {
|
|
73
|
-
|
|
80
|
+
// Belt and braces: redact the token even though it should never appear now.
|
|
81
|
+
setFailed(`failed to push branch:\n${push.combined.split(token).join("***")}`);
|
|
74
82
|
return;
|
|
75
83
|
}
|
|
76
84
|
// Both "flagged for review" and "fix loop exhausted without going green"
|
|
@@ -92,6 +100,13 @@ async function main() {
|
|
|
92
100
|
labels: summary.needsReview ? [reviewLabel] : undefined,
|
|
93
101
|
});
|
|
94
102
|
setOutput("pr-url", url ?? "");
|
|
103
|
+
if (!url) {
|
|
104
|
+
// The branch IS pushed but the PR could not be created — saying
|
|
105
|
+
// "pr-opened" here would be lying about what happened.
|
|
106
|
+
setOutput("status", "pr-failed");
|
|
107
|
+
setFailed("branch pushed, but creating the pull request failed (see warnings above).");
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
95
110
|
setOutput("status", stillBroken ? "unfixed" : summary.needsReview ? "pr-needs-review" : "pr-opened");
|
|
96
111
|
info(draft ? "opened a draft PR for review." : "opened a PR — build + tests green.");
|
|
97
112
|
}
|
package/dist/action/main.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"main.js","sourceRoot":"","sources":["../../src/action/main.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"main.js","sourceRoot":"","sources":["../../src/action/main.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { mkdir, appendFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
function statsDir() {
|
|
5
|
+
return process.env.GREENBUMP_STATS_DIR ?? join(homedir(), ".greenbump");
|
|
6
|
+
}
|
|
7
|
+
function callsPath() {
|
|
8
|
+
return join(statsDir(), "llm-calls.jsonl");
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Append one call record. Best-effort: a write failure is logged once and
|
|
12
|
+
* swallowed — cost logging must never fail a run.
|
|
13
|
+
*/
|
|
14
|
+
export async function recordLlmCall(rec, onLog) {
|
|
15
|
+
try {
|
|
16
|
+
await mkdir(statsDir(), { recursive: true });
|
|
17
|
+
await appendFile(callsPath(), JSON.stringify(rec) + "\n", "utf8");
|
|
18
|
+
}
|
|
19
|
+
catch (err) {
|
|
20
|
+
onLog?.(`stats: failed to record LLM call (${err.message})`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=calllog.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"calllog.js","sourceRoot":"","sources":["../../src/agent/calllog.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AA0BlC,SAAS,QAAQ;IACf,OAAO,OAAO,CAAC,GAAG,CAAC,mBAAmB,IAAI,IAAI,CAAC,OAAO,EAAE,EAAE,YAAY,CAAC,CAAC;AAC1E,CAAC;AAED,SAAS,SAAS;IAChB,OAAO,IAAI,CAAC,QAAQ,EAAE,EAAE,iBAAiB,CAAC,CAAC;AAC7C,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,GAAkB,EAAE,KAA2B;IACjF,IAAI,CAAC;QACH,MAAM,KAAK,CAAC,QAAQ,EAAE,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAC7C,MAAM,UAAU,CAAC,SAAS,EAAE,EAAE,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,IAAI,EAAE,MAAM,CAAC,CAAC;IACpE,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,KAAK,EAAE,CAAC,qCAAsC,GAAa,CAAC,OAAO,GAAG,CAAC,CAAC;IAC1E,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { test } from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { mkdtemp, readFile, rm } from "node:fs/promises";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { recordLlmCall } from "./calllog.js";
|
|
7
|
+
test("recordLlmCall: appends one JSON line per call to the stats dir", async () => {
|
|
8
|
+
const dir = await mkdtemp(join(tmpdir(), "greenbump-calllog-"));
|
|
9
|
+
const prev = process.env.GREENBUMP_STATS_DIR;
|
|
10
|
+
process.env.GREENBUMP_STATS_DIR = dir;
|
|
11
|
+
try {
|
|
12
|
+
await recordLlmCall({
|
|
13
|
+
ts: 1,
|
|
14
|
+
feature: "fix-loop",
|
|
15
|
+
provider: "stub",
|
|
16
|
+
model: "stub-model",
|
|
17
|
+
inputTokens: 10,
|
|
18
|
+
outputTokens: 5,
|
|
19
|
+
durationMs: 42,
|
|
20
|
+
attempts: 1,
|
|
21
|
+
ok: true,
|
|
22
|
+
});
|
|
23
|
+
await recordLlmCall({
|
|
24
|
+
ts: 2,
|
|
25
|
+
feature: "changelog-digest",
|
|
26
|
+
provider: "stub",
|
|
27
|
+
model: "stub-cheap",
|
|
28
|
+
inputTokens: 0,
|
|
29
|
+
outputTokens: 0,
|
|
30
|
+
durationMs: 7,
|
|
31
|
+
attempts: 3,
|
|
32
|
+
ok: false,
|
|
33
|
+
error: "boom",
|
|
34
|
+
});
|
|
35
|
+
const lines = (await readFile(join(dir, "llm-calls.jsonl"), "utf8")).trim().split("\n");
|
|
36
|
+
assert.equal(lines.length, 2);
|
|
37
|
+
const first = JSON.parse(lines[0]);
|
|
38
|
+
assert.equal(first.feature, "fix-loop");
|
|
39
|
+
assert.equal(first.ok, true);
|
|
40
|
+
const second = JSON.parse(lines[1]);
|
|
41
|
+
assert.equal(second.ok, false);
|
|
42
|
+
assert.equal(second.error, "boom");
|
|
43
|
+
}
|
|
44
|
+
finally {
|
|
45
|
+
if (prev === undefined)
|
|
46
|
+
delete process.env.GREENBUMP_STATS_DIR;
|
|
47
|
+
else
|
|
48
|
+
process.env.GREENBUMP_STATS_DIR = prev;
|
|
49
|
+
await rm(dir, { recursive: true, force: true });
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
test("recordLlmCall: a write failure is swallowed and reported via onLog, never thrown", async () => {
|
|
53
|
+
const prev = process.env.GREENBUMP_STATS_DIR;
|
|
54
|
+
process.env.GREENBUMP_STATS_DIR = "/dev/null/impossible";
|
|
55
|
+
const logs = [];
|
|
56
|
+
try {
|
|
57
|
+
await recordLlmCall({
|
|
58
|
+
ts: 1,
|
|
59
|
+
feature: "fix-loop",
|
|
60
|
+
provider: "stub",
|
|
61
|
+
model: "m",
|
|
62
|
+
inputTokens: 0,
|
|
63
|
+
outputTokens: 0,
|
|
64
|
+
durationMs: 1,
|
|
65
|
+
attempts: 1,
|
|
66
|
+
ok: true,
|
|
67
|
+
}, (m) => logs.push(m));
|
|
68
|
+
assert.equal(logs.length, 1);
|
|
69
|
+
}
|
|
70
|
+
finally {
|
|
71
|
+
if (prev === undefined)
|
|
72
|
+
delete process.env.GREENBUMP_STATS_DIR;
|
|
73
|
+
else
|
|
74
|
+
process.env.GREENBUMP_STATS_DIR = prev;
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
//# sourceMappingURL=calllog.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"calllog.test.js","sourceRoot":"","sources":["../../src/agent/calllog.test.ts"],"names":[],"mappings":"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"}
|