@eldrforge/kodrdriv 0.0.33 → 0.0.38
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 +46 -69
- package/dist/application.js +146 -0
- package/dist/application.js.map +1 -0
- package/dist/arguments.js +22 -21
- package/dist/arguments.js.map +1 -1
- package/dist/commands/audio-commit.js +43 -21
- package/dist/commands/audio-commit.js.map +1 -1
- package/dist/commands/audio-review.js +46 -38
- package/dist/commands/audio-review.js.map +1 -1
- package/dist/commands/clean.js +28 -12
- package/dist/commands/clean.js.map +1 -1
- package/dist/commands/commit.js +132 -39
- package/dist/commands/commit.js.map +1 -1
- package/dist/commands/link.js +177 -159
- package/dist/commands/link.js.map +1 -1
- package/dist/commands/publish-tree.js +19 -6
- package/dist/commands/publish-tree.js.map +1 -1
- package/dist/commands/publish.js +152 -82
- package/dist/commands/publish.js.map +1 -1
- package/dist/commands/release.js +21 -16
- package/dist/commands/release.js.map +1 -1
- package/dist/commands/review.js +286 -60
- package/dist/commands/review.js.map +1 -1
- package/dist/commands/select-audio.js +25 -8
- package/dist/commands/select-audio.js.map +1 -1
- package/dist/commands/unlink.js +349 -159
- package/dist/commands/unlink.js.map +1 -1
- package/dist/constants.js +14 -5
- package/dist/constants.js.map +1 -1
- package/dist/content/diff.js +7 -5
- package/dist/content/diff.js.map +1 -1
- package/dist/content/log.js +4 -1
- package/dist/content/log.js.map +1 -1
- package/dist/error/CancellationError.js +9 -0
- package/dist/error/CancellationError.js.map +1 -0
- package/dist/error/CommandErrors.js +120 -0
- package/dist/error/CommandErrors.js.map +1 -0
- package/dist/logging.js +55 -12
- package/dist/logging.js.map +1 -1
- package/dist/main.js +6 -131
- package/dist/main.js.map +1 -1
- package/dist/prompt/commit.js +4 -0
- package/dist/prompt/commit.js.map +1 -1
- package/dist/prompt/instructions/commit.md +33 -24
- package/dist/prompt/instructions/release.md +39 -5
- package/dist/prompt/release.js +41 -1
- package/dist/prompt/release.js.map +1 -1
- package/dist/types.js +9 -2
- package/dist/types.js.map +1 -1
- package/dist/util/github.js +71 -4
- package/dist/util/github.js.map +1 -1
- package/dist/util/npmOptimizations.js +174 -0
- package/dist/util/npmOptimizations.js.map +1 -0
- package/dist/util/openai.js +4 -2
- package/dist/util/openai.js.map +1 -1
- package/dist/util/performance.js +202 -0
- package/dist/util/performance.js.map +1 -0
- package/dist/util/safety.js +166 -0
- package/dist/util/safety.js.map +1 -0
- package/dist/util/storage.js +10 -0
- package/dist/util/storage.js.map +1 -1
- package/dist/util/validation.js +81 -0
- package/dist/util/validation.js.map +1 -0
- package/package.json +19 -18
- package/packages/components/package.json +4 -0
- package/packages/tools/package.json +4 -0
- package/packages/utils/package.json +4 -0
- package/scripts/pre-commit-hook.sh +52 -0
- package/test-project/package.json +1 -0
package/dist/commands/unlink.js
CHANGED
|
@@ -1,205 +1,395 @@
|
|
|
1
1
|
import path from 'path';
|
|
2
|
-
import
|
|
3
|
-
import { getLogger } from '../logging.js';
|
|
2
|
+
import { getDryRunLogger, getLogger } from '../logging.js';
|
|
4
3
|
import { create } from '../util/storage.js';
|
|
5
|
-
import {
|
|
4
|
+
import { safeJsonParse, validateLinkBackup } from '../util/validation.js';
|
|
5
|
+
import { PerformanceTimer, findAllPackageJsonFiles, scanDirectoryForPackages } from '../util/performance.js';
|
|
6
|
+
import { smartNpmInstall } from '../util/npmOptimizations.js';
|
|
6
7
|
|
|
7
|
-
const scanDirectoryForPackages = async (rootDir, storage)=>{
|
|
8
|
-
const logger = getLogger();
|
|
9
|
-
const packageMap = new Map(); // packageName -> relativePath
|
|
10
|
-
const absoluteRootDir = path.resolve(process.cwd(), rootDir);
|
|
11
|
-
logger.verbose(`Scanning directory for packages: ${absoluteRootDir}`);
|
|
12
|
-
try {
|
|
13
|
-
// Use single stat call to check if directory exists and is directory
|
|
14
|
-
const rootStat = await storage.exists(absoluteRootDir);
|
|
15
|
-
if (!rootStat) {
|
|
16
|
-
logger.verbose(`Root directory does not exist: ${absoluteRootDir}`);
|
|
17
|
-
return packageMap;
|
|
18
|
-
}
|
|
19
|
-
if (!await storage.isDirectory(absoluteRootDir)) {
|
|
20
|
-
logger.verbose(`Root path is not a directory: ${absoluteRootDir}`);
|
|
21
|
-
return packageMap;
|
|
22
|
-
}
|
|
23
|
-
// Get all items in the root directory
|
|
24
|
-
const items = await storage.listFiles(absoluteRootDir);
|
|
25
|
-
// Process directories in batches to avoid overwhelming the filesystem
|
|
26
|
-
const directories = [];
|
|
27
|
-
for (const item of items){
|
|
28
|
-
const itemPath = path.join(absoluteRootDir, item);
|
|
29
|
-
try {
|
|
30
|
-
// Quick check if it's a directory without logging
|
|
31
|
-
if (await storage.isDirectory(itemPath)) {
|
|
32
|
-
directories.push({
|
|
33
|
-
item,
|
|
34
|
-
itemPath
|
|
35
|
-
});
|
|
36
|
-
}
|
|
37
|
-
} catch (error) {
|
|
38
|
-
continue;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
logger.verbose(`Found ${directories.length} subdirectories to check for packages`);
|
|
42
|
-
// Check each directory for package.json
|
|
43
|
-
for (const { item, itemPath } of directories){
|
|
44
|
-
const packageJsonPath = path.join(itemPath, 'package.json');
|
|
45
|
-
try {
|
|
46
|
-
if (await storage.exists(packageJsonPath)) {
|
|
47
|
-
const packageJsonContent = await storage.readFile(packageJsonPath, 'utf-8');
|
|
48
|
-
const packageJson = JSON.parse(packageJsonContent);
|
|
49
|
-
if (packageJson.name) {
|
|
50
|
-
const relativePath = path.relative(process.cwd(), itemPath);
|
|
51
|
-
packageMap.set(packageJson.name, relativePath);
|
|
52
|
-
logger.debug(`Found package: ${packageJson.name} at ${relativePath}`);
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
} catch (error) {
|
|
56
|
-
// Skip directories with unreadable or invalid package.json
|
|
57
|
-
logger.debug(`Skipped ${packageJsonPath}: ${error.message || error}`);
|
|
58
|
-
continue;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
} catch (error) {
|
|
62
|
-
logger.warn(`Failed to read directory ${absoluteRootDir}: ${error}`);
|
|
63
|
-
}
|
|
64
|
-
return packageMap;
|
|
65
|
-
};
|
|
66
8
|
const findPackagesToUnlink = async (scopeRoots, storage)=>{
|
|
67
9
|
const logger = getLogger();
|
|
10
|
+
const timer = PerformanceTimer.start(logger, 'Finding packages to unlink');
|
|
68
11
|
const packagesToUnlink = [];
|
|
69
12
|
logger.silly(`Finding packages to unlink from scope roots: ${JSON.stringify(scopeRoots)}`);
|
|
70
13
|
// Scan all scope roots to build a comprehensive map of packages that should be unlinked
|
|
14
|
+
const scopeTimer = PerformanceTimer.start(logger, 'Scanning all scope roots for packages to unlink');
|
|
71
15
|
const allScopePackages = new Map(); // packageName -> relativePath
|
|
72
|
-
|
|
16
|
+
// Process all scopes in parallel for better performance
|
|
17
|
+
const scopePromises = Object.entries(scopeRoots).map(async ([scope, rootDir])=>{
|
|
73
18
|
logger.verbose(`Scanning scope ${scope} at root directory: ${rootDir}`);
|
|
74
19
|
const scopePackages = await scanDirectoryForPackages(rootDir, storage);
|
|
75
20
|
// Add packages from this scope to the overall map
|
|
21
|
+
const scopeResults = [];
|
|
76
22
|
for (const [packageName, packagePath] of scopePackages){
|
|
77
23
|
if (packageName.startsWith(scope)) {
|
|
78
|
-
|
|
79
|
-
|
|
24
|
+
scopeResults.push([
|
|
25
|
+
packageName,
|
|
26
|
+
packagePath
|
|
27
|
+
]);
|
|
80
28
|
logger.debug(`Package to unlink: ${packageName} -> ${packagePath}`);
|
|
81
29
|
}
|
|
82
30
|
}
|
|
31
|
+
return scopeResults;
|
|
32
|
+
});
|
|
33
|
+
const allScopeResults = await Promise.all(scopePromises);
|
|
34
|
+
// Flatten results and collect package names
|
|
35
|
+
for (const scopeResults of allScopeResults){
|
|
36
|
+
for (const [packageName, packagePath] of scopeResults){
|
|
37
|
+
allScopePackages.set(packageName, packagePath);
|
|
38
|
+
packagesToUnlink.push(packageName);
|
|
39
|
+
}
|
|
83
40
|
}
|
|
41
|
+
scopeTimer.end(`Scanned ${Object.keys(scopeRoots).length} scope roots, found ${packagesToUnlink.length} packages to unlink`);
|
|
42
|
+
timer.end(`Found ${packagesToUnlink.length} packages to unlink`);
|
|
84
43
|
return packagesToUnlink;
|
|
85
44
|
};
|
|
86
|
-
const
|
|
87
|
-
|
|
45
|
+
const readLinkBackup = async (storage)=>{
|
|
46
|
+
const backupPath = path.join(process.cwd(), '.kodrdriv-link-backup.json');
|
|
47
|
+
if (await storage.exists(backupPath)) {
|
|
88
48
|
try {
|
|
89
|
-
const content = await storage.readFile(
|
|
90
|
-
|
|
49
|
+
const content = await storage.readFile(backupPath, 'utf-8');
|
|
50
|
+
const parsed = safeJsonParse(content, 'link backup file');
|
|
51
|
+
return validateLinkBackup(parsed);
|
|
91
52
|
} catch (error) {
|
|
92
|
-
throw new Error(`Failed to parse
|
|
53
|
+
throw new Error(`Failed to parse link backup file: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
93
54
|
}
|
|
94
55
|
}
|
|
95
56
|
return {};
|
|
96
57
|
};
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
yamlContent = yamlContent.replace(/: '(\d+(?:\.\d+)*)'/g, ': $1');
|
|
108
|
-
await storage.writeFile(workspaceFilePath, yamlContent, 'utf-8');
|
|
58
|
+
const writeLinkBackup = async (backup, storage)=>{
|
|
59
|
+
const backupPath = path.join(process.cwd(), '.kodrdriv-link-backup.json');
|
|
60
|
+
if (Object.keys(backup).length === 0) {
|
|
61
|
+
// Remove backup file if empty
|
|
62
|
+
if (await storage.exists(backupPath)) {
|
|
63
|
+
await storage.deleteFile(backupPath);
|
|
64
|
+
}
|
|
65
|
+
} else {
|
|
66
|
+
await storage.writeFile(backupPath, JSON.stringify(backup, null, 2), 'utf-8');
|
|
67
|
+
}
|
|
109
68
|
};
|
|
110
|
-
const
|
|
111
|
-
|
|
69
|
+
const restorePackageJson = async (packageJsonLocation, packagesToUnlink, backup, storage)=>{
|
|
70
|
+
const logger = getLogger();
|
|
71
|
+
let restoredCount = 0;
|
|
72
|
+
const { packageJson, path: packageJsonPath, relativePath } = packageJsonLocation;
|
|
73
|
+
// Restore original versions from backup
|
|
74
|
+
for (const packageName of packagesToUnlink){
|
|
75
|
+
var _currentDeps_packageName;
|
|
76
|
+
const backupKey = `${relativePath}:${packageName}`;
|
|
77
|
+
const backupEntry = backup[backupKey];
|
|
78
|
+
if (!backupEntry) {
|
|
79
|
+
logger.debug(`No backup found for ${backupKey}, skipping`);
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
const currentDeps = packageJson[backupEntry.dependencyType];
|
|
83
|
+
if (currentDeps && ((_currentDeps_packageName = currentDeps[packageName]) === null || _currentDeps_packageName === void 0 ? void 0 : _currentDeps_packageName.startsWith('file:'))) {
|
|
84
|
+
// Restore the original version
|
|
85
|
+
currentDeps[packageName] = backupEntry.originalVersion;
|
|
86
|
+
restoredCount++;
|
|
87
|
+
logger.verbose(`Restored ${relativePath}/${backupEntry.dependencyType}.${packageName}: file:... -> ${backupEntry.originalVersion}`);
|
|
88
|
+
// Remove from backup
|
|
89
|
+
delete backup[backupKey];
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
// NOTE: Don't write the file here - let the caller handle all modifications
|
|
93
|
+
return restoredCount;
|
|
94
|
+
};
|
|
95
|
+
/**
|
|
96
|
+
* Comprehensive scan for all types of problematic dependencies that could cause GitHub build failures
|
|
97
|
+
*/ const scanForProblematicDependencies = (packageJsonFiles)=>{
|
|
98
|
+
const logger = getLogger();
|
|
99
|
+
const timer = PerformanceTimer.start(logger, 'Scanning for problematic dependencies');
|
|
100
|
+
const problematicDeps = [];
|
|
101
|
+
for (const { path: packagePath, packageJson, relativePath } of packageJsonFiles){
|
|
102
|
+
const extendedPackageJson = packageJson;
|
|
103
|
+
// Check dependencies, devDependencies, peerDependencies
|
|
104
|
+
const depTypes = [
|
|
105
|
+
'dependencies',
|
|
106
|
+
'devDependencies',
|
|
107
|
+
'peerDependencies'
|
|
108
|
+
];
|
|
109
|
+
for (const depType of depTypes){
|
|
110
|
+
const deps = extendedPackageJson[depType];
|
|
111
|
+
if (!deps) continue;
|
|
112
|
+
for (const [name, version] of Object.entries(deps)){
|
|
113
|
+
let problemType = null;
|
|
114
|
+
let reason = '';
|
|
115
|
+
// Check for file: dependencies
|
|
116
|
+
if (version.startsWith('file:')) {
|
|
117
|
+
problemType = 'file:';
|
|
118
|
+
reason = 'File dependencies cause build failures in CI/CD environments';
|
|
119
|
+
} else if (version.startsWith('link:')) {
|
|
120
|
+
problemType = 'link:';
|
|
121
|
+
reason = 'Link dependencies are not resolvable in remote environments';
|
|
122
|
+
} else if (version.includes('../') || version.includes('./') || version.startsWith('/')) {
|
|
123
|
+
problemType = 'relative-path';
|
|
124
|
+
reason = 'Relative path dependencies are not resolvable in different environments';
|
|
125
|
+
} else if (version.startsWith('workspace:')) {
|
|
126
|
+
problemType = 'workspace';
|
|
127
|
+
reason = 'Workspace protocol dependencies require workspace configuration';
|
|
128
|
+
}
|
|
129
|
+
if (problemType) {
|
|
130
|
+
problematicDeps.push({
|
|
131
|
+
name,
|
|
132
|
+
version,
|
|
133
|
+
type: problemType,
|
|
134
|
+
dependencyType: depType,
|
|
135
|
+
packagePath: relativePath,
|
|
136
|
+
reason
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
// Check workspace configurations
|
|
142
|
+
if (extendedPackageJson.workspaces) {
|
|
143
|
+
problematicDeps.push({
|
|
144
|
+
name: 'workspaces',
|
|
145
|
+
version: JSON.stringify(extendedPackageJson.workspaces),
|
|
146
|
+
type: 'workspace',
|
|
147
|
+
dependencyType: 'workspaces',
|
|
148
|
+
packagePath: relativePath,
|
|
149
|
+
reason: 'Workspace configurations can cause issues when published to npm'
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
// Check overrides (npm 8.3+)
|
|
153
|
+
if (extendedPackageJson.overrides) {
|
|
154
|
+
for (const [name, override] of Object.entries(extendedPackageJson.overrides)){
|
|
155
|
+
if (typeof override === 'string' && (override.startsWith('file:') || override.startsWith('link:') || override.includes('../'))) {
|
|
156
|
+
problematicDeps.push({
|
|
157
|
+
name,
|
|
158
|
+
version: override,
|
|
159
|
+
type: 'override',
|
|
160
|
+
dependencyType: 'overrides',
|
|
161
|
+
packagePath: relativePath,
|
|
162
|
+
reason: 'Override configurations with local paths cause build failures'
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
// Check resolutions (Yarn)
|
|
168
|
+
if (extendedPackageJson.resolutions) {
|
|
169
|
+
for (const [name, resolution] of Object.entries(extendedPackageJson.resolutions)){
|
|
170
|
+
if (typeof resolution === 'string' && (resolution.startsWith('file:') || resolution.startsWith('link:') || resolution.includes('../'))) {
|
|
171
|
+
problematicDeps.push({
|
|
172
|
+
name,
|
|
173
|
+
version: resolution,
|
|
174
|
+
type: 'resolution',
|
|
175
|
+
dependencyType: 'resolutions',
|
|
176
|
+
packagePath: relativePath,
|
|
177
|
+
reason: 'Resolution configurations with local paths cause build failures'
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
timer.end(`Found ${problematicDeps.length} problematic dependencies`);
|
|
184
|
+
return problematicDeps;
|
|
185
|
+
};
|
|
186
|
+
/**
|
|
187
|
+
* Enhanced function to display problematic dependencies with detailed information
|
|
188
|
+
*/ const displayProblematicDependencies = (problematicDeps)=>{
|
|
189
|
+
const logger = getLogger();
|
|
190
|
+
if (problematicDeps.length === 0) {
|
|
191
|
+
logger.info('✅ No problematic dependencies found');
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
logger.info('🔓 Found problematic dependencies that could cause GitHub build failures:');
|
|
195
|
+
// Group by package path for better readability
|
|
196
|
+
const grouped = problematicDeps.reduce((acc, dep)=>{
|
|
197
|
+
if (!acc[dep.packagePath]) {
|
|
198
|
+
acc[dep.packagePath] = [];
|
|
199
|
+
}
|
|
200
|
+
acc[dep.packagePath].push(dep);
|
|
201
|
+
return acc;
|
|
202
|
+
}, {});
|
|
203
|
+
for (const [packagePath, deps] of Object.entries(grouped)){
|
|
204
|
+
logger.info(` 📄 ${packagePath}:`);
|
|
205
|
+
for (const dep of deps){
|
|
206
|
+
logger.info(` ❌ ${dep.dependencyType}.${dep.name}: ${dep.version} (${dep.type})`);
|
|
207
|
+
logger.info(` 💡 ${dep.reason}`);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
/**
|
|
212
|
+
* Verification step to ensure no problematic dependencies remain after cleanup
|
|
213
|
+
*/ const verifyCleanup = async (packageJsonFiles)=>{
|
|
112
214
|
const logger = getLogger();
|
|
215
|
+
const timer = PerformanceTimer.start(logger, 'Verifying cleanup completion');
|
|
216
|
+
const remainingProblems = scanForProblematicDependencies(packageJsonFiles);
|
|
217
|
+
if (remainingProblems.length === 0) {
|
|
218
|
+
logger.info('✅ Verification passed: No problematic dependencies remain');
|
|
219
|
+
timer.end('Verification successful');
|
|
220
|
+
return true;
|
|
221
|
+
} else {
|
|
222
|
+
logger.warn('⚠️ Verification failed: Found remaining problematic dependencies');
|
|
223
|
+
displayProblematicDependencies(remainingProblems);
|
|
224
|
+
timer.end('Verification failed');
|
|
225
|
+
return false;
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
const execute = async (runConfig)=>{
|
|
229
|
+
var _runConfig_unlink, _runConfig_unlink1, _runConfig_link, _runConfig_unlink2;
|
|
230
|
+
const isDryRun = runConfig.dryRun || ((_runConfig_unlink = runConfig.unlink) === null || _runConfig_unlink === void 0 ? void 0 : _runConfig_unlink.dryRun) || false;
|
|
231
|
+
const logger = getDryRunLogger(isDryRun);
|
|
232
|
+
const overallTimer = PerformanceTimer.start(logger, 'Unlink command execution');
|
|
113
233
|
const storage = create({
|
|
114
234
|
log: logger.info
|
|
115
235
|
});
|
|
116
|
-
logger.info('🔓 Unlinking workspace packages...');
|
|
117
|
-
// Read current package.json
|
|
118
|
-
const packageJsonPath = path.join(process.cwd(), 'package.json');
|
|
119
|
-
if (!await storage.exists(packageJsonPath)) {
|
|
120
|
-
throw new Error('package.json not found in current directory.');
|
|
121
|
-
}
|
|
122
|
-
let packageJson;
|
|
123
|
-
try {
|
|
124
|
-
const packageJsonContent = await storage.readFile(packageJsonPath, 'utf-8');
|
|
125
|
-
packageJson = JSON.parse(packageJsonContent);
|
|
126
|
-
} catch (error) {
|
|
127
|
-
throw new Error(`Failed to parse package.json: ${error}`);
|
|
128
|
-
}
|
|
236
|
+
logger.info('🔓 Unlinking workspace packages and cleaning up problematic dependencies...');
|
|
129
237
|
// Get configuration
|
|
130
|
-
const
|
|
131
|
-
const
|
|
132
|
-
|
|
238
|
+
const configTimer = PerformanceTimer.start(logger, 'Reading configuration');
|
|
239
|
+
const scopeRoots = ((_runConfig_unlink1 = runConfig.unlink) === null || _runConfig_unlink1 === void 0 ? void 0 : _runConfig_unlink1.scopeRoots) || ((_runConfig_link = runConfig.link) === null || _runConfig_link === void 0 ? void 0 : _runConfig_link.scopeRoots) || {};
|
|
240
|
+
((_runConfig_unlink2 = runConfig.unlink) === null || _runConfig_unlink2 === void 0 ? void 0 : _runConfig_unlink2.workspaceFile) || 'pnpm-workspace.yaml';
|
|
241
|
+
configTimer.end('Configuration loaded');
|
|
133
242
|
if (Object.keys(scopeRoots).length === 0) {
|
|
134
|
-
logger.info('No scope roots configured. Skipping
|
|
135
|
-
|
|
243
|
+
logger.info('No scope roots configured. Skipping link management.');
|
|
244
|
+
overallTimer.end('Unlink command (no scope roots)');
|
|
245
|
+
return 'No scope roots configured. Skipping link management.';
|
|
136
246
|
}
|
|
247
|
+
// Find all package.json files in current directory tree
|
|
248
|
+
const packageJsonFiles = await findAllPackageJsonFiles(process.cwd(), storage);
|
|
249
|
+
if (packageJsonFiles.length === 0) {
|
|
250
|
+
throw new Error('No package.json files found in current directory or subdirectories.');
|
|
251
|
+
}
|
|
252
|
+
logger.info(`Found ${packageJsonFiles.length} package.json file(s) to process`);
|
|
137
253
|
logger.info(`Scanning ${Object.keys(scopeRoots).length} scope root(s): ${Object.keys(scopeRoots).join(', ')}`);
|
|
254
|
+
// Comprehensive scan for all problematic dependencies
|
|
255
|
+
const problematicDeps = scanForProblematicDependencies(packageJsonFiles);
|
|
256
|
+
displayProblematicDependencies(problematicDeps);
|
|
138
257
|
// Find packages to unlink based on scope roots
|
|
139
|
-
const
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
logger.info('✅ No packages found matching scope roots for unlinking.');
|
|
145
|
-
return 'No packages found matching scope roots for unlinking.';
|
|
146
|
-
}
|
|
147
|
-
logger.verbose(`Found ${packagesToUnlinkPaths.length} packages that could be unlinked: ${packagesToUnlinkPaths.join(', ')}`);
|
|
148
|
-
// Read existing workspace configuration
|
|
149
|
-
const workspaceFilePath = path.join(process.cwd(), workspaceFileName);
|
|
150
|
-
const workspaceConfig = await readCurrentWorkspaceFile(workspaceFilePath, storage);
|
|
151
|
-
if (!workspaceConfig.overrides || Object.keys(workspaceConfig.overrides).length === 0) {
|
|
152
|
-
logger.info('✅ No overrides found in workspace file. Nothing to do.');
|
|
153
|
-
return 'No overrides found in workspace file. Nothing to do.';
|
|
258
|
+
const packagesToUnlinkNames = await findPackagesToUnlink(scopeRoots, storage);
|
|
259
|
+
if (packagesToUnlinkNames.length === 0 && problematicDeps.length === 0) {
|
|
260
|
+
logger.info('✅ No packages found matching scope roots for unlinking and no problematic dependencies detected.');
|
|
261
|
+
overallTimer.end('Unlink command (nothing to clean)');
|
|
262
|
+
return 'No packages found matching scope roots for unlinking and no problematic dependencies detected.';
|
|
154
263
|
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
const
|
|
158
|
-
const
|
|
159
|
-
|
|
160
|
-
for (const [pkgName, pkgLink] of Object.entries(existingOverrides)){
|
|
161
|
-
if (packagesToUnlinkSet.has(pkgLink)) {
|
|
162
|
-
actuallyRemovedPackages.push(pkgName);
|
|
163
|
-
} else {
|
|
164
|
-
remainingOverrides[pkgName] = pkgLink;
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
if (actuallyRemovedPackages.length === 0) {
|
|
168
|
-
logger.info('✅ No linked packages found in workspace file that match scope roots.');
|
|
169
|
-
return 'No linked packages found in workspace file that match scope roots.';
|
|
170
|
-
}
|
|
171
|
-
logger.info(`Found ${actuallyRemovedPackages.length} package(s) to unlink: ${actuallyRemovedPackages.join(', ')}`);
|
|
172
|
-
const updatedConfig = {
|
|
173
|
-
...workspaceConfig,
|
|
174
|
-
overrides: remainingOverrides
|
|
175
|
-
};
|
|
176
|
-
if (Object.keys(remainingOverrides).length === 0) {
|
|
177
|
-
delete updatedConfig.overrides;
|
|
178
|
-
}
|
|
179
|
-
// Write the updated workspace file
|
|
264
|
+
logger.verbose(`Found ${packagesToUnlinkNames.length} packages that could be unlinked: ${packagesToUnlinkNames.join(', ')}`);
|
|
265
|
+
// Read existing backup
|
|
266
|
+
const backupTimer = PerformanceTimer.start(logger, 'Reading link backup');
|
|
267
|
+
const backup = await readLinkBackup(storage);
|
|
268
|
+
backupTimer.end('Link backup loaded');
|
|
180
269
|
if (isDryRun) {
|
|
181
|
-
logger.info('
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
270
|
+
logger.info('Would clean up problematic dependencies and restore original package.json dependencies');
|
|
271
|
+
// Show what would be cleaned up
|
|
272
|
+
let dryRunCount = 0;
|
|
273
|
+
for (const packageName of packagesToUnlinkNames){
|
|
274
|
+
for (const { relativePath } of packageJsonFiles){
|
|
275
|
+
const backupKey = `${relativePath}:${packageName}`;
|
|
276
|
+
const backupEntry = backup[backupKey];
|
|
277
|
+
if (backupEntry) {
|
|
278
|
+
logger.verbose(`Would restore ${relativePath}/${packageName}: file:... -> ${backupEntry.originalVersion}`);
|
|
279
|
+
dryRunCount++;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
// Show what problematic dependencies would be cleaned
|
|
284
|
+
if (problematicDeps.length > 0) {
|
|
285
|
+
logger.verbose(`Would clean up ${problematicDeps.length} problematic dependencies`);
|
|
286
|
+
}
|
|
287
|
+
overallTimer.end('Unlink command (dry run)');
|
|
288
|
+
return `DRY RUN: Would unlink ${dryRunCount} dependency reference(s) and clean up ${problematicDeps.length} problematic dependencies across ${packageJsonFiles.length} package.json files`;
|
|
187
289
|
} else {
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
290
|
+
// Restore package.json files with original versions and clean up problematic dependencies
|
|
291
|
+
let totalRestoredCount = 0;
|
|
292
|
+
let totalCleanedCount = 0;
|
|
293
|
+
for (const packageJsonLocation of packageJsonFiles){
|
|
294
|
+
const { packageJson, path: packageJsonPath, relativePath } = packageJsonLocation;
|
|
295
|
+
let modified = false;
|
|
296
|
+
// Restore from backup
|
|
297
|
+
const restoredCount = await restorePackageJson(packageJsonLocation, packagesToUnlinkNames, backup);
|
|
298
|
+
totalRestoredCount += restoredCount;
|
|
299
|
+
if (restoredCount > 0) modified = true;
|
|
300
|
+
// Clean up problematic dependencies for this specific package
|
|
301
|
+
const extendedPackageJson = packageJson;
|
|
302
|
+
// Remove workspace configurations
|
|
303
|
+
if (extendedPackageJson.workspaces) {
|
|
304
|
+
delete extendedPackageJson.workspaces;
|
|
305
|
+
logger.verbose(`Removed workspace configuration from ${relativePath}`);
|
|
306
|
+
modified = true;
|
|
307
|
+
totalCleanedCount++;
|
|
308
|
+
}
|
|
309
|
+
// Clean overrides with problematic paths
|
|
310
|
+
if (extendedPackageJson.overrides) {
|
|
311
|
+
const cleanOverrides = {};
|
|
312
|
+
let overridesModified = false;
|
|
313
|
+
for (const [name, override] of Object.entries(extendedPackageJson.overrides)){
|
|
314
|
+
if (typeof override === 'string' && (override.startsWith('file:') || override.startsWith('link:') || override.includes('../'))) {
|
|
315
|
+
logger.verbose(`Removed problematic override ${relativePath}/overrides.${name}: ${override}`);
|
|
316
|
+
overridesModified = true;
|
|
317
|
+
totalCleanedCount++;
|
|
318
|
+
} else {
|
|
319
|
+
cleanOverrides[name] = override;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
if (overridesModified) {
|
|
323
|
+
if (Object.keys(cleanOverrides).length === 0) {
|
|
324
|
+
delete extendedPackageJson.overrides;
|
|
325
|
+
} else {
|
|
326
|
+
extendedPackageJson.overrides = cleanOverrides;
|
|
327
|
+
}
|
|
328
|
+
modified = true;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
// Clean resolutions with problematic paths
|
|
332
|
+
if (extendedPackageJson.resolutions) {
|
|
333
|
+
const cleanResolutions = {};
|
|
334
|
+
let resolutionsModified = false;
|
|
335
|
+
for (const [name, resolution] of Object.entries(extendedPackageJson.resolutions)){
|
|
336
|
+
if (typeof resolution === 'string' && (resolution.startsWith('file:') || resolution.startsWith('link:') || resolution.includes('../'))) {
|
|
337
|
+
logger.verbose(`Removed problematic resolution ${relativePath}/resolutions.${name}: ${resolution}`);
|
|
338
|
+
resolutionsModified = true;
|
|
339
|
+
totalCleanedCount++;
|
|
340
|
+
} else {
|
|
341
|
+
cleanResolutions[name] = resolution;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
if (resolutionsModified) {
|
|
345
|
+
if (Object.keys(cleanResolutions).length === 0) {
|
|
346
|
+
delete extendedPackageJson.resolutions;
|
|
347
|
+
} else {
|
|
348
|
+
extendedPackageJson.resolutions = cleanResolutions;
|
|
349
|
+
}
|
|
350
|
+
modified = true;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
// Save the modified package.json if any changes were made
|
|
354
|
+
if (modified) {
|
|
355
|
+
await storage.writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2), 'utf-8');
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
// Save updated backup (with restored items removed)
|
|
359
|
+
await writeLinkBackup(backup, storage);
|
|
360
|
+
if (totalRestoredCount === 0 && totalCleanedCount === 0) {
|
|
361
|
+
logger.info('✅ No problematic dependencies were found to clean up.');
|
|
362
|
+
overallTimer.end('Unlink command (nothing to clean)');
|
|
363
|
+
return 'No problematic dependencies were found to clean up.';
|
|
364
|
+
}
|
|
365
|
+
logger.info(`Cleaned up ${totalRestoredCount} linked dependencies and ${totalCleanedCount} other problematic dependencies across ${packageJsonFiles.length} package.json file(s)`);
|
|
366
|
+
// Re-read package.json files for verification
|
|
367
|
+
const updatedPackageJsonFiles = await findAllPackageJsonFiles(process.cwd(), storage);
|
|
368
|
+
// Verification step
|
|
369
|
+
const verificationPassed = await verifyCleanup(updatedPackageJsonFiles);
|
|
370
|
+
if (!verificationPassed) {
|
|
371
|
+
logger.warn('⚠️ Some problematic dependencies may still remain. Please review the output above.');
|
|
372
|
+
}
|
|
373
|
+
// Rebuild dependencies
|
|
374
|
+
logger.info('⏳ Running npm install to apply changes (this may take a moment)...');
|
|
193
375
|
try {
|
|
194
|
-
await
|
|
195
|
-
|
|
196
|
-
|
|
376
|
+
const installResult = await smartNpmInstall({
|
|
377
|
+
skipIfNotNeeded: false,
|
|
378
|
+
preferCi: true,
|
|
379
|
+
verbose: false
|
|
380
|
+
});
|
|
381
|
+
if (installResult.skipped) {
|
|
382
|
+
logger.info(`⚡ Dependencies were up to date (${installResult.method})`);
|
|
383
|
+
} else {
|
|
384
|
+
logger.info(`✅ Dependencies rebuilt successfully using ${installResult.method} (${installResult.duration}ms)`);
|
|
385
|
+
}
|
|
197
386
|
} catch (error) {
|
|
198
|
-
logger.warn(`Failed to rebuild dependencies: ${error}. You may need to run '
|
|
387
|
+
logger.warn(`Failed to rebuild dependencies: ${error}. You may need to run 'npm install' manually.`);
|
|
199
388
|
}
|
|
389
|
+
const summary = `Successfully cleaned up ${totalRestoredCount} linked dependencies and ${totalCleanedCount} other problematic dependencies across ${packageJsonFiles.length} package.json file(s)`;
|
|
390
|
+
overallTimer.end('Unlink command completed');
|
|
391
|
+
return summary;
|
|
200
392
|
}
|
|
201
|
-
const summary = `Successfully unlinked ${actuallyRemovedPackages.length} sibling packages:\n${actuallyRemovedPackages.map((pkg)=>` - ${pkg}`).join('\n')}`;
|
|
202
|
-
return summary;
|
|
203
393
|
};
|
|
204
394
|
|
|
205
395
|
export { execute };
|