Publishing an example as a standalone repository¶
This guide turns one directory from examples/ into its
own self-contained GitHub repository, so a colleague can:
- download a prebuilt binary for Linux/macOS/Windows and run it, and
- optionally play it in the browser (a WebAssembly build on GitHub Pages).
It is the process used for the two reference repos — copy either as a template:
| Repo | Kind | Browser build? |
|---|---|---|
chrplr/Language-Localizer-French-audio |
audio experiment, ships .wav assets |
no |
chrplr/Rush-Hour |
mouse experiment, everything embedded | yes (Pages) |
Just need to hand off a zip, not publish a repo? Use the Makefile wrapper from the repo root:
make share-<ExampleName> # → _build/share/<ExampleName>/ make share-<ExampleName> VERSION=v0.12.5 # pin a specific goxpyriment releaseThat exports a standalone module (buildable on its own with
go run ., no workspace) to zip and email — seeexamples/README.md. This guide goes further: it turns that output into a published repository with release CI and an optional in-browser build. Start fromshare.sh(Step 1), then add the pieces below.
When to use it¶
Publish an example this way when you want an external audience to run it without the goxpyriment workspace. Only publish a browser build for paradigms that tolerate browser timing (choice RT, memory, attention, puzzles) — not ones that need sub-millisecond onset control (rapid RSVP, subliminal priming). See WASM.md.
Prerequisites¶
- The
ghCLI, authenticated (gh auth status). - For a browser build: the example must embed all its assets with
//go:embed(no filesystem exists in the browser), and it should avoid APIs still stubbed on js — see WASM.md. - Use a goxpyriment release that contains the browser fixes (≥ v0.12.5):
the js
Save()no-op (one download at the end, not per block) and the mouse path fix (RenderCoordinatesFromWindow). Earlier releases crash or spam downloads in the browser.
Step 1 — Generate the standalone module¶
From the goxpyriment repo root, export the example with the latest release pinned (or pass a specific version):
bash examples/share.sh <ExampleName> <version> <output-parent-dir>
# e.g.
bash examples/share.sh Rush-Hour v0.12.5 ~/00_git
# → ~/00_git/Rush-Hour/ with a generated go.mod/go.sum requiring goxpyriment v0.12.5
share.sh copies the directory, drops the workspace go.mod/go.work coupling,
writes a fresh go.mod requiring the published goxpyriment, and runs
go mod tidy. The result builds on its own with go build ..
make share-<ExampleName> is the convenient wrapper, but it always writes to
_build/share/<ExampleName>/; call share.sh directly with the third argument
when you want the module created straight where the repo will live (e.g.
~/00_git).
Step 2 — Prune goxpyriment-internal files¶
The copy still contains files that only make sense inside the monorepo. Remove:
cd ~/00_git/<ExampleName>
rm -rf .claude meta.yaml description.md # gallery/agent metadata, not needed standalone
Keep the .go sources, embedded assets, tests, and README.md.
Step 3 — Add the repo scaffolding¶
Add these files (copy and adapt from the reference repos):
LICENSE— pick a license. goxpyriment itself is Apache-2.0, but you are its author, so you may license your example however you like (Rush-Hour is MIT). If you change the license, update the per-file header comments to match.build.sh/build.bat— a one-command local build (CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o <Name> .). These let a colleague build a locally-signed binary that avoids OS gatekeeper warnings..gitignore— ignore the built binary. Notego build .produces a binary named after the module (lowercase, e.g.rush-hour) andbuild.shproduces the-oname (e.g.Rush-Hour); ignore both, plusdist/and_build/.README.md— adapt the example's README: changego run ./examples/<Name>togo run ., and add "Binaries" (Releases) and, if applicable, "Play in your browser" sections. The reference READMEs include the standard notes about unsigned macOS/Windows binaries.- For a browser build only:
web/index.html(copy Rush-Hour's; it wires the canvas,sdl.js,wasm_exec.js, andmain.wasm).
Step 4 — Add the CI workflows¶
.github/workflows/release.yml (native binaries)¶
Cross-compiles Linux/macOS/Windows on every push and, on a v* tag, publishes a
GitHub Release with archives + SHA256SUMS. CGO_ENABLED=0; go-sdl3 is pure Go
and embeds SDL for every platform, so all three cross-compile from Linux. This
path uses upstream go-sdl3 — no replace. Copy Rush-Hour's verbatim and
change the binary name.
.github/workflows/pages.yml (browser build — optional)¶
Builds the WebAssembly bundle and deploys it to GitHub Pages. Two things make it work (copy Rush-Hour's file and change nothing but the repo name in comments):
- It checks out the
chrplr/go-sdl3-wasmfork (branchwasm-render-fixes), which supplies the js/wasm SDL bindings and thewasmsdlbundler (shipssdl.js+sdl.wasm). - It runs
go mod edit -replace github.com/Zyko0/go-sdl3=../go-sdl3-wasmin CI only (never committed), so the browser build uses the fork while native/release builds keep using upstream go-sdl3.
Gotcha — branch ref, not pinned commit.
pages.ymltracks the fork's branch HEAD, whereas goxpyriment's owngo.modpins a specific fork pseudo-version. A fix that lives only in goxpyriment's vendored copy (or an uncommitted fork edit) will not reach your repo's browser build until it is committed and pushed towasm-render-fixes. This is the trap that broke Rush-Hour's first deploy.
Step 5 — Create the GitHub repo and enable Pages¶
cd ~/00_git/<ExampleName>
git init -b main && git add -A && git commit -m "Initial commit"
gh repo create chrplr/<ExampleName> --public --source=. --remote=origin --push
# Browser build only: enable Pages with GitHub Actions as the source
gh api -X POST repos/chrplr/<ExampleName>/pages -f build_type=workflow
The first push runs both workflows. The Pages URL is
https://chrplr.github.io/<ExampleName>/.
Step 6 — Cut a release¶
Binaries are published only on a version tag:
Native binaries are independent of the browser build — tag whenever the source is ready.
Updating the pinned goxpyriment later¶
When a newer goxpyriment fixes something you need, re-pin and redeploy:
go get github.com/chrplr/goxpyriment@vX.Y.Z && go mod tidy
git commit -am "Bump goxpyriment to vX.Y.Z" && git push # redeploys Pages
Gotcha — fresh tags and the checksum DB. Right after you push a goxpyriment tag,
sum.golang.orgmay not have indexed it yet andgo getfails with a500. Fetch straight from the tag once withGOPROXY=direct GOSUMDB=off go get github.com/chrplr/goxpyriment@vX.Y.Z; the correct hashes land in yourgo.sum, so CI (which readsgo.sum) is fine.
Verifying a browser build¶
Serve the bundle and open it in a real, focused, foreground tab:
# from the goxpyriment repo, before you split it out:
make wasm-<ExampleName>-serve # http://localhost:8080/?s=1
# or the built standalone repo's Pages URL after deploy
Sanity checks: the experiment renders and responds; a normal end triggers one
.csv + -info.txt download (not one per block); on a crash you see the error
overlay and no download (see WASM.md). Check the browser console
for panics — each names the file:line of any still-stubbed binding.
See also¶
examples/README.md—make share-NAME(the zip-and-email path)- WASM.md — how the browser build works, timing, and known gaps
- How_to_package_your_experiment.md — the GoReleaser alternative for native binaries