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
8 changes: 8 additions & 0 deletions .jscpd.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"path": ["src"],
"format": ["typescript"],
"minLines": 6,
"threshold": 0.25,
"reporters": ["console"],
"gitignore": true
}
8 changes: 5 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,10 @@ npm test # the vitest suite
npm run gate # everything a change has to pass before it is handed over
```

`npm run gate` builds, runs knip and the tests, the isomorphism check of the browser-safe core, the
layout-regression budget and a short fuzz campaign. CI runs `typecheck` and `gate` on every pull
`npm run gate` builds, runs knip twice (the second time in production mode: code in `src/` that only
tests use fails unless its export is tagged `@internal`), the duplication budget of `src/` (jscpd,
`.jscpd.json`), the tests, the isomorphism check of the browser-safe core, the layout-regression
budget and a short fuzz campaign. CI runs `typecheck` and `gate` on every pull
request and every push to `main`. The test layers, the benchmark and the fuzzer, and how to run
them on a private corpus without copying it into the repository, are described in
[docs/testing.md](docs/testing.md).
Expand Down Expand Up @@ -60,7 +62,7 @@ them on a private corpus without copying it into the repository, are described i
| `adapters/in/cli/` | the driving adapter: the `Cli` (commander, streams, exit codes, errors) and one class per command; it knows the use cases only by their contracts |
| `adapters/in/report/` | what a use case's result looks like to the one who asked: text and JSON reports (`MutationText`, `MutationReportBuilder`, `ValidationText`, ...), the views as text, and the reference the guide and `bpmn kinds` print; the CLI prints them and `index.ts` / `api.ts` return them |
| `adapters/out/files/` | the driven adapters on disk, Node only: `FileDocumentStore`, `BpmnlintProjectSettings`, `FileImageWriter` (PNG) |
| `adapters/out/memory/` | the driven adapters of the in-memory API: `InMemoryDocumentStore`, `NoProjectSettings` |
| `adapters/out/memory/` | the driven adapters of the in-memory API: `NoDocumentFiles`, `NoProjectSettings` |
| `types/` | type shims for the bpmn-io packages that ship no usable declarations |

Where new code goes: a new command is a type in `domain/commands/` and a `...Service` of the
Expand Down
63 changes: 25 additions & 38 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ flowchart TB
service --> domain["Domain<br/>domain/"]
service --> portOut{{"Port<br/>application/port/out"}}
portOut --> files["Files<br/>adapters/out/files"]
portOut --> memory["No project<br/>adapters/out/memory"]
portOut --> memory["No project, no files<br/>adapters/out/memory"]

classDef adapter stroke:#2f80ed,stroke-width:2px
classDef app stroke:#f2994a,stroke-width:2px
Expand Down Expand Up @@ -100,23 +100,21 @@ export class AddElementCommand extends MutationCommand {
options: RawOpts,
) {
const opts = this.mutationOptions(options)
await this.cli.guarded(async () => {
this.checkPlacementFlags(options)
const op: AddElement = {
op: 'add',
kind, // 'userTask'
name, // 'Check invoice'
after: options['after'], // 'Event_OrderReceived'
// …
}
await this.mutate(file, [op], opts)
}, opts.json)
this.checkPlacementFlags(options)
const op: AddElement = {
op: 'add',
kind, // 'userTask'
name, // 'Check invoice'
after: options['after'], // 'Event_OrderReceived'
// …
}
await this.mutate(file, [op], opts)
}
}
```

`guarded()` catches any error and prints it with its code and hint. From here on nobody knows
about flags any more.
An error thrown here reaches `Cli.run`, which prints it with its code and hint. From here on
nobody knows about flags any more.

### 2. Through the port

Expand Down Expand Up @@ -158,44 +156,33 @@ export declare namespace EditDocumentUseCase {
### 3. Load, run, save

The service is short on purpose: get the document, let the domain work, write the result.
`settings`, `load` and `save` are outbound ports, handed in by the constructor; without `load`
or `save` the service takes only a document and writes nothing.
`settings`, `load` and `save` are outbound ports, handed in by the constructor. The call into
the domain stands on a line of its own; reading and writing the file are small steps around it.

```ts
// src/application/service/EditDocumentService.ts
export class EditDocumentService implements EditDocumentUseCase {
constructor(
private readonly settings: LoadProjectSettingsPort,
private readonly load?: LoadDocumentPort,
private readonly save?: SaveDocumentPort,
private readonly load: LoadDocumentPort,
private readonly save: SaveDocumentPort,
) {}

async edit({ source, commands, ...opts }: EditDocumentUseCase.Command) {
// step 7: read the file
const doc = 'doc' in source
? source.doc
: await Doc.fromXml(await this.loadPort(source.file).read(source.file), source.file)
const target = opts.out ?? doc.file

// the project's .bpmnlintrc, if any
const lint = await this.settings.lint(target, opts)

// step 4: the domain does the work
const batchOpts = lint.applyTo(opts, target)
const batch = new EditBatchService(doc, commands, batchOpts)
const result = await batch.run(target)

// step 7: write the file
if (!opts.dryRun) {
result.written = await this.savePort().save(target, result.xml, { … })
}
async edit(command: EditDocumentUseCase.Command) {
const { source, commands, ...opts } = command
const doc = await this.open(source, opts) // step 7: read the file
const target = await this.targetOf(doc, opts)
const project = (await this.settings.lint(opts.file ?? target, opts)).applyTo(opts, target)
const result = await new EditBatchService(doc, commands, project).run(target) // step 4: the domain
await this.write(result, doc, target, opts) // step 7: write the file
return result
}
}
```

In the CLI the ports are a `FileDocumentStore` and `BpmnlintProjectSettings`; `applyToXml()`
passes only `NoProjectSettings` and a `{ doc }` with `dryRun`, so nothing is read or written.
passes `NoProjectSettings`, `NoDocumentFiles` and a `{ doc }` with `dryRun`, so nothing is read
or written.

### 4. The batch

Expand Down
10 changes: 4 additions & 6 deletions docs/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -234,9 +234,7 @@ ending on a number that reads like a suffix (`Phase1To...` kept), a cut
inside a word that keeps the numbers, a camelCase end without its first
word, a suffix within the cap, none skipped, also on an id that names the
anchor's kind, a file prefix or scope too long for the cap in every flow
form, cut flows voting for their form, flows 0.6 cut still following their
ends, also with `_100` and a leading separator, ends of any length cut
fast: #31),
form, cut flows voting for their form, ends of any length cut fast: #31),
transliteration beyond German, the definitions id of `new`, and the warnings
delta: platform findings counted, a shrinking `W_DUPLICATE_NAME`, a bridge
that takes over a removed flow's id, warnings of a batch's final state, `new`
Expand Down Expand Up @@ -787,9 +785,9 @@ node bin/bpmn.js add file.bpmn task "X" --after <id> --render /tmp/png/after.png
reviews and blind judging, render with real bpmn-js:

```
tools/render.sh /tmp/png file1.bpmn file2.bpmn # any files
tools/bench/render.sh /tmp/png file1.bpmn file2.bpmn # any files
tools/bench/render.sh /tmp/png tools/bench/results/latest/work/edits/<arm>/<corpus>/<model>/*/model.bpmn
```

Both need Google Chrome (`PUPPETEER_EXECUTABLE_PATH` overrides the macOS
default path) and fetch `bpmn-to-image` with npx.
It needs Google Chrome (`PUPPETEER_EXECUTABLE_PATH` overrides the macOS
default path) and fetches `bpmn-to-image` with npx.
6 changes: 3 additions & 3 deletions knip.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"$schema": "https://unpkg.com/knip@6/schema.json",
"entry": ["src/api.ts", "tools/**/*.mjs", "test/**/*.test.ts"],
"project": ["src/**/*.ts", "tools/**/*.mjs", "test/**/*.ts"],
"ignoreDependencies": ["@resvg/resvg-wasm", "playwright"]
"entry": ["src/api.ts!", "tools/**/*.mjs", "test/**/*.test.ts"],
"project": ["src/**/*.ts!", "tools/**/*.mjs", "test/**/*.ts"],
"ignoreDependencies": ["@resvg/resvg-wasm"]
}
181 changes: 151 additions & 30 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@
"docs:dev": "npm --prefix docs run dev",
"docs:build": "npm --prefix docs run build",
"docs:preview": "npm --prefix docs run preview",
"gate": "npm run build && knip && vitest run && node tools/iso/check.mjs && node tools/bench/regress-gate.mjs && node tools/fuzz/run.mjs --walks 12 --steps 15 --seed 1 --hard warn --out tools/fuzz/out/gate",
"gate": "npm run build && knip && knip --production --exclude dependencies && jscpd && vitest run && node tools/iso/check.mjs && node tools/bench/regress-gate.mjs && node tools/fuzz/run.mjs --walks 12 --steps 15 --seed 1 --hard warn --out tools/fuzz/out/gate",
"prepublishOnly": "npm run build"
},
"dependencies": {
Expand All @@ -78,8 +78,8 @@
"archunit": "2.5.4",
"camunda-bpmn-moddle": "8.0.2",
"esbuild": "^0.28.2",
"jscpd": "5.4.1",
"knip": "6.43.0",
"playwright": "1.64.1",
"tsx": "^4.23.0",
"typescript": "^7.0.2",
"vitest": "^5.0.2",
Expand Down
Loading
Loading