@tenonhq/sincronia-core 0.0.72 → 0.0.74

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 CHANGED
@@ -138,12 +138,20 @@ const getFileExtension = (filePath) => {
138
138
  return "";
139
139
  }
140
140
  };
141
- const getBuildExt = (table, recordName, field) => {
142
- const manifest = ConfigManager.getManifest();
141
+ const getBuildExt = (table, recordName, field, scope) => {
142
+ var manifest = ConfigManager.getManifest();
143
143
  if (!manifest) {
144
144
  throw new Error("Failed to retrieve manifest");
145
145
  }
146
- const files = manifest.tables[table].records[recordName].files;
146
+ var resolvedManifest = manifest;
147
+ if (scope && ConfigManager.isMultiScopeManifest(manifest)) {
148
+ var scopeMan = ConfigManager.resolveManifestForScope(manifest, scope);
149
+ if (!scopeMan) {
150
+ throw new Error("Failed to find scope " + scope + " in manifest");
151
+ }
152
+ resolvedManifest = scopeMan;
153
+ }
154
+ const files = resolvedManifest.tables[table].records[recordName].files;
147
155
  const file = files.find((f) => f.name === field);
148
156
  if (!file) {
149
157
  throw new Error("Unable to find file");
@@ -163,11 +171,28 @@ const getFileContextFromPath = (filePath) => {
163
171
  .split(path_1.default.sep)
164
172
  .slice(-2);
165
173
  const targetField = getTargetFieldFromPath(filePath, tableName, ext);
166
- const manifest = ConfigManager.getManifest();
174
+ var manifest = ConfigManager.getManifest();
167
175
  if (!manifest) {
168
176
  throw new Error("No manifest has been loaded!");
169
177
  }
170
- const { tables, scope } = manifest;
178
+ var scope;
179
+ var tables;
180
+ if (ConfigManager.isMultiScopeManifest(manifest)) {
181
+ var detectedScope = ConfigManager.resolveScopeFromPath(filePath);
182
+ if (!detectedScope) {
183
+ return undefined;
184
+ }
185
+ var scopeMan = ConfigManager.resolveManifestForScope(manifest, detectedScope);
186
+ if (!scopeMan) {
187
+ return undefined;
188
+ }
189
+ scope = scopeMan.scope || detectedScope;
190
+ tables = scopeMan.tables;
191
+ }
192
+ else {
193
+ scope = manifest.scope;
194
+ tables = manifest.tables;
195
+ }
171
196
  try {
172
197
  const { records } = tables[tableName];
173
198
  const record = records[recordName];
@@ -32,18 +32,23 @@ 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;
38
41
  const Logger_1 = require("./Logger");
39
42
  const FileLogger_1 = require("./FileLogger");
40
43
  const ConfigManager = __importStar(require("./config"));
44
+ const AppUtils = __importStar(require("./appUtils"));
41
45
  const wizard_1 = require("./wizard");
42
46
  const snClient_1 = require("./snClient");
43
47
  const commands_1 = require("./commands");
44
48
  const path = __importStar(require("path"));
45
49
  const fs = __importStar(require("fs"));
46
50
  const child_process_1 = require("child_process");
51
+ const progress_1 = __importDefault(require("progress"));
47
52
  const fsp = fs.promises;
48
53
  // Custom function to process manifest with specific source directory
49
54
  async function processManifestForScope(manifest, sourceDirectory, forceWrite = false) {
@@ -58,7 +63,20 @@ async function processManifestForScope(manifest, sourceDirectory, forceWrite = f
58
63
  }
59
64
  const tables = manifest.tables || {};
60
65
  const tableNames = Object.keys(tables);
61
- Logger_1.logger.info("Processing " + tableNames.length + " tables for " + sourceDirectory);
66
+ var totalRecords = 0;
67
+ for (var tn = 0; tn < tableNames.length; tn++) {
68
+ totalRecords += Object.keys(tables[tableNames[tn]].records || {}).length;
69
+ }
70
+ var scopeLabel = sourceDirectory.split(path.sep).pop() || "scope";
71
+ var progBar = null;
72
+ if (Logger_1.logger.getLogLevel() === "info" && totalRecords > 0) {
73
+ progBar = new progress_1.default(":scope :bar :current/:total (:percent)", {
74
+ total: totalRecords,
75
+ width: 40,
76
+ complete: "=",
77
+ incomplete: "-",
78
+ });
79
+ }
62
80
  for (const tableName of tableNames) {
63
81
  const tableRecords = tables[tableName];
64
82
  const tablePath = path.join(sourceDirectory, tableName);
@@ -102,9 +120,10 @@ async function processManifestForScope(manifest, sourceDirectory, forceWrite = f
102
120
  Logger_1.logger.error("Failed to write metadata: " + metadataFilePath);
103
121
  }
104
122
  }
123
+ if (progBar)
124
+ progBar.tick({ scope: scopeLabel });
105
125
  }
106
126
  }
107
- Logger_1.logger.info("Files written to " + sourceDirectory);
108
127
  }
109
128
  catch (error) {
110
129
  Logger_1.logger.error("Error processing files for " + sourceDirectory + ": " + error);
@@ -175,6 +194,8 @@ async function processScope(scopeName, scopeConfig, apiDelay = 0) {
175
194
  else {
176
195
  Logger_1.logger.warn("No _tables whitelist defined — writing ALL tables for " + scopeName);
177
196
  }
197
+ // Normalize record keys from sys_id to display name
198
+ AppUtils.normalizeManifestKeys(manifest);
178
199
  // Build the missing files structure from the filtered manifest
179
200
  var manifestTables = manifest.tables || {};
180
201
  var allMissingFiles = {};
@@ -340,7 +361,7 @@ async function watchAllScopesCommand(args) {
340
361
  }
341
362
  // Start dashboard unless --no-dashboard flag is set
342
363
  if (!args.noDashboard) {
343
- dashboardProcess = startDashboardProcess();
364
+ dashboardProcess = startDashboardProcess(args.port);
344
365
  }
345
366
  // Import and start the multi-scope watcher
346
367
  const { startMultiScopeWatching } = await Promise.resolve().then(() => __importStar(require("./MultiScopeWatcher")));
@@ -365,7 +386,7 @@ async function watchAllScopesCommand(args) {
365
386
  throw error;
366
387
  }
367
388
  }
368
- function startDashboardProcess() {
389
+ function startDashboardProcess(portOverride) {
369
390
  var serverPath;
370
391
  try {
371
392
  serverPath = require.resolve("@tenonhq/sincronia-dashboard/server.js");
@@ -374,11 +395,11 @@ function startDashboardProcess() {
374
395
  Logger_1.logger.warn("Dashboard package not installed. Run: npm install @tenonhq/sincronia-dashboard");
375
396
  return null;
376
397
  }
377
- var port = process.env.DASHBOARD_PORT || "3456";
398
+ var port = portOverride ? String(portOverride) : (process.env.DASHBOARD_PORT || "3456");
378
399
  var server = (0, child_process_1.spawn)("node", [serverPath], {
379
400
  cwd: process.cwd(),
380
401
  stdio: "ignore",
381
- env: { ...process.env },
402
+ env: { ...process.env, DASHBOARD_PORT: port },
382
403
  detached: false,
383
404
  });
384
405
  server.on("error", function (err) {
package/dist/appUtils.js CHANGED
@@ -36,7 +36,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
36
36
  return (mod && mod.__esModule) ? mod : { "default": mod };
37
37
  };
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
- exports.checkScope = exports.createAndAssignUpdateSet = exports.swapScope = exports.buildFiles = exports.summarizeRecord = exports.pushFiles = exports.getAppFileList = exports.groupAppFiles = exports.processMissingFiles = exports.findMissingFiles = exports.syncManifest = exports.processManifest = void 0;
39
+ exports.createAndAssignUpdateSet = exports.swapScope = exports.buildFiles = exports.summarizeRecord = exports.pushFiles = exports.getAppFileList = exports.groupAppFiles = exports.processMissingFiles = exports.findMissingFiles = exports.syncManifest = exports.processManifest = exports.normalizeManifestKeys = void 0;
40
40
  const path_1 = __importDefault(require("path"));
41
41
  const fs_1 = __importDefault(require("fs"));
42
42
  const progress_1 = __importDefault(require("progress"));
@@ -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,62 @@ 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
+ });
107
103
  };
108
- const processTablesInManifest = async (tables, forceWrite, sourcePath) => {
104
+ const countRecordsInTables = (tables) => {
105
+ return Object.keys(tables).reduce(function (sum, tableName) {
106
+ return sum + Object.keys(tables[tableName].records).length;
107
+ }, 0);
108
+ };
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
+ /**
117
+ * Re-keys manifest records from sys_id to record.name (display value).
118
+ * Some ServiceNow tables return records keyed by sys_id instead of display name.
119
+ * This ensures consistent naming for directories and manifest lookups.
120
+ */
121
+ const normalizeManifestKeys = (manifest) => {
122
+ var tables = manifest.tables || {};
123
+ var tableNames = Object.keys(tables);
124
+ for (var i = 0; i < tableNames.length; i++) {
125
+ var tableName = tableNames[i];
126
+ var records = tables[tableName].records || {};
127
+ var recordKeys = Object.keys(records);
128
+ var normalized = {};
129
+ for (var j = 0; j < recordKeys.length; j++) {
130
+ var key = recordKeys[j];
131
+ var record = records[key];
132
+ var displayKey = record.name || key;
133
+ // Handle duplicate display names by appending sys_id suffix
134
+ if (normalized[displayKey]) {
135
+ displayKey = displayKey + " (" + record.sys_id.substring(0, 8) + ")";
136
+ }
137
+ normalized[displayKey] = record;
138
+ }
139
+ tables[tableName].records = normalized;
140
+ }
141
+ return manifest;
142
+ };
143
+ exports.normalizeManifestKeys = normalizeManifestKeys;
116
144
  const processManifest = async (manifest, forceWrite = false, sourcePath) => {
117
145
  const tableCount = Object.keys(manifest.tables).length;
118
146
  FileLogger_1.fileLogger.debug("Processing manifest: " + (manifest.scope || "legacy") + " (" + tableCount + " tables)");
119
- await processTablesInManifest(manifest.tables, forceWrite, sourcePath);
147
+ var recordCount = countRecordsInTables(manifest.tables);
148
+ var progress = createScopeProgress(Logger_1.logger.getLogLevel(), {
149
+ scope: manifest.scope || "default",
150
+ total: recordCount,
151
+ });
152
+ await processTablesInManifest(manifest.tables, forceWrite, sourcePath, progress.tick);
120
153
  if (manifest.scope) {
121
154
  await fUtils.writeScopeManifest(manifest.scope, manifest);
122
155
  }
@@ -137,20 +170,20 @@ const syncManifest = async (scope) => {
137
170
  const config = ConfigManager.getConfig();
138
171
  // Resolve scope-specific source directory
139
172
  var scopeSourcePath = ConfigManager.getSourcePathForScope(scope);
140
- const newManifest = await (0, snClient_1.unwrapSNResponse)(client.getManifest(scope, config));
173
+ const newManifest = (0, exports.normalizeManifestKeys)(await (0, snClient_1.unwrapSNResponse)(client.getManifest(scope, config)));
141
174
  const refreshTableCount = Object.keys(newManifest.tables).length;
142
175
  FileLogger_1.fileLogger.debug("Refreshed manifest for " + scope + ": " + refreshTableCount + " tables");
143
176
  await fUtils.writeScopeManifest(scope, newManifest);
144
177
  await (0, exports.processMissingFiles)(newManifest, scopeSourcePath);
145
178
  // Update the in-memory manifest for this scope
146
- if (typeof curManifest === "object" && !curManifest.tables) {
179
+ if (ConfigManager.isMultiScopeManifest(curManifest)) {
147
180
  curManifest[scope] = newManifest;
148
181
  ConfigManager.updateManifest(curManifest);
149
182
  }
150
183
  }
151
184
  else {
152
185
  // Sync all scopes if manifest has multiple scopes
153
- if (typeof curManifest === "object" && !curManifest.tables) {
186
+ if (ConfigManager.isMultiScopeManifest(curManifest)) {
154
187
  // Multiple scopes detected
155
188
  for (const scopeName of Object.keys(curManifest)) {
156
189
  await (0, exports.syncManifest)(scopeName);
@@ -245,13 +278,18 @@ const processMissingFiles = async (newManifest, sourcePath) => {
245
278
  try {
246
279
  const missing = await (0, exports.findMissingFiles)(newManifest, sourcePath);
247
280
  const missingTableCount = Object.keys(missing).length;
248
- if (missingTableCount > 0) {
249
- FileLogger_1.fileLogger.debug("Downloading missing files from " + missingTableCount + " tables");
250
- }
281
+ if (missingTableCount === 0)
282
+ return;
283
+ FileLogger_1.fileLogger.debug("Downloading missing files from " + missingTableCount + " tables");
251
284
  const { tableOptions = {} } = ConfigManager.getConfig();
252
285
  const client = (0, snClient_1.defaultClient)();
253
286
  const filesToProcess = await (0, snClient_1.unwrapSNResponse)(client.getMissingFiles(missing, tableOptions));
254
- await processTablesInManifest(filesToProcess, false, sourcePath);
287
+ var recordCount = countRecordsInTables(filesToProcess);
288
+ var progress = createScopeProgress(Logger_1.logger.getLogLevel(), {
289
+ scope: newManifest.scope || "default",
290
+ total: recordCount,
291
+ });
292
+ await processTablesInManifest(filesToProcess, false, sourcePath, progress.tick);
255
293
  }
256
294
  catch (e) {
257
295
  throw e;
@@ -349,7 +387,7 @@ const pushFiles = async (recs) => {
349
387
  Logger_1.logger.info(`Update set routing active: ${activeScopes}`);
350
388
  }
351
389
  const tick = getProgTick(Logger_1.logger.getLogLevel(), recs.length * 2) || (() => { });
352
- const pushResultPromises = recs.map(async (rec) => {
390
+ const results = await (0, genericUtils_1.allSettledBatched)(recs, constants_1.CONCURRENCY_PUSH, async function (rec) {
353
391
  const fieldNames = Object.keys(rec.fields);
354
392
  const firstField = rec.fields[fieldNames[0]];
355
393
  const recSummary = (0, exports.summarizeRecord)(rec.table, firstField.name);
@@ -364,8 +402,7 @@ const pushFiles = async (recs) => {
364
402
  tick();
365
403
  return pushRes;
366
404
  });
367
- const results = await Promise.allSettled(pushResultPromises);
368
- return results.map((result) => {
405
+ return results.map(function (result) {
369
406
  if (result.status === "fulfilled") {
370
407
  return result.value;
371
408
  }
@@ -375,6 +412,25 @@ const pushFiles = async (recs) => {
375
412
  exports.pushFiles = pushFiles;
376
413
  const summarizeRecord = (table, recDescriptor) => `${table} > ${recDescriptor}`;
377
414
  exports.summarizeRecord = summarizeRecord;
415
+ const createScopeProgress = (logLevel, options) => {
416
+ if (logLevel !== "info" || options.total === 0) {
417
+ return { tick: function () { }, setTotal: function () { } };
418
+ }
419
+ var progBar = new progress_1.default(":scope :bar :current/:total (:percent)", {
420
+ total: options.total,
421
+ width: 40,
422
+ complete: "=",
423
+ incomplete: "-",
424
+ });
425
+ return {
426
+ tick: function () {
427
+ progBar.tick({ scope: options.scope });
428
+ },
429
+ setTotal: function (n) {
430
+ progBar.total = n;
431
+ },
432
+ };
433
+ };
378
434
  const getProgTick = (logLevel, total) => {
379
435
  if (logLevel === "info") {
380
436
  const progBar = new progress_1.default(":bar (:percent)", {
@@ -399,7 +455,7 @@ const writeBuildFile = async (preBuild, buildRes, summary) => {
399
455
  const srcFilePath = fieldCtx.filePath;
400
456
  const relativePath = path_1.default.relative(sourcePath, srcFilePath);
401
457
  const relPathNoExt = relativePath.split(".").slice(0, -1).join();
402
- const buildExt = fUtils.getBuildExt(fieldCtx.tableName, fieldCtx.name, fieldCtx.targetField);
458
+ const buildExt = fUtils.getBuildExt(fieldCtx.tableName, fieldCtx.name, fieldCtx.targetField, fieldCtx.scope);
403
459
  const relPathNewExt = `${relPathNoExt}.${buildExt}`;
404
460
  const buildFilePath = path_1.default.join(buildPath, relPathNewExt);
405
461
  await fUtils.createDirRecursively(path_1.default.dirname(buildFilePath));
@@ -407,7 +463,17 @@ const writeBuildFile = async (preBuild, buildRes, summary) => {
407
463
  return writeResult;
408
464
  });
409
465
  try {
410
- await Promise.all(writePromises);
466
+ await (0, genericUtils_1.processBatched)(fieldNames, constants_1.CONCURRENCY_FILES, async function (field) {
467
+ const fieldCtx = fields[field];
468
+ const srcFilePath = fieldCtx.filePath;
469
+ const relativePath = path_1.default.relative(sourcePath, srcFilePath);
470
+ const relPathNoExt = relativePath.split(".").slice(0, -1).join();
471
+ const buildExt = fUtils.getBuildExt(fieldCtx.tableName, fieldCtx.name, fieldCtx.targetField);
472
+ const relPathNewExt = `${relPathNoExt}.${buildExt}`;
473
+ const buildFilePath = path_1.default.join(buildPath, relPathNewExt);
474
+ await fUtils.createDirRecursively(path_1.default.dirname(buildFilePath));
475
+ await fUtils.writeFileForce(buildFilePath, buildRes.builtRec[fieldCtx.targetField]);
476
+ });
411
477
  return { success: true, message: `${recSummary} built successfully` };
412
478
  }
413
479
  catch (e) {
@@ -419,7 +485,7 @@ const writeBuildFile = async (preBuild, buildRes, summary) => {
419
485
  };
420
486
  const buildFiles = async (fileList) => {
421
487
  const tick = getProgTick(Logger_1.logger.getLogLevel(), fileList.length * 2) || (() => { });
422
- const buildPromises = fileList.map(async (rec) => {
488
+ const results = await (0, genericUtils_1.allSettledBatched)(fileList, constants_1.CONCURRENCY_BUILD, async function (rec) {
423
489
  const { fields, table } = rec;
424
490
  const fieldNames = Object.keys(fields);
425
491
  const recSummary = (0, exports.summarizeRecord)(table, fields[fieldNames[0]].name);
@@ -434,8 +500,7 @@ const buildFiles = async (fileList) => {
434
500
  tick();
435
501
  return writeRes;
436
502
  });
437
- const results = await Promise.allSettled(buildPromises);
438
- return results.map((result) => {
503
+ return results.map(function (result) {
439
504
  if (result.status === "fulfilled") {
440
505
  return result.value;
441
506
  }
@@ -507,92 +572,3 @@ const createAndAssignUpdateSet = async (updateSetName = "", scope) => {
507
572
  };
508
573
  };
509
574
  exports.createAndAssignUpdateSet = createAndAssignUpdateSet;
510
- const checkScope = async (swap) => {
511
- try {
512
- const man = ConfigManager.getManifest();
513
- if (man) {
514
- // Detect multi-scope manifest (keys are scope names, no top-level .scope)
515
- var isMultiScope = typeof man === "object" && !man.scope && !man.tables;
516
- if (isMultiScope) {
517
- // Multi-scope: session scope just needs to match any configured scope
518
- var configuredScopes = Object.keys(man);
519
- var client = (0, snClient_1.defaultClient)();
520
- var scopeObj;
521
- try {
522
- scopeObj = await (0, snClient_1.unwrapSNResponse)(client.getCurrentScope());
523
- }
524
- catch (scopeErr) {
525
- Logger_1.logger.info("Scope check endpoint unavailable, assuming match for multi-scope project");
526
- return { match: true, sessionScope: configuredScopes[0], manifestScope: configuredScopes[0] };
527
- }
528
- var sessionScope = scopeObj.scope;
529
- var scopeMatch = configuredScopes.indexOf(sessionScope) !== -1;
530
- Logger_1.logger.info("Current scope: " + sessionScope + ", Configured scopes: " + configuredScopes.join(", "));
531
- if (scopeMatch) {
532
- return { match: true, sessionScope: sessionScope, manifestScope: sessionScope };
533
- }
534
- else if (swap && configuredScopes.length > 0) {
535
- Logger_1.logger.info("Current scope (" + sessionScope + ") not in configured scopes. Swapping to " + configuredScopes[0] + "...\n");
536
- var swappedScopeObj = await (0, exports.swapScope)(configuredScopes[0]);
537
- return {
538
- match: configuredScopes.indexOf(swappedScopeObj.scope) !== -1,
539
- sessionScope: swappedScopeObj.scope,
540
- manifestScope: configuredScopes[0],
541
- };
542
- }
543
- else {
544
- return { match: false, sessionScope: sessionScope, manifestScope: configuredScopes.join(", ") };
545
- }
546
- }
547
- // Single-scope manifest
548
- var singleClient = (0, snClient_1.defaultClient)();
549
- var singleScopeObj;
550
- try {
551
- singleScopeObj = await (0, snClient_1.unwrapSNResponse)(singleClient.getCurrentScope());
552
- }
553
- catch (scopeErr) {
554
- // getCurrentScope endpoint may not exist on this instance — skip scope check
555
- Logger_1.logger.info(`Scope check endpoint unavailable, assuming scope match for: ${man.scope}`);
556
- return {
557
- match: true,
558
- sessionScope: man.scope,
559
- manifestScope: man.scope,
560
- };
561
- }
562
- Logger_1.logger.info(`Current scope: ${singleScopeObj.scope}, Manifest scope: ${man.scope}`);
563
- if (singleScopeObj.scope === man.scope) {
564
- return {
565
- match: true,
566
- sessionScope: singleScopeObj.scope,
567
- manifestScope: man.scope,
568
- };
569
- }
570
- else if (swap) {
571
- Logger_1.logger.info(`Current scope (${singleScopeObj.scope}) does not match manifest scope (${man.scope}). Swapping...\n`);
572
- var singleSwapped = await (0, exports.swapScope)(man.scope);
573
- return {
574
- match: singleSwapped.scope === man.scope,
575
- sessionScope: singleSwapped.scope,
576
- manifestScope: man.scope,
577
- };
578
- }
579
- else {
580
- return {
581
- match: false,
582
- sessionScope: singleScopeObj.scope,
583
- manifestScope: man.scope,
584
- };
585
- }
586
- }
587
- //first time case
588
- return {
589
- match: true,
590
- sessionScope: "",
591
- manifestScope: "",
592
- };
593
- }
594
- catch (e) {
595
- throw e;
596
- }
597
- };
598
- exports.checkScope = checkScope;
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) => {
@@ -45,12 +50,6 @@ async function initCommands() {
45
50
  default: "",
46
51
  describe: "Specify branch to do git diff against",
47
52
  },
48
- scopeSwap: {
49
- alias: "ss",
50
- type: "boolean",
51
- default: false,
52
- describe: "Will auto-swap to the correct scope for the files being pushed",
53
- },
54
53
  updateSet: {
55
54
  alias: "us",
56
55
  type: "string",
@@ -286,7 +285,19 @@ async function initCommands() {
286
285
  });
287
286
  return cmdArgs;
288
287
  }, updateSetCommands_1.showCurrentScopeCommand)
289
- .command("dashboard", "Launch the Update Set Dashboard web UI", sharedOptions, dashboardCommand_1.dashboardCommand)
288
+ .command("dashboard", "Launch the Update Set Dashboard web UI", (cmdArgs) => {
289
+ cmdArgs.options({
290
+ ...sharedOptions,
291
+ port: {
292
+ alias: "p",
293
+ type: "number",
294
+ describe: "Dashboard port (default: DASHBOARD_PORT env or 3456)",
295
+ },
296
+ });
297
+ return cmdArgs;
298
+ }, async (args) => {
299
+ await (0, dashboardCommand_1.dashboardCommand)(args);
300
+ })
290
301
  .command("schema <subcommand>", "Manage ServiceNow table schemas (subcommands: pull)", (cmdArgs) => {
291
302
  cmdArgs.positional("subcommand", {
292
303
  describe: "Schema subcommand to run",
package/dist/commands.js CHANGED
@@ -37,7 +37,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
37
37
  };
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
39
  exports.setLogLevel = setLogLevel;
40
- exports.devCommand = devCommand;
41
40
  exports.refreshCommand = refreshCommand;
42
41
  exports.pushCommand = pushCommand;
43
42
  exports.downloadCommand = downloadCommand;
@@ -46,7 +45,6 @@ exports.buildCommand = buildCommand;
46
45
  exports.deployCommand = deployCommand;
47
46
  exports.statusCommand = statusCommand;
48
47
  const ConfigManager = __importStar(require("./config"));
49
- const Watcher_1 = require("./Watcher");
50
48
  const AppUtils = __importStar(require("./appUtils"));
51
49
  const orchestrator_1 = require("./initSystem/orchestrator");
52
50
  const Logger_1 = require("./Logger");
@@ -56,135 +54,108 @@ const snClient_1 = require("./snClient");
56
54
  const inquirer_1 = __importDefault(require("inquirer"));
57
55
  const gitUtils_1 = require("./gitUtils");
58
56
  const FileUtils_1 = require("./FileUtils");
59
- async function scopeCheck(successFunc, swapScopes = false) {
57
+ function setLogLevel(args) {
58
+ Logger_1.logger.setLogLevel(args.logLevel);
59
+ }
60
+ async function refreshCommand(args, log = true) {
61
+ setLogLevel(args);
60
62
  try {
61
- const scopeCheck = await AppUtils.checkScope(swapScopes);
62
- if (!scopeCheck.match) {
63
- (0, logMessages_1.scopeCheckMessage)(scopeCheck);
64
- // Throw exception to register this as an error
65
- throw new Error();
66
- }
67
- else {
68
- await successFunc();
69
- }
63
+ if (!log)
64
+ setLogLevel({ logLevel: "warn" });
65
+ FileLogger_1.fileLogger.debug("Syncing manifest from instance");
66
+ await AppUtils.syncManifest();
67
+ Logger_1.logger.success("Refresh complete!");
68
+ setLogLevel(args);
70
69
  }
71
70
  catch (e) {
72
- Logger_1.logger.error("Scope check failed. Verify your project configuration or run `npx sinc init`");
73
- // Throw exception to register this as an error
74
- process.exit(1);
71
+ throw e;
75
72
  }
76
73
  }
77
- function setLogLevel(args) {
78
- Logger_1.logger.setLogLevel(args.logLevel);
79
- }
80
- async function devCommand(args) {
74
+ async function pushCommand(args) {
81
75
  setLogLevel(args);
82
- await scopeCheck(async () => {
83
- (0, Watcher_1.startWatching)(ConfigManager.getSourcePath());
84
- (0, logMessages_1.devModeLog)();
85
- let refresher = () => {
86
- refreshCommand(args, false);
87
- };
88
- let interval = ConfigManager.getRefresh();
89
- if (interval && interval > 0) {
90
- Logger_1.logger.info(`Checking for new manifest files every ${interval} seconds`);
91
- setInterval(refresher, interval * 1000);
76
+ try {
77
+ const { updateSet, ci: skipPrompt, target, diff } = args;
78
+ let encodedPaths;
79
+ if (target !== undefined && target !== "")
80
+ encodedPaths = target;
81
+ else
82
+ encodedPaths = await (0, gitUtils_1.gitDiffToEncodedPaths)(diff);
83
+ const fileList = await AppUtils.getAppFileList(encodedPaths);
84
+ const instance = process.env.SN_INSTANCE || "unknown";
85
+ Logger_1.logger.info(fileList.length + " files to push to " + instance);
86
+ if (!skipPrompt) {
87
+ const targetServer = process.env.SN_INSTANCE;
88
+ if (!targetServer) {
89
+ Logger_1.logger.error("No SN_INSTANCE configured. Set it in your .env file.");
90
+ return;
91
+ }
92
+ const answers = await inquirer_1.default.prompt([
93
+ {
94
+ type: "confirm",
95
+ name: "confirmed",
96
+ message: "Pushing will overwrite code in your instance. Are you sure?",
97
+ default: false,
98
+ },
99
+ ]);
100
+ if (!answers["confirmed"])
101
+ return;
92
102
  }
93
- });
94
- }
95
- async function refreshCommand(args, log = true) {
96
- setLogLevel(args);
97
- await scopeCheck(async () => {
98
- try {
99
- if (!log)
100
- setLogLevel({ logLevel: "warn" });
101
- FileLogger_1.fileLogger.debug("Syncing manifest from instance");
102
- await AppUtils.syncManifest();
103
- Logger_1.logger.success("Refresh complete!");
104
- setLogLevel(args);
103
+ // Handle --clickup flag: resolve ClickUp task into an update set name
104
+ let resolvedUpdateSet = updateSet;
105
+ if (!resolvedUpdateSet && args.clickup) {
106
+ try {
107
+ const { resolveClickUpForPush } = await Promise.resolve().then(() => __importStar(require("./clickupPushHelper")));
108
+ resolvedUpdateSet = await resolveClickUpForPush(args.clickup);
109
+ }
110
+ catch (cuErr) {
111
+ if (cuErr instanceof Error)
112
+ Logger_1.logger.error(cuErr.message);
113
+ process.exit(1);
114
+ }
105
115
  }
106
- catch (e) {
107
- throw e;
116
+ // Extract scope from file list and auto-swap to it
117
+ var pushScope;
118
+ if (fileList.length > 0) {
119
+ var fieldKeys = Object.keys(fileList[0].fields);
120
+ if (fieldKeys.length > 0) {
121
+ pushScope = fileList[0].fields[fieldKeys[0]].scope;
122
+ }
108
123
  }
109
- });
110
- }
111
- async function pushCommand(args) {
112
- setLogLevel(args);
113
- await scopeCheck(async () => {
114
- try {
115
- const { updateSet, ci: skipPrompt, target, diff } = args;
116
- let encodedPaths;
117
- if (target !== undefined && target !== "")
118
- encodedPaths = target;
119
- else
120
- encodedPaths = await (0, gitUtils_1.gitDiffToEncodedPaths)(diff);
121
- const fileList = await AppUtils.getAppFileList(encodedPaths);
122
- const instance = process.env.SN_INSTANCE || "unknown";
123
- Logger_1.logger.info(fileList.length + " files to push to " + instance);
124
+ if (pushScope) {
125
+ try {
126
+ var client = (0, snClient_1.defaultClient)();
127
+ await client.changeScope(pushScope);
128
+ Logger_1.logger.info("Switched to scope: " + pushScope);
129
+ }
130
+ catch (scopeErr) {
131
+ Logger_1.logger.warn("Could not auto-switch scope: " + scopeErr);
132
+ }
133
+ }
134
+ // Does not create update set if updateSetName is blank
135
+ if (resolvedUpdateSet) {
124
136
  if (!skipPrompt) {
125
- const targetServer = process.env.SN_INSTANCE;
126
- if (!targetServer) {
127
- Logger_1.logger.error("No SN_INSTANCE configured. Set it in your .env file.");
128
- return;
129
- }
130
- const answers = await inquirer_1.default.prompt([
137
+ let answers = await inquirer_1.default.prompt([
131
138
  {
132
139
  type: "confirm",
133
140
  name: "confirmed",
134
- message: "Pushing will overwrite code in your instance. Are you sure?",
141
+ message: `A new Update Set "${resolvedUpdateSet}" will be created for these pushed changes. Do you want to proceed?`,
135
142
  default: false,
136
143
  },
137
144
  ]);
138
- if (!answers["confirmed"])
139
- return;
140
- }
141
- // Handle --clickup flag: resolve ClickUp task into an update set name
142
- let resolvedUpdateSet = updateSet;
143
- if (!resolvedUpdateSet && args.clickup) {
144
- try {
145
- const { resolveClickUpForPush } = await Promise.resolve().then(() => __importStar(require("./clickupPushHelper")));
146
- resolvedUpdateSet = await resolveClickUpForPush(args.clickup);
147
- }
148
- catch (cuErr) {
149
- if (cuErr instanceof Error)
150
- Logger_1.logger.error(cuErr.message);
151
- process.exit(1);
145
+ if (!answers["confirmed"]) {
146
+ process.exit(0);
152
147
  }
153
148
  }
154
- // Extract scope from file list for update set creation
155
- var pushScope;
156
- if (fileList.length > 0) {
157
- var fieldKeys = Object.keys(fileList[0].fields);
158
- if (fieldKeys.length > 0) {
159
- pushScope = fileList[0].fields[fieldKeys[0]].scope;
160
- }
161
- }
162
- // Does not create update set if updateSetName is blank
163
- if (resolvedUpdateSet) {
164
- if (!skipPrompt) {
165
- let answers = await inquirer_1.default.prompt([
166
- {
167
- type: "confirm",
168
- name: "confirmed",
169
- message: `A new Update Set "${resolvedUpdateSet}" will be created for these pushed changes. Do you want to proceed?`,
170
- default: false,
171
- },
172
- ]);
173
- if (!answers["confirmed"]) {
174
- process.exit(0);
175
- }
176
- }
177
- const newUpdateSet = await AppUtils.createAndAssignUpdateSet(resolvedUpdateSet, pushScope);
178
- Logger_1.logger.debug(`New Update Set Created(${newUpdateSet.name}) sys_id:${newUpdateSet.id}`);
179
- }
180
- const pushResults = await AppUtils.pushFiles(fileList);
181
- (0, logMessages_1.logPushResults)(pushResults);
182
- }
183
- catch (e) {
184
- Logger_1.logger.getInternalLogger().error(e);
185
- process.exit(1);
149
+ const newUpdateSet = await AppUtils.createAndAssignUpdateSet(resolvedUpdateSet, pushScope);
150
+ Logger_1.logger.debug(`New Update Set Created(${newUpdateSet.name}) sys_id:${newUpdateSet.id}`);
186
151
  }
187
- }, args.scopeSwap);
152
+ const pushResults = await AppUtils.pushFiles(fileList);
153
+ (0, logMessages_1.logPushResults)(pushResults);
154
+ }
155
+ catch (e) {
156
+ Logger_1.logger.getInternalLogger().error(e);
157
+ process.exit(1);
158
+ }
188
159
  }
189
160
  async function downloadCommand(args) {
190
161
  setLogLevel(args);
@@ -233,7 +204,6 @@ async function downloadCommand(args) {
233
204
  });
234
205
  Logger_1.logger.info("Received " + tableCount + " tables, " + recordCount + " records");
235
206
  FileLogger_1.fileLogger.debug("Download manifest: " + tableCount + " tables, " + recordCount + " records");
236
- Logger_1.logger.info("Writing files to disk...");
237
207
  await AppUtils.processManifest(man, true);
238
208
  Logger_1.logger.success("Download complete — " + recordCount + " records written");
239
209
  }
@@ -285,43 +255,72 @@ async function getDeployPaths() {
285
255
  }
286
256
  async function deployCommand(args) {
287
257
  setLogLevel(args);
288
- await scopeCheck(async () => {
289
- try {
290
- const targetServer = process.env.SN_INSTANCE || "";
291
- if (!targetServer) {
292
- Logger_1.logger.error("No SN_INSTANCE configured. Set it in your .env file.");
293
- return;
294
- }
295
- const { confirmed } = await inquirer_1.default.prompt([
296
- {
297
- type: "confirm",
298
- name: "confirmed",
299
- message: "Deploying will overwrite code in your instance. Are you sure?",
300
- default: false,
301
- },
302
- ]);
303
- if (!confirmed) {
304
- return;
305
- }
306
- const paths = await getDeployPaths();
307
- Logger_1.logger.silly(`${paths.length} paths found...`);
308
- Logger_1.logger.silly(JSON.stringify(paths, null, 2));
309
- const appFileList = await AppUtils.getAppFileList(paths);
310
- const pushResults = await AppUtils.pushFiles(appFileList);
311
- (0, logMessages_1.logPushResults)(pushResults);
258
+ try {
259
+ const targetServer = process.env.SN_INSTANCE || "";
260
+ if (!targetServer) {
261
+ Logger_1.logger.error("No SN_INSTANCE configured. Set it in your .env file.");
262
+ return;
312
263
  }
313
- catch (e) {
314
- throw e;
264
+ const { confirmed } = await inquirer_1.default.prompt([
265
+ {
266
+ type: "confirm",
267
+ name: "confirmed",
268
+ message: "Deploying will overwrite code in your instance. Are you sure?",
269
+ default: false,
270
+ },
271
+ ]);
272
+ if (!confirmed) {
273
+ return;
315
274
  }
316
- });
275
+ const paths = await getDeployPaths();
276
+ Logger_1.logger.silly(`${paths.length} paths found...`);
277
+ Logger_1.logger.silly(JSON.stringify(paths, null, 2));
278
+ const appFileList = await AppUtils.getAppFileList(paths);
279
+ // Auto-swap to scope detected from files
280
+ if (appFileList.length > 0) {
281
+ var fieldKeys = Object.keys(appFileList[0].fields);
282
+ if (fieldKeys.length > 0) {
283
+ var deployScope = appFileList[0].fields[fieldKeys[0]].scope;
284
+ if (deployScope) {
285
+ try {
286
+ var client = (0, snClient_1.defaultClient)();
287
+ await client.changeScope(deployScope);
288
+ Logger_1.logger.info("Switched to scope: " + deployScope);
289
+ }
290
+ catch (scopeErr) {
291
+ Logger_1.logger.warn("Could not auto-switch scope: " + scopeErr);
292
+ }
293
+ }
294
+ }
295
+ }
296
+ const pushResults = await AppUtils.pushFiles(appFileList);
297
+ (0, logMessages_1.logPushResults)(pushResults);
298
+ }
299
+ catch (e) {
300
+ throw e;
301
+ }
317
302
  }
318
303
  async function statusCommand() {
319
304
  try {
320
305
  const client = (0, snClient_1.defaultClient)();
306
+ var config = ConfigManager.getConfig();
321
307
  let scopeObj = await (0, snClient_1.unwrapSNResponse)(client.getCurrentScope());
322
- Logger_1.logger.info("Instance: " + (process.env.SN_INSTANCE || "not set"));
323
- Logger_1.logger.info("Scope: " + scopeObj.scope);
324
- Logger_1.logger.info("User: " + (process.env.SN_USER || "not set"));
308
+ Logger_1.logger.info("Instance: " + (process.env.SN_INSTANCE || "not set"));
309
+ Logger_1.logger.info("User: " + (process.env.SN_USER || "not set"));
310
+ Logger_1.logger.info("Active scope: " + scopeObj.scope);
311
+ if (config.scopes) {
312
+ var scopeNames = Object.keys(config.scopes);
313
+ Logger_1.logger.info("\nConfigured scopes (" + scopeNames.length + "):");
314
+ for (var i = 0; i < scopeNames.length; i++) {
315
+ var scopeName = scopeNames[i];
316
+ var scopeConf = config.scopes[scopeName];
317
+ var srcDir = (typeof scopeConf === "object" && scopeConf.sourceDirectory)
318
+ ? scopeConf.sourceDirectory
319
+ : "src/" + scopeName;
320
+ var marker = scopeName === scopeObj.scope ? " (active)" : "";
321
+ Logger_1.logger.info(" " + scopeName + marker + " — " + srcDir);
322
+ }
323
+ }
325
324
  }
326
325
  catch (e) {
327
326
  throw e;
package/dist/config.js CHANGED
@@ -54,6 +54,9 @@ exports.getRefresh = getRefresh;
54
54
  exports.getDefaultConfigFile = getDefaultConfigFile;
55
55
  exports.loadScopeManifest = loadScopeManifest;
56
56
  exports.updateManifest = updateManifest;
57
+ exports.isMultiScopeManifest = isMultiScopeManifest;
58
+ exports.resolveManifestForScope = resolveManifestForScope;
59
+ exports.resolveScopeFromPath = resolveScopeFromPath;
57
60
  exports.isDirectiveKey = isDirectiveKey;
58
61
  exports.stripDirectiveKeys = stripDirectiveKeys;
59
62
  exports.resolveConfigForScope = resolveConfigForScope;
@@ -290,6 +293,35 @@ async function loadScopeManifest(scope) {
290
293
  function updateManifest(man) {
291
294
  manifest = man;
292
295
  }
296
+ function isMultiScopeManifest(man) {
297
+ return typeof man === "object" && man !== null && !man.scope && !man.tables;
298
+ }
299
+ function resolveManifestForScope(man, scope) {
300
+ if (!isMultiScopeManifest(man)) {
301
+ if (!scope || man.scope === scope) {
302
+ return man;
303
+ }
304
+ return undefined;
305
+ }
306
+ var scopeMan = man[scope];
307
+ if (scopeMan && scopeMan.tables) {
308
+ return scopeMan;
309
+ }
310
+ return undefined;
311
+ }
312
+ function resolveScopeFromPath(filePath) {
313
+ var cfg = getConfig();
314
+ if (!cfg.scopes)
315
+ return undefined;
316
+ var scopeNames = Object.keys(cfg.scopes);
317
+ for (var i = 0; i < scopeNames.length; i++) {
318
+ var scopeSourcePath = getSourcePathForScope(scopeNames[i]);
319
+ if (filePath.indexOf(scopeSourcePath) === 0) {
320
+ return scopeNames[i];
321
+ }
322
+ }
323
+ return undefined;
324
+ }
293
325
  async function loadConfigPath(pth) {
294
326
  if (!pth) {
295
327
  pth = process.cwd();
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;
@@ -102,7 +102,7 @@ async function resolveScope(args) {
102
102
  // Try to get scope from current manifest
103
103
  try {
104
104
  var manifest = ConfigManager.getManifest();
105
- if (manifest && manifest.scope) {
105
+ if (manifest && !ConfigManager.isMultiScopeManifest(manifest) && manifest.scope) {
106
106
  return manifest.scope;
107
107
  }
108
108
  }
@@ -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) => {
@@ -57,7 +57,7 @@ async function resolveScope(args) {
57
57
  // Try to get scope from current manifest
58
58
  try {
59
59
  var manifest = ConfigManager.getManifest();
60
- if (manifest && manifest.scope) {
60
+ if (manifest && !ConfigManager.isMultiScopeManifest(manifest) && manifest.scope) {
61
61
  return manifest.scope;
62
62
  }
63
63
  }
@@ -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.
@@ -173,6 +173,34 @@ async function runLoginPhase(plugin, context) {
173
173
  }
174
174
  }
175
175
  }
176
+ async function runConfigPhase(context) {
177
+ if (context.hasConfig) {
178
+ var answer = await inquirer_1.default.prompt([{
179
+ type: "list",
180
+ name: "configAction",
181
+ message: "Existing config found. Would you like to update it or use the current one?",
182
+ choices: [
183
+ { name: "Use current config", value: "keep" },
184
+ { name: "Update config", value: "update" },
185
+ ],
186
+ }]);
187
+ if (answer.configAction === "keep") {
188
+ Logger_1.logger.info(chalk_1.default.green(" ✓ Using existing sinc.config.js"));
189
+ return;
190
+ }
191
+ }
192
+ // TODO: Future config wizard steps:
193
+ // 1. Scopes — multi-select from available scopes
194
+ // 2. Tables — multi-select with search (inquirer-autocomplete)
195
+ // 3. Fields for selected tables
196
+ // 4. Special scope tables
197
+ // 5. Special scope fields for tables
198
+ Logger_1.logger.info("");
199
+ Logger_1.logger.info(chalk_1.default.magenta(" 🎬 Coming soon to a terminal near you!"));
200
+ Logger_1.logger.info(chalk_1.default.dim(" The config wizard is still in development — stay tuned."));
201
+ Logger_1.logger.info(chalk_1.default.dim(" For now, " + (context.hasConfig ? "we'll keep your current config." : "we'll set you up with the defaults.")));
202
+ Logger_1.logger.info("");
203
+ }
176
204
  async function runConfigurePhase(plugin, context) {
177
205
  const hooks = plugin.configure;
178
206
  if (!hooks || hooks.length === 0)
@@ -239,6 +267,11 @@ async function runInit(options) {
239
267
  }
240
268
  // 5. Save env vars after login
241
269
  saveEnvVars(context, selectedPlugins);
270
+ // 5.5 Config phase
271
+ Logger_1.logger.info("");
272
+ Logger_1.logger.info(chalk_1.default.bold(" ── Config " + "─".repeat(29)));
273
+ Logger_1.logger.info("");
274
+ await runConfigPhase(context);
242
275
  // 6. Configure phase
243
276
  Logger_1.logger.info("");
244
277
  Logger_1.logger.info(chalk_1.default.bold(" ── Configure " + "─".repeat(26)));
@@ -4,21 +4,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.logBuildResults = exports.logPushResults = exports.log = void 0;
7
- exports.scopeCheckMessage = scopeCheckMessage;
8
- exports.devModeLog = devModeLog;
9
7
  exports.logFilePush = logFilePush;
10
8
  exports.logDeploy = logDeploy;
11
9
  const Logger_1 = require("./Logger");
12
10
  const chalk_1 = __importDefault(require("chalk"));
13
11
  exports.log = console.log;
14
- function scopeCheckMessage(scopeCheck) {
15
- let sScope = chalk_1.default.blue(scopeCheck.sessionScope);
16
- let mScope = chalk_1.default.blue(scopeCheck.manifestScope);
17
- Logger_1.logger.error("Scope mismatch: your session is " + sScope + " but this project targets " + mScope + ". Switch scopes in ServiceNow to continue.");
18
- }
19
- function devModeLog() {
20
- Logger_1.logger.info(`Dev mode started! Watching for changes...[${chalk_1.default.red("Press CTRL-C to Stop")}]\n`);
21
- }
22
12
  function parseError(err) {
23
13
  return `${err.name}:
24
14
  ${err.message}
@@ -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.74",
4
4
  "description": "Next-gen file syncer",
5
5
  "license": "GPL-3.0",
6
6
  "main": "./dist/index.js",