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 path | Runs |
|---|---|
modelers/<m>/** | the jobs of modeler <m> |
packages/**, .github/**, root package.json, package-lock.json, .npmrc, any other root file or folder | the 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 plustest:plugin(the repository-level checks intest/: the modeler-kit and release-config drift tests, every modeler's boundaries and the Dependabot auto-merge workflow), build andpublint; 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:browserande2e(in the Playwright image of the root'splaywrightpin),build(web app build andcheck:build, VSIX viapackage:vscode). Every job builds the shared packages first. The Node version is the floor of the root'sengines.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 everypackage.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 underxvfb, on the newest stable release and on the oldest thatengines.vscodeaccepts. 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:
| Job | Skipped by design when |
|---|---|
dependency-review | the run is no pull request |
shared-lint, shared-test, shared-e2e | changes succeeded and reports no shared change (shared=false) |
the five modeler-* jobs | changes 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 path | Component (tag <component>-v<version>) | Publishes |
|---|---|---|
packages/api, packages/shell | modeler-api, modeler-shell | npm |
modelers/<m>/packages/<p> | <m>-<p>, e.g. wardley-renderer | npm |
modelers/<m>/apps/vscode | <m>-vscode | VS Code Marketplace, Open VSX |
modelers/<m>/apps/webapp | <m>-webapp | nothing (Netlify deploys main) |
modelers/team-topologies | team-topologies | its 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 (
featminor,fixpatch, before 1.0 a breaking change is a minor). A commit counts for every component whose files it touches.BEGIN_COMMIT_OVERRIDE … END_COMMIT_OVERRIDEin the description replaces the commit message; theRelease notescheck makes sure every breaking change reaches the changelog. - Dependents release with their dependencies. The
node-workspaceplugin 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 anextra-filesentry of the pinned component: the pins in workspaces that are no component (eache2e, team-topologies' lockstep packages, the modeler-kit template) and every version and pin in the rootpackage-lock.json, because the plugin leaves the lockfile alone once anyextra-filesentry 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). Afternpm publishthe job waits up to five minutes for the registry to list the version: that, not the exit status ofnpm 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, thenvsce publish(secretVSCE_PUBLISH) orovsx publish(secretOVSX_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):
| Setting | Value |
|---|---|
| Repository | Miragon/modeler-suite, production branch main |
| Base directory | empty (the repository root) |
| Package directory | modelers/<m> |
| Build command | empty (from netlify.toml) |
| Publish directory | empty (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,mainand 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.ymlmerges 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 withGITHUB_TOKENstarts no workflow onmain(noci-ok, no CodeQL, no release-please). The token getscontentsandpull-requestsonly and is minted for npm updates only: GitHub merges a change to.github/workflowsonly for a token with theworkflowspermission, so GitHub Actions updates are merged by hand, and so is any ecosystem added todependabot.ymluntil the workflow names it. It runs onpull_request_target, because a run Dependabot starts onpull_requestgets only Dependabot secrets; that event runs the workflow frommainwith the Actions secrets, so the workflow must never check out or run code of the pull request. GitHub blockspull_request_targetin 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.