oh-my-node-modules 1.3.2 → 1.3.3
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/dist/cli.js +89 -15
- package/dist/index.js +70 -9
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -3484,7 +3484,10 @@ async function isNodeModulesInUse(path) {
|
|
|
3484
3484
|
|
|
3485
3485
|
// src/deletion.ts
|
|
3486
3486
|
import { promises as fs3 } from "fs";
|
|
3487
|
-
import { join as join3 } from "path";
|
|
3487
|
+
import { join as join3, resolve } from "path";
|
|
3488
|
+
import { exec as exec2 } from "child_process";
|
|
3489
|
+
import { promisify as promisify2 } from "util";
|
|
3490
|
+
var execAsync2 = promisify2(exec2);
|
|
3488
3491
|
async function deleteSelectedNodeModules(nodeModules, options, onProgress) {
|
|
3489
3492
|
const selected = nodeModules.filter((nm) => nm.selected);
|
|
3490
3493
|
const result = {
|
|
@@ -3547,8 +3550,26 @@ async function deleteNodeModules(nodeModules, options) {
|
|
|
3547
3550
|
if (options.dryRun) {
|
|
3548
3551
|
detail.success = true;
|
|
3549
3552
|
} else {
|
|
3550
|
-
|
|
3551
|
-
|
|
3553
|
+
try {
|
|
3554
|
+
if (options.force) {
|
|
3555
|
+
await forceDelete(nodeModules.path);
|
|
3556
|
+
} else {
|
|
3557
|
+
await fs3.rm(nodeModules.path, { recursive: true, force: true });
|
|
3558
|
+
}
|
|
3559
|
+
detail.success = true;
|
|
3560
|
+
} catch (rmError) {
|
|
3561
|
+
const errorCode = rmError.code;
|
|
3562
|
+
const errorMessage = rmError.message;
|
|
3563
|
+
if (errorCode === "EPERM" || errorCode === "EACCES") {
|
|
3564
|
+
detail.error = "Permission denied - run as Administrator or check file permissions";
|
|
3565
|
+
} else if (errorCode === "EBUSY") {
|
|
3566
|
+
detail.error = "Directory in use - close any programs using these files";
|
|
3567
|
+
} else if (errorMessage?.includes("ENOTEMPTY")) {
|
|
3568
|
+
detail.error = "Directory not empty - may contain read-only files. Try using --force";
|
|
3569
|
+
} else {
|
|
3570
|
+
detail.error = errorMessage || "Unknown error during deletion";
|
|
3571
|
+
}
|
|
3572
|
+
}
|
|
3552
3573
|
}
|
|
3553
3574
|
detail.durationMs = Date.now() - startTime;
|
|
3554
3575
|
} catch (error) {
|
|
@@ -3557,16 +3578,55 @@ async function deleteNodeModules(nodeModules, options) {
|
|
|
3557
3578
|
}
|
|
3558
3579
|
return detail;
|
|
3559
3580
|
}
|
|
3560
|
-
async function
|
|
3581
|
+
async function forceDelete(dirPath) {
|
|
3582
|
+
const isWindows = process.platform === "win32";
|
|
3583
|
+
try {
|
|
3584
|
+
await fs3.rm(dirPath, { recursive: true, force: true });
|
|
3585
|
+
return;
|
|
3586
|
+
} catch {}
|
|
3587
|
+
try {
|
|
3588
|
+
await makeWritableRecursive(dirPath);
|
|
3589
|
+
await fs3.rm(dirPath, { recursive: true, force: true });
|
|
3590
|
+
return;
|
|
3591
|
+
} catch {}
|
|
3592
|
+
try {
|
|
3593
|
+
if (isWindows) {
|
|
3594
|
+
const windowsPath = dirPath.length > 240 ? `\\\\?\\${resolve(dirPath)}` : dirPath;
|
|
3595
|
+
await execAsync2(`rd /s /q "${windowsPath}"`, { timeout: 30000 });
|
|
3596
|
+
} else {
|
|
3597
|
+
await execAsync2(`rm -rf "${dirPath}"`, { timeout: 30000 });
|
|
3598
|
+
}
|
|
3599
|
+
return;
|
|
3600
|
+
} catch {}
|
|
3601
|
+
const tempPath = `${dirPath}.old.${Date.now()}`;
|
|
3602
|
+
await fs3.rename(dirPath, tempPath);
|
|
3603
|
+
try {
|
|
3604
|
+
await fs3.rm(tempPath, { recursive: true, force: true });
|
|
3605
|
+
} catch {}
|
|
3606
|
+
}
|
|
3607
|
+
async function makeWritableRecursive(dirPath) {
|
|
3561
3608
|
try {
|
|
3562
|
-
const
|
|
3563
|
-
|
|
3609
|
+
const entries = await fs3.readdir(dirPath, { withFileTypes: true });
|
|
3610
|
+
for (const entry of entries) {
|
|
3611
|
+
const fullPath = join3(dirPath, entry.name);
|
|
3612
|
+
if (entry.isDirectory()) {
|
|
3613
|
+
await makeWritableRecursive(fullPath);
|
|
3614
|
+
}
|
|
3615
|
+
await fs3.chmod(fullPath, 511).catch(() => {});
|
|
3616
|
+
}
|
|
3617
|
+
await fs3.chmod(dirPath, 511).catch(() => {});
|
|
3618
|
+
} catch {}
|
|
3619
|
+
}
|
|
3620
|
+
async function verifyNodeModules(dirPath) {
|
|
3621
|
+
try {
|
|
3622
|
+
const baseName = dirPath.replace(/\\/g, "/").split("/").pop();
|
|
3623
|
+
if (baseName !== "node_modules") {
|
|
3564
3624
|
return false;
|
|
3565
3625
|
}
|
|
3566
|
-
const entries = await fs3.readdir(
|
|
3626
|
+
const entries = await fs3.readdir(dirPath);
|
|
3567
3627
|
let hasSubdirs = false;
|
|
3568
3628
|
for (const entry of entries) {
|
|
3569
|
-
const entryPath = join3(
|
|
3629
|
+
const entryPath = join3(dirPath, entry);
|
|
3570
3630
|
try {
|
|
3571
3631
|
const stats = await fs3.stat(entryPath);
|
|
3572
3632
|
if (stats.isDirectory()) {
|
|
@@ -3575,7 +3635,8 @@ async function verifyNodeModules(path) {
|
|
|
3575
3635
|
}
|
|
3576
3636
|
} catch {}
|
|
3577
3637
|
}
|
|
3578
|
-
const
|
|
3638
|
+
const normalizedPath = dirPath.replace(/\\/g, "/");
|
|
3639
|
+
const parentPath = normalizedPath.replace(/\/node_modules$/, "");
|
|
3579
3640
|
const hasPackageJson = await fileExists(join3(parentPath, "package.json"));
|
|
3580
3641
|
return hasSubdirs || hasPackageJson;
|
|
3581
3642
|
} catch {
|
|
@@ -3606,7 +3667,7 @@ function formatItem(item) {
|
|
|
3606
3667
|
const warning = !isPending && item.sizeCategory === "huge" ? import_picocolors2.default.red(" ⚠") : "";
|
|
3607
3668
|
return `${size} ${item.projectName}${warning} [${age}]`;
|
|
3608
3669
|
}
|
|
3609
|
-
async function interactiveMode(rootPath) {
|
|
3670
|
+
async function interactiveMode(rootPath, force = false) {
|
|
3610
3671
|
pe(import_picocolors2.default.cyan("oh-my-node-modules"));
|
|
3611
3672
|
const s = _2();
|
|
3612
3673
|
s.start("Scanning for node_modules...");
|
|
@@ -3682,10 +3743,22 @@ ${import_picocolors2.default.gray("Total:")} ${import_picocolors2.default.white(
|
|
|
3682
3743
|
const result2 = await deleteSelectedNodeModules(items, {
|
|
3683
3744
|
dryRun: false,
|
|
3684
3745
|
yes: true,
|
|
3746
|
+
force,
|
|
3685
3747
|
checkRunningProcesses: false,
|
|
3686
3748
|
showProgress: false
|
|
3687
3749
|
});
|
|
3688
3750
|
ds.stop(`Deleted ${result2.successful}/${result2.totalAttempted} directories`);
|
|
3751
|
+
const failedDeletions = result2.details.filter((d2) => !d2.success && d2.error);
|
|
3752
|
+
if (failedDeletions.length > 0) {
|
|
3753
|
+
console.log(import_picocolors2.default.red(`
|
|
3754
|
+
Failed to delete ${failedDeletions.length} directories:`));
|
|
3755
|
+
for (const detail of failedDeletions.slice(0, 5)) {
|
|
3756
|
+
console.log(import_picocolors2.default.red(` • ${detail.nodeModules.projectName}: ${detail.error}`));
|
|
3757
|
+
}
|
|
3758
|
+
if (failedDeletions.length > 5) {
|
|
3759
|
+
console.log(import_picocolors2.default.red(` ... and ${failedDeletions.length - 5} more`));
|
|
3760
|
+
}
|
|
3761
|
+
}
|
|
3689
3762
|
ge(import_picocolors2.default.green(`Freed ${result2.formattedBytesFreed}`));
|
|
3690
3763
|
return;
|
|
3691
3764
|
}
|
|
@@ -3735,7 +3808,7 @@ ${import_picocolors2.default.gray("Total size:")} ${import_picocolors2.default.c
|
|
|
3735
3808
|
process.exit(1);
|
|
3736
3809
|
}
|
|
3737
3810
|
}
|
|
3738
|
-
async function autoDeleteMode(rootPath, minSize, dryRun = false) {
|
|
3811
|
+
async function autoDeleteMode(rootPath, minSize, dryRun = false, force = false) {
|
|
3739
3812
|
const s = _2();
|
|
3740
3813
|
s.start("Scanning...");
|
|
3741
3814
|
try {
|
|
@@ -3781,8 +3854,9 @@ Dry run - no files deleted.`));
|
|
|
3781
3854
|
const ds = _2();
|
|
3782
3855
|
ds.start("Deleting...");
|
|
3783
3856
|
const result2 = await deleteSelectedNodeModules(items, {
|
|
3784
|
-
dryRun
|
|
3857
|
+
dryRun,
|
|
3785
3858
|
yes: true,
|
|
3859
|
+
force,
|
|
3786
3860
|
checkRunningProcesses: false,
|
|
3787
3861
|
showProgress: false
|
|
3788
3862
|
});
|
|
@@ -3796,13 +3870,13 @@ Dry run - no files deleted.`));
|
|
|
3796
3870
|
}
|
|
3797
3871
|
}
|
|
3798
3872
|
var program2 = new Command;
|
|
3799
|
-
program2.name("onm").description("Find and clean up node_modules directories").version(getVersion()).argument("<path>", "Directory to scan").option("--scan", "quick scan mode (no interactive UI)").option("--auto", "auto-delete mode with filters").option("--min-size <size>", "minimum size in bytes for auto mode").option("--dry-run", "simulate deletion without actually deleting").option("--json", "output as JSON").action(async (path, options) => {
|
|
3873
|
+
program2.name("onm").description("Find and clean up node_modules directories").version(getVersion()).argument("<path>", "Directory to scan").option("--scan", "quick scan mode (no interactive UI)").option("--auto", "auto-delete mode with filters").option("--min-size <size>", "minimum size in bytes for auto mode").option("--dry-run", "simulate deletion without actually deleting").option("--force", "force delete (handles read-only files and long paths)").option("--json", "output as JSON").action(async (path, options) => {
|
|
3800
3874
|
if (options.scan) {
|
|
3801
3875
|
await quickScanMode(path, options.json);
|
|
3802
3876
|
} else if (options.auto) {
|
|
3803
|
-
await autoDeleteMode(path, options.minSize, options.dryRun);
|
|
3877
|
+
await autoDeleteMode(path, options.minSize, options.dryRun, options.force);
|
|
3804
3878
|
} else {
|
|
3805
|
-
await interactiveMode(path);
|
|
3879
|
+
await interactiveMode(path, options.force);
|
|
3806
3880
|
}
|
|
3807
3881
|
});
|
|
3808
3882
|
program2.parse();
|
package/dist/index.js
CHANGED
|
@@ -765,7 +765,10 @@ async function quickScan(rootPath) {
|
|
|
765
765
|
}
|
|
766
766
|
// src/deletion.ts
|
|
767
767
|
import { promises as fs3 } from "fs";
|
|
768
|
-
import { join as join3 } from "path";
|
|
768
|
+
import { join as join3, resolve } from "path";
|
|
769
|
+
import { exec as exec2 } from "child_process";
|
|
770
|
+
import { promisify as promisify2 } from "util";
|
|
771
|
+
var execAsync2 = promisify2(exec2);
|
|
769
772
|
async function deleteSelectedNodeModules(nodeModules, options, onProgress) {
|
|
770
773
|
const selected = nodeModules.filter((nm) => nm.selected);
|
|
771
774
|
const result = {
|
|
@@ -828,8 +831,26 @@ async function deleteNodeModules(nodeModules, options) {
|
|
|
828
831
|
if (options.dryRun) {
|
|
829
832
|
detail.success = true;
|
|
830
833
|
} else {
|
|
831
|
-
|
|
832
|
-
|
|
834
|
+
try {
|
|
835
|
+
if (options.force) {
|
|
836
|
+
await forceDelete(nodeModules.path);
|
|
837
|
+
} else {
|
|
838
|
+
await fs3.rm(nodeModules.path, { recursive: true, force: true });
|
|
839
|
+
}
|
|
840
|
+
detail.success = true;
|
|
841
|
+
} catch (rmError) {
|
|
842
|
+
const errorCode = rmError.code;
|
|
843
|
+
const errorMessage = rmError.message;
|
|
844
|
+
if (errorCode === "EPERM" || errorCode === "EACCES") {
|
|
845
|
+
detail.error = "Permission denied - run as Administrator or check file permissions";
|
|
846
|
+
} else if (errorCode === "EBUSY") {
|
|
847
|
+
detail.error = "Directory in use - close any programs using these files";
|
|
848
|
+
} else if (errorMessage?.includes("ENOTEMPTY")) {
|
|
849
|
+
detail.error = "Directory not empty - may contain read-only files. Try using --force";
|
|
850
|
+
} else {
|
|
851
|
+
detail.error = errorMessage || "Unknown error during deletion";
|
|
852
|
+
}
|
|
853
|
+
}
|
|
833
854
|
}
|
|
834
855
|
detail.durationMs = Date.now() - startTime;
|
|
835
856
|
} catch (error) {
|
|
@@ -838,16 +859,55 @@ async function deleteNodeModules(nodeModules, options) {
|
|
|
838
859
|
}
|
|
839
860
|
return detail;
|
|
840
861
|
}
|
|
841
|
-
async function
|
|
862
|
+
async function forceDelete(dirPath) {
|
|
863
|
+
const isWindows = process.platform === "win32";
|
|
864
|
+
try {
|
|
865
|
+
await fs3.rm(dirPath, { recursive: true, force: true });
|
|
866
|
+
return;
|
|
867
|
+
} catch {}
|
|
868
|
+
try {
|
|
869
|
+
await makeWritableRecursive(dirPath);
|
|
870
|
+
await fs3.rm(dirPath, { recursive: true, force: true });
|
|
871
|
+
return;
|
|
872
|
+
} catch {}
|
|
873
|
+
try {
|
|
874
|
+
if (isWindows) {
|
|
875
|
+
const windowsPath = dirPath.length > 240 ? `\\\\?\\${resolve(dirPath)}` : dirPath;
|
|
876
|
+
await execAsync2(`rd /s /q "${windowsPath}"`, { timeout: 30000 });
|
|
877
|
+
} else {
|
|
878
|
+
await execAsync2(`rm -rf "${dirPath}"`, { timeout: 30000 });
|
|
879
|
+
}
|
|
880
|
+
return;
|
|
881
|
+
} catch {}
|
|
882
|
+
const tempPath = `${dirPath}.old.${Date.now()}`;
|
|
883
|
+
await fs3.rename(dirPath, tempPath);
|
|
884
|
+
try {
|
|
885
|
+
await fs3.rm(tempPath, { recursive: true, force: true });
|
|
886
|
+
} catch {}
|
|
887
|
+
}
|
|
888
|
+
async function makeWritableRecursive(dirPath) {
|
|
889
|
+
try {
|
|
890
|
+
const entries = await fs3.readdir(dirPath, { withFileTypes: true });
|
|
891
|
+
for (const entry of entries) {
|
|
892
|
+
const fullPath = join3(dirPath, entry.name);
|
|
893
|
+
if (entry.isDirectory()) {
|
|
894
|
+
await makeWritableRecursive(fullPath);
|
|
895
|
+
}
|
|
896
|
+
await fs3.chmod(fullPath, 511).catch(() => {});
|
|
897
|
+
}
|
|
898
|
+
await fs3.chmod(dirPath, 511).catch(() => {});
|
|
899
|
+
} catch {}
|
|
900
|
+
}
|
|
901
|
+
async function verifyNodeModules(dirPath) {
|
|
842
902
|
try {
|
|
843
|
-
const
|
|
844
|
-
if (
|
|
903
|
+
const baseName = dirPath.replace(/\\/g, "/").split("/").pop();
|
|
904
|
+
if (baseName !== "node_modules") {
|
|
845
905
|
return false;
|
|
846
906
|
}
|
|
847
|
-
const entries = await fs3.readdir(
|
|
907
|
+
const entries = await fs3.readdir(dirPath);
|
|
848
908
|
let hasSubdirs = false;
|
|
849
909
|
for (const entry of entries) {
|
|
850
|
-
const entryPath = join3(
|
|
910
|
+
const entryPath = join3(dirPath, entry);
|
|
851
911
|
try {
|
|
852
912
|
const stats = await fs3.stat(entryPath);
|
|
853
913
|
if (stats.isDirectory()) {
|
|
@@ -856,7 +916,8 @@ async function verifyNodeModules(path) {
|
|
|
856
916
|
}
|
|
857
917
|
} catch {}
|
|
858
918
|
}
|
|
859
|
-
const
|
|
919
|
+
const normalizedPath = dirPath.replace(/\\/g, "/");
|
|
920
|
+
const parentPath = normalizedPath.replace(/\/node_modules$/, "");
|
|
860
921
|
const hasPackageJson = await fileExists(join3(parentPath, "package.json"));
|
|
861
922
|
return hasSubdirs || hasPackageJson;
|
|
862
923
|
} catch {
|