diff --git a/cloudformation/iam.yml b/cloudformation/iam.yml index f7d62d76..40474d00 100644 --- a/cloudformation/iam.yml +++ b/cloudformation/iam.yml @@ -79,6 +79,8 @@ Resources: - Fn::Sub: arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/infra-core-api-room-requests-status - Fn::Sub: arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/infra-core-api-linkry - Fn::Sub: arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/infra-core-api-keys + - Fn::Sub: arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/infra-core-api-sig-member-details + - Fn::Sub: arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/infra-core-api-sig-details # Index accesses - Fn::Sub: arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/infra-core-api-stripe-links/index/* - Fn::Sub: arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/infra-core-api-events/index/* @@ -100,6 +102,14 @@ Resources: Resource: - Fn::Sub: arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/infra-core-api-cache + - Sid: DynamoDBRateLimitTableAccess + Effect: Allow + Action: + - dynamodb:DescribeTable + - dynamodb:UpdateItem + Resource: + - Fn::Sub: arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/infra-core-api-rate-limiter + - Sid: DynamoDBAuditLogTableAccess Effect: Allow Action: diff --git a/src/api/functions/entraId.ts b/src/api/functions/entraId.ts index c134775c..94be2709 100644 --- a/src/api/functions/entraId.ts +++ b/src/api/functions/entraId.ts @@ -33,7 +33,7 @@ import { checkPaidMembershipFromTable } from "./membership.js"; import type pino from "pino"; import { type FastifyBaseLogger } from "fastify"; -function validateGroupId(groupId: string): boolean { +export function validateGroupId(groupId: string): boolean { const groupIdPattern = /^[a-zA-Z0-9-]+$/; // Adjust the pattern as needed return groupIdPattern.test(groupId); } diff --git a/src/api/functions/siglead.ts b/src/api/functions/siglead.ts new file mode 100644 index 00000000..e803ae64 --- /dev/null +++ b/src/api/functions/siglead.ts @@ -0,0 +1,172 @@ +import { + AttributeValue, + DynamoDBClient, + GetItemCommand, + PutItemCommand, + PutItemCommandInput, + QueryCommand, + ScanCommand, +} from "@aws-sdk/client-dynamodb"; +import { unmarshall } from "@aws-sdk/util-dynamodb"; +import { DatabaseInsertError } from "../../common/errors/index.js"; +import { OrganizationList, orgIds2Name } from "../../common/orgs.js"; +import { + SigDetailRecord, + SigMemberCount, + SigMemberRecord, + SigMemberUpdateRecord, +} from "common/types/siglead.js"; +import { transformSigLeadToURI } from "common/utils.js"; +import { KeyObject } from "crypto"; +import { string } from "zod"; + +export async function fetchMemberRecords( + sigid: string, + tableName: string, + dynamoClient: DynamoDBClient, +) { + const fetchSigMemberRecords = new QueryCommand({ + TableName: tableName, + KeyConditionExpression: "#sigid = :accessVal", + ExpressionAttributeNames: { + "#sigid": "sigGroupId", + }, + ExpressionAttributeValues: { + ":accessVal": { S: sigid }, + }, + ScanIndexForward: false, + }); + + const result = await dynamoClient.send(fetchSigMemberRecords); + + // Process the results + return (result.Items || []).map((item) => { + const unmarshalledItem = unmarshall(item); + return unmarshalledItem as SigMemberRecord; + }); +} + +export async function fetchSigDetail( + sigid: string, + tableName: string, + dynamoClient: DynamoDBClient, +) { + const fetchSigDetail = new QueryCommand({ + TableName: tableName, + KeyConditionExpression: "#sigid = :accessVal", + ExpressionAttributeNames: { + "#sigid": "sigid", + }, + ExpressionAttributeValues: { + ":accessVal": { S: sigid }, + }, + ScanIndexForward: false, + }); + + const result = await dynamoClient.send(fetchSigDetail); + + // Process the results + return (result.Items || [{}]).map((item) => { + const unmarshalledItem = unmarshall(item); + + // Strip '#' from access field + delete unmarshalledItem.leadGroupId; + delete unmarshalledItem.memberGroupId; + + return unmarshalledItem as SigDetailRecord; + })[0]; +} + +// select count(sigid) +// from table +// groupby sigid +export async function fetchSigCounts( + sigMemberTableName: string, + dynamoClient: DynamoDBClient, +) { + const scan = new ScanCommand({ + TableName: sigMemberTableName, + ProjectionExpression: "sigGroupId", + }); + + const result = await dynamoClient.send(scan); + + const counts: Record = {}; + // Object.entries(orgIds2Name).forEach(([id, _]) => { + // counts[id] = 0; + // }); + + (result.Items || []).forEach((item) => { + const sigGroupId = item.sigGroupId?.S; + if (sigGroupId) { + counts[sigGroupId] = (counts[sigGroupId] || 0) + 1; + } + }); + + const countsArray: SigMemberCount[] = Object.entries(counts).map( + ([id, count]) => ({ + sigid: id, + signame: orgIds2Name[id], + count, + }), + ); + console.log(countsArray); + return countsArray; +} + +export async function addMemberToSigDynamo( + sigMemberTableName: string, + sigMemberUpdateRequest: SigMemberUpdateRecord, + dynamoClient: DynamoDBClient, +) { + const item: Record = {}; + Object.entries(sigMemberUpdateRequest).forEach(([k, v]) => { + item[k] = { S: v }; + }); + + // put into table + const put = new PutItemCommand({ + Item: item, + ReturnConsumedCapacity: "TOTAL", + TableName: sigMemberTableName, + }); + try { + const response = await dynamoClient.send(put); + console.log(response); + } catch (e) { + console.error("Put to dynamo db went wrong."); + throw e; + } + + // fetch from db and check if fetched item update time = input item update time + const validatePutQuery = new GetItemCommand({ + TableName: sigMemberTableName, + Key: { + sigGroupId: { S: sigMemberUpdateRequest.sigGroupId }, + email: { S: sigMemberUpdateRequest.email }, + }, + ProjectionExpression: "updatedAt", + }); + + try { + const response = await dynamoClient.send(validatePutQuery); + const item = response.Item; + + if (!item || !item.updatedAt?.S) { + throw new Error("Item not found or missing 'updatedAt'"); + } + + if (item.updatedAt.S !== sigMemberUpdateRequest.updatedAt) { + throw new DatabaseInsertError({ + message: "The member exists, but was updated by someone else!", + }); + } + } catch (e) { + console.error("Validate DynamoDB get went wrong.", e); + throw e; + } +} + +export async function addMemberToSigEntra() { + // uuid validation not implemented yet +} diff --git a/src/api/index.ts b/src/api/index.ts index d82be73f..28aaca8b 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -26,6 +26,7 @@ import * as dotenv from "dotenv"; import iamRoutes from "./routes/iam.js"; import ticketsPlugin from "./routes/tickets.js"; import linkryRoutes from "./routes/linkry.js"; +import sigleadRoutes from "./routes/siglead.js"; import { STSClient, GetCallerIdentityCommand } from "@aws-sdk/client-sts"; import NodeCache from "node-cache"; import { DynamoDBClient } from "@aws-sdk/client-dynamodb"; @@ -309,6 +310,7 @@ async function init(prettyPrint: boolean = false) { api.register(linkryRoutes, { prefix: "/linkry" }); api.register(mobileWalletRoute, { prefix: "/mobileWallet" }); api.register(stripeRoutes, { prefix: "/stripe" }); + api.register(sigleadRoutes, { prefix: "/siglead" }); api.register(roomRequestRoutes, { prefix: "/roomRequests" }); api.register(logsPlugin, { prefix: "/logs" }); api.register(apiKeyRoute, { prefix: "/apiKey" }); diff --git a/src/api/routes/siglead.ts b/src/api/routes/siglead.ts new file mode 100644 index 00000000..4b5283ee --- /dev/null +++ b/src/api/routes/siglead.ts @@ -0,0 +1,149 @@ +import { FastifyPluginAsync } from "fastify"; +import { DatabaseFetchError } from "../../common/errors/index.js"; + +import { genericConfig } from "../../common/config.js"; + +import { + SigDetailRecord, + SigleadGetRequest, + SigMemberCount, + SigMemberRecord, + SigMemberUpdateRecord, +} from "common/types/siglead.js"; +import { + addMemberToSigDynamo, + fetchMemberRecords, + fetchSigCounts, + fetchSigDetail, +} from "../../api/functions/siglead.js"; + +const sigleadRoutes: FastifyPluginAsync = async (fastify, _options) => { + const limitedRoutes: FastifyPluginAsync = async (fastify) => { + /*fastify.register(rateLimiter, { + limit: 30, + duration: 60, + rateLimitIdentifier: "linkry", + });*/ + + fastify.get( + "/sigmembers/:sigid", + { + onRequest: async (request, reply) => { + /*await fastify.authorize(request, reply, [ + AppRoles.LINKS_MANAGER, + AppRoles.LINKS_ADMIN, + ]);*/ + }, + }, + async (request, reply) => { + const { sigid } = request.params; + const tableName = genericConfig.SigleadDynamoSigMemberTableName; + + // First try-catch: Fetch owner records + let memberRecords: SigMemberRecord[]; + try { + memberRecords = await fetchMemberRecords( + sigid, + tableName, + fastify.dynamoClient, + ); + } catch (error) { + request.log.error( + `Failed to fetch member records: ${error instanceof Error ? error.toString() : "Unknown error"}`, + ); + throw new DatabaseFetchError({ + message: "Failed to fetch member records from Dynamo table.", + }); + } + + // Send the response + reply.code(200).send(memberRecords); + }, + ); + + fastify.get( + "/sigdetail/:sigid", + { + onRequest: async (request, reply) => { + /*await fastify.authorize(request, reply, [ + AppRoles.LINKS_MANAGER, + AppRoles.LINKS_ADMIN, + ]);*/ + }, + }, + async (request, reply) => { + const { sigid } = request.params; + const tableName = genericConfig.SigleadDynamoSigDetailTableName; + + // First try-catch: Fetch owner records + let sigDetail: SigDetailRecord; + try { + sigDetail = await fetchSigDetail( + sigid, + tableName, + fastify.dynamoClient, + ); + } catch (error) { + request.log.error( + `Failed to fetch sig detail record: ${error instanceof Error ? error.toString() : "Unknown error"}`, + ); + throw new DatabaseFetchError({ + message: "Failed to fetch sig detail record from Dynamo table.", + }); + } + + // Send the response + reply.code(200).send(sigDetail); + }, + ); + + // fetch sig count + fastify.get("/sigcount", async (request, reply) => { + // First try-catch: Fetch owner records + let sigMemCounts: SigMemberCount[]; + try { + sigMemCounts = await fetchSigCounts( + genericConfig.SigleadDynamoSigMemberTableName, + fastify.dynamoClient, + ); + } catch (error) { + request.log.error( + `Failed to fetch sig member counts record: ${error instanceof Error ? error.toString() : "Unknown error"}`, + ); + throw new DatabaseFetchError({ + message: + "Failed to fetch sig member counts record from Dynamo table.", + }); + } + + // Send the response + reply.code(200).send(sigMemCounts); + }); + + // add member + fastify.post<{ Body: SigMemberUpdateRecord }>( + "/addMember", + async (request, reply) => { + try { + await addMemberToSigDynamo( + genericConfig.SigleadDynamoSigMemberTableName, + request.body, + fastify.dynamoClient, + ); + } catch (error) { + request.log.error( + `Failed to add member: ${error instanceof Error ? error.toString() : "Unknown error"}`, + ); + throw new DatabaseFetchError({ + message: "Failed to add sig member record to Dynamo table.", + }); + } + reply.code(200); + }, + ); + }; + + fastify.register(limitedRoutes); +}; + +export default sigleadRoutes; diff --git a/src/common/config.ts b/src/common/config.ts index 38c7e959..e64fe9fb 100644 --- a/src/common/config.ts +++ b/src/common/config.ts @@ -52,6 +52,8 @@ export type GenericConfigType = { EntraReadOnlySecretName: string; AuditLogTable: string; ApiKeyTable: string; + SigleadDynamoSigDetailTableName: string; + SigleadDynamoSigMemberTableName: string; ConfigSecretName: string; TestingCredentialsSecret: string; }; @@ -68,6 +70,8 @@ export const execCouncilTestingGroupId = "dbe18eb2-9675-46c4-b1ef-749a6db4fedd"; export const commChairsTestingGroupId = "d714adb7-07bb-4d4d-a40a-b035bc2a35a3"; export const commChairsGroupId = "105e7d32-7289-435e-a67a-552c7f215507"; +export const orgsGroupId = "0b3be7c2-748e-46ce-97e7-cf86f9ca7337"; + const genericConfig: GenericConfigType = { EventsDynamoTableName: "infra-core-api-events", StripeLinksDynamoTableName: "infra-core-api-stripe-links", @@ -90,6 +94,8 @@ const genericConfig: GenericConfigType = { RoomRequestsStatusTableName: "infra-core-api-room-requests-status", AuditLogTable: "infra-core-api-audit-log", ApiKeyTable: "infra-core-api-keys", + SigleadDynamoSigDetailTableName: "infra-core-api-sig-details", + SigleadDynamoSigMemberTableName: "infra-core-api-sig-member-details", ConfigSecretName: "infra-core-api-config", TestingCredentialsSecret: "infra-core-api-testing-credentials", } as const; diff --git a/src/common/orgs.ts b/src/common/orgs.ts new file mode 100644 index 00000000..becff6fc --- /dev/null +++ b/src/common/orgs.ts @@ -0,0 +1,39 @@ +import { transformSigLeadToURI } from "./utils.js"; + +export const SIGList = [ + "SIGPwny", + "SIGCHI", + "GameBuilders", + "SIGAIDA", + "SIGGRAPH", + "SIGICPC", + "SIGMobile", + "SIGMusic", + "GLUG", + "SIGNLL", + "SIGma", + "SIGQuantum", + "SIGecom", + "SIGPLAN", + "SIGPolicy", + "SIGARCH", + "SIGRobotics", + "SIGtricity", +] as [string, ...string[]]; + +export const CommitteeList = [ + "Infrastructure Committee", + "Social Committee", + "Mentorship Committee", + "Academic Committee", + "Corporate Committee", + "Marketing Committee", +] as [string, ...string[]]; +export const OrganizationList = ["ACM", ...SIGList, ...CommitteeList] as [string, ...string[]]; + +const orgIds2Name: Record = {}; +OrganizationList.forEach((org) => { + const sigid = transformSigLeadToURI(org); + orgIds2Name[sigid] = org; +}); +export { orgIds2Name }; \ No newline at end of file diff --git a/src/common/roles.ts b/src/common/roles.ts index cb99cf6f..acbeee73 100644 --- a/src/common/roles.ts +++ b/src/common/roles.ts @@ -3,6 +3,7 @@ export const runEnvironments = ["dev", "prod"] as const; export type RunEnvironment = (typeof runEnvironments)[number]; export enum AppRoles { EVENTS_MANAGER = "manage:events", + SIGLEAD_MANAGER = "manage:siglead", TICKETS_SCANNER = "scan:tickets", TICKETS_MANAGER = "manage:tickets", IAM_ADMIN = "admin:iam", @@ -17,5 +18,8 @@ export enum AppRoles { MANAGE_ORG_API_KEYS = "manage:orgApiKey" } export const allAppRoles = Object.values(AppRoles).filter( - (value) => typeof value === "string", + (value) => typeof value === "string", ); + + + \ No newline at end of file diff --git a/src/common/types/siglead.ts b/src/common/types/siglead.ts new file mode 100644 index 00000000..9da5b696 --- /dev/null +++ b/src/common/types/siglead.ts @@ -0,0 +1,34 @@ +export type SigDetailRecord = { + sigid: string; + signame: string; + description: string; +}; + +export type SigMemberRecord = { + sigGroupId: string; + email: string; + designation: string; + memberName: string; +}; + +export type SigleadGetRequest = { + Params: { sigid: string }; + Querystring: undefined; + Body: undefined; +}; + +export type SigMemberCount = { + sigid: string; + signame: string; + count: number; +}; + +export type SigMemberUpdateRecord = { + sigGroupId: string; + email: string; + id: string; + memberName: string; + designation: string; + createdAt: string; + updatedAt: string; +} \ No newline at end of file diff --git a/src/common/utils.ts b/src/common/utils.ts index 786c998f..5efddccc 100644 --- a/src/common/utils.ts +++ b/src/common/utils.ts @@ -12,3 +12,60 @@ export function transformCommaSeperatedName(name: string) { } return name; } + +const notUnreservedCharsRegex = /[^a-zA-Z0-9\-._~]/g; +const reservedCharsRegex = /[:\/?#\[\]@!$&'()*+,;=]/g; +/** + * Transforms an organization name (sig lead) into a URI-friendly format. + * The function performs the following transformations: + * - Removes characters that are reserved or not unreserved. + * - Adds spaces between camel case words. + * - Converts reserved characters to spaces. + * - Converts all characters to lowercase and replaces all types of whitespace with hyphens. + * - Replaces any sequence of repeated hyphens with a single hyphen. + * - Refer to RFC 3986 https://datatracker.ietf.org/doc/html/rfc3986#section-2.3 + * + * @param {string} org - The organization (sig lead) name to be transformed. + * @returns {string} - The transformed organization name, ready for use as a URL. + */ +export function transformSigLeadToURI(org: string) { + // console.log(`org\t${org}`) + org = org + // change not reserved chars to spaces + .trim() + .replace(notUnreservedCharsRegex, " ") + .trim() + .replace(/\s/g, "-") + + // remove all that is reserved or not unreserved + .replace(reservedCharsRegex, "") + + // convert SIG -> sig for camel case + .replace(/SIG/g, "sig") + + // add hyphen for camel case + .replace(/([a-z])([A-Z])/g, "$1-$2") + + // lower + .toLowerCase() + + // add spaces between chars and numbers (seq2seq -> seq-2-seq) + .replace(/(?<=[a-z])([0-9]+)(?=[a-z])/g, "-$1-") + + // remove duplicate hyphens + .replace(/-{2,}/g, "-"); + + return org === "-" ? "" : org; +} + +export function getTimeInFormat() { + const date = new Date(); + const year = date.getUTCFullYear(); + const month = String(date.getUTCMonth() + 1).padStart(2, '0'); + const day = String(date.getUTCDate()).padStart(2, '0'); + const hours = String(date.getUTCHours()).padStart(2, '0'); + const minutes = String(date.getUTCMinutes()).padStart(2, '0'); + const seconds = String(date.getUTCSeconds()).padStart(2, '0'); + + return `${year}-${month}-${day}T${hours}:${minutes}:${seconds}Z`; +} diff --git a/src/ui/Router.tsx b/src/ui/Router.tsx index 0276bb31..28927859 100644 --- a/src/ui/Router.tsx +++ b/src/ui/Router.tsx @@ -5,6 +5,7 @@ import { RouterProvider, useLocation, } from "react-router-dom"; + import { AcmAppShell } from "./components/AppShell"; import { useAuth } from "./components/AuthContext"; import AuthCallback from "./components/AuthContext/AuthCallbackHandler.page"; @@ -24,6 +25,12 @@ import { ManageIamPage } from "./pages/iam/ManageIam.page"; import { ManageProfilePage } from "./pages/profile/ManageProfile.page"; import { ManageStripeLinksPage } from "./pages/stripe/ViewLinks.page"; import { ManageRoomRequestsPage } from "./pages/roomRequest/RoomRequestLanding.page"; +import { EditSigLeadsPage } from "./pages/siglead/EditSigLeads.page"; +import { ManageSigLeadsPage } from "./pages/siglead/ManageSigLeads.page"; +import { + AddMemberToSigPage, + ViewSigLeadPage, +} from "./pages/siglead/ViewSigLead.page"; import { ViewRoomRequest } from "./pages/roomRequest/ViewRoomRequest.page"; import { ViewLogsPage } from "./pages/logs/ViewLogs.page"; import { TermsOfService } from "./pages/tos/TermsOfService.page"; @@ -187,6 +194,22 @@ const authenticatedRouter = createBrowserRouter([ path: "/stripe", element: , }, + { + path: "/siglead-management", + element: , + }, + { + path: "/siglead-management/edit", + element: , + }, + { + path: "/siglead-management/:sigId", + element: , + }, + { + path: "/siglead-management/:sigId/addMember", + element: , + }, { path: "/roomRequests", element: , diff --git a/src/ui/components/AppShell/index.tsx b/src/ui/components/AppShell/index.tsx index 335f0b00..59ca6bfe 100644 --- a/src/ui/components/AppShell/index.tsx +++ b/src/ui/components/AppShell/index.tsx @@ -21,6 +21,7 @@ import { IconDoor, IconHistory, IconKey, + IconUsers, } from "@tabler/icons-react"; import { ReactNode } from "react"; import { useNavigate } from "react-router-dom"; @@ -76,6 +77,13 @@ export const navItems = [ description: null, validRoles: [AppRoles.ROOM_REQUEST_CREATE, AppRoles.ROOM_REQUEST_UPDATE], }, + { + link: "/siglead-management", + name: "SigLead", + icon: IconUsers, + description: null, + validRoles: [AppRoles.SIGLEAD_MANAGER], + }, { link: "/linkry", name: "Link Shortener", diff --git a/src/ui/pages/siglead/EditSigLeads.page.tsx b/src/ui/pages/siglead/EditSigLeads.page.tsx new file mode 100644 index 00000000..c5c03d38 --- /dev/null +++ b/src/ui/pages/siglead/EditSigLeads.page.tsx @@ -0,0 +1,116 @@ +import { Title, TextInput, Button, Container, Group } from "@mantine/core"; +import { useForm, zodResolver } from "@mantine/form"; +import { notifications } from "@mantine/notifications"; +import React, { useState } from "react"; +import { useNavigate } from "react-router-dom"; +import { z } from "zod"; +import { AuthGuard } from "@ui/components/AuthGuard"; +import { useApi } from "@ui/util/api"; +import { AppRoles } from "@common/roles"; +import { transformSigLeadToURI } from "@common/utils"; + +const baseSigSchema = z.object({ + signame: z + .string() + .min(1, "Title is required") + .regex( + /^[a-zA-Z0-9]+$/, + "Sig name should only contain alphanumeric characters", + ), + description: z.string().min(1, "Description is required"), +}); + +type SigPostRequest = z.infer; + +export const EditSigLeadsPage: React.FC = () => { + const [isSubmitting, setIsSubmitting] = useState(false); + const navigate = useNavigate(); + const api = useApi("core"); + + const form = useForm({ + validate: zodResolver(baseSigSchema), + initialValues: { + signame: "", + description: "", + }, + }); + + const checkSigId = async (signame: string) => { + try { + const sigid = transformSigLeadToURI(signame); + const result = await api.get(`/api/v1/siglead/sigdetail/${sigid}`); + return result.data; + } catch (error) { + console.error("Error validating if sigid already exists", error); + notifications.show({ + message: `Error validating if sigid already exists`, + }); + } + }; + + const handleSubmit = async (sigdetails: SigPostRequest) => { + try { + setIsSubmitting(true); + const found = await checkSigId(sigdetails.signame); + if (found) { + form.setErrors({ + signame: "This signame is reserved already.", + }); + setIsSubmitting(false); + return; + } + notifications.show({ + message: `This will eventually make to a post request with signame: + ${sigdetails.signame} and description: ${sigdetails.description} + `, + }); + //Post... + navigate("/siglead-management"); + } catch (error) { + setIsSubmitting(false); + console.error("Error creating sig:", error); + notifications.show({ + message: "Failed to create sig, please try again.", + }); + } + }; + + return ( + + + Registering a new Sig +
+ + + + + + + +
+
+ ); +}; diff --git a/src/ui/pages/siglead/ManageSigLeads.page.tsx b/src/ui/pages/siglead/ManageSigLeads.page.tsx new file mode 100644 index 00000000..ced9bccd --- /dev/null +++ b/src/ui/pages/siglead/ManageSigLeads.page.tsx @@ -0,0 +1,67 @@ +import { Title, Button, Container, Group } from "@mantine/core"; +import { useDisclosure } from "@mantine/hooks"; +import { DateTimePicker } from "@mantine/dates"; +import { useForm, zodResolver } from "@mantine/form"; +import { notifications } from "@mantine/notifications"; +import dayjs from "dayjs"; +import React, { useEffect, useState } from "react"; +import { useNavigate, useParams } from "react-router-dom"; +import { z } from "zod"; +import { AuthGuard } from "@ui/components/AuthGuard"; +import { getRunEnvironmentConfig } from "@ui/config"; +import { useApi } from "@ui/util/api"; +import { OrganizationList as orgList } from "@common/orgs"; +import { AppRoles } from "@common/roles"; +import { ScreenComponent } from "./SigScreenComponents"; +import { transformSigLeadToURI } from "@common/utils"; +import { + SigDetailRecord, + SigleadGetRequest, + SigMemberCount, + SigMemberRecord, +} from "@common/types/siglead"; + +export const ManageSigLeadsPage: React.FC = () => { + const [SigMemberCounts, setSigMemberCounts] = useState([]); + const navigate = useNavigate(); + const api = useApi("core"); + + useEffect(() => { + const getMemberCounts = async () => { + try { + const sigMemberCountsRequest = await api.get( + `/api/v1/siglead/sigcount`, + ); + setSigMemberCounts(sigMemberCountsRequest.data); + } catch (error) { + console.error("Error fetching sig member counts:", error); + notifications.show({ + message: "Failed to fetch sig member counts, please try again.", + }); + } + }; + getMemberCounts(); + }, []); + + return ( + + + + SigLead Management System + + + + + {/* */} + + + ); +}; diff --git a/src/ui/pages/siglead/SigScreenComponents.tsx b/src/ui/pages/siglead/SigScreenComponents.tsx new file mode 100644 index 00000000..50d45eaf --- /dev/null +++ b/src/ui/pages/siglead/SigScreenComponents.tsx @@ -0,0 +1,76 @@ +import React, { useEffect, useMemo, useState } from "react"; +import { OrganizationList } from "@common/orgs"; +import { NavLink, Paper } from "@mantine/core"; +import { IconUsersGroup } from "@tabler/icons-react"; +import { useNavigate } from "react-router-dom"; +import { SigMemberCount } from "@common/types/siglead"; + +const renderSigLink = (sigMemCount: SigMemberCount, index: number) => { + const navigate = useNavigate(); + + const color = + "light-dark(var(--mantine-color-black), var(--mantine-color-white))"; + const size = "18px"; + const name = sigMemCount.signame; + const id = sigMemCount.sigid; + const count = sigMemCount.count; + return ( + navigate(`./${id}`)} + active={index % 2 === 0} + label={name} + color="var(--mantine-color-blue-light)" + variant="filled" + rightSection={ +
+ {count} + +
+ } + styles={{ + label: { + color: `${color}`, + fontSize: `${size}`, + }, + }} + /> + ); +}; + +type props = { + SigMemberCounts: SigMemberCount[]; +}; + +export const ScreenComponent: React.FC = ({ SigMemberCounts }) => { + return ( + <> + + Organization + Member Count + + {/* {OrganizationList.map(renderSigLink)} */} + {SigMemberCounts.map(renderSigLink)} + + ); +}; diff --git a/src/ui/pages/siglead/ViewSigLead.page.tsx b/src/ui/pages/siglead/ViewSigLead.page.tsx new file mode 100644 index 00000000..5e5db651 --- /dev/null +++ b/src/ui/pages/siglead/ViewSigLead.page.tsx @@ -0,0 +1,266 @@ +import { + Title, + Box, + Button, + Container, + Transition, + useMantineColorScheme, + Table, + Group, + Stack, +} from "@mantine/core"; + +import { notifications } from "@mantine/notifications"; +import React, { FC, useEffect, useState } from "react"; +import { useNavigate, useParams } from "react-router-dom"; +import { AuthGuard } from "@ui/components/AuthGuard"; +import { useApi } from "@ui/util/api"; +import { AppRoles } from "@common/roles"; +import { + SigDetailRecord, + SigMemberRecord, + SigMemberUpdateRecord, +} from "@common/types/siglead.js"; +import { getTimeInFormat } from "@common/utils"; +import { orgIds2Name } from "@common/orgs"; + +export const ViewSigLeadPage: React.FC = () => { + const navigate = useNavigate(); + const api = useApi("core"); + const { colorScheme } = useMantineColorScheme(); + const { sigId } = useParams(); + const [sigMembers, setSigMembers] = useState([]); + const [sigDetails, setSigDetails] = useState({ + sigid: sigId || "", + signame: "Default Sig", + description: + "A cool Sig with a lot of money and members. Founded in 1999 by Sir Charlie of Edinburgh. Focuses on making money and helping others earn more money via education.", + }); + + useEffect(() => { + // Fetch sig data and populate form + const getSig = async () => { + try { + /*const formValues = { + }; + form.setValues(formValues);*/ + const sigMemberRequest = await api.get( + `/api/v1/siglead/sigmembers/${sigId}`, + ); + setSigMembers(sigMemberRequest.data); + + const sigDetailRequest = await api.get( + `/api/v1/siglead/sigdetail/${sigId}`, + ); + setSigDetails(sigDetailRequest.data); + } catch (error) { + console.error("Error fetching sig data:", error); + notifications.show({ + message: "Failed to fetch sig data, please try again.", + }); + } + }; + getSig(); + }, [sigId]); + + const renderSigMember = (member: SigMemberRecord, index: number) => { + const shouldShow = true; + return ( + + {(styles) => ( + + {member.memberName} + {member.email} + {member.designation} + + )} + + ); + }; + + /* + const form = useForm({ + validate: zodResolver(requestBodySchema), + initialValues: { + title: '', + description: '', + start: new Date(), + end: new Date(new Date().valueOf() + 3.6e6), // 1 hr later + location: 'ACM Room (Siebel CS 1104)', + locationLink: 'https://maps.app.goo.gl/dwbBBBkfjkgj8gvA8', + host: 'ACM', + featured: false, + repeats: undefined, + repeatEnds: undefined, + paidEventId: undefined, + }, + }); + /* + const handleSubmit = async (values: EventPostRequest) => { + try { + setIsSubmitting(true); + const realValues = { + ...values, + start: dayjs(values.start).format('YYYY-MM-DD[T]HH:mm:00'), + end: values.end ? dayjs(values.end).format('YYYY-MM-DD[T]HH:mm:00') : undefined, + repeatEnds: + values.repeatEnds && values.repeats + ? dayjs(values.repeatEnds).format('YYYY-MM-DD[T]HH:mm:00') + : undefined, + repeats: values.repeats ? values.repeats : undefined, + }; + + const eventURL = isEditing ? `/api/v1/events/${eventId}` : '/api/v1/events'; + const response = await api.post(eventURL, realValues); + notifications.show({ + title: isEditing ? 'Event updated!' : 'Event created!', + message: isEditing ? undefined : `The event ID is "${response.data.id}".`, + }); + navigate('/events/manage'); + } catch (error) { + setIsSubmitting(false); + console.error('Error creating/editing event:', error); + notifications.show({ + message: 'Failed to create/edit event, please try again.', + }); + } + };*/ + + return ( + + + + + {sigDetails.signame} + {sigDetails.description || ""} + + + + + + + + + + +
+ + + + Name + Email + Roles + + + + {sigMembers.length > 0 ? sigMembers.map(renderSigMember) : <>} + +
+
+
+
+ ); +}; + +export const AddMemberToSigPage: FC = () => { + // const { sigId } = useParams(); + const api = useApi("core"); + + async function handleSubmit(event: React.FormEvent) { + event.preventDefault(); + const formData = new FormData(event.currentTarget); + // console.log(formData) + const data = Object.fromEntries(formData.entries()) as { + groupid: string; + aid: string; + rid: string; + }; + + try { + const response = await api.patch(`/api/v1/iam/groups/${data.groupid}`, { + add: data.aid !== "" ? [data.aid] : [], + remove: data.rid !== "" ? [data.rid] : [], + }); + + console.warn(`GRAPH API RESPONSE: ${response}`); + notifications.show({ + message: JSON.stringify(response), + }); + } catch (error) { + notifications.show({ + message: JSON.stringify(error), + }); + } + + // console.log(response); + } + + // async function testAddGroup() { + // await api.patch( + // `/api/v1/iam/groups/:e37a2420-1030-48da-9d17-f7e201b446e1`, + // { add: ["d115c8cb-2520-4ba4-bc36-dd55af69c590"], remove: [] }, + // ); + // } + + return ( + +
+ + +
+ + +
+ + +
+ +
+
+ ); +}; diff --git a/tests/unit/common/utils.test.ts b/tests/unit/common/utils.test.ts index 15177175..e22d642c 100644 --- a/tests/unit/common/utils.test.ts +++ b/tests/unit/common/utils.test.ts @@ -1,5 +1,5 @@ import { expect, test, describe } from "vitest"; -import { transformCommaSeperatedName } from "../../../src/common/utils.js"; +import { transformCommaSeperatedName, transformSigLeadToURI } from "../../../src/common/utils.js"; describe("Comma-seperated name transformer tests", () => { test("Already-transformed names are returned as-is", () => { @@ -27,3 +27,146 @@ describe("Comma-seperated name transformer tests", () => { expect(output).toEqual(", Test"); }); }); + +describe("transformSigLeadToURI tests", () => { + + // Basic Functionality Tests + test("should convert simple names with spaces to lowercase hyphenated", () => { + const output = transformSigLeadToURI("SIG Network"); + expect(output).toEqual("sig-network"); + }); + + test("should convert simple names to lowercase", () => { + const output = transformSigLeadToURI("Testing"); + expect(output).toEqual("testing"); + }); + + test("should handle names already in the desired format", () => { + const output = transformSigLeadToURI("already-transformed-name"); + expect(output).toEqual("already-transformed-name"); + }); + + // Camel Case Tests + test("should add hyphens between camelCase words", () => { + const output = transformSigLeadToURI("SIGAuth"); + expect(output).toEqual("sig-auth"); + }); + + test("should handle multiple camelCase words", () => { + const output = transformSigLeadToURI("SuperCamelCaseProject"); + expect(output).toEqual("super-camel-case-project"); + }); + + test("should handle mixed camelCase and spaces", () => { + const output = transformSigLeadToURI("SIG ContribEx"); // SIG Contributor Experience + expect(output).toEqual("sig-contrib-ex"); + }); + + test("should handle camelCase starting with lowercase", () => { + const output = transformSigLeadToURI("myCamelCaseName"); + expect(output).toEqual("my-camel-case-name"); + }); + + // Reserved Character Tests (RFC 3986 gen-delims and sub-delims) + test("should convert reserved characters like & to hyphens", () => { + const output = transformSigLeadToURI("SIG Storage & Backup"); + expect(output).toEqual("sig-storage-backup"); // & -> space -> hyphen + }); + + test("should convert reserved characters like / and : to hyphens", () => { + const output = transformSigLeadToURI("Project:Alpha/Beta"); + expect(output).toEqual("project-alpha-beta"); // : -> space, / -> space, space+space -> hyphen + }); + + test("should convert reserved characters like () and + to hyphens", () => { + const output = transformSigLeadToURI("My Project (Test+Alpha)"); + expect(output).toEqual("my-project-test-alpha"); + }); + + test("should convert various reserved characters #[]@?$, to hyphens", () => { + const output = transformSigLeadToURI("Special#Chars[Test]?@Value,$"); + expect(output).toEqual("special-chars-test-value"); + }); + + // Non-Allowed Character Removal Tests + test("should remove characters not unreserved or reserved (e.g., ™, ©)", () => { + const output = transformSigLeadToURI("MyOrg™ With © Symbols"); + expect(output).toEqual("my-org-with-symbols"); + }); + + test("should remove emoji", () => { + const output = transformSigLeadToURI("Project ✨ Fun"); + expect(output).toEqual("project-fun"); + }); + + + // Whitespace and Hyphen Collapsing Tests + test("should handle multiple spaces between words", () => { + const output = transformSigLeadToURI("SIG UI Project"); + expect(output).toEqual("sig-ui-project"); + }); + + test("should handle leading/trailing whitespace", () => { + const output = transformSigLeadToURI(" Leading and Trailing "); + expect(output).toEqual("leading-and-trailing"); + }); + + test("should handle mixed whitespace (tabs, newlines)", () => { + const output = transformSigLeadToURI("Mix\tOf\nWhite Space"); + expect(output).toEqual("mix-of-white-space"); + }); + + test("should collapse multiple hyphens resulting from transformations", () => { + const output = transformSigLeadToURI("Test--Multiple / Spaces"); + expect(output).toEqual("test-multiple-spaces"); + }); + + test("should collapse hyphens from start/end after transformations", () => { + const output = transformSigLeadToURI("&Another Test!"); + expect(output).toEqual("another-test"); + }); + + // Unreserved Character Tests (RFC 3986) + test("should keep unreserved characters: hyphen, period, underscore, tilde", () => { + const output = transformSigLeadToURI("Keep.These-Chars_Okay~123"); + expect(output).toEqual("keep.these-chars_okay~123"); + }); + + test("should handle unreserved chars next to reserved chars", () => { + const output = transformSigLeadToURI("Test._~&Stuff"); + expect(output).toEqual("test._~-stuff"); + }); + + + // Edge Case Tests + test("should return an empty string for an empty input", () => { + const output = transformSigLeadToURI(""); + expect(output).toEqual(""); + }); + + test("should return an empty string for input with only spaces", () => { + const output = transformSigLeadToURI(" "); + expect(output).toEqual(""); + }); + + test("should return an empty string for input with only reserved/non-allowed chars and spaces", () => { + const output = transformSigLeadToURI(" & / # ™ © "); + expect(output).toEqual(""); + }); + + test("should handle numbers correctly", () => { + const output = transformSigLeadToURI("ProjectApollo11"); + expect(output).toEqual("project-apollo11"); // Number doesn't trigger camel case break after letter + }); + + test("should handle numbers triggering camel case break", () => { + const output = transformSigLeadToURI("Project11Apollo"); + expect(output).toEqual("project-11-apollo"); // Letter after number triggers camel case break + }); + + test("should handle names starting with lowercase", () => { + const output = transformSigLeadToURI("myOrg"); + expect(output).toEqual("my-org"); + }); + +}); diff --git a/tests/unit/siglead.test.ts b/tests/unit/siglead.test.ts new file mode 100644 index 00000000..a205b005 --- /dev/null +++ b/tests/unit/siglead.test.ts @@ -0,0 +1,228 @@ +import { test, describe, expect, vi, beforeEach, afterEach } from "vitest"; +import Fastify, { FastifyInstance } from "fastify"; +import sigleadRoutes from "../../src/api/routes/siglead.js"; +import * as sigleadFunctions from "../../src/api/functions/siglead.js"; +import { + SigDetailRecord, + SigMemberCount, + SigMemberRecord, +} from "../../src/common/types/siglead.js"; + +// Mock the entire module of siglead functions +vi.mock("../../src/api/functions/siglead.js"); + +// A helper function to build our Fastify app for each test +const build = async (t: any): Promise => { + const app = Fastify(); + // Register the routes we are testing, with a prefix + app.register(sigleadRoutes, { prefix: "/siglead" }); + + // Add a cleanup hook + // t.after(() => app.close()); + + // Make the app available for injection-based testing + await app.ready(); + return app; +}; + +describe("SIGLead Routes", () => { + let app: FastifyInstance; + + // Before each test, build a new Fastify instance and mock the functions + beforeEach(async (t) => { + app = await build(t); + }); + + // After each test, close the server and restore the mocks to their original state + // afterEach(async () => { + // await app.close(); + // vi.restoreAllMocks(); + // }); + + // --- Tests for GET /sigmembers/:sigid --- + describe("GET /siglead/sigmembers/:sigid", () => { + test("should return 200 and member records on success", async () => { + const mockSigId = "sig-awesome"; + const mockMembers: SigMemberRecord[] = [ + { + sigGroupId: mockSigId, + email: "test1@example.com", + designation: "M", + memberName: "test1", + }, + { + sigGroupId: mockSigId, + email: "test2@example.com", + designation: "L", + memberName: "test2", + }, + ]; + + // Control the mock: make fetchMemberRecords return our fake data + vi.mocked(sigleadFunctions.fetchMemberRecords).mockResolvedValue( + mockMembers, + ); + + const response = await app.inject({ + method: "GET", + url: `/siglead/sigmembers/${mockSigId}`, + }); + + expect(response.statusCode).toBe(200); + expect(JSON.parse(response.payload)).toEqual(mockMembers); + }); + + test("should return 500 when fetchMemberRecords fails", async () => { + // Control the mock: make it throw an error + vi.mocked(sigleadFunctions.fetchMemberRecords).mockRejectedValue( + new Error("DynamoDB dyed :("), + ); + + const response = await app.inject({ + method: "GET", + url: "/siglead/sigmembers/sig-fail", + }); + + expect(response.statusCode).toBe(500); + const payload = JSON.parse(response.payload); + expect(payload.message).toBe( + "Failed to fetch member records from Dynamo table.", + ); + }); + }); + + // --- Tests for GET /sigdetail/:sigid --- + describe("GET /siglead/sigdetail/:sigid", () => { + test("should return 200 and sig detail on success", async () => { + const mockSigId = "sig-details"; + const mockDetail: SigDetailRecord = { + sigid: mockSigId, + signame: "The Awesome SIG", + description: "A SIG for testing.", + }; + + vi.mocked(sigleadFunctions.fetchSigDetail).mockResolvedValue(mockDetail); + + const response = await app.inject({ + method: "GET", + url: `/siglead/sigdetail/${mockSigId}`, + }); + + expect(response.statusCode).toBe(200); + expect(JSON.parse(response.payload)).toEqual(mockDetail); + }); + + test("should return 500 when fetchSigDetail fails", async () => { + vi.mocked(sigleadFunctions.fetchSigDetail).mockRejectedValue( + new Error("Database connection lost"), + ); + + const response = await app.inject({ + method: "GET", + url: "/siglead/sigdetail/sig-fail", + }); + + expect(response.statusCode).toBe(500); + const payload = JSON.parse(response.payload); + expect(payload.message).toBe( + "Failed to fetch sig detail record from Dynamo table.", + ); + }); + }); + + // --- Tests for GET /sigcount --- + describe("GET /siglead/sigcount", () => { + test("should return 200 and sig member counts on success", async () => { + const mockCounts: SigMemberCount[] = [ + { + sigid: "sig-a", + count: 10, + signame: "a", + }, + { + sigid: "sig-b", + count: 25, + signame: "b", + }, + ]; + + vi.mocked(sigleadFunctions.fetchSigCounts).mockResolvedValue(mockCounts); + + const response = await app.inject({ + method: "GET", + url: "/siglead/sigcount", + }); + + expect(response.statusCode).toBe(200); + expect(JSON.parse(response.payload)).toEqual(mockCounts); + }); + + test("should return 500 when fetchSigCounts fails", async () => { + vi.mocked(sigleadFunctions.fetchSigCounts).mockRejectedValue( + new Error("Could not count"), + ); + + const response = await app.inject({ + method: "GET", + url: "/siglead/sigcount", + }); + + expect(response.statusCode).toBe(500); + const payload = JSON.parse(response.payload); + expect(payload.message).toBe( + "Failed to fetch sig member counts record from Dynamo table.", + ); + }); + }); + + // --- Tests for POST /addMember --- + describe("POST /siglead/addMember", () => { + test("should return 200 on successful member addition", async () => { + const newMember = { + sigGroupId: "sig-new", + email: "new.member@example.com", + }; + + // For functions that don't return anything, we just resolve with void + vi.mocked(sigleadFunctions.addMemberToSigDynamo).mockResolvedValue( + undefined, + ); + + const response = await app.inject({ + method: "POST", + url: "/siglead/addMember", + payload: newMember, // Send the data in the request body + }); + + expect(response.statusCode).toBe(200); + + // Verify that our mock was called with the correct data + expect(sigleadFunctions.addMemberToSigDynamo).toHaveBeenCalledWith( + "infra-core-api-sig-member-details", // We don't need to test the table name config + newMember, + undefined, // We don't need to test the dynamoClient instance + ); + }); + + test("should return 500 when addMemberToSigDynamo fails", async () => { + vi.mocked(sigleadFunctions.addMemberToSigDynamo).mockRejectedValue( + new Error("Insert failed"), + ); + + const response = await app.inject({ + method: "POST", + url: "/siglead/addMember", + payload: { + sigGroupId: "sig-fail", + email: "fail@example.com", + }, + }); + + expect(response.statusCode).toBe(500); + const payload = JSON.parse(response.payload); + expect(payload.message).toBe( + "Failed to add sig member record to Dynamo table.", + ); + }); + }); +}); diff --git a/yarn.lock b/yarn.lock index e0d3755c..f9c15cd7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2183,101 +2183,51 @@ resolved "https://registry.yarnpkg.com/@napi-rs/canvas-android-arm64/-/canvas-android-arm64-0.1.70.tgz#9b8eff01f5be1597becd77d4c1c6117c0eb3a164" integrity sha512-I/YOuQ0wbkVYxVaYtCgN42WKTYxNqFA0gTcTrHIGG1jfpDSyZWII/uHcjOo4nzd19io6Y4+/BqP8E5hJgf9OmQ== -"@napi-rs/canvas-android-arm64@0.1.71": - version "0.1.71" - resolved "https://registry.yarnpkg.com/@napi-rs/canvas-android-arm64/-/canvas-android-arm64-0.1.71.tgz#e048a83b640bed55f75f4d92115a90ba3e86b375" - integrity sha512-cxi3VCotIOS9kNFQI7dcysbVJi106pxryVY1Hi85pX+ZeqahRyeqc/NsLaZ998Ae99+F3HI5X/39G1Y/Byrf0A== - "@napi-rs/canvas-darwin-arm64@0.1.70": version "0.1.70" resolved "https://registry.yarnpkg.com/@napi-rs/canvas-darwin-arm64/-/canvas-darwin-arm64-0.1.70.tgz#3bba057cec0780b8c24fe04ca20e530b08213320" integrity sha512-4pPGyXetHIHkw2TOJHujt3mkCP8LdDu8+CT15ld9Id39c752RcI0amDHSuMLMQfAjvusA9B5kKxazwjMGjEJpQ== -"@napi-rs/canvas-darwin-arm64@0.1.71": - version "0.1.71" - resolved "https://registry.yarnpkg.com/@napi-rs/canvas-darwin-arm64/-/canvas-darwin-arm64-0.1.71.tgz#d2154f6d4327dfc39a88fd58130b80981bf0cdb9" - integrity sha512-7Y4D/6vIuMLYsVNtRM/w2j0+fB1GyqeOxc7I0BTx8eLP1S6BZE2Rj6zJfdG+zmLEOW0IlHa+VQq1q2MUAjW84w== - "@napi-rs/canvas-darwin-x64@0.1.70": version "0.1.70" resolved "https://registry.yarnpkg.com/@napi-rs/canvas-darwin-x64/-/canvas-darwin-x64-0.1.70.tgz#954dc89245a32a82bc6f24707f1521c14ba4ce58" integrity sha512-+2N6Os9LbkmDMHL+raknrUcLQhsXzc5CSXRbXws9C3pv/mjHRVszQ9dhFUUe9FjfPhCJznO6USVdwOtu7pOrzQ== -"@napi-rs/canvas-darwin-x64@0.1.71": - version "0.1.71" - resolved "https://registry.yarnpkg.com/@napi-rs/canvas-darwin-x64/-/canvas-darwin-x64-0.1.71.tgz#7d414b24f2736f295a7046c626fbc51d301bb0f2" - integrity sha512-Z0IUqxclrYdfVt/SK9nKCzUHTOXKTWiygtO71YCzs0OtxKdNI7GJRJdYG48wXZEDQ/pqTF4F7Ifgtidfc2tYpg== - "@napi-rs/canvas-linux-arm-gnueabihf@0.1.70": version "0.1.70" resolved "https://registry.yarnpkg.com/@napi-rs/canvas-linux-arm-gnueabihf/-/canvas-linux-arm-gnueabihf-0.1.70.tgz#d48dafa8788d7d32c932bac01ed9224115574822" integrity sha512-QjscX9OaKq/990sVhSMj581xuqLgiaPVMjjYvWaCmAJRkNQ004QfoSMEm3FoTqM4DRoquP8jvuEXScVJsc1rqQ== -"@napi-rs/canvas-linux-arm-gnueabihf@0.1.71": - version "0.1.71" - resolved "https://registry.yarnpkg.com/@napi-rs/canvas-linux-arm-gnueabihf/-/canvas-linux-arm-gnueabihf-0.1.71.tgz#5108e480bf0ed34340f5ac35b68472d3ee90817d" - integrity sha512-KlpqqCASak5ruY+UIolJgmhMZ9Pa2o1QyaNu648L8sz4WNBbNa+aOT60XCLCL1VIKLv11B3MlNgiOHoYNmDhXQ== - "@napi-rs/canvas-linux-arm64-gnu@0.1.70": version "0.1.70" resolved "https://registry.yarnpkg.com/@napi-rs/canvas-linux-arm64-gnu/-/canvas-linux-arm64-gnu-0.1.70.tgz#522d3f1446941e3acec0bde344bc1711a60373ab" integrity sha512-LNakMOwwqwiHIwMpnMAbFRczQMQ7TkkMyATqFCOtUJNlE6LPP/QiUj/mlFrNbUn/hctqShJ60gWEb52ZTALbVw== -"@napi-rs/canvas-linux-arm64-gnu@0.1.71": - version "0.1.71" - resolved "https://registry.yarnpkg.com/@napi-rs/canvas-linux-arm64-gnu/-/canvas-linux-arm64-gnu-0.1.71.tgz#477f7f68748ddfa0bfd86172ef7215f96284f476" - integrity sha512-bdGZCGu8YQNAiu3nkIVVUp6nIn6fPd36IuZsLXTG027E52KyIuZ3obCxehSwjDIUNkFWvmff5D6JYfWwAoioEw== - "@napi-rs/canvas-linux-arm64-musl@0.1.70": version "0.1.70" resolved "https://registry.yarnpkg.com/@napi-rs/canvas-linux-arm64-musl/-/canvas-linux-arm64-musl-0.1.70.tgz#ab6a35f29c1b71ed42a395de3976406e1198c3c5" integrity sha512-wBTOllEYNfJCHOdZj9v8gLzZ4oY3oyPX8MSRvaxPm/s7RfEXxCyZ8OhJ5xAyicsDdbE5YBZqdmaaeP5+xKxvtg== -"@napi-rs/canvas-linux-arm64-musl@0.1.71": - version "0.1.71" - resolved "https://registry.yarnpkg.com/@napi-rs/canvas-linux-arm64-musl/-/canvas-linux-arm64-musl-0.1.71.tgz#4df1cddc27303d568e04d5d81cec83d45a3b1ac0" - integrity sha512-1R5sMWe9ur8uM+hAeylBwG0b6UHDR+iWQNgzXmF9vbBYRooQvmDWqpcgytKLJAC0vnWhIkKwqd7yExn7cwczmg== - "@napi-rs/canvas-linux-riscv64-gnu@0.1.70": version "0.1.70" resolved "https://registry.yarnpkg.com/@napi-rs/canvas-linux-riscv64-gnu/-/canvas-linux-riscv64-gnu-0.1.70.tgz#1ea23aa40f94009a334da745184d64f56ba26343" integrity sha512-GVUUPC8TuuFqHip0rxHkUqArQnlzmlXmTEBuXAWdgCv85zTCFH8nOHk/YCF5yo0Z2eOm8nOi90aWs0leJ4OE5Q== -"@napi-rs/canvas-linux-riscv64-gnu@0.1.71": - version "0.1.71" - resolved "https://registry.yarnpkg.com/@napi-rs/canvas-linux-riscv64-gnu/-/canvas-linux-riscv64-gnu-0.1.71.tgz#d0ed832bf05abffe34c8d5a12b79084d40ff6ae6" - integrity sha512-xjjKsipueuG+LdKIk6/uAlqdo+rzGcmNpTZPXdakIT1sHX4NNSnQTzjRaj9Gh96Czjd9G89UWR0KIlE7fwOgFA== - "@napi-rs/canvas-linux-x64-gnu@0.1.70": version "0.1.70" resolved "https://registry.yarnpkg.com/@napi-rs/canvas-linux-x64-gnu/-/canvas-linux-x64-gnu-0.1.70.tgz#ac16dd88895aa81323cca015bde2f0432c7f361c" integrity sha512-/kvUa2lZRwGNyfznSn5t1ShWJnr/m5acSlhTV3eXECafObjl0VBuA1HJw0QrilLpb4Fe0VLywkpD1NsMoVDROQ== -"@napi-rs/canvas-linux-x64-gnu@0.1.71": - version "0.1.71" - resolved "https://registry.yarnpkg.com/@napi-rs/canvas-linux-x64-gnu/-/canvas-linux-x64-gnu-0.1.71.tgz#005d24a800fa11a50e04856d005d7d1b4cdbbd23" - integrity sha512-3s6YpklXDB4OeeULG1XTRyKrKAOo7c3HHEqM9A6N4STSjMaJtzmpp7tB/JTvAFeOeFte6gWN8IwC+7AjGJ6MpQ== - "@napi-rs/canvas-linux-x64-musl@0.1.70": version "0.1.70" resolved "https://registry.yarnpkg.com/@napi-rs/canvas-linux-x64-musl/-/canvas-linux-x64-musl-0.1.70.tgz#d85c2f435f5f1c59139b5a7e12fcdfcedf8e6e70" integrity sha512-aqlv8MLpycoMKRmds7JWCfVwNf1fiZxaU7JwJs9/ExjTD8lX2KjsO7CTeAj5Cl4aEuzxUWbJPUUE2Qu9cZ1vfg== -"@napi-rs/canvas-linux-x64-musl@0.1.71": - version "0.1.71" - resolved "https://registry.yarnpkg.com/@napi-rs/canvas-linux-x64-musl/-/canvas-linux-x64-musl-0.1.71.tgz#06f567ddeb846b79d56c1232cdb7c51f9f0b1b20" - integrity sha512-5v9aCLzCXw7u10ray5juQMdl7TykZSn1X5AIGYwBvTAcKSgrqaR9QkRxp1Lqk3njQmFekOW1SFN9bZ/i/6y6kA== - "@napi-rs/canvas-win32-x64-msvc@0.1.70": version "0.1.70" resolved "https://registry.yarnpkg.com/@napi-rs/canvas-win32-x64-msvc/-/canvas-win32-x64-msvc-0.1.70.tgz#b66c858f39a605846a4951305afe6b12ae078238" integrity sha512-Q9QU3WIpwBTVHk4cPfBjGHGU4U0llQYRXgJtFtYqqGNEOKVN4OT6PQ+ve63xwIPODMpZ0HHyj/KLGc9CWc3EtQ== -"@napi-rs/canvas-win32-x64-msvc@0.1.71": - version "0.1.71" - resolved "https://registry.yarnpkg.com/@napi-rs/canvas-win32-x64-msvc/-/canvas-win32-x64-msvc-0.1.71.tgz#4df1bc32816d5ed5daabe0f872e62f438c1b958b" - integrity sha512-oJughk6xjsRIr0Rd9EqjmZmhIMkvcPuXgr3MNn2QexTqn+YFOizrwHS5ha0BDfFl7TEGRvwaDUXBQtu8JKXb8A== - "@napi-rs/canvas@^0.1.65": version "0.1.70" resolved "https://registry.yarnpkg.com/@napi-rs/canvas/-/canvas-0.1.70.tgz#b8624eb6c4142d171d81b604f9822ead8144cc1a" @@ -2294,22 +2244,6 @@ "@napi-rs/canvas-linux-x64-musl" "0.1.70" "@napi-rs/canvas-win32-x64-msvc" "0.1.70" -"@napi-rs/canvas@^0.1.67": - version "0.1.71" - resolved "https://registry.yarnpkg.com/@napi-rs/canvas/-/canvas-0.1.71.tgz#4d3c637634ca506f9af004667805c8815c2b8c37" - integrity sha512-92ybDocKl6JM48ZpYbj+A7Qt45IaTABDk0y3sDecEQfgdhfNzJtEityqNHoCZ4Vty2dldPkJhxgvOnbrQMXTTA== - optionalDependencies: - "@napi-rs/canvas-android-arm64" "0.1.71" - "@napi-rs/canvas-darwin-arm64" "0.1.71" - "@napi-rs/canvas-darwin-x64" "0.1.71" - "@napi-rs/canvas-linux-arm-gnueabihf" "0.1.71" - "@napi-rs/canvas-linux-arm64-gnu" "0.1.71" - "@napi-rs/canvas-linux-arm64-musl" "0.1.71" - "@napi-rs/canvas-linux-riscv64-gnu" "0.1.71" - "@napi-rs/canvas-linux-x64-gnu" "0.1.71" - "@napi-rs/canvas-linux-x64-musl" "0.1.71" - "@napi-rs/canvas-win32-x64-msvc" "0.1.71" - "@napi-rs/wasm-runtime@^0.2.9": version "0.2.9" resolved "https://registry.yarnpkg.com/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.9.tgz#7278122cf94f3b36d8170a8eee7d85356dfa6a96" @@ -4790,7 +4724,7 @@ binary-extensions@^2.0.0: resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522" integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw== -bl@^4.0.2, bl@^4.0.3: +bl@^4.0.2: version "4.1.0" resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w== @@ -4963,14 +4897,6 @@ caniuse-lite@^1.0.30001688: resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001715.tgz#bd325a37ad366e3fe90827d74062807a34fbaeb2" integrity sha512-7ptkFGMm2OAOgvZpwgA4yjQ5SQbrNVGdRjzH0pBdy1Fasvcr+KAeECmbCAECzTuDuoX0FCY8KzUxjf9+9kfZEw== -canvas@^3.0.0-rc2: - version "3.1.1" - resolved "https://registry.yarnpkg.com/canvas/-/canvas-3.1.1.tgz#d5399808e5cd19b0a9679248a7f98929e3f96341" - integrity sha512-o8JptL14zrYFYSh5r7ClVzuwG77tvjRBgSJECyIF6AAHpL4MgORhlI+rru3/ZPKtdC7uopl+biESZWz1l/NpFA== - dependencies: - node-addon-api "^7.0.0" - prebuild-install "^7.1.3" - caseless@~0.12.0: version "0.12.0" resolved "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz" @@ -5065,11 +4991,6 @@ chokidar@^3.5.2, chokidar@^3.5.3: optionalDependencies: fsevents "~2.3.2" -chownr@^1.1.1: - version "1.1.4" - resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" - integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== - cli-boxes@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-3.0.0.tgz#71a10c716feeba005e4504f36329ef0b17cf3145" @@ -5459,13 +5380,6 @@ decimal.js@^10.5.0: resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.5.0.tgz#0f371c7cf6c4898ce0afb09836db73cd82010f22" integrity sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw== -decompress-response@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc" - integrity sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ== - dependencies: - mimic-response "^3.1.0" - deep-eql@^5.0.1: version "5.0.2" resolved "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz" @@ -5548,11 +5462,6 @@ dequal@^2.0.2, dequal@^2.0.3: resolved "https://registry.yarnpkg.com/dequal/-/dequal-2.0.3.tgz#2644214f1997d39ed0ee0ece72335490a7ac67be" integrity sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA== -detect-libc@^2.0.0: - version "2.0.4" - resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.4.tgz#f04715b8ba815e53b4d8109655b6508a6865a7e8" - integrity sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA== - detect-node-es@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/detect-node-es/-/detect-node-es-1.1.0.tgz#163acdf643330caa0b4cd7c21e7ee7755d6fa493" @@ -6352,11 +6261,6 @@ execa@^5.1.1: signal-exit "^3.0.3" strip-final-newline "^2.0.0" -expand-template@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c" - integrity sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg== - expect-type@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/expect-type/-/expect-type-1.2.1.tgz#af76d8b357cf5fa76c41c09dafb79c549e75f71f" @@ -6708,11 +6612,6 @@ formidable@^3.5.1: hexoid "^2.0.0" once "^1.4.0" -fs-constants@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" - integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== - fs-extra@^10.0.1: version "10.1.0" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.1.0.tgz#02873cfbc4084dde127eaa5f9905eef2325d1abf" @@ -6847,11 +6746,6 @@ getpass@^0.1.1: dependencies: assert-plus "^1.0.0" -github-from-package@0.0.0: - version "0.0.0" - resolved "https://registry.yarnpkg.com/github-from-package/-/github-from-package-0.0.0.tgz#97fb5d96bfde8973313f20e8288ef9a167fa64ce" - integrity sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw== - glob-parent@^5.0.0, glob-parent@^5.1.2, glob-parent@~5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" @@ -8277,11 +8171,6 @@ mimic-fn@^2.1.0: resolved "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== -mimic-response@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9" - integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ== - min-indent@^1.0.0, min-indent@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869" @@ -8308,7 +8197,7 @@ minimatch@^9.0.4: dependencies: brace-expansion "^2.0.1" -minimist@^1.1.0, minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.5, minimist@^1.2.6: +minimist@^1.1.0, minimist@^1.2.0, minimist@^1.2.5, minimist@^1.2.6: version "1.2.8" resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz" integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== @@ -8318,11 +8207,6 @@ minimist@^1.1.0, minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.5, minimist@^1. resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.2.tgz#93a9626ce5e5e66bd4db86849e7515e92340a707" integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw== -mkdirp-classic@^0.5.2, mkdirp-classic@^0.5.3: - version "0.5.3" - resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113" - integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A== - mkdirp@^0.5.1: version "0.5.6" resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz" @@ -8413,11 +8297,6 @@ nanoid@^3.3.8: resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.8.tgz#b1be3030bee36aaff18bacb375e5cce521684baf" integrity sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w== -napi-build-utils@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/napi-build-utils/-/napi-build-utils-2.0.0.tgz#13c22c0187fcfccce1461844136372a47ddc027e" - integrity sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA== - napi-postinstall@^0.2.2: version "0.2.2" resolved "https://registry.yarnpkg.com/napi-postinstall/-/napi-postinstall-0.2.2.tgz#c80d400a3b760bf7a75f7eabbd5c3fee2f0d60a8" @@ -8461,18 +8340,6 @@ nmtree@^1.0.6: dependencies: commander "^2.11.0" -node-abi@^3.3.0: - version "3.75.0" - resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.75.0.tgz#2f929a91a90a0d02b325c43731314802357ed764" - integrity sha512-OhYaY5sDsIka7H7AtijtI9jwGYLyl29eQn/W623DiN/MIv5sUqc4g7BIDThX+gb7di9f6xK02nkp8sdfFWZLTg== - dependencies: - semver "^7.3.5" - -node-addon-api@^7.0.0: - version "7.1.1" - resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-7.1.1.tgz#1aba6693b0f255258a049d621329329322aad558" - integrity sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ== - node-addon-api@^8.3.1: version "8.3.1" resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-8.3.1.tgz#53bc8a4f8dbde3de787b9828059da94ba9fd4eed" @@ -8844,11 +8711,6 @@ path-type@^4.0.0: resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== -path2d@^0.2.1: - version "0.2.2" - resolved "https://registry.yarnpkg.com/path2d/-/path2d-0.2.2.tgz#cc85d61ed7827e7863a2ee36713d4b5315a3d85d" - integrity sha512-+vnG6S4dYcYxZd+CZxzXCNKdELYZSKfohrk98yajCo1PtRoDgCTrrwOvK1GT0UoAdVszagDVllQc0U1vaX4NUQ== - pathe@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/pathe/-/pathe-2.0.3.tgz#3ecbec55421685b70a9da872b2cff3e1cbed1716" @@ -8859,28 +8721,13 @@ pathval@^2.0.0: resolved "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz" integrity sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA== -pdfjs-dist@4.8.69: - version "4.8.69" - resolved "https://registry.yarnpkg.com/pdfjs-dist/-/pdfjs-dist-4.8.69.tgz#61ea5d66863d49b40e5eacbd4070341175bdda2e" - integrity sha512-IHZsA4T7YElCKNNXtiLgqScw4zPd3pG9do8UrznC757gMd7UPeHSL2qwNNMJo4r79fl8oj1Xx+1nh2YkzdMpLQ== - optionalDependencies: - canvas "^3.0.0-rc2" - path2d "^0.2.1" - -pdfjs-dist@^4.6.82: +pdfjs-dist@4.8.69, pdfjs-dist@^4.6.82, pdfjs-dist@^4.8.69, pdfjs-dist@^5.2.133: version "4.10.38" resolved "https://registry.yarnpkg.com/pdfjs-dist/-/pdfjs-dist-4.10.38.tgz#3ee698003790dc266cc8b55c0e662ccb9ae18f53" integrity sha512-/Y3fcFrXEAsMjJXeL9J8+ZG9U01LbuWaYypvDW2ycW1jL269L3js3DVBjDJ0Up9Np1uqDXsDrRihHANhZOlwdQ== optionalDependencies: "@napi-rs/canvas" "^0.1.65" -pdfjs-dist@^5.2.133: - version "5.3.31" - resolved "https://registry.yarnpkg.com/pdfjs-dist/-/pdfjs-dist-5.3.31.tgz#0c429f3bc43c57ec1a95fa2874f2f93189d2d40e" - integrity sha512-EhPdIjNX0fcdwYQO+e3BAAJPXt+XI29TZWC7COhIXs/K0JHcUt1Gdz1ITpebTwVMFiLsukdUZ3u0oTO7jij+VA== - optionalDependencies: - "@napi-rs/canvas" "^0.1.67" - performance-now@^2.1.0: version "2.1.0" resolved "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz" @@ -9080,24 +8927,6 @@ postcss@^8.5.3: picocolors "^1.1.1" source-map-js "^1.2.1" -prebuild-install@^7.1.3: - version "7.1.3" - resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-7.1.3.tgz#d630abad2b147443f20a212917beae68b8092eec" - integrity sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug== - dependencies: - detect-libc "^2.0.0" - expand-template "^2.0.3" - github-from-package "0.0.0" - minimist "^1.2.3" - mkdirp-classic "^0.5.3" - napi-build-utils "^2.0.0" - node-abi "^3.3.0" - pump "^3.0.0" - rc "^1.2.7" - simple-get "^4.0.0" - tar-fs "^2.0.0" - tunnel-agent "^0.6.0" - prelude-ls@^1.2.1: version "1.2.1" resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz" @@ -9244,7 +9073,7 @@ raw-body@^3.0.0: iconv-lite "0.6.3" unpipe "1.0.0" -rc@^1.0.1, rc@^1.1.6, rc@^1.2.7: +rc@^1.0.1, rc@^1.1.6: version "1.2.8" resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== @@ -9791,11 +9620,6 @@ semver@^6.1.2, semver@^6.3.0, semver@^6.3.1: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== -semver@^7.3.5: - version "7.7.2" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.2.tgz#67d99fdcd35cec21e6f8b87a7fd515a33f982b58" - integrity sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA== - semver@^7.5.3, semver@^7.5.4, semver@^7.6.0, semver@^7.6.2, semver@^7.6.3, semver@^7.7.1: version "7.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.1.tgz#abd5098d82b18c6c81f6074ff2647fd3e7220c9f" @@ -9961,20 +9785,6 @@ signal-exit@^4.0.1: resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz" integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== -simple-concat@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.1.tgz#f46976082ba35c2263f1c8ab5edfe26c41c9552f" - integrity sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q== - -simple-get@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-4.0.1.tgz#4a39db549287c979d352112fa03fd99fd6bc3543" - integrity sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA== - dependencies: - decompress-response "^6.0.0" - once "^1.3.1" - simple-concat "^1.0.0" - simple-update-notifier@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz#d70b92bdab7d6d90dfd73931195a30b6e3d7cebb" @@ -10545,27 +10355,6 @@ table@^6.9.0: string-width "^4.2.3" strip-ansi "^6.0.1" -tar-fs@^2.0.0: - version "2.1.3" - resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.3.tgz#fb3b8843a26b6f13a08e606f7922875eb1fbbf92" - integrity sha512-090nwYJDmlhwFwEW3QQl+vaNnxsO2yVsd45eTKRBzSzu+hlb1w2K9inVq5b0ngXuLVqQ4ApvsUHHnu/zQNkWAg== - dependencies: - chownr "^1.1.1" - mkdirp-classic "^0.5.2" - pump "^3.0.0" - tar-stream "^2.1.4" - -tar-stream@^2.1.4: - version "2.2.0" - resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287" - integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ== - dependencies: - bl "^4.0.3" - end-of-stream "^1.4.1" - fs-constants "^1.0.0" - inherits "^2.0.3" - readable-stream "^3.1.1" - test-exclude@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-7.0.1.tgz#20b3ba4906ac20994e275bbcafd68d510264c2a2"