Munter / netlify-plugin-hashfiles

Netlify build plugin to get optimal file caching with hashed file names and immutable cache headers
BSD 3-Clause "New" or "Revised" License
32 stars 2 forks source link

Error: ENOENT: no such file or directory, unlink #107

Open fedegl opened 4 years ago

fedegl commented 4 years ago

6:10:41 PM: │ 3. onPostBuild command from netlify-plugin-hashfiles │
6:10:41 PM: └──────────────────────────────────────────────────────┘
6:10:41 PM: ​
6:10:41 PM:  ✔ 0.001 secs: logEvents
6:10:42 PM:  ✔ 0.885 secs: loadAssets
6:10:42 PM:  ⚠ WARN: ENOENT: no such file or directory, open 'output/ordenar'
6:10:42 PM:  ⚠ WARN: ENOENT: no such file or directory, open 'output/contacto'
6:10:42 PM:  ⚠ WARN: ENOENT: no such file or directory, open 'output/aviso-de-privacidad'
6:10:43 PM:  ⚠ WARN: ENOENT: no such file or directory, open 'output/android-icon-36x36.png'
6:10:43 PM:  ⚠ WARN: ENOENT: no such file or directory, open 'output/android-icon-48x48.png'
6:10:43 PM:  ⚠ WARN: ENOENT: no such file or directory, open 'output/android-icon-72x72.png'
6:10:43 PM:  ⚠ WARN: ENOENT: no such file or directory, open 'output/android-icon-144x144.png'
6:10:43 PM:  ⚠ WARN: ENOENT: no such file or directory, open 'output/android-icon-96x96.png'
6:10:43 PM:  ⚠ WARN: ENOENT: no such file or directory, open 'output/android-icon-192x192.png'
6:10:43 PM:  ✔ 0.566 secs: populate
6:10:43 PM:  ✔ 0.122 secs: moveAssetsInOrder
6:10:43 PM:  ✔ 0.184 secs: moveAssetsInOrder
6:10:43 PM: ​
6:10:43 PM: ┌──────────────────────────────────────────────────┐
6:10:43 PM: │ Plugin "netlify-plugin-hashfiles" internal error │
6:10:43 PM: └──────────────────────────────────────────────────┘
6:10:43 PM: ​
6:10:43 PM:   Error message
6:10:43 PM:   UnhandledRejection: a promise was rejected but not handled: Error: ENOENT: no such file or directory, unlink '/opt/build/repo/output/_bridgetown/static/fonts/d6cbd8208050bd66f295b8e6c5ae8e10.eot'
6:10:43 PM:   Error: ENOENT: no such file or directory, unlink '/opt/build/repo/output/_bridgetown/static/fonts/d6cbd8208050bd66f295b8e6c5ae8e10.eot'
6:10:43 PM: ​
6:10:43 PM:   Plugin details
6:10:43 PM:   Package:        netlify-plugin-hashfiles
6:10:43 PM:   Version:        4.0.2
6:10:43 PM:   Repository:     git://github.com/munter/netlify-plugin-hashfiles.git
6:10:43 PM:   npm link:       https://www.npmjs.com/package/netlify-plugin-hashfiles
6:10:43 PM:   Report issues:  https://github.com/munter/netlify-plugin-hashfiles/issues
6:10:43 PM: ​
6:10:43 PM:   Error location
6:10:43 PM:   In "onPostBuild" event in "netlify-plugin-hashfiles" from Netlify app
6:10:43 PM: ​
6:10:43 PM:   Error properties
6:10:43 PM:   {
6:10:43 PM:     errno: -2,
6:10:43 PM:     code: 'ENOENT',
6:10:43 PM:     syscall: 'unlink',
6:10:43 PM:     path: '/opt/build/repo/output/_bridgetown/static/fonts/d6cbd8208050bd66f295b8e6c5ae8e10.eot'
6:10:43 PM:   }
6:10:43 PM: ​
6:10:43 PM:   Resolved config
6:10:43 PM:   build:
6:10:43 PM:     command: yarn deploy
6:10:43 PM:     publish: /opt/build/repo/output
6:10:43 PM:   plugins:
6:10:43 PM:     - inputs: {}
6:10:43 PM:       origin: ui
6:10:43 PM:       package: '@netlify/plugin-sitemap'
6:10:43 PM:     - inputs: {}
6:10:43 PM:       origin: ui
6:10:43 PM:       package: netlify-plugin-hashfiles```
Munter commented 4 years ago

Thanks for the report.

From the error it looks like you are linking to a .eot font which doesn't exist. Is this a correct interpretation, or does that .eot font actually exist in your file system?

If the repository for this page is public, could you link to it to help me recreate the problem?

Munter commented 4 years ago

@ehmicky No stack traces in the netlify-build output any more?

ehmicky commented 4 years ago

There should be a stack trace. I am not sure why this is not shown in this specific case though.

fedegl commented 4 years ago

@Munter The file is there.

Screen Shot 2020-06-24 at 10 11 20

The repository is not public, but I could grant you access if that would be useful.

jonleighton commented 4 years ago

@Munter Ha, I bumped into this issue too and was trying to work up a patch just now :slightly_smiling_face:

Some debugging revealed that graph.findAssets() is returning the same file multiple times, at least in my project. This means that the result array gets multiple entries with the same { from, to } pair. Thus, the problem here is that we're trying to delete the same path multiple times. The second deletion obviously fails.

Here's the patch I've applied:

diff --git a/plugins/netlify-plugin-hashfiles/lib/index.js b/plugins/netlify-plugin-hashfiles/lib/index.js
index bdc8a3f..1ea7f36 100644
--- a/plugins/netlify-plugin-hashfiles/lib/index.js
+++ b/plugins/netlify-plugin-hashfiles/lib/index.js
@@ -38,21 +38,20 @@ module.exports = {

     await hashfiles(graph, { trimmedStaticDir });

-    const result = [];
+    const result = new Map();

     for (const asset of graph.findAssets({ isLoaded: true, isInline: false })) {
       const from = pathMap.get(asset);
       const to = asset.url.replace('file://', '').split('?')[0];

       if (from !== to) {
-        result.push({
-          from,
-          to,
-        });
+        result.set(from, to);
       }
     }

-    const deletePromises = Promise.all(result.map((r) => deleteFile(r.from)));
+    const deletePromises = Promise.all(
+      Array.from(result, ([from, to]) => deleteFile(from))
+    );

     const writePromise = await graph.writeAssetsToDisc(
       { isLoaded: true },
@@ -75,11 +74,10 @@ module.exports = {

     console.log('** hashfiles moved files: **');
     console.log(
-      result
-        .map(
-          ({ from, to }) => `${relative(root, from)} ==> ${relative(root, to)}`
-        )
-        .join('\n')
+      Array.from(
+        result,
+        ([from, to]) => `${relative(root, from)} ==> ${relative(root, to)}`
+      ).join('\n')
     );
   },
 };

I guess one advantage of this is that it also fixes the console output -- with your fix above, the output will still show the same path repeatedly.

jonleighton commented 4 years ago

FWIW, I find it surprising that this plugin deletes the original files at all... it doesn't really seem necessary?

Munter commented 4 years ago

@jonleighton Lol, this is great timing! I couldn't figure out how to recreate the problem, so I just opted for the lazy solution to avoid people being blocked.

I'm a bit surprised you're finding that assetgraph returns the same asset multiple times in that query. This sounds like a bug in assetgraph, since each asset should be deduplicated using the URL as an identifier during graph population.

Do you have a way to recreate this problem? I'd love if you could submit a PR for a test case. I just added a bunch in the test folder, so it should be a bit easier to help recreate bugs like these now

Munter commented 4 years ago

FWIW, I find it surprising that this plugin deletes the original files at all... it doesn't really seem necessary?

It isn't strictly necessary. It's an attempt at reducing the distribution size so the deployment to the CDN doesn't become slower

jonleighton commented 4 years ago

Sure, I'll try to dig into it a bit more to figure out why it's returning the same asset multiple times :+1:

jonleighton commented 4 years ago

It isn't strictly necessary. It's an attempt at reducing the distribution size so the deployment to the CDN doesn't become slower

Would be nice to provide a way to turn this off. I just came across a problem where I have:

  <meta name="og:image" content="https://my.site/path/to/photo.jpg">

It isn't getting rewritten by the plugin (possibly due to the absolute URL?), so is now a broken link. However, AFAICT an absolute URL is mandatory for this.

Munter commented 4 years ago

This sounds like a separate bug. Assetgraph does support open-graph images and it also does have the capability to classify an absolute URL as an internal URL, provided that the graphs canonicalRoot is set and matches with the absolute URL. If that content attribute is not updated to point tp the hashed asset there is either a misconfiguration of the canonicalRoot or a bug in Assetgraph

Munter commented 4 years ago

I think I might be coming around to not deleting the original files though. There might very well be cases of source references that assetgraph hasn't yet implemented. And if you have an asset that is references by a combination of relations that are supported and ones that are not supported, only the supported ones will get rewritten, and the unsupported ones will indeed link to the now deleted original asset.

I'll remove the file deletion code.

I'd still like to track down both the double asset listing and this open-graph image case

jonleighton commented 4 years ago

Sounds good :+1: I agree there are likely to always be edge cases where the deletion of the original image might be confusing.

That said, I think the og:image problem was my fault. The meta tag should be:

<meta property="og:image" ...>

But I was using:

<meta name="og:image" ...>

When I switched to the correct markup, assetgraph correctly found and replaced the image URL.

I'll try to dig into the duplicate paths problem more tomorrow.

jonleighton commented 4 years ago

Test case submitted here. Though I'm not sure how similar my problem is to @fedegl's, since mine relates to the use of srcset with the same image path repeated multiple times with a different query string, but the original issue related to a font file.