2020-01-02 15:13:47 -05:00
|
|
|
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
|
2019-10-16 14:39:33 -04:00
|
|
|
import * as path from "../path/mod.ts";
|
2019-05-16 12:19:17 -04:00
|
|
|
import { ensureDir, ensureDirSync } from "./ensure_dir.ts";
|
2020-09-27 06:22:32 -04:00
|
|
|
import { getFileInfoType, isSubdir } from "./_util.ts";
|
2020-06-07 09:20:33 -04:00
|
|
|
import { assert } from "../_util/assert.ts";
|
2019-05-16 12:19:17 -04:00
|
|
|
|
2020-05-18 18:46:02 -04:00
|
|
|
const isWindows = Deno.build.os === "windows";
|
|
|
|
|
2019-05-16 12:19:17 -04:00
|
|
|
export interface CopyOptions {
|
|
|
|
/**
|
|
|
|
* overwrite existing file or directory. Default is `false`
|
|
|
|
*/
|
|
|
|
overwrite?: boolean;
|
|
|
|
/**
|
2019-06-19 00:22:01 -04:00
|
|
|
* When `true`, will set last modification and access times to the ones of the
|
|
|
|
* original source files.
|
2019-05-16 12:19:17 -04:00
|
|
|
* When `false`, timestamp behavior is OS-dependent.
|
|
|
|
* Default is `false`.
|
|
|
|
*/
|
|
|
|
preserveTimestamps?: boolean;
|
|
|
|
}
|
|
|
|
|
|
|
|
async function ensureValidCopy(
|
|
|
|
src: string,
|
|
|
|
dest: string,
|
|
|
|
options: CopyOptions,
|
2020-07-14 15:24:17 -04:00
|
|
|
isCopyFolder = false,
|
2020-02-08 15:15:59 -05:00
|
|
|
): Promise<Deno.FileInfo | undefined> {
|
|
|
|
let destStat: Deno.FileInfo;
|
2019-12-18 09:45:19 -05:00
|
|
|
|
|
|
|
try {
|
|
|
|
destStat = await Deno.lstat(dest);
|
|
|
|
} catch (err) {
|
2020-02-24 15:48:35 -05:00
|
|
|
if (err instanceof Deno.errors.NotFound) {
|
2019-12-18 09:45:19 -05:00
|
|
|
return;
|
2019-05-16 12:19:17 -04:00
|
|
|
}
|
2020-02-08 15:15:59 -05:00
|
|
|
throw err;
|
2019-05-16 12:19:17 -04:00
|
|
|
}
|
|
|
|
|
2020-04-16 01:40:30 -04:00
|
|
|
if (isCopyFolder && !destStat.isDirectory) {
|
2019-12-18 09:45:19 -05:00
|
|
|
throw new Error(
|
2020-07-14 15:24:17 -04:00
|
|
|
`Cannot overwrite non-directory '${dest}' with directory '${src}'.`,
|
2019-12-18 09:45:19 -05:00
|
|
|
);
|
|
|
|
}
|
|
|
|
if (!options.overwrite) {
|
|
|
|
throw new Error(`'${dest}' already exists.`);
|
|
|
|
}
|
|
|
|
|
2020-02-08 15:15:59 -05:00
|
|
|
return destStat;
|
2019-05-16 12:19:17 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
function ensureValidCopySync(
|
|
|
|
src: string,
|
|
|
|
dest: string,
|
|
|
|
options: CopyOptions,
|
2020-07-14 15:24:17 -04:00
|
|
|
isCopyFolder = false,
|
2020-02-08 15:15:59 -05:00
|
|
|
): Deno.FileInfo | undefined {
|
|
|
|
let destStat: Deno.FileInfo;
|
2019-05-16 12:19:17 -04:00
|
|
|
try {
|
|
|
|
destStat = Deno.lstatSync(dest);
|
2019-12-18 09:45:19 -05:00
|
|
|
} catch (err) {
|
2020-02-24 15:48:35 -05:00
|
|
|
if (err instanceof Deno.errors.NotFound) {
|
2019-12-18 09:45:19 -05:00
|
|
|
return;
|
|
|
|
}
|
2020-02-08 15:15:59 -05:00
|
|
|
throw err;
|
2019-05-16 12:19:17 -04:00
|
|
|
}
|
|
|
|
|
2020-04-16 01:40:30 -04:00
|
|
|
if (isCopyFolder && !destStat.isDirectory) {
|
2019-12-18 09:45:19 -05:00
|
|
|
throw new Error(
|
2020-07-14 15:24:17 -04:00
|
|
|
`Cannot overwrite non-directory '${dest}' with directory '${src}'.`,
|
2019-12-18 09:45:19 -05:00
|
|
|
);
|
|
|
|
}
|
|
|
|
if (!options.overwrite) {
|
|
|
|
throw new Error(`'${dest}' already exists.`);
|
2019-05-16 12:19:17 -04:00
|
|
|
}
|
|
|
|
|
2020-02-08 15:15:59 -05:00
|
|
|
return destStat;
|
2019-05-16 12:19:17 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
/* copy file to dest */
|
|
|
|
async function copyFile(
|
|
|
|
src: string,
|
|
|
|
dest: string,
|
2020-07-14 15:24:17 -04:00
|
|
|
options: CopyOptions,
|
2019-05-16 12:19:17 -04:00
|
|
|
): Promise<void> {
|
|
|
|
await ensureValidCopy(src, dest, options);
|
|
|
|
await Deno.copyFile(src, dest);
|
|
|
|
if (options.preserveTimestamps) {
|
|
|
|
const statInfo = await Deno.stat(src);
|
2020-04-27 14:09:56 -04:00
|
|
|
assert(statInfo.atime instanceof Date, `statInfo.atime is unavailable`);
|
|
|
|
assert(statInfo.mtime instanceof Date, `statInfo.mtime is unavailable`);
|
|
|
|
await Deno.utime(dest, statInfo.atime, statInfo.mtime);
|
2019-05-16 12:19:17 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
/* copy file to dest synchronously */
|
|
|
|
function copyFileSync(src: string, dest: string, options: CopyOptions): void {
|
|
|
|
ensureValidCopySync(src, dest, options);
|
|
|
|
Deno.copyFileSync(src, dest);
|
|
|
|
if (options.preserveTimestamps) {
|
|
|
|
const statInfo = Deno.statSync(src);
|
2020-04-27 14:09:56 -04:00
|
|
|
assert(statInfo.atime instanceof Date, `statInfo.atime is unavailable`);
|
|
|
|
assert(statInfo.mtime instanceof Date, `statInfo.mtime is unavailable`);
|
|
|
|
Deno.utimeSync(dest, statInfo.atime, statInfo.mtime);
|
2019-05-16 12:19:17 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* copy symlink to dest */
|
|
|
|
async function copySymLink(
|
|
|
|
src: string,
|
|
|
|
dest: string,
|
2020-07-14 15:24:17 -04:00
|
|
|
options: CopyOptions,
|
2019-05-16 12:19:17 -04:00
|
|
|
): Promise<void> {
|
|
|
|
await ensureValidCopy(src, dest, options);
|
2020-04-29 16:39:37 -04:00
|
|
|
const originSrcFilePath = await Deno.readLink(src);
|
2019-05-16 12:19:17 -04:00
|
|
|
const type = getFileInfoType(await Deno.lstat(src));
|
2020-05-18 18:46:02 -04:00
|
|
|
if (isWindows) {
|
|
|
|
await Deno.symlink(originSrcFilePath, dest, {
|
|
|
|
type: type === "dir" ? "dir" : "file",
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
await Deno.symlink(originSrcFilePath, dest);
|
|
|
|
}
|
2019-05-16 12:19:17 -04:00
|
|
|
if (options.preserveTimestamps) {
|
|
|
|
const statInfo = await Deno.lstat(src);
|
2020-04-27 14:09:56 -04:00
|
|
|
assert(statInfo.atime instanceof Date, `statInfo.atime is unavailable`);
|
|
|
|
assert(statInfo.mtime instanceof Date, `statInfo.mtime is unavailable`);
|
|
|
|
await Deno.utime(dest, statInfo.atime, statInfo.mtime);
|
2019-05-16 12:19:17 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* copy symlink to dest synchronously */
|
|
|
|
function copySymlinkSync(
|
|
|
|
src: string,
|
|
|
|
dest: string,
|
2020-07-14 15:24:17 -04:00
|
|
|
options: CopyOptions,
|
2019-05-16 12:19:17 -04:00
|
|
|
): void {
|
|
|
|
ensureValidCopySync(src, dest, options);
|
2020-04-29 16:39:37 -04:00
|
|
|
const originSrcFilePath = Deno.readLinkSync(src);
|
2019-05-16 12:19:17 -04:00
|
|
|
const type = getFileInfoType(Deno.lstatSync(src));
|
2020-05-18 18:46:02 -04:00
|
|
|
if (isWindows) {
|
|
|
|
Deno.symlinkSync(originSrcFilePath, dest, {
|
|
|
|
type: type === "dir" ? "dir" : "file",
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
Deno.symlinkSync(originSrcFilePath, dest);
|
|
|
|
}
|
|
|
|
|
2019-05-16 12:19:17 -04:00
|
|
|
if (options.preserveTimestamps) {
|
|
|
|
const statInfo = Deno.lstatSync(src);
|
2020-04-27 14:09:56 -04:00
|
|
|
assert(statInfo.atime instanceof Date, `statInfo.atime is unavailable`);
|
|
|
|
assert(statInfo.mtime instanceof Date, `statInfo.mtime is unavailable`);
|
|
|
|
Deno.utimeSync(dest, statInfo.atime, statInfo.mtime);
|
2019-05-16 12:19:17 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* copy folder from src to dest. */
|
|
|
|
async function copyDir(
|
|
|
|
src: string,
|
|
|
|
dest: string,
|
2020-07-14 15:24:17 -04:00
|
|
|
options: CopyOptions,
|
2019-05-16 12:19:17 -04:00
|
|
|
): Promise<void> {
|
|
|
|
const destStat = await ensureValidCopy(src, dest, options, true);
|
|
|
|
|
|
|
|
if (!destStat) {
|
|
|
|
await ensureDir(dest);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (options.preserveTimestamps) {
|
|
|
|
const srcStatInfo = await Deno.stat(src);
|
2020-04-27 14:09:56 -04:00
|
|
|
assert(srcStatInfo.atime instanceof Date, `statInfo.atime is unavailable`);
|
|
|
|
assert(srcStatInfo.mtime instanceof Date, `statInfo.mtime is unavailable`);
|
|
|
|
await Deno.utime(dest, srcStatInfo.atime, srcStatInfo.mtime);
|
2019-05-16 12:19:17 -04:00
|
|
|
}
|
|
|
|
|
2020-04-29 16:39:37 -04:00
|
|
|
for await (const entry of Deno.readDir(src)) {
|
2020-04-29 16:00:31 -04:00
|
|
|
const srcPath = path.join(src, entry.name);
|
2019-05-16 12:19:17 -04:00
|
|
|
const destPath = path.join(dest, path.basename(srcPath as string));
|
2020-04-29 16:00:31 -04:00
|
|
|
if (entry.isSymlink) {
|
|
|
|
await copySymLink(srcPath, destPath, options);
|
|
|
|
} else if (entry.isDirectory) {
|
2019-05-16 12:19:17 -04:00
|
|
|
await copyDir(srcPath, destPath, options);
|
2020-04-29 16:00:31 -04:00
|
|
|
} else if (entry.isFile) {
|
2019-05-16 12:19:17 -04:00
|
|
|
await copyFile(srcPath, destPath, options);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* copy folder from src to dest synchronously */
|
|
|
|
function copyDirSync(src: string, dest: string, options: CopyOptions): void {
|
2020-02-08 15:15:59 -05:00
|
|
|
const destStat = ensureValidCopySync(src, dest, options, true);
|
2019-05-16 12:19:17 -04:00
|
|
|
|
|
|
|
if (!destStat) {
|
|
|
|
ensureDirSync(dest);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (options.preserveTimestamps) {
|
|
|
|
const srcStatInfo = Deno.statSync(src);
|
2020-04-27 14:09:56 -04:00
|
|
|
assert(srcStatInfo.atime instanceof Date, `statInfo.atime is unavailable`);
|
|
|
|
assert(srcStatInfo.mtime instanceof Date, `statInfo.mtime is unavailable`);
|
|
|
|
Deno.utimeSync(dest, srcStatInfo.atime, srcStatInfo.mtime);
|
2019-05-16 12:19:17 -04:00
|
|
|
}
|
|
|
|
|
2020-04-29 16:39:37 -04:00
|
|
|
for (const entry of Deno.readDirSync(src)) {
|
2020-04-29 16:00:31 -04:00
|
|
|
assert(entry.name != null, "file.name must be set");
|
|
|
|
const srcPath = path.join(src, entry.name);
|
2019-05-16 12:19:17 -04:00
|
|
|
const destPath = path.join(dest, path.basename(srcPath as string));
|
2020-04-29 16:00:31 -04:00
|
|
|
if (entry.isSymlink) {
|
|
|
|
copySymlinkSync(srcPath, destPath, options);
|
|
|
|
} else if (entry.isDirectory) {
|
2019-05-16 12:19:17 -04:00
|
|
|
copyDirSync(srcPath, destPath, options);
|
2020-04-29 16:00:31 -04:00
|
|
|
} else if (entry.isFile) {
|
2019-05-16 12:19:17 -04:00
|
|
|
copyFileSync(srcPath, destPath, options);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Copy a file or directory. The directory can have contents. Like `cp -r`.
|
2020-05-16 14:36:13 -04:00
|
|
|
* Requires the `--allow-read` and `--allow-write` flag.
|
2019-05-16 12:19:17 -04:00
|
|
|
* @param src the file/directory path.
|
2019-06-19 00:22:01 -04:00
|
|
|
* Note that if `src` is a directory it will copy everything inside
|
|
|
|
* of this directory, not the entire directory itself
|
|
|
|
* @param dest the destination path. Note that if `src` is a file, `dest` cannot
|
|
|
|
* be a directory
|
2019-05-16 12:19:17 -04:00
|
|
|
* @param options
|
|
|
|
*/
|
|
|
|
export async function copy(
|
|
|
|
src: string,
|
|
|
|
dest: string,
|
2020-07-14 15:24:17 -04:00
|
|
|
options: CopyOptions = {},
|
2019-05-16 12:19:17 -04:00
|
|
|
): Promise<void> {
|
|
|
|
src = path.resolve(src);
|
|
|
|
dest = path.resolve(dest);
|
|
|
|
|
|
|
|
if (src === dest) {
|
|
|
|
throw new Error("Source and destination cannot be the same.");
|
|
|
|
}
|
|
|
|
|
|
|
|
const srcStat = await Deno.lstat(src);
|
|
|
|
|
2020-04-16 01:40:30 -04:00
|
|
|
if (srcStat.isDirectory && isSubdir(src, dest)) {
|
2019-05-16 12:19:17 -04:00
|
|
|
throw new Error(
|
2020-07-14 15:24:17 -04:00
|
|
|
`Cannot copy '${src}' to a subdirectory of itself, '${dest}'.`,
|
2019-05-16 12:19:17 -04:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2020-04-29 16:00:31 -04:00
|
|
|
if (srcStat.isSymlink) {
|
|
|
|
await copySymLink(src, dest, options);
|
|
|
|
} else if (srcStat.isDirectory) {
|
2019-05-16 12:19:17 -04:00
|
|
|
await copyDir(src, dest, options);
|
2020-04-16 01:40:30 -04:00
|
|
|
} else if (srcStat.isFile) {
|
2019-05-16 12:19:17 -04:00
|
|
|
await copyFile(src, dest, options);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Copy a file or directory. The directory can have contents. Like `cp -r`.
|
2020-05-16 14:36:13 -04:00
|
|
|
* Requires the `--allow-read` and `--allow-write` flag.
|
2019-05-16 12:19:17 -04:00
|
|
|
* @param src the file/directory path.
|
2019-06-19 00:22:01 -04:00
|
|
|
* Note that if `src` is a directory it will copy everything inside
|
|
|
|
* of this directory, not the entire directory itself
|
|
|
|
* @param dest the destination path. Note that if `src` is a file, `dest` cannot
|
|
|
|
* be a directory
|
2019-05-16 12:19:17 -04:00
|
|
|
* @param options
|
|
|
|
*/
|
|
|
|
export function copySync(
|
|
|
|
src: string,
|
|
|
|
dest: string,
|
2020-07-14 15:24:17 -04:00
|
|
|
options: CopyOptions = {},
|
2019-05-16 12:19:17 -04:00
|
|
|
): void {
|
|
|
|
src = path.resolve(src);
|
|
|
|
dest = path.resolve(dest);
|
|
|
|
|
|
|
|
if (src === dest) {
|
|
|
|
throw new Error("Source and destination cannot be the same.");
|
|
|
|
}
|
|
|
|
|
|
|
|
const srcStat = Deno.lstatSync(src);
|
|
|
|
|
2020-04-16 01:40:30 -04:00
|
|
|
if (srcStat.isDirectory && isSubdir(src, dest)) {
|
2019-05-16 12:19:17 -04:00
|
|
|
throw new Error(
|
2020-07-14 15:24:17 -04:00
|
|
|
`Cannot copy '${src}' to a subdirectory of itself, '${dest}'.`,
|
2019-05-16 12:19:17 -04:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2020-04-29 16:00:31 -04:00
|
|
|
if (srcStat.isSymlink) {
|
|
|
|
copySymlinkSync(src, dest, options);
|
|
|
|
} else if (srcStat.isDirectory) {
|
2019-05-16 12:19:17 -04:00
|
|
|
copyDirSync(src, dest, options);
|
2020-04-16 01:40:30 -04:00
|
|
|
} else if (srcStat.isFile) {
|
2019-05-16 12:19:17 -04:00
|
|
|
copyFileSync(src, dest, options);
|
|
|
|
}
|
|
|
|
}
|