@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,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
|
+
}
|