Thanks for your interest in @miragon/bpmnlint-plugin-rules! Contributions of all kinds are welcome —
bug reports, rule ideas, docs and code.
git clone https://github.com/Miragon/bpmnlint-rules.git
cd bpmnlint-rules
npm ci
npm testThis is a single-package TypeScript library, built with tsup and tested with Vitest. Node >= 22 is required.
npm run typecheck # tsc, no emit
npm run lint # ESLint
npm run format:check # Prettier, check only (npm run format to write)
npm run knip # knip: unused files, dependencies and exports
npm run lint:deps # dependency-cruiser architecture check
npm test # rule specs (Vitest, driving bpmnlint's RuleTester)
npm run build # tsup build into dist/ (ESM + CJS + d.ts)
npm run test:distro # pack + install the tarball, then drive the bpmnlint CLI + resolverAll of these run in CI on Node 22 for every push and pull request. Run them locally before opening a PR — the "Before opening a PR" section below is the exact sequence.
src/rules/common/ the bpmnlint built-in rules (structural base) — static-import glue
src/rules/camunda-7/ the camunda-compat C7 engine layer — static-import glue
src/rules/camunda-8/ the camunda-compat C8 engine layer — static-import glue
src/rules/miragon/ the ONLY folder whose rule source we author (one file per rule) + index
src/lib/ shared helpers — a leaf layer, pure functions over moddle data / geometry
src/resolver/ createBundledResolver: merges every folder's resolver entries
src/config/ engineConfig: getDefaultLintConfig / getRulesForEngine
src/presets/ ready-to-use recommended-for-modeling / -for-automation / all configs
test/ RuleTester specs, the resolver integration + sync specs, and fixtures
test/fixtures/rules/ valid.bpmn / invalid.bpmn pairs per rule (rendered to the docs SVGs)
docs/rules/ one documentation page per rule
Only src/rules/miragon/ holds rule source we author; common, camunda-7 and camunda-8 just
enumerate upstream rule modules (bpmnlint / camunda-compat) and map them to resolver cache keys —
test/rules/camunda-sync.spec.ts (and bpmnlint-sync.spec.ts) fail the build if an upstream bump adds or renames one.
Two boundaries are enforced by npm run lint:deps and matter more than they look:
- A rule may never import another rule. bpmnlint resolves and instantiates every rule
independently, so a rule-to-rule import would make one rule's behaviour depend on another's
internals — invisible from
.bpmnlintrc. Shared logic goes insrc/lib/. src/lib/stays a leaf. The helpers are pure functions over geometry and moddle data. That is what makes them testable without a linter around them.
npm run knip covers what dependency-cruiser structurally cannot: it works per export, not
per module, so it catches a helper that is exported but imported nowhere — and unused
dependencies in package.json.
- Rule file. Add
src/rules/miragon/<name>.ts. Itexport defaults a factory returning{ check(node, reporter) }— see bpmnlint's plugin docs. Keep any reusable, linter-free logic insrc/lib/(typed againstModdleElement/Reporterfromsrc/lib/moddle.ts). - Spec + fixtures. Add
test/rules/<name>.spec.tsusingbpmnlint'sRuleTester, and atest/fixtures/rules/<name>/{valid,invalid}.bpmnpair (the SVGs in the docs are rendered from them bynpm run docs:examples). Cover both sides: every exclusion the rule makes (a shape it deliberately ignores, a case it deliberately allows) needs a valid case whose geometry would otherwise trip it — otherwise the exclusion is untested and the next refactor silently drops it. Add the pair totest/rules/examples.spec.tsso the docs picture stays honest. The pair is also held to a wider bar bytest/rules/fixtures-self-lint.spec.ts(self-enrolling from the folder): linted throughbpmnlint:recommended+plugin:@miragon/rules/all,valid.bpmnmust be clean against every rule (a complete, labeled process on the<typePrefix>_<camelCase>id convention), andinvalid.bpmnmay carry only its one intentional defect — nothing flagged on any other element. A model that is "valid" only w.r.t. its own rule is a docs example that quietly teaches broken modeling. - Docs page. Add
docs/rules/<name>.mdexplaining what the rule catches, why it matters, and the valid/invalid example pair. - Wire it in. Import the factory in
src/rules/miragon/index.tsand add it tomiragonRuleFactories—resolverEntriespicks it up automatically, and the bundled resolver with it. - Severity. List it in
miragonAllaterrorand inmiragonRecommendedForAutomationatwarn. InmiragonRecommendedForModelingship itoff, unless it is a non-blocking layout hint safe on hand-drawn diagrams — those ship atwarn(all insrc/rules/miragon/index.ts). The tworecommended-for-*layers stay non-blocking (onlyalliserror), and the modeling layer must never block a modeler on execution-only conventions; consumers opt in viaplugin:@miragon/rules/all, the automation layer, or per rule.
- Deterministic. Decidable from the moddle tree or the DI coordinates. If it needs judgment, it is not a lint rule.
- No false positives. When in doubt, ship it as
warn, or leave it out of therecommended-for-*layers. - Not already covered. Check core bpmnlint and
bpmnlint-plugin-camunda-compatfirst. Engine-compatibility rules are not this plugin's job.
- Conventional Commits. Commit messages and PR titles follow
Conventional Commits (
feat:,fix:,docs:,refactor:,test:,chore:). Releases and the changelog are generated from them via release-please. For a lint preset "breaking" is not the same as for a library: only a change that can turn a consumer's green build red — one that introduces or raises anerror-level finding — is breaking (feat!:). Therecommended-for-*layers emit onlywarn, so adding a rule there is not breaking; raising a rule toerror(inall, or a consumer's config) is. Adding a rule atwarn, or lowering a severity, is not. - Exact dependency versions. No
^, no~..npmrcsetssave-exact=trueand CI verifies it — a floating transitive bump must never be able to change what the rules catch.
npm run typecheck
npm run lint
npm run format:check
npm run knip
npm run lint:deps
npm test
npm run build
npm run test:distroReleases are automated with release-please:
- Merge PRs with Conventional-Commit titles into
main. - release-please opens (and keeps updating) a release PR that bumps the version and the
CHANGELOG.mdfrom the accumulated commits. - Merging that release PR tags the release and triggers the publish job, which publishes to npm
using OIDC trusted publishing (tokenless, with provenance). No manual
npm publishand noNPM_TOKENare involved.
Use the GitHub issue templates. For a false positive or a missed defect, attaching the .bpmn
file is by far the fastest path to a fix — the rules are geometric, so the coordinates are the
bug report.