From 4002d3c387bae476ad90cacb182b05820ddfdc5e Mon Sep 17 00:00:00 2001 From: zaldih Date: Mon, 10 Oct 2022 18:01:46 +0200 Subject: [PATCH 01/14] feat: add days since the project was worked on -technical proof- Each result is assigned the modification time of the most recent file in the workspace (parent directory of the node_module) and the days that have elapsed will be displayed --- src/controller.ts | 65 +++++++++++++++++++----- src/interfaces/file-service.interface.ts | 8 +++ src/interfaces/folder.interface.ts | 1 + src/services/files.service.ts | 40 ++++++++++++++- 4 files changed, 99 insertions(+), 15 deletions(-) diff --git a/src/controller.ts b/src/controller.ts index 5d7f7fb2..dc413a7b 100644 --- a/src/controller.ts +++ b/src/controller.ts @@ -53,6 +53,7 @@ import { bufferUntil } from './libs/buffer-until.js'; import colors from 'colors'; import keypress from 'keypress'; import __dirname from './dirname.js'; +import path from 'path'; export class Controller { private folderRoot = ''; @@ -278,6 +279,15 @@ export class Controller { /////////////////////////// // folder size header + this.printAt(colors.gray('days since ↓'), { + x: + this.stdout.columns - + 14 - + (MARGINS.FOLDER_SIZE_COLUMN + + Math.round(INFO_MSGS.HEADER_SIZE_COLUMN.length / 5)), + y: UI_POSITIONS.FOLDER_SIZE_HEADER.y, + }); + this.printAt(colors.gray(INFO_MSGS.HEADER_SIZE_COLUMN), { x: this.stdout.columns - @@ -347,12 +357,13 @@ export class Controller { } private printFolderRow(folder: IFolder, row: number) { - let { path, size } = this.getFolderTexts(folder); + let { path, lastModification, size } = this.getFolderTexts(folder); const isRowSelected = row === this.getRealCursorPosY(); - if (isRowSelected) { path = colors[this.config.backgroundColor](path); size = colors[this.config.backgroundColor](size); + lastModification = colors[this.config.backgroundColor](lastModification); + this.paintBgRow(row); } @@ -364,15 +375,31 @@ export class Controller { y: row, }); + this.printAt(lastModification, { + x: this.stdout.columns - MARGINS.FOLDER_SIZE_COLUMN - 8, + y: row, + }); + this.printAt(size, { x: this.stdout.columns - MARGINS.FOLDER_SIZE_COLUMN, y: row, }); } - private getFolderTexts(folder: IFolder): { path: string; size: string } { + private getFolderTexts(folder: IFolder): { + path: string; + size: string; + lastModification: string; + } { const folderText = this.getFolderPathText(folder); let folderSize = `${folder.size.toFixed(DECIMALS_SIZE)} GB`; + let daysSinceLastModification = folder.modificationTime + ? Math.floor( + (new Date().getTime() / 1000 - folder.modificationTime) / 86400, + ) + 'd' + : '--'; + + if (folder.isDangerous) daysSinceLastModification = ''; if (!this.config.folderSizeInGB) { const size = this.fileService.convertGBToMB(folder.size); @@ -384,6 +411,7 @@ export class Controller { return { path: folderText, size: folderSizeText, + lastModification: daysSinceLastModification, }; } @@ -532,12 +560,15 @@ export class Controller { from(this.consoleService.splitData(dataFolder)), ), filter((path) => !!path), - map((path) => ({ - path, - size: 0, - isDangerous: this.fileService.isDangerous(path), - status: 'live', - })), + map((path) => { + return { + path, + size: 0, + modificationTime: 0, + isDangerous: this.fileService.isDangerous(path), + status: 'live', + }; + }), tap((nodeFolder) => { this.resultsService.addResult(nodeFolder); @@ -576,10 +607,18 @@ export class Controller { messages.map((msg) => this.printError(msg)); } - private calculateFolderStats(nodeFolder: IFolder): Observable { - return this.fileService - .getFolderSize(nodeFolder.path) - .pipe(tap((size) => this.finishFolderStats(nodeFolder, size))); + private calculateFolderStats(nodeFolder: IFolder): Observable { + return this.fileService.getFolderSize(nodeFolder.path).pipe( + tap((size) => this.finishFolderStats(nodeFolder, size)), + tap(async () => { + // Saves resources by not scanning a result that is probably not of interest + if (nodeFolder.isDangerous) return; + const parentFolder = path.join(nodeFolder.path, '../'); + const result = await this.fileService.getProjectLastUsage(parentFolder); + nodeFolder.modificationTime = result; + this.printFoldersSection(); + }), + ); } private finishFolderStats(folder: IFolder, size: string): void { diff --git a/src/interfaces/file-service.interface.ts b/src/interfaces/file-service.interface.ts index 88971d5b..d9d5fdc8 100644 --- a/src/interfaces/file-service.interface.ts +++ b/src/interfaces/file-service.interface.ts @@ -11,4 +11,12 @@ export interface IFileService { getFileContent(path: string): string; isSafeToDelete(path: string, targetFolder: string): boolean; isDangerous(path: string): boolean; + getFileList(path: string): any; + getProjectLastUsage(path: string): Promise; + getFileList(dirname: string): Promise; +} + +export interface IFileList { + path: string; + modificationTime: number; } diff --git a/src/interfaces/folder.interface.ts b/src/interfaces/folder.interface.ts index 52eaee9b..6bb2872a 100644 --- a/src/interfaces/folder.interface.ts +++ b/src/interfaces/folder.interface.ts @@ -1,6 +1,7 @@ export interface IFolder { path: string; size: number; + modificationTime: number; isDangerous: boolean; status: 'live' | 'deleting' | 'error-deleting' | 'deleted'; } diff --git a/src/services/files.service.ts b/src/services/files.service.ts index 149c9536..5a9ec650 100644 --- a/src/services/files.service.ts +++ b/src/services/files.service.ts @@ -1,7 +1,12 @@ -import { IFileService, IListDirParams } from '../interfaces/index.js'; +import { + IFileList, + IFileService, + IListDirParams, +} from '../interfaces/index.js'; import { Observable } from 'rxjs'; -import { readFileSync } from 'fs'; +import { readFileSync, statSync } from 'fs'; +import { readdir, stat } from 'fs/promises'; export abstract class FileService implements IFileService { abstract getFolderSize(path: string): Observable; @@ -43,4 +48,35 @@ export abstract class FileService implements IFileService { const hiddenFilePattern = /(^|\/)\.[^\/\.]/g; return hiddenFilePattern.test(path); } + + async getProjectLastUsage(path: string): Promise { + const files = await this.getFileList(path); + const sorted = files.sort( + (a, b) => b.modificationTime - a.modificationTime, + ); + return sorted[0]?.modificationTime || null; + } + + async getFileList(dirname: string): Promise { + let files: IFileList[] = []; + const items = await readdir(dirname, { withFileTypes: true }); + + for (const item of items) { + try { + if (item.isDirectory()) { + if (item.name === 'node_modules') continue; + files = [ + ...files, + ...(await this.getFileList(`${dirname}/${item.name}`)), + ]; + } else { + const path = `${dirname}/${item.name}`; + const lastModify = (await stat(path)).mtime; + files.push({ path, modificationTime: lastModify.getTime() / 1000 }); + } + } catch (error) {} + } + + return files; + } } From 491f45e6828b431df932958f86ebba635ae212e9 Mon Sep 17 00:00:00 2001 From: zaldih Date: Fri, 14 Oct 2022 23:14:28 +0200 Subject: [PATCH 02/14] test(results): add missing parameter --- __tests__/result.service.test.ts | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/__tests__/result.service.test.ts b/__tests__/result.service.test.ts index aeea3e79..12ab45de 100644 --- a/__tests__/result.service.test.ts +++ b/__tests__/result.service.test.ts @@ -13,6 +13,7 @@ describe('Result Service', () => { path: 'path', size: 5, status: 'live', + modificationTime: 0, isDangerous: false, }; const resultExpected = [newResult]; @@ -25,18 +26,21 @@ describe('Result Service', () => { path: 'path', size: 1, status: 'live', + modificationTime: 0, isDangerous: false, }, { path: 'path2', size: 2, status: 'deleted', + modificationTime: 0, isDangerous: false, }, { path: 'path3', size: 3, status: 'live', + modificationTime: 0, isDangerous: false, }, ]; @@ -56,36 +60,42 @@ describe('Result Service', () => { path: 'pathd', size: 5, status: 'live', + modificationTime: 0, isDangerous: false, }, { path: 'patha', size: 6, status: 'live', + modificationTime: 0, isDangerous: false, }, { path: 'pathc', size: 16, status: 'live', + modificationTime: 0, isDangerous: false, }, { path: 'pathcc', size: 0, status: 'deleted', + modificationTime: 0, isDangerous: false, }, { path: 'pathb', size: 3, status: 'deleted', + modificationTime: 0, isDangerous: false, }, { path: 'pathz', size: 8, status: 'live', + modificationTime: 0, isDangerous: false, }, ]; @@ -99,36 +109,42 @@ describe('Result Service', () => { path: 'patha', size: 6, status: 'live', + modificationTime: 0, isDangerous: false, }, { path: 'pathb', size: 3, status: 'deleted', + modificationTime: 0, isDangerous: false, }, { path: 'pathc', size: 16, status: 'live', + modificationTime: 0, isDangerous: false, }, { path: 'pathcc', size: 0, status: 'deleted', + modificationTime: 0, isDangerous: false, }, { path: 'pathd', size: 5, status: 'live', + modificationTime: 0, isDangerous: false, }, { path: 'pathz', size: 8, status: 'live', + modificationTime: 0, isDangerous: false, }, ]; @@ -189,36 +205,42 @@ describe('Result Service', () => { path: 'pathd', size: 5, status: 'live', + modificationTime: 0, isDangerous: false, }, { path: 'patha', size: 6, status: 'deleted', + modificationTime: 0, isDangerous: false, }, { path: 'pathc', size: 16, status: 'live', + modificationTime: 0, isDangerous: false, }, { path: 'pathcc', size: 0, status: 'deleted', + modificationTime: 0, isDangerous: false, }, { path: 'pathb', size: 3, status: 'deleted', + modificationTime: 0, isDangerous: false, }, { path: 'pathz', size: 8, status: 'live', + modificationTime: 0, isDangerous: false, }, ]; From 88194e16a16342024564e8246431e07d0ae33f5e Mon Sep 17 00:00:00 2001 From: zaldih Date: Sat, 15 Oct 2022 11:25:03 +0200 Subject: [PATCH 03/14] feat(results): sort by date --- src/constants/cli.constants.ts | 3 ++- src/constants/messages.constants.ts | 2 +- src/constants/sort.result.ts | 5 +++++ src/controller.ts | 11 ++++++----- 4 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/constants/cli.constants.ts b/src/constants/cli.constants.ts index 46cb2832..3d746f4a 100644 --- a/src/constants/cli.constants.ts +++ b/src/constants/cli.constants.ts @@ -53,7 +53,8 @@ export const OPTIONS: ICliOptions[] = [ }, { arg: ['-s', '--sort'], - description: 'Sort results by: size or path', + description: + 'Sort results by: size, path or date (last time the most recent file was modified in the workspace)', name: 'sort-by', }, { diff --git a/src/constants/messages.constants.ts b/src/constants/messages.constants.ts index f6fd1d9b..9e01041e 100644 --- a/src/constants/messages.constants.ts +++ b/src/constants/messages.constants.ts @@ -18,7 +18,7 @@ export const INFO_MSGS = { NO_TTY: // tslint:disable-next-line: max-line-length 'Oh no! Npkill does not support this terminal (TTY is required). This is a bug, which has to be fixed. Please try another command interpreter (for example, CMD in windows)', - NO_VALID_SORT_NAME: 'Invalid sort option. Available: path | size', + NO_VALID_SORT_NAME: 'Invalid sort option. Available: path | size | date', SEARCHING: 'searching ', SEARCH_COMPLETED: 'search completed ', SPACE_RELEASED: 'space saved: ', diff --git a/src/constants/sort.result.ts b/src/constants/sort.result.ts index 94e86571..f6180fd7 100644 --- a/src/constants/sort.result.ts +++ b/src/constants/sort.result.ts @@ -3,4 +3,9 @@ import { IFolder } from '../interfaces/folder.interface.js'; export const FOLDER_SORT = { path: (a: IFolder, b: IFolder) => (a.path > b.path ? 1 : -1), size: (a: IFolder, b: IFolder) => (a.size < b.size ? 1 : -1), + date: (a: IFolder, b: IFolder) => { + if (b.isDangerous || !b.modificationTime) return -1; + if (!a.isDangerous || a.modificationTime) return 1; + return a.modificationTime - b.modificationTime; + }, }; diff --git a/src/controller.ts b/src/controller.ts index f26533cb..bc0d374e 100644 --- a/src/controller.ts +++ b/src/controller.ts @@ -613,21 +613,22 @@ export class Controller { private calculateFolderStats(nodeFolder: IFolder): Observable { return this.fileService.getFolderSize(nodeFolder.path).pipe( - tap((size) => this.finishFolderStats(nodeFolder, size)), + tap((size) => (nodeFolder.size = this.transformFolderSize(size))), tap(async () => { // Saves resources by not scanning a result that is probably not of interest if (nodeFolder.isDangerous) return; const parentFolder = path.join(nodeFolder.path, '../'); const result = await this.fileService.getProjectLastUsage(parentFolder); nodeFolder.modificationTime = result; - this.printFoldersSection(); }), + tap(() => this.finishFolderStats()), ); } - private finishFolderStats(folder: IFolder, size: string): void { - folder.size = this.transformFolderSize(size); - if (this.config.sortBy === 'size') { + private finishFolderStats(): void { + const needSort = + this.config.sortBy === 'size' || this.config.sortBy === 'date'; + if (needSort) { this.resultsService.sortResults(this.config.sortBy); this.clearFolderSection(); } From 196dc1b2833a9bff3b1ce439fdfbb69fb50ef6d6 Mon Sep 17 00:00:00 2001 From: zaldih Date: Tue, 18 Oct 2022 15:42:42 +0200 Subject: [PATCH 04/14] fix(sort): fix date sort method --- src/constants/sort.result.ts | 2 +- src/controller.ts | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/constants/sort.result.ts b/src/constants/sort.result.ts index f6180fd7..7729dd53 100644 --- a/src/constants/sort.result.ts +++ b/src/constants/sort.result.ts @@ -5,7 +5,7 @@ export const FOLDER_SORT = { size: (a: IFolder, b: IFolder) => (a.size < b.size ? 1 : -1), date: (a: IFolder, b: IFolder) => { if (b.isDangerous || !b.modificationTime) return -1; - if (!a.isDangerous || a.modificationTime) return 1; + if (a.isDangerous || !a.modificationTime) return 1; return a.modificationTime - b.modificationTime; }, }; diff --git a/src/controller.ts b/src/controller.ts index bc0d374e..5a362199 100644 --- a/src/controller.ts +++ b/src/controller.ts @@ -43,6 +43,7 @@ import { filter, map, mergeMap, + switchMap, takeUntil, tap, } from 'rxjs/operators'; @@ -614,7 +615,7 @@ export class Controller { private calculateFolderStats(nodeFolder: IFolder): Observable { return this.fileService.getFolderSize(nodeFolder.path).pipe( tap((size) => (nodeFolder.size = this.transformFolderSize(size))), - tap(async () => { + switchMap(async () => { // Saves resources by not scanning a result that is probably not of interest if (nodeFolder.isDangerous) return; const parentFolder = path.join(nodeFolder.path, '../'); From a74339c3966b3d29edb2152a02092b914f9a5b62 Mon Sep 17 00:00:00 2001 From: zaldih Date: Fri, 28 Oct 2022 17:55:58 +0200 Subject: [PATCH 05/14] style: improve columns --- src/constants/main.constants.ts | 4 ++-- src/constants/messages.constants.ts | 2 +- src/controller.ts | 13 ++++++------- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/constants/main.constants.ts b/src/constants/main.constants.ts index 463b9596..7951f983 100644 --- a/src/constants/main.constants.ts +++ b/src/constants/main.constants.ts @@ -5,7 +5,7 @@ export const CURSOR_SIMBOL = '~>'; export const WIDTH_OVERFLOW = '...'; export const DEFAULT_SIZE = '0 MB'; export const DECIMALS_SIZE = 2; -export const OVERFLOW_CUT_FROM = 8; +export const OVERFLOW_CUT_FROM = 11; export const DEFAULT_CONFIG: IConfig = { backgroundColor: 'bgBlue', @@ -21,7 +21,7 @@ export const DEFAULT_CONFIG: IConfig = { }; export const MARGINS = { - FOLDER_COLUMN_END: 16, + FOLDER_COLUMN_END: 19, FOLDER_COLUMN_START: 2, FOLDER_SIZE_COLUMN: 10, ROW_RESULTS_START: 8, diff --git a/src/constants/messages.constants.ts b/src/constants/messages.constants.ts index 9e01041e..1d91f6e0 100644 --- a/src/constants/messages.constants.ts +++ b/src/constants/messages.constants.ts @@ -9,7 +9,7 @@ export const INFO_MSGS = { DISABLED: '[-D, --delete-all] option has been disabled until future versions. Please restart npkill without this option.', - HEADER_SIZE_COLUMN: 'folder size', + HEADER_SIZE_COLUMN: 'size', HELP_TITLE: ' NPKILL HELP ', MIN_CLI_CLOMUNS: 'Oh no! The terminal is too narrow. Please, ' + diff --git a/src/controller.ts b/src/controller.ts index 5a362199..adf0b0e1 100644 --- a/src/controller.ts +++ b/src/controller.ts @@ -284,20 +284,17 @@ export class Controller { /////////////////////////// // folder size header - this.printAt(colors.gray('days since ↓'), { + this.printAt(colors.gray('old |'), { x: this.stdout.columns - - 14 - + 5 - (MARGINS.FOLDER_SIZE_COLUMN + Math.round(INFO_MSGS.HEADER_SIZE_COLUMN.length / 5)), y: UI_POSITIONS.FOLDER_SIZE_HEADER.y, }); this.printAt(colors.gray(INFO_MSGS.HEADER_SIZE_COLUMN), { - x: - this.stdout.columns - - (MARGINS.FOLDER_SIZE_COLUMN + - Math.round(INFO_MSGS.HEADER_SIZE_COLUMN.length / 5)), + x: this.stdout.columns - MARGINS.FOLDER_SIZE_COLUMN + 2, y: UI_POSITIONS.FOLDER_SIZE_HEADER.y, }); @@ -364,6 +361,8 @@ export class Controller { private printFolderRow(folder: IFolder, row: number) { let { path, lastModification, size } = this.getFolderTexts(folder); const isRowSelected = row === this.getRealCursorPosY(); + + lastModification = colors.gray(lastModification); if (isRowSelected) { path = colors[this.config.backgroundColor](path); size = colors[this.config.backgroundColor](size); @@ -381,7 +380,7 @@ export class Controller { }); this.printAt(lastModification, { - x: this.stdout.columns - MARGINS.FOLDER_SIZE_COLUMN - 8, + x: this.stdout.columns - MARGINS.FOLDER_SIZE_COLUMN - 6, y: row, }); From 1107962679c39153dbe54bba9ecc6c592b6d770f Mon Sep 17 00:00:00 2001 From: zaldih Date: Fri, 28 Oct 2022 18:45:40 +0200 Subject: [PATCH 06/14] fix: fix sort by date --- src/constants/sort.result.ts | 12 ++++++++++-- src/controller.ts | 21 ++++++++++++++------- src/interfaces/folder.interface.ts | 1 + src/services/files.service.ts | 2 +- 4 files changed, 26 insertions(+), 10 deletions(-) diff --git a/src/constants/sort.result.ts b/src/constants/sort.result.ts index 7729dd53..ed2ee0f3 100644 --- a/src/constants/sort.result.ts +++ b/src/constants/sort.result.ts @@ -4,8 +4,16 @@ export const FOLDER_SORT = { path: (a: IFolder, b: IFolder) => (a.path > b.path ? 1 : -1), size: (a: IFolder, b: IFolder) => (a.size < b.size ? 1 : -1), date: (a: IFolder, b: IFolder) => { - if (b.isDangerous || !b.modificationTime) return -1; - if (a.isDangerous || !a.modificationTime) return 1; + // const idOrder = a.id - b.id; + // if (b.isDangerous || !b.modificationTime) { + // return idOrder; + // // return -1; + // } + + // if (b.isDangerous || b.modificationTime <= 0) return 1; + // if (a.isDangerous || a.modificationTime <= 0) return -1; + if (b.modificationTime <= 0 && a.modificationTime > 0) return -1; + if (a.modificationTime <= 0 && b.modificationTime > 0) return 1; return a.modificationTime - b.modificationTime; }, }; diff --git a/src/controller.ts b/src/controller.ts index 412531ad..b8359e00 100644 --- a/src/controller.ts +++ b/src/controller.ts @@ -414,13 +414,14 @@ export class Controller { } { const folderText = this.getFolderPathText(folder); let folderSize = `${folder.size.toFixed(DECIMALS_SIZE)} GB`; - let daysSinceLastModification = folder.modificationTime - ? Math.floor( - (new Date().getTime() / 1000 - folder.modificationTime) / 86400, - ) + 'd' - : '--'; + let daysSinceLastModification = + folder.modificationTime > 0 + ? Math.floor( + (new Date().getTime() / 1000 - folder.modificationTime) / 86400, + ) + 'd' + : '--'; - if (folder.isDangerous) daysSinceLastModification = ''; + if (folder.isDangerous) daysSinceLastModification = 'xx'; if (!this.config.folderSizeInGB) { const size = this.fileService.convertGBToMB(folder.size); @@ -589,6 +590,7 @@ export class Controller { filter((path) => !isExcludedDangerousDirectory(path)), map((path) => { return { + id: this.resultsService.results.length, path, size: 0, modificationTime: 0, @@ -639,7 +641,10 @@ export class Controller { tap((size) => (nodeFolder.size = this.transformFolderSize(size))), switchMap(async () => { // Saves resources by not scanning a result that is probably not of interest - if (nodeFolder.isDangerous) return; + if (nodeFolder.isDangerous) { + nodeFolder.modificationTime = 0; + return; + } const parentFolder = path.join(nodeFolder.path, '../'); const result = await this.fileService.getProjectLastUsage(parentFolder); nodeFolder.modificationTime = result; @@ -783,6 +788,8 @@ export class Controller { private delete(): void { const nodeFolder = this.resultsService.results[this.cursorPosY - MARGINS.ROW_RESULTS_START]; + console.log(nodeFolder); + process.exit(); this.clearErrors(); this.deleteFolder(nodeFolder); } diff --git a/src/interfaces/folder.interface.ts b/src/interfaces/folder.interface.ts index 6bb2872a..c5f6a8f7 100644 --- a/src/interfaces/folder.interface.ts +++ b/src/interfaces/folder.interface.ts @@ -1,4 +1,5 @@ export interface IFolder { + id: number; path: string; size: number; modificationTime: number; diff --git a/src/services/files.service.ts b/src/services/files.service.ts index 5c2997a6..2c007406 100644 --- a/src/services/files.service.ts +++ b/src/services/files.service.ts @@ -54,7 +54,7 @@ export abstract class FileService implements IFileService { const sorted = files.sort( (a, b) => b.modificationTime - a.modificationTime, ); - return sorted[0]?.modificationTime || null; + return sorted[0]?.modificationTime || 0; } async getFileList(dirname: string): Promise { From ad35fe91060b6e55e5d40b6c50cac169fa919370 Mon Sep 17 00:00:00 2001 From: zaldih Date: Tue, 1 Nov 2022 18:34:50 +0100 Subject: [PATCH 07/14] perf(sort-date): improve sort by date method --- src/constants/sort.result.ts | 20 +++++++++++--------- src/controller.ts | 6 +++--- src/services/files.service.ts | 2 +- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/constants/sort.result.ts b/src/constants/sort.result.ts index ed2ee0f3..0bd9bd76 100644 --- a/src/constants/sort.result.ts +++ b/src/constants/sort.result.ts @@ -4,16 +4,18 @@ export const FOLDER_SORT = { path: (a: IFolder, b: IFolder) => (a.path > b.path ? 1 : -1), size: (a: IFolder, b: IFolder) => (a.size < b.size ? 1 : -1), date: (a: IFolder, b: IFolder) => { - // const idOrder = a.id - b.id; - // if (b.isDangerous || !b.modificationTime) { - // return idOrder; - // // return -1; - // } + if (a.modificationTime === b.modificationTime) { + return FOLDER_SORT.path(a, b); + } + + if (a.modificationTime === null && b.modificationTime !== null) { + return 1; + } + + if (b.modificationTime === null && a.modificationTime !== null) { + return -1; + } - // if (b.isDangerous || b.modificationTime <= 0) return 1; - // if (a.isDangerous || a.modificationTime <= 0) return -1; - if (b.modificationTime <= 0 && a.modificationTime > 0) return -1; - if (a.modificationTime <= 0 && b.modificationTime > 0) return 1; return a.modificationTime - b.modificationTime; }, }; diff --git a/src/controller.ts b/src/controller.ts index b8359e00..86daa6e5 100644 --- a/src/controller.ts +++ b/src/controller.ts @@ -415,7 +415,7 @@ export class Controller { const folderText = this.getFolderPathText(folder); let folderSize = `${folder.size.toFixed(DECIMALS_SIZE)} GB`; let daysSinceLastModification = - folder.modificationTime > 0 + folder.modificationTime !== null && folder.modificationTime > 0 ? Math.floor( (new Date().getTime() / 1000 - folder.modificationTime) / 86400, ) + 'd' @@ -593,7 +593,7 @@ export class Controller { id: this.resultsService.results.length, path, size: 0, - modificationTime: 0, + modificationTime: null, isDangerous: this.fileService.isDangerous(path), status: 'live', }; @@ -642,7 +642,7 @@ export class Controller { switchMap(async () => { // Saves resources by not scanning a result that is probably not of interest if (nodeFolder.isDangerous) { - nodeFolder.modificationTime = 0; + nodeFolder.modificationTime = null; return; } const parentFolder = path.join(nodeFolder.path, '../'); diff --git a/src/services/files.service.ts b/src/services/files.service.ts index 2c007406..5c2997a6 100644 --- a/src/services/files.service.ts +++ b/src/services/files.service.ts @@ -54,7 +54,7 @@ export abstract class FileService implements IFileService { const sorted = files.sort( (a, b) => b.modificationTime - a.modificationTime, ); - return sorted[0]?.modificationTime || 0; + return sorted[0]?.modificationTime || null; } async getFileList(dirname: string): Promise { From 5cfba79b5b3524afa16ad04040ff6481eb5aa242 Mon Sep 17 00:00:00 2001 From: zaldih Date: Fri, 4 Nov 2022 15:12:47 +0100 Subject: [PATCH 08/14] style: align old column to right --- src/controller.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/controller.ts b/src/controller.ts index 86daa6e5..591d2d15 100644 --- a/src/controller.ts +++ b/src/controller.ts @@ -421,7 +421,12 @@ export class Controller { ) + 'd' : '--'; - if (folder.isDangerous) daysSinceLastModification = 'xx'; + if (folder.isDangerous) daysSinceLastModification = 'xxx'; + + // Align to right + daysSinceLastModification = + ' '.repeat(4 - daysSinceLastModification.length) + + daysSinceLastModification; if (!this.config.folderSizeInGB) { const size = this.fileService.convertGBToMB(folder.size); From ace3e01dc8a13f58c56d9a0e122b5367ed2904cc Mon Sep 17 00:00:00 2001 From: zaldih Date: Fri, 4 Nov 2022 17:41:53 +0100 Subject: [PATCH 09/14] refactor(last-usage): rename some methods --- src/controller.ts | 4 +++- src/interfaces/file-service.interface.ts | 7 +++---- src/services/files.service.ts | 17 +++++++++-------- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/controller.ts b/src/controller.ts index 591d2d15..1f68741a 100644 --- a/src/controller.ts +++ b/src/controller.ts @@ -651,7 +651,9 @@ export class Controller { return; } const parentFolder = path.join(nodeFolder.path, '../'); - const result = await this.fileService.getProjectLastUsage(parentFolder); + const result = await this.fileService.getRecentModificationInDir( + parentFolder, + ); nodeFolder.modificationTime = result; }), tap(() => this.finishFolderStats()), diff --git a/src/interfaces/file-service.interface.ts b/src/interfaces/file-service.interface.ts index d9d5fdc8..9a97f978 100644 --- a/src/interfaces/file-service.interface.ts +++ b/src/interfaces/file-service.interface.ts @@ -11,12 +11,11 @@ export interface IFileService { getFileContent(path: string): string; isSafeToDelete(path: string, targetFolder: string): boolean; isDangerous(path: string): boolean; - getFileList(path: string): any; - getProjectLastUsage(path: string): Promise; - getFileList(dirname: string): Promise; + getRecentModificationInDir(path: string): Promise; + getFileStatsInDir(dirname: string): Promise; } -export interface IFileList { +export interface IFileStat { path: string; modificationTime: number; } diff --git a/src/services/files.service.ts b/src/services/files.service.ts index 5c2997a6..27cd14e9 100644 --- a/src/services/files.service.ts +++ b/src/services/files.service.ts @@ -1,5 +1,5 @@ import { - IFileList, + IFileStat, IFileService, IListDirParams, } from '../interfaces/index.js'; @@ -49,16 +49,16 @@ export abstract class FileService implements IFileService { return hiddenFilePattern.test(path); } - async getProjectLastUsage(path: string): Promise { - const files = await this.getFileList(path); + async getRecentModificationInDir(path: string): Promise { + const files = await this.getFileStatsInDir(path); const sorted = files.sort( (a, b) => b.modificationTime - a.modificationTime, ); return sorted[0]?.modificationTime || null; } - async getFileList(dirname: string): Promise { - let files: IFileList[] = []; + async getFileStatsInDir(dirname: string): Promise { + let files: IFileStat[] = []; const items = await readdir(dirname, { withFileTypes: true }); for (const item of items) { @@ -67,12 +67,13 @@ export abstract class FileService implements IFileService { if (item.name === 'node_modules') continue; files = [ ...files, - ...(await this.getFileList(`${dirname}/${item.name}`)), + ...(await this.getFileStatsInDir(`${dirname}/${item.name}`)), ]; } else { const path = `${dirname}/${item.name}`; - const lastModify = (await stat(path)).mtime; - files.push({ path, modificationTime: lastModify.getTime() / 1000 }); + const fileStat = await stat(path); + + files.push({ path, modificationTime: fileStat.mtimeMs / 1000 }); } } catch (error) {} } From 057932ebba92b73849d2eb2a195cd048d6406177 Mon Sep 17 00:00:00 2001 From: zaldih Date: Fri, 4 Nov 2022 17:58:31 +0100 Subject: [PATCH 10/14] fix: handle invalid amount for repeat --- src/controller.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/controller.ts b/src/controller.ts index 1f68741a..f5315f44 100644 --- a/src/controller.ts +++ b/src/controller.ts @@ -424,9 +424,9 @@ export class Controller { if (folder.isDangerous) daysSinceLastModification = 'xxx'; // Align to right + const alignMargin = 4 - daysSinceLastModification.length; daysSinceLastModification = - ' '.repeat(4 - daysSinceLastModification.length) + - daysSinceLastModification; + ' '.repeat(alignMargin > 0 ? alignMargin : 0) + daysSinceLastModification; if (!this.config.folderSizeInGB) { const size = this.fileService.convertGBToMB(folder.size); From 72fe4fa8d4f693383afaae92bc6eb8206fc5e743 Mon Sep 17 00:00:00 2001 From: zaldih Date: Fri, 4 Nov 2022 18:09:11 +0100 Subject: [PATCH 11/14] refactor: remove id property --- src/controller.ts | 1 - src/interfaces/folder.interface.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/src/controller.ts b/src/controller.ts index f5315f44..5ab9b871 100644 --- a/src/controller.ts +++ b/src/controller.ts @@ -595,7 +595,6 @@ export class Controller { filter((path) => !isExcludedDangerousDirectory(path)), map((path) => { return { - id: this.resultsService.results.length, path, size: 0, modificationTime: null, diff --git a/src/interfaces/folder.interface.ts b/src/interfaces/folder.interface.ts index c5f6a8f7..6bb2872a 100644 --- a/src/interfaces/folder.interface.ts +++ b/src/interfaces/folder.interface.ts @@ -1,5 +1,4 @@ export interface IFolder { - id: number; path: string; size: number; modificationTime: number; From 5af4dbd857fd67b7194a349c4b1b27778b0bdcfd Mon Sep 17 00:00:00 2001 From: zaldih Date: Fri, 4 Nov 2022 21:39:30 +0100 Subject: [PATCH 12/14] refactor(header): move columns header to constant --- src/constants/messages.constants.ts | 2 +- src/controller.ts | 15 +++------------ 2 files changed, 4 insertions(+), 13 deletions(-) diff --git a/src/constants/messages.constants.ts b/src/constants/messages.constants.ts index 1d91f6e0..4e6b5420 100644 --- a/src/constants/messages.constants.ts +++ b/src/constants/messages.constants.ts @@ -9,7 +9,7 @@ export const INFO_MSGS = { DISABLED: '[-D, --delete-all] option has been disabled until future versions. Please restart npkill without this option.', - HEADER_SIZE_COLUMN: 'size', + HEADER_COLUMNS: 'last_mod size', HELP_TITLE: ' NPKILL HELP ', MIN_CLI_CLOMUNS: 'Oh no! The terminal is too narrow. Please, ' + diff --git a/src/controller.ts b/src/controller.ts index 5ab9b871..a5c6c300 100644 --- a/src/controller.ts +++ b/src/controller.ts @@ -286,18 +286,9 @@ export class Controller { ); /////////////////////////// - // folder size header - this.printAt(colors.gray('old |'), { - x: - this.stdout.columns - - 5 - - (MARGINS.FOLDER_SIZE_COLUMN + - Math.round(INFO_MSGS.HEADER_SIZE_COLUMN.length / 5)), - y: UI_POSITIONS.FOLDER_SIZE_HEADER.y, - }); - - this.printAt(colors.gray(INFO_MSGS.HEADER_SIZE_COLUMN), { - x: this.stdout.columns - MARGINS.FOLDER_SIZE_COLUMN + 2, + // Columns headers + this.printAt(colors.gray(INFO_MSGS.HEADER_COLUMNS), { + x: this.stdout.columns - INFO_MSGS.HEADER_COLUMNS.length - 4, y: UI_POSITIONS.FOLDER_SIZE_HEADER.y, }); From 99b7291bf286a9487f5e1df00fca94e9e540a13d Mon Sep 17 00:00:00 2001 From: zaldih Date: Fri, 4 Nov 2022 22:06:27 +0100 Subject: [PATCH 13/14] docs(readme): add last_mod to sort options section --- README.MD | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.MD b/README.MD index 1fdd593a..d7600351 100644 --- a/README.MD +++ b/README.MD @@ -98,7 +98,7 @@ To exit, Q or Ctrl + c if you're brave. | -gb | Show folders in Gigabytes instead of Megabytes. | | -h, --help, ? | Show this help page and exit | | -nu, --no-check-update | Don't check for updates on startup | -| -s, --sort | Sort results by: size or path _[ beta ]_ | +| -s, --sort | Sort results by: `size`, `path` or `last_mod` | | -t, --target | Specify the name of the directories you want to search (by default, is node_modules) | | -x, --exclude-hidden-directories | Exclude hidden directories ("dot" directories) from search. | | -v, --version | Show npkill version | From f21351cec43cebde032aad940377416d370cbf41 Mon Sep 17 00:00:00 2001 From: zaldih Date: Mon, 7 Nov 2022 19:16:40 +0100 Subject: [PATCH 14/14] chore: final adjustaments and describe the feat in readme --- README.MD | 7 ++++--- src/constants/messages.constants.ts | 2 +- src/constants/sort.result.ts | 2 +- src/controller.ts | 2 +- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/README.MD b/README.MD index d7600351..034f9b82 100644 --- a/README.MD +++ b/README.MD @@ -36,7 +36,9 @@ This tool allows you to list any _node_modules_ directories in your system, as w # :heavy_check_mark: Features -- **Clear space:** Get rid of old and dusty node_modules cluttering up your machine. +- **Clear space:** Get rid of old and dusty *node_modules* cluttering up your machine. + +- **Last Workspace Usage**: Check when was the last time you modified a file in the workspace (indicated in the **last_mod** column). - **Very fast:** NPKILL is written in TypeScript, but searches are performed at a low level, improving performance greatly. @@ -44,7 +46,6 @@ This tool allows you to list any _node_modules_ directories in your system, as w - **Minified:** It barely has any dependencies. -Npkill requir # :cloud: Installation @@ -98,7 +99,7 @@ To exit, Q or Ctrl + c if you're brave. | -gb | Show folders in Gigabytes instead of Megabytes. | | -h, --help, ? | Show this help page and exit | | -nu, --no-check-update | Don't check for updates on startup | -| -s, --sort | Sort results by: `size`, `path` or `last_mod` | +| -s, --sort | Sort results by: `size`, `path` or `last-mod` | | -t, --target | Specify the name of the directories you want to search (by default, is node_modules) | | -x, --exclude-hidden-directories | Exclude hidden directories ("dot" directories) from search. | | -v, --version | Show npkill version | diff --git a/src/constants/messages.constants.ts b/src/constants/messages.constants.ts index 4e6b5420..b6e4e133 100644 --- a/src/constants/messages.constants.ts +++ b/src/constants/messages.constants.ts @@ -18,7 +18,7 @@ export const INFO_MSGS = { NO_TTY: // tslint:disable-next-line: max-line-length 'Oh no! Npkill does not support this terminal (TTY is required). This is a bug, which has to be fixed. Please try another command interpreter (for example, CMD in windows)', - NO_VALID_SORT_NAME: 'Invalid sort option. Available: path | size | date', + NO_VALID_SORT_NAME: 'Invalid sort option. Available: path | size | last-mod', SEARCHING: 'searching ', SEARCH_COMPLETED: 'search completed ', SPACE_RELEASED: 'space saved: ', diff --git a/src/constants/sort.result.ts b/src/constants/sort.result.ts index 0bd9bd76..9471732f 100644 --- a/src/constants/sort.result.ts +++ b/src/constants/sort.result.ts @@ -3,7 +3,7 @@ import { IFolder } from '../interfaces/folder.interface.js'; export const FOLDER_SORT = { path: (a: IFolder, b: IFolder) => (a.path > b.path ? 1 : -1), size: (a: IFolder, b: IFolder) => (a.size < b.size ? 1 : -1), - date: (a: IFolder, b: IFolder) => { + 'last-mod': (a: IFolder, b: IFolder) => { if (a.modificationTime === b.modificationTime) { return FOLDER_SORT.path(a, b); } diff --git a/src/controller.ts b/src/controller.ts index a5c6c300..227938be 100644 --- a/src/controller.ts +++ b/src/controller.ts @@ -652,7 +652,7 @@ export class Controller { private finishFolderStats(): void { const needSort = - this.config.sortBy === 'size' || this.config.sortBy === 'date'; + this.config.sortBy === 'size' || this.config.sortBy === 'last-mod'; if (needSort) { this.resultsService.sortResults(this.config.sortBy); this.clearFolderSection();