Repository navigation
feat(cli): void github — install + connect a repo for Deploy-on-GitHub - #229
Conversation
Add GithubInstallation and GithubConnection exported types plus getGithubInstallUrl, listGithubInstallations, and connectGithub methods to PlatformClient. Tests mirror direct-r2-client.test.ts fetch-stub style and cover GETs, POST body/headers, and error path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds github-args.ts with githubSubcommands defs, GithubArgs discriminated union, and parseGithubArgs; covers install, installations, connect (+flags). Unit tests for all paths including error cases. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Export openBrowser from auth.ts - Add github-cmd.ts: runGithubCommand dispatching install/installations/connect - Pure helpers: parseGithubRepoFullName, formatInstallations - Unit tests for both pure helpers (13 tests) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Extract `describeConnectError` pure helper with substring matching against the
actual API error strings ('project already connected', 'installation is not
active', 'repository not accessible by installation'). Replaces the broken
`code === 'already_connected'` exact-match that never fired, which caused
already-connected repos to show the wrong "not active" message.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Register `github` in the CLI dispatcher (non-interactive dispatch + interactive top-level list/switch) and add the HELP_TREE entry with install/installations/connect subcommands. Extend cli-help.test.ts to assert all three subcommands appear in help. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…stallation id, non-TTY --branch) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- In the `selected` repo-resolution branch, auto-pick when exactly one repo is accessible (mirrors the installation-resolution single-item pattern); only error with "Multiple repositories" when count > 1 and non-TTY. - Expand the non-TTY no-project error to mention VOID_PROJECT and `void project link` as additional resolution sources. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 44b43b467a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…fallback) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2eaba5f374
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
openBrowser built shell command strings with the URL interpolated (start ""/open/xdg-open/wslview). A login or install URL is server- or git-derived, so a crafted URL could inject shell commands. Pass the URL as a single argv element via execFile — no shell, no parsing. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…nect The 'all' repository_selection path inferred the repo from git (GITHUB_REPOSITORY / origin) when --repo was omitted. In CI, a fork, or a wrong-cwd checkout that silently binds the project to the wrong repo. Fail closed and require --repo, matching the --branch rule, the 'selected' multi-repo path, and the documented contract. The git default is now only the interactive prompt placeholder. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 89365bb. Configure here.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 89365bb380
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
openBrowser switched from exec to execFile in 7d8fc59, but the node:child_process mock still only exported `exec`, so runLogin's import of execFile failed under vitest. Export execFile from the mock. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The connect docs contradicted themselves: the --repo row said it was required only for all-repo installs, while the CI paragraph said all flags were always required in non-TTY. Neither matched the code, which auto-picks a sole installation / sole selected repo (server-authoritative and server-validated) and requires a flag only when ambiguous, for all-repo, or for --branch. Document the actual fail-closed-on-ambiguity behavior. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Advances the framework submodule 94b0d4e → 7cec391, picking up the new `void github` command (install + connect a repo for Deploy-on-GitHub without the dashboard). Framework-only change: CLI + tests + docs, no dependency or dashboard-runtime changes. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…nic 504s) (#229) * feat(dispatch): add withTimeout helper for bounding request-path awaits Adds a pure, unit-tested timeout utility (DispatchTimeoutError + withTimeout) that races an awaited promise against a timer, clears the timer as soon as the promise settles, propagates the original rejection on a fast reject, and treats non-positive/non-finite ms as an escape hatch. Not yet wired into index.ts (Task 2). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(dispatch): bound static-asset reads and primary sub-dispatch with timeouts Wrap the two named request-path hot paths with `withTimeout` so a stalled KV/R2 read or user-worker fetch returns a fast metered response instead of hanging the request: - static-asset reads (main static branch + hashed-asset static branch): on timeout return `meter(timeoutResponse(...))` — 404 when the not-found asset read stalled (keeps crawler junk off the 5xx alert), else 503. - primary user-worker sub-dispatch (`worker.fetch(dispatchReq)`): on `DispatchTimeoutError` return a metered 503 (checked before the existing 'Worker exceeded' 429 mapping). Budget from `DISPATCH_TIMEOUT_MS` env (default 10000ms); `withTimeout`'s escape hatch degrades a 0/invalid override to no-timer. Fast path is an unchanged pass-through. ISR/SSR render, hashed-asset worker fetch, applyWorkerFallbackRules inner fetch, and background revalidate are left unwrapped (follow-ups). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(dispatch): honor DISPATCH_TIMEOUT_MS=0 escape hatch The old `Number(env.DISPATCH_TIMEOUT_MS ?? '10000') || 10000` collapsed every disabling value ('0', '', negatives, NaN, Infinity) back to the 10s default via the `|| 10000` short-circuit, so the documented env kill-switch never reached withTimeout's escape hatch. Add a pure resolveTimeoutMs helper that only applies the fallback when the value is unset and passes explicit values through unchanged, restoring the no-redeploy disable path. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(dispatch): meter dispatch timeouts with correct request type A timed-out user-worker dispatch was metered with type='static' because requestType was only reclassified to 'api'/'ssr' after a successful worker.fetch; on timeout the catch ran first with the default still 'static'. Extract classification into a pure classifyDynamicRequestType helper (with hasExtension) and apply it in the DispatchTimeoutError catch branch as well as the success path, so timed-out /api and SSR dispatches are metered with the right type. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

What
Adds a
void githubCLI command so users can install the Void GitHub App and connect a repo to a project for Deploy on GitHub — entirely from the CLI, without the dashboard.Why
The Deploy-on-GitHub connect flow was API-only and intended to be driven by the user-facing dashboard, which isn't deployed. This fills that gap with first-class CLI commands.
connectresolution (interactive, with flag overrides for CI)!process.stdin.isTTY; missing required values fail closed naming the flag.connectrequires--branch(deploy config is persistent — never silently bind to the current checkout branch).masterrepos correctly), not a hardcodedmain.Tests
77 unit tests across arg parsing, the API client methods, the pure helpers (
parseGithubRepoFullName,formatInstallations,describeConnectError), and help wiring.vp run check,vp fmt --check,vp lintall clean.Notes
--projectflag were all fixed).docs/reference/cli.mdupdated.🤖 Generated with Claude Code
Note
Medium Risk
Connect persists repo/branch deploy config and drives automatic deploys on push; mistakes in CI flags could bind the wrong repo, though non-interactive guards reduce that risk.
Overview
Adds a
void githubcommand group so users can wire Deploy on GitHub from the terminal:installfetches the App install URL and opens it,installationslists accounts/repos with installation IDs, andconnectbinds a repo + branch (and optional--executor) to a Void project via newPlatformClientGitHub endpoints.connectresolves project (same precedence as deploy), installation, repo, and branch with TTY prompts or explicit flags; in non-interactive mode it fails closed—--branchis always required,--installationwhen multiple installs exist, and--repowhen the install isn’t a single selected repo (no git-remote inference in CI for “all repos” installs). API errors are mapped to clearer messages.openBrowser(login + install) now usesexecFileinstead of shellexec, passing the URL as a single argument to avoid shell metacharacter issues.CLI help, global help, and
docs/reference/cli.mddocument the flow; unit tests cover arg parsing, client calls, helpers, and help routing.Reviewed by Cursor Bugbot for commit 880d06f. Bugbot is set up for automated code reviews on this repo. Configure here.