Skip to content

feat(installer): use GitHub API as primary fetch with raw CDN fallback#2248

Merged
alexeyv merged 5 commits intomainfrom
feat/github-api-fallback
Apr 18, 2026
Merged

feat(installer): use GitHub API as primary fetch with raw CDN fallback#2248
alexeyv merged 5 commits intomainfrom
feat/github-api-fallback

Conversation

@alexeyv
Copy link
Copy Markdown
Collaborator

@alexeyv alexeyv commented Apr 11, 2026

Summary

  • Adds fetchGitHubFile() to RegistryClient that tries the GitHub Contents API (api.github.com) first, falling back to raw.githubusercontent.com on failure
  • Updates ExternalModuleManager and CommunityModuleManager to use structured owner/repo/path params instead of hardcoded raw URLs
  • Corporate proxies commonly block raw.githubusercontent.com while allowing api.github.com — this makes the installer work on more networks
  • No authentication required — GitHub API allows 60 unauthenticated requests/hour for public repos, more than sufficient for installer use (3-4 files per install). If rate-limited, falls back to raw CDN transparently.

Test plan

  • 15 new unit tests for cascade logic (API success, fallback, both-fail, URL construction, YAML/JSON parsing)
  • All 258 existing tests pass
  • npm run quality passes
  • Manual: verify bmad install works with raw.githubusercontent.com blocked at proxy/hosts level

🤖 Generated with Claude Code

@augmentcode
Copy link
Copy Markdown

augmentcode bot commented Apr 11, 2026

🤖 Augment PR Summary

Summary: This PR makes the installer fetch marketplace registry files via the GitHub Contents API first, with a fallback to raw GitHub URLs when the API path fails.

Changes:

  • Added RegistryClient.fetchGitHubFile() plus YAML/JSON helpers to implement API-first + raw-CDN fallback behavior
  • Updated ExternalModuleManager and CommunityModuleManager to fetch marketplace YAML via owner/repo/path/ref parameters instead of hardcoded raw URLs
  • Added a dedicated registry-client unit test script and wired it into npm test and npm run quality

Rationale: Some corporate networks block raw.githubusercontent.com while still allowing api.github.com, so the installer should work in more environments.

Testing: Adds new unit tests for cascade logic and parsing; CI now runs them via test:registry.

🤖 Was this summary useful? React with 👍 or 👎

Copy link
Copy Markdown

@augmentcode augmentcode bot left a comment

Choose a reason for hiding this comment

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

Review completed. 3 suggestions posted.

Fix All in Augment

Comment augment review to trigger a new review at any time.

Comment thread tools/installer/modules/registry-client.js
Comment thread tools/installer/modules/registry-client.js
Comment thread test/test-registry-client.js Outdated
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 11, 2026

📝 Walkthrough

Walkthrough

This change introduces GitHub-specific fetching methods to the RegistryClient class with fallback capability from GitHub API to raw CDN content. The installer modules are updated to use these new methods instead of hardcoded URLs. A new test script validates the fallback behavior and parsing functionality.

Changes

Cohort / File(s) Summary
Test Infrastructure
package.json, test/test-registry-client.js
Added test:registry script that executes a new test file (test-registry-client.js). Updated quality and test scripts to include the registry test. New test file contains 213 lines validating GitHub API fallback behavior, URL construction, and YAML/JSON parsing.
Registry Client Core
tools/installer/modules/registry-client.js
Added four new methods: fetchGitHubFile() (with fallback from GitHub API to raw.githubusercontent.com), fetchGitHubYaml(), fetchGitHubJson(), and private helper _fetchWithHeaders() for custom header HTTP requests with timeout support.
Registry Client Usage
tools/installer/modules/community-manager.js, tools/installer/modules/external-manager.js
Replaced hardcoded GitHub URLs with calls to new fetchGitHubYaml() method using explicit owner/repo/path/ref parameters. Removed REGISTRY_RAW_URL constant. Fetch logic now delegates to RegistryClient instead of inline URL construction.

Sequence Diagram(s)

sequenceDiagram
    participant Caller as Installer Module
    participant RC as RegistryClient
    participant GitAPI as GitHub API
    participant RawCDN as raw.githubusercontent.com
    
    Caller->>RC: fetchGitHubYaml(owner, repo, path, ref)
    RC->>RC: fetchGitHubFile(...)
    RC->>GitAPI: GET /repos/owner/repo/contents/path (with Accept header)
    alt API Success
        GitAPI-->>RC: 200 + content
        RC->>RC: Parse YAML
        RC-->>Caller: Parsed YAML object
    else API Failure
        GitAPI-->>RC: Error/Non-200
        RC->>RawCDN: GET owner/repo/ref/path
        alt CDN Success
            RawCDN-->>RC: 200 + content
            RC->>RC: Parse YAML
            RC-->>Caller: Parsed YAML object
        else CDN Failure
            RawCDN-->>RC: Error
            RC-->>Caller: Throw error
        end
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 30.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The PR title accurately describes the main change: implementing GitHub API as primary fetch method with raw CDN fallback for the installer.
Description check ✅ Passed The PR description accurately describes the changeset: adding GitHub API fallback to RegistryClient and updating managers to use structured parameters instead of hardcoded URLs to improve network compatibility.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/github-api-fallback

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 and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
tools/installer/modules/external-manager.js (1)

35-35: Consider centralizing marketplace repo coordinates.

This hardcoded owner/repo/ref now duplicates tools/installer/modules/community-manager.js constants, which can drift over time. A shared constant module would keep both managers aligned.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tools/installer/modules/external-manager.js` at line 35, The call in
external-manager.js that hardcodes the marketplace repo coordinates (the
fetchGitHubYaml invocation with 'bmad-code-org', 'bmad-plugins-marketplace',
'registry/official.yaml', 'main') duplicates constants used in
community-manager.js; extract these owner/repo/ref values into a shared module
(e.g., export MARKETPLACE_OWNER, MARKETPLACE_REPO, MARKETPLACE_REF or a single
MARKETPLACE_COORDS object) and replace the literal strings in
external-manager.js (and update community-manager.js to import the same symbols)
so both managers consume the centralized constants.
test/test-registry-client.js (1)

54-58: Add an assertion for API headers in the stubbed call.

The tests validate URL construction, but not the expected Accept media type passed to _fetchWithHeaders(). Capturing/asserting headers would harden regression detection.

Also applies to: 126-135

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/test-registry-client.js` around lines 54 - 58, Update the stub for
_fetchWithHeaders to accept and record the headers argument (e.g., push a tuple
or an object like {url, headers} into calls) and add assertions in the tests
that the captured headers include the correct Accept media type; specifically
modify the stubbed client._fetchWithHeaders and the tests around those stubs
(also the other occurrence mentioned) to assert headers['Accept'] === the
expected media type string used by the Registry client under test so the Accept
header is validated alongside the URL.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tools/installer/modules/registry-client.js`:
- Around line 85-91: When falling back from this._fetchWithHeaders(apiUrl, ...)
to this.fetch(rawUrl, ...), preserve the original API error context so failures
show both causes: catch the error thrown by _fetchWithHeaders (capture it as
apiErr), then if the raw CDN call also fails capture that error (rawErr) and
throw a new Error that includes both apiErr and rawErr messages (or attach them
as properties) so callers can see the API failure plus the CDN failure; update
the try/catch around _fetchWithHeaders and the subsequent this.fetch(rawUrl,
...) to rethrow a combined error when both attempts fail and ensure the thrown
error contains apiUrl and rawUrl and the original error objects for debugging.
- Around line 147-149: The _fetchWithHeaders function currently follows Location
redirects unboundedly; add a redirect cap by introducing a maxRedirects
parameter (or redirectsLeft) with a sensible default (e.g., 5–10) to
_fetchWithHeaders and decrement it on each recursive call when res.statusCode is
3xx and res.headers.location; if the redirect limit is exceeded, reject/throw a
descriptive error (including the URL and status) instead of recursing, and
ensure callers that invoke _fetchWithHeaders propagate that rejection so the
tool exits with a non-zero error code.

---

Nitpick comments:
In `@test/test-registry-client.js`:
- Around line 54-58: Update the stub for _fetchWithHeaders to accept and record
the headers argument (e.g., push a tuple or an object like {url, headers} into
calls) and add assertions in the tests that the captured headers include the
correct Accept media type; specifically modify the stubbed
client._fetchWithHeaders and the tests around those stubs (also the other
occurrence mentioned) to assert headers['Accept'] === the expected media type
string used by the Registry client under test so the Accept header is validated
alongside the URL.

In `@tools/installer/modules/external-manager.js`:
- Line 35: The call in external-manager.js that hardcodes the marketplace repo
coordinates (the fetchGitHubYaml invocation with 'bmad-code-org',
'bmad-plugins-marketplace', 'registry/official.yaml', 'main') duplicates
constants used in community-manager.js; extract these owner/repo/ref values into
a shared module (e.g., export MARKETPLACE_OWNER, MARKETPLACE_REPO,
MARKETPLACE_REF or a single MARKETPLACE_COORDS object) and replace the literal
strings in external-manager.js (and update community-manager.js to import the
same symbols) so both managers consume the centralized constants.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 862fd9ee-7a6f-4aeb-920b-9305f05fbfaf

📥 Commits

Reviewing files that changed from the base of the PR and between eabcd03 and ab2ea6f.

📒 Files selected for processing (5)
  • package.json
  • test/test-registry-client.js
  • tools/installer/modules/community-manager.js
  • tools/installer/modules/external-manager.js
  • tools/installer/modules/registry-client.js

Comment thread tools/installer/modules/registry-client.js Outdated
Comment thread tools/installer/modules/registry-client.js
@alexeyv
Copy link
Copy Markdown
Collaborator Author

alexeyv commented Apr 13, 2026

Triage Summary

5 findings from Augment + CodeRabbit — FIX: 3, DISMISS: 2, DEFER: 0

# Severity Finding Decision
F1 LOW URL-encode dynamic segments in fetchGitHubFile DISMISS — all callers pass hardcoded string constants
F2 MEDIUM Unbounded redirect recursion in _fetchWithHeaders FIX — added maxRedirects param (default 3) to fetch() and _fetchWithHeaders()
F3 MEDIUM Preserve API error context when both fallbacks fail FIX — wrapped CDN fallback in try/catch, throws AggregateError with both errors
F4 LOW Test stub ignores headers DISMISS — Accept header is a hardcoded literal; asserting on it over-couples tests to implementation
F5 LOW Centralize marketplace repo coordinates FIX — extracted MARKETPLACE_OWNER/MARKETPLACE_REPO/MARKETPLACE_REF constants in external-manager.js

Fixes in commit 745a402.

alexeyv and others added 3 commits April 14, 2026 00:58
Corporate proxies commonly block raw.githubusercontent.com while allowing
api.github.com. Add fetchGitHubFile() to RegistryClient that tries the
GitHub Contents API first, falling back to the raw CDN transparently.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add maxRedirects parameter to fetch() and _fetchWithHeaders() to prevent
unbounded redirect recursion. Wrap CDN fallback in try/catch and throw
AggregateError with both API and CDN errors for better diagnostics.
Extract marketplace repo coordinates into named constants in
external-manager.
Neither method has any callers. Also drop the corresponding test.
@alexeyv alexeyv force-pushed the feat/github-api-fallback branch from 745a402 to 104dd9f Compare April 18, 2026 15:37
alexeyv added 2 commits April 18, 2026 08:46
No reason for RegistryClient tests to be a separate runner — the same
file already tests the registry consumers in Suite 33. Drop test:registry
from package.json scripts and quality gate.
… errors

Non-2xx responses previously yielded bare `HTTP 403`. Now surface the
request URL, GitHub's JSON error message (or body snippet), X-RateLimit-Reset
when quota is exhausted, and Retry-After. Turns a mystery 403 into
'rate limit exhausted; resets at 2026-04-15T18:00:00Z' — the difference
between 'try GITHUB_TOKEN' and a wild goose chase.
@alexeyv alexeyv merged commit d09363b into main Apr 18, 2026
5 checks passed
@alexeyv alexeyv deleted the feat/github-api-fallback branch April 18, 2026 15:53
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