Jelajahi Sumber

storage-node-v2: Add static file endpoint.

Shamil Gadelshin 3 tahun lalu
induk
melakukan
4cc1e2691f

+ 9 - 0
runtime-modules/storage/src/lib.rs

@@ -99,6 +99,7 @@ use sp_std::collections::btree_set::BTreeSet;
 use sp_std::iter;
 use sp_std::marker::PhantomData;
 use sp_std::vec::Vec;
+use frame_system::ensure_root;
 
 use common::constraints::BoundedValueConstraint;
 use common::origin::ActorOriginValidator;
@@ -1564,6 +1565,14 @@ decl_module! {
                 )
             );
         }
+
+        /// Upload new data objects. Development mode.
+        #[weight = 10_000_000] // TODO: adjust weight
+        pub fn sudo_upload_data_objects(origin, params: UploadParameters<T>) {
+          ensure_root(origin)?;
+
+          Self::upload_data_objects(params)?;
+        }
     }
 }
 

+ 6 - 0
storage-node-v2/package.json

@@ -15,8 +15,11 @@
     "@polkadot/api": "4.2.1",
     "@types/base64url": "^2.0.0",
     "@types/express": "4.17.1",
+    "@types/file-type": "^10.9.1",
     "@types/multer": "^1.4.5",
     "@types/node-cache": "^4.2.5",
+    "@types/read-chunk": "^3.1.0",
+    "@types/send": "^0.17.0",
     "@types/winston": "^2.4.4",
     "await-lock": "^2.1.0",
     "base64url": "^3.0.1",
@@ -24,9 +27,12 @@
     "express": "4.17.1",
     "express-openapi-validator": "^4.12.4",
     "express-winston": "^4.1.0",
+    "file-type": "^16.5.0",
     "multihashes": "^4.0.2",
     "node-cache": "^5.1.2",
     "openapi-editor": "^0.3.0",
+    "read-chunk": "^3.2.0",
+    "send": "^0.17.1",
     "tslib": "^1",
     "winston": "^3.3.3"
   },

+ 2 - 2
storage-node-v2/scripts/create-auth-request-signature.ts

@@ -13,8 +13,8 @@ createApi('ws://localhost:9944').then(() => {
   const tokenRequestBody: UploadTokenRequestBody = {
     memberId: 0,
     accountId: alice.address,
-    dataObjectId: 55,
-    storageBucketId: 1,
+    dataObjectId: 2,
+    storageBucketId: 0,
     bagId: 'static:council'
   }
   

+ 1 - 1
storage-node-v2/scripts/init-dev-bucket.sh

@@ -7,6 +7,6 @@
 yarn storage-node dev:init
 yarn storage-node leader:update-bag-limit -l 7 --dev
 yarn storage-node leader:update-voucher-limits -o 100 -s 10000000 --dev
-yarn storage-node leader:create-bucket -i=0 -a --dev 
+yarn storage-node leader:create-bucket -i=0 -a -n=100 -s=10000000  --dev 
 yarn storage-node operator:accept-invitation -w=0 -b=0 --dev
 yarn storage-node leader:update-bag -b=0 -i static:council --dev 

+ 43 - 2
storage-node-v2/src/api-spec/openapi.yaml

@@ -19,6 +19,47 @@ tags:
     description: Public storage node API
 
 paths:
+  /files/{cid}:
+    get:
+      operationId: publicApi.files
+      description: Returns a media file.
+      tags:
+        - public
+      parameters:
+        - name: cid
+          required: true
+          in: path
+          description: Content ID
+          schema:
+            type: string
+      responses:
+        200:
+          description: Ok
+          content:
+            image/*:
+              schema:
+                type: string
+                format: binary
+            audio/*:
+              schema:
+                type: string
+                format: binary
+            video/*:
+              schema:
+                type: string
+                format: binary
+        404:
+          description: File not found
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        410:
+          description: File request problem
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
   /upload:
     post:
       security:
@@ -61,8 +102,8 @@ paths:
               schema:
                 type: object
                 properties:
-                  success:
-                    type: boolean
+                  status:
+                    type: string
         401:
           description: Unauthorized
         410:

+ 1 - 1
storage-node-v2/src/commands/server.ts

@@ -15,7 +15,7 @@ export default class Server extends ApiCommandBase {
     uploads: flags.string({
       char: 'd',
       required: true,
-      description: 'Data uploading directory.',
+      description: 'Data uploading directory (absolute path).',
     }),
     port: flags.integer({
       char: 'o',

+ 1 - 3
storage-node-v2/src/services/webApi/app.ts

@@ -33,12 +33,10 @@ export async function createApp(
   app.use(express.json())
   app.use(httpLogger())
 
-  // TODO: check path
-  app.use('/files', express.static(uploadsDir))
-
   app.use(
     // Set parameters for each request.
     (req: express.Request, res: express.Response, next: NextFunction) => {
+      res.locals.uploadsDir = uploadsDir
       res.locals.storageProviderAccount = account
       res.locals.workerId = workerId
       res.locals.api = api

+ 99 - 5
storage-node-v2/src/services/webApi/controllers/publicApi.ts

@@ -1,4 +1,3 @@
-import * as express from 'express'
 import { acceptPendingDataObjects } from '../../runtime/extrinsics'
 import {
   UploadTokenRequest,
@@ -11,12 +10,17 @@ import {
   createNonce,
   TokenExpirationPeriod,
 } from '../../../services/helpers/tokenNonceKeeper'
+import { parseBagId } from '../../../services/helpers/bagIdParser'
+
+import FileType from 'file-type'
+import readChunk from 'read-chunk'
 import { KeyringPair } from '@polkadot/keyring/types'
 import { ApiPromise } from '@polkadot/api'
-import { parseBagId } from '../../../services/helpers/bagIdParser'
-import fs from 'fs'
 import { Membership } from '@joystream/types/members'
-
+import * as express from 'express'
+import fs from 'fs'
+import path from 'path'
+import send from 'send'
 const fsPromises = fs.promises
 
 interface UploadRequest {
@@ -25,6 +29,53 @@ interface UploadRequest {
   bagId: string
 }
 
+export async function files(
+  req: express.Request,
+  res: express.Response
+): Promise<void> {
+  try {
+    const cid = getCid(req)
+    const uploadsDir = getUploadsDir(res)
+    const fullPath = path.resolve(uploadsDir, cid)
+
+    const fileInfo = await getFileInfo(fullPath)
+
+    const stream = send(req, fullPath)
+
+    stream.on('headers', (res) => {
+      // serve all files for download
+      res.setHeader('Content-Disposition', 'inline')
+      res.setHeader('Content-Type', fileInfo.mimeType)
+    })
+
+    stream.on('error', (err) => {
+      // General error
+      let statusCode = 410
+      const errorString = err.toString()
+
+      const errorObj = {
+        type: 'files',
+        message: errorString,
+      }
+
+      // Special case - file not found.
+      if (errorString.includes('ENOENT')) {
+        statusCode = 404
+        errorObj.message = 'File not found'
+      }
+
+      res.status(statusCode).json(errorObj)
+    })
+
+    stream.pipe(res)
+  } catch (err) {
+    res.status(410).json({
+      type: 'files',
+      message: err.toString(),
+    })
+  }
+}
+
 export async function upload(
   req: express.Request,
   res: express.Response
@@ -51,7 +102,7 @@ export async function upload(
       [uploadRequest.dataObjectId]
     )
     res.status(201).json({
-      file: 'received',
+      status: 'received',
     })
   } catch (err) {
     res.status(410).json({
@@ -111,6 +162,14 @@ function getWorkerId(res: express.Response): number {
   throw new Error('No Joystream worker ID loaded.')
 }
 
+function getUploadsDir(res: express.Response): string {
+  if (res.locals.uploadsDir) {
+    return res.locals.uploadsDir
+  }
+
+  throw new Error('No upload directory path loaded.')
+}
+
 function getAccount(res: express.Response): KeyringPair {
   if (res.locals.storageProviderAccount) {
     return res.locals.storageProviderAccount
@@ -127,6 +186,15 @@ function getApi(res: express.Response): ApiPromise {
   throw new Error('No Joystream API loaded.')
 }
 
+function getCid(req: express.Request): string {
+  const cid = req.params.cid || ''
+  if (cid.length > 0) {
+    return cid
+  }
+
+  throw new Error('No CID provided.')
+}
+
 function getTokenRequest(req: express.Request): UploadTokenRequest {
   const tokenRequest = req.body as UploadTokenRequest
   if (tokenRequest) {
@@ -160,3 +228,29 @@ async function validateTokenRequest(
 function getTokenExpirationTime(): number {
   return Date.now() + TokenExpirationPeriod
 }
+
+type FileInfo = {
+  mimeType: string
+  ext: string
+}
+
+const MINIMUM_FILE_CHUNK = 4100
+async function getFileInfo(fullPath: string): Promise<FileInfo> {
+  // Default file info if nothing could be detected.
+  const DEFAULT_FILE_INFO = {
+    mimeType: 'application/octet-stream',
+    ext: 'bin',
+  }
+
+  const buffer = readChunk.sync(fullPath, 0, MINIMUM_FILE_CHUNK)
+  const fileType = await FileType.fromBuffer(buffer)
+
+  if (fileType === undefined) {
+    return DEFAULT_FILE_INFO
+  }
+
+  return {
+    mimeType: fileType.mime.toString(),
+    ext: fileType.ext.toString(),
+  }
+}

+ 103 - 7
yarn.lock

@@ -4941,6 +4941,11 @@
   dependencies:
     defer-to-connect "^1.0.1"
 
+"@tokenizer/token@^0.1.1":
+  version "0.1.1"
+  resolved "https://registry.yarnpkg.com/@tokenizer/token/-/token-0.1.1.tgz#f0d92c12f87079ddfd1b29f614758b9696bc29e3"
+  integrity sha512-XO6INPbZCxdprl+9qa/AAbFFOMzzwqYxpjPgLICrMD6C2FCw6qfJOPcBk6JqqPLSaZ/Qx87qn4rpPmPMwaAK6w==
+
 "@types/accepts@*", "@types/accepts@^1.3.5":
   version "1.3.5"
   resolved "https://registry.yarnpkg.com/@types/accepts/-/accepts-1.3.5.tgz#c34bec115cfc746e04fe5a059df4ce7e7b391575"
@@ -5211,6 +5216,13 @@
   resolved "https://registry.yarnpkg.com/@types/file-saver/-/file-saver-2.0.1.tgz#e18eb8b069e442f7b956d313f4fadd3ef887354e"
   integrity sha512-g1QUuhYVVAamfCifK7oB7G3aIl4BbOyzDOqVyUfEr4tfBKrXfeH+M+Tg7HKCXSrbzxYdhyCP7z9WbKo0R2hBCw==
 
+"@types/file-type@^10.9.1":
+  version "10.9.1"
+  resolved "https://registry.yarnpkg.com/@types/file-type/-/file-type-10.9.1.tgz#fc9a6b38697777eca346dba914fdea4b38e04b97"
+  integrity sha512-oq0fy8Jqj19HofanFsZ56o5anMDUQtFO9B3wfLqM9o42RyCe1WT+wRbSvRbL2l8ARZXNaJturHk0b442+0yi+g==
+  dependencies:
+    file-type "*"
+
 "@types/filesystem@*":
   version "0.0.29"
   resolved "https://registry.yarnpkg.com/@types/filesystem/-/filesystem-0.0.29.tgz#ee3748eb5be140dcf980c3bd35f11aec5f7a3748"
@@ -5520,6 +5532,11 @@
   resolved "https://registry.yarnpkg.com/@types/mime/-/mime-2.0.3.tgz#c893b73721db73699943bfc3653b1deb7faa4a3a"
   integrity sha512-Jus9s4CDbqwocc5pOAnh8ShfrnMcPHuJYzVcSUU7lrh8Ni5HuIqX3oilL86p3dlTrk0LzHRCgA/GQ7uNCw6l2Q==
 
+"@types/mime@^1":
+  version "1.3.2"
+  resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.2.tgz#93e25bf9ee75fe0fd80b594bc4feb0e862111b5a"
+  integrity sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==
+
 "@types/minimatch@*":
   version "3.0.3"
   resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d"
@@ -5808,6 +5825,21 @@
   dependencies:
     "@types/react" "*"
 
+"@types/read-chunk@^3.1.0":
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/@types/read-chunk/-/read-chunk-3.1.0.tgz#8283607de66bfa6231f46cf9b61f7fafd67fc629"
+  integrity sha512-i4nyM4ZS2/OMowGGy7pgQnNJRiBEryIalR0lLx9wqcdQ79Gv1LNE03v2+n+bfbpdYHrQAxCtV7m0agc8+2MzEg==
+  dependencies:
+    read-chunk "*"
+
+"@types/readable-stream@^2.3.9":
+  version "2.3.10"
+  resolved "https://registry.yarnpkg.com/@types/readable-stream/-/readable-stream-2.3.10.tgz#0f1a512ca30bec5e53d3282133b9237a703e7562"
+  integrity sha512-xwSXvAv9x4B9Vj88AMZnFyEVLilz1EBxKvRUhGqIF4nJpRQBSTm7jS236X4Y9Y2qPsVvaMxwrGJlNhLHEahlFQ==
+  dependencies:
+    "@types/node" "*"
+    safe-buffer "*"
+
 "@types/retry@*":
   version "0.12.0"
   resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.0.tgz#2b35eccfcee7d38cd72ad99232fbd58bffb3c84d"
@@ -5825,6 +5857,14 @@
   dependencies:
     "@types/node" "*"
 
+"@types/send@^0.17.0":
+  version "0.17.0"
+  resolved "https://registry.yarnpkg.com/@types/send/-/send-0.17.0.tgz#2c8cd6a4a97efd0ae54be3bc16647d864be2f94a"
+  integrity sha512-Kh5ghRpkAcjAak9sCns9XTu9xL/hzJHMQIEi2xLZ8kJ1JVKPly0E0NA7snmSk9Ah7BndN8jEnltzz2VPjBMQIg==
+  dependencies:
+    "@types/mime" "^1"
+    "@types/node" "*"
+
 "@types/serve-static@*":
   version "1.13.6"
   resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.13.6.tgz#866b1b8dec41c36e28c7be40ac725b88be43c5c1"
@@ -13771,6 +13811,15 @@ file-system-cache@^1.0.5:
     fs-extra "^0.30.0"
     ramda "^0.21.0"
 
+file-type@*, file-type@^16.5.0:
+  version "16.5.0"
+  resolved "https://registry.yarnpkg.com/file-type/-/file-type-16.5.0.tgz#16a2626f3b33bac612f6e81e52216f3a7c8e12a2"
+  integrity sha512-OxgWA9tbL8N/WP00GD1z8O0MiwQKFyWRs1q+3FhjdvcGgKqwxcejyGWso3n4/IMU6DdwV+ARZ4A7TTnPkDcSiw==
+  dependencies:
+    readable-web-to-node-stream "^3.0.0"
+    strtok3 "^6.0.3"
+    token-types "^2.0.0"
+
 file-type@^11.0.0:
   version "11.1.0"
   resolved "https://registry.yarnpkg.com/file-type/-/file-type-11.1.0.tgz#93780f3fed98b599755d846b99a1617a2ad063b8"
@@ -22820,7 +22869,7 @@ p-try@^1.0.0:
   resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3"
   integrity sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=
 
-p-try@^2.0.0:
+p-try@^2.0.0, p-try@^2.1.0:
   version "2.2.0"
   resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6"
   integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==
@@ -23211,6 +23260,11 @@ pbkdf2@^3.1.1:
     safe-buffer "^5.0.1"
     sha.js "^2.4.8"
 
+peek-readable@^3.1.3:
+  version "3.1.3"
+  resolved "https://registry.yarnpkg.com/peek-readable/-/peek-readable-3.1.3.tgz#932480d46cf6aa553c46c68566c4fb69a82cd2b1"
+  integrity sha512-mpAcysyRJxmICBcBa5IXH7SZPvWkcghm6Fk8RekoS3v+BpbSzlZzuWbMx+GXrlUwESi9qHar4nVEZNMKylIHvg==
+
 peer-id@~0.12.2:
   version "0.12.5"
   resolved "https://registry.yarnpkg.com/peer-id/-/peer-id-0.12.5.tgz#b22a1edc5b4aaaa2bb830b265ba69429823e5179"
@@ -25156,6 +25210,14 @@ reactcss@^1.2.0:
   dependencies:
     lodash "^4.0.1"
 
+read-chunk@*, read-chunk@^3.2.0:
+  version "3.2.0"
+  resolved "https://registry.yarnpkg.com/read-chunk/-/read-chunk-3.2.0.tgz#2984afe78ca9bfbbdb74b19387bf9e86289c16ca"
+  integrity sha512-CEjy9LCzhmD7nUpJ1oVOE6s/hBkejlcJEgLQHVnQznOSilOPb+kpKktlLfFDK3/WP43+F80xkUTM2VOkYoSYvQ==
+  dependencies:
+    pify "^4.0.1"
+    with-open-file "^0.1.6"
+
 read-cmd-shim@^1.0.1:
   version "1.0.5"
   resolved "https://registry.yarnpkg.com/read-cmd-shim/-/read-cmd-shim-1.0.5.tgz#87e43eba50098ba5a32d0ceb583ab8e43b961c16"
@@ -25365,6 +25427,14 @@ readable-stream@~2.0.5:
     string_decoder "~0.10.x"
     util-deprecate "~1.0.1"
 
+readable-web-to-node-stream@^3.0.0:
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.1.tgz#3f619b1bc5dd73a4cfe5c5f9b4f6faba55dff845"
+  integrity sha512-4zDC6CvjUyusN7V0QLsXVB7pJCD9+vtrM9bYDRv6uBQ+SKfx36rp5AFNPRgh9auKRul/a1iFZJYXcCbwRL+SaA==
+  dependencies:
+    "@types/readable-stream" "^2.3.9"
+    readable-stream "^3.6.0"
+
 readdir-scoped-modules@^1.0.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/readdir-scoped-modules/-/readdir-scoped-modules-1.1.0.tgz#8d45407b4f870a0dcaebc0e28670d18e74514309"
@@ -26237,6 +26307,11 @@ rxjs@^6.3.3, rxjs@^6.4.0, rxjs@^6.5.1, rxjs@^6.5.2, rxjs@^6.5.3, rxjs@^6.6.6:
   dependencies:
     tslib "^1.9.0"
 
+safe-buffer@*, safe-buffer@>=5.1.0, safe-buffer@^5.2.0:
+  version "5.2.1"
+  resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
+  integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
+
 safe-buffer@5.1.1:
   version "5.1.1"
   resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853"
@@ -26247,11 +26322,6 @@ safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
   resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
   integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
 
-safe-buffer@>=5.1.0, safe-buffer@^5.2.0:
-  version "5.2.1"
-  resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
-  integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
-
 safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.2.0:
   version "5.2.0"
   resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519"
@@ -26517,7 +26587,7 @@ semver@~5.3.0:
   resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f"
   integrity sha1-myzl094C0XxgEq0yaqa00M9U+U8=
 
-send@0.17.1:
+send@0.17.1, send@^0.17.1:
   version "0.17.1"
   resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8"
   integrity sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==
@@ -27704,6 +27774,15 @@ strong-log-transformer@^2.0.0:
     minimist "^1.2.0"
     through "^2.3.4"
 
+strtok3@^6.0.3:
+  version "6.0.8"
+  resolved "https://registry.yarnpkg.com/strtok3/-/strtok3-6.0.8.tgz#c839157f615c10ba0f4ae35067dad9959eeca346"
+  integrity sha512-QLgv+oiXwXgCgp2PdPPa+Jpp4D9imK9e/0BsyfeFMr6QL6wMVqoVn9+OXQ9I7MZbmUzN6lmitTJ09uwS2OmGcw==
+  dependencies:
+    "@tokenizer/token" "^0.1.1"
+    "@types/debug" "^4.1.5"
+    peek-readable "^3.1.3"
+
 style-loader@^1.0.0, style-loader@^1.2.1:
   version "1.2.1"
   resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-1.2.1.tgz#c5cbbfbf1170d076cfdd86e0109c5bba114baa1a"
@@ -28535,6 +28614,14 @@ toidentifier@1.0.0:
   resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553"
   integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==
 
+token-types@^2.0.0:
+  version "2.1.1"
+  resolved "https://registry.yarnpkg.com/token-types/-/token-types-2.1.1.tgz#bd585d64902aaf720b8979d257b4b850b4d45c45"
+  integrity sha512-wnQcqlreS6VjthyHO3Y/kpK/emflxDBNhlNUPfh7wE39KnuDdOituXomIbyI79vBtF0Ninpkh72mcuRHo+RG3Q==
+  dependencies:
+    "@tokenizer/token" "^0.1.1"
+    ieee754 "^1.2.1"
+
 toml@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/toml/-/toml-3.0.0.tgz#342160f1af1904ec9d204d03a5d61222d762c5ee"
@@ -30613,6 +30700,15 @@ winston@*, winston@^3.3.3:
     triple-beam "^1.3.0"
     winston-transport "^4.4.0"
 
+with-open-file@^0.1.6:
+  version "0.1.7"
+  resolved "https://registry.yarnpkg.com/with-open-file/-/with-open-file-0.1.7.tgz#e2de8d974e8a8ae6e58886be4fe8e7465b58a729"
+  integrity sha512-ecJS2/oHtESJ1t3ZfMI3B7KIDKyfN0O16miWxdn30zdh66Yd3LsRFebXZXq6GU4xfxLf6nVxp9kIqElb5fqczA==
+  dependencies:
+    p-finally "^1.0.0"
+    p-try "^2.1.0"
+    pify "^4.0.1"
+
 word-wrap@^1.2.3, word-wrap@~1.2.3:
   version "1.2.3"
   resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c"