diff --git a/README.md b/README.md index b7003a0..419736b 100644 --- a/README.md +++ b/README.md @@ -25,8 +25,6 @@ gets files' paths from `nature-pack.torrent` and compares them with files from ` `--dir` (or `-d`) - Path to directory with downloaded files -`--verbose` - Output all outdated filenames. By default only first 20 filenames are displayed - `--version` - Outputs the app version ## Config files diff --git a/package-lock.json b/package-lock.json index beed09c..b8bdcfb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "torrent-clean", - "version": "1.4.2", + "version": "1.5.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -987,9 +987,9 @@ } }, "eslint-config-prettier": { - "version": "6.10.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-6.10.0.tgz", - "integrity": "sha512-AtndijGte1rPILInUdHjvKEGbIV06NuvPrqlIEaEaWtbtvJh464mDeyGMdZEQMsGvC0ZVkiex1fSNcC4HAbRGg==", + "version": "6.10.1", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-6.10.1.tgz", + "integrity": "sha512-svTy6zh1ecQojvpbJSgH3aei/Rt7C6i090l5f2WQ4aB05lYHeZIR1qL4wZyyILTbtmnbHP5Yn8MrsOJMGa8RkQ==", "dev": true, "requires": { "get-stdin": "^6.0.0" @@ -1004,9 +1004,9 @@ } }, "eslint-config-standard": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-14.1.0.tgz", - "integrity": "sha512-EF6XkrrGVbvv8hL/kYa/m6vnvmUT+K82pJJc4JJVMM6+Qgqh0pnwprSxdduDLB9p/7bIxD+YV5O0wfb8lmcPbA==", + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-14.1.1.tgz", + "integrity": "sha512-Z9B+VR+JIXRxz21udPTL9HpFMyoMUEeX1G251EQ6e05WD9aPVtVBn09XUmZ259wCMlCDmYDSZG62Hhm+ZTJcUg==", "dev": true }, "eslint-import-resolver-node": { @@ -1138,9 +1138,9 @@ } }, "eslint-plugin-node": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-11.0.0.tgz", - "integrity": "sha512-chUs/NVID+sknFiJzxoN9lM7uKSOEta8GC8365hw1nDfwIPIjjpRSwwPvQanWv8dt/pDe9EV4anmVSwdiSndNg==", + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz", + "integrity": "sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g==", "dev": true, "requires": { "eslint-plugin-es": "^3.0.0", @@ -1881,6 +1881,7 @@ "version": "0.5.1", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, "requires": { "minimist": "0.0.8" }, @@ -1888,10 +1889,16 @@ "minimist": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true } } }, + "mkdirp-classic": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.2.tgz", + "integrity": "sha512-ejdnDQcR75gwknmMw/tx02AuRs8jCtqFoFqDZMjiNxsu85sRIJVXDKHuLYvUUPRBUtV2FpSZa9bL1BUa3BdR2g==" + }, "mp4-box-encoding": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/mp4-box-encoding/-/mp4-box-encoding-1.4.1.tgz", @@ -2182,9 +2189,9 @@ "dev": true }, "prettier": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.19.1.tgz", - "integrity": "sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.0.2.tgz", + "integrity": "sha512-5xJQIPT8BraI7ZnaDwSbu5zLrB6vvi8hVV58yHQ+QK64qrY40dULy0HSRlQ2/2IdzeBpjhDkqdcFBnFeDEMVdg==", "dev": true }, "prettier-linter-helpers": { @@ -2223,11 +2230,11 @@ "integrity": "sha512-F9wwNePtXrzZenAB3ax0Y8TSKGvuB7Qw16J30hspEUTbfUM+H827XyN3rlpwhVmtm5wuZtbKIHjOnwDn7MUxWQ==" }, "random-access-file": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/random-access-file/-/random-access-file-2.1.3.tgz", - "integrity": "sha512-AE0Z1ywR5gIkzACMC1lCsR6LP8g4ynNm7oYWYdKPSSU6Y3H+mGDJxBqfcV9B9KstfHNemhfX3nYmx99ZC9f/yg==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/random-access-file/-/random-access-file-2.1.4.tgz", + "integrity": "sha512-WAcBP5iLhg1pbjZA40WyMenjK7c5gJUY6Pi5HJ3fLJCeVFNSZv3juf20yFMKxBdvcX5GKbX/HZSfFzlLBdGTdQ==", "requires": { - "mkdirp": "^0.5.1", + "mkdirp-classic": "^0.5.2", "random-access-storage": "^1.1.1" } }, diff --git a/package.json b/package.json index e130d60..042645c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "torrent-clean", - "version": "1.4.2", + "version": "1.5.0", "description": "Removes files that are not specified in selected torrent file", "author": "Nikolay Borzov ", "license": "MIT", @@ -37,13 +37,13 @@ "devDependencies": { "babel-eslint": "^10.1.0", "eslint": "^6.8.0", - "eslint-config-prettier": "^6.10.0", - "eslint-config-standard": "^14.1.0", + "eslint-config-prettier": "^6.10.1", + "eslint-config-standard": "^14.1.1", "eslint-plugin-import": "^2.20.1", - "eslint-plugin-node": "^11.0.0", + "eslint-plugin-node": "^11.1.0", "eslint-plugin-prettier": "^3.1.2", "eslint-plugin-promise": "^4.2.1", "eslint-plugin-standard": "^4.0.1", - "prettier": "^1.19.1" + "prettier": "^2.0.2" } } diff --git a/src/config-loader.js b/src/config-loader.js index 8ab262e..10587ac 100644 --- a/src/config-loader.js +++ b/src/config-loader.js @@ -20,8 +20,8 @@ function loadConfig(searchFrom) { `.${moduleName}rc.json`, `.${moduleName}rc.yaml`, `.${moduleName}rc.yml`, - `.${moduleName}rc.js` - ] + `.${moduleName}rc.js`, + ], }) const results = [] @@ -37,7 +37,7 @@ function loadConfig(searchFrom) { } const mergedConfig = results - .map(result => result.config) + .map((result) => result.config) .reverse() .reduce(merge, {}) diff --git a/src/delete-files.js b/src/delete-files.js index 0b4a117..55a655a 100644 --- a/src/delete-files.js +++ b/src/delete-files.js @@ -7,7 +7,7 @@ const logColor = require('./log-color') async function deleteFiles(filenames) { try { - const deletePromises = filenames.map(filename => unlink(filename)) + const deletePromises = filenames.map((filename) => unlink(filename)) await Promise.all(deletePromises) } catch (error) { console.log(logColor.error('Cannot delete files'), error) diff --git a/src/file-select-prompt.js b/src/file-select-prompt.js new file mode 100644 index 0000000..fd8d1e0 --- /dev/null +++ b/src/file-select-prompt.js @@ -0,0 +1,60 @@ +const { MultiSelect } = require('enquirer') +const utils = require('enquirer/lib/utils') + +module.exports = class FilesSelect extends MultiSelect { + constructor(options) { + super(options) + + this.digitsCount = (Math.log10(options.choices.length) + 1) | 0 + } + + // https://github.com/enquirer/enquirer/blob/master/lib/prompts/select.js#L46 + async renderChoice(choice, i) { + await this.onChoice(choice, i) + + const focused = this.index === i + const pointer = await this.pointer(choice, i) + const check = (await this.indicator(choice, i)) + (choice.pad || '') + let hint = await this.resolve(choice.hint, this.state, choice, i) + + if (hint && !utils.hasColor(hint)) { + hint = this.styles.muted(hint) + } + + const ind = this.indent(choice) + let msg = await this.choiceMessage(choice, i) + + const choiceNumber = `${(choice.index + 1) + .toString() + .padStart(this.digitsCount)}|` + + const line = () => + [ + this.margin[3], + ind + pointer + check, + choiceNumber, + msg, + this.margin[1], + hint, + ] + .filter(Boolean) + .join(' ') + + if (choice.role === 'heading') { + return line() + } + + if (choice.disabled) { + if (!utils.hasColor(msg)) { + msg = this.styles.disabled(msg) + } + return line() + } + + if (focused) { + msg = this.styles.em(msg) + } + + return line() + } +} diff --git a/src/index.js b/src/index.js index 1369eef..3ff9f48 100644 --- a/src/index.js +++ b/src/index.js @@ -3,7 +3,7 @@ const os = require('os') const path = require('path') const recursive = require('recursive-readdir') -const { Confirm, MultiSelect } = require('enquirer') +const { Confirm } = require('enquirer') const chalk = require('chalk') const packageJson = require('../package.json') @@ -11,17 +11,18 @@ const logColor = require('./log-color') const loadConfig = require('./config-loader') const parseTorrent = require('./parse-torrent') const deleteFilesAndEmptyFolders = require('./delete-files') +const FilesSelect = require('./file-select-prompt') const FILES_ON_SCREEN_LIMIT = 20 const argv = require('minimist')(process.argv.slice(2), { alias: { torrent: ['t'], dir: ['d'], version: ['v'] }, - default: { dir: process.cwd() } + default: { dir: process.cwd() }, }) if (argv.version) { console.log(packageJson.version) - process.exit() + process.exit(0) } console.log(logColor.info.bold('dir:'.padEnd(10)), argv.dir) @@ -29,7 +30,7 @@ console.log(logColor.info.bold('torrent:'.padEnd(10)), argv.torrent, os.EOL) if (!argv.torrent) { console.log(logColor.error(`${chalk.bold('torrent')} argument is required`)) - process.exit() + process.exit(1) } let config @@ -37,7 +38,7 @@ try { config = loadConfig(argv.dir) } catch (error) { console.log(logColor.error('Cannot parse config file'), error) - process.exit() + process.exit(1) } const torrentId = argv.torrent @@ -56,7 +57,7 @@ Promise.all([parseTorrent(torrentId), recursive(directoryPath, config.ignore)]) const rootDir = `${name}${path.sep}` // Get absolute paths of torrent files - const torrentFiles = files.map(file => + const torrentFiles = files.map((file) => path.join(directoryPath, file.replace(rootDir, '')) ) @@ -74,14 +75,14 @@ Promise.all([parseTorrent(torrentId), recursive(directoryPath, config.ignore)]) const dirRoot = `${directoryPath}${path.sep}` - const filesChoices = extraFiles.map(filename => ({ + const filesChoices = extraFiles.map((filename) => ({ name: filename.replace(dirRoot, ''), - value: filename + value: filename, })) - const filesToDeleteMultiSelect = new MultiSelect({ + const filesToDeleteMultiSelect = new FilesSelect({ name: 'selectFilesToDelete', - message: `Select file(s) to delete (Use 'Space')`, + message: `Select file(s) to delete ('space' - toggle item selection, 'i' - invert selection)`, choices: filesChoices, limit: FILES_ON_SCREEN_LIMIT, indicator: '■', @@ -92,8 +93,8 @@ Promise.all([parseTorrent(torrentId), recursive(directoryPath, config.ignore)]) }, // Get values from selected names https://github.com/enquirer/enquirer/issues/121 result(names) { - return names.map(name => this.find(name).value) - } + return names.map((name) => this.find(name).value) + }, }) const filesToDelete = await filesToDeleteMultiSelect.run() @@ -105,7 +106,7 @@ Promise.all([parseTorrent(torrentId), recursive(directoryPath, config.ignore)]) const deleteConfirm = new Confirm({ name: 'delete', message: 'Delete extra files?', - initial: true + initial: true, }) const deleteFilesAnswer = await deleteConfirm.run() @@ -122,6 +123,8 @@ Promise.all([parseTorrent(torrentId), recursive(directoryPath, config.ignore)]) console.log('No extra files found!') } }) - .catch(error => { + .catch((error) => { console.log(logColor.error('Error ocurred'), error) + + process.exitCode = 1 }) diff --git a/src/parse-torrent.js b/src/parse-torrent.js index c36ea97..325f34f 100644 --- a/src/parse-torrent.js +++ b/src/parse-torrent.js @@ -12,10 +12,10 @@ function parseTorrent(torrentId, onDone, onError) { // Use memory-chunk-store to avoid creating directories inside tmp/webtorrent (https://github.com/webtorrent/webtorrent/issues/1562) const torrent = client.add(torrentId, { - store: memoryChunkStore + store: memoryChunkStore, }) - torrent.on('error', error => { + torrent.on('error', (error) => { onError(error) client.destroy() @@ -24,7 +24,7 @@ function parseTorrent(torrentId, onDone, onError) { torrent.on('metadata', () => { onDone({ name: torrent.name, - files: torrent.files.map(file => file.path) + files: torrent.files.map((file) => file.path), }) client.destroy()