google-gemini / google-gemini/gemini-cli
Convert bundled ripgrep to platform-specific optionalDependencies
- Dominant language
- TypeScript
- Stars
- 107k
- Forks
- 14.6k
- Avg merge
- 2d 3h
- Merged PRs (30d)
- 45
Description
## Summary
Convert the bundled `ripgrep` binaries to platform-specific `optionalDependencies` (the `esbuild` / `@swc/core` / `rollup` pattern) so that:
1. npm-installed users get offline `ripgrep` again — restoring the goal of #25342 for the npm distribution channel.
2. Each user only downloads the **one** binary their OS+arch needs (~4–5 MB), instead of either getting nothing (today, after #25841) or all five (~21 MB, what #25342 tried).
3. The `@google/gemini-cli` tarball stays well under wombat-dressing-room's per-package upload limit (no recurrence of #25507's E413).
This is the long-term shape that #25841 deliberately did not block on (it required new npm packages + new wombat tokens, and we needed to unblock the release first).
## Why is this needed?
Today, after #25841, npm-installed users without a system `ripgrep` on PATH fall back to the JS-based `GrepTool` (slower, less feature-complete), which is the pre-#25342 behavior. Bringing offline `ripgrep` back requires either:
- This issue (per-platform `optionalDependencies`) — the standard npm pattern; works everywhere npm works; no postinstall network call.
- A `postinstall` download script — breaks behind corporate proxies, opt-out unfriendly, slower install.
- Compressing binaries inside the tarball — adds first-run latency and a writable cache requirement; doesn't address the underlying "we ship N×4 unused bytes" waste.
## Proposed plan
### 1. Five new sub-packages under `@google/`
```
@google/gemini-cli-ripgrep-darwin-arm64 ~3.4 MB
@google/gemini-cli-ripgrep-darwin-x64 ~4.1 MB
@google/gemini-cli-ripgrep-linux-arm64 ~3.8 MB
@google/gemini-cli-ripgrep-linux-x64 ~5.1 MB
@google/gemini-cli-ripgrep-win32-x64 ~4.7 MB
```
Each contains:
- `bin/rg` (or `rg.exe`)
- `package.json` with `"os"` + `"cpu"` constraints, e.g.:
```json
{
"name": "@google/gemini-cli-ripgrep-darwin-arm64",
"version": "",
"os": ["darwin"],
"cpu": ["arm64"],
"files": ["bin"],
"license": "MIT"
}
```
### 2. Wire into `@google/gemini-cli`
`scripts/prepare-npm-release.js` adds `optionalDependencies` to the published cli `package.json`:
```json
"optionalDependencies": {
"@google/gemini-cli-ripgrep-darwin-arm64": "",
"@google/gemini-cli-ripgrep-darwin-x64": "",
"@google/gemini-cli-ripgrep-linux-arm64": "",
"@google/gemini-cli-ripgrep-linux-x64": "",
"@google/gemini-cli-ripgrep-win32-x64": ""
}
```
npm only installs the dependency matching the user's OS+arch — exactly the same mechanism the cli already uses for `@lydell/node-pty-*`.
### 3. Runtime resolver
Add a third candidate to `getRipgrepPath()` in `packages/core/src/tools/ripGrep.ts`:
```ts
const candidatePaths = [
// 1. SEA layout (flattened)
path.resolve(__dirname, 'vendor/ripgrep', binName),
// 2. Dev layout
path.resolve(__dirname, '../../vendor/ripgrep', binName),
// 3. NEW: npm install layout (optionalDependency)
tryResolve(`@google/gemini-cli-ripgrep-${platform}-${arch}/bin/${binName}`),
].filter(Boolean);
```
Plus a small `tryResolve` helper that swallows `MODULE_NOT_FOUND`. SEA build is unchanged — still reads from `packages/core/vendor/ripgrep/`.
### 4. Release pipeline (the time-consuming part)
- Get 5 new wombat-dressing-room publish tokens provisioned by Google OSPO. **This is the blocker for total elapsed time** — past requests have taken anywhere from 2 days to 2 weeks.
- Extend `.github/actions/npm-auth-token/action.yml` to handle the 5 new package names (or refactor to a token-per-package map).
- Extend `.github/actions/publish-release/action.yml` with 5 new `npm publish --workspace=...` steps. Order: ripgrep packages first, then `core`, then `cli`, then `a2a-server`. This way, if ripgrep publish fails, the cli release isn't half-published with broken `optionalDependencies`.
- Add a verification step that all 5 ripgrep packages and the cli are at the same version after publish.
### 5. Tests
- Unit-test `getRipgrepPath()` resolution path 3 (mock `require.resolve`).
- Add a CI matrix smoke test that on each OS+arch the installed `@google/gemini-cli` resolves the matching `@google/gemini-cli-ripgrep-*` package and `rg --version` works.
## Estimated effort
- **Engineering work:** ~½ day end-to-end.
- **Total elapsed time:** gated on OSPO/wombat token provisioning — anywhere from a few days to a couple of weeks.
## Related Issues
- Caused by the trade-off in #25841 (this issue's predecessor)
- The original feature: #25342
- The release failure that led here: #25507
## Additional context
- `getRipgrepPath` already does multi-candidate path resolution, so adding a third candidate is a one-line change.
- The cli already uses the same pattern (`@lydell/node-pty-*` per-platform optional deps for native modules) — this would be the second instance.
- Consider naming under `@google-gemini/` instead of `@google/` if the OSPO token process is faster for the former.
Contributor guide
Assessment
This issue has not been assessed yet.