Skip to content

Commit 3d2810e

Browse files
committed
Merge branch 'main' into feat/api
2 parents 9f0e9c7 + 5d3f546 commit 3d2810e

5 files changed

Lines changed: 172 additions & 43 deletions

File tree

README.id.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ Kami berusaha untuk menerjemahkan dokumen Npkill ke berbagai bahasa. Berikut daf
2323

2424
- [Español](./README.es.md)
2525
- [Indonesian](./README.id.md)
26+
- [Portugis](./README.pt.md)
27+
- [Turki](./README.tr.md)
2628

2729
## Daftar Isi
2830

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,9 @@ This tool allows you to list any _node_modules_ directories in your system, as w
2121
We're making an effort to internationalize the Npkill docs. Here's a list of the available translations:
2222

2323
- [Español](./README.es.md)
24-
- [Português](./README.pt.md)
2524
- [Indonesian](./README.id.md)
25+
- [Português](./README.pt.md)
26+
- [Turkish](./README.tr.md)
2627

2728
## Table of Contents
2829

README.tr.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,9 @@ Bu araç, sisteminizdeki tüm _node_modules_ dizinlerini ve kapladıkları alan
2121

2222
Npkill dokümantasyonunu uluslararası hale getirmek için çaba gösteriyoruz. İşte mevcut çevirilerin listesi:
2323

24-
- [Español](./README.es.md)
25-
- [Português](./README.pt.md)
24+
- [Endonezce](./README.id.md)
25+
- [İspanyolca](./README.es.md)
26+
- [Portekizce](./README.pt.md)
2627
- [Türkçe](./README.tr.md)
2728

2829
## İçindekiler

src/core/services/files/files.service.ts

Lines changed: 58 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import path from 'path';
12
import {
23
ScanOptions,
34
IFileService,
@@ -86,16 +87,64 @@ export abstract class FileService implements IFileService {
8687
* application-specific data or configurations that should not be tampered with. Deleting node_modules
8788
* from these locations could potentially disrupt the normal operation of these applications.
8889
*/
89-
isDangerous(path: string): boolean {
90-
const hiddenFilePattern = /(^|\/)\.[^/.]/g;
91-
const macAppsPattern = /(^|\/)Applications\/[^/]+\.app\//g;
92-
const windowsAppDataPattern = /(^|\\)AppData\\/g;
93-
94-
return (
95-
hiddenFilePattern.test(path) ||
96-
macAppsPattern.test(path) ||
97-
windowsAppDataPattern.test(path)
90+
isDangerous(originalPath: string): boolean {
91+
const absolutePath = path.resolve(originalPath);
92+
const normalizedPath = absolutePath.replace(/\\/g, '/').toLowerCase();
93+
94+
const home = process.env.HOME ?? process.env.USERPROFILE ?? '';
95+
let isInHome = false;
96+
97+
if (home !== '') {
98+
const normalizedHome = path
99+
.resolve(home)
100+
.replace(/\\/g, '/')
101+
.toLowerCase();
102+
isInHome = normalizedPath.startsWith(normalizedHome);
103+
}
104+
105+
if (isInHome) {
106+
if (/\/\.config(\/|$)/.test(normalizedPath)) {
107+
return true;
108+
}
109+
if (/\/\.local\/share(\/|$)/.test(normalizedPath)) {
110+
return true;
111+
}
112+
if (/\/\.(cache|npm|pnpm)(\/|$)/.test(normalizedPath)) {
113+
return false;
114+
}
115+
}
116+
117+
if (/\/applications\/[^/]+\.app\//.test(normalizedPath)) {
118+
return true;
119+
}
120+
121+
if (normalizedPath.includes('/appdata/roaming')) {
122+
return true;
123+
}
124+
if (normalizedPath.includes('/appdata/local')) {
125+
if (/\/\.(cache|npm|pnpm)(\/|$)/.test(normalizedPath)) {
126+
return false;
127+
}
128+
return true;
129+
}
130+
if (/program files( \(x86\))?\//.test(normalizedPath)) {
131+
return true;
132+
}
133+
134+
const segments = normalizedPath.split('/');
135+
const hasUnsafeHiddenFolder = segments.some(
136+
(segment) =>
137+
segment.startsWith('.') &&
138+
segment !== '.' &&
139+
segment !== '..' &&
140+
!['.cache', '.npm', '.pnpm'].includes(segment),
98141
);
142+
143+
if (hasUnsafeHiddenFolder) {
144+
return true;
145+
}
146+
147+
return false;
99148
}
100149

101150
async getRecentModificationInDir(

tests/core/services/files/files.service.test.ts

Lines changed: 107 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,9 @@ describe('File Service', () => {
132132
fileService.isSafeToDelete('/one/node_/ro/modules', target),
133133
).toBeFalsy();
134134
expect(fileService.isSafeToDelete('nodemodules', target)).toBeFalsy();
135+
expect(fileService.isSafeToDelete('/', target)).toBeFalsy();
136+
expect(fileService.isSafeToDelete('/home', target)).toBeFalsy();
137+
expect(fileService.isSafeToDelete('/home/user', target)).toBeFalsy();
135138
});
136139

137140
it('should get true if is safe to delete ', () => {
@@ -144,39 +147,112 @@ describe('File Service', () => {
144147
});
145148
});
146149

147-
describe('#isDangerous', () => {
148-
it('should return false for paths that are not considered dangerous', () => {
149-
expect(
150-
fileService.isDangerous('/home/apps/myapp/node_modules'),
151-
).toBeFalsy();
152-
expect(fileService.isDangerous('node_modules')).toBeFalsy();
153-
expect(
154-
fileService.isDangerous('/home/user/projects/a/node_modules'),
155-
).toBeFalsy();
156-
expect(
157-
fileService.isDangerous('/Applications/NotAnApp/node_modules'),
158-
).toBeFalsy();
159-
expect(
160-
fileService.isDangerous('C:\\Users\\User\\Documents\\node_modules'),
161-
).toBeFalsy();
150+
describe('isDangerous', () => {
151+
const originalEnv = { ...process.env };
152+
const originalCwd = process.cwd();
153+
154+
const mockCwd = (cwd: string) => {
155+
jest.spyOn(process, 'cwd').mockReturnValue(cwd);
156+
};
157+
158+
afterAll(() => {
159+
process.env = originalEnv;
162160
});
163161

164-
it('should return true for paths that are considered dangerous', () => {
165-
expect(
166-
fileService.isDangerous('/home/.config/myapp/node_modules'),
167-
).toBeTruthy();
168-
expect(fileService.isDangerous('.apps/node_modules')).toBeTruthy();
169-
expect(
170-
fileService.isDangerous('.apps/.sample/node_modules'),
171-
).toBeTruthy();
172-
expect(
173-
fileService.isDangerous('/Applications/MyApp.app/node_modules'),
174-
).toBeTruthy();
175-
expect(
176-
fileService.isDangerous(
177-
'C:\\Users\\User\\AppData\\Local\\node_modules',
178-
),
179-
).toBeTruthy();
162+
describe('POSIX paths', () => {
163+
beforeAll(() => {
164+
process.env.HOME = '/home/user';
165+
delete process.env.USERPROFILE;
166+
});
167+
168+
test('safe relative path', () => {
169+
mockCwd('/safe/path');
170+
expect(fileService.isDangerous('../project/node_modules')).toBe(false);
171+
});
172+
173+
test('hidden relative path', () => {
174+
mockCwd('/home/user/projects');
175+
expect(fileService.isDangerous('../.config/secret')).toBe(true);
176+
});
177+
178+
test('node_modules in ~/.cache', () => {
179+
expect(
180+
fileService.isDangerous('/home/user/.cache/project/node_modules'),
181+
).toBe(false);
182+
});
183+
184+
test('parent relative path (..)', () => {
185+
mockCwd('/home/user');
186+
expect(fileService.isDangerous('..')).toBe(false);
187+
});
188+
189+
test('current relative path (.)', () => {
190+
mockCwd('/home/user');
191+
expect(fileService.isDangerous('.')).toBe(false);
192+
});
193+
});
194+
195+
describe('Windows paths', () => {
196+
beforeAll(() => {
197+
process.env.USERPROFILE = 'C:\\Users\\user';
198+
process.env.HOME = '';
199+
});
200+
201+
test('safe relative path', () => {
202+
mockCwd('D:\\safe\\path');
203+
expect(fileService.isDangerous('..\\project\\node_modules')).toBe(
204+
false,
205+
);
206+
});
207+
208+
test('AppData relative path', () => {
209+
mockCwd('C:\\Users\\user\\Documents');
210+
expect(fileService.isDangerous('..\\AppData\\Roaming\\app')).toBe(true);
211+
});
212+
213+
test('Program Files (x86)', () => {
214+
expect(
215+
fileService.isDangerous('C:\\Program Files (x86)\\app\\node_modules'),
216+
).toBe(true);
217+
});
218+
219+
test('network paths', () => {
220+
expect(
221+
fileService.isDangerous('\\\\network\\share\\.hidden\\node_modules'),
222+
).toBe(true);
223+
});
224+
});
225+
226+
describe('Edge cases', () => {
227+
test('no home directory', () => {
228+
delete process.env.HOME;
229+
delete process.env.USERPROFILE;
230+
expect(fileService.isDangerous('/some/path')).toBe(false);
231+
});
232+
233+
test('whitelisted hidden segments', () => {
234+
expect(
235+
fileService.isDangerous('/tmp/.cache/project/node_modules'),
236+
).toBe(false);
237+
expect(fileService.isDangerous('/tmp/.npm/project/node_modules')).toBe(
238+
false,
239+
);
240+
});
241+
242+
test('macOS application bundle', () => {
243+
expect(
244+
fileService.isDangerous(
245+
'/Applications/MyApp.app/Contents/node_modules',
246+
),
247+
).toBe(true);
248+
});
249+
250+
test('Windows-style path on POSIX', () => {
251+
process.env.HOME = '/home/user';
252+
expect(fileService.isDangerous('/home/user/AppData/Local/.cache')).toBe(
253+
false,
254+
);
255+
});
180256
});
181257
});
182258

0 commit comments

Comments
 (0)