A purely client-side static web application that renders
RefactorFirst reports by fetching
.refactorfirst/refactor-first.json data directly from repositories. No server-side code and no database — the site
is a Next.js static export (bun run build produces out/, which any static host can serve).
- Search over a curated listing of repositories (
repositories.txt) - Reports rendered with Mustache.js from raw platform content, with
main→masterbranch fallback — the same report the RefactorFirst report viewer produces: class/package maps (vizdom WASM SVGs with pan/zoom, plus Sigma 2D and 3D force-graph popups), relationship-removal priority tables, Chart.js disharmony bubble charts and class cycle summaries - Repository submission via a pre-filled issue on the hosting platform (no login, apps or tokens on this site): your platform account is captured as the issue author and validated server-side by the platform's CI
- Reports and submissions work for repositories hosted on the same platform as the deployment (GitHub, GitLab or Bitbucket)
- Accessible: the site and rendered reports target WCAG 2.2 AA, with automated checks for selected requirements such as semantic HTML5, color contrast, per-page titles, table semantics, labeled controls and landmarks
- Works with a plain static file server:
python3 -m http.server 8000
- Project Layout
- Deploying to GitHub Pages (organization or personal account)
- Deploying to GitHub Enterprise Server
- Deploying to Bitbucket
- Deploying to GitLab
- How repository submission works
- Making Changes (Developer Guide)
- Testing
app/ # Next.js App Router (static export): layout
# (CSP, header/footer), static pages, 404 +
# pipeline error surfaces, and the dynamic
# routes /{user}, /{user}/{repo},
# /{user}/{repo}/{branch}
components/ # Client components (search combobox, hero/menu
# search, menu toggle, workflow sample, user
# listing, report view, submission form,
# platform-config context, sentry provider)
lib/ # Shared logic consumed by client components and
# RSC alike (routes, fetcher, renderer, search,
# repo-submission, error-handler, rate-limiter,
# cache-manager, utils, host, report-view,
# static-params, widget-loader)
public/ # Published verbatim: repositories.txt, assets/
# (mustache template, logo), templates/
# (workflow samples), widgets/ (WASM + ESM
# bridges for vizdom, three-spritetext, Sentry)
repositories.txt # Listed repositories, one "user/repo" per line
ci/process-submissions.sh # Shared submission validator used by GitHub
# Actions, GitLab CI and Bitbucket Pipelines
templates/ # User CI samples: user-refactorfirst-workflow.yml,
# user-refactorfirst-gitlab-ci.yml,
# user-refactorfirst-bitbucket-pipeline.yml
.gitlab-ci.yml # Deploys the static export (out/) to GitLab Pages
bitbucket-pipelines.yml # Builds out/ on Bitbucket Pipelines
tests/ # unit/ (Bun), integration/ (Bun + RTL), e2e/ (Playwright
# against the built out/ via scripts/serve-out.py)
.github/workflows/ # test.yml, static.yml, redeploy.yml,
# add-repository.yml, deploy-repositories-fast.yml
- Personal account: create a repository named
<username>.github.io. - Organization: create a repository named
<orgname>.github.ioin the org, or any project repository if you want a project page (https://<org>.github.io/<repo>/).
This project is a Next.js app that builds a static export (out/). Push this directory to the default branch — the
included
.github/workflows/static.yml workflow builds the export (bun run build)
and deploys out/ to GitHub Pages:
git init
git add .
git commit -m "RefactorFirst Pages site"
git remote add origin https://github.com/<owner>/<repo>.git
git push -u origin mainSet NEXT_PUBLIC_BASE_PATH=/<repo> for a project page (https://<org>.github.io/<repo>/).
Go to Settings → Pages:
- Source: GitHub Actions (required —
static.ymland the scheduled redeployment in.github/workflows/redeploy.ymlboth build the Next.js static export and uploadout/).
The included redeploy.yml workflow redeploys every 10 minutes, but only when
repositories.txt changed in the last 15 minutes. The add-repository.yml workflow reacts to newly opened submission
issues, validates the submitter and commits new entries to repositories.txt.
"Add Your Repo" submissions are pre-filled issues created in the listing repository. Point the site at your repository
via the meta tag in
app/layout.jsx:
<meta name="submission-target" content="<owner>/<repo>">No GitHub Apps, OAuth apps, client IDs or secrets are needed — identity is captured by GitHub as the issue author. See How repository submission works.
Add a CNAME file containing your domain (e.g. reports.example.com), configure your DNS (CNAME record pointing to
<owner>.github.io), and enable Enforce HTTPS
in Settings → Pages.
The application is fully static, so it works on any GitHub Enterprise Server (GHES) instance with Pages enabled.
A site admin must enable GitHub Pages for the instance (Management Console → Pages → Enable), then create the
repository (<owner>.<pages-host> or a project repo) and push this project as described above.
Raw content and API calls default to github.com / raw.githubusercontent.com
/ api.github.com. For a self-hosted instance, update the URL builders:
lib/fetcher.js— thegithubentry ofPLATFORM_BUILDERSshould build URLs likehttps://github.example.com/raw/<user>/<repo>/<branch>/.refactorfirst/refactor-first.json.lib/repo-submission.js—repositoryInfoUrl()andbuildSubmissionIssueUrl()github branches must target your instance (https://github.example.com/...).ci/process-submissions.sh— setGH_API(and raw URL handling) to your instance endpoints (GH_HOSTis respected bygh-style tooling).app/layout.jsx— extend the CSPconnect-srcdirective with your instance host and set thesubmission-targetmeta to your listing repository.
(Tip: keep these behind a single config module such as enterprise-config.json
if you need to support multiple deployments from one codebase.)
add-repository.yml and redeploy.yml use the built-in GITHUB_TOKEN;
ci/process-submissions.sh needs only curl and jq (preinstalled on Actions runners). If your instance lacks
internet access, ensure raw/API endpoints are reachable from the browser — reports and submission pre-checks are
client-side, so end users' browsers (not the server) must be able to reach your GHES host.
A Bitbucket deployment lists Bitbucket-hosted repositories: report fetching and submission use
bitbucket.org/.../raw/... and the Bitbucket REST API.
- Personal account: create a repository named
<username>.bitbucket.io. - Workspace/team: static sites are per-workspace:
<workspace>.bitbucket.io.
git init
git add .
git commit -m "RefactorFirst Pages site"
git remote add origin git@bitbucket.org:<workspace>/<workspace>.bitbucket.io.git
git push -u origin mainRun NEXT_PUBLIC_HOSTING_ENVIRONMENT=bitbucket bun run build locally or let the included bitbucket-pipelines.yml run
it in CI, then publish the generated
out/ directory to https://<workspace>.bitbucket.io (Bitbucket serves the uploaded static tree; deep links to
/{user}/{repo} paths rely on the exported _404/404.html fallback semantics — where unavailable, share the
two-segment URLs from search results which are pre-generated).
The site is detected as bitbucket from the <workspace>.bitbucket.io
hostname (or via NEXT_PUBLIC_HOSTING_ENVIRONMENT=bitbucket at build time); set submission-target in app/layout.jsx
to
<workspace>/<workspace>.bitbucket.io, enable the issue tracker on that repository and extend the CSP connect-src
with https://api.bitbucket.org
and https://bitbucket.org.
Bitbucket has no issue-triggered pipelines, so submissions are processed by the custom process-submissions pipeline in
bitbucket-pipelines.yml:
- In the repository go to Pipelines → Schedules and schedule
custom: process-submissions(e.g. every 10 minutes). - Create a workspace OAuth consumer with
issues:writeandrepositories:writescopes and store its credentials as the secured repository variablesBITBUCKET_CLIENT_ID/BITBUCKET_CLIENT_SECRET(server-side CI secrets only — the site itself never sees them).
The pipeline polls open issues titled Add repository: owner/repo, checks the author has write/admin permission on
the repository, verifies the report file exists, commits repositories.txt and closes the issue with the outcome. The
manual sort-repos pipeline from the shipped bitbucket-pipelines.yml
also normalizes the listing on demand.
Users generating reports on Bitbucket: point them at
templates/user-refactorfirst-bitbucket-pipeline.yml— a copy-paste pipeline that runsmvn refactorfirst:jsonReportand commits.refactorfirst/refactor-first.jsonon every push tomain/master.
- Personal account: create a project named
<username>.gitlab.io. - Group: create a project named
<groupname>.gitlab.io, or any project for a project page athttps://<group>.gitlab.io/<project>/.
This repository ships a ready-to-use .gitlab-ci.yml: the pages job runs
bun run build (the Next.js static export) with
NEXT_PUBLIC_HOSTING_ENVIRONMENT=gitlab and publishes out/ as the Pages public/ directory:
pages:
stage: build
script:
- bun install
- NEXT_PUBLIC_HOSTING_ENVIRONMENT=gitlab bun run build
- mkdir -p public && cp -r out/. public/
artifacts:
paths:
- public
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCHgit init
git add .
git commit -m "RefactorFirst Pages site"
git remote add origin https://gitlab.com/<group>/<project>.git
git push -u origin mainGitLab Pages deploys from the pages job and serves
https://<group>.gitlab.io/<project>/.
- Client-side routing: GitLab Pages serves
404.htmlfor unknown paths; the Next.js export already producesout/404.html(fromapp/not-found.jsx), which handles branch deep-link recovery — no extra copy step needed. - Submission processing: set
submission-targetinapp/layout.jsxto your<group>/<project>, extend the CSPconnect-srcwith your GitLab base (https://gitlab.comor your self-managed host), and create a pipeline schedule (CI/CD → Schedules, e.g. every 10 minutes) — GitLab has no issue-triggered pipelines, so theprocess-submissionsjob in.gitlab-ci.ymlpolls open submission issues. For self-managed GitLab also add<meta name="platform-base-url" content="https://your-gitlab.example.com">. The job usesCI_JOB_TOKENby default; if your GitLab version/instance restricts its API scope, set a maskedGITLAB_TOKENCI variable with a project access token (apiscope) instead. - Listing redeploys: schedule another pipeline (or extend the same one) to re-run
pageswhenrepositories.txtchanged. - Custom domains: set up under Settings → Pages with automatic Let's Encrypt certificates. Note: the
hostname-based environment detection only recognises
*.gitlab.io; on a custom domain setNEXT_PUBLIC_HOSTING_ENVIRONMENT=gitlabat build time so the app identifies as GitLab.
Users generating reports on GitLab: point them at
templates/user-refactorfirst-gitlab-ci.yml— a copy-paste pipeline that runsmvn refactorfirst:jsonReporton the default branch and commits.refactorfirst/refactor-first.jsonback using the built-inCI_JOB_TOKEN.
No OAuth app, client ID, token or secret is involved on the client side — forks need zero auth setup. The flow on every supported platform:
- The user fills in owner and repository on
/add-repo(no login on this site — identity is captured later, by the platform itself). - The app verifies client-side (unauthenticated) that the repository exists and publishes
.refactorfirst/refactor-first.jsonon itsmain, default ormasterbranch, then opens a pre-filled issue (Add repository: owner/repo) in the listing project in a new tab. - The user — now on GitHub/GitLab/Bitbucket, logged in there — creates the issue. The platform-verified issue author is the captured submitter identity; it cannot be spoofed.
- The platform's CI (GitHub Actions
add-repository.yml, GitLab scheduledprocess-submissionspipeline, Bitbucket scheduledprocess-submissionspipeline; all drivingci/process-submissions.sh) validates:- the issue title matches the exact submission format,
- the issue author has write access to the submitted repository (GitHub collaborator permission, GitLab Developer+
membership, Bitbucket
write/adminpermission), - the report file exists and the repository is not already listed.
- Valid submissions are committed to
repositories.txtand the issue receives a comment with the outcome and is closed; rejected submissions are commented with the reason and closed.
| GitHub | GitLab | Bitbucket | |
|---|---|---|---|
| Trigger | instant (issues: opened event) |
scheduled pipeline (10 min) | scheduled pipeline (10 min) |
| CI credentials | built-in GITHUB_TOKEN |
CI_JOB_TOKEN (or GITLAB_TOKEN project token) |
workspace OAuth consumer (secured variables) |
| Access check | collaborator permission |
member access_level >= 30 (Developer) |
permissions write/admin |
Limitations: only public repositories can be submitted (the report checks are unauthenticated), and each deployment serves exactly one platform — the one it is hosted on.
- Bun ≥ 1.0 (install + unit/integration tests)
- Node.js 22 (Next.js build + Playwright E2E)
- Python 3 (serves the static export locally, scripts/serve-out.py)
bun install # install devDependencies (mustache, jsdom, playwright, eslint)bun run dev # Next.js dev server at http://localhost:3000
# or the production-shaped static export:
bun run build && python3 scripts/serve-out.py # then open http://localhost:8003This project follows strict TDD — write the failing test before production code:
- Write a failing test in
tests/unit/(pure module logic) ortests/integration/(DOM + routing flows). - Run
bun test tests/unit tests/integrationand watch it fail. - Write the minimal implementation in
lib/,components/orapp/to make it pass. - Refactor while keeping tests green.
bun test tests/unit tests/integration # unit + integration (jsdom)
bun test --watch tests/unit # watch mode
bun test --coverage tests/unit tests/integrationE2E tests run under Node.js/Playwright against the built static export (the Playwright webServer runs bun run build
then serves out/):
npx playwright install # one-time: download browsers
npx playwright test # full E2E suite (chromium, firefox, webkit)
npx playwright test --ui # interactive mode
bun run test:e2e:basepath # NEXT_PUBLIC_BASE_PATH=/preview leg (chromium)This application targets WCAG 2.2 Level AA. Dedicated guards cover selected requirements:
tests/unit/html5-attributes.test.js, tests/unit/report-template-wcag.test.js, tests/unit/css-a11y.test.js and
tests/unit/page-titles.test.js verify HTML5-valid markup (no obsolete presentational attributes), semantic heading
hierarchy, table captions and scoped headers, chart alternative text, landmark names, color contrast, focus visibility,
target sizes and per-page titles. Extend these tests when you introduce new markup patterns.
npx eslint "lib/**/*.js" "app/**/*.{js,jsx}" "components/**/*" "tests/**/*"The Getting Started page shows only the CI sample matching the hosting environment, detected from the hostname
(*.github.io → GitHub Actions,
*.gitlab.io → GitLab CI, *.bitbucket.io → Bitbucket Pipelines; anything else defaults to GitHub). The samples live
in
public/templates/workflow-sample-{github,gitlab,bitbucket}.html, and detection lives in
lib/host.js (or NEXT_PUBLIC_HOSTING_ENVIRONMENT at build time, plus the
platform meta tag).
| Change | Files |
|---|---|
| URL routes | lib/routes.js (+ tests/unit/routes.test.js) |
| Raw fetching / branch fallback (platform-aware) | lib/fetcher.js |
| Mustache rendering | lib/renderer.js, public/assets/refactor-first-report.mustache (port of the RefactorFirst viewer template) |
| Interactive report widgets | lib/report-view.js + CDN widgets loaded via next/script in components/report-view.jsx (Chart.js, sigma/graphology, graphlib-dot, svg-pan-zoom, 3d-force-graph) and the ES-module bridges in public/widgets/ (vizdom WASM, three-spritetext) |
| Search / type-ahead | lib/search.js + components/{search-combobox,hero-search,menu-search}.jsx |
| Submission flow | lib/repo-submission.js, components/repo-submission-form.jsx |
| Submission validation (CI) | ci/process-submissions.sh, .github/workflows/add-repository.yml, .gitlab-ci.yml, bitbucket-pipelines.yml |
| Error pages | lib/error-handler.js, components/error-boundary-view.jsx, app/error.jsx, app/not-found.jsx |
| Page content | app/*/page.jsx |
| Styling | css/main.css, css/components.css |
| Listing data | repositories.txt (one user/repo per line) |
| Scheduled redeploy | .github/workflows/redeploy.yml |
.github/workflows/test.yml runs Bun unit/integration tests and the Playwright suite on every push and pull request.
Keep it green before merging.
- Unit (
tests/unit/): router, fetcher (incl. branch fallback, retry and per-platform URL construction), renderer, report-view (charts/graphs/popups), search, repo-submission (incl. report-file existence check and per-platform issue URLs), error-handler, rate-limiter, cache-manager, utils. - Integration (
tests/integration/): search flow, submission flow (missing report, unknown repo, per-platform issue redirect), report rendering. - E2E (
tests/e2e/): user journeys (incl. the submission → pre-filled issue hand-off), cross-browser smoke tests, mobile responsiveness (hamburger menu, single-column grid).
Coverage target: 80%+ on core modules. Current suite: 165 tests.