@eldrforge/kodrdriv 0.0.46 → 0.0.48

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.
@@ -0,0 +1,745 @@
1
+ #!/usr/bin/env node
2
+ import path from 'path';
3
+ import fs from 'fs/promises';
4
+ import { exec } from 'child_process';
5
+ import util from 'util';
6
+ import { getLogger } from '../logging.js';
7
+ import { create } from '../util/storage.js';
8
+ import { safeJsonParse, validatePackageJson } from '../util/validation.js';
9
+
10
+ // Enhanced run function that can show output based on log level
11
+ const runWithLogging = async (command, packageLogger, options = {}, showOutput = 'none')=>{
12
+ const execPromise = util.promisify(exec);
13
+ if (showOutput === 'full') {
14
+ packageLogger.debug(`Executing command: ${command}`);
15
+ // Use info level to show on console in debug mode
16
+ packageLogger.info(`🔧 Running: ${command}`);
17
+ } else if (showOutput === 'minimal') {
18
+ packageLogger.verbose(`Running: ${command}`);
19
+ }
20
+ try {
21
+ const result = await execPromise(command, options);
22
+ if (showOutput === 'full') {
23
+ if (result.stdout.trim()) {
24
+ packageLogger.debug('STDOUT:');
25
+ packageLogger.debug(result.stdout);
26
+ // Show on console using info level for immediate feedback
27
+ packageLogger.info(`📤 STDOUT:`);
28
+ result.stdout.split('\n').forEach((line)=>{
29
+ if (line.trim()) packageLogger.info(`${line}`);
30
+ });
31
+ }
32
+ if (result.stderr.trim()) {
33
+ packageLogger.debug('STDERR:');
34
+ packageLogger.debug(result.stderr);
35
+ // Show on console using info level for immediate feedback
36
+ packageLogger.info(`⚠️ STDERR:`);
37
+ result.stderr.split('\n').forEach((line)=>{
38
+ if (line.trim()) packageLogger.info(`${line}`);
39
+ });
40
+ }
41
+ }
42
+ return result;
43
+ } catch (error) {
44
+ if (showOutput === 'full' || showOutput === 'minimal') {
45
+ packageLogger.error(`Command failed: ${command}`);
46
+ if (error.stdout && showOutput === 'full') {
47
+ packageLogger.debug('STDOUT:');
48
+ packageLogger.debug(error.stdout);
49
+ packageLogger.info(`📤 STDOUT:`);
50
+ error.stdout.split('\n').forEach((line)=>{
51
+ if (line.trim()) packageLogger.info(`${line}`);
52
+ });
53
+ }
54
+ if (error.stderr && showOutput === 'full') {
55
+ packageLogger.debug('STDERR:');
56
+ packageLogger.debug(error.stderr);
57
+ packageLogger.info(`❌ STDERR:`);
58
+ error.stderr.split('\n').forEach((line)=>{
59
+ if (line.trim()) packageLogger.info(`${line}`);
60
+ });
61
+ }
62
+ }
63
+ throw error;
64
+ }
65
+ };
66
+ // Create a package-scoped logger that prefixes all messages
67
+ const createPackageLogger = (packageName, sequenceNumber, totalCount, isDryRun = false)=>{
68
+ const baseLogger = getLogger();
69
+ const prefix = `[${sequenceNumber}/${totalCount}] ${packageName}:`;
70
+ const dryRunPrefix = isDryRun ? 'DRY RUN: ' : '';
71
+ return {
72
+ info: (message, ...args)=>baseLogger.info(`${dryRunPrefix}${prefix} ${message}`, ...args),
73
+ warn: (message, ...args)=>baseLogger.warn(`${dryRunPrefix}${prefix} ${message}`, ...args),
74
+ error: (message, ...args)=>baseLogger.error(`${dryRunPrefix}${prefix} ${message}`, ...args),
75
+ debug: (message, ...args)=>baseLogger.debug(`${dryRunPrefix}${prefix} ${message}`, ...args),
76
+ verbose: (message, ...args)=>baseLogger.verbose(`${dryRunPrefix}${prefix} ${message}`, ...args),
77
+ silly: (message, ...args)=>baseLogger.silly(`${dryRunPrefix}${prefix} ${message}`, ...args)
78
+ };
79
+ };
80
+ // Helper function to format subproject error output
81
+ const formatSubprojectError = (packageName, error)=>{
82
+ const lines = [];
83
+ lines.push(`❌ Command failed in package ${packageName}:`);
84
+ // Format the main error message with indentation
85
+ if (error.message) {
86
+ const indentedMessage = error.message.split('\n').map((line)=>` ${line}`).join('\n');
87
+ lines.push(indentedMessage);
88
+ }
89
+ // If there's stderr output, show it indented as well
90
+ if (error.stderr && error.stderr.trim()) {
91
+ lines.push(' STDERR:');
92
+ const indentedStderr = error.stderr.split('\n').filter((line)=>line.trim()).map((line)=>` ${line}`).join('\n');
93
+ lines.push(indentedStderr);
94
+ }
95
+ // If there's stdout output, show it indented as well
96
+ if (error.stdout && error.stdout.trim()) {
97
+ lines.push(' STDOUT:');
98
+ const indentedStdout = error.stdout.split('\n').filter((line)=>line.trim()).map((line)=>` ${line}`).join('\n');
99
+ lines.push(indentedStdout);
100
+ }
101
+ return lines.join('\n');
102
+ };
103
+ const matchesPattern = (filePath, pattern)=>{
104
+ // Convert simple glob patterns to regex
105
+ const regexPattern = pattern.replace(/\\/g, '\\\\') // Escape backslashes
106
+ .replace(/\*\*/g, '.*') // ** matches any path segments
107
+ .replace(/\*/g, '[^/]*') // * matches any characters except path separator
108
+ .replace(/\?/g, '.') // ? matches any single character
109
+ .replace(/\./g, '\\.'); // Escape literal dots
110
+ const regex = new RegExp(`^${regexPattern}$`);
111
+ return regex.test(filePath) || regex.test(path.basename(filePath));
112
+ };
113
+ const shouldExclude = (packageJsonPath, excludedPatterns)=>{
114
+ if (!excludedPatterns || excludedPatterns.length === 0) {
115
+ return false;
116
+ }
117
+ // 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));
120
+ };
121
+ const scanForPackageJsonFiles = async (directory, excludedPatterns = [])=>{
122
+ const logger = getLogger();
123
+ const packageJsonPaths = [];
124
+ try {
125
+ // First check if there's a package.json in the specified directory itself
126
+ const directPackageJsonPath = path.join(directory, 'package.json');
127
+ try {
128
+ await fs.access(directPackageJsonPath);
129
+ // Check if this package should be excluded
130
+ if (!shouldExclude(directPackageJsonPath, excludedPatterns)) {
131
+ packageJsonPaths.push(directPackageJsonPath);
132
+ logger.verbose(`Found package.json at: ${directPackageJsonPath}`);
133
+ } else {
134
+ logger.verbose(`Excluding package.json at: ${directPackageJsonPath} (matches exclusion pattern)`);
135
+ }
136
+ } catch {
137
+ // No package.json in the root of this directory, that's fine
138
+ }
139
+ // Then scan subdirectories for package.json files
140
+ const entries = await fs.readdir(directory, {
141
+ withFileTypes: true
142
+ });
143
+ for (const entry of entries){
144
+ if (entry.isDirectory()) {
145
+ const subDirPath = path.join(directory, entry.name);
146
+ const packageJsonPath = path.join(subDirPath, 'package.json');
147
+ try {
148
+ await fs.access(packageJsonPath);
149
+ // Check if this package should be excluded
150
+ if (shouldExclude(packageJsonPath, excludedPatterns)) {
151
+ logger.verbose(`Excluding package.json at: ${packageJsonPath} (matches exclusion pattern)`);
152
+ continue;
153
+ }
154
+ packageJsonPaths.push(packageJsonPath);
155
+ logger.verbose(`Found package.json at: ${packageJsonPath}`);
156
+ } catch {
157
+ // No package.json in this directory, continue
158
+ }
159
+ }
160
+ }
161
+ } catch (error) {
162
+ logger.error(`Failed to scan directory ${directory}: ${error}`);
163
+ throw error;
164
+ }
165
+ return packageJsonPaths;
166
+ };
167
+ const parsePackageJson = async (packageJsonPath)=>{
168
+ const logger = getLogger();
169
+ const storage = create({
170
+ log: logger.info
171
+ });
172
+ try {
173
+ const content = await storage.readFile(packageJsonPath, 'utf-8');
174
+ const parsed = safeJsonParse(content, packageJsonPath);
175
+ const packageJson = validatePackageJson(parsed, packageJsonPath);
176
+ if (!packageJson.name) {
177
+ throw new Error(`Package at ${packageJsonPath} has no name field`);
178
+ }
179
+ const dependencies = new Set();
180
+ // Collect all types of dependencies
181
+ const depTypes = [
182
+ 'dependencies',
183
+ 'devDependencies',
184
+ 'peerDependencies',
185
+ 'optionalDependencies'
186
+ ];
187
+ for (const depType of depTypes){
188
+ if (packageJson[depType]) {
189
+ Object.keys(packageJson[depType]).forEach((dep)=>dependencies.add(dep));
190
+ }
191
+ }
192
+ return {
193
+ name: packageJson.name,
194
+ version: packageJson.version || '0.0.0',
195
+ path: path.dirname(packageJsonPath),
196
+ dependencies,
197
+ localDependencies: new Set() // Will be populated later
198
+ };
199
+ } catch (error) {
200
+ logger.error(`Failed to parse package.json at ${packageJsonPath}: ${error}`);
201
+ throw error;
202
+ }
203
+ };
204
+ const buildDependencyGraph = async (packageJsonPaths)=>{
205
+ const logger = getLogger();
206
+ const packages = new Map();
207
+ const edges = new Map();
208
+ // First pass: parse all package.json files
209
+ for (const packageJsonPath of packageJsonPaths){
210
+ const packageInfo = await parsePackageJson(packageJsonPath);
211
+ packages.set(packageInfo.name, packageInfo);
212
+ logger.verbose(`Parsed package: ${packageInfo.name} at ${packageInfo.path}`);
213
+ }
214
+ // Second pass: identify local dependencies and build edges
215
+ for (const [packageName, packageInfo] of packages){
216
+ const localDeps = new Set();
217
+ const edges_set = new Set();
218
+ for (const dep of packageInfo.dependencies){
219
+ if (packages.has(dep)) {
220
+ localDeps.add(dep);
221
+ edges_set.add(dep);
222
+ logger.verbose(`${packageName} depends on local package: ${dep}`);
223
+ }
224
+ }
225
+ packageInfo.localDependencies = localDeps;
226
+ edges.set(packageName, edges_set);
227
+ }
228
+ return {
229
+ packages,
230
+ edges
231
+ };
232
+ };
233
+ const topologicalSort = (graph)=>{
234
+ const logger = getLogger();
235
+ const { packages, edges } = graph;
236
+ const visited = new Set();
237
+ const visiting = new Set();
238
+ const result = [];
239
+ const visit = (packageName)=>{
240
+ if (visited.has(packageName)) {
241
+ return;
242
+ }
243
+ if (visiting.has(packageName)) {
244
+ throw new Error(`Circular dependency detected involving package: ${packageName}`);
245
+ }
246
+ visiting.add(packageName);
247
+ // Visit all dependencies first
248
+ const deps = edges.get(packageName) || new Set();
249
+ for (const dep of deps){
250
+ visit(dep);
251
+ }
252
+ visiting.delete(packageName);
253
+ visited.add(packageName);
254
+ result.push(packageName);
255
+ };
256
+ // Visit all packages
257
+ for (const packageName of packages.keys()){
258
+ if (!visited.has(packageName)) {
259
+ visit(packageName);
260
+ }
261
+ }
262
+ logger.verbose(`Topological sort completed. Build order determined for ${result.length} packages.`);
263
+ return result;
264
+ };
265
+ // Group packages into dependency levels for parallel execution
266
+ const groupPackagesByDependencyLevels = (graph, buildOrder, runConfig)=>{
267
+ const logger = getLogger();
268
+ const { edges } = graph;
269
+ const levels = [];
270
+ const packageLevels = new Map();
271
+ // Calculate the dependency level for each package
272
+ const calculateLevel = (packageName)=>{
273
+ if (packageLevels.has(packageName)) {
274
+ return packageLevels.get(packageName);
275
+ }
276
+ const deps = edges.get(packageName) || new Set();
277
+ if (deps.size === 0) {
278
+ // No dependencies - this is level 0
279
+ packageLevels.set(packageName, 0);
280
+ if (runConfig === null || runConfig === void 0 ? void 0 : runConfig.debug) {
281
+ logger.debug(`${packageName}: Level 0 (no local dependencies)`);
282
+ }
283
+ return 0;
284
+ }
285
+ // Level is 1 + max level of dependencies
286
+ let maxDepLevel = -1;
287
+ for (const dep of deps){
288
+ const depLevel = calculateLevel(dep);
289
+ maxDepLevel = Math.max(maxDepLevel, depLevel);
290
+ }
291
+ const level = maxDepLevel + 1;
292
+ packageLevels.set(packageName, level);
293
+ if (runConfig === null || runConfig === void 0 ? void 0 : runConfig.debug) {
294
+ const depsList = Array.from(deps).join(', ');
295
+ logger.debug(`${packageName}: Level ${level} (depends on: ${depsList})`);
296
+ }
297
+ return level;
298
+ };
299
+ // Calculate levels for all packages
300
+ for (const packageName of buildOrder){
301
+ calculateLevel(packageName);
302
+ }
303
+ // Group packages by their levels
304
+ for (const packageName of buildOrder){
305
+ const level = packageLevels.get(packageName);
306
+ while(levels.length <= level){
307
+ levels.push([]);
308
+ }
309
+ levels[level].push(packageName);
310
+ }
311
+ // Only show grouping info if verbose or debug mode is enabled
312
+ if ((runConfig === null || runConfig === void 0 ? void 0 : runConfig.verbose) || (runConfig === null || runConfig === void 0 ? void 0 : runConfig.debug)) {
313
+ logger.verbose(`Packages grouped into ${levels.length} dependency levels for parallel execution`);
314
+ for(let i = 0; i < levels.length; i++){
315
+ logger.verbose(` Level ${i}: ${levels[i].join(', ')}`);
316
+ }
317
+ }
318
+ return levels;
319
+ };
320
+ // Execute a single package and return execution result
321
+ const executePackage = async (packageName, packageInfo, commandToRun, runConfig, isDryRun, index, total, isBuiltInCommand = false)=>{
322
+ const packageLogger = createPackageLogger(packageName, index + 1, total, isDryRun);
323
+ const packageDir = packageInfo.path;
324
+ const logger = getLogger();
325
+ // Determine output level based on flags
326
+ let showOutput = 'none';
327
+ if (runConfig.debug) {
328
+ showOutput = 'full';
329
+ } else if (runConfig.verbose) {
330
+ showOutput = 'minimal';
331
+ }
332
+ // Show package start info - always visible for progress tracking
333
+ if (runConfig.debug) {
334
+ packageLogger.debug(`Starting execution in ${packageDir}`);
335
+ packageLogger.debug(`Command: ${commandToRun}`);
336
+ } else if (runConfig.verbose) {
337
+ packageLogger.verbose(`Starting execution in ${packageDir}`);
338
+ } else {
339
+ // Basic progress info even without flags
340
+ logger.info(`[${index + 1}/${total}] ${packageName}: Running ${commandToRun}...`);
341
+ }
342
+ try {
343
+ if (isDryRun) {
344
+ // Use main logger for the specific message tests expect
345
+ logger.info(`DRY RUN: Would execute: ${commandToRun}`);
346
+ if (runConfig.debug || runConfig.verbose) {
347
+ packageLogger.info(`In directory: ${packageDir}`);
348
+ }
349
+ } else {
350
+ // Change to the package directory and run the command
351
+ const originalCwd = process.cwd();
352
+ try {
353
+ process.chdir(packageDir);
354
+ if (runConfig.debug) {
355
+ packageLogger.debug(`Changed to directory: ${packageDir}`);
356
+ }
357
+ if (runConfig.debug || runConfig.verbose) {
358
+ if (isBuiltInCommand) {
359
+ packageLogger.info(`Executing built-in command: ${commandToRun}`);
360
+ } else {
361
+ packageLogger.info(`Executing command: ${commandToRun}`);
362
+ }
363
+ }
364
+ // For built-in commands, shell out to a separate kodrdriv process
365
+ // This preserves individual project configurations
366
+ if (isBuiltInCommand) {
367
+ // Extract the command name from "kodrdriv <command>"
368
+ const builtInCommandName = commandToRun.replace('kodrdriv ', '');
369
+ if (runConfig.debug) {
370
+ packageLogger.debug(`Shelling out to separate kodrdriv process for ${builtInCommandName} command`);
371
+ }
372
+ // Use runWithLogging for built-in commands to capture all output
373
+ await runWithLogging(commandToRun, packageLogger, {}, showOutput);
374
+ } else {
375
+ // For custom commands, use the existing logic
376
+ await runWithLogging(commandToRun, packageLogger, {}, showOutput);
377
+ }
378
+ if (runConfig.debug || runConfig.verbose) {
379
+ packageLogger.info(`Command completed successfully`);
380
+ } else {
381
+ // Basic completion info
382
+ logger.info(`[${index + 1}/${total}] ${packageName}: ✅ Completed`);
383
+ }
384
+ } finally{
385
+ process.chdir(originalCwd);
386
+ if (runConfig.debug) {
387
+ packageLogger.debug(`Restored working directory to: ${originalCwd}`);
388
+ }
389
+ }
390
+ }
391
+ return {
392
+ success: true
393
+ };
394
+ } catch (error) {
395
+ if (runConfig.debug || runConfig.verbose) {
396
+ packageLogger.error(`❌ Execution failed: ${error.message}`);
397
+ } else {
398
+ logger.error(`[${index + 1}/${total}] ${packageName}: ❌ Failed - ${error.message}`);
399
+ }
400
+ return {
401
+ success: false,
402
+ error
403
+ };
404
+ }
405
+ };
406
+ const execute = async (runConfig)=>{
407
+ var _runConfig_tree, _runConfig_tree1;
408
+ const logger = getLogger();
409
+ const isDryRun = runConfig.dryRun || false;
410
+ // 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;
412
+ const supportedBuiltInCommands = [
413
+ 'commit',
414
+ 'publish',
415
+ 'link',
416
+ 'unlink'
417
+ ];
418
+ if (builtInCommand && !supportedBuiltInCommands.includes(builtInCommand)) {
419
+ throw new Error(`Unsupported built-in command: ${builtInCommand}. Supported commands: ${supportedBuiltInCommands.join(', ')}`);
420
+ }
421
+ // 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) || [
423
+ process.cwd()
424
+ ];
425
+ if (targetDirectories.length === 1) {
426
+ logger.info(`${isDryRun ? 'DRY RUN: ' : ''}Analyzing workspace at: ${targetDirectories[0]}`);
427
+ } else {
428
+ logger.info(`${isDryRun ? 'DRY RUN: ' : ''}Analyzing workspaces at: ${targetDirectories.join(', ')}`);
429
+ }
430
+ try {
431
+ var _runConfig_tree2, _runConfig_tree3, _runConfig_tree4, _runConfig_tree5;
432
+ // 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) || [];
434
+ if (excludedPatterns.length > 0) {
435
+ logger.verbose(`${isDryRun ? 'DRY RUN: ' : ''}Using exclusion patterns: ${excludedPatterns.join(', ')}`);
436
+ }
437
+ // Scan for package.json files across all directories
438
+ logger.verbose(`${isDryRun ? 'DRY RUN: ' : ''}Scanning for package.json files...`);
439
+ let allPackageJsonPaths = [];
440
+ for (const targetDirectory of targetDirectories){
441
+ logger.verbose(`${isDryRun ? 'DRY RUN: ' : ''}Scanning directory: ${targetDirectory}`);
442
+ const packageJsonPaths = await scanForPackageJsonFiles(targetDirectory, excludedPatterns);
443
+ allPackageJsonPaths = allPackageJsonPaths.concat(packageJsonPaths);
444
+ }
445
+ const packageJsonPaths = allPackageJsonPaths;
446
+ if (packageJsonPaths.length === 0) {
447
+ const directoriesStr = targetDirectories.join(', ');
448
+ const message = `No package.json files found in subdirectories of: ${directoriesStr}`;
449
+ logger.warn(message);
450
+ return message;
451
+ }
452
+ logger.info(`${isDryRun ? 'DRY RUN: ' : ''}Found ${packageJsonPaths.length} package.json files`);
453
+ // Build dependency graph
454
+ logger.verbose(`${isDryRun ? 'DRY RUN: ' : ''}Building dependency graph...`);
455
+ const dependencyGraph = await buildDependencyGraph(packageJsonPaths);
456
+ // Perform topological sort to determine build order
457
+ logger.verbose(`${isDryRun ? 'DRY RUN: ' : ''}Determining build order...`);
458
+ let buildOrder = topologicalSort(dependencyGraph);
459
+ // Handle start-from functionality if specified
460
+ const startFrom = (_runConfig_tree3 = runConfig.tree) === null || _runConfig_tree3 === void 0 ? void 0 : _runConfig_tree3.startFrom;
461
+ if (startFrom) {
462
+ logger.verbose(`${isDryRun ? 'DRY RUN: ' : ''}Looking for start package: ${startFrom}`);
463
+ // Find the package that matches the startFrom directory name
464
+ const startIndex = buildOrder.findIndex((packageName)=>{
465
+ const packageInfo = dependencyGraph.packages.get(packageName);
466
+ const dirName = path.basename(packageInfo.path);
467
+ return dirName === startFrom || packageName === startFrom;
468
+ });
469
+ if (startIndex === -1) {
470
+ // Check if the package exists but was excluded across all directories
471
+ let allPackageJsonPathsForCheck = [];
472
+ for (const targetDirectory of targetDirectories){
473
+ const packageJsonPaths = await scanForPackageJsonFiles(targetDirectory, []); // No exclusions
474
+ allPackageJsonPathsForCheck = allPackageJsonPathsForCheck.concat(packageJsonPaths);
475
+ }
476
+ let wasExcluded = false;
477
+ for (const packageJsonPath of allPackageJsonPathsForCheck){
478
+ try {
479
+ const packageInfo = await parsePackageJson(packageJsonPath);
480
+ const dirName = path.basename(packageInfo.path);
481
+ if (dirName === startFrom || packageInfo.name === startFrom) {
482
+ // Check if this package was excluded
483
+ if (shouldExclude(packageJsonPath, excludedPatterns)) {
484
+ wasExcluded = true;
485
+ break;
486
+ }
487
+ }
488
+ } catch {
489
+ continue;
490
+ }
491
+ }
492
+ if (wasExcluded) {
493
+ const excludedPatternsStr = excludedPatterns.join(', ');
494
+ throw new Error(`Package directory '${startFrom}' was excluded by exclusion patterns: ${excludedPatternsStr}. Remove the exclusion pattern or choose a different starting package.`);
495
+ } else {
496
+ const availablePackages = buildOrder.map((name)=>{
497
+ const packageInfo = dependencyGraph.packages.get(name);
498
+ return `${path.basename(packageInfo.path)} (${name})`;
499
+ }).join(', ');
500
+ throw new Error(`Package directory '${startFrom}' not found. Available packages: ${availablePackages}`);
501
+ }
502
+ }
503
+ const skippedCount = startIndex;
504
+ buildOrder = buildOrder.slice(startIndex);
505
+ if (skippedCount > 0) {
506
+ logger.info(`${isDryRun ? 'DRY RUN: ' : ''}Resuming from '${startFrom}' - skipping ${skippedCount} package${skippedCount === 1 ? '' : 's'}`);
507
+ }
508
+ }
509
+ // Display results
510
+ logger.info(`${isDryRun ? 'DRY RUN: ' : ''}Build order determined:`);
511
+ let returnOutput = '';
512
+ if (runConfig.verbose || runConfig.debug) {
513
+ // Verbose mode: Skip simple format, show detailed format before command execution
514
+ logger.info(''); // Add spacing
515
+ logger.info(`Detailed Build Order for ${buildOrder.length} packages${startFrom ? ` (starting from ${startFrom})` : ''}:`);
516
+ logger.info('==========================================');
517
+ buildOrder.forEach((packageName, index)=>{
518
+ const packageInfo = dependencyGraph.packages.get(packageName);
519
+ const localDeps = Array.from(packageInfo.localDependencies);
520
+ logger.info(`${index + 1}. ${packageName} (${packageInfo.version})`);
521
+ logger.info(` Path: ${packageInfo.path}`);
522
+ if (localDeps.length > 0) {
523
+ logger.info(` Local Dependencies: ${localDeps.join(', ')}`);
524
+ } else {
525
+ logger.info(` Local Dependencies: none`);
526
+ }
527
+ logger.info(''); // Add spacing between packages
528
+ });
529
+ // Simple return output for verbose mode (no need to repeat detailed info)
530
+ returnOutput = `\nBuild order: ${buildOrder.join(' → ')}\n`;
531
+ } else {
532
+ // Non-verbose mode: Show simple build order
533
+ buildOrder.forEach((packageName, index)=>{
534
+ const packageInfo = dependencyGraph.packages.get(packageName);
535
+ const localDeps = Array.from(packageInfo.localDependencies);
536
+ // Log each step
537
+ if (localDeps.length > 0) {
538
+ logger.info(`${index + 1}. ${packageName} (depends on: ${localDeps.join(', ')})`);
539
+ } else {
540
+ logger.info(`${index + 1}. ${packageName} (no local dependencies)`);
541
+ }
542
+ });
543
+ // Simple return output for non-verbose mode
544
+ returnOutput = `\nBuild order: ${buildOrder.join(' → ')}\n`;
545
+ }
546
+ // 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;
549
+ // Determine command to execute
550
+ let commandToRun;
551
+ let isBuiltInCommand = false;
552
+ if (builtInCommand) {
553
+ // Built-in command mode: shell out to kodrdriv subprocess
554
+ commandToRun = `kodrdriv ${builtInCommand}`;
555
+ isBuiltInCommand = true;
556
+ } else if (cmd) {
557
+ // Custom command mode
558
+ commandToRun = cmd;
559
+ }
560
+ if (commandToRun) {
561
+ // Add spacing before command execution
562
+ logger.info('');
563
+ const executionDescription = isBuiltInCommand ? `built-in command "${builtInCommand}"` : `"${commandToRun}"`;
564
+ const parallelInfo = useParallel ? ' (with parallel execution)' : '';
565
+ logger.info(`${isDryRun ? 'DRY RUN: ' : ''}Executing ${executionDescription} in ${buildOrder.length} packages${parallelInfo}...`);
566
+ let successCount = 0;
567
+ let failedPackage = null;
568
+ if (useParallel) {
569
+ // Parallel execution: group packages by dependency levels
570
+ const dependencyLevels = groupPackagesByDependencyLevels(dependencyGraph, buildOrder, runConfig);
571
+ if (runConfig.debug) {
572
+ logger.debug(`Parallel execution strategy: ${dependencyLevels.length} dependency levels identified`);
573
+ for(let i = 0; i < dependencyLevels.length; i++){
574
+ const level = dependencyLevels[i];
575
+ logger.debug(` Level ${i + 1}: ${level.join(', ')} ${level.length > 1 ? '(parallel)' : '(sequential)'}`);
576
+ }
577
+ }
578
+ for(let levelIndex = 0; levelIndex < dependencyLevels.length; levelIndex++){
579
+ const currentLevel = dependencyLevels[levelIndex];
580
+ if (runConfig.debug) {
581
+ if (currentLevel.length === 1) {
582
+ const packageName = currentLevel[0];
583
+ logger.debug(`Starting Level ${levelIndex + 1}: ${packageName} (no dependencies within this level)`);
584
+ } else {
585
+ logger.debug(`Starting Level ${levelIndex + 1}: ${currentLevel.length} packages can run in parallel`);
586
+ logger.debug(` Parallel packages: ${currentLevel.join(', ')}`);
587
+ }
588
+ } else if (runConfig.verbose) {
589
+ if (currentLevel.length === 1) {
590
+ const packageName = currentLevel[0];
591
+ logger.verbose(`Level ${levelIndex + 1}: Executing ${packageName}...`);
592
+ } else {
593
+ logger.verbose(`Level ${levelIndex + 1}: Executing ${currentLevel.length} packages in parallel: ${currentLevel.join(', ')}...`);
594
+ }
595
+ } else {
596
+ // Basic level info
597
+ if (currentLevel.length === 1) {
598
+ const packageName = currentLevel[0];
599
+ logger.info(`${isDryRun ? 'DRY RUN: ' : ''}Level ${levelIndex + 1}: Executing ${packageName}...`);
600
+ } else {
601
+ logger.info(`${isDryRun ? 'DRY RUN: ' : ''}Level ${levelIndex + 1}: Executing ${currentLevel.length} packages in parallel: ${currentLevel.join(', ')}...`);
602
+ }
603
+ }
604
+ // Execute all packages in this level in parallel
605
+ const levelPromises = currentLevel.map((packageName)=>{
606
+ const packageInfo = dependencyGraph.packages.get(packageName);
607
+ const globalIndex = buildOrder.indexOf(packageName);
608
+ return executePackage(packageName, packageInfo, commandToRun, runConfig, isDryRun, globalIndex, buildOrder.length, isBuiltInCommand);
609
+ });
610
+ // Wait for all packages in this level to complete
611
+ const results = await Promise.allSettled(levelPromises);
612
+ // Check results and handle errors
613
+ for(let i = 0; i < results.length; i++){
614
+ const result = results[i];
615
+ const packageName = currentLevel[i];
616
+ const globalIndex = buildOrder.indexOf(packageName);
617
+ const packageLogger = createPackageLogger(packageName, globalIndex + 1, buildOrder.length, isDryRun);
618
+ if (result.status === 'fulfilled') {
619
+ if (result.value.success) {
620
+ successCount++;
621
+ // Add spacing between packages (except after the last one in the level)
622
+ if (i < currentLevel.length - 1) {
623
+ logger.info('');
624
+ logger.info('');
625
+ }
626
+ } else {
627
+ // Package failed
628
+ failedPackage = packageName;
629
+ const formattedError = formatSubprojectError(packageName, result.value.error);
630
+ if (!isDryRun) {
631
+ packageLogger.error(`Execution failed`);
632
+ logger.error(formattedError);
633
+ logger.error(`Failed after ${successCount} successful packages.`);
634
+ const packageDir = dependencyGraph.packages.get(packageName).path;
635
+ const packageDirName = path.basename(packageDir);
636
+ logger.error(`To resume from this package, run:`);
637
+ if (isBuiltInCommand) {
638
+ logger.error(` kodrdriv tree ${builtInCommand} --start-from ${packageDirName}`);
639
+ } else {
640
+ logger.error(` kodrdriv tree --start-from ${packageDirName} --cmd "${commandToRun}"`);
641
+ }
642
+ throw new Error(`Command failed in package ${packageName}`);
643
+ }
644
+ break;
645
+ }
646
+ } else {
647
+ // Promise was rejected
648
+ failedPackage = packageName;
649
+ if (!isDryRun) {
650
+ packageLogger.error(`Unexpected error: ${result.reason}`);
651
+ logger.error(`Failed after ${successCount} successful packages.`);
652
+ const packageDir = dependencyGraph.packages.get(packageName).path;
653
+ const packageDirName = path.basename(packageDir);
654
+ logger.error(`To resume from this package, run:`);
655
+ if (isBuiltInCommand) {
656
+ logger.error(` kodrdriv tree ${builtInCommand} --start-from ${packageDirName}`);
657
+ } else {
658
+ logger.error(` kodrdriv tree --start-from ${packageDirName} --cmd "${commandToRun}"`);
659
+ }
660
+ throw new Error(`Unexpected error in package ${packageName}`);
661
+ }
662
+ break;
663
+ }
664
+ }
665
+ // If any package failed, stop execution
666
+ if (failedPackage) {
667
+ break;
668
+ }
669
+ // Level completion logging
670
+ if (runConfig.debug) {
671
+ if (currentLevel.length > 1) {
672
+ logger.debug(`✅ Level ${levelIndex + 1} completed: all ${currentLevel.length} packages finished successfully`);
673
+ logger.debug(` Completed packages: ${currentLevel.join(', ')}`);
674
+ } else if (currentLevel.length === 1 && successCount > 0) {
675
+ const packageName = currentLevel[0];
676
+ logger.debug(`✅ Level ${levelIndex + 1} completed: ${packageName} finished successfully`);
677
+ }
678
+ } else if (runConfig.verbose) {
679
+ if (currentLevel.length > 1) {
680
+ logger.verbose(`✅ Level ${levelIndex + 1} completed: all ${currentLevel.length} packages finished successfully`);
681
+ } else if (currentLevel.length === 1 && successCount > 0) {
682
+ const packageName = currentLevel[0];
683
+ logger.verbose(`✅ Level ${levelIndex + 1} completed: ${packageName} finished successfully`);
684
+ }
685
+ } else {
686
+ // Basic completion info
687
+ if (currentLevel.length > 1) {
688
+ logger.info(`✅ Level ${levelIndex + 1} completed: all ${currentLevel.length} packages finished successfully`);
689
+ } else if (currentLevel.length === 1 && successCount > 0) {
690
+ const packageName = currentLevel[0];
691
+ logger.info(`✅ Level ${levelIndex + 1} completed: ${packageName} finished successfully`);
692
+ }
693
+ }
694
+ }
695
+ } else {
696
+ // Sequential execution
697
+ for(let i = 0; i < buildOrder.length; i++){
698
+ const packageName = buildOrder[i];
699
+ const packageInfo = dependencyGraph.packages.get(packageName);
700
+ const packageLogger = createPackageLogger(packageName, i + 1, buildOrder.length, isDryRun);
701
+ const result = await executePackage(packageName, packageInfo, commandToRun, runConfig, isDryRun, i, buildOrder.length, isBuiltInCommand);
702
+ if (result.success) {
703
+ successCount++;
704
+ // Add spacing between packages (except after the last one)
705
+ if (i < buildOrder.length - 1) {
706
+ logger.info('');
707
+ logger.info('');
708
+ }
709
+ } else {
710
+ failedPackage = packageName;
711
+ const formattedError = formatSubprojectError(packageName, result.error);
712
+ if (!isDryRun) {
713
+ packageLogger.error(`Execution failed`);
714
+ logger.error(formattedError);
715
+ 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:`);
719
+ if (isBuiltInCommand) {
720
+ logger.error(` kodrdriv tree ${builtInCommand} --start-from ${packageDirName}`);
721
+ } else {
722
+ logger.error(` kodrdriv tree --start-from ${packageDirName} --cmd "${commandToRun}"`);
723
+ }
724
+ throw new Error(`Command failed in package ${packageName}`);
725
+ }
726
+ break;
727
+ }
728
+ }
729
+ }
730
+ if (!failedPackage) {
731
+ const summary = `${isDryRun ? 'DRY RUN: ' : ''}All ${buildOrder.length} packages completed successfully! 🎉`;
732
+ logger.info(summary);
733
+ return returnOutput; // Don't duplicate the summary in return string
734
+ }
735
+ }
736
+ return returnOutput;
737
+ } catch (error) {
738
+ const errorMessage = `Failed to analyze workspace: ${error.message}`;
739
+ logger.error(errorMessage);
740
+ throw new Error(errorMessage);
741
+ }
742
+ };
743
+
744
+ export { execute };
745
+ //# sourceMappingURL=tree.js.map