@eldrforge/kodrdriv 0.0.51 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/README.md +3 -1
  2. package/dist/application.js +29 -5
  3. package/dist/application.js.map +1 -1
  4. package/dist/arguments.js +187 -75
  5. package/dist/arguments.js.map +1 -1
  6. package/dist/commands/audio-commit.js +35 -14
  7. package/dist/commands/audio-commit.js.map +1 -1
  8. package/dist/commands/audio-review.js +41 -20
  9. package/dist/commands/audio-review.js.map +1 -1
  10. package/dist/commands/clean.js +2 -2
  11. package/dist/commands/clean.js.map +1 -1
  12. package/dist/commands/commit.js +369 -47
  13. package/dist/commands/commit.js.map +1 -1
  14. package/dist/commands/development.js +264 -0
  15. package/dist/commands/development.js.map +1 -0
  16. package/dist/commands/link.js +357 -182
  17. package/dist/commands/link.js.map +1 -1
  18. package/dist/commands/publish.js +419 -306
  19. package/dist/commands/publish.js.map +1 -1
  20. package/dist/commands/release.js +240 -18
  21. package/dist/commands/release.js.map +1 -1
  22. package/dist/commands/review.js +56 -40
  23. package/dist/commands/review.js.map +1 -1
  24. package/dist/commands/select-audio.js +4 -4
  25. package/dist/commands/select-audio.js.map +1 -1
  26. package/dist/commands/tree.js +779 -40
  27. package/dist/commands/tree.js.map +1 -1
  28. package/dist/commands/unlink.js +267 -372
  29. package/dist/commands/unlink.js.map +1 -1
  30. package/dist/commands/versions.js +224 -0
  31. package/dist/commands/versions.js.map +1 -0
  32. package/dist/constants.js +50 -12
  33. package/dist/constants.js.map +1 -1
  34. package/dist/content/diff.js +122 -1
  35. package/dist/content/diff.js.map +1 -1
  36. package/dist/content/files.js +192 -0
  37. package/dist/content/files.js.map +1 -0
  38. package/dist/content/issues.js +17 -46
  39. package/dist/content/issues.js.map +1 -1
  40. package/dist/content/log.js +16 -0
  41. package/dist/content/log.js.map +1 -1
  42. package/dist/logging.js +3 -3
  43. package/dist/logging.js.map +1 -1
  44. package/dist/main.js +0 -0
  45. package/dist/prompt/commit.js +11 -4
  46. package/dist/prompt/commit.js.map +1 -1
  47. package/dist/prompt/instructions/commit.md +20 -2
  48. package/dist/prompt/instructions/release.md +27 -10
  49. package/dist/prompt/instructions/review.md +75 -8
  50. package/dist/prompt/release.js +15 -7
  51. package/dist/prompt/release.js.map +1 -1
  52. package/dist/prompt/review.js +2 -2
  53. package/dist/prompt/review.js.map +1 -1
  54. package/dist/types.js +36 -7
  55. package/dist/types.js.map +1 -1
  56. package/dist/util/child.js +146 -4
  57. package/dist/util/child.js.map +1 -1
  58. package/dist/util/countdown.js +215 -0
  59. package/dist/util/countdown.js.map +1 -0
  60. package/dist/util/general.js +157 -13
  61. package/dist/util/general.js.map +1 -1
  62. package/dist/util/git.js +587 -0
  63. package/dist/util/git.js.map +1 -0
  64. package/dist/util/github.js +531 -11
  65. package/dist/util/github.js.map +1 -1
  66. package/dist/util/interactive.js +463 -0
  67. package/dist/util/interactive.js.map +1 -0
  68. package/dist/util/openai.js +152 -25
  69. package/dist/util/openai.js.map +1 -1
  70. package/dist/util/performance.js +5 -73
  71. package/dist/util/performance.js.map +1 -1
  72. package/dist/util/safety.js +4 -4
  73. package/dist/util/safety.js.map +1 -1
  74. package/dist/util/storage.js +30 -3
  75. package/dist/util/storage.js.map +1 -1
  76. package/dist/util/validation.js +1 -25
  77. package/dist/util/validation.js.map +1 -1
  78. package/package.json +12 -10
  79. package/test-increment.js +0 -0
  80. package/test-multiline/cli/package.json +8 -0
  81. package/test-multiline/core/package.json +5 -0
  82. package/test-multiline/mobile/package.json +8 -0
  83. package/test-multiline/web/package.json +8 -0
  84. package/dist/util/npmOptimizations.js +0 -174
  85. package/dist/util/npmOptimizations.js.map +0 -1
@@ -1,12 +1,245 @@
1
1
  #!/usr/bin/env node
2
- import path from 'path';
3
- import fs from 'fs/promises';
2
+ import path__default from 'path';
3
+ import fs__default from 'fs/promises';
4
4
  import { exec } from 'child_process';
5
5
  import util from 'util';
6
6
  import { getLogger } from '../logging.js';
7
7
  import { create } from '../util/storage.js';
8
8
  import { safeJsonParse, validatePackageJson } from '../util/validation.js';
9
+ import { getOutputPath } from '../util/general.js';
10
+ import { DEFAULT_OUTPUT_DIRECTORY } from '../constants.js';
11
+ import { execute as execute$1 } from './commit.js';
12
+ import { getGloballyLinkedPackages, getGitStatusSummary, getLinkedDependencies, getLinkCompatibilityProblems } from '../util/git.js';
9
13
 
14
+ function _define_property(obj, key, value) {
15
+ if (key in obj) {
16
+ Object.defineProperty(obj, key, {
17
+ value: value,
18
+ enumerable: true,
19
+ configurable: true,
20
+ writable: true
21
+ });
22
+ } else {
23
+ obj[key] = value;
24
+ }
25
+ return obj;
26
+ }
27
+ // Global state to track published versions during tree execution - protected by mutex
28
+ let publishedVersions = [];
29
+ let executionContext = null;
30
+ // Simple mutex to prevent race conditions in global state access
31
+ class SimpleMutex {
32
+ async lock() {
33
+ return new Promise((resolve, reject)=>{
34
+ if (this.destroyed) {
35
+ reject(new Error('Mutex has been destroyed'));
36
+ return;
37
+ }
38
+ if (!this.locked) {
39
+ this.locked = true;
40
+ resolve();
41
+ } else {
42
+ this.queue.push(resolve);
43
+ }
44
+ });
45
+ }
46
+ unlock() {
47
+ if (this.destroyed) {
48
+ return;
49
+ }
50
+ this.locked = false;
51
+ const next = this.queue.shift();
52
+ if (next) {
53
+ this.locked = true;
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
+ }
81
+ }
82
+ }
83
+ isDestroyed() {
84
+ return this.destroyed;
85
+ }
86
+ constructor(){
87
+ _define_property(this, "locked", false);
88
+ _define_property(this, "queue", []);
89
+ _define_property(this, "destroyed", false);
90
+ }
91
+ }
92
+ const globalStateMutex = new SimpleMutex();
93
+ // Update inter-project dependencies in package.json based on published versions
94
+ const updateInterProjectDependencies = async (packageDir, publishedVersions, allPackageNames, packageLogger, isDryRun)=>{
95
+ const storage = create({
96
+ log: packageLogger.info
97
+ });
98
+ const packageJsonPath = path__default.join(packageDir, 'package.json');
99
+ if (!await storage.exists(packageJsonPath)) {
100
+ packageLogger.verbose('No package.json found, skipping dependency updates');
101
+ return false;
102
+ }
103
+ let hasChanges = false;
104
+ try {
105
+ const packageJsonContent = await storage.readFile(packageJsonPath, 'utf-8');
106
+ const parsed = safeJsonParse(packageJsonContent, packageJsonPath);
107
+ const packageJson = validatePackageJson(parsed, packageJsonPath);
108
+ const sectionsToUpdate = [
109
+ 'dependencies',
110
+ 'devDependencies',
111
+ 'peerDependencies'
112
+ ];
113
+ for (const publishedVersion of publishedVersions){
114
+ const { packageName, version } = publishedVersion;
115
+ // Only update if this is an inter-project dependency (exists in our build tree)
116
+ if (!allPackageNames.has(packageName)) {
117
+ continue;
118
+ }
119
+ // Update the dependency in all relevant sections
120
+ for (const section of sectionsToUpdate){
121
+ const deps = packageJson[section];
122
+ if (deps && deps[packageName]) {
123
+ const oldVersion = deps[packageName];
124
+ const newVersion = `^${version}`;
125
+ if (oldVersion !== newVersion) {
126
+ if (isDryRun) {
127
+ packageLogger.info(`Would update ${section}.${packageName}: ${oldVersion} → ${newVersion}`);
128
+ } else {
129
+ packageLogger.info(`Updating ${section}.${packageName}: ${oldVersion} → ${newVersion}`);
130
+ deps[packageName] = newVersion;
131
+ }
132
+ hasChanges = true;
133
+ }
134
+ }
135
+ }
136
+ }
137
+ if (hasChanges && !isDryRun) {
138
+ // Write updated package.json
139
+ await storage.writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n', 'utf-8');
140
+ packageLogger.info('Inter-project dependencies updated successfully');
141
+ }
142
+ } catch (error) {
143
+ packageLogger.warn(`Failed to update inter-project dependencies: ${error.message}`);
144
+ return false;
145
+ }
146
+ return hasChanges;
147
+ };
148
+ // Get the context file path
149
+ const getContextFilePath = (outputDirectory)=>{
150
+ const outputDir = outputDirectory || DEFAULT_OUTPUT_DIRECTORY;
151
+ return getOutputPath(outputDir, '.kodrdriv-context');
152
+ };
153
+ // Save execution context to file
154
+ const saveExecutionContext = async (context, outputDirectory)=>{
155
+ const storage = create({
156
+ log: ()=>{}
157
+ }); // Silent storage for context operations
158
+ const contextFilePath = getContextFilePath(outputDirectory);
159
+ try {
160
+ // Ensure output directory exists
161
+ await storage.ensureDirectory(path__default.dirname(contextFilePath));
162
+ // Save context with JSON serialization that handles dates
163
+ const contextData = {
164
+ ...context,
165
+ startTime: context.startTime.toISOString(),
166
+ lastUpdateTime: context.lastUpdateTime.toISOString(),
167
+ publishedVersions: context.publishedVersions.map((v)=>({
168
+ ...v,
169
+ publishTime: v.publishTime.toISOString()
170
+ }))
171
+ };
172
+ await storage.writeFile(contextFilePath, JSON.stringify(contextData, null, 2), 'utf-8');
173
+ } catch (error) {
174
+ // Don't fail the entire operation if context saving fails
175
+ const logger = getLogger();
176
+ logger.warn(`Warning: Failed to save execution context: ${error.message}`);
177
+ }
178
+ };
179
+ // Load execution context from file
180
+ const loadExecutionContext = async (outputDirectory)=>{
181
+ const storage = create({
182
+ log: ()=>{}
183
+ }); // Silent storage for context operations
184
+ const contextFilePath = getContextFilePath(outputDirectory);
185
+ try {
186
+ if (!await storage.exists(contextFilePath)) {
187
+ return null;
188
+ }
189
+ const contextContent = await storage.readFile(contextFilePath, 'utf-8');
190
+ const contextData = safeJsonParse(contextContent, contextFilePath);
191
+ // Restore dates from ISO strings
192
+ return {
193
+ ...contextData,
194
+ startTime: new Date(contextData.startTime),
195
+ lastUpdateTime: new Date(contextData.lastUpdateTime),
196
+ publishedVersions: contextData.publishedVersions.map((v)=>({
197
+ ...v,
198
+ publishTime: new Date(v.publishTime)
199
+ }))
200
+ };
201
+ } catch (error) {
202
+ const logger = getLogger();
203
+ logger.warn(`Warning: Failed to load execution context: ${error.message}`);
204
+ return null;
205
+ }
206
+ };
207
+ // Clean up context file
208
+ const cleanupContext = async (outputDirectory)=>{
209
+ const storage = create({
210
+ log: ()=>{}
211
+ }); // Silent storage for context operations
212
+ const contextFilePath = getContextFilePath(outputDirectory);
213
+ try {
214
+ if (await storage.exists(contextFilePath)) {
215
+ await storage.deleteFile(contextFilePath);
216
+ }
217
+ } catch (error) {
218
+ // Don't fail if cleanup fails
219
+ const logger = getLogger();
220
+ logger.warn(`Warning: Failed to cleanup execution context: ${error.message}`);
221
+ }
222
+ };
223
+ // Extract published version from package.json after successful publish
224
+ const extractPublishedVersion = async (packageDir, packageLogger)=>{
225
+ const storage = create({
226
+ log: packageLogger.info
227
+ });
228
+ const packageJsonPath = path__default.join(packageDir, 'package.json');
229
+ try {
230
+ const packageJsonContent = await storage.readFile(packageJsonPath, 'utf-8');
231
+ const parsed = safeJsonParse(packageJsonContent, packageJsonPath);
232
+ const packageJson = validatePackageJson(parsed, packageJsonPath);
233
+ return {
234
+ packageName: packageJson.name,
235
+ version: packageJson.version,
236
+ publishTime: new Date()
237
+ };
238
+ } catch (error) {
239
+ packageLogger.warn(`Failed to extract published version: ${error.message}`);
240
+ return null;
241
+ }
242
+ };
10
243
  // Enhanced run function that can show output based on log level
11
244
  const runWithLogging = async (command, packageLogger, options = {}, showOutput = 'none')=>{
12
245
  const execPromise = util.promisify(exec);
@@ -108,24 +341,24 @@ const matchesPattern = (filePath, pattern)=>{
108
341
  .replace(/\?/g, '.') // ? matches any single character
109
342
  .replace(/\./g, '\\.'); // Escape literal dots
110
343
  const regex = new RegExp(`^${regexPattern}$`);
111
- return regex.test(filePath) || regex.test(path.basename(filePath));
344
+ return regex.test(filePath) || regex.test(path__default.basename(filePath));
112
345
  };
113
346
  const shouldExclude = (packageJsonPath, excludedPatterns)=>{
114
347
  if (!excludedPatterns || excludedPatterns.length === 0) {
115
348
  return false;
116
349
  }
117
350
  // Check both the full path and relative path patterns
118
- const relativePath = path.relative(process.cwd(), packageJsonPath);
119
- return excludedPatterns.some((pattern)=>matchesPattern(packageJsonPath, pattern) || matchesPattern(relativePath, pattern) || matchesPattern(path.dirname(packageJsonPath), pattern) || matchesPattern(path.dirname(relativePath), pattern));
351
+ const relativePath = path__default.relative(process.cwd(), packageJsonPath);
352
+ return excludedPatterns.some((pattern)=>matchesPattern(packageJsonPath, pattern) || matchesPattern(relativePath, pattern) || matchesPattern(path__default.dirname(packageJsonPath), pattern) || matchesPattern(path__default.dirname(relativePath), pattern));
120
353
  };
121
354
  const scanForPackageJsonFiles = async (directory, excludedPatterns = [])=>{
122
355
  const logger = getLogger();
123
356
  const packageJsonPaths = [];
124
357
  try {
125
358
  // First check if there's a package.json in the specified directory itself
126
- const directPackageJsonPath = path.join(directory, 'package.json');
359
+ const directPackageJsonPath = path__default.join(directory, 'package.json');
127
360
  try {
128
- await fs.access(directPackageJsonPath);
361
+ await fs__default.access(directPackageJsonPath);
129
362
  // Check if this package should be excluded
130
363
  if (!shouldExclude(directPackageJsonPath, excludedPatterns)) {
131
364
  packageJsonPaths.push(directPackageJsonPath);
@@ -137,15 +370,15 @@ const scanForPackageJsonFiles = async (directory, excludedPatterns = [])=>{
137
370
  // No package.json in the root of this directory, that's fine
138
371
  }
139
372
  // Then scan subdirectories for package.json files
140
- const entries = await fs.readdir(directory, {
373
+ const entries = await fs__default.readdir(directory, {
141
374
  withFileTypes: true
142
375
  });
143
376
  for (const entry of entries){
144
377
  if (entry.isDirectory()) {
145
- const subDirPath = path.join(directory, entry.name);
146
- const packageJsonPath = path.join(subDirPath, 'package.json');
378
+ const subDirPath = path__default.join(directory, entry.name);
379
+ const packageJsonPath = path__default.join(subDirPath, 'package.json');
147
380
  try {
148
- await fs.access(packageJsonPath);
381
+ await fs__default.access(packageJsonPath);
149
382
  // Check if this package should be excluded
150
383
  if (shouldExclude(packageJsonPath, excludedPatterns)) {
151
384
  logger.verbose(`Excluding package.json at: ${packageJsonPath} (matches exclusion pattern)`);
@@ -192,7 +425,7 @@ const parsePackageJson = async (packageJsonPath)=>{
192
425
  return {
193
426
  name: packageJson.name,
194
427
  version: packageJson.version || '0.0.0',
195
- path: path.dirname(packageJsonPath),
428
+ path: path__default.dirname(packageJsonPath),
196
429
  dependencies,
197
430
  localDependencies: new Set() // Will be populated later
198
431
  };
@@ -318,7 +551,7 @@ const groupPackagesByDependencyLevels = (graph, buildOrder, runConfig)=>{
318
551
  return levels;
319
552
  };
320
553
  // Execute a single package and return execution result
321
- const executePackage = async (packageName, packageInfo, commandToRun, runConfig, isDryRun, index, total, isBuiltInCommand = false)=>{
554
+ const executePackage = async (packageName, packageInfo, commandToRun, runConfig, isDryRun, index, total, allPackageNames, isBuiltInCommand = false)=>{
322
555
  const packageLogger = createPackageLogger(packageName, index + 1, total, isDryRun);
323
556
  const packageDir = packageInfo.path;
324
557
  const logger = getLogger();
@@ -341,6 +574,26 @@ const executePackage = async (packageName, packageInfo, commandToRun, runConfig,
341
574
  }
342
575
  try {
343
576
  if (isDryRun) {
577
+ // Handle inter-project dependency updates for publish commands in dry run mode
578
+ if (isBuiltInCommand && commandToRun.includes('publish') && publishedVersions.length > 0) {
579
+ let mutexLocked = false;
580
+ try {
581
+ await globalStateMutex.lock();
582
+ mutexLocked = true;
583
+ packageLogger.info('Would check for inter-project dependency updates before publish...');
584
+ const versionSnapshot = [
585
+ ...publishedVersions
586
+ ]; // Create safe copy
587
+ globalStateMutex.unlock();
588
+ mutexLocked = false;
589
+ await updateInterProjectDependencies(packageDir, versionSnapshot, allPackageNames, packageLogger, isDryRun);
590
+ } catch (error) {
591
+ if (mutexLocked) {
592
+ globalStateMutex.unlock();
593
+ }
594
+ throw error;
595
+ }
596
+ }
344
597
  // Use main logger for the specific message tests expect
345
598
  logger.info(`DRY RUN: Would execute: ${commandToRun}`);
346
599
  if (runConfig.debug || runConfig.verbose) {
@@ -350,10 +603,39 @@ const executePackage = async (packageName, packageInfo, commandToRun, runConfig,
350
603
  // Change to the package directory and run the command
351
604
  const originalCwd = process.cwd();
352
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
+ }
353
616
  process.chdir(packageDir);
354
617
  if (runConfig.debug) {
355
618
  packageLogger.debug(`Changed to directory: ${packageDir}`);
356
619
  }
620
+ // Handle inter-project dependency updates for publish commands before executing
621
+ if (isBuiltInCommand && commandToRun.includes('publish') && publishedVersions.length > 0) {
622
+ packageLogger.info('Updating inter-project dependencies based on previously published packages...');
623
+ const hasUpdates = await updateInterProjectDependencies(packageDir, publishedVersions, allPackageNames, packageLogger, isDryRun);
624
+ if (hasUpdates) {
625
+ // Commit the dependency updates using kodrdriv commit
626
+ packageLogger.info('Committing inter-project dependency updates...');
627
+ try {
628
+ await execute$1({
629
+ ...runConfig,
630
+ dryRun: false
631
+ });
632
+ packageLogger.info('Inter-project dependency updates committed successfully');
633
+ } catch (commitError) {
634
+ packageLogger.warn(`Failed to commit inter-project dependency updates: ${commitError.message}`);
635
+ // Continue with publish anyway - the updates are still in place
636
+ }
637
+ }
638
+ }
357
639
  if (runConfig.debug || runConfig.verbose) {
358
640
  if (isBuiltInCommand) {
359
641
  packageLogger.info(`Executing built-in command: ${commandToRun}`);
@@ -375,6 +657,26 @@ const executePackage = async (packageName, packageInfo, commandToRun, runConfig,
375
657
  // For custom commands, use the existing logic
376
658
  await runWithLogging(commandToRun, packageLogger, {}, showOutput);
377
659
  }
660
+ // Track published version after successful publish
661
+ if (isBuiltInCommand && commandToRun.includes('publish')) {
662
+ const publishedVersion = await extractPublishedVersion(packageDir, packageLogger);
663
+ if (publishedVersion) {
664
+ let mutexLocked = false;
665
+ try {
666
+ await globalStateMutex.lock();
667
+ mutexLocked = true;
668
+ publishedVersions.push(publishedVersion);
669
+ packageLogger.info(`Tracked published version: ${publishedVersion.packageName}@${publishedVersion.version}`);
670
+ globalStateMutex.unlock();
671
+ mutexLocked = false;
672
+ } catch (error) {
673
+ if (mutexLocked) {
674
+ globalStateMutex.unlock();
675
+ }
676
+ throw error;
677
+ }
678
+ }
679
+ }
378
680
  if (runConfig.debug || runConfig.verbose) {
379
681
  packageLogger.info(`Command completed successfully`);
380
682
  } else {
@@ -382,9 +684,20 @@ const executePackage = async (packageName, packageInfo, commandToRun, runConfig,
382
684
  logger.info(`[${index + 1}/${total}] ${packageName}: ✅ Completed`);
383
685
  }
384
686
  } finally{
385
- process.chdir(originalCwd);
386
- if (runConfig.debug) {
387
- packageLogger.debug(`Restored working directory to: ${originalCwd}`);
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
388
701
  }
389
702
  }
390
703
  }
@@ -404,22 +717,62 @@ const executePackage = async (packageName, packageInfo, commandToRun, runConfig,
404
717
  }
405
718
  };
406
719
  const execute = async (runConfig)=>{
407
- var _runConfig_tree, _runConfig_tree1;
720
+ var _runConfig_tree, _runConfig_tree1, _runConfig_tree2;
408
721
  const logger = getLogger();
409
722
  const isDryRun = runConfig.dryRun || false;
723
+ const isContinue = ((_runConfig_tree = runConfig.tree) === null || _runConfig_tree === void 0 ? void 0 : _runConfig_tree.continue) || false;
724
+ // Handle continue mode
725
+ if (isContinue) {
726
+ const savedContext = await loadExecutionContext(runConfig.outputDirectory);
727
+ if (savedContext) {
728
+ logger.info('Continuing previous tree execution...');
729
+ logger.info(`Original command: ${savedContext.command}`);
730
+ logger.info(`Started: ${savedContext.startTime.toISOString()}`);
731
+ logger.info(`Previously completed: ${savedContext.completedPackages.length}/${savedContext.buildOrder.length} packages`);
732
+ // Restore state safely
733
+ let mutexLocked = false;
734
+ try {
735
+ await globalStateMutex.lock();
736
+ mutexLocked = true;
737
+ publishedVersions = savedContext.publishedVersions;
738
+ globalStateMutex.unlock();
739
+ mutexLocked = false;
740
+ } catch (error) {
741
+ if (mutexLocked) {
742
+ globalStateMutex.unlock();
743
+ }
744
+ throw error;
745
+ }
746
+ executionContext = savedContext;
747
+ // Use original config but allow some overrides (like dry run)
748
+ runConfig = {
749
+ ...savedContext.originalConfig,
750
+ dryRun: runConfig.dryRun,
751
+ outputDirectory: runConfig.outputDirectory || savedContext.originalConfig.outputDirectory
752
+ };
753
+ } else {
754
+ logger.warn('No previous execution context found. Starting new execution...');
755
+ }
756
+ } else {
757
+ // Reset published versions tracking for new tree execution
758
+ publishedVersions = [];
759
+ executionContext = null;
760
+ }
410
761
  // Check if we're in built-in command mode (tree command with second argument)
411
- const builtInCommand = (_runConfig_tree = runConfig.tree) === null || _runConfig_tree === void 0 ? void 0 : _runConfig_tree.builtInCommand;
762
+ const builtInCommand = (_runConfig_tree1 = runConfig.tree) === null || _runConfig_tree1 === void 0 ? void 0 : _runConfig_tree1.builtInCommand;
412
763
  const supportedBuiltInCommands = [
413
764
  'commit',
414
765
  'publish',
415
766
  'link',
416
- 'unlink'
767
+ 'unlink',
768
+ 'development',
769
+ 'branches'
417
770
  ];
418
771
  if (builtInCommand && !supportedBuiltInCommands.includes(builtInCommand)) {
419
772
  throw new Error(`Unsupported built-in command: ${builtInCommand}. Supported commands: ${supportedBuiltInCommands.join(', ')}`);
420
773
  }
421
774
  // Determine the target directories - either specified or current working directory
422
- const targetDirectories = ((_runConfig_tree1 = runConfig.tree) === null || _runConfig_tree1 === void 0 ? void 0 : _runConfig_tree1.directories) || [
775
+ const targetDirectories = ((_runConfig_tree2 = runConfig.tree) === null || _runConfig_tree2 === void 0 ? void 0 : _runConfig_tree2.directories) || [
423
776
  process.cwd()
424
777
  ];
425
778
  if (targetDirectories.length === 1) {
@@ -428,9 +781,9 @@ const execute = async (runConfig)=>{
428
781
  logger.info(`${isDryRun ? 'DRY RUN: ' : ''}Analyzing workspaces at: ${targetDirectories.join(', ')}`);
429
782
  }
430
783
  try {
431
- var _runConfig_tree2, _runConfig_tree3, _runConfig_tree4, _runConfig_tree5;
784
+ var _runConfig_tree3, _runConfig_tree4, _runConfig_tree5, _runConfig_tree6, _runConfig_tree7;
432
785
  // Get exclusion patterns from config, fallback to empty array
433
- const excludedPatterns = ((_runConfig_tree2 = runConfig.tree) === null || _runConfig_tree2 === void 0 ? void 0 : _runConfig_tree2.excludedPatterns) || [];
786
+ const excludedPatterns = ((_runConfig_tree3 = runConfig.tree) === null || _runConfig_tree3 === void 0 ? void 0 : _runConfig_tree3.excludedPatterns) || [];
434
787
  if (excludedPatterns.length > 0) {
435
788
  logger.verbose(`${isDryRun ? 'DRY RUN: ' : ''}Using exclusion patterns: ${excludedPatterns.join(', ')}`);
436
789
  }
@@ -457,13 +810,13 @@ const execute = async (runConfig)=>{
457
810
  logger.verbose(`${isDryRun ? 'DRY RUN: ' : ''}Determining build order...`);
458
811
  let buildOrder = topologicalSort(dependencyGraph);
459
812
  // Handle start-from functionality if specified
460
- const startFrom = (_runConfig_tree3 = runConfig.tree) === null || _runConfig_tree3 === void 0 ? void 0 : _runConfig_tree3.startFrom;
813
+ const startFrom = (_runConfig_tree4 = runConfig.tree) === null || _runConfig_tree4 === void 0 ? void 0 : _runConfig_tree4.startFrom;
461
814
  if (startFrom) {
462
815
  logger.verbose(`${isDryRun ? 'DRY RUN: ' : ''}Looking for start package: ${startFrom}`);
463
816
  // Find the package that matches the startFrom directory name
464
817
  const startIndex = buildOrder.findIndex((packageName)=>{
465
818
  const packageInfo = dependencyGraph.packages.get(packageName);
466
- const dirName = path.basename(packageInfo.path);
819
+ const dirName = path__default.basename(packageInfo.path);
467
820
  return dirName === startFrom || packageName === startFrom;
468
821
  });
469
822
  if (startIndex === -1) {
@@ -477,7 +830,7 @@ const execute = async (runConfig)=>{
477
830
  for (const packageJsonPath of allPackageJsonPathsForCheck){
478
831
  try {
479
832
  const packageInfo = await parsePackageJson(packageJsonPath);
480
- const dirName = path.basename(packageInfo.path);
833
+ const dirName = path__default.basename(packageInfo.path);
481
834
  if (dirName === startFrom || packageInfo.name === startFrom) {
482
835
  // Check if this package was excluded
483
836
  if (shouldExclude(packageJsonPath, excludedPatterns)) {
@@ -495,7 +848,7 @@ const execute = async (runConfig)=>{
495
848
  } else {
496
849
  const availablePackages = buildOrder.map((name)=>{
497
850
  const packageInfo = dependencyGraph.packages.get(name);
498
- return `${path.basename(packageInfo.path)} (${name})`;
851
+ return `${path__default.basename(packageInfo.path)} (${name})`;
499
852
  }).join(', ');
500
853
  throw new Error(`Package directory '${startFrom}' not found. Available packages: ${availablePackages}`);
501
854
  }
@@ -506,13 +859,335 @@ const execute = async (runConfig)=>{
506
859
  logger.info(`${isDryRun ? 'DRY RUN: ' : ''}Resuming from '${startFrom}' - skipping ${skippedCount} package${skippedCount === 1 ? '' : 's'}`);
507
860
  }
508
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
+ }
509
1180
  // Display results
510
1181
  logger.info(`${isDryRun ? 'DRY RUN: ' : ''}Build order determined:`);
511
1182
  let returnOutput = '';
512
1183
  if (runConfig.verbose || runConfig.debug) {
513
1184
  // Verbose mode: Skip simple format, show detailed format before command execution
514
1185
  logger.info(''); // Add spacing
515
- logger.info(`Detailed Build Order for ${buildOrder.length} packages${startFrom ? ` (starting from ${startFrom})` : ''}:`);
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}:`);
516
1191
  logger.info('==========================================');
517
1192
  buildOrder.forEach((packageName, index)=>{
518
1193
  const packageInfo = dependencyGraph.packages.get(packageName);
@@ -544,27 +1219,74 @@ const execute = async (runConfig)=>{
544
1219
  returnOutput = `\nBuild order: ${buildOrder.join(' → ')}\n`;
545
1220
  }
546
1221
  // Execute command if provided (custom command or built-in command)
547
- const cmd = (_runConfig_tree4 = runConfig.tree) === null || _runConfig_tree4 === void 0 ? void 0 : _runConfig_tree4.cmd;
548
- const useParallel = ((_runConfig_tree5 = runConfig.tree) === null || _runConfig_tree5 === void 0 ? void 0 : _runConfig_tree5.parallel) || false;
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;
549
1224
  // Determine command to execute
550
1225
  let commandToRun;
551
1226
  let isBuiltInCommand = false;
552
1227
  if (builtInCommand) {
1228
+ var _runConfig_tree8, _runConfig_tree9;
553
1229
  // Built-in command mode: shell out to kodrdriv subprocess
554
- commandToRun = `kodrdriv ${builtInCommand}`;
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}`;
555
1253
  isBuiltInCommand = true;
556
1254
  } else if (cmd) {
557
1255
  // Custom command mode
558
1256
  commandToRun = cmd;
559
1257
  }
560
1258
  if (commandToRun) {
1259
+ // Create set of all package names for inter-project dependency detection
1260
+ const allPackageNames = new Set(Array.from(dependencyGraph.packages.keys()));
1261
+ // Initialize execution context if not continuing
1262
+ if (!executionContext) {
1263
+ executionContext = {
1264
+ command: commandToRun,
1265
+ originalConfig: runConfig,
1266
+ publishedVersions: [],
1267
+ completedPackages: [],
1268
+ buildOrder: buildOrder,
1269
+ startTime: new Date(),
1270
+ lastUpdateTime: new Date()
1271
+ };
1272
+ // Save initial context
1273
+ if (isBuiltInCommand && builtInCommand === 'publish' && !isDryRun) {
1274
+ await saveExecutionContext(executionContext, runConfig.outputDirectory);
1275
+ }
1276
+ }
561
1277
  // Add spacing before command execution
562
1278
  logger.info('');
563
1279
  const executionDescription = isBuiltInCommand ? `built-in command "${builtInCommand}"` : `"${commandToRun}"`;
564
1280
  const parallelInfo = useParallel ? ' (with parallel execution)' : '';
565
1281
  logger.info(`${isDryRun ? 'DRY RUN: ' : ''}Executing ${executionDescription} in ${buildOrder.length} packages${parallelInfo}...`);
1282
+ // Show info for publish commands
1283
+ if (isBuiltInCommand && builtInCommand === 'publish') {
1284
+ logger.info('Inter-project dependencies will be automatically updated before each publish.');
1285
+ }
566
1286
  let successCount = 0;
567
1287
  let failedPackage = null;
1288
+ // If continuing, start from where we left off
1289
+ const startIndex = isContinue && executionContext ? executionContext.completedPackages.length : 0;
568
1290
  if (useParallel) {
569
1291
  // Parallel execution: group packages by dependency levels
570
1292
  const dependencyLevels = groupPackagesByDependencyLevels(dependencyGraph, buildOrder, runConfig);
@@ -605,7 +1327,7 @@ const execute = async (runConfig)=>{
605
1327
  const levelPromises = currentLevel.map((packageName)=>{
606
1328
  const packageInfo = dependencyGraph.packages.get(packageName);
607
1329
  const globalIndex = buildOrder.indexOf(packageName);
608
- return executePackage(packageName, packageInfo, commandToRun, runConfig, isDryRun, globalIndex, buildOrder.length, isBuiltInCommand);
1330
+ return executePackage(packageName, packageInfo, commandToRun, runConfig, isDryRun, globalIndex, buildOrder.length, allPackageNames, isBuiltInCommand);
609
1331
  });
610
1332
  // Wait for all packages in this level to complete
611
1333
  const results = await Promise.allSettled(levelPromises);
@@ -632,7 +1354,7 @@ const execute = async (runConfig)=>{
632
1354
  logger.error(formattedError);
633
1355
  logger.error(`Failed after ${successCount} successful packages.`);
634
1356
  const packageDir = dependencyGraph.packages.get(packageName).path;
635
- const packageDirName = path.basename(packageDir);
1357
+ const packageDirName = path__default.basename(packageDir);
636
1358
  logger.error(`To resume from this package, run:`);
637
1359
  if (isBuiltInCommand) {
638
1360
  logger.error(` kodrdriv tree ${builtInCommand} --start-from ${packageDirName}`);
@@ -650,7 +1372,7 @@ const execute = async (runConfig)=>{
650
1372
  packageLogger.error(`Unexpected error: ${result.reason}`);
651
1373
  logger.error(`Failed after ${successCount} successful packages.`);
652
1374
  const packageDir = dependencyGraph.packages.get(packageName).path;
653
- const packageDirName = path.basename(packageDir);
1375
+ const packageDirName = path__default.basename(packageDir);
654
1376
  logger.error(`To resume from this package, run:`);
655
1377
  if (isBuiltInCommand) {
656
1378
  logger.error(` kodrdriv tree ${builtInCommand} --start-from ${packageDirName}`);
@@ -694,13 +1416,25 @@ const execute = async (runConfig)=>{
694
1416
  }
695
1417
  } else {
696
1418
  // Sequential execution
697
- for(let i = 0; i < buildOrder.length; i++){
1419
+ for(let i = startIndex; i < buildOrder.length; i++){
698
1420
  const packageName = buildOrder[i];
1421
+ // Skip if already completed (in continue mode)
1422
+ if (executionContext && executionContext.completedPackages.includes(packageName)) {
1423
+ successCount++;
1424
+ continue;
1425
+ }
699
1426
  const packageInfo = dependencyGraph.packages.get(packageName);
700
1427
  const packageLogger = createPackageLogger(packageName, i + 1, buildOrder.length, isDryRun);
701
- const result = await executePackage(packageName, packageInfo, commandToRun, runConfig, isDryRun, i, buildOrder.length, isBuiltInCommand);
1428
+ const result = await executePackage(packageName, packageInfo, commandToRun, runConfig, isDryRun, i, buildOrder.length, allPackageNames, isBuiltInCommand);
702
1429
  if (result.success) {
703
1430
  successCount++;
1431
+ // Update context
1432
+ if (executionContext && isBuiltInCommand && builtInCommand === 'publish' && !isDryRun) {
1433
+ executionContext.completedPackages.push(packageName);
1434
+ executionContext.publishedVersions = publishedVersions;
1435
+ executionContext.lastUpdateTime = new Date();
1436
+ await saveExecutionContext(executionContext, runConfig.outputDirectory);
1437
+ }
704
1438
  // Add spacing between packages (except after the last one)
705
1439
  if (i < buildOrder.length - 1) {
706
1440
  logger.info('');
@@ -713,13 +1447,11 @@ const execute = async (runConfig)=>{
713
1447
  packageLogger.error(`Execution failed`);
714
1448
  logger.error(formattedError);
715
1449
  logger.error(`Failed after ${successCount} successful packages.`);
716
- const packageDir = packageInfo.path;
717
- const packageDirName = path.basename(packageDir);
718
- logger.error(`To resume from this package, run:`);
1450
+ logger.error(`To resume from this point, run:`);
719
1451
  if (isBuiltInCommand) {
720
- logger.error(` kodrdriv tree ${builtInCommand} --start-from ${packageDirName}`);
1452
+ logger.error(` kodrdriv tree ${builtInCommand} --continue`);
721
1453
  } else {
722
- logger.error(` kodrdriv tree --start-from ${packageDirName} --cmd "${commandToRun}"`);
1454
+ logger.error(` kodrdriv tree --continue --cmd "${commandToRun}"`);
723
1455
  }
724
1456
  throw new Error(`Command failed in package ${packageName}`);
725
1457
  }
@@ -730,6 +1462,10 @@ const execute = async (runConfig)=>{
730
1462
  if (!failedPackage) {
731
1463
  const summary = `${isDryRun ? 'DRY RUN: ' : ''}All ${buildOrder.length} packages completed successfully! 🎉`;
732
1464
  logger.info(summary);
1465
+ // Clean up context on successful completion
1466
+ if (isBuiltInCommand && builtInCommand === 'publish' && !isDryRun) {
1467
+ await cleanupContext(runConfig.outputDirectory);
1468
+ }
733
1469
  return returnOutput; // Don't duplicate the summary in return string
734
1470
  }
735
1471
  }
@@ -738,6 +1474,9 @@ const execute = async (runConfig)=>{
738
1474
  const errorMessage = `Failed to analyze workspace: ${error.message}`;
739
1475
  logger.error(errorMessage);
740
1476
  throw new Error(errorMessage);
1477
+ } finally{
1478
+ // Clean up mutex resources to prevent memory leaks
1479
+ globalStateMutex.destroy();
741
1480
  }
742
1481
  };
743
1482