@tenonhq/sincronia-core 0.0.69 → 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.
|
@@ -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/commands.js
CHANGED
|
@@ -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
|
}
|
|
@@ -87,8 +87,8 @@ exports.corePlugin = {
|
|
|
87
87
|
key: "app",
|
|
88
88
|
label: "Selecting ServiceNow application",
|
|
89
89
|
run: async (context) => {
|
|
90
|
-
const
|
|
91
|
-
const client = (0, snClient_1.snClient)(
|
|
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
92
|
Logger_1.logger.info("Fetching application list...");
|
|
93
93
|
const apps = await (0, snClient_1.unwrapSNResponse)(client.getAppList());
|
|
94
94
|
if (apps.length === 0) {
|
|
@@ -167,8 +167,8 @@ exports.corePlugin = {
|
|
|
167
167
|
}
|
|
168
168
|
// Download application files — errors propagate to orchestrator
|
|
169
169
|
Logger_1.logger.info("Downloading " + scope + "...");
|
|
170
|
-
const
|
|
171
|
-
const client = (0, snClient_1.snClient)(
|
|
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
172
|
const config = ConfigManager.getConfig();
|
|
173
173
|
const man = await (0, snClient_1.unwrapSNResponse)(client.getManifest(scope, config, true));
|
|
174
174
|
await AppUtils.processManifest(man);
|
|
@@ -191,20 +191,38 @@ async function validateCoreLogin(context) {
|
|
|
191
191
|
return "Missing required credentials";
|
|
192
192
|
}
|
|
193
193
|
const instanceUrl = normalizeInstance(instance);
|
|
194
|
+
const baseUrl = instanceBaseUrl(instance);
|
|
194
195
|
try {
|
|
195
|
-
const client = (0, snClient_1.snClient)(
|
|
196
|
+
const client = (0, snClient_1.snClient)(baseUrl, user, password);
|
|
196
197
|
await (0, snClient_1.unwrapSNResponse)(client.getAppList());
|
|
197
198
|
context.env.SN_INSTANCE = instanceUrl;
|
|
198
199
|
return true;
|
|
199
200
|
}
|
|
200
201
|
catch (e) {
|
|
201
|
-
|
|
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;
|
|
202
217
|
}
|
|
203
218
|
}
|
|
204
219
|
function normalizeInstance(instance) {
|
|
205
220
|
let url = instance.trim().replace("https://", "").replace("http://", "");
|
|
206
|
-
if (
|
|
207
|
-
url
|
|
221
|
+
if (url.endsWith("/")) {
|
|
222
|
+
url = url.slice(0, -1);
|
|
208
223
|
}
|
|
209
224
|
return url;
|
|
210
225
|
}
|
|
226
|
+
function instanceBaseUrl(instance) {
|
|
227
|
+
return "https://" + normalizeInstance(instance) + "/";
|
|
228
|
+
}
|
|
@@ -35,10 +35,7 @@ function buildInitContext(plugins) {
|
|
|
35
35
|
// No .env yet — starting fresh
|
|
36
36
|
}
|
|
37
37
|
// Pull from process.env for keys declared by plugins but missing from .env
|
|
38
|
-
const pluginEnvKeys = plugins.flatMap(p => [
|
|
39
|
-
...(p.login || []).map(h => h.envKey),
|
|
40
|
-
...(p.configure || []).map(h => h.key),
|
|
41
|
-
]);
|
|
38
|
+
const pluginEnvKeys = plugins.flatMap(p => (p.login || []).map(h => h.envKey));
|
|
42
39
|
pluginEnvKeys.forEach(key => {
|
|
43
40
|
if (process.env[key] && !env[key]) {
|
|
44
41
|
env[key] = process.env[key];
|
|
@@ -76,10 +73,7 @@ async function promptPluginSelection(externalPlugins) {
|
|
|
76
73
|
const selected = externalPlugins.filter(p => selectedNames.has(p.name));
|
|
77
74
|
return [corePlugin_1.corePlugin, ...selected];
|
|
78
75
|
}
|
|
79
|
-
async function
|
|
80
|
-
const hooks = plugin.login;
|
|
81
|
-
if (!hooks || hooks.length === 0)
|
|
82
|
-
return;
|
|
76
|
+
async function collectLoginHooks(hooks, context) {
|
|
83
77
|
for (const hook of hooks) {
|
|
84
78
|
const existingValue = context.env[hook.envKey] || "";
|
|
85
79
|
// Show instructions if provided
|
|
@@ -112,7 +106,63 @@ async function runLoginPhase(plugin, context) {
|
|
|
112
106
|
const answer = await inquirer_1.default.prompt([promptConfig]);
|
|
113
107
|
context.env[hook.envKey] = answer.value.trim();
|
|
114
108
|
}
|
|
115
|
-
|
|
109
|
+
}
|
|
110
|
+
async function runLoginPhase(plugin, context) {
|
|
111
|
+
const hooks = plugin.login;
|
|
112
|
+
if (!hooks || hooks.length === 0)
|
|
113
|
+
return;
|
|
114
|
+
// Core plugin: retry loop with specific error messages
|
|
115
|
+
if (plugin.name === "core") {
|
|
116
|
+
while (true) {
|
|
117
|
+
await collectLoginHooks(hooks, context);
|
|
118
|
+
// Run per-hook validation
|
|
119
|
+
let hookFailed = false;
|
|
120
|
+
for (const hook of hooks) {
|
|
121
|
+
if (hook.validate) {
|
|
122
|
+
const result = await hook.validate(context.env[hook.envKey], context);
|
|
123
|
+
if (result !== true) {
|
|
124
|
+
Logger_1.logger.error(chalk_1.default.red("✗ " + result));
|
|
125
|
+
hookFailed = true;
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
if (hookFailed) {
|
|
131
|
+
const retry = await inquirer_1.default.prompt([{
|
|
132
|
+
type: "confirm",
|
|
133
|
+
name: "again",
|
|
134
|
+
message: "Try again?",
|
|
135
|
+
default: true,
|
|
136
|
+
}]);
|
|
137
|
+
if (!retry.again) {
|
|
138
|
+
throw new Error("Login cancelled");
|
|
139
|
+
}
|
|
140
|
+
context.env.SN_PASSWORD = "";
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
Logger_1.logger.info("Validating credentials...");
|
|
144
|
+
const coreResult = await (0, corePlugin_1.validateCoreLogin)(context);
|
|
145
|
+
if (coreResult === true) {
|
|
146
|
+
Logger_1.logger.success(chalk_1.default.green("✓ Connected to " + context.env.SN_INSTANCE));
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
Logger_1.logger.error(chalk_1.default.red("✗ " + coreResult));
|
|
150
|
+
Logger_1.logger.info("");
|
|
151
|
+
const retry = await inquirer_1.default.prompt([{
|
|
152
|
+
type: "confirm",
|
|
153
|
+
name: "again",
|
|
154
|
+
message: "Try again?",
|
|
155
|
+
default: true,
|
|
156
|
+
}]);
|
|
157
|
+
if (!retry.again) {
|
|
158
|
+
throw new Error("Login cancelled");
|
|
159
|
+
}
|
|
160
|
+
// Clear password so it re-prompts; instance and user show as defaults
|
|
161
|
+
context.env.SN_PASSWORD = "";
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
// Non-core plugins: original behavior (no retry loop)
|
|
165
|
+
await collectLoginHooks(hooks, context);
|
|
116
166
|
for (const hook of hooks) {
|
|
117
167
|
if (hook.validate) {
|
|
118
168
|
const result = await hook.validate(context.env[hook.envKey], context);
|
|
@@ -122,16 +172,6 @@ async function runLoginPhase(plugin, context) {
|
|
|
122
172
|
}
|
|
123
173
|
}
|
|
124
174
|
}
|
|
125
|
-
// Core plugin gets special post-login validation (all 3 credentials at once)
|
|
126
|
-
if (plugin.name === "core") {
|
|
127
|
-
Logger_1.logger.info("Validating credentials...");
|
|
128
|
-
const coreResult = await (0, corePlugin_1.validateCoreLogin)(context);
|
|
129
|
-
if (coreResult !== true) {
|
|
130
|
-
Logger_1.logger.error(chalk_1.default.red("✗ " + coreResult));
|
|
131
|
-
throw new Error("ServiceNow login failed");
|
|
132
|
-
}
|
|
133
|
-
Logger_1.logger.success(chalk_1.default.green("✓ Connected to " + context.env.SN_INSTANCE));
|
|
134
|
-
}
|
|
135
175
|
}
|
|
136
176
|
async function runConfigurePhase(plugin, context) {
|
|
137
177
|
const hooks = plugin.configure;
|
|
@@ -3,21 +3,21 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
const corePlugin_1 = require("../initSystem/corePlugin");
|
|
4
4
|
describe("normalizeInstance", function () {
|
|
5
5
|
it("strips https:// prefix", function () {
|
|
6
|
-
expect((0, corePlugin_1.normalizeInstance)("https://mycompany.service-now.com")).toBe("mycompany.service-now.com
|
|
6
|
+
expect((0, corePlugin_1.normalizeInstance)("https://mycompany.service-now.com")).toBe("mycompany.service-now.com");
|
|
7
7
|
});
|
|
8
8
|
it("strips http:// prefix", function () {
|
|
9
|
-
expect((0, corePlugin_1.normalizeInstance)("http://mycompany.service-now.com")).toBe("mycompany.service-now.com
|
|
9
|
+
expect((0, corePlugin_1.normalizeInstance)("http://mycompany.service-now.com")).toBe("mycompany.service-now.com");
|
|
10
10
|
});
|
|
11
|
-
it("
|
|
12
|
-
expect((0, corePlugin_1.normalizeInstance)("mycompany.service-now.com")).toBe("mycompany.service-now.com
|
|
11
|
+
it("returns bare hostname without trailing slash", function () {
|
|
12
|
+
expect((0, corePlugin_1.normalizeInstance)("mycompany.service-now.com")).toBe("mycompany.service-now.com");
|
|
13
13
|
});
|
|
14
|
-
it("
|
|
15
|
-
expect((0, corePlugin_1.normalizeInstance)("mycompany.service-now.com/")).toBe("mycompany.service-now.com
|
|
14
|
+
it("strips trailing slash if present", function () {
|
|
15
|
+
expect((0, corePlugin_1.normalizeInstance)("mycompany.service-now.com/")).toBe("mycompany.service-now.com");
|
|
16
16
|
});
|
|
17
17
|
it("trims whitespace", function () {
|
|
18
|
-
expect((0, corePlugin_1.normalizeInstance)(" mycompany.service-now.com ")).toBe("mycompany.service-now.com
|
|
18
|
+
expect((0, corePlugin_1.normalizeInstance)(" mycompany.service-now.com ")).toBe("mycompany.service-now.com");
|
|
19
19
|
});
|
|
20
20
|
it("handles full URL with protocol and trailing slash", function () {
|
|
21
|
-
expect((0, corePlugin_1.normalizeInstance)("https://mycompany.service-now.com/")).toBe("mycompany.service-now.com
|
|
21
|
+
expect((0, corePlugin_1.normalizeInstance)("https://mycompany.service-now.com/")).toBe("mycompany.service-now.com");
|
|
22
22
|
});
|
|
23
23
|
});
|