Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,17 @@ const linter = new Linter({
const results = await linter.lint(rootElement);
```

`engine` picks the Camunda deployability layer + typed moddle; `preset` picks the Miragon opinion
layer independently — `'modeling'` (layout hints only, safe on hand-drawn diagrams) or
`'automation'` (every Miragon rule at `error`). When `preset` is omitted it defaults to
`'automation'` for an engine-bound config and `'modeling'` otherwise. Pass both to decouple them —
e.g. a modeler that wants Camunda 8's typed properties and deployability checks but the relaxed
modeling opinion layer:

```ts
getDefaultLintConfig({ engine: 'c8', preset: 'modeling' });
```

Results are keyed by rule; each finding has a `category` (`error` | `warn`). Block on any error — a
CI gate, or the reject signal that sends an AI agent back to fix its output:

Expand Down
50 changes: 37 additions & 13 deletions src/config/engineConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@
*
* The zero-config default is layered, mirroring Camunda's `@camunda/linting`:
* - `bpmnlint:recommended` — shared generic BPMN correctness, identical for every engine;
* - the Miragon opinion layer (see `rules/miragon`), chosen by context: an engine-less document is
* a *modeling* diagram (`recommended-for-modeling`), an engine-bound one is *automation*
* (`recommended-for-automation`, every Miragon rule at `error`);
* - the Miragon opinion layer (see `rules/miragon`), chosen by `preset`: a *modeling* diagram
* (`recommended-for-modeling`, layout hints only, safe on hand-drawn diagrams) or *automation*
* (`recommended-for-automation`, every Miragon rule at `error`). When `preset` is omitted it is
* derived from `engine` for backwards compatibility — an engine-bound document defaults to
* automation, an engine-less one to modeling;
* - `plugin:camunda-compat/<platform-version>` — the engine deployability matrix, added **only**
* when an engine is given. An engine-less document gets the structural + modeling base alone.
* when an engine is given, *independently* of the preset. This lets a modeler ask for the
* engine's typed moddle + deployability rules while keeping the relaxed modeling opinion layer.
*
* The engine layer's moddle descriptor is embedded directly (not referenced by module path) so a
* config-less host can still parse the typed `zeebe:`/`camunda:` properties those rules inspect.
Expand All @@ -23,42 +26,63 @@ import * as camunda8 from '../rules/camunda-8';
/** A supported execution platform. `undefined` selects the structural base only. */
export type Engine = 'c7' | 'c8';

/**
* The Miragon opinion layer to apply, decoupled from the engine:
* - `modeling` — layout hints only, safe on hand-drawn diagrams;
* - `automation` — every Miragon rule at `error`, the execution-ready bar.
*/
export type Preset = 'modeling' | 'automation';

/** The Miragon opinion layers, referenced by name (resolved from the bundled resolver). */
const MIRAGON_LAYER_MODELING = `plugin:${MIRAGON_NAME}/recommended-for-modeling`;
const MIRAGON_LAYER_AUTOMATION = `plugin:${MIRAGON_NAME}/recommended-for-automation`;

/** The structural base + the modeling Miragon layer — the default for an engine-less document. */
const MODELING_EXTENDS = [structuralLayer, MIRAGON_LAYER_MODELING];

/** The structural base + the automation Miragon layer — the base for any engine-bound document. */
const AUTOMATION_EXTENDS = [structuralLayer, MIRAGON_LAYER_AUTOMATION];
const MIRAGON_LAYER: Record<Preset, string> = {
modeling: MIRAGON_LAYER_MODELING,
automation: MIRAGON_LAYER_AUTOMATION,
};

export interface DefaultLintConfigOptions {
/**
* The execution platform whose deployability layer + typed moddle to add. Omit for the
* structural + Miragon base alone.
*/
engine?: Engine;
/**
* The Miragon opinion layer. Defaults to `automation` when an `engine` is given and `modeling`
* otherwise — pass it explicitly to decouple the two, e.g. a modeler wanting the engine's typed
* moddle and deployability rules but the relaxed modeling opinion layer.
*/
preset?: Preset;
}

/**
* The zero-config default config for a host with no `.bpmnlintrc`: structural base + Miragon
* opinion, plus the matching Camunda engine layer and moddle extension when `engine` is given.
*/
export function getDefaultLintConfig(options: DefaultLintConfigOptions = {}): BpmnlintConfig {
switch (options.engine) {
const { engine } = options;
const preset: Preset = options.preset ?? (engine ? 'automation' : 'modeling');

const base = [structuralLayer, MIRAGON_LAYER[preset]];

switch (engine) {
case 'c7':
return {
extends: [...AUTOMATION_EXTENDS, camunda7.extendsLayer],
extends: [...base, camunda7.extendsLayer],
moddleExtensions: {
[camunda7.moddleExtension.prefix]: camunda7.moddleExtension.descriptor,
},
};
case 'c8':
return {
extends: [...AUTOMATION_EXTENDS, camunda8.extendsLayer],
extends: [...base, camunda8.extendsLayer],
moddleExtensions: {
[camunda8.moddleExtension.prefix]: camunda8.moddleExtension.descriptor,
},
};
default:
return { extends: [...MODELING_EXTENDS] };
return { extends: base };
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export type { Resolver } from './resolver/Resolver';

// Config building — the zero-config default, layered per engine.
export { getDefaultLintConfig, getRulesForEngine } from './config/engineConfig';
export type { Engine, DefaultLintConfigOptions } from './config/engineConfig';
export type { Engine, Preset, DefaultLintConfigOptions } from './config/engineConfig';

// Ready-to-use presets (structural base + Miragon layer).
export { recommendedForModeling, recommendedForAutomation } from './presets/recommended';
Expand Down
30 changes: 29 additions & 1 deletion test/rules/resolver-integration.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Linter from 'bpmnlint/lib/linter';

import { createBundledResolver, getDefaultLintConfig } from '../../src';
import type { Engine } from '../../src';
import type { DefaultLintConfigOptions, Engine } from '../../src';
import { model } from '../support/model';

/**
Expand All @@ -26,6 +26,10 @@ async function lintKeys(engine?: Engine): Promise<string[]> {
return Object.keys(await linter.lint(parsed.root));
}

function extendsOf(options: DefaultLintConfigOptions): string[] {
return getDefaultLintConfig(options).extends as string[];
}

describe('bundled resolver + engine config, end to end', () => {
it('fires the structural base under every engine', async () => {
for (const engine of [undefined, 'c7', 'c8'] as const) {
Expand All @@ -48,3 +52,27 @@ describe('bundled resolver + engine config, end to end', () => {
expect(await lintKeys('c8')).toContain('camunda-compat/implementation');
});
});

describe('getDefaultLintConfig preset selection', () => {
const MODELING = 'plugin:@miragon/rules/recommended-for-modeling';
const AUTOMATION = 'plugin:@miragon/rules/recommended-for-automation';

it('defaults the preset from the engine — modeling engine-less, automation engine-bound', () => {
expect(extendsOf({})).toContain(MODELING);
expect(extendsOf({ engine: 'c8' })).toContain(AUTOMATION);
});

it('honours an explicit preset regardless of the engine', () => {
expect(extendsOf({ preset: 'automation' })).toContain(AUTOMATION);
expect(extendsOf({ engine: 'c8', preset: 'modeling' })).toContain(MODELING);
});

it('keeps the engine deployability layer when the modeling preset is forced', () => {
const extendsLayers = extendsOf({ engine: 'c7', preset: 'modeling' });
expect(extendsLayers).toContain(MODELING);
expect(extendsLayers.some((layer) => layer.startsWith('plugin:camunda-compat/'))).toBe(true);
expect(
getDefaultLintConfig({ engine: 'c7', preset: 'modeling' }).moddleExtensions,
).toHaveProperty('camunda');
});
});