@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,408 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.runInit = runInit;
7
+ exports.runLogin = runLogin;
8
+ const inquirer_1 = __importDefault(require("inquirer"));
9
+ const chalk_1 = __importDefault(require("chalk"));
10
+ const dotenv_1 = __importDefault(require("dotenv"));
11
+ const fs_1 = __importDefault(require("fs"));
12
+ const path_1 = __importDefault(require("path"));
13
+ const Logger_1 = require("../Logger");
14
+ const FileUtils_1 = require("../FileUtils");
15
+ const discovery_1 = require("./discovery");
16
+ const corePlugin_1 = require("./corePlugin");
17
+ // ============================================================================
18
+ // Init Context
19
+ // ============================================================================
20
+ /**
21
+ * @description Builds an InitContext from the current environment.
22
+ * Reads existing .env if present. Pulls process.env fallbacks only for
23
+ * env keys declared by the provided plugins (no hardcoded allowlist).
24
+ */
25
+ function buildInitContext(plugins) {
26
+ const rootDir = process.cwd();
27
+ const envPath = path_1.default.resolve(rootDir, ".env");
28
+ const env = {};
29
+ // Load existing .env values
30
+ try {
31
+ const parsed = dotenv_1.default.parse(fs_1.default.readFileSync(envPath, "utf8"));
32
+ Object.assign(env, parsed);
33
+ }
34
+ catch (e) {
35
+ // No .env yet — starting fresh
36
+ }
37
+ // Pull from process.env for keys declared by plugins but missing from .env
38
+ const pluginEnvKeys = plugins.flatMap(p => (p.login || []).map(h => h.envKey));
39
+ pluginEnvKeys.forEach(key => {
40
+ if (process.env[key] && !env[key]) {
41
+ env[key] = process.env[key];
42
+ }
43
+ });
44
+ let hasConfig = false;
45
+ try {
46
+ fs_1.default.accessSync(path_1.default.join(rootDir, "dove.config.js"), fs_1.default.constants.F_OK);
47
+ hasConfig = true;
48
+ }
49
+ catch (e) {
50
+ try {
51
+ fs_1.default.accessSync(path_1.default.join(rootDir, "sinc.config.js"), fs_1.default.constants.F_OK);
52
+ hasConfig = true;
53
+ }
54
+ catch (_e) {
55
+ // No config yet
56
+ }
57
+ }
58
+ return { env, answers: {}, rootDir, hasConfig, inquirer: inquirer_1.default, chalk: chalk_1.default };
59
+ }
60
+ // ============================================================================
61
+ // Plugin Selection
62
+ // ============================================================================
63
+ async function promptPluginSelection(externalPlugins) {
64
+ if (externalPlugins.length === 0) {
65
+ return [corePlugin_1.corePlugin];
66
+ }
67
+ const choices = externalPlugins.map(plugin => ({
68
+ name: plugin.displayName + " — " + plugin.description,
69
+ value: plugin.name,
70
+ checked: false,
71
+ }));
72
+ const answer = await inquirer_1.default.prompt([{
73
+ type: "checkbox",
74
+ name: "plugins",
75
+ message: "Which integrations would you like to configure?",
76
+ choices,
77
+ }]);
78
+ const selectedNames = new Set(answer.plugins);
79
+ const selected = externalPlugins.filter(p => selectedNames.has(p.name));
80
+ return [corePlugin_1.corePlugin, ...selected];
81
+ }
82
+ async function collectLoginHooks(hooks, context) {
83
+ for (const hook of hooks) {
84
+ const existingValue = context.env[hook.envKey] || "";
85
+ // Show instructions if provided
86
+ if (hook.instructions && hook.instructions.length > 0) {
87
+ Logger_1.logger.info("");
88
+ hook.instructions.forEach(line => Logger_1.logger.info(line));
89
+ Logger_1.logger.info("");
90
+ }
91
+ // Build the prompt
92
+ const promptConfig = {
93
+ type: hook.prompt.type,
94
+ name: "value",
95
+ message: hook.prompt.message,
96
+ };
97
+ if (hook.prompt.mask) {
98
+ promptConfig.mask = hook.prompt.mask;
99
+ }
100
+ // Show existing value as default for non-password fields
101
+ if (existingValue && hook.prompt.type !== "password") {
102
+ promptConfig.default = existingValue;
103
+ }
104
+ // Add basic validation for required fields
105
+ if (hook.required !== false) {
106
+ promptConfig.validate = (input) => {
107
+ if (!input || input.trim() === "")
108
+ return "This field is required";
109
+ return true;
110
+ };
111
+ }
112
+ const answer = await inquirer_1.default.prompt([promptConfig]);
113
+ context.env[hook.envKey] = answer.value.trim();
114
+ }
115
+ }
116
+ async function runLoginPhase(plugin, context) {
117
+ const hooks = plugin.login;
118
+ if (!hooks || hooks.length === 0)
119
+ return;
120
+ // Core plugin: retry loop with specific error messages
121
+ if (plugin.name === "core") {
122
+ // If .env already has all required credentials, offer to skip login
123
+ const hasAllCreds = context.env.SN_INSTANCE && context.env.SN_USER && context.env.SN_PASSWORD;
124
+ if (hasAllCreds) {
125
+ Logger_1.logger.info("Found existing credentials:");
126
+ Logger_1.logger.info(" Instance: " + context.env.SN_INSTANCE);
127
+ Logger_1.logger.info(" User: " + context.env.SN_USER);
128
+ Logger_1.logger.info("");
129
+ const useExisting = await inquirer_1.default.prompt([{
130
+ type: "confirm",
131
+ name: "confirmed",
132
+ message: "Use these credentials?",
133
+ default: true,
134
+ }]);
135
+ if (useExisting.confirmed) {
136
+ Logger_1.logger.info("Validating credentials...");
137
+ const result = await (0, corePlugin_1.validateCoreLogin)(context);
138
+ if (result === true) {
139
+ Logger_1.logger.success(chalk_1.default.green("✓ Connected to " + context.env.SN_INSTANCE));
140
+ return;
141
+ }
142
+ Logger_1.logger.error(chalk_1.default.red("✗ " + result));
143
+ Logger_1.logger.info("Please re-enter your credentials.");
144
+ context.env.SN_PASSWORD = "";
145
+ }
146
+ }
147
+ while (true) {
148
+ await collectLoginHooks(hooks, context);
149
+ // Run per-hook validation
150
+ let hookFailed = false;
151
+ for (const hook of hooks) {
152
+ if (hook.validate) {
153
+ const result = await hook.validate(context.env[hook.envKey], context);
154
+ if (result !== true) {
155
+ Logger_1.logger.error(chalk_1.default.red("✗ " + result));
156
+ hookFailed = true;
157
+ break;
158
+ }
159
+ }
160
+ }
161
+ if (hookFailed) {
162
+ const retry = await inquirer_1.default.prompt([{
163
+ type: "confirm",
164
+ name: "again",
165
+ message: "Try again?",
166
+ default: true,
167
+ }]);
168
+ if (!retry.again) {
169
+ throw new Error("Login cancelled");
170
+ }
171
+ context.env.SN_PASSWORD = "";
172
+ continue;
173
+ }
174
+ Logger_1.logger.info("Validating credentials...");
175
+ const coreResult = await (0, corePlugin_1.validateCoreLogin)(context);
176
+ if (coreResult === true) {
177
+ Logger_1.logger.success(chalk_1.default.green("✓ Connected to " + context.env.SN_INSTANCE));
178
+ return;
179
+ }
180
+ Logger_1.logger.error(chalk_1.default.red("✗ " + coreResult));
181
+ Logger_1.logger.info("");
182
+ const retry = await inquirer_1.default.prompt([{
183
+ type: "confirm",
184
+ name: "again",
185
+ message: "Try again?",
186
+ default: true,
187
+ }]);
188
+ if (!retry.again) {
189
+ throw new Error("Login cancelled");
190
+ }
191
+ // Clear password so it re-prompts; instance and user show as defaults
192
+ context.env.SN_PASSWORD = "";
193
+ }
194
+ }
195
+ // Non-core plugins: original behavior (no retry loop)
196
+ await collectLoginHooks(hooks, context);
197
+ for (const hook of hooks) {
198
+ if (hook.validate) {
199
+ const result = await hook.validate(context.env[hook.envKey], context);
200
+ if (result !== true) {
201
+ Logger_1.logger.error(chalk_1.default.red("✗ " + result));
202
+ throw new Error("Validation failed for " + hook.envKey);
203
+ }
204
+ }
205
+ }
206
+ }
207
+ async function runConfigPhase(context) {
208
+ if (context.hasConfig) {
209
+ var answer = await inquirer_1.default.prompt([{
210
+ type: "list",
211
+ name: "configAction",
212
+ message: "Existing config (dove.config.js or legacy sinc.config.js) found:",
213
+ choices: [
214
+ { name: "Use current config", value: "keep" },
215
+ { name: "Replace config (regenerate after scope selection)", value: "replace" },
216
+ ],
217
+ }]);
218
+ context.answers.configAction = answer.configAction;
219
+ if (answer.configAction === "keep") {
220
+ Logger_1.logger.info(chalk_1.default.green(" ✓ Using existing config"));
221
+ }
222
+ return;
223
+ }
224
+ // No config — will be generated in initialize phase after scopes are selected
225
+ context.answers.configAction = "generate";
226
+ }
227
+ async function runConfigurePhase(plugin, context) {
228
+ const hooks = plugin.configure;
229
+ if (!hooks || hooks.length === 0)
230
+ return;
231
+ for (const hook of hooks) {
232
+ Logger_1.logger.debug("Running configure hook: " + hook.label);
233
+ const result = await hook.run(context);
234
+ if (result !== null && result !== undefined) {
235
+ context.answers[hook.key] = result;
236
+ }
237
+ }
238
+ }
239
+ /**
240
+ * @description Saves env vars declared by plugins to .env (merge-style).
241
+ * Only writes keys that plugins explicitly declared via login hooks or
242
+ * configure hooks — never writes transient context values.
243
+ */
244
+ function saveEnvVars(context, plugins) {
245
+ const pluginKeys = new Set();
246
+ plugins.forEach(plugin => {
247
+ (plugin.login || []).forEach(hook => pluginKeys.add(hook.envKey));
248
+ (plugin.configure || []).forEach(hook => {
249
+ if (context.env[hook.key])
250
+ pluginKeys.add(hook.key);
251
+ });
252
+ });
253
+ const vars = Array.from(pluginKeys)
254
+ .filter(key => context.env[key])
255
+ .map(key => ({ key, value: context.env[key] }));
256
+ if (vars.length > 0) {
257
+ (0, FileUtils_1.writeEnvVars)({ vars });
258
+ // Also set on process.env for immediate use
259
+ vars.forEach(({ key, value }) => { process.env[key] = value; });
260
+ Logger_1.logger.success(chalk_1.default.green("✓ Saved to .env (existing values preserved)"));
261
+ }
262
+ }
263
+ async function runInit(options) {
264
+ let failed = false;
265
+ try {
266
+ Logger_1.logger.info("");
267
+ Logger_1.logger.info(chalk_1.default.bold(" Dovetail Setup"));
268
+ Logger_1.logger.info(" " + "═".repeat(40));
269
+ Logger_1.logger.info("");
270
+ // 1. Discover plugins
271
+ const externalPlugins = (0, discovery_1.discoverPlugins)();
272
+ if (externalPlugins.length > 0) {
273
+ Logger_1.logger.info(" Detected packages:");
274
+ Logger_1.logger.info(" ● dovetail-core (" + chalk_1.default.cyan("ServiceNow") + ")");
275
+ externalPlugins.forEach(p => {
276
+ Logger_1.logger.info(" ● dovetail-" + p.name + " (" + chalk_1.default.cyan(p.displayName) + ")");
277
+ });
278
+ Logger_1.logger.info("");
279
+ }
280
+ // 2. Select plugins
281
+ const selectedPlugins = await promptPluginSelection(externalPlugins);
282
+ // 3. Build context (passes plugins so env fallback uses their declared keys)
283
+ const context = buildInitContext(selectedPlugins);
284
+ // 4. Login phase
285
+ Logger_1.logger.info("");
286
+ Logger_1.logger.info(chalk_1.default.bold(" ── Login " + "─".repeat(30)));
287
+ Logger_1.logger.info("");
288
+ for (const plugin of selectedPlugins) {
289
+ await runLoginPhase(plugin, context);
290
+ }
291
+ // 5. Save env vars after login
292
+ saveEnvVars(context, selectedPlugins);
293
+ // 5.5 Config phase
294
+ Logger_1.logger.info("");
295
+ Logger_1.logger.info(chalk_1.default.bold(" ── Config " + "─".repeat(29)));
296
+ Logger_1.logger.info("");
297
+ await runConfigPhase(context);
298
+ // 6. Configure phase
299
+ Logger_1.logger.info("");
300
+ Logger_1.logger.info(chalk_1.default.bold(" ── Configure " + "─".repeat(26)));
301
+ Logger_1.logger.info("");
302
+ for (const plugin of selectedPlugins) {
303
+ await runConfigurePhase(plugin, context);
304
+ }
305
+ // 7. Initialize phase
306
+ Logger_1.logger.info("");
307
+ Logger_1.logger.info(chalk_1.default.bold(" ── Initialize " + "─".repeat(25)));
308
+ Logger_1.logger.info("");
309
+ for (const plugin of selectedPlugins) {
310
+ if (plugin.initialize) {
311
+ try {
312
+ await plugin.initialize(context);
313
+ }
314
+ catch (e) {
315
+ const msg = e instanceof Error ? e.message : String(e);
316
+ Logger_1.logger.error("Initialization failed for " + plugin.displayName + ": " + msg);
317
+ failed = true;
318
+ }
319
+ }
320
+ }
321
+ // 8. Summary
322
+ Logger_1.logger.info("");
323
+ Logger_1.logger.info(" " + "═".repeat(40));
324
+ if (failed) {
325
+ Logger_1.logger.warn(" Setup completed with errors. Review the output above.");
326
+ }
327
+ else {
328
+ Logger_1.logger.success(chalk_1.default.green(" Setup complete!") + " Run " + chalk_1.default.cyan("dove watch") + " to start.");
329
+ }
330
+ Logger_1.logger.info("");
331
+ }
332
+ catch (e) {
333
+ const message = e instanceof Error ? e.message : String(e);
334
+ Logger_1.logger.error("Init failed: " + message);
335
+ }
336
+ }
337
+ async function runLogin(options) {
338
+ try {
339
+ const opts = options || {};
340
+ // Determine which plugins to log in to
341
+ let pluginsToLogin;
342
+ const coreAliases = new Set(["core", "servicenow", "sn"]);
343
+ if (opts.pluginName) {
344
+ if (coreAliases.has(opts.pluginName)) {
345
+ pluginsToLogin = [corePlugin_1.corePlugin];
346
+ }
347
+ else {
348
+ const externalPlugins = (0, discovery_1.discoverPlugins)();
349
+ const match = externalPlugins.find(p => p.name === opts.pluginName);
350
+ if (!match) {
351
+ Logger_1.logger.error("Plugin '" + opts.pluginName + "' not found. Available plugins:");
352
+ externalPlugins.forEach(p => Logger_1.logger.info(" - " + p.name + " (" + p.displayName + ")"));
353
+ return;
354
+ }
355
+ pluginsToLogin = [match];
356
+ }
357
+ }
358
+ else if (opts.all) {
359
+ pluginsToLogin = [corePlugin_1.corePlugin, ...(0, discovery_1.discoverPlugins)()];
360
+ }
361
+ else {
362
+ pluginsToLogin = [corePlugin_1.corePlugin];
363
+ }
364
+ // Build context with discovered plugins for env key resolution
365
+ const context = buildInitContext(pluginsToLogin);
366
+ // Apply CLI flag overrides for non-interactive mode
367
+ if (opts.instance)
368
+ context.env.SN_INSTANCE = opts.instance;
369
+ if (opts.user)
370
+ context.env.SN_USER = opts.user;
371
+ if (opts.password)
372
+ context.env.SN_PASSWORD = opts.password;
373
+ // Dynamic header based on which plugins are being logged in
374
+ const pluginNames = pluginsToLogin.map(p => p.displayName).join(" + ");
375
+ Logger_1.logger.info("");
376
+ Logger_1.logger.info(chalk_1.default.bold(" " + pluginNames + " Login"));
377
+ Logger_1.logger.info(" " + "─".repeat(40));
378
+ Logger_1.logger.info("");
379
+ // Check if all core credentials provided via flags (non-interactive mode)
380
+ const hasAllCoreFlags = opts.instance && opts.user && opts.password;
381
+ if (hasAllCoreFlags && pluginsToLogin.length === 1 && pluginsToLogin[0].name === "core") {
382
+ Logger_1.logger.info("Validating credentials...");
383
+ const result = await (0, corePlugin_1.validateCoreLogin)(context);
384
+ if (result !== true) {
385
+ Logger_1.logger.error(chalk_1.default.red("✗ " + result));
386
+ return;
387
+ }
388
+ Logger_1.logger.success(chalk_1.default.green("✓ Connected to " + context.env.SN_INSTANCE));
389
+ }
390
+ else {
391
+ for (const plugin of pluginsToLogin) {
392
+ await runLoginPhase(plugin, context);
393
+ }
394
+ }
395
+ // Save env vars
396
+ saveEnvVars(context, pluginsToLogin);
397
+ Logger_1.logger.info("");
398
+ Logger_1.logger.info("You can now use:");
399
+ Logger_1.logger.info(" dove init — Initialize a new project");
400
+ Logger_1.logger.info(" dove watch — Watch for changes");
401
+ Logger_1.logger.info(" dove status — Check instance connection");
402
+ Logger_1.logger.info("");
403
+ }
404
+ catch (e) {
405
+ const message = e instanceof Error ? e.message : String(e);
406
+ Logger_1.logger.error("Login failed: " + message);
407
+ }
408
+ }
@@ -0,0 +1,82 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.logBuildResults = exports.logPushResults = exports.log = void 0;
7
+ exports.logFilePush = logFilePush;
8
+ exports.logDeploy = logDeploy;
9
+ const Logger_1 = require("./Logger");
10
+ const chalk_1 = __importDefault(require("chalk"));
11
+ exports.log = console.log;
12
+ function parseError(err) {
13
+ return `${err.name}:
14
+ ${err.message}
15
+ Stack Trace:
16
+ ${err.stack || "none"}`;
17
+ }
18
+ function logFilePush(context, res) {
19
+ const { message, success } = res;
20
+ const instance = process.env.SN_INSTANCE || "instance";
21
+ const label = chalk_1.default.bold.blue;
22
+ const timestamp = new Date().toLocaleTimeString();
23
+ if (success) {
24
+ Logger_1.logger.info(chalk_1.default.green("Pushed") + " " + context.tableName + "/" + context.name +
25
+ " (" + context.targetField + ") to " + instance + " at " + timestamp);
26
+ }
27
+ else {
28
+ Logger_1.logger.error("Failed to push " + context.tableName + "/" + context.name +
29
+ " (" + context.targetField + ") to " + instance);
30
+ Logger_1.logger.error(message);
31
+ }
32
+ }
33
+ function multiLog(files, success, resultSet, successMessage, errorMessage, err) {
34
+ if (success) {
35
+ let fileNum = chalk_1.default.bold.blue(resultSet.filter((result) => result).length + "");
36
+ let message = chalk_1.default.green(`${fileNum} files ${successMessage}`);
37
+ Logger_1.logger.info(message);
38
+ }
39
+ else {
40
+ Logger_1.logger.error(errorMessage);
41
+ if (err) {
42
+ Logger_1.logger.error(parseError(err));
43
+ }
44
+ }
45
+ spacer();
46
+ }
47
+ function logDeploy(files, success, resultSet, err) {
48
+ multiLog(files, success, resultSet, "successfully deployed", "Failed to deploy files", err);
49
+ }
50
+ function spacer() {
51
+ Logger_1.logger.info("");
52
+ }
53
+ const logOperationResults = (results, operation) => {
54
+ const unsuccessful = results.filter((r) => !r.success);
55
+ const logr = Logger_1.logger.getInternalLogger();
56
+ const label = (content) => chalk_1.default.bold.blue(content);
57
+ const success = (content) => chalk_1.default.bold.green(content);
58
+ const fail = (content) => chalk_1.default.bold.red(content);
59
+ logr.info(`${label("Total Records:")} ${results.length}`);
60
+ logr.info(`${label(`Successful ${operation}:`)} ${success(results.length - unsuccessful.length + "")}`);
61
+ logr.info(`${label(`Failed ${operation}:`)} ${fail(unsuccessful.length + "")}`);
62
+ if (unsuccessful.length === 0) {
63
+ return;
64
+ }
65
+ Logger_1.logger.error("-".repeat(60));
66
+ Logger_1.logger.error(fail("Error Summary"));
67
+ Logger_1.logger.error("-".repeat(60));
68
+ unsuccessful.forEach(({ message }, index) => {
69
+ if (unsuccessful.length === 1) {
70
+ logr.error(message);
71
+ }
72
+ logr.error(`${index + 1}. ${message}`);
73
+ });
74
+ };
75
+ const logPushResults = (results) => {
76
+ logOperationResults(results, "Pushes");
77
+ };
78
+ exports.logPushResults = logPushResults;
79
+ const logBuildResults = (results) => {
80
+ logOperationResults(results, "Builds");
81
+ };
82
+ exports.logBuildResults = logBuildResults;
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.loginCommand = loginCommand;
4
+ const commands_1 = require("./commands");
5
+ const orchestrator_1 = require("./initSystem/orchestrator");
6
+ /**
7
+ * @description Command handler for `dove login`.
8
+ * Authenticates with ServiceNow and other integrations, saves credentials to .env.
9
+ */
10
+ async function loginCommand(args) {
11
+ (0, commands_1.setLogLevel)(args);
12
+ await (0, orchestrator_1.runLogin)({
13
+ logLevel: args.logLevel,
14
+ pluginName: args.plugin || undefined,
15
+ all: args.all || false,
16
+ instance: args.instance || undefined,
17
+ user: args.user || undefined,
18
+ password: args.password || undefined,
19
+ });
20
+ }
@@ -0,0 +1,123 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.migrateCommand = migrateCommand;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const Logger_1 = require("./Logger");
10
+ const commands_1 = require("./commands");
11
+ // Map legacy -> new artifact filenames at the project root.
12
+ const FILE_RENAMES = [
13
+ { legacy: "sinc.config.js", next: "dove.config.js" },
14
+ { legacy: "sinc.manifest.json", next: "dove.manifest.json" },
15
+ { legacy: "sinc.diff.manifest.json", next: "dove.diff.manifest.json" },
16
+ { legacy: ".sinc-active-task.json", next: ".dove-active-task.json" },
17
+ { legacy: ".sinc-update-sets.json", next: ".dove-update-sets.json" },
18
+ { legacy: ".sinc-recent-edits.json", next: ".dove-recent-edits.json" },
19
+ ];
20
+ // Per-scope manifests follow the pattern sinc.manifest.<scope>.json. Glob from disk.
21
+ function collectScopeManifestRenames(rootDir) {
22
+ let entries = [];
23
+ try {
24
+ entries = fs_1.default.readdirSync(rootDir);
25
+ }
26
+ catch {
27
+ return [];
28
+ }
29
+ return entries
30
+ .filter((f) => f.startsWith("sinc.manifest.") && f !== "sinc.manifest.json" && f.endsWith(".json"))
31
+ .map((f) => ({
32
+ legacy: f,
33
+ next: f.replace(/^sinc\.manifest\./, "dove.manifest."),
34
+ }));
35
+ }
36
+ // Rewrite package.json scripts and dependencies.
37
+ function buildPackageJsonStep(rootDir) {
38
+ const pkgPath = path_1.default.join(rootDir, "package.json");
39
+ if (!fs_1.default.existsSync(pkgPath))
40
+ return null;
41
+ const original = fs_1.default.readFileSync(pkgPath, "utf8");
42
+ // Order matters: rewrite the @tenonhq/sincronia-* deps first so the
43
+ // subsequent \bsinc\b sweep doesn't have to know about package-name
44
+ // collisions. \bsinc\b doesn't match `sincronia` (no word boundary between
45
+ // `c` and `r`), so the package-name replace and the script-token replace
46
+ // are independent. The standalone \bsinc\b catches every CLI invocation
47
+ // shape — bare (`sinc watch`), chained (`&& sinc push`), prefixed
48
+ // (`npx sinc`), trailing (`build && sinc deploy`) — without the four
49
+ // separate fragile regexes we had before.
50
+ const updated = original
51
+ .replace(/@tenonhq\/sincronia-/g, "@tenonhq/dovetail-")
52
+ .replace(/\bsinc\b/g, "dove");
53
+ if (updated === original)
54
+ return null;
55
+ return {
56
+ description: "Update package.json (rename @tenonhq/sincronia-* deps and `sinc` scripts to `dove`)",
57
+ apply: () => {
58
+ fs_1.default.writeFileSync(pkgPath, updated);
59
+ },
60
+ };
61
+ }
62
+ function buildFileRenameStep(rootDir, legacy, next) {
63
+ const legacyPath = path_1.default.join(rootDir, legacy);
64
+ const nextPath = path_1.default.join(rootDir, next);
65
+ if (!fs_1.default.existsSync(legacyPath))
66
+ return null;
67
+ if (fs_1.default.existsSync(nextPath)) {
68
+ Logger_1.logger.warn("Skipping rename of " + legacy + " — " + next + " already exists. Resolve manually.");
69
+ return null;
70
+ }
71
+ return {
72
+ description: "Rename " + legacy + " -> " + next,
73
+ apply: () => {
74
+ fs_1.default.renameSync(legacyPath, nextPath);
75
+ },
76
+ };
77
+ }
78
+ async function migrateCommand(args) {
79
+ (0, commands_1.setLogLevel)(args);
80
+ const rootDir = process.cwd();
81
+ const apply = !!args.apply;
82
+ const steps = [];
83
+ // 1. Single-file renames.
84
+ for (const r of FILE_RENAMES) {
85
+ const step = buildFileRenameStep(rootDir, r.legacy, r.next);
86
+ if (step)
87
+ steps.push(step);
88
+ }
89
+ // 2. Per-scope manifest renames.
90
+ for (const r of collectScopeManifestRenames(rootDir)) {
91
+ const step = buildFileRenameStep(rootDir, r.legacy, r.next);
92
+ if (step)
93
+ steps.push(step);
94
+ }
95
+ // 3. package.json rewrite.
96
+ const pkgStep = buildPackageJsonStep(rootDir);
97
+ if (pkgStep)
98
+ steps.push(pkgStep);
99
+ if (steps.length === 0) {
100
+ Logger_1.logger.success("Nothing to migrate — project already uses Dovetail filenames.");
101
+ return;
102
+ }
103
+ Logger_1.logger.info("Migration plan (" + steps.length + " step" + (steps.length === 1 ? "" : "s") + "):");
104
+ steps.forEach((s, i) => {
105
+ Logger_1.logger.info(" " + (i + 1) + ". " + s.description);
106
+ });
107
+ if (!apply) {
108
+ Logger_1.logger.info("");
109
+ Logger_1.logger.warn("Dry run only — re-run with `--apply` to perform the migration.");
110
+ return;
111
+ }
112
+ for (const step of steps) {
113
+ try {
114
+ step.apply();
115
+ Logger_1.logger.success("✓ " + step.description);
116
+ }
117
+ catch (e) {
118
+ Logger_1.logger.error("✗ " + step.description + ": " + (e instanceof Error ? e.message : String(e)));
119
+ }
120
+ }
121
+ Logger_1.logger.info("");
122
+ Logger_1.logger.info("Migration complete. Review the changes, run `npm install` if package.json was updated, and re-import the Dovetail Scripted REST API XML on your ServiceNow instance (see docs/dovetail-servicenow-migration.md).");
123
+ }
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.getActiveTaskPath = getActiveTaskPath;
7
+ exports.getUpdateSetsConfigPath = getUpdateSetsConfigPath;
8
+ exports.getRecentEditsPath = getRecentEditsPath;
9
+ const fs_1 = __importDefault(require("fs"));
10
+ const path_1 = __importDefault(require("path"));
11
+ // Resolve a project-root dotfile by its short name (e.g. "active-task" → ".dove-active-task.json").
12
+ // Reads prefer the new `.dove-*` filename; if only the legacy `.sinc-*` sibling exists, it is
13
+ // returned so existing projects keep working until the user runs `dove migrate`. New writes
14
+ // land at the `.dove-*` path when neither file is present yet.
15
+ function resolveDoveDotfile(name) {
16
+ const dovePath = path_1.default.resolve(process.cwd(), ".dove-" + name + ".json");
17
+ const sincPath = path_1.default.resolve(process.cwd(), ".sinc-" + name + ".json");
18
+ if (fs_1.default.existsSync(dovePath))
19
+ return dovePath;
20
+ if (fs_1.default.existsSync(sincPath))
21
+ return sincPath;
22
+ return dovePath;
23
+ }
24
+ function getActiveTaskPath() {
25
+ return resolveDoveDotfile("active-task");
26
+ }
27
+ function getUpdateSetsConfigPath() {
28
+ return resolveDoveDotfile("update-sets");
29
+ }
30
+ function getRecentEditsPath() {
31
+ return resolveDoveDotfile("recent-edits");
32
+ }