@eldrforge/kodrdriv 1.2.5 → 1.2.6

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.
@@ -227,6 +227,84 @@ const cleanupContext = async (outputDirectory)=>{
227
227
  logger.warn(`Warning: Failed to cleanup execution context: ${error.message}`);
228
228
  }
229
229
  };
230
+ // Helper function to promote a package to completed status in the context
231
+ const promotePackageToCompleted = async (packageName, outputDirectory)=>{
232
+ const storage = create({
233
+ log: ()=>{}
234
+ });
235
+ const contextFilePath = getContextFilePath(outputDirectory);
236
+ try {
237
+ if (!await storage.exists(contextFilePath)) {
238
+ return;
239
+ }
240
+ const contextContent = await storage.readFile(contextFilePath, 'utf-8');
241
+ const contextData = safeJsonParse(contextContent, contextFilePath);
242
+ // Restore dates from ISO strings
243
+ const context = {
244
+ ...contextData,
245
+ startTime: new Date(contextData.startTime),
246
+ lastUpdateTime: new Date(contextData.lastUpdateTime),
247
+ publishedVersions: contextData.publishedVersions.map((v)=>({
248
+ ...v,
249
+ publishTime: new Date(v.publishTime)
250
+ }))
251
+ };
252
+ // Add package to completed list if not already there
253
+ if (!context.completedPackages.includes(packageName)) {
254
+ context.completedPackages.push(packageName);
255
+ context.lastUpdateTime = new Date();
256
+ await saveExecutionContext(context, outputDirectory);
257
+ }
258
+ } catch (error) {
259
+ const logger = getLogger();
260
+ logger.warn(`Warning: Failed to promote package to completed: ${error.message}`);
261
+ }
262
+ };
263
+ // Helper function to validate that all packages have the required scripts
264
+ const validateScripts = async (packages, scripts)=>{
265
+ const logger = getLogger();
266
+ const missingScripts = new Map();
267
+ const storage = create({
268
+ log: ()=>{}
269
+ });
270
+ logger.debug(`Validating scripts: ${scripts.join(', ')}`);
271
+ for (const [packageName, packageInfo] of packages){
272
+ const packageJsonPath = path__default.join(packageInfo.path, 'package.json');
273
+ const missingForPackage = [];
274
+ try {
275
+ const packageJsonContent = await storage.readFile(packageJsonPath, 'utf-8');
276
+ const packageJson = safeJsonParse(packageJsonContent, packageJsonPath);
277
+ const validated = validatePackageJson(packageJson, packageJsonPath);
278
+ // Check if each required script exists
279
+ for (const script of scripts){
280
+ if (!validated.scripts || !validated.scripts[script]) {
281
+ missingForPackage.push(script);
282
+ }
283
+ }
284
+ if (missingForPackage.length > 0) {
285
+ missingScripts.set(packageName, missingForPackage);
286
+ logger.debug(`Package ${packageName} missing scripts: ${missingForPackage.join(', ')}`);
287
+ }
288
+ } catch (error) {
289
+ logger.debug(`Error reading package.json for ${packageName}: ${error.message}`);
290
+ // If we can't read the package.json, assume all scripts are missing
291
+ missingScripts.set(packageName, scripts);
292
+ }
293
+ }
294
+ const valid = missingScripts.size === 0;
295
+ if (valid) {
296
+ logger.info(`✅ All packages have the required scripts: ${scripts.join(', ')}`);
297
+ } else {
298
+ logger.error(`❌ Script validation failed. Missing scripts:`);
299
+ for (const [packageName, missing] of missingScripts){
300
+ logger.error(` ${packageName}: ${missing.join(', ')}`);
301
+ }
302
+ }
303
+ return {
304
+ valid,
305
+ missingScripts
306
+ };
307
+ };
230
308
  // Extract published version from package.json after successful publish
231
309
  const extractPublishedVersion = async (packageDir, packageLogger)=>{
232
310
  const storage = create({
@@ -318,7 +396,7 @@ const createPackageLogger = (packageName, sequenceNumber, totalCount, isDryRun =
318
396
  };
319
397
  };
320
398
  // Helper function to format subproject error output
321
- const formatSubprojectError = (packageName, error)=>{
399
+ const formatSubprojectError = (packageName, error, _packageInfo, _position, _total)=>{
322
400
  const lines = [];
323
401
  lines.push(`❌ Command failed in package ${packageName}:`);
324
402
  // Format the main error message with indentation
@@ -671,22 +749,36 @@ const executePackage = async (packageName, packageInfo, commandToRun, runConfig,
671
749
  success: true
672
750
  };
673
751
  } catch (error) {
752
+ var _error_message;
674
753
  if (runConfig.debug || runConfig.verbose) {
675
754
  packageLogger.error(`❌ Execution failed: ${error.message}`);
676
755
  } else {
677
756
  logger.error(`[${index + 1}/${total}] ${packageName}: ❌ Failed - ${error.message}`);
678
757
  }
758
+ // Check if this is a timeout error
759
+ const errorMessage = ((_error_message = error.message) === null || _error_message === void 0 ? void 0 : _error_message.toLowerCase()) || '';
760
+ const isTimeoutError = errorMessage && (errorMessage.includes('timeout waiting for pr') || errorMessage.includes('timeout waiting for release workflows') || errorMessage.includes('timeout reached') || errorMessage.includes('timeout') || errorMessage.includes('timed out') || errorMessage.includes('timed_out'));
679
761
  return {
680
762
  success: false,
681
- error
763
+ error,
764
+ isTimeoutError
682
765
  };
683
766
  }
684
767
  };
685
768
  const execute = async (runConfig)=>{
686
- var _runConfig_tree, _runConfig_tree1, _runConfig_tree2, _runConfig_tree3, _runConfig_tree4;
769
+ var _runConfig_tree, _runConfig_tree1, _runConfig_tree2, _runConfig_tree3, _runConfig_tree4, _runConfig_tree5;
687
770
  const logger = getLogger();
688
771
  const isDryRun = runConfig.dryRun || false;
689
772
  const isContinue = ((_runConfig_tree = runConfig.tree) === null || _runConfig_tree === void 0 ? void 0 : _runConfig_tree.continue) || false;
773
+ const promotePackage = (_runConfig_tree1 = runConfig.tree) === null || _runConfig_tree1 === void 0 ? void 0 : _runConfig_tree1.promote;
774
+ // Handle promote mode
775
+ if (promotePackage) {
776
+ logger.info(`Promoting package '${promotePackage}' to completed status...`);
777
+ await promotePackageToCompleted(promotePackage, runConfig.outputDirectory);
778
+ logger.info(`✅ Package '${promotePackage}' has been marked as completed.`);
779
+ logger.info('You can now run the tree command with --continue to resume from the next package.');
780
+ return `Package '${promotePackage}' promoted to completed status.`;
781
+ }
690
782
  // Handle continue mode
691
783
  if (isContinue) {
692
784
  const savedContext = await loadExecutionContext(runConfig.outputDirectory);
@@ -725,24 +817,50 @@ const execute = async (runConfig)=>{
725
817
  executionContext = null;
726
818
  }
727
819
  // Check if we're in built-in command mode (tree command with second argument)
728
- const builtInCommand = (_runConfig_tree1 = runConfig.tree) === null || _runConfig_tree1 === void 0 ? void 0 : _runConfig_tree1.builtInCommand;
820
+ const builtInCommand = (_runConfig_tree2 = runConfig.tree) === null || _runConfig_tree2 === void 0 ? void 0 : _runConfig_tree2.builtInCommand;
729
821
  const supportedBuiltInCommands = [
730
822
  'commit',
731
823
  'publish',
732
824
  'link',
733
825
  'unlink',
734
826
  'development',
735
- 'branches'
827
+ 'branches',
828
+ 'run'
736
829
  ];
737
830
  if (builtInCommand && !supportedBuiltInCommands.includes(builtInCommand)) {
738
831
  throw new Error(`Unsupported built-in command: ${builtInCommand}. Supported commands: ${supportedBuiltInCommands.join(', ')}`);
739
832
  }
833
+ // Handle run subcommand - convert space-separated scripts to npm run commands
834
+ if (builtInCommand === 'run') {
835
+ var _runConfig_tree6;
836
+ const packageArgument = (_runConfig_tree6 = runConfig.tree) === null || _runConfig_tree6 === void 0 ? void 0 : _runConfig_tree6.packageArgument;
837
+ if (!packageArgument) {
838
+ throw new Error('run subcommand requires script names. Usage: kodrdriv tree run "clean build test"');
839
+ }
840
+ // Split the package argument by spaces to get individual script names
841
+ const scripts = packageArgument.trim().split(/\s+/).filter((script)=>script.length > 0);
842
+ if (scripts.length === 0) {
843
+ throw new Error('run subcommand requires at least one script name. Usage: kodrdriv tree run "clean build test"');
844
+ }
845
+ // Convert to npm run commands joined with &&
846
+ const npmCommands = scripts.map((script)=>`npm run ${script}`).join(' && ');
847
+ // Set this as the custom command to run
848
+ runConfig.tree = {
849
+ ...runConfig.tree,
850
+ cmd: npmCommands
851
+ };
852
+ // Clear the built-in command since we're now using custom command mode
853
+ runConfig.tree.builtInCommand = undefined;
854
+ logger.info(`Converting run subcommand to: ${npmCommands}`);
855
+ // Store scripts for later validation
856
+ runConfig.__scriptsToValidate = scripts;
857
+ }
740
858
  // Determine the target directories - either specified or current working directory
741
- const directories = ((_runConfig_tree2 = runConfig.tree) === null || _runConfig_tree2 === void 0 ? void 0 : _runConfig_tree2.directories) || [
859
+ const directories = ((_runConfig_tree3 = runConfig.tree) === null || _runConfig_tree3 === void 0 ? void 0 : _runConfig_tree3.directories) || [
742
860
  process.cwd()
743
861
  ];
744
862
  // Handle link status subcommand
745
- if (builtInCommand === 'link' && ((_runConfig_tree3 = runConfig.tree) === null || _runConfig_tree3 === void 0 ? void 0 : _runConfig_tree3.packageArgument) === 'status') {
863
+ if (builtInCommand === 'link' && ((_runConfig_tree4 = runConfig.tree) === null || _runConfig_tree4 === void 0 ? void 0 : _runConfig_tree4.packageArgument) === 'status') {
746
864
  // For tree link status, we want to show status across all packages
747
865
  logger.info(`${isDryRun ? 'DRY RUN: ' : ''}Running link status across workspace...`);
748
866
  // Create a config that will be passed to the link command
@@ -762,7 +880,7 @@ const execute = async (runConfig)=>{
762
880
  }
763
881
  }
764
882
  // Handle unlink status subcommand
765
- if (builtInCommand === 'unlink' && ((_runConfig_tree4 = runConfig.tree) === null || _runConfig_tree4 === void 0 ? void 0 : _runConfig_tree4.packageArgument) === 'status') {
883
+ if (builtInCommand === 'unlink' && ((_runConfig_tree5 = runConfig.tree) === null || _runConfig_tree5 === void 0 ? void 0 : _runConfig_tree5.packageArgument) === 'status') {
766
884
  // For tree unlink status, we want to show status across all packages
767
885
  logger.info(`${isDryRun ? 'DRY RUN: ' : ''}Running unlink status across workspace...`);
768
886
  // Create a config that will be passed to the unlink command
@@ -787,9 +905,9 @@ const execute = async (runConfig)=>{
787
905
  logger.info(`${isDryRun ? 'DRY RUN: ' : ''}Analyzing workspaces at: ${directories.join(', ')}`);
788
906
  }
789
907
  try {
790
- var _runConfig_tree5, _runConfig_tree6, _runConfig_tree7, _runConfig_tree8;
908
+ var _runConfig_tree7, _runConfig_tree8, _runConfig_tree9, _runConfig_tree10;
791
909
  // Get exclusion patterns from config, fallback to empty array
792
- const excludedPatterns = ((_runConfig_tree5 = runConfig.tree) === null || _runConfig_tree5 === void 0 ? void 0 : _runConfig_tree5.exclude) || [];
910
+ const excludedPatterns = ((_runConfig_tree7 = runConfig.tree) === null || _runConfig_tree7 === void 0 ? void 0 : _runConfig_tree7.exclude) || [];
793
911
  if (excludedPatterns.length > 0) {
794
912
  logger.verbose(`${isDryRun ? 'DRY RUN: ' : ''}Using exclusion patterns: ${excludedPatterns.join(', ')}`);
795
913
  }
@@ -816,7 +934,7 @@ const execute = async (runConfig)=>{
816
934
  logger.verbose(`${isDryRun ? 'DRY RUN: ' : ''}Determining build order...`);
817
935
  let buildOrder = topologicalSort(dependencyGraph);
818
936
  // Handle start-from functionality if specified
819
- const startFrom = (_runConfig_tree6 = runConfig.tree) === null || _runConfig_tree6 === void 0 ? void 0 : _runConfig_tree6.startFrom;
937
+ const startFrom = (_runConfig_tree8 = runConfig.tree) === null || _runConfig_tree8 === void 0 ? void 0 : _runConfig_tree8.startFrom;
820
938
  if (startFrom) {
821
939
  logger.verbose(`${isDryRun ? 'DRY RUN: ' : ''}Looking for start package: ${startFrom}`);
822
940
  // Resolve the actual package name (can be package name or directory name)
@@ -862,63 +980,18 @@ const execute = async (runConfig)=>{
862
980
  throw new Error(`Package directory '${startFrom}' not found. Available packages: ${availablePackages}`);
863
981
  }
864
982
  }
865
- // Build reverse dependency map (who depends on whom)
866
- const reverseEdges = new Map();
867
- for (const [pkg, deps] of dependencyGraph.edges){
868
- for (const dep of deps){
869
- if (!reverseEdges.has(dep)) reverseEdges.set(dep, new Set());
870
- reverseEdges.get(dep).add(pkg);
871
- }
872
- if (!reverseEdges.has(pkg)) reverseEdges.set(pkg, new Set());
873
- }
874
- // Step 1: collect the start package and all its transitive dependents (consumers)
875
- const dependentsClosure = new Set();
876
- const queueDependents = [
877
- startPackageName
878
- ];
879
- while(queueDependents.length > 0){
880
- const current = queueDependents.shift();
881
- if (dependentsClosure.has(current)) continue;
882
- dependentsClosure.add(current);
883
- const consumers = reverseEdges.get(current) || new Set();
884
- for (const consumer of consumers){
885
- if (!dependentsClosure.has(consumer)) queueDependents.push(consumer);
886
- }
887
- }
888
- // Step 2: expand to include all forward dependencies required to build those packages
889
- const relevantPackages = new Set(dependentsClosure);
890
- const queueDependencies = Array.from(relevantPackages);
891
- while(queueDependencies.length > 0){
892
- const current = queueDependencies.shift();
893
- const deps = dependencyGraph.edges.get(current) || new Set();
894
- for (const dep of deps){
895
- if (!relevantPackages.has(dep)) {
896
- relevantPackages.add(dep);
897
- queueDependencies.push(dep);
898
- }
899
- }
900
- }
901
- // Filter graph to only relevant packages
902
- const filteredGraph = {
903
- packages: new Map(),
904
- edges: new Map()
905
- };
906
- for (const pkgName of relevantPackages){
907
- const info = dependencyGraph.packages.get(pkgName);
908
- filteredGraph.packages.set(pkgName, info);
909
- const deps = dependencyGraph.edges.get(pkgName) || new Set();
910
- const filteredDeps = new Set();
911
- for (const dep of deps){
912
- if (relevantPackages.has(dep)) filteredDeps.add(dep);
913
- }
914
- filteredGraph.edges.set(pkgName, filteredDeps);
983
+ // Find the start package in the build order and start execution from there
984
+ const startIndex = buildOrder.findIndex((pkgName)=>pkgName === startPackageName);
985
+ if (startIndex === -1) {
986
+ throw new Error(`Package '${startFrom}' not found in build order. This should not happen.`);
915
987
  }
916
- // Recompute build order for the filtered subgraph
917
- buildOrder = topologicalSort(filteredGraph);
918
- logger.info(`${isDryRun ? 'DRY RUN: ' : ''}Limiting scope to '${startFrom}' and its dependencies (${buildOrder.length} package${buildOrder.length === 1 ? '' : 's'}).`);
988
+ // Filter build order to start from the specified package
989
+ const originalLength = buildOrder.length;
990
+ buildOrder = buildOrder.slice(startIndex);
991
+ logger.info(`${isDryRun ? 'DRY RUN: ' : ''}Starting execution from package '${startFrom}' (${buildOrder.length} of ${originalLength} packages remaining).`);
919
992
  }
920
993
  // Handle stop-at functionality if specified
921
- const stopAt = (_runConfig_tree7 = runConfig.tree) === null || _runConfig_tree7 === void 0 ? void 0 : _runConfig_tree7.stopAt;
994
+ const stopAt = (_runConfig_tree9 = runConfig.tree) === null || _runConfig_tree9 === void 0 ? void 0 : _runConfig_tree9.stopAt;
922
995
  if (stopAt) {
923
996
  logger.verbose(`${isDryRun ? 'DRY RUN: ' : ''}Looking for stop package: ${stopAt}`);
924
997
  // Find the package that matches the stopAt directory name
@@ -1277,12 +1350,12 @@ const execute = async (runConfig)=>{
1277
1350
  returnOutput = `\nBuild order: ${buildOrder.join(' → ')}\n`;
1278
1351
  }
1279
1352
  // Execute command if provided (custom command or built-in command)
1280
- const cmd = (_runConfig_tree8 = runConfig.tree) === null || _runConfig_tree8 === void 0 ? void 0 : _runConfig_tree8.cmd;
1353
+ const cmd = (_runConfig_tree10 = runConfig.tree) === null || _runConfig_tree10 === void 0 ? void 0 : _runConfig_tree10.cmd;
1281
1354
  // Determine command to execute
1282
1355
  let commandToRun;
1283
1356
  let isBuiltInCommand = false;
1284
1357
  if (builtInCommand) {
1285
- var _runConfig_tree9, _runConfig_tree10, _runConfig_tree11;
1358
+ var _runConfig_tree11, _runConfig_tree12, _runConfig_tree13;
1286
1359
  // Built-in command mode: shell out to kodrdriv subprocess
1287
1360
  // Build command with propagated global options
1288
1361
  const globalOptions = [];
@@ -1299,14 +1372,14 @@ const execute = async (runConfig)=>{
1299
1372
  // Build the command with global options
1300
1373
  const optionsString = globalOptions.length > 0 ? ` ${globalOptions.join(' ')}` : '';
1301
1374
  // Add package argument for link/unlink commands
1302
- const packageArg = (_runConfig_tree9 = runConfig.tree) === null || _runConfig_tree9 === void 0 ? void 0 : _runConfig_tree9.packageArgument;
1375
+ const packageArg = (_runConfig_tree11 = runConfig.tree) === null || _runConfig_tree11 === void 0 ? void 0 : _runConfig_tree11.packageArgument;
1303
1376
  const packageArgString = packageArg && (builtInCommand === 'link' || builtInCommand === 'unlink') ? ` "${packageArg}"` : '';
1304
1377
  // Add command-specific options
1305
1378
  let commandSpecificOptions = '';
1306
- if (builtInCommand === 'unlink' && ((_runConfig_tree10 = runConfig.tree) === null || _runConfig_tree10 === void 0 ? void 0 : _runConfig_tree10.cleanNodeModules)) {
1379
+ if (builtInCommand === 'unlink' && ((_runConfig_tree12 = runConfig.tree) === null || _runConfig_tree12 === void 0 ? void 0 : _runConfig_tree12.cleanNodeModules)) {
1307
1380
  commandSpecificOptions += ' --clean-node-modules';
1308
1381
  }
1309
- if ((builtInCommand === 'link' || builtInCommand === 'unlink') && ((_runConfig_tree11 = runConfig.tree) === null || _runConfig_tree11 === void 0 ? void 0 : _runConfig_tree11.externals) && runConfig.tree.externals.length > 0) {
1382
+ if ((builtInCommand === 'link' || builtInCommand === 'unlink') && ((_runConfig_tree13 = runConfig.tree) === null || _runConfig_tree13 === void 0 ? void 0 : _runConfig_tree13.externals) && runConfig.tree.externals.length > 0) {
1310
1383
  commandSpecificOptions += ` --externals ${runConfig.tree.externals.join(' ')}`;
1311
1384
  }
1312
1385
  commandToRun = `kodrdriv ${builtInCommand}${optionsString}${packageArgString}${commandSpecificOptions}`;
@@ -1316,6 +1389,23 @@ const execute = async (runConfig)=>{
1316
1389
  commandToRun = cmd;
1317
1390
  }
1318
1391
  if (commandToRun) {
1392
+ // Validate scripts for run command before execution
1393
+ const scriptsToValidate = runConfig.__scriptsToValidate;
1394
+ if (scriptsToValidate && scriptsToValidate.length > 0) {
1395
+ logger.info(`🔍 Validating scripts before execution: ${scriptsToValidate.join(', ')}`);
1396
+ const validation = await validateScripts(dependencyGraph.packages, scriptsToValidate);
1397
+ if (!validation.valid) {
1398
+ logger.error('');
1399
+ logger.error('❌ Script validation failed. Cannot proceed with execution.');
1400
+ logger.error('');
1401
+ logger.error('💡 To fix this:');
1402
+ logger.error(' 1. Add the missing scripts to the package.json files');
1403
+ logger.error(' 2. Or exclude packages that don\'t need these scripts using --exclude');
1404
+ logger.error(' 3. Or run individual packages that have the required scripts');
1405
+ logger.error('');
1406
+ throw new Error('Script validation failed. See details above.');
1407
+ }
1408
+ }
1319
1409
  // Create set of all package names for inter-project dependency detection
1320
1410
  const allPackageNames = new Set(Array.from(dependencyGraph.packages.keys()));
1321
1411
  // Initialize execution context if not continuing
@@ -1329,8 +1419,8 @@ const execute = async (runConfig)=>{
1329
1419
  startTime: new Date(),
1330
1420
  lastUpdateTime: new Date()
1331
1421
  };
1332
- // Save initial context
1333
- if (isBuiltInCommand && builtInCommand === 'publish' && !isDryRun) {
1422
+ // Save initial context for commands that support continuation
1423
+ if (isBuiltInCommand && (builtInCommand === 'publish' || builtInCommand === 'run') && !isDryRun) {
1334
1424
  await saveExecutionContext(executionContext, runConfig.outputDirectory);
1335
1425
  }
1336
1426
  }
@@ -1360,7 +1450,7 @@ const execute = async (runConfig)=>{
1360
1450
  if (result.success) {
1361
1451
  successCount++;
1362
1452
  // Update context
1363
- if (executionContext && isBuiltInCommand && builtInCommand === 'publish' && !isDryRun) {
1453
+ if (executionContext && isBuiltInCommand && (builtInCommand === 'publish' || builtInCommand === 'run') && !isDryRun) {
1364
1454
  executionContext.completedPackages.push(packageName);
1365
1455
  executionContext.publishedVersions = publishedVersions;
1366
1456
  executionContext.lastUpdateTime = new Date();
@@ -1373,17 +1463,76 @@ const execute = async (runConfig)=>{
1373
1463
  }
1374
1464
  } else {
1375
1465
  failedPackage = packageName;
1376
- const formattedError = formatSubprojectError(packageName, result.error);
1466
+ const formattedError = formatSubprojectError(packageName, result.error, packageInfo, i + 1, buildOrder.length);
1377
1467
  if (!isDryRun) {
1468
+ var _result_error;
1378
1469
  packageLogger.error(`Execution failed`);
1379
1470
  logger.error(formattedError);
1380
1471
  logger.error(`Failed after ${successCount} successful packages.`);
1472
+ // Special handling for timeout errors
1473
+ if (result.isTimeoutError) {
1474
+ logger.error('');
1475
+ logger.error('⏰ TIMEOUT DETECTED: This appears to be a timeout error.');
1476
+ logger.error(' This commonly happens when PR checks take longer than expected.');
1477
+ logger.error(' The execution context has been saved for recovery.');
1478
+ logger.error('');
1479
+ // Save context even on timeout for recovery
1480
+ if (executionContext && isBuiltInCommand && (builtInCommand === 'publish' || builtInCommand === 'run')) {
1481
+ executionContext.completedPackages.push(packageName);
1482
+ executionContext.publishedVersions = publishedVersions;
1483
+ executionContext.lastUpdateTime = new Date();
1484
+ await saveExecutionContext(executionContext, runConfig.outputDirectory);
1485
+ logger.info('💾 Execution context saved for recovery.');
1486
+ }
1487
+ // For publish commands, provide specific guidance about CI/CD setup
1488
+ if (builtInCommand === 'publish') {
1489
+ logger.error('');
1490
+ logger.error('💡 PUBLISH TIMEOUT TROUBLESHOOTING:');
1491
+ logger.error(' This project may not have CI/CD workflows configured.');
1492
+ logger.error(' Common solutions:');
1493
+ logger.error(' 1. Set up GitHub Actions workflows for this repository');
1494
+ logger.error(' 2. Use --sendit flag to skip user confirmation:');
1495
+ logger.error(` kodrdriv tree publish --sendit`);
1496
+ logger.error(' 3. Or manually promote this package:');
1497
+ logger.error(` kodrdriv tree publish --promote ${packageName}`);
1498
+ logger.error('');
1499
+ }
1500
+ }
1381
1501
  logger.error(`To resume from this point, run:`);
1382
1502
  if (isBuiltInCommand) {
1383
1503
  logger.error(` kodrdriv tree ${builtInCommand} --continue`);
1384
1504
  } else {
1385
1505
  logger.error(` kodrdriv tree --continue --cmd "${commandToRun}"`);
1386
1506
  }
1507
+ // For timeout errors, provide additional recovery instructions
1508
+ if (result.isTimeoutError) {
1509
+ logger.error('');
1510
+ logger.error('🔧 RECOVERY OPTIONS:');
1511
+ if (builtInCommand === 'publish') {
1512
+ logger.error(' 1. Wait for the PR checks to complete, then run:');
1513
+ logger.error(` cd ${packageInfo.path}`);
1514
+ logger.error(` kodrdriv publish`);
1515
+ logger.error(' 2. After the individual publish completes, run:');
1516
+ logger.error(` kodrdriv tree ${builtInCommand} --continue`);
1517
+ } else {
1518
+ logger.error(' 1. Fix any issues in the package, then run:');
1519
+ logger.error(` cd ${packageInfo.path}`);
1520
+ logger.error(` ${commandToRun}`);
1521
+ logger.error(' 2. After the command completes successfully, run:');
1522
+ logger.error(` kodrdriv tree ${builtInCommand} --continue`);
1523
+ }
1524
+ logger.error(' 3. Or promote this package to completed status:');
1525
+ logger.error(` kodrdriv tree ${builtInCommand} --promote ${packageName}`);
1526
+ logger.error(' 4. Or manually edit .kodrdriv-context to mark this package as completed');
1527
+ }
1528
+ // Add clear error summary at the very end
1529
+ logger.error('');
1530
+ logger.error('📋 ERROR SUMMARY:');
1531
+ logger.error(` Project that failed: ${packageName}`);
1532
+ logger.error(` Location: ${packageInfo.path}`);
1533
+ logger.error(` Position in tree: ${i + 1} of ${buildOrder.length} packages`);
1534
+ logger.error(` What failed: ${((_result_error = result.error) === null || _result_error === void 0 ? void 0 : _result_error.message) || 'Unknown error'}`);
1535
+ logger.error('');
1387
1536
  throw new Error(`Command failed in package ${packageName}`);
1388
1537
  }
1389
1538
  break;
@@ -1393,7 +1542,7 @@ const execute = async (runConfig)=>{
1393
1542
  const summary = `${isDryRun ? 'DRY RUN: ' : ''}All ${buildOrder.length} packages completed successfully! 🎉`;
1394
1543
  logger.info(summary);
1395
1544
  // Clean up context on successful completion
1396
- if (isBuiltInCommand && builtInCommand === 'publish' && !isDryRun) {
1545
+ if (isBuiltInCommand && (builtInCommand === 'publish' || builtInCommand === 'run') && !isDryRun) {
1397
1546
  await cleanupContext(runConfig.outputDirectory);
1398
1547
  }
1399
1548
  return returnOutput; // Don't duplicate the summary in return string