@tenonhq/dovetail-servicenow 0.0.29 → 0.0.31
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 +43 -2
- package/dist/cli.js +159 -10
- package/dist/fieldsFromJson.d.ts +22 -0
- package/dist/fieldsFromJson.js +107 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +13 -1
- package/dist/mcp/registry.d.ts +1 -1
- package/dist/mcp/registry.js +35 -0
- package/dist/mcp/schemas.d.ts +25 -0
- package/dist/mcp/schemas.js +14 -1
- package/dist/publishApp.d.ts +173 -0
- package/dist/publishApp.js +664 -0
- package/dist/table/formSession.d.ts +8 -0
- package/dist/table/formSession.js +12 -4
- package/dist/table/index.d.ts +1 -1
- package/dist/table/index.js +2 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -406,6 +406,45 @@ Programmatic: `invokeRest({ method, path, body, confirm })` is exported, and the
|
|
|
406
406
|
client gained `now.put` / `now.delete` / `now.invoke` (the latter returns
|
|
407
407
|
`{ status, body }` verbatim) alongside the existing `now.get` / `now.post`.
|
|
408
408
|
|
|
409
|
+
### Publish an app to the Store / application repository
|
|
410
|
+
|
|
411
|
+
Publish a scoped application to the **ServiceNow Store**, the **company
|
|
412
|
+
application repository**, or both — headlessly, with the publish's progress
|
|
413
|
+
tracker polled to completion.
|
|
414
|
+
|
|
415
|
+
```bash
|
|
416
|
+
# Dry-run — the DEFAULT: resolves the app, prints the plan, publishes NOTHING
|
|
417
|
+
npx dove-sn publish-app --app x_cadso_filter --version 6.0.20260716 --target both
|
|
418
|
+
|
|
419
|
+
# Publish for real (store, then repo, same version)
|
|
420
|
+
npx dove-sn publish-app --app x_cadso_filter --version 6.0.20260716 \
|
|
421
|
+
--target both --dev-notes "July release" --confirm --json
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
**Store publish is EXTERNALLY VISIBLE on the ServiceNow Store — treat
|
|
425
|
+
`--target store --confirm` as a release.** `publish-app` is dry-run by default:
|
|
426
|
+
without `--confirm` it prints the resolved plan and exits `1` (a deliberate
|
|
427
|
+
refusal). `--target both` runs store then repo sequentially and short-circuits
|
|
428
|
+
if the store leg fails.
|
|
429
|
+
|
|
430
|
+
The two targets ride different transports:
|
|
431
|
+
|
|
432
|
+
- **store** replays the `sys_app` form's upload flow (`xmlhttp.do` +
|
|
433
|
+
`sn_appauthor.ScopedAppUploaderAJAX`) over a form-login session — basic auth
|
|
434
|
+
alone no-ops there. It needs the Store account credentials in the loaded env
|
|
435
|
+
file as `SN_STORE_USERNAME` / `SN_STORE_PASSWORD`. The password is **never
|
|
436
|
+
accepted as a flag** and never appears in output, dry-run previews included.
|
|
437
|
+
- **repo** uses the supported CI/CD REST API (`POST /api/sn_cicd/app_repo/publish`
|
|
438
|
+
+ `GET /api/sn_cicd/progress/{id}`) over basic auth. The API user needs the
|
|
439
|
+
`sn_cicd` role (or admin).
|
|
440
|
+
|
|
441
|
+
`--app` accepts a scope name, `sys_app` sys_id, or app name; `--version` must be
|
|
442
|
+
above the currently published version. The result carries the progress-tracker
|
|
443
|
+
id, per-step states ("Packaging application", "Uploading application"), the
|
|
444
|
+
Store `appLink`, and the publish's update-set sys_id where the instance reports
|
|
445
|
+
one. Exit codes: `0` published or dry-run, `1` bad args/unconfirmed, `2`
|
|
446
|
+
failed/timeout. Programmatic: `publishApp({ app, version, target, confirm })`.
|
|
447
|
+
|
|
409
448
|
`test-flow` defaults to **validate** — a safe pre-flight (published? inputs match
|
|
410
449
|
declared variables?) that never runs the flow; `--execute --confirm` runs it via
|
|
411
450
|
the server-side FlowAPI runner (deploy `resources/runFlow.md` first).
|
|
@@ -488,8 +527,10 @@ publish is read back and verified), `flow_publish` (compile a flow/subflow snaps
|
|
|
488
527
|
publish, grafting a template), `flow_test` (validate or run a flow), and
|
|
489
528
|
`flow_edit` (patch a flow), plus `invoke_rest` (invoke an arbitrary authenticated
|
|
490
529
|
REST operation — Scripted REST included — with GET/POST/PUT/DELETE; dry-run by
|
|
491
|
-
default, response passed through verbatim, bodies never logged)
|
|
492
|
-
|
|
530
|
+
default, response passed through verbatim, bodies never logged) and `app_publish`
|
|
531
|
+
(publish a scoped app to the ServiceNow Store and/or the application repository;
|
|
532
|
+
dry-run by default, Store credentials env-only — they never transit tool
|
|
533
|
+
arguments). It reads ServiceNow credentials from the same env vars as the CLI.
|
|
493
534
|
|
|
494
535
|
```bash
|
|
495
536
|
npx dove-sn mcp --smoke # list the registered tools and exit
|
package/dist/cli.js
CHANGED
|
@@ -60,6 +60,7 @@ const fs = __importStar(require("fs"));
|
|
|
60
60
|
const path = __importStar(require("path"));
|
|
61
61
|
const loadEnv_1 = require("./loadEnv");
|
|
62
62
|
const client_1 = require("./client");
|
|
63
|
+
const fieldsFromJson_1 = require("./fieldsFromJson");
|
|
63
64
|
const choices_1 = require("./choices");
|
|
64
65
|
const formatter_1 = require("./formatter");
|
|
65
66
|
const views_1 = require("./layout/views");
|
|
@@ -82,6 +83,7 @@ const table_1 = require("./table");
|
|
|
82
83
|
const setField_1 = require("./setField");
|
|
83
84
|
const createRecord_1 = require("./createRecord");
|
|
84
85
|
const invokeRest_1 = require("./invokeRest");
|
|
86
|
+
const publishApp_1 = require("./publishApp");
|
|
85
87
|
const hostAssets_1 = require("./hostAssets");
|
|
86
88
|
const flowDesigner_formatter_2 = require("./flowDesigner-formatter");
|
|
87
89
|
function parseArgs(argv) {
|
|
@@ -897,11 +899,12 @@ function printHelp() {
|
|
|
897
899
|
" DRY-RUN BY DEFAULT — nothing is sent without --confirm\n" +
|
|
898
900
|
" (--method <GET|POST|PUT|DELETE> --path /api/<scope>/<service>/<resource>\n" +
|
|
899
901
|
" [--body '<json>' | --body-json <path>] [--confirm] [--dry-run] [--json])\n" +
|
|
900
|
-
" set-field Set
|
|
901
|
-
|
|
902
|
-
|
|
902
|
+
" set-field Set field value(s) on an EXISTING record, into an update set, then verify\n" +
|
|
903
|
+
" (--table <t> --sys-id <id>|--query <q> --update-set <sys_id>\n" +
|
|
904
|
+
' (--fields "k=v,k2=v2" | --from-json <path>) [--dry-run] [--json])\n' +
|
|
903
905
|
" create-record Create ONE NEW record in a data table, into an update set, then verify\n" +
|
|
904
|
-
|
|
906
|
+
" (--table <t> --scope <s> --update-set <sys_id>\n" +
|
|
907
|
+
' (--fields "k=v,k2=v2" | --from-json <path>)\n' +
|
|
905
908
|
" [--if-absent <encoded-query>] [--dry-run] [--json])\n" +
|
|
906
909
|
" host-assets Deploy a built dist/ to ServiceNow (carrier sys_ui_script + attachment + m2m)\n" +
|
|
907
910
|
" (--dir <dist> --app <sys_id> --scope <namespace>\n" +
|
|
@@ -916,6 +919,15 @@ function printHelp() {
|
|
|
916
919
|
" [--script-input <name>] [--update-set <sys_id>] [--apply] [--json])\n" +
|
|
917
920
|
" edit-flow Patch a flow/subflow (rename, description, step inputs)\n" +
|
|
918
921
|
" (--sys-id <sys_id> --from-json <ops.json> [--apply] [--update-set <sys_id>] [--scope <sys_id>] [--json])\n" +
|
|
922
|
+
" publish-app Publish a scoped app to the ServiceNow Store and/or the company\n" +
|
|
923
|
+
" application repository, then poll the publish to completion.\n" +
|
|
924
|
+
" STORE PUBLISH IS EXTERNALLY VISIBLE on the ServiceNow Store.\n" +
|
|
925
|
+
" DRY-RUN BY DEFAULT — nothing is published without --confirm\n" +
|
|
926
|
+
" (--app <scope|sys_id|name> --version <v> --target store|repo|both\n" +
|
|
927
|
+
" [--dev-notes <text>] [--store-user <email>] [--timeout-ms <n>]\n" +
|
|
928
|
+
" [--dry-run] [--json] [--confirm])\n" +
|
|
929
|
+
" Store creds: SN_STORE_USERNAME/SN_STORE_PASSWORD in the --env file;\n" +
|
|
930
|
+
" the password is never a flag. Repo publish needs the sn_cicd role.\n" +
|
|
919
931
|
" mcp Run the MCP stdio server (--smoke lists tools and exits)\n" +
|
|
920
932
|
"\nGlobal flags:\n" +
|
|
921
933
|
" --env <path> Load credentials from a specific .env file (also --env-file,\n" +
|
|
@@ -1228,24 +1240,48 @@ function parseFieldsInline(input) {
|
|
|
1228
1240
|
}
|
|
1229
1241
|
return out;
|
|
1230
1242
|
}
|
|
1243
|
+
/**
|
|
1244
|
+
* Merge the two field sources for the record-write verbs: inline `--fields "k=v,k2=v2"`
|
|
1245
|
+
* and `--from-json <path>` (a JSON object — the only form that can carry a large or
|
|
1246
|
+
* multiline value, since the inline form splits on commas and trims). On a key present in
|
|
1247
|
+
* both, `--from-json` wins: it is the explicit spec file. Throws on an unreadable file,
|
|
1248
|
+
* malformed JSON, or a non-scalar value (surfaced by the caller as a bad-args error).
|
|
1249
|
+
*/
|
|
1250
|
+
function mergeFields(flags) {
|
|
1251
|
+
var inline = parseFieldsInline(flags.fields || "");
|
|
1252
|
+
if (!flags["from-json"])
|
|
1253
|
+
return inline;
|
|
1254
|
+
return Object.assign(inline, (0, fieldsFromJson_1.readFieldsFromJsonFile)(flags["from-json"]));
|
|
1255
|
+
}
|
|
1231
1256
|
/**
|
|
1232
1257
|
* dove-sn set-field:
|
|
1233
1258
|
* --table x_cadso_core_metric_point_type
|
|
1234
1259
|
* --sys-id <id> | --query "name=send_size" (query must resolve to exactly 1 row)
|
|
1235
|
-
* --fields "order=20"
|
|
1260
|
+
* [--fields "order=20"] (comma-separated key=value pairs)
|
|
1261
|
+
* [--from-json <path>] (JSON { field: value }; carries large or
|
|
1262
|
+
* multiline values the inline form can't;
|
|
1263
|
+
* overrides --fields on a shared key)
|
|
1264
|
+
* — at least one of --fields / --from-json is required
|
|
1236
1265
|
* --update-set <sys_id> (required — the change is captured here)
|
|
1237
1266
|
* [--dry-run] [--json]
|
|
1238
1267
|
* Exit codes: 0 applied/dry-run, 1 bad args, 2 write landed but read-back unverified.
|
|
1239
1268
|
*/
|
|
1240
1269
|
async function runSetField(flags) {
|
|
1241
1270
|
var table = flags.table;
|
|
1242
|
-
var fields
|
|
1271
|
+
var fields;
|
|
1272
|
+
try {
|
|
1273
|
+
fields = mergeFields(flags);
|
|
1274
|
+
}
|
|
1275
|
+
catch (err) {
|
|
1276
|
+
process.stderr.write("set-field: " + (err instanceof Error ? err.message : String(err)) + "\n");
|
|
1277
|
+
return 1;
|
|
1278
|
+
}
|
|
1243
1279
|
var hasTarget = Boolean(flags["sys-id"] || flags.query);
|
|
1244
1280
|
if (!table ||
|
|
1245
1281
|
Object.keys(fields).length === 0 ||
|
|
1246
1282
|
!hasTarget ||
|
|
1247
1283
|
!flags["update-set"]) {
|
|
1248
|
-
process.stderr.write('set-field: --table,
|
|
1284
|
+
process.stderr.write('set-field: --table, one of --sys-id/--query, --update-set, and at least one field (--fields "k=v" or --from-json <path>) are required\n');
|
|
1249
1285
|
return 1;
|
|
1250
1286
|
}
|
|
1251
1287
|
var params = {
|
|
@@ -1285,7 +1321,10 @@ async function runSetField(flags) {
|
|
|
1285
1321
|
/**
|
|
1286
1322
|
* dove-sn create-record:
|
|
1287
1323
|
* --table x_cadso_core_metric_point_type
|
|
1288
|
-
* --fields "name=avg_message_parts,label=Avg. Message Parts,order=35"
|
|
1324
|
+
* [--fields "name=avg_message_parts,label=Avg. Message Parts,order=35"]
|
|
1325
|
+
* [--from-json <path>] (JSON { field: value }; carries large or
|
|
1326
|
+
* multiline values the inline form can't)
|
|
1327
|
+
* — at least one of --fields / --from-json is required
|
|
1289
1328
|
* --scope x_cadso_core (the app that owns the new record)
|
|
1290
1329
|
* --update-set <sys_id> (required — the insert is captured here)
|
|
1291
1330
|
* [--if-absent "name=avg_message_parts"] (skip the insert when this query already matches)
|
|
@@ -1295,12 +1334,21 @@ async function runSetField(flags) {
|
|
|
1295
1334
|
*/
|
|
1296
1335
|
async function runCreateRecord(flags) {
|
|
1297
1336
|
var table = flags.table;
|
|
1298
|
-
var fields
|
|
1337
|
+
var fields;
|
|
1338
|
+
try {
|
|
1339
|
+
fields = mergeFields(flags);
|
|
1340
|
+
}
|
|
1341
|
+
catch (err) {
|
|
1342
|
+
process.stderr.write("create-record: " +
|
|
1343
|
+
(err instanceof Error ? err.message : String(err)) +
|
|
1344
|
+
"\n");
|
|
1345
|
+
return 1;
|
|
1346
|
+
}
|
|
1299
1347
|
if (!table ||
|
|
1300
1348
|
Object.keys(fields).length === 0 ||
|
|
1301
1349
|
!flags.scope ||
|
|
1302
1350
|
!flags["update-set"]) {
|
|
1303
|
-
process.stderr.write('create-record: --table, --fields "k=v"
|
|
1351
|
+
process.stderr.write('create-record: --table, --scope, --update-set, and at least one field (--fields "k=v" or --from-json <path>) are required\n');
|
|
1304
1352
|
return 1;
|
|
1305
1353
|
}
|
|
1306
1354
|
var params = {
|
|
@@ -1473,6 +1521,104 @@ async function runInvokeRest(flags) {
|
|
|
1473
1521
|
}
|
|
1474
1522
|
return 0;
|
|
1475
1523
|
}
|
|
1524
|
+
/**
|
|
1525
|
+
* dove-sn publish-app:
|
|
1526
|
+
* --app <scope|sys_id|name> Required. The sys_app to publish.
|
|
1527
|
+
* --version <v> Required. Version to publish (e.g. 6.0.20260716).
|
|
1528
|
+
* --target store|repo|both Required. STORE PUBLISH IS EXTERNALLY VISIBLE.
|
|
1529
|
+
* [--dev-notes <text>] Optional developer notes.
|
|
1530
|
+
* [--store-user <email>] Store account email (else SN_STORE_USERNAME).
|
|
1531
|
+
* The password comes ONLY from SN_STORE_PASSWORD —
|
|
1532
|
+
* there is no flag for it, ever.
|
|
1533
|
+
* [--timeout-ms <n>] Progress-poll budget (default 120000).
|
|
1534
|
+
* [--dry-run] [--json] [--confirm]
|
|
1535
|
+
*
|
|
1536
|
+
* DRY-RUN unless --confirm: without it the resolved plan is printed and the
|
|
1537
|
+
* command exits 1 (a deliberate refusal, not success). --target both publishes
|
|
1538
|
+
* store then repo sequentially with the same version and short-circuits if the
|
|
1539
|
+
* store leg fails; --json emits an array of per-target results.
|
|
1540
|
+
* Exit codes: 0 published/dry-run, 1 bad args/unconfirmed, 2 failed/timeout.
|
|
1541
|
+
*/
|
|
1542
|
+
async function runPublishApp(flags) {
|
|
1543
|
+
var app = flags.app;
|
|
1544
|
+
var version = flags.version;
|
|
1545
|
+
var target = flags.target;
|
|
1546
|
+
if (!app || !version || !target) {
|
|
1547
|
+
process.stderr.write("publish-app: --app, --version and --target store|repo|both are required\n");
|
|
1548
|
+
return 1;
|
|
1549
|
+
}
|
|
1550
|
+
if (target !== "store" && target !== "repo" && target !== "both") {
|
|
1551
|
+
process.stderr.write("publish-app: --target must be store, repo or both (got '" +
|
|
1552
|
+
target +
|
|
1553
|
+
"')\n");
|
|
1554
|
+
return 1;
|
|
1555
|
+
}
|
|
1556
|
+
var targets = target === "both" ? ["store", "repo"] : [target];
|
|
1557
|
+
var dryRun = flags["dry-run"] === "true";
|
|
1558
|
+
var confirmed = flags.confirm === "true";
|
|
1559
|
+
var client = (0, client_1.createClient)({});
|
|
1560
|
+
var results = [];
|
|
1561
|
+
var exitCode = 0;
|
|
1562
|
+
for (var i = 0; i < targets.length; i += 1) {
|
|
1563
|
+
var params = {
|
|
1564
|
+
client: client,
|
|
1565
|
+
app: app,
|
|
1566
|
+
version: version,
|
|
1567
|
+
target: targets[i],
|
|
1568
|
+
confirm: confirmed,
|
|
1569
|
+
dryRun: dryRun,
|
|
1570
|
+
};
|
|
1571
|
+
if (flags["dev-notes"])
|
|
1572
|
+
params.devNotes = flags["dev-notes"];
|
|
1573
|
+
if (flags["store-user"])
|
|
1574
|
+
params.storeUsername = flags["store-user"];
|
|
1575
|
+
if (flags["timeout-ms"]) {
|
|
1576
|
+
// A NaN timeout would make the poll-loop budget check always false —
|
|
1577
|
+
// an infinite loop. Validate here, exit 1 on garbage.
|
|
1578
|
+
var timeoutMs = Number(flags["timeout-ms"]);
|
|
1579
|
+
if (!Number.isInteger(timeoutMs) || timeoutMs <= 0) {
|
|
1580
|
+
process.stderr.write("publish-app: --timeout-ms must be a positive integer (got '" +
|
|
1581
|
+
flags["timeout-ms"] +
|
|
1582
|
+
"')\n");
|
|
1583
|
+
return 1;
|
|
1584
|
+
}
|
|
1585
|
+
params.timeoutMs = timeoutMs;
|
|
1586
|
+
}
|
|
1587
|
+
var result = await (0, publishApp_1.publishApp)(params);
|
|
1588
|
+
results.push(result);
|
|
1589
|
+
if (flags.json !== "true") {
|
|
1590
|
+
process.stdout.write("[" +
|
|
1591
|
+
result.status +
|
|
1592
|
+
"] " +
|
|
1593
|
+
result.target +
|
|
1594
|
+
" — " +
|
|
1595
|
+
result.appName +
|
|
1596
|
+
" (" +
|
|
1597
|
+
result.appScope +
|
|
1598
|
+
") v" +
|
|
1599
|
+
result.version +
|
|
1600
|
+
(result.appLink ? " — " + result.appLink : "") +
|
|
1601
|
+
(result.updateSetSysId
|
|
1602
|
+
? " — update set " + result.updateSetSysId
|
|
1603
|
+
: "") +
|
|
1604
|
+
"\n" +
|
|
1605
|
+
result.note +
|
|
1606
|
+
"\n");
|
|
1607
|
+
}
|
|
1608
|
+
if (result.status === "failed" || result.status === "timeout") {
|
|
1609
|
+
exitCode = 2;
|
|
1610
|
+
break; // Short-circuit: never repo-publish after a failed store leg.
|
|
1611
|
+
}
|
|
1612
|
+
}
|
|
1613
|
+
if (flags.json === "true") {
|
|
1614
|
+
process.stdout.write(JSON.stringify(results, null, 2) + "\n");
|
|
1615
|
+
}
|
|
1616
|
+
if (exitCode === 0 && !dryRun && !confirmed) {
|
|
1617
|
+
process.stderr.write("publish-app: refusing to publish without --confirm (the plan above is a dry-run).\n");
|
|
1618
|
+
return 1;
|
|
1619
|
+
}
|
|
1620
|
+
return exitCode;
|
|
1621
|
+
}
|
|
1476
1622
|
async function main() {
|
|
1477
1623
|
var parsed = parseArgs(process.argv.slice(2));
|
|
1478
1624
|
// Load credentials before any command runs. `--env`/`--env-file` (or the
|
|
@@ -1547,6 +1693,9 @@ async function main() {
|
|
|
1547
1693
|
await runSetRelatedLists(parsed.flags);
|
|
1548
1694
|
return 0;
|
|
1549
1695
|
}
|
|
1696
|
+
if (parsed.command === "publish-app") {
|
|
1697
|
+
return await runPublishApp(parsed.flags);
|
|
1698
|
+
}
|
|
1550
1699
|
if (parsed.command === "mcp") {
|
|
1551
1700
|
return await runMcp(parsed.flags);
|
|
1552
1701
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `--from-json` field-map loader for the record-write verbs (set-field, create-record).
|
|
3
|
+
*
|
|
4
|
+
* The inline `--fields "k=v,k2=v2"` form splits on commas and trims each piece — so it
|
|
5
|
+
* cannot carry a value that contains a comma, a newline, an `=`, or leading/trailing
|
|
6
|
+
* whitespace. That rules out every large field: a script body, an HTML/XML/CSS field, a
|
|
7
|
+
* JSON blob. `--from-json <path>` closes that gap: the file is a flat JSON object of
|
|
8
|
+
* `field → value`, and JSON quoting carries any character faithfully.
|
|
9
|
+
*
|
|
10
|
+
* Values may be string | number | boolean. Numbers/booleans are stringified — matching
|
|
11
|
+
* the inline parser and the wire format, where ServiceNow scalar fields are strings.
|
|
12
|
+
* `null`/`undefined` values are skipped. Nested objects and arrays are rejected: a scalar
|
|
13
|
+
* field cannot take one, and silently JSON-encoding it would write garbage into the record.
|
|
14
|
+
*/
|
|
15
|
+
export declare function coerceFieldsFromJson(parsed: unknown): Record<string, string>;
|
|
16
|
+
/**
|
|
17
|
+
* Read + parse a `--from-json` file into a validated field map. The path is resolved
|
|
18
|
+
* against the process cwd, matching the other `--from-json` consumers in the CLI.
|
|
19
|
+
* Throws a clean, prefixed message on a missing file or malformed JSON so the caller can
|
|
20
|
+
* surface it as a bad-args error rather than an uncaught stack trace.
|
|
21
|
+
*/
|
|
22
|
+
export declare function readFieldsFromJsonFile(filePath: string): Record<string, string>;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.coerceFieldsFromJson = coerceFieldsFromJson;
|
|
37
|
+
exports.readFieldsFromJsonFile = readFieldsFromJsonFile;
|
|
38
|
+
const fs = __importStar(require("fs"));
|
|
39
|
+
const path = __importStar(require("path"));
|
|
40
|
+
/**
|
|
41
|
+
* `--from-json` field-map loader for the record-write verbs (set-field, create-record).
|
|
42
|
+
*
|
|
43
|
+
* The inline `--fields "k=v,k2=v2"` form splits on commas and trims each piece — so it
|
|
44
|
+
* cannot carry a value that contains a comma, a newline, an `=`, or leading/trailing
|
|
45
|
+
* whitespace. That rules out every large field: a script body, an HTML/XML/CSS field, a
|
|
46
|
+
* JSON blob. `--from-json <path>` closes that gap: the file is a flat JSON object of
|
|
47
|
+
* `field → value`, and JSON quoting carries any character faithfully.
|
|
48
|
+
*
|
|
49
|
+
* Values may be string | number | boolean. Numbers/booleans are stringified — matching
|
|
50
|
+
* the inline parser and the wire format, where ServiceNow scalar fields are strings.
|
|
51
|
+
* `null`/`undefined` values are skipped. Nested objects and arrays are rejected: a scalar
|
|
52
|
+
* field cannot take one, and silently JSON-encoding it would write garbage into the record.
|
|
53
|
+
*/
|
|
54
|
+
function coerceFieldsFromJson(parsed) {
|
|
55
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
56
|
+
throw new Error("--from-json must contain a JSON object of field → value pairs");
|
|
57
|
+
}
|
|
58
|
+
var obj = parsed;
|
|
59
|
+
var out = {};
|
|
60
|
+
var keys = Object.keys(obj);
|
|
61
|
+
for (var i = 0; i < keys.length; i += 1) {
|
|
62
|
+
var key = keys[i];
|
|
63
|
+
var value = obj[key];
|
|
64
|
+
if (value === null || value === undefined)
|
|
65
|
+
continue;
|
|
66
|
+
var valueType = typeof value;
|
|
67
|
+
if (valueType === "string") {
|
|
68
|
+
out[key] = value;
|
|
69
|
+
}
|
|
70
|
+
else if (valueType === "number" || valueType === "boolean") {
|
|
71
|
+
out[key] = String(value);
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
throw new Error("--from-json field '" +
|
|
75
|
+
key +
|
|
76
|
+
"' must be a string, number, or boolean (got " +
|
|
77
|
+
valueType +
|
|
78
|
+
")");
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return out;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Read + parse a `--from-json` file into a validated field map. The path is resolved
|
|
85
|
+
* against the process cwd, matching the other `--from-json` consumers in the CLI.
|
|
86
|
+
* Throws a clean, prefixed message on a missing file or malformed JSON so the caller can
|
|
87
|
+
* surface it as a bad-args error rather than an uncaught stack trace.
|
|
88
|
+
*/
|
|
89
|
+
function readFieldsFromJsonFile(filePath) {
|
|
90
|
+
var raw;
|
|
91
|
+
try {
|
|
92
|
+
raw = fs.readFileSync(path.resolve(filePath), "utf8");
|
|
93
|
+
}
|
|
94
|
+
catch (err) {
|
|
95
|
+
var readMsg = err instanceof Error ? err.message : String(err);
|
|
96
|
+
throw new Error("--from-json: cannot read '" + filePath + "': " + readMsg);
|
|
97
|
+
}
|
|
98
|
+
var parsed;
|
|
99
|
+
try {
|
|
100
|
+
parsed = JSON.parse(raw);
|
|
101
|
+
}
|
|
102
|
+
catch (err) {
|
|
103
|
+
var parseMsg = err instanceof Error ? err.message : String(err);
|
|
104
|
+
throw new Error("--from-json: '" + filePath + "' is not valid JSON: " + parseMsg);
|
|
105
|
+
}
|
|
106
|
+
return coerceFieldsFromJson(parsed);
|
|
107
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -27,3 +27,5 @@ export type { RecordWriteResult, SetFieldParams, SetFieldResult, } from "./setFi
|
|
|
27
27
|
export type { CreateRecordParams, CreateRecordResult } from "./createRecord";
|
|
28
28
|
export { invokeRest, INVOKE_REST_METHODS } from "./invokeRest";
|
|
29
29
|
export type { InvokeRestParams, InvokeRestResult } from "./invokeRest";
|
|
30
|
+
export { publishApp, buildStartFields, parseXmlAnswer, parseProgressTree, classifyProgress, flattenSteps, harvestProgressResults, parseCicdPublishResponse, parseCicdProgress, DEFAULT_PUBLISH_TIMEOUT_MS, PUBLISH_POLL_DELAYS_MS, } from "./publishApp";
|
|
31
|
+
export type { PublishAppParams, PublishAppResult, PublishTarget, PublishStep, ProgressNode, CicdProgress, PublishTransport, } from "./publishApp";
|
package/dist/index.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
9
|
exports.addColumn = exports.DEFAULT_SAVE_ACTION = exports.DEFAULT_SUPER_CLASS = exports.TYPE_MAP = exports.defaultAccessFlags = exports.applyTableSaveOverlay = exports.resolveType = exports.normalizeColumns = exports.buildColumnXml = exports.projectTableGraph = exports.createTable = exports.WriteOrderError = exports.executeWritePlan = exports.topoSort = exports.generateSysId = exports.DEFAULT_RUN_FLOW_PATH = exports.testFlow = exports.editFlow = exports.buildPublishModel = exports.createFlow = exports.copyFlow = exports.publishFlow = exports.readActionType = exports.readFlow = exports.formatStepPill = exports.summarizeSteps = exports.verifySteps = exports.applyStepOps = exports.editActionType = exports.publishActionType = exports.triggerPublication = exports.cloneActionType = exports.cloneSubflow = exports.verifyArtifact = exports.listTemplates = exports.sincPlugin = exports.formatCreateViewResult = exports.formatLayoutResult = exports.setRelatedLists = exports.setFormLayout = exports.setListLayout = exports.createView = exports.formatAddChoicesResult = exports.formatHostAssetsResult = exports.classifyChunks = exports.hostAssets = exports.addChoicesToField = exports.resolveConfigFromEnvFile = exports.createClientFromEnvFile = exports.createClient = void 0;
|
|
10
|
-
exports.INVOKE_REST_METHODS = exports.invokeRest = exports.createRecord = exports.setField = exports.toStoredValue = exports.resolveAttributes = exports.setColumn = exports.deriveElement = void 0;
|
|
10
|
+
exports.PUBLISH_POLL_DELAYS_MS = exports.DEFAULT_PUBLISH_TIMEOUT_MS = exports.parseCicdProgress = exports.parseCicdPublishResponse = exports.harvestProgressResults = exports.flattenSteps = exports.classifyProgress = exports.parseProgressTree = exports.parseXmlAnswer = exports.buildStartFields = exports.publishApp = exports.INVOKE_REST_METHODS = exports.invokeRest = exports.createRecord = exports.setField = exports.toStoredValue = exports.resolveAttributes = exports.setColumn = exports.deriveElement = void 0;
|
|
11
11
|
var client_1 = require("./client");
|
|
12
12
|
Object.defineProperty(exports, "createClient", { enumerable: true, get: function () { return client_1.createClient; } });
|
|
13
13
|
var createClientFromEnvFile_1 = require("./createClientFromEnvFile");
|
|
@@ -82,3 +82,15 @@ Object.defineProperty(exports, "createRecord", { enumerable: true, get: function
|
|
|
82
82
|
var invokeRest_1 = require("./invokeRest");
|
|
83
83
|
Object.defineProperty(exports, "invokeRest", { enumerable: true, get: function () { return invokeRest_1.invokeRest; } });
|
|
84
84
|
Object.defineProperty(exports, "INVOKE_REST_METHODS", { enumerable: true, get: function () { return invokeRest_1.INVOKE_REST_METHODS; } });
|
|
85
|
+
var publishApp_1 = require("./publishApp");
|
|
86
|
+
Object.defineProperty(exports, "publishApp", { enumerable: true, get: function () { return publishApp_1.publishApp; } });
|
|
87
|
+
Object.defineProperty(exports, "buildStartFields", { enumerable: true, get: function () { return publishApp_1.buildStartFields; } });
|
|
88
|
+
Object.defineProperty(exports, "parseXmlAnswer", { enumerable: true, get: function () { return publishApp_1.parseXmlAnswer; } });
|
|
89
|
+
Object.defineProperty(exports, "parseProgressTree", { enumerable: true, get: function () { return publishApp_1.parseProgressTree; } });
|
|
90
|
+
Object.defineProperty(exports, "classifyProgress", { enumerable: true, get: function () { return publishApp_1.classifyProgress; } });
|
|
91
|
+
Object.defineProperty(exports, "flattenSteps", { enumerable: true, get: function () { return publishApp_1.flattenSteps; } });
|
|
92
|
+
Object.defineProperty(exports, "harvestProgressResults", { enumerable: true, get: function () { return publishApp_1.harvestProgressResults; } });
|
|
93
|
+
Object.defineProperty(exports, "parseCicdPublishResponse", { enumerable: true, get: function () { return publishApp_1.parseCicdPublishResponse; } });
|
|
94
|
+
Object.defineProperty(exports, "parseCicdProgress", { enumerable: true, get: function () { return publishApp_1.parseCicdProgress; } });
|
|
95
|
+
Object.defineProperty(exports, "DEFAULT_PUBLISH_TIMEOUT_MS", { enumerable: true, get: function () { return publishApp_1.DEFAULT_PUBLISH_TIMEOUT_MS; } });
|
|
96
|
+
Object.defineProperty(exports, "PUBLISH_POLL_DELAYS_MS", { enumerable: true, get: function () { return publishApp_1.PUBLISH_POLL_DELAYS_MS; } });
|
package/dist/mcp/registry.d.ts
CHANGED
|
@@ -10,7 +10,7 @@ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
10
10
|
import { z } from "zod";
|
|
11
11
|
import type { ToolAnnotations } from "@tenonhq/dovetail-mcp-kit";
|
|
12
12
|
import type { ServiceNowClient } from "../client";
|
|
13
|
-
export declare var TOOL_NAMES: readonly ["create_view", "set_list_layout", "set_form_layout", "set_related_lists", "add_choices_to_field", "flow_view", "action_view", "action_edit", "flow_publish", "flow_copy", "flow_create", "flow_test", "flow_edit", "create_table", "add_column", "set_column", "set_field", "create_record", "host_assets", "invoke_rest"];
|
|
13
|
+
export declare var TOOL_NAMES: readonly ["create_view", "set_list_layout", "set_form_layout", "set_related_lists", "add_choices_to_field", "flow_view", "action_view", "action_edit", "flow_publish", "flow_copy", "flow_create", "flow_test", "flow_edit", "create_table", "add_column", "set_column", "set_field", "create_record", "host_assets", "invoke_rest", "app_publish"];
|
|
14
14
|
export type ToolName = (typeof TOOL_NAMES)[number];
|
|
15
15
|
export interface RegistryDeps {
|
|
16
16
|
/** Optional client injection for tests; defaults to createClient({}). */
|
package/dist/mcp/registry.js
CHANGED
|
@@ -31,6 +31,7 @@ const hostAssets_1 = require("../hostAssets");
|
|
|
31
31
|
const setField_1 = require("../setField");
|
|
32
32
|
const createRecord_1 = require("../createRecord");
|
|
33
33
|
const invokeRest_1 = require("../invokeRest");
|
|
34
|
+
const publishApp_1 = require("../publishApp");
|
|
34
35
|
const schemas_1 = require("./schemas");
|
|
35
36
|
exports.TOOL_NAMES = [
|
|
36
37
|
"create_view",
|
|
@@ -53,6 +54,7 @@ exports.TOOL_NAMES = [
|
|
|
53
54
|
"create_record",
|
|
54
55
|
"host_assets",
|
|
55
56
|
"invoke_rest",
|
|
57
|
+
"app_publish",
|
|
56
58
|
];
|
|
57
59
|
// Annotation presets (READ_ONLY / WRITE_ADDITIVE_IDEMPOTENT / WRITE_CREATE /
|
|
58
60
|
// WRITE_OVERWRITE / WRITE_EXECUTE) come from @tenonhq/dovetail-mcp-kit.
|
|
@@ -491,6 +493,39 @@ function buildDescriptors(deps = {}) {
|
|
|
491
493
|
return (0, invokeRest_1.invokeRest)(params);
|
|
492
494
|
},
|
|
493
495
|
},
|
|
496
|
+
{
|
|
497
|
+
name: "app_publish",
|
|
498
|
+
annotations: dovetail_mcp_kit_1.WRITE_EXECUTE,
|
|
499
|
+
description: "Publish a scoped ServiceNow application to ONE target per call — the ServiceNow Store " +
|
|
500
|
+
"(target 'store') or the company Application Repository (target 'repo'); call twice to " +
|
|
501
|
+
"publish to both — then poll the publish to completion. STORE PUBLISH IS EXTERNALLY " +
|
|
502
|
+
"VISIBLE on the ServiceNow Store — treat it as a release. DRY-RUN BY DEFAULT: without " +
|
|
503
|
+
"confirm:true the resolved plan (app, current version, target) is returned and nothing is " +
|
|
504
|
+
"published. target 'store' replays the sys_app form's upload flow over a form-login session " +
|
|
505
|
+
"and requires SN_STORE_USERNAME/SN_STORE_PASSWORD in the server's env file — credentials " +
|
|
506
|
+
"never transit tool arguments. target 'repo' uses the supported CI/CD REST API " +
|
|
507
|
+
"(/api/sn_cicd/app_repo/publish) and requires the sn_cicd role. app is a scope name, " +
|
|
508
|
+
"sys_app sys_id, or app name; version must be above the currently published version. The " +
|
|
509
|
+
"result carries the progress-tracker id, per-step states, the Store appLink, and the " +
|
|
510
|
+
"publish's update-set sys_id where the instance reports one.",
|
|
511
|
+
shape: schemas_1.publishAppSchema.shape,
|
|
512
|
+
handler: async function (args) {
|
|
513
|
+
var p = schemas_1.publishAppSchema.parse(args);
|
|
514
|
+
var params = {
|
|
515
|
+
app: p.app,
|
|
516
|
+
version: p.version,
|
|
517
|
+
target: p.target,
|
|
518
|
+
devNotes: p.devNotes,
|
|
519
|
+
confirm: p.confirm,
|
|
520
|
+
dryRun: p.dryRun,
|
|
521
|
+
timeoutMs: p.timeoutMs,
|
|
522
|
+
};
|
|
523
|
+
if (deps.client) {
|
|
524
|
+
params.client = deps.client;
|
|
525
|
+
}
|
|
526
|
+
return (0, publishApp_1.publishApp)(params);
|
|
527
|
+
},
|
|
528
|
+
},
|
|
494
529
|
];
|
|
495
530
|
}
|
|
496
531
|
function registerAllTools(server, deps = {}) {
|
package/dist/mcp/schemas.d.ts
CHANGED
|
@@ -958,6 +958,31 @@ export declare var createRecordSchema: z.ZodObject<{
|
|
|
958
958
|
dryRun?: boolean | undefined;
|
|
959
959
|
ifAbsentQuery?: string | undefined;
|
|
960
960
|
}>;
|
|
961
|
+
export declare var publishAppSchema: z.ZodObject<{
|
|
962
|
+
app: z.ZodString;
|
|
963
|
+
version: z.ZodString;
|
|
964
|
+
devNotes: z.ZodOptional<z.ZodString>;
|
|
965
|
+
target: z.ZodUnion<[z.ZodLiteral<"store">, z.ZodLiteral<"repo">]>;
|
|
966
|
+
confirm: z.ZodOptional<z.ZodBoolean>;
|
|
967
|
+
dryRun: z.ZodOptional<z.ZodBoolean>;
|
|
968
|
+
timeoutMs: z.ZodOptional<z.ZodNumber>;
|
|
969
|
+
}, "strip", z.ZodTypeAny, {
|
|
970
|
+
target: "store" | "repo";
|
|
971
|
+
version: string;
|
|
972
|
+
app: string;
|
|
973
|
+
confirm?: boolean | undefined;
|
|
974
|
+
dryRun?: boolean | undefined;
|
|
975
|
+
devNotes?: string | undefined;
|
|
976
|
+
timeoutMs?: number | undefined;
|
|
977
|
+
}, {
|
|
978
|
+
target: "store" | "repo";
|
|
979
|
+
version: string;
|
|
980
|
+
app: string;
|
|
981
|
+
confirm?: boolean | undefined;
|
|
982
|
+
dryRun?: boolean | undefined;
|
|
983
|
+
devNotes?: string | undefined;
|
|
984
|
+
timeoutMs?: number | undefined;
|
|
985
|
+
}>;
|
|
961
986
|
export declare var invokeRestSchema: z.ZodObject<{
|
|
962
987
|
method: z.ZodEffects<z.ZodEnum<["GET", "POST", "PUT", "DELETE"]>, "GET" | "DELETE" | "POST" | "PUT", unknown>;
|
|
963
988
|
path: z.ZodString;
|
package/dist/mcp/schemas.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* own file so registry.ts stays focused on wiring.
|
|
5
5
|
*/
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
exports.invokeRestSchema = exports.createRecordSchema = exports.setFieldSchema = exports.setColumnSchema = exports.columnAttributesSchema = exports.addColumnSchema = exports.createTableSchema = exports.columnSpecSchema = exports.hostAssetsSchema = exports.editFlowSchema = exports.editActionSchema = exports.stepInputPatchSchema = exports.testFlowSchema = exports.createFlowSchema = exports.copyFlowSchema = exports.publishFlowSchema = exports.viewActionSchema = exports.viewFlowSchema = exports.addChoicesToFieldSchema = exports.choiceValueSchema = exports.setRelatedListsSchema = exports.setFormLayoutSchema = exports.formSectionSchema = exports.setListLayoutSchema = exports.createViewSchema = void 0;
|
|
7
|
+
exports.invokeRestSchema = exports.publishAppSchema = exports.createRecordSchema = exports.setFieldSchema = exports.setColumnSchema = exports.columnAttributesSchema = exports.addColumnSchema = exports.createTableSchema = exports.columnSpecSchema = exports.hostAssetsSchema = exports.editFlowSchema = exports.editActionSchema = exports.stepInputPatchSchema = exports.testFlowSchema = exports.createFlowSchema = exports.copyFlowSchema = exports.publishFlowSchema = exports.viewActionSchema = exports.viewFlowSchema = exports.addChoicesToFieldSchema = exports.choiceValueSchema = exports.setRelatedListsSchema = exports.setFormLayoutSchema = exports.formSectionSchema = exports.setListLayoutSchema = exports.createViewSchema = void 0;
|
|
8
8
|
const zod_1 = require("zod");
|
|
9
9
|
exports.createViewSchema = zod_1.z.object({
|
|
10
10
|
name: zod_1.z.string().min(1),
|
|
@@ -261,6 +261,19 @@ exports.createRecordSchema = zod_1.z.object({
|
|
|
261
261
|
ifAbsentQuery: zod_1.z.string().optional(),
|
|
262
262
|
dryRun: zod_1.z.boolean().optional(),
|
|
263
263
|
});
|
|
264
|
+
// app_publish: publish a scoped app to the ServiceNow Store or the company
|
|
265
|
+
// application repository. Deliberately NO credential fields — the Store
|
|
266
|
+
// account resolves from SN_STORE_USERNAME/SN_STORE_PASSWORD inside the verb,
|
|
267
|
+
// so credentials never transit MCP arguments or telemetry.
|
|
268
|
+
exports.publishAppSchema = zod_1.z.object({
|
|
269
|
+
app: zod_1.z.string().min(1),
|
|
270
|
+
version: zod_1.z.string().min(1),
|
|
271
|
+
devNotes: zod_1.z.string().optional(),
|
|
272
|
+
target: zod_1.z.union([zod_1.z.literal("store"), zod_1.z.literal("repo")]),
|
|
273
|
+
confirm: zod_1.z.boolean().optional(),
|
|
274
|
+
dryRun: zod_1.z.boolean().optional(),
|
|
275
|
+
timeoutMs: zod_1.z.number().int().positive().optional(),
|
|
276
|
+
});
|
|
264
277
|
// invoke_rest: transport primitive for arbitrary authenticated REST operations
|
|
265
278
|
// (Scripted REST included). The dry-run-unless-confirm gate lives in invokeRest
|
|
266
279
|
// itself; the regex here rejects absolute URLs and non-/api/ paths early.
|