## Summary
Closes the last open Dependabot alert on `main` — [alert
1504](https://github.com/twentyhq/twenty/security/dependabot/1504)
(GHSA-h67p-54hq-rp68 / CVE-2026-53550, js-yaml merge-key
quadratic-complexity DoS; medium, dev scope).
The 4.x js-yaml copies are already pinned to 4.2.0 (the seven
`@mintlify/*` + `@verdaccio/config` resolutions). The remaining
vulnerable copy was **js-yaml 3.14.2**, held by two `^3.13.1` consumers
with no fixed upstream release:
- `@istanbuljs/load-nyc-config@1.1.0` (jest coverage)
- `front-matter@4.0.2` (mintlify docs tooling — EOL, latest is 4.0.2)
## Approach
Both are forced to **js-yaml 4.2.0** via scoped resolutions, which
evicts the 3.x copy entirely:
```jsonc
"front-matter/js-yaml": "4.2.0",
"@istanbuljs/load-nyc-config/js-yaml": "4.2.0",
```
- **load-nyc-config** already calls `yaml.load` (present +
safe-by-default in 4.x), so its pin alone works.
- **front-matter** crashed on js-yaml 4.x because its default loader
called the removed `safeLoad`, so it's also **patched**
(`.yarn/patches/front-matter-npm-4.0.2-e1cc0efa69.patch`):
```diff
- var loader = allowUnsafe ? parser.load : parser.safeLoad
+ var loader = parser.load
```
On 4.x `load` is already safe-by-default and there's no full/unsafe
schema, so the `allowUnsafe` ternary is dead.
**Why the version move is a separate resolution, not folded into the
patch:** a `yarn patch` only rewrites a package's *files* — it does
**not** change the resolved dependency graph. Bumping js-yaml inside the
patched `package.json` is ignored by resolution (verified:
front-matter@patch still pulled 3.14.2 until the explicit pin was
added). This is the repo's first *transitive* patch; like every other
transitive override it lives in root `resolutions`. Documented in the
`//resolutions` note.
## Verification
- js-yaml 3.x **fully evicted** — the tree resolves js-yaml to **4.2.0
only** (no `^3.13.1` descriptor remains).
- `yarn install --immutable` passes.
- front-matter parses real docs front-matter correctly on 4.2.0 (unit
smoke test).
- **`mintlify validate` passes** — the docs toolchain builds with the
patched front-matter.
## Problem
The `danger-js` check (`twenty-utils:danger:ci`) started failing
intermittently with:
```
FetchError: Invalid response body while trying to fetch
https://api.github.com/repos/twentyhq/twenty/pulls/<n>/files: Premature close
errno: 'ERR_STREAM_PREMATURE_CLOSE'
```
It fails before the Dangerfile even runs, while fetching PR files / diff
/ commits. The existing retry wrapper
([#22151](https://github.com/twentyhq/twenty/pull/22151)) reduced it but
can't absorb longer GitHub-API windows, so checks still go red.
## Root cause
Not "node-fetch is old" generically — a specific recent regression:
- Node **22.23.0 / 24.17.0** shipped a security fix for CVE-2026-48931
(http.Agent response-queue poisoning) that attaches a `'data'` listener
to idle keep-alive sockets.
- `node-fetch@2` misreads that listener as an unclean connection close —
but only on **gzip-encoded responses without `Content-Length`**, which
is exactly what `api.github.com` returns.
- The GitHub-hosted runners rolling into the patched Node 24.17.x in
recent weeks is why this surfaced now.
See
[danger/danger-js#1515](https://github.com/danger/danger-js/issues/1515),
[nodejs/node#63989](https://github.com/nodejs/node/issues/63989).
## Why this approach
- `node-fetch@2` can't be removed downstream — Danger imports it
directly, and it's pervasive transitively (gaxios/googleapis). Dropping
it is an upstream migration.
- We don't want to pin an old Node version.
So: bump `danger` 13.0.4 → 13.0.8 and backport
[danger/danger-js#1516](https://github.com/danger/danger-js/pull/1516)
via a yarn patch — set `compress: false` on Danger's shared `api()`
wrapper. GitHub then returns identity-encoded responses with
`Content-Length`, and node-fetch's faulty premature-close detector never
fires. Negligible bandwidth cost on these small JSON payloads; explicit
caller overrides are preserved via an `=== undefined` guard.
## Changes
- `packages/twenty-utils/package.json` — `danger` → patched 13.0.8
- `yarn.lock` — registers the `danger@patch:` resolution
- `.yarn/patches/danger-npm-13.0.8-48aba2788c.patch` — the `compress:
false` fix
## Verification
- Patch dry-run applies cleanly against pristine danger 13.0.8 source.
- Inspected yarn's materialized patched cache package — the `compress`
fix is present in the linked `distribution/api/fetch.js`.
- Confirmed the failing calls (`getPullRequestInfo` /
`getPullRequestCommits` / `getPullRequestDiff`) all route through
`this.api` → the patched wrapper.
## Lifecycle
Temporary backport. When #1516 ships in a Danger release, drop the patch
and bump to that version (flagged in a comment inside the patch). The
existing CI retry wrapper stays as defense-in-depth.
<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22171?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
## TL;DR
The metadata API silently drops relation rows whenever a batched
relation query exceeds the requested page size. A dev-seeded workspace
already has **558 fieldMetadata rows across 31 objects**, so
`objects(paging:{first:50}) { fields(paging:{first:500}) }` executes:
```sql
SELECT DISTINCT ... FROM core."fieldMetadata" fields
WHERE workspaceId = $1 AND objectMetadataId IN (...31 ids...)
LIMIT 501 OFFSET 0 -- no ORDER BY
```
…and returns exactly **501 of 558** fields — ~57 rows dropped, and
*which object loses which field is scan-order-dependent*. This is what
made `example-app-postcard` CI flap with "PostCard object missing field
X" (different X per run).
## Root cause
`@ptc-org/nestjs-query-typeorm`'s `batchQueryRelations` (the DataLoader
batch path behind every `@CursorConnection`) applies the **per-parent**
page size as a **single global LIMIT** on the batched query, then groups
rows per parent in memory. Any batch whose combined relation rows exceed
`first + 1` truncates arbitrary parents. This affects production
metadata reads, not just CI — any workspace with enough fields/objects
loses rows in `objects.fields`-style connections.
## Fix
Yarn patch on `@ptc-org/nestjs-query-typeorm@9.4.0` (same vehicle as the
existing `nestjs-query-graphql` patch):
- `RelationQueryBuilder.batchSelect`: only apply LIMIT/OFFSET when the
batch has a **single parent**; multi-parent batches stay **bounded**
with `parents × (offset + limit)` — the upper bound a correct per-parent
pager can ever need, so it cannot wrongly truncate while still guarding
against unbounded fetches on high-cardinality relations;
- `batchQueryRelations`: enforce paging **per parent** by slicing after
`mapRelations` (preserves the `first + 1` hasNextPage probe semantics).
## Verification
- On a frozen repro DB (postcard installed, 558 fields): unpatched
returns 501 fields with `postCard` missing `deliveredAt`; patched
returns **558/558** with the full `postCard` field set. Reproduced
identically on typeorm 0.3.20 and 0.3.26 — pre-existing bug, **not** a
typeorm regression (this unblocks the typeorm upgrade that was reverted
from #21448).
- Postcard install/uninstall stress loop: unpatched fails within 1–2
iterations; patched **12/12 green**.
- `npx nx typecheck twenty-server` clean, full `twenty-server` unit
suite green (5651 passed).
## Related
#21435 chases the **same CI symptom** (postcard randomly missing a
freshly synced field) at a different layer — a workspace-cache write
racing invalidation. The two are complementary: the repro behind this PR
survives a **cold server restart + `redis-cli FLUSHALL`** with all rows
intact in Postgres, which no cache race can explain — the truncation
happens on the DB read itself (`LIMIT 501` over 558 matching rows,
captured via `log_statement=all`). Both fixes are likely needed for the
postcard job to be fully reliable.
## Notes
Worth upstreaming to `@ptc-org/nestjs-query` eventually; the proper
upstream fix is per-parent windowed pagination (`ROW_NUMBER() OVER
(PARTITION BY parentId)`), but the in-memory per-parent slice is correct
and proportionate at metadata-API scale.
## What
Upgrades the NestJS GraphQL stack to clear the High **`ws`** alert
(GHSA-3h5v-q93c-6h6q) and modernize off two heavily-patched majors.
`@nestjs/graphql@13` pulls `ws@8.20.1` (was 8.16.0).
This had to be a **coordinated** upgrade: `@ptc-org/nestjs-query@4.2.0`
doesn't support `@nestjs/graphql@13`, so all three move together.
| Package | From → To |
|---|---|
| `@nestjs/config` | 3.3.0 → ^4.0.4 |
| `@nestjs/graphql` | 12.1.1 → ^13.4.2 |
| `@ptc-org/nestjs-query-{core,graphql,typeorm}` | 4.x → ^9.4.0 |
## The tricky bits
- **Re-ported the custom `@nestjs/graphql` patch onto v13.** v13 rewrote
the schema builder and added its *own* native multi-schema support
(`includeModules`, native `clear()`). Twenty's patch
(`resolverSchemaScope` + `computeReachableTypes` — the
core/metadata/admin split) is re-merged into v13's new
`generate(options, includeModules, reachableTypes)` flow, with a
link-preserving `storage.clear()` so cross-schema `resolveType` closures
keep working.
- **Re-ported the `@ptc-org` patch onto 9.4.0**: removes the
`@shareable` federation directive from built-in connection/response
types, **and** adds a `.js` extension to its extensionless deep import
of `@nestjs/graphql` internals — which v13's new `"exports"` map
otherwise rejects at runtime (this was the boot blocker).
- **`AppTokenService`**: nestjs-query 9 requires custom services to
inject their repo and `super(repo)` it (added an `@InjectRepository`
constructor).
- **`gridPosition` input fields**: dropped the `deprecationReason` (a
*required* input field can't be `@deprecated` under the upgraded
graphql) — fields keep their original nullability, so the **schema is
unchanged**.
- **Service specs**: nestjs-query 9's `TypeOrmQueryService` reads the
repo's driver/metadata at construction, so the mocked repos now include
`manager`/`metadata`.
## Verification
- `nx typecheck twenty-server`: **0 errors**; lint clean
- Server boots; **all 3 GraphQL schemas** (`/graphql`, `/metadata`,
`/admin-panel`) generate and respond `200`
- `graphql:generate` for all 3 schemas is **byte-identical** to before
the upgrade (the reachable-types re-port is faithful)
- **108 service unit tests pass** (incl. all 6 `TypeOrmQueryService`
services)
- `ws@8.16.0` gone (now 8.17.1 + 8.18.0); `yarn install --immutable`
clean
## Note on lodash
`lodash@4.17.21` still remains via `zapier-platform-core` (runtime) and
`@stoplight/spectral`, so the lodash alert is **reduced but not fully
cleared** by this PR — it needs those separate sources addressed (or a
resolution).
## What
Clears the 2 High `happy-dom` alerts (GHSA-w4gp-fjgq-3q4g,
GHSA-6q6h-j7hj-3r64) via a parent bump — **no resolution**.
`happy-dom@15.11.7` came from **`@wyw-in-js/transform@0.7.0`**
(Linaria's CSS transform), pinned by a root resolution + a local `.yarn`
patch and requested by `@wyw-in-js/vite@^0.7.0` in twenty-front +
twenty-ui-deprecated.
- `@wyw-in-js/vite` `^0.7.0` → `^1.1.0` (twenty-front,
twenty-ui-deprecated)
- `@wyw-in-js/babel-preset` `^0.6.0` → `^1.1.0` (twenty-ui-deprecated)
- **drop the `@wyw-in-js/transform` 0.7.0 resolutions + the `.yarn`
patch** — the patch added a `visited` cycle-guard to
`TransformCacheCollection.invalidateIfChanged`, which is **already
upstream** in transform 1.1.0, so it's obsolete.
`@wyw-in-js/transform` now resolves to **1.1.0** (→ happy-dom 20.10.2)
and 0.8.1 (website, unchanged, → happy-dom 20.8.9). The vulnerable
0.7.0/15.11.7 are gone.
## Required config change
wyw-in-js 1.x resolves modules in its CSS pre-build via vite's
`resolve.alias` instead of `vite-tsconfig-paths`. So twenty-front's `@/`
and `~/` tsconfig path aliases are mirrored into `vite.config`
`resolve.alias` — otherwise the CSS evaluator throws `Cannot find module
'@/...'` for aliased imports used inside `styled` definitions.
## Verification
- happy-dom now **20.8.9 + 20.10.2** (both patched); no 15.x left
- `nx build twenty-front` — CSS extraction works (**1018 files
transformed**) + `typecheck`
- `nx build twenty-ui`, `twenty-ui-deprecated` (Linaria CSS extraction)
- website's Linaria transform runs fine (local build only stops on a
missing `TWENTY_PARTNERS_API_URL` env var, unrelated)
- `yarn install --immutable` clean
## Summary
### Externalize `twenty-client-sdk` from `twenty-sdk`
Previously, `twenty-client-sdk` was listed as a `devDependency` of
`twenty-sdk`, which caused Vite to bundle it inline into the dist
output. This meant end-user apps had two copies of `twenty-client-sdk`:
one hidden inside `twenty-sdk`'s bundle, and one installed explicitly in
their `node_modules`. These copies could drift apart since they weren't
guaranteed to be the same version.
**Change:** Moved `twenty-client-sdk` from `devDependencies` to
`dependencies` in `twenty-sdk/package.json`. Vite's `external` function
now recognizes it and keeps it as an external `require`/`import` in the
dist output. End users get a single deduplicated copy resolved by their
package manager.
### Externalize `twenty-sdk` from `create-twenty-app`
Similarly, `create-twenty-app` had `twenty-sdk` as a `devDependency`
(bundled inline). After refactoring `create-twenty-app` to
programmatically import operations from `twenty-sdk` (instead of
shelling out via `execSync`), it became a proper runtime dependency.
**Change:** Moved `twenty-sdk` from `devDependencies` to `dependencies`
in `create-twenty-app/package.json`.
### Switch E2E CI to `yarn npm publish`
The `workspace:*` protocol in `dependencies` is a Yarn-specific feature.
`npm publish` publishes it as-is (which breaks for consumers), while
`yarn npm publish` automatically replaces `workspace:*` with the
resolved version at publish time (e.g., `workspace:*` becomes `=1.2.3`).
**Change:** Replaced `npm publish` with `yarn npm publish` in
`.github/workflows/ci-create-app-e2e.yaml`.
### Replace `execSync` with programmatic SDK calls in
`create-twenty-app`
`create-twenty-app` was shelling out to `yarn twenty remote add` and
`yarn twenty server start` via `execSync`, which assumed the `twenty`
binary was already installed in the scaffolded app. This was fragile and
created an implicit circular dependency.
**Changes:**
- Replaced `execSync('yarn twenty remote add ...')` with a direct call
to `authLoginOAuth()` from `twenty-sdk/cli`
- Replaced `execSync('yarn twenty server start')` with a direct call to
`serverStart()` from `twenty-sdk/cli`
- Deleted the duplicated `setup-local-instance.ts` from
`create-twenty-app`
### Centralize `serverStart` as a dedicated operation
The Docker server start logic was previously inline in the `server
start` CLI command handler (`server.ts`), and `setup-local-instance.ts`
was shelling out to `yarn twenty server start` to invoke it -- meaning
`twenty-sdk` was calling itself via a child process.
**Changes:**
- Extracted the Docker container management logic into a new
`serverStart` operation (`cli/operations/server-start.ts`)
- Merged the detect-or-start flow from `setup-local-instance.ts` into
`serverStart` (detect across multiple ports, start Docker if needed,
poll for health)
- Deleted `setup-local-instance.ts` from `twenty-sdk`
- Added `onProgress` callback (consistent with other operations like
`appBuild`) instead of direct `console.log` calls
- Both the `server start` CLI command and `create-twenty-app` now call
`serverStart()` programmatically
related to https://github.com/twentyhq/twenty-infra/pull/525
Updates yarn to the latest version 4.9.2 (from 4.4.0).
Also removes the explicit `enableHardenedMode` from yarnrc as it
significantly slows down installation.
This is already enabled automatically for pull requests on Github, thus
preventing lockfile poisoning where it's relevant.
See <https://yarnpkg.com/features/security#hardened-mode>:
> in most cases you won't even have to think about it - the hardened
mode is enabled by default when Yarn detects it runs in a pull request
from a public GitHub repository.
It can additionally be enabled explicitly for specific CI jobs by using
an environment variable, if desired:
> The hardened mode can be set (or disabled) [...] by defining
`YARN_ENABLE_HARDENED_MODE=1|0` in your environment variables
If this is the case, yarn still recommends **not** enabling it
everywhere:
> **DANGER**
>
> The hardened mode makes installs significantly slower as Yarn has to
query the registry to make sure the information contained in the
lockfile are accurate. If your CI pipeline runs multiple jobs, we
recommend disabling the hardened mode in all but one of them so as to
limit the performance impact.
---------
Co-authored-by: prastoin <paul@twenty.com>
This PR fixes a bug with phone input clearing its value when we press
space right after a country calling code.
As the problem comes from the library `react-phone-input-number` this PR
implements a yarn patch.
Fixes https://github.com/twentyhq/twenty/issues/12903
# Introduction
In this PR we've migrated `twenty-shared` from a `vite` app
[libary-mode](https://vite.dev/guide/build#library-mode) to a
[preconstruct](https://preconstruct.tools/) "atomic" application ( in
the future would like to introduce preconstruct to handle of all our
atomic dependencies such as `twenty-emails` `twenty-ui` etc it will be
integrated at the monorepo's root directly, would be to invasive in the
first, starting incremental via `twenty-shared`)
For more information regarding the motivations please refer to nor:
- https://github.com/twentyhq/core-team-issues/issues/587
-
https://github.com/twentyhq/core-team-issues/issues/281#issuecomment-2630949682
close https://github.com/twentyhq/core-team-issues/issues/589
close https://github.com/twentyhq/core-team-issues/issues/590
## How to test
In order to ease the review this PR will ship all the codegen at the
very end, the actual meaning full diff is `+2,411 −114`
In order to migrate existing dependent packages to `twenty-shared` multi
barrel new arch you need to run in local:
```sh
yarn tsx packages/twenty-shared/scripts/migrateFromSingleToMultiBarrelImport.ts && \
npx nx run-many -t lint --fix -p twenty-front twenty-ui twenty-server twenty-emails twenty-shared twenty-zapier
```
Note that `migrateFromSingleToMultiBarrelImport` is idempotent, it's atm
included in the PR but should not be merged. ( such as codegen will be
added before merging this script will be removed )
## Misc
- related opened issue preconstruct
https://github.com/preconstruct/preconstruct/issues/617
## Closed related PR
- https://github.com/twentyhq/twenty/pull/11028
- https://github.com/twentyhq/twenty/pull/10993
- https://github.com/twentyhq/twenty/pull/10960
## Upcoming enhancement: ( in others dedicated PRs )
- 1/ refactor generate barrel to export atomic module instead of `*`
- 2/ generate barrel own package with several files and tests
- 3/ Migration twenty-ui the same way
- 4/ Use `preconstruct` at monorepo global level
## Conclusion
As always any suggestions are welcomed !