feat: add trpc websocket (#205)

This commit is contained in:
Manuel
2024-03-14 18:43:47 +01:00
committed by GitHub
parent 9ae99ad06c
commit 4f375cbe6d
14 changed files with 268 additions and 32 deletions

View File

@@ -2,6 +2,7 @@
"name": "@homarr/nextjs",
"version": "0.1.0",
"private": true,
"type": "module",
"scripts": {
"build": "pnpm with-env next build",
"clean": "git clean -xdf .next .turbo node_modules",
@@ -38,7 +39,7 @@
"@tiptap/extension-link": "^2.2.4",
"@tiptap/react": "^2.2.4",
"@tiptap/starter-kit": "^2.2.4",
"@trpc/client": "next",
"@trpc/client": "11.0.0-next-beta.316",
"@trpc/next": "next",
"@trpc/react-query": "next",
"@trpc/server": "next",
@@ -63,8 +64,10 @@
"@types/react-dom": "^18.2.22",
"@types/chroma-js": "2.4.4",
"dotenv-cli": "^7.4.1",
"concurrently": "^8.2.2",
"eslint": "^8.57.0",
"prettier": "^3.2.5",
"tsx": "^4.7.1",
"typescript": "^5.4.2"
},
"eslintConfig": {

View File

@@ -5,11 +5,21 @@ import { useState } from "react";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
import { ReactQueryStreamedHydration } from "@tanstack/react-query-next-experimental";
import { loggerLink, unstable_httpBatchStreamLink } from "@trpc/client";
import {
createWSClient,
loggerLink,
unstable_httpBatchStreamLink,
wsLink,
} from "@trpc/client";
import superjson from "superjson";
import type { AppRouter } from "@homarr/api";
import { clientApi } from "@homarr/api/client";
const wsClient = createWSClient({
url: "ws://localhost:3001",
});
export function TRPCReactProvider(props: PropsWithChildren) {
const [queryClient] = useState(
() =>
@@ -22,26 +32,39 @@ export function TRPCReactProvider(props: PropsWithChildren) {
}),
);
const [trpcClient] = useState(() =>
clientApi.createClient({
const [trpcClient] = useState(() => {
return clientApi.createClient({
links: [
loggerLink({
enabled: (opts) =>
process.env.NODE_ENV === "development" ||
(opts.direction === "down" && opts.result instanceof Error),
}),
unstable_httpBatchStreamLink({
transformer: superjson,
url: getBaseUrl() + "/api/trpc",
headers() {
const headers = new Headers();
headers.set("x-trpc-source", "nextjs-react");
return headers;
},
}),
(args) => {
return ({ op, next }) => {
console.log("op", op.type, op.input, op.path, op.id);
if (op.type === "subscription") {
const link = wsLink<AppRouter>({
client: wsClient,
transformer: superjson,
});
return link(args)({ op, next });
}
return unstable_httpBatchStreamLink({
transformer: superjson,
url: `${getBaseUrl()}/api/trpc`,
headers() {
const headers = new Headers();
headers.set("x-trpc-source", "nextjs-react");
return headers;
},
})(args)({ op, next });
};
},
],
}),
);
});
});
return (
<clientApi.Provider client={trpcClient} queryClient={queryClient}>

View File

@@ -1,6 +1,8 @@
import { getScopedI18n } from "@homarr/translation/server";
import { Title } from "@homarr/ui";
import { Test } from "./test";
export async function generateMetadata() {
const t = await getScopedI18n("management");
const metaTitle = `${t("metaTitle")} • Homarr`;
@@ -24,6 +26,7 @@ export default async function ManagementPage() {
return (
<>
<Title>{t(timeOfDay, { username: "admin" })}</Title>
<Test />
</>
);
}

View File

@@ -0,0 +1,23 @@
"use client";
import { useState } from "react";
import { clientApi } from "@homarr/api/client";
import { Stack, Text } from "@homarr/ui";
export const Test = () => {
const [value, setValue] = useState<number>(0);
clientApi.user.test.useSubscription(undefined, {
onData(data) {
setValue(data);
},
onError(err) {
alert(err);
},
});
return (
<Stack>
<Text>This will change after one second: {value}</Text>
</Stack>
);
};

View File

@@ -2,6 +2,7 @@ import { fetchRequestHandler } from "@trpc/server/adapters/fetch";
import { appRouter, createTRPCContext } from "@homarr/api";
import { auth } from "@homarr/auth";
import { logger } from "@homarr/log";
/**
* Configure basic CORS headers
@@ -29,8 +30,10 @@ const handler = auth(async (req) => {
req,
createContext: () =>
createTRPCContext({ session: req.auth, headers: req.headers }),
onError({ error, path }) {
console.error(`>>> tRPC Error on '${path}'`, error);
onError({ error, path, type }) {
logger.error(
`tRPC Error with ${type} on '${path}': (${error.code}) - ${error.message}`,
);
},
});

View File

@@ -10,7 +10,9 @@
"main": "./index.ts",
"types": "./index.ts",
"license": "MIT",
"type": "module",
"scripts": {
"dev": "pnpm tsx ./src/wssDevServer.ts",
"clean": "rm -rf .turbo node_modules",
"lint": "eslint .",
"format": "prettier --check . --ignore-path ../../.gitignore",
@@ -18,17 +20,20 @@
},
"dependencies": {
"@homarr/auth": "workspace:^0.1.0",
"@homarr/definitions": "workspace:^0.1.0",
"@homarr/db": "workspace:^0.1.0",
"@homarr/definitions": "workspace:^0.1.0",
"@homarr/log": "workspace:^",
"@homarr/validation": "workspace:^0.1.0",
"@trpc/client": "next",
"@trpc/server": "next",
"superjson": "2.2.1"
"superjson": "2.2.1",
"ws": "^8.16.0"
},
"devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"@types/ws": "^8.5.10",
"eslint": "^8.57.0",
"prettier": "^3.2.5",
"typescript": "^5.4.2"

View File

@@ -1,6 +1,5 @@
import "server-only";
import { TRPCError } from "@trpc/server";
import { observable } from "@trpc/server/observable";
import { createSalt, hashPassword } from "@homarr/auth";
import type { Database } from "@homarr/db";
@@ -99,6 +98,15 @@ export const userRouter = createTRPCRouter({
})
.where(eq(users.id, input.userId));
}),
test: publicProcedure.subscription(() => {
return observable<number>((emit) => {
let counter = 0;
setInterval(() => {
counter = counter + 1;
emit.next(counter);
}, 1000);
});
}),
});
const createUser = async (

View File

@@ -12,6 +12,7 @@ import superjson from "superjson";
import type { Session } from "@homarr/auth";
import { auth } from "@homarr/auth";
import { db } from "@homarr/db";
import { logger } from "@homarr/log";
import { ZodError } from "@homarr/validation";
/**
@@ -33,7 +34,10 @@ export const createTRPCContext = async (opts: {
const session = opts.session ?? (await auth());
const source = opts.headers.get("x-trpc-source") ?? "unknown";
console.log(">>> tRPC Request from", source, "by", session?.user);
logger.info(
`tRPC request from ${source} by user '${session?.user.id}'`,
session?.user,
);
return {
session,

View File

@@ -0,0 +1,53 @@
import { applyWSSHandler } from "@trpc/server/adapters/ws";
import { WebSocketServer } from "ws";
import { logger } from "@homarr/log";
import { appRouter } from "./root";
import { createTRPCContext } from "./trpc";
const wss = new WebSocketServer({
port: 3001,
});
const handler = applyWSSHandler({
wss,
router: appRouter,
createContext: ({ req }) => {
return createTRPCContext({
headers: {
...req.headers,
get(key: string) {
const item = req.headers[key];
return typeof item === "string" ? item ?? null : item?.at(0) ?? null;
},
} as Headers,
session: {
// TODO: replace with actual session
user: {
id: "1",
name: "Test User",
email: "",
},
expires: new Date().toISOString(),
},
});
},
});
wss.on("connection", (ws, incomingMessage) => {
logger.info(
` Connection (${wss.clients.size}) ${incomingMessage.method} ${incomingMessage.url}`,
);
ws.once("close", (code, reason) => {
logger.info(
` Connection (${wss.clients.size}) ${code} ${reason.toString()}`,
);
});
});
logger.info("✅ WebSocket Server listening on ws://localhost:3001");
process.on("SIGTERM", () => {
logger.info("SIGTERM");
handler.broadcastReconnectNotification();
wss.close();
});

View File

@@ -1,6 +1,7 @@
{
"name": "@homarr/auth",
"version": "0.1.0",
"type": "module",
"exports": {
".": "./index.ts",
"./security": "./security.ts",

View File

@@ -2,6 +2,7 @@
"name": "@homarr/common",
"private": true,
"version": "0.1.0",
"type": "module",
"exports": {
".": "./index.ts"
},

View File

@@ -2,6 +2,7 @@
"name": "@homarr/definitions",
"private": true,
"version": "0.1.0",
"type": "module",
"exports": {
".": "./index.ts"
},

View File

@@ -2,6 +2,7 @@
"name": "@homarr/validation",
"private": true,
"version": "0.1.0",
"type": "module",
"exports": {
".": "./index.ts"
},

127
pnpm-lock.yaml generated
View File

@@ -211,14 +211,14 @@ importers:
specifier: ^2.2.4
version: 2.2.4(@tiptap/pm@2.2.4)
'@trpc/client':
specifier: next
version: 11.0.0-next-beta.289(@trpc/server@11.0.0-next-beta.289)
specifier: 11.0.0-next-beta.316
version: 11.0.0-next-beta.316(@trpc/server@11.0.0-next-beta.289)
'@trpc/next':
specifier: next
version: 11.0.0-next-beta.289(@tanstack/react-query@5.28.0)(@trpc/client@11.0.0-next-beta.289)(@trpc/react-query@11.0.0-next-beta.289)(@trpc/server@11.0.0-next-beta.289)(next@14.1.3)(react-dom@18.2.0)(react@18.2.0)
version: 11.0.0-next-beta.289(@tanstack/react-query@5.28.0)(@trpc/client@11.0.0-next-beta.316)(@trpc/react-query@11.0.0-next-beta.289)(@trpc/server@11.0.0-next-beta.289)(next@14.1.3)(react-dom@18.2.0)(react@18.2.0)
'@trpc/react-query':
specifier: next
version: 11.0.0-next-beta.289(@tanstack/react-query@5.28.0)(@trpc/client@11.0.0-next-beta.289)(@trpc/server@11.0.0-next-beta.289)(react-dom@18.2.0)(react@18.2.0)
version: 11.0.0-next-beta.289(@tanstack/react-query@5.28.0)(@trpc/client@11.0.0-next-beta.316)(@trpc/server@11.0.0-next-beta.289)(react-dom@18.2.0)(react@18.2.0)
'@trpc/server':
specifier: next
version: 11.0.0-next-beta.289
@@ -277,6 +277,9 @@ importers:
'@types/react-dom':
specifier: ^18.2.22
version: 18.2.22
concurrently:
specifier: ^8.2.2
version: 8.2.2
dotenv-cli:
specifier: ^7.4.1
version: 7.4.1
@@ -286,6 +289,9 @@ importers:
prettier:
specifier: ^3.2.5
version: 3.2.5
tsx:
specifier: ^4.7.1
version: 4.7.1
typescript:
specifier: ^5.4.2
version: 5.4.2
@@ -301,6 +307,9 @@ importers:
'@homarr/definitions':
specifier: workspace:^0.1.0
version: link:../definitions
'@homarr/log':
specifier: workspace:^
version: link:../log
'@homarr/validation':
specifier: workspace:^0.1.0
version: link:../validation
@@ -313,6 +322,9 @@ importers:
superjson:
specifier: 2.2.1
version: 2.2.1
ws:
specifier: ^8.16.0
version: 8.16.0
devDependencies:
'@homarr/eslint-config':
specifier: workspace:^0.2.0
@@ -323,6 +335,9 @@ importers:
'@homarr/tsconfig':
specifier: workspace:^0.1.0
version: link:../../tooling/typescript
'@types/ws':
specifier: ^8.5.10
version: 8.5.10
eslint:
specifier: ^8.57.0
version: 8.57.0
@@ -2964,7 +2979,15 @@ packages:
'@trpc/server': 11.0.0-next-beta.289
dev: false
/@trpc/next@11.0.0-next-beta.289(@tanstack/react-query@5.28.0)(@trpc/client@11.0.0-next-beta.289)(@trpc/react-query@11.0.0-next-beta.289)(@trpc/server@11.0.0-next-beta.289)(next@14.1.3)(react-dom@18.2.0)(react@18.2.0):
/@trpc/client@11.0.0-next-beta.316(@trpc/server@11.0.0-next-beta.289):
resolution: {integrity: sha512-SR5Z+LtvQ/IzY2ymSnbGYl4kB7GnqBktTH9o3YhGN2MrwgEJ9qNotVQ7wcAmhumpY26hJnXqG3hD5SZPZdeJlg==}
peerDependencies:
'@trpc/server': 11.0.0-next-beta.316+dcae8bd89
dependencies:
'@trpc/server': 11.0.0-next-beta.289
dev: false
/@trpc/next@11.0.0-next-beta.289(@tanstack/react-query@5.28.0)(@trpc/client@11.0.0-next-beta.316)(@trpc/react-query@11.0.0-next-beta.289)(@trpc/server@11.0.0-next-beta.289)(next@14.1.3)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-AKCrcbtHh/zFrld6lMG0RC37d/aac4ZisLDjJcViMnEmJXCo0J5nhoZa6f+G9N683NdMWZVmY2rmJidw9IX3QQ==}
peerDependencies:
'@tanstack/react-query': ^5.0.0
@@ -2981,15 +3004,15 @@ packages:
optional: true
dependencies:
'@tanstack/react-query': 5.28.0(react@18.2.0)
'@trpc/client': 11.0.0-next-beta.289(@trpc/server@11.0.0-next-beta.289)
'@trpc/react-query': 11.0.0-next-beta.289(@tanstack/react-query@5.28.0)(@trpc/client@11.0.0-next-beta.289)(@trpc/server@11.0.0-next-beta.289)(react-dom@18.2.0)(react@18.2.0)
'@trpc/client': 11.0.0-next-beta.316(@trpc/server@11.0.0-next-beta.289)
'@trpc/react-query': 11.0.0-next-beta.289(@tanstack/react-query@5.28.0)(@trpc/client@11.0.0-next-beta.316)(@trpc/server@11.0.0-next-beta.289)(react-dom@18.2.0)(react@18.2.0)
'@trpc/server': 11.0.0-next-beta.289
next: 14.1.3(@babel/core@7.23.9)(react-dom@18.2.0)(react@18.2.0)(sass@1.71.1)
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
dev: false
/@trpc/react-query@11.0.0-next-beta.289(@tanstack/react-query@5.28.0)(@trpc/client@11.0.0-next-beta.289)(@trpc/server@11.0.0-next-beta.289)(react-dom@18.2.0)(react@18.2.0):
/@trpc/react-query@11.0.0-next-beta.289(@tanstack/react-query@5.28.0)(@trpc/client@11.0.0-next-beta.316)(@trpc/server@11.0.0-next-beta.289)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-SAn09DmZ4eFYLS0cCHOVNvRHJhHZ2ssUj4LUTj56wym0MieaCSrcxTqiolnaMfF+mWc1SJlLOzebrxaTHPwJSw==}
peerDependencies:
'@tanstack/react-query': ^5.0.0
@@ -2999,7 +3022,7 @@ packages:
react-dom: '>=18.2.0'
dependencies:
'@tanstack/react-query': 5.28.0(react@18.2.0)
'@trpc/client': 11.0.0-next-beta.289(@trpc/server@11.0.0-next-beta.289)
'@trpc/client': 11.0.0-next-beta.316(@trpc/server@11.0.0-next-beta.289)
'@trpc/server': 11.0.0-next-beta.289
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
@@ -3321,6 +3344,12 @@ packages:
resolution: {integrity: sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==}
dev: false
/@types/ws@8.5.10:
resolution: {integrity: sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==}
dependencies:
'@types/node': 20.11.27
dev: true
/@typescript-eslint/eslint-plugin@7.2.0(@typescript-eslint/parser@7.2.0)(eslint@8.57.0)(typescript@5.4.2):
resolution: {integrity: sha512-mdekAHOqS9UjlmyF/LSs6AIEvfceV749GFxoBAjwAv0nkevfKHWQFDMcBZWUiIC5ft6ePWivXoS36aKQ0Cy3sw==}
engines: {node: ^16.0.0 || >=18.0.0}
@@ -4341,6 +4370,15 @@ packages:
resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==}
dev: false
/cliui@8.0.1:
resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
engines: {node: '>=12'}
dependencies:
string-width: 4.2.3
strip-ansi: 6.0.1
wrap-ansi: 7.0.0
dev: true
/clone@1.0.4:
resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==}
engines: {node: '>=0.8'}
@@ -4447,6 +4485,22 @@ packages:
readable-stream: 2.3.8
typedarray: 0.0.6
/concurrently@8.2.2:
resolution: {integrity: sha512-1dP4gpXFhei8IOtlXRE/T/4H88ElHgTiUzh71YUmtjTEHMSRS2Z/fgOxHSxxusGHogsRfxNq1vyAwxSC+EVyDg==}
engines: {node: ^14.13.0 || >=16.0.0}
hasBin: true
dependencies:
chalk: 4.1.2
date-fns: 2.30.0
lodash: 4.17.21
rxjs: 7.8.1
shell-quote: 1.8.1
spawn-command: 0.0.2
supports-color: 8.1.1
tree-kill: 1.2.2
yargs: 17.7.2
dev: true
/consola@2.15.3:
resolution: {integrity: sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw==}
@@ -4603,6 +4657,13 @@ packages:
whatwg-url: 14.0.0
dev: true
/date-fns@2.30.0:
resolution: {integrity: sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==}
engines: {node: '>=0.11'}
dependencies:
'@babel/runtime': 7.23.9
dev: true
/dayjs@1.11.10:
resolution: {integrity: sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==}
dev: false
@@ -5868,6 +5929,11 @@ packages:
resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
engines: {node: '>=6.9.0'}
/get-caller-file@2.0.5:
resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
engines: {node: 6.* || 8.* || >= 10.*}
dev: true
/get-func-name@2.0.2:
resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==}
dev: true
@@ -8439,6 +8505,11 @@ packages:
engines: {node: '>=0.10'}
dev: true
/require-directory@2.1.1:
resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
engines: {node: '>=0.10.0'}
dev: true
/require-from-string@2.0.2:
resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==}
engines: {node: '>=0.10.0'}
@@ -8730,6 +8801,10 @@ packages:
resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
engines: {node: '>=8'}
/shell-quote@1.8.1:
resolution: {integrity: sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==}
dev: true
/shelljs@0.8.5:
resolution: {integrity: sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==}
engines: {node: '>=4'}
@@ -8847,6 +8922,10 @@ packages:
engines: {node: '>= 8'}
dev: true
/spawn-command@0.0.2:
resolution: {integrity: sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ==}
dev: true
/sprintf-js@1.1.3:
resolution: {integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==}
dev: true
@@ -9377,6 +9456,17 @@ packages:
engines: {node: '>=0.6.x'}
dev: false
/tsx@4.7.1:
resolution: {integrity: sha512-8d6VuibXHtlN5E3zFkgY8u4DX7Y3Z27zvvPKVmLon/D4AjuKzarkUBTLDBgj9iTQ0hg5xM7c/mYiRVM+HETf0g==}
engines: {node: '>=18.0.0'}
hasBin: true
dependencies:
esbuild: 0.19.12
get-tsconfig: 4.7.2
optionalDependencies:
fsevents: 2.3.3
dev: true
/tunnel-agent@0.6.0:
resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==}
dependencies:
@@ -10133,7 +10223,6 @@ packages:
optional: true
utf-8-validate:
optional: true
dev: true
/xml-name-validator@5.0.0:
resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==}
@@ -10148,6 +10237,11 @@ packages:
resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}
engines: {node: '>=0.4'}
/y18n@5.0.8:
resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
engines: {node: '>=10'}
dev: true
/yallist@3.1.1:
resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
@@ -10159,6 +10253,19 @@ packages:
engines: {node: '>=12'}
dev: true
/yargs@17.7.2:
resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==}
engines: {node: '>=12'}
dependencies:
cliui: 8.0.1
escalade: 3.1.2
get-caller-file: 2.0.5
require-directory: 2.1.1
string-width: 4.2.3
y18n: 5.0.8
yargs-parser: 21.1.1
dev: true
/yn@3.1.1:
resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==}
engines: {node: '>=6'}