Skip to content

feat(cli): void github — install + connect a repo for Deploy-on-GitHub - #229

Merged
Brooooooklyn merged 13 commits into
mainfrom
feat/cli-github-connect
Jun 23, 2026
Merged

Brooooooklyn merged 13 commits into
mainfrom
feat/cli-github-connect

Conversation

@Brooooooklyn

@Brooooooklyn Brooooooklyn commented Jun 22, 2026 •

Copy link
Copy Markdown
Member

What

Adds a void github CLI 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.

void github install         print + open the GitHub App install URL
void github installations    list your installations (account, repos, status, id)
void github connect [project]  connect a repo → project for auto-deploy

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.

connect resolution (interactive, with flag overrides for CI)

project    [project] arg / --project / $VOID_PROJECT / linked .void/project.json / TTY prompt
install    --installation <id> / single active / TTY select   (non-TTY: required when >1)
repo       --repo owner/repo / selected-list / 'all'+git-remote default
branch     --branch / TTY prompt prefilled with current git branch
executor   --executor container|github_actions  (default: container)
  • CI-safe: no prompts when !process.stdin.isTTY; missing required values fail closed naming the flag.
  • Non-interactive connect requires --branch (deploy config is persistent — never silently bind to the current checkout branch).
  • Branch default in interactive mode is the actual git branch (handles master repos correctly), not a hardcoded main.
  • Friendly mapping of the API's 409/400/403/404 responses.

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 lint all clean.

Notes

  • Framework-only change; no new dependencies.
  • Built and reviewed via per-task spec/quality reviews, a whole-branch review, and a Codex adversarial review (findings on empty-flag fallthrough, installation-id discoverability, non-TTY branch binding, and a missing --project flag were all fixed).
  • Docs: docs/reference/cli.md updated.

🤖 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 github command group so users can wire Deploy on GitHub from the terminal: install fetches the App install URL and opens it, installations lists accounts/repos with installation IDs, and connect binds a repo + branch (and optional --executor) to a Void project via new PlatformClient GitHub endpoints.

connect resolves project (same precedence as deploy), installation, repo, and branch with TTY prompts or explicit flags; in non-interactive mode it fails closed—--branch is always required, --installation when multiple installs exist, and --repo when 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 uses execFile instead of shell exec, passing the URL as a single argument to avoid shell metacharacter issues.

CLI help, global help, and docs/reference/cli.md document 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.

Brooooooklyn and others added 7 commits June 22, 2026 21:57
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>
Comment thread packages/void/src/cli/github-cmd.ts
Comment thread packages/void/src/cli/github-cmd.ts
Comment thread packages/void/src/cli/github-cmd.ts
- 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>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread packages/void/src/cli/github-cmd.ts Outdated
Comment thread packages/void/src/cli/github-cmd.ts
…fallback)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread packages/void/src/cli/github-cmd.ts Outdated
Brooooooklyn and others added 2 commits June 22, 2026 23:24
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>

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ 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.

Comment thread packages/void/src/cli/github-cmd.ts

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread packages/void/src/cli/auth.ts
Brooooooklyn and others added 2 commits June 22, 2026 23:49
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>
@Brooooooklyn
Brooooooklyn merged commit 7cec391 into main Jun 23, 2026
7 checks passed
@Brooooooklyn
Brooooooklyn deleted the feat/cli-github-connect branch June 23, 2026 02:44
cpojer pushed a commit that referenced this pull request Sep 11, 2026
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>
cpojer pushed a commit that referenced this pull request Sep 11, 2026
…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>
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.

1 participant