@tenonhq/sincronia-core 0.0.68 → 0.0.71
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/dist/FileUtils.js +60 -0
- package/dist/allScopesCommands.js +20 -2
- package/dist/appUtils.js +46 -9
- package/dist/clickupCommands.js +2 -59
- package/dist/commander.js +29 -1
- package/dist/commands.js +2 -3
- package/dist/initSystem/corePlugin.js +228 -0
- package/dist/initSystem/discovery.js +91 -0
- package/dist/initSystem/orchestrator.js +352 -0
- package/dist/loginCommand.js +20 -0
- package/dist/tests/clickupCommands.test.js +9 -1
- package/dist/tests/discovery.test.js +56 -0
- package/dist/tests/orchestrator.test.js +23 -0
- package/dist/tests/writeEnvVar.test.js +119 -0
- package/dist/wizard.js +8 -146
- package/package.json +1 -2
package/dist/FileUtils.js
CHANGED
|
@@ -37,6 +37,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
37
37
|
};
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
39
|
exports.writeFileForce = exports.writeSNFileForce = exports.writeSNFileIfNotExists = exports.writeBuildFile = exports.summarizeFile = exports.encodedPathsToFilePaths = exports.isValidPath = exports.splitEncodedPaths = exports.getPathsInPath = exports.isDirectory = exports.toAbsolutePath = exports.getFileContextFromPath = exports.getBuildExt = exports.isUnderPath = exports.appendToPath = exports.pathExists = exports.createDirRecursively = exports.writeSNFileCurry = exports.writeScopeManifest = exports.writeManifestFile = exports.SNFileExists = void 0;
|
|
40
|
+
exports.writeEnvVar = writeEnvVar;
|
|
41
|
+
exports.writeEnvVars = writeEnvVars;
|
|
40
42
|
const constants_1 = require("./constants");
|
|
41
43
|
const fs_1 = __importStar(require("fs"));
|
|
42
44
|
const path_1 = __importDefault(require("path"));
|
|
@@ -255,3 +257,61 @@ exports.writeBuildFile = writeBuildFile;
|
|
|
255
257
|
exports.writeSNFileIfNotExists = (0, exports.writeSNFileCurry)(true);
|
|
256
258
|
exports.writeSNFileForce = (0, exports.writeSNFileCurry)(false);
|
|
257
259
|
exports.writeFileForce = fs_1.promises.writeFile;
|
|
260
|
+
// ============================================================================
|
|
261
|
+
// .env File Utilities — merge-style writes (never destructive)
|
|
262
|
+
// ============================================================================
|
|
263
|
+
function escapeRegex(str) {
|
|
264
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
265
|
+
}
|
|
266
|
+
function quoteEnvValue(value) {
|
|
267
|
+
if (/[\s#"'\\]/.test(value)) {
|
|
268
|
+
return "\"" + value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/\n/g, "\\n") + "\"";
|
|
269
|
+
}
|
|
270
|
+
return value;
|
|
271
|
+
}
|
|
272
|
+
function mergeEnvLine(content, key, value) {
|
|
273
|
+
const escaped = escapeRegex(key);
|
|
274
|
+
const regex = new RegExp("^" + escaped + "=.*$", "m");
|
|
275
|
+
const line = key + "=" + quoteEnvValue(value);
|
|
276
|
+
if (regex.test(content)) {
|
|
277
|
+
return content.replace(regex, line);
|
|
278
|
+
}
|
|
279
|
+
if (content.length > 0 && content.charAt(content.length - 1) !== "\n") {
|
|
280
|
+
content += "\n";
|
|
281
|
+
}
|
|
282
|
+
return content + line + "\n";
|
|
283
|
+
}
|
|
284
|
+
function readEnvFile(envPath) {
|
|
285
|
+
try {
|
|
286
|
+
return fs_1.default.readFileSync(envPath, "utf8");
|
|
287
|
+
}
|
|
288
|
+
catch (e) {
|
|
289
|
+
return "";
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* @description Writes a single env variable to a .env file, preserving existing values.
|
|
294
|
+
* @param {Object} params - Parameters object.
|
|
295
|
+
* @param {string} params.key - The environment variable name.
|
|
296
|
+
* @param {string} params.value - The value to set.
|
|
297
|
+
* @param {string} [params.envPath] - Path to .env file. Defaults to process.cwd()/.env.
|
|
298
|
+
*/
|
|
299
|
+
function writeEnvVar(params) {
|
|
300
|
+
const resolvedPath = params.envPath || path_1.default.resolve(process.cwd(), ".env");
|
|
301
|
+
const content = mergeEnvLine(readEnvFile(resolvedPath), params.key, params.value);
|
|
302
|
+
fs_1.default.writeFileSync(resolvedPath, content, "utf8");
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* @description Writes multiple env variables to a .env file in a single read/write cycle.
|
|
306
|
+
* @param {Object} params - Parameters object.
|
|
307
|
+
* @param {Array<{key: string; value: string}>} params.vars - Array of key/value pairs to write.
|
|
308
|
+
* @param {string} [params.envPath] - Path to .env file. Defaults to process.cwd()/.env.
|
|
309
|
+
*/
|
|
310
|
+
function writeEnvVars(params) {
|
|
311
|
+
const resolvedPath = params.envPath || path_1.default.resolve(process.cwd(), ".env");
|
|
312
|
+
let content = readEnvFile(resolvedPath);
|
|
313
|
+
params.vars.forEach(({ key, value }) => {
|
|
314
|
+
content = mergeEnvLine(content, key, value);
|
|
315
|
+
});
|
|
316
|
+
fs_1.default.writeFileSync(resolvedPath, content, "utf8");
|
|
317
|
+
}
|
|
@@ -32,6 +32,9 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
32
32
|
return result;
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
35
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
39
|
exports.initScopesCommand = initScopesCommand;
|
|
37
40
|
exports.watchAllScopesCommand = watchAllScopesCommand;
|
|
@@ -44,6 +47,7 @@ const commands_1 = require("./commands");
|
|
|
44
47
|
const path = __importStar(require("path"));
|
|
45
48
|
const fs = __importStar(require("fs"));
|
|
46
49
|
const child_process_1 = require("child_process");
|
|
50
|
+
const progress_1 = __importDefault(require("progress"));
|
|
47
51
|
const fsp = fs.promises;
|
|
48
52
|
// Custom function to process manifest with specific source directory
|
|
49
53
|
async function processManifestForScope(manifest, sourceDirectory, forceWrite = false) {
|
|
@@ -58,7 +62,20 @@ async function processManifestForScope(manifest, sourceDirectory, forceWrite = f
|
|
|
58
62
|
}
|
|
59
63
|
const tables = manifest.tables || {};
|
|
60
64
|
const tableNames = Object.keys(tables);
|
|
61
|
-
|
|
65
|
+
var totalRecords = 0;
|
|
66
|
+
for (var tn = 0; tn < tableNames.length; tn++) {
|
|
67
|
+
totalRecords += Object.keys(tables[tableNames[tn]].records || {}).length;
|
|
68
|
+
}
|
|
69
|
+
var scopeLabel = sourceDirectory.split(path.sep).pop() || "scope";
|
|
70
|
+
var progBar = null;
|
|
71
|
+
if (Logger_1.logger.getLogLevel() === "info" && totalRecords > 0) {
|
|
72
|
+
progBar = new progress_1.default(":scope :bar :current/:total (:percent)", {
|
|
73
|
+
total: totalRecords,
|
|
74
|
+
width: 40,
|
|
75
|
+
complete: "=",
|
|
76
|
+
incomplete: "-",
|
|
77
|
+
});
|
|
78
|
+
}
|
|
62
79
|
for (const tableName of tableNames) {
|
|
63
80
|
const tableRecords = tables[tableName];
|
|
64
81
|
const tablePath = path.join(sourceDirectory, tableName);
|
|
@@ -102,9 +119,10 @@ async function processManifestForScope(manifest, sourceDirectory, forceWrite = f
|
|
|
102
119
|
Logger_1.logger.error("Failed to write metadata: " + metadataFilePath);
|
|
103
120
|
}
|
|
104
121
|
}
|
|
122
|
+
if (progBar)
|
|
123
|
+
progBar.tick({ scope: scopeLabel });
|
|
105
124
|
}
|
|
106
125
|
}
|
|
107
|
-
Logger_1.logger.info("Files written to " + sourceDirectory);
|
|
108
126
|
}
|
|
109
127
|
catch (error) {
|
|
110
128
|
Logger_1.logger.error("Error processing files for " + sourceDirectory + ": " + error);
|
package/dist/appUtils.js
CHANGED
|
@@ -89,7 +89,7 @@ const processFilesInManRec = async (recPath, rec, forceWrite) => {
|
|
|
89
89
|
return fileCopy;
|
|
90
90
|
});
|
|
91
91
|
};
|
|
92
|
-
const processRecsInManTable = async (tablePath, table, forceWrite) => {
|
|
92
|
+
const processRecsInManTable = async (tablePath, table, forceWrite, onRecordProcessed) => {
|
|
93
93
|
const { records } = table;
|
|
94
94
|
const recKeys = Object.keys(records);
|
|
95
95
|
const recKeyToPath = (key) => path_1.default.join(tablePath, records[key].name);
|
|
@@ -100,23 +100,36 @@ const processRecsInManTable = async (tablePath, table, forceWrite) => {
|
|
|
100
100
|
const filePromises = recKeys.reduce((acc, recKey) => {
|
|
101
101
|
return [
|
|
102
102
|
...acc,
|
|
103
|
-
processFilesInManRec(recKeyToPath(recKey), records[recKey], forceWrite)
|
|
103
|
+
processFilesInManRec(recKeyToPath(recKey), records[recKey], forceWrite).then(function () {
|
|
104
|
+
if (onRecordProcessed)
|
|
105
|
+
onRecordProcessed();
|
|
106
|
+
}),
|
|
104
107
|
];
|
|
105
108
|
}, []);
|
|
106
109
|
return Promise.all(filePromises);
|
|
107
110
|
};
|
|
108
|
-
const
|
|
111
|
+
const countRecordsInTables = (tables) => {
|
|
112
|
+
return Object.keys(tables).reduce(function (sum, tableName) {
|
|
113
|
+
return sum + Object.keys(tables[tableName].records).length;
|
|
114
|
+
}, 0);
|
|
115
|
+
};
|
|
116
|
+
const processTablesInManifest = async (tables, forceWrite, sourcePath, onRecordProcessed) => {
|
|
109
117
|
var basePath = sourcePath || ConfigManager.getSourcePath();
|
|
110
118
|
const tableNames = Object.keys(tables);
|
|
111
119
|
const tablePromises = tableNames.map((tableName) => {
|
|
112
|
-
return processRecsInManTable(path_1.default.join(basePath, tableName), tables[tableName], forceWrite);
|
|
120
|
+
return processRecsInManTable(path_1.default.join(basePath, tableName), tables[tableName], forceWrite, onRecordProcessed);
|
|
113
121
|
});
|
|
114
122
|
await Promise.all(tablePromises);
|
|
115
123
|
};
|
|
116
124
|
const processManifest = async (manifest, forceWrite = false, sourcePath) => {
|
|
117
125
|
const tableCount = Object.keys(manifest.tables).length;
|
|
118
126
|
FileLogger_1.fileLogger.debug("Processing manifest: " + (manifest.scope || "legacy") + " (" + tableCount + " tables)");
|
|
119
|
-
|
|
127
|
+
var recordCount = countRecordsInTables(manifest.tables);
|
|
128
|
+
var progress = createScopeProgress(Logger_1.logger.getLogLevel(), {
|
|
129
|
+
scope: manifest.scope || "default",
|
|
130
|
+
total: recordCount,
|
|
131
|
+
});
|
|
132
|
+
await processTablesInManifest(manifest.tables, forceWrite, sourcePath, progress.tick);
|
|
120
133
|
if (manifest.scope) {
|
|
121
134
|
await fUtils.writeScopeManifest(manifest.scope, manifest);
|
|
122
135
|
}
|
|
@@ -245,13 +258,18 @@ const processMissingFiles = async (newManifest, sourcePath) => {
|
|
|
245
258
|
try {
|
|
246
259
|
const missing = await (0, exports.findMissingFiles)(newManifest, sourcePath);
|
|
247
260
|
const missingTableCount = Object.keys(missing).length;
|
|
248
|
-
if (missingTableCount
|
|
249
|
-
|
|
250
|
-
|
|
261
|
+
if (missingTableCount === 0)
|
|
262
|
+
return;
|
|
263
|
+
FileLogger_1.fileLogger.debug("Downloading missing files from " + missingTableCount + " tables");
|
|
251
264
|
const { tableOptions = {} } = ConfigManager.getConfig();
|
|
252
265
|
const client = (0, snClient_1.defaultClient)();
|
|
253
266
|
const filesToProcess = await (0, snClient_1.unwrapSNResponse)(client.getMissingFiles(missing, tableOptions));
|
|
254
|
-
|
|
267
|
+
var recordCount = countRecordsInTables(filesToProcess);
|
|
268
|
+
var progress = createScopeProgress(Logger_1.logger.getLogLevel(), {
|
|
269
|
+
scope: newManifest.scope || "default",
|
|
270
|
+
total: recordCount,
|
|
271
|
+
});
|
|
272
|
+
await processTablesInManifest(filesToProcess, false, sourcePath, progress.tick);
|
|
255
273
|
}
|
|
256
274
|
catch (e) {
|
|
257
275
|
throw e;
|
|
@@ -375,6 +393,25 @@ const pushFiles = async (recs) => {
|
|
|
375
393
|
exports.pushFiles = pushFiles;
|
|
376
394
|
const summarizeRecord = (table, recDescriptor) => `${table} > ${recDescriptor}`;
|
|
377
395
|
exports.summarizeRecord = summarizeRecord;
|
|
396
|
+
const createScopeProgress = (logLevel, options) => {
|
|
397
|
+
if (logLevel !== "info" || options.total === 0) {
|
|
398
|
+
return { tick: function () { }, setTotal: function () { } };
|
|
399
|
+
}
|
|
400
|
+
var progBar = new progress_1.default(":scope :bar :current/:total (:percent)", {
|
|
401
|
+
total: options.total,
|
|
402
|
+
width: 40,
|
|
403
|
+
complete: "=",
|
|
404
|
+
incomplete: "-",
|
|
405
|
+
});
|
|
406
|
+
return {
|
|
407
|
+
tick: function () {
|
|
408
|
+
progBar.tick({ scope: options.scope });
|
|
409
|
+
},
|
|
410
|
+
setTotal: function (n) {
|
|
411
|
+
progBar.total = n;
|
|
412
|
+
},
|
|
413
|
+
};
|
|
414
|
+
};
|
|
378
415
|
const getProgTick = (logLevel, total) => {
|
|
379
416
|
if (logLevel === "info") {
|
|
380
417
|
const progBar = new progress_1.default(":bar (:percent)", {
|
package/dist/clickupCommands.js
CHANGED
|
@@ -1,37 +1,4 @@
|
|
|
1
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
2
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
4
|
};
|
|
@@ -51,8 +18,7 @@ const inquirer_1 = __importDefault(require("inquirer"));
|
|
|
51
18
|
const chalk_1 = __importDefault(require("chalk"));
|
|
52
19
|
const Logger_1 = require("./Logger");
|
|
53
20
|
const commands_1 = require("./commands");
|
|
54
|
-
const
|
|
55
|
-
const path = __importStar(require("path"));
|
|
21
|
+
const FileUtils_1 = require("./FileUtils");
|
|
56
22
|
// --- Token & API Helpers ---
|
|
57
23
|
function getClickUpToken() {
|
|
58
24
|
var token = process.env.CLICKUP_API_TOKEN;
|
|
@@ -337,7 +303,7 @@ async function clickupSetupCommand(args) {
|
|
|
337
303
|
Logger_1.logger.info(" Workspace: " + teams[i].name + " (" + memberCount + " members)");
|
|
338
304
|
}
|
|
339
305
|
// Write token to .env file
|
|
340
|
-
|
|
306
|
+
(0, FileUtils_1.writeEnvVar)({ key: "CLICKUP_API_TOKEN", value: token });
|
|
341
307
|
Logger_1.logger.info("");
|
|
342
308
|
Logger_1.logger.success(chalk_1.default.green("✓ ClickUp configured! Token saved to .env"));
|
|
343
309
|
Logger_1.logger.info("");
|
|
@@ -504,26 +470,3 @@ function refineUpdateSetName(params) {
|
|
|
504
470
|
.trim()
|
|
505
471
|
.substring(0, 80);
|
|
506
472
|
}
|
|
507
|
-
// --- Env File Helper ---
|
|
508
|
-
async function writeEnvVar(key, value) {
|
|
509
|
-
var envPath = path.resolve(process.cwd(), ".env");
|
|
510
|
-
var content = "";
|
|
511
|
-
try {
|
|
512
|
-
content = fs.readFileSync(envPath, "utf8");
|
|
513
|
-
}
|
|
514
|
-
catch (e) {
|
|
515
|
-
// File doesn't exist yet — will create
|
|
516
|
-
}
|
|
517
|
-
var regex = new RegExp("^" + key + "=.*$", "m");
|
|
518
|
-
var line = key + "=" + value;
|
|
519
|
-
if (regex.test(content)) {
|
|
520
|
-
content = content.replace(regex, line);
|
|
521
|
-
}
|
|
522
|
-
else {
|
|
523
|
-
if (content.length > 0 && content.charAt(content.length - 1) !== "\n") {
|
|
524
|
-
content = content + "\n";
|
|
525
|
-
}
|
|
526
|
-
content = content + line + "\n";
|
|
527
|
-
}
|
|
528
|
-
fs.writeFileSync(envPath, content, "utf8");
|
|
529
|
-
}
|
package/dist/commander.js
CHANGED
|
@@ -13,6 +13,7 @@ const claudeCommand_1 = require("./claudeCommand");
|
|
|
13
13
|
const createRecordCommand_1 = require("./createRecordCommand");
|
|
14
14
|
const deleteRecordCommand_1 = require("./deleteRecordCommand");
|
|
15
15
|
const clickupCommands_1 = require("./clickupCommands");
|
|
16
|
+
const loginCommand_1 = require("./loginCommand");
|
|
16
17
|
const yargs_1 = __importDefault(require("yargs"));
|
|
17
18
|
async function initCommands() {
|
|
18
19
|
const sharedOptions = {
|
|
@@ -74,7 +75,34 @@ async function initCommands() {
|
|
|
74
75
|
.command("download <scope>", "Downloads a scoped application's files from ServiceNow. Must specify a scope prefix for a scoped app.", sharedOptions, async (args) => {
|
|
75
76
|
await (0, commands_1.downloadCommand)(args);
|
|
76
77
|
})
|
|
77
|
-
.command("init", "
|
|
78
|
+
.command("init", "Set up Sincronia — discovers installed packages, authenticates, and configures your project", sharedOptions, commands_1.initCommand)
|
|
79
|
+
.command("login [plugin]", "Authenticate with ServiceNow and other integrations", function (cmdArgs) {
|
|
80
|
+
cmdArgs.positional("plugin", {
|
|
81
|
+
describe: "Plugin name to login (e.g., clickup). Omit for ServiceNow only.",
|
|
82
|
+
type: "string",
|
|
83
|
+
});
|
|
84
|
+
cmdArgs.options({
|
|
85
|
+
...sharedOptions,
|
|
86
|
+
all: {
|
|
87
|
+
type: "boolean",
|
|
88
|
+
default: false,
|
|
89
|
+
describe: "Login to all detected integrations",
|
|
90
|
+
},
|
|
91
|
+
instance: {
|
|
92
|
+
type: "string",
|
|
93
|
+
describe: "ServiceNow instance URL (non-interactive)",
|
|
94
|
+
},
|
|
95
|
+
user: {
|
|
96
|
+
type: "string",
|
|
97
|
+
describe: "ServiceNow username (non-interactive)",
|
|
98
|
+
},
|
|
99
|
+
password: {
|
|
100
|
+
type: "string",
|
|
101
|
+
describe: "ServiceNow password (non-interactive)",
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
return cmdArgs;
|
|
105
|
+
}, loginCommand_1.loginCommand)
|
|
78
106
|
.command("initScopes", "Provisions an initial project for the scopes defined in the config", {
|
|
79
107
|
...sharedOptions,
|
|
80
108
|
delay: {
|
package/dist/commands.js
CHANGED
|
@@ -48,7 +48,7 @@ exports.statusCommand = statusCommand;
|
|
|
48
48
|
const ConfigManager = __importStar(require("./config"));
|
|
49
49
|
const Watcher_1 = require("./Watcher");
|
|
50
50
|
const AppUtils = __importStar(require("./appUtils"));
|
|
51
|
-
const
|
|
51
|
+
const orchestrator_1 = require("./initSystem/orchestrator");
|
|
52
52
|
const Logger_1 = require("./Logger");
|
|
53
53
|
const FileLogger_1 = require("./FileLogger");
|
|
54
54
|
const logMessages_1 = require("./logMessages");
|
|
@@ -233,7 +233,6 @@ async function downloadCommand(args) {
|
|
|
233
233
|
});
|
|
234
234
|
Logger_1.logger.info("Received " + tableCount + " tables, " + recordCount + " records");
|
|
235
235
|
FileLogger_1.fileLogger.debug("Download manifest: " + tableCount + " tables, " + recordCount + " records");
|
|
236
|
-
Logger_1.logger.info("Writing files to disk...");
|
|
237
236
|
await AppUtils.processManifest(man, true);
|
|
238
237
|
Logger_1.logger.success("Download complete — " + recordCount + " records written");
|
|
239
238
|
}
|
|
@@ -244,7 +243,7 @@ async function downloadCommand(args) {
|
|
|
244
243
|
async function initCommand(args) {
|
|
245
244
|
setLogLevel(args);
|
|
246
245
|
try {
|
|
247
|
-
await (0,
|
|
246
|
+
await (0, orchestrator_1.runInit)({ logLevel: args.logLevel });
|
|
248
247
|
}
|
|
249
248
|
catch (e) {
|
|
250
249
|
throw e;
|
|
@@ -0,0 +1,228 @@
|
|
|
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.corePlugin = void 0;
|
|
40
|
+
exports.validateCoreLogin = validateCoreLogin;
|
|
41
|
+
exports.normalizeInstance = normalizeInstance;
|
|
42
|
+
const snClient_1 = require("../snClient");
|
|
43
|
+
const Logger_1 = require("../Logger");
|
|
44
|
+
const ConfigManager = __importStar(require("../config"));
|
|
45
|
+
const AppUtils = __importStar(require("../appUtils"));
|
|
46
|
+
const inquirer_1 = __importDefault(require("inquirer"));
|
|
47
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
48
|
+
const fs_1 = __importDefault(require("fs"));
|
|
49
|
+
const path_1 = __importDefault(require("path"));
|
|
50
|
+
/**
|
|
51
|
+
* @description Core init plugin — handles ServiceNow authentication, app selection, and file download.
|
|
52
|
+
* This plugin is always included in sinc init and sinc login.
|
|
53
|
+
*/
|
|
54
|
+
exports.corePlugin = {
|
|
55
|
+
name: "core",
|
|
56
|
+
displayName: "ServiceNow",
|
|
57
|
+
description: "Connect to a ServiceNow instance and sync application files",
|
|
58
|
+
login: [
|
|
59
|
+
{
|
|
60
|
+
envKey: "SN_INSTANCE",
|
|
61
|
+
prompt: {
|
|
62
|
+
type: "input",
|
|
63
|
+
message: "ServiceNow instance (e.g. mycompany.service-now.com):",
|
|
64
|
+
},
|
|
65
|
+
required: true,
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
envKey: "SN_USER",
|
|
69
|
+
prompt: {
|
|
70
|
+
type: "input",
|
|
71
|
+
message: "Username:",
|
|
72
|
+
},
|
|
73
|
+
required: true,
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
envKey: "SN_PASSWORD",
|
|
77
|
+
prompt: {
|
|
78
|
+
type: "password",
|
|
79
|
+
message: "Password:",
|
|
80
|
+
mask: "*",
|
|
81
|
+
},
|
|
82
|
+
required: true,
|
|
83
|
+
},
|
|
84
|
+
],
|
|
85
|
+
configure: [
|
|
86
|
+
{
|
|
87
|
+
key: "app",
|
|
88
|
+
label: "Selecting ServiceNow application",
|
|
89
|
+
run: async (context) => {
|
|
90
|
+
const baseUrl = instanceBaseUrl(context.env.SN_INSTANCE);
|
|
91
|
+
const client = (0, snClient_1.snClient)(baseUrl, context.env.SN_USER, context.env.SN_PASSWORD);
|
|
92
|
+
Logger_1.logger.info("Fetching application list...");
|
|
93
|
+
const apps = await (0, snClient_1.unwrapSNResponse)(client.getAppList());
|
|
94
|
+
if (apps.length === 0) {
|
|
95
|
+
Logger_1.logger.warn("No applications found on this instance.");
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
const choices = apps.map((app) => ({
|
|
99
|
+
name: app.displayName + " (" + app.scope + ")",
|
|
100
|
+
value: app.scope,
|
|
101
|
+
short: app.displayName,
|
|
102
|
+
}));
|
|
103
|
+
const answer = await inquirer_1.default.prompt([{
|
|
104
|
+
type: "list",
|
|
105
|
+
name: "app",
|
|
106
|
+
message: "Which app would you like to work with?",
|
|
107
|
+
choices,
|
|
108
|
+
}]);
|
|
109
|
+
context.answers.selectedScope = answer.app;
|
|
110
|
+
context.answers.apps = apps;
|
|
111
|
+
return answer.app;
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
],
|
|
115
|
+
initialize: async (context) => {
|
|
116
|
+
const scope = context.answers.selectedScope;
|
|
117
|
+
if (!scope) {
|
|
118
|
+
Logger_1.logger.warn("No application selected — skipping initialization.");
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
const rootDir = context.rootDir;
|
|
122
|
+
const configPath = path_1.default.join(rootDir, "sinc.config.js");
|
|
123
|
+
// Write or merge sinc.config.js
|
|
124
|
+
let hasExistingConfig = false;
|
|
125
|
+
try {
|
|
126
|
+
fs_1.default.accessSync(configPath, fs_1.default.constants.F_OK);
|
|
127
|
+
hasExistingConfig = true;
|
|
128
|
+
}
|
|
129
|
+
catch (e) {
|
|
130
|
+
// No existing config
|
|
131
|
+
}
|
|
132
|
+
if (!hasExistingConfig) {
|
|
133
|
+
Logger_1.logger.info("Generating sinc.config.js...");
|
|
134
|
+
fs_1.default.writeFileSync(configPath, ConfigManager.getDefaultConfigFile(), "utf8");
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
Logger_1.logger.info("sinc.config.js already exists — preserving configuration.");
|
|
138
|
+
}
|
|
139
|
+
// Reload configs so ConfigManager picks up the new/existing config
|
|
140
|
+
try {
|
|
141
|
+
await ConfigManager.loadConfigs();
|
|
142
|
+
}
|
|
143
|
+
catch (e) {
|
|
144
|
+
Logger_1.logger.warn("Config reload incomplete — this is expected during first-time init.");
|
|
145
|
+
}
|
|
146
|
+
// Check if manifest already exists for this scope
|
|
147
|
+
const manifestPath = path_1.default.join(rootDir, "sinc.manifest." + scope + ".json");
|
|
148
|
+
let hasManifest = false;
|
|
149
|
+
try {
|
|
150
|
+
fs_1.default.accessSync(manifestPath, fs_1.default.constants.F_OK);
|
|
151
|
+
hasManifest = true;
|
|
152
|
+
}
|
|
153
|
+
catch (e) {
|
|
154
|
+
// No existing manifest
|
|
155
|
+
}
|
|
156
|
+
if (hasManifest) {
|
|
157
|
+
const redownload = await inquirer_1.default.prompt([{
|
|
158
|
+
type: "confirm",
|
|
159
|
+
name: "confirmed",
|
|
160
|
+
message: "Manifest for " + scope + " already exists. Re-download?",
|
|
161
|
+
default: false,
|
|
162
|
+
}]);
|
|
163
|
+
if (!redownload.confirmed) {
|
|
164
|
+
Logger_1.logger.info("Skipping download for " + scope);
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
// Download application files — errors propagate to orchestrator
|
|
169
|
+
Logger_1.logger.info("Downloading " + scope + "...");
|
|
170
|
+
const baseUrl = instanceBaseUrl(context.env.SN_INSTANCE);
|
|
171
|
+
const client = (0, snClient_1.snClient)(baseUrl, context.env.SN_USER, context.env.SN_PASSWORD);
|
|
172
|
+
const config = ConfigManager.getConfig();
|
|
173
|
+
const man = await (0, snClient_1.unwrapSNResponse)(client.getManifest(scope, config, true));
|
|
174
|
+
await AppUtils.processManifest(man);
|
|
175
|
+
const tableNames = Object.keys(man.tables || {});
|
|
176
|
+
const recordCount = tableNames.reduce((sum, t) => {
|
|
177
|
+
return sum + Object.keys(man.tables[t].records || {}).length;
|
|
178
|
+
}, 0);
|
|
179
|
+
Logger_1.logger.success(chalk_1.default.green("✓ ServiceNow configured — " + tableNames.length + " tables, " + recordCount + " records"));
|
|
180
|
+
},
|
|
181
|
+
};
|
|
182
|
+
/**
|
|
183
|
+
* @description Validates ServiceNow credentials by testing the connection.
|
|
184
|
+
* Called by the orchestrator after all core login hooks are collected.
|
|
185
|
+
*/
|
|
186
|
+
async function validateCoreLogin(context) {
|
|
187
|
+
const instance = context.env.SN_INSTANCE;
|
|
188
|
+
const user = context.env.SN_USER;
|
|
189
|
+
const password = context.env.SN_PASSWORD;
|
|
190
|
+
if (!instance || !user || !password) {
|
|
191
|
+
return "Missing required credentials";
|
|
192
|
+
}
|
|
193
|
+
const instanceUrl = normalizeInstance(instance);
|
|
194
|
+
const baseUrl = instanceBaseUrl(instance);
|
|
195
|
+
try {
|
|
196
|
+
const client = (0, snClient_1.snClient)(baseUrl, user, password);
|
|
197
|
+
await (0, snClient_1.unwrapSNResponse)(client.getAppList());
|
|
198
|
+
context.env.SN_INSTANCE = instanceUrl;
|
|
199
|
+
return true;
|
|
200
|
+
}
|
|
201
|
+
catch (e) {
|
|
202
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
203
|
+
const status = e && e.response && e.response.status;
|
|
204
|
+
if (msg.includes("Invalid URL") || msg.includes("ENOTFOUND") || msg.includes("getaddrinfo")) {
|
|
205
|
+
return "Instance not found — check the URL (got: " + instanceUrl + ")";
|
|
206
|
+
}
|
|
207
|
+
if (status === 401 || msg.includes("401")) {
|
|
208
|
+
return "Invalid username or password.";
|
|
209
|
+
}
|
|
210
|
+
if (status === 403 || msg.includes("403")) {
|
|
211
|
+
return "Access denied — user may lack required roles.";
|
|
212
|
+
}
|
|
213
|
+
if (msg.includes("ECONNREFUSED") || msg.includes("ETIMEDOUT") || msg.includes("ECONNRESET")) {
|
|
214
|
+
return "Could not reach " + instanceUrl + " — check network connectivity.";
|
|
215
|
+
}
|
|
216
|
+
return "Connection failed: " + msg;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
function normalizeInstance(instance) {
|
|
220
|
+
let url = instance.trim().replace("https://", "").replace("http://", "");
|
|
221
|
+
if (url.endsWith("/")) {
|
|
222
|
+
url = url.slice(0, -1);
|
|
223
|
+
}
|
|
224
|
+
return url;
|
|
225
|
+
}
|
|
226
|
+
function instanceBaseUrl(instance) {
|
|
227
|
+
return "https://" + normalizeInstance(instance) + "/";
|
|
228
|
+
}
|