@tenonhq/dovetail-servicenow 0.0.51 → 0.0.53
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 +77 -15
- package/dist/cli.js +95 -8
- package/dist/cliUsage.js +33 -6
- package/dist/client.d.ts +11 -0
- package/dist/client.js +17 -6
- package/dist/createRecord.d.ts +2 -1
- package/dist/createRecord.js +12 -1
- package/dist/deleteRecord.d.ts +35 -12
- package/dist/deleteRecord.js +238 -38
- package/dist/flowDesigner/index.d.ts +2 -2
- package/dist/flowDesigner/index.js +2 -1
- package/dist/flowDesigner/testFlow.d.ts +40 -16
- package/dist/flowDesigner/testFlow.js +175 -51
- package/dist/index.d.ts +4 -2
- package/dist/index.js +8 -3
- package/dist/mcp/registry.d.ts +1 -1
- package/dist/mcp/registry.js +93 -14
- package/dist/mcp/schemas.d.ts +22 -0
- package/dist/mcp/schemas.js +22 -4
- package/dist/table/addColumn.d.ts +3 -2
- package/dist/table/addColumn.js +63 -35
- package/dist/table/addIndex.js +48 -2
- package/dist/table/createIndex.d.ts +14 -0
- package/dist/table/createIndex.js +205 -2
- package/dist/table/designAccess.d.ts +2 -2
- package/dist/table/designAccess.js +30 -7
- package/dist/table/listIndexes.d.ts +40 -0
- package/dist/table/listIndexes.js +159 -24
- package/dist/table/overrideColumn.d.ts +12 -18
- package/dist/table/overrideColumn.js +13 -41
- package/dist/table/setColumn.d.ts +5 -3
- package/dist/table/setColumn.js +27 -8
- package/dist/table/updateSetGuard.d.ts +23 -0
- package/dist/table/updateSetGuard.js +57 -0
- package/dist/uxEvents.d.ts +114 -0
- package/dist/uxEvents.js +407 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -349,6 +349,7 @@ npx dove-sn publish-flow --sys-id <sys_id> # scope defaults to the f
|
|
|
349
349
|
# Test a flow: validate (default, read-only) or actually run it
|
|
350
350
|
npx dove-sn test-flow --sys-id <sys_id> --inputs '{"phone":"+1555..."}'
|
|
351
351
|
npx dove-sn test-flow --sys-id <sys_id> --execute --confirm --inputs '{...}' # runs it
|
|
352
|
+
npx dove-sn test-flow --sys-id <action_sys_id> --action --execute --confirm # runs an action
|
|
352
353
|
|
|
353
354
|
# Edit a flow in place (rename / description / step inputs)
|
|
354
355
|
echo '{"rename":{"name":"New Name"},"patchStepInputs":[{"step":"Calculate SMS Send At","input":"send_rate","value":"5"}]}' > ops.json
|
|
@@ -742,8 +743,16 @@ substring-matched, because `"[owner_id]"` contains `"owner"`. `type` is `access_
|
|
|
742
743
|
**Uniqueness is not readable.** `v_db_index` has no uniqueness field, so a unique index
|
|
743
744
|
and an ordinary one are indistinguishable in it: `unique` is left **absent** rather than
|
|
744
745
|
guessed, and `uniqueness-enforced` is reported in `unverified` on every result. Only a
|
|
745
|
-
duplicate-insert test proves enforcement. An empty result
|
|
746
|
-
|
|
746
|
+
duplicate-insert test proves enforcement. An empty result for a table that does not exist
|
|
747
|
+
means the table name is wrong - every physical table has a `PRIMARY`.
|
|
748
|
+
|
|
749
|
+
**Table-per-hierarchy children list their storage root's indexes.** `v_db_index` lists
|
|
750
|
+
indexes by *physical* table, and a table stored in an ancestor's physical table (anything
|
|
751
|
+
extending `task`, for example) has no rows under its own name. When the table exists but
|
|
752
|
+
has no rows, its `super_class` chain is walked to the first ancestor that has them, and
|
|
753
|
+
that root's indexes are listed - with `storageTable` and the note naming the root.
|
|
754
|
+
`index-create` and `add-index` **refuse** such a child on the live path, naming the root,
|
|
755
|
+
rather than build on it or report a false "NOT created".
|
|
747
756
|
|
|
748
757
|
### Create an index (composite and non-unique included)
|
|
749
758
|
|
|
@@ -792,6 +801,15 @@ was a silent no-op for that reason.)
|
|
|
792
801
|
- **Idempotent.** On the live path `v_db_index` is read first, and an index over *exactly*
|
|
793
802
|
these columns short-circuits to `already-exists` with no pin, no form session and no
|
|
794
803
|
write. Column **order** is part of an index's identity - `[a;b]` is not `[b;a]`.
|
|
804
|
+
- **Table-per-hierarchy children are refused.** A table with no `v_db_index` rows of its
|
|
805
|
+
own whose ancestor has them is stored in that ancestor's physical table; the run stops
|
|
806
|
+
before any write and names the root (run against the root if that is what you want).
|
|
807
|
+
- **Identity check (warns, never refuses).** The pin runs through the REST client (an API
|
|
808
|
+
key when one is configured) but the build is scheduled by the form session (always
|
|
809
|
+
`SN_USER`), and the build captures into the *form* user's current set. The REST caller's
|
|
810
|
+
own `sys_user` row is read first; when it is not the form-login user (or cannot be read)
|
|
811
|
+
the run still goes ahead and every result note carries an `IDENTITY WARNING`. The capture
|
|
812
|
+
read-back then reports where the row actually landed (`captured` / `captureFoundIn`).
|
|
795
813
|
- **The pin is read back.** The set is pinned with Dovetail's own `changeUpdateSet` and
|
|
796
814
|
`currentUpdateSet` is read; a pin that did not take stops the run before the session
|
|
797
815
|
opens.
|
|
@@ -804,7 +822,8 @@ was a silent no-op for that reason.)
|
|
|
804
822
|
values (EMPTY counts). Then the capture row is looked for in the pinned set:
|
|
805
823
|
`captured:true` only when it was read back; an index that exists but was not captured
|
|
806
824
|
is `created:true, captured:false` with `update-set-capture` in `unverified` - and
|
|
807
|
-
exit code 2, because it will not travel.
|
|
825
|
+
exit code 2, because it will not travel. In that case the capture name is searched
|
|
826
|
+
across every set and the set(s) it actually landed in come back in `captureFoundIn`.
|
|
808
827
|
- **Uniqueness is still never claimed.** `uniqueness-enforced` stays in `unverified` on
|
|
809
828
|
every status.
|
|
810
829
|
|
|
@@ -859,7 +878,8 @@ npx dove-sn create-record \
|
|
|
859
878
|
switches the executing user's app scope + update set server-side, inserts, and
|
|
860
879
|
restores both — so the record is owned by the right app and the insert is captured
|
|
861
880
|
in the right update set. Like `set-field` it **refuses** schema tables and verifies
|
|
862
|
-
via read-back.
|
|
881
|
+
via read-back. It also **refuses** `sys_update_set` — the op cannot set an update
|
|
882
|
+
set's application, so create sets with `dove createUpdateSet` instead. `--scope` and `--update-set` are required; `--if-absent
|
|
863
883
|
"<encoded-query>"` makes re-runs idempotent (the insert is skipped when the query
|
|
864
884
|
already matches a row). Exit codes: `0` created / skipped-in-sync / dry-run, `1` bad
|
|
865
885
|
args, `2` write landed unverified (or skipped with drift). To **update** an existing
|
|
@@ -916,14 +936,19 @@ npx dove-sn delete-record \
|
|
|
916
936
|
`delete-record` wraps the core `deleteRecord` op. It is **dry-run by default** —
|
|
917
937
|
nothing is deleted without `--apply` (`--dry-run` wins if both are given). `--sys-id`
|
|
918
938
|
must be a 32-character lowercase hex id and `--table` a plain table name; both are
|
|
919
|
-
validated before any network call. `--update-set` is **required
|
|
920
|
-
|
|
921
|
-
[#297](https://github.com/TenonHQ/Dovetail/issues/297) ships server-side
|
|
922
|
-
`update_set_sys_id` and captures the delete into the session's **current** update set
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
`
|
|
926
|
-
`
|
|
939
|
+
validated before any network call. `--update-set` is **required**, must exist and be
|
|
940
|
+
**in progress** (checked on the dry-run too). Until
|
|
941
|
+
[#297](https://github.com/TenonHQ/Dovetail/issues/297) ships server-side the op ignores
|
|
942
|
+
`update_set_sys_id` and captures the delete into the session's **current** update set, so
|
|
943
|
+
the verb pins `--update-set` as current first (refusing, nothing deleted, if the pin does
|
|
944
|
+
not read back) and then reads the DELETE row back from `sys_update_xml`. The result carries
|
|
945
|
+
`captured`, `capturedInto` and `captureState` (`in-set` / `other-set` / `none` /
|
|
946
|
+
`unverified`). Like its siblings it **refuses** schema tables (`sys_db_object` /
|
|
947
|
+
`sys_dictionary`). A failed delete call never skips the read-back. Exit codes: `0` deleted
|
|
948
|
+
and captured in the set, a table that writes no capture at all (`none`, with a note), or a
|
|
949
|
+
dry-run; `1` bad args, no such record, or an unknown/closed update set; `2` the record is
|
|
950
|
+
**still present** on read-back (including a server-refused delete), its state is unknown,
|
|
951
|
+
the pin did not take, or the DELETE landed in a different set / could not be read back.
|
|
927
952
|
|
|
928
953
|
All three verbs are exported for programmatic use:
|
|
929
954
|
|
|
@@ -941,6 +966,36 @@ var r = await setField({
|
|
|
941
966
|
console.log(r.status, r.verified); // "applied" true
|
|
942
967
|
```
|
|
943
968
|
|
|
969
|
+
### Register UI component events (sync-ux-events)
|
|
970
|
+
|
|
971
|
+
UI Builder can only map an event a component dispatches when it exists as a
|
|
972
|
+
`sys_ux_event` record **and** is listed in the component macroponent's
|
|
973
|
+
`dispatched_events`. A component deploy does not reliably create either, so
|
|
974
|
+
`sync-ux-events` reads the component's `now-ui.json` and reconciles every
|
|
975
|
+
`components.<tag>.actions[]` entry against the instance.
|
|
976
|
+
|
|
977
|
+
```bash
|
|
978
|
+
# Dry-run (the default) — per action: ok / create / link / drift / ambiguous, plus orphans
|
|
979
|
+
npx dove-sn sync-ux-events --file path/to/now-ui.json
|
|
980
|
+
|
|
981
|
+
# Apply — creates the missing events and appends them to dispatched_events
|
|
982
|
+
npx dove-sn sync-ux-events --file path/to/now-ui.json \
|
|
983
|
+
--component cadso-journey-builder --update-set <sys_id> --apply
|
|
984
|
+
```
|
|
985
|
+
|
|
986
|
+
The macroponent is the `sys_ux_macroponent` (category `component`) whose
|
|
987
|
+
`root_component` is the `sys_ux_lib_component` with that tag; events are created in
|
|
988
|
+
the macroponent's scope via the scope-aware `createRecord` op and the list edit goes
|
|
989
|
+
through `pushWithUpdateSet`, both captured into `--update-set`. The write is
|
|
990
|
+
**append-only** — existing `dispatched_events` entries are never dropped or reordered —
|
|
991
|
+
and is re-read to verify. Label/description **drift** and **orphans** (linked but no
|
|
992
|
+
longer declared) are reported, never changed; an event name carried by several records
|
|
993
|
+
is **ambiguous** and never written. Event names and tags are charset-validated before
|
|
994
|
+
they reach a query. Re-runs are no-ops. Exit codes: `0` in sync / dry-run / applied and
|
|
995
|
+
verified; `1` bad args or unreadable file; `2` an unresolved or ambiguous
|
|
996
|
+
component/event, or a write that did not verify. MCP: `sync_ux_events` (dry-run unless
|
|
997
|
+
`confirm:true`).
|
|
998
|
+
|
|
944
999
|
### Invoke an arbitrary REST operation
|
|
945
1000
|
|
|
946
1001
|
Invoke any authenticated ServiceNow REST operation — an application's own
|
|
@@ -1106,7 +1161,14 @@ failed/timeout. Programmatic: `exportUpdateSet({ updateSet, mode })`,
|
|
|
1106
1161
|
|
|
1107
1162
|
`test-flow` defaults to **validate** — a safe pre-flight (published? inputs match
|
|
1108
1163
|
declared variables?) that never runs the flow; `--execute --confirm` runs it via
|
|
1109
|
-
the
|
|
1164
|
+
the Dovetail Core `runFlow` op (`POST /api/cadso/dovetail_core/runFlow`), which
|
|
1165
|
+
ships with the Dovetail app — nothing to deploy. On a route-level 404 it falls
|
|
1166
|
+
back once to the legacy `/api/cadso/dovetail/runFlow`; `--runner <path>` is used
|
|
1167
|
+
as-is with no fallback. Add `--action` when `--sys-id` is a
|
|
1168
|
+
`sys_hub_action_type_definition` (sent as `actionSysId`). The caller needs the
|
|
1169
|
+
`admin` or `dovetail_user` role; a rejected run (400/403/404/422/500 with the
|
|
1170
|
+
op's `{ ok: false, error }`) reports `ok=false` and exits 2. Contract and source:
|
|
1171
|
+
[`resources/runFlow.md`](resources/runFlow.md).
|
|
1110
1172
|
|
|
1111
1173
|
`edit-flow` defaults to a **dry-run** diff. With `--apply`: rename/description are
|
|
1112
1174
|
written to `sys_hub_flow` through the update-set-aware API (so `--update-set` is
|
|
@@ -1198,8 +1260,8 @@ existing record), `create_record` (insert one record) and `delete_record` (delet
|
|
|
1198
1260
|
record — dry-run by default, `confirm:true` to apply, `updateSetSysId` required, the
|
|
1199
1261
|
record read back before AND after so success is only reported once it is confirmed
|
|
1200
1262
|
gone) — all read-back-verified; `set_field` / `create_record` are captured in the
|
|
1201
|
-
update set you pass, while `delete_record`
|
|
1202
|
-
until [#297](https://github.com/TenonHQ/Dovetail/issues/297) ships — `host_assets` (deploy a built
|
|
1263
|
+
update set you pass, while `delete_record` pins the set as current and reads the DELETE
|
|
1264
|
+
capture back (`captureState`) until [#297](https://github.com/TenonHQ/Dovetail/issues/297) ships — `host_assets` (deploy a built
|
|
1203
1265
|
dist/), plus the Flow Designer
|
|
1204
1266
|
tools `flow_view` (read a flow/subflow's step graph), `action_view` (read an action
|
|
1205
1267
|
type's model), `action_edit` (structurally edit a published action type — per-step
|
package/dist/cli.js
CHANGED
|
@@ -86,6 +86,7 @@ const deleteRecord_1 = require("./deleteRecord");
|
|
|
86
86
|
const invokeRest_1 = require("./invokeRest");
|
|
87
87
|
const publishApp_1 = require("./publishApp");
|
|
88
88
|
const hostAssets_1 = require("./hostAssets");
|
|
89
|
+
const uxEvents_1 = require("./uxEvents");
|
|
89
90
|
const flowDesigner_formatter_2 = require("./flowDesigner-formatter");
|
|
90
91
|
function parseArgs(argv) {
|
|
91
92
|
// `dove-sn --help` has no verb: a leading flag means an empty command and the
|
|
@@ -663,15 +664,18 @@ async function runCreateFlow(flags) {
|
|
|
663
664
|
}
|
|
664
665
|
/**
|
|
665
666
|
* dove-sn test-flow:
|
|
666
|
-
* --sys-id <sys_id> Required. sys_hub_flow sys_id (flow or subflow)
|
|
667
|
+
* --sys-id <sys_id> Required. sys_hub_flow sys_id (flow or subflow), or a
|
|
668
|
+
* sys_hub_action_type_definition sys_id with --action.
|
|
669
|
+
* --action Optional. --sys-id is an action (sent as actionSysId).
|
|
667
670
|
* --execute Optional. Actually run it (default is validate-only).
|
|
668
671
|
* --confirm Required with --execute. A deliberate run-for-real gate.
|
|
669
672
|
* --inputs <json> Optional. JSON object of inputs (or --inputs-json <path>).
|
|
670
673
|
* --json Optional. Emit the structured TestFlowResult.
|
|
671
674
|
*
|
|
672
675
|
* Default (no --execute) is a safe pre-flight: published? readable? inputs match
|
|
673
|
-
* declared variables? --execute POSTs the
|
|
674
|
-
*
|
|
676
|
+
* declared variables? --execute POSTs the Dovetail Core runFlow op
|
|
677
|
+
* (/api/cadso/dovetail_core/runFlow, legacy fallback on 404; --runner overrides
|
|
678
|
+
* with no fallback — see resources/runFlow.md). Executing can cause real side effects.
|
|
675
679
|
*/
|
|
676
680
|
async function runTestFlow(flags) {
|
|
677
681
|
var sysId = flags["sys-id"] || flags.sysId;
|
|
@@ -689,6 +693,7 @@ async function runTestFlow(flags) {
|
|
|
689
693
|
var params = {
|
|
690
694
|
client: (0, client_1.createClient)({}),
|
|
691
695
|
sysId: sysId,
|
|
696
|
+
target: flags.action === "true" ? "action" : "flow",
|
|
692
697
|
mode: flags.execute === "true" ? "execute" : "validate",
|
|
693
698
|
inputs: inputs,
|
|
694
699
|
confirm: flags.confirm === "true",
|
|
@@ -1572,7 +1577,14 @@ async function runIndexList(flags) {
|
|
|
1572
1577
|
process.stdout.write(JSON.stringify(result, null, 2) + "\n");
|
|
1573
1578
|
return 0;
|
|
1574
1579
|
}
|
|
1575
|
-
process.stdout.write(result.table +
|
|
1580
|
+
process.stdout.write(result.table +
|
|
1581
|
+
" — " +
|
|
1582
|
+
result.indexes.length +
|
|
1583
|
+
" index(es)" +
|
|
1584
|
+
(result.storageTable && result.storageTable !== result.table
|
|
1585
|
+
? " (stored in " + result.storageTable + "'s physical table — these are its indexes)"
|
|
1586
|
+
: "") +
|
|
1587
|
+
"\n");
|
|
1576
1588
|
for (var i = 0; i < result.indexes.length; i += 1) {
|
|
1577
1589
|
var idx = result.indexes[i];
|
|
1578
1590
|
process.stdout.write(" " +
|
|
@@ -2006,18 +2018,76 @@ async function runCreateRecord(flags) {
|
|
|
2006
2018
|
return 2;
|
|
2007
2019
|
return 0;
|
|
2008
2020
|
}
|
|
2021
|
+
/**
|
|
2022
|
+
* dove-sn sync-ux-events:
|
|
2023
|
+
* --file <path/to/now-ui.json>
|
|
2024
|
+
* [--component <tag>]
|
|
2025
|
+
* [--update-set <sys_id>] (required with --apply)
|
|
2026
|
+
* [--apply] [--dry-run] [--json]
|
|
2027
|
+
* DRY-RUN BY DEFAULT. Exit codes: 0 in sync / dry-run / applied+verified, 1 bad args or
|
|
2028
|
+
* unreadable file, 2 unresolved/ambiguous component or event, or an unverified write.
|
|
2029
|
+
*/
|
|
2030
|
+
async function runSyncUxEvents(flags, bare) {
|
|
2031
|
+
var bareErr = bareStringFlagError("sync-ux-events", bare);
|
|
2032
|
+
if (bareErr) {
|
|
2033
|
+
process.stderr.write(bareErr);
|
|
2034
|
+
return 1;
|
|
2035
|
+
}
|
|
2036
|
+
var file = flags.file;
|
|
2037
|
+
if (!file) {
|
|
2038
|
+
process.stderr.write("sync-ux-events: --file <now-ui.json> is required\n");
|
|
2039
|
+
return 1;
|
|
2040
|
+
}
|
|
2041
|
+
var apply = flags.apply === "true" && flags["dry-run"] !== "true";
|
|
2042
|
+
var nowUi;
|
|
2043
|
+
try {
|
|
2044
|
+
nowUi = JSON.parse(fs.readFileSync(path.resolve(file), "utf8"));
|
|
2045
|
+
}
|
|
2046
|
+
catch (err) {
|
|
2047
|
+
process.stderr.write("sync-ux-events: cannot read " + file + ": " + (err instanceof Error ? err.message : String(err)) + "\n");
|
|
2048
|
+
return 1;
|
|
2049
|
+
}
|
|
2050
|
+
var result;
|
|
2051
|
+
try {
|
|
2052
|
+
result = await (0, uxEvents_1.syncUxEvents)({
|
|
2053
|
+
client: (0, client_1.createClient)({}),
|
|
2054
|
+
nowUi: nowUi,
|
|
2055
|
+
component: flags.component,
|
|
2056
|
+
updateSetSysId: flags["update-set"],
|
|
2057
|
+
apply: apply,
|
|
2058
|
+
});
|
|
2059
|
+
}
|
|
2060
|
+
catch (err) {
|
|
2061
|
+
var message = err instanceof Error ? err.message : String(err);
|
|
2062
|
+
if (message.indexOf("ux-events: ") === 0)
|
|
2063
|
+
message = message.slice("ux-events: ".length);
|
|
2064
|
+
process.stderr.write("sync-ux-events: " + message + "\n");
|
|
2065
|
+
return 1;
|
|
2066
|
+
}
|
|
2067
|
+
if (flags.json === "true") {
|
|
2068
|
+
process.stdout.write(JSON.stringify(result, null, 2) + "\n");
|
|
2069
|
+
}
|
|
2070
|
+
else {
|
|
2071
|
+
process.stdout.write((0, uxEvents_1.formatUxEventSync)(result) + "\n");
|
|
2072
|
+
}
|
|
2073
|
+
return result.ok ? 0 : 2;
|
|
2074
|
+
}
|
|
2009
2075
|
/**
|
|
2010
2076
|
* dove-sn delete-record:
|
|
2011
2077
|
* --table x_cadso_core_metric_point_type
|
|
2012
2078
|
* --sys-id <32-hex sys_id> (the record to delete)
|
|
2013
|
-
* --update-set <sys_id> (required —
|
|
2014
|
-
* session
|
|
2079
|
+
* --update-set <sys_id> (required — must exist and be in progress; pinned as
|
|
2080
|
+
* the session's current set before the delete, and the
|
|
2081
|
+
* DELETE capture row is read back from sys_update_xml)
|
|
2015
2082
|
* [--apply] (DRY-RUN BY DEFAULT — nothing is deleted without it)
|
|
2016
2083
|
* [--dry-run] [--json] (--dry-run wins over --apply)
|
|
2017
2084
|
* Reads the record BEFORE (a missing record is an error, not a no-op delete) and AFTER
|
|
2018
2085
|
* (success is only reported once the record is confirmed gone).
|
|
2019
|
-
* Exit codes: 0 deleted/dry-run, 1 bad args
|
|
2020
|
-
* record is STILL PRESENT on read-back
|
|
2086
|
+
* Exit codes: 0 deleted (and captured in the requested set) / dry-run, 1 bad args, missing
|
|
2087
|
+
* record or unknown/closed update set, 2 the record is STILL PRESENT on read-back (including a
|
|
2088
|
+
* delete the server refused with an error), its state is unknown, the update-set pin did not
|
|
2089
|
+
* take (nothing deleted), or it was deleted but the DELETE capture landed in a different set or
|
|
2090
|
+
* could not be read back. A table with no update-set capture at all exits 0 with a note.
|
|
2021
2091
|
*/
|
|
2022
2092
|
async function runDeleteRecord(flags) {
|
|
2023
2093
|
var table = flags.table;
|
|
@@ -2063,7 +2133,15 @@ async function runDeleteRecord(flags) {
|
|
|
2063
2133
|
result.sysId +
|
|
2064
2134
|
" → update set " +
|
|
2065
2135
|
result.updateSetSysId +
|
|
2136
|
+
(result.updateSetName ? " (" + result.updateSetName + ")" : "") +
|
|
2066
2137
|
(result.verified ? " — verified gone" : "") +
|
|
2138
|
+
(result.status === "deleted"
|
|
2139
|
+
? (result.captureState === "in-set"
|
|
2140
|
+
? ", capture verified"
|
|
2141
|
+
: result.captureState === "none"
|
|
2142
|
+
? ", no update-set capture (table not recorded?)"
|
|
2143
|
+
: ", NOT captured in that set")
|
|
2144
|
+
: "") +
|
|
2067
2145
|
"\n" +
|
|
2068
2146
|
result.note +
|
|
2069
2147
|
"\n");
|
|
@@ -2073,6 +2151,12 @@ async function runDeleteRecord(flags) {
|
|
|
2073
2151
|
}
|
|
2074
2152
|
if (result.status === "failed")
|
|
2075
2153
|
return 2;
|
|
2154
|
+
// Gone but the DELETE landed in ANOTHER set (the requested set would promote without it),
|
|
2155
|
+
// or the capture could not be read back: neither may read as success to a script. A table
|
|
2156
|
+
// that is not recorded in update sets at all ("none") is a plain data delete — exit 0, and
|
|
2157
|
+
// the note says the delete will not travel.
|
|
2158
|
+
if (result.status === "deleted" && (result.captureState === "other-set" || result.captureState === "unverified"))
|
|
2159
|
+
return 2;
|
|
2076
2160
|
return 0;
|
|
2077
2161
|
}
|
|
2078
2162
|
/**
|
|
@@ -2704,6 +2788,9 @@ async function dispatch(parsed) {
|
|
|
2704
2788
|
if (parsed.command === "create-record") {
|
|
2705
2789
|
return await runCreateRecord(parsed.flags);
|
|
2706
2790
|
}
|
|
2791
|
+
if (parsed.command === "sync-ux-events") {
|
|
2792
|
+
return await runSyncUxEvents(parsed.flags, parsed.bare);
|
|
2793
|
+
}
|
|
2707
2794
|
if (parsed.command === "delete-record") {
|
|
2708
2795
|
return await runDeleteRecord(parsed.flags);
|
|
2709
2796
|
}
|
package/dist/cliUsage.js
CHANGED
|
@@ -408,7 +408,7 @@ exports.VERB_USAGE = {
|
|
|
408
408
|
{
|
|
409
409
|
flag: "update-set",
|
|
410
410
|
value: "<sys_id>",
|
|
411
|
-
note: "
|
|
411
|
+
note: "Must exist and be in progress (checked on the dry-run too); pinned as current before the delete.",
|
|
412
412
|
},
|
|
413
413
|
],
|
|
414
414
|
optional: [
|
|
@@ -420,10 +420,36 @@ exports.VERB_USAGE = {
|
|
|
420
420
|
gateNote: "The dry-run prints the record snapshot that would be deleted.",
|
|
421
421
|
example: "dove-sn delete-record --table x_cadso_core_metric_point_type --sys-id <32-hex sys_id> --update-set <sys_id> --apply",
|
|
422
422
|
notes: [
|
|
423
|
-
"Reads the record BEFORE (a missing record is an error, never a no-op delete) and AFTER (exit 2 if it is still present).",
|
|
424
|
-
"Until TenonHQ/Dovetail#297 ships server-side the delete op IGNORES --update-set and captures into the session's current update set —
|
|
423
|
+
"Reads the record BEFORE (a missing record is an error, never a no-op delete) and AFTER (exit 2 if it is still present — including when the server refused the delete with an error).",
|
|
424
|
+
"Until TenonHQ/Dovetail#297 ships server-side the delete op IGNORES --update-set and captures into the session's current update set — so the verb pins --update-set as current first (refusing, nothing deleted, if the pin does not read back) and reads the DELETE row back from sys_update_xml: exit 2 when it landed in a different set or the read-back failed. A table that writes no update-set capture at all exits 0 with a note (captureState \"none\") — that delete will not travel.",
|
|
425
425
|
],
|
|
426
426
|
},
|
|
427
|
+
"sync-ux-events": {
|
|
428
|
+
summary: "Register a UI component's now-ui.json actions as sys_ux_event records on its macroponent",
|
|
429
|
+
required: [
|
|
430
|
+
{ flag: "file", value: "<now-ui.json>", note: "Path to the component's now-ui.json; every component with `actions` is synced." },
|
|
431
|
+
],
|
|
432
|
+
optional: [
|
|
433
|
+
{ flag: "component", value: "<tag>", note: "Limit to one component tag, e.g. cadso-journey-builder." },
|
|
434
|
+
{
|
|
435
|
+
flag: "update-set",
|
|
436
|
+
value: "<sys_id>",
|
|
437
|
+
note: "Required with --apply — the event creates and the macroponent edit are captured here.",
|
|
438
|
+
},
|
|
439
|
+
{ flag: "apply", note: "Write (without it the run is a dry-run)." },
|
|
440
|
+
{ flag: "dry-run", note: "Force a dry-run even with --apply." },
|
|
441
|
+
JSON_FLAG,
|
|
442
|
+
],
|
|
443
|
+
gate: "apply",
|
|
444
|
+
gateNote: "The dry-run prints each action as ok / create / link / drift / ambiguous, plus orphans.",
|
|
445
|
+
example: "dove-sn sync-ux-events --file path/to/now-ui.json --component cadso-journey-builder --update-set <sys_id> --apply",
|
|
446
|
+
notes: [
|
|
447
|
+
"Macroponent = sys_ux_macroponent whose root_component is the sys_ux_lib_component with that tag (category component). Events are created in the macroponent's scope.",
|
|
448
|
+
"Append-only: missing events are created and their sys_ids appended to dispatched_events; existing entries are never dropped or reordered. Label/description drift and orphaned (linked but undeclared) events are reported, never changed.",
|
|
449
|
+
"Exit codes: 0 in sync / dry-run / applied and verified, 1 bad args or unreadable file, 2 unresolved or ambiguous component/event, or a write that did not verify.",
|
|
450
|
+
],
|
|
451
|
+
stringFlags: ["file", "component", "update-set"],
|
|
452
|
+
},
|
|
427
453
|
"host-assets": {
|
|
428
454
|
summary: "Deploy a built dist/ to ServiceNow (carrier sys_ui_script + attachment + m2m)",
|
|
429
455
|
required: [
|
|
@@ -568,14 +594,15 @@ exports.VERB_USAGE = {
|
|
|
568
594
|
notes: ["Exit 2 when the flow was created but the snapshot did not compile (not published)."],
|
|
569
595
|
},
|
|
570
596
|
"test-flow": {
|
|
571
|
-
summary: "Validate (default) or run a flow/subflow",
|
|
572
|
-
required: [{ flag: "sys-id", value: "<sys_id>", note: "sys_hub_flow sys_id (flow or subflow)." }],
|
|
597
|
+
summary: "Validate (default) or run a flow/subflow/action",
|
|
598
|
+
required: [{ flag: "sys-id", value: "<sys_id>", note: "sys_hub_flow sys_id (flow or subflow), or an action's with --action." }],
|
|
573
599
|
optional: [
|
|
600
|
+
{ flag: "action", note: "--sys-id is a sys_hub_action_type_definition (sent as actionSysId)." },
|
|
574
601
|
{ flag: "execute", note: "Actually run it (default is validate-only)." },
|
|
575
602
|
{ flag: "confirm", note: "Required with --execute — the deliberate run-for-real gate." },
|
|
576
603
|
{ flag: "inputs", value: "'<json>'", note: "JSON object of flow inputs." },
|
|
577
604
|
{ flag: "inputs-json", value: "<path>", note: "Same, from a file." },
|
|
578
|
-
{ flag: "runner", value: "<path>", note: "Override the
|
|
605
|
+
{ flag: "runner", value: "<path>", note: "Override the runner path (default /api/cadso/dovetail_core/runFlow; no legacy fallback when set)." },
|
|
579
606
|
JSON_FLAG,
|
|
580
607
|
],
|
|
581
608
|
gate: "execute-confirm",
|
package/dist/client.d.ts
CHANGED
|
@@ -231,4 +231,15 @@ export interface ServiceNowClient {
|
|
|
231
231
|
}) => Promise<void>;
|
|
232
232
|
};
|
|
233
233
|
}
|
|
234
|
+
/**
|
|
235
|
+
* Dovetail core Scripted REST API base (ships in the Dovetail app). Every
|
|
236
|
+
* Dovetail server op is tried here first.
|
|
237
|
+
*/
|
|
238
|
+
export declare var DOVETAIL_CORE_API_BASE: string;
|
|
239
|
+
/**
|
|
240
|
+
* Legacy global-scope Dovetail Scripted REST API base. Clients fall back to it
|
|
241
|
+
* once, on a 404 from DOVETAIL_CORE_API_BASE, for instances that predate the
|
|
242
|
+
* Dovetail app.
|
|
243
|
+
*/
|
|
244
|
+
export declare var DOVETAIL_LEGACY_API_BASE: string;
|
|
234
245
|
export declare function createClient(config?: ServiceNowClientConfig): ServiceNowClient;
|
package/dist/client.js
CHANGED
|
@@ -24,7 +24,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
24
24
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
25
25
|
};
|
|
26
26
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
27
|
-
exports.PROCESSFLOW_PATH_PREFIX = void 0;
|
|
27
|
+
exports.DOVETAIL_LEGACY_API_BASE = exports.DOVETAIL_CORE_API_BASE = exports.PROCESSFLOW_PATH_PREFIX = void 0;
|
|
28
28
|
exports.resolveFlowAuth = resolveFlowAuth;
|
|
29
29
|
exports.isProcessflowPath = isProcessflowPath;
|
|
30
30
|
exports.createClient = createClient;
|
|
@@ -134,6 +134,17 @@ function sleep(ms) {
|
|
|
134
134
|
setTimeout(resolve, ms);
|
|
135
135
|
});
|
|
136
136
|
}
|
|
137
|
+
/**
|
|
138
|
+
* Dovetail core Scripted REST API base (ships in the Dovetail app). Every
|
|
139
|
+
* Dovetail server op is tried here first.
|
|
140
|
+
*/
|
|
141
|
+
exports.DOVETAIL_CORE_API_BASE = "/api/cadso/dovetail_core/";
|
|
142
|
+
/**
|
|
143
|
+
* Legacy global-scope Dovetail Scripted REST API base. Clients fall back to it
|
|
144
|
+
* once, on a 404 from DOVETAIL_CORE_API_BASE, for instances that predate the
|
|
145
|
+
* Dovetail app.
|
|
146
|
+
*/
|
|
147
|
+
exports.DOVETAIL_LEGACY_API_BASE = "/api/cadso/dovetail/";
|
|
137
148
|
/**
|
|
138
149
|
* Match a thrown error message against the 403/404 patterns produced by request().
|
|
139
150
|
* buildAgent.* uses this to decide when to fall back to the plain Table API.
|
|
@@ -303,8 +314,8 @@ function createClient(config = {}) {
|
|
|
303
314
|
// fall back to the legacy /api/cadso/dovetail/<op> on 404 (one-time warning).
|
|
304
315
|
async function dovetailRequest(method, op, body, params, ctx) {
|
|
305
316
|
var url = useDovetailLegacyPath
|
|
306
|
-
?
|
|
307
|
-
:
|
|
317
|
+
? exports.DOVETAIL_LEGACY_API_BASE + op
|
|
318
|
+
: exports.DOVETAIL_CORE_API_BASE + op;
|
|
308
319
|
try {
|
|
309
320
|
return await request({ method: method, url: url, data: body, params: params }, ctx);
|
|
310
321
|
}
|
|
@@ -312,11 +323,11 @@ function createClient(config = {}) {
|
|
|
312
323
|
var msg = e && e.message ? String(e.message) : "";
|
|
313
324
|
if (!useDovetailLegacyPath && msg.indexOf("SN 404 on") === 0) {
|
|
314
325
|
// eslint-disable-next-line no-console
|
|
315
|
-
console.warn("[deprecation]
|
|
316
|
-
" returned 404. Falling back to legacy
|
|
326
|
+
console.warn("[deprecation] " + exports.DOVETAIL_CORE_API_BASE + op +
|
|
327
|
+
" returned 404. Falling back to legacy " + exports.DOVETAIL_LEGACY_API_BASE + op +
|
|
317
328
|
". Install the Dovetail application's Scripted REST APIs to silence this warning.");
|
|
318
329
|
useDovetailLegacyPath = true;
|
|
319
|
-
var legacyUrl =
|
|
330
|
+
var legacyUrl = exports.DOVETAIL_LEGACY_API_BASE + op;
|
|
320
331
|
return await request({ method: method, url: legacyUrl, data: body, params: params }, ctx);
|
|
321
332
|
}
|
|
322
333
|
throw e;
|
package/dist/createRecord.d.ts
CHANGED
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
* The INSERT counterpart to `set-field`.
|
|
10
10
|
*
|
|
11
11
|
* NOT for schema tables (sys_db_object / sys_dictionary) — that's
|
|
12
|
-
* create-table / add-column.
|
|
12
|
+
* create-table / add-column. NOT for sys_update_set — that's
|
|
13
|
+
* `dove createUpdateSet`. To UPDATE an existing record, use `set-field`.
|
|
13
14
|
*/
|
|
14
15
|
import type { ServiceNowClient } from "./client";
|
|
15
16
|
import type { RecordWriteResult } from "./setField";
|
package/dist/createRecord.js
CHANGED
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
* The INSERT counterpart to `set-field`.
|
|
11
11
|
*
|
|
12
12
|
* NOT for schema tables (sys_db_object / sys_dictionary) — that's
|
|
13
|
-
* create-table / add-column.
|
|
13
|
+
* create-table / add-column. NOT for sys_update_set — that's
|
|
14
|
+
* `dove createUpdateSet`. To UPDATE an existing record, use `set-field`.
|
|
14
15
|
*/
|
|
15
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
17
|
exports.createRecord = createRecord;
|
|
@@ -19,6 +20,11 @@ const setField_1 = require("./setField");
|
|
|
19
20
|
// Same guard as set-field: schema tables are never written as data — routed to
|
|
20
21
|
// the dedicated schema verbs instead so we never orphan or corrupt metadata.
|
|
21
22
|
var REFUSED_TABLES = ["sys_db_object", "sys_dictionary"];
|
|
23
|
+
// The generic createRecord op never sets `application`, so an update set
|
|
24
|
+
// inserted through it lands in the session app while the read-back (name only)
|
|
25
|
+
// still reports success. Update sets go through the scope-correct
|
|
26
|
+
// createUpdateSet op instead.
|
|
27
|
+
var UPDATE_SET_TABLE = "sys_update_set";
|
|
22
28
|
async function createRecord(params) {
|
|
23
29
|
var client = params.client || (0, client_1.createClient)({});
|
|
24
30
|
var table = params.table;
|
|
@@ -29,6 +35,11 @@ async function createRecord(params) {
|
|
|
29
35
|
throw new Error("create-record: refusing to write " + table + " as data — it is a schema table. "
|
|
30
36
|
+ "Use add-column / create-table for schema changes.");
|
|
31
37
|
}
|
|
38
|
+
if (table === UPDATE_SET_TABLE) {
|
|
39
|
+
throw new Error("create-record: refusing to create a sys_update_set record — the generic createRecord op "
|
|
40
|
+
+ "does not set its application, so the set would land in the session app. "
|
|
41
|
+
+ "Use `dove createUpdateSet --name <name> --scope <scope>` instead.");
|
|
42
|
+
}
|
|
32
43
|
var fieldNames = params.fields ? Object.keys(params.fields) : [];
|
|
33
44
|
if (fieldNames.length === 0) {
|
|
34
45
|
throw new Error("create-record: at least one field (--fields key=value) is required.");
|
package/dist/deleteRecord.d.ts
CHANGED
|
@@ -7,12 +7,15 @@
|
|
|
7
7
|
*
|
|
8
8
|
* Wraps the Dovetail core Scripted REST `deleteRecord` op. DRY-RUN BY DEFAULT:
|
|
9
9
|
* without confirm:true nothing is deleted; dryRun:true forces a dry-run even
|
|
10
|
-
* with confirm. updateSetSysId is REQUIRED and
|
|
11
|
-
*
|
|
12
|
-
* op ignores update_set_sys_id and captures into the session's
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
10
|
+
* with confirm. updateSetSysId is REQUIRED and resolved up front (it must exist
|
|
11
|
+
* and be in progress — checked on the dry-run too). Until TenonHQ/Dovetail#297
|
|
12
|
+
* ships the server op ignores update_set_sys_id and captures into the session's
|
|
13
|
+
* current update set, so the verb does what every other deleteRecord caller does:
|
|
14
|
+
* it pins the set as current (changeUpdateSet) and reads the pin back, refusing
|
|
15
|
+
* to delete when it did not take. After the delete it reads the DELETE row back
|
|
16
|
+
* from sys_update_xml and reports where it actually landed (captured /
|
|
17
|
+
* capturedInto) — the pin alone is not proof (#297 saw it not stick cross-scope).
|
|
18
|
+
* The client keeps sending the field so it takes effect once the server honours it.
|
|
16
19
|
*
|
|
17
20
|
* NOT for schema tables (sys_db_object / sys_dictionary) — dropping a table or
|
|
18
21
|
* column is a privileged lifecycle op, not a data delete.
|
|
@@ -24,9 +27,9 @@ export interface DeleteRecordParams {
|
|
|
24
27
|
/** sys_id of the record to delete — 32 lowercase hex characters. */
|
|
25
28
|
sysId: string;
|
|
26
29
|
/**
|
|
27
|
-
* Update set the delete should be captured into. Required and
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
+
* Update set the delete should be captured into. Required; must exist and be in progress.
|
|
31
|
+
* Pinned as the session's current set before the delete (the server op ignores the field
|
|
32
|
+
* until TenonHQ/Dovetail#297 ships) and verified via the sys_update_xml capture row.
|
|
30
33
|
*/
|
|
31
34
|
updateSetSysId?: string;
|
|
32
35
|
/** The delete gate: the record is only deleted when confirm is exactly true. */
|
|
@@ -43,14 +46,34 @@ export interface DeleteRecordResult {
|
|
|
43
46
|
before: Record<string, string>;
|
|
44
47
|
/** True only when the post-delete read-back confirmed the record is gone. */
|
|
45
48
|
verified: boolean;
|
|
49
|
+
/** Name of the requested update set, resolved before anything else is done. */
|
|
50
|
+
updateSetName: string;
|
|
51
|
+
/**
|
|
52
|
+
* True only when the DELETE's sys_update_xml capture row was read back IN the requested
|
|
53
|
+
* update set. A deleted record with captured:false will NOT travel with that set.
|
|
54
|
+
*/
|
|
55
|
+
captured: boolean;
|
|
56
|
+
/** The update set the DELETE capture row actually landed in; null when none was found. */
|
|
57
|
+
capturedInto: {
|
|
58
|
+
sysId: string;
|
|
59
|
+
name: string;
|
|
60
|
+
} | null;
|
|
61
|
+
/**
|
|
62
|
+
* Where the DELETE capture stands: "in-set" (captured in the requested set), "other-set"
|
|
63
|
+
* (captured, but into a different set — the requested set promotes without it),
|
|
64
|
+
* "none" (no capture row anywhere — the table is likely not recorded in update sets),
|
|
65
|
+
* "unverified" (the capture read-back failed), or "n/a" (dry-run / nothing deleted).
|
|
66
|
+
*/
|
|
67
|
+
captureState: "in-set" | "other-set" | "none" | "unverified" | "n/a";
|
|
46
68
|
note: string;
|
|
47
69
|
}
|
|
48
70
|
export declare var TABLE_NAME_PATTERN: RegExp;
|
|
49
71
|
export declare var SYS_ID_PATTERN: RegExp;
|
|
50
72
|
/**
|
|
51
|
-
* Appended to
|
|
52
|
-
* yet honour update_set_sys_id, so the
|
|
53
|
-
*
|
|
73
|
+
* Appended to the dry-run and deleted notes while TenonHQ/Dovetail#297 is open: the server op
|
|
74
|
+
* does not yet honour update_set_sys_id, so the delete is captured into the session's current
|
|
75
|
+
* update set — which is why the verb pins it first and reads the capture row back. Revisit
|
|
76
|
+
* (and its test) when #297 ships.
|
|
54
77
|
*/
|
|
55
78
|
export declare var UPDATE_SET_CAVEAT: string;
|
|
56
79
|
export declare function validateDeleteTable(table: unknown): string;
|