@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/unlink.js
CHANGED
|
@@ -1,417 +1,312 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { getDryRunLogger, getLogger } from '../logging.js';
|
|
1
|
+
import { getLogger, getDryRunLogger } from '../logging.js';
|
|
3
2
|
import { create } from '../util/storage.js';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
3
|
+
import { run, runSecure } from '../util/child.js';
|
|
4
|
+
import { findAllPackageJsonFiles } from '../util/performance.js';
|
|
5
|
+
import { safeJsonParse, validatePackageJson } from '../util/validation.js';
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
if (packageName.startsWith(scope)) {
|
|
24
|
-
scopeResults.push([
|
|
25
|
-
packageName,
|
|
26
|
-
packagePath
|
|
27
|
-
]);
|
|
28
|
-
logger.debug(`Package to unlink: ${packageName} -> ${packagePath}`);
|
|
29
|
-
}
|
|
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
|
-
}
|
|
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`);
|
|
43
|
-
return packagesToUnlink;
|
|
44
|
-
};
|
|
45
|
-
const readLinkBackup = async (storage)=>{
|
|
46
|
-
const backupPath = path.join(process.cwd(), '.kodrdriv-link-backup.json');
|
|
47
|
-
if (await storage.exists(backupPath)) {
|
|
48
|
-
try {
|
|
49
|
-
const content = await storage.readFile(backupPath, 'utf-8');
|
|
50
|
-
const parsed = safeJsonParse(content, 'link backup file');
|
|
51
|
-
return validateLinkBackup(parsed);
|
|
52
|
-
} catch (error) {
|
|
53
|
-
throw new Error(`Failed to parse link backup file: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
return {};
|
|
57
|
-
};
|
|
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);
|
|
7
|
+
// Helper function to parse package names and scopes (same as link command)
|
|
8
|
+
const parsePackageArgument = (packageArg)=>{
|
|
9
|
+
if (packageArg.startsWith('@')) {
|
|
10
|
+
const parts = packageArg.split('/');
|
|
11
|
+
if (parts.length === 1) {
|
|
12
|
+
// Just a scope like "@fjell"
|
|
13
|
+
return {
|
|
14
|
+
scope: parts[0]
|
|
15
|
+
};
|
|
16
|
+
} else {
|
|
17
|
+
// Full package name like "@fjell/core"
|
|
18
|
+
return {
|
|
19
|
+
scope: parts[0],
|
|
20
|
+
packageName: packageArg
|
|
21
|
+
};
|
|
64
22
|
}
|
|
65
23
|
} else {
|
|
66
|
-
|
|
24
|
+
throw new Error(`Package argument must start with @ (scope): ${packageArg}`);
|
|
67
25
|
}
|
|
68
26
|
};
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
for (const
|
|
75
|
-
|
|
76
|
-
|
|
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
|
-
}
|
|
27
|
+
// Find packages in the workspace that match the given scope or package name
|
|
28
|
+
const findMatchingPackages = async (targetDirectories, scope, storage, logger, packageName)=>{
|
|
29
|
+
const matchingPackages = [];
|
|
30
|
+
// Find all package.json files in target directories
|
|
31
|
+
let allPackageJsonFiles = [];
|
|
32
|
+
for (const targetDirectory of targetDirectories){
|
|
33
|
+
const packageJsonFiles = await findAllPackageJsonFiles(targetDirectory, storage);
|
|
34
|
+
allPackageJsonFiles = allPackageJsonFiles.concat(packageJsonFiles);
|
|
91
35
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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
|
-
}
|
|
36
|
+
for (const packageJsonLocation of allPackageJsonFiles){
|
|
37
|
+
const packageDir = packageJsonLocation.path.replace('/package.json', '');
|
|
38
|
+
try {
|
|
39
|
+
const packageJsonContent = await storage.readFile(packageJsonLocation.path, 'utf-8');
|
|
40
|
+
const parsed = safeJsonParse(packageJsonContent, packageJsonLocation.path);
|
|
41
|
+
const packageJson = validatePackageJson(parsed, packageJsonLocation.path);
|
|
42
|
+
if (!packageJson.name) continue;
|
|
43
|
+
const isInScope = packageJson.name.startsWith(scope + '/');
|
|
44
|
+
const isExactMatch = packageName && packageJson.name === packageName;
|
|
45
|
+
if (isInScope || isExactMatch) {
|
|
46
|
+
matchingPackages.push({
|
|
47
|
+
name: packageJson.name,
|
|
48
|
+
path: packageDir,
|
|
49
|
+
isSource: packageName ? packageJson.name === packageName : isInScope
|
|
50
|
+
});
|
|
180
51
|
}
|
|
52
|
+
} catch (error) {
|
|
53
|
+
logger.warn(`Failed to parse ${packageJsonLocation.path}: ${error.message}`);
|
|
181
54
|
}
|
|
182
55
|
}
|
|
183
|
-
|
|
184
|
-
return problematicDeps;
|
|
56
|
+
return matchingPackages;
|
|
185
57
|
};
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
58
|
+
// Find packages that depend on the target package
|
|
59
|
+
const findConsumingPackages = async (targetDirectories, targetPackageName, storage, logger)=>{
|
|
60
|
+
const consumingPackages = [];
|
|
61
|
+
// Find all package.json files in target directories
|
|
62
|
+
let allPackageJsonFiles = [];
|
|
63
|
+
for (const targetDirectory of targetDirectories){
|
|
64
|
+
const packageJsonFiles = await findAllPackageJsonFiles(targetDirectory, storage);
|
|
65
|
+
allPackageJsonFiles = allPackageJsonFiles.concat(packageJsonFiles);
|
|
193
66
|
}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
67
|
+
for (const packageJsonLocation of allPackageJsonFiles){
|
|
68
|
+
const packageDir = packageJsonLocation.path.replace('/package.json', '');
|
|
69
|
+
try {
|
|
70
|
+
const packageJsonContent = await storage.readFile(packageJsonLocation.path, 'utf-8');
|
|
71
|
+
const parsed = safeJsonParse(packageJsonContent, packageJsonLocation.path);
|
|
72
|
+
const packageJson = validatePackageJson(parsed, packageJsonLocation.path);
|
|
73
|
+
if (!packageJson.name) continue;
|
|
74
|
+
// Check if this package depends on the target package
|
|
75
|
+
const dependencyTypes = [
|
|
76
|
+
'dependencies',
|
|
77
|
+
'devDependencies',
|
|
78
|
+
'peerDependencies',
|
|
79
|
+
'optionalDependencies'
|
|
80
|
+
];
|
|
81
|
+
const hasDependency = dependencyTypes.some((depType)=>packageJson[depType] && packageJson[depType][targetPackageName]);
|
|
82
|
+
if (hasDependency && packageJson.name !== targetPackageName) {
|
|
83
|
+
consumingPackages.push({
|
|
84
|
+
name: packageJson.name,
|
|
85
|
+
path: packageDir
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
} catch (error) {
|
|
89
|
+
logger.warn(`Failed to parse ${packageJsonLocation.path}: ${error.message}`);
|
|
208
90
|
}
|
|
209
91
|
}
|
|
92
|
+
return consumingPackages;
|
|
210
93
|
};
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
*/ const verifyCleanup = async (packageJsonFiles)=>{
|
|
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;
|
|
94
|
+
const executeInternal = async (runConfig, packageArgument)=>{
|
|
95
|
+
var _runConfig_unlink, _runConfig_tree;
|
|
230
96
|
const isDryRun = runConfig.dryRun || ((_runConfig_unlink = runConfig.unlink) === null || _runConfig_unlink === void 0 ? void 0 : _runConfig_unlink.dryRun) || false;
|
|
231
97
|
const logger = getDryRunLogger(isDryRun);
|
|
232
|
-
const overallTimer = PerformanceTimer.start(logger, 'Unlink command execution');
|
|
233
98
|
const storage = create({
|
|
234
99
|
log: logger.info
|
|
235
100
|
});
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
(
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
logger.info(
|
|
244
|
-
overallTimer.end('Unlink command (no scope roots)');
|
|
245
|
-
return 'No scope roots configured. Skipping link management.';
|
|
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`);
|
|
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);
|
|
257
|
-
// Find packages to unlink based on scope roots
|
|
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.';
|
|
101
|
+
// Get target directories from config, default to current directory
|
|
102
|
+
const targetDirectories = ((_runConfig_tree = runConfig.tree) === null || _runConfig_tree === void 0 ? void 0 : _runConfig_tree.directories) || [
|
|
103
|
+
process.cwd()
|
|
104
|
+
];
|
|
105
|
+
if (targetDirectories.length === 1) {
|
|
106
|
+
logger.info(`Analyzing workspace at: ${targetDirectories[0]}`);
|
|
107
|
+
} else {
|
|
108
|
+
logger.info(`Analyzing workspaces at: ${targetDirectories.join(', ')}`);
|
|
263
109
|
}
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
110
|
+
// If no package argument provided, implement new behavior for current project
|
|
111
|
+
if (!packageArgument) {
|
|
112
|
+
var _runConfig_unlink1;
|
|
113
|
+
logger.info('๐ Unlinking current project...');
|
|
114
|
+
const currentDir = process.cwd();
|
|
115
|
+
const packageJsonPath = `${currentDir}/package.json`;
|
|
116
|
+
// Check if we're in a directory with package.json
|
|
117
|
+
if (!await storage.exists(packageJsonPath)) {
|
|
118
|
+
const message = `No package.json found in current directory: ${currentDir}`;
|
|
119
|
+
logger.warn(message);
|
|
120
|
+
return message;
|
|
121
|
+
}
|
|
122
|
+
// Parse package.json to get package name
|
|
123
|
+
let packageName;
|
|
124
|
+
try {
|
|
125
|
+
const packageJsonContent = await storage.readFile(packageJsonPath, 'utf-8');
|
|
126
|
+
const parsed = safeJsonParse(packageJsonContent, packageJsonPath);
|
|
127
|
+
const packageJson = validatePackageJson(parsed, packageJsonPath);
|
|
128
|
+
if (!packageJson.name) {
|
|
129
|
+
throw new Error('package.json has no name field');
|
|
281
130
|
}
|
|
131
|
+
packageName = packageJson.name;
|
|
132
|
+
} catch (error) {
|
|
133
|
+
const message = `Failed to parse package.json: ${error.message}`;
|
|
134
|
+
logger.error(message);
|
|
135
|
+
return message;
|
|
282
136
|
}
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
137
|
+
logger.info(`Processing package: ${packageName}`);
|
|
138
|
+
const cleanNodeModules = ((_runConfig_unlink1 = runConfig.unlink) === null || _runConfig_unlink1 === void 0 ? void 0 : _runConfig_unlink1.cleanNodeModules) || false;
|
|
139
|
+
if (isDryRun) {
|
|
140
|
+
logger.info(`DRY RUN: Would execute unlink steps for ${packageName}:`);
|
|
141
|
+
logger.info(` 1. npm unlink -g`);
|
|
142
|
+
if (cleanNodeModules) {
|
|
143
|
+
logger.info(` 2. rm -rf node_modules package-lock.json`);
|
|
144
|
+
logger.info(` 3. npm install`);
|
|
145
|
+
logger.info(` 4. Check for remaining links with npm ls --link`);
|
|
146
|
+
} else {
|
|
147
|
+
logger.info(` 2. Check for remaining links with npm ls --link`);
|
|
148
|
+
logger.info(` Note: Use --clean-node-modules flag to also clean and reinstall dependencies`);
|
|
149
|
+
}
|
|
150
|
+
return `DRY RUN: Would execute unlink steps for ${packageName}`;
|
|
286
151
|
}
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
//
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
if (extendedPackageJson.workspaces) {
|
|
305
|
-
delete extendedPackageJson.workspaces;
|
|
306
|
-
logger.verbose(`Removed workspace configuration from ${relativePath}`);
|
|
307
|
-
modified = true;
|
|
308
|
-
totalCleanedCount++;
|
|
152
|
+
// Step 1: Remove global link
|
|
153
|
+
logger.info('Step 1: Removing global link...');
|
|
154
|
+
try {
|
|
155
|
+
await run('npm unlink -g');
|
|
156
|
+
logger.info('โ
Global link removed');
|
|
157
|
+
} catch (error) {
|
|
158
|
+
// This might fail if the package wasn't globally linked, which is OK
|
|
159
|
+
logger.warn(`โ ๏ธ Failed to remove global link (this is OK if package wasn't linked): ${error.message}`);
|
|
160
|
+
}
|
|
161
|
+
if (cleanNodeModules) {
|
|
162
|
+
// Step 2: Clean node_modules and package-lock.json
|
|
163
|
+
logger.info('Step 2: Cleaning node_modules and package-lock.json...');
|
|
164
|
+
try {
|
|
165
|
+
await run('rm -rf node_modules package-lock.json');
|
|
166
|
+
logger.info('โ
Cleaned node_modules and package-lock.json');
|
|
167
|
+
} catch (error) {
|
|
168
|
+
logger.warn(`โ ๏ธ Failed to clean node_modules/package-lock.json: ${error.message}`);
|
|
309
169
|
}
|
|
310
|
-
//
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
totalCleanedCount++;
|
|
319
|
-
} else {
|
|
320
|
-
cleanOverrides[name] = override;
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
if (overridesModified) {
|
|
324
|
-
if (Object.keys(cleanOverrides).length === 0) {
|
|
325
|
-
delete extendedPackageJson.overrides;
|
|
326
|
-
} else {
|
|
327
|
-
extendedPackageJson.overrides = cleanOverrides;
|
|
328
|
-
}
|
|
329
|
-
modified = true;
|
|
330
|
-
}
|
|
170
|
+
// Step 3: Install dependencies
|
|
171
|
+
logger.info('Step 3: Installing dependencies...');
|
|
172
|
+
try {
|
|
173
|
+
await run('npm install');
|
|
174
|
+
logger.info('โ
Dependencies installed');
|
|
175
|
+
} catch (error) {
|
|
176
|
+
logger.error(`โ Failed to install dependencies: ${error.message}`);
|
|
177
|
+
throw error;
|
|
331
178
|
}
|
|
332
|
-
//
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
179
|
+
// Step 4: Check for remaining links (suppress output and errors)
|
|
180
|
+
logger.info('Step 4: Checking for remaining links...');
|
|
181
|
+
} else {
|
|
182
|
+
// Step 2: Check for remaining links (suppress output and errors)
|
|
183
|
+
logger.info('Step 2: Checking for remaining links...');
|
|
184
|
+
logger.info('Note: Use --clean-node-modules flag to also clean and reinstall dependencies');
|
|
185
|
+
}
|
|
186
|
+
try {
|
|
187
|
+
// Use child_process directly to suppress logging and get JSON output
|
|
188
|
+
const util = await import('util');
|
|
189
|
+
const child_process = await import('child_process');
|
|
190
|
+
const execPromise = util.promisify(child_process.exec);
|
|
191
|
+
const result = await execPromise('npm ls --link --json');
|
|
192
|
+
// Parse JSON output to check for links to packages in the same scope
|
|
193
|
+
const packageScope = packageName.includes('/') ? packageName.split('/')[0] : null;
|
|
194
|
+
if (packageScope && result.stdout.trim()) {
|
|
195
|
+
try {
|
|
196
|
+
const linksData = safeJsonParse(result.stdout, 'npm ls output after unlink');
|
|
197
|
+
const linkedPackages = Object.keys(linksData.dependencies || {});
|
|
198
|
+
const scopeLinkedPackages = linkedPackages.filter((pkg)=>pkg.startsWith(packageScope + '/'));
|
|
199
|
+
if (scopeLinkedPackages.length > 0) {
|
|
200
|
+
logger.warn(`โ ๏ธ Found remaining links to packages in scope ${packageScope}: ${scopeLinkedPackages.join(', ')}`);
|
|
201
|
+
logger.verbose('This may be expected if other packages in your workspace are still linked');
|
|
341
202
|
} else {
|
|
342
|
-
|
|
203
|
+
logger.info('โ
No problematic links found');
|
|
343
204
|
}
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
205
|
+
} catch {
|
|
206
|
+
// If JSON parsing fails, fall back to basic check
|
|
207
|
+
logger.verbose('Failed to parse npm ls --link --json output, using basic check');
|
|
208
|
+
if (result.stdout.includes(packageScope)) {
|
|
209
|
+
logger.warn(`โ ๏ธ Found remaining links to packages in scope ${packageScope}`);
|
|
210
|
+
logger.verbose('This may be expected if other packages in your workspace are still linked');
|
|
348
211
|
} else {
|
|
349
|
-
|
|
212
|
+
logger.info('โ
No problematic links found');
|
|
350
213
|
}
|
|
351
|
-
modified = true;
|
|
352
214
|
}
|
|
215
|
+
} else {
|
|
216
|
+
logger.info('โ
No problematic links found');
|
|
353
217
|
}
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
}
|
|
359
|
-
// Save updated backup (with restored items removed)
|
|
360
|
-
await writeLinkBackup(backup, storage);
|
|
361
|
-
if (totalRestoredCount === 0 && totalCleanedCount === 0) {
|
|
362
|
-
logger.info('โ
No problematic dependencies were found to clean up.');
|
|
363
|
-
overallTimer.end('Unlink command (nothing to clean)');
|
|
364
|
-
return 'No problematic dependencies were found to clean up.';
|
|
218
|
+
} catch {
|
|
219
|
+
// npm ls --link returns non-zero when there are no links, which is what we want
|
|
220
|
+
// So we only log this at verbose level
|
|
221
|
+
logger.verbose('npm ls --link check completed (non-zero exit is expected when no links exist)');
|
|
365
222
|
}
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
223
|
+
const summary = `Successfully unlinked ${packageName}`;
|
|
224
|
+
logger.info(summary);
|
|
225
|
+
return summary;
|
|
226
|
+
}
|
|
227
|
+
// New scope-based unlinking behavior
|
|
228
|
+
logger.info(`๐ Unlinking scope/package: ${packageArgument}`);
|
|
229
|
+
const { scope, packageName } = parsePackageArgument(packageArgument);
|
|
230
|
+
logger.verbose(`Parsed scope: ${scope}, package: ${packageName || 'all packages in scope'}`);
|
|
231
|
+
// Find matching packages in the workspace
|
|
232
|
+
const matchingPackages = await findMatchingPackages(targetDirectories, scope, storage, logger, packageName);
|
|
233
|
+
if (matchingPackages.length === 0) {
|
|
234
|
+
const message = packageName ? `No package found matching: ${packageName}` : `No packages found in scope: ${scope}`;
|
|
235
|
+
logger.warn(message);
|
|
236
|
+
return message;
|
|
237
|
+
}
|
|
238
|
+
logger.info(`Found ${matchingPackages.length} matching package(s)`);
|
|
239
|
+
const unlinkedPackages = [];
|
|
240
|
+
// If specific package name provided, use that; otherwise unlink all packages in scope
|
|
241
|
+
const packagesToUnlink = packageName ? matchingPackages.filter((pkg)=>pkg.name === packageName) : matchingPackages;
|
|
242
|
+
for (const pkg of packagesToUnlink){
|
|
243
|
+
logger.info(`Processing package: ${pkg.name}`);
|
|
244
|
+
// Step A: Find all packages that depend on this package and unlink them first
|
|
245
|
+
const consumingPackages = await findConsumingPackages(targetDirectories, pkg.name, storage, logger);
|
|
246
|
+
if (consumingPackages.length === 0) {
|
|
247
|
+
logger.info(`No consuming packages found for: ${pkg.name}`);
|
|
248
|
+
} else {
|
|
249
|
+
logger.info(`Found ${consumingPackages.length} consuming package(s) for: ${pkg.name}`);
|
|
250
|
+
for (const consumer of consumingPackages){
|
|
381
251
|
try {
|
|
382
|
-
|
|
383
|
-
|
|
252
|
+
const consumerOriginalCwd = process.cwd();
|
|
253
|
+
process.chdir(consumer.path);
|
|
254
|
+
try {
|
|
255
|
+
if (isDryRun) {
|
|
256
|
+
logger.info(`DRY RUN: Would run 'npm unlink ${pkg.name}' in: ${consumer.path}`);
|
|
257
|
+
} else {
|
|
258
|
+
logger.verbose(`Running 'npm unlink ${pkg.name}' in consumer: ${consumer.path}`);
|
|
259
|
+
await runSecure('npm', [
|
|
260
|
+
'unlink',
|
|
261
|
+
pkg.name
|
|
262
|
+
]);
|
|
263
|
+
logger.info(`โ
Consumer unlinked: ${consumer.name} -/-> ${pkg.name}`);
|
|
264
|
+
}
|
|
265
|
+
} finally{
|
|
266
|
+
process.chdir(consumerOriginalCwd);
|
|
267
|
+
}
|
|
384
268
|
} catch (error) {
|
|
385
|
-
|
|
269
|
+
// npm unlink can fail if package wasn't linked, but that's OK
|
|
270
|
+
logger.warn(`โ ๏ธ Failed to unlink ${pkg.name} in ${consumer.name}: ${error.message}`);
|
|
386
271
|
}
|
|
387
272
|
}
|
|
388
273
|
}
|
|
389
|
-
//
|
|
390
|
-
const updatedPackageJsonFiles = await findAllPackageJsonFiles(process.cwd(), storage);
|
|
391
|
-
// Verification step
|
|
392
|
-
const verificationPassed = await verifyCleanup(updatedPackageJsonFiles);
|
|
393
|
-
if (!verificationPassed) {
|
|
394
|
-
logger.warn('โ ๏ธ Some problematic dependencies may still remain. Please review the output above.');
|
|
395
|
-
}
|
|
396
|
-
// Rebuild dependencies with fresh install (NOT npm ci to avoid using stale lock file)
|
|
397
|
-
logger.info('โณ Running fresh npm install to regenerate clean dependencies (this may take a moment)...');
|
|
274
|
+
// Step B: Run 'npm unlink' in the source package directory
|
|
398
275
|
try {
|
|
399
|
-
const
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
276
|
+
const originalCwd = process.cwd();
|
|
277
|
+
process.chdir(pkg.path);
|
|
278
|
+
try {
|
|
279
|
+
if (isDryRun) {
|
|
280
|
+
logger.info(`DRY RUN: Would run 'npm unlink' in: ${pkg.path}`);
|
|
281
|
+
} else {
|
|
282
|
+
logger.verbose(`Running 'npm unlink' in source: ${pkg.path}`);
|
|
283
|
+
await run('npm unlink');
|
|
284
|
+
logger.info(`โ
Source unlinked: ${pkg.name}`);
|
|
285
|
+
}
|
|
286
|
+
} finally{
|
|
287
|
+
process.chdir(originalCwd);
|
|
408
288
|
}
|
|
289
|
+
unlinkedPackages.push(pkg.name);
|
|
409
290
|
} catch (error) {
|
|
410
|
-
|
|
291
|
+
// npm unlink can fail if package wasn't linked, but that's OK
|
|
292
|
+
logger.warn(`โ ๏ธ Failed to unlink source package ${pkg.name}: ${error.message}`);
|
|
293
|
+
unlinkedPackages.push(pkg.name); // Still count as success
|
|
411
294
|
}
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
295
|
+
}
|
|
296
|
+
const summary = `Successfully unlinked ${unlinkedPackages.length} package(s): ${unlinkedPackages.join(', ')}`;
|
|
297
|
+
logger.info(summary);
|
|
298
|
+
return summary;
|
|
299
|
+
};
|
|
300
|
+
const execute = async (runConfig, packageArgument)=>{
|
|
301
|
+
try {
|
|
302
|
+
var _runConfig_unlink;
|
|
303
|
+
// Use packageArgument from runConfig if not provided as parameter
|
|
304
|
+
const finalPackageArgument = packageArgument || ((_runConfig_unlink = runConfig.unlink) === null || _runConfig_unlink === void 0 ? void 0 : _runConfig_unlink.packageArgument);
|
|
305
|
+
return await executeInternal(runConfig, finalPackageArgument);
|
|
306
|
+
} catch (error) {
|
|
307
|
+
const logger = getLogger();
|
|
308
|
+
logger.error(`unlink failed: ${error.message}`);
|
|
309
|
+
throw error;
|
|
415
310
|
}
|
|
416
311
|
};
|
|
417
312
|
|