Skip to content

Commit 324ee20

Browse files
committed
cf deploy: publish playground + explorer to Cloudflare Pages
Adds a composed deploy pipeline to imgui-bundle.pages.dev, hosting: - playground_python/ (pyodide playground) - min_pyodide_app/ (minimal pyodide template) - explorer/ (ibex emscripten build) Local flow via new `cf_*` justfile recipes (cf_stage_all, cf_deploy, cf_serve_local, cf_clean). Manually triggered CI workflow mirrors the same flow via cloudflare/wrangler-action. Existing traineq rsync recipes are kept as fallbacks. COOP/COEP headers are scoped to /explorer/* via a _headers file to avoid breaking the playground's CDN fetches.
1 parent 49c4973 commit 324ee20

10 files changed

Lines changed: 295 additions & 6 deletions

File tree

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
name: Cloudflare Pages deploy
2+
3+
on:
4+
workflow_dispatch:
5+
# pull_request:
6+
# release:
7+
# types:
8+
# - published
9+
# push:
10+
# branches:
11+
# - master
12+
# - main
13+
14+
# Refuse concurrent runs
15+
concurrency:
16+
group: cf-pages-deploy
17+
cancel-in-progress: false
18+
19+
jobs:
20+
build_ibex:
21+
name: Build Bundle Explorer Wasm binaries
22+
runs-on: ubuntu-latest
23+
24+
steps:
25+
- uses: actions/checkout@v6
26+
with:
27+
submodules: recursive
28+
29+
# install just
30+
- uses: extractions/setup-just@v4
31+
32+
# install emscripten (and cache it)
33+
- name: Setup emsdk
34+
uses: emscripten-core/setup-emsdk@v15
35+
with:
36+
# Make sure to set a version number!
37+
version: 5.0.4
38+
# This is the name of the cache folder.
39+
# The cache folder will be placed in the build directory,
40+
# so make sure it doesn't conflict with anything!
41+
actions-cache-folder: 'emsdk-cache'
42+
43+
- name: Verify emscripten toolchain
44+
run: emcc -v
45+
46+
- name: Setup interactive tmate session
47+
uses: mxschmitt/action-tmate@v3
48+
49+
- name: Build Pyodide wheel
50+
run: |
51+
just pyodide_setup_local_build
52+
just pyodide_build
53+
54+
- name: Build Bundle Binaries
55+
run: just ibex_build
56+
57+
- name: Stage site
58+
run: just cf_stage_all
59+
60+
- name: Deploy to Cloudflare Pages
61+
uses: cloudflare/wrangler-action@v3
62+
with:
63+
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
64+
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
65+
command: pages deploy pyodide_projects/_cf_staging --project-name=imgui-bundle

.gitignore

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,5 @@ hello_imgui.ini
175175
_backends
176176
emsdk/
177177
.aider*
178-
179-
_mdmath_draft/
178+
.wrangler/
180179

ci_scripts/webserver_multithread_policy.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,14 @@
1616
import argparse
1717

1818

19+
COI_PREFIX = '' # set by main(); empty string = apply site-wide
20+
21+
1922
class CORSRequestHandler (SimpleHTTPRequestHandler):
2023
def end_headers (self):
21-
self.send_header('Cross-Origin-Opener-Policy', 'same-origin')
22-
self.send_header('Cross-Origin-Embedder-Policy', 'require-corp')
24+
if not COI_PREFIX or self.path.startswith(COI_PREFIX):
25+
self.send_header('Cross-Origin-Opener-Policy', 'same-origin')
26+
self.send_header('Cross-Origin-Embedder-Policy', 'require-corp')
2327
SimpleHTTPRequestHandler.end_headers(self)
2428

2529
def do_GET(self):
@@ -49,8 +53,11 @@ def do_GET(self):
4953
parser = argparse.ArgumentParser()
5054
parser.add_argument('-p', '--port', default=8000, type=int)
5155
parser.add_argument('-r', '--root', default='', type=str)
56+
parser.add_argument('--coi-prefix', default='', type=str,
57+
help='Only set COOP/COEP on URLs under this prefix (e.g. /explorer/). Empty = site-wide.')
5258
args = parser.parse_args()
5359
if args.root:
5460
os.chdir(args.root)
61+
globals()['COI_PREFIX'] = args.coi_prefix
5562
print(f"http://localhost:{args.port}/")
5663
test(CORSRequestHandler, HTTPServer, port=args.port)

docs/book/_toc.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,5 +54,6 @@ parts:
5454
- file: devel_docs/bindings_debug
5555
- file: devel_docs/pypi_deploy
5656
- file: devel_docs/Readme_pyodide_bundle
57+
- file: devel_docs/cloudflare_deploy
5758
- file: devel_docs/build_opencv_immvision
5859

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
# Cloudflare Pages deploy
2+
3+
The project publishes three static subparts to a single Cloudflare Pages project
4+
named `imgui-bundle`, reachable at <https://imgui-bundle.pages.dev/>:
5+
6+
| Path | Contents |
7+
|-------------------------------|-------------------------------------------------------------------|
8+
| `/playground_python/` | Pyodide playground (Python examples in the browser) |
9+
| `/min_pyodide_app/` | Minimal Pyodide template (single-file demo) |
10+
| `/explorer/` | Dear ImGui Bundle Explorer (Emscripten build with OpenCV) |
11+
| `/local_wheels/` | Shared `imgui_bundle-*.whl`, loaded by the pyodide subparts |
12+
13+
A tracked landing page at the root (`pyodide_projects/cf_landing.html`) links to
14+
each subpart. A `_headers` file (`pyodide_projects/cf_headers`) scopes
15+
`Cross-Origin-Opener-Policy` / `Cross-Origin-Embedder-Policy` to `/explorer/*`
16+
only; site-wide COI would break the playground (which pulls Pyodide and
17+
CodeMirror from CDNs that don't set `Cross-Origin-Resource-Policy`).
18+
19+
20+
## How the deploy works
21+
22+
Cloudflare Pages replaces the whole site on every upload, so we maintain a
23+
local composed staging directory (`pyodide_projects/_cf_staging/`, gitignored)
24+
that holds the union of all subparts. `just cf_stage_all` refreshes every
25+
subdir; `just cf_deploy` uploads the whole staging tree via `wrangler`.
26+
27+
The trade-off: on every deploy the other subparts must already be present in
28+
staging. If staging gets wiped (`just cf_clean`), re-run `cf_stage_all` before
29+
`cf_deploy`.
30+
31+
32+
## One-time setup
33+
34+
1. **Install wrangler** (needs Node.js 18+):
35+
```bash
36+
npm install -g wrangler
37+
```
38+
39+
2. **Create an API token** at <https://dash.cloudflare.com/profile/api-tokens>
40+
using the **Edit Cloudflare Workers** template (it covers Pages too).
41+
42+
3. **Export credentials** in your shell (or your shell rc):
43+
```bash
44+
export CLOUDFLARE_API_TOKEN=...
45+
export CLOUDFLARE_ACCOUNT_ID=...
46+
```
47+
48+
4. **Verify**: `wrangler whoami` should print the account.
49+
50+
51+
## Local workflow
52+
53+
```bash
54+
# Full refresh of staging, then deploy
55+
just cf_stage_all
56+
just cf_deploy
57+
58+
# Test the composed site locally before deploying
59+
just cf_serve_local # http://localhost:8765/
60+
# → COOP/COEP headers are applied only to /explorer/* (mirrors prod)
61+
62+
# Inspect the staging tree (gitignored)
63+
ls pyodide_projects/_cf_staging/
64+
65+
# Nuke staging (forces a full re-stage next time)
66+
just cf_clean
67+
```
68+
69+
The ibex subpart requires `just ibex_build` first (produces
70+
`build_ibex_ems/bin/`); `cf_stage_all` then copies those binaries into
71+
`_cf_staging/explorer/`.
72+
73+
74+
## CI workflow
75+
76+
`.github/workflows/cf_pages_deploy.yml` mirrors the local flow and is
77+
triggered manually from the Actions tab (`workflow_dispatch`). It runs:
78+
79+
1. `just pyodide_setup_local_build` + `just pyodide_build` (builds the wheel)
80+
2. `just ibex_build` (builds Emscripten binaries; slowest step, typically
81+
30–60 min because `IMMVISION_FETCH_OPENCV=ON` rebuilds OpenCV for wasm)
82+
3. `just cf_stage_all`
83+
4. `cloudflare/wrangler-action@v3` to upload
84+
85+
Required GitHub Actions secrets: `CLOUDFLARE_API_TOKEN`,
86+
`CLOUDFLARE_ACCOUNT_ID` (same values used locally).
87+
88+
89+
## Related
90+
91+
- Local traineq deploy (kept as fallback): `just pyodide_deploy_imgui_bundle_online`, `just ibex_deploy`

docs/book/devel_docs/intro.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,4 +67,5 @@ Terms used throughout the developer docs:
6767
- [Debug Native C++](bindings_debug.md) – debugging C++ code from Python
6868
- [PyPI Deployment](pypi_deploy.md) – releasing to PyPI
6969
- [Pyodide Build Guide](Readme_pyodide_bundle.md) – building for Python-in-the-browser
70+
- [Cloudflare Pages Deploy](cloudflare_deploy.md) – publishing the playground + explorer to imgui-bundle.pages.dev
7071
- [OpenCV Build Guide](build_opencv_immvision.md) – building with OpenCV / ImmVision

justfile

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ libs_bindings_all:
7171
ibex_build:
7272
mkdir -p build_ibex_ems && \
7373
cd build_ibex_ems && \
74-
source ~/emsdk/emsdk_env.sh && \
74+
if [ -f ~/emsdk/emsdk_env.sh ]; then source ~/emsdk/emsdk_env.sh; fi && \
7575
emcmake cmake .. -DCMAKE_BUILD_TYPE=Release -DIMMVISION_FETCH_OPENCV=ON && \
7676
make -j
7777

@@ -108,7 +108,7 @@ ibex_serve:
108108
imex_ems_build:
109109
mkdir -p build_imex_ems && \
110110
cd build_imex_ems && \
111-
source ~/emsdk/emsdk_env.sh && \
111+
if [ -f ~/emsdk/emsdk_env.sh ]; then source ~/emsdk/emsdk_env.sh; fi && \
112112
emcmake cmake .. -DCMAKE_BUILD_TYPE=Release \
113113
-DIMGUI_BUNDLE_BUILD_IMGUI_EXPLORER_APP=ON -DIMGUI_BUNDLE_WITH_IMANIM=OFF -DIMGUI_BUNDLE_BUILD_DEMOS=OFF -DIMGUI_BUNDLE_WITH_IMMVISION=OFF && \
114114
cmake --build . -j 8
@@ -245,6 +245,65 @@ pyodide_deploy_imgui_bundle_online:
245245
echo "Deployed to https://traineq.org/imgui_bundle_online/"
246246

247247

248+
# ==============================================================
249+
# Cloudflare Pages deploy (to https://imgui-bundle.pages.dev/)
250+
# ==============================================================
251+
# See docs/book/devel_docs/cloudflare_deploy.md for the full workflow.
252+
#
253+
# imgui-bundle.pages.dev/ landing page (links)
254+
# imgui-bundle.pages.dev/playground_python/ pyodide playground
255+
# imgui-bundle.pages.dev/min_pyodide_app/ minimal pyodide sample
256+
# imgui-bundle.pages.dev/local_wheels/ shared wheel (referenced as ../local_wheels/)
257+
# imgui-bundle.pages.dev/explorer/ ibex
258+
#
259+
# Requires: wrangler (`npm i -g wrangler`) and either
260+
# - env vars CLOUDFLARE_API_TOKEN + CLOUDFLARE_ACCOUNT_ID, or
261+
# - `wrangler login` / `wrangler whoami` auth
262+
_CF_STAGING := "pyodide_projects/_cf_staging"
263+
_CF_PROJECT := "imgui-bundle"
264+
265+
# populates pyodide_projects/_cf_staging which is what will be uploaded to imgui-bundle.pages.dev/
266+
[group('cloudflare')]
267+
cf_stage:
268+
# 0. Make dir pyodide_projects/_cf_staging (gitignored)
269+
mkdir -p {{_CF_STAGING}}
270+
# 1. Copy landing page and CF _headers file
271+
# ------------------------------------------------------------
272+
cp pyodide_projects/cf_landing.html {{_CF_STAGING}}/index.html
273+
cp pyodide_projects/cf_headers {{_CF_STAGING}}/_headers
274+
# 2. Copy min_pyodide_app
275+
# ------------------------------------------------------------
276+
rm -rf {{_CF_STAGING}}/min_pyodide_app
277+
cd pyodide_projects/projects/min_bundle_pyodide_app && cp -f demo_heart.html demo_heart.source.txt
278+
rsync -a pyodide_projects/projects/min_bundle_pyodide_app/ {{_CF_STAGING}}/min_pyodide_app/
279+
# 3. Copy python playground
280+
# ------------------------------------------------------------
281+
# --copy-unsafe-links resolves the examples/ symlink (points outside the tree)
282+
rm -rf {{_CF_STAGING}}/playground_python {{_CF_STAGING}}/local_wheels
283+
rsync -a --copy-unsafe-links pyodide_projects/projects/imgui_bundle_playground/ {{_CF_STAGING}}/playground_python/
284+
rsync -a pyodide_projects/projects/local_wheels/ {{_CF_STAGING}}/local_wheels/
285+
# 4. Copy bundle explorer (ibex)
286+
# ------------------------------------------------------------
287+
rm -rf {{_CF_STAGING}}/explorer
288+
rsync -a --exclude='*.gz' build_ibex_ems/bin/ {{_CF_STAGING}}/explorer/
289+
cp build_ibex_ems/bin/demo_imgui_bundle.html {{_CF_STAGING}}/explorer/index.html
290+
291+
# Upload the current staging dir to Cloudflare Pages (the whole site snapshot)
292+
[group('cloudflare')]
293+
cf_deploy:
294+
wrangler pages deploy {{_CF_STAGING}} --project-name={{_CF_PROJECT}} --commit-dirty=true
295+
echo "Deployed to https://imgui-bundle.pages.dev/"
296+
297+
# Wipe staging
298+
[group('cloudflare')]
299+
cf_clean:
300+
rm -rf {{_CF_STAGING}}
301+
302+
# Serves locally the current staging dir (add coi headers for the explorer)
303+
[group('cloudflare')]
304+
cf_serve_local:
305+
cd {{_CF_STAGING}} && python ../../ci_scripts/webserver_multithread_policy.py -p 8765 --coi-prefix=/explorer/
306+
248307
# ==============================================================
249308
# Tests
250309
# ==============================================================

pyodide_projects/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
_cf_staging/

pyodide_projects/cf_headers

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Cloudflare Pages _headers file (copied to _cf_staging/_headers at stage time).
2+
# Syntax: https://developers.cloudflare.com/pages/configuration/headers/
3+
#
4+
# Scope COOP/COEP to /explorer/* only. Site-wide would break the playground,
5+
# which pulls Pyodide from a CDN that doesn't set CORP headers.
6+
7+
/explorer/*
8+
Cross-Origin-Opener-Policy: same-origin
9+
Cross-Origin-Embedder-Policy: require-corp

pyodide_projects/cf_landing.html

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8">
5+
<title>Dear ImGui Bundle — Online</title>
6+
<meta name="viewport" content="width=device-width, initial-scale=1">
7+
<style>
8+
body {
9+
font-family: system-ui, -apple-system, sans-serif;
10+
max-width: 640px;
11+
margin: 4em auto;
12+
padding: 0 1em;
13+
line-height: 1.5;
14+
color: #222;
15+
}
16+
h1 { margin-bottom: 0.2em; }
17+
.tag { color: #888; font-size: 0.9em; margin-bottom: 2em; }
18+
ul { list-style: none; padding: 0; }
19+
li { margin: 0.8em 0; }
20+
a { color: #0366d6; text-decoration: none; font-weight: 500; }
21+
a:hover { text-decoration: underline; }
22+
.desc { color: #555; font-size: 0.9em; }
23+
footer { margin-top: 4em; color: #888; font-size: 0.85em; }
24+
</style>
25+
</head>
26+
<body>
27+
<h1>Dear ImGui Bundle</h1>
28+
<div class="tag">Online demos and tools.</div>
29+
30+
<ul>
31+
<li>
32+
<a href="playground_python/">Python Playground (Pyodide)</a><br>
33+
<span class="desc">Edit and run Dear ImGui Bundle Python examples in your browser.</span>
34+
</li>
35+
<li>
36+
<a href="min_pyodide_app/demo_heart.html">Minimal Pyodide Template</a><br>
37+
<span class="desc">Minimal Template showing Dear ImGui Bundle app running in the browser.</span>
38+
</li>
39+
<li>
40+
<a href="min_pyodide_app/demo_heart.source.txt">Minimal Pyodide Template - Source</a><br>
41+
<span class="desc">Minimal Template Source.</span>
42+
</li>
43+
44+
<!-- <li>-->
45+
<!-- <a href="explorer/">ImGui Bundle Explorer</a><br>-->
46+
<!-- <span class="desc">Interactive explorer for Dear ImGui Bundle C++ demos (Emscripten).</span>-->
47+
<!-- </li>-->
48+
49+
</ul>
50+
51+
<footer>
52+
<a href="https://github.com/pthom/imgui_bundle">GitHub</a> ·
53+
<a href="https://pthom.github.io/imgui_bundle/">Documentation</a>
54+
</footer>
55+
</body>
56+
</html>

0 commit comments

Comments
 (0)