diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml new file mode 100644 index 0000000..be0bac4 --- /dev/null +++ b/.github/workflows/integration-tests.yml @@ -0,0 +1,60 @@ +name: Run Integration Tests + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + integration-tests: + runs-on: ubuntu-latest + strategy: + matrix: + node-version: [18.x] + services: + registry: + image: registry:2 + ports: + - 5000:5000 + steps: + - name: Check out the repo + uses: actions/checkout@v4 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + id: buildx + uses: docker/setup-buildx-action@v3 + with: + driver-opts: network=host + + - name: Copy patches for Docker Buildx + run: cp -r patches/* packages/graphql-mesh/patches + + - name: Build and push on local registry + id: docker_build + uses: docker/build-push-action@v5 + with: + context: ./packages/graphql-mesh + push: true + tags: localhost:5000/test/graphql-mesh:latest + platforms: linux/amd64 + + - name: Setup services for testing purpose + run: export IMAGE_TAG=localhost:5000/test/graphql-mesh:latest && cd ./test/integration && docker compose up -d + + - name: Wait for services to be ready + run: sleep 30 + + - name: Set up Node.js + uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node-version }} + + - name: Install dependencies + run: cd ./test/integration/tests && npm install + + - name: Run tests + run: cd ./test/integration/tests && npm test diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index 2dea47d..49ac0f0 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -15,8 +15,6 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v3 - with: - node-version: ${{ matrix.node-version }} - name: Set up Node.js uses: actions/setup-node@v3 diff --git a/packages/directive-headers/src/index.ts b/packages/directive-headers/src/index.ts index cab6ce1..42d1433 100644 --- a/packages/directive-headers/src/index.ts +++ b/packages/directive-headers/src/index.ts @@ -56,3 +56,15 @@ export default class HeadersDirectiveTransform implements MeshTransform { }) } } + +export const headersDirectiveTypeDef: string = /* GraphQL */ ` + input Header { + key: String + value: String + } + + """ + This directive is used to add headers to the request. + """ + directive @headers(input: [Header]) on FIELD +` diff --git a/packages/directive-no-auth/src/index.ts b/packages/directive-no-auth/src/index.ts index 51d4808..c6b84ca 100644 --- a/packages/directive-no-auth/src/index.ts +++ b/packages/directive-no-auth/src/index.ts @@ -50,3 +50,10 @@ export default class NoAuthDirectiveTransform implements MeshTransform { }) } } + +export const noAuthDirectiveTypeDef: string = /* GraphQL */ ` + """ + This directive is used to disable the authorization header for the request + """ + directive @noAuth on FIELD +` diff --git a/packages/directive-spl/package.json b/packages/directive-spl/package.json index 523197a..b874f8e 100644 --- a/packages/directive-spl/package.json +++ b/packages/directive-spl/package.json @@ -19,7 +19,6 @@ "build:cjs": "tsc --project tsconfig-cjs.json", "build:esm": "tsc --project tsconfig-esm.json", "build": "rm -rf _build && npm run build:esm && npm run build:cjs && node ./scripts/prepare-package-json", - "dev": "vite", "pack": "npm run build && npm pack --pack-destination ../graphql-mesh/local-pkg", "test": "vitest" }, diff --git a/packages/directive-spl/src/index.ts b/packages/directive-spl/src/index.ts index 400455d..4d26017 100644 --- a/packages/directive-spl/src/index.ts +++ b/packages/directive-spl/src/index.ts @@ -52,3 +52,10 @@ export default class SplDirectiveTransform implements MeshTransform { }) } } + +export const splDirectiveTypeDef: string = /* GraphQL */ ` + """ + This is a very small, lightweight, straightforward and non-evaluated expression language to sort, filter and paginate arrays of maps. + """ + directive @SPL(query: String) on FIELD +` diff --git a/packages/directive-spl/vite.config.ts b/packages/directive-spl/vite.config.ts index 2abcb3e..0324084 100644 --- a/packages/directive-spl/vite.config.ts +++ b/packages/directive-spl/vite.config.ts @@ -1,16 +1,4 @@ /// // Configure Vitest (https://vitest.dev/config/) -import { resolve } from 'path' import { defineConfig } from 'vite' -import dts from 'vite-plugin-dts' -// https://vitejs.dev/guide/build.html#library-mode -export default defineConfig({ - build: { - lib: { - entry: resolve(__dirname, 'src/index.ts'), - name: 'my-lib', - fileName: 'my-lib' - } - }, - plugins: [dts()] -}) +export default defineConfig({}) diff --git a/packages/graphql-mesh/.meshrc.ts b/packages/graphql-mesh/.meshrc.ts index 21ada87..2784d42 100644 --- a/packages/graphql-mesh/.meshrc.ts +++ b/packages/graphql-mesh/.meshrc.ts @@ -1,5 +1,8 @@ import { YamlConfig } from '@graphql-mesh/types' import ConfigFromSwaggers from './utils/ConfigFromSwaggers' +import { splDirectiveTypeDef } from 'directive-spl' +import { headersDirectiveTypeDef } from 'directive-headers' +import { noAuthDirectiveTypeDef } from 'directive-no-auth' const configFromSwaggers = new ConfigFromSwaggers() const { defaultConfig, additionalTypeDefs, sources } = @@ -17,13 +20,19 @@ const config = { } }, ...(defaultConfig.transforms || []) - ], + ].filter(Boolean), sources: [...sources], - additionalTypeDefs: [defaultConfig.additionalTypeDefs || '', ...additionalTypeDefs], + additionalTypeDefs: [ + splDirectiveTypeDef, + headersDirectiveTypeDef, + noAuthDirectiveTypeDef, + additionalTypeDefs, + defaultConfig.additionalTypeDefs || '' + ].filter(Boolean), additionalResolvers: [ ...(defaultConfig.additionalResolvers || []), './utils/additionalResolvers.ts' - ] + ].filter(Boolean) } export default config diff --git a/packages/graphql-mesh/Dockerfile b/packages/graphql-mesh/Dockerfile index 276d6f2..e2a7129 100644 --- a/packages/graphql-mesh/Dockerfile +++ b/packages/graphql-mesh/Dockerfile @@ -31,5 +31,6 @@ VOLUME /app/sources VOLUME /app/config.yaml VOLUME /app/transforms VOLUME /app/plugins +VOLUME /app/resolvers CMD [ "npm", "run", "serve" ] diff --git a/packages/graphql-mesh/local-pkg/directive-headers-1.0.0.tgz b/packages/graphql-mesh/local-pkg/directive-headers-1.0.0.tgz index 4618084..e0ed99a 100644 Binary files a/packages/graphql-mesh/local-pkg/directive-headers-1.0.0.tgz and b/packages/graphql-mesh/local-pkg/directive-headers-1.0.0.tgz differ diff --git a/packages/graphql-mesh/local-pkg/directive-no-auth-1.0.0.tgz b/packages/graphql-mesh/local-pkg/directive-no-auth-1.0.0.tgz index 7667a24..23c6179 100644 Binary files a/packages/graphql-mesh/local-pkg/directive-no-auth-1.0.0.tgz and b/packages/graphql-mesh/local-pkg/directive-no-auth-1.0.0.tgz differ diff --git a/packages/graphql-mesh/local-pkg/directive-spl-1.0.0.tgz b/packages/graphql-mesh/local-pkg/directive-spl-1.0.0.tgz index 4c91b36..506efb6 100644 Binary files a/packages/graphql-mesh/local-pkg/directive-spl-1.0.0.tgz and b/packages/graphql-mesh/local-pkg/directive-spl-1.0.0.tgz differ diff --git a/packages/graphql-mesh/local-pkg/inject-additional-transforms-1.0.0.tgz b/packages/graphql-mesh/local-pkg/inject-additional-transforms-1.0.0.tgz index 0c456c0..93b007f 100644 Binary files a/packages/graphql-mesh/local-pkg/inject-additional-transforms-1.0.0.tgz and b/packages/graphql-mesh/local-pkg/inject-additional-transforms-1.0.0.tgz differ diff --git a/packages/graphql-mesh/scripts/download-sources.ts b/packages/graphql-mesh/scripts/download-sources.ts index 31bad97..89a975b 100644 --- a/packages/graphql-mesh/scripts/download-sources.ts +++ b/packages/graphql-mesh/scripts/download-sources.ts @@ -10,20 +10,34 @@ let config = getConfig() const sources = config?.sources?.filter((source) => source?.handler?.openapi) || [] const swaggers = sources.map((source) => source?.handler?.openapi?.source) || [] +/** + * Get the name of generated from the source object + * @param {Record} source + * @returns {string | undefined} + */ +const getFileName = (url: string): string | undefined => { + return sources.find((source) => source?.handler?.openapi?.source === url)?.name +} + /** * Download the swagger from the given URL and save it to the sources folder * @param {string} url */ -const downSwaggerFromUrl = async (url: string): Promise => { +const downSwaggerFromUrl = async (url: string | undefined, index: string): Promise => { + if (!url) return Promise.resolve() try { const content: Record = await readFileOrUrl(url, { allowUnknownExtensions: true, cwd: '.', fetch: fetch, - importFn: null, + importFn: (mod) => import(mod), logger: logger }) - const fileName = url.split('/').pop() + let fileName = getFileName(url) || `${index}-${url.split('/').pop()}` + if (!fileName.endsWith('.json')) { + fileName += '.json' + } + if (fileName) { const filePath = `./sources/${fileName}` writeFileSync(filePath, JSON.stringify(content, null, 2), 'utf8') @@ -37,7 +51,7 @@ const downSwaggerFromUrl = async (url: string): Promise => { * Download all the swaggers from the given URLs * @param {string[]} swaggers */ -const downloadSwaggers = (swaggers: string[]) => { +const downloadSwaggers = (swaggers: (string | undefined)[]) => { logger.info(`Downloading ${swaggers.length} swaggers sources...`) // Create the sources folder if it doesn't exist @@ -46,7 +60,7 @@ const downloadSwaggers = (swaggers: string[]) => { } if (swaggers.length) { - swaggers.forEach(downSwaggerFromUrl) + swaggers.forEach((file, index) => downSwaggerFromUrl(file, index.toString())) } } diff --git a/packages/graphql-mesh/utils/ConfigFromSwaggers.ts b/packages/graphql-mesh/utils/ConfigFromSwaggers.ts index 2082f37..2f822c9 100644 --- a/packages/graphql-mesh/utils/ConfigFromSwaggers.ts +++ b/packages/graphql-mesh/utils/ConfigFromSwaggers.ts @@ -1,11 +1,10 @@ import { globSync } from 'glob' import { readFileSync } from 'node:fs' import { Catalog, Spec, SwaggerName, ConfigExtension } from '../types' -import { getConfig, getSourceOpenapiEnpoint } from './config' +import { getConfig, getSourceName, getSourceOpenapiEnpoint } from './config' import { getAvailableTypes } from './swaggers' import { mergeObjects } from './helpers' import { generateTypeDefsAndResolversFromSwagger } from './swaggers' -import { directiveTypeDefs } from './directive-typedefs' export default class ConfigFromSwaggers { swaggers: SwaggerName[] = [] @@ -28,7 +27,7 @@ export default class ConfigFromSwaggers { content?.['application/json']?.schema['$ref'] ?? content?.['*/*']?.schema['$ref'] const schema = ref?.replace('#/components/schemas/', '') if (schema) { - acc[path] = [query?.operationId, schema, this.swaggers[i]] + acc[path] = [query?.operationId || '', schema, this.swaggers[i]] } }) return acc @@ -37,18 +36,20 @@ export default class ConfigFromSwaggers { getInterfacesWithChildren() { this.specs.forEach((s) => { - const { schemas } = s.components - const entries = Object.entries(schemas).filter(([_, value]) => + const { schemas } = s.components || {} + const entries = Object.entries(schemas || {}).filter(([_, value]) => Object.keys(value).includes('discriminator') ) for (const [schemaKey, schemaValue] of entries) { - const mapping = schemaValue['discriminator']['mapping'] ?? {} - const mappingTypes = [] - mappingTypes.push( - ...Object.keys(mapping) - .filter((k) => k !== schemaKey) - .map((k) => mapping[k].replace('#/components/schemas/', '')) - ) + const mapping: { [key: string]: string } = schemaValue['discriminator']['mapping'] ?? {} + const mappingTypes: string[] = [] + if (Object.keys(mapping).length > 0) { + mappingTypes.push( + ...Object.keys(mapping) + .filter((k) => k !== schemaKey) + .map((k) => mapping[k].replace('#/components/schemas/', '')) + ) + } if (this.interfacesWithChildren[schemaKey] === undefined) { this.interfacesWithChildren[schemaKey] = mappingTypes } else { @@ -71,7 +72,8 @@ export default class ConfigFromSwaggers { spec, availableTypes, this.getInterfacesWithChildren(), - this.catalog + this.catalog, + this.config ) acc.typeDefs += typeDefs acc.resolvers = mergeObjects(acc.resolvers, resolvers) @@ -84,14 +86,16 @@ export default class ConfigFromSwaggers { getOpenApiSources() { return ( this.swaggers.map((source) => ({ - name: source, + name: getSourceName(source, this.config), handler: { openapi: { source, endpoint: getSourceOpenapiEnpoint(source, this.config) || '{env.ENDPOINT}', ignoreErrorResponses: true, operationHeaders: { - Authorization: `{context.headers["authorization"]}` + Authorization: `{context.headers["authorization"]}`, + ...(this.config.sources?.find((item) => source.includes(item.name))?.handler?.openapi + ?.operationHeaders || {}) } } } @@ -118,7 +122,7 @@ export default class ConfigFromSwaggers { getMeshConfigFromSwaggers(): { defaultConfig: any - additionalTypeDefs: string[] + additionalTypeDefs: string additionalResolvers: any sources: any[] } { @@ -126,7 +130,7 @@ export default class ConfigFromSwaggers { return { defaultConfig: this.config, - additionalTypeDefs: [typeDefs, directiveTypeDefs], + additionalTypeDefs: typeDefs, additionalResolvers: resolvers, sources: [...this.getOpenApiSources(), ...this.getOtherSources()] } diff --git a/packages/graphql-mesh/utils/config/index.ts b/packages/graphql-mesh/utils/config/index.ts index 4859499..edd32d7 100644 --- a/packages/graphql-mesh/utils/config/index.ts +++ b/packages/graphql-mesh/utils/config/index.ts @@ -43,8 +43,17 @@ export const getSourceOpenapiEnpoint = ( source: string, config: YamlConfig.Config ): string | undefined => { - const data = config.sources?.find((item) => - item?.handler?.openapi?.source?.includes(source.split('/').pop()) - ) + const data = config.sources?.find((item) => source.includes(item.name)) return data?.handler.openapi?.endpoint } + +/** Get source name from config + * @param source {string} - source name + * @param config {YamlConfig.Config} - config object + * @returns {string} - source name + * + */ +export const getSourceName = (source: string, config: YamlConfig.Config): string => { + const data = config.sources?.find((item) => source.includes(item.name)) + return data?.name || source +} diff --git a/packages/graphql-mesh/utils/directive-typedefs/index.ts b/packages/graphql-mesh/utils/directive-typedefs/index.ts deleted file mode 100644 index 6c92b9a..0000000 --- a/packages/graphql-mesh/utils/directive-typedefs/index.ts +++ /dev/null @@ -1,26 +0,0 @@ -export const directiveTypeDefs = /* GraphQL */ ` - """ - This is a very small, lightweight, straightforward and non-evaluated expression language to sort, filter and paginate arrays of maps. - """ - directive @SPL(query: String) on FIELD - - """ - This directive is used to disable authentication for a specific operation. - """ - directive @noAuth on FIELD - - type LinkItem { - rel: String - href: String - } - - input Header { - key: String - value: String - } - - """ - This directive is used to add headers to the request. - """ - directive @headers(input: [Header]) on FIELD -` diff --git a/packages/graphql-mesh/utils/swaggers/index.ts b/packages/graphql-mesh/utils/swaggers/index.ts index 5bc1a04..66b0f40 100644 --- a/packages/graphql-mesh/utils/swaggers/index.ts +++ b/packages/graphql-mesh/utils/swaggers/index.ts @@ -1,4 +1,5 @@ import { Spec, ConfigExtension, Resolvers } from '../../types' +import { getSourceName } from '../config' import { trimLinks, anonymizePathAndGetParams } from '../helpers' /** * This function creates, for a Swagger file, the additional typeDefs for each schema having at least one x-link, and one resolver for each x-link @@ -10,7 +11,8 @@ export const generateTypeDefsAndResolversFromSwagger = ( spec: Spec, availableTypes: string[], interfacesWithChildren: { [key: string]: string[] }, - catalog: { [key: string]: [string, string, string] } + catalog: { [key: string]: [string, string, string] }, + config: any ): ConfigExtension => { if (!spec.components) { return { @@ -30,7 +32,15 @@ export const generateTypeDefsAndResolversFromSwagger = ( } } + const linkItemTypeDef = /* GraphQL */ ` + type LinkItem { + rel: String + href: String + } + ` + let typeDefs = '' + typeDefs += linkItemTypeDef const resolvers: Resolvers = {} @@ -85,7 +95,7 @@ export const generateTypeDefsAndResolversFromSwagger = ( const query = targetedOperationName const type = targetedOperationType - const source = targetedSwaggerName + const source = getSourceName(targetedSwaggerName, config) if ( targetedOperationType !== 'TYPE_NOT_FOUND' && @@ -118,7 +128,8 @@ export const generateTypeDefsAndResolversFromSwagger = ( if (paramsToSend.length) { paramsToSend.forEach((param, i) => { - args[param] = root[param] || root[paramsFromLink[i]] || '' + // To avoid params validation error in case of missing params or type mismatch we set default value to '0' + args[param] = root[param] || root[paramsFromLink[i]] || '0' }) } diff --git a/test/integration/camouflage.yaml b/test/integration/camouflage.yaml new file mode 100644 index 0000000..84df4c0 --- /dev/null +++ b/test/integration/camouflage.yaml @@ -0,0 +1,65 @@ +loglevel: info +cpus: 1 +monitoring: + port: 5555 +ssl: + cert: "./certs/server.cert" + key: "./certs/server.key" + root_cert: "./certs/root.cert" +protocols: + http: + enable: true + mocks_dir: "./mocks" + port: 8080 + https: + enable: false + port: 8443 + http2: + enable: false + port: 8081 + ws: + enable: false + mocks_dir: "./ws_mocks" + port: 8082 + grpc: + enable: false + host: 0.0.0.0 + port: 4312 + mocks_dir: "./grpc/mocks" + protos_dir: "./grpc/protos" + grpc_tls: false + thrift: + enable: false + mocks_dir: "./thrift/mocks" + services: + - port: 9999 + service: "/opt/gen-nodejs/Calculator" + handlers: + - add + - substract + - ping +backup: + enable: false + cron: "0 * * * *" # Hourly Backup +cache: + enable: false + ttl_seconds: 300 +injection: + enable: true +origins: + - http://localhost:3000 + - http://localhost:3001 + - http://localhost:5000 +validation: + enable: true + schemas: + - type: OpenApi + url: https://raw.githubusercontent.com/OAI/OpenAPI-Specification/main/examples/v3.0/petstore.json +# ext_helpers: "./custom_handlebar.json" +# ext_data_source: +# pg: +# host: localhost +# port: 5432 +# user: root +# password: password +# database: postgres diff --git a/test/integration/compose.yaml b/test/integration/compose.yaml new file mode 100644 index 0000000..ac9c740 --- /dev/null +++ b/test/integration/compose.yaml @@ -0,0 +1,35 @@ +services: + # + api-products: + image: shubhendumadhukar/camouflage + ports: + - 45537:8080 + volumes: + - ./camouflage.yaml:/app/config.yml:ro + - ./mocks:/app/mocks:ro + + healthcheck: + # https://stackoverflow.com/questions/72708667/use-wget-instead-of-curl-for-healthchecks-in-asp-net-core-docker-images + test: wget --spider --tries=1 --no-verbose http://localhost:8080/products || exit 1 + interval: 10s + timeout: 10s + retries: 3 + restart: unless-stopped + + # + graphql-mesh: + depends_on: + api-products: + condition: service_healthy + links: + - api-products + image: ${IMAGE_TAG:-graphql-mesh} + environment: + - DEBUG=1 + ports: + - 45538:3000 + volumes: + - ./transforms:/app/transforms:ro + - ./plugins:/app/plugins:ro + - ./config.yaml:/app/config.yaml:ro + restart: unless-stopped diff --git a/test/integration/config.yaml b/test/integration/config.yaml new file mode 100644 index 0000000..9bb03b7 --- /dev/null +++ b/test/integration/config.yaml @@ -0,0 +1,33 @@ +sources: + - name: Products + handler: + openapi: + source: http://api-products:8080/api-docs/products + endpoint: http://api-products:8080 + - name: Suppliers + handler: + openapi: + source: http://api-products:8080/api-docs/suppliers + endpoint: http://api-products:8080 + + - name: Authentication + handler: + openapi: + source: http://api-products:8080/api-docs/authenticate + endpoint: http://api-products:8080 + +additionalEnvelopPlugins: "./plugins" +additionalTransforms: [{ "./transforms/index.ts": {} }] +additionalTypeDefs: | + """ + This directive is used to convert the result to uppercase. + """ + directive @lower on FIELD +skipSSLValidation: true +serve: + hostname: 0.0.0.0 + port: 3000 + cors: + origin: "*" + playground: true + playgroundTitle: Console GraphQL diff --git a/test/integration/mocks/api-docs/authenticate.yml b/test/integration/mocks/api-docs/authenticate.yml new file mode 100644 index 0000000..4198109 --- /dev/null +++ b/test/integration/mocks/api-docs/authenticate.yml @@ -0,0 +1,20 @@ +openapi: 3.0.0 +info: + title: Authentication API + version: 1.0.0 +paths: + /authenticate: + post: + summary: Authenticate user + operationId: isAuthenticated + responses: + '200': + description: Authentication successful + content: + application/json: + schema: + type: object + properties: + authenticate: + type: boolean + description: Yes or No based on authentication header diff --git a/test/integration/mocks/api-docs/authenticate/GET.mock b/test/integration/mocks/api-docs/authenticate/GET.mock new file mode 100644 index 0000000..a7adc8e --- /dev/null +++ b/test/integration/mocks/api-docs/authenticate/GET.mock @@ -0,0 +1,36 @@ +HTTP/1.1 200 OK +Content-Type: application/json + +{ + "openapi": "3.0.0", + "info": { + "title": "Authentication API", + "version": "1.0.0" + }, + "paths": { + "/authenticate": { + "post": { + "summary": "Authenticate user", + "operationId": "isAuthenticated", + "responses": { + "200": { + "description": "Authentication successful", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "authenticate": { + "type": "boolean", + "description": "Yes or No based on authentication header" + } + } + } + } + } + } + } + } + } + } +} diff --git a/test/integration/mocks/api-docs/products.yml b/test/integration/mocks/api-docs/products.yml new file mode 100644 index 0000000..1b8f5a4 --- /dev/null +++ b/test/integration/mocks/api-docs/products.yml @@ -0,0 +1,82 @@ +openapi: 3.0.0 +info: + title: Products API + description: API to manage products with HATEOAS. + version: "1.0" +servers: + - url: http://localhost:3000/ +paths: + /products: + get: + operationId: getProducts + summary: List all products + responses: + "200": + description: A list of products with HATEOAS links + content: + application/json: + schema: + $ref: "#/components/schemas/Products" + /products/{id}: + get: + operationId: getProductById + summary: Get a product by ID + parameters: + - name: id + in: path + required: true + schema: + type: integer + description: The product ID + responses: + "200": + description: A list of products with HATEOAS links + content: + application/json: + schema: + $ref: "#/components/schemas/Product" +components: + schemas: + Product: + type: object + properties: + _links: + $ref: "#/components/schemas/ProductLinks" + id: + type: integer + name: + type: string + price: + type: number + supplierId: + type: integer + ProductLinks: + type: object + required: + - self + - supplier + properties: + self: + $ref: "#/components/schemas/Link" + supplier: + $ref: "#/components/schemas/Link" + x-links: + - rel: self + hrefPattern: "/products/{id}" + - rel: supplier + hrefPattern: "/suppliers/{id}" + + Products: + type: object + properties: + items: + type: array + items: + $ref: "#/components/schemas/Product" + Link: + type: object + required: + - href + properties: + href: + type: string diff --git a/test/integration/mocks/api-docs/products/GET.mock b/test/integration/mocks/api-docs/products/GET.mock new file mode 100644 index 0000000..8648768 --- /dev/null +++ b/test/integration/mocks/api-docs/products/GET.mock @@ -0,0 +1,136 @@ +HTTP/1.1 200 OK +Content-Type: application/json + +{ + "openapi": "3.0.0", + "info": { + "title": "Products API", + "description": "API to manage products with HATEOAS.", + "version": "1.0" + }, + "servers": [ + { + "url": "http://localhost:3000/" + } + ], + "paths": { + "/products": { + "get": { + "operationId": "getProducts", + "summary": "List all products", + "responses": { + "200": { + "description": "A list of products with HATEOAS links", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Products" + } + } + } + } + } + } + }, + "/products/{id}": { + "get": { + "operationId": "getProductById", + "summary": "Get a product by ID", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + }, + "description": "The product ID" + } + ], + "responses": { + "200": { + "description": "A list of products with HATEOAS links", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Product" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "Product": { + "type": "object", + "properties": { + "_links": { + "$ref": "#/components/schemas/ProductLinks" + }, + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "price": { + "type": "number" + }, + "supplierId": { + "type": "integer" + } + } + }, + "ProductLinks": { + "type": "object", + "required": [ + "self", + "supplier" + ], + "properties": { + "self": { + "$ref": "#/components/schemas/Link" + }, + "supplier": { + "$ref": "#/components/schemas/Link" + } + }, + "x-links": [ + { + "rel": "self", + "hrefPattern": "/products/{id}" + }, + { + "rel": "supplier", + "hrefPattern": "/suppliers/{id}" + } + ] + }, + "Products": { + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Product" + } + } + } + }, + "Link": { + "type": "object", + "required": [ + "href" + ], + "properties": { + "href": { + "type": "string" + } + } + } + } + } +} diff --git a/test/integration/mocks/api-docs/suppliers.yml b/test/integration/mocks/api-docs/suppliers.yml new file mode 100644 index 0000000..7bfefa0 --- /dev/null +++ b/test/integration/mocks/api-docs/suppliers.yml @@ -0,0 +1,60 @@ +openapi: 3.0.0 +info: + title: Suppliers API + description: API to manage suppliers with HATEOAS. + version: "1.0" +servers: + - url: http://localhost:3000/ +paths: + /suppliers: + get: + operationId: getSuppliers + summary: List all suppliers + responses: + "200": + description: A list of suppliers with HATEOAS links + content: + application/json: + schema: + $ref: "#/components/schemas/Suppliers" + /suppliers/{id}: + get: + operationId: getSupplierById + summary: Get a supplier by ID + parameters: + - name: id + in: path + required: true + schema: + type: integer + description: The supplier ID + responses: + "200": + description: A list of suppliers with HATEOAS links + content: + application/json: + schema: + $ref: "#/components/schemas/Supplier" +components: + schemas: + Supplier: + type: object + properties: + id: + type: integer + name: + type: string + Link: + type: object + required: + - href + properties: + href: + type: string + Suppliers: + type: object + properties: + items: + type: array + items: + $ref: "#/components/schemas/Supplier" diff --git a/test/integration/mocks/api-docs/suppliers/GET.mock b/test/integration/mocks/api-docs/suppliers/GET.mock new file mode 100644 index 0000000..cdbdd65 --- /dev/null +++ b/test/integration/mocks/api-docs/suppliers/GET.mock @@ -0,0 +1,102 @@ +HTTP/1.1 200 OK +Content-Type: application/json + +{ + "openapi": "3.0.0", + "info": { + "title": "Suppliers API", + "description": "API to manage suppliers with HATEOAS.", + "version": "1.0" + }, + "servers": [ + { + "url": "http://localhost:3000/" + } + ], + "paths": { + "/suppliers": { + "get": { + "operationId": "getSuppliers", + "summary": "List all suppliers", + "responses": { + "200": { + "description": "A list of suppliers with HATEOAS links", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Suppliers" + } + } + } + } + } + } + }, + "/suppliers/{id}": { + "get": { + "operationId": "getSupplierById", + "summary": "Get a supplier by ID", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + }, + "description": "The supplier ID" + } + ], + "responses": { + "200": { + "description": "A list of suppliers with HATEOAS links", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Supplier" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "Supplier": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string" + } + } + }, + "Link": { + "type": "object", + "required": [ + "href" + ], + "properties": { + "href": { + "type": "string" + } + } + }, + "Suppliers": { + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Supplier" + } + } + } + } + } + } +} diff --git a/test/integration/mocks/authenticate/POST.mock b/test/integration/mocks/authenticate/POST.mock new file mode 100644 index 0000000..deb6879 --- /dev/null +++ b/test/integration/mocks/authenticate/POST.mock @@ -0,0 +1,16 @@ +HTTP/1.1 200 OK +Content-Type: application/json + +{{#code}} +((request) => { + // logger.info(JSON.stringify(request.headers)) + console.log(request.headers['authorization']) + const supplierId = 1 + return { + status: 200, + body: JSON.stringify({ + "authenticate": request.headers['authorization'] === "Bearer token" + }, null, 2) + } +})(request); +{{/code}} diff --git a/test/integration/mocks/products/GET.mock b/test/integration/mocks/products/GET.mock new file mode 100644 index 0000000..1c317f8 --- /dev/null +++ b/test/integration/mocks/products/GET.mock @@ -0,0 +1,32 @@ +HTTP/1.1 200 OK +Content-Type: application/json + +{{#code}} +(() => { + const products = [] + for (let i = 1; i <= 50; i++) { + const supplierId = (i % 10) + 1 + products.push({ + id: i, + name: `Product ${i}`, + price: Math.floor(Math.random() * 100), + supplierId, + _links: { + self: { + href: `/products/${i}` + }, + supplier: { + href: `/suppliers/${supplierId}` + } + } + }) + } + + return { + status: 200, + body: JSON.stringify({ + "items": products + }, null, 2) + } +})(); +{{/code}} diff --git a/test/integration/mocks/products/__/GET.mock b/test/integration/mocks/products/__/GET.mock new file mode 100644 index 0000000..dec062d --- /dev/null +++ b/test/integration/mocks/products/__/GET.mock @@ -0,0 +1,27 @@ +{{assign name='id' value=(capture from='path' regex='\/products\/(.+)?') }} + +HTTP/1.1 200 OK +Content-Type: application/json + +{{#code}} +((id) => { + const supplierId = (parseInt(id) % 10) + 1 + return { + status: 200, + body: JSON.stringify({ + "id": 1, + "name": `Product ${id}`, + "price": `${Math.floor(Math.random() * 100)}`, + "supplierId": `${supplierId}`, + "_links": { + "self": { + "href": `/products/${id}` + }, + "supplier": { + "href": `/suppliers/${supplierId}` + } + } + }, null, 2) + } +})(request.params[0].split('/').reverse()[0]); +{{/code}} diff --git a/test/integration/mocks/suppliers/GET.mock b/test/integration/mocks/suppliers/GET.mock new file mode 100644 index 0000000..56e6033 --- /dev/null +++ b/test/integration/mocks/suppliers/GET.mock @@ -0,0 +1,21 @@ +HTTP/1.1 200 OK +Content-Type: application/json + +{{#code}} +(() => { + const suppliers = [] + for (let i = 1; i <= 10; i++) { + suppliers.push({ + id: i, + name: `Supplier ${i}` + }) + } + + return { + status: 200, + body: JSON.stringify({ + "items": suppliers + }, null, 2) + } +})(); +{{/code}} diff --git a/test/integration/mocks/suppliers/__/GET.mock b/test/integration/mocks/suppliers/__/GET.mock new file mode 100644 index 0000000..d6afe99 --- /dev/null +++ b/test/integration/mocks/suppliers/__/GET.mock @@ -0,0 +1,9 @@ +{{assign name='id' value=(capture from='path' regex='\/suppliers\/(.+)?') }} + +HTTP/1.1 200 OK +Content-Type: application/json + +{ + "id": "{{id}}", + "name": "Supplier {{id}}" +} diff --git a/test/integration/plugins/index.ts b/test/integration/plugins/index.ts new file mode 100644 index 0000000..829007d --- /dev/null +++ b/test/integration/plugins/index.ts @@ -0,0 +1,45 @@ +import { type Plugin } from '@envelop/core'; + +/** + * This plugin auto-populates the Server-Timing header to the response. + * + * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Server-Timing + */ +export default () => { + return { + onFetch({ context, info }) { + if (!info) { + return; + } + + const start = Date.now(); + + return () => { + const duration = Date.now() - start; + const timing = `${info.fieldName};desc="${info.fieldName} (${info.sourceName})";dur=${duration}`; + if (!context.timings) { + context.timings = []; + } + context.timings.push(timing); + }; + }, + + onExecute() { + return { + onExecuteDone({ args }) { + // @ts-ignore + const { timings } = args.contextValue; + if (!timings) { + return; + } + + // @ts-ignore + args.contextValue.res.setHeader?.( + 'Server-Timing', + timings.join(', ') + ); + }, + }; + }, + }; +}; diff --git a/test/integration/tests/.gitignore b/test/integration/tests/.gitignore new file mode 100644 index 0000000..3c3629e --- /dev/null +++ b/test/integration/tests/.gitignore @@ -0,0 +1 @@ +node_modules diff --git a/test/integration/tests/cases/directive-headers.test.ts b/test/integration/tests/cases/directive-headers.test.ts new file mode 100644 index 0000000..d3e6894 --- /dev/null +++ b/test/integration/tests/cases/directive-headers.test.ts @@ -0,0 +1,21 @@ +import { test, expect } from 'vitest' +import axios from 'axios' +import { url, headers } from '../config' + +/* SPL filter */ +const isAuthenticatedMutation = /* GraphQL */ ` + mutation isAuthenticated { + isAuthenticated @headers(input: [{ key: "Authorization", value: "Bearer token" }]) { + authenticate + } + } +` + +test('isAuthenticatedMutation: test directive @headers work properly', async () => { + const response = await axios.post(url, { query: isAuthenticatedMutation }, { headers }) + + const result = response.data + expect(response.status).toBe(200) + expect(result.errors).toBeUndefined() + expect(result.data.isAuthenticated.authenticate).toBe(true) +}) diff --git a/test/integration/tests/cases/directive-no-auth.test.ts b/test/integration/tests/cases/directive-no-auth.test.ts new file mode 100644 index 0000000..fd9b59e --- /dev/null +++ b/test/integration/tests/cases/directive-no-auth.test.ts @@ -0,0 +1,26 @@ +import { test, expect } from 'vitest' +import axios from 'axios' +import { url, headers } from '../config' + +/* SPL filter */ +const isAuthenticatedMutation = /* GraphQL */ ` + mutation isAuthenticated { + isAuthenticated @noAuth { + authenticate + } + } +` + +// Add Authorization header +const _headers = { + ...headers, + Authorization: 'Bearer token' +} +test('isAuthenticatedMutation: test directive @noAuth properly remove authorization header', async () => { + const response = await axios.post(url, { query: isAuthenticatedMutation }, { headers: _headers }) + + const result = response.data + expect(response.status).toBe(200) + expect(result.errors).toBeUndefined() + expect(result.data.isAuthenticated.authenticate).toBe(false) +}) diff --git a/test/integration/tests/cases/directive-spl.test.ts b/test/integration/tests/cases/directive-spl.test.ts new file mode 100644 index 0000000..c6c62b2 --- /dev/null +++ b/test/integration/tests/cases/directive-spl.test.ts @@ -0,0 +1,26 @@ +import { test, expect } from 'vitest' +import axios from 'axios' +import { url, headers } from '../config' + +/* SPL filter */ +const getTenFirstProductsQuery = /* GraphQL */ ` + query getTenFirstProducts { + getProducts { + items @SPL(query: "id <= 10") { + name + id + price + supplierId + } + } + } +` + +test('getTenFirstProductsQuery: test SPL filter inside query', async () => { + const response = await axios.post(url, { query: getTenFirstProductsQuery }, { headers }) + + const result = response.data + expect(response.status).toBe(200) + expect(result.errors).toBeUndefined() + expect(result.data.getProducts.items.length).toEqual(10) +}) diff --git a/test/integration/tests/cases/graphql-mesh.test.ts b/test/integration/tests/cases/graphql-mesh.test.ts new file mode 100644 index 0000000..3e475be --- /dev/null +++ b/test/integration/tests/cases/graphql-mesh.test.ts @@ -0,0 +1,83 @@ +import { test, expect } from 'vitest' +import axios from 'axios' +import { url, headers } from '../config' + +/* Get all products */ +const getAllProductsQuery = /* GraphQL */ ` + query getAllProducts { + getProducts { + items { + name + id + price + supplierId + } + } + } +` + +test('getAllProductsQuery: query working properly', async () => { + const response = await axios.post(url, { query: getAllProductsQuery }, { headers }) + + const result = response.data + expect(response.status).toBe(200) + expect(result).toHaveProperty('data') + expect(result.errors).toBeUndefined() + expect(result.data).toHaveProperty('getProducts') + expect(result.data.getProducts.items.length).toEqual(50) +}) + +/* Hateoas link */ +const getProductAndSupplierInfo = /* GraphQL */ ` + query getProductAndSupplierInfo { + getProductById(id: 1) { + id + name + price + supplier { + name + id + } + } + } +` + +test('getProductAndSupplierInfo: follow hateoas link', async () => { + const response = await axios.post(url, { query: getProductAndSupplierInfo }, { headers }) + + const result = response.data + expect(result.errors).toBeUndefined() + expect(result.data.getProductById.supplier.name).contains('Supplier 2') +}) + +/* Linklist property */ + +const getProductwithLinkList = /* GraphQL */ ` + query getProductWithLinkList { + getProductById(id: 1) { + supplierId + _linksList { + rel + href + } + } + } +` + +test('getProductAndSupplierInfo: Get "_linksList" attributes', async () => { + const response = await axios.post(url, { query: getProductwithLinkList }, { headers }) + + const result = response.data + expect(result.errors).toBeUndefined() + expect(result.data.getProductById._linksList.length).toEqual(2) + expect(result.data.getProductById._linksList).toEqual([ + { + rel: 'self', + href: '/products/1' + }, + { + rel: 'supplier', + href: '/suppliers/2' + } + ]) +}) diff --git a/test/integration/tests/cases/inject-additionnal-transforms.test.ts b/test/integration/tests/cases/inject-additionnal-transforms.test.ts new file mode 100644 index 0000000..0617130 --- /dev/null +++ b/test/integration/tests/cases/inject-additionnal-transforms.test.ts @@ -0,0 +1,23 @@ +import { test, expect } from 'vitest' +import axios from 'axios' +import { url, headers } from '../config' + +/* Get all products */ +const getProductById = /* GraphQL */ ` + query getProduct { + getProductById(id: 1) { + name @lower + price + supplierId + } + } +` + +test('getProductById: @lower directive added throw transform', async () => { + const response = await axios.post(url, { query: getProductById }, { headers }) + + const result = response.data + expect(result.errors).toBeUndefined() + expect(result).toHaveProperty('data') + expect(result.data.getProductById.name).toEqual('product 1') +}) diff --git a/test/integration/tests/cases/plugins.test.ts b/test/integration/tests/cases/plugins.test.ts new file mode 100644 index 0000000..f2a756a --- /dev/null +++ b/test/integration/tests/cases/plugins.test.ts @@ -0,0 +1,22 @@ +import { test, expect } from 'vitest' +import axios from 'axios' +import { url, headers } from '../config' + +/* Get all products */ +const getProductById = /* GraphQL */ ` + query getProduct { + getProductById(id: 1) { + name + price + supplierId + } + } +` + +test('getProductById: Server timing plugin', async () => { + const response = await axios.post(url, { query: getProductById }, { headers }) + + expect(response.data.errors).toBeUndefined() + const serverTiming = response.headers['server-timing'] + expect(serverTiming).contains('getProductById;desc="getProductById (Products)";dur') +}) diff --git a/test/integration/tests/config.ts b/test/integration/tests/config.ts new file mode 100644 index 0000000..48a36bc --- /dev/null +++ b/test/integration/tests/config.ts @@ -0,0 +1,3 @@ +export const url = 'http://0.0.0.0:45538/graphql' +// export const url = 'http://0.0.0.0:4000/graphql' +export const headers = { 'Content-Type': 'application/json' } diff --git a/test/integration/tests/package-lock.json b/test/integration/tests/package-lock.json new file mode 100644 index 0000000..3957168 --- /dev/null +++ b/test/integration/tests/package-lock.json @@ -0,0 +1,1515 @@ +{ + "name": "tests", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "tests", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "axios": "^1.6.8", + "vitest": "^1.5.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz", + "integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==", + "cpu": [ + "ppc64" + ], + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz", + "integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz", + "integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz", + "integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz", + "integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz", + "integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz", + "integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz", + "integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz", + "integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz", + "integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz", + "integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz", + "integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==", + "cpu": [ + "loong64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz", + "integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==", + "cpu": [ + "mips64el" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz", + "integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==", + "cpu": [ + "ppc64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz", + "integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==", + "cpu": [ + "riscv64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz", + "integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==", + "cpu": [ + "s390x" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz", + "integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz", + "integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz", + "integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz", + "integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz", + "integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz", + "integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz", + "integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.14.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.14.2.tgz", + "integrity": "sha512-ahxSgCkAEk+P/AVO0vYr7DxOD3CwAQrT0Go9BJyGQ9Ef0QxVOfjDZMiF4Y2s3mLyPrjonchIMH/tbWHucJMykQ==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.14.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.14.2.tgz", + "integrity": "sha512-lAarIdxZWbFSHFSDao9+I/F5jDaKyCqAPMq5HqnfpBw8dKDiCaaqM0lq5h1pQTLeIqueeay4PieGR5jGZMWprw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.14.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.14.2.tgz", + "integrity": "sha512-SWsr8zEUk82KSqquIMgZEg2GE5mCSfr9sE/thDROkX6pb3QQWPp8Vw8zOq2GyxZ2t0XoSIUlvHDkrf5Gmf7x3Q==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.14.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.14.2.tgz", + "integrity": "sha512-o/HAIrQq0jIxJAhgtIvV5FWviYK4WB0WwV91SLUnsliw1lSAoLsmgEEgRWzDguAFeUEUUoIWXiJrPqU7vGiVkA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.14.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.14.2.tgz", + "integrity": "sha512-nwlJ65UY9eGq91cBi6VyDfArUJSKOYt5dJQBq8xyLhvS23qO+4Nr/RreibFHjP6t+5ap2ohZrUJcHv5zk5ju/g==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.14.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.14.2.tgz", + "integrity": "sha512-Pg5TxxO2IVlMj79+c/9G0LREC9SY3HM+pfAwX7zj5/cAuwrbfj2Wv9JbMHIdPCfQpYsI4g9mE+2Bw/3aeSs2rQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.14.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.14.2.tgz", + "integrity": "sha512-cAOTjGNm84gc6tS02D1EXtG7tDRsVSDTBVXOLbj31DkwfZwgTPYZ6aafSU7rD/4R2a34JOwlF9fQayuTSkoclA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.14.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.14.2.tgz", + "integrity": "sha512-4RyT6v1kXb7C0fn6zV33rvaX05P0zHoNzaXI/5oFHklfKm602j+N4mn2YvoezQViRLPnxP8M1NaY4s/5kXO5cw==", + "cpu": [ + "ppc64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.14.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.14.2.tgz", + "integrity": "sha512-KNUH6jC/vRGAKSorySTyc/yRYlCwN/5pnMjXylfBniwtJx5O7X17KG/0efj8XM3TZU7raYRXJFFReOzNmL1n1w==", + "cpu": [ + "riscv64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.14.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.14.2.tgz", + "integrity": "sha512-xPV4y73IBEXToNPa3h5lbgXOi/v0NcvKxU0xejiFw6DtIYQqOTMhZ2DN18/HrrP0PmiL3rGtRG9gz1QE8vFKXQ==", + "cpu": [ + "s390x" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.14.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.14.2.tgz", + "integrity": "sha512-QBhtr07iFGmF9egrPOWyO5wciwgtzKkYPNLVCFZTmr4TWmY0oY2Dm/bmhHjKRwZoGiaKdNcKhFtUMBKvlchH+Q==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.14.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.14.2.tgz", + "integrity": "sha512-8zfsQRQGH23O6qazZSFY5jP5gt4cFvRuKTpuBsC1ZnSWxV8ZKQpPqOZIUtdfMOugCcBvFGRa1pDC/tkf19EgBw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.14.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.14.2.tgz", + "integrity": "sha512-H4s8UjgkPnlChl6JF5empNvFHp77Jx+Wfy2EtmYPe9G22XV+PMuCinZVHurNe8ggtwoaohxARJZbaH/3xjB/FA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.14.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.14.2.tgz", + "integrity": "sha512-djqpAjm/i8erWYF0K6UY4kRO3X5+T4TypIqw60Q8MTqSBaQNpNXDhxdjpZ3ikgb+wn99svA7jxcXpiyg9MUsdw==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.14.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.14.2.tgz", + "integrity": "sha512-teAqzLT0yTYZa8ZP7zhFKEx4cotS8Tkk5XiqNMJhD4CpaWB1BHARE4Qy+RzwnXvSAYv+Q3jAqCVBS+PS+Yee8Q==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==" + }, + "node_modules/@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==" + }, + "node_modules/@vitest/expect": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.5.0.tgz", + "integrity": "sha512-0pzuCI6KYi2SIC3LQezmxujU9RK/vwC1U9R0rLuGlNGcOuDWxqWKu6nUdFsX9tH1WU0SXtAxToOsEjeUn1s3hA==", + "dependencies": { + "@vitest/spy": "1.5.0", + "@vitest/utils": "1.5.0", + "chai": "^4.3.10" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.5.0.tgz", + "integrity": "sha512-7HWwdxXP5yDoe7DTpbif9l6ZmDwCzcSIK38kTSIt6CFEpMjX4EpCgT6wUmS0xTXqMI6E/ONmfgRKmaujpabjZQ==", + "dependencies": { + "@vitest/utils": "1.5.0", + "p-limit": "^5.0.0", + "pathe": "^1.1.1" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.5.0.tgz", + "integrity": "sha512-qpv3fSEuNrhAO3FpH6YYRdaECnnRjg9VxbhdtPwPRnzSfHVXnNzzrpX4cJxqiwgRMo7uRMWDFBlsBq4Cr+rO3A==", + "dependencies": { + "magic-string": "^0.30.5", + "pathe": "^1.1.1", + "pretty-format": "^29.7.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.5.0.tgz", + "integrity": "sha512-vu6vi6ew5N5MMHJjD5PoakMRKYdmIrNJmyfkhRpQt5d9Ewhw9nZ5Aqynbi3N61bvk9UvZ5UysMT6ayIrZ8GA9w==", + "dependencies": { + "tinyspy": "^2.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.5.0.tgz", + "integrity": "sha512-BDU0GNL8MWkRkSRdNFvCUCAVOeHaUlVJ9Tx0TYBZyXaaOTmGtUFObzchCivIBrIwKzvZA7A9sCejVhXM2aY98A==", + "dependencies": { + "diff-sequences": "^29.6.3", + "estree-walker": "^3.0.3", + "loupe": "^2.3.7", + "pretty-format": "^29.7.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/acorn": { + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", + "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "engines": { + "node": "*" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/axios": { + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.8.tgz", + "integrity": "sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/chai": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.4.1.tgz", + "integrity": "sha512-13sOfMv2+DWduEU+/xbun3LScLoqN17nBeTLUsmDfKdoiC1fr0n9PU4guu4AhRcOVFk/sW8LyZWHuhWtQZiF+g==", + "dependencies": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.3", + "deep-eql": "^4.1.3", + "get-func-name": "^2.0.2", + "loupe": "^2.3.6", + "pathval": "^1.1.1", + "type-detect": "^4.0.8" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/check-error": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", + "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", + "dependencies": { + "get-func-name": "^2.0.2" + }, + "engines": { + "node": "*" + } + }, + "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==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-eql": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", + "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", + "dependencies": { + "type-detect": "^4.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/esbuild": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz", + "integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==", + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.20.2", + "@esbuild/android-arm": "0.20.2", + "@esbuild/android-arm64": "0.20.2", + "@esbuild/android-x64": "0.20.2", + "@esbuild/darwin-arm64": "0.20.2", + "@esbuild/darwin-x64": "0.20.2", + "@esbuild/freebsd-arm64": "0.20.2", + "@esbuild/freebsd-x64": "0.20.2", + "@esbuild/linux-arm": "0.20.2", + "@esbuild/linux-arm64": "0.20.2", + "@esbuild/linux-ia32": "0.20.2", + "@esbuild/linux-loong64": "0.20.2", + "@esbuild/linux-mips64el": "0.20.2", + "@esbuild/linux-ppc64": "0.20.2", + "@esbuild/linux-riscv64": "0.20.2", + "@esbuild/linux-s390x": "0.20.2", + "@esbuild/linux-x64": "0.20.2", + "@esbuild/netbsd-x64": "0.20.2", + "@esbuild/openbsd-x64": "0.20.2", + "@esbuild/sunos-x64": "0.20.2", + "@esbuild/win32-arm64": "0.20.2", + "@esbuild/win32-ia32": "0.20.2", + "@esbuild/win32-x64": "0.20.2" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/execa": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-func-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", + "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", + "engines": { + "node": "*" + } + }, + "node_modules/get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/human-signals": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", + "engines": { + "node": ">=16.17.0" + } + }, + "node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + }, + "node_modules/js-tokens": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.0.tgz", + "integrity": "sha512-WriZw1luRMlmV3LGJaR6QOJjWwgLUTf89OwT2lUOyjX2dJGBwgmIkbcz+7WFZjrZM635JOIR517++e/67CP9dQ==" + }, + "node_modules/jsonc-parser": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.1.tgz", + "integrity": "sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==" + }, + "node_modules/local-pkg": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.5.0.tgz", + "integrity": "sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==", + "dependencies": { + "mlly": "^1.4.2", + "pkg-types": "^1.0.3" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/loupe": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", + "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==", + "dependencies": { + "get-func-name": "^2.0.1" + } + }, + "node_modules/magic-string": { + "version": "0.30.9", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.9.tgz", + "integrity": "sha512-S1+hd+dIrC8EZqKyT9DstTH/0Z+f76kmmvZnkfQVmOpDEF9iVgdYif3Q/pIWHmCoo59bQVGW0kVL3e2nl+9+Sw==", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" + }, + "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==", + "engines": { + "node": ">= 0.6" + } + }, + "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==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mlly": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.6.1.tgz", + "integrity": "sha512-vLgaHvaeunuOXHSmEbZ9izxPx3USsk8KCQ8iC+aTlp5sKRSoZvwhHh5L9VbKSaVC6sJDqbyohIS76E2VmHIPAA==", + "dependencies": { + "acorn": "^8.11.3", + "pathe": "^1.1.2", + "pkg-types": "^1.0.3", + "ufo": "^1.3.2" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/npm-run-path": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", + "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-limit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-5.0.0.tgz", + "integrity": "sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==", + "dependencies": { + "yocto-queue": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "engines": { + "node": ">=8" + } + }, + "node_modules/pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==" + }, + "node_modules/pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "engines": { + "node": "*" + } + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + }, + "node_modules/pkg-types": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.0.3.tgz", + "integrity": "sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A==", + "dependencies": { + "jsonc-parser": "^3.2.0", + "mlly": "^1.2.0", + "pathe": "^1.1.0" + } + }, + "node_modules/postcss": { + "version": "8.4.38", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", + "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.0.0", + "source-map-js": "^1.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, + "node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + }, + "node_modules/rollup": { + "version": "4.14.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.14.2.tgz", + "integrity": "sha512-WkeoTWvuBoFjFAhsEOHKRoZ3r9GfTyhh7Vff1zwebEFLEFjT1lG3784xEgKiTa7E+e70vsC81roVL2MP4tgEEQ==", + "dependencies": { + "@types/estree": "1.0.5" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.14.2", + "@rollup/rollup-android-arm64": "4.14.2", + "@rollup/rollup-darwin-arm64": "4.14.2", + "@rollup/rollup-darwin-x64": "4.14.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.14.2", + "@rollup/rollup-linux-arm64-gnu": "4.14.2", + "@rollup/rollup-linux-arm64-musl": "4.14.2", + "@rollup/rollup-linux-powerpc64le-gnu": "4.14.2", + "@rollup/rollup-linux-riscv64-gnu": "4.14.2", + "@rollup/rollup-linux-s390x-gnu": "4.14.2", + "@rollup/rollup-linux-x64-gnu": "4.14.2", + "@rollup/rollup-linux-x64-musl": "4.14.2", + "@rollup/rollup-win32-arm64-msvc": "4.14.2", + "@rollup/rollup-win32-ia32-msvc": "4.14.2", + "@rollup/rollup-win32-x64-msvc": "4.14.2", + "fsevents": "~2.3.2" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "engines": { + "node": ">=8" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==" + }, + "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==", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/source-map-js": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==" + }, + "node_modules/std-env": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.7.0.tgz", + "integrity": "sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==" + }, + "node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-literal": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-2.1.0.tgz", + "integrity": "sha512-Op+UycaUt/8FbN/Z2TWPBLge3jWrP3xj10f3fnYxf052bKuS3EKs1ZQcVGjnEMdsNVAM+plXRdmjrZ/KgG3Skw==", + "dependencies": { + "js-tokens": "^9.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/tinybench": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.7.0.tgz", + "integrity": "sha512-Qgayeb106x2o4hNzNjsZEfFziw8IbKqtbXBjVh7VIZfBxfD5M4gWtpyx5+YTae2gJ6Y6Dz/KLepiv16RFeQWNA==" + }, + "node_modules/tinypool": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.8.3.tgz", + "integrity": "sha512-Ud7uepAklqRH1bvwy22ynrliC7Dljz7Tm8M/0RBUW+YRa4YHhZ6e4PpgE+fu1zr/WqB1kbeuVrdfeuyIBpy4tw==", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-2.2.1.tgz", + "integrity": "sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "engines": { + "node": ">=4" + } + }, + "node_modules/ufo": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.5.3.tgz", + "integrity": "sha512-Y7HYmWaFwPUmkoQCUIAYpKqkOf+SbVj/2fJJZ4RJMCfZp0rTGwRbzQD+HghfnhKOjL9E01okqz+ncJskGYfBNw==" + }, + "node_modules/vite": { + "version": "5.2.8", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.8.tgz", + "integrity": "sha512-OyZR+c1CE8yeHw5V5t59aXsUPPVTHMDjEZz8MgguLL/Q7NblxhZUlTu9xSPqlsUO/y+X7dlU05jdhvyycD55DA==", + "dependencies": { + "esbuild": "^0.20.1", + "postcss": "^8.4.38", + "rollup": "^4.13.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vite-node": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.5.0.tgz", + "integrity": "sha512-tV8h6gMj6vPzVCa7l+VGq9lwoJjW8Y79vst8QZZGiuRAfijU+EEWuc0kFpmndQrWhMMhet1jdSF+40KSZUqIIw==", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.3.4", + "pathe": "^1.1.1", + "picocolors": "^1.0.0", + "vite": "^5.0.0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vitest": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.5.0.tgz", + "integrity": "sha512-d8UKgR0m2kjdxDWX6911uwxout6GHS0XaGH1cksSIVVG8kRlE7G7aBw7myKQCvDI5dT4j7ZMa+l706BIORMDLw==", + "dependencies": { + "@vitest/expect": "1.5.0", + "@vitest/runner": "1.5.0", + "@vitest/snapshot": "1.5.0", + "@vitest/spy": "1.5.0", + "@vitest/utils": "1.5.0", + "acorn-walk": "^8.3.2", + "chai": "^4.3.10", + "debug": "^4.3.4", + "execa": "^8.0.1", + "local-pkg": "^0.5.0", + "magic-string": "^0.30.5", + "pathe": "^1.1.1", + "picocolors": "^1.0.0", + "std-env": "^3.5.0", + "strip-literal": "^2.0.0", + "tinybench": "^2.5.1", + "tinypool": "^0.8.3", + "vite": "^5.0.0", + "vite-node": "1.5.0", + "why-is-node-running": "^2.2.2" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/node": "^18.0.0 || >=20.0.0", + "@vitest/browser": "1.5.0", + "@vitest/ui": "1.5.0", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/why-is-node-running": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.2.2.tgz", + "integrity": "sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yocto-queue": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", + "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/test/integration/tests/package.json b/test/integration/tests/package.json new file mode 100644 index 0000000..702a1e7 --- /dev/null +++ b/test/integration/tests/package.json @@ -0,0 +1,16 @@ +{ + "name": "tests", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "vitest" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "axios": "^1.6.8", + "vitest": "^1.5.0" + } +} diff --git a/test/integration/tests/vite.config.ts b/test/integration/tests/vite.config.ts new file mode 100644 index 0000000..0324084 --- /dev/null +++ b/test/integration/tests/vite.config.ts @@ -0,0 +1,4 @@ +/// +// Configure Vitest (https://vitest.dev/config/) +import { defineConfig } from 'vite' +export default defineConfig({}) diff --git a/test/integration/transforms/index.ts b/test/integration/transforms/index.ts new file mode 100644 index 0000000..a4acf47 --- /dev/null +++ b/test/integration/transforms/index.ts @@ -0,0 +1,51 @@ +import { defaultFieldResolver, GraphQLSchema } from 'graphql' +import { MeshTransform } from '@graphql-mesh/types' +import { MapperKind, mapSchema } from '@graphql-tools/utils' + +export default class LowerDirectiveTransform implements MeshTransform { + noWrap = true + + transformSchema(schema: GraphQLSchema) { + return mapSchema(schema, { + [MapperKind.OBJECT_FIELD]: (fieldConfig) => { + const originalResolver = + fieldConfig.resolve != null ? fieldConfig.resolve : defaultFieldResolver + + const resolver = async (next: any, _source: any, _args: any, context: any, info: any) => { + const { directives } = info.fieldNodes[0] + const upperDirective = directives.find( + (directive: { name: { value: string } }) => directive.name.value === 'lower' + ) + + let result = await next(context) + + if (upperDirective) { + if (typeof result === 'string') { + result = result.toLowerCase() + } + } + + return result + } + + fieldConfig.resolve = (source, originalArgs, context, info) => { + return resolver( + (context: unknown) => + new Promise((resolve, reject) => { + const result = originalResolver(source, originalArgs, context, info) + if (result instanceof Error) { + reject(result) + } + resolve(result) + }), + source, + originalArgs, + context, + info + ) + } + return fieldConfig + } + }) + } +}