@tenonhq/dovetail-servicenow 0.0.27 → 0.0.29
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/choices.d.ts +1 -0
- package/dist/choices.js +34 -16
- package/dist/cli.js +226 -5
- 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 +9 -9
- package/dist/index.js +9 -2
- package/dist/mcp/registry.d.ts +1 -1
- package/dist/mcp/registry.js +69 -0
- package/dist/mcp/schemas.d.ts +341 -10
- package/dist/mcp/schemas.js +119 -29
- package/dist/table/index.d.ts +7 -5
- package/dist/table/index.js +6 -1
- package/dist/table/setColumn.d.ts +153 -0
- package/dist/table/setColumn.js +639 -0
- 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/choices.d.ts
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import type { ServiceNowClient } from "./client";
|
|
11
11
|
import type { AddChoicesParams, AddChoicesResult } from "./types";
|
|
12
|
+
export declare function encodeQueryValue(v: string): string;
|
|
12
13
|
/**
|
|
13
14
|
* Upsert choices for a field and (optionally) toggle sys_dictionary.choice.
|
|
14
15
|
* Idempotent: re-running with the same inputs returns `action: "unchanged"`
|
package/dist/choices.js
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* so choices stay in the same application as the field.
|
|
10
10
|
*/
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.encodeQueryValue = encodeQueryValue;
|
|
12
13
|
exports.addChoicesToField = addChoicesToField;
|
|
13
14
|
function encodeQueryValue(v) {
|
|
14
15
|
// ServiceNow encoded-query values: commas/carets/equals are special. We
|
|
@@ -39,7 +40,10 @@ async function resolveScopeName(client, scopeSysId) {
|
|
|
39
40
|
async function fetchDictionary(client, table, column) {
|
|
40
41
|
var rows = await client.table.query("sys_dictionary", "name=" + encodeQueryValue(table) + "^element=" + encodeQueryValue(column), 1);
|
|
41
42
|
if (rows.length === 0) {
|
|
42
|
-
throw new Error("sys_dictionary record not found for " +
|
|
43
|
+
throw new Error("sys_dictionary record not found for " +
|
|
44
|
+
table +
|
|
45
|
+
"." +
|
|
46
|
+
column +
|
|
43
47
|
" — verify the field exists and your user has read access.");
|
|
44
48
|
}
|
|
45
49
|
var row = rows[0];
|
|
@@ -53,7 +57,7 @@ async function fetchDictionary(client, table, column) {
|
|
|
53
57
|
name: row.name,
|
|
54
58
|
element: row.element,
|
|
55
59
|
choice: String(row.choice || "0"),
|
|
56
|
-
sys_scope: scope || ""
|
|
60
|
+
sys_scope: scope || "",
|
|
57
61
|
};
|
|
58
62
|
}
|
|
59
63
|
async function fetchUpdateSet(client, sysId) {
|
|
@@ -63,14 +67,16 @@ async function fetchUpdateSet(client, sysId) {
|
|
|
63
67
|
}
|
|
64
68
|
var row = rows[0];
|
|
65
69
|
if (row.state && row.state !== "in progress" && row.state !== "in_progress") {
|
|
66
|
-
throw new Error("Update set " +
|
|
70
|
+
throw new Error("Update set " +
|
|
71
|
+
row.name +
|
|
72
|
+
" is in state '" +
|
|
73
|
+
row.state +
|
|
67
74
|
"' — only 'in progress' update sets can capture new changes.");
|
|
68
75
|
}
|
|
69
76
|
return row;
|
|
70
77
|
}
|
|
71
78
|
async function fetchExistingChoices(client, table, column) {
|
|
72
|
-
return client.table.query("sys_choice", "name=" + encodeQueryValue(table) +
|
|
73
|
-
"^element=" + encodeQueryValue(column), 1000);
|
|
79
|
+
return client.table.query("sys_choice", "name=" + encodeQueryValue(table) + "^element=" + encodeQueryValue(column), 1000);
|
|
74
80
|
}
|
|
75
81
|
function buildChoiceFields(table, column, choice, scope) {
|
|
76
82
|
var fields = {
|
|
@@ -79,7 +85,7 @@ function buildChoiceFields(table, column, choice, scope) {
|
|
|
79
85
|
value: choice.value,
|
|
80
86
|
label: choice.label,
|
|
81
87
|
language: choice.language || "en",
|
|
82
|
-
inactive: "false"
|
|
88
|
+
inactive: "false",
|
|
83
89
|
};
|
|
84
90
|
if (choice.sequence != null) {
|
|
85
91
|
fields.sequence = String(choice.sequence);
|
|
@@ -114,7 +120,9 @@ async function addChoicesToField(client, params) {
|
|
|
114
120
|
var scopeName = await resolveScopeName(client, dict.sys_scope);
|
|
115
121
|
var targetChoiceType = params.choiceType === null
|
|
116
122
|
? Number(dict.choice)
|
|
117
|
-
:
|
|
123
|
+
: params.choiceType != null
|
|
124
|
+
? params.choiceType
|
|
125
|
+
: 3;
|
|
118
126
|
var choiceWas = Number(dict.choice);
|
|
119
127
|
var choiceNow = choiceWas;
|
|
120
128
|
if (params.choiceType !== null && Number(dict.choice) !== targetChoiceType) {
|
|
@@ -122,7 +130,7 @@ async function addChoicesToField(client, params) {
|
|
|
122
130
|
update_set_sys_id: params.updateSetSysId,
|
|
123
131
|
table: "sys_dictionary",
|
|
124
132
|
record_sys_id: dict.sys_id,
|
|
125
|
-
fields: { choice: String(targetChoiceType) }
|
|
133
|
+
fields: { choice: String(targetChoiceType) },
|
|
126
134
|
});
|
|
127
135
|
choiceNow = targetChoiceType;
|
|
128
136
|
}
|
|
@@ -138,14 +146,19 @@ async function addChoicesToField(client, params) {
|
|
|
138
146
|
var key = (choice.language || "en") + "::" + choice.value;
|
|
139
147
|
var match = existingByValue[key];
|
|
140
148
|
if (match && isUnchanged(match, choice)) {
|
|
141
|
-
results.push({
|
|
149
|
+
results.push({
|
|
150
|
+
value: choice.value,
|
|
151
|
+
label: choice.label,
|
|
152
|
+
sysId: match.sys_id,
|
|
153
|
+
action: "unchanged",
|
|
154
|
+
});
|
|
142
155
|
continue;
|
|
143
156
|
}
|
|
144
157
|
if (match) {
|
|
145
158
|
var updFields = {
|
|
146
159
|
label: choice.label,
|
|
147
160
|
language: choice.language || "en",
|
|
148
|
-
inactive: "false"
|
|
161
|
+
inactive: "false",
|
|
149
162
|
};
|
|
150
163
|
if (choice.sequence != null) {
|
|
151
164
|
updFields.sequence = String(choice.sequence);
|
|
@@ -154,22 +167,27 @@ async function addChoicesToField(client, params) {
|
|
|
154
167
|
update_set_sys_id: params.updateSetSysId,
|
|
155
168
|
table: "sys_choice",
|
|
156
169
|
record_sys_id: match.sys_id,
|
|
157
|
-
fields: updFields
|
|
170
|
+
fields: updFields,
|
|
171
|
+
});
|
|
172
|
+
results.push({
|
|
173
|
+
value: choice.value,
|
|
174
|
+
label: choice.label,
|
|
175
|
+
sysId: match.sys_id,
|
|
176
|
+
action: "updated",
|
|
158
177
|
});
|
|
159
|
-
results.push({ value: choice.value, label: choice.label, sysId: match.sys_id, action: "updated" });
|
|
160
178
|
continue;
|
|
161
179
|
}
|
|
162
180
|
var created = await client.claude.createRecord({
|
|
163
181
|
table: "sys_choice",
|
|
164
182
|
fields: buildChoiceFields(params.table, params.column, choice, dict.sys_scope),
|
|
165
183
|
scope: scopeName,
|
|
166
|
-
update_set_sys_id: params.updateSetSysId
|
|
184
|
+
update_set_sys_id: params.updateSetSysId,
|
|
167
185
|
});
|
|
168
186
|
results.push({
|
|
169
187
|
value: choice.value,
|
|
170
188
|
label: choice.label,
|
|
171
189
|
sysId: created.sys_id,
|
|
172
|
-
action: "created"
|
|
190
|
+
action: "created",
|
|
173
191
|
});
|
|
174
192
|
}
|
|
175
193
|
return {
|
|
@@ -177,9 +195,9 @@ async function addChoicesToField(client, params) {
|
|
|
177
195
|
sysId: dict.sys_id,
|
|
178
196
|
scope: dict.sys_scope,
|
|
179
197
|
choiceWas: choiceWas,
|
|
180
|
-
choiceNow: choiceNow
|
|
198
|
+
choiceNow: choiceNow,
|
|
181
199
|
},
|
|
182
200
|
updateSet: { sysId: updateSet.sys_id, name: updateSet.name },
|
|
183
|
-
choices: results
|
|
201
|
+
choices: results,
|
|
184
202
|
};
|
|
185
203
|
}
|
package/dist/cli.js
CHANGED
|
@@ -87,24 +87,30 @@ const flowDesigner_formatter_2 = require("./flowDesigner-formatter");
|
|
|
87
87
|
function parseArgs(argv) {
|
|
88
88
|
var command = argv[0] || "";
|
|
89
89
|
var flags = {};
|
|
90
|
+
var bare = {};
|
|
90
91
|
for (var i = 1; i < argv.length; i += 1) {
|
|
91
92
|
var arg = argv[i];
|
|
92
93
|
if (arg.indexOf("--") !== 0)
|
|
93
94
|
continue;
|
|
94
95
|
var key = arg.slice(2);
|
|
95
96
|
var value = "true";
|
|
97
|
+
var isBare = true;
|
|
96
98
|
var eq = key.indexOf("=");
|
|
97
99
|
if (eq !== -1) {
|
|
98
100
|
value = key.slice(eq + 1);
|
|
99
101
|
key = key.slice(0, eq);
|
|
102
|
+
isBare = false;
|
|
100
103
|
}
|
|
101
104
|
else if (i + 1 < argv.length && argv[i + 1].indexOf("--") !== 0) {
|
|
102
105
|
value = argv[i + 1];
|
|
103
106
|
i += 1;
|
|
107
|
+
isBare = false;
|
|
104
108
|
}
|
|
105
109
|
flags[key] = value;
|
|
110
|
+
if (isBare)
|
|
111
|
+
bare[key] = true;
|
|
106
112
|
}
|
|
107
|
-
return { command: command, flags: flags };
|
|
113
|
+
return { command: command, flags: flags, bare: bare };
|
|
108
114
|
}
|
|
109
115
|
function parseChoicesInline(input) {
|
|
110
116
|
return input.split(",").map(function (pair) {
|
|
@@ -666,17 +672,54 @@ async function runEditFlow(flags) {
|
|
|
666
672
|
* dove-sn edit-action:
|
|
667
673
|
* --sys-id <sys_id> Required. sys_hub_action_type_definition sys_id.
|
|
668
674
|
* --scope <sys_id> Required. sysparm_transaction_scope (app scope sys_id).
|
|
669
|
-
* --
|
|
670
|
-
*
|
|
675
|
+
* --from-json <path> Optional. JSON EditActionTypeOps — the full surface, incl.
|
|
676
|
+
* per-step ops: patchStepScripts / addStepOutputs / addStepInputs.
|
|
677
|
+
* --patch-script "<find>::<replace>" Optional. Find/replace in the auto-detected script step value.
|
|
678
|
+
* --set-script <path> Optional. Replace the auto-detected script step value from a file.
|
|
671
679
|
* --merge-outputs <path> Optional. JSON file: an output-variable object/array to merge by name.
|
|
672
680
|
* --script-input <name> Optional. Input name holding the script (default: auto-detect).
|
|
673
681
|
* --update-set <sys_id> Optional. Capture the republish into this update set.
|
|
674
682
|
* --apply Optional. Republish (POST /snapshot). Omit for dry-run.
|
|
675
683
|
* --json Optional. Emit the structured EditActionTypeResult.
|
|
676
684
|
*
|
|
677
|
-
* Edits a published Custom Action Type
|
|
678
|
-
*
|
|
685
|
+
* Edits a published Custom Action Type and republishes through the snapshot POST.
|
|
686
|
+
* Dry-run (read-only) by default; --apply writes.
|
|
687
|
+
*
|
|
688
|
+
* The flag form handles the single-script case. For anything structural — patching
|
|
689
|
+
* several steps' scripts, adding a step-level output, adding a step-level input
|
|
690
|
+
* pill-wired to another step's output — use --from-json:
|
|
691
|
+
*
|
|
692
|
+
* {
|
|
693
|
+
* "patchStepScripts": [{ "step": "Parse Response", "scriptFile": "./parse.js" }],
|
|
694
|
+
* "addStepOutputs": [{ "step": "Parse Response", "name": "isRetryable", "type": "boolean" }],
|
|
695
|
+
* "addStepInputs": [{ "step": "Handle Error", "name": "isRetryable", "type": "boolean",
|
|
696
|
+
* "pillFrom": { "step": "Parse Response", "output": "isRetryable" } }]
|
|
697
|
+
* }
|
|
698
|
+
*
|
|
699
|
+
* `step` is a step cid or label. `scriptFile` is sugar for `setScript` and is
|
|
700
|
+
* resolved RELATIVE TO THE OPS FILE, so an ops file can sit next to its scripts.
|
|
679
701
|
*/
|
|
702
|
+
/** Resolve `scriptFile` sugar in patchStepScripts, relative to the ops file's own dir. */
|
|
703
|
+
function resolveScriptFiles(ops, opsPath) {
|
|
704
|
+
var stepScripts = ops.patchStepScripts;
|
|
705
|
+
if (!Array.isArray(stepScripts)) {
|
|
706
|
+
return;
|
|
707
|
+
}
|
|
708
|
+
var opsDir = path.dirname(path.resolve(opsPath));
|
|
709
|
+
for (var i = 0; i < stepScripts.length; i += 1) {
|
|
710
|
+
var op = stepScripts[i];
|
|
711
|
+
if (!op || typeof op !== "object" || typeof op.scriptFile !== "string") {
|
|
712
|
+
continue;
|
|
713
|
+
}
|
|
714
|
+
if (typeof op.setScript === "string") {
|
|
715
|
+
throw new Error("edit-action: step '" +
|
|
716
|
+
String(op.step) +
|
|
717
|
+
"' sets both scriptFile and setScript — pick one.");
|
|
718
|
+
}
|
|
719
|
+
op.setScript = fs.readFileSync(path.resolve(opsDir, op.scriptFile), "utf8");
|
|
720
|
+
delete op.scriptFile;
|
|
721
|
+
}
|
|
722
|
+
}
|
|
680
723
|
async function runEditAction(flags) {
|
|
681
724
|
var sysId = flags["sys-id"] || flags.sysId;
|
|
682
725
|
var scope = flags.scope || flags.scopeSysId;
|
|
@@ -685,6 +728,14 @@ async function runEditAction(flags) {
|
|
|
685
728
|
return 1;
|
|
686
729
|
}
|
|
687
730
|
var ops = {};
|
|
731
|
+
if (flags["from-json"]) {
|
|
732
|
+
ops = JSON.parse(fs.readFileSync(flags["from-json"], "utf8"));
|
|
733
|
+
if (!ops || typeof ops !== "object" || Array.isArray(ops)) {
|
|
734
|
+
process.stderr.write("edit-action: --from-json must contain an EditActionTypeOps object\n");
|
|
735
|
+
return 1;
|
|
736
|
+
}
|
|
737
|
+
resolveScriptFiles(ops, flags["from-json"]);
|
|
738
|
+
}
|
|
688
739
|
if (flags["patch-script"]) {
|
|
689
740
|
var parts = String(flags["patch-script"]).split("::");
|
|
690
741
|
if (parts.length !== 2) {
|
|
@@ -730,6 +781,51 @@ async function runEditAction(flags) {
|
|
|
730
781
|
for (var wi = 0; wi < result.warnings.length; wi += 1) {
|
|
731
782
|
process.stdout.write(" ! " + result.warnings[wi] + "\n");
|
|
732
783
|
}
|
|
784
|
+
// Per-step before/after — the dry-run's whole job is to make this inspectable.
|
|
785
|
+
if (result.stepsBefore && result.stepsAfter) {
|
|
786
|
+
process.stdout.write("\n--- steps (before -> after) ---\n");
|
|
787
|
+
for (var si = 0; si < result.stepsAfter.length; si += 1) {
|
|
788
|
+
var after = result.stepsAfter[si];
|
|
789
|
+
var before = result.stepsBefore[si];
|
|
790
|
+
var io = function (label, list) {
|
|
791
|
+
if (list.length === 0) {
|
|
792
|
+
return "";
|
|
793
|
+
}
|
|
794
|
+
var rendered = list
|
|
795
|
+
.map(function (e) {
|
|
796
|
+
return e.name + (e.value ? "=" + e.value : "");
|
|
797
|
+
})
|
|
798
|
+
.join(", ");
|
|
799
|
+
return "\n " + label + ": " + rendered;
|
|
800
|
+
};
|
|
801
|
+
process.stdout.write(" " +
|
|
802
|
+
after.label +
|
|
803
|
+
" (" +
|
|
804
|
+
after.cid +
|
|
805
|
+
")\n" +
|
|
806
|
+
" script: " +
|
|
807
|
+
String(before ? before.scriptChars : "?") +
|
|
808
|
+
" -> " +
|
|
809
|
+
String(after.scriptChars) +
|
|
810
|
+
" chars" +
|
|
811
|
+
io("in ", after.extendedInputs) +
|
|
812
|
+
io("out", after.extendedOutputs) +
|
|
813
|
+
"\n");
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
if (result.verified) {
|
|
817
|
+
process.stdout.write("\n--- verify (read back from the instance) ---\n");
|
|
818
|
+
process.stdout.write(" " + (result.verified.ok ? "OK" : "FAILED") + "\n");
|
|
819
|
+
for (var vi = 0; vi < result.verified.notes.length; vi += 1) {
|
|
820
|
+
process.stdout.write(" " +
|
|
821
|
+
(result.verified.ok ? "+ " : "! ") +
|
|
822
|
+
result.verified.notes[vi] +
|
|
823
|
+
"\n");
|
|
824
|
+
}
|
|
825
|
+
if (!result.verified.ok) {
|
|
826
|
+
return 2;
|
|
827
|
+
}
|
|
828
|
+
}
|
|
733
829
|
if (result.status === "preview" &&
|
|
734
830
|
result.scriptAfter !== undefined &&
|
|
735
831
|
result.scriptAfter !== result.scriptBefore) {
|
|
@@ -786,6 +882,17 @@ function printHelp() {
|
|
|
786
882
|
" [--name <element>] [--max-length <n>] [--reference <table>]\n" +
|
|
787
883
|
" [--mandatory] [--default <v>] [--scope <s>] [--dry-run] [--json])\n" +
|
|
788
884
|
" --update-set is REQUIRED on the live path (not for --dry-run).\n" +
|
|
885
|
+
" set-column Update an EXISTING column's SCHEMA (label/mandatory/default/read-only/max-length),\n" +
|
|
886
|
+
" into an update set, then verify against the instance\n" +
|
|
887
|
+
" (--table <t> --column <c> --update-set <sys_id>\n" +
|
|
888
|
+
" [--label <l>] [--mandatory true|false] [--default <v>]\n" +
|
|
889
|
+
" [--read-only true|false] [--max-length <n>]\n" +
|
|
890
|
+
" [--dry-run] [--json])\n" +
|
|
891
|
+
" A max-length SHRINK is REFUSED while rows hold longer values —\n" +
|
|
892
|
+
" ServiceNow silently ignores such a shrink (200 OK, no change).\n" +
|
|
893
|
+
" Shorten or clear those values first, then re-run.\n" +
|
|
894
|
+
" --element / --internal-type are REFUSED with an explanation:\n" +
|
|
895
|
+
" ServiceNow silently ignores both on an existing column.\n" +
|
|
789
896
|
" invoke-rest Invoke an arbitrary authenticated REST operation (Scripted REST incl.)\n" +
|
|
790
897
|
" DRY-RUN BY DEFAULT — nothing is sent without --confirm\n" +
|
|
791
898
|
" (--method <GET|POST|PUT|DELETE> --path /api/<scope>/<service>/<resource>\n" +
|
|
@@ -801,6 +908,12 @@ function printHelp() {
|
|
|
801
908
|
" [--update-set <sys_id>] [--max-bytes <n>] [--allow-oversize] [--dry-run] [--json])\n" +
|
|
802
909
|
" test-flow Validate (default) or run a flow/subflow\n" +
|
|
803
910
|
" (--sys-id <sys_id> [--execute --confirm] [--inputs <json>] [--json])\n" +
|
|
911
|
+
" edit-action Patch a published Custom Action Type and republish (snapshot)\n" +
|
|
912
|
+
" (--sys-id <sys_id> --scope <sys_id>\n" +
|
|
913
|
+
" --from-json <ops.json> ops: patchStepScripts / addStepOutputs / addStepInputs\n" +
|
|
914
|
+
" (per-step scripts + step IO + data-pill wiring)\n" +
|
|
915
|
+
' | --patch-script "<find>::<replace>" | --set-script <path> | --merge-outputs <path>\n' +
|
|
916
|
+
" [--script-input <name>] [--update-set <sys_id>] [--apply] [--json])\n" +
|
|
804
917
|
" edit-flow Patch a flow/subflow (rename, description, step inputs)\n" +
|
|
805
918
|
" (--sys-id <sys_id> --from-json <ops.json> [--apply] [--update-set <sys_id>] [--scope <sys_id>] [--json])\n" +
|
|
806
919
|
" mcp Run the MCP stdio server (--smoke lists tools and exits)\n" +
|
|
@@ -991,6 +1104,111 @@ async function runAddColumn(flags) {
|
|
|
991
1104
|
return 2;
|
|
992
1105
|
return 0;
|
|
993
1106
|
}
|
|
1107
|
+
/** Parse a CLI boolean flag. Bare `--mandatory` means true; `--mandatory false` means
|
|
1108
|
+
* false. Anything else is rejected rather than quietly coerced to `true`. */
|
|
1109
|
+
function parseBoolFlag(name, raw) {
|
|
1110
|
+
if (raw === "true")
|
|
1111
|
+
return true;
|
|
1112
|
+
if (raw === "false")
|
|
1113
|
+
return false;
|
|
1114
|
+
throw new Error("set-column: --" + name + " must be true or false (got '" + raw + "').");
|
|
1115
|
+
}
|
|
1116
|
+
/**
|
|
1117
|
+
* dove-sn set-column:
|
|
1118
|
+
* --table x_cadso_journey --column description --update-set <sys_id>
|
|
1119
|
+
* [--label "Description"] [--mandatory true|false] [--default <v>]
|
|
1120
|
+
* [--read-only true|false] [--max-length 4000] [--dry-run] [--json]
|
|
1121
|
+
*
|
|
1122
|
+
* Updates an EXISTING column's schema. `internal_type` and a rename are refused —
|
|
1123
|
+
* ServiceNow silently ignores both on an existing column. To CREATE one, use add-column;
|
|
1124
|
+
* to set a RECORD's value, use set-field.
|
|
1125
|
+
*/
|
|
1126
|
+
async function runSetColumn(flags, bare) {
|
|
1127
|
+
// A string flag whose value was forgotten arrives as the literal "true" — `--label`
|
|
1128
|
+
// with nothing after it would rename the column to "true". Booleans legitimately do
|
|
1129
|
+
// that, strings never do, so refuse rather than silently write nonsense.
|
|
1130
|
+
var stringFlags = ["label", "default", "table", "column", "update-set"];
|
|
1131
|
+
for (var f = 0; f < stringFlags.length; f += 1) {
|
|
1132
|
+
if (bare[stringFlags[f]]) {
|
|
1133
|
+
process.stderr.write("set-column: --" +
|
|
1134
|
+
stringFlags[f] +
|
|
1135
|
+
" needs a value (it was given none).\n");
|
|
1136
|
+
return 1;
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
var table = flags.table;
|
|
1140
|
+
var column = flags.column;
|
|
1141
|
+
if (!table || !column) {
|
|
1142
|
+
process.stderr.write("set-column: --table and --column are required " +
|
|
1143
|
+
"(--update-set is required too, unless --dry-run)\n");
|
|
1144
|
+
return 1;
|
|
1145
|
+
}
|
|
1146
|
+
var attributes = {};
|
|
1147
|
+
// Accepted so that setColumn can REFUSE them by name with the reason. Dropping them
|
|
1148
|
+
// silently would leave someone who asked for a rename believing it happened.
|
|
1149
|
+
if (flags.element !== undefined)
|
|
1150
|
+
attributes.element = flags.element;
|
|
1151
|
+
if (flags["internal-type"] !== undefined) {
|
|
1152
|
+
attributes.internalType = flags["internal-type"];
|
|
1153
|
+
}
|
|
1154
|
+
if (flags.label !== undefined)
|
|
1155
|
+
attributes.label = flags.label;
|
|
1156
|
+
if (flags.default !== undefined)
|
|
1157
|
+
attributes.default = flags.default;
|
|
1158
|
+
if (flags.mandatory !== undefined) {
|
|
1159
|
+
attributes.mandatory = parseBoolFlag("mandatory", flags.mandatory);
|
|
1160
|
+
}
|
|
1161
|
+
if (flags["read-only"] !== undefined) {
|
|
1162
|
+
attributes.readOnly = parseBoolFlag("read-only", flags["read-only"]);
|
|
1163
|
+
}
|
|
1164
|
+
if (flags["max-length"] !== undefined) {
|
|
1165
|
+
var len = Number(flags["max-length"]);
|
|
1166
|
+
// sys_dictionary.max_length is an integer; the MCP schema enforces int() too.
|
|
1167
|
+
if (!Number.isInteger(len) || len < 1) {
|
|
1168
|
+
process.stderr.write("set-column: --max-length must be a positive integer\n");
|
|
1169
|
+
return 1;
|
|
1170
|
+
}
|
|
1171
|
+
attributes.maxLength = len;
|
|
1172
|
+
}
|
|
1173
|
+
var params = {
|
|
1174
|
+
client: (0, client_1.createClient)({}),
|
|
1175
|
+
table: table,
|
|
1176
|
+
column: column,
|
|
1177
|
+
attributes: attributes,
|
|
1178
|
+
};
|
|
1179
|
+
// Accept the same alias pair as the other verbs (create-view, set-list-layout, …).
|
|
1180
|
+
var setColumnUs = flags["update-set"] || flags.updateSetSysId;
|
|
1181
|
+
if (setColumnUs)
|
|
1182
|
+
params.updateSetSysId = setColumnUs;
|
|
1183
|
+
if (flags["dry-run"] === "true")
|
|
1184
|
+
params.dryRun = true;
|
|
1185
|
+
var result = await (0, table_1.setColumn)(params);
|
|
1186
|
+
if (flags.json === "true") {
|
|
1187
|
+
process.stdout.write(JSON.stringify(result, null, 2) + "\n");
|
|
1188
|
+
}
|
|
1189
|
+
else {
|
|
1190
|
+
process.stdout.write("[" +
|
|
1191
|
+
result.status +
|
|
1192
|
+
"] " +
|
|
1193
|
+
result.table +
|
|
1194
|
+
"." +
|
|
1195
|
+
result.column +
|
|
1196
|
+
(result.verified && result.status === "applied" ? " — verified" : "") +
|
|
1197
|
+
(result.status === "applied" && !result.capturedInUpdateSet
|
|
1198
|
+
? " — NOT CAPTURED"
|
|
1199
|
+
: "") +
|
|
1200
|
+
"\n" +
|
|
1201
|
+
result.note +
|
|
1202
|
+
"\n");
|
|
1203
|
+
}
|
|
1204
|
+
// 2 = the write landed but the instance does not reflect it (or it was not captured),
|
|
1205
|
+
// which must not read as success to a script.
|
|
1206
|
+
if (result.status === "failed")
|
|
1207
|
+
return 2;
|
|
1208
|
+
if (result.status === "applied" && !result.capturedInUpdateSet)
|
|
1209
|
+
return 2;
|
|
1210
|
+
return 0;
|
|
1211
|
+
}
|
|
994
1212
|
/** Parse inline `--fields "k=v, k2=v2"` into a field map. */
|
|
995
1213
|
function parseFieldsInline(input) {
|
|
996
1214
|
var out = {};
|
|
@@ -1292,6 +1510,9 @@ async function main() {
|
|
|
1292
1510
|
if (parsed.command === "add-column") {
|
|
1293
1511
|
return await runAddColumn(parsed.flags);
|
|
1294
1512
|
}
|
|
1513
|
+
if (parsed.command === "set-column") {
|
|
1514
|
+
return await runSetColumn(parsed.flags, parsed.bare);
|
|
1515
|
+
}
|
|
1295
1516
|
if (parsed.command === "set-field") {
|
|
1296
1517
|
return await runSetField(parsed.flags);
|
|
1297
1518
|
}
|
|
@@ -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;
|