Run the gate, the editor suite and the three games on the Gitea runner
game-maker / suite (push) Successful in 7m31s
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:
1 parent
c47fd5e4dc
commit
4f0908df77
8 files changed
+181
-4
No files matched your search
@@ -0,0 +1,38 @@
|
|||||||
|
# Runs the whole proof on the Gitea runner: gate, the editor suite and the three example games.
|
||||||
|
#
|
||||||
|
# `runs-on: game-maker-ci` picks the image built from tools/ci/Dockerfile. The render device and
|
||||||
|
# /dev/shm come from the runner's own container.options (/_stacks/git/runner/config.yaml), not
|
||||||
|
# from here – without them chromium falls back to software WebGL.
|
||||||
|
name: game-maker
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
suite:
|
||||||
|
runs-on: game-maker-ci
|
||||||
|
timeout-minutes: 45
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Versions
|
||||||
|
run: |
|
||||||
|
node --version
|
||||||
|
chromium --version
|
||||||
|
php --version | head -1
|
||||||
|
|
||||||
|
- name: Install
|
||||||
|
run: npm ci --no-audit --no-fund
|
||||||
|
|
||||||
|
- name: Gate
|
||||||
|
run: ./check.sh
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: npm run build
|
||||||
|
|
||||||
|
- name: Editor suite
|
||||||
|
run: tools/ci/run-e2e.sh
|
||||||
|
|
||||||
|
- name: Example games
|
||||||
|
run: tools/ci/run-smokes.sh
|
||||||
@@ -788,7 +788,7 @@ npm test # unit tests (core, editor, ui, viewport)
|
|||||||
npm run typecheck
|
npm run typecheck
|
||||||
npm run lint # ESLint (type-aware for src and tests); lint:fix for the fixable half
|
npm run lint # ESLint (type-aware for src and tests); lint:fix for the fixable half
|
||||||
npm run format # Prettier over the repo, minus what .prettierignore lists
|
npm run format # Prettier over the repo, minus what .prettierignore lists
|
||||||
npm run check # typecheck + lint + format:check + tests, what CI would run
|
npm run check # typecheck + lint + format:check + tests, the gate CI runs too
|
||||||
./check.sh # the same, ending in `=== CHECK EXIT n ===`: piped into `tail` npm's exit
|
./check.sh # the same, ending in `=== CHECK EXIT n ===`: piped into `tail` npm's exit
|
||||||
# code is lost (tail's is reported), so a red run reads as green
|
# code is lost (tail's is reported), so a red run reads as green
|
||||||
npm run build # writes dist/ and regenerates index.html
|
npm run build # writes dist/ and regenerates index.html
|
||||||
@@ -914,6 +914,24 @@ he walks on the spot – a clock that ticks without the picture following would
|
|||||||
|
|
||||||
The workspace folder itself is what nginx serves, so `npm run build` is the deployment.
|
The workspace folder itself is what nginx serves, so `npm run build` is the deployment.
|
||||||
|
|
||||||
|
### Continuous integration
|
||||||
|
|
||||||
|
Every push runs the whole proof on the Gitea runner: `.gitea/workflows/ci.yml` installs, runs
|
||||||
|
`./check.sh`, builds, then `tools/ci/run-e2e.sh` (the editor suite) and `tools/ci/run-smokes.sh`
|
||||||
|
(the three games). Both scripts source `tools/ci/serve.sh`, which puts the checkout behind
|
||||||
|
`http://127.0.0.1:8080/game-maker/` with `php -S` – the build wires its assets absolutely under
|
||||||
|
`/game-maker/dist/`, so the path segment is not optional – and writes the empty project files the
|
||||||
|
seeders need (`PUT` does not create a project, `POST` hands out a random id). Every tool takes its
|
||||||
|
server from `GAME_MAKER_BASE_URL`.
|
||||||
|
|
||||||
|
The job runs in the image from `tools/ci/Dockerfile` under the label `game-maker-ci`: chromium
|
||||||
|
with the Vulkan drivers, `php-cli`, and the host's font packages, because the checks measure text
|
||||||
|
widths to find click points. The **render device is not in this repo** – `/dev/dri/renderD128`
|
||||||
|
and a 1 GB `/dev/shm` come from `container.options` in the runner's own `config.yaml`, which
|
||||||
|
applies to every job on that runner. Without them chromium falls back to llvmpipe, which gets
|
||||||
|
WebGL 1.0 only. Measured in the job container: `ANGLE (Intel, Vulkan 1.3.230, HD Graphics 630)`,
|
||||||
|
WebGL 2.0, and the same 671 checks as here.
|
||||||
|
|
||||||
### Ideas, gaps and bugs
|
### Ideas, gaps and bugs
|
||||||
|
|
||||||
They live in the issue tracker of the Gitea repo: <https://git.nockal.com/meleki/game-maker/issues>.
|
They live in the issue tracker of the Gitea repo: <https://git.nockal.com/meleki/game-maker/issues>.
|
||||||
|
|||||||
@@ -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/*
|
||||||
Executable
+29
@@ -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}`);
|
||||||
|
}
|
||||||
Executable
+14
@@ -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
|
||||||
Executable
+17
@@ -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
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
@@ -24,12 +24,14 @@ export const PROJECTS = {
|
|||||||
export const DEFAULT_BASE_URL = 'https://nockal.com/game-maker/';
|
export const DEFAULT_BASE_URL = 'https://nockal.com/game-maker/';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads `[projectId] [baseUrl]` from argv, falling back to `defaultId`. Targeting the playground
|
* Reads `[projectId] [baseUrl]` from argv, falling back to `defaultId` and to
|
||||||
* aborts: no tool may overwrite or mutate it.
|
* `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) {
|
export function cliArgs(script, defaultId) {
|
||||||
const projectId = process.argv[2] ?? 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) {
|
if (projectId === PROJECTS.playground) {
|
||||||
console.error(`refusing to run ${script} against the playground project (${PROJECTS.playground}).`);
|
console.error(`refusing to run ${script} against the playground project (${PROJECTS.playground}).`);
|
||||||
process.exit(2);
|
process.exit(2);
|
||||||
|
|||||||
Reference in new issue
Block a user