@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.
- package/README.md +6 -0
- package/dist/FileLogger.js +207 -0
- package/dist/FileUtils.js +387 -0
- package/dist/Logger.js +88 -0
- package/dist/MultiScopeWatcher.js +615 -0
- package/dist/PluginManager.js +125 -0
- package/dist/_defaultOptionsOLD.js +44 -0
- package/dist/allScopesCommands.js +418 -0
- package/dist/appUtils.js +929 -0
- package/dist/benchmark.js +104 -0
- package/dist/bootstrap.js +62 -0
- package/dist/claudeCommand.js +60 -0
- package/dist/clickupCommands.js +472 -0
- package/dist/clickupPushHelper.js +33 -0
- package/dist/commander.js +466 -0
- package/dist/commands.js +378 -0
- package/dist/config.js +697 -0
- package/dist/constants.js +15 -0
- package/dist/createRecordCommand.js +314 -0
- package/dist/dashboardCommand.js +38 -0
- package/dist/defaultOptions.js +14 -0
- package/dist/deleteRecordCommand.js +282 -0
- package/dist/flowDesigner/values.js +55 -0
- package/dist/genericUtils.js +189 -0
- package/dist/gitUtils.js +99 -0
- package/dist/index.js +35 -0
- package/dist/initSystem/corePlugin.js +344 -0
- package/dist/initSystem/discovery.js +107 -0
- package/dist/initSystem/orchestrator.js +408 -0
- package/dist/logMessages.js +82 -0
- package/dist/loginCommand.js +20 -0
- package/dist/migrateCommand.js +123 -0
- package/dist/projectFiles.js +32 -0
- package/dist/recentEdits.js +66 -0
- package/dist/schemaCommand.js +85 -0
- package/dist/snClient.js +519 -0
- package/dist/tests/benchmarkRefresh.test.js +265 -0
- package/dist/tests/clickupCommands.test.js +384 -0
- package/dist/tests/clickupPushHelper.test.js +124 -0
- package/dist/tests/configFallback.test.js +68 -0
- package/dist/tests/cred.test.js +59 -0
- package/dist/tests/discovery.test.js +67 -0
- package/dist/tests/ensureUpdateSetWarnings.test.js +218 -0
- package/dist/tests/errorLogLevels.test.js +273 -0
- package/dist/tests/example.test.js +6 -0
- package/dist/tests/fileContextSkipReason.test.js +116 -0
- package/dist/tests/globalDebounce.test.js +308 -0
- package/dist/tests/migrateCommand.test.js +113 -0
- package/dist/tests/multi-scope-watcher.test.js +558 -0
- package/dist/tests/orchestrator.test.js +23 -0
- package/dist/tests/pushFiles.test.js +248 -0
- package/dist/tests/rateLimitCoordination.test.js +271 -0
- package/dist/tests/resolveConfigReadOnly.test.js +67 -0
- package/dist/tests/retryOnHttpErr.test.js +154 -0
- package/dist/tests/scopeCaching.test.js +115 -0
- package/dist/tests/serializeUpdateSetConfig.test.js +325 -0
- package/dist/tests/snClient.fallback.test.js +120 -0
- package/dist/tests/syncManifestRefresh.test.js +287 -0
- package/dist/tests/syncManifestWhitelist.test.js +192 -0
- package/dist/tests/taskClear.test.js +170 -0
- package/dist/tests/taskStaleness.test.js +220 -0
- package/dist/tests/unwrapSNResponseError.test.js +145 -0
- package/dist/tests/v2Values.test.js +79 -0
- package/dist/tests/validateTaskId.test.js +304 -0
- package/dist/tests/verifyUpdateSetSwitch.test.js +277 -0
- package/dist/tests/writeEnvVar.test.js +119 -0
- package/dist/updateSetCommands.js +702 -0
- package/dist/wizard.js +40 -0
- package/package.json +69 -0
- package/skills/dove-configure-pipeline.md +158 -0
- package/skills/dove-create-plugin.md +174 -0
- package/skills/dove-create-record.md +135 -0
- package/skills/dove-debug-build.md +117 -0
- package/skills/dove-delete-record.md +104 -0
- package/skills/dove-manage-tables.md +174 -0
- package/skills/dove-manage-update-sets.md +88 -0
- package/skills/dove-setup-project.md +154 -0
- package/skills/dove-troubleshoot-sync.md +113 -0
|
@@ -0,0 +1,702 @@
|
|
|
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.showCurrentScopeCommand = showCurrentScopeCommand;
|
|
40
|
+
exports.changeScopeCommand = changeScopeCommand;
|
|
41
|
+
exports.showCurrentUpdateSetCommand = showCurrentUpdateSetCommand;
|
|
42
|
+
exports.createUpdateSetCommand = createUpdateSetCommand;
|
|
43
|
+
exports.switchUpdateSetCommand = switchUpdateSetCommand;
|
|
44
|
+
exports.listUpdateSetsCommand = listUpdateSetsCommand;
|
|
45
|
+
const inquirer_1 = __importDefault(require("inquirer"));
|
|
46
|
+
const snClient_1 = require("./snClient");
|
|
47
|
+
const Logger_1 = require("./Logger");
|
|
48
|
+
const commands_1 = require("./commands");
|
|
49
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
50
|
+
const dovetail_clickup_1 = require("@tenonhq/dovetail-clickup");
|
|
51
|
+
const clickupCommands_1 = require("./clickupCommands");
|
|
52
|
+
/**
|
|
53
|
+
* Shows the current scope
|
|
54
|
+
*/
|
|
55
|
+
async function showCurrentScopeCommand(args) {
|
|
56
|
+
(0, commands_1.setLogLevel)(args);
|
|
57
|
+
try {
|
|
58
|
+
const client = (0, snClient_1.defaultClient)();
|
|
59
|
+
const scopeObj = await (0, snClient_1.unwrapSNResponse)(client.getCurrentScope());
|
|
60
|
+
if (scopeObj && scopeObj.scope) {
|
|
61
|
+
Logger_1.logger.info(chalk_1.default.bold("\nCurrent Scope:"));
|
|
62
|
+
Logger_1.logger.info("─".repeat(40));
|
|
63
|
+
Logger_1.logger.info(chalk_1.default.green(`► ${scopeObj.displayName || scopeObj.scope}`));
|
|
64
|
+
Logger_1.logger.info(` Scope ID: ${scopeObj.scope}`);
|
|
65
|
+
Logger_1.logger.info(` Sys ID: ${scopeObj.sys_id}`);
|
|
66
|
+
// Also show current update set for this scope
|
|
67
|
+
const updateSet = await getCurrentUpdateSetDetails();
|
|
68
|
+
if (updateSet) {
|
|
69
|
+
Logger_1.logger.info(` Update Set: ${updateSet.name}`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
Logger_1.logger.warn("Unable to retrieve current scope information");
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
catch (e) {
|
|
77
|
+
Logger_1.logger.error("Failed to get current scope");
|
|
78
|
+
if (e instanceof Error)
|
|
79
|
+
Logger_1.logger.error(e.message);
|
|
80
|
+
throw e;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Changes the current scope
|
|
85
|
+
*/
|
|
86
|
+
async function changeScopeCommand(args) {
|
|
87
|
+
(0, commands_1.setLogLevel)(args);
|
|
88
|
+
try {
|
|
89
|
+
const client = (0, snClient_1.defaultClient)();
|
|
90
|
+
// Get scope from args or prompt
|
|
91
|
+
let scope = args.scope;
|
|
92
|
+
if (!scope) {
|
|
93
|
+
const inquirer = (await Promise.resolve().then(() => __importStar(require("inquirer")))).default;
|
|
94
|
+
const answers = await inquirer.prompt([{
|
|
95
|
+
type: "input",
|
|
96
|
+
name: "scope",
|
|
97
|
+
message: "Enter scope to switch to (e.g., x_cadso_core):",
|
|
98
|
+
validate: (input) => {
|
|
99
|
+
if (!input || input.trim() === "") {
|
|
100
|
+
return "Scope is required";
|
|
101
|
+
}
|
|
102
|
+
return true;
|
|
103
|
+
}
|
|
104
|
+
}]);
|
|
105
|
+
scope = answers.scope;
|
|
106
|
+
}
|
|
107
|
+
Logger_1.logger.info(`Switching to scope: ${scope}`);
|
|
108
|
+
// Call the changeScope API
|
|
109
|
+
const response = await client.changeScope(scope);
|
|
110
|
+
let result = await response.data;
|
|
111
|
+
// Handle wrapped response
|
|
112
|
+
if (result && result.result) {
|
|
113
|
+
result = result.result;
|
|
114
|
+
}
|
|
115
|
+
if (result && result.message === 'Success') {
|
|
116
|
+
Logger_1.logger.success(chalk_1.default.green(`✓ Successfully switched to scope: ${scope}`));
|
|
117
|
+
// Try to get and show the current update set for this scope
|
|
118
|
+
const updateSet = await getCurrentUpdateSetDetails(scope);
|
|
119
|
+
if (updateSet) {
|
|
120
|
+
Logger_1.logger.info(`Current update set: ${updateSet.name}`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
else if (result && result.error) {
|
|
124
|
+
throw new Error(`Failed to switch scope: ${result.error}`);
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
throw new Error("Failed to switch scope - unexpected response");
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
catch (e) {
|
|
131
|
+
Logger_1.logger.error("Failed to change scope");
|
|
132
|
+
if (e instanceof Error) {
|
|
133
|
+
Logger_1.logger.error(e.message);
|
|
134
|
+
if (e.response) {
|
|
135
|
+
Logger_1.logger.error(`Response status: ${e.response.status}`);
|
|
136
|
+
Logger_1.logger.error(`Response data: ${JSON.stringify(e.response.data)}`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
throw e;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Shows the current active update set
|
|
144
|
+
*/
|
|
145
|
+
async function showCurrentUpdateSetCommand(args) {
|
|
146
|
+
(0, commands_1.setLogLevel)(args);
|
|
147
|
+
try {
|
|
148
|
+
const client = (0, snClient_1.defaultClient)();
|
|
149
|
+
const response = await client.getCurrentUpdateSet(args.scope);
|
|
150
|
+
let result = await response.data;
|
|
151
|
+
// Handle wrapped response
|
|
152
|
+
if (result && result.result) {
|
|
153
|
+
result = result.result;
|
|
154
|
+
}
|
|
155
|
+
Logger_1.logger.debug(`API Response: ${JSON.stringify(result)}`);
|
|
156
|
+
if (result && result.sysId) {
|
|
157
|
+
Logger_1.logger.info(chalk_1.default.bold("\nCurrent Update Set:"));
|
|
158
|
+
Logger_1.logger.info("─".repeat(40));
|
|
159
|
+
Logger_1.logger.info(chalk_1.default.green(`► ${result.name || 'Unknown'}`));
|
|
160
|
+
Logger_1.logger.info(` ID: ${result.sysId}`);
|
|
161
|
+
if (args.scope) {
|
|
162
|
+
Logger_1.logger.info(` Scope: ${args.scope}`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
else if (result && result.error) {
|
|
166
|
+
Logger_1.logger.warn(`Error from API: ${result.error}`);
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
Logger_1.logger.warn("No update set is currently active or unable to retrieve update set information");
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
catch (e) {
|
|
173
|
+
Logger_1.logger.error("Failed to get current update set");
|
|
174
|
+
if (e instanceof Error) {
|
|
175
|
+
Logger_1.logger.error(e.message);
|
|
176
|
+
if (e.response) {
|
|
177
|
+
Logger_1.logger.error(`Response status: ${e.response.status}`);
|
|
178
|
+
Logger_1.logger.error(`Response data: ${JSON.stringify(e.response.data)}`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
throw e;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Creates a new update set with the given name and switches to it
|
|
186
|
+
*/
|
|
187
|
+
async function createUpdateSetCommand(args) {
|
|
188
|
+
(0, commands_1.setLogLevel)(args);
|
|
189
|
+
try {
|
|
190
|
+
const client = (0, snClient_1.defaultClient)();
|
|
191
|
+
// If --clickup is provided, fetch task data and generate name/description
|
|
192
|
+
if (args.clickup && !args.name) {
|
|
193
|
+
var clickupResult = await resolveClickUpTaskForUpdateSet(args.clickup);
|
|
194
|
+
args.name = clickupResult.name;
|
|
195
|
+
if (!args.description) {
|
|
196
|
+
args.description = clickupResult.description;
|
|
197
|
+
}
|
|
198
|
+
args._clickupTaskId = clickupResult.taskId;
|
|
199
|
+
}
|
|
200
|
+
// Get update set details from user
|
|
201
|
+
const { name, description, scope } = await promptForUpdateSetDetails(args);
|
|
202
|
+
let scopeSysId;
|
|
203
|
+
if (scope) {
|
|
204
|
+
// Get scope sys_id if scope name provided
|
|
205
|
+
const scopeResult = await (0, snClient_1.unwrapSNResponse)(client.getScopeId(scope));
|
|
206
|
+
if (scopeResult.length === 0) {
|
|
207
|
+
throw new Error(`Scope "${scope}" not found`);
|
|
208
|
+
}
|
|
209
|
+
scopeSysId = scopeResult[0].sys_id;
|
|
210
|
+
// Switch to the target scope first
|
|
211
|
+
Logger_1.logger.info(`Switching to scope: ${scope}`);
|
|
212
|
+
await switchToScope(scopeSysId, scope);
|
|
213
|
+
}
|
|
214
|
+
// Create the update set in the correct scope
|
|
215
|
+
Logger_1.logger.info(`Creating update set: ${name}`);
|
|
216
|
+
const createResponse = client.createUpdateSet(name, scopeSysId, description);
|
|
217
|
+
const createResult = await (0, snClient_1.unwrapSNResponse)(createResponse);
|
|
218
|
+
const updateSetSysId = createResult.sys_id;
|
|
219
|
+
// Switch to the new update set
|
|
220
|
+
Logger_1.logger.info(`Activating update set: ${name}`);
|
|
221
|
+
try {
|
|
222
|
+
await switchToUpdateSet(updateSetSysId, name, scope);
|
|
223
|
+
Logger_1.logger.info(chalk_1.default.green(`✓ Update set "${name}" created and activated`));
|
|
224
|
+
}
|
|
225
|
+
catch (switchError) {
|
|
226
|
+
Logger_1.logger.warn(`Update set "${name}" created but could not be activated automatically`);
|
|
227
|
+
Logger_1.logger.info(`You can manually switch to it using: npx dove switchUpdateSet --name "${name}"`);
|
|
228
|
+
if (switchError instanceof Error) {
|
|
229
|
+
Logger_1.logger.warn(`Switch error: ${switchError.message}`);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
Logger_1.logger.info(`Update Set ID: ${updateSetSysId}`);
|
|
233
|
+
if (scope) {
|
|
234
|
+
Logger_1.logger.info(`Scope: ${scope}`);
|
|
235
|
+
}
|
|
236
|
+
// If created from a ClickUp task, offer to post a comment back
|
|
237
|
+
if (args._clickupTaskId) {
|
|
238
|
+
try {
|
|
239
|
+
var token = process.env.CLICKUP_API_TOKEN;
|
|
240
|
+
if (token) {
|
|
241
|
+
var commentAnswer = await inquirer_1.default.prompt([{
|
|
242
|
+
type: "confirm",
|
|
243
|
+
name: "postComment",
|
|
244
|
+
message: "Post a comment to the ClickUp task linking this update set?",
|
|
245
|
+
default: true,
|
|
246
|
+
}]);
|
|
247
|
+
if (commentAnswer.postComment) {
|
|
248
|
+
var clickupApi = (0, dovetail_clickup_1.createClickUpApi)({ token: token });
|
|
249
|
+
await clickupApi.addComment({
|
|
250
|
+
taskId: args._clickupTaskId,
|
|
251
|
+
commentText: "ServiceNow Update Set created: " + name + " (ID: " + updateSetSysId + ")",
|
|
252
|
+
});
|
|
253
|
+
Logger_1.logger.success(chalk_1.default.green("✓ Comment posted to ClickUp task"));
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
catch (commentErr) {
|
|
258
|
+
Logger_1.logger.warn("Could not post comment to ClickUp task");
|
|
259
|
+
if (commentErr instanceof Error)
|
|
260
|
+
Logger_1.logger.debug(commentErr.message);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
catch (e) {
|
|
265
|
+
Logger_1.logger.error("Failed to create update set");
|
|
266
|
+
if (e instanceof Error)
|
|
267
|
+
Logger_1.logger.error(e.message);
|
|
268
|
+
throw e;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Switches to an existing update set
|
|
273
|
+
*/
|
|
274
|
+
async function switchUpdateSetCommand(args) {
|
|
275
|
+
(0, commands_1.setLogLevel)(args);
|
|
276
|
+
try {
|
|
277
|
+
const client = (0, snClient_1.defaultClient)();
|
|
278
|
+
// Get update set to switch to
|
|
279
|
+
const targetUpdateSet = await selectUpdateSet(args.name, args.scope);
|
|
280
|
+
if (!targetUpdateSet) {
|
|
281
|
+
throw new Error("No update set selected");
|
|
282
|
+
}
|
|
283
|
+
// Extract the actual values from display_value objects
|
|
284
|
+
const sysId = typeof targetUpdateSet.sys_id === 'object' && targetUpdateSet.sys_id !== null ? targetUpdateSet.sys_id.value : targetUpdateSet.sys_id;
|
|
285
|
+
const name = typeof targetUpdateSet.name === 'object' && targetUpdateSet.name !== null ? targetUpdateSet.name.value : targetUpdateSet.name;
|
|
286
|
+
const applicationName = (targetUpdateSet.application && targetUpdateSet.application.display_value) || (typeof targetUpdateSet.application === 'object' && targetUpdateSet.application !== null ? targetUpdateSet.application.value : targetUpdateSet.application);
|
|
287
|
+
const applicationScope = typeof targetUpdateSet.application === 'object' && targetUpdateSet.application !== null ? targetUpdateSet.application.value : targetUpdateSet.application;
|
|
288
|
+
// Switch to the selected update set
|
|
289
|
+
await switchToUpdateSet(sysId, name, args.scope || applicationScope);
|
|
290
|
+
Logger_1.logger.info(chalk_1.default.green(`✓ Switched to update set: ${name}`));
|
|
291
|
+
Logger_1.logger.info(`Update Set ID: ${sysId}`);
|
|
292
|
+
if (applicationName) {
|
|
293
|
+
Logger_1.logger.info(`Scope: ${applicationName}`);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
catch (e) {
|
|
297
|
+
Logger_1.logger.error("Failed to switch update set");
|
|
298
|
+
if (e instanceof Error)
|
|
299
|
+
Logger_1.logger.error(e.message);
|
|
300
|
+
throw e;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Lists all open/in-progress update sets
|
|
305
|
+
*/
|
|
306
|
+
async function listUpdateSetsCommand(args) {
|
|
307
|
+
(0, commands_1.setLogLevel)(args);
|
|
308
|
+
try {
|
|
309
|
+
const client = (0, snClient_1.defaultClient)();
|
|
310
|
+
// Build query for update sets
|
|
311
|
+
let query = "state=in progress";
|
|
312
|
+
if (args.scope) {
|
|
313
|
+
// Get scope sys_id if scope name provided
|
|
314
|
+
const scopeResult = await (0, snClient_1.unwrapSNResponse)(client.getScopeId(args.scope));
|
|
315
|
+
if (scopeResult.length === 0) {
|
|
316
|
+
throw new Error(`Scope "${args.scope}" not found`);
|
|
317
|
+
}
|
|
318
|
+
query += `^application=${scopeResult[0].sys_id}`;
|
|
319
|
+
}
|
|
320
|
+
// Get list of update sets
|
|
321
|
+
const updateSets = await getUpdateSets(query);
|
|
322
|
+
if (updateSets.length === 0) {
|
|
323
|
+
Logger_1.logger.info("No in-progress update sets found");
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
// Get current update set
|
|
327
|
+
const currentUpdateSetId = await getCurrentUpdateSetId();
|
|
328
|
+
// Display update sets
|
|
329
|
+
Logger_1.logger.info(chalk_1.default.bold("\nIn-Progress Update Sets:"));
|
|
330
|
+
Logger_1.logger.info("─".repeat(80));
|
|
331
|
+
updateSets.forEach((updateSet) => {
|
|
332
|
+
// Handle both plain values and display_value objects
|
|
333
|
+
const sysId = typeof updateSet.sys_id === 'object' && updateSet.sys_id !== null ? updateSet.sys_id.value : updateSet.sys_id;
|
|
334
|
+
const name = typeof updateSet.name === 'object' && updateSet.name !== null ? updateSet.name.value : updateSet.name;
|
|
335
|
+
const description = typeof updateSet.description === 'object' && updateSet.description !== null ? updateSet.description.value : updateSet.description;
|
|
336
|
+
const createdBy = typeof updateSet.sys_created_by === 'object' && updateSet.sys_created_by !== null ? updateSet.sys_created_by.display_value || updateSet.sys_created_by.value : updateSet.sys_created_by;
|
|
337
|
+
const applicationName = (updateSet.application && updateSet.application.display_value) || (typeof updateSet.application === 'object' && updateSet.application !== null ? updateSet.application.value : updateSet.application);
|
|
338
|
+
const isCurrent = sysId === currentUpdateSetId;
|
|
339
|
+
const marker = isCurrent ? chalk_1.default.green("► ") : " ";
|
|
340
|
+
const displayName = isCurrent ? chalk_1.default.green(name) : name;
|
|
341
|
+
console.log(`${marker}${displayName}`);
|
|
342
|
+
if (description) {
|
|
343
|
+
console.log(` Description: ${description}`);
|
|
344
|
+
}
|
|
345
|
+
if (applicationName) {
|
|
346
|
+
console.log(` Scope: ${applicationName}`);
|
|
347
|
+
}
|
|
348
|
+
console.log(` Created: ${formatDate(updateSet.sys_created_on)} by ${createdBy}`);
|
|
349
|
+
console.log(` ID: ${sysId}`);
|
|
350
|
+
console.log("");
|
|
351
|
+
});
|
|
352
|
+
if (currentUpdateSetId) {
|
|
353
|
+
Logger_1.logger.info(chalk_1.default.dim("► indicates current active update set"));
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
catch (e) {
|
|
357
|
+
Logger_1.logger.error("Failed to list update sets");
|
|
358
|
+
if (e instanceof Error)
|
|
359
|
+
Logger_1.logger.error(e.message);
|
|
360
|
+
throw e;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* Helper function to prompt for update set details
|
|
365
|
+
*/
|
|
366
|
+
async function promptForUpdateSetDetails(args) {
|
|
367
|
+
const questions = [];
|
|
368
|
+
if (!args.name) {
|
|
369
|
+
questions.push({
|
|
370
|
+
type: "input",
|
|
371
|
+
name: "name",
|
|
372
|
+
message: "Update set name:",
|
|
373
|
+
validate: (input) => {
|
|
374
|
+
if (!input || input.trim() === "") {
|
|
375
|
+
return "Update set name is required";
|
|
376
|
+
}
|
|
377
|
+
return true;
|
|
378
|
+
}
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
if (!args.description && !args.skipDescription) {
|
|
382
|
+
questions.push({
|
|
383
|
+
type: "input",
|
|
384
|
+
name: "description",
|
|
385
|
+
message: "Description (optional):"
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
if (!args.scope && !args.skipScope) {
|
|
389
|
+
questions.push({
|
|
390
|
+
type: "input",
|
|
391
|
+
name: "scope",
|
|
392
|
+
message: "Scope (optional, e.g., x_company_app):"
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
const answers = questions.length > 0 ? await inquirer_1.default.prompt(questions) : {};
|
|
396
|
+
return {
|
|
397
|
+
name: args.name || answers.name,
|
|
398
|
+
description: args.description || answers.description,
|
|
399
|
+
scope: args.scope || answers.scope
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* Helper function to switch to an update set using the new API endpoint.
|
|
404
|
+
* Verifies the switch was successful by reading back the current update set.
|
|
405
|
+
*/
|
|
406
|
+
async function switchToUpdateSet(updateSetSysId, name, scope) {
|
|
407
|
+
const client = (0, snClient_1.defaultClient)();
|
|
408
|
+
Logger_1.logger.debug(`Switching to update set - sysId: ${updateSetSysId}, name: ${name}, scope: ${scope}`);
|
|
409
|
+
// Use the new changeUpdateSet endpoint
|
|
410
|
+
// Can use either sysId or name+scope combination
|
|
411
|
+
const params = {};
|
|
412
|
+
if (updateSetSysId) {
|
|
413
|
+
params.sysId = updateSetSysId;
|
|
414
|
+
}
|
|
415
|
+
if (name) {
|
|
416
|
+
params.name = name;
|
|
417
|
+
}
|
|
418
|
+
if (scope) {
|
|
419
|
+
params.scope = scope;
|
|
420
|
+
}
|
|
421
|
+
const response = await client.changeUpdateSet(params);
|
|
422
|
+
let result = await response.data;
|
|
423
|
+
// Handle wrapped response
|
|
424
|
+
if (result && result.result) {
|
|
425
|
+
result = result.result;
|
|
426
|
+
}
|
|
427
|
+
Logger_1.logger.debug(`Change update set response: ${JSON.stringify(result)}`);
|
|
428
|
+
if (result.error) {
|
|
429
|
+
throw new Error(result.error);
|
|
430
|
+
}
|
|
431
|
+
// Check if the message indicates success
|
|
432
|
+
if (result.message && !result.message.includes("Success") && !result.message.includes("changed")) {
|
|
433
|
+
throw new Error(result.message);
|
|
434
|
+
}
|
|
435
|
+
// Verify the switch was successful
|
|
436
|
+
var verified = await verifyActiveUpdateSet(client, updateSetSysId, scope);
|
|
437
|
+
if (!verified) {
|
|
438
|
+
// Retry once
|
|
439
|
+
Logger_1.logger.warn("Update set verification failed, retrying switch...");
|
|
440
|
+
var retryResponse = await client.changeUpdateSet(params);
|
|
441
|
+
var retryResult = await retryResponse.data;
|
|
442
|
+
if (retryResult && retryResult.result) {
|
|
443
|
+
retryResult = retryResult.result;
|
|
444
|
+
}
|
|
445
|
+
if (retryResult.error) {
|
|
446
|
+
throw new Error(retryResult.error);
|
|
447
|
+
}
|
|
448
|
+
var retryVerified = await verifyActiveUpdateSet(client, updateSetSysId, scope);
|
|
449
|
+
if (!retryVerified) {
|
|
450
|
+
var currentName = await getActiveUpdateSetName(client, scope);
|
|
451
|
+
throw new Error("Update set " + (name || updateSetSysId) + " was created but could not be activated. Current update set is " + (currentName || "unknown") + ".");
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
/**
|
|
456
|
+
* Verifies the currently active update set matches the expected sys_id.
|
|
457
|
+
*/
|
|
458
|
+
async function verifyActiveUpdateSet(client, expectedSysId, scope) {
|
|
459
|
+
try {
|
|
460
|
+
var response = await client.getCurrentUpdateSet(scope);
|
|
461
|
+
var result = await response.data;
|
|
462
|
+
if (result && result.result) {
|
|
463
|
+
result = result.result;
|
|
464
|
+
}
|
|
465
|
+
if (result && result.sysId === expectedSysId) {
|
|
466
|
+
return true;
|
|
467
|
+
}
|
|
468
|
+
Logger_1.logger.warn("Verification mismatch: expected " + expectedSysId + ", got " + (result && result.sysId ? result.sysId : "null"));
|
|
469
|
+
return false;
|
|
470
|
+
}
|
|
471
|
+
catch (e) {
|
|
472
|
+
Logger_1.logger.warn("Verification check failed: " + (e instanceof Error ? e.message : String(e)));
|
|
473
|
+
return false;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
/**
|
|
477
|
+
* Gets the name of the currently active update set for error messages.
|
|
478
|
+
*/
|
|
479
|
+
async function getActiveUpdateSetName(client, scope) {
|
|
480
|
+
try {
|
|
481
|
+
var response = await client.getCurrentUpdateSet(scope);
|
|
482
|
+
var result = await response.data;
|
|
483
|
+
if (result && result.result) {
|
|
484
|
+
result = result.result;
|
|
485
|
+
}
|
|
486
|
+
return (result && result.name) ? result.name : null;
|
|
487
|
+
}
|
|
488
|
+
catch (e) {
|
|
489
|
+
return null;
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
/**
|
|
493
|
+
* Helper function to switch to a scope
|
|
494
|
+
*/
|
|
495
|
+
async function switchToScope(scopeSysId, scopeName) {
|
|
496
|
+
const client = (0, snClient_1.defaultClient)();
|
|
497
|
+
// Get current user sys_id
|
|
498
|
+
const userSysId = await (0, snClient_1.unwrapTableAPIFirstItem)(client.getUserSysId(), "sys_id");
|
|
499
|
+
// Get or create user preference for current app
|
|
500
|
+
try {
|
|
501
|
+
const curAppUserPrefId = await (0, snClient_1.unwrapTableAPIFirstItem)(client.getCurrentAppUserPrefSysId(userSysId), "sys_id");
|
|
502
|
+
// Update existing preference
|
|
503
|
+
await client.updateCurrentAppUserPref(scopeSysId, curAppUserPrefId);
|
|
504
|
+
}
|
|
505
|
+
catch (e) {
|
|
506
|
+
// Create new preference if it doesn't exist
|
|
507
|
+
await client.createCurrentAppUserPref(scopeSysId, userSysId);
|
|
508
|
+
}
|
|
509
|
+
Logger_1.logger.info(`Switched to scope: ${scopeName}`);
|
|
510
|
+
}
|
|
511
|
+
/**
|
|
512
|
+
* Helper function to get update sets based on query
|
|
513
|
+
*/
|
|
514
|
+
async function getUpdateSets(query) {
|
|
515
|
+
const client = (0, snClient_1.defaultClient)();
|
|
516
|
+
const endpoint = "api/now/table/sys_update_set";
|
|
517
|
+
const response = client.client.get(endpoint, {
|
|
518
|
+
params: {
|
|
519
|
+
sysparm_query: query,
|
|
520
|
+
sysparm_fields: "sys_id,name,description,state,application,application.name,sys_created_on,sys_created_by",
|
|
521
|
+
sysparm_limit: 100,
|
|
522
|
+
sysparm_display_value: "all"
|
|
523
|
+
}
|
|
524
|
+
});
|
|
525
|
+
return (0, snClient_1.unwrapSNResponse)(response);
|
|
526
|
+
}
|
|
527
|
+
/**
|
|
528
|
+
* Helper function to get current update set ID using the new endpoint
|
|
529
|
+
*/
|
|
530
|
+
async function getCurrentUpdateSetId(scope) {
|
|
531
|
+
try {
|
|
532
|
+
const client = (0, snClient_1.defaultClient)();
|
|
533
|
+
const response = await client.getCurrentUpdateSet(scope);
|
|
534
|
+
let result = await response.data;
|
|
535
|
+
// Handle wrapped response
|
|
536
|
+
if (result && result.result) {
|
|
537
|
+
result = result.result;
|
|
538
|
+
}
|
|
539
|
+
if (result && result.sysId) {
|
|
540
|
+
return result.sysId;
|
|
541
|
+
}
|
|
542
|
+
return null;
|
|
543
|
+
}
|
|
544
|
+
catch (e) {
|
|
545
|
+
return null;
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
/**
|
|
549
|
+
* Helper function to get current update set details
|
|
550
|
+
*/
|
|
551
|
+
async function getCurrentUpdateSetDetails(scope) {
|
|
552
|
+
try {
|
|
553
|
+
const client = (0, snClient_1.defaultClient)();
|
|
554
|
+
const response = await client.getCurrentUpdateSet(scope);
|
|
555
|
+
let result = await response.data;
|
|
556
|
+
// Handle wrapped response
|
|
557
|
+
if (result && result.result) {
|
|
558
|
+
result = result.result;
|
|
559
|
+
}
|
|
560
|
+
if (result && result.sysId) {
|
|
561
|
+
return {
|
|
562
|
+
sysId: result.sysId,
|
|
563
|
+
name: result.name || 'Unknown'
|
|
564
|
+
};
|
|
565
|
+
}
|
|
566
|
+
return null;
|
|
567
|
+
}
|
|
568
|
+
catch (e) {
|
|
569
|
+
return null;
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
/**
|
|
573
|
+
* Helper function to select an update set from a list
|
|
574
|
+
*/
|
|
575
|
+
async function selectUpdateSet(nameFilter, scopeFilter) {
|
|
576
|
+
let query = "state=in progress";
|
|
577
|
+
if (scopeFilter) {
|
|
578
|
+
const client = (0, snClient_1.defaultClient)();
|
|
579
|
+
const scopeResult = await (0, snClient_1.unwrapSNResponse)(client.getScopeId(scopeFilter));
|
|
580
|
+
if (scopeResult.length === 0) {
|
|
581
|
+
Logger_1.logger.error(`Scope "${scopeFilter}" not found`);
|
|
582
|
+
return null;
|
|
583
|
+
}
|
|
584
|
+
query += `^application=${scopeResult[0].sys_id}`;
|
|
585
|
+
}
|
|
586
|
+
const updateSets = await getUpdateSets(query);
|
|
587
|
+
if (updateSets.length === 0) {
|
|
588
|
+
Logger_1.logger.info("No in-progress update sets found");
|
|
589
|
+
return null;
|
|
590
|
+
}
|
|
591
|
+
// Filter by name if provided
|
|
592
|
+
let filteredSets = updateSets;
|
|
593
|
+
if (nameFilter) {
|
|
594
|
+
filteredSets = updateSets.filter(us => {
|
|
595
|
+
const name = typeof us.name === 'object' && us.name !== null ? us.name.value : us.name;
|
|
596
|
+
return name.toLowerCase().includes(nameFilter.toLowerCase());
|
|
597
|
+
});
|
|
598
|
+
if (filteredSets.length === 0) {
|
|
599
|
+
Logger_1.logger.error(`No update sets found matching "${nameFilter}"`);
|
|
600
|
+
return null;
|
|
601
|
+
}
|
|
602
|
+
// If exact match found, use it
|
|
603
|
+
const exactMatch = filteredSets.find(us => {
|
|
604
|
+
const name = typeof us.name === 'object' && us.name !== null ? us.name.value : us.name;
|
|
605
|
+
return name.toLowerCase() === nameFilter.toLowerCase();
|
|
606
|
+
});
|
|
607
|
+
if (exactMatch) {
|
|
608
|
+
return exactMatch;
|
|
609
|
+
}
|
|
610
|
+
// If only one match, use it
|
|
611
|
+
if (filteredSets.length === 1) {
|
|
612
|
+
return filteredSets[0];
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
// Prompt user to select from list
|
|
616
|
+
const choices = filteredSets.map(us => {
|
|
617
|
+
const name = typeof us.name === 'object' && us.name !== null ? us.name.value : us.name;
|
|
618
|
+
const description = typeof us.description === 'object' && us.description !== null ? us.description.value : us.description;
|
|
619
|
+
const applicationName = (us.application && us.application.display_value) || (typeof us.application === 'object' && us.application !== null ? us.application.value : us.application);
|
|
620
|
+
return {
|
|
621
|
+
name: `${name}${applicationName ? ` (${applicationName})` : ""} - ${description || "No description"}`,
|
|
622
|
+
value: us
|
|
623
|
+
};
|
|
624
|
+
});
|
|
625
|
+
const { selectedUpdateSet } = await inquirer_1.default.prompt([
|
|
626
|
+
{
|
|
627
|
+
type: "list",
|
|
628
|
+
name: "selectedUpdateSet",
|
|
629
|
+
message: "Select an update set:",
|
|
630
|
+
choices,
|
|
631
|
+
pageSize: 15
|
|
632
|
+
}
|
|
633
|
+
]);
|
|
634
|
+
return selectedUpdateSet;
|
|
635
|
+
}
|
|
636
|
+
/**
|
|
637
|
+
* Helper function to format date
|
|
638
|
+
*/
|
|
639
|
+
/**
|
|
640
|
+
* Resolves a ClickUp task identifier into an update set name and description.
|
|
641
|
+
* Uses Claude CLI for name refinement with convention-based fallback.
|
|
642
|
+
*/
|
|
643
|
+
async function resolveClickUpTaskForUpdateSet(clickupIdentifier) {
|
|
644
|
+
var token = process.env.CLICKUP_API_TOKEN;
|
|
645
|
+
if (!token || token === "") {
|
|
646
|
+
throw new Error("CLICKUP_API_TOKEN not set. Run 'dove clickup setup' or add it to your .env file.");
|
|
647
|
+
}
|
|
648
|
+
var parsed = (0, dovetail_clickup_1.parseClickUpIdentifier)(clickupIdentifier);
|
|
649
|
+
Logger_1.logger.info("Fetching ClickUp task: " + parsed.taskId + "...");
|
|
650
|
+
var api = (0, dovetail_clickup_1.createClickUpApi)({ token: token });
|
|
651
|
+
var task = await api.getTask({ taskId: parsed.taskId });
|
|
652
|
+
Logger_1.logger.success(chalk_1.default.green('✓ Found: "' + task.name + '"'));
|
|
653
|
+
Logger_1.logger.info("");
|
|
654
|
+
// Generate update set name via Claude CLI (with fallback)
|
|
655
|
+
Logger_1.logger.info("Generating update set name...");
|
|
656
|
+
var suggestedName = (0, clickupCommands_1.refineUpdateSetName)({
|
|
657
|
+
taskName: task.name,
|
|
658
|
+
taskId: task.id,
|
|
659
|
+
taskDescription: task.description || "",
|
|
660
|
+
});
|
|
661
|
+
Logger_1.logger.success(chalk_1.default.green('✓ Suggested name: "' + suggestedName + '"'));
|
|
662
|
+
// Generate description from task summary
|
|
663
|
+
var suggestedDescription = (0, dovetail_clickup_1.formatTaskSummary)({ task: task });
|
|
664
|
+
// Let user confirm or edit
|
|
665
|
+
var confirmAnswers = await inquirer_1.default.prompt([
|
|
666
|
+
{
|
|
667
|
+
type: "input",
|
|
668
|
+
name: "name",
|
|
669
|
+
message: "Update set name:",
|
|
670
|
+
default: suggestedName,
|
|
671
|
+
validate: function (input) {
|
|
672
|
+
if (!input || input.trim() === "")
|
|
673
|
+
return "Name is required";
|
|
674
|
+
return true;
|
|
675
|
+
},
|
|
676
|
+
},
|
|
677
|
+
{
|
|
678
|
+
type: "input",
|
|
679
|
+
name: "description",
|
|
680
|
+
message: "Description:",
|
|
681
|
+
default: suggestedDescription,
|
|
682
|
+
},
|
|
683
|
+
]);
|
|
684
|
+
return {
|
|
685
|
+
name: confirmAnswers.name,
|
|
686
|
+
description: confirmAnswers.description,
|
|
687
|
+
taskId: parsed.taskId,
|
|
688
|
+
};
|
|
689
|
+
}
|
|
690
|
+
function formatDate(dateString) {
|
|
691
|
+
try {
|
|
692
|
+
const actualDateString = typeof dateString === 'object' && dateString !== null ? dateString.display_value || dateString.value : dateString;
|
|
693
|
+
const date = new Date(actualDateString);
|
|
694
|
+
if (isNaN(date.getTime())) {
|
|
695
|
+
return actualDateString || "Unknown";
|
|
696
|
+
}
|
|
697
|
+
return `${date.toLocaleDateString()} ${date.toLocaleTimeString()}`;
|
|
698
|
+
}
|
|
699
|
+
catch (e) {
|
|
700
|
+
return typeof dateString === 'string' ? dateString : "Unknown";
|
|
701
|
+
}
|
|
702
|
+
}
|