@uipath/maestro-builder-sdk 6.16.13 → 6.17.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/dist/api-index.md +108 -107
- package/dist/api-members.md +295 -291
- package/dist/bindings-v2.d.ts +39 -1
- package/dist/bindings-v2.js +289 -9
- package/dist/bpmn/compile-cli.js +11 -2
- package/dist/bpmn/decompile.js +2 -1
- package/dist/bpmn/registry-fallback.js +9 -7
- package/dist/case/compile-cli.js +16 -6
- package/dist/case/decompile-cli.js +2 -1
- package/dist/case/decompile.js +3 -2
- package/dist/case/merge-cli.js +3 -2
- package/dist/check.js +219 -37
- package/dist/cli-result.d.ts +9 -0
- package/dist/cli-result.js +12 -0
- package/dist/compile-cli.js +78 -53
- package/dist/core/actions.d.ts +70 -16
- package/dist/core/agentic-process-forms.d.ts +51 -0
- package/dist/core/agentic-process-forms.js +49 -0
- package/dist/core/binding-messages.d.ts +5 -0
- package/dist/core/comment-text.d.ts +29 -0
- package/dist/core/comment-text.js +40 -0
- package/dist/core/contribution-bindings.d.ts +73 -0
- package/dist/core/contribution-bindings.js +105 -0
- package/dist/core/expr-check.js +7 -2
- package/dist/core/json-file.d.ts +1 -0
- package/dist/core/json-file.js +12 -0
- package/dist/core/script-return.js +4 -1
- package/dist/core/typed-raw-node.js +1 -0
- package/dist/core/typescript-module.d.ts +2 -0
- package/dist/core/typescript-module.js +18 -0
- package/dist/core-definitions.json +210 -0
- package/dist/decompile-cli.js +2 -1
- package/dist/decompile.js +64 -24
- package/dist/emit.js +4 -3
- package/dist/flow-sdk.d.ts +14 -9
- package/dist/flow-sdk.js +14 -4
- package/dist/flow-step-tree.d.ts +2 -3
- package/dist/flow-step-tree.js +2 -3
- package/dist/load.js +1 -1
- package/dist/merge-cli.js +3 -2
- package/dist/merge.d.ts +2 -1
- package/dist/merge.js +0 -0
- package/dist/path-within.d.ts +33 -0
- package/dist/path-within.js +41 -0
- package/dist/registry/archive/extract.js +2 -5
- package/dist/registry/curated-overlay.js +3 -2
- package/dist/registry/generators/descriptors.js +20 -18
- package/dist/registry/generators/triggers.js +4 -4
- package/dist/registry/prepare/messages.js +2 -1
- package/dist/registry/prepare/source-labels.js +60 -1
- package/dist/registry/prepare-cli.js +14 -4
- package/dist/registry/pull-cli.js +3 -2
- package/dist/registry/uip/client.d.ts +28 -3
- package/dist/registry/uip/client.js +95 -34
- package/dist/registry/uip/host.d.ts +41 -0
- package/dist/registry/uip/host.js +312 -0
- package/dist/serialize.d.ts +7 -0
- package/dist/serialize.js +409 -275
- package/dist/workdir.js +1 -1
- package/package.json +3 -3
package/dist/check.js
CHANGED
|
@@ -7,16 +7,20 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { Expr, SCHEDULE_PRESETS, DELAY_PRESETS, VARIANT_OPERATION, parseIxpProjectType, flatNameOf, flatNameSegmentsOf, FLAT_SEP } from './flow-sdk.js';
|
|
9
9
|
import { FlowNode } from './core/node-classes.js';
|
|
10
|
+
import { planContributionBindings } from './core/contribution-bindings.js';
|
|
11
|
+
import { resolveContributionDefinition } from './serialize.js';
|
|
10
12
|
import { QUEUE_ITEM_RECORD_SCHEMA } from './core/queue-record.js';
|
|
13
|
+
import { AGENTIC_PROCESS_FORMS } from './core/agentic-process-forms.js';
|
|
11
14
|
import { checkEventResolution, checkEventSubscription, eventParamsFor, } from './core/event-checks.js';
|
|
12
15
|
import { checkCookedEscapes } from './flow-expr-check.js';
|
|
13
16
|
import { FLOW_EXPR_DIALECT, checkBindingLabels, checkBindingPresence, checkConnectorContract as coreCheckConnectorContract, checkConnectorLookups as coreCheckConnectorLookups, checkConnectorSchema as coreCheckConnectorSchema, collectConnectionUses, connectionConnectorConflicts, } from './core/connector-checks.js';
|
|
14
17
|
import { prepareCommand } from './core/cli-spelling.js';
|
|
18
|
+
import { commentText } from './core/comment-text.js';
|
|
15
19
|
import { editDistance } from './core/edit-distance.js';
|
|
16
20
|
import { isAnswerField } from './core/hitl-answers.js';
|
|
17
21
|
import { hitlDerivesOutcomeHandles, hitlExitModel, hitlRoutesPerOutcome } from './core/hitl-routing.js';
|
|
18
22
|
import { declaredExits, hasExits } from './core/step-ports.js';
|
|
19
|
-
import {
|
|
23
|
+
import { stopsAtEnd } from './flow-step-tree.js';
|
|
20
24
|
import { FLOW_SCHEMA_VERSIONS } from './format-profile.js';
|
|
21
25
|
import { compareSchemaVersions, schemaVersionRefusal } from './schema-version.js';
|
|
22
26
|
import { planFlowGlobals, planSharedInputs, sharedInputRoots } from './flow-variables.js';
|
|
@@ -54,9 +58,11 @@ function checkFlow(built, ancestors, opts = {}) {
|
|
|
54
58
|
if (maybeCustomTrigger !== undefined && !('kind' in maybeCustomTrigger) && maybeCustomTrigger instanceof FlowNode) {
|
|
55
59
|
// A custom FlowTrigger subclass reports through its own diagnostics() hook;
|
|
56
60
|
// the built-in trigger rules key on `kind` and don't apply.
|
|
57
|
-
|
|
61
|
+
const own = maybeCustomTrigger.diagnostics();
|
|
62
|
+
for (const d of own) {
|
|
58
63
|
diags.push({ level: d.severity, code: d.code, message: d.message, step: 'trigger' });
|
|
59
64
|
}
|
|
65
|
+
checkContribution(maybeCustomTrigger, built.triggerOptions?.version, 'trigger', own.some((d) => d.severity === 'error'), diags);
|
|
60
66
|
}
|
|
61
67
|
else if (built.trigger !== undefined)
|
|
62
68
|
checkTrigger(built.trigger, diags, opts);
|
|
@@ -366,9 +372,11 @@ function checkFlow(built, ancestors, opts = {}) {
|
|
|
366
372
|
// don't apply, and it reports through its own diagnostics() hook.
|
|
367
373
|
const maybeCustom = s.spec;
|
|
368
374
|
if (!('kind' in maybeCustom) && maybeCustom instanceof FlowNode) {
|
|
369
|
-
|
|
375
|
+
const own = maybeCustom.diagnostics();
|
|
376
|
+
for (const d of own) {
|
|
370
377
|
diags.push({ level: d.severity, code: d.code, message: d.message, step: s.name });
|
|
371
378
|
}
|
|
379
|
+
checkContribution(maybeCustom, s.options?.version, s.name, own.some((d) => d.severity === 'error'), diags);
|
|
372
380
|
}
|
|
373
381
|
if (s.spec.kind === 'transform')
|
|
374
382
|
checkTransform(s.name, s.spec.inputs, diags, typeOf, triggerId);
|
|
@@ -864,9 +872,11 @@ function checkFlow(built, ancestors, opts = {}) {
|
|
|
864
872
|
epIds.add(ep.id);
|
|
865
873
|
const maybeCustomEp = ep.trigger;
|
|
866
874
|
if (!('kind' in maybeCustomEp) && maybeCustomEp instanceof FlowNode) {
|
|
867
|
-
|
|
875
|
+
const own = maybeCustomEp.diagnostics();
|
|
876
|
+
for (const d of own) {
|
|
868
877
|
diags.push({ level: d.severity, code: d.code, message: d.message, step: ep.id });
|
|
869
878
|
}
|
|
879
|
+
checkContribution(maybeCustomEp, ep.options?.version, ep.id, own.some((d) => d.severity === 'error'), diags);
|
|
870
880
|
}
|
|
871
881
|
else {
|
|
872
882
|
checkTrigger(ep.trigger, diags, opts, ep.id);
|
|
@@ -1283,13 +1293,10 @@ function checkFlow(built, ancestors, opts = {}) {
|
|
|
1283
1293
|
}
|
|
1284
1294
|
}
|
|
1285
1295
|
}
|
|
1286
|
-
//
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
const hasTerminal = hasReturn(built.steps);
|
|
1291
|
-
if (!hasTerminal)
|
|
1292
|
-
diags.push({ level: 'warning', code: 'NO_RETURN', message: 'Flow has no return(); a default End node will be appended.' });
|
|
1296
|
+
// The End compile appends when the root path runs out of steps (#941).
|
|
1297
|
+
const fallOff = checkFallOffEnd(built);
|
|
1298
|
+
if (fallOff)
|
|
1299
|
+
diags.push(fallOff);
|
|
1293
1300
|
// Check every child flow with the same rules, in its OWN scope — a child's
|
|
1294
1301
|
// steps never see the parent's variables, so its undeclared references,
|
|
1295
1302
|
// duplicate step names and type mismatches are its own. Diagnostics come back
|
|
@@ -1376,9 +1383,124 @@ function checkLoopListEnvelope(loop, connectors, opts, diags) {
|
|
|
1376
1383
|
suggestion,
|
|
1377
1384
|
});
|
|
1378
1385
|
}
|
|
1379
|
-
/**
|
|
1380
|
-
|
|
1381
|
-
|
|
1386
|
+
/**
|
|
1387
|
+
* NO_RETURN: a path that runs off the end of the flow, where a declared output
|
|
1388
|
+
* is never written (#941).
|
|
1389
|
+
*
|
|
1390
|
+
* It asks serialize's question, not "is there a `.return()` anywhere?".
|
|
1391
|
+
* serialize appends an End exactly when the root walk has tails left
|
|
1392
|
+
* (`if (finalTails.length)`), and `armTails` mirrors that walk tail for tail.
|
|
1393
|
+
* So a `.terminate()` path appends no End, and a branch where one arm returns
|
|
1394
|
+
* and the other falls off DOES append one.
|
|
1395
|
+
*
|
|
1396
|
+
* The appended End maps every output from its own variable
|
|
1397
|
+
* (`result <- $vars.result`), the same as a `.return()` that names no value.
|
|
1398
|
+
* That loses nothing when the flow declares no outputs, or when each output
|
|
1399
|
+
* was set on the way: by a `default`, a `{ shared }` input that every root
|
|
1400
|
+
* feeds, or `{ updates }` on a step of that path. The warning names the outputs
|
|
1401
|
+
* that are not, and the steps the path falls off from.
|
|
1402
|
+
*
|
|
1403
|
+
* Not covered: the private End a `.stepToList` body gets (#742). `armTails`
|
|
1404
|
+
* skips those bodies, and that End is a different emission site with no
|
|
1405
|
+
* output mappings at all.
|
|
1406
|
+
*/
|
|
1407
|
+
function checkFallOffEnd(built) {
|
|
1408
|
+
if (built.outputs.length === 0)
|
|
1409
|
+
return undefined;
|
|
1410
|
+
const from = built.triggerId ?? 'start';
|
|
1411
|
+
if (armTails(built.steps, [{ from }]).length === 0)
|
|
1412
|
+
return undefined;
|
|
1413
|
+
const preset = new Set(built.outputs.filter((o) => o.default !== undefined).map((o) => o.name));
|
|
1414
|
+
// A shared input sets its variable only on runs that enter through a root
|
|
1415
|
+
// feeding it, so it counts as preset only when every root does.
|
|
1416
|
+
const roots = sharedInputRoots(built);
|
|
1417
|
+
for (const t of planSharedInputs({ roots, outputs: built.outputs, vars: built.vars }).targets) {
|
|
1418
|
+
if (t.declaredAs === 'output' && roots.every((r) => t.feeds.some((f) => f.root === r.id)))
|
|
1419
|
+
preset.add(t.name);
|
|
1420
|
+
}
|
|
1421
|
+
const tails = writtenTails(built.steps, [{ from, written: preset }], []);
|
|
1422
|
+
const missing = built.outputs.map((o) => o.name).filter((n) => tails.some((t) => !t.written.has(n)));
|
|
1423
|
+
if (missing.length === 0)
|
|
1424
|
+
return undefined;
|
|
1425
|
+
const steps = [...new Set(tails.filter((t) => missing.some((n) => !t.written.has(n))).map((t) => t.from))];
|
|
1426
|
+
const one = missing.length === 1;
|
|
1427
|
+
return {
|
|
1428
|
+
level: 'warning',
|
|
1429
|
+
code: 'NO_RETURN',
|
|
1430
|
+
message: `Path ending at ${steps.map((x) => `"${x}"`).join(', ')} falls into the End compile appends. `
|
|
1431
|
+
+ `${one ? 'Output' : 'Outputs'} ${missing.map((n) => `"${n}"`).join(', ')} may be unset there. `
|
|
1432
|
+
+ `End the path with .return({ … }) or set ${one ? 'it' : 'them'} with { updates } (a loop body may run zero times).`,
|
|
1433
|
+
step: steps[0],
|
|
1434
|
+
suggestion: `.return({ ${missing.map((n) => `${n}: …`).join(', ')} })`,
|
|
1435
|
+
};
|
|
1436
|
+
}
|
|
1437
|
+
/**
|
|
1438
|
+
* `armTails`, step for step, carrying what each path has surely written. A
|
|
1439
|
+
* `.return()` value is NOT a write here: it lives on that `.return()`'s End,
|
|
1440
|
+
* which is not the End this is about. Where paths meet, only what all of them
|
|
1441
|
+
* wrote counts.
|
|
1442
|
+
*
|
|
1443
|
+
* A `.break()` leaves the enclosing loop with what its path wrote so far, so
|
|
1444
|
+
* its tail goes to `breaks`, and the loop counts it as one of the ways out. A
|
|
1445
|
+
* loop body may run zero times, so it writes nothing for sure. A do-while body
|
|
1446
|
+
* runs at least once, and is left by its end or by a `.break()`, so what all of
|
|
1447
|
+
* those wrote counts. Every `.parallel` arm runs, so what each surely writes
|
|
1448
|
+
* counts.
|
|
1449
|
+
*/
|
|
1450
|
+
function writtenTails(steps, incoming, breaks) {
|
|
1451
|
+
let tails = incoming;
|
|
1452
|
+
for (const st of steps) {
|
|
1453
|
+
if (st.kind === 'stepToList')
|
|
1454
|
+
continue;
|
|
1455
|
+
if (st.kind === 'stepToRef') {
|
|
1456
|
+
if (st.port === 'output')
|
|
1457
|
+
tails = [];
|
|
1458
|
+
continue;
|
|
1459
|
+
}
|
|
1460
|
+
if (st.kind === 'break') {
|
|
1461
|
+
breaks.push(...tails);
|
|
1462
|
+
return [];
|
|
1463
|
+
}
|
|
1464
|
+
if (st.kind === 'return' || st.kind === 'terminate')
|
|
1465
|
+
return [];
|
|
1466
|
+
const before = surelyWritten(tails);
|
|
1467
|
+
const written = new Set([...before, ...Object.keys(st.options?.updates ?? {})]);
|
|
1468
|
+
const arm = (body) => writtenTails(body, [{ from: st.name, written }], breaks);
|
|
1469
|
+
if (st.kind === 'action') {
|
|
1470
|
+
tails = actionTails(st.name, st.spec, st.options?.version)
|
|
1471
|
+
.map((t) => ({ from: t.from, written }));
|
|
1472
|
+
}
|
|
1473
|
+
else if (st.kind === 'stepSwitch') {
|
|
1474
|
+
tails = st.cases.flatMap((c) => arm(c.body));
|
|
1475
|
+
}
|
|
1476
|
+
else if (st.kind === 'branch') {
|
|
1477
|
+
tails = [...arm(st.then), ...arm(st.otherwise)];
|
|
1478
|
+
}
|
|
1479
|
+
else if (st.kind === 'switch') {
|
|
1480
|
+
tails = [...st.cases.flatMap((c) => arm(c.body)), ...(st.default !== undefined ? arm(st.default) : [])];
|
|
1481
|
+
}
|
|
1482
|
+
else if (st.kind === 'doWhile') {
|
|
1483
|
+
const exits = [];
|
|
1484
|
+
exits.push(...writtenTails(st.body, [{ from: st.name, written }], exits));
|
|
1485
|
+
surelyWritten(exits).forEach((n) => written.add(n));
|
|
1486
|
+
tails = [{ from: st.name, written }];
|
|
1487
|
+
}
|
|
1488
|
+
else if (st.kind === 'parallel') {
|
|
1489
|
+
for (const body of st.arms)
|
|
1490
|
+
surelyWritten(arm(body)).forEach((n) => written.add(n));
|
|
1491
|
+
tails = [{ from: st.name, written }];
|
|
1492
|
+
}
|
|
1493
|
+
else {
|
|
1494
|
+
tails = [{ from: st.name, written }];
|
|
1495
|
+
}
|
|
1496
|
+
}
|
|
1497
|
+
return tails;
|
|
1498
|
+
}
|
|
1499
|
+
/** What every one of `tails` has written; nothing when there are none. */
|
|
1500
|
+
function surelyWritten(tails) {
|
|
1501
|
+
if (tails.length === 0)
|
|
1502
|
+
return new Set();
|
|
1503
|
+
return new Set([...tails[0].written].filter((n) => tails.every((t) => t.written.has(n))));
|
|
1382
1504
|
}
|
|
1383
1505
|
/**
|
|
1384
1506
|
* Closest of `candidates` to `name`: same word ignoring case, one contained in
|
|
@@ -1982,10 +2104,11 @@ function renamedFieldMessage(label, step, oldField) {
|
|
|
1982
2104
|
* - a `key` that is not a GUID. The commonest form of this is passing the
|
|
1983
2105
|
* process's NAME where its release key belongs, which produces a node type
|
|
1984
2106
|
* the platform cannot resolve to anything.
|
|
1985
|
-
* - a `
|
|
1986
|
-
* the
|
|
1987
|
-
*
|
|
1988
|
-
*
|
|
2107
|
+
* - a `name` that looks like a path (contains "/"). The two are swappable, and
|
|
2108
|
+
* the resulting `resourceKey` is wrong in a way that only surfaces as a 400
|
|
2109
|
+
* from Orchestrator ("Folder does not exist"). That is v1's own 2026-06-26
|
|
2110
|
+
* failure mode on this task. A single-segment `folderPath` is NOT decidable:
|
|
2111
|
+
* root folders and personal workspaces are one segment, so it is not flagged.
|
|
1989
2112
|
*/
|
|
1990
2113
|
function checkRpaWorkflow(step, inputs, diags) {
|
|
1991
2114
|
const at = (code, message, suggestion) => {
|
|
@@ -2009,33 +2132,33 @@ function checkRpaWorkflow(step, inputs, diags) {
|
|
|
2009
2132
|
at('RPA_NO_NAME', `RPA step "${step}" has no \`name\`. The runtime starts the job by name + folder, not by the key in `
|
|
2010
2133
|
+ `the node type, so without it the process cannot be resolved and the run fails before dispatch.`, `name: 'RPA Workflow'`);
|
|
2011
2134
|
}
|
|
2012
|
-
|
|
2135
|
+
const inSolution = inputs?.location === 'in-solution';
|
|
2136
|
+
if (!inSolution && blank(inputs?.folderPath)) {
|
|
2013
2137
|
at('RPA_NO_FOLDER', `RPA step "${step}" has no \`folderPath\`. The runtime starts the job by name + folder, so the `
|
|
2014
2138
|
+ `Orchestrator folder holding the process is required.`, `folderPath: 'Shared/uipath-maestro-flow/ProjectEuler RPA'`);
|
|
2015
2139
|
}
|
|
2140
|
+
if (inSolution && blank(inputs?.projectId)) {
|
|
2141
|
+
at('RPA_LOCAL_NO_PROJECT_ID', `In-solution RPA step "${step}" has no \`projectId\`. Read \`resource.projectKey\` from `
|
|
2142
|
+
+ `\`resources/solution_folder/process/process/<project>.json\`.`, `projectId: '<resource.projectKey>'`);
|
|
2143
|
+
}
|
|
2144
|
+
else if (inSolution && !GUID_RE.test(String(inputs.projectId).trim())) {
|
|
2145
|
+
at('RPA_LOCAL_PROJECT_ID_NOT_GUID', `In-solution RPA step "${step}" has projectId ${JSON.stringify(inputs.projectId)}, which is not `
|
|
2146
|
+
+ `the sibling project's GUID. Read \`resource.projectKey\` from the generated resource file.`, `projectId: '<resource.projectKey>'`);
|
|
2147
|
+
}
|
|
2148
|
+
if (inSolution && !blank(inputs?.folderPath)) {
|
|
2149
|
+
at('RPA_LOCAL_HAS_FOLDER', `In-solution RPA step "${step}" also sets \`folderPath\` ${JSON.stringify(inputs.folderPath)}. An `
|
|
2150
|
+
+ `in-solution sibling has no folder until the solution is deployed — the platform's own manifest `
|
|
2151
|
+
+ `writes \`""\` — so the value would be dropped. Remove it, or drop \`location: 'in-solution'\` to `
|
|
2152
|
+
+ `call the published copy.`);
|
|
2153
|
+
}
|
|
2016
2154
|
// The two are swappable by shape, and swapping them is invisible until
|
|
2017
2155
|
// Orchestrator answers "Folder does not exist or the user does not have access".
|
|
2018
2156
|
const name = typeof inputs?.name === 'string' ? inputs.name.trim() : '';
|
|
2019
|
-
const folder = typeof inputs?.folderPath === 'string' ? inputs.folderPath.trim() : '';
|
|
2020
2157
|
if (name.includes('/')) {
|
|
2021
2158
|
at('RPA_NAME_IS_PATH', `RPA step "${step}"'s \`name\` is ${JSON.stringify(inputs.name)}, which is a folder path. \`name\` is `
|
|
2022
2159
|
+ `the process's own name in Orchestrator; the path it lives in goes in \`folderPath\`. Together they `
|
|
2023
2160
|
+ `form the resourceKey "<folderPath>.<name>", so swapping them points the job at nothing.`);
|
|
2024
2161
|
}
|
|
2025
|
-
if (folder && !folder.includes('/') && name && !name.includes('/')) {
|
|
2026
|
-
// A single-segment folder is legal ("Shared"), so this is only worth saying
|
|
2027
|
-
// when the folder ALSO fails to look like one of the tenant's roots.
|
|
2028
|
-
if (!/^(Shared|Personal|My Workspace)$/i.test(folder)) {
|
|
2029
|
-
diags.push({
|
|
2030
|
-
level: 'warning',
|
|
2031
|
-
code: 'RPA_FOLDER_NOT_PATH',
|
|
2032
|
-
message: `RPA step "${step}"'s \`folderPath\` is ${JSON.stringify(inputs.folderPath)} — a single segment with no `
|
|
2033
|
-
+ `"/". Orchestrator folder paths are usually nested (e.g. "Shared/<team>/<solution>"). If this is `
|
|
2034
|
-
+ `the process's name rather than its folder, the two arguments are swapped.`,
|
|
2035
|
-
step,
|
|
2036
|
-
});
|
|
2037
|
-
}
|
|
2038
|
-
}
|
|
2039
2162
|
}
|
|
2040
2163
|
/**
|
|
2041
2164
|
* Check an `apiWorkflow(...)` call.
|
|
@@ -2636,10 +2759,33 @@ function checkAgenticProcess(step, inputs, diags) {
|
|
|
2636
2759
|
+ `the key in the node type, so without it the process cannot be resolved and the run fails before `
|
|
2637
2760
|
+ `dispatch.`, `name: 'ProcurementProcess'`);
|
|
2638
2761
|
}
|
|
2639
|
-
|
|
2762
|
+
const inSolution = inputs?.location === 'in-solution';
|
|
2763
|
+
if (!inSolution && blank(inputs?.folderPath)) {
|
|
2640
2764
|
at('AGENTIC_NO_FOLDER', `Agentic process step "${step}" has no \`folderPath\`. The runtime starts the job by name + folder, so `
|
|
2641
2765
|
+ `the Orchestrator folder holding the process is required.`, `folderPath: 'Shared/uipath-agents/ProcurementProcess'`);
|
|
2642
2766
|
}
|
|
2767
|
+
const form = inputs?.form ?? 'bpmn';
|
|
2768
|
+
if (inSolution && form !== 'flow') {
|
|
2769
|
+
at('AGENTIC_LOCAL_FORM_UNVERIFIED', `In-solution agentic process step "${step}" has \`form: ${JSON.stringify(form)}\`. Only \`form: 'flow'\` `
|
|
2770
|
+
+ `has been run against an in-solution sibling; the ${JSON.stringify(form)} shape is unproven. Call the `
|
|
2771
|
+
+ `published process instead (drop \`location\` and \`projectId\`, set \`folderPath\`), or use `
|
|
2772
|
+
+ `\`form: 'flow'\` if the sibling is a Flow.`, `location: 'published', folderPath: 'Shared/<folder>'`);
|
|
2773
|
+
}
|
|
2774
|
+
if (inSolution && blank(inputs?.projectId)) {
|
|
2775
|
+
const folder = (AGENTIC_PROCESS_FORMS[inputs?.form ?? 'bpmn'] ?? AGENTIC_PROCESS_FORMS.bpmn).solutionFolder;
|
|
2776
|
+
at('AGENTIC_LOCAL_NO_PROJECT_ID', `In-solution agentic process step "${step}" has no \`projectId\`. Read \`resource.projectKey\` from `
|
|
2777
|
+
+ `\`resources/solution_folder/process/${folder}/<project>.json\`.`, `projectId: '<resource.projectKey>'`);
|
|
2778
|
+
}
|
|
2779
|
+
else if (inSolution && !GUID_RE.test(String(inputs.projectId).trim())) {
|
|
2780
|
+
at('AGENTIC_LOCAL_PROJECT_ID_NOT_GUID', `In-solution agentic process step "${step}" has projectId ${JSON.stringify(inputs.projectId)}, which is not `
|
|
2781
|
+
+ `the sibling project's GUID. Read \`resource.projectKey\` from the generated resource file.`, `projectId: '<resource.projectKey>'`);
|
|
2782
|
+
}
|
|
2783
|
+
if (inSolution && !blank(inputs?.folderPath)) {
|
|
2784
|
+
at('AGENTIC_LOCAL_HAS_FOLDER', `In-solution agentic process step "${step}" also sets \`folderPath\` ${JSON.stringify(inputs.folderPath)}. An `
|
|
2785
|
+
+ `in-solution sibling has no folder until the solution is deployed — the platform's own manifest `
|
|
2786
|
+
+ `writes \`""\` — so the value would be dropped. Remove it, or drop \`location: 'in-solution'\` to `
|
|
2787
|
+
+ `call the published copy.`);
|
|
2788
|
+
}
|
|
2643
2789
|
const name = typeof inputs?.name === 'string' ? inputs.name.trim() : '';
|
|
2644
2790
|
if (name.includes('/')) {
|
|
2645
2791
|
at('AGENTIC_NAME_IS_PATH', `Agentic process step "${step}"'s \`name\` is ${JSON.stringify(inputs.name)}, which is a folder path. `
|
|
@@ -3642,7 +3788,7 @@ function checkQueueItem(step, inputs, diags) {
|
|
|
3642
3788
|
+ `Orchestrator rejects a queue item whose property values are not simple: `
|
|
3643
3789
|
+ `"Property values must be simple (like numbers or text) and not complex or composite `
|
|
3644
3790
|
+ `data types (like objects or arrays)". Flatten it, or JSON-encode it into one string `
|
|
3645
|
-
+ `field.`, `item: { ${k}: JSON.stringify(...) } // or ${k}Id, ${k}Name, …`);
|
|
3791
|
+
+ `field.`, `item: { ${k}: JSON.stringify(...) } // or ${commentText(k)}Id, ${commentText(k)}Name, …`);
|
|
3646
3792
|
}
|
|
3647
3793
|
}
|
|
3648
3794
|
}
|
|
@@ -3933,7 +4079,7 @@ function fanInDiag(join, arm, steps, tails) {
|
|
|
3933
4079
|
+ `edge, so the run never gets past "${join}" — with no incident, and \`flow validate\` answers Valid. Add a `
|
|
3934
4080
|
+ `step at the end of the arm${last?.name ? `, after "${last.name}"` : ''}, so the paths meet on that step and `
|
|
3935
4081
|
+ `only its one edge reaches the join, or move human task "${task}" out of the \`.parallel(…)\`.`,
|
|
3936
|
-
suggestion: `….step('record', …) // last step of arm ${arm + 1}`,
|
|
4082
|
+
suggestion: `….step('record', …) // last step of arm ${commentText(arm + 1)}`,
|
|
3937
4083
|
};
|
|
3938
4084
|
}
|
|
3939
4085
|
/**
|
|
@@ -5623,3 +5769,39 @@ function checkSchemaVersion(built, ancestors, diags) {
|
|
|
5623
5769
|
});
|
|
5624
5770
|
}
|
|
5625
5771
|
}
|
|
5772
|
+
/**
|
|
5773
|
+
* Run a custom node's contribution lowering DRY, so `check` reports what
|
|
5774
|
+
* `compile` would throw. For instance, a `rawNode` carrying an in-solution
|
|
5775
|
+
* sibling's manifest with no `values` row for `<bindings.folderPath>` fails here
|
|
5776
|
+
* with the same code compile throws.
|
|
5777
|
+
*
|
|
5778
|
+
* It calls `contribute()`, resolves the definition the way serialize does, and
|
|
5779
|
+
* plans the `bindings[]` entries serialize would build
|
|
5780
|
+
* ({@link planContributionBindings}). A name the plan cannot close is
|
|
5781
|
+
* `CONTRIBUTION_BINDING_UNRESOLVED` — the same code and sentence serialize
|
|
5782
|
+
* throws. A contribution that throws is `CONTRIBUTION_INVALID`, unless
|
|
5783
|
+
* `ownError` says the node's own `diagnostics()` already reported an error (a
|
|
5784
|
+
* raw connector node throws for the reason `RAW_NODE_CONNECTOR` already gives).
|
|
5785
|
+
*/
|
|
5786
|
+
function checkContribution(node, requestedVersion, step, ownError, diags) {
|
|
5787
|
+
let def;
|
|
5788
|
+
let supplied;
|
|
5789
|
+
try {
|
|
5790
|
+
const contribution = node.contribute(requestedVersion !== undefined ? { requestedVersion } : {});
|
|
5791
|
+
def = resolveContributionDefinition(contribution.definition, requestedVersion, `step "${step}"`);
|
|
5792
|
+
supplied = contribution.bindings;
|
|
5793
|
+
}
|
|
5794
|
+
catch (e) {
|
|
5795
|
+
if (ownError)
|
|
5796
|
+
return;
|
|
5797
|
+
diags.push({
|
|
5798
|
+
level: 'error', code: 'CONTRIBUTION_INVALID', step,
|
|
5799
|
+
message: `compile cannot lower step "${step}": ${e instanceof Error ? e.message : String(e)}`,
|
|
5800
|
+
});
|
|
5801
|
+
return;
|
|
5802
|
+
}
|
|
5803
|
+
const plan = planContributionBindings(def, supplied);
|
|
5804
|
+
if (plan.problems.length === 0)
|
|
5805
|
+
return;
|
|
5806
|
+
diags.push({ level: 'error', code: 'CONTRIBUTION_BINDING_UNRESOLVED', step, message: plan.problems.join(' ') });
|
|
5807
|
+
}
|
package/dist/cli-result.d.ts
CHANGED
|
@@ -34,3 +34,12 @@ export declare function reportDiagnostics(diags: ReadonlyArray<{
|
|
|
34
34
|
message: string;
|
|
35
35
|
suggestion?: string;
|
|
36
36
|
}>): void;
|
|
37
|
+
/**
|
|
38
|
+
* The error diagnostic for a throw that ends a compile: the error's own string
|
|
39
|
+
* `code` when it carries one, else `COMPILE_FAILED`.
|
|
40
|
+
*/
|
|
41
|
+
export declare function thrownDiagnostic(error: unknown): {
|
|
42
|
+
level: 'error';
|
|
43
|
+
code: string;
|
|
44
|
+
message: string;
|
|
45
|
+
};
|
package/dist/cli-result.js
CHANGED
|
@@ -47,3 +47,15 @@ export function reportDiagnostics(diags) {
|
|
|
47
47
|
diagnostics: diags.map((d) => ({ level: d.level, code: d.code, message: d.message, ...(d.suggestion ? { suggestion: d.suggestion } : {}) })),
|
|
48
48
|
});
|
|
49
49
|
}
|
|
50
|
+
/**
|
|
51
|
+
* The error diagnostic for a throw that ends a compile: the error's own string
|
|
52
|
+
* `code` when it carries one, else `COMPILE_FAILED`.
|
|
53
|
+
*/
|
|
54
|
+
export function thrownDiagnostic(error) {
|
|
55
|
+
const code = error?.code;
|
|
56
|
+
return {
|
|
57
|
+
level: 'error',
|
|
58
|
+
code: typeof code === 'string' && code.length > 0 ? code : 'COMPILE_FAILED',
|
|
59
|
+
message: error instanceof Error ? error.message : String(error),
|
|
60
|
+
};
|
|
61
|
+
}
|
package/dist/compile-cli.js
CHANGED
|
@@ -17,8 +17,9 @@
|
|
|
17
17
|
* product's own gates are supposed to answer. `.build()` still throws on
|
|
18
18
|
* structural problems; only the diagnostic pass is skipped.
|
|
19
19
|
*/
|
|
20
|
-
import { reportDiagnostics, reportResult } from './cli-result.js';
|
|
21
|
-
import {
|
|
20
|
+
import { reportDiagnostics, reportResult, thrownDiagnostic } from './cli-result.js';
|
|
21
|
+
import { writeJsonFile } from './core/json-file.js';
|
|
22
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync } from 'node:fs';
|
|
22
23
|
import { basename, dirname, join, resolve } from 'node:path';
|
|
23
24
|
import { resolveFlowFile, loadBuiltFlow } from './load.js';
|
|
24
25
|
import { serializeWithSidecars, connectorInputErrors, compileOwnsResource } from './serialize.js';
|
|
@@ -26,9 +27,10 @@ import { check } from './check.js';
|
|
|
26
27
|
import { byoaCheckOpts, eventResolveErrorFor, openCliLibrary } from './cli-library.js';
|
|
27
28
|
import { Bindings } from './bindings.js';
|
|
28
29
|
import { flagValue, runWhenInvokedDirectly } from './cli-run.js';
|
|
29
|
-
import { connectionResources, mergeBindingsV2, readBindingsV2 } from './bindings-v2.js';
|
|
30
|
+
import { connectionResources, inlineAgentResources, mergeBindingsV2, mergeInlineAgentResources, readBindingsV2, readInlineAgentFiles, } from './bindings-v2.js';
|
|
30
31
|
import { ensureTypeScriptRuntime } from './node-runtime.js';
|
|
31
32
|
import { defaultBindingsFile } from './workdir.js';
|
|
33
|
+
import { isWithin } from './path-within.js';
|
|
32
34
|
import { BindingRefusal } from './core/binding-messages.js';
|
|
33
35
|
import { ByoaRefusal } from './core/byoa.js';
|
|
34
36
|
export async function run(argv) {
|
|
@@ -65,6 +67,8 @@ export async function run(argv) {
|
|
|
65
67
|
const library = openCliLibrary(argv);
|
|
66
68
|
const bindFile = flagValue(argv, '--bindings') ?? defaultBindingsFile();
|
|
67
69
|
const bindings = new Bindings(bindFile);
|
|
70
|
+
// One list per run; every exit path reports it (reportResult overwrites keys).
|
|
71
|
+
const diagnostics = [];
|
|
68
72
|
if (!noCheck) {
|
|
69
73
|
const diags = check(built, library
|
|
70
74
|
? {
|
|
@@ -100,8 +104,9 @@ export async function run(argv) {
|
|
|
100
104
|
const errors = diags.filter((d) => d.level === 'error');
|
|
101
105
|
for (const d of diags)
|
|
102
106
|
console.error(` ${d.level === 'error' ? '✗' : '⚠'} [${d.code}] ${d.message}${d.suggestion ? ` (did you mean "${d.suggestion}"?)` : ''}`);
|
|
107
|
+
diagnostics.push(...diags);
|
|
103
108
|
if (errors.length) {
|
|
104
|
-
reportDiagnostics(
|
|
109
|
+
reportDiagnostics(diagnostics);
|
|
105
110
|
console.error(`compile: ${errors.length} error(s); not emitting.`);
|
|
106
111
|
return 1;
|
|
107
112
|
}
|
|
@@ -121,28 +126,27 @@ export async function run(argv) {
|
|
|
121
126
|
emitted = serializeWithSidecars(built, { library, bindings, warn: (w) => warnings.push(w) });
|
|
122
127
|
}
|
|
123
128
|
catch (error) {
|
|
124
|
-
if (!(error instanceof BindingRefusal || error instanceof ByoaRefusal))
|
|
125
|
-
throw error;
|
|
126
129
|
for (const w of warnings)
|
|
127
130
|
console.error(` ⚠ [${w.code}] ${w.message}`);
|
|
131
|
+
diagnostics.push(...warnings.map(serializeWarningDiagnostic));
|
|
132
|
+
if (!(error instanceof BindingRefusal || error instanceof ByoaRefusal)) {
|
|
133
|
+
// Any other throw still ends the run through the caller (exit 1, `--json`
|
|
134
|
+
// `error`); the list is reported first.
|
|
135
|
+
diagnostics.push(thrownDiagnostic(error));
|
|
136
|
+
reportDiagnostics(diagnostics);
|
|
137
|
+
throw error;
|
|
138
|
+
}
|
|
128
139
|
console.error(` ✗ [${error.code}] ${error.message}`);
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
{ level: 'error', code: error.code, message: error.message },
|
|
132
|
-
]);
|
|
140
|
+
diagnostics.push({ level: 'error', code: error.code, message: error.message });
|
|
141
|
+
reportDiagnostics(diagnostics);
|
|
133
142
|
console.error('compile: 1 error(s); not emitting.');
|
|
134
143
|
return 1;
|
|
135
144
|
}
|
|
136
145
|
const { flow: flowJson, sidecars } = emitted;
|
|
137
146
|
for (const w of warnings)
|
|
138
147
|
console.error(` ⚠ [${w.code}] ${w.message}`);
|
|
148
|
+
diagnostics.push(...warnings.map(serializeWarningDiagnostic));
|
|
139
149
|
const out = flagValue(argv, '-o') ?? `${built.name || basename(file).replace(/\.flow\.ts$|\.ts$/, '')}.flow`;
|
|
140
|
-
writeFileSync(out, JSON.stringify(flowJson, null, 2) + '\n');
|
|
141
|
-
console.log(`compile: wrote ${out} (${flowJson.nodes.length} nodes, ${flowJson.edges.length} edges)`);
|
|
142
|
-
const written = [];
|
|
143
|
-
reportResult({ path: resolve(out), nodes: flowJson.nodes.length, edges: flowJson.edges.length, sidecars: written });
|
|
144
|
-
// Same keys and types as a failed compile: `warnings` a count, `diagnostics` the list.
|
|
145
|
-
reportDiagnostics(warnings.map((w) => ({ level: 'warning', code: w.code, message: w.message })));
|
|
146
150
|
// An inline agent is TWO artifacts: the node, and the agent's own configuration
|
|
147
151
|
// at `<source>/agent.json` — which is where flow-v1's serializer points the
|
|
148
152
|
// runtime (`entryPoint: "<source>/agent.json"`, resolved relative to the
|
|
@@ -151,19 +155,31 @@ export async function run(argv) {
|
|
|
151
155
|
// Sidecar paths are relative to the emitted flow, and are confined to its
|
|
152
156
|
// directory: a `..` or absolute path in one would be a bug here, not author
|
|
153
157
|
// input, but it is cheap to refuse rather than write outside the project.
|
|
158
|
+
//
|
|
159
|
+
// Every sidecar is checked before anything is written; success is reported
|
|
160
|
+
// only once every file is on disk.
|
|
154
161
|
const outDir = dirname(resolve(out));
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
+
const targets = sidecars.map((s) => ({ sidecar: s, target: resolve(outDir, s.path) }));
|
|
163
|
+
const escaping = targets.find(({ target }) => target === outDir || !isWithin(outDir, target));
|
|
164
|
+
if (escaping) {
|
|
165
|
+
const message = `refusing to write a sidecar outside the flow's directory: ${escaping.sidecar.path}`;
|
|
166
|
+
console.error(`compile: ${message}; not emitting.`);
|
|
167
|
+
diagnostics.push({ level: 'error', code: 'SIDECAR_OUTSIDE_FLOW_DIR', message });
|
|
168
|
+
reportDiagnostics(diagnostics);
|
|
169
|
+
return 1;
|
|
170
|
+
}
|
|
171
|
+
// Reported before the first write, so a write that throws still carries it.
|
|
172
|
+
reportDiagnostics(diagnostics);
|
|
173
|
+
writeJsonFile(out, flowJson);
|
|
174
|
+
console.log(`compile: wrote ${out} (${flowJson.nodes.length} nodes, ${flowJson.edges.length} edges)`);
|
|
175
|
+
const written = [];
|
|
176
|
+
for (const { sidecar, target } of targets) {
|
|
162
177
|
mkdirSync(dirname(target), { recursive: true });
|
|
163
|
-
|
|
164
|
-
console.log(`compile: wrote ${
|
|
178
|
+
writeJsonFile(target, sidecar.json);
|
|
179
|
+
console.log(`compile: wrote ${sidecar.path}`);
|
|
165
180
|
written.push(target);
|
|
166
181
|
}
|
|
182
|
+
reportResult({ path: resolve(out), nodes: flowJson.nodes.length, edges: flowJson.edges.length, sidecars: written });
|
|
167
183
|
// PRUNE the agent resources this compile owns but did not write this time.
|
|
168
184
|
// Compile otherwise only ever ADDS files, and an agent resource is keyed by a
|
|
169
185
|
// uuid derived from the step name and the resource's identity — so dropping a
|
|
@@ -180,13 +196,14 @@ export async function run(argv) {
|
|
|
180
196
|
// families the SDK cannot into the same directory (including `internal`
|
|
181
197
|
// built-ins such as `load-attachments`), so anything else found there is
|
|
182
198
|
// someone else's and is left alone.
|
|
183
|
-
const
|
|
199
|
+
const removedResources = [];
|
|
184
200
|
const owned = new Set(sidecars
|
|
185
201
|
.filter((s) => /\/resources\/[^/]+\/resource\.json$/.test(s.path))
|
|
186
202
|
.map((s) => resolve(outDir, s.path)));
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
203
|
+
const agentDirs = [...new Set(sidecars
|
|
204
|
+
.filter((s) => s.path.endsWith('/agent.json'))
|
|
205
|
+
.map((s) => dirname(resolve(outDir, s.path))))];
|
|
206
|
+
for (const agentDir of agentDirs) {
|
|
190
207
|
const resourcesDir = join(agentDir, 'resources');
|
|
191
208
|
if (!existsSync(resourcesDir))
|
|
192
209
|
continue;
|
|
@@ -203,23 +220,7 @@ export async function run(argv) {
|
|
|
203
220
|
}
|
|
204
221
|
if (!compileOwnsResource(body))
|
|
205
222
|
continue;
|
|
206
|
-
|
|
207
|
-
// The binding `uip agent refresh` derived FROM this file has to go with it.
|
|
208
|
-
// Refresh will not retract it: with no resources left it writes nothing, and
|
|
209
|
-
// `mergeBindingsV2` preserves every non-connection entry, so the removed
|
|
210
|
-
// index would stay declared in `bindings_v2.json` and a deploy could still
|
|
211
|
-
// fail on it. Keyed the way refresh keys it — an index by its name.
|
|
212
|
-
const named = body?.indexName ?? body?.name;
|
|
213
|
-
const key = typeof named === 'string' && named !== '' ? named : undefined;
|
|
214
|
-
// A connector tool's binding is a `connection` row, and `mergeBindingsV2`
|
|
215
|
-
// re-derives every connection row from the flow below — nothing to retract.
|
|
216
|
-
// A built-in (`internal`) tool binds nothing at all: retracting a `process`
|
|
217
|
-
// row under its label could only remove a real process tool's binding.
|
|
218
|
-
if (key !== undefined && body?.type !== 'integration' && body?.type !== 'internal') {
|
|
219
|
-
// Keyed the way refresh keys it: an index by its name, a deployed-resource
|
|
220
|
-
// tool under `process` whatever its own type says.
|
|
221
|
-
staleBindings.push({ resource: resourceType === 'context' ? 'index' : 'process', key });
|
|
222
|
-
}
|
|
223
|
+
removedResources.push(body);
|
|
223
224
|
rmSync(join(resourcesDir, entry), { recursive: true, force: true });
|
|
224
225
|
console.log(`compile: removed stale ${join('resources', entry, 'resource.json')}`);
|
|
225
226
|
}
|
|
@@ -235,20 +236,44 @@ export async function run(argv) {
|
|
|
235
236
|
const sidecarPath = join(outDir, 'bindings_v2.json');
|
|
236
237
|
const connections = connectionResources(flowJson);
|
|
237
238
|
const existing = readBindingsV2(sidecarPath);
|
|
239
|
+
// Each inline agent's own rows (process, index, app, … — #962), derived from
|
|
240
|
+
// the agent directory as it now stands on disk, the way `uip agent refresh
|
|
241
|
+
// --inline-in-flow` derives them. `uip agent validate --inline-in-flow`
|
|
242
|
+
// requires every one of them, so leaving them to a refresh the SDK route
|
|
243
|
+
// never runs made every agent with a deployed-resource tool, a context index
|
|
244
|
+
// or an app escalation fail validate with `AgentValidationDrift`.
|
|
245
|
+
const agentRows = agentDirs
|
|
246
|
+
.map((dir) => readInlineAgentFiles(dir))
|
|
247
|
+
.filter((files) => files !== undefined)
|
|
248
|
+
.map((files) => inlineAgentResources(files));
|
|
249
|
+
// The rows a PRUNED resource declared go with it: refresh never retracts a
|
|
250
|
+
// row, so an index the flow dropped would stay declared and a deploy could
|
|
251
|
+
// still fail on it. Generated by the same function, so they match exactly,
|
|
252
|
+
// and kept when a resource that still exists declares the same row.
|
|
253
|
+
// Connection rows are skipped: every one is re-derived from the flow below.
|
|
254
|
+
const live = new Set(agentRows.flat().map((r) => `${r.resource}\u0000${r.key}`));
|
|
255
|
+
const stale = new Set(inlineAgentResources({ agent: {}, resources: removedResources, features: [] })
|
|
256
|
+
.filter((r) => r.resource !== 'connection' && !live.has(`${r.resource}\u0000${r.key}`))
|
|
257
|
+
.map((r) => `${r.resource}\u0000${r.key}`));
|
|
238
258
|
// Nothing to declare and nothing already declared — do not create an empty
|
|
239
259
|
// file for a flow that has no resources at all.
|
|
240
|
-
if (connections.length > 0 || existing !== undefined) {
|
|
260
|
+
if (connections.length > 0 || existing !== undefined || agentRows.some((rows) => rows.length > 0)) {
|
|
241
261
|
const merged = mergeBindingsV2(existing, connections);
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
merged.resources = merged.resources
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
const
|
|
262
|
+
merged.resources = merged.resources.filter((r) => !stale.has(`${r.resource}\u0000${r.key}`));
|
|
263
|
+
for (const rows of agentRows)
|
|
264
|
+
merged.resources = mergeInlineAgentResources(merged.resources, rows);
|
|
265
|
+
writeJsonFile(sidecarPath, merged);
|
|
266
|
+
const derived = new Set([...connections, ...agentRows.flat()].map((r) => `${r.resource}\u0000${r.key}`));
|
|
267
|
+
const agentCount = merged.resources.filter((r) => r.resource !== 'connection' && derived.has(`${r.resource}\u0000${r.key}`)).length;
|
|
268
|
+
const kept = merged.resources.filter((r) => !derived.has(`${r.resource}\u0000${r.key}`)).length;
|
|
248
269
|
console.log(`compile: wrote bindings_v2.json (${connections.length} connection resource(s)`
|
|
270
|
+
+ `${agentCount > 0 ? `, ${agentCount} inline-agent resource(s)` : ''}`
|
|
249
271
|
+ `${kept > 0 ? `, ${kept} other resource(s) preserved` : ''})`);
|
|
250
272
|
}
|
|
251
273
|
}
|
|
252
274
|
return 0;
|
|
253
275
|
}
|
|
276
|
+
function serializeWarningDiagnostic(w) {
|
|
277
|
+
return { level: 'warning', code: w.code, message: w.message };
|
|
278
|
+
}
|
|
254
279
|
runWhenInvokedDirectly(import.meta.url, 'compile', run);
|