Run the gate, the editor suite and the three games on the Gitea runner
game-maker / suite (push) Successful in 7m31s

The job image (tools/ci/Dockerfile, label game-maker-ci) carries chromium with the Vulkan
drivers, php-cli and the host's fonts; the render device itself comes from the runner's
container.options, so a job container gets the Intel GPU instead of llvmpipe.

tools/ci/serve.sh puts the checkout behind /game-maker/ with php -S and writes the project
shells the seeders need; every tool now takes its server from GAME_MAKER_BASE_URL rather than
from a project id repeated in shell scripts.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
rootandClaude Opus 5 committed 2026-09-12 20:01:45 +02:00
1 parent c47fd5e4dc
commit 4f0908df77
8 files changed
+181 -4

No files matched your search

+21
View File
@@ -0,0 +1,21 @@
# The image behind the Gitea Actions label `game-maker-ci`. It is built on the host and kept
# local (the runner has `force_pull: false`), so a change here needs a rebuild:
#
# DOCKER_BUILDKIT=0 docker build --network git_default -t game-maker-ci:latest tools/ci
#
# Both flags are needed on stein2: DNS is dead in Docker's default bridge network, and Buildkit
# cannot join a named network.
FROM node:22-bookworm
# chromium + the Vulkan drivers: the e2e harness starts chromium with --use-angle=vulkan and has
# no software fallback. The render device itself comes from the runner's container.options;
# without it chromium falls back to llvmpipe and gets WebGL 1.0 only.
# php-cli serves api/index.php, which every tool loads and saves its project through.
# The fonts are the host's set on purpose: the checks measure text widths to find click points,
# so a different set moves them.
RUN apt-get update && apt-get install -y --no-install-recommends \
chromium mesa-vulkan-drivers libvulkan1 \
php-cli php-mbstring \
fonts-dejavu-core fonts-droid-fallback fonts-noto-color-emoji fonts-noto-mono \
fonts-texgyre fonts-urw-base35 \
&& rm -rf /var/lib/apt/lists/*
+29
View File
@@ -0,0 +1,29 @@
#!/usr/bin/env node
/**
* Creates the empty project files the seeders write into. `PUT ?r=projects/{id}` does not create
* a project and `POST` hands out a random id, so a fresh CI checkout has to put the shells with
* the known ids in place itself. The playground is left out – no tool may touch it.
*
* Usage: node tools/ci/project-shells.mjs [dataDir]
*/
import { mkdir, writeFile } from 'node:fs/promises';
import { dirname, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
import { PROJECTS } from '../lib/projects.mjs';
const root = resolve(dirname(fileURLToPath(import.meta.url)), '../..');
const dir = process.argv[2] ?? resolve(root, 'data/projects');
await mkdir(dir, { recursive: true });
for (const [name, id] of Object.entries(PROJECTS)) {
if (name === 'playground') continue;
const doc = {
id,
name,
revision: 0,
updatedAt: '1970-01-01T00:00:00Z',
project: {},
};
await writeFile(resolve(dir, `${id}.json`), `${JSON.stringify(doc)}\n`);
console.log(`shell ${name} ${id}`);
}
+14
View File
@@ -0,0 +1,14 @@
#!/bin/bash
# The editor suite in CI: serve the checkout, seed the fixture, run every step.
# Ends in a line that survives a pipe, like check.sh does.
set -euo pipefail
cd "$(dirname "$(readlink -f "$0")")/../.."
source tools/ci/serve.sh
game_maker_serve
set +e
node tools/seed-e2e.mjs && node tools/e2e.mjs
code=$?
echo "=== E2E EXIT $code ==="
exit $code
+17
View File
@@ -0,0 +1,17 @@
#!/bin/bash
# The three example games in CI: seed each one, then play it in a headless browser.
# Ends in a line that survives a pipe, like check.sh does.
set -euo pipefail
cd "$(dirname "$(readlink -f "$0")")/../.."
source tools/ci/serve.sh
game_maker_serve
set +e
code=0
for game in jumprun tetris actor; do
echo "--- $game"
node "tools/seed-$game.mjs" && node "tools/smoke-$game.mjs" || code=1
done
echo "=== SMOKES EXIT $code ==="
exit $code
+38
View File
@@ -0,0 +1,38 @@
#!/bin/bash
# Sourced, not run: it leaves the PHP server in the caller's background and exports
# GAME_MAKER_BASE_URL, which every tool reads.
#
# The build wires its assets absolutely under /game-maker/dist/, so the checkout has to be
# served under that path segment – hence the symlink into /srv rather than `-t .`.
export GAME_MAKER_BASE_URL=http://127.0.0.1:8080/game-maker/
game_maker_serve() {
local root
root=$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)
mkdir -p "$root/data/projects" "$root/data/textures"
node "$root/tools/ci/project-shells.mjs"
# A server from an earlier step of the same job is fine to keep.
if curl -sf "${GAME_MAKER_BASE_URL}api/index.php?r=projects" >/dev/null 2>&1; then
echo "server already up at $GAME_MAKER_BASE_URL"
return 0
fi
mkdir -p /srv && ln -sfn "$root" /srv/game-maker
# Without workers the single-threaded dev server stalls: the editor fetches project and
# textures in parallel.
PHP_CLI_SERVER_WORKERS=8 php -S 0.0.0.0:8080 -t /srv >/tmp/php-server.log 2>&1 &
for _ in $(seq 60); do
curl -sf "${GAME_MAKER_BASE_URL}api/index.php?r=projects" >/dev/null 2>&1 && break
sleep 0.5
done
if ! curl -sf "${GAME_MAKER_BASE_URL}api/index.php?r=projects" >/dev/null 2>&1; then
echo "the server never came up" >&2
tail -20 /tmp/php-server.log >&2
return 1
fi
echo "serving $root at $GAME_MAKER_BASE_URL"
}
+5 -3
View File
@@ -24,12 +24,14 @@ export const PROJECTS = {
export const DEFAULT_BASE_URL = 'https://nockal.com/game-maker/';
/**
* Reads `[projectId] [baseUrl]` from argv, falling back to `defaultId`. Targeting the playground
* aborts: no tool may overwrite or mutate it.
* Reads `[projectId] [baseUrl]` from argv, falling back to `defaultId` and to
* `GAME_MAKER_BASE_URL` – which is how CI points every tool at its own server without
* repeating the project ids in shell scripts. Targeting the playground aborts: no tool may
* overwrite or mutate it.
*/
export function cliArgs(script, defaultId) {
const projectId = process.argv[2] ?? defaultId;
const baseUrl = process.argv[3] ?? DEFAULT_BASE_URL;
const baseUrl = process.argv[3] ?? process.env.GAME_MAKER_BASE_URL ?? DEFAULT_BASE_URL;
if (projectId === PROJECTS.playground) {
console.error(`refusing to run ${script} against the playground project (${PROJECTS.playground}).`);
process.exit(2);