@bpmn-ld/bpmn-ld 0.0.0-stage → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +125 -2
- package/contexts/combined.jsonld +3 -0
- package/contexts/core.jsonld +170 -0
- package/contexts/di.jsonld +37 -0
- package/dist/analyze/analyze.d.ts +3 -0
- package/dist/analyze/analyze.js +11 -0
- package/dist/analyze/findings.d.ts +8 -0
- package/dist/analyze/findings.js +1 -0
- package/dist/analyze/report.d.ts +3 -0
- package/dist/analyze/report.js +9 -0
- package/dist/analyze/rules/ad-hoc-completion-condition.d.ts +2 -0
- package/dist/analyze/rules/ad-hoc-completion-condition.js +7 -0
- package/dist/analyze/rules/cannot-reach-end.d.ts +2 -0
- package/dist/analyze/rules/cannot-reach-end.js +28 -0
- package/dist/analyze/rules/compensation-association.d.ts +2 -0
- package/dist/analyze/rules/compensation-association.js +13 -0
- package/dist/analyze/rules/dead-end.d.ts +2 -0
- package/dist/analyze/rules/dead-end.js +13 -0
- package/dist/analyze/rules/end-event.d.ts +2 -0
- package/dist/analyze/rules/end-event.js +8 -0
- package/dist/analyze/rules/error-catch.d.ts +2 -0
- package/dist/analyze/rules/error-catch.js +14 -0
- package/dist/analyze/rules/escalation-catch.d.ts +2 -0
- package/dist/analyze/rules/escalation-catch.js +14 -0
- package/dist/analyze/rules/gateway-cardinality.d.ts +3 -0
- package/dist/analyze/rules/gateway-cardinality.js +17 -0
- package/dist/analyze/rules/index.d.ts +4 -0
- package/dist/analyze/rules/index.js +21 -0
- package/dist/analyze/rules/instantiating-gateway.d.ts +2 -0
- package/dist/analyze/rules/instantiating-gateway.js +14 -0
- package/dist/analyze/rules/non-interrupting-start.d.ts +2 -0
- package/dist/analyze/rules/non-interrupting-start.js +14 -0
- package/dist/analyze/rules/reachability.d.ts +2 -0
- package/dist/analyze/rules/reachability.js +25 -0
- package/dist/analyze/rules/rule.d.ts +7 -0
- package/dist/analyze/rules/rule.js +1 -0
- package/dist/analyze/rules/signal-catch.d.ts +2 -0
- package/dist/analyze/rules/signal-catch.js +12 -0
- package/dist/analyze/rules/split-join-pairing.d.ts +3 -0
- package/dist/analyze/rules/split-join-pairing.js +50 -0
- package/dist/analyze/rules/start-event.d.ts +2 -0
- package/dist/analyze/rules/start-event.js +8 -0
- package/dist/analyze/rules/task-message-flow.d.ts +2 -0
- package/dist/analyze/rules/task-message-flow.js +19 -0
- package/dist/analyze/scope-graph.d.ts +56 -0
- package/dist/analyze/scope-graph.js +89 -0
- package/dist/analyze-cli.d.ts +1 -0
- package/dist/analyze-cli.js +5 -0
- package/dist/analyze-run.d.ts +12 -0
- package/dist/analyze-run.js +85 -0
- package/dist/bpmn-ld-cli.d.ts +2 -0
- package/dist/bpmn-ld-cli.js +97 -0
- package/dist/build-cli.d.ts +1 -0
- package/dist/build-cli.js +2 -0
- package/dist/build.d.ts +3 -0
- package/dist/build.js +38 -0
- package/dist/cheat-sheet-catalog.d.ts +10 -0
- package/dist/cheat-sheet-catalog.js +72 -0
- package/dist/cheat-sheet-cli.d.ts +1 -0
- package/dist/cheat-sheet-cli.js +7 -0
- package/dist/cheat-sheet-compose.d.ts +13 -0
- package/dist/cheat-sheet-compose.js +107 -0
- package/dist/cheat-sheet-run.d.ts +11 -0
- package/dist/cheat-sheet-run.js +65 -0
- package/dist/cheat-sheet-svg.d.ts +2 -0
- package/dist/cheat-sheet-svg.js +2 -0
- package/dist/cheat-sheet.d.ts +2 -0
- package/dist/cheat-sheet.js +2 -0
- package/dist/check-xml/check.d.ts +3 -0
- package/dist/check-xml/check.js +11 -0
- package/dist/check-xml/lint.d.ts +2 -0
- package/dist/check-xml/lint.js +47 -0
- package/dist/check-xml/moddle.d.ts +6 -0
- package/dist/check-xml/moddle.js +23 -0
- package/dist/check-xml/types.d.ts +20 -0
- package/dist/check-xml/types.js +1 -0
- package/dist/check-xml/xsd.d.ts +2 -0
- package/dist/check-xml/xsd.js +25 -0
- package/dist/check-xml-cli.d.ts +1 -0
- package/dist/check-xml-cli.js +5 -0
- package/dist/check-xml-run.d.ts +10 -0
- package/dist/check-xml-run.js +77 -0
- package/dist/convert/analyze-output.d.ts +3 -0
- package/dist/convert/analyze-output.js +8 -0
- package/dist/convert/convert.d.ts +19 -0
- package/dist/convert/convert.js +96 -0
- package/dist/convert/errors.d.ts +5 -0
- package/dist/convert/errors.js +8 -0
- package/dist/convert/extensions.d.ts +23 -0
- package/dist/convert/extensions.js +103 -0
- package/dist/convert/mapping-check.d.ts +15 -0
- package/dist/convert/mapping-check.js +66 -0
- package/dist/convert/mapping.d.ts +52 -0
- package/dist/convert/mapping.js +273 -0
- package/dist/convert/namespaces.d.ts +2 -0
- package/dist/convert/namespaces.js +19 -0
- package/dist/convert/validate-output.d.ts +2 -0
- package/dist/convert/validate-output.js +28 -0
- package/dist/convert/warnings.d.ts +9 -0
- package/dist/convert/warnings.js +67 -0
- package/dist/convert/xml-reader.d.ts +11 -0
- package/dist/convert/xml-reader.js +84 -0
- package/dist/convert-cli.d.ts +1 -0
- package/dist/convert-cli.js +5 -0
- package/dist/convert-run.d.ts +10 -0
- package/dist/convert-run.js +125 -0
- package/dist/coverage-cli.d.ts +1 -0
- package/dist/coverage-cli.js +8 -0
- package/dist/coverage-mappings.d.ts +3 -0
- package/dist/coverage-mappings.js +34 -0
- package/dist/coverage-scope.d.ts +11 -0
- package/dist/coverage-scope.js +187 -0
- package/dist/coverage-xsd.d.ts +15 -0
- package/dist/coverage-xsd.js +55 -0
- package/dist/coverage.d.ts +35 -0
- package/dist/coverage.js +152 -0
- package/dist/docs-cli.d.ts +1 -0
- package/dist/docs-cli.js +18 -0
- package/dist/docs.d.ts +8 -0
- package/dist/docs.js +136 -0
- package/dist/eval/checks.d.ts +19 -0
- package/dist/eval/checks.js +252 -0
- package/dist/eval/regenerate.d.ts +10 -0
- package/dist/eval/regenerate.js +48 -0
- package/dist/eval-regenerate-cli.d.ts +1 -0
- package/dist/eval-regenerate-cli.js +16 -0
- package/dist/export/data.d.ts +10 -0
- package/dist/export/data.js +127 -0
- package/dist/export/diagram.d.ts +8 -0
- package/dist/export/diagram.js +112 -0
- package/dist/export/element.d.ts +27 -0
- package/dist/export/element.js +175 -0
- package/dist/export/export.d.ts +22 -0
- package/dist/export/export.js +124 -0
- package/dist/export/extensions.d.ts +18 -0
- package/dist/export/extensions.js +122 -0
- package/dist/export/layout-export.d.ts +4 -0
- package/dist/export/layout-export.js +25 -0
- package/dist/export/mapping-check.d.ts +39 -0
- package/dist/export/mapping-check.js +175 -0
- package/dist/export/normalize.d.ts +3 -0
- package/dist/export/normalize.js +67 -0
- package/dist/export-cli.d.ts +1 -0
- package/dist/export-cli.js +5 -0
- package/dist/export-run.d.ts +10 -0
- package/dist/export-run.js +157 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +10 -0
- package/dist/install-skill-run.d.ts +8 -0
- package/dist/install-skill-run.js +30 -0
- package/dist/install-skill.d.ts +23 -0
- package/dist/install-skill.js +76 -0
- package/dist/layout/collaboration.d.ts +36 -0
- package/dist/layout/collaboration.js +152 -0
- package/dist/layout/di.d.ts +15 -0
- package/dist/layout/di.js +52 -0
- package/dist/layout/extract.d.ts +2 -0
- package/dist/layout/extract.js +99 -0
- package/dist/layout/lanes.d.ts +30 -0
- package/dist/layout/lanes.js +112 -0
- package/dist/layout/layering.d.ts +7 -0
- package/dist/layout/layering.js +53 -0
- package/dist/layout/layout.d.ts +12 -0
- package/dist/layout/layout.js +299 -0
- package/dist/layout/model.d.ts +52 -0
- package/dist/layout/model.js +10 -0
- package/dist/layout/placement.d.ts +25 -0
- package/dist/layout/placement.js +256 -0
- package/dist/layout/standalone.d.ts +8 -0
- package/dist/layout/standalone.js +50 -0
- package/dist/layout-cli.d.ts +1 -0
- package/dist/layout-cli.js +5 -0
- package/dist/layout-run.d.ts +9 -0
- package/dist/layout-run.js +105 -0
- package/dist/loader.d.ts +5 -0
- package/dist/loader.js +7 -0
- package/dist/namespace.d.ts +5 -0
- package/dist/namespace.js +5 -0
- package/dist/ontology.d.ts +19 -0
- package/dist/ontology.js +94 -0
- package/dist/paths.d.ts +1 -0
- package/dist/paths.js +2 -0
- package/dist/render/geometry.d.ts +40 -0
- package/dist/render/geometry.js +81 -0
- package/dist/render/renderer.d.ts +31 -0
- package/dist/render/renderer.js +369 -0
- package/dist/render/symbols.d.ts +10 -0
- package/dist/render/symbols.js +26 -0
- package/dist/render/text.d.ts +20 -0
- package/dist/render/text.js +28 -0
- package/dist/render-cli.d.ts +1 -0
- package/dist/render-cli.js +7 -0
- package/dist/render-run.d.ts +9 -0
- package/dist/render-run.js +161 -0
- package/dist/render.d.ts +4 -0
- package/dist/render.js +4 -0
- package/dist/schema-overrides.d.ts +14 -0
- package/dist/schema-overrides.js +36 -0
- package/dist/schema-validate.d.ts +5 -0
- package/dist/schema-validate.js +8 -0
- package/dist/schema.d.ts +6 -0
- package/dist/schema.js +114 -0
- package/dist/shacl.d.ts +15 -0
- package/dist/shacl.js +37 -0
- package/dist/validate-cli.d.ts +1 -0
- package/dist/validate-cli.js +5 -0
- package/dist/validate-run.d.ts +8 -0
- package/dist/validate-run.js +58 -0
- package/dist/validate.d.ts +11 -0
- package/dist/validate.js +94 -0
- package/dist/versioning-cli.d.ts +1 -0
- package/dist/versioning-cli.js +18 -0
- package/dist/versioning.d.ts +17 -0
- package/dist/versioning.js +45 -0
- package/ontology/core.ttl +993 -0
- package/ontology/di.ttl +167 -0
- package/package.json +77 -4
- package/reference/bpmn-xsd/BPMN20.xsd +37 -0
- package/reference/bpmn-xsd/BPMNDI.xsd +100 -0
- package/reference/bpmn-xsd/DC.xsd +29 -0
- package/reference/bpmn-xsd/DI.xsd +100 -0
- package/reference/bpmn-xsd/Semantic.xsd +1562 -0
- package/reference/rospocher/ATTRIBUTION.md +22 -0
- package/reference/rospocher/BPMNOntology.owl +8937 -0
- package/reference/rospocher-verified.json +221 -0
- package/schema/bpmn-ld.schema.json +15230 -0
- package/shapes/core.shapes.ttl +398 -0
- package/skills/bpmn-ld/SKILL.md +154 -0
- package/skills/bpmn-ld/evals/models/order-approval-di.jsonld +296 -0
- package/skills/bpmn-ld/evals/models/refund-di.bpmn +82 -0
- package/skills/bpmn-ld/evals/models/refund.bpmn +18 -0
- package/skills/bpmn-ld/evals/outputs/explain-collaboration.md +22 -0
- package/skills/bpmn-ld/evals/outputs/explain-order-approval.md +24 -0
- package/skills/bpmn-ld/evals/outputs/explain-refund.md +17 -0
- package/skills/bpmn-ld/evals/outputs/generate-activity-compensation.jsonld +39 -0
- package/skills/bpmn-ld/evals/outputs/generate-ad-hoc-sub-process.jsonld +52 -0
- package/skills/bpmn-ld/evals/outputs/generate-boundary-event.jsonld +27 -0
- package/skills/bpmn-ld/evals/outputs/generate-collaboration.jsonld +49 -0
- package/skills/bpmn-ld/evals/outputs/generate-conditions.jsonld +93 -0
- package/skills/bpmn-ld/evals/outputs/generate-diagram-properties.jsonld +340 -0
- package/skills/bpmn-ld/evals/outputs/generate-error.jsonld +217 -0
- package/skills/bpmn-ld/evals/outputs/generate-escalation.jsonld +231 -0
- package/skills/bpmn-ld/evals/outputs/generate-event-definition-ref.jsonld +67 -0
- package/skills/bpmn-ld/evals/outputs/generate-event-sub-process.jsonld +54 -0
- package/skills/bpmn-ld/evals/outputs/generate-exclusive-choice.jsonld +32 -0
- package/skills/bpmn-ld/evals/outputs/generate-instantiating-gateway.jsonld +24 -0
- package/skills/bpmn-ld/evals/outputs/generate-lane-partition.jsonld +46 -0
- package/skills/bpmn-ld/evals/outputs/generate-link-event.expect.json +11 -0
- package/skills/bpmn-ld/evals/outputs/generate-link-event.jsonld +57 -0
- package/skills/bpmn-ld/evals/outputs/generate-loop.jsonld +30 -0
- package/skills/bpmn-ld/evals/outputs/generate-message.jsonld +61 -0
- package/skills/bpmn-ld/evals/outputs/generate-parallel-branches.jsonld +25 -0
- package/skills/bpmn-ld/evals/outputs/generate-process-attributes.jsonld +49 -0
- package/skills/bpmn-ld/evals/outputs/generate-script-task.jsonld +18 -0
- package/skills/bpmn-ld/evals/outputs/generate-signal.jsonld +178 -0
- package/skills/bpmn-ld/evals/outputs/generate-sub-process.jsonld +31 -0
- package/skills/bpmn-ld/evals/outputs/generate-targeted-compensation.jsonld +40 -0
- package/skills/bpmn-ld/evals/outputs/generate-task-implementation.jsonld +18 -0
- package/skills/bpmn-ld/evals/outputs/generate-task-message-references.jsonld +49 -0
- package/skills/bpmn-ld/evals/outputs/generate-transaction.jsonld +48 -0
- package/skills/bpmn-ld/evals/outputs/modify-order-approval.jsonld +376 -0
- package/skills/bpmn-ld/evals/outputs/modify-refund.jsonld +373 -0
- package/skills/bpmn-ld/evals/outputs/order-approval.jsonld +29 -0
- package/skills/bpmn-ld/evals/outputs/refine-order-approval.jsonld +377 -0
- package/skills/bpmn-ld/evals/prompts/explain-collaboration.md +3 -0
- package/skills/bpmn-ld/evals/prompts/explain-order-approval.md +3 -0
- package/skills/bpmn-ld/evals/prompts/explain-refund.md +3 -0
- package/skills/bpmn-ld/evals/prompts/generate-activity-compensation.md +1 -0
- package/skills/bpmn-ld/evals/prompts/generate-ad-hoc-sub-process.md +1 -0
- package/skills/bpmn-ld/evals/prompts/generate-boundary-event.md +1 -0
- package/skills/bpmn-ld/evals/prompts/generate-collaboration.md +1 -0
- package/skills/bpmn-ld/evals/prompts/generate-conditions.md +1 -0
- package/skills/bpmn-ld/evals/prompts/generate-diagram-properties.md +1 -0
- package/skills/bpmn-ld/evals/prompts/generate-error.md +1 -0
- package/skills/bpmn-ld/evals/prompts/generate-escalation.md +1 -0
- package/skills/bpmn-ld/evals/prompts/generate-event-definition-ref.md +1 -0
- package/skills/bpmn-ld/evals/prompts/generate-event-sub-process.md +1 -0
- package/skills/bpmn-ld/evals/prompts/generate-exclusive-choice.md +1 -0
- package/skills/bpmn-ld/evals/prompts/generate-instantiating-gateway.md +1 -0
- package/skills/bpmn-ld/evals/prompts/generate-lane-partition.md +1 -0
- package/skills/bpmn-ld/evals/prompts/generate-link-event.md +1 -0
- package/skills/bpmn-ld/evals/prompts/generate-loop.md +1 -0
- package/skills/bpmn-ld/evals/prompts/generate-message.md +1 -0
- package/skills/bpmn-ld/evals/prompts/generate-parallel-branches.md +1 -0
- package/skills/bpmn-ld/evals/prompts/generate-process-attributes.md +1 -0
- package/skills/bpmn-ld/evals/prompts/generate-script-task.md +1 -0
- package/skills/bpmn-ld/evals/prompts/generate-signal.md +1 -0
- package/skills/bpmn-ld/evals/prompts/generate-sub-process.md +1 -0
- package/skills/bpmn-ld/evals/prompts/generate-targeted-compensation.md +1 -0
- package/skills/bpmn-ld/evals/prompts/generate-task-implementation.md +1 -0
- package/skills/bpmn-ld/evals/prompts/generate-task-message-references.md +1 -0
- package/skills/bpmn-ld/evals/prompts/generate-transaction.md +1 -0
- package/skills/bpmn-ld/evals/prompts/modify-order-approval.md +4 -0
- package/skills/bpmn-ld/evals/prompts/modify-refund.md +4 -0
- package/skills/bpmn-ld/evals/prompts/order-approval.md +1 -0
- package/skills/bpmn-ld/evals/prompts/refine-order-approval.md +8 -0
- package/skills/bpmn-ld/examples/positive/activity-compensation.jsonld +39 -0
- package/skills/bpmn-ld/examples/positive/ad-hoc-sub-process.jsonld +52 -0
- package/skills/bpmn-ld/examples/positive/boundary-event.jsonld +27 -0
- package/skills/bpmn-ld/examples/positive/collaboration.jsonld +49 -0
- package/skills/bpmn-ld/examples/positive/conditions.jsonld +93 -0
- package/skills/bpmn-ld/examples/positive/diagram-properties.jsonld +340 -0
- package/skills/bpmn-ld/examples/positive/error.jsonld +208 -0
- package/skills/bpmn-ld/examples/positive/escalation.jsonld +231 -0
- package/skills/bpmn-ld/examples/positive/event-definition-ref.jsonld +142 -0
- package/skills/bpmn-ld/examples/positive/event-sub-process.jsonld +54 -0
- package/skills/bpmn-ld/examples/positive/exclusive-choice.jsonld +32 -0
- package/skills/bpmn-ld/examples/positive/instantiating-gateway.jsonld +24 -0
- package/skills/bpmn-ld/examples/positive/lane-partition.jsonld +46 -0
- package/skills/bpmn-ld/examples/positive/link-event.expect.json +10 -0
- package/skills/bpmn-ld/examples/positive/link-event.jsonld +138 -0
- package/skills/bpmn-ld/examples/positive/loop.jsonld +30 -0
- package/skills/bpmn-ld/examples/positive/message.jsonld +60 -0
- package/skills/bpmn-ld/examples/positive/order-approval.jsonld +29 -0
- package/skills/bpmn-ld/examples/positive/parallel-branches.jsonld +25 -0
- package/skills/bpmn-ld/examples/positive/process-attributes.jsonld +49 -0
- package/skills/bpmn-ld/examples/positive/script-task.jsonld +18 -0
- package/skills/bpmn-ld/examples/positive/signal.jsonld +150 -0
- package/skills/bpmn-ld/examples/positive/sub-process.jsonld +31 -0
- package/skills/bpmn-ld/examples/positive/targeted-compensation.jsonld +40 -0
- package/skills/bpmn-ld/examples/positive/task-implementation.jsonld +18 -0
- package/skills/bpmn-ld/examples/positive/task-message-references.jsonld +151 -0
- package/skills/bpmn-ld/examples/positive/transaction.jsonld +48 -0
- package/skills/bpmn-ld/references/clarification-and-refinement.md +60 -0
- package/skills/bpmn-ld/references/common-mistakes.md +110 -0
- package/skills/bpmn-ld/references/explain-and-document.md +36 -0
- package/skills/bpmn-ld/references/modify-existing-model.md +49 -0
- package/skills/bpmn-ld/references/patterns.md +292 -0
- package/skills/bpmn-ld/references/render-output.md +1 -0
- package/skills/bpmn-ld/references/rule-catalogue.md +183 -0
- package/skills/bpmn-ld/references/vocabulary-summary.md +65 -0
- package/skills/bpmn-ld/template.jsonld +14 -0
- package/templates/combined.jsonld +3 -0
- package/templates/core.jsonld +170 -0
- package/templates/core.shapes.ttl +398 -0
- package/templates/core.ttl +993 -0
- package/templates/di.jsonld +37 -0
- package/templates/di.ttl +167 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export const compensationAssociationRule = {
|
|
2
|
+
id: "compensation-activity-without-association",
|
|
3
|
+
severity: "warning",
|
|
4
|
+
check: (scope) => scope.nodes
|
|
5
|
+
.filter((node) => node.forCompensation)
|
|
6
|
+
.filter((node) => !scope.associations.some((association) => association.target === node.id && scope.nodes.some((source) => source.id === association.source && source.compensationBoundary)))
|
|
7
|
+
.map((node) => ({
|
|
8
|
+
rule: "compensation-activity-without-association",
|
|
9
|
+
severity: "warning",
|
|
10
|
+
node: node.id,
|
|
11
|
+
message: "compensation activity is not the target of an association from a compensation boundary event, so no compensation event triggers it",
|
|
12
|
+
})),
|
|
13
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { hasType } from "../scope-graph.js";
|
|
2
|
+
export const deadEndRule = {
|
|
3
|
+
id: "node-dead-end",
|
|
4
|
+
severity: "warning",
|
|
5
|
+
check: (scope) => {
|
|
6
|
+
if (scope.adHoc)
|
|
7
|
+
return [];
|
|
8
|
+
const sources = new Set(scope.flows.map((flow) => flow.source));
|
|
9
|
+
return scope.nodes
|
|
10
|
+
.filter((node) => !node.exempt && !hasType(node, "EndEvent") && !sources.has(node.id))
|
|
11
|
+
.map((node) => ({ rule: "node-dead-end", severity: "warning", node: node.id, message: "flow node has no outgoing sequence flow and is not an end event" }));
|
|
12
|
+
},
|
|
13
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { hasType, isPlaceholder } from "../scope-graph.js";
|
|
2
|
+
export const endEventRule = {
|
|
3
|
+
id: "end-event-missing",
|
|
4
|
+
severity: "warning",
|
|
5
|
+
check: (scope) => isPlaceholder(scope) || scope.adHoc || scope.nodes.some((node) => hasType(node, "EndEvent"))
|
|
6
|
+
? []
|
|
7
|
+
: [{ rule: "end-event-missing", severity: "warning", node: scope.id, message: `${scope.kind === "process" ? "process" : "sub-process"} has no end event` }],
|
|
8
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export const errorCatchRule = {
|
|
2
|
+
id: "error-throw-without-catch",
|
|
3
|
+
severity: "warning",
|
|
4
|
+
check: (scope) => scope.catchesAnyError
|
|
5
|
+
? []
|
|
6
|
+
: scope.nodes
|
|
7
|
+
.filter((node) => node.errorThrows.some((error) => !scope.caughtErrors.includes(error)))
|
|
8
|
+
.map((node) => ({
|
|
9
|
+
rule: "error-throw-without-catch",
|
|
10
|
+
severity: "warning",
|
|
11
|
+
node: node.id,
|
|
12
|
+
message: "error end event references an error that no boundary event or event sub-process start event in the document catches",
|
|
13
|
+
})),
|
|
14
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export const escalationCatchRule = {
|
|
2
|
+
id: "escalation-throw-without-catch",
|
|
3
|
+
severity: "warning",
|
|
4
|
+
check: (scope) => scope.catchesAnyEscalation
|
|
5
|
+
? []
|
|
6
|
+
: scope.nodes
|
|
7
|
+
.filter((node) => node.escalationThrows.some((escalation) => !scope.caughtEscalations.includes(escalation)))
|
|
8
|
+
.map((node) => ({
|
|
9
|
+
rule: "escalation-throw-without-catch",
|
|
10
|
+
severity: "warning",
|
|
11
|
+
node: node.id,
|
|
12
|
+
message: "escalation throw event references an escalation that no boundary event or event sub-process start event in the document catches",
|
|
13
|
+
})),
|
|
14
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { bpmn } from "../scope-graph.js";
|
|
2
|
+
export const gatewayDivergingRule = {
|
|
3
|
+
id: "gateway-diverging-too-few-outgoing",
|
|
4
|
+
severity: "error",
|
|
5
|
+
check: (scope) => scope.nodes
|
|
6
|
+
.filter((node) => node.direction === bpmn("Diverging"))
|
|
7
|
+
.filter((node) => scope.flows.filter((flow) => flow.source === node.id).length < 2)
|
|
8
|
+
.map((node) => ({ rule: "gateway-diverging-too-few-outgoing", severity: "error", node: node.id, message: "diverging gateway has fewer than two outgoing flows" })),
|
|
9
|
+
};
|
|
10
|
+
export const gatewayConvergingRule = {
|
|
11
|
+
id: "gateway-converging-too-few-incoming",
|
|
12
|
+
severity: "error",
|
|
13
|
+
check: (scope) => scope.nodes
|
|
14
|
+
.filter((node) => node.direction === bpmn("Converging"))
|
|
15
|
+
.filter((node) => scope.flows.filter((flow) => flow.target === node.id).length < 2)
|
|
16
|
+
.map((node) => ({ rule: "gateway-converging-too-few-incoming", severity: "error", node: node.id, message: "converging gateway has fewer than two incoming flows" })),
|
|
17
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { STRUCTURAL_RULE } from "../findings.js";
|
|
2
|
+
import { adHocCompletionConditionRule } from "./ad-hoc-completion-condition.js";
|
|
3
|
+
import { cannotReachEndRule } from "./cannot-reach-end.js";
|
|
4
|
+
import { compensationAssociationRule } from "./compensation-association.js";
|
|
5
|
+
import { deadEndRule } from "./dead-end.js";
|
|
6
|
+
import { endEventRule } from "./end-event.js";
|
|
7
|
+
import { errorCatchRule } from "./error-catch.js";
|
|
8
|
+
import { escalationCatchRule } from "./escalation-catch.js";
|
|
9
|
+
import { gatewayConvergingRule, gatewayDivergingRule } from "./gateway-cardinality.js";
|
|
10
|
+
import { instantiatingGatewayRule } from "./instantiating-gateway.js";
|
|
11
|
+
import { nonInterruptingStartRule } from "./non-interrupting-start.js";
|
|
12
|
+
import { reachabilityRule } from "./reachability.js";
|
|
13
|
+
import { signalCatchRule } from "./signal-catch.js";
|
|
14
|
+
import { startEventRule } from "./start-event.js";
|
|
15
|
+
import { taskMessageFlowRule } from "./task-message-flow.js";
|
|
16
|
+
import { splitJoinFindingIds, splitJoinPairingRule } from "./split-join-pairing.js";
|
|
17
|
+
export const rules = [startEventRule, endEventRule, gatewayDivergingRule, gatewayConvergingRule, reachabilityRule, deadEndRule, cannotReachEndRule, splitJoinPairingRule, nonInterruptingStartRule, instantiatingGatewayRule, adHocCompletionConditionRule, compensationAssociationRule, taskMessageFlowRule, signalCatchRule, errorCatchRule, escalationCatchRule];
|
|
18
|
+
export const findingSeverities = () => ({
|
|
19
|
+
[STRUCTURAL_RULE]: "error",
|
|
20
|
+
...Object.fromEntries(rules.flatMap((rule) => (rule === splitJoinPairingRule ? splitJoinFindingIds : [rule.id]).map((id) => [id, rule.severity]))),
|
|
21
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { hasType } from "../scope-graph.js";
|
|
2
|
+
export const instantiatingGatewayRule = {
|
|
3
|
+
id: "instantiating-gateway-with-incoming-flow",
|
|
4
|
+
severity: "error",
|
|
5
|
+
check: (scope) => scope.nodes
|
|
6
|
+
.filter((node) => node.instantiating && hasType(node, "EventBasedGateway"))
|
|
7
|
+
.filter((node) => scope.flows.some((flow) => flow.target === node.id))
|
|
8
|
+
.map((node) => ({
|
|
9
|
+
rule: "instantiating-gateway-with-incoming-flow",
|
|
10
|
+
severity: "error",
|
|
11
|
+
node: node.id,
|
|
12
|
+
message: "instantiating event-based gateway has an incoming sequence flow, but it starts the process and must have none",
|
|
13
|
+
})),
|
|
14
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export const nonInterruptingStartRule = {
|
|
2
|
+
id: "non-interrupting-start-outside-event-sub-process",
|
|
3
|
+
severity: "warning",
|
|
4
|
+
check: (scope) => scope.eventSubProcess
|
|
5
|
+
? []
|
|
6
|
+
: scope.nodes
|
|
7
|
+
.filter((node) => node.nonInterrupting)
|
|
8
|
+
.map((node) => ({
|
|
9
|
+
rule: "non-interrupting-start-outside-event-sub-process",
|
|
10
|
+
severity: "warning",
|
|
11
|
+
node: node.id,
|
|
12
|
+
message: "start event is non-interrupting but is not in an event sub-process, so isInterrupting has no effect",
|
|
13
|
+
})),
|
|
14
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { hasType } from "../scope-graph.js";
|
|
2
|
+
export const reachabilityRule = {
|
|
3
|
+
id: "node-unreachable",
|
|
4
|
+
severity: "warning",
|
|
5
|
+
check: (scope) => {
|
|
6
|
+
if (scope.adHoc || !scope.nodes.some((node) => hasType(node, "StartEvent")))
|
|
7
|
+
return [];
|
|
8
|
+
const reached = new Set(scope.nodes.filter((node) => node.exempt || hasType(node, "StartEvent")).map((node) => node.id));
|
|
9
|
+
const queue = [...reached];
|
|
10
|
+
const visit = (id) => {
|
|
11
|
+
if (!reached.has(id)) {
|
|
12
|
+
reached.add(id);
|
|
13
|
+
queue.push(id);
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
while (queue.length > 0) {
|
|
17
|
+
const current = queue.shift();
|
|
18
|
+
scope.flows.filter((flow) => flow.source === current && flow.target !== null).forEach((flow) => visit(flow.target));
|
|
19
|
+
scope.nodes.filter((node) => node.attachedTo === current).forEach((node) => visit(node.id));
|
|
20
|
+
}
|
|
21
|
+
return scope.nodes
|
|
22
|
+
.filter((node) => !reached.has(node.id))
|
|
23
|
+
.map((node) => ({ rule: "node-unreachable", severity: "warning", node: node.id, message: "flow node is not reachable from a start event" }));
|
|
24
|
+
},
|
|
25
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export const signalCatchRule = {
|
|
2
|
+
id: "signal-throw-without-catch",
|
|
3
|
+
severity: "warning",
|
|
4
|
+
check: (scope) => scope.nodes
|
|
5
|
+
.filter((node) => node.signalThrows.some((signal) => !scope.caughtSignals.includes(signal)))
|
|
6
|
+
.map((node) => ({
|
|
7
|
+
rule: "signal-throw-without-catch",
|
|
8
|
+
severity: "warning",
|
|
9
|
+
node: node.id,
|
|
10
|
+
message: "signal throw event references a signal that no start, intermediate catch or boundary event in the document catches",
|
|
11
|
+
})),
|
|
12
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { bpmn, hasType } from "../scope-graph.js";
|
|
2
|
+
const MISMATCHES = [
|
|
3
|
+
{ split: "ExclusiveGateway", join: "ParallelGateway", rule: "split-join-exclusive-to-parallel", message: "exclusive split is merged by a parallel join, which may deadlock" },
|
|
4
|
+
{ split: "ParallelGateway", join: "ExclusiveGateway", rule: "split-join-parallel-to-exclusive", message: "parallel split is merged by an exclusive join, which may activate the flow more than once" },
|
|
5
|
+
];
|
|
6
|
+
export const splitJoinFindingIds = MISMATCHES.map((mismatch) => mismatch.rule);
|
|
7
|
+
const successors = (scope) => {
|
|
8
|
+
const next = new Map();
|
|
9
|
+
for (const flow of scope.flows) {
|
|
10
|
+
if (flow.source !== null && flow.target !== null)
|
|
11
|
+
next.set(flow.source, [...(next.get(flow.source) ?? []), flow.target]);
|
|
12
|
+
}
|
|
13
|
+
return next;
|
|
14
|
+
};
|
|
15
|
+
const reachableFrom = (start, split, next) => {
|
|
16
|
+
const seen = new Set();
|
|
17
|
+
const queue = [start];
|
|
18
|
+
while (queue.length > 0) {
|
|
19
|
+
const id = queue.shift();
|
|
20
|
+
if (id === split || seen.has(id))
|
|
21
|
+
continue;
|
|
22
|
+
seen.add(id);
|
|
23
|
+
queue.push(...(next.get(id) ?? []));
|
|
24
|
+
}
|
|
25
|
+
return seen;
|
|
26
|
+
};
|
|
27
|
+
const nearestJoins = (scope, splitId, next) => {
|
|
28
|
+
const branches = (next.get(splitId) ?? []).map((start) => reachableFrom(start, splitId, next));
|
|
29
|
+
const merged = scope.nodes.filter((node) => node.direction === bpmn("Converging") && branches.filter((branch) => branch.has(node.id)).length >= 2);
|
|
30
|
+
const mergedIds = new Set(merged.map((node) => node.id));
|
|
31
|
+
const downstream = (id) => reachableFrom(id, splitId, next);
|
|
32
|
+
return merged.filter((node) => ![...mergedIds].some((other) => other !== node.id && downstream(other).has(node.id) && !downstream(node.id).has(other)));
|
|
33
|
+
};
|
|
34
|
+
export const splitJoinPairingRule = {
|
|
35
|
+
id: "split-join-pairing-mismatch",
|
|
36
|
+
severity: "warning",
|
|
37
|
+
check: (scope) => {
|
|
38
|
+
const next = successors(scope);
|
|
39
|
+
const found = new Map();
|
|
40
|
+
for (const split of scope.nodes.filter((node) => node.direction === bpmn("Diverging"))) {
|
|
41
|
+
for (const join of nearestJoins(scope, split.id, next)) {
|
|
42
|
+
for (const mismatch of MISMATCHES) {
|
|
43
|
+
if (hasType(split, mismatch.split) && hasType(join, mismatch.join))
|
|
44
|
+
found.set(`${mismatch.rule}|${join.id}`, { rule: mismatch.rule, message: mismatch.message, node: join.id });
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return [...found.values()].map((entry) => ({ ...entry, severity: "warning" }));
|
|
49
|
+
},
|
|
50
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { hasType, isPlaceholder } from "../scope-graph.js";
|
|
2
|
+
export const startEventRule = {
|
|
3
|
+
id: "start-event-missing",
|
|
4
|
+
severity: "error",
|
|
5
|
+
check: (scope) => isPlaceholder(scope) || scope.adHoc || scope.nodes.some((node) => node.instantiating) || scope.nodes.some((node) => hasType(node, "StartEvent"))
|
|
6
|
+
? []
|
|
7
|
+
: [{ rule: "start-event-missing", severity: "error", node: scope.id, message: `${scope.kind === "process" ? "process" : "sub-process"} has no start event` }],
|
|
8
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { hasType } from "../scope-graph.js";
|
|
2
|
+
export const taskMessageFlowRule = {
|
|
3
|
+
id: "task-message-without-message-flow",
|
|
4
|
+
severity: "warning",
|
|
5
|
+
check: (scope) => scope.nodes
|
|
6
|
+
.filter((node) => node.messageRef !== null && (hasType(node, "SendTask") || hasType(node, "ReceiveTask")))
|
|
7
|
+
.filter((node) => {
|
|
8
|
+
const sends = hasType(node, "SendTask");
|
|
9
|
+
return !scope.messageFlows.some((messageFlow) => (sends ? messageFlow.source : messageFlow.target) === node.id && (messageFlow.messageRef === null || messageFlow.messageRef === node.messageRef));
|
|
10
|
+
})
|
|
11
|
+
.map((node) => ({
|
|
12
|
+
rule: "task-message-without-message-flow",
|
|
13
|
+
severity: "warning",
|
|
14
|
+
node: node.id,
|
|
15
|
+
message: hasType(node, "SendTask")
|
|
16
|
+
? "send task references a message but no message flow that leaves it carries that message"
|
|
17
|
+
: "receive task references a message but no message flow that enters it carries that message",
|
|
18
|
+
})),
|
|
19
|
+
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { Store } from "n3";
|
|
2
|
+
import type { Node } from "../validate.ts";
|
|
3
|
+
export interface FlowNode {
|
|
4
|
+
id: string;
|
|
5
|
+
types: string[];
|
|
6
|
+
attachedTo: string | null;
|
|
7
|
+
direction: string | null;
|
|
8
|
+
exempt: boolean;
|
|
9
|
+
forCompensation: boolean;
|
|
10
|
+
compensationBoundary: boolean;
|
|
11
|
+
nonInterrupting: boolean;
|
|
12
|
+
instantiating: boolean;
|
|
13
|
+
messageRef: string | null;
|
|
14
|
+
signalThrows: string[];
|
|
15
|
+
errorThrows: string[];
|
|
16
|
+
escalationThrows: string[];
|
|
17
|
+
}
|
|
18
|
+
export interface MessageFlow {
|
|
19
|
+
id: string;
|
|
20
|
+
source: string | null;
|
|
21
|
+
target: string | null;
|
|
22
|
+
messageRef: string | null;
|
|
23
|
+
}
|
|
24
|
+
export interface SequenceFlow {
|
|
25
|
+
id: string;
|
|
26
|
+
source: string | null;
|
|
27
|
+
target: string | null;
|
|
28
|
+
}
|
|
29
|
+
export interface Association {
|
|
30
|
+
id: string;
|
|
31
|
+
source: string | null;
|
|
32
|
+
target: string | null;
|
|
33
|
+
}
|
|
34
|
+
export type ScopeKind = "process" | "subProcess";
|
|
35
|
+
export interface Scope {
|
|
36
|
+
id: string;
|
|
37
|
+
kind: ScopeKind;
|
|
38
|
+
parent: string | null;
|
|
39
|
+
eventSubProcess: boolean;
|
|
40
|
+
adHoc: boolean;
|
|
41
|
+
completionCondition: boolean;
|
|
42
|
+
participant: string | null;
|
|
43
|
+
nodes: FlowNode[];
|
|
44
|
+
flows: SequenceFlow[];
|
|
45
|
+
associations: Association[];
|
|
46
|
+
messageFlows: MessageFlow[];
|
|
47
|
+
caughtSignals: string[];
|
|
48
|
+
caughtErrors: string[];
|
|
49
|
+
catchesAnyError: boolean;
|
|
50
|
+
caughtEscalations: string[];
|
|
51
|
+
catchesAnyEscalation: boolean;
|
|
52
|
+
}
|
|
53
|
+
export declare const isPlaceholder: (scope: Scope) => boolean;
|
|
54
|
+
export declare const bpmn: (name: string) => string;
|
|
55
|
+
export declare const hasType: (node: FlowNode, name: string) => boolean;
|
|
56
|
+
export declare const buildScopes: (expanded: Node[], store?: Store) => Scope[];
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { BPMN_NAMESPACE } from "../namespace.js";
|
|
2
|
+
import { isSubClassOf, loadOntology } from "../ontology.js";
|
|
3
|
+
export const isPlaceholder = (scope) => scope.kind === "subProcess" && scope.nodes.length === 0;
|
|
4
|
+
export const bpmn = (name) => `${BPMN_NAMESPACE}${name}`;
|
|
5
|
+
export const hasType = (node, name) => node.types.includes(bpmn(name));
|
|
6
|
+
const valuesOf = (value) => value.flatMap((v) => ("@list" in v ? v["@list"] : [v]));
|
|
7
|
+
const indexNode = (node, index) => {
|
|
8
|
+
const id = node["@id"];
|
|
9
|
+
const entry = id === undefined ? undefined : (index.get(id) ?? { types: new Set(), properties: new Map() });
|
|
10
|
+
if (id !== undefined && entry !== undefined)
|
|
11
|
+
index.set(id, entry);
|
|
12
|
+
for (const [key, value] of Object.entries(node)) {
|
|
13
|
+
if (key === "@type")
|
|
14
|
+
value.forEach((t) => entry?.types.add(t));
|
|
15
|
+
else if (!key.startsWith("@")) {
|
|
16
|
+
const members = valuesOf(value);
|
|
17
|
+
entry?.properties.set(key, [...(entry.properties.get(key) ?? []), ...members]);
|
|
18
|
+
members.filter((m) => !("@value" in m)).forEach((m) => indexNode(m, index));
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
const hasEventDefinition = (entry, definition) => (entry.properties.get(bpmn("eventDefinitions")) ?? []).some((candidate) => (candidate["@type"] ?? []).includes(bpmn(definition)));
|
|
23
|
+
const definitionsOf = (entry, definition) => (entry.properties.get(bpmn("eventDefinitions")) ?? []).filter((candidate) => (candidate["@type"] ?? []).includes(bpmn(definition)));
|
|
24
|
+
const definitionRefs = (definition, property) => (definition[bpmn(property)] ?? []).flatMap((ref) => (typeof ref["@id"] === "string" ? [ref["@id"]] : []));
|
|
25
|
+
const signalRefsOf = (entry) => definitionsOf(entry, "SignalEventDefinition").flatMap((definition) => definitionRefs(definition, "signalRef"));
|
|
26
|
+
const errorRefsOf = (entry) => definitionsOf(entry, "ErrorEventDefinition").flatMap((definition) => definitionRefs(definition, "errorRef"));
|
|
27
|
+
const escalationRefsOf = (entry) => definitionsOf(entry, "EscalationEventDefinition").flatMap((definition) => definitionRefs(definition, "escalationRef"));
|
|
28
|
+
const EXEMPT_EVENT_DEFINITIONS = [bpmn("LinkEventDefinition"), bpmn("SignalEventDefinition")];
|
|
29
|
+
const flagOf = (entry, property) => (entry.properties.get(bpmn(property)) ?? []).some((m) => m["@value"] === true);
|
|
30
|
+
const refsOf = (entry, property) => (entry?.properties.get(bpmn(property)) ?? []).flatMap((m) => (typeof m["@id"] === "string" ? [m["@id"]] : []));
|
|
31
|
+
export const buildScopes = (expanded, store = loadOntology()) => {
|
|
32
|
+
const index = new Map();
|
|
33
|
+
expanded.forEach((node) => indexNode(node, index));
|
|
34
|
+
const isA = (entry, name) => [...entry.types].some((t) => isSubClassOf(store, t, bpmn(name)));
|
|
35
|
+
const isExempt = (member) => flagOf(member, "isForCompensation") ||
|
|
36
|
+
flagOf(member, "triggeredByEvent") ||
|
|
37
|
+
(isA(member, "BoundaryEvent") && hasEventDefinition(member, "CompensateEventDefinition")) ||
|
|
38
|
+
(isA(member, "CatchEvent") &&
|
|
39
|
+
!isA(member, "BoundaryEvent") &&
|
|
40
|
+
(member.properties.get(bpmn("eventDefinitions")) ?? []).some((definition) => (definition["@type"] ?? []).some((t) => EXEMPT_EVENT_DEFINITIONS.includes(t))));
|
|
41
|
+
const participants = new Map();
|
|
42
|
+
for (const [id, entry] of index) {
|
|
43
|
+
if (isA(entry, "Participant"))
|
|
44
|
+
refsOf(entry, "processRef").forEach((processId) => participants.set(processId, id));
|
|
45
|
+
}
|
|
46
|
+
const messageFlows = [];
|
|
47
|
+
for (const [id, entry] of index) {
|
|
48
|
+
if (isA(entry, "MessageFlow")) {
|
|
49
|
+
messageFlows.push({ id, source: refsOf(entry, "sourceRef")[0] ?? null, target: refsOf(entry, "targetRef")[0] ?? null, messageRef: refsOf(entry, "messageRef")[0] ?? null });
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
const caughtSignals = [...new Set([...index.values()].filter((entry) => isA(entry, "CatchEvent")).flatMap(signalRefsOf))];
|
|
53
|
+
const catchEvents = [...index.values()].filter((entry) => isA(entry, "BoundaryEvent") || isA(entry, "StartEvent"));
|
|
54
|
+
const caughtErrors = [...new Set(catchEvents.flatMap(errorRefsOf))];
|
|
55
|
+
const catchesAnyError = catchEvents.some((entry) => definitionsOf(entry, "ErrorEventDefinition").some((definition) => definitionRefs(definition, "errorRef").length === 0));
|
|
56
|
+
const caughtEscalations = [...new Set(catchEvents.flatMap(escalationRefsOf))];
|
|
57
|
+
const catchesAnyEscalation = catchEvents.some((entry) => definitionsOf(entry, "EscalationEventDefinition").some((definition) => definitionRefs(definition, "escalationRef").length === 0));
|
|
58
|
+
const scopes = [];
|
|
59
|
+
const addScope = (id, entry, kind, parent) => {
|
|
60
|
+
const scope = { id, kind, parent, eventSubProcess: flagOf(entry, "triggeredByEvent"), adHoc: isA(entry, "AdHocSubProcess"), completionCondition: (entry.properties.get(bpmn("completionCondition")) ?? []).length > 0, participant: participants.get(id) ?? null, nodes: [], flows: [], associations: [], messageFlows, caughtSignals, caughtErrors, catchesAnyError, caughtEscalations, catchesAnyEscalation };
|
|
61
|
+
scopes.push(scope);
|
|
62
|
+
const subProcesses = [];
|
|
63
|
+
for (const artifactId of refsOf(entry, "artifacts")) {
|
|
64
|
+
const artifact = index.get(artifactId);
|
|
65
|
+
if (artifact !== undefined && isA(artifact, "Association")) {
|
|
66
|
+
scope.associations.push({ id: artifactId, source: refsOf(artifact, "sourceRef")[0] ?? null, target: refsOf(artifact, "targetRef")[0] ?? null });
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
for (const memberId of refsOf(entry, "flowElements")) {
|
|
70
|
+
const member = index.get(memberId);
|
|
71
|
+
if (member === undefined)
|
|
72
|
+
continue;
|
|
73
|
+
if (isA(member, "SequenceFlow")) {
|
|
74
|
+
scope.flows.push({ id: memberId, source: refsOf(member, "sourceRef")[0] ?? null, target: refsOf(member, "targetRef")[0] ?? null });
|
|
75
|
+
}
|
|
76
|
+
else if (isA(member, "FlowNode")) {
|
|
77
|
+
scope.nodes.push({ id: memberId, types: [...member.types], attachedTo: refsOf(member, "attachedToRef")[0] ?? null, direction: refsOf(member, "gatewayDirection")[0] ?? null, exempt: isExempt(member), forCompensation: flagOf(member, "isForCompensation"), compensationBoundary: isA(member, "BoundaryEvent") && hasEventDefinition(member, "CompensateEventDefinition"), nonInterrupting: isA(member, "StartEvent") && (member.properties.get(bpmn("isInterrupting")) ?? []).some((m) => m["@value"] === false), instantiating: flagOf(member, "instantiate"), messageRef: refsOf(member, "messageRef")[0] ?? null, signalThrows: isA(member, "ThrowEvent") ? signalRefsOf(member) : [], errorThrows: isA(member, "EndEvent") ? errorRefsOf(member) : [], escalationThrows: isA(member, "ThrowEvent") ? escalationRefsOf(member) : [] });
|
|
78
|
+
if (isA(member, "SubProcess"))
|
|
79
|
+
subProcesses.push([memberId, member]);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
subProcesses.forEach(([subId, sub]) => addScope(subId, sub, "subProcess", id));
|
|
83
|
+
};
|
|
84
|
+
for (const [id, entry] of index) {
|
|
85
|
+
if (isA(entry, "Process"))
|
|
86
|
+
addScope(id, entry, "process", null);
|
|
87
|
+
}
|
|
88
|
+
return scopes;
|
|
89
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Finding } from "./analyze/findings.ts";
|
|
2
|
+
export declare const EXIT_OK = 0;
|
|
3
|
+
export declare const EXIT_FINDINGS = 1;
|
|
4
|
+
export declare const EXIT_INPUT = 2;
|
|
5
|
+
export declare const exitCodeFor: (findings: Finding[]) => number;
|
|
6
|
+
export interface CliResult {
|
|
7
|
+
exitCode: number;
|
|
8
|
+
stdout: string;
|
|
9
|
+
stderr: string;
|
|
10
|
+
}
|
|
11
|
+
export declare const USAGE = "Usage: analyze <file.jsonld> [options]\n\nAnalyzes a BPMN-LD document and reports process-level findings.\n\nOptions:\n --format <text|json> Output format (default: text)\n -h, --help Show this help\n\nExit codes:\n 0 No findings, or warnings only\n 1 At least one error finding\n 2 Usage error or unusable input (missing file, invalid JSON)\n";
|
|
12
|
+
export declare const runAnalyze: (argv: string[]) => Promise<CliResult>;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { pathToFileURL } from "node:url";
|
|
4
|
+
import { analyze } from "./analyze/analyze.js";
|
|
5
|
+
import { formatJson, formatText } from "./analyze/report.js";
|
|
6
|
+
export const EXIT_OK = 0;
|
|
7
|
+
export const EXIT_FINDINGS = 1;
|
|
8
|
+
export const EXIT_INPUT = 2;
|
|
9
|
+
export const exitCodeFor = (findings) => (findings.some((f) => f.severity === "error") ? EXIT_FINDINGS : EXIT_OK);
|
|
10
|
+
export const USAGE = `Usage: analyze <file.jsonld> [options]
|
|
11
|
+
|
|
12
|
+
Analyzes a BPMN-LD document and reports process-level findings.
|
|
13
|
+
|
|
14
|
+
Options:
|
|
15
|
+
--format <text|json> Output format (default: text)
|
|
16
|
+
-h, --help Show this help
|
|
17
|
+
|
|
18
|
+
Exit codes:
|
|
19
|
+
0 No findings, or warnings only
|
|
20
|
+
1 At least one error finding
|
|
21
|
+
2 Usage error or unusable input (missing file, invalid JSON)
|
|
22
|
+
`;
|
|
23
|
+
class UsageError extends Error {
|
|
24
|
+
}
|
|
25
|
+
const parseArgs = (argv) => {
|
|
26
|
+
const options = { input: "", format: "text" };
|
|
27
|
+
let input;
|
|
28
|
+
for (let i = 0; i < argv.length; i++) {
|
|
29
|
+
const arg = argv[i];
|
|
30
|
+
if (arg === "-h" || arg === "--help")
|
|
31
|
+
return "help";
|
|
32
|
+
else if (arg === "--format") {
|
|
33
|
+
const value = argv[++i];
|
|
34
|
+
if (value !== "text" && value !== "json")
|
|
35
|
+
throw new UsageError("--format requires text or json");
|
|
36
|
+
options.format = value;
|
|
37
|
+
}
|
|
38
|
+
else if (arg.startsWith("-"))
|
|
39
|
+
throw new UsageError(`unknown option ${arg}`);
|
|
40
|
+
else if (input !== undefined)
|
|
41
|
+
throw new UsageError(`unexpected extra argument ${arg}`);
|
|
42
|
+
else
|
|
43
|
+
input = arg;
|
|
44
|
+
}
|
|
45
|
+
if (input === undefined)
|
|
46
|
+
throw new UsageError("missing input file");
|
|
47
|
+
options.input = input;
|
|
48
|
+
return options;
|
|
49
|
+
};
|
|
50
|
+
const failure = (message, usage = false) => ({
|
|
51
|
+
exitCode: EXIT_INPUT,
|
|
52
|
+
stdout: "",
|
|
53
|
+
stderr: `error: ${message}\n${usage ? `\n${USAGE}` : ""}`,
|
|
54
|
+
});
|
|
55
|
+
export const runAnalyze = async (argv) => {
|
|
56
|
+
let options;
|
|
57
|
+
try {
|
|
58
|
+
options = parseArgs(argv);
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
if (error instanceof UsageError)
|
|
62
|
+
return failure(error.message, true);
|
|
63
|
+
throw error;
|
|
64
|
+
}
|
|
65
|
+
if (options === "help")
|
|
66
|
+
return { exitCode: EXIT_OK, stdout: USAGE, stderr: "" };
|
|
67
|
+
let text;
|
|
68
|
+
try {
|
|
69
|
+
text = readFileSync(options.input, "utf8");
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
const reason = error.code === "ENOENT" ? "no such file" : error.message;
|
|
73
|
+
return failure(`cannot read ${options.input}: ${reason}`);
|
|
74
|
+
}
|
|
75
|
+
let document;
|
|
76
|
+
try {
|
|
77
|
+
document = JSON.parse(text);
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
return failure(`${options.input} is not valid JSON: ${error.message}`);
|
|
81
|
+
}
|
|
82
|
+
const findings = await analyze(document, pathToFileURL(resolve(options.input)).href);
|
|
83
|
+
const stdout = options.format === "json" ? formatJson(findings) : formatText(findings);
|
|
84
|
+
return { exitCode: exitCodeFor(findings), stdout, stderr: "" };
|
|
85
|
+
};
|