@uipath/maestro-builder-sdk 5.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 +21 -0
- package/README.md +322 -0
- package/dist/bindings-v2.d.ts +37 -0
- package/dist/bindings-v2.js +155 -0
- package/dist/bindings.d.ts +6 -0
- package/dist/bindings.js +6 -0
- package/dist/bpmn/bpmn-expr-check.d.ts +41 -0
- package/dist/bpmn/bpmn-expr-check.js +218 -0
- package/dist/bpmn/bpmn-sdk.d.ts +2257 -0
- package/dist/bpmn/bpmn-sdk.js +1476 -0
- package/dist/bpmn/check-cli.d.ts +3 -0
- package/dist/bpmn/check-cli.js +32 -0
- package/dist/bpmn/check.d.ts +25 -0
- package/dist/bpmn/check.js +430 -0
- package/dist/bpmn/compile-cli.d.ts +3 -0
- package/dist/bpmn/compile-cli.js +60 -0
- package/dist/bpmn/connector.d.ts +56 -0
- package/dist/bpmn/connector.js +199 -0
- package/dist/bpmn/decompile-cli.d.ts +8 -0
- package/dist/bpmn/decompile-cli.js +54 -0
- package/dist/bpmn/decompile.d.ts +6 -0
- package/dist/bpmn/decompile.js +1108 -0
- package/dist/bpmn/format-profile.d.ts +27 -0
- package/dist/bpmn/format-profile.js +11 -0
- package/dist/bpmn/intsvc.d.ts +77 -0
- package/dist/bpmn/intsvc.js +155 -0
- package/dist/bpmn/load.d.ts +4 -0
- package/dist/bpmn/load.js +22 -0
- package/dist/bpmn/merge-cli.d.ts +3 -0
- package/dist/bpmn/merge-cli.js +45 -0
- package/dist/bpmn/merge.d.ts +19 -0
- package/dist/bpmn/merge.js +293 -0
- package/dist/bpmn/registry-types.json +1869 -0
- package/dist/bpmn/serialize.d.ts +28 -0
- package/dist/bpmn/serialize.js +757 -0
- package/dist/bpmn/tidy-cli.d.ts +2 -0
- package/dist/bpmn/tidy-cli.js +39 -0
- package/dist/bpmn/typed-node.d.ts +382 -0
- package/dist/bpmn/typed-node.js +551 -0
- package/dist/bpmn/uipath-moddle.v1.json +823 -0
- package/dist/bpmn/xml-ranges.d.ts +45 -0
- package/dist/bpmn/xml-ranges.js +151 -0
- package/dist/case/case-expr-check.d.ts +35 -0
- package/dist/case/case-expr-check.js +85 -0
- package/dist/case/case-sdk.d.ts +1282 -0
- package/dist/case/case-sdk.js +923 -0
- package/dist/case/check-cli.d.ts +3 -0
- package/dist/case/check-cli.js +31 -0
- package/dist/case/check.d.ts +18 -0
- package/dist/case/check.js +456 -0
- package/dist/case/compile-cli.d.ts +3 -0
- package/dist/case/compile-cli.js +291 -0
- package/dist/case/decompile-cli.d.ts +9 -0
- package/dist/case/decompile-cli.js +117 -0
- package/dist/case/decompile.d.ts +187 -0
- package/dist/case/decompile.js +1170 -0
- package/dist/case/entry-points.d.ts +79 -0
- package/dist/case/entry-points.js +118 -0
- package/dist/case/format-profile.d.ts +60 -0
- package/dist/case/format-profile.js +79 -0
- package/dist/case/ids.d.ts +24 -0
- package/dist/case/ids.js +76 -0
- package/dist/case/load.d.ts +12 -0
- package/dist/case/load.js +32 -0
- package/dist/case/preserve.d.ts +133 -0
- package/dist/case/preserve.js +105 -0
- package/dist/case/serialize.d.ts +10 -0
- package/dist/case/serialize.js +1414 -0
- package/dist/check-cli.d.ts +3 -0
- package/dist/check-cli.js +118 -0
- package/dist/check.d.ts +146 -0
- package/dist/check.js +4898 -0
- package/dist/cli/commands/bpmn.d.ts +2 -0
- package/dist/cli/commands/bpmn.js +73 -0
- package/dist/cli/commands/case.d.ts +2 -0
- package/dist/cli/commands/case.js +60 -0
- package/dist/cli/commands/flow.d.ts +2 -0
- package/dist/cli/commands/flow.js +99 -0
- package/dist/cli/commands/registry.d.ts +10 -0
- package/dist/cli/commands/registry.js +170 -0
- package/dist/cli/common.d.ts +30 -0
- package/dist/cli/common.js +92 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +18 -0
- package/dist/cli-run.d.ts +2 -0
- package/dist/cli-run.js +42 -0
- package/dist/compile-cli.d.ts +3 -0
- package/dist/compile-cli.js +160 -0
- package/dist/config.d.ts +47 -0
- package/dist/config.js +201 -0
- package/dist/core/actions.d.ts +3244 -0
- package/dist/core/actions.js +1062 -0
- package/dist/core/binding-messages.d.ts +1 -0
- package/dist/core/binding-messages.js +16 -0
- package/dist/core/bindings.d.ts +12 -0
- package/dist/core/bindings.js +67 -0
- package/dist/core/ceql.d.ts +73 -0
- package/dist/core/ceql.js +272 -0
- package/dist/core/cli-spelling.d.ts +39 -0
- package/dist/core/cli-spelling.js +62 -0
- package/dist/core/connector-raw-node.d.ts +21 -0
- package/dist/core/connector-raw-node.js +47 -0
- package/dist/core/connectors.d.ts +184 -0
- package/dist/core/connectors.js +128 -0
- package/dist/core/expr-check.d.ts +93 -0
- package/dist/core/expr-check.js +186 -0
- package/dist/core/expr.d.ts +323 -0
- package/dist/core/expr.js +400 -0
- package/dist/core/index.d.ts +10 -0
- package/dist/core/index.js +10 -0
- package/dist/core/library.d.ts +440 -0
- package/dist/core/library.js +1183 -0
- package/dist/core/list-envelope.d.ts +45 -0
- package/dist/core/list-envelope.js +97 -0
- package/dist/core/lookup-store.d.ts +22 -0
- package/dist/core/lookup-store.js +85 -0
- package/dist/core/lookups.d.ts +227 -0
- package/dist/core/lookups.js +231 -0
- package/dist/core/node-classes.d.ts +234 -0
- package/dist/core/node-classes.js +158 -0
- package/dist/core/script-return.d.ts +68 -0
- package/dist/core/script-return.js +197 -0
- package/dist/core/stable-id.d.ts +28 -0
- package/dist/core/stable-id.js +37 -0
- package/dist/core-definitions.json +16249 -0
- package/dist/decompile-cli.d.ts +3 -0
- package/dist/decompile-cli.js +131 -0
- package/dist/decompile.d.ts +138 -0
- package/dist/decompile.js +2482 -0
- package/dist/emit.d.ts +10 -0
- package/dist/emit.js +60 -0
- package/dist/event-filters.d.ts +130 -0
- package/dist/event-filters.js +137 -0
- package/dist/flow-expr-check.d.ts +17 -0
- package/dist/flow-expr-check.js +193 -0
- package/dist/flow-sdk.d.ts +1341 -0
- package/dist/flow-sdk.js +1164 -0
- package/dist/flow-variables.d.ts +13 -0
- package/dist/flow-variables.js +42 -0
- package/dist/format-profile.d.ts +72 -0
- package/dist/format-profile.js +97 -0
- package/dist/generators/_connections.py +287 -0
- package/dist/generators/_events.py +355 -0
- package/dist/generators/_lookups.py +114 -0
- package/dist/generators/_naming.py +185 -0
- package/dist/generators/_objects.py +299 -0
- package/dist/generators/_resolve.py +273 -0
- package/dist/generators/convert_library_to_md.py +1441 -0
- package/dist/generators/extract_library.py +125 -0
- package/dist/generators/generate_connectors_ts.py +936 -0
- package/dist/generators/generate_library_json.py +2527 -0
- package/dist/generators/generate_triggers_ts.py +110 -0
- package/dist/generators/materialize_registry.py +155 -0
- package/dist/generators/prepare_connector.py +1688 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.js +35 -0
- package/dist/ixp-definition.json +1982 -0
- package/dist/library.d.ts +6 -0
- package/dist/library.js +6 -0
- package/dist/load.d.ts +16 -0
- package/dist/load.js +95 -0
- package/dist/merge-cli.d.ts +3 -0
- package/dist/merge-cli.js +47 -0
- package/dist/merge.d.ts +125 -0
- package/dist/merge.js +0 -0
- package/dist/migrate.d.ts +15 -0
- package/dist/migrate.js +245 -0
- package/dist/node-runtime.d.ts +3 -0
- package/dist/node-runtime.js +22 -0
- package/dist/package-name.d.ts +17 -0
- package/dist/package-name.js +17 -0
- package/dist/ref-resolve.d.ts +77 -0
- package/dist/ref-resolve.js +170 -0
- package/dist/registry/cache.d.ts +55 -0
- package/dist/registry/cache.js +134 -0
- package/dist/registry/curated-overlay.d.ts +16 -0
- package/dist/registry/curated-overlay.js +62 -0
- package/dist/registry/extractor.d.ts +22 -0
- package/dist/registry/extractor.js +18 -0
- package/dist/registry/materialize-bin.d.ts +2 -0
- package/dist/registry/materialize-bin.js +7 -0
- package/dist/registry/materialize-cli.d.ts +1 -0
- package/dist/registry/materialize-cli.js +46 -0
- package/dist/registry/materializer.d.ts +23 -0
- package/dist/registry/materializer.js +34 -0
- package/dist/registry/path-cli.d.ts +1 -0
- package/dist/registry/path-cli.js +56 -0
- package/dist/registry/prepare-cli.d.ts +62 -0
- package/dist/registry/prepare-cli.js +185 -0
- package/dist/registry/pull-cli.d.ts +1 -0
- package/dist/registry/pull-cli.js +274 -0
- package/dist/registry/python.d.ts +26 -0
- package/dist/registry/python.js +46 -0
- package/dist/registry/search-cli.d.ts +52 -0
- package/dist/registry/search-cli.js +159 -0
- package/dist/schema-version.d.ts +165 -0
- package/dist/schema-version.js +148 -0
- package/dist/serialize.d.ts +64 -0
- package/dist/serialize.js +6398 -0
- package/lib/README.md +112 -0
- package/lib/library-json.samples/index.json +158 -0
- package/lib/library-json.samples/invariant-collections.json +83 -0
- package/lib/library-json.samples/list-envelopes.json +123 -0
- package/lib/library-json.samples/uipath-atlassian-jira/create-issue@1.0.0.json +131 -0
- package/lib/library-json.samples/uipath-atlassian-jira/create-issue@1.0.0.v1def.json +306 -0
- package/lib/library-json.samples/uipath-atlassian-jira/generic/create_issue/create@1.0.0.json +86 -0
- package/lib/library-json.samples/uipath-atlassian-jira/generic/create_issue/create@1.0.0.v1def.json +208 -0
- package/lib/library-json.samples/uipath-atlassian-jira/get-issue@1.0.0.json +2040 -0
- package/lib/library-json.samples/uipath-atlassian-jira/get-issue@1.0.0.v1def.json +195 -0
- package/lib/library-json.samples/uipath-google-vertex/execute-google-vertex-agent@1.0.0.json +169 -0
- package/lib/library-json.samples/uipath-google-vertex/execute-google-vertex-agent@1.0.0.v1def.json +209 -0
- package/lib/library-json.samples/uipath-http-webhook/event-http-webhook@1.0.0.json +63 -0
- package/lib/library-json.samples/uipath-http-webhook/event-http-webhook@1.0.0.v1def.json +262 -0
- package/lib/library-json.samples/uipath-http-webhook/trigger-http-webhook@1.0.0.json +63 -0
- package/lib/library-json.samples/uipath-http-webhook/trigger-http-webhook@1.0.0.v1def.json +213 -0
- package/lib/library-json.samples/uipath-microsoft-azureactivedirectory/list-groups@1.0.0.json +227 -0
- package/lib/library-json.samples/uipath-microsoft-azureactivedirectory/list-groups@1.0.0.v1def.json +373 -0
- package/lib/library-json.samples/uipath-microsoft-onedrive/event-file-created@1.0.0.json +116 -0
- package/lib/library-json.samples/uipath-microsoft-onedrive/event-file-created@1.0.0.v1def.json +369 -0
- package/lib/library-json.samples/uipath-microsoft-onedrive/event-list-item-added@1.0.0.json +56 -0
- package/lib/library-json.samples/uipath-microsoft-onedrive/event-list-item-added@1.0.0.v1def.json +256 -0
- package/lib/library-json.samples/uipath-microsoft-onedrive/trigger-file-created@1.0.0.json +116 -0
- package/lib/library-json.samples/uipath-microsoft-onedrive/trigger-file-created@1.0.0.v1def.json +320 -0
- package/lib/library-json.samples/uipath-microsoft-onedrive/trigger-list-item-added@1.0.0.json +56 -0
- package/lib/library-json.samples/uipath-microsoft-onedrive/trigger-list-item-added@1.0.0.v1def.json +207 -0
- package/lib/library-json.samples/uipath-microsoft-outlook365/event-email-received@1.0.0.json +107 -0
- package/lib/library-json.samples/uipath-microsoft-outlook365/event-email-received@1.0.0.v1def.json +264 -0
- package/lib/library-json.samples/uipath-microsoft-outlook365/send-email@1.0.0.json +142 -0
- package/lib/library-json.samples/uipath-microsoft-outlook365/send-email@1.0.0.v1def.json +306 -0
- package/lib/library-json.samples/uipath-microsoft-outlook365/trigger-email-received@1.0.0.json +107 -0
- package/lib/library-json.samples/uipath-microsoft-outlook365/trigger-email-received@1.0.0.v1def.json +217 -0
- package/lib/library-json.samples/uipath-microsoft-powerautomate/invoke-a-microsoft-power-automate-flow@1.0.0.json +85 -0
- package/lib/library-json.samples/uipath-microsoft-powerautomate/invoke-a-microsoft-power-automate-flow@1.0.0.v1def.json +209 -0
- package/lib/library-json.samples/uipath-salesforce-slack/send-message-to-user@1.0.0.json +755 -0
- package/lib/library-json.samples/uipath-salesforce-slack/send-message-to-user@1.0.0.v1def.json +513 -0
- package/lib/library-json.samples/uipath-uipath-dataservice/query-entity-records@1.0.0.json +122 -0
- package/lib/library-json.samples/uipath-uipath-dataservice/query-entity-records@1.0.0.v1def.json +227 -0
- package/package.json +85 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* check-cli — fast static validation of a <Name>.bpmn.ts (no output file).
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* node --experimental-strip-types check-cli.js <bpmn.ts | BaseName>
|
|
7
|
+
*
|
|
8
|
+
* Exit 0 = clean; exit 1 = at least one error. Warnings print but do not fail.
|
|
9
|
+
*/
|
|
10
|
+
import { resolveBpmnFile, loadBuiltBpmn } from './load.js';
|
|
11
|
+
import { check } from './check.js';
|
|
12
|
+
import { runWhenInvokedDirectly } from '../cli-run.js';
|
|
13
|
+
import { ensureTypeScriptRuntime } from '../node-runtime.js';
|
|
14
|
+
export async function run(argv) {
|
|
15
|
+
if (argv.length === 0 || argv[0] === '-h' || argv[0] === '--help') {
|
|
16
|
+
console.error('usage: check <bpmn.ts | BaseName>');
|
|
17
|
+
return 2;
|
|
18
|
+
}
|
|
19
|
+
ensureTypeScriptRuntime();
|
|
20
|
+
const built = await loadBuiltBpmn(resolveBpmnFile(argv[0]));
|
|
21
|
+
const diags = check(built);
|
|
22
|
+
for (const d of diags)
|
|
23
|
+
console.error(` ${d.level === 'error' ? '✗' : '⚠'} [${d.code}] ${d.message}`);
|
|
24
|
+
const errors = diags.filter((d) => d.level === 'error').length;
|
|
25
|
+
if (errors) {
|
|
26
|
+
console.error(`check: ${errors} error(s).`);
|
|
27
|
+
return 1;
|
|
28
|
+
}
|
|
29
|
+
console.error(`check: ok${diags.length ? ` (${diags.length} warning(s))` : ''}.`);
|
|
30
|
+
return 0;
|
|
31
|
+
}
|
|
32
|
+
runWhenInvokedDirectly(import.meta.url, 'check', run);
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* bpmn/check — fast static validation of a BuiltBpmn before serialization.
|
|
3
|
+
*
|
|
4
|
+
* Surfaces the common Maestro canvas-rule failures early (dangling flow refs,
|
|
5
|
+
* duplicate ids, a gateway branch with no condition, a superfluous gateway, an
|
|
6
|
+
* implicit join, a missing start event, a boundary event attached to nothing, an
|
|
7
|
+
* empty timer) so authoring gets instant feedback. The authoritative gate is
|
|
8
|
+
* still `uip maestro bpmn validate` (`validateBpmnCanvas`) on the emitted
|
|
9
|
+
* `.bpmn`.
|
|
10
|
+
*
|
|
11
|
+
* **An `error` here must mirror a real platform rule.** Being stricter than
|
|
12
|
+
* `validate` is worse than being looser: it refuses processes that deploy and run
|
|
13
|
+
* correctly, and the author has no way to appeal. Where this file is deliberately
|
|
14
|
+
* stricter than the platform — an unconditioned inclusive-gateway branch, an
|
|
15
|
+
* implicit join — the diagnostic is a `warning`, and its comment says which
|
|
16
|
+
* platform rule it corresponds to and how the two differ.
|
|
17
|
+
*/
|
|
18
|
+
import type { BuiltBpmn } from './bpmn-sdk.js';
|
|
19
|
+
export interface Diagnostic {
|
|
20
|
+
level: 'error' | 'warning';
|
|
21
|
+
code: string;
|
|
22
|
+
message: string;
|
|
23
|
+
where?: string;
|
|
24
|
+
}
|
|
25
|
+
export declare function check(built: BuiltBpmn): Diagnostic[];
|
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
import { processId } from './serialize.js';
|
|
2
|
+
import { bindingRequiredFields, missingRequiredContext, missingRequiredInputs, registryType, REGISTRY_GAPS, unsatisfiableRequiredInputs, } from './typed-node.js';
|
|
3
|
+
const GATEWAY_KINDS = new Set(['exclusiveGateway', 'parallelGateway', 'inclusiveGateway', 'eventBasedGateway']);
|
|
4
|
+
/**
|
|
5
|
+
* What a boundary event may attach to. `connector` belongs here: it serializes to
|
|
6
|
+
* a `bpmn:sendTask`, which is an activity like any other, and catching a
|
|
7
|
+
* connector's failure is the commonest error-handling shape there is.
|
|
8
|
+
*/
|
|
9
|
+
const ACTIVITY_KINDS = new Set(['task', 'scriptTask', 'subProcess', 'connector', 'typed', 'connectorEvent', 'externalTask']);
|
|
10
|
+
export function check(built) {
|
|
11
|
+
const diags = [];
|
|
12
|
+
if (!built.id)
|
|
13
|
+
diags.push({ level: 'error', code: 'NO_ID', message: 'bpmn() requires an id.' });
|
|
14
|
+
const seenIds = new Set();
|
|
15
|
+
checkScope(built.nodes, built.flows, 'process', diags, seenIds, true);
|
|
16
|
+
checkScopedVars(built, diags);
|
|
17
|
+
checkDocumentIds(built, diags);
|
|
18
|
+
return diags;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Every `id` in the document must be unique — including across KINDS.
|
|
22
|
+
*
|
|
23
|
+
* Not a style rule. `bpmn-moddle` keeps one document-global id registry, so a
|
|
24
|
+
* second use of an id makes its whole element **unparsable**, and it drops it:
|
|
25
|
+
*
|
|
26
|
+
* unparsable content <bpmn:task> detected … nested error: duplicate ID <after>
|
|
27
|
+
*
|
|
28
|
+
* `.var('after', …)` next to `.task('after', …)` therefore emits a `.bpmn` whose
|
|
29
|
+
* task simply is not there. Both validators still pass it — the canvas one
|
|
30
|
+
* downgrades the now-dangling flows to warnings and `uip maestro bpmn validate`
|
|
31
|
+
* reports `Valid` — and the runtime then dies with `Unknown element 'after'`, or
|
|
32
|
+
* worse, silently does no work if nothing else routes through the hole. So this
|
|
33
|
+
* is the only gate that can catch it, and it has to be an error.
|
|
34
|
+
*
|
|
35
|
+
* The ids in play are every element and flow, every variable and binding, the
|
|
36
|
+
* synthesized `Message_n`/`Error_n` declarations, and the process itself.
|
|
37
|
+
*/
|
|
38
|
+
function checkDocumentIds(built, diags) {
|
|
39
|
+
const owners = new Map();
|
|
40
|
+
const claim = (id, kind) => {
|
|
41
|
+
const existing = owners.get(id);
|
|
42
|
+
if (existing === undefined) {
|
|
43
|
+
owners.set(id, kind);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
diags.push({
|
|
47
|
+
level: 'error',
|
|
48
|
+
code: 'DUPLICATE_DOCUMENT_ID',
|
|
49
|
+
message: `id "${id}" is used by both ${existing} and ${kind}. Every id in a .bpmn must be unique — ` +
|
|
50
|
+
`the XML parser drops the second element that reuses one, so it disappears from the process.`,
|
|
51
|
+
where: id,
|
|
52
|
+
});
|
|
53
|
+
};
|
|
54
|
+
claim(built.id, 'the process');
|
|
55
|
+
const walk = (nodes, flows) => {
|
|
56
|
+
for (const n of nodes) {
|
|
57
|
+
claim(n.id, `element "${n.id}"`);
|
|
58
|
+
if (n.kind === 'subProcess') {
|
|
59
|
+
for (const v of n.variables)
|
|
60
|
+
claim(v.id, `variable "${v.id}"`);
|
|
61
|
+
walk(n.nodes, n.flows);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
for (const f of flows)
|
|
65
|
+
claim(f.id, `flow "${f.id}"`);
|
|
66
|
+
};
|
|
67
|
+
walk(built.nodes, built.flows);
|
|
68
|
+
for (const v of built.variables)
|
|
69
|
+
claim(v.id, `variable "${v.id}"`);
|
|
70
|
+
for (const b of built.bindings)
|
|
71
|
+
claim(b.id, `binding "${b.id}"`);
|
|
72
|
+
for (const mm of built.messages)
|
|
73
|
+
claim(mm.id, `message "${mm.name}"`);
|
|
74
|
+
for (const e of built.errors)
|
|
75
|
+
claim(e.id, `error "${e.name}"`);
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* A variable scoped with `elementId` must name an element that exists.
|
|
79
|
+
*
|
|
80
|
+
* The runtime resolves the scope by id and has nothing to fall back on, so a typo
|
|
81
|
+
* makes the variable unreachable — silently, with no diagnostic anywhere else.
|
|
82
|
+
*/
|
|
83
|
+
function checkScopedVars(built, diags) {
|
|
84
|
+
const ids = new Set();
|
|
85
|
+
const walk = (nodes) => {
|
|
86
|
+
for (const n of nodes) {
|
|
87
|
+
ids.add(n.id);
|
|
88
|
+
if (n.kind === 'subProcess')
|
|
89
|
+
walk(n.nodes);
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
walk(built.nodes);
|
|
93
|
+
// The PROCESS is a legal scope for a variable, and its id is not a flow element,
|
|
94
|
+
// so it has to be added by hand. Real exports scope variables to it — the ones
|
|
95
|
+
// an author declares on the canvas rather than on a node — and every one of them
|
|
96
|
+
// was reported as scoped to an element that does not exist.
|
|
97
|
+
ids.add(processId(built.id));
|
|
98
|
+
const allVars = [...built.variables];
|
|
99
|
+
const collect = (nodes) => {
|
|
100
|
+
for (const n of nodes) {
|
|
101
|
+
if (n.kind !== 'subProcess')
|
|
102
|
+
continue;
|
|
103
|
+
allVars.push(...n.variables);
|
|
104
|
+
collect(n.nodes);
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
collect(built.nodes);
|
|
108
|
+
for (const v of allVars) {
|
|
109
|
+
if (v.elementId !== undefined && !ids.has(v.elementId))
|
|
110
|
+
diags.push({
|
|
111
|
+
level: 'error',
|
|
112
|
+
code: 'SCOPED_VAR_UNKNOWN_ELEMENT',
|
|
113
|
+
message: `Variable "${v.id}" is scoped to element "${v.elementId}", which does not exist.`,
|
|
114
|
+
where: v.id,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* A duration the RUNTIME can parse — days/hours/minutes/seconds only.
|
|
120
|
+
*
|
|
121
|
+
* Deliberately narrower than ISO-8601: the runtime's duration parser accepts
|
|
122
|
+
* `P[nD][T[nH][nM][nS]]` and nothing else, so a spec-legal `P1W` or `P1Y` is
|
|
123
|
+
* accepted by `validate` and then fails at run time. Same reason the canvas has
|
|
124
|
+
* its own `TIMER_DURATION_WEEK_UNSUPPORTED` rule.
|
|
125
|
+
*/
|
|
126
|
+
const RUNTIME_DURATION = /^P(?:\d+D)?(?:T(?:\d+H)?(?:\d+M)?(?:\d+(?:\.\d+)?S)?)?$/i;
|
|
127
|
+
function isRuntimeDuration(v) {
|
|
128
|
+
// A bare `P`/`PT` matches the shape but carries no components — the same
|
|
129
|
+
// "validates, then no runtime can parse it" trap the timer eval card exists for.
|
|
130
|
+
return RUNTIME_DURATION.test(v) && /\d/.test(v);
|
|
131
|
+
}
|
|
132
|
+
/** Retry and loop options, checked per activity. */
|
|
133
|
+
function checkActivityFields(n, diags) {
|
|
134
|
+
// A script task whose body is missing or blank. The wire form of a script task IS its
|
|
135
|
+
// `<bpmn:script>` body, so without one the emitter writes an activity that parses,
|
|
136
|
+
// validates and runs — and does nothing. Every gate stays green: `check` passed, the
|
|
137
|
+
// compile succeeded, `uip maestro bpmn validate` returned Success, and the emitted file
|
|
138
|
+
// contained zero `<bpmn:script>` elements. The failure only shows up as a variable that
|
|
139
|
+
// never changed, which is the hardest kind of wrong to attribute.
|
|
140
|
+
//
|
|
141
|
+
// Reachable from ordinary authoring: `script` is a required field, but passing the body
|
|
142
|
+
// under any other key (a plausible `body:`) satisfies the runtime object while leaving
|
|
143
|
+
// `script` undefined, and TypeScript only catches that when the author is compiling
|
|
144
|
+
// types rather than running the file.
|
|
145
|
+
if (n.kind === 'scriptTask' && (n.script === undefined || n.script.trim() === ''))
|
|
146
|
+
diags.push({
|
|
147
|
+
level: 'error',
|
|
148
|
+
code: 'SCRIPT_TASK_NO_BODY',
|
|
149
|
+
message: `Script task "${n.id}" has no script body, so it would emit an activity that runs and does nothing. ` +
|
|
150
|
+
`Pass the Jint JavaScript as \`script\` (\`.scriptTask('${n.id}', { script: 'return { … };' })\`).`,
|
|
151
|
+
where: n.id,
|
|
152
|
+
});
|
|
153
|
+
if (!('retry' in n) && !('loop' in n))
|
|
154
|
+
return;
|
|
155
|
+
const retry = 'retry' in n ? n.retry : undefined;
|
|
156
|
+
if (retry) {
|
|
157
|
+
if (!Number.isInteger(retry.maxRetries) || retry.maxRetries < 1)
|
|
158
|
+
diags.push({
|
|
159
|
+
level: 'error',
|
|
160
|
+
code: 'RETRY_INVALID_COUNT',
|
|
161
|
+
message: `Retry on "${n.id}" needs maxRetries to be a positive integer (got ${retry.maxRetries}); the runtime ignores a policy with fewer.`,
|
|
162
|
+
where: n.id,
|
|
163
|
+
});
|
|
164
|
+
for (const [field, value] of [
|
|
165
|
+
['backoff', retry.backoff],
|
|
166
|
+
['maxDuration', retry.maxDuration],
|
|
167
|
+
]) {
|
|
168
|
+
if (value !== undefined && !isRuntimeDuration(value))
|
|
169
|
+
diags.push({
|
|
170
|
+
level: 'error',
|
|
171
|
+
code: 'RETRY_INVALID_DURATION',
|
|
172
|
+
message: `Retry ${field} on "${n.id}" is "${value}"; the runtime parses days/hours/minutes/seconds only, e.g. "PT30S" or "P1DT12H".`,
|
|
173
|
+
where: n.id,
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
if (retry.exponentialBase !== undefined && retry.backoffType !== 'exponential')
|
|
177
|
+
diags.push({
|
|
178
|
+
level: 'warning',
|
|
179
|
+
code: 'RETRY_BASE_WITHOUT_EXPONENTIAL',
|
|
180
|
+
message: `Retry on "${n.id}" sets exponentialBase but backoffType is not 'exponential', so it has no effect.`,
|
|
181
|
+
where: n.id,
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
const loop = 'loop' in n ? n.loop : undefined;
|
|
185
|
+
if (loop) {
|
|
186
|
+
if (!loop.collection)
|
|
187
|
+
diags.push({ level: 'error', code: 'LOOP_NO_COLLECTION', message: `Loop on "${n.id}" needs a collection expression.`, where: n.id });
|
|
188
|
+
if (!loop.itemVar)
|
|
189
|
+
diags.push({ level: 'error', code: 'LOOP_NO_ITEM_VAR', message: `Loop on "${n.id}" needs an itemVar to bind each item to.`, where: n.id });
|
|
190
|
+
// The local engine now HONOURS a completion condition, but only on a sequential
|
|
191
|
+
// loop. A parallel loop has dispatched every iteration before the first could
|
|
192
|
+
// report, so there is nothing left to stop — BPMN's answer is to cancel the
|
|
193
|
+
// remaining instances, which the engine does not implement. So the warning
|
|
194
|
+
// narrowed from "never evaluated" to exactly the case that is still not.
|
|
195
|
+
if (loop.completion !== undefined && loop.sequential !== true)
|
|
196
|
+
diags.push({
|
|
197
|
+
level: 'warning',
|
|
198
|
+
code: 'LOOP_COMPLETION_NOT_LOCAL',
|
|
199
|
+
message: `Loop completion on "${n.id}" is emitted for the platform but NOT evaluated by the local engine on a ` +
|
|
200
|
+
`PARALLEL loop, so a local run iterates the whole collection. Set \`sequential: true\` to have it honoured locally.`,
|
|
201
|
+
where: n.id,
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
function checkScope(nodes, flows, where, diags, seenIds, isTop,
|
|
206
|
+
/**
|
|
207
|
+
* Whether the CONTAINER of these nodes is an event sub-process. The process root
|
|
208
|
+
* never is, so it defaults false — see ERROR_START_OUTSIDE_EVENT_SUBPROCESS below.
|
|
209
|
+
*/
|
|
210
|
+
inEventSubProcess = false) {
|
|
211
|
+
const nodeIds = new Set();
|
|
212
|
+
for (const n of nodes)
|
|
213
|
+
nodeIds.add(n.id);
|
|
214
|
+
// Duplicate ids (globally across the document).
|
|
215
|
+
for (const n of nodes)
|
|
216
|
+
dupId(n.id, seenIds, diags, where);
|
|
217
|
+
for (const f of flows)
|
|
218
|
+
dupId(f.id, seenIds, diags, where);
|
|
219
|
+
// At least one start event per scope that has any nodes.
|
|
220
|
+
//
|
|
221
|
+
// "Start event" is the ELEMENT, not the builder kind. A registry-backed node can
|
|
222
|
+
// be one too — `Intsvc.TimerTrigger` and `Intsvc.EventTrigger` both serialize to
|
|
223
|
+
// `bpmn:StartEvent` — and keying off `kind === 'startEvent'` alone reported
|
|
224
|
+
// NO_START on a process that has one, refusing an artifact the platform accepts.
|
|
225
|
+
if (nodes.length && !nodes.some(isStartElement))
|
|
226
|
+
diags.push({ level: isTop ? 'error' : 'warning', code: 'NO_START', message: `${where} has no start event.`, where });
|
|
227
|
+
// Flow endpoints must resolve within this scope.
|
|
228
|
+
for (const f of flows) {
|
|
229
|
+
if (!nodeIds.has(f.source))
|
|
230
|
+
diags.push({ level: 'error', code: 'DANGLING_FLOW', message: `Flow "${f.id}" source "${f.source}" is not an element in ${where}.`, where: f.id });
|
|
231
|
+
if (!nodeIds.has(f.target))
|
|
232
|
+
diags.push({ level: 'error', code: 'DANGLING_FLOW', message: `Flow "${f.id}" target "${f.target}" is not an element in ${where}.`, where: f.id });
|
|
233
|
+
}
|
|
234
|
+
const outgoing = new Map();
|
|
235
|
+
const incomingCount = new Map();
|
|
236
|
+
for (const f of flows) {
|
|
237
|
+
const outs = outgoing.get(f.source) ?? [];
|
|
238
|
+
outs.push(f);
|
|
239
|
+
outgoing.set(f.source, outs);
|
|
240
|
+
incomingCount.set(f.target, (incomingCount.get(f.target) ?? 0) + 1);
|
|
241
|
+
}
|
|
242
|
+
for (const n of nodes) {
|
|
243
|
+
const outs = outgoing.get(n.id) ?? [];
|
|
244
|
+
const ins = incomingCount.get(n.id) ?? 0;
|
|
245
|
+
// Exclusive/inclusive gateways: every non-default outgoing needs a condition —
|
|
246
|
+
// but only where the platform requires it. Its rule
|
|
247
|
+
// (ConditionalFlowRule/validateConditionalSequenceFlow) fires for a
|
|
248
|
+
// `bpmn:ExclusiveGateway` with MORE THAN ONE outgoing flow. A gateway with one
|
|
249
|
+
// outgoing flow is a JOIN, and its single flow is unconditional by definition;
|
|
250
|
+
// demanding a condition there refused a shape the platform validates and the
|
|
251
|
+
// runtime executes, leaving `default` as a misleading workaround. Inclusive
|
|
252
|
+
// gateways are outside the platform's rule, so an unconditioned branch there is
|
|
253
|
+
// a warning — suspicious, not invalid.
|
|
254
|
+
if (n.kind === 'exclusiveGateway' || n.kind === 'inclusiveGateway') {
|
|
255
|
+
if (n.default !== undefined && !outs.some((o) => o.id === n.default))
|
|
256
|
+
diags.push({ level: 'error', code: 'BAD_DEFAULT', message: `Gateway "${n.id}" default "${n.default}" is not one of its outgoing flows.`, where: n.id });
|
|
257
|
+
if (outs.length > 1) {
|
|
258
|
+
const level = n.kind === 'exclusiveGateway' ? 'error' : 'warning';
|
|
259
|
+
for (const o of outs)
|
|
260
|
+
if (o.id !== n.default && o.condition === undefined)
|
|
261
|
+
diags.push({ level, code: 'MISSING_CONDITION_EXPRESSION', message: `Outgoing flow "${o.id}" of gateway "${n.id}" has no condition.`, where: o.id });
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
// A gateway with exactly one in and one out is superfluous.
|
|
265
|
+
if (GATEWAY_KINDS.has(n.kind) && ins === 1 && outs.length === 1)
|
|
266
|
+
diags.push({ level: 'warning', code: 'SUPERFLUOUS_GATEWAY', message: `Gateway "${n.id}" has one incoming and one outgoing flow; remove it.`, where: n.id });
|
|
267
|
+
// An activity or event with >1 incoming flow is an implicit join. A WARNING,
|
|
268
|
+
// not an error: the platform's FAKE_JOIN rule only matches the abstract types
|
|
269
|
+
// `bpmn:Activity`/`bpmn:Event`, which never appear on real exported BPMN, so
|
|
270
|
+
// the rule is dormant there and an implicit merge validates and runs. Joining
|
|
271
|
+
// through a gateway is still the clearer shape, hence the nudge.
|
|
272
|
+
if (!GATEWAY_KINDS.has(n.kind) && n.kind !== 'boundaryEvent' && ins > 1)
|
|
273
|
+
diags.push({
|
|
274
|
+
level: 'warning',
|
|
275
|
+
code: 'FAKE_JOIN',
|
|
276
|
+
message: `"${n.id}" has ${ins} incoming flows; the platform accepts this, but joining with a gateway is clearer.`,
|
|
277
|
+
where: n.id,
|
|
278
|
+
});
|
|
279
|
+
// Boundary events must attach to an activity in the same scope.
|
|
280
|
+
if (n.kind === 'boundaryEvent') {
|
|
281
|
+
const target = nodes.find((x) => x.id === n.attachedTo);
|
|
282
|
+
if (!target)
|
|
283
|
+
diags.push({ level: 'error', code: 'BOUNDARY_UNATTACHED', message: `Boundary event "${n.id}" attaches to unknown element "${n.attachedTo}".`, where: n.id });
|
|
284
|
+
else if (!ACTIVITY_KINDS.has(target.kind))
|
|
285
|
+
diags.push({ level: 'error', code: 'BOUNDARY_NOT_ACTIVITY', message: `Boundary event "${n.id}" must attach to an activity, not "${target.kind}".`, where: n.id });
|
|
286
|
+
}
|
|
287
|
+
// A timer event definition needs one of duration/date/cycle.
|
|
288
|
+
// An error START event only means something inside an event sub-process: that is
|
|
289
|
+
// BPMN's construct for catching an error thrown elsewhere in the same container.
|
|
290
|
+
// At the process root nothing can throw into it, so the whole handler branch is
|
|
291
|
+
// unreachable — and `uip maestro bpmn validate` accepts it silently, so without
|
|
292
|
+
// this the author's first clue is a handler that never runs on a real failure.
|
|
293
|
+
if (n.kind === 'startEvent' && n.event?.type === 'error' && !inEventSubProcess) {
|
|
294
|
+
diags.push({
|
|
295
|
+
level: 'error',
|
|
296
|
+
code: 'ERROR_START_OUTSIDE_EVENT_SUBPROCESS',
|
|
297
|
+
message: `Error start event "${n.id}" is not inside an event sub-process, so nothing can throw into it. ` +
|
|
298
|
+
'Put it in `.subProcess(id, fn, { triggeredByEvent: true })` as that scope\'s single start event.',
|
|
299
|
+
where: n.id,
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
if (n.kind !== 'subProcess' && 'event' in n && n.event?.type === 'timer') {
|
|
303
|
+
const t = n.event.timer;
|
|
304
|
+
if (t.duration === undefined && t.date === undefined && t.cycle === undefined)
|
|
305
|
+
diags.push({ level: 'error', code: 'TIMER_MISSING_FIELD', message: `Timer on "${n.id}" needs a duration, date, or cycle.`, where: n.id });
|
|
306
|
+
}
|
|
307
|
+
// Retry / loop options on any activity.
|
|
308
|
+
checkActivityFields(n, diags);
|
|
309
|
+
// A registry-backed node's required context fields. This is what
|
|
310
|
+
// `uip maestro bpmn validate` reports as EMPTY_REQUIRED_FIELD; hearing it from
|
|
311
|
+
// the SDK is faster, and the registry snapshot already knows the answer.
|
|
312
|
+
if (n.kind === 'typed') {
|
|
313
|
+
// Fields the platform insists be `=bindings.<id>` references rather than
|
|
314
|
+
// literals. The typed builder methods bind these automatically, so this
|
|
315
|
+
// fires on a hand-built node or a family whose wrapper forgot — which is
|
|
316
|
+
// exactly when the CLI would otherwise be the one to say so
|
|
317
|
+
// (`context input "name" must reference project bindings`).
|
|
318
|
+
// Context reaches a node two ways — see the note on `contextNames` below; a
|
|
319
|
+
// binding declared in a verbatim row is still a binding.
|
|
320
|
+
const contextView = { ...n.context };
|
|
321
|
+
for (const row of n.contextRows ?? [])
|
|
322
|
+
contextView[row.name] = row.value ?? row.body ?? '';
|
|
323
|
+
for (const field of bindingRequiredFields(n.type)) {
|
|
324
|
+
const value = contextView[field.name];
|
|
325
|
+
if (typeof value === 'string' && value.startsWith('=bindings.'))
|
|
326
|
+
continue;
|
|
327
|
+
diags.push({
|
|
328
|
+
level: 'error',
|
|
329
|
+
code: 'BINDING_REQUIRED',
|
|
330
|
+
message: `"${n.id}" (${n.type}) field "${field.name}" must reference a binding ` +
|
|
331
|
+
`(=bindings.<id>) targeting resource "${field.bindingInfo?.resource}" / ` +
|
|
332
|
+
`"${field.bindingInfo?.propertyAttribute}", not a literal.`,
|
|
333
|
+
where: n.id,
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
// Context reaches a node two ways — the `context` record, or the verbatim
|
|
337
|
+
// `contextRows` an importer uses when a row carries detail the record cannot
|
|
338
|
+
// hold (its own type, a `target`, or the absence of a value). Both are context,
|
|
339
|
+
// so both satisfy a required field.
|
|
340
|
+
//
|
|
341
|
+
// Reading only the record meant an imported node whose rows happened to need
|
|
342
|
+
// spelling out reported EVERY required field missing — on an artifact that
|
|
343
|
+
// carries all of them. `contextRowsNeedSpellingOut` decides which form decompile
|
|
344
|
+
// emits, so whether this fired depended on a detail of the source document that
|
|
345
|
+
// has nothing to do with whether the field is set.
|
|
346
|
+
const contextNames = contextView;
|
|
347
|
+
for (const field of missingRequiredContext(n.type, contextNames)) {
|
|
348
|
+
diags.push({
|
|
349
|
+
level: 'error',
|
|
350
|
+
code: 'EMPTY_REQUIRED_FIELD',
|
|
351
|
+
message: `"${n.id}" (${n.type}) is missing required field "${field}".`,
|
|
352
|
+
where: n.id,
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
// The same rule over the PAYLOAD fields — the platform's RequiredFieldsRule
|
|
356
|
+
// draws on both, so an unset required input is the same EMPTY_REQUIRED_FIELD.
|
|
357
|
+
for (const field of missingRequiredInputs(n.type, n.inputs, contextNames)) {
|
|
358
|
+
diags.push({
|
|
359
|
+
level: 'error',
|
|
360
|
+
code: 'EMPTY_REQUIRED_FIELD',
|
|
361
|
+
message: `"${n.id}" (${n.type}) is missing required input "${field}".`,
|
|
362
|
+
where: n.id,
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
// A type that permits only the output row its spec declares. Reachable via
|
|
366
|
+
// the generic `.activity()`; the typed wrapper does not offer the option.
|
|
367
|
+
if (REGISTRY_GAPS[n.type]?.outputRowsFixed && Object.keys(n.outputs ?? {}).length) {
|
|
368
|
+
diags.push({
|
|
369
|
+
level: 'error',
|
|
370
|
+
code: 'OUTPUT_ROWS_FIXED',
|
|
371
|
+
message: `"${n.id}" (${n.type}) permits only its declared output row, so the extra row(s) ` +
|
|
372
|
+
`${Object.keys(n.outputs ?? {}).map((k) => `"${k}"`).join(', ')} will be refused ` +
|
|
373
|
+
`("output name must be \\"${registryType(n.type).output?.name}\\""). Use outputVar and read the ` +
|
|
374
|
+
`typed row instead.`,
|
|
375
|
+
where: n.id,
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
// A type whose required payload fields cannot be expressed at all. Warning,
|
|
379
|
+
// not error: the node is as correct as the registry allows, so refusing it
|
|
380
|
+
// would block work the platform, not the author, has to fix. Saying nothing
|
|
381
|
+
// is worse — the artifact validates here and is rejected by the CLI.
|
|
382
|
+
const unsatisfiable = unsatisfiableRequiredInputs(n.type);
|
|
383
|
+
if (unsatisfiable.length) {
|
|
384
|
+
diags.push({
|
|
385
|
+
level: 'warning',
|
|
386
|
+
code: 'REQUIRED_INPUT_UNSATISFIABLE',
|
|
387
|
+
message: `"${n.id}" (${n.type}) declares required input(s) ${unsatisfiable.map((f) => `"${f}"`).join(', ')}, ` +
|
|
388
|
+
`but its "${registryType(n.type).inputPattern}" pattern merges every input into one row, so the ` +
|
|
389
|
+
`platform's required-field rule cannot see them. \`uip maestro bpmn validate\` will report ` +
|
|
390
|
+
`EMPTY_REQUIRED_FIELD whatever this node carries — a platform defect, not an authoring error. ` +
|
|
391
|
+
`See docs/BPMN_COVERAGE_DESIGN.md D13.`,
|
|
392
|
+
where: n.id,
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
// Recurse into sub-process bodies.
|
|
397
|
+
if (n.kind === 'subProcess')
|
|
398
|
+
checkScope(n.nodes, n.flows, `subProcess "${n.id}"`, diags, seenIds, false, n.triggeredByEvent === true);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* Whether this node serializes to a `bpmn:StartEvent`.
|
|
403
|
+
*
|
|
404
|
+
* Two ways to be one: the builder's own `startEvent` kind, or a registry-backed
|
|
405
|
+
* node whose spec places it on that element. An unknown extension type answers
|
|
406
|
+
* `false` rather than throwing — a bad type has its own diagnostic, and NO_START is
|
|
407
|
+
* the wrong place to report it.
|
|
408
|
+
*/
|
|
409
|
+
function isStartElement(n) {
|
|
410
|
+
if (n.kind === 'startEvent')
|
|
411
|
+
return true;
|
|
412
|
+
// A connector EVENT TRIGGER is a start event too — `Intsvc.EventTrigger`
|
|
413
|
+
// serializes to `bpmn:StartEvent`. Its mid-flow sibling (`subscribe: 'event'`,
|
|
414
|
+
// a receive task) is not.
|
|
415
|
+
if (n.kind === 'connectorEvent')
|
|
416
|
+
return n.subscribe === 'trigger';
|
|
417
|
+
if (n.kind !== 'typed')
|
|
418
|
+
return false;
|
|
419
|
+
try {
|
|
420
|
+
return registryType(n.type).element === 'bpmn:StartEvent';
|
|
421
|
+
}
|
|
422
|
+
catch {
|
|
423
|
+
return false;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
function dupId(id, seen, diags, where) {
|
|
427
|
+
if (seen.has(id))
|
|
428
|
+
diags.push({ level: 'error', code: 'DUP_ID', message: `Duplicate element id "${id}" in ${where}.`, where: id });
|
|
429
|
+
seen.add(id);
|
|
430
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* compile-cli — <Name>.bpmn.ts → <Name>.bpmn
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* node --experimental-strip-types compile-cli.js <bpmn.ts | BaseName> [-o out.bpmn]
|
|
7
|
+
*
|
|
8
|
+
* Runs the fast static check first; if it passes, serializes to Maestro `.bpmn`
|
|
9
|
+
* XML (semantic-only — no diagram). The authoritative deploy gate is
|
|
10
|
+
* `uip maestro bpmn validate`; a separate "tidy" step lays the diagram out.
|
|
11
|
+
*/
|
|
12
|
+
import { writeFileSync, existsSync } from 'node:fs';
|
|
13
|
+
import { basename, join } from 'node:path';
|
|
14
|
+
import { resolveBpmnFile, loadBuiltBpmn } from './load.js';
|
|
15
|
+
import { serialize } from './serialize.js';
|
|
16
|
+
import { check } from './check.js';
|
|
17
|
+
import { Library } from '../core/library.js';
|
|
18
|
+
import { Bindings } from '../core/bindings.js';
|
|
19
|
+
import { runWhenInvokedDirectly } from '../cli-run.js';
|
|
20
|
+
import { ensureTypeScriptRuntime } from '../node-runtime.js';
|
|
21
|
+
import { resolveLibraryDir } from '../registry/cache.js';
|
|
22
|
+
function opt(argv, name) {
|
|
23
|
+
const i = argv.indexOf(name);
|
|
24
|
+
return i >= 0 ? argv[i + 1] : undefined;
|
|
25
|
+
}
|
|
26
|
+
export async function run(argv) {
|
|
27
|
+
if (argv.length === 0 || argv[0] === '-h' || argv[0] === '--help') {
|
|
28
|
+
console.error('usage: compile <bpmn.ts | BaseName> [-o out.bpmn]');
|
|
29
|
+
return 2;
|
|
30
|
+
}
|
|
31
|
+
const flags = new Set(['-o', '--library', '--bindings']);
|
|
32
|
+
const pos = argv.find((a, i) => !flags.has(a) && !flags.has(argv[i - 1]));
|
|
33
|
+
if (!pos) {
|
|
34
|
+
console.error('compile: missing <bpmn.ts | BaseName>');
|
|
35
|
+
return 2;
|
|
36
|
+
}
|
|
37
|
+
const file = resolveBpmnFile(pos);
|
|
38
|
+
ensureTypeScriptRuntime();
|
|
39
|
+
const built = await loadBuiltBpmn(file);
|
|
40
|
+
const diags = check(built);
|
|
41
|
+
for (const d of diags)
|
|
42
|
+
console.error(` ${d.level === 'error' ? '✗' : '⚠'} [${d.code}] ${d.message}`);
|
|
43
|
+
if (diags.some((d) => d.level === 'error')) {
|
|
44
|
+
console.error(`compile: ${diags.filter((d) => d.level === 'error').length} error(s); not emitting.`);
|
|
45
|
+
return 1;
|
|
46
|
+
}
|
|
47
|
+
// Connector service tasks resolve against the library (like Flow's/Case's
|
|
48
|
+
// compile-cli); load it only when present, and bindings.json alongside the .ts.
|
|
49
|
+
const libDir = resolveLibraryDir(opt(argv, '--library'));
|
|
50
|
+
const library = libDir && existsSync(join(libDir, 'index.json')) ? new Library(libDir) : undefined;
|
|
51
|
+
const bindFile = opt(argv, '--bindings') ?? (existsSync('bindings.json') ? 'bindings.json' : undefined);
|
|
52
|
+
const bindings = new Bindings(bindFile);
|
|
53
|
+
const xml = await serialize(built, { library, bindings });
|
|
54
|
+
const base = basename(file).replace(/\.bpmn\.ts$|\.ts$/, '');
|
|
55
|
+
const out = opt(argv, '-o') ?? `${base}.bpmn`;
|
|
56
|
+
writeFileSync(out, xml);
|
|
57
|
+
console.log(`compile: wrote ${out} (${built.nodes.length} element(s))`);
|
|
58
|
+
return 0;
|
|
59
|
+
}
|
|
60
|
+
runWhenInvokedDirectly(import.meta.url, 'compile', run);
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* bpmn/connector — emit an Integration Service connector service task as a
|
|
3
|
+
* `bpmn:sendTask` carrying a `uipath:activity` (`Intsvc.ActivityExecution`).
|
|
4
|
+
*
|
|
5
|
+
* The connector DATA (wiring context, path/query/body input split, output schema,
|
|
6
|
+
* metadata blob, connection/folder bindings) is exactly what the Flow node and the
|
|
7
|
+
* Case task carry — all derived from the same `ResolvedConnector` (`Library.resolve`)
|
|
8
|
+
* — only the ENVELOPE differs: moddle `uipath:*` elements here vs. caseplan JSON in
|
|
9
|
+
* `case/serialize.ts`. The shape was verified against real Studio Web `.bpmn`
|
|
10
|
+
* exports (`Intsvc.ActivityExecution` on a `bpmn:sendTask`; see CONNECTORS_DESIGN.md
|
|
11
|
+
* §D) and passes `uip maestro bpmn validate`.
|
|
12
|
+
*
|
|
13
|
+
* NOTE: the DATA computation mirrors `case/serialize.ts` (`connectorTaskData`/
|
|
14
|
+
* `connectorMetadata`/`connectorOutputs`); the two could be unified into a shared
|
|
15
|
+
* `core/` helper in a later cleanup — kept separate here to keep this change
|
|
16
|
+
* BPMN-only.
|
|
17
|
+
*
|
|
18
|
+
* All values are embedded via `bpmn-moddle` (`m.create`), which escapes attributes,
|
|
19
|
+
* text, and CDATA — never string-concatenated into XML.
|
|
20
|
+
*/
|
|
21
|
+
import type BpmnModdle from 'bpmn-moddle';
|
|
22
|
+
import type { ModdleElement } from 'bpmn-moddle';
|
|
23
|
+
import { type BpmnNode } from './bpmn-sdk.js';
|
|
24
|
+
import type { Library } from '../core/library.js';
|
|
25
|
+
import type { Bindings } from '../core/bindings.js';
|
|
26
|
+
type El = ModdleElement;
|
|
27
|
+
type ConnectorNode = Extract<BpmnNode, {
|
|
28
|
+
kind: 'connector';
|
|
29
|
+
}>;
|
|
30
|
+
/** The subset of the serializer context this emitter needs. */
|
|
31
|
+
export interface ConnectorCtx {
|
|
32
|
+
m: BpmnModdle;
|
|
33
|
+
library?: Library;
|
|
34
|
+
bindings?: Bindings;
|
|
35
|
+
bindingSink: Map<string, Record<string, unknown>>;
|
|
36
|
+
/**
|
|
37
|
+
* Declared Maestro `type` for a variable id, for typing an author-mapped output
|
|
38
|
+
* row. Optional so the connector emitter (which writes its own rows) is
|
|
39
|
+
* unaffected; the Phase 3d families pass it through to `createTypedNode`.
|
|
40
|
+
*/
|
|
41
|
+
varTypeOf?: (varId: string) => string | undefined;
|
|
42
|
+
}
|
|
43
|
+
export declare function createConnectorTask(ctx: ConnectorCtx, n: ConnectorNode): El;
|
|
44
|
+
/**
|
|
45
|
+
* Resolve a symbolic connection/folder to a process-level binding.
|
|
46
|
+
*
|
|
47
|
+
* Exported for the Phase 3d nodes, which need the identical closure: a connector
|
|
48
|
+
* event and an external agent/workflow each reference a connection and a folder
|
|
49
|
+
* the same way a connector activity does, and duplicating this was how the two
|
|
50
|
+
* families drifted apart in `case/serialize.ts`.
|
|
51
|
+
*/
|
|
52
|
+
export declare function connectorBindingRef(ctx: ConnectorCtx, name: string | undefined, resource: string, propertyAttribute: string, displayName: string): {
|
|
53
|
+
id: string;
|
|
54
|
+
resourceKey: string;
|
|
55
|
+
} | undefined;
|
|
56
|
+
export {};
|