UNPKG

2.74 kBPlain TextView Raw
1import { pFilter, pMap, _since } from '@naturalcycles/js-lib'
2import * as fs from 'fs-extra'
3import * as globby from 'globby'
4import { dimGrey, yellow } from '../colors'
5
6export interface DelOptions {
7 /**
8 * Globby patterns.
9 */
10 patterns: string[]
11
12 /**
13 * @default 0 (infinite)
14 */
15 concurrency?: number
16
17 verbose?: boolean
18
19 silent?: boolean
20
21 debug?: boolean
22
23 dry?: boolean
24}
25
26export type DelSingleOption = string
27
28const DEF_OPT: DelOptions = {
29 patterns: [],
30 concurrency: Number.POSITIVE_INFINITY,
31}
32
33/**
34 * Delete files that match input patterns.
35 *
36 * @experimental
37 */
38export async function del(_opt: DelOptions | DelSingleOption): Promise<void> {
39 const started = Date.now()
40
41 // Convert DelSingleOption to DelOptions
42 if (typeof _opt === 'string') {
43 _opt = {
44 patterns: [_opt],
45 }
46 }
47
48 const opt = {
49 ...DEF_OPT,
50 ..._opt,
51 concurrency: _opt.concurrency || DEF_OPT.concurrency,
52 }
53 const { patterns, concurrency, verbose, silent, debug, dry } = opt
54
55 if (debug) {
56 console.log(opt)
57 }
58
59 // 1. glob only files, expand dirs, delete
60
61 const filenames = await globby(patterns, {
62 dot: true,
63 expandDirectories: true,
64 onlyFiles: true,
65 })
66
67 if (verbose || debug || dry) {
68 console.log(`Will delete ${yellow(filenames.length)} files:`, filenames)
69 }
70
71 if (dry) return
72
73 await pMap(filenames, filepath => fs.remove(filepath), { concurrency })
74
75 // 2. glob only dirs, expand, delete only empty!
76 let dirnames = await globby(patterns, {
77 dot: true,
78 expandDirectories: true,
79 onlyDirectories: true,
80 })
81
82 // Add original patterns (if any of them are dirs)
83 dirnames = dirnames.concat(
84 await pFilter(patterns, async pattern => {
85 return (await fs.pathExists(pattern)) && (await fs.lstat(pattern)).isDirectory()
86 }),
87 )
88
89 const dirnamesSorted = dirnames.sort().reverse()
90
91 // console.log({ dirnamesSorted })
92
93 const deletedDirs = await pFilter(
94 dirnamesSorted,
95 async dirpath => {
96 if (await isEmptyDir(dirpath)) {
97 // console.log(`empty dir: ${dirpath}`)
98 await fs.remove(dirpath)
99 return true
100 }
101 return false
102 },
103 { concurrency: 1 },
104 )
105
106 if (verbose || debug) console.log({ deletedDirs })
107
108 if (!silent) {
109 console.log(
110 `del deleted ${yellow(filenames.length)} files and ${yellow(
111 deletedDirs.length,
112 )} dirs ${dimGrey(_since(started))}`,
113 )
114 }
115}
116
117// Improved algorithm:
118// 1. glob only files, expand dirs, delete
119// 2. glob only dirs, expand, delete only empty!
120// 3. test each original pattern, if it exists and is directory and is empty - delete
121
122async function isEmptyDir(dir: string): Promise<boolean> {
123 return (await fs.readdir(dir)).length === 0
124}