@eldrforge/kodrdriv 0.1.0 → 1.2.1
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/README.md +1 -0
- package/dist/application.js +25 -3
- package/dist/application.js.map +1 -1
- package/dist/arguments.js +103 -18
- package/dist/arguments.js.map +1 -1
- package/dist/commands/audio-commit.js +28 -7
- package/dist/commands/audio-commit.js.map +1 -1
- package/dist/commands/audio-review.js +28 -7
- package/dist/commands/audio-review.js.map +1 -1
- package/dist/commands/commit.js +75 -18
- package/dist/commands/commit.js.map +1 -1
- package/dist/commands/development.js +264 -0
- package/dist/commands/development.js.map +1 -0
- package/dist/commands/link.js +356 -181
- package/dist/commands/link.js.map +1 -1
- package/dist/commands/publish.js +166 -32
- package/dist/commands/publish.js.map +1 -1
- package/dist/commands/release.js +78 -13
- package/dist/commands/release.js.map +1 -1
- package/dist/commands/review.js +10 -6
- package/dist/commands/review.js.map +1 -1
- package/dist/commands/tree.js +450 -24
- package/dist/commands/tree.js.map +1 -1
- package/dist/commands/unlink.js +267 -372
- package/dist/commands/unlink.js.map +1 -1
- package/dist/commands/versions.js +224 -0
- package/dist/commands/versions.js.map +1 -0
- package/dist/constants.js +29 -10
- package/dist/constants.js.map +1 -1
- package/dist/content/diff.js.map +1 -1
- package/dist/content/files.js +192 -0
- package/dist/content/files.js.map +1 -0
- package/dist/content/log.js +16 -0
- package/dist/content/log.js.map +1 -1
- package/dist/main.js +0 -0
- package/dist/prompt/commit.js +9 -2
- package/dist/prompt/commit.js.map +1 -1
- package/dist/prompt/instructions/commit.md +20 -2
- package/dist/prompt/instructions/release.md +27 -10
- package/dist/prompt/instructions/review.md +75 -8
- package/dist/prompt/release.js +13 -5
- package/dist/prompt/release.js.map +1 -1
- package/dist/types.js +21 -5
- package/dist/types.js.map +1 -1
- package/dist/util/child.js +112 -26
- package/dist/util/child.js.map +1 -1
- package/dist/util/countdown.js +215 -0
- package/dist/util/countdown.js.map +1 -0
- package/dist/util/general.js +31 -7
- package/dist/util/general.js.map +1 -1
- package/dist/util/git.js +587 -0
- package/dist/util/git.js.map +1 -0
- package/dist/util/github.js +519 -3
- package/dist/util/github.js.map +1 -1
- package/dist/util/interactive.js +245 -79
- package/dist/util/interactive.js.map +1 -1
- package/dist/util/openai.js +70 -22
- package/dist/util/openai.js.map +1 -1
- package/dist/util/performance.js +1 -69
- package/dist/util/performance.js.map +1 -1
- package/dist/util/storage.js +28 -1
- package/dist/util/storage.js.map +1 -1
- package/dist/util/validation.js +1 -25
- package/dist/util/validation.js.map +1 -1
- package/package.json +10 -8
- package/test-multiline/cli/package.json +8 -0
- package/test-multiline/core/package.json +5 -0
- package/test-multiline/mobile/package.json +8 -0
- package/test-multiline/web/package.json +8 -0
- package/dist/util/npmOptimizations.js +0 -174
- package/dist/util/npmOptimizations.js.map +0 -1
package/dist/commands/tree.js
CHANGED
|
@@ -9,6 +9,7 @@ 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
11
|
import { execute as execute$1 } from './commit.js';
|
|
12
|
+
import { getGloballyLinkedPackages, getGitStatusSummary, getLinkedDependencies, getLinkCompatibilityProblems } from '../util/git.js';
|
|
12
13
|
|
|
13
14
|
function _define_property(obj, key, value) {
|
|
14
15
|
if (key in obj) {
|
|
@@ -29,7 +30,11 @@ let executionContext = null;
|
|
|
29
30
|
// Simple mutex to prevent race conditions in global state access
|
|
30
31
|
class SimpleMutex {
|
|
31
32
|
async lock() {
|
|
32
|
-
return new Promise((resolve)=>{
|
|
33
|
+
return new Promise((resolve, reject)=>{
|
|
34
|
+
if (this.destroyed) {
|
|
35
|
+
reject(new Error('Mutex has been destroyed'));
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
33
38
|
if (!this.locked) {
|
|
34
39
|
this.locked = true;
|
|
35
40
|
resolve();
|
|
@@ -39,16 +44,49 @@ class SimpleMutex {
|
|
|
39
44
|
});
|
|
40
45
|
}
|
|
41
46
|
unlock() {
|
|
47
|
+
if (this.destroyed) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
42
50
|
this.locked = false;
|
|
43
51
|
const next = this.queue.shift();
|
|
44
52
|
if (next) {
|
|
45
53
|
this.locked = true;
|
|
46
|
-
|
|
54
|
+
try {
|
|
55
|
+
next();
|
|
56
|
+
} catch {
|
|
57
|
+
// If resolver throws, unlock and continue with next in queue
|
|
58
|
+
this.locked = false;
|
|
59
|
+
const nextInQueue = this.queue.shift();
|
|
60
|
+
if (nextInQueue) {
|
|
61
|
+
this.locked = true;
|
|
62
|
+
nextInQueue();
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
destroy() {
|
|
68
|
+
this.destroyed = true;
|
|
69
|
+
this.locked = false;
|
|
70
|
+
// Reject all queued promises to prevent memory leaks
|
|
71
|
+
while(this.queue.length > 0){
|
|
72
|
+
const resolver = this.queue.shift();
|
|
73
|
+
if (resolver) {
|
|
74
|
+
try {
|
|
75
|
+
// Treat as rejected promise to clean up
|
|
76
|
+
resolver(new Error('Mutex destroyed'));
|
|
77
|
+
} catch {
|
|
78
|
+
// Ignore errors from rejected resolvers
|
|
79
|
+
}
|
|
80
|
+
}
|
|
47
81
|
}
|
|
48
82
|
}
|
|
83
|
+
isDestroyed() {
|
|
84
|
+
return this.destroyed;
|
|
85
|
+
}
|
|
49
86
|
constructor(){
|
|
50
87
|
_define_property(this, "locked", false);
|
|
51
88
|
_define_property(this, "queue", []);
|
|
89
|
+
_define_property(this, "destroyed", false);
|
|
52
90
|
}
|
|
53
91
|
}
|
|
54
92
|
const globalStateMutex = new SimpleMutex();
|
|
@@ -149,7 +187,7 @@ const loadExecutionContext = async (outputDirectory)=>{
|
|
|
149
187
|
return null;
|
|
150
188
|
}
|
|
151
189
|
const contextContent = await storage.readFile(contextFilePath, 'utf-8');
|
|
152
|
-
const contextData =
|
|
190
|
+
const contextData = safeJsonParse(contextContent, contextFilePath);
|
|
153
191
|
// Restore dates from ISO strings
|
|
154
192
|
return {
|
|
155
193
|
...contextData,
|
|
@@ -537,21 +575,24 @@ const executePackage = async (packageName, packageInfo, commandToRun, runConfig,
|
|
|
537
575
|
try {
|
|
538
576
|
if (isDryRun) {
|
|
539
577
|
// Handle inter-project dependency updates for publish commands in dry run mode
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
578
|
+
if (isBuiltInCommand && commandToRun.includes('publish') && publishedVersions.length > 0) {
|
|
579
|
+
let mutexLocked = false;
|
|
580
|
+
try {
|
|
581
|
+
await globalStateMutex.lock();
|
|
582
|
+
mutexLocked = true;
|
|
543
583
|
packageLogger.info('Would check for inter-project dependency updates before publish...');
|
|
544
584
|
const versionSnapshot = [
|
|
545
585
|
...publishedVersions
|
|
546
586
|
]; // Create safe copy
|
|
547
587
|
globalStateMutex.unlock();
|
|
588
|
+
mutexLocked = false;
|
|
548
589
|
await updateInterProjectDependencies(packageDir, versionSnapshot, allPackageNames, packageLogger, isDryRun);
|
|
549
|
-
}
|
|
550
|
-
|
|
590
|
+
} catch (error) {
|
|
591
|
+
if (mutexLocked) {
|
|
592
|
+
globalStateMutex.unlock();
|
|
593
|
+
}
|
|
594
|
+
throw error;
|
|
551
595
|
}
|
|
552
|
-
} catch (error) {
|
|
553
|
-
globalStateMutex.unlock();
|
|
554
|
-
throw error;
|
|
555
596
|
}
|
|
556
597
|
// Use main logger for the specific message tests expect
|
|
557
598
|
logger.info(`DRY RUN: Would execute: ${commandToRun}`);
|
|
@@ -562,6 +603,16 @@ const executePackage = async (packageName, packageInfo, commandToRun, runConfig,
|
|
|
562
603
|
// Change to the package directory and run the command
|
|
563
604
|
const originalCwd = process.cwd();
|
|
564
605
|
try {
|
|
606
|
+
// Validate package directory exists before changing to it
|
|
607
|
+
try {
|
|
608
|
+
await fs__default.access(packageDir);
|
|
609
|
+
const stat = await fs__default.stat(packageDir);
|
|
610
|
+
if (!stat.isDirectory()) {
|
|
611
|
+
throw new Error(`Path is not a directory: ${packageDir}`);
|
|
612
|
+
}
|
|
613
|
+
} catch (accessError) {
|
|
614
|
+
throw new Error(`Cannot access package directory: ${packageDir} - ${accessError.message}`);
|
|
615
|
+
}
|
|
565
616
|
process.chdir(packageDir);
|
|
566
617
|
if (runConfig.debug) {
|
|
567
618
|
packageLogger.debug(`Changed to directory: ${packageDir}`);
|
|
@@ -610,12 +661,19 @@ const executePackage = async (packageName, packageInfo, commandToRun, runConfig,
|
|
|
610
661
|
if (isBuiltInCommand && commandToRun.includes('publish')) {
|
|
611
662
|
const publishedVersion = await extractPublishedVersion(packageDir, packageLogger);
|
|
612
663
|
if (publishedVersion) {
|
|
613
|
-
|
|
664
|
+
let mutexLocked = false;
|
|
614
665
|
try {
|
|
666
|
+
await globalStateMutex.lock();
|
|
667
|
+
mutexLocked = true;
|
|
615
668
|
publishedVersions.push(publishedVersion);
|
|
616
669
|
packageLogger.info(`Tracked published version: ${publishedVersion.packageName}@${publishedVersion.version}`);
|
|
617
|
-
} finally{
|
|
618
670
|
globalStateMutex.unlock();
|
|
671
|
+
mutexLocked = false;
|
|
672
|
+
} catch (error) {
|
|
673
|
+
if (mutexLocked) {
|
|
674
|
+
globalStateMutex.unlock();
|
|
675
|
+
}
|
|
676
|
+
throw error;
|
|
619
677
|
}
|
|
620
678
|
}
|
|
621
679
|
}
|
|
@@ -626,9 +684,20 @@ const executePackage = async (packageName, packageInfo, commandToRun, runConfig,
|
|
|
626
684
|
logger.info(`[${index + 1}/${total}] ${packageName}: ✅ Completed`);
|
|
627
685
|
}
|
|
628
686
|
} finally{
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
687
|
+
// Safely restore working directory
|
|
688
|
+
try {
|
|
689
|
+
// Validate original directory still exists before changing back
|
|
690
|
+
const fs = await import('fs/promises');
|
|
691
|
+
await fs.access(originalCwd);
|
|
692
|
+
process.chdir(originalCwd);
|
|
693
|
+
if (runConfig.debug) {
|
|
694
|
+
packageLogger.debug(`Restored working directory to: ${originalCwd}`);
|
|
695
|
+
}
|
|
696
|
+
} catch (restoreError) {
|
|
697
|
+
// If we can't restore to original directory, at least log the issue
|
|
698
|
+
packageLogger.error(`Failed to restore working directory to ${originalCwd}: ${restoreError.message}`);
|
|
699
|
+
packageLogger.error(`Current working directory is now: ${process.cwd()}`);
|
|
700
|
+
// Don't throw here to avoid masking the original error
|
|
632
701
|
}
|
|
633
702
|
}
|
|
634
703
|
}
|
|
@@ -661,11 +730,18 @@ const execute = async (runConfig)=>{
|
|
|
661
730
|
logger.info(`Started: ${savedContext.startTime.toISOString()}`);
|
|
662
731
|
logger.info(`Previously completed: ${savedContext.completedPackages.length}/${savedContext.buildOrder.length} packages`);
|
|
663
732
|
// Restore state safely
|
|
664
|
-
|
|
733
|
+
let mutexLocked = false;
|
|
665
734
|
try {
|
|
735
|
+
await globalStateMutex.lock();
|
|
736
|
+
mutexLocked = true;
|
|
666
737
|
publishedVersions = savedContext.publishedVersions;
|
|
667
|
-
} finally{
|
|
668
738
|
globalStateMutex.unlock();
|
|
739
|
+
mutexLocked = false;
|
|
740
|
+
} catch (error) {
|
|
741
|
+
if (mutexLocked) {
|
|
742
|
+
globalStateMutex.unlock();
|
|
743
|
+
}
|
|
744
|
+
throw error;
|
|
669
745
|
}
|
|
670
746
|
executionContext = savedContext;
|
|
671
747
|
// Use original config but allow some overrides (like dry run)
|
|
@@ -688,7 +764,9 @@ const execute = async (runConfig)=>{
|
|
|
688
764
|
'commit',
|
|
689
765
|
'publish',
|
|
690
766
|
'link',
|
|
691
|
-
'unlink'
|
|
767
|
+
'unlink',
|
|
768
|
+
'development',
|
|
769
|
+
'branches'
|
|
692
770
|
];
|
|
693
771
|
if (builtInCommand && !supportedBuiltInCommands.includes(builtInCommand)) {
|
|
694
772
|
throw new Error(`Unsupported built-in command: ${builtInCommand}. Supported commands: ${supportedBuiltInCommands.join(', ')}`);
|
|
@@ -703,7 +781,7 @@ const execute = async (runConfig)=>{
|
|
|
703
781
|
logger.info(`${isDryRun ? 'DRY RUN: ' : ''}Analyzing workspaces at: ${targetDirectories.join(', ')}`);
|
|
704
782
|
}
|
|
705
783
|
try {
|
|
706
|
-
var _runConfig_tree3, _runConfig_tree4, _runConfig_tree5, _runConfig_tree6;
|
|
784
|
+
var _runConfig_tree3, _runConfig_tree4, _runConfig_tree5, _runConfig_tree6, _runConfig_tree7;
|
|
707
785
|
// Get exclusion patterns from config, fallback to empty array
|
|
708
786
|
const excludedPatterns = ((_runConfig_tree3 = runConfig.tree) === null || _runConfig_tree3 === void 0 ? void 0 : _runConfig_tree3.excludedPatterns) || [];
|
|
709
787
|
if (excludedPatterns.length > 0) {
|
|
@@ -781,13 +859,335 @@ const execute = async (runConfig)=>{
|
|
|
781
859
|
logger.info(`${isDryRun ? 'DRY RUN: ' : ''}Resuming from '${startFrom}' - skipping ${skippedCount} package${skippedCount === 1 ? '' : 's'}`);
|
|
782
860
|
}
|
|
783
861
|
}
|
|
862
|
+
// Handle stop-at functionality if specified
|
|
863
|
+
const stopAt = (_runConfig_tree5 = runConfig.tree) === null || _runConfig_tree5 === void 0 ? void 0 : _runConfig_tree5.stopAt;
|
|
864
|
+
if (stopAt) {
|
|
865
|
+
logger.verbose(`${isDryRun ? 'DRY RUN: ' : ''}Looking for stop package: ${stopAt}`);
|
|
866
|
+
// Find the package that matches the stopAt directory name
|
|
867
|
+
const stopIndex = buildOrder.findIndex((packageName)=>{
|
|
868
|
+
const packageInfo = dependencyGraph.packages.get(packageName);
|
|
869
|
+
const dirName = path__default.basename(packageInfo.path);
|
|
870
|
+
return dirName === stopAt || packageName === stopAt;
|
|
871
|
+
});
|
|
872
|
+
if (stopIndex === -1) {
|
|
873
|
+
// Check if the package exists but was excluded across all directories
|
|
874
|
+
let allPackageJsonPathsForCheck = [];
|
|
875
|
+
for (const targetDirectory of targetDirectories){
|
|
876
|
+
const packageJsonPaths = await scanForPackageJsonFiles(targetDirectory, []); // No exclusions
|
|
877
|
+
allPackageJsonPathsForCheck = allPackageJsonPathsForCheck.concat(packageJsonPaths);
|
|
878
|
+
}
|
|
879
|
+
let wasExcluded = false;
|
|
880
|
+
for (const packageJsonPath of allPackageJsonPathsForCheck){
|
|
881
|
+
try {
|
|
882
|
+
const packageInfo = await parsePackageJson(packageJsonPath);
|
|
883
|
+
const dirName = path__default.basename(packageInfo.path);
|
|
884
|
+
if (dirName === stopAt || packageInfo.name === stopAt) {
|
|
885
|
+
// Check if this package was excluded
|
|
886
|
+
if (shouldExclude(packageJsonPath, excludedPatterns)) {
|
|
887
|
+
wasExcluded = true;
|
|
888
|
+
break;
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
} catch {
|
|
892
|
+
continue;
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
if (wasExcluded) {
|
|
896
|
+
const excludedPatternsStr = excludedPatterns.join(', ');
|
|
897
|
+
throw new Error(`Package directory '${stopAt}' was excluded by exclusion patterns: ${excludedPatternsStr}. Remove the exclusion pattern or choose a different stop package.`);
|
|
898
|
+
} else {
|
|
899
|
+
const availablePackages = buildOrder.map((name)=>{
|
|
900
|
+
const packageInfo = dependencyGraph.packages.get(name);
|
|
901
|
+
return `${path__default.basename(packageInfo.path)} (${name})`;
|
|
902
|
+
}).join(', ');
|
|
903
|
+
throw new Error(`Package directory '${stopAt}' not found. Available packages: ${availablePackages}`);
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
// Truncate the build order before the stop package (the stop package is not executed)
|
|
907
|
+
const originalLength = buildOrder.length;
|
|
908
|
+
buildOrder = buildOrder.slice(0, stopIndex);
|
|
909
|
+
const stoppedCount = originalLength - stopIndex;
|
|
910
|
+
if (stoppedCount > 0) {
|
|
911
|
+
logger.info(`${isDryRun ? 'DRY RUN: ' : ''}Stopping before '${stopAt}' - excluding ${stoppedCount} package${stoppedCount === 1 ? '' : 's'}`);
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
// Helper function to determine version scope indicator
|
|
915
|
+
const getVersionScopeIndicator = (versionRange)=>{
|
|
916
|
+
// Remove whitespace and check the pattern
|
|
917
|
+
const cleanRange = versionRange.trim();
|
|
918
|
+
// Preserve the original prefix (^, ~, >=, etc.)
|
|
919
|
+
const prefixMatch = cleanRange.match(/^([^0-9]*)/);
|
|
920
|
+
const prefix = prefixMatch ? prefixMatch[1] : '';
|
|
921
|
+
// Extract the version part after the prefix
|
|
922
|
+
const versionPart = cleanRange.substring(prefix.length);
|
|
923
|
+
// Count the number of dots to determine scope
|
|
924
|
+
const dotCount = (versionPart.match(/\./g) || []).length;
|
|
925
|
+
if (dotCount >= 2) {
|
|
926
|
+
// Has patch version (e.g., "^4.4.32" -> "^P")
|
|
927
|
+
return prefix + 'P';
|
|
928
|
+
} else if (dotCount === 1) {
|
|
929
|
+
// Has minor version only (e.g., "^4.4" -> "^m")
|
|
930
|
+
return prefix + 'm';
|
|
931
|
+
} else if (dotCount === 0 && versionPart.match(/^\d+$/)) {
|
|
932
|
+
// Has major version only (e.g., "^4" -> "^M")
|
|
933
|
+
return prefix + 'M';
|
|
934
|
+
}
|
|
935
|
+
// For complex ranges or non-standard formats, return as-is
|
|
936
|
+
return cleanRange;
|
|
937
|
+
};
|
|
938
|
+
// Helper function to find packages that consume a given package
|
|
939
|
+
const findConsumingPackagesForBranches = async (targetPackageName, allPackages, storage)=>{
|
|
940
|
+
const consumers = [];
|
|
941
|
+
// Extract scope from target package name (e.g., "@fjell/eslint-config" -> "@fjell/")
|
|
942
|
+
const targetScope = targetPackageName.includes('/') ? targetPackageName.split('/')[0] + '/' : null;
|
|
943
|
+
for (const [packageName, packageInfo] of allPackages){
|
|
944
|
+
if (packageName === targetPackageName) continue;
|
|
945
|
+
try {
|
|
946
|
+
const packageJsonPath = path__default.join(packageInfo.path, 'package.json');
|
|
947
|
+
const packageJsonContent = await storage.readFile(packageJsonPath, 'utf-8');
|
|
948
|
+
const parsed = safeJsonParse(packageJsonContent, packageJsonPath);
|
|
949
|
+
const packageJson = validatePackageJson(parsed, packageJsonPath);
|
|
950
|
+
// Check if this package depends on the target package and get the version range
|
|
951
|
+
const dependencyTypes = [
|
|
952
|
+
'dependencies',
|
|
953
|
+
'devDependencies',
|
|
954
|
+
'peerDependencies',
|
|
955
|
+
'optionalDependencies'
|
|
956
|
+
];
|
|
957
|
+
let versionRange = null;
|
|
958
|
+
for (const depType of dependencyTypes){
|
|
959
|
+
if (packageJson[depType] && packageJson[depType][targetPackageName]) {
|
|
960
|
+
versionRange = packageJson[depType][targetPackageName];
|
|
961
|
+
break;
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
if (versionRange) {
|
|
965
|
+
// Apply scope substitution for consumers in the same scope
|
|
966
|
+
let consumerDisplayName = packageName;
|
|
967
|
+
if (targetScope && packageName.startsWith(targetScope)) {
|
|
968
|
+
// Replace scope with "./" (e.g., "@fjell/core" -> "./core")
|
|
969
|
+
consumerDisplayName = './' + packageName.substring(targetScope.length);
|
|
970
|
+
}
|
|
971
|
+
// Add version scope indicator
|
|
972
|
+
const scopeIndicator = getVersionScopeIndicator(versionRange);
|
|
973
|
+
consumerDisplayName += ` (${scopeIndicator})`;
|
|
974
|
+
consumers.push(consumerDisplayName);
|
|
975
|
+
}
|
|
976
|
+
} catch {
|
|
977
|
+
continue;
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
return consumers.sort();
|
|
981
|
+
};
|
|
982
|
+
// Handle special "branches" command that displays table
|
|
983
|
+
if (builtInCommand === 'branches') {
|
|
984
|
+
logger.info(`${isDryRun ? 'DRY RUN: ' : ''}Branch Status Summary:`);
|
|
985
|
+
logger.info('');
|
|
986
|
+
// Calculate column widths for nice formatting
|
|
987
|
+
let maxNameLength = 'Package'.length;
|
|
988
|
+
let maxBranchLength = 'Branch'.length;
|
|
989
|
+
let maxVersionLength = 'Version'.length;
|
|
990
|
+
let maxStatusLength = 'Status'.length;
|
|
991
|
+
let maxLinkLength = 'Linked'.length;
|
|
992
|
+
let maxConsumersLength = 'Consumers'.length;
|
|
993
|
+
const branchInfos = [];
|
|
994
|
+
// Create storage instance for consumer lookup
|
|
995
|
+
const storage = create({
|
|
996
|
+
log: ()=>{}
|
|
997
|
+
});
|
|
998
|
+
// Get globally linked packages once at the beginning
|
|
999
|
+
const globallyLinkedPackages = await getGloballyLinkedPackages();
|
|
1000
|
+
// ANSI escape codes for progress display
|
|
1001
|
+
const ANSI = {
|
|
1002
|
+
CURSOR_UP: '\x1b[1A',
|
|
1003
|
+
CURSOR_TO_START: '\x1b[0G',
|
|
1004
|
+
CLEAR_LINE: '\x1b[2K',
|
|
1005
|
+
GREEN: '\x1b[32m',
|
|
1006
|
+
BLUE: '\x1b[34m',
|
|
1007
|
+
YELLOW: '\x1b[33m',
|
|
1008
|
+
RESET: '\x1b[0m',
|
|
1009
|
+
BOLD: '\x1b[1m'
|
|
1010
|
+
};
|
|
1011
|
+
// Check if terminal supports ANSI
|
|
1012
|
+
const supportsAnsi = process.stdout.isTTY && process.env.TERM !== 'dumb' && !process.env.NO_COLOR;
|
|
1013
|
+
const totalPackages = buildOrder.length;
|
|
1014
|
+
const concurrency = 5; // Process up to 5 packages at a time
|
|
1015
|
+
let completedCount = 0;
|
|
1016
|
+
let isFirstProgress = true;
|
|
1017
|
+
// Function to update progress display
|
|
1018
|
+
const updateProgress = (currentPackage, completed, total)=>{
|
|
1019
|
+
if (!supportsAnsi) return;
|
|
1020
|
+
if (!isFirstProgress) {
|
|
1021
|
+
// Move cursor up and clear the line
|
|
1022
|
+
process.stdout.write(ANSI.CURSOR_UP + ANSI.CURSOR_TO_START + ANSI.CLEAR_LINE);
|
|
1023
|
+
}
|
|
1024
|
+
const percentage = Math.round(completed / total * 100);
|
|
1025
|
+
const progressBar = '█'.repeat(Math.floor(percentage / 5)) + '░'.repeat(20 - Math.floor(percentage / 5));
|
|
1026
|
+
const progress = `${ANSI.BLUE}${ANSI.BOLD}Analyzing packages... ${ANSI.GREEN}[${progressBar}] ${percentage}%${ANSI.RESET} ${ANSI.YELLOW}(${completed}/${total})${ANSI.RESET}`;
|
|
1027
|
+
const current = currentPackage ? ` - Currently: ${currentPackage}` : '';
|
|
1028
|
+
process.stdout.write(progress + current + '\n');
|
|
1029
|
+
isFirstProgress = false;
|
|
1030
|
+
};
|
|
1031
|
+
// Function to process a single package
|
|
1032
|
+
const processPackage = async (packageName)=>{
|
|
1033
|
+
const packageInfo = dependencyGraph.packages.get(packageName);
|
|
1034
|
+
try {
|
|
1035
|
+
// Process git status and consumers in parallel
|
|
1036
|
+
const [gitStatus, consumers] = await Promise.all([
|
|
1037
|
+
getGitStatusSummary(packageInfo.path),
|
|
1038
|
+
findConsumingPackagesForBranches(packageName, dependencyGraph.packages, storage)
|
|
1039
|
+
]);
|
|
1040
|
+
// Check if this package is globally linked (available to be linked to)
|
|
1041
|
+
const isGloballyLinked = globallyLinkedPackages.has(packageName);
|
|
1042
|
+
const linkedText = isGloballyLinked ? '✓' : '';
|
|
1043
|
+
// Add asterisk to consumers that are actively linking to globally linked packages
|
|
1044
|
+
// and check for link problems to highlight in red
|
|
1045
|
+
const consumersWithLinkStatus = await Promise.all(consumers.map(async (consumer)=>{
|
|
1046
|
+
// Extract the base consumer name from the format "package-name (^P)" or "./scoped-name (^m)"
|
|
1047
|
+
const baseConsumerName = consumer.replace(/ \([^)]+\)$/, ''); // Remove version scope indicator
|
|
1048
|
+
// Get the original package name from display name (remove scope substitution)
|
|
1049
|
+
const originalConsumerName = baseConsumerName.startsWith('./') ? baseConsumerName.replace('./', packageName.split('/')[0] + '/') : baseConsumerName;
|
|
1050
|
+
// Find the consumer package info to get its path
|
|
1051
|
+
const consumerPackageInfo = Array.from(dependencyGraph.packages.values()).find((pkg)=>pkg.name === originalConsumerName);
|
|
1052
|
+
if (consumerPackageInfo) {
|
|
1053
|
+
const [consumerLinkedDeps, linkProblems] = await Promise.all([
|
|
1054
|
+
getLinkedDependencies(consumerPackageInfo.path),
|
|
1055
|
+
getLinkCompatibilityProblems(consumerPackageInfo.path, dependencyGraph.packages)
|
|
1056
|
+
]);
|
|
1057
|
+
let consumerDisplay = consumer;
|
|
1058
|
+
// Add asterisk if this consumer is actively linking to this package
|
|
1059
|
+
if (consumerLinkedDeps.has(packageName)) {
|
|
1060
|
+
consumerDisplay += '*';
|
|
1061
|
+
}
|
|
1062
|
+
// Check if this consumer has link problems with the current package
|
|
1063
|
+
if (linkProblems.has(packageName)) {
|
|
1064
|
+
// Highlight in red using ANSI escape codes (only if terminal supports it)
|
|
1065
|
+
if (supportsAnsi) {
|
|
1066
|
+
consumerDisplay = `\x1b[31m${consumerDisplay}\x1b[0m`;
|
|
1067
|
+
} else {
|
|
1068
|
+
// Fallback for terminals that don't support ANSI colors
|
|
1069
|
+
consumerDisplay += ' [LINK PROBLEM]';
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
return consumerDisplay;
|
|
1073
|
+
}
|
|
1074
|
+
return consumer;
|
|
1075
|
+
}));
|
|
1076
|
+
return {
|
|
1077
|
+
name: packageName,
|
|
1078
|
+
branch: gitStatus.branch,
|
|
1079
|
+
version: packageInfo.version,
|
|
1080
|
+
status: gitStatus.status,
|
|
1081
|
+
linked: linkedText,
|
|
1082
|
+
consumers: consumersWithLinkStatus
|
|
1083
|
+
};
|
|
1084
|
+
} catch (error) {
|
|
1085
|
+
logger.warn(`Failed to get git status for ${packageName}: ${error.message}`);
|
|
1086
|
+
return {
|
|
1087
|
+
name: packageName,
|
|
1088
|
+
branch: 'error',
|
|
1089
|
+
version: packageInfo.version,
|
|
1090
|
+
status: 'error',
|
|
1091
|
+
linked: '✗',
|
|
1092
|
+
consumers: [
|
|
1093
|
+
'error'
|
|
1094
|
+
]
|
|
1095
|
+
};
|
|
1096
|
+
}
|
|
1097
|
+
};
|
|
1098
|
+
// Process packages in batches with progress updates
|
|
1099
|
+
updateProgress('Starting...', 0, totalPackages);
|
|
1100
|
+
for(let i = 0; i < buildOrder.length; i += concurrency){
|
|
1101
|
+
const batch = buildOrder.slice(i, i + concurrency);
|
|
1102
|
+
// Update progress to show current batch
|
|
1103
|
+
const currentBatchStr = batch.length === 1 ? batch[0] : `${batch[0]} + ${batch.length - 1} others`;
|
|
1104
|
+
updateProgress(currentBatchStr, completedCount, totalPackages);
|
|
1105
|
+
// Process batch in parallel
|
|
1106
|
+
const batchResults = await Promise.all(batch.map((packageName)=>processPackage(packageName)));
|
|
1107
|
+
// Add results and update column widths
|
|
1108
|
+
for (const result of batchResults){
|
|
1109
|
+
branchInfos.push(result);
|
|
1110
|
+
maxNameLength = Math.max(maxNameLength, result.name.length);
|
|
1111
|
+
maxBranchLength = Math.max(maxBranchLength, result.branch.length);
|
|
1112
|
+
maxVersionLength = Math.max(maxVersionLength, result.version.length);
|
|
1113
|
+
maxStatusLength = Math.max(maxStatusLength, result.status.length);
|
|
1114
|
+
maxLinkLength = Math.max(maxLinkLength, result.linked.length);
|
|
1115
|
+
// For consumers, calculate the width based on the longest consumer name
|
|
1116
|
+
const maxConsumerLength = result.consumers.length > 0 ? Math.max(...result.consumers.map((c)=>c.length)) : 0;
|
|
1117
|
+
maxConsumersLength = Math.max(maxConsumersLength, maxConsumerLength);
|
|
1118
|
+
}
|
|
1119
|
+
completedCount += batch.length;
|
|
1120
|
+
updateProgress('', completedCount, totalPackages);
|
|
1121
|
+
}
|
|
1122
|
+
// Clear progress line and add spacing
|
|
1123
|
+
if (supportsAnsi && !isFirstProgress) {
|
|
1124
|
+
process.stdout.write(ANSI.CURSOR_UP + ANSI.CURSOR_TO_START + ANSI.CLEAR_LINE);
|
|
1125
|
+
}
|
|
1126
|
+
logger.info(`${ANSI.GREEN}✅ Analysis complete!${ANSI.RESET} Processed ${totalPackages} packages in batches of ${concurrency}.`);
|
|
1127
|
+
logger.info('');
|
|
1128
|
+
// Print header (new order: Package | Branch | Version | Status | Linked | Consumers)
|
|
1129
|
+
const nameHeader = 'Package'.padEnd(maxNameLength);
|
|
1130
|
+
const branchHeader = 'Branch'.padEnd(maxBranchLength);
|
|
1131
|
+
const versionHeader = 'Version'.padEnd(maxVersionLength);
|
|
1132
|
+
const statusHeader = 'Status'.padEnd(maxStatusLength);
|
|
1133
|
+
const linkHeader = 'Linked'.padEnd(maxLinkLength);
|
|
1134
|
+
const consumersHeader = 'Consumers';
|
|
1135
|
+
logger.info(`${nameHeader} | ${branchHeader} | ${versionHeader} | ${statusHeader} | ${linkHeader} | ${consumersHeader}`);
|
|
1136
|
+
logger.info(`${'-'.repeat(maxNameLength)} | ${'-'.repeat(maxBranchLength)} | ${'-'.repeat(maxVersionLength)} | ${'-'.repeat(maxStatusLength)} | ${'-'.repeat(maxLinkLength)} | ${'-'.repeat(9)}`);
|
|
1137
|
+
// Print data rows with multi-line consumers
|
|
1138
|
+
for (const info of branchInfos){
|
|
1139
|
+
const nameCol = info.name.padEnd(maxNameLength);
|
|
1140
|
+
const branchCol = info.branch.padEnd(maxBranchLength);
|
|
1141
|
+
const versionCol = info.version.padEnd(maxVersionLength);
|
|
1142
|
+
const statusCol = info.status.padEnd(maxStatusLength);
|
|
1143
|
+
const linkCol = info.linked.padEnd(maxLinkLength);
|
|
1144
|
+
if (info.consumers.length === 0) {
|
|
1145
|
+
// No consumers - single line
|
|
1146
|
+
logger.info(`${nameCol} | ${branchCol} | ${versionCol} | ${statusCol} | ${linkCol} | `);
|
|
1147
|
+
} else if (info.consumers.length === 1) {
|
|
1148
|
+
// Single consumer - single line
|
|
1149
|
+
logger.info(`${nameCol} | ${branchCol} | ${versionCol} | ${statusCol} | ${linkCol} | ${info.consumers[0]}`);
|
|
1150
|
+
} else {
|
|
1151
|
+
// Multiple consumers - first consumer on same line, rest on new lines with continuous column separators
|
|
1152
|
+
logger.info(`${nameCol} | ${branchCol} | ${versionCol} | ${statusCol} | ${linkCol} | ${info.consumers[0]}`);
|
|
1153
|
+
// Additional consumers on separate lines with proper column separators
|
|
1154
|
+
const emptyNameCol = ' '.repeat(maxNameLength);
|
|
1155
|
+
const emptyBranchCol = ' '.repeat(maxBranchLength);
|
|
1156
|
+
const emptyVersionCol = ' '.repeat(maxVersionLength);
|
|
1157
|
+
const emptyStatusCol = ' '.repeat(maxStatusLength);
|
|
1158
|
+
const emptyLinkCol = ' '.repeat(maxLinkLength);
|
|
1159
|
+
for(let i = 1; i < info.consumers.length; i++){
|
|
1160
|
+
logger.info(`${emptyNameCol} | ${emptyBranchCol} | ${emptyVersionCol} | ${emptyStatusCol} | ${emptyLinkCol} | ${info.consumers[i]}`);
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
logger.info('');
|
|
1165
|
+
// Add legend explaining the symbols and colors
|
|
1166
|
+
logger.info('Legend:');
|
|
1167
|
+
logger.info(' * = Consumer is actively linking to this package');
|
|
1168
|
+
logger.info(' (^P) = Patch-level dependency (e.g., "^4.4.32")');
|
|
1169
|
+
logger.info(' (^m) = Minor-level dependency (e.g., "^4.4")');
|
|
1170
|
+
logger.info(' (^M) = Major-level dependency (e.g., "^4")');
|
|
1171
|
+
logger.info(' (~P), (>=M), etc. = Other version prefixes preserved');
|
|
1172
|
+
if (supportsAnsi) {
|
|
1173
|
+
logger.info(' \x1b[31mRed text\x1b[0m = Consumer has link problems (version mismatches) with this package');
|
|
1174
|
+
} else {
|
|
1175
|
+
logger.info(' [LINK PROBLEM] = Consumer has link problems (version mismatches) with this package');
|
|
1176
|
+
}
|
|
1177
|
+
logger.info('');
|
|
1178
|
+
return `Branch status summary for ${branchInfos.length} packages completed.`;
|
|
1179
|
+
}
|
|
784
1180
|
// Display results
|
|
785
1181
|
logger.info(`${isDryRun ? 'DRY RUN: ' : ''}Build order determined:`);
|
|
786
1182
|
let returnOutput = '';
|
|
787
1183
|
if (runConfig.verbose || runConfig.debug) {
|
|
788
1184
|
// Verbose mode: Skip simple format, show detailed format before command execution
|
|
789
1185
|
logger.info(''); // Add spacing
|
|
790
|
-
|
|
1186
|
+
const rangeInfo = [];
|
|
1187
|
+
if (startFrom) rangeInfo.push(`starting from ${startFrom}`);
|
|
1188
|
+
if (stopAt) rangeInfo.push(`stopping before ${stopAt}`);
|
|
1189
|
+
const rangeStr = rangeInfo.length > 0 ? ` (${rangeInfo.join(', ')})` : '';
|
|
1190
|
+
logger.info(`Detailed Build Order for ${buildOrder.length} packages${rangeStr}:`);
|
|
791
1191
|
logger.info('==========================================');
|
|
792
1192
|
buildOrder.forEach((packageName, index)=>{
|
|
793
1193
|
const packageInfo = dependencyGraph.packages.get(packageName);
|
|
@@ -819,14 +1219,37 @@ const execute = async (runConfig)=>{
|
|
|
819
1219
|
returnOutput = `\nBuild order: ${buildOrder.join(' → ')}\n`;
|
|
820
1220
|
}
|
|
821
1221
|
// Execute command if provided (custom command or built-in command)
|
|
822
|
-
const cmd = (
|
|
823
|
-
const useParallel = ((
|
|
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;
|
|
824
1224
|
// Determine command to execute
|
|
825
1225
|
let commandToRun;
|
|
826
1226
|
let isBuiltInCommand = false;
|
|
827
1227
|
if (builtInCommand) {
|
|
1228
|
+
var _runConfig_tree8, _runConfig_tree9;
|
|
828
1229
|
// Built-in command mode: shell out to kodrdriv subprocess
|
|
829
|
-
|
|
1230
|
+
// Build command with propagated global options
|
|
1231
|
+
const globalOptions = [];
|
|
1232
|
+
// Propagate global flags that should be inherited by subprocesses
|
|
1233
|
+
if (runConfig.debug) globalOptions.push('--debug');
|
|
1234
|
+
if (runConfig.verbose) globalOptions.push('--verbose');
|
|
1235
|
+
if (runConfig.dryRun) globalOptions.push('--dry-run');
|
|
1236
|
+
if (runConfig.overrides) globalOptions.push('--overrides');
|
|
1237
|
+
// Propagate global options with values
|
|
1238
|
+
if (runConfig.model) globalOptions.push(`--model "${runConfig.model}"`);
|
|
1239
|
+
if (runConfig.configDirectory) globalOptions.push(`--config-dir "${runConfig.configDirectory}"`);
|
|
1240
|
+
if (runConfig.outputDirectory) globalOptions.push(`--output-dir "${runConfig.outputDirectory}"`);
|
|
1241
|
+
if (runConfig.preferencesDirectory) globalOptions.push(`--preferences-dir "${runConfig.preferencesDirectory}"`);
|
|
1242
|
+
// Build the command with global options
|
|
1243
|
+
const optionsString = globalOptions.length > 0 ? ` ${globalOptions.join(' ')}` : '';
|
|
1244
|
+
// Add package argument for link/unlink commands
|
|
1245
|
+
const packageArg = (_runConfig_tree8 = runConfig.tree) === null || _runConfig_tree8 === void 0 ? void 0 : _runConfig_tree8.packageArgument;
|
|
1246
|
+
const packageArgString = packageArg && (builtInCommand === 'link' || builtInCommand === 'unlink') ? ` "${packageArg}"` : '';
|
|
1247
|
+
// Add command-specific options
|
|
1248
|
+
let commandSpecificOptions = '';
|
|
1249
|
+
if (builtInCommand === 'unlink' && ((_runConfig_tree9 = runConfig.tree) === null || _runConfig_tree9 === void 0 ? void 0 : _runConfig_tree9.cleanNodeModules)) {
|
|
1250
|
+
commandSpecificOptions += ' --clean-node-modules';
|
|
1251
|
+
}
|
|
1252
|
+
commandToRun = `kodrdriv ${builtInCommand}${optionsString}${packageArgString}${commandSpecificOptions}`;
|
|
830
1253
|
isBuiltInCommand = true;
|
|
831
1254
|
} else if (cmd) {
|
|
832
1255
|
// Custom command mode
|
|
@@ -1051,6 +1474,9 @@ const execute = async (runConfig)=>{
|
|
|
1051
1474
|
const errorMessage = `Failed to analyze workspace: ${error.message}`;
|
|
1052
1475
|
logger.error(errorMessage);
|
|
1053
1476
|
throw new Error(errorMessage);
|
|
1477
|
+
} finally{
|
|
1478
|
+
// Clean up mutex resources to prevent memory leaks
|
|
1479
|
+
globalStateMutex.destroy();
|
|
1054
1480
|
}
|
|
1055
1481
|
};
|
|
1056
1482
|
|