From 60df59f196d50805f6c4dba484fa6f252bc733e2 Mon Sep 17 00:00:00 2001 From: Aponia Date: Mon, 16 Oct 2023 10:18:21 -0700 Subject: [PATCH] =?UTF-8?q?feat:=20=E2=9C=A8=20fourmilier=20devops=20(#96)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Eddy Chen <89349085+ecxyzzy@users.noreply.github.com> --- .eslintrc.cjs | 4 +- .github/workflows/check-typescript.yml | 3 - .github/workflows/deploy-prod.yml | 47 +- .github/workflows/deploy-staging.yml | 32 +- .github/workflows/destroy-staging.yml | 33 +- .npmrc | 3 - ant.config.ts | 91 - apps/api/.gitignore | 6 + apps/api/bronya.config.ts | 311 ++++ apps/api/package.json | 51 + .../api/src/lib/utils.ts | 0 apps/api/src/routes/v1/graphql/+config.ts | 30 + apps/api/src/routes/v1/graphql/+endpoint.ts | 82 + .../src => src/routes/v1/graphql}/lib.ts | 35 +- .../routes/v1/graphql}/resolvers.ts | 4 - .../routes/v1/graphql/schema}/base.graphql | 0 .../v1/graphql/schema}/calendar.graphql | 0 .../routes/v1/graphql/schema}/courses.graphql | 0 .../routes/v1/graphql/schema}/enum.graphql | 0 .../routes/v1/graphql/schema}/grades.graphql | 0 .../v1/graphql/schema}/instructors.graphql | 0 .../routes/v1/graphql/schema}/larc.graphql | 0 .../routes/v1/graphql/schema}/websoc.graphql | 0 .../routes/v1/graphql/schema}/week.graphql | 0 .../routes/v1/rest/calendar/+endpoint.ts} | 36 +- .../routes/v1/rest/calendar}/schema.ts | 2 +- .../src/routes/v1/rest/courses/+endpoint.ts | 46 + .../src => src/routes/v1/rest/courses}/lib.ts | 2 +- .../routes/v1/rest/courses}/schema.ts | 5 +- .../routes/v1/rest/courses/{id}/+endpoint.ts | 49 + .../routes/v1/rest/grades/{id}/+endpoint.ts} | 34 +- .../routes/v1/rest/grades/{id}}/lib.ts | 4 +- .../routes/v1/rest/grades/{id}}/schema.ts | 2 +- .../routes/v1/rest/instructors/+endpoint.ts | 58 + .../routes/v1/rest/instructors}/lib.ts | 0 .../routes/v1/rest/instructors}/schema.ts | 3 +- .../v1/rest/instructors/{id}/+endpoint.ts | 45 + .../routes/v1/rest/larc/+endpoint.ts} | 11 +- .../src => src/routes/v1/rest/larc}/lib.ts | 2 +- .../src => src/routes/v1/rest/larc}/schema.ts | 2 +- apps/api/src/routes/v1/rest/websoc/+config.ts | 42 + .../routes/v1/rest/websoc/+endpoint.ts} | 140 +- .../routes/v1/rest/websoc}/APILambdaClient.ts | 41 +- .../src => src/routes/v1/rest/websoc}/lib.ts | 2 +- .../routes/v1/rest/websoc}/schema.ts | 5 +- .../src/routes/v1/rest/websoc/{id}/+config.ts | 42 + .../routes/v1/rest/websoc/{id}/+endpoint.ts | 134 ++ .../routes/v1/rest/week/+endpoint.ts} | 35 +- .../src => src/routes/v1/rest/week}/lib.ts | 2 +- .../src => src/routes/v1/rest/week}/schema.ts | 0 apps/api/tsconfig.json | 3 + apps/api/v1/graphql/package.json | 22 - apps/api/v1/graphql/src/index.ts | 101 - apps/api/v1/rest/calendar/ant.config.ts | 28 - apps/api/v1/rest/calendar/package.json | 18 - apps/api/v1/rest/courses/ant.config.ts | 28 - apps/api/v1/rest/courses/package.json | 18 - apps/api/v1/rest/courses/src/index.ts | 58 - apps/api/v1/rest/grades/ant.config.ts | 28 - apps/api/v1/rest/grades/package.json | 18 - apps/api/v1/rest/instructors/ant.config.ts | 28 - apps/api/v1/rest/instructors/package.json | 18 - apps/api/v1/rest/instructors/src/index.ts | 72 - apps/api/v1/rest/larc/package.json | 15 - apps/api/v1/rest/websoc/ant.config.ts | 29 - apps/api/v1/rest/websoc/package.json | 21 - apps/api/v1/rest/week/ant.config.ts | 28 - apps/api/v1/rest/week/package.json | 18 - apps/docs/cdk/app.ts | 10 - apps/docs/cdk/cdk.json | 35 - apps/docs/cdk/package.json | 24 - apps/docs/cdk/tsconfig.json | 5 - apps/docs/docusaurus.config.js | 3 +- apps/docs/package.json | 5 +- env.ts | 30 - libs/build-tools/package.json | 11 - libs/build-tools/src/clean-copy.ts | 51 - libs/build-tools/src/index.ts | 2 - libs/build-tools/src/select-delete.ts | 25 - libs/lambda/package.json | 16 + libs/lambda/src/compress.ts | 59 + .../lambda/src}/constants.ts | 0 libs/lambda/src/index.ts | 4 + .../lambda-core => libs/lambda/src}/logger.ts | 2 +- .../internal => libs/lambda/src}/response.ts | 20 +- libs/registrar-api/package.json | 24 - libs/registrar-api/tests/index.test.ts | 51 - libs/registrar-api/tsup.config.ts | 25 - libs/registrar-api/vitest.config.ts | 12 - libs/uc-irvine-api/package.json | 62 + .../src/registrar}/index.ts | 63 +- .../src => uc-irvine-api/src/websoc}/index.ts | 100 +- libs/uc-irvine-api/tsconfig.json | 3 + libs/websoc-api-next/package.json | 25 - libs/websoc-api-next/tests/index.test.ts | 174 -- libs/websoc-api-next/tsup.config.ts | 25 - libs/websoc-api-next/vitest.config.ts | 12 - libs/websoc-utils/package.json | 6 +- libs/websoc-utils/src/index.ts | 4 +- package.json | 20 +- packages/ant-stack/README.md | 3 - packages/ant-stack/cdk.json | 3 - packages/ant-stack/package.json | 86 - packages/ant-stack/src/cdk/index.ts | 66 - packages/ant-stack/src/cdk/stack.ts | 185 -- packages/ant-stack/src/cli/commands/build.ts | 52 - packages/ant-stack/src/cli/commands/create.ts | 107 -- packages/ant-stack/src/cli/commands/dev.ts | 227 --- packages/ant-stack/src/cli/index.ts | 43 - packages/ant-stack/src/config.ts | 118 -- packages/ant-stack/src/lambda-core/index.ts | 5 - .../src/lambda-core/internal/handler.ts | 60 - .../src/lambda-core/internal/request.ts | 142 -- packages/ant-stack/src/templates/index.ts | 21 - packages/ant-stack/src/templates/package.json | 12 - packages/ant-stack/src/utils/compress.ts | 60 - packages/ant-stack/src/utils/index.ts | 4 - .../ant-stack/src/utils/search-projects.ts | 23 - packages/ant-stack/src/utils/search-root.ts | 97 - packages/ant-stack/tsconfig.json | 7 - packages/ant-stack/tsup.config.ts | 34 - .../LICENSE | 0 .../README.md | 0 .../index.ts | 0 .../package.json | 2 +- .../types/calendar.ts | 0 .../types/constants.ts | 0 .../types/courses.ts | 0 .../types/grades.ts | 0 .../types/instructor.ts | 0 .../types/larc.ts | 0 .../types/response.ts | 0 .../types/websoc.ts | 0 .../types/week.ts | 0 packages/websoc-fuzzy-search/package.json | 2 + packages/websoc-fuzzy-search/setup.ts | 4 +- pnpm-lock.yaml | 1621 ++++++++--------- renovate.json | 6 - services/cdk/cdk.json | 21 - services/cdk/package.json | 27 - services/cdk/src/app.ts | 14 - services/cdk/tsconfig.json | 5 - services/websoc-proxy/package.json | 9 +- services/websoc-proxy/src/index.ts | 6 +- services/websoc-scraper-v2/index.ts | 12 +- services/websoc-scraper-v2/package.json | 7 +- tools/cdk/package.json | 32 + tools/cdk/src/app/docs.ts | 49 + tools/cdk/src/app/services.ts | 23 + .../cdk/src/constructs/WebsocProxy.ts | 4 + .../cdk/src/constructs/WebsocScraperV2.ts | 0 .../cdk/src/stacks/docs.ts | 50 +- .../cdk/src/stacks/services.ts | 11 +- tools/cdk/src/wait-for-stack-idle.ts | 50 + tools/cdk/tsconfig.json | 3 + tools/grades-updater/package.json | 8 +- tools/grades-updater/src/lib.ts | 2 +- tools/grades-updater/src/populate-ge.ts | 2 +- tools/grades-updater/src/sanitize-data.ts | 8 +- tools/grades-updater/src/upload-data.ts | 2 +- tools/registrar-scraper/package.json | 8 +- tools/registrar-scraper/src/index.ts | 2 +- .../src/instructor-scraper/index.ts | 2 +- tools/registrar-scraper/src/lib.ts | 4 +- .../src/prereq-scraper/index.ts | 2 +- tsconfig.json | 35 +- turbo.json | 3 +- 167 files changed, 2319 insertions(+), 4007 deletions(-) delete mode 100644 ant.config.ts create mode 100644 apps/api/.gitignore create mode 100644 apps/api/bronya.config.ts create mode 100644 apps/api/package.json rename packages/ant-stack/src/utils/schema-tools.ts => apps/api/src/lib/utils.ts (100%) create mode 100644 apps/api/src/routes/v1/graphql/+config.ts create mode 100644 apps/api/src/routes/v1/graphql/+endpoint.ts rename apps/api/{v1/graphql/src => src/routes/v1/graphql}/lib.ts (66%) rename apps/api/{v1/graphql/src => src/routes/v1/graphql}/resolvers.ts (88%) rename apps/api/{v1/graphql/src/graphql => src/routes/v1/graphql/schema}/base.graphql (100%) rename apps/api/{v1/graphql/src/graphql => src/routes/v1/graphql/schema}/calendar.graphql (100%) rename apps/api/{v1/graphql/src/graphql => src/routes/v1/graphql/schema}/courses.graphql (100%) rename apps/api/{v1/graphql/src/graphql => src/routes/v1/graphql/schema}/enum.graphql (100%) rename apps/api/{v1/graphql/src/graphql => src/routes/v1/graphql/schema}/grades.graphql (100%) rename apps/api/{v1/graphql/src/graphql => src/routes/v1/graphql/schema}/instructors.graphql (100%) rename apps/api/{v1/graphql/src/graphql => src/routes/v1/graphql/schema}/larc.graphql (100%) rename apps/api/{v1/graphql/src/graphql => src/routes/v1/graphql/schema}/websoc.graphql (100%) rename apps/api/{v1/graphql/src/graphql => src/routes/v1/graphql/schema}/week.graphql (100%) rename apps/api/{v1/rest/calendar/src/index.ts => src/routes/v1/rest/calendar/+endpoint.ts} (65%) rename apps/api/{v1/rest/calendar/src => src/routes/v1/rest/calendar}/schema.ts (87%) create mode 100644 apps/api/src/routes/v1/rest/courses/+endpoint.ts rename apps/api/{v1/rest/courses/src => src/routes/v1/rest/courses}/lib.ts (98%) rename apps/api/{v1/rest/courses/src => src/routes/v1/rest/courses}/schema.ts (84%) create mode 100644 apps/api/src/routes/v1/rest/courses/{id}/+endpoint.ts rename apps/api/{v1/rest/grades/src/index.ts => src/routes/v1/rest/grades/{id}/+endpoint.ts} (86%) rename apps/api/{v1/rest/grades/src => src/routes/v1/rest/grades/{id}}/lib.ts (98%) rename apps/api/{v1/rest/grades/src => src/routes/v1/rest/grades/{id}}/schema.ts (89%) create mode 100644 apps/api/src/routes/v1/rest/instructors/+endpoint.ts rename apps/api/{v1/rest/instructors/src => src/routes/v1/rest/instructors}/lib.ts (100%) rename apps/api/{v1/rest/instructors/src => src/routes/v1/rest/instructors}/schema.ts (90%) create mode 100644 apps/api/src/routes/v1/rest/instructors/{id}/+endpoint.ts rename apps/api/{v1/rest/larc/src/index.ts => src/routes/v1/rest/larc/+endpoint.ts} (86%) rename apps/api/{v1/rest/larc/src => src/routes/v1/rest/larc}/lib.ts (93%) rename apps/api/{v1/rest/larc/src => src/routes/v1/rest/larc}/schema.ts (87%) create mode 100644 apps/api/src/routes/v1/rest/websoc/+config.ts rename apps/api/{v1/rest/websoc/src/index.ts => src/routes/v1/rest/websoc/+endpoint.ts} (50%) rename apps/api/{v1/rest/websoc/src => src/routes/v1/rest/websoc}/APILambdaClient.ts (79%) rename apps/api/{v1/rest/websoc/src => src/routes/v1/rest/websoc}/lib.ts (99%) rename apps/api/{v1/rest/websoc/src => src/routes/v1/rest/websoc}/schema.ts (97%) create mode 100644 apps/api/src/routes/v1/rest/websoc/{id}/+config.ts create mode 100644 apps/api/src/routes/v1/rest/websoc/{id}/+endpoint.ts rename apps/api/{v1/rest/week/src/index.ts => src/routes/v1/rest/week/+endpoint.ts} (86%) rename apps/api/{v1/rest/week/src => src/routes/v1/rest/week}/lib.ts (92%) rename apps/api/{v1/rest/week/src => src/routes/v1/rest/week}/schema.ts (100%) create mode 100644 apps/api/tsconfig.json delete mode 100644 apps/api/v1/graphql/package.json delete mode 100644 apps/api/v1/graphql/src/index.ts delete mode 100644 apps/api/v1/rest/calendar/ant.config.ts delete mode 100644 apps/api/v1/rest/calendar/package.json delete mode 100644 apps/api/v1/rest/courses/ant.config.ts delete mode 100644 apps/api/v1/rest/courses/package.json delete mode 100644 apps/api/v1/rest/courses/src/index.ts delete mode 100644 apps/api/v1/rest/grades/ant.config.ts delete mode 100644 apps/api/v1/rest/grades/package.json delete mode 100644 apps/api/v1/rest/instructors/ant.config.ts delete mode 100644 apps/api/v1/rest/instructors/package.json delete mode 100644 apps/api/v1/rest/instructors/src/index.ts delete mode 100644 apps/api/v1/rest/larc/package.json delete mode 100644 apps/api/v1/rest/websoc/ant.config.ts delete mode 100644 apps/api/v1/rest/websoc/package.json delete mode 100644 apps/api/v1/rest/week/ant.config.ts delete mode 100644 apps/api/v1/rest/week/package.json delete mode 100644 apps/docs/cdk/app.ts delete mode 100644 apps/docs/cdk/cdk.json delete mode 100644 apps/docs/cdk/package.json delete mode 100644 apps/docs/cdk/tsconfig.json delete mode 100644 env.ts delete mode 100644 libs/build-tools/package.json delete mode 100644 libs/build-tools/src/clean-copy.ts delete mode 100644 libs/build-tools/src/index.ts delete mode 100644 libs/build-tools/src/select-delete.ts create mode 100644 libs/lambda/package.json create mode 100644 libs/lambda/src/compress.ts rename {packages/ant-stack/src/lambda-core => libs/lambda/src}/constants.ts (100%) create mode 100644 libs/lambda/src/index.ts rename {packages/ant-stack/src/lambda-core => libs/lambda/src}/logger.ts (97%) rename {packages/ant-stack/src/lambda-core/internal => libs/lambda/src}/response.ts (88%) delete mode 100644 libs/registrar-api/package.json delete mode 100644 libs/registrar-api/tests/index.test.ts delete mode 100644 libs/registrar-api/tsup.config.ts delete mode 100644 libs/registrar-api/vitest.config.ts create mode 100644 libs/uc-irvine-api/package.json rename libs/{registrar-api/src => uc-irvine-api/src/registrar}/index.ts (86%) rename libs/{websoc-api-next/src => uc-irvine-api/src/websoc}/index.ts (95%) create mode 100644 libs/uc-irvine-api/tsconfig.json delete mode 100644 libs/websoc-api-next/package.json delete mode 100644 libs/websoc-api-next/tests/index.test.ts delete mode 100644 libs/websoc-api-next/tsup.config.ts delete mode 100644 libs/websoc-api-next/vitest.config.ts delete mode 100644 packages/ant-stack/README.md delete mode 100644 packages/ant-stack/cdk.json delete mode 100644 packages/ant-stack/package.json delete mode 100644 packages/ant-stack/src/cdk/index.ts delete mode 100644 packages/ant-stack/src/cdk/stack.ts delete mode 100644 packages/ant-stack/src/cli/commands/build.ts delete mode 100644 packages/ant-stack/src/cli/commands/create.ts delete mode 100644 packages/ant-stack/src/cli/commands/dev.ts delete mode 100644 packages/ant-stack/src/cli/index.ts delete mode 100644 packages/ant-stack/src/config.ts delete mode 100644 packages/ant-stack/src/lambda-core/index.ts delete mode 100644 packages/ant-stack/src/lambda-core/internal/handler.ts delete mode 100644 packages/ant-stack/src/lambda-core/internal/request.ts delete mode 100644 packages/ant-stack/src/templates/index.ts delete mode 100644 packages/ant-stack/src/templates/package.json delete mode 100644 packages/ant-stack/src/utils/compress.ts delete mode 100644 packages/ant-stack/src/utils/index.ts delete mode 100644 packages/ant-stack/src/utils/search-projects.ts delete mode 100644 packages/ant-stack/src/utils/search-root.ts delete mode 100644 packages/ant-stack/tsconfig.json delete mode 100644 packages/ant-stack/tsup.config.ts rename packages/{peterportal-api-next-types => types}/LICENSE (100%) rename packages/{peterportal-api-next-types => types}/README.md (100%) rename packages/{peterportal-api-next-types => types}/index.ts (100%) rename packages/{peterportal-api-next-types => types}/package.json (74%) rename packages/{peterportal-api-next-types => types}/types/calendar.ts (100%) rename packages/{peterportal-api-next-types => types}/types/constants.ts (100%) rename packages/{peterportal-api-next-types => types}/types/courses.ts (100%) rename packages/{peterportal-api-next-types => types}/types/grades.ts (100%) rename packages/{peterportal-api-next-types => types}/types/instructor.ts (100%) rename packages/{peterportal-api-next-types => types}/types/larc.ts (100%) rename packages/{peterportal-api-next-types => types}/types/response.ts (100%) rename packages/{peterportal-api-next-types => types}/types/websoc.ts (100%) rename packages/{peterportal-api-next-types => types}/types/week.ts (100%) delete mode 100644 services/cdk/cdk.json delete mode 100644 services/cdk/package.json delete mode 100644 services/cdk/src/app.ts delete mode 100644 services/cdk/tsconfig.json create mode 100644 tools/cdk/package.json create mode 100644 tools/cdk/src/app/docs.ts create mode 100644 tools/cdk/src/app/services.ts rename {services => tools}/cdk/src/constructs/WebsocProxy.ts (99%) rename {services => tools}/cdk/src/constructs/WebsocScraperV2.ts (100%) rename apps/docs/cdk/DocsStack.ts => tools/cdk/src/stacks/docs.ts (66%) rename services/cdk/src/stack.ts => tools/cdk/src/stacks/services.ts (81%) create mode 100644 tools/cdk/src/wait-for-stack-idle.ts create mode 100644 tools/cdk/tsconfig.json diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 3a7685fc..27b4df27 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -7,7 +7,7 @@ const config = { root: true, parser: "@typescript-eslint/parser", parserOptions: { - project: ["./tsconfig.json", "./apps/docs/tsconfig.json", "./apps/docs/cdk/tsconfig.json"], + project: ["./tsconfig.json", "./apps/docs/tsconfig.json"], sourceType: "module", }, plugins: ["import", "@typescript-eslint"], @@ -38,7 +38,7 @@ const config = { }, ], }, - ignorePatterns: ["*.config.*", "*.cjs"], + ignorePatterns: ["docusaurus.config.js", "*.cjs"], env: { es2020: true, node: true, diff --git a/.github/workflows/check-typescript.yml b/.github/workflows/check-typescript.yml index 88ddb1d5..3f9a5eff 100644 --- a/.github/workflows/check-typescript.yml +++ b/.github/workflows/check-typescript.yml @@ -21,8 +21,5 @@ jobs: - name: Install dependencies run: pnpm install --frozen-lockfile - - name: Build AntStack - run: pnpm build --filter="ant-stack" - - name: Run TypeScript type checking run: pnpm ci:tsc diff --git a/.github/workflows/deploy-prod.yml b/.github/workflows/deploy-prod.yml index a09d9336..3f032dcc 100644 --- a/.github/workflows/deploy-prod.yml +++ b/.github/workflows/deploy-prod.yml @@ -50,24 +50,11 @@ jobs: # Ensure NODE_ENV != production so pnpm will install devDependencies!!! run: NODE_ENV=development pnpm install --frozen-lockfile - - name: Build AntStack - run: pnpm build --filter="ant-stack" - - - name: Link AntStack - run: pnpm link ant-stack - - - name: Build API assets - run: pnpm build --filter="api-*" - - - name: Wait for API CloudFormation stack to idle - uses: ./.github/actions/wait-for-cloudformation-stack-idle - with: - stack_name: peterportal-api-next-prod - aws_access_key_id: ${{ secrets.AWS_ACCESS_KEY_ID }} - aws_secret_access_key: ${{ secrets.AWS_SECRETS_ACCESS_KEY }} + - name: Build API + run: pnpm build --filter="@apps/api" - name: Deploy API CloudFormation stack - run: pnpm run deploy --filter="ant-stack" + run: pnpm --filter="@apps/api" cdk-app deploy deploy-documentation: name: Deploy documentation CloudFormation stack and production environment @@ -89,18 +76,11 @@ jobs: # Ensure NODE_ENV != production so pnpm will install devDependencies!!! run: NODE_ENV=development pnpm install --frozen-lockfile - - name: Build documentation CDK - run: pnpm build --filter="docs-cdk" - - - name: Wait for documentation CloudFormation stack to idle - uses: ./.github/actions/wait-for-cloudformation-stack-idle - with: - stack_name: peterportal-api-next-docs-prod - aws_access_key_id: ${{ secrets.AWS_ACCESS_KEY_ID }} - aws_secret_access_key: ${{ secrets.AWS_SECRETS_ACCESS_KEY }} + - name: Build documentation + run: pnpm build --filter="@apps/docs" - name: Deploy documentation CloudFormation stack - run: pnpm run deploy --filter="docs-cdk" + run: pnpm --filter="@tools/cdk" docs deploy deploy-services: name: Deploy services CloudFormation stack and production environment @@ -121,21 +101,8 @@ jobs: # Ensure NODE_ENV != production so pnpm will install devDependencies!!! run: NODE_ENV=development pnpm install --frozen-lockfile - - name: Build AntStack - run: pnpm build --filter="ant-stack" - - - name: Link AntStack - run: pnpm link ant-stack - - name: Build services run: pnpm build --filter="@services/*" - - name: Wait for services CloudFormation stack to idle - uses: ./.github/actions/wait-for-cloudformation-stack-idle - with: - stack_name: peterportal-api-next-services-prod - aws_access_key_id: ${{ secrets.AWS_ACCESS_KEY_ID }} - aws_secret_access_key: ${{ secrets.AWS_SECRETS_ACCESS_KEY }} - - name: Deploy stack - run: pnpm run deploy --filter="@services/*" + run: pnpm --filter="@tools/cdk" services deploy diff --git a/.github/workflows/deploy-staging.yml b/.github/workflows/deploy-staging.yml index dd0543ae..24779a8c 100644 --- a/.github/workflows/deploy-staging.yml +++ b/.github/workflows/deploy-staging.yml @@ -52,24 +52,11 @@ jobs: - name: Install dependencies run: pnpm install --frozen-lockfile - - name: Build AntStack - run: pnpm build --filter="ant-stack" - - - name: Link AntStack - run: pnpm link ant-stack - - - name: Build API assets - run: pnpm build --filter="api-*" - - - name: Wait for API CloudFormation stack to stabilize - uses: ./.github/actions/wait-for-cloudformation-stack-idle - with: - stack_name: peterportal-api-next-staging-${{ github.event.pull_request.number }} - aws_access_key_id: ${{ secrets.AWS_ACCESS_KEY_ID }} - aws_secret_access_key: ${{ secrets.AWS_SECRETS_ACCESS_KEY }} + - name: Build API + run: pnpm build --filter="@apps/api" - name: Deploy API CloudFormation stack - run: pnpm run deploy --filter="ant-stack" + run: pnpm --filter="@apps/api" cdk-app deploy deploy-documentation: name: Deploy documentation CloudFormation stack and staging environment for pull request @@ -92,15 +79,8 @@ jobs: - name: Install dependencies run: pnpm install --frozen-lockfile - - name: Build documentation CDK - run: pnpm build --filter="docs-cdk" - - - name: Wait for documentation CloudFormation stack to idle - uses: ./.github/actions/wait-for-cloudformation-stack-idle - with: - stack_name: peterportal-api-next-docs-staging-${{ github.event.pull_request.number }} - aws_access_key_id: ${{ secrets.AWS_ACCESS_KEY_ID }} - aws_secret_access_key: ${{ secrets.AWS_SECRETS_ACCESS_KEY }} + - name: Build documentation + run: pnpm build --filter="@apps/docs" - name: Deploy documentation CloudFormation stack - run: pnpm run deploy --filter="docs-cdk" + run: pnpm --filter="@tools/cdk" docs deploy diff --git a/.github/workflows/destroy-staging.yml b/.github/workflows/destroy-staging.yml index e0b9f333..ebd13e2a 100644 --- a/.github/workflows/destroy-staging.yml +++ b/.github/workflows/destroy-staging.yml @@ -48,25 +48,11 @@ jobs: - name: Install dependencies run: pnpm install --frozen-lockfile - - name: Build AntStack - run: pnpm build --filter="ant-stack" - - - name: Link AntStack - run: pnpm link ant-stack - - # Do not remove this step, as the API assets are required for `cdk destroy` to work properly. - - name: Build API assets - run: pnpm build --filter="api-*" - - - name: Wait for API CloudFormation stack to idle - uses: ./.github/actions/wait-for-cloudformation-stack-idle - with: - stack_name: peterportal-api-next-staging-${{ github.event.pull_request.number }} - aws_access_key_id: ${{ secrets.AWS_ACCESS_KEY_ID }} - aws_secret_access_key: ${{ secrets.AWS_SECRETS_ACCESS_KEY }} + - name: Build API + run: pnpm build --filter="@apps/api" - name: Destroy API CloudFormation stack - run: pnpm destroy --filter="ant-stack" + run: pnpm --filter="@apps/api" cdk-app destroy - name: Set API staging environment to inactive uses: strumwolf/delete-deployment-environment@195215d2c5602aee6fb4b9cf0853970decca9e7a # v2.3.0 @@ -92,18 +78,11 @@ jobs: - name: Install dependencies run: pnpm install --frozen-lockfile - - name: Build documentation CDK - run: pnpm build --filter="docs-cdk" - - - name: Wait for documentation CloudFormation stack to idle - uses: ./.github/actions/wait-for-cloudformation-stack-idle - with: - stack_name: peterportal-api-next-docs-staging-${{ github.event.pull_request.number }} - aws_access_key_id: ${{ secrets.AWS_ACCESS_KEY_ID }} - aws_secret_access_key: ${{ secrets.AWS_SECRETS_ACCESS_KEY }} + - name: Build documentation + run: pnpm build --filter="@apps/docs" - name: Destroy documentation CloudFormation stack - run: pnpm destroy --filter="docs-cdk" + run: pnpm --filter="@tools/cdk" docs destroy - name: Set documentation staging environment as inactive uses: strumwolf/delete-deployment-environment@195215d2c5602aee6fb4b9cf0853970decca9e7a # v2.3.0 diff --git a/.npmrc b/.npmrc index d16b485a..c42da845 100644 --- a/.npmrc +++ b/.npmrc @@ -1,4 +1 @@ -enable-pre-post-scripts = true -shamefully-hoist = true engine-strict = true -link-workspace-packages = true diff --git a/ant.config.ts b/ant.config.ts deleted file mode 100644 index 05a7992e..00000000 --- a/ant.config.ts +++ /dev/null @@ -1,91 +0,0 @@ -/** - * Bruh, using the {@link defineConfig} helper makes {@link loadConfig} take so much longer!! - * (JITI has to just-in-time compile __ALL__ the TypeScript) - * import { defineConfig } from 'peterportal-api-sst' - * export default defineConfig({ ... }) - */ -import type { AntConfig, defineConfig } from "ant-stack/config"; -import type { loadConfig } from "unconfig"; - -import env from "./env"; -import { - Effect, - ManagedPolicy, - PolicyDocument, - PolicyStatement, - ServicePrincipal, -} from "aws-cdk-lib/aws-iam"; - -/** - * @see https://github.com/evanw/esbuild/issues/1921#issuecomment-1491470829 - */ -const js = `\ -import * as path from 'path'; -import { fileURLToPath } from 'url'; -import { createRequire as topLevelCreateRequire } from 'module'; -const require = topLevelCreateRequire(import.meta.url); -const __filename = fileURLToPath(import.meta.url); -const __dirname = path.dirname(__filename); -`; - -export const inDir = "./src"; -export const outDir = "./dist"; -export const entryFileName = "index"; - -/** - * Just using types is a lot faster!! - */ -const config: AntConfig = { - packageManager: "pnpm", - port: 8080, - aws: { - id: "peterportal-api-next", - zoneName: "peterportal.org", - routeRolePropsMapping: { - "v1-rest-websoc": { - assumedBy: new ServicePrincipal("lambda.amazonaws.com"), - managedPolicies: [ - ManagedPolicy.fromAwsManagedPolicyName("service-role/AWSLambdaBasicExecutionRole"), - ], - inlinePolicies: { - lambdaInvokePolicy: new PolicyDocument({ - statements: [ - new PolicyStatement({ - effect: Effect.ALLOW, - resources: [ - `arn:aws:lambda:${process.env.AWS_REGION}:${process.env.ACCOUNT_ID}:function:peterportal-api-next-services-prod-websoc-proxy-function`, - ], - actions: ["lambda:InvokeFunction"], - }), - ], - }), - }, - }, - }, - }, - env, - directory: "apps/api", - esbuild: { - entryPoints: [`${inDir}/${entryFileName}.ts`], - external: ["@aws-sdk/client-lambda"], - outdir: outDir, - platform: "node", - format: "esm", - target: "esnext", - bundle: true, - minify: true, - assetNames: "[name]", - loader: { - ".env": "copy", - }, - banner: { js }, - }, - runtime: { - entryFile: `${entryFileName}.js`, - entryHandlersName: "InternalHandlers", - lambdaCoreFile: "lambda-core.js", - nodeRuntimeFile: "lambda-node-runtime.js", - }, -}; - -export default config; diff --git a/apps/api/.gitignore b/apps/api/.gitignore new file mode 100644 index 00000000..cc4e7e3f --- /dev/null +++ b/apps/api/.gitignore @@ -0,0 +1,6 @@ +node_modules + +# Keep environment variables out of version control +.env + +.bronya diff --git a/apps/api/bronya.config.ts b/apps/api/bronya.config.ts new file mode 100644 index 00000000..d53e8d87 --- /dev/null +++ b/apps/api/bronya.config.ts @@ -0,0 +1,311 @@ +import { chmodSync, copyFileSync } from "node:fs"; +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 { logger } from "@libs/lambda"; +import { LambdaIntegration, ResponseType } from "aws-cdk-lib/aws-apigateway"; +import { Certificate } from "aws-cdk-lib/aws-certificatemanager"; +import { RuleTargetInput, Rule, Schedule } from "aws-cdk-lib/aws-events"; +import { LambdaFunction } from "aws-cdk-lib/aws-events-targets"; +import { Architecture, Code, Function as AwsLambdaFunction, Runtime } from "aws-cdk-lib/aws-lambda"; +import { ARecord, HostedZone, RecordTarget } from "aws-cdk-lib/aws-route53"; +import { ApiGateway } from "aws-cdk-lib/aws-route53-targets"; +import { App, Stack, Duration } from "aws-cdk-lib/core"; +import { config } from "dotenv"; +import type { BuildOptions } from "esbuild"; + +/** + * Whether we're executing in CDK. + * + * During development (not in CDK) ... + * - Bronya.js is responsible for handling the development server. + * - The exported `main` function is imported and then called to get the CDK app. + * - No AWS constructs are actually allocated, since this requires certain files to be built that + * may not exist during development. + * + * During deployment (in CDK) ... + * - The `cdk` CLI tool is responsible for deploying the CloudFormation stack. + * - The CLI expects the app to be created at the top level, + * so the main function is also called immediately after its declaration. + * - All the AWS constructs are allocated, and relevant transformations are applied before + * uploading the code via AWS CloudFormation. + */ +const executingInCdk = isCdk(); + +config({ + path: "../../.env", +}); + +/** + * @see https://github.com/evanw/esbuild/issues/1921#issuecomment-1623640043 + */ +// language=JavaScript +const js = ` + import topLevelModule from "node:module"; + import topLevelUrl from "node:url"; + import topLevelPath from "node:path"; + + const require = topLevelModule.createRequire(import.meta.url); + const __filename = topLevelUrl.fileURLToPath(import.meta.url); + const __dirname = topLevelPath.dirname(__filename); +`; + +const projectRoot = process.cwd(); + +/** + * Where @libs/db is located. + */ +const libsDbDirectory = resolve(projectRoot, "..", "..", "libs", "db"); + +/** + * Where all Prisma related files are located. + */ +const prismaClientDirectory = resolve(libsDbDirectory, "node_modules", "prisma"); + +/** + * Name of the schema file. + */ +const prismaSchemaFile = "schema.prisma"; + +/** + * Where the prisma schema is located. + */ +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"; + +/** + * The body of a warming request. + * + * TODO: actually recognize warming requests in the route handlers. + */ +const warmingRequestBody = { body: "warming request" }; + +/** + * Shared ESBuild options. + */ +export const esbuildOptions: BuildOptions = { + format: "esm", + platform: "node", + bundle: true, + minify: true, + banner: { js }, + + /** + * @remarks + * For Bronya.js: this is specified in order to guarantee that the file is interpreted as ESM. + * However, the framework will continue to assume `handler.js` is the entrypoint. + * + * @RFC What would be the best way to resolve these two values? + */ + outExtension: { ".js": ".mjs" }, +}; + +/** + * Shared construct props. + */ +export const constructs: ApiConstructProps = { + functionPlugin: ({ functionProps, handler }, scope) => { + const warmingTarget = new LambdaFunction(handler, { + event: RuleTargetInput.fromObject(warmingRequestBody), + }); + + const warmingRule = new Rule(scope, `${functionProps.functionName}-warming-rule`, { + schedule: Schedule.rate(Duration.minutes(5)), + }); + + warmingRule.addTarget(warmingTarget); + }, + lambdaUpload: (directory) => { + copyFileSync( + join(prismaClientDirectory, prismaQueryEngineFile), + join(directory, prismaQueryEngineFile), + ); + + chmodSync(join(directory, prismaQueryEngineFile), 0o755); + + copyFileSync(prismaSchema, join(directory, prismaSchemaFile)); + }, + restApiProps: () => ({ disableExecuteApiEndpoint: true, binaryMediaTypes: ["*/*"] }), +}; + +/** + * The backend API stack. i.e. AWS Lambda, API Gateway. + */ +class ApiStack extends Stack { + public api: Api; + + constructor(scope: App, id: string, stage: string) { + super(scope, id); + + this.api = new Api(this, id, { + directory: "src/routes", + plugins: createApiCliPlugins({ + dev: { + hooks: { + transformExpressParams: ({ req }) => { + logger.info(`Path params: ${JSON.stringify(req.params)}`); + logger.info(`Query: ${JSON.stringify(req.query)}`); + logger.info(`Body: ${JSON.stringify(req.body)}`); + logger.info(`Referer: ${req.headers.referer}`); + }, + }, + }, + }), + + /** + * @remarks + * For Bronya.js: although ESBuild specified that "js" -> "mjs", the framework + * still assumes that the entrypoint is `handler.js` unless explicitly specified. + */ + exitPoint: "handler.mjs", + + constructs, + environment: { + DATABASE_URL: process.env["DATABASE_URL"] ?? "", + NODE_ENV: process.env["NODE_ENV"] ?? "", + STAGE: stage, + }, + esbuild: esbuildOptions, + }); + } +} + +function getStage() { + if (!process.env.NODE_ENV) { + throw new Error("NODE_ENV not set."); + } + switch (process.env.NODE_ENV) { + case "production": + return "prod"; + case "staging": + if (!process.env.PR_NUM) { + throw new Error("NODE_ENV was set to staging, but a PR number was not provided."); + } + return `staging-${process.env.PR_NUM}`; + case "development": + return "dev"; + default: + throw new Error( + "Invalid NODE_ENV specified. Valid values are 'production', 'staging', and 'development'.", + ); + } +} + +/** + * Bronya requires a default export or exported named `main` function that returns an {@link App} + * in order to support development functionality. + */ +export async function main(): Promise { + const id = "peterportal-api-next"; + + const zoneName = "peterportal.org"; + + const app = new App(); + + const stage = getStage(); + + const stack = new ApiStack(app, `${id}-${stage}`, stage); + + await stack.api.init(); + + // In development mode, return the app so that Bronya can handle the development server. + // The app is not synthesized, and no AWS resources are allocated. + if (!executingInCdk) { + return app; + } + + if (stage === "dev") { + throw new Error("Cannot deploy this app in the development environment."); + } + + // In deployment mode, synthesize all the AWS resources. + const synthesized = await stack.api.synth(); + + /** + * Add gateway responses for 5xx and 404 errors, so that they remain compliant + * with the {@link `ErrorResponse`} type. + */ + synthesized.api.addGatewayResponse(`${id}-${stage}-5xx`, { + type: ResponseType.DEFAULT_5XX, + statusCode: "500", + templates: { + "application/json": JSON.stringify({ + timestamp: "$context.requestTime", + requestId: "$context.requestId", + statusCode: 500, + error: "Internal Server Error", + message: "An unknown error has occurred. Please try again.", + }), + }, + }); + + synthesized.api.addGatewayResponse(`${id}-${stage}-404`, { + type: ResponseType.MISSING_AUTHENTICATION_TOKEN, + statusCode: "404", + templates: { + "application/json": JSON.stringify({ + timestamp: "$context.requestTime", + requestId: "$context.requestId", + statusCode: 404, + error: "Not Found", + message: "The requested resource could not be found.", + }), + }, + }); + + /** + * Define the CORS response headers integration and add it to all endpoints. + * This is necessary since we hacked API Gateway to be able to serve binary data. + */ + const corsIntegration = new LambdaIntegration( + new AwsLambdaFunction(synthesized.api, `${id}-${stage}-options-handler`, { + code: Code.fromInline( + // language=JavaScript + '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, + architecture: Architecture.ARM_64, + }), + ); + + synthesized.api.methods.forEach((apiMethod) => { + try { + apiMethod.resource.addMethod("OPTIONS", corsIntegration); + } catch { + // no-op + } + }); + + // Set up the custom domain name and A record for the API. + synthesized.api.addDomainName(`${id}-${stage}-domain`, { + domainName: `${stage === "prod" ? "" : `${stage}.`}api-next.peterportal.org`, + certificate: Certificate.fromCertificateArn( + synthesized.api, + "peterportal-cert", + process.env.CERTIFICATE_ARN ?? "", + ), + }); + + new ARecord(synthesized.api, `${id}-${stage}-a-record`, { + zone: HostedZone.fromHostedZoneAttributes(synthesized.api, "peterportal-hosted-zone", { + zoneName, + hostedZoneId: process.env.HOSTED_ZONE_ID ?? "", + }), + recordName: `${stage === "prod" ? "" : `${stage}.`}api-next`, + target: RecordTarget.fromAlias(new ApiGateway(synthesized.api)), + }); + + return app; +} + +if (executingInCdk) { + // Sike, looks like even though we have top-level await, the dev server won't start with it :( + main().then(); +} diff --git a/apps/api/package.json b/apps/api/package.json new file mode 100644 index 00000000..59e36d1a --- /dev/null +++ b/apps/api/package.json @@ -0,0 +1,51 @@ +{ + "name": "@apps/api", + "version": "0.0.0", + "private": true, + "description": "The workspace for PeterPortal API", + "keywords": [], + "homepage": "https://github.com/icssc/peterportal-api-next", + "bugs": { + "url": "https://github.com/icssc/peterportal-api-next/issues" + }, + "repository": { + "type": "git", + "url": "https://github.com/icssc/peterportal-api-next", + "directory": "apps/api" + }, + "license": "MIT", + "type": "module", + "scripts": { + "cdk-app": "cdk --app 'npx tsx bronya.config.ts' --require-approval never", + "dev": "bunny dev-api" + }, + "dependencies": { + "@apollo/server": "4.9.2", + "@aws-sdk/client-lambda": "3.398.0", + "@graphql-tools/load-files": "7.0.0", + "@graphql-tools/merge": "9.0.0", + "@graphql-tools/utils": "10.0.5", + "@libs/db": "workspace:^", + "@libs/lambda": "workspace:^", + "@libs/uc-irvine-api": "workspace:^", + "@libs/websoc-utils": "workspace:^", + "@peterportal-api/types": "workspace:^", + "@services/websoc-proxy": "workspace:^", + "aws-cdk-lib": "2.93.0", + "cheerio": "1.0.0-rc.12", + "cross-fetch": "4.0.0", + "graphql": "16.8.0", + "zod": "3.22.2" + }, + "devDependencies": { + "@bronya.js/api-construct": "0.11.3", + "@bronya.js/core": "0.11.3", + "@types/aws-lambda": "8.10.119", + "aws-cdk": "2.93.0", + "dotenv": "16.3.1", + "dotenv-cli": "7.3.0", + "esbuild": "0.19.2", + "tsx": "3.12.7" + }, + "//": "the CDK configuration in this config file is used in the deployment package, @tools/cdk" +} diff --git a/packages/ant-stack/src/utils/schema-tools.ts b/apps/api/src/lib/utils.ts similarity index 100% rename from packages/ant-stack/src/utils/schema-tools.ts rename to apps/api/src/lib/utils.ts diff --git a/apps/api/src/routes/v1/graphql/+config.ts b/apps/api/src/routes/v1/graphql/+config.ts new file mode 100644 index 00000000..7a361f6b --- /dev/null +++ b/apps/api/src/routes/v1/graphql/+config.ts @@ -0,0 +1,30 @@ +import { cpSync, mkdirSync } from "node:fs"; +import { join, resolve } from "node:path"; + +import { ApiPropsOverride } from "@bronya.js/api-construct"; + +import { esbuildOptions } from "../../../../bronya.config"; + +export const overrides: ApiPropsOverride = { + esbuild: { + ...esbuildOptions, + plugins: [ + { + name: "copy-graphql-schema", + setup(build) { + build.onStart(async () => { + mkdirSync(build.initialOptions.outdir!, { recursive: true }); + + cpSync( + resolve("src/routes/v1/graphql/schema"), + join(build.initialOptions.outdir!, "schema"), + { + recursive: true, + }, + ); + }); + }, + }, + ], + }, +}; diff --git a/apps/api/src/routes/v1/graphql/+endpoint.ts b/apps/api/src/routes/v1/graphql/+endpoint.ts new file mode 100644 index 00000000..e9c5336c --- /dev/null +++ b/apps/api/src/routes/v1/graphql/+endpoint.ts @@ -0,0 +1,82 @@ +import { join } from "node:path"; +import { parse } from "node:url"; + +import { ApolloServer, HeaderMap, HTTPGraphQLRequest } from "@apollo/server"; +import { + ApolloServerPluginLandingPageLocalDefault, + ApolloServerPluginLandingPageProductionDefault, +} from "@apollo/server/plugin/landingPage/default"; +import { loadFilesSync } from "@graphql-tools/load-files"; +import { mergeTypeDefs } from "@graphql-tools/merge"; +import { compress, logger } from "@libs/lambda"; +import type { APIGatewayProxyHandler, APIGatewayProxyResult } from "aws-lambda"; + +import { transformBody } from "./lib"; +import { resolvers } from "./resolvers"; + +const responseHeaders: APIGatewayProxyResult["headers"] = { + "Access-Control-Allow-Headers": "Apollo-Require-Preflight, Content-Type", + "Access-Control-Allow-Origin": "*", + "Access-Control-Allow-Methods": "GET, POST, OPTIONS", +}; + +const graphqlServer = new ApolloServer({ + introspection: true, + plugins: [ + process.env.NODE_ENV === "development" + ? ApolloServerPluginLandingPageLocalDefault() + : ApolloServerPluginLandingPageProductionDefault({ footer: false }), + ], + resolvers, + typeDefs: mergeTypeDefs(loadFilesSync(join(__dirname, "schema/*.graphql"))), +}); + +export const ANY: APIGatewayProxyHandler = async (event) => { + const { body, headers: eventHeaders, httpMethod: method } = event; + + try { + graphqlServer.assertStarted(""); + } catch { + await graphqlServer.start(); + } + + const req: HTTPGraphQLRequest = { + body: typeof body === "string" ? JSON.parse(body.replaceAll("\n", "\\n")) : body, + headers: Object.entries(eventHeaders).reduce( + (m, [k, v]) => m.set(k, Array.isArray(v) ? v.join(", ") : v ?? ""), + new HeaderMap(), + ), + method, + search: parse(event.path ?? "").search ?? "", + }; + logger.info(JSON.stringify(req)); + const res = await graphqlServer.executeHTTPGraphQLRequest({ + httpGraphQLRequest: req, + context: async () => ({}), + }); + + const statusCode = res.status ?? 200; + res.headers.forEach((v, k) => (responseHeaders[k] = v)); + + try { + const { body, method } = compress( + await transformBody(res.body), + req.headers.get("accept-encoding"), + ); + if (method) { + responseHeaders["content-encoding"] = method; + } + return { + body, + headers: responseHeaders, + isBase64Encoded: !!method, + statusCode, + }; + } catch { + return { + body: "", + headers: responseHeaders, + statusCode: 500, + }; + } +}; diff --git a/apps/api/v1/graphql/src/lib.ts b/apps/api/src/routes/v1/graphql/lib.ts similarity index 66% rename from apps/api/v1/graphql/src/lib.ts rename to apps/api/src/routes/v1/graphql/lib.ts index ea605220..a643400a 100644 --- a/apps/api/v1/graphql/src/lib.ts +++ b/apps/api/src/routes/v1/graphql/lib.ts @@ -1,28 +1,36 @@ -import { BaseContext } from "@apollo/server/dist/cjs"; -import { IFieldResolver } from "@graphql-tools/utils/typings"; +import type { BaseContext, HTTPGraphQLResponse } from "@apollo/server"; +import type { IFieldResolver } from "@graphql-tools/utils"; +import { isErrorResponse } from "@peterportal-api/types"; +import type { RawResponse } from "@peterportal-api/types"; import { GraphQLError } from "graphql/error"; -import { isErrorResponse, RawResponse } from "peterportal-api-next-types"; function getBaseUrl() { switch (process.env.NODE_ENV) { - case "prod": case "production": return `https://api-next.peterportal.org`; - - case "development": - return `http://localhost:${process.env.API_PORT ?? 8080}`; - case "staging": - default: return `https://${process.env.STAGE}.api-next.peterportal.org`; + default: + return `http://localhost:${process.env.API_PORT ?? 8080}`; } } -export const geTransform = (args: Record) => { +export async function transformBody(body: HTTPGraphQLResponse["body"]): Promise { + if (body.kind === "complete") { + return body.string; + } + let transformedBody = ""; + for await (const chunk of body.asyncIterator) { + transformedBody += chunk; + } + return transformedBody; +} + +export function geTransform(args: Record) { if (args.ge) return { ...args, ge: args.ge.replace("_", "-") }; delete args.ge; return args; -}; +} export const proxyRestApi = ( @@ -35,13 +43,12 @@ export const proxyRestApi = async (_source, args, _context, _info) => { const { argsTransform = (args: Record) => args, pathArg } = proxyArgs ?? {}; const urlSearchParams = new URLSearchParams(argsTransform(args)); - const query = urlSearchParams.toString(); const data: RawResponse = await fetch( pathArg - ? `${getBaseUrl()}/${route}/${args[pathArg]}` - : `${getBaseUrl()}/${route}${query ? "?" + query : ""}`, + ? `${getBaseUrl()}${route}/${args[pathArg]}` + : `${getBaseUrl()}${route}${query ? "?" + query : ""}`, ) .then((res) => res.json()) .catch((err) => { diff --git a/apps/api/v1/graphql/src/resolvers.ts b/apps/api/src/routes/v1/graphql/resolvers.ts similarity index 88% rename from apps/api/v1/graphql/src/resolvers.ts rename to apps/api/src/routes/v1/graphql/resolvers.ts index 4f2f546f..45a82544 100644 --- a/apps/api/v1/graphql/src/resolvers.ts +++ b/apps/api/src/routes/v1/graphql/resolvers.ts @@ -2,10 +2,6 @@ import type { ApolloServerOptions, BaseContext } from "@apollo/server"; import { geTransform, proxyRestApi } from "./lib"; -/** - * Convention: either the route provided to {@link proxyRestApi} should start with a slash, - * or the fetch request should add a slash. For now, the latter will be used - */ export const resolvers: ApolloServerOptions["resolvers"] = { Query: { calendar: proxyRestApi("v1/rest/calendar"), diff --git a/apps/api/v1/graphql/src/graphql/base.graphql b/apps/api/src/routes/v1/graphql/schema/base.graphql similarity index 100% rename from apps/api/v1/graphql/src/graphql/base.graphql rename to apps/api/src/routes/v1/graphql/schema/base.graphql diff --git a/apps/api/v1/graphql/src/graphql/calendar.graphql b/apps/api/src/routes/v1/graphql/schema/calendar.graphql similarity index 100% rename from apps/api/v1/graphql/src/graphql/calendar.graphql rename to apps/api/src/routes/v1/graphql/schema/calendar.graphql diff --git a/apps/api/v1/graphql/src/graphql/courses.graphql b/apps/api/src/routes/v1/graphql/schema/courses.graphql similarity index 100% rename from apps/api/v1/graphql/src/graphql/courses.graphql rename to apps/api/src/routes/v1/graphql/schema/courses.graphql diff --git a/apps/api/v1/graphql/src/graphql/enum.graphql b/apps/api/src/routes/v1/graphql/schema/enum.graphql similarity index 100% rename from apps/api/v1/graphql/src/graphql/enum.graphql rename to apps/api/src/routes/v1/graphql/schema/enum.graphql diff --git a/apps/api/v1/graphql/src/graphql/grades.graphql b/apps/api/src/routes/v1/graphql/schema/grades.graphql similarity index 100% rename from apps/api/v1/graphql/src/graphql/grades.graphql rename to apps/api/src/routes/v1/graphql/schema/grades.graphql diff --git a/apps/api/v1/graphql/src/graphql/instructors.graphql b/apps/api/src/routes/v1/graphql/schema/instructors.graphql similarity index 100% rename from apps/api/v1/graphql/src/graphql/instructors.graphql rename to apps/api/src/routes/v1/graphql/schema/instructors.graphql diff --git a/apps/api/v1/graphql/src/graphql/larc.graphql b/apps/api/src/routes/v1/graphql/schema/larc.graphql similarity index 100% rename from apps/api/v1/graphql/src/graphql/larc.graphql rename to apps/api/src/routes/v1/graphql/schema/larc.graphql diff --git a/apps/api/v1/graphql/src/graphql/websoc.graphql b/apps/api/src/routes/v1/graphql/schema/websoc.graphql similarity index 100% rename from apps/api/v1/graphql/src/graphql/websoc.graphql rename to apps/api/src/routes/v1/graphql/schema/websoc.graphql diff --git a/apps/api/v1/graphql/src/graphql/week.graphql b/apps/api/src/routes/v1/graphql/schema/week.graphql similarity index 100% rename from apps/api/v1/graphql/src/graphql/week.graphql rename to apps/api/src/routes/v1/graphql/schema/week.graphql diff --git a/apps/api/v1/rest/calendar/src/index.ts b/apps/api/src/routes/v1/rest/calendar/+endpoint.ts similarity index 65% rename from apps/api/v1/rest/calendar/src/index.ts rename to apps/api/src/routes/v1/rest/calendar/+endpoint.ts index cf9a8c70..e4657b34 100644 --- a/apps/api/v1/rest/calendar/src/index.ts +++ b/apps/api/src/routes/v1/rest/calendar/+endpoint.ts @@ -1,27 +1,31 @@ import { PrismaClient } from "@libs/db"; -import { getTermDateData } from "@libs/registrar-api"; -import type { InternalHandler } from "ant-stack"; -import { createErrorResult, createOKResult } from "ant-stack"; -import { Quarter, QuarterDates } from "peterportal-api-next-types"; +import { createOKResult, createErrorResult } from "@libs/lambda"; +import { getTermDateData } from "@libs/uc-irvine-api/registrar"; +import type { Quarter, QuarterDates } from "@peterportal-api/types"; +import type { APIGatewayProxyHandler } from "aws-lambda"; import { ZodError } from "zod"; import { QuerySchema } from "./schema"; -let prisma: PrismaClient; +const prisma = new PrismaClient(); -export const GET: InternalHandler = async (request) => { - const { headers, query, requestId } = request; +export const GET: APIGatewayProxyHandler = async (event, context) => { + const headers = event.headers; + const query = event.queryStringParameters; + const requestId = context.awsRequestId; - prisma ??= new PrismaClient(); + /** + * TODO: handle warmer requests. + */ - if (request.isWarmerRequest) { - try { - await prisma.$connect(); - return createOKResult("Warmed", headers, requestId); - } catch (e) { - createErrorResult(500, e, requestId); - } - } + // if (request.isWarmerRequest) { + // try { + // await prisma.$connect(); + // return createOKResult("Warmed", headers, requestId); + // } catch (e) { + // createErrorResult(500, e, requestId); + // } + // } try { const where = QuerySchema.parse(query); diff --git a/apps/api/v1/rest/calendar/src/schema.ts b/apps/api/src/routes/v1/rest/calendar/schema.ts similarity index 87% rename from apps/api/v1/rest/calendar/src/schema.ts rename to apps/api/src/routes/v1/rest/calendar/schema.ts index e7ac867c..88f28e81 100644 --- a/apps/api/v1/rest/calendar/src/schema.ts +++ b/apps/api/src/routes/v1/rest/calendar/schema.ts @@ -1,4 +1,4 @@ -import { quarters } from "peterportal-api-next-types"; +import { quarters } from "@peterportal-api/types"; import { z } from "zod"; export const QuerySchema = z.object({ diff --git a/apps/api/src/routes/v1/rest/courses/+endpoint.ts b/apps/api/src/routes/v1/rest/courses/+endpoint.ts new file mode 100644 index 00000000..1e1d129c --- /dev/null +++ b/apps/api/src/routes/v1/rest/courses/+endpoint.ts @@ -0,0 +1,46 @@ +import { PrismaClient } from "@libs/db"; +import { createErrorResult, createOKResult } from "@libs/lambda"; +import type { APIGatewayProxyHandler } from "aws-lambda"; +import { ZodError } from "zod"; + +import { constructPrismaQuery, normalizeCourse } from "./lib"; +import { QuerySchema } from "./schema"; + +const prisma = new PrismaClient(); + +export const GET: APIGatewayProxyHandler = async (event, context) => { + const headers = event.headers; + const query = event.queryStringParameters; + const requestId = context.awsRequestId; + + /** + * TODO: handle warmer requests. + */ + + // if (request.isWarmerRequest) { + // try { + // await prisma.$connect(); + // return createOKResult("Warmed", headers, requestId); + // } catch (e) { + // createErrorResult(500, e, requestId); + // } + // } + + try { + const parsedQuery = QuerySchema.parse(query); + // The query object being empty shouldn't return all courses, since there's /courses/all for that. + if (!Object.keys(parsedQuery).length) { + return createErrorResult(400, "Course number not provided", requestId); + } + + const courses = await prisma.course.findMany({ where: constructPrismaQuery(parsedQuery) }); + + return createOKResult(courses.map(normalizeCourse), headers, requestId); + } catch (error) { + if (error instanceof ZodError) { + const messages = error.issues.map((issue) => issue.message); + return createErrorResult(400, messages.join("; "), requestId); + } + return createErrorResult(400, error, requestId); + } +}; diff --git a/apps/api/v1/rest/courses/src/lib.ts b/apps/api/src/routes/v1/rest/courses/lib.ts similarity index 98% rename from apps/api/v1/rest/courses/src/lib.ts rename to apps/api/src/routes/v1/rest/courses/lib.ts index b09ab8a5..ef40cd94 100644 --- a/apps/api/v1/rest/courses/src/lib.ts +++ b/apps/api/src/routes/v1/rest/courses/lib.ts @@ -1,5 +1,5 @@ import { Course as PrismaCourse, CourseLevel as PrismaCourseLevel, Prisma } from "@libs/db"; -import { Course, CourseLevel, GE, GECategory, PrerequisiteTree } from "peterportal-api-next-types"; +import { Course, CourseLevel, GE, GECategory, PrerequisiteTree } from "@peterportal-api/types"; import { Query } from "./schema"; diff --git a/apps/api/v1/rest/courses/src/schema.ts b/apps/api/src/routes/v1/rest/courses/schema.ts similarity index 84% rename from apps/api/v1/rest/courses/src/schema.ts rename to apps/api/src/routes/v1/rest/courses/schema.ts index 13334393..0687c044 100644 --- a/apps/api/v1/rest/courses/src/schema.ts +++ b/apps/api/src/routes/v1/rest/courses/schema.ts @@ -1,7 +1,8 @@ -import { flattenStringsAndSplit } from "ant-stack/utils"; -import { anyArray, divisionCodes, geCodes } from "peterportal-api-next-types"; +import { anyArray, divisionCodes, geCodes } from "@peterportal-api/types"; import { z } from "zod"; +import { flattenStringsAndSplit } from "../../../../lib/utils"; + export const QuerySchema = z.object({ department: z.string().optional(), courseNumber: z.string().optional(), diff --git a/apps/api/src/routes/v1/rest/courses/{id}/+endpoint.ts b/apps/api/src/routes/v1/rest/courses/{id}/+endpoint.ts new file mode 100644 index 00000000..4598cf44 --- /dev/null +++ b/apps/api/src/routes/v1/rest/courses/{id}/+endpoint.ts @@ -0,0 +1,49 @@ +import { PrismaClient } from "@libs/db"; +import { createErrorResult, createOKResult } from "@libs/lambda"; +import type { APIGatewayProxyHandler } from "aws-lambda"; + +import { normalizeCourse } from "../lib"; + +const prisma = new PrismaClient(); + +export const GET: APIGatewayProxyHandler = async (event, context) => { + const headers = event.headers; + const requestId = context.awsRequestId; + const params = event.pathParameters; + + /** + * TODO: handle warmer requests. + */ + + // if (request.isWarmerRequest) { + // try { + // await prisma.$connect(); + // return createOKResult("Warmed", headers, requestId); + // } catch (e) { + // createErrorResult(500, e, requestId); + // } + // } + + if (params?.id == null) { + return createErrorResult(400, "Course number not provided", requestId); + } + + if (params?.id === "all") { + const courses = await prisma.course.findMany(); + return createOKResult(courses.map(normalizeCourse), headers, requestId); + } + + try { + return createOKResult( + normalizeCourse( + await prisma.course.findFirstOrThrow({ + where: { id: decodeURIComponent(params.id) }, + }), + ), + headers, + requestId, + ); + } catch { + return createErrorResult(404, `Course ${params.id} not found`, requestId); + } +}; diff --git a/apps/api/v1/rest/grades/src/index.ts b/apps/api/src/routes/v1/rest/grades/{id}/+endpoint.ts similarity index 86% rename from apps/api/v1/rest/grades/src/index.ts rename to apps/api/src/routes/v1/rest/grades/{id}/+endpoint.ts index 02c26694..556fd1a4 100644 --- a/apps/api/v1/rest/grades/src/index.ts +++ b/apps/api/src/routes/v1/rest/grades/{id}/+endpoint.ts @@ -1,12 +1,12 @@ import { PrismaClient } from "@libs/db"; -import { createErrorResult, createOKResult, logger } from "ant-stack"; -import type { InternalHandler } from "ant-stack"; +import { createErrorResult, createOKResult, logger } from "@libs/lambda"; import type { AggregateGradesByCourse, AggregateGradesByOffering, GradesOptions, RawGrades, -} from "peterportal-api-next-types"; +} from "@peterportal-api/types"; +import type { APIGatewayProxyHandler } from "aws-lambda"; import { ZodError } from "zod"; import { @@ -19,21 +19,25 @@ import { } from "./lib"; import { QuerySchema } from "./schema"; -let prisma: PrismaClient; +const prisma = new PrismaClient(); -export const GET: InternalHandler = async (request) => { - const { headers, params, query, requestId } = request; +export const GET: APIGatewayProxyHandler = async (event, context) => { + const { headers, pathParameters: params, queryStringParameters: query } = event; + const { awsRequestId: requestId } = context; - prisma ??= new PrismaClient(); + /** + * TODO: handle warmer requests. + */ + + // if (request.isWarmerRequest) { + // try { + // await prisma.$connect(); + // return createOKResult("Warmed", headers, requestId); + // } catch (e) { + // createErrorResult(500, e, requestId); + // } + // } - if (request.isWarmerRequest) { - try { - await prisma.$connect(); - return createOKResult("Warmed", headers, requestId); - } catch (e) { - createErrorResult(500, e, requestId); - } - } try { const parsedQuery = QuerySchema.parse(query); switch (params?.id) { diff --git a/apps/api/v1/rest/grades/src/lib.ts b/apps/api/src/routes/v1/rest/grades/{id}/lib.ts similarity index 98% rename from apps/api/v1/rest/grades/src/lib.ts rename to apps/api/src/routes/v1/rest/grades/{id}/lib.ts index 05e02da0..da431d93 100644 --- a/apps/api/v1/rest/grades/src/lib.ts +++ b/apps/api/src/routes/v1/rest/grades/{id}/lib.ts @@ -10,8 +10,8 @@ import type { RawGrades, AggregateGradesByCourse, AggregateGradeByCourseHeader, -} from "peterportal-api-next-types"; -import { geCodes } from "peterportal-api-next-types"; +} from "@peterportal-api/types"; +import { geCodes } from "@peterportal-api/types"; import { Query } from "./schema"; diff --git a/apps/api/v1/rest/grades/src/schema.ts b/apps/api/src/routes/v1/rest/grades/{id}/schema.ts similarity index 89% rename from apps/api/v1/rest/grades/src/schema.ts rename to apps/api/src/routes/v1/rest/grades/{id}/schema.ts index 05b3c581..b9e2a795 100644 --- a/apps/api/v1/rest/grades/src/schema.ts +++ b/apps/api/src/routes/v1/rest/grades/{id}/schema.ts @@ -1,4 +1,4 @@ -import { anyArray, divisionCodes, geCodes, quarters } from "peterportal-api-next-types"; +import { anyArray, divisionCodes, geCodes, quarters } from "@peterportal-api/types"; import { z } from "zod"; export const QuerySchema = z.object({ diff --git a/apps/api/src/routes/v1/rest/instructors/+endpoint.ts b/apps/api/src/routes/v1/rest/instructors/+endpoint.ts new file mode 100644 index 00000000..df946501 --- /dev/null +++ b/apps/api/src/routes/v1/rest/instructors/+endpoint.ts @@ -0,0 +1,58 @@ +import { PrismaClient } from "@libs/db"; +import { createErrorResult, createOKResult } from "@libs/lambda"; +import type { APIGatewayProxyHandler } from "aws-lambda"; +import { ZodError } from "zod"; + +import { constructPrismaQuery } from "./lib"; +import { QuerySchema } from "./schema"; + +const prisma = new PrismaClient(); + +export const GET: APIGatewayProxyHandler = async (event, context) => { + const headers = event.headers; + const query = event.queryStringParameters; + const requestId = context.awsRequestId; + + /** + * TODO: handle warmer requests. + */ + + // if (request.isWarmerRequest) { + // try { + // await prisma.$connect(); + // return createOKResult("Warmed", headers, requestId); + // } catch (e) { + // createErrorResult(500, e, requestId); + // } + // } + + try { + const parsedQuery = QuerySchema.parse(query); + // The query object being empty shouldn't return all courses, since there's /courses/all for that. + if (!Object.keys(parsedQuery).length) + return createErrorResult(400, "Instructor UCInetID not provided", requestId); + const instructors = await prisma.instructor.findMany({ + where: constructPrismaQuery(parsedQuery), + }); + if (parsedQuery.taughtInTerms) { + const terms = new Set(parsedQuery.taughtInTerms); + return createOKResult( + instructors.filter( + (instructor) => + [...new Set(Object.values(instructor.courseHistory as Record))] + .flat() + .filter((x) => terms.has(x)).length, + ), + headers, + requestId, + ); + } + return createOKResult(instructors, headers, requestId); + } catch (error) { + if (error instanceof ZodError) { + const messages = error.issues.map((issue) => issue.message); + return createErrorResult(400, messages.join("; "), requestId); + } + return createErrorResult(400, error, requestId); + } +}; diff --git a/apps/api/v1/rest/instructors/src/lib.ts b/apps/api/src/routes/v1/rest/instructors/lib.ts similarity index 100% rename from apps/api/v1/rest/instructors/src/lib.ts rename to apps/api/src/routes/v1/rest/instructors/lib.ts diff --git a/apps/api/v1/rest/instructors/src/schema.ts b/apps/api/src/routes/v1/rest/instructors/schema.ts similarity index 90% rename from apps/api/v1/rest/instructors/src/schema.ts rename to apps/api/src/routes/v1/rest/instructors/schema.ts index 34f821b7..3a71c2a3 100644 --- a/apps/api/v1/rest/instructors/src/schema.ts +++ b/apps/api/src/routes/v1/rest/instructors/schema.ts @@ -1,6 +1,7 @@ -import { flattenStringsAndSplit } from "ant-stack/utils"; import { z } from "zod"; +import { flattenStringsAndSplit } from "../../../../lib/utils"; + export const QuerySchema = z.object({ nameContains: z.string().optional(), shortenedName: z.string().optional(), diff --git a/apps/api/src/routes/v1/rest/instructors/{id}/+endpoint.ts b/apps/api/src/routes/v1/rest/instructors/{id}/+endpoint.ts new file mode 100644 index 00000000..371dcc04 --- /dev/null +++ b/apps/api/src/routes/v1/rest/instructors/{id}/+endpoint.ts @@ -0,0 +1,45 @@ +import { PrismaClient } from "@libs/db"; +import { createErrorResult, createOKResult } from "@libs/lambda"; +import type { APIGatewayProxyHandler } from "aws-lambda"; + +const prisma = new PrismaClient(); + +export const GET: APIGatewayProxyHandler = async (event, context) => { + const headers = event.headers; + const requestId = context.awsRequestId; + const params = event.pathParameters; + + /** + * TODO: handle warmer requests. + */ + + // if (request.isWarmerRequest) { + // try { + // await prisma.$connect(); + // return createOKResult("Warmed", headers, requestId); + // } catch (e) { + // createErrorResult(500, e, requestId); + // } + // } + + if (params?.id == null) { + return createErrorResult(400, "Instructor UCInetID not provided", requestId); + } + + try { + if (params.id === "all") { + const instructors = await prisma.instructor.findMany(); + return createOKResult(instructors, headers, requestId); + } + + return createOKResult( + await prisma.instructor.findFirstOrThrow({ + where: { ucinetid: decodeURIComponent(params.id) }, + }), + headers, + requestId, + ); + } catch { + return createErrorResult(404, `Instructor ${params.id} not found`, requestId); + } +}; diff --git a/apps/api/v1/rest/larc/src/index.ts b/apps/api/src/routes/v1/rest/larc/+endpoint.ts similarity index 86% rename from apps/api/v1/rest/larc/src/index.ts rename to apps/api/src/routes/v1/rest/larc/+endpoint.ts index 5d12ccc8..2e1d87d5 100644 --- a/apps/api/v1/rest/larc/src/index.ts +++ b/apps/api/src/routes/v1/rest/larc/+endpoint.ts @@ -1,5 +1,5 @@ -import { createErrorResult, createOKResult } from "ant-stack"; -import type { InternalHandler } from "ant-stack"; +import { createErrorResult, createOKResult } from "@libs/lambda"; +import type { APIGatewayProxyHandler } from "aws-lambda"; import { load } from "cheerio"; import { fetch } from "cross-fetch"; import { ZodError } from "zod"; @@ -7,8 +7,11 @@ import { ZodError } from "zod"; import { fmtBldg, fmtDays, fmtTime, quarterToLarcSuffix } from "./lib"; import { QuerySchema } from "./schema"; -export const GET: InternalHandler = async (request) => { - const { headers, query, requestId } = request; +export const GET: APIGatewayProxyHandler = async (event, context) => { + const headers = event.headers; + const requestId = context.awsRequestId; + const query = event.queryStringParameters; + try { const { year, quarter } = QuerySchema.parse(query); diff --git a/apps/api/v1/rest/larc/src/lib.ts b/apps/api/src/routes/v1/rest/larc/lib.ts similarity index 93% rename from apps/api/v1/rest/larc/src/lib.ts rename to apps/api/src/routes/v1/rest/larc/lib.ts index 1c4bf60d..b935eb58 100644 --- a/apps/api/v1/rest/larc/src/lib.ts +++ b/apps/api/src/routes/v1/rest/larc/lib.ts @@ -1,4 +1,4 @@ -import { Quarter } from "peterportal-api-next-types"; +import { Quarter } from "@peterportal-api/types"; export const quarterToLarcSuffix = (quarter: Exclude): string => { switch (quarter) { diff --git a/apps/api/v1/rest/larc/src/schema.ts b/apps/api/src/routes/v1/rest/larc/schema.ts similarity index 87% rename from apps/api/v1/rest/larc/src/schema.ts rename to apps/api/src/routes/v1/rest/larc/schema.ts index e7ac867c..88f28e81 100644 --- a/apps/api/v1/rest/larc/src/schema.ts +++ b/apps/api/src/routes/v1/rest/larc/schema.ts @@ -1,4 +1,4 @@ -import { quarters } from "peterportal-api-next-types"; +import { quarters } from "@peterportal-api/types"; import { z } from "zod"; export const QuerySchema = z.object({ diff --git a/apps/api/src/routes/v1/rest/websoc/+config.ts b/apps/api/src/routes/v1/rest/websoc/+config.ts new file mode 100644 index 00000000..acbad080 --- /dev/null +++ b/apps/api/src/routes/v1/rest/websoc/+config.ts @@ -0,0 +1,42 @@ +import { ApiPropsOverride } from "@bronya.js/api-construct"; +import { + Effect, + ManagedPolicy, + PolicyDocument, + PolicyStatement, + Role, + ServicePrincipal, +} from "aws-cdk-lib/aws-iam"; + +import { constructs, esbuildOptions } from "../../../../../bronya.config"; + +export const overrides: ApiPropsOverride = { + constructs: { + functionProps: (scope, id) => ({ + role: new Role(scope, `${id}-v1-rest-websoc-role`, { + assumedBy: new ServicePrincipal("lambda.amazonaws.com"), + managedPolicies: [ + ManagedPolicy.fromAwsManagedPolicyName("service-role/AWSLambdaBasicExecutionRole"), + ], + inlinePolicies: { + lambdaInvokePolicy: new PolicyDocument({ + statements: [ + new PolicyStatement({ + effect: Effect.ALLOW, + resources: [ + `arn:aws:lambda:${process.env["AWS_REGION"]}:${process.env["ACCOUNT_ID"]}:function:peterportal-api-next-services-prod-websoc-proxy-function`, + ], + actions: ["lambda:InvokeFunction"], + }), + ], + }), + }, + }), + }), + ...constructs, + }, + esbuild: { + ...esbuildOptions, + external: process.env.NODE_ENV === "development" ? [] : ["@services/websoc-proxy"], + }, +}; diff --git a/apps/api/v1/rest/websoc/src/index.ts b/apps/api/src/routes/v1/rest/websoc/+endpoint.ts similarity index 50% rename from apps/api/v1/rest/websoc/src/index.ts rename to apps/api/src/routes/v1/rest/websoc/+endpoint.ts index 15a5a05d..90f3c129 100644 --- a/apps/api/v1/rest/websoc/src/index.ts +++ b/apps/api/src/routes/v1/rest/websoc/+endpoint.ts @@ -1,132 +1,46 @@ import { PrismaClient } from "@libs/db"; -import type { WebsocAPIResponse } from "@libs/websoc-api-next"; +import { createErrorResult, createOKResult } from "@libs/lambda"; +import type { WebsocAPIResponse } from "@libs/uc-irvine-api/websoc"; import { combineAndNormalizeResponses, notNull, sortResponse } from "@libs/websoc-utils"; -import { createErrorResult, createOKResult, type InternalHandler } from "ant-stack"; +import type { APIGatewayProxyHandler } from "aws-lambda"; import { ZodError } from "zod"; import { APILambdaClient } from "./APILambdaClient"; import { constructPrismaQuery, normalizeQuery } from "./lib"; import { QuerySchema } from "./schema"; -const quarterOrder = ["Winter", "Spring", "Summer1", "Summer10wk", "Summer2", "Fall"]; +const prisma = new PrismaClient(); -let prisma: PrismaClient; -let connected = false; -let lambdaClient: APILambdaClient; +// let connected = false +const lambdaClient = await APILambdaClient.new(); -export const GET: InternalHandler = async (request) => { - const { headers, params, query, requestId } = request; +export const GET: APIGatewayProxyHandler = async (event, context) => { + const headers = event.headers; + const query = event.queryStringParameters; + const requestId = context.awsRequestId; - prisma ??= new PrismaClient(); - lambdaClient ??= await APILambdaClient.new(); + // if (!connected) { + // lambdaClient = await APILambdaClient.new(); + // try { + // await prisma.$connect(); + // connected = true; - if (!connected) { - try { - await prisma.$connect(); - connected = true; - if (request.isWarmerRequest) { - return createOKResult("Warmed", headers, requestId); - } - } catch { - // no-op - } - } + // /** + // * TODO: handle warmer requests. + // */ - try { - switch (params?.id) { - case "terms": { - const [gradesTerms, webSocTerms] = await Promise.all([ - connected - ? prisma.gradesSection.findMany({ - distinct: ["year", "quarter"], - select: { - year: true, - quarter: true, - }, - orderBy: [{ year: "desc" }, { quarter: "desc" }], - }) - : [], - lambdaClient.getTerms({ function: "terms" }), - ]); - - const shortNames = webSocTerms.map((x) => x.shortName); - - gradesTerms.forEach(({ year, quarter }) => { - if (!shortNames.includes(`${year} ${quarter}`)) { - let longName = year; - switch (quarter) { - case "Summer1": - longName += " Summer Session 1"; - break; - case "Summer2": - longName += " Summer Session 2"; - break; - case "Summer10wk": - longName += " 10-wk Summer"; - break; - default: - longName += ` ${quarter} Quarter`; - break; - } - - webSocTerms.push({ - shortName: `${year} ${quarter}`, - longName: longName, - }); - } - }); - - webSocTerms.sort((a, b) => { - if (a.shortName.substring(0, 4) > b.shortName.substring(0, 4)) return -1; - if (a.shortName.substring(0, 4) < b.shortName.substring(0, 4)) return 1; - return ( - quarterOrder.indexOf(b.shortName.substring(5)) - - quarterOrder.indexOf(a.shortName.substring(5)) - ); - }); - - return createOKResult(webSocTerms, headers, requestId); - } - - case "depts": { - const [gradesDepts, webSocDepts] = await Promise.all([ - connected - ? prisma.gradesSection.findMany({ - distinct: ["department"], - select: { - department: true, - }, - }) - : [], - lambdaClient.getDepts({ function: "depts" }), - ]); - - const deptValues = webSocDepts.map((x) => x.deptValue); - - gradesDepts.forEach((element) => { - if (!deptValues.includes(element.department)) { - webSocDepts.push({ - deptLabel: element.department, - deptValue: element.department, - }); - } - }); - - webSocDepts.sort((a, b) => { - if (a.deptValue == "ALL") return -1; - if (b.deptValue == "ALL") return 1; - if (a.deptValue > b.deptValue) return 1; - if (a.deptValue < b.deptValue) return -1; - return 0; - }); - - return createOKResult(webSocDepts, headers, requestId); - } - } + // // if (request.isWarmerRequest) { + // // return createOKResult("Warmed", headers, requestId); + // // } + // } catch { + // // no-op + // } + // } + try { const parsedQuery = QuerySchema.parse(query); - if (connected && parsedQuery.cache) { + if (parsedQuery.cache) { const websocSections = await prisma.websocSection.findMany({ where: constructPrismaQuery(parsedQuery), select: { department: true, courseNumber: true, data: true }, diff --git a/apps/api/v1/rest/websoc/src/APILambdaClient.ts b/apps/api/src/routes/v1/rest/websoc/APILambdaClient.ts similarity index 79% rename from apps/api/v1/rest/websoc/src/APILambdaClient.ts rename to apps/api/src/routes/v1/rest/websoc/APILambdaClient.ts index 0e37c777..30ba7fbb 100644 --- a/apps/api/v1/rest/websoc/src/APILambdaClient.ts +++ b/apps/api/src/routes/v1/rest/websoc/APILambdaClient.ts @@ -1,8 +1,8 @@ import { InvokeCommand, LambdaClient, LambdaClientConfig } from "@aws-sdk/client-lambda"; -import type { WebsocAPIResponse } from "@libs/websoc-api-next"; -import { zeroUUID } from "ant-stack"; +import { zeroUUID } from "@libs/lambda"; +import type { WebsocAPIResponse } from "@libs/uc-irvine-api/websoc"; +import type { Department, TermData } from "@peterportal-api/types"; import type { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from "aws-lambda"; -import type { Department, TermData } from "peterportal-api-next-types"; /** * A {@link `LambdaClient`} wrapper for the API to interface with the WebSoc proxy service. @@ -16,24 +16,24 @@ import type { Department, TermData } from "peterportal-api-next-types"; * the constructor is private, and doesn't actually do anything other than return an empty instance. */ export class APILambdaClient { - private client!: LambdaClient; + private client: LambdaClient; + + private initialized = false; private service?: ( event: APIGatewayProxyEvent, context: Context, ) => Promise; - private constructor() {} + private constructor(configuration: LambdaClientConfig = {}) { + this.client = new LambdaClient(configuration); + } static async new(configuration: LambdaClientConfig = {}) { - const client = new APILambdaClient(); - client.client = new LambdaClient(configuration); - if (process.env.NODE_ENV === "development") { - const { handler } = await import("@services/websoc-proxy"); - client.service = handler; - } else { - client.service = undefined; - } + const client = new APILambdaClient(configuration); + + await client.$connect(); + return client; } @@ -55,6 +55,21 @@ export class APILambdaClient { return JSON.parse(payload.body); } + public async $connect() { + if (this.initialized) { + return; + } + + if (process.env.NODE_ENV === "development") { + const { handler } = await import("@services/websoc-proxy"); + this.service = handler; + } else { + this.service = undefined; + } + + this.initialized = true; + } + async getDepts(body: Record): Promise { const invocationResponse = await this.invoke(body); return invocationResponse.payload; diff --git a/apps/api/v1/rest/websoc/src/lib.ts b/apps/api/src/routes/v1/rest/websoc/lib.ts similarity index 99% rename from apps/api/v1/rest/websoc/src/lib.ts rename to apps/api/src/routes/v1/rest/websoc/lib.ts index 625f1f56..b15c3cda 100644 --- a/apps/api/v1/rest/websoc/src/lib.ts +++ b/apps/api/src/routes/v1/rest/websoc/lib.ts @@ -1,5 +1,5 @@ import { Prisma } from "@libs/db"; -import { WebsocAPIOptions } from "@libs/websoc-api-next"; +import { WebsocAPIOptions } from "@libs/uc-irvine-api/websoc"; import type { Query } from "./schema"; diff --git a/apps/api/v1/rest/websoc/src/schema.ts b/apps/api/src/routes/v1/rest/websoc/schema.ts similarity index 97% rename from apps/api/v1/rest/websoc/src/schema.ts rename to apps/api/src/routes/v1/rest/websoc/schema.ts index 188e97ea..10312c38 100644 --- a/apps/api/v1/rest/websoc/src/schema.ts +++ b/apps/api/src/routes/v1/rest/websoc/schema.ts @@ -1,4 +1,3 @@ -import { flattenDayStringsAndSplit, flattenStringsAndSplit } from "ant-stack/utils"; import { anyArray, cancelledCoursesOptions, @@ -7,9 +6,11 @@ import { geCodes, quarters, sectionTypes, -} from "peterportal-api-next-types"; +} from "@peterportal-api/types"; import { z } from "zod"; +import { flattenDayStringsAndSplit, flattenStringsAndSplit } from "../../../../lib/utils"; + /** * Parse an unknown query to the websoc endpoint. */ diff --git a/apps/api/src/routes/v1/rest/websoc/{id}/+config.ts b/apps/api/src/routes/v1/rest/websoc/{id}/+config.ts new file mode 100644 index 00000000..aab8d96b --- /dev/null +++ b/apps/api/src/routes/v1/rest/websoc/{id}/+config.ts @@ -0,0 +1,42 @@ +import { ApiPropsOverride } from "@bronya.js/api-construct"; +import { + Effect, + ManagedPolicy, + PolicyDocument, + PolicyStatement, + Role, + ServicePrincipal, +} from "aws-cdk-lib/aws-iam"; + +import { constructs, esbuildOptions } from "../../../../../../bronya.config"; + +export const overrides: ApiPropsOverride = { + constructs: { + functionProps: (scope, id) => ({ + role: new Role(scope, `${id}-v1-rest-websoc-id-role`, { + assumedBy: new ServicePrincipal("lambda.amazonaws.com"), + managedPolicies: [ + ManagedPolicy.fromAwsManagedPolicyName("service-role/AWSLambdaBasicExecutionRole"), + ], + inlinePolicies: { + lambdaInvokePolicy: new PolicyDocument({ + statements: [ + new PolicyStatement({ + effect: Effect.ALLOW, + resources: [ + `arn:aws:lambda:${process.env["AWS_REGION"]}:${process.env["ACCOUNT_ID"]}:function:peterportal-api-next-services-prod-websoc-proxy-function`, + ], + actions: ["lambda:InvokeFunction"], + }), + ], + }), + }, + }), + }), + ...constructs, + }, + esbuild: { + ...esbuildOptions, + external: process.env.NODE_ENV === "development" ? [] : ["@services/websoc-proxy"], + }, +}; diff --git a/apps/api/src/routes/v1/rest/websoc/{id}/+endpoint.ts b/apps/api/src/routes/v1/rest/websoc/{id}/+endpoint.ts new file mode 100644 index 00000000..4becf3f0 --- /dev/null +++ b/apps/api/src/routes/v1/rest/websoc/{id}/+endpoint.ts @@ -0,0 +1,134 @@ +import { PrismaClient } from "@libs/db"; +import { createErrorResult, createOKResult } from "@libs/lambda"; +import type { APIGatewayProxyHandler } from "aws-lambda"; +import { ZodError } from "zod"; + +import { APILambdaClient } from "../APILambdaClient"; + +const quarterOrder = ["Winter", "Spring", "Summer1", "Summer10wk", "Summer2", "Fall"]; + +const prisma = new PrismaClient(); + +// let connected = false +const lambdaClient = await APILambdaClient.new(); + +export const GET: APIGatewayProxyHandler = async (event, context) => { + const headers = event.headers; + const requestId = context.awsRequestId; + const params = event.pathParameters; + + // if (!connected) { + // lambdaClient = await APILambdaClient.new(); + // try { + // await prisma.$connect(); + // connected = true; + + // /** + // * TODO: handle warmer requests. + // */ + + // // if (request.isWarmerRequest) { + // // return createOKResult("Warmed", headers, requestId); + // // } + // } catch { + // // no-op + // } + // } + + try { + switch (params?.id) { + case "terms": { + const [gradesTerms, webSocTerms] = await Promise.all([ + prisma.gradesSection.findMany({ + distinct: ["year", "quarter"], + select: { + year: true, + quarter: true, + }, + orderBy: [{ year: "desc" }, { quarter: "desc" }], + }), + lambdaClient.getTerms({ function: "terms" }), + ]); + + const shortNames = webSocTerms.map((x) => x.shortName); + + gradesTerms.forEach(({ year, quarter }) => { + if (!shortNames.includes(`${year} ${quarter}`)) { + let longName = year; + switch (quarter) { + case "Summer1": + longName += " Summer Session 1"; + break; + case "Summer2": + longName += " Summer Session 2"; + break; + case "Summer10wk": + longName += " 10-wk Summer"; + break; + default: + longName += ` ${quarter} Quarter`; + break; + } + + webSocTerms.push({ + shortName: `${year} ${quarter}`, + longName: longName, + }); + } + }); + + webSocTerms.sort((a, b) => { + if (a.shortName.substring(0, 4) > b.shortName.substring(0, 4)) return -1; + if (a.shortName.substring(0, 4) < b.shortName.substring(0, 4)) return 1; + return ( + quarterOrder.indexOf(b.shortName.substring(5)) - + quarterOrder.indexOf(a.shortName.substring(5)) + ); + }); + + return createOKResult(webSocTerms, headers, requestId); + } + + case "depts": { + const [gradesDepts, webSocDepts] = await Promise.all([ + prisma.gradesSection.findMany({ + distinct: ["department"], + select: { + department: true, + }, + }), + lambdaClient.getDepts({ function: "depts" }), + ]); + + const deptValues = webSocDepts.map((x) => x.deptValue); + + gradesDepts.forEach((element) => { + if (!deptValues.includes(element.department)) { + webSocDepts.push({ + deptLabel: element.department, + deptValue: element.department, + }); + } + }); + + webSocDepts.sort((a, b) => { + if (a.deptValue == "ALL") return -1; + if (b.deptValue == "ALL") return 1; + if (a.deptValue > b.deptValue) return 1; + if (a.deptValue < b.deptValue) return -1; + return 0; + }); + + return createOKResult(webSocDepts, headers, requestId); + } + } + } catch (error) { + if (error instanceof ZodError) { + const messages = error.issues.map((issue) => issue.message); + return createErrorResult(400, messages.join("; "), requestId); + } + return createErrorResult(400, error, requestId); + } + + return createErrorResult(400, "Invalid endpoint", requestId); +}; diff --git a/apps/api/v1/rest/week/src/index.ts b/apps/api/src/routes/v1/rest/week/+endpoint.ts similarity index 86% rename from apps/api/v1/rest/week/src/index.ts rename to apps/api/src/routes/v1/rest/week/+endpoint.ts index 0c502c72..7f47dbb9 100644 --- a/apps/api/v1/rest/week/src/index.ts +++ b/apps/api/src/routes/v1/rest/week/+endpoint.ts @@ -1,27 +1,31 @@ import { PrismaClient } from "@libs/db"; -import type { InternalHandler } from "ant-stack"; -import { createErrorResult, createOKResult } from "ant-stack"; -import type { WeekData } from "peterportal-api-next-types/types/week"; +import { createErrorResult, createOKResult } from "@libs/lambda"; +import type { WeekData } from "@peterportal-api/types"; +import type { APIGatewayProxyHandler } from "aws-lambda"; import { ZodError } from "zod"; import { getQuarter, getWeek } from "./lib"; import { QuerySchema } from "./schema"; -let prisma: PrismaClient; +const prisma = new PrismaClient(); -export const GET: InternalHandler = async (request) => { - const { headers, query, requestId } = request; +export const GET: APIGatewayProxyHandler = async (event, context) => { + const headers = event.headers; + const query = event.queryStringParameters; + const requestId = context.awsRequestId; - prisma ??= new PrismaClient(); + /** + * TODO: handle warmer requests. + */ - if (request.isWarmerRequest) { - try { - await prisma.$connect(); - return createOKResult("Warmed", headers, requestId); - } catch (error) { - createErrorResult(500, error, requestId); - } - } + // if (request.isWarmerRequest) { + // try { + // await prisma.$connect(); + // return createOKResult("Warmed", headers, requestId); + // } catch (e) { + // createErrorResult(500, e, requestId); + // } + // } try { const parsedQuery = QuerySchema.parse(query); @@ -31,6 +35,7 @@ export const GET: InternalHandler = async (request) => { .split(",")[0] .split("/") .map((x) => parseInt(x, 10)); + parsedQuery.year = year; parsedQuery.month = month; parsedQuery.day = day; diff --git a/apps/api/v1/rest/week/src/lib.ts b/apps/api/src/routes/v1/rest/week/lib.ts similarity index 92% rename from apps/api/v1/rest/week/src/lib.ts rename to apps/api/src/routes/v1/rest/week/lib.ts index e4ac8c92..1b0b2489 100644 --- a/apps/api/v1/rest/week/src/lib.ts +++ b/apps/api/src/routes/v1/rest/week/lib.ts @@ -1,5 +1,5 @@ import { CalendarTerm } from "@libs/db"; -import { Quarter } from "peterportal-api-next-types"; +import { Quarter } from "@peterportal-api/types"; const DAY_MS = 24 * 60 * 60 * 1000; const WEEK_MS = 7 * DAY_MS; diff --git a/apps/api/v1/rest/week/src/schema.ts b/apps/api/src/routes/v1/rest/week/schema.ts similarity index 100% rename from apps/api/v1/rest/week/src/schema.ts rename to apps/api/src/routes/v1/rest/week/schema.ts diff --git a/apps/api/tsconfig.json b/apps/api/tsconfig.json new file mode 100644 index 00000000..dc787c60 --- /dev/null +++ b/apps/api/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": ["../../tsconfig.json"] +} diff --git a/apps/api/v1/graphql/package.json b/apps/api/v1/graphql/package.json deleted file mode 100644 index 9a899f16..00000000 --- a/apps/api/v1/graphql/package.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": "api-v1-graphql", - "version": "0.0.0", - "private": true, - "type": "module", - "scripts": { - "build": "ant-stack build" - }, - "dependencies": { - "@apollo/server": "4.9.2", - "@graphql-tools/load-files": "7.0.0", - "@graphql-tools/merge": "9.0.0", - "@graphql-tools/utils": "10.0.5", - "ant-stack": "workspace:*", - "cross-fetch": "4.0.0", - "graphql": "16.8.0", - "peterportal-api-next-types": "workspace:*" - }, - "devDependencies": { - "@types/aws-lambda": "8.10.119" - } -} diff --git a/apps/api/v1/graphql/src/index.ts b/apps/api/v1/graphql/src/index.ts deleted file mode 100644 index a470a24d..00000000 --- a/apps/api/v1/graphql/src/index.ts +++ /dev/null @@ -1,101 +0,0 @@ -import path from "node:path"; -import url from "node:url"; - -import { ApolloServer, HTTPGraphQLRequest, HTTPGraphQLResponse, HeaderMap } from "@apollo/server"; -import { - ApolloServerPluginLandingPageLocalDefault, - ApolloServerPluginLandingPageProductionDefault, -} from "@apollo/server/plugin/landingPage/default"; -import { loadFilesSync } from "@graphql-tools/load-files"; -import { mergeTypeDefs } from "@graphql-tools/merge"; -import type { InternalHandler } from "ant-stack"; -import { compress, getClosestProjectDirectory } from "ant-stack/utils"; -import type { APIGatewayProxyResult } from "aws-lambda"; - -import { resolvers } from "./resolvers"; - -/** - * {@link __dirname} is injected by ESBuild - */ -const projectDirectory = getClosestProjectDirectory(__dirname); - -const graphqlServer = new ApolloServer({ - introspection: true, - plugins: [ - process.env.NODE_ENV === "development" - ? ApolloServerPluginLandingPageLocalDefault() - : ApolloServerPluginLandingPageProductionDefault({ footer: false }), - ], - resolvers, - typeDefs: mergeTypeDefs(loadFilesSync(path.join(projectDirectory, "src", "graphql/*.graphql"))), -}); - -export const ANY: InternalHandler = async (request) => { - try { - graphqlServer.assertStarted(""); - } catch { - await graphqlServer.start(); - } - - const headers = Object.entries(request.headers).reduce((headerMap, [key, value]) => { - return headerMap.set(key, Array.isArray(value) ? value.join(", ") : value); - }, new HeaderMap()); - - const httpGraphQLRequest: HTTPGraphQLRequest = { - method: request.method, - headers, - search: url.parse(request.path ?? "").search ?? "", - body: request.body, - }; - - const httpGraphQLResponse = await graphqlServer.executeHTTPGraphQLRequest({ - httpGraphQLRequest, - context: async () => ({}), - }); - - const resultStatusCode = httpGraphQLResponse.status ?? 200; - - const resultHeaders: APIGatewayProxyResult["headers"] = { - "Access-Control-Allow-Headers": "Apollo-Require-Preflight, Content-Type", - "Access-Control-Allow-Origin": "*", - "Access-Control-Allow-Methods": "GET, POST, OPTIONS", - }; - - httpGraphQLResponse.headers.forEach((value, key) => { - resultHeaders[key] = value; - }); - - try { - const { body, method } = compress( - await transformBody(httpGraphQLResponse.body), - headers.get("accept-encoding"), - ); - if (method) resultHeaders["Content-Encoding"] = method; - return { - isBase64Encoded: !!method, - statusCode: resultStatusCode, - headers: resultHeaders, - body, - }; - } catch (e) { - return { - statusCode: 500, - headers: resultHeaders, - body: "", - }; - } -}; - -async function transformBody(body: HTTPGraphQLResponse["body"]): Promise { - if (body.kind === "complete") { - return body.string; - } - - let transformedBody = ""; - - for await (const chunk of body.asyncIterator) { - transformedBody += chunk; - } - - return transformedBody; -} diff --git a/apps/api/v1/rest/calendar/ant.config.ts b/apps/api/v1/rest/calendar/ant.config.ts deleted file mode 100644 index 819b501b..00000000 --- a/apps/api/v1/rest/calendar/ant.config.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { dirname, resolve } from "node:path"; -import { fileURLToPath } from "node:url"; - -import type { AntConfigStub } from "ant-stack/config"; -import env from "../../../../../env"; - -import { cleanCopy, selectDelete } from "@libs/build-tools"; - -// ESM hack for __dirname -const cwd = dirname(fileURLToPath(import.meta.url)); - -// The relative path to the generated Prisma Client. -const prismaClientDir = "./node_modules/@libs/db/node_modules/prisma"; - -const prismaSchema = "./node_modules/@libs/db/prisma/schema.prisma"; - -const outDir = resolve(cwd, "./dist"); - -const config: AntConfigStub = { - esbuild: { - plugins: [ - cleanCopy({ cwd, outDir, prismaClientDir, prismaSchema }), - selectDelete(env.NODE_ENV, outDir), - ], - }, -}; - -export default config; diff --git a/apps/api/v1/rest/calendar/package.json b/apps/api/v1/rest/calendar/package.json deleted file mode 100644 index fa8cb92c..00000000 --- a/apps/api/v1/rest/calendar/package.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "api-v1-rest-calendar", - "version": "0.0.0", - "private": true, - "type": "module", - "scripts": { - "build": "ant-stack build" - }, - "dependencies": { - "@libs/db": "workspace:*", - "ant-stack": "workspace:*", - "zod": "3.22.2" - }, - "devDependencies": { - "@libs/build-tools": "workspace:*", - "peterportal-api-next-types": "workspace:*" - } -} diff --git a/apps/api/v1/rest/courses/ant.config.ts b/apps/api/v1/rest/courses/ant.config.ts deleted file mode 100644 index 819b501b..00000000 --- a/apps/api/v1/rest/courses/ant.config.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { dirname, resolve } from "node:path"; -import { fileURLToPath } from "node:url"; - -import type { AntConfigStub } from "ant-stack/config"; -import env from "../../../../../env"; - -import { cleanCopy, selectDelete } from "@libs/build-tools"; - -// ESM hack for __dirname -const cwd = dirname(fileURLToPath(import.meta.url)); - -// The relative path to the generated Prisma Client. -const prismaClientDir = "./node_modules/@libs/db/node_modules/prisma"; - -const prismaSchema = "./node_modules/@libs/db/prisma/schema.prisma"; - -const outDir = resolve(cwd, "./dist"); - -const config: AntConfigStub = { - esbuild: { - plugins: [ - cleanCopy({ cwd, outDir, prismaClientDir, prismaSchema }), - selectDelete(env.NODE_ENV, outDir), - ], - }, -}; - -export default config; diff --git a/apps/api/v1/rest/courses/package.json b/apps/api/v1/rest/courses/package.json deleted file mode 100644 index 53629d4a..00000000 --- a/apps/api/v1/rest/courses/package.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "api-v1-rest-courses", - "version": "0.0.0", - "private": true, - "type": "module", - "scripts": { - "build": "ant-stack build" - }, - "dependencies": { - "@libs/db": "workspace:*", - "ant-stack": "workspace:*", - "zod": "3.22.2" - }, - "devDependencies": { - "@libs/build-tools": "workspace:*", - "peterportal-api-next-types": "workspace:*" - } -} diff --git a/apps/api/v1/rest/courses/src/index.ts b/apps/api/v1/rest/courses/src/index.ts deleted file mode 100644 index cdb8dc1e..00000000 --- a/apps/api/v1/rest/courses/src/index.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { PrismaClient } from "@libs/db"; -import { createErrorResult, createOKResult, type InternalHandler } from "ant-stack"; -import { ZodError } from "zod"; - -import { constructPrismaQuery, normalizeCourse } from "./lib"; -import { QuerySchema } from "./schema"; - -let prisma: PrismaClient; - -export const GET: InternalHandler = async (request) => { - const { headers, params, query, requestId } = request; - - prisma ??= new PrismaClient(); - - if (request.isWarmerRequest) { - try { - await prisma.$connect(); - return createOKResult("Warmed", headers, requestId); - } catch (e) { - createErrorResult(500, e, requestId); - } - } - - if (params?.id) { - if (params.id === "all") { - const courses = await prisma.course.findMany(); - return createOKResult(courses.map(normalizeCourse), headers, requestId); - } - try { - return createOKResult( - normalizeCourse( - await prisma.course.findFirstOrThrow({ - where: { id: decodeURIComponent(params.id) }, - }), - ), - headers, - requestId, - ); - } catch { - return createErrorResult(404, `Course ${params.id} not found`, requestId); - } - } else { - try { - const parsedQuery = QuerySchema.parse(query); - // The query object being empty shouldn't return all courses, since there's /courses/all for that. - if (!Object.keys(parsedQuery).length) - return createErrorResult(400, "Course number not provided", requestId); - const courses = await prisma.course.findMany({ where: constructPrismaQuery(parsedQuery) }); - return createOKResult(courses.map(normalizeCourse), headers, requestId); - } catch (error) { - if (error instanceof ZodError) { - const messages = error.issues.map((issue) => issue.message); - return createErrorResult(400, messages.join("; "), requestId); - } - return createErrorResult(400, error, requestId); - } - } -}; diff --git a/apps/api/v1/rest/grades/ant.config.ts b/apps/api/v1/rest/grades/ant.config.ts deleted file mode 100644 index 819b501b..00000000 --- a/apps/api/v1/rest/grades/ant.config.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { dirname, resolve } from "node:path"; -import { fileURLToPath } from "node:url"; - -import type { AntConfigStub } from "ant-stack/config"; -import env from "../../../../../env"; - -import { cleanCopy, selectDelete } from "@libs/build-tools"; - -// ESM hack for __dirname -const cwd = dirname(fileURLToPath(import.meta.url)); - -// The relative path to the generated Prisma Client. -const prismaClientDir = "./node_modules/@libs/db/node_modules/prisma"; - -const prismaSchema = "./node_modules/@libs/db/prisma/schema.prisma"; - -const outDir = resolve(cwd, "./dist"); - -const config: AntConfigStub = { - esbuild: { - plugins: [ - cleanCopy({ cwd, outDir, prismaClientDir, prismaSchema }), - selectDelete(env.NODE_ENV, outDir), - ], - }, -}; - -export default config; diff --git a/apps/api/v1/rest/grades/package.json b/apps/api/v1/rest/grades/package.json deleted file mode 100644 index e4ab128e..00000000 --- a/apps/api/v1/rest/grades/package.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "api-v1-rest-grades", - "version": "0.0.0", - "private": true, - "type": "module", - "scripts": { - "build": "ant-stack build" - }, - "dependencies": { - "@libs/db": "workspace:*", - "ant-stack": "workspace:*", - "zod": "3.22.2" - }, - "devDependencies": { - "@libs/build-tools": "workspace:*", - "peterportal-api-next-types": "workspace:*" - } -} diff --git a/apps/api/v1/rest/instructors/ant.config.ts b/apps/api/v1/rest/instructors/ant.config.ts deleted file mode 100644 index 819b501b..00000000 --- a/apps/api/v1/rest/instructors/ant.config.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { dirname, resolve } from "node:path"; -import { fileURLToPath } from "node:url"; - -import type { AntConfigStub } from "ant-stack/config"; -import env from "../../../../../env"; - -import { cleanCopy, selectDelete } from "@libs/build-tools"; - -// ESM hack for __dirname -const cwd = dirname(fileURLToPath(import.meta.url)); - -// The relative path to the generated Prisma Client. -const prismaClientDir = "./node_modules/@libs/db/node_modules/prisma"; - -const prismaSchema = "./node_modules/@libs/db/prisma/schema.prisma"; - -const outDir = resolve(cwd, "./dist"); - -const config: AntConfigStub = { - esbuild: { - plugins: [ - cleanCopy({ cwd, outDir, prismaClientDir, prismaSchema }), - selectDelete(env.NODE_ENV, outDir), - ], - }, -}; - -export default config; diff --git a/apps/api/v1/rest/instructors/package.json b/apps/api/v1/rest/instructors/package.json deleted file mode 100644 index 069d5ecd..00000000 --- a/apps/api/v1/rest/instructors/package.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "api-v1-rest-instructors", - "version": "0.0.0", - "private": true, - "type": "module", - "scripts": { - "build": "ant-stack build" - }, - "dependencies": { - "@libs/db": "workspace:*", - "ant-stack": "workspace:*", - "zod": "3.22.2" - }, - "devDependencies": { - "@libs/build-tools": "workspace:*", - "peterportal-api-next-types": "workspace:*" - } -} diff --git a/apps/api/v1/rest/instructors/src/index.ts b/apps/api/v1/rest/instructors/src/index.ts deleted file mode 100644 index 64c3ecb2..00000000 --- a/apps/api/v1/rest/instructors/src/index.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { PrismaClient } from "@libs/db"; -import { createErrorResult, createOKResult } from "ant-stack"; -import type { InternalHandler } from "ant-stack"; -import { ZodError } from "zod"; - -import { constructPrismaQuery } from "./lib"; -import { QuerySchema } from "./schema"; - -let prisma: PrismaClient; - -export const GET: InternalHandler = async (request) => { - const { headers, params, query, requestId } = request; - - prisma ??= new PrismaClient(); - - if (request.isWarmerRequest) { - try { - await prisma.$connect(); - return createOKResult("Warmed", headers, requestId); - } catch (e) { - createErrorResult(500, e, requestId); - } - } - - if (params?.id) { - try { - if (params.id === "all") { - const instructors = await prisma.instructor.findMany(); - return createOKResult(instructors, headers, requestId); - } - return createOKResult( - await prisma.instructor.findFirstOrThrow({ - where: { ucinetid: decodeURIComponent(params.id) }, - }), - headers, - requestId, - ); - } catch { - return createErrorResult(404, `Instructor ${params.id} not found`, requestId); - } - } else { - try { - const parsedQuery = QuerySchema.parse(query); - // The query object being empty shouldn't return all courses, since there's /courses/all for that. - if (!Object.keys(parsedQuery).length) - return createErrorResult(400, "Instructor UCInetID not provided", requestId); - const instructors = await prisma.instructor.findMany({ - where: constructPrismaQuery(parsedQuery), - }); - if (parsedQuery.taughtInTerms) { - const terms = new Set(parsedQuery.taughtInTerms); - return createOKResult( - instructors.filter( - (instructor) => - [...new Set(Object.values(instructor.courseHistory as Record))] - .flat() - .filter((x) => terms.has(x)).length, - ), - headers, - requestId, - ); - } - return createOKResult(instructors, headers, requestId); - } catch (error) { - if (error instanceof ZodError) { - const messages = error.issues.map((issue) => issue.message); - return createErrorResult(400, messages.join("; "), requestId); - } - return createErrorResult(400, error, requestId); - } - } -}; diff --git a/apps/api/v1/rest/larc/package.json b/apps/api/v1/rest/larc/package.json deleted file mode 100644 index 78c0ba95..00000000 --- a/apps/api/v1/rest/larc/package.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "name": "api-v1-rest-larc", - "version": "0.0.0", - "private": true, - "type": "module", - "scripts": { - "build": "ant-stack build" - }, - "dependencies": { - "ant-stack": "workspace:*", - "cheerio": "1.0.0-rc.12", - "cross-fetch": "4.0.0", - "zod": "3.22.2" - } -} diff --git a/apps/api/v1/rest/websoc/ant.config.ts b/apps/api/v1/rest/websoc/ant.config.ts deleted file mode 100644 index d4fc3d16..00000000 --- a/apps/api/v1/rest/websoc/ant.config.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { dirname, resolve } from "node:path"; -import { fileURLToPath } from "node:url"; - -import type { AntConfigStub } from "ant-stack/config"; -import env from "../../../../../env"; - -import { cleanCopy, selectDelete } from "@libs/build-tools"; - -// ESM hack for __dirname -const cwd = dirname(fileURLToPath(import.meta.url)); - -// The relative path to the generated Prisma Client. -const prismaClientDir = "./node_modules/@libs/db/node_modules/prisma"; - -const prismaSchema = "./node_modules/@libs/db/prisma/schema.prisma"; - -const outDir = resolve(cwd, "./dist"); - -const config: AntConfigStub = { - esbuild: { - external: process.env.NODE_ENV === "development" ? [] : ["@services/websoc-proxy"], - plugins: [ - cleanCopy({ cwd, outDir, prismaClientDir, prismaSchema }), - selectDelete(env.NODE_ENV, outDir), - ], - }, -}; - -export default config; diff --git a/apps/api/v1/rest/websoc/package.json b/apps/api/v1/rest/websoc/package.json deleted file mode 100644 index 1007b01a..00000000 --- a/apps/api/v1/rest/websoc/package.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "name": "api-v1-rest-websoc", - "version": "0.0.0", - "private": true, - "type": "module", - "scripts": { - "build": "ant-stack build" - }, - "dependencies": { - "@aws-sdk/client-lambda": "3.398.0", - "@libs/db": "workspace:*", - "@libs/websoc-utils": "workspace:*", - "ant-stack": "workspace:*", - "zod": "3.22.2" - }, - "devDependencies": { - "@libs/build-tools": "workspace:*", - "@services/websoc-proxy": "workspace:*", - "peterportal-api-next-types": "workspace:*" - } -} diff --git a/apps/api/v1/rest/week/ant.config.ts b/apps/api/v1/rest/week/ant.config.ts deleted file mode 100644 index 819b501b..00000000 --- a/apps/api/v1/rest/week/ant.config.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { dirname, resolve } from "node:path"; -import { fileURLToPath } from "node:url"; - -import type { AntConfigStub } from "ant-stack/config"; -import env from "../../../../../env"; - -import { cleanCopy, selectDelete } from "@libs/build-tools"; - -// ESM hack for __dirname -const cwd = dirname(fileURLToPath(import.meta.url)); - -// The relative path to the generated Prisma Client. -const prismaClientDir = "./node_modules/@libs/db/node_modules/prisma"; - -const prismaSchema = "./node_modules/@libs/db/prisma/schema.prisma"; - -const outDir = resolve(cwd, "./dist"); - -const config: AntConfigStub = { - esbuild: { - plugins: [ - cleanCopy({ cwd, outDir, prismaClientDir, prismaSchema }), - selectDelete(env.NODE_ENV, outDir), - ], - }, -}; - -export default config; diff --git a/apps/api/v1/rest/week/package.json b/apps/api/v1/rest/week/package.json deleted file mode 100644 index fd702e4f..00000000 --- a/apps/api/v1/rest/week/package.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "api-v1-rest-week", - "version": "0.0.0", - "private": true, - "type": "module", - "scripts": { - "build": "ant-stack build" - }, - "dependencies": { - "@libs/db": "workspace:*", - "ant-stack": "workspace:*", - "zod": "3.22.2" - }, - "devDependencies": { - "@libs/build-tools": "workspace:*", - "peterportal-api-next-types": "workspace:*" - } -} diff --git a/apps/docs/cdk/app.ts b/apps/docs/cdk/app.ts deleted file mode 100644 index 674ae45a..00000000 --- a/apps/docs/cdk/app.ts +++ /dev/null @@ -1,10 +0,0 @@ -import "dotenv/config"; - -import { App } from "aws-cdk-lib"; - -import { DocsStack } from "./DocsStack"; - -// Instantiate the CDK app and the documentation stack. - -const app = new App({ autoSynth: true }); -new DocsStack(app, "peterportal-api-next-docs"); diff --git a/apps/docs/cdk/cdk.json b/apps/docs/cdk/cdk.json deleted file mode 100644 index 5854b710..00000000 --- a/apps/docs/cdk/cdk.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "app": "npx tsx app.ts", - "watch": { - "include": ["**"], - "exclude": [ - "README.md", - "cdk*.json", - "**/*.d.ts", - "**/*.js", - "tsconfig.json", - "package*.json", - "yarn.lock", - "node_modules", - "test" - ] - }, - "context": { - "@aws-cdk/aws-lambda:recognizeLayerVersion": true, - "@aws-cdk/core:checkSecretUsage": true, - "@aws-cdk/core:target-partitions": ["aws", "aws-cn"], - "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, - "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true, - "@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true, - "@aws-cdk/aws-iam:minimizePolicies": true, - "@aws-cdk/core:validateSnapshotRemovalPolicy": true, - "@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true, - "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, - "@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true, - "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, - "@aws-cdk/core:enablePartitionLiterals": true, - "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, - "@aws-cdk/aws-iam:standardizedServicePrincipals": true, - "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true - } -} diff --git a/apps/docs/cdk/package.json b/apps/docs/cdk/package.json deleted file mode 100644 index 1c20f434..00000000 --- a/apps/docs/cdk/package.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "name": "docs-cdk", - "version": "0.0.0", - "private": true, - "type": "module", - "scripts": { - "deploy": "cdk deploy --all --require-approval never", - "destroy": "cdk destroy --all --force" - }, - "dependencies": { - "docs": "workspace:*", - "dotenv": "16.3.1" - }, - "devDependencies": { - "@types/babel__traverse": "7.20.1", - "@types/node": "18.17.12", - "aws-cdk": "2.93.0", - "aws-cdk-lib": "2.93.0", - "constructs": "10.2.69", - "source-map-support": "0.5.21", - "tsx": "3.12.7", - "typescript": "5.2.2" - } -} diff --git a/apps/docs/cdk/tsconfig.json b/apps/docs/cdk/tsconfig.json deleted file mode 100644 index 32b3cf6c..00000000 --- a/apps/docs/cdk/tsconfig.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "extends": "../../../tsconfig.json", - "exclude": ["cdk.out/", "node_modules/"], - "include": ["./**/*"] -} diff --git a/apps/docs/docusaurus.config.js b/apps/docs/docusaurus.config.js index cfbf133e..bbfde86e 100644 --- a/apps/docs/docusaurus.config.js +++ b/apps/docs/docusaurus.config.js @@ -22,8 +22,7 @@ const config = { ({ docs: { sidebarPath: require.resolve("./sidebars.js"), - editUrl: - "https://github.com/icssc/peterportal-api-next/tree/ant-stack-migration/apps/docs/", + editUrl: "https://github.com/icssc/peterportal-api-next/tree/main/apps/docs/", showLastUpdateTime: true, remarkPlugins: [[require("@docusaurus/remark-plugin-npm2yarn"), { sync: true }]], }, diff --git a/apps/docs/package.json b/apps/docs/package.json index ca21fb41..29dda65d 100644 --- a/apps/docs/package.json +++ b/apps/docs/package.json @@ -1,5 +1,5 @@ { - "name": "docs", + "name": "@apps/docs", "version": "0.0.0", "private": true, "scripts": { @@ -38,8 +38,5 @@ "@tsconfig/docusaurus": "2.0.0", "@types/node": "18.17.12", "typescript": "5.2.2" - }, - "engines": { - "node": "18" } } diff --git a/env.ts b/env.ts deleted file mode 100644 index 4f0da78a..00000000 --- a/env.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { resolve } from "node:path"; -import { fileURLToPath } from "node:url"; - -import { type } from "arktype"; -import { config } from "dotenv"; - -const __dirname = fileURLToPath(new URL(".", import.meta.url)); - -/** - * esbuild will pick up on this and copy the env file to the output folder. - */ -try { - require("./.env"); -} catch { - /* noop */ -} - -config({ path: resolve(__dirname, ".env") }); - -export const envSchema = type( - { - DATABASE_URL: "string", - NODE_ENV: "string", - }, - { keys: "distilled" } -); - -export const env = envSchema.assert({ ...process.env }); - -export default env; diff --git a/libs/build-tools/package.json b/libs/build-tools/package.json deleted file mode 100644 index 56b17fe7..00000000 --- a/libs/build-tools/package.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "name": "@libs/build-tools", - "version": "0.0.0", - "private": true, - "license": "MIT", - "type": "module", - "main": "src/index.ts", - "devDependencies": { - "esbuild": "0.19.2" - } -} diff --git a/libs/build-tools/src/clean-copy.ts b/libs/build-tools/src/clean-copy.ts deleted file mode 100644 index 67705d48..00000000 --- a/libs/build-tools/src/clean-copy.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { chmod, copyFile, mkdir, readdir, rm } from "node:fs/promises"; -import { join } from "node:path"; - -import type { Plugin } from "esbuild"; - -type CleanCopyOptions = { - /** - * The current working directory. - */ - cwd: string; - /** - * The output directory. - */ - outDir: string; - /** - * The path to the Prisma Client. - */ - prismaClientDir: string; - /** - * The path to the Prisma schema. - */ - prismaSchema: string; -}; - -/** - * Generates an instance of the clean-copy plugin. The plugin does the following: - * - cleans the output directory; - * - copies all Prisma query engines to the output directory; - * - copies the Prisma schema to the output directory; and - * - sets the mode of each query engine to 755. - * @param options Copy options. - */ -export const cleanCopy = (options: CleanCopyOptions): Plugin => ({ - name: "clean-copy", - setup(build) { - build.onStart(async () => { - await rm(options.outDir, { recursive: true, force: true }); - await mkdir(options.outDir); - const queryEngines = (await readdir(join(options.cwd, options.prismaClientDir))).filter((x) => - x.endsWith(".so.node"), - ); - await Promise.all( - queryEngines.map((x) => - copyFile(join(options.cwd, `${options.prismaClientDir}/${x}`), join(options.outDir, x)), - ), - ); - await copyFile(options.prismaSchema, join(options.outDir, "schema.prisma")); - await Promise.all(queryEngines.map((x) => chmod(join(options.outDir, `${x}`), 0o755))); - }); - }, -}); diff --git a/libs/build-tools/src/index.ts b/libs/build-tools/src/index.ts deleted file mode 100644 index 17fe183d..00000000 --- a/libs/build-tools/src/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./clean-copy"; -export * from "./select-delete"; diff --git a/libs/build-tools/src/select-delete.ts b/libs/build-tools/src/select-delete.ts deleted file mode 100644 index a5b53ea4..00000000 --- a/libs/build-tools/src/select-delete.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { readdir, rm } from "node:fs/promises"; -import { join } from "node:path"; - -import { Plugin } from "esbuild"; - -/** - * - * @param nodeEnv - * @param outDir - */ -export const selectDelete = (nodeEnv: string, outDir: string): Plugin => ({ - name: "select-delete", - setup(build) { - build.onEnd(async () => { - if (nodeEnv === "development") return; - const queryEngines = (await readdir(outDir)).filter((x) => x.endsWith(".so.node")); - if (queryEngines.length === 1) return; - await Promise.all( - queryEngines - .filter((x) => x !== "libquery_engine-linux-arm64-openssl-1.0.x.so.node") - .map((x) => rm(join(outDir, x))) - ); - }); - }, -}); diff --git a/libs/lambda/package.json b/libs/lambda/package.json new file mode 100644 index 00000000..0e4e7ae0 --- /dev/null +++ b/libs/lambda/package.json @@ -0,0 +1,16 @@ +{ + "name": "@libs/lambda", + "version": "0.0.0", + "private": true, + "license": "MIT", + "type": "module", + "main": "src/index.ts", + "types": "src/index.ts", + "dependencies": { + "winston": "3.10.0" + }, + "devDependencies": { + "@peterportal-api/types": "workspace:^", + "@types/aws-lambda": "8.10.119" + } +} diff --git a/libs/lambda/src/compress.ts b/libs/lambda/src/compress.ts new file mode 100644 index 00000000..2c9c35c5 --- /dev/null +++ b/libs/lambda/src/compress.ts @@ -0,0 +1,59 @@ +import { deflateSync, gunzipSync, gzipSync, inflateSync } from "node:zlib"; + +/** + * The payload size above which we want to start compressing the response. + * Default: 128 KiB + */ +const MIN_COMPRESSION_SIZE = 128 * 1024; + +/** + * Mapping of compression algorithms to their function calls. + */ +const compressionAlgorithms: Record Buffer> = { + gzip: gzipSync, + deflate: deflateSync, +}; + +/** + * Pre-calculated entries for the compression algorithms. + */ +const compressionAlgorithmEntries = Object.entries(compressionAlgorithms); + +/** + * Mapping of decompression algorithms to their function calls. + */ +const decompressionAlgorithms: Record Buffer> = { + gzip: gunzipSync, + deflate: inflateSync, +}; + +export interface CompressionResult { + body: string; + method?: string; +} + +export function compress(body: string, acceptEncoding?: string): CompressionResult { + // Default to using gzip if the body size is greater than the threshold. + if (acceptEncoding === "" || body.length <= MIN_COMPRESSION_SIZE) { + return { body }; + } + + const matchingAlgorithm = compressionAlgorithmEntries.find( + (algorithm) => acceptEncoding?.includes(algorithm[0]), + ); + + // If accept-encoding is present and not empty, prioritize gzip over deflate. + // Unfortunately API Gateway does not currently support Brotli :( + if (matchingAlgorithm) { + return { + body: matchingAlgorithm[1](body).toString("base64"), + method: matchingAlgorithm[0], + }; + } + + return { body }; +} + +export function decompress(body: string, contentEncoding: string): string { + return decompressionAlgorithms[contentEncoding](Buffer.from(body, "base64")).toString(); +} diff --git a/packages/ant-stack/src/lambda-core/constants.ts b/libs/lambda/src/constants.ts similarity index 100% rename from packages/ant-stack/src/lambda-core/constants.ts rename to libs/lambda/src/constants.ts diff --git a/libs/lambda/src/index.ts b/libs/lambda/src/index.ts new file mode 100644 index 00000000..4747115a --- /dev/null +++ b/libs/lambda/src/index.ts @@ -0,0 +1,4 @@ +export * from "./logger"; +export * from "./compress"; +export * from "./response"; +export * from "./constants"; diff --git a/packages/ant-stack/src/lambda-core/logger.ts b/libs/lambda/src/logger.ts similarity index 97% rename from packages/ant-stack/src/lambda-core/logger.ts rename to libs/lambda/src/logger.ts index 090c25ab..dfdbbf41 100644 --- a/packages/ant-stack/src/lambda-core/logger.ts +++ b/libs/lambda/src/logger.ts @@ -6,7 +6,7 @@ import { createLogger, format, transports } from "winston"; const devFormat = format.combine( format.colorize({ all: true }), format.timestamp(), - format.printf((info) => `${info.timestamp} [${info.level}] ${info.message}`) + format.printf((info) => `${info.timestamp} [${info.level}] ${info.message}`), ); /** diff --git a/packages/ant-stack/src/lambda-core/internal/response.ts b/libs/lambda/src/response.ts similarity index 88% rename from packages/ant-stack/src/lambda-core/internal/response.ts rename to libs/lambda/src/response.ts index cdb73109..a4e90d62 100644 --- a/packages/ant-stack/src/lambda-core/internal/response.ts +++ b/libs/lambda/src/response.ts @@ -1,9 +1,9 @@ +import type { ErrorResponse, Response } from "@peterportal-api/types"; import type { APIGatewayProxyResult } from "aws-lambda"; -import type { ErrorResponse, Response } from "peterportal-api-next-types"; -import { compress } from "../../utils"; -import { httpErrorCodes, months } from "../constants"; -import { logger } from "../logger"; +import { compress } from "./compress"; +import { httpErrorCodes, months } from "./constants"; +import { logger } from "./logger"; /** * Common response headers. @@ -42,20 +42,26 @@ export function createTimestamp(date: Date = new Date()): string { */ export function createOKResult( payload: T, - requestHeaders: Record, + requestHeaders: Record, requestId: string, ): APIGatewayProxyResult { const statusCode = 200; const timestamp = createTimestamp(); const response: Response = { statusCode, timestamp, requestId, payload }; const headers = { ...responseHeaders }; + try { const { body, method } = compress(JSON.stringify(response), requestHeaders["accept-encoding"]); - if (method) headers["Content-Encoding"] = method; + + if (method) { + headers["content-encoding"] = method; + } + logger.info("200 OK"); + return { statusCode, - isBase64Encoded: !!headers["Content-Encoding"], + isBase64Encoded: !!method, body, headers, }; diff --git a/libs/registrar-api/package.json b/libs/registrar-api/package.json deleted file mode 100644 index 3d9507a5..00000000 --- a/libs/registrar-api/package.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "name": "@libs/registrar-api", - "version": "0.0.0", - "private": true, - "license": "MIT", - "type": "module", - "main": "src/index.ts", - "types": "src/index.ts", - "scripts": { - "build": "tsup", - "test": "vitest run --coverage", - "test:watch": "vitest run --coverage --watch" - }, - "dependencies": { - "cheerio": "1.0.0-rc.12", - "cross-fetch": "4.0.0" - }, - "devDependencies": { - "@vitest/coverage-istanbul": "0.34.3", - "peterportal-api-next-types": "*", - "tsup": "7.2.0", - "vitest": "0.34.3" - } -} diff --git a/libs/registrar-api/tests/index.test.ts b/libs/registrar-api/tests/index.test.ts deleted file mode 100644 index 0e9ea888..00000000 --- a/libs/registrar-api/tests/index.test.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { describe, expect, test } from "vitest"; - -import { getTermDateData } from "../src"; - -describe("registrar-api tests", () => { - test("getTermData on invalid year throws error", () => { - expect(getTermDateData("0")).rejects.toThrow(); - }); - test("getTermData on non-numeric year throws error", () => { - expect(getTermDateData("asdf")).rejects.toThrow(); - }); - test("getTermData return for 2022-23 AY is correct", async () => { - const data = await getTermDateData("2022"); - expect(data["2022 Fall"]).toEqual({ - instructionStart: new Date(2022, 8, 22), - instructionEnd: new Date(2022, 11, 2), - finalsStart: new Date(2022, 11, 3), - finalsEnd: new Date(2022, 11, 9), - }); - expect(data["2023 Winter"]).toEqual({ - instructionStart: new Date(2023, 0, 9), - instructionEnd: new Date(2023, 2, 17), - finalsStart: new Date(2023, 2, 18), - finalsEnd: new Date(2023, 2, 24), - }); - expect(data["2023 Spring"]).toEqual({ - instructionStart: new Date(2023, 3, 3), - instructionEnd: new Date(2023, 5, 9), - finalsStart: new Date(2023, 5, 10), - finalsEnd: new Date(2023, 5, 15), - }); - expect(data["2023 Summer1"]).toEqual({ - instructionStart: new Date(2023, 5, 26), - instructionEnd: new Date(2023, 7, 1), - finalsStart: new Date(2023, 7, 2), - finalsEnd: new Date(2023, 7, 3), - }); - expect(data["2023 Summer10wk"]).toEqual({ - instructionStart: new Date(2023, 5, 26), - instructionEnd: new Date(2023, 7, 31), - finalsStart: new Date(2023, 8, 1), - finalsEnd: new Date(2023, 8, 1), - }); - expect(data["2023 Summer2"]).toEqual({ - instructionStart: new Date(2023, 7, 7), - instructionEnd: new Date(2023, 8, 11), - finalsStart: new Date(2023, 8, 12), - finalsEnd: new Date(2023, 8, 13), - }); - }); -}); diff --git a/libs/registrar-api/tsup.config.ts b/libs/registrar-api/tsup.config.ts deleted file mode 100644 index 0565869b..00000000 --- a/libs/registrar-api/tsup.config.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { defineConfig } from "tsup"; - -/** - * @see https://github.com/evanw/esbuild/issues/1921#issuecomment-1491470829 - */ -const js = `\ -import * as path from 'path'; -import { fileURLToPath } from 'url'; -import { createRequire as topLevelCreateRequire } from 'module'; -const require = topLevelCreateRequire(import.meta.url); -const __filename = fileURLToPath(import.meta.url); -const __dirname = path.dirname(__filename); -`; - -const config: ReturnType = defineConfig({ - entry: ["src/index.ts"], - bundle: true, - external: ["esbuild"], - noExternal: [/(.*)/], - format: ["esm"], - banner(ctx) { - return ctx.format === "esm" ? { js } : undefined; - }, -}); -export default config; diff --git a/libs/registrar-api/vitest.config.ts b/libs/registrar-api/vitest.config.ts deleted file mode 100644 index c11f63da..00000000 --- a/libs/registrar-api/vitest.config.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { defineConfig } from "vitest/config"; - -const config: ReturnType = defineConfig({ - test: { - globals: true, - coverage: { - provider: "istanbul", - }, - }, -}); - -export default config; diff --git a/libs/uc-irvine-api/package.json b/libs/uc-irvine-api/package.json new file mode 100644 index 00000000..83df8fd6 --- /dev/null +++ b/libs/uc-irvine-api/package.json @@ -0,0 +1,62 @@ +{ + "name": "@libs/uc-irvine-api", + "version": "0.10.8", + "private": true, + "description": "SDK for various UC Irvine services", + "keywords": [], + "homepage": "https://docs.api-next.peterportal.org", + "bugs": { + "url": "https://github.com/icssc/peterportal-api-next/issues" + }, + "repository": { + "type": "git", + "url": "https://github.com/icssc/peterportal-api-next", + "directory": "libs/uc-irvine-api" + }, + "license": "MIT", + "type": "module", + "exports": { + "./registrar": { + "types": "./src/registrar/index.ts", + "import": "./src/registrar/index.ts", + "require": "./src/registrar/index.ts", + "default": "./src/registrar/index.ts" + }, + "./websoc": { + "types": "./src/websoc/index.ts", + "import": "./src/websoc/index.ts", + "require": "./src/websoc/index.ts", + "default": "./src/websoc/index.ts" + } + }, + "main": "src/index.ts", + "types": "src/index.ts", + "typesVersions": { + "*": { + "registrar": [ + "./src/registrar/index.ts" + ], + "websoc": [ + "./src/websoc/index.ts" + ] + } + }, + "files": [ + "dist", + "src" + ], + "dependencies": { + "@ap0nia/camaro": "6.2.5", + "@peterportal-api/types": "workspace:^", + "cheerio": "1.0.0-rc.12", + "cross-fetch": "4.0.0" + }, + "packageManager": "pnpm@8.7.4", + "engines": { + "node": "18", + "pnpm": "8" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/libs/registrar-api/src/index.ts b/libs/uc-irvine-api/src/registrar/index.ts similarity index 86% rename from libs/registrar-api/src/index.ts rename to libs/uc-irvine-api/src/registrar/index.ts index 4384aba1..908cca29 100644 --- a/libs/registrar-api/src/index.ts +++ b/libs/uc-irvine-api/src/registrar/index.ts @@ -1,21 +1,17 @@ +import { QuarterDates, quarters } from "@peterportal-api/types"; import { load } from "cheerio"; import fetch from "cross-fetch"; -import { QuarterDates, quarters } from "peterportal-api-next-types"; - -/* region Constants */ const months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; -/* region Helper functions */ - -const addSingleDateRow = ( +function addSingleDateRow( data: string[][], index: number, key: string, record: Record>, year: string, offset = 0, -): void => { +): void { for (const [idx, date] of data[index].entries()) { const currYear = idx == offset ? parseInt(year) : parseInt(year) + 1; const [month, day] = date.split(" "); @@ -25,9 +21,9 @@ const addSingleDateRow = ( parseInt(day), ); } -}; +} -const addMultipleDateRow = ( +function addMultipleDateRow( data: string[][], index: number, keyStart: string, @@ -35,49 +31,69 @@ const addMultipleDateRow = ( record: Record>, year: string, offset = 0, -): void => { +): void { for (const [idx, date] of data[index].entries()) { const currYear = idx == offset ? parseInt(year) : parseInt(year) + 1; + const start = date.split("–")[0]; + let end = date.split("–")[1]; - if (end === undefined) end = start; + + if (end === undefined) { + end = start; + } + const [startMonth, startDay] = start.split(" "); + let [endMonth, endDay] = end.split(" "); + if (endDay === undefined) { endDay = endMonth; endMonth = startMonth; } + record[`${currYear} ${quarters[idx + offset]}`][keyStart] = new Date( currYear, months.indexOf(startMonth), parseInt(startDay), ); + record[`${currYear} ${quarters[idx + offset]}`][keyEnd] = new Date( currYear, months.indexOf(endMonth), parseInt(endDay), ); } -}; +} -/* endregion */ - -/* region Exported functions */ +/** + * Returns relevant date data for each term in the given academic year. + */ +export async function getTermDateData(year: string): Promise> { + if (year.length !== 4 || isNaN(parseInt(year))) { + throw new Error("Error: Invalid year provided."); + } -// Returns relevant date data for each term in the given academic year. -export const getTermDateData = async (year: string): Promise> => { - if (year.length !== 4 || isNaN(parseInt(year))) throw new Error("Error: Invalid year provided."); const shortYear = year.slice(2); + const response = await fetch( `https://www.reg.uci.edu/calendars/quarterly/${year}-${ parseInt(year, 10) + 1 }/quarterly${shortYear}-${parseInt(shortYear, 10) + 1}.html`, ); - if (response.status === 404) return {}; + + if (response.status === 404) { + return {}; + } + const quarterData: string[][] = []; + const summerSessionData: string[][] = []; + const $ = load(await response.text()); + const $table = $("table.calendartable"); + $table .eq(2) .find("tr") @@ -91,6 +107,7 @@ export const getTermDateData = async (year: string): Promise `${i == 0 ? year : parseInt(year) + 1} ${x}`) .reduce( @@ -113,6 +131,7 @@ export const getTermDateData = async (year: string): Promise>, ); + addSingleDateRow(quarterData, 2, "instructionStart", ret, year); addSingleDateRow(quarterData, 17, "instructionEnd", ret, year); addMultipleDateRow(quarterData, 18, "finalsStart", "finalsEnd", ret, year); @@ -128,6 +147,7 @@ export const getTermDateData = async (year: string): Promise; -}; -/* endregion */ + return ret as Record; +} diff --git a/libs/websoc-api-next/src/index.ts b/libs/uc-irvine-api/src/websoc/index.ts similarity index 95% rename from libs/websoc-api-next/src/index.ts rename to libs/uc-irvine-api/src/websoc/index.ts index 7759f101..e11aa80e 100644 --- a/libs/websoc-api-next/src/index.ts +++ b/libs/uc-irvine-api/src/websoc/index.ts @@ -1,9 +1,7 @@ import { transform } from "@ap0nia/camaro"; +import type { Department, TermData } from "@peterportal-api/types"; import { load } from "cheerio"; import fetch from "cross-fetch"; -import type { Department, TermData } from "peterportal-api-next-types"; - -/* region Constants */ const template = { schools: [ @@ -68,10 +66,6 @@ const template = { ], }; -/* endregion */ - -/* region Internal type declarations */ - type RequireAtLeastOne = Omit & { [P in R]: Required> & Partial> }[R]; @@ -110,10 +104,6 @@ type OptionalOptions = { cancelledCourses?: CancelledCourses; }; -/* endregion */ - -/* region Exported type declarations */ - /** * The list of quarters in an academic year. */ @@ -135,6 +125,7 @@ export const sectionTypes = [ "Tap", "Tut", ] as const; + /** * The list of options for filtering full courses. */ @@ -144,10 +135,12 @@ export const fullCoursesOptions = [ "FullOnly", "OverEnrolled", ] as const; + /** * The list of options for filtering cancelled courses. */ export const cancelledCoursesOptions = ["Exclude", "Include", "Only"] as const; + /** * The list of GE category codes. */ @@ -163,6 +156,7 @@ export const geCodes = [ "GE-7", "GE-8", ] as const; + /** * The list of GE category names. */ @@ -178,10 +172,12 @@ export const geCategories = [ "GE VII: Multicultural Studies", "GE VIII: International/Global Issues", ] as const; + /** * The list of division codes. */ export const divisionCodes = ["LowerDiv", "UpperDiv", "Graduate"] as const; + /** * The list of course level (division) names. */ @@ -195,31 +191,39 @@ export const courseLevels = [ * Represents the absence of a particular value to filter for. */ export const anyArray = ["ANY"] as const; + export type Any = (typeof anyArray)[number]; + /** * The quarter in an academic year. */ export type Quarter = (typeof quarters)[number]; + /** * The type of the section. */ export type SectionType = Any | (typeof sectionTypes)[number]; + /** * The option to filter full courses by. */ export type FullCourses = Any | (typeof fullCoursesOptions)[number]; + /** * The option to filter cancelled courses by. */ export type CancelledCourses = (typeof cancelledCoursesOptions)[number]; + /** * The GE category code. */ export type GE = Any | (typeof geCodes)[number]; + /** * The division code. */ export type Division = Any | (typeof divisionCodes)[number]; + /** * The course level name. */ @@ -233,10 +237,12 @@ export type WebsocSectionMeeting = { * What day(s) the section meets on (e.g. ``MWF``). */ days: string; + /** * What time the section meets at. */ time: string; + /** * The building(s) the section meets in. */ @@ -251,6 +257,7 @@ export type WebsocSectionEnrollment = { * The total number of students enrolled in this section. */ totalEnrolled: string; + /** * The number of students enrolled in the section referred to by this section * code, if the section is cross-listed. If the section is not cross-listed, @@ -267,63 +274,78 @@ export type WebsocSection = { * The section code. */ sectionCode: string; + /** * The section type (e.g. ``Lec``, ``Dis``, ``Lab``, etc.) */ sectionType: string; + /** * The section number (e.g. ``A1``). */ sectionNum: string; + /** * The number of units afforded by taking this section. */ units: string; + /** * The name(s) of the instructor(s) teaching this section. */ instructors: string[]; + /** * The meeting time(s) of this section. */ meetings: WebsocSectionMeeting[]; + /** * The date and time of the final exam for this section. */ finalExam: string; + /** * The maximum capacity of this section. */ maxCapacity: string; + /** * The number of students currently enrolled (cross-listed or otherwise) in * this section. */ numCurrentlyEnrolled: WebsocSectionEnrollment; + /** * The number of students currently on the waitlist for this section. */ numOnWaitlist: string; + /** * The maximum number of students that can be on the waitlist for this section. */ numWaitlistCap: string; + /** * The number of students who have requested to be enrolled in this section. */ numRequested: string; + /** * The number of seats in this section reserved for new students. */ numNewOnlyReserved: string; + /** * The restriction code(s) for this section. */ restrictions: string; + /** * The enrollment status. */ status: "OPEN" | "Waitl" | "FULL" | "NewOnly"; + /** * Any comments for the section. */ @@ -338,22 +360,27 @@ export type WebsocCourse = { * The code of the department the course belongs to. */ deptCode: string; + /** * The course number. */ courseNumber: string; + /** * The title of the course. */ courseTitle: string; + /** * Any comments for the course. */ courseComment: string; + /** * The link to the WebReg Course Prerequisites page for this course. */ prerequisiteLink: string; + /** * All sections of the course. */ @@ -368,22 +395,27 @@ export type WebsocDepartment = { * The name of the department. */ deptName: string; + /** * The department code. */ deptCode: string; + /** * Any comments from the department. */ deptComment: string; + /** * All courses of the department. */ courses: WebsocCourse[]; + /** * Any comments for section code(s) under the department. */ sectionCodeRangeComments: string[]; + /** * Any comments for course number(s) under the department. */ @@ -398,10 +430,12 @@ export type WebsocSchool = { * The name of the school. */ schoolName: string; + /** * Any comments from the school. */ schoolComment: string; + /** * All departments of the school. */ @@ -416,6 +450,7 @@ export type Term = { * The year of the term. */ year: string; + /** * The quarter of the term. */ @@ -446,11 +481,7 @@ export type WebsocAPIResponse = { */ export type WebsocAPIOptions = RequiredOptions & BuildingRoomOptions & OptionalOptions; -/* endregion */ - -/* region Internal helper functions */ - -const getCodedTerm = (term: Term): string => { +function getCodedTerm(term: Term): string { switch (term.quarter) { case "Fall": return `${term.year}-92`; @@ -465,9 +496,9 @@ const getCodedTerm = (term: Term): string => { case "Summer2": return `${term.year}-76`; } -}; +} -const getCodedDiv = (div: Division): string => { +function getCodedDiv(div: Division): string { switch (div) { case "ANY": return "all"; @@ -478,16 +509,12 @@ const getCodedDiv = (div: Division): string => { case "Graduate": return "2xx"; } -}; - -/* endregion */ - -/* region Exported functions */ +} -export const callWebSocAPI = async ( +export async function callWebSocAPI( term: Term, options: WebsocAPIOptions, -): Promise => { +): Promise { const { ge = "ANY", department = "ANY", @@ -531,7 +558,9 @@ export const callWebSocAPI = async ( Bldg: building, Room: room, }; + const data = new URLSearchParams(postData); + const response = await fetch("https://www.reg.uci.edu/perl/WebSoc", { method: "POST", body: data, @@ -539,6 +568,7 @@ export const callWebSocAPI = async ( }); const json: WebsocAPIResponse = await transform(await response.text(), template); + json.schools.forEach((school) => school.departments.forEach((department) => { department.deptName = department.deptName.replace(/&/g, "&"); @@ -553,7 +583,7 @@ export const callWebSocAPI = async ( }), ); return json; -}; +} function getUniqueMeetings(meetings: WebsocSectionMeeting[]) { return meetings.reduce((acc, meeting) => { @@ -568,9 +598,11 @@ function getUniqueMeetings(meetings: WebsocSectionMeeting[]) { } // Returns all currently visible undergraduate and graduate terms. -export const getTerms = async (): Promise => { +export async function getTerms(): Promise { const response = await (await fetch("https://www.reg.uci.edu/perl/WebSoc")).text(); + const $ = load(response); + return $("form") .eq(1) .find("td") @@ -621,12 +653,16 @@ export const getTerms = async (): Promise => { }; } }) as TermData[]; -}; +} -// Returns all departments. -export const getDepts = async (): Promise => { +/** + * Returns all departments. + */ +export async function getDepts(): Promise { const response = await (await fetch("https://www.reg.uci.edu/perl/WebSoc")).text(); + const $ = load(response); + return $("form") .eq(1) .find("select") @@ -654,6 +690,4 @@ export const getDepts = async (): Promise => { }; }) .filter((x) => !x.deptLabel.includes("until")); -}; - -/* endregion */ +} diff --git a/libs/uc-irvine-api/tsconfig.json b/libs/uc-irvine-api/tsconfig.json new file mode 100644 index 00000000..dc787c60 --- /dev/null +++ b/libs/uc-irvine-api/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": ["../../tsconfig.json"] +} diff --git a/libs/websoc-api-next/package.json b/libs/websoc-api-next/package.json deleted file mode 100644 index c13c4781..00000000 --- a/libs/websoc-api-next/package.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "name": "@libs/websoc-api-next", - "version": "0.0.0", - "private": true, - "description": "fetches data from UCI's websoc API", - "type": "module", - "main": "src/index.ts", - "types": "src/index.ts", - "scripts": { - "build": "tsup", - "test": "vitest run --coverage", - "test:watch": "vitest run --coverage --watch" - }, - "dependencies": { - "@ap0nia/camaro": "6.2.5", - "cheerio": "1.0.0-rc.12", - "cross-fetch": "4.0.0", - "peterportal-api-next-types": "*" - }, - "devDependencies": { - "@vitest/coverage-istanbul": "0.34.3", - "tsup": "7.2.0", - "vitest": "0.34.3" - } -} diff --git a/libs/websoc-api-next/tests/index.test.ts b/libs/websoc-api-next/tests/index.test.ts deleted file mode 100644 index 5080cdbb..00000000 --- a/libs/websoc-api-next/tests/index.test.ts +++ /dev/null @@ -1,174 +0,0 @@ -import { describe, expect, test } from "vitest"; - -import { callWebSocAPI, getDepts, getTerms } from "../src"; - -describe("websoc-api-next tests", () => { - test("getDepts return includes COMPSCI", async () => { - expect(await getDepts()).toContainEqual({ - deptLabel: "COMPSCI: Computer Science", - deptValue: "COMPSCI", - }); - }); - - test("getTerms return includes 2023 Winter", async () => { - expect(await getTerms()).toContainEqual({ - shortName: "2023 Winter", - longName: "2023 Winter Quarter", - }); - }); - - test("WebSOC query for Lower Division I&C SCI courses in 2021 Fall contains I&C SCI 32A", async () => { - const res = await callWebSocAPI( - { year: "2021", quarter: "Fall" }, - { - department: "I&C SCI", - division: "LowerDiv", - }, - ); - expect(res.schools).toHaveLength(1); - expect(res.schools[0].departments).toHaveLength(1); - expect(res.schools[0].departments[0].deptCode).toEqual("I&C SCI"); - expect( - res.schools[0].departments[0].courses.filter((x) => x.courseNumber == "32A").length, - ).toEqual(1); - }); - - test("WebSOC query for Upper Division COMPSCI courses in 2022 Winter includes COMPSCI 161", async () => { - const res = await callWebSocAPI( - { year: "2022", quarter: "Winter" }, - { - department: "COMPSCI", - division: "UpperDiv", - }, - ); - expect(res.schools).toHaveLength(1); - expect(res.schools[0].departments).toHaveLength(1); - expect(res.schools[0].departments[0].deptCode).toEqual("COMPSCI"); - expect( - res.schools[0].departments[0].courses.filter((x) => x.courseNumber == "161").length, - ).toEqual(1); - }); - - test("WebSOC query for Graduate/Professional COMPSCI courses in 2022 Spring includes COMPSCI 260P", async () => { - const res = await callWebSocAPI( - { year: "2022", quarter: "Spring" }, - { - department: "COMPSCI", - division: "Graduate", - }, - ); - expect(res.schools).toHaveLength(1); - expect(res.schools[0].departments).toHaveLength(1); - expect(res.schools[0].departments[0].deptCode).toEqual("COMPSCI"); - expect( - res.schools[0].departments[0].courses.filter((x) => x.courseNumber == "260P").length, - ).toEqual(1); - }); - - test("WebSOC query for COMPSCI courses in 2022 Summer1 includes COMPSCI 143A", async () => { - const res = await callWebSocAPI( - { year: "2022", quarter: "Summer1" }, - { - department: "COMPSCI", - }, - ); - expect(res.schools).toHaveLength(1); - expect(res.schools[0].departments).toHaveLength(1); - expect(res.schools[0].departments[0].deptCode).toEqual("COMPSCI"); - expect( - res.schools[0].departments[0].courses.filter((x) => x.courseNumber == "143A").length, - ).toEqual(1); - }); - - test("WebSOC query for I&C SCI courses in 2022 Summer10wk includes I&C SCI 31", async () => { - const res = await callWebSocAPI( - { year: "2022", quarter: "Summer10wk" }, - { - department: "I&C SCI", - }, - ); - expect(res.schools).toHaveLength(1); - expect(res.schools[0].departments).toHaveLength(1); - expect(res.schools[0].departments[0].deptCode).toEqual("I&C SCI"); - expect( - res.schools[0].departments[0].courses.filter((x) => x.courseNumber == "31").length, - ).toEqual(1); - }); - - test("WebSOC query for I&C SCI courses in 2022 Summer2 includes I&C SCI 6B", async () => { - const res = await callWebSocAPI( - { year: "2022", quarter: "Summer2" }, - { - department: "I&C SCI", - }, - ); - expect(res.schools).toHaveLength(1); - expect(res.schools[0].departments).toHaveLength(1); - expect(res.schools[0].departments[0].deptCode).toEqual("I&C SCI"); - expect( - res.schools[0].departments[0].courses.filter((x) => x.courseNumber == "6B").length, - ).toEqual(1); - }); - - test("WebSOC query for I&C SCI 6B in 2022 Summer2 has blank waitlist count in all sections", async () => { - const res = await callWebSocAPI( - { year: "2022", quarter: "Summer2" }, - { - department: "I&C SCI", - courseNumber: "6B", - }, - ); - expect(res.schools).toHaveLength(1); - expect(res.schools[0].departments).toHaveLength(1); - expect(res.schools[0].departments[0].deptCode).toEqual("I&C SCI"); - expect( - res.schools[0].departments[0].courses[0].sections.every((x) => x.numOnWaitlist == ""), - ).toBeTruthy(); - }); - - test("WebSOC query for ECON courses in 2023 Winter includes comments for multiple course number ranges", async () => { - const res = await callWebSocAPI( - { year: "2023", quarter: "Winter" }, - { - department: "ECON", - }, - ); - expect(res.schools).toHaveLength(1); - expect(res.schools[0].departments).toHaveLength(1); - expect(res.schools[0].departments[0].deptCode).toEqual("ECON"); - expect(res.schools[0].departments[0].courseNumberRangeComments.length).toBeGreaterThan(1); - }); - - test("WebSOC query for HUMAN courses in 2023 Winter includes comments for multiple section code ranges", async () => { - const res = await callWebSocAPI( - { year: "2023", quarter: "Winter" }, - { - department: "HUMAN", - }, - ); - expect(res.schools).toHaveLength(1); - expect(res.schools[0].departments).toHaveLength(1); - expect(res.schools[0].departments[0].deptCode).toEqual("HUMAN"); - expect(res.schools[0].departments[0].sectionCodeRangeComments.length).toBeGreaterThan(1); - }); - - test("WebSOC query for GE-2 courses in 2023 Winter includes multiple schools", async () => { - const res = await callWebSocAPI( - { year: "2023", quarter: "Winter" }, - { - ge: "GE-2", - }, - ); - expect(res.schools.length).toBeGreaterThan(1); - }); - - test("WebSOC query for CBEMS (discontinued 2019 SS2) courses in 2023 Winter is empty", async () => { - const res = await callWebSocAPI( - { year: "2023", quarter: "Winter" }, - { - department: "CBEMS", - }, - ); - expect(res.schools.length).toEqual(0); - }); -}); diff --git a/libs/websoc-api-next/tsup.config.ts b/libs/websoc-api-next/tsup.config.ts deleted file mode 100644 index 0565869b..00000000 --- a/libs/websoc-api-next/tsup.config.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { defineConfig } from "tsup"; - -/** - * @see https://github.com/evanw/esbuild/issues/1921#issuecomment-1491470829 - */ -const js = `\ -import * as path from 'path'; -import { fileURLToPath } from 'url'; -import { createRequire as topLevelCreateRequire } from 'module'; -const require = topLevelCreateRequire(import.meta.url); -const __filename = fileURLToPath(import.meta.url); -const __dirname = path.dirname(__filename); -`; - -const config: ReturnType = defineConfig({ - entry: ["src/index.ts"], - bundle: true, - external: ["esbuild"], - noExternal: [/(.*)/], - format: ["esm"], - banner(ctx) { - return ctx.format === "esm" ? { js } : undefined; - }, -}); -export default config; diff --git a/libs/websoc-api-next/vitest.config.ts b/libs/websoc-api-next/vitest.config.ts deleted file mode 100644 index c11f63da..00000000 --- a/libs/websoc-api-next/vitest.config.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { defineConfig } from "vitest/config"; - -const config: ReturnType = defineConfig({ - test: { - globals: true, - coverage: { - provider: "istanbul", - }, - }, -}); - -export default config; diff --git a/libs/websoc-utils/package.json b/libs/websoc-utils/package.json index e9f203c4..9d414cc9 100644 --- a/libs/websoc-utils/package.json +++ b/libs/websoc-utils/package.json @@ -4,5 +4,9 @@ "private": true, "type": "module", "main": "src/index.ts", - "types": "src/index.ts" + "types": "src/index.ts", + "dependencies": { + "@libs/uc-irvine-api": "workspace:^", + "@peterportal-api/types": "workspace:^" + } } diff --git a/libs/websoc-utils/src/index.ts b/libs/websoc-utils/src/index.ts index 03c2dc6c..2b4bc2f7 100644 --- a/libs/websoc-utils/src/index.ts +++ b/libs/websoc-utils/src/index.ts @@ -5,7 +5,7 @@ import type { WebsocSchool, WebsocSection, WebsocSectionMeeting, -} from "@libs/websoc-api-next"; +} from "@libs/uc-irvine-api/websoc"; import type { DayOfWeek, WebsocAPIResponse as NormalizedResponse, @@ -15,7 +15,7 @@ import type { WebsocSchool as NormalizedSchool, WebsocSection as NormalizedSection, WebsocSectionMeeting as NormalizedMeeting, -} from "peterportal-api-next-types"; +} from "@peterportal-api/types"; export type EnhancedSection = { school: WebsocSchool; diff --git a/package.json b/package.json index a740e10c..83abbb23 100644 --- a/package.json +++ b/package.json @@ -3,12 +3,13 @@ "version": "1.0.0-rc.3", "private": true, "description": "The official PeterPortal API-Next monorepo", + "homepage": "https://docs.api-next.peterportal.org", + "license": "MIT", "type": "module", "scripts": { "build": "turbo run build", - "ci:tsc": "npx tsc -p . --noEmit && npx tsc -p apps/docs --noEmit && npx tsc -p apps/docs/cdk --noEmit", + "ci:tsc": "npx tsc -p . --noEmit && npx tsc -p apps/docs --noEmit", "commit": "cz", - "create": "dotenv -v DATABASE_URL=null -v NODE_ENV=development -- pnpm exec ant-stack create", "deploy": "turbo run deploy", "destroy": "turbo run destroy", "dev": "dotenv -c development -- turbo run dev", @@ -21,15 +22,16 @@ "@commitlint/cli": "17.7.1", "@commitlint/config-conventional": "17.7.0", "@commitlint/types": "17.4.4", - "@libs/db": "workspace:*", - "@libs/registrar-api": "workspace:*", - "@libs/websoc-api-next": "workspace:*", + "@tsconfig/node18": "^18.2.1", "@types/lint-staged": "13.2.0", + "@types/node": "18.17.12", "@typescript-eslint/eslint-plugin": "6.5.0", "@typescript-eslint/parser": "6.5.0", - "ant-stack": "^0.1.0", + "arktype": "1.0.16-alpha", + "aws-cdk-lib": "2.93.0", "cz-conventional-changelog": "3.3.0", "devmoji": "2.3.0", + "dotenv": "16.3.1", "eslint": "8.48.0", "eslint-config-prettier": "9.0.0", "eslint-config-turbo": "1.10.13", @@ -39,9 +41,11 @@ "prettier": "3.0.2", "prettier-plugin-packagejson": "2.4.5", "prettier-plugin-prisma": "5.0.0", - "turbo": "1.10.13" + "turbo": "1.10.13", + "typescript": "5.2.2", + "unconfig": "0.3.10" }, - "packageManager": "pnpm@8.7.0", + "packageManager": "pnpm@8.9.0", "engines": { "node": "18", "pnpm": "8" diff --git a/packages/ant-stack/README.md b/packages/ant-stack/README.md deleted file mode 100644 index 4fef4ac3..00000000 --- a/packages/ant-stack/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# 🐜 ant-stack - -_what is this, a serverless stack for ants?_ diff --git a/packages/ant-stack/cdk.json b/packages/ant-stack/cdk.json deleted file mode 100644 index 4770b672..00000000 --- a/packages/ant-stack/cdk.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "app": "npx tsx src/cdk" -} diff --git a/packages/ant-stack/package.json b/packages/ant-stack/package.json deleted file mode 100644 index 53f6b686..00000000 --- a/packages/ant-stack/package.json +++ /dev/null @@ -1,86 +0,0 @@ -{ - "name": "ant-stack", - "version": "0.1.0", - "description": "A serverless stack for ants", - "type": "module", - "exports": { - ".": { - "types": "./dist/lambda-core.d.ts", - "import": "./dist/lambda-core.js", - "default": "./dist/lambda-core.js" - }, - "./cli": { - "types": "./dist/cli.d.ts", - "import": "./dist/cli.js", - "default": "./dist/cli.js" - }, - "./config": { - "types": "./dist/config.d.ts", - "import": "./dist/config.js", - "default": "./dist/config.js" - }, - "./utils": { - "types": "./dist/utils.d.ts", - "import": "./dist/utils.js", - "default": "./dist/utils.js" - } - }, - "main": "dist/lambda-core.js", - "module": "dist/lambda-core.js", - "types": "dist/lambda-core.d.ts", - "typesVersions": { - "*": { - "cli": [ - "./dist/cli.d.ts" - ], - "config": [ - "./dist/config.d.ts" - ], - "utils": [ - "./dist/utils.d.ts" - ] - } - }, - "bin": { - "ant-stack": "./dist/cli.js" - }, - "files": [ - "dist", - "src", - "README.md" - ], - "scripts": { - "build": "tsup", - "deploy": "cdk deploy --all --require-approval never", - "destroy": "cdk destroy --all --force", - "dev": "pnpm exec ant-stack dev", - "start:cli": "node dist/cli" - }, - "dependencies": { - "arktype": "1.0.16-alpha", - "aws-cdk": "2.93.0", - "aws-cdk-lib": "2.93.0", - "body-parser": "1.20.2", - "chalk": "5.3.0", - "chokidar": "3.5.3", - "cleye": "1.3.2", - "consola": "3.2.3", - "constructs": "10.2.69", - "cors": "2.8.5", - "defu": "6.1.2", - "esbuild": "0.19.2", - "express": "4.18.2", - "unconfig": "0.3.10", - "winston": "3.10.0" - }, - "devDependencies": { - "@types/aws-lambda": "8.10.119", - "@types/body-parser": "1.19.2", - "@types/cors": "2.8.13", - "@types/express": "4.17.17", - "peterportal-api-next-types": "*", - "tsup": "7.2.0", - "tsx": "3.12.7", - "typescript": "5.2.2" - } -} diff --git a/packages/ant-stack/src/cdk/index.ts b/packages/ant-stack/src/cdk/index.ts deleted file mode 100644 index 7a8abf96..00000000 --- a/packages/ant-stack/src/cdk/index.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { join, relative } from "node:path"; -import { fileURLToPath as futp } from "node:url"; - -import * as cdk from "aws-cdk-lib"; - -import { getConfig } from "../config"; -import { findAllProjects, searchForWorkspaceRoot } from "../utils"; - -import { AntStack, type HandlerConfig } from "./stack"; - -const __dirname = futp(new URL(".", import.meta.url)); - -function getStage(NODE_ENV = "development") { - switch (NODE_ENV) { - case "production": - return "prod"; - - case "staging": { - if (!process.env.PR_NUM) - throw new Error("Running in staging environment but no PR number specified. Stop."); - return `staging-${process.env.PR_NUM}`; - } - - case "development": - throw new Error("Cannot deploy stack in development environment. Stop."); - - default: - throw new Error("Invalid environment specified. Stop."); - } -} - -async function start() { - const config = await getConfig(); - - /** - * TODO: schema validation. - */ - config.env ??= {}; - delete config.env.env; - delete config.env.envSchema; - config.env.stage = getStage(config.env.NODE_ENV); - - const app = new cdk.App(config.aws.appProps); - - /** - * Configs for all __unique__ Lambda routes. - */ - const handlerConfigs: HandlerConfig[] = findAllProjects( - join(searchForWorkspaceRoot(__dirname), config.directory), - ) - .map((apiRoute) => ({ - route: relative(join(searchForWorkspaceRoot(__dirname), config.directory), apiRoute), - directory: apiRoute, - env: config.env, - rolePropsMapping: config.aws.routeRolePropsMapping, - })) - .filter( - (config, index, configs) => configs.findIndex((c) => c.route === config.route) === index, - ); - - const stack = new AntStack(app, config); - - await Promise.all(handlerConfigs.map((handlerConfig) => stack.addRoute(handlerConfig))); -} - -start(); diff --git a/packages/ant-stack/src/cdk/stack.ts b/packages/ant-stack/src/cdk/stack.ts deleted file mode 100644 index 6cd61292..00000000 --- a/packages/ant-stack/src/cdk/stack.ts +++ /dev/null @@ -1,185 +0,0 @@ -import { Duration, Stack } from "aws-cdk-lib"; -import { EndpointType, LambdaIntegration, ResponseType, RestApi } from "aws-cdk-lib/aws-apigateway"; -import { Certificate } from "aws-cdk-lib/aws-certificatemanager"; -import { Rule, RuleTargetInput, Schedule } from "aws-cdk-lib/aws-events"; -import { LambdaFunction } from "aws-cdk-lib/aws-events-targets"; -import { Role, RoleProps } from "aws-cdk-lib/aws-iam"; -import lambda, { Architecture, Code, Runtime } from "aws-cdk-lib/aws-lambda"; -import { ARecord, HostedZone, RecordTarget } from "aws-cdk-lib/aws-route53"; -import { ApiGateway } from "aws-cdk-lib/aws-route53-targets"; -import type { Construct } from "constructs"; - -import type { AntConfig } from "../config"; -import { type InternalHandler, isHttpMethod, warmerRequestBody } from "../lambda-core"; - -export interface HandlerConfig { - /** - * The API route. - */ - route: string; - - /** - * Directory on the file system to find the API route. - */ - directory: string; - - /** - * Files to exclude. - * @default ["node_modules"] - */ - exclude?: string[]; - - /** - * Environment variables specific to the function. - */ - env?: Record; - - /** - * - */ - rolePropsMapping?: Record; -} - -export class AntStack extends Stack { - api: RestApi; - config: AntConfig; - optionsIntegration: LambdaIntegration; - - constructor(scope: Construct, config: AntConfig) { - super(scope, `${config.aws.id}-${config.env.stage}`, config.aws.stackProps); - - const recordName = `${config.env.stage === "prod" ? "" : `${config.env.stage}.`}api-next`; - - this.config = config; - - this.api = new RestApi(this, `${config.aws.id}-${config.env.stage}`, { - domainName: { - domainName: `${recordName}.${config.aws.zoneName}`, - certificate: Certificate.fromCertificateArn( - this, - "peterportal-cert", - process.env.CERTIFICATE_ARN ?? "", - ), - }, - disableExecuteApiEndpoint: true, - endpointTypes: [EndpointType.EDGE], - binaryMediaTypes: ["*/*"], - restApiName: `${config.aws.id}-${config.env.stage}`, - }); - - this.api.addGatewayResponse(`${config.aws.id}-${config.env.stage}-5xx`, { - type: ResponseType.DEFAULT_5XX, - statusCode: "500", - templates: { - "application/json": JSON.stringify({ - timestamp: "$context.requestTime", - requestId: "$context.requestId", - statusCode: 500, - error: "Internal Server Error", - message: "An unknown error has occurred. Please try again.", - }), - }, - }); - - this.api.addGatewayResponse(`${config.aws.id}-${config.env.stage}-404`, { - type: ResponseType.MISSING_AUTHENTICATION_TOKEN, - statusCode: "404", - templates: { - "application/json": JSON.stringify({ - timestamp: "$context.requestTime", - requestId: "$context.requestId", - statusCode: 404, - error: "Not Found", - message: "The requested resource could not be found.", - }), - }, - }); - - this.api.root.addMethod( - "OPTIONS", - (this.optionsIntegration = new LambdaIntegration( - new lambda.Function(this, `${config.aws.id}-${config.env.stage}-options-handler`, { - code: Code.fromInline( - 'exports.h=async _=>({body:"",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, - architecture: Architecture.ARM_64, - }), - )), - ); - - new ARecord(this, `${config.aws.id}-${config.env.stage}-a-record`, { - zone: HostedZone.fromHostedZoneAttributes(this, "peterportal-hosted-zone", { - zoneName: config.aws.zoneName, - hostedZoneId: process.env.HOSTED_ZONE_ID ?? "", - }), - recordName, - target: RecordTarget.fromAlias(new ApiGateway(this.api)), - }); - } - - /** - * Adds an endpoint to the API. - */ - async addRoute(handlerConfig: HandlerConfig) { - let resource = this.api.root; - - handlerConfig.route.split("/").forEach((route) => { - resource = resource.getResource(route) ?? resource.addResource(route); - }); - - const internalHandlers: Record = await import( - `${handlerConfig.directory}/dist/index.js` - ); - - Object.keys(internalHandlers) - .filter(isHttpMethod) - .forEach((httpMethod) => { - const route = handlerConfig.route.replace(/\//g, "-"); - const functionName = `${this.config.aws.id}-${this.config.env.stage}-${route}-${httpMethod}`; - const handler = new lambda.Function(this, `${functionName}-handler`, { - functionName, - runtime: Runtime.NODEJS_18_X, - code: Code.fromAsset(handlerConfig.directory, { - exclude: handlerConfig.exclude ?? ["node_modules"], - }), - handler: `${this.config.esbuild.outdir}/${this.config.runtime.nodeRuntimeFile.replace( - "js", - httpMethod, - )}`, - architecture: Architecture.ARM_64, - environment: { ...handlerConfig.env, ...this.config.env, STAGE: this.config.env.stage }, - timeout: Duration.seconds(15), - memorySize: 512, - role: - handlerConfig.rolePropsMapping && handlerConfig.rolePropsMapping[route] - ? new Role(this, `${functionName}-role`, handlerConfig.rolePropsMapping[route]) - : undefined, - }); - - const lambdaIntegration = new LambdaIntegration(handler); - resource.addMethod(httpMethod, lambdaIntegration); - const idResource = resource.getResource("{id}") ?? resource.addResource("{id}"); - idResource.addMethod(httpMethod, lambdaIntegration); - if (httpMethod === "GET") { - resource.addMethod("HEAD", lambdaIntegration); - idResource.addMethod("HEAD", lambdaIntegration); - } - - const warmingTarget = new LambdaFunction(handler, { - event: RuleTargetInput.fromObject({ body: warmerRequestBody }), - }); - const ruleName = `${functionName}-warming-rule`; - const warmingRule = new Rule(this, ruleName, { - schedule: Schedule.rate(Duration.minutes(5)), - }); - warmingRule.addTarget(warmingTarget); - }); - resource.addMethod("OPTIONS", this.optionsIntegration); - (resource.getResource("{id}") ?? resource.addResource("{id}")).addMethod( - "OPTIONS", - this.optionsIntegration, - ); - } -} diff --git a/packages/ant-stack/src/cli/commands/build.ts b/packages/ant-stack/src/cli/commands/build.ts deleted file mode 100644 index 75134efb..00000000 --- a/packages/ant-stack/src/cli/commands/build.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { copyFileSync, writeFileSync } from "node:fs"; -import { resolve } from "node:path"; - -import { build } from "esbuild"; - -import { type AntConfig, getConfig } from "../../config"; -import { createNodeHandler } from "../../lambda-core"; - -/** - * Compile for an AWS Lambda runtime - */ -async function compileRuntime(config: AntConfig, functionName: string, outputFile: string) { - const internalHandlers = await import( - resolve(config.esbuild.outdir ?? ".", config.runtime.entryFile) - ); - - copyFileSync( - resolve(__dirname, config.runtime.lambdaCoreFile), - resolve(config.esbuild.outdir ?? ".", config.runtime.lambdaCoreFile), - ); - - const exports = Object.keys(internalHandlers) - .map( - (httpMethod) => - `export const ${httpMethod} = ${functionName}(${config.runtime.entryHandlersName}.${httpMethod})`, - ) - .join("\n"); - - const script = `\ -import * as ${config.runtime.entryHandlersName} from './${config.runtime.entryFile}' -import { ${functionName} } from './${config.runtime.lambdaCoreFile}' -${exports} -`; - - writeFileSync(resolve(config.esbuild.outdir ?? ".", outputFile), script); -} - -/** - * Builds an {@link InternalHandler}. - * TODO: add the ability to specify options. - */ -export const buildInternalHandler = async () => { - const config = await getConfig(); - - const buildOutput = await build(config.esbuild); - - if (config.esbuild.logLevel === "info") { - console.log(buildOutput); - } - - await compileRuntime(config, createNodeHandler.name, config.runtime.nodeRuntimeFile); -}; diff --git a/packages/ant-stack/src/cli/commands/create.ts b/packages/ant-stack/src/cli/commands/create.ts deleted file mode 100644 index 741b8764..00000000 --- a/packages/ant-stack/src/cli/commands/create.ts +++ /dev/null @@ -1,107 +0,0 @@ -import fs from "node:fs"; -import path from "node:path"; - -import chalk from "chalk"; -import { consola } from "consola"; - -import { getConfig } from "../../config"; -import { getClosestProjectDirectory } from "../../utils"; - -const createHandlerTemplate = (httpMethod: string) => `\ -export const ${httpMethod}: InternalHandler = async (request) => { - return createOKResult({}, zeroUUID); -} -`; - -/** - * {@link __dirname} is compiled by ESBuild. - */ -const projectDirectory = getClosestProjectDirectory(__dirname); - -const templateDirectory = path.join(projectDirectory, "src", "templates"); - -export interface PackageJsonProps { - directory: string; -} - -function generatePackageJson(props: PackageJsonProps): string { - const apiRoute = props.directory.slice(1).replace(/\//g, "-"); - - const rawTemplate = fs.readFileSync(path.join(templateDirectory, "package.json"), "utf8"); - - const parsedTemplate = rawTemplate.replace("$name", `api-${apiRoute}`); - - return parsedTemplate; -} - -export interface EntryFileProps { - directory: string; - methods: string[]; -} - -function generateEntryFile(props: EntryFileProps): string { - const imports = - 'import { createErrorResult, createOKResult, type InternalHandler, zeroUUID } from "ant-stack";\n'; - - const exports = props.methods.map(createHandlerTemplate).join("\n"); - - return `${imports}\n${exports}`; -} - -export async function interactiveCreate() { - const config = await getConfig(); - - consola.info(chalk("Creating a new endpoint.")); - - let endpoint = ""; - - while (!endpoint) { - endpoint = await consola.prompt("What is the path of the endpoint?", { type: "text" }); - - if (!(endpoint.match(/\/[0-9A-Za-z]+/) && !endpoint.endsWith("/"))) { - consola.error( - chalk.red( - "Malformed path provided. A well-formed path must consist entirely of path parts (one slash followed by at least one alphanumeric character), and must not end with a slash (e.g. /v1/rest/test).", - ), - ); - endpoint = ""; - } - } - - const newProjectDirectory = path.join(config.directory, endpoint); - - if (fs.existsSync(newProjectDirectory)) { - consola.warn(`A route already exists at ${endpoint}.`); - - const create = await consola.prompt( - "Would you like to create a new route anyway? This will overwrite the existing route!", - { type: "confirm" }, - ); - - if (!create) { - consola.error(chalk.red("Aborting.")); - return; - } - } - - const methods: string[] = await consola.prompt("What HTTP methods does it use?", { - type: "multiselect", - options: ["GET", "POST", "PUT", "DELETE"], - }); - - consola.info(`Creating an endpoint at ${endpoint} that supports ${methods.join(", ")}`); - - fs.mkdirSync(path.join(newProjectDirectory, "src"), { recursive: true }); - - const packageJson = generatePackageJson({ directory: endpoint }); - const entryFile = generateEntryFile({ directory: endpoint, methods }); - - fs.writeFileSync(path.join(newProjectDirectory, "package.json"), packageJson); - fs.writeFileSync(path.join(newProjectDirectory, "src", "index.ts"), entryFile); - - consola.info( - `Endpoint created! Don't forget to run ${chalk.bold( - `${config.packageManager} install`, - )} to integrate the new route.`, - ); -} diff --git a/packages/ant-stack/src/cli/commands/dev.ts b/packages/ant-stack/src/cli/commands/dev.ts deleted file mode 100644 index 112b4654..00000000 --- a/packages/ant-stack/src/cli/commands/dev.ts +++ /dev/null @@ -1,227 +0,0 @@ -import { basename, join, relative, resolve } from "node:path"; - -import bodyParser from "body-parser"; -import chokidar from "chokidar"; -import { consola } from "consola"; -import cors from "cors"; -import { build, type BuildOptions } from "esbuild"; -import express from "express"; -import { RequestHandler, Router } from "express"; - -import { getConfig } from "../../config"; -import { createExpressHandler, createErrorResult, logger, zeroUUID } from "../../lambda-core"; -import { findAllProjects, getClosestProjectDirectory, searchForWorkspaceRoot } from "../../utils"; - -/** - * Translates the HTTP verbs exported by the lambda-core into Express methods. - */ -const MethodsToExpress = { - DELETE: "delete", - GET: "get", - HEAD: "head", - PATCH: "patch", - POST: "post", - PUT: "put", - OPTIONS: "options", - ANY: "use", -} as const; - -/** - * TODO: move to some location for "express-adapter" related stuff? - */ -function isMethod(method: string): method is keyof typeof MethodsToExpress { - return method in MethodsToExpress; -} - -/** - * TODO: move to utils. - */ -function isStringArray(value: Array): value is string[] { - return value.every((v) => typeof v === "string"); -} - -const notFoundHandler: RequestHandler = (req, res) => { - logger.info( - `${req.method} ${req.path} ${JSON.stringify(req.method === "GET" ? req.query : req.body)}`, - ); - const { statusCode, body, headers } = createErrorResult( - 404, - "The requested resource could not be found.", - zeroUUID, - ); - res.status(statusCode).set(headers).json(JSON.parse(body)); -}; - -/** - * Start a dev server. - */ -export async function startDevServer() { - const config = await getConfig(); - - const cwd = process.cwd(); - - const workspaceRoot = searchForWorkspaceRoot(cwd); - - if (cwd === workspaceRoot || basename(cwd) === "ant-stack") { - consola.info( - `🎏 Starting root dev server. All endpoints from ${config.directory} will be served.`, - ); - config.directory = join(workspaceRoot, config.directory); - } else { - const endpoint = relative(`${workspaceRoot}/${config.directory}`, cwd); - consola.info( - `🎏 Starting local dev server. Only the current endpoint, ${endpoint} will be served at the "/" route.`, - ); - config.directory = resolve(process.cwd()); - } - - const endpoints = findAllProjects(config.directory); - - //--------------------------------------------------------------------------------- - // Build. - //--------------------------------------------------------------------------------- - - /** - * Cache the build configs for each endpoint. - */ - const endpointBuildConfigs = endpoints.reduce( - (configs, endpoint) => { - /** - * {@link BuildOptions.entryPoints} can be way too many different things !! - */ - const entryPoints = Array.isArray(config.esbuild.entryPoints) - ? isStringArray(config.esbuild.entryPoints) - ? config.esbuild.entryPoints.map((entry) => `${endpoint}/${entry}`) - : config.esbuild.entryPoints.map((entry) => ({ - in: `${endpoint}/${entry.in}`, - out: `${endpoint}/${entry.out}`, - })) - : typeof config.esbuild.entryPoints === "object" - ? Object.entries(config.esbuild.entryPoints).map(([key, value]) => ({ - in: `${endpoint}/${key}`, - out: `${endpoint}/${value}`, - })) - : config.esbuild.entryPoints; - - const outdir = resolve(`${endpoint}/${config.esbuild.outdir}`); - - configs[endpoint] = { ...config.esbuild, entryPoints, outdir }; - - return configs; - }, - {} as Record, - ); - - /** - * Build all endpoints. - */ - await Promise.all( - endpoints.map(async (endpoint) => { - consola.info(`🔨 Building ${endpoint} to ${endpointBuildConfigs[endpoint].outdir}`); - await build(endpointBuildConfigs[endpoint]); - consola.info(`✅ Done building ${endpoint} to ${endpointBuildConfigs[endpoint].outdir}`); - }), - ); - - //--------------------------------------------------------------------------------- - // Express development server. - //--------------------------------------------------------------------------------- - - const app = express(); - - app.use(cors(), bodyParser.json()); - - /** - * Mutable global router can be hot-swapped when routes change. - * app.use ( global router .use (endpoint router ) ) - * To update the routes, re-assign the global router, and load all endpoint routes into the new router. - */ - let router = Router(); - - app.use((req, res, next) => router(req, res, next)); - - /** - * Express will assign middleware based on endpoints. - */ - const endpointMiddleware: Record = {}; - - /** - * Replace the global router with a fresh one and reload all endpoints. - */ - const refreshRouter = () => { - router = Router(); - - endpoints.forEach((endpoint) => { - const api = `/${relative(config.directory, endpoint)}`; - - consola.info(`🔄 Loading ${api} from ${endpointBuildConfigs[endpoint].outdir}`); - - router.use(api, (req, res, next) => endpointMiddleware[endpoint](req, res, next)); - }); - - router.all("*", notFoundHandler); - }; - - /** - * Load a specific endpoint's middleware. - */ - const loadEndpoint = async (endpoint: string) => { - consola.info(`🔧 Setting up router for ${endpoint}`); - - endpointMiddleware[endpoint] = Router(); - - const file = resolve(endpoint, `${config.esbuild.outdir}/index.js`); - - const internalHandlers = await import(`${file}?update=${Date.now()}`); - - const handlerFunctions = internalHandlers.default ?? internalHandlers; - - const handlerMethods = Object.keys(handlerFunctions); - - handlerMethods.filter(isMethod).forEach((key) => { - endpointMiddleware[endpoint][MethodsToExpress[key]]( - "/", - createExpressHandler(handlerFunctions[key]), - ); - }); - - handlerMethods.filter(isMethod).forEach((key) => { - endpointMiddleware[endpoint][MethodsToExpress[key]]( - "/:id", - createExpressHandler(handlerFunctions[key]), - ); - }); - }; - - /** - * Prepare the development server by loading all the endpoints and refreshing the routes. - */ - await Promise.all(endpoints.map(loadEndpoint)).then(refreshRouter); - - const port = process.env.API_PORT ?? config.port; - - app.listen(port, () => { - consola.info(`🎉 Express server listening at http://localhost:${port}`); - }); - - //--------------------------------------------------------------------------------- - // Watch file changes. - //--------------------------------------------------------------------------------- - - const watcher = chokidar.watch(endpoints, { - ignored: [ - /(^|[/\\])\../, // dotfiles - /node_modules/, // node_modules - `**/${config.esbuild.outdir ?? "dist"}/**`, // build output directory - ], - }); - - watcher.on("change", async (path) => { - const endpoint = getClosestProjectDirectory(path); - - consola.success("✨ endpoint changed: ", endpoint); - - await build(endpointBuildConfigs[endpoint]); - await loadEndpoint(endpoint).then(refreshRouter); - }); -} diff --git a/packages/ant-stack/src/cli/index.ts b/packages/ant-stack/src/cli/index.ts deleted file mode 100644 index a6988bc2..00000000 --- a/packages/ant-stack/src/cli/index.ts +++ /dev/null @@ -1,43 +0,0 @@ -import chalk from "chalk"; -import { cli, command } from "cleye"; -import { consola } from "consola"; - -import { buildInternalHandler } from "./commands/build"; -import { interactiveCreate } from "./commands/create"; -import { startDevServer } from "./commands/dev"; - -async function start() { - consola.log(chalk("🐜 ant-stack CLI")); - - const argv = cli({ - name: "ant-stack", - version: "0.1.0", - commands: [ - command({ - name: "build", - }), - - command({ - name: "create", - }), - - command({ - name: "dev", - }), - ], - }); - - switch (argv.command) { - case "build": { - return await buildInternalHandler(); - } - case "create": { - return await interactiveCreate(); - } - case "dev": { - return await startDevServer(); - } - } -} - -start(); diff --git a/packages/ant-stack/src/config.ts b/packages/ant-stack/src/config.ts deleted file mode 100644 index 761f8d63..00000000 --- a/packages/ant-stack/src/config.ts +++ /dev/null @@ -1,118 +0,0 @@ -import type { AppProps, StackProps } from "aws-cdk-lib"; -import { RoleProps } from "aws-cdk-lib/aws-iam"; -import type { BuildOptions } from "esbuild"; -import { loadConfig } from "unconfig"; - -/** - * AntStack's AWS configuration. - */ -interface AntAWS { - id: string; - - zoneName: string; - - stage?: string; - - appProps?: AppProps; - - stackProps?: StackProps; - - routeRolePropsMapping?: Record; -} - -/** - * Options that control dynamically generated files for different runtimes. - */ -interface AntRuntime { - /** - * The name of the built file with all the handlers for the route. - * @example dist/index.js - */ - entryFile: string; - - /** - * What to name the imported handles from the built entry file. - * - * @example entryHandlersName = InternalHandlers - * import * as InternalHandlers from './' - */ - entryHandlersName: string; - - /** - * Name of lambda-core file. Contains all the necessary runtime code/helpers. - * @example lambdaCoreFile = 'lambda-core.js' - * import { createNodeHandler } from './lambda-core.js' - */ - lambdaCoreFile: string; - - /** - * Name of dynamically generated script for AWS Lambda's NodeJS runtime. - * @example 'lambda-node-runtime.js' - */ - nodeRuntimeFile: string; -} - -/** - * AntStack's development server configuration. - */ -export interface AntConfig { - /** - * The package manager used by the project. - */ - packageManager: "npm" | "yarn" | "pnpm"; - - /** - * Directory to recursively find API routes. - */ - directory: string; - - /** - * Port to start the Express development server on. - */ - port: number | string; - - /** - * Esbuild options. - */ - esbuild: BuildOptions; - - /** - * Options for dynamically generating the different AWS Lambda runtime scripts. - */ - runtime: AntRuntime; - - /** - * AWS configuration. - */ - aws: AntAWS; - - /** - * Environment variables. - */ - env: Record; -} - -/** - * Stub for the root AntStack config. Useful for customizing route build behavior. - */ -export type AntConfigStub = Partial; - -/** - * Helper function to create configuration with type information in the input. - * FIXME: this is very slow when used with {@link loadConfig} ! - */ -export const defineConfig = (config: AntConfig) => config; - -export async function getConfig() { - const loadedConfig = await loadConfig>({ - sources: [ - { - files: ["ant.config"], - extensions: ["ts", "js"], - }, - ], - merge: true, - }); - - return loadedConfig.config; -} diff --git a/packages/ant-stack/src/lambda-core/index.ts b/packages/ant-stack/src/lambda-core/index.ts deleted file mode 100644 index 0b72ea31..00000000 --- a/packages/ant-stack/src/lambda-core/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export * from "./constants"; -export * from "./internal/handler"; -export * from "./internal/request"; -export * from "./internal/response"; -export * from "./logger"; diff --git a/packages/ant-stack/src/lambda-core/internal/handler.ts b/packages/ant-stack/src/lambda-core/internal/handler.ts deleted file mode 100644 index 84a249e1..00000000 --- a/packages/ant-stack/src/lambda-core/internal/handler.ts +++ /dev/null @@ -1,60 +0,0 @@ -import type { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from "aws-lambda"; -import type { RequestHandler } from "express"; - -import { decompress } from "../../utils"; -import { logger } from "../logger"; - -import { type InternalRequest, transformExpressRequest, transformNodeRequest } from "./request"; - -/** - * A runtime-agnostic handler function. - * Can be transformed into runtime specific handlers with the provided helper functions. - */ -export type InternalHandler = (request: InternalRequest) => Promise; - -/** - * Create an Express handler for a route from an {@link InternalHandler}. - * @remarks Used with the local Express development server. - */ -export const createExpressHandler = - (handler: InternalHandler): RequestHandler => - async (req, res) => { - const request = transformExpressRequest(req); - - logger.info(`Path params: ${JSON.stringify(request.params)}`); - logger.info(`Query: ${JSON.stringify(request.query)}`); - logger.info(`Body: ${JSON.stringify(request.body)}`); - logger.info(`Referer: ${request.headers?.referer}`); - - const result = await handler(request); - - const body = result.isBase64Encoded - ? decompress(result.body, result.headers?.["Content-Encoding"] as string) - : result.body; - - delete result.headers?.["Content-Encoding"]; - - res.status(result.statusCode); - res.set(result.headers); - - try { - res.send(JSON.parse(body)); - } catch { - res.send(body); - } - }; - -/** - * Create an AWS Lambda node-runtime handler for a route from an {@link InternalHandler}. - */ -export const createNodeHandler = - (handler: InternalHandler) => async (event: APIGatewayProxyEvent, context: Context) => { - const request = transformNodeRequest(event, context); - - logger.info(`Path params: ${JSON.stringify(request.params)}`); - logger.info(`Query: ${JSON.stringify(request.query)}`); - logger.info(`Body: ${JSON.stringify(request.body)}`); - logger.info(`Referer: ${request.headers?.referer}`); - - return handler(request); - }; diff --git a/packages/ant-stack/src/lambda-core/internal/request.ts b/packages/ant-stack/src/lambda-core/internal/request.ts deleted file mode 100644 index 7bae8be9..00000000 --- a/packages/ant-stack/src/lambda-core/internal/request.ts +++ /dev/null @@ -1,142 +0,0 @@ -import type { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from "aws-lambda"; -import type { Request as ExpressRequest } from "express"; - -import { zeroUUID } from "../constants"; - -/** - * The body of a warming request to an AWS Lambda function. - */ -export const warmerRequestBody = JSON.stringify({ isWarmer: true }); - -/** - * Basic request that will be processed by runtime-agnostic handler functions. - * Additional properties may be available to different runtimes. - */ -export interface InternalRequest { - /** - * The original request object received. This depends on the runtime. - */ - request: T; - - /** - * Request body. - */ - body: unknown; - - /** - * Request headers represented as a `Record`. - */ - headers: Record; - - /** - * Request HTTP method. - */ - method: string; - - /** - * Request path parameter(s). - */ - params: Record | null; - - /** - * The absolute path of the request. - */ - path: string; - - /** - * The parsed query string passed in the request. - */ - query: Record; - - /** - * The AWS Lambda request ID associated with the request. - */ - requestId: string; - - /** - * Whether the request is a warmer request. - */ - isWarmerRequest: boolean; -} - -/** - * Internal requests from a local Express development server only have the basic properties. - */ -export type InternalExpressRequest = InternalRequest; - -/** - * Internal requests from a live AWS Lambda function also include its original context. - */ -export type InternalNodeRequest = InternalRequest & { context: Context }; - -/** - * Transform an {@link ExpressRequest} into an {@link InternalExpressRequest}. - */ -export function transformExpressRequest(req: ExpressRequest) { - const internalExpressRequest: InternalExpressRequest = { - request: req, - body: req.body, - headers: normalizeRecord(req.headers), - method: req.method, - params: req.params, - path: req.path, - query: normalizeRecord(req.query), - requestId: zeroUUID, - isWarmerRequest: false, - }; - - return internalExpressRequest; -} - -/** - * Transform AWS Lambda event and context into an {@link InternalNodeRequest} from AWS Lambda's Node runtime. - */ -export function transformNodeRequest(event: APIGatewayProxyEvent, context: Context) { - const internalLambdaRequest: InternalNodeRequest = { - request: event, - context, - body: event.body - ? event.isBase64Encoded - ? JSON.parse(Buffer.from(event.body, "base64").toString()) - : JSON.parse(event.body) - : null, - headers: Object.fromEntries( - Object.entries(normalizeRecord(event.headers)).map(([k, v]) => [ - k.toLowerCase(), - v.toLowerCase(), - ]), - ), - method: event.httpMethod, - params: normalizeRecord(event.pathParameters ?? {}), - path: event.path, - query: normalizeRecord(event.multiValueQueryStringParameters ?? {}), - requestId: context.awsRequestId, - isWarmerRequest: event.body === warmerRequestBody, - }; - - return internalLambdaRequest; -} - -/** - * Why are there so many ways to create a stupid looking object! - */ -type StupidRecord = ExpressRequest["query"] | APIGatewayProxyResult["headers"]; - -/** - * Type guard that asserts the value of an object entry is not null. - */ -function entryValueNotNull(v: [string, T]): v is [string, NonNullable] { - return v != null; -} - -/** - * Given some dumb looking object, return a nicer looking one. - * FIXME: this seems computationally expensive to be doing on every request. Maybe we should lazily do it? - */ -export function normalizeRecord(headers: StupidRecord = {}): Record { - const headerEntries = Object.entries(headers) - .filter(entryValueNotNull) - .map(([k, v]) => [k, Array.isArray(v) ? (v.length === 1 ? v[0] : v) : v]); - - return Object.fromEntries(headerEntries); -} diff --git a/packages/ant-stack/src/templates/index.ts b/packages/ant-stack/src/templates/index.ts deleted file mode 100644 index b62657c4..00000000 --- a/packages/ant-stack/src/templates/index.ts +++ /dev/null @@ -1,21 +0,0 @@ -/** - * This file is not actually used, but rather an example of a route's entry point. - */ - -import { createOKResult, type InternalHandler, zeroUUID } from "ant-stack"; - -export const GET: InternalHandler = async (request) => { - return createOKResult(request.body, request.headers, zeroUUID); -}; - -export const POST: InternalHandler = async (request) => { - return createOKResult(request.body, request.headers, zeroUUID); -}; - -export const PUT: InternalHandler = async (request) => { - return createOKResult(request.body, request.headers, zeroUUID); -}; - -export const DELETE: InternalHandler = async (request) => { - return createOKResult(request.body, request.headers, zeroUUID); -}; diff --git a/packages/ant-stack/src/templates/package.json b/packages/ant-stack/src/templates/package.json deleted file mode 100644 index d95490b6..00000000 --- a/packages/ant-stack/src/templates/package.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "name": "$name", - "version": "0.0.0", - "private": true, - "type": "module", - "scripts": { - "build": "ant-stack build" - }, - "dependencies": { - "ant-stack": "workspace:*" - } -} diff --git a/packages/ant-stack/src/utils/compress.ts b/packages/ant-stack/src/utils/compress.ts deleted file mode 100644 index b3311a47..00000000 --- a/packages/ant-stack/src/utils/compress.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { deflateSync, gunzipSync, gzipSync, inflateSync } from "node:zlib"; - -/** - * The payload size above which we want to start compressing the response. - * Default: 128 KiB - */ -const MIN_COMPRESSION_SIZE = 128 * 1024; - -/** - * Mapping of compression algorithms to their function calls. - */ -const compressionAlgorithms: Record Buffer> = { - gzip: gzipSync, - deflate: deflateSync, -}; - -/** - * Mapping of decompression algorithms to their function calls. - */ -const decompressionAlgorithms: Record Buffer> = { - gzip: gunzipSync, - deflate: inflateSync, -}; - -export const compress = ( - body: string, - acceptEncoding?: string, -): { - body: string; - method?: string; -} => { - if (body.length > MIN_COMPRESSION_SIZE) { - if (acceptEncoding !== undefined) { - if (acceptEncoding !== "") { - // If accept-encoding is present and not empty, - // prioritize gzip over deflate. - // Unfortunately API Gateway does not currently support Brotli :( - for (const [name, func] of Object.entries(compressionAlgorithms)) { - if (acceptEncoding.includes(name)) { - return { - body: func(body).toString("base64"), - method: name, - }; - } - } - } - } else { - // Otherwise, we default to using gzip if - // the body size is greater than the threshold. - return { - body: gzipSync(body).toString("base64"), - method: "gzip", - }; - } - } - return { body }; -}; - -export const decompress = (body: string, contentEncoding: string): string => - decompressionAlgorithms[contentEncoding](Buffer.from(body, "base64")).toString(); diff --git a/packages/ant-stack/src/utils/index.ts b/packages/ant-stack/src/utils/index.ts deleted file mode 100644 index 10f64327..00000000 --- a/packages/ant-stack/src/utils/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from "./compress"; -export * from "./schema-tools"; -export * from "./search-root"; -export * from "./search-projects"; diff --git a/packages/ant-stack/src/utils/search-projects.ts b/packages/ant-stack/src/utils/search-projects.ts deleted file mode 100644 index 5466c93f..00000000 --- a/packages/ant-stack/src/utils/search-projects.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { existsSync, readdirSync } from "node:fs"; -import { join } from "node:path"; - -/** - * Recursively find all paths to projects starting from a given root directory. - */ -export const findAllProjects = (root = "."): string[] => [...new Set(findSubProjects(root))]; - -/** - * Recursively find all paths to projects starting from a given root directory. - */ -export function findSubProjects(root = ".", directory = "", paths: string[] = []): string[] { - if (existsSync(`${root}/${directory}/package.json`)) { - paths.push(join(root, directory)); - return paths; - } - - const subRoutes = readdirSync(`${root}/${directory}`, { withFileTypes: true }) - .filter((dirent) => dirent.isDirectory()) - .map((dirent) => dirent.name); - - return subRoutes.flatMap((subRoute) => findSubProjects(root, `${directory}/${subRoute}`, paths)); -} diff --git a/packages/ant-stack/src/utils/search-root.ts b/packages/ant-stack/src/utils/search-root.ts deleted file mode 100644 index 699c9fcc..00000000 --- a/packages/ant-stack/src/utils/search-root.ts +++ /dev/null @@ -1,97 +0,0 @@ -import fs from "node:fs"; -import { dirname, join } from "node:path"; - -// https://github.com/vitejs/vite/issues/2820#issuecomment-812495079 -const ROOT_FILES = [ - // '.git', - - // https://pnpm.io/workspaces/ - "pnpm-workspace.yaml", - - // https://rushjs.io/pages/advanced/config_files/ - // 'rush.json', - - // https://nx.dev/latest/react/getting-started/nx-setup - // 'workspace.json', - // 'nx.json', - - // https://github.com/lerna/lerna#lernajson - "lerna.json", -]; - -export function isFileReadable(filename: string): boolean { - try { - // The "throwIfNoEntry" is a performance optimization for cases where the file does not exist - if (!fs.statSync(filename, { throwIfNoEntry: false })) { - return false; - } - - // Check if current process has read permission to the file - fs.accessSync(filename, fs.constants.R_OK); - - return true; - } catch { - return false; - } -} - -// npm: https://docs.npmjs.com/cli/v7/using-npm/workspaces#installing-workspaces -// yarn: https://classic.yarnpkg.com/en/docs/workspaces/#toc-how-to-use-it -export function hasWorkspacePackageJSON(root: string): boolean { - const currentDirectoryPackageJson = join(root, "package.json"); - - if (!isFileReadable(currentDirectoryPackageJson)) { - return false; - } - - const content = JSON.parse(fs.readFileSync(currentDirectoryPackageJson, "utf-8")) || {}; - return !!content.workspaces; -} - -export function hasWorkspaceRootFile(root: string): boolean { - return ROOT_FILES.some((file) => fs.existsSync(join(root, file))); -} - -export function hasPackageJSON(root: string) { - const currentDirectoryPackageJson = join(root, "package.json"); - return fs.existsSync(currentDirectoryPackageJson); -} - -/** - * Search up for the nearest `package.json` - */ -export function getClosestProjectDirectory(current = process.cwd(), root = current): string { - if (hasPackageJSON(current)) { - return current; - } - - const dir = dirname(current); - - // reach the fs root - if (!dir || dir === current) { - return root; - } - - return getClosestProjectDirectory(dir, root); -} - -/** - * Search up for the nearest workspace root - */ -export function searchForWorkspaceRoot( - current: string, - root = getClosestProjectDirectory(current), -): string { - if (hasWorkspaceRootFile(current) || hasWorkspacePackageJSON(current)) { - return current; - } - - const dir = dirname(current); - - // reached the fs root - if (!dir || dir === current) { - return root; - } - - return searchForWorkspaceRoot(dir, root); -} diff --git a/packages/ant-stack/tsconfig.json b/packages/ant-stack/tsconfig.json deleted file mode 100644 index 039e0b4d..00000000 --- a/packages/ant-stack/tsconfig.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["src"] -} diff --git a/packages/ant-stack/tsup.config.ts b/packages/ant-stack/tsup.config.ts deleted file mode 100644 index a77a2aa3..00000000 --- a/packages/ant-stack/tsup.config.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { defineConfig } from "tsup"; - -/** - * @see https://github.com/evanw/esbuild/issues/1921#issuecomment-1491470829 - */ -const js = `\ -import topLevelPath from 'path'; -import topLevelUrl from 'url'; -import topLevelModule from 'module'; -const require = topLevelModule.createRequire(import.meta.url); -const __filename = topLevelUrl.fileURLToPath(import.meta.url); -const __dirname = topLevelPath.dirname(__filename); -`; - -export default defineConfig({ - entry: { - cli: "src/cli/index.ts", - config: "src/config.ts", - "lambda-core": "src/lambda-core/index.ts", - utils: "src/utils/index.ts", - }, - bundle: true, - format: "esm", - sourcemap: true, - dts: true, - splitting: false, - banner: { js }, - clean: true, - - /** - * Bundle __all__ dependencies into the output files to prepare for Lambda deployment. - */ - noExternal: [/^((?!esbuild).)*$/], -}); diff --git a/packages/peterportal-api-next-types/LICENSE b/packages/types/LICENSE similarity index 100% rename from packages/peterportal-api-next-types/LICENSE rename to packages/types/LICENSE diff --git a/packages/peterportal-api-next-types/README.md b/packages/types/README.md similarity index 100% rename from packages/peterportal-api-next-types/README.md rename to packages/types/README.md diff --git a/packages/peterportal-api-next-types/index.ts b/packages/types/index.ts similarity index 100% rename from packages/peterportal-api-next-types/index.ts rename to packages/types/index.ts diff --git a/packages/peterportal-api-next-types/package.json b/packages/types/package.json similarity index 74% rename from packages/peterportal-api-next-types/package.json rename to packages/types/package.json index 3284d0cb..e290093a 100644 --- a/packages/peterportal-api-next-types/package.json +++ b/packages/types/package.json @@ -1,5 +1,5 @@ { - "name": "peterportal-api-next-types", + "name": "@peterportal-api/types", "version": "1.0.0-rc.3", "license": "MIT", "type": "module", diff --git a/packages/peterportal-api-next-types/types/calendar.ts b/packages/types/types/calendar.ts similarity index 100% rename from packages/peterportal-api-next-types/types/calendar.ts rename to packages/types/types/calendar.ts diff --git a/packages/peterportal-api-next-types/types/constants.ts b/packages/types/types/constants.ts similarity index 100% rename from packages/peterportal-api-next-types/types/constants.ts rename to packages/types/types/constants.ts diff --git a/packages/peterportal-api-next-types/types/courses.ts b/packages/types/types/courses.ts similarity index 100% rename from packages/peterportal-api-next-types/types/courses.ts rename to packages/types/types/courses.ts diff --git a/packages/peterportal-api-next-types/types/grades.ts b/packages/types/types/grades.ts similarity index 100% rename from packages/peterportal-api-next-types/types/grades.ts rename to packages/types/types/grades.ts diff --git a/packages/peterportal-api-next-types/types/instructor.ts b/packages/types/types/instructor.ts similarity index 100% rename from packages/peterportal-api-next-types/types/instructor.ts rename to packages/types/types/instructor.ts diff --git a/packages/peterportal-api-next-types/types/larc.ts b/packages/types/types/larc.ts similarity index 100% rename from packages/peterportal-api-next-types/types/larc.ts rename to packages/types/types/larc.ts diff --git a/packages/peterportal-api-next-types/types/response.ts b/packages/types/types/response.ts similarity index 100% rename from packages/peterportal-api-next-types/types/response.ts rename to packages/types/types/response.ts diff --git a/packages/peterportal-api-next-types/types/websoc.ts b/packages/types/types/websoc.ts similarity index 100% rename from packages/peterportal-api-next-types/types/websoc.ts rename to packages/types/types/websoc.ts diff --git a/packages/peterportal-api-next-types/types/week.ts b/packages/types/types/week.ts similarity index 100% rename from packages/peterportal-api-next-types/types/week.ts rename to packages/types/types/week.ts diff --git a/packages/websoc-fuzzy-search/package.json b/packages/websoc-fuzzy-search/package.json index 985b1cba..52e40c78 100644 --- a/packages/websoc-fuzzy-search/package.json +++ b/packages/websoc-fuzzy-search/package.json @@ -21,6 +21,8 @@ "pako": "2.1.0" }, "devDependencies": { + "@peterportal-api/types": "workspace:^", + "@types/pako": "2.0.1", "@types/pluralize": "0.0.30", "cross-fetch": "4.0.0", "pluralize": "8.0.0", diff --git a/packages/websoc-fuzzy-search/setup.ts b/packages/websoc-fuzzy-search/setup.ts index 2ffa845f..358782dc 100644 --- a/packages/websoc-fuzzy-search/setup.ts +++ b/packages/websoc-fuzzy-search/setup.ts @@ -2,9 +2,9 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"; import { normalize } from "node:path"; import { gzipSync } from "node:zlib"; +import { isErrorResponse } from "@peterportal-api/types"; +import type { Course, Instructor, RawResponse } from "@peterportal-api/types"; import fetch from "cross-fetch"; -import { isErrorResponse } from "peterportal-api-next-types"; -import type { Course, Instructor, RawResponse } from "peterportal-api-next-types"; import pluralize from "pluralize"; // data sources diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 26378f8a..49538d82 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -17,33 +17,36 @@ importers: '@commitlint/types': specifier: 17.4.4 version: 17.4.4 - '@libs/db': - specifier: workspace:* - version: link:libs/db - '@libs/registrar-api': - specifier: workspace:* - version: link:libs/registrar-api - '@libs/websoc-api-next': - specifier: workspace:* - version: link:libs/websoc-api-next + '@tsconfig/node18': + specifier: ^18.2.1 + version: 18.2.1 '@types/lint-staged': specifier: 13.2.0 version: 13.2.0 + '@types/node': + specifier: 18.17.12 + version: 18.17.12 '@typescript-eslint/eslint-plugin': specifier: 6.5.0 version: 6.5.0(@typescript-eslint/parser@6.5.0)(eslint@8.48.0)(typescript@5.2.2) '@typescript-eslint/parser': specifier: 6.5.0 version: 6.5.0(eslint@8.48.0)(typescript@5.2.2) - ant-stack: - specifier: ^0.1.0 - version: link:packages/ant-stack + arktype: + specifier: 1.0.16-alpha + version: 1.0.16-alpha + aws-cdk-lib: + specifier: 2.93.0 + version: 2.93.0(constructs@10.2.69) cz-conventional-changelog: specifier: 3.3.0 version: 3.3.0 devmoji: specifier: 2.3.0 version: 2.3.0 + dotenv: + specifier: 16.3.1 + version: 16.3.1 eslint: specifier: 8.48.0 version: 8.48.0 @@ -74,12 +77,21 @@ importers: turbo: specifier: 1.10.13 version: 1.10.13 + typescript: + specifier: 5.2.2 + version: 5.2.2 + unconfig: + specifier: 0.3.10 + version: 0.3.10 - apps/api/v1/graphql: + apps/api: dependencies: '@apollo/server': specifier: 4.9.2 version: 4.9.2(graphql@16.8.0) + '@aws-sdk/client-lambda': + specifier: 3.398.0 + version: 3.398.0 '@graphql-tools/load-files': specifier: 7.0.0 version: 7.0.0(graphql@16.8.0) @@ -89,160 +101,64 @@ importers: '@graphql-tools/utils': specifier: 10.0.5 version: 10.0.5(graphql@16.8.0) - ant-stack: - specifier: workspace:* - version: link:../../../../packages/ant-stack - cross-fetch: - specifier: 4.0.0 - version: 4.0.0 - graphql: - specifier: 16.8.0 - version: 16.8.0 - peterportal-api-next-types: - specifier: workspace:* - version: link:../../../../packages/peterportal-api-next-types - devDependencies: - '@types/aws-lambda': - specifier: 8.10.119 - version: 8.10.119 - - apps/api/v1/rest/calendar: - dependencies: - '@libs/db': - specifier: workspace:* - version: link:../../../../../libs/db - ant-stack: - specifier: workspace:* - version: link:../../../../../packages/ant-stack - zod: - specifier: 3.22.2 - version: 3.22.2 - devDependencies: - '@libs/build-tools': - specifier: workspace:* - version: link:../../../../../libs/build-tools - peterportal-api-next-types: - specifier: workspace:* - version: link:../../../../../packages/peterportal-api-next-types - - apps/api/v1/rest/courses: - dependencies: - '@libs/db': - specifier: workspace:* - version: link:../../../../../libs/db - ant-stack: - specifier: workspace:* - version: link:../../../../../packages/ant-stack - zod: - specifier: 3.22.2 - version: 3.22.2 - devDependencies: - '@libs/build-tools': - specifier: workspace:* - version: link:../../../../../libs/build-tools - peterportal-api-next-types: - specifier: workspace:* - version: link:../../../../../packages/peterportal-api-next-types - - apps/api/v1/rest/grades: - dependencies: - '@libs/db': - specifier: workspace:* - version: link:../../../../../libs/db - ant-stack: - specifier: workspace:* - version: link:../../../../../packages/ant-stack - zod: - specifier: 3.22.2 - version: 3.22.2 - devDependencies: - '@libs/build-tools': - specifier: workspace:* - version: link:../../../../../libs/build-tools - peterportal-api-next-types: - specifier: workspace:* - version: link:../../../../../packages/peterportal-api-next-types - - apps/api/v1/rest/instructors: - dependencies: '@libs/db': - specifier: workspace:* - version: link:../../../../../libs/db - ant-stack: - specifier: workspace:* - version: link:../../../../../packages/ant-stack - zod: - specifier: 3.22.2 - version: 3.22.2 - devDependencies: - '@libs/build-tools': - specifier: workspace:* - version: link:../../../../../libs/build-tools - peterportal-api-next-types: - specifier: workspace:* - version: link:../../../../../packages/peterportal-api-next-types - - apps/api/v1/rest/larc: - dependencies: - ant-stack: - specifier: workspace:* - version: link:../../../../../packages/ant-stack + specifier: workspace:^ + version: link:../../libs/db + '@libs/lambda': + specifier: workspace:^ + version: link:../../libs/lambda + '@libs/uc-irvine-api': + specifier: workspace:^ + version: link:../../libs/uc-irvine-api + '@libs/websoc-utils': + specifier: workspace:^ + version: link:../../libs/websoc-utils + '@peterportal-api/types': + specifier: workspace:^ + version: link:../../packages/types + '@services/websoc-proxy': + specifier: workspace:^ + version: link:../../services/websoc-proxy + aws-cdk-lib: + specifier: 2.93.0 + version: 2.93.0(constructs@10.2.69) cheerio: specifier: 1.0.0-rc.12 version: 1.0.0-rc.12 cross-fetch: specifier: 4.0.0 version: 4.0.0 - zod: - specifier: 3.22.2 - version: 3.22.2 - - apps/api/v1/rest/websoc: - dependencies: - '@aws-sdk/client-lambda': - specifier: 3.398.0 - version: 3.398.0 - '@libs/db': - specifier: workspace:* - version: link:../../../../../libs/db - '@libs/websoc-utils': - specifier: workspace:* - version: link:../../../../../libs/websoc-utils - ant-stack: - specifier: workspace:* - version: link:../../../../../packages/ant-stack - zod: - specifier: 3.22.2 - version: 3.22.2 - devDependencies: - '@libs/build-tools': - specifier: workspace:* - version: link:../../../../../libs/build-tools - '@services/websoc-proxy': - specifier: workspace:* - version: link:../../../../../services/websoc-proxy - peterportal-api-next-types: - specifier: workspace:* - version: link:../../../../../packages/peterportal-api-next-types - - apps/api/v1/rest/week: - dependencies: - '@libs/db': - specifier: workspace:* - version: link:../../../../../libs/db - ant-stack: - specifier: workspace:* - version: link:../../../../../packages/ant-stack + graphql: + specifier: 16.8.0 + version: 16.8.0 zod: specifier: 3.22.2 version: 3.22.2 devDependencies: - '@libs/build-tools': - specifier: workspace:* - version: link:../../../../../libs/build-tools - peterportal-api-next-types: - specifier: workspace:* - version: link:../../../../../packages/peterportal-api-next-types + '@bronya.js/api-construct': + specifier: 0.11.3 + version: 0.11.3 + '@bronya.js/core': + specifier: 0.11.3 + version: 0.11.3 + '@types/aws-lambda': + specifier: 8.10.119 + version: 8.10.119 + aws-cdk: + specifier: 2.93.0 + version: 2.93.0 + dotenv: + specifier: 16.3.1 + version: 16.3.1 + dotenv-cli: + specifier: 7.3.0 + version: 7.3.0 + esbuild: + specifier: 0.19.2 + version: 0.19.2 + tsx: + specifier: 3.12.7 + version: 3.12.7 apps/docs: dependencies: @@ -299,46 +215,6 @@ importers: specifier: 5.2.2 version: 5.2.2 - apps/docs/cdk: - dependencies: - docs: - specifier: workspace:* - version: link:.. - dotenv: - specifier: 16.3.1 - version: 16.3.1 - devDependencies: - '@types/babel__traverse': - specifier: 7.20.1 - version: 7.20.1 - '@types/node': - specifier: 18.17.12 - version: 18.17.12 - aws-cdk: - specifier: 2.93.0 - version: 2.93.0 - aws-cdk-lib: - specifier: 2.93.0 - version: 2.93.0(constructs@10.2.69) - constructs: - specifier: 10.2.69 - version: 10.2.69 - source-map-support: - specifier: 0.5.21 - version: 0.5.21 - tsx: - specifier: 3.12.7 - version: 3.12.7 - typescript: - specifier: 5.2.2 - version: 5.2.2 - - libs/build-tools: - devDependencies: - esbuild: - specifier: 0.19.2 - version: 0.19.2 - libs/db: dependencies: '@prisma/client': @@ -349,129 +225,44 @@ importers: specifier: 5.2.0 version: 5.2.0 - libs/registrar-api: + libs/lambda: dependencies: - cheerio: - specifier: 1.0.0-rc.12 - version: 1.0.0-rc.12 - cross-fetch: - specifier: 4.0.0 - version: 4.0.0 + winston: + specifier: 3.10.0 + version: 3.10.0 devDependencies: - '@vitest/coverage-istanbul': - specifier: 0.34.3 - version: 0.34.3(vitest@0.34.3) - peterportal-api-next-types: - specifier: '*' - version: link:../../packages/peterportal-api-next-types - tsup: - specifier: 7.2.0 - version: 7.2.0(ts-node@10.9.1)(typescript@5.2.2) - vitest: - specifier: 0.34.3 - version: 0.34.3 + '@peterportal-api/types': + specifier: workspace:^ + version: link:../../packages/types + '@types/aws-lambda': + specifier: 8.10.119 + version: 8.10.119 - libs/websoc-api-next: + libs/uc-irvine-api: dependencies: '@ap0nia/camaro': specifier: 6.2.5 version: 6.2.5 + '@peterportal-api/types': + specifier: workspace:^ + version: link:../../packages/types cheerio: specifier: 1.0.0-rc.12 version: 1.0.0-rc.12 cross-fetch: specifier: 4.0.0 version: 4.0.0 - peterportal-api-next-types: - specifier: '*' - version: link:../../packages/peterportal-api-next-types - devDependencies: - '@vitest/coverage-istanbul': - specifier: 0.34.3 - version: 0.34.3(vitest@0.34.3) - tsup: - specifier: 7.2.0 - version: 7.2.0(ts-node@10.9.1)(typescript@5.2.2) - vitest: - specifier: 0.34.3 - version: 0.34.3 - libs/websoc-utils: {} - - packages/ant-stack: + libs/websoc-utils: dependencies: - arktype: - specifier: 1.0.16-alpha - version: 1.0.16-alpha - aws-cdk: - specifier: 2.93.0 - version: 2.93.0 - aws-cdk-lib: - specifier: 2.93.0 - version: 2.93.0(constructs@10.2.69) - body-parser: - specifier: 1.20.2 - version: 1.20.2 - chalk: - specifier: 5.3.0 - version: 5.3.0 - chokidar: - specifier: 3.5.3 - version: 3.5.3 - cleye: - specifier: 1.3.2 - version: 1.3.2 - consola: - specifier: 3.2.3 - version: 3.2.3 - constructs: - specifier: 10.2.69 - version: 10.2.69 - cors: - specifier: 2.8.5 - version: 2.8.5 - defu: - specifier: 6.1.2 - version: 6.1.2 - esbuild: - specifier: 0.19.2 - version: 0.19.2 - express: - specifier: 4.18.2 - version: 4.18.2 - unconfig: - specifier: 0.3.10 - version: 0.3.10 - winston: - specifier: 3.10.0 - version: 3.10.0 - devDependencies: - '@types/aws-lambda': - specifier: 8.10.119 - version: 8.10.119 - '@types/body-parser': - specifier: 1.19.2 - version: 1.19.2 - '@types/cors': - specifier: 2.8.13 - version: 2.8.13 - '@types/express': - specifier: 4.17.17 - version: 4.17.17 - peterportal-api-next-types: - specifier: '*' - version: link:../peterportal-api-next-types - tsup: - specifier: 7.2.0 - version: 7.2.0(ts-node@10.9.1)(typescript@5.2.2) - tsx: - specifier: 3.12.7 - version: 3.12.7 - typescript: - specifier: 5.2.2 - version: 5.2.2 + '@libs/uc-irvine-api': + specifier: workspace:^ + version: link:../uc-irvine-api + '@peterportal-api/types': + specifier: workspace:^ + version: link:../../packages/types - packages/peterportal-api-next-types: {} + packages/types: {} packages/websoc-fuzzy-search: dependencies: @@ -482,6 +273,12 @@ importers: specifier: 2.1.0 version: 2.1.0 devDependencies: + '@peterportal-api/types': + specifier: workspace:^ + version: link:../types + '@types/pako': + specifier: 2.0.1 + version: 2.0.1 '@types/pluralize': specifier: 0.0.30 version: 0.0.30 @@ -498,58 +295,21 @@ importers: specifier: 3.12.7 version: 3.12.7 - services/cdk: - dependencies: - '@services/registrar-scraper': - specifier: workspace:* - version: link:../../tools/registrar-scraper - '@services/websoc-proxy': - specifier: workspace:* - version: link:../websoc-proxy - '@services/websoc-scraper-v2': - specifier: workspace:* - version: link:../websoc-scraper-v2 - dotenv: - specifier: 16.3.1 - version: 16.3.1 - devDependencies: - '@types/babel__traverse': - specifier: 7.20.1 - version: 7.20.1 - '@types/node': - specifier: 18.17.12 - version: 18.17.12 - aws-cdk: - specifier: 2.93.0 - version: 2.93.0 - aws-cdk-lib: - specifier: 2.93.0 - version: 2.93.0(constructs@10.2.69) - constructs: - specifier: 10.2.69 - version: 10.2.69 - peterportal-api-next-types: - specifier: workspace:* - version: link:../../packages/peterportal-api-next-types - source-map-support: - specifier: 0.5.21 - version: 0.5.21 - tsx: - specifier: 3.12.7 - version: 3.12.7 - typescript: - specifier: 5.2.2 - version: 5.2.2 - services/websoc-proxy: dependencies: - '@libs/websoc-api-next': - specifier: workspace:* - version: link:../../libs/websoc-api-next - ant-stack: - specifier: workspace:* - version: link:../../packages/ant-stack + '@libs/lambda': + specifier: workspace:^ + version: link:../../libs/lambda + '@libs/uc-irvine-api': + specifier: workspace:^ + version: link:../../libs/uc-irvine-api + '@libs/websoc-utils': + specifier: workspace:^ + version: link:../../libs/websoc-utils devDependencies: + '@peterportal-api/types': + specifier: workspace:^ + version: link:../../packages/types '@types/aws-lambda': specifier: 8.10.119 version: 8.10.119 @@ -559,24 +319,18 @@ importers: esbuild: specifier: 0.19.2 version: 0.19.2 - peterportal-api-next-types: - specifier: workspace:* - version: link:../../packages/peterportal-api-next-types services/websoc-scraper-v2: dependencies: '@libs/db': - specifier: workspace:* + specifier: workspace:^ version: link:../../libs/db - '@libs/registrar-api': - specifier: workspace:* - version: link:../../libs/registrar-api - '@libs/websoc-api-next': - specifier: workspace:* - version: link:../../libs/websoc-api-next - peterportal-api-next-types: - specifier: workspace:* - version: link:../../packages/peterportal-api-next-types + '@libs/uc-irvine-api': + specifier: workspace:^ + version: link:../../libs/uc-irvine-api + '@peterportal-api/types': + specifier: workspace:^ + version: link:../../packages/types winston: specifier: 3.10.0 version: 3.10.0 @@ -585,11 +339,39 @@ importers: specifier: 0.19.2 version: 0.19.2 + tools/cdk: + dependencies: + '@aws-sdk/client-cloudformation': + specifier: ^3.408.0 + version: 3.408.0 + '@smithy/util-waiter': + specifier: ^2.0.6 + version: 2.0.6 + aws-cdk-lib: + specifier: 2.93.0 + version: 2.93.0(constructs@10.2.69) + constructs: + specifier: 10.2.69 + version: 10.2.69 + devDependencies: + aws-cdk: + specifier: 2.93.0 + version: 2.93.0 + tsx: + specifier: 3.12.7 + version: 3.12.7 + tools/grades-updater: dependencies: '@libs/db': - specifier: workspace:* + specifier: workspace:^ version: link:../../libs/db + '@libs/lambda': + specifier: workspace:^ + version: link:../../libs/lambda + '@libs/uc-irvine-api': + specifier: workspace:^ + version: link:../../libs/uc-irvine-api csv-parse: specifier: 5.5.0 version: 5.5.0 @@ -603,15 +385,15 @@ importers: specifier: 4.5.0 version: 4.5.0 devDependencies: + '@peterportal-api/types': + specifier: workspace:^ + version: link:../../packages/types '@types/node': specifier: 18.17.12 version: 18.17.12 dotenv-cli: specifier: 7.3.0 version: 7.3.0 - peterportal-api-next-types: - specifier: workspace:* - version: link:../../packages/peterportal-api-next-types tsx: specifier: 3.12.7 version: 3.12.7 @@ -619,7 +401,7 @@ importers: tools/registrar-scraper: dependencies: '@libs/db': - specifier: workspace:* + specifier: workspace:^ version: link:../../libs/db '@types/he': specifier: 1.2.0 @@ -640,12 +422,12 @@ importers: specifier: 3.10.0 version: 3.10.0 devDependencies: + '@peterportal-api/types': + specifier: workspace:^ + version: link:../../packages/types esbuild: specifier: 0.19.2 version: 0.19.2 - peterportal-api-next-types: - specifier: workspace:* - version: link:../../packages/peterportal-api-next-types packages: @@ -774,10 +556,11 @@ packages: dependencies: '@jridgewell/gen-mapping': 0.3.3 '@jridgewell/trace-mapping': 0.3.18 + dev: false /@antfu/utils@0.7.6: resolution: {integrity: sha512-pvFiLP2BeOKA/ZOS6jxx4XhKzdVLHDhGlFEaZ2flWWYf2xOqVniqpk38I04DFRyz+L0ASggl7SkItTc+ZLju4w==} - dev: false + dev: true /@ap0nia/camaro@6.2.5: resolution: {integrity: sha512-w7mMvo4meCeZ9uPo33Q4BmwOveZ9AcLlnjxmO/5pMzQ9foClkF8YXMv44taSI9jaEZ5b5JmD1dgutr3HhBao/w==} @@ -1018,7 +801,7 @@ packages: '@aws-crypto/sha256-js': 3.0.0 '@aws-crypto/supports-web-crypto': 3.0.0 '@aws-crypto/util': 3.0.0 - '@aws-sdk/types': 3.398.0 + '@aws-sdk/types': 3.408.0 '@aws-sdk/util-locate-window': 3.310.0 '@aws-sdk/util-utf8-browser': 3.259.0 tslib: 1.14.1 @@ -1028,7 +811,7 @@ packages: resolution: {integrity: sha512-PnNN7os0+yd1XvXAy23CFOmTbMaDxgxXtTKHybrJ39Y8kGzBATgBFibWJKH6BhytLI/Zyszs87xCOBNyBig6vQ==} dependencies: '@aws-crypto/util': 3.0.0 - '@aws-sdk/types': 3.398.0 + '@aws-sdk/types': 3.408.0 tslib: 1.14.1 dev: false @@ -1041,11 +824,58 @@ packages: /@aws-crypto/util@3.0.0: resolution: {integrity: sha512-2OJlpeJpCR48CC8r+uKVChzs9Iungj9wkZrl8Z041DWEWvyIHILYKCPNzJghKsivj+S3mLo6BVc7mBNzdxA46w==} dependencies: - '@aws-sdk/types': 3.398.0 + '@aws-sdk/types': 3.408.0 '@aws-sdk/util-utf8-browser': 3.259.0 tslib: 1.14.1 dev: false + /@aws-sdk/client-cloudformation@3.408.0: + resolution: {integrity: sha512-5VWNP6JbJMmdqIJOfXMxxUamZQui0hBLR3AX8uPnT7zABl32vdqc57xzPC/X/kCoFofmtI3pjcZqLOXi6TXSHw==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-crypto/sha256-browser': 3.0.0 + '@aws-crypto/sha256-js': 3.0.0 + '@aws-sdk/client-sts': 3.408.0 + '@aws-sdk/credential-provider-node': 3.408.0 + '@aws-sdk/middleware-host-header': 3.408.0 + '@aws-sdk/middleware-logger': 3.408.0 + '@aws-sdk/middleware-recursion-detection': 3.408.0 + '@aws-sdk/middleware-signing': 3.408.0 + '@aws-sdk/middleware-user-agent': 3.408.0 + '@aws-sdk/types': 3.408.0 + '@aws-sdk/util-endpoints': 3.408.0 + '@aws-sdk/util-user-agent-browser': 3.408.0 + '@aws-sdk/util-user-agent-node': 3.408.0 + '@smithy/config-resolver': 2.0.5 + '@smithy/fetch-http-handler': 2.0.5 + '@smithy/hash-node': 2.0.5 + '@smithy/invalid-dependency': 2.0.5 + '@smithy/middleware-content-length': 2.0.5 + '@smithy/middleware-endpoint': 2.0.5 + '@smithy/middleware-retry': 2.0.5 + '@smithy/middleware-serde': 2.0.5 + '@smithy/middleware-stack': 2.0.0 + '@smithy/node-config-provider': 2.0.7 + '@smithy/node-http-handler': 2.0.5 + '@smithy/protocol-http': 2.0.5 + '@smithy/smithy-client': 2.0.5 + '@smithy/types': 2.2.2 + '@smithy/url-parser': 2.0.5 + '@smithy/util-base64': 2.0.0 + '@smithy/util-body-length-browser': 2.0.0 + '@smithy/util-body-length-node': 2.1.0 + '@smithy/util-defaults-mode-browser': 2.0.6 + '@smithy/util-defaults-mode-node': 2.0.7 + '@smithy/util-retry': 2.0.0 + '@smithy/util-utf8': 2.0.0 + '@smithy/util-waiter': 2.0.6 + fast-xml-parser: 4.2.5 + tslib: 2.5.2 + uuid: 8.3.2 + transitivePeerDependencies: + - aws-crt + dev: false + /@aws-sdk/client-lambda@3.398.0: resolution: {integrity: sha512-f++i62vpdh/kUFBx2hWnAJRpb1IOGSqP6+2OP4c7WCpYrrGKleZCRPEZdsDTYnEG1ws987XYfDtal4CVVVL+nw==} engines: {node: '>=14.0.0'} @@ -1136,6 +966,47 @@ packages: - aws-crt dev: false + /@aws-sdk/client-sso@3.408.0: + resolution: {integrity: sha512-g0Y904ghLTg9JLJnmbuvf10Hrzwqn2pko6aCAK10vCI5Y2nQ6BAUXuPonxhZIlp+JHsk0B2FUBqquc+bErUspA==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-crypto/sha256-browser': 3.0.0 + '@aws-crypto/sha256-js': 3.0.0 + '@aws-sdk/middleware-host-header': 3.408.0 + '@aws-sdk/middleware-logger': 3.408.0 + '@aws-sdk/middleware-recursion-detection': 3.408.0 + '@aws-sdk/middleware-user-agent': 3.408.0 + '@aws-sdk/types': 3.408.0 + '@aws-sdk/util-endpoints': 3.408.0 + '@aws-sdk/util-user-agent-browser': 3.408.0 + '@aws-sdk/util-user-agent-node': 3.408.0 + '@smithy/config-resolver': 2.0.5 + '@smithy/fetch-http-handler': 2.0.5 + '@smithy/hash-node': 2.0.5 + '@smithy/invalid-dependency': 2.0.5 + '@smithy/middleware-content-length': 2.0.5 + '@smithy/middleware-endpoint': 2.0.5 + '@smithy/middleware-retry': 2.0.5 + '@smithy/middleware-serde': 2.0.5 + '@smithy/middleware-stack': 2.0.0 + '@smithy/node-config-provider': 2.0.7 + '@smithy/node-http-handler': 2.0.5 + '@smithy/protocol-http': 2.0.5 + '@smithy/smithy-client': 2.0.5 + '@smithy/types': 2.2.2 + '@smithy/url-parser': 2.0.5 + '@smithy/util-base64': 2.0.0 + '@smithy/util-body-length-browser': 2.0.0 + '@smithy/util-body-length-node': 2.1.0 + '@smithy/util-defaults-mode-browser': 2.0.6 + '@smithy/util-defaults-mode-node': 2.0.7 + '@smithy/util-retry': 2.0.0 + '@smithy/util-utf8': 2.0.0 + tslib: 2.5.2 + transitivePeerDependencies: + - aws-crt + dev: false + /@aws-sdk/client-sts@3.398.0: resolution: {integrity: sha512-/3Pa9wLMvBZipKraq3AtbmTfXW6q9kyvhwOno64f1Fz7kFb8ijQFMGoATS70B2pGEZTlxkUqJFWDiisT6Q6dFg==} engines: {node: '>=14.0.0'} @@ -1181,12 +1052,67 @@ packages: - aws-crt dev: false + /@aws-sdk/client-sts@3.408.0: + resolution: {integrity: sha512-PpNmhCuFjVrgGBy00RVh3evBxzFfvUrALDqpBnPYhz489Qzg2I+T90FqdSUedPQPYe+qhq0YJMPKc9leYBEB/w==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-crypto/sha256-browser': 3.0.0 + '@aws-crypto/sha256-js': 3.0.0 + '@aws-sdk/credential-provider-node': 3.408.0 + '@aws-sdk/middleware-host-header': 3.408.0 + '@aws-sdk/middleware-logger': 3.408.0 + '@aws-sdk/middleware-recursion-detection': 3.408.0 + '@aws-sdk/middleware-sdk-sts': 3.408.0 + '@aws-sdk/middleware-signing': 3.408.0 + '@aws-sdk/middleware-user-agent': 3.408.0 + '@aws-sdk/types': 3.408.0 + '@aws-sdk/util-endpoints': 3.408.0 + '@aws-sdk/util-user-agent-browser': 3.408.0 + '@aws-sdk/util-user-agent-node': 3.408.0 + '@smithy/config-resolver': 2.0.5 + '@smithy/fetch-http-handler': 2.0.5 + '@smithy/hash-node': 2.0.5 + '@smithy/invalid-dependency': 2.0.5 + '@smithy/middleware-content-length': 2.0.5 + '@smithy/middleware-endpoint': 2.0.5 + '@smithy/middleware-retry': 2.0.5 + '@smithy/middleware-serde': 2.0.5 + '@smithy/middleware-stack': 2.0.0 + '@smithy/node-config-provider': 2.0.7 + '@smithy/node-http-handler': 2.0.5 + '@smithy/protocol-http': 2.0.5 + '@smithy/smithy-client': 2.0.5 + '@smithy/types': 2.2.2 + '@smithy/url-parser': 2.0.5 + '@smithy/util-base64': 2.0.0 + '@smithy/util-body-length-browser': 2.0.0 + '@smithy/util-body-length-node': 2.1.0 + '@smithy/util-defaults-mode-browser': 2.0.6 + '@smithy/util-defaults-mode-node': 2.0.7 + '@smithy/util-retry': 2.0.0 + '@smithy/util-utf8': 2.0.0 + fast-xml-parser: 4.2.5 + tslib: 2.5.2 + transitivePeerDependencies: + - aws-crt + dev: false + /@aws-sdk/credential-provider-env@3.398.0: resolution: {integrity: sha512-Z8Yj5z7FroAsR6UVML+XUdlpoqEe9Dnle8c2h8/xWwIC2feTfIBhjLhRVxfbpbM1pLgBSNEcZ7U8fwq5l7ESVQ==} engines: {node: '>=14.0.0'} dependencies: '@aws-sdk/types': 3.398.0 - '@smithy/property-provider': 2.0.4 + '@smithy/property-provider': 2.0.6 + '@smithy/types': 2.2.2 + tslib: 2.5.2 + dev: false + + /@aws-sdk/credential-provider-env@3.408.0: + resolution: {integrity: sha512-GCpgHEHxRTzKaMkwDC2gLb3xlD+ZxhKPUJ1DVcO7I9E3eCGJsYVedIi0/2XE+NP+HVoy8LyW2qH8QQWh64JKow==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/types': 3.408.0 + '@smithy/property-provider': 2.0.6 '@smithy/types': 2.2.2 tslib: 2.5.2 dev: false @@ -1200,9 +1126,27 @@ packages: '@aws-sdk/credential-provider-sso': 3.398.0 '@aws-sdk/credential-provider-web-identity': 3.398.0 '@aws-sdk/types': 3.398.0 - '@smithy/credential-provider-imds': 2.0.4 - '@smithy/property-provider': 2.0.4 - '@smithy/shared-ini-file-loader': 2.0.4 + '@smithy/credential-provider-imds': 2.0.7 + '@smithy/property-provider': 2.0.6 + '@smithy/shared-ini-file-loader': 2.0.6 + '@smithy/types': 2.2.2 + tslib: 2.5.2 + transitivePeerDependencies: + - aws-crt + dev: false + + /@aws-sdk/credential-provider-ini@3.408.0: + resolution: {integrity: sha512-vXuayXiwHncd3Xush0jQYrnu2aPPlE+fpdnpEdZGgUJwdbv2vSeYZ73ldH1LzCd179BEDVT0J7nHc7fposo3kg==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/credential-provider-env': 3.408.0 + '@aws-sdk/credential-provider-process': 3.408.0 + '@aws-sdk/credential-provider-sso': 3.408.0 + '@aws-sdk/credential-provider-web-identity': 3.408.0 + '@aws-sdk/types': 3.408.0 + '@smithy/credential-provider-imds': 2.0.7 + '@smithy/property-provider': 2.0.6 + '@smithy/shared-ini-file-loader': 2.0.6 '@smithy/types': 2.2.2 tslib: 2.5.2 transitivePeerDependencies: @@ -1219,9 +1163,28 @@ packages: '@aws-sdk/credential-provider-sso': 3.398.0 '@aws-sdk/credential-provider-web-identity': 3.398.0 '@aws-sdk/types': 3.398.0 - '@smithy/credential-provider-imds': 2.0.4 - '@smithy/property-provider': 2.0.4 - '@smithy/shared-ini-file-loader': 2.0.4 + '@smithy/credential-provider-imds': 2.0.7 + '@smithy/property-provider': 2.0.6 + '@smithy/shared-ini-file-loader': 2.0.6 + '@smithy/types': 2.2.2 + tslib: 2.5.2 + transitivePeerDependencies: + - aws-crt + dev: false + + /@aws-sdk/credential-provider-node@3.408.0: + resolution: {integrity: sha512-AzDtlj2Mb01K5+AiDI14HsIs9I/pI4nM3kxeOZZvocaaThF5OFR+4wR2v2plhfGJ8QAPEE/KnqcJ3JlJ7orShg==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/credential-provider-env': 3.408.0 + '@aws-sdk/credential-provider-ini': 3.408.0 + '@aws-sdk/credential-provider-process': 3.408.0 + '@aws-sdk/credential-provider-sso': 3.408.0 + '@aws-sdk/credential-provider-web-identity': 3.408.0 + '@aws-sdk/types': 3.408.0 + '@smithy/credential-provider-imds': 2.0.7 + '@smithy/property-provider': 2.0.6 + '@smithy/shared-ini-file-loader': 2.0.6 '@smithy/types': 2.2.2 tslib: 2.5.2 transitivePeerDependencies: @@ -1233,8 +1196,19 @@ packages: engines: {node: '>=14.0.0'} dependencies: '@aws-sdk/types': 3.398.0 - '@smithy/property-provider': 2.0.4 - '@smithy/shared-ini-file-loader': 2.0.4 + '@smithy/property-provider': 2.0.6 + '@smithy/shared-ini-file-loader': 2.0.6 + '@smithy/types': 2.2.2 + tslib: 2.5.2 + dev: false + + /@aws-sdk/credential-provider-process@3.408.0: + resolution: {integrity: sha512-qCTf9tr6+I2s3+v5zP4YRQQrGlYw/jyZ7u/k6bGshhlvgwGPfjNuHrM8uK/W1kv4ng1myxaL1/tAY6RVVdXz4Q==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/types': 3.408.0 + '@smithy/property-provider': 2.0.6 + '@smithy/shared-ini-file-loader': 2.0.6 '@smithy/types': 2.2.2 tslib: 2.5.2 dev: false @@ -1246,8 +1220,23 @@ packages: '@aws-sdk/client-sso': 3.398.0 '@aws-sdk/token-providers': 3.398.0 '@aws-sdk/types': 3.398.0 - '@smithy/property-provider': 2.0.4 - '@smithy/shared-ini-file-loader': 2.0.4 + '@smithy/property-provider': 2.0.6 + '@smithy/shared-ini-file-loader': 2.0.6 + '@smithy/types': 2.2.2 + tslib: 2.5.2 + transitivePeerDependencies: + - aws-crt + dev: false + + /@aws-sdk/credential-provider-sso@3.408.0: + resolution: {integrity: sha512-iKU91cxrttQyDhdhF7vJZd6XibvwGolFzuJBG4DD4jOdvmTcVq4L26AH8bjR1psnS6pvTa66FaYt6BGtbXgVeA==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/client-sso': 3.408.0 + '@aws-sdk/token-providers': 3.408.0 + '@aws-sdk/types': 3.408.0 + '@smithy/property-provider': 2.0.6 + '@smithy/shared-ini-file-loader': 2.0.6 '@smithy/types': 2.2.2 tslib: 2.5.2 transitivePeerDependencies: @@ -1259,7 +1248,17 @@ packages: engines: {node: '>=14.0.0'} dependencies: '@aws-sdk/types': 3.398.0 - '@smithy/property-provider': 2.0.4 + '@smithy/property-provider': 2.0.6 + '@smithy/types': 2.2.2 + tslib: 2.5.2 + dev: false + + /@aws-sdk/credential-provider-web-identity@3.408.0: + resolution: {integrity: sha512-5FbDPF/zY/1t6k1zRI/HnrxcH2v7SwsEYu2SThI2qbzaP/K7MTnTanV5vNFcdQOpuQ7x3PrzTlH3AWZueCr3Vw==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/types': 3.408.0 + '@smithy/property-provider': 2.0.6 '@smithy/types': 2.2.2 tslib: 2.5.2 dev: false @@ -1274,6 +1273,16 @@ packages: tslib: 2.5.2 dev: false + /@aws-sdk/middleware-host-header@3.408.0: + resolution: {integrity: sha512-eofCXuSZ+ntbLzeCRdHzraXzgWqAplXU7W2qFFVC4O9lZBhADwNPI8n8x98TH0mftnmvZxh5Bo5U8WvEolIDkw==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/types': 3.408.0 + '@smithy/protocol-http': 2.0.5 + '@smithy/types': 2.2.2 + tslib: 2.5.2 + dev: false + /@aws-sdk/middleware-logger@3.398.0: resolution: {integrity: sha512-CiJjW+FL12elS6Pn7/UVjVK8HWHhXMfvHZvOwx/Qkpy340sIhkuzOO6fZEruECDTZhl2Wqn81XdJ1ZQ4pRKpCg==} engines: {node: '>=14.0.0'} @@ -1283,6 +1292,15 @@ packages: tslib: 2.5.2 dev: false + /@aws-sdk/middleware-logger@3.408.0: + resolution: {integrity: sha512-otwXPCubsGRFv8Hb6nKw6Vvnu4dC8CcPk05buStj42nF8QdjWrKGb2rDCvLph5lr576LF5HN+Y2moyOi7z/I7g==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/types': 3.408.0 + '@smithy/types': 2.2.2 + tslib: 2.5.2 + dev: false + /@aws-sdk/middleware-recursion-detection@3.398.0: resolution: {integrity: sha512-7QpOqPQAZNXDXv6vsRex4R8dLniL0E/80OPK4PPFsrCh9btEyhN9Begh4i1T+5lL28hmYkztLOkTQ2N5J3hgRQ==} engines: {node: '>=14.0.0'} @@ -1293,6 +1311,16 @@ packages: tslib: 2.5.2 dev: false + /@aws-sdk/middleware-recursion-detection@3.408.0: + resolution: {integrity: sha512-QfZwmX5z0IRC2c8pBi9VozSqbJw19V5oxyykSTqdjGe3CG3yNujXObV6xQesK67CWSnPb9wDgVGKUoYuIXwOxw==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/types': 3.408.0 + '@smithy/protocol-http': 2.0.5 + '@smithy/types': 2.2.2 + tslib: 2.5.2 + dev: false + /@aws-sdk/middleware-sdk-sts@3.398.0: resolution: {integrity: sha512-+JH76XHEgfVihkY+GurohOQ5Z83zVN1nYcQzwCFnCDTh4dG4KwhnZKG+WPw6XJECocY0R+H0ivofeALHvVWJtQ==} engines: {node: '>=14.0.0'} @@ -1303,12 +1331,35 @@ packages: tslib: 2.5.2 dev: false + /@aws-sdk/middleware-sdk-sts@3.408.0: + resolution: {integrity: sha512-dIO9BTX049P2PwaeAK2lxJeA2rZi9/bWzMP1GIE60VrMDHmN5Ljvh1lLActECLAqNQIqN5Ub0bKV2tC/jMn+CA==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/middleware-signing': 3.408.0 + '@aws-sdk/types': 3.408.0 + '@smithy/types': 2.2.2 + tslib: 2.5.2 + dev: false + /@aws-sdk/middleware-signing@3.398.0: resolution: {integrity: sha512-O0KqXAix1TcvZBFt1qoFkHMUNJOSgjJTYS7lFTRKSwgsD27bdW2TM2r9R8DAccWFt5Amjkdt+eOwQMIXPGTm8w==} engines: {node: '>=14.0.0'} dependencies: - '@aws-sdk/types': 3.398.0 - '@smithy/property-provider': 2.0.4 + '@aws-sdk/types': 3.398.0 + '@smithy/property-provider': 2.0.6 + '@smithy/protocol-http': 2.0.5 + '@smithy/signature-v4': 2.0.4 + '@smithy/types': 2.2.2 + '@smithy/util-middleware': 2.0.0 + tslib: 2.5.2 + dev: false + + /@aws-sdk/middleware-signing@3.408.0: + resolution: {integrity: sha512-flLiLKATJ4NLcLb7lPojyQ6NvLSyQ3axqIClqwMRnhSRxvREB7OgBKwmPecSl0I5JxsNEqo+mjARdMjUHadgWQ==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/types': 3.408.0 + '@smithy/property-provider': 2.0.6 '@smithy/protocol-http': 2.0.5 '@smithy/signature-v4': 2.0.4 '@smithy/types': 2.2.2 @@ -1327,6 +1378,17 @@ packages: tslib: 2.5.2 dev: false + /@aws-sdk/middleware-user-agent@3.408.0: + resolution: {integrity: sha512-UvlKri8/Mgf5W+tFU6ZJ65fC6HljcysIqfRFts/8Wurl322IS1I4j+pyjV2P6eK1054bzynfi3Trv+tRYHtVcA==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/types': 3.408.0 + '@aws-sdk/util-endpoints': 3.408.0 + '@smithy/protocol-http': 2.0.5 + '@smithy/types': 2.2.2 + tslib: 2.5.2 + dev: false + /@aws-sdk/token-providers@3.398.0: resolution: {integrity: sha512-nrYgjzavGCKJL/48Vt0EL+OlIc5UZLfNGpgyUW9cv3XZwl+kXV0QB+HH0rHZZLfpbBgZ2RBIJR9uD5ieu/6hpQ==} engines: {node: '>=14.0.0'} @@ -1352,9 +1414,52 @@ packages: '@smithy/middleware-stack': 2.0.0 '@smithy/node-config-provider': 2.0.7 '@smithy/node-http-handler': 2.0.5 - '@smithy/property-provider': 2.0.4 + '@smithy/property-provider': 2.0.6 + '@smithy/protocol-http': 2.0.5 + '@smithy/shared-ini-file-loader': 2.0.6 + '@smithy/smithy-client': 2.0.5 + '@smithy/types': 2.2.2 + '@smithy/url-parser': 2.0.5 + '@smithy/util-base64': 2.0.0 + '@smithy/util-body-length-browser': 2.0.0 + '@smithy/util-body-length-node': 2.1.0 + '@smithy/util-defaults-mode-browser': 2.0.6 + '@smithy/util-defaults-mode-node': 2.0.7 + '@smithy/util-retry': 2.0.0 + '@smithy/util-utf8': 2.0.0 + tslib: 2.5.2 + transitivePeerDependencies: + - aws-crt + dev: false + + /@aws-sdk/token-providers@3.408.0: + resolution: {integrity: sha512-D//BjUrVtDzDdCz1mRdZZSAc822fh75Ssq46smeS6S6NKq3vJeHhfrQJMyVU1GclXu1tn9AwykaQW5Jwb5im+g==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-crypto/sha256-browser': 3.0.0 + '@aws-crypto/sha256-js': 3.0.0 + '@aws-sdk/middleware-host-header': 3.408.0 + '@aws-sdk/middleware-logger': 3.408.0 + '@aws-sdk/middleware-recursion-detection': 3.408.0 + '@aws-sdk/middleware-user-agent': 3.408.0 + '@aws-sdk/types': 3.408.0 + '@aws-sdk/util-endpoints': 3.408.0 + '@aws-sdk/util-user-agent-browser': 3.408.0 + '@aws-sdk/util-user-agent-node': 3.408.0 + '@smithy/config-resolver': 2.0.5 + '@smithy/fetch-http-handler': 2.0.5 + '@smithy/hash-node': 2.0.5 + '@smithy/invalid-dependency': 2.0.5 + '@smithy/middleware-content-length': 2.0.5 + '@smithy/middleware-endpoint': 2.0.5 + '@smithy/middleware-retry': 2.0.5 + '@smithy/middleware-serde': 2.0.5 + '@smithy/middleware-stack': 2.0.0 + '@smithy/node-config-provider': 2.0.7 + '@smithy/node-http-handler': 2.0.5 + '@smithy/property-provider': 2.0.6 '@smithy/protocol-http': 2.0.5 - '@smithy/shared-ini-file-loader': 2.0.4 + '@smithy/shared-ini-file-loader': 2.0.6 '@smithy/smithy-client': 2.0.5 '@smithy/types': 2.2.2 '@smithy/url-parser': 2.0.5 @@ -1378,6 +1483,14 @@ packages: tslib: 2.5.2 dev: false + /@aws-sdk/types@3.408.0: + resolution: {integrity: sha512-sIsR5224xWQTW7O6h4V0S7DMWs4bK4DCunwOo7Avpq7ZVmH2YyLTs0n4NGL186j8xTosycF1ACQgpM48SLIvaA==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/types': 2.2.2 + tslib: 2.5.2 + dev: false + /@aws-sdk/util-endpoints@3.398.0: resolution: {integrity: sha512-Fy0gLYAei/Rd6BrXG4baspCnWTUSd0NdokU1pZh4KlfEAEN1i8SPPgfiO5hLk7+2inqtCmqxVJlfqbMVe9k4bw==} engines: {node: '>=14.0.0'} @@ -1386,6 +1499,14 @@ packages: tslib: 2.5.2 dev: false + /@aws-sdk/util-endpoints@3.408.0: + resolution: {integrity: sha512-N1D5cKEkCqf5Q7IF/pI9kfcNrT+/5ctZ6cQo4Ex6xaOcnUzdOZcXdPqaMRZVZRn8enjK2SpoLlRpXGISOugPaw==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/types': 3.408.0 + tslib: 2.5.2 + dev: false + /@aws-sdk/util-locate-window@3.310.0: resolution: {integrity: sha512-qo2t/vBTnoXpjKxlsC2e1gBrRm80M3bId27r0BRB2VniSSe7bL1mmzM+/HFtujm0iAxtPM+aLEflLJlJeDPg0w==} engines: {node: '>=14.0.0'} @@ -1402,6 +1523,15 @@ packages: tslib: 2.5.2 dev: false + /@aws-sdk/util-user-agent-browser@3.408.0: + resolution: {integrity: sha512-wOVjDprG5h6kM8aJZk/tRX/RgxNxr73d6kIsUePlAgil13q62M9lcFMcIXduqtDsa1B6FfVB2wx/pyUuOZri5g==} + dependencies: + '@aws-sdk/types': 3.408.0 + '@smithy/types': 2.2.2 + bowser: 2.11.0 + tslib: 2.5.2 + dev: false + /@aws-sdk/util-user-agent-node@3.398.0: resolution: {integrity: sha512-RTVQofdj961ej4//fEkppFf4KXqKGMTCqJYghx3G0C/MYXbg7MGl7LjfNGtJcboRE8pfHHQ/TUWBDA7RIAPPlQ==} engines: {node: '>=14.0.0'} @@ -1417,6 +1547,21 @@ packages: tslib: 2.5.2 dev: false + /@aws-sdk/util-user-agent-node@3.408.0: + resolution: {integrity: sha512-BzMFV+cIXrtfcfJk3GpXnkANFkzZisvAtD306TMgIscn5FF26K1jD5DU+h5Q5WMq7gx+oXh9kJ3Lu3hi7hahKQ==} + engines: {node: '>=14.0.0'} + peerDependencies: + aws-crt: '>=1.0.0' + peerDependenciesMeta: + aws-crt: + optional: true + dependencies: + '@aws-sdk/types': 3.408.0 + '@smithy/node-config-provider': 2.0.7 + '@smithy/types': 2.2.2 + tslib: 2.5.2 + dev: false + /@aws-sdk/util-utf8-browser@3.259.0: resolution: {integrity: sha512-UvFa/vR+e19XookZF8RzFZBrw2EUkQWxiBW0yYQAhvk3C+QVGl0H3ouca8LDBlBfQKXwmW3huo/59H8rwb1wJw==} dependencies: @@ -1432,6 +1577,7 @@ packages: /@babel/compat-data@7.22.3: resolution: {integrity: sha512-aNtko9OPOwVESUFp3MZfD8Uzxl7JzSeJpd7npIoxCasU37PFbAQRpKglkaKwlHOyeJdrREpo8TW8ldrkYWwvIQ==} engines: {node: '>=6.9.0'} + dev: false /@babel/core@7.12.9: resolution: {integrity: sha512-gTXYh3M5wb7FRXQy+FErKFAv90BnlOuNn1QkCK2lREoPAjrQCO49+HVSrFoe5uakFAF5eenS75KbO2vQiLrTMQ==} @@ -1478,6 +1624,7 @@ packages: semver: 6.3.1 transitivePeerDependencies: - supports-color + dev: false /@babel/generator@7.22.3: resolution: {integrity: sha512-C17MW4wlk//ES/CJDL51kPNwl+qiBQyN7b9SKyVp11BLGFeSPoVaHrv+MNt8jwQFhQWowW88z1eeBx3pFz9v8A==} @@ -1487,6 +1634,7 @@ packages: '@jridgewell/gen-mapping': 0.3.3 '@jridgewell/trace-mapping': 0.3.18 jsesc: 2.5.2 + dev: false /@babel/helper-annotate-as-pure@7.18.6: resolution: {integrity: sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==} @@ -1514,6 +1662,7 @@ packages: browserslist: 4.21.5 lru-cache: 5.1.1 semver: 6.3.1 + dev: false /@babel/helper-create-class-features-plugin@7.22.1(@babel/core@7.22.1): resolution: {integrity: sha512-SowrZ9BWzYFgzUMwUmowbPSGu6CXL5MSuuCkG3bejahSpSymioPmuLdhPxNOc9MjuNGjy7M/HaXvJ8G82Lywlw==} @@ -1566,6 +1715,7 @@ packages: /@babel/helper-environment-visitor@7.22.1: resolution: {integrity: sha512-Z2tgopurB/kTbidvzeBrc2To3PUP/9i5MUe+fU6QJCQDyPwSH2oRapkLw3KGECDYSjhQZCNxEvNvZlLw8JjGwA==} engines: {node: '>=6.9.0'} + dev: false /@babel/helper-function-name@7.21.0: resolution: {integrity: sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg==} @@ -1573,12 +1723,14 @@ packages: dependencies: '@babel/template': 7.21.9 '@babel/types': 7.22.3 + dev: false /@babel/helper-hoist-variables@7.18.6: resolution: {integrity: sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==} engines: {node: '>=6.9.0'} dependencies: '@babel/types': 7.22.3 + dev: false /@babel/helper-member-expression-to-functions@7.22.3: resolution: {integrity: sha512-Gl7sK04b/2WOb6OPVeNy9eFKeD3L6++CzL3ykPOWqTn08xgYYK0wz4TUh2feIImDXxcVW3/9WQ1NMKY66/jfZA==} @@ -1592,6 +1744,7 @@ packages: engines: {node: '>=6.9.0'} dependencies: '@babel/types': 7.22.3 + dev: false /@babel/helper-module-transforms@7.22.1: resolution: {integrity: sha512-dxAe9E7ySDGbQdCVOY/4+UcD8M9ZFqZcZhSPsPacvCG4M+9lwtDDQfI2EoaSvmf7W/8yCBkGU0m7Pvt1ru3UZw==} @@ -1607,6 +1760,7 @@ packages: '@babel/types': 7.22.3 transitivePeerDependencies: - supports-color + dev: false /@babel/helper-optimise-call-expression@7.18.6: resolution: {integrity: sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA==} @@ -1658,6 +1812,7 @@ packages: engines: {node: '>=6.9.0'} dependencies: '@babel/types': 7.22.3 + dev: false /@babel/helper-skip-transparent-expression-wrappers@7.20.0: resolution: {integrity: sha512-5y1JYeNKfvnT8sZcK9DVRtpTbGiomYIHviSP3OQWmDPU3DeH4a1ZlT/N2lyQ5P8egjcRaT/Y9aNqUxK0WsnIIg==} @@ -1671,10 +1826,12 @@ packages: engines: {node: '>=6.9.0'} dependencies: '@babel/types': 7.22.3 + dev: false /@babel/helper-string-parser@7.21.5: resolution: {integrity: sha512-5pTUx3hAJaZIdW99sJ6ZUUgWq/Y+Hja7TowEnLNMm1VivRgZQL3vpBY3qUACVsvw+yQU6+YgfBVmcbLaZtrA1w==} engines: {node: '>=6.9.0'} + dev: false /@babel/helper-validator-identifier@7.19.1: resolution: {integrity: sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==} @@ -1683,6 +1840,7 @@ packages: /@babel/helper-validator-option@7.21.0: resolution: {integrity: sha512-rmL/B8/f0mKS2baE9ZpyTcTavvEuWhTTW8amjzXNvYG4AwBsqTLikfXsEofsJEfKHf+HQVQbFOHy6o+4cnC/fQ==} engines: {node: '>=6.9.0'} + dev: false /@babel/helper-wrap-function@7.20.5: resolution: {integrity: sha512-bYMxIWK5mh+TgXGVqAtnu5Yn1un+v8DDZtqyzKRLUzrh70Eal2O3aZ7aPYiMADO4uKlkzOiRiZ6GX5q3qxvW9Q==} @@ -1705,6 +1863,7 @@ packages: '@babel/types': 7.22.3 transitivePeerDependencies: - supports-color + dev: false /@babel/highlight@7.18.6: resolution: {integrity: sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==} @@ -1720,6 +1879,7 @@ packages: hasBin: true dependencies: '@babel/types': 7.22.3 + dev: false /@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.18.6(@babel/core@7.22.1): resolution: {integrity: sha512-Dgxsyg54Fx1d4Nge8UnvTrED63vrwOdPmyvPzlNN/boaliRP54pm3pGzZD1SJUwrBA+Cs/xdG8kXX6Mn/RfISQ==} @@ -2800,6 +2960,7 @@ packages: '@babel/code-frame': 7.21.4 '@babel/parser': 7.22.3 '@babel/types': 7.22.3 + dev: false /@babel/traverse@7.22.1: resolution: {integrity: sha512-lAWkdCoUFnmwLBhIRLciFntGYsIIoC6vIbN8zrLPqBnJmPu7Z6nzqnKd7FsxQUNAvZfVZ0x6KdNvNp8zWIOHSQ==} @@ -2817,6 +2978,7 @@ packages: globals: 11.12.0 transitivePeerDependencies: - supports-color + dev: false /@babel/types@7.22.3: resolution: {integrity: sha512-P3na3xIQHTKY4L0YOG7pM8M8uoUIB910WQaSiiMCZUC2Cy8XFEQONGABFnHWBa2gpGKODTAJcNhi5Zk0sLRrzg==} @@ -2825,10 +2987,59 @@ packages: '@babel/helper-string-parser': 7.21.5 '@babel/helper-validator-identifier': 7.19.1 to-fast-properties: 2.0.0 + dev: false /@balena/dockerignore@1.0.2: resolution: {integrity: sha512-wMue2Sy4GAVTk6Ic4tJVcnfdau+gx2EnG7S+uAEe+TWJFqE4YoWN4/H8MSLj4eYJKxGg26lZwboEniNiNwZQ6Q==} + /@bronya.js/api-construct@0.11.3: + resolution: {integrity: sha512-ro5/IwJQIv36k0uoYYL/pMzvFYJMlnzBOdeDylet+9T7gbXSjZEt+LKA9QjFGoqDvRl3WOQIJLUfJRollOVXng==} + engines: {node: '>=18', pnpm: ^8.0.0} + dependencies: + '@bronya.js/cli': 0.11.3 + '@bronya.js/core': 0.11.3 + acorn: 8.10.0 + acorn-typescript: 1.4.5(acorn@8.10.0) + aws-cdk-lib: 2.93.0(constructs@10.2.69) + body-parser: 1.20.2 + chokidar: 3.5.3 + consola: 3.2.3 + constructs: 10.2.69 + cors: 2.8.5 + defu: 6.1.2 + express: 4.18.2 + fs-extra: 11.1.1 + jiti: 1.19.1 + transitivePeerDependencies: + - supports-color + dev: true + + /@bronya.js/cli@0.11.3: + resolution: {integrity: sha512-1FkvlcXR17rfSGo7cDScrxCeC1S3Ib7GJaGTa874tOtrVHZVM7T0ebyPI1lO715BVEvS+hursJElRU9MZsnQOQ==} + engines: {node: '>=18', pnpm: ^8.0.0} + dev: true + + /@bronya.js/core@0.11.3: + resolution: {integrity: sha512-D5RU9y6o7KSmUjewjPR9ImR61/FQG4Xw42onn2vwiIJzCyQ7qzo1c7ecnbh7AVdldwa698kySRGne5Um/AemOA==} + engines: {node: '>=18', pnpm: ^8.0.0} + hasBin: true + dependencies: + '@bronya.js/cli': 0.11.3 + acorn: 8.10.0 + acorn-typescript: 1.4.5(acorn@8.10.0) + aws-cdk-lib: 2.93.0(constructs@10.2.69) + body-parser: 1.20.2 + chokidar: 3.5.3 + consola: 3.2.3 + constructs: 10.2.69 + cors: 2.8.5 + defu: 6.1.2 + express: 4.18.2 + jiti: 1.19.1 + transitivePeerDependencies: + - supports-color + dev: true + /@colors/colors@1.5.0: resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} engines: {node: '>=0.1.90'} @@ -2932,7 +3143,7 @@ packages: lodash.merge: 4.6.2 lodash.uniq: 4.5.0 resolve-from: 5.0.0 - ts-node: 10.9.1(@types/node@20.4.7)(typescript@5.2.2) + ts-node: 10.9.1(@types/node@18.17.12)(typescript@5.2.2) typescript: 5.2.2 transitivePeerDependencies: - '@swc/core' @@ -3859,6 +4070,7 @@ packages: cpu: [arm64] os: [android] requiresBuild: true + dev: true optional: true /@esbuild/android-arm@0.17.19: @@ -3885,6 +4097,7 @@ packages: cpu: [arm] os: [android] requiresBuild: true + dev: true optional: true /@esbuild/android-x64@0.17.19: @@ -3911,6 +4124,7 @@ packages: cpu: [x64] os: [android] requiresBuild: true + dev: true optional: true /@esbuild/darwin-arm64@0.17.19: @@ -3937,6 +4151,7 @@ packages: cpu: [arm64] os: [darwin] requiresBuild: true + dev: true optional: true /@esbuild/darwin-x64@0.17.19: @@ -3963,6 +4178,7 @@ packages: cpu: [x64] os: [darwin] requiresBuild: true + dev: true optional: true /@esbuild/freebsd-arm64@0.17.19: @@ -3989,6 +4205,7 @@ packages: cpu: [arm64] os: [freebsd] requiresBuild: true + dev: true optional: true /@esbuild/freebsd-x64@0.17.19: @@ -4015,6 +4232,7 @@ packages: cpu: [x64] os: [freebsd] requiresBuild: true + dev: true optional: true /@esbuild/linux-arm64@0.17.19: @@ -4041,6 +4259,7 @@ packages: cpu: [arm64] os: [linux] requiresBuild: true + dev: true optional: true /@esbuild/linux-arm@0.17.19: @@ -4067,6 +4286,7 @@ packages: cpu: [arm] os: [linux] requiresBuild: true + dev: true optional: true /@esbuild/linux-ia32@0.17.19: @@ -4093,6 +4313,7 @@ packages: cpu: [ia32] os: [linux] requiresBuild: true + dev: true optional: true /@esbuild/linux-loong64@0.17.19: @@ -4119,6 +4340,7 @@ packages: cpu: [loong64] os: [linux] requiresBuild: true + dev: true optional: true /@esbuild/linux-mips64el@0.17.19: @@ -4145,6 +4367,7 @@ packages: cpu: [mips64el] os: [linux] requiresBuild: true + dev: true optional: true /@esbuild/linux-ppc64@0.17.19: @@ -4171,6 +4394,7 @@ packages: cpu: [ppc64] os: [linux] requiresBuild: true + dev: true optional: true /@esbuild/linux-riscv64@0.17.19: @@ -4197,6 +4421,7 @@ packages: cpu: [riscv64] os: [linux] requiresBuild: true + dev: true optional: true /@esbuild/linux-s390x@0.17.19: @@ -4223,6 +4448,7 @@ packages: cpu: [s390x] os: [linux] requiresBuild: true + dev: true optional: true /@esbuild/linux-x64@0.17.19: @@ -4249,6 +4475,7 @@ packages: cpu: [x64] os: [linux] requiresBuild: true + dev: true optional: true /@esbuild/netbsd-x64@0.17.19: @@ -4275,6 +4502,7 @@ packages: cpu: [x64] os: [netbsd] requiresBuild: true + dev: true optional: true /@esbuild/openbsd-x64@0.17.19: @@ -4301,6 +4529,7 @@ packages: cpu: [x64] os: [openbsd] requiresBuild: true + dev: true optional: true /@esbuild/sunos-x64@0.17.19: @@ -4327,6 +4556,7 @@ packages: cpu: [x64] os: [sunos] requiresBuild: true + dev: true optional: true /@esbuild/win32-arm64@0.17.19: @@ -4353,6 +4583,7 @@ packages: cpu: [arm64] os: [win32] requiresBuild: true + dev: true optional: true /@esbuild/win32-ia32@0.17.19: @@ -4379,6 +4610,7 @@ packages: cpu: [ia32] os: [win32] requiresBuild: true + dev: true optional: true /@esbuild/win32-x64@0.17.19: @@ -4405,6 +4637,7 @@ packages: cpu: [x64] os: [win32] requiresBuild: true + dev: true optional: true /@eslint-community/eslint-utils@4.4.0(eslint@8.48.0): @@ -4540,16 +4773,12 @@ packages: /@humanwhocodes/object-schema@1.2.1: resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==} - /@istanbuljs/schema@0.1.3: - resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} - engines: {node: '>=8'} - dev: true - /@jest/schemas@29.6.0: resolution: {integrity: sha512-rxLjXyJBTL4LQeJW3aKo0M/+GkCOXsO+8i9Iu7eDb6KwtP65ayoDsitrdPBtujxQ88k4wI2FNYfa6TOGwSn6cQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@sinclair/typebox': 0.27.8 + dev: false /@jest/types@29.5.0: resolution: {integrity: sha512-qbu7kN6czmVRc3xWFQcAN03RAUamgppVUdXrvl1Wr3jlNF93o9mJbGcDWrwGB6ht44u7efB1qCFgVQmca24Uog==} @@ -4773,6 +5002,7 @@ packages: /@sinclair/typebox@0.27.8: resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} + dev: false /@sindresorhus/is@0.14.0: resolution: {integrity: sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==} @@ -4796,24 +5026,21 @@ packages: tslib: 2.5.2 dev: false - /@smithy/config-resolver@2.0.5: - resolution: {integrity: sha512-n0c2AXz+kjALY2FQr7Zy9zhYigXzboIh1AuUUVCqFBKFtdEvTwnwPXrTDoEehLiRTUHNL+4yzZ3s+D0kKYSLSg==} + /@smithy/abort-controller@2.0.6: + resolution: {integrity: sha512-4I7g0lyGUlW2onf8mD76IzU37oRWSHsQ5zlW5MjDzgg4I4J9bOK4500Gx6qOuoN7+GulAnGLe1YwyrIluzhakg==} engines: {node: '>=14.0.0'} dependencies: - '@smithy/types': 2.2.2 - '@smithy/util-config-provider': 2.0.0 - '@smithy/util-middleware': 2.0.0 + '@smithy/types': 2.3.0 tslib: 2.5.2 dev: false - /@smithy/credential-provider-imds@2.0.4: - resolution: {integrity: sha512-vW7xoDKZwjjf/2GCwVf/uvZce/QJOAYan9r8UsqlzOrnnpeS2ffhxeZjLK0/emZu8n6qU3amGgZ/BTo3oVtEyQ==} + /@smithy/config-resolver@2.0.5: + resolution: {integrity: sha512-n0c2AXz+kjALY2FQr7Zy9zhYigXzboIh1AuUUVCqFBKFtdEvTwnwPXrTDoEehLiRTUHNL+4yzZ3s+D0kKYSLSg==} engines: {node: '>=14.0.0'} dependencies: - '@smithy/node-config-provider': 2.0.7 - '@smithy/property-provider': 2.0.4 '@smithy/types': 2.2.2 - '@smithy/url-parser': 2.0.5 + '@smithy/util-config-provider': 2.0.0 + '@smithy/util-middleware': 2.0.0 tslib: 2.5.2 dev: false @@ -4828,15 +5055,6 @@ packages: tslib: 2.5.2 dev: false - /@smithy/eventstream-codec@2.0.4: - resolution: {integrity: sha512-DkVLcQjhOxPj/4pf2hNj2kvOeoLczirHe57g7czMNJCUBvg9cpU9hNgqS37Y5sjdEtMSa2oTyCS5oeHZtKgoIw==} - dependencies: - '@aws-crypto/crc32': 3.0.0 - '@smithy/types': 2.2.2 - '@smithy/util-hex-encoding': 2.0.0 - tslib: 2.5.2 - dev: false - /@smithy/eventstream-codec@2.0.5: resolution: {integrity: sha512-iqR6OuOV3zbQK8uVs9o+9AxhVk8kW9NAxA71nugwUB+kTY9C35pUd0A5/m4PRT0Y0oIW7W4kgnSR3fdYXQjECw==} dependencies: @@ -4984,14 +5202,6 @@ packages: tslib: 2.5.2 dev: false - /@smithy/property-provider@2.0.4: - resolution: {integrity: sha512-OfaUIhnyvOkuCPHWMPkJqX++dUaDKsiZWuZqCdU04Z9dNAl2TtZAh7dw2rsZGb57vq6YH3PierNrDfQJTAKYtg==} - engines: {node: '>=14.0.0'} - dependencies: - '@smithy/types': 2.2.2 - tslib: 2.5.2 - dev: false - /@smithy/property-provider@2.0.6: resolution: {integrity: sha512-CVem6ZkkWxbTnhjDLyLESY0oLA6IUZYtdqrCpGQKUXaFBOuc/izjm7fIFGBxEbjZ1EGcH9hHxrjqX36RWULNRg==} engines: {node: '>=14.0.0'} @@ -5030,14 +5240,6 @@ packages: engines: {node: '>=14.0.0'} dev: false - /@smithy/shared-ini-file-loader@2.0.4: - resolution: {integrity: sha512-091yneupXnSqvAU+vLG7h0g4QRRO6TjulpECXYVU6yW/LiNp7QE533DBpaphmbtI6tTC4EfGrhn35gTa0w+GQg==} - engines: {node: '>=14.0.0'} - dependencies: - '@smithy/types': 2.2.2 - tslib: 2.5.2 - dev: false - /@smithy/shared-ini-file-loader@2.0.6: resolution: {integrity: sha512-NO6dHqho6APbVR0DxPtYoL4KXBqUeSM3Slsd103MOgL50YbzzsQmMLtDMZ87W8MlvvCN0tuiq+OrAO/rM7hTQg==} engines: {node: '>=14.0.0'} @@ -5050,7 +5252,7 @@ packages: resolution: {integrity: sha512-y2xblkS0hb44QJDn9YjPp5aRFYSiI7w0bI3tATE3ybOrII2fppqD0SE3zgvew/B/3rTunuiCW+frTD0W4UYb9Q==} engines: {node: '>=14.0.0'} dependencies: - '@smithy/eventstream-codec': 2.0.4 + '@smithy/eventstream-codec': 2.0.5 '@smithy/is-array-buffer': 2.0.0 '@smithy/types': 2.2.2 '@smithy/util-hex-encoding': 2.0.0 @@ -5077,6 +5279,13 @@ packages: tslib: 2.5.2 dev: false + /@smithy/types@2.3.0: + resolution: {integrity: sha512-pJce3rd39MElkV57UTPAoSYAApjQLELUxjU5adHNLYk9gnPvyIGbJNJTZVVFu00BrgZH3W/cQe8QuFcknDyodQ==} + engines: {node: '>=14.0.0'} + dependencies: + tslib: 2.5.2 + dev: false + /@smithy/url-parser@2.0.5: resolution: {integrity: sha512-OdMBvZhpckQSkugCXNJQCvqJ71wE7Ftxce92UOQLQ9pwF6hoS5PLL7wEfpnuEXtStzBqJYkzu1C1ZfjuFGOXAA==} dependencies: @@ -5203,6 +5412,15 @@ packages: tslib: 2.5.2 dev: false + /@smithy/util-waiter@2.0.6: + resolution: {integrity: sha512-wjxvKB4XSfgpOg3lr4RulnVhd21fMMC4CPARBwrSN7+3U28fwOifv8f7T+Ibay9DAQTj9qXxmd8ag6WXBRgNhg==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/abort-controller': 2.0.6 + '@smithy/types': 2.3.0 + tslib: 2.5.2 + dev: false + /@svgr/babel-plugin-add-jsx-attribute@6.5.1(@babel/core@7.22.1): resolution: {integrity: sha512-9PYGcXrAxitycIjRmZB+Q0JaN07GZIWaTBIGQzfaZv+qr1n8X1XUEJ5rZ/vx6OVD9RRYlrNnXWExQXcmZeD/BQ==} engines: {node: '>=10'} @@ -5392,14 +5610,12 @@ packages: requiresBuild: true dev: true - /@types/aws-lambda@8.10.119: - resolution: {integrity: sha512-Vqm22aZrCvCd6I5g1SvpW151jfqwTzEZ7XJ3yZ6xaZG31nUEOEyzzVImjRcsN8Wi/QyPxId/x8GTtgIbsy8kEw==} + /@tsconfig/node18@18.2.1: + resolution: {integrity: sha512-RDDZFuofwkcKpl8Vpj5wFbY+H53xOtqK7ckEL1sXsbPwvKwDdjQf3LkHbtt9sxIHn9nWIEwkmCwBRZ6z5TKU2A==} dev: true - /@types/babel__traverse@7.20.1: - resolution: {integrity: sha512-MitHFXnhtgwsGZWtT68URpOvLN4EREih1u3QtQiN4VdAxWKRVvGCSvw/Qth0M0Qq3pJpnGOu5JaM/ydK7OGbqg==} - dependencies: - '@babel/types': 7.22.3 + /@types/aws-lambda@8.10.119: + resolution: {integrity: sha512-Vqm22aZrCvCd6I5g1SvpW151jfqwTzEZ7XJ3yZ6xaZG31nUEOEyzzVImjRcsN8Wi/QyPxId/x8GTtgIbsy8kEw==} dev: true /@types/body-parser@1.19.2: @@ -5407,6 +5623,7 @@ packages: dependencies: '@types/connect': 3.4.35 '@types/node': 18.17.12 + dev: false /@types/bonjour@3.5.10: resolution: {integrity: sha512-p7ienRMiS41Nu2/igbJxxLDWrSZ0WxM8UQgCeO9KhoVF7cOVFkrKsiDr1EsJIla8vV3oEEjGcz11jc5yimhzZw==} @@ -5414,16 +5631,6 @@ packages: '@types/node': 18.17.12 dev: false - /@types/chai-subset@1.3.3: - resolution: {integrity: sha512-frBecisrNGz+F4T6bcc+NLeolfiojh5FxW2klu669+8BARtyQv2C/GkNW6FUodVe4BroGMP/wER/YDGc7rEllw==} - dependencies: - '@types/chai': 4.3.5 - dev: true - - /@types/chai@4.3.5: - resolution: {integrity: sha512-mEo1sAde+UCE6b2hxn332f1g1E8WfYRu6p5SvTKr2ZKC1f7gFJXk4h5PyGP9Dt6gCaG8y8XhwnXWC6Iy2cmBng==} - dev: true - /@types/connect-history-api-fallback@1.5.0: resolution: {integrity: sha512-4x5FkPpLipqwthjPsF7ZRbOv3uoLUFkTA9G9v583qi4pACvq0uTELrB8OLUzPWUI4IJIyvM85vzkV1nyiI2Lig==} dependencies: @@ -5435,12 +5642,7 @@ packages: resolution: {integrity: sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==} dependencies: '@types/node': 18.17.12 - - /@types/cors@2.8.13: - resolution: {integrity: sha512-RG8AStHlUiV5ysZQKq97copd2UmVYw3/pRMLefISZ3S1hK104Cwm7iLQ3fTKx+lsUH2CE8FlLaYeEA2LSeqYUA==} - dependencies: - '@types/node': 18.17.12 - dev: true + dev: false /@types/eslint-scope@3.7.4: resolution: {integrity: sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==} @@ -5464,6 +5666,7 @@ packages: '@types/qs': 6.9.7 '@types/range-parser': 1.2.4 '@types/send': 0.17.1 + dev: false /@types/express@4.17.17: resolution: {integrity: sha512-Q4FmmuLGBG58btUnfS1c1r/NQdlp3DMfGDGig8WhfpA2YRUtEkxAjkZb0yvplJGYdF1fsQ81iMDcH24sSCNC/Q==} @@ -5472,6 +5675,7 @@ packages: '@types/express-serve-static-core': 4.17.35 '@types/qs': 6.9.7 '@types/serve-static': 1.15.1 + dev: false /@types/hast@2.3.4: resolution: {integrity: sha512-wLEm0QvaoawEDoTRwzTXp4b4jpwiJDvR5KMnFnVodm3scufTlBOWRD6N1OBf9TZMhjlNsSfcO5V+7AF4+Vy+9g==} @@ -5541,9 +5745,11 @@ packages: /@types/mime@1.3.2: resolution: {integrity: sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==} + dev: false /@types/mime@3.0.1: resolution: {integrity: sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==} + dev: false /@types/minimist@1.2.2: resolution: {integrity: sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==} @@ -5572,6 +5778,10 @@ packages: resolution: {integrity: sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==} dev: true + /@types/pako@2.0.1: + resolution: {integrity: sha512-fXhui1fHdLrUR0KEyQsBzqdi3Z+MitnRcpI2eeFJyzaRdqO2miX/BDz2Hh0VdkBbrWprgcQ+ItFmbdKYdbMjvg==} + dev: true + /@types/parse-json@4.0.0: resolution: {integrity: sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==} dev: false @@ -5589,9 +5799,11 @@ packages: /@types/qs@6.9.7: resolution: {integrity: sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==} + dev: false /@types/range-parser@1.2.4: resolution: {integrity: sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==} + dev: false /@types/react-router-config@5.0.7: resolution: {integrity: sha512-pFFVXUIydHlcJP6wJm7sDii5mD/bCmmAY0wQzq+M+uX7bqS95AQqHZWP1iNMKrWVQSuHIzj5qi9BvrtLX2/T4w==} @@ -5648,6 +5860,7 @@ packages: dependencies: '@types/mime': 1.3.2 '@types/node': 18.17.12 + dev: false /@types/serve-index@1.9.1: resolution: {integrity: sha512-d/Hs3nWDxNL2xAczmOVZNj92YZCS6RGxfBPjKzuu/XirCgXdpKEb88dYNbrYGint6IVWLNP+yonwVAuRC0T2Dg==} @@ -5660,6 +5873,7 @@ packages: dependencies: '@types/mime': 3.0.1 '@types/node': 18.17.12 + dev: false /@types/sockjs@0.3.33: resolution: {integrity: sha512-f0KEEe05NvUnat+boPTZ0dgaLZ4SfSouXUgv5noUiefG2ajgKjmETo9ZJyuqsl7dfl2aHlLJUiki6B4ZYldiiw==} @@ -5830,60 +6044,6 @@ packages: eslint-visitor-keys: 3.4.3 dev: true - /@vitest/coverage-istanbul@0.34.3(vitest@0.34.3): - resolution: {integrity: sha512-RdEGzydbbalyDLmmJ5Qm+T3Lrubw/U9iCnhzM2B1V57t4cVa1t6uyfIHdv68d1au4PRzkLhY7Xouwuhb7BeG+Q==} - peerDependencies: - vitest: '>=0.32.0 <1' - dependencies: - istanbul-lib-coverage: 3.2.0 - istanbul-lib-instrument: 6.0.0 - istanbul-lib-report: 3.0.1 - istanbul-lib-source-maps: 4.0.1 - istanbul-reports: 3.1.5 - test-exclude: 6.0.0 - vitest: 0.34.3 - transitivePeerDependencies: - - supports-color - dev: true - - /@vitest/expect@0.34.3: - resolution: {integrity: sha512-F8MTXZUYRBVsYL1uoIft1HHWhwDbSzwAU9Zgh8S6WFC3YgVb4AnFV2GXO3P5Em8FjEYaZtTnQYoNwwBrlOMXgg==} - dependencies: - '@vitest/spy': 0.34.3 - '@vitest/utils': 0.34.3 - chai: 4.3.7 - dev: true - - /@vitest/runner@0.34.3: - resolution: {integrity: sha512-lYNq7N3vR57VMKMPLVvmJoiN4bqwzZ1euTW+XXYH5kzr3W/+xQG3b41xJn9ChJ3AhYOSoweu974S1V3qDcFESA==} - dependencies: - '@vitest/utils': 0.34.3 - p-limit: 4.0.0 - pathe: 1.1.1 - dev: true - - /@vitest/snapshot@0.34.3: - resolution: {integrity: sha512-QyPaE15DQwbnIBp/yNJ8lbvXTZxS00kRly0kfFgAD5EYmCbYcA+1EEyRalc93M0gosL/xHeg3lKAClIXYpmUiQ==} - dependencies: - magic-string: 0.30.1 - pathe: 1.1.1 - pretty-format: 29.6.1 - dev: true - - /@vitest/spy@0.34.3: - resolution: {integrity: sha512-N1V0RFQ6AI7CPgzBq9kzjRdPIgThC340DGjdKdPSE8r86aUSmeliTUgkTqLSgtEwWWsGfBQ+UetZWhK0BgJmkQ==} - dependencies: - tinyspy: 2.1.1 - dev: true - - /@vitest/utils@0.34.3: - resolution: {integrity: sha512-kiSnzLG6m/tiT0XEl4U2H8JDBjFtwVlaE8I3QfGiMFR0QvnRDfYfdP3YvTBWM/6iJDAyaPY6yVQiCTUc7ZzTHA==} - dependencies: - diff-sequences: 29.4.3 - loupe: 2.3.6 - pretty-format: 29.6.1 - dev: true - /@webassemblyjs/ast@1.11.6: resolution: {integrity: sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q==} dependencies: @@ -5995,7 +6155,6 @@ packages: dependencies: mime-types: 2.1.35 negotiator: 0.6.3 - dev: false /acorn-import-assertions@1.9.0(acorn@8.10.0): resolution: {integrity: sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==} @@ -6011,6 +6170,14 @@ packages: dependencies: acorn: 8.10.0 + /acorn-typescript@1.4.5(acorn@8.10.0): + resolution: {integrity: sha512-vQl+2YSoc/RLEGga1MNOm/4Z+R6yNQnGKFX9bdBukGzjjYbyQNEmNyZuqccXH8sgYemns+ZQJ4KZypnxBDzixw==} + peerDependencies: + acorn: '>=8.9.0' + dependencies: + acorn: 8.10.0 + dev: true + /acorn-walk@8.2.0: resolution: {integrity: sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==} engines: {node: '>=0.4.0'} @@ -6150,11 +6317,6 @@ packages: dependencies: color-convert: 2.0.1 - /ansi-styles@5.2.0: - resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} - engines: {node: '>=10'} - dev: true - /ansi-styles@6.2.1: resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} engines: {node: '>=12'} @@ -6191,7 +6353,7 @@ packages: /arktype@1.0.16-alpha: resolution: {integrity: sha512-k+poMKZ11h9iaB389WICNt8m/y2BtDCwe+kAg5BKklYqagKnFZOOOKwgdAxdDpKtU8ZYUedq7abqi9F+CEgnWA==} requiresBuild: true - dev: false + dev: true /array-buffer-byte-length@1.0.0: resolution: {integrity: sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==} @@ -6202,7 +6364,6 @@ packages: /array-flatten@1.1.1: resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==} - dev: false /array-flatten@2.1.2: resolution: {integrity: sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==} @@ -6267,10 +6428,6 @@ packages: resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==} dev: false - /assertion-error@1.1.0: - resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==} - dev: true - /astral-regex@2.0.0: resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==} engines: {node: '>=8'} @@ -6352,6 +6509,7 @@ packages: hasBin: true optionalDependencies: fsevents: 2.3.2 + dev: true /axios@0.25.0: resolution: {integrity: sha512-cD8FOb0tRH3uuEe6+evtAbgJtfxr7ly3fQjYcMcuPlgkwVS9xboaVIpcDV+cYQe+yGykgwZCs1pzjntcGa6l5g==} @@ -6501,7 +6659,6 @@ packages: unpipe: 1.0.0 transitivePeerDependencies: - supports-color - dev: false /body-parser@1.20.2: resolution: {integrity: sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==} @@ -6521,7 +6678,6 @@ packages: unpipe: 1.0.0 transitivePeerDependencies: - supports-color - dev: false /bonjour-service@1.1.1: resolution: {integrity: sha512-Z/5lQRMOG9k7W+FkeGTNjh7htqn/2LMnfOvBZ8pynNZCM9MwkQkI3zeI4oz09uWdcgmgHugVvBqxGg4VQJ5PCg==} @@ -6632,7 +6788,6 @@ packages: /bytes@3.1.2: resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} engines: {node: '>= 0.8'} - dev: false /cac@6.7.14: resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} @@ -6718,19 +6873,6 @@ packages: resolution: {integrity: sha512-vlNK021QdI7PNeiUh/lKkC/mNHHfV0m/Ad5JoI0TYtlBnJAslM/JIkm/tGC88bkLIwO6OQ5uV6ztS6kVAtCDlg==} dev: false - /chai@4.3.7: - resolution: {integrity: sha512-HLnAzZ2iupm25PlN0xFreAlBA5zaBSv3og0DdeGA4Ar6h6rJ3A0rolRUKJhSF2V10GZKDgWF/VmAEsNWjCRB+A==} - engines: {node: '>=4'} - dependencies: - assertion-error: 1.1.0 - check-error: 1.0.2 - deep-eql: 4.1.3 - get-func-name: 2.0.0 - loupe: 2.3.6 - pathval: 1.1.1 - type-detect: 4.0.8 - dev: true - /chalk@2.4.2: resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} engines: {node: '>=4'} @@ -6749,6 +6891,7 @@ packages: /chalk@5.3.0: resolution: {integrity: sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==} engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + dev: true /character-entities-legacy@1.1.4: resolution: {integrity: sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==} @@ -6766,10 +6909,6 @@ packages: resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} dev: true - /check-error@1.0.2: - resolution: {integrity: sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==} - dev: true - /cheerio-select@2.1.0: resolution: {integrity: sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==} dependencies: @@ -6833,13 +6972,6 @@ packages: engines: {node: '>=6'} dev: false - /cleye@1.3.2: - resolution: {integrity: sha512-MngIC2izcCz07iRKr3Pe8Z6ZBv4zbKFl/YnQEN/aMHis6PpH+MxI2e6n0bMUAmSVlMoAyQkdBCSTbfDmtcSovQ==} - dependencies: - terminal-columns: 1.4.1 - type-flag: 3.0.0 - dev: false - /cli-boxes@2.2.1: resolution: {integrity: sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==} engines: {node: '>=6'} @@ -7099,7 +7231,7 @@ packages: /consola@3.2.3: resolution: {integrity: sha512-I5qxpzLv+sJhTVEoLYNcTW+bThDCPsit0vLNKShZx6rLtpilNpmmeTPaeqJb9ZE9dV3DGaeby6Vuhrw38WjeyQ==} engines: {node: ^14.18.0 || >=16.10.0} - dev: false + dev: true /constructs@10.2.69: resolution: {integrity: sha512-0AiM/uQe5Uk6JVe/62oolmSN2MjbFQkOlYrM3fFGZLKuT+g7xlAI10EebFhyCcZwI2JAcWuWCmmCAyCothxjuw==} @@ -7115,12 +7247,10 @@ packages: engines: {node: '>= 0.6'} dependencies: safe-buffer: 5.2.1 - dev: false /content-type@1.0.5: resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} engines: {node: '>= 0.6'} - dev: false /conventional-changelog-angular@6.0.0: resolution: {integrity: sha512-6qLgrBF4gueoC7AFVHu51nHL9pF9FRjXrH+ceVf7WmAfH3gs+gEYOkvxhjMPjZu57I4AGUGoNTY8V7Hrgf1uqg==} @@ -7153,15 +7283,14 @@ packages: /convert-source-map@1.9.0: resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==} + dev: false /cookie-signature@1.0.6: resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==} - dev: false /cookie@0.5.0: resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==} engines: {node: '>= 0.6'} - dev: false /copy-text-to-clipboard@3.1.0: resolution: {integrity: sha512-PFM6BnjLnOON/lB3ta/Jg7Ywsv+l9kQGD4TWDCSlRBGmqnnTM5MrDkhAFgw+8HZt0wW6Q2BBE4cmy9sq+s9Qng==} @@ -7209,7 +7338,6 @@ packages: dependencies: object-assign: 4.1.1 vary: 1.1.2 - dev: false /cosmiconfig-typescript-loader@4.3.0(@types/node@20.4.7)(cosmiconfig@8.1.3)(ts-node@10.9.1)(typescript@5.2.2): resolution: {integrity: sha512-NTxV1MFfZDLPiBMjxbHRwSh5LaLcPMwNdCutmnHJCKoVnlvldPWlllonKwrsRJ5pYZBIBGRWWU2tfvzxgeSW5Q==} @@ -7223,7 +7351,7 @@ packages: dependencies: '@types/node': 20.4.7 cosmiconfig: 8.1.3 - ts-node: 10.9.1(@types/node@20.4.7)(typescript@5.2.2) + ts-node: 10.9.1(@types/node@18.17.12)(typescript@5.2.2) typescript: 5.2.2 dev: true @@ -7514,7 +7642,6 @@ packages: optional: true dependencies: ms: 2.0.0 - dev: false /debug@3.2.7: resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} @@ -7562,13 +7689,6 @@ packages: resolution: {integrity: sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==} dev: true - /deep-eql@4.1.3: - resolution: {integrity: sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==} - engines: {node: '>=6'} - dependencies: - type-detect: 4.0.8 - dev: true - /deep-extend@0.6.0: resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} engines: {node: '>=4.0.0'} @@ -7596,7 +7716,7 @@ packages: dependencies: bundle-name: 3.0.0 default-browser-id: 3.0.0 - execa: 7.1.1 + execa: 7.2.0 titleize: 3.0.0 dev: true @@ -7636,7 +7756,7 @@ packages: /defu@6.1.2: resolution: {integrity: sha512-+uO4+qr7msjNNWKYPHqN/3+Dx3NFkmIzayk2L1MyZQlvgZb/J1A0fo410dpKrN2SnqFjt8n4JL8fDJE0wIgjFQ==} - dev: false + dev: true /del@6.1.1: resolution: {integrity: sha512-ua8BhapfP0JUJKC/zV9yHHDW/rDoDxP4Zhn3AkA6/xT6gY7jYXJiaeyBZznYVujhZZET+UgcbZiQ7sN3WqcImg==} @@ -7665,12 +7785,10 @@ packages: /depd@2.0.0: resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} engines: {node: '>= 0.8'} - dev: false /destroy@1.2.0: resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} - dev: false /detab@2.0.4: resolution: {integrity: sha512-8zdsQA5bIkoRECvCrNKPla84lyoR7DSAyf7p0YgXzBO9PDJx8KntPUay7NS6yp+KdxdVtiE5SpHKtbp2ZQyA9g==} @@ -7732,11 +7850,6 @@ packages: ts-interface-checker: 1.0.0 dev: true - /diff-sequences@29.4.3: - resolution: {integrity: sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dev: true - /diff@4.0.2: resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} engines: {node: '>=0.3.1'} @@ -7889,6 +8002,7 @@ packages: /dotenv@16.3.1: resolution: {integrity: sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==} engines: {node: '>=12'} + dev: true /dotenv@8.6.0: resolution: {integrity: sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==} @@ -7913,7 +8027,6 @@ packages: /ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} - dev: false /electron-to-chromium@1.4.411: resolution: {integrity: sha512-5VXLW4Qw89vM2WTICHua/y8v7fKGDRVa2VPOtBB9IpLvW316B+xd8yD1wTmLPY2ot/00P/qt87xdolj4aG/Lzg==} @@ -7940,7 +8053,6 @@ packages: /encodeurl@1.0.2: resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} engines: {node: '>= 0.8'} - dev: false /end-of-stream@1.4.4: resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} @@ -8124,6 +8236,7 @@ packages: '@esbuild/win32-arm64': 0.19.2 '@esbuild/win32-ia32': 0.19.2 '@esbuild/win32-x64': 0.19.2 + dev: true /escalade@3.1.1: resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} @@ -8136,7 +8249,6 @@ packages: /escape-html@1.0.3: resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} - dev: false /escape-string-regexp@1.0.5: resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} @@ -8356,7 +8468,6 @@ packages: /etag@1.8.1: resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} engines: {node: '>= 0.6'} - dev: false /eval@0.1.8: resolution: {integrity: sha512-EzV94NYKoO09GLXGjXj9JIlXijVck4ONSr5wiCWDvhsvj5jxSrzTmRU/9C1DyB6uToszLs8aifA6NQ7lEQdvFw==} @@ -8400,21 +8511,6 @@ packages: signal-exit: 3.0.7 strip-final-newline: 2.0.0 - /execa@7.1.1: - resolution: {integrity: sha512-wH0eMf/UXckdUYnO21+HDztteVv05rq2GXksxT4fCGeHkBhw1DROXh40wcjMcRqDOWE7iPJ4n3M7e2+YFP+76Q==} - engines: {node: ^14.18.0 || ^16.14.0 || >=18.0.0} - dependencies: - cross-spawn: 7.0.3 - get-stream: 6.0.1 - human-signals: 4.3.1 - is-stream: 3.0.0 - merge-stream: 2.0.0 - npm-run-path: 5.1.0 - onetime: 6.0.0 - signal-exit: 3.0.7 - strip-final-newline: 3.0.0 - dev: true - /execa@7.2.0: resolution: {integrity: sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA==} engines: {node: ^14.18.0 || ^16.14.0 || >=18.0.0} @@ -8474,7 +8570,6 @@ packages: vary: 1.1.2 transitivePeerDependencies: - supports-color - dev: false /extend-shallow@2.0.1: resolution: {integrity: sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==} @@ -8625,7 +8720,6 @@ packages: unpipe: 1.0.0 transitivePeerDependencies: - supports-color - dev: false /find-cache-dir@3.3.2: resolution: {integrity: sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==} @@ -8764,7 +8858,6 @@ packages: /forwarded@0.2.0: resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} engines: {node: '>= 0.6'} - dev: false /fraction.js@4.2.0: resolution: {integrity: sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==} @@ -8773,7 +8866,6 @@ packages: /fresh@0.5.2: resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} engines: {node: '>= 0.6'} - dev: false /fs-extra@10.1.0: resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} @@ -8835,16 +8927,13 @@ packages: /gensync@1.0.0-beta.2: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} + dev: false /get-caller-file@2.0.5: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} dev: true - /get-func-name@2.0.0: - resolution: {integrity: sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==} - dev: true - /get-intrinsic@1.2.1: resolution: {integrity: sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==} dependencies: @@ -9001,6 +9090,7 @@ packages: /globals@11.12.0: resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} engines: {node: '>=4'} + dev: false /globals@13.20.0: resolution: {integrity: sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==} @@ -9265,10 +9355,6 @@ packages: resolution: {integrity: sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA==} dev: false - /html-escaper@2.0.2: - resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} - dev: true - /html-minifier-terser@6.1.0: resolution: {integrity: sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==} engines: {node: '>=12'} @@ -9351,7 +9437,6 @@ packages: setprototypeof: 1.2.0 statuses: 2.0.1 toidentifier: 1.0.1 - dev: false /http-parser-js@0.5.8: resolution: {integrity: sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==} @@ -9530,7 +9615,6 @@ packages: /ipaddr.js@1.9.1: resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} engines: {node: '>= 0.10'} - dev: false /ipaddr.js@2.0.1: resolution: {integrity: sha512-1qTgH9NG+IIJ4yfKs2e6Pp1bZg8wbDbKHT21HrLIeYBTRLgMYKnMTPAuI3Lcs61nfx5h1xlXnbJtH1kX5/d/ng==} @@ -9863,52 +9947,6 @@ packages: resolution: {integrity: sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==} engines: {node: '>=0.10.0'} - /istanbul-lib-coverage@3.2.0: - resolution: {integrity: sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==} - engines: {node: '>=8'} - dev: true - - /istanbul-lib-instrument@6.0.0: - resolution: {integrity: sha512-x58orMzEVfzPUKqlbLd1hXCnySCxKdDKa6Rjg97CwuLLRI4g3FHTdnExu1OqffVFay6zeMW+T6/DowFLndWnIw==} - engines: {node: '>=10'} - dependencies: - '@babel/core': 7.22.1 - '@babel/parser': 7.22.3 - '@istanbuljs/schema': 0.1.3 - istanbul-lib-coverage: 3.2.0 - semver: 7.5.4 - transitivePeerDependencies: - - supports-color - dev: true - - /istanbul-lib-report@3.0.1: - resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} - engines: {node: '>=10'} - dependencies: - istanbul-lib-coverage: 3.2.0 - make-dir: 4.0.0 - supports-color: 7.2.0 - dev: true - - /istanbul-lib-source-maps@4.0.1: - resolution: {integrity: sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==} - engines: {node: '>=10'} - dependencies: - debug: 4.3.4 - istanbul-lib-coverage: 3.2.0 - source-map: 0.6.1 - transitivePeerDependencies: - - supports-color - dev: true - - /istanbul-reports@3.1.5: - resolution: {integrity: sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==} - engines: {node: '>=8'} - dependencies: - html-escaper: 2.0.2 - istanbul-lib-report: 3.0.1 - dev: true - /iterall@1.3.0: resolution: {integrity: sha512-QZ9qOMdF+QLHxy1QIpUHUU1D5pS2CG2P69LF6L6CPjPYA/XMOmKV3PZpawHoAjHNyB0swdVTRxdYT4tbBbxqwg==} dev: false @@ -9943,15 +9981,9 @@ packages: supports-color: 8.1.1 dev: false - /jiti@1.18.2: - resolution: {integrity: sha512-QAdOptna2NYiSSpv0O/BwoHBSmz4YhpzJHyi+fnMRTXFjp7B8i/YG5Z8IfusxB1ufjcD2Sre1F3R+nX3fvy7gg==} - hasBin: true - dev: false - /jiti@1.19.1: resolution: {integrity: sha512-oVhqoRDaBXf7sjkll95LHVS6Myyyb1zaunVwk4Z0+WPSW4gjS0pl01zYKHScTuyEhQsFxV5L4DR5r+YqSyqyyg==} hasBin: true - dev: false /joi@17.9.2: resolution: {integrity: sha512-Itk/r+V4Dx0V3c7RLFdRh12IOjySm2/WGPMubBT92cQvRfYZhPM2W0hZlctjj72iES8jsRCwp7S/cRmWBnJ4nw==} @@ -9993,6 +10025,7 @@ packages: resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==} engines: {node: '>=4'} hasBin: true + dev: false /json-buffer@3.0.0: resolution: {integrity: sha512-CuUqjv0FUZIdXkHPI8MezCnFCdaTAacej1TZYulLoAg1h/PhwkdXFN4V/gzY4g+fMBCOV2xF+rp7t2XD2ns/NQ==} @@ -10022,9 +10055,11 @@ packages: resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} engines: {node: '>=6'} hasBin: true + dev: false /jsonc-parser@3.2.0: resolution: {integrity: sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==} + dev: true /jsonfile@6.1.0: resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} @@ -10158,11 +10193,6 @@ packages: engines: {node: '>= 12.13.0'} dev: false - /local-pkg@0.4.3: - resolution: {integrity: sha512-SFppqq5p42fe2qcZQqqEOiVRXl+WCP1MdT6k7BDEW1j++sp5fIY+/fdRQitvKgB5BrBcmrs5m/L0v2FrU5MY1g==} - engines: {node: '>=14'} - dev: true - /locate-path@3.0.0: resolution: {integrity: sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==} engines: {node: '>=6'} @@ -10302,12 +10332,6 @@ packages: dependencies: js-tokens: 4.0.0 - /loupe@2.3.6: - resolution: {integrity: sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA==} - dependencies: - get-func-name: 2.0.0 - dev: true - /lower-case@2.0.2: resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==} dependencies: @@ -10328,6 +10352,7 @@ packages: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} dependencies: yallist: 3.1.1 + dev: false /lru-cache@6.0.0: resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} @@ -10340,13 +10365,6 @@ packages: engines: {node: '>=12'} dev: false - /magic-string@0.30.1: - resolution: {integrity: sha512-mbVKXPmS0z0G4XqFDCTllmDQ6coZzn94aMlb0o/A4HEHJCKcanlDZwYJgwnkmgD3jyWhUgj9VsPrfd972yPffA==} - engines: {node: '>=12'} - dependencies: - '@jridgewell/sourcemap-codec': 1.4.15 - dev: true - /make-dir@3.1.0: resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==} engines: {node: '>=8'} @@ -10354,13 +10372,6 @@ packages: semver: 6.3.1 dev: false - /make-dir@4.0.0: - resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} - engines: {node: '>=10'} - dependencies: - semver: 7.5.4 - dev: true - /make-error@1.3.6: resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} requiresBuild: true @@ -10420,7 +10431,6 @@ packages: /media-typer@0.3.0: resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} engines: {node: '>= 0.6'} - dev: false /memfs@3.5.1: resolution: {integrity: sha512-UWbFJKvj5k+nETdteFndTpYxdeTMox/ULeqX5k/dpaQJCCFmj5EeKv3dBcyO2xmkRAx2vppRu5dVG7SOtsGOzA==} @@ -10448,7 +10458,6 @@ packages: /merge-descriptors@1.0.1: resolution: {integrity: sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==} - dev: false /merge-stream@2.0.0: resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} @@ -10464,7 +10473,6 @@ packages: /methods@1.1.2: resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} engines: {node: '>= 0.6'} - dev: false /micromatch@4.0.5: resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} @@ -10499,7 +10507,6 @@ packages: resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} engines: {node: '>=4'} hasBin: true - dev: false /mimic-fn@2.1.0: resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} @@ -10562,6 +10569,7 @@ packages: pathe: 1.1.1 pkg-types: 1.0.3 ufo: 1.1.2 + dev: true /mrmime@1.0.1: resolution: {integrity: sha512-hzzEagAgDyoU1Q6yg5uI+AorQgdvMCur3FcKf7NhMKWsaYg+RnbTyHRa/9IlLF9rf455MOCtcqqrQQ83pPP7Uw==} @@ -10570,7 +10578,6 @@ packages: /ms@2.0.0: resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} - dev: false /ms@2.1.2: resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} @@ -10602,6 +10609,7 @@ packages: resolution: {integrity: sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + dev: false /natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} @@ -10609,7 +10617,6 @@ packages: /negotiator@0.6.3: resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} engines: {node: '>= 0.6'} - dev: false /neo-async@2.6.2: resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} @@ -10779,7 +10786,6 @@ packages: engines: {node: '>= 0.8'} dependencies: ee-first: 1.1.1 - dev: false /on-headers@1.0.2: resolution: {integrity: sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==} @@ -10887,6 +10893,7 @@ packages: engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} dependencies: yocto-queue: 1.0.0 + dev: false /p-locate@3.0.0: resolution: {integrity: sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==} @@ -11002,7 +11009,6 @@ packages: /parseurl@1.3.3: resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} engines: {node: '>= 0.8'} - dev: false /pascal-case@3.1.2: resolution: {integrity: sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==} @@ -11042,7 +11048,6 @@ packages: /path-to-regexp@0.1.7: resolution: {integrity: sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==} - dev: false /path-to-regexp@1.8.0: resolution: {integrity: sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==} @@ -11060,9 +11065,6 @@ packages: /pathe@1.1.1: resolution: {integrity: sha512-d+RQGp0MAYTIaDBIMmOfMwz3E+LOZnxx1HZd5R18mmCZY0QBlK0LDZfPc8FW8Ed2DlvsuE6PRjroDY+wg4+j/Q==} - - /pathval@1.1.1: - resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==} dev: true /picocolors@1.0.0: @@ -11096,6 +11098,7 @@ packages: jsonc-parser: 3.2.0 mlly: 1.4.0 pathe: 1.1.1 + dev: true /pkg-up@3.1.0: resolution: {integrity: sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==} @@ -11202,7 +11205,7 @@ packages: optional: true dependencies: lilconfig: 2.1.0 - ts-node: 10.9.1(@types/node@20.4.7)(typescript@5.2.2) + ts-node: 10.9.1(@types/node@18.17.12)(typescript@5.2.2) yaml: 2.3.1 dev: true @@ -11214,7 +11217,7 @@ packages: webpack: ^5.0.0 dependencies: cosmiconfig: 8.1.3 - jiti: 1.18.2 + jiti: 1.19.1 klona: 2.0.6 postcss: 8.4.26 semver: 7.5.4 @@ -11533,6 +11536,7 @@ packages: nanoid: 3.3.6 picocolors: 1.0.0 source-map-js: 1.0.2 + dev: false /prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} @@ -11579,15 +11583,6 @@ packages: renderkid: 3.0.0 dev: false - /pretty-format@29.6.1: - resolution: {integrity: sha512-7jRj+yXO0W7e4/tSJKoR7HRIHLPPjtNaUGG2xxKQnGvPNRkgWcQ0AZX6P4KBRJN4FcTBWb3sa7DVUJmocYuoog==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/schemas': 29.6.0 - ansi-styles: 5.2.0 - react-is: 18.2.0 - dev: true - /pretty-time@1.1.0: resolution: {integrity: sha512-28iF6xPQrP8Oa6uxE6a1biz+lWeTOAPKggvjB8HAs6nVMKZwf5bG++632Dx614hIWgUPkgivRfG+a8uAXGTIbA==} engines: {node: '>=4'} @@ -11651,7 +11646,6 @@ packages: dependencies: forwarded: 0.2.0 ipaddr.js: 1.9.1 - dev: false /pump@3.0.0: resolution: {integrity: sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==} @@ -11684,7 +11678,6 @@ packages: engines: {node: '>=0.6'} dependencies: side-channel: 1.0.4 - dev: false /queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} @@ -11713,7 +11706,6 @@ packages: /range-parser@1.2.1: resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} engines: {node: '>= 0.6'} - dev: false /raw-body@2.5.1: resolution: {integrity: sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==} @@ -11723,7 +11715,6 @@ packages: http-errors: 2.0.0 iconv-lite: 0.4.24 unpipe: 1.0.0 - dev: false /raw-body@2.5.2: resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==} @@ -11733,7 +11724,6 @@ packages: http-errors: 2.0.0 iconv-lite: 0.4.24 unpipe: 1.0.0 - dev: false /rc@1.2.8: resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} @@ -11830,10 +11820,6 @@ packages: /react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} - /react-is@18.2.0: - resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==} - dev: true - /react-json-view@1.21.3(react-dom@17.0.2)(react@17.0.2): resolution: {integrity: sha512-13p8IREj9/x/Ye4WI/JpjhoIwuzEgUAtgJZNBJckfzJt1qyh24BdTm6UQNGnyTq9dapQdrqvquZTo3dz1X6Cjw==} peerDependencies: @@ -12410,7 +12396,6 @@ packages: statuses: 2.0.1 transitivePeerDependencies: - supports-color - dev: false /serialize-javascript@6.0.1: resolution: {integrity: sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==} @@ -12455,7 +12440,6 @@ packages: send: 0.18.0 transitivePeerDependencies: - supports-color - dev: false /setimmediate@1.0.5: resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==} @@ -12467,7 +12451,6 @@ packages: /setprototypeof@1.2.0: resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} - dev: false /sha.js@2.4.11: resolution: {integrity: sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==} @@ -12517,10 +12500,6 @@ packages: get-intrinsic: 1.2.1 object-inspect: 1.12.3 - /siginfo@2.0.0: - resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} - dev: true - /signal-exit@3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} @@ -12611,6 +12590,7 @@ packages: /source-map-js@1.0.2: resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} engines: {node: '>=0.10.0'} + dev: false /source-map-support@0.5.21: resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} @@ -12705,10 +12685,6 @@ packages: resolution: {integrity: sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==} dev: false - /stackback@0.0.2: - resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} - dev: true - /state-toggle@1.0.3: resolution: {integrity: sha512-d/5Z4/2iiCnHw6Xzghyhb+GcmF89bxwgXG60wjIiZaxnymbyOmI8Hk4VqHXiVVp6u2ysaskFfXg3ekCj4WNftQ==} dev: false @@ -12721,10 +12697,10 @@ packages: /statuses@2.0.1: resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} engines: {node: '>= 0.8'} - dev: false /std-env@3.3.3: resolution: {integrity: sha512-Rz6yejtVyWnVjC1RFvNmYL10kgjC49EOghxWn0RFqlCHGFpQx+Xe7yW3I4ceK1SGrWIGMjD5Kbue8W/udkbMJg==} + dev: false /string-argv@0.3.2: resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==} @@ -12848,12 +12824,6 @@ packages: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} - /strip-literal@1.0.1: - resolution: {integrity: sha512-QZTsipNpa2Ppr6v1AmJHESqJ3Uz247MUS0OjrnnZjFAvEoWqxuyFuXn2xLgMtRnijJShAa1HL0gtJyUs7u7n3Q==} - dependencies: - acorn: 8.10.0 - dev: true - /strnum@1.0.5: resolution: {integrity: sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==} dev: false @@ -12978,10 +12948,6 @@ packages: resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} engines: {node: '>=6'} - /terminal-columns@1.4.1: - resolution: {integrity: sha512-IKVL/itiMy947XWVv4IHV7a0KQXvKjj4ptbi7Ew9MPMcOLzkiQeyx3Gyvh62hKrfJ0RZc4M1nbhzjNM39Kyujw==} - dev: false - /terser-webpack-plugin@5.3.9(webpack@5.84.1): resolution: {integrity: sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA==} engines: {node: '>= 10.13.0'} @@ -13015,15 +12981,6 @@ packages: commander: 2.20.3 source-map-support: 0.5.21 - /test-exclude@6.0.0: - resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} - engines: {node: '>=8'} - dependencies: - '@istanbuljs/schema': 0.1.3 - glob: 7.2.3 - minimatch: 3.1.2 - dev: true - /text-extensions@1.9.0: resolution: {integrity: sha512-wiBrwC1EhBelW12Zy26JeOUkQ5mRu+5o8rpsJk5+2t+Y5vE7e842qtZDQ2g1NpX/29HdyFeJ4nSIhI47ENSxlQ==} engines: {node: '>=0.10'} @@ -13071,20 +13028,6 @@ packages: resolution: {integrity: sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==} dev: false - /tinybench@2.5.0: - resolution: {integrity: sha512-kRwSG8Zx4tjF9ZiyH4bhaebu+EDz1BOx9hOigYHlUW4xxI/wKIUQUqo018UlU4ar6ATPBsaMrdbKZ+tmPdohFA==} - dev: true - - /tinypool@0.7.0: - resolution: {integrity: sha512-zSYNUlYSMhJ6Zdou4cJwo/p7w5nmAH17GRfU/ui3ctvjXFErXXkruT4MWW6poDeXgCaIBlGLrfU6TbTXxyGMww==} - engines: {node: '>=14.0.0'} - dev: true - - /tinyspy@2.1.1: - resolution: {integrity: sha512-XPJL2uSzcOyBMky6OFrusqWlzfFrXtE0hPuMgW8A2HmaqrPo4ZQHRN/V0QXN3FSjKxpsbRrFc5LI7KOwBsT1/w==} - engines: {node: '>=14.0.0'} - dev: true - /titleize@3.0.0: resolution: {integrity: sha512-KxVu8EYHDPBdUYdKZdKtU2aj2XfEx9AfjXxE/Aj0vT06w2icA09Vus1rh6eSu1y01akYg6BjIK/hxyLJINoMLQ==} engines: {node: '>=12'} @@ -13100,6 +13043,7 @@ packages: /to-fast-properties@2.0.0: resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} engines: {node: '>=4'} + dev: false /to-readable-stream@1.0.0: resolution: {integrity: sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==} @@ -13115,7 +13059,6 @@ packages: /toidentifier@1.0.1: resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} engines: {node: '>=0.6'} - dev: false /totalist@1.1.0: resolution: {integrity: sha512-gduQwd1rOdDMGxFG1gEvhV88Oirdo2p+KjoYFU7k2g+i7n6AFFbDQ5kMPUsW0pNbfQsB/cwXvT1i4Bue0s9g5g==} @@ -13175,7 +13118,7 @@ packages: resolution: {integrity: sha512-yUeWbFBDiwPodNqrqpvQpGWheL6PvNu2/pVAb9yy2vzdkkflCgwVA4U2akByPCXzYTum3/5/nB92yKuiLpSo/Q==} dev: true - /ts-node@10.9.1(@types/node@20.4.7)(typescript@5.2.2): + /ts-node@10.9.1(@types/node@18.17.12)(typescript@5.2.2): resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==} hasBin: true requiresBuild: true @@ -13195,7 +13138,7 @@ packages: '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.4 - '@types/node': 20.4.7 + '@types/node': 18.17.12 acorn: 8.10.0 acorn-walk: 8.2.0 arg: 4.1.3 @@ -13336,11 +13279,6 @@ packages: dependencies: prelude-ls: 1.2.1 - /type-detect@4.0.8: - resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} - engines: {node: '>=4'} - dev: true - /type-fest@0.18.1: resolution: {integrity: sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==} engines: {node: '>=10'} @@ -13375,17 +13313,12 @@ packages: engines: {node: '>=12.20'} dev: false - /type-flag@3.0.0: - resolution: {integrity: sha512-3YaYwMseXCAhBB14RXW5cRQfJQlEknS6i4C8fCfeUdS3ihG9EdccdR9kt3vP73ZdeTGmPb4bZtkDn5XMIn1DLA==} - dev: false - /type-is@1.6.18: resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} engines: {node: '>= 0.6'} dependencies: media-typer: 0.3.0 mime-types: 2.1.35 - dev: false /typed-array-length@1.0.4: resolution: {integrity: sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==} @@ -13412,6 +13345,7 @@ packages: /ufo@1.1.2: resolution: {integrity: sha512-TrY6DsjTQQgyS3E3dBaOXf0TpPD8u9FVrVYmKVegJuFw51n/YB9XPt+U6ydzFG5ZIN7+DIjPbNmXoBj9esYhgQ==} + dev: true /unbox-primitive@1.0.2: resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} @@ -13429,7 +13363,7 @@ packages: defu: 6.1.2 jiti: 1.19.1 mlly: 1.4.0 - dev: false + dev: true /unherit@1.1.3: resolution: {integrity: sha512-Ft16BJcnapDKp0+J/rqFC3Rrk6Y/Ng4nzsC028k2jdDII/rdZ7Wd3pPT/6+vIIxRagwRc9K0IUX0Ra4fKvw+WQ==} @@ -13555,7 +13489,6 @@ packages: /unpipe@1.0.0: resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} engines: {node: '>= 0.8'} - dev: false /untildify@4.0.0: resolution: {integrity: sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==} @@ -13676,7 +13609,6 @@ packages: /utils-merge@1.0.1: resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} engines: {node: '>= 0.4.0'} - dev: false /uuid@8.3.2: resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} @@ -13712,7 +13644,6 @@ packages: /vary@1.1.2: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} - dev: false /vfile-location@3.2.0: resolution: {integrity: sha512-aLEIZKv/oxuCDZ8lkJGhuhztf/BW4M+iHdCwglA/eWc+vtuRFJj8EtgceYFX4LRjOhCAAiNHsKGssC6onJ+jbA==} @@ -13734,129 +13665,6 @@ packages: vfile-message: 2.0.4 dev: false - /vite-node@0.34.3(@types/node@18.17.12): - resolution: {integrity: sha512-+0TzJf1g0tYXj6tR2vEyiA42OPq68QkRZCu/ERSo2PtsDJfBpDyEfuKbRvLmZqi/CgC7SCBtyC+WjTGNMRIaig==} - engines: {node: '>=v14.18.0'} - hasBin: true - dependencies: - cac: 6.7.14 - debug: 4.3.4 - mlly: 1.4.0 - pathe: 1.1.1 - picocolors: 1.0.0 - vite: 4.4.4(@types/node@18.17.12) - transitivePeerDependencies: - - '@types/node' - - less - - lightningcss - - sass - - stylus - - sugarss - - supports-color - - terser - dev: true - - /vite@4.4.4(@types/node@18.17.12): - resolution: {integrity: sha512-4mvsTxjkveWrKDJI70QmelfVqTm+ihFAb6+xf4sjEU2TmUCTlVX87tmg/QooPEMQb/lM9qGHT99ebqPziEd3wg==} - engines: {node: ^14.18.0 || >=16.0.0} - hasBin: true - peerDependencies: - '@types/node': '>= 14' - less: '*' - lightningcss: ^1.21.0 - sass: '*' - stylus: '*' - sugarss: '*' - terser: ^5.4.0 - peerDependenciesMeta: - '@types/node': - optional: true - less: - optional: true - lightningcss: - optional: true - sass: - optional: true - stylus: - optional: true - sugarss: - optional: true - terser: - optional: true - dependencies: - '@types/node': 18.17.12 - esbuild: 0.18.14 - postcss: 8.4.26 - rollup: 3.26.3 - optionalDependencies: - fsevents: 2.3.2 - dev: true - - /vitest@0.34.3: - resolution: {integrity: sha512-7+VA5Iw4S3USYk+qwPxHl8plCMhA5rtfwMjgoQXMT7rO5ldWcdsdo3U1QD289JgglGK4WeOzgoLTsGFu6VISyQ==} - engines: {node: '>=v14.18.0'} - hasBin: true - peerDependencies: - '@edge-runtime/vm': '*' - '@vitest/browser': '*' - '@vitest/ui': '*' - happy-dom: '*' - jsdom: '*' - playwright: '*' - safaridriver: '*' - webdriverio: '*' - peerDependenciesMeta: - '@edge-runtime/vm': - optional: true - '@vitest/browser': - optional: true - '@vitest/ui': - optional: true - happy-dom: - optional: true - jsdom: - optional: true - playwright: - optional: true - safaridriver: - optional: true - webdriverio: - optional: true - dependencies: - '@types/chai': 4.3.5 - '@types/chai-subset': 1.3.3 - '@types/node': 18.17.12 - '@vitest/expect': 0.34.3 - '@vitest/runner': 0.34.3 - '@vitest/snapshot': 0.34.3 - '@vitest/spy': 0.34.3 - '@vitest/utils': 0.34.3 - acorn: 8.10.0 - acorn-walk: 8.2.0 - cac: 6.7.14 - chai: 4.3.7 - debug: 4.3.4 - local-pkg: 0.4.3 - magic-string: 0.30.1 - pathe: 1.1.1 - picocolors: 1.0.0 - std-env: 3.3.3 - strip-literal: 1.0.1 - tinybench: 2.5.0 - tinypool: 0.7.0 - vite: 4.4.4(@types/node@18.17.12) - vite-node: 0.34.3(@types/node@18.17.12) - why-is-node-running: 2.2.2 - transitivePeerDependencies: - - less - - lightningcss - - sass - - stylus - - sugarss - - supports-color - - terser - dev: true - /wait-on@6.0.1: resolution: {integrity: sha512-zht+KASY3usTY5u2LgaNqn/Cd8MukxLGjdcZxT2ns5QzDmTFc4XoWBgC+C/na+sMRZTuVygQoMYwdcVjHnYIVw==} engines: {node: '>=10.0.0'} @@ -14117,15 +13925,6 @@ packages: dependencies: isexe: 2.0.0 - /why-is-node-running@2.2.2: - resolution: {integrity: sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==} - engines: {node: '>=8'} - hasBin: true - dependencies: - siginfo: 2.0.0 - stackback: 0.0.2 - dev: true - /widest-line@3.1.0: resolution: {integrity: sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==} engines: {node: '>=8'} @@ -14252,6 +14051,7 @@ packages: /yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + dev: false /yallist@4.0.0: resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} @@ -14301,6 +14101,7 @@ packages: /yocto-queue@1.0.0: resolution: {integrity: sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==} engines: {node: '>=12.20'} + dev: false /zen-observable-ts@1.1.0: resolution: {integrity: sha512-1h4zlLSqI2cRLPJUHJFL8bCWHhkpuXkF+dbGkRaWjgDIG26DmzyshUMrdV/rL3UnR+mhaX4fRq8LPouq0MYYIA==} diff --git a/renovate.json b/renovate.json index 9ef41de7..fc0a600d 100644 --- a/renovate.json +++ b/renovate.json @@ -26,12 +26,6 @@ "matchPackageNames": ["@mdx-js/react", "clsx", "prism-react-renderer", "prismjs"], "groupName": "Docusaurus v2: other dependencies", "allowedVersions": "<2.0.0" - }, - { - "matchDepNames": ["ant-stack"], - "matchDepPatterns": ["^@libs", "^@services"], - "groupName": "Internal packages", - "enabled": false } ], "pin": { "commitMessageSuffix": "[skip ci]" } diff --git a/services/cdk/cdk.json b/services/cdk/cdk.json deleted file mode 100644 index 3105a0a2..00000000 --- a/services/cdk/cdk.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "app": "npx tsx src/app.ts", - "context": { - "@aws-cdk/aws-lambda:recognizeLayerVersion": true, - "@aws-cdk/core:checkSecretUsage": true, - "@aws-cdk/core:target-partitions": ["aws", "aws-cn"], - "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, - "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true, - "@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true, - "@aws-cdk/aws-iam:minimizePolicies": true, - "@aws-cdk/core:validateSnapshotRemovalPolicy": true, - "@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true, - "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, - "@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true, - "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, - "@aws-cdk/core:enablePartitionLiterals": true, - "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, - "@aws-cdk/aws-iam:standardizedServicePrincipals": true, - "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true - } -} diff --git a/services/cdk/package.json b/services/cdk/package.json deleted file mode 100644 index 75c51934..00000000 --- a/services/cdk/package.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "name": "@services/cdk", - "version": "0.0.0", - "private": true, - "type": "module", - "scripts": { - "deploy": "cdk deploy --all --require-approval never", - "destroy": "cdk destroy --all --force" - }, - "dependencies": { - "@services/registrar-scraper": "workspace:*", - "@services/websoc-proxy": "workspace:*", - "@services/websoc-scraper-v2": "workspace:*", - "dotenv": "16.3.1" - }, - "devDependencies": { - "@types/babel__traverse": "7.20.1", - "@types/node": "18.17.12", - "aws-cdk": "2.93.0", - "aws-cdk-lib": "2.93.0", - "constructs": "10.2.69", - "peterportal-api-next-types": "workspace:*", - "source-map-support": "0.5.21", - "tsx": "3.12.7", - "typescript": "5.2.2" - } -} diff --git a/services/cdk/src/app.ts b/services/cdk/src/app.ts deleted file mode 100644 index 0c792e1b..00000000 --- a/services/cdk/src/app.ts +++ /dev/null @@ -1,14 +0,0 @@ -import "dotenv/config"; - -import { App } from "aws-cdk-lib"; -import type { StackProps } from "aws-cdk-lib"; - -import { ServicesStack } from "./stack"; - -const app = new App({ autoSynth: true }); -const props: StackProps = { - env: { region: "us-east-1" }, - terminationProtection: true, -}; - -new ServicesStack(app, "peterportal-api-next-services-prod", props); diff --git a/services/cdk/tsconfig.json b/services/cdk/tsconfig.json deleted file mode 100644 index 339aedc4..00000000 --- a/services/cdk/tsconfig.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "exclude": ["cdk.out/", "node_modules/"], - "include": ["./**/*"] -} diff --git a/services/websoc-proxy/package.json b/services/websoc-proxy/package.json index 62374641..1755cb04 100644 --- a/services/websoc-proxy/package.json +++ b/services/websoc-proxy/package.json @@ -10,13 +10,14 @@ "build": "node build.mjs" }, "dependencies": { - "@libs/websoc-api-next": "workspace:*", - "ant-stack": "workspace:*" + "@libs/lambda": "workspace:^", + "@libs/uc-irvine-api": "workspace:^", + "@libs/websoc-utils": "workspace:^" }, "devDependencies": { + "@peterportal-api/types": "workspace:^", "@types/aws-lambda": "8.10.119", "@types/node": "18.17.12", - "esbuild": "0.19.2", - "peterportal-api-next-types": "workspace:*" + "esbuild": "0.19.2" } } diff --git a/services/websoc-proxy/src/index.ts b/services/websoc-proxy/src/index.ts index 73630368..51c9f473 100644 --- a/services/websoc-proxy/src/index.ts +++ b/services/websoc-proxy/src/index.ts @@ -1,7 +1,7 @@ -import { callWebSocAPI, getDepts, getTerms } from "@libs/websoc-api-next"; -import type { WebsocAPIResponse, WebsocAPIOptions } from "@libs/websoc-api-next"; +import { createErrorResult, createOKResult, logger } from "@libs/lambda"; +import { callWebSocAPI, getDepts, getTerms } from "@libs/uc-irvine-api/websoc"; +import type { WebsocAPIResponse, WebsocAPIOptions } from "@libs/uc-irvine-api/websoc"; import { combineAndNormalizeResponses, fulfilled, sleep, sortResponse } from "@libs/websoc-utils"; -import { createErrorResult, createOKResult, logger } from "ant-stack"; import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from "aws-lambda"; export const handler = async ( diff --git a/services/websoc-scraper-v2/index.ts b/services/websoc-scraper-v2/index.ts index fcbae322..989f3f19 100644 --- a/services/websoc-scraper-v2/index.ts +++ b/services/websoc-scraper-v2/index.ts @@ -1,5 +1,5 @@ import { PrismaClient } from "@libs/db"; -import { getTermDateData } from "@libs/registrar-api"; +import { getTermDateData } from "@libs/uc-irvine-api/registrar"; import type { GE, Quarter, @@ -10,8 +10,14 @@ import type { WebsocSchool, WebsocSection, WebsocSectionMeeting, -} from "@libs/websoc-api-next"; -import { callWebSocAPI, getDepts, getTerms, geCodes, sectionTypes } from "@libs/websoc-api-next"; +} from "@libs/uc-irvine-api/websoc"; +import { + callWebSocAPI, + getDepts, + getTerms, + geCodes, + sectionTypes, +} from "@libs/uc-irvine-api/websoc"; import { createLogger, format, transports } from "winston"; /** diff --git a/services/websoc-scraper-v2/package.json b/services/websoc-scraper-v2/package.json index 8e35ab0a..89b623df 100644 --- a/services/websoc-scraper-v2/package.json +++ b/services/websoc-scraper-v2/package.json @@ -8,10 +8,9 @@ "build": "node build.mjs" }, "dependencies": { - "@libs/db": "workspace:*", - "@libs/registrar-api": "workspace:*", - "@libs/websoc-api-next": "workspace:*", - "peterportal-api-next-types": "workspace:*", + "@libs/db": "workspace:^", + "@libs/uc-irvine-api": "workspace:^", + "@peterportal-api/types": "workspace:^", "winston": "3.10.0" }, "devDependencies": { diff --git a/tools/cdk/package.json b/tools/cdk/package.json new file mode 100644 index 00000000..0bd75381 --- /dev/null +++ b/tools/cdk/package.json @@ -0,0 +1,32 @@ +{ + "name": "@tools/cdk", + "version": "0.0.0", + "private": true, + "description": "CDK deployment configurations for PeterPortal API", + "keywords": [], + "homepage": "https://github.com/icssc/peterportal-api-next", + "bugs": { + "url": "https://github.com/icssc/peterportal-api-next/issues" + }, + "repository": { + "type": "git", + "url": "https://github.com/icssc/peterportal-api-next", + "directory": "tools/cdk" + }, + "license": "MIT", + "type": "module", + "scripts": { + "docs": "cdk --app 'npx tsx src/app/docs.ts' --require-approval never", + "services": "cdk --app 'npx tsx src/app/services.ts' --require-approval never" + }, + "dependencies": { + "@aws-sdk/client-cloudformation": "^3.408.0", + "@smithy/util-waiter": "^2.0.6", + "aws-cdk-lib": "2.93.0", + "constructs": "10.2.69" + }, + "devDependencies": { + "aws-cdk": "2.93.0", + "tsx": "3.12.7" + } +} diff --git a/tools/cdk/src/app/docs.ts b/tools/cdk/src/app/docs.ts new file mode 100644 index 00000000..72fb601b --- /dev/null +++ b/tools/cdk/src/app/docs.ts @@ -0,0 +1,49 @@ +import "dotenv/config"; + +import { App } from "aws-cdk-lib"; + +import { DocsStack } from "../stacks/docs"; +import { waitForStackIdle } from "../wait-for-stack-idle"; + +function getStage() { + switch (process.env.NODE_ENV) { + case "production": { + return "prod"; + } + + case "staging": { + if (!process.env.PR_NUM) { + throw new Error("Running in staging environment but no PR number specified. Stop."); + } + return `staging-${process.env.PR_NUM}`; + } + + case "development": { + throw new Error("Cannot deploy stack in development environment. Stop."); + } + + default: { + throw new Error("Invalid environment specified. Stop."); + } + } +} + +async function main() { + const stage = getStage(); + + const stackName = `peterportal-api-next-docs-${stage}`; + + await waitForStackIdle(stackName); + + const app = new App({ autoSynth: true }); + + new DocsStack(app, stackName, { + stage, + env: { + region: "us-east-1", + }, + terminationProtection: /*stage === "prod"*/ false, + }); +} + +main(); diff --git a/tools/cdk/src/app/services.ts b/tools/cdk/src/app/services.ts new file mode 100644 index 00000000..1201f043 --- /dev/null +++ b/tools/cdk/src/app/services.ts @@ -0,0 +1,23 @@ +import "dotenv/config"; + +import { App } from "aws-cdk-lib"; + +import { ServicesStack } from "../stacks/services"; +import { waitForStackIdle } from "../wait-for-stack-idle"; + +async function main() { + const stackName = "peterportal-api-next-services-prod"; + + await waitForStackIdle(stackName); + + const app = new App({ autoSynth: true }); + + new ServicesStack(app, stackName, { + env: { + region: "us-east-1", + }, + terminationProtection: true, + }); +} + +main(); diff --git a/services/cdk/src/constructs/WebsocProxy.ts b/tools/cdk/src/constructs/WebsocProxy.ts similarity index 99% rename from services/cdk/src/constructs/WebsocProxy.ts rename to tools/cdk/src/constructs/WebsocProxy.ts index 76d59191..87dc9dbf 100644 --- a/services/cdk/src/constructs/WebsocProxy.ts +++ b/tools/cdk/src/constructs/WebsocProxy.ts @@ -10,12 +10,16 @@ import { Construct } from "constructs"; export class WebsocProxy extends Construct { constructor(scope: Construct, id: string) { super(scope, id); + const ruleName = `${id}-rule`; + const rule = new Rule(this, ruleName, { ruleName, schedule: Schedule.rate(Duration.minutes(5)), }); + const functionName = `${id}-function`; + rule.addTarget( new LambdaFunction( new Function(this, functionName, { diff --git a/services/cdk/src/constructs/WebsocScraperV2.ts b/tools/cdk/src/constructs/WebsocScraperV2.ts similarity index 100% rename from services/cdk/src/constructs/WebsocScraperV2.ts rename to tools/cdk/src/constructs/WebsocScraperV2.ts diff --git a/apps/docs/cdk/DocsStack.ts b/tools/cdk/src/stacks/docs.ts similarity index 66% rename from apps/docs/cdk/DocsStack.ts rename to tools/cdk/src/stacks/docs.ts index 9ebf4c34..e38b0c49 100644 --- a/apps/docs/cdk/DocsStack.ts +++ b/tools/cdk/src/stacks/docs.ts @@ -1,5 +1,5 @@ -import path from "path"; -import { fileURLToPath } from "url"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; import { RemovalPolicy, Stack, StackProps } from "aws-cdk-lib"; import { Certificate } from "aws-cdk-lib/aws-certificatemanager"; @@ -17,40 +17,28 @@ import { Bucket } from "aws-cdk-lib/aws-s3"; import { BucketDeployment, Source } from "aws-cdk-lib/aws-s3-deployment"; import { Construct } from "constructs"; -export class DocsStack extends Stack { - constructor(scope: Construct, id: string) { - if (!process.env.CERTIFICATE_ARN) throw new Error("Certificate ARN not provided. Stop."); - if (!process.env.HOSTED_ZONE_ID) throw new Error("Hosted Zone ID not provided. Stop."); +export interface DocsStackProps extends StackProps { + stage?: string; +} - let stage: string; - switch (process.env.NODE_ENV) { - case "production": - stage = "prod"; - break; - case "staging": - if (!process.env.PR_NUM) - throw new Error("Running in staging environment but no PR number specified. Stop."); - stage = `staging-${process.env.PR_NUM}`; - break; - case "development": - throw new Error("Cannot deploy stack in development environment. Stop."); - default: - throw new Error("Invalid environment specified. Stop."); +export class DocsStack extends Stack { + constructor(scope: Construct, id: string, props: DocsStackProps = {}) { + if (!process.env.CERTIFICATE_ARN) { + throw new Error("Certificate ARN not provided. Stop."); } - const props: StackProps = { - env: { region: "us-east-1" }, - terminationProtection: /*stage === "prod"*/ false, - }; + if (!process.env.HOSTED_ZONE_ID) { + throw new Error("Hosted Zone ID not provided. Stop."); + } - super(scope, `${id}-${stage}`, props); + super(scope, id, props); const certificateArn = process.env.CERTIFICATE_ARN; const hostedZoneId = process.env.HOSTED_ZONE_ID; - const recordName = `${stage === "prod" ? "" : `${stage}-`}docs.api-next`; + const recordName = `${props.stage === "prod" ? "" : `${props.stage}-`}docs.api-next`; const zoneName = "peterportal.org"; - const destinationBucket = new Bucket(this, `peterportal-api-next-docs-bucket-${stage}`, { + const destinationBucket = new Bucket(this, `peterportal-api-next-docs-bucket-${props.stage}`, { bucketName: `${recordName}.${zoneName}`, removalPolicy: RemovalPolicy.DESTROY, autoDeleteObjects: true, @@ -88,7 +76,7 @@ export class DocsStack extends Stack { ], }); - new ARecord(this, `peterportal-api-next-docs-a-record-${stage}`, { + new ARecord(this, `peterportal-api-next-docs-a-record-${props.stage}`, { zone: HostedZone.fromHostedZoneAttributes(this, "peterportal-hosted-zone", { zoneName, hostedZoneId, @@ -98,7 +86,11 @@ export class DocsStack extends Stack { }); new BucketDeployment(this, "bucket-deployment", { - sources: [Source.asset(path.join(path.dirname(fileURLToPath(import.meta.url)), `../build`))], + sources: [ + Source.asset( + path.join(path.dirname(fileURLToPath(import.meta.url)), "../../../../apps/docs/build"), + ), + ], destinationBucket, distribution, distributionPaths: ["/*"], diff --git a/services/cdk/src/stack.ts b/tools/cdk/src/stacks/services.ts similarity index 81% rename from services/cdk/src/stack.ts rename to tools/cdk/src/stacks/services.ts index db6ad54f..6db45260 100644 --- a/services/cdk/src/stack.ts +++ b/tools/cdk/src/stacks/services.ts @@ -3,14 +3,17 @@ import type { StackProps } from "aws-cdk-lib"; import { SubnetType, Vpc } from "aws-cdk-lib/aws-ec2"; import { Construct } from "constructs"; -import { WebsocProxy } from "./constructs/WebsocProxy"; -import { WebsocScraperV2 } from "./constructs/WebsocScraperV2"; +import { WebsocProxy } from "../constructs/WebsocProxy"; +import { WebsocScraperV2 } from "../constructs/WebsocScraperV2"; export class ServicesStack extends Stack { constructor(scope: Construct, id: string, props: StackProps) { - if (process.env.NODE_ENV !== "production") + if (process.env.NODE_ENV !== "production") { throw new Error("Cannot deploy this stack outside of production. Stop."); + } + super(scope, id, props); + const vpc = new Vpc(this, `${id}-vpc`, { enableDnsHostnames: true, enableDnsSupport: true, @@ -23,7 +26,9 @@ export class ServicesStack extends Stack { }, ], }); + new WebsocProxy(this, `${id}-websoc-proxy`); + new WebsocScraperV2(this, `${id}-websoc-scraper-v2`, vpc); } } diff --git a/tools/cdk/src/wait-for-stack-idle.ts b/tools/cdk/src/wait-for-stack-idle.ts new file mode 100644 index 00000000..3df4cce5 --- /dev/null +++ b/tools/cdk/src/wait-for-stack-idle.ts @@ -0,0 +1,50 @@ +import { + CloudFormationClient, + DescribeStacksCommand, + StackStatus, + waitUntilStackCreateComplete, + waitUntilStackDeleteComplete, + waitUntilStackUpdateComplete, +} from "@aws-sdk/client-cloudformation"; +import { WaiterConfiguration, WaiterResult } from "@smithy/util-waiter"; + +/** + * Wait for existing CloudFormation stack to be in an idle state. + */ +export async function waitForStackIdle( + stackName: string, + cloudFormationClient: CloudFormationClient = new CloudFormationClient({}), +): Promise { + const stackCommand = new DescribeStacksCommand({ StackName: stackName }); + + try { + const stackInfo = await cloudFormationClient.send(stackCommand); + + const stackStatus = stackInfo.Stacks?.[0]?.StackStatus; + + if (!stackStatus) { + return; + } + + const params: WaiterConfiguration = { + client: cloudFormationClient, + maxWaitTime: 1800, + }; + + switch (stackStatus) { + case StackStatus.CREATE_IN_PROGRESS: + return await waitUntilStackCreateComplete(params, { StackName: stackName }); + + case StackStatus.UPDATE_IN_PROGRESS: + return await waitUntilStackUpdateComplete(params, { StackName: stackName }); + + case StackStatus.DELETE_IN_PROGRESS: + return await waitUntilStackDeleteComplete(params, { StackName: stackName }); + + default: + return; + } + } catch { + return; + } +} diff --git a/tools/cdk/tsconfig.json b/tools/cdk/tsconfig.json new file mode 100644 index 00000000..dc787c60 --- /dev/null +++ b/tools/cdk/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": ["../../tsconfig.json"] +} diff --git a/tools/grades-updater/package.json b/tools/grades-updater/package.json index bcff0e13..216dbf97 100644 --- a/tools/grades-updater/package.json +++ b/tools/grades-updater/package.json @@ -1,5 +1,5 @@ { - "name": "grades-updater", + "name": "@tools/grades-updater", "version": "0.0.0", "private": true, "type": "module", @@ -9,16 +9,18 @@ "upload": "dotenv -e ../../.env.grades -- tsx src/upload-data.ts" }, "dependencies": { - "@libs/db": "workspace:*", + "@libs/db": "workspace:^", + "@libs/lambda": "workspace:^", + "@libs/uc-irvine-api": "workspace:^", "csv-parse": "5.5.0", "csv-stringify": "6.4.2", "winston": "3.10.0", "winston-transport": "4.5.0" }, "devDependencies": { + "@peterportal-api/types": "workspace:^", "@types/node": "18.17.12", "dotenv-cli": "7.3.0", - "peterportal-api-next-types": "workspace:*", "tsx": "3.12.7" } } diff --git a/tools/grades-updater/src/lib.ts b/tools/grades-updater/src/lib.ts index 55c17c4c..caaa602e 100644 --- a/tools/grades-updater/src/lib.ts +++ b/tools/grades-updater/src/lib.ts @@ -1,7 +1,7 @@ import { dirname } from "path"; import { fileURLToPath } from "url"; -import type { Quarter } from "peterportal-api-next-types"; +import type { Quarter } from "@peterportal-api/types"; import winston, { type Logger } from "winston"; import type Transport from "winston-transport"; diff --git a/tools/grades-updater/src/populate-ge.ts b/tools/grades-updater/src/populate-ge.ts index e6b2ff7d..8d3bfd52 100644 --- a/tools/grades-updater/src/populate-ge.ts +++ b/tools/grades-updater/src/populate-ge.ts @@ -1,5 +1,5 @@ import { PrismaClient } from "@libs/db"; -import { callWebSocAPI, GE, geCodes, Quarter } from "@libs/websoc-api-next"; +import { callWebSocAPI, GE, geCodes, Quarter } from "@libs/uc-irvine-api/websoc"; const prisma = new PrismaClient({ log: [ diff --git a/tools/grades-updater/src/sanitize-data.ts b/tools/grades-updater/src/sanitize-data.ts index f2f58cf6..cc9278aa 100644 --- a/tools/grades-updater/src/sanitize-data.ts +++ b/tools/grades-updater/src/sanitize-data.ts @@ -1,13 +1,13 @@ import fs from "fs"; -import { EOL } from "os"; import { basename, resolve } from "node:path"; +import { EOL } from "os"; -import { callWebSocAPI } from "@libs/websoc-api-next"; -import type { WebsocAPIResponse, WebsocSection } from "@libs/websoc-api-next"; +import { callWebSocAPI } from "@libs/uc-irvine-api/websoc"; +import type { WebsocAPIResponse, WebsocSection } from "@libs/uc-irvine-api/websoc"; +import type { Quarter } from "@peterportal-api/types"; import type { CastingContext, Parser } from "csv-parse"; import { parse } from "csv-parse"; import { stringify } from "csv-stringify/sync"; -import type { Quarter } from "peterportal-api-next-types"; import { __dirname, dataColumns, type Grade, handleError, logger } from "./lib"; diff --git a/tools/grades-updater/src/upload-data.ts b/tools/grades-updater/src/upload-data.ts index 29e6fbed..b64d8f05 100644 --- a/tools/grades-updater/src/upload-data.ts +++ b/tools/grades-updater/src/upload-data.ts @@ -2,8 +2,8 @@ import fs from "fs"; import { resolve } from "path"; import { PrismaClient } from "@libs/db"; +import type { Quarter } from "@peterportal-api/types"; import { type CastingContext, parse, type Parser } from "csv-parse"; -import type { Quarter } from "peterportal-api-next-types"; import { __dirname, dataColumns, handleError, logger } from "./lib"; diff --git a/tools/registrar-scraper/package.json b/tools/registrar-scraper/package.json index ffac2ef2..122479c3 100644 --- a/tools/registrar-scraper/package.json +++ b/tools/registrar-scraper/package.json @@ -1,12 +1,12 @@ { - "name": "@services/registrar-scraper", + "name": "@tools/registrar-scraper", "version": "0.0.0", "private": true, "license": "MIT", "type": "module", "main": "index.ts", "dependencies": { - "@libs/db": "workspace:*", + "@libs/db": "workspace:^", "@types/he": "1.2.0", "cheerio": "1.0.0-rc.12", "he": "1.2.0", @@ -15,7 +15,7 @@ "winston": "3.10.0" }, "devDependencies": { - "esbuild": "0.19.2", - "peterportal-api-next-types": "workspace:*" + "@peterportal-api/types": "workspace:^", + "esbuild": "0.19.2" } } diff --git a/tools/registrar-scraper/src/index.ts b/tools/registrar-scraper/src/index.ts index 33c00695..8f49b8b9 100644 --- a/tools/registrar-scraper/src/index.ts +++ b/tools/registrar-scraper/src/index.ts @@ -1,5 +1,5 @@ import { PrismaClient } from "@libs/db"; -import { Instructor } from "peterportal-api-next-types"; +import { Instructor } from "@peterportal-api/types"; import { getCourses } from "./course-scraper"; import { getInstructors } from "./instructor-scraper"; diff --git a/tools/registrar-scraper/src/instructor-scraper/index.ts b/tools/registrar-scraper/src/instructor-scraper/index.ts index f644f969..b1dda259 100644 --- a/tools/registrar-scraper/src/instructor-scraper/index.ts +++ b/tools/registrar-scraper/src/instructor-scraper/index.ts @@ -2,12 +2,12 @@ import { existsSync, readFileSync } from "fs"; import { dirname, join } from "path"; import { fileURLToPath } from "url"; +import type { Instructor } from "@peterportal-api/types"; import { load } from "cheerio"; import type { Element } from "cheerio"; import fetch from "cross-fetch"; import he from "he"; import pLimit from "p-limit"; -import type { Instructor } from "peterportal-api-next-types"; import { stringSimilarity } from "string-similarity-js"; import winston from "winston"; diff --git a/tools/registrar-scraper/src/lib.ts b/tools/registrar-scraper/src/lib.ts index 8c4db7b1..40a97225 100644 --- a/tools/registrar-scraper/src/lib.ts +++ b/tools/registrar-scraper/src/lib.ts @@ -1,6 +1,6 @@ import type { Prisma } from "@libs/db"; -import type { Instructor, Prerequisite, PrerequisiteTree } from "peterportal-api-next-types"; -import { courseLevels, divisionCodes } from "peterportal-api-next-types"; +import type { Instructor, Prerequisite, PrerequisiteTree } from "@peterportal-api/types"; +import { courseLevels, divisionCodes } from "@peterportal-api/types"; export type ScrapedCourse = { department: string; diff --git a/tools/registrar-scraper/src/prereq-scraper/index.ts b/tools/registrar-scraper/src/prereq-scraper/index.ts index f601ebed..9a4d2f02 100644 --- a/tools/registrar-scraper/src/prereq-scraper/index.ts +++ b/tools/registrar-scraper/src/prereq-scraper/index.ts @@ -3,10 +3,10 @@ import { writeFileSync } from "node:fs"; import { dirname, join } from "path"; import { fileURLToPath } from "url"; +import { Prerequisite, PrerequisiteTree } from "@peterportal-api/types"; import { load } from "cheerio"; import type { Element } from "cheerio"; import fetch from "cross-fetch"; -import { Prerequisite, PrerequisiteTree } from "peterportal-api-next-types"; import winston from "winston"; const __dirname = dirname(fileURLToPath(import.meta.url)); diff --git a/tsconfig.json b/tsconfig.json index 267c8779..4f2189a6 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,48 +1,23 @@ { + "extends": "@tsconfig/node18/tsconfig.json", "compilerOptions": { "baseUrl": ".", - "strict": true, - "outDir": "dist", - "target": "ES2020", - "module": "ES2020", "declaration": true, - "skipLibCheck": true, - "noUnusedLocals": true, "declarationMap": true, - "esModuleInterop": true, "inlineSourceMap": true, "isolatedModules": true, + "module": "ES2022", "moduleResolution": "Node", - "paths": { - "@libs/*": ["libs/*"], - "ant-stack": [ - "packages/ant-stack/src", - "packages/ant-stack/src/lambda-core", - "packages/ant-stack/src/utils" - ], - "peterportal-api-next-types": ["packages/peterportal-api-next-types"] - }, - "resolveJsonModule": true, "noImplicitReturns": true, - "noUnusedParameters": true, - "noFallthroughCasesInSwitch": true, - "forceConsistentCasingInFileNames": true + "outDir": "dist", + "resolveJsonModule": true }, - "include": [ - "*.ts", - "api/**/*", - "apps/**/*", - "libs/**/*", - "packages/**/*", - "services/**/*", - "tools/**/*" - ], + "include": ["*.ts", "apps/**/*", "libs/**/*", "packages/**/*", "services/**/*", "tools/**/*"], "exclude": [ "**/build/**/*", "**/cdk.out/**/*", "**/dist/**/*", "**/node_modules/**/*", - "**/*.template.ts", "apps/docs/**/*" ] } diff --git a/turbo.json b/turbo.json index e4cca8be..c486fd86 100644 --- a/turbo.json +++ b/turbo.json @@ -4,9 +4,8 @@ "generate": { "cache": false }, - "ant-stack#build": {}, "build": { - "dependsOn": ["ant-stack#build", "^build", "^generate"], + "dependsOn": ["^build", "^generate"], "outputs": ["dist/**"] }, "deploy": {