Skip to content
Closed
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
3 changes: 3 additions & 0 deletions .bpmnlintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": "bpmnlint:recommended"
}
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,6 @@ composer.lock

# npm
node_modules/

# generated lint config bundle (rebuilt by build:vendor)
/build/generated/
58 changes: 58 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,45 @@ Renders using the bpmn.io js libraries within dokuwiki:

Refer to this page for details: <https://www.dokuwiki.org/plugin:bpmnio>

## Usage

Embed a diagram by wrapping inline XML (or referencing a media file) in a
`<bpmnio>` block:

```
<bpmnio type="bpmn">
...BPMN 2.0 XML...
</bpmnio>

<bpmnio type="bpmn" src="wiki:diagrams:zoning-map-amendment.bpmn" zoom="0.8" />
```

### Attributes

| Attribute | Values | Description |
| --------- | ------ | ----------- |
| `type` | `bpmn` (default), `dmn` | Diagram kind. |
| `src` | media id | Render a stored media file instead of inline XML. |
| `zoom` | positive number | Scale factor applied to the rendered diagram. |
| `lint` | `on`, `off`, `inactive` | Per-diagram [bpmnlint](https://github.com/bpmn-io/bpmnlint) behaviour (BPMN only). |

### Linting BPMN diagrams

BPMN diagrams ship with an embedded [bpmnlint](https://github.com/bpmn-io/bpmnlint)
linter via [bpmn-js-bpmnlint](https://github.com/bpmn-io/bpmn-js-bpmnlint). A
toggle button appears in the corner of the canvas; clicking it overlays
clickable error/warning badges on the offending elements, each opening the list
of issues for that element. This works on rendered wiki pages (read-only viewer)
as well as in the editor.

The `lint` attribute controls the default state per diagram:

* `lint="on"` — overlays are shown immediately.
* `lint="inactive"` — the toggle button is present but overlays start hidden.
* `lint="off"` — the linter is not loaded for that diagram (no button).
* omitted — viewer diagrams default to the button being present but inactive;
the editor defaults to active so authors get immediate feedback.

## Development

### Prerequisites
Expand Down Expand Up @@ -54,6 +93,25 @@ cd /path/to/dokuwiki
php vendor/bin/phpunit --group plugin_bpmnio
```

### Customising the BPMN linter

The lint rules are defined in [`.bpmnlintrc`](.bpmnlintrc) at the repo root and
are compiled into the committed viewer/modeler bundles at build time (bpmnlint
cannot resolve rules in the browser). The default config extends
[`bpmnlint:recommended`](https://github.com/bpmn-io/bpmnlint#built-in-rules).
After editing `.bpmnlintrc`, rebuild the bundles:

```bash
npm run build:vendor # or ./update-vendor.sh
```

To add project-specific rules, create a local
[bpmnlint plugin](https://github.com/bpmn-io/bpmnlint#plugins) (a
`bpmnlint-plugin-<name>` package exporting `rules` and a `recommended` config),
add it to `package.json` (e.g. as a `file:` dependency), reference it from
`.bpmnlintrc` via `plugin:<name>/recommended`, and rebuild. The packing step
inlines the resolved rules so no resolver is needed at runtime.

### Updating vendor libraries

The committed `vendor/` bundles are generated locally from the npm packages
Expand Down
65 changes: 65 additions & 0 deletions _test/syntax_plugin_bpmnio_bpmnio.test.php
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,71 @@ public function test_syntax_ignores_invalid_zoom_attribute()
$this->assertStringNotContainsString('data-zoom=', $xhtml);
}

public function test_syntax_lint_attribute()
{
$info = array();

$input = <<<IN
<bpmnio type="bpmn" lint="on">
XML...
</bpmnio>
IN;

$instructions = p_get_instructions($input);
$xhtml = p_render('xhtml', $instructions, $info);

$this->assertStringContainsString('data-lint="on"', $xhtml);
}

public function test_syntax_lint_off_attribute()
{
$info = array();

$input = <<<IN
<bpmnio type="bpmn" lint="off">
XML...
</bpmnio>
IN;

$instructions = p_get_instructions($input);
$xhtml = p_render('xhtml', $instructions, $info);

$this->assertStringContainsString('data-lint="off"', $xhtml);
}

public function test_syntax_ignores_invalid_lint_attribute()
{
$info = array();

$input = <<<IN
<bpmnio type="bpmn" lint="bogus">
XML...
</bpmnio>
IN;

$instructions = p_get_instructions($input);
$xhtml = p_render('xhtml', $instructions, $info);

$this->assertStringNotContainsString('data-lint=', $xhtml);
}

public function test_syntax_lint_and_zoom_coexist()
{
$info = array();

$input = <<<IN
<bpmnio type="bpmn" zoom="1.5" lint="inactive">
XML...
</bpmnio>
IN;

$instructions = p_get_instructions($input);
$xhtml = p_render('xhtml', $instructions, $info);

$this->assertStringContainsString('data-zoom="1.5"', $xhtml);
$this->assertStringContainsString('data-lint="inactive"', $xhtml);
}

public function test_syntax_builds_link_payload_for_named_elements()
{
$info = array();
Expand Down
1 change: 1 addition & 0 deletions all.less
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
@import "vendor/bpmn-js/dist/assets/diagram-js.less";
@import "vendor/bpmn-js/dist/assets/bpmn-font/css/bpmn-codes.less";
@import "vendor/bpmn-js/dist/assets/bpmn-font/css/bpmn-embedded.less";
@import "vendor/bpmn-js-bpmnlint/dist/assets/css/bpmn-js-bpmnlint.less";

@import "vendor/dmn-js/dist/assets/diagram-js.less";
@import "vendor/dmn-js/dist/assets/dmn-js-shared.less";
Expand Down
67 changes: 64 additions & 3 deletions build/build-vendor.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,21 @@ import { build } from 'esbuild';
import { cp, mkdir, readdir, readFile, rm, stat, writeFile } from 'node:fs/promises';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import packConfigModule from 'bpmnlint-pack-config';

const { packConfig } = packConfigModule;

const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
const nodeModulesDir = path.join(rootDir, 'node_modules');
const vendorDir = path.join(rootDir, 'vendor');
const fontDir = path.join(rootDir, 'font');
const generatedDir = path.join(rootDir, 'build', 'generated');

// .bpmnlintrc is resolved into a self-contained { config, resolver } module at
// build time. bpmnlint cannot resolve rule references in the browser, so the
// packed module is what the bpmn-viewer / bpmn-modeler entry points import.
const bpmnlintConfigPath = path.join(rootDir, '.bpmnlintrc');
const packedLintConfigPath = path.join(generatedDir, 'bpmnlintrc.packed.js');

const packages = [
{
Expand Down Expand Up @@ -153,7 +163,55 @@ async function buildBundle({ entryPoint, outFile, metadata, packageName }) {
console.log(`Built ${packageName} bundle: ${path.relative(rootDir, outFile)}`);
}

async function packLintConfig() {
if (!(await pathExists(bpmnlintConfigPath))) {
throw new Error(
`Missing .bpmnlintrc at repo root. It is required to build the linter bundle.`
);
}

await mkdir(generatedDir, { recursive: true });

// Produces an ES module exporting { config, resolver, ... } with every rule
// implementation inlined, so it can run in the browser without a resolver.
const output = await packConfig(bpmnlintConfigPath, 'es');
const banner = '/*! generated from .bpmnlintrc for dokuwiki-plugin-bpmnio — do not edit by hand */\n';

await writeFile(packedLintConfigPath, `${banner}${output.code}`);

console.log(`Packed lint config: ${path.relative(rootDir, packedLintConfigPath)}`);
}

async function copyLintAssets() {
const packageName = 'bpmn-js-bpmnlint';
const sourceDir = path.join(nodeModulesDir, packageName);

await ensureInstalled(packageName);
await cleanPackageOutput(packageName);
await copyMetadata(packageName);

// Only the stylesheet is needed as a committed asset; the JS is bundled into
// the viewer/modeler entry points. The .css is renamed to .less so DokuWiki's
// LESS pipeline (all.less) can @import it like the other vendor stylesheets.
const cssSource = path.join(sourceDir, 'dist', 'assets', 'css', 'bpmn-js-bpmnlint.css');
const cssTarget = path.join(
vendorDir,
packageName,
'dist',
'assets',
'css',
'bpmn-js-bpmnlint.less'
);

await copyFileEnsuringDir(cssSource, cssTarget);

console.log(`Copied ${packageName} stylesheet: ${path.relative(rootDir, cssTarget)}`);
}

async function main() {
await packLintConfig();
await copyLintAssets();

for (const pkg of packages) {
const metadata = await readPackageMetadata(pkg.name);
const sourceDir = path.join(nodeModulesDir, pkg.name);
Expand All @@ -176,12 +234,15 @@ async function main() {
}
}

const lintPackages = ['bpmn-js-bpmnlint', 'bpmnlint', 'bpmnlint-pack-config'];

const generatedMetadata = {
generatedAt: new Date().toISOString(),
packages: Object.fromEntries(
await Promise.all(
packages.map(async (pkg) => [pkg.name, (await readPackageMetadata(pkg.name)).version])
)
await Promise.all([
...packages.map(async (pkg) => [pkg.name, (await readPackageMetadata(pkg.name)).version]),
...lintPackages.map(async (name) => [name, (await readPackageMetadata(name)).version]),
])
),
};

Expand Down
14 changes: 14 additions & 0 deletions build/vendor-entrypoints/bpmn-modeler.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import Modeler from 'bpmn-js/lib/Modeler';
import NavigatedViewer from 'bpmn-js/lib/NavigatedViewer';
import Viewer from 'bpmn-js/lib/Viewer';
import lintModule from 'bpmn-js-bpmnlint';
import { config, resolver } from '../generated/bpmnlintrc.packed.js';

const root = globalThis;
Object.assign(Modeler, {
Expand All @@ -10,4 +12,16 @@ Object.assign(Modeler, {
});
root.BpmnJS = Modeler;

// Same packed lint config as the viewer bundle. Attaching it to each exported
// constructor (and to window globals) keeps the render script independent of
// which bundle loaded last.
const lintConfig = { config, resolver };

root.BpmnLintModule = lintModule;
root.BpmnLintConfig = lintConfig;
for (const Ctor of [Modeler, NavigatedViewer, Viewer]) {
Ctor.lintModule = lintModule;
Ctor.lintConfig = lintConfig;
}

export default Modeler;
14 changes: 14 additions & 0 deletions build/vendor-entrypoints/bpmn-viewer.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,21 @@
import Viewer from 'bpmn-js/lib/Viewer';
import lintModule from 'bpmn-js-bpmnlint';
import { config, resolver } from '../generated/bpmnlintrc.packed.js';

const root = globalThis;
root.BpmnJS = Viewer;
root.BpmnJS.Viewer = Viewer;

// Linter integration. bpmn-js-bpmnlint runs in a plain Viewer: every service it
// needs (canvas, overlays, elementRegistry, eventBus, translate, bpmnjs) ships
// with the Viewer, and its editor-action helper resolves editorActions
// optionally. The render script passes lintModule + lintConfig into the
// constructor; we expose them here so it can opt diagrams in or out.
const lintConfig = { config, resolver };

root.BpmnLintModule = lintModule;
root.BpmnLintConfig = lintConfig;
Viewer.lintModule = lintModule;
Viewer.lintConfig = lintConfig;

export default Viewer;
Loading