Continuous integration
All workflows live in .github/workflows.
| Workflow | File | Triggers | Publishes |
|---|---|---|---|
| CI | ci.yml |
every pull request; push to main and phase-*; manual |
nothing |
| H5P integration tests | h5p-integration.yml |
manual only | nothing |
| Publish images | publish.yml |
push to main, v* tags, manual |
images to GHCR |
| Docs | docs.yml |
pull requests touching the site, docs/ or the code it documents; push to main; manual |
this site to GitHub Pages |
CI (ci.yml)
Section titled “CI (ci.yml)”One workflow for the whole monorepo. Path filters are evaluated in a job, not in on.paths, so
skipped jobs still report a status and a single job can be the required check. A new push to a
pull request cancels the previous run (concurrency with cancel-in-progress on pull requests).
Permissions are contents: read.
changes (Detect changes) uses dorny/paths-filter@v3 to set two outputs:
js: something changed inadmin/**,front/**,api/h5p/**,api/pdf/**,package.json,yarn.lock,turbo.json,.nvmrcor.github/**;php: something changed inapi/**outsideapi/h5pandapi/pdf, or in.github/**.
It also runs two repository checks on every run: every api/packages/* directory has a README.md,
and (pull requests only) no newly added file is larger than 2 MB. Exceptions to the size check are
listed in .github/large-files-allowed.txt. yarn.lock is checked by yarn install --frozen-lockfile
in the js job, which fails when the lock file is not what yarn install would write.
js (JS: admin, front, web, ui, sdk, api-h5p, api-pdf) runs when js is true or on manual dispatch:
- Node from
.nvmrc, Yarn cache fromactions/setup-node,corepack yarn install --frozen-lockfile. - Licence guard:
yarn workspace front lint:gplfails on any@lumieducation/*or@escolalms/h5p-reactimport infront/src(shared library included), andlint:styledon any styled-components import infront/srcoradmin/src. - Prettier check for
admin(the adminlintscript runs Prettier with--write, which never fails). - Restores the Turborepo cache (
.turbo/cache, keyed by OS and commit, falling back to the latest). turbo run typecheck lint build test --filter=admin --filter=front --filter=api-h5p --filter=api-pdf --concurrency=2.turbo run typecheck lint test --filter=@ulams/web --filter=@ulams/ui --filter=@ulams/sdk --concurrency=2: the reference frontend and its libraries (type check, lint, unit tests).
php (PHPUnit, one job per shard) runs when php is true or on manual dispatch. Matrix
shard: [learning, tasks-topics, gift-reports, commerce, platform, content], fail-fast: false:
- Service containers:
postgres:12(databasedefault, userdefault, passwordsecret) andvalkey/valkey:8-alpine, both with health checks. - Resolves the shard’s suites from
PHPUNIT_SHARDSand checks that every<testsuite>inapi/phpunit.xmlis in exactly one shard. - Installs
ffmpeg(ffprobe is needed by topic types and the video package). - PHP 8.4 via
shivammathur/setup-phpwith the extensions of the PHP image (apcu,bcmath,exif,gd,intl,pcntl,pdo_pgsql,redis,zip),memory_limit=2G, no coverage. - Composer cache keyed on
api/composer.lock;composer installfrom the lock. - Copies
api/docker/envs/.env.ci.postgresto.env(the job’sDB_*andREDIS_*variables override it), runsmigrate:fresh, seeds permissions, creates Passport keys and a personal client, copies the keys for Testbench and runs the courses test migrations. - On the
platformshard only:php artisan l5-swagger:generate, which fails on broken Swagger annotations. - Reads
api/phpunit.quarantine.xml, then PHPUnit for the shard’s suites without the quarantined tests, with a JUnit report. - Quarantined tests of the shard, non-blocking (
continue-on-error); nothing runs when the file is empty. - On failure, uploads the JUnit file and
api/storage/logs/as an artifact for 7 days.
Shards and quarantine are explained in Testing.
ci-ok (CI OK) always runs after changes, js and php. It fails if any of them failed
or was cancelled and passes when they passed or were skipped by the path filters. This is the
job to mark as the required status check: the workflow comment names it as “the single required
check”. Branch protection is configured in GitHub, not in the repository.
H5P integration tests (h5p-integration.yml)
Section titled “H5P integration tests (h5p-integration.yml)”Manual (workflow_dispatch). Starts postgres:12, valkey/valkey:8-alpine and
bitnamilegacy/minio:latest as service containers, installs the workspaces, downloads the H5P
core and editor files (yarn workspace api-h5p download:core), waits for MinIO and runs
corepack yarn test:h5p:integration (test/http.test.ts and test/pg-storage.test.ts). The
unit suites of api-h5p already run in ci.yml.
Publish images (publish.yml)
Section titled “Publish images (publish.yml)”Builds the seven container images, multi-arch, with provenance and an SBOM, and pushes them to
ghcr.io/ulams-dev/*. Runs on push to main, on v* tags and manually; runs for the same ref
queue rather than cancel. Permissions: contents: read, packages: write (the GITHUB_TOKEN
logs in to GHCR).
- Job
php + api: buildsphpfromapi/docker/php, thenapifromapi/DockerfilewithBASE_IMAGEpinned to the digest of thephpimage it just pushed. - Job
apps(matrix,fail-fast: false):h5p,pdf,admin,front,web, all built from the repository root.
Layer caching uses the GitHub Actions cache (type=gha) with one scope per image. Tags, labels and
licences are on Container images.
Docs (docs.yml)
Section titled “Docs (docs.yml)”Builds this site (front/docs-site). It is separate from ci.yml and has two jobs:
check(Check and build) runs on pull requests that touch anything the site is made from:front/docs-site/**,docs/**, root*.mdfiles,api/packages/**,api/routes/**,api/app/Console/**,api/docs/**,admin/config/routes.ts,front/web/src/pages/**,front/ui/src/registry.ts,package.json,yarn.lockand the workflow itself; and on every push tomain. Steps:sync(generate pages from the repository),coverage --report(every package, admin route, learner route and topic type must be documented by a written page),typecheck(astro check) andbuildwith link validation.deploy(Deploy to GitHub Pages) runs aftercheckon pushes tomainonly. It reads the Pages origin and base path fromactions/configure-pages, builds withDOCS_SITEandDOCS_BASEset to them, uploadsfront/docs-site/distand publishes it withactions/deploy-pages. It needspages: writeandid-token: write, and the repository’s Pages source must be set to “GitHub Actions”.
Links are written without the base path; a build under a base path prefixes them in the HTML
and skips link validation, which the root build in check has already done.
DOCS_VALIDATE_LINKS=0 turns validation off for a local build.
Caching summary
Section titled “Caching summary”| Cache | Used by | Key |
|---|---|---|
Yarn cache (actions/setup-node, cache: yarn) |
js, H5P integration |
yarn.lock |
Turborepo local cache (.turbo/cache) |
js |
turbo-<os>-<sha>, restore from turbo-<os>- |
| Composer files cache | php |
api/composer.lock |
Docker layer cache (type=gha) |
Publish images | one scope per image |
Turborepo remote caching is not configured.
Before you push
Section titled “Before you push”corepack yarn turbo run typecheck lint build test --filter=<workspace>and the PHPUnit suites of the packages you touched (see Testing). The pre-commit hook runs lint-staged only (see Coding standards).