Skip to content

Commit

Permalink
Require Node.js 18
Browse files Browse the repository at this point in the history
  • Loading branch information
sindresorhus committed Nov 5, 2023
1 parent a98bdff commit 8b16c8e
Show file tree
Hide file tree
Showing 12 changed files with 73 additions and 71 deletions.
7 changes: 3 additions & 4 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,15 @@ jobs:
fail-fast: false
matrix:
node-version:
- 20
- 18
- 16
- 14
os:
- ubuntu-latest
- macos-latest
- windows-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- run: npm install
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ node_modules
yarn.lock
.nyc_output
coverage
temp
11 changes: 4 additions & 7 deletions copy-file-error.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
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);
export default class CopyFileError extends Error {
constructor(message, {cause} = {}) {
super(message, {cause});
Object.assign(this, cause);
this.name = 'CopyFileError';
}
}
20 changes: 10 additions & 10 deletions fs.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,60 +18,60 @@ export async function createReadStream(path, options) {
try {
await pEvent(read, ['readable', 'end']);
} catch (error) {
throw new CopyFileError(`Cannot read from \`${path}\`: ${error.message}`, error);
throw new CopyFileError(`Cannot read from \`${path}\`: ${error.message}`, {cause: error});
}

return read;
}

export const stat = path => statP(path).catch(error => {
throw new CopyFileError(`Cannot stat path \`${path}\`: ${error.message}`, error);
throw new CopyFileError(`Cannot stat path \`${path}\`: ${error.message}`, {cause: error});
});

export const lstat = path => lstatP(path).catch(error => {
throw new CopyFileError(`lstat \`${path}\` failed: ${error.message}`, error);
throw new CopyFileError(`lstat \`${path}\` failed: ${error.message}`, {cause: error});
});

export const utimes = (path, atime, mtime) => utimesP(path, atime, mtime).catch(error => {
throw new CopyFileError(`utimes \`${path}\` failed: ${error.message}`, error);
throw new CopyFileError(`utimes \`${path}\` failed: ${error.message}`, {cause: error});
});

export const chmod = (path, mode) => chmodP(path, mode).catch(error => {
throw new CopyFileError(`chmod \`${path}\` failed: ${error.message}`, error);
throw new CopyFileError(`chmod \`${path}\` failed: ${error.message}`, {cause: error});
});

export const statSync = path => {
try {
return fs.statSync(path);
} catch (error) {
throw new CopyFileError(`stat \`${path}\` failed: ${error.message}`, error);
throw new CopyFileError(`stat \`${path}\` failed: ${error.message}`, {cause: error});
}
};

export const utimesSync = (path, atime, mtime) => {
try {
return fs.utimesSync(path, atime, mtime);
} catch (error) {
throw new CopyFileError(`utimes \`${path}\` failed: ${error.message}`, error);
throw new CopyFileError(`utimes \`${path}\` failed: ${error.message}`, {cause: error});
}
};

export const makeDirectory = (path, options) => makeDirectoryP(path, {...options, recursive: true}).catch(error => {
throw new CopyFileError(`Cannot create directory \`${path}\`: ${error.message}`, error);
throw new CopyFileError(`Cannot create directory \`${path}\`: ${error.message}`, {cause: error});
});

export const makeDirectorySync = (path, options) => {
try {
fs.mkdirSync(path, {...options, recursive: true});
} catch (error) {
throw new CopyFileError(`Cannot create directory \`${path}\`: ${error.message}`, error);
throw new CopyFileError(`Cannot create directory \`${path}\`: ${error.message}`, {cause: error});
}
};

export const copyFileSync = (source, destination, flags) => {
try {
fs.copyFileSync(source, destination, flags);
} catch (error) {
throw new CopyFileError(`Cannot copy from \`${source}\` to \`${destination}\`: ${error.message}`, error);
throw new CopyFileError(`Cannot copy from \`${source}\` to \`${destination}\`: ${error.message}`, {cause: error});
}
};
12 changes: 6 additions & 6 deletions index.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export interface Options {
export type Options = {
/**
Overwrite existing destination file.
Expand All @@ -23,9 +23,9 @@ export interface Options {
@default process.cwd()
*/
readonly cwd?: string;
}
};

export interface AsyncOptions {
export type AsyncOptions = {
/**
The given function is called whenever there is measurable progress.
Expand All @@ -43,9 +43,9 @@ export interface AsyncOptions {
```
*/
readonly onProgress?: (progress: ProgressData) => void;
}
};

export interface ProgressData {
export type ProgressData = {
/**
Absolute path to source.
*/
Expand All @@ -70,7 +70,7 @@ export interface ProgressData {
Copied percentage, a value between `0` and `1`.
*/
percent: number;
}
};

/**
Copy a file.
Expand Down
4 changes: 2 additions & 2 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ const copyFileAsync = async (source, destination, options) => {
});

readStream.once('error', error => {
readError = new CopyFileError(`Cannot read from \`${source}\`: ${error.message}`, error);
readError = new CopyFileError(`Cannot read from \`${source}\`: ${error.message}`, {cause: error});
});

let shouldUpdateStats = false;
Expand All @@ -42,7 +42,7 @@ const copyFileAsync = async (source, destination, options) => {
emitProgress(size);
shouldUpdateStats = true;
} catch (error) {
throw new CopyFileError(`Cannot write to \`${destination}\`: ${error.message}`, error);
throw new CopyFileError(`Cannot write to \`${destination}\`: ${error.message}`, {cause: error});
}

if (readError) {
Expand Down
2 changes: 1 addition & 1 deletion index.test-d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {expectError, expectType} from 'tsd';
import {copyFile, copyFileSync, ProgressData} from './index.js';
import {copyFile, copyFileSync, type ProgressData} from './index.js';

expectType<Promise<void> >(
copyFile('source/unicorn.png', 'destination/unicorn.png'),
Expand Down
34 changes: 20 additions & 14 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@
"url": "https://sindresorhus.com"
},
"type": "module",
"exports": "./index.js",
"types": "./index.d.ts",
"exports": {
"types": "./index.d.ts",
"default": "./index.js"
},
"engines": {
"node": ">=14.16"
"node": ">=18"
},
"scripts": {
"test": "xo && nyc ava && tsd"
Expand All @@ -27,42 +29,46 @@
],
"keywords": [
"copy",
"copying",
"cp",
"file",
"clone",
"fs",
"stream",
"file-system",
"filesystem",
"ncp",
"fast",
"quick",
"data",
"content",
"contents"
"contents",
"read",
"write",
"io"
],
"dependencies": {
"graceful-fs": "^4.2.10",
"nested-error-stacks": "^2.1.1",
"p-event": "^5.0.1"
"graceful-fs": "^4.2.11",
"p-event": "^6.0.0"
},
"devDependencies": {
"ava": "^4.3.0",
"ava": "^5.3.1",
"clear-module": "^4.1.2",
"coveralls": "^3.1.1",
"del": "^6.1.1",
"del": "^7.1.0",
"import-fresh": "^3.3.0",
"nyc": "^15.1.0",
"sinon": "^14.0.0",
"tsd": "^0.21.0",
"xo": "^0.50.0"
"sinon": "^17.0.1",
"tsd": "^0.29.0",
"xo": "^0.56.0"
},
"xo": {
"rules": {
"unicorn/string-content": "off",
"ava/assertion-arguments": "off"
}
},
"ava": {
"workerThreads": false
"workerThreads": false,
"serial": true
}
}
4 changes: 2 additions & 2 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
- Fast by using streams in the async version and [`fs.copyFileSync()`](https://nodejs.org/api/fs.html#fs_fs_copyfilesync_src_dest_flags) in the synchronous version.
- Resilient by using [graceful-fs](https://github.com/isaacs/node-graceful-fs).
- User-friendly by creating non-existent destination directories for you.
- Can be safe by turning off [overwriting](#optionsoverwrite).
- Preserves file mode, [but not ownership](https://github.com/sindresorhus/cp-file/issues/22#issuecomment-502079547).
- Can be safe by turning off [overwriting](#overwrite).
- Preserves file mode [but not ownership](https://github.com/sindresorhus/cp-file/issues/22#issuecomment-502079547).
- User-friendly errors.

## Install
Expand Down
15 changes: 8 additions & 7 deletions test/async.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import fs from 'node:fs';
import {fileURLToPath} from 'node:url';
import importFresh from 'import-fresh';
import clearModule from 'clear-module';
import del from 'del';
import {deleteSync} from 'del';
import test from 'ava';
import sinon from 'sinon';
import {copyFile} from '../index.js';
Expand All @@ -18,16 +18,17 @@ const THREE_HUNDRED_KILO = (100 * 3 * 1024) + 1;

test.before(() => {
process.chdir(path.dirname(__dirname));
deleteSync('temp'); // In case last test run failed.
fs.mkdirSync('temp');
});

test.beforeEach(t => {
t.context.source = crypto.randomUUID();
t.context.destination = crypto.randomUUID();
t.context.creates = [t.context.source, t.context.destination];
test.after(() => {
deleteSync('temp');
});

test.afterEach.always(t => {
del.sync(t.context.creates);
test.beforeEach(t => {
t.context.source = path.join('temp', crypto.randomUUID());
t.context.destination = path.join('temp', crypto.randomUUID());
});

test('reject an Error on missing `source`', async t => {
Expand Down
17 changes: 8 additions & 9 deletions test/progress.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import crypto from 'node:crypto';
import path from 'node:path';
import {fileURLToPath} from 'node:url';
import fs from 'node:fs';
import del from 'del';
import {deleteSync} from 'del';
import test from 'ava';
import {copyFile} from '../index.js';

Expand All @@ -13,18 +13,17 @@ const THREE_HUNDRED_KILO = (100 * 3 * 1024) + 1;

test.before(() => {
process.chdir(path.dirname(__dirname));
deleteSync('temp'); // In case last test run failed.
fs.mkdirSync('temp');
});

test.beforeEach(t => {
t.context.source = crypto.randomUUID();
t.context.destination = crypto.randomUUID();
t.context.creates = [t.context.source, t.context.destination];
test.after(() => {
deleteSync('temp');
});

test.afterEach.always(t => {
for (const path of t.context.creates) {
del.sync(path);
}
test.beforeEach(t => {
t.context.source = path.join('temp', crypto.randomUUID());
t.context.destination = path.join('temp', crypto.randomUUID());
});

test('report progress', async t => {
Expand Down
17 changes: 8 additions & 9 deletions test/sync.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import crypto from 'node:crypto';
import {fileURLToPath} from 'node:url';
import path from 'node:path';
import fs from 'node:fs';
import del from 'del';
import {deleteSync} from 'del';
import test from 'ava';
import sinon from 'sinon';
import {copyFileSync} from '../index.js';
Expand All @@ -16,18 +16,17 @@ const THREE_HUNDRED_KILO = (100 * 3 * 1024) + 1;

test.before(() => {
process.chdir(path.dirname(__dirname));
deleteSync('temp'); // In case last test run failed.
fs.mkdirSync('temp');
});

test.beforeEach(t => {
t.context.source = crypto.randomUUID();
t.context.destination = crypto.randomUUID();
t.context.creates = [t.context.source, t.context.destination];
test.after(() => {
deleteSync('temp');
});

test.afterEach.always(t => {
for (const path_ of t.context.creates) {
del.sync(path_);
}
test.beforeEach(t => {
t.context.source = path.join('temp', crypto.randomUUID());
t.context.destination = path.join('temp', crypto.randomUUID());
});

test('throw an Error on missing `source`', t => {
Expand Down

0 comments on commit 8b16c8e

Please sign in to comment.