Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

npkill

Package Overview
Dependencies
Maintainers
2
Versions
46
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

npkill - npm Package Compare versions

Comparing version 0.11.3 to 0.12.0

lib/ui/components/warning.ui.js

13

lib/constants/cli.constants.js

@@ -15,6 +15,11 @@ import colors from 'colors';

arg: ['-D', '--delete-all'],
description: 'CURRENTLY DISABLED. Automatically delete all node_modules folders that are found.',
description: 'Auto-delete all node_modules folders that are found.',
name: 'delete-all',
},
{
arg: ['-y'],
description: 'Avoid displaying a warning when executing --delete-all.',
name: 'yes',
},
{
arg: ['-e', '--hide-errors'],

@@ -65,2 +70,7 @@ description: 'Hide errors if any.',

{
arg: ['--dry-run'],
description: 'It does not delete anything (will simulate it with a random delay).',
name: 'dry-run',
},
{
arg: ['-v', '--version'],

@@ -79,2 +89,3 @@ description: 'Show version.',

🭲 home, end: move to the first and last result
🭲 o: open the parent directory of the selected result
🭲 e: show errors popup, next page`;

@@ -81,0 +92,0 @@ export const HELP_PROGRESSBAR = ` ------- PROGRESS BAR --------------------

17

lib/constants/main.constants.js

@@ -12,2 +12,3 @@ export const MIN_CLI_COLUMNS_SIZE = 60;

deleteAll: false,
dryRun: false,
exclude: ['.git'],

@@ -20,2 +21,3 @@ excludeHiddenDirectories: false,

targetFolder: 'node_modules',
yes: false,
};

@@ -34,12 +36,15 @@ export const MARGINS = {

export const UI_POSITIONS = {
FOLDER_SIZE_HEADER: { x: -1, y: 7 },
FOLDER_SIZE_HEADER: { x: -1, y: 7 }, // x is calculated in controller
INITIAL: { x: 0, y: 0 },
VERSION: { x: 38, y: 5 },
DRY_RUN_NOTICE: { x: 1, y: 6 },
NEW_UPDATE_FOUND: { x: 42, y: 0 },
SPACE_RELEASED: { x: 50, y: 4 },
STATUS: { x: 50, y: 5 },
STATUS_BAR: { x: 50, y: 6 },
TOTAL_SPACE: { x: 50, y: 3 },
ERRORS_COUNT: { x: 50, y: 2 },
SPACE_RELEASED: { x: 50, y: 3 },
STATUS: { x: 50, y: 4 },
STATUS_BAR: { x: 50, y: 5 },
PENDING_TASKS: { x: 50, y: 6 }, //Starting position. It will then be replaced.
TOTAL_SPACE: { x: 50, y: 2 },
ERRORS_COUNT: { x: 50, y: 1 },
TUTORIAL_TIP: { x: 1, y: 7 },
WARNINGS: { x: 0, y: 9 },
};

@@ -46,0 +51,0 @@ // export const VALID_KEYS: string[] = [

export const HELP_MSGS = {
BASIC_USAGE: '> CURSORS for select; SPACE to delete <',
BASIC_USAGE: ' CURSORS for select - SPACE to delete',
};

@@ -8,5 +8,3 @@ export const INFO_MSGS = {

ERROR_DELETING_FOLDER: '[ ERROR ] ',
DISABLED: '[-D, --delete-all] option has been disabled until future versions. ' +
'Please restart npkill without this option.',
HEADER_COLUMNS: 'last_mod size',
HEADER_COLUMNS: 'Last_mod Size',
HELP_TITLE: ' NPKILL HELP ',

@@ -21,8 +19,14 @@ MIN_CLI_CLOMUNS: 'Oh no! The terminal is too narrow. Please, ' +

STARTING: 'Initializing ',
SEARCHING: 'searching ',
CALCULATING_STATS: 'calculating stats ',
SEARCHING: 'Searching ',
CALCULATING_STATS: 'Calculating stats ',
FATAL_ERROR: 'Fatal error ',
SEARCH_COMPLETED: 'search completed ',
SPACE_RELEASED: 'space saved: ',
TOTAL_SPACE: 'releasable space: ',
SEARCH_COMPLETED: 'Search completed ',
SPACE_RELEASED: 'Space saved: ',
TOTAL_SPACE: 'Releasable space: ',
DRY_RUN: 'Dry run mode',
DELETE_ALL_WARNING: ' --delete-all may have undesirable effects and\n' +
' delete dependencies needed by some applications.\n' +
' Recommended to use -x and preview with --dry-run.\n\n' +
' Press y to continue.\n\n' +
' pass -y to not show this next time',
};

@@ -29,0 +33,0 @@ export const ERROR_MSG = {

@@ -7,3 +7,3 @@ import { DEFAULT_CONFIG, MIN_CLI_COLUMNS_SIZE, UI_POSITIONS, } from './constants/index.js';

import { FOLDER_SORT } from './constants/sort.result.js';
import { StatusUi, StatsUi, ResultsUi, LogsUi, HelpUi, HeaderUi, GeneralUi, } from './ui/index.js';
import { StatusUi, StatsUi, ResultsUi, LogsUi, HelpUi, HeaderUi, GeneralUi, WarningUi, } from './ui/index.js';
import _dirname from './dirname.js';

@@ -13,2 +13,3 @@ import colors from 'colors';

import path from 'path';
import openExplorer from 'open-file-explorer';
export class Controller {

@@ -34,2 +35,3 @@ logger;

uiLogs;
uiWarning;
activeComponent = null;

@@ -58,8 +60,23 @@ constructor(logger, searchStatus, fileService, spinnerService, consoleService, updateService, resultsService, uiService) {

this.setupEventsListener();
this.uiStatus.start();
if (this.config.checkUpdates) {
this.checkVersion();
}
if (this.config.deleteAll && !this.config.yes) {
this.showDeleteAllWarning();
this.uiWarning.confirm$
.pipe(tap(() => {
this.activeComponent = this.uiResults;
this.uiWarning.setDeleteAllWarningVisibility(false);
this.uiService.renderAll();
this.scan();
}))
.subscribe();
return;
}
this.scan();
}
showDeleteAllWarning() {
this.uiWarning.setDeleteAllWarningVisibility(true);
this.activeComponent = this.uiWarning;
}
initUi() {

@@ -78,2 +95,4 @@ this.uiHeader = new HeaderUi();

this.uiService.add(this.uiLogs);
this.uiWarning = new WarningUi();
this.uiService.add(this.uiWarning);
// Set Events

@@ -83,2 +102,3 @@ this.uiResults.delete$.subscribe((folder) => this.deleteFolder(folder));

this.uiLogs.close$.subscribe(() => this.showErrorPopup(false));
this.uiResults.openFolder$.subscribe((path) => openExplorer(path));
// Activate the main interactive component

@@ -98,4 +118,3 @@ this.activeComponent = this.uiResults;

if (options.isTrue('delete-all')) {
this.showObsoleteMessage();
process.exit();
this.config.deleteAll = true;
}

@@ -143,2 +162,9 @@ if (options.isTrue('sort-by')) {

}
if (options.isTrue('dry-run')) {
this.config.dryRun = true;
this.uiHeader.isDryRun = true;
}
if (options.isTrue('yes')) {
this.config.yes = true;
}
// Remove trailing slash from folderRoot for consistency

@@ -174,5 +200,2 @@ this.folderRoot = this.folderRoot.replace(/[/\\]$/, '');

}
showObsoleteMessage() {
this.uiService.print(INFO_MSGS.DISABLED);
}
setColor(color) {

@@ -289,2 +312,3 @@ if (this.isValidColor(color)) {

scan() {
this.uiStatus.start();
const params = this.prepareListDirParams();

@@ -323,3 +347,7 @@ const isExcludedDangerousDirectory = (path) => this.config.excludeHiddenDirectories &&

return this.calculateFolderStats(nodeFolder);
}, 2), tap(() => this.searchStatus.completeStatCalculation()))
}, 2), tap(() => this.searchStatus.completeStatCalculation()), tap((folder) => {
if (this.config.deleteAll) {
this.deleteFolder(folder);
}
}))
.subscribe({

@@ -351,3 +379,3 @@ next: () => this.printFoldersSection(),

nodeFolder.modificationTime = -1;
return;
return nodeFolder;
}

@@ -358,2 +386,3 @@ const parentFolder = path.join(nodeFolder.path, '../');

this.logger.info(`Last mod. of ${nodeFolder.path}: ${result}`);
return nodeFolder;
}), tap(() => {

@@ -417,8 +446,15 @@ this.finishFolderStats();

folder.status = 'deleting';
this.searchStatus.pendingDeletions++;
this.uiStatus.render();
this.printFoldersSection();
this.fileService
.deleteDir(folder.path)
const deleteFunction = this.config
.dryRun
? this.fileService.fakeDeleteDir
: this.fileService.deleteDir;
deleteFunction(folder.path)
.then(() => {
folder.status = 'deleted';
this.searchStatus.pendingDeletions--;
this.uiStats.render();
this.uiStatus.render();
this.printFoldersSection();

@@ -429,2 +465,4 @@ this.logger.info(`Deleted ${folder.path}`);

folder.status = 'error-deleting';
this.searchStatus.pendingDeletions--;
this.uiStatus.render();
this.printFoldersSection();

@@ -431,0 +469,0 @@ this.newError(e.message);

@@ -7,2 +7,3 @@ export class SearchStatus {

resultsFound = 0;
pendingDeletions = 0;
workerStatus = 'stopped';

@@ -9,0 +10,0 @@ workersJobs;

import fs, { accessSync, readFileSync, statSync } from 'fs';
import { readdir, stat } from 'fs/promises';
export class FileService {
/** Used for dry-run or testing. */
async fakeDeleteDir(_path) {
const randomDelay = Math.floor(Math.random() * 4000 + 200);
await new Promise((r) => setTimeout(r, randomDelay));
return true;
}
isValidRootFolder(path) {

@@ -5,0 +11,0 @@ let stat;

@@ -6,11 +6,15 @@ import { BANNER, UI_POSITIONS, HELP_MSGS, INFO_MSGS, DEFAULT_SIZE, } from '../../../constants/index.js';

programVersion;
isDryRun;
render() {
// banner and tutorial
this.printAt(BANNER, UI_POSITIONS.INITIAL);
this.printAt(colors.yellow(colors.inverse(HELP_MSGS.BASIC_USAGE)), UI_POSITIONS.TUTORIAL_TIP);
this.renderHeader();
if (this.programVersion !== undefined) {
this.printAt(colors.gray(this.programVersion), UI_POSITIONS.VERSION);
}
if (this.isDryRun) {
this.printAt(colors.black(colors.bgMagenta(` ${INFO_MSGS.DRY_RUN} `)), UI_POSITIONS.DRY_RUN_NOTICE);
}
// Columns headers
this.printAt(colors.gray(INFO_MSGS.HEADER_COLUMNS), {
this.printAt(colors.bgYellow(colors.black(INFO_MSGS.HEADER_COLUMNS)), {
x: this.terminal.columns - INFO_MSGS.HEADER_COLUMNS.length - 4,

@@ -23,2 +27,8 @@ y: UI_POSITIONS.FOLDER_SIZE_HEADER.y,

}
renderHeader() {
const { columns } = this.terminal;
const spaceToFill = Math.max(0, columns - HELP_MSGS.BASIC_USAGE.length - 2);
const text = HELP_MSGS.BASIC_USAGE + ' '.repeat(spaceToFill);
this.printAt(colors.yellow(colors.inverse(text)), UI_POSITIONS.TUTORIAL_TIP);
}
}

@@ -8,2 +8,10 @@ import { UI_POSITIONS, INFO_MSGS } from '../../../constants/index.js';

logger;
lastValues = {
totalSpace: '',
spaceReleased: '',
};
timeouts = {
totalSpace: setTimeout(() => { }),
spaceReleased: setTimeout(() => { }),
};
constructor(config, resultsService, logger) {

@@ -17,8 +25,16 @@ super();

const { totalSpace, spaceReleased } = this.resultsService.getStats();
const totalSpacePosition = { ...UI_POSITIONS.TOTAL_SPACE };
const spaceReleasedPosition = { ...UI_POSITIONS.SPACE_RELEASED };
totalSpacePosition.x += INFO_MSGS.TOTAL_SPACE.length;
spaceReleasedPosition.x += INFO_MSGS.SPACE_RELEASED.length;
this.printAt(totalSpace, totalSpacePosition);
this.printAt(spaceReleased, spaceReleasedPosition);
this.showStat({
description: INFO_MSGS.TOTAL_SPACE,
value: totalSpace,
lastValueKey: 'totalSpace',
position: UI_POSITIONS.TOTAL_SPACE,
updateColor: 'yellow',
});
this.showStat({
description: INFO_MSGS.SPACE_RELEASED,
value: spaceReleased,
lastValueKey: 'spaceReleased',
position: UI_POSITIONS.SPACE_RELEASED,
updateColor: 'green',
});
if (this.config.showErrors) {

@@ -28,2 +44,26 @@ this.showErrorsCount();

}
/** Print the value of the stat and if it is a different value from the
* previous run, highlight it for a while.
*/
showStat({ description, value, lastValueKey, position, updateColor, }) {
if (value === this.lastValues[lastValueKey]) {
return;
}
const statPosition = { ...position };
statPosition.x += description.length;
// If is first render, initialize.
if (!this.lastValues[lastValueKey]) {
this.printAt(value, statPosition);
this.lastValues[lastValueKey] = value;
return;
}
this.printAt(colors[updateColor](`${value} ▲`), statPosition);
if (this.timeouts[lastValueKey]) {
clearTimeout(this.timeouts[lastValueKey]);
}
this.timeouts[lastValueKey] = setTimeout(() => {
this.printAt(value + ' ', statPosition);
}, 700);
this.lastValues[lastValueKey] = value;
}
showErrorsCount() {

@@ -30,0 +70,0 @@ const errors = this.logger.get('error').length;

@@ -14,2 +14,4 @@ import { BaseUi } from '../../base.ui.js';

barClosing = false;
showProgressBar = true;
pendingTasksPosition = { ...UI_POSITIONS.PENDING_TASKS };
searchEnd$ = new Subject();

@@ -46,4 +48,20 @@ SEARCH_STATES = {

this.printAt(this.text, UI_POSITIONS.STATUS);
this.renderProgressBar();
if (this.showProgressBar) {
this.renderProgressBar();
}
this.renderPendingTasks();
}
renderPendingTasks() {
this.clearPendingTasks();
if (this.searchStatus.pendingDeletions === 0) {
return;
}
const { pendingDeletions } = this.searchStatus;
const text = pendingDeletions > 1 ? 'pending tasks' : 'pending task ';
this.printAt(colors.yellow(`${pendingDeletions} ${text}`), this.pendingTasksPosition);
}
clearPendingTasks() {
const PENDING_TASK_LENGHT = 17;
this.printAt(' '.repeat(PENDING_TASK_LENGHT), this.pendingTasksPosition);
}
renderProgressBar() {

@@ -62,10 +80,6 @@ const { pendingSearchTasks, completedSearchTasks, completedStatsCalculation, pendingStatsCalculation, } = this.searchStatus;

const barSearchMax = pendingSearchTasks + completedSearchTasks;
const barStatsMax = completedStatsCalculation + pendingStatsCalculation;
let barLenght = proportional(barSearchMax, BAR_WIDTH, barSearchMax);
if (barLenght === 0) {
barLenght = BAR_WIDTH;
}
barLenght = Math.floor(barLenght * modifier);
const barStatsMax = pendingStatsCalculation + completedStatsCalculation;
let barLenght = Math.ceil(BAR_WIDTH * modifier);
let searchBarLenght = proportional(completedSearchTasks, BAR_WIDTH, barSearchMax);
searchBarLenght = Math.floor(searchBarLenght * modifier);
searchBarLenght = Math.ceil(searchBarLenght * modifier);
let doneBarLenght = proportional(completedStatsCalculation, searchBarLenght, barStatsMax);

@@ -98,2 +112,4 @@ doneBarLenght = Math.floor(doneBarLenght * modifier);

this.barNormalizedWidth = 0;
this.showProgressBar = false;
this.movePendingTaskToTop();
return;

@@ -105,6 +121,14 @@ }

}
/** When the progress bar disappears, "pending tasks" will move up one
position. */
movePendingTaskToTop() {
this.clearPendingTasks();
this.pendingTasksPosition = { ...UI_POSITIONS.STATUS_BAR };
this.renderPendingTasks();
}
printProgressBar(progressBar) {
if (this.barClosing) {
const postX = Math.round(UI_POSITIONS.STATUS_BAR.x +
(BAR_WIDTH / 2) * (1 - this.barNormalizedWidth));
const postX = UI_POSITIONS.STATUS_BAR.x -
1 +
Math.round((BAR_WIDTH / 2) * (1 - this.barNormalizedWidth));
// Clear previus bar

@@ -111,0 +135,0 @@ this.printAt(' '.repeat(BAR_WIDTH), UI_POSITIONS.STATUS_BAR);

@@ -6,2 +6,3 @@ import { DECIMALS_SIZE, DEFAULT_CONFIG, MARGINS, OVERFLOW_CUT_FROM, } from '../../constants/main.constants.js';

import colors from 'colors';
import { resolve } from 'node:path';
export class ResultsUi extends HeavyUi {

@@ -17,2 +18,3 @@ resultsService;

showErrors$ = new Subject();
openFolder$ = new Subject();
config = DEFAULT_CONFIG;

@@ -35,2 +37,3 @@ KEYS = {

e: () => this.showErrorsPopup(),
o: () => this.openFolder(),
};

@@ -43,2 +46,7 @@ constructor(resultsService, consoleService, fileService) {

}
openFolder() {
const folder = this.resultsService.results[this.resultIndex];
const parentPath = resolve(folder.path, '..');
this.openFolder$.next(parentPath);
}
onKeyInput({ name }) {

@@ -45,0 +53,0 @@ const action = this.KEYS[name];

@@ -6,2 +6,3 @@ export * from './base.ui.js';

export * from './components/logs.ui.js';
export * from './components/warning.ui.js';
export * from './components/results.ui.js';

@@ -8,0 +9,0 @@ export * from './components/header/header.ui.js';

{
"name": "npkill",
"version": "0.11.3",
"version": "0.12.0",
"description": "List any node_modules directories in your system, as well as the space they take up. You can then select which ones you want to erase to free up space.",

@@ -50,40 +50,41 @@ "exports": "./lib/index.js",

"dependencies": {
"ansi-escapes": "^6.0.0",
"ansi-escapes": "^6.2.1",
"colors": "1.4.0",
"get-folder-size": "^2.0.0",
"node-emoji": "^1.10.0",
"rxjs": "^7.5.7"
"get-folder-size": "^4.0.0",
"node-emoji": "^2.1.3",
"open-file-explorer": "^1.0.2",
"rxjs": "^7.8.1"
},
"devDependencies": {
"@commitlint/config-conventional": "^17.1.0",
"@stryker-mutator/core": "^6.2.2",
"@stryker-mutator/jest-runner": "^6.2.2",
"@commitlint/config-conventional": "^19.2.2",
"@stryker-mutator/core": "^8.2.6",
"@stryker-mutator/jest-runner": "^8.2.6",
"@types/colors": "^1.2.1",
"@types/gulp": "^4.0.9",
"@types/jest": "^29.1.2",
"@types/node": "^18.15.10",
"@types/gulp": "^4.0.17",
"@types/jest": "^29.5.12",
"@types/node": "^20.12.7",
"@types/rimraf": "^3.0.2",
"@typescript-eslint/eslint-plugin": "^5.56.0",
"commitlint": "^17.1.2",
"del": "^7.0.0",
"eslint": "^8.36.0",
"eslint-config-prettier": "^8.8.0",
"@typescript-eslint/eslint-plugin": "^5.62.0",
"commitlint": "^19.2.2",
"del": "^7.1.0",
"eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0",
"eslint-config-standard-with-typescript": "^34.0.1",
"eslint-plugin-import": "^2.27.5",
"eslint-plugin-n": "^15.6.1",
"eslint-plugin-import": "^2.29.1",
"eslint-plugin-n": "^15.7.0",
"eslint-plugin-promise": "^6.1.1",
"gulp": "^4.0.2",
"gulp": "^5.0.0",
"gulp-typescript": "^6.0.0-alpha.1",
"husky": "^8.0.0",
"jest": "^29.1.2",
"lint-staged": "^13.0.3",
"np": "^7.6.3",
"husky": "^9.0.11",
"jest": "^29.7.0",
"lint-staged": "^15.2.2",
"np": "^10.0.3",
"pre-commit": "^1.2.2",
"prettier": "^2.7.1",
"rimraf": "^3.0.2",
"prettier": "^3.2.5",
"rimraf": "^5.0.5",
"stryker-cli": "^1.0.2",
"ts-jest": "^29.0.3",
"ts-node": "^10.9.1",
"ts-jest": "^29.1.2",
"ts-node": "^10.9.2",
"tslint": "^6.1.0",
"typescript": "^4.9.5"
"typescript": "^5.4.5"
},

@@ -90,0 +91,0 @@ "husky": {

@@ -83,2 +83,4 @@ <p align="center">

Puedes abrir el directorio donde se aloja el resultado seleccionado pulsando <kbd>o</kbd>.
Para salir de Npkill, utiliza <kbd>Q</kbd>, o si te sientes valiente, <kbd>Ctrl</kbd> + <kbd>c</kbd>.

@@ -96,3 +98,3 @@

| -d, --directory | Permite seleccionar el directorio desde el que comienza la búsqueda. Por defecto, se empieza en . |
| -D, --delete-all | ACTUALMENTE DESHABILITADA. Borra automáticamente todos los node_modules que se encuentran |
| -D, --delete-all | Borra automáticamente todos los node_modules que se encuentren. Recomendable utilizar junto a `-x` |
| -e, --hide-errors | Esconde los errores en el caso de que ocurra alguno |

@@ -107,2 +109,3 @@ | -E, --exclude | Excluye directorios de la búsqueda (la lista de directorios debe estar entre comillas dobles "", cada directorio separado por ',' Ejemplo: "ignore1, ignore2") |

| -x, --exclude-hidden-directories | Excluye directorios ocultos (directorios "dot") de la búsqueda |
| --dry-run | No borra nada (simula un tiempo de borrado aleatorio) |
| -v, --version | Muestra la versión de Npkill |

@@ -153,3 +156,2 @@

```bash
# Deshabilitado por razones de seguridad (puedes utilizar esta opción en la versión 0.2.4 bajo tu propia responsabilidad)
npkill -d ~/backups/ --delete-all

@@ -156,0 +158,0 @@ ```

@@ -86,4 +86,6 @@ <p align="center">

Move between the listed folders with <kbd>↓</kbd> <kbd>↑</kbd>, and use <kbd>Space</kbd> or <kbd>Del</kbd> to delete the selected folder.
You can also use <kbd>j</kbd> and <kbd>k</kbd> to move between the results
You can also use <kbd>j</kbd> and <kbd>k</kbd> to move between the results.
You can open the directory where the selected result is placed by pressing <kbd>o</kbd>.
To exit, <kbd>Q</kbd> or <kbd>Ctrl</kbd> + <kbd>c</kbd> if you're brave.

@@ -101,3 +103,3 @@

| -d, --directory | Set the directory from which to begin searching. By default, starting-point is . |
| -D, --delete-all | CURRENTLY DISABLED. Automatically delete all node_modules folders that are found |
| -D, --delete-all | Automatically delete all node_modules folders that are found. Suggested to be used together with `-x`. |
| -e, --hide-errors | Hide errors if any |

@@ -112,2 +114,3 @@ | -E, --exclude | Exclude directories from search (directory list must be inside double quotes "", each directory separated by ',' ) Example: "ignore1, ignore2" |

| -x, --exclude-hidden-directories | Exclude hidden directories ("dot" directories) from search. |
| --dry-run | It does not delete anything (will simulate it with a random delay). |
| -v, --version | Show npkill version |

@@ -158,3 +161,2 @@

```bash
# Disabled for security reasons (you can use it in version 0.2.4 at your risk)
npkill -d ~/backups/ --delete-all

@@ -161,0 +163,0 @@ ```

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc