aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorkontrollanten <6680299+kontrollanten@users.noreply.github.com>2022-11-14 18:26:20 +0300
committerGitHub <noreply@github.com>2022-11-14 16:26:20 +0100
commit564b9b55976873d87e669ace916f037b72fe2865 (patch)
tree8c3c097cf3a34766e60fa1bdcf1de7d8558eff8c
parentff91b644fb1b063d0a8eff7492beb1a9bf7e4ce1 (diff)
downloadPeerTube-564b9b55976873d87e669ace916f037b72fe2865.tar.gz
PeerTube-564b9b55976873d87e669ace916f037b72fe2865.tar.zst
PeerTube-564b9b55976873d87e669ace916f037b72fe2865.zip
refactor(server): redis > ioredis (#5371)
* refactor(server): redis > ioredis * refactor(JobQueue): reuse redis connection builder * fix(redisio) * fix(redis): setValue * feat(redis): showFriendlyErrorStack * feat(redis): auto pipelining https://github.com/luin/ioredis/blob/308017a6b9429c16b074e03e70f5524499476fa9/README.md#autopipelining * dont use autopipelining for bullmq * ioredis events
-rw-r--r--package.json2
-rw-r--r--server/lib/job-queue/job-queue.ts21
-rw-r--r--server/lib/redis.ts82
-rw-r--r--server/middlewares/cache/shared/api-cache.ts6
-rw-r--r--yarn.lock65
5 files changed, 57 insertions, 119 deletions
diff --git a/package.json b/package.json
index 249455068..f0115fdbc 100644
--- a/package.json
+++ b/package.json
@@ -129,6 +129,7 @@
129 "helmet": "^6.0.0", 129 "helmet": "^6.0.0",
130 "hpagent": "^1.0.0", 130 "hpagent": "^1.0.0",
131 "http-problem-details": "^0.1.5", 131 "http-problem-details": "^0.1.5",
132 "ioredis": "^5.2.3",
132 "ip-anonymize": "^0.1.0", 133 "ip-anonymize": "^0.1.0",
133 "ipaddr.js": "2.0.1", 134 "ipaddr.js": "2.0.1",
134 "is-cidr": "^4.0.0", 135 "is-cidr": "^4.0.0",
@@ -157,7 +158,6 @@
157 "prompt": "^1.0.0", 158 "prompt": "^1.0.0",
158 "proxy-addr": "^2.0.7", 159 "proxy-addr": "^2.0.7",
159 "pug": "^3.0.0", 160 "pug": "^3.0.0",
160 "redis": "^4.0.1",
161 "reflect-metadata": "^0.1.12", 161 "reflect-metadata": "^0.1.12",
162 "sanitize-html": "2.x", 162 "sanitize-html": "2.x",
163 "sequelize": "6.21.6", 163 "sequelize": "6.21.6",
diff --git a/server/lib/job-queue/job-queue.ts b/server/lib/job-queue/job-queue.ts
index 655be6568..6bc59732f 100644
--- a/server/lib/job-queue/job-queue.ts
+++ b/server/lib/job-queue/job-queue.ts
@@ -63,6 +63,7 @@ import { processVideoLiveEnding } from './handlers/video-live-ending'
63import { processVideoStudioEdition } from './handlers/video-studio-edition' 63import { processVideoStudioEdition } from './handlers/video-studio-edition'
64import { processVideoTranscoding } from './handlers/video-transcoding' 64import { processVideoTranscoding } from './handlers/video-transcoding'
65import { processVideosViewsStats } from './handlers/video-views-stats' 65import { processVideosViewsStats } from './handlers/video-views-stats'
66import { Redis } from '../redis'
66 67
67export type CreateJobArgument = 68export type CreateJobArgument =
68 { type: 'activitypub-http-broadcast', payload: ActivitypubHttpBroadcastPayload } | 69 { type: 'activitypub-http-broadcast', payload: ActivitypubHttpBroadcastPayload } |
@@ -183,7 +184,7 @@ class JobQueue {
183 } 184 }
184 185
185 this.flowProducer = new FlowProducer({ 186 this.flowProducer = new FlowProducer({
186 connection: this.getRedisConnection(), 187 connection: Redis.getRedisClientOptions('FlowProducer'),
187 prefix: this.jobRedisPrefix 188 prefix: this.jobRedisPrefix
188 }) 189 })
189 this.flowProducer.on('error', err => { logger.error('Error in flow producer', { err }) }) 190 this.flowProducer.on('error', err => { logger.error('Error in flow producer', { err }) })
@@ -196,7 +197,7 @@ class JobQueue {
196 autorun: false, 197 autorun: false,
197 concurrency: this.getJobConcurrency(handlerName), 198 concurrency: this.getJobConcurrency(handlerName),
198 prefix: this.jobRedisPrefix, 199 prefix: this.jobRedisPrefix,
199 connection: this.getRedisConnection() 200 connection: Redis.getRedisClientOptions('Worker')
200 } 201 }
201 202
202 const handler = function (job: Job) { 203 const handler = function (job: Job) {
@@ -236,7 +237,7 @@ class JobQueue {
236 237
237 private buildQueue (handlerName: JobType) { 238 private buildQueue (handlerName: JobType) {
238 const queueOptions: QueueOptions = { 239 const queueOptions: QueueOptions = {
239 connection: this.getRedisConnection(), 240 connection: Redis.getRedisClientOptions('Queue'),
240 prefix: this.jobRedisPrefix 241 prefix: this.jobRedisPrefix
241 } 242 }
242 243
@@ -249,7 +250,7 @@ class JobQueue {
249 private buildQueueScheduler (handlerName: JobType) { 250 private buildQueueScheduler (handlerName: JobType) {
250 const queueSchedulerOptions: QueueSchedulerOptions = { 251 const queueSchedulerOptions: QueueSchedulerOptions = {
251 autorun: false, 252 autorun: false,
252 connection: this.getRedisConnection(), 253 connection: Redis.getRedisClientOptions('QueueScheduler'),
253 prefix: this.jobRedisPrefix, 254 prefix: this.jobRedisPrefix,
254 maxStalledCount: 10 255 maxStalledCount: 10
255 } 256 }
@@ -263,7 +264,7 @@ class JobQueue {
263 private buildQueueEvent (handlerName: JobType) { 264 private buildQueueEvent (handlerName: JobType) {
264 const queueEventsOptions: QueueEventsOptions = { 265 const queueEventsOptions: QueueEventsOptions = {
265 autorun: false, 266 autorun: false,
266 connection: this.getRedisConnection(), 267 connection: Redis.getRedisClientOptions('QueueEvent'),
267 prefix: this.jobRedisPrefix 268 prefix: this.jobRedisPrefix
268 } 269 }
269 270
@@ -273,16 +274,6 @@ class JobQueue {
273 this.queueEvents[handlerName] = queueEvents 274 this.queueEvents[handlerName] = queueEvents
274 } 275 }
275 276
276 private getRedisConnection () {
277 return {
278 password: CONFIG.REDIS.AUTH,
279 db: CONFIG.REDIS.DB,
280 host: CONFIG.REDIS.HOSTNAME,
281 port: CONFIG.REDIS.PORT,
282 path: CONFIG.REDIS.SOCKET
283 }
284 }
285
286 // --------------------------------------------------------------------------- 277 // ---------------------------------------------------------------------------
287 278
288 async terminate () { 279 async terminate () {
diff --git a/server/lib/redis.ts b/server/lib/redis.ts
index b7523492a..4d7947d40 100644
--- a/server/lib/redis.ts
+++ b/server/lib/redis.ts
@@ -1,4 +1,4 @@
1import { createClient, RedisClientOptions, RedisModules } from 'redis' 1import IoRedis, { RedisOptions } from 'ioredis'
2import { exists } from '@server/helpers/custom-validators/misc' 2import { exists } from '@server/helpers/custom-validators/misc'
3import { sha256 } from '@shared/extra-utils' 3import { sha256 } from '@shared/extra-utils'
4import { logger } from '../helpers/logger' 4import { logger } from '../helpers/logger'
@@ -22,7 +22,7 @@ class Redis {
22 private static instance: Redis 22 private static instance: Redis
23 private initialized = false 23 private initialized = false
24 private connected = false 24 private connected = false
25 private client: ReturnType<typeof createClient> 25 private client: IoRedis
26 private prefix: string 26 private prefix: string
27 27
28 private constructor () { 28 private constructor () {
@@ -33,46 +33,42 @@ class Redis {
33 if (this.initialized === true) return 33 if (this.initialized === true) return
34 this.initialized = true 34 this.initialized = true
35 35
36 this.client = createClient(Redis.getRedisClientOptions())
37 this.client.on('error', err => logger.error('Redis Client Error', { err }))
38
39 logger.info('Connecting to redis...') 36 logger.info('Connecting to redis...')
40 37
41 this.client.connect() 38 this.client = new IoRedis(Redis.getRedisClientOptions('', { enableAutoPipelining: true }))
42 .then(() => { 39 this.client.on('error', err => logger.error('Redis failed to connect', { err }))
43 logger.info('Connected to redis.') 40 this.client.on('connect', () => {
44 41 logger.info('Connected to redis.')
45 this.connected = true 42
46 }).catch(err => { 43 this.connected = true
47 logger.error('Cannot connect to redis', { err }) 44 })
48 process.exit(-1) 45 this.client.on('reconnecting', (ms) => {
49 }) 46 logger.error(`Reconnecting to redis in ${ms}.`)
47 })
48 this.client.on('close', () => {
49 logger.error('Connection to redis has closed.')
50 this.connected = false
51 })
52
53 this.client.on('end', () => {
54 logger.error('Connection to redis has closed and no more reconnects will be done.')
55 })
50 56
51 this.prefix = 'redis-' + WEBSERVER.HOST + '-' 57 this.prefix = 'redis-' + WEBSERVER.HOST + '-'
52 } 58 }
53 59
54 static getRedisClientOptions () { 60 static getRedisClientOptions (connectionName?: string, options: RedisOptions = {}): RedisOptions {
55 let config: RedisClientOptions<RedisModules, {}> = { 61 return {
56 socket: { 62 connectionName: [ 'PeerTube', connectionName ].join(''),
57 connectTimeout: 20000 // Could be slow since node use sync call to compile PeerTube 63 connectTimeout: 20000, // Could be slow since node use sync call to compile PeerTube
58 } 64 password: CONFIG.REDIS.AUTH,
59 } 65 db: CONFIG.REDIS.DB,
60 66 host: CONFIG.REDIS.HOSTNAME,
61 if (CONFIG.REDIS.AUTH) { 67 port: CONFIG.REDIS.PORT,
62 config = { ...config, password: CONFIG.REDIS.AUTH } 68 path: CONFIG.REDIS.SOCKET,
63 } 69 showFriendlyErrorStack: true,
64 70 ...options
65 if (CONFIG.REDIS.DB) {
66 config = { ...config, database: CONFIG.REDIS.DB }
67 } 71 }
68
69 if (CONFIG.REDIS.HOSTNAME && CONFIG.REDIS.PORT) {
70 config.socket = { ...config.socket, host: CONFIG.REDIS.HOSTNAME, port: CONFIG.REDIS.PORT }
71 } else {
72 config.socket = { ...config.socket, path: CONFIG.REDIS.SOCKET }
73 }
74
75 return config
76 } 72 }
77 73
78 getClient () { 74 getClient () {
@@ -388,15 +384,15 @@ class Redis {
388 } 384 }
389 385
390 private getSet (key: string) { 386 private getSet (key: string) {
391 return this.client.sMembers(this.prefix + key) 387 return this.client.smembers(this.prefix + key)
392 } 388 }
393 389
394 private addToSet (key: string, value: string) { 390 private addToSet (key: string, value: string) {
395 return this.client.sAdd(this.prefix + key, value) 391 return this.client.sadd(this.prefix + key, value)
396 } 392 }
397 393
398 private deleteFromSet (key: string, value: string) { 394 private deleteFromSet (key: string, value: string) {
399 return this.client.sRem(this.prefix + key, value) 395 return this.client.srem(this.prefix + key, value)
400 } 396 }
401 397
402 private deleteKey (key: string) { 398 private deleteKey (key: string) {
@@ -415,11 +411,13 @@ class Redis {
415 } 411 }
416 412
417 private async setValue (key: string, value: string, expirationMilliseconds?: number) { 413 private async setValue (key: string, value: string, expirationMilliseconds?: number) {
418 const options = expirationMilliseconds 414 let result
419 ? { PX: expirationMilliseconds }
420 : {}
421 415
422 const result = await this.client.set(this.prefix + key, value, options) 416 if (expirationMilliseconds !== undefined) {
417 result = await this.client.set(this.prefix + key, value, 'PX', expirationMilliseconds)
418 } else {
419 result = await this.client.set(this.prefix + key, value)
420 }
423 421
424 if (result !== 'OK') throw new Error('Redis set result is not OK.') 422 if (result !== 'OK') throw new Error('Redis set result is not OK.')
425 } 423 }
diff --git a/server/middlewares/cache/shared/api-cache.ts b/server/middlewares/cache/shared/api-cache.ts
index abc919339..9e15bf2d6 100644
--- a/server/middlewares/cache/shared/api-cache.ts
+++ b/server/middlewares/cache/shared/api-cache.ts
@@ -49,7 +49,7 @@ export class ApiCache {
49 if (!Redis.Instance.isConnected()) return this.makeResponseCacheable(res, next, key, duration) 49 if (!Redis.Instance.isConnected()) return this.makeResponseCacheable(res, next, key, duration)
50 50
51 try { 51 try {
52 const obj = await redis.hGetAll(key) 52 const obj = await redis.hgetall(key)
53 if (obj?.response) { 53 if (obj?.response) {
54 return this.sendCachedResponse(req, res, JSON.parse(obj.response), duration) 54 return this.sendCachedResponse(req, res, JSON.parse(obj.response), duration)
55 } 55 }
@@ -100,8 +100,8 @@ export class ApiCache {
100 100
101 if (Redis.Instance.isConnected()) { 101 if (Redis.Instance.isConnected()) {
102 await Promise.all([ 102 await Promise.all([
103 redis.hSet(key, 'response', JSON.stringify(value)), 103 redis.hset(key, 'response', JSON.stringify(value)),
104 redis.hSet(key, 'duration', duration + ''), 104 redis.hset(key, 'duration', duration + ''),
105 redis.expire(key, duration / 1000) 105 redis.expire(key, duration / 1000)
106 ]) 106 ])
107 } 107 }
diff --git a/yarn.lock b/yarn.lock
index 0dde0a362..315a79918 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1869,40 +1869,6 @@
1869 smtp-server "^3.9.0" 1869 smtp-server "^3.9.0"
1870 wildstring "1.0.9" 1870 wildstring "1.0.9"
1871 1871
1872"@redis/bloom@1.0.2":
1873 version "1.0.2"
1874 resolved "https://registry.yarnpkg.com/@redis/bloom/-/bloom-1.0.2.tgz#42b82ec399a92db05e29fffcdfd9235a5fc15cdf"
1875 integrity sha512-EBw7Ag1hPgFzdznK2PBblc1kdlj5B5Cw3XwI9/oG7tSn85/HKy3X9xHy/8tm/eNXJYHLXHJL/pkwBpFMVVefkw==
1876
1877"@redis/client@1.3.0":
1878 version "1.3.0"
1879 resolved "https://registry.yarnpkg.com/@redis/client/-/client-1.3.0.tgz#c62ccd707f16370a2dc2f9e158a28b7da049fa77"
1880 integrity sha512-XCFV60nloXAefDsPnYMjHGtvbtHR8fV5Om8cQ0JYqTNbWcQo/4AryzJ2luRj4blveWazRK/j40gES8M7Cp6cfQ==
1881 dependencies:
1882 cluster-key-slot "1.1.0"
1883 generic-pool "3.8.2"
1884 yallist "4.0.0"
1885
1886"@redis/graph@1.0.1":
1887 version "1.0.1"
1888 resolved "https://registry.yarnpkg.com/@redis/graph/-/graph-1.0.1.tgz#eabc58ba99cd70d0c907169c02b55497e4ec8a99"
1889 integrity sha512-oDE4myMCJOCVKYMygEMWuriBgqlS5FqdWerikMoJxzmmTUErnTRRgmIDa2VcgytACZMFqpAOWDzops4DOlnkfQ==
1890
1891"@redis/json@1.0.4":
1892 version "1.0.4"
1893 resolved "https://registry.yarnpkg.com/@redis/json/-/json-1.0.4.tgz#f372b5f93324e6ffb7f16aadcbcb4e5c3d39bda1"
1894 integrity sha512-LUZE2Gdrhg0Rx7AN+cZkb1e6HjoSKaeeW8rYnt89Tly13GBI5eP4CwDVr+MY8BAYfCg4/N15OUrtLoona9uSgw==
1895
1896"@redis/search@1.1.0":
1897 version "1.1.0"
1898 resolved "https://registry.yarnpkg.com/@redis/search/-/search-1.1.0.tgz#7abb18d431f27ceafe6bcb4dd83a3fa67e9ab4df"
1899 integrity sha512-NyFZEVnxIJEybpy+YskjgOJRNsfTYqaPbK/Buv6W2kmFNaRk85JiqjJZA5QkRmWvGbyQYwoO5QfDi2wHskKrQQ==
1900
1901"@redis/time-series@1.0.3":
1902 version "1.0.3"
1903 resolved "https://registry.yarnpkg.com/@redis/time-series/-/time-series-1.0.3.tgz#4cfca8e564228c0bddcdf4418cba60c20b224ac4"
1904 integrity sha512-OFp0q4SGrTH0Mruf6oFsHGea58u8vS/iI5+NpYdicaM+7BgqBZH8FFvNZ8rYYLrUO/QRqMq72NpXmxLVNcdmjA==
1905
1906"@selderee/plugin-htmlparser2@^0.6.0": 1872"@selderee/plugin-htmlparser2@^0.6.0":
1907 version "0.6.0" 1873 version "0.6.0"
1908 resolved "https://registry.yarnpkg.com/@selderee/plugin-htmlparser2/-/plugin-htmlparser2-0.6.0.tgz#27e994afd1c2cb647ceb5406a185a5574188069d" 1874 resolved "https://registry.yarnpkg.com/@selderee/plugin-htmlparser2/-/plugin-htmlparser2-0.6.0.tgz#27e994afd1c2cb647ceb5406a185a5574188069d"
@@ -3460,7 +3426,7 @@ clone@^2.0.0:
3460 resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f" 3426 resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f"
3461 integrity sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w== 3427 integrity sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==
3462 3428
3463cluster-key-slot@1.1.0, cluster-key-slot@^1.1.0: 3429cluster-key-slot@^1.1.0:
3464 version "1.1.0" 3430 version "1.1.0"
3465 resolved "https://registry.yarnpkg.com/cluster-key-slot/-/cluster-key-slot-1.1.0.tgz#30474b2a981fb12172695833052bc0d01336d10d" 3431 resolved "https://registry.yarnpkg.com/cluster-key-slot/-/cluster-key-slot-1.1.0.tgz#30474b2a981fb12172695833052bc0d01336d10d"
3466 integrity sha512-2Nii8p3RwAPiFwsnZvukotvow2rIHM+yQ6ZcBXGHdniadkYGZYiGmkHJIbZPIV9nfv7m/U1IPMVVcAhoWFeklw== 3432 integrity sha512-2Nii8p3RwAPiFwsnZvukotvow2rIHM+yQ6ZcBXGHdniadkYGZYiGmkHJIbZPIV9nfv7m/U1IPMVVcAhoWFeklw==
@@ -5021,11 +4987,6 @@ gauge@^3.0.0:
5021 strip-ansi "^6.0.1" 4987 strip-ansi "^6.0.1"
5022 wide-align "^1.1.2" 4988 wide-align "^1.1.2"
5023 4989
5024generic-pool@3.8.2:
5025 version "3.8.2"
5026 resolved "https://registry.yarnpkg.com/generic-pool/-/generic-pool-3.8.2.tgz#aab4f280adb522fdfbdc5e5b64d718d3683f04e9"
5027 integrity sha512-nGToKy6p3PAbYQ7p1UlWl6vSPwfwU6TMSWK7TTu+WUY4ZjyZQGniGGt2oNVvyNSpyZYSB43zMXVLcBm08MTMkg==
5028
5029get-browser-rtc@^1.1.0: 4990get-browser-rtc@^1.1.0:
5030 version "1.1.0" 4991 version "1.1.0"
5031 resolved "https://registry.yarnpkg.com/get-browser-rtc/-/get-browser-rtc-1.1.0.tgz#d1494e299b00f33fc8e9d6d3343ba4ba99711a2c" 4992 resolved "https://registry.yarnpkg.com/get-browser-rtc/-/get-browser-rtc-1.1.0.tgz#d1494e299b00f33fc8e9d6d3343ba4ba99711a2c"
@@ -5553,7 +5514,7 @@ invariant@2.2.4:
5553 dependencies: 5514 dependencies:
5554 loose-envify "^1.0.0" 5515 loose-envify "^1.0.0"
5555 5516
5556ioredis@^5.2.2: 5517ioredis@^5.2.2, ioredis@^5.2.3:
5557 version "5.2.3" 5518 version "5.2.3"
5558 resolved "https://registry.yarnpkg.com/ioredis/-/ioredis-5.2.3.tgz#d5b37eb13e643241660d6cee4eeb41a026cda8c0" 5519 resolved "https://registry.yarnpkg.com/ioredis/-/ioredis-5.2.3.tgz#d5b37eb13e643241660d6cee4eeb41a026cda8c0"
5559 integrity sha512-gQNcMF23/NpvjCaa1b5YycUyQJ9rBNH2xP94LWinNpodMWVUPP5Ai/xXANn/SM7gfIvI62B5CCvZxhg5pOgyMw== 5520 integrity sha512-gQNcMF23/NpvjCaa1b5YycUyQJ9rBNH2xP94LWinNpodMWVUPP5Ai/xXANn/SM7gfIvI62B5CCvZxhg5pOgyMw==
@@ -7865,18 +7826,6 @@ redis-parser@^3.0.0:
7865 dependencies: 7826 dependencies:
7866 redis-errors "^1.0.0" 7827 redis-errors "^1.0.0"
7867 7828
7868redis@^4.0.1:
7869 version "4.3.1"
7870 resolved "https://registry.yarnpkg.com/redis/-/redis-4.3.1.tgz#290532a0c22221e05e991162ac4dca1e1b2ff6da"
7871 integrity sha512-cM7yFU5CA6zyCF7N/+SSTcSJQSRMEKN0k0Whhu6J7n9mmXRoXugfWDBo5iOzGwABmsWKSwGPTU5J4Bxbl+0mrA==
7872 dependencies:
7873 "@redis/bloom" "1.0.2"
7874 "@redis/client" "1.3.0"
7875 "@redis/graph" "1.0.1"
7876 "@redis/json" "1.0.4"
7877 "@redis/search" "1.1.0"
7878 "@redis/time-series" "1.0.3"
7879
7880reflect-metadata@^0.1.12: 7829reflect-metadata@^0.1.12:
7881 version "0.1.13" 7830 version "0.1.13"
7882 resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.1.13.tgz#67ae3ca57c972a2aa1642b10fe363fe32d49dc08" 7831 resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.1.13.tgz#67ae3ca57c972a2aa1642b10fe363fe32d49dc08"
@@ -9574,16 +9523,16 @@ y18n@^5.0.5:
9574 resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" 9523 resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55"
9575 integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== 9524 integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==
9576 9525
9577yallist@4.0.0, yallist@^4.0.0:
9578 version "4.0.0"
9579 resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
9580 integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
9581
9582yallist@^2.1.2: 9526yallist@^2.1.2:
9583 version "2.1.2" 9527 version "2.1.2"
9584 resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" 9528 resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52"
9585 integrity sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A== 9529 integrity sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==
9586 9530
9531yallist@^4.0.0:
9532 version "4.0.0"
9533 resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
9534 integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
9535
9587yaml@^1.10.0: 9536yaml@^1.10.0:
9588 version "1.10.2" 9537 version "1.10.2"
9589 resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" 9538 resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b"