Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 23 additions & 14 deletions .github/workflows/deploy-pages.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
name: Deploy web overlay (lab.usersfirst.games)

# Publishes the production site to the ROOT of the `gh-pages` branch. We use a
# branch-based Pages source (rather than the GitHub-Actions Pages source) so
# the per-PR previews from pr-preview.yml β€” which live under gh-pages
# `pr-preview/pr-<N>/` via rossjrw/pr-preview-action β€” can coexist with prod on
# the same site. `keep_files: true` means a prod deploy never wipes the
# `pr-preview/` umbrella (or vice-versa).
#
# ⚠️ One-time repo setting required: Settings β†’ Pages β†’ "Build and deployment"
# β†’ Source = "Deploy from a branch" β†’ Branch = `gh-pages` / `(root)`.
# The custom domain (lab.usersfirst.games) is preserved via the CNAME file
# that build-web.js writes into _site (and the `cname:` below).
on:
push:
branches: [main]
Expand All @@ -13,23 +24,18 @@ on:
- '.github/workflows/deploy-pages.yml'
workflow_dispatch:

# Required for the GitHub Actions Pages deployment.
# Pushing to the gh-pages branch needs write to repo contents.
permissions:
contents: read
pages: write
id-token: write
contents: write

# One in-flight deploy at a time; don't cancel a running publish.
# Serialize gh-pages writes so a prod deploy and a preview deploy don't race.
concurrency:
group: pages
group: gh-pages-write
cancel-in-progress: false

jobs:
deploy:
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deploy.outputs.page_url }}
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
Expand All @@ -46,9 +52,12 @@ jobs:
node scripts/stamp-build-version.js apps/overlay/src/index.html web/index.html
- name: Assemble _site (landing + /overlay + CNAME)
run: node scripts/build-web.js
- uses: actions/configure-pages@v6
- uses: actions/upload-pages-artifact@v5
- name: Publish to gh-pages root
uses: peaceiris/actions-gh-pages@v4
with:
path: _site
- id: deploy
uses: actions/deploy-pages@v5
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./_site
# Preserve the pr-preview/ umbrella (and anything else) on gh-pages.
keep_files: true
# Re-assert the custom domain on every deploy so it survives keep_files.
cname: lab.usersfirst.games
221 changes: 221 additions & 0 deletions .github/workflows/pr-preview.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
name: PR preview (web + desktop)

# Per-PR preview so a change can be tested fully and in isolation (issue #77),
# mirroring the PR-preview workflow in petegordon/tandemonium but extended with
# the desktop installers:
#
# β€’ Web preview β€” the browser overlay deployed to the gh-pages `pr-preview/`
# umbrella at https://lab.usersfirst.games/pr-preview/pr-<N>/
# (overlay at …/pr-preview/pr-<N>/overlay/). Torn down on close.
# β€’ Desktop β€” Windows .exe + macOS .dmg/zip published as a per-PR
# *prerelease* tagged `pr-<N>`, deleted when the PR closes.
# β€’ One sticky comment links the web URL and the desktop downloads, refreshed
# on every push.
#
# Scope: INTERNAL branches only. Fork PRs get a read-only GITHUB_TOKEN (and no
# secrets), so they can't publish to gh-pages / Releases anyway β€” every job is
# guarded on the head repo being this repo to avoid noisy guaranteed failures.
on:
pull_request:
types: [opened, synchronize, reopened, closed]

# Supersede an in-flight preview when the PR is pushed again; each PR is its own
# group so PRs never cancel each other.
concurrency:
group: pr-preview-${{ github.event.number }}
cancel-in-progress: true

permissions:
contents: write # gh-pages preview branch + per-PR prerelease
pull-requests: write # sticky preview comment

jobs:
# ── Web preview β†’ gh-pages /pr-preview/pr-<N>/ (and teardown on close) ──
web-preview:
if: github.event.pull_request.head.repo.full_name == github.repository
runs-on: ubuntu-latest
# Share the repo-wide gh-pages serialization group with deploy-pages.yml so
# a prod deploy and a preview deploy never push to gh-pages concurrently.
concurrency:
group: gh-pages-write
cancel-in-progress: false
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: 22
# Build steps are skipped on `closed` β€” the deploy action then only runs
# its teardown (removes this PR's preview dir from gh-pages).
- name: Assemble _site for the preview subpath
if: github.event.action != 'closed'
run: |
node apps/overlay/scripts/copy-workspace.js
HASH=$(git rev-parse --short HEAD)
export BUILD_STAMP="PR #${{ github.event.number }} &middot; <a href=\"${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/commit/${GITHUB_SHA}\" target=\"_blank\" rel=\"noopener\">v.${HASH}</a> &middot; $(date -u +'%Y-%m-%d %H:%M UTC')"
node scripts/stamp-build-version.js apps/overlay/src/index.html web/index.html
node scripts/build-web.js
# Scope the landing page's relative URLs to the preview subpath so its
# links (e.g. β†’ /overlay) resolve under /pr-preview/pr-<N>/.
sed -i 's|<head>|<head><base href="/pr-preview/pr-${{ github.event.number }}/">|' _site/index.html
- name: Deploy / teardown PR web preview
uses: rossjrw/pr-preview-action@v1
with:
source-dir: ./_site/
preview-branch: gh-pages
umbrella-dir: pr-preview
action: auto
# We post a single combined sticky comment (web + desktop) ourselves.
comment: false

# ── Desktop installers (skipped on close) ──
build-macos:
if: github.event.action != 'closed' && github.event.pull_request.head.repo.full_name == github.repository
runs-on: macos-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: 22
cache: npm
- run: npm ci
- name: Stamp build version
shell: bash
run: |
HASH=$(git rev-parse --short HEAD)
export BUILD_STAMP="PR #${{ github.event.number }} &middot; <a href=\"${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/commit/${GITHUB_SHA}\" target=\"_blank\" rel=\"noopener\">v.${HASH}</a> &middot; $(date -u +'%Y-%m-%d %H:%M UTC')"
node scripts/stamp-build-version.js apps/overlay/src/index.html
- name: Build macOS (zip + dmg)
working-directory: apps/overlay
shell: bash
run: |
node scripts/copy-three.js
node scripts/copy-workspace.js
npx electron-forge make --arch universal --targets @electron-forge/maker-zip
bash scripts/make-dmg.sh
- uses: actions/upload-artifact@v7
with:
name: macos
if-no-files-found: error
path: |
apps/overlay/out/WebHID-Controller-Overlay.dmg
apps/overlay/out/make/zip/darwin/**/*.zip

build-windows:
if: github.event.action != 'closed' && github.event.pull_request.head.repo.full_name == github.repository
runs-on: windows-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: 22
cache: npm
- run: npm ci
- name: Stamp build version
shell: bash
run: |
HASH=$(git rev-parse --short HEAD)
export BUILD_STAMP="PR #${{ github.event.number }} &middot; <a href=\"${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/commit/${GITHUB_SHA}\" target=\"_blank\" rel=\"noopener\">v.${HASH}</a> &middot; $(date -u +'%Y-%m-%d %H:%M UTC')"
node scripts/stamp-build-version.js apps/overlay/src/index.html
- name: Build Windows (squirrel + zip)
working-directory: apps/overlay
shell: bash
run: |
node scripts/copy-three.js
node scripts/copy-workspace.js
npx electron-forge make --targets @electron-forge/maker-squirrel,@electron-forge/maker-zip
- uses: actions/upload-artifact@v7
with:
name: windows
if-no-files-found: error
path: |
apps/overlay/out/make/squirrel.windows/**/*.exe
apps/overlay/out/make/zip/win32/**/*.zip

# ── Per-PR prerelease so the installers have stable, clickable URLs ──
prerelease:
needs: [build-macos, build-windows]
if: github.event.action != 'closed' && github.event.pull_request.head.repo.full_name == github.repository
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/download-artifact@v8
with:
path: artifacts
- name: Gather release files
run: |
mkdir -p release-assets
find artifacts -type f \( -name '*.dmg' -o -name '*.exe' -o -name '*.zip' \) -exec cp {} release-assets/ \;
ls -lh release-assets
- name: Publish PR prerelease
uses: softprops/action-gh-release@v3
with:
tag_name: pr-${{ github.event.number }}
name: "PR #${{ github.event.number }} preview (${{ github.event.pull_request.head.sha }})"
# Prerelease + NOT latest so it never displaces the rolling
# `webhid-overlay-latest` release the landing page tracks.
prerelease: true
make_latest: false
target_commitish: ${{ github.event.pull_request.head.sha }}
files: release-assets/*
body: |
Automated **preview build** for PR #${{ github.event.number }} β€” for testing in isolation only.

- **Commit:** `${{ github.event.pull_request.head.sha }}`
- **Windows:** `*-Setup.exe` (installer) or `.zip` (portable)
- **macOS:** `WebHID-Controller-Overlay.dmg` or `.zip` (unsigned β€” right-click β†’ Open on first launch)

This prerelease is deleted automatically when the PR closes.

# ── One combined sticky comment (web URL + desktop downloads) ──
comment:
needs: [web-preview, prerelease]
if: always() && github.event.action != 'closed' && github.event.pull_request.head.repo.full_name == github.repository
runs-on: ubuntu-latest
steps:
- name: Post / update preview comment
uses: marocchino/sticky-pull-request-comment@v2
with:
header: pr-preview
message: |
### πŸ”Ž PR #${{ github.event.number }} preview β€” test it in isolation

**🌐 Web overlay:** https://lab.usersfirst.games/pr-preview/pr-${{ github.event.number }}/overlay/
_(landing page: https://lab.usersfirst.games/pr-preview/pr-${{ github.event.number }}/)_

**πŸ’» Desktop installers (prerelease):** https://github.com/${{ github.repository }}/releases/tag/pr-${{ github.event.number }}
- **Windows** β€” `…-Setup.exe` (installer) or `.zip` (portable)
- **macOS** β€” `WebHID-Controller-Overlay.dmg` or `.zip` (unsigned: right-click β†’ Open)

<sub>Updated for ${{ github.event.pull_request.head.sha }}. Web preview and prerelease are torn down when this PR closes.</sub>

# ── Teardown on close: delete the per-PR prerelease + tag, note the comment ──
cleanup:
if: github.event.action == 'closed' && github.event.pull_request.head.repo.full_name == github.repository
runs-on: ubuntu-latest
steps:
- name: Delete PR prerelease and tag
uses: actions/github-script@v7
with:
script: |
const tag = `pr-${context.issue.number}`;
try {
const rel = await github.rest.repos.getReleaseByTag({ ...context.repo, tag });
await github.rest.repos.deleteRelease({ ...context.repo, release_id: rel.data.id });
core.info(`Deleted release ${tag}`);
} catch (e) {
core.info(`No release ${tag} to delete (${e.status})`);
}
try {
await github.rest.git.deleteRef({ ...context.repo, ref: `tags/${tag}` });
core.info(`Deleted tag ${tag}`);
} catch (e) {
core.info(`No tag ${tag} to delete (${e.status})`);
}
- name: Update sticky comment to "torn down"
uses: marocchino/sticky-pull-request-comment@v2
with:
header: pr-preview
message: |
### πŸ”Ž PR #${{ github.event.number }} preview β€” torn down

This PR is closed; its web preview and desktop prerelease have been removed.
11 changes: 11 additions & 0 deletions apps/overlay/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,17 @@ npm --workspace @usersfirst/overlay run make # ZIP + Windows Squirrel
npm --workspace @usersfirst/overlay run make:dmg # macOS DMG (uses hdiutil)
```

## PR previews (test a PR in isolation)

Every internal PR gets a preview built by [`.github/workflows/pr-preview.yml`](../../.github/workflows/pr-preview.yml), with a single sticky comment linking:

- **Web overlay** β€” deployed to `gh-pages` under `https://lab.usersfirst.games/pr-preview/pr-<N>/overlay/`.
- **Desktop installers** β€” Windows `.exe` + macOS `.dmg`/zip, published as a per-PR **prerelease** tagged `pr-<N>`.

Both are torn down automatically when the PR closes. Previews run for branches in this repo only (fork PRs get a read-only token and can't publish).

> **One-time setup:** the site is served from the **`gh-pages` branch** (Settings β†’ Pages β†’ Source = "Deploy from a branch" β†’ `gh-pages` / `(root)`) so prod and the `pr-preview/` umbrella can share one Pages site. The custom domain is preserved via the `CNAME` that `build-web.js` writes.

## How it talks to the packages

| File | Imports from |
Expand Down
Loading