Continuous integration and package releases
Pipeline structure
Agon Atrium follows the Agon skeleton’s split GitLab configuration. The root .gitlab-ci.yml includes .gitlab/ci/workflow.gitlab-ci.yml, base, pre, prepare, lint, test, build, release, publish, deploy, and noop. Shared runtime, cache, verification, and delivery definitions live in base. Jobs use runners tagged agon. Docker jobs follow publica-negotia: the runner supplies Docker, Buildx, and Compose, .base:docker:login authenticates with the Harbor integration, and the build job calls Bake directly.
| Stage | Jobs and responsibility |
|---|---|
.pre | pre:cache:warmup:yarn populates the dependency cache for explicit runs, changes to the lockfile on develop, and schedules with CACHE_WARMUP=true. |
prepare | prepare:frontend:assets builds the showcase, starter, and /control-panel/ showcase fixture. |
lint | lint:frontend checks JavaScript, JSDoc, formatting, source headers, and EditorConfig. |
test | test:unit checks contracts and emits line coverage, Cobertura, HTML, and JUnit reports. test:e2e:browser checks the production showcase and subdirectory deployment. |
build | build:npm:package creates four archives and integrity proofs only after lint and tests pass. build:npm:package:smoke-test installs both templates outside the workspace, runs their publication commands, builds both applications, and runs the full production browser suite against the generated showcase and starter. |
release | release:env prepares the verified version and changelog notes. release:creation waits for maintainer approval and creates the GitLab release and version tag. |
publish | publish:artifacts:npm publishes the core, publisher, starter, and showcase to this project’s npm registry after release creation succeeds. |
noop | pipeline:noop keeps accepted pipelines visible when no optional scheduled work is selected. |
Node jobs use node:24-trixie so the cache warmup, Node jobs, and Ubuntu-based Playwright jobs all use glibc. They share one Yarn download cache with the yarn-cache-node24-glibc prefix and the package.json and yarn.lock fingerprints. This avoids restoring an Alpine musl cache into Playwright and downloading different native variants. The supported Node range is declared in package.json. .nvmrc remains an optional local development selection and does not pin CI to one patch release. Corepack resolves Yarn from the root packageManager field, and every Node job checks that Yarn version before immutable installation. The browser image must match the exact @playwright/test version and supply a compatible Node 24 runtime. The Debian image already supplies Git for source inventories and GNU tar for deterministic archives. Only dependency downloads are cached. Each job still installs its own node_modules. Builds, package archives, integrity metadata, JUnit reports, and browser diagnostics are job artifacts.
CI entry points live in src/scripts/ci/helpers/. Shared implementation belongs in src/scripts/ci/lib/. Quality rules stay in src/scripts/quality/, and fixture servers stay in src/tests/support/. playwright.ci.config.mjs starts and stops its own production fixture servers and emits GitLab JUnit reports.
The separate site build and hosting guide describes prepare:sites, test:sites:browser, build:docker:images:verify, image publication in the existing build stage, durable release attachments, and four manual Coolify deployments. Image publication follows protected-branch push/UI rules so playground remains independent of release creation. npm publication retains the stricter release-only rules below.
Workflow rules
Merge requests run verification. Protected branches run verification on push. A push to protected main that changes CHANGELOG.md or any of the four release package manifests selects the release candidate workflow. Protected tag pipelines verify the tagged source without creating another release or publishing again.
The skeleton’s pipeline:manual commit-message marker enables verification on an otherwise excluded branch push. Ordinary unprotected pushes are excluded, and pushes with an open merge request defer to the merge-request pipeline unless an earlier explicit rule applies. GitLab UI pipelines and schedules require protected refs. UI pipelines run verification. Scheduled verification requires NIGHTLY_BUILD=true, and scheduled cache maintenance requires CACHE_WARMUP=true. The workflow retains the skeleton’s duplicate-pipeline rule ordering, automatic cancellation, and descriptive pipeline names.
Release and npm delivery additionally require a hosted push pipeline on protected main with CI_RELEASE_PIPELINE=true. Local verification markers, merge requests, tags, schedules, UI pipelines, and unprotected manual-message branch pushes cannot publish npm packages. Delivery jobs are non-interruptible. Release creation and publication each have a resource group to serialize their respective operations.
GitLab project setup
- Push the repository to its intended GitLab project when ready and enable the package registry.
- Configure a runner tagged
agonwith access to Docker Hub, Microsoft’s Playwright registry, andhub.agon-partners-innovation.ch/ci-pipeline-images/glab:latest, matching the skeleton’s release tooling image. - Protect
developandmain, and protect the numeric release tags used here, for example1.*and subsequent major-version patterns. Release tags are1.0.0, without avprefix. Allow the release operator to create those protected tags. - Keep project/package visibility restricted according to the intended audience. The npm
accessfield is not a substitute for GitLab project permissions. - Optional schedules set
NIGHTLY_BUILD=truefor verification orCACHE_WARMUP=truefor cache maintenance.
Publication uses the predefined CI_JOB_TOKEN and ${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/npm/. No project URL, numeric ID, deploy password, or personal access token needs to be committed or configured for same-project publication. The helper requires HTTPS, verifies the release commit, checks all four packages for existing versions, and repacks all four sources to compare their SHA-512 digests with the archives that passed the consumer gate. Every preflight check completes before the first upload. Yarn then publishes the matching sources in dependency order: core, publisher, starter, showcase to that project endpoint with the latest distribution tag. See GitLab’s npm registry documentation and Yarn publication documentation.
The checked-in Yarn configuration uses a non-routable publication registry by default. The hosted helper supplies the real endpoint and job token only to its publication subprocess. The repository root remains private. @agon/atrium, @agon/atrium-publisher, @agon/atrium-starter, and @agon/atrium-showcase are separate publishable packages. Applications install the selected template and its dependencies, not a root workspace bundle.
Release procedure
- Set the same new stable semantic version in
src/atrium/package.json,src/publisher/package.json,src/starter/package.json, andsrc/showcase/package.jsonand document it in exactly one matchingCHANGELOG.mdsection. UseUNRELEASEDduring development and the repository’sNEW,BUG,SEC,REV, andDOCconventions. Before the release pipeline, commit the intended publication date in English long format, for example## [1.0.0] - 20 September 2026. - Review and merge the change to protected
main. The pipeline must pass lint, unit tests, production browser checks, package creation, and the independent packed-consumer check. - Inspect the extracted release notes and approve
release:creationin GitLab on the date recorded in the changelog. Preparation requires that date to match the current day inEurope/Zurichand copies it unchanged into the notes artifact. The manual creation job checks the date again immediately before creating the release and numeric version tag at the verified commit. Missing dates,UNRELEASED, other formats, and past or future dates fail validation. The configured GLab image must already includetzdatafor the Zurich timezone. The job fails explicitly when it is absent and never installs it at runtime. If publication is postponed to another day, update the changelog date in a new commit and rerun the pipeline. CI never dates or modifies the source changelog. publish:artifacts:npmruns after release creation succeeds. Find the package under the project’s package registry and install the exact version in consumers as described in integration.
Prereleases are deliberately excluded from this initial release flow. Each publication must use a new stable version. If publication fails before upload, fix the cause and retry that job while the verified artifacts remain available. A retry after a successful upload refuses to replace the existing version. The four uploads are not an atomic registry transaction. If some succeed before a later upload fails, duplicate-version protection prevents a blind whole-job retry. Inspect the registry and prepare a new coordinated version of all four packages through a fresh verified release. Do not overwrite or delete existing versions to bypass this guard. A created GitLab release can therefore exist before all packages are available. Inspect every package before announcing availability. Expired artifacts require a new verified pipeline before delivery.
After npm publication, publish:release:assets attaches all four package archives, both static distributions, checksums, and immutable image evidence to the release. Production deployments require that job and independently verify the release manifest. See site delivery.
Local verification
Use yarn check, yarn check:reference when the optional reference exists, and the production verification procedure. These perform no registry writes. Package archive construction and independent consumer builds are also ordinary local checks:
node src/scripts/ci/helpers/build-package.jsnode src/scripts/ci/helpers/build-consumer.jsThe archive helper validates synchronized versions and template dependency declarations. It writes agon-atrium.tgz, agon-atrium-publisher.tgz, agon-atrium-starter.tgz, and agon-atrium-showcase.tgz under .build/packages/, with version, commit, and SHA-512 proof files. These are development artifacts, not registry uploads.
The consumer helper verifies all four proofs, initializes two owned temporary applications outside the workspace, and resolves the four packages to their archives. It installs each template and runs its actual atrium-starter publish or atrium-showcase publish command, including dependency installation. It then builds both applications and a showcase subdirectory deployment. Outputs are retained under .build/consumer/starter/, .build/consumer/showcase/, and .build/consumer/showcase-subpath/. Temporary sources are removed in success and failure paths. Each application creates its own fresh lockfile. Repository jobs still use yarn install --immutable.
With ATRIUM_PACKED_CONSUMER=true, the CI Playwright configuration serves those outputs and runs every production workflow plus the starter check. This verifies template commands, nested assets, shared package exports, root/subdirectory routing, CSP, accessibility, and native components without workspace links.
Local GitLab CI
yarn ci:local executes the actual GitLab verification jobs in Docker, following the Agon skeleton’s local-runner pattern. .gitlab-ci-local.yml includes the hosted pipeline and disables delivery and placeholder jobs. The wrapper lives at src/scripts/ci/run-local-ci.js. Argument policy, process management, and orchestration live under src/scripts/ci/lib/. It provides named actions, validates the active graph, refuses unknown or delivery jobs, and checks that explicitly selected jobs are active before dispatch.
Use Linux or macOS with Node 24, Yarn 4.18.0, a running Docker daemon, Git, rsync, and Bash 4 or newer. Like the Agon webhook application, Atrium installs gitlab-ci-local 4.75.1 as a pinned development dependency through yarn install --immutable. No global emulator installation is needed. The wrapper resolves this installed package directly and checks it against the manifest. Verify upgrades with list, validate, a targeted job, and full before changing that contract. See the upstream prerequisites. Production dependencies are unchanged.
Create the ignored private variables file with owner-only permissions:
cp .gitlab-ci-local-variables-example.yml .gitlab-ci-local-variables.ymlchmod 0600 .gitlab-ci-local-variables.ymlKeep CI_LOCAL_PIPELINE: 'true'. CI_MANUAL_PIPELINE belongs to hosted manual workflows and is not needed locally. The wrapper explicitly forces the local marker for every invocation. Current verification jobs need no registry credentials. Never commit or print the private variables file. The wrapper checks its owner and refuses group/world permissions. Alternate variables files are supported with --variables-file PATH. Paths are normalized relative to the repository because the emulator does not reliably apply absolute variable-file paths.
The emulator copies working-tree contents for paths known to Git. The wrapper refuses untracked, non-ignored files so local CI cannot silently omit new source or configuration. Review newly added files and stage them or use git add --intent-to-add path/to/reviewed-file before local CI. Include files must also be known to Git. Never stage private variables, .env, generated assets, reports, or reference files to make a job see them. Artifacts move between jobs through the declared needs graph.
Inspect and validate before running jobs:
yarn ci:local --helpyarn ci:local lint --helpyarn ci:local listyarn ci:local validateThe root help lists supported actions. list shows the active jobs after rule evaluation. Every execution action repeats the graph and dependency checks before dispatch. Local rule-change evaluation is disabled because explicit local rules already select the verification graph, allowing use before an origin remote exists. This does not replace hosted workflow validation.
| Action | Selected GitLab jobs |
|---|---|
warmup | pre:cache:warmup:yarn |
frontend | prepare:frontend:assets |
lint | lint:frontend |
unit | test:unit, including coverage |
browser | test:e2e:browser and its preparation dependency |
package | build:npm:package and all quality prerequisites |
sites | test:sites:browser and its artifact prerequisites. |
smoke | build:npm:package:smoke-test and all package prerequisites |
full | Warmup, then one shared graph rooted at site browsers and the packed-consumer smoke job |
yarn ci:local lintyarn ci:local unityarn ci:local browseryarn ci:local fullThe full action runs all nine verification jobs, including both site artifact jobs. Shared preparation, lint, tests, and package prerequisites execute once. Artifact consumers use --needs internally. Target actions accept --variables-file PATH, --concurrency JOBS, and --dry-run. Concurrency defaults to one. An explicit positive integer permits more simultaneous jobs. Docker job workspaces are isolated, but they share the workstation’s CPU, memory, and disk. The wrapper sets --no-mount-cache so each job restores its own cache copy and respects the declared pull or pull-push policy. Concurrent installations can update their own copies without modifying another job’s dependency archives. Hosted jobs keep their existing parallel execution.
yarn ci:local full --dry-runyarn ci:local full --concurrency 2--dry-run inspects and validates the graph and prints allowlisted commands without running jobs. The validate action also accepts --dry-run, which prints its dependency-validation command after graph inspection. list accepts only --variables-file and help. Arbitrary emulator arguments and job names are rejected. Expected errors are concise, positive executor failure statuses are preserved, and Ctrl+C requests cleanup with a thirty-second limit before stopping only the owned child process group and returning status 130.
Each invocation requests cleanup of its job containers and networks. The wrapper never mounts the host Docker socket inside jobs. Dependency cache snapshots remain reusable under .gitlab-ci-local/cache/, alongside the emulator state. Job artifacts remain in .gitlab-ci-local/artifacts/, including coverage, application builds, browser reports, and package archives. They are not copied into the developer’s working tree. Do not use broad Docker prune commands to clean up a single local run.
Never replace the wrapper with an unqualified emulator invocation. Never execute release:*, publish:*, tagging, upload, or deployment jobs locally, including dry runs. Release and publication entry points may be imported only by isolated unit tests that mock every filesystem, network, and subprocess operation. Those tests inspect decisions and arguments without invoking delivery jobs or contacting registries. The local overlay disables delivery, and the allowlist provides a second refusal boundary when the pipeline evolves.
Coverage reports
yarn test:coverage and GitLab’s test:unit collect V8 unit-test coverage with the provider pinned to the Vitest version. The report includes all maintained JavaScript under src/atrium, src/publisher, src/showcase, src/starter, and src/scripts, including files not imported by a test. Generated output, dependencies, and the JSDoc-only types.js module are excluded. Test and configuration files are outside the coverage scope.
The job extracts the line percentage from the text summary for GitLab’s coverage badge and pipeline display. Cobertura enables merge-request line annotations, while the HTML and JSON summaries are retained alongside JUnit results under .reports/coverage/. Open .reports/coverage/index.html after a host run, or the corresponding directory under .gitlab-ci-local/artifacts/ after local CI. Reports are retained even when assertions fail. See Vitest coverage and GitLab coverage reports.
The percentage measures unit-test execution only. Playwright acceptance runs remain a separate gate and are not folded into it. vitest.config.mjs requires 100% statements, branches, functions, and lines for every included file. yarn check, hosted CI, and local CI enforce the same thresholds. Adding an untested module or leaving a branch uncovered fails the gate. Keep the complete source scope and avoid coverage-ignore directives.
Unit tests use Happy DOM for native element, storage, event, and focus contracts, controlled promises and clocks for cancellation and polling, and isolated platform mocks for helper failures. Browser tests still verify actual Alpine startup, rendering, CSP, accessibility, mobile layouts, and deployment. Coding-standard tests load the same ESLint configuration through Vitest so the rule implementation has one instrumented module instance.
README pipeline and coverage badges target develop in https://git.konzeptplus.ch/agon/innovation/themes/agon-atrium. The coverage badge selects test:unit and links to its retained HTML report. Live values appear after the corresponding GitLab pipeline runs. Local runs do not update hosted badges.
Author
Laurent Declercq l.declercq@agon-innovation.ch
License
Unless otherwise stated all source code is licensed under LGPL 2.1 and has the following copyright:
© 2026, Agon Partners Innovation AG, All rights reserved.The design material and the “Agon Ātrium” trademark is the property of their authors. Reuse of them without prior consent of their respective authors is strictly prohibited.
Version
Version: 20260921