@eldrforge/kodrdriv 1.2.25 → 1.2.26
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/MONOREPO-PUBLISH-IMPROVEMENTS.md +281 -0
- package/dist/commands/tree.js +151 -45
- package/dist/commands/tree.js.map +1 -1
- package/dist/constants.js +1 -1
- package/dist/execution/DynamicTaskPool.js +112 -16
- package/dist/execution/DynamicTaskPool.js.map +1 -1
- package/dist/execution/RecoveryManager.js +6 -2
- package/dist/execution/RecoveryManager.js.map +1 -1
- package/dist/execution/TreeExecutionAdapter.js +45 -10
- package/dist/execution/TreeExecutionAdapter.js.map +1 -1
- package/dist/ui/ProgressFormatter.js +22 -2
- package/dist/ui/ProgressFormatter.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
# Kodrdriv Monorepo Publish Workflow Improvements
|
|
2
|
+
|
|
3
|
+
## Implementation Summary
|
|
4
|
+
|
|
5
|
+
This document summarizes the improvements made to the `kodrdriv tree publish --parallel` workflow based on real-world usage feedback with the Fjell monorepo (16 packages).
|
|
6
|
+
|
|
7
|
+
## Completed Improvements
|
|
8
|
+
|
|
9
|
+
### 1. ✅ Fixed Recovery Mode to Actually Continue Execution (#2 - High Priority)
|
|
10
|
+
|
|
11
|
+
**Problem:** When using `--continue --mark-completed`, the system would apply recovery options but then exit immediately without continuing execution.
|
|
12
|
+
|
|
13
|
+
**Root Cause:** The sequential execution context loading was overwriting the `runConfig`, removing the `--parallel` flag, causing the system to skip parallel execution entirely.
|
|
14
|
+
|
|
15
|
+
**Solution:**
|
|
16
|
+
- Modified `src/commands/tree.ts` to detect parallel mode and skip sequential context loading
|
|
17
|
+
- Parallel execution now properly continues after recovery options are applied
|
|
18
|
+
- Recovery is now atomic: apply recovery + continue execution in one step
|
|
19
|
+
|
|
20
|
+
**Files Changed:**
|
|
21
|
+
- `src/commands/tree.ts` (lines 1093-1144)
|
|
22
|
+
|
|
23
|
+
### 2. ✅ Git Submodule Support (#3 - High Priority)
|
|
24
|
+
|
|
25
|
+
**Status:** Already implemented and tested in previous work.
|
|
26
|
+
|
|
27
|
+
**Implementation:** The file-based lock mechanism in `src/util/fileLock.ts` already handles both regular repositories and git submodules by:
|
|
28
|
+
- Detecting if `.git` is a file (submodule) vs directory (regular repo)
|
|
29
|
+
- Reading and parsing the `gitdir:` reference for submodules
|
|
30
|
+
- Creating lock files in the actual git directory
|
|
31
|
+
|
|
32
|
+
**Files:**
|
|
33
|
+
- `src/util/fileLock.ts`
|
|
34
|
+
- `tests/fileLock.test.ts` (comprehensive test coverage)
|
|
35
|
+
- `SUBMODULE-LOCK-FIX.md` (documentation)
|
|
36
|
+
|
|
37
|
+
### 3. ✅ Better Status Distinctions (#1/#4 - High Priority)
|
|
38
|
+
|
|
39
|
+
**Problem:** The system reported "Completed successfully" for packages that were skipped due to no code changes, making it impossible to tell what was actually published.
|
|
40
|
+
|
|
41
|
+
**Solution:**
|
|
42
|
+
- Added new `skippedNoChanges` field to `ExecutionState` and `ExecutionResult` types
|
|
43
|
+
- Modified `DynamicTaskPool` to track packages skipped due to no changes separately from those skipped due to failed dependencies
|
|
44
|
+
- Updated progress logger to show distinct icons and messages:
|
|
45
|
+
- ✅ Published (actually executed)
|
|
46
|
+
- ⊘ Skipped (no code changes)
|
|
47
|
+
- ⊘ Skipped (dependency failed)
|
|
48
|
+
- ❌ Failed
|
|
49
|
+
- Enhanced result summary to show detailed breakdown
|
|
50
|
+
|
|
51
|
+
**Files Changed:**
|
|
52
|
+
- `src/types/parallelExecution.ts` - Added `skippedNoChanges` to state and result types
|
|
53
|
+
- `src/execution/DynamicTaskPool.ts` - Track and report skip reasons
|
|
54
|
+
- `src/execution/TreeExecutionAdapter.ts` - Pass through skip status and format results
|
|
55
|
+
- `src/execution/RecoveryManager.ts` - Include skippedNoChanges in validation
|
|
56
|
+
- `src/commands/tree.ts` - Detect and return skip status from executePackage
|
|
57
|
+
|
|
58
|
+
**Example Output:**
|
|
59
|
+
```
|
|
60
|
+
📊 Execution Summary:
|
|
61
|
+
|
|
62
|
+
✅ Published: 3 package(s)
|
|
63
|
+
@fjell/common-config, @fjell/logging, @fjell/docs-template
|
|
64
|
+
|
|
65
|
+
⊘ Skipped (no code changes): 5 package(s)
|
|
66
|
+
@fjell/core, @fjell/http-api, @fjell/registry, @fjell/client-api, @fjell/lib
|
|
67
|
+
|
|
68
|
+
⊘ Skipped (dependency failed): 8 package(s)
|
|
69
|
+
@fjell/cache, @fjell/providers, @fjell/sample-app, ...
|
|
70
|
+
Blocked by: @fjell/core
|
|
71
|
+
|
|
72
|
+
❌ Failed: 0 package(s)
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### 4. ✅ Show Actual Errors Inline (#9 - High Priority)
|
|
76
|
+
|
|
77
|
+
**Problem:** When packages failed, only generic error messages were shown. The actual error (test failure, build error, merge conflict) was buried in log files.
|
|
78
|
+
|
|
79
|
+
**Solution:**
|
|
80
|
+
- Added `errorDetails` field to `FailedPackageSnapshot` type with structured error information
|
|
81
|
+
- Implemented `extractErrorDetails()` method in `DynamicTaskPool` to parse errors and extract:
|
|
82
|
+
- Error type (test_coverage, build_error, merge_conflict, test_failure, timeout, unknown)
|
|
83
|
+
- Context (specific details about the error)
|
|
84
|
+
- Log file location
|
|
85
|
+
- Suggested fix command
|
|
86
|
+
- Enhanced `ProgressFormatter.createErrorSummary()` to display detailed error information
|
|
87
|
+
|
|
88
|
+
**Files Changed:**
|
|
89
|
+
- `src/types/parallelExecution.ts` - Added errorDetails to FailedPackageSnapshot
|
|
90
|
+
- `src/execution/DynamicTaskPool.ts` - Extract and attach error details
|
|
91
|
+
- `src/ui/ProgressFormatter.ts` - Display detailed error information
|
|
92
|
+
|
|
93
|
+
**Example Output:**
|
|
94
|
+
```
|
|
95
|
+
❌ Failure Summary:
|
|
96
|
+
|
|
97
|
+
@fjell/core:
|
|
98
|
+
Type: Test Coverage
|
|
99
|
+
Details: Lines: 89.5% (threshold: 90%)
|
|
100
|
+
Log: /path/to/core/output/kodrdriv/publish_*.log
|
|
101
|
+
💡 Suggestion: cd /path/to/core && npm test -- --coverage
|
|
102
|
+
Blocked: @fjell/cache, @fjell/providers +6 more
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### 5. ✅ Add Dry-Run Mode (#6 - Medium Priority)
|
|
106
|
+
|
|
107
|
+
**Problem:** No way to preview what will happen without actually executing.
|
|
108
|
+
|
|
109
|
+
**Solution:**
|
|
110
|
+
- Added `generateDryRunPreview()` function that analyzes the dependency graph and shows:
|
|
111
|
+
- Build order grouped by dependency level
|
|
112
|
+
- Status for each package (will publish, will skip, etc.)
|
|
113
|
+
- For publish commands, checks git diff to determine if packages have code changes
|
|
114
|
+
- Summary statistics
|
|
115
|
+
- Integrated with parallel execution to show preview before executing
|
|
116
|
+
|
|
117
|
+
**Files Changed:**
|
|
118
|
+
- `src/commands/tree.ts` - Added generateDryRunPreview() and integrated with parallel execution
|
|
119
|
+
|
|
120
|
+
**Example Output:**
|
|
121
|
+
```
|
|
122
|
+
🔍 DRY RUN MODE - No changes will be made
|
|
123
|
+
|
|
124
|
+
Build order determined:
|
|
125
|
+
|
|
126
|
+
Level 1: (1 package)
|
|
127
|
+
@fjell/common-config
|
|
128
|
+
Status: 📝 Has changes (23 files), will publish
|
|
129
|
+
Path: /path/to/common-config
|
|
130
|
+
|
|
131
|
+
Level 2: (1 package)
|
|
132
|
+
@fjell/logging
|
|
133
|
+
Status: ⊘ Only version bump, will skip
|
|
134
|
+
Path: /path/to/logging
|
|
135
|
+
|
|
136
|
+
...
|
|
137
|
+
|
|
138
|
+
Summary:
|
|
139
|
+
Total packages: 16
|
|
140
|
+
Dependency levels: 6
|
|
141
|
+
Command: kodrdriv publish
|
|
142
|
+
Max concurrency: 8
|
|
143
|
+
|
|
144
|
+
To execute for real, run the same command without --dry-run
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## Not Yet Implemented (Lower Priority)
|
|
148
|
+
|
|
149
|
+
The following improvements were identified but not implemented in this session:
|
|
150
|
+
|
|
151
|
+
### 5. Progress Indicators for Long-Running Operations
|
|
152
|
+
- Show sub-step progress during PR checks
|
|
153
|
+
- Display which checks are passing/failing
|
|
154
|
+
- Estimate completion time
|
|
155
|
+
|
|
156
|
+
### 7. Better Checkpoint Management
|
|
157
|
+
- Add `kodrdriv tree --status` command
|
|
158
|
+
- Add `kodrdriv tree --reset` command
|
|
159
|
+
- Auto-detect and prompt when checkpoint exists
|
|
160
|
+
|
|
161
|
+
### 8. Fix Concurrency Recommendation Inconsistency
|
|
162
|
+
- Use recommended concurrency by default or explain why not
|
|
163
|
+
|
|
164
|
+
### 10. Interactive Conflict Resolution
|
|
165
|
+
- Offer to auto-resolve common conflicts (package.json versions, lockfiles)
|
|
166
|
+
- Interactive prompts for manual resolution
|
|
167
|
+
|
|
168
|
+
### 11. Smart Dependency Updating
|
|
169
|
+
- Auto-update dependent packages when a package is published
|
|
170
|
+
- `kodrdriv tree update-deps --package "@fjell/core@4.4.72"`
|
|
171
|
+
|
|
172
|
+
### 12. Publish Groups/Profiles
|
|
173
|
+
- Define groups of packages to publish together
|
|
174
|
+
- `kodrdriv tree publish --group core`
|
|
175
|
+
|
|
176
|
+
### 13. Better npm Registry Integration
|
|
177
|
+
- Check npm for latest versions before publishing
|
|
178
|
+
- Warn about version conflicts
|
|
179
|
+
|
|
180
|
+
### 14. Automatic Changelog Generation
|
|
181
|
+
- Generate changelogs based on commits since last release
|
|
182
|
+
- Include in release notes automatically
|
|
183
|
+
|
|
184
|
+
## Testing
|
|
185
|
+
|
|
186
|
+
### Manual Testing Recommended
|
|
187
|
+
|
|
188
|
+
1. **Recovery Mode:**
|
|
189
|
+
```bash
|
|
190
|
+
# Start a publish
|
|
191
|
+
kodrdriv tree publish --parallel
|
|
192
|
+
|
|
193
|
+
# If it fails, mark packages as completed and continue
|
|
194
|
+
kodrdriv tree publish --parallel --continue --mark-completed "pkg1,pkg2"
|
|
195
|
+
|
|
196
|
+
# Verify it actually continues execution (not just exits)
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
2. **Status Distinctions:**
|
|
200
|
+
```bash
|
|
201
|
+
# Publish a monorepo where some packages have no changes
|
|
202
|
+
kodrdriv tree publish --parallel
|
|
203
|
+
|
|
204
|
+
# Verify the summary shows:
|
|
205
|
+
# - ✅ Published: X packages
|
|
206
|
+
# - ⊘ Skipped (no changes): Y packages
|
|
207
|
+
# - ⊘ Skipped (dependency failed): Z packages
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
3. **Error Details:**
|
|
211
|
+
```bash
|
|
212
|
+
# Cause a test failure in one package
|
|
213
|
+
# Run publish and verify the error summary shows:
|
|
214
|
+
# - Error type
|
|
215
|
+
# - Specific details
|
|
216
|
+
# - Log file location
|
|
217
|
+
# - Suggested fix
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
4. **Dry Run:**
|
|
221
|
+
```bash
|
|
222
|
+
kodrdriv tree publish --parallel --dry-run
|
|
223
|
+
|
|
224
|
+
# Verify it shows:
|
|
225
|
+
# - Build order by level
|
|
226
|
+
# - Status for each package
|
|
227
|
+
# - Summary statistics
|
|
228
|
+
# - Does not actually execute
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### Unit Tests
|
|
232
|
+
|
|
233
|
+
The following test files should be updated to cover the new functionality:
|
|
234
|
+
|
|
235
|
+
- `tests/execution/DynamicTaskPool.test.ts` - Test skippedNoChanges tracking
|
|
236
|
+
- `tests/execution/TreeExecutionAdapter.test.ts` - Test result formatting
|
|
237
|
+
- `tests/execution/RecoveryManager.test.ts` - Test validation with skippedNoChanges
|
|
238
|
+
- `tests/commands/tree.test.ts` - Test dry-run preview and recovery continuation
|
|
239
|
+
|
|
240
|
+
## Migration Notes
|
|
241
|
+
|
|
242
|
+
### Breaking Changes
|
|
243
|
+
|
|
244
|
+
None. All changes are backward compatible.
|
|
245
|
+
|
|
246
|
+
### API Changes
|
|
247
|
+
|
|
248
|
+
- `ExecutionState` now includes `skippedNoChanges: string[]`
|
|
249
|
+
- `ExecutionResult` now includes `skippedNoChanges: string[]`
|
|
250
|
+
- `PackageResult` now includes optional `skippedNoChanges?: boolean`
|
|
251
|
+
- `FailedPackageSnapshot` now includes optional `errorDetails?: { type, context, logFile, suggestion }`
|
|
252
|
+
- `executePackage()` return type now includes optional `skippedNoChanges?: boolean`
|
|
253
|
+
|
|
254
|
+
### Configuration Changes
|
|
255
|
+
|
|
256
|
+
None required. All new features work with existing configurations.
|
|
257
|
+
|
|
258
|
+
## Performance Impact
|
|
259
|
+
|
|
260
|
+
Minimal. The changes primarily affect:
|
|
261
|
+
- Error handling (extracting details from error messages)
|
|
262
|
+
- Status tracking (additional array in state)
|
|
263
|
+
- Dry-run preview (only runs when --dry-run is specified)
|
|
264
|
+
|
|
265
|
+
## Documentation Updates Needed
|
|
266
|
+
|
|
267
|
+
- Update `docs/public/commands/tree.md` with:
|
|
268
|
+
- New status distinctions
|
|
269
|
+
- Dry-run mode usage
|
|
270
|
+
- Enhanced error reporting
|
|
271
|
+
- Recovery mode improvements
|
|
272
|
+
- Update examples to show new output format
|
|
273
|
+
|
|
274
|
+
## Related Issues/PRs
|
|
275
|
+
|
|
276
|
+
This implementation addresses the feedback document "Kodrdriv Monorepo Publish Workflow Improvements" which identified 14 pain points based on real-world usage with the Fjell monorepo.
|
|
277
|
+
|
|
278
|
+
## Contributors
|
|
279
|
+
|
|
280
|
+
Implementation based on detailed feedback from production usage of kodrdriv with a 16-package monorepo.
|
|
281
|
+
|
package/dist/commands/tree.js
CHANGED
|
@@ -493,6 +493,8 @@ const executePackage = async (packageName, packageInfo, commandToRun, runConfig,
|
|
|
493
493
|
// Basic progress info even without flags
|
|
494
494
|
logger.info(`[${index + 1}/${total}] ${packageName}: Running ${commandToRun}...`);
|
|
495
495
|
}
|
|
496
|
+
// Track if publish was skipped due to no changes
|
|
497
|
+
let publishWasSkipped = false;
|
|
496
498
|
try {
|
|
497
499
|
if (isDryRun && !isBuiltInCommand) {
|
|
498
500
|
// Handle inter-project dependency updates for publish commands in dry run mode
|
|
@@ -604,7 +606,6 @@ const executePackage = async (packageName, packageInfo, commandToRun, runConfig,
|
|
|
604
606
|
}
|
|
605
607
|
// For built-in commands, shell out to a separate kodrdriv process
|
|
606
608
|
// This preserves individual project configurations
|
|
607
|
-
let publishWasSkipped;
|
|
608
609
|
if (isBuiltInCommand) {
|
|
609
610
|
// Extract the command name from "kodrdriv <command>"
|
|
610
611
|
const builtInCommandName = commandToRun.replace('kodrdriv ', '');
|
|
@@ -701,13 +702,22 @@ const executePackage = async (packageName, packageInfo, commandToRun, runConfig,
|
|
|
701
702
|
}
|
|
702
703
|
// Show completion status
|
|
703
704
|
if (runConfig.debug || runConfig.verbose) {
|
|
704
|
-
|
|
705
|
+
if (publishWasSkipped) {
|
|
706
|
+
packageLogger.info(`⊘ Skipped (no code changes)`);
|
|
707
|
+
} else {
|
|
708
|
+
packageLogger.info(`✅ Completed successfully`);
|
|
709
|
+
}
|
|
705
710
|
} else if (isPublishCommand) {
|
|
706
711
|
// For publish commands, always show completion even without verbose
|
|
707
|
-
|
|
712
|
+
if (publishWasSkipped) {
|
|
713
|
+
logger.info(`[${index + 1}/${total}] ${packageName}: ⊘ Skipped (no code changes)`);
|
|
714
|
+
} else {
|
|
715
|
+
logger.info(`[${index + 1}/${total}] ${packageName}: ✅ Completed`);
|
|
716
|
+
}
|
|
708
717
|
}
|
|
709
718
|
return {
|
|
710
|
-
success: true
|
|
719
|
+
success: true,
|
|
720
|
+
skippedNoChanges: publishWasSkipped
|
|
711
721
|
};
|
|
712
722
|
} catch (error) {
|
|
713
723
|
var _error_message;
|
|
@@ -726,6 +736,86 @@ const executePackage = async (packageName, packageInfo, commandToRun, runConfig,
|
|
|
726
736
|
};
|
|
727
737
|
}
|
|
728
738
|
};
|
|
739
|
+
/**
|
|
740
|
+
* Generate a dry-run preview showing what would happen without executing
|
|
741
|
+
*/ const generateDryRunPreview = async (dependencyGraph, buildOrder, command, runConfig)=>{
|
|
742
|
+
var _runConfig_tree;
|
|
743
|
+
const lines = [];
|
|
744
|
+
lines.push('');
|
|
745
|
+
lines.push('🔍 DRY RUN MODE - No changes will be made');
|
|
746
|
+
lines.push('');
|
|
747
|
+
lines.push('Build order determined:');
|
|
748
|
+
lines.push('');
|
|
749
|
+
// Group packages by dependency level
|
|
750
|
+
const levels = [];
|
|
751
|
+
const packageLevels = new Map();
|
|
752
|
+
for (const pkg of buildOrder){
|
|
753
|
+
const deps = dependencyGraph.edges.get(pkg) || new Set();
|
|
754
|
+
let maxDepLevel = -1;
|
|
755
|
+
for (const dep of deps){
|
|
756
|
+
var _packageLevels_get;
|
|
757
|
+
const depLevel = (_packageLevels_get = packageLevels.get(dep)) !== null && _packageLevels_get !== void 0 ? _packageLevels_get : 0;
|
|
758
|
+
maxDepLevel = Math.max(maxDepLevel, depLevel);
|
|
759
|
+
}
|
|
760
|
+
const pkgLevel = maxDepLevel + 1;
|
|
761
|
+
packageLevels.set(pkg, pkgLevel);
|
|
762
|
+
if (!levels[pkgLevel]) {
|
|
763
|
+
levels[pkgLevel] = [];
|
|
764
|
+
}
|
|
765
|
+
levels[pkgLevel].push(pkg);
|
|
766
|
+
}
|
|
767
|
+
// Show packages grouped by level
|
|
768
|
+
for(let i = 0; i < levels.length; i++){
|
|
769
|
+
const levelPackages = levels[i];
|
|
770
|
+
lines.push(`Level ${i + 1}: (${levelPackages.length} package${levelPackages.length === 1 ? '' : 's'})`);
|
|
771
|
+
for (const pkg of levelPackages){
|
|
772
|
+
const pkgInfo = dependencyGraph.packages.get(pkg);
|
|
773
|
+
if (!pkgInfo) continue;
|
|
774
|
+
// Check if package has changes (for publish command)
|
|
775
|
+
const isPublish = command.includes('publish');
|
|
776
|
+
let status = '📝 Has changes, will execute';
|
|
777
|
+
if (isPublish) {
|
|
778
|
+
try {
|
|
779
|
+
// Check git diff to see if there are code changes
|
|
780
|
+
const { stdout } = await runSecure('git', [
|
|
781
|
+
'diff',
|
|
782
|
+
'--name-only',
|
|
783
|
+
'origin/main...HEAD'
|
|
784
|
+
], {
|
|
785
|
+
cwd: pkgInfo.path
|
|
786
|
+
});
|
|
787
|
+
const changedFiles = stdout.split('\n').filter(Boolean);
|
|
788
|
+
const nonVersionFiles = changedFiles.filter((f)=>f !== 'package.json' && f !== 'package-lock.json');
|
|
789
|
+
if (changedFiles.length === 0) {
|
|
790
|
+
status = '⊘ No changes, will skip';
|
|
791
|
+
} else if (nonVersionFiles.length === 0) {
|
|
792
|
+
status = '⊘ Only version bump, will skip';
|
|
793
|
+
} else {
|
|
794
|
+
status = `📝 Has changes (${nonVersionFiles.length} files), will publish`;
|
|
795
|
+
}
|
|
796
|
+
} catch {
|
|
797
|
+
// If we can't check git status, assume changes
|
|
798
|
+
status = '📝 Will execute';
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
lines.push(` ${pkg}`);
|
|
802
|
+
lines.push(` Status: ${status}`);
|
|
803
|
+
lines.push(` Path: ${pkgInfo.path}`);
|
|
804
|
+
}
|
|
805
|
+
lines.push('');
|
|
806
|
+
}
|
|
807
|
+
lines.push('Summary:');
|
|
808
|
+
lines.push(` Total packages: ${buildOrder.length}`);
|
|
809
|
+
lines.push(` Dependency levels: ${levels.length}`);
|
|
810
|
+
lines.push(` Command: ${command}`);
|
|
811
|
+
if ((_runConfig_tree = runConfig.tree) === null || _runConfig_tree === void 0 ? void 0 : _runConfig_tree.maxConcurrency) {
|
|
812
|
+
lines.push(` Max concurrency: ${runConfig.tree.maxConcurrency}`);
|
|
813
|
+
}
|
|
814
|
+
lines.push('');
|
|
815
|
+
lines.push('To execute for real, run the same command without --dry-run');
|
|
816
|
+
lines.push('');
|
|
817
|
+
return lines.join('\n');
|
|
818
|
+
};
|
|
729
819
|
// Add a simple status check function
|
|
730
820
|
const checkTreePublishStatus = async ()=>{
|
|
731
821
|
const logger = getLogger();
|
|
@@ -880,35 +970,46 @@ const execute = async (runConfig)=>{
|
|
|
880
970
|
}
|
|
881
971
|
// Handle continue mode
|
|
882
972
|
if (isContinue) {
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
mutexLocked = false;
|
|
897
|
-
|
|
898
|
-
|
|
973
|
+
var _runConfig_tree19;
|
|
974
|
+
// For parallel execution, the checkpoint is managed by DynamicTaskPool/CheckpointManager
|
|
975
|
+
// For sequential execution, we use the legacy context file
|
|
976
|
+
const isParallelMode = (_runConfig_tree19 = runConfig.tree) === null || _runConfig_tree19 === void 0 ? void 0 : _runConfig_tree19.parallel;
|
|
977
|
+
if (!isParallelMode) {
|
|
978
|
+
// Sequential execution: load legacy context
|
|
979
|
+
const savedContext = await loadExecutionContext(runConfig.outputDirectory);
|
|
980
|
+
if (savedContext) {
|
|
981
|
+
logger.info('Continuing previous tree execution...');
|
|
982
|
+
logger.info(`Original command: ${savedContext.command}`);
|
|
983
|
+
logger.info(`Started: ${savedContext.startTime.toISOString()}`);
|
|
984
|
+
logger.info(`Previously completed: ${savedContext.completedPackages.length}/${savedContext.buildOrder.length} packages`);
|
|
985
|
+
// Restore state safely
|
|
986
|
+
let mutexLocked = false;
|
|
987
|
+
try {
|
|
988
|
+
await globalStateMutex.lock();
|
|
989
|
+
mutexLocked = true;
|
|
990
|
+
publishedVersions = savedContext.publishedVersions;
|
|
899
991
|
globalStateMutex.unlock();
|
|
992
|
+
mutexLocked = false;
|
|
993
|
+
} catch (error) {
|
|
994
|
+
if (mutexLocked) {
|
|
995
|
+
globalStateMutex.unlock();
|
|
996
|
+
}
|
|
997
|
+
throw error;
|
|
900
998
|
}
|
|
901
|
-
|
|
999
|
+
executionContext = savedContext;
|
|
1000
|
+
// Use original config but allow some overrides (like dry run)
|
|
1001
|
+
runConfig = {
|
|
1002
|
+
...savedContext.originalConfig,
|
|
1003
|
+
dryRun: runConfig.dryRun,
|
|
1004
|
+
outputDirectory: runConfig.outputDirectory || savedContext.originalConfig.outputDirectory
|
|
1005
|
+
};
|
|
1006
|
+
} else {
|
|
1007
|
+
logger.warn('No previous execution context found. Starting new execution...');
|
|
902
1008
|
}
|
|
903
|
-
executionContext = savedContext;
|
|
904
|
-
// Use original config but allow some overrides (like dry run)
|
|
905
|
-
runConfig = {
|
|
906
|
-
...savedContext.originalConfig,
|
|
907
|
-
dryRun: runConfig.dryRun,
|
|
908
|
-
outputDirectory: runConfig.outputDirectory || savedContext.originalConfig.outputDirectory
|
|
909
|
-
};
|
|
910
1009
|
} else {
|
|
911
|
-
|
|
1010
|
+
// Parallel execution: checkpoint is managed by DynamicTaskPool
|
|
1011
|
+
// Just log that we're continuing - the actual checkpoint loading happens in DynamicTaskPool
|
|
1012
|
+
logger.info('Continuing previous parallel execution...');
|
|
912
1013
|
}
|
|
913
1014
|
} else {
|
|
914
1015
|
// Reset published versions tracking for new tree execution
|
|
@@ -933,8 +1034,8 @@ const execute = async (runConfig)=>{
|
|
|
933
1034
|
}
|
|
934
1035
|
// Handle run subcommand - convert space-separated scripts to npm run commands
|
|
935
1036
|
if (builtInCommand === 'run') {
|
|
936
|
-
var
|
|
937
|
-
const packageArgument = (
|
|
1037
|
+
var _runConfig_tree20;
|
|
1038
|
+
const packageArgument = (_runConfig_tree20 = runConfig.tree) === null || _runConfig_tree20 === void 0 ? void 0 : _runConfig_tree20.packageArgument;
|
|
938
1039
|
if (!packageArgument) {
|
|
939
1040
|
throw new Error('run subcommand requires script names. Usage: kodrdriv tree run "clean build test"');
|
|
940
1041
|
}
|
|
@@ -1006,9 +1107,9 @@ const execute = async (runConfig)=>{
|
|
|
1006
1107
|
logger.info(`${isDryRun ? 'DRY RUN: ' : ''}Analyzing workspaces at: ${directories.join(', ')}`);
|
|
1007
1108
|
}
|
|
1008
1109
|
try {
|
|
1009
|
-
var
|
|
1110
|
+
var _runConfig_tree21, _runConfig_tree22, _runConfig_tree23, _runConfig_tree24;
|
|
1010
1111
|
// Get exclusion patterns from config, fallback to empty array
|
|
1011
|
-
const excludedPatterns = ((
|
|
1112
|
+
const excludedPatterns = ((_runConfig_tree21 = runConfig.tree) === null || _runConfig_tree21 === void 0 ? void 0 : _runConfig_tree21.exclude) || [];
|
|
1012
1113
|
if (excludedPatterns.length > 0) {
|
|
1013
1114
|
logger.verbose(`${isDryRun ? 'DRY RUN: ' : ''}Using exclusion patterns: ${excludedPatterns.join(', ')}`);
|
|
1014
1115
|
}
|
|
@@ -1035,7 +1136,7 @@ const execute = async (runConfig)=>{
|
|
|
1035
1136
|
logger.verbose(`${isDryRun ? 'DRY RUN: ' : ''}Determining build order...`);
|
|
1036
1137
|
let buildOrder = topologicalSort(dependencyGraph);
|
|
1037
1138
|
// Handle start-from functionality if specified
|
|
1038
|
-
const startFrom = (
|
|
1139
|
+
const startFrom = (_runConfig_tree22 = runConfig.tree) === null || _runConfig_tree22 === void 0 ? void 0 : _runConfig_tree22.startFrom;
|
|
1039
1140
|
if (startFrom) {
|
|
1040
1141
|
logger.verbose(`${isDryRun ? 'DRY RUN: ' : ''}Looking for start package: ${startFrom}`);
|
|
1041
1142
|
// Resolve the actual package name (can be package name or directory name)
|
|
@@ -1092,7 +1193,7 @@ const execute = async (runConfig)=>{
|
|
|
1092
1193
|
logger.info(`${isDryRun ? 'DRY RUN: ' : ''}Starting execution from package '${startFrom}' (${buildOrder.length} of ${originalLength} packages remaining).`);
|
|
1093
1194
|
}
|
|
1094
1195
|
// Handle stop-at functionality if specified
|
|
1095
|
-
const stopAt = (
|
|
1196
|
+
const stopAt = (_runConfig_tree23 = runConfig.tree) === null || _runConfig_tree23 === void 0 ? void 0 : _runConfig_tree23.stopAt;
|
|
1096
1197
|
if (stopAt) {
|
|
1097
1198
|
logger.verbose(`${isDryRun ? 'DRY RUN: ' : ''}Looking for stop package: ${stopAt}`);
|
|
1098
1199
|
// Find the package that matches the stopAt directory name
|
|
@@ -1411,8 +1512,8 @@ const execute = async (runConfig)=>{
|
|
|
1411
1512
|
}
|
|
1412
1513
|
// Handle special "checkout" command that switches all packages to specified branch
|
|
1413
1514
|
if (builtInCommand === 'checkout') {
|
|
1414
|
-
var
|
|
1415
|
-
const targetBranch = (
|
|
1515
|
+
var _runConfig_tree25;
|
|
1516
|
+
const targetBranch = (_runConfig_tree25 = runConfig.tree) === null || _runConfig_tree25 === void 0 ? void 0 : _runConfig_tree25.packageArgument;
|
|
1416
1517
|
if (!targetBranch) {
|
|
1417
1518
|
throw new Error('checkout subcommand requires a branch name. Usage: kodrdriv tree checkout <branch-name>');
|
|
1418
1519
|
}
|
|
@@ -1591,12 +1692,12 @@ const execute = async (runConfig)=>{
|
|
|
1591
1692
|
returnOutput = `\nBuild order: ${buildOrder.join(' → ')}\n`;
|
|
1592
1693
|
}
|
|
1593
1694
|
// Execute command if provided (custom command or built-in command)
|
|
1594
|
-
const cmd = (
|
|
1695
|
+
const cmd = (_runConfig_tree24 = runConfig.tree) === null || _runConfig_tree24 === void 0 ? void 0 : _runConfig_tree24.cmd;
|
|
1595
1696
|
// Determine command to execute
|
|
1596
1697
|
let commandToRun;
|
|
1597
1698
|
let isBuiltInCommand = false;
|
|
1598
1699
|
if (builtInCommand) {
|
|
1599
|
-
var
|
|
1700
|
+
var _runConfig_tree26, _runConfig_tree27, _runConfig_tree28;
|
|
1600
1701
|
// Built-in command mode: shell out to kodrdriv subprocess
|
|
1601
1702
|
// Build command with propagated global options
|
|
1602
1703
|
const globalOptions = [];
|
|
@@ -1613,14 +1714,14 @@ const execute = async (runConfig)=>{
|
|
|
1613
1714
|
// Build the command with global options
|
|
1614
1715
|
const optionsString = globalOptions.length > 0 ? ` ${globalOptions.join(' ')}` : '';
|
|
1615
1716
|
// Add package argument for link/unlink/updates commands
|
|
1616
|
-
const packageArg = (
|
|
1717
|
+
const packageArg = (_runConfig_tree26 = runConfig.tree) === null || _runConfig_tree26 === void 0 ? void 0 : _runConfig_tree26.packageArgument;
|
|
1617
1718
|
const packageArgString = packageArg && (builtInCommand === 'link' || builtInCommand === 'unlink' || builtInCommand === 'updates') ? ` "${packageArg}"` : '';
|
|
1618
1719
|
// Add command-specific options
|
|
1619
1720
|
let commandSpecificOptions = '';
|
|
1620
|
-
if (builtInCommand === 'unlink' && ((
|
|
1721
|
+
if (builtInCommand === 'unlink' && ((_runConfig_tree27 = runConfig.tree) === null || _runConfig_tree27 === void 0 ? void 0 : _runConfig_tree27.cleanNodeModules)) {
|
|
1621
1722
|
commandSpecificOptions += ' --clean-node-modules';
|
|
1622
1723
|
}
|
|
1623
|
-
if ((builtInCommand === 'link' || builtInCommand === 'unlink') && ((
|
|
1724
|
+
if ((builtInCommand === 'link' || builtInCommand === 'unlink') && ((_runConfig_tree28 = runConfig.tree) === null || _runConfig_tree28 === void 0 ? void 0 : _runConfig_tree28.externals) && runConfig.tree.externals.length > 0) {
|
|
1624
1725
|
commandSpecificOptions += ` --externals ${runConfig.tree.externals.join(' ')}`;
|
|
1625
1726
|
}
|
|
1626
1727
|
commandToRun = `kodrdriv ${builtInCommand}${optionsString}${packageArgString}${commandSpecificOptions}`;
|
|
@@ -1630,7 +1731,7 @@ const execute = async (runConfig)=>{
|
|
|
1630
1731
|
commandToRun = cmd;
|
|
1631
1732
|
}
|
|
1632
1733
|
if (commandToRun) {
|
|
1633
|
-
var
|
|
1734
|
+
var _runConfig_tree29, _runConfig_tree30;
|
|
1634
1735
|
// Validate scripts for run command before execution
|
|
1635
1736
|
const scriptsToValidate = runConfig.__scriptsToValidate;
|
|
1636
1737
|
if (scriptsToValidate && scriptsToValidate.length > 0) {
|
|
@@ -1649,7 +1750,7 @@ const execute = async (runConfig)=>{
|
|
|
1649
1750
|
}
|
|
1650
1751
|
}
|
|
1651
1752
|
// Validate command for parallel execution if parallel mode is enabled
|
|
1652
|
-
if ((
|
|
1753
|
+
if ((_runConfig_tree29 = runConfig.tree) === null || _runConfig_tree29 === void 0 ? void 0 : _runConfig_tree29.parallel) {
|
|
1653
1754
|
const { CommandValidator } = await import('../execution/CommandValidator.js');
|
|
1654
1755
|
const validation = CommandValidator.validateForParallel(commandToRun, builtInCommand);
|
|
1655
1756
|
CommandValidator.logValidation(validation);
|
|
@@ -1700,9 +1801,14 @@ const execute = async (runConfig)=>{
|
|
|
1700
1801
|
// If continuing, start from where we left off
|
|
1701
1802
|
const startIndex = isContinue && executionContext ? executionContext.completedPackages.length : 0;
|
|
1702
1803
|
// Check if parallel execution is enabled
|
|
1703
|
-
if ((
|
|
1804
|
+
if ((_runConfig_tree30 = runConfig.tree) === null || _runConfig_tree30 === void 0 ? void 0 : _runConfig_tree30.parallel) {
|
|
1704
1805
|
var _runConfig_tree_retry1, _runConfig_tree_retry2, _runConfig_tree_retry3, _runConfig_tree_retry4;
|
|
1705
1806
|
logger.info('🚀 Using parallel execution mode');
|
|
1807
|
+
// If dry run, show preview instead of executing
|
|
1808
|
+
if (isDryRun) {
|
|
1809
|
+
const preview = await generateDryRunPreview(dependencyGraph, buildOrder, commandToRun, runConfig);
|
|
1810
|
+
return preview;
|
|
1811
|
+
}
|
|
1706
1812
|
// Import parallel execution components
|
|
1707
1813
|
const { TreeExecutionAdapter, createParallelProgressLogger, formatParallelResult } = await import('../execution/TreeExecutionAdapter.js');
|
|
1708
1814
|
const os = await import('os');
|