diff --git a/README.md b/README.md index a2b8256..46df76d 100644 --- a/README.md +++ b/README.md @@ -141,7 +141,7 @@ Synchronous form of `rimraf.moveRemove()` ### Command Line Interface ``` -rimraf version 4.0.4 +rimraf version 4.2.0 Usage: rimraf [ ...] Deletes all files and folders at "path", recursively. @@ -151,8 +151,10 @@ Options: -h --help Display this usage info --preserve-root Do not remove '/' recursively (default) --no-preserve-root Do not treat '/' specially + -G --no-glob Treat arguments as literal paths, not globs (default) + -g --glob Treat arguments as glob patterns - --impl= Specify the implementationt to use. + --impl= Specify the implementation to use. rimraf: choose the best option native: the built-in implementation in Node.js manual: the platform-specific JS implementation diff --git a/fixup.sh b/fixup.sh index 9fd16d1..34d813f 100644 --- a/fixup.sh +++ b/fixup.sh @@ -2,12 +2,14 @@ cat >dist/cjs/package.json <dist/mjs/package.json <=16 || 14 >=14.17" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -1761,6 +1759,36 @@ "node": ">=10.13.0" } }, + "node_modules/glob/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==", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-7.4.2.tgz", + "integrity": "sha512-xy4q7wou3vUoC9k1xGTXc+awNdGaGVHtFUaey8tiX4H1QRc04DZ/rmDFwNm2EBsuYEhAZ6SgMmYf3InGY6OauA==", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob/node_modules/minipass": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.4.tgz", + "integrity": "sha512-lwycX3cBMTvcejsHITUgYj6Gy6A7Nh4Q6h9NP4sTHY1ccJlC7yKzDmiShEHsJ16Jf1nKGDEaiHxiltsJEvk0nQ==", + "engines": { + "node": ">=8" + } + }, "node_modules/globals": { "version": "13.20.0", "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", @@ -2429,6 +2457,26 @@ "node": ">=8" } }, + "node_modules/nyc/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/nyc/node_modules/locate-path": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", @@ -2689,6 +2737,37 @@ "node": ">=8" } }, + "node_modules/path-scurry": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.6.1.tgz", + "integrity": "sha512-OW+5s+7cw6253Q4E+8qQ/u1fVvcJQCJo/VFD8pje+dbJCF1n5ZRMV2AEHbGp+5Q7jxQIYJxkHopnj6nzdGeZLA==", + "dependencies": { + "lru-cache": "^7.14.1", + "minipass": "^4.0.2" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "7.18.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.1.tgz", + "integrity": "sha512-8/HcIENyQnfUTCDizRu9rrDyG6XG/21M4X7/YEGZeD76ZJilFPAUVb/2zysFf7VVO1LEjCDFyHp8pMMvozIrvg==", + "engines": { + "node": ">=12" + } + }, + "node_modules/path-scurry/node_modules/minipass": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.4.tgz", + "integrity": "sha512-lwycX3cBMTvcejsHITUgYj6Gy6A7Nh4Q6h9NP4sTHY1ccJlC7yKzDmiShEHsJ16Jf1nKGDEaiHxiltsJEvk0nQ==", + "engines": { + "node": ">=8" + } + }, "node_modules/picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", @@ -2926,6 +3005,26 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/rimraf/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -3228,6 +3327,26 @@ "node": ">=8" } }, + "node_modules/tap-mocha-reporter/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/tap-parser": { "version": "11.0.2", "resolved": "https://registry.npmjs.org/tap-parser/-/tap-parser-11.0.2.tgz", @@ -5147,6 +5266,26 @@ "node": ">=8" } }, + "node_modules/test-exclude/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", diff --git a/package.json b/package.json index 700d130..d6deeac 100644 --- a/package.json +++ b/package.json @@ -77,5 +77,8 @@ }, "engines": { "node": ">=14" + }, + "dependencies": { + "glob": "^9.2.0" } } diff --git a/src/bin.ts b/src/bin.ts index 3791e36..0785d82 100755 --- a/src/bin.ts +++ b/src/bin.ts @@ -16,8 +16,10 @@ Options: -h --help Display this usage info --preserve-root Do not remove '/' recursively (default) --no-preserve-root Do not treat '/' specially + -G --no-glob Treat arguments as literal paths, not globs (default) + -g --glob Treat arguments as glob patterns - --impl= Specify the implementationt to use. + --impl= Specify the implementation to use. rimraf: choose the best option native: the built-in implementation in Node.js manual: the platform-specific JS implementation @@ -59,6 +61,12 @@ const main = async (...args: string[]) => { } else if (arg === '-h' || arg === '--help') { console.log(help) return 0 + } else if (arg === '-g' || arg === '--glob') { + opt.glob = true + continue + } else if (arg === '-G' || arg === '--no-glob') { + opt.glob = false + continue } else if (arg === '--preserve-root') { opt.preserveRoot = true continue diff --git a/src/index.ts b/src/index.ts index 7074d23..e417d51 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,8 @@ import optArg from './opt-arg.js' import pathArg from './path-arg.js' +import { glob, GlobOptions, globSync } from 'glob' + export interface RimrafOptions { preserveRoot?: boolean tmp?: string @@ -9,6 +11,7 @@ export interface RimrafOptions { backoff?: number maxBackoff?: number signal?: AbortSignal + glob?: boolean | GlobOptions } const typeOrUndef = (val: any, t: string) => @@ -22,7 +25,8 @@ export const isRimrafOptions = (o: any): o is RimrafOptions => typeOrUndef(o.maxRetries, 'number') && typeOrUndef(o.retryDelay, 'number') && typeOrUndef(o.backoff, 'number') && - typeOrUndef(o.maxBackoff, 'number') + typeOrUndef(o.maxBackoff, 'number') && + (typeOrUndef(o.glob, 'boolean') || (o.glob && typeof o.glob === 'object')) export const assertRimrafOptions: (o: any) => void = ( o: any @@ -42,7 +46,10 @@ import { useNative, useNativeSync } from './use-native.js' const wrap = (fn: (p: string, o: RimrafOptions) => Promise) => async (path: string | string[], opt?: RimrafOptions): Promise => { - const options: RimrafOptions = optArg(opt) + const options = optArg(opt) + if (options.glob) { + path = await glob(path, options.glob) + } await (Array.isArray(path) ? Promise.all(path.map(p => fn(pathArg(p, options), options))) : fn(pathArg(path, options), options)) @@ -52,6 +59,9 @@ const wrapSync = (fn: (p: string, o: RimrafOptions) => void) => (path: string | string[], opt?: RimrafOptions): void => { const options = optArg(opt) + if (options.glob) { + path = globSync(path, options.glob) + } return Array.isArray(path) ? path.forEach(p => fn(pathArg(p, options), options)) : fn(pathArg(path, options), options) diff --git a/src/opt-arg.ts b/src/opt-arg.ts index f767f1b..b2f7e61 100644 --- a/src/opt-arg.ts +++ b/src/opt-arg.ts @@ -1,5 +1,32 @@ +import { GlobOptions } from 'glob' import { assertRimrafOptions, RimrafOptions } from './index.js' -export default (opt: RimrafOptions = {}) => { +export default ( + opt: RimrafOptions = {} +): RimrafOptions & { + glob?: GlobOptions & { withFileTypes: false } +} => { assertRimrafOptions(opt) - return opt + const { glob, ...options } = opt + if (!glob) return options + const globOpt = + glob === true + ? opt.signal + ? { signal: opt.signal } + : {} + : opt.signal + ? { + signal: opt.signal, + ...glob, + } + : glob + return { + ...options, + glob: { + ...globOpt, + // always get absolute paths from glob, to ensure + // that we are referencing the correct thing. + absolute: true, + withFileTypes: false, + }, + } } diff --git a/test/bin.js b/test/bin.js index 03d6ff1..2940763 100644 --- a/test/bin.js +++ b/test/bin.js @@ -66,6 +66,25 @@ t.test('basic arg parsing stuff', t => { ]) }) + t.test('glob true', async t => { + t.equal(await bin('-g', 'foo'), 0) + t.equal(await bin('--glob', 'foo'), 0) + t.equal(await bin('-G', '-g', 'foo'), 0) + t.equal(await bin('-g', '-G', 'foo'), 0) + t.equal(await bin('-G', 'foo'), 0) + t.equal(await bin('--no-glob', 'foo'), 0) + t.same(LOGS, []) + t.same(ERRS, []) + t.same(CALLS, [ + ['rimraf', ['foo'], { glob: true }], + ['rimraf', ['foo'], { glob: true }], + ['rimraf', ['foo'], { glob: true }], + ['rimraf', ['foo'], { glob: false }], + ['rimraf', ['foo'], { glob: false }], + ['rimraf', ['foo'], { glob: false }], + ]) + }) + t.test('dashdash', async t => { t.equal(await bin('--', '-h'), 0) t.same(LOGS, []) diff --git a/test/index.js b/test/index.js index 4a6a1a8..d7cb429 100644 --- a/test/index.js +++ b/test/index.js @@ -1,3 +1,4 @@ +const {statSync} = require('fs') const t = require('tap') t.same( @@ -138,7 +139,7 @@ t.test('actually delete some stuff', t => { }, }, } - const { rimraf } = require('../') + const { rimraf, rimrafSync } = require('../') const { statSync } = require('../dist/cjs/src/fs.js') t.test('sync', t => { const path = t.testdir(fixture) @@ -190,3 +191,45 @@ t.test('accept array of paths as first arg', async t => { [resolve('o'), { cat: 'chai' }], ]) }) + +t.test('deleting globs', t => { + const { rimraf, rimrafSync } = require('../') + + const fixture = { + a: 'a', + b: 'b', + c: { + d: 'd', + e: 'e', + f: { + g: 'g', + h: 'h', + i: { + j: 'j', + k: 'k', + l: 'l', + m: { + n: 'n', + o: 'o', + }, + }, + }, + }, + } + + t.test('sync', t => { + const cwd = t.testdir(fixture) + rimrafSync('**/f/**/m', { glob: { cwd }}) + t.throws(() => statSync(cwd + '/c/f/i/m')) + statSync(cwd + '/c/f/i/l') + t.end() + }) + t.test('async', async t => { + const cwd = t.testdir(fixture) + await rimraf('**/f/**/m', { glob: { cwd }}) + t.throws(() => statSync(cwd + '/c/f/i/m')) + statSync(cwd + '/c/f/i/l') + }) + + t.end() +}) diff --git a/test/opt-arg.js b/test/opt-arg.js index 9d2686f..1388025 100644 --- a/test/opt-arg.js +++ b/test/opt-arg.js @@ -1,7 +1,7 @@ const t = require('tap') 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(opt), opt, 'returns equivalent object if provided') t.same(optArg(), {}, 'returns new object otherwise') t.throws(() => optArg(true)) @@ -27,7 +27,8 @@ t.test('every kind of invalid option value', t => { maxRetries === undefined && retryDelay === undefined && backoff === undefined && - maxBackoff === undefined + maxBackoff === undefined && + tmp === undefined ) { continue } @@ -38,6 +39,7 @@ t.test('every kind of invalid option value', t => { retryDelay, backoff, maxBackoff, + tmp, }) ) } @@ -68,6 +70,7 @@ t.test('test every allowed combination', t => { retryDelay, backoff, maxBackoff, + tmp, }) ) } @@ -78,3 +81,42 @@ t.test('test every allowed combination', t => { } t.end() }) + +t.test('glob option handling', t => { + t.same(optArg({ glob: true }), { + glob: { absolute: true, withFileTypes: false }, + }) + const gws = optArg({ signal: { x: 1 }, glob: true }) + t.same(gws, { + signal: { x: 1 }, + glob: { absolute: true, signal: { x: 1 }, withFileTypes: false }, + }) + t.equal(gws.signal, gws.glob.signal) + t.same(optArg({ glob: { nodir: true } }), { + glob: { absolute: true, nodir: true, withFileTypes: false }, + }) + const gwsg = optArg({ signal: { x: 1 }, glob: { nodir: true } }) + t.same(gwsg, { + signal: { x: 1 }, + glob: { + absolute: true, + nodir: true, + withFileTypes: false, + signal: { x: 1 }, + }, + }) + t.equal(gwsg.signal, gwsg.glob.signal) + t.same( + optArg({ signal: { x: 1 }, glob: { nodir: true, signal: { y: 1 } } }), + { + signal: { x: 1 }, + glob: { + absolute: true, + nodir: true, + signal: { y: 1 }, + withFileTypes: false, + }, + } + ) + t.end() +}) diff --git a/test/rimraf-move-remove.js b/test/rimraf-move-remove.js index 31b9c5c..c158e56 100644 --- a/test/rimraf-move-remove.js +++ b/test/rimraf-move-remove.js @@ -532,7 +532,6 @@ t.test( ac.abort(new Error('aborted rimraf')) await t.rejects(() => rimrafMoveRemove(d, { signal })) }) - t.end() } )