From ffe387cc935ae1bbaedc1a991f84250088143344 Mon Sep 17 00:00:00 2001 From: Eddy Chen <89349085+ecxyzzy@users.noreply.github.com> Date: Thu, 21 Dec 2023 05:17:12 -0800 Subject: [PATCH] =?UTF-8?q?perf:=20=E2=9A=A1=EF=B8=8F=20optimize=20courses?= =?UTF-8?q?/instructors=20by=20ID=20(#117)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Aponia --- .../actions/setup-node-and-pnpm/action.yml | 2 +- .nvmrc | 2 +- apps/api/bronya.config.ts | 45 +++++- apps/api/src/global.d.ts | 19 +++ apps/api/src/lib/utils.ts | 37 +++++ .../routes/v1/graphql/schema/courses.graphql | 4 +- .../v1/graphql/schema/instructors.graphql | 4 +- .../src/routes/v1/rest/courses/+endpoint.ts | 4 +- apps/api/src/routes/v1/rest/courses/lib.ts | 55 +------- .../routes/v1/rest/courses/{id}/+config.ts | 11 ++ .../routes/v1/rest/courses/{id}/+endpoint.ts | 45 ++---- .../v1/rest/instructors/{id}/+config.ts | 11 ++ .../v1/rest/instructors/{id}/+endpoint.ts | 41 ++---- .../contributors-guide/getting-started.md | 4 +- apps/docs/package.json | 2 +- libs/db/prisma/schema.prisma | 2 +- libs/uc-irvine-api/package.json | 2 +- package.json | 6 +- pnpm-lock.yaml | 128 ++++++------------ renovate.json | 2 +- services/websoc-proxy/package.json | 2 +- services/websoc-scraper-v2/Dockerfile | 2 +- tools/cdk/src/constructs/WebsocProxy.ts | 2 +- tools/cdk/src/constructs/WebsocScraperV2.ts | 2 +- tools/grades-updater/package.json | 2 +- tsconfig.json | 2 +- 26 files changed, 213 insertions(+), 225 deletions(-) create mode 100644 apps/api/src/global.d.ts create mode 100644 apps/api/src/routes/v1/rest/courses/{id}/+config.ts create mode 100644 apps/api/src/routes/v1/rest/instructors/{id}/+config.ts diff --git a/.github/actions/setup-node-and-pnpm/action.yml b/.github/actions/setup-node-and-pnpm/action.yml index 0bd4124a..bdc66301 100644 --- a/.github/actions/setup-node-and-pnpm/action.yml +++ b/.github/actions/setup-node-and-pnpm/action.yml @@ -11,7 +11,7 @@ runs: - name: Setup Node.js environment uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4 with: - node-version: lts/hydrogen + node-version: lts/* - name: Install pnpm uses: pnpm/action-setup@v2 diff --git a/.nvmrc b/.nvmrc index a77793ec..b009dfb9 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -lts/hydrogen +lts/* diff --git a/apps/api/bronya.config.ts b/apps/api/bronya.config.ts index 07b7921c..fb9edc20 100644 --- a/apps/api/bronya.config.ts +++ b/apps/api/bronya.config.ts @@ -4,6 +4,7 @@ import { join, resolve } from "node:path"; import { Api, type ApiConstructProps } from "@bronya.js/api-construct"; import { createApiCliPlugins } from "@bronya.js/api-construct/plugins/cli"; import { isCdk } from "@bronya.js/core"; +import { PrismaClient } from "@libs/db"; import { logger, warmingRequestBody } from "@libs/lambda"; import { LambdaIntegration, ResponseType } from "aws-cdk-lib/aws-apigateway"; import { Certificate } from "aws-cdk-lib/aws-certificatemanager"; @@ -16,6 +17,10 @@ import { App, Stack, Duration } from "aws-cdk-lib/core"; import { config } from "dotenv"; import type { BuildOptions } from "esbuild"; +import { normalizeCourse } from "./src/lib/utils"; + +const prisma = new PrismaClient(); + /** * Whether we're executing in CDK. * @@ -77,7 +82,12 @@ const prismaSchema = resolve(libsDbDirectory, "prisma", prismaSchemaFile); /** * Name of the Prisma query engine file that's used on AWS Lambda. */ -const prismaQueryEngineFile = "libquery_engine-linux-arm64-openssl-1.0.x.so.node"; +const prismaQueryEngineFile = "libquery_engine-linux-arm64-openssl-3.0.x.so.node"; + +/** + * Namespace for virtual files. + */ +const namespace = "peterportal-api-next:virtual"; /** * Shared ESBuild options. @@ -86,7 +96,7 @@ export const esbuildOptions: BuildOptions = { format: "esm", platform: "node", bundle: true, - // minify: true, + minify: true, banner: { js }, /** @@ -97,12 +107,41 @@ export const esbuildOptions: BuildOptions = { * @RFC What would be the best way to resolve these two values? */ outExtension: { ".js": ".mjs" }, + + plugins: [ + { + name: "in-memory-cache", + setup: (build) => { + build.onResolve({ filter: /virtual:courses/ }, (args) => ({ + path: args.path, + namespace, + })); + build.onResolve({ filter: /virtual:instructors/ }, (args) => ({ + path: args.path, + namespace, + })); + build.onLoad({ filter: /virtual:courses/, namespace }, async () => ({ + contents: `export const courses = ${JSON.stringify( + Object.fromEntries( + (await prisma.course.findMany()).map(normalizeCourse).map((x) => [x.id, x]), + ), + )}`, + })); + build.onLoad({ filter: /virtual:instructors/, namespace }, async () => ({ + contents: `export const instructors = ${JSON.stringify( + Object.fromEntries((await prisma.instructor.findMany()).map((x) => [x.ucinetid, x])), + )}`, + })); + }, + }, + ], }; /** * Shared construct props. */ export const constructs: ApiConstructProps = { + functionProps: () => ({ runtime: Runtime.NODEJS_20_X }), functionPlugin: ({ functionProps, handler }, scope) => { const warmingTarget = new LambdaFunction(handler, { event: RuleTargetInput.fromObject(warmingRequestBody), @@ -263,7 +302,7 @@ export async function main(): Promise { 'exports.h=async _=>({headers:{"Access-Control-Allow-Origin": "*","Access-Control-Allow-Headers": "Apollo-Require-Preflight,Content-Type","Access-Control-Allow-Methods": "GET,POST,OPTIONS"},statusCode:204})', ), handler: "index.h", - runtime: Runtime.NODEJS_18_X, + runtime: Runtime.NODEJS_20_X, architecture: Architecture.ARM_64, }), ); diff --git a/apps/api/src/global.d.ts b/apps/api/src/global.d.ts new file mode 100644 index 00000000..b262aa62 --- /dev/null +++ b/apps/api/src/global.d.ts @@ -0,0 +1,19 @@ +/** + * Ambient declaration file for defining "virtual" modules/files. + * The file contents are generated dynamically during build time by esbuild. + * DO NOT add any imports/exports; that converts the file to a regular module + * and removes the global declarations. + */ + +/** + * Virtual module for caching course information during build time. + */ +declare module "virtual:courses" { + declare const courses: Record; +} +/** + * Virtual module for caching instructor information during build time. + */ +declare module "virtual:instructors" { + declare const instructors: Record; +} diff --git a/apps/api/src/lib/utils.ts b/apps/api/src/lib/utils.ts index 8497192e..1ed1a295 100644 --- a/apps/api/src/lib/utils.ts +++ b/apps/api/src/lib/utils.ts @@ -1,5 +1,27 @@ +import { Course as PrismaCourse } from "@libs/db"; +import { Course, CourseLevel, GECategory, PrerequisiteTree } from "@peterportal-api/types"; + const days = ["Su", "M", "Tu", "W", "Th", "F", "Sa"]; +const courseLevels: Record = { + LowerDiv: "Lower Division (1-99)", + UpperDiv: "Upper Division (100-199)", + Graduate: "Graduate/Professional Only (200+)", +}; + +const geMapping: Record = { + "GE-1A": "GE Ia: Lower Division Writing", + "GE-1B": "GE Ib: Upper Division Writing", + "GE-2": "GE II: Science and Technology", + "GE-3": "GE III: Social & Behavioral Sciences", + "GE-4": "GE IV: Arts and Humanities", + "GE-5A": "GE Va: Quantitative Literacy", + "GE-5B": "GE Vb: Formal Reasoning", + "GE-6": "GE VI: Language Other Than English", + "GE-7": "GE VII: Multicultural Studies", + "GE-8": "GE VIII: International/Global Issues", +}; + /** * Input to a transform function. */ @@ -35,3 +57,18 @@ export const flattenDayStringsAndSplit = (value: TransformInput): TransformOutpu ), ) : undefined; + +export function normalizeCourse(course: PrismaCourse): Course { + const courseLevel = courseLevels[course.courseLevel]; + const geList = (course.geList as string[]).map((x) => geMapping[x]); + return { + ...course, + courseLevel, + instructorHistory: course.instructorHistory as unknown as string[], + prerequisiteTree: course.prerequisiteTree as unknown as PrerequisiteTree, + prerequisiteList: course.prerequisiteList as unknown as string[], + prerequisiteFor: course.prerequisiteFor as unknown as string[], + geList, + terms: course.terms as unknown as string[], + }; +} diff --git a/apps/api/src/routes/v1/graphql/schema/courses.graphql b/apps/api/src/routes/v1/graphql/schema/courses.graphql index f7584eec..ca496793 100644 --- a/apps/api/src/routes/v1/graphql/schema/courses.graphql +++ b/apps/api/src/routes/v1/graphql/schema/courses.graphql @@ -55,8 +55,8 @@ type Course { } extend type Query { - "Get the course with the given ID." - course(courseId: String!): Course! + "Get the course with the given ID, or null if no such course exists." + course(courseId: String!): Course "Get courses that match the given constraints." courses( "The department the courses are in." diff --git a/apps/api/src/routes/v1/graphql/schema/instructors.graphql b/apps/api/src/routes/v1/graphql/schema/instructors.graphql index 0bb3a637..d18005fd 100644 --- a/apps/api/src/routes/v1/graphql/schema/instructors.graphql +++ b/apps/api/src/routes/v1/graphql/schema/instructors.graphql @@ -25,8 +25,8 @@ type Instructor { } extend type Query { - "Get the instructor with the corresponding UCInetID." - instructor(ucinetid: String!): Instructor! + "Get the instructor with the corresponding UCInetID, or null if no such instructor exists." + instructor(ucinetid: String!): Instructor "Get instructors that match the given constraints." instructors( "A substring of the instructors' full names." diff --git a/apps/api/src/routes/v1/rest/courses/+endpoint.ts b/apps/api/src/routes/v1/rest/courses/+endpoint.ts index 52f949cb..3dbd9e59 100644 --- a/apps/api/src/routes/v1/rest/courses/+endpoint.ts +++ b/apps/api/src/routes/v1/rest/courses/+endpoint.ts @@ -2,7 +2,9 @@ import { PrismaClient } from "@libs/db"; import { createHandler } from "@libs/lambda"; import { ZodError } from "zod"; -import { constructPrismaQuery, normalizeCourse } from "./lib"; +import { normalizeCourse } from "../../../../lib/utils"; + +import { constructPrismaQuery } from "./lib"; import { QuerySchema } from "./schema"; const prisma = new PrismaClient(); diff --git a/apps/api/src/routes/v1/rest/courses/lib.ts b/apps/api/src/routes/v1/rest/courses/lib.ts index 223011b2..aa13a485 100644 --- a/apps/api/src/routes/v1/rest/courses/lib.ts +++ b/apps/api/src/routes/v1/rest/courses/lib.ts @@ -1,60 +1,7 @@ -import { Course as PrismaCourse, CourseLevel as PrismaCourseLevel, Prisma } from "@libs/db"; -import { Course, CourseLevel, GE, GECategory, PrerequisiteTree } from "@peterportal-api/types"; +import { Prisma } from "@libs/db"; import { Query } from "./schema"; -export function normalizeCourse(course: PrismaCourse): Course { - let courseLevel: CourseLevel; - switch (course.courseLevel as PrismaCourseLevel) { - case "LowerDiv": - courseLevel = "Lower Division (1-99)"; - break; - case "UpperDiv": - courseLevel = "Upper Division (100-199)"; - break; - case "Graduate": - courseLevel = "Graduate/Professional Only (200+)"; - break; - } - const geList = (course.geList as Array>).map((x): GECategory => { - switch (x) { - case "GE-1A": - return "GE Ia: Lower Division Writing"; - case "GE-1B": - return "GE Ib: Upper Division Writing"; - case "GE-2": - return "GE II: Science and Technology"; - case "GE-3": - return "GE III: Social & Behavioral Sciences"; - case "GE-4": - return "GE IV: Arts and Humanities"; - case "GE-5A": - return "GE Va: Quantitative Literacy"; - case "GE-5B": - return "GE Vb: Formal Reasoning"; - case "GE-6": - return "GE VI: Language Other Than English"; - case "GE-7": - return "GE VII: Multicultural Studies"; - case "GE-8": - return "GE VIII: International/Global Issues"; - // this branch should never happen - default: - throw new Error(); - } - }); - return { - ...course, - courseLevel, - instructorHistory: course.instructorHistory as unknown as string[], - prerequisiteTree: course.prerequisiteTree as unknown as PrerequisiteTree, - prerequisiteList: course.prerequisiteList as unknown as string[], - prerequisiteFor: course.prerequisiteFor as unknown as string[], - geList, - terms: course.terms as unknown as string[], - }; -} - /** * Constructs a Prisma query for the given filter parameters. * @param parsedQuery The query object parsed by Zod. diff --git a/apps/api/src/routes/v1/rest/courses/{id}/+config.ts b/apps/api/src/routes/v1/rest/courses/{id}/+config.ts new file mode 100644 index 00000000..3dac3a61 --- /dev/null +++ b/apps/api/src/routes/v1/rest/courses/{id}/+config.ts @@ -0,0 +1,11 @@ +import { ApiPropsOverride } from "@bronya.js/api-construct"; + +import { esbuildOptions, constructs } from "../../../../../../bronya.config"; + +export const overrides: ApiPropsOverride = { + esbuild: esbuildOptions, + constructs: { + functionPlugin: constructs.functionPlugin, + restApiProps: constructs.restApiProps, + }, +}; diff --git a/apps/api/src/routes/v1/rest/courses/{id}/+endpoint.ts b/apps/api/src/routes/v1/rest/courses/{id}/+endpoint.ts index 5bd6e3b5..d1a2486f 100644 --- a/apps/api/src/routes/v1/rest/courses/{id}/+endpoint.ts +++ b/apps/api/src/routes/v1/rest/courses/{id}/+endpoint.ts @@ -1,39 +1,20 @@ -import { PrismaClient } from "@libs/db"; import { createHandler } from "@libs/lambda"; - -import { normalizeCourse } from "../lib"; - -const prisma = new PrismaClient(); - -async function onWarm() { - await prisma.$connect(); -} +import { courses } from "virtual:courses"; export const GET = createHandler(async (event, context, res) => { const headers = event.headers; const requestId = context.awsRequestId; - const params = event.pathParameters; - - if (params?.id == null) { - return res.createErrorResult(400, "Course number not provided", requestId); - } - - if (params?.id === "all") { - const courses = await prisma.course.findMany(); - return res.createOKResult(courses.map(normalizeCourse), headers, requestId); - } + const { id } = event.pathParameters ?? {}; - try { - return res.createOKResult( - normalizeCourse( - await prisma.course.findFirstOrThrow({ - where: { id: decodeURIComponent(params.id) }, - }), - ), - headers, - requestId, - ); - } catch { - return res.createErrorResult(404, `Course ${params.id} not found`, requestId); + switch (id) { + case null: + case undefined: + return res.createErrorResult(400, "Course number not provided", requestId); + case "all": + return res.createOKResult(Object.values(courses), headers, requestId); + default: + return courses[decodeURIComponent(id)] + ? res.createOKResult(courses[decodeURIComponent(id)], headers, requestId) + : res.createErrorResult(404, `Course ${id} not found`, requestId); } -}, onWarm); +}); diff --git a/apps/api/src/routes/v1/rest/instructors/{id}/+config.ts b/apps/api/src/routes/v1/rest/instructors/{id}/+config.ts new file mode 100644 index 00000000..3dac3a61 --- /dev/null +++ b/apps/api/src/routes/v1/rest/instructors/{id}/+config.ts @@ -0,0 +1,11 @@ +import { ApiPropsOverride } from "@bronya.js/api-construct"; + +import { esbuildOptions, constructs } from "../../../../../../bronya.config"; + +export const overrides: ApiPropsOverride = { + esbuild: esbuildOptions, + constructs: { + functionPlugin: constructs.functionPlugin, + restApiProps: constructs.restApiProps, + }, +}; diff --git a/apps/api/src/routes/v1/rest/instructors/{id}/+endpoint.ts b/apps/api/src/routes/v1/rest/instructors/{id}/+endpoint.ts index 2e9d489e..2e96e136 100644 --- a/apps/api/src/routes/v1/rest/instructors/{id}/+endpoint.ts +++ b/apps/api/src/routes/v1/rest/instructors/{id}/+endpoint.ts @@ -1,35 +1,20 @@ -import { PrismaClient } from "@libs/db"; import { createHandler } from "@libs/lambda"; - -const prisma = new PrismaClient(); - -async function onWarm() { - await prisma.$connect(); -} +import { instructors } from "virtual:instructors"; export const GET = createHandler(async (event, context, res) => { const headers = event.headers; const requestId = context.awsRequestId; - const params = event.pathParameters; - - if (params?.id == null) { - return res.createErrorResult(400, "Instructor UCInetID not provided", requestId); - } - - try { - if (params.id === "all") { - const instructors = await prisma.instructor.findMany(); - return res.createOKResult(instructors, headers, requestId); - } + const { id } = event.pathParameters ?? {}; - return res.createOKResult( - await prisma.instructor.findFirstOrThrow({ - where: { ucinetid: decodeURIComponent(params.id) }, - }), - headers, - requestId, - ); - } catch { - return res.createErrorResult(404, `Instructor ${params.id} not found`, requestId); + switch (id) { + case null: + case undefined: + return res.createErrorResult(400, "Instructor UCInetID not provided", requestId); + case "all": + return res.createOKResult(Object.values(instructors), headers, requestId); + default: + return instructors[decodeURIComponent(id)] + ? res.createOKResult(instructors[decodeURIComponent(id)], headers, requestId) + : res.createErrorResult(404, `Instructor ${id} not found`, requestId); } -}, onWarm); +}); diff --git a/apps/docs/docs/contributors-guide/getting-started.md b/apps/docs/docs/contributors-guide/getting-started.md index dfa90e35..854951ed 100644 --- a/apps/docs/docs/contributors-guide/getting-started.md +++ b/apps/docs/docs/contributors-guide/getting-started.md @@ -13,12 +13,12 @@ If you're a developer who's interested in using PeterPortal API in your next pro To begin, you'll need to install the correct version of Node.js. We recommend using the [Node Version Manager (`nvm`)](https://github.com/nvm-sh/nvm), which is a painless way to ensure that you will always be using the correct version of Node.js. -As of the time of writing, PeterPortal API targets the latest version of Node.js 18 (Hydrogen). If you choose not to use `nvm`, please be aware that code tested with other versions of Node.js may not work as expected during our testing and deployment procedures. The rest of this guide will assume that you will be using `nvm`. +PeterPortal API targets the latest LTS version of Node.js. If you choose not to use `nvm`, please be aware that code tested with other versions of Node.js may not work as expected during our testing and deployment procedures. The rest of this guide will assume that you will be using `nvm`. After installing nvm per the instructions in its repository, execute the following command: ```shell -nvm install lts/gallium +nvm install lts/* ``` This will install the correct version of Node.js (if it hasn't already been installed) and set it as the active version. diff --git a/apps/docs/package.json b/apps/docs/package.json index 07db974e..b7c96fd5 100644 --- a/apps/docs/package.json +++ b/apps/docs/package.json @@ -36,7 +36,7 @@ "@docusaurus/module-type-aliases": "2.4.3", "@docusaurus/types": "2.4.3", "@tsconfig/docusaurus": "2.0.2", - "@types/node": "18.18.5", + "@types/node": "20.10.4", "typescript": "5.3.2" } } diff --git a/libs/db/prisma/schema.prisma b/libs/db/prisma/schema.prisma index 73496a1e..7e357fd4 100644 --- a/libs/db/prisma/schema.prisma +++ b/libs/db/prisma/schema.prisma @@ -1,6 +1,6 @@ generator client { provider = "prisma-client-js" - binaryTargets = ["native", "debian-openssl-3.0.x", "linux-arm64-openssl-1.0.x"] + binaryTargets = ["native", "debian-openssl-3.0.x", "linux-arm64-openssl-3.0.x"] } datasource db { diff --git a/libs/uc-irvine-api/package.json b/libs/uc-irvine-api/package.json index f8b5af1d..e86f3214 100644 --- a/libs/uc-irvine-api/package.json +++ b/libs/uc-irvine-api/package.json @@ -53,7 +53,7 @@ }, "packageManager": "pnpm@8.11.0", "engines": { - "node": "18", + "node": "20", "pnpm": "8" }, "publishConfig": { diff --git a/package.json b/package.json index 7e93e64e..c5a2d14f 100644 --- a/package.json +++ b/package.json @@ -22,9 +22,9 @@ "@commitlint/cli": "18.4.3", "@commitlint/config-conventional": "18.4.3", "@commitlint/types": "18.4.3", - "@tsconfig/node18": "18.2.2", + "@tsconfig/node20": "20.1.2", "@types/lint-staged": "13.2.2", - "@types/node": "18.18.5", + "@types/node": "20.10.4", "@typescript-eslint/eslint-plugin": "6.13.1", "@typescript-eslint/parser": "6.13.1", "arktype": "1.0.16-alpha", @@ -48,7 +48,7 @@ }, "packageManager": "pnpm@8.11.0", "engines": { - "node": "18", + "node": "20", "pnpm": "8" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d3ed856e..36293072 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -17,15 +17,15 @@ importers: '@commitlint/types': specifier: 18.4.3 version: 18.4.3 - '@tsconfig/node18': - specifier: 18.2.2 - version: 18.2.2 + '@tsconfig/node20': + specifier: 20.1.2 + version: 20.1.2 '@types/lint-staged': specifier: 13.2.2 version: 13.2.2 '@types/node': - specifier: 18.18.5 - version: 18.18.5 + specifier: 20.10.4 + version: 20.10.4 '@typescript-eslint/eslint-plugin': specifier: 6.13.1 version: 6.13.1(@typescript-eslint/parser@6.13.1)(eslint@8.54.0)(typescript@5.3.2) @@ -212,8 +212,8 @@ importers: specifier: 2.0.2 version: 2.0.2 '@types/node': - specifier: 18.18.5 - version: 18.18.5 + specifier: 20.10.4 + version: 20.10.4 typescript: specifier: 5.3.2 version: 5.3.2 @@ -317,8 +317,8 @@ importers: specifier: 8.10.129 version: 8.10.129 '@types/node': - specifier: 18.18.5 - version: 18.18.5 + specifier: 20.10.4 + version: 20.10.4 esbuild: specifier: 0.19.8 version: 0.19.8 @@ -398,8 +398,8 @@ importers: specifier: workspace:^ version: link:../../packages/types '@types/node': - specifier: 18.18.5 - version: 18.18.5 + specifier: 20.10.4 + version: 20.10.4 dotenv-cli: specifier: 7.3.0 version: 7.3.0 @@ -2805,16 +2805,6 @@ packages: conventional-changelog-conventionalcommits: 7.0.2 dev: true - /@commitlint/config-validator@18.4.0: - resolution: {integrity: sha512-1y6qHMU3o4cYQSK+Y9EnmH6H1GRiwQGjnLIUOIKlekrmfc8MrMk1ByNmb8od4vK3qHJAaL/77/5n+1uyyIF5dA==} - engines: {node: '>=v18'} - requiresBuild: true - dependencies: - '@commitlint/types': 18.4.3 - ajv: 8.12.0 - dev: true - optional: true - /@commitlint/config-validator@18.4.3: resolution: {integrity: sha512-FPZZmTJBARPCyef9ohRC9EANiQEKSWIdatx5OlgeHKu878dWwpyeFauVkhzuBRJFcCA4Uvz/FDtlDKs008IHcA==} engines: {node: '>=v18'} @@ -2835,13 +2825,6 @@ packages: lodash.upperfirst: 4.3.1 dev: true - /@commitlint/execute-rule@18.4.0: - resolution: {integrity: sha512-g013SWki6ZWhURBLOSXTaVQGWHdA0QlPJGiW4a+YpThezmJOemvc4LiKVpn13AjSKQ40QnmBqpBrxujOaSo+3A==} - engines: {node: '>=v18'} - requiresBuild: true - dev: true - optional: true - /@commitlint/execute-rule@18.4.3: resolution: {integrity: sha512-t7FM4c+BdX9WWZCPrrbV5+0SWLgT3kCq7e7/GhHCreYifg3V8qyvO127HF796vyFql75n4TFF+5v1asOOWkV1Q==} engines: {node: '>=v18'} @@ -2873,28 +2856,6 @@ packages: '@commitlint/types': 18.4.3 dev: true - /@commitlint/load@18.4.2(typescript@5.3.2): - resolution: {integrity: sha512-CKmzXdF9XwZJoVijAqpUlV9qzZOkyiYni4KuSCtTZVAAVudi9H84cJ4FqZxSwEP9G21vmoJiNrW8G042AsduVg==} - engines: {node: '>=v18'} - requiresBuild: true - dependencies: - '@commitlint/config-validator': 18.4.0 - '@commitlint/execute-rule': 18.4.0 - '@commitlint/resolve-extends': 18.4.0 - '@commitlint/types': 18.4.3 - '@types/node': 18.18.5 - chalk: 4.1.2 - cosmiconfig: 8.3.6(typescript@5.3.2) - cosmiconfig-typescript-loader: 5.0.0(@types/node@18.18.5)(cosmiconfig@8.3.6)(typescript@5.3.2) - lodash.isplainobject: 4.0.6 - lodash.merge: 4.6.2 - lodash.uniq: 4.5.0 - resolve-from: 5.0.0 - transitivePeerDependencies: - - typescript - dev: true - optional: true - /@commitlint/load@18.4.3(typescript@5.3.2): resolution: {integrity: sha512-v6j2WhvRQJrcJaj5D+EyES2WKTxPpxENmNpNG3Ww8MZGik3jWRXtph0QTzia5ZJyPh2ib5aC/6BIDymkUUM58Q==} engines: {node: '>=v18'} @@ -2940,20 +2901,6 @@ packages: minimist: 1.2.8 dev: true - /@commitlint/resolve-extends@18.4.0: - resolution: {integrity: sha512-qhgU6ach+S6sJMD9NjCYiEycOObGhxzWQLQzqlScJCv9zkPs15Bg0ffLXTQ3z7ipXv46XEKYMnSJzjLRw2Tlkg==} - engines: {node: '>=v18'} - requiresBuild: true - dependencies: - '@commitlint/config-validator': 18.4.0 - '@commitlint/types': 18.4.3 - import-fresh: 3.3.0 - lodash.mergewith: 4.6.2 - resolve-from: 5.0.0 - resolve-global: 1.0.0 - dev: true - optional: true - /@commitlint/resolve-extends@18.4.3: resolution: {integrity: sha512-30sk04LZWf8+SDgJrbJCjM90gTg2LxsD9cykCFeFu+JFHvBFq5ugzp2eO/DJGylAdVaqxej3c7eTSE64hR/lnw==} engines: {node: '>=v18'} @@ -4339,7 +4286,7 @@ packages: '@jest/schemas': 29.6.0 '@types/istanbul-lib-coverage': 2.0.4 '@types/istanbul-reports': 3.0.1 - '@types/node': 18.18.5 + '@types/node': 20.10.4 '@types/yargs': 17.0.24 chalk: 4.1.2 dev: false @@ -5235,8 +5182,8 @@ packages: resolution: {integrity: sha512-12HWfYmgUl4M2o76/TFufGtI68wl2k/b8qPrIrG7ci9YJLrpAtadpy897Bz5v29Mlkr7a1Hq4KHdQTKtU+2rhQ==} dev: true - /@tsconfig/node18@18.2.2: - resolution: {integrity: sha512-d6McJeGsuoRlwWZmVIeE8CUA27lu6jLjvv1JzqmpsytOYYbVi1tHZEnwCNVOXnj4pyLvneZlFlpXUK+X9wBWyw==} + /@tsconfig/node20@20.1.2: + resolution: {integrity: sha512-madaWq2k+LYMEhmcp0fs+OGaLFk0OenpHa4gmI4VEmCKX4PJntQ6fnnGADVFrVkBj0wIdAlQnK/MrlYTHsa1gQ==} dev: true /@types/aws-lambda@8.10.129: @@ -5247,26 +5194,26 @@ packages: resolution: {integrity: sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==} dependencies: '@types/connect': 3.4.35 - '@types/node': 18.18.5 + '@types/node': 20.10.4 dev: false /@types/bonjour@3.5.10: resolution: {integrity: sha512-p7ienRMiS41Nu2/igbJxxLDWrSZ0WxM8UQgCeO9KhoVF7cOVFkrKsiDr1EsJIla8vV3oEEjGcz11jc5yimhzZw==} dependencies: - '@types/node': 18.18.5 + '@types/node': 20.10.4 dev: false /@types/connect-history-api-fallback@1.5.0: resolution: {integrity: sha512-4x5FkPpLipqwthjPsF7ZRbOv3uoLUFkTA9G9v583qi4pACvq0uTELrB8OLUzPWUI4IJIyvM85vzkV1nyiI2Lig==} dependencies: '@types/express-serve-static-core': 4.17.35 - '@types/node': 18.18.5 + '@types/node': 20.10.4 dev: false /@types/connect@3.4.35: resolution: {integrity: sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==} dependencies: - '@types/node': 18.18.5 + '@types/node': 20.10.4 dev: false /@types/eslint-scope@3.7.4: @@ -5287,7 +5234,7 @@ packages: /@types/express-serve-static-core@4.17.35: resolution: {integrity: sha512-wALWQwrgiB2AWTT91CB62b6Yt0sNHpznUXeZEcnPU3DRdlDIz74x8Qg1UUYKSVFi+va5vKOLYRBI1bRKiLLKIg==} dependencies: - '@types/node': 18.18.5 + '@types/node': 20.10.4 '@types/qs': 6.9.7 '@types/range-parser': 1.2.4 '@types/send': 0.17.1 @@ -5322,7 +5269,7 @@ packages: /@types/http-proxy@1.17.11: resolution: {integrity: sha512-HC8G7c1WmaF2ekqpnFq626xd3Zz0uvaqFmBJNRZCGEZCXkvSdJoNFn/8Ygbd9fKNQj8UzLdCETaI0UWPAjK7IA==} dependencies: - '@types/node': 18.18.5 + '@types/node': 20.10.4 dev: false /@types/istanbul-lib-coverage@2.0.4: @@ -5351,7 +5298,7 @@ packages: /@types/keyv@3.1.4: resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==} dependencies: - '@types/node': 18.18.5 + '@types/node': 20.10.4 dev: false /@types/lint-staged@13.2.2: @@ -5383,7 +5330,7 @@ packages: /@types/node-fetch@2.6.4: resolution: {integrity: sha512-1ZX9fcN4Rvkvgv4E6PAY5WXUFWFcRWxZa3EW83UjycOB9ljJCedb2CupIP4RZMEwF/M3eTcCihbBRgwtGbg5Rg==} dependencies: - '@types/node': 18.18.5 + '@types/node': 20.10.4 form-data: 3.0.1 dev: false @@ -5393,6 +5340,12 @@ packages: /@types/node@18.18.5: resolution: {integrity: sha512-4slmbtwV59ZxitY4ixUZdy1uRLf9eSIvBWPQxNjhHYWEtn0FryfKpyS2cvADYXTayWdKEIsJengncrVvkI4I6A==} + dev: true + + /@types/node@20.10.4: + resolution: {integrity: sha512-D08YG6rr8X90YB56tSIuBaddy/UXAA9RKJoFvrsnogAum/0pmjkgi4+2nx96A330FmioegBWmEYQ+syqCFaveg==} + dependencies: + undici-types: 5.26.5 /@types/normalize-package-data@2.4.1: resolution: {integrity: sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==} @@ -5455,7 +5408,7 @@ packages: /@types/responselike@1.0.0: resolution: {integrity: sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==} dependencies: - '@types/node': 18.18.5 + '@types/node': 20.10.4 dev: false /@types/retry@0.12.0: @@ -5465,7 +5418,7 @@ packages: /@types/sax@1.2.4: resolution: {integrity: sha512-pSAff4IAxJjfAXUG6tFkO7dsSbTmf8CtUpfhhZ5VhkRpC4628tJhh3+V6H1E+/Gs9piSzYKT5yzHO5M4GG9jkw==} dependencies: - '@types/node': 18.18.5 + '@types/node': 20.10.4 dev: false /@types/scheduler@0.16.3: @@ -5479,7 +5432,7 @@ packages: resolution: {integrity: sha512-Cwo8LE/0rnvX7kIIa3QHCkcuF21c05Ayb0ZfxPiv0W8VRiZiNW/WuRupHKpqqGVGf7SUA44QSOUKaEd9lIrd/Q==} dependencies: '@types/mime': 1.3.2 - '@types/node': 18.18.5 + '@types/node': 20.10.4 dev: false /@types/serve-index@1.9.1: @@ -5492,13 +5445,13 @@ packages: resolution: {integrity: sha512-NUo5XNiAdULrJENtJXZZ3fHtfMolzZwczzBbnAeBbqBwG+LaG6YaJtuwzwGSQZ2wsCrxjEhNNjAkKigy3n8teQ==} dependencies: '@types/mime': 3.0.1 - '@types/node': 18.18.5 + '@types/node': 20.10.4 dev: false /@types/sockjs@0.3.33: resolution: {integrity: sha512-f0KEEe05NvUnat+boPTZ0dgaLZ4SfSouXUgv5noUiefG2ajgKjmETo9ZJyuqsl7dfl2aHlLJUiki6B4ZYldiiw==} dependencies: - '@types/node': 18.18.5 + '@types/node': 20.10.4 dev: false /@types/triple-beam@1.3.2: @@ -5516,7 +5469,7 @@ packages: /@types/ws@8.5.4: resolution: {integrity: sha512-zdQDHKUgcX/zBc4GrwsE/7dVdAD8JR4EuiAXiiUhhfyIJXXb2+PrGshFyeXWQPMmmZ2XxgaqclgpIC7eTXc1mg==} dependencies: - '@types/node': 18.18.5 + '@types/node': 20.10.4 dev: false /@types/yargs-parser@21.0.0: @@ -7248,7 +7201,7 @@ packages: longest: 2.0.1 word-wrap: 1.2.3 optionalDependencies: - '@commitlint/load': 18.4.2(typescript@5.3.2) + '@commitlint/load': 18.4.3(typescript@5.3.2) transitivePeerDependencies: - typescript dev: true @@ -8080,7 +8033,7 @@ packages: resolution: {integrity: sha512-EzV94NYKoO09GLXGjXj9JIlXijVck4ONSr5wiCWDvhsvj5jxSrzTmRU/9C1DyB6uToszLs8aifA6NQ7lEQdvFw==} engines: {node: '>= 0.8'} dependencies: - '@types/node': 18.18.5 + '@types/node': 20.10.4 require-like: 0.1.2 dev: false @@ -9620,7 +9573,7 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/types': 29.5.0 - '@types/node': 18.18.5 + '@types/node': 20.10.4 chalk: 4.1.2 ci-info: 3.8.0 graceful-fs: 4.2.11 @@ -9631,7 +9584,7 @@ packages: resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==} engines: {node: '>= 10.13.0'} dependencies: - '@types/node': 18.18.5 + '@types/node': 20.10.4 merge-stream: 2.0.0 supports-color: 8.1.1 @@ -9639,7 +9592,7 @@ packages: resolution: {integrity: sha512-NcrQnevGoSp4b5kg+akIpthoAFHxPBcb5P6mYPY0fUNT+sSvmtu6jlkEle3anczUKIKEbMxFimk9oTP/tpIPgA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@types/node': 18.18.5 + '@types/node': 20.10.4 jest-util: 29.5.0 merge-stream: 2.0.0 supports-color: 8.1.1 @@ -13132,6 +13085,9 @@ packages: mlly: 1.4.2 dev: true + /undici-types@5.26.5: + resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + /unherit@1.1.3: resolution: {integrity: sha512-Ft16BJcnapDKp0+J/rqFC3Rrk6Y/Ng4nzsC028k2jdDII/rdZ7Wd3pPT/6+vIIxRagwRc9K0IUX0Ra4fKvw+WQ==} dependencies: diff --git a/renovate.json b/renovate.json index 05bee269..4d5a5241 100644 --- a/renovate.json +++ b/renovate.json @@ -40,7 +40,7 @@ { "matchPackageNames": ["node"], "groupName": "Node.js", - "allowedVersions": "<19.0.0" + "allowedVersions": "<21.0.0" } ], "pin": { "commitMessageSuffix": "[skip ci]" } diff --git a/services/websoc-proxy/package.json b/services/websoc-proxy/package.json index 68f81cf7..5ad0689d 100644 --- a/services/websoc-proxy/package.json +++ b/services/websoc-proxy/package.json @@ -17,7 +17,7 @@ "devDependencies": { "@peterportal-api/types": "workspace:^", "@types/aws-lambda": "8.10.129", - "@types/node": "18.18.5", + "@types/node": "20.10.4", "esbuild": "0.19.8" } } diff --git a/services/websoc-scraper-v2/Dockerfile b/services/websoc-scraper-v2/Dockerfile index 7d3525f3..b11e3e83 100644 --- a/services/websoc-scraper-v2/Dockerfile +++ b/services/websoc-scraper-v2/Dockerfile @@ -1,4 +1,4 @@ -FROM node:18-bookworm-slim@sha256:02632fa826cdbdaa5fba25032bd2931fc79348c60110fefea2edf3fc480f39c5 +FROM node:20-bookworm-slim RUN npm i -g tsx RUN apt-get update -y && apt-get install -y openssl USER node diff --git a/tools/cdk/src/constructs/WebsocProxy.ts b/tools/cdk/src/constructs/WebsocProxy.ts index 90fc85eb..51f8cadf 100644 --- a/tools/cdk/src/constructs/WebsocProxy.ts +++ b/tools/cdk/src/constructs/WebsocProxy.ts @@ -30,7 +30,7 @@ export class WebsocProxy extends Construct { functionName, handler: "index.handler", timeout: Duration.seconds(15), - runtime: Runtime.NODEJS_18_X, + runtime: Runtime.NODEJS_20_X, memorySize: 512, }), { diff --git a/tools/cdk/src/constructs/WebsocScraperV2.ts b/tools/cdk/src/constructs/WebsocScraperV2.ts index 32cd4358..74d35dc5 100644 --- a/tools/cdk/src/constructs/WebsocScraperV2.ts +++ b/tools/cdk/src/constructs/WebsocScraperV2.ts @@ -89,7 +89,7 @@ export class WebsocScraperV2 extends Construct { 'var{DescribeTasksCommand:a,ECSClient:e,ListClustersCommand:s,ListTasksCommand:t,StopTaskCommand:n}=require("@aws-sdk/client-ecs");exports.h=async _=>{let d=new e,{clusterArns:i}=await d.send(new s({})),l=i?.filter(a=>a.includes("websoc-scraper-v2"))[0],{taskArns:r}=await d.send(new t({cluster:l})),{tasks:c}=await d.send(new a({cluster:l,tasks:r}));await Promise.all(c?.filter(a=>a.startedAt&&a.startedAt.valueOf()+36e5d.send(new n({cluster:l,task:a.taskArn})))??[])};', ), handler: "index.h", - runtime: Runtime.NODEJS_18_X, + runtime: Runtime.NODEJS_20_X, architecture: Architecture.ARM_64, role: new Role(this, `${id}-auto-restart-role`, { assumedBy: new ServicePrincipal("lambda.amazonaws.com"), diff --git a/tools/grades-updater/package.json b/tools/grades-updater/package.json index 5a152c66..89bf3bce 100644 --- a/tools/grades-updater/package.json +++ b/tools/grades-updater/package.json @@ -19,7 +19,7 @@ }, "devDependencies": { "@peterportal-api/types": "workspace:^", - "@types/node": "18.18.5", + "@types/node": "20.10.4", "dotenv-cli": "7.3.0", "tsx": "4.6.0" } diff --git a/tsconfig.json b/tsconfig.json index 4f2189a6..cc3c2ac9 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "@tsconfig/node18/tsconfig.json", + "extends": "@tsconfig/node20/tsconfig.json", "compilerOptions": { "baseUrl": ".", "declaration": true,