Release process
This page documents the release workflow for maintainers. The user-facing version history lives on GitHub Releases.
Overview
The repo ships on four independent release lines, so a change to one host no longer forces a release of the others:
| Line | Tag | Covers | Publishes to |
|---|---|---|---|
npm (path ., the root component) | bpmn-modeler-v<version> | The publishable @miragon/bpmn-modeler package + the 10 libs it inlines | npm registry |
vscode (path apps/vscode-plugin) | vscode-v<version> | VS Code extension + Open VSX + Standalone desktop app | VS Code Marketplace, Open VSX, GitHub Release (DMG/NSIS/Flatpak) + Homebrew |
intellij (path apps/intellij-plugin) | intellij-v<version> | IntelliJ plugin | JetBrains Marketplace + updatePlugins.xml |
dmn-modeler (path packages/dmn-modeler) | dmn-modeler-v<version> | The publishable @miragon/dmn-modeler package + the two libs it inlines | npm registry |
VS Code, Open VSX and Standalone stay on one shared version because they are all built from the same frontend (libs/shared, the webviews). IntelliJ is a separate Gradle plugin and versions on its own. The @miragon/bpmn-modeler npm package (epic #1293) versions independently and holds the root release-please slot (see below and Release and publishing: release components and markers), so the root package.json version tracks the npm package, not the extension.
The flow has two phases, both automated:
- Prepare — release-please watches
mainand maintains one Release PR per line (separate-pull-requests: true). Each PR bumps its line's version files, updates that line'sCHANGELOG.md, and — when merged — cuts that line's tag- GitHub Release.
- Publish — merging a Release PR triggers
release-please.yml, which fans out only to that line's publish jobs. Eachpublish-*workflow is also runnable on its own viaworkflow_dispatchfor reruns.
How commits are routed
release-please routes each commit purely by its changed file paths, and only the root component (.) can watch multiple paths — it receives every commit minus its exclude-paths; all other components are single-directory. The root slot belongs to the npm package, the one artifact where semver correctness is a hard requirement and whose sources span packages/bpmn-modeler plus 10 inlined libs. The hosts are single-path components fed by sync markers for the sources they bundle from elsewhere (Release and publishing: release components and markers).
| Line | Natively watched paths | Bundled sources (marker-covered) | Marker file |
|---|---|---|---|
npm (root .) | packages/bpmn-modeler, the 10 inlined libs + remaining root files | — | — |
vscode (apps/vscode-plugin) | apps/vscode-plugin only | the four webviews, apps/standalone, libs/modeler-core, libs/shared, libs/standalone-extension, packages/dmn-modeler, the npm-package sphere | apps/vscode-plugin/BUNDLED_WEBVIEW |
intellij (apps/intellij-plugin) | apps/intellij-plugin only | apps/bpmn-webview, apps/deployment-webview, apps/modeler-bridge, libs/modeler-core, libs/shared, the npm-package sphere | apps/intellij-plugin/BUNDLED_WEBVIEW |
dmn-modeler (packages/dmn-modeler) | packages/dmn-modeler only | libs/modeler-types, libs/bpmn-i18n-extras | packages/dmn-modeler/BUNDLED_LIBS |
The 10 inlined libs (with packages/bpmn-modeler, the "npm-package sphere" — see INLINED_LIBS in packages/bpmn-modeler/vite.config.mts): libs/modeler-types, libs/bpmn-diff, libs/bpmn-clipboard, libs/bpmn-i18n-extras, libs/element-template-chooser, libs/append-menu, libs/model-navigation, libs/code-link, libs/inline-scripting, libs/flow-navigation. libs/modeler-core and libs/shared are host-side (never inlined into the npm package) and are excluded from the root along with libs/standalone-extension — they reach the hosts via markers.
Unwatched by everyone — apps/demo-webapp, docs, .github and all repo-root tooling files (yarn.lock, package.json, tsconfig.base.json, README.md, …). Tooling and demo churn never releases anything. The root component is a blocklist (exclude-paths has no globs), so a newly added root-level file or directory must also be added to exclude-paths in release-please-config.json — otherwise commits touching it start feeding the npm release line.
Sync markers
sync-release-markers.yml maintains three markers: the two host BUNDLED_WEBVIEW markers and the dmn-modeler line's BUNDLED_LIBS marker (bundled libs into a package, vs BUNDLED_WEBVIEW's webviews into a host — the DMN package inlines libs/modeler-types and libs/bpmn-i18n-extras). When a feat/fix lands on a bundled path, it commits one marker commit — touching the marker file of every component that bundles the change — whose subject mirrors the triggering PR title, e.g. fix(append-menu): restore flat menu entries … (#1428) [sync d512d6c]. That routes the real change, under its real title, into each host's release line and changelog. Key properties:
- The marker keeps the triggering title's type — a shared
featgives the hosts a minor bump under "New Features", afixa patch — but strips a breaking!: a package-breaking change is not a host-breaking change (Release and publishing: release components and markers). A host a change genuinely breaks must touch that host's own directory (see "Signalling severity" below). - One marker commit per shared PR (PRs are squash-merged, so the pushed head commit is the PR title). Markers are therefore always current and a release PR can never ship unattributed
feat/fixchanges. chore/docs/refactorshared changes are skipped — they ship with the next host release but get no changelog line. Deliberate noise/value cut.- A host is left out of the marker commit when the push already routes natively (it touched the host's own directory), so no double-bump.
- No marker push can cascade into more markers: the two host markers sit outside all trigger paths, and the DMN
BUNDLED_LIBSmarker is negated from its ownpackages/dmn-modeler/**trigger path. - Re-run red
Sync release markersruns. The ref update is fast-forward-only, so a run that races a concurrent push (or hits an API hiccup) fails without writing its marker — thatfeat/fixstays unattributed until the run is re-run. Re-running is safe: the same-sha guard makes it a no-op if the marker already landed. workflow_dispatchis a manual seed/escape hatch: it bypasses the type filter and writes a genericfix: sync bundled sourcesmarker for every component whose marker isn't already atHEAD(used once when this routing was adopted).
Signalling severity
Markers propagate feat/fix but never a breaking !, so let a ! PR touch exactly the lines it actually breaks — the squashed PR's changed files decide who bumps and how:
- Breaks npm-package consumers → the PR touches
packages/bpmn-modelerand/or its inlined libs → the npm line majors automatically; hosts get the change as a marker with the!stripped (a non-breakingfeat/fix). - Breaks vscode users (removed setting/command, changed behaviour) → the change touches
apps/vscode-plugin→!majors vscode automatically. Same for intellij andapps/intellij-plugin. - Breaks both → touch both in one
!PR → both major. - Mismatched severities in one PR → split into two PRs, or force the intended bump with a
Release-As: x.0.0footer commit touching that line's path.
Pipeline flow
Configuration
release-please is driven by two checked-in files:
release-please-config.json— four packages,separate-pull-requests: true:"."—release-type: node,component: bpmn-modeler,include-component-in-tag: true→ tagbpmn-modeler-v<version>. The root catch-all:exclude-pathsremovesapps,docs,.githuband the host-side libs, leaving the npm-package sphere.changelog-pathkeeps the changelog atpackages/bpmn-modeler/CHANGELOG.md; anextra-filesentry stampspackages/bpmn-modeler/package.json(the rootpackage.jsonis bumped natively and tracks the same version)."apps/vscode-plugin"—release-type: node,component: vscode,include-component-in-tag: true→ tagvscode-v<version>. Bumps its ownpackage.json+CHANGELOG.md;extra-fileswith a leading/(repo-root-relative) stampapps/standalone/package.jsonandlibs/standalone-extension/package.jsonto keep the lockstep version."apps/intellij-plugin"—release-type: simple,component: intellij,include-component-in-tag: true→ tagintellij-v<version>. Itsextra-filesstampgradle.properties(pluginVersion) via thegenericupdater, anchored by the# x-release-please-start-versionmarkers."packages/dmn-modeler"—release-type: node,component: dmn-modeler,include-component-in-tag: true→ tagdmn-modeler-v<version>. It lives in its own directory, so nochangelog-path/extra-filesare needed — the defaultpackages/dmn-modeler/{package.json,CHANGELOG.md}handling applies. (packages/dmn-modeleris already in the root'sexclude-paths, so a DMN-package change never feeds the npm line.)changelog-sectionsmap commit types (feat/fix/refactor/docs/chore) to changelog headings.
.release-please-manifest.json—{ ".": "…", "apps/vscode-plugin": "…", "apps/intellij-plugin": "…", "packages/dmn-modeler": "…" }, the current version of each line. release-please updates these on each release.
Releasing
1. Cut the release (prepare)
- Merge your feature/fix PRs into
mainwith Conventional-Commit messages. The changed file paths — not the commit scope — decide which line(s) a PR lands on (see "How commits are routed" above). - release-please opens/updates a Release PR per affected line (
chore(main): release <component> <version>). Review the version + changelog. - Merge the Release PR. It pushes the version bumps, tags the line (
vscode-v<version>orintellij-v<version>), and creates the GitHub Release — which triggers that line's publish jobs automatically.
2. Publishing (automatic, per line)
Merging a Release PR fans out via release-please.yml:
| Line | Auto-publishes | Notes |
|---|---|---|
vscode | publish-vscode-modeler.yml, publish-open-vsx-modeler.yml, release-standalone.yml | Marketplace + Open VSX + DMG/NSIS/Flatpak + Homebrew. |
intellij | publish-intellij.yml | Multi-platform ZIP → JetBrains Marketplace, refreshes docs/public/updatePlugins.xml. |
npm | publish-npm-modeler.yml (npm job) | Builds + packs + smoke-tests, then npm publish --provenance to the npm registry via Trusted Publishing (OIDC). |
dmn-modeler | publish-npm-modeler.yml (npm-dmn job) | Same workflow, parameterised for @miragon/dmn-modeler (workspace/package-dir/smoke-extra-deps: jsdom esbuild inputs); publishes to npm via Trusted Publishing. |
Each publish-* workflow is also runnable on its own via workflow_dispatch (pass the line's tag, e.g. tag: vscode-v1.4.0 / tag: intellij-v1.4.0) with a dry-run option for reruns.
publish-npm-modeler.ymldispatch is dry-run-only. The one workflow serves both npm packages — dispatch it with theworkspace/package-dir/smoke-extra-depsinputs (defaults target@miragon/bpmn-modeler; pass@miragon/dmn-modeler+packages/dmn-modeler+jsdom esbuildfor the DMN package — the DMN smoke bundles its consumer, since the dmn-js stack only resolves through a bundler). npm Trusted Publishing validates the top-level caller workflow filename, and the trusted publisher is configured forrelease-please.yml. A manualworkflow_dispatchof the npm workflow therefore always runs as a dry-run (build + pack + smoke +npm publish --dry-run), even if you setdry-run: false. To re-run a failed real publish, re-run thenpm(ornpm-dmn) job on the triggeringrelease-please.ymlrun — not the standalone workflow. The publish step is idempotent: it skips if the version is already on npm.
The
@miragon/create-append-c7polyfill that the BPMN webview depends on lives in its own repository and is consumed here as a published npm dependency — its release is cut there, not in this repo.
Per-host "what's live" overview
Each publish workflow records a GitHub deployment on success, to a per-host environment:
| Host | Environment |
|---|---|
| VS Code | vscode-marketplace |
| IntelliJ | jetbrains-marketplace |
| Standalone | standalone |
npm package (@miragon/bpmn-modeler) | npm-registry |
DMN npm package (@miragon/dmn-modeler) | npm-registry (shared) |
The repo Environments / Deployments page then shows the last-published version per host.
Artefact distribution
- VS Code → VS Code Marketplace and the Open VSX Registry.
- IntelliJ → the JetBrains Marketplace is the primary channel. The plugin ZIP also attaches to the
intellij-v<version>release anddocs/public/updatePlugins.xml(served via GitHub Pages) points the IDE's custom-repository updater at it — a legacy/fallback channel that still runs on every release but is no longer the recommended install path. - Standalone → DMG / NSIS installers and the x86_64 Flatpak bundle attach to the
vscode-v<version>release, and the Homebrew Cask in Miragon/homebrew-tap is updated forbrew upgrade --cask miragon-bpmn-modeler. Auto-update uses aelectron-updatergeneric feed: each publish also mirrors the installers +latest-mac.yml/latest.ymlonto a rollingstandalone-latestprerelease, which the app reads from a fixed URL (the repo-wide/releases/latestcan't be used — it is often an IntelliJ release with no DMG). Flatpak updates remain manual, so the bundle is not mirrored tostandalone-latest. The docs download page resolves the most recent release that carries an arm64 DMG, independent of the tag scheme. - npm packages → the
@miragon/bpmn-modelerand@miragon/dmn-modelernpm registry entries, published with provenance. Each release publishes the yarn-packed tarball with the npm CLI (npm publish <tarball> --provenance); yarnpackrewrites theworkspace:*ranges to real versions first.
npm Trusted Publishing (one-time setup)
Both npm lines use Trusted Publishing (OIDC) — no long-lived npm token lives in CI. npm only lets you configure a trusted publisher after the package exists, so each package's first 0.1.0 release is a one-time manual bootstrap publish (build → yarn workspace <workspace> pack → npm publish <tarball> --access public with a short-lived granular token, then the token is revoked). @miragon/dmn-modeler@0.1.0 needs the same bootstrap before its trusted publisher can be configured. Every release since publishes over OIDC.
The trusted publisher is configured per package on npmjs.com, but each one points at the same caller workflow (release-please.yml) and the shared npm-registry GitHub environment. Two constraints follow from how npm scopes it:
- The trusted publisher is bound to the top-level caller workflow (
release-please.yml) and thenpm-registryGitHub environment, sopublish-npm-modeler.ymlmust stay called fromrelease-please.ymlto publish for real (see the dispatch-is-dry-run-only note above). This holds for both thenpmandnpm-dmnfan-out jobs. id-token: writeis required in both the caller job (npm/npm-dmn) inrelease-please.ymlandpublish-npm-modeler.ymlitself, and npm ≥ 11.5.1 (the workflow upgrades npm, since Node 22 ships npm 10).