@tenonhq/dovetail-core 0.0.85

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.
Files changed (78) hide show
  1. package/README.md +6 -0
  2. package/dist/FileLogger.js +207 -0
  3. package/dist/FileUtils.js +387 -0
  4. package/dist/Logger.js +88 -0
  5. package/dist/MultiScopeWatcher.js +615 -0
  6. package/dist/PluginManager.js +125 -0
  7. package/dist/_defaultOptionsOLD.js +44 -0
  8. package/dist/allScopesCommands.js +418 -0
  9. package/dist/appUtils.js +929 -0
  10. package/dist/benchmark.js +104 -0
  11. package/dist/bootstrap.js +62 -0
  12. package/dist/claudeCommand.js +60 -0
  13. package/dist/clickupCommands.js +472 -0
  14. package/dist/clickupPushHelper.js +33 -0
  15. package/dist/commander.js +466 -0
  16. package/dist/commands.js +378 -0
  17. package/dist/config.js +697 -0
  18. package/dist/constants.js +15 -0
  19. package/dist/createRecordCommand.js +314 -0
  20. package/dist/dashboardCommand.js +38 -0
  21. package/dist/defaultOptions.js +14 -0
  22. package/dist/deleteRecordCommand.js +282 -0
  23. package/dist/flowDesigner/values.js +55 -0
  24. package/dist/genericUtils.js +189 -0
  25. package/dist/gitUtils.js +99 -0
  26. package/dist/index.js +35 -0
  27. package/dist/initSystem/corePlugin.js +344 -0
  28. package/dist/initSystem/discovery.js +107 -0
  29. package/dist/initSystem/orchestrator.js +408 -0
  30. package/dist/logMessages.js +82 -0
  31. package/dist/loginCommand.js +20 -0
  32. package/dist/migrateCommand.js +123 -0
  33. package/dist/projectFiles.js +32 -0
  34. package/dist/recentEdits.js +66 -0
  35. package/dist/schemaCommand.js +85 -0
  36. package/dist/snClient.js +519 -0
  37. package/dist/tests/benchmarkRefresh.test.js +265 -0
  38. package/dist/tests/clickupCommands.test.js +384 -0
  39. package/dist/tests/clickupPushHelper.test.js +124 -0
  40. package/dist/tests/configFallback.test.js +68 -0
  41. package/dist/tests/cred.test.js +59 -0
  42. package/dist/tests/discovery.test.js +67 -0
  43. package/dist/tests/ensureUpdateSetWarnings.test.js +218 -0
  44. package/dist/tests/errorLogLevels.test.js +273 -0
  45. package/dist/tests/example.test.js +6 -0
  46. package/dist/tests/fileContextSkipReason.test.js +116 -0
  47. package/dist/tests/globalDebounce.test.js +308 -0
  48. package/dist/tests/migrateCommand.test.js +113 -0
  49. package/dist/tests/multi-scope-watcher.test.js +558 -0
  50. package/dist/tests/orchestrator.test.js +23 -0
  51. package/dist/tests/pushFiles.test.js +248 -0
  52. package/dist/tests/rateLimitCoordination.test.js +271 -0
  53. package/dist/tests/resolveConfigReadOnly.test.js +67 -0
  54. package/dist/tests/retryOnHttpErr.test.js +154 -0
  55. package/dist/tests/scopeCaching.test.js +115 -0
  56. package/dist/tests/serializeUpdateSetConfig.test.js +325 -0
  57. package/dist/tests/snClient.fallback.test.js +120 -0
  58. package/dist/tests/syncManifestRefresh.test.js +287 -0
  59. package/dist/tests/syncManifestWhitelist.test.js +192 -0
  60. package/dist/tests/taskClear.test.js +170 -0
  61. package/dist/tests/taskStaleness.test.js +220 -0
  62. package/dist/tests/unwrapSNResponseError.test.js +145 -0
  63. package/dist/tests/v2Values.test.js +79 -0
  64. package/dist/tests/validateTaskId.test.js +304 -0
  65. package/dist/tests/verifyUpdateSetSwitch.test.js +277 -0
  66. package/dist/tests/writeEnvVar.test.js +119 -0
  67. package/dist/updateSetCommands.js +702 -0
  68. package/dist/wizard.js +40 -0
  69. package/package.json +69 -0
  70. package/skills/dove-configure-pipeline.md +158 -0
  71. package/skills/dove-create-plugin.md +174 -0
  72. package/skills/dove-create-record.md +135 -0
  73. package/skills/dove-debug-build.md +117 -0
  74. package/skills/dove-delete-record.md +104 -0
  75. package/skills/dove-manage-tables.md +174 -0
  76. package/skills/dove-manage-update-sets.md +88 -0
  77. package/skills/dove-setup-project.md +154 -0
  78. package/skills/dove-troubleshoot-sync.md +113 -0
@@ -0,0 +1,929 @@
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
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.createAndAssignUpdateSet = exports.swapScope = exports.buildFiles = exports.summarizeRecord = exports.pushFiles = exports.getAppFileList = exports.groupAppFiles = exports.processMissingFiles = exports.refreshAllFiles = exports.findMissingFiles = exports.syncManifest = exports.processManifest = exports.normalizeManifestKeys = void 0;
40
+ const path_1 = __importDefault(require("path"));
41
+ const fs_1 = __importDefault(require("fs"));
42
+ const progress_1 = __importDefault(require("progress"));
43
+ const fUtils = __importStar(require("./FileUtils"));
44
+ const ConfigManager = __importStar(require("./config"));
45
+ const constants_1 = require("./constants");
46
+ const PluginManager_1 = __importDefault(require("./PluginManager"));
47
+ const FileLogger_1 = require("./FileLogger");
48
+ const projectFiles_1 = require("./projectFiles");
49
+ const snClient_1 = require("./snClient");
50
+ const benchmark_1 = require("./benchmark");
51
+ const Logger_1 = require("./Logger");
52
+ const genericUtils_1 = require("./genericUtils");
53
+ const getUpdateSetConfig = () => {
54
+ const configPath = (0, projectFiles_1.getUpdateSetsConfigPath)();
55
+ try {
56
+ if (fs_1.default.existsSync(configPath)) {
57
+ return JSON.parse(fs_1.default.readFileSync(configPath, "utf8"));
58
+ }
59
+ }
60
+ catch (e) {
61
+ Logger_1.logger.warn(`Failed to parse update set config at ${configPath}: ${e instanceof Error ? e.message : String(e)}`);
62
+ }
63
+ return {};
64
+ };
65
+ // Merge _lastUpdatedOn into the server-provided metadata content. Preserves all
66
+ // record fields (sys_id, sys_scope, field value/display_value pairs, etc.) so
67
+ // the local metaData.json is a full snapshot of the record, not just a stub.
68
+ const stampMetadataContent = (file) => {
69
+ if (file.name !== "metaData" || file.type !== "json")
70
+ return file;
71
+ const stamp = new Date().toISOString();
72
+ if (!file.content) {
73
+ return { ...file, content: JSON.stringify({ _lastUpdatedOn: stamp }, null, 2) };
74
+ }
75
+ try {
76
+ const metadata = JSON.parse(file.content);
77
+ if (metadata.sys_updated_on && metadata.sys_updated_on.value) {
78
+ metadata._lastUpdatedOn = metadata.sys_updated_on.value;
79
+ }
80
+ else {
81
+ metadata._lastUpdatedOn = stamp;
82
+ }
83
+ return { ...file, content: JSON.stringify(metadata, null, 2) };
84
+ }
85
+ catch (e) {
86
+ // Content isn't JSON — leave as-is, it will be written verbatim.
87
+ return file;
88
+ }
89
+ };
90
+ const hasServerMetadata = (files) => files.some((f) => f.name === "metaData" && f.type === "json" && !!f.content);
91
+ const processFilesInManRec = async (recPath, rec, forceWrite) => {
92
+ FileLogger_1.fileLogger.debug("Processing record: " + rec.name + " (" + rec.files.length + " files)");
93
+ const fileWrite = fUtils.writeSNFileCurry(forceWrite);
94
+ // If the server did not provide a metadata file, fall back to a timestamp-only
95
+ // stub so the record directory always has a metaData.json.
96
+ if (!hasServerMetadata(rec.files)) {
97
+ const stubMetadata = {
98
+ name: "metaData",
99
+ type: "json",
100
+ content: JSON.stringify({ _lastUpdatedOn: new Date().toISOString() }, null, 2),
101
+ };
102
+ await fileWrite(stubMetadata, recPath);
103
+ }
104
+ const writeResults = await (0, genericUtils_1.allSettledBatched)(rec.files, constants_1.CONCURRENCY_FILES, function (file) { return fileWrite(stampMetadataContent(file), recPath); });
105
+ const writeFailures = writeResults.filter((r) => r.status === "rejected");
106
+ if (writeFailures.length > 0) {
107
+ writeFailures.forEach((f) => {
108
+ FileLogger_1.fileLogger.error("File write failed: " + f.reason);
109
+ });
110
+ }
111
+ // Remove content from ALL files (metadata is not included in manifest)
112
+ rec.files = rec.files.map((file) => {
113
+ const fileCopy = { ...file };
114
+ delete fileCopy.content;
115
+ return fileCopy;
116
+ });
117
+ };
118
+ const processRecsInManTable = async (tablePath, table, forceWrite, onRecordProcessed) => {
119
+ const { records } = table;
120
+ const recKeys = Object.keys(records);
121
+ const recKeyToPath = (key) => path_1.default.join(tablePath, records[key].name);
122
+ const recPathPromises = recKeys
123
+ .map(recKeyToPath)
124
+ .map(fUtils.createDirRecursively);
125
+ await Promise.all(recPathPromises);
126
+ await (0, genericUtils_1.processBatched)(recKeys, constants_1.CONCURRENCY_RECORDS, function (recKey) {
127
+ return processFilesInManRec(recKeyToPath(recKey), records[recKey], forceWrite).then(function () {
128
+ if (onRecordProcessed)
129
+ onRecordProcessed();
130
+ });
131
+ });
132
+ };
133
+ const countRecordsInTables = (tables) => {
134
+ return Object.keys(tables).reduce(function (sum, tableName) {
135
+ return sum + Object.keys(tables[tableName].records).length;
136
+ }, 0);
137
+ };
138
+ const processTablesInManifest = async (tables, forceWrite, sourcePath, onRecordProcessed, scope) => {
139
+ var basePath = sourcePath || ConfigManager.getSourcePath();
140
+ var tableNames = Object.keys(tables);
141
+ // Defense-in-depth: filter out any table not in the resolved whitelist before
142
+ // writing to disk. Protects against upstream defects that let non-whitelisted
143
+ // tables through (e.g. server-side manifest fanout, stale manifest entries).
144
+ if (scope) {
145
+ try {
146
+ var resolved = ConfigManager.resolveConfigForScope(scope);
147
+ var allowed = resolved.tables;
148
+ if (allowed && allowed.length > 0) {
149
+ var skipped = [];
150
+ tableNames = tableNames.filter(function (t) {
151
+ if (allowed.indexOf(t) === -1) {
152
+ skipped.push(t);
153
+ return false;
154
+ }
155
+ return true;
156
+ });
157
+ if (skipped.length > 0) {
158
+ FileLogger_1.fileLogger.debug("processTablesInManifest: dropped " + skipped.length +
159
+ " non-whitelisted tables for scope '" + scope + "': " + skipped.join(", "));
160
+ }
161
+ }
162
+ }
163
+ catch (e) {
164
+ // Config resolution can fail for legacy single-scope manifests; fall
165
+ // through and process whatever the manifest contains.
166
+ FileLogger_1.fileLogger.debug("processTablesInManifest: could not resolve whitelist for scope '" + scope + "'");
167
+ }
168
+ }
169
+ await (0, genericUtils_1.processBatched)(tableNames, constants_1.CONCURRENCY_TABLES, function (tableName) {
170
+ return processRecsInManTable(path_1.default.join(basePath, tableName), tables[tableName], forceWrite, onRecordProcessed);
171
+ });
172
+ };
173
+ /**
174
+ * Re-keys manifest records from sys_id to record.name (display value).
175
+ * Some ServiceNow tables return records keyed by sys_id instead of display name.
176
+ * This ensures consistent naming for directories and manifest lookups.
177
+ */
178
+ const normalizeManifestKeys = (manifest) => {
179
+ var tables = manifest.tables || {};
180
+ var tableNames = Object.keys(tables);
181
+ for (var i = 0; i < tableNames.length; i++) {
182
+ var tableName = tableNames[i];
183
+ var records = tables[tableName].records || {};
184
+ var recordKeys = Object.keys(records);
185
+ var normalized = {};
186
+ for (var j = 0; j < recordKeys.length; j++) {
187
+ var key = recordKeys[j];
188
+ var record = records[key];
189
+ var displayKey = record.name || key;
190
+ // Handle duplicate display names by appending sys_id suffix
191
+ if (normalized[displayKey]) {
192
+ displayKey = displayKey + " (" + record.sys_id.substring(0, 8) + ")";
193
+ }
194
+ normalized[displayKey] = record;
195
+ }
196
+ tables[tableName].records = normalized;
197
+ }
198
+ return manifest;
199
+ };
200
+ exports.normalizeManifestKeys = normalizeManifestKeys;
201
+ const processManifest = async (manifest, forceWrite = false, sourcePath) => {
202
+ const tableCount = Object.keys(manifest.tables).length;
203
+ FileLogger_1.fileLogger.debug("Processing manifest: " + (manifest.scope || "legacy") + " (" + tableCount + " tables)");
204
+ var recordCount = countRecordsInTables(manifest.tables);
205
+ var progress = createScopeProgress(Logger_1.logger.getLogLevel(), {
206
+ scope: manifest.scope || "default",
207
+ total: recordCount,
208
+ });
209
+ await processTablesInManifest(manifest.tables, forceWrite, sourcePath, progress.tick, manifest.scope);
210
+ if (manifest.scope) {
211
+ await fUtils.writeScopeManifest(manifest.scope, manifest);
212
+ }
213
+ else {
214
+ await fUtils.writeFileForce(ConfigManager.getManifestPath(), JSON.stringify(manifest, null, 2));
215
+ }
216
+ };
217
+ exports.processManifest = processManifest;
218
+ const syncManifest = async (scope, options = {}) => {
219
+ // Top-level entry owns the collector lifecycle. Recursive calls (all-scopes
220
+ // → per-scope) inherit the collector via options._benchmarkCollector.
221
+ var isBenchmarkOwner = false;
222
+ var collector = options._benchmarkCollector;
223
+ if (options.benchmark && !collector) {
224
+ collector = new benchmark_1.BenchmarkCollector();
225
+ (0, snClient_1.setBenchmarkSink)(collector);
226
+ isBenchmarkOwner = true;
227
+ }
228
+ try {
229
+ const curManifest = await ConfigManager.getManifest();
230
+ if (!curManifest)
231
+ throw new Error("No manifest file loaded!");
232
+ const config = ConfigManager.getConfig();
233
+ const declaredScopes = (config.scopes && Object.keys(config.scopes)) || [];
234
+ // If a specific scope is provided, sync only that scope
235
+ if (scope) {
236
+ // Scope whitelist gate: refuse to refresh scopes not declared in dove.config.js.
237
+ // Without this, stale entries in dove.manifest.json leak undeclared scopes into
238
+ // the refresh loop (see RFC-0004 / sys_alias debris incident 2026-04-14).
239
+ if (declaredScopes.length > 0 && declaredScopes.indexOf(scope) === -1) {
240
+ Logger_1.logger.warn("Skipping scope '" + scope + "' — not declared in dove.config.js `scopes`. " +
241
+ "Add it to config.scopes to sync, or remove its manifest file.");
242
+ FileLogger_1.fileLogger.debug("syncManifest: skipped undeclared scope '" + scope + "'");
243
+ return;
244
+ }
245
+ Logger_1.logger.info("Refreshing scope: " + scope + "...");
246
+ const client = (0, snClient_1.defaultClient)();
247
+ // Resolve scope-specific source directory + table whitelist
248
+ var scopeSourcePath = ConfigManager.getSourcePathForScope(scope);
249
+ var resolvedConfig = ConfigManager.resolveConfigForScope(scope);
250
+ var allowedTables = resolvedConfig.tables;
251
+ const newManifest = (0, exports.normalizeManifestKeys)(await (0, snClient_1.unwrapSNResponse)(client.getManifest(scope, config)));
252
+ // Table whitelist gate: drop any table the server returned that is not in
253
+ // the resolved _tables whitelist for this scope. Mirrors the filter in
254
+ // commands.ts downloadCommand() and allScopesCommands.ts processScope().
255
+ if (allowedTables && allowedTables.length > 0) {
256
+ var manifestTableNames = Object.keys(newManifest.tables || {});
257
+ var filteredTables = {};
258
+ var skippedCount = 0;
259
+ for (var t = 0; t < manifestTableNames.length; t++) {
260
+ var tName = manifestTableNames[t];
261
+ if (allowedTables.indexOf(tName) !== -1) {
262
+ filteredTables[tName] = newManifest.tables[tName];
263
+ }
264
+ else {
265
+ skippedCount++;
266
+ }
267
+ }
268
+ if (skippedCount > 0) {
269
+ FileLogger_1.fileLogger.debug("syncManifest: filtered " + skippedCount + " tables not in _tables whitelist for " +
270
+ scope + " (kept " + Object.keys(filteredTables).length + " of " + manifestTableNames.length + ")");
271
+ }
272
+ newManifest.tables = filteredTables;
273
+ }
274
+ else {
275
+ Logger_1.logger.warn("No _tables whitelist defined — writing ALL tables for " + scope);
276
+ }
277
+ const refreshTableCount = Object.keys(newManifest.tables).length;
278
+ FileLogger_1.fileLogger.debug("Refreshed manifest for " + scope + ": " + refreshTableCount + " tables");
279
+ await fUtils.writeScopeManifest(scope, newManifest);
280
+ if (collector)
281
+ collector.startScope(scope);
282
+ await (0, exports.refreshAllFiles)(newManifest, scopeSourcePath, {
283
+ force: options.force,
284
+ benchmarkCollector: collector,
285
+ });
286
+ // Update the in-memory manifest for this scope
287
+ if (ConfigManager.isMultiScopeManifest(curManifest)) {
288
+ curManifest[scope] = newManifest;
289
+ ConfigManager.updateManifest(curManifest);
290
+ }
291
+ }
292
+ else {
293
+ // Sync all scopes. Prefer the declared-scopes list (config.scopes) over
294
+ // the persisted manifest keys — the manifest may contain stale undeclared
295
+ // scopes that leaked in before the whitelist gate existed.
296
+ var childOptions = {
297
+ force: options.force,
298
+ _benchmarkCollector: collector,
299
+ };
300
+ if (declaredScopes.length > 0) {
301
+ for (var d = 0; d < declaredScopes.length; d++) {
302
+ await (0, exports.syncManifest)(declaredScopes[d], childOptions);
303
+ }
304
+ }
305
+ else if (ConfigManager.isMultiScopeManifest(curManifest)) {
306
+ // No declared scopes — fall back to the persisted manifest's scopes.
307
+ for (const scopeName of Object.keys(curManifest)) {
308
+ await (0, exports.syncManifest)(scopeName, childOptions);
309
+ }
310
+ }
311
+ else if (curManifest.scope) {
312
+ // Single scope manifest
313
+ await (0, exports.syncManifest)(curManifest.scope, childOptions);
314
+ }
315
+ }
316
+ }
317
+ catch (e) {
318
+ let message;
319
+ if (e instanceof Error)
320
+ message = e.message;
321
+ else
322
+ message = String(e);
323
+ Logger_1.logger.error("Refresh failed: " + message);
324
+ }
325
+ finally {
326
+ if (isBenchmarkOwner && collector) {
327
+ (0, snClient_1.setBenchmarkSink)(null);
328
+ Logger_1.logger.info(collector.formatSummary());
329
+ }
330
+ }
331
+ };
332
+ exports.syncManifest = syncManifest;
333
+ const markFileMissing = (missingObj) => (table) => (recordId) => (file) => {
334
+ if (!missingObj[table]) {
335
+ missingObj[table] = {};
336
+ }
337
+ if (!missingObj[table][recordId]) {
338
+ missingObj[table][recordId] = [];
339
+ }
340
+ const { name, type } = file;
341
+ missingObj[table][recordId].push({ name, type });
342
+ };
343
+ const markRecordMissing = (record, missingFunc) => {
344
+ record.files.forEach((file) => {
345
+ missingFunc(record.sys_id)(file);
346
+ });
347
+ };
348
+ const markTableMissing = (table, tableName, missingFunc) => {
349
+ Object.keys(table.records).forEach((recName) => {
350
+ markRecordMissing(table.records[recName], missingFunc(tableName));
351
+ });
352
+ };
353
+ const checkFilesForMissing = async (recPath, files, missingFunc) => {
354
+ const checkPromises = files.map(fUtils.SNFileExists(recPath));
355
+ const checks = await Promise.all(checkPromises);
356
+ checks.forEach((check, index) => {
357
+ if (!check) {
358
+ missingFunc(files[index]);
359
+ }
360
+ });
361
+ };
362
+ const checkRecordsForMissing = async (tablePath, records, missingFunc) => {
363
+ const recNames = Object.keys(records);
364
+ const recPaths = recNames.map(fUtils.appendToPath(tablePath));
365
+ const checkPromises = recNames.map((recName, index) => fUtils.pathExists(recPaths[index]));
366
+ const checks = await Promise.all(checkPromises);
367
+ const fileCheckPromises = checks.map(async (check, index) => {
368
+ const recName = recNames[index];
369
+ const record = records[recName];
370
+ if (!check) {
371
+ markRecordMissing(record, missingFunc);
372
+ return;
373
+ }
374
+ await checkFilesForMissing(recPaths[index], record.files, missingFunc(record.sys_id));
375
+ });
376
+ await Promise.all(fileCheckPromises);
377
+ };
378
+ const checkTablesForMissing = async (topPath, tables, missingFunc) => {
379
+ const tableNames = Object.keys(tables);
380
+ const tablePaths = tableNames.map(fUtils.appendToPath(topPath));
381
+ const checkPromises = tableNames.map((tableName, index) => fUtils.pathExists(tablePaths[index]));
382
+ const checks = await Promise.all(checkPromises);
383
+ const recCheckPromises = checks.map(async (check, index) => {
384
+ const tableName = tableNames[index];
385
+ if (!check) {
386
+ markTableMissing(tables[tableName], tableName, missingFunc);
387
+ return;
388
+ }
389
+ await checkRecordsForMissing(tablePaths[index], tables[tableName].records, missingFunc(tableName));
390
+ });
391
+ await Promise.all(recCheckPromises);
392
+ };
393
+ const findMissingFiles = async (manifest, sourcePath) => {
394
+ const missing = {};
395
+ const { tables } = manifest;
396
+ const missingTableFunc = markFileMissing(missing);
397
+ await checkTablesForMissing(sourcePath || ConfigManager.getSourcePath(), tables, missingTableFunc);
398
+ // missing gets mutated along the way as things get processed
399
+ return missing;
400
+ };
401
+ exports.findMissingFiles = findMissingFiles;
402
+ // Chunk bulkDownload by table to stay under ServiceNow's 10 MB REST payload cap.
403
+ // A single unchunked call 500s on large scopes (e.g. x_cadso_automate at ~29 MB).
404
+ // Must mirror the chunk size used by allScopesCommands.ts (watch path) so behaviour
405
+ // is consistent across `refresh` and `watch`.
406
+ const BULK_DOWNLOAD_TABLE_CHUNK_SIZE = 5;
407
+ /**
408
+ * Builds a MissingFileTableMap containing EVERY file in the manifest — ignores
409
+ * local disk state. Used by `sinc refresh` to pull instance-side edits down.
410
+ */
411
+ const buildAllFilesMap = (manifest) => {
412
+ const result = {};
413
+ const { tables } = manifest;
414
+ const tableNames = Object.keys(tables);
415
+ for (var t = 0; t < tableNames.length; t++) {
416
+ var tableName = tableNames[t];
417
+ var records = tables[tableName].records;
418
+ var recNames = Object.keys(records);
419
+ if (recNames.length === 0)
420
+ continue;
421
+ var recMap = {};
422
+ for (var r = 0; r < recNames.length; r++) {
423
+ var rec = records[recNames[r]];
424
+ if (!rec.files || rec.files.length === 0)
425
+ continue;
426
+ // Strip any content that may be lingering on manifest file entries; the
427
+ // bulkDownload endpoint only needs name + type to resolve each field.
428
+ recMap[rec.sys_id] = rec.files.map(function (f) {
429
+ return { name: f.name, type: f.type };
430
+ });
431
+ }
432
+ if (Object.keys(recMap).length > 0)
433
+ result[tableName] = recMap;
434
+ }
435
+ return result;
436
+ };
437
+ /**
438
+ * Refreshes local files against the ServiceNow instance for every file in the
439
+ * given manifest. Unlike `processMissingFiles` (which only writes files absent
440
+ * from disk), this walks ALL manifest files, fetches their current content from
441
+ * the instance, and writes when content differs.
442
+ *
443
+ * @param options.force — when true, always overwrite local files even if their
444
+ * content matches the instance. Use for deliberate "reset local to instance".
445
+ */
446
+ const refreshAllFiles = async (newManifest, sourcePath, options = {}) => {
447
+ try {
448
+ const allFiles = buildAllFilesMap(newManifest);
449
+ const tableNames = Object.keys(allFiles);
450
+ if (tableNames.length === 0)
451
+ return;
452
+ FileLogger_1.fileLogger.debug("Refreshing file content for " + tableNames.length + " tables (force=" + !!options.force + ")");
453
+ const { tableOptions = {} } = ConfigManager.getConfig();
454
+ const client = (0, snClient_1.defaultClient)();
455
+ const totalChunks = Math.ceil(tableNames.length / BULK_DOWNLOAD_TABLE_CHUNK_SIZE);
456
+ const filesToProcess = {};
457
+ for (var i = 0; i < tableNames.length; i += BULK_DOWNLOAD_TABLE_CHUNK_SIZE) {
458
+ const chunkTableNames = tableNames.slice(i, i + BULK_DOWNLOAD_TABLE_CHUNK_SIZE);
459
+ const chunkMissing = {};
460
+ for (var j = 0; j < chunkTableNames.length; j++) {
461
+ chunkMissing[chunkTableNames[j]] = allFiles[chunkTableNames[j]];
462
+ }
463
+ const batchNum = Math.floor(i / BULK_DOWNLOAD_TABLE_CHUNK_SIZE) + 1;
464
+ FileLogger_1.fileLogger.debug("Refresh download batch " + batchNum + "/" + totalChunks +
465
+ " (" + chunkTableNames.length + " tables): " + chunkTableNames.join(", "));
466
+ const chunkResult = await (0, snClient_1.unwrapSNResponse)(client.getMissingFiles(chunkMissing, tableOptions));
467
+ for (var tableName in chunkResult) {
468
+ filesToProcess[tableName] = chunkResult[tableName];
469
+ }
470
+ }
471
+ var basePath = sourcePath || ConfigManager.getSourcePath();
472
+ var recordCount = countRecordsInTables(filesToProcess);
473
+ var progress = createScopeProgress(Logger_1.logger.getLogLevel(), {
474
+ scope: newManifest.scope || "default",
475
+ total: recordCount,
476
+ });
477
+ var writtenCount = 0;
478
+ var unchangedCount = 0;
479
+ const forceWrite = !!options.force;
480
+ const forceWriter = fUtils.writeSNFileCurry(false);
481
+ const processedTableNames = Object.keys(filesToProcess);
482
+ await (0, genericUtils_1.processBatched)(processedTableNames, constants_1.CONCURRENCY_TABLES, async function (tableName) {
483
+ var tablePath = path_1.default.join(basePath, tableName);
484
+ var recs = filesToProcess[tableName].records;
485
+ var recKeys = Object.keys(recs);
486
+ await Promise.all(recKeys.map(function (k) {
487
+ return fUtils.createDirRecursively(path_1.default.join(tablePath, recs[k].name));
488
+ }));
489
+ await (0, genericUtils_1.processBatched)(recKeys, constants_1.CONCURRENCY_RECORDS, async function (recKey) {
490
+ var rec = recs[recKey];
491
+ var recPath = path_1.default.join(tablePath, rec.name);
492
+ // Split server-provided metadata off from the regular files so we can
493
+ // track whether any regular file actually changed — metaData shouldn't
494
+ // be the trigger for "this record changed" since we stamp it on every
495
+ // touch.
496
+ var metadataFiles = [];
497
+ var regularFiles = [];
498
+ for (var mi = 0; mi < rec.files.length; mi++) {
499
+ var rf = rec.files[mi];
500
+ if (rf.name === "metaData" && rf.type === "json") {
501
+ metadataFiles.push(rf);
502
+ }
503
+ else {
504
+ regularFiles.push(rf);
505
+ }
506
+ }
507
+ var results = await (0, genericUtils_1.allSettledBatched)(regularFiles, constants_1.CONCURRENCY_FILES, async function (file) {
508
+ if (forceWrite) {
509
+ await forceWriter(file, recPath);
510
+ return true;
511
+ }
512
+ return fUtils.writeSNFileIfDifferent(file, recPath);
513
+ });
514
+ var anyChanged = false;
515
+ for (var f = 0; f < results.length; f++) {
516
+ var res = results[f];
517
+ if (res.status === "rejected") {
518
+ FileLogger_1.fileLogger.error("File write failed: " + res.reason);
519
+ continue;
520
+ }
521
+ if (res.value) {
522
+ anyChanged = true;
523
+ writtenCount++;
524
+ }
525
+ else {
526
+ unchangedCount++;
527
+ }
528
+ }
529
+ // Only touch metaData when at least one regular file in the record
530
+ // actually changed. Avoids rewriting _lastUpdatedOn for records that
531
+ // were already in sync with the instance. Prefer the server-provided
532
+ // metadata (full field snapshot) over a stub; fall back to a stub only
533
+ // when the server didn't send metadata at all.
534
+ if (anyChanged || forceWrite) {
535
+ let metadataFile;
536
+ if (metadataFiles.length > 0 && metadataFiles[0].content) {
537
+ metadataFile = stampMetadataContent(metadataFiles[0]);
538
+ }
539
+ else {
540
+ metadataFile = {
541
+ name: "metaData",
542
+ type: "json",
543
+ content: JSON.stringify({ _lastUpdatedOn: new Date().toISOString() }, null, 2),
544
+ };
545
+ }
546
+ await forceWriter(metadataFile, recPath);
547
+ }
548
+ // Strip content from manifest entries to keep memory bounded.
549
+ rec.files = rec.files.map(function (file) {
550
+ var copy = Object.assign({}, file);
551
+ delete copy.content;
552
+ return copy;
553
+ });
554
+ progress.tick();
555
+ });
556
+ });
557
+ FileLogger_1.fileLogger.debug("Refresh complete: " + writtenCount + " written, " + unchangedCount + " unchanged");
558
+ if (writtenCount > 0) {
559
+ Logger_1.logger.info("Refreshed " + writtenCount + " file(s) from instance" +
560
+ (unchangedCount > 0 ? " (" + unchangedCount + " already in sync)" : ""));
561
+ }
562
+ else {
563
+ Logger_1.logger.debug("No file changes detected from instance (" + unchangedCount + " checked)");
564
+ }
565
+ if (options.benchmarkCollector) {
566
+ options.benchmarkCollector.endScope(writtenCount, unchangedCount);
567
+ }
568
+ }
569
+ catch (e) {
570
+ if (options.benchmarkCollector) {
571
+ options.benchmarkCollector.endScope(0, 0);
572
+ }
573
+ throw e;
574
+ }
575
+ };
576
+ exports.refreshAllFiles = refreshAllFiles;
577
+ const processMissingFiles = async (newManifest, sourcePath) => {
578
+ try {
579
+ const missing = await (0, exports.findMissingFiles)(newManifest, sourcePath);
580
+ const missingTableCount = Object.keys(missing).length;
581
+ if (missingTableCount === 0)
582
+ return;
583
+ FileLogger_1.fileLogger.debug("Downloading missing files from " + missingTableCount + " tables");
584
+ const { tableOptions = {} } = ConfigManager.getConfig();
585
+ const client = (0, snClient_1.defaultClient)();
586
+ // Chunk the bulkDownload request: ServiceNow rejects REST payloads > 10 MB,
587
+ // so send table batches and merge the results before processing.
588
+ const tableNames = Object.keys(missing);
589
+ const totalChunks = Math.ceil(tableNames.length / BULK_DOWNLOAD_TABLE_CHUNK_SIZE);
590
+ const filesToProcess = {};
591
+ for (var i = 0; i < tableNames.length; i += BULK_DOWNLOAD_TABLE_CHUNK_SIZE) {
592
+ const chunkTableNames = tableNames.slice(i, i + BULK_DOWNLOAD_TABLE_CHUNK_SIZE);
593
+ const chunkMissing = {};
594
+ for (var j = 0; j < chunkTableNames.length; j++) {
595
+ chunkMissing[chunkTableNames[j]] = missing[chunkTableNames[j]];
596
+ }
597
+ const batchNum = Math.floor(i / BULK_DOWNLOAD_TABLE_CHUNK_SIZE) + 1;
598
+ FileLogger_1.fileLogger.debug("Bulk download batch " + batchNum + "/" + totalChunks +
599
+ " (" + chunkTableNames.length + " tables): " + chunkTableNames.join(", "));
600
+ const chunkResult = await (0, snClient_1.unwrapSNResponse)(client.getMissingFiles(chunkMissing, tableOptions));
601
+ // Chunks are partitioned by table key, so merging is a simple assign.
602
+ for (var tableName in chunkResult) {
603
+ filesToProcess[tableName] = chunkResult[tableName];
604
+ }
605
+ }
606
+ var recordCount = countRecordsInTables(filesToProcess);
607
+ var progress = createScopeProgress(Logger_1.logger.getLogLevel(), {
608
+ scope: newManifest.scope || "default",
609
+ total: recordCount,
610
+ });
611
+ await processTablesInManifest(filesToProcess, false, sourcePath, progress.tick, newManifest.scope);
612
+ }
613
+ catch (e) {
614
+ throw e;
615
+ }
616
+ };
617
+ exports.processMissingFiles = processMissingFiles;
618
+ const groupAppFiles = (fileCtxs) => {
619
+ const combinedFiles = fileCtxs.reduce((groupMap, cur) => {
620
+ const { tableName, targetField, sys_id } = cur;
621
+ const key = `${tableName}-${sys_id}`;
622
+ const entry = groupMap[key] ?? {
623
+ table: tableName,
624
+ sysId: sys_id,
625
+ fields: {},
626
+ };
627
+ const newEntry = {
628
+ ...entry,
629
+ fields: { ...entry.fields, [targetField]: cur ?? "" },
630
+ };
631
+ return { ...groupMap, [key]: newEntry };
632
+ }, {});
633
+ return Object.values(combinedFiles);
634
+ };
635
+ exports.groupAppFiles = groupAppFiles;
636
+ const getAppFileList = async (paths) => {
637
+ const validPaths = typeof paths === "object"
638
+ ? paths
639
+ : await fUtils.encodedPathsToFilePaths(paths);
640
+ const appFileCtxs = [];
641
+ validPaths.forEach(function (filePath) {
642
+ var result = fUtils.getFileContextWithSkipReason(filePath);
643
+ if (result.context) {
644
+ appFileCtxs.push(result.context);
645
+ }
646
+ else {
647
+ var reason = result.skipReason || "unknown";
648
+ if (reason === "not in manifest") {
649
+ Logger_1.logger.info(`Skipped: ${filePath} (${reason})`);
650
+ }
651
+ else {
652
+ Logger_1.logger.warn(`Skipped: ${filePath} (${reason})`);
653
+ }
654
+ }
655
+ });
656
+ return (0, exports.groupAppFiles)(appFileCtxs);
657
+ };
658
+ exports.getAppFileList = getAppFileList;
659
+ const buildRec = async (rec) => {
660
+ const fields = Object.keys(rec.fields);
661
+ const buildPromises = fields.map((field) => {
662
+ return PluginManager_1.default.getFinalFileContents(rec.fields[field]);
663
+ });
664
+ const builtFiles = await (0, genericUtils_1.allSettled)(buildPromises);
665
+ const buildSuccess = !builtFiles.find((buildRes) => buildRes.status === "rejected");
666
+ if (!buildSuccess) {
667
+ return {
668
+ success: false,
669
+ message: (0, genericUtils_1.aggregateErrorMessages)(builtFiles
670
+ .filter((b) => b.status === "rejected")
671
+ .map((b) => b.reason), "Failed to build!", (_, index) => `${index}`),
672
+ };
673
+ }
674
+ const builtRec = builtFiles.reduce((acc, buildRes, index) => {
675
+ const { value: content } = buildRes;
676
+ const fieldName = fields[index];
677
+ return { ...acc, [fieldName]: content };
678
+ }, {});
679
+ return {
680
+ success: true,
681
+ builtRec,
682
+ };
683
+ };
684
+ const pushRec = async (client, table, sysId, builtRec, summary, scope, updateSetConfig) => {
685
+ const recSummary = summary ?? `${table} > ${sysId}`;
686
+ try {
687
+ // Use the batch-level config passed from pushFiles() to avoid re-reading per record
688
+ const config = updateSetConfig || {};
689
+ const updateSet = scope ? config[scope] : undefined;
690
+ const pushFn = updateSet
691
+ ? () => {
692
+ Logger_1.logger.debug(`Pushing ${recSummary} via update set: ${updateSet.name}`);
693
+ return client.pushWithUpdateSet(updateSet.sys_id, table, sysId, builtRec);
694
+ }
695
+ : () => client.updateRecord(table, sysId, builtRec);
696
+ const pushRes = await (0, snClient_1.retryOnHttpErr)(pushFn, recSummary);
697
+ return (0, snClient_1.processPushResponse)(pushRes, recSummary);
698
+ }
699
+ catch (e) {
700
+ let message;
701
+ if (e instanceof Error)
702
+ message = e.message;
703
+ else
704
+ message = String(e);
705
+ const errMsg = message || "Too many retries";
706
+ return { success: false, message: `${recSummary} : ${errMsg}` };
707
+ }
708
+ };
709
+ const pushFiles = async (recs) => {
710
+ const client = (0, snClient_1.defaultClient)();
711
+ const updateSetConfig = getUpdateSetConfig();
712
+ const hasUpdateSets = Object.keys(updateSetConfig).length > 0;
713
+ if (hasUpdateSets) {
714
+ const activeScopes = Object.entries(updateSetConfig)
715
+ .map(([scope, us]) => `${scope} -> ${us.name}`)
716
+ .join(", ");
717
+ Logger_1.logger.info(`Update set routing active: ${activeScopes}`);
718
+ }
719
+ // Pre-resolve read-only table sets for every scope present in this batch.
720
+ // Cheaper than re-resolving per record. Tables flagged in _readOnlyTables
721
+ // are pulled normally but pushes are skipped.
722
+ const readOnlyByScope = {};
723
+ for (const rec of recs) {
724
+ const fieldNames = Object.keys(rec.fields);
725
+ if (fieldNames.length === 0)
726
+ continue;
727
+ const scope = rec.fields[fieldNames[0]].scope;
728
+ if (scope && !readOnlyByScope[scope]) {
729
+ readOnlyByScope[scope] = ConfigManager.getReadOnlyTablesForScope(scope);
730
+ }
731
+ }
732
+ const announcedSkipTables = {};
733
+ const tick = getProgTick(Logger_1.logger.getLogLevel(), recs.length * 2) || (() => { });
734
+ const results = await (0, genericUtils_1.allSettledBatched)(recs, constants_1.CONCURRENCY_PUSH, async function (rec) {
735
+ const fieldNames = Object.keys(rec.fields);
736
+ const firstField = rec.fields[fieldNames[0]];
737
+ const recSummary = (0, exports.summarizeRecord)(rec.table, firstField.name);
738
+ const scope = firstField.scope;
739
+ const readOnlySet = scope ? readOnlyByScope[scope] : undefined;
740
+ if (readOnlySet && readOnlySet.has(rec.table)) {
741
+ tick();
742
+ tick();
743
+ const skipKey = `${scope}:${rec.table}`;
744
+ if (!announcedSkipTables[skipKey]) {
745
+ announcedSkipTables[skipKey] = true;
746
+ Logger_1.logger.info(`Read-only table ${rec.table} in scope ${scope}: push skipped`);
747
+ }
748
+ return { success: true, message: `${recSummary} : skipped (read-only table)` };
749
+ }
750
+ const buildRes = await buildRec(rec);
751
+ tick();
752
+ if (!buildRes.success) {
753
+ tick();
754
+ return { success: false, message: `${recSummary} : ${buildRes.message}` };
755
+ }
756
+ const pushRes = await pushRec(client, rec.table, rec.sysId, buildRes.builtRec, recSummary, scope, updateSetConfig);
757
+ tick();
758
+ return pushRes;
759
+ });
760
+ return results.map(function (result) {
761
+ if (result.status === "fulfilled") {
762
+ return result.value;
763
+ }
764
+ return { success: false, message: `Push failed: ${result.reason}` };
765
+ });
766
+ };
767
+ exports.pushFiles = pushFiles;
768
+ const summarizeRecord = (table, recDescriptor) => `${table} > ${recDescriptor}`;
769
+ exports.summarizeRecord = summarizeRecord;
770
+ const createScopeProgress = (logLevel, options) => {
771
+ if (logLevel !== "info" || options.total === 0) {
772
+ return { tick: function () { }, setTotal: function () { } };
773
+ }
774
+ var progBar = new progress_1.default(":scope :bar :current/:total (:percent)", {
775
+ total: options.total,
776
+ width: 40,
777
+ complete: "=",
778
+ incomplete: "-",
779
+ });
780
+ return {
781
+ tick: function () {
782
+ progBar.tick({ scope: options.scope });
783
+ },
784
+ setTotal: function (n) {
785
+ progBar.total = n;
786
+ },
787
+ };
788
+ };
789
+ const getProgTick = (logLevel, total) => {
790
+ if (logLevel === "info") {
791
+ const progBar = new progress_1.default(":bar (:percent)", {
792
+ total,
793
+ width: 60,
794
+ });
795
+ return () => {
796
+ progBar.tick();
797
+ };
798
+ }
799
+ // no-op at other log levels
800
+ return undefined;
801
+ };
802
+ const writeBuildFile = async (preBuild, buildRes, summary) => {
803
+ const { fields, table, sysId } = preBuild;
804
+ const recSummary = summary ?? `${table} > ${sysId}`;
805
+ const sourcePath = ConfigManager.getSourcePath();
806
+ const buildPath = ConfigManager.getBuildPath();
807
+ const fieldNames = Object.keys(fields);
808
+ const writePromises = fieldNames.map(async (field) => {
809
+ const fieldCtx = fields[field];
810
+ const srcFilePath = fieldCtx.filePath;
811
+ const relativePath = path_1.default.relative(sourcePath, srcFilePath);
812
+ const relPathNoExt = relativePath.split(".").slice(0, -1).join();
813
+ const buildExt = fUtils.getBuildExt(fieldCtx.tableName, fieldCtx.name, fieldCtx.targetField, fieldCtx.scope);
814
+ const relPathNewExt = `${relPathNoExt}.${buildExt}`;
815
+ const buildFilePath = path_1.default.join(buildPath, relPathNewExt);
816
+ await fUtils.createDirRecursively(path_1.default.dirname(buildFilePath));
817
+ const writeResult = await fUtils.writeFileForce(buildFilePath, buildRes.builtRec[fieldCtx.targetField]);
818
+ return writeResult;
819
+ });
820
+ try {
821
+ await (0, genericUtils_1.processBatched)(fieldNames, constants_1.CONCURRENCY_FILES, async function (field) {
822
+ const fieldCtx = fields[field];
823
+ const srcFilePath = fieldCtx.filePath;
824
+ const relativePath = path_1.default.relative(sourcePath, srcFilePath);
825
+ const relPathNoExt = relativePath.split(".").slice(0, -1).join();
826
+ const buildExt = fUtils.getBuildExt(fieldCtx.tableName, fieldCtx.name, fieldCtx.targetField);
827
+ const relPathNewExt = `${relPathNoExt}.${buildExt}`;
828
+ const buildFilePath = path_1.default.join(buildPath, relPathNewExt);
829
+ await fUtils.createDirRecursively(path_1.default.dirname(buildFilePath));
830
+ await fUtils.writeFileForce(buildFilePath, buildRes.builtRec[fieldCtx.targetField]);
831
+ });
832
+ return { success: true, message: `${recSummary} built successfully` };
833
+ }
834
+ catch (e) {
835
+ return {
836
+ success: false,
837
+ message: `${recSummary} : ${e}`,
838
+ };
839
+ }
840
+ };
841
+ const buildFiles = async (fileList) => {
842
+ const tick = getProgTick(Logger_1.logger.getLogLevel(), fileList.length * 2) || (() => { });
843
+ const results = await (0, genericUtils_1.allSettledBatched)(fileList, constants_1.CONCURRENCY_BUILD, async function (rec) {
844
+ const { fields, table } = rec;
845
+ const fieldNames = Object.keys(fields);
846
+ const recSummary = (0, exports.summarizeRecord)(table, fields[fieldNames[0]].name);
847
+ const buildRes = await buildRec(rec);
848
+ tick();
849
+ if (!buildRes.success) {
850
+ tick();
851
+ return { success: false, message: `${recSummary} : ${buildRes.message}` };
852
+ }
853
+ // writeFile
854
+ const writeRes = await writeBuildFile(rec, buildRes, recSummary);
855
+ tick();
856
+ return writeRes;
857
+ });
858
+ return results.map(function (result) {
859
+ if (result.status === "fulfilled") {
860
+ return result.value;
861
+ }
862
+ return { success: false, message: `Build failed: ${result.reason}` };
863
+ });
864
+ };
865
+ exports.buildFiles = buildFiles;
866
+ const swapScope = async (currentScope) => {
867
+ try {
868
+ const client = (0, snClient_1.defaultClient)();
869
+ const scopeId = await (0, snClient_1.unwrapTableAPIFirstItem)(client.getScopeId(currentScope), "sys_id");
870
+ await swapServerScope(scopeId);
871
+ const scopeObj = await (0, snClient_1.unwrapSNResponse)(client.getCurrentScope());
872
+ return scopeObj;
873
+ }
874
+ catch (e) {
875
+ throw e;
876
+ }
877
+ };
878
+ exports.swapScope = swapScope;
879
+ const swapServerScope = async (scopeId) => {
880
+ try {
881
+ const client = (0, snClient_1.defaultClient)();
882
+ const userSysId = await (0, snClient_1.unwrapTableAPIFirstItem)(client.getUserSysId(), "sys_id");
883
+ const curAppUserPrefId = (await (0, snClient_1.unwrapTableAPIFirstItem)(client.getCurrentAppUserPrefSysId(userSysId), "sys_id")) || "";
884
+ // If not user pref record exists, create it.
885
+ if (curAppUserPrefId !== "")
886
+ await client.updateCurrentAppUserPref(scopeId, curAppUserPrefId);
887
+ else
888
+ await client.createCurrentAppUserPref(scopeId, userSysId);
889
+ }
890
+ catch (e) {
891
+ let message;
892
+ if (e instanceof Error)
893
+ message = e.message;
894
+ else
895
+ message = String(e);
896
+ Logger_1.logger.error(message);
897
+ throw e;
898
+ }
899
+ };
900
+ /**
901
+ * Creates a new update set and assigns it to the current user.
902
+ * @param updateSetName - does not create update set if value is blank
903
+ * @param scope - optional scope name (e.g. x_cadso_work) to create the update set in
904
+ */
905
+ const createAndAssignUpdateSet = async (updateSetName = "", scope) => {
906
+ Logger_1.logger.info(`Update Set Name: ${updateSetName}` + (scope ? ` (scope: ${scope})` : ""));
907
+ const client = (0, snClient_1.defaultClient)();
908
+ var scopeSysId;
909
+ if (scope) {
910
+ var scopeResult = await (0, snClient_1.unwrapSNResponse)(client.getScopeId(scope));
911
+ if (scopeResult.length > 0) {
912
+ scopeSysId = scopeResult[0].sys_id;
913
+ }
914
+ }
915
+ const { sys_id: updateSetSysId } = await (0, snClient_1.unwrapSNResponse)(client.createUpdateSet(updateSetName, scopeSysId));
916
+ const userSysId = await (0, snClient_1.unwrapTableAPIFirstItem)(client.getUserSysId(), "sys_id");
917
+ const curUpdateSetUserPrefId = await (0, snClient_1.unwrapTableAPIFirstItem)(client.getCurrentUpdateSetUserPref(userSysId), "sys_id");
918
+ if (curUpdateSetUserPrefId !== "") {
919
+ await client.updateCurrentUpdateSetUserPref(updateSetSysId, curUpdateSetUserPrefId);
920
+ }
921
+ else {
922
+ await client.createCurrentUpdateSetUserPref(updateSetSysId, userSysId);
923
+ }
924
+ return {
925
+ name: updateSetName,
926
+ id: updateSetSysId,
927
+ };
928
+ };
929
+ exports.createAndAssignUpdateSet = createAndAssignUpdateSet;