Skip to content

Commit 8b7fc2c

Browse files
author
Dan Clayton
committed
docs(changeset): support symlinks
1 parent 75c6120 commit 8b7fc2c

2 files changed

Lines changed: 58 additions & 3 deletions

File tree

.changeset/nine-needles-return.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@azwebmaster/dependency-optimizer": patch
3+
---
4+
5+
support symlinks

src/analyzer.ts

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,19 +80,50 @@ export class NodeModulesAnalyzer {
8080
const entries = await fs.readdir(nodeModulesPath, { withFileTypes: true });
8181

8282
for (const entry of entries) {
83-
if (entry.isDirectory()) {
83+
if (entry.isDirectory() || entry.isSymbolicLink()) {
8484
const entryPath = path.join(nodeModulesPath, entry.name);
8585

86+
// For symlinks, check if they point to directories
87+
if (entry.isSymbolicLink()) {
88+
try {
89+
const stats = await fs.stat(entryPath);
90+
if (!stats.isDirectory()) {
91+
debug('Symlink %s does not point to a directory, skipping', entry.name);
92+
continue;
93+
}
94+
debug('Found symlinked directory: %s', entry.name);
95+
} catch (error) {
96+
debug('Failed to resolve symlink %s: %O', entry.name, error);
97+
continue;
98+
}
99+
}
100+
86101
if (entry.name.startsWith('@')) {
87102
// Scoped packages
88103
debug('Processing scoped package directory: %s', entry.name);
89104
try {
90105
const scopedEntries = await fs.readdir(entryPath, { withFileTypes: true });
91106
for (const scopedEntry of scopedEntries) {
92-
if (scopedEntry.isDirectory()) {
107+
if (scopedEntry.isDirectory() || scopedEntry.isSymbolicLink()) {
93108
const scopedPath = path.join(entryPath, scopedEntry.name);
109+
110+
// For symlinks in scoped packages, check if they point to directories
111+
if (scopedEntry.isSymbolicLink()) {
112+
try {
113+
const stats = await fs.stat(scopedPath);
114+
if (!stats.isDirectory()) {
115+
debug('Symlink %s/%s does not point to a directory, skipping', entry.name, scopedEntry.name);
116+
continue;
117+
}
118+
debug('Found symlinked scoped package: %s/%s', entry.name, scopedEntry.name);
119+
} catch (error) {
120+
debug('Failed to resolve symlink %s/%s: %O', entry.name, scopedEntry.name, error);
121+
continue;
122+
}
123+
}
124+
94125
const packageName = `${entry.name}/${scopedEntry.name}`;
95-
126+
96127
// Verify it's a valid package
97128
if (await this.isValidPackage(scopedPath)) {
98129
debug('Found scoped package: %s', packageName);
@@ -175,6 +206,25 @@ export class NodeModulesAnalyzer {
175206
continue;
176207
}
177208
size += await this.getDirectorySize(entryPath);
209+
} else if (entry.isSymbolicLink()) {
210+
try {
211+
const stats = await fs.stat(entryPath);
212+
if (stats.isDirectory()) {
213+
// Skip nested node_modules to avoid double counting
214+
if (entry.name === 'node_modules') {
215+
debug('Skipping symlinked nested node_modules in: %s', dirPath);
216+
continue;
217+
}
218+
debug('Following symlinked directory: %s', entryPath);
219+
size += await this.getDirectorySize(entryPath);
220+
} else if (stats.isFile()) {
221+
debug('Following symlinked file: %s', entryPath);
222+
size += stats.size;
223+
}
224+
} catch (error) {
225+
// Skip symlinks we can't resolve
226+
debug('Failed to resolve symlink %s: %O', entryPath, error);
227+
}
178228
} else if (entry.isFile()) {
179229
try {
180230
const stats = await fs.stat(entryPath);

0 commit comments

Comments
 (0)