From 30c821abd89e07deb7bbccd0a8ecdf1da728eb48 Mon Sep 17 00:00:00 2001 From: miklosbarabas Date: Sun, 16 Nov 2025 14:24:09 +0100 Subject: [PATCH 01/12] feat: add user context in controlplane sentry --- controlplane/src/core/util.ts | 40 +++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/controlplane/src/core/util.ts b/controlplane/src/core/util.ts index 4018396f53..61dbfe4316 100644 --- a/controlplane/src/core/util.ts +++ b/controlplane/src/core/util.ts @@ -1,4 +1,5 @@ import { randomFill } from 'node:crypto'; +import * as Sentry from '@sentry/node'; import { S3ClientConfig } from '@aws-sdk/client-s3'; import { HandlerContext } from '@connectrpc/connect'; import { @@ -34,6 +35,7 @@ import { isAuthenticationError, isAuthorizationError, isPublicError } from './er import { GraphKeyAuthContext } from './services/GraphApiTokenAuthenticator.js'; import { composeFederatedContract, composeFederatedGraphWithPotentialContracts } from './composition/composition.js'; import { SubgraphsToCompose } from './repositories/FeatureFlagRepository.js'; +import { envVariables } from "./env.schema.js"; const labelRegex = /^[\dA-Za-z](?:[\w.-]{0,61}[\dA-Za-z])?$/; const organizationSlugRegex = /^[\da-z]+(?:-[\da-z]+)*$/; @@ -41,6 +43,11 @@ const namespaceRegex = /^[\da-z]+(?:[_-][\da-z]+)*$/; const schemaTagRegex = /^(?![/-])[\d/A-Za-z-]+(?({ id: fastifyLoggerId, defaultValue: newLogger }, newLogger); return newLogger; From 2bc194175fb08e9e90bed94feec5001ce3215214 Mon Sep 17 00:00:00 2001 From: miklosbarabas Date: Sun, 16 Nov 2025 16:07:28 +0100 Subject: [PATCH 02/12] feat: add user context in controlplane sentry - also added appVersion to Sentry release --- controlplane/src/core/sentry.config.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/controlplane/src/core/sentry.config.ts b/controlplane/src/core/sentry.config.ts index 923a609f2a..4242b88f9c 100644 --- a/controlplane/src/core/sentry.config.ts +++ b/controlplane/src/core/sentry.config.ts @@ -1,3 +1,4 @@ +import { createRequire } from 'node:module'; import * as Sentry from '@sentry/node'; import { nodeProfilingIntegration } from '@sentry/profiling-node'; import { eventLoopBlockIntegration } from '@sentry/node-native'; @@ -16,8 +17,18 @@ const { } = envVariables.parse(process.env); if (SENTRY_ENABLED && SENTRY_DSN) { + const require = createRequire(import.meta.url); + let release: string | undefined; + try { + const pkg = require('../../package.json') as { version?: string }; + release = pkg.version; + } catch (error) { + console.debug('Sentry: failed to read package.json version for release', error); + } + Sentry.init({ dsn: SENTRY_DSN, + release, integrations: [ fastifyIntegration(), eventLoopBlockIntegration({ threshold: SENTRY_EVENT_LOOP_BLOCK_THRESHOLD_MS }), From bd1e37ff36898c84de5066dabd0214321992ec3e Mon Sep 17 00:00:00 2001 From: miklosbarabas Date: Sun, 16 Nov 2025 16:22:44 +0100 Subject: [PATCH 03/12] feat: add user context in controlplane sentry - also added appVersion to Sentry release --- controlplane/src/core/util.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/controlplane/src/core/util.ts b/controlplane/src/core/util.ts index 61dbfe4316..48ff958f8f 100644 --- a/controlplane/src/core/util.ts +++ b/controlplane/src/core/util.ts @@ -115,32 +115,33 @@ export const enrichLogger = ( }); if (SENTRY_ENABLED && SENTRY_DSN) { + const sentryScope = Sentry.getCurrentScope(); try { if (authContext.userId) { - Sentry.setUser({ + sentryScope.setUser({ id: authContext.userId, username: authContext.userDisplayName, }); } if (authContext.organizationId) { - Sentry.setTag('org.id', authContext.organizationId); + sentryScope.setTag('org.id', authContext.organizationId); } if ('organizationSlug' in authContext && authContext.organizationSlug) { - Sentry.setTag('org.slug', authContext.organizationSlug); + sentryScope.setTag('org.slug', authContext.organizationSlug); } if ('apiKeyName' in authContext && authContext.apiKeyName) { - Sentry.setTag('api.key', authContext.apiKeyName); + sentryScope.setTag('api.key', authContext.apiKeyName); } if ('federatedGraphId' in authContext && authContext.federatedGraphId) { - Sentry.setTag('graph.id', authContext.federatedGraphId); + sentryScope.setTag('graph.id', authContext.federatedGraphId); } if ('auth' in authContext && authContext.auth) { - Sentry.setTag('auth.kind', authContext.auth); + sentryScope.setTag('auth.kind', authContext.auth); } } catch (error) { newLogger.debug({ err: error }, 'Failed to enrich Sentry context'); From 036ee65d6deea75fd97460b6e0461376e4e673f4 Mon Sep 17 00:00:00 2001 From: miklosbarabas Date: Sun, 16 Nov 2025 16:25:53 +0100 Subject: [PATCH 04/12] feat: add user context in controlplane sentry - also added appVersion to Sentry release --- controlplane/src/core/util.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/controlplane/src/core/util.ts b/controlplane/src/core/util.ts index 48ff958f8f..3cc1dc0445 100644 --- a/controlplane/src/core/util.ts +++ b/controlplane/src/core/util.ts @@ -35,7 +35,7 @@ import { isAuthenticationError, isAuthorizationError, isPublicError } from './er import { GraphKeyAuthContext } from './services/GraphApiTokenAuthenticator.js'; import { composeFederatedContract, composeFederatedGraphWithPotentialContracts } from './composition/composition.js'; import { SubgraphsToCompose } from './repositories/FeatureFlagRepository.js'; -import { envVariables } from "./env.schema.js"; +import { envVariables } from './env.schema.js'; const labelRegex = /^[\dA-Za-z](?:[\w.-]{0,61}[\dA-Za-z])?$/; const organizationSlugRegex = /^[\da-z]+(?:-[\da-z]+)*$/; @@ -43,10 +43,7 @@ const namespaceRegex = /^[\da-z]+(?:[_-][\da-z]+)*$/; const schemaTagRegex = /^(?![/-])[\d/A-Za-z-]+(? Date: Sun, 16 Nov 2025 16:36:45 +0100 Subject: [PATCH 05/12] feat: add user context in controlplane sentry - also added appVersion to Sentry release --- controlplane/src/core/util.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/controlplane/src/core/util.ts b/controlplane/src/core/util.ts index 3cc1dc0445..c65c43e9da 100644 --- a/controlplane/src/core/util.ts +++ b/controlplane/src/core/util.ts @@ -112,33 +112,33 @@ export const enrichLogger = ( }); if (SENTRY_ENABLED && SENTRY_DSN) { - const sentryScope = Sentry.getCurrentScope(); try { + // NB: Fastify integration automatically creates request-specific scopes if (authContext.userId) { - sentryScope.setUser({ + Sentry.setUser({ id: authContext.userId, username: authContext.userDisplayName, }); } if (authContext.organizationId) { - sentryScope.setTag('org.id', authContext.organizationId); + Sentry.setTag('org.id', authContext.organizationId); } if ('organizationSlug' in authContext && authContext.organizationSlug) { - sentryScope.setTag('org.slug', authContext.organizationSlug); + Sentry.setTag('org.slug', authContext.organizationSlug); } if ('apiKeyName' in authContext && authContext.apiKeyName) { - sentryScope.setTag('api.key', authContext.apiKeyName); + Sentry.setTag('api.key', authContext.apiKeyName); } if ('federatedGraphId' in authContext && authContext.federatedGraphId) { - sentryScope.setTag('graph.id', authContext.federatedGraphId); + Sentry.setTag('graph.id', authContext.federatedGraphId); } if ('auth' in authContext && authContext.auth) { - sentryScope.setTag('auth.kind', authContext.auth); + Sentry.setTag('auth.kind', authContext.auth); } } catch (error) { newLogger.debug({ err: error }, 'Failed to enrich Sentry context'); From 47c630a7a19ad0afb37f08caea8e4a36a0f1ee8c Mon Sep 17 00:00:00 2001 From: miklosbarabas Date: Sun, 16 Nov 2025 16:41:13 +0100 Subject: [PATCH 06/12] feat: add user context in controlplane sentry - also added appVersion to Sentry release --- controlplane/src/core/util.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/controlplane/src/core/util.ts b/controlplane/src/core/util.ts index c65c43e9da..dc3928cadb 100644 --- a/controlplane/src/core/util.ts +++ b/controlplane/src/core/util.ts @@ -45,7 +45,6 @@ const graphNameRegex = /^[\dA-Za-z]+(?:[./@_-][\dA-Za-z]+)*$/; const pluginVersionRegex = /^v\d+$/; const { SENTRY_ENABLED, SENTRY_DSN } = envVariables.parse(process.env); - /** * Wraps a function with a try/catch block and logs any errors that occur. * If the error is a public error, it is returned as a response message. From db7c87c67e78f825ce0635087b61fb323898674b Mon Sep 17 00:00:00 2001 From: miklosbarabas Date: Sun, 16 Nov 2025 17:33:45 +0100 Subject: [PATCH 07/12] feat: add user context in controlplane sentry - also added appVersion to Sentry release --- controlplane/vite.config.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/controlplane/vite.config.ts b/controlplane/vite.config.ts index e8ef4b89c2..8f00920b51 100644 --- a/controlplane/vite.config.ts +++ b/controlplane/vite.config.ts @@ -8,5 +8,6 @@ export default defineConfig({ teardownTimeout: 10_000, // Ensure always the CJS version is used otherwise we might conflict with multiple versions of graphql alias: [{ find: /^graphql$/, replacement: 'graphql/index.js' }], + setupFiles: ['dotenv/config'], }, }); From e6d811fdd6097cc10820fea8d7738911b6b5a58d Mon Sep 17 00:00:00 2001 From: miklosbarabas Date: Sun, 16 Nov 2025 19:26:28 +0100 Subject: [PATCH 08/12] feat: add user context in controlplane sentry - also added appVersion to Sentry release --- controlplane/vite.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/controlplane/vite.config.ts b/controlplane/vite.config.ts index 8f00920b51..08100b5150 100644 --- a/controlplane/vite.config.ts +++ b/controlplane/vite.config.ts @@ -4,7 +4,7 @@ export default defineConfig({ test: { pool: 'forks', // Increase the timeout for integration tests - testTimeout: 20_000, + testTimeout: 35_000, teardownTimeout: 10_000, // Ensure always the CJS version is used otherwise we might conflict with multiple versions of graphql alias: [{ find: /^graphql$/, replacement: 'graphql/index.js' }], From 5287c98ac7a5b94fe2227dbc9dae0b4f7d1fe205 Mon Sep 17 00:00:00 2001 From: miklosbarabas Date: Sun, 16 Nov 2025 19:36:36 +0100 Subject: [PATCH 09/12] feat: add user context in controlplane sentry - also added appVersion to Sentry release --- .github/workflows/controlplane-ci.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/controlplane-ci.yaml b/.github/workflows/controlplane-ci.yaml index 408744925e..79479cdfc8 100644 --- a/.github/workflows/controlplane-ci.yaml +++ b/.github/workflows/controlplane-ci.yaml @@ -80,6 +80,9 @@ jobs: - name: Setup Keycloak run: nohup .github/scripts/setup-keycloak.sh & + - name: Copy dummy env file + run: cp .env.example .env + - name: Test run: pnpm run --filter controlplane test env: From 1382a467aa1a6578cf8cbd95ea9d885b047b197d Mon Sep 17 00:00:00 2001 From: miklosbarabas Date: Sun, 16 Nov 2025 19:41:02 +0100 Subject: [PATCH 10/12] feat: add user context in controlplane sentry - also added appVersion to Sentry release --- .github/workflows/controlplane-ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/controlplane-ci.yaml b/.github/workflows/controlplane-ci.yaml index 79479cdfc8..5c3ad299d6 100644 --- a/.github/workflows/controlplane-ci.yaml +++ b/.github/workflows/controlplane-ci.yaml @@ -81,7 +81,7 @@ jobs: run: nohup .github/scripts/setup-keycloak.sh & - name: Copy dummy env file - run: cp .env.example .env + run: cp ./controlplane/.env.example ./controlplane/.env - name: Test run: pnpm run --filter controlplane test From 70e97ba7054503d50d9fff130e6d1ab9e0de1d04 Mon Sep 17 00:00:00 2001 From: miklosbarabas Date: Sun, 16 Nov 2025 21:40:40 +0100 Subject: [PATCH 11/12] feat: add user context in controlplane sentry - also added appVersion to Sentry release --- controlplane/src/core/sentry.config.ts | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/controlplane/src/core/sentry.config.ts b/controlplane/src/core/sentry.config.ts index 4242b88f9c..0c931aed23 100644 --- a/controlplane/src/core/sentry.config.ts +++ b/controlplane/src/core/sentry.config.ts @@ -1,8 +1,8 @@ -import { createRequire } from 'node:module'; import * as Sentry from '@sentry/node'; import { nodeProfilingIntegration } from '@sentry/profiling-node'; import { eventLoopBlockIntegration } from '@sentry/node-native'; import { fastifyIntegration, pinoIntegration } from '@sentry/node'; +import { version } from '../../package.json'; import { envVariables } from './env.schema.js'; const { @@ -17,18 +17,9 @@ const { } = envVariables.parse(process.env); if (SENTRY_ENABLED && SENTRY_DSN) { - const require = createRequire(import.meta.url); - let release: string | undefined; - try { - const pkg = require('../../package.json') as { version?: string }; - release = pkg.version; - } catch (error) { - console.debug('Sentry: failed to read package.json version for release', error); - } - Sentry.init({ dsn: SENTRY_DSN, - release, + release: version, integrations: [ fastifyIntegration(), eventLoopBlockIntegration({ threshold: SENTRY_EVENT_LOOP_BLOCK_THRESHOLD_MS }), From ec97d989bd199ddef55b1dc20c5d4418948c180b Mon Sep 17 00:00:00 2001 From: miklosbarabas Date: Mon, 17 Nov 2025 20:59:45 +0100 Subject: [PATCH 12/12] feat: add user context in controlplane sentry - also added appVersion to Sentry release --- .github/workflows/controlplane-ci.yaml | 3 -- controlplane/src/core/build-server.ts | 37 ++++++++++++++++++- controlplane/src/core/sentry.config.ts | 13 ++++++- .../src/core/services/Authentication.ts | 25 +++++++++++-- controlplane/src/core/util.ts | 37 ------------------- controlplane/src/index.ts | 4 ++ controlplane/test/authentication.test.ts | 4 ++ controlplane/vite.config.ts | 3 +- 8 files changed, 78 insertions(+), 48 deletions(-) diff --git a/.github/workflows/controlplane-ci.yaml b/.github/workflows/controlplane-ci.yaml index 5c3ad299d6..408744925e 100644 --- a/.github/workflows/controlplane-ci.yaml +++ b/.github/workflows/controlplane-ci.yaml @@ -80,9 +80,6 @@ jobs: - name: Setup Keycloak run: nohup .github/scripts/setup-keycloak.sh & - - name: Copy dummy env file - run: cp ./controlplane/.env.example ./controlplane/.env - - name: Test run: pnpm run --filter controlplane test env: diff --git a/controlplane/src/core/build-server.ts b/controlplane/src/core/build-server.ts index 4ecb4e8173..bdc6096e6b 100644 --- a/controlplane/src/core/build-server.ts +++ b/controlplane/src/core/build-server.ts @@ -8,6 +8,7 @@ import { compressionBrotli, compressionGzip } from '@connectrpc/connect-node'; import fastifyGracefulShutdown from 'fastify-graceful-shutdown'; import { App } from 'octokit'; import { Worker } from 'bullmq'; +import * as Sentry from '@sentry/node'; import routes from './routes.js'; import fastifyHealth from './plugins/health.js'; import fastifyMetrics, { MetricsPluginOptions } from './plugins/metrics.js'; @@ -136,6 +137,10 @@ export interface BuildConfig { key?: string; // e.g. string or '/path/to/my/client-key.pem' }; }; + sentry: { + enabled: boolean; + dsn?: string; + }; } export interface MetricsOptions { @@ -249,7 +254,37 @@ export default async function build(opts: BuildConfig) { const webAuth = new WebSessionAuthenticator(opts.auth.secret, userRepo); const graphKeyAuth = new GraphApiTokenAuthenticator(opts.auth.secret); const accessTokenAuth = new AccessTokenAuthenticator(organizationRepository, authUtils); - const authenticator = new Authentication(webAuth, apiKeyAuth, accessTokenAuth, graphKeyAuth, organizationRepository); + const authenticator = new Authentication( + webAuth, + apiKeyAuth, + accessTokenAuth, + graphKeyAuth, + organizationRepository, + (authContext) => { + if (opts.sentry.enabled && opts.sentry.dsn) { + try { + Sentry.setUser({ + id: authContext.userId, + username: authContext.userDisplayName, + }); + + Sentry.setTag('org.id', authContext.organizationId); + + if (authContext.organizationSlug) { + Sentry.setTag('org.slug', authContext.organizationSlug); + } + + if (authContext.apiKeyName) { + Sentry.setTag('api.key', authContext.apiKeyName); + } + + Sentry.setTag('auth.kind', authContext.auth); + } catch (error) { + logger.debug({ err: error }, 'Failed to enrich Sentry user context'); + } + } + }, + ); const authorizer = new Authorization(logger, opts.stripe?.defaultPlanId); diff --git a/controlplane/src/core/sentry.config.ts b/controlplane/src/core/sentry.config.ts index 0c931aed23..4242b88f9c 100644 --- a/controlplane/src/core/sentry.config.ts +++ b/controlplane/src/core/sentry.config.ts @@ -1,8 +1,8 @@ +import { createRequire } from 'node:module'; import * as Sentry from '@sentry/node'; import { nodeProfilingIntegration } from '@sentry/profiling-node'; import { eventLoopBlockIntegration } from '@sentry/node-native'; import { fastifyIntegration, pinoIntegration } from '@sentry/node'; -import { version } from '../../package.json'; import { envVariables } from './env.schema.js'; const { @@ -17,9 +17,18 @@ const { } = envVariables.parse(process.env); if (SENTRY_ENABLED && SENTRY_DSN) { + const require = createRequire(import.meta.url); + let release: string | undefined; + try { + const pkg = require('../../package.json') as { version?: string }; + release = pkg.version; + } catch (error) { + console.debug('Sentry: failed to read package.json version for release', error); + } + Sentry.init({ dsn: SENTRY_DSN, - release: version, + release, integrations: [ fastifyIntegration(), eventLoopBlockIntegration({ threshold: SENTRY_EVENT_LOOP_BLOCK_THRESHOLD_MS }), diff --git a/controlplane/src/core/services/Authentication.ts b/controlplane/src/core/services/Authentication.ts index f1c1ec0efb..da0a3ce2b1 100644 --- a/controlplane/src/core/services/Authentication.ts +++ b/controlplane/src/core/services/Authentication.ts @@ -12,6 +12,8 @@ import { RBACEvaluator } from './RBACEvaluator.js'; // The maximum time to cache the user auth context for the web session authentication. const maxAuthCacheTtl = 30 * 1000; // 30 seconds +export type PostAuthHook = (authContext: AuthContext) => void; + export interface Authenticator { authenticate(headers: Headers): Promise; authenticateRouter(headers: Headers): Promise; @@ -26,6 +28,7 @@ export class Authentication implements Authenticator { private accessTokenAuth: AccessTokenAuthenticator, private graphKeyAuth: GraphApiTokenAuthenticator, private orgRepo: OrganizationRepository, + private postAuthHook?: PostAuthHook, ) {} /** @@ -44,11 +47,19 @@ export class Authentication implements Authenticator { const authorization = headers.get('authorization'); if (authorization) { const token = authorization.replace(/^bearer\s+/i, ''); + let authContext: AuthContext; if (token.startsWith('cosmo')) { - return await this.keyAuth.authenticate(token); + authContext = await this.keyAuth.authenticate(token); + } else { + const organizationSlug = headers.get('cosmo-org-slug'); + authContext = await this.accessTokenAuth.authenticate(token, organizationSlug); + } + + if (this.postAuthHook) { + this.postAuthHook(authContext); } - const organizationSlug = headers.get('cosmo-org-slug'); - return await this.accessTokenAuth.authenticate(token, organizationSlug); + + return authContext; } /** @@ -66,6 +77,10 @@ export class Authentication implements Authenticator { const cachedUserContext = this.#cache.get(cacheKey); if (cachedUserContext) { + if (this.postAuthHook) { + this.postAuthHook(cachedUserContext); + } + return cachedUserContext; } @@ -100,6 +115,10 @@ export class Authentication implements Authenticator { userDisplayName: user.userDisplayName, }; + if (this.postAuthHook) { + this.postAuthHook(userContext); + } + this.#cache.set(cacheKey, userContext); return userContext; diff --git a/controlplane/src/core/util.ts b/controlplane/src/core/util.ts index dc3928cadb..4018396f53 100644 --- a/controlplane/src/core/util.ts +++ b/controlplane/src/core/util.ts @@ -1,5 +1,4 @@ import { randomFill } from 'node:crypto'; -import * as Sentry from '@sentry/node'; import { S3ClientConfig } from '@aws-sdk/client-s3'; import { HandlerContext } from '@connectrpc/connect'; import { @@ -35,7 +34,6 @@ import { isAuthenticationError, isAuthorizationError, isPublicError } from './er import { GraphKeyAuthContext } from './services/GraphApiTokenAuthenticator.js'; import { composeFederatedContract, composeFederatedGraphWithPotentialContracts } from './composition/composition.js'; import { SubgraphsToCompose } from './repositories/FeatureFlagRepository.js'; -import { envVariables } from './env.schema.js'; const labelRegex = /^[\dA-Za-z](?:[\w.-]{0,61}[\dA-Za-z])?$/; const organizationSlugRegex = /^[\da-z]+(?:-[\da-z]+)*$/; @@ -43,7 +41,6 @@ const namespaceRegex = /^[\da-z]+(?:[_-][\da-z]+)*$/; const schemaTagRegex = /^(?![/-])[\d/A-Za-z-]+(?({ id: fastifyLoggerId, defaultValue: newLogger }, newLogger); return newLogger; diff --git a/controlplane/src/index.ts b/controlplane/src/index.ts index a34d7577cb..57ffbd68bd 100644 --- a/controlplane/src/index.ts +++ b/controlplane/src/index.ts @@ -158,6 +158,10 @@ const options: BuildConfig = { } : undefined, }, + sentry: { + enabled: SENTRY_ENABLED, + dsn: SENTRY_DSN, + }, }; if (STRIPE_SECRET_KEY) { diff --git a/controlplane/test/authentication.test.ts b/controlplane/test/authentication.test.ts index 7e7cf3b4a7..222553a0d8 100644 --- a/controlplane/test/authentication.test.ts +++ b/controlplane/test/authentication.test.ts @@ -72,6 +72,10 @@ describe('Authentication', (ctx) => { admissionWebhook: { secret: 'secret', }, + sentry: { + enabled: false, + dsn: '', + } }); testContext.onTestFailed(async () => { diff --git a/controlplane/vite.config.ts b/controlplane/vite.config.ts index 08100b5150..e8ef4b89c2 100644 --- a/controlplane/vite.config.ts +++ b/controlplane/vite.config.ts @@ -4,10 +4,9 @@ export default defineConfig({ test: { pool: 'forks', // Increase the timeout for integration tests - testTimeout: 35_000, + testTimeout: 20_000, teardownTimeout: 10_000, // Ensure always the CJS version is used otherwise we might conflict with multiple versions of graphql alias: [{ find: /^graphql$/, replacement: 'graphql/index.js' }], - setupFiles: ['dotenv/config'], }, });