Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix the cwd option #96

Merged
merged 4 commits into from Jul 12, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
24 changes: 13 additions & 11 deletions index.js
Expand Up @@ -3,40 +3,41 @@ const {promisify} = require('util');
const path = require('path');
const globby = require('globby');
const isPathCwd = require('is-path-cwd');
const isPathInCwd = require('is-path-in-cwd');
const isPathInside = require('is-path-inside');
const rimraf = require('rimraf');
const pMap = require('p-map');

const rimrafP = promisify(rimraf);

function safeCheck(file) {
function safeCheck(file, cwd) {
if (isPathCwd(file)) {
throw new Error('Cannot delete the current working directory. Can be overridden with the `force` option.');
}

if (!isPathInCwd(file)) {
if (!isPathInside(file, cwd)) {
throw new Error('Cannot delete files/directories outside the current working directory. Can be overridden with the `force` option.');
}
}

module.exports = async (patterns, {force, dryRun, ...options} = {}) => {
module.exports = async (patterns, {force, dryRun, cwd = process.cwd(), ...options} = {}) => {
options = {
expandDirectories: false,
onlyFiles: false,
followSymbolicLinks: false,
cwd,
...options
};

const files = (await globby(patterns, options))
.sort((a, b) => b.localeCompare(a));

const mapper = async file => {
file = path.resolve(cwd, file);

if (!force) {
safeCheck(file);
safeCheck(file, cwd);
}

file = path.resolve(options.cwd || '', file);

if (!dryRun) {
await rimrafP(file, {glob: false});
}
Expand All @@ -47,24 +48,25 @@ module.exports = async (patterns, {force, dryRun, ...options} = {}) => {
return pMap(files, mapper, options);
};

module.exports.sync = (patterns, {force, dryRun, ...options} = {}) => {
module.exports.sync = (patterns, {force, dryRun, cwd = process.cwd(), ...options} = {}) => {
options = {
expandDirectories: false,
onlyFiles: false,
followSymbolicLinks: false,
cwd,
...options
};

const files = globby.sync(patterns, options)
.sort((a, b) => b.localeCompare(a));

return files.map(file => {
file = path.resolve(cwd, file);

if (!force) {
safeCheck(file);
safeCheck(file, cwd);
}

file = path.resolve(options.cwd || '', file);

if (!dryRun) {
rimraf.sync(file, {glob: false});
}
Expand Down
2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -46,7 +46,7 @@
"dependencies": {
"globby": "^10.0.0",
"is-path-cwd": "^2.0.0",
"is-path-in-cwd": "^2.0.0",
"is-path-inside": "^3.0.1",
"p-map": "^2.0.0",
"rimraf": "^2.6.3"
},
Expand Down
138 changes: 134 additions & 4 deletions test.js
@@ -1,10 +1,12 @@
import path from 'path';
import fs from 'fs';
import test from 'ava';
import {serial as test} from 'ava';
import tempy from 'tempy';
import makeDir from 'make-dir';
import del from '.';

const processCwd = process.cwd();

function exists(t, files) {
for (const file of files) {
t.true(fs.existsSync(path.join(t.context.tmp, file)));
Expand Down Expand Up @@ -67,7 +69,7 @@ test('take options into account - sync', t => {
notExists(t, ['2.tmp', '3.tmp', '4.tmp', '.dot.tmp']);
});

test.serial('return deleted files - async', async t => {
test('return deleted files - async', async t => {
t.deepEqual(
await del('1.tmp', {cwd: t.context.tmp}),
[path.join(t.context.tmp, '1.tmp')]
Expand Down Expand Up @@ -109,7 +111,7 @@ test('don\'t delete files, but return them - sync', t => {

// Currently this only testable locally on an osx machine.
// https://github.com/sindresorhus/del/issues/68
test.serial('does not throw EINVAL - async', async t => {
test('does not throw EINVAL - async', async t => {
await del('**/*', {
cwd: t.context.tmp,
dot: true
Expand Down Expand Up @@ -144,7 +146,7 @@ test.serial('does not throw EINVAL - async', async t => {
t.is(count, totalAttempts);
});

test.serial('does not throw EINVAL - sync', t => {
test('does not throw EINVAL - sync', t => {
del.sync('**/*', {
cwd: t.context.tmp,
dot: true
Expand Down Expand Up @@ -177,3 +179,131 @@ test.serial('does not throw EINVAL - sync', t => {
notExists(t, [...fixtures, 'a']);
t.is(count, totalAttempts);
});

test('delete relative files outside of process.cwd using cwd - async', async t => {
await del(['1.tmp'], {cwd: t.context.tmp});

exists(t, ['2.tmp', '3.tmp', '4.tmp', '.dot.tmp']);
notExists(t, ['1.tmp']);
});

test('delete relative files outside of process.cwd using cwd - sync', t => {
del.sync(['1.tmp'], {cwd: t.context.tmp});

exists(t, ['2.tmp', '3.tmp', '4.tmp', '.dot.tmp']);
notExists(t, ['1.tmp']);
});

test('delete absolute files outside of process.cwd using cwd - async', async t => {
const absolutePath = path.resolve(t.context.tmp, '1.tmp');
await del([absolutePath], {cwd: t.context.tmp});

exists(t, ['2.tmp', '3.tmp', '4.tmp', '.dot.tmp']);
notExists(t, ['1.tmp']);
});

test('delete absolute files outside of process.cwd using cwd - sync', t => {
const absolutePath = path.resolve(t.context.tmp, '1.tmp');
del.sync([absolutePath], {cwd: t.context.tmp});

exists(t, ['2.tmp', '3.tmp', '4.tmp', '.dot.tmp']);
notExists(t, ['1.tmp']);
});

test('cannot delete actual working directory without force: true - async', async t => {
process.chdir(t.context.tmp);

await t.throwsAsync(() => del([t.context.tmp]), {
instanceOf: Error,
message: 'Cannot delete the current working directory. Can be overridden with the `force` option.'
});

exists(t, ['', '1.tmp', '2.tmp', '3.tmp', '4.tmp', '.dot.tmp']);
process.chdir(processCwd);
});

test('cannot delete actual working directory without force: true - sync', t => {
process.chdir(t.context.tmp);

t.throws(() => del.sync([t.context.tmp]), {
instanceOf: Error,
message: 'Cannot delete the current working directory. Can be overridden with the `force` option.'
});

exists(t, ['', '1.tmp', '2.tmp', '3.tmp', '4.tmp', '.dot.tmp']);
process.chdir(processCwd);
});

test('cannot delete actual working directory with cwd option without force: true - async', async t => {
process.chdir(t.context.tmp);

await t.throwsAsync(() => del([t.context.tmp], {cwd: __dirname}), {
instanceOf: Error,
message: 'Cannot delete the current working directory. Can be overridden with the `force` option.'
});

exists(t, ['', '1.tmp', '2.tmp', '3.tmp', '4.tmp', '.dot.tmp']);
process.chdir(processCwd);
});

test('cannot delete actual working directory with cwd option without force: true - sync', t => {
process.chdir(t.context.tmp);

t.throws(() => del.sync([t.context.tmp], {cwd: __dirname}), {
instanceOf: Error,
message: 'Cannot delete the current working directory. Can be overridden with the `force` option.'
});

exists(t, ['', '1.tmp', '2.tmp', '3.tmp', '4.tmp', '.dot.tmp']);
process.chdir(processCwd);
});

test('cannot delete files outside cwd without force: true - async', async t => {
const absolutePath = path.resolve(t.context.tmp, '1.tmp');

await t.throwsAsync(() => del([absolutePath]), {
instanceOf: Error,
message: 'Cannot delete files/directories outside the current working directory. Can be overridden with the `force` option.'
});

exists(t, ['1.tmp', '2.tmp', '3.tmp', '4.tmp', '.dot.tmp']);
});

test('cannot delete files outside cwd without force: true - sync', t => {
const absolutePath = path.resolve(t.context.tmp, '1.tmp');

t.throws(() => del.sync([absolutePath]), {
instanceOf: Error,
message: 'Cannot delete files/directories outside the current working directory. Can be overridden with the `force` option.'
});

exists(t, ['', '1.tmp', '2.tmp', '3.tmp', '4.tmp', '.dot.tmp']);
});

test('cannot delete files inside process.cwd when outside cwd without force: true - async', async t => {
process.chdir(t.context.tmp);
const removeFile = path.resolve(t.context.tmp, '2.tmp');
const cwd = path.resolve(t.context.tmp, '1.tmp');

await t.throwsAsync(() => del([removeFile], {cwd}), {
instanceOf: Error,
message: 'Cannot delete files/directories outside the current working directory. Can be overridden with the `force` option.'
});

exists(t, ['1.tmp', '2.tmp', '3.tmp', '4.tmp', '.dot.tmp']);
process.chdir(processCwd);
});

test('cannot delete files inside process.cwd when outside cwd without force: true - sync', t => {
process.chdir(t.context.tmp);
const removeFile = path.resolve(t.context.tmp, '2.tmp');
const cwd = path.resolve(t.context.tmp, '1.tmp');

t.throws(() => del.sync([removeFile], {cwd}), {
instanceOf: Error,
message: 'Cannot delete files/directories outside the current working directory. Can be overridden with the `force` option.'
});

exists(t, ['1.tmp', '2.tmp', '3.tmp', '4.tmp', '.dot.tmp']);
process.chdir(processCwd);
});