@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,125 @@
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
+ const ConfigManager = __importStar(require("./config"));
40
+ const fs_1 = __importDefault(require("fs"));
41
+ const path_1 = __importDefault(require("path"));
42
+ const fsp = fs_1.default.promises;
43
+ class PluginManager {
44
+ pluginRules;
45
+ constructor() {
46
+ this.pluginRules = [];
47
+ }
48
+ async loadPluginConfig() {
49
+ let conf = ConfigManager.getConfig();
50
+ if (conf && conf.rules) {
51
+ this.pluginRules = conf.rules;
52
+ }
53
+ }
54
+ determinePlugins(context) {
55
+ let plugins = [];
56
+ for (let rule of this.pluginRules) {
57
+ let reg = rule.match;
58
+ if (reg.test(context.filePath)) {
59
+ plugins = rule.plugins;
60
+ //only match first rule
61
+ break;
62
+ }
63
+ }
64
+ return plugins;
65
+ }
66
+ async runPlugins(plugins, context, content) {
67
+ try {
68
+ let output = content;
69
+ for (let pConfig of plugins) {
70
+ let pluginPath = path_1.default.join(ConfigManager.getRootDir(), "node_modules", pConfig.name);
71
+ let plugin = await Promise.resolve(`${pluginPath}`).then(s => __importStar(require(s)));
72
+ let results = await plugin.run(context, output, pConfig.options);
73
+ if (!results.success) {
74
+ return {
75
+ success: false,
76
+ content: "",
77
+ };
78
+ }
79
+ output = results.output;
80
+ }
81
+ return {
82
+ success: true,
83
+ content: output,
84
+ };
85
+ }
86
+ catch (e) {
87
+ throw e;
88
+ }
89
+ }
90
+ async processFile(context, content) {
91
+ const plugins = this.determinePlugins(context);
92
+ if (plugins.length > 0) {
93
+ try {
94
+ const pluginResults = await this.runPlugins(plugins, context, content);
95
+ if (pluginResults.success) {
96
+ return pluginResults.content;
97
+ }
98
+ else {
99
+ throw new Error(`Failed to build ${context.tableName}=>${context.sys_id}!`);
100
+ }
101
+ }
102
+ catch (e) {
103
+ throw e;
104
+ }
105
+ }
106
+ else {
107
+ return content;
108
+ }
109
+ }
110
+ async getFinalFileContents(context, processFile = true) {
111
+ const { filePath } = context;
112
+ try {
113
+ const contents = await fsp.readFile(filePath, "utf-8");
114
+ if (processFile) {
115
+ await this.loadPluginConfig();
116
+ return await this.processFile(context, contents);
117
+ }
118
+ return contents;
119
+ }
120
+ catch (e) {
121
+ throw e;
122
+ }
123
+ }
124
+ }
125
+ exports.default = new PluginManager();
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.tableOptions = exports.scopes = exports.includes = exports.excludes = void 0;
4
+ let excludes = {
5
+ sys_scope_privilege: true,
6
+ sys_dictionary: true,
7
+ sys_impex_entry: true,
8
+ sys_security_acl: true,
9
+ sys_transform_map: true,
10
+ sys_ui_policy: true,
11
+ sys_ui_list_control: true,
12
+ sys_relationship: true,
13
+ sys_report: true,
14
+ item_option_new: true,
15
+ sys_process_flow: true,
16
+ content_block_programmatic: true,
17
+ sp_instance: true,
18
+ sys_transform_script: true,
19
+ sc_category: true,
20
+ sysrule_view: true,
21
+ sc_cat_item: true,
22
+ sysevent_in_email_action: true,
23
+ sys_navigator: true,
24
+ sys_transform_entry: true,
25
+ metric_definition: true,
26
+ content_block_lists: true,
27
+ content_block_detail: true,
28
+ sp_portal: true,
29
+ sc_cat_item_producer: true,
30
+ sys_impex_map: true,
31
+ };
32
+ exports.excludes = excludes;
33
+ let includes = {
34
+ content_css: {
35
+ style: {
36
+ type: "css",
37
+ },
38
+ },
39
+ };
40
+ exports.includes = includes;
41
+ let tableOptions = {};
42
+ exports.tableOptions = tableOptions;
43
+ let scopes = {};
44
+ exports.scopes = scopes;
@@ -0,0 +1,418 @@
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.processScope = processScope;
40
+ exports.initScopesCommand = initScopesCommand;
41
+ exports.watchAllScopesCommand = watchAllScopesCommand;
42
+ const Logger_1 = require("./Logger");
43
+ const FileLogger_1 = require("./FileLogger");
44
+ const ConfigManager = __importStar(require("./config"));
45
+ const AppUtils = __importStar(require("./appUtils"));
46
+ const wizard_1 = require("./wizard");
47
+ const snClient_1 = require("./snClient");
48
+ const commands_1 = require("./commands");
49
+ const path = __importStar(require("path"));
50
+ const fs = __importStar(require("fs"));
51
+ const child_process_1 = require("child_process");
52
+ const progress_1 = __importDefault(require("progress"));
53
+ const fsp = fs.promises;
54
+ // Custom function to process manifest with specific source directory
55
+ async function processManifestForScope(manifest, sourceDirectory, forceWrite = false) {
56
+ try {
57
+ // Ensure the source directory exists
58
+ try {
59
+ await fsp.mkdir(sourceDirectory, { recursive: true });
60
+ }
61
+ catch (dirError) {
62
+ Logger_1.logger.error("Failed to create directory: " + sourceDirectory);
63
+ throw dirError;
64
+ }
65
+ const tables = manifest.tables || {};
66
+ const tableNames = Object.keys(tables);
67
+ var totalRecords = 0;
68
+ for (var tn = 0; tn < tableNames.length; tn++) {
69
+ totalRecords += Object.keys(tables[tableNames[tn]].records || {}).length;
70
+ }
71
+ var scopeLabel = sourceDirectory.split(path.sep).pop() || "scope";
72
+ var progBar = null;
73
+ if (Logger_1.logger.getLogLevel() === "info" && totalRecords > 0) {
74
+ progBar = new progress_1.default(":scope :bar :current/:total (:percent)", {
75
+ total: totalRecords,
76
+ width: 40,
77
+ complete: "=",
78
+ incomplete: "-",
79
+ });
80
+ }
81
+ for (const tableName of tableNames) {
82
+ const tableRecords = tables[tableName];
83
+ const tablePath = path.join(sourceDirectory, tableName);
84
+ // Process each record in the table
85
+ const recordNames = Object.keys(tableRecords.records || {});
86
+ FileLogger_1.fileLogger.debug("Table " + tableName + ": " + recordNames.length + " records");
87
+ for (const recordName of recordNames) {
88
+ const record = tableRecords.records[recordName];
89
+ // Use the record's name property instead of the key for the directory name
90
+ const recordDirName = record.name || recordName;
91
+ const recordPath = path.join(tablePath, recordDirName);
92
+ // Check if metadata file exists in the files from server
93
+ const hasMetadataFromServer = record.files && record.files.some((f) => f.name === 'metaData' && f.type === 'json');
94
+ // Ensure the record directory exists
95
+ await fsp.mkdir(recordPath, { recursive: true });
96
+ // Process each file in the record
97
+ for (const file of record.files || []) {
98
+ const filePath = path.join(recordPath, `${file.name}.${file.type}`);
99
+ const fileContent = file.content || "";
100
+ const fileDir = path.dirname(filePath);
101
+ await fsp.mkdir(fileDir, { recursive: true });
102
+ try {
103
+ await fsp.writeFile(filePath, fileContent, "utf8");
104
+ }
105
+ catch (writeError) {
106
+ Logger_1.logger.error("Failed to write: " + filePath);
107
+ throw writeError;
108
+ }
109
+ }
110
+ // If no metadata from server, create a basic one
111
+ if (!hasMetadataFromServer) {
112
+ const metadataFilePath = path.join(recordPath, "metaData.json");
113
+ const metadataContent = {
114
+ _generatedAt: new Date().toISOString(),
115
+ _note: "Generated locally - metadata not provided by server"
116
+ };
117
+ try {
118
+ await fsp.writeFile(metadataFilePath, JSON.stringify(metadataContent, null, 2), "utf8");
119
+ }
120
+ catch (metaError) {
121
+ Logger_1.logger.error("Failed to write metadata: " + metadataFilePath);
122
+ }
123
+ }
124
+ if (progBar)
125
+ progBar.tick({ scope: scopeLabel });
126
+ }
127
+ }
128
+ }
129
+ catch (error) {
130
+ Logger_1.logger.error("Error processing files for " + sourceDirectory + ": " + error);
131
+ throw error;
132
+ }
133
+ }
134
+ async function processScope(scopeName, scopeConfig, apiDelay = 0) {
135
+ try {
136
+ const instance = process.env.SN_INSTANCE || "unknown";
137
+ Logger_1.logger.info("Processing scope: " + scopeName + " (" + instance + ")");
138
+ // Get the client
139
+ const client = (0, snClient_1.defaultClient)();
140
+ // Get the config
141
+ const config = ConfigManager.getConfig();
142
+ // Determine the source directory for this scope
143
+ let sourceDirectory;
144
+ if (typeof scopeConfig === "object" && scopeConfig.sourceDirectory) {
145
+ sourceDirectory = path.resolve(ConfigManager.getRootDir(), scopeConfig.sourceDirectory);
146
+ }
147
+ else {
148
+ // Default to src/{scope} if no sourceDirectory specified
149
+ sourceDirectory = path.resolve(ConfigManager.getRootDir(), "src", scopeName);
150
+ }
151
+ FileLogger_1.fileLogger.debug("Source directory for " + scopeName + ": " + sourceDirectory);
152
+ const apps = await (0, snClient_1.unwrapSNResponse)(client.getAppList());
153
+ const scopeApp = apps.find((app) => app.scope === scopeName);
154
+ if (!scopeApp) {
155
+ Logger_1.logger.warn("Scope " + scopeName + " not found in ServiceNow apps list");
156
+ }
157
+ else {
158
+ Logger_1.logger.info("Found app: " + scopeApp.displayName);
159
+ }
160
+ // Resolve the table whitelist and field overrides for this scope
161
+ var resolvedConfig = ConfigManager.resolveConfigForScope(scopeName);
162
+ var allowedTables = resolvedConfig.tables;
163
+ // Build a config safe for the ServiceNow API (no _ directive keys)
164
+ var apiConfig = Object.assign({}, config, {
165
+ includes: resolvedConfig.apiIncludes,
166
+ excludes: resolvedConfig.apiExcludes,
167
+ });
168
+ Logger_1.logger.info("Downloading manifest for " + scopeName + "...");
169
+ if (apiDelay > 0) {
170
+ await delay(apiDelay);
171
+ }
172
+ const manifest = await (0, snClient_1.unwrapSNResponse)(client.getManifest(scopeName, apiConfig, false));
173
+ // Client-side table filtering — only keep tables in the _tables whitelist
174
+ if (allowedTables && allowedTables.length > 0) {
175
+ var manifestTableNames = Object.keys(manifest.tables || {});
176
+ var filteredTables = {};
177
+ var skippedCount = 0;
178
+ for (var t = 0; t < manifestTableNames.length; t++) {
179
+ var tName = manifestTableNames[t];
180
+ if (allowedTables.indexOf(tName) !== -1) {
181
+ filteredTables[tName] = manifest.tables[tName];
182
+ }
183
+ else {
184
+ skippedCount++;
185
+ }
186
+ }
187
+ var keptCount = Object.keys(filteredTables).length;
188
+ if (skippedCount > 0) {
189
+ FileLogger_1.fileLogger.debug("Filtered " + skippedCount + " tables not in _tables whitelist for " + scopeName +
190
+ ". Keeping " + keptCount + " of " + manifestTableNames.length);
191
+ }
192
+ Logger_1.logger.info(scopeName + ": " + keptCount + " tables match config (" + skippedCount + " filtered out)");
193
+ manifest.tables = filteredTables;
194
+ }
195
+ else {
196
+ Logger_1.logger.warn("No _tables whitelist defined — writing ALL tables for " + scopeName);
197
+ }
198
+ // Normalize record keys from sys_id to display name
199
+ AppUtils.normalizeManifestKeys(manifest);
200
+ // Build the missing files structure from the filtered manifest
201
+ var manifestTables = manifest.tables || {};
202
+ var allMissingFiles = {};
203
+ for (var tableName in manifestTables) {
204
+ var table = manifestTables[tableName];
205
+ allMissingFiles[tableName] = {};
206
+ for (var recordName in table.records || {}) {
207
+ var record = table.records[recordName];
208
+ allMissingFiles[tableName][record.sys_id] = record.files.map(function (f) {
209
+ return { name: f.name, type: f.type };
210
+ });
211
+ }
212
+ }
213
+ // Download file contents in chunks to avoid ServiceNow 500 errors on large payloads
214
+ var CHUNK_SIZE = 5;
215
+ var tableNames = Object.keys(allMissingFiles);
216
+ var totalChunks = Math.ceil(tableNames.length / CHUNK_SIZE);
217
+ Logger_1.logger.info("Downloading file contents for " + scopeName + " (" + tableNames.length + " tables in " + totalChunks + " batch" + (totalChunks !== 1 ? "es" : "") + ")...");
218
+ for (var chunkIdx = 0; chunkIdx < tableNames.length; chunkIdx += CHUNK_SIZE) {
219
+ var chunkTableNames = tableNames.slice(chunkIdx, chunkIdx + CHUNK_SIZE);
220
+ var chunkMissing = {};
221
+ for (var ci = 0; ci < chunkTableNames.length; ci++) {
222
+ chunkMissing[chunkTableNames[ci]] = allMissingFiles[chunkTableNames[ci]];
223
+ }
224
+ if (apiDelay > 0) {
225
+ await delay(apiDelay);
226
+ }
227
+ var batchNum = Math.floor(chunkIdx / CHUNK_SIZE) + 1;
228
+ FileLogger_1.fileLogger.debug("Downloading batch " + batchNum + "/" + totalChunks + " for " + scopeName + ": " + chunkTableNames.join(", "));
229
+ var chunkContent = await (0, snClient_1.unwrapSNResponse)(client.getMissingFiles(chunkMissing, config.tableOptions || {}));
230
+ // Merge chunk content back into the manifest
231
+ for (var chunkTable in chunkContent || {}) {
232
+ if (manifest.tables[chunkTable]) {
233
+ for (var chunkRecName in chunkContent[chunkTable].records || {}) {
234
+ var recordWithContent = chunkContent[chunkTable].records[chunkRecName];
235
+ var manifestRecords = manifest.tables[chunkTable].records;
236
+ var matchingRecord = Object.values(manifestRecords).find(function (r) {
237
+ return r.sys_id === recordWithContent.sys_id;
238
+ });
239
+ if (matchingRecord) {
240
+ matchingRecord.files = recordWithContent.files;
241
+ }
242
+ }
243
+ }
244
+ }
245
+ }
246
+ const tableCount = Object.keys((manifest && manifest.tables) || {}).length;
247
+ Logger_1.logger.info("Writing " + tableCount + " tables for " + scopeName + "...");
248
+ await processManifestForScope(manifest, sourceDirectory, true);
249
+ // Create the scope-specific manifest structure
250
+ const scopeManifest = {
251
+ tables: (manifest && manifest.tables) || {},
252
+ scope: scopeName,
253
+ };
254
+ Logger_1.logger.success("Scope " + scopeName + " complete — files saved to " + sourceDirectory);
255
+ return {
256
+ scope: scopeName,
257
+ success: true,
258
+ manifest: scopeManifest,
259
+ };
260
+ }
261
+ catch (error) {
262
+ const errorMessage = error instanceof Error ? error.message : String(error);
263
+ Logger_1.logger.error("Failed to process scope " + scopeName + ": " + errorMessage);
264
+ return {
265
+ scope: scopeName,
266
+ success: false,
267
+ error: error instanceof Error ? error : new Error(String(error)),
268
+ };
269
+ }
270
+ }
271
+ // Helper function to add delay between API calls
272
+ async function delay(ms) {
273
+ if (ms > 0) {
274
+ return new Promise(resolve => setTimeout(resolve, ms));
275
+ }
276
+ }
277
+ async function initScopesCommand(args) {
278
+ (0, commands_1.setLogLevel)(args);
279
+ // Get API delay from args, default to 0ms (no delay)
280
+ const apiDelay = args.delay || 0;
281
+ if (apiDelay > 0) {
282
+ Logger_1.logger.info(`Using ${apiDelay}ms delay between API calls to prevent server overload`);
283
+ }
284
+ try {
285
+ // First check if we have environment variables set
286
+ if (!process.env.SN_USER ||
287
+ !process.env.SN_PASSWORD ||
288
+ !process.env.SN_INSTANCE) {
289
+ try {
290
+ let loginAnswers = await (0, wizard_1.getLoginInfo)();
291
+ await (0, wizard_1.setupDotEnv)(loginAnswers);
292
+ }
293
+ catch (error) {
294
+ Logger_1.logger.error("Error getting ServiceNow credentials: " + error);
295
+ throw error;
296
+ }
297
+ if (!process.env.SN_USER ||
298
+ !process.env.SN_PASSWORD ||
299
+ !process.env.SN_INSTANCE) {
300
+ Logger_1.logger.error("Missing ServiceNow credentials. Please ensure SN_USER, SN_PASSWORD, and SN_INSTANCE are set in your .env file");
301
+ throw new Error("ServiceNow credentials not configured");
302
+ }
303
+ }
304
+ // Load config
305
+ await ConfigManager.loadConfigs();
306
+ const config = ConfigManager.getConfig();
307
+ if (!config.scopes) {
308
+ Logger_1.logger.error("No scopes defined in dove.config.js");
309
+ throw new Error("No scopes defined in configuration");
310
+ }
311
+ const scopes = Object.keys(config.scopes);
312
+ const instance = process.env.SN_INSTANCE || "unknown";
313
+ Logger_1.logger.info("Initializing " + scopes.length + " scopes from " + instance + ": " + scopes.join(", "));
314
+ const scopePromises = scopes.map((scopeName) => processScope(scopeName, config.scopes[scopeName], apiDelay));
315
+ const results = await Promise.allSettled(scopePromises);
316
+ // Collect successful manifests
317
+ const manifests = {};
318
+ let successCount = 0;
319
+ let failCount = 0;
320
+ results.forEach((result, index) => {
321
+ const scopeName = scopes[index];
322
+ if (result.status === "fulfilled" && result.value.success) {
323
+ manifests[scopeName] = result.value.manifest;
324
+ successCount++;
325
+ }
326
+ else {
327
+ failCount++;
328
+ const error = result.status === "rejected" ? result.reason : (result.value && result.value.error);
329
+ Logger_1.logger.error(`Failed to process ${scopeName}: ${(error && error.message) || "Unknown error"}`);
330
+ }
331
+ });
332
+ // Write per-scope manifest files instead of a single combined one
333
+ for (const [scopeName, scopeData] of Object.entries(manifests)) {
334
+ const scopeManifestPath = ConfigManager.getScopeManifestPath(scopeName);
335
+ await fsp.writeFile(scopeManifestPath, JSON.stringify(scopeData, null, 2));
336
+ Logger_1.logger.info(`Wrote manifest for ${scopeName} to: ${scopeManifestPath}`);
337
+ }
338
+ Logger_1.logger.info("─".repeat(50));
339
+ Logger_1.logger.success("Scope initialization complete — " + successCount + "/" + scopes.length + " scopes processed");
340
+ if (failCount > 0) {
341
+ Logger_1.logger.warn(failCount + " scope(s) failed — check errors above");
342
+ }
343
+ Logger_1.logger.info("Manifests: dove.manifest.<scope>.json");
344
+ Logger_1.logger.success("Run 'npx dove watch' to start development");
345
+ }
346
+ catch (e) {
347
+ Logger_1.logger.error("Error initializing scopes: " + e);
348
+ throw e;
349
+ }
350
+ }
351
+ async function watchAllScopesCommand(args) {
352
+ (0, commands_1.setLogLevel)(args);
353
+ var dashboardProcess = null;
354
+ try {
355
+ // First check if we have environment variables set
356
+ if (!process.env.SN_USER ||
357
+ !process.env.SN_PASSWORD ||
358
+ !process.env.SN_INSTANCE) {
359
+ let loginAnswers = await (0, wizard_1.getLoginInfo)();
360
+ Logger_1.logger.error("Missing ServiceNow credentials. Please ensure SN_USER, SN_PASSWORD, and SN_INSTANCE are set in your .env file");
361
+ throw new Error("ServiceNow credentials not configured");
362
+ }
363
+ // Start dashboard unless --no-dashboard flag is set
364
+ if (!args.noDashboard) {
365
+ dashboardProcess = startDashboardProcess(args.port);
366
+ }
367
+ // Import and start the multi-scope watcher
368
+ const { startMultiScopeWatching } = await Promise.resolve().then(() => __importStar(require("./MultiScopeWatcher")));
369
+ // Start watching all scopes with monitoring options
370
+ await startMultiScopeWatching({
371
+ monitorIntervalMs: args.noMonitoring ? 0 : (args.monitorInterval || 120) * 1000,
372
+ });
373
+ // Keep the process running
374
+ process.on("SIGINT", async () => {
375
+ Logger_1.logger.info("\nStopping multi-scope watch...");
376
+ if (dashboardProcess) {
377
+ dashboardProcess.kill("SIGINT");
378
+ }
379
+ const { stopMultiScopeWatching } = await Promise.resolve().then(() => __importStar(require("./MultiScopeWatcher")));
380
+ stopMultiScopeWatching();
381
+ process.exit(0);
382
+ });
383
+ }
384
+ catch (error) {
385
+ if (dashboardProcess) {
386
+ dashboardProcess.kill("SIGINT");
387
+ }
388
+ Logger_1.logger.error("Failed to start multi-scope watch: " + error);
389
+ throw error;
390
+ }
391
+ }
392
+ function startDashboardProcess(portOverride) {
393
+ var serverPath;
394
+ try {
395
+ serverPath = require.resolve("@tenonhq/dovetail-dashboard/server.js");
396
+ }
397
+ catch (e) {
398
+ Logger_1.logger.warn("Dashboard package not installed. Run: npm install @tenonhq/dovetail-dashboard");
399
+ return null;
400
+ }
401
+ var port = portOverride ? String(portOverride) : (process.env.DASHBOARD_PORT || "3456");
402
+ var server = (0, child_process_1.spawn)("node", [serverPath], {
403
+ cwd: process.cwd(),
404
+ stdio: "ignore",
405
+ env: { ...process.env, DASHBOARD_PORT: port },
406
+ detached: false,
407
+ });
408
+ server.on("error", function (err) {
409
+ Logger_1.logger.warn("Failed to start dashboard: " + err.message);
410
+ });
411
+ Logger_1.logger.info("Dashboard started at http://localhost:" + port);
412
+ // Open browser after a short delay
413
+ setTimeout(function () {
414
+ var url = "http://localhost:" + port;
415
+ (0, child_process_1.spawn)("open", [url], { stdio: "ignore" });
416
+ }, 1000);
417
+ return server;
418
+ }