diff --git a/dev-packages/e2e-tests/test-applications/tanstackstart-react-orchestrion/.gitignore b/dev-packages/e2e-tests/test-applications/tanstackstart-react-orchestrion/.gitignore new file mode 100644 index 000000000000..a547bf36d8d1 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/tanstackstart-react-orchestrion/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/dev-packages/e2e-tests/test-applications/tanstackstart-react-orchestrion/add-instrumentation.mjs b/dev-packages/e2e-tests/test-applications/tanstackstart-react-orchestrion/add-instrumentation.mjs new file mode 100644 index 000000000000..df1fa5f6051b --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/tanstackstart-react-orchestrion/add-instrumentation.mjs @@ -0,0 +1,19 @@ +import { readFileSync, writeFileSync } from 'node:fs'; + +// Prepend the Sentry server init to the built Nitro server entry so it runs at +// process boot — before the first request and before `node:http` is set up. +// +// TanStack Start lazy-loads the request-handler module (src/server.ts), so an +// `import` there only runs on the first request: too late for `Sentry.init()` +// to patch `node:http`, leaving the first request without an `http.server` +// transaction (its spans come in orphaned). This mirrors how `@sentry/nuxt` +// injects the server config into Nitro's entry, and removes the need for +// `node --import` (which isn't available on platforms like Vercel/Netlify). +const entryFile = '.output/server/index.mjs'; +const topImport = "import './instrument.server.mjs';\n"; + +const contents = readFileSync(entryFile, 'utf8'); + +if (!contents.startsWith(topImport)) { + writeFileSync(entryFile, topImport + contents, 'utf8'); +} diff --git a/dev-packages/e2e-tests/test-applications/tanstackstart-react-orchestrion/docker-compose.yml b/dev-packages/e2e-tests/test-applications/tanstackstart-react-orchestrion/docker-compose.yml new file mode 100644 index 000000000000..011bed846445 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/tanstackstart-react-orchestrion/docker-compose.yml @@ -0,0 +1,31 @@ +services: + db: + image: mysql:8.0 + restart: always + container_name: e2e-tests-tanstackstart-react-orchestrion-mysql + # The `mysql` 2.x driver doesn't speak MySQL 8's default + # `caching_sha2_password` auth, so force the legacy plugin. + command: ['--default-authentication-plugin=mysql_native_password'] + ports: + - '3306:3306' + environment: + MYSQL_ROOT_PASSWORD: docker + healthcheck: + test: ['CMD-SHELL', 'mysqladmin ping -h 127.0.0.1 -uroot -pdocker'] + interval: 2s + timeout: 3s + retries: 30 + start_period: 10s + + redis: + image: redis:7 + restart: always + container_name: e2e-tests-tanstackstart-react-orchestrion-redis + ports: + - '6379:6379' + healthcheck: + test: ['CMD', 'redis-cli', 'ping'] + interval: 2s + timeout: 3s + retries: 30 + start_period: 5s diff --git a/dev-packages/e2e-tests/test-applications/tanstackstart-react-orchestrion/global-setup.mjs b/dev-packages/e2e-tests/test-applications/tanstackstart-react-orchestrion/global-setup.mjs new file mode 100644 index 000000000000..8f5f52009158 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/tanstackstart-react-orchestrion/global-setup.mjs @@ -0,0 +1,14 @@ +import { execSync } from 'child_process'; +import { dirname } from 'path'; +import { fileURLToPath } from 'url'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); + +export default async function globalSetup() { + // Start MySQL and Redis via Docker Compose. `--wait` blocks until the + // healthchecks in docker-compose.yml pass, so the app can connect immediately. + execSync('docker compose up -d --wait', { + cwd: __dirname, + stdio: 'inherit', + }); +} diff --git a/dev-packages/e2e-tests/test-applications/tanstackstart-react-orchestrion/global-teardown.mjs b/dev-packages/e2e-tests/test-applications/tanstackstart-react-orchestrion/global-teardown.mjs new file mode 100644 index 000000000000..2742279431ad --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/tanstackstart-react-orchestrion/global-teardown.mjs @@ -0,0 +1,12 @@ +import { execSync } from 'child_process'; +import { dirname } from 'path'; +import { fileURLToPath } from 'url'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); + +export default async function globalTeardown() { + execSync('docker compose down --volumes', { + cwd: __dirname, + stdio: 'inherit', + }); +} diff --git a/dev-packages/e2e-tests/test-applications/tanstackstart-react-orchestrion/instrument.server.mjs b/dev-packages/e2e-tests/test-applications/tanstackstart-react-orchestrion/instrument.server.mjs new file mode 100644 index 000000000000..b74f100217d1 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/tanstackstart-react-orchestrion/instrument.server.mjs @@ -0,0 +1,19 @@ +import * as Sentry from '@sentry/tanstackstart-react'; + +// Opt into diagnostics-channel-based auto-instrumentation. This registers the +// channel subscribers (e.g. for `mysql` and `ioredis`) that turn the +// diagnostics-channel events — injected at build time by the orchestrion Vite +// plugin (see `vite.config.ts`) — into Sentry spans. Must run before +// `Sentry.init()`. +Sentry.experimentalUseDiagnosticsChannelInjection(); + +Sentry.init({ + environment: 'qa', // dynamic sampling bias to keep transactions + dsn: process.env.E2E_TEST_DSN, + tunnel: 'http://localhost:3031/', // proxy server + tracesSampleRate: 1, + transportOptions: { + // We expect the app to send a lot of events in a short time + bufferSize: 1000, + }, +}); diff --git a/dev-packages/e2e-tests/test-applications/tanstackstart-react-orchestrion/package.json b/dev-packages/e2e-tests/test-applications/tanstackstart-react-orchestrion/package.json new file mode 100644 index 000000000000..62db38c6b47c --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/tanstackstart-react-orchestrion/package.json @@ -0,0 +1,43 @@ +{ + "name": "tanstackstart-react-orchestrion", + "private": true, + "version": "0.0.1", + "type": "module", + "scripts": { + "build": "vite build && cp instrument.server.mjs .output/server && node add-instrumentation.mjs", + "start": "node .output/server/index.mjs", + "test": "playwright test", + "clean": "npx rimraf node_modules pnpm-lock.yaml", + "test:build": "pnpm install && pnpm build", + "test:build-latest": "pnpm add @tanstack/react-start@latest @tanstack/react-router@latest && pnpm install && pnpm build", + "test:assert": "pnpm test" + }, + "//": "Need to use ioredis 5.10.1 because that's the last version before they support tracing channels", + "dependencies": { + "@sentry/tanstackstart-react": "file:../../packed/sentry-tanstackstart-react-packed.tgz", + "@sentry/server-utils": "file:../../packed/sentry-server-utils-packed.tgz", + "@tanstack/react-start": "^1.136.0", + "@tanstack/react-router": "^1.136.0", + "react": "^19.2.0", + "react-dom": "^19.2.0", + "nitro": "latest || *", + "ioredis": "5.10.1", + "mysql": "^2.18.1" + }, + "devDependencies": { + "@types/react": "^19.2.0", + "@types/react-dom": "^19.2.0", + "@types/node": "^24.10.0", + "@typescript-eslint/eslint-plugin": "^7.2.0", + "@typescript-eslint/parser": "^7.2.0", + "@vitejs/plugin-react-swc": "^3.5.0", + "typescript": "^5.9.0", + "vite": "7.3.2", + "vite-tsconfig-paths": "^5.1.4", + "@playwright/test": "~1.56.0", + "@sentry-internal/test-utils": "link:../../../test-utils" + }, + "volta": { + "extends": "../../package.json" + } +} diff --git a/dev-packages/e2e-tests/test-applications/tanstackstart-react-orchestrion/playwright.config.mjs b/dev-packages/e2e-tests/test-applications/tanstackstart-react-orchestrion/playwright.config.mjs new file mode 100644 index 000000000000..a071f5533f93 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/tanstackstart-react-orchestrion/playwright.config.mjs @@ -0,0 +1,12 @@ +import { getPlaywrightConfig } from '@sentry-internal/test-utils'; + +const config = getPlaywrightConfig({ + startCommand: 'pnpm start', + port: 3000, +}); + +export default { + ...config, + globalSetup: './global-setup.mjs', + globalTeardown: './global-teardown.mjs', +}; diff --git a/dev-packages/e2e-tests/test-applications/tanstackstart-react-orchestrion/src/client.tsx b/dev-packages/e2e-tests/test-applications/tanstackstart-react-orchestrion/src/client.tsx new file mode 100644 index 000000000000..b2ee5a7ab3ad --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/tanstackstart-react-orchestrion/src/client.tsx @@ -0,0 +1,14 @@ +// Imported first so Sentry.init() runs before any other import's side effects. +import './instrument.client'; +import { StartClient } from '@tanstack/react-start/client'; +import { StrictMode, startTransition } from 'react'; +import { hydrateRoot } from 'react-dom/client'; + +startTransition(() => { + hydrateRoot( + document, + + + , + ); +}); diff --git a/dev-packages/e2e-tests/test-applications/tanstackstart-react-orchestrion/src/globals.d.ts b/dev-packages/e2e-tests/test-applications/tanstackstart-react-orchestrion/src/globals.d.ts new file mode 100644 index 000000000000..6e7d31c7a4e6 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/tanstackstart-react-orchestrion/src/globals.d.ts @@ -0,0 +1,2 @@ +declare const __APP_DSN__: string; +declare const __APP_TUNNEL__: string | undefined; diff --git a/dev-packages/e2e-tests/test-applications/tanstackstart-react-orchestrion/src/instrument.client.ts b/dev-packages/e2e-tests/test-applications/tanstackstart-react-orchestrion/src/instrument.client.ts new file mode 100644 index 000000000000..ad7d0b23a8fa --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/tanstackstart-react-orchestrion/src/instrument.client.ts @@ -0,0 +1,9 @@ +import * as Sentry from '@sentry/tanstackstart-react'; + +Sentry.init({ + environment: 'qa', // dynamic sampling bias to keep transactions + dsn: __APP_DSN__, + tracesSampleRate: 1.0, + release: 'e2e-test', + tunnel: __APP_TUNNEL__, +}); diff --git a/dev-packages/e2e-tests/test-applications/tanstackstart-react-orchestrion/src/router.tsx b/dev-packages/e2e-tests/test-applications/tanstackstart-react-orchestrion/src/router.tsx new file mode 100644 index 000000000000..670b22bc688d --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/tanstackstart-react-orchestrion/src/router.tsx @@ -0,0 +1,16 @@ +import * as Sentry from '@sentry/tanstackstart-react'; +import { createRouter } from '@tanstack/react-router'; +import { routeTree } from './routeTree.gen'; + +export const getRouter = () => { + const router = createRouter({ + routeTree, + scrollRestoration: true, + }); + + if (!router.isServer) { + Sentry.addIntegration(Sentry.tanstackRouterBrowserTracingIntegration(router)); + } + + return router; +}; diff --git a/dev-packages/e2e-tests/test-applications/tanstackstart-react-orchestrion/src/routes/__root.tsx b/dev-packages/e2e-tests/test-applications/tanstackstart-react-orchestrion/src/routes/__root.tsx new file mode 100644 index 000000000000..04716c61f3b6 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/tanstackstart-react-orchestrion/src/routes/__root.tsx @@ -0,0 +1,42 @@ +import { type ReactNode } from 'react'; +import { Outlet, createRootRoute, HeadContent, Scripts } from '@tanstack/react-router'; + +export const Route = createRootRoute({ + head: () => ({ + meta: [ + { + charSet: 'utf-8', + }, + { + name: 'viewport', + content: 'width=device-width, initial-scale=1', + }, + { + title: 'TanStack Start Orchestrion', + }, + ], + }), + component: RootComponent, +}); + +function RootComponent() { + return ( + + + + ); +} + +function RootDocument({ children }: Readonly<{ children: ReactNode }>) { + return ( + + + + + + {children} + + + + ); +} diff --git a/dev-packages/e2e-tests/test-applications/tanstackstart-react-orchestrion/src/routes/api.db-ioredis.ts b/dev-packages/e2e-tests/test-applications/tanstackstart-react-orchestrion/src/routes/api.db-ioredis.ts new file mode 100644 index 000000000000..c3453f5be70f --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/tanstackstart-react-orchestrion/src/routes/api.db-ioredis.ts @@ -0,0 +1,24 @@ +import { createFileRoute } from '@tanstack/react-router'; +import Redis from 'ioredis'; + +export const Route = createFileRoute('/api/db-ioredis')({ + server: { + handlers: { + GET: async () => { + const redis = new Redis({ + // Don't keep retrying forever if Redis goes away (e.g. on test teardown) + maxRetriesPerRequest: 1, + retryStrategy: () => null, + }); + + try { + await redis.set('test-key', 'test-value'); + const value = await redis.get('test-key'); + return Response.json({ value }); + } finally { + redis.disconnect(); + } + }, + }, + }, +}); diff --git a/dev-packages/e2e-tests/test-applications/tanstackstart-react-orchestrion/src/routes/api.db-mysql.ts b/dev-packages/e2e-tests/test-applications/tanstackstart-react-orchestrion/src/routes/api.db-mysql.ts new file mode 100644 index 000000000000..3c5e634c015b --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/tanstackstart-react-orchestrion/src/routes/api.db-mysql.ts @@ -0,0 +1,23 @@ +import { createFileRoute } from '@tanstack/react-router'; +import mysql from 'mysql'; + +const connection = mysql.createConnection({ + user: 'root', + password: 'docker', +}); + +export const Route = createFileRoute('/api/db-mysql')({ + server: { + handlers: { + GET: () => { + return new Promise(resolve => { + connection.query('SELECT 1 + 1 AS solution', () => { + connection.query('SELECT NOW()', ['1', '2'], () => { + resolve(Response.json({ status: 'ok' })); + }); + }); + }); + }, + }, + }, +}); diff --git a/dev-packages/e2e-tests/test-applications/tanstackstart-react-orchestrion/src/routes/index.tsx b/dev-packages/e2e-tests/test-applications/tanstackstart-react-orchestrion/src/routes/index.tsx new file mode 100644 index 000000000000..70a2dec4e190 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/tanstackstart-react-orchestrion/src/routes/index.tsx @@ -0,0 +1,9 @@ +import { createFileRoute } from '@tanstack/react-router'; + +export const Route = createFileRoute('/')({ + component: Home, +}); + +function Home() { + return
TanStack Start orchestrion e2e app
; +} diff --git a/dev-packages/e2e-tests/test-applications/tanstackstart-react-orchestrion/src/server.ts b/dev-packages/e2e-tests/test-applications/tanstackstart-react-orchestrion/src/server.ts new file mode 100644 index 000000000000..b10a3bc1e37b --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/tanstackstart-react-orchestrion/src/server.ts @@ -0,0 +1,12 @@ +import { wrapFetchWithSentry } from '@sentry/tanstackstart-react'; + +import handler, { createServerEntry } from '@tanstack/react-start/server-entry'; +import type { ServerEntry } from '@tanstack/react-start/server-entry'; + +const requestHandler: ServerEntry = wrapFetchWithSentry({ + fetch(request: Request) { + return handler.fetch(request); + }, +}); + +export default createServerEntry(requestHandler); diff --git a/dev-packages/e2e-tests/test-applications/tanstackstart-react-orchestrion/src/start.ts b/dev-packages/e2e-tests/test-applications/tanstackstart-react-orchestrion/src/start.ts new file mode 100644 index 000000000000..719869f235ef --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/tanstackstart-react-orchestrion/src/start.ts @@ -0,0 +1,9 @@ +import { sentryGlobalFunctionMiddleware, sentryGlobalRequestMiddleware } from '@sentry/tanstackstart-react'; +import { createStart } from '@tanstack/react-start'; + +export const startInstance = createStart(() => { + return { + requestMiddleware: [sentryGlobalRequestMiddleware], + functionMiddleware: [sentryGlobalFunctionMiddleware], + }; +}); diff --git a/dev-packages/e2e-tests/test-applications/tanstackstart-react-orchestrion/start-event-proxy.mjs b/dev-packages/e2e-tests/test-applications/tanstackstart-react-orchestrion/start-event-proxy.mjs new file mode 100644 index 000000000000..f04ee5bc54ab --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/tanstackstart-react-orchestrion/start-event-proxy.mjs @@ -0,0 +1,6 @@ +import { startEventProxyServer } from '@sentry-internal/test-utils'; + +startEventProxyServer({ + port: 3031, + proxyServerName: 'tanstackstart-react-orchestrion', +}); diff --git a/dev-packages/e2e-tests/test-applications/tanstackstart-react-orchestrion/tests/db.test.ts b/dev-packages/e2e-tests/test-applications/tanstackstart-react-orchestrion/tests/db.test.ts new file mode 100644 index 000000000000..d722361aad7a --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/tanstackstart-react-orchestrion/tests/db.test.ts @@ -0,0 +1,88 @@ +import { expect, test } from '@playwright/test'; +import { waitForTransaction } from '@sentry-internal/test-utils'; + +test('Instruments ioredis automatically via orchestrion', async ({ baseURL }) => { + const transactionEventPromise = waitForTransaction('tanstackstart-react-orchestrion', transactionEvent => { + return ( + transactionEvent.contexts?.trace?.op === 'http.server' && transactionEvent.transaction === 'GET /api/db-ioredis' + ); + }); + + await fetch(`${baseURL}/api/db-ioredis`); + + const transactionEvent = await transactionEventPromise; + + const spans = transactionEvent.spans || []; + + expect(spans).toContainEqual( + expect.objectContaining({ + op: 'db', + origin: 'auto.db.orchestrion.redis', + description: 'set test-key [1 other arguments]', + status: 'ok', + data: expect.objectContaining({ + 'db.system': 'redis', + 'db.statement': 'set test-key [1 other arguments]', + }), + }), + ); + expect(spans).toContainEqual( + expect.objectContaining({ + op: 'db', + origin: 'auto.db.orchestrion.redis', + description: 'get test-key', + status: 'ok', + data: expect.objectContaining({ + 'db.system': 'redis', + 'db.statement': 'get test-key', + }), + }), + ); +}); + +test('Instruments mysql automatically via orchestrion', async ({ baseURL }) => { + const transactionEventPromise = waitForTransaction('tanstackstart-react-orchestrion', transactionEvent => { + return ( + transactionEvent.contexts?.trace?.op === 'http.server' && transactionEvent.transaction === 'GET /api/db-mysql' + ); + }); + + await fetch(`${baseURL}/api/db-mysql`); + + const transactionEvent = await transactionEventPromise; + + const spans = transactionEvent.spans || []; + + expect(spans).toContainEqual( + expect.objectContaining({ + op: 'db', + origin: 'auto.db.orchestrion.mysql', + description: 'SELECT 1 + 1 AS solution', + status: 'ok', + data: expect.objectContaining({ + 'db.system': 'mysql', + 'db.statement': 'SELECT 1 + 1 AS solution', + 'db.user': 'root', + 'db.connection_string': expect.any(String), + 'net.peer.name': expect.any(String), + 'net.peer.port': 3306, + }), + }), + ); + expect(spans).toContainEqual( + expect.objectContaining({ + op: 'db', + origin: 'auto.db.orchestrion.mysql', + description: 'SELECT NOW()', + status: 'ok', + data: expect.objectContaining({ + 'db.system': 'mysql', + 'db.statement': 'SELECT NOW()', + 'db.user': 'root', + 'db.connection_string': expect.any(String), + 'net.peer.name': expect.any(String), + 'net.peer.port': 3306, + }), + }), + ); +}); diff --git a/dev-packages/e2e-tests/test-applications/tanstackstart-react-orchestrion/tsconfig.json b/dev-packages/e2e-tests/test-applications/tanstackstart-react-orchestrion/tsconfig.json new file mode 100644 index 000000000000..5dcdb1fa6f4a --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/tanstackstart-react-orchestrion/tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "target": "ES2022", + "useDefineForClassFields": true, + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "strictNullChecks": true + }, + "include": ["src"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/dev-packages/e2e-tests/test-applications/tanstackstart-react-orchestrion/tsconfig.node.json b/dev-packages/e2e-tests/test-applications/tanstackstart-react-orchestrion/tsconfig.node.json new file mode 100644 index 000000000000..97ede7ee6f2d --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/tanstackstart-react-orchestrion/tsconfig.node.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true, + "strict": true + }, + "include": ["vite.config.ts"] +} diff --git a/dev-packages/e2e-tests/test-applications/tanstackstart-react-orchestrion/vite.config.ts b/dev-packages/e2e-tests/test-applications/tanstackstart-react-orchestrion/vite.config.ts new file mode 100644 index 000000000000..5930d075be33 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/tanstackstart-react-orchestrion/vite.config.ts @@ -0,0 +1,36 @@ +import { defineConfig } from 'vite'; +import tsConfigPaths from 'vite-tsconfig-paths'; +import { tanstackStart } from '@tanstack/react-start/plugin/vite'; +import viteReact from '@vitejs/plugin-react-swc'; +import { nitro } from 'nitro/vite'; +import { sentryTanstackStart } from '@sentry/tanstackstart-react/vite'; +import { sentryOrchestrionPlugin } from '@sentry/server-utils/orchestrion/vite'; + +const appDsn = 'http://public@localhost:3031/1337'; + +export default defineConfig({ + server: { + port: 3000, + }, + define: { + __APP_DSN__: JSON.stringify(appDsn), + __APP_TUNNEL__: JSON.stringify('http://localhost:3031/'), + }, + plugins: [ + tsConfigPaths(), + tanstackStart(), + nitro(), + // react's vite plugin must come after start's vite plugin + viteReact(), + sentryTanstackStart({ + org: process.env.E2E_TEST_SENTRY_ORG_SLUG, + project: process.env.E2E_TEST_SENTRY_PROJECT, + authToken: process.env.E2E_TEST_AUTH_TOKEN, + debug: true, + }), + // Runs the orchestrion code transform over the server bundle and + // force-bundles the instrumented deps (mysql, ioredis, …) so the + // diagnostics-channel calls are actually injected. + sentryOrchestrionPlugin(), + ], +});