@danieldietrich/copy

中文文档
0.4.2发布于 2019/12/26MITTypeScriptCommonJS零依赖近 30 天下载 11.5万

Simple yet powerful copy tool.

原始使用文档
文档目录9 节
  1. Installation
  2. Usage
  3. Examples
  4. Using copy operations
  5. Changing file attributes
  6. Implementing a progress indicator
  7. API
  8. Options
  9. Option precedence

npm versionvulnerabilitiesminzipped size   buildcoverage   Platform   Sponsordonatelicense   Follow

copy

Simple yet powerful copy tool.

The copy tool recursively copies files, directories and links from a source directory to a destination directory.

Features:

  • Renaming and moving files and directories
  • Filtering paths
  • Transformation of file contents
  • Performing actions after each path has been copied
  • Preserving user permissions
  • Overwriting or keeping existing files
  • Creating or dereferencing links
  • Preserving original timestamps
  • Collecting results (size & number of directories, files and links)
  • Performing a dry run without writing files
  • May be used as drop-in for fs-extra/copy, see Options

The copy tool intentionally does not

  • provide a synchronous API
  • check for cycles, i.e. if the destination is a subdirectory of the source
  • provide a transform API based on streams and chunks
  • preserve the timestamp of links (because node does not provide an OS independent API for that purpose)
  • rewrite the relative link of symlinks if source and/or destination were renamed or moved

Installation

npm i @danieldietrich/copy

Usage

The module supports ES6 import and CommonJS require style.

import copy from '@danieldietrich/copy';

(async function() {

    // Performs a dry run of copying ./node_modules to ./temp
    const totals = await copy('node_modules', 'temp', { dryRun: true });

    console.log('Totals:', totals);

})();

Totals contains information about the copy operation:

{
    directories: 1338,
    files: 7929,
    symlinks: 48,
    size: 87873775
}

The number of directories, files and symlinks corresponds to the source. The size reflects the number of written bytes. In particular, the size might be smaller than the source, if existing files are not ovewritten.

Examples

Using copy operations

See also option precedence.

const copy = require('@danieldietrich/copy');
const path = require('path');

(async function() {

    // move dist/ dir contents to parent dir and rename index.js files to index.mjs
    const rename = (source, target) => {
        if (source.stats.isDirectory() && source.path.endsWith('/dist')) {
            return path.dirname(target.path);
        } else if (source.stats.isFile() && source.path.endsWith('/index.js')) {
            return path.join(path.dirname(target.path), 'index.mjs');
        } else {
            return;
        }
    };

    // recursively copy all .js files
    const filter = (source, target) =>
        source.stats.isDirectory() || source.stats.isFile() && source.path.endsWith('.js');

    // transform the contents of all index.mjs files to upper case
    const transform = (data, source, target) => {
        if (source.stats.isFile() && target.path.endsWith('/index.mjs')) {
            return Buffer.from(data.toString('utf8').toUpperCase(), 'utf8');
        } else {
            return data;
        }
    };

    // log some information about copied files
    const afterEach = (source, target, options) => {
        const dryRun = options.dryRun ? '[DRY RUN] ' : '';
        if (source.stats.isDirectory()) {
            console.log(`${dryRun}Created ${target.path}`);
        } else {
            // target.stats is undefined on a dry run if the target does not already exist!
            const size = target.stats?.size || '?';
            console.log(`${dryRun}Copied ${source.path} to ${target.path} (${size} bytes)`);
        }
    };

    const totals = await copy('node_modules', 'temp', {
        rename,
        filter,
        transform,
        afterEach
    });

    console.log('Totals:', totals);

})();

Changing file attributes

In the following example we change the file owner uid. A chgrp or chmod may be performed in a similar way.

import * as copy from '@danieldietrich/copy';
import * as fs from 'fs';

const { lchown } = fs.promises;

async function changeOwner(src: string, dst: string, uid: number) {
    copy(src, dst, {
        afterEach: async (source, target) => {
            lchown(target.path, uid, source.stats.gid);
        }
    });
}

Implementing a progress indicator

import * as copy from '@danieldietrich/copy';

async function copyWithProgress(src: string, dst: string, callback: (curr: copy.Totals, sum: copy.Totals) => void) {
    const curr: copy.Totals = {
        directories: 0,
        files: 0,
        symlinks: 0,
        size: 0
    };
    const sum = await copy(src, dst, { dryRun: true });
    const interval = 100; // ms
    let update = Date.now();
    await copy(src, dst, { afterEach: (source) => {
        if (source.stats.isDirectory()) {
            curr.directories += 1;
        } else if (source.stats.isFile()) {
            curr.files += 1;
            curr.size += source.stats.size;
        } else if (source.stats.isSymbolicLink()) {
            curr.symlinks += 1;
            curr.size += source.stats.size;
        }
        if (Date.now() - update >= interval) {
            update = Date.now();
            callback(curr, sum);
        }
    }});
    callback(sum, sum);
}

// usage
(async function() {
    copyWithProgress('node_modules', 'temp', (curr, sum) => {
        const progress = Math.min(100, Math.floor(curr.size / sum.size * 100));
        console.log(`${Number.parseFloat(progress).toFixed(1)} %`);
    });
})();

API

The general signature of copy is:

async function copy(sourcePath: string, targetPath: string, options?: copy.Options): Promise<copy.Totals>;

The public types are:

// compatible to fs-extra.copy
type Options = {
    overwrite?: boolean;
    errorOnExist?: boolean;
    dereference?: boolean;
    preserveTimestamps?: boolean;
    dryRun?: boolean;
    rename?: (source: Source, target: Target, options: Options) => string | void | Promise<string | void>;
    filter?: (source: Source, target: Target, options: Options) => boolean | Promise<boolean>;
    transform?: (data: Buffer, source: Source, target: Target, options: Options) => Buffer | Promise<Buffer>;
    afterEach?: (source: Source, target: Target, options: Options) => void | Promise<void>;
};

type Source = {
    path: string;
    stats: fs.Stats;
};

type Target = {
    path: string;
    stats?: fs.Stats;
};

type Totals = {
    directories: number;
    files: number;
    symlinks: number;
    size: number; // not size on disk in blocks
};

Options

Copy is a superset of fs-extra/copy. Option names and default values correspond to fs-extra/copy options.

OptionDescription
overwritePreserves exising files when set to false. Default: true
errorOnExistUsed in conjunction with overwrite: false. Default: false
dereferenceCopies files if true. Default: false
preserveTimestampsPreserves the original timestamps. Default: false
dryRun*)Does not perform any write operations. afterEach is called and needs to check options.dryRun. Default: false
rename*)Optional rename function. A target path is renamed when returning a non-empty string, otherwise the original name is taken. When moving a directory to a different location, internally a recursive mkdir might be used. In such a case at least node v10.12 is required.
filterOptional path filter. Paths are excluded when returning false and included on true.
transform*)Optional transformation of file contents.
afterEach*)Optional action that is performed after a path has been copied, even on a dry-run. Please check options.dryRun and/or if target.stats is defined.

*) fs-extra does not have this feature

Option precedence

  1. First, the target path is updated by calling the rename function, if present. Please note that the current target path is passed to the function.
  2. Then the optional filter function is applied. Because we do this after the target has been renamed, we are able to take the contents of an existing target into account.
  3. Next, the optional transform function is called.
  4. After the target has been written, the optional afterEach function is called.

Copyright © 2020 by Daniel Dietrich. Released under the MIT license.

版本与发布记录

@danieldietrich/copy 共发布 16 个版本,首次发布于 2019/09/20;过去 12 个月发布了 0 个版本。

版本发布日期距今
0.4.2latest2019/12/266 年前
0.4.12019/12/266 年前
2.0.02019/12/256 年前
1.0.02019/12/256 年前
0.4.02019/12/256 年前
0.3.22019/12/246 年前

关于 @danieldietrich/copy 的常见问题

@danieldietrich/copy 是什么?

Simple yet powerful copy tool.

如何安装 @danieldietrich/copy?

在项目目录运行 npm install @danieldietrich/copy,或使用 pnpm add / yarn add / bun add。

@danieldietrich/copy 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@danieldietrich/copy 支持 ESM 还是 CommonJS?

以 CommonJS 形式发布,可以直接 require();在 ES Module 项目中也可以用 import 引入(Node.js 会把 module.exports 作为默认导出)。

@danieldietrich/copy 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=10.12.0。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@danieldietrich/copy 可以商用吗?

@danieldietrich/copy 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@danieldietrich/copy 的下载量有多少?

根据 npm 官方下载统计,@danieldietrich/copy 在 2026/10/11 – 2026/11/09 这 30 天内被下载 115,068 次,日均约 3,836 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@danieldietrich/copy 还在维护吗?

最新版本 0.4.2 发布于 2019/12/26(6 年前);至今共发布 16 个版本,其中过去 12 个月发布了 0 个;首次发布于 2019/09/20。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。