@eldrforge/kodrdriv 1.2.1 → 1.2.3

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.
Files changed (40) hide show
  1. package/dist/application.js +7 -19
  2. package/dist/application.js.map +1 -1
  3. package/dist/arguments.js +345 -96
  4. package/dist/arguments.js.map +1 -1
  5. package/dist/commands/clean.js +1 -1
  6. package/dist/commands/clean.js.map +1 -1
  7. package/dist/commands/commit.js +59 -14
  8. package/dist/commands/commit.js.map +1 -1
  9. package/dist/commands/link.js +200 -11
  10. package/dist/commands/link.js.map +1 -1
  11. package/dist/commands/release.js +9 -3
  12. package/dist/commands/release.js.map +1 -1
  13. package/dist/commands/review.js +356 -145
  14. package/dist/commands/review.js.map +1 -1
  15. package/dist/commands/tree.js +111 -250
  16. package/dist/commands/tree.js.map +1 -1
  17. package/dist/commands/unlink.js +238 -10
  18. package/dist/commands/unlink.js.map +1 -1
  19. package/dist/constants.js +28 -12
  20. package/dist/constants.js.map +1 -1
  21. package/dist/content/issues.js +1 -1
  22. package/dist/error/CommandErrors.js +8 -1
  23. package/dist/error/CommandErrors.js.map +1 -1
  24. package/dist/prompt/commit.js +6 -0
  25. package/dist/prompt/commit.js.map +1 -1
  26. package/dist/prompt/instructions/review.md +13 -3
  27. package/dist/util/openai.js +70 -9
  28. package/dist/util/openai.js.map +1 -1
  29. package/dist/util/validation.js +24 -1
  30. package/dist/util/validation.js.map +1 -1
  31. package/package.json +2 -2
  32. package/test-external-unlink/package.json +16 -0
  33. package/test-externals/package.json +8 -0
  34. package/test-review-flow.sh +15 -0
  35. package/test-sort-files/alpha.md +3 -0
  36. package/test-sort-files/middle.txt +3 -0
  37. package/test-sort-files/zebra.txt +3 -0
  38. package/test_output.txt +161 -0
  39. package/dist/types.js +0 -140
  40. package/dist/types.js.map +0 -1
@@ -8,7 +8,9 @@ import { create } from '../util/storage.js';
8
8
  import { safeJsonParse, validatePackageJson } from '../util/validation.js';
9
9
  import { getOutputPath } from '../util/general.js';
10
10
  import { DEFAULT_OUTPUT_DIRECTORY } from '../constants.js';
11
- import { execute as execute$1 } from './commit.js';
11
+ import { execute as execute$3 } from './commit.js';
12
+ import { execute as execute$1 } from './link.js';
13
+ import { execute as execute$2 } from './unlink.js';
12
14
  import { getGloballyLinkedPackages, getGitStatusSummary, getLinkedDependencies, getLinkCompatibilityProblems } from '../util/git.js';
13
15
 
14
16
  function _define_property(obj, key, value) {
@@ -495,61 +497,6 @@ const topologicalSort = (graph)=>{
495
497
  logger.verbose(`Topological sort completed. Build order determined for ${result.length} packages.`);
496
498
  return result;
497
499
  };
498
- // Group packages into dependency levels for parallel execution
499
- const groupPackagesByDependencyLevels = (graph, buildOrder, runConfig)=>{
500
- const logger = getLogger();
501
- const { edges } = graph;
502
- const levels = [];
503
- const packageLevels = new Map();
504
- // Calculate the dependency level for each package
505
- const calculateLevel = (packageName)=>{
506
- if (packageLevels.has(packageName)) {
507
- return packageLevels.get(packageName);
508
- }
509
- const deps = edges.get(packageName) || new Set();
510
- if (deps.size === 0) {
511
- // No dependencies - this is level 0
512
- packageLevels.set(packageName, 0);
513
- if (runConfig === null || runConfig === void 0 ? void 0 : runConfig.debug) {
514
- logger.debug(`${packageName}: Level 0 (no local dependencies)`);
515
- }
516
- return 0;
517
- }
518
- // Level is 1 + max level of dependencies
519
- let maxDepLevel = -1;
520
- for (const dep of deps){
521
- const depLevel = calculateLevel(dep);
522
- maxDepLevel = Math.max(maxDepLevel, depLevel);
523
- }
524
- const level = maxDepLevel + 1;
525
- packageLevels.set(packageName, level);
526
- if (runConfig === null || runConfig === void 0 ? void 0 : runConfig.debug) {
527
- const depsList = Array.from(deps).join(', ');
528
- logger.debug(`${packageName}: Level ${level} (depends on: ${depsList})`);
529
- }
530
- return level;
531
- };
532
- // Calculate levels for all packages
533
- for (const packageName of buildOrder){
534
- calculateLevel(packageName);
535
- }
536
- // Group packages by their levels
537
- for (const packageName of buildOrder){
538
- const level = packageLevels.get(packageName);
539
- while(levels.length <= level){
540
- levels.push([]);
541
- }
542
- levels[level].push(packageName);
543
- }
544
- // Only show grouping info if verbose or debug mode is enabled
545
- if ((runConfig === null || runConfig === void 0 ? void 0 : runConfig.verbose) || (runConfig === null || runConfig === void 0 ? void 0 : runConfig.debug)) {
546
- logger.verbose(`Packages grouped into ${levels.length} dependency levels for parallel execution`);
547
- for(let i = 0; i < levels.length; i++){
548
- logger.verbose(` Level ${i}: ${levels[i].join(', ')}`);
549
- }
550
- }
551
- return levels;
552
- };
553
500
  // Execute a single package and return execution result
554
501
  const executePackage = async (packageName, packageInfo, commandToRun, runConfig, isDryRun, index, total, allPackageNames, isBuiltInCommand = false)=>{
555
502
  const packageLogger = createPackageLogger(packageName, index + 1, total, isDryRun);
@@ -573,7 +520,7 @@ const executePackage = async (packageName, packageInfo, commandToRun, runConfig,
573
520
  logger.info(`[${index + 1}/${total}] ${packageName}: Running ${commandToRun}...`);
574
521
  }
575
522
  try {
576
- if (isDryRun) {
523
+ if (isDryRun && !isBuiltInCommand) {
577
524
  // Handle inter-project dependency updates for publish commands in dry run mode
578
525
  if (isBuiltInCommand && commandToRun.includes('publish') && publishedVersions.length > 0) {
579
526
  let mutexLocked = false;
@@ -617,15 +564,15 @@ const executePackage = async (packageName, packageInfo, commandToRun, runConfig,
617
564
  if (runConfig.debug) {
618
565
  packageLogger.debug(`Changed to directory: ${packageDir}`);
619
566
  }
620
- // Handle inter-project dependency updates for publish commands before executing
621
- if (isBuiltInCommand && commandToRun.includes('publish') && publishedVersions.length > 0) {
567
+ // Handle inter-project dependency updates for publish commands before executing (skip during dry run)
568
+ if (!isDryRun && isBuiltInCommand && commandToRun.includes('publish') && publishedVersions.length > 0) {
622
569
  packageLogger.info('Updating inter-project dependencies based on previously published packages...');
623
570
  const hasUpdates = await updateInterProjectDependencies(packageDir, publishedVersions, allPackageNames, packageLogger, isDryRun);
624
571
  if (hasUpdates) {
625
572
  // Commit the dependency updates using kodrdriv commit
626
573
  packageLogger.info('Committing inter-project dependency updates...');
627
574
  try {
628
- await execute$1({
575
+ await execute$3({
629
576
  ...runConfig,
630
577
  dryRun: false
631
578
  });
@@ -651,14 +598,16 @@ const executePackage = async (packageName, packageInfo, commandToRun, runConfig,
651
598
  if (runConfig.debug) {
652
599
  packageLogger.debug(`Shelling out to separate kodrdriv process for ${builtInCommandName} command`);
653
600
  }
601
+ // Ensure dry-run propagates to subprocess even during overall dry-run mode
602
+ const effectiveCommand = runConfig.dryRun && !commandToRun.includes('--dry-run') ? `${commandToRun} --dry-run` : commandToRun;
654
603
  // Use runWithLogging for built-in commands to capture all output
655
- await runWithLogging(commandToRun, packageLogger, {}, showOutput);
604
+ await runWithLogging(effectiveCommand, packageLogger, {}, showOutput);
656
605
  } else {
657
606
  // For custom commands, use the existing logic
658
607
  await runWithLogging(commandToRun, packageLogger, {}, showOutput);
659
608
  }
660
- // Track published version after successful publish
661
- if (isBuiltInCommand && commandToRun.includes('publish')) {
609
+ // Track published version after successful publish (skip during dry run)
610
+ if (!isDryRun && isBuiltInCommand && commandToRun.includes('publish')) {
662
611
  const publishedVersion = await extractPublishedVersion(packageDir, packageLogger);
663
612
  if (publishedVersion) {
664
613
  let mutexLocked = false;
@@ -717,7 +666,7 @@ const executePackage = async (packageName, packageInfo, commandToRun, runConfig,
717
666
  }
718
667
  };
719
668
  const execute = async (runConfig)=>{
720
- var _runConfig_tree, _runConfig_tree1, _runConfig_tree2;
669
+ var _runConfig_tree, _runConfig_tree1, _runConfig_tree2, _runConfig_tree3, _runConfig_tree4;
721
670
  const logger = getLogger();
722
671
  const isDryRun = runConfig.dryRun || false;
723
672
  const isContinue = ((_runConfig_tree = runConfig.tree) === null || _runConfig_tree === void 0 ? void 0 : _runConfig_tree.continue) || false;
@@ -772,32 +721,72 @@ const execute = async (runConfig)=>{
772
721
  throw new Error(`Unsupported built-in command: ${builtInCommand}. Supported commands: ${supportedBuiltInCommands.join(', ')}`);
773
722
  }
774
723
  // Determine the target directories - either specified or current working directory
775
- const targetDirectories = ((_runConfig_tree2 = runConfig.tree) === null || _runConfig_tree2 === void 0 ? void 0 : _runConfig_tree2.directories) || [
724
+ const directories = ((_runConfig_tree2 = runConfig.tree) === null || _runConfig_tree2 === void 0 ? void 0 : _runConfig_tree2.directories) || [
776
725
  process.cwd()
777
726
  ];
778
- if (targetDirectories.length === 1) {
779
- logger.info(`${isDryRun ? 'DRY RUN: ' : ''}Analyzing workspace at: ${targetDirectories[0]}`);
727
+ // Handle link status subcommand
728
+ if (builtInCommand === 'link' && ((_runConfig_tree3 = runConfig.tree) === null || _runConfig_tree3 === void 0 ? void 0 : _runConfig_tree3.packageArgument) === 'status') {
729
+ // For tree link status, we want to show status across all packages
730
+ logger.info(`${isDryRun ? 'DRY RUN: ' : ''}Running link status across workspace...`);
731
+ // Create a config that will be passed to the link command
732
+ const linkConfig = {
733
+ ...runConfig,
734
+ tree: {
735
+ ...runConfig.tree,
736
+ directories: directories
737
+ }
738
+ };
739
+ try {
740
+ const result = await execute$1(linkConfig, 'status');
741
+ return result;
742
+ } catch (error) {
743
+ logger.error(`Link status failed: ${error.message}`);
744
+ throw error;
745
+ }
746
+ }
747
+ // Handle unlink status subcommand
748
+ if (builtInCommand === 'unlink' && ((_runConfig_tree4 = runConfig.tree) === null || _runConfig_tree4 === void 0 ? void 0 : _runConfig_tree4.packageArgument) === 'status') {
749
+ // For tree unlink status, we want to show status across all packages
750
+ logger.info(`${isDryRun ? 'DRY RUN: ' : ''}Running unlink status across workspace...`);
751
+ // Create a config that will be passed to the unlink command
752
+ const unlinkConfig = {
753
+ ...runConfig,
754
+ tree: {
755
+ ...runConfig.tree,
756
+ directories: directories
757
+ }
758
+ };
759
+ try {
760
+ const result = await execute$2(unlinkConfig, 'status');
761
+ return result;
762
+ } catch (error) {
763
+ logger.error(`Unlink status failed: ${error.message}`);
764
+ throw error;
765
+ }
766
+ }
767
+ if (directories.length === 1) {
768
+ logger.info(`${isDryRun ? 'DRY RUN: ' : ''}Analyzing workspace at: ${directories[0]}`);
780
769
  } else {
781
- logger.info(`${isDryRun ? 'DRY RUN: ' : ''}Analyzing workspaces at: ${targetDirectories.join(', ')}`);
770
+ logger.info(`${isDryRun ? 'DRY RUN: ' : ''}Analyzing workspaces at: ${directories.join(', ')}`);
782
771
  }
783
772
  try {
784
- var _runConfig_tree3, _runConfig_tree4, _runConfig_tree5, _runConfig_tree6, _runConfig_tree7;
773
+ var _runConfig_tree5, _runConfig_tree6, _runConfig_tree7, _runConfig_tree8;
785
774
  // Get exclusion patterns from config, fallback to empty array
786
- const excludedPatterns = ((_runConfig_tree3 = runConfig.tree) === null || _runConfig_tree3 === void 0 ? void 0 : _runConfig_tree3.excludedPatterns) || [];
775
+ const excludedPatterns = ((_runConfig_tree5 = runConfig.tree) === null || _runConfig_tree5 === void 0 ? void 0 : _runConfig_tree5.exclude) || [];
787
776
  if (excludedPatterns.length > 0) {
788
777
  logger.verbose(`${isDryRun ? 'DRY RUN: ' : ''}Using exclusion patterns: ${excludedPatterns.join(', ')}`);
789
778
  }
790
779
  // Scan for package.json files across all directories
791
780
  logger.verbose(`${isDryRun ? 'DRY RUN: ' : ''}Scanning for package.json files...`);
792
781
  let allPackageJsonPaths = [];
793
- for (const targetDirectory of targetDirectories){
782
+ for (const targetDirectory of directories){
794
783
  logger.verbose(`${isDryRun ? 'DRY RUN: ' : ''}Scanning directory: ${targetDirectory}`);
795
784
  const packageJsonPaths = await scanForPackageJsonFiles(targetDirectory, excludedPatterns);
796
785
  allPackageJsonPaths = allPackageJsonPaths.concat(packageJsonPaths);
797
786
  }
798
787
  const packageJsonPaths = allPackageJsonPaths;
799
788
  if (packageJsonPaths.length === 0) {
800
- const directoriesStr = targetDirectories.join(', ');
789
+ const directoriesStr = directories.join(', ');
801
790
  const message = `No package.json files found in subdirectories of: ${directoriesStr}`;
802
791
  logger.warn(message);
803
792
  return message;
@@ -810,7 +799,7 @@ const execute = async (runConfig)=>{
810
799
  logger.verbose(`${isDryRun ? 'DRY RUN: ' : ''}Determining build order...`);
811
800
  let buildOrder = topologicalSort(dependencyGraph);
812
801
  // Handle start-from functionality if specified
813
- const startFrom = (_runConfig_tree4 = runConfig.tree) === null || _runConfig_tree4 === void 0 ? void 0 : _runConfig_tree4.startFrom;
802
+ const startFrom = (_runConfig_tree6 = runConfig.tree) === null || _runConfig_tree6 === void 0 ? void 0 : _runConfig_tree6.startFrom;
814
803
  if (startFrom) {
815
804
  logger.verbose(`${isDryRun ? 'DRY RUN: ' : ''}Looking for start package: ${startFrom}`);
816
805
  // Find the package that matches the startFrom directory name
@@ -822,7 +811,7 @@ const execute = async (runConfig)=>{
822
811
  if (startIndex === -1) {
823
812
  // Check if the package exists but was excluded across all directories
824
813
  let allPackageJsonPathsForCheck = [];
825
- for (const targetDirectory of targetDirectories){
814
+ for (const targetDirectory of directories){
826
815
  const packageJsonPaths = await scanForPackageJsonFiles(targetDirectory, []); // No exclusions
827
816
  allPackageJsonPathsForCheck = allPackageJsonPathsForCheck.concat(packageJsonPaths);
828
817
  }
@@ -860,7 +849,7 @@ const execute = async (runConfig)=>{
860
849
  }
861
850
  }
862
851
  // Handle stop-at functionality if specified
863
- const stopAt = (_runConfig_tree5 = runConfig.tree) === null || _runConfig_tree5 === void 0 ? void 0 : _runConfig_tree5.stopAt;
852
+ const stopAt = (_runConfig_tree7 = runConfig.tree) === null || _runConfig_tree7 === void 0 ? void 0 : _runConfig_tree7.stopAt;
864
853
  if (stopAt) {
865
854
  logger.verbose(`${isDryRun ? 'DRY RUN: ' : ''}Looking for stop package: ${stopAt}`);
866
855
  // Find the package that matches the stopAt directory name
@@ -872,7 +861,7 @@ const execute = async (runConfig)=>{
872
861
  if (stopIndex === -1) {
873
862
  // Check if the package exists but was excluded across all directories
874
863
  let allPackageJsonPathsForCheck = [];
875
- for (const targetDirectory of targetDirectories){
864
+ for (const targetDirectory of directories){
876
865
  const packageJsonPaths = await scanForPackageJsonFiles(targetDirectory, []); // No exclusions
877
866
  allPackageJsonPathsForCheck = allPackageJsonPathsForCheck.concat(packageJsonPaths);
878
867
  }
@@ -1219,13 +1208,12 @@ const execute = async (runConfig)=>{
1219
1208
  returnOutput = `\nBuild order: ${buildOrder.join(' → ')}\n`;
1220
1209
  }
1221
1210
  // Execute command if provided (custom command or built-in command)
1222
- const cmd = (_runConfig_tree6 = runConfig.tree) === null || _runConfig_tree6 === void 0 ? void 0 : _runConfig_tree6.cmd;
1223
- const useParallel = ((_runConfig_tree7 = runConfig.tree) === null || _runConfig_tree7 === void 0 ? void 0 : _runConfig_tree7.parallel) || false;
1211
+ const cmd = (_runConfig_tree8 = runConfig.tree) === null || _runConfig_tree8 === void 0 ? void 0 : _runConfig_tree8.cmd;
1224
1212
  // Determine command to execute
1225
1213
  let commandToRun;
1226
1214
  let isBuiltInCommand = false;
1227
1215
  if (builtInCommand) {
1228
- var _runConfig_tree8, _runConfig_tree9;
1216
+ var _runConfig_tree9, _runConfig_tree10, _runConfig_tree11;
1229
1217
  // Built-in command mode: shell out to kodrdriv subprocess
1230
1218
  // Build command with propagated global options
1231
1219
  const globalOptions = [];
@@ -1242,13 +1230,16 @@ const execute = async (runConfig)=>{
1242
1230
  // Build the command with global options
1243
1231
  const optionsString = globalOptions.length > 0 ? ` ${globalOptions.join(' ')}` : '';
1244
1232
  // Add package argument for link/unlink commands
1245
- const packageArg = (_runConfig_tree8 = runConfig.tree) === null || _runConfig_tree8 === void 0 ? void 0 : _runConfig_tree8.packageArgument;
1233
+ const packageArg = (_runConfig_tree9 = runConfig.tree) === null || _runConfig_tree9 === void 0 ? void 0 : _runConfig_tree9.packageArgument;
1246
1234
  const packageArgString = packageArg && (builtInCommand === 'link' || builtInCommand === 'unlink') ? ` "${packageArg}"` : '';
1247
1235
  // Add command-specific options
1248
1236
  let commandSpecificOptions = '';
1249
- if (builtInCommand === 'unlink' && ((_runConfig_tree9 = runConfig.tree) === null || _runConfig_tree9 === void 0 ? void 0 : _runConfig_tree9.cleanNodeModules)) {
1237
+ if (builtInCommand === 'unlink' && ((_runConfig_tree10 = runConfig.tree) === null || _runConfig_tree10 === void 0 ? void 0 : _runConfig_tree10.cleanNodeModules)) {
1250
1238
  commandSpecificOptions += ' --clean-node-modules';
1251
1239
  }
1240
+ if ((builtInCommand === 'link' || builtInCommand === 'unlink') && ((_runConfig_tree11 = runConfig.tree) === null || _runConfig_tree11 === void 0 ? void 0 : _runConfig_tree11.externals) && runConfig.tree.externals.length > 0) {
1241
+ commandSpecificOptions += ` --externals ${runConfig.tree.externals.join(' ')}`;
1242
+ }
1252
1243
  commandToRun = `kodrdriv ${builtInCommand}${optionsString}${packageArgString}${commandSpecificOptions}`;
1253
1244
  isBuiltInCommand = true;
1254
1245
  } else if (cmd) {
@@ -1277,8 +1268,7 @@ const execute = async (runConfig)=>{
1277
1268
  // Add spacing before command execution
1278
1269
  logger.info('');
1279
1270
  const executionDescription = isBuiltInCommand ? `built-in command "${builtInCommand}"` : `"${commandToRun}"`;
1280
- const parallelInfo = useParallel ? ' (with parallel execution)' : '';
1281
- logger.info(`${isDryRun ? 'DRY RUN: ' : ''}Executing ${executionDescription} in ${buildOrder.length} packages${parallelInfo}...`);
1271
+ logger.info(`${isDryRun ? 'DRY RUN: ' : ''}Executing ${executionDescription} in ${buildOrder.length} packages...`);
1282
1272
  // Show info for publish commands
1283
1273
  if (isBuiltInCommand && builtInCommand === 'publish') {
1284
1274
  logger.info('Inter-project dependencies will be automatically updated before each publish.');
@@ -1287,176 +1277,47 @@ const execute = async (runConfig)=>{
1287
1277
  let failedPackage = null;
1288
1278
  // If continuing, start from where we left off
1289
1279
  const startIndex = isContinue && executionContext ? executionContext.completedPackages.length : 0;
1290
- if (useParallel) {
1291
- // Parallel execution: group packages by dependency levels
1292
- const dependencyLevels = groupPackagesByDependencyLevels(dependencyGraph, buildOrder, runConfig);
1293
- if (runConfig.debug) {
1294
- logger.debug(`Parallel execution strategy: ${dependencyLevels.length} dependency levels identified`);
1295
- for(let i = 0; i < dependencyLevels.length; i++){
1296
- const level = dependencyLevels[i];
1297
- logger.debug(` Level ${i + 1}: ${level.join(', ')} ${level.length > 1 ? '(parallel)' : '(sequential)'}`);
1298
- }
1280
+ // Sequential execution
1281
+ for(let i = startIndex; i < buildOrder.length; i++){
1282
+ const packageName = buildOrder[i];
1283
+ // Skip if already completed (in continue mode)
1284
+ if (executionContext && executionContext.completedPackages.includes(packageName)) {
1285
+ successCount++;
1286
+ continue;
1299
1287
  }
1300
- for(let levelIndex = 0; levelIndex < dependencyLevels.length; levelIndex++){
1301
- const currentLevel = dependencyLevels[levelIndex];
1302
- if (runConfig.debug) {
1303
- if (currentLevel.length === 1) {
1304
- const packageName = currentLevel[0];
1305
- logger.debug(`Starting Level ${levelIndex + 1}: ${packageName} (no dependencies within this level)`);
1306
- } else {
1307
- logger.debug(`Starting Level ${levelIndex + 1}: ${currentLevel.length} packages can run in parallel`);
1308
- logger.debug(` Parallel packages: ${currentLevel.join(', ')}`);
1309
- }
1310
- } else if (runConfig.verbose) {
1311
- if (currentLevel.length === 1) {
1312
- const packageName = currentLevel[0];
1313
- logger.verbose(`Level ${levelIndex + 1}: Executing ${packageName}...`);
1314
- } else {
1315
- logger.verbose(`Level ${levelIndex + 1}: Executing ${currentLevel.length} packages in parallel: ${currentLevel.join(', ')}...`);
1316
- }
1317
- } else {
1318
- // Basic level info
1319
- if (currentLevel.length === 1) {
1320
- const packageName = currentLevel[0];
1321
- logger.info(`${isDryRun ? 'DRY RUN: ' : ''}Level ${levelIndex + 1}: Executing ${packageName}...`);
1322
- } else {
1323
- logger.info(`${isDryRun ? 'DRY RUN: ' : ''}Level ${levelIndex + 1}: Executing ${currentLevel.length} packages in parallel: ${currentLevel.join(', ')}...`);
1324
- }
1325
- }
1326
- // Execute all packages in this level in parallel
1327
- const levelPromises = currentLevel.map((packageName)=>{
1328
- const packageInfo = dependencyGraph.packages.get(packageName);
1329
- const globalIndex = buildOrder.indexOf(packageName);
1330
- return executePackage(packageName, packageInfo, commandToRun, runConfig, isDryRun, globalIndex, buildOrder.length, allPackageNames, isBuiltInCommand);
1331
- });
1332
- // Wait for all packages in this level to complete
1333
- const results = await Promise.allSettled(levelPromises);
1334
- // Check results and handle errors
1335
- for(let i = 0; i < results.length; i++){
1336
- const result = results[i];
1337
- const packageName = currentLevel[i];
1338
- const globalIndex = buildOrder.indexOf(packageName);
1339
- const packageLogger = createPackageLogger(packageName, globalIndex + 1, buildOrder.length, isDryRun);
1340
- if (result.status === 'fulfilled') {
1341
- if (result.value.success) {
1342
- successCount++;
1343
- // Add spacing between packages (except after the last one in the level)
1344
- if (i < currentLevel.length - 1) {
1345
- logger.info('');
1346
- logger.info('');
1347
- }
1348
- } else {
1349
- // Package failed
1350
- failedPackage = packageName;
1351
- const formattedError = formatSubprojectError(packageName, result.value.error);
1352
- if (!isDryRun) {
1353
- packageLogger.error(`Execution failed`);
1354
- logger.error(formattedError);
1355
- logger.error(`Failed after ${successCount} successful packages.`);
1356
- const packageDir = dependencyGraph.packages.get(packageName).path;
1357
- const packageDirName = path__default.basename(packageDir);
1358
- logger.error(`To resume from this package, run:`);
1359
- if (isBuiltInCommand) {
1360
- logger.error(` kodrdriv tree ${builtInCommand} --start-from ${packageDirName}`);
1361
- } else {
1362
- logger.error(` kodrdriv tree --start-from ${packageDirName} --cmd "${commandToRun}"`);
1363
- }
1364
- throw new Error(`Command failed in package ${packageName}`);
1365
- }
1366
- break;
1367
- }
1368
- } else {
1369
- // Promise was rejected
1370
- failedPackage = packageName;
1371
- if (!isDryRun) {
1372
- packageLogger.error(`Unexpected error: ${result.reason}`);
1373
- logger.error(`Failed after ${successCount} successful packages.`);
1374
- const packageDir = dependencyGraph.packages.get(packageName).path;
1375
- const packageDirName = path__default.basename(packageDir);
1376
- logger.error(`To resume from this package, run:`);
1377
- if (isBuiltInCommand) {
1378
- logger.error(` kodrdriv tree ${builtInCommand} --start-from ${packageDirName}`);
1379
- } else {
1380
- logger.error(` kodrdriv tree --start-from ${packageDirName} --cmd "${commandToRun}"`);
1381
- }
1382
- throw new Error(`Unexpected error in package ${packageName}`);
1383
- }
1384
- break;
1385
- }
1386
- }
1387
- // If any package failed, stop execution
1388
- if (failedPackage) {
1389
- break;
1390
- }
1391
- // Level completion logging
1392
- if (runConfig.debug) {
1393
- if (currentLevel.length > 1) {
1394
- logger.debug(`✅ Level ${levelIndex + 1} completed: all ${currentLevel.length} packages finished successfully`);
1395
- logger.debug(` Completed packages: ${currentLevel.join(', ')}`);
1396
- } else if (currentLevel.length === 1 && successCount > 0) {
1397
- const packageName = currentLevel[0];
1398
- logger.debug(`✅ Level ${levelIndex + 1} completed: ${packageName} finished successfully`);
1399
- }
1400
- } else if (runConfig.verbose) {
1401
- if (currentLevel.length > 1) {
1402
- logger.verbose(`✅ Level ${levelIndex + 1} completed: all ${currentLevel.length} packages finished successfully`);
1403
- } else if (currentLevel.length === 1 && successCount > 0) {
1404
- const packageName = currentLevel[0];
1405
- logger.verbose(`✅ Level ${levelIndex + 1} completed: ${packageName} finished successfully`);
1406
- }
1407
- } else {
1408
- // Basic completion info
1409
- if (currentLevel.length > 1) {
1410
- logger.info(`✅ Level ${levelIndex + 1} completed: all ${currentLevel.length} packages finished successfully`);
1411
- } else if (currentLevel.length === 1 && successCount > 0) {
1412
- const packageName = currentLevel[0];
1413
- logger.info(`✅ Level ${levelIndex + 1} completed: ${packageName} finished successfully`);
1414
- }
1288
+ const packageInfo = dependencyGraph.packages.get(packageName);
1289
+ const packageLogger = createPackageLogger(packageName, i + 1, buildOrder.length, isDryRun);
1290
+ const result = await executePackage(packageName, packageInfo, commandToRun, runConfig, isDryRun, i, buildOrder.length, allPackageNames, isBuiltInCommand);
1291
+ if (result.success) {
1292
+ successCount++;
1293
+ // Update context
1294
+ if (executionContext && isBuiltInCommand && builtInCommand === 'publish' && !isDryRun) {
1295
+ executionContext.completedPackages.push(packageName);
1296
+ executionContext.publishedVersions = publishedVersions;
1297
+ executionContext.lastUpdateTime = new Date();
1298
+ await saveExecutionContext(executionContext, runConfig.outputDirectory);
1415
1299
  }
1416
- }
1417
- } else {
1418
- // Sequential execution
1419
- for(let i = startIndex; i < buildOrder.length; i++){
1420
- const packageName = buildOrder[i];
1421
- // Skip if already completed (in continue mode)
1422
- if (executionContext && executionContext.completedPackages.includes(packageName)) {
1423
- successCount++;
1424
- continue;
1300
+ // Add spacing between packages (except after the last one)
1301
+ if (i < buildOrder.length - 1) {
1302
+ logger.info('');
1303
+ logger.info('');
1425
1304
  }
1426
- const packageInfo = dependencyGraph.packages.get(packageName);
1427
- const packageLogger = createPackageLogger(packageName, i + 1, buildOrder.length, isDryRun);
1428
- const result = await executePackage(packageName, packageInfo, commandToRun, runConfig, isDryRun, i, buildOrder.length, allPackageNames, isBuiltInCommand);
1429
- if (result.success) {
1430
- successCount++;
1431
- // Update context
1432
- if (executionContext && isBuiltInCommand && builtInCommand === 'publish' && !isDryRun) {
1433
- executionContext.completedPackages.push(packageName);
1434
- executionContext.publishedVersions = publishedVersions;
1435
- executionContext.lastUpdateTime = new Date();
1436
- await saveExecutionContext(executionContext, runConfig.outputDirectory);
1437
- }
1438
- // Add spacing between packages (except after the last one)
1439
- if (i < buildOrder.length - 1) {
1440
- logger.info('');
1441
- logger.info('');
1442
- }
1443
- } else {
1444
- failedPackage = packageName;
1445
- const formattedError = formatSubprojectError(packageName, result.error);
1446
- if (!isDryRun) {
1447
- packageLogger.error(`Execution failed`);
1448
- logger.error(formattedError);
1449
- logger.error(`Failed after ${successCount} successful packages.`);
1450
- logger.error(`To resume from this point, run:`);
1451
- if (isBuiltInCommand) {
1452
- logger.error(` kodrdriv tree ${builtInCommand} --continue`);
1453
- } else {
1454
- logger.error(` kodrdriv tree --continue --cmd "${commandToRun}"`);
1455
- }
1456
- throw new Error(`Command failed in package ${packageName}`);
1305
+ } else {
1306
+ failedPackage = packageName;
1307
+ const formattedError = formatSubprojectError(packageName, result.error);
1308
+ if (!isDryRun) {
1309
+ packageLogger.error(`Execution failed`);
1310
+ logger.error(formattedError);
1311
+ logger.error(`Failed after ${successCount} successful packages.`);
1312
+ logger.error(`To resume from this point, run:`);
1313
+ if (isBuiltInCommand) {
1314
+ logger.error(` kodrdriv tree ${builtInCommand} --continue`);
1315
+ } else {
1316
+ logger.error(` kodrdriv tree --continue --cmd "${commandToRun}"`);
1457
1317
  }
1458
- break;
1318
+ throw new Error(`Command failed in package ${packageName}`);
1459
1319
  }
1320
+ break;
1460
1321
  }
1461
1322
  }
1462
1323
  if (!failedPackage) {
@@ -1475,8 +1336,8 @@ const execute = async (runConfig)=>{
1475
1336
  logger.error(errorMessage);
1476
1337
  throw new Error(errorMessage);
1477
1338
  } finally{
1478
- // Clean up mutex resources to prevent memory leaks
1479
- globalStateMutex.destroy();
1339
+ // Intentionally preserve the mutex across executions to support multiple runs in the same process (e.g., test suite)
1340
+ // Do not destroy here; the process lifecycle will clean up resources.
1480
1341
  }
1481
1342
  };
1482
1343