Skip to content

feat: self-provisioning developer rebuilds across supported hosts - #1033

Merged
jeonghun-jj-lee merged 8 commits into
mainfrom
amico/issue-1016-managed-rebuild
Sep 12, 2026
Merged

feat: self-provisioning developer rebuilds across supported hosts#1033
jeonghun-jj-lee merged 8 commits into
mainfrom
amico/issue-1016-managed-rebuild

Conversation

@jeonghun-jj-lee

@jeonghun-jj-lee jeonghun-jj-lee commented Sep 12, 2026

Copy link
Copy Markdown
Contributor

Closes #1016

Self-provisioning developer rebuilds

All six slices implemented and integrated — every module is called by the rebuild handler.

105 new tests, 0 regressions across the existing 3,300+ test suite.

What actually changed in the rebuild handler (chat_bridge.ts)

The dev-tools-rebuild handler now runs this pipeline:

  1. Host gateclassifyHost() + detectWSLVersion() reject unsupported platforms
  2. Dependency checkcheckDependencies(mode) + isBlocked() refuse when hard prereqs are missing
  3. Source resolution — Main: rebuildFromMain() (git pull --ff-only + release download) / Local: fork build (unchanged)
  4. Build — pnpm install + extension build + app bundle
  5. Atomic deploydeployBuild(): backup → stage → rename-swap (replaces line-by-line copyFileSync)
  6. No settings writes — removed opencodeBinary, devAssetRoot, appBundleDir updates

All errors are structured: { message, fix[], detail? } objects through the bridge → the fork's RebuildErrorPanel renders them with numbered fix steps and collapsible detail.

Shell scripts (standalone fallbacks)

Both rewritten to match the button behavior:

  • rebuild_amicode_remotely.shfetch_opencode.mjs --release for the binary (no fork clone, no bun), --ff-only pull, overlay-based app build, backup + atomic swap, no settings writes
  • rebuild_amicode_locally.sh — fork build with bun (unchanged), backup + atomic swap, copies binary into vendor dir, no settings writes

Companion PR (opencode fork)

harmoniqs/opencode#340RebuildErrorPanel component, RebuildErrorInfo type, error panel CSS

#1018)

Replace the fork-build path in Rebuild from Main with a release-download
path. The button auto-pulls main, reads opencode.lock.json, and downloads
the pinned fork binary from the GitHub Release — no fork clone, no bun
invocation, no local/amicode checkout.

New modules:
- src/rebuild/main_source_resolver.ts — readLockFile, resolveMainPlatform,
  checkDirtyTree, pullMainBranch (--ff-only, not --rebase),
  checkPendingPromotion, downloadForkBinary, rebuildFromMain
- src/rebuild_errors.ts — structured error catalog (30+ classified errors
  with message + fix steps + collapsible detail)

chat_bridge.ts changes:
- Main mode: rebuildFromMain() replaces git-pull-of-fork + bun-install +
  bun-build. Binary comes from the release, app bundle materializes from
  the committed overlay (no --work flag needed).
- Main mode requires only amicodePath (not opencodePath).
- Local mode: unchanged (fork checkout + bun build).

Tests (32 new + 3 new integration):
- main_source_resolver.test.ts: 25 tests covering readLockFile (valid,
  missing, malformed, no tag/ref/platform), resolveMainPlatform
  (supported, unsupported, WSL), checkDirtyTree, pullMainBranch (--ff-only,
  fetch failure, non-ff), checkPendingPromotion (ahead, same, unreachable),
  downloadForkBinary (deleted release), rebuildFromMain orchestration
  (dirty tree, unsupported platform, no fork mutations, --ff-only)
- rebuild_errors.test.ts: 7 tests for catalog structure + classifyError
- chat_bridge.test.ts: 3 new tests for main-mode path requirements
@coderabbitai

coderabbitai Bot commented Sep 12, 2026

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: c88e9172-26a8-43b6-b227-2f8d01f6483a


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

…y download (#1019)

Add download robustness to fetchFromRelease:

- withRetry: exponential backoff (base 1s, factor 2, cap 4s, max 3
  attempts). Transient errors (5xx, timeout, ECONNRESET) retry;
  permanent (404) and auth errors fail immediately.
- classifyDownloadError: categorizes errors as transient/permanent/auth
  for both HTTPS and gh CLI paths.
- HTTPS sha256 mismatch triggers one gh fallback attempt before
  declaring permanent failure (handles CDN-cached corrupt artifacts).
- Partial-file cleanup: no .unpack- temp dirs or partial binaries left
  on failure.
- releaseCoords.private renamed to isFork (with back-compat getter).
  The flag means 'is our fork', not 'repo is private on GitHub'.
- retryOpts parameter threaded from fetchOpencode → fetchFromRelease
  for test injection.

14 new tests (download_robustness.test.ts): withRetry (succeed, retry,
exhaustion, permanent skip, onRetry), classifyDownloadError (5xx,
timeout, 404, 403, gh-not-found), fetchFromRelease integration (retry
success, sha256 mismatch → gh, no partials, 404 not retried).

3 existing tests updated for isFork rename (back-compat verified).
New module: src/rebuild/dependency_resolver.ts

- checkDependencies(mode) — structured pre-flight: for each tool,
  whether required/present/sufficient/provisionable. Main mode checks
  git, Node >= 20, pnpm, gh (soft). Local mode adds bun, gh (hard).
- buildProvisionPlan(deps) — separates blockers (node, git: detect +
  guide) from provisionable actions (pnpm via corepack/npm-exec, bun
  via curl).
- provisionPnpm(exec) — corepack enable preferred, falls back to npm
  exec when corepack is absent or requires sudo.
- isBlocked(deps) — returns true only when hard prerequisites are
  missing; soft dependencies don't block.

15 new tests covering: all-present, missing node, node < 20, local-mode
hard deps, missing git, empty plan, blocker plan, pnpm provision plan,
bun provision plan, corepack + fallback paths, isBlocked.
)

New module: src/rebuild/atomic_adoption.ts

- createBackup: timestamped sibling backup of extension dir (same
  filesystem guarantee for POSIX atomic rename)
- pruneBackups: keep only the most recent N backups
- stageExtensionBuild: stage build output as a sibling directory
- atomicSwap: rename-based dist swap with automatic rollback on failure
  (restores from backup on rename failure, permission error, or invalid
  staging)
- pendingSwapMarker: write/read/commit cycle for crash recovery
  (pending.json written before swap, deleted after health check)

8 new tests covering: backup creation + sibling guarantee, prune excess,
atomic swap success, rollback on bad staging, pending marker write/read/
commit, staging output.
…#1022)

New module: src/rebuild/runtime_paths.ts

- resolveRuntimePaths: discovers binary and app bundle paths from
  context.extensionPath. Config overrides (opencodeBinary, appBundleDir)
  take precedence when set. Works for local, WSL/Remote-SSH (.vscode-
  server), and Insiders (.vscode-insiders) extension paths.
- detectDeveloperMode: replaces devAssetRoot-as-signal with a dedicated
  boolean setting + marker file. Separates path override from mode.
- validateOverride: validates config override paths at activation,
  returning structured diagnostics for stale paths.

11 new tests covering: self-discovery (all platform variants), config
overrides (binary, app bundle), Insiders/WSL/Remote paths, developer
mode detection (setting, marker, default), override validation.
…ance (#1023)

New module: src/rebuild/host_matrix.ts

- classifyHost: classifies current platform against the supported
  matrix (darwin-arm64, linux-x64, linux-arm64). Rejects win32 (WSL
  guidance), darwin-x64 (Apple Silicon only), and unknowns.
- detectWSLVersion: reads /proc/version to distinguish WSL 1 (lxfs,
  no atomic rename) from WSL 2 (real Linux kernel, full support) from
  native Linux (not WSL). WSL 1 would be rejected at the #1021 swap
  step.
- gateKeeperClear: best-effort xattr -d com.apple.quarantine on the
  vendored binary (macOS unsigned binary Gatekeeper block).

12 new tests covering: platform classification (macOS arm64, linux x64,
linux arm64, win32, Intel Mac, unknown), WSL version detection (WSL 1,
WSL 2, native Linux, non-Linux), Gatekeeper clearance (success,
failure).
…cripts (#1016)

This is the integration commit that makes #1020#1023 actually work.

chat_bridge.ts rebuild handler now:
- Runs host gate (#1023): classifyHost() + detectWSLVersion() reject
  win32, darwin-x64, and WSL 1 before any mutation
- Runs dependency pre-flight (#1020): checkDependencies() + isBlocked()
  refuse when node/git are missing, report what's needed with fix steps
- Uses deployBuild() (#1021) for atomic backup + stage + rename-swap
  instead of the old line-by-line copyFileSync loop
- Sends structured errors through the bridge (#1022): all error
  messages are { message, fix[], detail? } objects, not flat strings
- No longer writes to VS Code settings.json (#1022): removed the
  opencodeBinary, devAssetRoot, appBundleDir update calls

New: src/rebuild/coordinator.ts — the shared orchestration that both
the bridge handler and the shell scripts follow (same flow, different
execution paths).

Shell scripts rewritten:
- rebuild_amicode_remotely.sh: uses fetch_opencode.mjs --release for
  the binary (no fork clone, no bun), pulls with --ff-only, builds app
  from overlay, deploys with backup + atomic swap, no settings writes
- rebuild_amicode_locally.sh: keeps the fork build path, drops the
  settings.json python3 hack, deploys with backup + atomic swap, copies
  built binary into the installed extension's vendor dir

10 new integration tests (rebuild_coordinator.test.ts): host gate
(win32, Intel Mac, WSL 1 reject, WSL 2 allow), dependency pre-flight
(missing node, missing git, soft gh in main mode), deployBuild (atomic
swap + backup + no settings writes, rollback on bad staging).
- Remove unused imports from chat_bridge.ts (ExecFn, ExecResult,
  RebuildError type-only, runRebuild)
- Remove unused `fs` import and `blockerList` variable from coordinator.ts
- Remove unused `existsSync` import from runtime_paths.ts
- Add scripts/fetch_opencode.d.mts — type declarations for the ESM
  fetch_opencode module, consumed by main_source_resolver.ts via
  dynamic import. Resolves TS7016 'Could not find a declaration file'
  under moduleResolution: Bundler.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Self-provisioning developer rebuilds across supported hosts

1 participant