2019-06-06 12:33:11 +02:00
|
|
|
import path from 'path'
|
2021-08-09 17:28:00 +02:00
|
|
|
import { promises, constants, Dirent, Stats } from 'fs'
|
2020-03-29 00:27:09 +01:00
|
|
|
import { Sema } from 'next/dist/compiled/async-sema'
|
2021-09-16 18:06:57 +02:00
|
|
|
import isError from './is-error'
|
2019-06-06 12:33:11 +02:00
|
|
|
|
2020-05-02 06:10:19 +02:00
|
|
|
const COPYFILE_EXCL = constants.COPYFILE_EXCL
|
2019-06-06 12:33:11 +02:00
|
|
|
|
|
|
|
export async function recursiveCopy(
|
|
|
|
source: string,
|
|
|
|
dest: string,
|
|
|
|
{
|
2019-11-15 22:53:59 +01:00
|
|
|
concurrency = 32,
|
|
|
|
overwrite = false,
|
2019-06-06 12:33:11 +02:00
|
|
|
filter = () => true,
|
2019-11-15 22:53:59 +01:00
|
|
|
}: {
|
|
|
|
concurrency?: number
|
|
|
|
overwrite?: boolean
|
2021-04-25 20:34:36 +02:00
|
|
|
filter?(filePath: string): boolean
|
2019-11-15 22:53:59 +01:00
|
|
|
} = {}
|
2020-05-19 10:59:03 +02:00
|
|
|
): Promise<void> {
|
2019-06-06 12:33:11 +02:00
|
|
|
const cwdPath = process.cwd()
|
|
|
|
const from = path.resolve(cwdPath, source)
|
|
|
|
const to = path.resolve(cwdPath, dest)
|
|
|
|
|
|
|
|
const sema = new Sema(concurrency)
|
|
|
|
|
2021-08-09 17:28:00 +02:00
|
|
|
// deep copy the file/directory
|
|
|
|
async function _copy(item: string, lstats?: Stats | Dirent): Promise<void> {
|
2019-06-06 12:33:11 +02:00
|
|
|
const target = item.replace(from, to)
|
|
|
|
|
|
|
|
await sema.acquire()
|
|
|
|
|
2021-08-09 17:28:00 +02:00
|
|
|
if (!lstats) {
|
|
|
|
// after lock on first run
|
|
|
|
lstats = await promises.lstat(from)
|
|
|
|
}
|
|
|
|
|
|
|
|
// readdir & lstat do not follow symbolic links
|
|
|
|
// if part is a symbolic link, follow it with stat
|
|
|
|
let isFile = lstats.isFile()
|
|
|
|
let isDirectory = lstats.isDirectory()
|
|
|
|
if (lstats.isSymbolicLink()) {
|
|
|
|
const stats = await promises.stat(item)
|
|
|
|
isFile = stats.isFile()
|
|
|
|
isDirectory = stats.isDirectory()
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isDirectory) {
|
2019-06-06 12:33:11 +02:00
|
|
|
try {
|
2021-11-09 18:03:20 +01:00
|
|
|
await promises.mkdir(target, { recursive: true })
|
2019-06-06 12:33:11 +02:00
|
|
|
} catch (err) {
|
|
|
|
// do not throw `folder already exists` errors
|
2021-09-16 18:06:57 +02:00
|
|
|
if (isError(err) && err.code !== 'EEXIST') {
|
2019-06-06 12:33:11 +02:00
|
|
|
throw err
|
|
|
|
}
|
|
|
|
}
|
2019-11-15 22:53:59 +01:00
|
|
|
sema.release()
|
2021-08-09 17:28:00 +02:00
|
|
|
const files = await promises.readdir(item, { withFileTypes: true })
|
|
|
|
await Promise.all(
|
|
|
|
files.map((file) => _copy(path.join(item, file.name), file))
|
|
|
|
)
|
2019-06-06 12:33:11 +02:00
|
|
|
} else if (
|
2021-08-09 17:28:00 +02:00
|
|
|
isFile &&
|
2019-06-06 12:33:11 +02:00
|
|
|
// before we send the path to filter
|
|
|
|
// we remove the base path (from) and replace \ by / (windows)
|
|
|
|
filter(item.replace(from, '').replace(/\\/g, '/'))
|
|
|
|
) {
|
2020-05-02 06:10:19 +02:00
|
|
|
await promises.copyFile(
|
|
|
|
item,
|
|
|
|
target,
|
|
|
|
overwrite ? undefined : COPYFILE_EXCL
|
|
|
|
)
|
2019-11-15 22:53:59 +01:00
|
|
|
sema.release()
|
2021-08-09 17:28:00 +02:00
|
|
|
} else {
|
|
|
|
sema.release()
|
2019-06-06 12:33:11 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
await _copy(from)
|
|
|
|
}
|