@tenonhq/dovetail-servicenow 0.0.21 → 0.0.23
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/dist/cli.js +117 -0
- package/dist/client.d.ts +35 -0
- package/dist/client.js +31 -0
- package/dist/hostAssets.d.ts +44 -0
- package/dist/hostAssets.js +579 -0
- package/dist/index.d.ts +5 -4
- package/dist/index.js +8 -1
- package/dist/mcp/registry.d.ts +1 -1
- package/dist/mcp/registry.js +51 -1
- package/dist/mcp/schemas.d.ts +83 -0
- package/dist/mcp/schemas.js +20 -1
- package/dist/table/addColumn.d.ts +98 -0
- package/dist/table/addColumn.js +384 -0
- package/dist/table/formSession.d.ts +18 -6
- package/dist/table/formSession.js +98 -37
- package/dist/table/index.d.ts +3 -1
- package/dist/table/index.js +6 -1
- package/dist/types.d.ts +81 -0
- package/package.json +1 -1
|
@@ -0,0 +1,579 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* hostAssets — deploy a pre-built front-end bundle to ServiceNow.
|
|
4
|
+
*
|
|
5
|
+
* For each chunk in a built dist/ (index.html + assets/*.{js,css}) this:
|
|
6
|
+
* 1. Upserts a carrier sys_ui_script named `app_shell_asset:<vite-relative-path>`.
|
|
7
|
+
* The name carries the rotating hash on purpose — the Scripted REST serving
|
|
8
|
+
* resource resolves an asset request to its carrier by this exact name, so the
|
|
9
|
+
* verb's naming MUST match the path the built index.html references, or the
|
|
10
|
+
* asset 404s on serve.
|
|
11
|
+
* 2. Stores the chunk's bytes as a sys_attachment on that record (the script field
|
|
12
|
+
* caps at 65 KB; real chunks are far larger). Identical bytes are detected by
|
|
13
|
+
* SHA-256 and left in place.
|
|
14
|
+
* 3. Wires an x_cadso_app_shell_m2m_app_script row (application, script, chunk_role,
|
|
15
|
+
* order) so the app shell loads the chunk.
|
|
16
|
+
* 4. Prunes carriers + m2m rows for chunks no longer in the build — hashes rotate
|
|
17
|
+
* every build, so last build's records would otherwise pile up.
|
|
18
|
+
*
|
|
19
|
+
* The sys_ui_script + m2m writes route through the Dovetail Scripted REST API and are
|
|
20
|
+
* captured in the target update set. Attachments are data, not customizations, so they
|
|
21
|
+
* are not part of the update set. Re-runnable per build, per instance; idempotent for
|
|
22
|
+
* an identical dist/.
|
|
23
|
+
*
|
|
24
|
+
* The serving layer streams each chunk via GlideSysAttachment.getContentStream(), capped
|
|
25
|
+
* at ~5 MB (glide.scriptable.excel.max_file_size). A chunk at/over the cap truncates on
|
|
26
|
+
* serve, so the verb fails fast on an oversize chunk unless allowOversize is set.
|
|
27
|
+
*/
|
|
28
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
29
|
+
if (k2 === undefined) k2 = k;
|
|
30
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
31
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
32
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
33
|
+
}
|
|
34
|
+
Object.defineProperty(o, k2, desc);
|
|
35
|
+
}) : (function(o, m, k, k2) {
|
|
36
|
+
if (k2 === undefined) k2 = k;
|
|
37
|
+
o[k2] = m[k];
|
|
38
|
+
}));
|
|
39
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
40
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
41
|
+
}) : function(o, v) {
|
|
42
|
+
o["default"] = v;
|
|
43
|
+
});
|
|
44
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
45
|
+
var ownKeys = function(o) {
|
|
46
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
47
|
+
var ar = [];
|
|
48
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
49
|
+
return ar;
|
|
50
|
+
};
|
|
51
|
+
return ownKeys(o);
|
|
52
|
+
};
|
|
53
|
+
return function (mod) {
|
|
54
|
+
if (mod && mod.__esModule) return mod;
|
|
55
|
+
var result = {};
|
|
56
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
57
|
+
__setModuleDefault(result, mod);
|
|
58
|
+
return result;
|
|
59
|
+
};
|
|
60
|
+
})();
|
|
61
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
62
|
+
exports.classifyChunks = classifyChunks;
|
|
63
|
+
exports.hostAssets = hostAssets;
|
|
64
|
+
exports.formatHostAssetsResult = formatHostAssetsResult;
|
|
65
|
+
const fs = __importStar(require("fs"));
|
|
66
|
+
const path = __importStar(require("path"));
|
|
67
|
+
const crypto = __importStar(require("crypto"));
|
|
68
|
+
var SCRIPT_TABLE = "sys_ui_script";
|
|
69
|
+
var M2M_TABLE = "x_cadso_app_shell_m2m_app_script";
|
|
70
|
+
var ASSET_NAME_PREFIX = "app_shell_asset:";
|
|
71
|
+
/** ~5 MB serving cap (glide.scriptable.excel.max_file_size). Conservative MiB reading. */
|
|
72
|
+
var DEFAULT_MAX_CHUNK_BYTES = 5 * 1024 * 1024;
|
|
73
|
+
var CONTENT_TYPES = {
|
|
74
|
+
js: "application/javascript",
|
|
75
|
+
css: "text/css",
|
|
76
|
+
html: "text/html",
|
|
77
|
+
};
|
|
78
|
+
var ROLE_PRIORITY = {
|
|
79
|
+
index: 0,
|
|
80
|
+
entry: 1,
|
|
81
|
+
vendor: 2,
|
|
82
|
+
router: 3,
|
|
83
|
+
state: 4,
|
|
84
|
+
lazy: 5,
|
|
85
|
+
style: 6,
|
|
86
|
+
};
|
|
87
|
+
// A trailing `-<hash>` of 8+ url-safe chars (Vite's content hash). Used only to derive
|
|
88
|
+
// the base name for role inference — never to name the carrier record.
|
|
89
|
+
var HASH_RE = /^(.+)-[A-Za-z0-9_]{8,}$/;
|
|
90
|
+
function stripHash(nameNoExt) {
|
|
91
|
+
var m = HASH_RE.exec(nameNoExt);
|
|
92
|
+
return m ? m[1] : nameNoExt;
|
|
93
|
+
}
|
|
94
|
+
function roleForBase(base, ext, isIndexHtml) {
|
|
95
|
+
if (isIndexHtml)
|
|
96
|
+
return "index";
|
|
97
|
+
if (ext === "css")
|
|
98
|
+
return "style";
|
|
99
|
+
var b = base.toLowerCase();
|
|
100
|
+
if (b === "index" || b === "main" || b === "entry" || b === "app") {
|
|
101
|
+
return "entry";
|
|
102
|
+
}
|
|
103
|
+
if (b.indexOf("vendor") >= 0)
|
|
104
|
+
return "vendor";
|
|
105
|
+
if (b === "router" || b === "routes" || b === "route")
|
|
106
|
+
return "router";
|
|
107
|
+
if (b === "store" ||
|
|
108
|
+
b === "state" ||
|
|
109
|
+
b === "redux" ||
|
|
110
|
+
b === "pinia" ||
|
|
111
|
+
b === "vuex") {
|
|
112
|
+
return "state";
|
|
113
|
+
}
|
|
114
|
+
return "lazy";
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Classify build files into ordered ChunkInfo. Pure — no filesystem access — so the
|
|
118
|
+
* naming + role + order logic is unit-testable in isolation.
|
|
119
|
+
*/
|
|
120
|
+
function classifyChunks(files) {
|
|
121
|
+
var infos = files.map(function (f) {
|
|
122
|
+
var lowerExt = f.ext.toLowerCase();
|
|
123
|
+
var fileName = f.viteRelPath.split("/").pop() || f.viteRelPath;
|
|
124
|
+
var nameNoExt = fileName.slice(0, fileName.length - (lowerExt.length + 1));
|
|
125
|
+
var base = f.isIndexHtml ? "index" : stripHash(nameNoExt);
|
|
126
|
+
var role = roleForBase(base, lowerExt, f.isIndexHtml);
|
|
127
|
+
return {
|
|
128
|
+
viteRelPath: f.viteRelPath,
|
|
129
|
+
name: ASSET_NAME_PREFIX + f.viteRelPath,
|
|
130
|
+
fileName: fileName,
|
|
131
|
+
base: base,
|
|
132
|
+
ext: lowerExt,
|
|
133
|
+
role: role,
|
|
134
|
+
order: 0,
|
|
135
|
+
contentType: CONTENT_TYPES[lowerExt] || "application/octet-stream",
|
|
136
|
+
};
|
|
137
|
+
});
|
|
138
|
+
// Deterministic order: role priority, then relative path. Gap-free integers.
|
|
139
|
+
infos.sort(function (a, b) {
|
|
140
|
+
var pa = ROLE_PRIORITY[a.role];
|
|
141
|
+
var pb = ROLE_PRIORITY[b.role];
|
|
142
|
+
if (pa !== pb)
|
|
143
|
+
return pa - pb;
|
|
144
|
+
return a.viteRelPath < b.viteRelPath
|
|
145
|
+
? -1
|
|
146
|
+
: a.viteRelPath > b.viteRelPath
|
|
147
|
+
? 1
|
|
148
|
+
: 0;
|
|
149
|
+
});
|
|
150
|
+
infos.forEach(function (info, i) {
|
|
151
|
+
info.order = i;
|
|
152
|
+
});
|
|
153
|
+
return infos;
|
|
154
|
+
}
|
|
155
|
+
function readDistChunks(dir) {
|
|
156
|
+
if (!fs.existsSync(dir)) {
|
|
157
|
+
throw new Error("host-assets: dist directory not found: " + dir);
|
|
158
|
+
}
|
|
159
|
+
var found = [];
|
|
160
|
+
var indexHtml = path.join(dir, "index.html");
|
|
161
|
+
if (fs.existsSync(indexHtml)) {
|
|
162
|
+
found.push({
|
|
163
|
+
viteRelPath: "index.html",
|
|
164
|
+
ext: "html",
|
|
165
|
+
isIndexHtml: true,
|
|
166
|
+
abs: indexHtml,
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
var assetsDir = path.join(dir, "assets");
|
|
170
|
+
if (fs.existsSync(assetsDir)) {
|
|
171
|
+
fs.readdirSync(assetsDir).forEach(function (name) {
|
|
172
|
+
var ext = path.extname(name).replace(/^\./, "").toLowerCase();
|
|
173
|
+
if (ext !== "js" && ext !== "css")
|
|
174
|
+
return;
|
|
175
|
+
found.push({
|
|
176
|
+
viteRelPath: "assets/" + name,
|
|
177
|
+
ext: ext,
|
|
178
|
+
isIndexHtml: false,
|
|
179
|
+
abs: path.join(assetsDir, name),
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
if (found.length === 0) {
|
|
184
|
+
throw new Error("host-assets: no index.html or assets/*.{js,css} found under " + dir);
|
|
185
|
+
}
|
|
186
|
+
var infos = classifyChunks(found.map(function (f) {
|
|
187
|
+
return {
|
|
188
|
+
viteRelPath: f.viteRelPath,
|
|
189
|
+
ext: f.ext,
|
|
190
|
+
isIndexHtml: f.isIndexHtml,
|
|
191
|
+
};
|
|
192
|
+
}));
|
|
193
|
+
var absByPath = {};
|
|
194
|
+
found.forEach(function (f) {
|
|
195
|
+
absByPath[f.viteRelPath] = f.abs;
|
|
196
|
+
});
|
|
197
|
+
return infos.map(function (info) {
|
|
198
|
+
return { info: info, data: fs.readFileSync(absByPath[info.viteRelPath]) };
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
function guardOversize(chunks, maxBytes, allowOversize) {
|
|
202
|
+
var oversize = chunks.filter(function (c) {
|
|
203
|
+
return c.data.length >= maxBytes;
|
|
204
|
+
});
|
|
205
|
+
if (oversize.length === 0)
|
|
206
|
+
return;
|
|
207
|
+
var detail = oversize
|
|
208
|
+
.map(function (c) {
|
|
209
|
+
return c.info.viteRelPath + " (" + c.data.length + "B)";
|
|
210
|
+
})
|
|
211
|
+
.join(", ");
|
|
212
|
+
var msg = "host-assets: " +
|
|
213
|
+
oversize.length +
|
|
214
|
+
" chunk(s) at/over the " +
|
|
215
|
+
maxBytes +
|
|
216
|
+
"B serve cap (glide.scriptable.excel.max_file_size) — these truncate on serve: " +
|
|
217
|
+
detail +
|
|
218
|
+
". Split deps via Vite manualChunks, or pass allowOversize to proceed anyway.";
|
|
219
|
+
if (!allowOversize)
|
|
220
|
+
throw new Error(msg);
|
|
221
|
+
// eslint-disable-next-line no-console
|
|
222
|
+
console.warn("[host-assets] WARNING — " + msg);
|
|
223
|
+
}
|
|
224
|
+
function sha256Hex(buf) {
|
|
225
|
+
return crypto.createHash("sha256").update(buf).digest("hex");
|
|
226
|
+
}
|
|
227
|
+
async function resolveScopeSysId(client, namespace) {
|
|
228
|
+
var rows = await client.table.query("sys_scope", "scope=" + namespace, 1);
|
|
229
|
+
if (rows.length === 0) {
|
|
230
|
+
throw new Error("host-assets: scope '" + namespace + "' not found in sys_scope.");
|
|
231
|
+
}
|
|
232
|
+
return rows[0].sys_id;
|
|
233
|
+
}
|
|
234
|
+
async function fetchUpdateSet(client, sysId) {
|
|
235
|
+
var rows = await client.table.query("sys_update_set", "sys_id=" + sysId, 1);
|
|
236
|
+
if (rows.length === 0) {
|
|
237
|
+
throw new Error("host-assets: update set " + sysId + " not found.");
|
|
238
|
+
}
|
|
239
|
+
var row = rows[0];
|
|
240
|
+
if (row.state && row.state !== "in progress" && row.state !== "in_progress") {
|
|
241
|
+
throw new Error("host-assets: update set " +
|
|
242
|
+
row.name +
|
|
243
|
+
" is '" +
|
|
244
|
+
row.state +
|
|
245
|
+
"' — only an in-progress update set can capture changes.");
|
|
246
|
+
}
|
|
247
|
+
return { sys_id: row.sys_id, name: row.name };
|
|
248
|
+
}
|
|
249
|
+
function scriptFields(info, scopeSysId) {
|
|
250
|
+
return {
|
|
251
|
+
name: info.name,
|
|
252
|
+
// Bytes live on the attachment; the script field caps at 65 KB.
|
|
253
|
+
script: "// Hosted front-end asset — bytes stored as a sys_attachment on this record.\n" +
|
|
254
|
+
"// path=" +
|
|
255
|
+
info.viteRelPath +
|
|
256
|
+
" role=" +
|
|
257
|
+
info.role,
|
|
258
|
+
active: "true",
|
|
259
|
+
sys_scope: scopeSysId,
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
function scriptUnchanged(existing, info) {
|
|
263
|
+
var fields = scriptFields(info, existing.sys_id);
|
|
264
|
+
return (existing.name === info.name &&
|
|
265
|
+
String(existing.active) === "true" &&
|
|
266
|
+
existing.script === fields.script);
|
|
267
|
+
}
|
|
268
|
+
/** Reconcile one chunk's carrier script + attachment + m2m row. */
|
|
269
|
+
async function reconcileChunk(opts) {
|
|
270
|
+
var client = opts.client;
|
|
271
|
+
var info = opts.chunk.info;
|
|
272
|
+
var data = opts.chunk.data;
|
|
273
|
+
var dryRun = opts.dryRun;
|
|
274
|
+
// ── carrier sys_ui_script ────────────────────────────────────────────────
|
|
275
|
+
var existingScript = opts.scriptByName[info.name];
|
|
276
|
+
var scriptSysId = existingScript ? existingScript.sys_id : "";
|
|
277
|
+
var scriptAction;
|
|
278
|
+
if (!existingScript) {
|
|
279
|
+
scriptAction = "created";
|
|
280
|
+
if (!dryRun) {
|
|
281
|
+
var created = await client.claude.createRecord({
|
|
282
|
+
table: SCRIPT_TABLE,
|
|
283
|
+
fields: scriptFields(info, opts.scopeSysId),
|
|
284
|
+
scope: opts.scope,
|
|
285
|
+
update_set_sys_id: opts.updateSetSysId,
|
|
286
|
+
});
|
|
287
|
+
scriptSysId = created.sys_id;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
else if (scriptUnchanged(existingScript, info)) {
|
|
291
|
+
scriptAction = "unchanged";
|
|
292
|
+
}
|
|
293
|
+
else {
|
|
294
|
+
scriptAction = "updated";
|
|
295
|
+
if (!dryRun) {
|
|
296
|
+
await client.claude.pushWithUpdateSet({
|
|
297
|
+
update_set_sys_id: opts.updateSetSysId,
|
|
298
|
+
table: SCRIPT_TABLE,
|
|
299
|
+
record_sys_id: existingScript.sys_id,
|
|
300
|
+
fields: scriptFields(info, opts.scopeSysId),
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
// ── attachment (replace by content hash) ──────────────────────────────────
|
|
305
|
+
var attachmentSysId = "";
|
|
306
|
+
var attachmentAction = "uploaded";
|
|
307
|
+
if (dryRun || !scriptSysId) {
|
|
308
|
+
// Cannot read/write attachments without a concrete record sys_id.
|
|
309
|
+
attachmentAction = existingScript ? "replaced" : "uploaded";
|
|
310
|
+
}
|
|
311
|
+
else {
|
|
312
|
+
var newHash = sha256Hex(data);
|
|
313
|
+
var existingAtts = await client.attachment.listFor({
|
|
314
|
+
table: SCRIPT_TABLE,
|
|
315
|
+
sysId: scriptSysId,
|
|
316
|
+
});
|
|
317
|
+
var matching = existingAtts.filter(function (a) {
|
|
318
|
+
return a.hash && a.hash.toLowerCase() === newHash;
|
|
319
|
+
});
|
|
320
|
+
if (matching.length > 0) {
|
|
321
|
+
attachmentSysId = matching[0].sys_id;
|
|
322
|
+
attachmentAction = "unchanged";
|
|
323
|
+
// Drop any stale or duplicate attachments so exactly one carrier remains.
|
|
324
|
+
var extras = existingAtts.filter(function (a) {
|
|
325
|
+
return a.sys_id !== matching[0].sys_id;
|
|
326
|
+
});
|
|
327
|
+
for (var e = 0; e < extras.length; e += 1) {
|
|
328
|
+
await client.attachment.remove({ sysId: extras[e].sys_id });
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
else {
|
|
332
|
+
for (var d = 0; d < existingAtts.length; d += 1) {
|
|
333
|
+
await client.attachment.remove({ sysId: existingAtts[d].sys_id });
|
|
334
|
+
}
|
|
335
|
+
var uploaded = await client.attachment.upload({
|
|
336
|
+
table: SCRIPT_TABLE,
|
|
337
|
+
sysId: scriptSysId,
|
|
338
|
+
fileName: info.fileName,
|
|
339
|
+
contentType: info.contentType,
|
|
340
|
+
data: data,
|
|
341
|
+
});
|
|
342
|
+
attachmentSysId = uploaded.sys_id;
|
|
343
|
+
attachmentAction = existingAtts.length > 0 ? "replaced" : "uploaded";
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
// ── m2m wiring ────────────────────────────────────────────────────────────
|
|
347
|
+
var existingM2m = scriptSysId ? opts.m2mByScript[scriptSysId] : undefined;
|
|
348
|
+
var m2mSysId = existingM2m ? existingM2m.sys_id : "";
|
|
349
|
+
var m2mAction;
|
|
350
|
+
var m2mFields = {
|
|
351
|
+
application: opts.app,
|
|
352
|
+
script: scriptSysId,
|
|
353
|
+
chunk_role: info.role,
|
|
354
|
+
order: String(info.order),
|
|
355
|
+
sys_scope: opts.scopeSysId,
|
|
356
|
+
};
|
|
357
|
+
if (existingM2m) {
|
|
358
|
+
var m2mChanged = existingM2m.chunk_role !== info.role ||
|
|
359
|
+
String(existingM2m.order) !== String(info.order);
|
|
360
|
+
m2mAction = m2mChanged ? "updated" : "unchanged";
|
|
361
|
+
if (m2mChanged && !dryRun) {
|
|
362
|
+
await client.claude.pushWithUpdateSet({
|
|
363
|
+
update_set_sys_id: opts.updateSetSysId,
|
|
364
|
+
table: M2M_TABLE,
|
|
365
|
+
record_sys_id: existingM2m.sys_id,
|
|
366
|
+
fields: { chunk_role: info.role, order: String(info.order) },
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
else {
|
|
371
|
+
m2mAction = "created";
|
|
372
|
+
if (!dryRun && scriptSysId) {
|
|
373
|
+
var m2mCreated = await client.claude.createRecord({
|
|
374
|
+
table: M2M_TABLE,
|
|
375
|
+
fields: m2mFields,
|
|
376
|
+
scope: opts.scope,
|
|
377
|
+
update_set_sys_id: opts.updateSetSysId,
|
|
378
|
+
});
|
|
379
|
+
m2mSysId = m2mCreated.sys_id;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
// ── read-back verify ──────────────────────────────────────────────────────
|
|
383
|
+
var verified = false;
|
|
384
|
+
if (!dryRun && scriptSysId) {
|
|
385
|
+
var back = await client.table.query(SCRIPT_TABLE, "sys_id=" + scriptSysId, 1);
|
|
386
|
+
var atts = await client.attachment.listFor({
|
|
387
|
+
table: SCRIPT_TABLE,
|
|
388
|
+
sysId: scriptSysId,
|
|
389
|
+
});
|
|
390
|
+
verified = back.length > 0 && atts.length > 0;
|
|
391
|
+
}
|
|
392
|
+
return {
|
|
393
|
+
name: info.name,
|
|
394
|
+
viteRelPath: info.viteRelPath,
|
|
395
|
+
base: info.base,
|
|
396
|
+
role: info.role,
|
|
397
|
+
order: info.order,
|
|
398
|
+
contentType: info.contentType,
|
|
399
|
+
bytes: data.length,
|
|
400
|
+
scriptSysId: scriptSysId,
|
|
401
|
+
scriptAction: scriptAction,
|
|
402
|
+
attachmentSysId: attachmentSysId,
|
|
403
|
+
attachmentAction: attachmentAction,
|
|
404
|
+
m2mSysId: m2mSysId,
|
|
405
|
+
m2mAction: m2mAction,
|
|
406
|
+
verified: verified,
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
/** Remove carriers + m2m rows for chunks no longer in the build. */
|
|
410
|
+
async function pruneRemoved(opts) {
|
|
411
|
+
var client = opts.client;
|
|
412
|
+
var pruned = [];
|
|
413
|
+
for (var i = 0; i < opts.ownedM2m.length; i += 1) {
|
|
414
|
+
var row = opts.ownedM2m[i];
|
|
415
|
+
var scriptId = String(row.script);
|
|
416
|
+
var script = opts.scriptById[scriptId];
|
|
417
|
+
var name = script ? script.name : "";
|
|
418
|
+
// Carrier still in the build (by name) — keep it.
|
|
419
|
+
if (name && opts.keepNames[name])
|
|
420
|
+
continue;
|
|
421
|
+
var scriptDeleted = false;
|
|
422
|
+
if (!opts.dryRun) {
|
|
423
|
+
// Unwire this app first.
|
|
424
|
+
await client.claude.deleteRecord({
|
|
425
|
+
table: M2M_TABLE,
|
|
426
|
+
sys_id: row.sys_id,
|
|
427
|
+
});
|
|
428
|
+
// Delete the carrier only if no other app's m2m still references it.
|
|
429
|
+
if (scriptId) {
|
|
430
|
+
var others = await client.table.query(M2M_TABLE, "script=" + scriptId + "^application!=" + opts.app, 1);
|
|
431
|
+
if (others.length === 0) {
|
|
432
|
+
var atts = await client.attachment.listFor({
|
|
433
|
+
table: SCRIPT_TABLE,
|
|
434
|
+
sysId: scriptId,
|
|
435
|
+
});
|
|
436
|
+
for (var a = 0; a < atts.length; a += 1) {
|
|
437
|
+
await client.attachment.remove({ sysId: atts[a].sys_id });
|
|
438
|
+
}
|
|
439
|
+
await client.claude.deleteRecord({
|
|
440
|
+
table: SCRIPT_TABLE,
|
|
441
|
+
sys_id: scriptId,
|
|
442
|
+
});
|
|
443
|
+
scriptDeleted = true;
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
pruned.push({
|
|
448
|
+
name: name || "(unknown)",
|
|
449
|
+
scriptSysId: scriptId,
|
|
450
|
+
m2mSysId: row.sys_id,
|
|
451
|
+
scriptDeleted: scriptDeleted,
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
return pruned;
|
|
455
|
+
}
|
|
456
|
+
/**
|
|
457
|
+
* Deploy a built dist/ to ServiceNow as app-shell carrier scripts + attachments + m2m
|
|
458
|
+
* wiring. Idempotent; re-running an identical dist/ reports every chunk unchanged.
|
|
459
|
+
*/
|
|
460
|
+
async function hostAssets(client, params) {
|
|
461
|
+
if (!params.dir)
|
|
462
|
+
throw new Error("host-assets: dir is required.");
|
|
463
|
+
if (!/^[0-9a-f]{32}$/i.test(params.app || "")) {
|
|
464
|
+
throw new Error("host-assets: app must be a 32-character application record sys_id.");
|
|
465
|
+
}
|
|
466
|
+
if (!params.scope) {
|
|
467
|
+
throw new Error("host-assets: scope is required (e.g. x_cadso_app_shell).");
|
|
468
|
+
}
|
|
469
|
+
var dryRun = params.dryRun === true;
|
|
470
|
+
var maxBytes = typeof params.maxBytes === "number" && params.maxBytes > 0
|
|
471
|
+
? params.maxBytes
|
|
472
|
+
: DEFAULT_MAX_CHUNK_BYTES;
|
|
473
|
+
var chunks = readDistChunks(params.dir);
|
|
474
|
+
guardOversize(chunks, maxBytes, params.allowOversize === true);
|
|
475
|
+
var scopeSysId = await resolveScopeSysId(client, params.scope);
|
|
476
|
+
var updateSetSysId = params.updateSetSysId || "";
|
|
477
|
+
var updateSetName = "";
|
|
478
|
+
if (updateSetSysId) {
|
|
479
|
+
var us = await fetchUpdateSet(client, updateSetSysId);
|
|
480
|
+
updateSetName = us.name;
|
|
481
|
+
}
|
|
482
|
+
else {
|
|
483
|
+
var cur = await client.claude.currentUpdateSet(params.scope);
|
|
484
|
+
updateSetSysId = cur.sys_id;
|
|
485
|
+
updateSetName = cur.name;
|
|
486
|
+
}
|
|
487
|
+
if (!updateSetSysId) {
|
|
488
|
+
throw new Error("host-assets: could not resolve a target update set for scope " +
|
|
489
|
+
params.scope +
|
|
490
|
+
".");
|
|
491
|
+
}
|
|
492
|
+
// Existing state: every asset carrier (global, by name prefix) and this app's wiring.
|
|
493
|
+
var allCarriers = await client.table.query(SCRIPT_TABLE, "nameSTARTSWITH" + ASSET_NAME_PREFIX, 2000);
|
|
494
|
+
var scriptByName = {};
|
|
495
|
+
var scriptById = {};
|
|
496
|
+
allCarriers.forEach(function (s) {
|
|
497
|
+
scriptByName[s.name] = s;
|
|
498
|
+
scriptById[s.sys_id] = s;
|
|
499
|
+
});
|
|
500
|
+
var ownedM2m = await client.table.query(M2M_TABLE, "application=" + params.app, 2000);
|
|
501
|
+
var m2mByScript = {};
|
|
502
|
+
ownedM2m.forEach(function (r) {
|
|
503
|
+
m2mByScript[String(r.script)] = r;
|
|
504
|
+
});
|
|
505
|
+
var results = [];
|
|
506
|
+
for (var i = 0; i < chunks.length; i += 1) {
|
|
507
|
+
var r = await reconcileChunk({
|
|
508
|
+
client: client,
|
|
509
|
+
chunk: chunks[i],
|
|
510
|
+
app: params.app,
|
|
511
|
+
scope: params.scope,
|
|
512
|
+
scopeSysId: scopeSysId,
|
|
513
|
+
updateSetSysId: updateSetSysId,
|
|
514
|
+
dryRun: dryRun,
|
|
515
|
+
scriptByName: scriptByName,
|
|
516
|
+
m2mByScript: m2mByScript,
|
|
517
|
+
});
|
|
518
|
+
results.push(r);
|
|
519
|
+
}
|
|
520
|
+
var keepNames = {};
|
|
521
|
+
chunks.forEach(function (c) {
|
|
522
|
+
keepNames[c.info.name] = true;
|
|
523
|
+
});
|
|
524
|
+
var pruned = await pruneRemoved({
|
|
525
|
+
client: client,
|
|
526
|
+
app: params.app,
|
|
527
|
+
dryRun: dryRun,
|
|
528
|
+
keepNames: keepNames,
|
|
529
|
+
ownedM2m: ownedM2m,
|
|
530
|
+
scriptById: scriptById,
|
|
531
|
+
});
|
|
532
|
+
return {
|
|
533
|
+
scope: params.scope,
|
|
534
|
+
app: params.app,
|
|
535
|
+
updateSet: { sysId: updateSetSysId, name: updateSetName },
|
|
536
|
+
dryRun: dryRun,
|
|
537
|
+
chunks: results,
|
|
538
|
+
pruned: pruned,
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
/** Human-readable one-line-per-chunk summary for the CLI. */
|
|
542
|
+
function formatHostAssetsResult(r) {
|
|
543
|
+
var lines = [];
|
|
544
|
+
lines.push((r.dryRun ? "[dry-run] " : "") +
|
|
545
|
+
"host-assets → scope=" +
|
|
546
|
+
r.scope +
|
|
547
|
+
" app=" +
|
|
548
|
+
r.app +
|
|
549
|
+
" update-set=" +
|
|
550
|
+
r.updateSet.name +
|
|
551
|
+
" (" +
|
|
552
|
+
r.chunks.length +
|
|
553
|
+
" chunks)");
|
|
554
|
+
r.chunks.forEach(function (c) {
|
|
555
|
+
lines.push(" [" +
|
|
556
|
+
c.role +
|
|
557
|
+
"] " +
|
|
558
|
+
c.viteRelPath +
|
|
559
|
+
" — script:" +
|
|
560
|
+
c.scriptAction +
|
|
561
|
+
" attach:" +
|
|
562
|
+
c.attachmentAction +
|
|
563
|
+
" m2m:" +
|
|
564
|
+
c.m2mAction +
|
|
565
|
+
" (" +
|
|
566
|
+
c.bytes +
|
|
567
|
+
"B)" +
|
|
568
|
+
(r.dryRun ? "" : c.verified ? " ✓" : " ⚠ unverified"));
|
|
569
|
+
});
|
|
570
|
+
if (r.pruned.length > 0) {
|
|
571
|
+
lines.push(" pruned: " +
|
|
572
|
+
r.pruned
|
|
573
|
+
.map(function (p) {
|
|
574
|
+
return p.name + (p.scriptDeleted ? "" : " (m2m only)");
|
|
575
|
+
})
|
|
576
|
+
.join(", "));
|
|
577
|
+
}
|
|
578
|
+
return lines.join("\n");
|
|
579
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -6,8 +6,9 @@
|
|
|
6
6
|
*/
|
|
7
7
|
export { createClient } from "./client";
|
|
8
8
|
export { createClientFromEnvFile, resolveConfigFromEnvFile } from "./createClientFromEnvFile";
|
|
9
|
-
export type { ServiceNowClient, TableQueryOptions, TableSchema, TableSchemaField } from "./client";
|
|
9
|
+
export type { ServiceNowClient, TableQueryOptions, TableSchema, TableSchemaField, AttachmentMeta } from "./client";
|
|
10
10
|
export { addChoicesToField } from "./choices";
|
|
11
|
+
export { hostAssets, classifyChunks, formatHostAssetsResult } from "./hostAssets";
|
|
11
12
|
export { formatAddChoicesResult } from "./formatter";
|
|
12
13
|
export { createView } from "./layout/views";
|
|
13
14
|
export { setListLayout } from "./layout/listLayout";
|
|
@@ -17,6 +18,6 @@ export { formatLayoutResult, formatCreateViewResult } from "./layout/formatter";
|
|
|
17
18
|
export { sincPlugin } from "./plugin";
|
|
18
19
|
export { listTemplates, verifyArtifact, cloneSubflow, cloneActionType, triggerPublication, publishActionType, editActionType, readFlow, readActionType, publishFlow, copyFlow, createFlow, buildPublishModel, editFlow, testFlow, DEFAULT_RUN_FLOW_PATH, generateSysId, topoSort, executeWritePlan, WriteOrderError } from "./flowDesigner";
|
|
19
20
|
export type { TemplateRef, ListTemplatesParams, FlowKind, VerifyExpect, VerifyFound, VerifyFailure, VerifyReport, VerifyArtifactParams, CloneSubflowParams, CloneSubflowResult, CloneActionTypeParams, CloneActionTypeResult, TriggerPublicationParams, TriggerPublicationResult, PublishActionTypeParams, PublishActionTypeResult, EditActionTypeParams, EditActionTypeResult, EditActionTypeOps, ReadFlowParams, ReadFlowResult, FlowStep, FlowVariable, ReadActionTypeParams, ReadActionTypeResult, ActionIo, PublishFlowParams, PublishFlowResult, CopyFlowParams, CopyFlowResult, CreateFlowParams, CreateFlowResult, EditFlowParams, EditFlowResult, EditFlowOps, StepInputPatch, TestFlowParams, TestFlowResult, WriteOp, WriteOpResult } from "./flowDesigner";
|
|
20
|
-
export type { ServiceNowClientConfig, ChoiceValue, ChoiceType, AddChoicesParams, AddChoicesResult, ChoiceActionResult, DictionaryRecord, UpdateSetRecord, LayoutAction, LayoutRecordResult, LayoutResult, CreateViewParams, CreateViewResult, FormSectionSpec, SetFormLayoutParams, SetListLayoutParams, SetRelatedListsParams } from "./types";
|
|
21
|
-
export { createTable, projectTableGraph, buildColumnXml, normalizeColumns, resolveType, applyTableSaveOverlay, defaultAccessFlags, TYPE_MAP, DEFAULT_SUPER_CLASS, DEFAULT_SAVE_ACTION } from "./table";
|
|
22
|
-
export type { CreateTableParams, CreateTableResult, TableGraph, NormalizedColumn, ColumnSpec, AccessFlags, OverlaySpec } from "./table";
|
|
21
|
+
export type { ServiceNowClientConfig, ChoiceValue, ChoiceType, AddChoicesParams, AddChoicesResult, ChoiceActionResult, DictionaryRecord, UpdateSetRecord, LayoutAction, LayoutRecordResult, LayoutResult, CreateViewParams, CreateViewResult, FormSectionSpec, SetFormLayoutParams, SetListLayoutParams, SetRelatedListsParams, ChunkRole, ChunkInfo, ChunkResult, PrunedResult, HostAssetsParams, HostAssetsResult } from "./types";
|
|
22
|
+
export { createTable, projectTableGraph, buildColumnXml, normalizeColumns, resolveType, applyTableSaveOverlay, defaultAccessFlags, TYPE_MAP, DEFAULT_SUPER_CLASS, DEFAULT_SAVE_ACTION, addColumn, deriveElement, applyAddColumnOverlay } from "./table";
|
|
23
|
+
export type { CreateTableParams, CreateTableResult, AddColumnParams, AddColumnResult, TableGraph, NormalizedColumn, ColumnSpec, AccessFlags, OverlaySpec } from "./table";
|
package/dist/index.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* REST API so every change lands in the target update set and scope.
|
|
7
7
|
*/
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
-
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.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.addChoicesToField = exports.resolveConfigFromEnvFile = exports.createClientFromEnvFile = exports.createClient = void 0;
|
|
9
|
+
exports.applyAddColumnOverlay = exports.deriveElement = 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.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
10
|
var client_1 = require("./client");
|
|
11
11
|
Object.defineProperty(exports, "createClient", { enumerable: true, get: function () { return client_1.createClient; } });
|
|
12
12
|
var createClientFromEnvFile_1 = require("./createClientFromEnvFile");
|
|
@@ -14,6 +14,10 @@ Object.defineProperty(exports, "createClientFromEnvFile", { enumerable: true, ge
|
|
|
14
14
|
Object.defineProperty(exports, "resolveConfigFromEnvFile", { enumerable: true, get: function () { return createClientFromEnvFile_1.resolveConfigFromEnvFile; } });
|
|
15
15
|
var choices_1 = require("./choices");
|
|
16
16
|
Object.defineProperty(exports, "addChoicesToField", { enumerable: true, get: function () { return choices_1.addChoicesToField; } });
|
|
17
|
+
var hostAssets_1 = require("./hostAssets");
|
|
18
|
+
Object.defineProperty(exports, "hostAssets", { enumerable: true, get: function () { return hostAssets_1.hostAssets; } });
|
|
19
|
+
Object.defineProperty(exports, "classifyChunks", { enumerable: true, get: function () { return hostAssets_1.classifyChunks; } });
|
|
20
|
+
Object.defineProperty(exports, "formatHostAssetsResult", { enumerable: true, get: function () { return hostAssets_1.formatHostAssetsResult; } });
|
|
17
21
|
var formatter_1 = require("./formatter");
|
|
18
22
|
Object.defineProperty(exports, "formatAddChoicesResult", { enumerable: true, get: function () { return formatter_1.formatAddChoicesResult; } });
|
|
19
23
|
var views_1 = require("./layout/views");
|
|
@@ -61,3 +65,6 @@ Object.defineProperty(exports, "defaultAccessFlags", { enumerable: true, get: fu
|
|
|
61
65
|
Object.defineProperty(exports, "TYPE_MAP", { enumerable: true, get: function () { return table_1.TYPE_MAP; } });
|
|
62
66
|
Object.defineProperty(exports, "DEFAULT_SUPER_CLASS", { enumerable: true, get: function () { return table_1.DEFAULT_SUPER_CLASS; } });
|
|
63
67
|
Object.defineProperty(exports, "DEFAULT_SAVE_ACTION", { enumerable: true, get: function () { return table_1.DEFAULT_SAVE_ACTION; } });
|
|
68
|
+
Object.defineProperty(exports, "addColumn", { enumerable: true, get: function () { return table_1.addColumn; } });
|
|
69
|
+
Object.defineProperty(exports, "deriveElement", { enumerable: true, get: function () { return table_1.deriveElement; } });
|
|
70
|
+
Object.defineProperty(exports, "applyAddColumnOverlay", { enumerable: true, get: function () { return table_1.applyAddColumnOverlay; } });
|
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", "flow_publish", "flow_copy", "flow_create", "flow_test", "flow_edit", "create_table"];
|
|
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", "flow_publish", "flow_copy", "flow_create", "flow_test", "flow_edit", "create_table", "add_column", "host_assets"];
|
|
14
14
|
export type ToolName = typeof TOOL_NAMES[number];
|
|
15
15
|
export interface RegistryDeps {
|
|
16
16
|
/** Optional client injection for tests; defaults to createClient({}). */
|