@tenonhq/dovetail-servicenow 0.0.38 → 0.0.40
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 +35 -9
- package/dist/cli.js +46 -18
- package/dist/index.d.ts +1 -1
- package/dist/index.js +8 -2
- package/dist/loadEnv.d.ts +37 -7
- package/dist/loadEnv.js +94 -14
- package/dist/mcp/registry.js +19 -10
- package/dist/mcp/schemas.d.ts +12 -3
- package/dist/mcp/schemas.js +9 -1
- package/dist/publishApp.d.ts +128 -16
- package/dist/publishApp.js +463 -100
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -516,11 +516,11 @@ Programmatic: `invokeRest({ method, path, body, confirm })` is exported, and the
|
|
|
516
516
|
client gained `now.put` / `now.delete` / `now.invoke` (the latter returns
|
|
517
517
|
`{ status, body }` verbatim) alongside the existing `now.get` / `now.post`.
|
|
518
518
|
|
|
519
|
-
### Publish an app to the Store / application repository
|
|
519
|
+
### Publish an app to the Store / application repository / an update set
|
|
520
520
|
|
|
521
521
|
Publish a scoped application to the **ServiceNow Store**, the **company
|
|
522
|
-
application repository**, or
|
|
523
|
-
tracker polled to completion.
|
|
522
|
+
application repository**, and/or **into a new update set** — headlessly, with
|
|
523
|
+
each publish's progress tracker polled to completion.
|
|
524
524
|
|
|
525
525
|
```bash
|
|
526
526
|
# Dry-run — the DEFAULT: resolves the app, prints the plan, publishes NOTHING
|
|
@@ -529,15 +529,21 @@ npx dove-sn publish-app --app x_cadso_filter --version 6.0.20260716 --target bot
|
|
|
529
529
|
# Publish for real (store, then repo, same version)
|
|
530
530
|
npx dove-sn publish-app --app x_cadso_filter --version 6.0.20260716 \
|
|
531
531
|
--target both --dev-notes "July release" --confirm --json
|
|
532
|
+
|
|
533
|
+
# Release flow on an instance WITHOUT the sn_cicd plugin: publish to the company
|
|
534
|
+
# repository over the UI uploader, then capture the app into a dated update set.
|
|
535
|
+
npx dove-sn publish-app --app x_cadso_filter --version 6.0.20260729 \
|
|
536
|
+
--target repo-ui,update-set --update-set-description 20260729 --confirm --json
|
|
532
537
|
```
|
|
533
538
|
|
|
534
539
|
**Store publish is EXTERNALLY VISIBLE on the ServiceNow Store — treat
|
|
535
540
|
`--target store --confirm` as a release.** `publish-app` is dry-run by default:
|
|
536
541
|
without `--confirm` it prints the resolved plan and exits `1` (a deliberate
|
|
537
|
-
refusal). `--target
|
|
538
|
-
|
|
542
|
+
refusal). `--target` takes one target, a comma-separated list, or `both` (an
|
|
543
|
+
alias for `store,repo`); targets run **in the order given** and short-circuit on
|
|
544
|
+
the first failure.
|
|
539
545
|
|
|
540
|
-
The
|
|
546
|
+
The targets ride different transports:
|
|
541
547
|
|
|
542
548
|
- **store** replays the `sys_app` form's upload flow (`xmlhttp.do` +
|
|
543
549
|
`sn_appauthor.ScopedAppUploaderAJAX`) over a form-login session — basic auth
|
|
@@ -547,13 +553,33 @@ The two targets ride different transports:
|
|
|
547
553
|
- **repo** uses the supported CI/CD REST API (`POST /api/sn_cicd/app_repo/publish`
|
|
548
554
|
+ `GET /api/sn_cicd/progress/{id}`) over basic auth. The API user needs the
|
|
549
555
|
`sn_cicd` role (or admin).
|
|
556
|
+
- **repo-ui** reaches the *same* company repository as `repo`, but over the UI
|
|
557
|
+
uploader (`sysparm_publish_to_store=false`) instead of REST. Use it when the
|
|
558
|
+
instance has no CI/CD plugin — `tenonworkshop`, for instance, has no `sn_cicd`
|
|
559
|
+
scope and no `app_repo` service, so `repo` 404s there while `repo-ui` works.
|
|
560
|
+
It needs no Store credentials.
|
|
561
|
+
- **update-set** publishes the app *into a newly created update set* via the
|
|
562
|
+
two-call `com.snc.apps.AppsAjaxProcessor` flow (`createUpdateSet` →
|
|
563
|
+
`publishToUpdateSet`). There is no REST equivalent. `--update-set-name`
|
|
564
|
+
defaults to the app's name (the dialog's field is readonly, so that is what
|
|
565
|
+
the UI submits); `--update-set-description` is conventionally the release date
|
|
566
|
+
stamp `YYYYMMDD`, which makes a whole release one query
|
|
567
|
+
(`sys_update_set` where `description=20260729`). `--include-data` maps to the
|
|
568
|
+
dialog's "Include demo data" box and defaults **off**, matching the value the
|
|
569
|
+
UI actually puts on the wire.
|
|
570
|
+
|
|
571
|
+
Ordering matters when you combine them: the repo publish is what bumps
|
|
572
|
+
`sys_app.version`, so put it **before** `update-set` if you want the set
|
|
573
|
+
captured at the new version.
|
|
550
574
|
|
|
551
575
|
`--app` accepts a scope name, `sys_app` sys_id, or app name; `--version` must be
|
|
552
576
|
above the currently published version. The result carries the progress-tracker
|
|
553
577
|
id, per-step states ("Packaging application", "Uploading application"), the
|
|
554
|
-
Store `appLink`, and the
|
|
555
|
-
|
|
556
|
-
|
|
578
|
+
Store `appLink`, and the update-set sys_id — for the `update-set` target that is
|
|
579
|
+
recorded as soon as the set is created, so it survives a later failure and you
|
|
580
|
+
can always find (or delete) the set. Exit codes: `0` published or dry-run, `1`
|
|
581
|
+
bad args/unconfirmed, `2` failed/timeout. Programmatic:
|
|
582
|
+
`publishApp({ app, version, target, confirm })`.
|
|
557
583
|
|
|
558
584
|
### Export an update set (or a whole app) to importable XML
|
|
559
585
|
|
package/dist/cli.js
CHANGED
|
@@ -1043,15 +1043,18 @@ function printHelp() {
|
|
|
1043
1043
|
" [--script-input <name>] [--update-set <sys_id>] [--apply] [--json])\n" +
|
|
1044
1044
|
" edit-flow Patch a flow/subflow (rename, description, step inputs)\n" +
|
|
1045
1045
|
" (--sys-id <sys_id> --from-json <ops.json> [--apply] [--update-set <sys_id>] [--scope <sys_id>] [--json])\n" +
|
|
1046
|
-
" publish-app Publish a scoped app to the ServiceNow Store
|
|
1047
|
-
"
|
|
1046
|
+
" publish-app Publish a scoped app to the ServiceNow Store, the company application\n" +
|
|
1047
|
+
" repository, and/or a new update set, then poll each to completion.\n" +
|
|
1048
1048
|
" STORE PUBLISH IS EXTERNALLY VISIBLE on the ServiceNow Store.\n" +
|
|
1049
1049
|
" DRY-RUN BY DEFAULT — nothing is published without --confirm\n" +
|
|
1050
|
-
" (--app <scope|sys_id|name> --version <v
|
|
1050
|
+
" (--app <scope|sys_id|name> --version <v>\n" +
|
|
1051
|
+
" --target store|repo|repo-ui|update-set|both (comma-separated ok)\n" +
|
|
1051
1052
|
" [--dev-notes <text>] [--store-user <email>] [--timeout-ms <n>]\n" +
|
|
1052
|
-
" [--
|
|
1053
|
+
" [--update-set-name <name>] [--update-set-description <text>]\n" +
|
|
1054
|
+
" [--include-data] [--dry-run] [--json] [--confirm])\n" +
|
|
1053
1055
|
" Store creds: SN_STORE_USERNAME/SN_STORE_PASSWORD in the --env file;\n" +
|
|
1054
|
-
" the password is never a flag.
|
|
1056
|
+
" the password is never a flag. 'repo' needs the sn_cicd plugin+role;\n" +
|
|
1057
|
+
" 'repo-ui' reaches the same repository over the UI uploader instead.\n" +
|
|
1055
1058
|
" export-update-set Export an update set to importable <unload> XML, with secret\n" +
|
|
1056
1059
|
" values replaced by __SET_DURING_INSTALL__ (no opt-out).\n" +
|
|
1057
1060
|
" assemble mode is READ-ONLY; complete mode marks the set\n" +
|
|
@@ -1070,8 +1073,11 @@ function printHelp() {
|
|
|
1070
1073
|
" (--in <file> [--out <file>] [--rules <file>] [--report] [--json])\n" +
|
|
1071
1074
|
" mcp Run the MCP stdio server (--smoke lists tools and exits)\n" +
|
|
1072
1075
|
"\nGlobal flags:\n" +
|
|
1073
|
-
" --env <path>
|
|
1074
|
-
" or the DOVETAIL_ENV_FILE env var).
|
|
1076
|
+
" --env <name|path> Load credentials from a specific env file (also --env-file,\n" +
|
|
1077
|
+
" or the DOVETAIL_ENV_FILE env var). A bare name like 'prod'\n" +
|
|
1078
|
+
" resolves to .env.prod in the cwd. The file's SN_* connection\n" +
|
|
1079
|
+
" vars replace any already exported; a missing or incomplete\n" +
|
|
1080
|
+
" file is an error (no fallback). Default: .env in the cwd.\n");
|
|
1075
1081
|
}
|
|
1076
1082
|
/** Parse inline `--columns "Label:type:max, Other:choice, ..."` into ColumnSpec[]. */
|
|
1077
1083
|
function parseColumnsInline(input) {
|
|
@@ -1830,8 +1836,19 @@ async function runInvokeRest(flags) {
|
|
|
1830
1836
|
* dove-sn publish-app:
|
|
1831
1837
|
* --app <scope|sys_id|name> Required. The sys_app to publish.
|
|
1832
1838
|
* --version <v> Required. Version to publish (e.g. 6.0.20260716).
|
|
1833
|
-
* --target store|repo|
|
|
1834
|
-
*
|
|
1839
|
+
* --target <t[,t...]> Required. store | repo | repo-ui | update-set |
|
|
1840
|
+
* both (= store,repo). STORE IS EXTERNALLY VISIBLE.
|
|
1841
|
+
* repo = CI/CD REST API (needs the sn_cicd plugin)
|
|
1842
|
+
* repo-ui= same destination over the UI uploader,
|
|
1843
|
+
* for instances without sn_cicd
|
|
1844
|
+
* update-set = publish the app INTO a new update set
|
|
1845
|
+
* [--dev-notes <text>] Optional developer notes (uploader targets).
|
|
1846
|
+
* [--update-set-name <name>] update-set only. Defaults to the app's name.
|
|
1847
|
+
* [--update-set-description <text>]
|
|
1848
|
+
* update-set only. Tenon convention is the release
|
|
1849
|
+
* date stamp (YYYYMMDD) so a release is one query.
|
|
1850
|
+
* [--include-data] update-set only. Include demo data (default off,
|
|
1851
|
+
* matching the observed wire value).
|
|
1835
1852
|
* [--store-user <email>] Store account email (else SN_STORE_USERNAME).
|
|
1836
1853
|
* The password comes ONLY from SN_STORE_PASSWORD —
|
|
1837
1854
|
* there is no flag for it, ever.
|
|
@@ -1839,9 +1856,9 @@ async function runInvokeRest(flags) {
|
|
|
1839
1856
|
* [--dry-run] [--json] [--confirm]
|
|
1840
1857
|
*
|
|
1841
1858
|
* DRY-RUN unless --confirm: without it the resolved plan is printed and the
|
|
1842
|
-
* command exits 1 (a deliberate refusal, not success).
|
|
1843
|
-
*
|
|
1844
|
-
*
|
|
1859
|
+
* command exits 1 (a deliberate refusal, not success). Multiple targets run
|
|
1860
|
+
* sequentially with the same version and short-circuit on the first failure;
|
|
1861
|
+
* --json emits an array of per-target results.
|
|
1845
1862
|
* Exit codes: 0 published/dry-run, 1 bad args/unconfirmed, 2 failed/timeout.
|
|
1846
1863
|
*/
|
|
1847
1864
|
async function runPublishApp(flags) {
|
|
@@ -1849,16 +1866,17 @@ async function runPublishApp(flags) {
|
|
|
1849
1866
|
var version = flags.version;
|
|
1850
1867
|
var target = flags.target;
|
|
1851
1868
|
if (!app || !version || !target) {
|
|
1852
|
-
process.stderr.write("publish-app: --app, --version and --target
|
|
1869
|
+
process.stderr.write("publish-app: --app, --version and --target <" +
|
|
1870
|
+
publishApp_1.PUBLISH_TARGETS.join("|") +
|
|
1871
|
+
"|both> are required\n");
|
|
1853
1872
|
return 1;
|
|
1854
1873
|
}
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
"')\n");
|
|
1874
|
+
var parsedTargets = (0, publishApp_1.parsePublishTargets)(target);
|
|
1875
|
+
if (parsedTargets.error) {
|
|
1876
|
+
process.stderr.write("publish-app: " + parsedTargets.error + "\n");
|
|
1859
1877
|
return 1;
|
|
1860
1878
|
}
|
|
1861
|
-
var targets =
|
|
1879
|
+
var targets = parsedTargets.targets;
|
|
1862
1880
|
var dryRun = flags["dry-run"] === "true";
|
|
1863
1881
|
var confirmed = flags.confirm === "true";
|
|
1864
1882
|
var client = (0, client_1.createClient)({});
|
|
@@ -1877,6 +1895,16 @@ async function runPublishApp(flags) {
|
|
|
1877
1895
|
params.devNotes = flags["dev-notes"];
|
|
1878
1896
|
if (flags["store-user"])
|
|
1879
1897
|
params.storeUsername = flags["store-user"];
|
|
1898
|
+
if (flags["update-set-name"]) {
|
|
1899
|
+
params.updateSetName = flags["update-set-name"];
|
|
1900
|
+
}
|
|
1901
|
+
// Read with !== undefined, not truthiness: an intentionally empty
|
|
1902
|
+
// description must stay empty rather than silently fall back.
|
|
1903
|
+
if (flags["update-set-description"] !== undefined) {
|
|
1904
|
+
params.updateSetDescription = flags["update-set-description"];
|
|
1905
|
+
}
|
|
1906
|
+
if (flags["include-data"] === "true")
|
|
1907
|
+
params.includeData = true;
|
|
1880
1908
|
if (flags["timeout-ms"]) {
|
|
1881
1909
|
// A NaN timeout would make the poll-loop budget check always false —
|
|
1882
1910
|
// an infinite loop. Validate here, exit 1 on garbage.
|
package/dist/index.d.ts
CHANGED
|
@@ -29,7 +29,7 @@ export type { RecordWriteResult, SetFieldParams, SetFieldResult, } from "./setFi
|
|
|
29
29
|
export type { CreateRecordParams, CreateRecordResult } from "./createRecord";
|
|
30
30
|
export { invokeRest, INVOKE_REST_METHODS } from "./invokeRest";
|
|
31
31
|
export type { InvokeRestParams, InvokeRestResult } from "./invokeRest";
|
|
32
|
-
export { publishApp, buildStartFields, parseXmlAnswer, parseProgressTree, classifyProgress, flattenSteps, harvestProgressResults, parseCicdPublishResponse, parseCicdProgress, DEFAULT_PUBLISH_TIMEOUT_MS, PUBLISH_POLL_DELAYS_MS, } from "./publishApp";
|
|
32
|
+
export { publishApp, buildStartFields, buildCreateUpdateSetFields, buildPublishToUpdateSetFields, resolveUpdateSetNaming, describeTarget, parsePublishTargets, PUBLISH_TARGETS, parseXmlAnswer, parseProgressTree, classifyProgress, flattenSteps, harvestProgressResults, parseCicdPublishResponse, parseCicdProgress, DEFAULT_PUBLISH_TIMEOUT_MS, PUBLISH_POLL_DELAYS_MS, } from "./publishApp";
|
|
33
33
|
export type { PublishAppParams, PublishAppResult, PublishTarget, PublishStep, ProgressNode, CicdProgress, PublishTransport, } from "./publishApp";
|
|
34
34
|
export { exportUpdateSet, renderUpdateXmlRow, renderRemoteUpdateSet, renderUnload, countUnloadRecords, countUpdateXml, fetchUpdateXmlRows, refreshTypeFields, parseStatsCount, formatUnloadDate, xmlEscape, UPDATE_XML_FIELDS, DEFAULT_PAGE_SIZE, MAX_PAGE_SIZE, DEFAULT_MAX_ROWS, } from "./exportUpdateSet";
|
|
35
35
|
export type { ExportUpdateSetParams, ExportUpdateSetResult, ExportMode, ExportTransport, } from "./exportUpdateSet";
|
package/dist/index.js
CHANGED
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
*/
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
9
|
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.formatRemoveChoicesResult = exports.formatAddChoicesResult = exports.formatHostAssetsResult = exports.classifyChunks = exports.hostAssets = exports.ChoiceWriteError = exports.removeChoicesFromField = exports.addChoicesToField = exports.resolveConfigFromEnvFile = exports.createClientFromEnvFile = exports.assertWriteAllowed = exports.evaluateWriteGate = exports.isCiEnvironment = exports.resolveExecutionContext = exports.createClient = void 0;
|
|
10
|
-
exports.
|
|
11
|
-
exports.SECRET_INTERNAL_TYPES = exports.SENTINEL = exports.isCapturable = exports.secretFieldsFromDictionary = exports.mergeSecretRules = exports.loadSecretRules = exports.defaultSecretRules = exports.escapeRegExp = exports.encodeXmlText = exports.encodeXmlEntities = exports.stripJsonValue = exports.stripField = exports.plannedStrips = exports.recordFieldNames = exports.readRecordTable = exports.readField = exports.verifyStripped = exports.stripSecrets = void 0;
|
|
10
|
+
exports.DEFAULT_PAGE_SIZE = exports.UPDATE_XML_FIELDS = exports.xmlEscape = exports.formatUnloadDate = exports.parseStatsCount = exports.refreshTypeFields = exports.fetchUpdateXmlRows = exports.countUpdateXml = exports.countUnloadRecords = exports.renderUnload = exports.renderRemoteUpdateSet = exports.renderUpdateXmlRow = exports.exportUpdateSet = 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.PUBLISH_TARGETS = exports.parsePublishTargets = exports.describeTarget = exports.resolveUpdateSetNaming = exports.buildPublishToUpdateSetFields = exports.buildCreateUpdateSetFields = exports.buildStartFields = exports.publishApp = exports.INVOKE_REST_METHODS = exports.invokeRest = exports.createRecord = exports.setField = exports.resolveTableAttributes = exports.setTable = exports.toStoredValue = exports.resolveAttributes = exports.setColumn = exports.indexMatchesColumns = exports.parseIndexColumns = exports.addIndex = exports.deriveElement = exports.addColumn = exports.DEFAULT_SAVE_ACTION = exports.DEFAULT_SUPER_CLASS = exports.TYPE_MAP = exports.defaultAccessFlags = exports.applyTableSaveOverlay = exports.resolveType = void 0;
|
|
11
|
+
exports.SECRET_INTERNAL_TYPES = exports.SENTINEL = exports.isCapturable = exports.secretFieldsFromDictionary = exports.mergeSecretRules = exports.loadSecretRules = exports.defaultSecretRules = exports.escapeRegExp = exports.encodeXmlText = exports.encodeXmlEntities = exports.stripJsonValue = exports.stripField = exports.plannedStrips = exports.recordFieldNames = exports.readRecordTable = exports.readField = exports.verifyStripped = exports.stripSecrets = exports.DEFAULT_EXPORT_APP_TIMEOUT_MS = exports.buildPublishFields = exports.buildCreateSetFields = exports.exportApp = exports.DEFAULT_MAX_ROWS = exports.MAX_PAGE_SIZE = void 0;
|
|
12
12
|
var client_1 = require("./client");
|
|
13
13
|
Object.defineProperty(exports, "createClient", { enumerable: true, get: function () { return client_1.createClient; } });
|
|
14
14
|
var executionContext_1 = require("./executionContext");
|
|
@@ -99,6 +99,12 @@ Object.defineProperty(exports, "INVOKE_REST_METHODS", { enumerable: true, get: f
|
|
|
99
99
|
var publishApp_1 = require("./publishApp");
|
|
100
100
|
Object.defineProperty(exports, "publishApp", { enumerable: true, get: function () { return publishApp_1.publishApp; } });
|
|
101
101
|
Object.defineProperty(exports, "buildStartFields", { enumerable: true, get: function () { return publishApp_1.buildStartFields; } });
|
|
102
|
+
Object.defineProperty(exports, "buildCreateUpdateSetFields", { enumerable: true, get: function () { return publishApp_1.buildCreateUpdateSetFields; } });
|
|
103
|
+
Object.defineProperty(exports, "buildPublishToUpdateSetFields", { enumerable: true, get: function () { return publishApp_1.buildPublishToUpdateSetFields; } });
|
|
104
|
+
Object.defineProperty(exports, "resolveUpdateSetNaming", { enumerable: true, get: function () { return publishApp_1.resolveUpdateSetNaming; } });
|
|
105
|
+
Object.defineProperty(exports, "describeTarget", { enumerable: true, get: function () { return publishApp_1.describeTarget; } });
|
|
106
|
+
Object.defineProperty(exports, "parsePublishTargets", { enumerable: true, get: function () { return publishApp_1.parsePublishTargets; } });
|
|
107
|
+
Object.defineProperty(exports, "PUBLISH_TARGETS", { enumerable: true, get: function () { return publishApp_1.PUBLISH_TARGETS; } });
|
|
102
108
|
Object.defineProperty(exports, "parseXmlAnswer", { enumerable: true, get: function () { return publishApp_1.parseXmlAnswer; } });
|
|
103
109
|
Object.defineProperty(exports, "parseProgressTree", { enumerable: true, get: function () { return publishApp_1.parseProgressTree; } });
|
|
104
110
|
Object.defineProperty(exports, "classifyProgress", { enumerable: true, get: function () { return publishApp_1.classifyProgress; } });
|
package/dist/loadEnv.d.ts
CHANGED
|
@@ -1,15 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ServiceNow connection variables. When an env file is selected explicitly,
|
|
3
|
+
* these are taken from the file ONLY — any value already in process.env is
|
|
4
|
+
* cleared first — so the selected file fully determines the target instance
|
|
5
|
+
* and auth mode. (Without this, a shell/session-exported SN_INSTANCE or
|
|
6
|
+
* SN_API_KEY silently won over the file and the command hit the wrong
|
|
7
|
+
* instance.)
|
|
8
|
+
*/
|
|
9
|
+
export declare var SN_CONNECTION_KEYS: string[];
|
|
10
|
+
/**
|
|
11
|
+
* Resolve an `--env` selector to an absolute env-file path.
|
|
12
|
+
*
|
|
13
|
+
* Accepts, in order:
|
|
14
|
+
* - an absolute path, or any value containing a path separator → used as a path
|
|
15
|
+
* (relative paths resolve against cwd) — unchanged legacy behavior;
|
|
16
|
+
* - a bare name that exists as a file in cwd (e.g. `--env my.env`) → that file;
|
|
17
|
+
* - a bare name like `loft` → `<cwd>/.env.loft`, matching the MCP tool's
|
|
18
|
+
* per-call `env` resolution; a `.env`-prefixed basename is used as-is.
|
|
19
|
+
*
|
|
20
|
+
* Returns the resolved path; does not check existence (loadEnvFile does).
|
|
21
|
+
*/
|
|
22
|
+
export declare function resolveEnvSelection(raw: string, cwd?: string): string;
|
|
1
23
|
/**
|
|
2
24
|
* Loads ServiceNow credentials for the `dove-sn` CLI and its MCP server.
|
|
3
25
|
*
|
|
4
26
|
* Resolution order for the env file:
|
|
5
|
-
* 1. An explicit `--env <path>` / `--env-file <path>` flag
|
|
6
|
-
* 2. The `DOVETAIL_ENV_FILE` environment variable
|
|
7
|
-
* `dove-sn mcp` at a specific credential file without a CLI flag.
|
|
27
|
+
* 1. An explicit `--env <name|path>` / `--env-file <name|path>` flag.
|
|
28
|
+
* 2. The `DOVETAIL_ENV_FILE` environment variable.
|
|
8
29
|
* 3. The default `.env` in the current working directory.
|
|
9
30
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
31
|
+
* For an explicit selection (1 or 2) this FAILS CLOSED: a missing file, or a
|
|
32
|
+
* file that doesn't define an instance plus credentials, throws instead of
|
|
33
|
+
* silently falling back to whatever instance the surrounding environment
|
|
34
|
+
* points at. The file's ServiceNow connection variables (SN_CONNECTION_KEYS)
|
|
35
|
+
* replace any already in process.env; every other variable keeps dotenv's
|
|
36
|
+
* never-override semantics.
|
|
37
|
+
*
|
|
38
|
+
* The default cwd `.env` (3) keeps its historical behavior: optional, and
|
|
39
|
+
* never overrides already-exported variables.
|
|
12
40
|
*
|
|
13
|
-
* @param {string} [
|
|
41
|
+
* @param {string} [explicitSelection] - Value of the `--env` / `--env-file` flag.
|
|
42
|
+
* @returns {string|undefined} The absolute path of the explicitly selected
|
|
43
|
+
* file, or undefined when the default `.env` path was used.
|
|
14
44
|
*/
|
|
15
|
-
export declare function loadEnvFile(
|
|
45
|
+
export declare function loadEnvFile(explicitSelection?: string): string | undefined;
|
package/dist/loadEnv.js
CHANGED
|
@@ -3,30 +3,110 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.SN_CONNECTION_KEYS = void 0;
|
|
7
|
+
exports.resolveEnvSelection = resolveEnvSelection;
|
|
6
8
|
exports.loadEnvFile = loadEnvFile;
|
|
9
|
+
const fs_1 = __importDefault(require("fs"));
|
|
7
10
|
const dotenv_1 = __importDefault(require("dotenv"));
|
|
8
11
|
const path_1 = __importDefault(require("path"));
|
|
12
|
+
const createClientFromEnvFile_1 = require("./createClientFromEnvFile");
|
|
13
|
+
/**
|
|
14
|
+
* ServiceNow connection variables. When an env file is selected explicitly,
|
|
15
|
+
* these are taken from the file ONLY — any value already in process.env is
|
|
16
|
+
* cleared first — so the selected file fully determines the target instance
|
|
17
|
+
* and auth mode. (Without this, a shell/session-exported SN_INSTANCE or
|
|
18
|
+
* SN_API_KEY silently won over the file and the command hit the wrong
|
|
19
|
+
* instance.)
|
|
20
|
+
*/
|
|
21
|
+
exports.SN_CONNECTION_KEYS = [
|
|
22
|
+
"SN_INSTANCE",
|
|
23
|
+
"SN_DEV_INSTANCE",
|
|
24
|
+
"SN_PROD_INSTANCE",
|
|
25
|
+
"SN_API_KEY",
|
|
26
|
+
"SN_DEV_API_KEY",
|
|
27
|
+
"SN_PROD_API_KEY",
|
|
28
|
+
"SN_USER",
|
|
29
|
+
"SN_PASSWORD",
|
|
30
|
+
"SN_DEV_USERNAME",
|
|
31
|
+
"SN_DEV_PASSWORD",
|
|
32
|
+
"SN_PROD_USERNAME",
|
|
33
|
+
"SN_PROD_PASSWORD",
|
|
34
|
+
];
|
|
35
|
+
/**
|
|
36
|
+
* Resolve an `--env` selector to an absolute env-file path.
|
|
37
|
+
*
|
|
38
|
+
* Accepts, in order:
|
|
39
|
+
* - an absolute path, or any value containing a path separator → used as a path
|
|
40
|
+
* (relative paths resolve against cwd) — unchanged legacy behavior;
|
|
41
|
+
* - a bare name that exists as a file in cwd (e.g. `--env my.env`) → that file;
|
|
42
|
+
* - a bare name like `loft` → `<cwd>/.env.loft`, matching the MCP tool's
|
|
43
|
+
* per-call `env` resolution; a `.env`-prefixed basename is used as-is.
|
|
44
|
+
*
|
|
45
|
+
* Returns the resolved path; does not check existence (loadEnvFile does).
|
|
46
|
+
*/
|
|
47
|
+
function resolveEnvSelection(raw, cwd) {
|
|
48
|
+
if (typeof raw !== "string" || raw.trim().length === 0) {
|
|
49
|
+
throw new Error("--env must be a non-empty env-file name or path.");
|
|
50
|
+
}
|
|
51
|
+
var value = raw.trim();
|
|
52
|
+
var base = cwd || process.cwd();
|
|
53
|
+
if (path_1.default.isAbsolute(value) || /[\\/]/.test(value)) {
|
|
54
|
+
return path_1.default.resolve(base, value);
|
|
55
|
+
}
|
|
56
|
+
var literal = path_1.default.resolve(base, value);
|
|
57
|
+
if (fs_1.default.existsSync(literal)) {
|
|
58
|
+
return literal;
|
|
59
|
+
}
|
|
60
|
+
if (value.indexOf(".env") === 0) {
|
|
61
|
+
return literal;
|
|
62
|
+
}
|
|
63
|
+
return path_1.default.resolve(base, ".env." + value);
|
|
64
|
+
}
|
|
9
65
|
/**
|
|
10
66
|
* Loads ServiceNow credentials for the `dove-sn` CLI and its MCP server.
|
|
11
67
|
*
|
|
12
68
|
* Resolution order for the env file:
|
|
13
|
-
* 1. An explicit `--env <path>` / `--env-file <path>` flag
|
|
14
|
-
* 2. The `DOVETAIL_ENV_FILE` environment variable
|
|
15
|
-
* `dove-sn mcp` at a specific credential file without a CLI flag.
|
|
69
|
+
* 1. An explicit `--env <name|path>` / `--env-file <name|path>` flag.
|
|
70
|
+
* 2. The `DOVETAIL_ENV_FILE` environment variable.
|
|
16
71
|
* 3. The default `.env` in the current working directory.
|
|
17
72
|
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
73
|
+
* For an explicit selection (1 or 2) this FAILS CLOSED: a missing file, or a
|
|
74
|
+
* file that doesn't define an instance plus credentials, throws instead of
|
|
75
|
+
* silently falling back to whatever instance the surrounding environment
|
|
76
|
+
* points at. The file's ServiceNow connection variables (SN_CONNECTION_KEYS)
|
|
77
|
+
* replace any already in process.env; every other variable keeps dotenv's
|
|
78
|
+
* never-override semantics.
|
|
79
|
+
*
|
|
80
|
+
* The default cwd `.env` (3) keeps its historical behavior: optional, and
|
|
81
|
+
* never overrides already-exported variables.
|
|
20
82
|
*
|
|
21
|
-
* @param {string} [
|
|
83
|
+
* @param {string} [explicitSelection] - Value of the `--env` / `--env-file` flag.
|
|
84
|
+
* @returns {string|undefined} The absolute path of the explicitly selected
|
|
85
|
+
* file, or undefined when the default `.env` path was used.
|
|
22
86
|
*/
|
|
23
|
-
function loadEnvFile(
|
|
24
|
-
var raw =
|
|
25
|
-
if (raw) {
|
|
26
|
-
|
|
27
|
-
dotenv_1.default.config(
|
|
28
|
-
return;
|
|
87
|
+
function loadEnvFile(explicitSelection) {
|
|
88
|
+
var raw = explicitSelection || process.env.DOVETAIL_ENV_FILE;
|
|
89
|
+
if (!raw) {
|
|
90
|
+
// No explicit selection — load .env from cwd if it exists (no-op otherwise).
|
|
91
|
+
dotenv_1.default.config();
|
|
92
|
+
return undefined;
|
|
93
|
+
}
|
|
94
|
+
var resolved = resolveEnvSelection(raw);
|
|
95
|
+
if (!fs_1.default.existsSync(resolved)) {
|
|
96
|
+
throw new Error("env file not found: '" + resolved + "' (from --env '" + raw + "'). " +
|
|
97
|
+
"Refusing to fall back to the default environment — pass an existing env-file " +
|
|
98
|
+
"name (e.g. 'prod' → .env.prod in the current directory) or path.");
|
|
29
99
|
}
|
|
30
|
-
//
|
|
31
|
-
|
|
100
|
+
// Validates instance + credentials and throws loudly if either is missing.
|
|
101
|
+
(0, createClientFromEnvFile_1.resolveConfigFromEnvFile)(resolved);
|
|
102
|
+
var parsed = dotenv_1.default.parse(fs_1.default.readFileSync(resolved, "utf8"));
|
|
103
|
+
exports.SN_CONNECTION_KEYS.forEach(function (key) {
|
|
104
|
+
delete process.env[key];
|
|
105
|
+
if (Object.prototype.hasOwnProperty.call(parsed, key) && parsed[key] !== "") {
|
|
106
|
+
process.env[key] = parsed[key];
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
// Everything else in the file: dotenv's normal never-override semantics.
|
|
110
|
+
dotenv_1.default.config({ path: resolved });
|
|
111
|
+
return resolved;
|
|
32
112
|
}
|
package/dist/mcp/registry.js
CHANGED
|
@@ -624,18 +624,24 @@ function buildDescriptors(deps = {}) {
|
|
|
624
624
|
{
|
|
625
625
|
name: "app_publish",
|
|
626
626
|
annotations: dovetail_mcp_kit_1.WRITE_EXECUTE,
|
|
627
|
-
description: "Publish a scoped ServiceNow application to ONE target per call
|
|
628
|
-
"
|
|
629
|
-
"
|
|
630
|
-
"
|
|
631
|
-
"
|
|
632
|
-
"
|
|
633
|
-
"
|
|
634
|
-
"
|
|
635
|
-
"
|
|
627
|
+
description: "Publish a scoped ServiceNow application to ONE target per call, then poll the publish to " +
|
|
628
|
+
"completion; call repeatedly to hit several targets. STORE PUBLISH IS EXTERNALLY VISIBLE " +
|
|
629
|
+
"on the ServiceNow Store — treat it as a release. DRY-RUN BY DEFAULT: without confirm:true " +
|
|
630
|
+
"the resolved plan (app, current version, target) is returned and nothing is published. " +
|
|
631
|
+
"Targets: 'store' replays the sys_app form's upload flow over a form-login session and " +
|
|
632
|
+
"requires SN_STORE_USERNAME/SN_STORE_PASSWORD in the server's env file — credentials never " +
|
|
633
|
+
"transit tool arguments. 'repo' publishes to the company Application Repository via the " +
|
|
634
|
+
"supported CI/CD REST API (/api/sn_cicd/app_repo/publish) and requires the sn_cicd plugin " +
|
|
635
|
+
"and role. 'repo-ui' reaches the SAME company repository over the UI uploader instead — use " +
|
|
636
|
+
"it on instances without sn_cicd, where 'repo' 404s. 'update-set' publishes the app INTO a " +
|
|
637
|
+
"newly created update set via the two-call AppsAjaxProcessor flow (no REST equivalent " +
|
|
638
|
+
"exists); updateSetName defaults to the app's name and updateSetDescription is conventionally " +
|
|
639
|
+
"the release date stamp (YYYYMMDD) so a whole release is one sys_update_set query. " +
|
|
640
|
+
"includeData (default false) is the dialog's 'Include demo data' box. app is a scope name, " +
|
|
636
641
|
"sys_app sys_id, or app name; version must be above the currently published version. The " +
|
|
637
642
|
"result carries the progress-tracker id, per-step states, the Store appLink, and the " +
|
|
638
|
-
"
|
|
643
|
+
"update-set sys_id (for 'update-set' this is set as soon as the set is created, so it " +
|
|
644
|
+
"survives a later failure).",
|
|
639
645
|
shape: schemas_1.publishAppSchema.shape,
|
|
640
646
|
handler: async function (args) {
|
|
641
647
|
var p = schemas_1.publishAppSchema.parse(args);
|
|
@@ -644,6 +650,9 @@ function buildDescriptors(deps = {}) {
|
|
|
644
650
|
version: p.version,
|
|
645
651
|
target: p.target,
|
|
646
652
|
devNotes: p.devNotes,
|
|
653
|
+
updateSetName: p.updateSetName,
|
|
654
|
+
updateSetDescription: p.updateSetDescription,
|
|
655
|
+
includeData: p.includeData,
|
|
647
656
|
confirm: p.confirm,
|
|
648
657
|
dryRun: p.dryRun,
|
|
649
658
|
timeoutMs: p.timeoutMs,
|
package/dist/mcp/schemas.d.ts
CHANGED
|
@@ -1099,26 +1099,35 @@ export declare var publishAppSchema: z.ZodObject<{
|
|
|
1099
1099
|
app: z.ZodString;
|
|
1100
1100
|
version: z.ZodString;
|
|
1101
1101
|
devNotes: z.ZodOptional<z.ZodString>;
|
|
1102
|
-
target: z.ZodUnion<[z.ZodLiteral<"store">, z.ZodLiteral<"repo">]>;
|
|
1102
|
+
target: z.ZodUnion<[z.ZodLiteral<"store">, z.ZodLiteral<"repo">, z.ZodLiteral<"repo-ui">, z.ZodLiteral<"update-set">]>;
|
|
1103
|
+
updateSetName: z.ZodOptional<z.ZodString>;
|
|
1104
|
+
updateSetDescription: z.ZodOptional<z.ZodString>;
|
|
1105
|
+
includeData: z.ZodOptional<z.ZodBoolean>;
|
|
1103
1106
|
confirm: z.ZodOptional<z.ZodBoolean>;
|
|
1104
1107
|
dryRun: z.ZodOptional<z.ZodBoolean>;
|
|
1105
1108
|
timeoutMs: z.ZodOptional<z.ZodNumber>;
|
|
1106
1109
|
}, "strip", z.ZodTypeAny, {
|
|
1107
|
-
target: "store" | "repo";
|
|
1110
|
+
target: "store" | "repo" | "repo-ui" | "update-set";
|
|
1108
1111
|
version: string;
|
|
1109
1112
|
app: string;
|
|
1110
1113
|
confirm?: boolean | undefined;
|
|
1111
1114
|
dryRun?: boolean | undefined;
|
|
1115
|
+
includeData?: boolean | undefined;
|
|
1112
1116
|
timeoutMs?: number | undefined;
|
|
1113
1117
|
devNotes?: string | undefined;
|
|
1118
|
+
updateSetName?: string | undefined;
|
|
1119
|
+
updateSetDescription?: string | undefined;
|
|
1114
1120
|
}, {
|
|
1115
|
-
target: "store" | "repo";
|
|
1121
|
+
target: "store" | "repo" | "repo-ui" | "update-set";
|
|
1116
1122
|
version: string;
|
|
1117
1123
|
app: string;
|
|
1118
1124
|
confirm?: boolean | undefined;
|
|
1119
1125
|
dryRun?: boolean | undefined;
|
|
1126
|
+
includeData?: boolean | undefined;
|
|
1120
1127
|
timeoutMs?: number | undefined;
|
|
1121
1128
|
devNotes?: string | undefined;
|
|
1129
|
+
updateSetName?: string | undefined;
|
|
1130
|
+
updateSetDescription?: string | undefined;
|
|
1122
1131
|
}>;
|
|
1123
1132
|
export declare var invokeRestSchema: z.ZodObject<{
|
|
1124
1133
|
method: z.ZodEffects<z.ZodEnum<["GET", "POST", "PUT", "DELETE"]>, "GET" | "DELETE" | "POST" | "PUT", unknown>;
|
package/dist/mcp/schemas.js
CHANGED
|
@@ -339,7 +339,15 @@ exports.publishAppSchema = zod_1.z.object({
|
|
|
339
339
|
app: zod_1.z.string().min(1),
|
|
340
340
|
version: zod_1.z.string().min(1),
|
|
341
341
|
devNotes: zod_1.z.string().optional(),
|
|
342
|
-
target: zod_1.z.union([
|
|
342
|
+
target: zod_1.z.union([
|
|
343
|
+
zod_1.z.literal("store"),
|
|
344
|
+
zod_1.z.literal("repo"),
|
|
345
|
+
zod_1.z.literal("repo-ui"),
|
|
346
|
+
zod_1.z.literal("update-set"),
|
|
347
|
+
]),
|
|
348
|
+
updateSetName: zod_1.z.string().optional(),
|
|
349
|
+
updateSetDescription: zod_1.z.string().optional(),
|
|
350
|
+
includeData: zod_1.z.boolean().optional(),
|
|
343
351
|
confirm: zod_1.z.boolean().optional(),
|
|
344
352
|
dryRun: zod_1.z.boolean().optional(),
|
|
345
353
|
timeoutMs: zod_1.z.number().int().positive().optional(),
|