Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: webpack-contrib/copy-webpack-plugin
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 650d44da2c68035850cd52dc67dcb409e66baaf7
Choose a base ref
...
head repository: webpack-contrib/copy-webpack-plugin
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 64e60c2a5146cc7e51fbbd824503bbde2b512c1a
Choose a head ref

Commits on Jan 13, 2020

  1. docs: fix typo (#428)

    jcfranco authored and evilebottnawi committed Jan 13, 2020
    Copy the full SHA
    8f8a2fe View commit details

Commits on Jan 20, 2020

  1. Copy the full SHA
    034fd3f View commit details

Commits on Mar 10, 2020

  1. Copy the full SHA
    cc55042 View commit details

Commits on Apr 27, 2020

  1. chore: update (#445)

    BREAKING CHANGE: minimum supported Node.js version is `10.13`, use `md4` by default for hashes
    evilebottnawi authored Apr 27, 2020
    Copy the full SHA
    44104c7 View commit details
  2. feat: migrate on webpack built-in logger (#446)

    BREAKING CHANGE: the `logLever` was removed in favor the `infrastructureLogging.level` option, please read the [documentation](https://webpack.js.org/configuration/other-options/#infrastructurelogginglevel)
    evilebottnawi authored Apr 27, 2020
    Copy the full SHA
    5af02bc View commit details
  3. refactor: remove legacy hacks (#447)

    BREAKING CHANGE: the `2` version of `webpack-dev-server` is not supported anymore
    evilebottnawi authored Apr 27, 2020
    Copy the full SHA
    5a0daeb View commit details
  4. fix: persist assets between rebuilds

    BREAKING CHANGE: the `copyUnmodified` was removed without replacements
    evilebottnawi authored Apr 27, 2020
    Copy the full SHA
    57f3e61 View commit details

Commits on Apr 28, 2020

  1. refactor: 'from' option (#450)

    BREAKING CHANGE: the 'from' option now can only be a string, if you use `{ from: { glob: 'directory/**', dot: false } }` changed it to `{ from: 'directory/**', globOptions: { dot: false } }`
    evilebottnawi authored Apr 28, 2020
    Copy the full SHA
    383ff9d View commit details
  2. refactor: plugin options (#451)

    BREAKING CHANGE: the plugin now accepts an object as options, you should change `new CopyPlugin(patterns, options)` to `new CopyPlugin( { patterns, options })`
    evilebottnawi authored Apr 28, 2020
    Copy the full SHA
    aedd2bd View commit details
  3. refactor: rename the cache option to cacheTransform (#452)

    BREAKING CHANGE: the `cache` option was renamed to `cacheTransform`
    evilebottnawi authored Apr 28, 2020
    Copy the full SHA
    f6da280 View commit details
  4. refactor: remove the global context option (#453)

    BREAKING CHANGE: the global `context` option was removed in favor the `patten.context` option
    evilebottnawi authored Apr 28, 2020
    Copy the full SHA
    64b2e1a View commit details

Commits on Apr 30, 2020

  1. chore: update globby

    BRAKING CHANGE: globby was update to latest version, some pattern can't work on windows, please read documentation
    cap-Bernardito authored Apr 30, 2020
    Copy the full SHA
    a728675 View commit details

Commits on May 7, 2020

  1. Copy the full SHA
    1e2f3a1 View commit details

Commits on May 8, 2020

  1. refactor: code (#459)

    BREAKING CHANGE: migrate on `compilation.additionalAssets` hook
    cap-Bernardito authored May 8, 2020
    Copy the full SHA
    42194f3 View commit details
  2. refactor: code

    cap-Bernardito authored May 8, 2020
    Copy the full SHA
    6b07a63 View commit details

Commits on May 12, 2020

  1. refactor: remove the ignore option in favor globOptions.ignore (#463)

    BREAKING CHANGE: the `ignore` option was removed in favor `globOptions.ignore`
    cap-Bernardito authored May 12, 2020
    Copy the full SHA
    0be6470 View commit details
  2. refactor: code (#465)

    cap-Bernardito authored May 12, 2020
    Copy the full SHA
    e5e410a View commit details
  3. Copy the full SHA
    c176d7d View commit details

Commits on May 14, 2020

  1. fix: asset size

    cap-Bernardito authored May 14, 2020
    Copy the full SHA
    197b0d8 View commit details
  2. Copy the full SHA
    c0c9fa7 View commit details
  3. Copy the full SHA
    bfb4f0e View commit details
  4. Copy the full SHA
    20c9ae5 View commit details
  5. refactor: code (#471)

    cap-Bernardito authored May 14, 2020
    Copy the full SHA
    045f629 View commit details

Commits on May 15, 2020

  1. refactor: drop test option in favor transformPath (#472)

    BREAKING CHANGE: the `test` option was removed in favor the `transformPath` option
    cap-Bernardito authored May 15, 2020
    Copy the full SHA
    3ef3f25 View commit details
  2. docs: improve (#473)

    evilebottnawi authored May 15, 2020
    Copy the full SHA
    6b85c86 View commit details
  3. Copy the full SHA
    8e5bc1b View commit details
  4. Copy the full SHA
    e3803ce View commit details
  5. refactor: code

    BREAKING CHANGE: the missing file error is now an error
    evilebottnawi authored May 15, 2020
    Copy the full SHA
    bebafcf View commit details
  6. feat: implement the directory option for the cacheTransform option

    BREAKING CHANGE: the `cacheTransform` option should have only `directory` and `keys` properties when it is an object
    evilebottnawi authored May 15, 2020
    Copy the full SHA
    29254e3 View commit details
  7. test: coverage

    evilebottnawi authored May 15, 2020
    Copy the full SHA
    dd9ce50 View commit details
  8. chore(release): 6.0.0

    alexander-akait committed May 15, 2020
    Copy the full SHA
    64e60c2 View commit details
Showing with 10,339 additions and 8,045 deletions.
  1. +102 −0 .github/workflows/nodejs.yml
  2. +1 −5 .prettierrc.js
  3. +32 −0 CHANGELOG.md
  4. +549 −219 README.md
  5. +0 −218 azure-pipelines.yml
  6. +1 −1 babel.config.js
  7. +1 −0 jest.config.js
  8. +2 −2 lint-staged.config.js
  9. +7,319 −5,656 package-lock.json
  10. +34 −38 package.json
  11. +67 −108 src/index.js
  12. +62 −47 src/options.json
  13. +155 −175 src/postProcessPattern.js
  14. +29 −133 src/preProcessPattern.js
  15. +42 −96 src/processPattern.js
  16. +87 −0 src/utils/createPatternGlob.js
  17. +0 −2 src/utils/isObject.js
  18. +0 −32 src/utils/normalize.js
  19. +149 −239 test/CopyPlugin.test.js
  20. +94 −0 test/__snapshots__/CopyPlugin.test.js.snap
  21. +127 −130 test/__snapshots__/validate-options.test.js.snap
  22. +0 −250 test/cache-option.test.js
  23. +486 −0 test/cacheTransform-option.test.js
  24. +44 −99 test/context-option.test.js
  25. 0 test/fixtures/watch/_t1/.gitkeep
  26. 0 test/fixtures/watch/_t1/directory/.gitkeep
  27. 0 test/fixtures/watch/_t2/.gitkeep
  28. 0 test/fixtures/watch/_t2/directory/.gitkeep
  29. 0 test/fixtures/watch/_t3/.gitkeep
  30. 0 test/fixtures/watch/_t3/directory/.gitkeep
  31. 0 test/fixtures/watch/_t4/.gitkeep
  32. 0 test/fixtures/watch/_t4/directory/.gitkeep
  33. 0 test/fixtures/watch/_t5/.gitkeep
  34. 0 test/fixtures/watch/_t5/directory/.gitkeep
  35. +61 −94 test/from-option.test.js
  36. +380 −2 test/globOptions-option.test.js
  37. +29 −0 test/helpers/PreCopyPlugin.js
  38. +11 −0 test/helpers/compile.js
  39. +1 −0 test/helpers/enter.js
  40. +32 −0 test/helpers/getCompiler.js
  41. +6 −0 test/helpers/index.js
  42. +0 −59 test/helpers/mocks.js
  43. +28 −0 test/helpers/readAsset.js
  44. +13 −0 test/helpers/readAssets.js
  45. +109 −110 test/helpers/run.js
  46. +0 −186 test/ignore-option.test.js
  47. +51 −0 test/noErrorOnMissing.test.js
  48. +0 −73 test/test-option.test.js
  49. +23 −9 test/to-option.test.js
  50. +2 −4 test/toType-option.test.js
  51. +51 −1 test/transform-option.test.js
  52. +81 −3 test/transformPath-option.test.js
  53. +78 −54 test/validate-options.test.js
102 changes: 102 additions & 0 deletions .github/workflows/nodejs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
name: copy-webpack-plugin

on:
push:
branches:
- master
- next
pull_request:
branches:
- master
- next

jobs:
lint:
name: Lint - ${{ matrix.os }} - Node v${{ matrix.node-version }}

env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

strategy:
matrix:
os: [ubuntu-latest]
node-version: [12.x]

runs-on: ${{ matrix.os }}

steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0

- name: Use Node.js ${{ env.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ env.node-version }}

- name: Use latest NPM
run: sudo npm i -g npm

- name: Install dependencies
run: npm ci

- name: Lint
run: npm run lint

# - name: Security audit
# run: npm run security

- name: Check commit message
uses: wagoid/commitlint-github-action@v1

test:
name: Test - ${{ matrix.os }} - Node v${{ matrix.node-version }}, Webpack ${{ matrix.webpack-version }}

strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
node-version: [10.x, 12.x, 14.x]
webpack-version: [latest, next]
exclude:
- os: macos-latest
node-version: 12.x
webpack-version: latest
- os: macos-latest
node-version: 14.x
webpack-version: latest

runs-on: ${{ matrix.os }}

steps:
- name: Setup Git
if: matrix.os == 'windows-latest'
run: git config --global core.autocrlf input

- uses: actions/checkout@v2

- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}

- name: Use latest NPM on ubuntu/macos
if: matrix.os == 'ubuntu-latest' || matrix.os == 'macos-latest'
run: sudo npm i -g npm

- name: Use latest NPM on windows
if: matrix.os == 'windows-latest'
run: npm i -g npm

- name: Install dependencies
run: npm ci

- name: Install webpack ${{ matrix.webpack-version }}
run: npm i webpack@${{ matrix.webpack-version }}

- name: Run tests for webpack version ${{ matrix.webpack-version }}
run: npm run test:coverage -- --ci

- name: Submit coverage data to codecov
uses: codecov/codecov-action@v1
with:
token: ${{ secrets.CODECOV_TOKEN }}
6 changes: 1 addition & 5 deletions .prettierrc.js
Original file line number Diff line number Diff line change
@@ -1,5 +1 @@
module.exports = {
singleQuote: true,
trailingComma: 'es5',
arrowParens: 'always',
};
module.exports = { singleQuote: true };
32 changes: 32 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -2,6 +2,38 @@

All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.

## [6.0.0](https://github.com/webpack-contrib/copy-webpack-plugin/compare/v5.1.1...v6.0.0) (2020-05-15)


### ⚠ BREAKING CHANGES

* minimum supported Node.js version is `10.13`,
* the plugin now accepts an object, you should change `new CopyPlugin(patterns, options)` to `new CopyPlugin({ patterns, options })`
* migrate on `compilation.additionalAssets` hook
* the `ignore` option was removed in favor `globOptions.ignore`
* the `test` option was removed in favor the `transformPath` option
* the `cache` option was renamed to the `cacheTransform` option, `cacheTransform` option should have only `directory` and `keys` properties when it is an object
* global `context` and `ignore` options were removed in favor `patten.context` and `pattern.globOptions.ignore` options
* the missing file error is now an error, before it was a warning
* the `from` option now can only be a string, if you use `{ from: { glob: 'directory/**', dot: false } }` changed it to `{ from: 'directory/**', globOptions: { dot: false } }`
* the `copyUnmodified` was removed without replacements
* the `2` version of `webpack-dev-server` is not supported anymore
* the `logLever` was removed in favor the `infrastructureLogging.level` option, please read the [documentation](https://webpack.js.org/configuration/other-options/#infrastructurelogginglevel)


### Features

* implement the `concurrency` option ([#466](https://github.com/webpack-contrib/copy-webpack-plugin/issues/466)) ([c176d7d](https://github.com/webpack-contrib/copy-webpack-plugin/commit/c176d7d124cf3c5ad372576d4b0f7fbf5e1d0afc))
* implement the `directory` option for the `cacheTransform` option ([29254e3](https://github.com/webpack-contrib/copy-webpack-plugin/commit/29254e394cb695d89b477f44f3a3bf8c99c74ca7))
* implement the `noErrorOnMissing` option ([#475](https://github.com/webpack-contrib/copy-webpack-plugin/issues/475)) ([e3803ce](https://github.com/webpack-contrib/copy-webpack-plugin/commit/e3803ceffe93361184efc9b799be4c9dfb4eb467))
* migrate on webpack built-in logger ([#446](https://github.com/webpack-contrib/copy-webpack-plugin/issues/446)) ([5af02bc](https://github.com/webpack-contrib/copy-webpack-plugin/commit/5af02bcfc716b6bca96b569740a45221a974ae61))


### Bug Fixes

* asset size ([197b0d8](https://github.com/webpack-contrib/copy-webpack-plugin/commit/197b0d8d08e6ce976f645dade7641cfdcfc0781d))
* persist assets between rebuilds ([57f3e61](https://github.com/webpack-contrib/copy-webpack-plugin/commit/57f3e618108c3a6c1f61f21186d69433ee51a561))

### [5.1.1](https://github.com/webpack-contrib/copy-webpack-plugin/compare/v5.1.0...v5.1.1) (2019-12-12)


768 changes: 549 additions & 219 deletions README.md

Large diffs are not rendered by default.

218 changes: 0 additions & 218 deletions azure-pipelines.yml

This file was deleted.

2 changes: 1 addition & 1 deletion babel.config.js
Original file line number Diff line number Diff line change
@@ -10,7 +10,7 @@ module.exports = (api) => {
'@babel/preset-env',
{
targets: {
node: '6.9.0',
node: '10.13.0',
},
},
],
1 change: 1 addition & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
module.exports = {
testEnvironment: 'node',
globalSetup: '<rootDir>/globalSetup.js',
};
4 changes: 2 additions & 2 deletions lint-staged.config.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
module.exports = {
'*.js': ['prettier --write', 'eslint --fix', 'git add'],
'*.{json,md,yml,css,ts}': ['prettier --write', 'git add'],
'*.js': ['prettier --write', 'eslint --fix'],
'*.{json,md,yml,css,ts}': ['prettier --write'],
};
12,975 changes: 7,319 additions & 5,656 deletions package-lock.json

Large diffs are not rendered by default.

72 changes: 34 additions & 38 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "copy-webpack-plugin",
"version": "5.1.1",
"version": "6.0.0",
"description": "Copy files && directories with webpack",
"license": "MIT",
"repository": "webpack-contrib/copy-webpack-plugin",
@@ -13,7 +13,7 @@
},
"main": "dist/cjs.js",
"engines": {
"node": ">= 6.9.0"
"node": ">= 10.13.0"
},
"scripts": {
"start": "npm run build -- -w",
@@ -22,7 +22,7 @@
"build": "cross-env NODE_ENV=production babel src -d dist --copy-files",
"commitlint": "commitlint --from=master",
"security": "npm audit",
"lint:prettier": "prettier \"{**/*,*}.{js,json,md,yml,css,ts}\" --list-different",
"lint:prettier": "prettier --list-different .",
"lint:js": "eslint --cache .",
"lint": "npm-run-all -l -p \"lint:**\"",
"test:only": "cross-env NODE_ENV=test jest",
@@ -38,50 +38,46 @@
"dist"
],
"peerDependencies": {
"webpack": "^4.0.0 || ^5.0.0"
"webpack": "^4.37.0 || ^5.0.0"
},
"dependencies": {
"cacache": "^12.0.3",
"find-cache-dir": "^2.1.0",
"glob-parent": "^3.1.0",
"globby": "^7.1.1",
"is-glob": "^4.0.1",
"loader-utils": "^1.2.3",
"minimatch": "^3.0.4",
"cacache": "^15.0.3",
"find-cache-dir": "^3.3.1",
"glob-parent": "^5.1.1",
"globby": "^11.0.0",
"loader-utils": "^2.0.0",
"normalize-path": "^3.0.0",
"p-limit": "^2.2.1",
"schema-utils": "^1.0.0",
"serialize-javascript": "^2.1.2",
"webpack-log": "^2.0.0"
"p-limit": "^2.3.0",
"schema-utils": "^2.6.6",
"serialize-javascript": "^3.0.0",
"webpack-sources": "^1.4.3"
},
"devDependencies": {
"@babel/cli": "^7.7.5",
"@babel/core": "^7.7.5",
"@babel/preset-env": "^7.7.6",
"@commitlint/cli": "^8.2.0",
"@commitlint/config-conventional": "^8.2.0",
"@webpack-contrib/defaults": "^6.2.0",
"@babel/cli": "^7.8.4",
"@babel/core": "^7.9.6",
"@babel/preset-env": "^7.9.6",
"@commitlint/cli": "^8.3.5",
"@commitlint/config-conventional": "^8.3.4",
"@webpack-contrib/defaults": "^6.3.0",
"@webpack-contrib/eslint-config-webpack": "^3.0.0",
"babel-jest": "^24.9.0",
"commitlint-azure-pipelines-cli": "^1.0.2",
"cross-env": "^5.2.1",
"del": "^4.1.1",
"del-cli": "^1.1.0",
"babel-jest": "^26.0.1",
"cross-env": "^7.0.2",
"del": "^5.1.0",
"del-cli": "^3.0.0",
"enhanced-resolve": "^4.1.1",
"eslint": "^6.7.2",
"eslint-config-prettier": "^6.7.0",
"eslint-plugin-import": "^2.19.1",
"husky": "^3.1.0",
"eslint": "^7.0.0",
"eslint-config-prettier": "^6.11.0",
"eslint-plugin-import": "^2.20.2",
"husky": "^4.2.5",
"is-gzip": "^2.0.0",
"jest": "^24.9.0",
"jest-junit": "^10.0.0",
"lint-staged": "^9.5.0",
"memfs": "^3.0.1",
"mkdirp": "^0.5.1",
"jest": "^26.0.1",
"lint-staged": "^10.2.2",
"memfs": "^3.1.3",
"mkdirp": "^1.0.4",
"npm-run-all": "^4.1.5",
"prettier": "^1.19.1",
"standard-version": "^7.0.1",
"webpack": "^4.41.2"
"prettier": "^2.0.5",
"standard-version": "^8.0.0",
"webpack": "^4.43.0"
},
"keywords": [
"webpack",
175 changes: 67 additions & 108 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,125 +1,84 @@
import path from 'path';

import validateOptions from 'schema-utils';
import log from 'webpack-log';
import pLimit from 'p-limit';

import schema from './options.json';
import preProcessPattern from './preProcessPattern';
import processPattern from './processPattern';
import postProcessPattern from './postProcessPattern';

class CopyPlugin {
constructor(patterns = [], options = {}) {
validateOptions(schema, patterns, this.constructor.name);
constructor(options = {}) {
validateOptions(schema, options, {
name: 'Copy Plugin',
baseDataPath: 'options',
});

this.patterns = patterns;
this.options = options;
this.patterns = options.patterns;
this.options = options.options || {};
}

apply(compiler) {
const fileDependencies = new Set();
const contextDependencies = new Set();
const written = {};

let context;

if (!this.options.context) {
({ context } = compiler.options);
} else if (!path.isAbsolute(this.options.context)) {
context = path.join(compiler.options.context, this.options.context);
} else {
({ context } = this.options);
}

const logger = log({
name: 'copy-webpack-plugin',
level: this.options.logLevel || 'warn',
});

const plugin = { name: 'CopyPlugin' };

compiler.hooks.emit.tapAsync(plugin, (compilation, callback) => {
logger.debug('starting emit');

const globalRef = {
logger,
compilation,
written,
fileDependencies,
contextDependencies,
context,
inputFileSystem: compiler.inputFileSystem,
output: compiler.options.output.path,
ignore: this.options.ignore || [],
copyUnmodified: this.options.copyUnmodified,
concurrency: this.options.concurrency,
};

if (
globalRef.output === '/' &&
compiler.options.devServer &&
compiler.options.devServer.outputPath
) {
globalRef.output = compiler.options.devServer.outputPath;
}

const { patterns } = this;

Promise.all(
patterns.map((pattern) =>
Promise.resolve()
.then(() => preProcessPattern(globalRef, pattern))
// Every source (from) is assumed to exist here
// eslint-disable-next-line no-shadow
.then((pattern) =>
processPattern(globalRef, pattern).then((files) => {
if (!files) {
return Promise.resolve();
}

return Promise.all(
files
.filter(Boolean)
.map((file) => postProcessPattern(globalRef, pattern, file))
);
})
)
)
)
.catch((error) => {
compilation.errors.push(error);
})
.then(() => {
logger.debug('finishing emit');

callback();
});
});

compiler.hooks.afterEmit.tapAsync(plugin, (compilation, callback) => {
logger.debug('starting after-emit');

// Add file dependencies
if ('addAll' in compilation.fileDependencies) {
compilation.fileDependencies.addAll(fileDependencies);
} else {
for (const fileDependency of fileDependencies) {
compilation.fileDependencies.add(fileDependency);
}
}

// Add context dependencies
if ('addAll' in compilation.contextDependencies) {
compilation.contextDependencies.addAll(contextDependencies);
} else {
for (const contextDependency of contextDependencies) {
compilation.contextDependencies.add(contextDependency);
const limit = pLimit(this.options.concurrency || 100);

compiler.hooks.compilation.tap(plugin, (compilation) => {
const logger = compilation.getLogger('copy-webpack-plugin');

compilation.hooks.additionalAssets.tapAsync(
'copy-webpack-plugin',
async (callback) => {
logger.debug('start to adding additional assets');

const globalRef = {
context: compiler.options.context,
logger,
compilation,
inputFileSystem: compiler.inputFileSystem,
output: compiler.options.output.path,
};

try {
await Promise.all(
this.patterns.map((pattern) =>
limit(async () => {
const patternAfterPreProcess = await preProcessPattern(
globalRef,
pattern
);

const files = await processPattern(
globalRef,
patternAfterPreProcess
);

if (!files) {
return Promise.resolve();
}

return Promise.all(
files.filter(Boolean).map((file) =>
limit(() => {
return postProcessPattern(
globalRef,
patternAfterPreProcess,
file
);
})
)
);
})
)
);

logger.debug('end to adding additional assets');

callback();
} catch (error) {
compilation.errors.push(error);
callback();
}
}
}

logger.debug('finishing after-emit');

callback();
);
});
}
}
109 changes: 62 additions & 47 deletions src/options.json
Original file line number Diff line number Diff line change
@@ -2,71 +2,69 @@
"definitions": {
"ObjectPattern": {
"type": "object",
"additionalProperties": false,
"properties": {
"from": {
"anyOf": [
{
"type": "string",
"minLength": 1
},
{
"type": "object"
}
]
"type": "string",
"minLength": 1
},
"to": {
"type": "string"
},
"context": {
"type": "string"
},
"globOptions": {
"type": "object"
},
"toType": {
"enum": ["dir", "file", "template"]
},
"test": {
"anyOf": [
{
"type": "string"
},
{
"instanceof": "RegExp"
}
]
},
"force": {
"type": "boolean"
},
"ignore": {
"type": "array",
"items": {
"anyOf": [
{
"type": "string"
},
{
"type": "object"
}
]
}
},
"flatten": {
"type": "boolean"
},
"cache": {
"transform": {
"instanceof": "Function"
},
"cacheTransform": {
"anyOf": [
{
"type": "boolean"
},
{
"type": "object"
"type": "string"
},
{
"type": "object",
"additionalProperties": false,
"properties": {
"directory": {
"type": "string",
"absolutePath": true
},
"keys": {
"anyOf": [
{
"type": "object",
"additionalProperties": true
},
{
"instanceof": "Function"
}
]
}
}
}
]
},
"transform": {
"instanceof": "Function"
},
"transformPath": {
"instanceof": "Function"
},
"noErrorOnMissing": {
"type": "boolean"
}
},
"required": ["from"]
@@ -76,15 +74,32 @@
"minLength": 1
}
},
"type": "array",
"items": {
"anyOf": [
{
"$ref": "#/definitions/StringPattern"
},
{
"$ref": "#/definitions/ObjectPattern"
"type": "object",
"additionalProperties": false,
"properties": {
"patterns": {
"type": "array",
"minItems": 1,
"items": {
"anyOf": [
{
"$ref": "#/definitions/StringPattern"
},
{
"$ref": "#/definitions/ObjectPattern"
}
]
}
]
}
},
"options": {
"type": "object",
"additionalProperties": false,
"properties": {
"concurrency": {
"type": "number"
}
}
}
},
"required": ["patterns"]
}
330 changes: 155 additions & 175 deletions src/postProcessPattern.js
Original file line number Diff line number Diff line change
@@ -8,196 +8,176 @@ import serialize from 'serialize-javascript';
import findCacheDir from 'find-cache-dir';
import normalizePath from 'normalize-path';

import { name, version } from '../package.json';
import { RawSource } from 'webpack-sources';

import { version } from '../package.json';

import { stat, readFile } from './utils/promisify';

/* eslint-disable no-param-reassign */

export default function postProcessPattern(globalRef, pattern, file) {
const {
logger,
compilation,
fileDependencies,
written,
inputFileSystem,
copyUnmodified,
} = globalRef;
export default async function postProcessPattern(globalRef, pattern, file) {
const { logger, compilation, inputFileSystem } = globalRef;

logger.debug(`getting stats for '${file.absoluteFrom}' to write to assets`);

const getStats = pattern.stats
? Promise.resolve().then(() => pattern.stats)
? pattern.stats
: stat(inputFileSystem, file.absoluteFrom);

return getStats.then((stats) => {
// We don't write empty directories
if (stats.isDirectory()) {
logger.debug(
`skipping '${file.absoluteFrom}' because it is empty directory`
);
let stats;

try {
stats = await getStats;
} catch (error) {
compilation.errors.push(error);

return;
}

if (stats.isDirectory()) {
logger.debug(
`skipping '${file.absoluteFrom}' because it is empty directory`
);

return;
}

// If this came from a glob, add it to the file watchlist
if (pattern.fromType === 'glob') {
logger.debug(`add ${file.absoluteFrom} as fileDependencies`);

compilation.fileDependencies.add(file.absoluteFrom);
}

logger.debug(`reading '${file.absoluteFrom}' to write to assets`);

let content;

try {
content = await readFile(inputFileSystem, file.absoluteFrom);
} catch (error) {
compilation.errors.push(error);

return;
}

if (pattern.transform) {
logger.log(`transforming content for '${file.absoluteFrom}'`);

if (pattern.cacheTransform) {
const cacheDirectory = pattern.cacheTransform.directory
? pattern.cacheTransform.directory
: typeof pattern.cacheTransform === 'string'
? pattern.cacheTransform
: findCacheDir({ name: 'copy-webpack-plugin' }) || os.tmpdir();
let defaultCacheKeys = {
version,
transform: pattern.transform,
contentHash: crypto.createHash('md4').update(content).digest('hex'),
};

if (typeof pattern.cacheTransform.keys === 'function') {
defaultCacheKeys = await pattern.cacheTransform.keys(
defaultCacheKeys,
file.absoluteFrom
);
} else {
defaultCacheKeys = {
...defaultCacheKeys,
...pattern.cacheTransform.keys,
};
}

return Promise.resolve();
const cacheKeys = serialize(defaultCacheKeys);

try {
const result = await cacache.get(cacheDirectory, cacheKeys);

logger.debug(
`getting cached transformation for '${file.absoluteFrom}'`
);

content = result.data;
} catch (_ignoreError) {
content = await pattern.transform(content, file.absoluteFrom);

logger.debug(`caching transformation for '${file.absoluteFrom}'`);

content = await cacache
.put(cacheDirectory, cacheKeys, content)
.then(() => content);
}
} else {
content = await pattern.transform(content, file.absoluteFrom);
}
}

// If this came from a glob, add it to the file watchlist
if (pattern.fromType === 'glob') {
fileDependencies.add(file.absoluteFrom);
if (pattern.toType === 'template') {
logger.log(
`interpolating template '${file.webpackTo}' for '${file.relativeFrom}'`
);

// If it doesn't have an extension, remove it from the pattern
// ie. [name].[ext] or [name][ext] both become [name]
if (!path.extname(file.relativeFrom)) {
file.webpackTo = file.webpackTo.replace(/\.?\[ext]/g, '');
}

logger.debug(`reading '${file.absoluteFrom}' to write to assets`);

return readFile(inputFileSystem, file.absoluteFrom)
.then((content) => {
if (pattern.transform) {
logger.info(`transforming content for '${file.absoluteFrom}'`);

// eslint-disable-next-line no-shadow
const transform = (content, absoluteFrom) =>
pattern.transform(content, absoluteFrom);

if (pattern.cache) {
if (!globalRef.cacheDir) {
globalRef.cacheDir =
findCacheDir({
name: 'copy-webpack-plugin',
}) || os.tmpdir();
}

const cacheKey = pattern.cache.key
? pattern.cache.key
: serialize({
name,
version,
pattern,
hash: crypto
.createHash('md4')
.update(content)
.digest('hex'),
});

return cacache.get(globalRef.cacheDir, cacheKey).then(
(result) => {
logger.debug(
`getting cached transformation for '${file.absoluteFrom}'`
);

return result.data;
},
() =>
Promise.resolve()
.then(() => transform(content, file.absoluteFrom))
// eslint-disable-next-line no-shadow
.then((content) => {
logger.debug(
`caching transformation for '${file.absoluteFrom}'`
);

return cacache
.put(globalRef.cacheDir, cacheKey, content)
.then(() => content);
})
);
}

content = transform(content, file.absoluteFrom);
}

return content;
})
.then((content) => {
if (pattern.toType === 'template') {
logger.info(
`interpolating template '${file.webpackTo}' for '${file.relativeFrom}'`
);

// If it doesn't have an extension, remove it from the pattern
// ie. [name].[ext] or [name][ext] both become [name]
if (!path.extname(file.relativeFrom)) {
file.webpackTo = file.webpackTo.replace(/\.?\[ext\]/g, '');
}

file.webpackTo = loaderUtils.interpolateName(
{ resourcePath: file.absoluteFrom },
file.webpackTo,
{
content,
regExp: file.webpackToRegExp,
context: pattern.context,
}
);

// Bug in `loader-utils`, package convert `\\` to `/`, need fix in loader-utils
file.webpackTo = path.normalize(file.webpackTo);
}

return content;
})
.then((content) => {
if (pattern.transformPath) {
logger.info(
`transforming path '${file.webpackTo}' for '${file.absoluteFrom}'`
);

return Promise.resolve()
.then(() =>
pattern.transformPath(file.webpackTo, file.absoluteFrom)
)
.then((newPath) => {
file.webpackTo = newPath;

return content;
});
}

return content;
})
.then((content) => {
const hash = loaderUtils.getHashDigest(content);
const targetPath = normalizePath(file.webpackTo);
const targetAbsolutePath = normalizePath(file.absoluteFrom);

if (
!copyUnmodified &&
written[targetPath] &&
written[targetPath][targetAbsolutePath] &&
written[targetPath][targetAbsolutePath] === hash
) {
logger.info(
`skipping '${file.webpackTo}', because content hasn't changed`
);

return;
}

logger.debug(`adding '${file.webpackTo}' for tracking content changes`);

if (!written[targetPath]) {
written[targetPath] = {};
}

written[targetPath][targetAbsolutePath] = hash;

if (compilation.assets[targetPath] && !file.force) {
logger.info(
`skipping '${file.webpackTo}', because it already exists`
);

return;
}

logger.info(
`writing '${file.webpackTo}' to compilation assets from '${file.absoluteFrom}'`
);
file.webpackTo = loaderUtils.interpolateName(
{ resourcePath: file.absoluteFrom },
file.webpackTo,
{
content,
context: pattern.context,
}
);

// Bug in `loader-utils`, package convert `\\` to `/`, need fix in loader-utils
file.webpackTo = path.normalize(file.webpackTo);
}

if (pattern.transformPath) {
logger.log(
`transforming path '${file.webpackTo}' for '${file.absoluteFrom}'`
);

file.webpackTo = await pattern.transformPath(
file.webpackTo,
file.absoluteFrom
);
}

const targetPath = normalizePath(file.webpackTo);
const source = new RawSource(content);

// For old version webpack 4
/* istanbul ignore if */
if (typeof compilation.emitAsset !== 'function') {
compilation.assets[targetPath] = source;

return;
}

if (compilation.getAsset(targetPath)) {
if (pattern.force) {
logger.log(
`force updating '${file.webpackTo}' to compilation assets from '${file.absoluteFrom}'`
);

compilation.assets[targetPath] = {
size() {
return stats.size;
},
source() {
return content;
},
};
});
});
compilation.updateAsset(targetPath, source);

return;
}

logger.log(`skipping '${file.webpackTo}', because it already exists`);

return;
}

logger.log(
`writing '${file.webpackTo}' to compilation assets from '${file.absoluteFrom}'`
);

compilation.emitAsset(targetPath, source);
}
162 changes: 29 additions & 133 deletions src/preProcessPattern.js
Original file line number Diff line number Diff line change
@@ -1,52 +1,32 @@
import path from 'path';

import isGlob from 'is-glob';
import globParent from 'glob-parent';

import normalize from './utils/normalize';
import isTemplateLike from './utils/isTemplateLike';
import isObject from './utils/isObject';
import { stat } from './utils/promisify';

/* eslint-disable no-param-reassign */

export default function preProcessPattern(globalRef, pattern) {
const {
logger,
context,
inputFileSystem,
fileDependencies,
contextDependencies,
compilation,
} = globalRef;

pattern =
typeof pattern === 'string'
? { from: pattern }
: Object.assign({}, pattern);
export default async function preProcessPattern(globalRef, pattern) {
const { context, logger, inputFileSystem } = globalRef;

pattern.to = pattern.to || '';
pattern.context = pattern.context || context;
pattern = typeof pattern === 'string' ? { from: pattern } : { ...pattern };
pattern.fromOrigin = pattern.from;
pattern.from = path.normalize(pattern.from);
pattern.to = path.normalize(
typeof pattern.to !== 'undefined' ? pattern.to : ''
);
pattern.context = path.normalize(
typeof pattern.context !== 'undefined'
? !path.isAbsolute(pattern.context)
? path.join(context, pattern.context)
: pattern.context
: context
);

if (!path.isAbsolute(pattern.context)) {
pattern.context = path.join(context, pattern.context);
}
logger.debug(`processing from: '${pattern.from}' to: '${pattern.to}'`);

const isFromGlobPatten =
(isObject(pattern.from) && pattern.from.glob) || pattern.globOptions;
// Todo remove this in next major
const isToDirectory =
path.extname(pattern.to) === '' || pattern.to.slice(-1) === path.sep;

// Normalize paths
pattern.from = isFromGlobPatten ? pattern.from : path.normalize(pattern.from);
pattern.context = path.normalize(pattern.context);
pattern.to = path.normalize(pattern.to);

pattern.ignore = globalRef.ignore.concat(pattern.ignore || []);

logger.debug(`processing from: '${pattern.from}' to: '${pattern.to}'`);

switch (true) {
// if toType already exists
case !!pattern.toType:
@@ -61,114 +41,30 @@ export default function preProcessPattern(globalRef, pattern) {
pattern.toType = 'file';
}

// If we know it's a glob, then bail early
if (isFromGlobPatten) {
logger.debug(`determined '${pattern.absoluteFrom}' is a glob`);

pattern.fromType = 'glob';

const globOptions = Object.assign(
{},
pattern.globOptions ? pattern.globOptions : pattern.from
);
delete globOptions.glob;

pattern.absoluteFrom = path.resolve(
pattern.context,
pattern.globOptions ? pattern.from : pattern.from.glob
);
pattern.glob = normalize(
pattern.context,
pattern.globOptions ? pattern.from : pattern.from.glob
);
pattern.globOptions = globOptions;

return Promise.resolve(pattern);
}

if (path.isAbsolute(pattern.from)) {
pattern.absoluteFrom = pattern.from;
} else {
pattern.absoluteFrom = path.resolve(pattern.context, pattern.from);
}

logger.debug(
`determined '${pattern.from}' to be read from '${pattern.absoluteFrom}'`
);

const noStatsHandler = () => {
// If from doesn't appear to be a glob, then log a warning
if (isGlob(pattern.from) || pattern.from.includes('*')) {
logger.debug(`determined '${pattern.absoluteFrom}' is a glob`);

pattern.fromType = 'glob';
pattern.glob = normalize(pattern.context, pattern.from);

// We need to add context directory as dependencies to avoid problems when new files added in directories
// when we already in watch mode and this directories are not in context dependencies
// `glob-parent` always return `/` we need normalize path
contextDependencies.add(path.normalize(globParent(pattern.absoluteFrom)));
} else {
const newWarning = new Error(
`unable to locate '${pattern.from}' at '${pattern.absoluteFrom}'`
);
const hasWarning = compilation.warnings.some(
// eslint-disable-next-line no-shadow
(warning) => warning.message === newWarning.message
);

// Only display the same message once
if (!hasWarning) {
logger.warn(newWarning.message);

compilation.warnings.push(newWarning);
}

pattern.fromType = 'nonexistent';
}
};

logger.debug(
`getting stats for '${pattern.absoluteFrom}' to determinate 'fromType'`
);

return stat(inputFileSystem, pattern.absoluteFrom)
.catch(() => noStatsHandler())
.then((stats) => {
if (!stats) {
noStatsHandler();

return pattern;
}

if (stats.isDirectory()) {
logger.debug(`determined '${pattern.absoluteFrom}' is a directory`);

contextDependencies.add(pattern.absoluteFrom);
let stats;

pattern.fromType = 'dir';
pattern.context = pattern.absoluteFrom;
pattern.glob = normalize(pattern.absoluteFrom, '**/*');
pattern.absoluteFrom = path.join(pattern.absoluteFrom, '**/*');
pattern.globOptions = {
dot: true,
};
} else if (stats.isFile()) {
logger.debug(`determined '${pattern.absoluteFrom}' is a file`);

fileDependencies.add(pattern.absoluteFrom);
try {
stats = await stat(inputFileSystem, pattern.absoluteFrom);
} catch (error) {
return pattern;
}

pattern.stats = stats;
pattern.fromType = 'file';
pattern.context = path.dirname(pattern.absoluteFrom);
pattern.glob = normalize(pattern.absoluteFrom);
pattern.globOptions = {
dot: true,
};
} else if (!pattern.fromType) {
logger.warn(`unrecognized file type for ${pattern.from}`);
}
if (stats.isDirectory()) {
pattern.fromType = 'dir';
} else if (stats.isFile()) {
pattern.fromType = 'file';
pattern.stats = stats;
}

return pattern;
});
return pattern;
}
138 changes: 42 additions & 96 deletions src/processPattern.js
Original file line number Diff line number Diff line change
@@ -1,121 +1,67 @@
import path from 'path';

import globby from 'globby';
import pLimit from 'p-limit';
import minimatch from 'minimatch';

import isObject from './utils/isObject';

export default function processPattern(globalRef, pattern) {
const { logger, output, concurrency, compilation } = globalRef;
const globOptions = Object.assign(
{
cwd: pattern.context,
follow: true,
// Todo in next major release
// dot: false
},
pattern.globOptions || {}
);

if (pattern.fromType === 'nonexistent') {
return Promise.resolve();
}
import createPatternGlob from './utils/createPatternGlob';

export default async function processPattern(globalRef, pattern) {
const { logger, output, compilation } = globalRef;

const limit = pLimit(concurrency || 100);
createPatternGlob(pattern, globalRef);

logger.info(
logger.log(
`begin globbing '${pattern.glob}' with a context of '${pattern.context}'`
);

return globby(pattern.glob, globOptions).then((paths) =>
Promise.all(
paths.map((from) =>
limit(() => {
const file = {
force: pattern.force,
absoluteFrom: path.resolve(pattern.context, from),
};

file.relativeFrom = path.relative(pattern.context, file.absoluteFrom);

if (pattern.flatten) {
file.relativeFrom = path.basename(file.relativeFrom);
}

logger.debug(`found ${from}`);
const paths = await globby(pattern.glob, pattern.globOptions);

// Check the ignore list
let il = pattern.ignore.length;
if (paths.length === 0) {
if (pattern.noErrorOnMissing) {
return Promise.resolve();
}

// eslint-disable-next-line no-plusplus
while (il--) {
const ignoreGlob = pattern.ignore[il];
const missingError = new Error(
`unable to locate '${pattern.from}' at '${pattern.absoluteFrom}'`
);

let globParams = {
dot: true,
matchBase: true,
};
logger.error(missingError.message);

let glob;
compilation.errors.push(missingError);

if (typeof ignoreGlob === 'string') {
glob = ignoreGlob;
} else if (isObject(ignoreGlob)) {
glob = ignoreGlob.glob || '';

const ignoreGlobParams = Object.assign({}, ignoreGlob);
delete ignoreGlobParams.glob;

// Overwrite minimatch defaults
globParams = Object.assign(globParams, ignoreGlobParams);
} else {
glob = '';
}

logger.debug(`testing ${glob} against ${file.relativeFrom}`);
return Promise.resolve();
}

if (minimatch(file.relativeFrom, glob, globParams)) {
logger.info(
`ignoring '${file.relativeFrom}', because it matches the ignore glob '${glob}'`
);
return paths.map((filepath) => {
let from;

return Promise.resolve();
}
if (typeof filepath === 'string') {
from = filepath;
} else {
from = filepath.path;
}

logger.debug(`${glob} doesn't match ${file.relativeFrom}`);
}
const file = { absoluteFrom: path.resolve(pattern.context, from) };

// Change the to path to be relative for webpack
if (pattern.toType === 'dir') {
file.webpackTo = path.join(pattern.to, file.relativeFrom);
} else if (pattern.toType === 'file') {
file.webpackTo = pattern.to || file.relativeFrom;
} else if (pattern.toType === 'template') {
file.webpackTo = pattern.to;
file.webpackToRegExp = pattern.test;
}
file.relativeFrom = path.relative(pattern.context, file.absoluteFrom);

if (path.isAbsolute(file.webpackTo)) {
if (output === '/') {
const message =
'using older versions of webpack-dev-server, devServer.outputPath must be defined to write to absolute paths';
if (pattern.flatten) {
file.relativeFrom = path.basename(file.relativeFrom);
}

logger.error(message);
logger.debug(`found ${from}`);

compilation.errors.push(new Error(message));
}
// Change the to path to be relative for webpack
file.webpackTo =
pattern.toType === 'dir'
? path.join(pattern.to, file.relativeFrom)
: pattern.to;

file.webpackTo = path.relative(output, file.webpackTo);
}
if (path.isAbsolute(file.webpackTo)) {
file.webpackTo = path.relative(output, file.webpackTo);
}

logger.info(
`determined that '${from}' should write to '${file.webpackTo}'`
);
logger.log(`determined that '${from}' should write to '${file.webpackTo}'`);

return file;
})
)
)
);
return file;
});
}
87 changes: 87 additions & 0 deletions src/utils/createPatternGlob.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import path from 'path';

import normalizePath from 'normalize-path';
import globParent from 'glob-parent';

function getAbsoluteContext(context) {
const result = normalizePath(path.resolve(context));

return result.replace(
// eslint-disable-next-line no-useless-escape
/[\*|\?|\!|\||\@|\+|\(|\)|\[|\]|\{|\}]/g,
(substring) => `\\${substring}`
);
}

function createPatternGlob(pattern, globalRef) {
const { logger, compilation } = globalRef;

// eslint-disable-next-line no-param-reassign
pattern.globOptions = {
...{ followSymbolicLinks: true },
...(pattern.globOptions || {}),
...{ cwd: pattern.context },
};

switch (pattern.fromType) {
case 'dir':
logger.debug(`determined '${pattern.absoluteFrom}' is a directory`);
logger.debug(`add ${pattern.absoluteFrom} as contextDependencies`);

compilation.contextDependencies.add(pattern.absoluteFrom);

/* eslint-disable no-param-reassign */
pattern.context = pattern.absoluteFrom;
pattern.glob = path.posix.join(
getAbsoluteContext(pattern.absoluteFrom),
'**/*'
);
pattern.absoluteFrom = path.join(pattern.absoluteFrom, '**/*');

if (typeof pattern.globOptions.dot === 'undefined') {
pattern.globOptions.dot = true;
}
/* eslint-enable no-param-reassign */
break;
case 'file':
logger.debug(`determined '${pattern.absoluteFrom}' is a file`);
logger.debug(`add ${pattern.absoluteFrom} as fileDependencies`);

compilation.fileDependencies.add(pattern.absoluteFrom);

/* eslint-disable no-param-reassign */
pattern.context = path.dirname(pattern.absoluteFrom);
pattern.glob = getAbsoluteContext(pattern.absoluteFrom);

if (typeof pattern.globOptions.dot === 'undefined') {
pattern.globOptions.dot = true;
}
/* eslint-enable no-param-reassign */
break;
default: {
logger.debug(`determined '${pattern.absoluteFrom}' is a glob`);

const contextDependencies = path.normalize(
globParent(pattern.absoluteFrom)
);

logger.debug(`add ${contextDependencies} as contextDependencies`);

compilation.contextDependencies.add(contextDependencies);

/* eslint-disable no-param-reassign */
pattern.fromType = 'glob';
pattern.glob = path.isAbsolute(pattern.fromOrigin)
? pattern.fromOrigin
: path.posix.join(
getAbsoluteContext(pattern.context),
pattern.fromOrigin
);
/* eslint-enable no-param-reassign */
}
}

return pattern;
}

export default createPatternGlob;
2 changes: 0 additions & 2 deletions src/utils/isObject.js

This file was deleted.

32 changes: 0 additions & 32 deletions src/utils/normalize.js

This file was deleted.

388 changes: 149 additions & 239 deletions test/CopyPlugin.test.js

Large diffs are not rendered by default.

94 changes: 94 additions & 0 deletions test/__snapshots__/CopyPlugin.test.js.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`CopyPlugin logging should logging when "from" is a directory: logs 1`] = `
Object {
"logs": Array [
"add ./fixtures/directory as contextDependencies",
"begin globbing './fixtures/directory/**/*' with a context of './fixtures/directory'",
"determined './fixtures/directory' is a directory",
"determined that './fixtures/directory/.dottedfile' should write to '.dottedfile'",
"determined that './fixtures/directory/directoryfile.txt' should write to 'directoryfile.txt'",
"determined that './fixtures/directory/nested/deep-nested/deepnested.txt' should write to 'nested/deep-nested/deepnested.txt'",
"determined that './fixtures/directory/nested/nestedfile.txt' should write to 'nested/nestedfile.txt'",
"end to adding additional assets",
"found ./fixtures/directory/.dottedfile",
"found ./fixtures/directory/directoryfile.txt",
"found ./fixtures/directory/nested/deep-nested/deepnested.txt",
"found ./fixtures/directory/nested/nestedfile.txt",
"getting stats for './fixtures/directory' to determinate 'fromType'",
"getting stats for './fixtures/directory/.dottedfile' to write to assets",
"getting stats for './fixtures/directory/directoryfile.txt' to write to assets",
"getting stats for './fixtures/directory/nested/deep-nested/deepnested.txt' to write to assets",
"getting stats for './fixtures/directory/nested/nestedfile.txt' to write to assets",
"processing from: 'directory' to: '.'",
"reading './fixtures/directory/.dottedfile' to write to assets",
"reading './fixtures/directory/directoryfile.txt' to write to assets",
"reading './fixtures/directory/nested/deep-nested/deepnested.txt' to write to assets",
"reading './fixtures/directory/nested/nestedfile.txt' to write to assets",
"start to adding additional assets",
"writing '.dottedfile' to compilation assets from './fixtures/directory/.dottedfile'",
"writing 'directoryfile.txt' to compilation assets from './fixtures/directory/directoryfile.txt'",
"writing 'nested/deep-nested/deepnested.txt' to compilation assets from './fixtures/directory/nested/deep-nested/deepnested.txt'",
"writing 'nested/nestedfile.txt' to compilation assets from './fixtures/directory/nested/nestedfile.txt'",
],
}
`;

exports[`CopyPlugin logging should logging when "from" is a file: logs 1`] = `
Object {
"logs": Array [
"add ./fixtures/file.txt as fileDependencies",
"begin globbing './fixtures/file.txt' with a context of './fixtures'",
"determined './fixtures/file.txt' is a file",
"determined that './fixtures/file.txt' should write to 'file.txt'",
"end to adding additional assets",
"found ./fixtures/file.txt",
"getting stats for './fixtures/file.txt' to determinate 'fromType'",
"getting stats for './fixtures/file.txt' to write to assets",
"processing from: 'file.txt' to: '.'",
"reading './fixtures/file.txt' to write to assets",
"start to adding additional assets",
"writing 'file.txt' to compilation assets from './fixtures/file.txt'",
],
}
`;

exports[`CopyPlugin logging should logging when "from" is a glob: logs 1`] = `
Object {
"logs": Array [
"add ./fixtures/directory as contextDependencies",
"add ./fixtures/directory/directoryfile.txt as fileDependencies",
"add ./fixtures/directory/nested/deep-nested/deepnested.txt as fileDependencies",
"add ./fixtures/directory/nested/nestedfile.txt as fileDependencies",
"begin globbing './fixtures/directory/**' with a context of './fixtures'",
"determined './fixtures/directory/**' is a glob",
"determined that './fixtures/directory/directoryfile.txt' should write to 'directory/directoryfile.txt'",
"determined that './fixtures/directory/nested' should write to 'directory/nested'",
"determined that './fixtures/directory/nested/deep-nested' should write to 'directory/nested/deep-nested'",
"determined that './fixtures/directory/nested/deep-nested/deepnested.txt' should write to 'directory/nested/deep-nested/deepnested.txt'",
"determined that './fixtures/directory/nested/nestedfile.txt' should write to 'directory/nested/nestedfile.txt'",
"end to adding additional assets",
"found ./fixtures/directory/directoryfile.txt",
"found ./fixtures/directory/nested",
"found ./fixtures/directory/nested/deep-nested",
"found ./fixtures/directory/nested/deep-nested/deepnested.txt",
"found ./fixtures/directory/nested/nestedfile.txt",
"getting stats for './fixtures/directory/**' to determinate 'fromType'",
"getting stats for './fixtures/directory/directoryfile.txt' to write to assets",
"getting stats for './fixtures/directory/nested' to write to assets",
"getting stats for './fixtures/directory/nested/deep-nested' to write to assets",
"getting stats for './fixtures/directory/nested/deep-nested/deepnested.txt' to write to assets",
"getting stats for './fixtures/directory/nested/nestedfile.txt' to write to assets",
"processing from: 'directory/**' to: '.'",
"reading './fixtures/directory/directoryfile.txt' to write to assets",
"reading './fixtures/directory/nested/deep-nested/deepnested.txt' to write to assets",
"reading './fixtures/directory/nested/nestedfile.txt' to write to assets",
"skipping './fixtures/directory/nested' because it is empty directory",
"skipping './fixtures/directory/nested/deep-nested' because it is empty directory",
"start to adding additional assets",
"writing 'directory/directoryfile.txt' to compilation assets from './fixtures/directory/directoryfile.txt'",
"writing 'directory/nested/deep-nested/deepnested.txt' to compilation assets from './fixtures/directory/nested/deep-nested/deepnested.txt'",
"writing 'directory/nested/nestedfile.txt' to compilation assets from './fixtures/directory/nested/nestedfile.txt'",
],
}
`;
257 changes: 127 additions & 130 deletions test/__snapshots__/validate-options.test.js.snap

Large diffs are not rendered by default.

250 changes: 0 additions & 250 deletions test/cache-option.test.js

This file was deleted.

486 changes: 486 additions & 0 deletions test/cacheTransform-option.test.js

Large diffs are not rendered by default.

143 changes: 44 additions & 99 deletions test/context-option.test.js
Original file line number Diff line number Diff line change
@@ -5,7 +5,7 @@ import { runEmit } from './helpers/run';
const FIXTURES_DIR = path.join(__dirname, 'fixtures');

describe('context option', () => {
it('should work when "from" is a file', (done) => {
it('should work when "from" is a file and "context" is a relative path', (done) => {
runEmit({
expectedAssetKeys: ['directoryfile.txt'],
patterns: [
@@ -19,26 +19,29 @@ describe('context option', () => {
.catch(done);
});

it('should work when "from" is a file and "context" with special characters', (done) => {
it('should work when "from" is a directory and "context" is a relative path', (done) => {
runEmit({
expectedAssetKeys: ['directoryfile.txt'],
expectedAssetKeys: ['deep-nested/deepnested.txt', 'nestedfile.txt'],
patterns: [
{
from: 'directoryfile.txt',
context: '[special?directory]',
from: 'nested',
context: 'directory',
},
],
})
.then(done)
.catch(done);
});

it('should work when "from" is a directory', (done) => {
it('should work when "from" is a glob and "context" is a relative path', (done) => {
runEmit({
expectedAssetKeys: ['deep-nested/deepnested.txt', 'nestedfile.txt'],
expectedAssetKeys: [
'nested/deep-nested/deepnested.txt',
'nested/nestedfile.txt',
],
patterns: [
{
from: 'nested',
from: 'nested/**/*',
context: 'directory',
},
],
@@ -47,44 +50,35 @@ describe('context option', () => {
.catch(done);
});

it('should work when "from" is a directory and "to" is a new directory', (done) => {
it('should work when "from" is a file and "context" is an absolute path', (done) => {
runEmit({
expectedAssetKeys: [
'newdirectory/deep-nested/deepnested.txt',
'newdirectory/nestedfile.txt',
],
expectedAssetKeys: ['directoryfile.txt'],
patterns: [
{
context: 'directory',
from: 'nested',
to: 'newdirectory',
from: 'directoryfile.txt',
context: path.join(FIXTURES_DIR, 'directory'),
},
],
})
.then(done)
.catch(done);
});

it('should work when "from" is a directory and "context" with special characters', (done) => {
it('should work when "from" is a directory and "context" is an absolute path', (done) => {
runEmit({
expectedAssetKeys: [
'directoryfile.txt',
'(special-*file).txt',
'nested/nestedfile.txt',
],
expectedAssetKeys: ['deep-nested/deepnested.txt', 'nestedfile.txt'],
patterns: [
{
// Todo strange behavour when you use `FIXTURES_DIR`, need investigate for next major release
from: '.',
context: '[special?directory]',
from: 'nested',
context: path.join(FIXTURES_DIR, 'directory'),
},
],
})
.then(done)
.catch(done);
});

it('should work when "from" is a glob', (done) => {
it('should work when "from" is a glob and "context" is an absolute path', (done) => {
runEmit({
expectedAssetKeys: [
'nested/deep-nested/deepnested.txt',
@@ -93,45 +87,40 @@ describe('context option', () => {
patterns: [
{
from: 'nested/**/*',
context: 'directory',
context: path.join(FIXTURES_DIR, 'directory'),
},
],
})
.then(done)
.catch(done);
});

it('should work when "from" is a glob and "to" is a directory', (done) => {
it('should work when "from" is a file and "context" with special characters', (done) => {
runEmit({
expectedAssetKeys: [
'nested/directoryfile.txt',
'nested/nested/deep-nested/deepnested.txt',
'nested/nested/nestedfile.txt',
],
expectedAssetKeys: ['directoryfile.txt'],
patterns: [
{
context: 'directory',
from: '**/*',
to: 'nested',
from: 'directoryfile.txt',
context: '[special?directory]',
},
],
})
.then(done)
.catch(done);
});

it('should work when "from" is a glob and "to" is a directory and "content" is an absolute path', (done) => {
it('should work when "from" is a directory and "context" with special characters', (done) => {
runEmit({
expectedAssetKeys: [
'nested/directoryfile.txt',
'nested/nested/deep-nested/deepnested.txt',
'nested/nested/nestedfile.txt',
'directoryfile.txt',
'(special-*file).txt',
'nested/nestedfile.txt',
],
patterns: [
{
context: path.join(FIXTURES_DIR, 'directory'),
from: '**/*',
to: 'nested',
// Todo strange behavour when you use `FIXTURES_DIR`, need investigate for next major release
from: '.',
context: '[special?directory]',
},
],
})
@@ -162,7 +151,7 @@ describe('context option', () => {
expectedAssetKeys: ['(special-*file).txt'],
patterns: [
{
from: '(special-*file).txt',
from: '\\(special-*file\\).txt',
context: '[special?directory]',
},
],
@@ -171,32 +160,13 @@ describe('context option', () => {
.catch(done);
});

it('should work when "from" is a file and "context" is an absolute path', (done) => {
it('should work when "from" is a file and "to" is a directory', (done) => {
runEmit({
expectedAssetKeys: ['directoryfile.txt'],
expectedAssetKeys: ['newdirectory/directoryfile.txt'],
patterns: [
{
context: 'directory',
from: 'directoryfile.txt',
context: path.join(FIXTURES_DIR, 'directory'),
},
],
})
.then(done)
.catch(done);
});

it('should override webpack config context with an absolute path', (done) => {
runEmit({
expectedAssetKeys: [
'newdirectory/deep-nested/deepnested.txt',
'newdirectory/nestedfile.txt',
],
options: {
context: path.join(FIXTURES_DIR, 'directory'),
},
patterns: [
{
from: 'nested',
to: 'newdirectory',
},
],
@@ -205,17 +175,15 @@ describe('context option', () => {
.catch(done);
});

it('should override webpack config context with a relative path', (done) => {
it('should work when "from" is a directory and "to" is a directory', (done) => {
runEmit({
expectedAssetKeys: [
'newdirectory/deep-nested/deepnested.txt',
'newdirectory/nestedfile.txt',
],
options: {
context: 'directory',
},
patterns: [
{
context: 'directory',
from: 'nested',
to: 'newdirectory',
},
@@ -225,41 +193,18 @@ describe('context option', () => {
.catch(done);
});

it('should override global context on pattern context with a relative path', (done) => {
runEmit({
expectedAssetKeys: [
'newdirectory/deep-nested/deepnested.txt',
'newdirectory/nestedfile.txt',
],
options: {
context: 'directory',
},
patterns: [
{
context: 'nested',
from: '.',
to: 'newdirectory',
},
],
})
.then(done)
.catch(done);
});

it('overrides webpack config context with an absolute path', (done) => {
it('should work when "from" is a glob and "to" is a directory', (done) => {
runEmit({
expectedAssetKeys: [
'newdirectory/file.txt',
'newdirectory/nesteddir/deepnesteddir/deepnesteddir.txt',
'newdirectory/nesteddir/nestedfile.txt',
'nested/directoryfile.txt',
'nested/nested/deep-nested/deepnested.txt',
'nested/nested/nestedfile.txt',
],
options: {
context: path.join(FIXTURES_DIR, 'dir (86)'),
},
patterns: [
{
context: 'directory',
from: '**/*',
to: 'newdirectory',
to: 'nested',
},
],
})
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
155 changes: 61 additions & 94 deletions test/from-option.test.js
Original file line number Diff line number Diff line change
@@ -3,6 +3,9 @@ import path from 'path';
import { runEmit } from './helpers/run';

const FIXTURES_DIR = path.join(__dirname, 'fixtures');
const FIXTURES_DIR_NORMALIZED = path
.join(__dirname, 'fixtures')
.replace(/\\/g, '/');

describe('from option', () => {
describe('is a file', () => {
@@ -24,7 +27,7 @@ describe('from option', () => {
expectedAssetKeys: ['file.txt'],
patterns: [
{
from: path.join(FIXTURES_DIR, 'file.txt'),
from: path.posix.join(FIXTURES_DIR_NORMALIZED, 'file.txt'),
},
],
})
@@ -50,7 +53,10 @@ describe('from option', () => {
expectedAssetKeys: ['directoryfile.txt'],
patterns: [
{
from: path.join(FIXTURES_DIR, 'directory/directoryfile.txt'),
from: path.posix.join(
FIXTURES_DIR_NORMALIZED,
'directory/directoryfile.txt'
),
},
],
})
@@ -61,6 +67,14 @@ describe('from option', () => {
it('should move a file (symbolic link)', (done) => {
runEmit({
symlink: true,
expectedErrors:
process.platform === 'win32'
? [
new Error(
`unable to locate 'symlink${path.sep}file-ln.txt' at '${FIXTURES_DIR}${path.sep}symlink${path.sep}file-ln.txt'`
),
]
: [],
expectedAssetKeys: process.platform === 'win32' ? [] : ['file-ln.txt'],
patterns: [
{
@@ -72,10 +86,10 @@ describe('from option', () => {
.catch(done);
});

it('should warn when file not found', (done) => {
it('should throw an error on the missing file', (done) => {
runEmit({
expectedAssetKeys: [],
expectedWarnings: [
expectedErrors: [
new Error(
`unable to locate 'nonexistent.txt' at '${FIXTURES_DIR}${path.sep}nonexistent.txt'`
),
@@ -192,6 +206,14 @@ describe('from option', () => {
runEmit({
// Windows doesn't support symbolic link
symlink: true,
expectedErrors:
process.platform === 'win32'
? [
new Error(
`unable to locate 'symlink${path.sep}directory-ln' at '${FIXTURES_DIR}${path.sep}symlink${path.sep}directory-ln'`
),
]
: [],
expectedAssetKeys:
process.platform === 'win32'
? []
@@ -216,7 +238,7 @@ describe('from option', () => {
],
patterns: [
{
from: path.join(FIXTURES_DIR, 'directory'),
from: path.posix.join(FIXTURES_DIR_NORMALIZED, 'directory'),
},
],
})
@@ -260,18 +282,18 @@ describe('from option', () => {
expectedAssetKeys: ['deep-nested/deepnested.txt', 'nestedfile.txt'],
patterns: [
{
from: path.join(FIXTURES_DIR, 'directory/nested'),
from: path.posix.join(FIXTURES_DIR_NORMALIZED, 'directory/nested'),
},
],
})
.then(done)
.catch(done);
});

it('should warn when directory not found', (done) => {
it('should throw an error on the missing directory', (done) => {
runEmit({
expectedAssetKeys: [],
expectedWarnings: [
expectedErrors: [
new Error(
`unable to locate 'nonexistent' at '${FIXTURES_DIR}${path.sep}nonexistent'`
),
@@ -306,7 +328,7 @@ describe('from option', () => {
expectedAssetKeys: ['file.txt'],
patterns: [
{
from: path.join(FIXTURES_DIR, '*.txt'),
from: path.posix.join(FIXTURES_DIR_NORMALIZED, '*.txt'),
},
],
})
@@ -359,7 +381,7 @@ describe('from option', () => {
],
patterns: [
{
from: path.join(FIXTURES_DIR, '**/*.txt'),
from: path.posix.join(FIXTURES_DIR_NORMALIZED, '**/*.txt'),
},
],
})
@@ -370,20 +392,20 @@ describe('from option', () => {
it('should move files in nested directory using globstar', (done) => {
runEmit({
expectedAssetKeys: [
'nested/[!]/hello-d41d8c.txt',
'nested/binextension-d41d8c.bin',
'nested/dir (86)/file-d41d8c.txt',
'nested/dir (86)/nesteddir/deepnesteddir/deepnesteddir-d41d8c.txt',
'nested/dir (86)/nesteddir/nestedfile-d41d8c.txt',
'nested/file-22af64.txt',
'nested/file.txt-5b311c.gz',
'nested/directory/directoryfile-22af64.txt',
'nested/directory/nested/deep-nested/deepnested-d41d8c.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',
'nested/[!]/hello-31d6cf.txt',
'nested/binextension-31d6cf.bin',
'nested/dir (86)/file-31d6cf.txt',
'nested/dir (86)/nesteddir/deepnesteddir/deepnesteddir-31d6cf.txt',
'nested/dir (86)/nesteddir/nestedfile-31d6cf.txt',
'nested/file-5d7817.txt',
'nested/file.txt-f18c8d.gz',
'nested/directory/directoryfile-5d7817.txt',
'nested/directory/nested/deep-nested/deepnested-31d6cf.txt',
'nested/directory/nested/nestedfile-31d6cf.txt',
'nested/[special?directory]/(special-*file)-517cf2.txt',
'nested/[special?directory]/directoryfile-5d7817.txt',
'nested/[special?directory]/nested/nestedfile-31d6cf.txt',
'nested/noextension-31d6cf',
],
patterns: [
{
@@ -449,6 +471,14 @@ describe('from option', () => {
runEmit({
// Windows doesn't support symbolic link
symlink: true,
expectedErrors:
process.platform === 'win32'
? [
new Error(
`unable to locate 'symlink\\**\\*.txt' at '${FIXTURES_DIR}${path.sep}symlink\\**\\*.txt'`
),
]
: [],
expectedAssetKeys:
process.platform === 'win32'
? []
@@ -469,81 +499,18 @@ describe('from option', () => {
.then(done)
.catch(done);
});
});

describe('is a object with glob and glob options', () => {
it('should move files', (done) => {
runEmit({
expectedAssetKeys: ['file.txt'],
patterns: [
{
from: {
glob: '*.txt',
},
},
],
})
.then(done)
.catch(done);
});

it('should move files exclude dot files', (done) => {
runEmit({
expectedAssetKeys: ['file.txt'],
patterns: [
{
from: {
glob: '*.txt',
dot: false,
},
},
],
})
.then(done)
.catch(done);
});

it('should move files with dot files', (done) => {
it('should throw an error on the missing glob', (done) => {
runEmit({
expectedAssetKeys: ['file.txt', '.file.txt'],
patterns: [
{
from: {
glob: '*.txt',
dot: true,
},
},
],
})
.then(done)
.catch(done);
});

it('should move files with the "globOptions" option', (done) => {
runEmit({
expectedAssetKeys: ['.file.txt', 'file.txt'],
patterns: [
{
from: '*.txt',
globOptions: {
dot: true,
},
},
expectedAssetKeys: [],
expectedErrors: [
new Error(
`unable to locate 'nonexistent${path.sep}**${path.sep}*' at '${FIXTURES_DIR}${path.sep}nonexistent${path.sep}**${path.sep}*'`
),
],
})
.then(done)
.catch(done);
});

it('should move files with the "globOptions" option #2', (done) => {
runEmit({
expectedAssetKeys: ['file.txt'],
patterns: [
{
from: '*.txt',
globOptions: {
dot: false,
},
from: 'nonexistent/**/*',
},
],
})
382 changes: 380 additions & 2 deletions test/globOptions-option.test.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,48 @@
import path from 'path';

import { runEmit } from './helpers/run';

describe('from option', () => {
it('should move files exclude dot files', (done) => {
const FIXTURES_DIR = path.join(__dirname, 'fixtures');

describe('globOptions option', () => {
// Expected behavior from `globby`/`fast-glob`
it('should move files exclude dot files when "from" is a directory', (done) => {
runEmit({
expectedAssetKeys: ['.file.txt'],
patterns: [
{
from: '.file.txt',
globOptions: {
dot: false,
},
},
],
})
.then(done)
.catch(done);
});

it('should move files exclude dot files when "from" is a directory', (done) => {
runEmit({
expectedAssetKeys: [
'directoryfile.txt',
'nested/deep-nested/deepnested.txt',
'nested/nestedfile.txt',
],
patterns: [
{
from: 'directory',
globOptions: {
dot: false,
},
},
],
})
.then(done)
.catch(done);
});

it('should move files exclude dot files when "from" is a glob', (done) => {
runEmit({
expectedAssetKeys: ['file.txt'],
patterns: [
@@ -32,4 +73,341 @@ describe('from option', () => {
.then(done)
.catch(done);
});

it('should ignore files when "from" is a file', (done) => {
runEmit({
expectedErrors: [
new Error(
`unable to locate 'file.txt' at '${FIXTURES_DIR}${path.sep}file.txt'`
),
],
patterns: [
{
from: 'file.txt',
globOptions: {
ignore: ['**/file.*'],
},
},
],
})
.then(done)
.catch(done);
});

it('should files when "from" is a directory', (done) => {
runEmit({
expectedAssetKeys: [
'.dottedfile',
'directoryfile.txt',
'nested/deep-nested/deepnested.txt',
],
patterns: [
{
from: 'directory',
globOptions: {
ignore: ['**/nestedfile.*'],
},
},
],
})
.then(done)
.catch(done);
});

it('should files in nested directory when "from" is a directory', (done) => {
runEmit({
expectedAssetKeys: ['.dottedfile', 'directoryfile.txt'],
patterns: [
{
from: 'directory',
globOptions: {
ignore: ['**/nested/**'],
},
},
],
})
.then(done)
.catch(done);
});

it('should files when from is a glob', (done) => {
runEmit({
expectedAssetKeys: [
'directory/directoryfile.txt',
'directory/nested/deep-nested/deepnested.txt',
],
patterns: [
{
from: 'directory/**/*',
globOptions: {
ignore: ['**/nestedfile.*'],
},
},
],
})
.then(done)
.catch(done);
});

it('should files in nested directory when from is a glob', (done) => {
runEmit({
expectedAssetKeys: ['directory/directoryfile.txt'],
patterns: [
{
from: 'directory/**/*',
globOptions: {
ignore: ['**/nested/**'],
},
},
],
})
.then(done)
.catch(done);
});

it('should ignore files with a certain extension', (done) => {
runEmit({
expectedAssetKeys: ['.dottedfile'],
patterns: [
{
from: 'directory',
globOptions: {
ignore: ['**/*.txt'],
},
},
],
})
.then(done)
.catch(done);
});

it('should ignore files with multiple ignore patterns', (done) => {
runEmit({
expectedAssetKeys: ['directory/nested/nestedfile.txt'],
patterns: [
{
from: 'directory/**/*',
globOptions: {
ignore: ['**/directoryfile.*', '**/deep-nested/**'],
},
},
],
})
.then(done)
.catch(done);
});

it('should ignore files with flatten true', (done) => {
runEmit({
expectedAssetKeys: ['img/.dottedfile', 'img/nestedfile.txt'],
patterns: [
{
from: 'directory/',
to: 'img/',
flatten: true,
globOptions: {
ignore: ['**/directoryfile.*', '**/deep-nested/**'],
},
},
],
})
.then(done)
.catch(done);
});

it('should ignore files except those with dots', (done) => {
runEmit({
expectedAssetKeys: ['.dottedfile'],
patterns: [
{
from: 'directory',
globOptions: {
ignore: ['!(**/.*)'],
},
},
],
})
.then(done)
.catch(done);
});

it('should ignore files that start with a dot', (done) => {
runEmit({
expectedAssetKeys: [
'directoryfile.txt',
'nested/deep-nested/deepnested.txt',
'nested/nestedfile.txt',
],
patterns: [
{
from: 'directory',
globOptions: {
ignore: ['**/.*'],
},
},
],
})
.then(done)
.catch(done);
});

it('should ignores all files even if they start with a dot', (done) => {
runEmit({
expectedErrors: [
new Error(
`unable to locate 'directory' at '${FIXTURES_DIR}${path.sep}directory${path.sep}**${path.sep}*'`
),
],
patterns: [
{
from: 'directory',
globOptions: {
ignore: ['**/*'],
},
},
],
})
.then(done)
.catch(done);
});

it('should ignore files when "from" is a file (global ignore)', (done) => {
runEmit({
expectedErrors: [
new Error(
`unable to locate 'file.txt' at '${FIXTURES_DIR}${path.sep}file.txt'`
),
],
patterns: [
{
from: 'file.txt',
globOptions: {
ignore: ['**/file.*'],
},
},
],
})
.then(done)
.catch(done);
});

it('should ignore the "cwd" option', (done) => {
runEmit({
expectedAssetKeys: [
'.dottedfile',
'directoryfile.txt',
'nested/deep-nested/deepnested.txt',
'nested/nestedfile.txt',
],
patterns: [
{
from: 'directory',
globOptions: {
cwd: path.resolve(__dirname, 'fixtures/nested'),
},
},
],
})
.then(done)
.catch(done);
});

it('should work with the "deep" option', (done) => {
runEmit({
expectedAssetKeys: [
'.dottedfile',
'directoryfile.txt',
'nested/nestedfile.txt',
],
patterns: [
{
from: 'directory',
globOptions: {
deep: 2,
},
},
],
})
.then(done)
.catch(done);
});

it('should work with the "markDirectories" option', (done) => {
runEmit({
expectedAssetKeys: [
'.dottedfile',
'directoryfile.txt',
'nested/deep-nested/deepnested.txt',
'nested/nestedfile.txt',
],
patterns: [
{
from: 'directory',
globOptions: {
markDirectories: true,
},
},
],
})
.then(done)
.catch(done);
});

it('should work with the "objectMode" option', (done) => {
runEmit({
expectedAssetKeys: [
'.dottedfile',
'directoryfile.txt',
'nested/deep-nested/deepnested.txt',
'nested/nestedfile.txt',
],
patterns: [
{
from: 'directory',
globOptions: {
objectMode: true,
},
},
],
})
.then(done)
.catch(done);
});

it('should work with the "onlyDirectories" option', (done) => {
runEmit({
expectedAssetKeys: [],
patterns: [
{
from: 'directory',
globOptions: {
onlyDirectories: true,
},
},
],
})
.then(done)
.catch(done);
});

it('should work with the "onlyFiles" option', (done) => {
runEmit({
expectedAssetKeys: [
'.dottedfile',
'directoryfile.txt',
'nested/deep-nested/deepnested.txt',
'nested/nestedfile.txt',
],
patterns: [
{
from: 'directory',
globOptions: {
onlyFiles: true,
},
},
],
})
.then(done)
.catch(done);
});
});
29 changes: 29 additions & 0 deletions test/helpers/PreCopyPlugin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
class PreCopyPlugin {
constructor(options = {}) {
this.options = options.options || {};
}

apply(compiler) {
const plugin = { name: 'PreCopyPlugin' };

compiler.hooks.compilation.tap(plugin, (compilation) => {
compilation.hooks.additionalAssets.tapAsync(
'copy-webpack-plugin',
(callback) => {
this.options.existingAssets.forEach((assetName) => {
// eslint-disable-next-line no-param-reassign
compilation.assets[assetName] = {
source() {
return 'existing';
},
};
});

callback();
}
);
});
}
}

export default PreCopyPlugin;
11 changes: 11 additions & 0 deletions test/helpers/compile.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export default (compiler) => {
return new Promise((resolve, reject) => {
compiler.run((error, stats) => {
if (error) {
return reject(error);
}

return resolve({ stats, compiler });
});
});
};
1 change: 1 addition & 0 deletions test/helpers/enter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
// Entry point for tests
32 changes: 32 additions & 0 deletions test/helpers/getCompiler.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import path from 'path';

import webpack from 'webpack';
import { createFsFromVolume, Volume } from 'memfs';

export default (config = {}) => {
const fullConfig = {
mode: 'development',
context: path.resolve(__dirname, '../fixtures'),
entry: path.resolve(__dirname, '../helpers/enter.js'),
output: {
path: path.resolve(__dirname, '../build'),
},
...config,
};

if (webpack.version[0] === 5) {
fullConfig.stats.source = true;
}

const compiler = webpack(fullConfig);

if (!config.outputFileSystem) {
const outputFileSystem = createFsFromVolume(new Volume());
// Todo remove when we drop webpack@4 support
outputFileSystem.join = path.join.bind(path);

compiler.outputFileSystem = outputFileSystem;
}

return compiler;
};
6 changes: 6 additions & 0 deletions test/helpers/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import compile from './compile';
import getCompiler from './getCompiler';
import readAsset from './readAsset';
import readAssets from './readAssets';

export { compile, getCompiler, readAsset, readAssets };
59 changes: 0 additions & 59 deletions test/helpers/mocks.js

This file was deleted.

28 changes: 28 additions & 0 deletions test/helpers/readAsset.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import path from 'path';

export default (asset, compiler, stats) => {
const usedFs = compiler.outputFileSystem;
const outputPath = stats.compilation.outputOptions.path;

let data = '';
let targetFile = asset;

const queryStringIdx = targetFile.indexOf('?');

if (queryStringIdx >= 0) {
targetFile = targetFile.substr(0, queryStringIdx);
}

try {
const isArchive = /.gz$/i.test(targetFile);
data = usedFs.readFileSync(path.join(outputPath, targetFile));

if (!isArchive) {
data = data.toString();
}
} catch (error) {
data = error.toString();
}

return data;
};
13 changes: 13 additions & 0 deletions test/helpers/readAssets.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import readAsset from './readAsset';

export default function readAssets(compiler, stats) {
const assets = {};

Object.keys(stats.compilation.assets)
.filter((a) => a !== 'main.js')
.forEach((asset) => {
assets[asset] = readAsset(asset, compiler, stats);
});

return assets;
}
Loading