Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
1782b91
feat(site): build the site for the Stable or Next channel base with a…
Exoridus Oct 21, 2026
6cc0122
feat(pages): select, verify and compose the Stable and Next channel s…
Exoridus Oct 21, 2026
ddc7026
ci(pages): publish both channels from one composing workflow
Exoridus Oct 21, 2026
ea26d00
feat(build): add AST module specifier rewriter
Exoridus Oct 21, 2026
a855de8
feat(build)!: ship the runtime modules as Core subpaths
Exoridus Oct 21, 2026
2f9ff11
feat(codemods): add the v0.20 runtime import migration
Exoridus Oct 21, 2026
4e8d508
ci(release): derive the release matrix from package roles
Exoridus Oct 21, 2026
6944419
test(release): verify the hybrid Core package from its tarball
Exoridus Oct 21, 2026
ed200d9
refactor!: import the runtime modules through Core subpaths
Exoridus Oct 21, 2026
7590eba
test(particles): type-check the README against the Core subpath
Exoridus Oct 21, 2026
4cd4f1a
feat(release): add legacy shims for the retired runtime names and a r…
Exoridus Oct 21, 2026
afbcf3f
feat(distribution)!: ship v0.20 as ESM-only and drop the IIFE global …
Exoridus Oct 21, 2026
11efca4
feat(content): add the @codexo/exojs-demo-assets descriptor catalog p…
Exoridus Oct 21, 2026
bd208c8
fix(content): pin generated demo catalog artifacts against formatter …
Exoridus Oct 21, 2026
d01bebc
fix(content): exempt and type-check the demo-assets copy bin
Exoridus Oct 21, 2026
5b8981b
fix(release): keep independent content packages off the lockstep cohe…
Exoridus Oct 21, 2026
0eed411
Merge branch 'infra/pages-channels' into arch/v020-migration
Exoridus Oct 21, 2026
5dd03c0
fix(release): type the independent-package name set as plain strings
Exoridus Oct 21, 2026
cddba1d
fix(rendering): record the demo-assets colour boundary outcome
Exoridus Oct 21, 2026
09789ea
fix(ci): hand the built demo-assets media to the package verify job
Exoridus Oct 21, 2026
77899e8
ci: run the dist-dependent release tests in the build job and count t…
Exoridus Oct 21, 2026
423780f
fix(content): record the ship as original work and ship the metal imp…
Exoridus Oct 21, 2026
f1ff0b1
docs(content): show a scene lifecycle and the bundler behaviour in th…
Exoridus Oct 21, 2026
0e51f66
feat(examples)!: load the example media through @codexo/exojs-demo-as…
Exoridus Oct 21, 2026
174ea16
feat(examples): add a declarative demo panel that shares one state wi…
Exoridus Oct 21, 2026
d545e40
fix(examples): level the example audio and give each audio example a …
Exoridus Oct 21, 2026
b911329
feat(examples): fit the world, physics and tilemap examples to their …
Exoridus Oct 21, 2026
ddbbce1
feat(examples): fit the rendering, lighting and particle examples to …
Exoridus Oct 21, 2026
5998f77
feat(examples): fit the input, scene and animation examples to their …
Exoridus Oct 21, 2026
55902be
docs(examples): describe each example's interaction in the catalog an…
Exoridus Oct 21, 2026
9461e5b
fix(examples): size the firework sparks to read at playground scale a…
Exoridus Oct 21, 2026
ed91212
fix(site): resolve the demo catalog in the guide snippet typecheck
Exoridus Oct 21, 2026
7025825
fix(examples): re-list the asset browser when the category is picked …
Exoridus Oct 21, 2026
8e4c13e
feat(site): capture the preview's console in a collapsible Output pan…
Exoridus Oct 22, 2026
1437318
chore(examples): allow console output in examples now that the playgr…
Exoridus Oct 22, 2026
86d564b
fix(demo-assets): write the sound sprite sidecars in the start and en…
Exoridus Oct 22, 2026
b29f930
feat(examples): bring the Asset Browser back as a playground example …
Exoridus Oct 22, 2026
ebf2191
fix(site): run edited TypeScript in the playground preview and restar…
Exoridus Oct 22, 2026
7d4aab7
fix(audio): unlock audio on a pointer press even when the canvas cons…
Exoridus Oct 22, 2026
499a213
feat(examples): log input, hit-test, physics and settings events to t…
Exoridus Oct 22, 2026
6b4f08a
test(site): drive the Output panel and the Asset Browser through the …
Exoridus Oct 22, 2026
4904ac2
fix(site): drop a replaced preview's output at once and keep the Outp…
Exoridus Oct 22, 2026
3b26123
fix(examples): keep a discarded Asset Browser preview from touching t…
Exoridus Oct 22, 2026
f455b08
feat(examples): draw one waveform lane per channel in the asset browser
Exoridus Oct 22, 2026
06b2bf9
feat(examples): show the live signal and playhead while the asset bro…
Exoridus Oct 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
70 changes: 62 additions & 8 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ name: CI
# runs locally. A lane is added there, never here.
#
# plan ─┬─► gates (matrix)
# ├─► test (matrix) ─► skip-budget
# └─► build ─┬─► verify (matrix)
# ├─► test (matrix) ──┬─► skip-budget
# └─► build ─┬────────┘
# ├─► verify (matrix)
# └─► site ─► smoke
# all ─► verdict (the required check)
#
Expand Down Expand Up @@ -202,8 +203,8 @@ jobs:
# One budget over every lane's report: a test that moves from one lane to
# another must not change the count.
skip-budget:
needs: [plan, test]
if: ${{ !cancelled() && needs.plan.outputs.skipBudget == 'true' && needs.test.result == 'success' }}
needs: [plan, test, build]
if: ${{ !cancelled() && needs.plan.outputs.skipBudget == 'true' && needs.test.result == 'success' && (needs.build.result == 'success' || needs.build.result == 'skipped') }}
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
Expand All @@ -225,6 +226,8 @@ jobs:
steps:
- uses: actions/checkout@v6
- uses: ./.github/actions/setup
with:
browser: chromium

- name: Build core
env:
Expand All @@ -241,12 +244,32 @@ jobs:
EXOJS_REQUIRE_PRODUCTION_BUILD: '1'
run: pnpm test:production-stripping

- name: Verify the packed Core, its runtime subpaths and the legacy shims against the built dist
env:
EXOJS_REQUIRE_PRODUCTION_BUILD: '1'
run: pnpm test:release-dist --reporter=minimal --reporter=junit --outputFile.junit=./test-results/build.junit.xml

- name: Keep the JUnit report for the skip budget
if: ${{ !cancelled() }}
uses: actions/upload-artifact@v7
with:
name: junit-build
path: test-results/*.junit.xml
retention-days: 1
if-no-files-found: ignore

- name: Verify the built ESM tree through a browser import map
run: pnpm test:esm-importmap

- uses: actions/upload-artifact@v7
with:
name: dist
# The demo-assets build copies its standard media into media/, which
# the package exports and packs; the verify jobs pack from this tree.
path: |
dist/
packages/*/dist/
packages/exojs-demo-assets/media/
retention-days: 1
if-no-files-found: error

Expand All @@ -272,21 +295,42 @@ jobs:
if: ${{ !cancelled() && needs.plan.outputs.site == 'true' && needs.build.result == 'success' }}
runs-on: ubuntu-latest
timeout-minutes: 20
outputs:
# A re-run of failed jobs keeps this job's earlier attempt, so the smoke
# and the publisher find the candidate by this name, not by recomputing it.
candidate: site-${{ steps.channel.outputs.channel }}-${{ github.sha }}-${{ github.run_attempt }}
steps:
- uses: actions/checkout@v6
- uses: ./.github/actions/setup
with:
dist: 'true'

# main builds Stable at /ExoJS/, next builds Next at /ExoJS/next/ (a pull
# request builds the channel of the branch it targets); any other ref
# builds Stable. The table lives in scripts/pages/site-channel.ts.
- id: channel
env:
SITE_REF: ${{ github.event_name == 'pull_request' && github.base_ref || github.ref_name }}
run: node scripts/pages/site-channel.ts >> "$GITHUB_OUTPUT"

# Typechecks the site against the published entry points, so it needs
# the dist the build job produced.
- run: pnpm gates site

- env:
EXOJS_PACKAGE_PATH: ..
EXOJS_SITE_CHANNEL: ${{ steps.channel.outputs.channel }}
EXOJS_SITE_BASE: ${{ steps.channel.outputs.base }}
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
run: pnpm site:build

# The tree must match its own site-build.json, carry this commit, channel
# and base, contain no links or special files and fit the site budget.
- name: Verify the site candidate
env:
SITE_CHANNEL: ${{ steps.channel.outputs.channel }}
run: node scripts/pages/channel-artifact.ts verify --dir site/dist --channel "$SITE_CHANNEL" --sha "$GITHUB_SHA"

- name: Upload tooling logs on failure
if: ${{ failure() }}
uses: actions/upload-artifact@v7
Expand All @@ -297,12 +341,14 @@ jobs:
include-hidden-files: true
if-no-files-found: warn

# Deploy Pages ships exactly this artifact; it builds nothing itself.
# Deploy Pages ships exactly this artifact; it builds nothing itself. A
# push to main or next is a publishable channel candidate and is kept for
# 90 days, so the publisher never depends on a short-lived download.
- uses: actions/upload-artifact@v7
with:
name: site-dist-${{ github.sha }}
name: site-${{ steps.channel.outputs.channel }}-${{ github.sha }}-${{ github.run_attempt }}
path: site/dist/
retention-days: 1
retention-days: ${{ github.event_name == 'push' && 90 || 1 }}
if-no-files-found: error

# Every catalog example through the real playground route, against the site
Expand All @@ -321,13 +367,21 @@ jobs:
- uses: ./.github/actions/setup
with:
browser: chromium
# The exact candidate tree, served at the base its site-build.json names.
- uses: actions/download-artifact@v8
with:
name: site-dist-${{ github.sha }}
name: ${{ needs.site.outputs.candidate }}
path: site/dist

- run: pnpm test:examples:smoke --renderer webgl2 ${{ needs.plan.outputs.smokeSample == 'true' && '--sample' || '' }}

# The playground shell around the examples: console capture across the
# preview iframe and the Output panel's layouts, and the Asset Browser's
# previews, cancelled loads and generated snippets. Runs after a failed
# smoke too, so one run reports both.
- run: pnpm test:playground:e2e
if: ${{ !cancelled() }}

- name: Keep the smoke report
if: ${{ !cancelled() }}
uses: actions/upload-artifact@v7
Expand Down
146 changes: 94 additions & 52 deletions .github/workflows/deploy-pages.yml
Original file line number Diff line number Diff line change
@@ -1,83 +1,125 @@
name: Deploy Pages

# Deploys the site EXACTLY as CI built and validated it — this workflow builds
# no source code. CI's `site` job (and a release's `prepare` job) runs the site
# gates and `site:build` and uploads `site/dist/**` as `site-dist-<sha>`; here
# that same artifact is downloaded from the run that triggered this one and
# handed to Pages unchanged.
# The one publisher of the project site. GitHub Pages serves a single tree, so
# every deployment carries both channels: Stable at /ExoJS/ and Next at
# /ExoJS/next/, each exactly as a green CI push to main or next (or a stable
# Release) built and smoked it. This workflow builds no site code.
#
# The defect this prevents: the workflow used to check the sources out and
# rebuild the library and the site itself, so what went live was a second build
# that no gate had ever seen. A green CI run said nothing about the deployed
# bytes.
# The triggering run is only a wake-up call. On every start the publisher
# re-decides BOTH channels from GitHub's own metadata (scripts/pages/
# select-channels.ts): each channel gets the newest qualified candidate on its
# branch's current history, or keeps what is already published. A queued or
# re-run event can therefore neither roll a channel back nor drop the other
# channel's update, and a green run without a site candidate changes nothing.
#
# Fail-closed by construction: there is no `workflow_dispatch` path, because a
# manual run has no triggering run to read the artifact from. If the artifact is
# missing or belongs to a different commit, the download step fails and nothing
# is deployed. To re-deploy, re-run the CI workflow for the commit.
# Trust: only successful same-repository push runs of CI (main/next) and
# stable Release tags qualify; a pull request, a fork or a manual dispatch
# never does. Candidates are downloaded by the trusted scripts of the
# default-branch checkout below, never executed, and checked against the
# digest GitHub reports, their own site-build.json and the archive rules
# before anything is composed. Any doubt deploys nothing and leaves the live
# site as it is.
#
# A deployment also keeps its composed tree (the Pages artifact) and its
# channels.json for 90 days, so a channel whose candidate expired can be
# carried over unchanged from the last published snapshot.

on:
workflow_run:
workflows: ['Release', 'CI']
types: [completed]

permissions:
contents: read
# Reading an artifact from ANOTHER workflow run needs `actions: read` plus an
# explicit token on the download step.
actions: read
pages: write
id-token: write
permissions: {}

# One queue for the whole site, and a running deployment is never cancelled.
# Ordering does not rest on this: each run re-decides both channels when it
# starts.
concurrency:
group: pages-${{ github.event.workflow_run.head_sha }}
cancel-in-progress: true
group: pages-combined
cancel-in-progress: false

jobs:
deploy:
name: Deploy to GitHub Pages
compose:
name: Compose Stable and Next
if: >-
${{
github.event_name == 'workflow_run' &&
github.event.workflow_run.conclusion == 'success' &&
(
github.event.workflow_run.name == 'Release' ||
(
github.event.workflow_run.name == 'CI' &&
github.event.workflow_run.event == 'push' &&
github.event.workflow_run.head_branch == 'main'
)
)
github.event.workflow_run.event == 'push' &&
github.event.workflow_run.repository.full_name == github.repository &&
github.event.workflow_run.head_repository.full_name == github.repository
}}
runs-on: ubuntu-latest
timeout-minutes: 10
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
timeout-minutes: 30
permissions:
contents: read
# Listing runs and reading artifacts of other workflow runs.
actions: read
outputs:
deploy: ${{ steps.select.outputs.deploy }}
steps:
# `run-id` pins the download to the exact run that triggered this one, and
# the commit in the artifact name pins it to that run's commit — an older
# or unrelated build cannot be picked up. A missing artifact fails here.
- name: Download validated site artifact
uses: actions/download-artifact@v8
# The publisher's own scripts, from the default branch - never from a
# candidate. Dependency-free TypeScript that node strips: no install.
- uses: actions/checkout@v6
with:
name: site-dist-${{ github.event.workflow_run.head_sha }}
run-id: ${{ github.event.workflow_run.id }}
github-token: ${{ github.token }}
path: site-dist
ref: ${{ github.event.repository.default_branch }}
persist-credentials: false
sparse-checkout-cone-mode: false
sparse-checkout: |
/.nvmrc
/scripts/pages/

- uses: actions/setup-node@v6
with:
node-version-file: .nvmrc

- name: Select both channels
id: select
env:
GITHUB_TOKEN: ${{ github.token }}
TRIGGER_RUN_ID: ${{ github.event.workflow_run.id }}
run: node scripts/pages/select-channels.ts --out "$RUNNER_TEMP/selection.json"

- name: Verify the artifact carries a built site
run: |
test -f site-dist/index.html || {
echo "::error::site-dist-${{ github.event.workflow_run.head_sha }} has no index.html — refusing to deploy."
exit 1
}
- name: Fetch, verify and compose the channels
if: steps.select.outputs.deploy == 'true'
env:
GITHUB_TOKEN: ${{ github.token }}
run: >-
node scripts/pages/compose-site.ts
--selection "$RUNNER_TEMP/selection.json"
--work "$RUNNER_TEMP/channels"
--out "$RUNNER_TEMP/site"
--channels-out "$RUNNER_TEMP/channels-record"

- name: Package for Pages
if: steps.select.outputs.deploy == 'true'
uses: actions/upload-pages-artifact@v5
with:
path: site-dist
path: ${{ runner.temp }}/site
retention-days: 90

- name: Keep the deployed pairing
if: steps.select.outputs.deploy == 'true'
uses: actions/upload-artifact@v7
with:
name: pages-channels-${{ github.run_id }}
path: ${{ runner.temp }}/channels-record/channels.json
retention-days: 90
if-no-files-found: error

deploy:
name: Deploy to GitHub Pages
needs: compose
if: needs.compose.outputs.deploy == 'true'
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
pages: write
id-token: write
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Deploy
id: deployment
uses: actions/deploy-pages@v5
25 changes: 14 additions & 11 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -81,33 +81,36 @@ jobs:
pnpm verify:lockstep
pnpm verify:release-matrix

- name: Build core, extensions and the site
- name: Build core (with its runtime modules), adapters and the site
env:
EXOJS_PACKAGE_PATH: ..
# A release tag sits on main: its site is the Stable channel.
EXOJS_SITE_CHANNEL: stable
# One line per package: verify:release-matrix checks each against the
# lockstep list, so a package added without a build line fails there.
run: |
pnpm build
pnpm --filter @codexo/exojs-particles build
pnpm --filter @codexo/exojs-tilemap build
pnpm --filter @codexo/exojs-tiled build
pnpm --filter @codexo/exojs-physics build
pnpm --filter @codexo/exojs-tilemap-physics build
pnpm --filter @codexo/exojs-lighting build
pnpm --filter @codexo/exojs-pathfinding build
pnpm --filter @codexo/exojs-audio-fx build
pnpm --filter @codexo/exojs-aseprite build
pnpm --filter @codexo/exojs-ldtk build
pnpm --filter @codexo/exojs-react build
pnpm --filter create-exo-app build
# Not a lockstep package, but the site serves its catalog and media.
pnpm --filter @codexo/exojs-demo-assets build
pnpm site:build

# Deploy Pages ships the site of a successful release from this artifact.
- name: Verify the Stable site candidate
env:
RELEASE_SHA: ${{ needs.trust.outputs.sha }}
run: node scripts/pages/channel-artifact.ts verify --dir site/dist --channel stable --sha "$RELEASE_SHA"

# Deploy Pages may ship the Stable site of a successful release from this
# artifact, and falls back to it when the CI candidate has expired.
- uses: actions/upload-artifact@v7
with:
name: site-dist-${{ needs.trust.outputs.sha }}
name: site-stable-${{ needs.trust.outputs.sha }}-${{ github.run_attempt }}
path: site/dist/
retention-days: 1
retention-days: 90
if-no-files-found: error

# The site build regenerates tracked content with platform-dependent
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
node_modules
dist/*
packages/*/dist
# Assembled from examples/assets by the demo-assets build and archive scripts.
packages/exojs-demo-assets/media/
packages/exojs-demo-assets/archives/
coverage
__screenshots__
npm-debug.log*
Expand Down
Loading
Loading