@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 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), `flow_publish` (compile a flow/subflow snapshot), `flow_copy`
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
- * --patch-script "<find>::<replace>" Optional. Find/replace in the script step value.
670
- * --set-script <path> Optional. Replace the script step value from a file.
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's script and/or output variables and
678
- * republishes through the snapshot POST. Dry-run (read-only) by default; --apply writes.
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
- if (!ops.patchScript && !ops.setScript && !(ops.mergeOutputs && ops.mergeOutputs.length > 0)) {
74
- throw new Error("editActionType: no ops — supply patchScript, setScript, and/or mergeOutputs.");
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;