sharp 0.33.0-alpha.8 → 0.33.0-rc.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/output.js CHANGED
@@ -86,7 +86,8 @@ function toFile (fileOut, callback) {
86
86
  }
87
87
  } else {
88
88
  this.options.fileOut = fileOut;
89
- return this._pipeline(callback);
89
+ const stack = Error();
90
+ return this._pipeline(callback, stack);
90
91
  }
91
92
  return this;
92
93
  }
@@ -157,43 +158,190 @@ function toBuffer (options, callback) {
157
158
  this.options.resolveWithObject = false;
158
159
  }
159
160
  this.options.fileOut = '';
160
- return this._pipeline(is.fn(options) ? options : callback);
161
+ const stack = Error();
162
+ return this._pipeline(is.fn(options) ? options : callback, stack);
161
163
  }
162
164
 
163
165
  /**
164
- * Include all metadata (EXIF, XMP, IPTC) from the input image in the output image.
165
- * This will also convert to and add a web-friendly sRGB ICC profile if appropriate,
166
- * unless a custom output profile is provided.
167
- *
168
- * The default behaviour, when `withMetadata` is not used, is to convert to the device-independent
169
- * sRGB colour space and strip all metadata, including the removal of any ICC profile.
166
+ * Keep all EXIF metadata from the input image in the output image.
170
167
  *
171
168
  * EXIF metadata is unsupported for TIFF output.
172
169
  *
170
+ * @since 0.33.0
171
+ *
173
172
  * @example
174
- * sharp('input.jpg')
175
- * .withMetadata()
176
- * .toFile('output-with-metadata.jpg')
177
- * .then(info => { ... });
173
+ * const outputWithExif = await sharp(inputWithExif)
174
+ * .keepExif()
175
+ * .toBuffer();
176
+ *
177
+ * @returns {Sharp}
178
+ */
179
+ function keepExif () {
180
+ this.options.keepMetadata |= 0b00001;
181
+ return this;
182
+ }
183
+
184
+ /**
185
+ * Set EXIF metadata in the output image, ignoring any EXIF in the input image.
186
+ *
187
+ * @since 0.33.0
178
188
  *
179
189
  * @example
180
- * // Set output EXIF metadata
181
- * const data = await sharp(input)
182
- * .withMetadata({
183
- * exif: {
184
- * IFD0: {
185
- * Copyright: 'The National Gallery'
186
- * },
187
- * IFD3: {
188
- * GPSLatitudeRef: 'N',
189
- * GPSLatitude: '51/1 30/1 3230/100',
190
- * GPSLongitudeRef: 'W',
191
- * GPSLongitude: '0/1 7/1 4366/100'
192
- * }
190
+ * const dataWithExif = await sharp(input)
191
+ * .withExif({
192
+ * IFD0: {
193
+ * Copyright: 'The National Gallery'
194
+ * },
195
+ * IFD3: {
196
+ * GPSLatitudeRef: 'N',
197
+ * GPSLatitude: '51/1 30/1 3230/100',
198
+ * GPSLongitudeRef: 'W',
199
+ * GPSLongitude: '0/1 7/1 4366/100'
193
200
  * }
194
201
  * })
195
202
  * .toBuffer();
196
203
  *
204
+ * @param {Object<string, Object<string, string>>} exif Object keyed by IFD0, IFD1 etc. of key/value string pairs to write as EXIF data.
205
+ * @returns {Sharp}
206
+ * @throws {Error} Invalid parameters
207
+ */
208
+ function withExif (exif) {
209
+ if (is.object(exif)) {
210
+ for (const [ifd, entries] of Object.entries(exif)) {
211
+ if (is.object(entries)) {
212
+ for (const [k, v] of Object.entries(entries)) {
213
+ if (is.string(v)) {
214
+ this.options.withExif[`exif-${ifd.toLowerCase()}-${k}`] = v;
215
+ } else {
216
+ throw is.invalidParameterError(`${ifd}.${k}`, 'string', v);
217
+ }
218
+ }
219
+ } else {
220
+ throw is.invalidParameterError(ifd, 'object', entries);
221
+ }
222
+ }
223
+ } else {
224
+ throw is.invalidParameterError('exif', 'object', exif);
225
+ }
226
+ this.options.withExifMerge = false;
227
+ return this.keepExif();
228
+ }
229
+
230
+ /**
231
+ * Update EXIF metadata from the input image in the output image.
232
+ *
233
+ * @since 0.33.0
234
+ *
235
+ * @example
236
+ * const dataWithMergedExif = await sharp(inputWithExif)
237
+ * .withExifMerge({
238
+ * IFD0: {
239
+ * Copyright: 'The National Gallery'
240
+ * }
241
+ * })
242
+ * .toBuffer();
243
+ *
244
+ * @param {Object<string, Object<string, string>>} exif Object keyed by IFD0, IFD1 etc. of key/value string pairs to write as EXIF data.
245
+ * @returns {Sharp}
246
+ * @throws {Error} Invalid parameters
247
+ */
248
+ function withExifMerge (exif) {
249
+ this.withExif(exif);
250
+ this.options.withExifMerge = true;
251
+ return this;
252
+ }
253
+
254
+ /**
255
+ * Keep ICC profile from the input image in the output image.
256
+ *
257
+ * Where necessary, will attempt to convert the output colour space to match the profile.
258
+ *
259
+ * @since 0.33.0
260
+ *
261
+ * @example
262
+ * const outputWithIccProfile = await sharp(inputWithIccProfile)
263
+ * .keepIccProfile()
264
+ * .toBuffer();
265
+ *
266
+ * @returns {Sharp}
267
+ */
268
+ function keepIccProfile () {
269
+ this.options.keepMetadata |= 0b01000;
270
+ return this;
271
+ }
272
+
273
+ /**
274
+ * Transform using an ICC profile and attach to the output image.
275
+ *
276
+ * This can either be an absolute filesystem path or
277
+ * built-in profile name (`srgb`, `p3`, `cmyk`).
278
+ *
279
+ * @since 0.33.0
280
+ *
281
+ * @example
282
+ * const outputWithP3 = await sharp(input)
283
+ * .withIccProfile('p3')
284
+ * .toBuffer();
285
+ *
286
+ * @param {string} icc - Absolute filesystem path to output ICC profile or built-in profile name (srgb, p3, cmyk).
287
+ * @param {Object} [options]
288
+ * @param {number} [options.attach=true] Should the ICC profile be included in the output image metadata?
289
+ * @returns {Sharp}
290
+ * @throws {Error} Invalid parameters
291
+ */
292
+ function withIccProfile (icc, options) {
293
+ if (is.string(icc)) {
294
+ this.options.withIccProfile = icc;
295
+ } else {
296
+ throw is.invalidParameterError('icc', 'string', icc);
297
+ }
298
+ this.keepIccProfile();
299
+ if (is.object(options)) {
300
+ if (is.defined(options.attach)) {
301
+ if (is.bool(options.attach)) {
302
+ if (!options.attach) {
303
+ this.options.keepMetadata &= ~0b01000;
304
+ }
305
+ } else {
306
+ throw is.invalidParameterError('attach', 'boolean', options.attach);
307
+ }
308
+ }
309
+ }
310
+ return this;
311
+ }
312
+
313
+ /**
314
+ * Keep all metadata (EXIF, ICC, XMP, IPTC) from the input image in the output image.
315
+ *
316
+ * The default behaviour, when `keepMetadata` is not used, is to convert to the device-independent
317
+ * sRGB colour space and strip all metadata, including the removal of any ICC profile.
318
+ *
319
+ * @since 0.33.0
320
+ *
321
+ * @example
322
+ * const outputWithMetadata = await sharp(inputWithMetadata)
323
+ * .keepMetadata()
324
+ * .toBuffer();
325
+ *
326
+ * @returns {Sharp}
327
+ */
328
+ function keepMetadata () {
329
+ this.options.keepMetadata = 0b11111;
330
+ return this;
331
+ }
332
+
333
+ /**
334
+ * Keep most metadata (EXIF, XMP, IPTC) from the input image in the output image.
335
+ *
336
+ * This will also convert to and add a web-friendly sRGB ICC profile if appropriate.
337
+ *
338
+ * Allows orientation and density to be set or updated.
339
+ *
340
+ * @example
341
+ * const outputSrgbWithMetadata = await sharp(inputRgbWithMetadata)
342
+ * .withMetadata()
343
+ * .toBuffer();
344
+ *
197
345
  * @example
198
346
  * // Set output metadata to 96 DPI
199
347
  * const data = await sharp(input)
@@ -201,15 +349,14 @@ function toBuffer (options, callback) {
201
349
  * .toBuffer();
202
350
  *
203
351
  * @param {Object} [options]
204
- * @param {number} [options.orientation] value between 1 and 8, used to update the EXIF `Orientation` tag.
205
- * @param {string} [options.icc='srgb'] Filesystem path to output ICC profile, relative to `process.cwd()`, defaults to built-in sRGB.
206
- * @param {Object<Object>} [options.exif={}] Object keyed by IFD0, IFD1 etc. of key/value string pairs to write as EXIF data.
352
+ * @param {number} [options.orientation] Used to update the EXIF `Orientation` tag, integer between 1 and 8.
207
353
  * @param {number} [options.density] Number of pixels per inch (DPI).
208
354
  * @returns {Sharp}
209
355
  * @throws {Error} Invalid parameters
210
356
  */
211
357
  function withMetadata (options) {
212
- this.options.withMetadata = is.bool(options) ? options : true;
358
+ this.keepMetadata();
359
+ this.withIccProfile('srgb');
213
360
  if (is.object(options)) {
214
361
  if (is.defined(options.orientation)) {
215
362
  if (is.integer(options.orientation) && is.inRange(options.orientation, 1, 8)) {
@@ -226,30 +373,10 @@ function withMetadata (options) {
226
373
  }
227
374
  }
228
375
  if (is.defined(options.icc)) {
229
- if (is.string(options.icc)) {
230
- this.options.withMetadataIcc = options.icc;
231
- } else {
232
- throw is.invalidParameterError('icc', 'string filesystem path to ICC profile', options.icc);
233
- }
376
+ this.withIccProfile(options.icc);
234
377
  }
235
378
  if (is.defined(options.exif)) {
236
- if (is.object(options.exif)) {
237
- for (const [ifd, entries] of Object.entries(options.exif)) {
238
- if (is.object(entries)) {
239
- for (const [k, v] of Object.entries(entries)) {
240
- if (is.string(v)) {
241
- this.options.withMetadataStrs[`exif-${ifd.toLowerCase()}-${k}`] = v;
242
- } else {
243
- throw is.invalidParameterError(`exif.${ifd}.${k}`, 'string', v);
244
- }
245
- }
246
- } else {
247
- throw is.invalidParameterError(`exif.${ifd}`, 'object', entries);
248
- }
249
- }
250
- } else {
251
- throw is.invalidParameterError('exif', 'object', options.exif);
252
- }
379
+ this.withExifMerge(options.exif);
253
380
  }
254
381
  }
255
382
  return this;
@@ -780,6 +907,7 @@ function trySetAnimationOptions (source, target) {
780
907
  * @param {number} [options.yres=1.0] - vertical resolution in pixels/mm
781
908
  * @param {string} [options.resolutionUnit='inch'] - resolution unit options: inch, cm
782
909
  * @param {number} [options.bitdepth=8] - reduce bitdepth to 1, 2 or 4 bit
910
+ * @param {boolean} [options.miniswhite=false] - write 1-bit images as miniswhite
783
911
  * @returns {Sharp}
784
912
  * @throws {Error} Invalid options
785
913
  */
@@ -817,6 +945,10 @@ function tiff (options) {
817
945
  throw is.invalidParameterError('tileHeight', 'integer greater than zero', options.tileHeight);
818
946
  }
819
947
  }
948
+ // miniswhite
949
+ if (is.defined(options.miniswhite)) {
950
+ this._setBooleanOption('tiffMiniswhite', options.miniswhite);
951
+ }
820
952
  // pyramid
821
953
  if (is.defined(options.pyramid)) {
822
954
  this._setBooleanOption('tiffPyramid', options.pyramid);
@@ -1282,7 +1414,8 @@ function _read () {
1282
1414
  /* istanbul ignore else */
1283
1415
  if (!this.options.streamOut) {
1284
1416
  this.options.streamOut = true;
1285
- this._pipeline();
1417
+ const stack = Error();
1418
+ this._pipeline(undefined, stack);
1286
1419
  }
1287
1420
  }
1288
1421
 
@@ -1291,18 +1424,30 @@ function _read () {
1291
1424
  * Supports callback, stream and promise variants
1292
1425
  * @private
1293
1426
  */
1294
- function _pipeline (callback) {
1427
+ function _pipeline (callback, stack) {
1295
1428
  if (typeof callback === 'function') {
1296
1429
  // output=file/buffer
1297
1430
  if (this._isStreamInput()) {
1298
1431
  // output=file/buffer, input=stream
1299
1432
  this.on('finish', () => {
1300
1433
  this._flattenBufferIn();
1301
- sharp.pipeline(this.options, callback);
1434
+ sharp.pipeline(this.options, (err, data, info) => {
1435
+ if (err) {
1436
+ callback(is.nativeError(err, stack));
1437
+ } else {
1438
+ callback(null, data, info);
1439
+ }
1440
+ });
1302
1441
  });
1303
1442
  } else {
1304
1443
  // output=file/buffer, input=file/buffer
1305
- sharp.pipeline(this.options, callback);
1444
+ sharp.pipeline(this.options, (err, data, info) => {
1445
+ if (err) {
1446
+ callback(is.nativeError(err, stack));
1447
+ } else {
1448
+ callback(null, data, info);
1449
+ }
1450
+ });
1306
1451
  }
1307
1452
  return this;
1308
1453
  } else if (this.options.streamOut) {
@@ -1313,7 +1458,7 @@ function _pipeline (callback) {
1313
1458
  this._flattenBufferIn();
1314
1459
  sharp.pipeline(this.options, (err, data, info) => {
1315
1460
  if (err) {
1316
- this.emit('error', err);
1461
+ this.emit('error', is.nativeError(err, stack));
1317
1462
  } else {
1318
1463
  this.emit('info', info);
1319
1464
  this.push(data);
@@ -1329,7 +1474,7 @@ function _pipeline (callback) {
1329
1474
  // output=stream, input=file/buffer
1330
1475
  sharp.pipeline(this.options, (err, data, info) => {
1331
1476
  if (err) {
1332
- this.emit('error', err);
1477
+ this.emit('error', is.nativeError(err, stack));
1333
1478
  } else {
1334
1479
  this.emit('info', info);
1335
1480
  this.push(data);
@@ -1348,7 +1493,7 @@ function _pipeline (callback) {
1348
1493
  this._flattenBufferIn();
1349
1494
  sharp.pipeline(this.options, (err, data, info) => {
1350
1495
  if (err) {
1351
- reject(err);
1496
+ reject(is.nativeError(err, stack));
1352
1497
  } else {
1353
1498
  if (this.options.resolveWithObject) {
1354
1499
  resolve({ data, info });
@@ -1364,7 +1509,7 @@ function _pipeline (callback) {
1364
1509
  return new Promise((resolve, reject) => {
1365
1510
  sharp.pipeline(this.options, (err, data, info) => {
1366
1511
  if (err) {
1367
- reject(err);
1512
+ reject(is.nativeError(err, stack));
1368
1513
  } else {
1369
1514
  if (this.options.resolveWithObject) {
1370
1515
  resolve({ data, info });
@@ -1387,6 +1532,12 @@ module.exports = function (Sharp) {
1387
1532
  // Public
1388
1533
  toFile,
1389
1534
  toBuffer,
1535
+ keepExif,
1536
+ withExif,
1537
+ withExifMerge,
1538
+ keepIccProfile,
1539
+ withIccProfile,
1540
+ keepMetadata,
1390
1541
  withMetadata,
1391
1542
  toFormat,
1392
1543
  jpeg,
package/lib/resize.js CHANGED
@@ -494,70 +494,67 @@ function extract (options) {
494
494
  *
495
495
  * If the result of this operation would trim an image to nothing then no change is made.
496
496
  *
497
- * The `info` response Object, obtained from callback of `.toFile()` or `.toBuffer()`,
498
- * will contain `trimOffsetLeft` and `trimOffsetTop` properties.
497
+ * The `info` response Object will contain `trimOffsetLeft` and `trimOffsetTop` properties.
499
498
  *
500
499
  * @example
501
500
  * // Trim pixels with a colour similar to that of the top-left pixel.
502
- * sharp(input)
501
+ * await sharp(input)
503
502
  * .trim()
504
- * .toFile(output, function(err, info) {
505
- * ...
506
- * });
503
+ * .toFile(output);
504
+ *
507
505
  * @example
508
506
  * // Trim pixels with the exact same colour as that of the top-left pixel.
509
- * sharp(input)
510
- * .trim(0)
511
- * .toFile(output, function(err, info) {
512
- * ...
513
- * });
507
+ * await sharp(input)
508
+ * .trim({
509
+ * threshold: 0
510
+ * })
511
+ * .toFile(output);
512
+ *
514
513
  * @example
515
- * // Trim only pixels with a similar colour to red.
516
- * sharp(input)
517
- * .trim("#FF0000")
518
- * .toFile(output, function(err, info) {
519
- * ...
520
- * });
514
+ * // Assume input is line art and trim only pixels with a similar colour to red.
515
+ * const output = await sharp(input)
516
+ * .trim({
517
+ * background: "#FF0000",
518
+ * lineArt: true
519
+ * })
520
+ * .toBuffer();
521
+ *
521
522
  * @example
522
523
  * // Trim all "yellow-ish" pixels, being more lenient with the higher threshold.
523
- * sharp(input)
524
+ * const output = await sharp(input)
524
525
  * .trim({
525
526
  * background: "yellow",
526
527
  * threshold: 42,
527
528
  * })
528
- * .toFile(output, function(err, info) {
529
- * ...
530
- * });
529
+ * .toBuffer();
531
530
  *
532
- * @param {string|number|Object} trim - the specific background colour to trim, the threshold for doing so or an Object with both.
533
- * @param {string|Object} [trim.background='top-left pixel'] - background colour, parsed by the [color](https://www.npmjs.org/package/color) module, defaults to that of the top-left pixel.
534
- * @param {number} [trim.threshold=10] - the allowed difference from the above colour, a positive number.
531
+ * @param {Object} [options]
532
+ * @param {string|Object} [options.background='top-left pixel'] - Background colour, parsed by the [color](https://www.npmjs.org/package/color) module, defaults to that of the top-left pixel.
533
+ * @param {number} [options.threshold=10] - Allowed difference from the above colour, a positive number.
534
+ * @param {boolean} [options.lineArt=false] - Does the input more closely resemble line art (e.g. vector) rather than being photographic?
535
535
  * @returns {Sharp}
536
536
  * @throws {Error} Invalid parameters
537
537
  */
538
- function trim (trim) {
539
- if (!is.defined(trim)) {
540
- this.options.trimThreshold = 10;
541
- } else if (is.string(trim)) {
542
- this._setBackgroundColourOption('trimBackground', trim);
543
- this.options.trimThreshold = 10;
544
- } else if (is.number(trim)) {
545
- if (trim >= 0) {
546
- this.options.trimThreshold = trim;
547
- } else {
548
- throw is.invalidParameterError('threshold', 'positive number', trim);
549
- }
550
- } else if (is.object(trim)) {
551
- this._setBackgroundColourOption('trimBackground', trim.background);
552
- if (!is.defined(trim.threshold)) {
553
- this.options.trimThreshold = 10;
554
- } else if (is.number(trim.threshold) && trim.threshold >= 0) {
555
- this.options.trimThreshold = trim.threshold;
538
+ function trim (options) {
539
+ this.options.trimThreshold = 10;
540
+ if (is.defined(options)) {
541
+ if (is.object(options)) {
542
+ if (is.defined(options.background)) {
543
+ this._setBackgroundColourOption('trimBackground', options.background);
544
+ }
545
+ if (is.defined(options.threshold)) {
546
+ if (is.number(options.threshold) && options.threshold >= 0) {
547
+ this.options.trimThreshold = options.threshold;
548
+ } else {
549
+ throw is.invalidParameterError('threshold', 'positive number', options.threshold);
550
+ }
551
+ }
552
+ if (is.defined(options.lineArt)) {
553
+ this._setBooleanOption('trimLineArt', options.lineArt);
554
+ }
556
555
  } else {
557
- throw is.invalidParameterError('threshold', 'positive number', trim);
556
+ throw is.invalidParameterError('trim', 'object', options);
558
557
  }
559
- } else {
560
- throw is.invalidParameterError('trim', 'string, number or object', trim);
561
558
  }
562
559
  if (isRotationExpected(this.options)) {
563
560
  this.options.rotateBeforePreExtract = true;
package/lib/sharp.js CHANGED
@@ -9,61 +9,78 @@ const { familySync, versionSync } = require('detect-libc');
9
9
 
10
10
  const { runtimePlatformArch, prebuiltPlatforms, minimumLibvipsVersion } = require('./libvips');
11
11
  const runtimePlatform = runtimePlatformArch();
12
- const [isLinux, isMacOs, isWindows] = ['linux', 'darwin', 'win32'].map(os => runtimePlatform.startsWith(os));
13
12
 
14
- /* istanbul ignore next */
15
- try {
16
- // Check for local build
17
- module.exports = require(`../src/build/Release/sharp-${runtimePlatform}.node`);
18
- } catch (errLocal) {
13
+ const paths = [
14
+ `../src/build/Release/sharp-${runtimePlatform}.node`,
15
+ '../src/build/Release/sharp-wasm32.node',
16
+ `@img/sharp-${runtimePlatform}/sharp.node`,
17
+ '@img/sharp-wasm32/sharp.node'
18
+ ];
19
+
20
+ let sharp;
21
+ const errors = [];
22
+ for (const path of paths) {
19
23
  try {
20
- // Check for runtime package
21
- module.exports = require(`@sharpen/sharp-${runtimePlatform}/sharp.node`);
22
- } catch (errPackage) {
23
- const help = ['Could not load the "sharp" module at runtime'];
24
- if (errLocal.code !== 'MODULE_NOT_FOUND') {
25
- help.push(`${errLocal.code}: ${errLocal.message}`);
26
- }
27
- if (errPackage.code !== 'MODULE_NOT_FOUND') {
28
- help.push(`${errPackage.code}: ${errPackage.message}`);
29
- }
30
- help.push('Possible solutions:');
31
- // Common error messages
32
- if (prebuiltPlatforms.includes(runtimePlatform)) {
33
- help.push('- Add an explicit dependency for the runtime platform:');
34
- help.push(` npm install --force @sharpen/sharp-${runtimePlatform}"`);
35
- } else {
36
- help.push(`- The ${runtimePlatform} platform requires manual installation of libvips >= ${minimumLibvipsVersion}`);
37
- }
38
- if (isLinux && /symbol not found/i.test(errPackage)) {
39
- try {
40
- const { engines } = require(`@sharpen/sharp-libvips-${runtimePlatform}/package`);
41
- const libcFound = `${familySync()} ${versionSync()}`;
42
- const libcRequires = `${engines.musl ? 'musl' : 'glibc'} ${engines.musl || engines.glibc}`;
43
- help.push('- Update your OS:');
44
- help.push(` Found ${libcFound}`);
45
- help.push(` Requires ${libcRequires}`);
46
- } catch (errEngines) {}
47
- }
48
- if (isMacOs && /Incompatible library version/.test(errLocal.message)) {
49
- help.push('- Update Homebrew:');
50
- help.push(' brew update && brew upgrade vips');
51
- }
52
- if (errPackage.code === 'ERR_DLOPEN_DISABLED') {
53
- help.push('- Run Node.js without using the --no-addons flag');
54
- }
55
- if (process.versions.pnp) {
56
- help.push('- Use a supported yarn linker, either pnpm or node-modules:');
57
- help.push(' yarn config set nodeLinker node-modules');
58
- }
59
- // Link to installation docs
60
- if (isLinux && /Module did not self-register/.test(errLocal.message + errPackage.message)) {
61
- help.push('- Using worker threads on Linux? See https://sharp.pixelplumbing.com/install#worker-threads');
62
- } else if (isWindows && /The specified procedure could not be found/.test(errPackage.message)) {
63
- help.push('- Using the canvas package on Windows? See https://sharp.pixelplumbing.com/install#canvas-and-windows');
64
- } else {
65
- help.push('- Consult the installation documentation: https://sharp.pixelplumbing.com/install');
24
+ sharp = require(path);
25
+ break;
26
+ } catch (err) {
27
+ /* istanbul ignore next */
28
+ errors.push(err);
29
+ }
30
+ }
31
+
32
+ /* istanbul ignore next */
33
+ if (sharp) {
34
+ module.exports = sharp;
35
+ } else {
36
+ const [isLinux, isMacOs, isWindows] = ['linux', 'darwin', 'win32'].map(os => runtimePlatform.startsWith(os));
37
+
38
+ const help = [`Could not load the "sharp" module using the ${runtimePlatform} runtime`];
39
+ errors.forEach(err => {
40
+ if (err.code !== 'MODULE_NOT_FOUND') {
41
+ help.push(`${err.code}: ${err.message}`);
66
42
  }
67
- throw new Error(help.join('\n'));
43
+ });
44
+ const messages = errors.map(err => err.message).join(' ');
45
+ help.push('Possible solutions:');
46
+ // Common error messages
47
+ if (prebuiltPlatforms.includes(runtimePlatform)) {
48
+ const [os, cpu] = runtimePlatform.split('-');
49
+ help.push('- Add platform-specific dependencies:');
50
+ help.push(` npm install --os=${os} --cpu=${cpu} sharp`);
51
+ help.push(' or');
52
+ help.push(` npm install --force @img/sharp-${runtimePlatform}`);
53
+ } else {
54
+ help.push(`- Manually install libvips >= ${minimumLibvipsVersion}`);
55
+ help.push('- Add experimental WebAssembly-based dependencies:');
56
+ help.push(' npm install --cpu=wasm32 sharp');
57
+ }
58
+ if (isLinux && /symbol not found/i.test(messages)) {
59
+ try {
60
+ const { engines } = require(`@img/sharp-libvips-${runtimePlatform}/package`);
61
+ const libcFound = `${familySync()} ${versionSync()}`;
62
+ const libcRequires = `${engines.musl ? 'musl' : 'glibc'} ${engines.musl || engines.glibc}`;
63
+ help.push('- Update your OS:');
64
+ help.push(` Found ${libcFound}`);
65
+ help.push(` Requires ${libcRequires}`);
66
+ } catch (errEngines) {}
67
+ }
68
+ if (isMacOs && /Incompatible library version/.test(messages)) {
69
+ help.push('- Update Homebrew:');
70
+ help.push(' brew update && brew upgrade vips');
71
+ }
72
+ if (errors.some(err => err.code === 'ERR_DLOPEN_DISABLED')) {
73
+ help.push('- Run Node.js without using the --no-addons flag');
74
+ }
75
+ if (process.versions.pnp) {
76
+ help.push('- Use a supported yarn linker, either pnpm or node-modules:');
77
+ help.push(' yarn config set nodeLinker node-modules');
78
+ }
79
+ // Link to installation docs
80
+ if (isWindows && /The specified procedure could not be found/.test(messages)) {
81
+ help.push('- Using the canvas package on Windows? See https://sharp.pixelplumbing.com/install#canvas-and-windows');
82
+ } else {
83
+ help.push('- Consult the installation documentation: https://sharp.pixelplumbing.com/install');
68
84
  }
85
+ throw new Error(help.join('\n'));
69
86
  }
package/lib/utility.js CHANGED
@@ -59,11 +59,17 @@ let versions = {
59
59
  };
60
60
  /* istanbul ignore next */
61
61
  if (!libvipsVersion.isGlobal) {
62
- try {
63
- versions = require(`@sharpen/sharp-${runtimePlatform}/versions`);
64
- } catch (_) {
62
+ if (!libvipsVersion.isWasm) {
65
63
  try {
66
- versions = require(`@sharpen/sharp-libvips-${runtimePlatform}/versions`);
64
+ versions = require(`@img/sharp-${runtimePlatform}/versions`);
65
+ } catch (_) {
66
+ try {
67
+ versions = require(`@img/sharp-libvips-${runtimePlatform}/versions`);
68
+ } catch (_) {}
69
+ }
70
+ } else {
71
+ try {
72
+ versions = require('@img/sharp-wasm32/versions');
67
73
  } catch (_) {}
68
74
  }
69
75
  }