@eldrforge/kodrdriv 0.0.38 → 0.0.40

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.
@@ -7,6 +7,35 @@ import { run } from '../util/child.js';
7
7
  import { execute as execute$1 } from './publish.js';
8
8
  import { safeJsonParse, validatePackageJson } from '../util/validation.js';
9
9
 
10
+ // Create a package-scoped logger that prefixes all messages
11
+ const createPackageLogger = (packageName, sequenceNumber, totalCount, isDryRun = false)=>{
12
+ const baseLogger = getLogger();
13
+ const prefix = `[${sequenceNumber}/${totalCount}] ${packageName}:`;
14
+ const dryRunPrefix = isDryRun ? 'DRY RUN: ' : '';
15
+ return {
16
+ info: (message, ...args)=>baseLogger.info(`${dryRunPrefix}${prefix} ${message}`, ...args),
17
+ warn: (message, ...args)=>baseLogger.warn(`${dryRunPrefix}${prefix} ${message}`, ...args),
18
+ error: (message, ...args)=>baseLogger.error(`${dryRunPrefix}${prefix} ${message}`, ...args),
19
+ debug: (message, ...args)=>baseLogger.debug(`${dryRunPrefix}${prefix} ${message}`, ...args),
20
+ verbose: (message, ...args)=>baseLogger.verbose(`${dryRunPrefix}${prefix} ${message}`, ...args),
21
+ silly: (message, ...args)=>baseLogger.silly(`${dryRunPrefix}${prefix} ${message}`, ...args)
22
+ };
23
+ };
24
+ // Execute an operation with package context logging for nested operations
25
+ const withPackageContext = async (packageName, sequenceNumber, totalCount, isDryRun, operation)=>{
26
+ const packageLogger = createPackageLogger(packageName, sequenceNumber, totalCount, isDryRun);
27
+ // For now, just execute the operation directly
28
+ // In the future, we could implement more sophisticated context passing
29
+ try {
30
+ packageLogger.verbose(`Starting nested operation...`);
31
+ const result = await operation();
32
+ packageLogger.verbose(`Nested operation completed`);
33
+ return result;
34
+ } catch (error) {
35
+ packageLogger.error(`Nested operation failed: ${error.message}`);
36
+ throw error;
37
+ }
38
+ };
10
39
  // Helper function to format subproject error output
11
40
  const formatSubprojectError = (packageName, error)=>{
12
41
  const lines = [];
@@ -176,6 +205,105 @@ const topologicalSort = (graph)=>{
176
205
  logger.verbose(`Topological sort completed. Build order determined for ${result.length} packages.`);
177
206
  return result;
178
207
  };
208
+ // Group packages into dependency levels for parallel execution
209
+ const groupPackagesByDependencyLevels = (graph, buildOrder)=>{
210
+ const logger = getLogger();
211
+ const { edges } = graph;
212
+ const levels = [];
213
+ const packageLevels = new Map();
214
+ // Calculate the dependency level for each package
215
+ const calculateLevel = (packageName)=>{
216
+ if (packageLevels.has(packageName)) {
217
+ return packageLevels.get(packageName);
218
+ }
219
+ const deps = edges.get(packageName) || new Set();
220
+ if (deps.size === 0) {
221
+ // No dependencies - this is level 0
222
+ packageLevels.set(packageName, 0);
223
+ return 0;
224
+ }
225
+ // Level is 1 + max level of dependencies
226
+ let maxDepLevel = -1;
227
+ for (const dep of deps){
228
+ const depLevel = calculateLevel(dep);
229
+ maxDepLevel = Math.max(maxDepLevel, depLevel);
230
+ }
231
+ const level = maxDepLevel + 1;
232
+ packageLevels.set(packageName, level);
233
+ return level;
234
+ };
235
+ // Calculate levels for all packages
236
+ for (const packageName of buildOrder){
237
+ calculateLevel(packageName);
238
+ }
239
+ // Group packages by their levels
240
+ for (const packageName of buildOrder){
241
+ const level = packageLevels.get(packageName);
242
+ while(levels.length <= level){
243
+ levels.push([]);
244
+ }
245
+ levels[level].push(packageName);
246
+ }
247
+ logger.verbose(`Packages grouped into ${levels.length} dependency levels for parallel execution`);
248
+ for(let i = 0; i < levels.length; i++){
249
+ logger.verbose(` Level ${i}: ${levels[i].join(', ')}`);
250
+ }
251
+ return levels;
252
+ };
253
+ // Execute a single package and return execution result
254
+ const executePackage = async (packageName, packageInfo, commandToRun, shouldPublish, runConfig, isDryRun, index, total)=>{
255
+ const packageLogger = createPackageLogger(packageName, index + 1, total, isDryRun);
256
+ const packageDir = packageInfo.path;
257
+ packageLogger.info(`Starting execution...`);
258
+ packageLogger.verbose(`Working directory: ${packageDir}`);
259
+ try {
260
+ if (isDryRun) {
261
+ if (shouldPublish) {
262
+ packageLogger.info(`Would execute publish command directly`);
263
+ } else {
264
+ // Use main logger for the specific message tests expect
265
+ const logger = getLogger();
266
+ logger.info(`DRY RUN: Would execute: ${commandToRun}`);
267
+ packageLogger.info(`In directory: ${packageDir}`);
268
+ }
269
+ } else {
270
+ // Change to the package directory and run the command
271
+ const originalCwd = process.cwd();
272
+ try {
273
+ process.chdir(packageDir);
274
+ packageLogger.verbose(`Changed to directory: ${packageDir}`);
275
+ if (shouldPublish) {
276
+ packageLogger.info(`Starting publish process...`);
277
+ // Call publish command with package context so all nested logs are prefixed
278
+ await withPackageContext(packageName, index + 1, total, isDryRun, async ()=>{
279
+ await execute$1(runConfig);
280
+ });
281
+ packageLogger.info(`Publish completed successfully`);
282
+ } else {
283
+ packageLogger.info(`Executing command: ${commandToRun}`);
284
+ // Wrap command execution in package context
285
+ await withPackageContext(packageName, index + 1, total, isDryRun, async ()=>{
286
+ await run(commandToRun); // Non-null assertion since we're inside if (commandToRun)
287
+ });
288
+ packageLogger.info(`Command completed successfully`);
289
+ }
290
+ packageLogger.info(`✅ Execution completed successfully`);
291
+ } finally{
292
+ process.chdir(originalCwd);
293
+ packageLogger.verbose(`Restored working directory to: ${originalCwd}`);
294
+ }
295
+ }
296
+ return {
297
+ success: true
298
+ };
299
+ } catch (error) {
300
+ packageLogger.error(`❌ Execution failed: ${error.message}`);
301
+ return {
302
+ success: false,
303
+ error
304
+ };
305
+ }
306
+ };
179
307
  const execute = async (runConfig)=>{
180
308
  var _runConfig_publishTree;
181
309
  const logger = getLogger();
@@ -184,7 +312,7 @@ const execute = async (runConfig)=>{
184
312
  const targetDirectory = ((_runConfig_publishTree = runConfig.publishTree) === null || _runConfig_publishTree === void 0 ? void 0 : _runConfig_publishTree.directory) || process.cwd();
185
313
  logger.info(`${isDryRun ? 'DRY RUN: ' : ''}Analyzing workspace at: ${targetDirectory}`);
186
314
  try {
187
- var _runConfig_publishTree1, _runConfig_publishTree2, _runConfig_publishTree3, _runConfig_publishTree4, _runConfig_publishTree5;
315
+ var _runConfig_publishTree1, _runConfig_publishTree2, _runConfig_publishTree3, _runConfig_publishTree4, _runConfig_publishTree5, _runConfig_publishTree6;
188
316
  // Get exclusion patterns from config, fallback to empty array
189
317
  const excludedPatterns = ((_runConfig_publishTree1 = runConfig.publishTree) === null || _runConfig_publishTree1 === void 0 ? void 0 : _runConfig_publishTree1.excludedPatterns) || [];
190
318
  if (excludedPatterns.length > 0) {
@@ -253,7 +381,8 @@ const execute = async (runConfig)=>{
253
381
  // Execute script, cmd, or publish if provided
254
382
  const script = (_runConfig_publishTree3 = runConfig.publishTree) === null || _runConfig_publishTree3 === void 0 ? void 0 : _runConfig_publishTree3.script;
255
383
  const cmd = (_runConfig_publishTree4 = runConfig.publishTree) === null || _runConfig_publishTree4 === void 0 ? void 0 : _runConfig_publishTree4.cmd;
256
- const shouldPublish = (_runConfig_publishTree5 = runConfig.publishTree) === null || _runConfig_publishTree5 === void 0 ? void 0 : _runConfig_publishTree5.publish;
384
+ const shouldPublish = ((_runConfig_publishTree5 = runConfig.publishTree) === null || _runConfig_publishTree5 === void 0 ? void 0 : _runConfig_publishTree5.publish) || false;
385
+ const useParallel = ((_runConfig_publishTree6 = runConfig.publishTree) === null || _runConfig_publishTree6 === void 0 ? void 0 : _runConfig_publishTree6.parallel) || false;
257
386
  // Handle conflicts between --script, --cmd, and --publish
258
387
  // Priority order: --publish > --cmd > --script
259
388
  let commandToRun;
@@ -280,56 +409,106 @@ const execute = async (runConfig)=>{
280
409
  }
281
410
  if (commandToRun || shouldPublish) {
282
411
  const executionDescription = shouldPublish ? 'publish command' : `"${commandToRun}"`;
283
- logger.info(`${isDryRun ? 'DRY RUN: ' : ''}Executing ${actionName} ${executionDescription} in ${buildOrder.length} packages...`);
412
+ const parallelInfo = useParallel ? ' (with parallel execution)' : '';
413
+ logger.info(`${isDryRun ? 'DRY RUN: ' : ''}Executing ${actionName} ${executionDescription} in ${buildOrder.length} packages${parallelInfo}...`);
284
414
  let successCount = 0;
285
415
  let failedPackage = null;
286
- for(let i = 0; i < buildOrder.length; i++){
287
- const packageName = buildOrder[i];
288
- const packageInfo = dependencyGraph.packages.get(packageName);
289
- const packageDir = packageInfo.path;
290
- logger.info(`${isDryRun ? 'DRY RUN: ' : ''}[${i + 1}/${buildOrder.length}] Running "${commandToRun}" in ${packageName}...`);
291
- logger.verbose(`${isDryRun ? 'DRY RUN: ' : ''}Working directory: ${packageDir}`);
292
- try {
293
- if (isDryRun) {
294
- if (shouldPublish) {
295
- logger.info(`DRY RUN: Would execute publish command directly`);
296
- } else {
297
- logger.info(`DRY RUN: Would execute: ${commandToRun}`);
298
- }
299
- logger.info(`DRY RUN: In directory: ${packageDir}`);
416
+ if (useParallel) {
417
+ // Parallel execution: group packages by dependency levels
418
+ const dependencyLevels = groupPackagesByDependencyLevels(dependencyGraph, buildOrder);
419
+ for(let levelIndex = 0; levelIndex < dependencyLevels.length; levelIndex++){
420
+ const currentLevel = dependencyLevels[levelIndex];
421
+ if (currentLevel.length === 1) {
422
+ const packageName = currentLevel[0];
423
+ logger.info(`${isDryRun ? 'DRY RUN: ' : ''}Level ${levelIndex + 1}: Executing ${packageName}...`);
300
424
  } else {
301
- // Change to the package directory and run the command
302
- const originalCwd = process.cwd();
303
- try {
304
- process.chdir(packageDir);
305
- if (shouldPublish) {
306
- // Call publish command directly instead of shelling out to npx
307
- await execute$1(runConfig);
425
+ logger.info(`${isDryRun ? 'DRY RUN: ' : ''}Level ${levelIndex + 1}: Executing ${currentLevel.length} packages in parallel: ${currentLevel.join(', ')}...`);
426
+ }
427
+ // Execute all packages in this level in parallel
428
+ const levelPromises = currentLevel.map((packageName)=>{
429
+ const packageInfo = dependencyGraph.packages.get(packageName);
430
+ const globalIndex = buildOrder.indexOf(packageName);
431
+ return executePackage(packageName, packageInfo, commandToRun, shouldPublish, runConfig, isDryRun, globalIndex, buildOrder.length);
432
+ });
433
+ // Wait for all packages in this level to complete
434
+ const results = await Promise.allSettled(levelPromises);
435
+ // Check results and handle errors
436
+ for(let i = 0; i < results.length; i++){
437
+ const result = results[i];
438
+ const packageName = currentLevel[i];
439
+ const globalIndex = buildOrder.indexOf(packageName);
440
+ const packageLogger = createPackageLogger(packageName, globalIndex + 1, buildOrder.length, isDryRun);
441
+ if (result.status === 'fulfilled') {
442
+ if (result.value.success) {
443
+ successCount++;
308
444
  } else {
309
- await run(commandToRun); // Non-null assertion since we're inside if (commandToRun)
445
+ // Package failed
446
+ failedPackage = packageName;
447
+ const formattedError = formatSubprojectError(packageName, result.value.error);
448
+ if (!isDryRun) {
449
+ packageLogger.error(`Execution failed`);
450
+ logger.error(formattedError);
451
+ logger.error(`Failed after ${successCount} successful packages.`);
452
+ const packageDir = dependencyGraph.packages.get(packageName).path;
453
+ const packageDirName = path.basename(packageDir);
454
+ logger.error(`To resume from this package, run:`);
455
+ logger.error(` kodrdriv publish-tree --start-from ${packageDirName}`);
456
+ throw new Error(`Script failed in package ${packageName}`);
457
+ }
458
+ break;
310
459
  }
311
- successCount++;
312
- logger.info(`✅ [${i + 1}/${buildOrder.length}] ${packageName} completed successfully`);
313
- } finally{
314
- process.chdir(originalCwd);
460
+ } else {
461
+ // Promise was rejected
462
+ failedPackage = packageName;
463
+ if (!isDryRun) {
464
+ packageLogger.error(`Unexpected error: ${result.reason}`);
465
+ logger.error(`Failed after ${successCount} successful packages.`);
466
+ const packageDir = dependencyGraph.packages.get(packageName).path;
467
+ const packageDirName = path.basename(packageDir);
468
+ logger.error(`To resume from this package, run:`);
469
+ logger.error(` kodrdriv publish-tree --start-from ${packageDirName}`);
470
+ throw new Error(`Unexpected error in package ${packageName}`);
471
+ }
472
+ break;
315
473
  }
316
474
  }
317
- } catch (error) {
318
- failedPackage = packageName;
319
- // Format the subproject error with proper indentation
320
- const formattedError = formatSubprojectError(packageName, error);
321
- if (!isDryRun) {
322
- // Log the formatted subproject error first
323
- logger.error(formattedError);
324
- logger.error(`Failed after ${successCount} successful packages.`);
325
- // Show recovery command last (most important info)
326
- const packageDirName = path.basename(packageDir);
327
- logger.error(`To resume from this package, run:`);
328
- logger.error(` kodrdriv publish-tree --start-from ${packageDirName}`);
329
- // Create a concise error for the throw
330
- throw new Error(`Script failed in package ${packageName}`);
475
+ // If any package failed, stop execution
476
+ if (failedPackage) {
477
+ break;
478
+ }
479
+ if (currentLevel.length > 1) {
480
+ logger.info(`✅ Level ${levelIndex + 1} completed: all ${currentLevel.length} packages finished successfully`);
481
+ } else if (currentLevel.length === 1 && successCount > 0) {
482
+ const packageName = currentLevel[0];
483
+ const globalIndex = buildOrder.indexOf(packageName);
484
+ const packageLogger = createPackageLogger(packageName, globalIndex + 1, buildOrder.length, isDryRun);
485
+ packageLogger.info(`✅ Level ${levelIndex + 1} completed successfully`);
486
+ }
487
+ }
488
+ } else {
489
+ // Sequential execution (original logic)
490
+ for(let i = 0; i < buildOrder.length; i++){
491
+ const packageName = buildOrder[i];
492
+ const packageInfo = dependencyGraph.packages.get(packageName);
493
+ const packageLogger = createPackageLogger(packageName, i + 1, buildOrder.length, isDryRun);
494
+ const result = await executePackage(packageName, packageInfo, commandToRun, shouldPublish, runConfig, isDryRun, i, buildOrder.length);
495
+ if (result.success) {
496
+ successCount++;
497
+ } else {
498
+ failedPackage = packageName;
499
+ const formattedError = formatSubprojectError(packageName, result.error);
500
+ if (!isDryRun) {
501
+ packageLogger.error(`Execution failed`);
502
+ logger.error(formattedError);
503
+ logger.error(`Failed after ${successCount} successful packages.`);
504
+ const packageDir = packageInfo.path;
505
+ const packageDirName = path.basename(packageDir);
506
+ logger.error(`To resume from this package, run:`);
507
+ logger.error(` kodrdriv publish-tree --start-from ${packageDirName}`);
508
+ throw new Error(`Script failed in package ${packageName}`);
509
+ }
510
+ break;
331
511
  }
332
- break;
333
512
  }
334
513
  }
335
514
  if (!failedPackage) {