@tenonhq/dovetail-servicenow 0.0.27 → 0.0.28
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/README.md +57 -1
- package/dist/cli.js +100 -4
- package/dist/flowDesigner/editActionType.d.ts +29 -0
- package/dist/flowDesigner/editActionType.js +53 -3
- package/dist/flowDesigner/index.d.ts +2 -0
- package/dist/flowDesigner/index.js +8 -1
- package/dist/flowDesigner/stepOps.d.ts +140 -0
- package/dist/flowDesigner/stepOps.js +463 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +6 -2
- package/dist/mcp/registry.d.ts +1 -1
- package/dist/mcp/registry.js +27 -0
- package/dist/mcp/schemas.d.ts +257 -10
- package/dist/mcp/schemas.js +44 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -201,8 +201,62 @@ npx dove-sn edit-action --sys-id <action_type_sys_id> --scope <scope_sys_id> \
|
|
|
201
201
|
--patch-script "grabHashData::grabRecipients" # dry-run (diff)
|
|
202
202
|
npx dove-sn edit-action --sys-id <id> --scope <scope> --set-script ./script.js \
|
|
203
203
|
--merge-outputs ./output-var.json --apply --update-set <id> # persist + publish
|
|
204
|
+
|
|
205
|
+
# Edit it STRUCTURALLY — several steps' scripts, step-level IO, pill wiring — in one publish
|
|
206
|
+
npx dove-sn edit-action --sys-id <id> --scope <scope> --from-json ops.json # dry-run
|
|
207
|
+
npx dove-sn edit-action --sys-id <id> --scope <scope> --from-json ops.json \
|
|
208
|
+
--apply --update-set <id> # publish + verify
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
### Editing an action type's steps (`--from-json`)
|
|
212
|
+
|
|
213
|
+
The flag form above patches the one auto-detected script. When you need to touch
|
|
214
|
+
more than one step — or the step's own inputs and outputs — pass an ops file:
|
|
215
|
+
|
|
216
|
+
```json
|
|
217
|
+
{
|
|
218
|
+
"patchStepScripts": [
|
|
219
|
+
{ "step": "Parse Response", "scriptFile": "./parse-response.js" },
|
|
220
|
+
{ "step": "Handle Error", "patchScript": { "find": "gs.error", "replace": "gs.warn" } }
|
|
221
|
+
],
|
|
222
|
+
"addStepOutputs": [
|
|
223
|
+
{ "step": "Parse Response", "name": "isRetryable", "label": "Is Retryable", "type": "boolean" }
|
|
224
|
+
],
|
|
225
|
+
"addStepInputs": [
|
|
226
|
+
{
|
|
227
|
+
"step": "Handle Error",
|
|
228
|
+
"name": "isRetryable",
|
|
229
|
+
"type": "boolean",
|
|
230
|
+
"pillFrom": { "step": "Parse Response", "output": "isRetryable" }
|
|
231
|
+
}
|
|
232
|
+
]
|
|
233
|
+
}
|
|
204
234
|
```
|
|
205
235
|
|
|
236
|
+
- **`step`** is a step's `cid` **or** its label — an unknown ref fails with the list of steps that do exist.
|
|
237
|
+
- **`scriptFile`** is sugar for `setScript`, resolved **relative to the ops file**, so scripts can live beside it.
|
|
238
|
+
- **`addStepInputs[].pillFrom`** wires the input to another step's output. You never write the pill
|
|
239
|
+
yourself — the correct format is `{{step[<source_cid>].<output>}}`, and getting it wrong does not
|
|
240
|
+
fail the publish, it compiles a dead reference that reads `undefined` at runtime.
|
|
241
|
+
- Ops are **order-independent**: an input may pill from an output added in the same call. Everything
|
|
242
|
+
lands in a **single** `/snapshot` POST.
|
|
243
|
+
- Adding IO is **idempotent** — a name that is already present is skipped with a warning, not duplicated.
|
|
244
|
+
|
|
245
|
+
Two behaviours worth knowing before you rely on this:
|
|
246
|
+
|
|
247
|
+
**It refuses to guess an entry shape.** A new `extended_inputs` / `extended_outputs` entry is built by
|
|
248
|
+
mirroring an existing sibling entry on the same step, because those entries carry more keys than the
|
|
249
|
+
four you supply and some are wrapped as `{value: x}` inconsistently. If the step has *no* existing
|
|
250
|
+
entry in that list, there is nothing to mirror and the command **errors out** rather than hand-author
|
|
251
|
+
an object that would corrupt the action. Author one entry in the Designer first, then re-run.
|
|
252
|
+
|
|
253
|
+
**It verifies the publish.** A `201` from `/snapshot` means the snapshot compiled — not that your edit
|
|
254
|
+
landed as intended. With `--apply`, the steps are read back from the instance and compared against what
|
|
255
|
+
was sent: script **content** (hashed, so a same-length-but-different script can't pass) and each IO
|
|
256
|
+
entry's **name, type and value** — so an entry that landed with a mis-wired pill is caught, not just a
|
|
257
|
+
missing one. A mismatch prints the diff and exits **2**. When every op was a no-op, there is nothing to
|
|
258
|
+
read back and the round-trip is skipped.
|
|
259
|
+
|
|
206
260
|
`copy-flow` calls the Designer's own `POST /processflow/flow/{id}/copy` — a
|
|
207
261
|
complete, faithful clone created as an **inactive draft**. (Don't publish +
|
|
208
262
|
activate a copy of a triggered production flow unless you intend it to fire.)
|
|
@@ -427,7 +481,9 @@ Claude Code and agents: `create_view`, `set_list_layout`, `set_form_layout`,
|
|
|
427
481
|
existing record) and `create_record` (insert one record) — both update-set-captured
|
|
428
482
|
and read-back-verified — `host_assets` (deploy a built dist/), plus the Flow Designer
|
|
429
483
|
tools `flow_view` (read a flow/subflow's step graph), `action_view` (read an action
|
|
430
|
-
type's model), `
|
|
484
|
+
type's model), `action_edit` (structurally edit a published action type — per-step
|
|
485
|
+
scripts, step-level inputs/outputs, data-pill wiring — dry-run by default, and the
|
|
486
|
+
publish is read back and verified), `flow_publish` (compile a flow/subflow snapshot), `flow_copy`
|
|
431
487
|
(copy a flow as an inactive draft), `flow_create` (create a NEW flow from scratch +
|
|
432
488
|
publish, grafting a template), `flow_test` (validate or run a flow), and
|
|
433
489
|
`flow_edit` (patch a flow), plus `invoke_rest` (invoke an arbitrary authenticated
|
package/dist/cli.js
CHANGED
|
@@ -666,17 +666,54 @@ async function runEditFlow(flags) {
|
|
|
666
666
|
* dove-sn edit-action:
|
|
667
667
|
* --sys-id <sys_id> Required. sys_hub_action_type_definition sys_id.
|
|
668
668
|
* --scope <sys_id> Required. sysparm_transaction_scope (app scope sys_id).
|
|
669
|
-
* --
|
|
670
|
-
*
|
|
669
|
+
* --from-json <path> Optional. JSON EditActionTypeOps — the full surface, incl.
|
|
670
|
+
* per-step ops: patchStepScripts / addStepOutputs / addStepInputs.
|
|
671
|
+
* --patch-script "<find>::<replace>" Optional. Find/replace in the auto-detected script step value.
|
|
672
|
+
* --set-script <path> Optional. Replace the auto-detected script step value from a file.
|
|
671
673
|
* --merge-outputs <path> Optional. JSON file: an output-variable object/array to merge by name.
|
|
672
674
|
* --script-input <name> Optional. Input name holding the script (default: auto-detect).
|
|
673
675
|
* --update-set <sys_id> Optional. Capture the republish into this update set.
|
|
674
676
|
* --apply Optional. Republish (POST /snapshot). Omit for dry-run.
|
|
675
677
|
* --json Optional. Emit the structured EditActionTypeResult.
|
|
676
678
|
*
|
|
677
|
-
* Edits a published Custom Action Type
|
|
678
|
-
*
|
|
679
|
+
* Edits a published Custom Action Type and republishes through the snapshot POST.
|
|
680
|
+
* Dry-run (read-only) by default; --apply writes.
|
|
681
|
+
*
|
|
682
|
+
* The flag form handles the single-script case. For anything structural — patching
|
|
683
|
+
* several steps' scripts, adding a step-level output, adding a step-level input
|
|
684
|
+
* pill-wired to another step's output — use --from-json:
|
|
685
|
+
*
|
|
686
|
+
* {
|
|
687
|
+
* "patchStepScripts": [{ "step": "Parse Response", "scriptFile": "./parse.js" }],
|
|
688
|
+
* "addStepOutputs": [{ "step": "Parse Response", "name": "isRetryable", "type": "boolean" }],
|
|
689
|
+
* "addStepInputs": [{ "step": "Handle Error", "name": "isRetryable", "type": "boolean",
|
|
690
|
+
* "pillFrom": { "step": "Parse Response", "output": "isRetryable" } }]
|
|
691
|
+
* }
|
|
692
|
+
*
|
|
693
|
+
* `step` is a step cid or label. `scriptFile` is sugar for `setScript` and is
|
|
694
|
+
* resolved RELATIVE TO THE OPS FILE, so an ops file can sit next to its scripts.
|
|
679
695
|
*/
|
|
696
|
+
/** Resolve `scriptFile` sugar in patchStepScripts, relative to the ops file's own dir. */
|
|
697
|
+
function resolveScriptFiles(ops, opsPath) {
|
|
698
|
+
var stepScripts = ops.patchStepScripts;
|
|
699
|
+
if (!Array.isArray(stepScripts)) {
|
|
700
|
+
return;
|
|
701
|
+
}
|
|
702
|
+
var opsDir = path.dirname(path.resolve(opsPath));
|
|
703
|
+
for (var i = 0; i < stepScripts.length; i += 1) {
|
|
704
|
+
var op = stepScripts[i];
|
|
705
|
+
if (!op || typeof op !== "object" || typeof op.scriptFile !== "string") {
|
|
706
|
+
continue;
|
|
707
|
+
}
|
|
708
|
+
if (typeof op.setScript === "string") {
|
|
709
|
+
throw new Error("edit-action: step '" +
|
|
710
|
+
String(op.step) +
|
|
711
|
+
"' sets both scriptFile and setScript — pick one.");
|
|
712
|
+
}
|
|
713
|
+
op.setScript = fs.readFileSync(path.resolve(opsDir, op.scriptFile), "utf8");
|
|
714
|
+
delete op.scriptFile;
|
|
715
|
+
}
|
|
716
|
+
}
|
|
680
717
|
async function runEditAction(flags) {
|
|
681
718
|
var sysId = flags["sys-id"] || flags.sysId;
|
|
682
719
|
var scope = flags.scope || flags.scopeSysId;
|
|
@@ -685,6 +722,14 @@ async function runEditAction(flags) {
|
|
|
685
722
|
return 1;
|
|
686
723
|
}
|
|
687
724
|
var ops = {};
|
|
725
|
+
if (flags["from-json"]) {
|
|
726
|
+
ops = JSON.parse(fs.readFileSync(flags["from-json"], "utf8"));
|
|
727
|
+
if (!ops || typeof ops !== "object" || Array.isArray(ops)) {
|
|
728
|
+
process.stderr.write("edit-action: --from-json must contain an EditActionTypeOps object\n");
|
|
729
|
+
return 1;
|
|
730
|
+
}
|
|
731
|
+
resolveScriptFiles(ops, flags["from-json"]);
|
|
732
|
+
}
|
|
688
733
|
if (flags["patch-script"]) {
|
|
689
734
|
var parts = String(flags["patch-script"]).split("::");
|
|
690
735
|
if (parts.length !== 2) {
|
|
@@ -730,6 +775,51 @@ async function runEditAction(flags) {
|
|
|
730
775
|
for (var wi = 0; wi < result.warnings.length; wi += 1) {
|
|
731
776
|
process.stdout.write(" ! " + result.warnings[wi] + "\n");
|
|
732
777
|
}
|
|
778
|
+
// Per-step before/after — the dry-run's whole job is to make this inspectable.
|
|
779
|
+
if (result.stepsBefore && result.stepsAfter) {
|
|
780
|
+
process.stdout.write("\n--- steps (before -> after) ---\n");
|
|
781
|
+
for (var si = 0; si < result.stepsAfter.length; si += 1) {
|
|
782
|
+
var after = result.stepsAfter[si];
|
|
783
|
+
var before = result.stepsBefore[si];
|
|
784
|
+
var io = function (label, list) {
|
|
785
|
+
if (list.length === 0) {
|
|
786
|
+
return "";
|
|
787
|
+
}
|
|
788
|
+
var rendered = list
|
|
789
|
+
.map(function (e) {
|
|
790
|
+
return e.name + (e.value ? "=" + e.value : "");
|
|
791
|
+
})
|
|
792
|
+
.join(", ");
|
|
793
|
+
return "\n " + label + ": " + rendered;
|
|
794
|
+
};
|
|
795
|
+
process.stdout.write(" " +
|
|
796
|
+
after.label +
|
|
797
|
+
" (" +
|
|
798
|
+
after.cid +
|
|
799
|
+
")\n" +
|
|
800
|
+
" script: " +
|
|
801
|
+
String(before ? before.scriptChars : "?") +
|
|
802
|
+
" -> " +
|
|
803
|
+
String(after.scriptChars) +
|
|
804
|
+
" chars" +
|
|
805
|
+
io("in ", after.extendedInputs) +
|
|
806
|
+
io("out", after.extendedOutputs) +
|
|
807
|
+
"\n");
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
if (result.verified) {
|
|
811
|
+
process.stdout.write("\n--- verify (read back from the instance) ---\n");
|
|
812
|
+
process.stdout.write(" " + (result.verified.ok ? "OK" : "FAILED") + "\n");
|
|
813
|
+
for (var vi = 0; vi < result.verified.notes.length; vi += 1) {
|
|
814
|
+
process.stdout.write(" " +
|
|
815
|
+
(result.verified.ok ? "+ " : "! ") +
|
|
816
|
+
result.verified.notes[vi] +
|
|
817
|
+
"\n");
|
|
818
|
+
}
|
|
819
|
+
if (!result.verified.ok) {
|
|
820
|
+
return 2;
|
|
821
|
+
}
|
|
822
|
+
}
|
|
733
823
|
if (result.status === "preview" &&
|
|
734
824
|
result.scriptAfter !== undefined &&
|
|
735
825
|
result.scriptAfter !== result.scriptBefore) {
|
|
@@ -801,6 +891,12 @@ function printHelp() {
|
|
|
801
891
|
" [--update-set <sys_id>] [--max-bytes <n>] [--allow-oversize] [--dry-run] [--json])\n" +
|
|
802
892
|
" test-flow Validate (default) or run a flow/subflow\n" +
|
|
803
893
|
" (--sys-id <sys_id> [--execute --confirm] [--inputs <json>] [--json])\n" +
|
|
894
|
+
" edit-action Patch a published Custom Action Type and republish (snapshot)\n" +
|
|
895
|
+
" (--sys-id <sys_id> --scope <sys_id>\n" +
|
|
896
|
+
" --from-json <ops.json> ops: patchStepScripts / addStepOutputs / addStepInputs\n" +
|
|
897
|
+
" (per-step scripts + step IO + data-pill wiring)\n" +
|
|
898
|
+
' | --patch-script "<find>::<replace>" | --set-script <path> | --merge-outputs <path>\n' +
|
|
899
|
+
" [--script-input <name>] [--update-set <sys_id>] [--apply] [--json])\n" +
|
|
804
900
|
" edit-flow Patch a flow/subflow (rename, description, step inputs)\n" +
|
|
805
901
|
" (--sys-id <sys_id> --from-json <ops.json> [--apply] [--update-set <sys_id>] [--scope <sys_id>] [--json])\n" +
|
|
806
902
|
" mcp Run the MCP stdio server (--smoke lists tools and exits)\n" +
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
* Full write-up: docs/servicenow-flow-designer-headless-authoring.md.
|
|
24
24
|
*/
|
|
25
25
|
import type { ServiceNowClient } from "../client";
|
|
26
|
+
import type { AddStepInputOp, AddStepOutputOp, PatchStepScriptOp, StepSummary, VerifyStepsResult } from "./stepOps";
|
|
26
27
|
export interface EditActionTypeOps {
|
|
27
28
|
/** Replace every occurrence of `find` with `replace` inside the script step value. */
|
|
28
29
|
patchScript?: {
|
|
@@ -31,6 +32,25 @@ export interface EditActionTypeOps {
|
|
|
31
32
|
};
|
|
32
33
|
/** Replace the script step value outright (wins over patchScript). */
|
|
33
34
|
setScript?: string;
|
|
35
|
+
/**
|
|
36
|
+
* Per-step script edits, addressing each step by `cid` or `label`. Use this
|
|
37
|
+
* instead of patchScript/setScript when the action has more than one scripted
|
|
38
|
+
* step, or when you need to target a specific one rather than the auto-detected
|
|
39
|
+
* first match.
|
|
40
|
+
*/
|
|
41
|
+
patchStepScripts?: Array<PatchStepScriptOp>;
|
|
42
|
+
/**
|
|
43
|
+
* Step-level outputs (`extended_outputs`) to add — the values one step exposes
|
|
44
|
+
* to the steps after it. Idempotent: an output whose name is already present is
|
|
45
|
+
* skipped, not duplicated.
|
|
46
|
+
*/
|
|
47
|
+
addStepOutputs?: Array<AddStepOutputOp>;
|
|
48
|
+
/**
|
|
49
|
+
* Step-level inputs (`extended_inputs`) to add, each wired by data pill to
|
|
50
|
+
* another step's output via `pillFrom: { step, output }`. Outputs added in the
|
|
51
|
+
* same call are visible to these — it all lands in one snapshot.
|
|
52
|
+
*/
|
|
53
|
+
addStepInputs?: Array<AddStepInputOp>;
|
|
34
54
|
/**
|
|
35
55
|
* Output-variable definition objects to merge into `model.outputs`, matched by
|
|
36
56
|
* `name` (replaced in place, else appended). Supply the modeled output JSON —
|
|
@@ -63,6 +83,15 @@ export interface EditActionTypeResult {
|
|
|
63
83
|
scriptBefore?: string;
|
|
64
84
|
scriptAfter?: string;
|
|
65
85
|
outputsMerged: Array<string>;
|
|
86
|
+
/** Per-step scripts + step-level IO, as read (before) and as sent (after). */
|
|
87
|
+
stepsBefore?: Array<StepSummary>;
|
|
88
|
+
stepsAfter?: Array<StepSummary>;
|
|
89
|
+
/**
|
|
90
|
+
* Post-publish read-back of /step_instances for the steps we touched. A 201 from
|
|
91
|
+
* /snapshot means "compiled", not "your edit landed" — only set when applied and
|
|
92
|
+
* step ops were supplied.
|
|
93
|
+
*/
|
|
94
|
+
verified?: VerifyStepsResult;
|
|
66
95
|
/** HTTP status of the snapshot POST (201 on success); only set when applied. */
|
|
67
96
|
httpStatus?: number;
|
|
68
97
|
snapshotSysId?: string;
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
*/
|
|
26
26
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
27
27
|
exports.editActionType = editActionType;
|
|
28
|
+
const stepOps_1 = require("./stepOps");
|
|
28
29
|
function actionTypePath(sysId, scopeSysId, suffix) {
|
|
29
30
|
return "/api/now/processflow/action/action_types/" + encodeURIComponent(sysId)
|
|
30
31
|
+ suffix
|
|
@@ -70,12 +71,21 @@ async function editActionType(params) {
|
|
|
70
71
|
if (!scopeSysId) {
|
|
71
72
|
throw new Error("editActionType: scopeSysId is required (sysparm_transaction_scope).");
|
|
72
73
|
}
|
|
73
|
-
|
|
74
|
-
|
|
74
|
+
var stepOpsSupplied = (0, stepOps_1.hasStepOps)(ops);
|
|
75
|
+
if (!ops.patchScript && !ops.setScript && !(ops.mergeOutputs && ops.mergeOutputs.length > 0) && !stepOpsSupplied) {
|
|
76
|
+
throw new Error("editActionType: no ops — supply patchScript, setScript, mergeOutputs, "
|
|
77
|
+
+ "patchStepScripts, addStepOutputs, and/or addStepInputs.");
|
|
78
|
+
}
|
|
79
|
+
if (stepOpsSupplied && (ops.patchScript || ops.setScript)) {
|
|
80
|
+
throw new Error("editActionType: patchScript/setScript (auto-detected single script) cannot be combined with "
|
|
81
|
+
+ "patchStepScripts (explicit per-step). Use patchStepScripts for all script edits.");
|
|
75
82
|
}
|
|
76
83
|
var changes = [];
|
|
77
84
|
var warnings = [];
|
|
78
85
|
var outputsMerged = [];
|
|
86
|
+
var stepsBefore;
|
|
87
|
+
var stepsAfter;
|
|
88
|
+
var touchedCids = [];
|
|
79
89
|
// 1. GET the model (outputs[], steps:null).
|
|
80
90
|
var model = unwrap(await client.now.get(actionTypePath(sysId, scopeSysId, "")));
|
|
81
91
|
if (!model || typeof model !== "object") {
|
|
@@ -87,6 +97,20 @@ async function editActionType(params) {
|
|
|
87
97
|
if (steps.length === 0) {
|
|
88
98
|
throw new Error("editActionType: /step_instances returned no steps for action type " + sysId);
|
|
89
99
|
}
|
|
100
|
+
// 2b. Per-step ops — scripts, step-level IO, pill wiring. Pure; see stepOps.ts.
|
|
101
|
+
if (stepOpsSupplied) {
|
|
102
|
+
stepsBefore = (0, stepOps_1.summarizeSteps)(steps);
|
|
103
|
+
var applied = (0, stepOps_1.applyStepOps)(steps, {
|
|
104
|
+
patchStepScripts: ops.patchStepScripts,
|
|
105
|
+
addStepOutputs: ops.addStepOutputs,
|
|
106
|
+
addStepInputs: ops.addStepInputs
|
|
107
|
+
});
|
|
108
|
+
steps = applied.steps;
|
|
109
|
+
stepsAfter = (0, stepOps_1.summarizeSteps)(steps);
|
|
110
|
+
touchedCids = applied.touchedCids;
|
|
111
|
+
changes = changes.concat(applied.changes);
|
|
112
|
+
warnings = warnings.concat(applied.warnings);
|
|
113
|
+
}
|
|
90
114
|
// 3. Patch the script step input.
|
|
91
115
|
var scriptBefore;
|
|
92
116
|
var scriptAfter;
|
|
@@ -164,7 +188,9 @@ async function editActionType(params) {
|
|
|
164
188
|
warnings: warnings,
|
|
165
189
|
scriptBefore: scriptBefore,
|
|
166
190
|
scriptAfter: scriptAfter,
|
|
167
|
-
outputsMerged: outputsMerged
|
|
191
|
+
outputsMerged: outputsMerged,
|
|
192
|
+
stepsBefore: stepsBefore,
|
|
193
|
+
stepsAfter: stepsAfter
|
|
168
194
|
};
|
|
169
195
|
}
|
|
170
196
|
// 6. Apply: optionally pin the update set, then POST /snapshot (compiles the
|
|
@@ -183,6 +209,27 @@ async function editActionType(params) {
|
|
|
183
209
|
snapshotSysId = snap;
|
|
184
210
|
}
|
|
185
211
|
}
|
|
212
|
+
// 7. Verify — a 201 says the snapshot compiled, not that the edit landed as
|
|
213
|
+
// intended. Read the steps back and compare against what we sent.
|
|
214
|
+
var verified;
|
|
215
|
+
if (stepOpsSupplied && touchedCids.length === 0) {
|
|
216
|
+
// Every op was a no-op (already present / nothing to patch) — there is nothing
|
|
217
|
+
// to read back, so don't spend a round-trip proving we changed nothing.
|
|
218
|
+
verified = { ok: true, notes: ["no step changed — nothing to verify"] };
|
|
219
|
+
}
|
|
220
|
+
else if (stepOpsSupplied && stepsAfter) {
|
|
221
|
+
var freshResp = unwrap(await client.now.get(actionTypePath(sysId, scopeSysId, "/step_instances")));
|
|
222
|
+
var freshSteps = freshResp && Array.isArray(freshResp.steps) ? freshResp.steps : [];
|
|
223
|
+
if (freshSteps.length === 0) {
|
|
224
|
+
verified = { ok: false, notes: ["read-back returned no steps — could not verify the publish"] };
|
|
225
|
+
}
|
|
226
|
+
else {
|
|
227
|
+
verified = (0, stepOps_1.verifySteps)(stepsAfter, (0, stepOps_1.summarizeSteps)(freshSteps), touchedCids);
|
|
228
|
+
}
|
|
229
|
+
if (!verified.ok) {
|
|
230
|
+
warnings.push("VERIFY FAILED — the snapshot compiled but the read-back does not match what was sent");
|
|
231
|
+
}
|
|
232
|
+
}
|
|
186
233
|
return {
|
|
187
234
|
status: "published",
|
|
188
235
|
changes: changes,
|
|
@@ -190,6 +237,9 @@ async function editActionType(params) {
|
|
|
190
237
|
scriptBefore: scriptBefore,
|
|
191
238
|
scriptAfter: scriptAfter,
|
|
192
239
|
outputsMerged: outputsMerged,
|
|
240
|
+
stepsBefore: stepsBefore,
|
|
241
|
+
stepsAfter: stepsAfter,
|
|
242
|
+
verified: verified,
|
|
193
243
|
httpStatus: 201,
|
|
194
244
|
snapshotSysId: snapshotSysId
|
|
195
245
|
};
|
|
@@ -18,6 +18,8 @@ export { publishActionType } from "./publishActionType";
|
|
|
18
18
|
export type { PublishActionTypeParams, PublishActionTypeResult } from "./publishActionType";
|
|
19
19
|
export { editActionType } from "./editActionType";
|
|
20
20
|
export type { EditActionTypeParams, EditActionTypeResult, EditActionTypeOps } from "./editActionType";
|
|
21
|
+
export { applyStepOps, verifySteps, summarizeSteps, formatStepPill, findStep, hasStepOps } from "./stepOps";
|
|
22
|
+
export type { StepOps, StepRecord, StepSummary, StepIoSummary, PatchStepScriptOp, AddStepOutputOp, AddStepInputOp, ApplyStepOpsResult, VerifyStepsResult } from "./stepOps";
|
|
21
23
|
export { readFlow } from "./readFlow";
|
|
22
24
|
export type { ReadFlowParams, ReadFlowResult, FlowStep, FlowVariable } from "./readFlow";
|
|
23
25
|
export { readActionType } from "./readActionType";
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* functions land in Phase 1.C/D.
|
|
7
7
|
*/
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
-
exports.WriteOrderError = exports.executeWritePlan = exports.topoSort = exports.SYSTEM_FIELDS_TO_STRIP = exports.assertSysId = exports.applyScope = exports.stripSystemFields = exports.generateSysId = exports.DEFAULT_RUN_FLOW_PATH = exports.testFlow = exports.editFlow = exports.buildPublishModel = exports.createFlow = exports.copyFlow = exports.publishFlow = exports.readActionType = exports.readFlow = exports.editActionType = exports.publishActionType = exports.triggerPublication = exports.cloneActionType = exports.cloneSubflow = exports.verifyArtifact = exports.listTemplates = void 0;
|
|
9
|
+
exports.WriteOrderError = exports.executeWritePlan = exports.topoSort = exports.SYSTEM_FIELDS_TO_STRIP = exports.assertSysId = exports.applyScope = exports.stripSystemFields = exports.generateSysId = exports.DEFAULT_RUN_FLOW_PATH = exports.testFlow = exports.editFlow = exports.buildPublishModel = exports.createFlow = exports.copyFlow = exports.publishFlow = exports.readActionType = exports.readFlow = exports.hasStepOps = exports.findStep = exports.formatStepPill = exports.summarizeSteps = exports.verifySteps = exports.applyStepOps = exports.editActionType = exports.publishActionType = exports.triggerPublication = exports.cloneActionType = exports.cloneSubflow = exports.verifyArtifact = exports.listTemplates = void 0;
|
|
10
10
|
var listTemplates_1 = require("./listTemplates");
|
|
11
11
|
Object.defineProperty(exports, "listTemplates", { enumerable: true, get: function () { return listTemplates_1.listTemplates; } });
|
|
12
12
|
var verifyArtifact_1 = require("./verifyArtifact");
|
|
@@ -21,6 +21,13 @@ var publishActionType_1 = require("./publishActionType");
|
|
|
21
21
|
Object.defineProperty(exports, "publishActionType", { enumerable: true, get: function () { return publishActionType_1.publishActionType; } });
|
|
22
22
|
var editActionType_1 = require("./editActionType");
|
|
23
23
|
Object.defineProperty(exports, "editActionType", { enumerable: true, get: function () { return editActionType_1.editActionType; } });
|
|
24
|
+
var stepOps_1 = require("./stepOps");
|
|
25
|
+
Object.defineProperty(exports, "applyStepOps", { enumerable: true, get: function () { return stepOps_1.applyStepOps; } });
|
|
26
|
+
Object.defineProperty(exports, "verifySteps", { enumerable: true, get: function () { return stepOps_1.verifySteps; } });
|
|
27
|
+
Object.defineProperty(exports, "summarizeSteps", { enumerable: true, get: function () { return stepOps_1.summarizeSteps; } });
|
|
28
|
+
Object.defineProperty(exports, "formatStepPill", { enumerable: true, get: function () { return stepOps_1.formatStepPill; } });
|
|
29
|
+
Object.defineProperty(exports, "findStep", { enumerable: true, get: function () { return stepOps_1.findStep; } });
|
|
30
|
+
Object.defineProperty(exports, "hasStepOps", { enumerable: true, get: function () { return stepOps_1.hasStepOps; } });
|
|
24
31
|
var readFlow_1 = require("./readFlow");
|
|
25
32
|
Object.defineProperty(exports, "readFlow", { enumerable: true, get: function () { return readFlow_1.readFlow; } });
|
|
26
33
|
var readActionType_1 = require("./readActionType");
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Step-graph operations for a Custom Action Type — the pure half of `editActionType`.
|
|
3
|
+
*
|
|
4
|
+
* Everything here operates on the `steps` array returned by
|
|
5
|
+
* `GET /api/now/processflow/action/action_types/{id}/step_instances`. No client,
|
|
6
|
+
* no network: the step graph goes in, a patched copy comes out, and the caller
|
|
7
|
+
* grafts it onto the model and POSTs `/snapshot`.
|
|
8
|
+
*
|
|
9
|
+
* Two facts about that payload were established against a live instance and are
|
|
10
|
+
* enforced here, because getting either wrong corrupts the action type in a way
|
|
11
|
+
* the publish POST happily accepts:
|
|
12
|
+
*
|
|
13
|
+
* 1. STEP-TO-STEP DATA PILL FORMAT — `{{step[<source_cid>].<output_name>}}`.
|
|
14
|
+
* NOT `{{<cid>.<name>}}`, which is what the flow-variable pill syntax suggests.
|
|
15
|
+
* A wrong pill does not fail the publish; it compiles a snapshot with a dead
|
|
16
|
+
* reference, and the action silently reads `undefined` at runtime.
|
|
17
|
+
*
|
|
18
|
+
* 2. NEW IO ENTRIES MUST MIRROR AN EXISTING SIBLING — an `extended_inputs` /
|
|
19
|
+
* `extended_outputs` entry carries more keys than the four you care about, and
|
|
20
|
+
* some are wrapped as `{value: x}` while others are bare, inconsistently by key.
|
|
21
|
+
* So a new entry is a deep copy of an existing entry from the SAME list with
|
|
22
|
+
* only our keys overwritten, preserving each key's wrapped-vs-bare shape.
|
|
23
|
+
* When the list is empty there is no shape to mirror and we refuse rather than
|
|
24
|
+
* guess — a clear error beats a corrupted action type.
|
|
25
|
+
*
|
|
26
|
+
* Full write-up: docs/servicenow-flow-designer-headless-authoring.md.
|
|
27
|
+
*/
|
|
28
|
+
/** A step's field value: either bare, or wrapped by the Designer as `{ value: x }`. */
|
|
29
|
+
export type StepScalar = string | number | boolean | null;
|
|
30
|
+
/** One entry in a step's `inputs` / `extended_inputs` / `extended_outputs` list. */
|
|
31
|
+
export interface IoEntry {
|
|
32
|
+
[key: string]: unknown;
|
|
33
|
+
}
|
|
34
|
+
/** One step from `/step_instances`. */
|
|
35
|
+
export interface StepRecord {
|
|
36
|
+
[key: string]: unknown;
|
|
37
|
+
}
|
|
38
|
+
export interface PatchStepScriptOp {
|
|
39
|
+
/** Step to patch — its `cid`, or its `label`/`name`. */
|
|
40
|
+
step: string;
|
|
41
|
+
/** Replace the script outright (wins over patchScript). */
|
|
42
|
+
setScript?: string;
|
|
43
|
+
/** Replace every occurrence of `find` with `replace` inside the script. */
|
|
44
|
+
patchScript?: {
|
|
45
|
+
find: string;
|
|
46
|
+
replace: string;
|
|
47
|
+
};
|
|
48
|
+
/** Input name holding the script. Default: auto-detect (`script`, else by signature). */
|
|
49
|
+
scriptInputName?: string;
|
|
50
|
+
}
|
|
51
|
+
export interface AddStepOutputOp {
|
|
52
|
+
/** Step to add the output to — its `cid`, or its `label`/`name`. */
|
|
53
|
+
step: string;
|
|
54
|
+
name: string;
|
|
55
|
+
label?: string;
|
|
56
|
+
/** ServiceNow variable type. Default: "boolean". */
|
|
57
|
+
type?: string;
|
|
58
|
+
}
|
|
59
|
+
export interface AddStepInputOp {
|
|
60
|
+
/** Step to add the input to — its `cid`, or its `label`/`name`. */
|
|
61
|
+
step: string;
|
|
62
|
+
name: string;
|
|
63
|
+
label?: string;
|
|
64
|
+
/** ServiceNow variable type. Default: "boolean". */
|
|
65
|
+
type?: string;
|
|
66
|
+
/** Wire the input's value to another step's output via a data pill. */
|
|
67
|
+
pillFrom: {
|
|
68
|
+
step: string;
|
|
69
|
+
output: string;
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
export interface StepOps {
|
|
73
|
+
patchStepScripts?: Array<PatchStepScriptOp>;
|
|
74
|
+
addStepOutputs?: Array<AddStepOutputOp>;
|
|
75
|
+
addStepInputs?: Array<AddStepInputOp>;
|
|
76
|
+
}
|
|
77
|
+
export interface StepIoSummary {
|
|
78
|
+
name: string;
|
|
79
|
+
type: string;
|
|
80
|
+
value: string;
|
|
81
|
+
}
|
|
82
|
+
export interface StepSummary {
|
|
83
|
+
label: string;
|
|
84
|
+
cid: string;
|
|
85
|
+
/** For display. NOT sufficient to verify a script — two different scripts can share a length. */
|
|
86
|
+
scriptChars: number | null;
|
|
87
|
+
/** Content hash of the script — what the verify pass actually compares. */
|
|
88
|
+
scriptHash: string | null;
|
|
89
|
+
extendedInputs: Array<StepIoSummary>;
|
|
90
|
+
extendedOutputs: Array<StepIoSummary>;
|
|
91
|
+
}
|
|
92
|
+
export interface ApplyStepOpsResult {
|
|
93
|
+
/** A patched deep copy — the input graph is never mutated. */
|
|
94
|
+
steps: Array<StepRecord>;
|
|
95
|
+
changes: Array<string>;
|
|
96
|
+
warnings: Array<string>;
|
|
97
|
+
/** cids of the steps an op actually changed — what the verify pass re-reads. */
|
|
98
|
+
touchedCids: Array<string>;
|
|
99
|
+
}
|
|
100
|
+
export interface VerifyStepsResult {
|
|
101
|
+
ok: boolean;
|
|
102
|
+
notes: Array<string>;
|
|
103
|
+
}
|
|
104
|
+
/** Unwrap the Designer's `{ value: x }` envelope; pass anything else through. */
|
|
105
|
+
export declare function unwrapValue(v: unknown): unknown;
|
|
106
|
+
/** Coerce an unwrapped field to a string ("" for null/undefined/objects). */
|
|
107
|
+
export declare function readString(v: unknown): string;
|
|
108
|
+
/** A step's addressable identity: its `cid` and its human label. */
|
|
109
|
+
export declare function stepIdentity(step: StepRecord): {
|
|
110
|
+
cid: string;
|
|
111
|
+
label: string;
|
|
112
|
+
};
|
|
113
|
+
/** Find a step by `cid` or by `label`/`name`. Throws listing what IS there. */
|
|
114
|
+
export declare function findStep(steps: Array<StepRecord>, ref: string): StepRecord;
|
|
115
|
+
/** Locate the input holding a step's script — by name, else by script signature. */
|
|
116
|
+
export declare function findScriptInput(step: StepRecord, inputName?: string): IoEntry | null;
|
|
117
|
+
/** Format a step-to-step data pill. THE format — see the header note. */
|
|
118
|
+
export declare function formatStepPill(sourceCid: string, outputName: string): string;
|
|
119
|
+
/**
|
|
120
|
+
* FNV-1a over the script text. The verify pass compares content, not length —
|
|
121
|
+
* two different scripts of the same length must not pass as equal.
|
|
122
|
+
*/
|
|
123
|
+
export declare function hashScript(script: string): string;
|
|
124
|
+
/** A compact, loggable view of a step's scripts and step-level IO. */
|
|
125
|
+
export declare function summarizeSteps(steps: Array<StepRecord>): Array<StepSummary>;
|
|
126
|
+
/**
|
|
127
|
+
* Apply per-step ops to a DEEP COPY of the step graph.
|
|
128
|
+
*
|
|
129
|
+
* Outputs are added before inputs, so an input may pill from an output created in
|
|
130
|
+
* the same call — everything lands in one `/snapshot` POST.
|
|
131
|
+
*/
|
|
132
|
+
export declare function applyStepOps(steps: Array<StepRecord>, ops: StepOps): ApplyStepOpsResult;
|
|
133
|
+
/**
|
|
134
|
+
* Compare what we published against what the instance now reports, for the steps
|
|
135
|
+
* we touched. A 201 from `/snapshot` means "snapshot compiled", NOT "your edit
|
|
136
|
+
* landed the way you meant it" — so read the steps back and prove it.
|
|
137
|
+
*/
|
|
138
|
+
export declare function verifySteps(expected: Array<StepSummary>, actual: Array<StepSummary>, touchedCids: Array<string>): VerifyStepsResult;
|
|
139
|
+
/** True when `ops` carries at least one per-step op. */
|
|
140
|
+
export declare function hasStepOps(ops: StepOps): boolean;
|