@eldrforge/kodrdriv 1.2.133 → 1.2.135

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.
Files changed (96) hide show
  1. package/.cursor/rules/no-local-dependencies.md +6 -0
  2. package/AI-GUIDE.md +4 -1
  3. package/LICENSE +1 -1
  4. package/dist/application.js +34 -42
  5. package/dist/application.js.map +1 -1
  6. package/dist/arguments.js +3 -3
  7. package/dist/arguments.js.map +1 -1
  8. package/dist/constants.js +5 -7
  9. package/dist/constants.js.map +1 -1
  10. package/dist/logging.js +4 -32
  11. package/dist/logging.js.map +1 -1
  12. package/dist/types.js +283 -0
  13. package/dist/types.js.map +1 -0
  14. package/guide/ai-system.md +3 -0
  15. package/guide/architecture.md +3 -0
  16. package/guide/commands.md +3 -0
  17. package/guide/configuration.md +3 -0
  18. package/guide/debugging.md +4 -1
  19. package/guide/development.md +3 -0
  20. package/guide/index.md +4 -1
  21. package/guide/integration.md +3 -0
  22. package/guide/monorepo.md +3 -0
  23. package/guide/quickstart.md +3 -0
  24. package/guide/testing.md +3 -0
  25. package/guide/tree-operations.md +3 -0
  26. package/guide/usage.md +3 -0
  27. package/package.json +15 -10
  28. package/DUPLICATION-CLEANUP.md +0 -104
  29. package/agentic-reflection-commit-2025-12-27T22-56-06-143Z.md +0 -50
  30. package/agentic-reflection-commit-2025-12-27T23-01-57-294Z.md +0 -50
  31. package/agentic-reflection-commit-2025-12-27T23-11-57-811Z.md +0 -50
  32. package/agentic-reflection-commit-2025-12-27T23-12-50-645Z.md +0 -50
  33. package/agentic-reflection-commit-2025-12-27T23-13-59-347Z.md +0 -52
  34. package/agentic-reflection-commit-2025-12-27T23-14-36-001Z.md +0 -50
  35. package/agentic-reflection-commit-2025-12-27T23-18-59-832Z.md +0 -50
  36. package/agentic-reflection-commit-2025-12-27T23-25-20-667Z.md +0 -62
  37. package/dist/commands/audio-commit.js +0 -152
  38. package/dist/commands/audio-commit.js.map +0 -1
  39. package/dist/commands/audio-review.js +0 -274
  40. package/dist/commands/audio-review.js.map +0 -1
  41. package/dist/commands/clean.js +0 -49
  42. package/dist/commands/clean.js.map +0 -1
  43. package/dist/commands/commit.js +0 -680
  44. package/dist/commands/commit.js.map +0 -1
  45. package/dist/commands/development.js +0 -467
  46. package/dist/commands/development.js.map +0 -1
  47. package/dist/commands/link.js +0 -646
  48. package/dist/commands/link.js.map +0 -1
  49. package/dist/commands/precommit.js +0 -99
  50. package/dist/commands/precommit.js.map +0 -1
  51. package/dist/commands/publish.js +0 -1432
  52. package/dist/commands/publish.js.map +0 -1
  53. package/dist/commands/release.js +0 -376
  54. package/dist/commands/release.js.map +0 -1
  55. package/dist/commands/review.js +0 -733
  56. package/dist/commands/review.js.map +0 -1
  57. package/dist/commands/select-audio.js +0 -46
  58. package/dist/commands/select-audio.js.map +0 -1
  59. package/dist/commands/tree.js +0 -2363
  60. package/dist/commands/tree.js.map +0 -1
  61. package/dist/commands/unlink.js +0 -537
  62. package/dist/commands/unlink.js.map +0 -1
  63. package/dist/commands/updates.js +0 -211
  64. package/dist/commands/updates.js.map +0 -1
  65. package/dist/commands/versions.js +0 -221
  66. package/dist/commands/versions.js.map +0 -1
  67. package/dist/content/diff.js +0 -346
  68. package/dist/content/diff.js.map +0 -1
  69. package/dist/content/files.js +0 -190
  70. package/dist/content/files.js.map +0 -1
  71. package/dist/content/log.js +0 -72
  72. package/dist/content/log.js.map +0 -1
  73. package/dist/util/aiAdapter.js +0 -28
  74. package/dist/util/aiAdapter.js.map +0 -1
  75. package/dist/util/fileLock.js +0 -241
  76. package/dist/util/fileLock.js.map +0 -1
  77. package/dist/util/general.js +0 -379
  78. package/dist/util/general.js.map +0 -1
  79. package/dist/util/gitMutex.js +0 -161
  80. package/dist/util/gitMutex.js.map +0 -1
  81. package/dist/util/interactive.js +0 -32
  82. package/dist/util/interactive.js.map +0 -1
  83. package/dist/util/loggerAdapter.js +0 -41
  84. package/dist/util/loggerAdapter.js.map +0 -1
  85. package/dist/util/performance.js +0 -134
  86. package/dist/util/performance.js.map +0 -1
  87. package/dist/util/precommitOptimizations.js +0 -310
  88. package/dist/util/precommitOptimizations.js.map +0 -1
  89. package/dist/util/stopContext.js +0 -146
  90. package/dist/util/stopContext.js.map +0 -1
  91. package/dist/util/storageAdapter.js +0 -31
  92. package/dist/util/storageAdapter.js.map +0 -1
  93. package/dist/util/validation.js +0 -45
  94. package/dist/util/validation.js.map +0 -1
  95. package/dist/utils/branchState.js +0 -700
  96. package/dist/utils/branchState.js.map +0 -1
@@ -1,646 +0,0 @@
1
- import { getLogger, getDryRunLogger } from '../logging.js';
2
- import { safeJsonParse, validatePackageJson, run, runSecure } from '@eldrforge/git-tools';
3
- import { findAllPackageJsonFiles } from '../util/performance.js';
4
- import { createStorage } from '@eldrforge/shared';
5
- import fs from 'fs/promises';
6
- import path__default from 'path';
7
-
8
- // Helper function to check if a path is a symbolic link
9
- const isSymbolicLink = async (filePath)=>{
10
- try {
11
- const stats = await fs.lstat(filePath);
12
- return stats.isSymbolicLink();
13
- } catch {
14
- return false;
15
- }
16
- };
17
- // Helper function to get the target of a symbolic link
18
- const getSymbolicLinkTarget = async (filePath)=>{
19
- try {
20
- const target = await fs.readlink(filePath);
21
- return target;
22
- } catch {
23
- return null;
24
- }
25
- };
26
- // Helper function to find all linked dependencies in a package
27
- const findLinkedDependencies = async (packagePath, packageName, storage, logger)=>{
28
- const linkedDependencies = [];
29
- try {
30
- const packageJsonPath = path__default.join(packagePath, 'package.json');
31
- const packageJsonContent = await storage.readFile(packageJsonPath, 'utf-8');
32
- const parsed = safeJsonParse(packageJsonContent, packageJsonPath);
33
- const packageJson = validatePackageJson(parsed, packageJsonPath);
34
- const allDependencies = {
35
- ...packageJson.dependencies,
36
- ...packageJson.devDependencies
37
- };
38
- const nodeModulesPath = path__default.join(packagePath, 'node_modules');
39
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
40
- for (const [dependencyName, version] of Object.entries(allDependencies)){
41
- let dependencyPath;
42
- if (dependencyName.startsWith('@')) {
43
- // Scoped package
44
- const [scope, name] = dependencyName.split('/');
45
- dependencyPath = path__default.join(nodeModulesPath, scope, name);
46
- } else {
47
- // Unscoped package
48
- dependencyPath = path__default.join(nodeModulesPath, dependencyName);
49
- }
50
- if (await isSymbolicLink(dependencyPath)) {
51
- const target = await getSymbolicLinkTarget(dependencyPath);
52
- if (target) {
53
- // Determine if this is an external dependency (not in the same workspace)
54
- const isExternal = !target.includes('node_modules') || target.startsWith('..');
55
- linkedDependencies.push({
56
- dependencyName,
57
- targetPath: target,
58
- isExternal
59
- });
60
- }
61
- }
62
- }
63
- } catch (error) {
64
- logger.warn(`LINKED_DEPS_CHECK_FAILED: Unable to check linked dependencies | Package: ${packageName} | Error: ${error.message}`);
65
- }
66
- return linkedDependencies;
67
- };
68
- // Helper function to check if a dependency matches any external link patterns
69
- const matchesExternalLinkPattern = (dependencyName, externalLinkPatterns)=>{
70
- if (!externalLinkPatterns || externalLinkPatterns.length === 0) {
71
- return false;
72
- }
73
- return externalLinkPatterns.some((pattern)=>{
74
- // Simple string matching - could be enhanced with glob patterns later
75
- return dependencyName === pattern || dependencyName.startsWith(pattern);
76
- });
77
- };
78
- // Helper function to create symbolic links manually
79
- const createSymbolicLink = async (packageName, sourcePath, targetDir, logger, isDryRun = false)=>{
80
- try {
81
- // Parse package name to get scope and name parts
82
- const [scope, name] = packageName.startsWith('@') ? packageName.split('/') : [
83
- null,
84
- packageName
85
- ];
86
- // Create the target path structure
87
- const nodeModulesPath = path__default.join(targetDir, 'node_modules');
88
- let targetPath;
89
- if (scope) {
90
- // Scoped package: node_modules/@scope/name
91
- const scopeDir = path__default.join(nodeModulesPath, scope);
92
- targetPath = path__default.join(scopeDir, name);
93
- if (!isDryRun) {
94
- // Ensure scope directory exists
95
- await fs.mkdir(scopeDir, {
96
- recursive: true
97
- });
98
- }
99
- } else {
100
- // Unscoped package: node_modules/name
101
- targetPath = path__default.join(nodeModulesPath, name);
102
- if (!isDryRun) {
103
- // Ensure node_modules directory exists
104
- await fs.mkdir(nodeModulesPath, {
105
- recursive: true
106
- });
107
- }
108
- }
109
- if (isDryRun) {
110
- logger.verbose(`DRY RUN: Would create symlink: ${targetPath} -> ${sourcePath}`);
111
- return true;
112
- }
113
- // Create the symbolic link using relative path for better portability
114
- const relativePath = path__default.relative(path__default.dirname(targetPath), sourcePath);
115
- // Check if something already exists at the target path
116
- try {
117
- const stats = await fs.lstat(targetPath); // Use lstat to not follow symlinks
118
- if (stats.isSymbolicLink()) {
119
- // It's a symlink, check if it points to the correct target
120
- const existingLink = await fs.readlink(targetPath);
121
- if (existingLink === relativePath) {
122
- logger.verbose(`Symlink already exists and points to correct target: ${targetPath} -> ${relativePath}`);
123
- return true;
124
- } else {
125
- logger.info(`SYMLINK_FIXING: Correcting symlink target | Path: ${targetPath} | Old Target: ${existingLink} | New Target: ${relativePath}`);
126
- await fs.unlink(targetPath);
127
- await fs.symlink(relativePath, targetPath, 'dir');
128
- logger.info(`SYMLINK_FIXED: Successfully updated symlink | Path: ${targetPath} | Target: ${relativePath} | Type: directory`);
129
- return true;
130
- }
131
- } else if (stats.isDirectory()) {
132
- // It's a directory, remove it
133
- logger.warn(`SYMLINK_DIRECTORY_CONFLICT: Removing existing directory to create symlink | Path: ${targetPath} | Type: directory | Action: Remove and replace with symlink`);
134
- await fs.rm(targetPath, {
135
- recursive: true,
136
- force: true
137
- });
138
- await fs.symlink(relativePath, targetPath, 'dir');
139
- logger.info(`SYMLINK_CREATED: Successfully created symlink after directory removal | Path: ${targetPath} | Target: ${relativePath} | Type: directory`);
140
- return true;
141
- } else {
142
- // It's a file, remove it
143
- logger.warn(`SYMLINK_FILE_CONFLICT: Removing existing file to create symlink | Path: ${targetPath} | Type: file | Action: Remove and replace with symlink`);
144
- await fs.unlink(targetPath);
145
- await fs.symlink(relativePath, targetPath, 'dir');
146
- logger.info(`SYMLINK_CREATED: Successfully created symlink after file removal | Path: ${targetPath} | Target: ${relativePath} | Type: directory`);
147
- return true;
148
- }
149
- } catch (error) {
150
- if (error.code === 'ENOENT') {
151
- // Nothing exists at target path, create the symlink
152
- await fs.symlink(relativePath, targetPath, 'dir');
153
- logger.verbose(`Created symlink: ${targetPath} -> ${relativePath}`);
154
- return true;
155
- } else {
156
- throw error; // Re-throw unexpected errors
157
- }
158
- }
159
- } catch (error) {
160
- logger.warn(`SYMLINK_CREATE_FAILED: Unable to create symlink | Package: ${packageName} | Error: ${error.message} | Status: failed`);
161
- return false;
162
- }
163
- };
164
- // Helper function to parse package names and scopes
165
- const parsePackageArgument = (packageArg)=>{
166
- if (packageArg.startsWith('@')) {
167
- const parts = packageArg.split('/');
168
- if (parts.length === 1) {
169
- // Just a scope like "@fjell"
170
- return {
171
- scope: parts[0]
172
- };
173
- } else {
174
- // Full package name like "@fjell/core"
175
- return {
176
- scope: parts[0],
177
- packageName: packageArg
178
- };
179
- }
180
- } else {
181
- throw new Error(`Package argument must start with @ (scope): ${packageArg}`);
182
- }
183
- };
184
- // Find packages in the workspace that match the given scope or package name
185
- const findMatchingPackages = async (targetDirectories, scope, storage, logger, packageName)=>{
186
- const matchingPackages = [];
187
- // Find all package.json files in target directories
188
- let allPackageJsonFiles = [];
189
- for (const targetDirectory of targetDirectories){
190
- const packageJsonFiles = await findAllPackageJsonFiles(targetDirectory, storage);
191
- allPackageJsonFiles = allPackageJsonFiles.concat(packageJsonFiles);
192
- }
193
- for (const packageJsonLocation of allPackageJsonFiles){
194
- const packageDir = packageJsonLocation.path.replace('/package.json', '');
195
- try {
196
- const packageJsonContent = await storage.readFile(packageJsonLocation.path, 'utf-8');
197
- const parsed = safeJsonParse(packageJsonContent, packageJsonLocation.path);
198
- const packageJson = validatePackageJson(parsed, packageJsonLocation.path);
199
- if (!packageJson.name) continue;
200
- const isInScope = packageJson.name.startsWith(scope + '/');
201
- const isExactMatch = packageName && packageJson.name === packageName;
202
- if (isInScope || isExactMatch) {
203
- matchingPackages.push({
204
- name: packageJson.name,
205
- path: packageDir,
206
- isSource: packageName ? packageJson.name === packageName : isInScope
207
- });
208
- }
209
- } catch (error) {
210
- logger.warn(`PACKAGE_JSON_PARSE_FAILED: Unable to parse package.json | Path: ${packageJsonLocation.path} | Error: ${error.message}`);
211
- }
212
- }
213
- return matchingPackages;
214
- };
215
- // Find packages that depend on the target package
216
- const findConsumingPackages = async (targetDirectories, targetPackageName, storage, logger)=>{
217
- const consumingPackages = [];
218
- // Find all package.json files in target directories
219
- let allPackageJsonFiles = [];
220
- for (const targetDirectory of targetDirectories){
221
- const packageJsonFiles = await findAllPackageJsonFiles(targetDirectory, storage);
222
- allPackageJsonFiles = allPackageJsonFiles.concat(packageJsonFiles);
223
- }
224
- for (const packageJsonLocation of allPackageJsonFiles){
225
- const packageDir = packageJsonLocation.path.replace('/package.json', '');
226
- try {
227
- const packageJsonContent = await storage.readFile(packageJsonLocation.path, 'utf-8');
228
- const parsed = safeJsonParse(packageJsonContent, packageJsonLocation.path);
229
- const packageJson = validatePackageJson(parsed, packageJsonLocation.path);
230
- if (!packageJson.name) continue;
231
- // Check if this package depends on the target package
232
- const dependencyTypes = [
233
- 'dependencies',
234
- 'devDependencies',
235
- 'peerDependencies',
236
- 'optionalDependencies'
237
- ];
238
- const hasDependency = dependencyTypes.some((depType)=>packageJson[depType] && packageJson[depType][targetPackageName]);
239
- if (hasDependency && packageJson.name !== targetPackageName) {
240
- consumingPackages.push({
241
- name: packageJson.name,
242
- path: packageDir
243
- });
244
- }
245
- } catch (error) {
246
- logger.warn(`Failed to parse ${packageJsonLocation.path}: ${error.message}`);
247
- }
248
- }
249
- return consumingPackages;
250
- };
251
- const executeInternal = async (runConfig, packageArgument)=>{
252
- var _runConfig_link, _runConfig_tree;
253
- const isDryRun = runConfig.dryRun || ((_runConfig_link = runConfig.link) === null || _runConfig_link === void 0 ? void 0 : _runConfig_link.dryRun) || false;
254
- const logger = getDryRunLogger(isDryRun);
255
- const storage = createStorage();
256
- // Check if this is a status command
257
- if (packageArgument === 'status') {
258
- return await executeStatus(runConfig);
259
- }
260
- // Get target directories from config, default to current directory
261
- const targetDirectories = ((_runConfig_tree = runConfig.tree) === null || _runConfig_tree === void 0 ? void 0 : _runConfig_tree.directories) || [
262
- process.cwd()
263
- ];
264
- if (targetDirectories.length === 1) {
265
- logger.info(`WORKSPACE_ANALYSIS: Analyzing single workspace directory | Path: ${targetDirectories[0]} | Purpose: Find linkable packages`);
266
- } else {
267
- logger.info(`WORKSPACE_ANALYSIS: Analyzing multiple workspace directories | Paths: ${targetDirectories.join(', ')} | Count: ${targetDirectories.length} | Purpose: Find linkable packages across workspaces`);
268
- }
269
- // If no package argument provided, use new smart same-scope linking behavior
270
- if (!packageArgument) {
271
- var _runConfig_link1, _runConfig_link2;
272
- logger.info('LINK_SMART_MODE: Smart linking mode activated for current project | Mode: smart | Target: current directory | Purpose: Auto-link dependencies based on scope');
273
- // Work in current directory only - read the package.json
274
- const currentDir = process.cwd();
275
- const packageJsonPath = `${currentDir}/package.json`;
276
- let currentPackageJson;
277
- try {
278
- const packageJsonContent = await storage.readFile(packageJsonPath, 'utf-8');
279
- const parsed = safeJsonParse(packageJsonContent, packageJsonPath);
280
- currentPackageJson = validatePackageJson(parsed, packageJsonPath);
281
- } catch (error) {
282
- const message = `PACKAGE_JSON_NOT_FOUND: No valid package.json in current directory | Error: ${error.message} | Action: Cannot proceed with smart linking`;
283
- logger.error(message);
284
- return message;
285
- }
286
- if (!currentPackageJson.name) {
287
- const message = 'PACKAGE_NAME_MISSING: package.json must have a name field | Field: name | Requirement: Required for linking | Action: Add name field to package.json';
288
- logger.error(message);
289
- return message;
290
- }
291
- // Extract the scope from the current package name
292
- const currentScope = currentPackageJson.name.startsWith('@') ? currentPackageJson.name.split('/')[0] : null;
293
- if (!currentScope) {
294
- const message = 'PACKAGE_SCOPE_MISSING: Package must have scoped name for smart linking | Format Required: @scope/package | Current: ' + currentPackageJson.name + ' | Action: Use scoped package name';
295
- logger.warn(message);
296
- return message;
297
- }
298
- logger.info(`CURRENT_PACKAGE_IDENTIFIED: Current package identified for smart linking | Package: ${currentPackageJson.name} | Scope: ${currentScope} | Path: ${currentDir}`);
299
- // Step 1: Link the current package globally (optional - continue even if this fails)
300
- try {
301
- if (isDryRun) {
302
- logger.info(`SELF_LINK_DRY_RUN: Would link current package globally | Mode: dry-run | Package: ${currentPackageJson.name} | Command: npm link`);
303
- } else {
304
- logger.verbose(`SELF_LINK_STARTING: Registering package globally | Package: ${currentPackageJson.name} | Command: npm link | Purpose: Make available for dependency linking`);
305
- await run('npm link');
306
- logger.info(`SELF_LINK_SUCCESS: Current package linked globally | Package: ${currentPackageJson.name} | Location: Global npm | Purpose: Make available for linking`);
307
- }
308
- } catch (error) {
309
- logger.warn(`SELF_LINK_FAILED: Unable to self-link current package | Package: ${currentPackageJson.name} | Error: ${error.message} | Impact: Continuing with dependency linking`);
310
- logger.info(`LINK_CONTINUING: Proceeding with dependency linking despite self-link failure | Next: Link matching dependencies`);
311
- }
312
- // Step 2: Find same-scope dependencies in current package
313
- const allDependencies = {
314
- ...currentPackageJson.dependencies,
315
- ...currentPackageJson.devDependencies
316
- };
317
- const sameScopeDependencies = Object.keys(allDependencies).filter((depName)=>depName.startsWith(currentScope + '/'));
318
- // Step 2.5: Find external dependencies that match external link patterns
319
- const externalLinkPatterns = ((_runConfig_link1 = runConfig.link) === null || _runConfig_link1 === void 0 ? void 0 : _runConfig_link1.externals) || [];
320
- const externalDependencies = Object.keys(allDependencies).filter((depName)=>matchesExternalLinkPattern(depName, externalLinkPatterns));
321
- const allDependenciesToLink = [
322
- ...sameScopeDependencies,
323
- ...externalDependencies
324
- ];
325
- if (allDependenciesToLink.length === 0) {
326
- logger.info(`No same-scope or external dependencies found for ${currentScope}`);
327
- if (isDryRun) {
328
- return `DRY RUN: Would self-link, no dependencies found to link`;
329
- } else {
330
- return `Self-linked ${currentPackageJson.name}, no dependencies to link`;
331
- }
332
- }
333
- logger.info(`Found ${sameScopeDependencies.length} same-scope dependencies: ${sameScopeDependencies.join(', ')}`);
334
- if (externalDependencies.length > 0) {
335
- logger.info(`Found ${externalDependencies.length} external dependencies matching patterns: ${externalDependencies.join(', ')}`);
336
- }
337
- // Step 2.6: Handle external dependencies using scopeRoots configuration
338
- const scopeRoots = ((_runConfig_link2 = runConfig.link) === null || _runConfig_link2 === void 0 ? void 0 : _runConfig_link2.scopeRoots) || {};
339
- const globallyLinkedViaScopeRoots = [];
340
- if (Object.keys(scopeRoots).length > 0 && externalDependencies.length > 0) {
341
- logger.info('Using scopeRoots configuration to discover and link external packages...');
342
- for (const depName of externalDependencies){
343
- const depScope = depName.startsWith('@') ? depName.split('/')[0] : null;
344
- const scopeRoot = depScope ? scopeRoots[depScope] : null;
345
- if (scopeRoot) {
346
- logger.verbose(`Processing ${depName} with scope ${depScope} -> ${scopeRoot}`);
347
- // Convert relative path to absolute
348
- const absoluteScopeRoot = path__default.resolve(currentDir, scopeRoot);
349
- logger.verbose(`Scanning scope root directory: ${absoluteScopeRoot}`);
350
- try {
351
- // Look for package with matching name in the scope directory
352
- const expectedPackageName = depName.startsWith('@') ? depName.split('/')[1] : depName;
353
- const packageDir = path__default.join(absoluteScopeRoot, expectedPackageName);
354
- const packageJsonPath = path__default.join(packageDir, 'package.json');
355
- logger.verbose(`Checking for package at: ${packageDir}`);
356
- try {
357
- const packageJsonContent = await storage.readFile(packageJsonPath, 'utf-8');
358
- const parsed = safeJsonParse(packageJsonContent, packageJsonPath);
359
- const packageJson = validatePackageJson(parsed, packageJsonPath);
360
- if (packageJson.name === depName) {
361
- logger.info(`Found matching package: ${depName} at ${packageDir}`);
362
- if (isDryRun) {
363
- logger.info(`DRY RUN: Would run 'npm link' in: ${packageDir}`);
364
- globallyLinkedViaScopeRoots.push(depName);
365
- } else {
366
- // Step A: Run 'npm link' in the source package directory
367
- const originalCwd = process.cwd();
368
- try {
369
- process.chdir(packageDir);
370
- logger.verbose(`Running 'npm link' in source: ${packageDir}`);
371
- await run('npm link');
372
- logger.info(`LINK_SOURCE_SCOPE_ROOTS: Source linked via scopeRoots | Package: ${depName} | Method: scopeRoots | Status: linked`);
373
- globallyLinkedViaScopeRoots.push(depName);
374
- } catch (linkError) {
375
- logger.warn(`LINK_SOURCE_FAILED: Failed to link source package | Package: ${depName} | Error: ${linkError.message}`);
376
- } finally{
377
- process.chdir(originalCwd);
378
- }
379
- }
380
- } else {
381
- logger.verbose(`Package name mismatch: expected ${depName}, found ${packageJson.name}`);
382
- }
383
- } catch (packageError) {
384
- logger.verbose(`Package not found or invalid: ${packageJsonPath} - ${packageError.message}`);
385
- }
386
- } catch (error) {
387
- logger.verbose(`Error processing scope ${depScope}: ${error.message}`);
388
- }
389
- } else {
390
- logger.verbose(`No scope root configured for ${depScope}`);
391
- }
392
- }
393
- if (globallyLinkedViaScopeRoots.length > 0) {
394
- logger.info(`Successfully prepared ${globallyLinkedViaScopeRoots.length} packages via scopeRoots: ${globallyLinkedViaScopeRoots.join(', ')}`);
395
- }
396
- }
397
- // Step 3: Get globally linked packages directories (only if we have dependencies to link)
398
- let globallyLinkedPackages = {};
399
- try {
400
- if (isDryRun) {
401
- logger.info(`DRY RUN: Would run 'npm ls --link -g -p' to discover linked package directories`);
402
- logger.info(`DRY RUN: Would attempt to link dependencies: ${allDependenciesToLink.join(', ')}`);
403
- return `DRY RUN: Would self-link and attempt to link ${allDependenciesToLink.length} dependencies`;
404
- } else {
405
- logger.verbose(`Discovering globally linked package directories...`);
406
- const result = await run('npm ls --link -g -p');
407
- const resultStr = typeof result === 'string' ? result : result.stdout;
408
- // Parse the directory paths - each line is a directory path
409
- const directoryPaths = resultStr.trim().split('\n').filter((line)=>line.trim() !== '');
410
- // Extract package names from directory paths and build a map
411
- for (const dirPath of directoryPaths){
412
- try {
413
- // Read the package.json to get the actual package name
414
- const packageJsonPath = `${dirPath.trim()}/package.json`;
415
- const packageJsonContent = await storage.readFile(packageJsonPath, 'utf-8');
416
- const parsed = safeJsonParse(packageJsonContent, packageJsonPath);
417
- const packageJson = validatePackageJson(parsed, packageJsonPath);
418
- if (packageJson.name) {
419
- globallyLinkedPackages[packageJson.name] = dirPath.trim();
420
- }
421
- } catch (packageError) {
422
- logger.verbose(`Could not read package.json from ${dirPath}: ${packageError.message}`);
423
- }
424
- }
425
- const linkedCount = Object.keys(globallyLinkedPackages).length;
426
- logger.verbose(`Found ${linkedCount} globally linked package(s)`);
427
- }
428
- } catch (error) {
429
- logger.warn(`Failed to get globally linked packages (continuing anyway): ${error.message}`);
430
- globallyLinkedPackages = {};
431
- }
432
- // Step 4: Link same-scope dependencies that are available globally using manual symlinks
433
- const linkedDependencies = [];
434
- for (const depName of allDependenciesToLink){
435
- const sourcePath = globallyLinkedPackages[depName];
436
- if (sourcePath) {
437
- try {
438
- logger.verbose(`Linking dependency: ${depName} from ${sourcePath}`);
439
- // Create the symbolic link manually using the directory path directly
440
- const success = await createSymbolicLink(depName, sourcePath, currentDir, logger, isDryRun);
441
- if (success) {
442
- logger.info(`LINK_DEPENDENCY_SUCCESS: Linked dependency successfully | Dependency: ${depName} | Status: symlink-created`);
443
- linkedDependencies.push(depName);
444
- } else {
445
- logger.warn(`LINK_DEPENDENCY_FAILED: Failed to link dependency | Dependency: ${depName} | Status: failed`);
446
- }
447
- } catch (error) {
448
- logger.warn(`⚠️ Failed to link ${depName}: ${error.message}`);
449
- }
450
- } else {
451
- logger.verbose(`Skipping ${depName} (not globally linked)`);
452
- }
453
- }
454
- const summary = linkedDependencies.length > 0 ? `Self-linked ${currentPackageJson.name} and linked ${linkedDependencies.length} dependencies: ${linkedDependencies.join(', ')}` : `Self-linked ${currentPackageJson.name}, no dependencies were available to link`;
455
- // Step 5: Regenerate package-lock.json without modifying node_modules
456
- try {
457
- if (isDryRun) {
458
- logger.info(`DRY RUN: Would run 'npm install --package-lock-only --no-audit --no-fund' to regenerate package-lock.json`);
459
- } else {
460
- logger.verbose(`Running 'npm install --package-lock-only --no-audit --no-fund' to regenerate package-lock.json without touching node_modules...`);
461
- await run('npm install --package-lock-only --no-audit --no-fund');
462
- logger.info(`LINK_LOCK_REGENERATED: Regenerated package-lock.json successfully | File: package-lock.json | Status: updated`);
463
- }
464
- } catch (error) {
465
- logger.warn(`LINK_LOCK_REGEN_FAILED: Failed to regenerate package-lock.json | Error: ${error.message} | Impact: Lock file may be out of sync`);
466
- }
467
- logger.info(summary);
468
- return summary;
469
- }
470
- // New scope-based linking behavior
471
- logger.info(`LINK_SCOPE_MODE: Linking scope or specific package | Target: ${packageArgument} | Mode: scope-based | Purpose: Link packages by scope`);
472
- const { scope, packageName } = parsePackageArgument(packageArgument);
473
- logger.verbose(`Parsed scope: ${scope}, package: ${packageName || 'all packages in scope'}`);
474
- // Find matching packages in the workspace
475
- const matchingPackages = await findMatchingPackages(targetDirectories, scope, storage, logger, packageName);
476
- if (matchingPackages.length === 0) {
477
- const message = packageName ? `No package found matching: ${packageName}` : `No packages found in scope: ${scope}`;
478
- logger.warn(message);
479
- return message;
480
- }
481
- logger.info(`Found ${matchingPackages.length} matching package(s)`);
482
- const linkedPackages = [];
483
- // If specific package name provided, use that; otherwise link all packages in scope
484
- const packagesToLink = packageName ? matchingPackages.filter((pkg)=>pkg.name === packageName) : matchingPackages;
485
- for (const pkg of packagesToLink){
486
- logger.info(`Processing package: ${pkg.name}`);
487
- // Step A: Run 'npm link' in the source package directory
488
- try {
489
- const originalCwd = process.cwd();
490
- process.chdir(pkg.path);
491
- try {
492
- if (isDryRun) {
493
- logger.info(`DRY RUN: Would run 'npm link' in: ${pkg.path}`);
494
- } else {
495
- logger.verbose(`Running 'npm link' in source: ${pkg.path}`);
496
- await run('npm link');
497
- logger.info(`LINK_SOURCE_SUCCESS: Source package linked globally | Package: ${pkg.name} | Status: linked`);
498
- }
499
- } finally{
500
- process.chdir(originalCwd);
501
- }
502
- // Step B: Find all packages that depend on this package and link them
503
- const consumingPackages = await findConsumingPackages(targetDirectories, pkg.name, storage, logger);
504
- if (consumingPackages.length === 0) {
505
- logger.info(`No consuming packages found for: ${pkg.name}`);
506
- } else {
507
- logger.info(`Found ${consumingPackages.length} consuming package(s) for: ${pkg.name}`);
508
- for (const consumer of consumingPackages){
509
- try {
510
- const consumerOriginalCwd = process.cwd();
511
- process.chdir(consumer.path);
512
- try {
513
- if (isDryRun) {
514
- logger.info(`DRY RUN: Would run 'npm link ${pkg.name}' in: ${consumer.path}`);
515
- } else {
516
- logger.verbose(`Running 'npm link ${pkg.name}' in consumer: ${consumer.path}`);
517
- await runSecure('npm', [
518
- 'link',
519
- pkg.name
520
- ]);
521
- logger.info(`LINK_CONSUMER_SUCCESS: Consumer linked to package | Consumer: ${consumer.name} | Package: ${pkg.name} | Status: linked`);
522
- }
523
- } finally{
524
- process.chdir(consumerOriginalCwd);
525
- }
526
- } catch (error) {
527
- logger.error(`LINK_CONSUMER_FAILED: Failed to link package in consumer | Package: ${pkg.name} | Consumer: ${consumer.name} | Error: ${error.message}`);
528
- throw new Error(`Failed to link ${pkg.name} in consumer ${consumer.name}: ${error.message}`);
529
- }
530
- }
531
- }
532
- linkedPackages.push(pkg.name);
533
- } catch (error) {
534
- logger.error(`LINK_SOURCE_PACKAGE_FAILED: Failed to link source package | Package: ${pkg.name} | Error: ${error.message}`);
535
- throw new Error(`Failed to link source package ${pkg.name}: ${error.message}`);
536
- }
537
- }
538
- const summary = `Successfully linked ${linkedPackages.length} package(s): ${linkedPackages.join(', ')}`;
539
- // Final step: Regenerate package-lock.json files in all affected packages without modifying node_modules
540
- if (!isDryRun) {
541
- logger.info(`LINK_LOCK_REGENERATING_ALL: Regenerating package-lock.json files in all packages | Mode: lockfile-only | Purpose: Update lock files after linking`);
542
- // Get all unique consuming packages
543
- const allConsumingPackages = new Set();
544
- for (const pkg of packagesToLink){
545
- const consumingPackages = await findConsumingPackages(targetDirectories, pkg.name, storage, logger);
546
- consumingPackages.forEach((consumer)=>allConsumingPackages.add(consumer.path));
547
- }
548
- // Also include the source packages
549
- packagesToLink.forEach((pkg)=>allConsumingPackages.add(pkg.path));
550
- // Run lockfile-only install in each package
551
- for (const packagePath of allConsumingPackages){
552
- try {
553
- const originalCwd = process.cwd();
554
- process.chdir(packagePath);
555
- try {
556
- logger.verbose(`Running 'npm install --package-lock-only --no-audit --no-fund' in: ${packagePath}`);
557
- await run('npm install --package-lock-only --no-audit --no-fund');
558
- logger.verbose(`LINK_LOCK_PACKAGE_REGENERATED: Regenerated package-lock.json | Path: ${packagePath} | Status: updated`);
559
- } finally{
560
- process.chdir(originalCwd);
561
- }
562
- } catch (error) {
563
- logger.warn(`LINK_LOCK_PACKAGE_REGEN_FAILED: Failed to regenerate package-lock.json | Path: ${packagePath} | Error: ${error.message}`);
564
- }
565
- }
566
- logger.info(`LINK_LOCK_ALL_REGENERATED: Regenerated package-lock.json files in all packages | Package Count: ${allConsumingPackages.size} | Status: completed`);
567
- } else {
568
- logger.info(`DRY RUN: Would run 'npm install --package-lock-only --no-audit --no-fund' to regenerate package-lock.json files in all packages`);
569
- }
570
- logger.info(summary);
571
- return summary;
572
- };
573
- // Status function to show what's currently linked
574
- const executeStatus = async (runConfig)=>{
575
- var _runConfig_tree;
576
- const logger = getLogger();
577
- const storage = createStorage();
578
- // Get target directories from config, default to current directory
579
- const targetDirectories = ((_runConfig_tree = runConfig.tree) === null || _runConfig_tree === void 0 ? void 0 : _runConfig_tree.directories) || [
580
- process.cwd()
581
- ];
582
- if (targetDirectories.length === 1) {
583
- logger.info(`🔍 Checking link status in: ${targetDirectories[0]}`);
584
- } else {
585
- logger.info(`🔍 Checking link status in: ${targetDirectories.join(', ')}`);
586
- }
587
- // Find all packages in the workspace
588
- let allPackageJsonFiles = [];
589
- for (const targetDirectory of targetDirectories){
590
- const packageJsonFiles = await findAllPackageJsonFiles(targetDirectory, storage);
591
- allPackageJsonFiles = allPackageJsonFiles.concat(packageJsonFiles);
592
- }
593
- const packageStatuses = [];
594
- for (const packageJsonLocation of allPackageJsonFiles){
595
- const packageDir = packageJsonLocation.path.replace('/package.json', '');
596
- try {
597
- const packageJsonContent = await storage.readFile(packageJsonLocation.path, 'utf-8');
598
- const parsed = safeJsonParse(packageJsonContent, packageJsonLocation.path);
599
- const packageJson = validatePackageJson(parsed, packageJsonLocation.path);
600
- if (!packageJson.name) continue;
601
- const linkedDependencies = await findLinkedDependencies(packageDir, packageJson.name, storage, logger);
602
- if (linkedDependencies.length > 0) {
603
- packageStatuses.push({
604
- name: packageJson.name,
605
- path: packageDir,
606
- linkedDependencies
607
- });
608
- }
609
- } catch (error) {
610
- logger.warn(`Failed to parse ${packageJsonLocation.path}: ${error.message}`);
611
- }
612
- }
613
- if (packageStatuses.length === 0) {
614
- return 'No linked dependencies found in workspace.';
615
- }
616
- // Format the output
617
- let output = `Found ${packageStatuses.length} package(s) with linked dependencies:\n\n`;
618
- for (const packageStatus of packageStatuses){
619
- output += `📦 ${packageStatus.name}\n`;
620
- output += ` Path: ${packageStatus.path}\n`;
621
- if (packageStatus.linkedDependencies.length > 0) {
622
- output += ` Linked dependencies:\n`;
623
- for (const dep of packageStatus.linkedDependencies){
624
- const type = dep.isExternal ? '🔗 External' : '🔗 Internal';
625
- output += ` ${type} ${dep.dependencyName} -> ${dep.targetPath}\n`;
626
- }
627
- }
628
- output += '\n';
629
- }
630
- return output;
631
- };
632
- const execute = async (runConfig, packageArgument)=>{
633
- try {
634
- var _runConfig_link;
635
- // Use packageArgument from runConfig if not provided as parameter
636
- const finalPackageArgument = packageArgument || ((_runConfig_link = runConfig.link) === null || _runConfig_link === void 0 ? void 0 : _runConfig_link.packageArgument);
637
- return await executeInternal(runConfig, finalPackageArgument);
638
- } catch (error) {
639
- const logger = getLogger();
640
- logger.error(`link failed: ${error.message}`);
641
- throw error;
642
- }
643
- };
644
-
645
- export { execute };
646
- //# sourceMappingURL=link.js.map