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
42 changes: 42 additions & 0 deletions .cursor/rules/demos-pages.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
---
description: OpenAPI demo publishing — token safety, Pages assemble, CI trust
globs: demos/**,.github/workflows/pages*.yml
alwaysApply: false
---

# Demos and GitHub Pages

Source of truth: `demos/README.md` and `demos/assemble-pages.sh`. Keep the README inclusion rules identical to the script `if`s.

## Token

- Client-only: OAT in memory only; send only as `x-api-key` to hosts in `config` / OpenAPI `servers`.
- No `sessionStorage` / `localStorage` / cookies / analytics / CDN scripts that can read the token.
- `fetch` that carries the OAT: `redirect: 'error'`.
- Do not official-host a backend that accepts someone else's OAT.

## Assemble script (`demos/assemble-pages.sh`)

- Guard `rm -rf -- "$out"`: canonicalize `out` (`abspath` + `normpath`), then refuse empty, `.`, `..`, `/`, leading `-`, repo root, `demos/`, or an `out` that is an ancestor of the repo.
- Demo folder names: `A–Z a–z 0–9 . _ -` only; skip the rest (they go into HTML).
- Publish when there is no `vercel.json` and either:
- no `package.json` + root `index.html` → copy the **whole folder**;
- `package.json` + `package-lock.json` (or `npm-shrinkwrap.json`) → `rm -rf dist && npm ci --no-audit --no-fund && npm run build`, publish `dist/` (must contain `dist/index.html`). Fail fast if the lockfile is missing.
- Vite: `base: './'`. Do not commit `node_modules/` or `dist/`.

## Workflow

- PR: assemble only, never `deploy-pages`.
- Deploy only from `master` (push or `workflow_dispatch` on that ref).
- Do not run `npm` / assemble on **fork** PRs (`head.repo.full_name != github.repository`). Same-repo PRs may assemble.
- Generic `upload-artifact` of `_site` must set `include-hidden-files: true` so `.nojekyll` reaches Pages.
- One PR should not both invent a new CI trust path and change publish layout. Split those.

## Before opening a PR

```bash
bash demos/assemble-pages.sh _site
npx --yes shellcheck demos/assemble-pages.sh
```

Then re-read `demos/README.md` against the script. Serve `_site` locally if the change is user-visible.
43 changes: 34 additions & 9 deletions .github/workflows/pages-demos.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,52 @@ on:
paths:
- 'demos/**'
- '.github/workflows/pages-demos.yml'
pull_request:
paths:
- 'demos/**'
- '.github/workflows/pages-demos.yml'
workflow_dispatch:

permissions:
contents: read
pages: write
id-token: write

concurrency:
group: pages
group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref }}
cancel-in-progress: true

jobs:
assemble:
# Fork PRs can run arbitrary npm scripts; only build same-repo PRs and master.
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
runs-on: ubuntu-latest
permissions:
contents: read
steps:
Comment thread
zjn0505 marked this conversation as resolved.
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20.x
- name: Assemble site
run: bash demos/assemble-pages.sh _site
- uses: actions/upload-artifact@v4
with:
name: site
path: _site
include-hidden-files: true

deploy:
if: github.event_name != 'pull_request' && github.ref == 'refs/heads/master'
needs: assemble
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
permissions:
contents: read
pages: write
id-token: write
steps:
- uses: actions/checkout@v4
- name: Assemble site
run: bash demos/assemble-pages.sh _site
- uses: actions/download-artifact@v4
with:
name: site
path: _site
- uses: actions/configure-pages@v5
- uses: actions/upload-pages-artifact@v3
with:
Expand Down
11 changes: 8 additions & 3 deletions demos/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,10 @@ Each demo should document how the caller supplies their own token.

## Catalog

Official static host: [https://robotemi.github.io/openapi/](https://robotemi.github.io/openapi/) (index of every client-only demo). A folder is published when it has `index.html` and no `vercel.json`.
Official static host: [https://robotemi.github.io/openapi/](https://robotemi.github.io/openapi/). A folder is published when it has no `vercel.json` and either:

- **No build:** root `index.html` and no `package.json` — the **whole folder** is copied (HTML, JS, README, assets).
- **npm build:** `package.json` plus `package-lock.json` (or `npm-shrinkwrap.json`). CI runs `rm -rf dist && npm ci --no-audit --no-fund && npm run build` and publishes only `dist/` (must contain `dist/index.html`). A source `index.html` is only needed if the bundler uses it (Vite does).

| Demo | Mode | Deploy |
|------|------|--------|
Expand All @@ -34,7 +37,9 @@ Official static host: [https://robotemi.github.io/openapi/](https://robotemi.git
1. Add a self-contained directory under `demos/`.
2. Include a short `README.md` with purpose, setup, and how the OAT is provided.
3. Keep dependencies and scope small enough that someone else can run the demo without a private environment.
4. Client-only static demos: add `index.html` at the folder root, omit `vercel.json`, and use a URL-safe folder name (`A–Z a–z 0–9 . _ -`). Merge to `master` and the Pages workflow copies the folder to `https://robotemi.github.io/openapi/<name>/`.
5. Serverless demos: do not add `index.html` as a Pages app (or include `vercel.json`). Deploy as a separate Vercel project.
4. Client-only static demos (no build): add `index.html` at the folder root, omit `package.json` and `vercel.json`, and use a URL-safe folder name (`A–Z a–z 0–9 . _ -`). Merge to `master` and the Pages workflow copies the **entire folder** to `https://robotemi.github.io/openapi/<name>/`.
5. Client-only demos that use Vite/TypeScript: commit `package.json` **and** a lockfile (`package-lock.json` or `npm-shrinkwrap.json`), with `npm run build` writing `dist/index.html`. Set Vite `base` to `./`. Omit `vercel.json`. Do not commit `node_modules/` or `dist/`. Pages runs `rm -rf dist && npm ci --no-audit --no-fund && npm run build` and publishes `dist/`.
6. Serverless demos: include `vercel.json`. Deploy as a separate Vercel project.
7. Pull-request assemble (build, no publish) runs only for branches on `robotemi/openapi`. Fork PRs are not built automatically; a maintainer can check out the branch and run `bash demos/assemble-pages.sh _site` locally.

Questions about the API itself belong in the [API reference](https://openapi-docs.robotemi.com).
31 changes: 24 additions & 7 deletions demos/assemble-pages.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#!/usr/bin/env bash
# Build the GitHub Pages tree: index + each static demo as a subfolder.
# A demo is published when it has index.html and no vercel.json.
# A demo is published when it has no vercel.json and either:
# - package.json + lockfile → rm -rf dist && npm ci --no-audit --no-fund && npm run build, publish dist/
# - root index.html (no package.json) → copy the whole folder
set -euo pipefail

demos="$(cd "$(dirname "$0")" && pwd)"
Expand All @@ -17,10 +19,7 @@ case "$out" in
exit 1
;;
esac
if [ "${out#/}" = "$out" ]; then
out="$(pwd)/$out"
fi
out="${out%/}"
out="$(python3 -c 'import os, sys; print(os.path.normpath(os.path.abspath(os.path.expanduser(sys.argv[1]))))' "$out")"
if [ "$out" = "$repo" ] || [ "$out" = "$demos" ]; then
echo "assemble-pages: refusing to delete the repo or demos directory" >&2
exit 1
Expand Down Expand Up @@ -59,7 +58,6 @@ touch -- "$out/.nojekyll"
EOF
for dir in "$demos"/*/; do
[ -d "$dir" ] || continue
[ -f "${dir}index.html" ] || continue
[ -f "${dir}vercel.json" ] && continue
name="$(basename "$dir")"
case "$name" in
Expand All @@ -68,7 +66,26 @@ EOF
continue
;;
esac
cp -R -- "$dir" "$out/$name"
if [ -f "${dir}package.json" ]; then
if [ ! -f "${dir}package-lock.json" ] && [ ! -f "${dir}npm-shrinkwrap.json" ]; then
echo "assemble-pages: $name has package.json but no package-lock.json (or npm-shrinkwrap.json); npm ci requires a lockfile" >&2
exit 1
fi
echo "assemble-pages: building $name"
(cd "$dir" && rm -rf dist && npm ci --no-audit --no-fund && npm run build)
if [ ! -f "${dir}dist/index.html" ]; then
echo "assemble-pages: $name build produced no dist/index.html" >&2
exit 1
fi
mkdir -p -- "$out/$name"
cp -R -- "${dir}dist/." "$out/$name/"
touch -- "$out/$name/.nojekyll"
elif [ -f "${dir}index.html" ]; then
cp -R -- "$dir" "$out/$name"
else
echo "assemble-pages: skipping $name (need index.html, or package.json + lockfile)" >&2
continue
fi
printf ' <li><a href="./%s/">%s</a></li>\n' "$name" "$name"
done
cat <<'EOF'
Expand Down
Loading