@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.
- package/README.md +3 -1
- package/dist/application.js +29 -5
- package/dist/application.js.map +1 -1
- package/dist/arguments.js +187 -75
- package/dist/arguments.js.map +1 -1
- package/dist/commands/audio-commit.js +35 -14
- package/dist/commands/audio-commit.js.map +1 -1
- package/dist/commands/audio-review.js +41 -20
- package/dist/commands/audio-review.js.map +1 -1
- package/dist/commands/clean.js +2 -2
- package/dist/commands/clean.js.map +1 -1
- package/dist/commands/commit.js +369 -47
- package/dist/commands/commit.js.map +1 -1
- package/dist/commands/development.js +264 -0
- package/dist/commands/development.js.map +1 -0
- package/dist/commands/link.js +357 -182
- package/dist/commands/link.js.map +1 -1
- package/dist/commands/publish.js +419 -306
- package/dist/commands/publish.js.map +1 -1
- package/dist/commands/release.js +240 -18
- package/dist/commands/release.js.map +1 -1
- package/dist/commands/review.js +56 -40
- package/dist/commands/review.js.map +1 -1
- package/dist/commands/select-audio.js +4 -4
- package/dist/commands/select-audio.js.map +1 -1
- package/dist/commands/tree.js +779 -40
- package/dist/commands/tree.js.map +1 -1
- package/dist/commands/unlink.js +267 -372
- package/dist/commands/unlink.js.map +1 -1
- package/dist/commands/versions.js +224 -0
- package/dist/commands/versions.js.map +1 -0
- package/dist/constants.js +50 -12
- package/dist/constants.js.map +1 -1
- package/dist/content/diff.js +122 -1
- package/dist/content/diff.js.map +1 -1
- package/dist/content/files.js +192 -0
- package/dist/content/files.js.map +1 -0
- package/dist/content/issues.js +17 -46
- package/dist/content/issues.js.map +1 -1
- package/dist/content/log.js +16 -0
- package/dist/content/log.js.map +1 -1
- package/dist/logging.js +3 -3
- package/dist/logging.js.map +1 -1
- package/dist/main.js +0 -0
- package/dist/prompt/commit.js +11 -4
- package/dist/prompt/commit.js.map +1 -1
- package/dist/prompt/instructions/commit.md +20 -2
- package/dist/prompt/instructions/release.md +27 -10
- package/dist/prompt/instructions/review.md +75 -8
- package/dist/prompt/release.js +15 -7
- package/dist/prompt/release.js.map +1 -1
- package/dist/prompt/review.js +2 -2
- package/dist/prompt/review.js.map +1 -1
- package/dist/types.js +36 -7
- package/dist/types.js.map +1 -1
- package/dist/util/child.js +146 -4
- package/dist/util/child.js.map +1 -1
- package/dist/util/countdown.js +215 -0
- package/dist/util/countdown.js.map +1 -0
- package/dist/util/general.js +157 -13
- package/dist/util/general.js.map +1 -1
- package/dist/util/git.js +587 -0
- package/dist/util/git.js.map +1 -0
- package/dist/util/github.js +531 -11
- package/dist/util/github.js.map +1 -1
- package/dist/util/interactive.js +463 -0
- package/dist/util/interactive.js.map +1 -0
- package/dist/util/openai.js +152 -25
- package/dist/util/openai.js.map +1 -1
- package/dist/util/performance.js +5 -73
- package/dist/util/performance.js.map +1 -1
- package/dist/util/safety.js +4 -4
- package/dist/util/safety.js.map +1 -1
- package/dist/util/storage.js +30 -3
- package/dist/util/storage.js.map +1 -1
- package/dist/util/validation.js +1 -25
- package/dist/util/validation.js.map +1 -1
- package/package.json +12 -10
- package/test-increment.js +0 -0
- package/test-multiline/cli/package.json +8 -0
- package/test-multiline/core/package.json +5 -0
- package/test-multiline/mobile/package.json +8 -0
- package/test-multiline/web/package.json +8 -0
- package/dist/util/npmOptimizations.js +0 -174
- package/dist/util/npmOptimizations.js.map +0 -1
package/dist/commands/tree.js
CHANGED
|
@@ -1,12 +1,245 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import
|
|
3
|
-
import
|
|
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(
|
|
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 =
|
|
119
|
-
return excludedPatterns.some((pattern)=>matchesPattern(packageJsonPath, pattern) || matchesPattern(relativePath, pattern) || matchesPattern(
|
|
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 =
|
|
359
|
+
const directPackageJsonPath = path__default.join(directory, 'package.json');
|
|
127
360
|
try {
|
|
128
|
-
await
|
|
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
|
|
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 =
|
|
146
|
-
const packageJsonPath =
|
|
378
|
+
const subDirPath = path__default.join(directory, entry.name);
|
|
379
|
+
const packageJsonPath = path__default.join(subDirPath, 'package.json');
|
|
147
380
|
try {
|
|
148
|
-
await
|
|
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:
|
|
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
|
-
|
|
386
|
-
|
|
387
|
-
|
|
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 = (
|
|
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 = ((
|
|
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
|
|
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 = ((
|
|
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 = (
|
|
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 =
|
|
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 =
|
|
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 `${
|
|
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
|
-
|
|
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 = (
|
|
548
|
-
const useParallel = ((
|
|
1222
|
+
const cmd = (_runConfig_tree6 = runConfig.tree) === null || _runConfig_tree6 === void 0 ? void 0 : _runConfig_tree6.cmd;
|
|
1223
|
+
const useParallel = ((_runConfig_tree7 = runConfig.tree) === null || _runConfig_tree7 === void 0 ? void 0 : _runConfig_tree7.parallel) || false;
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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} --
|
|
1452
|
+
logger.error(` kodrdriv tree ${builtInCommand} --continue`);
|
|
721
1453
|
} else {
|
|
722
|
-
logger.error(` kodrdriv tree --
|
|
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
|
|