Skip to content

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:

LineTagCoversPublishes to
npm (path ., the root component)bpmn-modeler-v<version>The publishable @miragon/bpmn-modeler package + the 10 libs it inlinesnpm registry
vscode (path apps/vscode-plugin)vscode-v<version>VS Code extension + Open VSX + Standalone desktop appVS Code Marketplace, Open VSX, GitHub Release (DMG/NSIS/Flatpak) + Homebrew
intellij (path apps/intellij-plugin)intellij-v<version>IntelliJ pluginJetBrains Marketplace + updatePlugins.xml
dmn-modeler (path packages/dmn-modeler)dmn-modeler-v<version>The publishable @miragon/dmn-modeler package + the two libs it inlinesnpm 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:

  1. Prepare — release-please watches main and maintains one Release PR per line (separate-pull-requests: true). Each PR bumps its line's version files, updates that line's CHANGELOG.md, and — when merged — cuts that line's tag
    • GitHub Release.
  2. Publish — merging a Release PR triggers release-please.yml, which fans out only to that line's publish jobs. Each publish-* workflow is also runnable on its own via workflow_dispatch for 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).

LineNatively watched pathsBundled sources (marker-covered)Marker file
npm (root .)packages/bpmn-modeler, the 10 inlined libs + remaining root files——
vscode (apps/vscode-plugin)apps/vscode-plugin onlythe four webviews, apps/standalone, libs/modeler-core, libs/shared, libs/standalone-extension, packages/dmn-modeler, the npm-package sphereapps/vscode-plugin/BUNDLED_WEBVIEW
intellij (apps/intellij-plugin)apps/intellij-plugin onlyapps/bpmn-webview, apps/deployment-webview, apps/modeler-bridge, libs/modeler-core, libs/shared, the npm-package sphereapps/intellij-plugin/BUNDLED_WEBVIEW
dmn-modeler (packages/dmn-modeler)packages/dmn-modeler onlylibs/modeler-types, libs/bpmn-i18n-extraspackages/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 feat gives the hosts a minor bump under "New Features", a fix a 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/fix changes.
  • chore/docs/refactor shared 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_LIBS marker is negated from its own packages/dmn-modeler/** trigger path.
  • Re-run red Sync release markers runs. 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 — that feat/fix stays 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_dispatch is a manual seed/escape hatch: it bypasses the type filter and writes a generic fix: sync bundled sources marker for every component whose marker isn't already at HEAD (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-modeler and/or its inlined libs → the npm line majors automatically; hosts get the change as a marker with the ! stripped (a non-breaking feat/fix).
  • Breaks vscode users (removed setting/command, changed behaviour) → the change touches apps/vscode-plugin → ! majors vscode automatically. Same for intellij and apps/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.0 footer 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 → tag bpmn-modeler-v<version>. The root catch-all: exclude-paths removes apps, docs, .github and the host-side libs, leaving the npm-package sphere. changelog-path keeps the changelog at packages/bpmn-modeler/CHANGELOG.md; an extra-files entry stamps packages/bpmn-modeler/package.json (the root package.json is bumped natively and tracks the same version).
    • "apps/vscode-plugin" — release-type: node, component: vscode, include-component-in-tag: true → tag vscode-v<version>. Bumps its own package.json + CHANGELOG.md; extra-files with a leading / (repo-root-relative) stamp apps/standalone/package.json and libs/standalone-extension/package.json to keep the lockstep version.
    • "apps/intellij-plugin" — release-type: simple, component: intellij, include-component-in-tag: true → tag intellij-v<version>. Its extra-files stamp gradle.properties (pluginVersion) via the generic updater, anchored by the # x-release-please-start-version markers.
    • "packages/dmn-modeler" — release-type: node, component: dmn-modeler, include-component-in-tag: true → tag dmn-modeler-v<version>. It lives in its own directory, so no changelog-path/extra-files are needed — the default packages/dmn-modeler/{package.json,CHANGELOG.md} handling applies. (packages/dmn-modeler is already in the root's exclude-paths, so a DMN-package change never feeds the npm line.)
    • changelog-sections map 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) ​

  1. Merge your feature/fix PRs into main with 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).
  2. release-please opens/updates a Release PR per affected line (chore(main): release <component> <version>). Review the version + changelog.
  3. Merge the Release PR. It pushes the version bumps, tags the line (vscode-v<version> or intellij-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:

LineAuto-publishesNotes
vscodepublish-vscode-modeler.yml, publish-open-vsx-modeler.yml, release-standalone.ymlMarketplace + Open VSX + DMG/NSIS/Flatpak + Homebrew.
intellijpublish-intellij.ymlMulti-platform ZIP → JetBrains Marketplace, refreshes docs/public/updatePlugins.xml.
npmpublish-npm-modeler.yml (npm job)Builds + packs + smoke-tests, then npm publish --provenance to the npm registry via Trusted Publishing (OIDC).
dmn-modelerpublish-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.yml dispatch is dry-run-only. The one workflow serves both npm packages — dispatch it with the workspace /package-dir / smoke-extra-deps inputs (defaults target @miragon/bpmn-modeler; pass @miragon/dmn-modeler + packages/dmn-modeler + jsdom esbuild for 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 for release-please.yml. A manual workflow_dispatch of the npm workflow therefore always runs as a dry-run (build + pack + smoke + npm publish --dry-run), even if you set dry-run: false. To re-run a failed real publish, re-run the npm (or npm-dmn) job on the triggering release-please.yml run — not the standalone workflow. The publish step is idempotent: it skips if the version is already on npm.

The @miragon/create-append-c7 polyfill 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:

HostEnvironment
VS Codevscode-marketplace
IntelliJjetbrains-marketplace
Standalonestandalone
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 and docs/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 for brew upgrade --cask miragon-bpmn-modeler. Auto-update uses a electron-updater generic feed: each publish also mirrors the installers + latest-mac.yml / latest.yml onto a rolling standalone-latest prerelease, which the app reads from a fixed URL (the repo-wide /releases/latest can't be used — it is often an IntelliJ release with no DMG). Flatpak updates remain manual, so the bundle is not mirrored to standalone-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-modeler and @miragon/dmn-modeler npm registry entries, published with provenance. Each release publishes the yarn-packed tarball with the npm CLI (npm publish <tarball> --provenance); yarn pack rewrites the workspace:* 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 the npm-registry GitHub environment, so publish-npm-modeler.yml must stay called from release-please.yml to publish for real (see the dispatch-is-dry-run-only note above). This holds for both the npm and npm-dmn fan-out jobs.
  • id-token: write is required in both the caller job (npm / npm-dmn) in release-please.yml and publish-npm-modeler.yml itself, and npm ≥ 11.5.1 (the workflow upgrades npm, since Node 22 ships npm 10).