From 6a0ac63bb91bd2e1ab5c7a6383bcd5e7dc78b293 Mon Sep 17 00:00:00 2001 From: Danijel Martinek Date: Sat, 9 May 2026 01:32:43 +0200 Subject: [PATCH] fix(realtime): CI gate fixes surfaced by Phase 11 smoke tests - no-direct-socket-io: extend allowlist to cover apps/*/src/**/*.test.ts so the realtime-ping e2e integration test can import socket.io/socket.io-client directly; add two valid test cases to keep the rule's own test suite green - tsconfig.json (root): add root-level tsconfig with experimentalDecorators + emitDecoratorMetadata and no "include" so tsx 4.21.0's createFilesMatcher resolves decorator config for all workspace packages, not just web-next's own source tree - web-next dev script: pass TSX_TSCONFIG_PATH=../../tsconfig.json so the custom Node server uses the root tsconfig for all modules it loads - next.config.mjs: add @repo/core-events and @repo/core-realtime to transpilePackages so Next.js webpack can resolve their workspace source files - server.ts: replace static authContainer import with a dynamic import inside IRealtimeAuthenticator.authenticate so Inversify decorators are applied only after bindAll() has already populated the container All CI gates pass: lint (0 errors), typecheck, 20 tests (incl. realtime-ping e2e), boundaries (0 issues). Co-Authored-By: Claude Sonnet 4.6 --- apps/web-next/next.config.mjs | 2 ++ apps/web-next/package.json | 2 +- apps/web-next/server.ts | 6 +++++- packages/core-eslint/rules/no-direct-socket-io.js | 1 + packages/core-eslint/rules/no-direct-socket-io.test.js | 3 +++ tsconfig.json | 7 +++++++ 6 files changed, 19 insertions(+), 2 deletions(-) create mode 100644 tsconfig.json diff --git a/apps/web-next/next.config.mjs b/apps/web-next/next.config.mjs index eaa8607..fa80bf2 100644 --- a/apps/web-next/next.config.mjs +++ b/apps/web-next/next.config.mjs @@ -7,6 +7,8 @@ const nextConfig = { "@repo/blog", "@repo/core-api", "@repo/core-cms", + "@repo/core-events", + "@repo/core-realtime", "@repo/core-shared", "@repo/core-trpc", "@repo/core-ui", diff --git a/apps/web-next/package.json b/apps/web-next/package.json index 90f30d0..0614b3b 100644 --- a/apps/web-next/package.json +++ b/apps/web-next/package.json @@ -5,7 +5,7 @@ "type": "module", "scripts": { "build": "echo 'Next.js build requires full environment — use pnpm dev or docker'", - "dev": "tsx server.ts", + "dev": "TSX_TSCONFIG_PATH=../../tsconfig.json tsx server.ts", "start": "node --import tsx server.ts", "lint": "eslint .", "test": "vitest run --passWithNoTests", diff --git a/apps/web-next/server.ts b/apps/web-next/server.ts index d271648..9a4959b 100644 --- a/apps/web-next/server.ts +++ b/apps/web-next/server.ts @@ -11,7 +11,6 @@ import { type IRealtimeAuthenticator, } from "@repo/core-realtime"; import { SESSION_COOKIE } from "@repo/auth"; -import { authContainer } from "@repo/auth/di/container"; import { AUTH_SYMBOLS } from "@repo/auth/di/symbols"; import { bindAll } from "./src/server/bind-production.js"; @@ -35,6 +34,11 @@ const authenticator: IRealtimeAuthenticator = { authenticate: async ({ cookies }) => { const sessionId = cookies[SESSION_COOKIE]; if (!sessionId) return null; + // Lazy-import the auth container after bindAll() has already populated it. + // Dynamic import defers tsx's module transformation to runtime, which lets + // reflect-metadata and the DI decorators resolve correctly in this server + // entry point. + const { authContainer } = await import("@repo/auth/di/container"); const authService = authContainer.get<{ validateSession: (id: string) => Promise<{ user: { id: string }; session: unknown } | null>; }>(AUTH_SYMBOLS.IAuthenticationService); diff --git a/packages/core-eslint/rules/no-direct-socket-io.js b/packages/core-eslint/rules/no-direct-socket-io.js index 6e854f5..63448a6 100644 --- a/packages/core-eslint/rules/no-direct-socket-io.js +++ b/packages/core-eslint/rules/no-direct-socket-io.js @@ -2,6 +2,7 @@ const ALLOWED = [ /\/packages\/core-realtime\/src\//, /\/apps\/[^/]+\/server\.ts$/, + /\/apps\/[^/]+\/src\/.*\.test\.ts$/, ]; export default { diff --git a/packages/core-eslint/rules/no-direct-socket-io.test.js b/packages/core-eslint/rules/no-direct-socket-io.test.js index 2837fce..bf74953 100644 --- a/packages/core-eslint/rules/no-direct-socket-io.test.js +++ b/packages/core-eslint/rules/no-direct-socket-io.test.js @@ -12,6 +12,9 @@ tester.run("no-direct-socket-io", rule, { { code: 'import { Server } from "socket.io";', filename: "/repo/packages/core-realtime/src/socket-io-realtime-server.ts" }, // Allowed in app servers { code: 'import { Server } from "socket.io";', filename: "/repo/apps/web-next/server.ts" }, + // Allowed in app integration tests (e.g. realtime-ping e2e) + { code: 'import { Server } from "socket.io";', filename: "/repo/apps/web-next/src/__tests__/realtime-ping.test.ts" }, + { code: 'import { io } from "socket.io-client";', filename: "/repo/apps/web-next/src/__tests__/realtime-ping.test.ts" }, // Allowed elsewhere when not importing socket.io { code: 'import { foo } from "bar";', filename: "/repo/packages/blog/src/foo.ts" }, ], diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..0caab9a --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,7 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "compilerOptions": { + "experimentalDecorators": true, + "emitDecoratorMetadata": true + } +}