Skip to content

CI, releases and publishing ​

How the suite is checked, released and published: one CI with one merge gate, one release pull request for every package, extension and web app, and one Netlify site per web app. The workflows live in .github/workflows/, their logic in .github/scripts/, so it runs locally too.

CI and the merge gate ​

ci.yml runs on every pull request, every push to main, the merge queue and by hand. A new push to a pull request cancels its running checks; no other run is ever cancelled.

What a change runs ​

The first job, changes (.github/scripts/ci-paths.sh affected), compares the change with its base commit and decides which jobs it can break:

Changed pathRuns
modelers/<m>/**the jobs of modeler <m>
packages/**, .github/**, root package.json, package-lock.json, .npmrc, any other root file or folderthe shared jobs and all five modelers
plugins/**, test/**, .claude-plugin/**the shared jobs
docs/**, modelers/<m>/docs/**, Markdown at the root or at the top of a modeler, LICENSE, .husky/**nothing but the always-on jobs

Markdown elsewhere is no documentation here: skills under plugins/ feed drift tests, and a package or an extension ships its README. A run without a base commit (started by hand, a new branch) and a base that cannot be fetched run everything; so does any path no rule names.

Jobs ​

  • Shared (shared-lint, shared-test, shared-e2e): the type check of modeler-api, modeler-shell and the root tests; their unit tests plus test:plugin (the repository-level checks in test/: the modeler-kit and release-config drift tests, every modeler's boundaries and the Dependabot auto-merge workflow), build and publint; the shell's Playwright suite in four shards, each of which runs a quarter of the other tests and then a quarter of the saboteurs that run the whole kit (sharded together, those all fell into one shard).
  • Per modeler, a matrix over the modelers the change reaches, each job named <modeler> / <check>: lint (ESLint, type check, dependency-cruiser), test (build, unit tests), test:browser and e2e (in the Playwright image of the root's playwright pin), build (web app build and check:build, VSIX via package:vscode). Every job builds the shared packages first. The Node version is the floor of the root's engines.node, raised to the modeler's own (Node 24.15.0 for context-maps).
  • Always: format-check (Prettier over the whole suite), pin-check (exact pins in every package.json, every Playwright pin equal to the root's), dependency-review (pull requests), release-bootstrap (see the imported history).
  • VS Code smoke (vscode-smoke.yml), called once per modeler: the packaged extension in a real VS Code under xvfb, on the newest stable release and on the oldest that engines.vscode accepts. The workflow decides itself whether the change can reach that VSIX (ci-paths.sh vscode-smoke <modeler>); when it cannot, a job that succeeds stands in, so the smoke always reports.

ci-ok ​

ci-ok (.github/scripts/ci-gate.sh) is the one check the ruleset requires. It needs every other job of ci.yml (and fails if one is missing from its needs), and it fails on any failed or cancelled job and on every skip except these, each checked against its reason:

JobSkipped by design when
dependency-reviewthe run is no pull request
shared-lint, shared-test, shared-e2echanges succeeded and reports no shared change (shared=false)
the five modeler-* jobschanges succeeded and reports no modeler (modelers=[])

A skip after a failed or cancelled changes is never accepted, and the smoke never skips.

Weekly smoke ​

vscode-smoke-weekly.yml runs every modeler's smoke on Mondays; a failure opens, or comments on, the issue "VS Code smoke: the weekly run failed".

Releases ​

release-please keeps one release pull request, chore: release main, for the whole suite. Every package keeps its own version, changelog and tag:

Component pathComponent (tag <component>-v<version>)Publishes
packages/api, packages/shellmodeler-api, modeler-shellnpm
modelers/<m>/packages/<p><m>-<p>, e.g. wardley-renderernpm
modelers/<m>/apps/vscode<m>-vscodeVS Code Marketplace, Open VSX
modelers/<m>/apps/webapp<m>-webappnothing (Netlify deploys main)
modelers/team-topologiesteam-topologiesits two npm packages and its extension

for <m> in event-storming, context-maps and value-chain; Wardley Maps lives in modelers/wardley-maps and keeps its released components wardley-<p>, wardley-vscode and wardley-webapp. Team Topologies stays in lockstep: one component whose extra-files move every one of its packages, the plugin and the marketplace entry to the same version, with its changelog in modelers/team-topologies/CHANGELOG.md. Every other changelog stays in its package.

  • The squash title decides. A pull request lands as one commit whose message is its title and description; the title's type decides the bump (feat minor, fix patch, before 1.0 a breaking change is a minor). A commit counts for every component whose files it touches. BEGIN_COMMIT_OVERRIDE … END_COMMIT_OVERRIDE in the description replaces the commit message; the Release notes check makes sure every breaking change reaches the changelog.
  • Dependents release with their dependencies. The node-workspace plugin bumps every component that depends on a released one (a patch, with a "Dependencies" note), so a fix in modeler-shell releases every extension and web app that bundles it.
  • Pins and the lockfile. The plugin moves a component's pins in other components' package.json. Everything else is an extra-files entry of the pinned component: the pins in workspaces that are no component (each e2e, team-topologies' lockstep packages, the modeler-kit template) and every version and pin in the root package-lock.json, because the plugin leaves the lockfile alone once any extra-files entry points into it. The release-config drift test (test/release-config.test.ts) fails when one is missing or stale.

The imported history ​

The modelers came with their full history, whose feat and fix commits must not reach the new changelogs. bootstrap-sha in release-please-config.json names the migration's last commit before CI and releases moved to the root: as long as a component has no release in this repository, release-please collects commits only back to it. Once every component has released here it has no effect, and it can go.

The release-bootstrap job guards it: the commit has to be on the branch (else release-please would read the whole history), and no releasable commit to a component with a release here may lie before it or beside it (one merged to main next to the migration), because release-please could walk past such a commit. Land the migration with its history (a merge, not a squash), after releasing anything still unreleased on main.

Publishing ​

release-please.yml runs on every push to main. When a release pull request is merged, it tags each released component, creates its GitHub release and publishes exactly what released (.github/scripts/release-plan.mjs: every public workspace and every extension at or below a released component's path):

  • npm, one job per package in the environment npm, with OIDC trusted publishing and provenance, on Node 24 (npm 11.5.1 or later). A version already on the registry is skipped; a package that was never published fails with a hint (publish it once by hand, then attach the trusted publisher). After npm publish the job waits up to five minutes for the registry to list the version: that, not the exit status of npm publish, is the verdict.
  • VS Code, one job per extension and marketplace in the environment vscode-marketplace: build, vsce package, the smoke test of that very VSIX in VS Code, then vsce publish (secret VSCE_PUBLISH) or ovsx publish (secret OVSX_PUBLISH).

Started by hand with dry_run, the workflow releases nothing: it packs every npm package (npm publish --dry-run) and packs and smoke-tests every extension.

Web apps on Netlify ​

Each web app is a Netlify site configured by modelers/<m>/netlify.toml: Netlify installs the workspace at the root, builds the shared packages and the web app, and publishes modelers/<m>/apps/webapp/dist. Its ignore command skips the build unless the modeler, the shared packages or the root install changed. Site settings (Site configuration → Build & deploy):

SettingValue
RepositoryMiragon/modeler-suite, production branch main
Base directoryempty (the repository root)
Package directorymodelers/<m>
Build commandempty (from netlify.toml)
Publish directoryempty (from netlify.toml)

Other workflows ​

  • pr-title.yml (validate-pr-title): the title is a Conventional Commit, and a single-commit pull request's commit says the same.
  • codeql.yml: JavaScript and TypeScript, on pull requests, main and weekly.
  • dependabot.yml: npm weekly (minor and patch grouped, each major on its own, the suite's @miragon/* workspaces ignored), GitHub Actions monthly. dependabot-automerge.yml merges green npm patch and minor updates once auto-merge is enabled for the repository. It enables auto-merge with a token of the release-please app: GitHub merges in the name of whoever enabled it, and a merge made with GITHUB_TOKEN starts no workflow on main (no ci-ok, no CodeQL, no release-please). The token gets contents and pull-requests only and is minted for npm updates only: GitHub merges a change to .github/workflows only for a token with the workflows permission, so GitHub Actions updates are merged by hand, and so is any ecosystem added to dependabot.yml until the workflow names it. It runs on pull_request_target, because a run Dependabot starts on pull_request gets only Dependabot secrets; that event runs the workflow from main with the Actions secrets, so the workflow must never check out or run code of the pull request. GitHub blocks pull_request_target in public repositories from 2 December 2026 unless an Actions policy (Settings → Actions → Policies) allows it, so the suite needs one that allows the event for this workflow.