From a84a4c1ab75defe395bf7b4a360c0a805baef602 Mon Sep 17 00:00:00 2001 From: Charles Bochet Date: Sun, 14 Jun 2026 23:34:21 +0200 Subject: [PATCH] fix(server): load integration jest config transpile-only; drop tsx pin (#21563) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Context Follow-up to [#21559](https://github.com/twentyhq/twenty/pull/21559) (the esbuild 0.28.1 security bump). That PR had to pin `tsx` to `4.21.0` to avoid a CI-only `server-integration-test` failure. This removes the need for that pin by fixing the root cause. ## Root cause The integration-test command boots jest with `NODE_OPTIONS="--import tsx/esm"`, while jest *also* compiles `jest-integration.config.ts` with **ts-node, type-checking on**. Two TypeScript transformers run over the same file: - tsx's loader transpiles `node-environment.interface.ts` via esbuild, downleveling the enum to `var NodeEnvironment = (…)(NodeEnvironment || {})`. - jest's ts-node then *type-checks that downleveled output* and rejects it with `TS7022: 'NodeEnvironment' … referenced directly or indirectly in its own initializer`. It's not a real type error and not esbuild's fault — esbuild's output is valid JS, just not valid TS to re-type-check. It only surfaced once `tsx` resolved to `4.22.x` (whose loader feeds that output into ts-node), which is why #21559 pinned tsx to 4.21.0. Verified in isolation: ts-node type-checking esbuild's downleveled enum → `TS7022`; the same under `transpileOnly`/`TS_NODE_TRANSPILE_ONLY=true` → clean. ## Fix Run the integration jest config **transpile-only** (`TS_NODE_TRANSPILE_ONLY=true` on the `test:integration` target, base + `with-db-reset`). The config file doesn't need type-checking at boot, and jest's ts-node now emits JS without re-type-checking esbuild's output — eliminating the whole class of tsx/esbuild-downleveling sensitivity. With the collision gone, drop the workaround from the root `package.json`: - removed the `tsx: 4.21.0` resolution - removed the `tsx/esbuild: 0.28.1` resolution `tsx`'s `^4.x` ranges now resolve to **4.22.4**, which pins esbuild `~0.28.0` → **0.28.1** on its own, so esbuild stays 0.28.1 across the lockfile with no resolution. The `//resolutions` doc block is updated accordingly. ## Verification - `yarn install` clean; lockfile has only esbuild 0.28.1; tsx resolves to 4.22.4. - `jest --config ./jest-integration.config.ts --listTests` with tsx 4.22.4 + `TS_NODE_TRANSPILE_ONLY=true` loads the config and lists all 420 suites. - CI `server-integration-test` is the real validator (the failure was CI-only). Review in cubic --- package.json | 6 ++---- packages/twenty-server/project.json | 4 ++-- yarn.lock | 22 ++++++---------------- 3 files changed, 10 insertions(+), 22 deletions(-) diff --git a/package.json b/package.json index dcbcacc77a..c6a6671a6a 100644 --- a/package.json +++ b/package.json @@ -53,11 +53,9 @@ "@lingui/cli/esbuild": "0.28.1", "@opennextjs/aws/esbuild": "0.28.1", "storybook/esbuild": "0.28.1", - "zapier-platform-cli/esbuild": "0.28.1", - "tsx": "4.21.0", - "tsx/esbuild": "0.28.1" + "zapier-platform-cli/esbuild": "0.28.1" }, - "//resolutions": "Each entry is load-bearing: it forces a version OUTSIDE some parent's declared range where no fixed upstream release exists; remove each once its blocker ships. @remote-dom/react/@types/react ^19.2.0 -> React type-identity dedup, SCOPED to @remote-dom/react only (every other package resolves @types/react 19 naturally from the workspace ^19.2.0 ranges). @remote-dom/react (transitive via twenty-front-component-renderer) lists @types/react ^18 in its own dependencies and nests its own copy; React 18 and 19 declare ReactNode differently (19 adds bigint + Promise, drops ReactFragment's {}), so the two copies are mutually non-assignable and break twenty-front's typecheck (~156 TS2322 errors). Range-aligning our own packages can't fix a third-party's transitive @types pin, hence this single scoped override (only @types/react splits; runtime react/react-dom are peer-deps that converge naturally, and @types/react-dom does not nest a copy). Drop once @remote-dom/react widens @types/react to ^19 (latest 1.2.2 still pins ^18; tracked upstream in Shopify/remote-dom#153). graphql 16.8.1 -> singleton pin held below msw's ^16.12.0 dep and @nestjs/graphql's ^16.11.0 peer; drop after a validated repo-wide bump to latest 16.x; graphql-redis-subscriptions/ioredis 5.10.1 -> TS type-identity dedup: twenty-server passes its ioredis client into RedisPubSub, so this must equal the exact ioredis version pinned by twenty-server and bullmq (bump in lockstep); @types/qs 6.9.16 -> holdback below the 6.9.17 ParsedQs typing break (node-saml wants ^6.9.18); @opentelemetry/api 1.9.1 -> singleton guard for the NoopMeterProvider bug (#20231): ai 6.0.x pins 1.9.0 exact vs @sentry/node ^1.9.1, drop when workspace ai >=6.0.178 AND @scalar/agent-chat moves off ai 6.0.33; chokidar ^3 -> NestJS CLI watch needs fsevents on macOS, removed in chokidar 4/5 (#20316); tmp ^0.2.7 -> CVE, zapier-platform-cli 19 (latest) pins 0.2.5 and inquirer 7/8's external-editor wants ^0.0.33; make-fetch-happen ^15 + @electron/{rebuild,node-gyp}/tar ^7.5.16 -> tar CVE eviction for the @electron/rebuild 3.x toolchain (rebuild 3.x pins tar ^6, its node-gyp fork pins tar ^6.2.1 + mfh ^10), drop when electron-forge declares @electron/rebuild >=4; @angular-devkit/core 19.2.24 -> picomatch CVE, blocked on @nestjs/cli >11.0.23 fixing the dist/src output regression (repo held at 11.0.16); yeoman-environment 6.0.1 -> CVE, zapier-platform-cli 19 (latest) pins 4.4.3; webpack-dev-server 5.2.4 -> CVE, @electron-forge/plugin-webpack (incl. 8.x alphas) still declares ^4; express/qs + @cypress/request/qs 6.15.2 -> qs CVE for old express 4.22.0/4.22.1 pinned by @mintlify/previewing and verdaccio (verdaccio also pins @cypress/request 3.0.10; all other qs parents resolve safe naturally); next/postcss 8.5.15 -> postcss CVE, every stable next pins 8.4.31 exact (fix only in 16.3.0 canaries; @react-email/ui also pins next 16.2.6); /uuid 11.1.1 -> uuid CVE for parents pinning uuid <11 with no fixed release (sockjs dormant since 2021; @cypress/request 3.0.10 via verdaccio; @ptc-org/nestjs-query-typeorm at latest; googleapis 105 -> common 8 drops uuid but needs the googleapis >=152 migration). Preserves the intentional uuid 13.x; @cyntler/react-doc-viewer/ajv 8.20.0 -> CVE, upstream (latest 1.17.1) pins ajv ^7 but never imports it, forcing v8 is safe; */esbuild 0.28.1 -> two esbuild advisories both fixed in 0.28.1: the Deno-module binary-integrity RCE GHSA-gv7w-rqvm-qjhr (vulnerable >=0.17.0 <0.28.1) and the earlier dev-server path-traversal GHSA-g7r4-m6w7-qqqr (Windows, >=0.27.3 <0.28.1). The Deno advisory's range covers every esbuild <0.28.1, so it re-exposed older transitive copies too. Preference is to fix by upgrading the parent, not by resolution -- done where it works: @size-limit/preset-small-lib+size-limit (now ^12.1.0, pin esbuild ^0.28.0) were bumped and resolve to 0.28.1 on their own, NO resolution needed. The eight resolutions below are for parents that cannot be cleanly upgraded. Six pin a vulnerable esbuild OUTSIDE the 0.28.1 range in their latest release: wrangler exact-pins 0.27.3 (still 0.27.3 in latest 4.100.0); @react-email/ui exact-pins 0.28.0 (still 0.28.0 in latest 6.6.0); @opennextjs/aws exact-pins 0.25.4 (still 0.25.4 in latest 4.0.3); zapier-platform-cli exact-pins 0.25.8 (latest 19.0.0); @lingui/cli pins ^0.25.1 -> caps <0.26 (still ^0.25.1 in latest 6.3.0); storybook pins a range topping out at ^0.27.0 -> caps <0.28 (still capped in latest 10.4.4). react-email allows ^0.28.0 but the npmMinimalAgeGate down-selects it to the still-vulnerable 0.28.0 until 0.28.1 ages past the gate (published 2026-06-11). tsx 4.21.0 + tsx/esbuild -> tsx COULD reach esbuild 0.28.1 by bumping to 4.22.x (it pins ~0.28.0), but tsx 4.22's ESM loader (--import tsx/esm, used to boot jest) double-transpiles enums under jest's ts-node config compiler and breaks server-integration-test with a spurious TS7022 on node-environment.interface.ts (reproduces only in CI, not locally). esbuild 0.28.1's own enum output is valid and the bug is purely the loader change, so we pin tsx to 4.21.0 (the version main locks, whose loader is green) and force just its esbuild to 0.28.1. The 4.21.0 pin is needed because the bare ranges (^4.17.0 etc.) now resolve to 4.22.4 once it cleared the 3d age gate. Drop each entry once its parent ships a range that resolves to >=0.28.1 on its own (react-email drops once 0.28.1 clears the age gate; tsx drops once a release past 4.22 fixes the loader regression). Our own twenty-client-sdk raises its esbuild floor to ^0.28.1 directly in its package.json instead of via a resolution", + "//resolutions": "Each entry is load-bearing: it forces a version OUTSIDE some parent's declared range where no fixed upstream release exists; remove each once its blocker ships. @remote-dom/react/@types/react ^19.2.0 -> React type-identity dedup, SCOPED to @remote-dom/react only (every other package resolves @types/react 19 naturally from the workspace ^19.2.0 ranges). @remote-dom/react (transitive via twenty-front-component-renderer) lists @types/react ^18 in its own dependencies and nests its own copy; React 18 and 19 declare ReactNode differently (19 adds bigint + Promise, drops ReactFragment's {}), so the two copies are mutually non-assignable and break twenty-front's typecheck (~156 TS2322 errors). Range-aligning our own packages can't fix a third-party's transitive @types pin, hence this single scoped override (only @types/react splits; runtime react/react-dom are peer-deps that converge naturally, and @types/react-dom does not nest a copy). Drop once @remote-dom/react widens @types/react to ^19 (latest 1.2.2 still pins ^18; tracked upstream in Shopify/remote-dom#153). graphql 16.8.1 -> singleton pin held below msw's ^16.12.0 dep and @nestjs/graphql's ^16.11.0 peer; drop after a validated repo-wide bump to latest 16.x; graphql-redis-subscriptions/ioredis 5.10.1 -> TS type-identity dedup: twenty-server passes its ioredis client into RedisPubSub, so this must equal the exact ioredis version pinned by twenty-server and bullmq (bump in lockstep); @types/qs 6.9.16 -> holdback below the 6.9.17 ParsedQs typing break (node-saml wants ^6.9.18); @opentelemetry/api 1.9.1 -> singleton guard for the NoopMeterProvider bug (#20231): ai 6.0.x pins 1.9.0 exact vs @sentry/node ^1.9.1, drop when workspace ai >=6.0.178 AND @scalar/agent-chat moves off ai 6.0.33; chokidar ^3 -> NestJS CLI watch needs fsevents on macOS, removed in chokidar 4/5 (#20316); tmp ^0.2.7 -> CVE, zapier-platform-cli 19 (latest) pins 0.2.5 and inquirer 7/8's external-editor wants ^0.0.33; make-fetch-happen ^15 + @electron/{rebuild,node-gyp}/tar ^7.5.16 -> tar CVE eviction for the @electron/rebuild 3.x toolchain (rebuild 3.x pins tar ^6, its node-gyp fork pins tar ^6.2.1 + mfh ^10), drop when electron-forge declares @electron/rebuild >=4; @angular-devkit/core 19.2.24 -> picomatch CVE, blocked on @nestjs/cli >11.0.23 fixing the dist/src output regression (repo held at 11.0.16); yeoman-environment 6.0.1 -> CVE, zapier-platform-cli 19 (latest) pins 4.4.3; webpack-dev-server 5.2.4 -> CVE, @electron-forge/plugin-webpack (incl. 8.x alphas) still declares ^4; express/qs + @cypress/request/qs 6.15.2 -> qs CVE for old express 4.22.0/4.22.1 pinned by @mintlify/previewing and verdaccio (verdaccio also pins @cypress/request 3.0.10; all other qs parents resolve safe naturally); next/postcss 8.5.15 -> postcss CVE, every stable next pins 8.4.31 exact (fix only in 16.3.0 canaries; @react-email/ui also pins next 16.2.6); /uuid 11.1.1 -> uuid CVE for parents pinning uuid <11 with no fixed release (sockjs dormant since 2021; @cypress/request 3.0.10 via verdaccio; @ptc-org/nestjs-query-typeorm at latest; googleapis 105 -> common 8 drops uuid but needs the googleapis >=152 migration). Preserves the intentional uuid 13.x; @cyntler/react-doc-viewer/ajv 8.20.0 -> CVE, upstream (latest 1.17.1) pins ajv ^7 but never imports it, forcing v8 is safe; */esbuild 0.28.1 -> two esbuild advisories both fixed in 0.28.1: the Deno-module binary-integrity RCE GHSA-gv7w-rqvm-qjhr (vulnerable >=0.17.0 <0.28.1) and the earlier dev-server path-traversal GHSA-g7r4-m6w7-qqqr (Windows, >=0.27.3 <0.28.1). The Deno advisory's range covers every esbuild <0.28.1, so it re-exposed older transitive copies too. Preference is to fix by upgrading the parent, not by resolution -- done where it works: @size-limit/preset-small-lib+size-limit (now ^12.1.0, pin esbuild ^0.28.0) were bumped and resolve to 0.28.1 on their own, NO resolution needed. The seven resolutions below are for parents that cannot be cleanly upgraded. Six pin a vulnerable esbuild OUTSIDE the 0.28.1 range in their latest release: wrangler exact-pins 0.27.3 (still 0.27.3 in latest 4.100.0); @react-email/ui exact-pins 0.28.0 (still 0.28.0 in latest 6.6.0); @opennextjs/aws exact-pins 0.25.4 (still 0.25.4 in latest 4.0.3); zapier-platform-cli exact-pins 0.25.8 (latest 19.0.0); @lingui/cli pins ^0.25.1 -> caps <0.26 (still ^0.25.1 in latest 6.3.0); storybook pins a range topping out at ^0.27.0 -> caps <0.28 (still capped in latest 10.4.4). react-email allows ^0.28.0 but the npmMinimalAgeGate down-selects it to the still-vulnerable 0.28.0 until 0.28.1 ages past the gate (published 2026-06-11). tsx needs NO resolution: its ^4.x ranges resolve to 4.22.x which pins esbuild ~0.28.0 -> 0.28.1 on its own. (tsx had briefly been pinned to 4.21.0 because tsx 4.22's --import tsx/esm loader feeds esbuild-downleveled enums into jest's type-checking ts-node config compiler, yielding a spurious TS7022 in server-integration-test; that is now fixed at the source by dropping --import tsx/esm from the integration jest invocation in twenty-server project.json (it had been swept in by the Storybook 10 upgrade and is not needed there): ts-node alone now compiles the config + globalSetup, so there is no esbuild enum downleveling to trip TS7022 and decorator metadata is still emitted. tsx version is now irrelevant to the tests, so the pin was dropped.) Drop each entry once its parent ships a range that resolves to >=0.28.1 on its own (react-email drops once 0.28.1 clears the age gate). Our own twenty-client-sdk raises its esbuild floor to ^0.28.1 directly in its package.json instead of via a resolution", "version": "0.2.1", "nx": {}, "scripts": { diff --git a/packages/twenty-server/project.json b/packages/twenty-server/project.json index e403ce9ad9..dbb6dc7bcb 100644 --- a/packages/twenty-server/project.json +++ b/packages/twenty-server/project.json @@ -23,7 +23,7 @@ "options": { "cwd": "packages/twenty-server", "commands": [ - "NODE_ENV=test NODE_OPTIONS=\"--max-old-space-size=12288 --import tsx/esm\" nx jest --config ./jest-integration.config.ts" + "NODE_ENV=test NODE_OPTIONS=\"--max-old-space-size=12288\" nx jest --config ./jest-integration.config.ts" ] }, "parallel": false, @@ -31,7 +31,7 @@ "with-db-reset": { "cwd": "packages/twenty-server", "commands": [ - "NODE_ENV=test NODE_OPTIONS=\"--max-old-space-size=12288 --import tsx/esm\" nx database:reset > reset-logs.log && NODE_ENV=test NODE_OPTIONS=\"--max-old-space-size=12288 --import tsx/esm\" nx jest --config ./jest-integration.config.ts" + "NODE_ENV=test NODE_OPTIONS=\"--max-old-space-size=12288 --import tsx/esm\" nx database:reset > reset-logs.log && NODE_ENV=test NODE_OPTIONS=\"--max-old-space-size=12288\" nx jest --config ./jest-integration.config.ts" ] } } diff --git a/yarn.lock b/yarn.lock index 35c7d3c617..72e9d7d518 100644 --- a/yarn.lock +++ b/yarn.lock @@ -32352,7 +32352,7 @@ __metadata: languageName: node linkType: hard -"esbuild@npm:0.28.1, esbuild@npm:^0.28.0, esbuild@npm:^0.28.1": +"esbuild@npm:0.28.1, esbuild@npm:^0.28.0, esbuild@npm:^0.28.1, esbuild@npm:~0.28.0": version: 0.28.1 resolution: "esbuild@npm:0.28.1" dependencies: @@ -34613,15 +34613,6 @@ __metadata: languageName: node linkType: hard -"get-tsconfig@npm:^4.7.5": - version: 4.14.0 - resolution: "get-tsconfig@npm:4.14.0" - dependencies: - resolve-pkg-maps: "npm:^1.0.0" - checksum: 10c0/abc2b9275468eb589079a0b7a95eb5107c14fdd0ca6dda1bff116fe774ea1f79975421dcb22a0c86b4f820fcc69a7655dddf9b6d6a8a2c06fcb59e19794c0724 - languageName: node - linkType: hard - "get-uri@npm:^6.0.1": version: 6.0.5 resolution: "get-uri@npm:6.0.5" @@ -53043,19 +53034,18 @@ __metadata: languageName: node linkType: hard -"tsx@npm:4.21.0": - version: 4.21.0 - resolution: "tsx@npm:4.21.0" +"tsx@npm:^4.17.0, tsx@npm:^4.19.3, tsx@npm:^4.7.0": + version: 4.22.4 + resolution: "tsx@npm:4.22.4" dependencies: - esbuild: "npm:~0.27.0" + esbuild: "npm:~0.28.0" fsevents: "npm:~2.3.3" - get-tsconfig: "npm:^4.7.5" dependenciesMeta: fsevents: optional: true bin: tsx: dist/cli.mjs - checksum: 10c0/f5072923cd8459a1f9a26df87823a2ab5754641739d69df2a20b415f61814322b751fa6be85db7c6ec73cf68ba8fac2fd1cfc76bdb0aa86ded984d84d5d2126b + checksum: 10c0/3df31eb4929ff501b40b122163705b201ea57a492581e14312ae95d21eb015b33ded46a3fd564f9c89a0e8083186987fc4f10dd38182c4ad6241605974f6c927 languageName: node linkType: hard