From a99cc86bbc95d7a5ff5d8c0603dabaf7d689e69e Mon Sep 17 00:00:00 2001 From: Thomas Parisot Date: Wed, 5 Mar 2025 10:56:17 +0100 Subject: [PATCH 1/2] =?UTF-8?q?chore(mongo):=20mise=20=C3=A0=20jour=20vers?= =?UTF-8?q?=20mongo@5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- docker-compose.yaml | 3 ++- graphql/jest-mongodb-config.js | 8 ++++---- graphql/migrations/20250305093600-mongo5.js | 11 +++++++++++ graphql/tests/setup-db.js | 11 +++-------- infrastructure/templates/docker-compose.yaml | 2 +- 6 files changed, 22 insertions(+), 15 deletions(-) create mode 100644 graphql/migrations/20250305093600-mongo5.js diff --git a/README.md b/README.md index c1db1f9ab..ec53177f6 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ Plus d'informations sur [la documentation](http://stylo-doc.ecrituresnumeriques. # Pré-requis - Node.js v22+ -- MongoDB 4.4 +- MongoDB 5 ## Sous MacOS diff --git a/docker-compose.yaml b/docker-compose.yaml index ff36694a2..e232ccaba 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,6 +1,7 @@ services: mongodb-stylo: - image: mongo:4.4 + # to keep in sync with ./graphql/jest-mongodb-config.js / README.md + image: mongo:5 container_name: 'mongodb-stylo' environment: - MONGO_DATA_DIR=/data/db diff --git a/graphql/jest-mongodb-config.js b/graphql/jest-mongodb-config.js index f70b0b937..d38a58ffa 100644 --- a/graphql/jest-mongodb-config.js +++ b/graphql/jest-mongodb-config.js @@ -3,10 +3,10 @@ module.exports = { mongodbMemoryServerOptions: { autoStart: false, binary: { - version: '4.4.29' + version: '5.0.31', }, instance: { - dbName: 'stylo-tests' - } - } + dbName: 'stylo-tests', + }, + }, } diff --git a/graphql/migrations/20250305093600-mongo5.js b/graphql/migrations/20250305093600-mongo5.js new file mode 100644 index 000000000..37107e62a --- /dev/null +++ b/graphql/migrations/20250305093600-mongo5.js @@ -0,0 +1,11 @@ +exports.up = function (db) { + return db._run('executeDbAdminCommand', { + setFeatureCompatibilityVersion: '5.0', + }) +} + +exports.down = function (db) { + return db._run('executeDbAdminCommand', { + setFeatureCompatibilityVersion: '4.4', + }) +} diff --git a/graphql/tests/setup-db.js b/graphql/tests/setup-db.js index 5c53552a8..99047cd86 100644 --- a/graphql/tests/setup-db.js +++ b/graphql/tests/setup-db.js @@ -1,11 +1,6 @@ const mongoose = require('mongoose') const migrate = require('db-migrate') -mongoose.set('useNewUrlParser', true) -mongoose.set('useUnifiedTopology', true) -mongoose.set('useCreateIndex', true) -mongoose.set('useFindAndModify', false) - const databaseUrl = global.__MONGO_URI__ + 'stylo-tests' beforeAll(async () => { @@ -17,16 +12,16 @@ beforeAll(async () => { await migrateInstance.up() }) - beforeAll(async () => { globalThis.__MONGO__ = await mongoose.connect(databaseUrl, { useNewUrlParser: true, useUnifiedTopology: true, useCreateIndex: true, - useFindAndModify: true + useFindAndModify: true, }) - globalThis.__MONGO_SESSION__ = await globalThis.__MONGO__.connection.startSession() + globalThis.__MONGO_SESSION__ = + await globalThis.__MONGO__.connection.startSession() }) afterAll(async () => { diff --git a/infrastructure/templates/docker-compose.yaml b/infrastructure/templates/docker-compose.yaml index 939b7e38c..b44050737 100644 --- a/infrastructure/templates/docker-compose.yaml +++ b/infrastructure/templates/docker-compose.yaml @@ -2,7 +2,7 @@ version: '2.1' services: mongodb-stylo: - image: mongo:4.4 + image: mongo:5 container_name: 'mongodb-stylo' environment: - MONGO_DATA_DIR=/data/db From 23f901223d209385ace82b663b00ce865028652d Mon Sep 17 00:00:00 2001 From: Thomas Parisot Date: Wed, 5 Mar 2025 10:57:00 +0100 Subject: [PATCH 2/2] =?UTF-8?q?chore(graphql):=20mise=20=C3=A0=20jour=20ve?= =?UTF-8?q?rsion=20connect-mongo@5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- graphql/app.js | 58 +++++++++------ graphql/package-lock.json | 151 +++++++++++++++----------------------- graphql/package.json | 5 +- 3 files changed, 97 insertions(+), 117 deletions(-) diff --git a/graphql/app.js b/graphql/app.js index dd70fc179..166d0b8b7 100644 --- a/graphql/app.js +++ b/graphql/app.js @@ -39,7 +39,7 @@ const mongoose = require('mongoose') const cors = require('cors') const session = require('express-session') -const MongoStore = require('connect-mongo')(session) +const MongoStore = require('connect-mongo') const passport = require('passport') const OidcStrategy = require('passport-openidconnect').Strategy const LocalStrategy = require('passport-local').Strategy @@ -115,8 +115,22 @@ const corsOptions = { }, } -const app = express() +/* + * Setup database + */ +const mongooseP = mongoose + .connect(config.get('mongo.databaseUrl'), { + useNewUrlParser: true, + useUnifiedTopology: true, + useCreateIndex: true, + useFindAndModify: true, + }) + .then((m) => m.connection.getClient()) +/* + * Setup App + */ +const app = express() config.get('sentry.dsn') && Sentry.setupExpressErrorHandler(app) passport.use( @@ -220,7 +234,11 @@ app.use( resave: false, proxy: true, saveUninitialized: false, - store: new MongoStore({ mongooseConnection: mongoose.connection }), + store: MongoStore.create({ + clientPromise: mongooseP, + autoRemove: 'native', + touchAfter: 12 * 3600, + }), cookie: { httpOnly: true, secure: secureCookie, @@ -365,25 +383,19 @@ app.post( // Collaborative Writing Websocket wss.on('connection', setupWSConnection) -// fix deprecation warnings: https://mongoosejs.com/docs/deprecations.html -mongoose.set('useNewUrlParser', true) -mongoose.set('useUnifiedTopology', true) -mongoose.set('useCreateIndex', true) -mongoose.set('useFindAndModify', false) - -mongoose - .connect(config.get('mongo.databaseUrl')) - .then(() => { - logger.info('Listening on http://localhost:%s', listenPort) - const server = app.listen(listenPort) - server.on('upgrade', (request, socket, head) => { - wss.handleUpgrade(request, socket, head, function handleAuth(ws) { - // const jwtToken = new URL('http://localhost' + request.url).searchParams.get("token") - // TODO: check token and permissions - wss.emit('connection', ws, request) - }) - }) - }) - .catch((err) => { +const server = app.listen(listenPort, (err) => { + if (err) { logger.error({ err }, 'Unable to connect to MongoDB.') + throw err + } + + logger.info('Listening on http://localhost:%s', listenPort) +}) + +server.on('upgrade', (request, socket, head) => { + wss.handleUpgrade(request, socket, head, function handleAuth(ws) { + // const jwtToken = new URL('http://localhost' + request.url).searchParams.get("token") + // TODO: check token and permissions + wss.emit('connection', ws, request) }) +}) diff --git a/graphql/package-lock.json b/graphql/package-lock.json index cc6aa97e5..a2e579275 100644 --- a/graphql/package-lock.json +++ b/graphql/package-lock.json @@ -16,7 +16,7 @@ "bcryptjs": "^2.4.3", "body-parser": "^1.18.3", "colornames": "^1.1.1", - "connect-mongo": "^3.2.0", + "connect-mongo": "^5.1.0", "convict": "^6.2.4", "convict-format-with-validator": "^6.2.0", "cors": "^2.8.5", @@ -32,6 +32,7 @@ "graphql-scalars": "^1.20.1", "jsdom": "^21.0.0", "jsonwebtoken": "^9.0.0", + "mongodb": "^6.14.2", "mongoose": "^5.11.1", "passport": "^0.7.0", "passport-local": "^1.0.0", @@ -1214,7 +1215,6 @@ "version": "1.1.9", "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.9.tgz", "integrity": "sha512-tVkljjeEaAhCqTzajSdgbQ6gE6f3oneVwa3iXR6csiEwXXOFsiC6Uh9iAjAhXPtqa/XMDHWjjeNH/77m/Yq2dw==", - "dev": true, "license": "MIT", "dependencies": { "sparse-bitfield": "^3.0.3" @@ -2232,16 +2232,13 @@ "version": "7.0.3", "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz", "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==", - "dev": true, "license": "MIT" }, "node_modules/@types/whatwg-url": { "version": "11.0.5", "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.5.tgz", "integrity": "sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==", - "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/webidl-conversions": "*" } @@ -2619,6 +2616,18 @@ "safer-buffer": "~2.1.0" } }, + "node_modules/asn1.js": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", + "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", + "license": "MIT", + "dependencies": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "safer-buffer": "^2.1.0" + } + }, "node_modules/async": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/async/-/async-3.2.3.tgz", @@ -2898,6 +2907,12 @@ "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", "license": "MIT" }, + "node_modules/bn.js": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.1.tgz", + "integrity": "sha512-k8TVBiPkPJT9uHLdOKfFpqcfprwBFOAAXXozRubr7R7PfIuKvQlzcI4M0pALeqXN09vdaMbUdUj+pass+uULAg==", + "license": "MIT" + }, "node_modules/body-parser": { "version": "1.20.3", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", @@ -3005,12 +3020,10 @@ } }, "node_modules/bson": { - "version": "6.10.1", - "resolved": "https://registry.npmjs.org/bson/-/bson-6.10.1.tgz", - "integrity": "sha512-P92xmHDQjSKPLHqFxefqMxASNq/aWJMEZugpCjf+AF/pgcUpMMQCg7t7+ewko0/u8AapvF3luf/FoehddEK+sA==", - "dev": true, + "version": "6.10.3", + "resolved": "https://registry.npmjs.org/bson/-/bson-6.10.3.tgz", + "integrity": "sha512-MTxGsqgYTwfshYWTRdmZRC+M7FnG1b4y7RO7p2k3X24Wq0yv1m77Wsj0BzlPzd/IowgESfsruQCUToa7vbOpPQ==", "license": "Apache-2.0", - "peer": true, "engines": { "node": ">=16.20.1" } @@ -3285,72 +3298,20 @@ "license": "MIT" }, "node_modules/connect-mongo": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/connect-mongo/-/connect-mongo-3.2.0.tgz", - "integrity": "sha512-0Mx88079Z20CG909wCFlR3UxhMYGg6Ibn1hkIje1hwsqOLWtL9HJV+XD0DAjUvQScK6WqY/FA8tSVQM9rR64Rw==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/connect-mongo/-/connect-mongo-5.1.0.tgz", + "integrity": "sha512-xT0vxQLqyqoUTxPLzlP9a/u+vir0zNkhiy9uAdHjSCcUUf7TS5b55Icw8lVyYFxfemP3Mf9gdwUOgeF3cxCAhw==", "license": "MIT", "dependencies": { - "mongodb": "^3.1.0" - } - }, - "node_modules/connect-mongo/node_modules/bson": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/bson/-/bson-1.1.6.tgz", - "integrity": "sha512-EvVNVeGo4tHxwi8L6bPj3y3itEvStdwvvlojVxxbyYfoaxJ6keLgrTuKdyfEAszFK+H3olzBuafE0yoh0D1gdg==", - "license": "Apache-2.0", - "engines": { - "node": ">=0.6.19" - } - }, - "node_modules/connect-mongo/node_modules/mongodb": { - "version": "3.7.4", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.7.4.tgz", - "integrity": "sha512-K5q8aBqEXMwWdVNh94UQTwZ6BejVbFhh1uB6c5FKtPE9eUMZPUO3sRZdgIEcHSrAWmxzpG/FeODDKL388sqRmw==", - "license": "Apache-2.0", - "dependencies": { - "bl": "^2.2.1", - "bson": "^1.1.4", - "denque": "^1.4.1", - "optional-require": "^1.1.8", - "safe-buffer": "^5.1.2" + "debug": "^4.3.1", + "kruptein": "^3.0.0" }, "engines": { - "node": ">=4" - }, - "optionalDependencies": { - "saslprep": "^1.0.0" + "node": ">=12.9.0" }, - "peerDependenciesMeta": { - "aws4": { - "optional": true - }, - "bson-ext": { - "optional": true - }, - "kerberos": { - "optional": true - }, - "mongodb-client-encryption": { - "optional": true - }, - "mongodb-extjson": { - "optional": true - }, - "snappy": { - "optional": true - } - } - }, - "node_modules/connect-mongo/node_modules/optional-require": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/optional-require/-/optional-require-1.1.8.tgz", - "integrity": "sha512-jq83qaUb0wNg9Krv1c5OQ+58EK+vHde6aBPzLvPPqJm89UQWsvSuFy9X/OSNJnFeSOKo7btE0n8Nl2+nE+z5nA==", - "license": "Apache-2.0", - "dependencies": { - "require-at": "^1.0.6" - }, - "engines": { - "node": ">=4" + "peerDependencies": { + "express-session": "^1.17.1", + "mongodb": ">= 5.1.0 < 7" } }, "node_modules/content-disposition": { @@ -5271,7 +5232,7 @@ "version": "9.0.5", "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "jsbn": "1.1.0", @@ -5285,7 +5246,7 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", - "dev": true, + "devOptional": true, "license": "BSD-3-Clause" }, "node_modules/ipaddr.js": { @@ -6179,7 +6140,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/jsdom": { @@ -6350,6 +6311,18 @@ "node": ">=6" } }, + "node_modules/kruptein": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/kruptein/-/kruptein-3.0.7.tgz", + "integrity": "sha512-vTftnEjfbqFHLqxDUMQCj6gBo5lKqjV4f0JsM8rk8rM3xmvFZ2eSy4YALdaye7E+cDKnEj7eAjFR3vwh8a4PgQ==", + "license": "MIT", + "dependencies": { + "asn1.js": "^5.4.1" + }, + "engines": { + "node": ">8" + } + }, "node_modules/level": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/level/-/level-6.0.1.tgz", @@ -6712,7 +6685,6 @@ "version": "1.5.0", "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", - "devOptional": true, "license": "MIT" }, "node_modules/merge-descriptors": { @@ -6807,6 +6779,12 @@ "node": ">=6" } }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "license": "ISC" + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -6848,15 +6826,13 @@ "license": "MIT" }, "node_modules/mongodb": { - "version": "6.12.0", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.12.0.tgz", - "integrity": "sha512-RM7AHlvYfS7jv7+BXund/kR64DryVI+cHbVAy9P61fnb1RcWZqOW1/Wj2YhqMCx+MuYhqTRGv7AwHBzmsCKBfA==", - "dev": true, + "version": "6.14.2", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.14.2.tgz", + "integrity": "sha512-kMEHNo0F3P6QKDq17zcDuPeaywK/YaJVCEQRzPF3TOM/Bl9MFg64YE5Tu7ifj37qZJMhwU1tl2Ioivws5gRG5Q==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@mongodb-js/saslprep": "^1.1.9", - "bson": "^6.10.1", + "bson": "^6.10.3", "mongodb-connection-string-url": "^3.0.0" }, "engines": { @@ -6899,9 +6875,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.2.tgz", "integrity": "sha512-rMO7CGo/9BFwyZABcKAWL8UJwH/Kc2x0g72uhDWzG48URRax5TCIcJ7Rc3RZqffZzO/Gwff/jyKwCU9TN8gehA==", - "dev": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "@types/whatwg-url": "^11.0.2", "whatwg-url": "^14.1.0 || ^13.0.0" @@ -6911,9 +6885,7 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=6" } @@ -6922,9 +6894,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.0.0.tgz", "integrity": "sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==", - "dev": true, "license": "MIT", - "peer": true, "dependencies": { "punycode": "^2.3.1" }, @@ -6936,9 +6906,7 @@ "version": "14.1.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.1.0.tgz", "integrity": "sha512-jlf/foYIKywAt3x/XWKZ/3rz8OSJPiWktjmk891alJUEjiVxKX9LEO92qH3hv4aJ0mN3MWPvGMCy8jQi95xK4w==", - "dev": true, "license": "MIT", - "peer": true, "dependencies": { "tr46": "^5.0.0", "webidl-conversions": "^7.0.0" @@ -8868,7 +8836,7 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">= 6.0.0", @@ -8879,7 +8847,7 @@ "version": "2.8.3", "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz", "integrity": "sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "ip-address": "^9.0.5", @@ -8933,7 +8901,6 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==", - "devOptional": true, "license": "MIT", "dependencies": { "memory-pager": "^1.0.2" diff --git a/graphql/package.json b/graphql/package.json index 218fafc1c..165fc4713 100644 --- a/graphql/package.json +++ b/graphql/package.json @@ -28,7 +28,7 @@ "bcryptjs": "^2.4.3", "body-parser": "^1.18.3", "colornames": "^1.1.1", - "connect-mongo": "^3.2.0", + "connect-mongo": "^5.1.0", "convict": "^6.2.4", "convict-format-with-validator": "^6.2.0", "cors": "^2.8.5", @@ -44,6 +44,7 @@ "graphql-scalars": "^1.20.1", "jsdom": "^21.0.0", "jsonwebtoken": "^9.0.0", + "mongodb": "^6.14.2", "mongoose": "^5.11.1", "passport": "^0.7.0", "passport-local": "^1.0.0", @@ -69,4 +70,4 @@ "node": "22.13.1", "npm": "10.9.2" } -} \ No newline at end of file +}