From ea0c05f97640539f106da2e88d73ff3cc3616697 Mon Sep 17 00:00:00 2001 From: clydin <19598772+clydin@users.noreply.github.com> Date: Fri, 26 Jan 2018 17:12:50 -0500 Subject: [PATCH] fix(preProcessPatterns): support glob context paths with special characters (#208) --- src/preProcessPattern.js | 7 +- src/utils/escape.js | 24 ++++ .../[special?directory]/(special-*file).txt | 1 + .../[special?directory]/directoryfile.txt | 1 + .../[special?directory]/nested/nestedfile.txt | 0 tests/index.js | 113 +++++++++++++++++- 6 files changed, 142 insertions(+), 4 deletions(-) create mode 100644 src/utils/escape.js create mode 100644 tests/helpers/[special?directory]/(special-*file).txt create mode 100644 tests/helpers/[special?directory]/directoryfile.txt create mode 100644 tests/helpers/[special?directory]/nested/nestedfile.txt diff --git a/src/preProcessPattern.js b/src/preProcessPattern.js index 6c81df35..4ae5def0 100644 --- a/src/preProcessPattern.js +++ b/src/preProcessPattern.js @@ -2,6 +2,7 @@ import fs from 'fs'; import pify from 'pify'; import path from 'path'; import isGlob from 'is-glob'; +import escape from './utils/escape'; import isObject from './utils/isObject'; // https://www.debuggex.com/r/VH2yS2mvJOitiyr3 @@ -46,7 +47,7 @@ export default function preProcessPattern(globalRef, pattern) { delete fromArgs.glob; pattern.fromArgs = fromArgs; - pattern.absoluteFrom = path.resolve(pattern.context, pattern.from.glob); + pattern.absoluteFrom = escape(pattern.context, pattern.from.glob); return Promise.resolve(pattern); } @@ -63,6 +64,7 @@ export default function preProcessPattern(globalRef, pattern) { // If from doesn't appear to be a glob, then log a warning if (isGlob(pattern.from) || pattern.from.indexOf('*') !== -1) { pattern.fromType = 'glob'; + pattern.absoluteFrom = escape(pattern.context, pattern.from); } else { const msg = `unable to locate '${pattern.from}' at '${pattern.absoluteFrom}'`; warning(msg); @@ -79,13 +81,14 @@ export default function preProcessPattern(globalRef, pattern) { pattern.fromType = 'dir'; pattern.context = pattern.absoluteFrom; contextDependencies.push(pattern.absoluteFrom); - pattern.absoluteFrom = path.join(pattern.absoluteFrom, '**/*'); + pattern.absoluteFrom = escape(pattern.absoluteFrom, '**/*'); pattern.fromArgs = { dot: true }; } else if(stat.isFile()) { pattern.fromType = 'file'; pattern.context = path.dirname(pattern.absoluteFrom); + pattern.absoluteFrom = escape(pattern.absoluteFrom); pattern.fromArgs = { dot: true }; diff --git a/src/utils/escape.js b/src/utils/escape.js new file mode 100644 index 00000000..447f376b --- /dev/null +++ b/src/utils/escape.js @@ -0,0 +1,24 @@ +import path from 'path'; + +export default function escape(context, from) { + if (from && path.isAbsolute(from)) { + return from; + } else { + // Ensure context is escaped before globbing + // Handles special characters in paths + const absoluteContext = path.resolve(context) + .replace(/\\/, '/') + .replace(/[\*|\?|\!|\(|\)|\[|\]|\{|\}]/g, (substring) => `\\${substring}`); + + if (!from) { + return absoluteContext; + } + + // Cannot use path.join/resolve as it "fixes" the path separators + if (absoluteContext.endsWith('/')) { + return `${absoluteContext}${from}`; + } else { + return `${absoluteContext}/${from}`; + } + } +} \ No newline at end of file diff --git a/tests/helpers/[special?directory]/(special-*file).txt b/tests/helpers/[special?directory]/(special-*file).txt new file mode 100644 index 00000000..065d0200 --- /dev/null +++ b/tests/helpers/[special?directory]/(special-*file).txt @@ -0,0 +1 @@ +special \ No newline at end of file diff --git a/tests/helpers/[special?directory]/directoryfile.txt b/tests/helpers/[special?directory]/directoryfile.txt new file mode 100644 index 00000000..3e5126c4 --- /dev/null +++ b/tests/helpers/[special?directory]/directoryfile.txt @@ -0,0 +1 @@ +new \ No newline at end of file diff --git a/tests/helpers/[special?directory]/nested/nestedfile.txt b/tests/helpers/[special?directory]/nested/nestedfile.txt new file mode 100644 index 00000000..e69de29b diff --git a/tests/index.js b/tests/index.js index 0a52460d..3965052b 100644 --- a/tests/index.js +++ b/tests/index.js @@ -269,6 +269,9 @@ describe('apply function', () => { 'file.txt.gz', 'directory/directoryfile.txt', 'directory/nested/nestedfile.txt', + '[special?directory]/directoryfile.txt', + '[special?directory]/(special-*file).txt', + '[special?directory]/nested/nestedfile.txt', 'noextension' ], patterns: [{ @@ -287,6 +290,9 @@ describe('apply function', () => { 'nested/file.txt.gz', 'nested/directory/directoryfile.txt', 'nested/directory/nested/nestedfile.txt', + 'nested/[special?directory]/directoryfile.txt', + 'nested/[special?directory]/(special-*file).txt', + 'nested/[special?directory]/nested/nestedfile.txt', 'nested/noextension' ], patterns: [{ @@ -314,6 +320,38 @@ describe('apply function', () => { .catch(done); }); + it('can use a direct glob to move multiple files in a different relative context with special characters', (done) => { + runEmit({ + expectedAssetKeys: [ + 'directoryfile.txt', + '(special-*file).txt', + 'nested/nestedfile.txt' + ], + patterns: [{ + context: '[special?directory]', + from: { glob: '**/*' } + }] + }) + .then(done) + .catch(done); + }); + + it('can use a glob to move multiple files in a different relative context with special characters', (done) => { + runEmit({ + expectedAssetKeys: [ + 'directoryfile.txt', + '(special-*file).txt', + 'nested/nestedfile.txt' + ], + patterns: [{ + context: '[special?directory]', + from: '**/*' + }] + }) + .then(done) + .catch(done); + }); + it('can use a glob to flatten multiple files in a relative context to a non-root directory', (done) => { runEmit({ expectedAssetKeys: [ @@ -365,7 +403,10 @@ describe('apply function', () => { expectedAssetKeys: [ 'file.txt', 'directory/directoryfile.txt', - 'directory/nested/nestedfile.txt' + 'directory/nested/nestedfile.txt', + '[special?directory]/directoryfile.txt', + '[special?directory]/(special-*file).txt', + '[special?directory]/nested/nestedfile.txt' ], patterns: [{ from: path.join(HELPER_DIR, '**/*.txt') @@ -383,6 +424,9 @@ describe('apply function', () => { 'nested/file.txt-5b311c.gz', 'nested/directory/directoryfile-22af64.txt', 'nested/directory/nested/nestedfile-d41d8c.txt', + 'nested/[special?directory]/(special-*file)-0bd650.txt', + 'nested/[special?directory]/directoryfile-22af64.txt', + 'nested/[special?directory]/nested/nestedfile-d41d8c.txt', 'nested/noextension-d41d8c' ], patterns: [{ @@ -586,6 +630,34 @@ describe('apply function', () => { .catch(done); }); + it('can move a file with a context containing special characters', (done) => { + runEmit({ + expectedAssetKeys: [ + 'directoryfile.txt' + ], + patterns: [{ + from: 'directoryfile.txt', + context: '[special?directory]' + }] + }) + .then(done) + .catch(done); + }); + + it('can move a file with special characters with a context containing special characters', (done) => { + runEmit({ + expectedAssetKeys: [ + '(special-*file).txt' + ], + patterns: [{ + from: '(special-*file).txt', + context: '[special?directory]' + }] + }) + .then(done) + .catch(done); + }); + it('can move a file to a new directory with an extension', (done) => { runEmit({ expectedAssetKeys: [ @@ -797,6 +869,9 @@ describe('apply function', () => { 'binextension.bin', 'directory/directoryfile.txt', 'directory/nested/nestedfile.txt', + '[special?directory]/directoryfile.txt', + '[special?directory]/(special-*file).txt', + '[special?directory]/nested/nestedfile.txt', 'noextension' ], patterns: [{ @@ -880,6 +955,37 @@ describe('apply function', () => { .catch(done); }); + it('can move a directory\'s contents to the root directory using from with special characters', (done) => { + runEmit({ + expectedAssetKeys: [ + 'directoryfile.txt', + '(special-*file).txt', + 'nested/nestedfile.txt' + ], + patterns: [{ + from: '[special?directory]' + }] + }) + .then(done) + .catch(done); + }); + + it('can move a directory\'s contents to the root directory using context with special characters', (done) => { + runEmit({ + expectedAssetKeys: [ + 'directoryfile.txt', + '(special-*file).txt', + 'nested/nestedfile.txt' + ], + patterns: [{ + from: '.', + context: '[special?directory]' + }] + }) + .then(done) + .catch(done); + }); + it('warns when directory not found', (done) => { runEmit({ expectedAssetKeys: [], @@ -1202,6 +1308,9 @@ describe('apply function', () => { 'file.txt.gz', 'directory/directoryfile.txt', 'directory/nested/nestedfile.txt', + '[special?directory]/directoryfile.txt', + '[special?directory]/(special-*file).txt', + '[special?directory]/nested/nestedfile.txt', 'noextension' ], options: { @@ -1260,7 +1369,7 @@ describe('apply function', () => { 'noextension' ], options: { - ignore: ['directory/**/*'] + ignore: ['directory/**/*', '\\[special\\?directory\\]/**/*'] }, patterns: [{ from: '.'