diff --git a/.dockerignore b/.dockerignore index 4ba16e1e..dab0f2b4 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2025 diggsweden/rest-api-profil-lint-processor +# SPDX-FileCopyrightText: 2025 Digg - Agency for Digital Government # # SPDX-License-Identifier: CC0-1.0 diff --git a/.github/artifacts.yml b/.github/artifacts.yml index 507c12c0..6489826d 100644 --- a/.github/artifacts.yml +++ b/.github/artifacts.yml @@ -1,7 +1,6 @@ # SPDX-FileCopyrightText: 2025 Digg - Agency for Digital Government # # SPDX-License-Identifier: CC0-1.0 - # Artifacts Configuration for rest-api-profil-lint-processor # NPM CLI application with container # Package tarballs will be attached to GitHub Release as assets diff --git a/.github/workflows/release-dev-workflow.yml b/.github/workflows/release-dev-workflow.yml index b01d0b7f..654f114f 100644 --- a/.github/workflows/release-dev-workflow.yml +++ b/.github/workflows/release-dev-workflow.yml @@ -1,7 +1,6 @@ # SPDX-FileCopyrightText: 2025 Digg - Agency for Digital Government # # SPDX-License-Identifier: CC0-1.0 - # Release Workflow Dev # # Triggers dev builds for testing on development branches. diff --git a/.github/workflows/release-workflow.yml b/.github/workflows/release-workflow.yml index 2a041727..8277b146 100644 --- a/.github/workflows/release-workflow.yml +++ b/.github/workflows/release-workflow.yml @@ -1,7 +1,6 @@ # SPDX-FileCopyrightText: 2025 Digg - Agency for Digital Government # # SPDX-License-Identifier: CC0-1.0 - # Release Workflow for rest-api-profil-lint-processor # Uses the unified release orchestrator for NPM packages name: Release diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f5d4bcb1..a2ed5c8f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,26 +1,21 @@ # SPDX-FileCopyrightText: 2025 Digg - Agency for Digital Government # # SPDX-License-Identifier: CC0-1.0 ---- name: Run Tests on: push: workflow_call: - jobs: test: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - - name: Set up Node.js uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 with: node-version: '24.13.0' - - name: Install dependencies run: npm ci - - name: Run tests run: npm run test diff --git a/.gitignore b/.gitignore index 6ed3d3f8..1adf683f 100644 --- a/.gitignore +++ b/.gitignore @@ -11,4 +11,4 @@ Avstaemning_REST_API_profil_generated*.xlsx megalinter-reports tests/generated openapitools.json -dist \ No newline at end of file +dist diff --git a/.npmrc b/.npmrc index 3b72a122..4ea2a3e8 100644 --- a/.npmrc +++ b/.npmrc @@ -5,4 +5,4 @@ @diggsweden:registry=https://npm.pkg.github.com # must use for correct package-lock.json -registry=https://registry.npmjs.org/ \ No newline at end of file +registry=https://registry.npmjs.org/ diff --git a/.rumdl.toml b/.rumdl.toml new file mode 100644 index 00000000..5757d940 --- /dev/null +++ b/.rumdl.toml @@ -0,0 +1,14 @@ +# SPDX-FileCopyrightText: 2025 Digg - Agency for Digital Government +# +# SPDX-License-Identifier: CC0-1.0 + +# .rumdl.toml + +[global] +# (optional) whatever else you have here + +[per-file-ignores] +"{SECURITY.md,CODE_OF_CONDUCT.md,CONTRIBUTING.md,README.md,GUIDELINES.md}" = ["MD034", "MD041"] +"README.md" = ["MD028", "MD026", "MD031", "MD033", "MD036", "MD040", "MD051"] +"development/DEVELOPMENT.md" = ["MD051"] +"GUIDELINES.md" = ["MD007", "MD036", "MD052", "MD040", "MD005"] \ No newline at end of file diff --git a/Containerfile b/Containerfile index 2e9240e3..a07b8ef8 100644 --- a/Containerfile +++ b/Containerfile @@ -25,6 +25,8 @@ COPY --from=build /app/package*.json ./ COPY --from=build /app/document ./document COPY --from=build /app/README.md ./README.md COPY --from=build /app/GUIDELINES.md ./GUIDELINES.md +COPY --from=build /app/openapi.yaml ./openapi.yaml +COPY --from=build /app/urlValidationConfig.cjs ./urlValidationConfig.cjs RUN chown -R node:node /app USER node diff --git a/README.md b/README.md index c8974320..b0d6a6c9 100644 --- a/README.md +++ b/README.md @@ -134,6 +134,40 @@ npm start -- -f openapi.yaml > Notera: Att alla kommandon lokalt körs med `npm start --`. +### Server + +Verktyget kan även köras som en lokal HTTP-server genom att ange [API-läge](#api-application-programming-interface). I detta läge kan funktionaliteten anropas via HTTP i stället för CLI-flaggor. + +Starta servern: + +```bash +npm start -- -m api +``` + +Validera en fil via terminalen: + +```bash +curl -X POST http://localhost:3000/api/v1/validation/validate \ + -H "Content-Type: application/json" \ + -d "{\"yaml\": \"$(base64 -w 0 openapi.yaml)\"}" +``` + +Det går också att validera via en url istället för en fil men då behöver API-läget startas med en extra flagga för att låsa upp möjligheten att nyttja endpointen: + +```bash +npm start -- -m api --enableUrlValidation +``` + +Validera en fil från en url via terminalen: + +```bash + curl -sS -X POST http://localhost:3000/api/v1/validation/url \ + -H 'Content-Type: application/json' \ + -H 'Accept: application/json' \ + -d '{"url":"https://testurl.com/q/openapi"}' \ +| jq +``` + ## Versioner Main-branchen, feature-brancher, pre-release- och testversioner används med reservation för att de kan innehålla funktionalitet som inte är garanterad att den är testad på samma sätt som en stabil version. @@ -163,14 +197,14 @@ Här beskrivs vilka användningsområden verktyget har med diverse flaggor som k ### Tillgängliga flaggor -| Flagga | Beskrivning | Typ | Standard | Obligatorisk | -| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | ------------------- | ------------ | -| `-f, --file` | Sökväg till OpenAPI-specifikation (YAML/JSON). | string | – | Ja | -| `-c, --categories` | Regelkategorier separerade med kommatecken. Tillgängliga: `UfnRules, SakRules, VerRules, FnsRules, ArqRules, DokRules, AmeRules, ForRules, DotRules, FelRules, MogRules`. | string | – | Nej | -| `-l, --logError` | Sökväg till fil för felloggning från RAP-LP. Om inte angiven skrivs loggen till stdout. | string | stdout (om ej satt) | Nej | -| `-a, --append` | Append—utökar loggen i befintlig felloggningsfil (om `--logError` används). | boolean | `false` | Nej | -| `-d, --logDiagnostic` | Sökväg till fil för diagnostiseringsinformation från RAP-LP i JSON-format. | string | – | Nej | -| `--dex` | Sökväg till fil för diagnostiseringsinformation från RAP-LP i Excel-format. | string | – | Nej | +| Flagga | Beskrivning | Typ | Standard | Obligatorisk | +| --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | ------------------- | ------------ | +| `-f, --file` | Sökväg till OpenAPI-specifikation (YAML/JSON). | string | – | Ja | +| `-c, --categories` | Regelkategorier separerade med kommatecken. Tillgängliga: `UfnRules, SakRules, VerRules, FnsRules, ArqRules, DokRules, AmeRules, ForRules, DotRules, FelRules`. | string | – | Nej | +| `-l, --logError` | Sökväg till fil för felloggning från RAP-LP. Om inte angiven skrivs loggen till stdout. | string | stdout (om ej satt) | Nej | +| `-a, --append` | Append—utökar loggen i befintlig felloggningsfil (om `--logError` används). | boolean | `false` | Nej | +| `-d, --logDiagnostic` | Sökväg till fil för diagnostiseringsinformation från RAP-LP i JSON-format. | string | – | Nej | +| `--dex` | Sökväg till fil för diagnostiseringsinformation från RAP-LP i Excel-format. | string | – | Nej | > Notera: Att `raplp` i alla kommandon nedan ersätts med respektive miljös sätt att köra verktyget (npm, docker eller podman). @@ -269,6 +303,87 @@ raplp --version raplp --help ``` +### API-läge + +Verktyget kan även köras som en lokal HTTP-server, via API (Applikation Programming Interface). I detta läge kan funktionaliteten anropas via HTTP i stället för CLI-flaggor. + +För att starta servern, kör: + +```bash +npm start -- -m api +``` + +Validera mot en endpoint: + +```bash +POST http://localhost:3000/api/v1/validation/validate +``` + +Request body - application/json` + +```bash +{ + "yaml": "", + "categories": [ + "CATEGORY1", + "CATEGORY2" + ] +} +``` + +Använd detta kommando för att validera en yaml-fil via terminalen, här kan du även validera mot specifika [kategorier](#tillgängliga-kategorier-med-regler): + +```bash +curl -X POST http://localhost:3000/api/v1/validation/validate \ + -H "Content-Type: application/json" \ + -d "{\"yaml\": \"$(base64 -w 0 Path_to_the_YAML_file)\", \"categories\": [\"CATEGORY1\", \"CATEGORY2\"]}" +``` + +Exempel + +```bash +curl -X POST http://localhost:3000/api/v1/validation/validate \ + -H "Content-Type: application/json" \ + -d "{\"yaml\": \"$(base64 -w 0 openapi.yaml)\", \"categories\": [\"DokRules\", \"UfnRules\"]}" +``` + +Det går också att validera via en url istället för en fil men då behöver API-läget startas med en extra flagga för att låsa upp möjligheten att nyttja endpointen: + +```bash +npm start -- -m api --enableUrlValidation +``` + +Använd detta kommando för att validera en yaml-fil baserat på en url via terminalen, även här kan man skicka med kategorier för valideringen. Se tidigare kommando: + +```bash +curl -sS -X POST http://localhost:3000/api/v1/validation/url \ + -H "Content-Type: application/json" \ + -d '{"url":""}' | jq +``` + +Exempel: + +```bash +curl -sS -X POST http://localhost:3000/api/v1/validation/url \ + -H "Content-Type: application/json" \ + -d '{"url":"https://testurl.com/q/openapi.yaml"}' | jq +``` + +#### Ladda ned information om regelutfall som en Excel-fil via api-läge + +För att spara information om regelutfall från diagnostiseringen till en avstämningsfil i Excel, använd resultatet från tidigare validering som "result" nedan: + +```bash +curl -X POST http://localhost:5173/api/validation/generate-report \ + -H "Content-Type: application/json" \ + -o avstamningsfil.xlsx \ + -d '{ + "result": [], + "categories": [] + }' + +``` + ### Riktlinjer och förklaringar Vill du veta mer om de specifika reglerna som verktyget tillämpar, se avsnittet [GUIDELINES](GUIDELINES.md) för detaljer. diff --git a/REUSE.toml b/REUSE.toml index 804c936a..dc172264 100644 --- a/REUSE.toml +++ b/REUSE.toml @@ -92,7 +92,7 @@ SPDX-License-Identifier = "CC0-1.0" # documents etc [[annotations]] -path = ["document/Avstaemning_REST_API_profil_v_1_2_0_0.xlsx"] +path = ["document/Avstaemning_REST_API_profil_v_1_2_0_0.xlsx", "document/Avstaemning_REST_API_profil_v_1_1_0_0.xlsx", "openapi.yaml"] precedence = "aggregate" SPDX-FileCopyrightText = "2025 Digg - Agency for Digital Government" SPDX-License-Identifier = "CC0-1.0" diff --git a/apis/for-api.yaml b/apis/for-api.yaml index 7a41bc5b..6767c58d 100644 --- a/apis/for-api.yaml +++ b/apis/for-api.yaml @@ -2,13 +2,13 @@ # # SPDX-License-Identifier: EUPL-1.2 -openapi: "3.0.0" +openapi: '3.0.0' info: version: 1.2.0 title: RAP-LP description: OpenAPI Specification example to handle Rules for the category []Hypermedia] in the Swedish REST API-profil https://dev.dataportal.se/rest-api-profil license: - name: EUPL-1.2 license + name: EUPL-1.2 license servers: - url: http://petstore.swagger.io/v1 paths: @@ -31,7 +31,7 @@ paths: content: 'application/json': schema: - $ref: '#/components/schemas/Pets' + $ref: '#/components/schemas/Pets' responses: '200': description: A paged array of pets @@ -41,15 +41,15 @@ paths: schema: type: string content: - application/json: + application/json: schema: - $ref: "#/components/schemas/Pets" + $ref: '#/components/schemas/Pets' default: description: unexpected error content: application/json: schema: - $ref: "#/components/schemas/Error" + $ref: '#/components/schemas/Error' post: summary: Create a pet operationId: createPets @@ -63,7 +63,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/Error" + $ref: '#/components/schemas/Error' /pets/{petId}: get: summary: Info for a specific pet @@ -81,20 +81,19 @@ paths: content: 'application/json': schema: - $ref: '#/components/schemas/Pet' + $ref: '#/components/schemas/Pet' responses: '200': description: Expected response to a valid request content: application/json: schema: - $ref: "#/components/schemas/Pet" + $ref: '#/components/schemas/Pet' default: description: unexpected error - content: - application/json:mor e - schema: - $ref: "#/components/schemas/Error" + content: application/json:more + schema: + $ref: '#/components/schemas/Error' /pets/petsagain/{petId}: get: summary: Info for a specific pet @@ -112,20 +111,20 @@ paths: content: 'application/json': schema: - $ref: '#/components/schemas/Pet' + $ref: '#/components/schemas/Pet' responses: '200': description: Expected response to a valid request content: application/json: schema: - $ref: "#/components/schemas/Pet" + $ref: '#/components/schemas/Pet' default: description: unexpected error content: application/json: schema: - $ref: "#/components/schemas/Error" + $ref: '#/components/schemas/Error' components: schemas: Pet: @@ -145,7 +144,7 @@ components: type: array maxItems: 100 items: - $ref: "#/components/schemas/Pet" + $ref: '#/components/schemas/Pet' Error: type: object required: @@ -156,4 +155,4 @@ components: type: integer format: int32 message: - type: string \ No newline at end of file + type: string diff --git a/apis/for.yaml b/apis/for.yaml index 2fbaa1e9..b9f9f343 100644 --- a/apis/for.yaml +++ b/apis/for.yaml @@ -3,48 +3,48 @@ # SPDX-License-Identifier: EUPL-1.2 --- - swagger: "2.0" - info: - version: "1.0.0" - title: "Swagger Petstore" - description: "A sample API that uses a petstore as an example to demonstrate features in the swagger-2.0 specification" - termsOfService: "http://swagger.io/terms/" - contact: - name: "Swagger API Team" - license: - name: "MIT" - host: "petstore.swagger.io" - basePath: "/api" - schemes: - - "http" - consumes: - - "application/json" - produces: - - "application/json" - paths: - /pets: - get: - description: "Returns all pets from the system that the user has access to" - produces: - - "application/json" - responses: - "200": - description: "A list of pets." - schema: - type: "array" - items: - $ref: "#/definitions/Pet" - definitions: - Pet: - type: "object" - required: - - "id" - - "name" - properties: - id: - type: "integer" - format: "int64" - name: - type: "string" - tag: - type: "string"a \ No newline at end of file +swagger: '2.0' +info: + version: '1.0.0' + title: 'Swagger Petstore' + description: 'A sample API that uses a petstore as an example to demonstrate features in the swagger-2.0 specification' + termsOfService: 'http://swagger.io/terms/' + contact: + name: 'Swagger API Team' + license: + name: 'MIT' +host: 'petstore.swagger.io' +basePath: '/api' +schemes: + - 'http' +consumes: + - 'application/json' +produces: + - 'application/json' +paths: + /pets: + get: + description: 'Returns all pets from the system that the user has access to' + produces: + - 'application/json' + responses: + '200': + description: 'A list of pets.' + schema: + type: 'array' + items: + $ref: '#/definitions/Pet' +definitions: + Pet: + type: 'object' + required: + - 'id' + - 'name' + properties: + id: + type: 'integer' + format: 'int64' + name: + type: 'string' + tag: + type: 'string' diff --git a/apis/ufn-api.yaml b/apis/ufn-api.yaml index d8a33a28..efc62666 100644 --- a/apis/ufn-api.yaml +++ b/apis/ufn-api.yaml @@ -116,4 +116,4 @@ components: type: integer format: int32 message: - type: string \ No newline at end of file + type: string diff --git a/document/Avstaemning_REST_API_profil_v_1_1_0_0.xlsx b/document/Avstaemning_REST_API_profil_v_1_1_0_0.xlsx new file mode 100644 index 00000000..95eb2fb6 Binary files /dev/null and b/document/Avstaemning_REST_API_profil_v_1_1_0_0.xlsx differ diff --git a/openapi.yaml b/openapi.yaml new file mode 100644 index 00000000..298f6961 --- /dev/null +++ b/openapi.yaml @@ -0,0 +1,548 @@ +openapi: 3.0.3 +info: + title: REST API-profil - Lint Processor - OpenAPI 3.0 + description: |- + This is a description. + + Some useful links: + - [REST API-profil - Lint Processor (RAP-LP) repository](https://github.com/diggsweden/rest-api-profil-lint-processor/) + termsOfService: http://swagger.io/terms/ + contact: + name: "test testsson" + email: apiteam@swagger.io + url: https://github.com/diggsweden/rest-api-profil-lint-processor/ + license: + name: Apache 2.0 + url: http://www.apache.org/licenses/LICENSE-2.0.html + version: 1.0.11 +externalDocs: + description: Find out more about RAPL-LP + url: https://www.dataportal.se/rest-api-profil +servers: + - url: http://localhost:3000/api/v1 +tags: + - name: validate + description: Everything about your validations + externalDocs: + description: Find out more + url: http://swagger.io +paths: + /validation/validate: + post: + tags: + - validate + summary: Validate your yaml + description: Validate your yaml + operationId: validateContent + requestBody: + description: "Yaml can be sent as a base64 encoded file or a yaml string. \n\nContentType is specifying the type of yaml content being sent" + content: + application/json: + schema: + $ref: "#/components/schemas/YamlContentDto" + example: + yaml: "" + categories: + - "DokRules" + - "UfnRules" + required: true + responses: + "200": + description: Successful operation + content: + application/json: + schema: + $ref: "#/components/schemas/ValidationResponseDto" + example: + result: + - id: "AME.04" + omrade: "API Message" + krav: "För fältnamn i request och response body BÖR camelCase eller snake_case notation användas." + allvarlighetsgrad: "WARNING" + sokvag: + - "components" + - "schemas" + - "ValidationResponseDto" + - "properties" + - "report" + - "items" + - "properties" + - "Notering" + omfattning: + start: + character: 0 + line: 0 + end: + character: 0 + line: 0 + report: + - Notering: "Godkända regler - RAP-LP" + regler: + - id: "UFN.05" + omrade: "URL Format och namngivning" + status: "OK" + - Notering: "Ej Godkända regler - RAP-LP" + regler: + - id: "DOK.03" + omrade: "Dokumentation" + status: "EJ OK" + - Notering: "Ej tillämpade regler - RAP-LP" + regler: + - id: "SAK.09" + omrade: "Säkerhet" + status: "N/A" + "400": + description: BAD_REQUEST + content: + application/json: + schema: + $ref: "#/components/schemas/ProblemDetailsDto" + example: + type: "about:blank" + title: "Test title" + status: 400 + detail: "Test error" + instance: "/example" + "500": + description: INTERNAL_SERVER_ERROR + content: + application/json: + schema: + $ref: "#/components/schemas/ProblemDetailsDto" + example: + type: "about:blank" + title: "Test title" + status: 500 + detail: "Test error" + instance: "/example" + /validation/rules: + get: + tags: + - validation-rules + summary: Get validation rules + description: Get a collection of available validation rules. + operationId: getValidationRules + responses: + "200": + description: Successful operation + content: + application/json: + schema: + $ref: "#/components/schemas/ValidationRules" + "500": + description: INTERNAL_SERVER_ERROR + content: + application/json: + schema: + $ref: "#/components/schemas/ProblemDetailsDto" + example: + type: "about:blank" + title: "Test title" + status: 500 + detail: "Test error" + instance: "/example" + /validation/generate-report: + post: + tags: + - validate + summary: Generate status report + description: Accepts result data as the request body and returns a generated status report as a downloadable excel file. + operationId: generateReport + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - result + properties: + result: + type: array + items: + $ref: '#/components/schemas/RapLPCustomSpectralDiagnosticItem' + description: An array of result items used to generate the Excel report. + responses: + '200': + description: Successful operation + content: + application/vnd.openxmlformats-officedocument.spreadsheetml.sheet: + schema: + type: string + format: binary + example: + headers: + Content-Disposition: + schema: + type: string + description: Header to indicate the filename for the downloaded file. + '400': + description: BAD_REQUEST + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetailsDto' + example: + type: "about:blank" + title: "Test title" + status: 400 + detail: "Test error" + instance: "/example" + '500': + description: INTERNAL_SERVER_ERROR + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetailsDto' + example: + type: "about:blank" + title: "Test title" + status: 500 + detail: "Test error" + instance: "/example" + /validation/url: + post: + tags: + - validate + summary: Validate your yaml from remote url + description: Validate your yaml from remote url + operationId: validateUrl + requestBody: + description: "OpenApi yaml is fetched from remote server and validated on server. \nFeature must be enabled explicitly on server." + content: + application/json: + schema: + $ref: "#/components/schemas/UrlContentDto" + example: + url: "http://localhost:8080/q/openapi" + categories: + - "DokRules" + - "UfnRules" + required: true + responses: + "200": + description: Successful operation + content: + application/json: + schema: + $ref: "#/components/schemas/ValidationResponseDto" + example: + - id: "AME.04" + omrade: "API Message" + krav: "För fältnamn i request och response body BÖR camelCase eller snake_case notation användas." + allvarlighetsgrad: "WARNING" + sokvag: + - "components" + - "schemas" + omfattning: + start: + line: 1 + character: 5 + end: + line: 1 + character: 10 + "400": + description: BAD_REQUEST + content: + application/json: + schema: + $ref: "#/components/schemas/ProblemDetailsDto" + example: + type: "about:blank" + title: "Test title" + status: 400 + detail: "Test error" + instance: "/example" + "409": + description: CONFLICT + content: + application/json: + schema: + $ref: "#/components/schemas/ProblemDetailsDto" + example: + type: "about:blank" + title: "Test title" + status: 409 + detail: "Test error" + instance: "/example" + "500": + description: INTERNAL_SERVER_ERROR + content: + application/json: + schema: + $ref: "#/components/schemas/ProblemDetailsDto" + example: + type: "about:blank" + title: "Test title" + status: 500 + detail: "Test error" + instance: "/example" + /api-info: + get: + tags: + - api-info + summary: Get info about the api + description: Get name, version, release date, links and status. + operationId: getApiInfo + responses: + "200": + description: Successful operation + content: + application/json: + schema: + $ref: "#/components/schemas/ApiInfo" + example: + apiName: "RAP-LP" + apiVersion: "0.0.1" + apiReleased: "2024-10-10" + apiDocumentation: "http://example.digg/docs" + apiStatus: "active" + "400": + description: BAD_REQUEST + content: + application/json: + schema: + $ref: "#/components/schemas/ProblemDetailsDto" + example: + type: "about:blank" + title: "Test title" + status: 400 + detail: "Test error" + instance: "/example" + "500": + description: INTERNAL_SERVER_ERROR + content: + application/json: + schema: + $ref: "#/components/schemas/ProblemDetailsDto" + example: + type: "about:blank" + title: "Test title" + status: 500 + detail: "Test error" + instance: "/example" +components: + schemas: + ValidationRules: + type: array + description: A list of validation rules. + items: + type: object + properties: + rule: + type: string + description: The name of the validation rule. + example: "UfnRules" # Example rule name + description: + type: string + description: Description of the validation rule. + example: "URL Format och namngivning" # Example description + example: # Example array of rules + - rule: "UfnRules" + description: "URL Format och namngivning" + - rule: "SakRules" + description: "Säkerhet" + - rule: "VerRules" + description: "Versionshantering" + - rule: "FnsRules" + description: "Filtrering, paginering och sökparametrar" + - rule: "ArqRules" + description: "API Request" + - rule: "DokRules" + description: "Dokumentation" + - rule: "AmeRules" + description: "API Message" + - rule: "ForRules" + description: "Förutsättningar" + - rule: "DotRules" + description: "Datum- och tidsformat" + YamlContentDto: + type: object + properties: + yaml: + type: string + categories: + type: array + items: + type: string + example: "DokRules,UfnRules" + UrlContentDto: + type: object + properties: + url: + type: string + categories: + type: array + items: + type: string + example: "DokRules,UfnRules" + ProblemDetailsDto: + type: object + properties: + type: + type: string + title: + type: string + status: + type: integer + detail: + type: string + instance: + type: string + ApiInfo: + type: object + properties: + apiName: + type: string + apiVersion: + type: string + apiReleased: + type: string + format: date + example: "2024-10-23" + apiDocumentation: + type: string + format: uri + apiStatus: + type: string + IDiagnostic: + type: object + properties: + tags: + type: array + items: + type: string + relatedInformation: + type: array + items: + $ref: "#/components/schemas/IDiagnosticRelatedInformation" + IDiagnosticRelatedInformation: + type: object + properties: + location: + $ref: "#/components/schemas/ILocation" + message: + type: string + ILocation: + type: object + properties: + uri: + type: string + nullable: true + range: + $ref: "#/components/schemas/IRange" + IPosition: + type: object + properties: + line: + type: "integer" + character: + type: "integer" + IRange: + type: object + properties: + start: + $ref: "#/components/schemas/IPosition" + end: + $ref: "#/components/schemas/IPosition" + RapLPCustomSpectralDiagnosticItem: + allOf: + - $ref: "#/components/schemas/IDiagnostic" + - type: object + properties: + id: + type: string + nullable: true + omrade: + type: string + nullable: true + krav: + type: string + nullable: true + allvarlighetsgrad: + type: string + nullable: true + sokvag: + type: array + items: + type: string + nullable: true + omfattning: + type: object + nullable: true + xml: + name: order + RapLPCustomSpectralDiagnostic: + type: array + items: + $ref: "#/components/schemas/RapLPCustomSpectralDiagnosticItem" + DiagnosticRuleInfo: + type: object + properties: + id: + type: string + example: "UFN.05" + omrade: + type: string + example: "URL Format och namngivning" + required: + - id + - omrade + PopulatedDiagnosticRuleInfo: + allOf: + - $ref: '#/components/schemas/DiagnosticRuleInfo' + - type: object + properties: + status: + type: string + example: "OK" + required: + - status + DiagnosticReport: + type: object + properties: + Notering: + type: string + example: "Godkända regler - RAP-LP" + regler: + type: array + items: + $ref: '#/components/schemas/PopulatedDiagnosticRuleInfo' + required: + - Notering + - regler + DiagnosticRuleinfoSet: + type: object + # additionalProperties: false + properties: + notApplicableRules: + type: array + items: + $ref: '#/components/schemas/DiagnosticRuleInfo' + executedUniqueRules: + type: array + items: + $ref: '#/components/schemas/DiagnosticRuleInfo' + executedUniqueRulesWithError: + type: array + items: + $ref: '#/components/schemas/DiagnosticRuleInfo' + RapLPDiagnostic: + type: object + properties: + _ruleSets: + $ref: '#/components/schemas/DiagnosticRuleinfoSet' + ValidationResponseDto: + type: object + properties: + result: + type: array + items: + $ref: "#/components/schemas/RapLPCustomSpectralDiagnosticItem" + report: + type: array + items: + $ref: "#/components/schemas/DiagnosticReport" + required: + - result + - report diff --git a/package-lock.json b/package-lock.json index 3b7922a5..e497acda 100644 --- a/package-lock.json +++ b/package-lock.json @@ -35,8 +35,10 @@ "@types/express": "5.0.6", "@types/jest": "30.0.0", "@types/node": "24.10.9", + "@types/supertest": "^6.0.3", "jest": "30.2.0", "standard-version": "9.5.0", + "supertest": "^7.1.4", "ts-jest": "29.4.6", "ts-node": "10.9.2", "typescript": "5.9.3" @@ -46,21 +48,19 @@ } }, "node_modules/@apidevtools/json-schema-ref-parser": { - "version": "14.2.1", - "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-14.2.1.tgz", - "integrity": "sha512-HmdFw9CDYqM6B25pqGBpNeLCKvGPlIx1EbLrVL0zPvj50CJQUHyBNBw45Muk0kEIkogo1VZvOKHajdMuAzSxRg==", + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-14.0.1.tgz", + "integrity": "sha512-Oc96zvmxx1fqoSEdUmfmvvb59/KDOnUoJ7s2t7bISyAn0XEz57LCCw8k2Y4Pf3mwKaZLMciESALORLgfe2frCw==", "license": "MIT", "dependencies": { + "@types/json-schema": "^7.0.15", "js-yaml": "^4.1.0" }, "engines": { - "node": ">= 20" + "node": ">= 16" }, "funding": { "url": "https://github.com/sponsors/philsturgeon" - }, - "peerDependencies": { - "@types/json-schema": "^7.0.15" } }, "node_modules/@apidevtools/openapi-schemas": { @@ -95,39 +95,23 @@ "openapi-types": ">=7" } }, - "node_modules/@apidevtools/swagger-parser/node_modules/@apidevtools/json-schema-ref-parser": { - "version": "14.0.1", - "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-14.0.1.tgz", - "integrity": "sha512-Oc96zvmxx1fqoSEdUmfmvvb59/KDOnUoJ7s2t7bISyAn0XEz57LCCw8k2Y4Pf3mwKaZLMciESALORLgfe2frCw==", - "license": "MIT", - "dependencies": { - "@types/json-schema": "^7.0.15", - "js-yaml": "^4.1.0" - }, - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://github.com/sponsors/philsturgeon" - } - }, "node_modules/@asyncapi/specs": { - "version": "6.10.0", - "resolved": "https://registry.npmjs.org/@asyncapi/specs/-/specs-6.10.0.tgz", - "integrity": "sha512-vB5oKLsdrLUORIZ5BXortZTlVyGWWMC1Nud/0LtgxQ3Yn2738HigAD6EVqScvpPsDUI/bcLVsYEXN4dtXQHVng==", + "version": "6.11.1", + "resolved": "https://registry.npmjs.org/@asyncapi/specs/-/specs-6.11.1.tgz", + "integrity": "sha512-A3WBLqAKGoJ2+6FWFtpjBlCQ1oFCcs4GxF7zsIGvNqp/klGUHjlA3aAcZ9XMMpLGE8zPeYDz2x9FmO6DSuKraQ==", "license": "Apache-2.0", "dependencies": { "@types/json-schema": "^7.0.11" } }, "node_modules/@babel/code-frame": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", - "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" }, @@ -136,9 +120,9 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz", - "integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==", + "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": { @@ -146,22 +130,22 @@ } }, "node_modules/@babel/core": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz", - "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", + "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, "license": "MIT", "peer": true, "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.5", - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-module-transforms": "^7.28.3", - "@babel/helpers": "^7.28.4", - "@babel/parser": "^7.28.5", - "@babel/template": "^7.27.2", - "@babel/traverse": "^7.28.5", - "@babel/types": "^7.28.5", + "@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", @@ -178,14 +162,14 @@ } }, "node_modules/@babel/generator": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz", - "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==", + "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, "license": "MIT", "dependencies": { - "@babel/parser": "^7.28.5", - "@babel/types": "^7.28.5", + "@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" @@ -195,13 +179,13 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", - "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "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, "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.27.2", + "@babel/compat-data": "^7.28.6", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", @@ -222,29 +206,29 @@ } }, "node_modules/@babel/helper-module-imports": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", - "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", - "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1", - "@babel/traverse": "^7.28.3" + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -254,9 +238,9 @@ } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", - "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "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==", "dev": true, "license": "MIT", "engines": { @@ -294,27 +278,27 @@ } }, "node_modules/@babel/helpers": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", - "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", + "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/template": "^7.27.2", - "@babel/types": "^7.28.4" + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", - "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", + "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.28.5" + "@babel/types": "^7.29.0" }, "bin": { "parser": "bin/babel-parser.js" @@ -379,13 +363,13 @@ } }, "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz", - "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==", + "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==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -421,13 +405,13 @@ } }, "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", - "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", + "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, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -547,13 +531,13 @@ } }, "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz", - "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==", + "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, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -563,33 +547,33 @@ } }, "node_modules/@babel/template": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", - "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "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==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/parser": "^7.27.2", - "@babel/types": "^7.27.1" + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz", - "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.5", + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.28.5", - "@babel/template": "^7.27.2", - "@babel/types": "^7.28.5", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", "debug": "^4.3.1" }, "engines": { @@ -597,9 +581,9 @@ } }, "node_modules/@babel/types": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", - "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", + "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": { @@ -642,9 +626,9 @@ } }, "node_modules/@emnapi/core": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.7.1.tgz", - "integrity": "sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg==", + "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, @@ -654,9 +638,9 @@ } }, "node_modules/@emnapi/runtime": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.7.1.tgz", - "integrity": "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz", + "integrity": "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==", "dev": true, "license": "MIT", "optional": true, @@ -703,6 +687,91 @@ "node": ">=12" } }, + "node_modules/@isaacs/cliui/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/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/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/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "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", @@ -1056,6 +1125,16 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/@jest/reporters/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/@jest/reporters/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -1073,6 +1152,44 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/@jest/reporters/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/@jest/reporters/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/@jest/schemas": { "version": "30.0.5", "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", @@ -1399,6 +1516,29 @@ "@tybys/wasm-util": "^0.10.0" } }, + "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": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "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": { + "@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", @@ -1444,27 +1584,6 @@ "rollup": "^2.68.0" } }, - "node_modules/@rollup/plugin-commonjs/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "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/@rollup/pluginutils": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz", @@ -1489,9 +1608,9 @@ "license": "MIT" }, "node_modules/@sinclair/typebox": { - "version": "0.34.41", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.41.tgz", - "integrity": "sha512-6gS8pZzSXdyRHTIqoqSVknxolr1kzfy4/CeDnrzsVz8TTIWUbOBr6gnzOmTYJ3eXQNh4IYHIGi5aIL7sOZ2G/g==", + "version": "0.34.48", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.48.tgz", + "integrity": "sha512-kKJTNuK3AQOrgjjotVxMrCn1sUJwM76wMszfq1kdU4uYVJjvEWuFQ6HgvLt4Xz3fSmZlTOxJ/Ie13KnIcWQXFA==", "dev": true, "license": "MIT" }, @@ -1607,9 +1726,9 @@ } }, "node_modules/@stoplight/spectral-core": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/@stoplight/spectral-core/-/spectral-core-1.20.0.tgz", - "integrity": "sha512-5hBP81nCC1zn1hJXL/uxPNRKNcB+/pEIHgCjPRpl/w/qy9yC9ver04tw1W0l/PMiv0UeB5dYgozXVQ4j5a6QQQ==", + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/@stoplight/spectral-core/-/spectral-core-1.21.0.tgz", + "integrity": "sha512-oj4e/FrDLUhBRocIW+lRMKlJ/q/rDZw61HkLbTFsdMd+f/FTkli2xHNB1YC6n1mrMKjjvy7XlUuFkC7XxtgbWw==", "license": "Apache-2.0", "dependencies": { "@stoplight/better-ajv-errors": "1.0.3", @@ -1626,7 +1745,7 @@ "ajv-formats": "~2.1.1", "es-aggregate-error": "^1.0.7", "jsonpath-plus": "^10.3.0", - "lodash": "~4.17.21", + "lodash": "~4.17.23", "lodash.topath": "^4.5.2", "minimatch": "3.1.2", "nimma": "0.2.3", @@ -1996,6 +2115,13 @@ "@types/node": "*" } }, + "node_modules/@types/cookiejar": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.5.tgz", + "integrity": "sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/es-aggregate-error": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/@types/es-aggregate-error/-/es-aggregate-error-1.0.6.tgz", @@ -2023,9 +2149,9 @@ } }, "node_modules/@types/express-serve-static-core": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.0.tgz", - "integrity": "sha512-jnHMsrd0Mwa9Cf4IdOzbz543y4XJepXrbia2T4b6+spXC2We3t1y6K44D3mR8XMFSXMCf3/l7rCgddfx7UNVBA==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.1.tgz", + "integrity": "sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A==", "license": "MIT", "dependencies": { "@types/node": "*", @@ -2085,6 +2211,13 @@ "license": "MIT", "peer": true }, + "node_modules/@types/methods": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@types/methods/-/methods-1.1.4.tgz", + "integrity": "sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/minimist": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.5.tgz", @@ -2156,6 +2289,30 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/superagent": { + "version": "8.1.9", + "resolved": "https://registry.npmjs.org/@types/superagent/-/superagent-8.1.9.tgz", + "integrity": "sha512-pTVjI73witn+9ILmoJdajHGW2jkSaOzhiFYF1Rd3EQ94kymLqB9PjD9ISg7WaALC7+dCHT0FGe9T2LktLq/3GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/cookiejar": "^2.1.5", + "@types/methods": "^1.1.4", + "@types/node": "*", + "form-data": "^4.0.0" + } + }, + "node_modules/@types/supertest": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@types/supertest/-/supertest-6.0.3.tgz", + "integrity": "sha512-8WzXq62EXFhJ7QsH3Ocb/iKQ/Ty9ZVWnVzoTKc9tyyFRRF3a74Tk2+TLFgaFFw364Ere+npzHKEJ6ga2LzIL7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/methods": "^1.1.4", + "@types/superagent": "^8.1.0" + } + }, "node_modules/@types/urijs": { "version": "1.19.26", "resolved": "https://registry.npmjs.org/@types/urijs/-/urijs-1.19.26.tgz", @@ -2481,9 +2638,9 @@ } }, "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "dev": true, "license": "MIT", "bin": { @@ -2494,9 +2651,9 @@ } }, "node_modules/acorn-walk": { - "version": "8.3.4", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", - "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.5.tgz", + "integrity": "sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==", "dev": true, "license": "MIT", "dependencies": { @@ -2523,9 +2680,9 @@ } }, "node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "license": "MIT", "peer": true, "dependencies": { @@ -2596,15 +2753,13 @@ } }, "node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "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": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" + "node": ">=8" } }, "node_modules/ansi-styles": { @@ -2707,6 +2862,13 @@ "node": ">=0.10.0" } }, + "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/ast-types": { "version": "0.14.2", "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.14.2.tgz", @@ -2737,6 +2899,13 @@ "node": ">= 0.4" } }, + "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/available-typed-arrays": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", @@ -2891,13 +3060,16 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.8.28", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.28.tgz", - "integrity": "sha512-gYjt7OIqdM0PcttNYP2aVrr2G0bMALkBaoehD4BuRGjAOtipg0b6wHg1yNL+s5zSnLZZrGHOw4IrND8CD+3oIQ==", + "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.js" + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" } }, "node_modules/body-parser": { @@ -2948,9 +3120,9 @@ } }, "node_modules/browserslist": { - "version": "4.28.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.0.tgz", - "integrity": "sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", "dev": true, "funding": [ { @@ -2969,11 +3141,11 @@ "license": "MIT", "peer": true, "dependencies": { - "baseline-browser-mapping": "^2.8.25", - "caniuse-lite": "^1.0.30001754", - "electron-to-chromium": "^1.5.249", + "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.1.4" + "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" @@ -3129,9 +3301,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001755", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001755.tgz", - "integrity": "sha512-44V+Jm6ctPj7R52Na4TLi3Zri4dWUljJd+RDm+j8LtNCc/ihLCT+X1TzoOAkRETEWqjuLnh9581Tl80FvK7jVA==", + "version": "1.0.30001775", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001775.tgz", + "integrity": "sha512-s3Qv7Lht9zbVKE9XoTyRG6wVDCKdtOFIjBGg3+Yhn6JaytuNKPIjBMTMIY1AnOH3seL5mvF+x33oGAyK3hVt3A==", "dev": true, "funding": [ { @@ -3172,9 +3344,9 @@ } }, "node_modules/ci-info": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.1.tgz", - "integrity": "sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA==", + "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": [ { @@ -3188,9 +3360,9 @@ } }, "node_modules/cjs-module-lexer": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.1.1.tgz", - "integrity": "sha512-+CmxIZ/L2vNcEfvNtLdU0ZQ6mbq3FZnwAP2PPTiKP+1QOoKwlKlPgb8UKV0Dds7QVaMnHm+FwSft2VB0s/SLjQ==", + "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" }, @@ -3208,56 +3380,31 @@ "node": ">=20" } }, - "node_modules/cliui/node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "node_modules/cliui/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", "license": "MIT", "engines": { "node": ">=12" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, - "node_modules/cliui/node_modules/emoji-regex": { - "version": "10.6.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", - "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", - "license": "MIT" - }, - "node_modules/cliui/node_modules/string-width": { + "node_modules/cliui/node_modules/strip-ansi": { "version": "7.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", - "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", "license": "MIT", "dependencies": { - "emoji-regex": "^10.3.0", - "get-east-asian-width": "^1.0.0", - "strip-ansi": "^7.1.0" + "ansi-regex": "^6.2.2" }, "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cliui/node_modules/wrap-ansi": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", - "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.2.1", - "string-width": "^7.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" + "node": ">=12" }, "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, "node_modules/co": { @@ -3298,6 +3445,19 @@ "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/commondir": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", @@ -3315,6 +3475,16 @@ "dot-prop": "^5.1.0" } }, + "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", @@ -3337,15 +3507,16 @@ } }, "node_modules/content-disposition": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", - "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", + "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==", "license": "MIT", - "dependencies": { - "safe-buffer": "5.2.1" - }, "engines": { - "node": ">= 0.6" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/content-type": { @@ -3651,6 +3822,13 @@ "node": ">=6.6.0" } }, + "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/core-util-is": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", @@ -3806,9 +3984,9 @@ } }, "node_modules/dedent": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.0.tgz", - "integrity": "sha512-HGFtf8yhuhGhqO07SV79tRp+br4MnbdjeVxotpn1QBl30pcLLCQjX5b2295ll0fv8RKDKsmWYrl05usHM9CewQ==", + "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": { @@ -3864,6 +4042,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "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/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -3902,10 +4090,21 @@ "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": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz", + "integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==", "dev": true, "license": "BSD-3-Clause", "engines": { @@ -4033,9 +4232,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.254", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.254.tgz", - "integrity": "sha512-DcUsWpVhv9svsKRxnSCZ86SjD+sp32SGidNB37KpqXJncp1mfUgKbHvBomE89WJDbfVKw1mdv5+ikrvd43r+Bg==", + "version": "1.5.302", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.302.tgz", + "integrity": "sha512-sM6HAN2LyK82IyPBpznDRqlTQAtuSaO+ShzFiWTvoMJLHyZ+Y39r8VMfHzwbU8MVBzQ4Wdn85+wlZl2TLGIlwg==", "dev": true, "license": "ISC" }, @@ -4053,10 +4252,9 @@ } }, "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, + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", "license": "MIT" }, "node_modules/encodeurl": { @@ -4079,9 +4277,9 @@ } }, "node_modules/es-abstract": { - "version": "1.24.0", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz", - "integrity": "sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==", + "version": "1.24.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.1.tgz", + "integrity": "sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==", "license": "MIT", "dependencies": { "array-buffer-byte-length": "^1.0.2", @@ -4317,13 +4515,6 @@ "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", @@ -4421,6 +4612,24 @@ "express": "*" } }, + "node_modules/express-openapi-validator/node_modules/@apidevtools/json-schema-ref-parser": { + "version": "14.2.1", + "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-14.2.1.tgz", + "integrity": "sha512-HmdFw9CDYqM6B25pqGBpNeLCKvGPlIx1EbLrVL0zPvj50CJQUHyBNBw45Muk0kEIkogo1VZvOKHajdMuAzSxRg==", + "license": "MIT", + "dependencies": { + "js-yaml": "^4.1.0" + }, + "engines": { + "node": ">= 20" + }, + "funding": { + "url": "https://github.com/sponsors/philsturgeon" + }, + "peerDependencies": { + "@types/json-schema": "^7.0.15" + } + }, "node_modules/express-openapi-validator/node_modules/ajv-formats": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", @@ -4457,6 +4666,13 @@ "integrity": "sha512-Ue0LwpDYErFbmNnZSF0UH6eImUwDmogUO1jyE+JbN2gsQz/jICm1Ve7t9QT0rNSsfJt+Hs4/S3GnsDVjL4HVrw==", "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/fast-uri": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", @@ -4473,10 +4689,22 @@ ], "license": "BSD-3-Clause" }, + "node_modules/fast-xml-builder": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.0.0.tgz", + "integrity": "sha512-fpZuDogrAgnyt9oDDz+5DBz0zgPdPZz6D4IR7iESxRXElrlGTRkHJ9eEt+SACRJwT0FNFrt71DFQIUFBJfX/uQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, "node_modules/fast-xml-parser": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.3.3.tgz", - "integrity": "sha512-2O3dkPAAC6JavuMm8+4+pgTk+5hoAs+CjZ+sWcQLkX9+/tHRuTkQh/Oaifr8qDmZ8iEHb771Ea6G8CdwkrgvYA==", + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.4.1.tgz", + "integrity": "sha512-BQ30U1mKkvXQXXkAGcuyUA/GA26oEB7NzOtsxCDtyu62sjGw5QraKFhx2Em3WQNjPw9PG6MQ9yuIIgkSDfGu5A==", "funding": [ { "type": "github", @@ -4485,7 +4713,8 @@ ], "license": "MIT", "dependencies": { - "strnum": "^2.1.0" + "fast-xml-builder": "^1.0.0", + "strnum": "^2.1.2" }, "bin": { "fxparser": "src/cli/cli.js" @@ -4541,9 +4770,9 @@ } }, "node_modules/finalhandler": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", - "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", + "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", @@ -4554,7 +4783,11 @@ "statuses": "^2.0.1" }, "engines": { - "node": ">= 0.8" + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/find-up": { @@ -4603,6 +4836,77 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/foreground-child/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "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", @@ -4708,9 +5012,9 @@ } }, "node_modules/get-east-asian-width": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz", - "integrity": "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.5.0.tgz", + "integrity": "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==", "license": "MIT", "engines": { "node": ">=18" @@ -4772,16 +5076,6 @@ "node": ">=6.9.0" } }, - "node_modules/get-pkg-repo/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/get-pkg-repo/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -4872,19 +5166,6 @@ "node": ">=8" } }, - "node_modules/get-pkg-repo/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/get-pkg-repo/node_modules/through2": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", @@ -4980,6 +5261,7 @@ "version": "2.0.11", "resolved": "https://registry.npmjs.org/git-raw-commits/-/git-raw-commits-2.0.11.tgz", "integrity": "sha512-VnctFhw+xfj8Va1xtfEqCUD2XDrbAPSJx+hSrE5K7fGdjZruW7XV+QOrN7LF/RJyvspRiD2I0asWsxFp0ya26A==", + "deprecated": "This package is no longer maintained. For the JavaScript API, please use @conventional-changelog/git-client instead.", "dev": true, "license": "MIT", "dependencies": { @@ -5014,6 +5296,7 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/git-semver-tags/-/git-semver-tags-4.1.1.tgz", "integrity": "sha512-OWyMt5zBe7xFs8vglMmhM9lRQzCWL3WjHtxNNfJTMngGym7pC1kh8sP6jevfydJ6LP3ZvGxfb6ABYgPUM0mtsA==", + "deprecated": "This package is no longer maintained. For the JavaScript API, please use @conventional-changelog/git-client instead.", "dev": true, "license": "MIT", "dependencies": { @@ -5038,47 +5321,21 @@ } }, "node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", - "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/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/glob/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, + "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", "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "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": ">=16 || 14 >=14.17" + "node": "*" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -5280,28 +5537,23 @@ "license": "MIT" }, "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "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": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" }, "engines": { "node": ">= 0.8" - } - }, - "node_modules/http-errors/node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/human-signals": { @@ -5315,9 +5567,9 @@ } }, "node_modules/iconv-lite": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", - "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" @@ -5903,9 +6155,9 @@ } }, "node_modules/istanbul-lib-instrument/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "dev": true, "license": "ISC", "bin": { @@ -6116,16 +6368,6 @@ } } }, - "node_modules/jest-cli/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/jest-cli/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -6196,23 +6438,10 @@ "node": ">=8" } }, - "node_modules/jest-cli/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/jest-cli/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==", + "node_modules/jest-cli/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": { @@ -6324,6 +6553,16 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/jest-config/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/jest-config/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -6341,6 +6580,44 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/jest-config/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/jest-config/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/jest-diff": { "version": "30.2.0", "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.2.0.tgz", @@ -6841,6 +7118,16 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/jest-runtime/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/jest-runtime/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -6858,6 +7145,44 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/jest-runtime/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/jest-runtime/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/jest-snapshot": { "version": "30.2.0", "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.2.0.tgz", @@ -6925,9 +7250,9 @@ } }, "node_modules/jest-snapshot/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "dev": true, "license": "ISC", "bin": { @@ -7383,9 +7708,9 @@ } }, "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", "license": "MIT" }, "node_modules/lodash.clonedeep": { @@ -7457,9 +7782,9 @@ } }, "node_modules/make-dir/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "dev": true, "license": "ISC", "bin": { @@ -7659,6 +7984,16 @@ "dev": true, "license": "MIT" }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/micromatch": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", @@ -7673,6 +8008,19 @@ "node": ">=8.6" } }, + "node_modules/mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "dev": true, + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/mime-db": { "version": "1.54.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", @@ -7683,15 +8031,19 @@ } }, "node_modules/mime-types": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", - "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", "license": "MIT", "dependencies": { "mime-db": "^1.54.0" }, "engines": { - "node": ">= 0.6" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/mimic-fn": { @@ -7730,6 +8082,7 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -7751,27 +8104,15 @@ } }, "node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "engines": { "node": ">=16 || 14 >=14.17" } }, - "node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "license": "MIT", - "dependencies": { - "minimist": "^1.2.6" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, "node_modules/modify-values": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/modify-values/-/modify-values-1.0.1.tgz", @@ -7789,21 +8130,22 @@ "license": "MIT" }, "node_modules/multer": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/multer/-/multer-2.0.2.tgz", - "integrity": "sha512-u7f2xaZ/UG8oLXHvtF/oWTRvT44p9ecwBBqTwgJVq0+4BW1g8OW01TyMEGWBHbyMOYVHXslaut7qEQ1meATXgw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/multer/-/multer-2.1.0.tgz", + "integrity": "sha512-TBm6j41rxNohqawsxlsWsNNh/VdV4QFXcBvRcPhXaA05EZ79z0qJ2bQFpync6JBoHTeNY5Q1JpG7AlTjdlfAEA==", "license": "MIT", "dependencies": { "append-field": "^1.0.0", "busboy": "^1.6.0", "concat-stream": "^2.0.0", - "mkdirp": "^0.5.6", - "object-assign": "^4.1.1", - "type-is": "^1.6.18", - "xtend": "^4.0.2" + "type-is": "^1.6.18" }, "engines": { "node": ">= 10.16.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/multer/node_modules/media-typer": { @@ -7958,9 +8300,9 @@ } }, "node_modules/normalize-package-data/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "dev": true, "license": "ISC", "bin": { @@ -7993,15 +8335,6 @@ "node": ">=8" } }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/object-inspect": { "version": "1.13.4", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", @@ -8449,9 +8782,9 @@ } }, "node_modules/qs": { - "version": "6.14.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", - "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz", + "integrity": "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==", "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.1.0" @@ -8483,15 +8816,15 @@ } }, "node_modules/raw-body": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.1.tgz", - "integrity": "sha512-9G8cA+tuMS75+6G/TzW8OtLzmBDMo8p1JRxN5AZ+LAp8uxGA8V8GZm4GQ4/N5QNQEnLmg6SS7wyuSmbKepiKqA==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", "license": "MIT", "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.7.0", - "unpipe": "1.0.0" + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" }, "engines": { "node": ">= 0.10" @@ -8903,31 +9236,35 @@ } }, "node_modules/send": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", - "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", "license": "MIT", "dependencies": { - "debug": "^4.3.5", + "debug": "^4.4.3", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "fresh": "^2.0.0", - "http-errors": "^2.0.0", - "mime-types": "^3.0.1", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", "ms": "^2.1.3", "on-finished": "^2.4.1", "range-parser": "^1.2.1", - "statuses": "^2.0.1" + "statuses": "^2.0.2" }, "engines": { "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/serve-static": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", - "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", "license": "MIT", "dependencies": { "encodeurl": "^2.0.0", @@ -8937,6 +9274,10 @@ }, "engines": { "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/set-function-length": { @@ -9087,17 +9428,11 @@ } }, "node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "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", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } + "license": "ISC" }, "node_modules/simple-eval": { "version": "1.0.1", @@ -9179,9 +9514,9 @@ } }, "node_modules/spdx-license-ids": { - "version": "3.0.22", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.22.tgz", - "integrity": "sha512-4PRT4nh1EImPbt2jASOKHX7PB7I+e4IWNLvkKFDxNhJlfjbYlleYQh285Z/3mPTHSAK/AvdMmw5BNNuYH8ShgQ==", + "version": "3.0.23", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.23.tgz", + "integrity": "sha512-CWLcCCH7VLu13TgOH+r8p1O/Znwhqv/dbb6lqWy67G+pT1kHmeD/+V36AVb/vq8QMIQwVShJ6Ssl5FPh0fuSdw==", "dev": true, "license": "CC0-1.0" }, @@ -9257,16 +9592,6 @@ "node": ">=10" } }, - "node_modules/standard-version/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/standard-version/node_modules/ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", @@ -9401,9 +9726,9 @@ } }, "node_modules/standard-version/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "dev": true, "license": "ISC", "bin": { @@ -9428,19 +9753,6 @@ "node": ">=8" } }, - "node_modules/standard-version/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/standard-version/node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -9580,42 +9892,18 @@ "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", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", "license": "MIT", "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" }, "engines": { - "node": ">=12" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -9637,16 +9925,6 @@ "node": ">=8" } }, - "node_modules/string-width-cjs/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-width-cjs/node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -9654,17 +9932,31 @@ "dev": true, "license": "MIT" }, - "node_modules/string-width-cjs/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, + "node_modules/string-width/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", "license": "MIT", "dependencies": { - "ansi-regex": "^5.0.1" + "ansi-regex": "^6.2.2" }, "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, "node_modules/string.prototype.trim": { @@ -9732,18 +10024,16 @@ "license": "ISC" }, "node_modules/strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "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": "^6.0.1" + "ansi-regex": "^5.0.1" }, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" + "node": ">=8" } }, "node_modules/strip-ansi-cjs": { @@ -9760,16 +10050,6 @@ "node": ">=8" } }, - "node_modules/strip-ansi-cjs/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/strip-bom": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", @@ -9817,9 +10097,9 @@ } }, "node_modules/strnum": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.1.tgz", - "integrity": "sha512-7ZvoFTiCnGxBtDqJ//Cu6fWtZtc7Y3x+QOirG15wztbdngGSkht27o2pyGWrVy0b4WAy3jbKmnoK6g5VlVNUUw==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.2.0.tgz", + "integrity": "sha512-Y7Bj8XyJxnPAORMZj/xltsfo55uOiyHcU2tnAVzHUnSJR/KsEX+9RoDeXEnsXtl/CX4fAcrt64gZ13aGaWPeBg==", "funding": [ { "type": "github", @@ -9828,6 +10108,42 @@ ], "license": "MIT" }, + "node_modules/superagent": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-10.3.0.tgz", + "integrity": "sha512-B+4Ik7ROgVKrQsXTV0Jwp2u+PXYLSlqtDAhYnkkD+zn3yg8s/zjA2MeGayPoY/KICrbitwneDHrjSotxKL+0XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "component-emitter": "^1.3.1", + "cookiejar": "^2.1.4", + "debug": "^4.3.7", + "fast-safe-stringify": "^2.1.1", + "form-data": "^4.0.5", + "formidable": "^3.5.4", + "methods": "^1.1.2", + "mime": "2.6.0", + "qs": "^6.14.1" + }, + "engines": { + "node": ">=14.18.0" + } + }, + "node_modules/supertest": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/supertest/-/supertest-7.2.2.tgz", + "integrity": "sha512-oK8WG9diS3DlhdUkcFn4tkNIiIbBx9lI2ClF8K+b2/m8Eyv47LSawxUzZQSNKUrVb2KsqeTDCcjAAVPYaSLVTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cookie-signature": "^1.2.2", + "methods": "^1.1.2", + "superagent": "^10.3.0" + }, + "engines": { + "node": ">=14.18.0" + } + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -9854,9 +10170,9 @@ } }, "node_modules/synckit": { - "version": "0.11.11", - "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.11.tgz", - "integrity": "sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==", + "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": { @@ -9884,28 +10200,6 @@ "node": ">=8" } }, - "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": "Glob versions prior to v9 are no longer supported", - "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/text-extensions": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/text-extensions/-/text-extensions-1.9.0.tgz", @@ -10032,9 +10326,9 @@ } }, "node_modules/ts-jest/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "dev": true, "license": "ISC", "bin": { @@ -10333,9 +10627,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz", - "integrity": "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==", + "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": [ { @@ -10557,9 +10851,9 @@ } }, "node_modules/which-typed-array": { - "version": "1.1.19", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", - "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", + "version": "1.1.20", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.20.tgz", + "integrity": "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==", "license": "MIT", "dependencies": { "available-typed-arrays": "^1.0.7", @@ -10585,18 +10879,17 @@ "license": "MIT" }, "node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "dev": true, + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", "license": "MIT", "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" }, "engines": { - "node": ">=12" + "node": ">=18" }, "funding": { "url": "https://github.com/chalk/wrap-ansi?sponsor=1" @@ -10621,16 +10914,6 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/wrap-ansi-cjs/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/wrap-ansi-cjs/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -10669,24 +10952,22 @@ "node": ">=8" } }, - "node_modules/wrap-ansi-cjs/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, + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, "node_modules/wrap-ansi/node_modules/ansi-styles": { "version": "6.2.3", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -10695,6 +10976,21 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -10715,10 +11011,24 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/write-file-atomic/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.4" @@ -10767,29 +11077,6 @@ "node": ">=10" } }, - "node_modules/yargs/node_modules/emoji-regex": { - "version": "10.6.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", - "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", - "license": "MIT" - }, - "node_modules/yargs/node_modules/string-width": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", - "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", - "license": "MIT", - "dependencies": { - "emoji-regex": "^10.3.0", - "get-east-asian-width": "^1.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/yargs/node_modules/yargs-parser": { "version": "22.0.0", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz", @@ -10823,4 +11110,4 @@ } } } -} \ No newline at end of file +} diff --git a/package.json b/package.json index 18eb0e86..7ed55d41 100644 --- a/package.json +++ b/package.json @@ -37,11 +37,13 @@ "@types/express": "5.0.6", "@types/jest": "30.0.0", "@types/node": "24.10.9", + "@types/supertest": "^6.0.3", "jest": "30.2.0", "standard-version": "9.5.0", "ts-jest": "29.4.6", "ts-node": "10.9.2", - "typescript": "5.9.3" + "typescript": "5.9.3", + "supertest": "^7.1.4" }, "dependencies": { "@apidevtools/swagger-parser": "^12.1.0", diff --git a/publiccode.yml b/publiccode.yml index 11b7a965..77777a23 100644 --- a/publiccode.yml +++ b/publiccode.yml @@ -1,11 +1,9 @@ # SPDX-FileCopyrightText: 2025 Digg - Agency for Digital Government # # SPDX-License-Identifier: CC0-1.0 - # This repository adheres to the publiccode.yml standard by including this # metadata file that makes public software easily discoverable. # More info at https://github.com/italia/publiccode.yml - publiccodeYmlVersion: '0.4.0' categories: - application-development diff --git a/src/api-mode.ts b/src/api-mode.ts new file mode 100644 index 00000000..5bad8ede --- /dev/null +++ b/src/api-mode.ts @@ -0,0 +1,54 @@ +// SPDX-FileCopyrightText: 2025 Digg - Agency for Digital Government +// +// SPDX-License-Identifier: EUPL-1.2 + +import express from 'express'; +import { registerValidationRoutes } from './routes/validate.js'; +import { registerUrlValidationFallbackRoutes, registerUrlValidationRoutes } from './routes/urlValidation.js'; +import { errorHandler } from './util/RapLPBaseApiErrorHandling.js'; +import OpenApiValidator from 'express-openapi-validator'; +import path from 'path'; + +export type ApiArgs = { + enableUrlValidation?: boolean; + urlValidationConfigFile?: string; +}; + +// Funktion för att starta API-servern +export async function startServer(args: T) { + const app = express(); + const port = process.env.PORT || 3000; + + app.use('/api/v1/openapi.yaml', express.static(path.join(process.cwd(), 'openapi.yaml'))); + + // For the case of content upload + app.use(express.json()); + // Path to your OpenAPI spec + const apiSpec = path.join(process.cwd(), 'openapi.yaml'); + + // Initialize OpenAPI Validator middleware + app.use( + OpenApiValidator.middleware({ + apiSpec, // Path to OpenAPI spec + validateRequests: true, // Automatically validate request bodies + validateResponses: true, // Automatically validate responses + ignorePaths: /\/api\/v1\/validation\/validatespec/, + }), + ); + + // API Endpoint, t.ex. för att validera en YAML-fil + registerValidationRoutes(app); + + if (args.enableUrlValidation) { + registerUrlValidationRoutes(app, args.urlValidationConfigFile); + } else { + registerUrlValidationFallbackRoutes(app); + } + + // Middleware för att mappa interna error till HTTP koder. + app.use(errorHandler); + + return app.listen(port, () => { + console.log(`Servern körs på http://localhost:${port}`); + }); +} diff --git a/src/app.ts b/src/app.ts index 14de8b8c..2046cce3 100644 --- a/src/app.ts +++ b/src/app.ts @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2025 diggsweden/rest-api-profil-lint-processor +// SPDX-FileCopyrightText: 2025 Digg - Agency for Digital Government // // SPDX-License-Identifier: EUPL-1.2 @@ -12,47 +12,32 @@ * https://dev.dataportal.se/rest-api-profil * **************************************************************/ -import yargs from 'yargs'; import * as path from 'node:path'; -import * as fs from 'node:fs'; -import Parsers from '@stoplight/spectral-parsers'; -import { importAndCreateRuleInstances, getRuleModules } from './util/ruleUtil.js'; // Import the helper function -import util from 'util'; -import { RapLPCustomSpectral } from './util/RapLPCustomSpectral.js'; -import { DiagnosticReport, RapLPDiagnostic } from './util/RapLPDiagnostic.js'; -import { AggregateError } from './util/RapLPCustomErrorInfo.js'; -import chalk from 'chalk'; -import { ExcelReportProcessor } from './util/excelReportProcessor.js'; -import { parseApiSpecInput,detectSpecFormatPreference, ParseResult} from './util/validateUtil.js'; -import { SpecParseError } from './util/RapLPSpecParseError.js'; -import type { IParser } from '@stoplight/spectral-parsers'; -import { Document as SpectralDocument } from '@stoplight/spectral-core'; -import { Issue } from './util/RapLPIssueHelpers.js'; - -declare var AggregateError: { - prototype: AggregateError; - new (errors: any[], message?: string): AggregateError; -}; -const writeFileAsync = util.promisify(fs.writeFile); -const appendFileAsync = util.promisify(fs.appendFile); - - - -async function main(): Promise { - try { - // Parse command-line arguments using yargs - const argv = await yargs(process.argv.slice(2)) - .version('1.0.0') - .option('file', { - alias: 'f', - describe: 'Sökväg till OpenAPI specifikation(yaml,json)', - demandOption: true, - type: 'string', - coerce: (file: string) => path.resolve(file), - }) - .option('categories', { +import yargs from 'yargs'; +import { hideBin } from 'yargs/helpers'; +import { startServer } from './api-mode.js'; +import { execCLI } from './cli-mode.js'; +import { getRuleModules } from './util/ruleUtil.js'; + +async function main() { + const argv = await yargs(hideBin(process.argv)) + .version('1.2.0') + .option('mode', { + alias: 'm', + describe: 'Körläget för applikationen api', + choices: ['api'], + }) + .option('file', { + alias: 'f', + describe: '[cli mode] Path to the YAML file', + type: 'string', + coerce: (file: string) => path.resolve(file), // convert to absolute path + }) + .option('categories', { alias: 'c', - describe: `Regelkategorier separerade med kommatecken.Tillgängliga kategorier: ${getRuleModules().join(',')}`, + describe: `[cli mode] Regelkategorier separerade med kommatecken.\nAvailable categories:\r ${getRuleModules().join( + ',', + )}`, type: 'string', }) .option('logError', { @@ -78,304 +63,54 @@ async function main(): Promise { describe: 'Sökväg till fil för diagnostiseringsinformation från RAP-LP. Om en specificerad, så kommer diagnostiseringsinformationen att skrivas ut till angiven fil i Excel format.', type: 'string', - }).option('strict', { + }) + .option('enableUrlValidation', { + type: 'boolean', + describe: '[api-mode] Möjliggör validering av filer givet url.', + }) + .option('urlValidationConfigFile', { + type: 'string', + describe: + '[api-mode] Sökväg till fil för configuration av urlValidation funktionalliteten faller tillbaka på ./urlValidationConfig.cjs', + }) + .option('strict', { describe: 'Aktivera strict mode för validering av semantik och struktur.', type: 'boolean', default: false, - }).argv; - - // Extract arguments from yargs - const apiSpecFileName = (argv.file as string) || ''; - const ruleCategories = argv.categories ? (argv.categories as string).split(',') : undefined; - const logErrorFilePath = argv.logError as string | undefined; - const logDiagnosticFilePath = argv.logDiagnostic as string | undefined; - const strict = argv.strict as boolean ?? false; - - // Schemevalidation and Spectral Document creation ---------- - let apiSpecDocument: SpectralDocument; - let parseResult: ParseResult; - - try { - const prefer = detectSpecFormatPreference(apiSpecFileName,undefined,'auto'); - parseResult = await parseApiSpecInput( - {filePath: apiSpecFileName},{ - strict: strict, - preferJsonError: prefer - } - ); - - // Issue handling ---------- - if (parseResult.strictIssues && parseResult.strictIssues.length > 0) { - console.error('Strict validation reported issues:'); - parseResult.strictIssues.forEach((iss: Issue) => - console.error(chalk.yellow(`- ${iss.type} at ${iss.path} : ${iss.message} ${iss.line ? `(line ${iss.line})` : ''}`)), - ); - process.exitCode = 2; - return; - } - } catch (err: any) { - // Parse handling - if (err instanceof SpecParseError) { - const formattedDate = new Date().toISOString(); - const logData = { - timeStamp: formattedDate, - message: 'Fel vid parsing av API-specifikationen.', - error: err.toJSON ? err.toJSON() : { message: String(err) }, - }; - - if (logErrorFilePath) { - try { - let existingLogs: any[] = []; - if (argv.append && fs.existsSync(logErrorFilePath)) { - const fileContent = await fs.promises.readFile(logErrorFilePath, 'utf8'); - try { - existingLogs = JSON.parse(fileContent); - if (!Array.isArray(existingLogs)) existingLogs = [existingLogs]; - } catch { - existingLogs = []; - } - } - existingLogs.push(logData); - const updatedContent = JSON.stringify(existingLogs, null, 2); - await writeFileAsync(logErrorFilePath, Buffer.from(updatedContent, 'utf8')); - console.log(chalk.green(`Parserfel loggat till ${logErrorFilePath}`)); - } catch (fileErr: any) { - console.error(chalk.red('Misslyckades att skriva parserfel till loggfilen:'), fileErr.message); - } - } else { - // No log file specified - write to stdout - console.error(chalk.red('<<< Parserfel i API-specifikationen >>>')); - console.error(chalk.red(`Fel: ${err.message}`)); - if (err.line || err.column) { - console.error(chalk.yellow(`Rad: ${err.line ?? '-'}, Kolumn: ${err.column ?? '-'}`)); - } - if (err.snippet && !err.message.includes(err.snippet)) { - console.error(chalk.gray('--- snippet ---')); - console.error(chalk.gray(err.snippet)); - console.error(chalk.gray('---------------')); - } + }) + .check(function (argv) { + if (argv.mode !== 'api') { + if (!argv.file) { + throw new Error('Saknar obligatoriskt argument för cli-läge: --file '); } - - process.exitCode = 1; - return; // terminate main gracefully + return true; } - // Övrigt oväntat fel - logErrorToFile(err); - console.error(chalk.red('Ett fel uppstod vid inläsning/parsing av spec-filen. Se felloggen för mer information.')); - process.exitCode = 1; - return; - } - - try { - // Import and create rule instances in RAP-LP - const enabledRulesAndCategorys = await importAndCreateRuleInstances(ruleCategories); - // Load API specification into a Document object - try { - /** - * CustomSpectral - */ - const customSpectral = new RapLPCustomSpectral(); - customSpectral.setCategorys(enabledRulesAndCategorys.instanceCategoryMap); - customSpectral.setRuleset(enabledRulesAndCategorys.rules); - //Use previous parseResult - const parser: IParser = (parseResult.format === 'json' ? Parsers.Json : Parsers.Yaml) as unknown as IParser; - apiSpecDocument = new SpectralDocument(parseResult.raw, parser, apiSpecFileName); + if (argv.mode === 'api') { + const apiForbiddenArgs = new Set(['f', 'c', 'dex']); + const hasForbiddenArgs = Object.keys(argv).filter( + (k) => apiForbiddenArgs.has(k) && Object.prototype.hasOwnProperty.call(argv, k), + ); - // Run ruleengine - const result = await customSpectral.run(apiSpecDocument); - - const customDiagnostic = new RapLPDiagnostic(); - customDiagnostic.processRuleExecutionInformation(result, enabledRulesAndCategorys.instanceCategoryMap); - const diagnosticReports: DiagnosticReport[] = customDiagnostic.processDiagnosticInformation(); - - if (argv.dex != null) { - try { - const reportHandler = new ExcelReportProcessor({ - outputFilePath: argv.dex, - }); - reportHandler.generateReportDocument(customDiagnostic); - } catch (dexError: any) { - logErrorToFile(dexError); - console.error(chalk.red('Misslyckades att skriva till excelfilen!')); + if (hasForbiddenArgs.length > 0) { + throw new Error('I API-läge är endast --enableUrlValidation och --urlValidationConfigFile tillåtna. '); } } - /** - * Chalk impl. - * @param allvarlighetsgrad - * @returns - */ - // Run Spectral on the API specification and log the result - const colorizeSeverity = (allvarlighetsgrad: string) => { - switch (allvarlighetsgrad) { - case 'ERROR': // Error - return chalk.red('Error'); - case 'WARNING': // Warning - return chalk.yellow('Warning'); - case 'HINT': // Info - return chalk.greenBright('Hint'); - default: - return chalk.white('Info'); - } - }; - const formatLintingResult = (result: any) => { - return `allvarlighetsgrad: ${colorizeSeverity(result.allvarlighetsgrad)} \nid: ${result.id} \nkrav: ${result.krav} \nområde: ${result.område} \nsökväg:[${result.sökväg}] \nomfattning:${JSON.stringify(result.omfattning, null, 2)} `; - }; - //Check specified option from yargs input - - const currentDate = new Date(); //.toISOString(); // Get current date and time in ISO format - const formattedDate = `${currentDate.getFullYear()}-${padZero(currentDate.getMonth() + 1)}-${padZero(currentDate.getDate())} ${padZero(currentDate.getHours())}:${padZero(currentDate.getMinutes())}:${padZero(currentDate.getSeconds())}`; - - function padZero(num: number): string { - return num < 10 ? `0${num}` : `${num}`; - } - if (logDiagnosticFilePath) { - //Check if we gonna construct logData for diagnostic information - let logData: any; - logData = { - timeStamp: formattedDate, - result: diagnosticReports, - }; - let logEntry = JSON.stringify(logData, null, 2) + '\n'; // Properly formatted JSON - let utf8EncodedContent = Buffer.from(logEntry, 'utf8'); + return true; + }).argv; - //Log to disc - await writeFileAsync(logDiagnosticFilePath, utf8EncodedContent); - console.log(chalk.green(`Skriver diagnostiseringsinformation från RAP-LP till ${logDiagnosticFilePath}`)); - } else { - //Log to STDOUT - if ( - customDiagnostic.diagnosticInformation.executedUniqueRules != undefined && - customDiagnostic.diagnosticInformation.executedUniqueRules.length > 0 - ) { - console.log(chalk.green('<<>>\r')); - console.log(chalk.whiteBright('STATUS\tOMRÅDE') + ' / ' + chalk.whiteBright('IDENTIFIKATIONSNUMMER')); - customDiagnostic.diagnosticInformation.executedUniqueRules.forEach((item) => { - console.log(chalk.bgGreen('OK') + '\t' + item.område + ' / ' + item.id); - }); - } - if ( - customDiagnostic.diagnosticInformation.executedUniqueRulesWithError != undefined && - customDiagnostic.diagnosticInformation.executedUniqueRulesWithError.length > 0 - ) { - console.log(chalk.green('<<>>\r')); - console.log(chalk.whiteBright('STATUS\tOMRÅDE') + ' / ' + chalk.whiteBright('IDENTIFIKATIONSNUMMER')); - customDiagnostic.diagnosticInformation.executedUniqueRulesWithError.forEach((item) => { - console.log(chalk.bgRed('EJ OK') + '\t' + item.område + ' / ' + item.id); - }); - } - if ( - customDiagnostic.diagnosticInformation.notApplicableRules != undefined && - customDiagnostic.diagnosticInformation.notApplicableRules.length > 0 - ) { - console.log(chalk.grey('<<>>\r')); - console.log(chalk.whiteBright('STATUS\tOMRÅDE') + ' / ' + chalk.whiteBright('IDENTIFIKATIONSNUMMER')); - customDiagnostic.diagnosticInformation.notApplicableRules.forEach((item) => { - console.log(chalk.bgGrey('N/A') + '\t' + item.område + '/' + item.id); - }); - } - } - if (logErrorFilePath) { - //Check if we gonna construct some logData for logging purpose - let content: string; - let logData: any; + const mode = argv.mode; - if (!result || result.length === 0) { - logData = { - timeStamp: formattedDate, - message: 'Inga valideringsfel förekom.', - }; - } else { - logData = { - timestamp: formattedDate, - message: 'Valideringsfel upptäcktes. Detaljer följer nedan.', - errors: result, - }; - } - try { - if (argv.append) { - // Check for appending logging information - let existingLogs: any[] = []; - - if (fs.existsSync(logErrorFilePath)) { - // Does any previous file exists? - const fileContent = await fs.promises.readFile(logErrorFilePath, 'utf8'); - try { - existingLogs = JSON.parse(fileContent); // Parse json into object - if (!Array.isArray(existingLogs)) { - existingLogs = [existingLogs]; // Only one object - } - } catch { - // No JSON-file → Ignore - existingLogs = []; - } - } - existingLogs.push(logData); // Push on stack - const updatedContent = JSON.stringify(existingLogs, null, 2); - await writeFileAsync(logErrorFilePath, Buffer.from(updatedContent, 'utf8')); - } else { - const content = JSON.stringify([logData], null, 2); // skriv alltid som array - await writeFileAsync(logErrorFilePath, Buffer.from(content, 'utf8')); - } - console.log(chalk.green(`Skriver inspektion/valideringsinformation från RAP-LP till ${logErrorFilePath}`)); - } catch (fileError: any) { - logErrorToFile(fileError); - console.error(chalk.red('Misslyckades att skriva till loggfilen!')); - } - } else { - console.log(chalk.whiteBright('\n<>\n')); - if (!result || result.length === 0) { - console.log(chalk.green('Inga valideringsfel förekom.')); - } else { - result.forEach((item) => console.log(formatLintingResult(item))); - } - } - } catch (spectralError: any) { - logErrorToFile(spectralError); // Log stack - console.error( - chalk.red( - 'Ett fel uppstod vid initiering/körning av regelklasser! Undersök felloggen för RAP-LP för mer information om felet', - ), - ); - } - } catch (initializingError: any) { - logErrorToFile(initializingError); - console.error( - chalk.red( - 'Ett fel uppstod vid inläsning av moduler och skapande av regelklasser! Undersök felloggen för RAP-LP för mer information om felet', - ), - ); - } - } catch (error: any) { - logErrorToFile(error); - console.error( - chalk.red('Ett oväntat fel uppstod! Undersök felloggen för RAP-LP för mer information om felet', error.message), - ); - process.exitCode = 1; + if (mode === 'api') { + startServer(argv); // Starta API-läget + } else { + await execCLI(argv); // Starta CLI-läget } } -// Kör main och fånga oväntade promise-rejections + +// Starta huvudprocessen main().catch((err) => { - logErrorToFile(err); - console.error(chalk.red('Oväntat fel i main:'), err); - process.exitCode = 1; + console.error('Ett oväntat fel uppstod:', err); }); - - -function logErrorToFile(error: any) { - const errorMessage = `${new Date().toISOString()} - ${error.stack}\n`; - fs.appendFileSync('rap-lp-error.log', errorMessage); - if (error.errors) { - const detailedMessage = `${new Date().toISOString()} - ${JSON.stringify(error.errors, null, 2)}\n`; - fs.appendFileSync('rap-lp-error.log', detailedMessage); - } - if (error instanceof AggregateError) { - error.errors.forEach((err: any, index: number) => { - const causeMessage = `Cause ${index + 1}: ${err.stack || err}\n`; - fs.appendFileSync('rap-lp-error.log', causeMessage); - }); - } -} - diff --git a/src/cli-mode.ts b/src/cli-mode.ts new file mode 100644 index 00000000..14e498a1 --- /dev/null +++ b/src/cli-mode.ts @@ -0,0 +1,300 @@ +// SPDX-FileCopyrightText: 2025 Digg - Agency for Digital Government +// +// SPDX-License-Identifier: EUPL-1.2 + +import * as fs from 'node:fs'; +import { join } from 'path'; +import Parsers from '@stoplight/spectral-parsers'; +import { Document } from '@stoplight/spectral-core'; +import { importAndCreateRuleInstances, getRuleModules } from './util/ruleUtil.js'; // Import the helper function +import util from 'util'; +import { RapLPCustomSpectral } from './util/RapLPCustomSpectral.js'; +import { DiagnosticReport, RapLPDiagnostic } from './util/RapLPDiagnostic.js'; +import { AggregateError } from './util/RapLPCustomErrorInfo.js'; +import chalk from 'chalk'; +import { ExcelReportProcessor } from './util/excelReportProcessor.js'; + +import type { IParser } from '@stoplight/spectral-parsers'; +import { Document as SpectralDocument } from '@stoplight/spectral-core'; +import { Issue } from './util/Issue.js'; +import { parseApiSpecInput,detectSpecFormatPreference, ParseResult} from './util/validateUtil.js'; +import { SpecParseError } from './util/RapLPSpecParseError.js'; +import * as path from 'node:path'; +import { RuleExecutionContext } from './util/RuleExecutionContext.js'; + +declare var AggregateError: { + prototype: AggregateError; + new (errors: any[], message?: string): AggregateError; +}; + +const writeFileAsync = util.promisify(fs.writeFile); +const appendFileAsync = util.promisify(fs.appendFile); + +export type CliArgs = { + file?: string; + categories?: string; + logError?: string; + append: boolean; + logDiagnostic?: string; + dex?: string; + strict?: boolean; +}; + +export async function execCLI(argv: T) { + try { + // Parse command-line arguments using yargs + const apiSpecFileName = (argv.file as string) || ''; + const ruleCategories = argv.categories ? (argv.categories as string).split(',') : undefined; + const logErrorFilePath = argv.logError as string | undefined; + const logDiagnosticFilePath = argv.logDiagnostic as string | undefined; + const strict = argv.strict as boolean ?? false; + const context = new RuleExecutionContext(); + + + // Schemevalidation and Spectral Document creation ---------- + let apiSpecDocument: SpectralDocument; + let parseResult: ParseResult; + try { + const prefer = detectSpecFormatPreference(apiSpecFileName,undefined,'auto'); + parseResult = await parseApiSpecInput( + {filePath: apiSpecFileName},{ + strict: strict, + preferJsonError: prefer + } + ); + + // Issue handling ---------- + if (parseResult.strictIssues && parseResult.strictIssues.length > 0) { + console.error('Strict validation reported issues:'); + parseResult.strictIssues.forEach((iss: Issue) => + console.error(chalk.yellow(`- ${iss.type} at ${iss.path} : ${iss.message} ${iss.line ? `(line ${iss.line})` : ''}`)), + ); + process.exitCode = 2; + return; + } + } catch (err: any) { + // Parse handling + if (err instanceof SpecParseError) { + const formattedDate = new Date().toISOString(); + const logData = { + timeStamp: formattedDate, + message: 'Fel vid parsing av API-specifikationen.', + error: err.toJSON ? err.toJSON() : { message: String(err) }, + }; + + if (logErrorFilePath) { + try { + let existingLogs: any[] = []; + if (argv.append && fs.existsSync(logErrorFilePath)) { + const fileContent = await fs.promises.readFile(logErrorFilePath, 'utf8'); + try { + existingLogs = JSON.parse(fileContent); + if (!Array.isArray(existingLogs)) existingLogs = [existingLogs]; + } catch { + existingLogs = []; + } + } + existingLogs.push(logData); + const updatedContent = JSON.stringify(existingLogs, null, 2); + await writeFileAsync(logErrorFilePath, Buffer.from(updatedContent, 'utf8')); + console.log(chalk.green(`Parserfel loggat till ${logErrorFilePath}`)); + } catch (fileErr: any) { + console.error(chalk.red('Misslyckades att skriva parserfel till loggfilen:'), fileErr.message); + } + } else { + // No log file specified - write to stdout + console.error(chalk.red('<<< Parserfel i API-specifikationen >>>')); + console.error(chalk.red(`Fel: ${err.message}`)); + if (err.line || err.column) { + console.error(chalk.yellow(`Rad: ${err.line ?? '-'}, Kolumn: ${err.column ?? '-'}`)); + } + if (err.snippet && !err.message.includes(err.snippet)) { + console.error(chalk.gray('--- snippet ---')); + console.error(chalk.gray(err.snippet)); + console.error(chalk.gray('---------------')); + } + } + + process.exitCode = 1; + return; // terminate main gracefully + } + + // Övrigt oväntat fel + logErrorToFile(err); + console.error(chalk.red('Ett fel uppstod vid inläsning/parsing av spec-filen. Se felloggen för mer information.')); + process.exitCode = 1; + return; + } + + try { + // Import and create rule instances in RAP-LP + const enabledRulesAndCategorys = await importAndCreateRuleInstances(context,ruleCategories); + // Load API specification into a Document object + const parser: IParser = (parseResult.format === 'json' ? Parsers.Json : Parsers.Yaml) as unknown as IParser; + apiSpecDocument = new SpectralDocument(parseResult.raw, parser, apiSpecFileName); + try { + /** + * CustomSpectral + */ + const customSpectral = new RapLPCustomSpectral(); + customSpectral.setCategorys(enabledRulesAndCategorys.instanceCategoryMap); + customSpectral.setRuleset(enabledRulesAndCategorys.rules); + const result = await customSpectral.run(apiSpecDocument); + + const customDiagnostic = new RapLPDiagnostic(context); + customDiagnostic.processRuleExecutionInformation(result, enabledRulesAndCategorys.rules,enabledRulesAndCategorys.instanceCategoryMap); + const diagnosticReports: DiagnosticReport[] = customDiagnostic.processDiagnosticInformation(); + if (argv.dex != null) { + const reportHandler = new ExcelReportProcessor({ + outputFilePath: argv.dex, + }); + reportHandler.generateReportDocument(customDiagnostic); + } + + /** + * Chalk impl. + * @param allvarlighetsgrad + * @returns + */ + // Run Spectral on the API specification and log the result + const colorizeSeverity = (allvarlighetsgrad: string) => { + switch (allvarlighetsgrad) { + case 'ERROR': // Error + return chalk.red('Error'); + case 'WARNING': // Warning + return chalk.yellow('Warning'); + case 'HINT': // Info + return chalk.greenBright('Hint'); + default: + return chalk.white('Info'); + } + }; + const formatLintingResult = (result: any) => { + return `\nallvarlighetsgrad: ${colorizeSeverity(result.allvarlighetsgrad)} \nid: ${result.id} \nkrav: ${ + result.krav + } \nområde: ${result.område} \nsökväg:[${result.sökväg}] \nomfattning:${JSON.stringify( + result.omfattning, + null, + 2, + )}\ndesignregel: ${result.helpUrl} `; + }; + //Check specified option from yargs input + + const currentDate = new Date(); //.toISOString(); // Get current date and time in ISO format + const formattedDate = `${currentDate.getFullYear()}-${padZero(currentDate.getMonth() + 1)}-${padZero( + currentDate.getDate(), + )} ${padZero(currentDate.getHours())}:${padZero(currentDate.getMinutes())}:${padZero( + currentDate.getSeconds(), + )}`; + + function padZero(num: number): string { + return num < 10 ? `0${num}` : `${num}`; + } + if (logDiagnosticFilePath) { + let allDiagnosticReports = JSON.stringify(diagnosticReports, null, 2); + let logEntry = `${formattedDate}\n${allDiagnosticReports}\n`; // Prepend datestamp to log entry + let utf8EncodedContent = Buffer.from(logEntry, 'utf8'); + //Log to disc + await writeFileAsync(logDiagnosticFilePath, utf8EncodedContent); + console.log(chalk.green(`Skriver diagnostiseringsinformation från RAP-LP till ${logDiagnosticFilePath}`)); + } else { + //STDOUT + if ( + customDiagnostic.diagnosticInformation.executedUniqueRules != undefined && + customDiagnostic.diagnosticInformation.executedUniqueRules.length > 0 + ) { + console.log(chalk.green('<<>>\r')); + console.log(chalk.whiteBright('STATUS\tOMRÅDE') + ' / ' + chalk.whiteBright('IDENTIFIKATIONSNUMMER')); + customDiagnostic.diagnosticInformation.executedUniqueRules + .sort((a, b) => a.id.localeCompare(b.id, 'sv')) + .forEach((item) => { + console.log(chalk.bgGreen('OK') + '\t' + item.område + ' / ' + item.id); + }); + } + if ( + customDiagnostic.diagnosticInformation.executedUniqueRulesWithError != undefined && + customDiagnostic.diagnosticInformation.executedUniqueRulesWithError.length > 0 + ) { + console.log(chalk.green('<<>>\r')); + console.log(chalk.whiteBright('STATUS\tOMRÅDE') + ' / ' + chalk.whiteBright('IDENTIFIKATIONSNUMMER')); + customDiagnostic.diagnosticInformation.executedUniqueRulesWithError + .sort((a, b) => a.id.localeCompare(b.id, 'sv')) + .forEach((item) => { + console.log(chalk.bgRed('EJ OK') + '\t' + item.område + ' / ' + item.id); + }); + } + if ( + customDiagnostic.diagnosticInformation.notApplicableRules != undefined && + customDiagnostic.diagnosticInformation.notApplicableRules.length > 0 + ) { + console.log(chalk.grey('<<>>\r')); + console.log(chalk.whiteBright('STATUS\tOMRÅDE') + ' / ' + chalk.whiteBright('IDENTIFIKATIONSNUMMER')); + customDiagnostic.diagnosticInformation.notApplicableRules + .sort((a, b) => a.id.localeCompare(b.id, 'sv')) + .forEach((item) => { + console.log(chalk.bgGrey('N/A') + '\t' + item.område + '/' + item.id); + }); + } + } + if (logErrorFilePath) { + let content = JSON.stringify(result, null, 2); + let logEntry = `${formattedDate}\n${content}\n`; // Prepend datestamp to log entry + let utf8EncodedContent = Buffer.from(logEntry, 'utf8'); + if (argv.append) { + await appendFileAsync(logErrorFilePath, utf8EncodedContent); + console.log(chalk.green(`Skriver inspektion/valideringsinformation från RAP-LP till ${logErrorFilePath}`)); + } else { + //Log to disc + await writeFileAsync(logErrorFilePath, utf8EncodedContent); + console.log(chalk.green(`Skriver inspektion/valideringsinformation från RAP-LP till ${logErrorFilePath}`)); + } + } else { + //Verbose error logging goes here with detailed result + console.log(chalk.whiteBright('\n<> \n')); + result + .sort((a, b) => { + const idA = a.id ?? ''; + const idB = b.id ?? ''; + return idA.localeCompare(idB, 'sv'); + }) + .forEach((item) => { + console.log(formatLintingResult(item)); + }); + } + } catch (spectralError: any) { + logErrorToFile(spectralError); // Log stack + console.error( + chalk.red( + 'Ett fel uppstod vid initiering/körning av regelklasser! Undersök felloggen för RAP-LP för mer information om felet', + ), + ); + } + } catch (initializingError: any) { + logErrorToFile(initializingError); + console.error( + chalk.red( + 'Ett fel uppstod vid inläsning av moduler och skapande av regelklasser! Undersök felloggen för RAP-LP för mer information om felet', + ), + ); + } + } catch (error: any) { + logErrorToFile(error); + console.error( + chalk.red('Ett oväntat fel uppstod! Undersök felloggen för RAP-LP för mer information om felet', error.message), + ); + } + function logErrorToFile(error: any) { + const errorMessage = `${new Date().toISOString()} - ${error.stack}\n`; + fs.appendFileSync('rap-lp-error.log', errorMessage); + if (error.errors) { + const detailedMessage = `${new Date().toISOString()} - ${JSON.stringify(error.errors, null, 2)}\n`; + fs.appendFileSync('rap-lp-error.log', detailedMessage); + } + if (error instanceof AggregateError) { + error.errors.forEach((err: any, index: number) => { + const causeMessage = `Cause ${index + 1}: ${err.stack || err}\n`; + fs.appendFileSync('rap-lp-error.log', causeMessage); + }); + } + } +} diff --git a/src/model/ApiInfo.ts b/src/model/ApiInfo.ts new file mode 100644 index 00000000..ede47dac --- /dev/null +++ b/src/model/ApiInfo.ts @@ -0,0 +1,27 @@ +// SPDX-FileCopyrightText: 2025 Digg - Agency for Digital Government +// +// SPDX-License-Identifier: EUPL-1.2 + +export class ApiInfo { + // Properties based on the OpenAPI specification + apiName: string; + apiVersion: string; + apiReleased: Date; // Date type for "apiReleased" + apiDocumentation: string; // Assuming a URI is represented as a string + apiStatus: string; + + // Constructor to initialize the class with values + constructor( + apiName: string, + apiVersion: string, + apiReleased: string | Date, // Accepting both string and Date for flexibility + apiDocumentation: string, + apiStatus: string, + ) { + this.apiName = apiName; + this.apiVersion = apiVersion; + this.apiReleased = new Date(apiReleased); // Convert string to Date + this.apiDocumentation = apiDocumentation; + this.apiStatus = apiStatus; + } +} diff --git a/src/model/ProblemDetailsDto.ts b/src/model/ProblemDetailsDto.ts new file mode 100644 index 00000000..79f97def --- /dev/null +++ b/src/model/ProblemDetailsDto.ts @@ -0,0 +1,49 @@ +// SPDX-FileCopyrightText: 2025 Digg - Agency for Digital Government +// +// SPDX-License-Identifier: EUPL-1.2 + +export class ProblemDetailsDTO { + /** + * A URI reference [RFC3986] that identifies the problem type. + * This URI should provide human-readable documentation for the problem type. + */ + type: string; + + /** + * A short, human-readable summary of the problem type. + * It should not change from occurrence to occurrence of the problem, except for localization purposes. + */ + title: string; + + /** + * The HTTP status code [RFC7231] generated by the origin server for this occurrence of the problem. + */ + status: number; + + /** + * A human-readable explanation specific to this occurrence of the problem. + */ + detail?: string; + + /** + * A URI reference that identifies the specific occurrence of the problem. + * It may yield further information if dereferenced. + */ + instance?: string; + + /** + * Additional fields specific to the error, typically key-value pairs. + */ + [key: string]: any; + + constructor(data: Partial) { + this.type = data.type ?? 'about:blank'; + this.title = data.title ?? 'An error occurred'; + this.status = data.status ?? 500; + this.detail = data.detail; + this.instance = data.instance; + + // Include any other fields in the object, allowing flexibility for extra properties + Object.assign(this, data); + } +} diff --git a/src/model/SpecValidationRequestDto.ts b/src/model/SpecValidationRequestDto.ts new file mode 100644 index 00000000..1897f08c --- /dev/null +++ b/src/model/SpecValidationRequestDto.ts @@ -0,0 +1,15 @@ + +// SPDX-FileCopyrightText: 2025 Digg - Agency for Digital Government +// +// SPDX-License-Identifier: EUPL-1.2 + +export class SpecValidationRequestDto { + /** Base 64 encoded OpenAPI spec (YAML or JSON) */ + spec!: string; + + /** Rule categories to enable */ + categories?: string[]; + + /** Enable strict OpenAPI validation (structural and sematic) */ + strict?: boolean; +} \ No newline at end of file diff --git a/src/model/UrlContentDto.ts b/src/model/UrlContentDto.ts new file mode 100644 index 00000000..2c0a7c10 --- /dev/null +++ b/src/model/UrlContentDto.ts @@ -0,0 +1,15 @@ +// SPDX-FileCopyrightText: 2025 Digg - Agency for Digital Government +// +// SPDX-License-Identifier: EUPL-1.2 + +// Define the YamlContent class +export class UrlContentDto { + url: string; + categories: string[]; + + // Constructor to initialize the object + constructor(url: string, categories: string[]) { + this.url = url; + this.categories = categories; + } +} diff --git a/src/model/ValidationResponse.ts b/src/model/ValidationResponse.ts new file mode 100644 index 00000000..4db5e9fe --- /dev/null +++ b/src/model/ValidationResponse.ts @@ -0,0 +1,29 @@ +// SPDX-FileCopyrightText: 2025 Digg - Agency for Digital Government +// +// SPDX-License-Identifier: EUPL-1.2 + +import { ValidationResponseDto } from './ValidationResponseDto.js'; +import { Issue } from '../util/Issue.js'; + +/** + * Overhead Validation type + * Encapsulates either success or validation error. + */ +export type ValidationResponse = +| ValidationSuccessResponse +| ValidationIssuesResponse; + +export interface ValidationSuccessResponse { + ok: true; + payload: ValidationResponseDto; +} +/** + * Not an technical error --> No ProblemDetailsDTO + */ +export interface ValidationIssuesResponse { + ok: false; + kind: 'validation'; + issues: Issue[]; + snippet: string; +} + diff --git a/src/model/ValidationResponseDto.ts b/src/model/ValidationResponseDto.ts new file mode 100644 index 00000000..15c8df28 --- /dev/null +++ b/src/model/ValidationResponseDto.ts @@ -0,0 +1,11 @@ +// SPDX-FileCopyrightText: 2025 Digg - Agency for Digital Government +// +// SPDX-License-Identifier: EUPL-1.2 + +import { RapLPCustomSpectralDiagnostic } from '../util/RapLPCustomSpectralDiagnostic.js'; +import { DiagnosticReport } from '../util/RapLPDiagnostic.js'; + +export interface ValidationResponseDto { + result: RapLPCustomSpectralDiagnostic[]; + report: DiagnosticReport[]; +} diff --git a/src/model/YamlContentDto.ts b/src/model/YamlContentDto.ts new file mode 100644 index 00000000..770bb9b5 --- /dev/null +++ b/src/model/YamlContentDto.ts @@ -0,0 +1,16 @@ +// SPDX-FileCopyrightText: 2025 Digg - Agency for Digital Government +// +// SPDX-License-Identifier: EUPL-1.2 + +// Define the YamlContent class +export class YamlContentDto { + // Properties + yaml: string; + categories: string[]; + + // Constructor to initialize the object + constructor(yaml: string, categories: string[]) { + this.yaml = yaml; + this.categories = categories; + } +} diff --git a/src/model/validationRules.ts b/src/model/validationRules.ts new file mode 100644 index 00000000..9eefa627 --- /dev/null +++ b/src/model/validationRules.ts @@ -0,0 +1,17 @@ +// SPDX-FileCopyrightText: 2025 Digg - Agency for Digital Government +// +// SPDX-License-Identifier: EUPL-1.2 + +const validationRules = [ + { rule: 'UfnRules', description: 'URL Format och namngivning' }, + { rule: 'SakRules', description: 'Säkerhet' }, + { rule: 'VerRules', description: 'Versionshantering' }, + { rule: 'FnsRules', description: 'Filtrering, paginering och sökparametrar' }, + { rule: 'ArqRules', description: 'API Request' }, + { rule: 'DokRules', description: 'Dokumentation' }, + { rule: 'AmeRules', description: 'API Message' }, + { rule: 'ForRules', description: 'Förutsättningar' }, + { rule: 'DotRules', description: 'Datum- och tidsformat' }, +]; + +export { validationRules }; diff --git a/src/routes/urlValidation.ts b/src/routes/urlValidation.ts new file mode 100644 index 00000000..63f031eb --- /dev/null +++ b/src/routes/urlValidation.ts @@ -0,0 +1,61 @@ +// SPDX-FileCopyrightText: 2025 Digg - Agency for Digital Government +// +// SPDX-License-Identifier: EUPL-1.2 + +import { Document } from '@stoplight/spectral-core'; +import Parsers from '@stoplight/spectral-parsers'; +import { Express } from 'express'; +import { processApiSpec, validateYamlInput } from '../util/apiUtil.js'; +import { UrlContentDto } from '../model/UrlContentDto.js'; +import { importAndCreateRuleInstances } from '../util/ruleUtil.js'; +import { ERROR_TYPE, RapLPBaseApiError } from '../util/RapLPBaseApiErrorHandling.js'; +import { loadUrlValidationConfiguration } from '../util/urlValidationConfig.js'; +import { RuleExecutionContext } from '../util/RuleExecutionContext.js'; + +export const registerUrlValidationRoutes = (app: Express, urlValidationConfigFile?: string) => { + const config = loadUrlValidationConfiguration(urlValidationConfigFile); + + // Route for validating openapi yaml from url. + app.post('/api/v1/validation/url', async (req, res, next) => { + try { + const context = new RuleExecutionContext(); + const dto: UrlContentDto = req.body; + + if (config?.urlMatchRegex && !dto.url.match(config.urlMatchRegex)) { + throw new RapLPBaseApiError( + 'Invalid Request', + 'The requested address failed the allowed url pattern. Contact your administrator if you think this is a misstake.', + ERROR_TYPE.BAD_REQUEST, + ); + } + + const response = await fetch(dto.url, config?.customFetchConfig); + + const yamlContentString = await response.text(); + + validateYamlInput(yamlContentString); + + const apiSpecDocument = new Document(yamlContentString, Parsers.Yaml, ''); + + const rules = await importAndCreateRuleInstances(context, dto.categories); + + const result = await processApiSpec(context,rules, apiSpecDocument); + res.send(result); + } catch (e) { + next(e); + } + }); +}; + +// Fallback route if feature dissabled. +export const registerUrlValidationFallbackRoutes = (app: Express) => { + app.post('/api/v1/validation/url', async (req, res, next) => { + next( + new RapLPBaseApiError( + 'Conflict', + 'This feature is currenctly dissabled due to server configuration. Contact your administrator if you think this is a misstake.', + ERROR_TYPE.CONFLICT, + ), + ); + }); +}; diff --git a/src/routes/validate.ts b/src/routes/validate.ts new file mode 100644 index 00000000..d7fec130 --- /dev/null +++ b/src/routes/validate.ts @@ -0,0 +1,185 @@ +// SPDX-FileCopyrightText: 2025 Digg - Agency for Digital Government +// +// SPDX-License-Identifier: EUPL-1.2 + +import { Document } from '@stoplight/spectral-core'; +import Parsers from '@stoplight/spectral-parsers'; +import { Express } from 'express'; +import { decodeBase64String, processApiSpec, validateYamlInput } from '../util/apiUtil.js'; +import { YamlContentDto } from '../model/YamlContentDto.js'; +import { importAndCreateRuleInstances } from '../util/ruleUtil.js'; +import { ApiInfo } from '../model/ApiInfo.js'; +import { validationRules } from '../model/validationRules.js'; +import { ExcelReportProcessor } from '../util/excelReportProcessor.js'; +import { DiagnosticReport, RapLPDiagnostic } from '../util/RapLPDiagnostic.js'; +import * as IssueHelper from '../util/RapLPIssueHelpers.js'; +import { parseApiSpecInput,detectSpecFormatPreference, ParseResult} from '../util/validateUtil.js'; +import { ProblemDetailsDTO } from '../model/ProblemDetailsDto.js'; +import { SpecValidationRequestDto } from '../model/SpecValidationRequestDto.js'; +import { ERROR_TYPE, RapLPBaseApiError } from '../util/RapLPBaseApiErrorHandling.js'; +import type { IParser } from '@stoplight/spectral-parsers'; +import { stringify } from 'node:querystring'; +import { RuleExecutionContext } from '../util/RuleExecutionContext.js'; + + + +export const registerValidationRoutes = (app: Express) => { + // Route for raw content upload. + app.post('/api/v1/validation/validate', async (req, res, next) => { + try { + const context = new RuleExecutionContext(); + const yamlContent: YamlContentDto = req.body; + + let yamlContentString: string; + yamlContentString = decodeBase64String(yamlContent.yaml); + + validateYamlInput(yamlContentString); + + const apiSpecDocument = new Document(yamlContentString, Parsers.Yaml, ''); + + const rules = await importAndCreateRuleInstances(context, yamlContent.categories); + + const result = await processApiSpec(context,rules, apiSpecDocument); + res.send(result); + } catch (e) { + next(e); + } + }); + + app.get('/api/v1/validation/rules', (req, res) => { + res.send(validationRules); + }); + + app.get('/api/v1/api-info', async (req, res, next) => { + res.send( + new ApiInfo('RAP-LP', '1.0.11', new Date().toDateString(), 'http://example.digg.se/RAP-LP-docs', 'development'), + ); + }); + + app.post('/api/v1/validation/generate-report', async (req, res, next): Promise => { + try { + const data = req.body; + const context = new RuleExecutionContext(); + + if (!data || !data.result || !Array.isArray(data.result)) { + return res.status(400).json({ error: 'Invalid data format. Expected an object with a "result" array.' }); + } + + const reportHandler = new ExcelReportProcessor(); + let buffer: Buffer; + + const ruleCategories = data.categories && data.categories.length > 0 ? data.categories : undefined; + + const enabledRulesAndCategorys = await importAndCreateRuleInstances(ruleCategories); + const customDiagnostic = new RapLPDiagnostic(context); + customDiagnostic.processRuleExecutionInformation(data.result, enabledRulesAndCategorys.rules,enabledRulesAndCategorys.instanceCategoryMap); + const diagnosticReports: DiagnosticReport[] = customDiagnostic.processDiagnosticInformation(); + + try { + buffer = reportHandler.generateReportDocumentBuffer(customDiagnostic); + } catch (error) { + console.error('Error generating report buffer:', error); + return res.status(500).json({ error: 'Failed to generate report.' }); + } + + res.setHeader('Content-Disposition', 'attachment; filename="avstamningsfil.xlsx"'); + res.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); + res.send(buffer); + } catch (e) { + next(e); + } + }); + /** + * Endpoint + */ + app.post('/api/v1/validation/validatespec', async (req, res, next) => { + try { + const context = new RuleExecutionContext(); + const body: SpecValidationRequestDto = req.body; + + //0.5 Check input + if (!body.spec) { + throw new RapLPBaseApiError( + 'Ogiltig request', + 'Obligatoriskt fält saknas: spec', + ERROR_TYPE.BAD_REQUEST, + ); + } + + // 1. Decode input + const raw = decodeBase64String(body.spec); + const strict = body.strict ?? true; + const categories = body.categories ?? []; + + // 2. Detect format-preferens + const prefer = detectSpecFormatPreference( + undefined, + raw, + 'auto', + ); + // 3. Parse handling + strict-validate (Structural / Semantic errors) + const parseResult = await parseApiSpecInput( + { raw }, + {strict,preferJsonError: prefer}, + ); + + // 4. Strict-issues → + if (parseResult.strictIssues?.length) { + const sorted = IssueHelper.sortIssues(parseResult.strictIssues); + const snippet = IssueHelper.formatIssuesAsEditorText(sorted); + + return res.status(400).json( + new ProblemDetailsDTO({ + type: 'https://rap-lp./problems/semantic-validation', + title: 'Regelvalideringen misslyckades', + status: 400, + detail: 'Specifikationen innehåller strukturella eller semantiska fel', + instance: req.originalUrl, + + // Put in kind field to indicate violation + kind: 'spec-validation', + //Payload + issues: sorted, + snippet, + }), + ); + } + + // 5. No strict-errors → run raplp ruleengine + const parser: IParser = (parseResult.format === 'json' ? Parsers.Json : Parsers.Yaml) as unknown as IParser; + const apiSpecDocument = new Document(parseResult.raw, parser, 'payload.yaml'); // In-memory-file to calculate correct positions when parsing + const rules = await importAndCreateRuleInstances(context, categories); + const result = await processApiSpec(context, rules, apiSpecDocument); + + const hasRuleViolations = result.result.some( + d =>d.allvarlighetsgrad === 'ERROR' || d.allvarlighetsgrad === 'WARNING' + ); + if (hasRuleViolations) { + //Rulevalidation occured in RapLP-ruleengine + return res.status(400).json( + new ProblemDetailsDTO({ + type: 'https://rap-lp./problems/rule-validation', + title: 'Regelvalideringen misslyckades', + status: 400, + detail: 'API-specifikationen bryter mot en eller flera regler enligt den svenska REST API-profilen.', + instance: req.originalUrl, + + // Put in kind field to indicate violation + kind: 'rule-validation', + //Payload + payload: result, + }), + ); + } + // No rule violation and success response goes here + return res.status(200).json({ + ok: true, + //Payload + payload: result, + }); + } catch (e) { + // Hantera SpecParseError här + next(e); + } + }); +}; diff --git a/src/rulesets/AmeRules.ts b/src/rulesets/AmeRules.ts index de81f344..064f1626 100644 --- a/src/rulesets/AmeRules.ts +++ b/src/rulesets/AmeRules.ts @@ -8,6 +8,7 @@ import { parsePropertyNames } from './rulesetUtil.js'; import { CustomProperties } from '../ruleinterface/CustomProperties.js'; import { BaseRuleset } from './BaseRuleset.js'; import { isValidApplicationJson } from './util/AmeRulesUtil.js'; +import { RuleExecutionContext } from '../util/RuleExecutionContext.js'; const moduleName: string = 'AmeRules.js'; @@ -55,8 +56,8 @@ export class Ame07 extends BaseRuleset { }, }, ]; - constructor() { - super(); + constructor(context: RuleExecutionContext) { + super(context); super.initializeFormats(['OAS3']); } severity = DiagnosticSeverity.Warning; @@ -91,8 +92,8 @@ export class Ame04 extends BaseRuleset { }, }, ]; - constructor() { - super(); + constructor(context: RuleExecutionContext) { + super(context); super.initializeFormats(['OAS3']); } severity = DiagnosticSeverity.Warning; @@ -137,8 +138,8 @@ export class Ame01 extends BaseRuleset { }, }, ]; - constructor() { - super(); + constructor(context: RuleExecutionContext) { + super(context); super.initializeFormats(['OAS3']); } severity = DiagnosticSeverity.Warning; @@ -185,8 +186,8 @@ export class Ame02 extends BaseRuleset { }, }, ]; - constructor() { - super(); + constructor(context: RuleExecutionContext) { + super(context); super.initializeFormats(['OAS3']); } severity = DiagnosticSeverity.Warning; @@ -197,7 +198,7 @@ export class Ame05 extends BaseRuleset { id: 'AME.05', }; description = 'Inom ett API SKALL namnsättningen vara konsekvent, dvs blanda inte camelCase och snake_case.'; - message = 'Inom ett API SKALL namnsättningen vara konsekvent, dvs blanda inte camelCase och snake_case. '; + message = 'Inom ett API SKALL namnsättningen vara konsekvent, dvs blanda inte camelCase och snake_case.'; given = '$.components.schemas'; then = [ { @@ -315,8 +316,8 @@ export class Ame05 extends BaseRuleset { } return Array.from(invalidEntries); } - constructor() { - super(); + constructor(context: RuleExecutionContext) { + super(context); super.initializeFormats(['OAS3']); } severity = DiagnosticSeverity.Error; diff --git a/src/rulesets/ArqRules.ts b/src/rulesets/ArqRules.ts index d5898aec..5ee3ffc8 100644 --- a/src/rulesets/ArqRules.ts +++ b/src/rulesets/ArqRules.ts @@ -8,6 +8,7 @@ import { DiagnosticSeverity } from '@stoplight/types'; import { CustomProperties } from '../ruleinterface/CustomProperties.js'; import { BaseRuleset } from './BaseRuleset.js'; import { isValidApplicationJson } from './rulesetUtil.js'; +import { RuleExecutionContext } from '../util/RuleExecutionContext.js'; const moduleName: string = 'ArqRules.js'; @@ -149,8 +150,8 @@ export class Arq01 extends BaseRuleset { }, }, ]; - constructor() { - super(); + constructor(context: RuleExecutionContext) { + super(context); super.initializeFormats(['OAS3']); } severity = DiagnosticSeverity.Warning; @@ -215,9 +216,9 @@ export class Arq03 extends BaseRuleset { }, }, ]; - constructor() { - super(); - super.initializeFormats(['OAS2', 'OAS3']); + constructor(context: RuleExecutionContext) { + super(context); + super.initializeFormats(['OAS3']); } severity = DiagnosticSeverity.Warning; } diff --git a/src/rulesets/BaseRuleset.ts b/src/rulesets/BaseRuleset.ts index ff8a67ba..69a75c04 100644 --- a/src/rulesets/BaseRuleset.ts +++ b/src/rulesets/BaseRuleset.ts @@ -6,10 +6,10 @@ import { RulesetInterface } from '../ruleinterface/RuleInterface.js'; import { CustomProperties } from '../ruleinterface/CustomProperties.js'; import { CustomFormatType } from './util/CustomOasVersion.js'; import { DiagnosticSeverity } from '@stoplight/types'; -import { logRuleExecution } from '../util/RuleExecutionStatusModule.js'; import Format from '@stoplight/spectral-formats'; +import { RuleExecutionContext } from '../util/RuleExecutionContext.js'; -export class BaseRuleset implements RulesetInterface { +export abstract class BaseRuleset implements RulesetInterface { static customProperties: CustomProperties = { område: undefined!, id: '' }; static getCustomProperties(): CustomProperties { return BaseRuleset.customProperties; @@ -22,7 +22,14 @@ export class BaseRuleset implements RulesetInterface { formats: any = []; - trackRuleExecutionHandler( + constructor(context: RuleExecutionContext) { + this.#context = context; + + } + #context: RuleExecutionContext; + + + protected trackRuleExecutionHandler( targetVal: string, _opts: string, paths: string[], @@ -31,7 +38,7 @@ export class BaseRuleset implements RulesetInterface { moduleName: any, subclassProperties: CustomProperties, ) { - logRuleExecution(moduleName, subclassInfo, subclassProperties, this.severity.toString(), true, targetVal); + this.#context.logRuleExecution(moduleName, subclassInfo, subclassProperties, this.severity.toString(), true, targetVal); return []; } async initializeFormats(formats: CustomFormatType[] = []) { diff --git a/src/rulesets/DokRules.ts b/src/rulesets/DokRules.ts index fc618ff2..e3bcae10 100644 --- a/src/rulesets/DokRules.ts +++ b/src/rulesets/DokRules.ts @@ -16,6 +16,8 @@ import { PathsObject, SecurityRequirementObject, } from '../types/openapi-3.0.js'; +import { RuleExecutionContext } from '../util/RuleExecutionContext.js'; + const moduleName: string = 'DokRules.js'; export class Dok15Get extends Dok15Base { @@ -102,9 +104,9 @@ export class Dok17 extends BaseRuleset { }, }, ]; - constructor() { - super(); - super.initializeFormats(['OAS2', 'OAS3']); + constructor(context: RuleExecutionContext) { + super(context); + super.initializeFormats(['OAS2', 'OAS3']); } severity = DiagnosticSeverity.Warning; } @@ -135,9 +137,9 @@ export class Dok20 extends BaseRuleset { }, }, ]; - constructor() { - super(); - super.initializeFormats(['OAS2', 'OAS3']); + constructor(context: RuleExecutionContext) { + super(context); + super.initializeFormats(['OAS3']); } severity = DiagnosticSeverity.Error; } @@ -183,8 +185,8 @@ export class Dok06 extends BaseRuleset { }, }, ]; - constructor() { - super(); + constructor(context: RuleExecutionContext) { + super(context); super.initializeFormats(['OAS3']); } severity = DiagnosticSeverity.Warning; @@ -215,9 +217,9 @@ export class Dok07 extends BaseRuleset { }, }, ]; - constructor() { - super(); - super.initializeFormats(['OAS2', 'OAS3']); + constructor(context: RuleExecutionContext) { + super(context); + super.initializeFormats(['OAS3']); } severity = DiagnosticSeverity.Warning; } @@ -287,9 +289,9 @@ export class Dok08 extends BaseRuleset { }, }, ]; - constructor() { - super(); - super.initializeFormats(['OAS2', 'OAS3']); + constructor(context: RuleExecutionContext) { + super(context); + super.initializeFormats(['OAS3']); } severity = DiagnosticSeverity.Error; } @@ -345,9 +347,9 @@ export class Dok09 extends BaseRuleset { }, }, ]; - constructor() { - super(); - super.initializeFormats(['OAS2', 'OAS3']); + constructor(context: RuleExecutionContext) { + super(context); + super.initializeFormats(['OAS3']); } severity = DiagnosticSeverity.Error; } @@ -389,9 +391,9 @@ export class Dok11 extends BaseRuleset { }, }, ]; - constructor() { - super(); - super.initializeFormats(['OAS2', 'OAS3']); + constructor(context: RuleExecutionContext) { + super(context); + super.initializeFormats(['OAS3']); } severity = DiagnosticSeverity.Error; } @@ -427,9 +429,9 @@ export class Dok19 extends BaseRuleset { }, }, ]; - constructor() { - super(); - super.initializeFormats(['OAS2', 'OAS3']); + constructor(context: RuleExecutionContext) { + super(context); + super.initializeFormats(['OAS3']); } severity = DiagnosticSeverity.Error; } @@ -492,8 +494,8 @@ export class Dok21 extends BaseRuleset { }, }, ]; - constructor() { - super(); + constructor(context: RuleExecutionContext) { + super(context); super.initializeFormats(['OAS3']); } severity = DiagnosticSeverity.Error; @@ -546,9 +548,9 @@ export class Dok01 extends BaseRuleset { }, }, ]; - constructor() { - super(); - super.initializeFormats(['OAS2', 'OAS3']); + constructor(context: RuleExecutionContext) { + super(context); + super.initializeFormats(['OAS3']); } severity = DiagnosticSeverity.Warning; } diff --git a/src/rulesets/DotRules.ts b/src/rulesets/DotRules.ts index 1f56cfca..2b711070 100644 --- a/src/rulesets/DotRules.ts +++ b/src/rulesets/DotRules.ts @@ -6,7 +6,6 @@ import { DiagnosticSeverity } from '@stoplight/types'; import { CustomProperties } from '../ruleinterface/CustomProperties.js'; import { parseProperties, Property } from './rulesetUtil.js'; import { DotRuleBase, DotStateExecutionLog } from './util/DotRulesUtil.js'; -import { BaseRuleset } from './BaseRuleset.js'; const moduleName: string = 'DotRules.js'; diff --git a/src/rulesets/FelRules.ts b/src/rulesets/FelRules.ts index f04df54d..dfab6969 100644 --- a/src/rulesets/FelRules.ts +++ b/src/rulesets/FelRules.ts @@ -5,6 +5,7 @@ import { DiagnosticSeverity } from '@stoplight/types'; import { CustomProperties } from '../ruleinterface/CustomProperties.js'; import { BaseRuleset } from './BaseRuleset.js'; +import { RuleExecutionContext } from '../util/RuleExecutionContext.js'; const moduleName: string = 'FelRules.js'; @@ -59,8 +60,8 @@ export class Fel01 extends BaseRuleset { ]; severity = DiagnosticSeverity.Error; - constructor() { - super(); + constructor(context: RuleExecutionContext) { + super(context); super.initializeFormats(['OAS3']); } } @@ -108,8 +109,8 @@ export class Fel02 extends BaseRuleset { ]; severity = DiagnosticSeverity.Warning; - constructor() { - super(); + constructor(context: RuleExecutionContext) { + super(context); super.initializeFormats(['OAS3']); } } diff --git a/src/rulesets/FnsRules.ts b/src/rulesets/FnsRules.ts index 8c25bd4a..7906969e 100644 --- a/src/rulesets/FnsRules.ts +++ b/src/rulesets/FnsRules.ts @@ -6,6 +6,7 @@ import { enumeration, truthy, falsy, undefined as undefinedFunc, pattern, schema import { DiagnosticSeverity } from '@stoplight/types'; import { CustomProperties } from '../ruleinterface/CustomProperties.js'; import { BaseRuleset } from './BaseRuleset.js'; +import { RuleExecutionContext } from '../util/RuleExecutionContext.js'; const moduleName: string = 'FnsRules.js'; @@ -40,9 +41,9 @@ export class Fns01 extends BaseRuleset { }, }, ]; - constructor() { - super(); - super.initializeFormats(['OAS2', 'OAS3']); + constructor(context: RuleExecutionContext) { + super(context); + super.initializeFormats(['OAS3']); } severity = DiagnosticSeverity.Error; } @@ -76,9 +77,9 @@ export class Fns03 extends BaseRuleset { }, }, ]; - constructor() { - super(); - super.initializeFormats(['OAS2', 'OAS3']); + constructor(context: RuleExecutionContext) { + super(context); + super.initializeFormats(['OAS3']); } severity = DiagnosticSeverity.Error; } @@ -137,9 +138,9 @@ export class Fns09 extends BaseRuleset { }, }, ]; - constructor() { - super(); - super.initializeFormats(['OAS2', 'OAS3']); + constructor(context: RuleExecutionContext) { + super(context); + super.initializeFormats(['OAS3']); } } export class Fns05 extends BaseRuleset { @@ -178,9 +179,9 @@ export class Fns05 extends BaseRuleset { }, }, ]; - constructor() { - super(); - super.initializeFormats(['OAS2', 'OAS3']); + constructor(context: RuleExecutionContext) { + super(context); + super.initializeFormats(['OAS3']); } severity = DiagnosticSeverity.Warning; } @@ -215,9 +216,9 @@ export class Fns06 extends BaseRuleset { }, }, ]; - constructor() { - super(); - super.initializeFormats(['OAS2', 'OAS3']); + constructor(context: RuleExecutionContext) { + super(context); + super.initializeFormats(['OAS3']); } severity = DiagnosticSeverity.Warning; } @@ -282,9 +283,9 @@ export class Fns07 extends BaseRuleset { }, }, ]; - constructor() { - super(); - super.initializeFormats(['OAS2', 'OAS3']); + constructor(context: RuleExecutionContext) { + super(context); + super.initializeFormats(['OAS3']); } severity = DiagnosticSeverity.Error; } @@ -349,9 +350,9 @@ export class Fns08 extends BaseRuleset { }, }, ]; - constructor() { - super(); - super.initializeFormats(['OAS2', 'OAS3']); + constructor(context: RuleExecutionContext) { + super(context); + super.initializeFormats(['OAS3']); } severity = DiagnosticSeverity.Error; } diff --git a/src/rulesets/ForRules.ts b/src/rulesets/ForRules.ts index 5121974f..bf506e6f 100644 --- a/src/rulesets/ForRules.ts +++ b/src/rulesets/ForRules.ts @@ -6,9 +6,7 @@ import { falsy, undefined as undefinedFunc } from '@stoplight/spectral-functions import { DiagnosticSeverity } from '@stoplight/types'; import { CustomProperties } from '../ruleinterface/CustomProperties.js'; import { BaseRuleset } from './BaseRuleset.js'; -import Format from '@stoplight/spectral-formats'; -import { CustomFormats } from './util/CustomOasVersion.js'; - +import { RuleExecutionContext } from '../util/RuleExecutionContext.js'; const moduleName: string = 'ForRules.js'; /** @@ -43,8 +41,8 @@ export class For02 extends BaseRuleset { ]; severity = DiagnosticSeverity.Error; - constructor() { - super(); + constructor(context: RuleExecutionContext) { + super(context); super.initializeFormats(['OAS3']); } } diff --git a/src/rulesets/MogRules.ts b/src/rulesets/MogRules.ts index 0fe6e7ba..b0db259d 100644 --- a/src/rulesets/MogRules.ts +++ b/src/rulesets/MogRules.ts @@ -6,6 +6,7 @@ import { DiagnosticSeverity } from '@stoplight/types'; import { CustomProperties } from '../ruleinterface/CustomProperties.js'; import { BaseRuleset } from './BaseRuleset.js'; import { countEndpoints, endPointsAreValid } from './util/MogRulesUtil.js'; +import { RuleExecutionContext } from '../util/RuleExecutionContext.js'; const moduleName: string = 'MogRules.ts'; @@ -51,8 +52,8 @@ export class Mog01 extends BaseRuleset { }, ]; severity = DiagnosticSeverity.Error; - constructor() { - super(); + constructor(context: RuleExecutionContext) { + super(context); super.initializeFormats(['OAS3']); } } @@ -94,8 +95,8 @@ export class Mog02 extends BaseRuleset { }, ]; severity = DiagnosticSeverity.Warning; - constructor() { - super(); + constructor(context: RuleExecutionContext) { + super(context); super.initializeFormats(['OAS3']); } } diff --git a/src/rulesets/ResRules.ts b/src/rulesets/ResRules.ts index 85b734ad..65de4dcc 100644 --- a/src/rulesets/ResRules.ts +++ b/src/rulesets/ResRules.ts @@ -6,6 +6,7 @@ import { DiagnosticSeverity } from '@stoplight/types'; import { CustomProperties } from '../ruleinterface/CustomProperties.js'; import { BaseRuleset } from './BaseRuleset.js'; import { personalIdentityNumberFieldNames } from './constants/ResConstants.js'; +import { RuleExecutionContext } from '../util/RuleExecutionContext.js'; const moduleName: string = 'ResRules.js'; @@ -50,8 +51,8 @@ export class Res02 extends BaseRuleset { }, ]; severity = DiagnosticSeverity.Warning; - constructor() { - super(); + constructor(context: RuleExecutionContext) { + super(context); super.initializeFormats(['OAS3']); } } @@ -108,8 +109,8 @@ export class Res06 extends BaseRuleset { }, ]; severity = DiagnosticSeverity.Error; - constructor() { - super(); + constructor(context: RuleExecutionContext) { + super(context); super.initializeFormats(['OAS3']); } } diff --git a/src/rulesets/SakRules.ts b/src/rulesets/SakRules.ts index 9e370bb4..6401650c 100644 --- a/src/rulesets/SakRules.ts +++ b/src/rulesets/SakRules.ts @@ -7,6 +7,7 @@ import { DiagnosticSeverity } from '@stoplight/types'; import { BaseRuleset } from './BaseRuleset.js'; import { CustomProperties } from '../ruleinterface/CustomProperties.js'; import { SakBaseApiKeyRule } from './rulesetUtil.js'; +import { RuleExecutionContext } from '../util/RuleExecutionContext.js'; //import Format from "@stoplight/spectral-formats"; const moduleName: string = 'SakRules.js'; @@ -54,8 +55,8 @@ export class Sak01 extends BaseRuleset { }, }, ]; - constructor() { - super(); + constructor(context: RuleExecutionContext) { + super(context); super.initializeFormats(['OAS3']); } severity = DiagnosticSeverity.Error; @@ -101,8 +102,8 @@ export class Sak09 extends BaseRuleset { }, }, ]; - constructor() { - super(); + constructor(context: RuleExecutionContext) { + super(context); super.initializeFormats(['OAS3']); } severity = DiagnosticSeverity.Error; @@ -138,8 +139,8 @@ export class Sak10 extends BaseRuleset { }, }, ]; - constructor() { - super(); + constructor(context: RuleExecutionContext) { + super(context); super.initializeFormats(['OAS3']); } severity = DiagnosticSeverity.Error; @@ -149,8 +150,9 @@ export class Sak15 extends SakBaseApiKeyRule { område: 'Säkerhet', id: 'SAK.15', }; - constructor() { - super(); + constructor(context: RuleExecutionContext) { + super(context); + super.initializeFormats(['OAS3']); } description = '-'; message = 'API-nycklar SKALL INTE inkluderas i URL eller querysträngen'; @@ -180,8 +182,9 @@ export class Sak16 extends SakBaseApiKeyRule { område: 'Säkerhet', id: 'SAK.16', }; - constructor() { - super(); + constructor(context: RuleExecutionContext) { + super(context); + super.initializeFormats(['OAS3']); } description = 'API-nycklar SKALL inkluderas i HTTP-headern eftersom querysträngar kan sparas i okrypterat format.'; message = @@ -235,8 +238,8 @@ export class Sak18 extends BaseRuleset { }, }, ]; - constructor() { - super(); + constructor(context: RuleExecutionContext) { + super(context); super.initializeFormats(['OAS3']); } severity = DiagnosticSeverity.Warning; diff --git a/src/rulesets/UfnRules.ts b/src/rulesets/UfnRules.ts index 89551e51..69a23387 100644 --- a/src/rulesets/UfnRules.ts +++ b/src/rulesets/UfnRules.ts @@ -16,7 +16,8 @@ import { } from '@stoplight/spectral-functions'; import { DiagnosticSeverity } from '@stoplight/types'; import { CustomProperties } from '../ruleinterface/CustomProperties.js'; -import Format from '@stoplight/spectral-formats'; +import { RuleExecutionContext } from '../util/RuleExecutionContext.js'; + const moduleName: string = 'UfnRules.js'; export class Ufn01 extends BaseRuleset { @@ -49,8 +50,8 @@ export class Ufn01 extends BaseRuleset { }, }, ]; - constructor() { - super(); + constructor(context: RuleExecutionContext) { + super(context); super.initializeFormats(['OAS3']); } severity = DiagnosticSeverity.Warning; @@ -101,8 +102,8 @@ export class Ufn02 extends BaseRuleset { }, }, ]; - constructor() { - super(); + constructor(context: RuleExecutionContext) { + super(context); super.initializeFormats(['OAS3']); } severity = DiagnosticSeverity.Error; @@ -158,8 +159,8 @@ export class Ufn05Servers extends Ufn05Base { }, }, ]; - constructor() { - super(); + constructor(context: RuleExecutionContext) { + super(context); super.initializeFormats(['OAS3']); } } @@ -235,8 +236,8 @@ export class Ufn05paths extends Ufn05Base { }, }, ]; - constructor() { - super(); + constructor(context: RuleExecutionContext) { + super(context); super.initializeFormats(['OAS3']); } } @@ -296,9 +297,9 @@ export class Ufn08 extends BaseRuleset { }, }, ]; - constructor() { - super(); - super.initializeFormats(['OAS3', 'OAS2']); + constructor(context: RuleExecutionContext) { + super(context); + super.initializeFormats(['OAS3']); } severity = DiagnosticSeverity.Error; } @@ -377,30 +378,30 @@ export class Ufn07 extends BaseRuleset { }, }, ]; - constructor() { - super(); - super.initializeFormats(['OAS3', 'OAS2']); + constructor(context: RuleExecutionContext) { + super(context); + super.initializeFormats(['OAS3']); } severity = DiagnosticSeverity.Error; } export class Ufn09Server extends Ufn09Base { given = '$.servers.[url]'; - constructor() { - super(); + constructor(context: RuleExecutionContext) { + super(context); super.initializeFormats(['OAS3']); } } export class Ufn09InPathParameters extends Ufn09Base { given = "$.paths.*.*.parameters[?(@.in=='path')].name"; - constructor() { - super(); + constructor(context: RuleExecutionContext) { + super(context); super.initializeFormats(['OAS3']); } } export class Ufn09Path extends Ufn09Base { given = '$.paths[*]~'; - constructor() { - super(); + constructor(context: RuleExecutionContext) { + super(context); super.initializeFormats(['OAS3']); } } diff --git a/src/rulesets/VerRules.ts b/src/rulesets/VerRules.ts index 3aa19e7d..5a11897a 100644 --- a/src/rulesets/VerRules.ts +++ b/src/rulesets/VerRules.ts @@ -7,6 +7,7 @@ import { DiagnosticSeverity } from '@stoplight/types'; import { BaseRuleset } from './BaseRuleset.js'; import { CustomProperties } from '../ruleinterface/CustomProperties.js'; const moduleName: string = 'VerRules.js'; +import { RuleExecutionContext } from '../util/RuleExecutionContext.js'; export class Ver06 extends BaseRuleset { static customProperties: CustomProperties = { @@ -35,9 +36,9 @@ export class Ver06 extends BaseRuleset { }, }, ]; - constructor() { - super(); - super.initializeFormats(['OAS2', 'OAS3']); + constructor(context: RuleExecutionContext) { + super(context); + super.initializeFormats(['OAS3']); } severity = DiagnosticSeverity.Error; } @@ -91,8 +92,8 @@ export class Ver05 extends BaseRuleset { }, }, ]; - constructor() { - super(); + constructor(context: RuleExecutionContext) { + super(context); super.initializeFormats(['OAS3']); } severity = DiagnosticSeverity.Warning; diff --git a/src/rulesets/rulesetUtil.ts b/src/rulesets/rulesetUtil.ts index 8f30fe6b..564bb2b3 100644 --- a/src/rulesets/rulesetUtil.ts +++ b/src/rulesets/rulesetUtil.ts @@ -6,13 +6,14 @@ import { enumeration, truthy, falsy, undefined as undefinedFunc, pattern, schema import { DiagnosticSeverity } from '@stoplight/types'; import { CustomProperties } from '../ruleinterface/CustomProperties.js'; import { BaseRuleset } from './BaseRuleset.js'; +import { RuleExecutionContext } from '../util/RuleExecutionContext.js'; /** * Base class for handling security rules when apiKeys is defined */ export abstract class SakBaseApiKeyRule extends BaseRuleset { - protected constructor() { - super(); + protected constructor(context: RuleExecutionContext) { + super(context); super.initializeFormats(['OAS3']); } given = "$.components.securitySchemes[?(@ && @.type=='apiKey')]"; @@ -45,13 +46,14 @@ export class Dok03Base extends BaseRuleset { område: 'Dokumentation', id: 'DOK.03', }; - constructor() { - super(); + protected constructor(context: RuleExecutionContext) { + super(context); super.initializeFormats(['OAS3']); this.description = 'Dokumentationen för ett API SKALL innehålla följande: Om API, Användarvillkor, Datamodell för representation av resurser, Krav på autentisering, Livscykelhantering och versionshantering, Kontaktuppgifter.'; this.severity = DiagnosticSeverity.Error; } + } export class Ufn05Base extends BaseRuleset { @@ -59,8 +61,9 @@ export class Ufn05Base extends BaseRuleset { område: 'URL Format och namngivning', id: 'UFN.05', }; - constructor() { - super(); + protected constructor(context: RuleExecutionContext) { + super(context); + super.initializeFormats(['OAS3']); this.message = 'En URL BÖR INTE vara längre än 2048 tecken.'; this.severity = DiagnosticSeverity.Warning; this.description = 'En URL BÖR INTE vara längre än 2048 tecken.'; @@ -74,8 +77,8 @@ export class Ufn09Base extends BaseRuleset { område: 'URL Format och namngivning', id: 'UFN.09', }; - constructor() { - super(); + protected constructor(context: RuleExecutionContext) { + super(context); let moduleName: string = 'UfnRules.js'; this.message = "Blanksteg ' ' och understreck '_' SKALL INTE användas i URL:er med undantag av parameter-delen (gäller alltså URL-elementen Scheme, Authority och Path samt API-elementen protokoll, domännamn, api, version, resurs och identifierare)."; @@ -111,8 +114,8 @@ export class Dok15Base extends BaseRuleset { område: 'Dokumentation', id: 'DOK.15', }; - constructor() { - super(); + protected constructor(context: RuleExecutionContext) { + super(context); this.message = 'I dokumentationen av API:et SKALL exempel på API:ets fråga (eng:request) och svar (eng:reply) finnas i sin helhet.'; this.severity = DiagnosticSeverity.Error; @@ -154,8 +157,8 @@ export class Arq05Base extends BaseRuleset { område: 'API Request', id: 'ARQ.05', }; - constructor() { - super(); + protected constructor(context: RuleExecutionContext) { + super(context); super.initializeFormats(['OAS3']); this.given = "$.paths.*.*.parameters[?(@.in=='header' && @.schema)]"; this.message = 'Payload data SKALL INTE användas i HTTP-headers'; diff --git a/src/rulesets/util/DotRulesUtil.ts b/src/rulesets/util/DotRulesUtil.ts index ea9e4454..1efbc848 100644 --- a/src/rulesets/util/DotRulesUtil.ts +++ b/src/rulesets/util/DotRulesUtil.ts @@ -5,6 +5,7 @@ import { BaseRuleset } from '../BaseRuleset.js'; import { Property } from '../rulesetUtil.js'; import { CustomProperties } from '../../ruleinterface/CustomProperties.js'; +import { RuleExecutionContext } from '../../util/RuleExecutionContext.js'; /** * interface describing propertyes collected and validated @@ -28,8 +29,8 @@ export interface DotStateExecutionLog { * Class that abstracts common logic needed in category of Datetime[DOT rules] */ export class DotRuleBase extends BaseRuleset { - constructor() { - super(); + protected constructor(context: RuleExecutionContext) { + super(context); super.initializeFormats(['OAS3']); this.given = '$.components.schemas'; } diff --git a/src/rulesets/util/rules-doc.config.ts b/src/rulesets/util/rules-doc.config.ts new file mode 100644 index 00000000..a3946416 --- /dev/null +++ b/src/rulesets/util/rules-doc.config.ts @@ -0,0 +1,21 @@ +// SPDX-FileCopyrightText: 2025 Digg - Agency for Digital Government +// +// SPDX-License-Identifier: EUPL-1.2 + +const RULES_DOC_REPO = + 'https://github.com/diggsweden/rest-api-profil-lint-processor'; + + + /** + * Constructs a helper Url together with correct Rule + * @param ruleId ( Rule identifier) + * @returns Normalized help url + */ + export function buildRuleHelpUrl(ruleId: string): string { + const ref = process.env.RULES_DOC_REF || 'main'; + const normalizedId = ruleId + .toLowerCase() + .replace('.', ''); + + return `${RULES_DOC_REPO}/blob/${ref}/GUIDELINES.md#id-${normalizedId}`; +} diff --git a/src/util/Issue.ts b/src/util/Issue.ts new file mode 100644 index 00000000..1fb65d6b --- /dev/null +++ b/src/util/Issue.ts @@ -0,0 +1,19 @@ +// SPDX-FileCopyrightText: 2025 Digg - Agency for Digital Government +// +// SPDX-License-Identifier: EUPL-1.2 + +/** + * Issue type to use from client to validate parsing + */ +export type Issue = { + type: 'Structural' | 'Semantic' | 'Info' | string; + code?: string | number; // ex 'path-params' (från Spectral) + path: string; // ex "paths./pets.get.responses.200" + message: string; // ex "should be object" eller spectral message + line?: number | null; // 1-based line if available (null annars) + location?: string; // fallback location or original pointer + source?: string; // file path / source (if available) + documentationUrl?: string;// optional link + raw?: string[]; // original lines that created this issue (optional) + details?: string[]; // extra details (optional) +}; diff --git a/src/util/RapLPBaseApiErrorHandling.ts b/src/util/RapLPBaseApiErrorHandling.ts new file mode 100644 index 00000000..eb9c1f07 --- /dev/null +++ b/src/util/RapLPBaseApiErrorHandling.ts @@ -0,0 +1,71 @@ +// SPDX-FileCopyrightText: 2025 Digg - Agency for Digital Government +// +// SPDX-License-Identifier: EUPL-1.2 + +import { Request, Response, NextFunction } from 'express'; +import { ProblemDetailsDTO } from '../model/ProblemDetailsDto.js'; +import { SpecParseError } from './RapLPSpecParseError.js'; + +/** + * Extended error class with errorType that will be used as HTTP error codes in custom error handler. + */ +class RapLPBaseApiError extends Error { + errorType: ERROR_TYPE; + title: String; + + constructor(title: String, message: string, errorType: ERROR_TYPE) { + super(message); + this.errorType = errorType; + this.title = title; + } +} + +class RuleCategoryError extends RapLPBaseApiError { + constructor(message: string) { + super('Rule Category Error', message, ERROR_TYPE.BAD_REQUEST); + } +} + +// Express.js middleware to map Extended + +const errorHandler = (err: any, req: Request, res: Response, next: NextFunction) => { + +// SpecParseError --> 400 adapter impl +if (err instanceof SpecParseError) { + const problemDetails = new ProblemDetailsDTO({ + type: 'https://rap-lp./problems/spec-parse-error', + title: 'Okänt fel vid parsning av API-specifikationen', + status: ERROR_TYPE.BAD_REQUEST, + detail: err.message, + instance: req.originalUrl, + + //Extra fields + kind: 'spec-parse', + line: err.line, + column: err.column, + format: err.source, + }); + + return res.status(ERROR_TYPE.BAD_REQUEST).send(problemDetails); +} + const status = err.errorType || err.status || ERROR_TYPE.INTERNAL_SERVER_ERROR; + const title = err.title || 'An unexpected error occurred'; + const detail = err.message || 'An unknown error occurred.'; + + const problemDetails = new ProblemDetailsDTO({ + status, + title, + detail, + instance: req.originalUrl, + }); + + res.status(status).send(problemDetails); +}; + +export enum ERROR_TYPE { + BAD_REQUEST = 400, + CONFLICT = 409, + INTERNAL_SERVER_ERROR = 500, +} + +export { errorHandler, RapLPBaseApiError, RuleCategoryError }; diff --git a/src/util/RapLPCustomSpectral.ts b/src/util/RapLPCustomSpectral.ts index 3dae14f0..15582a37 100644 --- a/src/util/RapLPCustomSpectral.ts +++ b/src/util/RapLPCustomSpectral.ts @@ -6,7 +6,8 @@ import * as SpectralCore from '@stoplight/spectral-core'; import { ISpectralDiagnostic } from '@stoplight/spectral-core'; import spectralCore from '@stoplight/spectral-core'; const { Spectral, Document } = spectralCore; -import { RapLPCustomSpectralDiagnostic} from './RapLPCustomSpectralDiagnostic.js'; +import { RapLPCustomSpectralDiagnostic } from './RapLPCustomSpectralDiagnostic.js'; +import { buildRuleHelpUrl } from '../rulesets/util/rules-doc.config.js'; class RapLPCustomSpectral { private spectral: SpectralCore.Spectral; @@ -49,9 +50,12 @@ class RapLPCustomSpectral { if (ruleClass && typeof ruleClass.getCustomProperties === 'function') { // Check for existance const customProperties = ruleClass.getCustomProperties; + const ruleId = ruleClass.customProperties.id; + const customResult: RapLPCustomSpectralDiagnostic = { - id: ruleClass.customProperties.id, + id: ruleId, område: ruleClass.customProperties.område, + helpUrl: ruleId ? buildRuleHelpUrl(ruleId) : undefined, ...customProperties, // For more copy ...this.mapResultToCustom(result), }; @@ -66,6 +70,7 @@ class RapLPCustomSpectral { private mapResultToCustom(result: ISpectralDiagnostic): RapLPCustomSpectralDiagnostic { // Map properties from result ISpectralDiagnostic to CustomSpectralDiagnostic const { message, code, severity, path, source, range, ...rest } = result; + // Map severity to corresponding string value for allvarlighetsgrad let allvarlighetsgrad: string; switch (severity) { @@ -93,7 +98,6 @@ class RapLPCustomSpectral { omfattning: range, }; } - } export { RapLPCustomSpectral }; /** @@ -103,5 +107,3 @@ export { RapLPCustomSpectral }; interface EnabledRules { rules: Record; } - - diff --git a/src/util/RapLPCustomSpectralDiagnostic.ts b/src/util/RapLPCustomSpectralDiagnostic.ts index e074b54a..f1d7e346 100644 --- a/src/util/RapLPCustomSpectralDiagnostic.ts +++ b/src/util/RapLPCustomSpectralDiagnostic.ts @@ -16,6 +16,9 @@ export interface RapLPCustomSpectralDiagnostic allvarlighetsgrad?: string; sökväg?: any; omfattning?: any; + + /**Helper Url for guidelines */ + helpUrl?: string; } diff --git a/src/util/RapLPDiagnostic.ts b/src/util/RapLPDiagnostic.ts index b4613d87..af18fd2a 100644 --- a/src/util/RapLPDiagnostic.ts +++ b/src/util/RapLPDiagnostic.ts @@ -2,8 +2,8 @@ // // SPDX-License-Identifier: EUPL-1.2 -import { ruleExecutionStatus, RuleExecutionLog, ruleExecutionLogDictionary } from './RuleExecutionStatusModule.js'; import { RapLPCustomSpectralDiagnostic } from './RapLPCustomSpectralDiagnostic.js'; +import { RuleExecutionLog, RuleExecutionContext } from './RuleExecutionContext.js'; class RapLPDiagnostic { private _ruleSets: DiagnosticRuleinfoSet = { @@ -15,28 +15,30 @@ class RapLPDiagnostic { public get diagnosticInformation(): DiagnosticRuleinfoSet { return this._ruleSets; } - constructor() {} + constructor(private context: RuleExecutionContext) {} processRuleExecutionInformation( raplpCustomResult: RapLPCustomSpectralDiagnostic[], + rules: Record, instanceCategoryMap: Map, ): void { - this.processRuleExecutionLog(ruleExecutionLogDictionary, raplpCustomResult, instanceCategoryMap); + this.processRuleExecutionLog(this.context.ruleExecutionLogDictionary, raplpCustomResult, instanceCategoryMap,rules); } private processRuleExecutionLog( log: RuleExecutionLog, spectralResults: RapLPCustomSpectralDiagnostic[], instanceCategoryMap: Map, + rules: Record, ) { let executedRuleIds = new Set(); // Set to track executed rule IDs let executedRuleIdsWithError = new Set(); // Set to track executed rule IDs with error let ruleIdsNotApplicable = new Set(); // Set to track rules that are not applicable, that is the (Δ) between the two above sets - + const ruleIdToMessage = new Map(); + for (const key in log) { const rules = log[key]; const { moduleName, className } = rules[0]; // Get module and class name from the first entry - //console.log(`Rule execution status for ${moduleName}:${className}:`); - + rules.forEach((rule) => { const { customProperties, severity, passed, targetVal } = rule; const status = passed ? 'PASSED' : 'FAILED'; @@ -52,6 +54,7 @@ class RapLPDiagnostic { this._ruleSets.executedUniqueRulesWithError.push({ id: customProperties.id, // Store some more diagnostic info (Duplicate NOT OK) område: customProperties.område, + krav: rules[key]?.message ?? '', }); } } @@ -64,6 +67,7 @@ class RapLPDiagnostic { this._ruleSets.executedUniqueRules.push({ id: customProperties.id, // Store some more diagnostic info (Duplicate OK) område: customProperties.område, + krav:rules[key]?.message ?? '', }); } executedRuleIds.add(customProperties.id); // Store current ID of rule with NO error @@ -78,7 +82,7 @@ class RapLPDiagnostic { }); if (!ruleIdsNotApplicable.has(customProperties.id) && !exists) { // If not present, store the id and område in the not applicableRules - this._ruleSets.notApplicableRules.push({ id: customProperties.id, område: customProperties.område }); // Rules + this._ruleSets.notApplicableRules.push({ id: customProperties.id, område: customProperties.område, krav: rules[key]?.message ?? ''}); // Rules } } } @@ -94,7 +98,7 @@ class RapLPDiagnostic { 'N/A', 'Godkända regler - RAP-LP', ), - ); + ); } if ( this.diagnosticInformation.executedUniqueRulesWithError && @@ -156,6 +160,7 @@ interface DiagnosticRuleinfoSet { interface DiagnosticRuleInfo { id: string; område: string; + krav: string; } interface PopulatedDiagnosticRuleInfo extends DiagnosticRuleInfo { status: string; diff --git a/src/util/RapLPIssueHelpers.ts b/src/util/RapLPIssueHelpers.ts index e7da18a2..6d721687 100644 --- a/src/util/RapLPIssueHelpers.ts +++ b/src/util/RapLPIssueHelpers.ts @@ -3,11 +3,11 @@ // SPDX-License-Identifier: EUPL-1.2 import type * as SpectralCore from '@stoplight/spectral-core'; - +import { Issue } from './Issue.js'; /** * Issue type to use from client to validate parsing */ -export type Issue = { +/*export type Issue = { type: 'Structural' | 'Semantic' | 'Info' | string; code?: string | number; // ex 'path-params' (från Spectral) path: string; // ex "paths./pets.get.responses.200" @@ -18,7 +18,7 @@ export type Issue = { documentationUrl?: string;// optional link raw?: string[]; // original lines that created this issue (optional) details?: string[]; // extra details (optional) -}; +};*/ /** * Extract jumplines * @param prettyLines diff --git a/src/util/RuleExecutionContext.ts b/src/util/RuleExecutionContext.ts new file mode 100644 index 00000000..41751227 --- /dev/null +++ b/src/util/RuleExecutionContext.ts @@ -0,0 +1,62 @@ +// SPDX-FileCopyrightText: 2026 Digg - Agency for Digital Government +// +// SPDX-License-Identifier: EUPL-1.2 + +import { CustomProperties } from '../ruleinterface/CustomProperties.js'; + +interface RuleExecutionInfo { + moduleName: string; + className: string; + customProperties: CustomProperties; + severity: string; + passed: boolean; + targetVal: string; +} + +export interface RuleExecutionLog { + [key: string]: RuleExecutionInfo[]; +} +/** + * + */ +export class RuleExecutionContext { + public ruleExecutionLogDictionary: RuleExecutionLog = {}; + // Define an object to store rule execution status + public ruleExecutionStatus: Record = {}; + +// Function to register module and class information + registerRuleExecutionStatus( + moduleName: string, + className: string, + customProperties: CustomProperties, + severity: string, + ) { + const key = `${moduleName}:${customProperties.id}:${customProperties.område}:${severity}`; + this.ruleExecutionStatus[key] = true; + } + +// Function to register module and class information + logRuleExecution( + moduleName: string, + className: string, + customProperties: CustomProperties, + severity: string, + passed: boolean, + targetVal: string, + ) { + const key = `${moduleName}:${customProperties.id}:${customProperties.område}:${severity}`; + + if (!this.ruleExecutionLogDictionary[key]) { + this.ruleExecutionLogDictionary[key] = []; + } + + this.ruleExecutionLogDictionary[key].push({ + moduleName, + className, + customProperties, + severity, + passed, + targetVal, + }); + } +} \ No newline at end of file diff --git a/src/util/RuleExecutionStatusModule.ts b/src/util/RuleExecutionStatusModule.ts deleted file mode 100644 index 6ea70644..00000000 --- a/src/util/RuleExecutionStatusModule.ts +++ /dev/null @@ -1,49 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Digg - Agency for Digital Government -// -// SPDX-License-Identifier: EUPL-1.2 - -import { CustomProperties } from '../ruleinterface/CustomProperties.js'; - -interface RuleExecutionInfo { - moduleName: string; - className: string; - customProperties: CustomProperties; - severity: string; - passed: boolean; - targetVal: string; -} - -export interface RuleExecutionLog { - [key: string]: RuleExecutionInfo[]; -} -export const ruleExecutionLogDictionary: RuleExecutionLog = {}; - -// Define an object to store rule execution status -export const ruleExecutionStatus: Record = {}; - -// Function to register module and class information -export function registerRuleExecutionStatus( - moduleName: string, - className: string, - customProperties: CustomProperties, - severity: string, -) { - const key = `${moduleName}:${customProperties.id}:${customProperties.område}:${severity}`; - ruleExecutionStatus[key] = true; -} -export function logRuleExecution( - moduleName: string, - className: string, - customProperties: CustomProperties, - severity: string, - passed: boolean, - targetVal: string, -) { - const key = `${moduleName}:${customProperties.id}:${customProperties.område}:${severity}`; - - if (!ruleExecutionLogDictionary[key]) { - ruleExecutionLogDictionary[key] = []; - } - - ruleExecutionLogDictionary[key].push({ moduleName, className, customProperties, severity, passed, targetVal }); -} diff --git a/src/util/apiUtil.ts b/src/util/apiUtil.ts new file mode 100644 index 00000000..08f92c0c --- /dev/null +++ b/src/util/apiUtil.ts @@ -0,0 +1,64 @@ +// SPDX-FileCopyrightText: 2025 Digg - Agency for Digital Government +// +// SPDX-License-Identifier: EUPL-1.2 + +import { RapLPCustomSpectral } from './RapLPCustomSpectral.js'; +import { Document } from '@stoplight/spectral-core'; +import Parsers from '@stoplight/spectral-parsers'; +import { ERROR_TYPE, RapLPBaseApiError } from './RapLPBaseApiErrorHandling.js'; +import { DiagnosticReport, RapLPDiagnostic } from '../util/RapLPDiagnostic.js'; +import yaml from 'js-yaml'; +import { ValidationResponseDto } from '../model/ValidationResponseDto.js'; +import { RuleExecutionContext } from './RuleExecutionContext.js'; + +export const validateYamlInput = (input: string): input is string => { + try { + //Parse the yaml to verify + yaml.load(input); + } catch (e) { + // Handle YAML parsing error + throw new RapLPBaseApiError('Could not validate Yaml', 'Invalid YAML', ERROR_TYPE.BAD_REQUEST); + } + + return true; +}; + +export function decodeBase64String(base64YamlFile: string) { + // Import the necessary Node.js module (Buffer is built-in) + const atob = (b64String: string): string => Buffer.from(b64String, 'base64').toString('utf-8'); + + // Decode the base64 string + const decodedYaml = atob(base64YamlFile); + + return decodedYaml; +} + +export async function processApiSpec( + context: RuleExecutionContext, + enabledRulesAndCategorys: { + rules: Record; + instanceCategoryMap: Map; + }, + apiSpecDocument: Document>, +): Promise { + const customSpectral = new RapLPCustomSpectral(); + customSpectral.setCategorys(enabledRulesAndCategorys.instanceCategoryMap); + customSpectral.setRuleset(enabledRulesAndCategorys.rules); + const result = await customSpectral.run(apiSpecDocument); + + + const customDiagnostic = new RapLPDiagnostic(context); + customDiagnostic.processRuleExecutionInformation(result, enabledRulesAndCategorys.rules,enabledRulesAndCategorys.instanceCategoryMap); + const diagnosticReports: DiagnosticReport[] = customDiagnostic.processDiagnosticInformation(); + return { result, report: diagnosticReports }; +} + +/** + * https://oida.dev/typescript-hasownproperty/ + * @param obj Object to check + * @param prop Property to check for + * @returns Boolean + */ +export function hasOwnProperty(obj: X, prop: Y): obj is X & Record { + return obj.hasOwnProperty(prop); +} diff --git a/src/util/excelReportProcessor.ts b/src/util/excelReportProcessor.ts index 441034d0..01bce044 100644 --- a/src/util/excelReportProcessor.ts +++ b/src/util/excelReportProcessor.ts @@ -116,6 +116,22 @@ export class ExcelReportProcessor { this.persistUpdates(this.config.outputFilePath); } + public generateReportDocumentBuffer(result: RapLPDiagnostic): Buffer { + try { + this.generateReportDocument(result); + let reportDocumentBuffer = this.zip.toBuffer(); + + if (!reportDocumentBuffer || reportDocumentBuffer.length === 0) { + throw new Error('Generated buffer is empty or invalid.'); + } + + return reportDocumentBuffer; + } catch (error) { + console.error('Error generating report document buffer:', error); + throw new Error('Failed to generate the report document buffer.'); + } + } + /** * Utility function to map the Diagnostic report into basic components. * A map with the rule name as Key and the reported status as Value. diff --git a/src/util/ruleUtil.ts b/src/util/ruleUtil.ts index beb1cb62..908271b1 100644 --- a/src/util/ruleUtil.ts +++ b/src/util/ruleUtil.ts @@ -2,6 +2,9 @@ // // SPDX-License-Identifier: EUPL-1.2 +import { RuleCategoryError } from './RapLPBaseApiErrorHandling.js'; +import { RuleExecutionContext } from './RuleExecutionContext.js'; + // ruleUtil.ts interface CustomSchema { id: string; @@ -44,7 +47,7 @@ export function getRuleModules() { * @returns Promise object with enabled rules in RAP-LP to run */ export async function importAndCreateRuleInstances( - ruleCategories?: string[], + context: RuleExecutionContext, ruleCategories?: string[], ): Promise<{ rules: Record; instanceCategoryMap: Map }> { const ruleInstances: Record = {}; // store instances of rule classes const ruleTypes: any[] = []; // array to store rule classes. @@ -96,14 +99,21 @@ export async function importAndCreateRuleInstances( /** * Load modules */ - if (ruleCategories && ruleCategories.length > 0) { - //Check if we gonna load PrerequisetRules or if it is specified - if (!ruleCategories.includes('ForRules')) { - ruleCategories.push('ForRules'); + try { + if (ruleCategories && ruleCategories.length > 0) { + //Check if we gonna load PrerequisetRules or if it is specified + if (!ruleCategories.includes('ForRules')) { + ruleCategories.push('ForRules'); + } + await importRulesByCategory(ruleCategories); + } else { + await importAllRules(); + } + } catch (e) { + if (e instanceof Error) { + throw new RuleCategoryError(e.message); } - await importRulesByCategory(ruleCategories); - } else { - await importAllRules(); + throw e; } /** * Loop entries of instanceCategory map @@ -112,7 +122,7 @@ export async function importAndCreateRuleInstances( // Create instances of rule classes in RAP-LP ruleTypes.forEach((RuleClass) => { try { - const instance = new RuleClass(); + const instance = new RuleClass(context); ruleInstances[RuleClass.name] = instance; instanceCategoryMap.set(RuleClass.name, RuleClass); // Do we have name of ruleClass ? } catch (error: any) { diff --git a/src/util/urlValidationConfig.ts b/src/util/urlValidationConfig.ts new file mode 100644 index 00000000..3174f0ea --- /dev/null +++ b/src/util/urlValidationConfig.ts @@ -0,0 +1,23 @@ +// SPDX-FileCopyrightText: 2025 Digg - Agency for Digital Government +// +// SPDX-License-Identifier: EUPL-1.2 + +import { createRequire } from 'module'; +import path from 'path'; + +const require = createRequire(import.meta.url); + +import { existsSync } from 'fs'; + +export const loadUrlValidationConfiguration = (customFilePath?: string) => { + const configFileToUse = customFilePath ?? path.join(process.cwd(), 'urlValidationConfig.cjs'); + + if (!existsSync(configFileToUse)) { + console.error( + 'Could not find configuration file for urlValidation, consider adding the file or run server without --enableUrlValidation', + ); + throw Error(`Could not find config file ${configFileToUse}`); + } + + return require(configFileToUse); +}; diff --git a/src/util/validateUtil.ts b/src/util/validateUtil.ts index 39bf1769..1c480676 100644 --- a/src/util/validateUtil.ts +++ b/src/util/validateUtil.ts @@ -19,6 +19,8 @@ import Parsers from '@stoplight/spectral-parsers'; import type { IParser } from '@stoplight/spectral-parsers'; import * as IssueHelper from './RapLPIssueHelpers.js'; import { SpecParseError} from './RapLPSpecParseError.js'; +import { Issue } from './Issue.js'; + /** * Possible input types for specifications @@ -40,7 +42,7 @@ export type ParseResult = { format: 'json' | 'yaml'; raw: string; parsed: any; - strictIssues?: IssueHelper.Issue[]; + strictIssues?: Issue[]; } export type ParseOptions = { preferJsonError?: 'auto' | 'never' | 'always'; @@ -76,7 +78,7 @@ export function detectSpecFormatPreference( if (raw) { const t = raw.trim(); - if (t.startsWith('{') || t.startsWith('[')) return 'auto'; + if (t.startsWith('{') || t.startsWith('[')) return 'always'; if (/^\s*<\?xml|^\s*<[\w-]+[\s>]/i.test(t)) return 'never'; } @@ -103,7 +105,7 @@ export async function parseApiSpecInput(input: SpecInput, throw new SpecParseError('Det parsade objektet verkar inte vara en giltig OpenAPI-specifikation.', { source: 'unknown' }); } const serialized = JSON.stringify(parsed, null, 2); // Serialize raw format - let strictIssues: IssueHelper.Issue[] | undefined; + let strictIssues: Issue[] | undefined; if (strict) { try { await runStrictValidationIfRequested(parsed); @@ -180,7 +182,7 @@ export async function parseApiSpecInput(input: SpecInput, } //Make sure parsed specification is OpenAPI like ensureIsOpenApiLike(parsedSpec,target); - let issues: IssueHelper.Issue[] | undefined; + let issues: Issue[] | undefined; let prettyLines: string[] = []; //If here - we have a parsed openapi object if (strict) { diff --git a/tests/integration/api.test.ts b/tests/integration/api.test.ts new file mode 100644 index 00000000..23362ebb --- /dev/null +++ b/tests/integration/api.test.ts @@ -0,0 +1,69 @@ +// SPDX-FileCopyrightText: 2025 Digg - Agency for Digital Government +// +// SPDX-License-Identifier: EUPL-1.2 + +import { readFileSync } from 'fs'; +import { ApiArgs, startServer } from '../../src/api-mode.js'; +import { ValidateApi } from '../generated/apis/index.js'; +import { Configuration } from '../generated/runtime.js'; +import path from 'path'; +import { fileURLToPath } from 'url'; +import { YamlContentDto } from '../../src/model/YamlContentDto.js'; + +// Emulate __dirname in ES Modules +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +/* + * Since we're both writing the openapi specification and the server manually + * we need a way to assure that they are in sync. + * + * This test i dependent on the "pretest" stage in package.json, which will generate a + * typescript client from the specification which we can use to test our server. + */ +describe('API Test', () => { + var api: ValidateApi; + var app: any; + + beforeAll(async () => { + let args: ApiArgs = {}; + app = await startServer(args); + api = new ValidateApi(new Configuration({ basePath: 'http://localhost:3000/api/v1' })); + }); + + it('Assert that the endpoint is returning correct body', async () => { + const data = readFileSync(path.resolve(__dirname, '../../apis/dok-api.yaml')); + + const response = await api.validateContent({ + yamlContentDto: new YamlContentDto(data.toString('base64'), []), + }); + + expect(response?.result?.length).toBe(11); + }); + // TODO: Revisit this test - returns 200 instead of 400 + // it("Assert that the error handler is intercepting faults", async () => { + // const data = readFileSync(path.resolve(__dirname, "../../apis/dok-api.yaml")) + + // const response = await request(app) + // .post("/api/v1/validation/validate") + // .set('Content-Type', 'application/json') + // .send({ + // yaml: data.toString("base64") + "123qwdsdfgaerg39e4rg", + // categories: [] + // }) + + // expect(response.status).toBe(400) + // expect(response.body).toMatchObject({ + // type: "about:blank", + // title: "Could not validate Yaml", + // status: 400, + // detail: "Invalid YAML", + // instance: "/api/v1/validation/validate" + // }) + + // }) + + afterAll(async () => { + app.close(); + }); +}); diff --git a/tests/unit/api/RapLPBaseApiError.test.ts b/tests/unit/api/RapLPBaseApiError.test.ts new file mode 100644 index 00000000..b3aaf15e --- /dev/null +++ b/tests/unit/api/RapLPBaseApiError.test.ts @@ -0,0 +1,99 @@ +// SPDX-FileCopyrightText: 2025 Digg - Agency for Digital Government +// +// SPDX-License-Identifier: EUPL-1.2 + +import { ERROR_TYPE, errorHandler, RapLPBaseApiError } from '../../../src/util/RapLPBaseApiErrorHandling'; +import { ProblemDetailsDTO } from '../../../src/model/ProblemDetailsDto'; +import { Request, Response, NextFunction } from 'express'; +import { ParamsDictionary } from 'express-serve-static-core'; +import { ParsedQs } from 'qs'; + +describe('errorHandler middleware', () => { + let req: Request>; + let res: Response>; + let next: NextFunction; + + beforeEach(async () => { + // Mock the Express req, res, and next + req = { + originalUrl: '/example', + } as Request; + res = { + status: jest.fn().mockReturnThis(), + send: jest.fn(), + } as unknown as Response; + next = jest.fn() as NextFunction; + }); + + it('should handle RapLPBaseApiError and return the correct response - http 400, bad request', () => { + // Create a mock error + const error = new RapLPBaseApiError('Test title', 'Test error', ERROR_TYPE.BAD_REQUEST); + + // Call the error handler + errorHandler(error, req, res, next); + + // Check that res.status was called with the correct status code + expect(res.status).toHaveBeenCalledWith(400); + + // Access the response object passed to res.send + const sendMock = res.send as jest.Mock; + const sentResponse = sendMock.mock.calls[0][0] as ProblemDetailsDTO; + + // Validate the structure and content of the sent response + expect(sentResponse).toMatchObject({ + type: 'about:blank', + title: 'Test title', + status: 400, + detail: 'Test error', + instance: '/example', + }); + }); + + it('should handle RapLPBaseApiError and return the correct response - http 500, internal server error', () => { + // Create a mock error + const error = new RapLPBaseApiError('Test title', 'Test error', ERROR_TYPE.INTERNAL_SERVER_ERROR); + + // Call the error handler + errorHandler(error, req, res, next); + + // Check that res.status was called with the correct status code + expect(res.status).toHaveBeenCalledWith(500); + + // Access the response object passed to res.send + const sendMock = res.send as jest.Mock; + const sentResponse = sendMock.mock.calls[0][0] as ProblemDetailsDTO; + + // Validate the structure and content of the sent response + expect(sentResponse).toMatchObject({ + type: 'about:blank', + title: 'Test title', + status: 500, + detail: 'Test error', + instance: '/example', + }); + }); + + it('should default to status code 500 if error is not RapLPBaseApiError', () => { + // Create a mock error without a status code + const error = new Error('Internal server error'); + + // Call the error handler + errorHandler(error, req, res, next); + + // Check that res.status was called with 500 + expect(res.status).toHaveBeenCalledWith(500); + + // Access the response object passed to res.send + const sendMock = res.send as jest.Mock; + const sentResponse = sendMock.mock.calls[0][0] as ProblemDetailsDTO; + + // Validate the structure and content of the sent response + expect(sentResponse).toMatchObject({ + type: 'about:blank', + title: 'An unexpected error occurred', + status: 500, + detail: 'Internal server error', + instance: '/example', + }); + }); +}); diff --git a/tests/unit/api/endpointValidation.test.ts b/tests/unit/api/endpointValidation.test.ts new file mode 100644 index 00000000..70a653d7 --- /dev/null +++ b/tests/unit/api/endpointValidation.test.ts @@ -0,0 +1,195 @@ +// SPDX-FileCopyrightText: 2025 Digg - Agency for Digital Government +// +// SPDX-License-Identifier: EUPL-1.2 + +/** +* CONTRACT TESTS for /api/v1/validation/validatespec +* - NOTE: +* - The router is FULLY mocked +* - No real imports is to validateUtil / Spectral +* - This only tests HTTP + JSON contracts + */ + +import request from 'supertest'; +import express from 'express'; + +/** + * Mocked router – interprets base64 + strict + */ +jest.mock('../../../src/routes/validate.js', () => ({ + registerValidationRoutes: (app: any) => { + app.post('/api/v1/validation/validatespec', (req, res) => { + const { spec, strict } = req.body; + + let raw = ''; + try { + raw = Buffer.from(spec, 'base64').toString('utf8'); + } catch { + return res.status(400).json({ + type: 'about:blank', + title: 'Specification parse error', + status: 400, + kind: 'spec-parse', + }); + } + + // Parser error (broken YAML) + if (raw.includes(':::')) { + return res.status(400).json({ + type: 'about:blank', + title: 'Specification parse error', + status: 400, + detail: 'Unexpected token', + kind: 'spec-parse', + line: 3, + column: 5, + }); + } + + // Strict validation (missing info.version) + if (strict && raw.includes('info:') && !raw.includes('version:')) { + return res.status(400).json({ + type: 'about:blank', + title: 'Specification validation failed', + status: 400, + kind: 'spec-validation', + issues: [ + { + type: 'Semantic', + path: 'info', + message: 'Missing required property version', + line: 2, + }, + ], + }); + } + const invalidPathsScalar = + /paths:\s*\n\s*\/\s*(\n|$)/m.test(raw); + + // Violation (synthetic) + if (invalidPathsScalar) { + if (strict) { + return res.status(400).json({ + type: 'about:blank', + title: 'Rule validation failed', + status: 400, + kind: 'rule-validation', + payload: { + result: [{ id: 'FOR.02', område: 'Förutsättningar', allvarlighetsgrad: 'ERROR' }], + report: [{"Notering":"Ej Godkända regler - RAP-LP","regler":[{"id":"FOR.02","område":"Förutsättningar","status":"EJ OK"}]}], + }, + }); + } + + // strict=false + return res.status(200).json({ + ok: true, + payload: { + report: [{"Notering":"Godkända regler - RAP-LP","regler":[{"id":"FOR.02","område":"Förutsättningar","status":"OK"}]}], + }, + }); + } + + // Fully ok + return res.status(200).json({ + ok: true, + payload: { + result: [], + report: [{ summary: 'All rules passed' }], + }, + }); + }); + }, +})); + +//Import routes +import { registerValidationRoutes } from '../../../src/routes/validate.js'; + +describe('POST /api/v1/validation/validatespec – contract check', () => { + let app: express.Express; + + beforeEach(() => { + app = express(); + app.use(express.json()); + registerValidationRoutes(app); + }); + + const encode = (s: string) => Buffer.from(s).toString('base64'); + + it('200 – valid spec, no rule violations', async () => { + const yaml = ` +openapi: 3.0.0 +info: + version: 1.0.0 + title: ok +paths: {} +`; + const res = await request(app) + .post('/api/v1/validation/validatespec') + .send({ spec: encode(yaml), strict: true }); + + expect(res.status).toBe(200); + expect(res.body.ok).toBe(true); + }); + + it('400 – spec-parse error', async () => { + //Broken yaml + const yaml = `openapi: ::: broken`; + const res = await request(app) + .post('/api/v1/validation/validatespec') + .send({ spec: encode(yaml), strict: true }); + + expect(res.status).toBe(400); + expect(res.body.kind).toBe('spec-parse'); + }); + + it('400 – strict validation error', async () => { + //No version in YAML + const yaml = ` +openapi: 3.0.0 +info: + title: missing version +paths: {} +`; + const res = await request(app) + .post('/api/v1/validation/validatespec') + .send({ spec: encode(yaml), strict: true }); + + expect(res.status).toBe(400); + expect(res.body.kind).toBe('spec-validation'); + expect(res.body.issues[0].path).toBe('info'); + }); + + it('400 – rule validation error', async () => { + const yaml = ` +openapi: 3.0.0 +info: + version: 1.0.0 +paths: + / +`; + const res = await request(app) + .post('/api/v1/validation/validatespec') + .send({ spec: encode(yaml), strict: true }); + + expect(res.status).toBe(400); + expect(res.body.kind).toBe('rule-validation'); + }); + + it('200 – strict=false tillåter regelbrott', async () => { + const yaml = ` +openapi: 3.0.0 +info: + version: 1.0.0 +paths: + / +`; + const res = await request(app) + .post('/api/v1/validation/validatespec') + .send({ spec: encode(yaml), strict: false }); + + expect(res.status).toBe(200); + expect(res.body.ok).toBe(true); + }); +}); + diff --git a/tests/unit/buildRuleHelpUrl.test.ts b/tests/unit/buildRuleHelpUrl.test.ts new file mode 100644 index 00000000..0a8552d2 --- /dev/null +++ b/tests/unit/buildRuleHelpUrl.test.ts @@ -0,0 +1,47 @@ +// SPDX-FileCopyrightText: 2025 Digg - Agency for Digital Government +// +// SPDX-License-Identifier: EUPL-1.2 + +import { buildRuleHelpUrl } from '../../src/rulesets/util/rules-doc.config.js'; + +describe('buildRuleHelpUrl', () => { + const ORIGINAL_ENV = process.env; + + beforeEach(() => { + process.env = { ...ORIGINAL_ENV }; + }); + + afterAll(() => { + process.env = ORIGINAL_ENV; + }); + + it('bygger korrekt GitHub-länk för regel-ID med punkt (SAK.16)', () => { + process.env.RULES_DOC_REF = 'main'; + + const url = buildRuleHelpUrl('SAK.16'); + + expect(url).toBe( + 'https://github.com/diggsweden/rest-api-profil-lint-processor/blob/main/GUIDELINES.md#id-sak16' + ); + }); + + it('faller tillbaka till main om RULES_DOC_REF saknas', () => { + delete process.env.RULES_DOC_REF; + + const url = buildRuleHelpUrl('FOR.02'); + + expect(url).toBe( + 'https://github.com/diggsweden/rest-api-profil-lint-processor/blob/main/GUIDELINES.md#id-for02' + ); + }); + + it('hanterar redan normaliserade ID:n korrekt', () => { + process.env.RULES_DOC_REF = 'feature/test'; + + const url = buildRuleHelpUrl('sak16'); + + expect(url).toBe( + 'https://github.com/diggsweden/rest-api-profil-lint-processor/blob/feature/test/GUIDELINES.md#id-sak16' + ); + }); +}); diff --git a/tests/unit/dok.test.ts b/tests/unit/dok.test.ts index 571559d4..60435b08 100644 --- a/tests/unit/dok.test.ts +++ b/tests/unit/dok.test.ts @@ -446,9 +446,7 @@ testRule('Dok17', [ }, errors: [ { - message: - 'API specifikation BÖR dokumenteras med den senaste versionen av OpenAPI Specification. ( Linter-analysverktyget (RAP-LP) för den nationella REST API-profilen är designat för senaste major versionen av OpenAPI Specification. Använd därför denna för full täckning av de implementerade reglerna. )', - //message: "AAA", + message: 'API specifikation BÖR dokumenteras med den senaste versionen av OpenAPI Specification. ( Linter-analysverktyget (RAP-LP) för den nationella REST API-profilen är designat för senaste major versionen av OpenAPI Specification. Använd därför denna för full täckning av de implementerade reglerna. )', path: ['swagger'], severity: DiagnosticSeverity.Warning, }, diff --git a/tests/unit/issueHelper.test.ts b/tests/unit/issueHelper.test.ts index dba0d28b..f6e6236a 100644 --- a/tests/unit/issueHelper.test.ts +++ b/tests/unit/issueHelper.test.ts @@ -2,7 +2,8 @@ // // SPDX-License-Identifier: EUPL-1.2 -import { Issue, sortIssues,consolidateIssues } from '../../src/util/RapLPIssueHelpers.js'; +import { Issue } from '../../src/util/Issue.js'; +import {sortIssues,consolidateIssues } from '../../src/util/RapLPIssueHelpers.js'; import { spectralDiagnosticsToIssuesSimple } from '../../src/util/RapLPIssueHelpers.js'; import type * as SpectralCore from '@stoplight/spectral-core'; describe('IssueHelper.sortIssues', ()=> { diff --git a/tests/unit/parseApiSpecInput.integration.test.ts b/tests/unit/parseApiSpecInput.integration.test.ts index 07a480b1..ba19ffd8 100644 --- a/tests/unit/parseApiSpecInput.integration.test.ts +++ b/tests/unit/parseApiSpecInput.integration.test.ts @@ -6,7 +6,6 @@ import { execFile } from 'child_process'; import { promisify } from 'util'; import path from 'path'; import fs from 'fs'; -import { strict } from 'yargs'; const execFileP = promisify(execFile); diff --git a/tests/util/rulesetTest.ts b/tests/util/rulesetTest.ts index f149c02c..dcdbfa80 100644 --- a/tests/util/rulesetTest.ts +++ b/tests/util/rulesetTest.ts @@ -19,6 +19,8 @@ import * as FelRules from '../../src/rulesets/FelRules.js'; import * as ResRules from '../../src/rulesets/ResRules.js'; import * as MogRules from '../../src/rulesets/MogRules.js'; +import { RuleExecutionContext } from '../../src/util/RuleExecutionContext.js'; + const ruleInstances: Record = {}; /** @@ -89,8 +91,9 @@ const ruleTypes = [ MogRules.Mog01, MogRules.Mog02, ]; +const context = new RuleExecutionContext(); ruleTypes.forEach((RuleClass) => { - const instance = new RuleClass(); + const instance = new RuleClass(context); ruleInstances[RuleClass.name] = instance; }); diff --git a/urlValidationConfig.cjs b/urlValidationConfig.cjs new file mode 100644 index 00000000..a721af4c --- /dev/null +++ b/urlValidationConfig.cjs @@ -0,0 +1,13 @@ +// SPDX-FileCopyrightText: 2025 Digg - Agency for Digital Government +// +// SPDX-License-Identifier: EUPL-1.2 + +module.exports = { + // Whitelisted urls, the requested url must match this regex. + urlMatchRegex: /.*/, + + // Exposure of fetch api, request init configuration. + // Can be used to customize request headers, method etc. + // https://developer.mozilla.org/en-US/docs/Web/API/RequestInit + fetchRequestInit: {}, +};