fix oom issue and upgrade to nx least

This commit is contained in:
zizifn
2023-04-23 06:31:39 +08:00
committed by zizifn
parent 2cdf4ad6f8
commit d36ebb06da
11 changed files with 4456 additions and 4027 deletions

View File

@@ -2,14 +2,15 @@
export default { export default {
displayName: 'node-vless', displayName: 'node-vless',
preset: '../../jest.preset.js', preset: '../../jest.preset.js',
globals: { globals: {},
'ts-jest': {
tsconfig: '<rootDir>/tsconfig.spec.json',
},
},
testEnvironment: 'node', testEnvironment: 'node',
transform: { transform: {
'^.+\\.[tj]s$': 'ts-jest', '^.+\\.[tj]s$': [
'ts-jest',
{
tsconfig: '<rootDir>/tsconfig.spec.json',
},
],
}, },
moduleFileExtensions: ['ts', 'js', 'html'], moduleFileExtensions: ['ts', 'js', 'html'],
coverageDirectory: '../../coverage/apps/node-vless', coverageDirectory: '../../coverage/apps/node-vless',

View File

@@ -14,7 +14,7 @@ import {
closeWebSocket, closeWebSocket,
} from 'vless-js'; } from 'vless-js';
import { connect, Socket } from 'node:net'; import { connect, Socket } from 'node:net';
import { Duplex, Readable } from 'stream'; import { Duplex, Readable, Writable } from 'stream';
import { import {
TransformStream, TransformStream,
ReadableStream, ReadableStream,
@@ -162,6 +162,7 @@ vlessWServer.on('connection', async function connection(ws, request) {
// if (udpClientStream ) { // if (udpClientStream ) {
// udpClientStream.writable.close(); // udpClientStream.writable.close();
// } // }
// (remoteConnection as Socket).end();
console.log( console.log(
`[${address}:${portWithRandomLog}] readableWebSocketStream is close` `[${address}:${portWithRandomLog}] readableWebSocketStream is close`
); );
@@ -190,9 +191,34 @@ vlessWServer.on('connection', async function connection(ws, request) {
// remote --> ws // remote --> ws
let responseStream = udpClientStream?.readable; let responseStream = udpClientStream?.readable;
if (remoteConnection) { if (remoteConnection) {
responseStream = Readable.toWeb(remoteConnection); // ignore type error
// @ts-ignore
responseStream = Readable.toWeb(remoteConnection, {
strategy: {
// due to nodejs issue https://github.com/nodejs/node/issues/46347
highWaterMark: 1000, // 1000 * tcp mtu(64kb) = 64mb
},
});
} }
let count = 0;
// ws.send(vlessResponseHeader!);
// remoteConnection.pipe(
// new Writable({
// async write(chunk: Uint8Array, encoding, callback) {
// count += chunk.byteLength;
// console.log('ws write', count / (1024 * 1024));
// console.log(
// '-----++++',
// (remoteConnection as Socket).bytesRead / (1024 * 1024)
// );
// if (ws.readyState === ws.OPEN) {
// await wsAsyncWrite(ws, chunk);
// callback();
// }
// },
// })
// );
// if readable not pipe can't wait fro writeable write method // if readable not pipe can't wait fro writeable write method
await responseStream.pipeTo( await responseStream.pipeTo(
new WritableStream({ new WritableStream({
@@ -202,9 +228,21 @@ vlessWServer.on('connection', async function connection(ws, request) {
} }
}, },
async write(chunk: Uint8Array, controller) { async write(chunk: Uint8Array, controller) {
// console.log('ws write', chunk); // count += chunk.byteLength;
// console.log('ws write', count / (1024 * 1024));
// console.log(
// '-----++++',
// (remoteConnection as Socket).bytesRead / (1024 * 1024),
// (remoteConnection as Socket).readableHighWaterMark
// );
// we have issue there, maybe beacsue nodejs web stream has bug.
// socket web stream will read more data from socket
if (ws.readyState === ws.OPEN) { if (ws.readyState === ws.OPEN) {
await wsAsyncWrite(ws, chunk); await wsAsyncWrite(ws, chunk);
} else {
if (!(remoteConnection as Socket).destroyed) {
(remoteConnection as Socket).destroy();
}
} }
}, },
close() { close() {
@@ -281,10 +319,6 @@ async function socketAsyncWrite(ws: Duplex, chunk: Buffer) {
} }
async function wsAsyncWrite(ws: WebSocket, chunk: Uint8Array) { async function wsAsyncWrite(ws: WebSocket, chunk: Uint8Array) {
// 5m not transmitted to the network
while (ws.bufferedAmount > 1024 * 1024 * 5) {
await delay(1);
}
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
ws.send(chunk, (error) => { ws.send(chunk, (error) => {
if (error) { if (error) {

View File

@@ -5169,8 +5169,12 @@ function initAsClient(websocket, address, protocols, options) {
}); });
}); });
if (opts.finishRequest) {
opts.finishRequest(req, websocket);
} else {
req.end(); req.end();
} }
}
/** /**
* Emit the `'error'` and `'close'` events. * Emit the `'error'` and `'close'` events.
@@ -5566,7 +5570,7 @@ exports.vlessJs = exports.processVlessHeader = exports.closeWebSocket = exports.
var vless_js_1 = __webpack_require__("../../libs/vless-js/src/lib/vless-js.ts"); var vless_js_1 = __webpack_require__("../../libs/vless-js/src/lib/vless-js.ts");
Object.defineProperty(exports, "delay", ({ enumerable: true, get: function () { return vless_js_1.delay; } })); Object.defineProperty(exports, "delay", ({ enumerable: true, get: function () { return vless_js_1.delay; } }));
Object.defineProperty(exports, "makeReadableWebSocketStream", ({ enumerable: true, get: function () { return vless_js_1.makeReadableWebSocketStream; } })); Object.defineProperty(exports, "makeReadableWebSocketStream", ({ enumerable: true, get: function () { return vless_js_1.makeReadableWebSocketStream; } }));
Object.defineProperty(exports, "closeWebSocket", ({ enumerable: true, get: function () { return vless_js_1.closeWebSocket; } })); Object.defineProperty(exports, "closeWebSocket", ({ enumerable: true, get: function () { return vless_js_1.safeCloseWebSocket; } }));
Object.defineProperty(exports, "processVlessHeader", ({ enumerable: true, get: function () { return vless_js_1.processVlessHeader; } })); Object.defineProperty(exports, "processVlessHeader", ({ enumerable: true, get: function () { return vless_js_1.processVlessHeader; } }));
Object.defineProperty(exports, "vlessJs", ({ enumerable: true, get: function () { return vless_js_1.vlessJs; } })); Object.defineProperty(exports, "vlessJs", ({ enumerable: true, get: function () { return vless_js_1.vlessJs; } }));
@@ -5579,7 +5583,7 @@ Object.defineProperty(exports, "vlessJs", ({ enumerable: true, get: function ()
"use strict"; "use strict";
Object.defineProperty(exports, "__esModule", ({ value: true })); Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.processVlessHeader = exports.closeWebSocket = exports.makeReadableWebSocketStream = exports.delay = exports.vlessJs = void 0; exports.processVlessHeader = exports.safeCloseWebSocket = exports.makeReadableWebSocketStream = exports.delay = exports.vlessJs = void 0;
const tslib_1 = __webpack_require__("../../node_modules/tslib/tslib.es6.js"); const tslib_1 = __webpack_require__("../../node_modules/tslib/tslib.es6.js");
const uuid_1 = __webpack_require__("../../node_modules/uuid/dist/esm-node/index.js"); const uuid_1 = __webpack_require__("../../node_modules/uuid/dist/esm-node/index.js");
function vlessJs() { function vlessJs() {
@@ -5630,7 +5634,7 @@ function makeReadableWebSocketStream(ws, earlyDataHeader, log) {
const { earlyData, error } = base64ToArrayBuffer(earlyDataHeader); const { earlyData, error } = base64ToArrayBuffer(earlyDataHeader);
if (error) { if (error) {
log(`earlyDataHeader has invaild base64`); log(`earlyDataHeader has invaild base64`);
closeWebSocket(ws); safeCloseWebSocket(ws);
return; return;
} }
if (earlyData) { if (earlyData) {
@@ -5648,7 +5652,7 @@ function makeReadableWebSocketStream(ws, earlyDataHeader, log) {
return; return;
} }
readableStreamCancel = true; readableStreamCancel = true;
closeWebSocket(ws); safeCloseWebSocket(ws);
}, },
}); });
} }
@@ -5668,12 +5672,17 @@ function base64ToArrayBuffer(base64Str) {
return { error }; return { error };
} }
} }
function closeWebSocket(socket) { function safeCloseWebSocket(socket) {
try {
if (socket.readyState === socket.OPEN) { if (socket.readyState === socket.OPEN) {
socket.close(); socket.close();
} }
} }
exports.closeWebSocket = closeWebSocket; catch (error) {
console.error('safeCloseWebSocket error', error);
}
}
exports.safeCloseWebSocket = safeCloseWebSocket;
//https://github.com/v2ray/v2ray-core/issues/2636 //https://github.com/v2ray/v2ray-core/issues/2636
// 1 字节 16 字节 1 字节 M 字节 1 字节 2 字节 1 字节 S 字节 X 字节 // 1 字节 16 字节 1 字节 M 字节 1 字节 2 字节 1 字节 S 字节 X 字节
// 协议版本 等价 UUID 附加信息长度 M (附加信息 ProtoBuf) 指令(udp/tcp) 端口 地址类型 地址 请求数据 // 协议版本 等价 UUID 附加信息长度 M (附加信息 ProtoBuf) 指令(udp/tcp) 端口 地址类型 地址 请求数据
@@ -6172,6 +6181,7 @@ vlessWServer.on('connection', function connection(ws, request) {
// if (udpClientStream ) { // if (udpClientStream ) {
// udpClientStream.writable.close(); // udpClientStream.writable.close();
// } // }
// (remoteConnection as Socket).end();
console.log(`[${address}:${portWithRandomLog}] readableWebSocketStream is close`); console.log(`[${address}:${portWithRandomLog}] readableWebSocketStream is close`);
}, },
abort(reason) { abort(reason) {
@@ -6190,8 +6200,33 @@ vlessWServer.on('connection', function connection(ws, request) {
// remote --> ws // remote --> ws
let responseStream = udpClientStream === null || udpClientStream === void 0 ? void 0 : udpClientStream.readable; let responseStream = udpClientStream === null || udpClientStream === void 0 ? void 0 : udpClientStream.readable;
if (remoteConnection) { if (remoteConnection) {
responseStream = stream_1.Readable.toWeb(remoteConnection); // ignore type error
// @ts-ignore
responseStream = stream_1.Readable.toWeb(remoteConnection, {
strategy: {
// due to nodejs issue https://github.com/nodejs/node/issues/46347
highWaterMark: 1000, // 1000 * tcp mtu(64kb) = 64mb
},
});
} }
let count = 0;
// ws.send(vlessResponseHeader!);
// remoteConnection.pipe(
// new Writable({
// async write(chunk: Uint8Array, encoding, callback) {
// count += chunk.byteLength;
// console.log('ws write', count / (1024 * 1024));
// console.log(
// '-----++++',
// (remoteConnection as Socket).bytesRead / (1024 * 1024)
// );
// if (ws.readyState === ws.OPEN) {
// await wsAsyncWrite(ws, chunk);
// callback();
// }
// },
// })
// );
// if readable not pipe can't wait fro writeable write method // if readable not pipe can't wait fro writeable write method
yield responseStream.pipeTo(new web_1.WritableStream({ yield responseStream.pipeTo(new web_1.WritableStream({
start() { start() {
@@ -6201,10 +6236,23 @@ vlessWServer.on('connection', function connection(ws, request) {
}, },
write(chunk, controller) { write(chunk, controller) {
return tslib_1.__awaiter(this, void 0, void 0, function* () { return tslib_1.__awaiter(this, void 0, void 0, function* () {
// console.log('ws write', chunk); // count += chunk.byteLength;
// console.log('ws write', count / (1024 * 1024));
// console.log(
// '-----++++',
// (remoteConnection as Socket).bytesRead / (1024 * 1024),
// (remoteConnection as Socket).readableHighWaterMark
// );
// we have issue there, maybe beacsue nodejs web stream has bug.
// socket web stream will read more data from socket
if (ws.readyState === ws.OPEN) { if (ws.readyState === ws.OPEN) {
yield wsAsyncWrite(ws, chunk); yield wsAsyncWrite(ws, chunk);
} }
else {
if (!remoteConnection.destroyed) {
remoteConnection.destroy();
}
}
}); });
}, },
close() { close() {
@@ -6268,10 +6316,6 @@ function socketAsyncWrite(ws, chunk) {
} }
function wsAsyncWrite(ws, chunk) { function wsAsyncWrite(ws, chunk) {
return tslib_1.__awaiter(this, void 0, void 0, function* () { return tslib_1.__awaiter(this, void 0, void 0, function* () {
// 5m not transmitted to the network
while (ws.bufferedAmount > 1024 * 1024 * 5) {
yield (0, vless_js_1.delay)(1);
}
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
ws.send(chunk, (error) => { ws.send(chunk, (error) => {
if (error) { if (error) {

File diff suppressed because one or more lines are too long

View File

@@ -1,3 +1,15 @@
const nxPreset = require('@nrwl/jest/preset').default; const nxPreset = require('@nrwl/jest/preset').default;
module.exports = { ...nxPreset }; module.exports = {
...nxPreset,
/* TODO: Update to latest Jest snapshotFormat
* By default Nx has kept the older style of Jest Snapshot formats
* to prevent breaking of any existing tests with snapshots.
* It's recommend you update to the latest format.
* You can do this by removing snapshotFormat property
* and running tests with --update-snapshot flag.
* Example: "nx affected --targets=test --update-snapshot"
* More info: https://jestjs.io/docs/upgrading-to-jest29#snapshot-format
*/
snapshotFormat: { escapeString: true, printBasicPrototype: true },
};

View File

@@ -2,13 +2,14 @@
export default { export default {
displayName: 'vless-js', displayName: 'vless-js',
preset: '../../jest.preset.js', preset: '../../jest.preset.js',
globals: { globals: {},
'ts-jest': { transform: {
'^.+\\.[tj]s$': [
'ts-jest',
{
tsconfig: '<rootDir>/tsconfig.spec.json', tsconfig: '<rootDir>/tsconfig.spec.json',
}, },
}, ],
transform: {
'^.+\\.[tj]s$': 'ts-jest',
}, },
moduleFileExtensions: ['ts', 'js', 'html'], moduleFileExtensions: ['ts', 'js', 'html'],
coverageDirectory: '../../coverage/libs/vless-js', coverageDirectory: '../../coverage/libs/vless-js',

View File

@@ -1,7 +1,7 @@
export { export {
delay, delay,
makeReadableWebSocketStream, makeReadableWebSocketStream,
closeWebSocket, safeCloseWebSocket as closeWebSocket,
processVlessHeader, processVlessHeader,
vlessJs, vlessJs,
} from './lib/vless-js'; } from './lib/vless-js';

View File

@@ -50,7 +50,7 @@ export function makeReadableWebSocketStream(
const { earlyData, error } = base64ToArrayBuffer(earlyDataHeader); const { earlyData, error } = base64ToArrayBuffer(earlyDataHeader);
if (error) { if (error) {
log(`earlyDataHeader has invaild base64`); log(`earlyDataHeader has invaild base64`);
closeWebSocket(ws); safeCloseWebSocket(ws);
return; return;
} }
if (earlyData) { if (earlyData) {
@@ -68,7 +68,7 @@ export function makeReadableWebSocketStream(
return; return;
} }
readableStreamCancel = true; readableStreamCancel = true;
closeWebSocket(ws); safeCloseWebSocket(ws);
}, },
}); });
} }
@@ -88,10 +88,14 @@ function base64ToArrayBuffer(base64Str: string) {
} }
} }
export function closeWebSocket(socket: WebSocket | any) { export function safeCloseWebSocket(socket: WebSocket | any) {
try {
if (socket.readyState === socket.OPEN) { if (socket.readyState === socket.OPEN) {
socket.close(); socket.close();
} }
} catch (error) {
console.error('safeCloseWebSocket error', error);
}
} }
//https://github.com/v2ray/v2ray-core/issues/2636 //https://github.com/v2ray/v2ray-core/issues/2636

View File

@@ -1,92 +1,44 @@
{ {
"migrations": [ "migrations": [
{ {
"version": "15.7.0-beta.0",
"description": "Split global configuration files into individual project.json files. This migration has been added automatically to the beginning of your migration set to retroactively make them work with the new version of Nx.",
"cli": "nx", "cli": "nx",
"implementation": "./src/migrations/update-15-7-0/split-configuration-into-project-json-files", "version": "15.8.2-beta.0",
"package": "@nrwl/workspace", "description": "Updates the nx wrapper.",
"name": "15-7-0-split-configuration-into-project-json-files" "implementation": "./src/migrations/update-15-8-2/update-nxw",
"package": "nx",
"name": "15.8.2-update-nx-wrapper"
}, },
{ {
"cli": "nx", "cli": "nx",
"version": "15.7.1-beta.0", "version": "15.8.0-beta.0",
"description": "Add node_modules to root eslint ignore", "description": "Rename .lib.swcrc to .swcrc for better SWC support throughout the workspace",
"factory": "./src/migrations/update-15-7-1/add-eslint-ignore", "factory": "./src/migrations/update-15-8-0/rename-swcrc-config",
"package": "@nrwl/linter", "package": "@nrwl/js",
"name": "add-eslint-ignore" "name": "rename-swcrc-config"
}, },
{ {
"cli": "nx", "cli": "nx",
"version": "15.5.0-beta.0", "version": "15.9.1",
"description": "Update to Cypress v12. Cypress 12 contains a handful of breaking changes that might causes tests to start failing that nx cannot directly fix. Read more Cypress 12 changes: https://docs.cypress.io/guides/references/migration-guide#Migrating-to-Cypress-12-0.This migration will only run if you are already using Cypress v11.", "description": "Add @nrwl/linter, @nrwl/cypress, @nrwl/jest, @nrwl/rollup if they are used",
"factory": "./src/migrations/update-15-5-0/update-to-cypress-12", "factory": "./src/migrations/update-15-9-1/add-dropped-dependencies",
"package": "@nrwl/cypress",
"name": "update-to-cypress-12"
},
{
"version": "15.7.0-beta.0",
"description": "Split global configuration files (e.g., workspace.json) into individual project.json files.",
"cli": "nx",
"implementation": "./src/migrations/update-15-7-0/split-configuration-into-project-json-files",
"package": "@nrwl/workspace",
"name": "15-7-0-split-configuration-into-project-json-files"
},
{
"cli": "nx",
"version": "15.6.3-beta.0",
"description": "Creates or updates webpack.config.js file with the new options for webpack.",
"factory": "./src/migrations/update-15-6-3/webpack-config-setup",
"package": "@nrwl/react",
"name": "react-webpack-config-setup"
},
{
"cli": "nx",
"version": "15.3.4-beta.0",
"description": "Set the mode in configurations to match the configurationName.",
"factory": "./src/migrations/update-15-3-4/set-mode-in-configuration",
"package": "@nrwl/vite",
"name": "set-mode-in-configurations"
},
{
"cli": "nx",
"version": "15.4.3-beta.0",
"description": "Update @nrwl/vite:test reportsDirectory and outputs properties to point to correct paths.",
"factory": "./src/migrations/update-15-4-3/update-report-directory",
"package": "@nrwl/vite",
"name": "update-test-path-placeholder-vars"
},
{
"cli": "nx",
"version": "15.5.4-beta.0",
"description": "Update `@nrwl/web/babel` preset to `@nrwl/js/babel` for projects that have a .babelrc file.",
"factory": "./src/migrations/update-15-5-4/update-babel-preset",
"package": "@nrwl/web", "package": "@nrwl/web",
"name": "update-babel-preset" "name": "add-dropped-dependencies"
}, },
{ {
"version": "15.8.0-beta.0",
"cli": "nx", "cli": "nx",
"version": "15.4.5-beta.0", "description": "Update jest configs to support jest 29 changes (https://jestjs.io/docs/upgrading-to-jest29)",
"description": "Removes es2015Polyfills option since legacy browsers are no longer supported.", "factory": "./src/migrations/update-15-8-0/update-configs-jest-29",
"factory": "./src/migrations/update-15-4-5/remove-es2015-polyfills-option", "package": "@nrwl/jest",
"package": "@nrwl/webpack", "name": "update-configs-jest-29"
"name": "remove-es2015-polyfills-option"
}, },
{ {
"version": "15.8.0-beta.0",
"cli": "nx", "cli": "nx",
"version": "15.6.3-beta.0", "description": "Update jest test files to support jest 29 changes (https://jestjs.io/docs/upgrading-to-jest29)",
"description": "Creates or updates webpack.config.js file with the new options for webpack.", "factory": "./src/migrations/update-15-8-0/update-tests-jest-29",
"factory": "./src/migrations/update-15-6-3/webpack-config-setup", "package": "@nrwl/jest",
"package": "@nrwl/webpack", "name": "update-tests-jest-29"
"name": "webpack-config-setup"
},
{
"cli": "nx",
"version": "15.7.2-beta.0",
"description": "Add the babelUpwardRootMode option to the build executor options.",
"factory": "./src/migrations/update-15-7-2/add-babelUpwardRootMode-flag",
"package": "@nrwl/webpack",
"name": "add-babelUpwardRootMode-flag"
} }
] ]
} }

7060
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -10,7 +10,7 @@
"build": "nx build", "build": "nx build",
"cf-page": "nx build cf-page", "cf-page": "nx build cf-page",
"node-vless:build": "nx build cf-page --configuration=production && nx build node-vless --configurations=production", "node-vless:build": "nx build cf-page --configuration=production && nx build node-vless --configurations=production",
"node-vless:bunled": "nx build cf-page --configuration=production && nx build node-vless --configurations=production -- --externalDependencies=none", "node-vless:bunled": "nx build cf-page --configuration=production && nx build node-vless --skip-nx-cache --configurations=production -- --externalDependencies=none",
"node-vless:start": "node dist/apps/node-vless/main.js", "node-vless:start": "node dist/apps/node-vless/main.js",
"deno-vless:bunled": "nx deno-bunled deno-vless", "deno-vless:bunled": "nx deno-bunled deno-vless",
"test": "nx test" "test": "nx test"
@@ -35,23 +35,23 @@
"devDependencies": { "devDependencies": {
"@babel/preset-react": "^7.18.6", "@babel/preset-react": "^7.18.6",
"@cloudflare/workers-types": "^4.20230221.0", "@cloudflare/workers-types": "^4.20230221.0",
"@nrwl/cli": "15.7.2", "@nrwl/cli": "15.9.2",
"@nrwl/cypress": "15.7.2", "@nrwl/cypress": "15.9.2",
"@nrwl/deno": "^0.157.0", "@nrwl/deno": "^0.157.0",
"@nrwl/eslint-plugin-nx": "15.7.2", "@nrwl/eslint-plugin-nx": "15.9.2",
"@nrwl/jest": "15.7.2", "@nrwl/jest": "15.9.2",
"@nrwl/js": "15.7.2", "@nrwl/js": "15.9.2",
"@nrwl/linter": "15.7.2", "@nrwl/linter": "15.9.2",
"@nrwl/node": "15.7.2", "@nrwl/node": "15.9.2",
"@nrwl/react": "15.7.2", "@nrwl/react": "15.9.2",
"@nrwl/vite": "15.7.2", "@nrwl/vite": "15.9.2",
"@nrwl/web": "15.7.2", "@nrwl/web": "15.9.2",
"@nrwl/webpack": "15.7.2", "@nrwl/webpack": "15.9.2",
"@nrwl/workspace": "15.7.2", "@nrwl/workspace": "15.9.2",
"@testing-library/react": "13.4.0", "@testing-library/react": "13.4.0",
"@types/jest": "28.1.1", "@types/jest": "29.4.4",
"@types/lodash": "^4.14.191", "@types/lodash": "^4.14.191",
"@types/node": "18.14.1", "@types/node": "18.15.13",
"@types/qrcode": "^1.5.0", "@types/qrcode": "^1.5.0",
"@types/react": "18.0.28", "@types/react": "18.0.28",
"@types/react-dom": "18.0.11", "@types/react-dom": "18.0.11",
@@ -71,13 +71,12 @@
"jest": "29.4.3", "jest": "29.4.3",
"jest-environment-jsdom": "29.4.3", "jest-environment-jsdom": "29.4.3",
"jsdom": "~21.1.0", "jsdom": "~21.1.0",
"nx": "15.7.2", "nx": "15.9.2",
"pkg": "^5.8.0", "pkg": "^5.8.0",
"postcss": "^8.4.21", "postcss": "^8.4.21",
"prettier": "2.8.1", "prettier": "2.8.1",
"prettier-plugin-toml": "0.3.1", "prettier-plugin-toml": "0.3.1",
"react-test-renderer": "18.2.0", "react-test-renderer": "18.2.0",
"supabase": "^1.45.3",
"tailwindcss": "^3.2.4", "tailwindcss": "^3.2.4",
"ts-jest": "29.0.5", "ts-jest": "29.0.5",
"ts-node": "10.9.1", "ts-node": "10.9.1",