Skip to content

Commit 5e657e4

Browse files
committed
Fix negation patterns copying files outside the positive pattern
Fixes #123
1 parent c869169 commit 5e657e4

2 files changed

Lines changed: 67 additions & 4 deletions

File tree

index.js

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -477,6 +477,7 @@ export default function cpy(
477477
*/
478478
const getEntries = async (patterns, ignorePatterns) => {
479479
const entries = [];
480+
const resolvedIgnorePatterns = ignorePatterns.map(pattern => resolveCopyPath(pattern, options.cwd));
480481

481482
for (const pattern of patterns) {
482483
/**
@@ -491,7 +492,9 @@ export default function cpy(
491492
throw new CpyError(`Cannot glob \`${pattern.originalPath}\`: ${error.message}`, {cause: error});
492493
}
493494

494-
if (matches.length === 0 && !isDynamicPattern(pattern.originalPath) && !isDynamicPattern(ignorePatterns)) {
495+
const resolvedPatternPath = resolveCopyPath(pattern.originalPath, options.cwd);
496+
const isIgnoredByStaticPattern = resolvedIgnorePatterns.some(ignorePattern => relativizeWithin(ignorePattern, resolvedPatternPath) !== undefined);
497+
if (matches.length === 0 && !isDynamicPattern(pattern.originalPath) && !isIgnoredByStaticPattern && !isDynamicPattern(ignorePatterns)) {
495498
throw new CpyError(`Cannot copy \`${pattern.originalPath}\`: the file doesn't exist`);
496499
}
497500

@@ -532,15 +535,15 @@ export default function cpy(
532535
let patterns = expandPatternsWithBraceExpansion(rawPatterns)
533536
.map(string => string.replaceAll('\\', '/'));
534537
const sources = patterns.filter(item => !item.startsWith('!'));
535-
const ignore = patterns.filter(item => item.startsWith('!'));
538+
const ignorePatterns = patterns.filter(item => item.startsWith('!')).map(item => item.slice(1));
536539

537540
if (sources.length === 0 || !destination) {
538541
throw new CpyError('`source` and `destination` required');
539542
}
540543

541-
patterns = patterns.map(pattern => new GlobPattern(pattern, destination, {...options, ignore}));
544+
patterns = sources.map(pattern => new GlobPattern(pattern, destination, {...options, ignore: ignorePatterns}));
542545

543-
entries = await getEntries(patterns, ignore);
546+
entries = await getEntries(patterns, ignorePatterns);
544547

545548
const destinationPathByEntry = new Map();
546549
const resolveDestinationPaths = entry => {

test.js

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1715,6 +1715,66 @@ test('negative patterns', async () => {
17151715
assert.ok(!fs.existsSync(path.join(context.tmp, 'out/ignore.js')));
17161716
});
17171717

1718+
test('negative exact pattern does not throw for non-glob source', async () => {
1719+
fs.writeFileSync(path.join(context.tmp, 'foo.js'), 'console.log("foo");');
1720+
await assert.doesNotReject(cpy(['foo.js', '!foo.js'], path.join(context.tmp, 'out'), {cwd: context.tmp}));
1721+
assert.ok(!fs.existsSync(path.join(context.tmp, 'out/foo.js')));
1722+
});
1723+
1724+
test('negative exact pattern does not throw for equivalent non-glob source path variants', async () => {
1725+
fs.writeFileSync(path.join(context.tmp, 'foo.js'), 'console.log("foo");');
1726+
await assert.doesNotReject(cpy(['./foo.js', '!foo.js'], path.join(context.tmp, 'out'), {cwd: context.tmp}));
1727+
assert.ok(!fs.existsSync(path.join(context.tmp, 'out/foo.js')));
1728+
});
1729+
1730+
test('negative exact pattern does not throw for relative source and absolute negation', async () => {
1731+
const sourcePath = path.join(context.tmp, 'foo.js');
1732+
fs.writeFileSync(sourcePath, 'console.log("foo");');
1733+
await assert.doesNotReject(cpy(['foo.js', `!${sourcePath}`], path.join(context.tmp, 'out'), {cwd: context.tmp}));
1734+
assert.ok(!fs.existsSync(path.join(context.tmp, 'out/foo.js')));
1735+
});
1736+
1737+
test('negative exact pattern does not hide missing non-glob source for other files', async () => {
1738+
fs.writeFileSync(path.join(context.tmp, 'bar.js'), 'console.log("bar");');
1739+
await assert.rejects(cpy(['foo.js', '!bar.js'], path.join(context.tmp, 'out'), {cwd: context.tmp}), CpyError);
1740+
});
1741+
1742+
test('negative parent pattern does not throw for non-glob source', async () => {
1743+
fs.mkdirSync(path.join(context.tmp, 'source'));
1744+
fs.writeFileSync(path.join(context.tmp, 'source/file.js'), 'console.log("file");');
1745+
await assert.doesNotReject(cpy(['source/file.js', '!source'], path.join(context.tmp, 'out'), {cwd: context.tmp}));
1746+
assert.ok(!fs.existsSync(path.join(context.tmp, 'out/file.js')));
1747+
});
1748+
1749+
test('negative parent pattern does not throw for non-glob directory source', async () => {
1750+
fs.mkdirSync(path.join(context.tmp, 'source/nested'), {recursive: true});
1751+
fs.writeFileSync(path.join(context.tmp, 'source/nested/file.js'), 'console.log("file");');
1752+
await assert.doesNotReject(cpy(['source', '!source'], path.join(context.tmp, 'out'), {cwd: context.tmp}));
1753+
assert.ok(!fs.existsSync(path.join(context.tmp, 'out/source/nested/file.js')));
1754+
});
1755+
1756+
test('negative patterns should not copy files outside the positive pattern', async () => {
1757+
fs.mkdirSync(path.join(context.tmp, 'source'));
1758+
fs.mkdirSync(path.join(context.tmp, 'other'));
1759+
fs.mkdirSync(path.join(context.tmp, 'out'));
1760+
fs.writeFileSync(path.join(context.tmp, 'source/keep.js'), 'keep');
1761+
fs.writeFileSync(path.join(context.tmp, 'source/ignore.slim.js'), 'ignore');
1762+
fs.writeFileSync(path.join(context.tmp, 'other/should-not-be-copied.js'), 'no');
1763+
fs.writeFileSync(path.join(context.tmp, 'root-file.js'), 'no');
1764+
1765+
await cpy(['source/**', '!source/**/*.slim.*'], path.join(context.tmp, 'out'), {
1766+
cwd: context.tmp,
1767+
});
1768+
1769+
assert.strictEqual(
1770+
read(context.tmp, 'source/keep.js'),
1771+
read(context.tmp, 'out/keep.js'),
1772+
);
1773+
assert.ok(!fs.existsSync(path.join(context.tmp, 'out/ignore.slim.js')));
1774+
assert.ok(!fs.existsSync(path.join(context.tmp, 'out/should-not-be-copied.js')));
1775+
assert.ok(!fs.existsSync(path.join(context.tmp, 'out/root-file.js')));
1776+
});
1777+
17181778
test('recursive directory copying', async () => {
17191779
fs.mkdirSync(path.join(context.tmp, 'source'));
17201780
fs.mkdirSync(path.join(context.tmp, 'source/nested'));

0 commit comments

Comments
 (0)