From bb0bb9c7fa4bb214559d3dcbda42945d8f8e9b70 Mon Sep 17 00:00:00 2001 From: Chuck Date: Fri, 3 May 2019 09:02:29 -0400 Subject: [PATCH] chore(*): bump of sharp for Node v12 compatibility (#13646) --- packages/gatsby-plugin-manifest/.gitignore | 7 +- packages/gatsby-plugin-manifest/README.md | 31 ++++ packages/gatsby-plugin-manifest/package.json | 3 +- .../gatsby-plugin-manifest/src/gatsby-node.js | 2 +- .../gatsby-plugin-manifest/src/safe-sharp.js | 168 ++++++++++++++++++ packages/gatsby-plugin-sharp/README.md | 31 ++++ packages/gatsby-plugin-sharp/package.json | 3 +- packages/gatsby-plugin-sharp/src/duotone.js | 2 +- packages/gatsby-plugin-sharp/src/index.js | 21 +-- .../gatsby-plugin-sharp/src/process-file.js | 2 +- .../gatsby-plugin-sharp/src/safe-sharp.js | 168 ++++++++++++++++++ packages/gatsby-plugin-sharp/src/trace-svg.js | 2 +- .../gatsby-remark-images-contentful/.babelrc | 5 + .../.gitignore | 2 +- .../gatsby-remark-images-contentful/README.md | 31 ++++ .../package.json | 4 +- .../src/index.js | 2 +- .../src/safe-sharp.js | 168 ++++++++++++++++++ packages/gatsby-transformer-sharp/README.md | 31 ++++ .../gatsby-transformer-sharp/package.json | 3 +- .../src/extend-node-type.js | 2 +- .../src/safe-sharp.js | 168 ++++++++++++++++++ .../gatsby-transformer-sharp/src/types.js | 2 +- yarn.lock | 62 +++---- 24 files changed, 848 insertions(+), 72 deletions(-) create mode 100644 packages/gatsby-plugin-manifest/src/safe-sharp.js create mode 100644 packages/gatsby-plugin-sharp/src/safe-sharp.js create mode 100644 packages/gatsby-remark-images-contentful/.babelrc create mode 100644 packages/gatsby-remark-images-contentful/src/safe-sharp.js create mode 100644 packages/gatsby-transformer-sharp/src/safe-sharp.js diff --git a/packages/gatsby-plugin-manifest/.gitignore b/packages/gatsby-plugin-manifest/.gitignore index 9beb76b9199ad..08ddb94941023 100644 --- a/packages/gatsby-plugin-manifest/.gitignore +++ b/packages/gatsby-plugin-manifest/.gitignore @@ -1,5 +1,2 @@ -/gatsby-node.js -/gatsby-ssr.js -/gatsby-browser.js -/common.js -/app-shell.js +/*.js +!index.js diff --git a/packages/gatsby-plugin-manifest/README.md b/packages/gatsby-plugin-manifest/README.md index fdd7e8348a92b..876bd3bf2342b 100644 --- a/packages/gatsby-plugin-manifest/README.md +++ b/packages/gatsby-plugin-manifest/README.md @@ -368,3 +368,34 @@ This article from the Chrome DevRel team is a good intro to the web app manifest—https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/ For more information see the w3 spec https://www.w3.org/TR/appmanifest/ or Mozilla docs https://developer.mozilla.org/en-US/docs/Web/Manifest. + +## Troubleshooting + +### Incompatible library version: sharp.node requires version X or later, but Z provides version Y + +This means that there are multiple incompatible versions of the `sharp` package installed in `node_modules`. The complete error typically looks like this: + +``` +Something went wrong installing the "sharp" module + +dlopen(/Users/misiek/dev/gatsby-starter-blog/node_modules/sharp/build/Release/sharp.node, 1): Library not loaded: @rpath/libglib-2.0.dylib + Referenced from: /Users/misiek/dev/gatsby-starter-blog/node_modules/sharp/build/Release/sharp.node + Reason: Incompatible library version: sharp.node requires version 6001.0.0 or later, but libglib-2.0.dylib provides version 5801.0.0 +``` + +To fix this, you'll need to update all Gatsby plugins in the current project that depend on the `sharp` package. Here's a list of official plugins that you might need to update in case your projects uses them: + +- `gatsby-plugin-sharp` +- `gatsby-plugin-manifest` +- `gatsby-remark-images-contentful` +- `gatsby-source-contentful` +- `gatsby-transformer-sharp` +- `gatsby-transformer-sqip` + +To update these packages, run: + +```sh +npm install gatsby-plugin-sharp gatsby-plugin-manifest gatsby-remark-images-contentful gatsby-source-contentful gatsby-transformer-sharp gatsby-transformer-sqip +``` + +If updating these doesn't fix the issue, your project probably uses other plugins from the community that depend on a different version of `sharp`. Try running `npm list sharp` or `yarn why sharp` to see all packages in the current project that use `sharp` and try updating them as well. diff --git a/packages/gatsby-plugin-manifest/package.json b/packages/gatsby-plugin-manifest/package.json index 36b2c50d57599..b815f5c0e5d81 100644 --- a/packages/gatsby-plugin-manifest/package.json +++ b/packages/gatsby-plugin-manifest/package.json @@ -8,7 +8,8 @@ }, "dependencies": { "@babel/runtime": "^7.0.0", - "sharp": "^0.21.3" + "semver": "^5.6.0", + "sharp": "^0.22.1" }, "devDependencies": { "@babel/cli": "^7.0.0", diff --git a/packages/gatsby-plugin-manifest/src/gatsby-node.js b/packages/gatsby-plugin-manifest/src/gatsby-node.js index 5139b49a91ced..ab9b13e27f43c 100644 --- a/packages/gatsby-plugin-manifest/src/gatsby-node.js +++ b/packages/gatsby-plugin-manifest/src/gatsby-node.js @@ -1,6 +1,6 @@ import fs from "fs" import path from "path" -import sharp from "sharp" +import sharp from "./safe-sharp" import createContentDigest from "gatsby/dist/utils/create-content-digest" import { defaultIcons, doesIconExist, addDigestToPath } from "./common" diff --git a/packages/gatsby-plugin-manifest/src/safe-sharp.js b/packages/gatsby-plugin-manifest/src/safe-sharp.js new file mode 100644 index 0000000000000..6d97dfddd16a0 --- /dev/null +++ b/packages/gatsby-plugin-manifest/src/safe-sharp.js @@ -0,0 +1,168 @@ +// This is very hacky, but this should be temporary hack +// until we make "sharp" a peerDependency and can be removed afterwards. +// It's not done yet because it would cause too much friction. +// Image processing is important part of Gatsby ecosystem +// and there's lot of guides and tutorials that we don't control +// that would be outdated. Moving "sharp" to be peerDependency +// is scheduled for gatsby@3. + +// This file is duplicated in multiple location in this repository, +// So make sure to apply same changes every "safe-sharp" file. + +const childProcess = require(`child_process`) +const path = require(`path`) +const fs = require(`fs`) +const semver = require(`semver`) + +const originalConsoleError = console.error +const restoreConsoleError = () => { + console.error = originalConsoleError +} + +const getDetailedMessage = () => { + try { + // `npm list` seems to work in yarn installed projects as long + // as there is no package-lock.json, so let's bail out + // if both lock files exist + + if ( + fs.existsSync(path.join(process.cwd(), `package-lock.json`)) && + fs.existsSync(path.join(process.cwd(), `yarn.lock`)) + ) { + return null + } + + let msg = [] + const { dependencies } = JSON.parse( + childProcess.execSync(`npm list sharp --json`, { + encoding: `utf-8`, + }) + ) + + const findSharpVersion = dependency => { + if (dependency.dependencies.sharp) { + return dependency.dependencies.sharp.version + } + + for (let depName of Object.keys(dependency.dependencies)) { + const v = findSharpVersion(dependency.dependencies[depName]) + if (v) { + return v + } + } + + return null + } + + const { latestVersion, topLevelPackages } = Object.keys( + dependencies + ).reduce( + (acc, depName) => { + const sharpVersion = findSharpVersion(dependencies[depName]) + if (sharpVersion) { + acc.topLevelPackages[depName] = sharpVersion + + if ( + !acc.latestVersion || + semver.gt(sharpVersion, acc.latestVersion) + ) { + acc.latestVersion = sharpVersion + } + } + + return acc + }, + { + latestVersion: undefined, + topLevelPackages: {}, + } + ) + + let packagesToUpdate = [] + // list top level dependencies + msg = msg.concat([ + `List of installed packages that depend on sharp:`, + ...Object.keys(topLevelPackages).map(depName => { + const sharpVersion = topLevelPackages[depName] + if (sharpVersion !== latestVersion) { + packagesToUpdate.push(depName) + } + return ` - ${depName}${ + sharpVersion + ? ` (${sharpVersion})${ + sharpVersion !== latestVersion ? ` - needs update` : `` + }` + : `` + }` + }), + ]) + + if (packagesToUpdate.length > 0) { + msg = msg.concat([ + ``, + `If you are using npm, run:`, + ``, + `npm install ${packagesToUpdate.join(` `)}`, + ``, + `If you are using yarn, run:`, + ``, + `yarn add ${packagesToUpdate.join(` `)}`, + ]) + } + + return msg + // eslint-disable-next-line no-empty + } catch { + return null + } +} + +const handleMessage = msg => { + if (msg.includes(`Incompatible library version: sharp.node requires`)) { + restoreConsoleError() + + let msg = [ + `It looks like there are multiple versions of "sharp" module installed.`, + `Please update any packages that depend on "sharp".`, + ``, + ] + + const detailedMessage = getDetailedMessage() + if (!detailedMessage) { + msg = msg.concat([ + `To get a list of installed packages that depend on "sharp" try running:`, + ` - npm list sharp (if you use npm)`, + ` - yarn why sharp (if you use yarn)`, + ` and update packages that depend on version older than latest listed in output of above command.`, + ]) + } else { + msg = msg.concat(detailedMessage) + } + + msg = msg.concat([ + ``, + `If an older version of "sharp" still persists and this error is displayed after updating your packages, open an issue in the package's repository and request them to update the "sharp" dependency.`, + ]) + + console.error(msg.join(`\n`)) + } +} + +let sharp +try { + // sharp@0.22.1 uses console.error and then process.exit and doesn't throw + // so to capture error and provide meaningful troubleshooting guide + // we intercept console.error calls and add special handling. + console.error = (msg, ...args) => { + originalConsoleError(msg, ...args) + handleMessage(msg) + } + sharp = require(`sharp`) +} catch (e) { + handleMessage(e.toString()) + throw e +} finally { + restoreConsoleError() +} + +module.exports = sharp diff --git a/packages/gatsby-plugin-sharp/README.md b/packages/gatsby-plugin-sharp/README.md index 5f3e43eed6ad7..014823b1952b3 100644 --- a/packages/gatsby-plugin-sharp/README.md +++ b/packages/gatsby-plugin-sharp/README.md @@ -299,6 +299,37 @@ limited to the latitude/longitude information of where the picture was taken can either leave `stripMetadata` to its default of `true`, or manually pre-process your images with a tool such as [ExifTool][17]. +## Troubleshooting + +### Incompatible library version: sharp.node requires version X or later, but Z provides version Y + +This means that there are multiple incompatible versions of the `sharp` package installed in `node_modules`. The complete error typically looks like this: + +``` +Something went wrong installing the "sharp" module + +dlopen(/Users/misiek/dev/gatsby-starter-blog/node_modules/sharp/build/Release/sharp.node, 1): Library not loaded: @rpath/libglib-2.0.dylib + Referenced from: /Users/misiek/dev/gatsby-starter-blog/node_modules/sharp/build/Release/sharp.node + Reason: Incompatible library version: sharp.node requires version 6001.0.0 or later, but libglib-2.0.dylib provides version 5801.0.0 +``` + +To fix this, you'll need to update all Gatsby plugins in the current project that depend on the `sharp` package. Here's a list of official plugins that you might need to update in case your projects uses them: + +- `gatsby-plugin-sharp` +- `gatsby-plugin-manifest` +- `gatsby-remark-images-contentful` +- `gatsby-source-contentful` +- `gatsby-transformer-sharp` +- `gatsby-transformer-sqip` + +To update these packages, run: + +```sh +npm install gatsby-plugin-sharp gatsby-plugin-manifest gatsby-remark-images-contentful gatsby-source-contentful gatsby-transformer-sharp gatsby-transformer-sqip +``` + +If updating these doesn't fix the issue, your project probably uses other plugins from the community that depend on a different version of `sharp`. Try running `npm list sharp` or `yarn why sharp` to see all packages in the current project that use `sharp` and try updating them as well. + [1]: https://alistapart.com/article/finessing-fecolormatrix [2]: http://blog.72lions.com/blog/2015/7/7/duotone-in-js [3]: https://ines.io/blog/dynamic-duotone-svg-jade diff --git a/packages/gatsby-plugin-sharp/package.json b/packages/gatsby-plugin-sharp/package.json index 4710d8402d222..54d8c81f0c305 100644 --- a/packages/gatsby-plugin-sharp/package.json +++ b/packages/gatsby-plugin-sharp/package.json @@ -20,7 +20,8 @@ "potrace": "^2.1.1", "probe-image-size": "^4.0.0", "progress": "^1.1.8", - "sharp": "^0.21.3", + "semver": "^5.6.0", + "sharp": "^0.22.1", "svgo": "^1.2.0" }, "devDependencies": { diff --git a/packages/gatsby-plugin-sharp/src/duotone.js b/packages/gatsby-plugin-sharp/src/duotone.js index f1cc78e2645aa..bde8df7889dbf 100644 --- a/packages/gatsby-plugin-sharp/src/duotone.js +++ b/packages/gatsby-plugin-sharp/src/duotone.js @@ -1,4 +1,4 @@ -const sharp = require(`sharp`) +const sharp = require(`./safe-sharp`) module.exports = async function duotone(duotone, format, pipeline) { const duotoneGradient = createDuotoneGradient( diff --git a/packages/gatsby-plugin-sharp/src/index.js b/packages/gatsby-plugin-sharp/src/index.js index 89ae6e393b83f..987e7f0fd545c 100644 --- a/packages/gatsby-plugin-sharp/src/index.js +++ b/packages/gatsby-plugin-sharp/src/index.js @@ -1,23 +1,4 @@ -try { - require(`sharp`) -} catch (error) { - // Bail early if sharp isn't available - console.error( - ` - The dependency "sharp" does not seem to have been built or installed correctly. - - - Try to reinstall packages and look for errors during installation - - Consult "sharp" installation page at http://sharp.pixelplumbing.com/en/stable/install/ - - If neither of the above work, please open an issue in https://github.com/gatsbyjs/gatsby/issues - ` - ) - console.log() - console.error(error) - process.exit(1) -} - -const sharp = require(`sharp`) +const sharp = require(`./safe-sharp`) const imageSize = require(`probe-image-size`) diff --git a/packages/gatsby-plugin-sharp/src/process-file.js b/packages/gatsby-plugin-sharp/src/process-file.js index ce4994baee433..463f294f9941d 100644 --- a/packages/gatsby-plugin-sharp/src/process-file.js +++ b/packages/gatsby-plugin-sharp/src/process-file.js @@ -1,4 +1,4 @@ -const sharp = require(`sharp`) +const sharp = require(`./safe-sharp`) const fs = require(`fs-extra`) const debug = require(`debug`)(`gatsby:gatsby-plugin-sharp`) const duotone = require(`./duotone`) diff --git a/packages/gatsby-plugin-sharp/src/safe-sharp.js b/packages/gatsby-plugin-sharp/src/safe-sharp.js new file mode 100644 index 0000000000000..6d97dfddd16a0 --- /dev/null +++ b/packages/gatsby-plugin-sharp/src/safe-sharp.js @@ -0,0 +1,168 @@ +// This is very hacky, but this should be temporary hack +// until we make "sharp" a peerDependency and can be removed afterwards. +// It's not done yet because it would cause too much friction. +// Image processing is important part of Gatsby ecosystem +// and there's lot of guides and tutorials that we don't control +// that would be outdated. Moving "sharp" to be peerDependency +// is scheduled for gatsby@3. + +// This file is duplicated in multiple location in this repository, +// So make sure to apply same changes every "safe-sharp" file. + +const childProcess = require(`child_process`) +const path = require(`path`) +const fs = require(`fs`) +const semver = require(`semver`) + +const originalConsoleError = console.error +const restoreConsoleError = () => { + console.error = originalConsoleError +} + +const getDetailedMessage = () => { + try { + // `npm list` seems to work in yarn installed projects as long + // as there is no package-lock.json, so let's bail out + // if both lock files exist + + if ( + fs.existsSync(path.join(process.cwd(), `package-lock.json`)) && + fs.existsSync(path.join(process.cwd(), `yarn.lock`)) + ) { + return null + } + + let msg = [] + const { dependencies } = JSON.parse( + childProcess.execSync(`npm list sharp --json`, { + encoding: `utf-8`, + }) + ) + + const findSharpVersion = dependency => { + if (dependency.dependencies.sharp) { + return dependency.dependencies.sharp.version + } + + for (let depName of Object.keys(dependency.dependencies)) { + const v = findSharpVersion(dependency.dependencies[depName]) + if (v) { + return v + } + } + + return null + } + + const { latestVersion, topLevelPackages } = Object.keys( + dependencies + ).reduce( + (acc, depName) => { + const sharpVersion = findSharpVersion(dependencies[depName]) + if (sharpVersion) { + acc.topLevelPackages[depName] = sharpVersion + + if ( + !acc.latestVersion || + semver.gt(sharpVersion, acc.latestVersion) + ) { + acc.latestVersion = sharpVersion + } + } + + return acc + }, + { + latestVersion: undefined, + topLevelPackages: {}, + } + ) + + let packagesToUpdate = [] + // list top level dependencies + msg = msg.concat([ + `List of installed packages that depend on sharp:`, + ...Object.keys(topLevelPackages).map(depName => { + const sharpVersion = topLevelPackages[depName] + if (sharpVersion !== latestVersion) { + packagesToUpdate.push(depName) + } + return ` - ${depName}${ + sharpVersion + ? ` (${sharpVersion})${ + sharpVersion !== latestVersion ? ` - needs update` : `` + }` + : `` + }` + }), + ]) + + if (packagesToUpdate.length > 0) { + msg = msg.concat([ + ``, + `If you are using npm, run:`, + ``, + `npm install ${packagesToUpdate.join(` `)}`, + ``, + `If you are using yarn, run:`, + ``, + `yarn add ${packagesToUpdate.join(` `)}`, + ]) + } + + return msg + // eslint-disable-next-line no-empty + } catch { + return null + } +} + +const handleMessage = msg => { + if (msg.includes(`Incompatible library version: sharp.node requires`)) { + restoreConsoleError() + + let msg = [ + `It looks like there are multiple versions of "sharp" module installed.`, + `Please update any packages that depend on "sharp".`, + ``, + ] + + const detailedMessage = getDetailedMessage() + if (!detailedMessage) { + msg = msg.concat([ + `To get a list of installed packages that depend on "sharp" try running:`, + ` - npm list sharp (if you use npm)`, + ` - yarn why sharp (if you use yarn)`, + ` and update packages that depend on version older than latest listed in output of above command.`, + ]) + } else { + msg = msg.concat(detailedMessage) + } + + msg = msg.concat([ + ``, + `If an older version of "sharp" still persists and this error is displayed after updating your packages, open an issue in the package's repository and request them to update the "sharp" dependency.`, + ]) + + console.error(msg.join(`\n`)) + } +} + +let sharp +try { + // sharp@0.22.1 uses console.error and then process.exit and doesn't throw + // so to capture error and provide meaningful troubleshooting guide + // we intercept console.error calls and add special handling. + console.error = (msg, ...args) => { + originalConsoleError(msg, ...args) + handleMessage(msg) + } + sharp = require(`sharp`) +} catch (e) { + handleMessage(e.toString()) + throw e +} finally { + restoreConsoleError() +} + +module.exports = sharp diff --git a/packages/gatsby-plugin-sharp/src/trace-svg.js b/packages/gatsby-plugin-sharp/src/trace-svg.js index 966ed231127c4..4fd24127f61b7 100644 --- a/packages/gatsby-plugin-sharp/src/trace-svg.js +++ b/packages/gatsby-plugin-sharp/src/trace-svg.js @@ -2,7 +2,7 @@ const { promisify } = require(`bluebird`) const crypto = require(`crypto`) const _ = require(`lodash`) const tmpDir = require(`os`).tmpdir() -const sharp = require(`sharp`) +const sharp = require(`./safe-sharp`) const duotone = require(`./duotone`) const { getPluginOptions, healOptions } = require(`./plugin-options`) diff --git a/packages/gatsby-remark-images-contentful/.babelrc b/packages/gatsby-remark-images-contentful/.babelrc new file mode 100644 index 0000000000000..9f5de61e0d79e --- /dev/null +++ b/packages/gatsby-remark-images-contentful/.babelrc @@ -0,0 +1,5 @@ +{ + "presets": [ + ["babel-preset-gatsby-package"] + ] +} diff --git a/packages/gatsby-remark-images-contentful/.gitignore b/packages/gatsby-remark-images-contentful/.gitignore index 3d2dc6ea89d63..2d7cc22cefd27 100644 --- a/packages/gatsby-remark-images-contentful/.gitignore +++ b/packages/gatsby-remark-images-contentful/.gitignore @@ -1,4 +1,4 @@ -/index.js +/*.js /utils /__tests__/* tests diff --git a/packages/gatsby-remark-images-contentful/README.md b/packages/gatsby-remark-images-contentful/README.md index d2a2185f54506..b581d51abde35 100644 --- a/packages/gatsby-remark-images-contentful/README.md +++ b/packages/gatsby-remark-images-contentful/README.md @@ -54,5 +54,36 @@ plugins: [ | `backgroundColor` | `white` | Set the background color of the image to match the background of your design | | `withWebp` | `false` | Additionally generate WebP versions alongside your chosen file format. They are added as a srcset with the appropriate mimetype and will be loaded in browsers that support the format. | +## Troubleshooting + +### Incompatible library version: sharp.node requires version X or later, but Z provides version Y + +This means that there are multiple incompatible versions of the `sharp` package installed in `node_modules`. The complete error typically looks like this: + +``` +Something went wrong installing the "sharp" module + +dlopen(/Users/misiek/dev/gatsby-starter-blog/node_modules/sharp/build/Release/sharp.node, 1): Library not loaded: @rpath/libglib-2.0.dylib + Referenced from: /Users/misiek/dev/gatsby-starter-blog/node_modules/sharp/build/Release/sharp.node + Reason: Incompatible library version: sharp.node requires version 6001.0.0 or later, but libglib-2.0.dylib provides version 5801.0.0 +``` + +To fix this, you'll need to update all Gatsby plugins in the current project that depend on the `sharp` package. Here's a list of official plugins that you might need to update in case your projects uses them: + +- `gatsby-plugin-sharp` +- `gatsby-plugin-manifest` +- `gatsby-remark-images-contentful` +- `gatsby-source-contentful` +- `gatsby-transformer-sharp` +- `gatsby-transformer-sqip` + +To update these packages, run: + +```sh +npm install gatsby-plugin-sharp gatsby-plugin-manifest gatsby-remark-images-contentful gatsby-source-contentful gatsby-transformer-sharp gatsby-transformer-sqip +``` + +If updating these doesn't fix the issue, your project probably uses other plugins from the community that depend on a different version of `sharp`. Try running `npm list sharp` or `yarn why sharp` to see all packages in the current project that use `sharp` and try updating them as well. + [1]: https://jmperezperez.com/medium-image-progressive-loading-placeholder/ [2]: https://code.facebook.com/posts/991252547593574/the-technology-behind-preview-photos/ diff --git a/packages/gatsby-remark-images-contentful/package.json b/packages/gatsby-remark-images-contentful/package.json index acdd86bf0ba72..61ca63b8fdb46 100644 --- a/packages/gatsby-remark-images-contentful/package.json +++ b/packages/gatsby-remark-images-contentful/package.json @@ -14,12 +14,14 @@ "cheerio": "^1.0.0-rc.2", "is-relative-url": "^2.0.0", "lodash": "^4.17.10", - "sharp": "^0.21.3", + "semver": "^5.6.0", + "sharp": "^0.22.1", "unist-util-select": "^1.5.0" }, "devDependencies": { "@babel/cli": "^7.0.0", "@babel/core": "^7.0.0", + "babel-preset-gatsby-package": "^0.1.4", "cross-env": "^5.0.5" }, "keywords": [ diff --git a/packages/gatsby-remark-images-contentful/src/index.js b/packages/gatsby-remark-images-contentful/src/index.js index 46d5c2e47c457..277ca77c1ecd3 100644 --- a/packages/gatsby-remark-images-contentful/src/index.js +++ b/packages/gatsby-remark-images-contentful/src/index.js @@ -1,6 +1,6 @@ const crypto = require(`crypto`) const select = require(`unist-util-select`) -const sharp = require(`sharp`) +const sharp = require(`./safe-sharp`) const axios = require(`axios`) const _ = require(`lodash`) const Promise = require(`bluebird`) diff --git a/packages/gatsby-remark-images-contentful/src/safe-sharp.js b/packages/gatsby-remark-images-contentful/src/safe-sharp.js new file mode 100644 index 0000000000000..6d97dfddd16a0 --- /dev/null +++ b/packages/gatsby-remark-images-contentful/src/safe-sharp.js @@ -0,0 +1,168 @@ +// This is very hacky, but this should be temporary hack +// until we make "sharp" a peerDependency and can be removed afterwards. +// It's not done yet because it would cause too much friction. +// Image processing is important part of Gatsby ecosystem +// and there's lot of guides and tutorials that we don't control +// that would be outdated. Moving "sharp" to be peerDependency +// is scheduled for gatsby@3. + +// This file is duplicated in multiple location in this repository, +// So make sure to apply same changes every "safe-sharp" file. + +const childProcess = require(`child_process`) +const path = require(`path`) +const fs = require(`fs`) +const semver = require(`semver`) + +const originalConsoleError = console.error +const restoreConsoleError = () => { + console.error = originalConsoleError +} + +const getDetailedMessage = () => { + try { + // `npm list` seems to work in yarn installed projects as long + // as there is no package-lock.json, so let's bail out + // if both lock files exist + + if ( + fs.existsSync(path.join(process.cwd(), `package-lock.json`)) && + fs.existsSync(path.join(process.cwd(), `yarn.lock`)) + ) { + return null + } + + let msg = [] + const { dependencies } = JSON.parse( + childProcess.execSync(`npm list sharp --json`, { + encoding: `utf-8`, + }) + ) + + const findSharpVersion = dependency => { + if (dependency.dependencies.sharp) { + return dependency.dependencies.sharp.version + } + + for (let depName of Object.keys(dependency.dependencies)) { + const v = findSharpVersion(dependency.dependencies[depName]) + if (v) { + return v + } + } + + return null + } + + const { latestVersion, topLevelPackages } = Object.keys( + dependencies + ).reduce( + (acc, depName) => { + const sharpVersion = findSharpVersion(dependencies[depName]) + if (sharpVersion) { + acc.topLevelPackages[depName] = sharpVersion + + if ( + !acc.latestVersion || + semver.gt(sharpVersion, acc.latestVersion) + ) { + acc.latestVersion = sharpVersion + } + } + + return acc + }, + { + latestVersion: undefined, + topLevelPackages: {}, + } + ) + + let packagesToUpdate = [] + // list top level dependencies + msg = msg.concat([ + `List of installed packages that depend on sharp:`, + ...Object.keys(topLevelPackages).map(depName => { + const sharpVersion = topLevelPackages[depName] + if (sharpVersion !== latestVersion) { + packagesToUpdate.push(depName) + } + return ` - ${depName}${ + sharpVersion + ? ` (${sharpVersion})${ + sharpVersion !== latestVersion ? ` - needs update` : `` + }` + : `` + }` + }), + ]) + + if (packagesToUpdate.length > 0) { + msg = msg.concat([ + ``, + `If you are using npm, run:`, + ``, + `npm install ${packagesToUpdate.join(` `)}`, + ``, + `If you are using yarn, run:`, + ``, + `yarn add ${packagesToUpdate.join(` `)}`, + ]) + } + + return msg + // eslint-disable-next-line no-empty + } catch { + return null + } +} + +const handleMessage = msg => { + if (msg.includes(`Incompatible library version: sharp.node requires`)) { + restoreConsoleError() + + let msg = [ + `It looks like there are multiple versions of "sharp" module installed.`, + `Please update any packages that depend on "sharp".`, + ``, + ] + + const detailedMessage = getDetailedMessage() + if (!detailedMessage) { + msg = msg.concat([ + `To get a list of installed packages that depend on "sharp" try running:`, + ` - npm list sharp (if you use npm)`, + ` - yarn why sharp (if you use yarn)`, + ` and update packages that depend on version older than latest listed in output of above command.`, + ]) + } else { + msg = msg.concat(detailedMessage) + } + + msg = msg.concat([ + ``, + `If an older version of "sharp" still persists and this error is displayed after updating your packages, open an issue in the package's repository and request them to update the "sharp" dependency.`, + ]) + + console.error(msg.join(`\n`)) + } +} + +let sharp +try { + // sharp@0.22.1 uses console.error and then process.exit and doesn't throw + // so to capture error and provide meaningful troubleshooting guide + // we intercept console.error calls and add special handling. + console.error = (msg, ...args) => { + originalConsoleError(msg, ...args) + handleMessage(msg) + } + sharp = require(`sharp`) +} catch (e) { + handleMessage(e.toString()) + throw e +} finally { + restoreConsoleError() +} + +module.exports = sharp diff --git a/packages/gatsby-transformer-sharp/README.md b/packages/gatsby-transformer-sharp/README.md index 23e63835e4d33..05b1c89629f69 100644 --- a/packages/gatsby-transformer-sharp/README.md +++ b/packages/gatsby-transformer-sharp/README.md @@ -35,3 +35,34 @@ It recognizes files with the following extensions as images. - tiff Each image file is parsed into a node of type `ImageSharp`. + +## Troubleshooting + +### Incompatible library version: sharp.node requires version X or later, but Z provides version Y + +This means that there are multiple incompatible versions of the `sharp` package installed in `node_modules`. The complete error typically looks like this: + +``` +Something went wrong installing the "sharp" module + +dlopen(/Users/misiek/dev/gatsby-starter-blog/node_modules/sharp/build/Release/sharp.node, 1): Library not loaded: @rpath/libglib-2.0.dylib + Referenced from: /Users/misiek/dev/gatsby-starter-blog/node_modules/sharp/build/Release/sharp.node + Reason: Incompatible library version: sharp.node requires version 6001.0.0 or later, but libglib-2.0.dylib provides version 5801.0.0 +``` + +To fix this, you'll need to update all Gatsby plugins in the current project that depend on the `sharp` package. Here's a list of official plugins that you might need to update in case your projects uses them: + +- `gatsby-plugin-sharp` +- `gatsby-plugin-manifest` +- `gatsby-remark-images-contentful` +- `gatsby-source-contentful` +- `gatsby-transformer-sharp` +- `gatsby-transformer-sqip` + +To update these packages, run: + +```sh +npm install gatsby-plugin-sharp gatsby-plugin-manifest gatsby-remark-images-contentful gatsby-source-contentful gatsby-transformer-sharp gatsby-transformer-sqip +``` + +If updating these doesn't fix the issue, your project probably uses other plugins from the community that depend on a different version of `sharp`. Try running `npm list sharp` or `yarn why sharp` to see all packages in the current project that use `sharp` and try updating them as well. diff --git a/packages/gatsby-transformer-sharp/package.json b/packages/gatsby-transformer-sharp/package.json index 2657960923a88..4fe54fbde07dc 100644 --- a/packages/gatsby-transformer-sharp/package.json +++ b/packages/gatsby-transformer-sharp/package.json @@ -12,7 +12,8 @@ "fs-extra": "^7.0.0", "potrace": "^2.1.1", "probe-image-size": "^4.0.0", - "sharp": "^0.21.3" + "semver": "^5.6.0", + "sharp": "^0.22.1" }, "devDependencies": { "@babel/cli": "^7.0.0", diff --git a/packages/gatsby-transformer-sharp/src/extend-node-type.js b/packages/gatsby-transformer-sharp/src/extend-node-type.js index d3838d4c6d40e..712f897b4da8d 100644 --- a/packages/gatsby-transformer-sharp/src/extend-node-type.js +++ b/packages/gatsby-transformer-sharp/src/extend-node-type.js @@ -15,7 +15,7 @@ const { traceSVG, } = require(`gatsby-plugin-sharp`) -const sharp = require(`sharp`) +const sharp = require(`./safe-sharp`) const fs = require(`fs`) const fsExtra = require(`fs-extra`) const imageSize = require(`probe-image-size`) diff --git a/packages/gatsby-transformer-sharp/src/safe-sharp.js b/packages/gatsby-transformer-sharp/src/safe-sharp.js new file mode 100644 index 0000000000000..6d97dfddd16a0 --- /dev/null +++ b/packages/gatsby-transformer-sharp/src/safe-sharp.js @@ -0,0 +1,168 @@ +// This is very hacky, but this should be temporary hack +// until we make "sharp" a peerDependency and can be removed afterwards. +// It's not done yet because it would cause too much friction. +// Image processing is important part of Gatsby ecosystem +// and there's lot of guides and tutorials that we don't control +// that would be outdated. Moving "sharp" to be peerDependency +// is scheduled for gatsby@3. + +// This file is duplicated in multiple location in this repository, +// So make sure to apply same changes every "safe-sharp" file. + +const childProcess = require(`child_process`) +const path = require(`path`) +const fs = require(`fs`) +const semver = require(`semver`) + +const originalConsoleError = console.error +const restoreConsoleError = () => { + console.error = originalConsoleError +} + +const getDetailedMessage = () => { + try { + // `npm list` seems to work in yarn installed projects as long + // as there is no package-lock.json, so let's bail out + // if both lock files exist + + if ( + fs.existsSync(path.join(process.cwd(), `package-lock.json`)) && + fs.existsSync(path.join(process.cwd(), `yarn.lock`)) + ) { + return null + } + + let msg = [] + const { dependencies } = JSON.parse( + childProcess.execSync(`npm list sharp --json`, { + encoding: `utf-8`, + }) + ) + + const findSharpVersion = dependency => { + if (dependency.dependencies.sharp) { + return dependency.dependencies.sharp.version + } + + for (let depName of Object.keys(dependency.dependencies)) { + const v = findSharpVersion(dependency.dependencies[depName]) + if (v) { + return v + } + } + + return null + } + + const { latestVersion, topLevelPackages } = Object.keys( + dependencies + ).reduce( + (acc, depName) => { + const sharpVersion = findSharpVersion(dependencies[depName]) + if (sharpVersion) { + acc.topLevelPackages[depName] = sharpVersion + + if ( + !acc.latestVersion || + semver.gt(sharpVersion, acc.latestVersion) + ) { + acc.latestVersion = sharpVersion + } + } + + return acc + }, + { + latestVersion: undefined, + topLevelPackages: {}, + } + ) + + let packagesToUpdate = [] + // list top level dependencies + msg = msg.concat([ + `List of installed packages that depend on sharp:`, + ...Object.keys(topLevelPackages).map(depName => { + const sharpVersion = topLevelPackages[depName] + if (sharpVersion !== latestVersion) { + packagesToUpdate.push(depName) + } + return ` - ${depName}${ + sharpVersion + ? ` (${sharpVersion})${ + sharpVersion !== latestVersion ? ` - needs update` : `` + }` + : `` + }` + }), + ]) + + if (packagesToUpdate.length > 0) { + msg = msg.concat([ + ``, + `If you are using npm, run:`, + ``, + `npm install ${packagesToUpdate.join(` `)}`, + ``, + `If you are using yarn, run:`, + ``, + `yarn add ${packagesToUpdate.join(` `)}`, + ]) + } + + return msg + // eslint-disable-next-line no-empty + } catch { + return null + } +} + +const handleMessage = msg => { + if (msg.includes(`Incompatible library version: sharp.node requires`)) { + restoreConsoleError() + + let msg = [ + `It looks like there are multiple versions of "sharp" module installed.`, + `Please update any packages that depend on "sharp".`, + ``, + ] + + const detailedMessage = getDetailedMessage() + if (!detailedMessage) { + msg = msg.concat([ + `To get a list of installed packages that depend on "sharp" try running:`, + ` - npm list sharp (if you use npm)`, + ` - yarn why sharp (if you use yarn)`, + ` and update packages that depend on version older than latest listed in output of above command.`, + ]) + } else { + msg = msg.concat(detailedMessage) + } + + msg = msg.concat([ + ``, + `If an older version of "sharp" still persists and this error is displayed after updating your packages, open an issue in the package's repository and request them to update the "sharp" dependency.`, + ]) + + console.error(msg.join(`\n`)) + } +} + +let sharp +try { + // sharp@0.22.1 uses console.error and then process.exit and doesn't throw + // so to capture error and provide meaningful troubleshooting guide + // we intercept console.error calls and add special handling. + console.error = (msg, ...args) => { + originalConsoleError(msg, ...args) + handleMessage(msg) + } + sharp = require(`sharp`) +} catch (e) { + handleMessage(e.toString()) + throw e +} finally { + restoreConsoleError() +} + +module.exports = sharp diff --git a/packages/gatsby-transformer-sharp/src/types.js b/packages/gatsby-transformer-sharp/src/types.js index a994b596489e8..994e3b331bb8e 100644 --- a/packages/gatsby-transformer-sharp/src/types.js +++ b/packages/gatsby-transformer-sharp/src/types.js @@ -6,7 +6,7 @@ const { GraphQLFloat, GraphQLEnumType, } = require(`gatsby/graphql`) -const sharp = require(`sharp`) +const sharp = require(`./safe-sharp`) const { Potrace } = require(`potrace`) const ImageFormatType = new GraphQLEnumType({ diff --git a/yarn.lock b/yarn.lock index 6d7651e6a5bf4..4b10d9d8d4657 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5017,13 +5017,6 @@ binary-extensions@^1.0.0: resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.11.0.tgz#46aa1751fb6a2f93ee5e689bb1087d4b14c6c205" integrity sha1-RqoXUftqL5PuXmibsQh9SxTGwgU= -bindings@^1.3.1: - version "1.4.0" - resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.4.0.tgz#909efa49f2ebe07ecd3cb136778f665052040127" - integrity sha512-7znEVX22Djn+nYjxCWKDne0RRloa9XfYa84yk3s+HkE3LpDYZmhArYr9O9huBoHY3/oXispx5LorIX7Sl2CgSQ== - dependencies: - file-uri-to-path "1.0.0" - bl@^1.0.0: version "1.2.2" resolved "https://registry.yarnpkg.com/bl/-/bl-1.2.2.tgz#a160911717103c07410cef63ef51b397c025af9c" @@ -6246,10 +6239,10 @@ color@^3.0.0: color-convert "^1.9.1" color-string "^1.5.2" -color@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/color/-/color-3.1.0.tgz#d8e9fb096732875774c84bf922815df0308d0ffc" - integrity sha512-CwyopLkuRYO5ei2EpzpIh6LqJMt6Mt+jZhO5VI5f/wJLZriXQE32/SSqzmrh+QB+AZT81Cj8yv+7zwToW8ahZg== +color@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/color/-/color-3.1.1.tgz#7abf5c0d38e89378284e873c207ae2172dcc8a61" + integrity sha512-PvUltIXRjehRKPSy89VnDWFKY58xyhTLyxIg21vwQBI6qLwZNPmC8k3C1uytIgFKEpOIzN4y32iPm8231zFHIg== dependencies: color-convert "^1.9.1" color-string "^1.5.2" @@ -9358,11 +9351,6 @@ file-type@^8.1.0: resolved "https://registry.yarnpkg.com/file-type/-/file-type-8.1.0.tgz#244f3b7ef641bbe0cca196c7276e4b332399f68c" integrity sha512-qyQ0pzAy78gVoJsmYeNgl8uH8yKhr1lVhW7JbzJmnlRi0I4R2eEDEJZVKG8agpDnLpacwNbDhLNG/LMdxHD2YQ== -file-uri-to-path@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" - integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== - filename-regex@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/filename-regex/-/filename-regex-2.0.1.tgz#c1c4b9bee3e09725ddb106b75c1e301fe2f18b26" @@ -14897,11 +14885,16 @@ name-all-modules-plugin@^1.0.1: resolved "https://registry.yarnpkg.com/name-all-modules-plugin/-/name-all-modules-plugin-1.0.1.tgz#0abfb6ad835718b9fb4def0674e06657a954375c" integrity sha1-Cr+2rYNXGLn7Te8GdOBmV6lUN1w= -nan@^2.10.0, nan@^2.12.1: +nan@^2.10.0: version "2.12.1" resolved "https://registry.yarnpkg.com/nan/-/nan-2.12.1.tgz#7b1aa193e9aa86057e3c7bbd0ac448e770925552" integrity sha512-JY7V6lRkStKcKTvHO5NVSQRv+RV+FIL5pvDoLiAtSL9pKlC5x9PKQcZDsq7m4FO4d57mkhC6Z+QhAh3Jdk5JFw== +nan@^2.13.2: + version "2.13.2" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.13.2.tgz#f51dc7ae66ba7d5d55e1e6d4d8092e802c9aefe7" + integrity sha512-TghvYc72wlMGMVMluVo9WRJc0mB8KxxF/gZ4YYFy7V2ZQX9l7rgbPg7vjS9mt6U5HXODVFVI2bOduCzwOMv/lw== + nan@^2.9.2: version "2.11.0" resolved "https://registry.yarnpkg.com/nan/-/nan-2.11.0.tgz#574e360e4d954ab16966ec102c0c049fd961a099" @@ -14992,10 +14985,10 @@ no-case@^2.2.0, no-case@^2.3.2: dependencies: lower-case "^1.1.1" -node-abi@^2.2.0: - version "2.4.3" - resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-2.4.3.tgz#43666b7b17e57863e572409edbb82115ac7af28b" - integrity sha512-b656V5C0628gOOA2kwcpNA/bxdlqYF9FvxJ+qqVX0ctdXNVZpS8J6xEUYir3WAKc7U0BH/NRlSpNbGsy+azjeg== +node-abi@^2.7.0: + version "2.8.0" + resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-2.8.0.tgz#bd2e88dbe6a6871e6dd08553e0605779325737ec" + integrity sha512-1/aa2clS0pue0HjckL62CsbhWWU35HARvBDXcJtYKbYR7LnIutmpxmXbuDMV9kEviD2lP/wACOgWmmwljghHyQ== dependencies: semver "^5.4.1" @@ -16895,10 +16888,10 @@ potrace@^2.1.1: dependencies: jimp "^0.2.24" -prebuild-install@^5.2.2: - version "5.2.2" - resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-5.2.2.tgz#237888f21bfda441d0ee5f5612484390bccd4046" - integrity sha512-4e8VJnP3zJdZv/uP0eNWmr2r9urp4NECw7Mt1OSAi3rcLrbBRxGiAkfUFtre2MhQ5wfREAjRV+K1gubvs/GPsA== +prebuild-install@^5.3.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-5.3.0.tgz#58b4d8344e03590990931ee088dd5401b03004c8" + integrity sha512-aaLVANlj4HgZweKttFNUVNRxDukytuIuxeK2boIMHjagNJCiVKWFsKF4tCE3ql3GbrD2tExPQ7/pwtEJcHNZeg== dependencies: detect-libc "^1.0.3" expand-template "^2.0.3" @@ -16906,7 +16899,7 @@ prebuild-install@^5.2.2: minimist "^1.2.0" mkdirp "^0.5.1" napi-build-utils "^1.0.1" - node-abi "^2.2.0" + node-abi "^2.7.0" noop-logger "^0.1.1" npmlog "^4.0.1" os-homedir "^1.0.1" @@ -19014,19 +19007,18 @@ shallowequal@^1.0.2: resolved "https://registry.yarnpkg.com/shallowequal/-/shallowequal-1.1.0.tgz#188d521de95b9087404fd4dcb68b13df0ae4e7f8" integrity sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ== -sharp@^0.21.3: - version "0.21.3" - resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.21.3.tgz#381937de66c123687f2ac7186f85921c6bb19cdd" - integrity sha512-5qZk8r+YgfyztLEKkNez20Wynq/Uh1oNyP5T/3gTYwt2lBYGs9iDs5m0yVsZEPm8eVBbAJhS08J1wp/g+Ai1Qw== +sharp@^0.22.1: + version "0.22.1" + resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.22.1.tgz#a67c0e75567f03dd5a7861b901fec04072c5b0f4" + integrity sha512-lXzSk/FL5b/MpWrT1pQZneKe25stVjEbl6uhhJcTULm7PhmJgKKRbTDM/vtjyUuC/RLqL2PRyC4rpKwbv3soEw== dependencies: - bindings "^1.3.1" - color "^3.1.0" + color "^3.1.1" detect-libc "^1.0.3" fs-copy-file-sync "^1.1.1" - nan "^2.12.1" + nan "^2.13.2" npmlog "^4.1.2" - prebuild-install "^5.2.2" - semver "^5.6.0" + prebuild-install "^5.3.0" + semver "^6.0.0" simple-get "^3.0.3" tar "^4.4.8" tunnel-agent "^0.6.0"