├── .cspell.json
├── .editorconfig
├── .eslintignore
├── .eslintrc.js
├── .gitattributes
├── .github
├── CODEOWNERS
├── CONTRIBUTING.md
├── FUNDING.yml
├── ISSUE_TEMPLATE.md
├── ISSUE_TEMPLATE
│ ├── BUG.md
│ ├── DOCS.md
│ ├── FEATURE.md
│ ├── MODIFICATION.md
│ └── SUPPORT.md
├── PULL_REQUEST_TEMPLATE.md
└── workflows
│ ├── dependency-review.yml
│ └── nodejs.yml
├── .gitignore
├── .husky
├── commit-msg
└── pre-commit
├── .prettierignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── babel.config.js
├── commitlint.config.js
├── globalSetup.js
├── jest.config.js
├── lint-staged.config.js
├── package-lock.json
├── package.json
├── src
├── index.js
├── options.json
└── utils.js
├── test
├── CopyPlugin.test.js
├── __snapshots__
│ ├── CopyPlugin.test.js.snap
│ ├── transformAll-option.test.js.snap
│ └── validate-options.test.js.snap
├── context-option.test.js
├── filter-option.test.js
├── fixtures
│ ├── .file.txt
│ ├── [(){}[]!+@escaped-test^$]
│ │ └── hello.txt
│ ├── binextension.bin
│ ├── dir (86)
│ │ ├── file.txt
│ │ └── nesteddir
│ │ │ ├── deepnesteddir
│ │ │ └── deepnesteddir.txt
│ │ │ └── nestedfile.txt
│ ├── directory
│ │ ├── .dottedfile
│ │ ├── directoryfile.txt
│ │ └── nested
│ │ │ ├── deep-nested
│ │ │ └── deepnested.txt
│ │ │ └── nestedfile.txt
│ ├── file.txt
│ ├── file.txt.gz
│ ├── noextension
│ ├── symlink
│ │ ├── directory-ln
│ │ ├── directory
│ │ │ ├── file.txt
│ │ │ └── nested-directory
│ │ │ │ └── file-in-nested-directory.txt
│ │ ├── file-ln.txt
│ │ └── file.txt
│ └── watch
│ │ ├── .gitkeep
│ │ ├── _t1
│ │ ├── .gitkeep
│ │ └── directory
│ │ │ └── .gitkeep
│ │ ├── _t2
│ │ ├── .gitkeep
│ │ └── directory
│ │ │ └── .gitkeep
│ │ ├── _t3
│ │ ├── .gitkeep
│ │ └── directory
│ │ │ └── .gitkeep
│ │ ├── _t4
│ │ ├── .gitkeep
│ │ └── directory
│ │ │ └── .gitkeep
│ │ ├── _t5
│ │ ├── .gitkeep
│ │ └── directory
│ │ │ └── .gitkeep
│ │ └── directory
│ │ └── .gitkeep
├── force-option.test.js
├── from-option.test.js
├── globOptions-option.test.js
├── helpers
│ ├── BreakContenthashPlugin.js
│ ├── ChildCompiler.js
│ ├── PreCopyPlugin.js
│ ├── built-in-modules
│ │ ├── fs.js
│ │ ├── path.js
│ │ ├── process.js
│ │ ├── stream.js
│ │ ├── url.js
│ │ └── util.js
│ ├── compile.js
│ ├── enter-with-asset-modules.js
│ ├── enter.js
│ ├── getCompiler.js
│ ├── index.js
│ ├── readAsset.js
│ ├── readAssets.js
│ ├── removeIllegalCharacterForWindows.js
│ └── run.js
├── info-option.test.js
├── noErrorOnMissing.test.js
├── priority-option.test.js
├── to-option.test.js
├── toType-option.test.js
├── transform-option.test.js
├── transformAll-option.test.js
└── validate-options.test.js
├── tsconfig.json
└── types
├── index.d.ts
└── utils.d.ts
/.cspell.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.2",
3 | "language": "en,en-gb",
4 | "words": [
5 | "commitlint",
6 | "nestedfile",
7 | "directoryfile",
8 | "globby",
9 | "posix",
10 | "newfile",
11 | "fullhash",
12 | "deepnested",
13 | "subdir",
14 | "newdirectory",
15 | "nesteddir",
16 | "Globby",
17 | "determinated",
18 | "Etags",
19 | "newchanged",
20 | "dottedfile",
21 | "mathes",
22 | "newext",
23 | "noextension",
24 | "binextension",
25 | "tempdir",
26 | "memfs",
27 | "enry",
28 | "globstar",
29 | "deepnesteddir",
30 | "globstar",
31 | "bazz",
32 | "newbinextension",
33 | "behavour",
34 | "dottedfile",
35 | "tempfile"
36 | ],
37 |
38 | "ignorePaths": [
39 | "CHANGELOG.md",
40 | "package.json",
41 | "dist/**",
42 | "**/__snapshots__/**",
43 | "package-lock.json",
44 | "node_modules",
45 | "coverage",
46 | "*.log"
47 | ]
48 | }
49 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # editorconfig.org
2 |
3 | [*]
4 | charset = utf-8
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [*.md]
12 | trim_trailing_whitespace = false
13 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | /coverage
2 | /dist
3 | /node_modules
4 | /test/fixtures
5 | /test/bundled
6 | /types/**/*
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | parser: "@babel/eslint-parser",
4 | extends: ["@webpack-contrib/eslint-config-webpack", "prettier"],
5 | };
6 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | package-lock.json -diff
2 | * text=auto
3 | bin/* eol=lf
4 | yarn.lock -diff
5 |
--------------------------------------------------------------------------------
/.github/CODEOWNERS:
--------------------------------------------------------------------------------
1 | # These are the default owners for everything in
2 | # webpack-contrib
3 | @webpack-contrib/org-maintainers
4 |
5 | # Add repository specific users / groups
6 | # below here for libs that are not maintained by the org.
7 |
--------------------------------------------------------------------------------
/.github/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing in @webpack-contrib
2 |
3 | We'd always love contributions to further improve the webpack / webpack-contrib ecosystem!
4 | Here are the guidelines we'd like you to follow:
5 |
6 | - [Questions and Problems](#question)
7 | - [Issues and Bugs](#issue)
8 | - [Feature Requests](#feature)
9 | - [Pull Request Submission Guidelines](#submit-pr)
10 | - [Commit Message Conventions](#commit)
11 |
12 | ## Got a Question or Problem?
13 |
14 | Please submit support requests and questions to StackOverflow using the tag [[webpack]](http://stackoverflow.com/tags/webpack).
15 | StackOverflow is better suited for this kind of support though you may also inquire in [Webpack Gitter](https://gitter.im/webpack/webpack).
16 | The issue tracker is for bug reports and feature discussions.
17 |
18 | ## Found an Issue or Bug?
19 |
20 | Before you submit an issue, please search the issue tracker, an issue for your problem may already exist, and the discussion might inform you of workarounds readily available.
21 |
22 | We want to fix all the issues as soon as possible, but before fixing a bug, we need to reproduce and confirm it. In order to reproduce bugs, we ask that you provide a minimal reproduction scenario (GitHub repo or failing test case). Having a live, reproducible scenario gives us a wealth of important information without going back & forth to you with additional questions like:
23 |
24 | - version of Webpack used
25 | - version of the loader / plugin you are creating a bug report for
26 | - the use-case that fails
27 |
28 | A minimal reproduce scenario allows us to quickly confirm a bug (or point out config problems) as well as confirm that we are fixing the right problem.
29 |
30 | We will be insisting on a minimal reproduction scenario in order to save the maintainers' time and ultimately be able to fix more bugs. We understand that sometimes it might be hard to extract essential bits of code from a larger codebase, but we really need to isolate the problem before we can fix it.
31 |
32 | Unfortunately, we are unable to investigate or fix bugs without a minimal reproduction, so if we don't hear back from you, we may have to close an issue that doesn't have enough info to be reproduced.
33 |
34 | ## Feature Requests?
35 |
36 | You can _request_ a new feature by creating an issue on GitHub.
37 |
38 | If you would like to _implement_ a new feature yourself, please **first submit an issue** with a proposal to ensure the idea aligns with the goals of the project.
39 |
40 | ## Pull Request Submission Guidelines
41 |
42 | Before you submit your Pull Request (PR) consider the following guidelines:
43 |
44 | - Search GitHub for an open or closed PR related to your submission to avoid duplicating effort.
45 | - Commit your changes using a descriptive commit message that follows our [commit message conventions](#commit). This is important because release notes are automatically generated from these messages.
46 | - Complete the `Pull Request Template`. Pull requests that ignore the template will not be reviewed.
47 | - Please sign the `Contributor License Agreement (CLA)` when you open your pull request. We cannot accept your contribution without it. Be sure to sign using the primary email address associated with your local and GitHub account.
48 |
49 | ## Webpack Contrib Commit Conventions
50 |
51 | Each commit message consists of a **header**, a **body** and a **footer**. The header has a special
52 | format that includes a **type**, a **scope** and a **subject**:
53 |
54 | ```
55 | ():
56 |
57 |
58 |
59 |
60 | ```
61 |
62 | The **header** is mandatory and the **scope** of the header is optional.
63 |
64 | No line in the commit message should exceed 100 characters! This makes the message easier to read on GitHub as well as in various Git tools.
65 |
66 | The footer should contain a [closing reference to an issue](https://help.github.com/articles/closing-issues-via-commit-messages/) if any.
67 |
68 | Examples:
69 |
70 | ```
71 | docs(readme): update install instructions
72 | ```
73 |
74 | ```
75 | fix: refer to the `entrypoint` instead of the first `module`
76 | ```
77 |
78 | ### Revert
79 |
80 | If the commit reverts a previous commit, it should begin with `revert:`, followed by the header of the reverted commit.
81 | In the body it should say: `This reverts commit .`, where the hash is the SHA of the commit being reverted.
82 |
83 | ### Type
84 |
85 | Must be one of the following commit types:
86 |
87 | - **build**: Changes that affect the build system or external dependencies (example scopes: babel, npm)
88 | - **chore**: Changes that fall outside of build / docs that do not effect source code (example scopes: package, defaults)
89 | - **ci**: Changes to our CI configuration files and scripts (example scopes: circleci, travis)
90 | - **docs**: Documentation only changes (example scopes: readme, changelog)
91 | - **feat**: A new feature
92 | - **fix**: A bug fix
93 | - **perf**: A code change that improves performance
94 | - **refactor**: A code change that neither fixes a bug nor adds a feature
95 | - **revert**: Used when reverting a committed change
96 | - **style**: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons)
97 | - **test**: Addition of or updates to Jest tests
98 |
99 | ### Scope
100 |
101 | The scope is subjective & depends on the `type` see above. A good example of a scope would be a change to a particular class or module.
102 |
103 | ### Subject
104 |
105 | The subject contains a succinct description of the change:
106 |
107 | - use the imperative, present tense: "change" not "changed" or "changes"
108 | - don't capitalize the first letter
109 | - no dot (.) at the end
110 |
111 | ### Body
112 |
113 | Just as in the **subject**, use the imperative, present tense: "change" not "changed" or "changes".
114 | The body should include the motivation for the change and contrast it with previous behavior.
115 |
116 | ### Footer
117 |
118 | The footer should include any information about **Breaking Changes** and is also the place to reference GitHub issues that this commit **Closes**.
119 |
120 | **Breaking Changes** must start with the word `BREAKING CHANGE:` followed by a space or two new lines. The rest of the breaking change details should be provided after this.
121 |
122 | Example
123 |
124 | ```
125 | BREAKING CHANGE: Updates to `Chunk.mapModules`.
126 |
127 | This release is not backwards compatible with `Webpack 2.x` due to breaking changes in webpack/webpack#4764
128 | Migration: see webpack/webpack#5225
129 |
130 | ```
131 |
132 | ## Testing Your Pull Request
133 |
134 | You may need to test your changes in a real-world project or a dependent module. Thankfully, GitHub provides a means to do this. To add a dependency to the `package.json` of such a project, use the following syntax:
135 |
136 | ```json
137 | {
138 | "devDependencies": {
139 | "copy-webpack-plugin": "webpack-contrib/copy-webpack-plugin#{id}/head"
140 | }
141 | }
142 | ```
143 |
144 | Where `{id}` is the # ID of your Pull Request.
145 |
146 | ## Contributor License Agreement
147 |
148 | When submitting your contribution, a CLA (Contributor License Agreement) bot will verify whether you have signed the [CLA](https://easycla.lfx.linuxfoundation.org/#/?version=2).
149 | If it is your first time, it will link you to the right place to sign it.
150 | However, if the email used in your commits doesn’t match the email associated with your GitHub account, the CLA bot won’t accept your contribution.
151 |
152 | Run `git config user.email` to see your Git email, and verify it with [your GitHub email](https://github.com/settings/emails).
153 |
154 | ## Thanks
155 |
156 | For your interest, time, understanding, and for following this simple guide.
157 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | open_collective: webpack
2 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
17 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/BUG.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: 🐛 Bug Report
3 | about: Something went awry and you'd like to tell us about it.
4 | ---
5 |
6 |
7 |
8 |
9 | ### Bug report
10 |
11 |
12 |
13 |
14 |
15 |
16 | ### Actual Behavior
17 |
18 |
19 |
20 | ### Expected Behavior
21 |
22 |
23 |
24 |
25 | ### How Do We Reproduce?
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 | ### Please paste the results of `npx webpack-cli info` here, and mention other relevant information
35 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/DOCS.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: 📚 Documentation
3 | about: Are the docs lacking or missing something? Do they need some new 🔥 hotness? Tell us here.
4 | ---
5 |
6 |
7 |
8 |
9 | Documentation Is:
10 |
11 |
12 |
13 | - [ ] Missing
14 | - [ ] Needed
15 | - [ ] Confusing
16 | - [ ] Not Sure?
17 |
18 | ### Please Explain in Detail...
19 |
20 |
21 |
22 |
23 |
24 |
25 | ### Your Proposal for Changes
26 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/FEATURE.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: ✨ Feature Request
3 | about: Suggest an idea for this project
4 | ---
5 |
6 |
7 |
8 |
9 | ### Feature Proposal
10 |
11 |
12 |
13 |
14 |
15 |
16 | ### Feature Use Case
17 |
18 | ### Please paste the results of `npx webpack-cli info` here, and mention other relevant information
19 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/MODIFICATION.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: 🔧 Modification Request
3 | about: Would you like something work differently? Have an alternative approach? This is the template for you.
4 | ---
5 |
6 |
7 |
8 |
9 | ### Modification Proposal
10 |
11 |
12 |
13 |
14 |
15 |
16 | ### Expected Behavior / Situation
17 |
18 | ### Actual Behavior / Situation
19 |
20 | ### Please paste the results of `npx webpack-cli info` here, and mention other relevant information
21 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/SUPPORT.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: 🆘 Support, Help, and Advice
3 | about: 👉🏽 Need support, help, or advice? Don't open an issue! Head to https://github.com/webpack/webpack/discussions or StackOverflow.
4 | ---
5 |
6 | Hey there! If you need support, help, or advice then this is not the place to ask.
7 | Please visit [GitHub Discussions](https://github.com/webpack/webpack/discussions) or [StackOverflow](https://stackoverflow.com/questions/tagged/webpack) instead.
8 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
10 |
11 | This PR contains a:
12 |
13 | - [ ] **bugfix**
14 | - [ ] new **feature**
15 | - [ ] **code refactor**
16 | - [ ] **test update**
17 | - [ ] **typo fix**
18 | - [ ] **metadata update**
19 |
20 | ### Motivation / Use-Case
21 |
22 |
27 |
28 | ### Breaking Changes
29 |
30 |
34 |
35 | ### Additional Info
36 |
--------------------------------------------------------------------------------
/.github/workflows/dependency-review.yml:
--------------------------------------------------------------------------------
1 | name: "Dependency Review"
2 | on: [pull_request]
3 |
4 | permissions:
5 | contents: read
6 |
7 | jobs:
8 | dependency-review:
9 | runs-on: ubuntu-latest
10 | steps:
11 | - name: "Checkout Repository"
12 | uses: actions/checkout@v4
13 | - name: "Dependency Review"
14 | uses: actions/dependency-review-action@v4
15 |
--------------------------------------------------------------------------------
/.github/workflows/nodejs.yml:
--------------------------------------------------------------------------------
1 | name: copy-webpack-plugin
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 | - next
8 | pull_request:
9 | branches:
10 | - master
11 | - next
12 |
13 | permissions:
14 | contents: read
15 |
16 | jobs:
17 | lint:
18 | name: Lint - ${{ matrix.os }} - Node v${{ matrix.node-version }}
19 |
20 | env:
21 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
22 |
23 | strategy:
24 | matrix:
25 | os: [ubuntu-latest]
26 | node-version: [lts/*]
27 |
28 | runs-on: ${{ matrix.os }}
29 |
30 | concurrency:
31 | group: lint-${{ matrix.os }}-v${{ matrix.node-version }}-${{ github.ref }}
32 | cancel-in-progress: true
33 |
34 | steps:
35 | - uses: actions/checkout@v4
36 | with:
37 | fetch-depth: 0
38 |
39 | - name: Use Node.js ${{ matrix.node-version }}
40 | uses: actions/setup-node@v4
41 | with:
42 | node-version: ${{ matrix.node-version }}
43 | cache: "npm"
44 |
45 | - name: Install dependencies
46 | run: npm ci
47 |
48 | - name: Lint
49 | run: npm run lint
50 |
51 | - name: Build types
52 | run: npm run build:types
53 |
54 | - name: Check types
55 | run: if [ -n "$(git status types --porcelain)" ]; then echo "Missing types. Update types by running 'npm run build:types'"; exit 1; else echo "All types are valid"; fi
56 |
57 | - name: Security audit
58 | run: npm run security
59 |
60 | - name: Validate PR commits with commitlint
61 | if: github.event_name == 'pull_request'
62 | run: npx commitlint --from ${{ github.event.pull_request.head.sha }}~${{ github.event.pull_request.commits }} --to ${{ github.event.pull_request.head.sha }} --verbose
63 |
64 | test:
65 | name: Test - ${{ matrix.os }} - Node v${{ matrix.node-version }}, Webpack ${{ matrix.webpack-version }}
66 |
67 | strategy:
68 | matrix:
69 | os: [ubuntu-latest, windows-latest, macos-latest]
70 | node-version: [18.x, 20.x, 22.x, 24.x]
71 | webpack-version: [latest]
72 |
73 | runs-on: ${{ matrix.os }}
74 |
75 | concurrency:
76 | group: test-${{ matrix.os }}-v${{ matrix.node-version }}-${{ matrix.webpack-version }}-${{ github.ref }}
77 | cancel-in-progress: true
78 |
79 | steps:
80 | - name: Setup Git
81 | if: matrix.os == 'windows-latest'
82 | run: git config --global core.autocrlf input
83 |
84 | - uses: actions/checkout@v4
85 |
86 | - name: Use Node.js ${{ matrix.node-version }}
87 | uses: actions/setup-node@v4
88 | with:
89 | node-version: ${{ matrix.node-version }}
90 | cache: "npm"
91 |
92 | - name: Install dependencies
93 | run: npm ci
94 |
95 | - name: Install webpack ${{ matrix.webpack-version }}
96 | if: matrix.webpack-version != 'latest'
97 | run: npm i webpack@${{ matrix.webpack-version }}
98 |
99 | - name: Run tests for webpack version ${{ matrix.webpack-version }}
100 | run: npm run test:coverage -- --ci
101 |
102 | - name: Submit coverage data to codecov
103 | uses: codecov/codecov-action@v5
104 | with:
105 | token: ${{ secrets.CODECOV_TOKEN }}
106 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | logs
2 | *.log
3 | npm-debug.log*
4 | .eslintcache
5 | .cspellcache
6 |
7 | /coverage
8 | /dist
9 | /local
10 | /reports
11 | /node_modules
12 | /test/fixtures/\[special\$directory\]
13 | /test/outputs
14 | /test/bundled
15 |
16 | .DS_Store
17 | Thumbs.db
18 | .idea
19 | .vscode
20 | *.sublime-project
21 | *.sublime-workspace
22 | *.iml
23 |
--------------------------------------------------------------------------------
/.husky/commit-msg:
--------------------------------------------------------------------------------
1 | commitlint --edit $1
2 |
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | lint-staged
2 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | /coverage
2 | /dist
3 | /node_modules
4 | /test/fixtures
5 | /test/bundled
6 | CHANGELOG.md
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright JS Foundation and other contributors
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining
4 | a copy of this software and associated documentation files (the
5 | 'Software'), to deal in the Software without restriction, including
6 | without limitation the rights to use, copy, modify, merge, publish,
7 | distribute, sublicense, and/or sell copies of the Software, and to
8 | permit persons to whom the Software is furnished to do so, subject to
9 | the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be
12 | included in all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
17 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
18 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
19 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
20 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | const MIN_BABEL_VERSION = 7;
2 |
3 | module.exports = (api) => {
4 | api.assertVersion(MIN_BABEL_VERSION);
5 | api.cache(true);
6 |
7 | return {
8 | presets: [
9 | [
10 | "@babel/preset-env",
11 | {
12 | exclude: ["proposal-dynamic-import"],
13 | targets: {
14 | node: "18.12.0",
15 | },
16 | },
17 | ],
18 | ],
19 | };
20 | };
21 |
--------------------------------------------------------------------------------
/commitlint.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: ["@commitlint/config-conventional"],
3 | rules: {
4 | "header-max-length": [0],
5 | "body-max-line-length": [0],
6 | "footer-max-line-length": [0],
7 | },
8 | };
9 |
--------------------------------------------------------------------------------
/globalSetup.js:
--------------------------------------------------------------------------------
1 | const path = require("path");
2 | const fs = require("fs");
3 |
4 | const removeIllegalCharacterForWindows = require("./test/helpers/removeIllegalCharacterForWindows");
5 |
6 | const baseDir = path.resolve(__dirname, "test/fixtures");
7 |
8 | const specialFiles = {
9 | "[special$directory]/nested/nestedfile.txt": "",
10 | "[special$directory]/(special-*file).txt": "special",
11 | "[special$directory]/directoryfile.txt": "new",
12 | };
13 |
14 | module.exports = () => {
15 | Object.keys(specialFiles).forEach((originFile) => {
16 | const file = removeIllegalCharacterForWindows(originFile);
17 | const dir = path.dirname(file);
18 |
19 | fs.mkdirSync(path.join(baseDir, dir), { recursive: true });
20 | fs.writeFileSync(path.join(baseDir, file), specialFiles[originFile]);
21 | });
22 | };
23 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | testEnvironment: "node",
3 | globalSetup: "/globalSetup.js",
4 | };
5 |
--------------------------------------------------------------------------------
/lint-staged.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | "*": [
3 | "prettier --cache --write --ignore-unknown",
4 | "cspell --cache --no-must-find-files",
5 | ],
6 | "*.js": ["eslint --cache --fix"],
7 | };
8 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "copy-webpack-plugin",
3 | "version": "13.0.0",
4 | "description": "Copy files && directories with webpack",
5 | "license": "MIT",
6 | "repository": "webpack-contrib/copy-webpack-plugin",
7 | "author": "Len Boyette",
8 | "homepage": "https://github.com/webpack-contrib/copy-webpack-plugin",
9 | "bugs": "https://github.com/webpack-contrib/copy-webpack-plugin/issues",
10 | "funding": {
11 | "type": "opencollective",
12 | "url": "https://opencollective.com/webpack"
13 | },
14 | "main": "dist/index.js",
15 | "types": "types/index.d.ts",
16 | "engines": {
17 | "node": ">= 18.12.0"
18 | },
19 | "scripts": {
20 | "start": "npm run build -- -w",
21 | "clean": "del-cli dist types",
22 | "prebuild": "npm run clean",
23 | "build:types": "tsc --declaration --emitDeclarationOnly --outDir types --rootDir src && prettier \"types/**/*.ts\" --write",
24 | "build:code": "cross-env NODE_ENV=production babel src -d dist --copy-files",
25 | "build": "npm-run-all -p \"build:**\"",
26 | "commitlint": "commitlint --from=master",
27 | "security": "npm audit --production",
28 | "lint:prettier": "prettier --cache --list-different .",
29 | "lint:js": "eslint --cache .",
30 | "lint:spelling": "cspell --cache --no-must-find-files --quiet \"**/*.*\"",
31 | "lint:types": "tsc --pretty --noEmit",
32 | "lint": "npm-run-all -l -p \"lint:**\"",
33 | "fix:js": "npm run lint:js -- --fix",
34 | "fix:prettier": "npm run lint:prettier -- --write",
35 | "fix": "npm-run-all -l fix:js fix:prettier",
36 | "test:only": "cross-env NODE_ENV=test node --experimental-vm-modules node_modules/jest/bin/jest.js",
37 | "test:watch": "npm run test:only -- --watch",
38 | "test:coverage": "npm run test:only -- --collectCoverageFrom=\"src/**/*.js\" --coverage",
39 | "pretest": "npm run lint",
40 | "test": "npm run test:coverage",
41 | "prepare": "husky && npm run build",
42 | "release": "standard-version"
43 | },
44 | "files": [
45 | "dist",
46 | "types"
47 | ],
48 | "peerDependencies": {
49 | "webpack": "^5.1.0"
50 | },
51 | "dependencies": {
52 | "glob-parent": "^6.0.1",
53 | "normalize-path": "^3.0.0",
54 | "schema-utils": "^4.2.0",
55 | "serialize-javascript": "^6.0.2",
56 | "tinyglobby": "^0.2.12"
57 | },
58 | "devDependencies": {
59 | "@babel/cli": "^7.24.6",
60 | "@babel/core": "^7.25.2",
61 | "@babel/eslint-parser": "^7.25.1",
62 | "@babel/preset-env": "^7.25.3",
63 | "@commitlint/cli": "^19.3.0",
64 | "@commitlint/config-conventional": "^19.2.2",
65 | "@types/glob-parent": "^5.1.3",
66 | "@types/node": "^22.13.5",
67 | "@types/normalize-path": "^3.0.2",
68 | "@types/serialize-javascript": "^5.0.4",
69 | "@webpack-contrib/eslint-config-webpack": "^3.0.0",
70 | "babel-jest": "^29.7.0",
71 | "cross-env": "^7.0.3",
72 | "cspell": "^8.15.6",
73 | "del": "^6.1.1",
74 | "del-cli": "^6.0.0",
75 | "eslint": "^8.57.0",
76 | "eslint-config-prettier": "^9.1.0",
77 | "eslint-plugin-import": "^2.29.1",
78 | "file-loader": "^6.2.0",
79 | "husky": "^9.1.4",
80 | "is-gzip": "^2.0.0",
81 | "jest": "^29.7.0",
82 | "lint-staged": "^15.2.8",
83 | "memfs": "^4.11.1",
84 | "npm-run-all": "^4.1.5",
85 | "prettier": "^3.2.5",
86 | "standard-version": "^9.3.1",
87 | "typescript": "^5.4.5",
88 | "webpack": "^5.91.0"
89 | },
90 | "keywords": [
91 | "webpack",
92 | "plugin",
93 | "transfer",
94 | "move",
95 | "copy"
96 | ]
97 | }
98 |
--------------------------------------------------------------------------------
/src/options.json:
--------------------------------------------------------------------------------
1 | {
2 | "definitions": {
3 | "ObjectPattern": {
4 | "type": "object",
5 | "additionalProperties": false,
6 | "properties": {
7 | "from": {
8 | "type": "string",
9 | "description": "Glob or path from where we copy files.",
10 | "link": "https://github.com/webpack-contrib/copy-webpack-plugin#from",
11 | "minLength": 1
12 | },
13 | "to": {
14 | "anyOf": [
15 | {
16 | "type": "string"
17 | },
18 | {
19 | "instanceof": "Function"
20 | }
21 | ],
22 | "description": "Output path.",
23 | "link": "https://github.com/webpack-contrib/copy-webpack-plugin#to"
24 | },
25 | "context": {
26 | "type": "string",
27 | "description": "A path that determines how to interpret the 'from' path.",
28 | "link": "https://github.com/webpack-contrib/copy-webpack-plugin#context"
29 | },
30 | "globOptions": {
31 | "type": "object",
32 | "description": "Allows to configure the glob pattern matching library used by the plugin.",
33 | "link": "https://github.com/webpack-contrib/copy-webpack-plugin#globoptions"
34 | },
35 | "filter": {
36 | "instanceof": "Function",
37 | "description": "Allows to filter copied assets.",
38 | "link": "https://github.com/webpack-contrib/copy-webpack-plugin#filter"
39 | },
40 | "transformAll": {
41 | "instanceof": "Function",
42 | "description": "Allows you to modify the contents of multiple files and save the result to one file.",
43 | "link": "https://github.com/webpack-contrib/copy-webpack-plugin#transformall"
44 | },
45 | "toType": {
46 | "enum": ["dir", "file", "template"],
47 | "description": "Determinate what is to option - directory, file or template.",
48 | "link": "https://github.com/webpack-contrib/copy-webpack-plugin#totype"
49 | },
50 | "force": {
51 | "type": "boolean",
52 | "description": "Overwrites files already in 'compilation.assets' (usually added by other plugins/loaders).",
53 | "link": "https://github.com/webpack-contrib/copy-webpack-plugin#force"
54 | },
55 | "priority": {
56 | "type": "number",
57 | "description": "Allows to specify the priority of copying files with the same destination name.",
58 | "link": "https://github.com/webpack-contrib/copy-webpack-plugin#priority"
59 | },
60 | "info": {
61 | "anyOf": [
62 | {
63 | "type": "object"
64 | },
65 | {
66 | "instanceof": "Function"
67 | }
68 | ],
69 | "description": "Allows to add assets info.",
70 | "link": "https://github.com/webpack-contrib/copy-webpack-plugin#info"
71 | },
72 | "transform": {
73 | "description": "Allows to modify the file contents.",
74 | "link": "https://github.com/webpack-contrib/copy-webpack-plugin#transform",
75 | "anyOf": [
76 | {
77 | "instanceof": "Function"
78 | },
79 | {
80 | "type": "object",
81 | "additionalProperties": false,
82 | "properties": {
83 | "transformer": {
84 | "instanceof": "Function",
85 | "description": "Allows to modify the file contents.",
86 | "link": "https://github.com/webpack-contrib/copy-webpack-plugin#transformer"
87 | },
88 | "cache": {
89 | "description": "Enables/disables and configure caching.",
90 | "link": "https://github.com/webpack-contrib/copy-webpack-plugin#cache",
91 | "anyOf": [
92 | {
93 | "type": "boolean"
94 | },
95 | {
96 | "type": "object",
97 | "additionalProperties": false,
98 | "properties": {
99 | "keys": {
100 | "anyOf": [
101 | {
102 | "type": "object",
103 | "additionalProperties": true
104 | },
105 | {
106 | "instanceof": "Function"
107 | }
108 | ]
109 | }
110 | }
111 | }
112 | ]
113 | }
114 | }
115 | }
116 | ]
117 | },
118 | "noErrorOnMissing": {
119 | "type": "boolean",
120 | "description": "Doesn't generate an error on missing file(s).",
121 | "link": "https://github.com/webpack-contrib/copy-webpack-plugin#noerroronmissing"
122 | }
123 | },
124 | "required": ["from"]
125 | },
126 | "StringPattern": {
127 | "type": "string",
128 | "minLength": 1
129 | }
130 | },
131 | "type": "object",
132 | "additionalProperties": false,
133 | "properties": {
134 | "patterns": {
135 | "type": "array",
136 | "minItems": 1,
137 | "items": {
138 | "anyOf": [
139 | {
140 | "$ref": "#/definitions/StringPattern"
141 | },
142 | {
143 | "$ref": "#/definitions/ObjectPattern"
144 | }
145 | ]
146 | }
147 | },
148 | "options": {
149 | "type": "object",
150 | "additionalProperties": false,
151 | "properties": {
152 | "concurrency": {
153 | "type": "number",
154 | "description": "Limits the number of simultaneous requests to fs.",
155 | "link": "https://github.com/webpack-contrib/copy-webpack-plugin#concurrency"
156 | }
157 | }
158 | }
159 | },
160 | "required": ["patterns"]
161 | }
162 |
--------------------------------------------------------------------------------
/src/utils.js:
--------------------------------------------------------------------------------
1 | /** @typedef {import("webpack").Compilation["inputFileSystem"] } InputFileSystem */
2 | /** @typedef {import("fs").Stats } Stats */
3 |
4 | /**
5 | * @param {InputFileSystem} inputFileSystem
6 | * @param {string} path
7 | * @return {Promise}
8 | */
9 | function stat(inputFileSystem, path) {
10 | return new Promise((resolve, reject) => {
11 | inputFileSystem.stat(
12 | path,
13 | /**
14 | * @param {null | undefined | NodeJS.ErrnoException} err
15 | * @param {undefined | Stats} stats
16 | */
17 | // @ts-ignore
18 | (err, stats) => {
19 | if (err) {
20 | reject(err);
21 |
22 | return;
23 | }
24 |
25 | resolve(stats);
26 | },
27 | );
28 | });
29 | }
30 |
31 | /**
32 | * @param {InputFileSystem} inputFileSystem
33 | * @param {string} path
34 | * @return {Promise}
35 | */
36 | function readFile(inputFileSystem, path) {
37 | return new Promise((resolve, reject) => {
38 | inputFileSystem.readFile(
39 | path,
40 | /**
41 | * @param {null | undefined | NodeJS.ErrnoException} err
42 | * @param {undefined | string | Buffer} data
43 | */
44 | (err, data) => {
45 | if (err) {
46 | reject(err);
47 |
48 | return;
49 | }
50 |
51 | resolve(/** @type {string | Buffer} */ (data));
52 | },
53 | );
54 | });
55 | }
56 |
57 | const notSettled = Symbol(`not-settled`);
58 |
59 | /**
60 | * @template T
61 | * @typedef {() => Promise} Task
62 | */
63 |
64 | /**
65 | * Run tasks with limited concurrency.
66 | * @template T
67 | * @param {number} limit - Limit of tasks that run at once.
68 | * @param {Task[]} tasks - List of tasks to run.
69 | * @returns {Promise} A promise that fulfills to an array of the results
70 | */
71 | function throttleAll(limit, tasks) {
72 | if (!Number.isInteger(limit) || limit < 1) {
73 | throw new TypeError(
74 | `Expected \`limit\` to be a finite number > 0, got \`${limit}\` (${typeof limit})`,
75 | );
76 | }
77 |
78 | if (
79 | !Array.isArray(tasks) ||
80 | !tasks.every((task) => typeof task === `function`)
81 | ) {
82 | throw new TypeError(
83 | `Expected \`tasks\` to be a list of functions returning a promise`,
84 | );
85 | }
86 |
87 | return new Promise((resolve, reject) => {
88 | const result = Array(tasks.length).fill(notSettled);
89 |
90 | const entries = tasks.entries();
91 |
92 | const next = () => {
93 | const { done, value } = entries.next();
94 |
95 | if (done) {
96 | const isLast = !result.includes(notSettled);
97 |
98 | if (isLast) {
99 | resolve(/** @type{T[]} **/ (result));
100 | }
101 |
102 | return;
103 | }
104 |
105 | const [index, task] = value;
106 |
107 | /**
108 | * @param {T} x
109 | */
110 | const onFulfilled = (x) => {
111 | result[index] = x;
112 | next();
113 | };
114 |
115 | task().then(onFulfilled, reject);
116 | };
117 |
118 | Array(limit).fill(0).forEach(next);
119 | });
120 | }
121 |
122 | /**
123 | * @template T
124 | * @param fn {(function(): any) | undefined}
125 | * @returns {function(): T}
126 | */
127 | function memoize(fn) {
128 | let cache = false;
129 | /** @type {T} */
130 | let result;
131 |
132 | return () => {
133 | if (cache) {
134 | return result;
135 | }
136 |
137 | result = /** @type {function(): any} */ (fn)();
138 | cache = true;
139 | // Allow to clean up memory for fn
140 | // and all dependent resources
141 | // eslint-disable-next-line no-undefined, no-param-reassign
142 | fn = undefined;
143 |
144 | return result;
145 | };
146 | }
147 |
148 | module.exports = { stat, readFile, throttleAll, memoize };
149 |
--------------------------------------------------------------------------------
/test/__snapshots__/CopyPlugin.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`CopyPlugin basic should work with multi compiler mode: assets 1`] = `
4 | {
5 | ".dottedfile": "dottedfile contents
6 | ",
7 | "directoryfile.txt": "new",
8 | "nested/deep-nested/deepnested.txt": "",
9 | "nested/nestedfile.txt": "",
10 | }
11 | `;
12 |
13 | exports[`CopyPlugin basic should work with multi compiler mode: assets 2`] = `
14 | {
15 | ".dottedfile": "dottedfile contents
16 | ",
17 | "directoryfile.txt": "new",
18 | "nested/deep-nested/deepnested.txt": "",
19 | "nested/nestedfile.txt": "",
20 | }
21 | `;
22 |
23 | exports[`CopyPlugin basic should work with multi compiler mode: errors 1`] = `[]`;
24 |
25 | exports[`CopyPlugin basic should work with multi compiler mode: errors 2`] = `[]`;
26 |
27 | exports[`CopyPlugin basic should work with multi compiler mode: warnings 1`] = `[]`;
28 |
29 | exports[`CopyPlugin basic should work with multi compiler mode: warnings 2`] = `[]`;
30 |
31 | exports[`CopyPlugin cache should work with the "filesystem" cache and multi compiler mode: assets 1`] = `
32 | {
33 | ".dottedfile": "dottedfile contents
34 | ",
35 | "directoryfile.txt": "new",
36 | "nested/deep-nested/deepnested.txt": "",
37 | "nested/nestedfile.txt": "",
38 | }
39 | `;
40 |
41 | exports[`CopyPlugin cache should work with the "filesystem" cache and multi compiler mode: assets 2`] = `
42 | {
43 | ".dottedfile": "dottedfile contents
44 | ",
45 | "directoryfile.txt": "new",
46 | "nested/deep-nested/deepnested.txt": "",
47 | "nested/nestedfile.txt": "",
48 | }
49 | `;
50 |
51 | exports[`CopyPlugin cache should work with the "filesystem" cache and multi compiler mode: assets 3`] = `
52 | {
53 | ".dottedfile": "dottedfile contents
54 | ",
55 | "directoryfile.txt": "new",
56 | "nested/deep-nested/deepnested.txt": "",
57 | "nested/nestedfile.txt": "",
58 | }
59 | `;
60 |
61 | exports[`CopyPlugin cache should work with the "filesystem" cache and multi compiler mode: assets 4`] = `
62 | {
63 | ".dottedfile": "dottedfile contents
64 | ",
65 | "directoryfile.txt": "new",
66 | "nested/deep-nested/deepnested.txt": "",
67 | "nested/nestedfile.txt": "",
68 | }
69 | `;
70 |
71 | exports[`CopyPlugin cache should work with the "filesystem" cache and multi compiler mode: errors 1`] = `[]`;
72 |
73 | exports[`CopyPlugin cache should work with the "filesystem" cache and multi compiler mode: errors 2`] = `[]`;
74 |
75 | exports[`CopyPlugin cache should work with the "filesystem" cache and multi compiler mode: errors 3`] = `[]`;
76 |
77 | exports[`CopyPlugin cache should work with the "filesystem" cache and multi compiler mode: errors 4`] = `[]`;
78 |
79 | exports[`CopyPlugin cache should work with the "filesystem" cache and multi compiler mode: warnings 1`] = `[]`;
80 |
81 | exports[`CopyPlugin cache should work with the "filesystem" cache and multi compiler mode: warnings 2`] = `[]`;
82 |
83 | exports[`CopyPlugin cache should work with the "filesystem" cache and multi compiler mode: warnings 3`] = `[]`;
84 |
85 | exports[`CopyPlugin cache should work with the "filesystem" cache and multi compiler mode: warnings 4`] = `[]`;
86 |
87 | exports[`CopyPlugin cache should work with the "filesystem" cache: assets 1`] = `
88 | {
89 | ".dottedfile": "dottedfile contents
90 | ",
91 | "directoryfile.txt": "new",
92 | "nested/deep-nested/deepnested.txt": "",
93 | "nested/nestedfile.txt": "",
94 | }
95 | `;
96 |
97 | exports[`CopyPlugin cache should work with the "filesystem" cache: assets 2`] = `
98 | {
99 | ".dottedfile": "dottedfile contents
100 | ",
101 | "directoryfile.txt": "new",
102 | "nested/deep-nested/deepnested.txt": "",
103 | "nested/nestedfile.txt": "",
104 | }
105 | `;
106 |
107 | exports[`CopyPlugin cache should work with the "filesystem" cache: errors 1`] = `[]`;
108 |
109 | exports[`CopyPlugin cache should work with the "filesystem" cache: errors 2`] = `[]`;
110 |
111 | exports[`CopyPlugin cache should work with the "filesystem" cache: warnings 1`] = `[]`;
112 |
113 | exports[`CopyPlugin cache should work with the "filesystem" cache: warnings 2`] = `[]`;
114 |
115 | exports[`CopyPlugin cache should work with the "memory" cache: assets 1`] = `
116 | {
117 | ".dottedfile": "dottedfile contents
118 | ",
119 | "directoryfile.txt": "new",
120 | "nested/deep-nested/deepnested.txt": "",
121 | "nested/nestedfile.txt": "",
122 | }
123 | `;
124 |
125 | exports[`CopyPlugin cache should work with the "memory" cache: assets 2`] = `
126 | {
127 | ".dottedfile": "dottedfile contents
128 | ",
129 | "directoryfile.txt": "new",
130 | "nested/deep-nested/deepnested.txt": "",
131 | "nested/nestedfile.txt": "",
132 | }
133 | `;
134 |
135 | exports[`CopyPlugin cache should work with the "memory" cache: errors 1`] = `[]`;
136 |
137 | exports[`CopyPlugin cache should work with the "memory" cache: errors 2`] = `[]`;
138 |
139 | exports[`CopyPlugin cache should work with the "memory" cache: warnings 1`] = `[]`;
140 |
141 | exports[`CopyPlugin cache should work with the "memory" cache: warnings 2`] = `[]`;
142 |
143 | exports[`CopyPlugin cache should work with the "transform" option: assets 1`] = `
144 | {
145 | "new0.txt": "new",
146 | "new1-2.txt": "newadded1",
147 | "new1.txt": "newadded1",
148 | "new2.txt": "newadded2",
149 | "new3.txt": "newadded3",
150 | "new4.txt": "newbaz",
151 | "new5.txt": "newbaz",
152 | "new6.txt": "newbaz",
153 | }
154 | `;
155 |
156 | exports[`CopyPlugin cache should work with the "transform" option: assets 2`] = `
157 | {
158 | "new0.txt": "new",
159 | "new1-2.txt": "newadded1",
160 | "new1.txt": "newadded1",
161 | "new2.txt": "newadded2",
162 | "new3.txt": "newadded3",
163 | "new4.txt": "newbaz",
164 | "new5.txt": "newbaz",
165 | "new6.txt": "newbaz",
166 | }
167 | `;
168 |
169 | exports[`CopyPlugin cache should work with the "transform" option: errors 1`] = `[]`;
170 |
171 | exports[`CopyPlugin cache should work with the "transform" option: errors 2`] = `[]`;
172 |
173 | exports[`CopyPlugin cache should work with the "transform" option: warnings 1`] = `[]`;
174 |
175 | exports[`CopyPlugin cache should work with the "transform" option: warnings 2`] = `[]`;
176 |
177 | exports[`CopyPlugin logging should logging when "from" is a directory: logs 1`] = `
178 | {
179 | "logs": [
180 | "'to' option '.' determinated as 'dir'",
181 | "'to' option '.' determinated as 'dir'",
182 | "'to' option '.' determinated as 'dir'",
183 | "'to' option '.' determinated as 'dir'",
184 | "added './fixtures/directory' as a context dependency",
185 | "added './fixtures/directory/.dottedfile' as a file dependency",
186 | "added './fixtures/directory/directoryfile.txt' as a file dependency",
187 | "added './fixtures/directory/nested/deep-nested/deepnested.txt' as a file dependency",
188 | "added './fixtures/directory/nested/nestedfile.txt' as a file dependency",
189 | "begin globbing './fixtures/directory/**/*'...",
190 | "created snapshot for './fixtures/directory/.dottedfile'",
191 | "created snapshot for './fixtures/directory/directoryfile.txt'",
192 | "created snapshot for './fixtures/directory/nested/deep-nested/deepnested.txt'",
193 | "created snapshot for './fixtures/directory/nested/nestedfile.txt'",
194 | "creating snapshot for './fixtures/directory/.dottedfile'...",
195 | "creating snapshot for './fixtures/directory/directoryfile.txt'...",
196 | "creating snapshot for './fixtures/directory/nested/deep-nested/deepnested.txt'...",
197 | "creating snapshot for './fixtures/directory/nested/nestedfile.txt'...",
198 | "determined './fixtures/directory' is a directory",
199 | "determined that './fixtures/directory/.dottedfile' should write to '.dottedfile'",
200 | "determined that './fixtures/directory/directoryfile.txt' should write to 'directoryfile.txt'",
201 | "determined that './fixtures/directory/nested/deep-nested/deepnested.txt' should write to 'nested/deep-nested/deepnested.txt'",
202 | "determined that './fixtures/directory/nested/nestedfile.txt' should write to 'nested/nestedfile.txt'",
203 | "finished to adding additional assets",
204 | "finished to process a pattern from 'directory' using './fixtures/directory' context",
205 | "found './fixtures/directory/.dottedfile'",
206 | "found './fixtures/directory/directoryfile.txt'",
207 | "found './fixtures/directory/nested/deep-nested/deepnested.txt'",
208 | "found './fixtures/directory/nested/nestedfile.txt'",
209 | "getting cache for './fixtures/directory/.dottedfile'...",
210 | "getting cache for './fixtures/directory/directoryfile.txt'...",
211 | "getting cache for './fixtures/directory/nested/deep-nested/deepnested.txt'...",
212 | "getting cache for './fixtures/directory/nested/nestedfile.txt'...",
213 | "getting stats for './fixtures/directory'...",
214 | "missed cache for './fixtures/directory/.dottedfile'",
215 | "missed cache for './fixtures/directory/directoryfile.txt'",
216 | "missed cache for './fixtures/directory/nested/deep-nested/deepnested.txt'",
217 | "missed cache for './fixtures/directory/nested/nestedfile.txt'",
218 | "read './fixtures/directory/.dottedfile'",
219 | "read './fixtures/directory/directoryfile.txt'",
220 | "read './fixtures/directory/nested/deep-nested/deepnested.txt'",
221 | "read './fixtures/directory/nested/nestedfile.txt'",
222 | "reading './fixtures/directory/.dottedfile'...",
223 | "reading './fixtures/directory/directoryfile.txt'...",
224 | "reading './fixtures/directory/nested/deep-nested/deepnested.txt'...",
225 | "reading './fixtures/directory/nested/nestedfile.txt'...",
226 | "starting to add additional assets...",
227 | "starting to process a pattern from 'directory' using './fixtures' context",
228 | "stored cache for './fixtures/directory/.dottedfile'",
229 | "stored cache for './fixtures/directory/directoryfile.txt'",
230 | "stored cache for './fixtures/directory/nested/deep-nested/deepnested.txt'",
231 | "stored cache for './fixtures/directory/nested/nestedfile.txt'",
232 | "storing cache for './fixtures/directory/.dottedfile'...",
233 | "storing cache for './fixtures/directory/directoryfile.txt'...",
234 | "storing cache for './fixtures/directory/nested/deep-nested/deepnested.txt'...",
235 | "storing cache for './fixtures/directory/nested/nestedfile.txt'...",
236 | "writing '.dottedfile' from './fixtures/directory/.dottedfile' to compilation assets...",
237 | "writing 'directoryfile.txt' from './fixtures/directory/directoryfile.txt' to compilation assets...",
238 | "writing 'nested/deep-nested/deepnested.txt' from './fixtures/directory/nested/deep-nested/deepnested.txt' to compilation assets...",
239 | "writing 'nested/nestedfile.txt' from './fixtures/directory/nested/nestedfile.txt' to compilation assets...",
240 | "written '.dottedfile' from './fixtures/directory/.dottedfile' to compilation assets",
241 | "written 'directoryfile.txt' from './fixtures/directory/directoryfile.txt' to compilation assets",
242 | "written 'nested/deep-nested/deepnested.txt' from './fixtures/directory/nested/deep-nested/deepnested.txt' to compilation assets",
243 | "written 'nested/nestedfile.txt' from './fixtures/directory/nested/nestedfile.txt' to compilation assets",
244 | ],
245 | }
246 | `;
247 |
248 | exports[`CopyPlugin logging should logging when "from" is a file: logs 1`] = `
249 | {
250 | "logs": [
251 | "'to' option '.' determinated as 'dir'",
252 | "added './fixtures/file.txt' as a file dependency",
253 | "begin globbing './fixtures/file.txt'...",
254 | "created snapshot for './fixtures/file.txt'",
255 | "creating snapshot for './fixtures/file.txt'...",
256 | "determined './fixtures/file.txt' is a file",
257 | "determined that './fixtures/file.txt' should write to 'file.txt'",
258 | "finished to adding additional assets",
259 | "finished to process a pattern from 'file.txt' using './fixtures' context",
260 | "found './fixtures/file.txt'",
261 | "getting cache for './fixtures/file.txt'...",
262 | "getting stats for './fixtures/file.txt'...",
263 | "missed cache for './fixtures/file.txt'",
264 | "read './fixtures/file.txt'",
265 | "reading './fixtures/file.txt'...",
266 | "starting to add additional assets...",
267 | "starting to process a pattern from 'file.txt' using './fixtures' context",
268 | "stored cache for './fixtures/file.txt'",
269 | "storing cache for './fixtures/file.txt'...",
270 | "writing 'file.txt' from './fixtures/file.txt' to compilation assets...",
271 | "written 'file.txt' from './fixtures/file.txt' to compilation assets",
272 | ],
273 | }
274 | `;
275 |
276 | exports[`CopyPlugin logging should logging when "from" is a glob: logs 1`] = `
277 | {
278 | "logs": [
279 | "'to' option '.' determinated as 'dir'",
280 | "'to' option '.' determinated as 'dir'",
281 | "'to' option '.' determinated as 'dir'",
282 | "added './fixtures/directory' as a context dependency",
283 | "added './fixtures/directory/directoryfile.txt' as a file dependency",
284 | "added './fixtures/directory/nested/deep-nested/deepnested.txt' as a file dependency",
285 | "added './fixtures/directory/nested/nestedfile.txt' as a file dependency",
286 | "begin globbing './fixtures/directory/**'...",
287 | "created snapshot for './fixtures/directory/directoryfile.txt'",
288 | "created snapshot for './fixtures/directory/nested/deep-nested/deepnested.txt'",
289 | "created snapshot for './fixtures/directory/nested/nestedfile.txt'",
290 | "creating snapshot for './fixtures/directory/directoryfile.txt'...",
291 | "creating snapshot for './fixtures/directory/nested/deep-nested/deepnested.txt'...",
292 | "creating snapshot for './fixtures/directory/nested/nestedfile.txt'...",
293 | "determined './fixtures/directory/**' is a glob",
294 | "determined that './fixtures/directory/directoryfile.txt' should write to 'directory/directoryfile.txt'",
295 | "determined that './fixtures/directory/nested/deep-nested/deepnested.txt' should write to 'directory/nested/deep-nested/deepnested.txt'",
296 | "determined that './fixtures/directory/nested/nestedfile.txt' should write to 'directory/nested/nestedfile.txt'",
297 | "finished to adding additional assets",
298 | "finished to process a pattern from 'directory/**' using './fixtures' context",
299 | "found './fixtures/directory/directoryfile.txt'",
300 | "found './fixtures/directory/nested/deep-nested/deepnested.txt'",
301 | "found './fixtures/directory/nested/nestedfile.txt'",
302 | "getting cache for './fixtures/directory/directoryfile.txt'...",
303 | "getting cache for './fixtures/directory/nested/deep-nested/deepnested.txt'...",
304 | "getting cache for './fixtures/directory/nested/nestedfile.txt'...",
305 | "getting stats for './fixtures/directory/**'...",
306 | "missed cache for './fixtures/directory/directoryfile.txt'",
307 | "missed cache for './fixtures/directory/nested/deep-nested/deepnested.txt'",
308 | "missed cache for './fixtures/directory/nested/nestedfile.txt'",
309 | "read './fixtures/directory/directoryfile.txt'",
310 | "read './fixtures/directory/nested/deep-nested/deepnested.txt'",
311 | "read './fixtures/directory/nested/nestedfile.txt'",
312 | "reading './fixtures/directory/directoryfile.txt'...",
313 | "reading './fixtures/directory/nested/deep-nested/deepnested.txt'...",
314 | "reading './fixtures/directory/nested/nestedfile.txt'...",
315 | "starting to add additional assets...",
316 | "starting to process a pattern from 'directory/**' using './fixtures' context",
317 | "stored cache for './fixtures/directory/directoryfile.txt'",
318 | "stored cache for './fixtures/directory/nested/deep-nested/deepnested.txt'",
319 | "stored cache for './fixtures/directory/nested/nestedfile.txt'",
320 | "storing cache for './fixtures/directory/directoryfile.txt'...",
321 | "storing cache for './fixtures/directory/nested/deep-nested/deepnested.txt'...",
322 | "storing cache for './fixtures/directory/nested/nestedfile.txt'...",
323 | "writing 'directory/directoryfile.txt' from './fixtures/directory/directoryfile.txt' to compilation assets...",
324 | "writing 'directory/nested/deep-nested/deepnested.txt' from './fixtures/directory/nested/deep-nested/deepnested.txt' to compilation assets...",
325 | "writing 'directory/nested/nestedfile.txt' from './fixtures/directory/nested/nestedfile.txt' to compilation assets...",
326 | "written 'directory/directoryfile.txt' from './fixtures/directory/directoryfile.txt' to compilation assets",
327 | "written 'directory/nested/deep-nested/deepnested.txt' from './fixtures/directory/nested/deep-nested/deepnested.txt' to compilation assets",
328 | "written 'directory/nested/nestedfile.txt' from './fixtures/directory/nested/nestedfile.txt' to compilation assets",
329 | ],
330 | }
331 | `;
332 |
333 | exports[`CopyPlugin logging should logging when 'to' is a function: logs 1`] = `
334 | {
335 | "logs": [
336 | "'to' option 'newFile.txt' determinated as 'file'",
337 | "added './fixtures/file.txt' as a file dependency",
338 | "begin globbing './fixtures/file.txt'...",
339 | "created snapshot for './fixtures/file.txt'",
340 | "creating snapshot for './fixtures/file.txt'...",
341 | "determined './fixtures/file.txt' is a file",
342 | "determined that './fixtures/file.txt' should write to 'newFile.txt'",
343 | "finished to adding additional assets",
344 | "finished to process a pattern from 'file.txt' using './fixtures' context",
345 | "found './fixtures/file.txt'",
346 | "getting cache for './fixtures/file.txt'...",
347 | "getting stats for './fixtures/file.txt'...",
348 | "missed cache for './fixtures/file.txt'",
349 | "read './fixtures/file.txt'",
350 | "reading './fixtures/file.txt'...",
351 | "starting to add additional assets...",
352 | "starting to process a pattern from 'file.txt' using './fixtures' context",
353 | "stored cache for './fixtures/file.txt'",
354 | "storing cache for './fixtures/file.txt'...",
355 | "writing 'newFile.txt' from './fixtures/file.txt' to compilation assets...",
356 | "written 'newFile.txt' from './fixtures/file.txt' to compilation assets",
357 | ],
358 | }
359 | `;
360 |
361 | exports[`CopyPlugin stats should work have assets info: assets 1`] = `
362 | {
363 | ".dottedfile": "dottedfile contents
364 | ",
365 | "asset-modules/deepnested.txt": "",
366 | "directoryfile.txt": "new",
367 | "nested/deep-nested/deepnested.txt": "",
368 | "nested/nestedfile.txt": "",
369 | }
370 | `;
371 |
372 | exports[`CopyPlugin stats should work have assets info: assets info 1`] = `
373 | [
374 | {
375 | "info": {
376 | "copied": true,
377 | "immutable": undefined,
378 | "sourceFilename": "directory/.dottedfile",
379 | },
380 | "name": ".dottedfile",
381 | },
382 | {
383 | "info": {
384 | "copied": undefined,
385 | "immutable": undefined,
386 | "sourceFilename": "directory/nested/deep-nested/deepnested.txt",
387 | },
388 | "name": "asset-modules/deepnested.txt",
389 | },
390 | {
391 | "info": {
392 | "copied": true,
393 | "immutable": undefined,
394 | "sourceFilename": "directory/directoryfile.txt",
395 | },
396 | "name": "directoryfile.txt",
397 | },
398 | {
399 | "info": {
400 | "copied": undefined,
401 | "immutable": undefined,
402 | "sourceFilename": undefined,
403 | },
404 | "name": "main.js",
405 | },
406 | {
407 | "info": {
408 | "copied": true,
409 | "immutable": undefined,
410 | "sourceFilename": "directory/nested/deep-nested/deepnested.txt",
411 | },
412 | "name": "nested/deep-nested/deepnested.txt",
413 | },
414 | {
415 | "info": {
416 | "copied": true,
417 | "immutable": undefined,
418 | "sourceFilename": "directory/nested/nestedfile.txt",
419 | },
420 | "name": "nested/nestedfile.txt",
421 | },
422 | ]
423 | `;
424 |
425 | exports[`CopyPlugin stats should work have assets info: errors 1`] = `[]`;
426 |
427 | exports[`CopyPlugin stats should work have assets info: warnings 1`] = `[]`;
428 |
--------------------------------------------------------------------------------
/test/__snapshots__/transformAll-option.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`cache should work with the "memory" cache: assets 1`] = `
4 | {
5 | "file.txt": "new::directory/nested/deep-nested/deepnested.txt::directory/nested/nestedfile.txt::",
6 | }
7 | `;
8 |
9 | exports[`cache should work with the "memory" cache: assets 2`] = `
10 | {
11 | "file.txt": "new::directory/nested/deep-nested/deepnested.txt::directory/nested/nestedfile.txt::",
12 | }
13 | `;
14 |
15 | exports[`cache should work with the "memory" cache: errors 1`] = `[]`;
16 |
17 | exports[`cache should work with the "memory" cache: errors 2`] = `[]`;
18 |
19 | exports[`cache should work with the "memory" cache: warnings 1`] = `[]`;
20 |
21 | exports[`cache should work with the "memory" cache: warnings 2`] = `[]`;
22 |
--------------------------------------------------------------------------------
/test/__snapshots__/validate-options.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`validate options should throw an error on the "options" option with "{"concurrency":true}" value 1`] = `
4 | "Invalid options object. Copy Plugin has been initialized using an options object that does not match the API schema.
5 | - options.options.concurrency should be a number.
6 | -> Limits the number of simultaneous requests to fs.
7 | -> Read more at https://github.com/webpack-contrib/copy-webpack-plugin#concurrency"
8 | `;
9 |
10 | exports[`validate options should throw an error on the "options" option with "{"unknown":true}" value 1`] = `
11 | "Invalid options object. Copy Plugin has been initialized using an options object that does not match the API schema.
12 | - options.options has an unknown property 'unknown'. These properties are valid:
13 | object { concurrency? }"
14 | `;
15 |
16 | exports[`validate options should throw an error on the "patterns" option with "" value 1`] = `
17 | "Invalid options object. Copy Plugin has been initialized using an options object that does not match the API schema.
18 | - options.patterns should be an array:
19 | [non-empty string | object { from, to?, context?, globOptions?, filter?, transformAll?, toType?, force?, priority?, info?, transform?, noErrorOnMissing? }, ...] (should not have fewer than 1 item)"
20 | `;
21 |
22 | exports[`validate options should throw an error on the "patterns" option with "[""]" value 1`] = `
23 | "Invalid options object. Copy Plugin has been initialized using an options object that does not match the API schema.
24 | - options.patterns[0] should be a non-empty string."
25 | `;
26 |
27 | exports[`validate options should throw an error on the "patterns" option with "[]" value 1`] = `
28 | "Invalid options object. Copy Plugin has been initialized using an options object that does not match the API schema.
29 | - options.patterns should be a non-empty array."
30 | `;
31 |
32 | exports[`validate options should throw an error on the "patterns" option with "[{"from":"","to":"dir","context":"context","noErrorOnMissing":"true"}]" value 1`] = `
33 | "Invalid options object. Copy Plugin has been initialized using an options object that does not match the API schema.
34 | - options.patterns[0].noErrorOnMissing should be a boolean.
35 | -> Doesn't generate an error on missing file(s).
36 | -> Read more at https://github.com/webpack-contrib/copy-webpack-plugin#noerroronmissing"
37 | `;
38 |
39 | exports[`validate options should throw an error on the "patterns" option with "[{"from":"","to":"dir","context":"context"}]" value 1`] = `
40 | "Invalid options object. Copy Plugin has been initialized using an options object that does not match the API schema.
41 | - options.patterns[0].from should be a non-empty string.
42 | -> Glob or path from where we copy files.
43 | -> Read more at https://github.com/webpack-contrib/copy-webpack-plugin#from"
44 | `;
45 |
46 | exports[`validate options should throw an error on the "patterns" option with "[{"from":"dir","info":"string"}]" value 1`] = `
47 | "Invalid options object. Copy Plugin has been initialized using an options object that does not match the API schema.
48 | - options.patterns[0] should be one of these:
49 | non-empty string | object { from, to?, context?, globOptions?, filter?, transformAll?, toType?, force?, priority?, info?, transform?, noErrorOnMissing? }
50 | Details:
51 | * options.patterns[0].info should be one of these:
52 | object { … } | function
53 | -> Allows to add assets info.
54 | -> Read more at https://github.com/webpack-contrib/copy-webpack-plugin#info
55 | Details:
56 | * options.patterns[0].info should be an object:
57 | object { … }
58 | * options.patterns[0].info should be an instance of function."
59 | `;
60 |
61 | exports[`validate options should throw an error on the "patterns" option with "[{"from":"dir","info":true}]" value 1`] = `
62 | "Invalid options object. Copy Plugin has been initialized using an options object that does not match the API schema.
63 | - options.patterns[0] should be one of these:
64 | non-empty string | object { from, to?, context?, globOptions?, filter?, transformAll?, toType?, force?, priority?, info?, transform?, noErrorOnMissing? }
65 | Details:
66 | * options.patterns[0].info should be one of these:
67 | object { … } | function
68 | -> Allows to add assets info.
69 | -> Read more at https://github.com/webpack-contrib/copy-webpack-plugin#info
70 | Details:
71 | * options.patterns[0].info should be an object:
72 | object { … }
73 | * options.patterns[0].info should be an instance of function."
74 | `;
75 |
76 | exports[`validate options should throw an error on the "patterns" option with "[{"from":"test.txt","filter":"test"}]" value 1`] = `
77 | "Invalid options object. Copy Plugin has been initialized using an options object that does not match the API schema.
78 | - options.patterns[0].filter should be an instance of function.
79 | -> Allows to filter copied assets.
80 | -> Read more at https://github.com/webpack-contrib/copy-webpack-plugin#filter"
81 | `;
82 |
83 | exports[`validate options should throw an error on the "patterns" option with "[{"from":"test.txt","to":"dir","context":"context","force":"true"}]" value 1`] = `
84 | "Invalid options object. Copy Plugin has been initialized using an options object that does not match the API schema.
85 | - options.patterns[0].force should be a boolean.
86 | -> Overwrites files already in 'compilation.assets' (usually added by other plugins/loaders).
87 | -> Read more at https://github.com/webpack-contrib/copy-webpack-plugin#force"
88 | `;
89 |
90 | exports[`validate options should throw an error on the "patterns" option with "[{"from":"test.txt","to":"dir","context":"context","toType":"foo"}]" value 1`] = `
91 | "Invalid options object. Copy Plugin has been initialized using an options object that does not match the API schema.
92 | - options.patterns[0].toType should be one of these:
93 | "dir" | "file" | "template"
94 | -> Determinate what is to option - directory, file or template.
95 | -> Read more at https://github.com/webpack-contrib/copy-webpack-plugin#totype"
96 | `;
97 |
98 | exports[`validate options should throw an error on the "patterns" option with "[{"from":"test.txt","to":"dir","context":"context","transform":{"foo":"bar"}}]" value 1`] = `
99 | "Invalid options object. Copy Plugin has been initialized using an options object that does not match the API schema.
100 | - options.patterns[0].transform has an unknown property 'foo'. These properties are valid:
101 | object { transformer?, cache? }"
102 | `;
103 |
104 | exports[`validate options should throw an error on the "patterns" option with "[{"from":"test.txt","to":"dir","context":"context","transform":true}]" value 1`] = `
105 | "Invalid options object. Copy Plugin has been initialized using an options object that does not match the API schema.
106 | - options.patterns[0] should be one of these:
107 | non-empty string | object { from, to?, context?, globOptions?, filter?, transformAll?, toType?, force?, priority?, info?, transform?, noErrorOnMissing? }
108 | Details:
109 | * options.patterns[0].transform should be one of these:
110 | function | object { transformer?, cache? }
111 | -> Allows to modify the file contents.
112 | -> Read more at https://github.com/webpack-contrib/copy-webpack-plugin#transform
113 | Details:
114 | * options.patterns[0].transform should be an instance of function.
115 | * options.patterns[0].transform should be an object:
116 | object { transformer?, cache? }"
117 | `;
118 |
119 | exports[`validate options should throw an error on the "patterns" option with "[{"from":"test.txt","to":"dir","context":"context","transformAll":true}]" value 1`] = `
120 | "Invalid options object. Copy Plugin has been initialized using an options object that does not match the API schema.
121 | - options.patterns[0].transformAll should be an instance of function.
122 | -> Allows you to modify the contents of multiple files and save the result to one file.
123 | -> Read more at https://github.com/webpack-contrib/copy-webpack-plugin#transformall"
124 | `;
125 |
126 | exports[`validate options should throw an error on the "patterns" option with "[{"from":"test.txt","to":"dir","context":true}]" value 1`] = `
127 | "Invalid options object. Copy Plugin has been initialized using an options object that does not match the API schema.
128 | - options.patterns[0].context should be a string.
129 | -> A path that determines how to interpret the 'from' path.
130 | -> Read more at https://github.com/webpack-contrib/copy-webpack-plugin#context"
131 | `;
132 |
133 | exports[`validate options should throw an error on the "patterns" option with "[{"from":"test.txt","to":"dir","priority":"5"}]" value 1`] = `
134 | "Invalid options object. Copy Plugin has been initialized using an options object that does not match the API schema.
135 | - options.patterns[0].priority should be a number.
136 | -> Allows to specify the priority of copying files with the same destination name.
137 | -> Read more at https://github.com/webpack-contrib/copy-webpack-plugin#priority"
138 | `;
139 |
140 | exports[`validate options should throw an error on the "patterns" option with "[{"from":"test.txt","to":"dir","priority":true}]" value 1`] = `
141 | "Invalid options object. Copy Plugin has been initialized using an options object that does not match the API schema.
142 | - options.patterns[0].priority should be a number.
143 | -> Allows to specify the priority of copying files with the same destination name.
144 | -> Read more at https://github.com/webpack-contrib/copy-webpack-plugin#priority"
145 | `;
146 |
147 | exports[`validate options should throw an error on the "patterns" option with "[{"from":"test.txt","to":"dir"}]" value 1`] = `
148 | "Invalid options object. Copy Plugin has been initialized using an options object that does not match the API schema.
149 | - options.patterns[0].priority should be a number.
150 | -> Allows to specify the priority of copying files with the same destination name.
151 | -> Read more at https://github.com/webpack-contrib/copy-webpack-plugin#priority"
152 | `;
153 |
154 | exports[`validate options should throw an error on the "patterns" option with "[{"from":"test.txt","to":true,"context":"context"}]" value 1`] = `
155 | "Invalid options object. Copy Plugin has been initialized using an options object that does not match the API schema.
156 | - options.patterns[0] should be one of these:
157 | non-empty string | object { from, to?, context?, globOptions?, filter?, transformAll?, toType?, force?, priority?, info?, transform?, noErrorOnMissing? }
158 | Details:
159 | * options.patterns[0].to should be one of these:
160 | string | function
161 | -> Output path.
162 | -> Read more at https://github.com/webpack-contrib/copy-webpack-plugin#to
163 | Details:
164 | * options.patterns[0].to should be a string.
165 | * options.patterns[0].to should be an instance of function."
166 | `;
167 |
168 | exports[`validate options should throw an error on the "patterns" option with "[{"from":{"glob":"**/*","dot":false},"to":"dir","context":"context"}]" value 1`] = `
169 | "Invalid options object. Copy Plugin has been initialized using an options object that does not match the API schema.
170 | - options.patterns[0].from should be a non-empty string.
171 | -> Glob or path from where we copy files.
172 | -> Read more at https://github.com/webpack-contrib/copy-webpack-plugin#from"
173 | `;
174 |
175 | exports[`validate options should throw an error on the "patterns" option with "[{"from":true,"to":"dir","context":"context"}]" value 1`] = `
176 | "Invalid options object. Copy Plugin has been initialized using an options object that does not match the API schema.
177 | - options.patterns[0].from should be a non-empty string.
178 | -> Glob or path from where we copy files.
179 | -> Read more at https://github.com/webpack-contrib/copy-webpack-plugin#from"
180 | `;
181 |
182 | exports[`validate options should throw an error on the "patterns" option with "[{}]" value 1`] = `
183 | "Invalid options object. Copy Plugin has been initialized using an options object that does not match the API schema.
184 | - options.patterns[0] misses the property 'from'. Should be:
185 | non-empty string
186 | -> Glob or path from where we copy files.
187 | -> Read more at https://github.com/webpack-contrib/copy-webpack-plugin#from"
188 | `;
189 |
190 | exports[`validate options should throw an error on the "patterns" option with "{}" value 1`] = `
191 | "Invalid options object. Copy Plugin has been initialized using an options object that does not match the API schema.
192 | - options.patterns should be an array:
193 | [non-empty string | object { from, to?, context?, globOptions?, filter?, transformAll?, toType?, force?, priority?, info?, transform?, noErrorOnMissing? }, ...] (should not have fewer than 1 item)"
194 | `;
195 |
196 | exports[`validate options should throw an error on the "patterns" option with "true" value 1`] = `
197 | "Invalid options object. Copy Plugin has been initialized using an options object that does not match the API schema.
198 | - options.patterns should be an array:
199 | [non-empty string | object { from, to?, context?, globOptions?, filter?, transformAll?, toType?, force?, priority?, info?, transform?, noErrorOnMissing? }, ...] (should not have fewer than 1 item)"
200 | `;
201 |
202 | exports[`validate options should throw an error on the "patterns" option with "true" value 2`] = `
203 | "Invalid options object. Copy Plugin has been initialized using an options object that does not match the API schema.
204 | - options.patterns should be an array:
205 | [non-empty string | object { from, to?, context?, globOptions?, filter?, transformAll?, toType?, force?, priority?, info?, transform?, noErrorOnMissing? }, ...] (should not have fewer than 1 item)"
206 | `;
207 |
208 | exports[`validate options should throw an error on the "patterns" option with "undefined" value 1`] = `
209 | "Invalid options object. Copy Plugin has been initialized using an options object that does not match the API schema.
210 | - options misses the property 'patterns'. Should be:
211 | [non-empty string | object { from, to?, context?, globOptions?, filter?, transformAll?, toType?, force?, priority?, info?, transform?, noErrorOnMissing? }, ...] (should not have fewer than 1 item)"
212 | `;
213 |
214 | exports[`validate options should throw an error on the "unknown" option with "/test/" value 1`] = `
215 | "Invalid options object. Copy Plugin has been initialized using an options object that does not match the API schema.
216 | - options has an unknown property 'unknown'. These properties are valid:
217 | object { patterns, options? }"
218 | `;
219 |
220 | exports[`validate options should throw an error on the "unknown" option with "[]" value 1`] = `
221 | "Invalid options object. Copy Plugin has been initialized using an options object that does not match the API schema.
222 | - options has an unknown property 'unknown'. These properties are valid:
223 | object { patterns, options? }"
224 | `;
225 |
226 | exports[`validate options should throw an error on the "unknown" option with "{"foo":"bar"}" value 1`] = `
227 | "Invalid options object. Copy Plugin has been initialized using an options object that does not match the API schema.
228 | - options has an unknown property 'unknown'. These properties are valid:
229 | object { patterns, options? }"
230 | `;
231 |
232 | exports[`validate options should throw an error on the "unknown" option with "{}" value 1`] = `
233 | "Invalid options object. Copy Plugin has been initialized using an options object that does not match the API schema.
234 | - options has an unknown property 'unknown'. These properties are valid:
235 | object { patterns, options? }"
236 | `;
237 |
238 | exports[`validate options should throw an error on the "unknown" option with "1" value 1`] = `
239 | "Invalid options object. Copy Plugin has been initialized using an options object that does not match the API schema.
240 | - options has an unknown property 'unknown'. These properties are valid:
241 | object { patterns, options? }"
242 | `;
243 |
244 | exports[`validate options should throw an error on the "unknown" option with "false" value 1`] = `
245 | "Invalid options object. Copy Plugin has been initialized using an options object that does not match the API schema.
246 | - options has an unknown property 'unknown'. These properties are valid:
247 | object { patterns, options? }"
248 | `;
249 |
250 | exports[`validate options should throw an error on the "unknown" option with "test" value 1`] = `
251 | "Invalid options object. Copy Plugin has been initialized using an options object that does not match the API schema.
252 | - options has an unknown property 'unknown'. These properties are valid:
253 | object { patterns, options? }"
254 | `;
255 |
256 | exports[`validate options should throw an error on the "unknown" option with "true" value 1`] = `
257 | "Invalid options object. Copy Plugin has been initialized using an options object that does not match the API schema.
258 | - options has an unknown property 'unknown'. These properties are valid:
259 | object { patterns, options? }"
260 | `;
261 |
--------------------------------------------------------------------------------
/test/context-option.test.js:
--------------------------------------------------------------------------------
1 | import path from "path";
2 |
3 | import { runEmit } from "./helpers/run";
4 |
5 | const FIXTURES_DIR = path.join(__dirname, "fixtures");
6 |
7 | describe("context option", () => {
8 | it('should work when "from" is a file and "context" is a relative path', (done) => {
9 | runEmit({
10 | expectedAssetKeys: ["directoryfile.txt"],
11 | patterns: [
12 | {
13 | from: "directoryfile.txt",
14 | context: "directory",
15 | },
16 | ],
17 | })
18 | .then(done)
19 | .catch(done);
20 | });
21 |
22 | it('should work when "from" is a directory and "context" is a relative path', (done) => {
23 | runEmit({
24 | expectedAssetKeys: ["deep-nested/deepnested.txt", "nestedfile.txt"],
25 | patterns: [
26 | {
27 | from: "nested",
28 | context: "directory",
29 | },
30 | ],
31 | })
32 | .then(done)
33 | .catch(done);
34 | });
35 |
36 | it('should work when "from" is a glob and "context" is a relative path', (done) => {
37 | runEmit({
38 | expectedAssetKeys: [
39 | "nested/deep-nested/deepnested.txt",
40 | "nested/nestedfile.txt",
41 | ],
42 | patterns: [
43 | {
44 | from: "nested/**/*",
45 | context: "directory",
46 | },
47 | ],
48 | })
49 | .then(done)
50 | .catch(done);
51 | });
52 |
53 | it('should work when "from" is a file and "context" is an absolute path', (done) => {
54 | runEmit({
55 | expectedAssetKeys: ["directoryfile.txt"],
56 | patterns: [
57 | {
58 | from: "directoryfile.txt",
59 | context: path.join(FIXTURES_DIR, "directory"),
60 | },
61 | ],
62 | })
63 | .then(done)
64 | .catch(done);
65 | });
66 |
67 | it('should work when "from" is a directory and "context" is an absolute path', (done) => {
68 | runEmit({
69 | expectedAssetKeys: ["deep-nested/deepnested.txt", "nestedfile.txt"],
70 | patterns: [
71 | {
72 | from: "nested",
73 | context: path.join(FIXTURES_DIR, "directory"),
74 | },
75 | ],
76 | })
77 | .then(done)
78 | .catch(done);
79 | });
80 |
81 | it('should work when "from" is a glob and "context" is an absolute path', (done) => {
82 | runEmit({
83 | expectedAssetKeys: [
84 | "nested/deep-nested/deepnested.txt",
85 | "nested/nestedfile.txt",
86 | ],
87 | patterns: [
88 | {
89 | from: "nested/**/*",
90 | context: path.join(FIXTURES_DIR, "directory"),
91 | },
92 | ],
93 | })
94 | .then(done)
95 | .catch(done);
96 | });
97 |
98 | it('should work when "from" is a file and "context" with special characters', (done) => {
99 | runEmit({
100 | expectedAssetKeys: ["directoryfile.txt"],
101 | patterns: [
102 | {
103 | from: "directoryfile.txt",
104 | context: "[special$directory]",
105 | },
106 | ],
107 | })
108 | .then(done)
109 | .catch(done);
110 | });
111 |
112 | it('should work when "from" is a directory and "context" with special characters', (done) => {
113 | runEmit({
114 | expectedAssetKeys: [
115 | "directoryfile.txt",
116 | "(special-*file).txt",
117 | "nested/nestedfile.txt",
118 | ],
119 | patterns: [
120 | {
121 | // Todo strange behavour when you use `FIXTURES_DIR`, need investigate for next major release
122 | from: ".",
123 | context: "[special$directory]",
124 | },
125 | ],
126 | })
127 | .then(done)
128 | .catch(done);
129 | });
130 |
131 | it('should work when "from" is a glob and "context" with special characters', (done) => {
132 | runEmit({
133 | expectedAssetKeys: [
134 | "directoryfile.txt",
135 | "(special-*file).txt",
136 | "nested/nestedfile.txt",
137 | ],
138 | patterns: [
139 | {
140 | from: "**/*",
141 | context: "[special$directory]",
142 | },
143 | ],
144 | })
145 | .then(done)
146 | .catch(done);
147 | });
148 |
149 | it('should work when "from" is a glob and "context" with special characters #2', (done) => {
150 | runEmit({
151 | expectedAssetKeys: ["(special-*file).txt"],
152 | patterns: [
153 | {
154 | from: "\\(special-*file\\).txt",
155 | context: "[special$directory]",
156 | },
157 | ],
158 | })
159 | .then(done)
160 | .catch(done);
161 | });
162 |
163 | it('should work when "from" is a file and "to" is a directory', (done) => {
164 | runEmit({
165 | expectedAssetKeys: ["newdirectory/directoryfile.txt"],
166 | patterns: [
167 | {
168 | context: "directory",
169 | from: "directoryfile.txt",
170 | to: "newdirectory",
171 | },
172 | ],
173 | })
174 | .then(done)
175 | .catch(done);
176 | });
177 |
178 | it('should work when "from" is a directory and "to" is a directory', (done) => {
179 | runEmit({
180 | expectedAssetKeys: [
181 | "newdirectory/deep-nested/deepnested.txt",
182 | "newdirectory/nestedfile.txt",
183 | ],
184 | patterns: [
185 | {
186 | context: "directory",
187 | from: "nested",
188 | to: "newdirectory",
189 | },
190 | ],
191 | })
192 | .then(done)
193 | .catch(done);
194 | });
195 |
196 | it('should work when "from" is a glob and "to" is a directory', (done) => {
197 | runEmit({
198 | expectedAssetKeys: [
199 | "nested/directoryfile.txt",
200 | "nested/nested/deep-nested/deepnested.txt",
201 | "nested/nested/nestedfile.txt",
202 | ],
203 | patterns: [
204 | {
205 | context: "directory",
206 | from: "**/*",
207 | to: "nested",
208 | },
209 | ],
210 | })
211 | .then(done)
212 | .catch(done);
213 | });
214 | });
215 |
--------------------------------------------------------------------------------
/test/filter-option.test.js:
--------------------------------------------------------------------------------
1 | import fs from "fs";
2 |
3 | import { runEmit } from "./helpers/run";
4 |
5 | describe('"filter" option', () => {
6 | it("should work, copy files and filter some of them", (done) => {
7 | runEmit({
8 | expectedAssetKeys: [
9 | ".dottedfile",
10 | "nested/deep-nested/deepnested.txt",
11 | "nested/nestedfile.txt",
12 | ],
13 | patterns: [
14 | {
15 | from: "directory",
16 | filter: (resourcePath) => {
17 | if (/directoryfile\.txt$/.test(resourcePath)) {
18 | return false;
19 | }
20 |
21 | return true;
22 | },
23 | },
24 | ],
25 | })
26 | .then(done)
27 | .catch(done);
28 | });
29 |
30 | it("should work, copy files and filter some of them using async function", (done) => {
31 | runEmit({
32 | expectedAssetKeys: [
33 | ".dottedfile",
34 | "nested/deep-nested/deepnested.txt",
35 | "nested/nestedfile.txt",
36 | ],
37 | patterns: [
38 | {
39 | from: "directory",
40 | filter: async (resourcePath) => {
41 | const data = await fs.promises.readFile(resourcePath);
42 | const content = data.toString();
43 |
44 | if (content === "new") {
45 | return false;
46 | }
47 |
48 | return true;
49 | },
50 | },
51 | ],
52 | })
53 | .then(done)
54 | .catch(done);
55 | });
56 | });
57 |
--------------------------------------------------------------------------------
/test/fixtures/.file.txt:
--------------------------------------------------------------------------------
1 | dot
2 |
--------------------------------------------------------------------------------
/test/fixtures/[(){}[]!+@escaped-test^$]/hello.txt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webpack-contrib/copy-webpack-plugin/6a855e2b0af93374dfa2716eb6cee82a2416b3e1/test/fixtures/[(){}[]!+@escaped-test^$]/hello.txt
--------------------------------------------------------------------------------
/test/fixtures/binextension.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webpack-contrib/copy-webpack-plugin/6a855e2b0af93374dfa2716eb6cee82a2416b3e1/test/fixtures/binextension.bin
--------------------------------------------------------------------------------
/test/fixtures/dir (86)/file.txt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webpack-contrib/copy-webpack-plugin/6a855e2b0af93374dfa2716eb6cee82a2416b3e1/test/fixtures/dir (86)/file.txt
--------------------------------------------------------------------------------
/test/fixtures/dir (86)/nesteddir/deepnesteddir/deepnesteddir.txt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webpack-contrib/copy-webpack-plugin/6a855e2b0af93374dfa2716eb6cee82a2416b3e1/test/fixtures/dir (86)/nesteddir/deepnesteddir/deepnesteddir.txt
--------------------------------------------------------------------------------
/test/fixtures/dir (86)/nesteddir/nestedfile.txt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webpack-contrib/copy-webpack-plugin/6a855e2b0af93374dfa2716eb6cee82a2416b3e1/test/fixtures/dir (86)/nesteddir/nestedfile.txt
--------------------------------------------------------------------------------
/test/fixtures/directory/.dottedfile:
--------------------------------------------------------------------------------
1 | dottedfile contents
2 |
--------------------------------------------------------------------------------
/test/fixtures/directory/directoryfile.txt:
--------------------------------------------------------------------------------
1 | new
--------------------------------------------------------------------------------
/test/fixtures/directory/nested/deep-nested/deepnested.txt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webpack-contrib/copy-webpack-plugin/6a855e2b0af93374dfa2716eb6cee82a2416b3e1/test/fixtures/directory/nested/deep-nested/deepnested.txt
--------------------------------------------------------------------------------
/test/fixtures/directory/nested/nestedfile.txt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webpack-contrib/copy-webpack-plugin/6a855e2b0af93374dfa2716eb6cee82a2416b3e1/test/fixtures/directory/nested/nestedfile.txt
--------------------------------------------------------------------------------
/test/fixtures/file.txt:
--------------------------------------------------------------------------------
1 | new
--------------------------------------------------------------------------------
/test/fixtures/file.txt.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webpack-contrib/copy-webpack-plugin/6a855e2b0af93374dfa2716eb6cee82a2416b3e1/test/fixtures/file.txt.gz
--------------------------------------------------------------------------------
/test/fixtures/noextension:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webpack-contrib/copy-webpack-plugin/6a855e2b0af93374dfa2716eb6cee82a2416b3e1/test/fixtures/noextension
--------------------------------------------------------------------------------
/test/fixtures/symlink/directory-ln:
--------------------------------------------------------------------------------
1 | ./directory/
--------------------------------------------------------------------------------
/test/fixtures/symlink/directory/file.txt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webpack-contrib/copy-webpack-plugin/6a855e2b0af93374dfa2716eb6cee82a2416b3e1/test/fixtures/symlink/directory/file.txt
--------------------------------------------------------------------------------
/test/fixtures/symlink/directory/nested-directory/file-in-nested-directory.txt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webpack-contrib/copy-webpack-plugin/6a855e2b0af93374dfa2716eb6cee82a2416b3e1/test/fixtures/symlink/directory/nested-directory/file-in-nested-directory.txt
--------------------------------------------------------------------------------
/test/fixtures/symlink/file-ln.txt:
--------------------------------------------------------------------------------
1 | file.txt
--------------------------------------------------------------------------------
/test/fixtures/symlink/file.txt:
--------------------------------------------------------------------------------
1 | data
2 |
--------------------------------------------------------------------------------
/test/fixtures/watch/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webpack-contrib/copy-webpack-plugin/6a855e2b0af93374dfa2716eb6cee82a2416b3e1/test/fixtures/watch/.gitkeep
--------------------------------------------------------------------------------
/test/fixtures/watch/_t1/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webpack-contrib/copy-webpack-plugin/6a855e2b0af93374dfa2716eb6cee82a2416b3e1/test/fixtures/watch/_t1/.gitkeep
--------------------------------------------------------------------------------
/test/fixtures/watch/_t1/directory/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webpack-contrib/copy-webpack-plugin/6a855e2b0af93374dfa2716eb6cee82a2416b3e1/test/fixtures/watch/_t1/directory/.gitkeep
--------------------------------------------------------------------------------
/test/fixtures/watch/_t2/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webpack-contrib/copy-webpack-plugin/6a855e2b0af93374dfa2716eb6cee82a2416b3e1/test/fixtures/watch/_t2/.gitkeep
--------------------------------------------------------------------------------
/test/fixtures/watch/_t2/directory/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webpack-contrib/copy-webpack-plugin/6a855e2b0af93374dfa2716eb6cee82a2416b3e1/test/fixtures/watch/_t2/directory/.gitkeep
--------------------------------------------------------------------------------
/test/fixtures/watch/_t3/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webpack-contrib/copy-webpack-plugin/6a855e2b0af93374dfa2716eb6cee82a2416b3e1/test/fixtures/watch/_t3/.gitkeep
--------------------------------------------------------------------------------
/test/fixtures/watch/_t3/directory/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webpack-contrib/copy-webpack-plugin/6a855e2b0af93374dfa2716eb6cee82a2416b3e1/test/fixtures/watch/_t3/directory/.gitkeep
--------------------------------------------------------------------------------
/test/fixtures/watch/_t4/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webpack-contrib/copy-webpack-plugin/6a855e2b0af93374dfa2716eb6cee82a2416b3e1/test/fixtures/watch/_t4/.gitkeep
--------------------------------------------------------------------------------
/test/fixtures/watch/_t4/directory/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webpack-contrib/copy-webpack-plugin/6a855e2b0af93374dfa2716eb6cee82a2416b3e1/test/fixtures/watch/_t4/directory/.gitkeep
--------------------------------------------------------------------------------
/test/fixtures/watch/_t5/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webpack-contrib/copy-webpack-plugin/6a855e2b0af93374dfa2716eb6cee82a2416b3e1/test/fixtures/watch/_t5/.gitkeep
--------------------------------------------------------------------------------
/test/fixtures/watch/_t5/directory/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webpack-contrib/copy-webpack-plugin/6a855e2b0af93374dfa2716eb6cee82a2416b3e1/test/fixtures/watch/_t5/directory/.gitkeep
--------------------------------------------------------------------------------
/test/fixtures/watch/directory/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webpack-contrib/copy-webpack-plugin/6a855e2b0af93374dfa2716eb6cee82a2416b3e1/test/fixtures/watch/directory/.gitkeep
--------------------------------------------------------------------------------
/test/force-option.test.js:
--------------------------------------------------------------------------------
1 | import { runForce } from "./helpers/run";
2 |
3 | describe("force option", () => {
4 | describe("is not specified", () => {
5 | it('should not overwrite a file already in the compilation by default when "from" is a file', (done) => {
6 | runForce({
7 | additionalAssets: [{ name: "file.txt", data: "existing" }],
8 | expectedAssetKeys: ["file.txt"],
9 | expectedAssetContent: {
10 | "file.txt": "existing",
11 | },
12 | patterns: [
13 | {
14 | from: "file.txt",
15 | },
16 | ],
17 | })
18 | .then(done)
19 | .catch(done);
20 | });
21 |
22 | it('should not overwrite files already in the compilation when "from" is a directory', (done) => {
23 | runForce({
24 | additionalAssets: [
25 | { name: ".dottedfile", data: "existing" },
26 | { name: "directoryfile.txt", data: "existing" },
27 | { name: "nested/deep-nested/deepnested.txt", data: "existing" },
28 | { name: "nested/nestedfile.txt", data: "existing" },
29 | ],
30 | expectedAssetKeys: [
31 | ".dottedfile",
32 | "directoryfile.txt",
33 | "nested/deep-nested/deepnested.txt",
34 | "nested/nestedfile.txt",
35 | ],
36 | expectedAssetContent: {
37 | ".dottedfile": "existing",
38 | "nested/deep-nested/deepnested.txt": "existing",
39 | "nested/nestedfile.txt": "existing",
40 | "directoryfile.txt": "existing",
41 | },
42 | patterns: [
43 | {
44 | from: "directory",
45 | },
46 | ],
47 | })
48 | .then(done)
49 | .catch(done);
50 | });
51 |
52 | it('should not overwrite files already in the compilation when "from" is a glob', (done) => {
53 | runForce({
54 | additionalAssets: [
55 | { name: "directory/directoryfile.txt", data: "existing" },
56 | {
57 | name: "directory/nested/deep-nested/deepnested.txt",
58 | data: "existing",
59 | },
60 | { name: "directory/nested/nestedfile.txt", data: "existing" },
61 | ],
62 | expectedAssetKeys: [
63 | "directory/directoryfile.txt",
64 | "directory/nested/deep-nested/deepnested.txt",
65 | "directory/nested/nestedfile.txt",
66 | ],
67 | expectedAssetContent: {
68 | "directory/nested/deep-nested/deepnested.txt": "existing",
69 | "directory/nested/nestedfile.txt": "existing",
70 | "directory/directoryfile.txt": "existing",
71 | },
72 | patterns: [
73 | {
74 | from: "directory/**/*",
75 | },
76 | ],
77 | })
78 | .then(done)
79 | .catch(done);
80 | });
81 | });
82 |
83 | describe('is "false" (Boolean)', () => {
84 | it('should not overwrite a file already in the compilation by default when "from" is a file', (done) => {
85 | runForce({
86 | additionalAssets: [{ name: "file.txt", data: "existing" }],
87 | expectedAssetKeys: ["file.txt"],
88 | expectedAssetContent: {
89 | "file.txt": "existing",
90 | },
91 | patterns: [
92 | {
93 | force: false,
94 | from: "file.txt",
95 | },
96 | ],
97 | })
98 | .then(done)
99 | .catch(done);
100 | });
101 |
102 | it('should not overwrite files already in the compilation when "from" is a directory', (done) => {
103 | runForce({
104 | additionalAssets: [
105 | { name: ".dottedfile", data: "existing" },
106 | { name: "directoryfile.txt", data: "existing" },
107 | { name: "nested/deep-nested/deepnested.txt", data: "existing" },
108 | { name: "nested/nestedfile.txt", data: "existing" },
109 | ],
110 | expectedAssetKeys: [
111 | ".dottedfile",
112 | "directoryfile.txt",
113 | "nested/deep-nested/deepnested.txt",
114 | "nested/nestedfile.txt",
115 | ],
116 | expectedAssetContent: {
117 | ".dottedfile": "existing",
118 | "nested/deep-nested/deepnested.txt": "existing",
119 | "nested/nestedfile.txt": "existing",
120 | "directoryfile.txt": "existing",
121 | },
122 | patterns: [
123 | {
124 | force: false,
125 | from: "directory",
126 | },
127 | ],
128 | })
129 | .then(done)
130 | .catch(done);
131 | });
132 |
133 | it('should not overwrite files already in the compilation when "from" is a glob', (done) => {
134 | runForce({
135 | additionalAssets: [
136 | { name: "directory/directoryfile.txt", data: "existing" },
137 | {
138 | name: "directory/nested/deep-nested/deepnested.txt",
139 | data: "existing",
140 | },
141 | { name: "directory/nested/nestedfile.txt", data: "existing" },
142 | ],
143 | expectedAssetKeys: [
144 | "directory/directoryfile.txt",
145 | "directory/nested/deep-nested/deepnested.txt",
146 | "directory/nested/nestedfile.txt",
147 | ],
148 | expectedAssetContent: {
149 | "directory/nested/deep-nested/deepnested.txt": "existing",
150 | "directory/nested/nestedfile.txt": "existing",
151 | "directory/directoryfile.txt": "existing",
152 | },
153 | patterns: [
154 | {
155 | force: false,
156 | from: "directory/**/*",
157 | },
158 | ],
159 | })
160 | .then(done)
161 | .catch(done);
162 | });
163 | });
164 |
165 | describe('is "true" (Boolean)', () => {
166 | it('should force overwrite a file already in the compilation when "from" is a file', (done) => {
167 | runForce({
168 | additionalAssets: [{ name: "file.txt", data: "existing" }],
169 | expectedAssetKeys: ["file.txt"],
170 | expectedAssetContent: {
171 | "file.txt": "new",
172 | },
173 | patterns: [
174 | {
175 | force: true,
176 | from: "file.txt",
177 | },
178 | ],
179 | })
180 | .then(done)
181 | .catch(done);
182 | });
183 |
184 | it('should force overwrite files already in the compilation when "from" is a directory', (done) => {
185 | runForce({
186 | additionalAssets: [
187 | { name: ".dottedfile", data: "existing" },
188 | { name: "directoryfile.txt", data: "existing" },
189 | { name: "nested/deep-nested/deepnested.txt", data: "existing" },
190 | { name: "nested/nestedfile.txt", data: "existing" },
191 | ],
192 | expectedAssetKeys: [
193 | ".dottedfile",
194 | "directoryfile.txt",
195 | "nested/deep-nested/deepnested.txt",
196 | "nested/nestedfile.txt",
197 | ],
198 | expectedAssetContent: {
199 | ".dottedfile": "dottedfile contents\n",
200 | "nested/deep-nested/deepnested.txt": "",
201 | "nested/nestedfile.txt": "",
202 | "directoryfile.txt": "new",
203 | },
204 | patterns: [
205 | {
206 | force: true,
207 | from: "directory",
208 | },
209 | ],
210 | })
211 | .then(done)
212 | .catch(done);
213 | });
214 |
215 | it('should force overwrite files already in the compilation when "from" is a glob', (done) => {
216 | runForce({
217 | additionalAssets: [
218 | { name: "directory/directoryfile.txt", data: "existing" },
219 | {
220 | name: "directory/nested/deep-nested/deepnested.txt",
221 | data: "existing",
222 | },
223 | { name: "directory/nested/nestedfile.txt", data: "existing" },
224 | ],
225 | expectedAssetKeys: [
226 | "directory/directoryfile.txt",
227 | "directory/nested/deep-nested/deepnested.txt",
228 | "directory/nested/nestedfile.txt",
229 | ],
230 | expectedAssetContent: {
231 | "directory/nested/deep-nested/deepnested.txt": "",
232 | "directory/nested/nestedfile.txt": "",
233 | "directory/directoryfile.txt": "new",
234 | },
235 | patterns: [
236 | {
237 | force: true,
238 | from: "directory/**/*",
239 | },
240 | ],
241 | })
242 | .then(done)
243 | .catch(done);
244 | });
245 | });
246 | });
247 |
--------------------------------------------------------------------------------
/test/from-option.test.js:
--------------------------------------------------------------------------------
1 | import path from "path";
2 |
3 | import { runEmit } from "./helpers/run";
4 | import { getCompiler } from "./helpers";
5 |
6 | const FIXTURES_DIR_NORMALIZED = path
7 | .join(__dirname, "fixtures")
8 | .replace(/\\/g, "/");
9 |
10 | describe("from option", () => {
11 | describe("is a file", () => {
12 | it("should copy a file", (done) => {
13 | runEmit({
14 | expectedAssetKeys: ["file.txt"],
15 | patterns: [
16 | {
17 | from: "file.txt",
18 | },
19 | ],
20 | })
21 | .then(done)
22 | .catch(done);
23 | });
24 |
25 | it('should copy a file when "from" an absolute path', (done) => {
26 | runEmit({
27 | expectedAssetKeys: ["file.txt"],
28 | patterns: [
29 | {
30 | from: path.posix.join(FIXTURES_DIR_NORMALIZED, "file.txt"),
31 | },
32 | ],
33 | })
34 | .then(done)
35 | .catch(done);
36 | });
37 |
38 | it("should copy a file from nesting directory", (done) => {
39 | runEmit({
40 | expectedAssetKeys: ["directoryfile.txt"],
41 | patterns: [
42 | {
43 | from: "directory/directoryfile.txt",
44 | },
45 | ],
46 | })
47 | .then(done)
48 | .catch(done);
49 | });
50 |
51 | it('should copy a file from nesting directory when "from" an absolute path', (done) => {
52 | runEmit({
53 | expectedAssetKeys: ["directoryfile.txt"],
54 | patterns: [
55 | {
56 | from: path.posix.join(
57 | FIXTURES_DIR_NORMALIZED,
58 | "directory/directoryfile.txt",
59 | ),
60 | },
61 | ],
62 | })
63 | .then(done)
64 | .catch(done);
65 | });
66 |
67 | it("should copy a file (symbolic link)", (done) => {
68 | runEmit({
69 | symlink: true,
70 | expectedErrors:
71 | process.platform === "win32"
72 | ? [
73 | new Error(
74 | `unable to locate '${FIXTURES_DIR_NORMALIZED}/symlink/file-ln.txt' glob`,
75 | ),
76 | ]
77 | : [],
78 | expectedAssetKeys: process.platform === "win32" ? [] : ["file-ln.txt"],
79 | patterns: [
80 | {
81 | from: "symlink/file-ln.txt",
82 | },
83 | ],
84 | })
85 | .then(done)
86 | .catch(done);
87 | });
88 |
89 | it("should throw an error on the missing file", (done) => {
90 | runEmit({
91 | expectedAssetKeys: [],
92 | expectedErrors: [
93 | new Error(
94 | `unable to locate '${FIXTURES_DIR_NORMALIZED}/nonexistent.txt' glob`,
95 | ),
96 | ],
97 | patterns: [
98 | {
99 | from: "nonexistent.txt",
100 | },
101 | ],
102 | })
103 | .then(done)
104 | .catch(done);
105 | });
106 | });
107 |
108 | describe("is a directory", () => {
109 | it("should copy files", (done) => {
110 | runEmit({
111 | expectedAssetKeys: [
112 | ".dottedfile",
113 | "directoryfile.txt",
114 | "nested/deep-nested/deepnested.txt",
115 | "nested/nestedfile.txt",
116 | ],
117 | patterns: [
118 | {
119 | from: "directory",
120 | },
121 | ],
122 | })
123 | .then(done)
124 | .catch(done);
125 | });
126 |
127 | it('should copy files when "from" is current directory', (done) => {
128 | runEmit({
129 | expectedAssetKeys: [
130 | ".file.txt",
131 | "[(){}[]!+@escaped-test^$]/hello.txt",
132 | "[special$directory]/(special-*file).txt",
133 | "[special$directory]/directoryfile.txt",
134 | "[special$directory]/nested/nestedfile.txt",
135 | "binextension.bin",
136 | "dir (86)/file.txt",
137 | "dir (86)/nesteddir/deepnesteddir/deepnesteddir.txt",
138 | "dir (86)/nesteddir/nestedfile.txt",
139 | "directory/.dottedfile",
140 | "directory/directoryfile.txt",
141 | "directory/nested/deep-nested/deepnested.txt",
142 | "directory/nested/nestedfile.txt",
143 | "file.txt",
144 | "file.txt.gz",
145 | "noextension",
146 | ],
147 | patterns: [
148 | {
149 | from: ".",
150 | },
151 | ],
152 | })
153 | .then(done)
154 | .catch(done);
155 | });
156 |
157 | it('should copy files when "from" is relative path to context', (done) => {
158 | runEmit({
159 | expectedAssetKeys: [
160 | ".file.txt",
161 | "[(){}[]!+@escaped-test^$]/hello.txt",
162 | "[special$directory]/(special-*file).txt",
163 | "[special$directory]/directoryfile.txt",
164 | "[special$directory]/nested/nestedfile.txt",
165 | "binextension.bin",
166 | "dir (86)/file.txt",
167 | "dir (86)/nesteddir/deepnesteddir/deepnesteddir.txt",
168 | "dir (86)/nesteddir/nestedfile.txt",
169 | "directory/.dottedfile",
170 | "directory/directoryfile.txt",
171 | "directory/nested/deep-nested/deepnested.txt",
172 | "directory/nested/nestedfile.txt",
173 | "file.txt",
174 | "file.txt.gz",
175 | "noextension",
176 | ],
177 | patterns: [
178 | {
179 | from: "../fixtures",
180 | },
181 | ],
182 | })
183 | .then(done)
184 | .catch(done);
185 | });
186 |
187 | it("should copy files with a forward slash", (done) => {
188 | runEmit({
189 | expectedAssetKeys: [
190 | ".dottedfile",
191 | "directoryfile.txt",
192 | "nested/deep-nested/deepnested.txt",
193 | "nested/nestedfile.txt",
194 | ],
195 | patterns: [
196 | {
197 | from: "directory/",
198 | },
199 | ],
200 | })
201 | .then(done)
202 | .catch(done);
203 | });
204 |
205 | it("should copy files from symbolic link", (done) => {
206 | runEmit({
207 | // Windows doesn't support symbolic link
208 | symlink: true,
209 | expectedErrors:
210 | process.platform === "win32"
211 | ? [
212 | new Error(
213 | `unable to locate '${FIXTURES_DIR_NORMALIZED}/symlink/directory-ln/**/*' glob`,
214 | ),
215 | ]
216 | : [],
217 | expectedAssetKeys:
218 | process.platform === "win32"
219 | ? []
220 | : ["file.txt", "nested-directory/file-in-nested-directory.txt"],
221 | patterns: [
222 | {
223 | from: "symlink/directory-ln",
224 | },
225 | ],
226 | })
227 | .then(done)
228 | .catch(done);
229 | });
230 |
231 | it("should copy files when 'from' is a absolute path", (done) => {
232 | runEmit({
233 | expectedAssetKeys: [
234 | ".dottedfile",
235 | "directoryfile.txt",
236 | "nested/deep-nested/deepnested.txt",
237 | "nested/nestedfile.txt",
238 | ],
239 | patterns: [
240 | {
241 | from: path.posix.join(FIXTURES_DIR_NORMALIZED, "directory"),
242 | },
243 | ],
244 | })
245 | .then(done)
246 | .catch(done);
247 | });
248 |
249 | it("should copy files when 'from' with special characters", (done) => {
250 | runEmit({
251 | expectedAssetKeys: [
252 | "directoryfile.txt",
253 | "(special-*file).txt",
254 | "nested/nestedfile.txt",
255 | ],
256 | patterns: [
257 | {
258 | from:
259 | path.sep === "/" ? "[special$directory]" : "[special$directory]",
260 | },
261 | ],
262 | })
263 | .then(done)
264 | .catch(done);
265 | });
266 |
267 | it("should copy files from nested directory", (done) => {
268 | runEmit({
269 | expectedAssetKeys: ["deep-nested/deepnested.txt", "nestedfile.txt"],
270 | patterns: [
271 | {
272 | from: "directory/nested",
273 | },
274 | ],
275 | })
276 | .then(done)
277 | .catch(done);
278 | });
279 |
280 | it("should copy files from nested directory with an absolute path", (done) => {
281 | runEmit({
282 | expectedAssetKeys: ["deep-nested/deepnested.txt", "nestedfile.txt"],
283 | patterns: [
284 | {
285 | from: path.posix.join(FIXTURES_DIR_NORMALIZED, "directory/nested"),
286 | },
287 | ],
288 | })
289 | .then(done)
290 | .catch(done);
291 | });
292 |
293 | it("should throw an error on the missing directory", (done) => {
294 | runEmit({
295 | expectedAssetKeys: [],
296 | expectedErrors: [
297 | new Error(
298 | `unable to locate '${FIXTURES_DIR_NORMALIZED}/nonexistent' glob`,
299 | ),
300 | ],
301 | patterns: [
302 | {
303 | from: "nonexistent",
304 | },
305 | ],
306 | })
307 | .then(done)
308 | .catch(done);
309 | });
310 | });
311 |
312 | describe("is a glob", () => {
313 | it("should copy files", (done) => {
314 | runEmit({
315 | expectedAssetKeys: ["file.txt"],
316 | patterns: [
317 | {
318 | from: "*.txt",
319 | },
320 | ],
321 | })
322 | .then(done)
323 | .catch(done);
324 | });
325 |
326 | it("should copy files when a glob contains absolute path", (done) => {
327 | runEmit({
328 | expectedAssetKeys: ["file.txt"],
329 | patterns: [
330 | {
331 | from: path.posix.join(FIXTURES_DIR_NORMALIZED, "*.txt"),
332 | },
333 | ],
334 | })
335 | .then(done)
336 | .catch(done);
337 | });
338 |
339 | it("should copy files using globstar", (done) => {
340 | runEmit({
341 | expectedAssetKeys: [
342 | "[(){}[]!+@escaped-test^$]/hello.txt",
343 | "binextension.bin",
344 | "dir (86)/file.txt",
345 | "dir (86)/nesteddir/deepnesteddir/deepnesteddir.txt",
346 | "dir (86)/nesteddir/nestedfile.txt",
347 | "file.txt",
348 | "file.txt.gz",
349 | "directory/directoryfile.txt",
350 | "directory/nested/deep-nested/deepnested.txt",
351 | "directory/nested/nestedfile.txt",
352 | "[special$directory]/directoryfile.txt",
353 | "[special$directory]/(special-*file).txt",
354 | "[special$directory]/nested/nestedfile.txt",
355 | "noextension",
356 | ],
357 | patterns: [
358 | {
359 | from: "**/*",
360 | },
361 | ],
362 | })
363 | .then(done)
364 | .catch(done);
365 | });
366 |
367 | it("should copy files using globstar and contains an absolute path", (done) => {
368 | runEmit({
369 | expectedAssetKeys: [
370 | "[(){}[]!+@escaped-test^$]/hello.txt",
371 | "file.txt",
372 | "directory/directoryfile.txt",
373 | "directory/nested/deep-nested/deepnested.txt",
374 | "directory/nested/nestedfile.txt",
375 | "[special$directory]/directoryfile.txt",
376 | "[special$directory]/(special-*file).txt",
377 | "[special$directory]/nested/nestedfile.txt",
378 | "dir (86)/file.txt",
379 | "dir (86)/nesteddir/deepnesteddir/deepnesteddir.txt",
380 | "dir (86)/nesteddir/nestedfile.txt",
381 | ],
382 | patterns: [
383 | {
384 | from: path.posix.join(FIXTURES_DIR_NORMALIZED, "**/*.txt"),
385 | },
386 | ],
387 | })
388 | .then(done)
389 | .catch(done);
390 | });
391 |
392 | it("should copy files in nested directory using globstar", (done) => {
393 | const compiler = getCompiler({
394 | output: {
395 | hashDigestLength: 6,
396 | },
397 | });
398 |
399 | runEmit({
400 | compiler,
401 | expectedAssetKeys: [
402 | "nested/[(){}[]!+@escaped-test^$]/hello-31d6cf.txt",
403 | "nested/binextension-31d6cf.bin",
404 | "nested/dir (86)/file-31d6cf.txt",
405 | "nested/dir (86)/nesteddir/deepnesteddir/deepnesteddir-31d6cf.txt",
406 | "nested/dir (86)/nesteddir/nestedfile-31d6cf.txt",
407 | "nested/file-5d7817.txt",
408 | "nested/file.txt-f18c8d.gz",
409 | "nested/directory/directoryfile-5d7817.txt",
410 | "nested/directory/nested/deep-nested/deepnested-31d6cf.txt",
411 | "nested/directory/nested/nestedfile-31d6cf.txt",
412 | "nested/[special$directory]/(special-*file)-517cf2.txt",
413 | "nested/[special$directory]/directoryfile-5d7817.txt",
414 | "nested/[special$directory]/nested/nestedfile-31d6cf.txt",
415 | "nested/noextension-31d6cf",
416 | ],
417 | patterns: [
418 | {
419 | from: "**/*",
420 | to: "nested/[path][name]-[contenthash][ext]",
421 | },
422 | ],
423 | })
424 | .then(done)
425 | .catch(done);
426 | });
427 |
428 | it("should copy files from nested directory", (done) => {
429 | runEmit({
430 | expectedAssetKeys: ["directory/directoryfile.txt"],
431 | patterns: [
432 | {
433 | from: "directory/directory*.txt",
434 | },
435 | ],
436 | })
437 | .then(done)
438 | .catch(done);
439 | });
440 |
441 | it("should copy files from nested directory #2", (done) => {
442 | runEmit({
443 | expectedAssetKeys: [
444 | "directory/directoryfile.txt",
445 | "directory/nested/deep-nested/deepnested.txt",
446 | "directory/nested/nestedfile.txt",
447 | ],
448 | patterns: [
449 | {
450 | from: "directory/**/*.txt",
451 | },
452 | ],
453 | })
454 | .then(done)
455 | .catch(done);
456 | });
457 |
458 | it("should copy files using bracketed glob", (done) => {
459 | runEmit({
460 | expectedAssetKeys: [
461 | "directory/directoryfile.txt",
462 | "directory/nested/deep-nested/deepnested.txt",
463 | "directory/nested/nestedfile.txt",
464 | "file.txt",
465 | "noextension",
466 | ],
467 | patterns: [
468 | {
469 | from: "{file.txt,noextension,directory/**/*}",
470 | },
471 | ],
472 | })
473 | .then(done)
474 | .catch(done);
475 | });
476 |
477 | it("should copy files (symbolic link)", (done) => {
478 | runEmit({
479 | // Windows doesn't support symbolic link
480 | symlink: true,
481 | expectedErrors:
482 | process.platform === "win32"
483 | ? [
484 | new Error(
485 | `unable to locate '${FIXTURES_DIR_NORMALIZED}/symlink/**/*.txt' glob`,
486 | ),
487 | ]
488 | : [],
489 | expectedAssetKeys:
490 | process.platform === "win32"
491 | ? []
492 | : [
493 | "symlink/directory-ln/file.txt",
494 | "symlink/directory-ln/nested-directory/file-in-nested-directory.txt",
495 | "symlink/directory/file.txt",
496 | "symlink/directory/nested-directory/file-in-nested-directory.txt",
497 | "symlink/file-ln.txt",
498 | "symlink/file.txt",
499 | ],
500 | patterns: [
501 | {
502 | from: "symlink/**/*.txt",
503 | },
504 | ],
505 | })
506 | .then(done)
507 | .catch(done);
508 | });
509 |
510 | it("should throw an error on the missing glob", (done) => {
511 | runEmit({
512 | expectedAssetKeys: [],
513 | expectedErrors: [
514 | new Error(
515 | `unable to locate '${FIXTURES_DIR_NORMALIZED}/nonexistent/**/*' glob`,
516 | ),
517 | ],
518 | patterns: [
519 | {
520 | from: "nonexistent/**/*",
521 | },
522 | ],
523 | })
524 | .then(done)
525 | .catch(done);
526 | });
527 | });
528 | });
529 |
--------------------------------------------------------------------------------
/test/globOptions-option.test.js:
--------------------------------------------------------------------------------
1 | import path from "path";
2 |
3 | import { runEmit } from "./helpers/run";
4 |
5 | const FIXTURES_DIR_NORMALIZED = path
6 | .join(__dirname, "fixtures")
7 | .replace(/\\/g, "/");
8 |
9 | describe("globOptions option", () => {
10 | // Expected behavior from `globby`/`fast-glob`
11 | it('should copy files exclude dot files when "from" is a directory', (done) => {
12 | runEmit({
13 | expectedAssetKeys: [".file.txt"],
14 | patterns: [
15 | {
16 | from: ".file.txt",
17 | globOptions: {
18 | dot: false,
19 | },
20 | },
21 | ],
22 | })
23 | .then(done)
24 | .catch(done);
25 | });
26 |
27 | it('should copy files exclude dot files when "from" is a directory', (done) => {
28 | runEmit({
29 | expectedAssetKeys: [
30 | "directoryfile.txt",
31 | "nested/deep-nested/deepnested.txt",
32 | "nested/nestedfile.txt",
33 | ],
34 | patterns: [
35 | {
36 | from: "directory",
37 | globOptions: {
38 | dot: false,
39 | },
40 | },
41 | ],
42 | })
43 | .then(done)
44 | .catch(done);
45 | });
46 |
47 | it('should copy files exclude dot files when "from" is a glob', (done) => {
48 | runEmit({
49 | expectedAssetKeys: ["file.txt"],
50 | patterns: [
51 | {
52 | from: "*.txt",
53 | globOptions: {
54 | dot: false,
55 | },
56 | },
57 | ],
58 | })
59 | .then(done)
60 | .catch(done);
61 | });
62 |
63 | it("should copy files include dot files", (done) => {
64 | runEmit({
65 | expectedAssetKeys: [".file.txt", "file.txt"],
66 | patterns: [
67 | {
68 | from: "*.txt",
69 | globOptions: {
70 | dot: true,
71 | },
72 | },
73 | ],
74 | })
75 | .then(done)
76 | .catch(done);
77 | });
78 |
79 | it('should ignore files when "from" is a file', (done) => {
80 | runEmit({
81 | expectedErrors: [
82 | new Error(
83 | `unable to locate '${FIXTURES_DIR_NORMALIZED}/file.txt' glob`,
84 | ),
85 | ],
86 | patterns: [
87 | {
88 | from: "file.txt",
89 | globOptions: {
90 | ignore: ["**/file.*"],
91 | },
92 | },
93 | ],
94 | })
95 | .then(done)
96 | .catch(done);
97 | });
98 |
99 | it('should files when "from" is a directory', (done) => {
100 | runEmit({
101 | expectedAssetKeys: [
102 | ".dottedfile",
103 | "directoryfile.txt",
104 | "nested/deep-nested/deepnested.txt",
105 | ],
106 | patterns: [
107 | {
108 | from: "directory",
109 | globOptions: {
110 | ignore: ["**/nestedfile.*"],
111 | },
112 | },
113 | ],
114 | })
115 | .then(done)
116 | .catch(done);
117 | });
118 |
119 | it("should work with globOptions.objectMode && globOptions.gitignore", (done) => {
120 | runEmit({
121 | expectedAssetKeys: [
122 | ".dottedfile",
123 | "directoryfile.txt",
124 | "nested/deep-nested/deepnested.txt",
125 | ],
126 | patterns: [
127 | {
128 | from: "directory",
129 | globOptions: {
130 | objectMode: true,
131 | gitignore: true,
132 | ignore: ["**/nestedfile.*"],
133 | },
134 | },
135 | ],
136 | })
137 | .then(done)
138 | .catch(done);
139 | });
140 |
141 | it('should files in nested directory when "from" is a directory', (done) => {
142 | runEmit({
143 | expectedAssetKeys: [".dottedfile", "directoryfile.txt"],
144 | patterns: [
145 | {
146 | from: "directory",
147 | globOptions: {
148 | ignore: ["**/nested/**"],
149 | },
150 | },
151 | ],
152 | })
153 | .then(done)
154 | .catch(done);
155 | });
156 |
157 | it("should files when from is a glob", (done) => {
158 | runEmit({
159 | expectedAssetKeys: [
160 | "directory/directoryfile.txt",
161 | "directory/nested/deep-nested/deepnested.txt",
162 | ],
163 | patterns: [
164 | {
165 | from: "directory/**/*",
166 | globOptions: {
167 | ignore: ["**/nestedfile.*"],
168 | },
169 | },
170 | ],
171 | })
172 | .then(done)
173 | .catch(done);
174 | });
175 |
176 | it("should files in nested directory when from is a glob", (done) => {
177 | runEmit({
178 | expectedAssetKeys: ["directory/directoryfile.txt"],
179 | patterns: [
180 | {
181 | from: "directory/**/*",
182 | globOptions: {
183 | ignore: ["**/nested/**"],
184 | },
185 | },
186 | ],
187 | })
188 | .then(done)
189 | .catch(done);
190 | });
191 |
192 | it("should ignore files with a certain extension", (done) => {
193 | runEmit({
194 | expectedAssetKeys: [".dottedfile"],
195 | patterns: [
196 | {
197 | from: "directory",
198 | globOptions: {
199 | ignore: ["**/*.txt"],
200 | },
201 | },
202 | ],
203 | })
204 | .then(done)
205 | .catch(done);
206 | });
207 |
208 | it("should ignore files with multiple ignore patterns", (done) => {
209 | runEmit({
210 | expectedAssetKeys: ["directory/nested/nestedfile.txt"],
211 | patterns: [
212 | {
213 | from: "directory/**/*",
214 | globOptions: {
215 | ignore: ["**/directoryfile.*", "**/deep-nested/**"],
216 | },
217 | },
218 | ],
219 | })
220 | .then(done)
221 | .catch(done);
222 | });
223 |
224 | it("should ignore files with flatten copy", (done) => {
225 | runEmit({
226 | expectedAssetKeys: ["img/.dottedfile", "img/nestedfile.txt"],
227 | patterns: [
228 | {
229 | from: "directory/",
230 | toType: "file",
231 | to({ absoluteFilename }) {
232 | return `img/${path.basename(absoluteFilename)}`;
233 | },
234 | globOptions: {
235 | ignore: ["**/directoryfile.*", "**/deep-nested/**"],
236 | },
237 | },
238 | ],
239 | })
240 | .then(done)
241 | .catch(done);
242 | });
243 |
244 | it("should ignore files except those with dots", (done) => {
245 | runEmit({
246 | expectedAssetKeys: [".dottedfile"],
247 | patterns: [
248 | {
249 | from: "directory",
250 | globOptions: {
251 | ignore: ["!(**/.*)"],
252 | },
253 | },
254 | ],
255 | })
256 | .then(done)
257 | .catch(done);
258 | });
259 |
260 | it("should ignore files that start with a dot", (done) => {
261 | runEmit({
262 | expectedAssetKeys: [
263 | "directoryfile.txt",
264 | "nested/deep-nested/deepnested.txt",
265 | "nested/nestedfile.txt",
266 | ],
267 | patterns: [
268 | {
269 | from: "directory",
270 | globOptions: {
271 | ignore: ["**/.*"],
272 | },
273 | },
274 | ],
275 | })
276 | .then(done)
277 | .catch(done);
278 | });
279 |
280 | it("should ignores all files even if they start with a dot", (done) => {
281 | runEmit({
282 | expectedErrors: [
283 | new Error(
284 | `unable to locate '${FIXTURES_DIR_NORMALIZED}/directory/**/*' glob`,
285 | ),
286 | ],
287 | patterns: [
288 | {
289 | from: "directory",
290 | globOptions: {
291 | ignore: ["**/*"],
292 | },
293 | },
294 | ],
295 | })
296 | .then(done)
297 | .catch(done);
298 | });
299 |
300 | it('should ignore files when "from" is a file (global ignore)', (done) => {
301 | runEmit({
302 | expectedErrors: [
303 | new Error(
304 | `unable to locate '${FIXTURES_DIR_NORMALIZED}/file.txt' glob`,
305 | ),
306 | ],
307 | patterns: [
308 | {
309 | from: "file.txt",
310 | globOptions: {
311 | ignore: ["**/file.*"],
312 | },
313 | },
314 | ],
315 | })
316 | .then(done)
317 | .catch(done);
318 | });
319 |
320 | it('should ignore the "cwd" option', (done) => {
321 | runEmit({
322 | expectedAssetKeys: [
323 | ".dottedfile",
324 | "directoryfile.txt",
325 | "nested/deep-nested/deepnested.txt",
326 | "nested/nestedfile.txt",
327 | ],
328 | patterns: [
329 | {
330 | from: "directory",
331 | globOptions: {
332 | cwd: path.resolve(__dirname, "fixtures/nested"),
333 | },
334 | },
335 | ],
336 | })
337 | .then(done)
338 | .catch(done);
339 | });
340 |
341 | it('should work with the "deep" option', (done) => {
342 | runEmit({
343 | expectedAssetKeys: [
344 | ".dottedfile",
345 | "directoryfile.txt",
346 | "nested/nestedfile.txt",
347 | ],
348 | patterns: [
349 | {
350 | from: "directory",
351 | globOptions: {
352 | deep: 2,
353 | },
354 | },
355 | ],
356 | })
357 | .then(done)
358 | .catch(done);
359 | });
360 |
361 | it('should work with the "markDirectories" option', (done) => {
362 | runEmit({
363 | expectedAssetKeys: [
364 | ".dottedfile",
365 | "directoryfile.txt",
366 | "nested/deep-nested/deepnested.txt",
367 | "nested/nestedfile.txt",
368 | ],
369 | patterns: [
370 | {
371 | from: "directory",
372 | globOptions: {
373 | markDirectories: true,
374 | },
375 | },
376 | ],
377 | })
378 | .then(done)
379 | .catch(done);
380 | });
381 |
382 | it('should work with the "objectMode" option', (done) => {
383 | runEmit({
384 | expectedAssetKeys: [
385 | ".dottedfile",
386 | "directoryfile.txt",
387 | "nested/deep-nested/deepnested.txt",
388 | "nested/nestedfile.txt",
389 | ],
390 | patterns: [
391 | {
392 | from: "directory",
393 | globOptions: {
394 | objectMode: true,
395 | },
396 | },
397 | ],
398 | })
399 | .then(done)
400 | .catch(done);
401 | });
402 |
403 | it("should emit error when not found assets for copy", (done) => {
404 | expect.assertions(1);
405 |
406 | runEmit({
407 | expectedAssetKeys: [],
408 | patterns: [
409 | {
410 | from: "directory",
411 | globOptions: {
412 | onlyDirectories: true,
413 | },
414 | },
415 | ],
416 | })
417 | .then(done)
418 | .catch((error) => {
419 | expect(error).toBeDefined();
420 |
421 | done();
422 | });
423 | });
424 |
425 | it('should work with the "onlyFiles" option', (done) => {
426 | runEmit({
427 | expectedAssetKeys: [
428 | ".dottedfile",
429 | "directoryfile.txt",
430 | "nested/deep-nested/deepnested.txt",
431 | "nested/nestedfile.txt",
432 | ],
433 | patterns: [
434 | {
435 | from: "directory",
436 | globOptions: {
437 | onlyFiles: true,
438 | },
439 | },
440 | ],
441 | })
442 | .then(done)
443 | .catch(done);
444 | });
445 | });
446 |
--------------------------------------------------------------------------------
/test/helpers/BreakContenthashPlugin.js:
--------------------------------------------------------------------------------
1 | class BreakContenthashPlugin {
2 | constructor(options = {}) {
3 | this.options = options.options || {};
4 | }
5 |
6 | apply(compiler) {
7 | const plugin = { name: "BrokeContenthashPlugin" };
8 |
9 | compiler.hooks.thisCompilation.tap(plugin, (compilation) => {
10 | compilation.hooks.processAssets.tapAsync(
11 | {
12 | name: "broken-contenthash-webpack-plugin",
13 | stage: compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE,
14 | },
15 | (unusedAssets, callback) => {
16 | this.options.targetAssets.forEach(({ name, newName, newHash }) => {
17 | const asset = compilation.getAsset(name);
18 |
19 | compilation.updateAsset(asset.name, asset.source, {
20 | ...asset.info,
21 | contenthash: newHash,
22 | });
23 | compilation.renameAsset(asset.name, newName);
24 | });
25 |
26 | callback();
27 | },
28 | );
29 | });
30 | }
31 | }
32 |
33 | export default BreakContenthashPlugin;
34 |
--------------------------------------------------------------------------------
/test/helpers/ChildCompiler.js:
--------------------------------------------------------------------------------
1 | export default class ChildCompiler {
2 | // eslint-disable-next-line class-methods-use-this
3 | apply(compiler) {
4 | compiler.hooks.make.tapAsync("Child Compiler", (compilation, callback) => {
5 | const outputOptions = {
6 | filename: "output.js",
7 | publicPath: compilation.outputOptions.publicPath,
8 | };
9 | const childCompiler = compilation.createChildCompiler(
10 | "ChildCompiler",
11 | outputOptions,
12 | );
13 | childCompiler.runAsChild((error, entries, childCompilation) => {
14 | if (error) {
15 | throw error;
16 | }
17 |
18 | const assets = childCompilation.getAssets();
19 |
20 | if (assets.length > 0) {
21 | callback(
22 | new Error("Copy plugin should not be ran in child compilations"),
23 | );
24 |
25 | return;
26 | }
27 |
28 | callback();
29 | });
30 | });
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/test/helpers/PreCopyPlugin.js:
--------------------------------------------------------------------------------
1 | class PreCopyPlugin {
2 | constructor(options = {}) {
3 | this.options = options.options || {};
4 | }
5 |
6 | apply(compiler) {
7 | const plugin = { name: "PreCopyPlugin" };
8 |
9 | compiler.hooks.thisCompilation.tap(plugin, (compilation) => {
10 | compilation.hooks.additionalAssets.tapAsync(
11 | "pre-copy-webpack-plugin",
12 | (callback) => {
13 | this.options.additionalAssets.forEach(({ name, data, info }) => {
14 | const { RawSource } = compiler.webpack.sources;
15 | const source = new RawSource(data);
16 |
17 | compilation.emitAsset(name, source, info);
18 | });
19 |
20 | callback();
21 | },
22 | );
23 | });
24 | }
25 | }
26 |
27 | export default PreCopyPlugin;
28 |
--------------------------------------------------------------------------------
/test/helpers/built-in-modules/fs.js:
--------------------------------------------------------------------------------
1 | module.exports = require("fs");
2 |
--------------------------------------------------------------------------------
/test/helpers/built-in-modules/path.js:
--------------------------------------------------------------------------------
1 | module.exports = require("path");
2 |
--------------------------------------------------------------------------------
/test/helpers/built-in-modules/process.js:
--------------------------------------------------------------------------------
1 | module.exports = require("process");
2 |
--------------------------------------------------------------------------------
/test/helpers/built-in-modules/stream.js:
--------------------------------------------------------------------------------
1 | module.exports = require("stream");
2 |
--------------------------------------------------------------------------------
/test/helpers/built-in-modules/url.js:
--------------------------------------------------------------------------------
1 | module.exports = require("url");
2 |
--------------------------------------------------------------------------------
/test/helpers/built-in-modules/util.js:
--------------------------------------------------------------------------------
1 | module.exports = require("util");
2 |
--------------------------------------------------------------------------------
/test/helpers/compile.js:
--------------------------------------------------------------------------------
1 | export default (compiler) =>
2 | new Promise((resolve, reject) => {
3 | compiler.run((error, stats) => {
4 | if (error) {
5 | return reject(error);
6 | }
7 |
8 | return resolve({ stats, compiler });
9 | });
10 | });
11 |
--------------------------------------------------------------------------------
/test/helpers/enter-with-asset-modules.js:
--------------------------------------------------------------------------------
1 | import txtURL from "../fixtures/directory/nested/deep-nested/deepnested.txt";
2 |
3 | export default txtURL;
4 |
--------------------------------------------------------------------------------
/test/helpers/enter.js:
--------------------------------------------------------------------------------
1 | // Entry point for tests
2 |
--------------------------------------------------------------------------------
/test/helpers/getCompiler.js:
--------------------------------------------------------------------------------
1 | import path from "path";
2 |
3 | // eslint-disable-next-line import/no-extraneous-dependencies
4 | import webpack from "webpack";
5 | // eslint-disable-next-line import/no-extraneous-dependencies
6 | import { createFsFromVolume, Volume } from "memfs";
7 |
8 | export default (config = {}) => {
9 | const fullConfig = {
10 | mode: "development",
11 | context: path.resolve(__dirname, "../fixtures"),
12 | entry: path.resolve(__dirname, "../helpers/enter.js"),
13 | output: {
14 | path: path.resolve(__dirname, "../build"),
15 | },
16 | module: {
17 | rules: [
18 | {
19 | test: /\.txt/,
20 | type: "asset/resource",
21 | generator: {
22 | filename: "asset-modules/[name][ext]",
23 | },
24 | },
25 | ],
26 | },
27 | ...config,
28 | };
29 |
30 | const compiler = webpack(fullConfig);
31 |
32 | if (!config.outputFileSystem) {
33 | compiler.outputFileSystem = createFsFromVolume(new Volume());
34 | }
35 |
36 | return compiler;
37 | };
38 |
--------------------------------------------------------------------------------
/test/helpers/index.js:
--------------------------------------------------------------------------------
1 | import compile from "./compile";
2 | import getCompiler from "./getCompiler";
3 | import readAsset from "./readAsset";
4 | import readAssets from "./readAssets";
5 |
6 | export { compile, getCompiler, readAsset, readAssets };
7 |
--------------------------------------------------------------------------------
/test/helpers/readAsset.js:
--------------------------------------------------------------------------------
1 | import path from "path";
2 |
3 | export default (asset, compiler, stats) => {
4 | const usedFs = compiler.outputFileSystem;
5 | const outputPath = stats.compilation.outputOptions.path;
6 |
7 | let data = "";
8 | let targetFile = asset;
9 |
10 | const queryStringIdx = targetFile.indexOf("?");
11 |
12 | if (queryStringIdx >= 0) {
13 | targetFile = targetFile.slice(0, queryStringIdx);
14 | }
15 |
16 | try {
17 | const isArchive = /.gz$/i.test(targetFile);
18 | data = usedFs.readFileSync(path.join(outputPath, targetFile));
19 |
20 | if (!isArchive) {
21 | data = data.toString();
22 | }
23 | } catch (error) {
24 | data = error.toString();
25 | }
26 |
27 | return data;
28 | };
29 |
--------------------------------------------------------------------------------
/test/helpers/readAssets.js:
--------------------------------------------------------------------------------
1 | import readAsset from "./readAsset";
2 |
3 | export default function readAssets(compiler, stats) {
4 | const assets = {};
5 |
6 | Object.keys(stats.compilation.assets)
7 | .filter((a) => a !== "main.js")
8 | .forEach((asset) => {
9 | assets[asset] = readAsset(asset, compiler, stats);
10 | });
11 |
12 | return assets;
13 | }
14 |
--------------------------------------------------------------------------------
/test/helpers/removeIllegalCharacterForWindows.js:
--------------------------------------------------------------------------------
1 | module.exports = (string) =>
2 | process.platform !== "win32" ? string : string.replace(/[*?"<>|]/g, "");
3 |
--------------------------------------------------------------------------------
/test/helpers/run.js:
--------------------------------------------------------------------------------
1 | // Ideally we pass in patterns and confirm the resulting assets
2 | import fs from "fs";
3 | import path from "path";
4 |
5 | import CopyPlugin from "../../src/index";
6 |
7 | import ChildCompilerPlugin from "./ChildCompiler";
8 | import PreCopyPlugin from "./PreCopyPlugin";
9 | import BreakContenthashPlugin from "./BreakContenthashPlugin";
10 |
11 | import removeIllegalCharacterForWindows from "./removeIllegalCharacterForWindows";
12 |
13 | import { compile, getCompiler, readAssets } from "./";
14 |
15 | /* eslint-disable no-param-reassign */
16 |
17 | const isWin = process.platform === "win32";
18 |
19 | const ignore = [
20 | "**/symlink/**/*",
21 | "**/file-ln.txt",
22 | "**/directory-ln",
23 | "**/watch/**/*",
24 | ];
25 |
26 | function run(opts) {
27 | return new Promise((resolve, reject) => {
28 | if (Array.isArray(opts.patterns)) {
29 | opts.patterns.forEach((pattern) => {
30 | if (pattern.context) {
31 | // eslint-disable-next-line no-param-reassign
32 | pattern.context = removeIllegalCharacterForWindows(pattern.context);
33 | }
34 |
35 | if (typeof pattern !== "string") {
36 | if (!opts.symlink || isWin) {
37 | pattern.globOptions = pattern.globOptions || {};
38 | pattern.globOptions.ignore = [
39 | ...ignore,
40 | ...(pattern.globOptions.ignore || []),
41 | ];
42 | }
43 | }
44 | });
45 | }
46 |
47 | const compiler = opts.compiler || getCompiler();
48 |
49 | if (opts.preCopy) {
50 | new PreCopyPlugin({ options: opts.preCopy }).apply(compiler);
51 | }
52 |
53 | if (opts.breakContenthash) {
54 | new BreakContenthashPlugin({ options: opts.breakContenthash }).apply(
55 | compiler,
56 | );
57 | }
58 |
59 | new CopyPlugin({ patterns: opts.patterns, options: opts.options }).apply(
60 | compiler,
61 | );
62 |
63 | if (opts.withChildCompilation) {
64 | new ChildCompilerPlugin().apply(compiler);
65 | }
66 |
67 | // Execute the functions in series
68 | return compile(compiler)
69 | .then(({ stats }) => {
70 | const { compilation } = stats;
71 |
72 | if (opts.expectedErrors) {
73 | expect(compilation.errors).toEqual(opts.expectedErrors);
74 | } else if (compilation.errors.length > 0) {
75 | throw compilation.errors[0];
76 | }
77 |
78 | if (opts.expectedWarnings) {
79 | expect(compilation.warnings).toEqual(opts.expectedWarnings);
80 | } else if (compilation.warnings.length > 0) {
81 | throw compilation.warnings[0];
82 | }
83 |
84 | const enryPoint = path.resolve(__dirname, "enter.js");
85 |
86 | if (compilation.fileDependencies.has(enryPoint)) {
87 | compilation.fileDependencies.delete(enryPoint);
88 | }
89 |
90 | resolve({ compilation, compiler, stats });
91 | })
92 | .catch(reject);
93 | });
94 | }
95 |
96 | function runEmit(opts) {
97 | return run(opts).then(({ compilation, compiler, stats }) => {
98 | if (opts.skipAssetsTesting) {
99 | return;
100 | }
101 |
102 | if (opts.expectedAssetKeys && opts.expectedAssetKeys.length > 0) {
103 | expect(
104 | Object.keys(compilation.assets)
105 | .filter((a) => a !== "main.js")
106 | .sort(),
107 | ).toEqual(
108 | opts.expectedAssetKeys.sort().map(removeIllegalCharacterForWindows),
109 | );
110 | } else {
111 | // eslint-disable-next-line no-param-reassign
112 | delete compilation.assets["main.js"];
113 | expect(compilation.assets).toEqual({});
114 | }
115 |
116 | if (opts.expectedAssetContent) {
117 | // eslint-disable-next-line guard-for-in
118 | for (const assetName in opts.expectedAssetContent) {
119 | expect(compilation.assets[assetName]).toBeDefined();
120 |
121 | if (compilation.assets[assetName]) {
122 | let expectedContent = opts.expectedAssetContent[assetName];
123 | let compiledContent = readAssets(compiler, stats)[assetName];
124 |
125 | if (!Buffer.isBuffer(expectedContent)) {
126 | expectedContent = Buffer.from(expectedContent);
127 | }
128 |
129 | if (!Buffer.isBuffer(compiledContent)) {
130 | compiledContent = Buffer.from(compiledContent);
131 | }
132 |
133 | expect(Buffer.compare(expectedContent, compiledContent)).toBe(0);
134 | }
135 | }
136 | }
137 | });
138 | }
139 |
140 | function runForce(opts) {
141 | // eslint-disable-next-line no-param-reassign
142 | opts.compiler = getCompiler();
143 |
144 | new PreCopyPlugin({ options: opts }).apply(opts.compiler);
145 |
146 | return runEmit(opts).then(() => {});
147 | }
148 |
149 | const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
150 |
151 | function runChange(opts) {
152 | return new Promise(async (resolve) => {
153 | const compiler = getCompiler();
154 |
155 | new CopyPlugin({ patterns: opts.patterns, options: opts.options }).apply(
156 | compiler,
157 | );
158 |
159 | // Create two test files
160 | fs.writeFileSync(opts.newFileLoc1, "file1contents");
161 | fs.writeFileSync(opts.newFileLoc2, "file2contents");
162 |
163 | const arrayOfStats = [];
164 |
165 | const watching = compiler.watch({}, (error, stats) => {
166 | if (error || stats.hasErrors()) {
167 | throw error;
168 | }
169 |
170 | arrayOfStats.push(stats);
171 | });
172 |
173 | await delay(500);
174 |
175 | fs.appendFileSync(opts.newFileLoc1, "extra");
176 |
177 | await delay(500);
178 |
179 | watching.close(() => {
180 | const assetsBefore = readAssets(compiler, arrayOfStats[0]);
181 | const assetsAfter = readAssets(compiler, arrayOfStats.pop());
182 | const filesForCompare = Object.keys(assetsBefore);
183 | const changedFiles = [];
184 |
185 | filesForCompare.forEach((file) => {
186 | if (assetsBefore[file] === assetsAfter[file]) {
187 | changedFiles.push(file);
188 | }
189 | });
190 |
191 | const lastFiles = Object.keys(assetsAfter);
192 |
193 | if (
194 | opts.expectedAssetKeys &&
195 | opts.expectedAssetKeys.length > 0 &&
196 | changedFiles.length > 0
197 | ) {
198 | expect(lastFiles.sort()).toEqual(
199 | opts.expectedAssetKeys.sort().map(removeIllegalCharacterForWindows),
200 | );
201 | } else {
202 | expect(lastFiles).toEqual({});
203 | }
204 |
205 | resolve(watching);
206 | });
207 | }).then(() => {
208 | fs.unlinkSync(opts.newFileLoc1);
209 | fs.unlinkSync(opts.newFileLoc2);
210 | });
211 | }
212 |
213 | export { run, runChange, runEmit, runForce };
214 |
--------------------------------------------------------------------------------
/test/info-option.test.js:
--------------------------------------------------------------------------------
1 | import { run, runEmit } from "./helpers/run";
2 |
3 | describe("info option", () => {
4 | it('should work without "info" option', (done) => {
5 | runEmit({
6 | expectedAssetKeys: ["file.txt"],
7 | patterns: [
8 | {
9 | from: "file.txt",
10 | },
11 | ],
12 | })
13 | .then(done)
14 | .catch(done);
15 | });
16 |
17 | it('should work when "info" option is a object', (done) => {
18 | run({
19 | expectedAssetKeys: ["file.txt"],
20 | patterns: [
21 | {
22 | from: "file.txt",
23 | info: { test: true },
24 | },
25 | ],
26 | })
27 | .then(({ compilation }) => {
28 | expect(compilation.assetsInfo.get("file.txt").test).toBe(true);
29 | })
30 | .then(done)
31 | .catch(done);
32 | });
33 |
34 | it('should work when "info" option is a object and "force" option is true', (done) => {
35 | const expectedAssetKeys = ["file.txt"];
36 |
37 | run({
38 | preCopy: {
39 | additionalAssets: [
40 | { name: "file.txt", data: "Content", info: { custom: true } },
41 | ],
42 | },
43 | expectedAssetKeys,
44 | patterns: [
45 | {
46 | from: "file.txt",
47 | force: true,
48 | info: { test: true },
49 | },
50 | ],
51 | })
52 | .then(({ compilation }) => {
53 | expect(compilation.assetsInfo.get("file.txt").test).toBe(true);
54 | })
55 | .then(done)
56 | .catch(done);
57 | });
58 |
59 | it('should work when "info" option is a function', (done) => {
60 | run({
61 | expectedAssetKeys: ["file.txt"],
62 | patterns: [
63 | {
64 | from: "file.txt",
65 | info: (file) => {
66 | expect.assertions(4);
67 |
68 | const fileKeys = ["absoluteFilename", "sourceFilename", "filename"];
69 |
70 | for (const key of fileKeys) {
71 | expect(key in file).toBe(true);
72 | }
73 |
74 | return { test: true };
75 | },
76 | },
77 | ],
78 | })
79 | .then(({ compilation }) => {
80 | expect(compilation.assetsInfo.get("file.txt").test).toBe(true);
81 | })
82 | .then(done)
83 | .catch(done);
84 | });
85 |
86 | it('should work when "info" option is a function and "force" option is true', (done) => {
87 | const expectedAssetKeys = ["file.txt"];
88 |
89 | run({
90 | preCopy: {
91 | additionalAssets: [
92 | { name: "file.txt", data: "Content", info: { custom: true } },
93 | ],
94 | },
95 | expectedAssetKeys,
96 | patterns: [
97 | {
98 | from: "file.txt",
99 | force: true,
100 | info: () => {
101 | return { test: true };
102 | },
103 | },
104 | ],
105 | })
106 | .then(({ compilation }) => {
107 | expect(compilation.assetsInfo.get("file.txt").test).toBe(true);
108 | })
109 | .then(done)
110 | .catch(done);
111 | });
112 | });
113 |
--------------------------------------------------------------------------------
/test/noErrorOnMissing.test.js:
--------------------------------------------------------------------------------
1 | import { runEmit } from "./helpers/run";
2 |
3 | describe("noErrorOnMissing option", () => {
4 | describe("is a file", () => {
5 | it("should work", (done) => {
6 | runEmit({
7 | expectedAssetKeys: [],
8 | patterns: [
9 | {
10 | from: "unknown.unknown",
11 | noErrorOnMissing: true,
12 | },
13 | ],
14 | })
15 | .then(done)
16 | .catch(done);
17 | });
18 | });
19 |
20 | describe("is a directory", () => {
21 | it("should work", (done) => {
22 | runEmit({
23 | expectedAssetKeys: [],
24 | patterns: [
25 | {
26 | from: "unknown",
27 | noErrorOnMissing: true,
28 | },
29 | ],
30 | })
31 | .then(done)
32 | .catch(done);
33 | });
34 | });
35 |
36 | describe("is a glob", () => {
37 | it("should work", (done) => {
38 | runEmit({
39 | expectedAssetKeys: [],
40 | patterns: [
41 | {
42 | from: "*.unknown",
43 | noErrorOnMissing: true,
44 | },
45 | ],
46 | })
47 | .then(done)
48 | .catch(done);
49 | });
50 | });
51 | });
52 |
--------------------------------------------------------------------------------
/test/priority-option.test.js:
--------------------------------------------------------------------------------
1 | import { run } from "./helpers/run";
2 |
3 | describe("priority option", () => {
4 | it("should copy without specifying priority option", (done) => {
5 | run({
6 | expectedAssetKeys: [],
7 | patterns: [
8 | {
9 | from: "dir (86)/file.txt",
10 | to: "newfile.txt",
11 | },
12 | {
13 | from: "file.txt",
14 | to: "newfile.txt",
15 | force: true,
16 | },
17 | ],
18 | })
19 | .then(({ stats }) => {
20 | const { info } = stats.compilation.getAsset("newfile.txt");
21 |
22 | expect(info.sourceFilename).toEqual("file.txt");
23 |
24 | done();
25 | })
26 | .catch(done);
27 | });
28 |
29 | it("should copy with specifying priority option", (done) => {
30 | run({
31 | expectedAssetKeys: [],
32 | patterns: [
33 | {
34 | from: "dir (86)/file.txt",
35 | to: "newfile.txt",
36 | force: true,
37 | priority: 10,
38 | },
39 | {
40 | from: "file.txt",
41 | to: "newfile.txt",
42 | priority: 5,
43 | },
44 | ],
45 | })
46 | .then(({ stats }) => {
47 | const { info } = stats.compilation.getAsset("newfile.txt");
48 |
49 | expect(info.sourceFilename).toEqual("dir (86)/file.txt");
50 |
51 | done();
52 | })
53 | .catch(done);
54 | });
55 |
56 | it("should copy with specifying priority option and respect negative priority", (done) => {
57 | run({
58 | expectedAssetKeys: [],
59 | patterns: [
60 | {
61 | from: "dir (86)/file.txt",
62 | to: "newfile.txt",
63 | priority: 10,
64 | force: true,
65 | },
66 | {
67 | from: "file.txt",
68 | to: "other-newfile.txt",
69 | },
70 | {
71 | from: "file.txt",
72 | to: "newfile.txt",
73 | priority: -5,
74 | },
75 | ],
76 | })
77 | .then(({ stats }) => {
78 | const { info } = stats.compilation.getAsset("newfile.txt");
79 |
80 | expect(info.sourceFilename).toEqual("dir (86)/file.txt");
81 |
82 | done();
83 | })
84 | .catch(done);
85 | });
86 |
87 | it("should copy with specifying priority option and respect order of patterns", (done) => {
88 | run({
89 | expectedAssetKeys: [],
90 | patterns: [
91 | {
92 | from: "dir (86)/file.txt",
93 | to: "newfile.txt",
94 | priority: 10,
95 | },
96 | {
97 | from: "file.txt",
98 | to: "newfile.txt",
99 | priority: 10,
100 | force: true,
101 | },
102 | ],
103 | })
104 | .then(({ stats }) => {
105 | const { info } = stats.compilation.getAsset("newfile.txt");
106 |
107 | expect(info.sourceFilename).toEqual("file.txt");
108 |
109 | done();
110 | })
111 | .catch(done);
112 | });
113 | });
114 |
--------------------------------------------------------------------------------
/test/to-option.test.js:
--------------------------------------------------------------------------------
1 | import path from "path";
2 |
3 | import { runEmit } from "./helpers/run";
4 | import { getCompiler } from "./helpers";
5 |
6 | const BUILD_DIR = path.join(__dirname, "build");
7 | const TEMP_DIR = path.join(__dirname, "tempdir");
8 | const FIXTURES_DIR = path.join(__dirname, "fixtures");
9 |
10 | describe("to option", () => {
11 | describe("is a file", () => {
12 | it("should copy a file to a new file", (done) => {
13 | runEmit({
14 | expectedAssetKeys: ["newfile.txt"],
15 | patterns: [
16 | {
17 | from: "file.txt",
18 | to: "newfile.txt",
19 | },
20 | ],
21 | })
22 | .then(done)
23 | .catch(done);
24 | });
25 |
26 | it('should copy a file to a new file when "to" is absolute path', (done) => {
27 | runEmit({
28 | expectedAssetKeys: ["../tempdir/newfile.txt"],
29 | patterns: [
30 | {
31 | from: "file.txt",
32 | to: path.join(TEMP_DIR, "newfile.txt"),
33 | },
34 | ],
35 | })
36 | .then(done)
37 | .catch(done);
38 | });
39 |
40 | it("should copy a file to a new file inside nested directory", (done) => {
41 | runEmit({
42 | expectedAssetKeys: ["newdirectory/newfile.txt"],
43 | patterns: [
44 | {
45 | from: "file.txt",
46 | to: "newdirectory/newfile.txt",
47 | },
48 | ],
49 | })
50 | .then(done)
51 | .catch(done);
52 | });
53 |
54 | it('should copy a file to a new file inside nested directory when "to" an absolute path', (done) => {
55 | runEmit({
56 | expectedAssetKeys: ["newdirectory/newfile.txt"],
57 | patterns: [
58 | {
59 | from: "file.txt",
60 | to: path.join(BUILD_DIR, "newdirectory/newfile.txt"),
61 | },
62 | ],
63 | })
64 | .then(done)
65 | .catch(done);
66 | });
67 |
68 | it("should copy a file to a new file inside other directory what out of context", (done) => {
69 | runEmit({
70 | expectedAssetKeys: ["../tempdir/newdirectory/newfile.txt"],
71 | patterns: [
72 | {
73 | from: "file.txt",
74 | to: path.join(TEMP_DIR, "newdirectory/newfile.txt"),
75 | },
76 | ],
77 | })
78 | .then(done)
79 | .catch(done);
80 | });
81 |
82 | it("should copy a file using invalid template syntax", (done) => {
83 | runEmit({
84 | expectedAssetKeys: ["directory/[md5::base64:20].txt"],
85 | patterns: [
86 | {
87 | from: "directory/directoryfile.txt",
88 | to: "directory/[md5::base64:20].txt",
89 | },
90 | ],
91 | })
92 | .then(done)
93 | .catch(done);
94 | });
95 | });
96 |
97 | describe("is a directory", () => {
98 | it("should copy a file to a new directory", (done) => {
99 | runEmit({
100 | expectedAssetKeys: ["newdirectory/file.txt"],
101 | patterns: [
102 | {
103 | from: "file.txt",
104 | to: "newdirectory",
105 | },
106 | ],
107 | })
108 | .then(done)
109 | .catch(done);
110 | });
111 |
112 | it("should copy a file to a new directory out of context", (done) => {
113 | runEmit({
114 | expectedAssetKeys: ["../tempdir/file.txt"],
115 | patterns: [
116 | {
117 | from: "file.txt",
118 | to: TEMP_DIR,
119 | },
120 | ],
121 | })
122 | .then(done)
123 | .catch(done);
124 | });
125 |
126 | it("should copy a file to a new directory with a forward slash", (done) => {
127 | runEmit({
128 | expectedAssetKeys: ["newdirectory/file.txt"],
129 | patterns: [
130 | {
131 | from: "file.txt",
132 | to: "newdirectory/",
133 | },
134 | ],
135 | })
136 | .then(done)
137 | .catch(done);
138 | });
139 |
140 | it("should copy a file to a new directory with an extension and path separator at end", (done) => {
141 | runEmit({
142 | expectedAssetKeys: ["newdirectory.ext/file.txt"],
143 | patterns: [
144 | {
145 | from: "file.txt",
146 | to: `newdirectory.ext${path.sep}`,
147 | },
148 | ],
149 | })
150 | .then(done)
151 | .catch(done);
152 | });
153 |
154 | it('should copy a file to a new directory when "to" is absolute path', (done) => {
155 | runEmit({
156 | expectedAssetKeys: ["file.txt"],
157 | patterns: [
158 | {
159 | from: "file.txt",
160 | to: BUILD_DIR,
161 | },
162 | ],
163 | })
164 | .then(done)
165 | .catch(done);
166 | });
167 |
168 | it('should copy a file to a new directory when "to" is absolute path with a forward slash', (done) => {
169 | runEmit({
170 | expectedAssetKeys: ["file.txt"],
171 | patterns: [
172 | {
173 | from: "file.txt",
174 | to: `${BUILD_DIR}/`,
175 | },
176 | ],
177 | })
178 | .then(done)
179 | .catch(done);
180 | });
181 |
182 | it("should copy a file to a new directory from nested directory", (done) => {
183 | runEmit({
184 | expectedAssetKeys: ["newdirectory/directoryfile.txt"],
185 | patterns: [
186 | {
187 | from: "directory/directoryfile.txt",
188 | to: "newdirectory",
189 | },
190 | ],
191 | })
192 | .then(done)
193 | .catch(done);
194 | });
195 |
196 | it('should copy a file to a new directory from nested directory when "from" is absolute path', (done) => {
197 | runEmit({
198 | expectedAssetKeys: ["newdirectory/directoryfile.txt"],
199 | patterns: [
200 | {
201 | from: path.join(FIXTURES_DIR, "directory", "directoryfile.txt"),
202 | to: "newdirectory",
203 | },
204 | ],
205 | })
206 | .then(done)
207 | .catch(done);
208 | });
209 |
210 | it('should copy a file to a new directory from nested directory when "from" is absolute path with a forward slash', (done) => {
211 | runEmit({
212 | expectedAssetKeys: ["newdirectory/directoryfile.txt"],
213 | patterns: [
214 | {
215 | from: path.join(FIXTURES_DIR, "directory", "directoryfile.txt"),
216 | to: "newdirectory/",
217 | },
218 | ],
219 | })
220 | .then(done)
221 | .catch(done);
222 | });
223 |
224 | it("should copy files to a new directory", (done) => {
225 | runEmit({
226 | expectedAssetKeys: [
227 | "newdirectory/.dottedfile",
228 | "newdirectory/directoryfile.txt",
229 | "newdirectory/nested/deep-nested/deepnested.txt",
230 | "newdirectory/nested/nestedfile.txt",
231 | ],
232 | patterns: [
233 | {
234 | from: "directory",
235 | to: "newdirectory",
236 | },
237 | ],
238 | })
239 | .then(done)
240 | .catch(done);
241 | });
242 |
243 | it("should copy files to a new nested directory", (done) => {
244 | runEmit({
245 | expectedAssetKeys: [
246 | "newdirectory/deep-nested/deepnested.txt",
247 | "newdirectory/nestedfile.txt",
248 | ],
249 | patterns: [
250 | {
251 | from: path.join(FIXTURES_DIR, "directory", "nested"),
252 | to: "newdirectory",
253 | },
254 | ],
255 | })
256 | .then(done)
257 | .catch(done);
258 | });
259 |
260 | it("should copy files to a new directory out of context", (done) => {
261 | runEmit({
262 | expectedAssetKeys: [
263 | "../tempdir/.dottedfile",
264 | "../tempdir/directoryfile.txt",
265 | "../tempdir/nested/deep-nested/deepnested.txt",
266 | "../tempdir/nested/nestedfile.txt",
267 | ],
268 | patterns: [
269 | {
270 | from: "directory",
271 | to: TEMP_DIR,
272 | },
273 | ],
274 | })
275 | .then(done)
276 | .catch(done);
277 | });
278 |
279 | it('should copy files to a new directory when "to" is absolute path', (done) => {
280 | runEmit({
281 | expectedAssetKeys: [
282 | ".dottedfile",
283 | "directoryfile.txt",
284 | "nested/deep-nested/deepnested.txt",
285 | "nested/nestedfile.txt",
286 | ],
287 | patterns: [
288 | {
289 | from: "directory",
290 | to: BUILD_DIR,
291 | },
292 | ],
293 | })
294 | .then(done)
295 | .catch(done);
296 | });
297 |
298 | it('should copy files to a new directory when "to" is absolute path with a forward slash', (done) => {
299 | runEmit({
300 | expectedAssetKeys: [
301 | ".dottedfile",
302 | "directoryfile.txt",
303 | "nested/deep-nested/deepnested.txt",
304 | "nested/nestedfile.txt",
305 | ],
306 | patterns: [
307 | {
308 | from: "directory",
309 | to: `${BUILD_DIR}/`,
310 | },
311 | ],
312 | })
313 | .then(done)
314 | .catch(done);
315 | });
316 |
317 | it("should copy files to a new directory from nested directory", (done) => {
318 | runEmit({
319 | expectedAssetKeys: [
320 | "newdirectory/deep-nested/deepnested.txt",
321 | "newdirectory/nestedfile.txt",
322 | ],
323 | patterns: [
324 | {
325 | from: "directory/nested",
326 | to: "newdirectory",
327 | },
328 | ],
329 | })
330 | .then(done)
331 | .catch(done);
332 | });
333 |
334 | it('should copy a file to a new directory when "to" is empty', (done) => {
335 | runEmit({
336 | expectedAssetKeys: ["file.txt"],
337 | patterns: [
338 | {
339 | from: "file.txt",
340 | to: "",
341 | },
342 | ],
343 | })
344 | .then(done)
345 | .catch(done);
346 | });
347 | });
348 |
349 | describe("is a template", () => {
350 | it('should copy a file using "contenthash"', (done) => {
351 | const compiler = getCompiler({
352 | output: {
353 | hashDigestLength: 6,
354 | },
355 | });
356 |
357 | runEmit({
358 | compiler,
359 | expectedAssetKeys: ["directory/5d7817.txt"],
360 | patterns: [
361 | {
362 | from: "directory/directoryfile.txt",
363 | to: "directory/[contenthash].txt",
364 | },
365 | ],
366 | })
367 | .then(done)
368 | .catch(done);
369 | });
370 |
371 | it("should copy a file using custom `contenthash` digest", (done) => {
372 | const compiler = getCompiler({
373 | output: {
374 | hashFunction: "sha1",
375 | hashDigest: "hex",
376 | hashDigestLength: 4,
377 | },
378 | });
379 |
380 | runEmit({
381 | expectedAssetKeys: ["directory/c2a6.txt"],
382 | patterns: [
383 | {
384 | from: "directory/directoryfile.txt",
385 | to: "directory/[contenthash].txt",
386 | },
387 | ],
388 | compiler,
389 | })
390 | .then(done)
391 | .catch(done);
392 | });
393 |
394 | it("should copy a file using `contenthash` with hashSalt", (done) => {
395 | const compiler = getCompiler({
396 | output: {
397 | hashSalt: "qwerty",
398 | },
399 | });
400 |
401 | runEmit({
402 | expectedAssetKeys: ["directory/64cc145fc382934bd97a.txt"],
403 | patterns: [
404 | {
405 | from: "directory/directoryfile.txt",
406 | to: "directory/[contenthash].txt",
407 | },
408 | ],
409 | compiler,
410 | })
411 | .then(done)
412 | .catch(done);
413 | });
414 |
415 | it('should copy a file using "name" and "ext"', (done) => {
416 | runEmit({
417 | expectedAssetKeys: ["binextension.bin"],
418 | patterns: [
419 | {
420 | from: "binextension.bin",
421 | to: "[name][ext]",
422 | },
423 | ],
424 | })
425 | .then(done)
426 | .catch(done);
427 | });
428 |
429 | it('should copy a file using "name", "contenthash" and "ext"', (done) => {
430 | runEmit({
431 | expectedAssetKeys: ["file-5d7817.txt"],
432 | patterns: [
433 | {
434 | from: "file.txt",
435 | to: "[name]-[contenthash:6][ext]",
436 | },
437 | ],
438 | })
439 | .then(done)
440 | .catch(done);
441 | });
442 |
443 | it("should copy a file from nested directory", (done) => {
444 | runEmit({
445 | expectedAssetKeys: ["directoryfile-5d7817.txt"],
446 | patterns: [
447 | {
448 | from: "directory/directoryfile.txt",
449 | to: "[name]-[contenthash:6][ext]",
450 | },
451 | ],
452 | })
453 | .then(done)
454 | .catch(done);
455 | });
456 |
457 | it("should copy a file from nested directory to new directory", (done) => {
458 | runEmit({
459 | expectedAssetKeys: ["newdirectory/directoryfile-5d7817.txt"],
460 | patterns: [
461 | {
462 | from: "directory/directoryfile.txt",
463 | to: "newdirectory/[name]-[contenthash:6][ext]",
464 | },
465 | ],
466 | })
467 | .then(done)
468 | .catch(done);
469 | });
470 |
471 | it('should copy a file without an extension using "name", "ext"', (done) => {
472 | runEmit({
473 | expectedAssetKeys: ["noextension.31d6cf.newext"],
474 | patterns: [
475 | {
476 | from: "noextension",
477 | to: "[name][ext].[contenthash:6].newext",
478 | },
479 | ],
480 | })
481 | .then(done)
482 | .catch(done);
483 | });
484 |
485 | it('should copy files using "path", "name", "contenthash" and "ext"', (done) => {
486 | runEmit({
487 | expectedAssetKeys: [
488 | "newdirectory/.dottedfile-5e294e",
489 | "newdirectory/directoryfile-5d7817.txt",
490 | "newdirectory/nested/deep-nested/deepnested-31d6cf.txt",
491 | "newdirectory/nested/nestedfile-31d6cf.txt",
492 | ],
493 | patterns: [
494 | {
495 | from: "directory",
496 | to: "newdirectory/[path][name]-[contenthash:6][ext]",
497 | },
498 | ],
499 | })
500 | .then(done)
501 | .catch(done);
502 | });
503 |
504 | it('should copy a file to "compiler.options.output" by default', (done) => {
505 | runEmit({
506 | compilation: { output: { path: "/path/to" } },
507 | expectedAssetKeys: ["newfile.txt"],
508 | patterns: [
509 | {
510 | from: "file.txt",
511 | to: "newfile.txt",
512 | },
513 | ],
514 | })
515 | .then(done)
516 | .catch(done);
517 | });
518 | });
519 |
520 | describe("to option as function", () => {
521 | it('should transform target path when "from" is a file', (done) => {
522 | runEmit({
523 | expectedAssetKeys: ["subdir/test.txt"],
524 | patterns: [
525 | {
526 | from: "file.txt",
527 | to({ context, absoluteFilename }) {
528 | expect(absoluteFilename).toBe(
529 | path.join(FIXTURES_DIR, "file.txt"),
530 | );
531 |
532 | const targetPath = path.relative(context, absoluteFilename);
533 |
534 | return targetPath.replace("file.txt", "subdir/test.txt");
535 | },
536 | },
537 | ],
538 | })
539 | .then(done)
540 | .catch(done);
541 | });
542 |
543 | it('should transform target path of every when "from" is a directory', (done) => {
544 | runEmit({
545 | expectedAssetKeys: [
546 | "../.dottedfile",
547 | "../deepnested.txt",
548 | "../directoryfile.txt",
549 | "../nestedfile.txt",
550 | ],
551 | patterns: [
552 | {
553 | from: "directory",
554 | toType: "file",
555 | to({ context, absoluteFilename }) {
556 | expect(
557 | absoluteFilename.includes(path.join(FIXTURES_DIR, "directory")),
558 | ).toBe(true);
559 |
560 | const targetPath = path.relative(context, absoluteFilename);
561 |
562 | return path.resolve(__dirname, path.basename(targetPath));
563 | },
564 | },
565 | ],
566 | })
567 | .then(done)
568 | .catch(done);
569 | });
570 |
571 | it('should transform target path of every file when "from" is a glob', (done) => {
572 | runEmit({
573 | expectedAssetKeys: [
574 | "../deepnested.txt.tst",
575 | "../directoryfile.txt.tst",
576 | "../nestedfile.txt.tst",
577 | ],
578 | patterns: [
579 | {
580 | from: "directory/**/*",
581 | to({ context, absoluteFilename }) {
582 | expect(absoluteFilename.includes(FIXTURES_DIR)).toBe(true);
583 |
584 | const targetPath = path.relative(context, absoluteFilename);
585 |
586 | return path.resolve(
587 | __dirname,
588 | `${path.basename(targetPath)}.tst`,
589 | );
590 | },
591 | },
592 | ],
593 | })
594 | .then(done)
595 | .catch(done);
596 | });
597 |
598 | it("should transform target path when function return Promise", (done) => {
599 | runEmit({
600 | expectedAssetKeys: ["../file.txt"],
601 | patterns: [
602 | {
603 | from: "file.txt",
604 | to({ context, absoluteFilename }) {
605 | expect(absoluteFilename.includes(FIXTURES_DIR)).toBe(true);
606 |
607 | const targetPath = path.relative(context, absoluteFilename);
608 |
609 | return new Promise((resolve) => {
610 | resolve(path.resolve(__dirname, path.basename(targetPath)));
611 | });
612 | },
613 | },
614 | ],
615 | })
616 | .then(done)
617 | .catch(done);
618 | });
619 |
620 | it("should transform target path when async function used", (done) => {
621 | runEmit({
622 | expectedAssetKeys: ["../file.txt"],
623 | patterns: [
624 | {
625 | from: "file.txt",
626 | async to({ context, absoluteFilename }) {
627 | expect(absoluteFilename.includes(FIXTURES_DIR)).toBe(true);
628 |
629 | const targetPath = path.relative(context, absoluteFilename);
630 |
631 | const newPath = await new Promise((resolve) => {
632 | resolve(path.resolve(__dirname, path.basename(targetPath)));
633 | });
634 |
635 | return newPath;
636 | },
637 | },
638 | ],
639 | })
640 | .then(done)
641 | .catch(done);
642 | });
643 |
644 | it("should warn when function throw error", (done) => {
645 | runEmit({
646 | expectedAssetKeys: [],
647 | expectedErrors: [new Error("a failure happened")],
648 | patterns: [
649 | {
650 | from: "file.txt",
651 | to() {
652 | throw new Error("a failure happened");
653 | },
654 | },
655 | ],
656 | })
657 | .then(done)
658 | .catch(done);
659 | });
660 |
661 | it("should warn when Promise was rejected", (done) => {
662 | runEmit({
663 | expectedAssetKeys: [],
664 | expectedErrors: [new Error("a failure happened")],
665 | patterns: [
666 | {
667 | from: "file.txt",
668 | to() {
669 | return new Promise((resolve, reject) =>
670 | reject(new Error("a failure happened")),
671 | );
672 | },
673 | },
674 | ],
675 | })
676 | .then(done)
677 | .catch(done);
678 | });
679 |
680 | it("should warn when async function throw error", (done) => {
681 | runEmit({
682 | expectedAssetKeys: [],
683 | expectedErrors: [new Error("a failure happened")],
684 | patterns: [
685 | {
686 | from: "file.txt",
687 | async to() {
688 | await new Promise((resolve, reject) => {
689 | reject(new Error("a failure happened"));
690 | });
691 | },
692 | },
693 | ],
694 | })
695 | .then(done)
696 | .catch(done);
697 | });
698 |
699 | it("should transform target path of every file in glob after applying template", (done) => {
700 | runEmit({
701 | expectedAssetKeys: [
702 | "transformed/directory/directoryfile-5d7817.txt",
703 | "transformed/directory/nested/deep-nested/deepnested-31d6cf.txt",
704 | "transformed/directory/nested/nestedfile-31d6cf.txt",
705 | ],
706 | patterns: [
707 | {
708 | from: "directory/**/*",
709 | to({ absoluteFilename }) {
710 | expect(absoluteFilename.includes(FIXTURES_DIR)).toBe(true);
711 |
712 | return "transformed/[path][name]-[contenthash:6][ext]";
713 | },
714 | },
715 | ],
716 | })
717 | .then(done)
718 | .catch(done);
719 | });
720 |
721 | it("should copy files", (done) => {
722 | runEmit({
723 | expectedAssetKeys: ["txt"],
724 | patterns: [
725 | {
726 | from: "directory/nested/deep-nested",
727 | toType: "file",
728 | to({ absoluteFilename }) {
729 | const mathes = absoluteFilename.match(/\.([^.]*)$/);
730 | const [, res] = mathes;
731 | const target = res;
732 |
733 | return target;
734 | },
735 | },
736 | ],
737 | })
738 | .then(done)
739 | .catch(done);
740 | });
741 |
742 | it("should copy files to a non-root directory", (done) => {
743 | runEmit({
744 | expectedAssetKeys: ["nested/txt"],
745 | patterns: [
746 | {
747 | from: "directory/nested/deep-nested",
748 | toType: "file",
749 | to({ absoluteFilename }) {
750 | const mathes = absoluteFilename.match(/\.([^.]*)$/);
751 | const [, res] = mathes;
752 | const target = `nested/${res}`;
753 |
754 | return target;
755 | },
756 | },
757 | ],
758 | })
759 | .then(done)
760 | .catch(done);
761 | });
762 |
763 | it("should copy files", (done) => {
764 | runEmit({
765 | expectedAssetKeys: [
766 | "deep-nested-deepnested.txt",
767 | "directoryfile.txt",
768 | "nested-nestedfile.txt",
769 | ],
770 | patterns: [
771 | {
772 | from: "**/*",
773 | context: "directory",
774 | to({ context, absoluteFilename }) {
775 | const targetPath = path.relative(context, absoluteFilename);
776 | const pathSegments = path.parse(targetPath);
777 | const result = [];
778 |
779 | if (pathSegments.root) {
780 | result.push(pathSegments.root);
781 | }
782 |
783 | if (pathSegments.dir) {
784 | result.push(pathSegments.dir.split(path.sep).pop());
785 | }
786 |
787 | if (pathSegments.base) {
788 | result.push(pathSegments.base);
789 | }
790 |
791 | return result.join("-");
792 | },
793 | },
794 | ],
795 | })
796 | .then(done)
797 | .catch(done);
798 | });
799 | });
800 |
801 | describe("settings for to option for flatten copy", () => {
802 | it('should flatten a directory\'s files to a root directory when "from" is a file', (done) => {
803 | runEmit({
804 | expectedAssetKeys: ["directoryfile.txt"],
805 | patterns: [
806 | {
807 | to: ".",
808 | from: "directory/directoryfile.txt",
809 | },
810 | ],
811 | })
812 | .then(done)
813 | .catch(done);
814 | });
815 |
816 | it('should flatten a directory\'s files to a new directory when "from" is a file', (done) => {
817 | runEmit({
818 | expectedAssetKeys: ["nested/directoryfile.txt"],
819 | patterns: [
820 | {
821 | to({ absoluteFilename }) {
822 | return `nested/${path.basename(absoluteFilename)}`;
823 | },
824 | from: "directory/directoryfile.txt",
825 | },
826 | ],
827 | })
828 | .then(done)
829 | .catch(done);
830 | });
831 |
832 | it('should flatten a directory\'s files to a root directory when "from" is a directory', (done) => {
833 | runEmit({
834 | expectedAssetKeys: [
835 | ".dottedfile",
836 | "deepnested.txt",
837 | "directoryfile.txt",
838 | "nestedfile.txt",
839 | ],
840 | patterns: [
841 | {
842 | to: "[name][ext]",
843 | from: "directory",
844 | },
845 | ],
846 | })
847 | .then(done)
848 | .catch(done);
849 | });
850 |
851 | it('should flatten a directory\'s files to new directory when "from" is a directory', (done) => {
852 | runEmit({
853 | expectedAssetKeys: [
854 | "newdirectory/.dottedfile",
855 | "newdirectory/deepnested.txt",
856 | "newdirectory/directoryfile.txt",
857 | "newdirectory/nestedfile.txt",
858 | ],
859 | patterns: [
860 | {
861 | toType: "file",
862 | to({ absoluteFilename }) {
863 | return `newdirectory/${path.basename(absoluteFilename)}`;
864 | },
865 | from: "directory",
866 | },
867 | ],
868 | })
869 | .then(done)
870 | .catch(done);
871 | });
872 |
873 | it('should flatten a directory\'s files to a root directory when "from" is a glob', (done) => {
874 | runEmit({
875 | expectedAssetKeys: [
876 | "deepnested.txt",
877 | "directoryfile.txt",
878 | "nestedfile.txt",
879 | ],
880 | patterns: [
881 | {
882 | to({ absoluteFilename }) {
883 | return path.basename(absoluteFilename);
884 | },
885 | from: "directory/**/*",
886 | },
887 | ],
888 | })
889 | .then(done)
890 | .catch(done);
891 | });
892 |
893 | it('should flatten a directory\'s files to a new directory when "from" is a glob', (done) => {
894 | runEmit({
895 | expectedAssetKeys: [
896 | "nested/deepnested.txt",
897 | "nested/directoryfile.txt",
898 | "nested/nestedfile.txt",
899 | ],
900 | patterns: [
901 | {
902 | to({ absoluteFilename }) {
903 | return `nested/${path.basename(absoluteFilename)}`;
904 | },
905 | from: "directory/**/*",
906 | },
907 | ],
908 | })
909 | .then(done)
910 | .catch(done);
911 | });
912 |
913 | it('should flatten files in a relative context to a root directory when "from" is a glob', (done) => {
914 | runEmit({
915 | expectedAssetKeys: [
916 | "deepnested.txt",
917 | "directoryfile.txt",
918 | "nestedfile.txt",
919 | ],
920 | patterns: [
921 | {
922 | context: "directory",
923 | from: "**/*",
924 | to({ absoluteFilename }) {
925 | return path.basename(absoluteFilename);
926 | },
927 | },
928 | ],
929 | })
930 | .then(done)
931 | .catch(done);
932 | });
933 |
934 | it('should flatten files in a relative context to a non-root directory when "from" is a glob', (done) => {
935 | runEmit({
936 | expectedAssetKeys: [
937 | "nested/deepnested.txt",
938 | "nested/directoryfile.txt",
939 | "nested/nestedfile.txt",
940 | ],
941 | patterns: [
942 | {
943 | context: "directory",
944 | from: "**/*",
945 | to({ absoluteFilename }) {
946 | return `nested/${path.basename(absoluteFilename)}`;
947 | },
948 | },
949 | ],
950 | })
951 | .then(done)
952 | .catch(done);
953 | });
954 | });
955 |
956 | it("should process template string", (done) => {
957 | runEmit({
958 | expectedAssetKeys: [
959 | "directory/directoryfile.txt-new-directoryfile.txt.5d7817ed5bc246756d73.47e8bdc316eff74b2d6e-47e8bdc316eff74b2d6e.txt--[unknown]",
960 | ],
961 | patterns: [
962 | {
963 | from: "directory/directoryfile.*",
964 | to: "[path][base]-new-[name][ext].[contenthash].[hash]-[fullhash][ext]--[unknown]",
965 | },
966 | ],
967 | })
968 | .then(done)
969 | .catch(done);
970 | });
971 |
972 | it("should rewrite invalid [contenthash] in 'production' mode", (done) => {
973 | const compiler = getCompiler({
974 | mode: "production",
975 | });
976 |
977 | runEmit({
978 | compiler,
979 | breakContenthash: {
980 | // eslint-disable-next-line no-useless-escape
981 | targetAssets: [
982 | {
983 | name: "5d7817ed5bc246756d73-directoryfile.txt",
984 | newName: "33333333333333333333-directoryfile.txt",
985 | newHash: "33333333333333333333",
986 | },
987 | ],
988 | },
989 | expectedAssetKeys: ["5d7817ed5bc246756d73-directoryfile.txt"],
990 | patterns: [
991 | {
992 | from: "directory/directoryfile.*",
993 | to: "[contenthash]-[name][ext]",
994 | toType: "template",
995 | },
996 | ],
997 | })
998 | .then(done)
999 | .catch(done);
1000 | });
1001 | });
1002 |
--------------------------------------------------------------------------------
/test/toType-option.test.js:
--------------------------------------------------------------------------------
1 | import path from "path";
2 |
3 | import { runEmit } from "./helpers/run";
4 |
5 | const FIXTURES_DIR_NORMALIZED = path
6 | .join(__dirname, "fixtures")
7 | .replace(/\\/g, "/");
8 |
9 | describe("toType option", () => {
10 | it("should copy a file to a new file", (done) => {
11 | runEmit({
12 | expectedAssetKeys: ["new-file.txt"],
13 | patterns: [
14 | {
15 | from: "file.txt",
16 | to: "new-file.txt",
17 | toType: "file",
18 | },
19 | ],
20 | })
21 | .then(done)
22 | .catch(done);
23 | });
24 |
25 | it("should copy a file to a new directory", (done) => {
26 | runEmit({
27 | expectedAssetKeys: ["new-file.txt/file.txt"],
28 | patterns: [
29 | {
30 | from: "file.txt",
31 | to: "new-file.txt",
32 | toType: "dir",
33 | },
34 | ],
35 | })
36 | .then(done)
37 | .catch(done);
38 | });
39 |
40 | it("should copy a file to a new directory", (done) => {
41 | runEmit({
42 | expectedAssetKeys: [
43 | "directory/directoryfile.txt-new-directoryfile.txt.5d7817ed5bc246756d73.47e8bdc316eff74b2d6e-47e8bdc316eff74b2d6e.txt",
44 | ],
45 | patterns: [
46 | {
47 | from: "directory/directoryfile.*",
48 | to: "[path][base]-new-[name][ext].[contenthash].[hash]-[fullhash][ext]",
49 | toType: "template",
50 | },
51 | ],
52 | })
53 | .then(done)
54 | .catch(done);
55 | });
56 |
57 | it("should copy a file to a new file with no extension", (done) => {
58 | runEmit({
59 | expectedAssetKeys: ["newname"],
60 | patterns: [
61 | {
62 | from: "file.txt",
63 | to: "newname",
64 | toType: "file",
65 | },
66 | ],
67 | })
68 | .then(done)
69 | .catch(done);
70 | });
71 |
72 | it("should copy a file to a new directory with an extension", (done) => {
73 | runEmit({
74 | expectedAssetKeys: ["newdirectory.ext/file.txt"],
75 | patterns: [
76 | {
77 | from: "file.txt",
78 | to: "newdirectory.ext",
79 | toType: "dir",
80 | },
81 | ],
82 | })
83 | .then(done)
84 | .catch(done);
85 | });
86 |
87 | it("should warn when file not found and stats is undefined", (done) => {
88 | runEmit({
89 | expectedAssetKeys: [],
90 | expectedErrors: [
91 | new Error(
92 | `unable to locate '${FIXTURES_DIR_NORMALIZED}/nonexistent.txt' glob`,
93 | ),
94 | ],
95 | patterns: [
96 | {
97 | from: "nonexistent.txt",
98 | to: ".",
99 | toType: "dir",
100 | },
101 | ],
102 | })
103 | .then(done)
104 | .catch(done);
105 | });
106 | });
107 |
--------------------------------------------------------------------------------
/test/transform-option.test.js:
--------------------------------------------------------------------------------
1 | import path from "path";
2 | import zlib from "zlib";
3 |
4 | import { run, runEmit } from "./helpers/run";
5 |
6 | const FIXTURES_DIR = path.join(__dirname, "fixtures");
7 |
8 | describe("transform option", () => {
9 | it('should transform file when "from" is a file', (done) => {
10 | runEmit({
11 | expectedAssetKeys: ["file.txt"],
12 | expectedAssetContent: {
13 | "file.txt": "newchanged",
14 | },
15 | patterns: [
16 | {
17 | from: "file.txt",
18 | transform: {
19 | transformer(content, absoluteFrom) {
20 | expect(absoluteFrom.includes(FIXTURES_DIR)).toBe(true);
21 |
22 | return `${content}changed`;
23 | },
24 | },
25 | },
26 | ],
27 | })
28 | .then(done)
29 | .catch(done);
30 | });
31 |
32 | it('should transform target path of every when "from" is a directory', (done) => {
33 | runEmit({
34 | expectedAssetKeys: [
35 | ".dottedfile",
36 | "directoryfile.txt",
37 | "nested/deep-nested/deepnested.txt",
38 | "nested/nestedfile.txt",
39 | ],
40 | expectedAssetContent: {
41 | ".dottedfile": "dottedfile contents\nchanged",
42 | "directoryfile.txt": "newchanged",
43 | "nested/deep-nested/deepnested.txt": "changed",
44 | "nested/nestedfile.txt": "changed",
45 | },
46 | patterns: [
47 | {
48 | from: "directory",
49 | transform: {
50 | transformer(content, absoluteFrom) {
51 | expect(absoluteFrom.includes(FIXTURES_DIR)).toBe(true);
52 |
53 | return `${content}changed`;
54 | },
55 | },
56 | },
57 | ],
58 | })
59 | .then(done)
60 | .catch(done);
61 | });
62 |
63 | it('should transform target path of every file when "from" is a glob', (done) => {
64 | runEmit({
65 | expectedAssetKeys: [
66 | "directory/directoryfile.txt",
67 | "directory/nested/deep-nested/deepnested.txt",
68 | "directory/nested/nestedfile.txt",
69 | ],
70 | expectedAssetContent: {
71 | "directory/directoryfile.txt": "newchanged",
72 | "directory/nested/deep-nested/deepnested.txt": "changed",
73 | "directory/nested/nestedfile.txt": "changed",
74 | },
75 | patterns: [
76 | {
77 | from: "directory/**/*",
78 | transform: {
79 | transformer(content, absoluteFrom) {
80 | expect(absoluteFrom.includes(FIXTURES_DIR)).toBe(true);
81 |
82 | return `${content}changed`;
83 | },
84 | },
85 | },
86 | ],
87 | })
88 | .then(done)
89 | .catch(done);
90 | });
91 |
92 | it("should transform file when transform is function", (done) => {
93 | runEmit({
94 | expectedAssetKeys: ["file.txt"],
95 | expectedAssetContent: {
96 | "file.txt": "newchanged!",
97 | },
98 | patterns: [
99 | {
100 | from: "file.txt",
101 | transform: (content) => `${content}changed!`,
102 | },
103 | ],
104 | })
105 | .then(done)
106 | .catch(done);
107 | });
108 |
109 | it("should transform file when function return Promise", (done) => {
110 | runEmit({
111 | expectedAssetKeys: ["file.txt"],
112 | expectedAssetContent: {
113 | "file.txt": "newchanged!",
114 | },
115 | patterns: [
116 | {
117 | from: "file.txt",
118 | transform(content) {
119 | return new Promise((resolve) => {
120 | resolve(`${content}changed!`);
121 | });
122 | },
123 | },
124 | ],
125 | })
126 | .then(done)
127 | .catch(done);
128 | });
129 |
130 | it("should transform file when function `transformer` return Promise", (done) => {
131 | runEmit({
132 | expectedAssetKeys: ["file.txt"],
133 | expectedAssetContent: {
134 | "file.txt": "newchanged!",
135 | },
136 | patterns: [
137 | {
138 | from: "file.txt",
139 | transform: {
140 | transformer(content) {
141 | return new Promise((resolve) => {
142 | resolve(`${content}changed!`);
143 | });
144 | },
145 | },
146 | },
147 | ],
148 | })
149 | .then(done)
150 | .catch(done);
151 | });
152 |
153 | it("should transform target path when async function used", (done) => {
154 | runEmit({
155 | expectedAssetKeys: ["file.txt"],
156 | expectedAssetContent: {
157 | "file.txt": "newchanged!",
158 | },
159 | patterns: [
160 | {
161 | from: "file.txt",
162 | transform: {
163 | async transformer(content) {
164 | const newPath = await new Promise((resolve) => {
165 | resolve(`${content}changed!`);
166 | });
167 |
168 | return newPath;
169 | },
170 | },
171 | },
172 | ],
173 | })
174 | .then(done)
175 | .catch(done);
176 | });
177 |
178 | it("should warn when function throw error", (done) => {
179 | runEmit({
180 | expectedAssetKeys: [],
181 | expectedErrors: [new Error("a failure happened")],
182 | patterns: [
183 | {
184 | from: "file.txt",
185 | transform: {
186 | transformer() {
187 | // eslint-disable-next-line no-throw-literal
188 | throw new Error("a failure happened");
189 | },
190 | },
191 | },
192 | ],
193 | })
194 | .then(done)
195 | .catch(done);
196 | });
197 |
198 | it("should warn when Promise was rejected", (done) => {
199 | runEmit({
200 | expectedAssetKeys: [],
201 | expectedErrors: [new Error("a failure happened")],
202 | patterns: [
203 | {
204 | from: "file.txt",
205 | transform: {
206 | transformer() {
207 | return new Promise((resolve, reject) =>
208 | reject(new Error("a failure happened")),
209 | );
210 | },
211 | },
212 | },
213 | ],
214 | })
215 | .then(done)
216 | .catch(done);
217 | });
218 |
219 | it("should warn when async function throw error", (done) => {
220 | runEmit({
221 | expectedAssetKeys: [],
222 | expectedErrors: [new Error("a failure happened")],
223 | patterns: [
224 | {
225 | from: "file.txt",
226 | transform: {
227 | async transformer() {
228 | await new Promise((resolve, reject) => {
229 | reject(new Error("a failure happened"));
230 | });
231 | },
232 | },
233 | },
234 | ],
235 | })
236 | .then(done)
237 | .catch(done);
238 | });
239 |
240 | it("should be a different size for the source file and the converted file", (done) => {
241 | run({
242 | patterns: [
243 | {
244 | from: "file.txt",
245 | },
246 | {
247 | from: "file.txt",
248 | to: "file.txt.gz",
249 | transform: {
250 | transformer: (content) => zlib.gzipSync(content),
251 | },
252 | },
253 | ],
254 | })
255 | .then(({ compilation }) => {
256 | expect(
257 | compilation.assets["file.txt"].size() !==
258 | compilation.assets["file.txt.gz"].size(),
259 | ).toBe(true);
260 | })
261 | .then(done)
262 | .catch(done);
263 | });
264 |
265 | it('should transform file when "from" is a file', (done) => {
266 | runEmit({
267 | expectedAssetKeys: ["subdir/test.txt"],
268 | expectedAssetContent: {
269 | "subdir/test.txt": "newchanged",
270 | },
271 | patterns: [
272 | {
273 | from: "file.txt",
274 | transform: {
275 | transformer(content, absoluteFrom) {
276 | expect(absoluteFrom.includes(FIXTURES_DIR)).toBe(true);
277 |
278 | return `${content}changed`;
279 | },
280 | },
281 | to({ context, absoluteFilename }) {
282 | expect(absoluteFilename).toBe(path.join(FIXTURES_DIR, "file.txt"));
283 |
284 | const targetPath = path.relative(context, absoluteFilename);
285 |
286 | return targetPath.replace("file.txt", "subdir/test.txt");
287 | },
288 | },
289 | ],
290 | })
291 | .then(done)
292 | .catch(done);
293 | });
294 | });
295 |
--------------------------------------------------------------------------------
/test/transformAll-option.test.js:
--------------------------------------------------------------------------------
1 | import CopyPlugin from "../src";
2 |
3 | import { runEmit } from "./helpers/run";
4 | import { compile, getCompiler, readAssets } from "./helpers";
5 |
6 | describe("transformAll option", () => {
7 | it('should be defined "assets"', (done) => {
8 | runEmit({
9 | expectedAssetKeys: ["file.txt"],
10 | patterns: [
11 | {
12 | from: "file.txt",
13 | to: "file.txt",
14 | transformAll(assets) {
15 | expect(assets).toBeDefined();
16 |
17 | return "";
18 | },
19 | },
20 | ],
21 | })
22 | .then(done)
23 | .catch(done);
24 | });
25 |
26 | it("should transform files", (done) => {
27 | runEmit({
28 | expectedAssetKeys: ["file.txt"],
29 | expectedAssetContent: {
30 | "file.txt":
31 | "new::directory/nested/deep-nested/deepnested.txt::directory/nested/nestedfile.txt::",
32 | },
33 | patterns: [
34 | {
35 | from: "directory/**/*.txt",
36 | to: "file.txt",
37 | transformAll(assets) {
38 | const result = assets.reduce((accumulator, asset) => {
39 | const content = asset.data.toString() || asset.sourceFilename;
40 | // eslint-disable-next-line no-param-reassign
41 | accumulator = `${accumulator}${content}::`;
42 | return accumulator;
43 | }, "");
44 |
45 | return result;
46 | },
47 | },
48 | ],
49 | })
50 | .then(done)
51 | .catch(done);
52 | });
53 |
54 | it("should transform files when async function used", (done) => {
55 | runEmit({
56 | expectedAssetKeys: ["file.txt"],
57 | expectedAssetContent: {
58 | "file.txt":
59 | "directory/directoryfile.txt::directory/nested/deep-nested/deepnested.txt::directory/nested/nestedfile.txt::",
60 | },
61 | patterns: [
62 | {
63 | from: "directory/**/*.txt",
64 | to: "file.txt",
65 | async transformAll(assets) {
66 | const result = assets.reduce((accumulator, asset) => {
67 | // eslint-disable-next-line no-param-reassign
68 | accumulator = `${accumulator}${asset.sourceFilename}::`;
69 | return accumulator;
70 | }, "");
71 |
72 | return result;
73 | },
74 | },
75 | ],
76 | })
77 | .then(done)
78 | .catch(done);
79 | });
80 |
81 | it("should transform files with force option enabled", (done) => {
82 | runEmit({
83 | expectedAssetKeys: ["file.txt"],
84 | expectedAssetContent: {
85 | "file.txt":
86 | "directory/directoryfile.txt::directory/nested/deep-nested/deepnested.txt::directory/nested/nestedfile.txt::",
87 | },
88 | patterns: [
89 | {
90 | from: "file.txt",
91 | },
92 | {
93 | from: "directory/**/*.txt",
94 | to: "file.txt",
95 | transformAll(assets) {
96 | const result = assets.reduce((accumulator, asset) => {
97 | // eslint-disable-next-line no-param-reassign
98 | accumulator = `${accumulator}${asset.sourceFilename}::`;
99 | return accumulator;
100 | }, "");
101 |
102 | return result;
103 | },
104 | force: true,
105 | },
106 | ],
107 | })
108 | .then(done)
109 | .catch(done);
110 | });
111 |
112 | it('should warn when "to" option is not defined', (done) => {
113 | runEmit({
114 | expectedAssetKeys: [],
115 | expectedErrors: [
116 | new Error(
117 | `Invalid "pattern.to" for the "pattern.from": "file.txt" and "pattern.transformAll" function. The "to" option must be specified.`,
118 | ),
119 | ],
120 | patterns: [
121 | {
122 | from: "file.txt",
123 | transformAll() {
124 | return "";
125 | },
126 | },
127 | ],
128 | })
129 | .then(done)
130 | .catch(done);
131 | });
132 |
133 | it("should warn when function throw error", (done) => {
134 | runEmit({
135 | expectedAssetKeys: [],
136 | expectedErrors: [new Error("a failure happened")],
137 | patterns: [
138 | {
139 | from: "directory/**/*.txt",
140 | to: "file.txt",
141 | transformAll() {
142 | // eslint-disable-next-line no-throw-literal
143 | throw new Error("a failure happened");
144 | },
145 | },
146 | ],
147 | })
148 | .then(done)
149 | .catch(done);
150 | });
151 |
152 | it("should interpolate [fullhash] and [contenthash]", (done) => {
153 | runEmit({
154 | expectedAssetKeys: ["4333a40fa67dfaaaefc9-47e8bdc316eff74b2d6e-file.txt"],
155 | expectedAssetContent: {
156 | "4333a40fa67dfaaaefc9-47e8bdc316eff74b2d6e-file.txt":
157 | "::special::new::::::::::new::::::new::",
158 | },
159 | patterns: [
160 | {
161 | from: "**/*.txt",
162 | to: "[contenthash]-[fullhash]-file.txt",
163 | transformAll(assets) {
164 | return assets.reduce((accumulator, asset) => {
165 | // eslint-disable-next-line no-param-reassign
166 | accumulator = `${accumulator}${asset.data}::`;
167 | return accumulator;
168 | }, "");
169 | },
170 | },
171 | ],
172 | })
173 | .then(done)
174 | .catch(done);
175 | });
176 |
177 | it("should interpolate [fullhash] and [contenthash] #2", (done) => {
178 | runEmit({
179 | expectedAssetKeys: ["4333a40fa67dfaaaefc9-47e8bdc316eff74b2d6e-file.txt"],
180 | expectedAssetContent: {
181 | "4333a40fa67dfaaaefc9-47e8bdc316eff74b2d6e-file.txt":
182 | "::special::new::::::::::new::::::new::",
183 | },
184 | patterns: [
185 | {
186 | from: "**/*.txt",
187 | to: () => "[contenthash]-[fullhash]-file.txt",
188 | transformAll(assets) {
189 | return assets.reduce((accumulator, asset) => {
190 | // eslint-disable-next-line no-param-reassign
191 | accumulator = `${accumulator}${asset.data}::`;
192 |
193 | return accumulator;
194 | }, "");
195 | },
196 | },
197 | ],
198 | })
199 | .then(done)
200 | .catch(done);
201 | });
202 | });
203 |
204 | describe("cache", () => {
205 | it('should work with the "memory" cache', async () => {
206 | const compiler = getCompiler({});
207 |
208 | new CopyPlugin({
209 | patterns: [
210 | {
211 | from: "directory/**/*.txt",
212 | to: "file.txt",
213 | transformAll(assets) {
214 | const result = assets.reduce((accumulator, asset) => {
215 | const content = asset.data.toString() || asset.sourceFilename;
216 | // eslint-disable-next-line no-param-reassign
217 | accumulator = `${accumulator}${content}::`;
218 | return accumulator;
219 | }, "");
220 |
221 | return result;
222 | },
223 | },
224 | ],
225 | }).apply(compiler);
226 |
227 | const { stats } = await compile(compiler);
228 |
229 | expect(stats.compilation.emittedAssets.size).toBe(2);
230 | expect(readAssets(compiler, stats)).toMatchSnapshot("assets");
231 | expect(stats.compilation.errors).toMatchSnapshot("errors");
232 | expect(stats.compilation.warnings).toMatchSnapshot("warnings");
233 |
234 | await new Promise(async (resolve) => {
235 | const { stats: newStats } = await compile(compiler);
236 |
237 | expect(newStats.compilation.emittedAssets.size).toBe(0);
238 | expect(readAssets(compiler, newStats)).toMatchSnapshot("assets");
239 | expect(newStats.compilation.errors).toMatchSnapshot("errors");
240 | expect(newStats.compilation.warnings).toMatchSnapshot("warnings");
241 |
242 | resolve();
243 | });
244 | });
245 | });
246 |
--------------------------------------------------------------------------------
/test/validate-options.test.js:
--------------------------------------------------------------------------------
1 | import CopyPlugin from "../src/index";
2 |
3 | describe("validate options", () => {
4 | const tests = {
5 | patterns: {
6 | success: [
7 | ["test.txt"],
8 | ["test.txt", "test-other.txt"],
9 | [
10 | "test.txt",
11 | {
12 | from: "test.txt",
13 | to: "dir",
14 | context: "context",
15 | },
16 | ],
17 | [
18 | {
19 | from: "test.txt",
20 | },
21 | ],
22 | [
23 | {
24 | from: "test.txt",
25 | to: "dir",
26 | },
27 | ],
28 | [
29 | {
30 | from: "test.txt",
31 | to: () => {},
32 | },
33 | ],
34 | [
35 | {
36 | from: "test.txt",
37 | context: "context",
38 | },
39 | ],
40 | [
41 | {
42 | from: "test.txt",
43 | to: "dir",
44 | context: "context",
45 | toType: "file",
46 | force: true,
47 | transform: {
48 | transformer: () => {},
49 | cache: true,
50 | },
51 | noErrorOnMissing: true,
52 | },
53 | ],
54 | [
55 | {
56 | from: "test.txt",
57 | to: "dir",
58 | context: "context",
59 | transform: () => {},
60 | },
61 | ],
62 | [
63 | {
64 | from: "test.txt",
65 | to: "dir",
66 | context: "context",
67 | globOptions: {
68 | dot: false,
69 | },
70 | },
71 | ],
72 | [
73 | {
74 | from: "test.txt",
75 | to: "dir",
76 | context: "context",
77 | transform: {
78 | cache: {
79 | keys: {
80 | foo: "bar",
81 | },
82 | },
83 | },
84 | },
85 | ],
86 | [
87 | {
88 | from: "test.txt",
89 | to: "dir",
90 | context: "context",
91 | transform: {
92 | cache: {
93 | keys: () => {
94 | return {
95 | foo: "bar",
96 | };
97 | },
98 | },
99 | },
100 | },
101 | ],
102 | [
103 | {
104 | from: "test.txt",
105 | to: "dir",
106 | context: "context",
107 | transform: {
108 | cache: {
109 | keys: async () => {
110 | return {
111 | foo: "bar",
112 | };
113 | },
114 | },
115 | },
116 | },
117 | ],
118 | [
119 | {
120 | from: "test.txt",
121 | filter: () => true,
122 | },
123 | ],
124 | [
125 | {
126 | from: "test.txt",
127 | info: { custom: true },
128 | },
129 | {
130 | from: "test.txt",
131 | info: () => {
132 | return { custom: true };
133 | },
134 | },
135 | ],
136 | [
137 | {
138 | from: "test.txt",
139 | to: "dir",
140 | priority: 5,
141 | },
142 | ],
143 | [
144 | {
145 | from: "test.txt",
146 | to: "dir",
147 | context: "context",
148 | transformAll: ({ existingAsset }) => existingAsset.source.source(),
149 | },
150 | ],
151 | ],
152 | failure: [
153 | // eslint-disable-next-line no-undefined
154 | undefined,
155 | true,
156 | "true",
157 | "",
158 | {},
159 | [],
160 | [""],
161 | [{}],
162 | [
163 | {
164 | from: "dir",
165 | info: "string",
166 | },
167 | ],
168 | [
169 | {
170 | from: "dir",
171 | info: true,
172 | },
173 | ],
174 | [
175 | {
176 | from: "",
177 | to: "dir",
178 | context: "context",
179 | },
180 | ],
181 | [
182 | {
183 | from: true,
184 | to: "dir",
185 | context: "context",
186 | },
187 | ],
188 | [
189 | {
190 | from: "test.txt",
191 | to: true,
192 | context: "context",
193 | },
194 | ],
195 | [
196 | {
197 | from: "test.txt",
198 | to: "dir",
199 | context: true,
200 | },
201 | ],
202 | [
203 | {
204 | from: "test.txt",
205 | to: "dir",
206 | context: "context",
207 | toType: "foo",
208 | },
209 | ],
210 | [
211 | {
212 | from: "test.txt",
213 | to: "dir",
214 | context: "context",
215 | force: "true",
216 | },
217 | ],
218 | [
219 | {
220 | from: "test.txt",
221 | to: "dir",
222 | context: "context",
223 | transform: {
224 | foo: "bar",
225 | },
226 | },
227 | ],
228 | [
229 | {
230 | from: "test.txt",
231 | to: "dir",
232 | context: "context",
233 | transform: true,
234 | },
235 | ],
236 | [
237 | {
238 | from: {
239 | glob: "**/*",
240 | dot: false,
241 | },
242 | to: "dir",
243 | context: "context",
244 | },
245 | ],
246 | [
247 | {
248 | from: "",
249 | to: "dir",
250 | context: "context",
251 | noErrorOnMissing: "true",
252 | },
253 | ],
254 | [
255 | {
256 | from: "test.txt",
257 | filter: "test",
258 | },
259 | ],
260 | [
261 | {
262 | from: "test.txt",
263 | to: "dir",
264 | priority: "5",
265 | },
266 | ],
267 | [
268 | {
269 | from: "test.txt",
270 | to: "dir",
271 | priority: () => {},
272 | },
273 | ],
274 | [
275 | {
276 | from: "test.txt",
277 | to: "dir",
278 | priority: true,
279 | },
280 | ],
281 | [
282 | {
283 | from: "test.txt",
284 | to: "dir",
285 | context: "context",
286 | transformAll: true,
287 | },
288 | ],
289 | ],
290 | },
291 | options: {
292 | success: [{ concurrency: 50 }],
293 | failure: [{ unknown: true }, { concurrency: true }],
294 | },
295 | unknown: {
296 | success: [],
297 | failure: [1, true, false, "test", /test/, [], {}, { foo: "bar" }],
298 | },
299 | };
300 |
301 | function stringifyValue(value) {
302 | if (
303 | Array.isArray(value) ||
304 | (value && typeof value === "object" && value.constructor === Object)
305 | ) {
306 | return JSON.stringify(value);
307 | }
308 |
309 | return value;
310 | }
311 |
312 | async function createTestCase(key, value, type) {
313 | it(`should ${
314 | type === "success" ? "successfully validate" : "throw an error on"
315 | } the "${key}" option with "${stringifyValue(value)}" value`, async () => {
316 | let error;
317 |
318 | try {
319 | // eslint-disable-next-line no-new
320 | new CopyPlugin(
321 | key === "options"
322 | ? { patterns: [{ from: "file.txt" }], [key]: value }
323 | : { [key]: value },
324 | );
325 | } catch (errorFromPlugin) {
326 | if (errorFromPlugin.name !== "ValidationError") {
327 | throw errorFromPlugin;
328 | }
329 |
330 | error = errorFromPlugin;
331 | } finally {
332 | if (type === "success") {
333 | expect(error).toBeUndefined();
334 | } else if (type === "failure") {
335 | expect(() => {
336 | throw error;
337 | }).toThrowErrorMatchingSnapshot();
338 | }
339 | }
340 | });
341 | }
342 |
343 | for (const [key, values] of Object.entries(tests)) {
344 | for (const type of Object.keys(values)) {
345 | for (const value of values[type]) {
346 | createTestCase(key, value, type);
347 | }
348 | }
349 | }
350 | });
351 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "esnext",
4 | "moduleResolution": "node",
5 | "allowJs": true,
6 | "checkJs": true,
7 | "strict": true,
8 | "types": ["node"],
9 | "resolveJsonModule": true,
10 | "allowSyntheticDefaultImports": true
11 | },
12 | "include": ["./src/**/*"]
13 | }
14 |
--------------------------------------------------------------------------------
/types/index.d.ts:
--------------------------------------------------------------------------------
1 | export = CopyPlugin;
2 | declare class CopyPlugin {
3 | /**
4 | * @private
5 | * @param {Compilation} compilation
6 | * @param {number} startTime
7 | * @param {string} dependency
8 | * @returns {Promise}
9 | */
10 | private static createSnapshot;
11 | /**
12 | * @private
13 | * @param {Compilation} compilation
14 | * @param {Snapshot} snapshot
15 | * @returns {Promise}
16 | */
17 | private static checkSnapshotValid;
18 | /**
19 | * @private
20 | * @param {Compiler} compiler
21 | * @param {Compilation} compilation
22 | * @param {Buffer} source
23 | * @returns {string}
24 | */
25 | private static getContentHash;
26 | /**
27 | * @private
28 | * @param {typeof import("tinyglobby").glob} globby
29 | * @param {Compiler} compiler
30 | * @param {Compilation} compilation
31 | * @param {WebpackLogger} logger
32 | * @param {CacheFacade} cache
33 | * @param {number} concurrency
34 | * @param {ObjectPattern & { context: string }} inputPattern
35 | * @param {number} index
36 | * @returns {Promise | undefined>}
37 | */
38 | private static glob;
39 | /**
40 | * @param {PluginOptions} [options]
41 | */
42 | constructor(options?: PluginOptions);
43 | /**
44 | * @private
45 | * @type {Pattern[]}
46 | */
47 | private patterns;
48 | /**
49 | * @private
50 | * @type {AdditionalOptions}
51 | */
52 | private options;
53 | /**
54 | * @param {Compiler} compiler
55 | */
56 | apply(compiler: Compiler): void;
57 | }
58 | declare namespace CopyPlugin {
59 | export {
60 | Schema,
61 | Compiler,
62 | Compilation,
63 | WebpackError,
64 | Asset,
65 | GlobbyOptions,
66 | WebpackLogger,
67 | CacheFacade,
68 | Etag,
69 | Snapshot,
70 | Force,
71 | CopiedResult,
72 | StringPattern,
73 | NoErrorOnMissing,
74 | Context,
75 | From,
76 | ToFunction,
77 | To,
78 | ToType,
79 | TransformerFunction,
80 | TransformerCacheObject,
81 | TransformerObject,
82 | Transform,
83 | Filter,
84 | TransformAllFunction,
85 | Info,
86 | ObjectPattern,
87 | Pattern,
88 | AdditionalOptions,
89 | PluginOptions,
90 | };
91 | }
92 | type Schema = import("schema-utils/declarations/validate").Schema;
93 | type Compiler = import("webpack").Compiler;
94 | type Compilation = import("webpack").Compilation;
95 | type WebpackError = import("webpack").WebpackError;
96 | type Asset = import("webpack").Asset;
97 | type GlobbyOptions = import("tinyglobby").GlobOptions;
98 | type WebpackLogger = ReturnType;
99 | type CacheFacade = ReturnType;
100 | type Etag = ReturnType<
101 | ReturnType["getLazyHashedEtag"]
102 | >;
103 | type Snapshot = ReturnType;
104 | type Force = boolean;
105 | type CopiedResult = {
106 | sourceFilename: string;
107 | absoluteFilename: string;
108 | filename: string;
109 | source: Asset["source"];
110 | force: Force | undefined;
111 | info: Record;
112 | };
113 | type StringPattern = string;
114 | type NoErrorOnMissing = boolean;
115 | type Context = string;
116 | type From = string;
117 | type ToFunction = (pathData: {
118 | context: string;
119 | absoluteFilename?: string;
120 | }) => string | Promise;
121 | type To = string | ToFunction;
122 | type ToType = "dir" | "file" | "template";
123 | type TransformerFunction = (
124 | input: Buffer,
125 | absoluteFilename: string,
126 | ) => string | Buffer | Promise | Promise;
127 | type TransformerCacheObject =
128 | | {
129 | keys: {
130 | [key: string]: any;
131 | };
132 | }
133 | | {
134 | keys: (
135 | defaultCacheKeys: {
136 | [key: string]: any;
137 | },
138 | absoluteFilename: string,
139 | ) => Promise<{
140 | [key: string]: any;
141 | }>;
142 | };
143 | type TransformerObject = {
144 | transformer: TransformerFunction;
145 | cache?: boolean | TransformerCacheObject | undefined;
146 | };
147 | type Transform = TransformerFunction | TransformerObject;
148 | type Filter = (filepath: string) => boolean | Promise;
149 | type TransformAllFunction = (
150 | data: {
151 | data: Buffer;
152 | sourceFilename: string;
153 | absoluteFilename: string;
154 | }[],
155 | ) => string | Buffer | Promise | Promise;
156 | type Info =
157 | | Record
158 | | ((item: {
159 | absoluteFilename: string;
160 | sourceFilename: string;
161 | filename: string;
162 | toType: ToType;
163 | }) => Record);
164 | type ObjectPattern = {
165 | from: From;
166 | globOptions?: import("tinyglobby").GlobOptions | undefined;
167 | context?: string | undefined;
168 | to?: To | undefined;
169 | toType?: ToType | undefined;
170 | info?: Info | undefined;
171 | filter?: Filter | undefined;
172 | transform?: Transform | undefined;
173 | transformAll?: TransformAllFunction | undefined;
174 | force?: boolean | undefined;
175 | priority?: number | undefined;
176 | noErrorOnMissing?: boolean | undefined;
177 | };
178 | type Pattern = StringPattern | ObjectPattern;
179 | type AdditionalOptions = {
180 | concurrency?: number | undefined;
181 | };
182 | type PluginOptions = {
183 | patterns: Pattern[];
184 | options?: AdditionalOptions | undefined;
185 | };
186 |
--------------------------------------------------------------------------------
/types/utils.d.ts:
--------------------------------------------------------------------------------
1 | export type InputFileSystem = import("webpack").Compilation["inputFileSystem"];
2 | export type Stats = import("fs").Stats;
3 | export type Task = () => Promise;
4 | /** @typedef {import("webpack").Compilation["inputFileSystem"] } InputFileSystem */
5 | /** @typedef {import("fs").Stats } Stats */
6 | /**
7 | * @param {InputFileSystem} inputFileSystem
8 | * @param {string} path
9 | * @return {Promise}
10 | */
11 | export function stat(
12 | inputFileSystem: InputFileSystem,
13 | path: string,
14 | ): Promise;
15 | /**
16 | * @param {InputFileSystem} inputFileSystem
17 | * @param {string} path
18 | * @return {Promise}
19 | */
20 | export function readFile(
21 | inputFileSystem: InputFileSystem,
22 | path: string,
23 | ): Promise;
24 | /**
25 | * @template T
26 | * @typedef {() => Promise} Task
27 | */
28 | /**
29 | * Run tasks with limited concurrency.
30 | * @template T
31 | * @param {number} limit - Limit of tasks that run at once.
32 | * @param {Task[]} tasks - List of tasks to run.
33 | * @returns {Promise} A promise that fulfills to an array of the results
34 | */
35 | export function throttleAll(limit: number, tasks: Task[]): Promise;
36 | /**
37 | * @template T
38 | * @param fn {(function(): any) | undefined}
39 | * @returns {function(): T}
40 | */
41 | export function memoize(fn: (() => any) | undefined): () => T;
42 |
--------------------------------------------------------------------------------