diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index ba498dce..9a359e02 100644 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -40,7 +40,7 @@ TPEN Services is a Node.js Express API service for TPEN3 (Transcription for Pale - [IIIF Presentation API](https://iiif.io/api/presentation/) - [W3C Web Annotation](https://www.w3.org/TR/annotation-model/) - [Web Components MDN](https://developer.mozilla.org/en-US/docs/Web/Web_Components) -- [Jest Documentation](https://jestjs.io/docs/getting-started) +- [Node.js Test Runner](https://nodejs.org/api/test.html) - [TPEN3 Project Homepage](https://three.t-pen.org) - [TPEN3 Services API](https://dev.api.t-pen.org) - [TPEN3 Services GitHub](https://github.com/CenterForDigitalHumanities/TPEN-services) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index f9baba29..b5390fe1 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -16,7 +16,7 @@ TPEN Services is a Node.js Express API service for TPEN3 (Transcription for Pale ### Environment Configuration -TPEN Services uses a layered configuration approach with `--import ./env-loader.js` using the dotenv package: +TPEN Services uses a layered configuration approach with Node native env-file flags: - `config.env` - Safe defaults (committed to repo, no secrets) - Works out-of-the-box for local Docker/MongoDB/MariaDB @@ -33,10 +33,10 @@ TPEN Services uses a layered configuration approach with `--import ./env-loader. - Contains actual secrets and environment-specific values - Overrides values from `config.env` -Configuration loading order (via `--import ./env-loader.js` using the dotenv package): +Configuration loading order (via Node CLI env-file flags in npm scripts): 1. `config.env` is loaded first (provides safe defaults) -2. `.env.{NODE_ENV}` is loaded second (environment-specific: .env.development, .env.production, .env.test) +2. `.env.development` is loaded second (development defaults) 3. `.env` is loaded last (local/server overrides - HIGHEST PRIORITY) This allows developers to work immediately with sensible defaults while keeping secrets out of the repository. @@ -48,19 +48,15 @@ This allows developers to work immediately with sensible defaults while keeping - Start the application: `npm start` or `npm run dev` - Test the root endpoint: `curl http://localhost:3011/` -- should return HTML containing the TPEN3 Services index (heading + welcome text) - Run unit tests that don't require databases: `npm run unitTests` -- many tests pass without database connections -- Run existence tests: `npm run existsTests` -- validates route registration and class imports - Run all tests: `npm run allTests` -- Full test suite confirming full app functionality - ALWAYS wait for full test completion. Tests may appear to hang but should complete within 2 minutes. - NOTE: Application may crash after serving initial requests due to database connection attempts - this is expected behavior without running MongoDB/MariaDB. ### Test Categories Available - `npm run allTests` -- Full test suite which requires .env settings -- `npm run unitTests` -- Core unit tests (some require databases) -- `npm run existsTests` -- Route and class existence validation (database-independent) -- `npm run functionsTests` -- Function-level tests -- `npm run E2Etests` -- End-to-end API tests -- `npm run dbTests` -- Database-specific tests (require running databases) -- `npm run authTest` -- Authentication tests (require Auth0 configuration) +- `npm run unitTests` -- Fast local seam checks +- `npm run E2Etests` -- CI-oriented integration seam checks +- `npm run test:coverage` -- Coverage report via c8 ### Expected Test Behavior - Tests requiring databases will timeout/fail without MongoDB/MariaDB running @@ -108,8 +104,8 @@ Required for external services: 1. Do not overwrite the existing .env file. If an .env file does not exist or is not populated then copy environment configuration: `cp .env.development .env` 2. Install dependencies with `npm install` 3. Make code changes -4. Test with: `npm run existsTests` (fast, database-independent) -5. For all other tests use `npm run allTests` +4. Test with: `npm run unitTests` (fast, database-independent) +5. For full validation use `npm run allTests` 6. Test manually: `curl http://localhost:3011/` and relevant endpoints NEVER CANCEL long-running commands. Application builds and tests are designed to complete within documented timeouts. Always wait for completion to ensure accurate validation of changes. diff --git a/.github/workflows/cd_dev.yaml b/.github/workflows/cd_dev.yaml index c8b42fa9..d68801fc 100644 --- a/.github/workflows/cd_dev.yaml +++ b/.github/workflows/cd_dev.yaml @@ -86,4 +86,4 @@ jobs: - name: Run smoke tests run: | cd /srv/node/tpen-services/ - SMOKE_TEST_URL=https://dev.api.t-pen.org node __tests__/smoke.test.js + npm run test:integration diff --git a/.github/workflows/cd_prod.yaml b/.github/workflows/cd_prod.yaml index 7650f506..c8c1ebeb 100644 --- a/.github/workflows/cd_prod.yaml +++ b/.github/workflows/cd_prod.yaml @@ -86,4 +86,4 @@ jobs: - name: Run smoke tests run: | cd /srv/node/tpen-services/ - SMOKE_TEST_URL=https://api.t-pen.org node __tests__/smoke.test.js + npm run test:integration diff --git a/.github/workflows/ci_dev.yaml b/.github/workflows/ci_dev.yaml index 915f1403..6de22262 100644 --- a/.github/workflows/ci_dev.yaml +++ b/.github/workflows/ci_dev.yaml @@ -3,7 +3,7 @@ on: pull_request: branches: development jobs: - test: + local_seams: runs-on: ubuntu-latest steps: - uses: actions/checkout@master @@ -50,8 +50,66 @@ jobs: - name: Install dependencies and run the test run: | npm install - npm run E2Etests + npm run test:local + integration_seams: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@master + - name: Create .env from secrets + uses: SpicyPizza/create-envfile@v2 + with: + fail_on_empty: false + sort_keys: false + envkey_DOWN: ${{ secrets.DOWN }} + envkey_SERVERURL: ${{ secrets.SERVERURL }} + envkey_MONGODBNAME: ${{ secrets.MONGODBNAME }} + envkey_MONGODB: ${{ secrets.MONGODB }} + envkey_TPENPROJECTS: ${{ secrets.TPENPROJECTS }} + envkey_TPENGROUPS: ${{ secrets.TPENGROUPS }} + envkey_TPENUSERS: ${{ secrets.TPENUSERS }} + envkey_MARIADBNAME: ${{ secrets.MARIADBNAMEDEV }} + envkey_MARIADB: ${{ secrets.MARIADB }} + envkey_MARIADBUSER: ${{ secrets.MARIADBUSERDEV }} + envkey_MARIADBPASSWORD: ${{ secrets.MARIADBPASSWORDDEV }} + envkey_RERUMIDPREFIX: ${{ secrets.RERUMIDPREFIXDEV }} + envkey_TINYPEN: ${{ secrets.TINYPENDEV }} + envkey_AUDIENCE: ${{ secrets.AUDIENCE }} + envkey_DOMAIN: ${{ secrets.DOMAIN }} + envkey_TPEN_SUPPORT_EMAIL: ${{ secrets.TPEN_SUPPORT_EMAIL }} + envkey_SMTP_HOST: ${{ secrets.SMTP_HOST }} + envkey_SMTP_PORT: ${{ secrets.SMTP_PORT }} + envkey_TPEN_EMAIL_CC: ${{ secrets.TPEN_EMAIL_CC }} + - name: Setup Node.js + uses: actions/setup-node@master + with: + node-version: "24" + - name: Cache node modules + uses: actions/cache@master + env: + cache-name: cache-node-modules + with: + path: ~/.npm + key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ + hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-build-${{ env.cache-name }}- + ${{ runner.os }}-build- + ${{ runner.os }}- + - name: Install dependencies and run the test + run: | + npm install + npm run test:integration + + bump_version: + runs-on: ubuntu-latest + needs: [local_seams, integration_seams] + steps: + - uses: actions/checkout@master + - name: Setup Node.js + uses: actions/setup-node@master + with: + node-version: "24" - name: Bump version (patch + prerelease) run: | git config user.name "github-actions[bot]" diff --git a/.github/workflows/ci_prod.yaml b/.github/workflows/ci_prod.yaml index 3b66631b..152591df 100644 --- a/.github/workflows/ci_prod.yaml +++ b/.github/workflows/ci_prod.yaml @@ -3,7 +3,7 @@ on: pull_request: branches: main jobs: - test: + local_seams: runs-on: ubuntu-latest steps: - uses: actions/checkout@master @@ -50,8 +50,66 @@ jobs: - name: Install dependencies and run the test run: | npm install - npm run E2Etests + npm run test:local + integration_seams: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@master + - name: Create .env from secrets + uses: SpicyPizza/create-envfile@v2 + with: + fail_on_empty: false + sort_keys: false + envkey_DOWN: ${{ secrets.DOWN }} + envkey_SERVERURL: ${{ secrets.SERVERURL }} + envkey_MONGODBNAME: ${{ secrets.MONGODBNAME }} + envkey_MONGODB: ${{ secrets.MONGODB }} + envkey_TPENPROJECTS: ${{ secrets.TPENPROJECTS }} + envkey_TPENGROUPS: ${{ secrets.TPENGROUPS }} + envkey_TPENUSERS: ${{ secrets.TPENUSERS }} + envkey_MARIADBNAME: ${{ secrets.MARIADBNAME }} + envkey_MARIADB: ${{ secrets.MARIADB }} + envkey_MARIADBUSER: ${{ secrets.MARIADBUSER }} + envkey_MARIADBPASSWORD: ${{ secrets.MARIADBPASSWORD }} + envkey_RERUMIDPREFIX: ${{ secrets.RERUMIDPREFIX }} + envkey_TINYPEN: ${{ secrets.TINYPEN }} + envkey_AUDIENCE: ${{ secrets.AUDIENCE }} + envkey_DOMAIN: ${{ secrets.DOMAIN }} + envkey_TPEN_SUPPORT_EMAIL: ${{ secrets.TPEN_SUPPORT_EMAIL }} + envkey_SMTP_HOST: ${{ secrets.SMTP_HOST }} + envkey_SMTP_PORT: ${{ secrets.SMTP_PORT }} + envkey_TPEN_EMAIL_CC: ${{ secrets.TPEN_EMAIL_CC }} + - name: Setup Node.js + uses: actions/setup-node@master + with: + node-version: "24" + - name: Cache node modules + uses: actions/cache@master + env: + cache-name: cache-node-modules + with: + path: ~/.npm + key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ + hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-build-${{ env.cache-name }}- + ${{ runner.os }}-build- + ${{ runner.os }}- + - name: Install dependencies and run the test + run: | + npm install + npm run test:integration + + bump_version: + runs-on: ubuntu-latest + needs: [local_seams, integration_seams] + steps: + - uses: actions/checkout@master + - name: Setup Node.js + uses: actions/setup-node@master + with: + node-version: "24" - name: Bump version (minor + prerelease) run: | git config user.name "github-actions[bot]" diff --git a/.github/workflows/sync_tpen_shared_openapi.yaml b/.github/workflows/sync_tpen_shared_openapi.yaml new file mode 100644 index 00000000..79d0e3b8 --- /dev/null +++ b/.github/workflows/sync_tpen_shared_openapi.yaml @@ -0,0 +1,54 @@ +name: Sync TPEN-services shared OpenAPI artifact + +on: + push: + branches: + - development + paths: + - openapi/components/tpen-services-shared-components.openapi.yaml + workflow_dispatch: + +permissions: + contents: read + +jobs: + sync: + name: Sync TPEN-services shared OpenAPI artifact + runs-on: ubuntu-latest + steps: + - name: Checkout TPEN-services + uses: actions/checkout@v4 + + - name: Checkout receiver repository + uses: actions/checkout@v4 + with: + repository: cubap/rerum_openapi + ref: main + token: ${{ secrets.OPENAPI }} + path: receiver + + # No `test -f` guard: the receiver target may not exist on the first + # sync; cp creates it. Once the receiver registry references this file, + # add `test -f receiver/schemas/openapi/tpen-services-shared-components.openapi.yaml` + # to catch retargeted copies. + + - name: Copy canonical shared artifact + run: cp openapi/components/tpen-services-shared-components.openapi.yaml receiver/schemas/openapi/tpen-services-shared-components.openapi.yaml + + - name: Create or update sync pull request + uses: peter-evans/create-pull-request@v8 + with: + token: ${{ secrets.OPENAPI }} + path: receiver + add-paths: schemas/openapi/tpen-services-shared-components.openapi.yaml + commit-message: "chore: sync TPEN-services shared OpenAPI artifact" + branch: sync/tpen-services-shared-openapi + base: main + delete-branch: true + title: "chore: sync TPEN-services shared OpenAPI artifact" + body: | + Syncs the canonical TPEN-services shared OpenAPI artifact from CenterForDigitalHumanities/TPEN-services. + + - Source commit: ${{ github.sha }} + - Source artifact: `openapi/components/tpen-services-shared-components.openapi.yaml` + - Target artifact: `schemas/openapi/tpen-services-shared-components.openapi.yaml` diff --git a/CONFIG.md b/CONFIG.md index 6d6c2738..12aba63b 100644 --- a/CONFIG.md +++ b/CONFIG.md @@ -2,7 +2,7 @@ ## Configuration Architecture -TPEN Services uses a layered configuration approach to separate safe defaults from environment-specific secrets. Configuration is loaded using `--import ./env-loader.js` with the dotenv package: +TPEN Services uses a layered configuration approach to separate safe defaults from environment-specific secrets. Configuration is loaded using Node native env-file flags in npm scripts: 1. **`config.env`** (committed) - Safe defaults for development 2. **`.env`** (gitignored) - Environment-specific overrides @@ -47,10 +47,10 @@ cp .env.production .env # Optional: Database connections, SMTP, etc. ``` -The application loads configuration via `--import ./env-loader.js` using the dotenv package in this order: +The application loads configuration via Node flags in this order: 1. `config.env` - provides safe defaults -2. `.env.{NODE_ENV}` - environment-specific overrides (.env.development, .env.production, .env.test) +2. `.env.development` - development template values 3. `.env` - local/server overrides (HIGHEST PRIORITY) ### What Goes Where? diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9df4b210..9359f583 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -126,7 +126,7 @@ curl http://localhost:3011 ## Testing -TPEN Services uses Jest for testing with multiple test suites: +TPEN Services uses Node.js built-in test runner (`node:test`) with `c8` for coverage: ### Run All Tests ```bash @@ -135,31 +135,18 @@ npm run allTests ### Run Specific Test Suites ```bash -# Unit tests only +# Fast local seam checks npm run unitTests -# End-to-end tests +# CI-oriented integration seam checks npm run E2Etests -# Database tests -npm run dbTests - -# Authentication tests -npm run authTest - -# User class tests -npm run userClassTests - -# Import functionality tests -npm run importTests - -# Member invitation tests -npm run inviteMemberTests +# Coverage report +npm run test:coverage ``` ### Test Requirements -- Some tests require database connections and will be skipped if databases are not available -- Authentication tests require proper Auth0 configuration ( currently skipped ) +- Current suites focus on API seam and validation contract regressions, and are designed to run without live DB dependencies - Ensure your `.env` file is properly configured before running tests ## Code Style and Best Practices @@ -179,7 +166,7 @@ npm run inviteMemberTests - **API Routes**: Organized in feature folders (`project/`, `manifest/`, `line/`, etc.) - **Classes**: Located in `classes/` directory - **Utilities**: Helper functions in `utilities/` directory -- **Tests**: Use `__tests__` directories or `.test.js` suffix +- **Tests**: Use the `test/local` and `test/integration` directories with `.test.js` files ### Error Handling - Use appropriate HTTP status codes @@ -245,7 +232,7 @@ TPEN-services/ ├── line/ # Line/annotation routes ├── userProfile/ # User profile routes ├── utilities/ # Helper functions -├── __tests__/ # Global tests +├── test/ # node:test suites (local and integration) ├── app.js # Express app configuration ├── package.json # Dependencies and scripts ├── config.env # Safe defaults (committed) @@ -271,7 +258,7 @@ TPEN-services/ - [TPEN Project Homepage](https://t-pen.org/TPEN3) - [Express.js Documentation](https://expressjs.com/) - [MongoDB Documentation](https://docs.mongodb.com/) -- [Jest Testing Framework](https://jestjs.io/) +- [Node.js Test Runner](https://nodejs.org/api/test.html) - [Auth0 Documentation](https://auth0.com/docs) ## License diff --git a/README.md b/README.md index e1b834df..598fe83e 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ Services required by TPEN interfaces in order to interact with data. ### Configuration Files -TPEN Services uses a layered configuration approach with `--import ./env-loader.js` using the dotenv package: +TPEN Services uses a layered configuration approach with Node's native env-file support: - **`config.env`** - Safe defaults, committed to repository - Works out-of-the-box for local Docker/MongoDB/MariaDB @@ -58,10 +58,10 @@ TPEN Services uses a layered configuration approach with `--import ./env-loader. - Contains your specific settings and secrets - Overrides values from `config.env` -Configuration is loaded via `--import ./env-loader.js` using the dotenv package: +Configuration is loaded via Node CLI flags in npm scripts: 1. `config.env` is loaded first (provides safe defaults) -2. `.env.{NODE_ENV}` is loaded second (environment-specific: .env.development, .env.production, .env.test) +2. `.env.development` is loaded second (development defaults) 3. `.env` is loaded last (local/server overrides - HIGHEST PRIORITY) ### Environment Variables @@ -79,14 +79,17 @@ See [CONFIG.md](./CONFIG.md) for complete configuration documentation. ## Testing ```bash -# Run all tests +# Fast local regression suite (node:test) +npm test + +# Run local + integration seams npm run allTests -# Run specific test suites -npm run unitTests # Core unit tests -npm run existsTests # Route/class validation -npm run E2Etests # End-to-end API tests -npm run dbTests # Database tests +# Run only CI-oriented integration seams +npm run E2Etests + +# Coverage with c8 +npm run test:coverage ``` ## Deployment diff --git a/__tests__/full_end_to_end.test.js b/__tests__/full_end_to_end.test.js deleted file mode 100644 index aacc1f05..00000000 --- a/__tests__/full_end_to_end.test.js +++ /dev/null @@ -1,42 +0,0 @@ -// End to end tests that spin up and test it? - -import app from '../app.js' -import express from 'express' -import request from 'supertest' - -/** - * The route uses the json, set, status and location functions on the res object. - * type() - Sets the type (content-type) of response - * status() - Sets the status code of the response - * send() - Set the text of the response - * - * The test creates a simple mock response object that duplicates what the Express response object would do in the real application. - */ -describe('App index test. #e2e', () => { - - it('responds to / with a 200 status and and the index.html page.', async () => { - const res = await request(app) - .get('/') - expect(res.statusCode).toBe(200) - expect(res.type).toMatch(/html/) - }) - -}) - -describe('Invalid site path test. #e2e', () => { - - it('returns a graceful 404', async () => { - const res = await request(app) - .get('/potato/') - expect(res.statusCode).toBe(404) - }) - -}) - - -describe('Endpoint tests. #e2e', () => { - it('This is always going to pass because it is a good stub.', async () => { - expect(true).toBe(true) - }) - -}) diff --git a/__tests__/mount.test.js b/__tests__/mount.test.js deleted file mode 100644 index 38dde54d..00000000 --- a/__tests__/mount.test.js +++ /dev/null @@ -1,346 +0,0 @@ -/** - * Express Route Detection - * - * This approach checks routes without making HTTP requests by - * directly inspecting the Express app's routing table. - */ - -import { jest } from "@jest/globals" -import app from "../app.js" -import fs from "fs" - -/** - * Recursively extract all routes from Express 5 router layers - * @param {Object} layer - A router stack layer - * @param {string} prefix - The path prefix from parent routers - * @returns {Array} - Array of route objects with method and path - */ -function extractRoutes(layer, prefix = '') { - const routes = [] - - // If this layer has a route (actual endpoint) - if (layer.route) { - const methods = Object.keys(layer.route.methods) - const path = prefix + layer.route.path - methods.forEach(method => { - routes.push({ method: method.toLowerCase(), path }) - }) - } - - // If this layer is a nested router - if (layer.name === 'router' && layer.handle?.stack) { - // Try to extract the router's path prefix from matchers - let routerPath = '' - if (layer.matchers && layer.matchers[0]) { - // Test with various paths to detect the prefix - const testPaths = ['/test', '/a/b', '/x/y/z'] - for (const testPath of testPaths) { - const result = layer.matchers[0](testPath) - if (result && result.path) { - routerPath = result.path - break - } - } - } - - // Recursively extract routes from nested router - layer.handle.stack.forEach(nestedLayer => { - routes.push(...extractRoutes(nestedLayer, prefix + routerPath)) - }) - } - - return routes -} - -/** - * Get all registered routes from the Express app (cached) - * This is called once when the module loads to avoid repeated extraction - */ -const allRoutes = (() => { - const routes = [] - app.router.stack.forEach(layer => { - routes.push(...extractRoutes(layer)) - }) - return routes -})() - -/** - * Check if a route exists in the Express app - * @param {string} path - The route path to check (e.g., '/', '/api', '/:id') - * @param {string} [method] - Optional HTTP method to check (e.g., 'get', 'post', 'put', 'delete', 'patch') - * @returns {boolean} - True if the route exists (and matches the method if specified) - */ -function routeExists(path, method = null) { - return allRoutes.some(route => { - // Normalize paths for comparison (handle trailing slashes) - const routePath = route.path.replace(/\/+$/, '') || '/' - const testPath = path.replace(/\/+$/, '') || '/' - - const pathMatches = routePath === testPath - - // If method is specified, check if it matches - if (method) return pathMatches && route.method === method.toLowerCase() - - return pathMatches - }) -} - -describe('Check to see that all expected route patterns exist. #exists_unit', () => { - - describe('Root routes', () => { - it('GET / -- root index route', () => { - expect(routeExists('/', 'get')).toBe(true) - }) - }) - - describe('Private user routes (/my)', () => { - it('GET /my/profile -- authenticated or public user profile', () => { - expect(routeExists('/profile', 'get')).toBe(true) - }) - - it('PUT /my/profile -- update user profile', () => { - expect(routeExists('/profile', 'put')).toBe(true) - }) - - it('GET /my/projects -- user projects list', () => { - expect(routeExists('/projects', 'get')).toBe(true) - }) - }) - - describe('Project creation and import routes', () => { - it('POST /project/create -- create new project', () => { - expect(routeExists('/create', 'post')).toBe(true) - }) - - it('POST /project/import -- import from manifest URL', () => { - expect(routeExists('/import', 'post')).toBe(true) - }) - - it('POST /project/import-image -- import image', () => { - expect(routeExists('/import-image', 'post')).toBe(true) - }) - - it('GET /project/deletecookie -- TPEN 2.8 import cookie deletion', () => { - expect(routeExists('/deletecookie', 'get')).toBe(true) - }) - - it('GET /project/import28/:uid -- TPEN 2.8 import by user ID', () => { - expect(routeExists('/import28/:uid', 'get')).toBe(true) - }) - - it('GET /project/import28/selectedproject/:selectedProjectId -- TPEN 2.8 selected project import', () => { - expect(routeExists('/import28/selectedproject/:selectedProjectId', 'get')).toBe(true) - }) - }) - - describe('Project read and management routes', () => { - it('GET /project/:id -- get project by ID', () => { - expect(routeExists('/:id', 'get')).toBe(true) - }) - - it('GET /project/:id/manifest -- export project manifest', () => { - expect(routeExists('/:id/manifest', 'get')).toBe(true) - }) - - it('GET /project/:id/deploymentStatus -- check manifest deployment status', () => { - expect(routeExists('/:id/deploymentStatus', 'get')).toBe(true) - }) - - it('PATCH /project/:projectId/label -- update project label', () => { - expect(routeExists('/:projectId/label', 'patch')).toBe(true) - }) - - it('PUT /project/:projectId/metadata -- update project metadata', () => { - expect(routeExists('/:projectId/metadata', 'put')).toBe(true) - }) - }) - - describe('Project member management routes', () => { - it('POST /project/:id/invite-member -- invite project member', () => { - expect(routeExists('/:id/invite-member', 'post')).toBe(true) - }) - - it('POST /project/:id/remove-member -- remove project member', () => { - expect(routeExists('/:id/remove-member', 'post')).toBe(true) - }) - - it('POST /project/:projectId/collaborator/:collaboratorId/addRoles -- add roles to collaborator', () => { - expect(routeExists('/:projectId/collaborator/:collaboratorId/addRoles', 'post')).toBe(true) - }) - - it('PUT /project/:projectId/collaborator/:collaboratorId/setRoles -- set collaborator roles', () => { - expect(routeExists('/:projectId/collaborator/:collaboratorId/setRoles', 'put')).toBe(true) - }) - - it('POST /project/:projectId/collaborator/:collaboratorId/removeRoles -- remove collaborator roles', () => { - expect(routeExists('/:projectId/collaborator/:collaboratorId/removeRoles', 'post')).toBe(true) - }) - - it('POST /project/:projectId/switch/owner -- switch project owner', () => { - expect(routeExists('/:projectId/switch/owner', 'post')).toBe(true) - }) - - it('GET /project/:projectId/collaborator/:collaboratorId/agent/:agentId -- get collaborator agent info', () => { - expect(routeExists('/:projectId/collaborator/:collaboratorId/agent/:agentId', 'get')).toBe(true) - }) - - it('GET /project/:projectId/collaborator/:collaboratorId/decline -- decline project invitation', () => { - expect(routeExists('/:projectId/collaborator/:collaboratorId/decline', 'get')).toBe(true) - }) - - it('POST /project/:projectId/leave -- member leave project', () => { - expect(routeExists('/:id/leave', 'post')).toBe(true) - }) - }) - - describe('Project custom roles routes', () => { - it('GET /project/:projectId/customRoles -- get custom roles', () => { - expect(routeExists('/:projectId/customRoles', 'get')).toBe(true) - }) - - it('POST /project/:projectId/addCustomRoles -- add custom roles', () => { - expect(routeExists('/:projectId/addCustomRoles', 'post')).toBe(true) - }) - - it('PUT /project/:projectId/updateCustomRoles -- update custom roles', () => { - expect(routeExists('/:projectId/updateCustomRoles', 'put')).toBe(true) - }) - - it('DELETE /project/:projectId/removeCustomRoles -- remove custom roles', () => { - expect(routeExists('/:projectId/removeCustomRoles', 'delete')).toBe(true) - }) - }) - - describe('Project custom metadata routes', () => { - it('GET /project/:id/custom -- get custom metadata namespaces', () => { - expect(routeExists('/:id/custom', 'get')).toBe(true) - }) - - it('POST /project/:id/custom -- create or replace namespace metadata', () => { - expect(routeExists('/:id/custom', 'post')).toBe(true) - }) - - it('PUT /project/:id/custom -- upsert namespace metadata', () => { - expect(routeExists('/:id/custom', 'put')).toBe(true) - }) - }) - - describe('Project tools routes', () => { - it('POST /project/:projectId/tool -- add tool to project', () => { - expect(routeExists('/:projectId/tool', 'post')).toBe(true) - }) - - it('DELETE /project/:projectId/tool -- remove tool from project', () => { - expect(routeExists('/:projectId/tool', 'delete')).toBe(true) - }) - - it('PUT /project/:projectId/toggleTool -- toggle tool state', () => { - expect(routeExists('/:projectId/toggleTool', 'put')).toBe(true) - }) - }) - - describe('Project copy routes', () => { - it('POST /project/:projectId/copy -- copy project', () => { - expect(routeExists('/:projectId/copy', 'post')).toBe(true) - }) - - it('POST /project/:projectId/copy-without-annotations -- copy project without annotations', () => { - expect(routeExists('/:projectId/copy-without-annotations', 'post')).toBe(true) - }) - - it('POST /project/:projectId/copy-with-group -- copy project with group', () => { - expect(routeExists('/:projectId/copy-with-group', 'post')).toBe(true) - }) - - it('POST /project/:projectId/copy-with-customizations -- copy project with customizations', () => { - expect(routeExists('/:projectId/copy-with-customizations', 'post')).toBe(true) - }) - }) - - describe('Layer routes (/project/:projectId/layer)', () => { - it('GET /project/:projectId/layer/:layerId -- get layer by ID', () => { - expect(routeExists('/:layerId', 'get')).toBe(true) - }) - - it('PUT /project/:projectId/layer/:layerId -- update layer', () => { - expect(routeExists('/:layerId', 'put')).toBe(true) - }) - - it('POST /project/:projectId/layer -- create new layer', () => { - expect(routeExists('/', 'post')).toBe(true) - }) - }) - - describe('Page routes (/project/:projectId/page)', () => { - it('GET /project/:projectId/page/:pageId -- get page by ID', () => { - expect(routeExists('/:pageId', 'get')).toBe(true) - }) - - it('PUT /project/:projectId/page/:pageId -- update page', () => { - expect(routeExists('/:pageId', 'put')).toBe(true) - }) - }) - - describe('Line routes (/project/:projectId/page/:pageId/line)', () => { - it('GET /project/:projectId/page/:pageId/line/:lineId -- get line by ID', () => { - expect(routeExists('/:lineId', 'get')).toBe(true) - }) - - it('POST /project/:projectId/page/:pageId/line -- create new line', () => { - expect(routeExists('/', 'post')).toBe(true) - }) - - it('PUT /project/:projectId/page/:pageId/line/:lineId -- update line', () => { - expect(routeExists('/:lineId', 'put')).toBe(true) - }) - - it('PATCH /project/:projectId/page/:pageId/line/:lineId/text -- update line text', () => { - expect(routeExists('/:lineId/text', 'patch')).toBe(true) - }) - - it('PATCH /project/:projectId/page/:pageId/line/:lineId/bounds -- update line bounds', () => { - expect(routeExists('/:lineId/bounds', 'patch')).toBe(true) - }) - }) - - describe('Proxy routes', () => { - it('GET /proxy/*_ -- proxy any GET request', () => { - expect(routeExists('/*_', 'get')).toBe(true) - }) - - it('OPTIONS /proxy/*_ -- proxy CORS preflight', () => { - expect(routeExists('/*_', 'options')).toBe(true) - }) - }) - - describe('Feedback routes (/beta)', () => { - it('POST /beta/feedback -- submit user feedback', () => { - expect(routeExists('/feedback', 'post')).toBe(true) - }) - - it('POST /beta/bug -- submit bug report', () => { - expect(routeExists('/bug', 'post')).toBe(true) - }) - }) -}) - -describe('Check to see that critical static files are present #exists_unit', () => { - it('/public folder files', () => { - const filePath = './public/' - expect(fs.existsSync(filePath+"index.html")).toBeTruthy() - expect(fs.existsSync(filePath+"API.html")).toBeTruthy() - }) -}) - - -describe('Check to see that critical repo files are present #exists_unit', () => { - it('root folder files', () => { - const filePath = './' // Replace with the actual file path - expect(fs.existsSync(filePath+"CODEOWNERS")).toBeTruthy() - expect(fs.existsSync(filePath+"CONTRIBUTING.md")).toBeTruthy() - expect(fs.existsSync(filePath+"README.md")).toBeTruthy() - expect(fs.existsSync(filePath+"LICENSE.md")).toBeTruthy() - expect(fs.existsSync(filePath+".gitignore")).toBeTruthy() - expect(fs.existsSync(filePath+"package.json")).toBeTruthy() - }) -}) diff --git a/__tests__/smoke.test.js b/__tests__/smoke.test.js deleted file mode 100644 index a3cf220c..00000000 --- a/__tests__/smoke.test.js +++ /dev/null @@ -1,119 +0,0 @@ -#!/usr/bin/env node - -/** - * Post-deployment smoke tests for TPEN Services - * Validates basic functionality after deployment - */ - -import https from 'https' -import http from 'http' - -const BASE_URL = process.env.SMOKE_TEST_URL || 'http://localhost:3011' -const isHttps = BASE_URL.startsWith('https') -const httpModule = isHttps ? https : http - -function request(path, options = {}) { - return new Promise((resolve, reject) => { - const url = new URL(path, BASE_URL) - const req = httpModule.request(url, { - method: options.method || 'GET', - headers: options.headers || {}, - ...options - }, (res) => { - let data = '' - res.on('data', chunk => data += chunk) - res.on('end', () => resolve({ status: res.statusCode, data, headers: res.headers })) - }) - req.on('error', reject) - if (options.timeout) { - req.setTimeout(options.timeout, () => { - req.destroy() - reject(new Error('Request timeout')) - }) - } - req.end() - }) -} - -async function runCheck(name, fn) { - try { - await fn() - return { name, ok: true } - } catch (err) { - return { name, ok: false, error: err.message || String(err) } - } -} - -// Main smoke check function that can run standalone or in Jest -async function runSmokeChecks() { - const checks = [] - - // Root endpoint returns expected content - checks.push(await runCheck('Root endpoint returns service index HTML', async () => { - const res = await request('/') - if (res.status !== 200) throw new Error(`Expected 200, got ${res.status}`) - const hasHeading = res.data.includes('

TPEN3 Services

') - const hasWelcome = res.data.toLowerCase().includes('welcome to') - if (!(hasHeading || hasWelcome)) throw new Error('Expected TPEN3 Services index HTML not found in response') - })) - - // Protected endpoint requires authentication (401 when Authorization header is missing) - checks.push(await runCheck('Protected endpoint requires authentication', async () => { - const res = await request('/my/profile') - if (res.status !== 401) throw new Error(`Expected 401, got ${res.status}`) - })) - - // CORS headers present for allowed origin - checks.push(await runCheck('CORS headers present for allowed origins', async () => { - const res = await request('/', { - headers: { Origin: 'https://app.t-pen.org' } - }) - const corsHeader = res.headers['access-control-allow-origin'] - if (!corsHeader) throw new Error('CORS header not present') - })) - - // Invalid route returns 404 - checks.push(await runCheck('Invalid route returns 404', async () => { - const res = await request('/this-does-not-exist') - if (res.status !== 404) throw new Error(`Expected 404, got ${res.status}`) - })) - - // Service responds within 3 seconds - checks.push(await runCheck('Service responds within 3 seconds', async () => { - const start = Date.now() - await request('/', { timeout: 3000 }) - const duration = Date.now() - start - if (duration > 3000) throw new Error(`Response took ${duration}ms (> 3000ms)`) - })) - - const failed = checks.filter(c => !c.ok) - if (failed.length > 0) { - const msg = failed.map(f => `${f.name}: ${f.error}`).join('\n') - throw new Error(`Smoke tests failed:\n${msg}`) - } - - return { total: checks.length, passed: checks.length - failed.length, failed: failed.length } -} - -// If running standalone (node script), execute and exit with appropriate code -// Check if this file is being run directly (not imported by Jest) -const isRunningStandalone = import.meta.url.endsWith(process.argv[1].replace(/\\/g, '/')) -if (isRunningStandalone || (typeof test === 'undefined' && process.argv[1]?.includes('smoke.test.js'))) { - console.log('Running smoke tests...') - runSmokeChecks() - .then(result => { - console.log(`✓ All ${result.total} smoke checks passed`) - process.exit(0) - }) - .catch(err => { - console.error('✗ Smoke tests failed:', err.message) - process.exit(1) - }) -} - -// If running in Jest, wrap in a test -if (typeof test !== 'undefined') { - test('post-deployment smoke checks', async () => { - await runSmokeChecks() - }, 30000) // set a longer timeout for the whole test if needed -} diff --git a/auth/__tests__/auth_unit_test.js b/auth/__tests__/auth_unit_test.js deleted file mode 100644 index 2801be63..00000000 --- a/auth/__tests__/auth_unit_test.js +++ /dev/null @@ -1,72 +0,0 @@ -import express from "express" -import request from "supertest" -import auth0Middleware from "../index.js" -import { ObjectId } from "mongodb" - -process.env.AUDIENCE = "provide audience to test" -// this test has a had time reading env directly. add - -const app = express() - -app.use(auth0Middleware()) -const TIME_OUT = process.env.TEST_TIMEOUT ?? 5000 - -describe("auth0Middleware #auth_test", () => { - it( - "should return 401 without Authorization Header", - async () => { - const res = await request(app).get("/protected-route") - expect(res.status).toBe(401) - }, - TIME_OUT - ) - - it( - "should return 401 with invalid token from Authorization Header", - async () => { - const res = await request(app) - .get("/protected-route") - .set("Authorization", `Bearer 123123123123123123123123123`) - expect(res.status).toBe(401) - }, - TIME_OUT - ) - - it.skip("should set req.user with payload from auth and call next", async () => { - const mockRequest = { - auth: { - payload: { - sub: "user123", - roles: ["admin", "user"], - "http://store.rerum.io/agent":`test_agent/id/${new ObjectId()}` - }, - - token: process.env.TEST_TOKEN, - - headers: { - authorization: "Bearer " + process.env.TEST_TOKEN - } - } - } - const mockResponse = {} - let mockNextCalled = false - const mockNext = function () { - mockNextCalled = true - } - - const [verifier, setUser] = auth0Middleware() - - await verifier(mockRequest, mockResponse, mockNext) - setUser(mockRequest, mockResponse, mockNext) - - expect(mockRequest.user).toEqual( - expect.objectContaining({ - sub: "user123", - roles: expect.arrayContaining(["admin", "user"]) - }) - ) - - // expect(mockNext).toHaveBeenCalled() - expect(mockNextCalled).toBe(true) - }) -}) diff --git a/bin/tpen3_services.js b/bin/tpen3_services.js index 82f5b427..b4dd88c0 100644 --- a/bin/tpen3_services.js +++ b/bin/tpen3_services.js @@ -1,6 +1,7 @@ #!/usr/bin/env node -// Load environment variables before anything else +// Bootstrap env vars before any other import. PM2 invokes this script +// directly (no --env-file flags), so the entry point must self-load. import '../env-loader.js' /** diff --git a/classes/Group/__tests__/Group.test.js b/classes/Group/__tests__/Group.test.js deleted file mode 100644 index bc63282c..00000000 --- a/classes/Group/__tests__/Group.test.js +++ /dev/null @@ -1,71 +0,0 @@ -import Group from "../Group.js" -import dbDriver from "../../../database/driver.js" -import { expect, jest } from "@jest/globals" - -jest.mock("../../../database/driver.js") - -describe.skip("Group Class", () => { - let group - let databaseMock - - beforeEach(() => { - databaseMock = new dbDriver("mongo") - group = new Group() - group.members = { member1 : {roles: ["role1"]}, - member2 : {roles: ["role2"]}, - member3 : {roles: ["role2", "role1"]} - } - }) - - test("should add a new member", async () => { - await group.addMember("member4", ["role1"]) - expect(group.members["member4"]).toBeTruthy() - expect(group.members["member4"].roles).toEqual(["role1"]) - }) - - test("should throw error when adding an existing member", () => { - expect(async () => await group.addMember("member1", ["role2"])).toThrow("Member already exists") - }) - - test("should update member roles", () => { - group.setMemberRoles("member1", ["role2"]) - expect(group.members["member1"].roles).toEqual(["role2"]) - }) - - test("should throw error when updating non-existing member", () => { - expect(() => group.setMemberRoles("nobody", ["role2"])).toThrow("Member not found") - }) - - test("should add roles to an existing member", () => { - group.addMemberRoles("member1", ["role2"]) - expect(group.members["member1"].roles).toEqual(["role1", "role2"]) - }) - - test("should remove roles from an existing member", () => { - group.members["member1"] = { roles: ["role1", "role2"] } - group.removeMemberRoles("member1", ["role1"]) - expect(group.members["member1"].roles).toEqual(["role2"]) - }) - - test("should remove a member", async () => { - await group.removeMember("member1") - expect(group.members["member1"]).toBeUndefined() - }) - - test("should get members by role", () => { - group.members["member1"] = { roles: ["role1"] } - group.members["member2"] = { roles: ["role2"] } - expect(group.getByRole("role1")).toEqual(["member1","member3"]) - }) - - test("should create a new group", async () => { - databaseMock.reserveId = jest.fn().mockReturnValue("01234567890123456789abcd") - databaseMock.save = jest.fn().mockResolvedValue("newGroup") - const payload = { label: "Test Group" } - const result = await Group.createNewGroup("01234567890123456789abcd", payload) - expect(result.creator).toBe("01234567890123456789abcd") - expect(result.label).toBe("Test Group") - expect(result.members['01234567890123456789abcd']).toBeDefined() - expect(result.members['01234567890123456789abcd'].roles).toEqual(["OWNER", "LEADER"]) - }) -}) diff --git a/classes/Layer/__tests__/exists.test.js b/classes/Layer/__tests__/exists.test.js deleted file mode 100644 index efe92f8b..00000000 --- a/classes/Layer/__tests__/exists.test.js +++ /dev/null @@ -1,29 +0,0 @@ -import Layer from "../Layer.js" - -describe('Layer Class looks how we expect it to. #Layer_exists_unit', () => { - it('Imports Layer', () => { - expect(typeof Layer).toBe('function') - }) - - const layer = new Layer("projectID", { id: "layerID", label: "Layer Label", pages: [{ id: "pageID", label: "Page Label", target: "https://example.com/canvas" }] }) - - it('has useful methods', () => { - expect(typeof layer.update).toBe('function') - expect(typeof layer.delete).toBe('function') - }) - - it('has expected properties', () => { - expect(layer).toHaveProperty('id') - expect(layer).toHaveProperty('label') - expect(layer).toHaveProperty('pages') - }) - - it('throws an error for poorly formed new Layer calls', () => { - expect(() => new Layer()).toThrow() // No arguments - expect(() => new Layer("projectID")).toThrow() // Missing required arguments - expect(() => new Layer("projectID", null)).toThrow() // Null layer object - expect(() => new Layer("projectID", { id: null, label: "Layer Label", pages: [] })).toThrow() // Invalid layer ID - expect(() => new Layer("projectID", { id: "layerID", label: null, pages: [] })).toThrow() // Invalid layer label - expect(() => new Layer("projectID", { id: "layerID", label: "Layer Label", pages: null })).toThrow() // Invalid pages array - }) -}) diff --git a/classes/Line/__tests__/Line.test.js b/classes/Line/__tests__/Line.test.js deleted file mode 100644 index 12ead642..00000000 --- a/classes/Line/__tests__/Line.test.js +++ /dev/null @@ -1,73 +0,0 @@ -import Line from '../Line.js' - -describe.skip('Line class unit tests', () => { - it('should throw an error if no ID, body, or target is provided', () => { - expect(() => new Line({})).toThrow('Line data is malformed.') - }) - - it('should create a new Line instance with valid data', () => { - const line = new Line({ id: '123', body: 'Sample text', target: 'https://example.com?xywh=10,10,100,100' }) - expect(line.id).toBe('123') - expect(line.body).toBe('Sample text') - expect(line.target).toBe('https://example.com?xywh=10,10,100,100') - }) - - it('should save a line to RERUM', async () => { - const line = new Line({ id: '123', body: 'Sample text', target: 'https://example.com?xywh=10,10,100,100' }) - const savedLine = await line.update() - expect(savedLine.id).toBeDefined() - }) - - it('should update the text of a line', async () => { - const line = new Line({ id: '123', body: 'Old text', target: 'https://example.com?xywh=10,10,100,100' }) - const updatedLine = await line.updateText('New text') - expect(updatedLine.body).toBe('New text') - }) - - it('should not update the text if it is the same', async () => { - const line = new Line({ id: '123', body: 'Same text', target: 'https://example.com?xywh=10,10,100,100' }) - const updatedLine = await line.updateText('Same text') - expect(updatedLine.body).toBe('Same text') - }) - - it('should update the bounds of a line', async () => { - const line = new Line({ id: '123', body: 'Sample text', target: 'https://example.com?xywh=10,10,100,100' }) - const updatedLine = await line.updateBounds({ x: 20, y: 20, w: 200, h: 200 }) - expect(updatedLine.target).toBe('https://example.com?xywh=20,20,200,200') - }) - - it('should not update the bounds if they are the same', async () => { - const line = new Line({ id: '123', body: 'Sample text', target: 'https://example.com?xywh=10,10,100,100' }) - const updatedLine = await line.updateBounds({ x: 10, y: 10, w: 100, h: 100 }) - expect(updatedLine.target).toBe('https://example.com?xywh=10,10,100,100') - }) - - it('should return JSON-LD format when isLD is true', () => { - const line = new Line({ id: '123', body: 'Sample text', target: 'https://example.com?xywh=10,10,100,100' }) - const jsonLD = line.asJSON(true) - expect(jsonLD).toEqual({ - '@context': 'http://iiif.io/api/presentation/3/context.json', - id: '123', - type: 'Annotation', - motivation: 'transcribing', - target: 'https://example.com?xywh=10,10,100,100', - body: 'Sample text' - }) - }) - - it('should return plain JSON format when isLD is false', () => { - const line = new Line({ id: '123', body: 'Sample text', target: 'https://example.com?xywh=10,10,100,100' }) - const json = line.asJSON(false) - expect(json).toEqual({ - id: '123', - body: 'Sample text', - target: 'https://example.com?xywh=10,10,100,100' - }) - }) - - it('should delete a line', async () => { - const line = new Line({ id: '123', body: 'Sample text', target: 'https://example.com?xywh=10,10,100,100' }) - const result = await line.delete() - expect(result).toBe(true) - }) -}) diff --git a/classes/Page/__tests__/exists.test.js b/classes/Page/__tests__/exists.test.js deleted file mode 100644 index 6496573c..00000000 --- a/classes/Page/__tests__/exists.test.js +++ /dev/null @@ -1,28 +0,0 @@ -import Page from "../Page.js" - -describe('Page Class looks how we expect it to. #Page_exists_unit', () => { - it('Imports Page', () => { - expect(typeof Page).toBe('function') - }) - - const page = new Page("layerID", { id: "canvasID", label: "Canvas Label", target: "https://example.com/canvas" }) - it('has expected methods', () => { - expect(typeof page.update).toBe('function') - expect(typeof page.delete).toBe('function') - expect(typeof Page.build).toBe('function') - }) - - it('has expected properties', () => { - expect(page).toHaveProperty('id') - expect(page).toHaveProperty('label') - expect(page).toHaveProperty('target') - }) - - it('throws an error for poorly formed new Page calls', () => { - expect(() => new Page()).toThrow() // No arguments - expect(() => new Page("layerID")).toThrow() // Missing required arguments - expect(() => new Page("layerID", null)).toThrow() // Null canvas object - expect(() => new Page("layerID", { id: null, label: "Canvas Label", target: "https://example.com/canvas" })).toThrow() // Invalid canvas ID - expect(() => new Page("layerID", { id: "canvasID", label: "Canvas Label", target: null })).toThrow() // Invalid canvas target - }) -}) diff --git a/classes/Project/__tests__/Project.test.js b/classes/Project/__tests__/Project.test.js deleted file mode 100644 index 04d282bf..00000000 --- a/classes/Project/__tests__/Project.test.js +++ /dev/null @@ -1,65 +0,0 @@ -import { validateProjectPayload } from "../../../utilities/validatePayload.js" -import Project from "../Project.js"; -import dbDriver from "../../../database/driver.js" -import { sendMail } from "../../../utilities/mailer/index.js" -import User from "../../User/User.js"; -import Group from "../../Group/Group.js" -import { jest } from "@jest/globals"; - -jest.mock("../../../database/driver.js", () => { - return { - save: jest.fn(), - remove: jest.fn().mockResolvedValue({ _id: "deleted" }), - }; -}); -jest.mock("../../../utilities/mailer/index.js"); -jest.mock("../../../utilities/validatePayload.js", () => { - return { - validateProjectPayload: jest.fn(), - }; -}); -jest.mock("../../User/User.js", () => { - return { - User: jest.fn().mockImplementation(() => { - return { - getByEmail: jest.fn(), - save: jest.fn(), - }; - }), - }; -}); -jest.mock("../../Group/Group.js", () => { - return jest.fn().mockImplementation(() => { - return { - addMember: jest.fn(), - removeMember: jest.fn(), - }; - }); -}); - -describe("Project Class", () => { - let project; - - beforeEach(() => { - project = new Project(); - }); - - test.skip("create method should save project", async () => { - validatePayload.validateProjectPayload.mockReturnValue(true); - dbDriver.save.mockResolvedValue({ _id: "newProjectId" }); - - const result = await project.create({ name: "Test Project" }); - - expect(validatePayload.validateProjectPayload).toHaveBeenCalledWith({ name: "Test Project" }); - expect(dbDriver.save).toHaveBeenCalledWith({ name: "Test Project" }); - expect(result).toEqual({ _id: "newProjectId" }); - }); - - test.skip("delete method should remove project", async () => { - const result = await project.delete("projectId"); - - expect(dbDriver.remove).toHaveBeenCalledWith("projectId"); - expect(result).toEqual({ _id: "deleted" }); - }); - -}); diff --git a/classes/Project/__tests__/exists_unit.test.js b/classes/Project/__tests__/exists_unit.test.js deleted file mode 100644 index 63151de7..00000000 --- a/classes/Project/__tests__/exists_unit.test.js +++ /dev/null @@ -1,44 +0,0 @@ -import Project from "../Project.js" -import ProjectFactory from "../ProjectFactory.js" - -describe("ProjectFactory Class #importTests", () => { - it("should have a constructor", () => { - expect(ProjectFactory.prototype.constructor).toBeInstanceOf(Function) - }) - - it("should have a static loadManifest method", () => { - expect(typeof ProjectFactory.loadManifest).toBe("function") - }) - - it("should have a static DBObjectFromManifest method", () => { - expect(typeof ProjectFactory.DBObjectFromManifest).toBe("function") - }) - - it("should have a static fromManifest method", () => { - expect(typeof ProjectFactory.fromManifestURL).toBe("function") - }) -}) - - - -describe("Project Class ", () => { - it("should have a constructor", () => { - expect(Project.prototype.constructor).toBeInstanceOf(Function) - }) - - it("should have a create method", () => { - expect(typeof Project.prototype.create).toBe("function") - }) - - it("should have a sendInvite method", () => { - expect(typeof Project.prototype.sendInvite).toBe("function") - }) - - it("should have a removeMember method", () => { - expect(typeof Project.prototype.removeMember).toBe("function") - }) - - it("should have a checkUserAccess method", () => { - expect(typeof Project.prototype.checkUserAccess).toBe("function") - }) -}) diff --git a/classes/Tools/Tools.js b/classes/Tools/Tools.js index a1d935bc..199b583c 100644 --- a/classes/Tools/Tools.js +++ b/classes/Tools/Tools.js @@ -214,11 +214,11 @@ export default class Tools { { "label": "History Tool", "toolName": "history", - "custom": { - "enabled": true, + "custom": { + "enabled": true, "tagName": "tpen-line-history" }, - "url": "https://app.t-pen.org/components/line-history/index.js", + "url": "https://centerfordigitalhumanities.github.io/tpen-line-history/tpen-line-history.js", "location": "pane" }, { diff --git a/classes/User/__tests__/exists.test.js b/classes/User/__tests__/exists.test.js deleted file mode 100644 index ded76cf5..00000000 --- a/classes/User/__tests__/exists.test.js +++ /dev/null @@ -1,17 +0,0 @@ -import User from "../User.js" - -const user = new User() - -describe("user Class appears and behaves as expected #user_exists_test #user_class", () => { - it("Imports user", () => { - expect(User.constructor).toBeInstanceOf(Function) - }) - - it("Has required methods", () => { - expect(user.getSelf).toBeInstanceOf(Function) - expect(user.getProjects).toBeInstanceOf(Function) - expect(user.getPublicInfo).toBeInstanceOf(Function) - expect(user.getByEmail).toBeInstanceOf(Function) - expect(User.create).toBeInstanceOf(Function) - }) -}) diff --git a/classes/User/__tests__/unit.test.js b/classes/User/__tests__/unit.test.js deleted file mode 100644 index 9ca2396b..00000000 --- a/classes/User/__tests__/unit.test.js +++ /dev/null @@ -1,181 +0,0 @@ -import {expect, jest} from "@jest/globals" -import request from "supertest" -import express from "express" -import User from "../User.js" - -import privateProfileRouter from "../../../userProfile/privateProfile.js" -import mainApp from "../../../app.js" - -jest.mock("../User.js") -jest.mock("../../../auth/index.js") - -const app = express() - -let token = process.env.TEST_TOKEN - -// app.use( (req, res, next)=>{ -// req.auth = { -// payload: { -// "http://store.rerum.io/agent": "agent_value", -// _id: "123456" -// }, -// token:`Bearer ${token}` -// } -// req.user = { -// _id: "123456", -// username: "exampleUser", -// profile: { name: "John Doe" } -// } - -// next() - -// }) - -app.use("/my", privateProfileRouter) - -describe.skip("GET /my/profile #user_class", () => { - it("should return 200", async () => { - const response = await request(mainApp) - .get("/my/profile") - .set("Authorization", `Bearer ${token}`) - expect(response.status).toBe(200) - }) -}) - -describe.skip("GET /my/projects #user_class", () => { - it("should return 200", async () => { - const response = await request(mainApp) - .get("/my/projects") - .set("Authorization", `Bearer ${token}`) - expect(response.status).toBe(200) - }) -}) - -describe.skip("GET /my/profile #user_class", () => { - it("should return 200", async () => { - const response = await request(app) - .get("/my/profile") - .set("Authorization", `Bearer ${token}`) - expect(response.status).toBe(200) - }) -}) - -describe.skip("GET /my/projects #user_class", () => { - it("should return 200", async () => { - const response = await request(app) - .get("/my/projects") - .set("Authorization", `Bearer ${token}`) - expect(response.status).toBe(200) - }) -}) - -describe("GET /my/profile #user_class", () => { - beforeAll(() => { - jest.spyOn(User.prototype, "getSelf").mockResolvedValue({ - _id: "123456", - userData: {name: "VOO"} - }) - }) - - afterAll(() => { - jest.restoreAllMocks() - }) - - it.skip("should return user profile data", async () => { - const response = await request(app) - .get("/my/profile") - .set("Authorization", `Bearer ${token}`) - expect(response.status).toBe(200) - expect(response.body).toHaveProperty("userData") - expect(response.body.userData).toEqual({name: "VOO"}) - }) - - it("should return 401 if user is not authenticated (no authorization header)", async () => { - const appWithoutAuth = express() - appWithoutAuth.use("/my", privateProfileRouter) - const response = await request(appWithoutAuth).get("/my/profile") - expect(response.status).toBe(401) - }) - - it("should return 401 if user is not authenticated (invalid authorization header)", async () => { - const appWithoutAuth = express() - appWithoutAuth.use("/my", privateProfileRouter) - const response = await request(appWithoutAuth) - .get("/my/profile") - .set("Authorization", `Bearer 123123123123123123123123123`) - expect(response.status).toBe(401) - }) -}) - -describe("GET /my/projects #user_class", () => { - beforeAll(() => { - jest.spyOn(User.prototype, "getProjects").mockResolvedValue([ - { - _id: "project.id", - "@type": "Project", - creator: "user.agent", - groups: { - members: [{agent: "user.agent", _id: "user._id"}] - } - } - ]) - }) - - afterAll(() => { - jest.restoreAllMocks() - }) - - it.skip("should return user projects as an array", async () => { - const response = await request(app) - .get("/my/projects") - .set("Authorization", `Bearer ${token}`) - expect(response.status).toBe(200) - expect(Array.isArray(response.body)).toBe(true) - - if (Array.isArray(response.body)) { - for (const obj of response.body) { - expect(obj).toHaveProperty("@type", "Project") - } - } - }) - it("should return 401 if user is not authenticated (no authorization header)", async () => { - const appWithoutAuth = express() - appWithoutAuth.use("/my", privateProfileRouter) - const response = await request(appWithoutAuth).get("/my/profile") - expect(response.status).toBe(401) - }) - - it("should return 401 if user is not authenticated (invalid authorization header)", async () => { - const appWithoutAuth = express() - appWithoutAuth.use("/my", privateProfileRouter) - const response = await request(appWithoutAuth) - .get("/my/profile") - .set("Authorization", `Bearer 123123123123123123123123123`) - expect(response.status).toBe(401) - }) -}) - -describe("GET /my/projects with no projects #user_class", () => { - beforeAll(() => { - jest.spyOn(User.prototype, "getProjects").mockResolvedValue([]) - jest.spyOn(User.prototype, "getSelf").mockResolvedValue({ - _id: "123456", - _lastModified: null - }) - }) - - afterAll(() => { - jest.restoreAllMocks() - }) - - it.skip("should return 200 with empty projects array when user has no projects", async () => { - const response = await request(app) - .get("/my/projects") - .set("Authorization", `Bearer ${token}`) - expect(response.status).toBe(200) - expect(response.body).toHaveProperty("projects") - expect(Array.isArray(response.body.projects)).toBe(true) - expect(response.body.projects.length).toBe(0) - expect(response.body.metrics).toBeNull() - }) -}) diff --git a/config.env b/config.env index 1bb3ac5a..49231eba 100644 --- a/config.env +++ b/config.env @@ -7,9 +7,9 @@ # 2. Copy .env.production to .env for production # 3. Edit .env with environment-specific values # -# Configuration is loaded via env-loader.js using the dotenv package: +# Configuration is loaded via Node native env-file flags in npm scripts: # 1. config.env is loaded first (this file - safe defaults) -# 2. .env.{NODE_ENV} is loaded second (environment-specific: .env.development, .env.production, .env.test) +# 2. .env.development is loaded second (development defaults) # 3. .env is loaded last (local/server overrides - HIGHEST PRIORITY) # Server Configuration diff --git a/database/__tests__/unit.test.js b/database/__tests__/unit.test.js deleted file mode 100644 index 6b4f85a1..00000000 --- a/database/__tests__/unit.test.js +++ /dev/null @@ -1,86 +0,0 @@ -/** - * This should test that the Driver is put together and that its pices - * map to the correct controllers. - * - * @author Bryan Haberberger - * https://github.com/thehabes - */ - -import DatabaseDriver from "../driver.js" -const TIME_OUT = process.env.DB_TEST_TIMEOUT ?? 6500 - -describe("Driver CRUD and query is registered. #driver_unit #db", () => { - const d = new DatabaseDriver() - it("create", async () => { - expect(typeof d.save).toBe("function") - }) - it("update", async () => { - expect(typeof d.update).toBe("function") - }) - it("delete", async () => { - expect(typeof d.delete).toBe("function") - }) - it("find", async () => { - expect(typeof d.find).toBe("function") - }) - it("choose controller", async () => { - expect(typeof d.chooseController).toBe("function") - }) - it("connected", async () => { - expect(typeof d.connected).toBe("function") - }) - it("close", async () => { - expect(typeof d.close).toBe("function") - }) - it("reserveId", async () => { - expect(typeof d.reserveId).toBe("function") - }) -}) - -describe("Can connect to all registered controllers. #driver_unit #db", () => { - it.skip( - "Tiny Connection", - async () => { - const d = new DatabaseDriver() - await d.chooseController("tiny") - expect(await d.connected()).toBe(true) - }, - TIME_OUT - ) - it.skip( - "Mongo Connection", - async () => { - const d = new DatabaseDriver() - await d.chooseController("mongo") - expect(await d.connected()).toBe(true) - }, - TIME_OUT - ) - it("Maria Connection Stub", async () => { - expect(true).toBeTruthy() - }) -}) - -describe("Can connect to all registered controllers with applied parameter. #driver_unit #db", () => { - it.skip( - "Tiny Connection Parameter", - async () => { - const d = new DatabaseDriver("tiny") - expect(await d.connected()).toBe(true) - }, - TIME_OUT - ) - it.skip( - "Mongo Connection Parameter", - async () => { - const d = new DatabaseDriver("mongo") - expect(await d.connected()).toBe(true) - }, - TIME_OUT - ) - it("Maria Connection Parameter Stub", async () => { - expect(true).toBeTruthy() - }) -}) - -// TODO should we test that each CRUD and query action functions, or is that test downstream good enough diff --git a/database/maria/__tests__/unit.test.js b/database/maria/__tests__/unit.test.js deleted file mode 100644 index d194b678..00000000 --- a/database/maria/__tests__/unit.test.js +++ /dev/null @@ -1,14 +0,0 @@ -/** - * This should test unit actions against the MariaDB Controller. - * - * @author Bryan Haberberger - * https://github.com/thehabes -*/ -import DatabaseController from '../controller.js' -const database = new DatabaseController() - -describe('A MARIADB stub test that is always true. #maria_unit #db',()=>{ - it('MARIA says hello', async () => { - expect(true).toBeTruthy() - }) -}) diff --git a/database/mongo/__tests__/unit.test.js b/database/mongo/__tests__/unit.test.js deleted file mode 100644 index a3d3f0ef..00000000 --- a/database/mongo/__tests__/unit.test.js +++ /dev/null @@ -1,225 +0,0 @@ -/** - * This should test unit actions against the MongoDB Controller. - * - * @author Bryan Haberberger - * https://github.com/thehabes - */ - -import DatabaseController from "../controller.js" -const database = new DatabaseController() -const TIME_OUT = process.env.DB_TEST_TIMEOUT ?? 6500 - -let test_proj = { - name: "Test Project", - group: "Test Group", - metadata: [], - layers: [], - label: "Test Label", - manifest: ["Test Manifest"], - creator: "Test Creator" -} -let test_group = {"@type": "Group", name: "Test Group", members: []} -let test_user = {"@type": "User", name: "Test User", _sub: `auth${Date.now().toString().slice(-8)}`, agent: `agent${Date.now().toString().slice(-8)}`} - -// Initialize the IDs to null to avoid cleanup attempts of non-existent objects -test_proj._id = null -test_group._id = null -test_user._id = null - -beforeAll(async () => { - return await database.connect() -}, 10000) - -afterAll(async () => { - // Only attempt to remove objects if they have valid IDs - const testObjects = [ - { obj: test_proj, collection: "project" }, - { obj: test_group, collection: "groups" }, - { obj: test_user, collection: "users" } - ] - const cleanupPromises = testObjects - .filter(({ obj }) => obj._id) - .map(({ obj, collection }) => database.remove(obj._id, collection)) - await Promise.all(cleanupPromises) - await database.close() - return -}, 10000) - -describe.skip("Mongo Database Unit Functions. #mongo_unit #db", () => { - it( - "connects for an active connection", - async () => { - const result = await database.connected() - expect(result).toBe(true) - }, - TIME_OUT - ) - - it( - "creates a new project", - async () => { - const result = await database.save(test_proj, "Project") - test_proj._id = result._id - expect(result._id).toBeTruthy() - }, - TIME_OUT - ) - - it( - "creates a new group", - async () => { - const result = await database.save(test_group, "groups") - test_group._id = result._id - expect(result._id).toBeTruthy() - }, - TIME_OUT - ) - - it( - "creates a new User", - async () => { - const result = await database.save(test_user, "users") - test_user._id = result._id - expect(result._id).toBeTruthy() - }, - TIME_OUT - ) - - it( - "updates an existing project", - async () => { - test_proj.name = "Test Project -- Updated" - const result = await database.update(test_proj, "Project") - expect(result["_id"]).toBeTruthy() - }, - TIME_OUT - ) - it( - "updates an existing group", - async () => { - test_group.name = "Test Group -- Updated" - await database.update(test_group, "groups") - const result = await database.update(test_group, "groups") - expect(result["_id"]).toBeTruthy() - }, - TIME_OUT - ) - it( - "updates an existing User", - async () => { - test_user.name = "Test User -- Updated" - await database.update(test_user, "users") - const result = await database.update(test_user, "users") - expect(result["_id"]).toBeTruthy() - }, - TIME_OUT - ) - - it( - "Finds matching projects by query", - async () => { - const result = await database.find(test_proj, "Project") - expect(result[0]["_id"]).toBe(test_proj["_id"]) - }, - TIME_OUT - ) - it( - "Finds matching groups by query", - async () => { - const result = await database.find(test_group, "groups") - expect(result[0]["_id"]).toBe(test_group["_id"]) - }, - TIME_OUT - ) - it( - "Finds matching User by query", - async () => { - const result = await database.find(test_user, "users") - expect(result[0]["_id"]).toBe(test_user["_id"]) - }, - TIME_OUT - ) - - //TODO - it("Deletes an object with the provided id", async () => { - expect(true).toBeTruthy() - }) - it("Validates a possible id string", () => { - expect(database.isValidId(123)).toBeTruthy() - expect(database.isValidId(-123)).toBeTruthy() - expect(database.isValidId("123")).toBeTruthy() - expect(database.isValidId({})).toBeFalsy() - expect(database.isValidId("123abc123abc123abc123abc")).toBeTruthy() - - expect(database.asValidId(123)).toBe(123) - expect(database.asValidId("123abc123abc123abc123abc")).toBe( - "123abc123abc123abc123abc" - ) - expect(database.asValidId({})).toBe("000000000000000000becbec") - expect(database.asValidId(-123)).toBe(-123) - }) -}) - -describe.skip("Mongo Database Unit Functions. #mongo_unit #db", () => { - it("connects for an active connection", async () => { - const result = await database.connected() - expect(result).toBe(true) - }) - it("creates a new project", async () => { - const result = await database.save(test_proj, "Project") - test_proj["_id"] = result["_id"] - expect(result["_id"]).toBeTruthy() - }) - it("creates a new group", async () => { - const result = await database.save(test_group) - test_group["_id"] = result["_id"] - expect(result["_id"]).toBeTruthy() - }) - it("creates a new User", async () => { - const result = await database.save(test_user) - test_user["_id"] = result["_id"] - expect(result["_id"]).toBeTruthy() - }) - - it("updates an existing project", async () => { - test_proj.name = "Test Project -- Updated" - const result = await database.update(test_proj, "Project") - expect(result["_id"]).toBeTruthy() - }) - it("updates an existing group", async () => { - test_group.name = "Test Group -- Updated" - const result = await database.update(test_group) - expect(result["_id"]).toBeTruthy() - }) - it("updates an existing User", async () => { - test_user.name = "Test User -- Updated" - const result = await database.update(test_user) - expect(result["_id"]).toBeTruthy() - }) - - it("Finds matching projects by query", async () => { - const result = await database.find(test_proj, "Project") - expect(result[0]["_id"]).toBe(test_proj["_id"]) - }) - it("Finds matching groups by query", async () => { - const result = await database.find(test_group) - expect(result[0]["_id"]).toBe(test_group["_id"]) - }) - it("Finds matching User by query", async () => { - const result = await database.find(test_user) - expect(result[0]["_id"]).toBe(test_user["_id"]) - }) - - //TODO - it("Deletes an object with the provided id", async () => { - expect(true).toBeTruthy() - }) -}) - -describe("Mongo Database Utilities. #mongo_unit #db", () => { - it("Assigns a new id for an Object", async () => { - const newId = database.reserveId() - expect(typeof newId).toEqual("string") - expect(newId).toHaveLength(24) - }) -}) diff --git a/database/tiny/__tests__/unit.test.js b/database/tiny/__tests__/unit.test.js deleted file mode 100644 index b343bc6a..00000000 --- a/database/tiny/__tests__/unit.test.js +++ /dev/null @@ -1,92 +0,0 @@ -/** - * This should test unit actions against the TinyPEN Controller. - * - * @author Bryan Haberberger - * https://github.com/thehabes - */ - -import DatabaseController from "../controller.js" -const database = new DatabaseController() -const TIME_OUT = process.env.DB_TEST_TIMEOUT ?? 6500 - -let test_manifest = {type: "Manifest", name: "Test Manifest"} - -beforeAll(async () => { - return await database.connect() -}) - -afterAll(async () => { - return await database.close() -}) - -describe.skip("TinyPen Unit Functions. #tiny_unit #db", () => { - it( - `connects for an active connection`, - async () => { - const result = await database.connected() - expect(result).toBe(true) - }, - TIME_OUT - ) - it( - "creates a new object", - async () => { - const result = await database.save(test_manifest) - test_manifest["@id"] = result["@id"] - expect(result["@id"]).toBeTruthy() - }, - TIME_OUT - ) - - it( - "updates an existing object", - async () => { - test_manifest.name = "Test Project -- Updated" - const result = await database.update(test_manifest) - expect(result["@id"]).toBeTruthy() - }, - TIME_OUT - ) - - it( - "Finds matching objects by query", - async () => { - const result = await database.find({"@id": test_manifest["@id"]}) - expect(result[0]["@id"]).toBe(test_manifest["@id"]) - }, - TIME_OUT - ) - - it("Assigns a new id for an Object", async () => { - const noSeedResult = database.reserveId() - const badSeedResult = database.reserveId("🕵️‍♀️🍤") - const goodSeedResult = database.reserveId(500) - expect(typeof noSeedResult).toEqual("string") - expect(noSeedResult).toHaveLength(24) - expect(typeof badSeedResult).toEqual("string") - expect(badSeedResult).toHaveLength(24) - expect(typeof goodSeedResult).toEqual("string") - expect(goodSeedResult).toHaveLength(24) - }) - - //TODO - it("Deletes an object with the provided id", async () => { - expect(true).toBeTruthy() - }) - - it("Validates a possible id string", async () => { - expect(database.isValidId(123)).toBeTruthy() - expect(database.isValidId(-123)).toBeTruthy() - expect(database.isValidId("123")).toBeTruthy() - expect(database.isValidId({})).toBeFalsy() - expect(database.isValidId("123abc123abc123abc123abc")).toBeTruthy() - expect(database.isValidId("123abc123abc123abc123abcTooLong")).toBeFalsy() - - expect(database.asValidId(123)).toBe(123) - expect(database.asValidId("123abc123abc123abc123abc")).toBe( - "123abc123abc123abc123abc" - ) - expect(database.asValidId({})).toBe("000000000000000000becbec") - expect(database.asValidId(-123)).toBe(-123) - }) -}) diff --git a/env-loader.js b/env-loader.js index 4b3dd519..55378b30 100644 --- a/env-loader.js +++ b/env-loader.js @@ -1,75 +1,46 @@ /** * Environment Variable Loader * - * Preloads environment variables before application starts. - * Uses --import flag to guarantee execution before any other imports. - * - * ENVIRONMENT VARIABLE LOADING PRIORITY (lowest to highest): - * 1. config.env - Safe defaults for all environments (committed) - * 2. .env.{NODE_ENV} - Environment-specific (.env.development, .env.production) (committed) - * 3. .env - Local/server secrets and overrides (gitignored) ← HIGHEST PRIORITY - * - * Each level overrides values from levels above it. - * - * FOR LOCAL DEVELOPMENT: - * - Create a .env file with your local database connections, API keys, etc. - * - .env is gitignored and will override ALL other environment files - * - Never commit .env - it contains your personal/local settings - * - * FOR SERVERS (Production/Development): - * - Server .env files contain production/staging database URLs and secrets - * - .env overrides committed .env.production or .env.development values - * - No need to rename existing .env files on servers - * - * FOR TESTING: - * - Tests run with NODE_ENV='test' by default - * - Your local .env overrides will apply to tests - * - Ensures tests use your local database connections - * - * COMMITTED FILES (in git): - * - config.env ✓ Safe defaults for all environments - * - .env.development ✓ Development environment settings - * - .env.production ✓ Production environment settings - * - * GITIGNORED FILES (local/server only): - * - .env ✗ Your personal/server secrets (HIGHEST PRIORITY) - * - * VIEWING OUTPUT LOGS: - * When running under PM2, env-loader.js output appears in PM2's system logs - * (~/.pm2/pm2.log), NOT in application logs (./logs/pm2-out.log), because - * this module executes BEFORE the application starts. - * - * To view env-loader output: - * - PM2 system logs: pm2 logs --lines 50 --nostream - * - Application logs: tail -f ./logs/pm2-out.log + * Bootstraps environment variables before the application starts. + * Imported at the top of bin/tpen3_services.js so PM2 (which invokes + * `node ./bin/tpen3_services.js` directly, bypassing the npm scripts' + * --env-file flags) still loads the same layered config. + * + * LOADING PRIORITY (highest to lowest): + * 1. .env - Local/server overrides (gitignored) + * 2. .env.{NODE_ENV} - Environment-specific (.env.development, .env.production) + * 3. config.env - Safe defaults (committed) + * + * Files are loaded in priority order (highest first) because Node's + * native `process.loadEnvFile()` does NOT override values already present + * in process.env — so the first file to define a key wins. + * + * This intentionally mirrors the priority of the npm scripts' + * `--env-file=config.env --env-file-if-exists=.env.development --env-file-if-exists=.env` + * chain, where `--env-file` CLI flags DO override and the last file wins. + * Both code paths converge on the same end-state precedence. */ -import dotenv from 'dotenv' -import { existsSync } from 'fs' -import { fileURLToPath } from 'url' -import { dirname, join } from 'path' - -// Get the directory where this file lives (absolute path) -const __filename = fileURLToPath(import.meta.url) -const __dirname = dirname(__filename) +import { existsSync } from 'node:fs' +import { fileURLToPath } from 'node:url' +import { dirname, join } from 'node:path' +const __dirname = dirname(fileURLToPath(import.meta.url)) const env = process.env.NODE_ENV || 'development' -// Load in priority order (later files override earlier ones) -// Using absolute paths to ensure they work in PM2 cluster mode on RHEL const files = [ - join(__dirname, 'config.env'), // 1. Base defaults (lowest priority) - join(__dirname, `.env.${env}`), // 2. Environment-specific (.env.development, .env.production, .env.test) - join(__dirname, '.env') // 3. Local/server overrides (HIGHEST PRIORITY) + join(__dirname, '.env'), + join(__dirname, `.env.${env}`), + join(__dirname, 'config.env') ] -files.forEach(file => { +for (const file of files) { if (existsSync(file)) { - dotenv.config({ path: file, override: true }) - console.log(`✓ Loaded environment file: ${file}`) + process.loadEnvFile(file) + console.log(`\x1b[32m✓\x1b[0m Loaded environment file: ${file}`) } else { - console.log(`✗ Missing environment file: ${file}`) + console.log(`\x1b[33m✗\x1b[0m Missing environment file: ${file}`) } -}) +} -console.log(`✓ Environment loaded: ${env}`) +console.log(`\x1b[1m✓ Environment loaded:\x1b[0m ${env}`) diff --git a/jest.config.js b/jest.config.js deleted file mode 100644 index 71145347..00000000 --- a/jest.config.js +++ /dev/null @@ -1,213 +0,0 @@ -/* - * For a detailed explanation regarding each configuration property, visit: - * https://jestjs.io/docs/configuration - */ - -let config = { - // All imported modules in your tests should be mocked automatically - // automock: false, - - // Stop running tests after `n` failures - // bail: 0, - - // The directory where Jest should store its cached dependency information - // cacheDirectory: "C:\\Users\\cubap\\AppData\\Local\\Temp\\jest", - - // Automatically clear mock calls, instances and results before every test - // clearMocks: false, - - //This will tell you why jest couldn't close. Right now, it will flag the client.connect() b/c there is no client.close() - //That is OK in the testing scenario. In production, only one connection is made and it is closed when the app exits. - detectOpenHandles : false, - - // Timeout for tests. Give them 30 seconds so that it has a chance to connect to a db - // testTimeout: 9000, - - displayName: { - name: 'TPEN3 Services', - color: 'cyan', - }, - - // Indicates whether the coverage information should be collected while executing the test - // collectCoverage: true, - - // An array of glob patterns indicating a set of files for which coverage information should be collected - collectCoverageFrom: [ - //"**/*.js", - // "**/manifest/index.js", - // "**/app.js", - // "**/index.js" - ], - - // Indicates which provider should be used to instrument code for coverage - coverageProvider: "v8", - - // A list of reporter names that Jest uses when writing coverage reports - coverageReporters: [ - "json", - "text", - "html" - ], - - // Indicates whether each individual test should be reported during the run - verbose: true, - - //Don't show console.log and console.debug from the app code - silent: true, - - // The root directory that Jest should scan for tests and modules within - rootDir: "./", - - testMatch: [ "**/__tests__/**/*.js", "**/?(*.)+(spec|test).js" ], - - // The directory where Jest should output its coverage files. Default is /coverage/. See /coverage/index.html. - // coverageDirectory: undefined, - - // An array of regexp pattern strings used to skip coverage collection - coveragePathIgnorePatterns: [ - "\\\\node_modules\\\\" - ], - - // An object that configures minimum threshold enforcement for coverage results - // coverageThreshold: undefined, - - // A path to a custom dependency extractor - // dependencyExtractor: undefined, - - // Make calling deprecated APIs throw helpful error messages - // errorOnDeprecated: false, - - // Force coverage collection from ignored files using an array of glob patterns - // forceCoverageMatch: [], - - // A path to a module which exports an async function that is triggered once before all test suites - // globalSetup: undefined, - - // A path to a module which exports an async function that is triggered once after all test suites - // globalTeardown: undefined, - - // A set of global variables that need to be available in all test environments - // globals: {}, - - // The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers. - // maxWorkers: "50%", - - // An array of directory names to be searched recursively up from the requiring module's location - // moduleDirectories: [ - // "node_modules" - // ], - - // An array of file extensions your modules use - moduleFileExtensions: [ - "js", - "json" - ], - - // A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module - // moduleNameMapper: {}, - - // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader - // modulePathIgnorePatterns: [], - - // Activates notifications for test results - // notify: false, - - // An enum that specifies notification mode. Requires { notify: true } - // notifyMode: "failure-change", - - // A preset that is used as a base for Jest's configuration - // preset: `@shelf/jest-mongodb`, - - // Run tests from one or more projects - // projects: undefined, - - // Use this configuration option to add custom reporters to Jest - // reporters: undefined, - - // Automatically reset mock state before every test - // resetMocks: false, - - // Reset the module registry before running each individual test - // resetModules: false, - - // A path to a custom resolver - // resolver: undefined, - - // Automatically restore mock state and implementation before every test - // restoreMocks: false, - - // A list of paths to directories that Jest should use to search for files in - // roots: [ - // "./__tests__" - // ], - - // Allows you to use a custom runner instead of Jest's default test runner - // runner: "jest-runner", - - // The paths to modules that run some code to configure or set up the testing environment before each test - // setupFiles: [], - - // A list of paths to modules that run some code to configure or set up the testing framework before each test - // setupFilesAfterEnv: [], - - // The number of seconds after which a test is considered as slow and reported as such in the results. - // slowTestThreshold: 5, - - // A list of paths to snapshot serializer modules Jest should use for snapshot testing - // snapshotSerializers: [], - - // The test environment that will be used for testing - // testEnvironment: "jest-environment-node", - - // Options that will be passed to the testEnvironment - // testEnvironmentOptions: {}, - - // Adds a location field to test results - // testLocationInResults: false, - - // The glob patterns Jest uses to detect test files - // testMatch: [ - // "**/__tests__/**/*.[jt]s?(x)", - // "**/?(*.)+(spec|test).[tj]s?(x)" - // ], - - // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped - testPathIgnorePatterns: [ - "\\\\node_modules\\\\", - "__tests__/smoke.test.js" - ], - - // The regexp pattern or array of patterns that Jest uses to detect test files - // testRegex: [], - - // This option allows the use of a custom results processor - // testResultsProcessor: undefined, - - // This option allows use of a custom test runner - // testRunner: "jest-circus/runner", - - // This option sets the URL for the jsdom environment. It is reflected in properties such as location.href - // testURL: "http://localhost", - - // Setting this value to "fake" allows the use of fake timers for functions such as "setTimeout" - // timers: "real", - - // A map from regular expressions to paths to transformers - transform: {}, - - // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation - // transformIgnorePatterns: [ - // "\\\\node_modules\\\\", - // "\\.pnp\\.[^\\\\]+$" - // ], - - // An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them - // unmockedModulePathPatterns: undefined, - - // An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode - watchPathIgnorePatterns: ['globalConfig'], - - // Whether to use watchman for file crawling - // watchman: true, -} -export {config as default} diff --git a/layer/__tests__/layer_routes.test.js b/layer/__tests__/layer_routes.test.js deleted file mode 100644 index 031a73cc..00000000 --- a/layer/__tests__/layer_routes.test.js +++ /dev/null @@ -1,148 +0,0 @@ -import request from 'supertest' -import express from 'express' -import layerRouter from '../index.js' -import Project from '../../classes/Project/Project.js' -import Layer from '../../classes/Layer/Layer.js' -import { jest } from '@jest/globals' - -const app = express() -app.use(express.json()) - -// Mock authentication middleware to bypass authentication -jest.mock('../../auth/index.js', () => jest.fn((req, res, next) => { - req.user = { id: 'test-user' } // Mock a valid user - next() -})) - -app.use('/project/:projectId/layer', layerRouter) - -// Project is not mocking... -jest.mock('../../classes/Project/Project.js', () => { - const mockProject = jest.fn().mockImplementation(() => ({ - loadProject: jest.fn(), - updateLayer: jest.fn() - })) - return { default: mockProject } -}) - -jest.mock('../../classes/Layer/Layer.js', () => ({ - Layer: jest.fn() -})) - -describe('Layer Routes', () => { - beforeEach(() => { - jest.clearAllMocks() - }) - - describe.skip('POST /project/:projectId/layer', () => { - it('should reject suspicious content in request body', async () => { - const suspiciousLayer = { - label: '', - canvases: ['canvas1', 'canvas2'] - } - - const res = await request(app) - .post('/project/123/layer') - .send(suspiciousLayer) - - expect(res.status).toBe(400) - }) - - it('should reject suspicious content in canvases array', async () => { - const suspiciousLayer = { - label: 'Clean Label', - canvases: ['eval(malicious)', 'canvas2'] - } - - const res = await request(app) - .post('/project/123/layer') - .send(suspiciousLayer) - - expect(res.status).toBe(400) - }) - - it('should create a new layer and return 201', async () => { - const mockLayer = { label: 'Layer 1', canvases: ['canvas1', 'canvas2'] } - const mockProject = new Project() - mockProject.loadProject.mockResolvedValue({ layers: [] }) - - Layer.build = jest.fn().mockReturnValue({ update: jest.fn(), asProjectLayer: jest.fn().mockReturnValue(mockLayer) }) - - const res = await request(app) - .post('/project/123/layer') - .send(mockLayer) - - expect(res.status).toBe(201) - expect(res.body).toEqual(mockLayer) - expect(Layer.build).toHaveBeenCalledWith('123', 'Layer 1', ['canvas1', 'canvas2'], 'test-user') - }) - - it('should return 400 for invalid input', async () => { - const res = await request(app) - .post('/project/123/layer') - .send({ label: 'Layer 1' }) // Missing canvases - - expect(res.status).toBe(400) - expect(res.body.message).toBe('Invalid layer data. Provide a label and an array of canvas IDs.') - }) - }) - - describe.skip('PUT /project/:projectId/layer/:layerId', () => { - it('should reject suspicious content in request body', async () => { - const suspiciousLayer = { - label: '', - canvases: ['canvas1', 'canvas2'] - } - - const res = await request(app) - .put('/project/123/layer/layer1') - .send(suspiciousLayer) - - expect(res.status).toBe(400) - }) - - it('should reject suspicious content in pages array', async () => { - const suspiciousLayer = { - label: 'Clean Label', - pages: [ - { id: 'page1', annotations: [] }, - { id: 'eval(malicious)', annotations: [] } - ] - } - - const res = await request(app) - .put('/project/123/layer/layer1') - .send(suspiciousLayer) - - expect(res.status).toBe(400) - }) - - it('should update an existing layer and return 200', async () => { - const mockLayer = { label: 'Updated Layer', canvases: ['canvas1', 'canvas2'] } - const mockProject = new Project() - mockProject.loadProject.mockResolvedValue({ layers: [{ id: 'layer1', label: 'Old Layer', canvases: [] }] }) - mockProject.updateLayer.mockResolvedValue() - - const res = await request(app) - .put('/project/123/layer/layer1') - .set('Authorization', 'token') - .send(mockLayer) - - expect(res.status).toBe(200) - expect(res.body.label).toBe('Updated Layer') - expect(mockProject.updateLayer).toHaveBeenCalled() - }) - - it('should return 404 if layer is not found', async () => { - const mockProject = new Project() - mockProject.loadProject.mockResolvedValue({ layers: [] }) - - const res = await request(app) - .put('/project/123/layer/layer1') - .send({ label: 'Updated Layer', canvases: ['canvas1'] }) - - expect(res.status).toBe(404) - expect(res.body.message).toBe('Layer not found in project') - }) - }) -}) diff --git a/line/__tests__/lineRouter.test.js b/line/__tests__/lineRouter.test.js deleted file mode 100644 index 1d33dd72..00000000 --- a/line/__tests__/lineRouter.test.js +++ /dev/null @@ -1,129 +0,0 @@ -import request from 'supertest' -import app from '../../app.js' -import Line from '../../classes/Line/Line.js' -import { jest } from '@jest/globals' - -jest.mock('../../classes/Line/Line.js', () => { - const mockLine = jest.fn() - mockLine.prototype.load = jest.fn() - mockLine.prototype.save = jest.fn() - mockLine.prototype.update = jest.fn() - mockLine.prototype.updateText = jest.fn() - mockLine.prototype.updateBounds = jest.fn() - return { Line: mockLine } -}) - -// mockResolved is all weird. -describe.skip('lineRouter API tests', () => { - beforeEach(() => { - jest.clearAllMocks() - }) - - it('GET /project/:pid/page/:page/line/:line should load a line', async () => { - Line.prototype.constructor.mockResolvedValue({ - asJSON: () => ({ id: '123', body: 'Sample Line', target: 'https://example.com?xywh=10,10,100,100' }) - }) - - const response = await request(app).get('/project/1/page/1/line/123') - - expect(response.status).toBe(200) - expect(response.body).toEqual({ id: '123', body: 'Sample Line', target: 'https://example.com?xywh=10,10,100,100' }) - }) - - it('POST /project/:pid/page/:pageid/line/ should create a line', async () => { - Line.prototype.save.mockResolvedValue({ - asJSON: () => ({ id: '123', body: 'New Line', target: 'https://example.com?xywh=10,10,100,100' }) - }) - - const response = await request(app) - .post('/project/1/page/1/line/') - .send({ body: 'New Line', target: 'https://example.com?xywh=10,10,100,100' }) - - expect(response.status).toBe(201) - expect(response.body).toEqual({ id: '123', body: 'New Line', target: 'https://example.com?xywh=10,10,100,100' }) - }) - - it('POST /project/:pid/page/:pageid/line/ should detect suspicious content', async () => { - const annotations = [ - { - id: 'anno-1', - body: {'value': 'This is fine'}, - target: 'canvas#xywh=0,0,100,100' - }, - { - id: 'anno-2', - body: {'value': ''}, - target: 'canvas#xywh=0,100,100,100' - } - ] - - const response = await request(app) - .post('/project/1/page/1/line/') - .send(annotations) - - expect(response.status).toBe(400) - }) - - it('PUT /project/:pid/page/:page/line/:line should update a line', async () => { - Line.prototype.update.mockResolvedValue({ - asJSON: () => ({ id: '123', body: 'Updated Line', target: 'https://example.com?xywh=10,10,100,100' }) - }) - - const response = await request(app) - .put('/project/1/page/1/line/123') - .send({ body: 'Updated Line', target: 'https://example.com?xywh=10,10,100,100' }) - - expect(response.status).toBe(200) - expect(response.body).toEqual({ id: '123', body: 'Updated Line', target: 'https://example.com?xywh=10,10,100,100' }) - }) - - it('PUT /project/:pid/page/:pageid/line/:line should reject suspicious content', async () => { - const suspiciousUpdate = { - id: 'anno-1', - body: { value: '' }, - target: 'canvas#xywh=0,0,100,100' - } - - const response = await request(app) - .put('/project/1/page/1/line/123') - .send(suspiciousUpdate) - - expect(response.status).toBe(400) - }) - - it('PATCH /project/:pid/page/:page/line/:line/text should update line text', async () => { - Line.prototype.updateText.mockResolvedValue({ - asJSON: () => ({ id: '123', body: 'Updated Text', target: 'https://example.com?xywh=10,10,100,100' }) - }) - - const response = await request(app) - .patch('/project/1/page/1/line/123/text') - .send({ body: 'Updated Text' }) - - expect(response.status).toBe(200) - expect(response.body).toEqual({ id: '123', body: 'Updated Text', target: 'https://example.com?xywh=10,10,100,100' }) - }) - - it('PATCH /project/:pid/page/:page/line/:line/text should detect suspicious content', async () => { - const suspiciousPatch = 'while(true) return true' - - const response = await request(app) - .patch('/project/1/page/1/line/123') - .send(suspiciousPatch) - - expect(response.status).toBe(400) - }) - - it('PATCH /project/:pid/page/:page/line/:line/bounds should update line bounds', async () => { - Line.prototype.updateBounds.mockResolvedValue({ - asJSON: () => ({ id: '123', body: 'Sample Line', target: 'https://example.com?xywh=20,20,200,200' }) - }) - - const response = await request(app) - .patch('/project/1/page/1/line/123/bounds') - .send({ x: 20, y: 20, w: 200, h: 200 }) - - expect(response.status).toBe(200) - expect(response.body).toEqual({ id: '123', body: 'Sample Line', target: 'https://example.com?xywh=20,20,200,200' }) - }) -}) diff --git a/openapi/components/tpen-services-shared-components.openapi.yaml b/openapi/components/tpen-services-shared-components.openapi.yaml new file mode 100644 index 00000000..c11dea10 --- /dev/null +++ b/openapi/components/tpen-services-shared-components.openapi.yaml @@ -0,0 +1,13 @@ +openapi: 3.0.3 +info: + title: TPEN Services Shared OpenAPI Components + version: 0.1.0 + description: Shared reusable OpenAPI components for TPEN services contracts. +externalDocs: + description: TPEN-services API reference + url: https://api.t-pen.org/API.html +x-upstream-provider: + name: TPEN-services + baseUrl: https://api.t-pen.org +components: + schemas: {} diff --git a/package-lock.json b/package-lock.json index 419f5bc9..869cc237 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,6 @@ "cookie-parser": "^1.4.7", "cors": "^2.8.6", "debug": "^4.4.3", - "dotenv": "^17.3.1", "express": "^5.2.1", "express-list-endpoints": "^7.1.1", "express-oauth2-jwt-bearer": "^1.7.4", @@ -26,8 +25,7 @@ "tpen3-services": "file:" }, "devDependencies": { - "@jest/globals": "^30.3.0", - "jest": "^30.3.0", + "c8": "^10.1.3", "nodemon": "^3.1.14", "sinon": "^21.0.2", "supertest": "^7.2.2" @@ -37,4138 +35,1611 @@ "npm": ">=11.0.0" } }, - "node_modules/@babel/code-frame": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", - "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", - "dev": true, + "node_modules/@iiif/helpers": { + "version": "1.5.8", + "resolved": "https://registry.npmjs.org/@iiif/helpers/-/helpers-1.5.8.tgz", + "integrity": "sha512-jNLhsmz5LsK3FhBtC6cIEoUM057zVL8zF0a8aQPFM5i6U/tf3QPFiq2mmm3Lop1ORFIZlEJXvsCwk7d3GucB2w==", "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.28.5", - "js-tokens": "^4.0.0", - "picocolors": "^1.1.1" + "@iiif/presentation-2": "1.0.4", + "@iiif/presentation-3": "2.2.3", + "@iiif/presentation-3-normalized": "0.9.7", + "@types/geojson": "7946.0.13" }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/compat-data": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", - "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" + "optionalDependencies": { + "abs-svg-path": "^0.1.1", + "parse-svg-path": "^0.1.2", + "svg-arc-to-cubic-bezier": "^3.2.0" + }, + "peerDependencies": { + "@iiif/parser": "^2.2.8" } }, - "node_modules/@babel/core": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", - "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", - "dev": true, + "node_modules/@iiif/parser": { + "version": "2.2.9", + "resolved": "https://registry.npmjs.org/@iiif/parser/-/parser-2.2.9.tgz", + "integrity": "sha512-HJVlnsz6mj56Qda9U7vhXEMxHFuFLeIDTcXqbPEDcuX1iefrWVsuDIY+DxyQO64vBnwme5NWOZbtbO73XNYB6A==", "license": "MIT", + "peer": true, "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-compilation-targets": "^7.28.6", - "@babel/helper-module-transforms": "^7.28.6", - "@babel/helpers": "^7.28.6", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/traverse": "^7.29.0", - "@babel/types": "^7.29.0", - "@jridgewell/remapping": "^2.3.5", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" + "@iiif/presentation-2": "^1.0.4", + "@iiif/presentation-3": "^2.2.2", + "@iiif/presentation-3-normalized": "^0.9.7", + "@types/geojson": "^7946.0.10" } }, - "node_modules/@babel/generator": { - "version": "7.29.1", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", - "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", - "dev": true, + "node_modules/@iiif/presentation-2": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@iiif/presentation-2/-/presentation-2-1.0.4.tgz", + "integrity": "sha512-hJakpq62VBajesLJrYPtFm6hcn6c/HkKP7CmKZ5atuzu40m0nifWYsqigR1l9sZGvhhHb/DRshPmiW/0GNrJoA==", "license": "MIT", - "dependencies": { - "@babel/parser": "^7.29.0", - "@babel/types": "^7.29.0", - "@jridgewell/gen-mapping": "^0.3.12", - "@jridgewell/trace-mapping": "^0.3.28", - "jsesc": "^3.0.2" - }, - "engines": { - "node": ">=6.9.0" + "peerDependencies": { + "@iiif/presentation-3": "*" } }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", - "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", - "dev": true, + "node_modules/@iiif/presentation-3": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/@iiif/presentation-3/-/presentation-3-2.2.3.tgz", + "integrity": "sha512-xCLbUr9euqegsrxGe65M2fWbv6gKpiUhHXCpOn+V+qtawkMbOSNWbYOISo2aLQdYVg4DGYD0g2bMzSCF33uNOQ==", "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.28.6", - "@babel/helper-validator-option": "^7.27.1", - "browserslist": "^4.24.0", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" + "@types/geojson": "^7946.0.10" } }, - "node_modules/@babel/helper-globals": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", - "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", - "dev": true, + "node_modules/@iiif/presentation-3-normalized": { + "version": "0.9.7", + "resolved": "https://registry.npmjs.org/@iiif/presentation-3-normalized/-/presentation-3-normalized-0.9.7.tgz", + "integrity": "sha512-Aqk0sYBFIH5W3wmVxW02tnAFbNzUU5oPygGQjvszB3PP2nSkFQ1skVjqJhQPPZTyi/de1qcJUrgSy0vp6s+c5A==", "license": "MIT", - "engines": { - "node": ">=6.9.0" + "dependencies": { + "@iiif/presentation-3": "^2.0.5" } }, - "node_modules/@babel/helper-module-imports": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", - "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "@babel/traverse": "^7.28.6", - "@babel/types": "^7.28.6" + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" }, "engines": { - "node": ">=6.9.0" + "node": ">=12" } }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", - "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.28.6", - "@babel/helper-validator-identifier": "^7.28.5", - "@babel/traverse": "^7.28.6" - }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" + "node": ">=8" } }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", - "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "dev": true, "license": "MIT", "engines": { - "node": ">=6.9.0" + "node": ">=6.0.0" } }, - "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", "dev": true, "license": "MIT", - "engines": { - "node": ">=6.9.0" + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", - "dev": true, + "node_modules/@mongodb-js/saslprep": { + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.4.6.tgz", + "integrity": "sha512-y+x3H1xBZd38n10NZF/rEBlvDOOMQ6LKUTHqr8R9VkJ+mmQOYtJFxIlkkK8fZrtOiL6VixbOBWMbZGBdal3Z1g==", "license": "MIT", - "engines": { - "node": ">=6.9.0" + "dependencies": { + "sparse-bitfield": "^3.0.3" } }, - "node_modules/@babel/helper-validator-option": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", - "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", "dev": true, "license": "MIT", "engines": { - "node": ">=6.9.0" + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" } }, - "node_modules/@babel/helpers": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", - "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", + "node_modules/@paralleldrive/cuid2": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@paralleldrive/cuid2/-/cuid2-2.3.1.tgz", + "integrity": "sha512-XO7cAxhnTZl0Yggq6jOgjiOHhbgcO4NqFqwSmQpjK3b6TEE6Uj/jfSk6wzYyemh3+I0sHirKSetjQwn5cZktFw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/template": "^7.28.6", - "@babel/types": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" + "@noble/hashes": "^1.1.5" } }, - "node_modules/@babel/parser": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", - "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", "dev": true, "license": "MIT", - "dependencies": { - "@babel/types": "^7.29.0" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, + "optional": true, "engines": { - "node": ">=6.0.0" + "node": ">=14" } }, - "node_modules/@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", "dev": true, - "license": "MIT", + "license": "BSD-3-Clause", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "type-detect": "4.0.8" } }, - "node_modules/@babel/plugin-syntax-bigint": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", - "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "node_modules/@sinonjs/fake-timers": { + "version": "15.1.1", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-15.1.1.tgz", + "integrity": "sha512-cO5W33JgAPbOh07tvZjUOJ7oWhtaqGHiZw+11DPbyqh2kHTBc3eF/CjJDeQ4205RLQsX6rxCuYOroFQwl7JDRw==", "dev": true, - "license": "MIT", + "license": "BSD-3-Clause", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@sinonjs/commons": "^3.0.1" } }, - "node_modules/@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", - "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "node_modules/@sinonjs/samsam": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-9.0.2.tgz", + "integrity": "sha512-H/JSxa4GNKZuuU41E3b8Y3tbSEx8y4uq4UH1C56ONQac16HblReJomIvv3Ud7ANQHQmkeSowY49Ij972e/pGxQ==", "dev": true, - "license": "MIT", + "license": "BSD-3-Clause", "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@sinonjs/commons": "^3.0.1", + "type-detect": "^4.1.0" } }, - "node_modules/@babel/plugin-syntax-class-static-block": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", - "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "node_modules/@sinonjs/samsam/node_modules/type-detect": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz", + "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==", "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=4" } }, - "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz", - "integrity": "sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==", + "node_modules/@types/geojson": { + "version": "7946.0.13", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.13.tgz", + "integrity": "sha512-bmrNrgKMOhM3WsafmbGmC+6dsF2Z308vLFsQ3a/bT8X8Sv5clVYpPars/UPq+sAaJP+5OoLAYgwbkS5QEJdLUQ==", + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "25.4.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.4.0.tgz", + "integrity": "sha512-9wLpoeWuBlcbBpOY3XmzSTG3oscB6xjBEEtn+pYXTfhyXhIxC5FsBer2KTopBlvKEiW9l13po9fq+SJY/5lkhw==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "undici-types": "~7.18.0" } }, - "node_modules/@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", - "dev": true, + "node_modules/@types/webidl-conversions": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz", + "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==", + "license": "MIT" + }, + "node_modules/@types/whatwg-url": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-13.0.0.tgz", + "integrity": "sha512-N8WXpbE6Wgri7KUSvrmQcqrMllKZ9uxkYWMt+mCSGwNc0Hsw9VQTW7ApqI4XNrx6/SaM2QQJCzMPDEXE058s+Q==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@types/webidl-conversions": "*" } }, - "node_modules/@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", - "dev": true, + "node_modules/abs-svg-path": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/abs-svg-path/-/abs-svg-path-0.1.1.tgz", + "integrity": "sha512-d8XPSGjfyzlXC3Xx891DJRyZfqk5JU0BJrDQcsWomFIV1/BIzPW5HDH5iDdWpqWaav0YVIEzT1RHTwWr0FFshA==", "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } + "optional": true }, - "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz", - "integrity": "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==", - "dev": true, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">= 0.6" } }, - "node_modules/@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" + "engines": { + "node": ">=12" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, - "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "color-convert": "^2.0.1" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">= 8" } }, - "node_modules/@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "node_modules/anymatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "engines": { + "node": ">=8.6" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } + "license": "MIT" }, - "node_modules/@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } + "license": "MIT" }, - "node_modules/@babel/plugin-syntax-private-property-in-object": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", - "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true, + "license": "MIT" + }, + "node_modules/basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" + "safe-buffer": "5.1.2" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">= 0.8" } }, - "node_modules/@babel/plugin-syntax-top-level-await": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", - "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, "engines": { - "node": ">=6.9.0" + "node": ">=8" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz", - "integrity": "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==", - "dev": true, + "node_modules/body-parser": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", + "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.3", + "http-errors": "^2.0.0", + "iconv-lite": "^0.7.0", + "on-finished": "^2.4.1", + "qs": "^6.14.1", + "raw-body": "^3.0.1", + "type-is": "^2.0.1" }, "engines": { - "node": ">=6.9.0" + "node": ">=18" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/@babel/template": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", - "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.28.6", - "@babel/parser": "^7.28.6", - "@babel/types": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" + "balanced-match": "^1.0.0" } }, - "node_modules/@babel/traverse": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", - "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/types": "^7.29.0", - "debug": "^4.3.1" + "fill-range": "^7.1.1" }, "engines": { - "node": ">=6.9.0" + "node": ">=8" } }, - "node_modules/@babel/types": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", - "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" - }, + "node_modules/bson": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/bson/-/bson-7.2.0.tgz", + "integrity": "sha512-YCEo7KjMlbNlyHhz7zAZNDpIpQbd+wOEHJYezv0nMYTn4x31eIUM2yomNNubclAt63dObUzKHWsBLJ9QcZNSnQ==", + "license": "Apache-2.0", "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@bcoe/v8-coverage": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@emnapi/core": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.8.1.tgz", - "integrity": "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "1.1.0", - "tslib": "^2.4.0" + "node": ">=20.19.0" } }, - "node_modules/@emnapi/runtime": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz", - "integrity": "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==", - "dev": true, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" + "engines": { + "node": ">= 0.8" } }, - "node_modules/@emnapi/wasi-threads": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz", - "integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==", + "node_modules/c8": { + "version": "10.1.3", + "resolved": "https://registry.npmjs.org/c8/-/c8-10.1.3.tgz", + "integrity": "sha512-LvcyrOAaOnrrlMpW22n690PUvxiq4Uf9WMhQwNJ9vgagkL/ph1+D4uvjvDA5XCbykrc0sx+ay6pVi9YZ1GnhyA==", "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@iiif/helpers": { - "version": "1.5.8", - "resolved": "https://registry.npmjs.org/@iiif/helpers/-/helpers-1.5.8.tgz", - "integrity": "sha512-jNLhsmz5LsK3FhBtC6cIEoUM057zVL8zF0a8aQPFM5i6U/tf3QPFiq2mmm3Lop1ORFIZlEJXvsCwk7d3GucB2w==", - "license": "MIT", + "license": "ISC", "dependencies": { - "@iiif/presentation-2": "1.0.4", - "@iiif/presentation-3": "2.2.3", - "@iiif/presentation-3-normalized": "0.9.7", - "@types/geojson": "7946.0.13" + "@bcoe/v8-coverage": "^1.0.1", + "@istanbuljs/schema": "^0.1.3", + "find-up": "^5.0.0", + "foreground-child": "^3.1.1", + "istanbul-lib-coverage": "^3.2.0", + "istanbul-lib-report": "^3.0.1", + "istanbul-reports": "^3.1.6", + "test-exclude": "^7.0.1", + "v8-to-istanbul": "^9.0.0", + "yargs": "^17.7.2", + "yargs-parser": "^21.1.1" }, - "optionalDependencies": { - "abs-svg-path": "^0.1.1", - "parse-svg-path": "^0.1.2", - "svg-arc-to-cubic-bezier": "^3.2.0" + "bin": { + "c8": "bin/c8.js" + }, + "engines": { + "node": ">=18" }, "peerDependencies": { - "@iiif/parser": "^2.2.8" - } - }, - "node_modules/@iiif/parser": { - "version": "2.2.9", - "resolved": "https://registry.npmjs.org/@iiif/parser/-/parser-2.2.9.tgz", - "integrity": "sha512-HJVlnsz6mj56Qda9U7vhXEMxHFuFLeIDTcXqbPEDcuX1iefrWVsuDIY+DxyQO64vBnwme5NWOZbtbO73XNYB6A==", - "license": "MIT", - "peer": true, - "dependencies": { - "@iiif/presentation-2": "^1.0.4", - "@iiif/presentation-3": "^2.2.2", - "@iiif/presentation-3-normalized": "^0.9.7", - "@types/geojson": "^7946.0.10" - } - }, - "node_modules/@iiif/presentation-2": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@iiif/presentation-2/-/presentation-2-1.0.4.tgz", - "integrity": "sha512-hJakpq62VBajesLJrYPtFm6hcn6c/HkKP7CmKZ5atuzu40m0nifWYsqigR1l9sZGvhhHb/DRshPmiW/0GNrJoA==", - "license": "MIT", - "peerDependencies": { - "@iiif/presentation-3": "*" - } - }, - "node_modules/@iiif/presentation-3": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/@iiif/presentation-3/-/presentation-3-2.2.3.tgz", - "integrity": "sha512-xCLbUr9euqegsrxGe65M2fWbv6gKpiUhHXCpOn+V+qtawkMbOSNWbYOISo2aLQdYVg4DGYD0g2bMzSCF33uNOQ==", - "license": "MIT", - "dependencies": { - "@types/geojson": "^7946.0.10" - } - }, - "node_modules/@iiif/presentation-3-normalized": { - "version": "0.9.7", - "resolved": "https://registry.npmjs.org/@iiif/presentation-3-normalized/-/presentation-3-normalized-0.9.7.tgz", - "integrity": "sha512-Aqk0sYBFIH5W3wmVxW02tnAFbNzUU5oPygGQjvszB3PP2nSkFQ1skVjqJhQPPZTyi/de1qcJUrgSy0vp6s+c5A==", - "license": "MIT", - "dependencies": { - "@iiif/presentation-3": "^2.0.5" - } - }, - "node_modules/@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + "monocart-coverage-reports": "^2" }, - "engines": { - "node": ">=12" + "peerDependenciesMeta": { + "monocart-coverage-reports": { + "optional": true + } } }, - "node_modules/@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "node_modules/c8/node_modules/@bcoe/v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", + "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", "dev": true, - "license": "ISC", - "dependencies": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" - }, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=18" } }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "node_modules/c8/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", "dev": true, "license": "MIT", "engines": { - "node": ">=8" + "node": "18 || 20 || >=22" } }, - "node_modules/@jest/console": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-30.3.0.tgz", - "integrity": "sha512-PAwCvFJ4696XP2qZj+LAn1BWjZaJ6RjG6c7/lkMaUJnkyMS34ucuIsfqYvfskVNvUI27R/u4P1HMYFnlVXG/Ww==", + "node_modules/c8/node_modules/brace-expansion": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "30.3.0", - "@types/node": "*", - "chalk": "^4.1.2", - "jest-message-util": "30.3.0", - "jest-util": "30.3.0", - "slash": "^3.0.0" + "balanced-match": "^4.0.2" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": "18 || 20 || >=22" } }, - "node_modules/@jest/core": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-30.3.0.tgz", - "integrity": "sha512-U5mVPsBxLSO6xYbf+tgkymLx+iAhvZX43/xI1+ej2ZOPnPdkdO1CzDmFKh2mZBn2s4XZixszHeQnzp1gm/DIxw==", + "node_modules/c8/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, "license": "MIT", "dependencies": { - "@jest/console": "30.3.0", - "@jest/pattern": "30.0.1", - "@jest/reporters": "30.3.0", - "@jest/test-result": "30.3.0", - "@jest/transform": "30.3.0", - "@jest/types": "30.3.0", - "@types/node": "*", - "ansi-escapes": "^4.3.2", - "chalk": "^4.1.2", - "ci-info": "^4.2.0", - "exit-x": "^0.2.2", - "graceful-fs": "^4.2.11", - "jest-changed-files": "30.3.0", - "jest-config": "30.3.0", - "jest-haste-map": "30.3.0", - "jest-message-util": "30.3.0", - "jest-regex-util": "30.0.1", - "jest-resolve": "30.3.0", - "jest-resolve-dependencies": "30.3.0", - "jest-runner": "30.3.0", - "jest-runtime": "30.3.0", - "jest-snapshot": "30.3.0", - "jest-util": "30.3.0", - "jest-validate": "30.3.0", - "jest-watcher": "30.3.0", - "pretty-format": "30.3.0", - "slash": "^3.0.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/@jest/diff-sequences": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.3.0.tgz", - "integrity": "sha512-cG51MVnLq1ecVUaQ3fr6YuuAOitHK1S4WUJHnsPFE/quQr33ADUx1FfrTCpMCRxvy0Yr9BThKpDjSlcTi91tMA==", - "dev": true, - "license": "MIT", "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@jest/environment": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.3.0.tgz", - "integrity": "sha512-SlLSF4Be735yQXyh2+mctBOzNDx5s5uLv88/j8Qn1wH679PDcwy67+YdADn8NJnGjzlXtN62asGH/T4vWOkfaw==", + "node_modules/c8/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/fake-timers": "30.3.0", - "@jest/types": "30.3.0", - "@types/node": "*", - "jest-mock": "30.3.0" + "p-locate": "^5.0.0" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@jest/expect": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.3.0.tgz", - "integrity": "sha512-76Nlh4xJxk2D/9URCn3wFi98d2hb19uWE1idLsTt2ywhvdOldbw3S570hBgn25P4ICUZ/cBjybrBex2g17IDbg==", + "node_modules/c8/node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", "dev": true, - "license": "MIT", + "license": "BlueOak-1.0.0", "dependencies": { - "expect": "30.3.0", - "jest-snapshot": "30.3.0" + "brace-expansion": "^5.0.5" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@jest/expect-utils": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.3.0.tgz", - "integrity": "sha512-j0+W5iQQ8hBh7tHZkTQv3q2Fh/M7Je72cIsYqC4OaktgtO7v1So9UTjp6uPBHIaB6beoF/RRsCgMJKvti0wADA==", + "node_modules/c8/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/get-type": "30.1.0" + "p-limit": "^3.0.2" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@jest/fake-timers": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.3.0.tgz", - "integrity": "sha512-WUQDs8SOP9URStX1DzhD425CqbN/HxUYCTwVrT8sTVBfMvFqYt/s61EK5T05qnHu0po6RitXIvP9otZxYDzTGQ==", + "node_modules/c8/node_modules/test-exclude": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.2.tgz", + "integrity": "sha512-u9E6A+ZDYdp7a4WnarkXPZOx8Ilz46+kby6p1yZ8zsGTz9gYa6FIS7lj2oezzNKmtdyyJNNmmXDppga5GB7kSw==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "@jest/types": "30.3.0", - "@sinonjs/fake-timers": "^15.0.0", - "@types/node": "*", - "jest-message-util": "30.3.0", - "jest-mock": "30.3.0", - "jest-util": "30.3.0" + "@istanbuljs/schema": "^0.1.2", + "glob": "^10.4.1", + "minimatch": "^10.2.2" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/get-type": { - "version": "30.1.0", - "resolved": "https://registry.npmjs.org/@jest/get-type/-/get-type-30.1.0.tgz", - "integrity": "sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=18" } }, - "node_modules/@jest/globals": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.3.0.tgz", - "integrity": "sha512-+owLCBBdfpgL3HU+BD5etr1SvbXpSitJK0is1kiYjJxAAJggYMRQz5hSdd5pq1sSggfxPbw2ld71pt4x5wwViA==", - "dev": true, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", "license": "MIT", "dependencies": { - "@jest/environment": "30.3.0", - "@jest/expect": "30.3.0", - "@jest/types": "30.3.0", - "jest-mock": "30.3.0" + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">= 0.4" } }, - "node_modules/@jest/pattern": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.1.tgz", - "integrity": "sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==", - "dev": true, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", "license": "MIT", "dependencies": { - "@types/node": "*", - "jest-regex-util": "30.0.1" + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/@jest/reporters": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-30.3.0.tgz", - "integrity": "sha512-a09z89S+PkQnL055bVj8+pe2Caed2PBOaczHcXCykW5ngxX9EWx/1uAwncxc/HiU0oZqfwseMjyhxgRjS49qPw==", + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", "dev": true, "license": "MIT", "dependencies": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "30.3.0", - "@jest/test-result": "30.3.0", - "@jest/transform": "30.3.0", - "@jest/types": "30.3.0", - "@jridgewell/trace-mapping": "^0.3.25", - "@types/node": "*", - "chalk": "^4.1.2", - "collect-v8-coverage": "^1.0.2", - "exit-x": "^0.2.2", - "glob": "^10.5.0", - "graceful-fs": "^4.2.11", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^6.0.0", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^5.0.0", - "istanbul-reports": "^3.1.3", - "jest-message-util": "30.3.0", - "jest-util": "30.3.0", - "jest-worker": "30.3.0", - "slash": "^3.0.0", - "string-length": "^4.0.2", - "v8-to-istanbul": "^9.0.1" + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">= 8.10.0" }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + "funding": { + "url": "https://paulmillr.com/funding/" }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } + "optionalDependencies": { + "fsevents": "~2.3.2" } }, - "node_modules/@jest/schemas": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", - "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "@sinclair/typebox": "^0.34.0" + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=12" } }, - "node_modules/@jest/snapshot-utils": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/@jest/snapshot-utils/-/snapshot-utils-30.3.0.tgz", - "integrity": "sha512-ORbRN9sf5PP82v3FXNSwmO1OTDR2vzR2YTaR+E3VkSBZ8zadQE6IqYdYEeFH1NIkeB2HIGdF02dapb6K0Mj05g==", + "node_modules/cliui/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, "license": "MIT", - "dependencies": { - "@jest/types": "30.3.0", - "chalk": "^4.1.2", - "graceful-fs": "^4.2.11", - "natural-compare": "^1.4.0" - }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=8" } }, - "node_modules/@jest/source-map": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-30.0.1.tgz", - "integrity": "sha512-MIRWMUUR3sdbP36oyNyhbThLHyJ2eEDClPCiHVbrYAe5g3CHRArIVpBw7cdSB5fr+ofSfIb2Tnsw8iEHL0PYQg==", + "node_modules/cliui/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/cliui/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/trace-mapping": "^0.3.25", - "callsites": "^3.1.0", - "graceful-fs": "^4.2.11" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=8" } }, - "node_modules/@jest/test-result": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-30.3.0.tgz", - "integrity": "sha512-e/52nJGuD74AKTSe0P4y5wFRlaXP0qmrS17rqOMHeSwm278VyNyXE3gFO/4DTGF9w+65ra3lo3VKj0LBrzmgdQ==", + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "license": "MIT", "dependencies": { - "@jest/console": "30.3.0", - "@jest/types": "30.3.0", - "@types/istanbul-lib-coverage": "^2.0.6", - "collect-v8-coverage": "^1.0.2" + "ansi-regex": "^5.0.1" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=8" } }, - "node_modules/@jest/test-sequencer": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-30.3.0.tgz", - "integrity": "sha512-dgbWy9b8QDlQeRZcv7LNF+/jFiiYHTKho1xirauZ7kVwY7avjFF6uTT0RqlgudB5OuIPagFdVtfFMosjVbk1eA==", + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, "license": "MIT", "dependencies": { - "@jest/test-result": "30.3.0", - "graceful-fs": "^4.2.11", - "jest-haste-map": "30.3.0", - "slash": "^3.0.0" + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/@jest/transform": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.3.0.tgz", - "integrity": "sha512-TLKY33fSLVd/lKB2YI1pH69ijyUblO/BQvCj566YvnwuzoTNr648iE0j22vRvVNk2HsPwByPxATg3MleS3gf5A==", + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/core": "^7.27.4", - "@jest/types": "30.3.0", - "@jridgewell/trace-mapping": "^0.3.25", - "babel-plugin-istanbul": "^7.0.1", - "chalk": "^4.1.2", - "convert-source-map": "^2.0.0", - "fast-json-stable-stringify": "^2.1.0", - "graceful-fs": "^4.2.11", - "jest-haste-map": "30.3.0", - "jest-regex-util": "30.0.1", - "jest-util": "30.3.0", - "pirates": "^4.0.7", - "slash": "^3.0.0", - "write-file-atomic": "^5.0.1" + "color-name": "~1.1.4" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=7.0.0" } }, - "node_modules/@jest/types": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.3.0.tgz", - "integrity": "sha512-JHm87k7bA33hpBngtU8h6UBub/fqqA9uXfw+21j5Hmk7ooPHlboRNxHq0JcMtC+n8VJGP1mcfnD3Mk+XKe1oSw==", + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/pattern": "30.0.1", - "@jest/schemas": "30.0.5", - "@types/istanbul-lib-coverage": "^2.0.6", - "@types/istanbul-reports": "^3.0.4", - "@types/node": "*", - "@types/yargs": "^17.0.33", - "chalk": "^4.1.2" + "delayed-stream": "~1.0.0" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">= 0.8" } }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "node_modules/component-emitter": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz", + "integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==", "dev": true, "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@jridgewell/remapping": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", - "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", - "dev": true, + "node_modules/content-disposition": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", + "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==", "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", "license": "MIT", "engines": { - "node": ">=6.0.0" + "node": ">= 0.6" } }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true, "license": "MIT" }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "dev": true, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" + "engines": { + "node": ">= 0.6" } }, - "node_modules/@mongodb-js/saslprep": { - "version": "1.4.6", - "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.4.6.tgz", - "integrity": "sha512-y+x3H1xBZd38n10NZF/rEBlvDOOMQ6LKUTHqr8R9VkJ+mmQOYtJFxIlkkK8fZrtOiL6VixbOBWMbZGBdal3Z1g==", + "node_modules/cookie-parser": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.7.tgz", + "integrity": "sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==", "license": "MIT", "dependencies": { - "sparse-bitfield": "^3.0.3" + "cookie": "0.7.2", + "cookie-signature": "1.0.6" + }, + "engines": { + "node": ">= 0.8.0" } }, - "node_modules/@napi-rs/wasm-runtime": { - "version": "0.2.12", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", - "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "^1.4.3", - "@emnapi/runtime": "^1.4.3", - "@tybys/wasm-util": "^0.10.0" - } + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "license": "MIT" }, - "node_modules/@noble/hashes": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", - "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "node_modules/cookiejar": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz", + "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==", "dev": true, + "license": "MIT" + }, + "node_modules/cors": { + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, "engines": { - "node": "^14.21.3 || >=16" + "node": ">= 0.10" }, "funding": { - "url": "https://paulmillr.com/funding/" + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/@paralleldrive/cuid2": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/@paralleldrive/cuid2/-/cuid2-2.3.1.tgz", - "integrity": "sha512-XO7cAxhnTZl0Yggq6jOgjiOHhbgcO4NqFqwSmQpjK3b6TEE6Uj/jfSk6wzYyemh3+I0sHirKSetjQwn5cZktFw==", + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, "license": "MIT", "dependencies": { - "@noble/hashes": "^1.1.5" - } - }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "dev": true, - "license": "MIT", - "optional": true, + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, "engines": { - "node": ">=14" + "node": ">= 8" } }, - "node_modules/@pkgr/core": { - "version": "0.2.9", - "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz", - "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==", - "dev": true, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, "engines": { - "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + "node": ">=6.0" }, - "funding": { - "url": "https://opencollective.com/pkgr" - } - }, - "node_modules/@sinclair/typebox": { - "version": "0.34.48", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.48.tgz", - "integrity": "sha512-kKJTNuK3AQOrgjjotVxMrCn1sUJwM76wMszfq1kdU4uYVJjvEWuFQ6HgvLt4Xz3fSmZlTOxJ/Ie13KnIcWQXFA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@sinonjs/commons": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", - "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "type-detect": "4.0.8" - } - }, - "node_modules/@sinonjs/fake-timers": { - "version": "15.1.1", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-15.1.1.tgz", - "integrity": "sha512-cO5W33JgAPbOh07tvZjUOJ7oWhtaqGHiZw+11DPbyqh2kHTBc3eF/CjJDeQ4205RLQsX6rxCuYOroFQwl7JDRw==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@sinonjs/commons": "^3.0.1" - } - }, - "node_modules/@sinonjs/samsam": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-9.0.2.tgz", - "integrity": "sha512-H/JSxa4GNKZuuU41E3b8Y3tbSEx8y4uq4UH1C56ONQac16HblReJomIvv3Ud7ANQHQmkeSowY49Ij972e/pGxQ==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@sinonjs/commons": "^3.0.1", - "type-detect": "^4.1.0" - } - }, - "node_modules/@sinonjs/samsam/node_modules/type-detect": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz", - "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/@tybys/wasm-util": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", - "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@types/babel__core": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", - "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "node_modules/@types/babel__generator": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", - "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__template": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", - "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__traverse": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", - "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.28.2" - } - }, - "node_modules/@types/geojson": { - "version": "7946.0.13", - "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.13.tgz", - "integrity": "sha512-bmrNrgKMOhM3WsafmbGmC+6dsF2Z308vLFsQ3a/bT8X8Sv5clVYpPars/UPq+sAaJP+5OoLAYgwbkS5QEJdLUQ==", - "license": "MIT" - }, - "node_modules/@types/istanbul-lib-coverage": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", - "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/istanbul-lib-report": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", - "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/istanbul-lib-coverage": "*" - } - }, - "node_modules/@types/istanbul-reports": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", - "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/@types/node": { - "version": "25.4.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.4.0.tgz", - "integrity": "sha512-9wLpoeWuBlcbBpOY3XmzSTG3oscB6xjBEEtn+pYXTfhyXhIxC5FsBer2KTopBlvKEiW9l13po9fq+SJY/5lkhw==", - "license": "MIT", - "dependencies": { - "undici-types": "~7.18.0" - } - }, - "node_modules/@types/stack-utils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", - "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/webidl-conversions": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz", - "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==", - "license": "MIT" - }, - "node_modules/@types/whatwg-url": { - "version": "13.0.0", - "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-13.0.0.tgz", - "integrity": "sha512-N8WXpbE6Wgri7KUSvrmQcqrMllKZ9uxkYWMt+mCSGwNc0Hsw9VQTW7ApqI4XNrx6/SaM2QQJCzMPDEXE058s+Q==", - "license": "MIT", - "dependencies": { - "@types/webidl-conversions": "*" - } - }, - "node_modules/@types/yargs": { - "version": "17.0.35", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", - "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@types/yargs-parser": { - "version": "21.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", - "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@ungap/structured-clone": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", - "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", - "dev": true, - "license": "ISC" - }, - "node_modules/@unrs/resolver-binding-android-arm-eabi": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz", - "integrity": "sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@unrs/resolver-binding-android-arm64": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.11.1.tgz", - "integrity": "sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@unrs/resolver-binding-darwin-arm64": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.11.1.tgz", - "integrity": "sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@unrs/resolver-binding-darwin-x64": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.11.1.tgz", - "integrity": "sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@unrs/resolver-binding-freebsd-x64": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.11.1.tgz", - "integrity": "sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.11.1.tgz", - "integrity": "sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.11.1.tgz", - "integrity": "sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.11.1.tgz", - "integrity": "sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-arm64-musl": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.11.1.tgz", - "integrity": "sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.11.1.tgz", - "integrity": "sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.11.1.tgz", - "integrity": "sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.11.1.tgz", - "integrity": "sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.11.1.tgz", - "integrity": "sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-x64-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.11.1.tgz", - "integrity": "sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-x64-musl": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.11.1.tgz", - "integrity": "sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-wasm32-wasi": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.11.1.tgz", - "integrity": "sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==", - "cpu": [ - "wasm32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@napi-rs/wasm-runtime": "^0.2.11" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.11.1.tgz", - "integrity": "sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.11.1.tgz", - "integrity": "sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@unrs/resolver-binding-win32-x64-msvc": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz", - "integrity": "sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/abs-svg-path": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/abs-svg-path/-/abs-svg-path-0.1.1.tgz", - "integrity": "sha512-d8XPSGjfyzlXC3Xx891DJRyZfqk5JU0BJrDQcsWomFIV1/BIzPW5HDH5iDdWpqWaav0YVIEzT1RHTwWr0FFshA==", - "license": "MIT", - "optional": true - }, - "node_modules/accepts": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", - "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", - "license": "MIT", - "dependencies": { - "mime-types": "^3.0.0", - "negotiator": "^1.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "type-fest": "^0.21.3" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "license": "ISC", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/anymatch/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "license": "MIT", - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/asap": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", - "dev": true, - "license": "MIT" - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/babel-jest": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.3.0.tgz", - "integrity": "sha512-gRpauEU2KRrCox5Z296aeVHR4jQ98BCnu0IO332D/xpHNOsIH/bgSRk9k6GbKIbBw8vFeN6ctuu6tV8WOyVfYQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/transform": "30.3.0", - "@types/babel__core": "^7.20.5", - "babel-plugin-istanbul": "^7.0.1", - "babel-preset-jest": "30.3.0", - "chalk": "^4.1.2", - "graceful-fs": "^4.2.11", - "slash": "^3.0.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.11.0 || ^8.0.0-0" - } - }, - "node_modules/babel-plugin-istanbul": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-7.0.1.tgz", - "integrity": "sha512-D8Z6Qm8jCvVXtIRkBnqNHX0zJ37rQcFJ9u8WOS6tkYOsRdHBzypCstaxWiu5ZIlqQtviRYbgnRLSoCEvjqcqbA==", - "dev": true, - "license": "BSD-3-Clause", - "workspaces": [ - "test/babel-8" - ], - "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.3", - "istanbul-lib-instrument": "^6.0.2", - "test-exclude": "^6.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/babel-plugin-jest-hoist": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.3.0.tgz", - "integrity": "sha512-+TRkByhsws6sfPjVaitzadk1I0F5sPvOVUH5tyTSzhePpsGIVrdeunHSw/C36QeocS95OOk8lunc4rlu5Anwsg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/babel__core": "^7.20.5" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/babel-preset-current-node-syntax": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz", - "integrity": "sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/plugin-syntax-class-properties": "^7.12.13", - "@babel/plugin-syntax-class-static-block": "^7.14.5", - "@babel/plugin-syntax-import-attributes": "^7.24.7", - "@babel/plugin-syntax-import-meta": "^7.10.4", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5", - "@babel/plugin-syntax-top-level-await": "^7.14.5" - }, - "peerDependencies": { - "@babel/core": "^7.0.0 || ^8.0.0-0" - } - }, - "node_modules/babel-preset-jest": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-30.3.0.tgz", - "integrity": "sha512-6ZcUbWHC+dMz2vfzdNwi87Z1gQsLNK2uLuK1Q89R11xdvejcivlYYwDlEv0FHX3VwEXpbBQ9uufB/MUNpZGfhQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "babel-plugin-jest-hoist": "30.3.0", - "babel-preset-current-node-syntax": "^1.2.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.11.0 || ^8.0.0-beta.1" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/baseline-browser-mapping": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.0.tgz", - "integrity": "sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "baseline-browser-mapping": "dist/cli.cjs" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/basic-auth": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", - "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", - "license": "MIT", - "dependencies": { - "safe-buffer": "5.1.2" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/body-parser": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", - "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", - "license": "MIT", - "dependencies": { - "bytes": "^3.1.2", - "content-type": "^1.0.5", - "debug": "^4.4.3", - "http-errors": "^2.0.0", - "iconv-lite": "^0.7.0", - "on-finished": "^2.4.1", - "qs": "^6.14.1", - "raw-body": "^3.0.1", - "type-is": "^2.0.1" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/browserslist": { - "version": "4.28.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", - "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "baseline-browser-mapping": "^2.9.0", - "caniuse-lite": "^1.0.30001759", - "electron-to-chromium": "^1.5.263", - "node-releases": "^2.0.27", - "update-browserslist-db": "^1.2.0" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/bser": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", - "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "node-int64": "^0.4.0" - } - }, - "node_modules/bson": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/bson/-/bson-7.2.0.tgz", - "integrity": "sha512-YCEo7KjMlbNlyHhz7zAZNDpIpQbd+wOEHJYezv0nMYTn4x31eIUM2yomNNubclAt63dObUzKHWsBLJ9QcZNSnQ==", - "license": "Apache-2.0", - "engines": { - "node": ">=20.19.0" - } - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/call-bound": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001777", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001777.tgz", - "integrity": "sha512-tmN+fJxroPndC74efCdp12j+0rk0RHwV5Jwa1zWaFVyw2ZxAuPeG8ZgWC3Wz7uSjT3qMRQ5XHZ4COgQmsCMJAQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/char-regex": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", - "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/ci-info": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz", - "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/cjs-module-lexer": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.2.0.tgz", - "integrity": "sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/cliui/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/cliui/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", - "dev": true, - "license": "MIT", - "engines": { - "iojs": ">= 1.0.0", - "node": ">= 0.12.0" - } - }, - "node_modules/collect-v8-coverage": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.3.tgz", - "integrity": "sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw==", - "dev": true, - "license": "MIT" - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, - "license": "MIT", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/component-emitter": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz", - "integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, - "license": "MIT" - }, - "node_modules/content-disposition": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", - "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, - "license": "MIT" - }, - "node_modules/cookie": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-parser": { - "version": "1.4.7", - "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.7.tgz", - "integrity": "sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==", - "license": "MIT", - "dependencies": { - "cookie": "0.7.2", - "cookie-signature": "1.0.6" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", - "license": "MIT" - }, - "node_modules/cookiejar": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz", - "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==", - "dev": true, - "license": "MIT" - }, - "node_modules/cors": { - "version": "2.8.6", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", - "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", - "license": "MIT", - "dependencies": { - "object-assign": "^4", - "vary": "^1" - }, - "engines": { - "node": ">= 0.10" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/dedent": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.2.tgz", - "integrity": "sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "babel-plugin-macros": "^3.1.0" - }, - "peerDependenciesMeta": { - "babel-plugin-macros": { - "optional": true - } - } - }, - "node_modules/deepmerge": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", - "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/denque": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", - "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", - "license": "Apache-2.0", - "engines": { - "node": ">=0.10" - } - }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/detect-newline": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", - "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/dezalgo": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", - "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", - "dev": true, - "license": "ISC", - "dependencies": { - "asap": "^2.0.0", - "wrappy": "1" - } - }, - "node_modules/diff": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.3.tgz", - "integrity": "sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/dotenv": { - "version": "17.3.1", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.3.1.tgz", - "integrity": "sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://dotenvx.com" - } - }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true, - "license": "MIT" - }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "license": "MIT" - }, - "node_modules/electron-to-chromium": { - "version": "1.5.307", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.307.tgz", - "integrity": "sha512-5z3uFKBWjiNR44nFcYdkcXjKMbg5KXNdciu7mhTPo9tB7NbqSNP2sSnGR+fqknZSCwKkBN+oxiiajWs4dT6ORg==", - "dev": true, - "license": "ISC" - }, - "node_modules/emittery": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", - "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sindresorhus/emittery?sponsor=1" - } - }, - "node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true, - "license": "MIT" - }, - "node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/error-ex": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", - "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-set-tostringtag": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", - "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "license": "MIT" - }, - "node_modules/escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true, - "license": "BSD-2-Clause", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, - "license": "MIT", - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/execa/node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/exit-x": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/exit-x/-/exit-x-0.2.2.tgz", - "integrity": "sha512-+I6B/IkJc1o/2tiURyz/ivu/O0nKNEArIUB5O7zBrlDVJr22SCLH3xTeEry428LvFhRzIA1g8izguxJ/gbNcVQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/expect": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/expect/-/expect-30.3.0.tgz", - "integrity": "sha512-1zQrciTiQfRdo7qJM1uG4navm8DayFa2TgCSRlzUyNkhcJ6XUZF3hjnpkyr3VhAqPH7i/9GkG7Tv5abz6fqz0Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/expect-utils": "30.3.0", - "@jest/get-type": "30.1.0", - "jest-matcher-utils": "30.3.0", - "jest-message-util": "30.3.0", - "jest-mock": "30.3.0", - "jest-util": "30.3.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/express": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", - "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", - "license": "MIT", - "dependencies": { - "accepts": "^2.0.0", - "body-parser": "^2.2.1", - "content-disposition": "^1.0.0", - "content-type": "^1.0.5", - "cookie": "^0.7.1", - "cookie-signature": "^1.2.1", - "debug": "^4.4.0", - "depd": "^2.0.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "finalhandler": "^2.1.0", - "fresh": "^2.0.0", - "http-errors": "^2.0.0", - "merge-descriptors": "^2.0.0", - "mime-types": "^3.0.0", - "on-finished": "^2.4.1", - "once": "^1.4.0", - "parseurl": "^1.3.3", - "proxy-addr": "^2.0.7", - "qs": "^6.14.0", - "range-parser": "^1.2.1", - "router": "^2.2.0", - "send": "^1.1.0", - "serve-static": "^2.2.0", - "statuses": "^2.0.1", - "type-is": "^2.0.1", - "vary": "^1.1.2" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/express-list-endpoints": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/express-list-endpoints/-/express-list-endpoints-7.1.1.tgz", - "integrity": "sha512-SA6YHH1r6DrioJ4fFJNqiwu1FweGFqJZO9KBApMzwPosoSGPOX2AW0wiMepOXjojjEXDuP9whIvckomheErbJA==", - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/express-oauth2-jwt-bearer": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/express-oauth2-jwt-bearer/-/express-oauth2-jwt-bearer-1.7.4.tgz", - "integrity": "sha512-teO/eyvU8OkJXiP4cRuoJMrp31nNvjnL47MIkso0D/21AqUGv1O+VEiLisrDA8xjkaCBTufYnV1zepCOCLK4vg==", - "license": "MIT", - "dependencies": { - "jose": "^4.15.5" - }, - "engines": { - "node": "^12.19.0 || ^14.15.0 || ^16.13.0 || ^18.12.0 || ^20.2.0 || ^22.1.0 || ^24.0.0" - } - }, - "node_modules/express/node_modules/cookie-signature": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", - "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", - "license": "MIT", - "engines": { - "node": ">=6.6.0" - } - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-safe-stringify": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", - "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", - "dev": true, - "license": "MIT" - }, - "node_modules/fb-watchman": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", - "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "bser": "2.1.1" - } - }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/finalhandler": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", - "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", - "license": "MIT", - "dependencies": { - "debug": "^4.4.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "on-finished": "^2.4.1", - "parseurl": "^1.3.3", - "statuses": "^2.0.1" - }, - "engines": { - "node": ">= 18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/foreground-child": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", - "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", - "dev": true, - "license": "ISC", - "dependencies": { - "cross-spawn": "^7.0.6", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/form-data": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", - "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", - "dev": true, - "license": "MIT", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.2", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/form-data/node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/form-data/node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/formidable": { - "version": "3.5.4", - "resolved": "https://registry.npmjs.org/formidable/-/formidable-3.5.4.tgz", - "integrity": "sha512-YikH+7CUTOtP44ZTnUhR7Ic2UASBPOqmaRkRKxRbywPTe5VxF7RRCck4af9wutiZ/QKM5nME9Bie2fFaPz5Gug==", - "dev": true, - "license": "MIT", - "dependencies": { - "@paralleldrive/cuid2": "^2.2.2", - "dezalgo": "^1.0.4", - "once": "^1.4.0" - }, - "engines": { - "node": ">=14.0.0" - }, - "funding": { - "url": "https://ko-fi.com/tunnckoCore/commissions" - } - }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fresh": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", - "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true, - "license": "ISC" - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, - "license": "ISC", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-package-type": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", - "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/glob": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", - "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "dev": true, - "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/has-tostringtag": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", "dev": true, "license": "MIT", - "dependencies": { - "has-symbols": "^1.0.3" - }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=0.4.0" } }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, + "node_modules/denque": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", + "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", + "license": "Apache-2.0", "engines": { - "node": ">= 0.4" + "node": ">=0.10" } }, - "node_modules/html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true, - "license": "MIT" - }, - "node_modules/http-errors": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", - "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", "license": "MIT", - "dependencies": { - "depd": "~2.0.0", - "inherits": "~2.0.4", - "setprototypeof": "~1.2.0", - "statuses": "~2.0.2", - "toidentifier": "~1.0.1" - }, "engines": { "node": ">= 0.8" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" } }, - "node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "node_modules/dezalgo": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", + "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=10.17.0" - } - }, - "node_modules/iconv-lite": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", - "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", - "license": "MIT", + "license": "ISC", "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "asap": "^2.0.0", + "wrappy": "1" } }, - "node_modules/ignore-by-default": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", - "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "node_modules/diff": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.3.tgz", + "integrity": "sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ==", "dev": true, - "license": "ISC" - }, - "node_modules/image-size": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/image-size/-/image-size-2.0.2.tgz", - "integrity": "sha512-IRqXKlaXwgSMAMtpNzZa1ZAe8m+Sa1770Dhk8VkSsP9LS+iHD62Zd8FQKs8fbPiagBE7BzoFX23cxFnwshpV6w==", - "license": "MIT", - "bin": { - "image-size": "bin/image-size.js" - }, + "license": "BSD-3-Clause", "engines": { - "node": ">=16.x" + "node": ">=0.3.1" } }, - "node_modules/import-local": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", - "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", - "dev": true, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", "license": "MIT", "dependencies": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - }, - "bin": { - "import-local-fixture": "fixtures/cli.js" - }, - "engines": { - "node": ">=8" + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "license": "MIT", "engines": { - "node": ">=0.8.19" + "node": ">= 0.4" } }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", "dev": true, - "license": "ISC", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "license": "ISC" - }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "license": "MIT", - "engines": { - "node": ">= 0.10" - } + "license": "MIT" }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", "license": "MIT" }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "dev": true, + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", "license": "MIT", - "dependencies": { - "binary-extensions": "^2.0.0" - }, "engines": { - "node": ">=8" + "node": ">= 0.8" } }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" } }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", "license": "MIT", "engines": { - "node": ">=8" + "node": ">= 0.4" } }, - "node_modules/is-generator-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", - "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", - "dev": true, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, "engines": { - "node": ">=6" + "node": ">= 0.4" } }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", "dev": true, "license": "MIT", "dependencies": { - "is-extglob": "^2.1.1" + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" } }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "dev": true, "license": "MIT", "engines": { - "node": ">=0.12.0" + "node": ">=6" } }, - "node_modules/is-promise": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", - "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", "license": "MIT" }, - "node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", "license": "MIT", "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "license": "ISC" - }, - "node_modules/istanbul-lib-coverage": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", - "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=8" + "node": ">= 0.6" } }, - "node_modules/istanbul-lib-instrument": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", - "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", - "dev": true, - "license": "BSD-3-Clause", + "node_modules/express": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "license": "MIT", "dependencies": { - "@babel/core": "^7.23.9", - "@babel/parser": "^7.23.9", - "@istanbuljs/schema": "^0.1.3", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^7.5.4" + "accepts": "^2.0.0", + "body-parser": "^2.2.1", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" }, "engines": { - "node": ">=10" + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/istanbul-lib-instrument/node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, + "node_modules/express-list-endpoints": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/express-list-endpoints/-/express-list-endpoints-7.1.1.tgz", + "integrity": "sha512-SA6YHH1r6DrioJ4fFJNqiwu1FweGFqJZO9KBApMzwPosoSGPOX2AW0wiMepOXjojjEXDuP9whIvckomheErbJA==", + "license": "MIT", "engines": { - "node": ">=10" + "node": ">=18" } }, - "node_modules/istanbul-lib-report": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", - "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", - "dev": true, - "license": "BSD-3-Clause", + "node_modules/express-oauth2-jwt-bearer": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/express-oauth2-jwt-bearer/-/express-oauth2-jwt-bearer-1.7.4.tgz", + "integrity": "sha512-teO/eyvU8OkJXiP4cRuoJMrp31nNvjnL47MIkso0D/21AqUGv1O+VEiLisrDA8xjkaCBTufYnV1zepCOCLK4vg==", + "license": "MIT", "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^4.0.0", - "supports-color": "^7.1.0" + "jose": "^4.15.5" }, "engines": { - "node": ">=10" + "node": "^12.19.0 || ^14.15.0 || ^16.13.0 || ^18.12.0 || ^20.2.0 || ^22.1.0 || ^24.0.0" } }, - "node_modules/istanbul-lib-source-maps": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", - "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.23", - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0" - }, + "node_modules/express/node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", "engines": { - "node": ">=10" + "node": ">=6.6.0" } }, - "node_modules/istanbul-reports": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", - "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "node_modules/fast-safe-stringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", "dev": true, - "license": "BSD-3-Clause", + "license": "MIT" + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" + "to-regex-range": "^5.0.1" }, "engines": { "node": ">=8" } }, - "node_modules/jackspeak": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", - "dev": true, - "license": "BlueOak-1.0.0", + "node_modules/finalhandler": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "license": "MIT", "dependencies": { - "@isaacs/cliui": "^8.0.2" + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "engines": { + "node": ">= 18.0.0" }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/jest": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest/-/jest-30.3.0.tgz", - "integrity": "sha512-AkXIIFcaazymvey2i/+F94XRnM6TsVLZDhBMLsd1Sf/W0wzsvvpjeyUrCZD6HGG4SDYPgDJDBKeiJTBb10WzMg==", + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "@jest/core": "30.3.0", - "@jest/types": "30.3.0", - "import-local": "^3.2.0", - "jest-cli": "30.3.0" - }, - "bin": { - "jest": "bin/jest.js" + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + "node": ">=14" }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/jest-changed-files": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-30.3.0.tgz", - "integrity": "sha512-B/7Cny6cV5At6M25EWDgf9S617lHivamL8vl6KEpJqkStauzcG4e+WPfDgMMF+H4FVH4A2PLRyvgDJan4441QA==", + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", "dev": true, "license": "MIT", "dependencies": { - "execa": "^5.1.1", - "jest-util": "30.3.0", - "p-limit": "^3.1.0" + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">= 6" } }, - "node_modules/jest-circus": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.3.0.tgz", - "integrity": "sha512-PyXq5szeSfR/4f1lYqCmmQjh0vqDkURUYi9N6whnHjlRz4IUQfMcXkGLeEoiJtxtyPqgUaUUfyQlApXWBSN1RA==", + "node_modules/form-data/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "dev": true, "license": "MIT", - "dependencies": { - "@jest/environment": "30.3.0", - "@jest/expect": "30.3.0", - "@jest/test-result": "30.3.0", - "@jest/types": "30.3.0", - "@types/node": "*", - "chalk": "^4.1.2", - "co": "^4.6.0", - "dedent": "^1.6.0", - "is-generator-fn": "^2.1.0", - "jest-each": "30.3.0", - "jest-matcher-utils": "30.3.0", - "jest-message-util": "30.3.0", - "jest-runtime": "30.3.0", - "jest-snapshot": "30.3.0", - "jest-util": "30.3.0", - "p-limit": "^3.1.0", - "pretty-format": "30.3.0", - "pure-rand": "^7.0.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.6" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-cli": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-30.3.0.tgz", - "integrity": "sha512-l6Tqx+j1fDXJEW5bqYykDQQ7mQg+9mhWXtnj+tQZrTWYHyHoi6Be8HPumDSA+UiX2/2buEgjA58iJzdj146uCw==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/form-data/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/core": "30.3.0", - "@jest/test-result": "30.3.0", - "@jest/types": "30.3.0", - "chalk": "^4.1.2", - "exit-x": "^0.2.2", - "import-local": "^3.2.0", - "jest-config": "30.3.0", - "jest-util": "30.3.0", - "jest-validate": "30.3.0", - "yargs": "^17.7.2" - }, - "bin": { - "jest": "bin/jest.js" + "mime-db": "1.52.0" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } + "node": ">= 0.6" } }, - "node_modules/jest-config": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.3.0.tgz", - "integrity": "sha512-WPMAkMAtNDY9P/oKObtsRG/6KTrhtgPJoBTmk20uDn4Uy6/3EJnnaZJre/FMT1KVRx8cve1r7/FlMIOfRVWL4w==", + "node_modules/formidable": { + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-3.5.4.tgz", + "integrity": "sha512-YikH+7CUTOtP44ZTnUhR7Ic2UASBPOqmaRkRKxRbywPTe5VxF7RRCck4af9wutiZ/QKM5nME9Bie2fFaPz5Gug==", "dev": true, "license": "MIT", "dependencies": { - "@babel/core": "^7.27.4", - "@jest/get-type": "30.1.0", - "@jest/pattern": "30.0.1", - "@jest/test-sequencer": "30.3.0", - "@jest/types": "30.3.0", - "babel-jest": "30.3.0", - "chalk": "^4.1.2", - "ci-info": "^4.2.0", - "deepmerge": "^4.3.1", - "glob": "^10.5.0", - "graceful-fs": "^4.2.11", - "jest-circus": "30.3.0", - "jest-docblock": "30.2.0", - "jest-environment-node": "30.3.0", - "jest-regex-util": "30.0.1", - "jest-resolve": "30.3.0", - "jest-runner": "30.3.0", - "jest-util": "30.3.0", - "jest-validate": "30.3.0", - "parse-json": "^5.2.0", - "pretty-format": "30.3.0", - "slash": "^3.0.0", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "@paralleldrive/cuid2": "^2.2.2", + "dezalgo": "^1.0.4", + "once": "^1.4.0" }, - "peerDependencies": { - "@types/node": "*", - "esbuild-register": ">=3.4.0", - "ts-node": ">=9.0.0" + "engines": { + "node": ">=14.0.0" }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "esbuild-register": { - "optional": true - }, - "ts-node": { - "optional": true - } + "funding": { + "url": "https://ko-fi.com/tunnckoCore/commissions" } }, - "node_modules/jest-diff": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.3.0.tgz", - "integrity": "sha512-n3q4PDQjS4LrKxfWB3Z5KNk1XjXtZTBwQp71OP0Jo03Z6V60x++K5L8k6ZrW8MY8pOFylZvHM0zsjS1RqlHJZQ==", - "dev": true, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", "license": "MIT", - "dependencies": { - "@jest/diff-sequences": "30.3.0", - "@jest/get-type": "30.1.0", - "chalk": "^4.1.2", - "pretty-format": "30.3.0" - }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">= 0.6" } }, - "node_modules/jest-docblock": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-30.2.0.tgz", - "integrity": "sha512-tR/FFgZKS1CXluOQzZvNH3+0z9jXr3ldGSD8bhyuxvlVUwbeLOGynkunvlTMxchC5urrKndYiwCFC0DLVjpOCA==", - "dev": true, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", "license": "MIT", - "dependencies": { - "detect-newline": "^3.1.0" - }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">= 0.8" } }, - "node_modules/jest-each": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-30.3.0.tgz", - "integrity": "sha512-V8eMndg/aZ+3LnCJgSm13IxS5XSBM22QSZc9BtPK8Dek6pm+hfUNfwBdvsB3d342bo1q7wnSkC38zjX259qZNA==", + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, + "hasInstallScript": true, "license": "MIT", - "dependencies": { - "@jest/get-type": "30.1.0", - "@jest/types": "30.3.0", - "chalk": "^4.1.2", - "jest-util": "30.3.0", - "pretty-format": "30.3.0" - }, + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/jest-environment-node": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.3.0.tgz", - "integrity": "sha512-4i6HItw/JSiJVsC5q0hnKIe/hbYfZLVG9YJ/0pU9Hz2n/9qZe3Rhn5s5CUZA5ORZlcdT/vmAXRMyONXJwPrmYQ==", - "dev": true, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", "license": "MIT", - "dependencies": { - "@jest/environment": "30.3.0", - "@jest/fake-timers": "30.3.0", - "@jest/types": "30.3.0", - "@types/node": "*", - "jest-mock": "30.3.0", - "jest-util": "30.3.0", - "jest-validate": "30.3.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/jest-haste-map": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.3.0.tgz", - "integrity": "sha512-mMi2oqG4KRU0R9QEtscl87JzMXfUhbKaFqOxmjb2CKcbHcUGFrJCBWHmnTiUqi6JcnzoBlO4rWfpdl2k/RfLCA==", + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "license": "MIT", "dependencies": { - "@jest/types": "30.3.0", - "@types/node": "*", - "anymatch": "^3.1.3", - "fb-watchman": "^2.0.2", - "graceful-fs": "^4.2.11", - "jest-regex-util": "30.0.1", - "jest-util": "30.3.0", - "jest-worker": "30.3.0", - "picomatch": "^4.0.3", - "walker": "^1.0.8" + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">= 0.4" }, - "optionalDependencies": { - "fsevents": "^2.3.3" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/jest-leak-detector": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-30.3.0.tgz", - "integrity": "sha512-cuKmUUGIjfXZAiGJ7TbEMx0bcqNdPPI6P1V+7aF+m/FUJqFDxkFR4JqkTu8ZOiU5AaX/x0hZ20KaaIPXQzbMGQ==", - "dev": true, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", "license": "MIT", "dependencies": { - "@jest/get-type": "30.1.0", - "pretty-format": "30.3.0" + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">= 0.4" } }, - "node_modules/jest-matcher-utils": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.3.0.tgz", - "integrity": "sha512-HEtc9uFQgaUHkC7nLSlQL3Tph4Pjxt/yiPvkIrrDCt9jhoLIgxaubo1G+CFOnmHYMxHwwdaSN7mkIFs6ZK8OhA==", + "node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "@jest/get-type": "30.1.0", - "chalk": "^4.1.2", - "jest-diff": "30.3.0", - "pretty-format": "30.3.0" + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/jest-message-util": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.3.0.tgz", - "integrity": "sha512-Z/j4Bo+4ySJ+JPJN3b2Qbl9hDq3VrXmnjjGEWD/x0BCXeOXPTV1iZYYzl2X8c1MaCOL+ewMyNBcm88sboE6YWw==", + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "@babel/code-frame": "^7.27.1", - "@jest/types": "30.3.0", - "@types/stack-utils": "^2.0.3", - "chalk": "^4.1.2", - "graceful-fs": "^4.2.11", - "picomatch": "^4.0.3", - "pretty-format": "30.3.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.6" + "is-glob": "^4.0.1" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">= 6" } }, - "node_modules/jest-mock": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.3.0.tgz", - "integrity": "sha512-OTzICK8CpE+t4ndhKrwlIdbM6Pn8j00lvmSmq5ejiO+KxukbLjgOflKWMn3KE34EZdQm5RqTuKj+5RIEniYhog==", - "dev": true, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", "license": "MIT", - "dependencies": { - "@jest/types": "30.3.0", - "@types/node": "*", - "jest-util": "30.3.0" - }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/jest-pnp-resolver": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", - "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, "license": "MIT", "engines": { - "node": ">=6" - }, - "peerDependencies": { - "jest-resolve": "*" - }, - "peerDependenciesMeta": { - "jest-resolve": { - "optional": true - } + "node": ">=8" } }, - "node_modules/jest-regex-util": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz", - "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==", - "dev": true, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "license": "MIT", "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/jest-resolve": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-30.3.0.tgz", - "integrity": "sha512-NRtTAHQlpd15F9rUR36jqwelbrDV/dY4vzNte3S2kxCKUJRYNd5/6nTSbYiak1VX5g8IoFF23Uj5TURkUW8O5g==", + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", "dev": true, "license": "MIT", "dependencies": { - "chalk": "^4.1.2", - "graceful-fs": "^4.2.11", - "jest-haste-map": "30.3.0", - "jest-pnp-resolver": "^1.2.3", - "jest-util": "30.3.0", - "jest-validate": "30.3.0", - "slash": "^3.0.0", - "unrs-resolver": "^1.7.11" + "has-symbols": "^1.0.3" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/jest-resolve-dependencies": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.3.0.tgz", - "integrity": "sha512-9ev8s3YN6Hsyz9LV75XUwkCVFlwPbaFn6Wp75qnI0wzAINYWY8Fb3+6y59Rwd3QaS3kKXffHXsZMziMavfz/nw==", - "dev": true, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", "license": "MIT", "dependencies": { - "jest-regex-util": "30.0.1", - "jest-snapshot": "30.3.0" + "function-bind": "^1.1.2" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">= 0.4" } }, - "node_modules/jest-runner": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-30.3.0.tgz", - "integrity": "sha512-gDv6C9LGKWDPLia9TSzZwf4h3kMQCqyTpq+95PODnTRDO0g9os48XIYYkS6D236vjpBir2fF63YmJFtqkS5Duw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/console": "30.3.0", - "@jest/environment": "30.3.0", - "@jest/test-result": "30.3.0", - "@jest/transform": "30.3.0", - "@jest/types": "30.3.0", - "@types/node": "*", - "chalk": "^4.1.2", - "emittery": "^0.13.1", - "exit-x": "^0.2.2", - "graceful-fs": "^4.2.11", - "jest-docblock": "30.2.0", - "jest-environment-node": "30.3.0", - "jest-haste-map": "30.3.0", - "jest-leak-detector": "30.3.0", - "jest-message-util": "30.3.0", - "jest-resolve": "30.3.0", - "jest-runtime": "30.3.0", - "jest-util": "30.3.0", - "jest-watcher": "30.3.0", - "jest-worker": "30.3.0", - "p-limit": "^3.1.0", - "source-map-support": "0.5.13" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-runtime": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.3.0.tgz", - "integrity": "sha512-CgC+hIBJbuh78HEffkhNKcbXAytQViplcl8xupqeIWyKQF50kCQA8J7GeJCkjisC6hpnC9Muf8jV5RdtdFbGng==", + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true, + "license": "MIT" + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", "license": "MIT", "dependencies": { - "@jest/environment": "30.3.0", - "@jest/fake-timers": "30.3.0", - "@jest/globals": "30.3.0", - "@jest/source-map": "30.0.1", - "@jest/test-result": "30.3.0", - "@jest/transform": "30.3.0", - "@jest/types": "30.3.0", - "@types/node": "*", - "chalk": "^4.1.2", - "cjs-module-lexer": "^2.1.0", - "collect-v8-coverage": "^1.0.2", - "glob": "^10.5.0", - "graceful-fs": "^4.2.11", - "jest-haste-map": "30.3.0", - "jest-message-util": "30.3.0", - "jest-mock": "30.3.0", - "jest-regex-util": "30.0.1", - "jest-resolve": "30.3.0", - "jest-snapshot": "30.3.0", - "jest-util": "30.3.0", - "slash": "^3.0.0", - "strip-bom": "^4.0.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-snapshot": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.3.0.tgz", - "integrity": "sha512-f14c7atpb4O2DeNhwcvS810Y63wEn8O1HqK/luJ4F6M4NjvxmAKQwBUWjbExUtMxWJQ0wVgmCKymeJK6NZMnfQ==", - "dev": true, + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", "license": "MIT", "dependencies": { - "@babel/core": "^7.27.4", - "@babel/generator": "^7.27.5", - "@babel/plugin-syntax-jsx": "^7.27.1", - "@babel/plugin-syntax-typescript": "^7.27.1", - "@babel/types": "^7.27.3", - "@jest/expect-utils": "30.3.0", - "@jest/get-type": "30.1.0", - "@jest/snapshot-utils": "30.3.0", - "@jest/transform": "30.3.0", - "@jest/types": "30.3.0", - "babel-preset-current-node-syntax": "^1.2.0", - "chalk": "^4.1.2", - "expect": "30.3.0", - "graceful-fs": "^4.2.11", - "jest-diff": "30.3.0", - "jest-matcher-utils": "30.3.0", - "jest-message-util": "30.3.0", - "jest-util": "30.3.0", - "pretty-format": "30.3.0", - "semver": "^7.7.2", - "synckit": "^0.11.8" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-snapshot/node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", "dev": true, - "license": "ISC", + "license": "ISC" + }, + "node_modules/image-size": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-2.0.2.tgz", + "integrity": "sha512-IRqXKlaXwgSMAMtpNzZa1ZAe8m+Sa1770Dhk8VkSsP9LS+iHD62Zd8FQKs8fbPiagBE7BzoFX23cxFnwshpV6w==", + "license": "MIT", "bin": { - "semver": "bin/semver.js" + "image-size": "bin/image-size.js" }, "engines": { - "node": ">=10" + "node": ">=16.x" } }, - "node_modules/jest-util": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.3.0.tgz", - "integrity": "sha512-/jZDa00a3Sz7rdyu55NLrQCIrbyIkbBxareejQI315f/i8HjYN+ZWsDLLpoQSiUIEIyZF/R8fDg3BmB8AtHttg==", - "dev": true, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", "license": "MIT", - "dependencies": { - "@jest/types": "30.3.0", - "@types/node": "*", - "chalk": "^4.1.2", - "ci-info": "^4.2.0", - "graceful-fs": "^4.2.11", - "picomatch": "^4.0.3" - }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">= 0.10" } }, - "node_modules/jest-validate": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.3.0.tgz", - "integrity": "sha512-I/xzC8h5G+SHCb2P2gWkJYrNiTbeL47KvKeW5EzplkyxzBRBw1ssSHlI/jXec0ukH2q7x2zAWQm7015iusg62Q==", + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/get-type": "30.1.0", - "@jest/types": "30.3.0", - "camelcase": "^6.3.0", - "chalk": "^4.1.2", - "leven": "^3.1.0", - "pretty-format": "30.3.0" + "binary-extensions": "^2.0.0" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=8" } }, - "node_modules/jest-validate/node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "dev": true, "license": "MIT", "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=0.10.0" } }, - "node_modules/jest-watcher": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-30.3.0.tgz", - "integrity": "sha512-PJ1d9ThtTR8aMiBWUdcownq9mDdLXsQzJayTk4kmaBRHKvwNQn+ANveuhEBUyNI2hR1TVhvQ8D5kHubbzBHR/w==", + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true, "license": "MIT", - "dependencies": { - "@jest/test-result": "30.3.0", - "@jest/types": "30.3.0", - "@types/node": "*", - "ansi-escapes": "^4.3.2", - "chalk": "^4.1.2", - "emittery": "^0.13.1", - "jest-util": "30.3.0", - "string-length": "^4.0.2" - }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=8" } }, - "node_modules/jest-worker": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.3.0.tgz", - "integrity": "sha512-DrCKkaQwHexjRUFTmPzs7sHQe0TSj9nvDALKGdwmK5mW9v7j90BudWirKAJHt3QQ9Dhrg1F7DogPzhChppkJpQ==", + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dev": true, "license": "MIT", "dependencies": { - "@types/node": "*", - "@ungap/structured-clone": "^1.3.0", - "jest-util": "30.3.0", - "merge-stream": "^2.0.0", - "supports-color": "^8.1.1" + "is-extglob": "^2.1.1" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=0.10.0" } }, - "node_modules/jest-worker/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true, "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/jose": { - "version": "4.15.9", - "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.9.tgz", - "integrity": "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/panva" + "node": ">=0.12.0" } }, - "node_modules/js-tokens": { + "node_modules/is-promise": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true, + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", "license": "MIT" }, - "node_modules/js-yaml": { - "version": "3.14.2", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", - "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } + "license": "ISC" }, - "node_modules/jsesc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", - "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", "dev": true, - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, + "license": "BSD-3-Clause", "engines": { - "node": ">=6" + "node": ">=8" } }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true, - "license": "MIT" - }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", "dev": true, - "license": "MIT", - "bin": { - "json5": "lib/cli.js" + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" }, "engines": { - "node": ">=6" - } - }, - "node_modules/leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" + "node": ">=10" } }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true, - "license": "MIT" - }, - "node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", "dev": true, - "license": "MIT", + "license": "BSD-3-Clause", "dependencies": { - "p-locate": "^4.1.0" + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" }, "engines": { "node": ">=8" } }, - "node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { - "yallist": "^3.0.2" + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jose": { + "version": "4.15.9", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.9.tgz", + "integrity": "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" } }, "node_modules/make-dir": { @@ -4200,16 +1671,6 @@ "node": ">=10" } }, - "node_modules/makeerror": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", - "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "tmpl": "1.0.5" - } - }, "node_modules/mariadb": { "version": "3.5.2", "resolved": "https://registry.npmjs.org/mariadb/-/mariadb-3.5.2.tgz", @@ -4274,13 +1735,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true, - "license": "MIT" - }, "node_modules/methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", @@ -4329,16 +1783,6 @@ "url": "https://opencollective.com/express" } }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/minimatch": { "version": "9.0.9", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", @@ -4507,29 +1951,6 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, - "node_modules/napi-postinstall": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz", - "integrity": "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==", - "dev": true, - "license": "MIT", - "bin": { - "napi-postinstall": "lib/cli.js" - }, - "engines": { - "node": "^12.20.0 || ^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/napi-postinstall" - } - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true, - "license": "MIT" - }, "node_modules/negotiator": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", @@ -4539,20 +1960,6 @@ "node": ">= 0.6" } }, - "node_modules/node-int64": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", - "dev": true, - "license": "MIT" - }, - "node_modules/node-releases": { - "version": "2.0.36", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.36.tgz", - "integrity": "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==", - "dev": true, - "license": "MIT" - }, "node_modules/nodemailer": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-8.0.2.tgz", @@ -4676,19 +2083,6 @@ "node": ">=0.10.0" } }, - "node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -4740,22 +2134,6 @@ "wrappy": "1" } }, - "node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -4772,45 +2150,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-locate/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/package-json-from-dist": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", @@ -4818,25 +2157,6 @@ "dev": true, "license": "BlueOak-1.0.0" }, - "node_modules/parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/parse-svg-path": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/parse-svg-path/-/parse-svg-path-0.1.2.tgz", @@ -4863,16 +2183,6 @@ "node": ">=8" } }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -4917,77 +2227,6 @@ "url": "https://opencollective.com/express" } }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pirates": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", - "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, - "node_modules/pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "find-up": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pretty-format": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.3.0.tgz", - "integrity": "sha512-oG4T3wCbfeuvljnyAzhBvpN45E8iOTXCU/TD3zXW80HA3dQ4ahdqMkWGiPWZvjpQwlbyHrPTWUAqUzGzv4l1JQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/schemas": "30.0.5", - "ansi-styles": "^5.2.0", - "react-is": "^18.3.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -5017,23 +2256,6 @@ "node": ">=6" } }, - "node_modules/pure-rand": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-7.0.1.tgz", - "integrity": "sha512-oTUZM/NAZS8p7ANR3SHh30kXB+zK2r2BPcEn/awJIbOvq82WoMN4p62AWWp3Hhw50G0xMsw1mhIBLqHw64EcNQ==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/dubzzz" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fast-check" - } - ], - "license": "MIT" - }, "node_modules/qs": { "version": "6.15.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz", @@ -5073,13 +2295,6 @@ "node": ">= 0.10" } }, - "node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "dev": true, - "license": "MIT" - }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -5116,29 +2331,6 @@ "node": ">=0.10.0" } }, - "node_modules/resolve-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/router": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", @@ -5167,16 +2359,6 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "license": "MIT" }, - "node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/send": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", @@ -5380,37 +2562,6 @@ "url": "https://opencollective.com/sinon" } }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-support": { - "version": "0.5.13", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", - "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, "node_modules/sparse-bitfield": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", @@ -5420,26 +2571,6 @@ "memory-pager": "^1.0.2" } }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/stack-utils": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", - "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "escape-string-regexp": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/statuses": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", @@ -5449,43 +2580,6 @@ "node": ">= 0.8" } }, - "node_modules/string-length": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", - "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "char-regex": "^1.0.2", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/string-length/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/string-length/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/string-width": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", @@ -5590,39 +2684,6 @@ "node": ">=8" } }, - "node_modules/strip-bom": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", - "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/superagent": { "version": "10.3.0", "resolved": "https://registry.npmjs.org/superagent/-/superagent-10.3.0.tgz", @@ -5689,90 +2750,6 @@ "license": "ISC", "optional": true }, - "node_modules/synckit": { - "version": "0.11.12", - "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.12.tgz", - "integrity": "sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@pkgr/core": "^0.2.9" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/synckit" - } - }, - "node_modules/test-exclude": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", - "dev": true, - "license": "ISC", - "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/test-exclude/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/test-exclude/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/test-exclude/node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/tmpl": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", - "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", - "dev": true, - "license": "BSD-3-Clause" - }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -5809,14 +2786,6 @@ "resolved": "", "link": true }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD", - "optional": true - }, "node_modules/type-detect": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", @@ -5827,19 +2796,6 @@ "node": ">=4" } }, - "node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/type-is": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", @@ -5876,72 +2832,6 @@ "node": ">= 0.8" } }, - "node_modules/unrs-resolver": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.11.1.tgz", - "integrity": "sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "dependencies": { - "napi-postinstall": "^0.3.0" - }, - "funding": { - "url": "https://opencollective.com/unrs-resolver" - }, - "optionalDependencies": { - "@unrs/resolver-binding-android-arm-eabi": "1.11.1", - "@unrs/resolver-binding-android-arm64": "1.11.1", - "@unrs/resolver-binding-darwin-arm64": "1.11.1", - "@unrs/resolver-binding-darwin-x64": "1.11.1", - "@unrs/resolver-binding-freebsd-x64": "1.11.1", - "@unrs/resolver-binding-linux-arm-gnueabihf": "1.11.1", - "@unrs/resolver-binding-linux-arm-musleabihf": "1.11.1", - "@unrs/resolver-binding-linux-arm64-gnu": "1.11.1", - "@unrs/resolver-binding-linux-arm64-musl": "1.11.1", - "@unrs/resolver-binding-linux-ppc64-gnu": "1.11.1", - "@unrs/resolver-binding-linux-riscv64-gnu": "1.11.1", - "@unrs/resolver-binding-linux-riscv64-musl": "1.11.1", - "@unrs/resolver-binding-linux-s390x-gnu": "1.11.1", - "@unrs/resolver-binding-linux-x64-gnu": "1.11.1", - "@unrs/resolver-binding-linux-x64-musl": "1.11.1", - "@unrs/resolver-binding-wasm32-wasi": "1.11.1", - "@unrs/resolver-binding-win32-arm64-msvc": "1.11.1", - "@unrs/resolver-binding-win32-ia32-msvc": "1.11.1", - "@unrs/resolver-binding-win32-x64-msvc": "1.11.1" - } - }, - "node_modules/update-browserslist-db": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", - "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, "node_modules/v8-to-istanbul": { "version": "9.3.0", "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", @@ -5966,16 +2856,6 @@ "node": ">= 0.8" } }, - "node_modules/walker": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", - "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "makeerror": "1.0.12" - } - }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -6093,20 +2973,6 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "license": "ISC" }, - "node_modules/write-file-atomic": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", - "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", - "dev": true, - "license": "ISC", - "dependencies": { - "imurmurhash": "^0.1.4", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", @@ -6117,13 +2983,6 @@ "node": ">=10" } }, - "node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true, - "license": "ISC" - }, "node_modules/yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", diff --git a/package.json b/package.json index c733e594..921a0e17 100644 --- a/package.json +++ b/package.json @@ -19,25 +19,22 @@ "author": "Research Computing Group (https://slu.edu)", "repository": "github:CenterForDigitalHumanities/rerum_server_nodejs", "scripts": { - "start": "node ./bin/tpen3_services.js", - "dev": "nodemon ./bin/tpen3_services.js", - "allTests": "node --import ./env-loader.js --experimental-vm-modules node_modules/jest/bin/jest.js", - "unitTests": "node --import ./env-loader.js --experimental-vm-modules node_modules/jest/bin/jest.js -t _unit", - "E2Etests": "node --import ./env-loader.js --experimental-vm-modules node_modules/jest/bin/jest.js -t e2e ", - "existsTests": "node --import ./env-loader.js --experimental-vm-modules node_modules/jest/bin/jest.js -t exists_unit ", - "functionsTests": "node --import ./env-loader.js --experimental-vm-modules node_modules/jest/bin/jest.js -t functions_unit ", - "dbTests": "node --import ./env-loader.js --experimental-vm-modules node_modules/jest/bin/jest.js -t db ", - "authTest": "node --import ./env-loader.js --experimental-vm-modules node_modules/jest/bin/jest.js -t auth_test ", - "userClassTests": "node --import ./env-loader.js --experimental-vm-modules node_modules/jest/bin/jest.js -t user_class ", - "importTests": "node --import ./env-loader.js --experimental-vm-modules node_modules/jest/bin/jest.js -t importTests ", - "inviteMemberTests": "node --import ./env-loader.js --experimental-vm-modules node_modules/jest/bin/jest.js -t inviteMemberTests " + "start": "node --env-file=config.env --env-file-if-exists=.env.development --env-file-if-exists=.env ./bin/tpen3_services.js", + "dev": "nodemon --exec \"node --env-file=config.env --env-file-if-exists=.env.development --env-file-if-exists=.env\" ./bin/tpen3_services.js", + "test": "npm run test:local", + "test:local": "node --env-file=config.env --env-file-if-exists=.env.development --env-file-if-exists=.env --test \"test/local/**/*.test.js\"", + "test:integration": "node --env-file=config.env --env-file-if-exists=.env.development --env-file-if-exists=.env --test \"test/integration/**/*.test.js\"", + "test:all": "node --env-file=config.env --env-file-if-exists=.env.development --env-file-if-exists=.env --test \"test/local/**/*.test.js\" \"test/integration/**/*.test.js\"", + "test:coverage": "c8 --reporter=text --reporter=html --reporter=json npm run test:all", + "allTests": "npm run test:all", + "unitTests": "npm run test:local", + "E2Etests": "npm run test:integration" }, "dependencies": { "@iiif/helpers": "^1.5.8", "cookie-parser": "^1.4.7", "cors": "^2.8.6", "debug": "^4.4.3", - "dotenv": "^17.3.1", "express": "^5.2.1", "express-list-endpoints": "^7.1.1", "express-oauth2-jwt-bearer": "^1.7.4", @@ -50,8 +47,7 @@ "tpen3-services": "file:" }, "devDependencies": { - "@jest/globals": "^30.3.0", - "jest": "^30.3.0", + "c8": "^10.1.3", "nodemon": "^3.1.14", "sinon": "^21.0.2", "supertest": "^7.2.2" diff --git a/page/__tests__/end_to_end_unit.test.js b/page/__tests__/end_to_end_unit.test.js deleted file mode 100644 index fd33eb0a..00000000 --- a/page/__tests__/end_to_end_unit.test.js +++ /dev/null @@ -1,108 +0,0 @@ -import pageRouter from '../index.js' -import express from 'express' -import request from 'supertest' - -const routeTester = new express() -routeTester.use("/", pageRouter) - -describe.skip('page endpoint end to end unit test (spinning up the endpoint and using it). #end2end_unit', () => { - - it('POST instead of GET. That status should be 405 with a message.', async () => { - const res = await request(routeTester) - .post('/dummyId') - expect(res.statusCode).toBe(405) - expect(res.body).toBeTruthy() - }) - - it('PUT unauthed. That status should be 401 with a message.', async () => { - const res = await request(routeTester) - .put('/dummyId') - expect(res.statusCode).toBe(401) - expect(res.body).toBeTruthy() - }) - - it('PATCH instead of GET. That status should be 405 with a message.', async () => { - const res = await request(routeTester) - .patch('/dummyId') - expect(res.statusCode).toBe(405) - expect(res.body).toBeTruthy() - }) - - it('Call to /page without a TPEN3 page ID. The status should be 404.', async () => { - const res = await request(routeTester) - .get('/') - expect(res.statusCode).toBe(404) - expect(res.body).toBeTruthy() - }) - - // These two cannot work without a corresponding project, so it will need to be rewritten - it('Call to /page with a TPEN3 page ID that does not exist. The status should be 404 with a message.', async () => { - const res = await request(routeTester) - .get('/0001') - expect(res.statusCode).toBe(404) - expect(res.body).toBeTruthy() - }) - - it('Call to /page with a TPEN3 page ID that does exist. The status should be 200 with a JSON page in the body.', async () => { - const res = await request(routeTester) - .get('/123') - let json = res.body - try{ - json = JSON.parse(JSON.stringify(json)) - } - catch(err){ - json = null - } - expect(json).not.toBe(null) - }) - - it('should reject suspicious label in request body', async () => { - const suspiciousPage = - { - label: "while(true) return true", - items: [ - { - "type":"Annotation", - "body": { - "type": "TextualBody", - "value": "OK", - "format": "text/plain" - }, - "target": "https://example.org/canvas/1#xywh=100,100,100,100" - } - ], - "target": "https://example.org/canvas/1#xywh=100,100,100,100" - } - - const res = await request(app) - .put('/project/123/layer/layer1/page/page1') - .send(suspiciousPage) - - expect(res.status).toBe(400) - }) - - it('should reject suspicious content in items array', async () => { - const suspiciousPage = - { - label: "OK", - items: [ - { - "type":"Annotation", - "body": { - "type": "TextualBody", - "value": "while(true) return true", - "format": "text/plain" - }, - "target": "https://example.org/canvas/1#xywh=100,100,100,100" - } - ], - "target": "https://example.org/canvas/1" - } - const res = await request(app) - .put('/project/123/layer/layer1/page/page1') - .send(suspiciousPage) - - expect(res.status).toBe(400) - }) - -}) diff --git a/project/__tests__/customMetadataRouter.test.js b/project/__tests__/customMetadataRouter.test.js deleted file mode 100644 index 5d8cf463..00000000 --- a/project/__tests__/customMetadataRouter.test.js +++ /dev/null @@ -1,78 +0,0 @@ -/** - * Custom Metadata Router Tests - * Tests for namespaced interfaces functionality - */ - -import { jest } from "@jest/globals" -import request from "supertest" -import app from "../../app.js" - -describe("Custom Metadata Router - Route Existence Tests #exists_unit", () => { - describe("GET /project/:id/custom", () => { - it("should have a GET endpoint to retrieve namespace keys", () => { - // This test is covered in mount.test.js - expect(true).toBe(true) - }) - }) - - describe("POST /project/:id/custom", () => { - it("should have a POST endpoint to create/replace namespace data", () => { - // This test is covered in mount.test.js - expect(true).toBe(true) - }) - }) - - describe("PUT /project/:id/custom", () => { - it("should have a PUT endpoint to upsert namespace data", () => { - // This test is covered in mount.test.js - expect(true).toBe(true) - }) - }) -}) - -describe("Custom Metadata Router - Namespace Detection #unit_test", () => { - describe("Origin detection", () => { - it("should detect localhost as wildcard", () => { - const testOrigins = [ - "http://localhost:3000", - "http://127.0.0.1:8080", - "http://0.0.0.0", - "http://[::1]:3000" - ] - - // These would be tested in integration tests with actual requests - expect(testOrigins.length).toBeGreaterThan(0) - }) - - it("should extract hostname from origin", () => { - const testOrigins = [ - { origin: "https://app.t-pen.org", expected: "app.t-pen.org" }, - { origin: "https://exampleapp.com:443", expected: "exampleapp.com" }, - { origin: "http://cnn.com", expected: "cnn.com" } - ] - - // These would be tested in integration tests - expect(testOrigins.length).toBeGreaterThan(0) - }) - }) -}) - -describe("Custom Metadata Router - Deep Upsert Logic #unit_test", () => { - describe("Type validation", () => { - it("should throw error on type mismatch", () => { - // Test cases for deepUpsert function - // These would be integration tests - expect(true).toBe(true) - }) - - it("should delete keys set to null", () => { - // Test null deletion logic - expect(true).toBe(true) - }) - - it("should recursively merge objects", () => { - // Test deep merge - expect(true).toBe(true) - }) - }) -}) diff --git a/project/__tests__/end_to_end_unit.test.js b/project/__tests__/end_to_end_unit.test.js deleted file mode 100644 index 26bdf4d6..00000000 --- a/project/__tests__/end_to_end_unit.test.js +++ /dev/null @@ -1,285 +0,0 @@ -import projectRouter from "../index.js" -import express from "express" -import request from "supertest" -import app from "../../app.js" -import {jest} from "@jest/globals" -import ProjectFactory from "../../classes/Project/ProjectFactory.js" -import Project from "../../classes/Project/Project.js" -import DatabaseController from "../../database/mongo/controller.js" - -const routeTester = new express() -let token = process.env.TEST_TOKEN -routeTester.use("/project", projectRouter) -describe.skip("Project endpoint end to end unit test (spinning up the endpoint and using it). #end2end_unit #projectTest", () => { - it("Call to /project with a non-hexadecimal project ID. The status should be 400 with a message.", async () => { - const res = await request(routeTester) - .get("/project/zzz") - .set("Authorization", `Bearer ${token}`) - expect(res.statusCode).toBe(400) - expect(res.body).toBeTruthy() - }) - - it("Call to /project with a TPEN3 project ID that does not exist. The status should be 404 with a message.", async () => { - const res = await request(routeTester) - .get("/project/0001") - .set("Authorization", `Bearer ${token}`) - expect(res.statusCode).toBe(404) - expect(res.body).toBeTruthy() - }) - - it("Call to /project with a TPEN3 project ID that does exist. The status should be 200 with a JSON Project in the body.", async () => { - const res = await request(routeTester) - .get("/project/6602dd2314cd575343f513ba") - .set("Authorization", `Bearer ${token}`) - expect(res.statusCode).toBe(200) - expect(res.body).toBeTruthy() - }) -}) - -describe.skip("Project endpoint end to end unit test to /project/create #end2end_unit", () => { - it("GET instead of POST. The status should be 404 with a message.", async () => { - const res = await request(routeTester) - .get("/project/create") - .set("Authorization", `Bearer ${token}`) - expect(res.statusCode).toBe(405) - expect(res.body).toBeTruthy() - }) - - it("PUT instead of POST. The status should be 404 with a message.", async () => { - const res = await request(routeTester).put("/project/create") - expect(res.statusCode).toBe(405) - expect(res.body).toBeTruthy() - }) - - it("PATCH instead of POST. The status should be 404 with a message.", async () => { - const res = await request(routeTester).patch("/project/create") - expect(res.statusCode).toBe(405) - expect(res.body).toBeTruthy() - }) - - it.skip("should create a project and respond with status 201 if the user is authenticated and valid data is provided", async () => { - const mockProject = {name: "New Project"} - const mockCreatedProject = { - ...mockProject, - _id: "newProjectId", - creator: "agentId" - } - - jest - .spyOn(Project.prototype, "create") - .mockResolvedValueOnce(mockCreatedProject) - - const response = await request(app) - .post("/project/create") - .set("Authorization", `Bearer ${token}`) - .send(mockProject) - - expect(response.status).toBe(201) - expect(response.headers.location).toBe(mockCreatedProject._id) - expect(response.body).toEqual(mockCreatedProject) - }) -}) - -describe.skip("POST /project/import?createFrom=URL #importTests", () => { - afterEach(() => { - jest.restoreAllMocks() - }) - - it("should import project successfully", async () => { - const manifestURL = "https://t-pen.org/TPEN/project/4080" - const mockProject = { - label: "Test Project", - "@type": "@Project", - metadata: [{label: "title", value: "Lorem Ipsum"}], - " @context": "http://t-pen.org/3/context.json", - layers: [] - } - - jest.spyOn(ProjectFactory, "fromManifestURL").mockResolvedValue(mockProject) - - const response = await request(app) - .post(`/project/import?createFrom=URL`) - .set("Authorization", `Bearer ${token}`) - .send({url: manifestURL}) - expect(response.status).toBe(201) - expect(response.body).toEqual(mockProject) - expect(ProjectFactory.fromManifestURL).toHaveBeenCalled() - }) - - it("should return 400 if createFrom is not provided #importTests", async () => { - const response = await request(app) - .post("/project/import") - .set("Authorization", `Bearer ${token}`) - .send({}) - expect(response.status).toBe(400) - expect(response.body.message).toBe( - "Query string 'createFrom' is required, specify manifest source as 'URL' or 'DOC' " - ) - }) - - it("should return 404 if manifest URL is not provided when createFrom=url", async () => { - const response = await request(app) - .post("/project/import?createFrom=url") - .set("Authorization", `Bearer ${token}`) - .send({}) - expect(response.status).toBe(404) - expect(response.body.message).toBe("Manifest URL is required for import") - }) - - it("should handle unknown server errors", async () => { - const manifestURL = "https://t-pen.org/TPEN/project/4080" - - jest - .spyOn(ProjectFactory, "fromManifestURL") - .mockRejectedValue(new Error("Import error")) - - const response = await request(app) - .post(`/project/import?createFrom=url`) - .set("Authorization", `Bearer ${token}`) - .send({url: manifestURL}) - expect(response.status).toBe(500) - expect(response.body.message).toBeTruthy() - }) -}) - - -// Invite member test cases -describe.skip("POST /project/:id/invite-member ", () => { - afterEach(() => { - jest.restoreAllMocks(); - }); - - it("should invite a member successfully when the user has permission", async () => { - const projectId = "6602dd2314cd575343f513ba"; - const mockResponse = { success: true, message: "Member invited" }; - - jest.spyOn(Project.prototype, "addMember").mockResolvedValue(mockResponse); - - const response = await request(app) - .post(`/project/${projectId}/invite-member`) - .set("Authorization", `Bearer ${token}`) - .send({ email: "newuser@example.com", roles: ["CONTRIBUTOR"] }); - - expect(response.status).toBe(200); - expect(response.body).toEqual(mockResponse); - expect(Project.prototype.addMember).toHaveBeenCalled(); - }); - - it("should return 400 if email is missing", async () => { - const projectId = "6602dd2314cd575343f513ba"; - - const response = await request(app) - .post(`/project/${projectId}/invite-member`) - .set("Authorization", `Bearer ${token}`) - .send({ roles: ["CONTRIBUTOR"] }); - - expect(response.status).toBe(400); - expect(response.body.message).toBe("Invitee's email is required"); - }); - - it("should return 401 if the user is unauthorized", async () => { - const projectId = "6602dd2314cd575343f513ba"; - - const response = await request(app) - .post(`/project/${projectId}/invite-member`) - .send({ email: "newuser@example.com", roles: ["CONTRIBUTOR"] }); - - expect(response.status).toBe(401); - expect(response.body).toBeTruthy(); - }); -}); - - -// Remove member Test cases -describe.skip("POST /project/:id/remove-member ", () => { - afterEach(() => { - jest.restoreAllMocks(); - }); - - it("should remove a member successfully when the user has permission", async () => { - const projectId = "6602dd2314cd575343f513ba"; - const mockResponse = { success: true, message: "Member removed" }; - - jest.spyOn(Project.prototype, "removeMember").mockResolvedValue(mockResponse); - - const response = await request(app) - .post(`/project/${projectId}/remove-member`) - .set("Authorization", `Bearer ${token}`) - .send({ userId: "userIdToRemove" }); - - expect(response.status).toBe(204); - expect(Project.prototype.removeMember).toHaveBeenCalled(); - }); - - it("should return 400 if userId is missing", async () => { - const projectId = "6602dd2314cd575343f513ba"; - - const response = await request(app) - .post(`/project/${projectId}/remove-member`) - .set("Authorization", `Bearer ${token}`) - .send({}); - - expect(response.status).toBe(400); - expect(response.body.message).toBe("User ID is required"); - }); - - it("should return 401 if the user is unauthorized", async () => { - const projectId = "6602dd2314cd575343f513ba"; - - const response = await request(app) - .post(`/project/${projectId}/remove-member`) - .send({ userId: "userIdToRemove" }); - - expect(response.status).toBe(401); - expect(response.body).toBeTruthy(); - }); -}); - -// Layer and Page Test cases -describe.skip("GET /project/:projectId/layer/:layerId", () => { - it("should return a valid AnnotationCollection for a valid layer", async () => { - const projectId = "6602dd2314cd575343f513ba" - const layerId = "layer123" - - const res = await request(routeTester) - .get(`/project/${projectId}/layer/${layerId}`) - - expect(res.statusCode).toBe(200) - expect(res.body).toHaveProperty("@context", "http://www.w3.org/ns/anno.jsonld") - expect(res.body).toHaveProperty("type", "AnnotationCollection") - }) - - it("should return 404 if the layer does not exist", async () => { - const projectId = "6602dd2314cd575343f513ba" - const layerId = "nonexistentLayer" - - const res = await request(routeTester) - .get(`/project/${projectId}/layer/${layerId}`) - - expect(res.statusCode).toBe(404) - }) -}) - -describe.skip("GET /project/:projectId/page/:pageId", () => { - it("should return a valid AnnotationPage for a valid page", async () => { - const projectId = "6602dd2314cd575343f513ba" - const pageId = "page123" - - const res = await request(routeTester) - .get(`/project/${projectId}/page/${pageId}`) - - expect(res.statusCode).toBe(200) - expect(res.body).toHaveProperty("@context", "http://www.w3.org/ns/anno.jsonld") - expect(res.body).toHaveProperty("type", "AnnotationPage") - }) - - it("should return 404 if the page does not exist", async () => { - const projectId = "6602dd2314cd575343f513ba" - const pageId = "nonexistentPage" - - const res = await request(routeTester) - .get(`/project/${projectId}/page/${pageId}`) - - expect(res.statusCode).toBe(404) - }) -}) diff --git a/project/__tests__/exists_unit.test.js b/project/__tests__/exists_unit.test.js deleted file mode 100644 index 93946e77..00000000 --- a/project/__tests__/exists_unit.test.js +++ /dev/null @@ -1,18 +0,0 @@ -import app from '../../app.js' -import { jest } from '@jest/globals' -import expressListEndpoints from "express-list-endpoints" - -// TODO check every /project endpoint. - -// Example -it.skip('/project/:id is a registered endpoint', () => { - let exists = false - const stack = expressListEndpoints(app.router) - for(const middleware of stack){ - if(middleware.path && middleware.path.includes("/project/:id")) { - exists = true - break - } - } - expect(exists).toBe(true) -}) diff --git a/test/integration/api.seams.integration.test.js b/test/integration/api.seams.integration.test.js new file mode 100644 index 00000000..894c2841 --- /dev/null +++ b/test/integration/api.seams.integration.test.js @@ -0,0 +1,51 @@ +import { describe, it } from 'node:test' +import assert from 'node:assert/strict' +import fs from 'node:fs' +import path from 'node:path' +import { fileURLToPath } from 'node:url' + +const __filename = fileURLToPath(import.meta.url) +const __dirname = path.dirname(__filename) +const repoRoot = path.resolve(__dirname, '../..') + +describe('API seam integration', () => { + it('keeps public static API docs artifact present', () => { + const apiHtml = path.join(repoRoot, 'public/API.html') + + assert.equal(fs.existsSync(apiHtml), true, 'public/API.html must be served as the API reference') + }) + + it('keeps root router method guard response contract in source', () => { + const indexRouterSource = fs.readFileSync(path.join(repoRoot, 'index.js'), 'utf8') + + assert.match(indexRouterSource, /\.all\(\(_req, res, next\) => \{/, 'index router must declare a method-guard .all handler') + assert.match(indexRouterSource, /respondWithError\(res, 404, 'There is nothing for you here\.'/, 'method-guard must respond with the documented 404 contract message') + }) + + it('keeps CORS middleware and fallback 404 handler in app source', () => { + const appSource = fs.readFileSync(path.join(repoRoot, 'app.js'), 'utf8') + + assert.match(appSource, /app\.use\(cors\(corsOptions\)\)/, 'app must register the cors middleware with the configured options') + assert.match(appSource, /app\.use\('\*_', \(req, res\) => \{/, 'app must register the catch-all 404 handler') + }) + + it('keeps maintenance-mode .all gate in app source', () => { + const appSource = fs.readFileSync(path.join(repoRoot, 'app.js'), 'utf8') + + // app.js declares two `*_` handlers: the maintenance .all gate (runs first, + // returns 503 when DOWN=true) and the catch-all .use 404. Anchor on the + // .all+DOWN condition together so a mutation that removes the gate — or + // inverts the env check — fails this test rather than silently letting + // traffic through during a declared outage. + assert.match( + appSource, + /app\.all\('\*_', \(req, res, next\) => \{[\s\S]*?process\.env\.DOWN === 'true'/, + 'app must declare an .all maintenance gate that branches on DOWN === "true"' + ) + assert.match( + appSource, + /respondWithError\(\s*res,\s*503,/, + 'maintenance gate must respond with 503 (not a generic 500 or 4xx)' + ) + }) +}) diff --git a/test/local/api.seams.static.test.js b/test/local/api.seams.static.test.js new file mode 100644 index 00000000..55f04b7e --- /dev/null +++ b/test/local/api.seams.static.test.js @@ -0,0 +1,64 @@ +import { describe, it } from 'node:test' +import assert from 'node:assert/strict' +import fs from 'node:fs' +import path from 'node:path' +import { fileURLToPath } from 'node:url' + +const __filename = fileURLToPath(import.meta.url) +const __dirname = path.dirname(__filename) +const repoRoot = path.resolve(__dirname, '../..') + +function readWorkspaceFile(relativePath) { + const filePath = path.join(repoRoot, relativePath) + return fs.readFileSync(filePath, 'utf8') +} + +describe('API seam contracts (static)', () => { + it('keeps core app route mounts in app.js', () => { + const appSource = readWorkspaceFile('app.js') + + assert.match(appSource, /app\.use\('\/'\s*,\s*indexRouter\)/, 'indexRouter must be mounted at /') + assert.match(appSource, /app\.use\('\/project\/:projectId\/page\/:pageId\/line'\s*,\s*lineRouter\)/, 'lineRouter must be mounted under the project/page hierarchy') + assert.match(appSource, /app\.use\('\/project\/:projectId\/page'\s*,\s*pageRouter\)/, 'pageRouter must be mounted under /project/:projectId/page') + assert.match(appSource, /app\.use\('\/project'\s*,\s*projectRouter\)/, 'projectRouter must be mounted at /project') + }) + + it('keeps column and clear-column page route seams', () => { + const pageSource = readWorkspaceFile('page/index.js') + + // Slice each route's block from its `router.route(...)` declaration up to the next + // top-level `router.` call. This anchors method/middleware assertions to a single + // route so a mutation removing auth from one route can't be masked by an identical + // chain on a sibling route. + const sliceRoute = (path) => { + const pattern = new RegExp(`router\\.route\\('${path}'\\)([\\s\\S]*?)\\n(?:router\\.|export )`) + return pageSource.match(pattern)?.[1] ?? '' + } + + assert.match(pageSource, /router\.route\('\/:pageId\/column'\)/, 'page router must declare a /:pageId/column route') + const columnRoute = sliceRoute('\\/:pageId\\/column') + assert.notEqual(columnRoute, '', '/:pageId/column route block must be extractable') + assert.match(columnRoute, /\.post\(auth0Middleware\(\)/, '/:pageId/column must accept POST guarded by auth0Middleware') + assert.match(columnRoute, /\.put\(auth0Middleware\(\)/, '/:pageId/column must accept PUT guarded by auth0Middleware') + assert.match(columnRoute, /\.patch\(auth0Middleware\(\)/, '/:pageId/column must accept PATCH guarded by auth0Middleware') + + assert.match(pageSource, /router\.route\('\/:pageId\/clear-columns'\)/, 'page router must declare a /:pageId/clear-columns route') + const clearColumnsRoute = sliceRoute('\\/:pageId\\/clear-columns') + assert.notEqual(clearColumnsRoute, '', '/:pageId/clear-columns route block must be extractable') + assert.match(clearColumnsRoute, /\.delete\(auth0Middleware\(\)/, '/:pageId/clear-columns must accept DELETE guarded by auth0Middleware') + + assert.match(pageSource, /router\.route\('\/:pageId\/resolved'\)/, 'page router must declare a /:pageId/resolved read endpoint') + const resolvedRoute = sliceRoute('\\/:pageId\\/resolved') + assert.notEqual(resolvedRoute, '', '/:pageId/resolved route block must be extractable') + assert.match(resolvedRoute, /\.get\(async \(req, res\) => \{/, '/:pageId/resolved must accept GET — read seam is part of the published contract') + }) + + it('keeps line route write seams and text patch endpoint', () => { + const lineSource = readWorkspaceFile('line/index.js') + + assert.match(lineSource, /router\.post\('\/'\s*,\s*auth0Middleware\(\)/, 'line router must accept POST / guarded by auth0Middleware') + assert.match(lineSource, /router\.put\('\/:lineId'\s*,\s*auth0Middleware\(\)/, 'line router must accept PUT /:lineId guarded by auth0Middleware') + assert.match(lineSource, /router\.patch\('\/:lineId\/text'\s*,\s*auth0Middleware\(\)/, 'line router must expose PATCH /:lineId/text guarded by auth0Middleware') + assert.match(lineSource, /router\.patch\('\/:lineId\/bounds'\s*,\s*auth0Middleware\(\)/, 'line router must expose PATCH /:lineId/bounds guarded by auth0Middleware') + }) +}) diff --git a/test/local/openapi.artifacts.test.js b/test/local/openapi.artifacts.test.js new file mode 100644 index 00000000..d917d7ce --- /dev/null +++ b/test/local/openapi.artifacts.test.js @@ -0,0 +1,46 @@ +import { describe, it } from 'node:test' +import assert from 'node:assert/strict' +import fs from 'node:fs' +import path from 'node:path' +import { fileURLToPath } from 'node:url' + +const __filename = fileURLToPath(import.meta.url) +const __dirname = path.dirname(__filename) +const repoRoot = path.resolve(__dirname, '../..') + +describe('OpenAPI shared artifacts', () => { + it('contains expected baseline fields in provider artifact', () => { + const providerArtifactPath = path.join(repoRoot, 'openapi/components/tpen-services-shared-components.openapi.yaml') + const providerArtifact = fs.readFileSync(providerArtifactPath, 'utf8') + + assert.match(providerArtifact, /^openapi: 3\.\d+\.\d+/m, 'must declare an openapi 3.x version') + assert.match(providerArtifact, /^\s+title: TPEN Services Shared OpenAPI Components/m, 'info.title must be present') + assert.match(providerArtifact, /^\s+version: \d+\.\d+\.\d+(-[\w.]+)?/m, 'info.version must be a semver-style string') + assert.match(providerArtifact, /^components:/m, 'must define a top-level components section') + }) + + it('keeps sync workflow wired to shared artifact path', () => { + const workflowPath = path.join(repoRoot, '.github/workflows/sync_tpen_shared_openapi.yaml') + const workflow = fs.readFileSync(workflowPath, 'utf8') + + // Pin the cp invocation as a single regex so source→target direction is enforced. + // Each path appears 3× in this workflow (trigger filter, cp command, PR body), so + // a global substring match for either side cannot detect a swap or a deleted cp step. + assert.match( + workflow, + /run:\s*cp\s+openapi\/components\/tpen-services-shared-components\.openapi\.yaml\s+receiver\/schemas\/openapi\/tpen-services-shared-components\.openapi\.yaml/, + 'sync workflow must cp the canonical source artifact to the receiver target path (this exact direction)' + ) + assert.match(workflow, /repository: cubap\/rerum_openapi/, 'workflow must check out cubap/rerum_openapi as the receiver') + assert.match( + workflow, + /peter-evans\/create-pull-request@v\d+/, + 'workflow must pin a major version of peter-evans/create-pull-request — an unpinned action drifts silently across runs' + ) + assert.match( + workflow, + /secrets\.OPENAPI(?!\w)/, + 'workflow must read the org-level OPENAPI secret — a rename to OPENAPI_FOO breaks the sync silently at receiver checkout' + ) + }) +}) diff --git a/test/local/utility.validation.test.js b/test/local/utility.validation.test.js new file mode 100644 index 00000000..aa19890d --- /dev/null +++ b/test/local/utility.validation.test.js @@ -0,0 +1,63 @@ +import { describe, it } from 'node:test' +import assert from 'node:assert/strict' +import { validateProjectPayload } from '../../utilities/validatePayload.js' +import scrubDefaultRoles from '../../utilities/isDefaultRole.js' + +function buildValidProjectPayload() { + return { + metadata: [{ label: { none: ['source'] }, value: 'test source' }], + layers: [ + { + id: 'layer-1', + label: 'Layer 1', + pages: [{ id: 'page-1', target: 'https://example.org/canvas/1' }] + } + ], + label: 'Project Label', + manifest: ['https://example.org/manifest.json'], + creator: 'user-1', + group: 'group-1', + tools: [ + { + label: 'Sample tool', + toolName: 'sample-tool', + location: 'pane', + custom: { enabled: true, tagName: 'sample-tool' } + } + ] + } +} + +describe('Validation utilities', () => { + it('accepts a minimally valid project payload', () => { + const result = validateProjectPayload(buildValidProjectPayload()) + + assert.equal(result.isValid, true, 'a fully-formed payload must validate as true') + assert.equal(result.errors, null, 'a valid payload must report no errors') + }) + + it('rejects payloads missing required fields', () => { + const result = validateProjectPayload({ label: 'only-label' }) + + assert.equal(result.isValid, false, 'payload missing required fields must not validate') + assert.match(result.errors ?? '', /Missing required elements/, 'error must call out missing required elements') + }) + + it('rejects invalid tool name format', () => { + const payload = buildValidProjectPayload() + payload.tools[0].toolName = 'BadToolName' + + const result = validateProjectPayload(payload) + + assert.equal(result.isValid, false, 'non-kebab-case toolName must not validate') + assert.match(result.errors ?? '', /lowercase-with-hyphens/, 'error must call out the lowercase-with-hyphens format rule') + }) + + it('scrubs default internal roles', () => { + const filteredArray = scrubDefaultRoles(['OWNER', 'CUSTOM_ROLE']) + assert.deepEqual(filteredArray, ['CUSTOM_ROLE'], 'OWNER must be stripped from a role-name array') + + const filteredObject = scrubDefaultRoles({ OWNER: ['*'], CUSTOM_ROLE: ['READ_*_*'] }) + assert.deepEqual(filteredObject, { CUSTOM_ROLE: ['READ_*_*'] }, 'OWNER key must be stripped from a roles map') + }) +}) diff --git a/userProfile/__tests__/end_to_end_unit.test.js b/userProfile/__tests__/end_to_end_unit.test.js deleted file mode 100644 index 45cd17f0..00000000 --- a/userProfile/__tests__/end_to_end_unit.test.js +++ /dev/null @@ -1,125 +0,0 @@ -import userProfileRouter from "../index.js" -import privateUserRouter from "../privateProfile.js" -import mainApp from "../../app.js" -import express from "express" -import request from "supertest" -import app from '../../app.js'; -import User from "../../classes/User/User.js" -import {jest} from "@jest/globals" - -const routeTester = new express() -const privateRoutesTester = new express() -routeTester.use("/user", userProfileRouter) -privateRoutesTester.use("/my", privateUserRouter) - -describe("Test private routes restfulness #user_class", () => { - it("POST instead of GET. That status should be 405 with a message.", async () => { - const res = await request(mainApp).post("/my/profile") - expect(res.statusCode).toBe(405) - expect(res.body).toBeTruthy() - }) - -}) - -describe("Unauthorized GETs #user_class", () => { - it("/my/project should return 401 if there is no Authorization header #user_class", async () => { - const response = await request(mainApp) - .get("/my/projects") - expect(response.status).toBe(401) - expect(response.body).toBeTruthy() - }) - - it("/my/project should return 401 if there is an invalid Authorization header #user_class", async () => { - const response = await request(mainApp) - .get("/my/projects") - .set("Authorization", `Bearer 123123123123123123123123123`) - expect(response.status).toBe(401) - expect(response.body).toBeTruthy() - }) - - it("/my/profile should return 401 if there is no Authorization header.", async () => { - const response = await request(mainApp) - .get("/my/profile") - expect(response.status).toBe(401) - expect(response.body).toBeTruthy() - }) - - it("/my/profile should return 401 for an invalid Authorization header.", async () => { - const response = await request(mainApp) - .get("/my/profile") - .set("Authorization", `Bearer 123123123123123123123123123`) - expect(response.status).toBe(401) - expect(response.body).toBeTruthy() - }) -}) - -describe('userProfile endpoint end to end unit test (spinning up the endpoint and using it). #end2end_unit', () => { - - it('POST instead of GET. That status should be 404 with a message.', async () => { - const res = await request(routeTester) - .post('/user/') - expect(res.statusCode).toBe(404) - expect(res.body).toBeTruthy() - }) - - it('PUT instead of GET. That status should be 404 with a message.', async () => { - const res = await request(routeTester) - .put('/user/') - expect(res.statusCode).toBe(404) - expect(res.body).toBeTruthy() - }) - - it('PATCH instead of GET. That status should be 404 with a message.', async () => { - const res = await request(routeTester) - .patch('/user/') - expect(res.statusCode).toBe(404) - expect(res.body).toBeTruthy() - }) - - it('Call to /user with a TPEN3 user ID that does not exist. The status should be 404 with a message.', async () => { - const res = await request(routeTester) - .get('/user/') - expect(res.statusCode).toBe(404) - expect(res.body).toBeTruthy() - }) - - it('Call to /user with a TPEN3 user ID that is invalid', async () => { - const res = await request(routeTester) - .get('/user/kjl') - expect(res.statusCode).toBe(404) - expect(res.body).toBeTruthy() - }) - - it('Call to /user with a TPEN3 user ID that does exist. The status should be 200 with a JSON user profile in the body.', async () => { - jest.spyOn(User.prototype, 'getPublicInfo').mockResolvedValueOnce({ _id: '123', displayName: 'Test User' }) - const res = await request(routeTester) - .get('/user/123') - expect(res.statusCode).toBe(200) - expect(res.body).toBeTruthy() - }) -}) - -describe('GET /:id route #testThis', () => { - it('should respond with status 400 if no user ID is provided', async () => { - const response = await request(app).get('/user/') - expect(response.status).toBe(404) - }) - - it('should respond with status 400 if the provided user ID is invalid', async () => { - const response = await request(app).get('/user/jkl') - expect(response.status).toBe(404) - expect(response.body.message).toBe('User not found') - }) - - - it.skip('should respond with status 404 and a message if no user found with provided ID', async () => { - jest.spyOn(User.prototype, 'getById').mockResolvedValueOnce({}); - - const response = await request(app).get('/user/123') - expect(response.status).toBe(404) - expect(response.body.message).toBe("No TPEN3 user with ID '123' found") - }) - - -}) - diff --git a/userProfile/__tests__/exists_unit.test.js b/userProfile/__tests__/exists_unit.test.js deleted file mode 100644 index a98dc31b..00000000 --- a/userProfile/__tests__/exists_unit.test.js +++ /dev/null @@ -1,39 +0,0 @@ -import app from '../../app.js' -import { jest } from '@jest/globals' -import expressListEndpoints from "express-list-endpoints" - -describe.skip('userProfile endpoint availability unit test (via a check on the app routes). #exists_unit', () => { - it('/user/:id is registered', () => { - let exists = false - const stack = expressListEndpoints(app.router) - for(const middleware of stack){ - if(middleware.path && middleware.path.includes("/user/:id")) { - exists = true - break - } - } - expect(exists).toBe(true) - }) - it('/my/profile is a registered endpoint', () => { - let exists = false - const stack = expressListEndpoints(app.router) - for(const middleware of stack){ - if(middleware.path && middleware.path.includes("/my/profile")) { - exists = true - break - } - } - expect(exists).toBe(true) - }) - it('/my/projects is a registered endpoint', () => { - let exists = false - const stack = expressListEndpoints(app.router) - for(const middleware of stack){ - if(middleware.path && middleware.path.includes("/my/projects")) { - exists = true - break - } - } - expect(exists).toBe(true) - }) -}) diff --git a/userProfile/__tests__/functionality_unit.test.js b/userProfile/__tests__/functionality_unit.test.js deleted file mode 100644 index 5805b09f..00000000 --- a/userProfile/__tests__/functionality_unit.test.js +++ /dev/null @@ -1,15 +0,0 @@ -import {validateID} from '../../utilities/shared.js' - -// These test the pieces of functionality in the route that make it work. -describe('Testing /user/:id helper functions) #testThis', () => { - - it('returns false for invalid ID and for no ID', () => { - expect(validateID()).toBe(false) - expect(validateID("jkl")).toBe(false) - }) - - it("returns true for valid id",()=>{ - // Use a valid 24-character hex string (MongoDB ObjectId format) - expect(validateID("507f1f77bcf86cd799439011")).toBe(true) - }) -}) diff --git a/utilities/__tests__/isDefaultRole.test.js b/utilities/__tests__/isDefaultRole.test.js deleted file mode 100644 index f9b3861a..00000000 --- a/utilities/__tests__/isDefaultRole.test.js +++ /dev/null @@ -1,32 +0,0 @@ -import scrubDefaultRoles from '../isDefaultRole.js' - -describe('scrubDefaultRoles function #customRole_unit', () => { - it('should remove default roles from an array of role strings', () => { - const roles = ['OWNER', 'customRole'] - const result = scrubDefaultRoles(roles) - expect(result).toEqual(['customRole']) - }) - - it('should return false if all roles in the array are default roles', () => { - const roles = ['OWNER', 'VIEWER'] - const result = scrubDefaultRoles(roles) - expect(result).toBe(false) - }) - - it('should throw an error if the array contains non-string elements', () => { - const roles = ['OWNER', 123] - expect(() => scrubDefaultRoles(roles)).toThrow('Expecting a RolesMap and not an Array.') - }) - - it('should remove default roles from an object of roles', () => { - const roles = { OWNER: 'OWNER', customRole: 'customRole' } - const result = scrubDefaultRoles(roles) - expect(result).toEqual({ customRole: 'customRole' }) - }) - - it('should return false if all roles in the object are default roles', () => { - const roles = { OWNER: 'OWNER', VIEWER: 'VIEWER' } - const result = scrubDefaultRoles(roles) - expect(result).toBe(false) - }) -}) diff --git a/utilities/__tests__/isSuspicious.test.js b/utilities/__tests__/isSuspicious.test.js deleted file mode 100644 index f03c4454..00000000 --- a/utilities/__tests__/isSuspicious.test.js +++ /dev/null @@ -1,118 +0,0 @@ -import { isSuspiciousValueString, isSuspiciousJSON } from "../checkIfSuspicious.js" - -describe("Suspicious string library. #suspiciousStrings", () => { - const notations = [ - "<>", - "{}", - "()", - "[]", - "< >", - "{ }", - "( )", - "[ ]", - "=[", - "= [", - "==", - "===" - ] - - const declarations = [ - "", - "#!", - "javascript:" - ] - - const rhel = [ - "sudo ", - "service httpd", - "service mongod", - "service node", - "pm2 ", - "nvm ", - "systemctl", - "rm -", - "mv -", - "cp -", - "cd -", - "ls -", - "ssh ", - "sftp " - ] - - const functiony = [ - "if(", - "if (", - "for(", - "for (", - "forEach(", - "forEach (", - "while(", - "while (", - "do{", - "do {", - "fetch(", - "fetch (", - "function(", - "function (", - "eval(", - "eval (", - "get(", - "get (", - "set(", - "set (", - "new Function(", - "=>{", - "=> {", - "==", - "===" - ] - - const mongoy = [ - "db.anything(" - ] - - const all = [...notations, ...declarations, ...rhel, ...functiony, ...mongoy] - const safeString = "This (string) is [safe] b/c no code! #sosafe. Have $1.00." - const safeJSON = { "value": "This (JSON) is [safe] b/c no code! #sosafe. Have $1.00." } - - it('returns suspicious warnings for all string values.', () => { - for (const value of all) { - if (!isSuspiciousValueString(value)) console.log(value) - expect(isSuspiciousValueString(value)).toBe(true) - } - }) - - it('returns suspicious warnings for all JSON values', () => { - for (const value of all) { - const common_keys = ["label", "name", "displayName", "email", "url", "value", "body", "target", "text", "textValue", "roles", "language", "description"] - for (const key of common_keys) { - const json1 = {} - json1[key] = value - const json2 = {} - const inner2 = {} - inner2[key] = value - json2[key] = inner2 - const json3 = { "label": { "none": value } } - expect(isSuspiciousJSON(json1)).toBe(true) - expect(isSuspiciousJSON(json2)).toBe(true) - expect(isSuspiciousJSON(json3)).toBe(true) - } - } - }) - - it('Does not return supicious warning for safe value string.', () => { - expect(isSuspiciousValueString(safeString)).toBe(false) - }) - - it('Does not return supicious warning for safe JSON.', () => { - expect(isSuspiciousJSON(safeJSON)).toBe(false) - }) - -}) diff --git a/utilities/__tests__/shared.test.js b/utilities/__tests__/shared.test.js deleted file mode 100644 index 375804c2..00000000 --- a/utilities/__tests__/shared.test.js +++ /dev/null @@ -1,108 +0,0 @@ -import { hasAnnotationChanges } from "../shared.js" - -describe("hasAnnotationChanges - Annotation change detection utility. #shared_unit", () => { - - describe("Body changes", () => { - it('returns true when body changes (string)', () => { - const existing = { body: 'old text', target: 'canvas#xywh=0,0,100,100' } - const incoming = { body: 'new text', target: 'canvas#xywh=0,0,100,100' } - expect(hasAnnotationChanges(existing, incoming)).toBe(true) - }) - - it('returns true when body changes (TextualBody object)', () => { - const existing = { body: { type: 'TextualBody', value: 'old' }, target: 'canvas' } - const incoming = { body: { type: 'TextualBody', value: 'new' }, target: 'canvas' } - expect(hasAnnotationChanges(existing, incoming)).toBe(true) - }) - - it('returns true when body type changes (string to object)', () => { - const existing = { body: 'old text', target: 'canvas' } - const incoming = { body: { type: 'TextualBody', value: 'old text' }, target: 'canvas' } - expect(hasAnnotationChanges(existing, incoming)).toBe(true) - }) - - it('returns true when body changes (array format)', () => { - const existing = { body: [{ type: 'TextualBody', value: 'old' }], target: 'canvas' } - const incoming = { body: [{ type: 'TextualBody', value: 'new' }], target: 'canvas' } - expect(hasAnnotationChanges(existing, incoming)).toBe(true) - }) - }) - - describe("Target changes", () => { - it('returns true when target changes (string)', () => { - const existing = { body: 'text', target: 'canvas#xywh=0,0,100,100' } - const incoming = { body: 'text', target: 'canvas#xywh=50,50,200,200' } - expect(hasAnnotationChanges(existing, incoming)).toBe(true) - }) - - it('returns true when target changes (SpecificResource object)', () => { - const existing = { - body: 'text', - target: { source: 'canvas', selector: { type: 'FragmentSelector', value: 'xywh=0,0,100,100' } } - } - const incoming = { - body: 'text', - target: { source: 'canvas', selector: { type: 'FragmentSelector', value: 'xywh=10,10,200,200' } } - } - expect(hasAnnotationChanges(existing, incoming)).toBe(true) - }) - }) - - describe("No changes detected", () => { - it('returns false when body and target are identical (strings)', () => { - const existing = { body: 'text', target: 'canvas#xywh=0,0,100,100' } - const incoming = { body: 'text', target: 'canvas#xywh=0,0,100,100' } - expect(hasAnnotationChanges(existing, incoming)).toBe(false) - }) - - it('returns false when body and target are identical (objects)', () => { - const existing = { - body: { type: 'TextualBody', value: 'text', format: 'text/plain' }, - target: { source: 'canvas', selector: { type: 'FragmentSelector', value: 'xywh=0,0,100,100' } } - } - const incoming = { - body: { type: 'TextualBody', value: 'text', format: 'text/plain' }, - target: { source: 'canvas', selector: { type: 'FragmentSelector', value: 'xywh=0,0,100,100' } } - } - expect(hasAnnotationChanges(existing, incoming)).toBe(false) - }) - - it('returns false when only non-content fields differ', () => { - const existing = { id: 'old-id', body: 'text', target: 'canvas', motivation: 'transcribing', creator: 'userA', '@context': 'http://iiif.io/api/presentation/3/context.json' } - const incoming = { id: 'new-id', body: 'text', target: 'canvas', motivation: 'commenting', creator: 'userB', '@context': 'http://www.w3.org/ns/anno.jsonld' } - expect(hasAnnotationChanges(existing, incoming)).toBe(false) - }) - - it('returns false when body and target objects have same content but different property order', () => { - const existing = { - body: { type: 'TextualBody', value: 'text', format: 'text/plain' }, - target: { source: 'canvas', selector: { type: 'FragmentSelector', value: 'xywh=0,0,100,100' } } - } - const incoming = { - body: { format: 'text/plain', value: 'text', type: 'TextualBody' }, - target: { selector: { value: 'xywh=0,0,100,100', type: 'FragmentSelector' }, source: 'canvas' } - } - expect(hasAnnotationChanges(existing, incoming)).toBe(false) - }) - }) - - describe("Edge cases", () => { - it('handles null body gracefully', () => { - const existing = { body: null, target: 'canvas' } - const incoming = { body: null, target: 'canvas' } - expect(hasAnnotationChanges(existing, incoming)).toBe(false) - }) - - it('returns true when body changes from null to value', () => { - const existing = { body: null, target: 'canvas' } - const incoming = { body: 'new text', target: 'canvas' } - expect(hasAnnotationChanges(existing, incoming)).toBe(true) - }) - - it('ignores extra RERUM metadata fields', () => { - const existing = { body: 'text', target: 'canvas', __rerum: { isOverwritten: 'some-date' } } - const incoming = { body: 'text', target: 'canvas' } - expect(hasAnnotationChanges(existing, incoming)).toBe(false) - }) - }) -}) diff --git a/utilities/__tests__/validatePayload.test.js b/utilities/__tests__/validatePayload.test.js deleted file mode 100644 index dcfb40b4..00000000 --- a/utilities/__tests__/validatePayload.test.js +++ /dev/null @@ -1,849 +0,0 @@ -import { validateProjectPayload } from '../validatePayload.js' -import { describe, test, expect } from '@jest/globals' - -describe('validateProjectPayload', () => { - describe('basic validation', () => { - test('should return invalid for null or undefined payload', () => { - expect(validateProjectPayload(null).isValid).toBe(false) - expect(validateProjectPayload(undefined).isValid).toBe(false) - expect(validateProjectPayload(null).errors).toBe("Project cannot be created from an empty object") - }) - - test('should return invalid for empty object', () => { - const result = validateProjectPayload({}) - expect(result.isValid).toBe(false) - expect(result.errors).toContain("Missing required elements") - }) - - test('should return invalid for missing required fields', () => { - const payload = { - label: "Test Project" - } - const result = validateProjectPayload(payload) - expect(result.isValid).toBe(false) - expect(result.errors).toContain("Missing required elements") - expect(result.errors).toContain("metadata") - expect(result.errors).toContain("layers") - expect(result.errors).toContain("manifest") - expect(result.errors).toContain("creator") - expect(result.errors).toContain("group") - }) - }) - - describe('label validation', () => { - test('should return invalid for non-string label', () => { - const payload = { - label: 123, - metadata: [], - layers: [], - manifest: ["http://example.com/manifest"], - creator: "http://example.com/user", - group: "abc123" - } - const result = validateProjectPayload(payload) - expect(result.isValid).toBe(false) - expect(result.errors).toContain("label must be a non-empty string") - }) - - test('should return invalid for empty string label', () => { - const payload = { - label: "", - metadata: [], - layers: [], - manifest: ["http://example.com/manifest"], - creator: "http://example.com/user", - group: "abc123" - } - const result = validateProjectPayload(payload) - expect(result.isValid).toBe(false) - expect(result.errors).toContain("label must be a non-empty string") - }) - - test('should accept valid string label', () => { - const payload = { - label: "Valid Project Name", - metadata: [], - layers: [], - manifest: ["http://example.com/manifest"], - creator: "http://example.com/user", - group: "abc123" - } - const result = validateProjectPayload(payload) - expect(result.isValid).toBe(true) - }) - }) - - describe('metadata validation', () => { - test('should return invalid for non-array metadata', () => { - const payload = { - label: "Test Project", - metadata: "metadata", - layers: [], - manifest: ["http://example.com/manifest"], - creator: "http://example.com/user", - group: "abc123" - } - const result = validateProjectPayload(payload) - expect(result.isValid).toBe(false) - expect(result.errors).toContain("metadata must be an array") - }) - - test('should accept empty array metadata', () => { - const payload = { - label: "Test Project", - metadata: [], - layers: [], - manifest: ["http://example.com/manifest"], - creator: "http://example.com/user", - group: "abc123" - } - const result = validateProjectPayload(payload) - expect(result.isValid).toBe(true) - }) - - test('should accept valid metadata array', () => { - const payload = { - label: "Test Project", - metadata: [ - { label: "Author", value: "John Doe" }, - { label: "Date", value: "2023" } - ], - layers: [], - manifest: ["http://example.com/manifest"], - creator: "http://example.com/user", - group: "abc123" - } - const result = validateProjectPayload(payload) - expect(result.isValid).toBe(true) - }) - - test('should return invalid for metadata array containing non-objects', () => { - const payload = { - label: "Test Project", - metadata: ["metadata"], // Array of strings instead of objects - layers: [], - manifest: ["http://example.com/manifest"], - creator: "http://example.com/user", - group: "abc123" - } - const result = validateProjectPayload(payload) - expect(result.isValid).toBe(false) - expect(result.errors).toBe("metadata item at index 0 must be an object") - }) - - test('should return invalid for metadata objects missing label property', () => { - const payload = { - label: "Test Project", - metadata: [ - { value: "John Doe" } // Missing label - ], - layers: [], - manifest: ["http://example.com/manifest"], - creator: "http://example.com/user", - group: "abc123" - } - const result = validateProjectPayload(payload) - expect(result.isValid).toBe(false) - expect(result.errors).toBe("metadata item must have label and value properties") - }) - - test('should return invalid for metadata objects missing value property', () => { - const payload = { - label: "Test Project", - metadata: [ - { label: "Author" } // Missing value - ], - layers: [], - manifest: ["http://example.com/manifest"], - creator: "http://example.com/user", - group: "abc123" - } - const result = validateProjectPayload(payload) - expect(result.isValid).toBe(false) - expect(result.errors).toBe("metadata item must have label and value properties") - }) - - test('should return invalid for metadata with empty label', () => { - const payload = { - label: "Test Project", - metadata: [ - { label: "", value: "John Doe" } - ], - layers: [], - manifest: ["http://example.com/manifest"], - creator: "http://example.com/user", - group: "abc123" - } - const result = validateProjectPayload(payload) - expect(result.isValid).toBe(false) - }) - - test('should return invalid for metadata with extra properties', () => { - const payload = { - label: "Test Project", - metadata: [ - { label: "Author", value: "John Doe", extra: "should not be here" } - ], - layers: [], - manifest: ["http://example.com/manifest"], - creator: "http://example.com/user", - group: "abc123" - } - const result = validateProjectPayload(payload) - expect(result.isValid).toBe(false) - }) - }) - - describe('layers validation', () => { - test('should return invalid for non-array layers', () => { - const payload = { - label: "Test Project", - metadata: [], - layers: "layers", - manifest: ["http://example.com/manifest"], - creator: "http://example.com/user", - group: "abc123" - } - const result = validateProjectPayload(payload) - expect(result.isValid).toBe(false) - expect(result.errors).toContain("layers must be an array") - }) - - test('should accept empty array layers', () => { - const payload = { - label: "Test Project", - metadata: [], - layers: [], - manifest: ["http://example.com/manifest"], - creator: "http://example.com/user", - group: "abc123" - } - const result = validateProjectPayload(payload) - expect(result.isValid).toBe(true) - }) - - test('should validate layer objects in array', () => { - const payload = { - label: "Test Project", - metadata: [], - layers: [ - { - id: "http://example.com/layer1", - label: "Layer 1", - pages: [] - } - ], - manifest: ["http://example.com/manifest"], - creator: "http://example.com/user", - group: "abc123" - } - const result = validateProjectPayload(payload) - expect(result.isValid).toBe(true) - }) - - test('should return invalid for malformed layer objects', () => { - const payload = { - label: "Test Project", - metadata: [], - layers: [ - { - id: "http://example.com/layer1" - // missing label and pages - } - ], - manifest: ["http://example.com/manifest"], - creator: "http://example.com/user", - group: "abc123" - } - const result = validateProjectPayload(payload) - expect(result.isValid).toBe(false) - expect(result.errors).toContain("layer must have id, label, and pages properties") - }) - - test('should return invalid for non-array layer pages', () => { - const payload = { - label: "Test Project", - metadata: [], - layers: [ - { - id: "http://example.com/layer1", - label: "Layer 1", - pages: "not-an-array" - } - ], - manifest: ["http://example.com/manifest"], - creator: "http://example.com/user", - group: "abc123" - } - const result = validateProjectPayload(payload) - expect(result.isValid).toBe(false) - expect(result.errors).toBe("layer pages must be an array") - }) - - test('should return invalid for malformed page objects', () => { - const payload = { - label: "Test Project", - metadata: [], - layers: [ - { - id: "http://example.com/layer1", - label: "Layer 1", - pages: [ - { - "no": "page" // missing id and target - } - ] - } - ], - manifest: ["http://example.com/manifest"], - creator: "http://example.com/user", - group: "abc123" - } - const result = validateProjectPayload(payload) - expect(result.isValid).toBe(false) - expect(result.errors).toBe("page must have id and target properties") - }) - - test('should return invalid for page with empty id', () => { - const payload = { - label: "Test Project", - metadata: [], - layers: [ - { - id: "http://example.com/layer1", - label: "Layer 1", - pages: [ - { - id: "", - target: "http://example.com/canvas1" - } - ] - } - ], - manifest: ["http://example.com/manifest"], - creator: "http://example.com/user", - group: "abc123" - } - const result = validateProjectPayload(payload) - expect(result.isValid).toBe(false) - expect(result.errors).toBe("page id must be a non-empty string") - }) - - test('should return invalid for page with empty target', () => { - const payload = { - label: "Test Project", - metadata: [], - layers: [ - { - id: "http://example.com/layer1", - label: "Layer 1", - pages: [ - { - id: "http://example.com/page1", - target: "" - } - ] - } - ], - manifest: ["http://example.com/manifest"], - creator: "http://example.com/user", - group: "abc123" - } - const result = validateProjectPayload(payload) - expect(result.isValid).toBe(false) - expect(result.errors).toBe("page target must be a non-empty string") - }) - - test('should return invalid for page with invalid label', () => { - const payload = { - label: "Test Project", - metadata: [], - layers: [ - { - id: "http://example.com/layer1", - label: "Layer 1", - pages: [ - { - id: "http://example.com/page1", - label: "", - target: "http://example.com/canvas1" - } - ] - } - ], - manifest: ["http://example.com/manifest"], - creator: "http://example.com/user", - group: "abc123" - } - const result = validateProjectPayload(payload) - expect(result.isValid).toBe(false) - expect(result.errors).toBe("page label must be a non-empty string when present") - }) - - test('should return invalid for page with non-array items', () => { - const payload = { - label: "Test Project", - metadata: [], - layers: [ - { - id: "http://example.com/layer1", - label: "Layer 1", - pages: [ - { - id: "http://example.com/page1", - target: "http://example.com/canvas1", - items: "not-an-array" - } - ] - } - ], - manifest: ["http://example.com/manifest"], - creator: "http://example.com/user", - group: "abc123" - } - const result = validateProjectPayload(payload) - expect(result.isValid).toBe(false) - expect(result.errors).toBe("page items must be an array when present") - }) - - test('should accept valid layer with properly structured pages', () => { - const payload = { - label: "Test Project", - metadata: [], - layers: [ - { - id: "http://example.com/layer1", - label: "Layer 1", - pages: [ - { - id: "http://example.com/page1", - label: "Page 1", - target: "http://example.com/canvas1", - items: [] - } - ] - } - ], - manifest: ["http://example.com/manifest"], - creator: "http://example.com/user", - group: "abc123" - } - const result = validateProjectPayload(payload) - expect(result.isValid).toBe(true) - }) - }) - - describe('manifest validation', () => { - test('should return invalid for non-array manifest', () => { - const payload = { - label: "Test Project", - metadata: [], - layers: [], - manifest: {"type":"Manifest"}, - creator: "http://example.com/user", - group: "abc123" - } - const result = validateProjectPayload(payload) - expect(result.isValid).toBe(false) - expect(result.errors).toContain("manifest must be an array") - }) - - test('should return invalid for empty array manifest', () => { - const payload = { - label: "Test Project", - metadata: [], - layers: [], - manifest: [], - creator: "http://example.com/user", - group: "abc123" - } - const result = validateProjectPayload(payload) - expect(result.isValid).toBe(false) - expect(result.errors).toContain("manifest array cannot be empty") - }) - - test('should accept valid manifest array with URIs', () => { - const payload = { - label: "Test Project", - metadata: [], - layers: [], - manifest: ["http://example.com/manifest1", "http://example.com/manifest2"], - creator: "http://example.com/user", - group: "abc123" - } - const result = validateProjectPayload(payload) - expect(result.isValid).toBe(true) - }) - - test('should return invalid for non-URI strings in manifest array', () => { - const payload = { - label: "Test Project", - metadata: [], - layers: [], - manifest: ["not-a-url"], - creator: "http://example.com/user", - group: "abc123" - } - const result = validateProjectPayload(payload) - expect(result.isValid).toBe(false) - expect(result.errors).toContain("manifest array must contain valid URIs") - }) - }) - - describe('creator validation', () => { - test('should return invalid for non-string creator', () => { - const payload = { - label: "Test Project", - metadata: [], - layers: [], - manifest: ["http://example.com/manifest"], - creator: 123, - group: "abc123" - } - const result = validateProjectPayload(payload) - expect(result.isValid).toBe(false) - expect(result.errors).toContain("creator must be a non-empty string") - }) - - test('should return invalid for empty string creator', () => { - const payload = { - label: "Test Project", - metadata: [], - layers: [], - manifest: ["http://example.com/manifest"], - creator: "", - group: "abc123" - } - const result = validateProjectPayload(payload) - expect(result.isValid).toBe(false) - expect(result.errors).toContain("creator must be a non-empty string") - }) - - test('should accept valid creator string', () => { - const payload = { - label: "Test Project", - metadata: [], - layers: [], - manifest: ["http://example.com/manifest"], - creator: "http://example.com/user", - group: "abc123" - } - const result = validateProjectPayload(payload) - expect(result.isValid).toBe(true) - }) - }) - - describe('group validation', () => { - test('should return invalid for non-string group', () => { - const payload = { - label: "Test Project", - metadata: [], - layers: [], - manifest: ["http://example.com/manifest"], - creator: "http://example.com/user", - group: true - } - const result = validateProjectPayload(payload) - expect(result.isValid).toBe(false) - expect(result.errors).toContain("group must be a non-empty string") - }) - - test('should return invalid for empty string group', () => { - const payload = { - label: "Test Project", - metadata: [], - layers: [], - manifest: ["http://example.com/manifest"], - creator: "http://example.com/user", - group: "" - } - const result = validateProjectPayload(payload) - expect(result.isValid).toBe(false) - expect(result.errors).toContain("group must be a non-empty string") - }) - - test('should accept valid group string', () => { - const payload = { - label: "Test Project", - metadata: [], - layers: [], - manifest: ["http://example.com/manifest"], - creator: "http://example.com/user", - group: "abc123def456" - } - const result = validateProjectPayload(payload) - expect(result.isValid).toBe(true) - }) - }) - - describe('tools validation', () => { - test('should accept valid payload without tools property', () => { - const payload = { - label: "Test Project", - metadata: [], - layers: [], - manifest: ["http://example.com/manifest"], - creator: "http://example.com/user", - group: "abc123" - } - const result = validateProjectPayload(payload) - expect(result.isValid).toBe(true) - }) - - test('should return invalid for non-array tools', () => { - const payload = { - label: "Test Project", - metadata: [], - layers: [], - manifest: ["http://example.com/manifest"], - creator: "http://example.com/user", - group: "abc123", - tools: "tools" // String instead of array - } - const result = validateProjectPayload(payload) - expect(result.isValid).toBe(false) - expect(result.errors).toBe("tools must be an array when present") - }) - - test('should return invalid for tools array containing non-objects', () => { - const payload = { - label: "Test Project", - metadata: [], - layers: [], - manifest: ["http://example.com/manifest"], - creator: "http://example.com/user", - group: "abc123", - tools: ["not-an-object"] - } - const result = validateProjectPayload(payload) - expect(result.isValid).toBe(false) - expect(result.errors).toBe("tool at index 0 must be an object") - }) - - test('should return invalid for tool missing required properties', () => { - const payload = { - label: "Test Project", - metadata: [], - layers: [], - manifest: ["http://example.com/manifest"], - creator: "http://example.com/user", - group: "abc123", - tools: [ - { - label: "Test Tool" - // Missing toolName, location, custom - } - ] - } - const result = validateProjectPayload(payload) - expect(result.isValid).toBe(false) - expect(result.errors).toBe("tool must have label, toolName, location, and custom properties") - }) - - test('should return invalid for tool with invalid toolName pattern', () => { - const payload = { - label: "Test Project", - metadata: [], - layers: [], - manifest: ["http://example.com/manifest"], - creator: "http://example.com/user", - group: "abc123", - tools: [ - { - label: "Test Tool", - toolName: "InvalidToolName", // Should be lowercase with hyphens - location: "dialog", - custom: { enabled: true } - } - ] - } - const result = validateProjectPayload(payload) - expect(result.isValid).toBe(false) - expect(result.errors).toBe("tool toolName must be in lowercase-with-hyphens format") - }) - - test('should return invalid for tool with invalid location', () => { - const payload = { - label: "Test Project", - metadata: [], - layers: [], - manifest: ["http://example.com/manifest"], - creator: "http://example.com/user", - group: "abc123", - tools: [ - { - label: "Test Tool", - toolName: "test-tool", - location: "invalid-location", - custom: { enabled: true } - } - ] - } - const result = validateProjectPayload(payload) - expect(result.isValid).toBe(false) - expect(result.errors).toBe("tool location must be one of: dialog, pane, drawer, linked, sidebar") - }) - - test('should return invalid for tool with invalid URL', () => { - const payload = { - label: "Test Project", - metadata: [], - layers: [], - manifest: ["http://example.com/manifest"], - creator: "http://example.com/user", - group: "abc123", - tools: [ - { - label: "Test Tool", - toolName: "test-tool", - url: "not-a-valid-url", - location: "dialog", - custom: { enabled: true } - } - ] - } - const result = validateProjectPayload(payload) - expect(result.isValid).toBe(false) - expect(result.errors).toBe("tool url must be a valid URL when not empty") - }) - - test('should return invalid for tool with non-object custom', () => { - const payload = { - label: "Test Project", - metadata: [], - layers: [], - manifest: ["http://example.com/manifest"], - creator: "http://example.com/user", - group: "abc123", - tools: [ - { - label: "Test Tool", - toolName: "test-tool", - location: "dialog", - custom: "not-an-object" - } - ] - } - const result = validateProjectPayload(payload) - expect(result.isValid).toBe(false) - expect(result.errors).toBe("tool custom must be an object") - }) - - test('should return invalid for tool with invalid custom.enabled', () => { - const payload = { - label: "Test Project", - metadata: [], - layers: [], - manifest: ["http://example.com/manifest"], - creator: "http://example.com/user", - group: "abc123", - tools: [ - { - label: "Test Tool", - toolName: "test-tool", - location: "dialog", - custom: { enabled: "not-a-boolean" } - } - ] - } - const result = validateProjectPayload(payload) - expect(result.isValid).toBe(false) - expect(result.errors).toBe("tool custom.enabled must be a boolean when present") - }) - - test('should accept valid tools array', () => { - const payload = { - label: "Test Project", - metadata: [], - layers: [], - manifest: ["http://example.com/manifest"], - creator: "http://example.com/user", - group: "abc123", - tools: [ - { - label: "Test Tool", - toolName: "test-tool", - url: "https://example.com/tool.js", - location: "dialog", - custom: { - enabled: true, - tagName: "test-element" - } - }, - { - label: "Another Tool", - toolName: "another-tool", - url: "", - location: "pane", - custom: { - enabled: false, - tagName: "" - } - } - ] - } - const result = validateProjectPayload(payload) - expect(result.isValid).toBe(true) - }) - }) - - describe('complete validation scenarios', () => { - test('should return invalid for payload missing required elements', () => { - const payload = { - "metadata": "metadata", - "label": "Some Label", - "layers": "layers", - "manifest": {"type":"Manifest"}, - "tools": "tools", - "group": true - } - const result = validateProjectPayload(payload) - expect(result.isValid).toBe(false) - expect(result.errors).toContain("Missing required elements: creator") - }) - - test('should return invalid for the malformed example from the issue with all fields present', () => { - const payload = { - "metadata": "metadata", - "label": "Some Label", - "layers": "layers", - "manifest": {"type":"Manifest"}, - "creator": "user123", - "group": true - } - const result = validateProjectPayload(payload) - expect(result.isValid).toBe(false) - // Since we now return the first error encountered, we only expect the first validation error - expect(result.errors).toBe("metadata must be an array") - }) - - test('should accept valid complete project payload', () => { - const payload = { - label: "Test Project", - metadata: [ - { label: "Author", value: "John Doe" }, - { label: "Date", value: "2023" } - ], - layers: [ - { - id: "http://example.com/layer1", - label: "Layer 1", - pages: [ - { - id: "http://example.com/page1", - label: "Page 1", - target: "http://example.com/canvas1" - } - ] - } - ], - manifest: ["http://example.com/manifest"], - creator: "http://example.com/user", - group: "abc123def456" - } - const result = validateProjectPayload(payload) - expect(result.isValid).toBe(true) - expect(result.errors).toBe(null) - }) - }) -}) \ No newline at end of file