| import NestedError from 'nested-error-stacks'; | ||
| // TODO: Use `Error#cause`. | ||
| export default class CopyFileError extends NestedError { | ||
| constructor(message, nested) { | ||
| super(message, nested); | ||
| Object.assign(this, nested); | ||
| this.name = 'CopyFileError'; | ||
| } | ||
| } |
+33
-34
@@ -1,17 +0,16 @@ | ||
| 'use strict'; | ||
| const {promisify} = require('util'); | ||
| const fs = require('graceful-fs'); | ||
| const makeDir = require('make-dir'); | ||
| const pEvent = require('p-event'); | ||
| const CpFileError = require('./cp-file-error'); | ||
| import {promisify} from 'node:util'; | ||
| import fs from 'graceful-fs'; | ||
| import {pEvent} from 'p-event'; | ||
| import CopyFileError from './copy-file-error.js'; | ||
| const stat = promisify(fs.stat); | ||
| const lstat = promisify(fs.lstat); | ||
| const utimes = promisify(fs.utimes); | ||
| const chmod = promisify(fs.chmod); | ||
| const statP = promisify(fs.stat); | ||
| const lstatP = promisify(fs.lstat); | ||
| const utimesP = promisify(fs.utimes); | ||
| const chmodP = promisify(fs.chmod); | ||
| const makeDirectoryP = promisify(fs.mkdir); | ||
| exports.closeSync = fs.closeSync.bind(fs); | ||
| exports.createWriteStream = fs.createWriteStream.bind(fs); | ||
| export const closeSync = fs.closeSync.bind(fs); | ||
| export const createWriteStream = fs.createWriteStream.bind(fs); | ||
| exports.createReadStream = async (path, options) => { | ||
| export async function createReadStream(path, options) { | ||
| const read = fs.createReadStream(path, options); | ||
@@ -22,58 +21,58 @@ | ||
| } catch (error) { | ||
| throw new CpFileError(`Cannot read from \`${path}\`: ${error.message}`, error); | ||
| throw new CopyFileError(`Cannot read from \`${path}\`: ${error.message}`, error); | ||
| } | ||
| return read; | ||
| }; | ||
| } | ||
| exports.stat = path => stat(path).catch(error => { | ||
| throw new CpFileError(`Cannot stat path \`${path}\`: ${error.message}`, error); | ||
| export const stat = path => statP(path).catch(error => { | ||
| throw new CopyFileError(`Cannot stat path \`${path}\`: ${error.message}`, error); | ||
| }); | ||
| exports.lstat = path => lstat(path).catch(error => { | ||
| throw new CpFileError(`lstat \`${path}\` failed: ${error.message}`, error); | ||
| export const lstat = path => lstatP(path).catch(error => { | ||
| throw new CopyFileError(`lstat \`${path}\` failed: ${error.message}`, error); | ||
| }); | ||
| exports.utimes = (path, atime, mtime) => utimes(path, atime, mtime).catch(error => { | ||
| throw new CpFileError(`utimes \`${path}\` failed: ${error.message}`, error); | ||
| export const utimes = (path, atime, mtime) => utimesP(path, atime, mtime).catch(error => { | ||
| throw new CopyFileError(`utimes \`${path}\` failed: ${error.message}`, error); | ||
| }); | ||
| exports.chmod = (path, mode) => chmod(path, mode).catch(error => { | ||
| throw new CpFileError(`chmod \`${path}\` failed: ${error.message}`, error); | ||
| export const chmod = (path, mode) => chmodP(path, mode).catch(error => { | ||
| throw new CopyFileError(`chmod \`${path}\` failed: ${error.message}`, error); | ||
| }); | ||
| exports.statSync = path => { | ||
| export const statSync = path => { | ||
| try { | ||
| return fs.statSync(path); | ||
| } catch (error) { | ||
| throw new CpFileError(`stat \`${path}\` failed: ${error.message}`, error); | ||
| throw new CopyFileError(`stat \`${path}\` failed: ${error.message}`, error); | ||
| } | ||
| }; | ||
| exports.utimesSync = (path, atime, mtime) => { | ||
| export const utimesSync = (path, atime, mtime) => { | ||
| try { | ||
| return fs.utimesSync(path, atime, mtime); | ||
| } catch (error) { | ||
| throw new CpFileError(`utimes \`${path}\` failed: ${error.message}`, error); | ||
| throw new CopyFileError(`utimes \`${path}\` failed: ${error.message}`, error); | ||
| } | ||
| }; | ||
| exports.makeDir = (path, options) => makeDir(path, {...options, fs}).catch(error => { | ||
| throw new CpFileError(`Cannot create directory \`${path}\`: ${error.message}`, error); | ||
| export const makeDirectory = (path, options) => makeDirectoryP(path, {...options, recursive: true}).catch(error => { | ||
| throw new CopyFileError(`Cannot create directory \`${path}\`: ${error.message}`, error); | ||
| }); | ||
| exports.makeDirSync = (path, options) => { | ||
| export const makeDirectorySync = (path, options) => { | ||
| try { | ||
| makeDir.sync(path, {...options, fs}); | ||
| fs.mkdirSync(path, {...options, recursive: true}); | ||
| } catch (error) { | ||
| throw new CpFileError(`Cannot create directory \`${path}\`: ${error.message}`, error); | ||
| throw new CopyFileError(`Cannot create directory \`${path}\`: ${error.message}`, error); | ||
| } | ||
| }; | ||
| exports.copyFileSync = (source, destination, flags) => { | ||
| export const copyFileSync = (source, destination, flags) => { | ||
| try { | ||
| fs.copyFileSync(source, destination, flags); | ||
| } catch (error) { | ||
| throw new CpFileError(`Cannot copy from \`${source}\` to \`${destination}\`: ${error.message}`, error); | ||
| throw new CopyFileError(`Cannot copy from \`${source}\` to \`${destination}\`: ${error.message}`, error); | ||
| } | ||
| }; |
+81
-67
@@ -1,91 +0,105 @@ | ||
| declare namespace cpFile { | ||
| interface Options { | ||
| /** | ||
| Overwrite existing destination file. | ||
| export interface Options { | ||
| /** | ||
| Overwrite existing destination file. | ||
| @default true | ||
| */ | ||
| readonly overwrite?: boolean; | ||
| @default true | ||
| */ | ||
| readonly overwrite?: boolean; | ||
| /** | ||
| [Permissions](https://en.wikipedia.org/wiki/File-system_permissions#Numeric_notation) for created directories. | ||
| /** | ||
| [Permissions](https://en.wikipedia.org/wiki/File-system_permissions#Numeric_notation) for created directories. | ||
| It has no effect on Windows. | ||
| It has no effect on Windows. | ||
| @default 0o777 | ||
| */ | ||
| readonly directoryMode?: number; | ||
| } | ||
| @default 0o777 | ||
| */ | ||
| readonly directoryMode?: number; | ||
| interface ProgressData { | ||
| /** | ||
| Absolute path to source. | ||
| */ | ||
| sourcePath: string; | ||
| /** | ||
| The working directory to find source files. | ||
| /** | ||
| Absolute path to destination. | ||
| */ | ||
| destinationPath: string; | ||
| The source and destination path are relative to this. | ||
| /** | ||
| File size in bytes. | ||
| */ | ||
| size: number; | ||
| /** | ||
| Copied size in bytes. | ||
| */ | ||
| writtenBytes: number; | ||
| /** | ||
| Copied percentage, a value between `0` and `1`. | ||
| */ | ||
| percent: number; | ||
| } | ||
| interface ProgressEmitter { | ||
| /** | ||
| Note: For empty files, the `progress` event is emitted only once. | ||
| */ | ||
| on(event: 'progress', handler: (data: ProgressData) => void): Promise<void>; | ||
| } | ||
| @default process.cwd() | ||
| */ | ||
| readonly cwd?: string; | ||
| } | ||
| declare const cpFile: { | ||
| export interface AsyncOptions { | ||
| /** | ||
| Copy a file. | ||
| The given function is called whenever there is measurable progress. | ||
| @param source - The file you want to copy. | ||
| @param destination - Where you want the file copied. | ||
| @returns A `Promise` that resolves when the file is copied. | ||
| Note: For empty files, the `onProgress` event is emitted only once. | ||
| @example | ||
| ``` | ||
| import cpFile = require('cp-file'); | ||
| import {copyFile} from 'cp-file'; | ||
| (async () => { | ||
| await cpFile('source/unicorn.png', 'destination/unicorn.png'); | ||
| console.log('File copied'); | ||
| })(); | ||
| await copyFile('source/unicorn.png', 'destination/unicorn.png', { | ||
| onProgress: progress => { | ||
| // … | ||
| } | ||
| }); | ||
| ``` | ||
| */ | ||
| (source: string, destination: string, options?: cpFile.Options): Promise<void> & cpFile.ProgressEmitter; | ||
| readonly onProgress?: (progress: ProgressData) => void; | ||
| } | ||
| export interface ProgressData { | ||
| /** | ||
| Copy a file synchronously. | ||
| Absolute path to source. | ||
| */ | ||
| sourcePath: string; | ||
| @param source - The file you want to copy. | ||
| @param destination - Where you want the file copied. | ||
| /** | ||
| Absolute path to destination. | ||
| */ | ||
| destinationPath: string; | ||
| @example | ||
| ``` | ||
| import cpFile = require('cp-file'); | ||
| /** | ||
| File size in bytes. | ||
| */ | ||
| size: number; | ||
| cpFile.sync('source/unicorn.png', 'destination/unicorn.png'); | ||
| ``` | ||
| /** | ||
| Copied size in bytes. | ||
| */ | ||
| sync(source: string, destination: string, options?: cpFile.Options): void; | ||
| }; | ||
| writtenBytes: number; | ||
| export = cpFile; | ||
| /** | ||
| Copied percentage, a value between `0` and `1`. | ||
| */ | ||
| percent: number; | ||
| } | ||
| /** | ||
| Copy a file. | ||
| @param source - The file you want to copy. | ||
| @param destination - Where you want the file copied. | ||
| @returns A `Promise` that resolves when the file is copied. | ||
| @example | ||
| ``` | ||
| import {copyFile} from 'cp-file'; | ||
| await copyFile('source/unicorn.png', 'destination/unicorn.png'); | ||
| console.log('File copied'); | ||
| ``` | ||
| */ | ||
| export function copyFile(source: string, destination: string, options?: Options & AsyncOptions): Promise<void>; | ||
| /** | ||
| Copy a file synchronously. | ||
| @param source - The file you want to copy. | ||
| @param destination - Where you want the file copied. | ||
| @example | ||
| ``` | ||
| import {copyFileSync} from 'cp-file'; | ||
| copyFileSync('source/unicorn.png', 'destination/unicorn.png'); | ||
| ``` | ||
| */ | ||
| export function copyFileSync(source: string, destination: string, options?: Options): void; |
+62
-44
@@ -1,25 +0,35 @@ | ||
| 'use strict'; | ||
| const path = require('path'); | ||
| const {constants: fsConstants} = require('fs'); | ||
| const pEvent = require('p-event'); | ||
| const CpFileError = require('./cp-file-error'); | ||
| const fs = require('./fs'); | ||
| const ProgressEmitter = require('./progress-emitter'); | ||
| import path from 'node:path'; | ||
| import {constants as fsConstants} from 'node:fs'; | ||
| import {pEvent} from 'p-event'; | ||
| import CopyFileError from './copy-file-error.js'; | ||
| import * as fs from './fs.js'; | ||
| const cpFileAsync = async (source, destination, options, progressEmitter) => { | ||
| const copyFileAsync = async (source, destination, options) => { | ||
| let readError; | ||
| const stat = await fs.stat(source); | ||
| progressEmitter.size = stat.size; | ||
| const {size} = await fs.stat(source); | ||
| const readStream = await fs.createReadStream(source); | ||
| await fs.makeDir(path.dirname(destination), {mode: options.directoryMode}); | ||
| await fs.makeDirectory(path.dirname(destination), {mode: options.directoryMode}); | ||
| const writeStream = fs.createWriteStream(destination, {flags: options.overwrite ? 'w' : 'wx'}); | ||
| const emitProgress = writtenBytes => { | ||
| if (typeof options.onProgress !== 'function') { | ||
| return; | ||
| } | ||
| options.onProgress({ | ||
| sourcePath: path.resolve(source), | ||
| destinationPath: path.resolve(destination), | ||
| size, | ||
| writtenBytes, | ||
| percent: writtenBytes === size ? 1 : writtenBytes / size, | ||
| }); | ||
| }; | ||
| readStream.on('data', () => { | ||
| progressEmitter.writtenBytes = writeStream.bytesWritten; | ||
| emitProgress(writeStream.bytesWritten); | ||
| }); | ||
| readStream.once('error', error => { | ||
| readError = new CpFileError(`Cannot read from \`${source}\`: ${error.message}`, error); | ||
| writeStream.end(); | ||
| readError = new CopyFileError(`Cannot read from \`${source}\`: ${error.message}`, error); | ||
| }); | ||
@@ -32,6 +42,6 @@ | ||
| await writePromise; | ||
| progressEmitter.writtenBytes = progressEmitter.size; | ||
| emitProgress(size); | ||
| shouldUpdateStats = true; | ||
| } catch (error) { | ||
| throw new CpFileError(`Cannot write to \`${destination}\`: ${error.message}`, error); | ||
| throw new CopyFileError(`Cannot write to \`${destination}\`: ${error.message}`, error); | ||
| } | ||
@@ -48,3 +58,3 @@ | ||
| fs.utimes(destination, stats.atime, stats.mtime), | ||
| fs.chmod(destination, stats.mode) | ||
| fs.chmod(destination, stats.mode), | ||
| ]); | ||
@@ -54,31 +64,35 @@ } | ||
| const cpFile = (sourcePath, destinationPath, options) => { | ||
| const resolvePath = (cwd, sourcePath, destinationPath) => { | ||
| sourcePath = path.resolve(cwd, sourcePath); | ||
| destinationPath = path.resolve(cwd, destinationPath); | ||
| return { | ||
| sourcePath, | ||
| destinationPath, | ||
| }; | ||
| }; | ||
| export async function copyFile(sourcePath, destinationPath, options = {}) { | ||
| if (!sourcePath || !destinationPath) { | ||
| return Promise.reject(new CpFileError('`source` and `destination` required')); | ||
| throw new CopyFileError('`source` and `destination` required'); | ||
| } | ||
| if (options.cwd) { | ||
| ({sourcePath, destinationPath} = resolvePath(options.cwd, sourcePath, destinationPath)); | ||
| } | ||
| options = { | ||
| overwrite: true, | ||
| ...options | ||
| ...options, | ||
| }; | ||
| const progressEmitter = new ProgressEmitter(path.resolve(sourcePath), path.resolve(destinationPath)); | ||
| const promise = cpFileAsync(sourcePath, destinationPath, options, progressEmitter); | ||
| return copyFileAsync(sourcePath, destinationPath, options); | ||
| } | ||
| promise.on = (...arguments_) => { | ||
| progressEmitter.on(...arguments_); | ||
| return promise; | ||
| }; | ||
| return promise; | ||
| }; | ||
| module.exports = cpFile; | ||
| const checkSourceIsFile = (stat, source) => { | ||
| if (stat.isDirectory()) { | ||
| throw Object.assign(new CpFileError(`EISDIR: illegal operation on a directory '${source}'`), { | ||
| throw Object.assign(new CopyFileError(`EISDIR: illegal operation on a directory '${source}'`), { | ||
| errno: -21, | ||
| code: 'EISDIR', | ||
| source | ||
| source, | ||
| }); | ||
@@ -88,19 +102,23 @@ } | ||
| module.exports.sync = (source, destination, options) => { | ||
| if (!source || !destination) { | ||
| throw new CpFileError('`source` and `destination` required'); | ||
| export function copyFileSync(sourcePath, destinationPath, options = {}) { | ||
| if (!sourcePath || !destinationPath) { | ||
| throw new CopyFileError('`source` and `destination` required'); | ||
| } | ||
| if (options.cwd) { | ||
| ({sourcePath, destinationPath} = resolvePath(options.cwd, sourcePath, destinationPath)); | ||
| } | ||
| options = { | ||
| overwrite: true, | ||
| ...options | ||
| ...options, | ||
| }; | ||
| const stat = fs.statSync(source); | ||
| checkSourceIsFile(stat, source); | ||
| fs.makeDirSync(path.dirname(destination), {mode: options.directoryMode}); | ||
| const stat = fs.statSync(sourcePath); | ||
| checkSourceIsFile(stat, sourcePath); | ||
| fs.makeDirectorySync(path.dirname(destinationPath), {mode: options.directoryMode}); | ||
| const flags = options.overwrite ? null : fsConstants.COPYFILE_EXCL; | ||
| try { | ||
| fs.copyFileSync(source, destination, flags); | ||
| fs.copyFileSync(sourcePath, destinationPath, flags); | ||
| } catch (error) { | ||
@@ -114,3 +132,3 @@ if (!options.overwrite && error.code === 'EEXIST') { | ||
| fs.utimesSync(destination, stat.atime, stat.mtime); | ||
| }; | ||
| fs.utimesSync(destinationPath, stat.atime, stat.mtime); | ||
| } |
+24
-20
| { | ||
| "name": "cp-file", | ||
| "version": "9.1.0", | ||
| "version": "10.0.0", | ||
| "description": "Copy a file", | ||
@@ -13,4 +13,7 @@ "license": "MIT", | ||
| }, | ||
| "type": "module", | ||
| "exports": "./index.js", | ||
| "types": "./index.d.ts", | ||
| "engines": { | ||
| "node": ">=10" | ||
| "node": ">=14.16" | ||
| }, | ||
@@ -21,7 +24,6 @@ "scripts": { | ||
| "files": [ | ||
| "cp-file-error.js", | ||
| "fs.js", | ||
| "index.js", | ||
| "index.d.ts", | ||
| "progress-emitter.js" | ||
| "copy-file-error.js", | ||
| "fs.js" | ||
| ], | ||
@@ -44,24 +46,26 @@ "keywords": [ | ||
| "dependencies": { | ||
| "graceful-fs": "^4.1.2", | ||
| "make-dir": "^3.0.0", | ||
| "nested-error-stacks": "^2.0.0", | ||
| "p-event": "^4.1.0" | ||
| "graceful-fs": "^4.2.10", | ||
| "nested-error-stacks": "^2.1.1", | ||
| "p-event": "^5.0.1" | ||
| }, | ||
| "devDependencies": { | ||
| "ava": "^2.1.0", | ||
| "clear-module": "^3.1.0", | ||
| "coveralls": "^3.0.4", | ||
| "del": "^5.1.0", | ||
| "import-fresh": "^3.0.0", | ||
| "nyc": "^15.0.0", | ||
| "sinon": "^9.0.0", | ||
| "tsd": "^0.11.0", | ||
| "uuid": "^7.0.2", | ||
| "xo": "^0.28.2" | ||
| "ava": "^4.3.0", | ||
| "clear-module": "^4.1.2", | ||
| "coveralls": "^3.1.1", | ||
| "del": "^6.1.1", | ||
| "import-fresh": "^3.3.0", | ||
| "nyc": "^15.1.0", | ||
| "sinon": "^14.0.0", | ||
| "tsd": "^0.21.0", | ||
| "xo": "^0.50.0" | ||
| }, | ||
| "xo": { | ||
| "rules": { | ||
| "unicorn/string-content": "off" | ||
| "unicorn/string-content": "off", | ||
| "ava/assertion-arguments": "off" | ||
| } | ||
| }, | ||
| "ava": { | ||
| "workerThreads": false | ||
| } | ||
| } |
+28
-23
@@ -16,5 +16,5 @@ # cp-file | ||
| ```sh | ||
| npm install cp-file | ||
| ``` | ||
| $ npm install cp-file | ||
| ``` | ||
@@ -24,8 +24,6 @@ ## Usage | ||
| ```js | ||
| const cpFile = require('cp-file'); | ||
| import {copyFile} from 'cp-file'; | ||
| (async () => { | ||
| await cpFile('source/unicorn.png', 'destination/unicorn.png'); | ||
| console.log('File copied'); | ||
| })(); | ||
| await copyFile('source/unicorn.png', 'destination/unicorn.png'); | ||
| console.log('File copied'); | ||
| ``` | ||
@@ -35,7 +33,7 @@ | ||
| ### cpFile(source, destination, options?) | ||
| ### copyFile(source, destination, options?) | ||
| Returns a `Promise` that resolves when the file is copied. | ||
| ### cpFile.sync(source, destination, options?) | ||
| ### copyFileSync(source, destination, options?) | ||
@@ -65,2 +63,11 @@ #### source | ||
| ##### cwd | ||
| Type: `string`\ | ||
| Default: `process.cwd()` | ||
| The working directory to find source files. | ||
| The source and destination path are relative to this. | ||
| ##### directoryMode | ||
@@ -75,11 +82,11 @@ | ||
| ### cpFile.on('progress', handler) | ||
| ##### onProgress | ||
| Progress reporting. Only available when using the async method. | ||
| Type: `(progress: ProgressData) => void` | ||
| #### handler(data) | ||
| The given function is called whenever there is measurable progress. | ||
| Type: `Function` | ||
| Only available when using the async method. | ||
| ##### data | ||
| ###### `ProgressData` | ||
@@ -96,3 +103,3 @@ ```js | ||
| - `source` and `destination` are absolute paths. | ||
| - `sourcePath` and `destinationPath` are absolute paths. | ||
| - `size` and `writtenBytes` are in bytes. | ||
@@ -103,14 +110,12 @@ - `percent` is a value between `0` and `1`. | ||
| - For empty files, the `progress` event is emitted only once. | ||
| - The `.on()` method is available only right after the initial `cpFile()` call. So make sure | ||
| you add a `handler` before `.then()`: | ||
| - For empty files, the `onProgress` callback function is emitted only once. | ||
| ```js | ||
| const cpFile = require('cp-file'); | ||
| import {copyFile} from 'cp-file'; | ||
| (async () => { | ||
| await cpFile(source, destination).on('progress', data => { | ||
| await copyFile(source, destination, { | ||
| onProgress: progress => { | ||
| // … | ||
| }); | ||
| })(); | ||
| } | ||
| }); | ||
| ``` | ||
@@ -117,0 +122,0 @@ |
| 'use strict'; | ||
| const NestedError = require('nested-error-stacks'); | ||
| class CpFileError extends NestedError { | ||
| constructor(message, nested) { | ||
| super(message, nested); | ||
| Object.assign(this, nested); | ||
| this.name = 'CpFileError'; | ||
| } | ||
| } | ||
| module.exports = CpFileError; |
| 'use strict'; | ||
| const EventEmitter = require('events'); | ||
| const writtenBytes = new WeakMap(); | ||
| class ProgressEmitter extends EventEmitter { | ||
| constructor(sourcePath, destinationPath) { | ||
| super(); | ||
| this._sourcePath = sourcePath; | ||
| this._destinationPath = destinationPath; | ||
| } | ||
| get writtenBytes() { | ||
| return writtenBytes.get(this); | ||
| } | ||
| set writtenBytes(value) { | ||
| writtenBytes.set(this, value); | ||
| this.emitProgress(); | ||
| } | ||
| emitProgress() { | ||
| const {size, writtenBytes} = this; | ||
| this.emit('progress', { | ||
| sourcePath: this._sourcePath, | ||
| destinationPath: this._destinationPath, | ||
| size, | ||
| writtenBytes, | ||
| percent: writtenBytes === size ? 1 : writtenBytes / size | ||
| }); | ||
| } | ||
| } | ||
| module.exports = ProgressEmitter; |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
12807
1.47%3
-25%9
-10%122
4.27%1
-50%Yes
NaN7
-12.5%258
-2.27%1
Infinity%+ Added
+ Added
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
Updated
Updated
Updated