Repository: webpack-contrib/copy-webpack-plugin
Pull request #564 (2020-12-07 14:14:10)

Feat add info option
<!--
  HOLY CRAP a Pull Request. We ❤️ those!

  If you remove or skip this template, you'll make the 🐼 sad and the mighty god
  of Github will appear and pile-drive the close button from a great height
  while making animal noises.

  Please place an x (no spaces!) in all [ ] that apply
-->

This PR contains a:

- [ ] **bugfix**
- [x] new **feature**
- [ ] **code refactor**
- [x] **test update** <!-- if bug or feature is checked, this should be too -->
- [ ] **typo fix**
- [x] **metadata update**

### Motivation / Use-Case

Added `info` option. 

### Breaking Changes

No

### Additional Info

No


codecov[bot]: # [Codecov](https://codecov.io/gh/webpack-contrib/copy-webpack-plugin/pull/564?src=pr&el=h1) Report
> Merging [#564](https://codecov.io/gh/webpack-contrib/copy-webpack-plugin/pull/564?src=pr&el=desc) (b986ab6) into [master](https://codecov.io/gh/webpack-contrib/copy-webpack-plugin/commit/71676457b5257ada0440aca4aea70ba1fceeaac0?el=desc) (7167645) will **increase** coverage by `0.11%`.
> The diff coverage is `100.00%`.

[![Impacted file tree graph](https://codecov.io/gh/webpack-contrib/copy-webpack-plugin/pull/564/graphs/tree.svg?width=650&height=150&src=pr&token=fEoU9iBFGL)](https://codecov.io/gh/webpack-contrib/copy-webpack-plugin/pull/564?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master     #564      +/-   ##
==========================================
+ Coverage   91.11%   91.22%   +0.11%     
==========================================
  Files           3        3              
  Lines         315      319       +4     
  Branches       96      100       +4     
==========================================
+ Hits          287      291       +4     
  Misses         28       28              
```


| [Impacted Files](https://codecov.io/gh/webpack-contrib/copy-webpack-plugin/pull/564?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [src/index.js](https://codecov.io/gh/webpack-contrib/copy-webpack-plugin/pull/564/diff?src=pr&el=tree#diff-c3JjL2luZGV4Lmpz) | `91.14% <100.00%> (+0.11%)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/webpack-contrib/copy-webpack-plugin/pull/564?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/webpack-contrib/copy-webpack-plugin/pull/564?src=pr&el=footer). Last update [7167645...b986ab6](https://codecov.io/gh/webpack-contrib/copy-webpack-plugin/pull/564?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/README.md b/README.md
index f0f575c..ca1073e 100644
--- a/README.md
+++ b/README.md
@@ -90,6 +90,7 @@ module.exports = {
 |   [`cacheTransform`](#cacheTransform)   | `{Boolean\|String\|Object}` |                     `false`                     | Enable `transform` caching. You can use `{ cache: { key: 'my-cache-key' } }` to invalidate the cache. |
 |    [`transformPath`](#transformpath)    |        `{Function}`         |                   `undefined`                   | Allows to modify the writing path.                                                                    |
 | [`noErrorOnMissing`](#noerroronmissing) |         `{Boolean}`         |                     `false`                     | Doesn't generate an error on missing file(s).                                                         |
+|             [`info`](#info)             |    `{Object\|Function}`     |                   `undefined`                   | Allows to add assets info.                                                                            |
 
 #### `from`
 
@@ -773,6 +774,51 @@ module.exports = {
 };
 ```
 
+#### `info`
+
+Type: `Object|Function<Object>`
+Default: `undefined`
+
+Allows to add assets info.
+
+**webpack.config.js**
+
+```js
+module.exports = {
+  plugins: [
+    new CopyPlugin({
+      patterns: [
+        "relative/path/to/file.ext",
+        {
+          from: "**/*",
+          // Terser skip this file for minimization
+          info: { minimized: true },
+        },
+      ],
+    }),
+  ],
+};
+```
+
+**webpack.config.js**
+
+```js
+module.exports = {
+  plugins: [
+    new CopyPlugin({
+      patterns: [
+        "relative/path/to/file.ext",
+        {
+          from: "**/*",
+          // Terser skip this file for minimization
+          info: (file) => ({ minimized: true }),
+        },
+      ],
+    }),
+  ],
+};
+```
+
 ### Options
 
 |             Name              |    Type    | Default | Description                                      |
diff --git a/src/index.js b/src/index.js
index f46255b..93de4c5 100644
--- a/src/index.js
+++ b/src/index.js
@@ -360,6 +360,10 @@ class CopyPlugin {
             sourceFilename,
             filename,
             force: pattern.force,
+            info:
+              typeof pattern.info === "function"
+                ? pattern.info(file) || {}
+                : pattern.info || {},
           };
 
           // If this came from a glob or dir, add it to the file dependencies
@@ -731,7 +735,10 @@ class CopyPlugin {
                     `force updating '${filename}' from '${absoluteFilename}' to compilation assets, because it already exists...`
                   );
 
-                  compilation.updateAsset(filename, source, info);
+                  compilation.updateAsset(filename, source, {
+                    ...info,
+                    ...asset.info,
+                  });
 
                   logger.log(
                     `force updated '${filename}' from '${absoluteFilename}' to compilation assets, because it already exists`
@@ -747,17 +754,20 @@ class CopyPlugin {
                 return;
               }
 
-              logger.log(
-                `writing '${filename}' from '${absoluteFilename}' to compilation assets...`
-              );
-
               const info = { copied: true, sourceFilename };
 
               if (asset.immutable) {
                 info.immutable = true;
               }
 
-              compilation.emitAsset(filename, source, info);
+              logger.log(
+                `writing '${filename}' from '${absoluteFilename}' to compilation assets...`
+              );
+
+              compilation.emitAsset(filename, source, {
+                ...info,
+                ...asset.info,
+              });
 
               logger.log(
                 `written '${filename}' from '${absoluteFilename}' to compilation assets`
diff --git a/src/options.json b/src/options.json
index 0e3ed49..667b3a5 100644
--- a/src/options.json
+++ b/src/options.json
@@ -33,6 +33,16 @@
         "force": {
           "type": "boolean"
         },
+        "info": {
+          "anyOf": [
+            {
+              "type": "object"
+            },
+            {
+              "instanceof": "Function"
+            }
+          ]
+        },
         "flatten": {
           "type": "boolean"
         },


Test patch:
diff --git a/test/__snapshots__/validate-options.test.js.snap b/test/__snapshots__/validate-options.test.js.snap
index 992c663..c4631c1 100644
--- a/test/__snapshots__/validate-options.test.js.snap
+++ b/test/__snapshots__/validate-options.test.js.snap
@@ -14,7 +14,7 @@ exports[`validate options should throw an error on the "options" option with "{"
 exports[`validate options should throw an error on the "patterns" option with "" value 1`] = `
 "Invalid options object. Copy Plugin has been initialized using an options object that does not match the API schema.
  - options.patterns should be an array:
-   [non-empty string | object { from, to?, context?, globOptions?, filter?, toType?, force?, flatten?, transform?, cacheTransform?, transformPath?, noErrorOnMissing? }, ...] (should not have fewer than 1 item)"
+   [non-empty string | object { from, to?, context?, globOptions?, filter?, toType?, force?, info?, flatten?, transform?, cacheTransform?, transformPath?, noErrorOnMissing? }, ...] (should not have fewer than 1 item)"
 `;
 
 exports[`validate options should throw an error on the "patterns" option with "[""]" value 1`] = `
@@ -37,6 +37,32 @@ exports[`validate options should throw an error on the "patterns" option with "[
  - options.patterns[0].from should be an non-empty string."
 `;
 
+exports[`validate options should throw an error on the "patterns" option with "[{"from":"dir","info":"string"}]" value 1`] = `
+"Invalid options object. Copy Plugin has been initialized using an options object that does not match the API schema.
+ - options.patterns[0] should be one of these:
+   non-empty string | object { from, to?, context?, globOptions?, filter?, toType?, force?, info?, flatten?, transform?, cacheTransform?, transformPath?, noErrorOnMissing? }
+   Details:
+    * options.patterns[0].info should be one of these:
+      object { … } | function
+      Details:
+       * options.patterns[0].info should be an object:
+         object { … }
+       * options.patterns[0].info should be an instance of function."
+`;
+
+exports[`validate options should throw an error on the "patterns" option with "[{"from":"dir","info":true}]" value 1`] = `
+"Invalid options object. Copy Plugin has been initialized using an options object that does not match the API schema.
+ - options.patterns[0] should be one of these:
+   non-empty string | object { from, to?, context?, globOptions?, filter?, toType?, force?, info?, flatten?, transform?, cacheTransform?, transformPath?, noErrorOnMissing? }
+   Details:
+    * options.patterns[0].info should be one of these:
+      object { … } | function
+      Details:
+       * options.patterns[0].info should be an object:
+         object { … }
+       * options.patterns[0].info should be an instance of function."
+`;
+
 exports[`validate options should throw an error on the "patterns" option with "[{"from":"test.txt","filter":"test"}]" value 1`] = `
 "Invalid options object. Copy Plugin has been initialized using an options object that does not match the API schema.
  - options.patterns[0].filter should be an instance of function."
@@ -77,7 +103,7 @@ exports[`validate options should throw an error on the "patterns" option with "[
 exports[`validate options should throw an error on the "patterns" option with "[{"from":"test.txt","to":"dir","context":"context"}]" value 1`] = `
 "Invalid options object. Copy Plugin has been initialized using an options object that does not match the API schema.
  - options.patterns[0] should be one of these:
-   non-empty string | object { from, to?, context?, globOptions?, filter?, toType?, force?, flatten?, transform?, cacheTransform?, transformPath?, noErrorOnMissing? }
+   non-empty string | object { from, to?, context?, globOptions?, filter?, toType?, force?, info?, flatten?, transform?, cacheTransform?, transformPath?, noErrorOnMissing? }
    Details:
     * options.patterns[0].cacheTransform should be one of these:
       boolean | string | object { directory?, keys? }
@@ -96,7 +122,7 @@ exports[`validate options should throw an error on the "patterns" option with "[
 exports[`validate options should throw an error on the "patterns" option with "[{"from":"test.txt","to":true,"context":"context"}]" value 1`] = `
 "Invalid options object. Copy Plugin has been initialized using an options object that does not match the API schema.
  - options.patterns[0] should be one of these:
-   non-empty string | object { from, to?, context?, globOptions?, filter?, toType?, force?, flatten?, transform?, cacheTransform?, transformPath?, noErrorOnMissing? }
+   non-empty string | object { from, to?, context?, globOptions?, filter?, toType?, force?, info?, flatten?, transform?, cacheTransform?, transformPath?, noErrorOnMissing? }
    Details:
     * options.patterns[0].to should be one of these:
       string | function
@@ -124,71 +150,71 @@ exports[`validate options should throw an error on the "patterns" option with "[
 exports[`validate options should throw an error on the "patterns" option with "{}" value 1`] = `
 "Invalid options object. Copy Plugin has been initialized using an options object that does not match the API schema.
  - options.patterns should be an array:
-   [non-empty string | object { from, to?, context?, globOptions?, filter?, toType?, force?, flatten?, transform?, cacheTransform?, transformPath?, noErrorOnMissing? }, ...] (should not have fewer than 1 item)"
+   [non-empty string | object { from, to?, context?, globOptions?, filter?, toType?, force?, info?, flatten?, transform?, cacheTransform?, transformPath?, noErrorOnMissing? }, ...] (should not have fewer than 1 item)"
 `;
 
 exports[`validate options should throw an error on the "patterns" option with "true" value 1`] = `
 "Invalid options object. Copy Plugin has been initialized using an options object that does not match the API schema.
  - options.patterns should be an array:
-   [non-empty string | object { from, to?, context?, globOptions?, filter?, toType?, force?, flatten?, transform?, cacheTransform?, transformPath?, noErrorOnMissing? }, ...] (should not have fewer than 1 item)"
+   [non-empty string | object { from, to?, context?, globOptions?, filter?, toType?, force?, info?, flatten?, transform?, cacheTransform?, transformPath?, noErrorOnMissing? }, ...] (should not have fewer than 1 item)"
 `;
 
 exports[`validate options should throw an error on the "patterns" option with "true" value 2`] = `
 "Invalid options object. Copy Plugin has been initialized using an options object that does not match the API schema.
  - options.patterns should be an array:
-   [non-empty string | object { from, to?, context?, globOptions?, filter?, toType?, force?, flatten?, transform?, cacheTransform?, transformPath?, noErrorOnMissing? }, ...] (should not have fewer than 1 item)"
+   [non-empty string | object { from, to?, context?, globOptions?, filter?, toType?, force?, info?, flatten?, transform?, cacheTransform?, transformPath?, noErrorOnMissing? }, ...] (should not have fewer than 1 item)"
 `;
 
 exports[`validate options should throw an error on the "patterns" option with "undefined" value 1`] = `
 "Invalid options object. Copy Plugin has been initialized using an options object that does not match the API schema.
  - options misses the property 'patterns'. Should be:
-   [non-empty string | object { from, to?, context?, globOptions?, filter?, toType?, force?, flatten?, transform?, cacheTransform?, transformPath?, noErrorOnMissing? }, ...] (should not have fewer than 1 item)"
+   [non-empty string | object { from, to?, context?, globOptions?, filter?, toType?, force?, info?, flatten?, transform?, cacheTransform?, transformPath?, noErrorOnMissing? }, ...] (should not have fewer than 1 item)"
 `;
 
 exports[`validate options should throw an error on the "unknown" option with "/test/" value 1`] = `
 "Invalid options object. Copy Plugin has been initialized using an options object that does not match the API schema.
  - options misses the property 'patterns'. Should be:
-   [non-empty string | object { from, to?, context?, globOptions?, filter?, toType?, force?, flatten?, transform?, cacheTransform?, transformPath?, noErrorOnMissing? }, ...] (should not have fewer than 1 item)"
+   [non-empty string | object { from, to?, context?, globOptions?, filter?, toType?, force?, info?, flatten?, transform?, cacheTransform?, transformPath?, noErrorOnMissing? }, ...] (should not have fewer than 1 item)"
 `;
 
 exports[`validate options should throw an error on the "unknown" option with "[]" value 1`] = `
 "Invalid options object. Copy Plugin has been initialized using an options object that does not match the API schema.
  - options misses the property 'patterns'. Should be:
-   [non-empty string | object { from, to?, context?, globOptions?, filter?, toType?, force?, flatten?, transform?, cacheTransform?, transformPath?, noErrorOnMissing? }, ...] (should not have fewer than 1 item)"
+   [non-empty string | object { from, to?, context?, globOptions?, filter?, toType?, force?, info?, flatten?, transform?, cacheTransform?, transformPath?, noErrorOnMissing? }, ...] (should not have fewer than 1 item)"
 `;
 
 exports[`validate options should throw an error on the "unknown" option with "{"foo":"bar"}" value 1`] = `
 "Invalid options object. Copy Plugin has been initialized using an options object that does not match the API schema.
  - options misses the property 'patterns'. Should be:
-   [non-empty string | object { from, to?, context?, globOptions?, filter?, toType?, force?, flatten?, transform?, cacheTransform?, transformPath?, noErrorOnMissing? }, ...] (should not have fewer than 1 item)"
+   [non-empty string | object { from, to?, context?, globOptions?, filter?, toType?, force?, info?, flatten?, transform?, cacheTransform?, transformPath?, noErrorOnMissing? }, ...] (should not have fewer than 1 item)"
 `;
 
 exports[`validate options should throw an error on the "unknown" option with "{}" value 1`] = `
 "Invalid options object. Copy Plugin has been initialized using an options object that does not match the API schema.
  - options misses the property 'patterns'. Should be:
-   [non-empty string | object { from, to?, context?, globOptions?, filter?, toType?, force?, flatten?, transform?, cacheTransform?, transformPath?, noErrorOnMissing? }, ...] (should not have fewer than 1 item)"
+   [non-empty string | object { from, to?, context?, globOptions?, filter?, toType?, force?, info?, flatten?, transform?, cacheTransform?, transformPath?, noErrorOnMissing? }, ...] (should not have fewer than 1 item)"
 `;
 
 exports[`validate options should throw an error on the "unknown" option with "1" value 1`] = `
 "Invalid options object. Copy Plugin has been initialized using an options object that does not match the API schema.
  - options misses the property 'patterns'. Should be:
-   [non-empty string | object { from, to?, context?, globOptions?, filter?, toType?, force?, flatten?, transform?, cacheTransform?, transformPath?, noErrorOnMissing? }, ...] (should not have fewer than 1 item)"
+   [non-empty string | object { from, to?, context?, globOptions?, filter?, toType?, force?, info?, flatten?, transform?, cacheTransform?, transformPath?, noErrorOnMissing? }, ...] (should not have fewer than 1 item)"
 `;
 
 exports[`validate options should throw an error on the "unknown" option with "false" value 1`] = `
 "Invalid options object. Copy Plugin has been initialized using an options object that does not match the API schema.
  - options misses the property 'patterns'. Should be:
-   [non-empty string | object { from, to?, context?, globOptions?, filter?, toType?, force?, flatten?, transform?, cacheTransform?, transformPath?, noErrorOnMissing? }, ...] (should not have fewer than 1 item)"
+   [non-empty string | object { from, to?, context?, globOptions?, filter?, toType?, force?, info?, flatten?, transform?, cacheTransform?, transformPath?, noErrorOnMissing? }, ...] (should not have fewer than 1 item)"
 `;
 
 exports[`validate options should throw an error on the "unknown" option with "test" value 1`] = `
 "Invalid options object. Copy Plugin has been initialized using an options object that does not match the API schema.
  - options misses the property 'patterns'. Should be:
-   [non-empty string | object { from, to?, context?, globOptions?, filter?, toType?, force?, flatten?, transform?, cacheTransform?, transformPath?, noErrorOnMissing? }, ...] (should not have fewer than 1 item)"
+   [non-empty string | object { from, to?, context?, globOptions?, filter?, toType?, force?, info?, flatten?, transform?, cacheTransform?, transformPath?, noErrorOnMissing? }, ...] (should not have fewer than 1 item)"
 `;
 
 exports[`validate options should throw an error on the "unknown" option with "true" value 1`] = `
 "Invalid options object. Copy Plugin has been initialized using an options object that does not match the API schema.
  - options misses the property 'patterns'. Should be:
-   [non-empty string | object { from, to?, context?, globOptions?, filter?, toType?, force?, flatten?, transform?, cacheTransform?, transformPath?, noErrorOnMissing? }, ...] (should not have fewer than 1 item)"
+   [non-empty string | object { from, to?, context?, globOptions?, filter?, toType?, force?, info?, flatten?, transform?, cacheTransform?, transformPath?, noErrorOnMissing? }, ...] (should not have fewer than 1 item)"
 `;
diff --git a/test/info-option.test.js b/test/info-option.test.js
new file mode 100644
index 0000000..622c2f5
--- /dev/null
+++ b/test/info-option.test.js
@@ -0,0 +1,110 @@
+import { run, runEmit } from "./helpers/run";
+
+describe("info option", () => {
+  it('should work without "info" option', (done) => {
+    runEmit({
+      expectedAssetKeys: ["file.txt"],
+      patterns: [
+        {
+          from: "file.txt",
+        },
+      ],
+    })
+      .then(done)
+      .catch(done);
+  });
+
+  it('should work when "info" option is a object', (done) => {
+    run({
+      expectedAssetKeys: ["file.txt"],
+      patterns: [
+        {
+          from: "file.txt",
+          info: { test: true },
+        },
+      ],
+    })
+      .then(({ compilation }) => {
+        expect(compilation.assetsInfo.get("file.txt").test).toBe(true);
+      })
+      .then(done)
+      .catch(done);
+  });
+
+  it('should work when "info" option is a object and "force" option is true', (done) => {
+    const expectedAssetKeys = ["file.txt"];
+
+    run({
+      preCopy: {
+        additionalAssets: [
+          { name: "file.txt", data: "Content", info: { custom: true } },
+        ],
+      },
+      expectedAssetKeys,
+      patterns: [
+        {
+          from: "file.txt",
+          force: true,
+          info: { test: true },
+        },
+      ],
+    })
+      .then(({ compilation }) => {
+        expect(compilation.assetsInfo.get("file.txt").test).toBe(true);
+      })
+      .then(done)
+      .catch(done);
+  });
+
+  it('should work when "info" option is a function', (done) => {
+    run({
+      expectedAssetKeys: ["file.txt"],
+      patterns: [
+        {
+          from: "file.txt",
+          info: (file) => {
+            expect.assertions(4);
+
+            const fileKeys = ["absoluteFilename", "sourceFilename", "filename"];
+
+            for (const key of fileKeys) {
+              expect(key in file).toBe(true);
+            }
+
+            return { test: true };
+          },
+        },
+      ],
+    })
+      .then(({ compilation }) => {
+        expect(compilation.assetsInfo.get("file.txt").test).toBe(true);
+      })
+      .then(done)
+      .catch(done);
+  });
+
+  it('should work when "info" option is a function and "force" option is true', (done) => {
+    const expectedAssetKeys = ["file.txt"];
+
+    run({
+      preCopy: {
+        additionalAssets: [
+          { name: "file.txt", data: "Content", info: { custom: true } },
+        ],
+      },
+      expectedAssetKeys,
+      patterns: [
+        {
+          from: "file.txt",
+          force: true,
+          info: () => ({ test: true }),
+        },
+      ],
+    })
+      .then(({ compilation }) => {
+        expect(compilation.assetsInfo.get("file.txt").test).toBe(true);
+      })
+      .then(done)
+      .catch(done);
+  });
+});
diff --git a/test/validate-options.test.js b/test/validate-options.test.js
index 9686377..7c6b787 100644
--- a/test/validate-options.test.js
+++ b/test/validate-options.test.js
@@ -144,6 +144,16 @@ describe("validate options", () => {
             filter: () => true,
           },
         ],
+        [
+          {
+            from: "test.txt",
+            info: { custom: true },
+          },
+          {
+            from: "test.txt",
+            info: () => ({ custom: true }),
+          },
+        ],
       ],
       failure: [
         // eslint-disable-next-line no-undefined
@@ -155,6 +165,18 @@ describe("validate options", () => {
         [],
         [""],
         [{}],
+        [
+          {
+            from: "dir",
+            info: "string",
+          },
+        ],
+        [
+          {
+            from: "dir",
+            info: true,
+          },
+        ],
         [
           {
             from: "",