aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2022-06-24 16:31:32 +0200
committerChocobozzz <me@florianbigard.com>2022-06-24 16:31:48 +0200
commitc53853ca1b8e32aea5259d436d3d284b9d178919 (patch)
treedfb081e7fb471ca4b75fd67eabd3216cc6b33129
parent2873f00bd89d8f1b5f88614415f8142a5c2065c3 (diff)
downloadPeerTube-c53853ca1b8e32aea5259d436d3d284b9d178919.tar.gz
PeerTube-c53853ca1b8e32aea5259d436d3d284b9d178919.tar.zst
PeerTube-c53853ca1b8e32aea5259d436d3d284b9d178919.zip
Introduce worker threads to process remote images
-rw-r--r--package.json1
-rw-r--r--server/controllers/lazy-static.ts4
-rw-r--r--server/helpers/requests.ts19
-rw-r--r--server/initializers/constants.ts9
-rw-r--r--server/lib/local-actor.ts35
-rw-r--r--server/lib/thumbnail.ts11
-rw-r--r--server/lib/worker/parent-process.ts18
-rw-r--r--server/lib/worker/workers/image-downloader.ts33
-rw-r--r--yarn.lock42
9 files changed, 123 insertions, 49 deletions
diff --git a/package.json b/package.json
index 0715cd224..67b80ac0e 100644
--- a/package.json
+++ b/package.json
@@ -137,6 +137,7 @@
137 "password-generator": "^2.0.2", 137 "password-generator": "^2.0.2",
138 "pem": "^1.12.3", 138 "pem": "^1.12.3",
139 "pg": "^8.2.1", 139 "pg": "^8.2.1",
140 "piscina": "^3.2.0",
140 "prompt": "^1.0.0", 141 "prompt": "^1.0.0",
141 "proxy-addr": "^2.0.7", 142 "proxy-addr": "^2.0.7",
142 "pug": "^3.0.0", 143 "pug": "^3.0.0",
diff --git a/server/controllers/lazy-static.ts b/server/controllers/lazy-static.ts
index 8a180b5bc..0cab5dcd0 100644
--- a/server/controllers/lazy-static.ts
+++ b/server/controllers/lazy-static.ts
@@ -6,7 +6,7 @@ import { HttpStatusCode } from '../../shared/models/http/http-error-codes'
6import { logger } from '../helpers/logger' 6import { logger } from '../helpers/logger'
7import { ACTOR_IMAGES_SIZE, LAZY_STATIC_PATHS, STATIC_MAX_AGE } from '../initializers/constants' 7import { ACTOR_IMAGES_SIZE, LAZY_STATIC_PATHS, STATIC_MAX_AGE } from '../initializers/constants'
8import { VideosCaptionCache, VideosPreviewCache } from '../lib/files-cache' 8import { VideosCaptionCache, VideosPreviewCache } from '../lib/files-cache'
9import { actorImagePathUnsafeCache, pushActorImageProcessInQueue } from '../lib/local-actor' 9import { actorImagePathUnsafeCache, downloadActorImageFromWorker } from '../lib/local-actor'
10import { asyncMiddleware } from '../middlewares' 10import { asyncMiddleware } from '../middlewares'
11import { ActorImageModel } from '../models/actor/actor-image' 11import { ActorImageModel } from '../models/actor/actor-image'
12 12
@@ -65,7 +65,7 @@ async function getActorImage (req: express.Request, res: express.Response, next:
65 logger.info('Lazy serve remote actor image %s.', image.fileUrl) 65 logger.info('Lazy serve remote actor image %s.', image.fileUrl)
66 66
67 try { 67 try {
68 await pushActorImageProcessInQueue({ 68 await downloadActorImageFromWorker({
69 filename: image.filename, 69 filename: image.filename,
70 fileUrl: image.fileUrl, 70 fileUrl: image.fileUrl,
71 size: getActorImageSize(image), 71 size: getActorImageSize(image),
diff --git a/server/helpers/requests.ts b/server/helpers/requests.ts
index a9869e987..495e83558 100644
--- a/server/helpers/requests.ts
+++ b/server/helpers/requests.ts
@@ -1,11 +1,8 @@
1import { createWriteStream, remove } from 'fs-extra' 1import { createWriteStream, remove } from 'fs-extra'
2import got, { CancelableRequest, NormalizedOptions, Options as GotOptions, RequestError, Response } from 'got' 2import got, { CancelableRequest, NormalizedOptions, Options as GotOptions, RequestError, Response } from 'got'
3import { HttpProxyAgent, HttpsProxyAgent } from 'hpagent' 3import { HttpProxyAgent, HttpsProxyAgent } from 'hpagent'
4import { join } from 'path'
5import { CONFIG } from '../initializers/config'
6import { ACTIVITY_PUB, BINARY_CONTENT_TYPES, PEERTUBE_VERSION, REQUEST_TIMEOUTS, WEBSERVER } from '../initializers/constants' 4import { ACTIVITY_PUB, BINARY_CONTENT_TYPES, PEERTUBE_VERSION, REQUEST_TIMEOUTS, WEBSERVER } from '../initializers/constants'
7import { pipelinePromise } from './core-utils' 5import { pipelinePromise } from './core-utils'
8import { processImage } from './image-utils'
9import { logger, loggerTagsFactory } from './logger' 6import { logger, loggerTagsFactory } from './logger'
10import { getProxy, isProxyEnabled } from './proxy' 7import { getProxy, isProxyEnabled } from './proxy'
11 8
@@ -147,21 +144,6 @@ async function doRequestAndSaveToFile (
147 } 144 }
148} 145}
149 146
150async function downloadImage (url: string, destDir: string, destName: string, size: { width: number, height: number }) {
151 const tmpPath = join(CONFIG.STORAGE.TMP_DIR, 'pending-' + destName)
152 await doRequestAndSaveToFile(url, tmpPath)
153
154 const destPath = join(destDir, destName)
155
156 try {
157 await processImage(tmpPath, destPath, size)
158 } catch (err) {
159 await remove(tmpPath)
160
161 throw err
162 }
163}
164
165function getAgent () { 147function getAgent () {
166 if (!isProxyEnabled()) return {} 148 if (!isProxyEnabled()) return {}
167 149
@@ -211,7 +193,6 @@ export {
211 doJSONRequest, 193 doJSONRequest,
212 doRequestAndSaveToFile, 194 doRequestAndSaveToFile,
213 isBinaryResponse, 195 isBinaryResponse,
214 downloadImage,
215 getAgent, 196 getAgent,
216 findLatestRedirection, 197 findLatestRedirection,
217 peertubeGot 198 peertubeGot
diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts
index d469ce425..175935835 100644
--- a/server/initializers/constants.ts
+++ b/server/initializers/constants.ts
@@ -744,8 +744,11 @@ const MEMOIZE_LENGTH = {
744 VIDEO_DURATION: 200 744 VIDEO_DURATION: 200
745} 745}
746 746
747const QUEUE_CONCURRENCY = { 747const WORKER_THREADS = {
748 ACTOR_PROCESS_IMAGE: 3 748 DOWNLOAD_IMAGE: {
749 CONCURRENCY: 3,
750 MAX_THREADS: 1
751 }
749} 752}
750 753
751const REDUNDANCY = { 754const REDUNDANCY = {
@@ -955,7 +958,7 @@ export {
955 VIDEO_PRIVACIES, 958 VIDEO_PRIVACIES,
956 VIDEO_LICENCES, 959 VIDEO_LICENCES,
957 VIDEO_STATES, 960 VIDEO_STATES,
958 QUEUE_CONCURRENCY, 961 WORKER_THREADS,
959 VIDEO_RATE_TYPES, 962 VIDEO_RATE_TYPES,
960 JOB_PRIORITY, 963 JOB_PRIORITY,
961 VIDEO_TRANSCODING_FPS, 964 VIDEO_TRANSCODING_FPS,
diff --git a/server/lib/local-actor.ts b/server/lib/local-actor.ts
index 01046d017..e3b04c094 100644
--- a/server/lib/local-actor.ts
+++ b/server/lib/local-actor.ts
@@ -1,4 +1,3 @@
1import { queue } from 'async'
2import { remove } from 'fs-extra' 1import { remove } from 'fs-extra'
3import LRUCache from 'lru-cache' 2import LRUCache from 'lru-cache'
4import { join } from 'path' 3import { join } from 'path'
@@ -8,13 +7,13 @@ import { buildUUID } from '@shared/extra-utils'
8import { ActivityPubActorType, ActorImageType } from '@shared/models' 7import { ActivityPubActorType, ActorImageType } from '@shared/models'
9import { retryTransactionWrapper } from '../helpers/database-utils' 8import { retryTransactionWrapper } from '../helpers/database-utils'
10import { processImage } from '../helpers/image-utils' 9import { processImage } from '../helpers/image-utils'
11import { downloadImage } from '../helpers/requests'
12import { CONFIG } from '../initializers/config' 10import { CONFIG } from '../initializers/config'
13import { ACTOR_IMAGES_SIZE, LRU_CACHE, QUEUE_CONCURRENCY, WEBSERVER } from '../initializers/constants' 11import { ACTOR_IMAGES_SIZE, LRU_CACHE, WEBSERVER } from '../initializers/constants'
14import { sequelizeTypescript } from '../initializers/database' 12import { sequelizeTypescript } from '../initializers/database'
15import { MAccountDefault, MActor, MChannelDefault } from '../types/models' 13import { MAccountDefault, MActor, MChannelDefault } from '../types/models'
16import { deleteActorImages, updateActorImages } from './activitypub/actors' 14import { deleteActorImages, updateActorImages } from './activitypub/actors'
17import { sendUpdateActor } from './activitypub/send' 15import { sendUpdateActor } from './activitypub/send'
16import { downloadImageFromWorker } from './worker/parent-process'
18 17
19function buildActorInstance (type: ActivityPubActorType, url: string, preferredUsername: string) { 18function buildActorInstance (type: ActivityPubActorType, url: string, preferredUsername: string) {
20 return new ActorModel({ 19 return new ActorModel({
@@ -87,27 +86,22 @@ async function deleteLocalActorImageFile (accountOrChannel: MAccountDefault | MC
87 }) 86 })
88} 87}
89 88
90type DownloadImageQueueTask = { 89// ---------------------------------------------------------------------------
90
91function downloadActorImageFromWorker (options: {
91 fileUrl: string 92 fileUrl: string
92 filename: string 93 filename: string
93 type: ActorImageType 94 type: ActorImageType
94 size: typeof ACTOR_IMAGES_SIZE[ActorImageType][0] 95 size: typeof ACTOR_IMAGES_SIZE[ActorImageType][0]
95} 96}) {
96 97 const downloaderOptions = {
97const downloadImageQueue = queue<DownloadImageQueueTask, Error>((task, cb) => { 98 url: options.fileUrl,
98 downloadImage(task.fileUrl, CONFIG.STORAGE.ACTOR_IMAGES, task.filename, task.size) 99 destDir: CONFIG.STORAGE.ACTOR_IMAGES,
99 .then(() => cb()) 100 destName: options.filename,
100 .catch(err => cb(err)) 101 size: options.size
101}, QUEUE_CONCURRENCY.ACTOR_PROCESS_IMAGE) 102 }
102
103function pushActorImageProcessInQueue (task: DownloadImageQueueTask) {
104 return new Promise<void>((res, rej) => {
105 downloadImageQueue.push(task, err => {
106 if (err) return rej(err)
107 103
108 return res() 104 return downloadImageFromWorker(downloaderOptions)
109 })
110 })
111} 105}
112 106
113// Unsafe so could returns paths that does not exist anymore 107// Unsafe so could returns paths that does not exist anymore
@@ -116,7 +110,8 @@ const actorImagePathUnsafeCache = new LRUCache<string, string>({ max: LRU_CACHE.
116export { 110export {
117 actorImagePathUnsafeCache, 111 actorImagePathUnsafeCache,
118 updateLocalActorImageFiles, 112 updateLocalActorImageFiles,
113 downloadActorImageFromWorker,
119 deleteLocalActorImageFile, 114 deleteLocalActorImageFile,
120 pushActorImageProcessInQueue, 115 downloadImageFromWorker,
121 buildActorInstance 116 buildActorInstance
122} 117}
diff --git a/server/lib/thumbnail.ts b/server/lib/thumbnail.ts
index aa2d7a813..f00c87623 100644
--- a/server/lib/thumbnail.ts
+++ b/server/lib/thumbnail.ts
@@ -1,13 +1,13 @@
1import { join } from 'path' 1import { join } from 'path'
2import { ThumbnailType } from '@shared/models' 2import { ThumbnailType } from '@shared/models'
3import { generateImageFilename, generateImageFromVideoFile, processImage } from '../helpers/image-utils' 3import { generateImageFilename, generateImageFromVideoFile, processImage } from '../helpers/image-utils'
4import { downloadImage } from '../helpers/requests'
5import { CONFIG } from '../initializers/config' 4import { CONFIG } from '../initializers/config'
6import { ASSETS_PATH, PREVIEWS_SIZE, THUMBNAILS_SIZE } from '../initializers/constants' 5import { ASSETS_PATH, PREVIEWS_SIZE, THUMBNAILS_SIZE } from '../initializers/constants'
7import { ThumbnailModel } from '../models/video/thumbnail' 6import { ThumbnailModel } from '../models/video/thumbnail'
8import { MVideoFile, MVideoThumbnail, MVideoUUID } from '../types/models' 7import { MVideoFile, MVideoThumbnail, MVideoUUID } from '../types/models'
9import { MThumbnail } from '../types/models/video/thumbnail' 8import { MThumbnail } from '../types/models/video/thumbnail'
10import { MVideoPlaylistThumbnail } from '../types/models/video/video-playlist' 9import { MVideoPlaylistThumbnail } from '../types/models/video/video-playlist'
10import { downloadImageFromWorker } from './local-actor'
11import { VideoPathManager } from './video-path-manager' 11import { VideoPathManager } from './video-path-manager'
12 12
13type ImageSize = { height?: number, width?: number } 13type ImageSize = { height?: number, width?: number }
@@ -49,7 +49,10 @@ function updatePlaylistMiniatureFromUrl (options: {
49 ? null 49 ? null
50 : downloadUrl 50 : downloadUrl
51 51
52 const thumbnailCreator = () => downloadImage(downloadUrl, basePath, filename, { width, height }) 52 const thumbnailCreator = () => {
53 return downloadImageFromWorker({ url: downloadUrl, destDir: basePath, destName: filename, size: { width, height } })
54 }
55
53 return updateThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, existingThumbnail, fileUrl }) 56 return updateThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, existingThumbnail, fileUrl })
54} 57}
55 58
@@ -75,7 +78,9 @@ function updateVideoMiniatureFromUrl (options: {
75 : existingThumbnail.filename 78 : existingThumbnail.filename
76 79
77 const thumbnailCreator = () => { 80 const thumbnailCreator = () => {
78 if (thumbnailUrlChanged) return downloadImage(downloadUrl, basePath, filename, { width, height }) 81 if (thumbnailUrlChanged) {
82 return downloadImageFromWorker({ url: downloadUrl, destDir: basePath, destName: filename, size: { width, height } })
83 }
79 84
80 return Promise.resolve() 85 return Promise.resolve()
81 } 86 }
diff --git a/server/lib/worker/parent-process.ts b/server/lib/worker/parent-process.ts
new file mode 100644
index 000000000..18dabd97f
--- /dev/null
+++ b/server/lib/worker/parent-process.ts
@@ -0,0 +1,18 @@
1import { join } from 'path'
2import Piscina from 'piscina'
3import { WORKER_THREADS } from '@server/initializers/constants'
4import { downloadImage } from './workers/image-downloader'
5
6const downloadImagerWorker = new Piscina({
7 filename: join(__dirname, 'workers', 'image-downloader.js'),
8 concurrentTasksPerWorker: WORKER_THREADS.DOWNLOAD_IMAGE.CONCURRENCY,
9 maxThreads: WORKER_THREADS.DOWNLOAD_IMAGE.MAX_THREADS
10})
11
12function downloadImageFromWorker (options: Parameters<typeof downloadImage>[0]): Promise<ReturnType<typeof downloadImage>> {
13 return downloadImagerWorker.run(options)
14}
15
16export {
17 downloadImageFromWorker
18}
diff --git a/server/lib/worker/workers/image-downloader.ts b/server/lib/worker/workers/image-downloader.ts
new file mode 100644
index 000000000..8d4a6b37e
--- /dev/null
+++ b/server/lib/worker/workers/image-downloader.ts
@@ -0,0 +1,33 @@
1import { remove } from 'fs-extra'
2import { join } from 'path'
3import { processImage } from '@server/helpers/image-utils'
4import { doRequestAndSaveToFile } from '@server/helpers/requests'
5import { CONFIG } from '@server/initializers/config'
6
7async function downloadImage (options: {
8 url: string
9 destDir: string
10 destName: string
11 size: { width: number, height: number }
12}) {
13 const { url, destDir, destName, size } = options
14
15 const tmpPath = join(CONFIG.STORAGE.TMP_DIR, 'pending-' + destName)
16 await doRequestAndSaveToFile(url, tmpPath)
17
18 const destPath = join(destDir, destName)
19
20 try {
21 await processImage(tmpPath, destPath, size)
22 } catch (err) {
23 await remove(tmpPath)
24
25 throw err
26 }
27}
28
29module.exports = downloadImage
30
31export {
32 downloadImage
33}
diff --git a/yarn.lock b/yarn.lock
index e8386afe3..a0519d54e 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -44,6 +44,11 @@
44 ajv-draft-04 "^1.0.0" 44 ajv-draft-04 "^1.0.0"
45 call-me-maybe "^1.0.1" 45 call-me-maybe "^1.0.1"
46 46
47"@assemblyscript/loader@^0.10.1":
48 version "0.10.1"
49 resolved "https://registry.yarnpkg.com/@assemblyscript/loader/-/loader-0.10.1.tgz#70e45678f06c72fa2e350e8553ec4a4d72b92e06"
50 integrity sha512-H71nDOOL8Y7kWRLqf6Sums+01Q5msqBW2KhDUTemh1tvY04eSkSXrK0uj/4mmY0Xr16/3zyZmsrxN7CKuRbNRg==
51
47"@assemblyscript/loader@^0.19.21": 52"@assemblyscript/loader@^0.19.21":
48 version "0.19.23" 53 version "0.19.23"
49 resolved "https://registry.yarnpkg.com/@assemblyscript/loader/-/loader-0.19.23.tgz#7fccae28d0a2692869f1d1219d36093bc24d5e72" 54 resolved "https://registry.yarnpkg.com/@assemblyscript/loader/-/loader-0.19.23.tgz#7fccae28d0a2692869f1d1219d36093bc24d5e72"
@@ -4357,6 +4362,11 @@ event-target-shim@^5.0.0:
4357 resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" 4362 resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789"
4358 integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== 4363 integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==
4359 4364
4365eventemitter-asyncresource@^1.0.0:
4366 version "1.0.0"
4367 resolved "https://registry.yarnpkg.com/eventemitter-asyncresource/-/eventemitter-asyncresource-1.0.0.tgz#734ff2e44bf448e627f7748f905d6bdd57bdb65b"
4368 integrity sha512-39F7TBIV0G7gTelxwbEqnwhp90eqCPON1k0NwNfwhgKn4Co4ybUbj2pECcXT0B3ztRKZ7Pw1JujUUgmQJHcVAQ==
4369
4360events@3.3.0, events@^3.3.0: 4370events@3.3.0, events@^3.3.0:
4361 version "3.3.0" 4371 version "3.3.0"
4362 resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" 4372 resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400"
@@ -4985,6 +4995,15 @@ has@^1.0.3:
4985 dependencies: 4995 dependencies:
4986 function-bind "^1.1.1" 4996 function-bind "^1.1.1"
4987 4997
4998hdr-histogram-js@^2.0.1:
4999 version "2.0.3"
5000 resolved "https://registry.yarnpkg.com/hdr-histogram-js/-/hdr-histogram-js-2.0.3.tgz#0b860534655722b6e3f3e7dca7b78867cf43dcb5"
5001 integrity sha512-Hkn78wwzWHNCp2uarhzQ2SGFLU3JY8SBDDd3TAABK4fc30wm+MuPOrg5QVFVfkKOQd6Bfz3ukJEI+q9sXEkK1g==
5002 dependencies:
5003 "@assemblyscript/loader" "^0.10.1"
5004 base64-js "^1.2.0"
5005 pako "^1.0.3"
5006
4988hdr-histogram-js@^3.0.0: 5007hdr-histogram-js@^3.0.0:
4989 version "3.0.0" 5008 version "3.0.0"
4990 resolved "https://registry.yarnpkg.com/hdr-histogram-js/-/hdr-histogram-js-3.0.0.tgz#8e2d9a68e3313147804c47d85a9c22a93f85e24b" 5009 resolved "https://registry.yarnpkg.com/hdr-histogram-js/-/hdr-histogram-js-3.0.0.tgz#8e2d9a68e3313147804c47d85a9c22a93f85e24b"
@@ -6477,7 +6496,15 @@ next-tick@1, next-tick@^1.1.0:
6477 resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.1.0.tgz#1836ee30ad56d67ef281b22bd199f709449b35eb" 6496 resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.1.0.tgz#1836ee30ad56d67ef281b22bd199f709449b35eb"
6478 integrity sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ== 6497 integrity sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==
6479 6498
6480node-addon-api@^3.1.0: 6499nice-napi@^1.0.2:
6500 version "1.0.2"
6501 resolved "https://registry.yarnpkg.com/nice-napi/-/nice-napi-1.0.2.tgz#dc0ab5a1eac20ce548802fc5686eaa6bc654927b"
6502 integrity sha512-px/KnJAJZf5RuBGcfD+Sp2pAKq0ytz8j+1NehvgIGFkvtvFrDM3T8E4x/JJODXK9WZow8RRGrbA9QQ3hs+pDhA==
6503 dependencies:
6504 node-addon-api "^3.0.0"
6505 node-gyp-build "^4.2.2"
6506
6507node-addon-api@^3.0.0, node-addon-api@^3.1.0:
6481 version "3.2.1" 6508 version "3.2.1"
6482 resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-3.2.1.tgz#81325e0a2117789c0128dab65e7e38f07ceba161" 6509 resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-3.2.1.tgz#81325e0a2117789c0128dab65e7e38f07ceba161"
6483 integrity sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A== 6510 integrity sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==
@@ -6513,7 +6540,7 @@ node-gyp-build-optional-packages@5.0.2:
6513 resolved "https://registry.yarnpkg.com/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.0.2.tgz#3de7d30bd1f9057b5dfbaeab4a4442b7fe9c5901" 6540 resolved "https://registry.yarnpkg.com/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.0.2.tgz#3de7d30bd1f9057b5dfbaeab4a4442b7fe9c5901"
6514 integrity sha512-PiN4NWmlQPqvbEFcH/omQsswWQbe5Z9YK/zdB23irp5j2XibaA2IrGvpSWmVVG4qMZdmPdwPctSy4a86rOMn6g== 6541 integrity sha512-PiN4NWmlQPqvbEFcH/omQsswWQbe5Z9YK/zdB23irp5j2XibaA2IrGvpSWmVVG4qMZdmPdwPctSy4a86rOMn6g==
6515 6542
6516node-gyp-build@^4.2.0, node-gyp-build@^4.3.0: 6543node-gyp-build@^4.2.0, node-gyp-build@^4.2.2, node-gyp-build@^4.3.0:
6517 version "4.4.0" 6544 version "4.4.0"
6518 resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.4.0.tgz#42e99687ce87ddeaf3a10b99dc06abc11021f3f4" 6545 resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.4.0.tgz#42e99687ce87ddeaf3a10b99dc06abc11021f3f4"
6519 integrity sha512-amJnQCcgtRVw9SvoebO3BKGESClrfXGCUTX9hSn1OuGQTQBOZmVd0Z0OlecpuRksKvbsUqALE8jls/ErClAPuQ== 6546 integrity sha512-amJnQCcgtRVw9SvoebO3BKGESClrfXGCUTX9hSn1OuGQTQBOZmVd0Z0OlecpuRksKvbsUqALE8jls/ErClAPuQ==
@@ -7033,6 +7060,17 @@ pify@^4.0.1:
7033 resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" 7060 resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231"
7034 integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== 7061 integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==
7035 7062
7063piscina@^3.2.0:
7064 version "3.2.0"
7065 resolved "https://registry.yarnpkg.com/piscina/-/piscina-3.2.0.tgz#f5a1dde0c05567775690cccefe59d9223924d154"
7066 integrity sha512-yn/jMdHRw+q2ZJhFhyqsmANcbF6V2QwmD84c6xRau+QpQOmtrBCoRGdvTfeuFDYXB5W2m6MfLkjkvQa9lUSmIA==
7067 dependencies:
7068 eventemitter-asyncresource "^1.0.0"
7069 hdr-histogram-js "^2.0.1"
7070 hdr-histogram-percentiles-obj "^3.0.0"
7071 optionalDependencies:
7072 nice-napi "^1.0.2"
7073
7036pixelmatch@^4.0.2: 7074pixelmatch@^4.0.2:
7037 version "4.0.2" 7075 version "4.0.2"
7038 resolved "https://registry.yarnpkg.com/pixelmatch/-/pixelmatch-4.0.2.tgz#8f47dcec5011b477b67db03c243bc1f3085e8854" 7076 resolved "https://registry.yarnpkg.com/pixelmatch/-/pixelmatch-4.0.2.tgz#8f47dcec5011b477b67db03c243bc1f3085e8854"