Deploy Dungeon Company to GitHub Pages: build-only Vite subpath base, asset path fixes, Actions workflow, README
The PR delivers a conditional Vite base, a one-line `index.html` path fix, a Pages workflow that matches the spec, and the README section, with no gameplay changes. The `isPreview` addition is a well-reasoned deviation that is needed to meet the preview criterion. The diff is small, clean and free of stray files. The only caveat is that no CI ran and the workflow is untested on GitHub, so verification depends on the builder's local checks.
Acceptance criteria · 7 of 7 met
- YES`npm run build` succeeds and `dist/index.html` references script and style assets under `/dungeon-company/``vite.config.ts` sets `base` to `'/dungeon-company/'` when `command === 'build'`, and the builder reports `dist/index.html` loads its JS and CSS from `/dungeon-company/assets/...`.
- YESNo `/`-prefixed local asset path literals in `src/`, `index.html` or CSS`index.html` changes `/src/main.ts` to `./src/main.ts`, and the builder's audit reports no Phaser `load.*` calls, CSS `url()` or `/assets` literals.
- YES`npm run preview` serves at `/dungeon-company/`; the page and a JS asset return 200The `isPreview` branch in `vite.config.ts` gives preview the subpath base, and the builder reports curl 200 for the page, the JS bundle and the CSS file; there are no game asset files.
- YES`npm run dev` starts with base `/`The config falls through to `'/'` for `serve` when not previewing, and the builder confirmed dev runs at `localhost:5173/`.
- YESWorkflow YAML parses and includes `workflow_dispatch`, deploy `needs: build`, lockfile install and no secrets`.github/workflows/deploy-pages.yml` has `workflow_dispatch`, `needs: build`, `npm ci` and no secrets, plus the required permissions, concurrency and action versions.
- YESExisting test, typecheck and lint scripts are unmodified and still passThe diff does not touch `package.json`, and the builder reports 41 tests and typecheck passing with no lint script present.
- YESREADME section with the URL, dev, build/preview, triggers and the Pages source settingThe `README.md` "Play online / Deployment" section covers all five items and also corrects the outdated relative-paths sentence.
- No CI ran on this commit. Build, preview, dev, test and typecheck results rest on the builder's sandbox report and could not be checked independently here.
- The workflow has not run on GitHub yet. Its first real test is the first push to `main`, and it will fail until a maintainer sets Settings → Pages → Source to GitHub Actions.
- `vite.config.ts` departs from the spec's example by adding `|| isPreview`. The change is justified, since `vite preview` loads the config with `command: 'serve'` and the preview acceptance criterion needs the `/dungeon-company/` base. It does rely on Vite's `isPreview` field in `ConfigEnv`, which the builder reports type-checks.
- The Node version is pinned to `20.19` from the `engines` minimum. That follows the spec, but Node 20 is near or past end of life, and the maintainer may want to raise it to 22.
CI details
No CI checks ran on this commit.
Accepted by the backers and merged by the maintainer.
Ballots · 1
Automated review cost $0.05, counted as builder cost.
Motivation
Make Dungeon Company playable at https://nabledhq.github.io/dungeon-company/ with no local setup. The existing Vite + Phaser app is built for the repository subpath and deployed with GitHub Actions. Game mechanics and UI must not change.
Scope
- Vite base path (confirmed by proposer)
- Production builds use
base: '/dungeon-company/'. - Dev uses
/. - Implement this conditionally in the Vite config, e.g.
defineConfig(({ command }) => ({ base: command === 'build' ? '/dungeon-company/' : '/', ... })). - Keep all other existing config options intact.
- Production builds use
- Asset path audit
- Find root-relative asset URLs, e.g.
'/assets/...', in:- Phaser
load.*calls; index.html;- CSS.
- Phaser
- Make each one base-relative using one of:
- Vite imports;
import.meta.env.BASE_URL;- relative paths.
- Change only paths, not logic.
- If no such paths exist, note that in the PR description.
- Find root-relative asset URLs, e.g.
- Workflow
.github/workflows/deploy-pages.yml- Triggers:
pushto the repository's default branch (assumedmain) andworkflow_dispatch. - Permissions:
contents: read,pages: write,id-token: write. concurrency: grouppages,cancel-in-progress: false.- Build job steps:
actions/checkout@v4actions/setup-node@v4with the Node version from.nvmrcorenginesif present, else 20, plus npm cache.- The lockfile-matching install (
npm ciforpackage-lock.json). npm run buildactions/configure-pages@v5actions/upload-pages-artifact@v3with pathdist.
- Deploy job:
needs: build, environmentgithub-pageswith url${{ steps.deployment.outputs.page_url }}, usesactions/deploy-pages@v4. - No secrets.
- Triggers:
- README section "Play online / Deployment" covering:
- the public URL;
npm run dev;npm run buildandnpm run preview;- the deployment triggers: push to main and manual run;
- the one-time maintainer setting: Settings → Pages → Source: GitHub Actions.
Acceptance criteria
npm run buildsucceeds, anddist/index.htmlreferences its script and style assets under/dungeon-company/.- Grepping
src/,index.htmland CSS finds no string literal asset path starting with/assetsor any other/-prefixed local asset path. External URLs are allowed. npm run previewserves the build at/dungeon-company/.curlof/dungeon-company/returns 200.- At least one referenced JS asset and, if any exist, one game asset also return 200.
npm run devstarts, and the resolved dev base is/.- The workflow YAML parses and meets all of the following:
- it includes
workflow_dispatch; - the deploy job has
needs: build; - it uses the lockfile-based install;
- it adds no secrets.
- it includes
- Existing test, typecheck and lint scripts are unmodified and still pass where present.
- The README section exists with all items listed above.
Out of scope
- Custom domain, CDN, analytics, backend, auth or cloud saves.
- Gameplay or UI changes.
- gh-pages branch deployment.
- Enabling Pages in repository settings (a maintainer action).
Model claude-opus-5-5 · ceiling $5 · started 1 hour ago · finished 1 hour ago
2026-10-09T09:56:03Z Build job #28 started for feature #26 on Claude Managed Agents using claude-opus-5-5 at high effort (spend cap $5.00)
2026-10-09T09:56:06Z Managed Agents session sesn_01DsEnua2uEZHDUkgTf8NNMr (agent agent_016XFr3GNwJwne19m6LhhwTU v3, spend cap $5.00)
2026-10-09T09:56:06Z Watch it live: https://platform.claude.com/workspaces/default/sessions/sesn_01DsEnua2uEZHDUkgTf8NNMr
2026-10-09T09:56:12Z [bash] cd /workspace/repo && git status && git log --oneline | head -5 && ls -a && cat package.json && ls .github 2>/dev/null; cat vite.config.* 2>/dev/null; cat .nvmrc 2>/dev/null; ls public src 2>/dev/null | head -50
2026-10-09T09:56:14Z [bash] cd /workspace/repo && git checkout -b nabled/feature-26-job-28 && cat index.html README.md && grep -rnE "['\"\`(]/[a-zA-Z]" src index.html | grep -v "//" | head -30; grep -rn "load\.\|url(" src | head; ls node_modules 2>/dev/null | head -2; node -v
2026-10-09T09:56:22Z [bash] cd /workspace/repo && grep -rnE "\.load\b|url\(|/assets|\.png|\.jpg|\.svg|\.mp3|\.json'" src index.html; ls tests; npm ci 2>&1 | tail -3
2026-10-09T09:56:43Z [claude] Now the changes.
2026-10-09T09:56:43Z [bash] cd /workspace/repo && python3 - <<'EOF' p='vite.config.ts' s=open(p).read() s=s.replace("""export default defineConfig({ // Relative asset paths so the built game can be served from any sub-path. base: './', ""","""export default defineConfig(({ command }) => ({ // Production builds are served...
2026-10-09T09:56:48Z [bash] cd /workspace/repo && npm run build 2>&1 | tail -8 && cat dist/index.html && (npx vite preview --port 4173 >/tmp/prev.log 2>&1 &) ; sleep 3; cat /tmp/prev.log; curl -s -o /dev/null -w "%{http_code}\n" http://localhost:4173/dungeon-company/
2026-10-09T09:57:02Z [claude] As suspected, `vite preview` resolves config with `command: 'serve'`, so it served at `/`. Adding `isPreview` so preview matches the build.
2026-10-09T09:57:02Z [bash] cd /workspace/repo && pkill -f "vite preview"; python3 - <<'EOF' p='vite.config.ts' s=open(p).read() s=s.replace("""export default defineConfig(({ command }) => ({ // Production builds are served from GitHub Pages at /dungeon-company/; dev uses /. base: command === 'build' ? '/dungeon-company/' ...
2026-10-09T09:57:18Z [claude] Now the workflow and README.
2026-10-09T09:57:18Z [write] /workspace/repo/.github/workflows/deploy-pages.yml
2026-10-09T09:57:18Z [edit] /workspace/repo/README.md
2026-10-09T09:57:26Z [bash] cd /workspace/repo && npm test 2>&1 | tail -5 && npm run typecheck 2>&1 | tail -2 && python3 -c "import yaml;d=yaml.safe_load(open('.github/workflows/deploy-pages.yml'));print(d[True], d['jobs']['deploy']['needs'])" && grep -rnE "['\"\`(]/(assets|src|[a-zA-Z0-9_-]+\.(png|jpg|svg|css|js|ts|json|mp3|o...
2026-10-09T09:57:33Z [bash] cd /workspace/repo && git config user.name >/dev/null || git config user.name "nabled builder"; git config user.email >/dev/null || git config user.email builder@nabled.dev; git add -A && git commit -qm "Deploy to GitHub Pages: /dungeon-company/ build base, Pages workflow, README section" && git pus...
2026-10-09T09:58:06Z [submit] success=true
## Deploy Dungeon Company to GitHub Pages
The game builds for `https://nabledhq.github.io/dungeon-company/` and a GitHub Actions workflow deploys it. Game mechanics and UI are unchanged.
### Changes
- **`vite.config.ts`**: the config is now a function. It sets `base: command === 'build' || isPreview ? '/dungeon-company/' : '/'` and replaces the old `base: './'`. All other options (chunk size limit, separate Phaser chunk, Vitest settings) are unchanged.
- **Differs from the spec's example:** I added `|| isPreview` on purpose. `vite preview` loads the config with `command: 'serve'`, so the spec's version (checking only `command`) served the preview at `/` instead of `/dungeon-company/`. I saw this happen before making the change. The dev server still uses `/`.
- **`index.html`**: changed `<script src="/src/main.ts">` to `./src/main.ts`. This was the only root-relative local path. It's a source entry that Vite rewrites, not a game asset, but changing it means a grep finds no `/`-prefixed local paths. It works the same in dev and build.
- **Asset audit:** there are no Phaser `load.*` calls, no CSS `url(...)`, and no `/assets/...` literals anywhere in `src/`, `index.html` or CSS. Everything is drawn in code. So nothing else needed changing, and there are no game asset files to serve.
- **`.github/workflows/deploy-pages.yml`** (new):
- Runs on push to `main` and on manual `workflow_dispatch`.
- Permissions `contents: read`, `pages: write`, `id-token: write`; concurrency group `pages` with `cancel-in-progress: false`.
- Build job: checkout@v4 → setup-node@v4 (Node `20.19`, from `engines: >=20.19`, with npm cache) → `npm ci` → `npm run build` → configure-pages@v5 → upload-pages-artifact@v3 (`dist`).
- Deploy job: `needs: build`, environment `github-pages` with the `page_url` output, deploy-pages@v4.
- Uses no secrets.
- **`README.md`**: new "Play online / Deployment" section covering:
- the public URL;
- `npm run dev`;
- `npm run build` / `npm run preview`;
- the triggers (push to `main` and manual run);
- the one-time setting Settings → Pages → Source: GitHub Actions.
I also changed the old sentence saying `dist/` uses relative paths, since that's no longer true.
### How it was checked (locally, in the sandbox)
- `npm run build` passes. `dist/index.html` loads its JS, modulepreload and CSS files from `/dungeon-company/assets/...`.
- `vite preview` starts at `http://localhost:4173/dungeon-company/`. `curl` got 200 for `/dungeon-company/`, the main JS bundle and the CSS file.
- `vite` (dev) starts at `http://localhost:5173/`, so the dev base is `/`. `/` and `/src/main.ts` return 200.
- `npm test`: 41 tests pass. `npm run typecheck` passes. The project has no lint script. No `package.json` scripts were changed.
- The workflow YAML loads cleanly with PyYAML and includes `workflow_dispatch` and `deploy.needs: build`.
- The workflow has not been run on GitHub, so the first real test is the first push to `main` after this merges.
### For the maintainer
- **Before merging:** set Settings → Pages → Source to **GitHub Actions**, or the deploy job will fail.
- **Node version:** the workflow pins Node `20.19` to match the `engines` minimum. Node 20 is past end-of-life, so you may want to raise it to 22.
2026-10-09T09:58:16Z [claude] The GitHub Pages deployment is built and pushed to `nabled/feature-26-job-28`. Once it's live the game will be at `https://nabledhq.github.io/dungeon-company/`. Locally the build, preview, dev server, tests and typecheck all pass. The workflow itself hasn't run on GitHub yet; its first real run will be the first push to `main` after merging.
**Before merging:** go to Settings → Pages and set Source to **GitHub Actions**. Until that's done, the deploy job fails.
What changed:
- **`vite.config.ts`:** builds use `/dungeon-company/` and the dev server uses `/`. One difference from your example: I also apply `/dungeon-company/` when running `npm run preview`. Preview loads the config as if it were the dev server, so with only your check it served the build at `/` instead of `/dungeon-company/`. I saw that happen before adding the fix.
- **Asset paths:** there were no root-relative asset paths to fix. The game draws everything in code, so there are no Phaser `load.*` calls, no CSS `url(...)` and no image or sound files. The only `/`-prefixed path was the script tag in `index.html` (`/src/main.ts`), which I changed to `./src/main.ts` so nothing starting with `/` is left. It works the same in dev and build.
- **`.github/workflows/deploy-pages.yml`:** runs on push to `main` and manually. It installs with `npm ci`, builds, uploads `dist` and deploys, with the permissions and concurrency from the spec and no secrets.
- **README:** new "Play online / Deployment" section with the public URL, the dev, build and preview commands, the deploy triggers and the Pages setting. I also corrected an older sentence that said `dist/` uses relative paths, which is no longer true.
**Local checks:**
- The built `dist/index.html` loads its scripts and styles from `/dungeon-company/assets/`.
- Preview at `/dungeon-company/` returned 200 for the page, the main JS file and the CSS file.
- The dev server runs at `/`.
- All 41 tests and the typecheck pass. There is no lint script, and no `package.json` scripts were changed.
- The workflow file parses as valid YAML.
The workflow uses Node 20.19, the minimum in `package.json`. Node 20 is past end-of-life, so you may want to raise it to 22.
2026-10-09T09:58:18Z Changed files:
added .github/workflows/deploy-pages.yml +47 -0
modified README.md +18 -3
modified index.html +1 -1
modified vite.config.ts +5 -4
2026-10-09T09:58:20Z Opened pull request https://github.com/nabledhq/dungeon-company/pull/2
2026-10-09T09:58:20Z Finished: success=true turns=11 tokens(in/out)=164376/6094 list cost=$0.25
Show patch
diff --git a/.github/workflows/deploy-pages.yml b/.github/workflows/deploy-pages.yml
new file mode 100644
index 0000000..f94eac8
--- /dev/null
+++ b/.github/workflows/deploy-pages.yml
@@ -0,0 +1,47 @@
+name: Deploy to GitHub Pages
+
+on:
+ push:
+ branches: [main]
+ workflow_dispatch:
+
+permissions:
+ contents: read
+ pages: write
+ id-token: write
+
+concurrency:
+ group: pages
+ cancel-in-progress: false
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+
+ - uses: actions/setup-node@v4
+ with:
+ # package.json "engines" requires Node >= 20.19.
+ node-version: '20.19'
+ cache: npm
+
+ - run: npm ci
+
+ - run: npm run build
+
+ - uses: actions/configure-pages@v5
+
+ - uses: actions/upload-pages-artifact@v3
+ with:
+ path: dist
+
+ deploy:
+ needs: build
+ runs-on: ubuntu-latest
+ environment:
+ name: github-pages
+ url: ${{ steps.deployment.outputs.page_url }}
+ steps:
+ - id: deployment
+ uses: actions/deploy-pages@v4
diff --git a/README.md b/README.md
index da8a10f..96a3da8 100644
--- a/README.md
+++ b/README.md
@@ -14,9 +14,24 @@ npm run build # type-check, then write static files to dist/
npm run preview # serve dist/ locally
```
-`dist/` is plain static HTML/JS/CSS with relative paths, so it can be hosted from any
-static web server or sub-path. Browsers block ES modules on `file://` URLs, so serve
-the folder (for example with `npm run preview`) instead of double-clicking `index.html`.
+`dist/` is plain static HTML/JS/CSS built for the `/dungeon-company/` sub-path (see
+below). Browsers block ES modules on `file://` URLs, so serve the folder (for example
+with `npm run preview`) instead of double-clicking `index.html`.
+
+## Play online / Deployment
+
+Play in the browser, no setup needed: **https://nabledhq.github.io/dungeon-company/**
+
+* **Local development**: `npm run dev` starts the dev server at `/` (for example
+ `http://localhost:5173/`).
+* **Production build**: `npm run build` writes `dist/` with all asset URLs under
+ `/dungeon-company/`, the GitHub Pages sub-path. `npm run preview` serves that build
+ at `http://localhost:4173/dungeon-company/`.
+* **Deployment**: the workflow `.github/workflows/deploy-pages.yml` builds the game and
+ deploys `dist/` to GitHub Pages on every push to `main`. You can also run it by hand
+ from the Actions tab ("Deploy to GitHub Pages" → Run workflow).
+* **One-time maintainer setup**: in the repository, open **Settings → Pages** and set
+ **Source** to **GitHub Actions**. Until then the deploy job fails.
## How to play
diff --git a/index.html b/index.html
index e6b6e35..c614d3f 100644
--- a/index.html
+++ b/index.html
@@ -10,6 +10,6 @@
<div id="game"></div>
<div id="hud"></div>
</div>
- <script type="module" src="/src/main.ts"></script>
+ <script type="module" src="./src/main.ts"></script>
</body>
</html>
diff --git a/vite.config.ts b/vite.config.ts
index 639a4a9..ccdd318 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -1,8 +1,9 @@
import { defineConfig } from 'vitest/config';
-export default defineConfig({
- // Relative asset paths so the built game can be served from any sub-path.
- base: './',
+export default defineConfig(({ command, isPreview }) => ({
+ // Production builds (and `vite preview` of them) are served from GitHub Pages at
+ // /dungeon-company/; the dev server uses /.
+ base: command === 'build' || isPreview ? '/dungeon-company/' : '/',
build: {
// Phaser alone is ~1.2 MB minified; keep it in its own cached chunk.
chunkSizeWarningLimit: 1500,
@@ -18,4 +19,4 @@ export default defineConfig({
environment: 'node',
include: ['tests/**/*.test.ts'],
},
-});
+}));
No comments yet.