Parcourir la source

MongoDB database support. Fixed logging objects

rigwild il y a 5 ans
Parent
commit
a9de24c6aa

+ 6 - 1
README.md

@@ -16,6 +16,7 @@ Use the following environment variables to configure the application.
 | `PORT` | `5000` | The port used by the started application |  ✅  | ✅ |
 | `SERVE_CLIENT` | `true` | Should the server serve client (Fully local application) |  ✅  | ⬜️ |
 | `IMAGES_PATH` | `./images` | The directory where the images are stored (absolute path if changed ⚠️) |  ✅  | ⬜️ |
+| `MONGO_URI` | `mongodb://localhost/webexpe` | MongoDB database connection URI |  ✅  | ⬜️ |
 
 Configure more deeply the way the app works by modifying *[config.js](config.js)*.
 
@@ -24,11 +25,15 @@ Configure more deeply the way the app works by modifying *[config.js](config.js)
 | `apiPrefix` | `/api` | The url prefix for the API |
 | `imageServedUrl` | `/api/images` | The url prefix from where the images are served |
 | `serverPort` | `5000` | The port used by the server |
+| `mongoDatabaseURI` | `mongodb://localhost/webexpe` | MongoDB database connection URI |
 | `imagesPath` | `./images` | The directory where the images are stored |
 | `serveClient` | `true` | Should the server serve client files from the `/dist` directory |
 | `fileNameConvention` | `/^(.*)?_([0-9]{2,})\.(.*)$/` | File name convention for images |
-| `sceneFileNameBlackList` | `['config', 'seuilExpe', 'extracts']` | Files to ignore in scenes |
 | `extractsDirName` | `extracts` | Name of the directory containing extracts |
+| `sceneFileNameBlackList` | `['config', 'seuilExpe', 'extracts']` | Files to ignore in scenes |
+| `logger` | Logs : `logs/server.combined.log` Errors : `logs/server.error.log` | Default application logger |
+| `wsLogger` | Logs : `logs/ws.log` Errors : `logs/ws.error.log` | WebSocket logger configuration |
+| `dbLogger` | Logs : `logs/db.log` Errors : `logs/db.error.log` | Database logger configuration |
 
 ### Run server + client
 Linux

+ 6 - 32
config.js

@@ -1,7 +1,7 @@
 'use strict'
 
 import path from 'path'
-import winston from 'winston'
+import { logger, wsLogger, dbLogger } from './server/winston.config'
 
 export const PRODUCTION_MODE = process.env.NODE_ENV === 'production'
 export const TEST_MODE = process.env.NODE_ENV === 'test'
@@ -15,6 +15,9 @@ export const imageServedUrl = apiPrefix + '/images'
 // The port used by the server
 export const serverPort = parseInt(process.env.PORT, 10) || 5000
 
+// MongoDB database connection URI
+export const mongoDatabaseURI = process.env.MONGO_URI || 'mongodb://localhost/webexpe'
+
 // The directory where the images are stored
 export const imagesPath = TEST_MODE
   ? path.resolve(__dirname, 'test', 'images') // Used for automated testing, don't touch
@@ -32,34 +35,5 @@ export const extractsDirName = 'extracts'
 // Files to ignore in scenes
 export const sceneFileNameBlackList = ['config', 'seuilExpe', extractsDirName]
 
-// Logger configuration
-export const logger = winston.createLogger({
-  level: 'info',
-  format: winston.format.json(),
-  transports: [
-    new winston.transports.File({ filename: 'logs/server.combined.log' }),
-    new winston.transports.File({ filename: 'logs/server.error.log', level: 'error' }),
-    new winston.transports.Console({
-      level: 'debug',
-      handleExceptions: true,
-      format: winston.format.simple()
-    })
-  ],
-  exitOnError: false
-})
-
-// WebSocket logger configuration
-export const wsLogger = winston.createLogger({
-  level: 'info',
-  format: winston.format.json(),
-  transports: [
-    new winston.transports.File({ filename: 'logs/ws.log' }),
-    new winston.transports.File({ filename: 'logs/ws.error.log', level: 'error' }),
-    new winston.transports.Console({
-      level: 'debug',
-      handleExceptions: true,
-      format: winston.format.simple()
-    })
-  ],
-  exitOnError: false
-})
+// Logger configurations (Default application, WebSocket, Database)
+export { logger, wsLogger, dbLogger }

+ 1 - 0
package.json

@@ -19,6 +19,7 @@
     "esm": "^3.2.22",
     "express": "^4.16.4",
     "helmet": "^3.16.0",
+    "mongoose": "^5.5.2",
     "morgan": "^1.9.1",
     "serve-static": "^1.13.2",
     "sharp": "^0.22.0",

+ 28 - 0
server/database/controllers/Data.js

@@ -0,0 +1,28 @@
+'use strict'
+
+import DataModel from '../models/Data'
+import { dbLogger } from '../../../config'
+import { formatLog } from '../../functions'
+
+export default class Data {
+  static async add(dataObj) {
+    const doc = await DataModel.create({ data: dataObj })
+    dbLogger.info(formatLog(`New document was added. id=${doc.id}`))
+    return doc
+  }
+
+  static async del(dataId) {
+    const doc = await DataModel.findByIdAndDelete(dataId)
+    dbLogger.info(formatLog(`A document was deleted. id=${doc.id}`))
+  }
+
+  static async update(dataId, newDataObj) {
+    const doc = await DataModel.findByIdAndUpdate(dataId, newDataObj, { new: true })
+    dbLogger.info(formatLog(`A document was updated. id=${doc.id}`))
+    return doc
+  }
+
+  static find(dataId) {
+    return DataModel.findById(dataId)
+  }
+}

+ 14 - 0
server/database/index.js

@@ -0,0 +1,14 @@
+'use strict'
+
+import mongoose from 'mongoose'
+import { mongoDatabaseURI, dbLogger } from '../../config'
+import { formatLog, formatError } from '../functions'
+
+const connectDb = async () => {
+  await mongoose.connect(mongoDatabaseURI, { useNewUrlParser: true })
+  mongoose.connection.on('error', err => dbLogger.error(formatError(err)))
+
+  dbLogger.info(formatLog('The database connection was established.'))
+}
+
+export default connectDb

+ 11 - 0
server/database/models/Data.js

@@ -0,0 +1,11 @@
+'use strict'
+
+import mongoose, { Schema } from 'mongoose'
+
+export default mongoose.model('Data', new Schema({
+  date: {
+    type: Date,
+    default: () => new Date()
+  },
+  data: Object
+}))

+ 21 - 0
server/functions.js

@@ -167,3 +167,24 @@ export const getSceneFilesData = async sceneName => {
 
   return data
 }
+
+/**
+ * Format a string or object to a log object
+ *
+ * @param {object|string} data any message or object
+ * @param {('info'|'message'|'error'|any|undefined)} event the type of event
+ * @returns {string} the log object stringified
+ */
+export const formatLog = (data, event = undefined) => (JSON.stringify({
+  event,
+  log: data,
+  date: new Date()
+}))
+
+/**
+ * Format an error object
+ *
+ * @param {Error} errObj an Error object
+ * @returns {string} formatted log object stringified
+ */
+export const formatError = errObj => formatLog({ error: errObj.message, stack: errObj.stack })

+ 13 - 5
server/index.js

@@ -7,9 +7,10 @@ import serveStatic from 'serve-static'
 import helmet from 'helmet'
 import cors from 'cors'
 import routes from './routes'
-import { errorHandler } from './functions'
+import { errorHandler, formatLog } from './functions'
 import { apiPrefix, imageServedUrl, serverPort, serveClient, imagesPath, logger } from '../config'
 import startWebSocketServer from './webSocket'
+import connectDb from './database'
 const morgan = require('morgan')
 
 const app = express()
@@ -45,8 +46,15 @@ else {
 // Error handler (Middleware called when throwing in another middleware)
 app.use(errorHandler)
 
-// Start the server on the configured port
-const server = app.listen(serverPort, () => logger.info('The server was started on http://localhost:' + serverPort))
+const setup = async () => {
+  // Connect to the MongoDB database
+  await connectDb()
 
-// Start the WebSocket server on top of the started HTTP server
-startWebSocketServer(server)
+  // Start the server on the configured port
+  const server = app.listen(serverPort, () => logger.info(formatLog(`The server was started on http://localhost:${serverPort}`)))
+
+  // Start the WebSocket server on top of the started HTTP server
+  startWebSocketServer(server)
+}
+
+setup()

+ 3 - 19
server/webSocket/index.js

@@ -1,26 +1,10 @@
 'use strict'
 
 import WebSocket from 'ws'
+import { formatLog, formatError } from '../functions'
 import { wsLogger } from '../../config'
 import messageHandler from './messageHandler'
 
-/**
- * @typedef {object} LogObj
- * @property {Date} date current date
- * @property {object|string} log anything to log
- */
-/**
- * Format a string or object to a log object
- *
- * @param {object|string} data any message or object
- * @param {('info'|'message'|'error')} event the type of event
- * @returns {LogObj} the log object
- */
-export const formatLog = (data, event = 'info') => ({
-  event,
-  log: typeof data === 'object' ? JSON.stringify(data) : data,
-  date: new Date()
-})
 
 /**
  * @typedef {function} ErrorLogger
@@ -34,7 +18,7 @@ export const formatLog = (data, event = 'info') => ({
  */
 export const errorHandler = ws => err => {
   ws.send(err.message)
-  wsLogger.error(formatLog({ error: err.message, stack: err.stack }, 'error'))
+  wsLogger.error(formatError(err))
 }
 
 /**
@@ -47,7 +31,7 @@ const createWsServer = httpServer => {
   const wss = new WebSocket.Server({ server: httpServer })
 
   wss.on('listening', () => wsLogger.info(formatLog('The WebSocket server was started')))
-  wss.on('error', err => wsLogger.error(formatLog('WebSocket server error - ' + err.message, 'error')))
+  wss.on('error', err => wsLogger.error(formatError(err)))
 
   wss.on('connection', ws => {
     wsLogger.info(formatLog('New client connected.'))

+ 1 - 1
server/webSocket/messageHandler.js

@@ -1,6 +1,6 @@
 'use strict'
 
-import { formatLog } from './index'
+import { formatLog } from '../functions'
 import { wsLogger } from '../../config'
 
 /**

+ 51 - 0
server/winston.config.js

@@ -0,0 +1,51 @@
+'use strict'
+
+import winston from 'winston'
+
+// Logger configuration
+export const logger = winston.createLogger({
+  level: 'info',
+  format: winston.format.json(),
+  transports: [
+    new winston.transports.File({ filename: 'logs/server.combined.log' }),
+    new winston.transports.File({ filename: 'logs/server.error.log', level: 'error' }),
+    new winston.transports.Console({
+      level: 'debug',
+      handleExceptions: true,
+      format: winston.format.simple()
+    })
+  ],
+  exitOnError: false
+})
+
+// WebSocket logger configuration
+export const wsLogger = winston.createLogger({
+  level: 'info',
+  format: winston.format.json(),
+  transports: [
+    new winston.transports.File({ filename: 'logs/ws.log' }),
+    new winston.transports.File({ filename: 'logs/ws.error.log', level: 'error' }),
+    new winston.transports.Console({
+      level: 'debug',
+      handleExceptions: true,
+      format: winston.format.simple()
+    })
+  ],
+  exitOnError: false
+})
+
+// Database logger configuration
+export const dbLogger = winston.createLogger({
+  level: 'info',
+  format: winston.format.json(),
+  transports: [
+    new winston.transports.File({ filename: 'logs/db.log' }),
+    new winston.transports.File({ filename: 'logs/db.error.log', level: 'error' }),
+    new winston.transports.Console({
+      level: 'debug',
+      handleExceptions: true,
+      format: winston.format.simple()
+    })
+  ],
+  exitOnError: false
+})

+ 136 - 1
yarn.lock

@@ -1451,6 +1451,13 @@ async-limiter@~1.0.0:
   resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.0.tgz#78faed8c3d074ab81f22b4e985d79e8738f720f8"
   integrity sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==
 
+async@2.6.1:
+  version "2.6.1"
+  resolved "https://registry.yarnpkg.com/async/-/async-2.6.1.tgz#b245a23ca71930044ec53fa46aa00a3e87c6a610"
+  integrity sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ==
+  dependencies:
+    lodash "^4.17.10"
+
 async@^1.5.2:
   version "1.5.2"
   resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a"
@@ -1715,6 +1722,11 @@ bl@^1.0.0:
     readable-stream "^2.3.5"
     safe-buffer "^5.1.1"
 
+bluebird@3.5.1:
+  version "3.5.1"
+  resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.1.tgz#d9551f9de98f1fcda1e683d17ee91a0602ee2eb9"
+  integrity sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==
+
 bluebird@^3.1.1, bluebird@^3.5.1, bluebird@^3.5.3:
   version "3.5.4"
   resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.4.tgz#d6cc661595de30d5b3af5fcedd3c0b3ef6ec5714"
@@ -1875,6 +1887,11 @@ browserslist@^4.0.0, browserslist@^4.3.4, browserslist@^4.5.4:
     electron-to-chromium "^1.3.122"
     node-releases "^1.1.13"
 
+bson@^1.1.1, bson@~1.1.1:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/bson/-/bson-1.1.1.tgz#4330f5e99104c4e751e7351859e2d408279f2f13"
+  integrity sha512-jCGVYLoYMHDkOsbwJZBCqwMHyH4c+wzgI9hG7Z6SZJRXWr+x58pdIbm2i9a/jFGCkRJqRUr8eoI7lDWa0hTkxg==
+
 buffer-alloc-unsafe@^1.1.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz#bd7dc26ae2972d0eda253be061dba992349c19f0"
@@ -2941,6 +2958,13 @@ debug@2.6.9, debug@^2.1.2, debug@^2.2.0, debug@^2.3.3, debug@^2.6.8, debug@^2.6.
   dependencies:
     ms "2.0.0"
 
+debug@3.1.0:
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
+  integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==
+  dependencies:
+    ms "2.0.0"
+
 debug@^3.1.0, debug@^3.2.5, debug@^3.2.6:
   version "3.2.6"
   resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b"
@@ -5476,6 +5500,11 @@ jsprim@^1.2.2:
     json-schema "0.2.3"
     verror "1.10.0"
 
+kareem@2.3.0:
+  version "2.3.0"
+  resolved "https://registry.yarnpkg.com/kareem/-/kareem-2.3.0.tgz#ef33c42e9024dce511eeaf440cd684f3af1fc769"
+  integrity sha512-6hHxsp9e6zQU8nXsP+02HGWXwTkOEw6IROhF2ZA28cYbUk4eJ6QbtZvdqZOdD9YPKghG3apk5eOCvs+tLl3lRg==
+
 killable@^1.0.0:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/killable/-/killable-1.0.1.tgz#4c8ce441187a061c7474fb87ca08e2a638194892"
@@ -5870,6 +5899,11 @@ memory-fs@^0.4.0, memory-fs@^0.4.1, memory-fs@~0.4.1:
     errno "^0.1.3"
     readable-stream "^2.0.1"
 
+memory-pager@^1.0.2:
+  version "1.5.0"
+  resolved "https://registry.yarnpkg.com/memory-pager/-/memory-pager-1.5.0.tgz#d8751655d22d384682741c972f2c3d6dfa3e66b5"
+  integrity sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==
+
 meow@^5.0.0:
   version "5.0.0"
   resolved "https://registry.yarnpkg.com/meow/-/meow-5.0.0.tgz#dfc73d63a9afc714a5e371760eb5c88b91078aa4"
@@ -6087,6 +6121,49 @@ mkdirp@0.5.1, mkdirp@0.5.x, mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@
   dependencies:
     minimist "0.0.8"
 
+mongodb-core@3.2.2:
+  version "3.2.2"
+  resolved "https://registry.yarnpkg.com/mongodb-core/-/mongodb-core-3.2.2.tgz#d1c084b34c102a98b4734087800115e639d907c5"
+  integrity sha512-YRgC39MuzKL0uoGoRdTmV1e9m47NbMnYmuEx4IOkgWAGXPSEzRY7cwb3N0XMmrDMnD9vp7MysNyAriIIeGgIQg==
+  dependencies:
+    bson "^1.1.1"
+    require_optional "^1.0.1"
+    safe-buffer "^5.1.2"
+  optionalDependencies:
+    saslprep "^1.0.0"
+
+mongodb@3.2.2:
+  version "3.2.2"
+  resolved "https://registry.yarnpkg.com/mongodb/-/mongodb-3.2.2.tgz#49b592be3cd50097f78e0964488d18c2e189de20"
+  integrity sha512-xQ6apOOV+w7VFApdaJpWhYhzartpjIDFQjG0AwgJkLh7dBs7PTsq4A3Bia2QWpDohmAzTBIdQVLMqqLy0mwt3Q==
+  dependencies:
+    mongodb-core "3.2.2"
+    safe-buffer "^5.1.2"
+
+mongoose-legacy-pluralize@1.0.2:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/mongoose-legacy-pluralize/-/mongoose-legacy-pluralize-1.0.2.tgz#3ba9f91fa507b5186d399fb40854bff18fb563e4"
+  integrity sha512-Yo/7qQU4/EyIS8YDFSeenIvXxZN+ld7YdV9LqFVQJzTLye8unujAWPZ4NWKfFA+RNjh+wvTWKY9Z3E5XM6ZZiQ==
+
+mongoose@^5.5.2:
+  version "5.5.2"
+  resolved "https://registry.yarnpkg.com/mongoose/-/mongoose-5.5.2.tgz#6fa985ffe036e7cb641f7234465d7f162f14f827"
+  integrity sha512-GMSPlyy90vVSeNZbV8P4VAeKxgYhF5UgnO7wU5BCn/N6P7mSFp9+XPZyHTqJA8Uh2kO2gfwgm2QVPHrol/V6zA==
+  dependencies:
+    async "2.6.1"
+    bson "~1.1.1"
+    kareem "2.3.0"
+    mongodb "3.2.2"
+    mongodb-core "3.2.2"
+    mongoose-legacy-pluralize "1.0.2"
+    mpath "0.5.1"
+    mquery "3.2.0"
+    ms "2.1.1"
+    regexp-clone "0.0.1"
+    safe-buffer "5.1.2"
+    sift "7.0.1"
+    sliced "1.0.1"
+
 morgan@^1.9.1:
   version "1.9.1"
   resolved "https://registry.yarnpkg.com/morgan/-/morgan-1.9.1.tgz#0a8d16734a1d9afbc824b99df87e738e58e2da59"
@@ -6110,12 +6187,28 @@ move-concurrently@^1.0.1:
     rimraf "^2.5.4"
     run-queue "^1.0.3"
 
+mpath@0.5.1:
+  version "0.5.1"
+  resolved "https://registry.yarnpkg.com/mpath/-/mpath-0.5.1.tgz#17131501f1ff9e6e4fbc8ffa875aa7065b5775ab"
+  integrity sha512-H8OVQ+QEz82sch4wbODFOz+3YQ61FYz/z3eJ5pIdbMEaUzDqA268Wd+Vt4Paw9TJfvDgVKaayC0gBzMIw2jhsg==
+
+mquery@3.2.0:
+  version "3.2.0"
+  resolved "https://registry.yarnpkg.com/mquery/-/mquery-3.2.0.tgz#e276472abd5109686a15eb2a8e0761db813c81cc"
+  integrity sha512-qPJcdK/yqcbQiKoemAt62Y0BAc0fTEKo1IThodBD+O5meQRJT/2HSe5QpBNwaa4CjskoGrYWsEyjkqgiE0qjhg==
+  dependencies:
+    bluebird "3.5.1"
+    debug "3.1.0"
+    regexp-clone "0.0.1"
+    safe-buffer "5.1.2"
+    sliced "1.0.1"
+
 ms@2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
   integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=
 
-ms@^2.1.1:
+ms@2.1.1, ms@^2.1.1:
   version "2.1.1"
   resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a"
   integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==
@@ -7613,6 +7706,11 @@ regex-not@^1.0.0, regex-not@^1.0.2:
     extend-shallow "^3.0.2"
     safe-regex "^1.1.0"
 
+regexp-clone@0.0.1:
+  version "0.0.1"
+  resolved "https://registry.yarnpkg.com/regexp-clone/-/regexp-clone-0.0.1.tgz#a7c2e09891fdbf38fbb10d376fb73003e68ac589"
+  integrity sha1-p8LgmJH9vzj7sQ03b7cwA+aKxYk=
+
 regexp-tree@^0.1.0:
   version "0.1.5"
   resolved "https://registry.yarnpkg.com/regexp-tree/-/regexp-tree-0.1.5.tgz#7cd71fca17198d04b4176efd79713f2998009397"
@@ -7796,6 +7894,14 @@ require-uncached@^1.0.3:
     caller-path "^0.1.0"
     resolve-from "^1.0.0"
 
+require_optional@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/require_optional/-/require_optional-1.0.1.tgz#4cf35a4247f64ca3df8c2ef208cc494b1ca8fc2e"
+  integrity sha512-qhM/y57enGWHAe3v/NcwML6a3/vfESLe/sGM2dII+gEO0BpKRUkWZow/tyloNqJyN6kXSl3RyyM8Ll5D/sJP8g==
+  dependencies:
+    resolve-from "^2.0.0"
+    semver "^5.1.0"
+
 requires-port@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
@@ -7813,6 +7919,11 @@ resolve-from@^1.0.0:
   resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-1.0.1.tgz#26cbfe935d1aeeeabb29bc3fe5aeb01e93d44226"
   integrity sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=
 
+resolve-from@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-2.0.0.tgz#9480ab20e94ffa1d9e80a804c7ea147611966b57"
+  integrity sha1-lICrIOlP+h2egKgEx+oUdhGWa1c=
+
 resolve-from@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748"
@@ -7923,6 +8034,13 @@ safe-regex@^1.1.0:
   resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
   integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
 
+saslprep@^1.0.0:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/saslprep/-/saslprep-1.0.2.tgz#da5ab936e6ea0bbae911ffec77534be370c9f52d"
+  integrity sha512-4cDsYuAjXssUSjxHKRe4DTZC0agDwsCqcMqtJAQPzC74nJ7LfAJflAtC1Zed5hMzEQKj82d3tuzqdGNRsLJ4Gw==
+  dependencies:
+    sparse-bitfield "^3.0.3"
+
 sax@^1.2.4, sax@~1.2.4:
   version "1.2.4"
   resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
@@ -8103,6 +8221,11 @@ shell-quote@^1.6.1:
     array-reduce "~0.0.0"
     jsonify "~0.0.0"
 
+sift@7.0.1:
+  version "7.0.1"
+  resolved "https://registry.yarnpkg.com/sift/-/sift-7.0.1.tgz#47d62c50b159d316f1372f8b53f9c10cd21a4b08"
+  integrity sha512-oqD7PMJ+uO6jV9EQCl0LrRw1OwsiPsiFQR5AR30heR+4Dl7jBBbDLnNvWiak20tzZlSE1H7RB30SX/1j/YYT7g==
+
 signal-exit@^3.0.0, signal-exit@^3.0.2:
   version "3.0.2"
   resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d"
@@ -8164,6 +8287,11 @@ slice-ansi@^2.1.0:
     astral-regex "^1.0.0"
     is-fullwidth-code-point "^2.0.0"
 
+sliced@1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/sliced/-/sliced-1.0.1.tgz#0b3a662b5d04c3177b1926bea82b03f837a2ef41"
+  integrity sha1-CzpmK10Ewxd7GSa+qCsD+Dei70E=
+
 slide@^1.1.5:
   version "1.1.6"
   resolved "https://registry.yarnpkg.com/slide/-/slide-1.1.6.tgz#56eb027d65b4d2dce6cb2e2d32c4d4afc9e1d707"
@@ -8266,6 +8394,13 @@ source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0, source-map@~0.6.1:
   resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
   integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
 
+sparse-bitfield@^3.0.3:
+  version "3.0.3"
+  resolved "https://registry.yarnpkg.com/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz#ff4ae6e68656056ba4b3e792ab3334d38273ca11"
+  integrity sha1-/0rm5oZWBWuks+eSqzM004JzyhE=
+  dependencies:
+    memory-pager "^1.0.2"
+
 spdx-correct@^3.0.0:
   version "3.1.0"
   resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.0.tgz#fb83e504445268f154b074e218c87c003cd31df4"