diff --git a/.gitignore b/.gitignore index ab548369..8a5cd37d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -node_modules/ -npm-debug.log -.nyc_output -coverage +/node_modules/ +/.nyc_output +/coverage +/dist diff --git a/CHANGELOG.md b/CHANGELOG.md index f116f141..774bdb32 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,14 @@ +# v4.0 + +- Remove `glob` dependency entirely. This library now only + accepts actual file and folder names to delete. +- Accept array of paths or single path. +- Windows performance and reliability improved. +- All strategies separated into explicitly exported methods. +- Drop support for Node.js below version 14 +- rewrite in TypeScript +- ship CJS/ESM hybrid module + # v3.0 - Add `--preserve-root` option to executable (default true) diff --git a/README.md b/README.md index 9599a038..0418112c 100644 --- a/README.md +++ b/README.md @@ -16,9 +16,24 @@ Install with `npm install rimraf`, or just drop rimraf.js somewhere. ## API +Hybrid module, load either with `import` or `require()`. + +```js +// default export is the main rimraf function +import rimraf from 'rimraf' +// or +const rimraf = require('rimraf').default + +// other strategies exported as well +import { rimraf, rimrafSync, native, nativeSync } from 'rimraf' +// or +const { rimraf, rimrafSync, native, nativeSync } = require('rimraf') +``` + ### `rimraf(f, [opts]) -> Promise` -This first parameter is a path. The second argument is an options object. +This first parameter is a path or array of paths. The second +argument is an options object. Options: @@ -31,9 +46,10 @@ Options: `os.tmpdir()` when that is on the same drive letter as the path being deleted, or `${drive}:\temp` if present, or `${drive}:\` if not. -- `retries`: Windows only. Maximum number of synchronous retry - attempts in case of `EBUSY`, `EMFILE`, and `ENFILE` errors. - Default `10` +- `maxRetries`: Windows and Native only. Maximum number of + retry attempts in case of `EBUSY`, `EMFILE`, and `ENFILE` + errors. Default `10` for Windows implementation, `0` for Native + implementation. - `backoff`: Windows only. Rate of exponential backoff for async removal in case of `EBUSY`, `EMFILE`, and `ENFILE` errors. Should be a number greater than 1. Default `1.2` @@ -42,6 +58,8 @@ Options: `ENFILE` errors. Default `200`. With the default `1.2` backoff rate, this results in 14 retries, with the final retry being delayed 33ms. +- `retryDelay`: Native only. Time to wait between retries, using + linear backoff. Default `100`. Any other options are provided to the native Node.js `fs.rm` implementation when that is used. @@ -123,7 +141,7 @@ Deletes all files and folders at "path", recursively. Options: -- Treat all subsequent arguments as paths -h --help Display this usage info - --preserve-root Do not remove '/' (default) + --preserve-root Do not remove '/' recursively (default) --no-preserve-root Do not treat '/' specially --impl= Specify the implementationt to use. @@ -132,11 +150,13 @@ Options: manual: the platform-specific JS implementation posix: the Posix JS implementation windows: the Windows JS implementation + move-remove: a slower Windows JS fallback implementation Implementation-specific options: - --tmp= Folder to hold temp files for 'windows' implementation - --max-retries= maxRetries for the 'native' implementation - --retry-delay= retryDelay for the 'native' implementation + --tmp= Folder to hold temp files for 'move-remove' implementation + --max-retries= maxRetries for the 'native' and 'windows' implementations + --retry-delay= retryDelay for the 'native' implementation, default 100 + --backoff= Exponential backoff factor for retries (default: 1.2) ``` ## mkdirp diff --git a/fixup.sh b/fixup.sh new file mode 100644 index 00000000..9fd16d16 --- /dev/null +++ b/fixup.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash + +cat >dist/cjs/package.json <dist/mjs/package.json < async path => { - try { - return await fn(path) - } catch (er) { - if (er.code === 'ENOENT') { - return - } - if (er.code === 'EPERM') { - try { - await chmod(path, 0o666) - } catch (er2) { - if (er2.code === 'ENOENT') { - return - } - throw er - } - return await fn(path) - } - throw er - } -} - -const fixEPERMSync = fn => path => { - try { - return fn(path) - } catch (er) { - if (er.code === 'ENOENT') { - return - } - if (er.code === 'EPERM') { - try { - chmodSync(path, 0o666) - } catch (er2) { - if (er2.code === 'ENOENT') { - return - } - throw er - } - return fn(path) - } - throw er - } -} - -module.exports = { - fixEPERM, - fixEPERMSync, -} diff --git a/lib/fs.js b/lib/fs.js deleted file mode 100644 index 46283b71..00000000 --- a/lib/fs.js +++ /dev/null @@ -1,89 +0,0 @@ -// promisify ourselves, because older nodes don't have fs.promises - -const fs = require('fs') - -// sync ones just take the sync version from node -const { - chmodSync, - mkdirSync, - readdirSync, - renameSync, - rmSync, - rmdirSync, - statSync, - unlinkSync, - writeFileSync, -} = fs - -// unrolled for better inlining, this seems to get better performance -// than something like: -// const makeCb = (res, rej) => (er, ...d) => er ? rej(er) : res(...d) -// which would be a bit cleaner. - -const chmod = (...args) => - new Promise((res, rej) => - fs.chmod(...args.concat((er, ...data) => (er ? rej(er) : res(...data)))) - ) - -const mkdir = (...args) => - new Promise((res, rej) => - fs.mkdir(...args.concat((er, ...data) => (er ? rej(er) : res(...data)))) - ) - -const readdir = (...args) => - new Promise((res, rej) => - fs.readdir(...args.concat((er, ...data) => (er ? rej(er) : res(...data)))) - ) - -const rename = (...args) => - new Promise((res, rej) => - fs.rename(...args.concat((er, ...data) => (er ? rej(er) : res(...data)))) - ) - -const rm = (...args) => - new Promise((res, rej) => - fs.rm(...args.concat((er, ...data) => (er ? rej(er) : res(...data)))) - ) - -const rmdir = (...args) => - new Promise((res, rej) => - fs.rmdir(...args.concat((er, ...data) => (er ? rej(er) : res(...data)))) - ) - -const stat = (...args) => - new Promise((res, rej) => - fs.stat(...args.concat((er, ...data) => (er ? rej(er) : res(...data)))) - ) - -const unlink = (...args) => - new Promise((res, rej) => - fs.unlink(...args.concat((er, ...data) => (er ? rej(er) : res(...data)))) - ) - -const writeFile = (...args) => - new Promise((res, rej) => - fs.writeFile(...args.concat((er, ...data) => (er ? rej(er) : res(...data)))) - ) - -module.exports = { - chmodSync, - mkdirSync, - readdirSync, - renameSync, - rmSync, - rmdirSync, - statSync, - unlinkSync, - writeFileSync, - promises: { - chmod, - mkdir, - readdir, - rename, - rm, - rmdir, - stat, - unlink, - writeFile, - }, -} diff --git a/lib/ignore-enoent.js b/lib/ignore-enoent.js deleted file mode 100644 index be5478ed..00000000 --- a/lib/ignore-enoent.js +++ /dev/null @@ -1,18 +0,0 @@ -const ignoreENOENT = async p => - p.catch(er => { - if (er.code !== 'ENOENT') { - throw er - } - }) - -const ignoreENOENTSync = fn => { - try { - return fn() - } catch (er) { - if (er.code !== 'ENOENT') { - throw er - } - } -} - -module.exports = { ignoreENOENT, ignoreENOENTSync } diff --git a/lib/index.js b/lib/index.js deleted file mode 100644 index 3ce0dba2..00000000 --- a/lib/index.js +++ /dev/null @@ -1,69 +0,0 @@ -const pathArg = require('./path-arg.js') -const optArg = require('./opt-arg.js') - -const { rimrafNative, rimrafNativeSync } = require('./rimraf-native.js') -const { rimrafManual, rimrafManualSync } = require('./rimraf-manual.js') -const { rimrafWindows, rimrafWindowsSync } = require('./rimraf-windows.js') -const { rimrafPosix, rimrafPosixSync } = require('./rimraf-posix.js') -const { - rimrafMoveRemove, - rimrafMoveRemoveSync, -} = require('./rimraf-move-remove.js') -const { useNative, useNativeSync } = require('./use-native.js') - -const wrap = fn => async (path, opt) => { - opt = optArg(opt) - await (Array.isArray(path) - ? Promise.all(path.map(p => fn(pathArg(p, opt), opt))) - : fn(pathArg(path, opt), opt)) -} - -const wrapSync = fn => (path, opt) => { - opt = optArg(opt) - return Array.isArray(path) - ? path.forEach(p => fn(pathArg(p, opt), opt)) - : fn(pathArg(path, opt), opt) -} - -const rimraf = wrap((path, opt) => - useNative(opt) ? rimrafNative(path, opt) : rimrafManual(path, opt) -) - -const rimrafSync = wrapSync((path, opt) => - useNativeSync(opt) ? rimrafNativeSync(path, opt) : rimrafManualSync(path, opt) -) - -rimraf.rimraf = rimraf -rimraf.sync = rimraf.rimrafSync = rimrafSync - -const native = wrap(rimrafNative) -const nativeSync = wrapSync(rimrafNativeSync) -native.sync = nativeSync -rimraf.native = native -rimraf.nativeSync = nativeSync - -const manual = wrap(rimrafManual) -const manualSync = wrapSync(rimrafManualSync) -manual.sync = manualSync -rimraf.manual = manual -rimraf.manualSync = manualSync - -const windows = wrap(rimrafWindows) -const windowsSync = wrapSync(rimrafWindowsSync) -windows.sync = windowsSync -rimraf.windows = windows -rimraf.windowsSync = windowsSync - -const posix = wrap(rimrafPosix) -const posixSync = wrapSync(rimrafPosixSync) -posix.sync = posixSync -rimraf.posix = posix -rimraf.posixSync = posixSync - -const moveRemove = wrap(rimrafMoveRemove) -const moveRemoveSync = wrapSync(rimrafMoveRemoveSync) -moveRemove.sync = moveRemoveSync -rimraf.moveRemove = moveRemove -rimraf.moveRemoveSync = moveRemoveSync - -module.exports = rimraf diff --git a/lib/opt-arg.js b/lib/opt-arg.js deleted file mode 100644 index b049f8c5..00000000 --- a/lib/opt-arg.js +++ /dev/null @@ -1,2 +0,0 @@ -// TODO: validate options -module.exports = (opt = {}) => opt diff --git a/lib/platform.js b/lib/platform.js deleted file mode 100644 index a74650e9..00000000 --- a/lib/platform.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = process.env.__TESTING_RIMRAF_PLATFORM__ || process.platform diff --git a/lib/readdir-or-error.js b/lib/readdir-or-error.js deleted file mode 100644 index 67822fa7..00000000 --- a/lib/readdir-or-error.js +++ /dev/null @@ -1,19 +0,0 @@ -// returns an array of entries if readdir() works, -// or the error that readdir() raised if not. -const { - readdirSync, - promises: { readdir }, -} = require('./fs.js') -const readdirOrError = path => readdir(path).catch(er => er) -const readdirOrErrorSync = path => { - try { - return readdirSync(path) - } catch (er) { - return er - } -} - -module.exports = { - readdirOrError, - readdirOrErrorSync, -} diff --git a/lib/rimraf-manual.js b/lib/rimraf-manual.js deleted file mode 100644 index 8ddf2e4f..00000000 --- a/lib/rimraf-manual.js +++ /dev/null @@ -1,14 +0,0 @@ -const platform = require('./platform.js') - -const { rimrafWindows, rimrafWindowsSync } = require('./rimraf-windows.js') -const { rimrafPosix, rimrafPosixSync } = require('./rimraf-posix.js') - -const [rimrafManual, rimrafManualSync] = - platform === 'win32' - ? [rimrafWindows, rimrafWindowsSync] - : [rimrafPosix, rimrafPosixSync] - -module.exports = { - rimrafManual, - rimrafManualSync, -} diff --git a/lib/rimraf-native.js b/lib/rimraf-native.js deleted file mode 100644 index e7fb2358..00000000 --- a/lib/rimraf-native.js +++ /dev/null @@ -1,23 +0,0 @@ -const { - rmSync, - promises: { rm }, -} = require('./fs.js') - -const rimrafNative = (path, opt) => - rm(path, { - ...opt, - force: true, - recursive: true, - }) - -const rimrafNativeSync = (path, opt) => - rmSync(path, { - ...opt, - force: true, - recursive: true, - }) - -module.exports = { - rimrafNative, - rimrafNativeSync, -} diff --git a/lib/use-native.js b/lib/use-native.js deleted file mode 100644 index a2b90442..00000000 --- a/lib/use-native.js +++ /dev/null @@ -1,9 +0,0 @@ -const version = process.env.__TESTING_RIMRAF_NODE_VERSION__ || process.version -const versArr = version.replace(/^v/, '').split('.') -const hasNative = +versArr[0] > 14 || (+versArr[0] === 14 && +versArr[1] >= 14) - -// TODO: check opt.rm === fs.rm, and use manual if they've overridden it -const useNative = !hasNative ? () => false : () => true -const useNativeSync = !hasNative ? () => false : () => true - -module.exports = { useNative, useNativeSync } diff --git a/libtap-settings.js b/libtap-settings.js index baf0d578..07c328f2 100644 --- a/libtap-settings.js +++ b/libtap-settings.js @@ -1,6 +1,6 @@ // use this module for tap's recursive directory removal, so that // the windows tests don't fail with EBUSY. -const rimraf = require('./') +const rimraf = require('./').default module.exports = { rmdirRecursiveSync: path => rimraf.sync(path), rmdirRecursive(path, cb) { diff --git a/package-lock.json b/package-lock.json index 95685c16..fa1ae8b8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,13 +9,19 @@ "version": "4.0.0-0", "license": "ISC", "bin": { - "rimraf": "lib/bin.js" + "rimraf": "dist/cjs/src/bin.js" }, "devDependencies": { + "@types/node": "^18.11.9", + "@types/tap": "^15.0.7", + "c8": "^7.12.0", "eslint-config-prettier": "^8.6.0", "mkdirp": "1", "prettier": "^2.8.2", - "tap": "^16.3.3" + "tap": "^16.3.3", + "ts-node": "^10.9.1", + "typedoc": "^0.23.21", + "typescript": "^4.9.3" }, "engines": { "node": ">=14" @@ -340,6 +346,34 @@ "node": ">=6.9.0" } }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, "node_modules/@eslint/eslintrc": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.4.1.tgz", @@ -559,12 +593,56 @@ "node": ">= 8" } }, + "node_modules/@tsconfig/node10": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", + "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", + "dev": true + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz", + "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==", + "dev": true + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", + "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", + "dev": true + }, + "node_modules/@types/node": { + "version": "18.11.18", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.18.tgz", + "integrity": "sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==", + "dev": true + }, + "node_modules/@types/tap": { + "version": "15.0.7", + "resolved": "https://registry.npmjs.org/@types/tap/-/tap-15.0.7.tgz", + "integrity": "sha512-TTMajw4gxQfFgYbhXhy/Tb2OiNcwS+4oP/9yp1/GdU0pFJo3wtnkYhRgmQy39ksh+rnoa0VrPHJ4Tuv2cLNQ5A==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/acorn": { "version": "8.8.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==", "dev": true, - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -582,6 +660,15 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/aggregate-error": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", @@ -664,6 +751,12 @@ "integrity": "sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==", "dev": true }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, "node_modules/argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -762,6 +855,129 @@ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "dev": true }, + "node_modules/c8": { + "version": "7.12.0", + "resolved": "https://registry.npmjs.org/c8/-/c8-7.12.0.tgz", + "integrity": "sha512-CtgQrHOkyxr5koX1wEUmN/5cfDa2ckbHRA4Gy5LAL0zaCFtVWJS5++n+w4/sr2GWGerBxgTjpKeDclk/Qk6W/A==", + "dev": true, + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@istanbuljs/schema": "^0.1.3", + "find-up": "^5.0.0", + "foreground-child": "^2.0.0", + "istanbul-lib-coverage": "^3.2.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-reports": "^3.1.4", + "rimraf": "^3.0.2", + "test-exclude": "^6.0.0", + "v8-to-istanbul": "^9.0.0", + "yargs": "^16.2.0", + "yargs-parser": "^20.2.9" + }, + "bin": { + "c8": "bin/c8.js" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/c8/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/c8/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/c8/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/c8/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/c8/node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/c8/node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/c8/node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true, + "engines": { + "node": ">=10" + } + }, "node_modules/caching-transform": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz", @@ -915,6 +1131,12 @@ "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", "dev": true }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -2106,6 +2328,12 @@ "node": ">=6" } }, + "node_modules/jsonc-parser": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", + "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", + "dev": true + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -2187,6 +2415,12 @@ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "dev": true }, + "node_modules/lunr": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz", + "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==", + "dev": true + }, "node_modules/make-dir": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", @@ -2202,6 +2436,24 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "node_modules/marked": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.2.5.tgz", + "integrity": "sha512-jPueVhumq7idETHkb203WDD4fMA3yV9emQ5vLwop58lu8bTclMghBWcYAavlDqIEMaisADinV1TooIFCfqOsYQ==", + "dev": true, + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 12" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -2717,6 +2969,17 @@ "node": ">=8" } }, + "node_modules/shiki": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-0.12.1.tgz", + "integrity": "sha512-aieaV1m349rZINEBkjxh2QbBvFFQOlgqYTNtCal82hHj4dDZ76oMlQIX+C7ryerBTDiga3e5NfH6smjdJ02BbQ==", + "dev": true, + "dependencies": { + "jsonc-parser": "^3.2.0", + "vscode-oniguruma": "^1.7.0", + "vscode-textmate": "^8.0.0" + } + }, "node_modules/signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", @@ -4900,6 +5163,49 @@ "integrity": "sha512-dagAKX7vaesNNAwOc9Np9C2mJ+7YopF4lk+jE2JML9ta4kZ91Y6UruJNH65bLRYoUROD8EY+Pmi44qQWwXR7sw==", "dev": true }, + "node_modules/ts-node": { + "version": "10.9.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", + "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", + "dev": true, + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -4931,6 +5237,61 @@ "is-typedarray": "^1.0.0" } }, + "node_modules/typedoc": { + "version": "0.23.24", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.23.24.tgz", + "integrity": "sha512-bfmy8lNQh+WrPYcJbtjQ6JEEsVl/ce1ZIXyXhyW+a1vFrjO39t6J8sL/d6FfAGrJTc7McCXgk9AanYBSNvLdIA==", + "dev": true, + "dependencies": { + "lunr": "^2.3.9", + "marked": "^4.2.5", + "minimatch": "^5.1.2", + "shiki": "^0.12.1" + }, + "bin": { + "typedoc": "bin/typedoc" + }, + "engines": { + "node": ">= 14.14" + }, + "peerDependencies": { + "typescript": "4.6.x || 4.7.x || 4.8.x || 4.9.x" + } + }, + "node_modules/typedoc/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/typedoc/node_modules/minimatch": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.2.tgz", + "integrity": "sha512-bNH9mmM9qsJ2X4r2Nat1B//1dJVcn3+iBLa3IgqJ7EbGaDNepL9QSHOxN4ng33s52VMMhhIfgCYDk3C4ZmlDAg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/typescript": { + "version": "4.9.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz", + "integrity": "sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, "node_modules/unicode-length": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/unicode-length/-/unicode-length-2.1.0.tgz", @@ -4985,6 +5346,38 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true + }, + "node_modules/v8-to-istanbul": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.0.1.tgz", + "integrity": "sha512-74Y4LqY74kLE6IFyIjPtkSTWzUZmj8tdHT9Ii/26dvQ6K9Dl2NbEfj0XgU2sHCtKgt5VupqhlO/5aWuqS+IY1w==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^1.6.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/vscode-oniguruma": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.7.0.tgz", + "integrity": "sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==", + "dev": true + }, + "node_modules/vscode-textmate": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-8.0.0.tgz", + "integrity": "sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg==", + "dev": true + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -5198,12 +5591,20 @@ "node": ">=8" } }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true, - "peer": true, "engines": { "node": ">=10" }, diff --git a/package.json b/package.json index 71221b4c..073c7e3d 100644 --- a/package.json +++ b/package.json @@ -1,13 +1,17 @@ { "name": "rimraf", "version": "4.0.0-0", - "main": "lib/index.js", + "main": "./dist/cjs/src/index.js", + "module": "./dist/mjs/src/index.js", + "bin": "./dist/cjs/src/bin.js", "exports": { - ".": "./lib/index.js" + ".": { + "import": "./dist/mjs/src/index.js", + "require": "./dist/cjs/src/index.js" + } }, - "bin": "lib/bin.js", "files": [ - "lib" + "dist" ], "description": "A deep deletion module for node (like `rm -rf`)", "author": "Isaac Z. Schlueter (http://blog.izs.me/)", @@ -17,10 +21,16 @@ "preversion": "npm test", "postversion": "npm publish", "prepublishOnly": "git push origin --follow-tags", - "test": "tap", - "snap": "tap", + "preprepare": "rm -rf dist", + "prepare": "tsc -p tsconfig-cjs.json && tsc -p tsconfig-esm.json", + "postprepare": "bash fixup.sh", + "pretest": "npm run prepare", + "presnap": "npm run prepare", + "test": "c8 tap", + "snap": "c8 tap", "format": "prettier --write . --loglevel warn", - "benchmark": "node benchmark/index.js" + "benchmark": "node benchmark/index.js", + "typedoc": "typedoc --tsconfig tsconfig-esm.json ./src/*.ts" }, "prettier": { "semi": false, @@ -34,14 +44,26 @@ "endOfLine": "lf" }, "devDependencies": { + "@types/node": "^18.11.9", + "@types/tap": "^15.0.7", + "c8": "^7.12.0", "eslint-config-prettier": "^8.6.0", "mkdirp": "1", "prettier": "^2.8.2", - "tap": "^16.3.3" + "tap": "^16.3.3", + "ts-node": "^10.9.1", + "typedoc": "^0.23.21", + "typescript": "^4.9.3" }, "tap": { - "coverage-map": "map.js", - "libtap-settings": "libtap-settings.js" + "coverage": false, + "libtap-settings": "libtap-settings.js", + "node-arg": [ + "--no-warnings", + "--loader", + "ts-node/esm" + ], + "ts": false }, "funding": { "url": "https://github.com/sponsors/isaacs" diff --git a/lib/bin.js b/src/bin.ts similarity index 68% rename from lib/bin.js rename to src/bin.ts index ea00ae9e..f6bc3272 100755 --- a/lib/bin.js +++ b/src/bin.ts @@ -1,13 +1,11 @@ #!/usr/bin/env node - -const rimraf = require('./index.js') - -const { version } = require('../package.json') +import { version } from '../package.json' +import rimraf, { RimrafOptions } from './' const runHelpForUsage = () => console.error('run `rimraf --help` for usage information') -const help = `rimraf version ${version} +export const help = `rimraf version ${version} Usage: rimraf [ ...] Deletes all files and folders at "path", recursively. @@ -15,7 +13,7 @@ Deletes all files and folders at "path", recursively. Options: -- Treat all subsequent arguments as paths -h --help Display this usage info - --preserve-root Do not remove '/' (default) + --preserve-root Do not remove '/' recursively (default) --no-preserve-root Do not treat '/' specially --impl= Specify the implementationt to use. @@ -24,24 +22,27 @@ Options: manual: the platform-specific JS implementation posix: the Posix JS implementation windows: the Windows JS implementation + move-remove: a slower Windows JS fallback implementation Implementation-specific options: - --tmp= Folder to hold temp files for 'windows' implementation - --max-retries= maxRetries for the 'native' implementation - --retry-delay= retryDelay for the 'native' implementation + --tmp= Folder to hold temp files for 'move-remove' implementation + --max-retries= maxRetries for the 'native' and 'windows' implementations + --retry-delay= retryDelay for the 'native' implementation, default 100 + --backoff= Exponential backoff factor for retries (default: 1.2) ` -const { resolve, parse } = require('path') +import { parse, resolve } from 'path' -const main = async (...args) => { +const main = async (...args: string[]) => { if (process.env.__RIMRAF_TESTING_BIN_FAIL__ === '1') { throw new Error('simulated rimraf failure') } - const opt = {} - const paths = [] + const opt: RimrafOptions = {} + const paths: string[] = [] let dashdash = false - let impl = rimraf + let impl: (path: string | string[], opt?: RimrafOptions) => Promise = + rimraf for (const arg of args) { if (dashdash) { @@ -61,19 +62,23 @@ const main = async (...args) => { opt.preserveRoot = false continue } else if (/^--tmp=/.test(arg)) { - const val = arg.substr('--tmp='.length) + const val = arg.substring('--tmp='.length) opt.tmp = val continue } else if (/^--max-retries=/.test(arg)) { - const val = +arg.substr('--max-retries='.length) + const val = +arg.substring('--max-retries='.length) opt.maxRetries = val continue } else if (/^--retry-delay=/.test(arg)) { - const val = +arg.substr('--retry-delay='.length) + const val = +arg.substring('--retry-delay='.length) opt.retryDelay = val continue + } else if (/^--backoff=/.test(arg)) { + const val = +arg.substring('--backoff='.length) + opt.backoff = val + continue } else if (/^--impl=/.test(arg)) { - const val = arg.substr('--impl='.length) + const val = arg.substring('--impl='.length) switch (val) { case 'rimraf': impl = rimraf @@ -84,6 +89,9 @@ const main = async (...args) => { case 'windows': impl = rimraf[val] continue + case 'move-remove': + impl = rimraf.moveRemove + continue default: console.error(`unknown implementation: ${val}`) runHelpForUsage() @@ -117,9 +125,15 @@ const main = async (...args) => { await impl(paths, opt) return 0 } +main.help = help + +export default main -module.exports = Object.assign(main, { help }) -if (module === require.main) { +if ( + typeof require === 'function' && + typeof module === 'object' && + require.main === module +) { const args = process.argv.slice(2) main(...args).then( code => process.exit(code), diff --git a/lib/default-tmp.js b/src/default-tmp.ts similarity index 72% rename from lib/default-tmp.js rename to src/default-tmp.ts index ea1c1bf9..ad545174 100644 --- a/lib/default-tmp.js +++ b/src/default-tmp.ts @@ -8,15 +8,13 @@ // or resolve(path, '\\temp') if it exists, or the root of the drive if not. // On Posix (not that you'd be likely to use the windows algorithm there), // it uses os.tmpdir() always. -const platform = require('./platform.js') -const { - statSync, - promises: { stat }, -} = require('./fs.js') -const { tmpdir } = require('os') -const { parse, resolve } = require('path') +import { tmpdir } from 'os' +import { parse, resolve } from 'path' +import { promises, statSync } from './fs' +import platform from './platform' +const { stat } = promises -const isDirSync = path => { +const isDirSync = (path: string) => { try { return statSync(path).isDirectory() } catch (er) { @@ -24,13 +22,13 @@ const isDirSync = path => { } } -const isDir = path => +const isDir = (path: string) => stat(path).then( st => st.isDirectory(), () => false ) -const win32DefaultTmp = async path => { +const win32DefaultTmp = async (path: string) => { const { root } = parse(path) const tmp = tmpdir() const { root: tmpRoot } = parse(tmp) @@ -46,7 +44,7 @@ const win32DefaultTmp = async path => { return root } -const win32DefaultTmpSync = path => { +const win32DefaultTmpSync = (path: string) => { const { root } = parse(path) const tmp = tmpdir() const { root: tmpRoot } = parse(tmp) @@ -65,13 +63,7 @@ const win32DefaultTmpSync = path => { const posixDefaultTmp = async () => tmpdir() const posixDefaultTmpSync = () => tmpdir() -module.exports = - platform === 'win32' - ? { - defaultTmp: win32DefaultTmp, - defaultTmpSync: win32DefaultTmpSync, - } - : { - defaultTmp: posixDefaultTmp, - defaultTmpSync: posixDefaultTmpSync, - } +export const defaultTmp = + platform === 'win32' ? win32DefaultTmp : posixDefaultTmp +export const defaultTmpSync = + platform === 'win32' ? win32DefaultTmpSync : posixDefaultTmpSync diff --git a/src/fix-eperm.ts b/src/fix-eperm.ts new file mode 100644 index 00000000..d6389775 --- /dev/null +++ b/src/fix-eperm.ts @@ -0,0 +1,51 @@ +import { promises, chmodSync, FsError } from './fs' +const { chmod } = promises + +export const fixEPERM = + (fn: (path: string) => Promise) => async (path: string) => { + try { + return await fn(path) + } catch (er) { + const fer = er as FsError + if (fer?.code === 'ENOENT') { + return + } + if (fer?.code === 'EPERM') { + try { + await chmod(path, 0o666) + } catch (er2) { + const fer2 = er2 as FsError + if (fer2?.code === 'ENOENT') { + return + } + throw er + } + return await fn(path) + } + throw er + } + } + +export const fixEPERMSync = (fn: (path: string) => any) => (path: string) => { + try { + return fn(path) + } catch (er) { + const fer = er as FsError + if (fer?.code === 'ENOENT') { + return + } + if (fer?.code === 'EPERM') { + try { + chmodSync(path, 0o666) + } catch (er2) { + const fer2 = er2 as FsError + if (fer2?.code === 'ENOENT') { + return + } + throw er + } + return fn(path) + } + throw er + } +} diff --git a/src/fs.ts b/src/fs.ts new file mode 100644 index 00000000..fb5c450e --- /dev/null +++ b/src/fs.ts @@ -0,0 +1,80 @@ +// promisify ourselves, because older nodes don't have fs.promises + +import fs from 'fs' + +export type FsError = Error & { code?: string } + +// sync ones just take the sync version from node +export { + chmodSync, + mkdirSync, + readdirSync, + renameSync, + rmdirSync, + rmSync, + statSync, + unlinkSync, +} from 'fs' + +// unrolled for better inlining, this seems to get better performance +// than something like: +// const makeCb = (res, rej) => (er, ...d) => er ? rej(er) : res(...d) +// which would be a bit cleaner. + +const chmod = (path: fs.PathLike, mode: fs.Mode): Promise => + new Promise((res, rej) => + fs.chmod(path, mode, (er, ...d: any[]) => (er ? rej(er) : res(...d))) + ) + +const mkdir = ( + path: fs.PathLike, + options?: + | fs.Mode + | (fs.MakeDirectoryOptions & { recursive?: boolean | null }) + | undefined + | null +): Promise => + new Promise((res, rej) => + fs.mkdir(path, options, (er, made) => (er ? rej(er) : res(made))) + ) + +const readdir = (path: fs.PathLike): Promise => + new Promise((res, rej) => + fs.readdir(path, (er, data) => (er ? rej(er) : res(data))) + ) + +const rename = (oldPath: fs.PathLike, newPath: fs.PathLike): Promise => + new Promise((res, rej) => + fs.rename(oldPath, newPath, (er, ...d: any[]) => (er ? rej(er) : res(...d))) + ) + +const rm = (path: fs.PathLike, options: fs.RmOptions): Promise => + new Promise((res, rej) => + fs.rm(path, options, (er, ...d: any[]) => (er ? rej(er) : res(...d))) + ) + +const rmdir = (path: fs.PathLike): Promise => + new Promise((res, rej) => + fs.rmdir(path, (er, ...d: any[]) => (er ? rej(er) : res(...d))) + ) + +const stat = (path: fs.PathLike): Promise => + new Promise((res, rej) => + fs.stat(path, (er, data) => (er ? rej(er) : res(data))) + ) + +const unlink = (path: fs.PathLike): Promise => + new Promise((res, rej) => + fs.unlink(path, (er, ...d: any[]) => (er ? rej(er) : res(...d))) + ) + +export const promises = { + chmod, + mkdir, + readdir, + rename, + rm, + rmdir, + stat, + unlink, +} diff --git a/src/ignore-enoent.ts b/src/ignore-enoent.ts new file mode 100644 index 00000000..c2e8c87a --- /dev/null +++ b/src/ignore-enoent.ts @@ -0,0 +1,18 @@ +import { FsError } from './fs' + +export const ignoreENOENT = async (p: Promise) => + p.catch(er => { + if (er.code !== 'ENOENT') { + throw er + } + }) + +export const ignoreENOENTSync = (fn: () => any) => { + try { + return fn() + } catch (er) { + if ((er as FsError)?.code !== 'ENOENT') { + throw er + } + } +} diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 00000000..4f4f6e14 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,108 @@ +import optArg from './opt-arg' +import pathArg from './path-arg' + +export interface RimrafOptions { + preserveRoot?: boolean + tmp?: string + maxRetries?: number + retryDelay?: number + backoff?: number + maxBackoff?: number +} + +/* c8 ignore start */ +const typeOrUndef = (val: any, t: string) => + typeof val === 'undefined' || typeof val === t +/* c8 ignore stop */ + +export const isRimrafOptions = (o: any): o is RimrafOptions => + !!o && + typeof o === 'object' && + typeOrUndef(o.preserveRoot, 'boolean') && + typeOrUndef(o.preserveRoot, 'number') && + typeOrUndef(o.maxRetries, 'number') && + typeOrUndef(o.retryDelay, 'number') && + typeOrUndef(o.backoff, 'number') && + typeOrUndef(o.maxBackoff, 'number') + +/* c8 ignore start */ +export const assertRimrafOptions: (o: any) => void = ( + o: any +): asserts o is RimrafOptions => { + if (!isRimrafOptions(o)) { + throw new Error('invalid rimraf options') + } +} +/* c8 ignore stop */ + +import { rimrafManual, rimrafManualSync } from './rimraf-manual' +import { rimrafMoveRemove, rimrafMoveRemoveSync } from './rimraf-move-remove' +import { rimrafNative, rimrafNativeSync } from './rimraf-native' +import { rimrafPosix, rimrafPosixSync } from './rimraf-posix' +import { rimrafWindows, rimrafWindowsSync } from './rimraf-windows' +import { useNative, useNativeSync } from './use-native' + +const wrap = + (fn: (p: string, o: RimrafOptions) => Promise) => + async (path: string | string[], opt?: RimrafOptions): Promise => { + const options: RimrafOptions = optArg(opt) + await (Array.isArray(path) + ? Promise.all(path.map(p => fn(pathArg(p, options), options))) + : fn(pathArg(path, options), options)) + } + +const wrapSync = + (fn: (p: string, o: RimrafOptions) => void) => + (path: string | string[], opt?: RimrafOptions): void => { + const options = optArg(opt) + return Array.isArray(path) + ? path.forEach(p => fn(pathArg(p, options), options)) + : fn(pathArg(path, options), options) + } + +export const nativeSync = wrapSync(rimrafNativeSync) +export const native = Object.assign(wrap(rimrafNative), { sync: nativeSync }) + +export const manualSync = wrapSync(rimrafManualSync) +export const manual = Object.assign(wrap(rimrafManual), { sync: manualSync }) + +export const windowsSync = wrapSync(rimrafWindowsSync) +export const windows = Object.assign(wrap(rimrafWindows), { sync: windowsSync }) + +export const posixSync = wrapSync(rimrafPosixSync) +export const posix = Object.assign(wrap(rimrafPosix), { sync: posixSync }) + +export const moveRemoveSync = wrapSync(rimrafMoveRemoveSync) +export const moveRemove = Object.assign(wrap(rimrafMoveRemove), { + sync: moveRemoveSync, +}) + +export const rimrafSync = wrapSync((path, opt) => + useNativeSync() ? rimrafNativeSync(path, opt) : rimrafManualSync(path, opt) +) +export const sync = rimrafSync + +export const rimraf = Object.assign( + wrap((path, opt) => + useNative() ? rimrafNative(path, opt) : rimrafManual(path, opt) + ), + { + // this weirdness because it's easier than explicitly declaring + rimraf: manual, + sync: rimrafSync, + rimrafSync: rimrafSync, + manual, + manualSync, + native, + nativeSync, + posix, + posixSync, + windows, + windowsSync, + moveRemove, + moveRemoveSync, + } +) +rimraf.rimraf = rimraf + +export default rimraf diff --git a/src/opt-arg.ts b/src/opt-arg.ts new file mode 100644 index 00000000..5c739b5d --- /dev/null +++ b/src/opt-arg.ts @@ -0,0 +1,5 @@ +import { assertRimrafOptions, RimrafOptions } from './index' +export default (opt: RimrafOptions = {}) => { + assertRimrafOptions(opt) + return opt +} diff --git a/lib/path-arg.js b/src/path-arg.ts similarity index 81% rename from lib/path-arg.js rename to src/path-arg.ts index e15257d2..a6a36c78 100644 --- a/lib/path-arg.js +++ b/src/path-arg.ts @@ -1,7 +1,9 @@ -const platform = require('./platform.js') -const { resolve, parse } = require('path') -const { inspect } = require('util') -const pathArg = (path, opt = {}) => { +import platform from './platform' +import { resolve, parse } from 'path' +import { inspect } from 'util' +import { RimrafOptions } from './index' + +const pathArg = (path: string, opt: RimrafOptions = {}) => { const type = typeof path if (type !== 'string') { const ctor = path && type === 'object' && path.constructor @@ -42,7 +44,7 @@ const pathArg = (path, opt = {}) => { if (platform === 'win32') { const badWinChars = /[*|"<>?:]/ const { root } = parse(path) - if (badWinChars.test(path.substr(root.length))) { + if (badWinChars.test(path.substring(root.length))) { throw Object.assign(new Error('Illegal characters in path.'), { path, code: 'EINVAL', @@ -52,4 +54,5 @@ const pathArg = (path, opt = {}) => { return path } -module.exports = pathArg + +export default pathArg diff --git a/src/platform.ts b/src/platform.ts new file mode 100644 index 00000000..afdd49ba --- /dev/null +++ b/src/platform.ts @@ -0,0 +1 @@ +export default process.env.__TESTING_RIMRAF_PLATFORM__ || process.platform diff --git a/src/readdir-or-error.ts b/src/readdir-or-error.ts new file mode 100644 index 00000000..ff639694 --- /dev/null +++ b/src/readdir-or-error.ts @@ -0,0 +1,13 @@ +// returns an array of entries if readdir() works, +// or the error that readdir() raised if not. +import { FsError, promises, readdirSync } from './fs' +const { readdir } = promises +export const readdirOrError = (path: string) => + readdir(path).catch(er => er as FsError) +export const readdirOrErrorSync = (path: string) => { + try { + return readdirSync(path) + } catch (er) { + return er as FsError + } +} diff --git a/lib/retry-busy.js b/src/retry-busy.ts similarity index 55% rename from lib/retry-busy.js rename to src/retry-busy.ts index af7286a4..117f6f9b 100644 --- a/lib/retry-busy.js +++ b/src/retry-busy.ts @@ -1,21 +1,30 @@ // note: max backoff is the maximum that any *single* backoff will do -// -const MAXBACKOFF = 200 -const RATE = 1.2 -const MAXRETRIES = 10 -const codes = new Set(['EMFILE', 'ENFILE', 'EBUSY']) -const retryBusy = fn => { - const method = async (path, opt, backoff = 1, total = 0) => { +import { RimrafOptions } from '.' +import { FsError } from './fs' + +export const MAXBACKOFF = 200 +export const RATE = 1.2 +export const MAXRETRIES = 10 +export const codes = new Set(['EMFILE', 'ENFILE', 'EBUSY']) + +export const retryBusy = (fn: (path: string) => Promise) => { + const method = async ( + path: string, + opt: RimrafOptions, + backoff = 1, + total = 0 + ) => { const mbo = opt.maxBackoff || MAXBACKOFF const rate = opt.backoff || RATE - const max = opt.retries || MAXRETRIES + const max = opt.maxRetries || MAXRETRIES let retries = 0 while (true) { try { return await fn(path) } catch (er) { - if (codes.has(er.code)) { + const fer = er as FsError + if (fer?.code && codes.has(fer.code)) { backoff = Math.ceil(backoff * rate) total = backoff + total if (total < mbo) { @@ -39,15 +48,16 @@ const retryBusy = fn => { } // just retries, no async so no backoff -const retryBusySync = fn => { - const method = (path, opt) => { - const max = opt.retries || MAXRETRIES +export const retryBusySync = (fn: (path: string) => any) => { + const method = (path: string, opt: RimrafOptions) => { + const max = opt.maxRetries || MAXRETRIES let retries = 0 while (true) { try { return fn(path) } catch (er) { - if (codes.has(er.code) && retries < max) { + const fer = er as FsError + if (fer?.code && codes.has(fer.code) && retries < max) { retries++ continue } @@ -57,12 +67,3 @@ const retryBusySync = fn => { } return method } - -module.exports = { - MAXBACKOFF, - RATE, - MAXRETRIES, - codes, - retryBusy, - retryBusySync, -} diff --git a/src/rimraf-manual.ts b/src/rimraf-manual.ts new file mode 100644 index 00000000..c377a60a --- /dev/null +++ b/src/rimraf-manual.ts @@ -0,0 +1,8 @@ +import platform from './platform' + +import { rimrafPosix, rimrafPosixSync } from './rimraf-posix' +import { rimrafWindows, rimrafWindowsSync } from './rimraf-windows' + +export const rimrafManual = platform === 'win32' ? rimrafWindows : rimrafPosix +export const rimrafManualSync = + platform === 'win32' ? rimrafWindowsSync : rimrafPosixSync diff --git a/lib/rimraf-move-remove.js b/src/rimraf-move-remove.ts similarity index 68% rename from lib/rimraf-move-remove.js rename to src/rimraf-move-remove.ts index 4b758cd0..cbf23556 100644 --- a/lib/rimraf-move-remove.js +++ b/src/rimraf-move-remove.ts @@ -11,26 +11,29 @@ // // However, it is HELLA SLOW, like 2-10x slower than a naive recursive rm. -const { resolve, basename, parse } = require('path') -const { defaultTmp, defaultTmpSync } = require('./default-tmp.js') +import { basename, parse, resolve } from 'path' +import { defaultTmp, defaultTmpSync } from './default-tmp' -const { ignoreENOENT, ignoreENOENTSync } = require('./ignore-enoent.js') +import { ignoreENOENT, ignoreENOENTSync } from './ignore-enoent' -const { +import { + chmodSync, + FsError, + promises as fsPromises, renameSync, - unlinkSync, rmdirSync, - chmodSync, - promises: { rename, unlink, rmdir, chmod }, -} = require('./fs.js') + unlinkSync, +} from './fs' +const { rename, unlink, rmdir, chmod } = fsPromises -const { readdirOrError, readdirOrErrorSync } = require('./readdir-or-error.js') +import { RimrafOptions } from '.' +import { readdirOrError, readdirOrErrorSync } from './readdir-or-error' // crypto.randomBytes is much slower, and Math.random() is enough here -const uniqueFilename = path => `.${basename(path)}.${Math.random()}` +const uniqueFilename = (path: string) => `.${basename(path)}.${Math.random()}` -const unlinkFixEPERM = async path => - unlink(path).catch(er => { +const unlinkFixEPERM = async (path: string) => + unlink(path).catch((er: Error & { code?: string }) => { if (er.code === 'EPERM') { return chmod(path, 0o666).then( () => unlink(path), @@ -47,27 +50,30 @@ const unlinkFixEPERM = async path => throw er }) -const unlinkFixEPERMSync = path => { +const unlinkFixEPERMSync = (path: string) => { try { unlinkSync(path) } catch (er) { - if (er.code === 'EPERM') { + if ((er as FsError)?.code === 'EPERM') { try { return chmodSync(path, 0o666) } catch (er2) { - if (er2.code === 'ENOENT') { + if ((er2 as FsError)?.code === 'ENOENT') { return } throw er } - } else if (er.code === 'ENOENT') { + } else if ((er as FsError)?.code === 'ENOENT') { return } throw er } } -const rimrafMoveRemove = async (path, opt) => { +export const rimrafMoveRemove = async ( + path: string, + opt: RimrafOptions +): Promise => { if (!opt.tmp) { return rimrafMoveRemove(path, { ...opt, tmp: await defaultTmp(path) }) } @@ -103,16 +109,24 @@ const rimrafMoveRemove = async (path, opt) => { return await ignoreENOENT(tmpUnlink(path, opt.tmp, rmdir)) } -const tmpUnlink = async (path, tmp, rm) => { +const tmpUnlink = async ( + path: string, + tmp: string, + rm: (p: string) => Promise +) => { const tmpFile = resolve(tmp, uniqueFilename(path)) await rename(path, tmpFile) return await rm(tmpFile) } -const rimrafMoveRemoveSync = (path, opt) => { +export const rimrafMoveRemoveSync = ( + path: string, + opt: RimrafOptions +): void => { if (!opt.tmp) { return rimrafMoveRemoveSync(path, { ...opt, tmp: defaultTmpSync(path) }) } + const tmp: string = opt.tmp if (path === opt.tmp && parse(path).root !== path) { throw new Error('cannot delete temp directory used for deletion') @@ -128,9 +142,7 @@ const rimrafMoveRemoveSync = (path, opt) => { throw entries } - return ignoreENOENTSync(() => - tmpUnlinkSync(path, opt.tmp, unlinkFixEPERMSync) - ) + return ignoreENOENTSync(() => tmpUnlinkSync(path, tmp, unlinkFixEPERMSync)) } for (const entry of entries) { @@ -141,16 +153,15 @@ const rimrafMoveRemoveSync = (path, opt) => { return } - return ignoreENOENTSync(() => tmpUnlinkSync(path, opt.tmp, rmdirSync)) + return ignoreENOENTSync(() => tmpUnlinkSync(path, tmp, rmdirSync)) } -const tmpUnlinkSync = (path, tmp, rmSync) => { +const tmpUnlinkSync = ( + path: string, + tmp: string, + rmSync: (p: string) => void +) => { const tmpFile = resolve(tmp, uniqueFilename(path)) renameSync(path, tmpFile) return rmSync(tmpFile) } - -module.exports = { - rimrafMoveRemove, - rimrafMoveRemoveSync, -} diff --git a/src/rimraf-native.ts b/src/rimraf-native.ts new file mode 100644 index 00000000..74b23243 --- /dev/null +++ b/src/rimraf-native.ts @@ -0,0 +1,17 @@ +import { RimrafOptions } from '.' +import { promises, rmSync } from './fs' +const { rm } = promises + +export const rimrafNative = (path: string, opt: RimrafOptions) => + rm(path, { + ...opt, + force: true, + recursive: true, + }) + +export const rimrafNativeSync = (path: string, opt: RimrafOptions) => + rmSync(path, { + ...opt, + force: true, + recursive: true, + }) diff --git a/lib/rimraf-posix.js b/src/rimraf-posix.ts similarity index 77% rename from lib/rimraf-posix.js rename to src/rimraf-posix.ts index 3be1d6c6..72dedac9 100644 --- a/lib/rimraf-posix.js +++ b/src/rimraf-posix.ts @@ -5,19 +5,17 @@ // calls), because sunos will let root unlink a directory, and some // SUPER weird breakage happens as a result. -const { - rmdirSync, - unlinkSync, - promises: { rmdir, unlink }, -} = require('./fs.js') +import { promises, rmdirSync, unlinkSync } from './fs' +const { rmdir, unlink } = promises -const { resolve, parse } = require('path') +import { parse, resolve } from 'path' -const { readdirOrError, readdirOrErrorSync } = require('./readdir-or-error.js') +import { readdirOrError, readdirOrErrorSync } from './readdir-or-error' -const { ignoreENOENT, ignoreENOENTSync } = require('./ignore-enoent.js') +import { RimrafOptions } from '.' +import { ignoreENOENT, ignoreENOENTSync } from './ignore-enoent' -const rimrafPosix = async (path, opt) => { +export const rimrafPosix = async (path: string, opt: RimrafOptions) => { const entries = await readdirOrError(path) if (!Array.isArray(entries)) { if (entries.code === 'ENOENT') { @@ -42,7 +40,7 @@ const rimrafPosix = async (path, opt) => { return ignoreENOENT(rmdir(path)) } -const rimrafPosixSync = (path, opt) => { +export const rimrafPosixSync = (path: string, opt: RimrafOptions) => { const entries = readdirOrErrorSync(path) if (!Array.isArray(entries)) { if (entries.code === 'ENOENT') { diff --git a/lib/rimraf-windows.js b/src/rimraf-windows.ts similarity index 72% rename from lib/rimraf-windows.js rename to src/rimraf-windows.ts index a566c6d8..785908c9 100644 --- a/lib/rimraf-windows.js +++ b/src/rimraf-windows.ts @@ -8,48 +8,43 @@ // // Note: "move then remove" is 2-10 times slower, and just as unreliable. -const { resolve, parse } = require('path') - -const { ignoreENOENT, ignoreENOENTSync } = require('./ignore-enoent.js') - -const { - unlinkSync, - rmdirSync, - promises: { unlink, rmdir }, -} = require('./fs.js') - -const { fixEPERM, fixEPERMSync } = require('./fix-eperm.js') - -const { readdirOrError, readdirOrErrorSync } = require('./readdir-or-error.js') - -const { retryBusy, retryBusySync } = require('./retry-busy.js') - -const { - rimrafMoveRemove, - rimrafMoveRemoveSync, -} = require('./rimraf-move-remove.js') +import { parse, resolve } from 'path' +import { ignoreENOENT, ignoreENOENTSync } from './ignore-enoent' +import { fixEPERM, fixEPERMSync } from './fix-eperm' +import { readdirOrError, readdirOrErrorSync } from './readdir-or-error' +import { retryBusy, retryBusySync } from './retry-busy' +import { rimrafMoveRemove, rimrafMoveRemoveSync } from './rimraf-move-remove' +import { FsError, promises, rmdirSync, unlinkSync } from './fs' +import { RimrafOptions } from '.' +const { unlink, rmdir } = promises const rimrafWindowsFile = retryBusy(fixEPERM(unlink)) const rimrafWindowsFileSync = retryBusySync(fixEPERMSync(unlinkSync)) const rimrafWindowsDir = retryBusy(fixEPERM(rmdir)) const rimrafWindowsDirSync = retryBusySync(fixEPERMSync(rmdirSync)) -const rimrafWindowsDirMoveRemoveFallback = async (path, opt) => { +const rimrafWindowsDirMoveRemoveFallback = async ( + path: string, + opt: RimrafOptions +) => { try { await rimrafWindowsDir(path, opt) } catch (er) { - if (er.code === 'ENOTEMPTY') { + if ((er as FsError)?.code === 'ENOTEMPTY') { return await rimrafMoveRemove(path, opt) } throw er } } -const rimrafWindowsDirMoveRemoveFallbackSync = (path, opt) => { +const rimrafWindowsDirMoveRemoveFallbackSync = ( + path: string, + opt: RimrafOptions +) => { try { rimrafWindowsDirSync(path, opt) } catch (er) { - if (er.code === 'ENOTEMPTY') { + if ((er as FsError)?.code === 'ENOTEMPTY') { return rimrafMoveRemoveSync(path, opt) } throw er @@ -61,7 +56,11 @@ const CHILD = Symbol('child') const FINISH = Symbol('finish') const states = new Set([START, CHILD, FINISH]) -const rimrafWindows = async (path, opt, state = START) => { +export const rimrafWindows = async ( + path: string, + opt: RimrafOptions, + state = START +): Promise => { if (!states.has(state)) { throw new TypeError('invalid third argument passed to rimraf') } @@ -94,7 +93,11 @@ const rimrafWindows = async (path, opt, state = START) => { } } -const rimrafWindowsSync = (path, opt, state = START) => { +export const rimrafWindowsSync = ( + path: string, + opt: RimrafOptions, + state = START +): void => { if (!states.has(state)) { throw new TypeError('invalid third argument passed to rimraf') } @@ -127,8 +130,3 @@ const rimrafWindowsSync = (path, opt, state = START) => { }) } } - -module.exports = { - rimrafWindows, - rimrafWindowsSync, -} diff --git a/src/use-native.ts b/src/use-native.ts new file mode 100644 index 00000000..92533d6a --- /dev/null +++ b/src/use-native.ts @@ -0,0 +1,5 @@ +const version = process.env.__TESTING_RIMRAF_NODE_VERSION__ || process.version +const versArr = version.replace(/^v/, '').split('.') +const hasNative = +versArr[0] > 14 || (+versArr[0] === 14 && +versArr[1] >= 14) +export const useNative = !hasNative ? () => false : () => true +export const useNativeSync = !hasNative ? () => false : () => true diff --git a/tap-snapshots/test/index.js.test.cjs b/tap-snapshots/test/index.js.test.cjs index fa006ef9..fa1e9960 100644 --- a/tap-snapshots/test/index.js.test.cjs +++ b/tap-snapshots/test/index.js.test.cjs @@ -19,9 +19,7 @@ Array [ ], Array [ "useNative", - Object { - "a": 1, - }, + undefined, ], Array [ "rimrafPosix", @@ -42,9 +40,7 @@ Array [ ], Array [ "useNativeSync", - Object { - "a": 2, - }, + undefined, ], Array [ "rimrafPosixSync", @@ -70,9 +66,7 @@ Array [ ], Array [ "useNative", - Object { - "a": 1, - }, + undefined, ], Array [ "rimrafNative", @@ -93,9 +87,7 @@ Array [ ], Array [ "useNativeSync", - Object { - "a": 2, - }, + undefined, ], Array [ "rimrafNativeSync", diff --git a/test/bin.js b/test/bin.js index ec4fcbc9..73f37a8e 100644 --- a/test/bin.js +++ b/test/bin.js @@ -13,14 +13,15 @@ t.test('basic arg parsing stuff', t => { const CALLS = [] const rimraf = async (path, opt) => CALLS.push(['rimraf', path, opt]) - const bin = t.mock('../lib/bin.js', { - '../lib/index.js': Object.assign(rimraf, { + const bin = t.mock('../dist/cjs/src/bin.js', { + '../dist/cjs/src/index.js': Object.assign(rimraf, { native: async (path, opt) => CALLS.push(['native', path, opt]), manual: async (path, opt) => CALLS.push(['manual', path, opt]), posix: async (path, opt) => CALLS.push(['posix', path, opt]), windows: async (path, opt) => CALLS.push(['windows', path, opt]), + moveRemove: async (path, opt) => CALLS.push(['move-remove', path, opt]), }), - }) + }).default t.afterEach(() => { LOGS.length = 0 @@ -92,6 +93,13 @@ t.test('basic arg parsing stuff', t => { t.same(CALLS, [['rimraf', ['foo'], { tmp: 'some-path' }]]) }) + t.test('--tmp=', async t => { + t.equal(await bin('--backoff=1.3', 'foo'), 0) + t.same(LOGS, []) + t.same(ERRS, []) + t.same(CALLS, [['rimraf', ['foo'], { backoff: 1.3 }]]) + }) + t.test('--max-retries=n', async t => { t.equal(await bin('--max-retries=100', 'foo'), 0) t.same(LOGS, []) @@ -126,7 +134,7 @@ t.test('basic arg parsing stuff', t => { t.same(CALLS, []) }) - const impls = ['rimraf', 'native', 'manual', 'posix', 'windows'] + const impls = ['rimraf', 'native', 'manual', 'posix', 'windows', 'move-remove'] for (const impl of impls) { t.test(`--impl=${impl}`, async t => { t.equal(await bin('foo', `--impl=${impl}`), 0) @@ -148,7 +156,7 @@ t.test('actually delete something with it', async t => { }, }) - const bin = require.resolve('../lib/bin.js') + const bin = require.resolve('../dist/cjs/src/bin.js') const { spawnSync } = require('child_process') const res = spawnSync(process.execPath, [bin, path]) const { statSync } = require('fs') @@ -165,7 +173,7 @@ t.test('print failure when impl throws', async t => { }, }) - const bin = require.resolve('../lib/bin.js') + const bin = require.resolve('../dist/cjs/src/bin.js') const { spawnSync } = require('child_process') const res = spawnSync(process.execPath, [bin, path], { env: { diff --git a/test/default-tmp.js b/test/default-tmp.js index a42d7f36..18946f10 100644 --- a/test/default-tmp.js +++ b/test/default-tmp.js @@ -2,8 +2,8 @@ const t = require('tap') t.test('posix platform', async t => { const { tmpdir } = require('os') - const { defaultTmp, defaultTmpSync } = t.mock('../lib/default-tmp.js', { - '../lib/platform.js': 'posix', + const { defaultTmp, defaultTmpSync } = t.mock('../dist/cjs/src/default-tmp.js', { + '../dist/cjs/src/platform.js': 'posix', }) t.equal(defaultTmpSync('anything'), tmpdir()) t.equal(await defaultTmp('anything').then(t => t), tmpdir()) @@ -20,13 +20,13 @@ t.test('windows', async t => { throw Object.assign(new Error('no ents here'), { code: 'ENOENT' }) } } - const { defaultTmp, defaultTmpSync } = t.mock('../lib/default-tmp.js', { + const { defaultTmp, defaultTmpSync } = t.mock('../dist/cjs/src/default-tmp.js', { os: { tmpdir: () => 'C:\\Windows\\Temp', }, path: require('path').win32, - '../lib/platform.js': 'win32', - '../lib/fs.js': { + '../dist/cjs/src/platform.js': 'win32', + '../dist/cjs/src/fs.js': { statSync: tempDirCheck, promises: { stat: async path => tempDirCheck(path), diff --git a/test/delete-many-files.js b/test/delete-many-files.js index 541d558b..f1bb1374 100644 --- a/test/delete-many-files.js +++ b/test/delete-many-files.js @@ -15,10 +15,10 @@ const DEPTH = +process.env.RIMRAF_TEST_DEPTH || 4 const { statSync, - writeFileSync, mkdirSync, readdirSync, -} = require('../lib/fs.js') +} = require('../dist/cjs/src/fs.js') +const { writeFileSync } = require('fs') const { resolve, dirname } = require('path') const create = (path, depth = 0) => { diff --git a/test/fix-eperm.js b/test/fix-eperm.js index 37e87b0b..b4b2e45a 100644 --- a/test/fix-eperm.js +++ b/test/fix-eperm.js @@ -1,8 +1,8 @@ const t = require('tap') -const fs = require('../lib/fs.js') +const fs = require('../dist/cjs/src/fs.js') t.test('works if it works', async t => { - const { fixEPERM, fixEPERMSync } = t.mock('../lib/fix-eperm.js', {}) + const { fixEPERM, fixEPERMSync } = t.mock('../dist/cjs/src/fix-eperm.js', {}) const fixed = fixEPERM(() => 1) await fixed().then(n => t.equal(n, 1)) const fixedSync = fixEPERMSync(() => 1) @@ -10,7 +10,7 @@ t.test('works if it works', async t => { }) t.test('throw non-EPERM just throws', async t => { - const { fixEPERM, fixEPERMSync } = t.mock('../lib/fix-eperm.js', {}) + const { fixEPERM, fixEPERMSync } = t.mock('../dist/cjs/src/fix-eperm.js', {}) const fixed = fixEPERM(() => { throw new Error('oops') }) @@ -23,7 +23,7 @@ t.test('throw non-EPERM just throws', async t => { t.test('throw ENOENT returns void', async t => { const er = Object.assign(new Error('no ents'), { code: 'ENOENT' }) - const { fixEPERM, fixEPERMSync } = t.mock('../lib/fix-eperm.js', {}) + const { fixEPERM, fixEPERMSync } = t.mock('../dist/cjs/src/fix-eperm.js', {}) const fixed = fixEPERM(() => { throw er }) @@ -55,8 +55,8 @@ t.test('chmod and try again', async t => { t.equal(mode, 0o666) } const chmod = async (p, mode) => chmodSync(p, mode) - const { fixEPERM, fixEPERMSync } = t.mock('../lib/fix-eperm.js', { - '../lib/fs.js': { + const { fixEPERM, fixEPERMSync } = t.mock('../dist/cjs/src/fix-eperm.js', { + '../dist/cjs/src/fs.js': { promises: { chmod }, chmodSync, }, @@ -92,8 +92,8 @@ t.test('chmod ENOENT is fine, abort', async t => { throw Object.assign(new Error('no ent'), { code: 'ENOENT' }) } const chmod = async (p, mode) => chmodSync(p, mode) - const { fixEPERM, fixEPERMSync } = t.mock('../lib/fix-eperm.js', { - '../lib/fs.js': { + const { fixEPERM, fixEPERMSync } = t.mock('../dist/cjs/src/fix-eperm.js', { + '../dist/cjs/src/fs.js': { promises: { chmod }, chmodSync, }, @@ -129,8 +129,8 @@ t.test('chmod other than ENOENT is failure', async t => { throw Object.assign(new Error('ent bro'), { code: 'OHNO' }) } const chmod = async (p, mode) => chmodSync(p, mode) - const { fixEPERM, fixEPERMSync } = t.mock('../lib/fix-eperm.js', { - '../lib/fs.js': { + const { fixEPERM, fixEPERMSync } = t.mock('../dist/cjs/src/fix-eperm.js', { + '../dist/cjs/src/fs.js': { promises: { chmod }, chmodSync, }, diff --git a/test/fs.js b/test/fs.ts similarity index 50% rename from test/fs.js rename to test/fs.ts index 8e31623e..7e3a7d5c 100644 --- a/test/fs.js +++ b/test/fs.ts @@ -1,33 +1,46 @@ -const t = require('tap') +import t from 'tap' // verify that every function in the root is *Sync, and every // function is fs.promises is the promisified version of fs[method], // and that when the cb returns an error, the promised version fails, // and when the cb returns data, the promisified version resolves to it. -const fs = require('../lib/fs.js') -const realFS = require('fs') +import realFS from 'fs' +import * as fs from '../src/fs' + const mockFSMethodPass = - method => - (...args) => { + (method: string) => + (...args: any[]) => { const cb = args.pop() process.nextTick(() => cb(null, method, 1, 2, 3)) } const mockFSMethodFail = - method => - (...args) => { + (method: string) => + (...args: any[]) => { const cb = args.pop() process.nextTick(() => cb(new Error('oops'), method, 1, 2, 3)) } -const { useNative } = require('../lib/use-native.js') +import { useNative } from '../src/use-native' t.type(fs.promises, Object) -const mockFSPass = {} -const mockFSFail = {} -for (const method of Object.keys(fs.promises)) { +const mockFSPass: { [k: string]: (...a: any[]) => any } = {} +const mockFSFail: { [k: string]: (...a: any[]) => any } = {} +for (const method of Object.keys( + fs.promises as { [k: string]: (...a: any[]) => any } +)) { // of course fs.rm is missing when we shouldn't use native :) if (method !== 'rm' || useNative()) { - t.type(realFS[method], Function, `real fs.${method} is a function`) - t.equal(fs[`${method}Sync`], realFS[`${method}Sync`], `has ${method}Sync`) + t.type( + (realFS as { [k: string]: any })[method], + Function, + `real fs.${method} is a function` + ) + t.equal( + (fs as { [k: string]: any })[`${method}Sync`], + (realFS as unknown as { [k: string]: (...a: any[]) => any })[ + `${method}Sync` + ], + `has ${method}Sync` + ) } // set up our pass/fails for the next tests @@ -41,19 +54,27 @@ for (const method of Object.keys(fs)) { continue } const m = method.replace(/Sync$/, '') - t.type(fs.promises[m], Function, `fs.promises.${m} is a function`) + t.type( + (fs.promises as { [k: string]: (...a: any[]) => any })[m], + Function, + `fs.promises.${m} is a function` + ) } t.test('passing resolves promise', async t => { - const fs = t.mock('../lib/fs.js', { fs: mockFSPass }) - for (const [m, fn] of Object.entries(fs.promises)) { + const fs = t.mock('../src/fs', { fs: mockFSPass }) + for (const [m, fn] of Object.entries( + fs.promises as { [k: string]: (...a: any) => Promise } + )) { t.same(await fn(), m, `got expected value for ${m}`) } }) t.test('failing rejects promise', async t => { - const fs = t.mock('../lib/fs.js', { fs: mockFSFail }) - for (const [m, fn] of Object.entries(fs.promises)) { + const fs = t.mock('../src/fs', { fs: mockFSFail }) + for (const [m, fn] of Object.entries( + fs.promises as { [k: string]: (...a: any[]) => Promise } + )) { t.rejects(fn(), { message: 'oops' }, `got expected value for ${m}`) } }) diff --git a/test/ignore-enoent.js b/test/ignore-enoent.js index 39e84e82..86908194 100644 --- a/test/ignore-enoent.js +++ b/test/ignore-enoent.js @@ -1,5 +1,5 @@ const t = require('tap') -const { ignoreENOENT, ignoreENOENTSync } = require('../lib/ignore-enoent.js') +const { ignoreENOENT, ignoreENOENTSync } = require('../dist/cjs/src/ignore-enoent.js') const enoent = Object.assign(new Error('no ent'), { code: 'ENOENT' }) const eperm = Object.assign(new Error('eperm'), { code: 'EPERM' }) diff --git a/test/index.js b/test/index.js index 7f9611fb..c64a0a90 100644 --- a/test/index.js +++ b/test/index.js @@ -2,7 +2,12 @@ const t = require('tap') t.same( require('../package.json').exports, - { '.': './lib/index.js' }, + { + '.': { + import: './dist/mjs/src/index.js', + require: './dist/cjs/src/index.js', + }, + }, 'nothing else exported except main' ) @@ -11,7 +16,7 @@ t.test('mocky unit tests to select the correct function', t => { const CALLS = [] let USE_NATIVE = true const mocks = { - '../lib/use-native.js': { + '../dist/cjs/src/use-native.js': { useNative: opt => { CALLS.push(['useNative', opt]) return USE_NATIVE @@ -21,15 +26,15 @@ t.test('mocky unit tests to select the correct function', t => { return USE_NATIVE }, }, - '../lib/path-arg.js': path => { + '../dist/cjs/src/path-arg.js': path => { CALLS.push(['pathArg', path]) return path }, - '../lib/opt-arg.js': opt => { + '../dist/cjs/src/opt-arg.js': opt => { CALLS.push(['optArg', opt]) return opt }, - '../lib/rimraf-posix.js': { + '../dist/cjs/src/rimraf-posix.js': { rimrafPosix: async (path, opt) => { CALLS.push(['rimrafPosix', path, opt]) }, @@ -37,7 +42,7 @@ t.test('mocky unit tests to select the correct function', t => { CALLS.push(['rimrafPosixSync', path, opt]) }, }, - '../lib/rimraf-windows.js': { + '../dist/cjs/src/rimraf-windows.js': { rimrafWindows: async (path, opt) => { CALLS.push(['rimrafWindows', path, opt]) }, @@ -45,7 +50,7 @@ t.test('mocky unit tests to select the correct function', t => { CALLS.push(['rimrafWindowsSync', path, opt]) }, }, - '../lib/rimraf-native.js': { + '../dist/cjs/src/rimraf-native.js': { rimrafNative: async (path, opt) => { CALLS.push(['rimrafNative', path, opt]) }, @@ -55,7 +60,7 @@ t.test('mocky unit tests to select the correct function', t => { }, } process.env.__TESTING_RIMRAF_PLATFORM__ = 'posix' - const rimraf = t.mock('../', mocks) + const rimraf = t.mock('../', mocks).default t.afterEach(() => (CALLS.length = 0)) for (const useNative of [true, false]) { @@ -127,8 +132,8 @@ t.test('actually delete some stuff', t => { }, }, } - const rimraf = require('../') - const { statSync } = require('../lib/fs.js') + const { rimraf } = require('../') + const { statSync } = require('../dist/cjs/src/fs.js') t.test('sync', t => { const path = t.testdir(fixture) rimraf.sync(path) @@ -148,11 +153,11 @@ t.test('accept array of paths as first arg', async t => { const ASYNC_CALLS = [] const SYNC_CALLS = [] const { rimraf, rimrafSync } = t.mock('../', { - '../lib/use-native.js': { + '../dist/cjs/src/use-native.js': { useNative: () => true, useNativeSync: () => true, }, - '../lib/rimraf-native.js': { + '../dist/cjs/src/rimraf-native.js': { rimrafNative: async (path, opt) => ASYNC_CALLS.push([path, opt]), rimrafNativeSync: (path, opt) => SYNC_CALLS.push([path, opt]), }, diff --git a/test/opt-arg.js b/test/opt-arg.js index 4d6c34f1..cf14d13d 100644 --- a/test/opt-arg.js +++ b/test/opt-arg.js @@ -1,5 +1,5 @@ const t = require('tap') -const optArg = require('../lib/opt-arg.js') +const optArg = require('../dist/cjs/src/opt-arg.js').default const opt = { a: 1 } t.equal(optArg(opt), opt, 'returns object if provided') t.same(optArg(), {}, 'returns new object otherwise') diff --git a/test/path-arg.js b/test/path-arg.js index abc71450..a917bc44 100644 --- a/test/path-arg.js +++ b/test/path-arg.js @@ -12,9 +12,9 @@ if (!process.env.__TESTING_RIMRAF_PLATFORM__) { const platform = process.env.__TESTING_RIMRAF_PLATFORM__ || process.platform const path = require('path')[platform] || require('path') -const pathArg = t.mock('../lib/path-arg.js', { +const pathArg = t.mock('../dist/cjs/src/path-arg.js', { path, -}) +}).default const { resolve } = path t.equal(pathArg('a/b/c'), resolve('a/b/c')) diff --git a/test/platform.js b/test/platform.js index 09563c33..c1ca077d 100644 --- a/test/platform.js +++ b/test/platform.js @@ -1,10 +1,10 @@ const t = require('tap') t.test('actual platform', t => { - t.equal(require('../lib/platform.js'), process.platform) + t.equal(require('../dist/cjs/src/platform.js').default, process.platform) t.end() }) t.test('fake platform', t => { process.env.__TESTING_RIMRAF_PLATFORM__ = 'not actual platform' - t.equal(t.mock('../lib/platform.js'), 'not actual platform') + t.equal(t.mock('../dist/cjs/src/platform.js').default, 'not actual platform') t.end() }) diff --git a/test/readdir-or-error.js b/test/readdir-or-error.js index fc590d1a..251bb867 100644 --- a/test/readdir-or-error.js +++ b/test/readdir-or-error.js @@ -2,7 +2,7 @@ const t = require('tap') const { readdirOrError, readdirOrErrorSync, -} = require('../lib/readdir-or-error.js') +} = require('../dist/cjs/src/readdir-or-error.js') const path = t.testdir({ file: 'file', diff --git a/test/retry-busy.js b/test/retry-busy.js index a16f7031..3a969e80 100644 --- a/test/retry-busy.js +++ b/test/retry-busy.js @@ -5,7 +5,7 @@ const { RATE, MAXRETRIES, codes, -} = require('../lib/retry-busy.js') +} = require('../dist/cjs/src/retry-busy.js') const t = require('tap') @@ -72,7 +72,7 @@ t.test('retry and eventually give up', t => { t.plan(codes.size) const opt = { maxBackoff: 2, - retries: 2, + maxRetries: 2, } for (const code of codes) { diff --git a/test/rimraf-manual.js b/test/rimraf-manual.js index 0cb9263e..d6c42d90 100644 --- a/test/rimraf-manual.js +++ b/test/rimraf-manual.js @@ -1,10 +1,10 @@ const t = require('tap') -const { rimrafWindows, rimrafWindowsSync } = require('../lib/rimraf-windows.js') +const { rimrafWindows, rimrafWindowsSync } = require('../dist/cjs/src/rimraf-windows.js') -const { rimrafPosix, rimrafPosixSync } = require('../lib/rimraf-posix.js') +const { rimrafPosix, rimrafPosixSync } = require('../dist/cjs/src/rimraf-posix.js') -const { rimrafManual, rimrafManualSync } = require('../lib/rimraf-manual.js') +const { rimrafManual, rimrafManualSync } = require('../dist/cjs/src/rimraf-manual.js') if (!process.env.__TESTING_RIMRAF_PLATFORM__) { const otherPlatform = process.platform !== 'win32' ? 'win32' : 'posix' diff --git a/test/rimraf-move-remove.js b/test/rimraf-move-remove.js index ee259b18..91de76e0 100644 --- a/test/rimraf-move-remove.js +++ b/test/rimraf-move-remove.js @@ -33,7 +33,7 @@ const fixture = { } t.only('actually delete some stuff', async t => { - const fs = require('../lib/fs.js') + const fs = require('../dist/cjs/src/fs.js') const fsMock = { ...fs, promises: { ...fs.promises } } // simulate annoying windows semantics, where an unlink or rmdir @@ -57,13 +57,13 @@ t.only('actually delete some stuff', async t => { // but actually do wait to clean them up, though t.teardown(() => Promise.all(danglers)) - const { rimrafPosix, rimrafPosixSync } = t.mock('../lib/rimraf-posix.js', { - '../lib/fs.js': fsMock, + const { rimrafPosix, rimrafPosixSync } = t.mock('../dist/cjs/src/rimraf-posix.js', { + '../dist/cjs/src/fs.js': fsMock, }) const { rimrafMoveRemove, rimrafMoveRemoveSync } = t.mock( - '../lib/rimraf-move-remove.js', - { '../lib/fs.js': fsMock } + '../dist/cjs/src/rimraf-move-remove.js', + { '../dist/cjs/src/fs.js': fsMock } ) t.test('posix does not work here', t => { @@ -101,15 +101,15 @@ t.only('actually delete some stuff', async t => { }) t.only('throw unlink errors', async t => { - const fs = require('../lib/fs.js') + const fs = require('../dist/cjs/src/fs.js') // only throw once here, or else it messes with tap's fixture cleanup // that's probably a bug in t.mock? let threwAsync = false let threwSync = false const { rimrafMoveRemove, rimrafMoveRemoveSync } = t.mock( - '../lib/rimraf-move-remove.js', + '../dist/cjs/src/rimraf-move-remove.js', { - '../lib/fs.js': { + '../dist/cjs/src/fs.js': { ...fs, unlinkSync: path => { if (threwSync) { @@ -146,13 +146,13 @@ t.only('throw unlink errors', async t => { }) t.only('ignore ENOENT unlink errors', async t => { - const fs = require('../lib/fs.js') + const fs = require('../dist/cjs/src/fs.js') const threwAsync = false let threwSync = false const { rimrafMoveRemove, rimrafMoveRemoveSync } = t.mock( - '../lib/rimraf-move-remove.js', + '../dist/cjs/src/rimraf-move-remove.js', { - '../lib/fs.js': { + '../dist/cjs/src/fs.js': { ...fs, unlinkSync: path => { fs.unlinkSync(path) @@ -191,11 +191,11 @@ t.only('ignore ENOENT unlink errors', async t => { }) t.test('throw rmdir errors', async t => { - const fs = require('../lib/fs.js') + const fs = require('../dist/cjs/src/fs.js') const { rimrafMoveRemove, rimrafMoveRemoveSync } = t.mock( - '../lib/rimraf-move-remove.js', + '../dist/cjs/src/rimraf-move-remove.js', { - '../lib/fs.js': { + '../dist/cjs/src/fs.js': { ...fs, rmdirSync: path => { throw Object.assign(new Error('cannot rmdir'), { code: 'FOO' }) @@ -225,11 +225,11 @@ t.test('throw rmdir errors', async t => { }) t.test('throw unexpected readdir errors', async t => { - const fs = require('../lib/fs.js') + const fs = require('../dist/cjs/src/fs.js') const { rimrafMoveRemove, rimrafMoveRemoveSync } = t.mock( - '../lib/rimraf-move-remove.js', + '../dist/cjs/src/rimraf-move-remove.js', { - '../lib/fs.js': { + '../dist/cjs/src/fs.js': { ...fs, readdirSync: path => { throw Object.assign(new Error('cannot readdir'), { code: 'FOO' }) @@ -260,7 +260,7 @@ t.test('throw unexpected readdir errors', async t => { t.test('refuse to delete the root dir', async t => { const { rimrafMoveRemove, rimrafMoveRemoveSync } = t.mock( - '../lib/rimraf-move-remove.js', + '../dist/cjs/src/rimraf-move-remove.js', { path: { ...require('path'), @@ -279,14 +279,14 @@ t.test('refuse to delete the root dir', async t => { }) t.test('handle EPERMs on unlink by trying to chmod 0o666', async t => { - const fs = require('../lib/fs.js') + const fs = require('../dist/cjs/src/fs.js') const CHMODS = [] let threwAsync = false let threwSync = false const { rimrafMoveRemove, rimrafMoveRemoveSync } = t.mock( - '../lib/rimraf-move-remove.js', + '../dist/cjs/src/rimraf-move-remove.js', { - '../lib/fs.js': { + '../dist/cjs/src/fs.js': { ...fs, chmodSync: (...args) => { CHMODS.push(['chmodSync', ...args]) @@ -337,14 +337,14 @@ t.test('handle EPERMs on unlink by trying to chmod 0o666', async t => { }) t.test('handle EPERMs, chmod returns ENOENT', async t => { - const fs = require('../lib/fs.js') + const fs = require('../dist/cjs/src/fs.js') const CHMODS = [] let threwAsync = false let threwSync = false const { rimrafMoveRemove, rimrafMoveRemoveSync } = t.mock( - '../lib/rimraf-move-remove.js', + '../dist/cjs/src/rimraf-move-remove.js', { - '../lib/fs.js': { + '../dist/cjs/src/fs.js': { ...fs, chmodSync: (...args) => { CHMODS.push(['chmodSync', ...args]) @@ -401,14 +401,14 @@ t.test('handle EPERMs, chmod returns ENOENT', async t => { }) t.test('handle EPERMs, chmod raises something other than ENOENT', async t => { - const fs = require('../lib/fs.js') + const fs = require('../dist/cjs/src/fs.js') const CHMODS = [] let threwAsync = false let threwSync = false const { rimrafMoveRemove, rimrafMoveRemoveSync } = t.mock( - '../lib/rimraf-move-remove.js', + '../dist/cjs/src/rimraf-move-remove.js', { - '../lib/fs.js': { + '../dist/cjs/src/fs.js': { ...fs, chmodSync: (...args) => { CHMODS.push(['chmodSync', ...args]) @@ -465,11 +465,11 @@ t.test('handle EPERMs, chmod raises something other than ENOENT', async t => { }) t.test('rimraffing root, do not actually rmdir root', async t => { - const fs = require('../lib/fs.js') + const fs = require('../dist/cjs/src/fs.js') let ROOT = null const { parse } = require('path') const { rimrafMoveRemove, rimrafMoveRemoveSync } = t.mock( - '../lib/rimraf-move-remove.js', + '../dist/cjs/src/rimraf-move-remove.js', { path: { ...require('path'), diff --git a/test/rimraf-native.js b/test/rimraf-native.js index 45496f94..900cb3dd 100644 --- a/test/rimraf-native.js +++ b/test/rimraf-native.js @@ -12,8 +12,8 @@ const fs = { }, } -const { rimrafNative, rimrafNativeSync } = t.mock('../lib/rimraf-native.js', { - '../lib/fs.js': fs, +const { rimrafNative, rimrafNativeSync } = t.mock('../dist/cjs/src/rimraf-native.js', { + '../dist/cjs/src/fs.js': fs, }) t.test('calls the right node function', async t => { diff --git a/test/rimraf-posix.js b/test/rimraf-posix.js index 69adc212..4ce17017 100644 --- a/test/rimraf-posix.js +++ b/test/rimraf-posix.js @@ -8,9 +8,9 @@ // } const t = require('tap') -const { rimrafPosix, rimrafPosixSync } = require('../lib/rimraf-posix.js') +const { rimrafPosix, rimrafPosixSync } = require('../dist/cjs/src/rimraf-posix.js') -const fs = require('../lib/fs.js') +const fs = require('../dist/cjs/src/fs.js') const fixture = { a: 'a', @@ -56,8 +56,8 @@ t.test('actually delete some stuff', t => { }) t.test('throw unlink errors', async t => { - const { rimrafPosix, rimrafPosixSync } = t.mock('../lib/rimraf-posix.js', { - '../lib/fs.js': { + const { rimrafPosix, rimrafPosixSync } = t.mock('../dist/cjs/src/rimraf-posix.js', { + '../dist/cjs/src/fs.js': { ...fs, unlinkSync: path => { throw Object.assign(new Error('cannot unlink'), { code: 'FOO' }) @@ -76,8 +76,8 @@ t.test('throw unlink errors', async t => { }) t.test('throw rmdir errors', async t => { - const { rimrafPosix, rimrafPosixSync } = t.mock('../lib/rimraf-posix.js', { - '../lib/fs.js': { + const { rimrafPosix, rimrafPosixSync } = t.mock('../dist/cjs/src/rimraf-posix.js', { + '../dist/cjs/src/fs.js': { ...fs, rmdirSync: path => { throw Object.assign(new Error('cannot rmdir'), { code: 'FOO' }) @@ -96,8 +96,8 @@ t.test('throw rmdir errors', async t => { }) t.test('throw unexpected readdir errors', async t => { - const { rimrafPosix, rimrafPosixSync } = t.mock('../lib/rimraf-posix.js', { - '../lib/fs.js': { + const { rimrafPosix, rimrafPosixSync } = t.mock('../dist/cjs/src/rimraf-posix.js', { + '../dist/cjs/src/fs.js': { ...fs, readdirSync: path => { throw Object.assign(new Error('cannot readdir'), { code: 'FOO' }) @@ -116,8 +116,8 @@ t.test('throw unexpected readdir errors', async t => { }) t.test('ignore ENOENTs from unlink/rmdir', async t => { - const { rimrafPosix, rimrafPosixSync } = t.mock('../lib/rimraf-posix.js', { - '../lib/fs.js': { + const { rimrafPosix, rimrafPosixSync } = t.mock('../dist/cjs/src/rimraf-posix.js', { + '../dist/cjs/src/fs.js': { ...fs, // simulate a case where two rimrafs are happening in parallel, // so the deletion happens AFTER the readdir, but before ours. @@ -161,7 +161,7 @@ t.test('ignore ENOENTs from unlink/rmdir', async t => { t.test('rimraffing root, do not actually rmdir root', async t => { let ROOT = null const { parse } = require('path') - const { rimrafPosix, rimrafPosixSync } = t.mock('../lib/rimraf-posix.js', { + const { rimrafPosix, rimrafPosixSync } = t.mock('../dist/cjs/src/rimraf-posix.js', { path: { ...require('path'), parse: path => { diff --git a/test/rimraf-windows.js b/test/rimraf-windows.js index a02c060e..7c151320 100644 --- a/test/rimraf-windows.js +++ b/test/rimraf-windows.js @@ -33,7 +33,7 @@ const fixture = { } t.only('actually delete some stuff', async t => { - const fs = require('../lib/fs.js') + const fs = require('../dist/cjs/src/fs.js') const fsMock = { ...fs, promises: { ...fs.promises } } // simulate annoying windows semantics, where an unlink or rmdir @@ -57,13 +57,13 @@ t.only('actually delete some stuff', async t => { // but actually do wait to clean them up, though t.teardown(() => Promise.all(danglers)) - const { rimrafPosix, rimrafPosixSync } = t.mock('../lib/rimraf-posix.js', { - '../lib/fs.js': fsMock, + const { rimrafPosix, rimrafPosixSync } = t.mock('../dist/cjs/src/rimraf-posix.js', { + '../dist/cjs/src/fs.js': fsMock, }) const { rimrafWindows, rimrafWindowsSync } = t.mock( - '../lib/rimraf-windows.js', - { '../lib/fs.js': fsMock } + '../dist/cjs/src/rimraf-windows.js', + { '../dist/cjs/src/fs.js': fsMock } ) t.test('posix does not work here', t => { @@ -101,15 +101,15 @@ t.only('actually delete some stuff', async t => { }) t.only('throw unlink errors', async t => { - const fs = require('../lib/fs.js') + const fs = require('../dist/cjs/src/fs.js') // only throw once here, or else it messes with tap's fixture cleanup // that's probably a bug in t.mock? let threwAsync = false let threwSync = false const { rimrafWindows, rimrafWindowsSync } = t.mock( - '../lib/rimraf-windows.js', + '../dist/cjs/src/rimraf-windows.js', { - '../lib/fs.js': { + '../dist/cjs/src/fs.js': { ...fs, unlinkSync: path => { if (threwSync) { @@ -146,13 +146,13 @@ t.only('throw unlink errors', async t => { }) t.only('ignore ENOENT unlink errors', async t => { - const fs = require('../lib/fs.js') + const fs = require('../dist/cjs/src/fs.js') const threwAsync = false let threwSync = false const { rimrafWindows, rimrafWindowsSync } = t.mock( - '../lib/rimraf-windows.js', + '../dist/cjs/src/rimraf-windows.js', { - '../lib/fs.js': { + '../dist/cjs/src/fs.js': { ...fs, unlinkSync: path => { fs.unlinkSync(path) @@ -191,11 +191,11 @@ t.only('ignore ENOENT unlink errors', async t => { }) t.test('throw rmdir errors', async t => { - const fs = require('../lib/fs.js') + const fs = require('../dist/cjs/src/fs.js') const { rimrafWindows, rimrafWindowsSync } = t.mock( - '../lib/rimraf-windows.js', + '../dist/cjs/src/rimraf-windows.js', { - '../lib/fs.js': { + '../dist/cjs/src/fs.js': { ...fs, rmdirSync: path => { throw Object.assign(new Error('cannot rmdir'), { code: 'FOO' }) @@ -225,11 +225,11 @@ t.test('throw rmdir errors', async t => { }) t.test('throw unexpected readdir errors', async t => { - const fs = require('../lib/fs.js') + const fs = require('../dist/cjs/src/fs.js') const { rimrafWindows, rimrafWindowsSync } = t.mock( - '../lib/rimraf-windows.js', + '../dist/cjs/src/rimraf-windows.js', { - '../lib/fs.js': { + '../dist/cjs/src/fs.js': { ...fs, readdirSync: path => { throw Object.assign(new Error('cannot readdir'), { code: 'FOO' }) @@ -259,14 +259,14 @@ t.test('throw unexpected readdir errors', async t => { }) t.test('handle EPERMs on unlink by trying to chmod 0o666', async t => { - const fs = require('../lib/fs.js') + const fs = require('../dist/cjs/src/fs.js') const CHMODS = [] let threwAsync = false let threwSync = false const { rimrafWindows, rimrafWindowsSync } = t.mock( - '../lib/rimraf-windows.js', + '../dist/cjs/src/rimraf-windows.js', { - '../lib/fs.js': { + '../dist/cjs/src/fs.js': { ...fs, chmodSync: (...args) => { CHMODS.push(['chmodSync', ...args]) @@ -317,14 +317,14 @@ t.test('handle EPERMs on unlink by trying to chmod 0o666', async t => { }) t.test('handle EPERMs, chmod returns ENOENT', async t => { - const fs = require('../lib/fs.js') + const fs = require('../dist/cjs/src/fs.js') const CHMODS = [] let threwAsync = false let threwSync = false const { rimrafWindows, rimrafWindowsSync } = t.mock( - '../lib/rimraf-windows.js', + '../dist/cjs/src/rimraf-windows.js', { - '../lib/fs.js': { + '../dist/cjs/src/fs.js': { ...fs, chmodSync: (...args) => { CHMODS.push(['chmodSync', ...args]) @@ -381,14 +381,14 @@ t.test('handle EPERMs, chmod returns ENOENT', async t => { }) t.test('handle EPERMs, chmod raises something other than ENOENT', async t => { - const fs = require('../lib/fs.js') + const fs = require('../dist/cjs/src/fs.js') const CHMODS = [] let threwAsync = false let threwSync = false const { rimrafWindows, rimrafWindowsSync } = t.mock( - '../lib/rimraf-windows.js', + '../dist/cjs/src/rimraf-windows.js', { - '../lib/fs.js': { + '../dist/cjs/src/fs.js': { ...fs, chmodSync: (...args) => { CHMODS.push(['chmodSync', ...args]) @@ -445,11 +445,11 @@ t.test('handle EPERMs, chmod raises something other than ENOENT', async t => { }) t.test('rimraffing root, do not actually rmdir root', async t => { - const fs = require('../lib/fs.js') + const fs = require('../dist/cjs/src/fs.js') let ROOT = null const { parse } = require('path') const { rimrafWindows, rimrafWindowsSync } = t.mock( - '../lib/rimraf-windows.js', + '../dist/cjs/src/rimraf-windows.js', { path: { ...require('path'), @@ -483,7 +483,7 @@ t.test('do not allow third arg', async t => { const { rimrafWindows, rimrafWindowsSync, - } = require('../lib/rimraf-windows.js') + } = require('../dist/cjs/src/rimraf-windows.js') t.rejects(rimrafWindows(ROOT, {}, true)) t.throws(() => rimrafWindowsSync(ROOT, {}, true)) }) diff --git a/test/use-native.js b/test/use-native.js index af0e9c13..0ca8e83d 100644 --- a/test/use-native.js +++ b/test/use-native.js @@ -6,7 +6,7 @@ if (/^v([0-8]\.|1[0-3]\.|14\.[0-9]\.|14\.1[1-3]\.)/.test(process.version)) { } const t = require('tap') -const { useNative, useNativeSync } = require('../lib/use-native.js') +const { useNative, useNativeSync } = require('../dist/cjs/src/use-native.js') if (!process.env.__TESTING_RIMRAF_NODE_VERSION__) { t.spawn(process.execPath, [__filename], { diff --git a/tsconfig-base.json b/tsconfig-base.json new file mode 100644 index 00000000..b72747bb --- /dev/null +++ b/tsconfig-base.json @@ -0,0 +1,17 @@ +{ + "exclude": ["./test", "./tap-snapshots"], + "include": ["src/**/*.ts"], + "compilerOptions": { + "allowSyntheticDefaultImports": true, + "declaration": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "isolatedModules": true, + "moduleResolution": "node", + "resolveJsonModule": true, + "skipLibCheck": true, + "sourceMap": true, + "strict": true, + "target": "es2022" + } +} diff --git a/tsconfig-cjs.json b/tsconfig-cjs.json new file mode 100644 index 00000000..7aae8118 --- /dev/null +++ b/tsconfig-cjs.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig-base.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "dist/cjs" + } +} diff --git a/tsconfig-esm.json b/tsconfig-esm.json new file mode 100644 index 00000000..9a571575 --- /dev/null +++ b/tsconfig-esm.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig-base.json", + "compilerOptions": { + "module": "esnext", + "outDir": "dist/mjs" + } +}