@tenonhq/sincronia-core 0.0.72 → 0.0.73

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
- Logger_1.logger.info("Processing " + tableNames.length + " tables for " + sourceDirectory);
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);
@@ -340,7 +358,7 @@ async function watchAllScopesCommand(args) {
340
358
  }
341
359
  // Start dashboard unless --no-dashboard flag is set
342
360
  if (!args.noDashboard) {
343
- dashboardProcess = startDashboardProcess();
361
+ dashboardProcess = startDashboardProcess(args.port);
344
362
  }
345
363
  // Import and start the multi-scope watcher
346
364
  const { startMultiScopeWatching } = await Promise.resolve().then(() => __importStar(require("./MultiScopeWatcher")));
@@ -365,7 +383,7 @@ async function watchAllScopesCommand(args) {
365
383
  throw error;
366
384
  }
367
385
  }
368
- function startDashboardProcess() {
386
+ function startDashboardProcess(portOverride) {
369
387
  var serverPath;
370
388
  try {
371
389
  serverPath = require.resolve("@tenonhq/sincronia-dashboard/server.js");
@@ -374,11 +392,11 @@ function startDashboardProcess() {
374
392
  Logger_1.logger.warn("Dashboard package not installed. Run: npm install @tenonhq/sincronia-dashboard");
375
393
  return null;
376
394
  }
377
- var port = process.env.DASHBOARD_PORT || "3456";
395
+ var port = portOverride ? String(portOverride) : (process.env.DASHBOARD_PORT || "3456");
378
396
  var server = (0, child_process_1.spawn)("node", [serverPath], {
379
397
  cwd: process.cwd(),
380
398
  stdio: "ignore",
381
- env: { ...process.env },
399
+ env: { ...process.env, DASHBOARD_PORT: port },
382
400
  detached: false,
383
401
  });
384
402
  server.on("error", function (err) {
package/dist/appUtils.js CHANGED
@@ -72,10 +72,7 @@ const processFilesInManRec = async (recPath, rec, forceWrite) => {
72
72
  }, null, 2)
73
73
  };
74
74
  await fileWrite(metadataFile, recPath);
75
- const regularPromises = rec.files.map((file) => {
76
- return fileWrite(file, recPath);
77
- });
78
- const writeResults = await Promise.allSettled(regularPromises);
75
+ const writeResults = await (0, genericUtils_1.allSettledBatched)(rec.files, constants_1.CONCURRENCY_FILES, function (file) { return fileWrite(file, recPath); });
79
76
  const writeFailures = writeResults.filter((r) => r.status === "rejected");
80
77
  if (writeFailures.length > 0) {
81
78
  writeFailures.forEach((f) => {
@@ -89,7 +86,7 @@ const processFilesInManRec = async (recPath, rec, forceWrite) => {
89
86
  return fileCopy;
90
87
  });
91
88
  };
92
- const processRecsInManTable = async (tablePath, table, forceWrite) => {
89
+ const processRecsInManTable = async (tablePath, table, forceWrite, onRecordProcessed) => {
93
90
  const { records } = table;
94
91
  const recKeys = Object.keys(records);
95
92
  const recKeyToPath = (key) => path_1.default.join(tablePath, records[key].name);
@@ -97,26 +94,34 @@ const processRecsInManTable = async (tablePath, table, forceWrite) => {
97
94
  .map(recKeyToPath)
98
95
  .map(fUtils.createDirRecursively);
99
96
  await Promise.all(recPathPromises);
100
- const filePromises = recKeys.reduce((acc, recKey) => {
101
- return [
102
- ...acc,
103
- processFilesInManRec(recKeyToPath(recKey), records[recKey], forceWrite),
104
- ];
105
- }, []);
106
- return Promise.all(filePromises);
97
+ await (0, genericUtils_1.processBatched)(recKeys, constants_1.CONCURRENCY_RECORDS, function (recKey) {
98
+ return processFilesInManRec(recKeyToPath(recKey), records[recKey], forceWrite).then(function () {
99
+ if (onRecordProcessed)
100
+ onRecordProcessed();
101
+ });
102
+ });
103
+ };
104
+ const countRecordsInTables = (tables) => {
105
+ return Object.keys(tables).reduce(function (sum, tableName) {
106
+ return sum + Object.keys(tables[tableName].records).length;
107
+ }, 0);
107
108
  };
108
- const processTablesInManifest = async (tables, forceWrite, sourcePath) => {
109
+ const processTablesInManifest = async (tables, forceWrite, sourcePath, onRecordProcessed) => {
109
110
  var basePath = sourcePath || ConfigManager.getSourcePath();
110
111
  const tableNames = Object.keys(tables);
111
- const tablePromises = tableNames.map((tableName) => {
112
- return processRecsInManTable(path_1.default.join(basePath, tableName), tables[tableName], forceWrite);
112
+ await (0, genericUtils_1.processBatched)(tableNames, constants_1.CONCURRENCY_TABLES, function (tableName) {
113
+ return processRecsInManTable(path_1.default.join(basePath, tableName), tables[tableName], forceWrite, onRecordProcessed);
113
114
  });
114
- await Promise.all(tablePromises);
115
115
  };
116
116
  const processManifest = async (manifest, forceWrite = false, sourcePath) => {
117
117
  const tableCount = Object.keys(manifest.tables).length;
118
118
  FileLogger_1.fileLogger.debug("Processing manifest: " + (manifest.scope || "legacy") + " (" + tableCount + " tables)");
119
- await processTablesInManifest(manifest.tables, forceWrite, sourcePath);
119
+ var recordCount = countRecordsInTables(manifest.tables);
120
+ var progress = createScopeProgress(Logger_1.logger.getLogLevel(), {
121
+ scope: manifest.scope || "default",
122
+ total: recordCount,
123
+ });
124
+ await processTablesInManifest(manifest.tables, forceWrite, sourcePath, progress.tick);
120
125
  if (manifest.scope) {
121
126
  await fUtils.writeScopeManifest(manifest.scope, manifest);
122
127
  }
@@ -245,13 +250,18 @@ const processMissingFiles = async (newManifest, sourcePath) => {
245
250
  try {
246
251
  const missing = await (0, exports.findMissingFiles)(newManifest, sourcePath);
247
252
  const missingTableCount = Object.keys(missing).length;
248
- if (missingTableCount > 0) {
249
- FileLogger_1.fileLogger.debug("Downloading missing files from " + missingTableCount + " tables");
250
- }
253
+ if (missingTableCount === 0)
254
+ return;
255
+ FileLogger_1.fileLogger.debug("Downloading missing files from " + missingTableCount + " tables");
251
256
  const { tableOptions = {} } = ConfigManager.getConfig();
252
257
  const client = (0, snClient_1.defaultClient)();
253
258
  const filesToProcess = await (0, snClient_1.unwrapSNResponse)(client.getMissingFiles(missing, tableOptions));
254
- await processTablesInManifest(filesToProcess, false, sourcePath);
259
+ var recordCount = countRecordsInTables(filesToProcess);
260
+ var progress = createScopeProgress(Logger_1.logger.getLogLevel(), {
261
+ scope: newManifest.scope || "default",
262
+ total: recordCount,
263
+ });
264
+ await processTablesInManifest(filesToProcess, false, sourcePath, progress.tick);
255
265
  }
256
266
  catch (e) {
257
267
  throw e;
@@ -349,7 +359,7 @@ const pushFiles = async (recs) => {
349
359
  Logger_1.logger.info(`Update set routing active: ${activeScopes}`);
350
360
  }
351
361
  const tick = getProgTick(Logger_1.logger.getLogLevel(), recs.length * 2) || (() => { });
352
- const pushResultPromises = recs.map(async (rec) => {
362
+ const results = await (0, genericUtils_1.allSettledBatched)(recs, constants_1.CONCURRENCY_PUSH, async function (rec) {
353
363
  const fieldNames = Object.keys(rec.fields);
354
364
  const firstField = rec.fields[fieldNames[0]];
355
365
  const recSummary = (0, exports.summarizeRecord)(rec.table, firstField.name);
@@ -364,8 +374,7 @@ const pushFiles = async (recs) => {
364
374
  tick();
365
375
  return pushRes;
366
376
  });
367
- const results = await Promise.allSettled(pushResultPromises);
368
- return results.map((result) => {
377
+ return results.map(function (result) {
369
378
  if (result.status === "fulfilled") {
370
379
  return result.value;
371
380
  }
@@ -375,6 +384,25 @@ const pushFiles = async (recs) => {
375
384
  exports.pushFiles = pushFiles;
376
385
  const summarizeRecord = (table, recDescriptor) => `${table} > ${recDescriptor}`;
377
386
  exports.summarizeRecord = summarizeRecord;
387
+ const createScopeProgress = (logLevel, options) => {
388
+ if (logLevel !== "info" || options.total === 0) {
389
+ return { tick: function () { }, setTotal: function () { } };
390
+ }
391
+ var progBar = new progress_1.default(":scope :bar :current/:total (:percent)", {
392
+ total: options.total,
393
+ width: 40,
394
+ complete: "=",
395
+ incomplete: "-",
396
+ });
397
+ return {
398
+ tick: function () {
399
+ progBar.tick({ scope: options.scope });
400
+ },
401
+ setTotal: function (n) {
402
+ progBar.total = n;
403
+ },
404
+ };
405
+ };
378
406
  const getProgTick = (logLevel, total) => {
379
407
  if (logLevel === "info") {
380
408
  const progBar = new progress_1.default(":bar (:percent)", {
@@ -394,20 +422,18 @@ const writeBuildFile = async (preBuild, buildRes, summary) => {
394
422
  const sourcePath = ConfigManager.getSourcePath();
395
423
  const buildPath = ConfigManager.getBuildPath();
396
424
  const fieldNames = Object.keys(fields);
397
- const writePromises = fieldNames.map(async (field) => {
398
- const fieldCtx = fields[field];
399
- const srcFilePath = fieldCtx.filePath;
400
- const relativePath = path_1.default.relative(sourcePath, srcFilePath);
401
- const relPathNoExt = relativePath.split(".").slice(0, -1).join();
402
- const buildExt = fUtils.getBuildExt(fieldCtx.tableName, fieldCtx.name, fieldCtx.targetField);
403
- const relPathNewExt = `${relPathNoExt}.${buildExt}`;
404
- const buildFilePath = path_1.default.join(buildPath, relPathNewExt);
405
- await fUtils.createDirRecursively(path_1.default.dirname(buildFilePath));
406
- const writeResult = await fUtils.writeFileForce(buildFilePath, buildRes.builtRec[fieldCtx.targetField]);
407
- return writeResult;
408
- });
409
425
  try {
410
- await Promise.all(writePromises);
426
+ await (0, genericUtils_1.processBatched)(fieldNames, constants_1.CONCURRENCY_FILES, async function (field) {
427
+ const fieldCtx = fields[field];
428
+ const srcFilePath = fieldCtx.filePath;
429
+ const relativePath = path_1.default.relative(sourcePath, srcFilePath);
430
+ const relPathNoExt = relativePath.split(".").slice(0, -1).join();
431
+ const buildExt = fUtils.getBuildExt(fieldCtx.tableName, fieldCtx.name, fieldCtx.targetField);
432
+ const relPathNewExt = `${relPathNoExt}.${buildExt}`;
433
+ const buildFilePath = path_1.default.join(buildPath, relPathNewExt);
434
+ await fUtils.createDirRecursively(path_1.default.dirname(buildFilePath));
435
+ await fUtils.writeFileForce(buildFilePath, buildRes.builtRec[fieldCtx.targetField]);
436
+ });
411
437
  return { success: true, message: `${recSummary} built successfully` };
412
438
  }
413
439
  catch (e) {
@@ -419,7 +445,7 @@ const writeBuildFile = async (preBuild, buildRes, summary) => {
419
445
  };
420
446
  const buildFiles = async (fileList) => {
421
447
  const tick = getProgTick(Logger_1.logger.getLogLevel(), fileList.length * 2) || (() => { });
422
- const buildPromises = fileList.map(async (rec) => {
448
+ const results = await (0, genericUtils_1.allSettledBatched)(fileList, constants_1.CONCURRENCY_BUILD, async function (rec) {
423
449
  const { fields, table } = rec;
424
450
  const fieldNames = Object.keys(fields);
425
451
  const recSummary = (0, exports.summarizeRecord)(table, fields[fieldNames[0]].name);
@@ -434,8 +460,7 @@ const buildFiles = async (fileList) => {
434
460
  tick();
435
461
  return writeRes;
436
462
  });
437
- const results = await Promise.allSettled(buildPromises);
438
- return results.map((result) => {
463
+ return results.map(function (result) {
439
464
  if (result.status === "fulfilled") {
440
465
  return result.value;
441
466
  }
package/dist/commander.js CHANGED
@@ -30,6 +30,11 @@ async function initCommands() {
30
30
  default: false,
31
31
  describe: "Skip launching the dashboard web UI",
32
32
  },
33
+ port: {
34
+ alias: "p",
35
+ type: "number",
36
+ describe: "Dashboard port (default: DASHBOARD_PORT env or 3456)",
37
+ },
33
38
  });
34
39
  return cmdArgs;
35
40
  }, async (args) => {
@@ -286,7 +291,19 @@ async function initCommands() {
286
291
  });
287
292
  return cmdArgs;
288
293
  }, updateSetCommands_1.showCurrentScopeCommand)
289
- .command("dashboard", "Launch the Update Set Dashboard web UI", sharedOptions, dashboardCommand_1.dashboardCommand)
294
+ .command("dashboard", "Launch the Update Set Dashboard web UI", (cmdArgs) => {
295
+ cmdArgs.options({
296
+ ...sharedOptions,
297
+ port: {
298
+ alias: "p",
299
+ type: "number",
300
+ describe: "Dashboard port (default: DASHBOARD_PORT env or 3456)",
301
+ },
302
+ });
303
+ return cmdArgs;
304
+ }, async (args) => {
305
+ await (0, dashboardCommand_1.dashboardCommand)(args);
306
+ })
290
307
  .command("schema <subcommand>", "Manage ServiceNow table schemas (subcommands: pull)", (cmdArgs) => {
291
308
  cmdArgs.positional("subcommand", {
292
309
  describe: "Schema subcommand to run",
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
  }
package/dist/constants.js CHANGED
@@ -3,8 +3,13 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.PUSH_RETRY_LIMIT = exports.PUSH_RETRY_WAIT = exports.PATH_DELIMITER = void 0;
6
+ exports.CONCURRENCY_BUILD = exports.CONCURRENCY_PUSH = exports.CONCURRENCY_FILES = exports.CONCURRENCY_RECORDS = exports.CONCURRENCY_TABLES = exports.PUSH_RETRY_LIMIT = exports.PUSH_RETRY_WAIT = exports.PATH_DELIMITER = void 0;
7
7
  const path_1 = __importDefault(require("path"));
8
8
  exports.PATH_DELIMITER = `${path_1.default.delimiter}${path_1.default.delimiter}`;
9
9
  exports.PUSH_RETRY_WAIT = 3000;
10
10
  exports.PUSH_RETRY_LIMIT = 3;
11
+ exports.CONCURRENCY_TABLES = 2;
12
+ exports.CONCURRENCY_RECORDS = 5;
13
+ exports.CONCURRENCY_FILES = 10;
14
+ exports.CONCURRENCY_PUSH = 3;
15
+ exports.CONCURRENCY_BUILD = 5;
@@ -13,16 +13,16 @@ async function dashboardCommand(args) {
13
13
  catch (e) {
14
14
  throw new Error("Dashboard package not installed. Run: npm install @tenonhq/sincronia-dashboard");
15
15
  }
16
+ var port = args.port ? String(args.port) : (process.env.DASHBOARD_PORT || "3456");
16
17
  Logger_1.logger.info("Starting Update Set Dashboard...");
17
18
  const server = (0, child_process_1.spawn)("node", [serverPath], {
18
19
  cwd: process.cwd(),
19
20
  stdio: "inherit",
20
- env: { ...process.env },
21
+ env: { ...process.env, DASHBOARD_PORT: port },
21
22
  });
22
23
  // Open browser after a short delay
23
24
  setTimeout(() => {
24
- const port = process.env.DASHBOARD_PORT || "3456";
25
- const url = `http://localhost:${port}`;
25
+ const url = "http://localhost:" + port;
26
26
  (0, child_process_1.spawn)("open", [url]);
27
27
  }, 1000);
28
28
  server.on("close", (code) => {
@@ -41,6 +41,8 @@ exports.parseFileNameParams = parseFileNameParams;
41
41
  exports.getParsedFilesPayload = getParsedFilesPayload;
42
42
  exports.wait = wait;
43
43
  exports.chunkArr = chunkArr;
44
+ exports.processBatched = processBatched;
45
+ exports.allSettledBatched = allSettledBatched;
44
46
  const path_1 = __importDefault(require("path"));
45
47
  const ConfigManager = __importStar(require("./config"));
46
48
  async function _getConfigFromPath(params) {
@@ -125,6 +127,48 @@ function chunkArr(arr, chunkSize) {
125
127
  }
126
128
  return chunks;
127
129
  }
130
+ async function processBatched(items, concurrency, fn) {
131
+ var results = new Array(items.length);
132
+ var index = 0;
133
+ async function runNext() {
134
+ while (index < items.length) {
135
+ var currentIndex = index;
136
+ index++;
137
+ results[currentIndex] = await fn(items[currentIndex]);
138
+ }
139
+ }
140
+ var workers = [];
141
+ var workerCount = Math.min(concurrency, items.length);
142
+ for (var i = 0; i < workerCount; i++) {
143
+ workers.push(runNext());
144
+ }
145
+ await Promise.all(workers);
146
+ return results;
147
+ }
148
+ async function allSettledBatched(items, concurrency, fn) {
149
+ var results = new Array(items.length);
150
+ var index = 0;
151
+ async function runNext() {
152
+ while (index < items.length) {
153
+ var currentIndex = index;
154
+ index++;
155
+ try {
156
+ var value = await fn(items[currentIndex]);
157
+ results[currentIndex] = { status: "fulfilled", value: value };
158
+ }
159
+ catch (e) {
160
+ results[currentIndex] = { status: "rejected", reason: e };
161
+ }
162
+ }
163
+ }
164
+ var workers = [];
165
+ var workerCount = Math.min(concurrency, items.length);
166
+ for (var i = 0; i < workerCount; i++) {
167
+ workers.push(runNext());
168
+ }
169
+ await Promise.all(workers);
170
+ return results;
171
+ }
128
172
  const allSettled = (promises) => {
129
173
  return Promise.all(promises.map((prom) => prom
130
174
  .then((value) => ({
@@ -37,7 +37,16 @@ exports.discoverPlugins = discoverPlugins;
37
37
  const fs = __importStar(require("fs"));
38
38
  const path = __importStar(require("path"));
39
39
  const Logger_1 = require("../Logger");
40
- const SKIP_PACKAGES = new Set(["sincronia-core", "sincronia-types"]);
40
+ // Skip packages that are not init plugins:
41
+ // - core/types: the discovery system itself
42
+ // - dashboard: starts Express server on require (side effect)
43
+ // - schema: library imported directly by schemaCommand, not a plugin
44
+ const SKIP_PACKAGES = new Set([
45
+ "sincronia-core",
46
+ "sincronia-types",
47
+ "sincronia-dashboard",
48
+ "sincronia-schema",
49
+ ]);
41
50
  const MAX_PARENT_DEPTH = 3;
42
51
  /**
43
52
  * @description Scans node_modules for @tenonhq/sincronia-* packages that export a sincPlugin.
@@ -53,4 +53,15 @@ describe("discoverPlugins", function () {
53
53
  const names = plugins.map(function (p) { return p.name; });
54
54
  expect(names).not.toContain("sass-plugin");
55
55
  });
56
+ it("skips sincronia-dashboard and sincronia-schema", function () {
57
+ Logger_1.logger.warn.mockClear();
58
+ mockReaddirSync.mockReturnValue([
59
+ "sincronia-dashboard",
60
+ "sincronia-schema",
61
+ ]);
62
+ const plugins = (0, discovery_1.discoverPlugins)();
63
+ expect(plugins).toEqual([]);
64
+ // No require() attempted, so no warn from load failure
65
+ expect(Logger_1.logger.warn).not.toHaveBeenCalled();
66
+ });
56
67
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tenonhq/sincronia-core",
3
- "version": "0.0.72",
3
+ "version": "0.0.73",
4
4
  "description": "Next-gen file syncer",
5
5
  "license": "GPL-3.0",
6
6
  "main": "./dist/index.js",