@eldrforge/kodrdriv 1.2.5 → 1.2.7
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/arguments.js +6 -4
- package/dist/arguments.js.map +1 -1
- package/dist/commands/publish.js +61 -22
- package/dist/commands/publish.js.map +1 -1
- package/dist/commands/release.js +9 -9
- package/dist/commands/release.js.map +1 -1
- package/dist/commands/tree.js +325 -87
- package/dist/commands/tree.js.map +1 -1
- package/dist/constants.js +4 -2
- package/dist/constants.js.map +1 -1
- package/dist/prompt/release.js +2 -2
- package/dist/prompt/release.js.map +1 -1
- package/dist/util/git.js +159 -8
- package/dist/util/git.js.map +1 -1
- package/dist/util/github.js +2 -2
- package/dist/util/github.js.map +1 -1
- package/package.json +2 -1
package/dist/commands/tree.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import path__default from 'path';
|
|
3
3
|
import fs__default from 'fs/promises';
|
|
4
4
|
import { exec } from 'child_process';
|
|
5
|
+
import { runSecure } from '../util/child.js';
|
|
5
6
|
import util from 'util';
|
|
6
7
|
import { getLogger } from '../logging.js';
|
|
7
8
|
import { create } from '../util/storage.js';
|
|
@@ -227,6 +228,84 @@ const cleanupContext = async (outputDirectory)=>{
|
|
|
227
228
|
logger.warn(`Warning: Failed to cleanup execution context: ${error.message}`);
|
|
228
229
|
}
|
|
229
230
|
};
|
|
231
|
+
// Helper function to promote a package to completed status in the context
|
|
232
|
+
const promotePackageToCompleted = async (packageName, outputDirectory)=>{
|
|
233
|
+
const storage = create({
|
|
234
|
+
log: ()=>{}
|
|
235
|
+
});
|
|
236
|
+
const contextFilePath = getContextFilePath(outputDirectory);
|
|
237
|
+
try {
|
|
238
|
+
if (!await storage.exists(contextFilePath)) {
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
const contextContent = await storage.readFile(contextFilePath, 'utf-8');
|
|
242
|
+
const contextData = safeJsonParse(contextContent, contextFilePath);
|
|
243
|
+
// Restore dates from ISO strings
|
|
244
|
+
const context = {
|
|
245
|
+
...contextData,
|
|
246
|
+
startTime: new Date(contextData.startTime),
|
|
247
|
+
lastUpdateTime: new Date(contextData.lastUpdateTime),
|
|
248
|
+
publishedVersions: contextData.publishedVersions.map((v)=>({
|
|
249
|
+
...v,
|
|
250
|
+
publishTime: new Date(v.publishTime)
|
|
251
|
+
}))
|
|
252
|
+
};
|
|
253
|
+
// Add package to completed list if not already there
|
|
254
|
+
if (!context.completedPackages.includes(packageName)) {
|
|
255
|
+
context.completedPackages.push(packageName);
|
|
256
|
+
context.lastUpdateTime = new Date();
|
|
257
|
+
await saveExecutionContext(context, outputDirectory);
|
|
258
|
+
}
|
|
259
|
+
} catch (error) {
|
|
260
|
+
const logger = getLogger();
|
|
261
|
+
logger.warn(`Warning: Failed to promote package to completed: ${error.message}`);
|
|
262
|
+
}
|
|
263
|
+
};
|
|
264
|
+
// Helper function to validate that all packages have the required scripts
|
|
265
|
+
const validateScripts = async (packages, scripts)=>{
|
|
266
|
+
const logger = getLogger();
|
|
267
|
+
const missingScripts = new Map();
|
|
268
|
+
const storage = create({
|
|
269
|
+
log: ()=>{}
|
|
270
|
+
});
|
|
271
|
+
logger.debug(`Validating scripts: ${scripts.join(', ')}`);
|
|
272
|
+
for (const [packageName, packageInfo] of packages){
|
|
273
|
+
const packageJsonPath = path__default.join(packageInfo.path, 'package.json');
|
|
274
|
+
const missingForPackage = [];
|
|
275
|
+
try {
|
|
276
|
+
const packageJsonContent = await storage.readFile(packageJsonPath, 'utf-8');
|
|
277
|
+
const packageJson = safeJsonParse(packageJsonContent, packageJsonPath);
|
|
278
|
+
const validated = validatePackageJson(packageJson, packageJsonPath);
|
|
279
|
+
// Check if each required script exists
|
|
280
|
+
for (const script of scripts){
|
|
281
|
+
if (!validated.scripts || !validated.scripts[script]) {
|
|
282
|
+
missingForPackage.push(script);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
if (missingForPackage.length > 0) {
|
|
286
|
+
missingScripts.set(packageName, missingForPackage);
|
|
287
|
+
logger.debug(`Package ${packageName} missing scripts: ${missingForPackage.join(', ')}`);
|
|
288
|
+
}
|
|
289
|
+
} catch (error) {
|
|
290
|
+
logger.debug(`Error reading package.json for ${packageName}: ${error.message}`);
|
|
291
|
+
// If we can't read the package.json, assume all scripts are missing
|
|
292
|
+
missingScripts.set(packageName, scripts);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
const valid = missingScripts.size === 0;
|
|
296
|
+
if (valid) {
|
|
297
|
+
logger.info(`✅ All packages have the required scripts: ${scripts.join(', ')}`);
|
|
298
|
+
} else {
|
|
299
|
+
logger.error(`❌ Script validation failed. Missing scripts:`);
|
|
300
|
+
for (const [packageName, missing] of missingScripts){
|
|
301
|
+
logger.error(` ${packageName}: ${missing.join(', ')}`);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
return {
|
|
305
|
+
valid,
|
|
306
|
+
missingScripts
|
|
307
|
+
};
|
|
308
|
+
};
|
|
230
309
|
// Extract published version from package.json after successful publish
|
|
231
310
|
const extractPublishedVersion = async (packageDir, packageLogger)=>{
|
|
232
311
|
const storage = create({
|
|
@@ -318,7 +397,7 @@ const createPackageLogger = (packageName, sequenceNumber, totalCount, isDryRun =
|
|
|
318
397
|
};
|
|
319
398
|
};
|
|
320
399
|
// Helper function to format subproject error output
|
|
321
|
-
const formatSubprojectError = (packageName, error)=>{
|
|
400
|
+
const formatSubprojectError = (packageName, error, _packageInfo, _position, _total)=>{
|
|
322
401
|
const lines = [];
|
|
323
402
|
lines.push(`❌ Command failed in package ${packageName}:`);
|
|
324
403
|
// Format the main error message with indentation
|
|
@@ -576,16 +655,44 @@ const executePackage = async (packageName, packageInfo, commandToRun, runConfig,
|
|
|
576
655
|
if (hasUpdates) {
|
|
577
656
|
// Commit the dependency updates using kodrdriv commit
|
|
578
657
|
packageLogger.info('Committing inter-project dependency updates...');
|
|
658
|
+
packageLogger.info('⏱️ This step may take a few minutes as it generates a commit message using AI...');
|
|
659
|
+
// Add timeout wrapper around commit execution
|
|
660
|
+
const commitTimeoutMs = 300000; // 5 minutes
|
|
661
|
+
const commitPromise = execute$3({
|
|
662
|
+
...runConfig,
|
|
663
|
+
dryRun: false
|
|
664
|
+
});
|
|
665
|
+
const timeoutPromise = new Promise((_, reject)=>{
|
|
666
|
+
setTimeout(()=>reject(new Error(`Commit operation timed out after ${commitTimeoutMs / 1000} seconds`)), commitTimeoutMs);
|
|
667
|
+
});
|
|
668
|
+
// Add progress indicator
|
|
669
|
+
let progressInterval = null;
|
|
579
670
|
try {
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
});
|
|
584
|
-
|
|
671
|
+
// Start progress indicator
|
|
672
|
+
progressInterval = setInterval(()=>{
|
|
673
|
+
packageLogger.info('⏳ Still generating commit message... (this can take 1-3 minutes)');
|
|
674
|
+
}, 30000); // Every 30 seconds
|
|
675
|
+
await Promise.race([
|
|
676
|
+
commitPromise,
|
|
677
|
+
timeoutPromise
|
|
678
|
+
]);
|
|
679
|
+
packageLogger.info('✅ Inter-project dependency updates committed successfully');
|
|
585
680
|
} catch (commitError) {
|
|
586
|
-
|
|
681
|
+
if (commitError.message.includes('timed out')) {
|
|
682
|
+
packageLogger.error(`❌ Commit operation timed out after ${commitTimeoutMs / 1000} seconds`);
|
|
683
|
+
packageLogger.error('This usually indicates an issue with the AI service or very large changes');
|
|
684
|
+
packageLogger.error('You may need to manually commit the dependency updates');
|
|
685
|
+
} else {
|
|
686
|
+
packageLogger.warn(`Failed to commit inter-project dependency updates: ${commitError.message}`);
|
|
687
|
+
}
|
|
587
688
|
// Continue with publish anyway - the updates are still in place
|
|
689
|
+
} finally{
|
|
690
|
+
if (progressInterval) {
|
|
691
|
+
clearInterval(progressInterval);
|
|
692
|
+
}
|
|
588
693
|
}
|
|
694
|
+
} else {
|
|
695
|
+
packageLogger.info('No inter-project dependency updates needed');
|
|
589
696
|
}
|
|
590
697
|
}
|
|
591
698
|
if (runConfig.debug || runConfig.verbose) {
|
|
@@ -604,14 +711,39 @@ const executePackage = async (packageName, packageInfo, commandToRun, runConfig,
|
|
|
604
711
|
if (runConfig.debug) {
|
|
605
712
|
packageLogger.debug(`Shelling out to separate kodrdriv process for ${builtInCommandName} command`);
|
|
606
713
|
}
|
|
714
|
+
// Add progress indication for publish commands
|
|
715
|
+
if (builtInCommandName === 'publish') {
|
|
716
|
+
packageLogger.info('🚀 Starting publish process...');
|
|
717
|
+
packageLogger.info('⏱️ This may take several minutes (AI processing, PR creation, etc.)');
|
|
718
|
+
}
|
|
607
719
|
// Ensure dry-run propagates to subprocess even during overall dry-run mode
|
|
608
720
|
const effectiveCommand = runConfig.dryRun && !commandToRun.includes('--dry-run') ? `${commandToRun} --dry-run` : commandToRun;
|
|
609
|
-
//
|
|
610
|
-
const
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
721
|
+
// Add timeout wrapper for publish commands
|
|
722
|
+
const commandTimeoutMs = 1800000; // 30 minutes for publish commands
|
|
723
|
+
if (builtInCommandName === 'publish') {
|
|
724
|
+
packageLogger.info(`⏰ Setting timeout of ${commandTimeoutMs / 60000} minutes for publish command`);
|
|
725
|
+
}
|
|
726
|
+
const commandPromise = runWithLogging(effectiveCommand, packageLogger, {}, showOutput);
|
|
727
|
+
const commandTimeoutPromise = new Promise((_, reject)=>{
|
|
728
|
+
setTimeout(()=>reject(new Error(`Command timed out after ${commandTimeoutMs / 60000} minutes`)), commandTimeoutMs);
|
|
729
|
+
});
|
|
730
|
+
try {
|
|
731
|
+
const { stdout } = await Promise.race([
|
|
732
|
+
commandPromise,
|
|
733
|
+
commandTimeoutPromise
|
|
734
|
+
]);
|
|
735
|
+
// Detect explicit skip marker from publish to avoid propagating versions
|
|
736
|
+
if (builtInCommandName === 'publish' && stdout && stdout.includes('KODRDRIV_PUBLISH_SKIPPED')) {
|
|
737
|
+
packageLogger.info('Publish skipped for this package; will not record or propagate a version.');
|
|
738
|
+
publishWasSkipped = true;
|
|
739
|
+
}
|
|
740
|
+
} catch (error) {
|
|
741
|
+
if (error.message.includes('timed out')) {
|
|
742
|
+
packageLogger.error(`❌ ${builtInCommandName} command timed out after ${commandTimeoutMs / 60000} minutes`);
|
|
743
|
+
packageLogger.error('This usually indicates the command is stuck waiting for user input or an external service');
|
|
744
|
+
throw error;
|
|
745
|
+
}
|
|
746
|
+
throw error;
|
|
615
747
|
}
|
|
616
748
|
} else {
|
|
617
749
|
// For custom commands, use the existing logic
|
|
@@ -671,22 +803,71 @@ const executePackage = async (packageName, packageInfo, commandToRun, runConfig,
|
|
|
671
803
|
success: true
|
|
672
804
|
};
|
|
673
805
|
} catch (error) {
|
|
806
|
+
var _error_message;
|
|
674
807
|
if (runConfig.debug || runConfig.verbose) {
|
|
675
808
|
packageLogger.error(`❌ Execution failed: ${error.message}`);
|
|
676
809
|
} else {
|
|
677
810
|
logger.error(`[${index + 1}/${total}] ${packageName}: ❌ Failed - ${error.message}`);
|
|
678
811
|
}
|
|
812
|
+
// Check if this is a timeout error
|
|
813
|
+
const errorMessage = ((_error_message = error.message) === null || _error_message === void 0 ? void 0 : _error_message.toLowerCase()) || '';
|
|
814
|
+
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
815
|
return {
|
|
680
816
|
success: false,
|
|
681
|
-
error
|
|
817
|
+
error,
|
|
818
|
+
isTimeoutError
|
|
682
819
|
};
|
|
683
820
|
}
|
|
684
821
|
};
|
|
822
|
+
// Add a simple status check function
|
|
823
|
+
const checkTreePublishStatus = async ()=>{
|
|
824
|
+
const logger = getLogger();
|
|
825
|
+
try {
|
|
826
|
+
// Check for running kodrdriv processes
|
|
827
|
+
const { stdout } = await runSecure('ps', [
|
|
828
|
+
'aux'
|
|
829
|
+
], {});
|
|
830
|
+
const kodrdrivProcesses = stdout.split('\n').filter((line)=>line.includes('kodrdriv') && !line.includes('grep') && !line.includes('ps aux') && !line.includes('tree --status') // Exclude the current status command
|
|
831
|
+
);
|
|
832
|
+
if (kodrdrivProcesses.length > 0) {
|
|
833
|
+
logger.info('🔍 Found running kodrdriv processes:');
|
|
834
|
+
kodrdrivProcesses.forEach((process1)=>{
|
|
835
|
+
const parts = process1.trim().split(/\s+/);
|
|
836
|
+
const pid = parts[1];
|
|
837
|
+
const command = parts.slice(10).join(' ');
|
|
838
|
+
logger.info(` PID ${pid}: ${command}`);
|
|
839
|
+
});
|
|
840
|
+
} else {
|
|
841
|
+
logger.info('No kodrdriv processes currently running');
|
|
842
|
+
}
|
|
843
|
+
} catch (error) {
|
|
844
|
+
logger.warn('Could not check process status:', error);
|
|
845
|
+
}
|
|
846
|
+
};
|
|
685
847
|
const execute = async (runConfig)=>{
|
|
686
|
-
var _runConfig_tree, _runConfig_tree1, _runConfig_tree2, _runConfig_tree3, _runConfig_tree4;
|
|
848
|
+
var _runConfig_tree, _runConfig_tree1, _runConfig_tree2, _runConfig_tree3, _runConfig_tree4, _runConfig_tree5, _runConfig_tree6, _runConfig_tree7;
|
|
687
849
|
const logger = getLogger();
|
|
688
850
|
const isDryRun = runConfig.dryRun || false;
|
|
689
851
|
const isContinue = ((_runConfig_tree = runConfig.tree) === null || _runConfig_tree === void 0 ? void 0 : _runConfig_tree.continue) || false;
|
|
852
|
+
const promotePackage = (_runConfig_tree1 = runConfig.tree) === null || _runConfig_tree1 === void 0 ? void 0 : _runConfig_tree1.promote;
|
|
853
|
+
// Debug logging
|
|
854
|
+
logger.debug('Tree config:', JSON.stringify(runConfig.tree, null, 2));
|
|
855
|
+
logger.debug('Status flag:', (_runConfig_tree2 = runConfig.tree) === null || _runConfig_tree2 === void 0 ? void 0 : _runConfig_tree2.status);
|
|
856
|
+
logger.debug('Full runConfig:', JSON.stringify(runConfig, null, 2));
|
|
857
|
+
// Handle status check
|
|
858
|
+
if ((_runConfig_tree3 = runConfig.tree) === null || _runConfig_tree3 === void 0 ? void 0 : _runConfig_tree3.status) {
|
|
859
|
+
logger.info('🔍 Checking for running kodrdriv processes...');
|
|
860
|
+
await checkTreePublishStatus();
|
|
861
|
+
return 'Status check completed';
|
|
862
|
+
}
|
|
863
|
+
// Handle promote mode
|
|
864
|
+
if (promotePackage) {
|
|
865
|
+
logger.info(`Promoting package '${promotePackage}' to completed status...`);
|
|
866
|
+
await promotePackageToCompleted(promotePackage, runConfig.outputDirectory);
|
|
867
|
+
logger.info(`✅ Package '${promotePackage}' has been marked as completed.`);
|
|
868
|
+
logger.info('You can now run the tree command with --continue to resume from the next package.');
|
|
869
|
+
return `Package '${promotePackage}' promoted to completed status.`;
|
|
870
|
+
}
|
|
690
871
|
// Handle continue mode
|
|
691
872
|
if (isContinue) {
|
|
692
873
|
const savedContext = await loadExecutionContext(runConfig.outputDirectory);
|
|
@@ -725,24 +906,50 @@ const execute = async (runConfig)=>{
|
|
|
725
906
|
executionContext = null;
|
|
726
907
|
}
|
|
727
908
|
// Check if we're in built-in command mode (tree command with second argument)
|
|
728
|
-
const builtInCommand = (
|
|
909
|
+
const builtInCommand = (_runConfig_tree4 = runConfig.tree) === null || _runConfig_tree4 === void 0 ? void 0 : _runConfig_tree4.builtInCommand;
|
|
729
910
|
const supportedBuiltInCommands = [
|
|
730
911
|
'commit',
|
|
731
912
|
'publish',
|
|
732
913
|
'link',
|
|
733
914
|
'unlink',
|
|
734
915
|
'development',
|
|
735
|
-
'branches'
|
|
916
|
+
'branches',
|
|
917
|
+
'run'
|
|
736
918
|
];
|
|
737
919
|
if (builtInCommand && !supportedBuiltInCommands.includes(builtInCommand)) {
|
|
738
920
|
throw new Error(`Unsupported built-in command: ${builtInCommand}. Supported commands: ${supportedBuiltInCommands.join(', ')}`);
|
|
739
921
|
}
|
|
922
|
+
// Handle run subcommand - convert space-separated scripts to npm run commands
|
|
923
|
+
if (builtInCommand === 'run') {
|
|
924
|
+
var _runConfig_tree8;
|
|
925
|
+
const packageArgument = (_runConfig_tree8 = runConfig.tree) === null || _runConfig_tree8 === void 0 ? void 0 : _runConfig_tree8.packageArgument;
|
|
926
|
+
if (!packageArgument) {
|
|
927
|
+
throw new Error('run subcommand requires script names. Usage: kodrdriv tree run "clean build test"');
|
|
928
|
+
}
|
|
929
|
+
// Split the package argument by spaces to get individual script names
|
|
930
|
+
const scripts = packageArgument.trim().split(/\s+/).filter((script)=>script.length > 0);
|
|
931
|
+
if (scripts.length === 0) {
|
|
932
|
+
throw new Error('run subcommand requires at least one script name. Usage: kodrdriv tree run "clean build test"');
|
|
933
|
+
}
|
|
934
|
+
// Convert to npm run commands joined with &&
|
|
935
|
+
const npmCommands = scripts.map((script)=>`npm run ${script}`).join(' && ');
|
|
936
|
+
// Set this as the custom command to run
|
|
937
|
+
runConfig.tree = {
|
|
938
|
+
...runConfig.tree,
|
|
939
|
+
cmd: npmCommands
|
|
940
|
+
};
|
|
941
|
+
// Clear the built-in command since we're now using custom command mode
|
|
942
|
+
runConfig.tree.builtInCommand = undefined;
|
|
943
|
+
logger.info(`Converting run subcommand to: ${npmCommands}`);
|
|
944
|
+
// Store scripts for later validation
|
|
945
|
+
runConfig.__scriptsToValidate = scripts;
|
|
946
|
+
}
|
|
740
947
|
// Determine the target directories - either specified or current working directory
|
|
741
|
-
const directories = ((
|
|
948
|
+
const directories = ((_runConfig_tree5 = runConfig.tree) === null || _runConfig_tree5 === void 0 ? void 0 : _runConfig_tree5.directories) || [
|
|
742
949
|
process.cwd()
|
|
743
950
|
];
|
|
744
951
|
// Handle link status subcommand
|
|
745
|
-
if (builtInCommand === 'link' && ((
|
|
952
|
+
if (builtInCommand === 'link' && ((_runConfig_tree6 = runConfig.tree) === null || _runConfig_tree6 === void 0 ? void 0 : _runConfig_tree6.packageArgument) === 'status') {
|
|
746
953
|
// For tree link status, we want to show status across all packages
|
|
747
954
|
logger.info(`${isDryRun ? 'DRY RUN: ' : ''}Running link status across workspace...`);
|
|
748
955
|
// Create a config that will be passed to the link command
|
|
@@ -762,7 +969,7 @@ const execute = async (runConfig)=>{
|
|
|
762
969
|
}
|
|
763
970
|
}
|
|
764
971
|
// Handle unlink status subcommand
|
|
765
|
-
if (builtInCommand === 'unlink' && ((
|
|
972
|
+
if (builtInCommand === 'unlink' && ((_runConfig_tree7 = runConfig.tree) === null || _runConfig_tree7 === void 0 ? void 0 : _runConfig_tree7.packageArgument) === 'status') {
|
|
766
973
|
// For tree unlink status, we want to show status across all packages
|
|
767
974
|
logger.info(`${isDryRun ? 'DRY RUN: ' : ''}Running unlink status across workspace...`);
|
|
768
975
|
// Create a config that will be passed to the unlink command
|
|
@@ -787,9 +994,9 @@ const execute = async (runConfig)=>{
|
|
|
787
994
|
logger.info(`${isDryRun ? 'DRY RUN: ' : ''}Analyzing workspaces at: ${directories.join(', ')}`);
|
|
788
995
|
}
|
|
789
996
|
try {
|
|
790
|
-
var
|
|
997
|
+
var _runConfig_tree9, _runConfig_tree10, _runConfig_tree11, _runConfig_tree12;
|
|
791
998
|
// Get exclusion patterns from config, fallback to empty array
|
|
792
|
-
const excludedPatterns = ((
|
|
999
|
+
const excludedPatterns = ((_runConfig_tree9 = runConfig.tree) === null || _runConfig_tree9 === void 0 ? void 0 : _runConfig_tree9.exclude) || [];
|
|
793
1000
|
if (excludedPatterns.length > 0) {
|
|
794
1001
|
logger.verbose(`${isDryRun ? 'DRY RUN: ' : ''}Using exclusion patterns: ${excludedPatterns.join(', ')}`);
|
|
795
1002
|
}
|
|
@@ -816,7 +1023,7 @@ const execute = async (runConfig)=>{
|
|
|
816
1023
|
logger.verbose(`${isDryRun ? 'DRY RUN: ' : ''}Determining build order...`);
|
|
817
1024
|
let buildOrder = topologicalSort(dependencyGraph);
|
|
818
1025
|
// Handle start-from functionality if specified
|
|
819
|
-
const startFrom = (
|
|
1026
|
+
const startFrom = (_runConfig_tree10 = runConfig.tree) === null || _runConfig_tree10 === void 0 ? void 0 : _runConfig_tree10.startFrom;
|
|
820
1027
|
if (startFrom) {
|
|
821
1028
|
logger.verbose(`${isDryRun ? 'DRY RUN: ' : ''}Looking for start package: ${startFrom}`);
|
|
822
1029
|
// Resolve the actual package name (can be package name or directory name)
|
|
@@ -862,63 +1069,18 @@ const execute = async (runConfig)=>{
|
|
|
862
1069
|
throw new Error(`Package directory '${startFrom}' not found. Available packages: ${availablePackages}`);
|
|
863
1070
|
}
|
|
864
1071
|
}
|
|
865
|
-
//
|
|
866
|
-
const
|
|
867
|
-
|
|
868
|
-
|
|
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);
|
|
1072
|
+
// Find the start package in the build order and start execution from there
|
|
1073
|
+
const startIndex = buildOrder.findIndex((pkgName)=>pkgName === startPackageName);
|
|
1074
|
+
if (startIndex === -1) {
|
|
1075
|
+
throw new Error(`Package '${startFrom}' not found in build order. This should not happen.`);
|
|
915
1076
|
}
|
|
916
|
-
//
|
|
917
|
-
|
|
918
|
-
|
|
1077
|
+
// Filter build order to start from the specified package
|
|
1078
|
+
const originalLength = buildOrder.length;
|
|
1079
|
+
buildOrder = buildOrder.slice(startIndex);
|
|
1080
|
+
logger.info(`${isDryRun ? 'DRY RUN: ' : ''}Starting execution from package '${startFrom}' (${buildOrder.length} of ${originalLength} packages remaining).`);
|
|
919
1081
|
}
|
|
920
1082
|
// Handle stop-at functionality if specified
|
|
921
|
-
const stopAt = (
|
|
1083
|
+
const stopAt = (_runConfig_tree11 = runConfig.tree) === null || _runConfig_tree11 === void 0 ? void 0 : _runConfig_tree11.stopAt;
|
|
922
1084
|
if (stopAt) {
|
|
923
1085
|
logger.verbose(`${isDryRun ? 'DRY RUN: ' : ''}Looking for stop package: ${stopAt}`);
|
|
924
1086
|
// Find the package that matches the stopAt directory name
|
|
@@ -1277,12 +1439,12 @@ const execute = async (runConfig)=>{
|
|
|
1277
1439
|
returnOutput = `\nBuild order: ${buildOrder.join(' → ')}\n`;
|
|
1278
1440
|
}
|
|
1279
1441
|
// Execute command if provided (custom command or built-in command)
|
|
1280
|
-
const cmd = (
|
|
1442
|
+
const cmd = (_runConfig_tree12 = runConfig.tree) === null || _runConfig_tree12 === void 0 ? void 0 : _runConfig_tree12.cmd;
|
|
1281
1443
|
// Determine command to execute
|
|
1282
1444
|
let commandToRun;
|
|
1283
1445
|
let isBuiltInCommand = false;
|
|
1284
1446
|
if (builtInCommand) {
|
|
1285
|
-
var
|
|
1447
|
+
var _runConfig_tree13, _runConfig_tree14, _runConfig_tree15;
|
|
1286
1448
|
// Built-in command mode: shell out to kodrdriv subprocess
|
|
1287
1449
|
// Build command with propagated global options
|
|
1288
1450
|
const globalOptions = [];
|
|
@@ -1299,14 +1461,14 @@ const execute = async (runConfig)=>{
|
|
|
1299
1461
|
// Build the command with global options
|
|
1300
1462
|
const optionsString = globalOptions.length > 0 ? ` ${globalOptions.join(' ')}` : '';
|
|
1301
1463
|
// Add package argument for link/unlink commands
|
|
1302
|
-
const packageArg = (
|
|
1464
|
+
const packageArg = (_runConfig_tree13 = runConfig.tree) === null || _runConfig_tree13 === void 0 ? void 0 : _runConfig_tree13.packageArgument;
|
|
1303
1465
|
const packageArgString = packageArg && (builtInCommand === 'link' || builtInCommand === 'unlink') ? ` "${packageArg}"` : '';
|
|
1304
1466
|
// Add command-specific options
|
|
1305
1467
|
let commandSpecificOptions = '';
|
|
1306
|
-
if (builtInCommand === 'unlink' && ((
|
|
1468
|
+
if (builtInCommand === 'unlink' && ((_runConfig_tree14 = runConfig.tree) === null || _runConfig_tree14 === void 0 ? void 0 : _runConfig_tree14.cleanNodeModules)) {
|
|
1307
1469
|
commandSpecificOptions += ' --clean-node-modules';
|
|
1308
1470
|
}
|
|
1309
|
-
if ((builtInCommand === 'link' || builtInCommand === 'unlink') && ((
|
|
1471
|
+
if ((builtInCommand === 'link' || builtInCommand === 'unlink') && ((_runConfig_tree15 = runConfig.tree) === null || _runConfig_tree15 === void 0 ? void 0 : _runConfig_tree15.externals) && runConfig.tree.externals.length > 0) {
|
|
1310
1472
|
commandSpecificOptions += ` --externals ${runConfig.tree.externals.join(' ')}`;
|
|
1311
1473
|
}
|
|
1312
1474
|
commandToRun = `kodrdriv ${builtInCommand}${optionsString}${packageArgString}${commandSpecificOptions}`;
|
|
@@ -1316,6 +1478,23 @@ const execute = async (runConfig)=>{
|
|
|
1316
1478
|
commandToRun = cmd;
|
|
1317
1479
|
}
|
|
1318
1480
|
if (commandToRun) {
|
|
1481
|
+
// Validate scripts for run command before execution
|
|
1482
|
+
const scriptsToValidate = runConfig.__scriptsToValidate;
|
|
1483
|
+
if (scriptsToValidate && scriptsToValidate.length > 0) {
|
|
1484
|
+
logger.info(`🔍 Validating scripts before execution: ${scriptsToValidate.join(', ')}`);
|
|
1485
|
+
const validation = await validateScripts(dependencyGraph.packages, scriptsToValidate);
|
|
1486
|
+
if (!validation.valid) {
|
|
1487
|
+
logger.error('');
|
|
1488
|
+
logger.error('❌ Script validation failed. Cannot proceed with execution.');
|
|
1489
|
+
logger.error('');
|
|
1490
|
+
logger.error('💡 To fix this:');
|
|
1491
|
+
logger.error(' 1. Add the missing scripts to the package.json files');
|
|
1492
|
+
logger.error(' 2. Or exclude packages that don\'t need these scripts using --exclude');
|
|
1493
|
+
logger.error(' 3. Or run individual packages that have the required scripts');
|
|
1494
|
+
logger.error('');
|
|
1495
|
+
throw new Error('Script validation failed. See details above.');
|
|
1496
|
+
}
|
|
1497
|
+
}
|
|
1319
1498
|
// Create set of all package names for inter-project dependency detection
|
|
1320
1499
|
const allPackageNames = new Set(Array.from(dependencyGraph.packages.keys()));
|
|
1321
1500
|
// Initialize execution context if not continuing
|
|
@@ -1329,8 +1508,8 @@ const execute = async (runConfig)=>{
|
|
|
1329
1508
|
startTime: new Date(),
|
|
1330
1509
|
lastUpdateTime: new Date()
|
|
1331
1510
|
};
|
|
1332
|
-
// Save initial context
|
|
1333
|
-
if (isBuiltInCommand && builtInCommand === 'publish' && !isDryRun) {
|
|
1511
|
+
// Save initial context for commands that support continuation
|
|
1512
|
+
if (isBuiltInCommand && (builtInCommand === 'publish' || builtInCommand === 'run') && !isDryRun) {
|
|
1334
1513
|
await saveExecutionContext(executionContext, runConfig.outputDirectory);
|
|
1335
1514
|
}
|
|
1336
1515
|
}
|
|
@@ -1360,7 +1539,7 @@ const execute = async (runConfig)=>{
|
|
|
1360
1539
|
if (result.success) {
|
|
1361
1540
|
successCount++;
|
|
1362
1541
|
// Update context
|
|
1363
|
-
if (executionContext && isBuiltInCommand && builtInCommand === 'publish' && !isDryRun) {
|
|
1542
|
+
if (executionContext && isBuiltInCommand && (builtInCommand === 'publish' || builtInCommand === 'run') && !isDryRun) {
|
|
1364
1543
|
executionContext.completedPackages.push(packageName);
|
|
1365
1544
|
executionContext.publishedVersions = publishedVersions;
|
|
1366
1545
|
executionContext.lastUpdateTime = new Date();
|
|
@@ -1373,17 +1552,76 @@ const execute = async (runConfig)=>{
|
|
|
1373
1552
|
}
|
|
1374
1553
|
} else {
|
|
1375
1554
|
failedPackage = packageName;
|
|
1376
|
-
const formattedError = formatSubprojectError(packageName, result.error);
|
|
1555
|
+
const formattedError = formatSubprojectError(packageName, result.error, packageInfo, i + 1, buildOrder.length);
|
|
1377
1556
|
if (!isDryRun) {
|
|
1557
|
+
var _result_error;
|
|
1378
1558
|
packageLogger.error(`Execution failed`);
|
|
1379
1559
|
logger.error(formattedError);
|
|
1380
1560
|
logger.error(`Failed after ${successCount} successful packages.`);
|
|
1561
|
+
// Special handling for timeout errors
|
|
1562
|
+
if (result.isTimeoutError) {
|
|
1563
|
+
logger.error('');
|
|
1564
|
+
logger.error('⏰ TIMEOUT DETECTED: This appears to be a timeout error.');
|
|
1565
|
+
logger.error(' This commonly happens when PR checks take longer than expected.');
|
|
1566
|
+
logger.error(' The execution context has been saved for recovery.');
|
|
1567
|
+
logger.error('');
|
|
1568
|
+
// Save context even on timeout for recovery
|
|
1569
|
+
if (executionContext && isBuiltInCommand && (builtInCommand === 'publish' || builtInCommand === 'run')) {
|
|
1570
|
+
executionContext.completedPackages.push(packageName);
|
|
1571
|
+
executionContext.publishedVersions = publishedVersions;
|
|
1572
|
+
executionContext.lastUpdateTime = new Date();
|
|
1573
|
+
await saveExecutionContext(executionContext, runConfig.outputDirectory);
|
|
1574
|
+
logger.info('💾 Execution context saved for recovery.');
|
|
1575
|
+
}
|
|
1576
|
+
// For publish commands, provide specific guidance about CI/CD setup
|
|
1577
|
+
if (builtInCommand === 'publish') {
|
|
1578
|
+
logger.error('');
|
|
1579
|
+
logger.error('💡 PUBLISH TIMEOUT TROUBLESHOOTING:');
|
|
1580
|
+
logger.error(' This project may not have CI/CD workflows configured.');
|
|
1581
|
+
logger.error(' Common solutions:');
|
|
1582
|
+
logger.error(' 1. Set up GitHub Actions workflows for this repository');
|
|
1583
|
+
logger.error(' 2. Use --sendit flag to skip user confirmation:');
|
|
1584
|
+
logger.error(` kodrdriv tree publish --sendit`);
|
|
1585
|
+
logger.error(' 3. Or manually promote this package:');
|
|
1586
|
+
logger.error(` kodrdriv tree publish --promote ${packageName}`);
|
|
1587
|
+
logger.error('');
|
|
1588
|
+
}
|
|
1589
|
+
}
|
|
1381
1590
|
logger.error(`To resume from this point, run:`);
|
|
1382
1591
|
if (isBuiltInCommand) {
|
|
1383
1592
|
logger.error(` kodrdriv tree ${builtInCommand} --continue`);
|
|
1384
1593
|
} else {
|
|
1385
1594
|
logger.error(` kodrdriv tree --continue --cmd "${commandToRun}"`);
|
|
1386
1595
|
}
|
|
1596
|
+
// For timeout errors, provide additional recovery instructions
|
|
1597
|
+
if (result.isTimeoutError) {
|
|
1598
|
+
logger.error('');
|
|
1599
|
+
logger.error('🔧 RECOVERY OPTIONS:');
|
|
1600
|
+
if (builtInCommand === 'publish') {
|
|
1601
|
+
logger.error(' 1. Wait for the PR checks to complete, then run:');
|
|
1602
|
+
logger.error(` cd ${packageInfo.path}`);
|
|
1603
|
+
logger.error(` kodrdriv publish`);
|
|
1604
|
+
logger.error(' 2. After the individual publish completes, run:');
|
|
1605
|
+
logger.error(` kodrdriv tree ${builtInCommand} --continue`);
|
|
1606
|
+
} else {
|
|
1607
|
+
logger.error(' 1. Fix any issues in the package, then run:');
|
|
1608
|
+
logger.error(` cd ${packageInfo.path}`);
|
|
1609
|
+
logger.error(` ${commandToRun}`);
|
|
1610
|
+
logger.error(' 2. After the command completes successfully, run:');
|
|
1611
|
+
logger.error(` kodrdriv tree ${builtInCommand} --continue`);
|
|
1612
|
+
}
|
|
1613
|
+
logger.error(' 3. Or promote this package to completed status:');
|
|
1614
|
+
logger.error(` kodrdriv tree ${builtInCommand} --promote ${packageName}`);
|
|
1615
|
+
logger.error(' 4. Or manually edit .kodrdriv-context to mark this package as completed');
|
|
1616
|
+
}
|
|
1617
|
+
// Add clear error summary at the very end
|
|
1618
|
+
logger.error('');
|
|
1619
|
+
logger.error('📋 ERROR SUMMARY:');
|
|
1620
|
+
logger.error(` Project that failed: ${packageName}`);
|
|
1621
|
+
logger.error(` Location: ${packageInfo.path}`);
|
|
1622
|
+
logger.error(` Position in tree: ${i + 1} of ${buildOrder.length} packages`);
|
|
1623
|
+
logger.error(` What failed: ${((_result_error = result.error) === null || _result_error === void 0 ? void 0 : _result_error.message) || 'Unknown error'}`);
|
|
1624
|
+
logger.error('');
|
|
1387
1625
|
throw new Error(`Command failed in package ${packageName}`);
|
|
1388
1626
|
}
|
|
1389
1627
|
break;
|
|
@@ -1393,7 +1631,7 @@ const execute = async (runConfig)=>{
|
|
|
1393
1631
|
const summary = `${isDryRun ? 'DRY RUN: ' : ''}All ${buildOrder.length} packages completed successfully! 🎉`;
|
|
1394
1632
|
logger.info(summary);
|
|
1395
1633
|
// Clean up context on successful completion
|
|
1396
|
-
if (isBuiltInCommand && builtInCommand === 'publish' && !isDryRun) {
|
|
1634
|
+
if (isBuiltInCommand && (builtInCommand === 'publish' || builtInCommand === 'run') && !isDryRun) {
|
|
1397
1635
|
await cleanupContext(runConfig.outputDirectory);
|
|
1398
1636
|
}
|
|
1399
1637
|
return returnOutput; // Don't duplicate the summary in return string
|