docs: add seeking-co-maintainer banner (refs #5) #102
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: deploy | |
| # Build and publish the static web client to GitHub Pages, stamping | |
| # the commit SHA into the version badge as part of the build so every | |
| # deployed page carries a clickable link to the exact commit it came | |
| # from. | |
| # | |
| # REQUIRED ONE-TIME REPO SETTING: Settings → Pages → Build and | |
| # deployment → Source must be set to "GitHub Actions". With the | |
| # legacy "Deploy from a branch" source selected, the auto-managed | |
| # pages-build-deployment workflow keeps running instead of this one | |
| # and no SHA stamping happens. | |
| on: | |
| push: | |
| branches: [master] | |
| workflow_dispatch: | |
| # Pages deployment permissions. `contents: write` is needed so the | |
| # workflow can push a per-deploy tag back to origin (see the "Tag | |
| # this deploy" step below); without it the GITHUB_TOKEN only has | |
| # read access and the push would be rejected. | |
| permissions: | |
| contents: write | |
| pages: write | |
| id-token: write | |
| # Only one concurrent Pages deploy at a time, but do not cancel in | |
| # flight — if two pushes land in quick succession we want both to | |
| # deploy sequentially, not race. | |
| concurrency: | |
| group: "pages" | |
| cancel-in-progress: false | |
| jobs: | |
| deploy: | |
| environment: | |
| name: github-pages | |
| url: ${{ steps.deployment.outputs.page_url }} | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v5 | |
| - name: JS module syntax check | |
| # No build step means a JavaScript syntax error ships straight to | |
| # users. A duplicate top-level declaration is a SyntaxError that | |
| # aborts the WHOLE module (this bit us in v0.7.0–v0.9.0: app.js | |
| # silently failed to load). Parse every module AS an ES module via | |
| # `node --check` (.mjs) so module-scope errors — duplicate top-level | |
| # let/const/function/class, bad import/export syntax — fail the | |
| # deploy before anything goes live. `--check` only parses; it does | |
| # not resolve imports, so bare specifiers and relative paths are fine. | |
| run: | | |
| node --version | |
| fail=0 | |
| for f in js/*.js lib/*.js; do | |
| tmp="$(mktemp --suffix=.mjs)" | |
| cp "$f" "$tmp" | |
| if err="$(node --check "$tmp" 2>&1)"; then | |
| echo "ok $f" | |
| else | |
| echo "FAIL $f"; echo "$err"; fail=1 | |
| fi | |
| rm -f "$tmp" | |
| done | |
| if [ "$fail" -ne 0 ]; then | |
| echo "::error::JavaScript module syntax check failed — not deploying" | |
| exit 1 | |
| fi | |
| - name: Sync version badge from package.json | |
| # Single source of truth for the semantic version is package.json. | |
| # This step rewrites the two "v?.?.?" tokens that immediately | |
| # precede the <!-- BUILD_SHA --> markers in index.html to match. | |
| # Means future version bumps only need to touch package.json. | |
| run: | | |
| python - <<'PY' | |
| import json, re | |
| version = json.load(open('package.json'))['version'] | |
| print(f"Syncing version badge to v{version}") | |
| with open('index.html', 'r', encoding='utf-8') as f: | |
| html = f.read() | |
| # Rewrite any >v1.2.3<!-- BUILD_SHA --> → >v{version}<!-- BUILD_SHA --> | |
| new_html, n = re.subn( | |
| r'>v\d+\.\d+\.\d+(<!-- BUILD_SHA -->)', | |
| lambda m: f'>v{version}{m.group(1)}', | |
| html | |
| ) | |
| if n == 0: | |
| print('WARNING: no version badge tokens found in index.html') | |
| else: | |
| print(f'Rewrote {n} version badge(s)') | |
| with open('index.html', 'w', encoding='utf-8') as f: | |
| f.write(new_html) | |
| PY | |
| - name: Inject short commit SHA into version badge | |
| # Replaces the <!-- BUILD_SHA --> comment placeholder in the | |
| # version badge with a clickable link to the commit. Locally | |
| # the comment is invisible so the badge shows just the | |
| # semantic version; deployed builds gain " · abc1234" that | |
| # opens the commit on GitHub. | |
| env: | |
| SHA_FULL: ${{ github.sha }} | |
| REPO: ${{ github.repository }} | |
| run: | | |
| python - <<'PY' | |
| import os | |
| sha_full = os.environ['SHA_FULL'] | |
| repo = os.environ['REPO'] | |
| sha_short = sha_full[:7] | |
| sha_url = f"https://github.com/{repo}/commit/{sha_full}" | |
| print(f"Stamping {sha_short} -> {sha_url}") | |
| with open('index.html', 'r', encoding='utf-8') as f: | |
| html = f.read() | |
| replacement = f' <a class="version-sha" href="{sha_url}" target="_blank" rel="noopener" title="Commit this build was deployed from">{sha_short}</a>' | |
| # Replace every BUILD_SHA marker in the file so all version | |
| # labels (sidebar logo + About card) are stamped, not just | |
| # the first one. | |
| new_html = html.replace('<!-- BUILD_SHA -->', replacement) | |
| if new_html == html: | |
| print('WARNING: <!-- BUILD_SHA --> marker not found in index.html; badge not stamped') | |
| with open('index.html', 'w', encoding='utf-8') as f: | |
| f.write(new_html) | |
| PY | |
| - name: Stage static site | |
| # Copy only the files the browser actually needs. Leaves tools/, | |
| # docs/, .github/, package.json, node_modules/, etc. out of the | |
| # Pages artifact so the public deploy is not bloated with Python | |
| # scripts and CI config. The test/ self-test harness IS published | |
| # (it's a static, dependency-free page) so module regressions can | |
| # be checked in a real browser straight from the live deploy. | |
| run: | | |
| mkdir -p _site | |
| cp index.html _site/ | |
| cp flasher.html _site/ | |
| cp privacy.html _site/ | |
| cp hubs.json _site/ | |
| cp -r css js lib test _site/ | |
| # .nojekyll disables Jekyll processing so GitHub Pages serves | |
| # files with underscores and other Jekyll-hostile names. | |
| touch _site/.nojekyll | |
| - name: Mirror bridge binaries to Pages (stable download URLs) | |
| # GitHub release assets are served from a shared host behind | |
| # rotating SAS-token URLs, so the download URL changes every time | |
| # — which means a SmartScreen "report as safe" can't attach to a | |
| # fixed URL, and per-URL reputation never accrues. Mirror the | |
| # latest bridge release into the Pages site under /bridge/ at a | |
| # stable, version-independent path so the in-app download (and the | |
| # URL users report to SmartScreen) never changes. | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| mkdir -p _site/bridge | |
| TAG=$(gh release list --repo "$GITHUB_REPOSITORY" --limit 200 --json tagName,createdAt \ | |
| -q 'map(select(.tagName|startswith("bridge-v"))) | sort_by(.createdAt) | last | .tagName') | |
| if [ -z "$TAG" ] || [ "$TAG" = "null" ]; then | |
| echo "::warning::no bridge-v* release found; /bridge/ download links will 404" | |
| else | |
| echo "Mirroring bridge release $TAG" | |
| gh release download "$TAG" --repo "$GITHUB_REPOSITORY" --dir _site/bridge \ | |
| --pattern 'ws_bridge-*' --pattern 'SHA256SUMS.txt' | |
| # Version-independent aliases so the in-app download URL is fixed | |
| # across bridge releases (the versioned files stay too). | |
| cd _site/bridge | |
| for f in ws_bridge-*-windows-amd64.exe; do cp -f "$f" ws_bridge-windows-amd64.exe; done | |
| for f in ws_bridge-*-linux-amd64; do cp -f "$f" ws_bridge-linux-amd64; done | |
| for f in ws_bridge-*-darwin-arm64; do cp -f "$f" ws_bridge-darwin-arm64; done | |
| ls -la | |
| fi | |
| - uses: actions/configure-pages@v6 | |
| - uses: actions/upload-pages-artifact@v5 | |
| with: | |
| path: _site | |
| - id: deployment | |
| uses: actions/deploy-pages@v5 | |
| - name: Tag this deploy | |
| # Runs after a successful Pages publish, so the tag only | |
| # exists for builds that actually went live. Tag format is | |
| # `v{package.json version}-{short SHA}` — semver-friendly, | |
| # unique per commit, and matches the short SHA users see in | |
| # the version badge, so a bug report carrying that hex | |
| # string resolves to a real git ref immediately. | |
| # | |
| # Manually re-running the workflow on the same commit | |
| # (workflow_dispatch) re-uses the same tag, so the push is | |
| # skipped if the tag already exists on origin. | |
| # | |
| # Auth: actions/checkout@v4 has already configured git with | |
| # a credential helper backed by the workflow's GITHUB_TOKEN, | |
| # so `git push` just works as long as contents: write is | |
| # declared in permissions above. | |
| run: | | |
| VERSION=$(node -p "require('./package.json').version") | |
| SHORT_SHA=$(git rev-parse --short=7 HEAD) | |
| TAG="v${VERSION}-${SHORT_SHA}" | |
| echo "Proposed tag: ${TAG}" | |
| if git ls-remote --tags origin "refs/tags/${TAG}" | grep -q "${TAG}"; then | |
| echo "Tag ${TAG} already exists on origin, skipping" | |
| exit 0 | |
| fi | |
| git config user.name "github-actions[bot]" | |
| git config user.email "41898282+github-actions[bot]@users.noreply.github.com" | |
| git tag -a "${TAG}" -m "Auto-tag: deployed $(date -u +%Y-%m-%dT%H:%M:%SZ) to GitHub Pages" | |
| git push origin "${TAG}" | |
| echo "Pushed ${TAG}" |