@tenonhq/dovetail-servicenow 0.0.6 → 0.0.8
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 +85 -3
- package/dist/cli.js +208 -0
- package/dist/client.d.ts +11 -0
- package/dist/client.js +8 -0
- package/dist/flowDesigner/buildFlowOrchestrator.d.ts +9 -0
- package/dist/flowDesigner/buildFlowOrchestrator.js +52 -19
- package/dist/flowDesigner/editFlow.d.ts +59 -0
- package/dist/flowDesigner/editFlow.js +132 -0
- package/dist/flowDesigner/index.d.ts +12 -0
- package/dist/flowDesigner/index.js +14 -1
- package/dist/flowDesigner/publishActionType.d.ts +47 -0
- package/dist/flowDesigner/publishActionType.js +108 -0
- package/dist/flowDesigner/publishFlow.d.ts +50 -0
- package/dist/flowDesigner/publishFlow.js +96 -0
- package/dist/flowDesigner/readActionType.d.ts +45 -0
- package/dist/flowDesigner/readActionType.js +84 -0
- package/dist/flowDesigner/readFlow.d.ts +75 -0
- package/dist/flowDesigner/readFlow.js +154 -0
- package/dist/flowDesigner/testFlow.d.ts +50 -0
- package/dist/flowDesigner/testFlow.js +113 -0
- package/dist/flowDesigner/triggerPublication.d.ts +12 -4
- package/dist/flowDesigner/triggerPublication.js +12 -4
- package/dist/flowDesigner-formatter.d.ts +6 -0
- package/dist/flowDesigner-formatter.js +58 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +8 -1
- package/dist/mcp/registry.d.ts +1 -1
- package/dist/mcp/registry.js +85 -1
- package/dist/mcp/schemas.d.ts +150 -0
- package/dist/mcp/schemas.js +39 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -154,8 +154,40 @@ npx dove-sn set-related-lists \
|
|
|
154
154
|
--table x_cadso_automate_audience \
|
|
155
155
|
--related-lists "x_cadso_automate_audience_member.audience" \
|
|
156
156
|
--update-set 0083c3bb33d003507b18bc534d5c7b6d
|
|
157
|
+
|
|
158
|
+
# View a flow / subflow's compiled step graph (read-only, headless)
|
|
159
|
+
npx dove-sn view-flow --sys-id 327c53bfc33e3250d4ddf1db05013135
|
|
160
|
+
npx dove-sn view-flow --sys-id <sys_id> --json --raw # structured + full model
|
|
161
|
+
|
|
162
|
+
# View a Custom Action Type's model (inputs/outputs)
|
|
163
|
+
npx dove-sn view-action --sys-id <action_type_sys_id> --scope <scope_sys_id>
|
|
164
|
+
|
|
165
|
+
# Publish (compile the snapshot of) a flow / subflow after editing in the Designer
|
|
166
|
+
npx dove-sn publish-flow --sys-id <sys_id> # scope defaults to the flow's
|
|
167
|
+
|
|
168
|
+
# Test a flow: validate (default, read-only) or actually run it
|
|
169
|
+
npx dove-sn test-flow --sys-id <sys_id> --inputs '{"phone":"+1555..."}'
|
|
170
|
+
npx dove-sn test-flow --sys-id <sys_id> --execute --confirm --inputs '{...}' # runs it
|
|
171
|
+
|
|
172
|
+
# Edit a flow in place (rename / description / step inputs), then publish
|
|
173
|
+
echo '{"rename":{"name":"New Name"},"patchStepInputs":[{"step":"Calculate SMS Send At","input":"send_rate","value":"5"}]}' > ops.json
|
|
174
|
+
npx dove-sn edit-flow --sys-id <sys_id> --from-json ops.json # dry-run (diff)
|
|
175
|
+
npx dove-sn edit-flow --sys-id <sys_id> --from-json ops.json --apply # publish the edit
|
|
157
176
|
```
|
|
158
177
|
|
|
178
|
+
`test-flow` defaults to **validate** — a safe pre-flight (published? inputs match
|
|
179
|
+
declared variables?) that never runs the flow. `--execute --confirm` runs it via
|
|
180
|
+
the server-side FlowAPI runner (deploy `resources/runFlow.md` first); running a
|
|
181
|
+
flow can cause real side effects. `edit-flow` defaults to a **dry-run** diff;
|
|
182
|
+
`--apply` re-publishes the patched model via the snapshot endpoint.
|
|
183
|
+
|
|
184
|
+
`view-flow` reads `GET /api/now/processflow/flow/{id}` — the Designer's own model
|
|
185
|
+
endpoint — and prints the ordered, nesting-aware action + flow-logic step graph
|
|
186
|
+
plus the flow variables. This works for the integration user with plain basic
|
|
187
|
+
auth; the raw `sys_hub_flow_snapshot` Table API 404 is a row-level restriction on
|
|
188
|
+
the working snapshot, not a barrier. `publish-flow` POSTs the model back to
|
|
189
|
+
`.../flow/{id}/snapshot`, recompiling the current design (a write).
|
|
190
|
+
|
|
159
191
|
`set-form-layout` JSON payload shape:
|
|
160
192
|
|
|
161
193
|
```json
|
|
@@ -193,10 +225,13 @@ console.log(formatLayoutResult("form layout", result));
|
|
|
193
225
|
|
|
194
226
|
## MCP server
|
|
195
227
|
|
|
196
|
-
`dove-sn mcp` runs a self-contained MCP stdio server exposing the
|
|
228
|
+
`dove-sn mcp` runs a self-contained MCP stdio server exposing the tools to
|
|
197
229
|
Claude Code and agents: `create_view`, `set_list_layout`, `set_form_layout`,
|
|
198
|
-
`set_related_lists`,
|
|
199
|
-
|
|
230
|
+
`set_related_lists`, `add_choices_to_field`, plus the Flow Designer tools
|
|
231
|
+
`flow_view` (read a flow/subflow's step graph), `action_view` (read an action
|
|
232
|
+
type's model), `flow_publish` (compile a flow/subflow snapshot), `flow_test`
|
|
233
|
+
(validate or run a flow), and `flow_edit` (patch a flow + publish). It reads
|
|
234
|
+
ServiceNow credentials from the same env vars as the CLI.
|
|
200
235
|
|
|
201
236
|
```bash
|
|
202
237
|
npx dove-sn mcp --smoke # list the registered tools and exit
|
|
@@ -212,6 +247,53 @@ npx dove-sn mcp # run the stdio server (wire into .mcp.json)
|
|
|
212
247
|
This server is separate from `@tenonhq/dovetail-mcp` (the read-only cross-system
|
|
213
248
|
aggregator) — `dovetail-servicenow`'s server is the ServiceNow **write** surface.
|
|
214
249
|
|
|
250
|
+
## Publishing a Custom Action Type
|
|
251
|
+
|
|
252
|
+
`publishActionType` compiles the `sys_hub_flow_snapshot` for a Custom Action Type
|
|
253
|
+
— the step that makes it draggable in the Flow Designer palette. This replays the
|
|
254
|
+
Designer's **Publish** button, which is a plain REST call that **works with basic
|
|
255
|
+
auth** (no session cookie, CSRF token, or `sn_build_agent` role):
|
|
256
|
+
|
|
257
|
+
```
|
|
258
|
+
GET /api/now/processflow/action/action_types/{sysId}?sysparm_transaction_scope={scope}
|
|
259
|
+
-> 200, the full action-type model EXCEPT `steps` (always returns null)
|
|
260
|
+
POST /api/now/processflow/action/action_types/{sysId}/snapshot?sysparm_transaction_scope={scope}
|
|
261
|
+
body = the model with a `steps` array grafted in
|
|
262
|
+
-> 201 Created (compiles the snapshot; also persists step input values
|
|
263
|
+
back to sys_variable_value)
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
This is the **real** snapshot compiler and supersedes `triggerPublication` for
|
|
267
|
+
action types — that function ships in degraded mode (it only sets
|
|
268
|
+
`status="published"` and polls, because the snapshot trigger was unknown when it
|
|
269
|
+
was written). `triggerPublication` is retained for back-compat and the subflow path.
|
|
270
|
+
|
|
271
|
+
```ts
|
|
272
|
+
import { createClient, publishActionType } from "@tenonhq/dovetail-servicenow";
|
|
273
|
+
|
|
274
|
+
var client = createClient({});
|
|
275
|
+
var result = await publishActionType({
|
|
276
|
+
client: client,
|
|
277
|
+
sysId: "60e6743e33814bd07b18bc534d5c7b9e", // sys_hub_action_type_definition
|
|
278
|
+
scopeSysId: "cd61acbbc3c85a1085b196c4e40131bd", // sysparm_transaction_scope
|
|
279
|
+
steps: require("./fixtures/my-action.steps.json") // see caveat below
|
|
280
|
+
});
|
|
281
|
+
// { status: "published", httpStatus: 201, snapshotSysId?: "..." }
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
### Steps-fixture caveat (required)
|
|
285
|
+
|
|
286
|
+
The GET returns **`steps: null`** even for an already-published action — the
|
|
287
|
+
Designer assembles `steps` client-side from the step records. So to publish you
|
|
288
|
+
**must supply a `steps` fixture** via `params.steps`. Each step's `action` field
|
|
289
|
+
is remapped to the target `sysId` automatically. If you omit `steps` and the
|
|
290
|
+
fetched model has no usable `steps` array, `publishActionType` throws with a clear
|
|
291
|
+
message. For a faithful clone, capture the source action's `steps` from a HAR of
|
|
292
|
+
the Publish call and store it as a fixture beside your driver.
|
|
293
|
+
|
|
294
|
+
Full recipe and the 6-record action-type graph:
|
|
295
|
+
`docs/servicenow-flow-designer-headless-authoring.md` in the CTO repo.
|
|
296
|
+
|
|
215
297
|
## Roadmap
|
|
216
298
|
|
|
217
299
|
The same query-to-diff pattern will continue across the rest of the
|
package/dist/cli.js
CHANGED
|
@@ -68,6 +68,12 @@ const formatter_2 = require("./layout/formatter");
|
|
|
68
68
|
const server_1 = require("./mcp/server");
|
|
69
69
|
const buildFlowOrchestrator_1 = require("./flowDesigner/buildFlowOrchestrator");
|
|
70
70
|
const flowDesigner_formatter_1 = require("./flowDesigner-formatter");
|
|
71
|
+
const readFlow_1 = require("./flowDesigner/readFlow");
|
|
72
|
+
const readActionType_1 = require("./flowDesigner/readActionType");
|
|
73
|
+
const publishFlow_1 = require("./flowDesigner/publishFlow");
|
|
74
|
+
const editFlow_1 = require("./flowDesigner/editFlow");
|
|
75
|
+
const testFlow_1 = require("./flowDesigner/testFlow");
|
|
76
|
+
const flowDesigner_formatter_2 = require("./flowDesigner-formatter");
|
|
71
77
|
function parseArgs(argv) {
|
|
72
78
|
var command = argv[0] || "";
|
|
73
79
|
var flags = {};
|
|
@@ -299,6 +305,183 @@ async function runSetRelatedLists(flags) {
|
|
|
299
305
|
* dove-sn mcp — run the MCP stdio server. With --smoke, list the registered
|
|
300
306
|
* tools and exit. Otherwise the process stays alive until the transport closes.
|
|
301
307
|
*/
|
|
308
|
+
/**
|
|
309
|
+
* dove-sn view-flow:
|
|
310
|
+
* --sys-id <sys_id> Required. sys_hub_flow sys_id (flow or subflow).
|
|
311
|
+
* --json Optional. Emit the structured ReadFlowResult.
|
|
312
|
+
* --raw Optional (with --json). Include the full processflow model.
|
|
313
|
+
*
|
|
314
|
+
* Reads the compiled flow headlessly via GET /api/now/processflow/flow/{id} and
|
|
315
|
+
* prints the ordered, nesting-aware step graph + flow variables. Read-only.
|
|
316
|
+
*/
|
|
317
|
+
async function runViewFlow(flags) {
|
|
318
|
+
var sysId = flags["sys-id"] || flags.sysId;
|
|
319
|
+
if (!sysId) {
|
|
320
|
+
process.stderr.write("view-flow: --sys-id <sys_id> is required\n");
|
|
321
|
+
return 1;
|
|
322
|
+
}
|
|
323
|
+
var client = (0, client_1.createClient)({});
|
|
324
|
+
var result = await (0, readFlow_1.readFlow)({ client: client, sysId: sysId, raw: flags.raw === "true" });
|
|
325
|
+
if (flags.json === "true") {
|
|
326
|
+
process.stdout.write(JSON.stringify(result, null, 2) + "\n");
|
|
327
|
+
return 0;
|
|
328
|
+
}
|
|
329
|
+
process.stdout.write((0, flowDesigner_formatter_2.formatReadFlowResult)(result) + "\n");
|
|
330
|
+
return 0;
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* dove-sn view-action:
|
|
334
|
+
* --sys-id <sys_id> Required. sys_hub_action_type_definition sys_id.
|
|
335
|
+
* --scope <sys_id> Required. Application scope (sysparm_transaction_scope).
|
|
336
|
+
* --json [--raw] Optional. Structured ReadActionTypeResult / full model.
|
|
337
|
+
*
|
|
338
|
+
* Reads a Custom Action Type's compiled model (identity, inputs, outputs). Read-only.
|
|
339
|
+
*/
|
|
340
|
+
async function runViewAction(flags) {
|
|
341
|
+
var sysId = flags["sys-id"] || flags.sysId;
|
|
342
|
+
var scope = flags.scope || flags.scopeSysId;
|
|
343
|
+
if (!sysId || !scope) {
|
|
344
|
+
process.stderr.write("view-action: --sys-id <sys_id> and --scope <sys_id> are required\n");
|
|
345
|
+
return 1;
|
|
346
|
+
}
|
|
347
|
+
var client = (0, client_1.createClient)({});
|
|
348
|
+
var result = await (0, readActionType_1.readActionType)({
|
|
349
|
+
client: client,
|
|
350
|
+
sysId: sysId,
|
|
351
|
+
scopeSysId: scope,
|
|
352
|
+
raw: flags.raw === "true"
|
|
353
|
+
});
|
|
354
|
+
if (flags.json === "true") {
|
|
355
|
+
process.stdout.write(JSON.stringify(result, null, 2) + "\n");
|
|
356
|
+
return 0;
|
|
357
|
+
}
|
|
358
|
+
process.stdout.write((0, flowDesigner_formatter_2.formatReadActionTypeResult)(result) + "\n");
|
|
359
|
+
return 0;
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* dove-sn publish-flow:
|
|
363
|
+
* --sys-id <sys_id> Required. sys_hub_flow sys_id (flow or subflow) to publish.
|
|
364
|
+
* --scope <sys_id> Optional. sysparm_transaction_scope (defaults to the model's scope).
|
|
365
|
+
* --json Optional. Emit the structured PublishFlowResult.
|
|
366
|
+
*
|
|
367
|
+
* Compiles the flow's snapshot via POST /api/now/processflow/flow/{id}/snapshot —
|
|
368
|
+
* a WRITE that recompiles the current design. Use the Designer to edit, then this
|
|
369
|
+
* to publish. (For edited content, the library publishFlow accepts a model.)
|
|
370
|
+
*/
|
|
371
|
+
async function runPublishFlow(flags) {
|
|
372
|
+
var sysId = flags["sys-id"] || flags.sysId;
|
|
373
|
+
if (!sysId) {
|
|
374
|
+
process.stderr.write("publish-flow: --sys-id <sys_id> is required\n");
|
|
375
|
+
return 1;
|
|
376
|
+
}
|
|
377
|
+
var params = {
|
|
378
|
+
client: (0, client_1.createClient)({}),
|
|
379
|
+
sysId: sysId
|
|
380
|
+
};
|
|
381
|
+
if (flags.scope || flags.scopeSysId) {
|
|
382
|
+
params.scopeSysId = flags.scope || flags.scopeSysId;
|
|
383
|
+
}
|
|
384
|
+
var result = await (0, publishFlow_1.publishFlow)(params);
|
|
385
|
+
if (flags.json === "true") {
|
|
386
|
+
process.stdout.write(JSON.stringify(result, null, 2) + "\n");
|
|
387
|
+
return 0;
|
|
388
|
+
}
|
|
389
|
+
process.stdout.write("Published flow " + sysId + " (HTTP " + result.httpStatus + ")"
|
|
390
|
+
+ (result.snapshotSysId ? " — snapshot " + result.snapshotSysId : "") + "\n");
|
|
391
|
+
return 0;
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* dove-sn test-flow:
|
|
395
|
+
* --sys-id <sys_id> Required. sys_hub_flow sys_id (flow or subflow).
|
|
396
|
+
* --execute Optional. Actually run it (default is validate-only).
|
|
397
|
+
* --confirm Required with --execute. A deliberate run-for-real gate.
|
|
398
|
+
* --inputs <json> Optional. JSON object of inputs (or --inputs-json <path>).
|
|
399
|
+
* --json Optional. Emit the structured TestFlowResult.
|
|
400
|
+
*
|
|
401
|
+
* Default (no --execute) is a safe pre-flight: published? readable? inputs match
|
|
402
|
+
* declared variables? --execute POSTs the FlowAPI runner endpoint (see
|
|
403
|
+
* resources/runFlow.md). Executing a flow can cause real side effects.
|
|
404
|
+
*/
|
|
405
|
+
async function runTestFlow(flags) {
|
|
406
|
+
var sysId = flags["sys-id"] || flags.sysId;
|
|
407
|
+
if (!sysId) {
|
|
408
|
+
process.stderr.write("test-flow: --sys-id <sys_id> is required\n");
|
|
409
|
+
return 1;
|
|
410
|
+
}
|
|
411
|
+
var inputs = {};
|
|
412
|
+
if (flags["inputs-json"]) {
|
|
413
|
+
inputs = JSON.parse(fs.readFileSync(flags["inputs-json"], "utf8"));
|
|
414
|
+
}
|
|
415
|
+
else if (flags.inputs) {
|
|
416
|
+
inputs = JSON.parse(flags.inputs);
|
|
417
|
+
}
|
|
418
|
+
var params = {
|
|
419
|
+
client: (0, client_1.createClient)({}),
|
|
420
|
+
sysId: sysId,
|
|
421
|
+
mode: flags.execute === "true" ? "execute" : "validate",
|
|
422
|
+
inputs: inputs,
|
|
423
|
+
confirm: flags.confirm === "true"
|
|
424
|
+
};
|
|
425
|
+
if (flags.runner) {
|
|
426
|
+
params.runnerPath = flags.runner;
|
|
427
|
+
}
|
|
428
|
+
var result = await (0, testFlow_1.testFlow)(params);
|
|
429
|
+
if (flags.json === "true") {
|
|
430
|
+
process.stdout.write(JSON.stringify(result, null, 2) + "\n");
|
|
431
|
+
return 0;
|
|
432
|
+
}
|
|
433
|
+
process.stdout.write("[" + result.mode + "] ok=" + result.ok + "\n");
|
|
434
|
+
for (var i = 0; i < result.notes.length; i += 1) {
|
|
435
|
+
process.stdout.write(" " + result.notes[i] + "\n");
|
|
436
|
+
}
|
|
437
|
+
return result.ok ? 0 : 2;
|
|
438
|
+
}
|
|
439
|
+
/**
|
|
440
|
+
* dove-sn edit-flow:
|
|
441
|
+
* --sys-id <sys_id> Required. sys_hub_flow sys_id (flow or subflow).
|
|
442
|
+
* --from-json <path> Required. JSON EditFlowOps { rename?, description?, patchStepInputs? }.
|
|
443
|
+
* --apply Optional. Publish the edit (default is a dry-run diff).
|
|
444
|
+
* --scope <sys_id> Optional. sysparm_transaction_scope for the publish.
|
|
445
|
+
* --json Optional. Emit the structured EditFlowResult.
|
|
446
|
+
*
|
|
447
|
+
* Reads the model, applies the declarative edits, and (with --apply) re-publishes
|
|
448
|
+
* via the snapshot endpoint. Without --apply it prints the would-be changes.
|
|
449
|
+
*/
|
|
450
|
+
async function runEditFlow(flags) {
|
|
451
|
+
var sysId = flags["sys-id"] || flags.sysId;
|
|
452
|
+
if (!sysId) {
|
|
453
|
+
process.stderr.write("edit-flow: --sys-id <sys_id> is required\n");
|
|
454
|
+
return 1;
|
|
455
|
+
}
|
|
456
|
+
if (!flags["from-json"]) {
|
|
457
|
+
process.stderr.write("edit-flow: --from-json <path> (EditFlowOps) is required\n");
|
|
458
|
+
return 1;
|
|
459
|
+
}
|
|
460
|
+
var ops = JSON.parse(fs.readFileSync(flags["from-json"], "utf8"));
|
|
461
|
+
var params = {
|
|
462
|
+
client: (0, client_1.createClient)({}),
|
|
463
|
+
sysId: sysId,
|
|
464
|
+
ops: ops,
|
|
465
|
+
apply: flags.apply === "true"
|
|
466
|
+
};
|
|
467
|
+
if (flags.scope || flags.scopeSysId) {
|
|
468
|
+
params.scopeSysId = flags.scope || flags.scopeSysId;
|
|
469
|
+
}
|
|
470
|
+
var result = await (0, editFlow_1.editFlow)(params);
|
|
471
|
+
if (flags.json === "true") {
|
|
472
|
+
process.stdout.write(JSON.stringify(result, null, 2) + "\n");
|
|
473
|
+
return 0;
|
|
474
|
+
}
|
|
475
|
+
process.stdout.write("[" + result.status + "] " + result.changes.length + " change(s)"
|
|
476
|
+
+ (result.snapshotSysId ? " — snapshot " + result.snapshotSysId : "") + "\n");
|
|
477
|
+
for (var i = 0; i < result.changes.length; i += 1) {
|
|
478
|
+
process.stdout.write(" + " + result.changes[i] + "\n");
|
|
479
|
+
}
|
|
480
|
+
for (var w = 0; w < result.warnings.length; w += 1) {
|
|
481
|
+
process.stdout.write(" ! " + result.warnings[w] + "\n");
|
|
482
|
+
}
|
|
483
|
+
return 0;
|
|
484
|
+
}
|
|
302
485
|
async function runMcp(flags) {
|
|
303
486
|
if (flags.smoke === "true") {
|
|
304
487
|
await (0, server_1.runSmoke)();
|
|
@@ -324,6 +507,16 @@ function printHelp() {
|
|
|
324
507
|
" [--view <v>] [--scope <s>] [--prune false] [--dry-run] [--json])\n" +
|
|
325
508
|
" build-flow Author Custom Action Types and Subflows from a JSON spec\n" +
|
|
326
509
|
" (--from-json <path> [--update-set <sys_id>] [--dry-run] [--skip-publish] [--json])\n" +
|
|
510
|
+
" view-flow Read a flow/subflow's compiled step graph (read-only)\n" +
|
|
511
|
+
" (--sys-id <sys_id> [--json] [--raw])\n" +
|
|
512
|
+
" view-action Read a Custom Action Type's model — inputs/outputs (read-only)\n" +
|
|
513
|
+
" (--sys-id <sys_id> --scope <sys_id> [--json] [--raw])\n" +
|
|
514
|
+
" publish-flow Compile a flow/subflow snapshot (write)\n" +
|
|
515
|
+
" (--sys-id <sys_id> [--scope <sys_id>] [--json])\n" +
|
|
516
|
+
" test-flow Validate (default) or run a flow/subflow\n" +
|
|
517
|
+
" (--sys-id <sys_id> [--execute --confirm] [--inputs <json>] [--json])\n" +
|
|
518
|
+
" edit-flow Patch a flow/subflow (rename, description, step inputs) + publish\n" +
|
|
519
|
+
" (--sys-id <sys_id> --from-json <ops.json> [--apply] [--scope <sys_id>] [--json])\n" +
|
|
327
520
|
" mcp Run the MCP stdio server (--smoke lists tools and exits)\n");
|
|
328
521
|
}
|
|
329
522
|
async function main() {
|
|
@@ -335,6 +528,21 @@ async function main() {
|
|
|
335
528
|
if (parsed.command === "build-flow") {
|
|
336
529
|
return await runBuildFlowCmd(parsed.flags);
|
|
337
530
|
}
|
|
531
|
+
if (parsed.command === "view-flow") {
|
|
532
|
+
return await runViewFlow(parsed.flags);
|
|
533
|
+
}
|
|
534
|
+
if (parsed.command === "view-action") {
|
|
535
|
+
return await runViewAction(parsed.flags);
|
|
536
|
+
}
|
|
537
|
+
if (parsed.command === "publish-flow") {
|
|
538
|
+
return await runPublishFlow(parsed.flags);
|
|
539
|
+
}
|
|
540
|
+
if (parsed.command === "test-flow") {
|
|
541
|
+
return await runTestFlow(parsed.flags);
|
|
542
|
+
}
|
|
543
|
+
if (parsed.command === "edit-flow") {
|
|
544
|
+
return await runEditFlow(parsed.flags);
|
|
545
|
+
}
|
|
338
546
|
if (parsed.command === "create-view") {
|
|
339
547
|
await runCreateView(parsed.flags);
|
|
340
548
|
return 0;
|
package/dist/client.d.ts
CHANGED
|
@@ -102,5 +102,16 @@ export interface ServiceNowClient {
|
|
|
102
102
|
[k: string]: any;
|
|
103
103
|
}>;
|
|
104
104
|
};
|
|
105
|
+
now: {
|
|
106
|
+
/**
|
|
107
|
+
* GET an arbitrary native ServiceNow REST path (e.g. /api/now/processflow/...).
|
|
108
|
+
* Basic auth, same credentials/retry/throttle as the rest of the client.
|
|
109
|
+
* Use for endpoints that aren't the Table API or the Dovetail Scripted REST API
|
|
110
|
+
* — currently the Flow Designer processflow endpoints. Returns the raw response body.
|
|
111
|
+
*/
|
|
112
|
+
get: <T = any>(path: string) => Promise<T>;
|
|
113
|
+
/** POST an arbitrary native ServiceNow REST path with a JSON body. See `get`. */
|
|
114
|
+
post: <T = any>(path: string, body: any) => Promise<T>;
|
|
115
|
+
};
|
|
105
116
|
}
|
|
106
117
|
export declare function createClient(config?: ServiceNowClientConfig): ServiceNowClient;
|
package/dist/client.js
CHANGED
|
@@ -296,6 +296,14 @@ function createClient(config = {}) {
|
|
|
296
296
|
var data = await dovetailRequest("POST", "deleteRecord", { table: params.table, sys_id: params.sys_id }, null, "claude.deleteRecord(" + params.table + ")");
|
|
297
297
|
return data.result || data;
|
|
298
298
|
}
|
|
299
|
+
},
|
|
300
|
+
now: {
|
|
301
|
+
get: function (path) {
|
|
302
|
+
return request({ method: "GET", url: path }, "now.get(" + path + ")");
|
|
303
|
+
},
|
|
304
|
+
post: function (path, body) {
|
|
305
|
+
return request({ method: "POST", url: path, data: body }, "now.post(" + path + ")");
|
|
306
|
+
}
|
|
299
307
|
}
|
|
300
308
|
};
|
|
301
309
|
}
|
|
@@ -29,6 +29,15 @@ export interface BuildFlowOptions {
|
|
|
29
29
|
skipPublish?: boolean;
|
|
30
30
|
/** Forwarded to triggerPublication. Useful for tight tests; defaults to its own 15s. */
|
|
31
31
|
snapshotTimeoutMs?: number;
|
|
32
|
+
/**
|
|
33
|
+
* Steps fixture for publishing an action-type clone. The action-type model GET
|
|
34
|
+
* returns `steps: null`, so the real publisher (publishActionType) needs a
|
|
35
|
+
* steps array. When provided for kind="actionType", the orchestrator uses the
|
|
36
|
+
* real processflow publisher; without it, action-type publish falls back to
|
|
37
|
+
* the degraded triggerPublication. Ignored for subflows (publishFlow re-posts
|
|
38
|
+
* the full model, which already carries the instance graph).
|
|
39
|
+
*/
|
|
40
|
+
steps?: Array<Record<string, any>>;
|
|
32
41
|
}
|
|
33
42
|
/**
|
|
34
43
|
* Outcome categories. Maps directly to CLI exit codes:
|
|
@@ -14,6 +14,8 @@ const cloneSubflow_1 = require("./cloneSubflow");
|
|
|
14
14
|
const cloneActionType_1 = require("./cloneActionType");
|
|
15
15
|
const verifyArtifact_1 = require("./verifyArtifact");
|
|
16
16
|
const triggerPublication_1 = require("./triggerPublication");
|
|
17
|
+
const publishFlow_1 = require("./publishFlow");
|
|
18
|
+
const publishActionType_1 = require("./publishActionType");
|
|
17
19
|
const RX_SYS_ID = /^[0-9a-f]{32}$/;
|
|
18
20
|
function unrecoverable(spec, stage, message) {
|
|
19
21
|
return {
|
|
@@ -23,6 +25,50 @@ function unrecoverable(spec, stage, message) {
|
|
|
23
25
|
error: { stage: stage, message: message },
|
|
24
26
|
};
|
|
25
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* Publish a freshly cloned artifact. Tries the real processflow publisher first
|
|
30
|
+
* (publishFlow for subflows, publishActionType for action types when a steps
|
|
31
|
+
* fixture is supplied), and falls back to the degraded triggerPublication if the
|
|
32
|
+
* real path is unavailable or throws. Never throws — always returns a
|
|
33
|
+
* TriggerPublicationResult-compatible object so the caller's outcome mapping is
|
|
34
|
+
* unchanged (status "published" => done; anything else => needs-ui-publish).
|
|
35
|
+
*/
|
|
36
|
+
async function publishArtifact(client, spec, sysId, opts) {
|
|
37
|
+
try {
|
|
38
|
+
if (spec.kind === "subflow") {
|
|
39
|
+
var pf = await (0, publishFlow_1.publishFlow)({ client: client, sysId: sysId, scopeSysId: spec.newScope });
|
|
40
|
+
return { status: "published", snapshotSysId: pf.snapshotSysId, pushSucceeded: true };
|
|
41
|
+
}
|
|
42
|
+
// actionType: the real publisher needs a steps fixture (model GET => steps:null).
|
|
43
|
+
if (spec.kind === "actionType" && opts.steps && opts.steps.length > 0) {
|
|
44
|
+
var pa = await (0, publishActionType_1.publishActionType)({
|
|
45
|
+
client: client,
|
|
46
|
+
sysId: sysId,
|
|
47
|
+
scopeSysId: spec.newScope,
|
|
48
|
+
steps: opts.steps,
|
|
49
|
+
});
|
|
50
|
+
return { status: "published", snapshotSysId: pa.snapshotSysId, pushSucceeded: true };
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
catch (err) {
|
|
54
|
+
// Real publish failed — fall through to the degraded path below, which is
|
|
55
|
+
// engineered to surface a UI publish URL rather than throw.
|
|
56
|
+
}
|
|
57
|
+
try {
|
|
58
|
+
return await (0, triggerPublication_1.triggerPublication)({
|
|
59
|
+
client: client,
|
|
60
|
+
sysId: sysId,
|
|
61
|
+
kind: spec.kind,
|
|
62
|
+
updateSetSysId: spec.updateSetSysId,
|
|
63
|
+
snapshotTimeoutMs: opts.snapshotTimeoutMs,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
catch (err) {
|
|
67
|
+
// triggerPublication is engineered to never throw on the happy path, but
|
|
68
|
+
// defensive: treat unexpected throws as needs-ui-publish.
|
|
69
|
+
return { status: "needs-ui-publish", pushSucceeded: false, uiPublishUrl: undefined };
|
|
70
|
+
}
|
|
71
|
+
}
|
|
26
72
|
function validateSpec(spec) {
|
|
27
73
|
if (!spec || typeof spec !== "object")
|
|
28
74
|
throw new Error("spec must be a JSON object");
|
|
@@ -163,27 +209,14 @@ async function runBuildFlow(client, rawSpec, opts = {}) {
|
|
|
163
209
|
verify: verify,
|
|
164
210
|
};
|
|
165
211
|
}
|
|
166
|
-
// Phase 4: publication
|
|
212
|
+
// Phase 4: publication. Try the real processflow publisher first (compiles
|
|
213
|
+
// the snapshot via the Designer's own Publish endpoint — basic auth, 2xx),
|
|
214
|
+
// and fall back to the degraded triggerPublication (status flip + poll, UI
|
|
215
|
+
// fallback) only if the real path throws. This is what replaces the long-
|
|
216
|
+
// standing "needs-ui-publish" default for the common case.
|
|
167
217
|
var publish;
|
|
168
218
|
if (!opts.skipPublish) {
|
|
169
|
-
|
|
170
|
-
publish = await (0, triggerPublication_1.triggerPublication)({
|
|
171
|
-
client: client,
|
|
172
|
-
sysId: cloneResult.sysId,
|
|
173
|
-
kind: spec.kind,
|
|
174
|
-
updateSetSysId: spec.updateSetSysId,
|
|
175
|
-
snapshotTimeoutMs: opts.snapshotTimeoutMs,
|
|
176
|
-
});
|
|
177
|
-
}
|
|
178
|
-
catch (err) {
|
|
179
|
-
// triggerPublication is engineered to never throw on the happy path,
|
|
180
|
-
// but defensive: treat unexpected throws as needs-ui-publish.
|
|
181
|
-
publish = {
|
|
182
|
-
status: "needs-ui-publish",
|
|
183
|
-
pushSucceeded: false,
|
|
184
|
-
uiPublishUrl: undefined,
|
|
185
|
-
};
|
|
186
|
-
}
|
|
219
|
+
publish = await publishArtifact(client, spec, cloneResult.sysId, opts);
|
|
187
220
|
}
|
|
188
221
|
var artifact = {
|
|
189
222
|
sysId: cloneResult.sysId,
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Edit a Flow Designer flow or subflow in place, headless.
|
|
3
|
+
*
|
|
4
|
+
* Strategy: read the compiled model (GET /processflow/flow/{id}), apply a small
|
|
5
|
+
* set of declarative mutations to it, then re-publish it (POST .../snapshot via
|
|
6
|
+
* publishFlow). The publish POST persists step input values back to the design
|
|
7
|
+
* records — the same mechanism the Designer's Publish button uses — so a patched
|
|
8
|
+
* model lands in both the running snapshot and the records the Designer reads.
|
|
9
|
+
*
|
|
10
|
+
* Supported edits (intentionally narrow + safe — no structural step add/remove,
|
|
11
|
+
* which requires the V2 instance graph and is out of scope here):
|
|
12
|
+
* - rename: set name / internalName on the flow
|
|
13
|
+
* - description: set the flow description
|
|
14
|
+
* - patchStepInputs: set named input values on specific steps (by uiId or label)
|
|
15
|
+
*
|
|
16
|
+
* Anything unmatched is reported in `result.warnings` rather than silently
|
|
17
|
+
* dropped, so a typo'd step label or input name is visible.
|
|
18
|
+
*
|
|
19
|
+
* By default this is a DRY RUN — it computes and returns the diff WITHOUT
|
|
20
|
+
* publishing. Pass `apply: true` to actually publish the edited model (a write).
|
|
21
|
+
*/
|
|
22
|
+
import type { ServiceNowClient } from "../client";
|
|
23
|
+
export interface StepInputPatch {
|
|
24
|
+
/** Identify the step by its uiUniqueIdentifier OR its label (name). */
|
|
25
|
+
step: string;
|
|
26
|
+
/** The input's `name` on the action instance. */
|
|
27
|
+
input: string;
|
|
28
|
+
/** New value to set (may be any JSON value, including the empty string). */
|
|
29
|
+
value?: any;
|
|
30
|
+
}
|
|
31
|
+
export interface EditFlowOps {
|
|
32
|
+
rename?: {
|
|
33
|
+
name?: string;
|
|
34
|
+
internalName?: string;
|
|
35
|
+
};
|
|
36
|
+
description?: string;
|
|
37
|
+
patchStepInputs?: Array<StepInputPatch>;
|
|
38
|
+
}
|
|
39
|
+
export interface EditFlowParams {
|
|
40
|
+
client: ServiceNowClient;
|
|
41
|
+
/** sys_id of the sys_hub_flow (flow or subflow) to edit. */
|
|
42
|
+
sysId: string;
|
|
43
|
+
ops: EditFlowOps;
|
|
44
|
+
/** When true, publish the edited model. When false/omitted, dry-run (no write). */
|
|
45
|
+
apply?: boolean;
|
|
46
|
+
/** Override sysparm_transaction_scope for the publish; defaults to the model's scope. */
|
|
47
|
+
scopeSysId?: string;
|
|
48
|
+
}
|
|
49
|
+
export interface EditFlowResult {
|
|
50
|
+
/** "dry-run" (computed, not published) or "published". */
|
|
51
|
+
status: "dry-run" | "published";
|
|
52
|
+
/** Human-readable list of changes that were applied to the model. */
|
|
53
|
+
changes: Array<string>;
|
|
54
|
+
/** Requested edits that could not be matched (e.g. unknown step or input). */
|
|
55
|
+
warnings: Array<string>;
|
|
56
|
+
/** Present when status="published". */
|
|
57
|
+
snapshotSysId?: string;
|
|
58
|
+
}
|
|
59
|
+
export declare function editFlow(params: EditFlowParams): Promise<EditFlowResult>;
|