Skip to content

Deployment

This document describes how @xmachines/xmachines-js packages are built, versioned, and published to npm. All releases are automated through semantic-release running inside a GitLab CI pipeline. No manual npm publish commands are required for normal releases.


Deployment Targets

All @xmachines/* packages are published to the public npm registry (registry.npmjs.org). Every package uses "publishConfig": { "access": "public" } so scoped packages are accessible without an npm org subscription. See the Published Packages table for the full list.

TargetConfig FilePurpose
npm registry.releaserc.json (per-package @semantic-release/npm plugin entries)Publish all public packages
GitLab Releases.releaserc.json (@semantic-release/gitlab plugin)Attach tarball artifacts to the GitLab release tag
GitLab CI.gitlab-ci.ymlTrigger builds, tests, and releases on push / MR / tag

The root package.json is marked "private": true and is never published to npm.


Published Packages

The following packages are published on every release (derived from .releaserc.json):

PackageDirectory
@xmachines/sharedpackages/shared
@xmachines/playpackages/play
@xmachines/play-actorpackages/play-actor
@xmachines/play-signalspackages/play-signals
@xmachines/play-routerpackages/play-router
@xmachines/play-dompackages/play-dom
@xmachines/play-dom-routerpackages/play-dom-router
@xmachines/play-reactpackages/play-react
@xmachines/play-react-routerpackages/play-react-router
@xmachines/play-solidpackages/play-solid
@xmachines/play-solid-routerpackages/play-solid-router
@xmachines/play-sveltepackages/play-svelte
@xmachines/play-sveltekit-routerpackages/play-sveltekit-router
@xmachines/play-svelte-spa-routerpackages/play-svelte-spa-router
@xmachines/play-tanstack-react-routerpackages/play-tanstack-react-router
@xmachines/play-tanstack-solid-routerpackages/play-tanstack-solid-router
@xmachines/play-vuepackages/play-vue
@xmachines/play-vue-routerpackages/play-vue-router
@xmachines/play-xstatepackages/play-xstate
@xmachines/docspackages/docs

Example demo packages (under packages/*/examples/demo) are not published to npm ("npmPublish": false) but are still packed into release tarballs and attached as GitLab release assets for testing.

Each published package includes only the directories listed in its files field: dist, README.md, and LICENSE (or equivalent for packages without a compilation step).


Build Pipeline

CI Pipeline Stages

Pipeline runs are triggered by .gitlab-ci.yml under the following conditions:

TriggerPipeline runs
Push to main / master / beta / pre/rcFull pipeline including release
Merge requestFull pipeline (release job skipped or manual)
Tag push ($CI_COMMIT_TAG)Full pipeline

The pipeline includes two reusable CI components:

  • to-be-continuous/node/gitlab-ci-node@5.1.2 — handles install, lint, build, test, and audit
  • to-be-continuous/semantic-release/gitlab-ci-semrel@4.1.0 — runs semantic-release on eligible branches

The node-build job runs with:

Terminal window
npm run build # tsc --build (TypeScript composite build)
npm test:coverage # vitest run --coverage (with JUnit + Cobertura reporters)

Coverage is extracted from the node-build job via the regex:

/All files[^|]*\|[^|]*\s+([\d\.]+)/

JUnit results are published as reports/junit.xml and Cobertura coverage as reports/coverage/cobertura-coverage.xml.

Release Job Detail

The semantic-release CI job is configured with:

semantic-release:
id_tokens:
NPM_ID_TOKEN:
aud: "npm:registry.npmjs.org"
artifacts:
when: always
expire_in: 1 week
paths:
- dist/releases/*.tgz

The job uses a GitLab OIDC id token (NPM_ID_TOKEN) scoped to npm:registry.npmjs.org rather than a static NPM_TOKEN.

The semantic-release command is called with --no-ci to allow it to run inside CI without triggering its own CI-environment mode.

Release Smoke Test Job

The release-pack-smoke job is defined in .gitlab-ci.yml and runs on the same trigger rules as other CI jobs (automatically on main pushes and tags, manually on MRs):

Terminal window
npm run build
node scripts/release-pack-smoke.mjs

scripts/release-pack-smoke.mjs:

  1. Reads .releaserc.json to find all @semantic-release/npm plugin entries where npmPublish is not false.
  2. For each publishable package: runs npm pack --json in the package directory.
  3. Creates a temporary directory, runs npm init -y, and installs the local tarball with --ignore-scripts.
  4. Asserts the install succeeds — any missing files, broken exports, or pack-time errors surface here.
  5. Cleans up temp directories and tarballs after each check.

This smoke test runs before the actual publish so packaging problems are caught in CI rather than on npm.


Semantic Release Process

Branch Model

Releases are driven by commit history on protected branches:

BranchChannelTag formatBehaviour
mainstable (default)v1.2.3Full production release
betabetav1.2.3-beta.NBeta pre-release
pre/rcpre/rcv1.2.3-rc.NRelease candidate pre-release

Tag format is v${version} (e.g., v1.0.0-beta.46).

Prepare Phase

Before any package is published, semantic-release runs the prepareCmd from .releaserc.json:

Terminal window
npm install --prefix . @npmcli/arborist
npm run apply-patches
node scripts/set-workspace-versions.mjs ${nextRelease.version}
npm run build
npm run typedoc -w @xmachines/docs -- --gitRevision v${nextRelease.version}
npm run format -w @xmachines/docs

Step by step:

  1. Install Arborist — ensures the workspace introspection tool is available.
  2. Apply patches — runs patch-package to apply any patches in the patches/ directory.
  3. Sync versionsscripts/set-workspace-versions.mjs sets the same release version across every package.json in the workspace (root + all packages), including cross-package workspace dependency ranges.
  4. Build — runs tsc --build to produce compiled dist/ output in all packages.
  5. Generate API docs — runs TypeDoc to regenerate packages/docs/api/ at the release git revision.
  6. Format docs — runs oxfmt on the docs package to ensure consistent formatting.

Publish Phase

Each publishable package is published in sequence via individual @semantic-release/npm plugin entries. Each entry specifies:

  • pkgRoot — the package directory relative to the workspace root
  • tarballDirdist/releases/ where the packed .tgz is saved

Example (from .releaserc.json):

["@semantic-release/npm", { "pkgRoot": "packages/play", "tarballDir": "dist/releases" }]

Post-Release Phase

After all packages are published:

  1. @semantic-release/git commits updated files back to the repository:

    • CHANGELOG.md
    • package.json and package-lock.json (root)
    • packages/*/{package.json,examples/*/package.json} (all workspace packages)
    • packages/docs/api/** (generated API documentation)

    Commit message format:

    chore(release): <version>
    <release notes>
    [skip ci]
  2. @semantic-release/gitlab creates a GitLab Release entry attached to the version tag, with all dist/releases/*.tgz tarballs as downloadable assets labelled “Release tarballs”.

  3. @semantic-release/changelog updates CHANGELOG.md with generated release notes.


Version Synchronisation

All packages in the monorepo share a single version at all times. This is enforced during release by scripts/set-workspace-versions.mjs, which:

  • Sets version in every workspace package.json (including the root) to the new release version.
  • Rewrites dependencies, devDependencies, peerDependencies, and optionalDependencies ranges for any @xmachines/* workspace package to the exact new version (no ^ or ~ prefix — a strict pinned version is written).

This means packages always depend on the exact same version of sibling packages that was released together.


Credentials and Protected Variables

VariableSourcePurpose
NPM_ID_TOKENGitLab CI OIDC (auto-generated per job)Authenticates semantic-release to npm registry via OIDC token exchange
GL_TOKEN / GITLAB_TOKEN GitLab CI protected variableAuthenticates @semantic-release/gitlab for release creation and git push back
CI_JOB_TOKENGitLab CI built-inUsed by the to-be-continuous components for GitLab API calls

No static npm token is committed to the repository. The NPM_ID_TOKEN is a short-lived OIDC token generated for each CI job with audience npm:registry.npmjs.org.

To publish packages, a maintainer must:

  1. Have push access to a release-eligible branch (main, beta, pre/rc).
  2. Ensure the GitLab project CI/CD variables include the required tokens.

Artifacts

Release tarballs are stored as GitLab CI artifacts for 1 week after each pipeline:

dist/releases/*.tgz

The same tarballs are attached to the GitLab Release entry as permanent downloadable assets. Tarballs are not committed to git (the dist/ directory is gitignored).

The API documentation (packages/docs/api/) is committed to git by the post-release git commit and is the only generated artifact tracked in version control.


Rollback Procedure

There is no automated rollback mechanism. To revert a release:

  1. npm deprecate (preferred) — deprecate the bad version on npm so it is hidden from installs:

    Terminal window
    npm deprecate @xmachines/play@<bad-version> "Deprecated: use <good-version>"

    Repeat for each affected package.

  2. npm unpublish — unpublish the bad version if it was published within 72 hours and no dependents exist:

    Terminal window
    npm unpublish @xmachines/play@<bad-version>
  3. Patch release — push a fix: commit to main (or the relevant branch) to trigger a patch version release via semantic-release. This is the preferred long-term approach for production issues.

  4. GitLab Release — delete or edit the GitLab Release entry via the GitLab UI or API to remove broken tarballs. This does not affect what is on npm.


Local Release Dry-Run (Manual)

To preview what semantic-release would do without publishing:

Terminal window
# Install dependencies first
npm ci
# Run semantic-release in dry-run mode
npx semantic-release --dry-run --no-ci

To test package packaging locally (the same check run by release-pack-smoke in CI):

Terminal window
npm run build
node scripts/release-pack-smoke.mjs

This packs every publishable package, installs each tarball in a temporary project, and verifies the install succeeds.

To manually version and pack a single package for local inspection:

Terminal window
# From the repo root
npm run build -w packages/play
npm pack --workspaces --workspace=packages/play

Do not run npm publish manually. All publishing must go through semantic-release in CI to keep CHANGELOG.md, git tags, and npm versions in sync.


Monitoring

The project does not include application runtime monitoring (no Sentry, Datadog, or OpenTelemetry) — it is a library monorepo, not a deployed service.

Post-release health checks:

  • npm package availability — verify new versions appear on https://www.npmjs.com/package/@xmachines/<pkg> after the pipeline completes.
  • GitLab pipeline status — monitor the semantic-release job in the GitLab CI UI for the release commit.
  • GitLab Releases page — confirm the release tag and tarball assets appear at https://gitlab.com/xmachin-es/xmachines-js/-/releases.
  • packages/docs/api/ commit — confirm the post-release git commit updated API docs on the target branch.