@eldrforge/kodrdriv 0.1.0 → 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 +1 -0
- package/dist/application.js +25 -3
- package/dist/application.js.map +1 -1
- package/dist/arguments.js +103 -18
- package/dist/arguments.js.map +1 -1
- package/dist/commands/audio-commit.js +28 -7
- package/dist/commands/audio-commit.js.map +1 -1
- package/dist/commands/audio-review.js +28 -7
- package/dist/commands/audio-review.js.map +1 -1
- package/dist/commands/commit.js +75 -18
- package/dist/commands/commit.js.map +1 -1
- package/dist/commands/development.js +264 -0
- package/dist/commands/development.js.map +1 -0
- package/dist/commands/link.js +356 -181
- package/dist/commands/link.js.map +1 -1
- package/dist/commands/publish.js +166 -32
- package/dist/commands/publish.js.map +1 -1
- package/dist/commands/release.js +78 -13
- package/dist/commands/release.js.map +1 -1
- package/dist/commands/review.js +10 -6
- package/dist/commands/review.js.map +1 -1
- package/dist/commands/tree.js +450 -24
- package/dist/commands/tree.js.map +1 -1
- package/dist/commands/unlink.js +267 -372
- package/dist/commands/unlink.js.map +1 -1
- package/dist/commands/versions.js +224 -0
- package/dist/commands/versions.js.map +1 -0
- package/dist/constants.js +29 -10
- package/dist/constants.js.map +1 -1
- package/dist/content/diff.js.map +1 -1
- package/dist/content/files.js +192 -0
- package/dist/content/files.js.map +1 -0
- package/dist/content/log.js +16 -0
- package/dist/content/log.js.map +1 -1
- package/dist/main.js +0 -0
- package/dist/prompt/commit.js +9 -2
- package/dist/prompt/commit.js.map +1 -1
- package/dist/prompt/instructions/commit.md +20 -2
- package/dist/prompt/instructions/release.md +27 -10
- package/dist/prompt/instructions/review.md +75 -8
- package/dist/prompt/release.js +13 -5
- package/dist/prompt/release.js.map +1 -1
- package/dist/types.js +21 -5
- package/dist/types.js.map +1 -1
- package/dist/util/child.js +112 -26
- package/dist/util/child.js.map +1 -1
- package/dist/util/countdown.js +215 -0
- package/dist/util/countdown.js.map +1 -0
- package/dist/util/general.js +10 -2
- package/dist/util/general.js.map +1 -1
- package/dist/util/git.js +587 -0
- package/dist/util/git.js.map +1 -0
- package/dist/util/github.js +519 -3
- package/dist/util/github.js.map +1 -1
- package/dist/util/interactive.js +245 -79
- package/dist/util/interactive.js.map +1 -1
- package/dist/util/openai.js +70 -22
- package/dist/util/openai.js.map +1 -1
- package/dist/util/performance.js +1 -69
- package/dist/util/performance.js.map +1 -1
- package/dist/util/storage.js +28 -1
- package/dist/util/storage.js.map +1 -1
- package/dist/util/validation.js +1 -25
- package/dist/util/validation.js.map +1 -1
- package/package.json +10 -8
- package/test-multiline/cli/package.json +8 -0
- package/test-multiline/core/package.json +5 -0
- package/test-multiline/mobile/package.json +8 -0
- package/test-multiline/web/package.json +8 -0
- package/dist/util/npmOptimizations.js +0 -174
- package/dist/util/npmOptimizations.js.map +0 -1
package/dist/commands/link.js
CHANGED
|
@@ -1,224 +1,399 @@
|
|
|
1
|
-
import path__default from 'path';
|
|
2
|
-
import { ValidationError, CommandError } from '../error/CommandErrors.js';
|
|
3
1
|
import { getLogger, getDryRunLogger } from '../logging.js';
|
|
2
|
+
import { run, runSecure } from '../util/child.js';
|
|
3
|
+
import { findAllPackageJsonFiles } from '../util/performance.js';
|
|
4
4
|
import { create } from '../util/storage.js';
|
|
5
|
-
import {
|
|
6
|
-
import
|
|
5
|
+
import { safeJsonParse, validatePackageJson } from '../util/validation.js';
|
|
6
|
+
import fs__default from 'fs/promises';
|
|
7
|
+
import path__default from 'path';
|
|
7
8
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
if (
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
9
|
+
// Helper function to create symbolic links manually
|
|
10
|
+
const createSymbolicLink = async (packageName, sourcePath, targetDir, logger, isDryRun = false)=>{
|
|
11
|
+
try {
|
|
12
|
+
// Parse package name to get scope and name parts
|
|
13
|
+
const [scope, name] = packageName.startsWith('@') ? packageName.split('/') : [
|
|
14
|
+
null,
|
|
15
|
+
packageName
|
|
16
|
+
];
|
|
17
|
+
// Create the target path structure
|
|
18
|
+
const nodeModulesPath = path__default.join(targetDir, 'node_modules');
|
|
19
|
+
let targetPath;
|
|
20
|
+
if (scope) {
|
|
21
|
+
// Scoped package: node_modules/@scope/name
|
|
22
|
+
const scopeDir = path__default.join(nodeModulesPath, scope);
|
|
23
|
+
targetPath = path__default.join(scopeDir, name);
|
|
24
|
+
if (!isDryRun) {
|
|
25
|
+
// Ensure scope directory exists
|
|
26
|
+
await fs__default.mkdir(scopeDir, {
|
|
27
|
+
recursive: true
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
} else {
|
|
31
|
+
// Unscoped package: node_modules/name
|
|
32
|
+
targetPath = path__default.join(nodeModulesPath, name);
|
|
33
|
+
if (!isDryRun) {
|
|
34
|
+
// Ensure node_modules directory exists
|
|
35
|
+
await fs__default.mkdir(nodeModulesPath, {
|
|
36
|
+
recursive: true
|
|
37
|
+
});
|
|
29
38
|
}
|
|
30
39
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
40
|
+
if (isDryRun) {
|
|
41
|
+
logger.verbose(`DRY RUN: Would create symlink: ${targetPath} -> ${sourcePath}`);
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
// Create the symbolic link using relative path for better portability
|
|
45
|
+
const relativePath = path__default.relative(path__default.dirname(targetPath), sourcePath);
|
|
46
|
+
// Check if something already exists at the target path
|
|
47
|
+
try {
|
|
48
|
+
const stats = await fs__default.lstat(targetPath); // Use lstat to not follow symlinks
|
|
49
|
+
if (stats.isSymbolicLink()) {
|
|
50
|
+
// It's a symlink, check if it points to the correct target
|
|
51
|
+
const existingLink = await fs__default.readlink(targetPath);
|
|
52
|
+
if (existingLink === relativePath) {
|
|
53
|
+
logger.verbose(`Symlink already exists and points to correct target: ${targetPath} -> ${relativePath}`);
|
|
54
|
+
return true;
|
|
55
|
+
} else {
|
|
56
|
+
logger.info(`🔧 Fixing symlink: ${targetPath} (was pointing to ${existingLink}, now pointing to ${relativePath})`);
|
|
57
|
+
await fs__default.unlink(targetPath);
|
|
58
|
+
await fs__default.symlink(relativePath, targetPath, 'dir');
|
|
59
|
+
logger.info(`✅ Fixed symlink: ${targetPath} -> ${relativePath}`);
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
} else if (stats.isDirectory()) {
|
|
63
|
+
// It's a directory, remove it
|
|
64
|
+
logger.warn(`⚠️ Removing existing directory to create symlink: ${targetPath}`);
|
|
65
|
+
await fs__default.rm(targetPath, {
|
|
66
|
+
recursive: true,
|
|
67
|
+
force: true
|
|
68
|
+
});
|
|
69
|
+
await fs__default.symlink(relativePath, targetPath, 'dir');
|
|
70
|
+
logger.info(`✅ Created symlink: ${targetPath} -> ${relativePath}`);
|
|
71
|
+
return true;
|
|
72
|
+
} else {
|
|
73
|
+
// It's a file, remove it
|
|
74
|
+
logger.warn(`⚠️ Removing existing file to create symlink: ${targetPath}`);
|
|
75
|
+
await fs__default.unlink(targetPath);
|
|
76
|
+
await fs__default.symlink(relativePath, targetPath, 'dir');
|
|
77
|
+
logger.info(`✅ Created symlink: ${targetPath} -> ${relativePath}`);
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
} catch (error) {
|
|
81
|
+
if (error.code === 'ENOENT') {
|
|
82
|
+
// Nothing exists at target path, create the symlink
|
|
83
|
+
await fs__default.symlink(relativePath, targetPath, 'dir');
|
|
84
|
+
logger.verbose(`Created symlink: ${targetPath} -> ${relativePath}`);
|
|
85
|
+
return true;
|
|
86
|
+
} else {
|
|
87
|
+
throw error; // Re-throw unexpected errors
|
|
88
|
+
}
|
|
38
89
|
}
|
|
90
|
+
} catch (error) {
|
|
91
|
+
logger.warn(`Failed to create symlink for ${packageName}: ${error.message}`);
|
|
92
|
+
return false;
|
|
39
93
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
94
|
+
};
|
|
95
|
+
// Helper function to parse package names and scopes
|
|
96
|
+
const parsePackageArgument = (packageArg)=>{
|
|
97
|
+
if (packageArg.startsWith('@')) {
|
|
98
|
+
const parts = packageArg.split('/');
|
|
99
|
+
if (parts.length === 1) {
|
|
100
|
+
// Just a scope like "@fjell"
|
|
101
|
+
return {
|
|
102
|
+
scope: parts[0]
|
|
103
|
+
};
|
|
104
|
+
} else {
|
|
105
|
+
// Full package name like "@fjell/core"
|
|
106
|
+
return {
|
|
107
|
+
scope: parts[0],
|
|
108
|
+
packageName: packageArg
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
} else {
|
|
112
|
+
throw new Error(`Package argument must start with @ (scope): ${packageArg}`);
|
|
44
113
|
}
|
|
45
|
-
timer.end(`Found ${packagesToLink.size} packages to link`);
|
|
46
|
-
return packagesToLink;
|
|
47
114
|
};
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
115
|
+
// Find packages in the workspace that match the given scope or package name
|
|
116
|
+
const findMatchingPackages = async (targetDirectories, scope, storage, logger, packageName)=>{
|
|
117
|
+
const matchingPackages = [];
|
|
118
|
+
// Find all package.json files in target directories
|
|
119
|
+
let allPackageJsonFiles = [];
|
|
120
|
+
for (const targetDirectory of targetDirectories){
|
|
121
|
+
const packageJsonFiles = await findAllPackageJsonFiles(targetDirectory, storage);
|
|
122
|
+
allPackageJsonFiles = allPackageJsonFiles.concat(packageJsonFiles);
|
|
123
|
+
}
|
|
124
|
+
for (const packageJsonLocation of allPackageJsonFiles){
|
|
125
|
+
const packageDir = packageJsonLocation.path.replace('/package.json', '');
|
|
51
126
|
try {
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
127
|
+
const packageJsonContent = await storage.readFile(packageJsonLocation.path, 'utf-8');
|
|
128
|
+
const parsed = safeJsonParse(packageJsonContent, packageJsonLocation.path);
|
|
129
|
+
const packageJson = validatePackageJson(parsed, packageJsonLocation.path);
|
|
130
|
+
if (!packageJson.name) continue;
|
|
131
|
+
const isInScope = packageJson.name.startsWith(scope + '/');
|
|
132
|
+
const isExactMatch = packageName && packageJson.name === packageName;
|
|
133
|
+
if (isInScope || isExactMatch) {
|
|
134
|
+
matchingPackages.push({
|
|
135
|
+
name: packageJson.name,
|
|
136
|
+
path: packageDir,
|
|
137
|
+
isSource: packageName ? packageJson.name === packageName : isInScope
|
|
138
|
+
});
|
|
58
139
|
}
|
|
59
|
-
|
|
140
|
+
} catch (error) {
|
|
141
|
+
logger.warn(`Failed to parse ${packageJsonLocation.path}: ${error.message}`);
|
|
60
142
|
}
|
|
61
143
|
}
|
|
62
|
-
return
|
|
63
|
-
};
|
|
64
|
-
const writeLinkBackup = async (backup, storage)=>{
|
|
65
|
-
const backupPath = path__default.join(process.cwd(), '.kodrdriv-link-backup.json');
|
|
66
|
-
await storage.writeFile(backupPath, JSON.stringify(backup, null, 2), 'utf-8');
|
|
144
|
+
return matchingPackages;
|
|
67
145
|
};
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
logger.verbose(`Linked ${relativePath}/${depType}.${packageName}: ${backup[backupKey].originalVersion} -> file:${fileReferencePath}`);
|
|
146
|
+
// Find packages that depend on the target package
|
|
147
|
+
const findConsumingPackages = async (targetDirectories, targetPackageName, storage, logger)=>{
|
|
148
|
+
const consumingPackages = [];
|
|
149
|
+
// Find all package.json files in target directories
|
|
150
|
+
let allPackageJsonFiles = [];
|
|
151
|
+
for (const targetDirectory of targetDirectories){
|
|
152
|
+
const packageJsonFiles = await findAllPackageJsonFiles(targetDirectory, storage);
|
|
153
|
+
allPackageJsonFiles = allPackageJsonFiles.concat(packageJsonFiles);
|
|
154
|
+
}
|
|
155
|
+
for (const packageJsonLocation of allPackageJsonFiles){
|
|
156
|
+
const packageDir = packageJsonLocation.path.replace('/package.json', '');
|
|
157
|
+
try {
|
|
158
|
+
const packageJsonContent = await storage.readFile(packageJsonLocation.path, 'utf-8');
|
|
159
|
+
const parsed = safeJsonParse(packageJsonContent, packageJsonLocation.path);
|
|
160
|
+
const packageJson = validatePackageJson(parsed, packageJsonLocation.path);
|
|
161
|
+
if (!packageJson.name) continue;
|
|
162
|
+
// Check if this package depends on the target package
|
|
163
|
+
const dependencyTypes = [
|
|
164
|
+
'dependencies',
|
|
165
|
+
'devDependencies',
|
|
166
|
+
'peerDependencies',
|
|
167
|
+
'optionalDependencies'
|
|
168
|
+
];
|
|
169
|
+
const hasDependency = dependencyTypes.some((depType)=>packageJson[depType] && packageJson[depType][targetPackageName]);
|
|
170
|
+
if (hasDependency && packageJson.name !== targetPackageName) {
|
|
171
|
+
consumingPackages.push({
|
|
172
|
+
name: packageJson.name,
|
|
173
|
+
path: packageDir
|
|
174
|
+
});
|
|
98
175
|
}
|
|
176
|
+
} catch (error) {
|
|
177
|
+
logger.warn(`Failed to parse ${packageJsonLocation.path}: ${error.message}`);
|
|
99
178
|
}
|
|
100
179
|
}
|
|
101
|
-
|
|
102
|
-
return linkedCount;
|
|
180
|
+
return consumingPackages;
|
|
103
181
|
};
|
|
104
|
-
const executeInternal = async (runConfig)=>{
|
|
105
|
-
var _runConfig_link,
|
|
182
|
+
const executeInternal = async (runConfig, packageArgument)=>{
|
|
183
|
+
var _runConfig_link, _runConfig_tree;
|
|
106
184
|
const isDryRun = runConfig.dryRun || ((_runConfig_link = runConfig.link) === null || _runConfig_link === void 0 ? void 0 : _runConfig_link.dryRun) || false;
|
|
107
185
|
const logger = getDryRunLogger(isDryRun);
|
|
108
|
-
const overallTimer = PerformanceTimer.start(logger, 'Link command execution');
|
|
109
186
|
const storage = create({
|
|
110
187
|
log: logger.info
|
|
111
188
|
});
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
return 'No scope roots configured. Skipping link management.';
|
|
121
|
-
}
|
|
122
|
-
// Find all package.json files in current directory tree
|
|
123
|
-
const packageJsonFiles = await findAllPackageJsonFiles(process.cwd(), storage);
|
|
124
|
-
if (packageJsonFiles.length === 0) {
|
|
125
|
-
overallTimer.end('Link command (no package.json files)');
|
|
126
|
-
throw new ValidationError('No package.json files found in current directory or subdirectories.');
|
|
127
|
-
}
|
|
128
|
-
logger.info(`Found ${packageJsonFiles.length} package.json file(s) to process`);
|
|
129
|
-
logger.info(`Scanning ${Object.keys(scopeRoots).length} scope root(s): ${Object.keys(scopeRoots).join(', ')}`);
|
|
130
|
-
// Check if any package.json files already have file: dependencies (safety check)
|
|
131
|
-
const safetyTimer = PerformanceTimer.start(logger, 'Safety check for existing file: dependencies');
|
|
132
|
-
// checkForFileDependencies(packageJsonFiles); // This function is no longer imported
|
|
133
|
-
safetyTimer.end('Safety check completed');
|
|
134
|
-
// Collect all dependencies from all package.json files using optimized function
|
|
135
|
-
// const allDependencies = collectAllDependencies(packageJsonFiles); // This function is no longer imported
|
|
136
|
-
// logger.verbose(`Found ${Object.keys(allDependencies).length} total unique dependencies across all package.json files`);
|
|
137
|
-
// Find matching sibling packages
|
|
138
|
-
const packagesToLink = await findPackagesToLink(scopeRoots, storage);
|
|
139
|
-
if (packagesToLink.size === 0) {
|
|
140
|
-
logger.info('✅ No matching sibling packages found for linking.');
|
|
141
|
-
overallTimer.end('Link command (no packages to link)');
|
|
142
|
-
return 'No matching sibling packages found for linking.';
|
|
189
|
+
// Get target directories from config, default to current directory
|
|
190
|
+
const targetDirectories = ((_runConfig_tree = runConfig.tree) === null || _runConfig_tree === void 0 ? void 0 : _runConfig_tree.directories) || [
|
|
191
|
+
process.cwd()
|
|
192
|
+
];
|
|
193
|
+
if (targetDirectories.length === 1) {
|
|
194
|
+
logger.info(`Analyzing workspace at: ${targetDirectories[0]}`);
|
|
195
|
+
} else {
|
|
196
|
+
logger.info(`Analyzing workspaces at: ${targetDirectories.join(', ')}`);
|
|
143
197
|
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
198
|
+
// If no package argument provided, use new smart same-scope linking behavior
|
|
199
|
+
if (!packageArgument) {
|
|
200
|
+
logger.info('🔗 Smart linking current project...');
|
|
201
|
+
// Work in current directory only - read the package.json
|
|
202
|
+
const currentDir = process.cwd();
|
|
203
|
+
const packageJsonPath = `${currentDir}/package.json`;
|
|
204
|
+
let currentPackageJson;
|
|
205
|
+
try {
|
|
206
|
+
const packageJsonContent = await storage.readFile(packageJsonPath, 'utf-8');
|
|
207
|
+
const parsed = safeJsonParse(packageJsonContent, packageJsonPath);
|
|
208
|
+
currentPackageJson = validatePackageJson(parsed, packageJsonPath);
|
|
209
|
+
} catch (error) {
|
|
210
|
+
const message = `No valid package.json found in current directory: ${error.message}`;
|
|
211
|
+
logger.error(message);
|
|
212
|
+
return message;
|
|
155
213
|
}
|
|
156
|
-
|
|
157
|
-
|
|
214
|
+
if (!currentPackageJson.name) {
|
|
215
|
+
const message = 'package.json must have a name field';
|
|
216
|
+
logger.error(message);
|
|
217
|
+
return message;
|
|
158
218
|
}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
if (
|
|
170
|
-
|
|
171
|
-
|
|
219
|
+
// Extract the scope from the current package name
|
|
220
|
+
const currentScope = currentPackageJson.name.startsWith('@') ? currentPackageJson.name.split('/')[0] : null;
|
|
221
|
+
if (!currentScope) {
|
|
222
|
+
const message = 'Current package must have a scoped name (e.g., @scope/package) for smart linking';
|
|
223
|
+
logger.warn(message);
|
|
224
|
+
return message;
|
|
225
|
+
}
|
|
226
|
+
logger.info(`Current package: ${currentPackageJson.name} (scope: ${currentScope})`);
|
|
227
|
+
// Step 1: Link the current package globally
|
|
228
|
+
try {
|
|
229
|
+
if (isDryRun) {
|
|
230
|
+
logger.info(`DRY RUN: Would run 'npm link' in current directory`);
|
|
231
|
+
} else {
|
|
232
|
+
logger.verbose(`Running 'npm link' to register ${currentPackageJson.name} globally...`);
|
|
233
|
+
await run('npm link');
|
|
234
|
+
logger.info(`✅ Self-linked: ${currentPackageJson.name}`);
|
|
172
235
|
}
|
|
236
|
+
} catch (error) {
|
|
237
|
+
logger.error(`❌ Failed to self-link ${currentPackageJson.name}: ${error.message}`);
|
|
238
|
+
throw new Error(`Failed to self-link ${currentPackageJson.name}: ${error.message}`);
|
|
173
239
|
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
240
|
+
// Step 2: Find same-scope dependencies in current package
|
|
241
|
+
const allDependencies = {
|
|
242
|
+
...currentPackageJson.dependencies,
|
|
243
|
+
...currentPackageJson.devDependencies
|
|
244
|
+
};
|
|
245
|
+
const sameScopeDependencies = Object.keys(allDependencies).filter((depName)=>depName.startsWith(currentScope + '/'));
|
|
246
|
+
if (sameScopeDependencies.length === 0) {
|
|
247
|
+
logger.info(`No same-scope dependencies found for ${currentScope}`);
|
|
248
|
+
if (isDryRun) {
|
|
249
|
+
return `DRY RUN: Would self-link, no same-scope dependencies found to link`;
|
|
250
|
+
} else {
|
|
251
|
+
return `Self-linked ${currentPackageJson.name}, no same-scope dependencies to link`;
|
|
252
|
+
}
|
|
179
253
|
}
|
|
180
|
-
//
|
|
181
|
-
|
|
182
|
-
await writeLinkBackup(backup, storage);
|
|
183
|
-
saveTimer.end('Link backup saved');
|
|
184
|
-
logger.info(`Updated ${packageJsonFiles.length} package.json file(s) with file: dependencies`);
|
|
185
|
-
// Run optimized npm install to create symlinks
|
|
186
|
-
logger.info('⏳ Installing dependencies to create symlinks...');
|
|
254
|
+
// Step 3: Get globally linked packages directories (only if we have same-scope dependencies)
|
|
255
|
+
let globallyLinkedPackages = {};
|
|
187
256
|
try {
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
});
|
|
193
|
-
if (installResult.skipped) {
|
|
194
|
-
logger.info(`⚡ Dependencies were up to date (${installResult.method})`);
|
|
257
|
+
if (isDryRun) {
|
|
258
|
+
logger.info(`DRY RUN: Would run 'npm ls --link -g -p' to discover linked package directories`);
|
|
259
|
+
logger.info(`DRY RUN: Would attempt to link same-scope dependencies: ${sameScopeDependencies.join(', ')}`);
|
|
260
|
+
return `DRY RUN: Would self-link and attempt to link ${sameScopeDependencies.length} same-scope dependencies`;
|
|
195
261
|
} else {
|
|
196
|
-
logger.
|
|
262
|
+
logger.verbose(`Discovering globally linked package directories...`);
|
|
263
|
+
const result = await run('npm ls --link -g -p');
|
|
264
|
+
const resultStr = typeof result === 'string' ? result : result.stdout;
|
|
265
|
+
// Parse the directory paths - each line is a directory path
|
|
266
|
+
const directoryPaths = resultStr.trim().split('\n').filter((line)=>line.trim() !== '');
|
|
267
|
+
// Extract package names from directory paths and build a map
|
|
268
|
+
for (const dirPath of directoryPaths){
|
|
269
|
+
try {
|
|
270
|
+
// Read the package.json to get the actual package name
|
|
271
|
+
const packageJsonPath = `${dirPath.trim()}/package.json`;
|
|
272
|
+
const packageJsonContent = await storage.readFile(packageJsonPath, 'utf-8');
|
|
273
|
+
const parsed = safeJsonParse(packageJsonContent, packageJsonPath);
|
|
274
|
+
const packageJson = validatePackageJson(parsed, packageJsonPath);
|
|
275
|
+
if (packageJson.name) {
|
|
276
|
+
globallyLinkedPackages[packageJson.name] = dirPath.trim();
|
|
277
|
+
}
|
|
278
|
+
} catch (packageError) {
|
|
279
|
+
logger.verbose(`Could not read package.json from ${dirPath}: ${packageError.message}`);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
const linkedCount = Object.keys(globallyLinkedPackages).length;
|
|
283
|
+
logger.verbose(`Found ${linkedCount} globally linked package(s)`);
|
|
197
284
|
}
|
|
198
285
|
} catch (error) {
|
|
199
|
-
logger.warn(`Failed to
|
|
286
|
+
logger.warn(`Failed to get globally linked packages (continuing anyway): ${error.message}`);
|
|
287
|
+
globallyLinkedPackages = {};
|
|
288
|
+
}
|
|
289
|
+
logger.info(`Found ${sameScopeDependencies.length} same-scope dependencies: ${sameScopeDependencies.join(', ')}`);
|
|
290
|
+
// Step 4: Link same-scope dependencies that are available globally using manual symlinks
|
|
291
|
+
const linkedDependencies = [];
|
|
292
|
+
for (const depName of sameScopeDependencies){
|
|
293
|
+
const sourcePath = globallyLinkedPackages[depName];
|
|
294
|
+
if (sourcePath) {
|
|
295
|
+
try {
|
|
296
|
+
logger.verbose(`Linking same-scope dependency: ${depName} from ${sourcePath}`);
|
|
297
|
+
// Create the symbolic link manually using the directory path directly
|
|
298
|
+
const success = await createSymbolicLink(depName, sourcePath, currentDir, logger, isDryRun);
|
|
299
|
+
if (success) {
|
|
300
|
+
logger.info(`✅ Linked dependency: ${depName}`);
|
|
301
|
+
linkedDependencies.push(depName);
|
|
302
|
+
} else {
|
|
303
|
+
logger.warn(`⚠️ Failed to link ${depName}`);
|
|
304
|
+
}
|
|
305
|
+
} catch (error) {
|
|
306
|
+
logger.warn(`⚠️ Failed to link ${depName}: ${error.message}`);
|
|
307
|
+
}
|
|
308
|
+
} else {
|
|
309
|
+
logger.verbose(`Skipping ${depName} (not globally linked)`);
|
|
310
|
+
}
|
|
200
311
|
}
|
|
201
|
-
const summary = `
|
|
202
|
-
|
|
203
|
-
].map(([name, path])=>` - ${name}: file:${path}`).join('\n')}`;
|
|
204
|
-
overallTimer.end('Link command execution completed');
|
|
312
|
+
const summary = linkedDependencies.length > 0 ? `Self-linked ${currentPackageJson.name} and linked ${linkedDependencies.length} same-scope dependencies: ${linkedDependencies.join(', ')}` : `Self-linked ${currentPackageJson.name}, no same-scope dependencies were available to link`;
|
|
313
|
+
logger.info(summary);
|
|
205
314
|
return summary;
|
|
206
315
|
}
|
|
316
|
+
// New scope-based linking behavior
|
|
317
|
+
logger.info(`🔗 Linking scope/package: ${packageArgument}`);
|
|
318
|
+
const { scope, packageName } = parsePackageArgument(packageArgument);
|
|
319
|
+
logger.verbose(`Parsed scope: ${scope}, package: ${packageName || 'all packages in scope'}`);
|
|
320
|
+
// Find matching packages in the workspace
|
|
321
|
+
const matchingPackages = await findMatchingPackages(targetDirectories, scope, storage, logger, packageName);
|
|
322
|
+
if (matchingPackages.length === 0) {
|
|
323
|
+
const message = packageName ? `No package found matching: ${packageName}` : `No packages found in scope: ${scope}`;
|
|
324
|
+
logger.warn(message);
|
|
325
|
+
return message;
|
|
326
|
+
}
|
|
327
|
+
logger.info(`Found ${matchingPackages.length} matching package(s)`);
|
|
328
|
+
const linkedPackages = [];
|
|
329
|
+
// If specific package name provided, use that; otherwise link all packages in scope
|
|
330
|
+
const packagesToLink = packageName ? matchingPackages.filter((pkg)=>pkg.name === packageName) : matchingPackages;
|
|
331
|
+
for (const pkg of packagesToLink){
|
|
332
|
+
logger.info(`Processing package: ${pkg.name}`);
|
|
333
|
+
// Step A: Run 'npm link' in the source package directory
|
|
334
|
+
try {
|
|
335
|
+
const originalCwd = process.cwd();
|
|
336
|
+
process.chdir(pkg.path);
|
|
337
|
+
try {
|
|
338
|
+
if (isDryRun) {
|
|
339
|
+
logger.info(`DRY RUN: Would run 'npm link' in: ${pkg.path}`);
|
|
340
|
+
} else {
|
|
341
|
+
logger.verbose(`Running 'npm link' in source: ${pkg.path}`);
|
|
342
|
+
await run('npm link');
|
|
343
|
+
logger.info(`✅ Source linked: ${pkg.name}`);
|
|
344
|
+
}
|
|
345
|
+
} finally{
|
|
346
|
+
process.chdir(originalCwd);
|
|
347
|
+
}
|
|
348
|
+
// Step B: Find all packages that depend on this package and link them
|
|
349
|
+
const consumingPackages = await findConsumingPackages(targetDirectories, pkg.name, storage, logger);
|
|
350
|
+
if (consumingPackages.length === 0) {
|
|
351
|
+
logger.info(`No consuming packages found for: ${pkg.name}`);
|
|
352
|
+
} else {
|
|
353
|
+
logger.info(`Found ${consumingPackages.length} consuming package(s) for: ${pkg.name}`);
|
|
354
|
+
for (const consumer of consumingPackages){
|
|
355
|
+
try {
|
|
356
|
+
const consumerOriginalCwd = process.cwd();
|
|
357
|
+
process.chdir(consumer.path);
|
|
358
|
+
try {
|
|
359
|
+
if (isDryRun) {
|
|
360
|
+
logger.info(`DRY RUN: Would run 'npm link ${pkg.name}' in: ${consumer.path}`);
|
|
361
|
+
} else {
|
|
362
|
+
logger.verbose(`Running 'npm link ${pkg.name}' in consumer: ${consumer.path}`);
|
|
363
|
+
await runSecure('npm', [
|
|
364
|
+
'link',
|
|
365
|
+
pkg.name
|
|
366
|
+
]);
|
|
367
|
+
logger.info(`✅ Consumer linked: ${consumer.name} -> ${pkg.name}`);
|
|
368
|
+
}
|
|
369
|
+
} finally{
|
|
370
|
+
process.chdir(consumerOriginalCwd);
|
|
371
|
+
}
|
|
372
|
+
} catch (error) {
|
|
373
|
+
logger.error(`❌ Failed to link ${pkg.name} in ${consumer.name}: ${error.message}`);
|
|
374
|
+
throw new Error(`Failed to link ${pkg.name} in consumer ${consumer.name}: ${error.message}`);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
linkedPackages.push(pkg.name);
|
|
379
|
+
} catch (error) {
|
|
380
|
+
logger.error(`❌ Failed to link source package ${pkg.name}: ${error.message}`);
|
|
381
|
+
throw new Error(`Failed to link source package ${pkg.name}: ${error.message}`);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
const summary = `Successfully linked ${linkedPackages.length} package(s): ${linkedPackages.join(', ')}`;
|
|
385
|
+
logger.info(summary);
|
|
386
|
+
return summary;
|
|
207
387
|
};
|
|
208
|
-
const execute = async (runConfig)=>{
|
|
388
|
+
const execute = async (runConfig, packageArgument)=>{
|
|
209
389
|
try {
|
|
210
|
-
|
|
390
|
+
var _runConfig_link;
|
|
391
|
+
// Use packageArgument from runConfig if not provided as parameter
|
|
392
|
+
const finalPackageArgument = packageArgument || ((_runConfig_link = runConfig.link) === null || _runConfig_link === void 0 ? void 0 : _runConfig_link.packageArgument);
|
|
393
|
+
return await executeInternal(runConfig, finalPackageArgument);
|
|
211
394
|
} catch (error) {
|
|
212
395
|
const logger = getLogger();
|
|
213
|
-
|
|
214
|
-
logger.error(`link failed: ${error.message}`);
|
|
215
|
-
if (error.cause) {
|
|
216
|
-
logger.debug(`Caused by: ${error.cause.message}`);
|
|
217
|
-
}
|
|
218
|
-
throw error;
|
|
219
|
-
}
|
|
220
|
-
// Unexpected errors
|
|
221
|
-
logger.error(`link encountered unexpected error: ${error.message}`);
|
|
396
|
+
logger.error(`link failed: ${error.message}`);
|
|
222
397
|
throw error;
|
|
223
398
|
}
|
|
224
399
|
};
|