-
Notifications
You must be signed in to change notification settings - Fork 0
/
mod.ts
132 lines (115 loc) · 3.29 KB
/
mod.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
import {
copy,
emptyDir,
ensureDir,
ensureFile,
globToRegExp,
parse,
resolve,
walk,
} from './deps.ts';
async function main(args: string[]) {
const {
help,
ignore,
gitignore = 'true',
force,
verbose,
_: [src, out],
}: { [key: string]: string } = parse(args, {
alias: {
h: 'help',
i: 'ignore',
f: 'force',
v: 'verbose',
},
});
if (help) {
return console.log(`
Usage:
$ dupl <src> <out> [flags]
Args:
src: The directory/file to copy from.
out: The directory/file to copy to.
Flags:
-h, --help: Show this help message
-i, --ignore: Ignore files/directories that match the given glob.
--gitignore: Use the .gitignore file in the src directory. (default: true)
-f, --force: Overwrite files/directories that already exist and ignore any prompts.
-v, --verbose: Show all files before prompt.
`);
}
if (!src || !out) {
console.error('Please provide a source and destination.');
Deno.exit(1);
}
// check if out is empty, ask to overwrite if not
await ensureDir(out);
for await (const file of walk(out)) {
if (file.isFile) {
if (
!force &&
!confirm(
`Overwrite existing files in ${out} (this will delete the entire folder before the operation)`,
)
) {
Deno.exit(0);
}
break;
}
}
// @ts-expect-error --ignore is treated as the boolean `true`. This is not an expected input and so we handle it by ignoring it
let ignoreGlobs: string[] = (ignore && ignore !== true)
? ignore.toString().split(',')
: [];
if (gitignore !== 'false') {
const files = Deno.readFileSync(`${src}/.gitignore`);
ignoreGlobs.push(
...new TextDecoder().decode(files).split('\n').filter((f) =>
!f.startsWith('#') && f.trim().length > 0
),
);
}
ignoreGlobs = ignoreGlobs.map((f) =>
resolve(src, f.startsWith('/') ? f.substr(1) : f)
);
const ignoreRegex: RegExp[] = ignoreGlobs.map((g) => globToRegExp(g));
const toCopy: string[][] = [];
if (verbose) console.log('Listing files to copy...')
for await (const entry of walk(resolve(src), { skip: ignoreRegex })) {
if (entry.isDirectory) continue;
toCopy.push([entry.path, entry.name]);
if (verbose) console.log(`${entry.path} -> ${resolve(out, entry.name)}`)
}
console.log(`Copying ${toCopy.length} files from ${src} to ${out}`);
if (
(!force) && !confirm(`Copy ${toCopy.length} files from ${src} to ${out}`)
) {
Deno.exit(0);
}
const startTime = performance.now();
await emptyDir(out);
let progress = 0;
const copyStringLength = toCopy.length.toString().length;
for (const file of toCopy) {
Deno.stdout.writeSync(
new TextEncoder().encode(
`\x1b[1K\r[${
' '.repeat(copyStringLength - progress.toString().length)
}${progress}/${toCopy.length}] Copying ${file[0]}`,
),
);
const fileOut = resolve(out, file[0].substring(resolve(src).length + 1));
await ensureFile(fileOut);
await copy(file[0], fileOut, { overwrite: true, preserveTimestamps: true });
progress++;
}
Deno.stdout.writeSync(
new TextEncoder().encode(
`\x1b[1K\r[${toCopy.length}/${toCopy.length}] Operation completed in ${
(performance.now() - startTime) / 1000
}s\n`,
),
);
}
main(Deno.args);