UNPKG

4.77 kBJavaScriptView Raw
1// This is very hacky, but this should be temporary hack
2// until we make "sharp" a peerDependency and can be removed afterwards.
3// It's not done yet because it would cause too much friction.
4// Image processing is important part of Gatsby ecosystem
5// and there's lot of guides and tutorials that we don't control
6// that would be outdated. Moving "sharp" to be peerDependency
7// is scheduled for gatsby@3.
8
9// This file is duplicated in multiple location in this repository,
10// So make sure to apply same changes every "safe-sharp" file.
11
12const childProcess = require(`child_process`)
13const path = require(`path`)
14const fs = require(`fs`)
15const semver = require(`semver`)
16
17const originalConsoleError = console.error
18const restoreConsoleError = () => {
19 console.error = originalConsoleError
20}
21
22const getDetailedMessage = () => {
23 try {
24 // `npm list` seems to work in yarn installed projects as long
25 // as there is no package-lock.json, so let's bail out
26 // if both lock files exist
27
28 if (
29 fs.existsSync(path.join(process.cwd(), `package-lock.json`)) &&
30 fs.existsSync(path.join(process.cwd(), `yarn.lock`))
31 ) {
32 return null
33 }
34
35 let msg = []
36 const { dependencies } = JSON.parse(
37 childProcess.execSync(`npm list sharp --json`, {
38 encoding: `utf-8`,
39 })
40 )
41
42 const findSharpVersion = dependency => {
43 if (dependency.dependencies.sharp) {
44 return dependency.dependencies.sharp.version
45 }
46
47 for (let depName of Object.keys(dependency.dependencies)) {
48 const v = findSharpVersion(dependency.dependencies[depName])
49 if (v) {
50 return v
51 }
52 }
53
54 return null
55 }
56
57 const { latestVersion, topLevelPackages } = Object.keys(
58 dependencies
59 ).reduce(
60 (acc, depName) => {
61 const sharpVersion = findSharpVersion(dependencies[depName])
62 if (sharpVersion) {
63 acc.topLevelPackages[depName] = sharpVersion
64
65 if (
66 !acc.latestVersion ||
67 semver.gt(sharpVersion, acc.latestVersion)
68 ) {
69 acc.latestVersion = sharpVersion
70 }
71 }
72
73 return acc
74 },
75 {
76 latestVersion: undefined,
77 topLevelPackages: {},
78 }
79 )
80
81 let packagesToUpdate = []
82 // list top level dependencies
83 msg = msg.concat([
84 `List of installed packages that depend on sharp:`,
85 ...Object.keys(topLevelPackages).map(depName => {
86 const sharpVersion = topLevelPackages[depName]
87 if (sharpVersion !== latestVersion) {
88 packagesToUpdate.push(depName)
89 }
90 return ` - ${depName}${
91 sharpVersion
92 ? ` (${sharpVersion})${
93 sharpVersion !== latestVersion ? ` - needs update` : ``
94 }`
95 : ``
96 }`
97 }),
98 ])
99
100 if (packagesToUpdate.length > 0) {
101 msg = msg.concat([
102 ``,
103 `If you are using npm, run:`,
104 ``,
105 `npm install ${packagesToUpdate.join(` `)}`,
106 ``,
107 `If you are using yarn, run:`,
108 ``,
109 `yarn add ${packagesToUpdate.join(` `)}`,
110 ])
111 }
112
113 return msg
114 // eslint-disable-next-line no-empty
115 } catch {
116 return null
117 }
118}
119
120const handleMessage = msg => {
121 if (msg.includes(`Incompatible library version: sharp.node requires`)) {
122 restoreConsoleError()
123
124 let msg = [
125 `It looks like there are multiple versions of "sharp" module installed.`,
126 `Please update any packages that depend on "sharp".`,
127 ``,
128 ]
129
130 const detailedMessage = getDetailedMessage()
131 if (!detailedMessage) {
132 msg = msg.concat([
133 `To get a list of installed packages that depend on "sharp" try running:`,
134 ` - npm list sharp (if you use npm)`,
135 ` - yarn why sharp (if you use yarn)`,
136 ` and update packages that depend on version older than latest listed in output of above command.`,
137 ])
138 } else {
139 msg = msg.concat(detailedMessage)
140 }
141
142 msg = msg.concat([
143 ``,
144 `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.`,
145 ])
146
147 console.error(msg.join(`\n`))
148 }
149}
150
151let sharp
152try {
153 // sharp@0.22.1 uses console.error and then process.exit and doesn't throw
154 // so to capture error and provide meaningful troubleshooting guide
155 // we intercept console.error calls and add special handling.
156 console.error = (msg, ...args) => {
157 originalConsoleError(msg, ...args)
158 handleMessage(msg)
159 }
160 sharp = require(`sharp`)
161} catch (e) {
162 handleMessage(e.toString())
163 throw e
164} finally {
165 restoreConsoleError()
166}
167
168module.exports = sharp