Compare commits

...
Author SHA1 Message Date
Sonarly Claude Code c903d614ca Unhandled pendingActivationUser auth context type in buildActorMetadata causes mutation failure during onboarding
https://sonarly.com/issue/14356?type=bug

Users in the onboarding/profile creation flow get a `pendingActivationUser` auth context that is not handled by `buildActorMetadata()`, causing all write mutations (like `UpdateOneWorkspaceMember`) to fail with an unhandled error.

Fix: Added handling for the `pendingActivationUser` auth context type in `buildActorMetadata()`. When a user is in the onboarding flow (at `/create/profile`), they don't yet have a `workspaceMember` record, so the middleware creates a `pendingActivationUser` auth context instead of a `user` context. The fix builds actor metadata from the user entity's `firstName`/`lastName` fields (available on `PendingActivationUserWorkspaceAuthContext.user`) with an empty `workspaceMemberId` since no workspace member exists yet.

Also improved the fallback error message to include `authContext.type`, so any future unhandled context types (e.g., `system`) will produce a more actionable error message for debugging.

Updated the existing test to cover the new `pendingActivationUser` case and updated the `system` type error assertion to match the new error message format.
2026-03-13 11:04:25 +00:00
Sonarly Claude Code 311a5f7e18 SSE optimistic update crashes on custom fields not yet in stale metadata cache
https://sonarly.com/issue/14352?type=bug

When a user creates custom fields on an object and then views that object's records, incoming SSE database events include the new fields, but the frontend metadata cache hasn't refreshed yet, causing a crash in the optimistic record computation.

Fix: **What changed:** In `computeOptimisticRecordFromInput.ts`, replaced the `throw new Error(...)` with `console.warn(...)` when unknown fields are detected in the record input (line 73-77).

**Why:** The function validates that all fields in `recordInput` exist in `objectMetadataItem.fields`. This validation was originally written for mutation-triggered optimistic updates where the frontend controls the input. When SSE real-time events were added (commit `d51c988a9e`, Jan 2026), this function started receiving server-controlled input that may include fields not yet known to the frontend's metadata cache — specifically when a user creates custom fields and immediately views records before the metadata refreshes.

The function's main for-loop (line 80) iterates only over `objectMetadataItem.fields`, so unknown fields in `recordInput` are already naturally skipped. The throw was a defensive assertion that became incorrect when the caller contract changed. Converting it to a warning preserves observability while allowing graceful degradation.

**Test update:** Updated the existing test from asserting a throw to asserting a `console.warn` call and verifying that known fields (like `city: 'Paris'`) are still correctly processed while unknown fields are skipped.
2026-03-13 10:59:58 +00:00
Thomas TrompetteandGitHub dfd28f5b4a Separate create draft cases op (#18613)
Bug: When creating a draft from an activated workflow version, the draft
row was inserted into the database without steps and trigger, then
updated with them in a separate operation. The SSE create-one event
fired on the INSERT, causing the frontend to refetch the draft before
the UPDATE — resulting in steps: null and trigger: null, which crashed
the step editor.

Fix: Reorder the operations so steps are duplicated first, then either
insert a new draft or update an existing one with steps and trigger
already populated. The row never exists in the database without complete
data.
2026-03-13 10:43:20 +00:00
Raphaël BosiandGitHub 349bfc8462 Backfill existing workspaces with standard command menu items (#18596)
Create a command to backfill command menu items.
2026-03-13 09:03:15 +00:00
Baptiste DevessierandGitHub 262f9f5fe1 Re-fetch conditional display property in the frontend (#18601) 2026-03-13 08:32:53 +00:00
Félix MalfaitGitHubcubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>claude[bot] <41898282+claude[bot]@users.noreply.github.com>Claude Opus 4.6
5f558e5539 fix: accept production enterprise keys in development environment (#18611)
## Problem
When `NODE_ENV` is development, the server was only using the dev public
key to verify enterprise JWTs. Production keys are signed with the
production private key, so they failed verification with the dev public
key, resulting in "Invalid enterprise key" errors.

## Solution
Try both production and dev public keys when in development, so
production keys work when testing locally. In production, only the
production key is used (unchanged behavior).

## Changes
- `enterprise-plan.service.ts`: Replaced `getPublicKey()` with
`getPublicKeysToTry()` that returns both keys in development; updated
`verifyJwt()` to try each key until one succeeds
- `enterprise-plan.service.spec.ts`: Added test for production key
acceptance when `NODE_ENV` is development

Made with [Cursor](https://cursor.com)

---------

Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 09:20:14 +01:00
WeikoandGitHub 1cb4c98cb3 Add dataloader and read from cache for view entities (#18594)
## Context
Improve view resolution using cache and dataloader

## Performance Comparison

|Run|Main (no DataLoaders/cache)|Feature Branch (DataLoaders +
cache)|Speedup|
|---|---|---|---|
|1 (cold)|418ms|95ms|~4.4x faster|
|2|42ms|19ms|~2.2x faster|
|3|37ms|19ms|~1.9x faster|
|4|39ms|12ms|~3.2x faster|
|5|33ms|13ms|~2.5x faster|

The biggest improvement is to use dataloaders for the multiple relations
associated with views. Cache is a bit less significant since there are
other cache mechanism such as PostgreSQL buffer cache but it will
probably be more meaningful with bigger workspaces
2026-03-12 18:05:30 +00:00
CopilotGitHubcopilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>Devessier
a3c392ce8b Reset selected widget when exiting record page layout edit mode (#18603)
## Before


https://github.com/user-attachments/assets/b9720898-3433-488b-b784-1fa78e4e68f7

## After


https://github.com/user-attachments/assets/8f3fdde5-773d-44c4-a0f5-cca683736782

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Devessier <29370468+Devessier@users.noreply.github.com>
2026-03-12 17:55:01 +00:00
Charles BochetandGitHub ab13020e2b Fix wrong uuid error on field metadata (#18598)
## Summary
- Same fix as #18590 but applied to `FieldMetadataDTO`
- Changed `universalIdentifier` from `UUID` to `String` type since field
metadata universal identifiers are not necessarily valid UUIDs
- Removed `universalIdentifier` from `FieldFilter` (was using
`UUIDFilterComparison`)
- Updated generated SDK and frontend types accordingly
2026-03-12 18:51:42 +01:00
WeikoandGitHub 3f420c84d7 Fix Flow tab missing for workflow run (#18602)
## Context
Conditional tab rendering was recently introduced for system objects
that now have record page layouts. However Workflow run is a system
object and has a specific "Flow" tab that was not displayed anymore

## Before
<img width="1191" height="640" alt="Screenshot 2026-03-12 at 18 10 03"
src="https://github.com/user-attachments/assets/6f2c6319-6ddf-4906-a83c-0db8a27a8267"
/>

## After
<img width="1299" height="802" alt="Screenshot 2026-03-12 at 18 09 35"
src="https://github.com/user-attachments/assets/35e1e356-e995-43a2-9207-adc0f67cc426"
/>
2026-03-12 17:25:12 +00:00
0ef4741473 i18n - translations (#18595)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-03-12 18:17:42 +01:00
WeikoandGitHub b5db955ac8 Fix sdk metadata client codegen (#18599)
## Context
Previous token was tied to a non-existing token and codegen was failing
locally due to the server throwing.
This is due to a regression introduced here
https://github.com/twentyhq/twenty/pull/18590/changes#diff-848fff5d5b6f9858c8e2391212dfa9da5151cd3b1325d410df8a82250a229558L26
where a token is hardcoded instead of using the one from the ENV
2026-03-12 18:13:37 +01:00
Baptiste DevessierandGitHub 2a6fcfcfb3 Side Panel Sub Page Framework® (#18579)
Replace hard-coded implementations for sub pages in the side panel with
a proper framework
2026-03-12 15:17:29 +00:00
5bfa4c5c39 Fix wrong uuid error (#18590)
as title

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
2026-03-12 16:12:30 +01:00
1685d066be i18n - translations (#18591)
Created by Github action

Co-authored-by: github-actions <github-actions@twenty.com>
2026-03-12 15:55:51 +01:00
Thomas TrompetteandGitHub 6a3281a18d Bug fix batches (#18588)
- clear sse state on logout
- fix no record not selectable through keyboard
- fix book a call design
- fix error notif design
2026-03-12 15:55:30 +01:00
Raphaël BosiandGitHub 741e9a8f81 Update yarn lock (#18589)
https://github.com/twentyhq/twenty/pull/18075
2026-03-12 15:42:01 +01:00
Charles BochetandGitHub 0897575fd0 Fix flaky return-to-path e2e tests (#18580)
## Summary

Fixes flaky `return-to-path` e2e tests that were failing intermittently
in CI merge queue runs.

**Root cause:** In the multi-workspace environment used by CI
(`IS_MULTIWORKSPACE_ENABLED=true`), navigating to
`localhost:3001/settings/accounts` triggers a full page redirect to
`app.localhost:3001/welcome` via `useRedirectToDefaultDomain`. This
redirect is a hard navigation (not a React Router transition), which
clears all in-memory Jotai state — including the `returnToPathState`
atom that stores the path the user should be redirected to after login.
After the redirect, the app has no memory of the intended destination
and falls back to `/objects/companies`.

**Fix:** Before performing the cross-domain redirect in
`useRedirectToDefaultDomain`, read the `returnToPath` from the Jotai
store and pass it as a URL search parameter. On the new page load,
`useInitializeQueryParamState` picks it up from the URL and re-hydrates
the Jotai atom, preserving the return-to-path across the full page
reload.

## Test plan

- [x] Verified locally against production build (`serve -s build`) with
`IS_MULTIWORKSPACE_ENABLED=true` — 33/33 consecutive passes of
`return-to-path.spec.ts`
- [x] Lint passes (`npx nx lint:diff-with-main twenty-front`)
2026-03-12 15:29:35 +01:00
501fcc737f i18n - translations (#18586)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-03-12 15:18:16 +01:00
Raphaël BosiandGitHub c9deab4373 [COMMAND MENU ITEMS] Remove standard front components (#18581)
All standard command menu items will link to an engine component instead
of standard front components.
2026-03-12 15:18:00 +01:00
MarieandGitHub c1da7be6d7 Billing for self-hosts (#18075)
## Summary

Implements enterprise licensing and per-seat billing for self-hosted
environments, with Stripe as the single source of truth for subscription
data.

### Components

- **twenty-website** hosts the private key to sign `ENTERPRISE_KEY` and
`ENTERPRISE_VALIDITY_TOKEN`. It communicates with Stripe to emit the
daily `ENTERPRISE_VALIDITY_TOKEN` if the subscription is active, based
on the user's Stripe subscription ID stored in `ENTERPRISE_KEY`.
- **Stripe** is the single source of truth for subscription data
(status, seats, billing).
- **The client** (twenty-server + DB + workers) saves `ENTERPRISE_KEY`
in the `keyValuePair` table (or `.env` if
`IS_CONFIG_VARIABLES_IN_DB_ENABLED` is false) and the daily-renewed
`ENTERPRISE_VALIDITY_TOKEN` in the `appToken` table.
`ENTERPRISE_VALIDITY_TOKEN` is verified client-side using a public key
to grant access to enterprise features (RLS, SSO, audit logs, etc.).

### Flow

1. When requesting an upgrade to an enterprise plan (from **Enterprise**
in settings), the user is shown a modal to choose monthly/yearly
billing, then redirected to Stripe to enter payment details. After
checkout, they land on twenty-website where they are exposed to their
`ENTERPRISE_KEY`, which they paste in the UI. It is saved in the
`keyValuePair` table. On activation, a first `ENTERPRISE_VALIDITY_TOKEN`
with 30-day validity is stored in the `appToken` table.

2. **Every day**, a cron job runs and does two things:
- **Refreshes the validity token**: communicates with twenty-website to
get a new `ENTERPRISE_VALIDITY_TOKEN` with 30-day validity if the Stripe
subscription is still active. If the subscription is in cancellation,
the emitted token has a validity equal to the cancellation date. If it's
no longer valid, the token is not replaced. The cron only needs to run
every 30 days in practice, but runs daily so it's resilient to
occasional failures.
- **Reports seat count**: counts active (non-soft-deleted)
`UserWorkspace` entries and sends the count to twenty-website, which
updates the Stripe subscription quantity with proration. Seats are also
reported on first activation. If the subscription is canceled or
scheduled for cancellation, the seat update is skipped.

3. `ENTERPRISE_VALIDITY_TOKEN` is verified server-side via a public key
to grant access to enterprise features.

### Key concepts

Three distinct checks are exposed as GraphQL fields on `Workspace`:

| Field | Meaning |
|---|---|
| `hasValidEnterpriseKey` | Has any valid enterprise key (signed JWT
**or** legacy plain string) |
| `hasValidSignedEnterpriseKey` | `ENTERPRISE_KEY` is a properly signed
JWT (billing portal makes sense) |
| `hasValidEnterpriseValidityToken` | `ENTERPRISE_VALIDITY_TOKEN` is
present and not expired (expiration depends on signed token payload, not
on "expiresAt" on appToken table which is only indicative) |

Feature access is gated by `isValid()` =
`hasValidEnterpriseValidityToken || hasValidEnterpriseKey` (to support
both new and legacy keys during transition). After transition isValid()
= hasValidEnterpriseValidityToken

### Frontend states

The Enterprise settings page handles multiple states:
- **No key**: show "Get Enterprise" with checkout modal
- **Orphaned validity token** (token valid but no signed key): prompt
user to set a valid enterprise key
- **Active/trialing but no validity token**: show subscription status
with a "Reload validity token" action
- **Active/trialing**: show full subscription info, billing portal
access, cancel option
- **Cancellation scheduled**: show cancellation date, billing portal
- **Canceled**: show billing history link and option to start a new
subscription
- **Past due / Incomplete**: prompt to update payment or restart

### Temporary retro-compatibility: legacy plain-text keys

Previously, enterprise features were gated by a simple check: any
non-empty string in `ENTERPRISE_KEY` granted access. With this PR, we
transition to a controlled system relying on signed JWTs.

To avoid breaking existing self-hosted users:
- **Legacy plain-text keys still grant access** to enterprise features.
`hasValidEnterpriseKey` returns `true` for both signed JWTs and plain
strings, and `isValid()` checks `hasValidEnterpriseKey` as a fallback
when no validity token is present.
- **A deprecation banner** is shown at the top of the app when
`hasValidEnterpriseKey` is `true` but `hasValidSignedEnterpriseKey` is
`false`, informing the user that their key format is deprecated and they
should activate a new signed key.
- **No billing portal or subscription management** is available for
legacy keys since there is no Stripe subscription to manage.

This retro-compatibility will be removed in a future version. At that
point, `isValid()` will only check `hasValidEnterpriseValidityToken`.

### Edge cases

- **Air-gapped / production environments**: for self-hosted clients that
block external traffic (or for our own production), provide a long-lived
`ENTERPRISE_VALIDITY_TOKEN` (e.g. 99 years) directly in the `appToken`
table, with no `ENTERPRISE_KEY`. The daily cron will skip the refresh
(no enterprise key to authenticate with), but the pre-seeded validity
token will be used to grant feature access. No billing or seat reporting
occurs in this mode.
- **`IS_CONFIG_VARIABLES_IN_DB_ENABLED` is false**: if the user tries to
activate an enterprise key but DB config writes are disabled, the
backend returns a clear error asking them to add `ENTERPRISE_KEY` to
their `.env` file manually.
- **Canceled subscriptions**: the `/seats` endpoint skips Stripe updates
for canceled or cancellation-scheduled subscriptions to avoid Stripe API
errors.

### How to test
- launch twenty-website on a different url (eg localhost:1002)
- add ENTERPRISE_API_URL=http://localhost:3002/api/enterprise (or else)
in your server .env
- ask me for twenty-website's .env file content (STRIPE_SECRET_KEY;
STRIPE_ENTERPRISE_MONTHLY_PRICE_ID;STRIPE_ENTERPRISE_YEARLY_PRICE_ID;
ENTERPRISE_JWT_PRIVATE_KEY; ENTERPRISE_JWT_PUBLIC_KEY;
NEXT_PUBLIC_WEBSITE_URL)
- visit Admin panel / enterprise
2026-03-12 15:07:53 +01:00
WeikoandGitHub c59f420d21 Hide tabs for system objects (#18583)
<img width="1286" height="793" alt="Screenshot 2026-03-12 at 13 57 16"
src="https://github.com/user-attachments/assets/bebfd23f-3172-424a-95ee-ba95358a6196"
/>
2026-03-12 14:46:15 +01:00
WeikoandGitHub 06d4d62e90 Move 1.19 backfill pagelayout and views to 1.20 (#18582) 2026-03-12 13:46:07 +01:00
WeikoandGitHub eb4665bc98 Create missing standard table and fields widget views (#18543) 2026-03-12 13:28:05 +01:00
f19fcd0010 i18n - translations (#18578)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-03-12 13:23:54 +01:00
Lucas BordeauandGitHub cb3e32df86 Fix AI demo workspace skill (#18575)
This PR fixes what allows to have a working demo workspace skill.

- Skill updated many times into something that works
- Fixed infinite loop in AI chat by memoizing ai-sdk output
- Finished navigateToView implementation
- Increased MAX_STEPS to 300 so the chat don't quit in the middle of a
long running skill
- Added CreateManyRelationFields
2026-03-12 13:19:01 +01:00
Hamza FaidiandGitHub db5b4d9c6c fix: replace unsafe JSON.parse casts with parseJson in filter dropdowns (#18513)
## Problem

Four filter dropdown components were calling `JSON.parse(filter.value)
as string[]` to parse stored filter state. This throws a `SyntaxError`
if the value is malformed (truncated URL, stale localStorage, migration
artifact), crashing the entire dropdown with no recovery.

## Solution

Replace with the existing `parseJson<string[]>` utility from
`twenty-shared`, which wraps `JSON.parse` in a try/catch and returns
`null` on failure. The `?? []` fallback gracefully degrades to an empty
selection instead of crashing.

All four files had an explicit `// TODO: replace by a safe parse`
marking this as a known issue.

## Testing


No new tests — `parseJson` is already tested in `twenty-shared`. No new
logic introduced.

## issue link 
#18514
2026-03-12 13:15:22 +01:00
Charles BochetandGitHub 660536d6bb Fix onboarding flow: workspace creation modal and invite team skip (#18577)
## Summary

- **Fix create-profile modal not showing after workspace creation**:
After activating a workspace, `CreateWorkspace.onSubmit` called
`refreshObjectMetadataItems()` which only updated the
`objectMetadataItemsState` atom but never marked the metadata store as
ready (`metadataStoreState` stayed at `'empty'`). Since `MetadataGater`
excludes `CreateWorkspace` but not `CreateProfile` from its loading
check, navigating to `/create/profile` triggered the skeleton loader
instead of the modal. The fix adds the full metadata pipeline after
refresh — `updateDraft('objectMetadataItems')` + `applyChanges()` for
objects, and `fetchAndLoadIndexViews()` for views — so
`isAppMetadataReady` is `true` before navigation.

- **Fix invite-team "Skip" not persisting to server**: Clicking "Skip"
on the invite-team page called `setNextOnboardingStatus()` which only
updated the local Jotai atom. The early return for empty emails bypassed
`sendInvitation`, so the server never cleared the
`ONBOARDING_INVITE_TEAM_PENDING` user var. On page refresh,
`GetCurrentUser` returned `INVITE_TEAM` and the user was stuck. The fix
removes the early return so `sendInvitation({ emails: [] })` always runs
— the server handles empty arrays fine and clears the pending flag.
2026-03-12 13:15:05 +01:00
e8f8189167 [COMMAND MENU ITEMS] Add engine component key (#18554)
## PR Description

In the process of migrating all the existing commands to the backend, we
stumbled across a couple of problems that made us reconsider the full
migration. This PR introduces a way for command menu items to bypass
front components and to directly reference a frontend component from
twenty front.

It:
- Introduces a `engineFrontComponentKey` field on `CommandMenuItem` as
an alternative to `frontComponentId` and `workflowVersionId`, allowing
command menu items to reference frontend components by key directly
rather than requiring a FrontComponent entity
- Updates the DB constraint to allow exactly one of `workflowVersionId`,
`frontComponentId`, or `engineFrontComponentKey`

### All standard command menu items from the frontend which use
`standardFrontComponentKey`

These are all commands that execute a GraphQL query or a mutation.
Two mains concerned have been raised that made us go with this
(temporary) architecture instead:
- If those commands are part of the standard application, they can only
alter objects from that application and not custom objects.
- We would need to implement a way to trigger optimistic rendering from
the front components, which might take some time to implement.

List:
- Create new record
- Delete (single record)
- Delete records (multiple)
- Restore record
- Restore records (multiple)
- Permanently destroy record
- Permanently destroy records (multiple)
- Add to favorites
- Remove from favorites
- Merge records
- Duplicate Dashboard
- Save Dashboard
- Save Page Layout
- Activate Workflow
- Deactivate Workflow
- Discard Draft (workflow)
- Test Workflow
- Tidy up workflow
- Duplicate Workflow
- Stop (workflow run)
- Use as draft (workflow version)

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
2026-03-12 13:14:45 +01:00
martmullandGitHub 78473a606a Fix app dev flickering (#18562)
- fix ticker issue
- fix too many rendering
2026-03-12 11:58:44 +01:00
neo773andGitHub b21fb4aa6f Fix PDF Upload edge case (#18533)
we were using an older version of `file-type` which has limited support
for PDF as it's a complex spec
Updated to latest version which includes support for plugins and added
`@file-type/pdf` which has extensive spec compliant detection approach

fixes TWENTY-SERVER-FAN
2026-03-12 10:34:24 +00:00
38664249cf i18n - translations (#18576)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-03-12 11:24:07 +01:00
Baptiste DevessierandGitHub 69542898a1 Display a single Add a Section button (#18563)
- Display a single Add a Section button at the end of the list
- Move other buttons to the section's dropdown menu


https://github.com/user-attachments/assets/b51d8846-635a-477a-9205-bf3266cfcff4
2026-03-12 10:01:48 +00:00
09beddb63d i18n - docs translations (#18566)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-03-12 10:55:44 +01:00
Hamza FaidiandGitHub 2eac82c207 fix(front): stabilize downloadFile unit test and return promise chain (#18484)
# Description

## What this PR fixes
This PR fixes a flaky/skipped unit test for
[downloadFile](vscode-file://vscode-app/Applications/Visual%20Studio%20Code.app/Contents/Resources/app/out/vs/code/electron-browser/workbench/workbench.html)
and aligns the test with the actual implementation.

## Changes made
Updated
[downloadFile](vscode-file://vscode-app/Applications/Visual%20Studio%20Code.app/Contents/Resources/app/out/vs/code/electron-browser/workbench/workbench.html)
test to validate file-saver behavior instead of DOM anchor creation.
Mocked
[saveAs](vscode-file://vscode-app/Applications/Visual%20Studio%20Code.app/Contents/Resources/app/out/vs/code/electron-browser/workbench/workbench.html)
from file-saver and asserted it is called with the fetched blob and
filename.
Added proper async assertions for:
successful file download
failed fetch path ([status !==
200](vscode-file://vscode-app/Applications/Visual%20Studio%20Code.app/Contents/Resources/app/out/vs/code/electron-browser/workbench/workbench.html))
rejecting with Failed downloading file
Updated
[downloadFile](vscode-file://vscode-app/Applications/Visual%20Studio%20Code.app/Contents/Resources/app/out/vs/code/electron-browser/workbench/workbench.html)
implementation to return the fetch promise chain so callers/tests can
await it reliably.

## Related Issue
Closes #18485
2026-03-12 09:00:50 +01:00
f262437da6 Refactor dev environment setup with auto-detection and Docker support (#18564)
## Summary
Completely rewrites the development environment setup script to be more
robust, idempotent, and flexible. The new implementation auto-detects
available services (local PostgreSQL/Redis vs Docker), provides multiple
operational modes, and includes comprehensive health checks and error
handling.

## Key Changes

- **Enhanced setup script** (`packages/twenty-utils/setup-dev-env.sh`):
- Added auto-detection logic to prefer local services (PostgreSQL 16,
Redis) over Docker
  - Implemented service health checks with retry logic (30s timeout)
- Added command-line flags: `--docker` (force Docker), `--down` (stop
services), `--reset` (wipe data)
- Improved error handling with `set -euo pipefail` and descriptive
failure messages
- Added helper functions for service detection, startup, and status
checking
  - Fallback to manual `.env` file copying if Nx is unavailable
  - Enhanced output with clear status messages and usage instructions

- **New Docker Compose file**
(`packages/twenty-docker/docker-compose.dev.yml`):
  - Dedicated development infrastructure file (PostgreSQL 16 + Redis 7)
  - Includes health checks for both services
  - Configured with appropriate restart policies and volume management
  - Separate from production compose configuration

- **Updated documentation** (`CLAUDE.md`):
- Clarified that all environments (CI, local, Claude Code, Cursor) use
the same setup script
  - Documented new command-line flags and their purposes
- Noted that CI workflows manage services independently via GitHub
Actions

- **Updated Cursor environment config** (`.cursor/environment.json`):
- Simplified to use the new unified setup script instead of complex
inline commands

## Implementation Details

The script now follows a clear three-phase approach:
1. **Service startup** — Auto-detects and starts PostgreSQL and Redis
(local or Docker)
2. **Database creation** — Creates 'default' and 'test' databases
3. **Environment configuration** — Sets up `.env` files via Nx or direct
file copy

The auto-detection logic prioritizes local services for better
performance while gracefully falling back to Docker if local services
aren't available. All operations are idempotent and safe to run multiple
times.

https://claude.ai/code/session_01UDxa2Kp1ub9tTL3pnpBVFs

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-12 08:43:58 +01:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
15d0970f72 Bump @swc/core from 1.15.11 to 1.15.18 (#18570)
Bumps
[@swc/core](https://github.com/swc-project/swc/tree/HEAD/packages/core)
from 1.15.11 to 1.15.18.
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/swc-project/swc/blob/main/CHANGELOG.md"><code>@​swc/core</code>'s
changelog</a>.</em></p>
<blockquote>
<h2>[1.15.18] - 2026-03-01</h2>
<h3>Bug Fixes</h3>
<ul>
<li><strong>(html/wasm)</strong> Publish <code>@​swc/html-wasm</code>
for nodejs (<a
href="https://redirect.github.com/swc-project/swc/issues/11601">#11601</a>)
(<a
href="https://github.com/swc-project/swc/commit/bd443f582c553e9d898a1d5e7395abaad60b26d2">bd443f5</a>)</li>
</ul>
<h3>Documentation</h3>
<ul>
<li>
<p>Add AGENTS note about next-gen ast (<a
href="https://redirect.github.com/swc-project/swc/issues/11592">#11592</a>)
(<a
href="https://github.com/swc-project/swc/commit/80b4be872d85dc82cbb6e84c91fe102d807a2780">80b4be8</a>)</p>
</li>
<li>
<p>Add typescript-eslint AST compatibility note (<a
href="https://redirect.github.com/swc-project/swc/issues/11598">#11598</a>)
(<a
href="https://github.com/swc-project/swc/commit/c7bfebec4fb691e6e49f3c3b7b257be178e7f238">c7bfebe</a>)</p>
</li>
</ul>
<h3>Features</h3>
<ul>
<li>
<p><strong>(es/ast)</strong> Add runtime arena crate and bootstrap
swc_es_ast (<a
href="https://redirect.github.com/swc-project/swc/issues/11588">#11588</a>)
(<a
href="https://github.com/swc-project/swc/commit/7a06d967e43fe2f84078fc241bc655b41450d2c1">7a06d96</a>)</p>
</li>
<li>
<p><strong>(es/parser)</strong> Add <code>swc_es_parser</code> (<a
href="https://redirect.github.com/swc-project/swc/issues/11593">#11593</a>)
(<a
href="https://github.com/swc-project/swc/commit/f11fd705ee84909f6b0f984b1b5fc35abf73ec05">f11fd70</a>)</p>
</li>
</ul>
<h3>Ci</h3>
<ul>
<li>Triage main CI breakage (<a
href="https://redirect.github.com/swc-project/swc/issues/11589">#11589</a>)
(<a
href="https://github.com/swc-project/swc/commit/075af578c46c0bfdb74c450c157d0e1753024a36">075af57</a>)</li>
</ul>
<h2>[1.15.17] - 2026-02-26</h2>
<h3>Documentation</h3>
<ul>
<li>Add submodule update step before test runs (<a
href="https://redirect.github.com/swc-project/swc/issues/11576">#11576</a>)
(<a
href="https://github.com/swc-project/swc/commit/81b22c31d1acb447caae1a2d2bd530b2e6a40c26">81b22c3</a>)</li>
</ul>
<h3>Features</h3>
<ul>
<li>
<p><strong>(bindings)</strong> Add html wasm binding and publish wiring
(<a
href="https://redirect.github.com/swc-project/swc/issues/11587">#11587</a>)
(<a
href="https://github.com/swc-project/swc/commit/b3869c3ae2a592d4539f4cbfbabeaf615e55d69e">b3869c3</a>)</p>
</li>
<li>
<p><strong>(sourcemap)</strong> Support safe scopes round-trip metadata
(<a
href="https://redirect.github.com/swc-project/swc/issues/11581">#11581</a>)
(<a
href="https://github.com/swc-project/swc/commit/de2a348daed80e47c75dabaf2f0ce945d850210a">de2a348</a>)</p>
</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/swc-project/swc/commit/7cb1be24a7857a94abd7f3cfe9709d22ac314379"><code>7cb1be2</code></a>
chore: Publish <code>1.15.18</code> with <code>swc_core</code>
<code>v58.0.1</code></li>
<li><a
href="https://github.com/swc-project/swc/commit/2821ed060a6f59a168ab8c60cde08ddc3e5cf0d5"><code>2821ed0</code></a>
chore: Publish <code>1.15.18-nightly-20260301.1</code> with
<code>swc_core</code> <code>v58.0.1</code></li>
<li><a
href="https://github.com/swc-project/swc/commit/08806dfffa4361414f1aad647bc1a9206ac29dbc"><code>08806df</code></a>
chore: Publish <code>1.15.17</code> with <code>swc_core</code>
<code>v58.0.1</code></li>
<li><a
href="https://github.com/swc-project/swc/commit/2fdd5ee6b17733583ba7cf5102534826d0d853bf"><code>2fdd5ee</code></a>
chore: Publish <code>1.15.17-nightly-20260226.1</code> with
<code>swc_core</code> <code>v58.0.1</code></li>
<li><a
href="https://github.com/swc-project/swc/commit/316e5035014020d6430262b4fc5e1b7cf4be9980"><code>316e503</code></a>
chore: Publish <code>1.15.16-nightly-20260226.1</code> with
<code>swc_core</code> <code>v58.0.1</code></li>
<li><a
href="https://github.com/swc-project/swc/commit/fb0ce2af0c6a88cd74bb3e55434f3081e7a5aa75"><code>fb0ce2a</code></a>
chore: Publish <code>1.15.15-nightly-20260226.1</code> with
<code>swc_core</code> <code>v58.0.1</code></li>
<li><a
href="https://github.com/swc-project/swc/commit/5f8fc7bac5be0e25a674455c581cea476aa2f6c7"><code>5f8fc7b</code></a>
chore: Publish <code>1.15.14-nightly-20260225.1</code> with
<code>swc_core</code> <code>v58.0.1</code></li>
<li><a
href="https://github.com/swc-project/swc/commit/2e0ea183f4a92735243a02829d8e02237aa94de3"><code>2e0ea18</code></a>
chore: Publish <code>1.15.13</code> with <code>swc_core</code>
<code>v57.0.1</code></li>
<li><a
href="https://github.com/swc-project/swc/commit/4e7a14c3a28ff667bb1aaac6e4aab83af626b173"><code>4e7a14c</code></a>
chore: Publish <code>1.15.13-nightly-20260223.1</code> with
<code>swc_core</code> <code>v57.0.1</code></li>
<li><a
href="https://github.com/swc-project/swc/commit/efccf48e991f21211377239ece3d7c1475eaae84"><code>efccf48</code></a>
chore: Publish <code>1.15.12-nightly-20260222.1</code> with
<code>swc_core</code> <code>v57.0.1</code></li>
<li>See full diff in <a
href="https://github.com/swc-project/swc/commits/v1.15.18/packages/core">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=@swc/core&package-manager=npm_and_yarn&previous-version=1.15.11&new-version=1.15.18)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-12 07:16:45 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
1adc325887 Bump path-to-regexp from 8.2.0 to 8.3.0 (#18571)
Bumps [path-to-regexp](https://github.com/pillarjs/path-to-regexp) from
8.2.0 to 8.3.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/pillarjs/path-to-regexp/releases">path-to-regexp's
releases</a>.</em></p>
<blockquote>
<h2>8.3.0</h2>
<p><strong>Changed</strong></p>
<ul>
<li>Add custom error class (<a
href="https://redirect.github.com/pillarjs/path-to-regexp/issues/398">#398</a>)
2a7f2a4</li>
<li>Allow plain objects for <code>TokenData</code> (<a
href="https://redirect.github.com/pillarjs/path-to-regexp/issues/391">#391</a>)
687a9bb</li>
<li>Escape text should escape backslash (<a
href="https://redirect.github.com/pillarjs/path-to-regexp/issues/390">#390</a>)
a4a8552</li>
<li>Improved error messages and stack size (<a
href="https://redirect.github.com/pillarjs/path-to-regexp/issues/363">#363</a>)
a6bdf40</li>
</ul>
<p><strong>Other</strong></p>
<ul>
<li>Minifying the parser
<ul>
<li>PR (<a
href="https://redirect.github.com/pillarjs/path-to-regexp/issues/401">#401</a>)
9df2448</li>
<li>PR (<a
href="https://redirect.github.com/pillarjs/path-to-regexp/issues/395">#395</a>)
4a91505</li>
<li>Shaving some bytes  d63f44b</li>
<li>Remove optional operator  973d15c</li>
</ul>
</li>
</ul>
<p><a
href="https://github.com/pillarjs/path-to-regexp/compare/v8.2.0...v8.3.0">https://github.com/pillarjs/path-to-regexp/compare/v8.2.0...v8.3.0</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/pillarjs/path-to-regexp/commit/c4f5b3fc10782a5de2bee55c3e40e5af890c9cad"><code>c4f5b3f</code></a>
8.3.0</li>
<li><a
href="https://github.com/pillarjs/path-to-regexp/commit/6587c812746cba94855867612f3a719bb25f794e"><code>6587c81</code></a>
Move parameter name errors up in docs (<a
href="https://redirect.github.com/pillarjs/path-to-regexp/issues/402">#402</a>)</li>
<li><a
href="https://github.com/pillarjs/path-to-regexp/commit/9df2448fdfca9d2957cf47a1777b5deda9be18cf"><code>9df2448</code></a>
Remove more bytes from parser (<a
href="https://redirect.github.com/pillarjs/path-to-regexp/issues/401">#401</a>)</li>
<li><a
href="https://github.com/pillarjs/path-to-regexp/commit/012a54a83c9e7fe77d1ee436c67048bca0512aca"><code>012a54a</code></a>
Bump actions/checkout from 4 to 5 (<a
href="https://redirect.github.com/pillarjs/path-to-regexp/issues/403">#403</a>)</li>
<li><a
href="https://github.com/pillarjs/path-to-regexp/commit/9385f7df7406b4607c3d18dfb276d5371f885418"><code>9385f7d</code></a>
Remove engines from package (<a
href="https://redirect.github.com/pillarjs/path-to-regexp/issues/399">#399</a>)</li>
<li><a
href="https://github.com/pillarjs/path-to-regexp/commit/2a7f2a4e9ba42eee41aa9d7a1a69eddb43b79a61"><code>2a7f2a4</code></a>
Add custom error class (<a
href="https://redirect.github.com/pillarjs/path-to-regexp/issues/398">#398</a>)</li>
<li><a
href="https://github.com/pillarjs/path-to-regexp/commit/265a2a7a26916a18fc6d1c5936c878a32a0fedb7"><code>265a2a7</code></a>
100% test coverage (<a
href="https://redirect.github.com/pillarjs/path-to-regexp/issues/396">#396</a>)</li>
<li><a
href="https://github.com/pillarjs/path-to-regexp/commit/4a915059a843dfdd122a0c4936837c7fdda2d4ee"><code>4a91505</code></a>
Reduce bytes in parse function (<a
href="https://redirect.github.com/pillarjs/path-to-regexp/issues/395">#395</a>)</li>
<li><a
href="https://github.com/pillarjs/path-to-regexp/commit/687a9bbc735245b2688c17db7e9fe86013ea0c77"><code>687a9bb</code></a>
Allow plain objects for <code>TokenData</code> (<a
href="https://redirect.github.com/pillarjs/path-to-regexp/issues/391">#391</a>)</li>
<li><a
href="https://github.com/pillarjs/path-to-regexp/commit/a4a8552c9fb4449c470fb9ead458df1c89cadb72"><code>a4a8552</code></a>
Escape text should escape backslash (<a
href="https://redirect.github.com/pillarjs/path-to-regexp/issues/390">#390</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/pillarjs/path-to-regexp/compare/v8.2.0...v8.3.0">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=path-to-regexp&package-manager=npm_and_yarn&previous-version=8.2.0&new-version=8.3.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-12 06:50:07 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
5726bd6e17 Bump oxlint from 1.51.0 to 1.53.0 (#18569)
Bumps [oxlint](https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint)
from 1.51.0 to 1.53.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/oxc-project/oxc/releases">oxlint's
releases</a>.</em></p>
<blockquote>
<h2>oxlint v1.27.0 &amp;&amp; oxfmt v0.12.0</h2>
<h1>Oxlint v1.27.0</h1>
<h3>🚀 Features</h3>
<ul>
<li>222a8f0 linter/plugins: Implement
<code>SourceCode#isSpaceBetween</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15498">#15498</a>)
(overlookmotel)</li>
<li>2f9735d linter/plugins: Implement
<code>context.languageOptions</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15486">#15486</a>)
(overlookmotel)</li>
<li>bc731ff linter/plugins: Stub out all <code>Context</code> APIs (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15479">#15479</a>)
(overlookmotel)</li>
<li>5822cb4 linter/plugins: Add <code>extend</code> method to
<code>FILE_CONTEXT</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15477">#15477</a>)
(overlookmotel)</li>
<li>7b1e6f3 apps: Add pure rust binaries and release to github (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15469">#15469</a>)
(Boshen)</li>
<li>2a89b43 linter: Introduce debug assertions after fixes to assert
validity (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15389">#15389</a>)
(camc314)</li>
<li>ad3c45a editor: Add <code>oxc.path.node</code> option (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15040">#15040</a>)
(Sysix)</li>
</ul>
<h3>🐛 Bug Fixes</h3>
<ul>
<li>6f3cd77 linter/no-var: Incorrect warning for blocks (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15504">#15504</a>)
(Hamir Mahal)</li>
<li>6957fb9 linter/plugins: Do not allow access to
<code>Context#id</code> in <code>createOnce</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15489">#15489</a>)
(overlookmotel)</li>
<li>7409630 linter/plugins: Allow access to <code>cwd</code> in
<code>createOnce</code> in ESLint interop mode (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15488">#15488</a>)
(overlookmotel)</li>
<li>732205e parser: Reject <code>using</code> / <code>await using</code>
in a switch <code>case</code> / <code>default</code> clause (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15225">#15225</a>)
(sapphi-red)</li>
<li>a17ca32 linter/plugins: Replace <code>Context</code> class (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15448">#15448</a>)
(overlookmotel)</li>
<li>ecf2f7b language_server: Fail gracefully when tsgolint executable
not found (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15436">#15436</a>)
(camc314)</li>
<li>3c8d3a7 lang-server: Improve logging in failure case for tsgolint
(<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15299">#15299</a>)
(camc314)</li>
<li>ef71410 linter: Use jsx if source type is JS in fix debug assertion
(<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15434">#15434</a>)
(camc314)</li>
<li>e32bbf6 linter/no-var: Handle TypeScript declare keyword in fixer
(<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15426">#15426</a>)
(camc314)</li>
<li>6565dbe linter/switch-case-braces: Skip comments when searching for
<code>:</code> token (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15425">#15425</a>)
(camc314)</li>
<li>85bd19a linter/prefer-class-fields: Insert value after type
annotation in fixer (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15423">#15423</a>)
(camc314)</li>
<li>fde753e linter/plugins: Block access to
<code>context.settings</code> in <code>createOnce</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15394">#15394</a>)
(overlookmotel)</li>
<li>ddd9f9f linter/forward-ref-uses-ref: Dont suggest removing wrapper
in invalid positions (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15388">#15388</a>)
(camc314)</li>
<li>dac2a9c linter/no-template-curly-in-string: Remove fixer (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15387">#15387</a>)
(camc314)</li>
<li>989b8e3 linter/no-var: Only fix to <code>const</code> if the var has
an initializer (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15385">#15385</a>)
(camc314)</li>
<li>cc403f5 linter/plugins: Return empty object for unimplemented
parserServices (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15364">#15364</a>)
(magic-akari)</li>
</ul>
<h3> Performance</h3>
<ul>
<li>25d577e language_server: Start tools in parallel (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15500">#15500</a>)
(Sysix)</li>
<li>3c57291 linter/plugins: Optimize loops (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15449">#15449</a>)
(overlookmotel)</li>
<li>3166233 linter/plugins: Remove <code>Arc</code>s (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15431">#15431</a>)
(overlookmotel)</li>
<li>9de1322 linter/plugins: Lazily deserialize settings JSON (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15395">#15395</a>)
(overlookmotel)</li>
<li>3049ec2 linter/plugins: Optimize <code>deepFreezeSettings</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15392">#15392</a>)
(overlookmotel)</li>
<li>444ebfd linter/plugins: Use single object for
<code>parserServices</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15378">#15378</a>)
(overlookmotel)</li>
</ul>
<h3>📚 Documentation</h3>
<ul>
<li>97d2104 linter: Update comment in lint.rs about default value for
tsconfig path (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15530">#15530</a>)
(Connor Shea)</li>
<li>2c6bd9e linter: Always refer as &quot;ES2015&quot; instead of
&quot;ES6&quot; (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15411">#15411</a>)
(sapphi-red)</li>
<li>a0c5203 linter/import/named: Update &quot;ES7&quot; comment in
examples (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15410">#15410</a>)
(sapphi-red)</li>
<li>3dc24b5 linter,minifier: Always refer as &quot;ES Modules&quot;
instead of &quot;ES6 Modules&quot; (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15409">#15409</a>)
(sapphi-red)</li>
<li>2ad77fb linter/no-this-before-super: Correct &quot;Why is this
bad?&quot; section (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15408">#15408</a>)
(sapphi-red)</li>
<li>57f0ce1 linter: Add backquotes where appropriate (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/15407">#15407</a>)
(sapphi-red)</li>
</ul>
<h1>Oxfmt v0.12.0</h1>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/oxc-project/oxc/blob/main/npm/oxlint/CHANGELOG.md">oxlint's
changelog</a>.</em></p>
<blockquote>
<h1>Changelog</h1>
<p>All notable changes to this package will be documented in this
file.</p>
<p>The format is based on <a
href="https://keepachangelog.com/en/1.0.0">Keep a Changelog</a>.</p>
<h2>[1.52.0] - 2026-03-09</h2>
<h3>🚀 Features</h3>
<ul>
<li>61bf388 linter: Add
<code>options.reportUnusedDisableDirectives</code> to config file (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/19799">#19799</a>)
(Peter Wagenet)</li>
<li>2919313 linter: Introduce denyWarnings config options (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/19926">#19926</a>)
(camc314)</li>
<li>a607119 linter: Introduce maxWarnings config option (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/19777">#19777</a>)
(camc314)</li>
</ul>
<h3>📚 Documentation</h3>
<ul>
<li>6c0e0b5 linter: Add oxlint.config.ts to the config docs. (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/19941">#19941</a>)
(connorshea)</li>
<li>160e423 linter: Add a note that the typeAware and typeCheck options
require oxlint-tsgolint (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/19940">#19940</a>)
(connorshea)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/oxc-project/oxc/commit/856781f99c7eb521b6221fab0047cfd09343df50"><code>856781f</code></a>
release(apps): oxlint v1.53.0 &amp;&amp; oxfmt v0.38.0 (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/20218">#20218</a>)</li>
<li><a
href="https://github.com/oxc-project/oxc/commit/9870467e0025f0cca44b24cda5ccaa9414b51a56"><code>9870467</code></a>
release(apps): oxlint v1.52.0 &amp;&amp; oxfmt v0.37.0 (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/20143">#20143</a>)</li>
<li><a
href="https://github.com/oxc-project/oxc/commit/61bf3883fd8435e061a055e528db6f664e737132"><code>61bf388</code></a>
feat(linter): add <code>options.reportUnusedDisableDirectives</code> to
config file (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/19">#19</a>...</li>
<li><a
href="https://github.com/oxc-project/oxc/commit/6c0e0b5721cd791f82e36bc7376f7417518c0548"><code>6c0e0b5</code></a>
docs(linter): Add oxlint.config.ts to the config docs. (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/19941">#19941</a>)</li>
<li><a
href="https://github.com/oxc-project/oxc/commit/160e423dce9900f7f7c6bce7f6845229d5732f8b"><code>160e423</code></a>
docs(linter): Add a note that the typeAware and typeCheck options
require oxl...</li>
<li><a
href="https://github.com/oxc-project/oxc/commit/2919313574b988ae9c711c9808fee57bea4d326f"><code>2919313</code></a>
feat(linter): introduce denyWarnings config options (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/19926">#19926</a>)</li>
<li><a
href="https://github.com/oxc-project/oxc/commit/a60711957ea4244885134972bbaac8810f42451c"><code>a607119</code></a>
feat(linter): introduce maxWarnings config option (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint/issues/19777">#19777</a>)</li>
<li>See full diff in <a
href="https://github.com/oxc-project/oxc/commits/oxlint_v1.53.0/npm/oxlint">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=oxlint&package-manager=npm_and_yarn&previous-version=1.51.0&new-version=1.53.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-12 06:38:30 +00:00
Abdul RahmanandGitHub 102b49f919 Fix new navbar item position after reordering existing items (#18516)
Closes [#2296](https://github.com/twentyhq/core-team-issues/issues/2296)
2026-03-11 20:35:15 +00:00
Thomas TrompetteandGitHub 2af3121c51 Fix dashboard creation + role permission page design (#18565)
1. **Creating a new dashboard crashes with "Tab not found"** and widgets
can't be added after the crash is prevented.

**Root cause:** `initializePageLayout` wrapped both the persisted and
draft state updates behind an `isDeeplyEqual` guard. After navigation,
`resetPageLayoutEditMode` resets the draft atom to its default but
leaves the persisted atom untouched. On re-initialization,
`isDeeplyEqual` returns true (persisted unchanged), so the draft is
never repopulated. But edit mode is still activated.

**Fix**: Move the draft store.set outside the isDeeplyEqual guard so
it's always set on initialization. Also add a defensive check in
`PageLayoutRendererContent` to prevent the crash when activeTabId
doesn't match available tabs.


https://github.com/user-attachments/assets/bcd69866-63eb-4e5e-a1bb-655e71ba6dc5

2. **Permission role page design broken**
Before
<img width="573" height="1130" alt="role-page-broken"
src="https://github.com/user-attachments/assets/09f60fd2-ef08-4133-bb28-034b15579481"
/>

After
<img width="573" height="266" alt="Capture d’écran 2026-03-11 à 14 25
55"
src="https://github.com/user-attachments/assets/c34f9993-51e1-4108-a7e6-f434f558edfd"
/>
2026-03-11 17:57:54 +00:00
Thomas TrompetteandGitHub a024a04e01 Fix breadcrumb infinite loop (#18561)
`RecordTableNoRecordGroupScrollToPreviousRecordEffect` uses
`useAtomState(lastShowPageRecordIdState)` to read the atom value and
check whether to trigger an effect. Inside `run()`, it calls
`setLastShowPageRecordId(null)` to reset the atom, then`
triggerInitialRecordTableDataLoad()` which fires many `store.set()`
calls on other atoms.

These high-frequency store updates cause the component to re-render
before Jotai's internal useReducer dispatch (propagating the null value)
is processed by React. The result: useAtomState returns a stale non-null
value on every subsequent render, even though the Jotai store already
holds null. The effect re-runs, sees the stale non-null value, calls
`run()` again, creating an infinite loop.

This is a Jotai v2 edge case where useAtom's rendered value desyncs from
the actual store value under high-frequency concurrent updates.

### The fix

Read lastShowPageRecordId directly from the Jotai store via
`store.get()` inside the effect instead of relying on the rendered value
from useAtomState. This guarantees the effect always sees the true store
value and correctly skips when the atom is null.
2026-03-11 18:26:02 +01:00
Félix MalfaitGitHubclaude[bot] <41898282+claude[bot]@users.noreply.github.com>
f0c83434a7 feat: default code interpreter and logic function to Disabled in production (#18559)
## Summary

For security reasons, the code interpreter and logic function drivers
now default based on `NODE_ENV`:

- **Production** (`NODE_ENV=production` or unset): Default to
**Disabled**
- **Development** (`NODE_ENV=development`): Default to **LOCAL** for
convenience

This ensures self-hosted production deployments don't accidentally run
user-provided code without explicit configuration.

## Changes

### Config (`config-variables.ts`)
- `CODE_INTERPRETER_TYPE`: Disabled in prod, LOCAL in dev
- `LOGIC_FUNCTION_TYPE`: Disabled in prod, LOCAL in dev

### Documentation (`setup.mdx`)
- Added **Security Defaults** section explaining NODE_ENV-based behavior
- Fixed variable names: `SERVERLESS_TYPE` → `LOGIC_FUNCTION_TYPE`,
`SERVERLESS_LAMBDA_*` → `LOGIC_FUNCTION_LAMBDA_*`
- Added **Code Interpreter** section with available drivers (Disabled,
Local, E2B)

### Environment files
- `.env.example`: Updated to `LOGIC_FUNCTION_TYPE` with comments
- `.env.test`: Added `LOGIC_FUNCTION_TYPE=LOCAL` for logic function
integration tests

Made with [Cursor](https://cursor.com)

---------

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
2026-03-11 17:54:55 +01:00
Paul RastoinandGitHub b699619756 Create twenty app e2e test ci (#18497)
# Introduction
Verifies whole following flow:
- Create and sdk app build and publication
- Global create-twenty-app installation
- Creating an app
- installing app dependencies
- auth:login
- app:build
- function:execute
- Running successfully auto-generated integration tests

## Create twenty app options refactor
Allow having a flow that do not require any prompt
2026-03-11 16:30:28 +01:00
Raphaël BosiandGitHub b2f053490d Add standard front component ci (#18560)
Checks if the build has been generated correctly before merging
2026-03-11 16:06:13 +01:00
21de221420 i18n - translations (#18557)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-03-11 15:57:19 +01:00
martmullandGitHub d9b3507866 Run vulnerable operation in isolated environment (#18523)
When driver = LAMBDA:
- run esbuild ts transpilation on dedicated lambda
- run yarn install on app dependencies on a dedicated lambda
2026-03-11 14:08:47 +00:00
Thomas TrompetteandGitHub b346f4fb59 Add common loader (#18556)
To avoid white screens on reload, building a shared skeleton.

Before

https://github.com/user-attachments/assets/42bd0667-141d-4df4-9072-4077192cc71d

After

https://github.com/user-attachments/assets/e6031a72-2e25-47e3-a873-b89aaddfbd3a
2026-03-11 14:56:49 +01:00
nitinandGitHub 2c5af2654d Separate code pathways for IS_COMMAND_MENU_ITEM_ENABLED flag (#18542) 2026-03-11 13:30:22 +00:00
WeikoandGitHub 6cbc7725b7 fix standard app for server build (#18558) 2026-03-11 14:37:18 +01:00
744ef3aa9d i18n - translations (#18555)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-03-11 14:21:20 +01:00
WeikoandGitHub ef92d2d321 Backfill standard views command (#18540) 2026-03-11 13:48:59 +01:00
WeikoandGitHub 00c3cd1051 Add system view fallback (#18536)
## Context
The goal is to add a "See records" button in all objects that would
redirect to that view (this will be done in a later PR). See screenshot
below.
<img width="665" height="312" alt="Screenshot 2026-03-10 at 15 54 36"
src="https://github.com/user-attachments/assets/6e23a75b-cff0-4d93-bce8-b5481b05c6f6"
/>

## Implementation
- If a view does not exist on an object, there is a **temporary**
fallback where the frontend creates the missing view as a custom view
when going over the object index page
- System objects are now surfaced but we don't want their records to be
editable, they will be readonly (mostly, all fields will be non-editable
except for their custom fields).
- We can't create a new record of a system object, some actions are also
hidden.
- The backend now rejects if you are trying to delete the last view of
an object
2026-03-11 13:48:02 +01:00
99f885306e i18n - translations (#18553)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-03-11 13:44:13 +01:00
413d1124bb Fix navigation drag drop indicator position (#18515)
Closes [#2295](https://github.com/twentyhq/core-team-issues/issues/2295)

---------

Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
Co-authored-by: Félix Malfait <felix@twenty.com>
2026-03-11 13:35:09 +01:00
ab5fb1f658 Replace newFieldDefaultConfiguration with newFieldDefaultVisibility (#18539)
https://github.com/user-attachments/assets/365092cb-0fe1-44f7-9ae6-c6fc5edb98b2

---------

Co-authored-by: Weiko <corentin@twenty.com>
2026-03-11 12:14:30 +00:00
Abdul RahmanandGitHub e4e7137660 Navigate to page when clicking nav item in edit mode (#18526)
Closes [#2298](https://github.com/twentyhq/core-team-issues/issues/2298)
2026-03-11 10:59:22 +00:00
Félix MalfaitandGitHub 7fb8cc1c39 Improve apps settings UI and remove unused tarball upload code (#18549)
## Summary
- Improves the Developer Tab in Settings > Applications: adds
source-type badges (Dev / Npm / Internal), better empty state with `yarn
twenty app:dev` CLI command, renames sections to "Create & Develop" and
"Your Apps"
- Simplifies the Distribution Tab: only shows marketplace section for
npm-sourced apps, removes the manual `isListed` toggle (now managed
automatically by the catalog sync cron)
- Removes dead frontend code: `installApplication` mutation (unused —
installs go through `installMarketplaceApp`), `uploadAppTarball`
mutation/hook, and `SettingsUploadTarballModal`

## Test plan
- [ ] Navigate to Settings > Applications > Developer tab: verify badges
show correctly, empty state shows CLI command
- [ ] Create/view a LOCAL app registration: verify Distribution tab does
NOT show marketplace section
- [ ] Create/view an NPM app registration: verify Distribution tab shows
marketplace section with Featured toggle
- [ ] Verify no references to upload tarball or install application
remain in the UI


Made with [Cursor](https://cursor.com)
2026-03-11 11:40:00 +01:00
Thomas TrompetteandGitHub 1d95670252 Fix form field select + form field number (#18538)
Before
<img width="398" height="138" alt="Capture d’écran 2026-03-10 à 16 23
19"
src="https://github.com/user-attachments/assets/c28c0a7f-6911-4c77-a45c-42a79073a92a"
/>

After
<img width="398" height="138" alt="Capture d’écran 2026-03-10 à 16 32
56"
src="https://github.com/user-attachments/assets/b93d2fe9-5928-4803-8d5e-30a0a9eeb28f"
/>

Addition:
- hover on forget password
- settings field width
2026-03-11 08:58:51 +00:00
f855dacec9 i18n - docs translations (#18546)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-03-11 09:55:15 +01:00
Thomas des FrancsandGitHub 0fe7e9d5fe small ai chat fix (#18547)
Summary
- capture the updated page structure in a new Playwright YAML artifact
to document the small-ai-chat-fix workstream
- store the generated snapshot under
`.playwright-cli/page-2026-03-09T13-12-30-691Z.yml` for reference

Testing
- Not run (not requested)
2026-03-10 19:27:34 +00:00
Raphaël BosiandGitHub 2de022afcf Add standard command menu items (#18527)
## Add standard command menu items

### Summary

This PR introduces standard command menu items, migrating hardcoded
command menu actions to the backend command menu item architecture
powered by front components. It adds a new `twenty-standard-application`
package that defines, builds, and registers front components as standard
command menu items, gated behind the `IS_COMMAND_MENU_ITEM_ENABLED`
feature flag.

### Description

- **New `twenty-standard-application` package**: Contains front
component definitions with an esbuild-based build pipeline that
generates minified `.mjs` bundles and a manifest with checksums.
- **Server-side registration**: New constants register all items with
metadata (labels, icons, positions, availability types, conditional
expressions). A `StandardFrontComponentUploadService` uploads built
components to file storage.
- **`FALLBACK` availability type**: New enum value for command menu
items that appear as fallback options (e.g., "Search Records" fallback).
- **`CommandMenuContextApi` refactor**
- **Conditional availability enhancements**: New array-based helper
functions for evaluating multi-record conditions.
- **Frontend wiring** (twenty-front):
`useCommandMenuItemFrontComponentCommands`

## Next steps

Only simple commands have been implemented for now:
- **Navigation (9)** -- `CommandLink`: go-to-companies,
go-to-dashboards, go-to-notes, go-to-opportunities, go-to-people,
go-to-runs, go-to-settings, go-to-tasks, go-to-workflows
- **Side panel (4)** -- `CommandOpenSidePanelPage`: ask-ai,
search-records, search-records-fallback, view-previous-ai-chats

We still have to implement front components for all the following
commands:
All have placeholder `execute` logic (`async () => {}`) with a `// TODO:
implement execute logic` comment:

**Record (22)**
- `add-to-favorites`, `remove-from-favorites`
- `create-new-record`, `create-new-view`
- `delete-single-record`, `delete-multiple-records`
- `destroy-single-record`, `destroy-multiple-records`
- `restore-single-record`, `restore-multiple-records`
- `export-from-record-index`, `export-from-record-show`,
`export-multiple-records`, `export-note-to-pdf`, `export-view`
- `hide-deleted-records`, `see-deleted-records`
- `import-records`, `merge-multiple-records`, `update-multiple-records`
- `navigate-to-next-record`, `navigate-to-previous-record`

**Page layout (3)** -- `cancel-record-page-layout`,
`edit-record-page-layout`, `save-record-page-layout`

**Dashboard (4)** -- `cancel-dashboard-layout`, `duplicate-dashboard`,
`edit-dashboard-layout`, `save-dashboard-layout`

**Workflow (10)** -- `activate-workflow`, `add-node-workflow`,
`deactivate-workflow`, `discard-draft-workflow`, `duplicate-workflow`,
`see-active-version-workflow`, `see-runs-workflow`,
`see-versions-workflow`, `test-workflow`, `tidy-up-workflow`

**Workflow version (4)** -- `see-runs-workflow-version`,
`see-versions-workflow-version`, `see-workflow-workflow-version`,
`use-as-draft-workflow-version`

**Workflow run (3)** -- `see-version-workflow-run`,
`see-workflow-workflow-run`, `stop-workflow-run`
2026-03-10 17:36:41 +00:00
25d9f2fcce fix: respect number format in currency input (#18469)
Fixed #18355 

Currency fields ignored the workspace number format when editing:
display showed e.g. 5 982,77 € (French style) but the input forced US
style (5,982.77) and rejected comma as decimal.
Fix: CurrencyInput now uses useNumberFormat() and passes the correct
thousandsSeparator and radix to the IMask input so edit mode matches the
chosen format (comma/space, dot/comma, etc.).
Files: CurrencyInput.tsx (use format for mask), new
CurrencyInput.test.tsx .

---------

Co-authored-by: root <root@dragon.second>
Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
2026-03-10 16:59:31 +00:00
Thomas TrompetteandGitHub 6e628bb751 Fix add more tab bottom separator (#18529)
Adding a full height prop

Before 
<img width="409" height="139" alt="Capture d’écran 2026-03-10 à 14 06
13"
src="https://github.com/user-attachments/assets/6f9bb8d1-8834-44b9-a0cf-1253dffc9ea0"
/>

After
<img width="409" height="139" alt="Capture d’écran 2026-03-10 à 14 06
33"
src="https://github.com/user-attachments/assets/1d3551fe-0b4b-4bbf-88e4-fd466c9bb704"
/>
2026-03-10 18:08:51 +01:00
40a8d18d38 feat : Added "Quarter" as a time unity to filter on date (#18289)
fixes #16674 

Simply added the Quarter functionality to the existing filter code with
months amount multiplied by 3.

<img width="1258" height="760" alt="Screenshot 2026-02-27 at 11 59
26 AM"
src="https://github.com/user-attachments/assets/30648259-2b72-480a-b8fa-55ed88d9ebf3"
/>

---------

Co-authored-by: Arun kumar <arunkumar@Aruns-MacBook-Air.local>
Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
2026-03-10 16:42:10 +00:00
e7fe435f60 i18n - docs translations (#18541)
Created by Github action

Co-authored-by: github-actions <github-actions@twenty.com>
2026-03-10 17:44:34 +01:00
Félix MalfaitandGitHub 621962e049 Move fixture apps from twenty-sdk to twenty-apps/fixtures (#18531)
## Summary

- Move 4 test fixture apps from `twenty-sdk/src/cli/__tests__/apps/` to
`twenty-apps/fixtures/` with meaningful names (`rich-app` →
`postcard-app`, `root-app` → `minimal-app`)
- Replace all `from '@/sdk'` imports with `from 'twenty-sdk'` so fixture
apps are proper, portable twenty-sdk apps
- Remove the fragile `"@/*": ["../../../../../src/*"]` tsconfig hack and
replace with standard `"src/*": ["./src/*"]` paths
- Create a centralized `fixture-paths.ts` utility in twenty-sdk tests
for clean app path resolution

## Why

The fixture apps were deeply nested in twenty-sdk's test directory and
tightly coupled to its internal source layout via a tsconfig path alias
hack. This made them:
- Impossible to reuse outside of SDK CLI tests (e.g., for server-side
dev seeding with `DevSeederService`)
- Fragile — moving any twenty-sdk source file could break the path alias
- Poorly discoverable — buried 5 directories deep in test infrastructure

Moving them to `twenty-apps/fixtures/` makes them first-class portable
apps that can be imported by `twenty-server` for seeding, used in E2E
testing, and serve as canonical examples alongside `hello-world`.

## Test plan

- [x] All 8 twenty-sdk integration tests pass (3 suites: postcard-app,
minimal-app, invalid-app)
- [x] Prettier formatting verified on all changed files
- [ ] CI should confirm E2E tests also pass (these require a running
server)

Made with [Cursor](https://cursor.com)
2026-03-10 17:29:53 +01:00
7b9939b43e fix: validate input before formatting in MultiItemFieldInput (#18334)
Reorder validateInput to run before formatInput to prevent
parsePhoneNumber from throwing INVALID_COUNTRY on bad input.

Fixes TWENTY-FRONT-5RQ
/closes https://github.com/twentyhq/twenty/issues/17670

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
2026-03-10 15:39:21 +00:00
ec1f08bc3b i18n - translations (#18537)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-03-10 16:35:05 +01:00
dd58eb6814 Fix linaria css regressions (#18492)
before
<img width="310" height="60" alt="SCR-20260309-ctul"
src="https://github.com/user-attachments/assets/7141d495-b8f2-4fd6-bf3b-36bb2b11d1aa"
/>

after (fixed icon alignment)
<img width="235" height="67" alt="SCR-20260309-ctel"
src="https://github.com/user-attachments/assets/36078039-93dc-4c2c-b553-0bbcde4cb81c"
/>

before
<img width="637" height="318" alt="SCR-20260309-ctnp"
src="https://github.com/user-attachments/assets/34b66129-d619-43a2-8896-aa92b511644e"
/>

after (fixed chart colors)
<img width="650" height="317" alt="SCR-20260309-cthj"
src="https://github.com/user-attachments/assets/82c095b1-34bb-4ae4-a8f2-7a3746a31b0a"
/>


before



<img width="909" height="650" alt="image"
src="https://github.com/user-attachments/assets/14649aed-bfa8-4b9d-aa35-f4de2bfaddd6"
/>


after (fixed buttons text color)
<img width="930" height="646" alt="SCR-20260309-csob"
src="https://github.com/user-attachments/assets/c724a849-dabe-406c-8258-0674211374f2"
/>

before

<img width="544" height="141" alt="image"
src="https://github.com/user-attachments/assets/815c3b70-2f7c-42ca-8a32-3fbd5fe4c556"
/>


after (fixed missing border on :active state)

<img width="554" height="145" alt="image"
src="https://github.com/user-attachments/assets/845b1afd-36b6-4ae4-b6ef-c49ccbd89c10"
/>

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
2026-03-10 16:26:53 +01:00
Baptiste DevessierandGitHub 926dd545f4 Create fake hidden fields group (#18525)
## Demo


https://github.com/user-attachments/assets/43f31c43-fe37-4553-ad42-fc97a948d6ea

## Ungrouped fields

<img width="3456" height="2160" alt="CleanShot 2026-03-10 at 13 52
57@2x"
src="https://github.com/user-attachments/assets/13d1db63-59ac-4e2b-8950-fccf430176c4"
/>
2026-03-10 16:26:35 +01:00
Thomas TrompetteandGitHub 8e003aa6cf Fix permission settings page (#18535)
<img width="589" height="651" alt="Capture d’écran 2026-03-10 à 15 59
37"
src="https://github.com/user-attachments/assets/6c4cf5e6-a5cc-4306-a0ae-322addad340f"
/>
2026-03-10 16:26:21 +01:00
982f0c4a4d i18n - docs translations (#18534)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
Co-authored-by: Charles Bochet <charles@twenty.com>
2026-03-10 15:57:15 +01:00
Raphaël BosiandGitHub d1c95e380e Update front components documentation (#18521)
Update front components documentation
2026-03-10 14:15:56 +00:00
6399e11220 i18n - translations (#18532)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-03-10 15:13:56 +01:00
033d297695 feat: add visual time picker to DateTimePicker (#15057) (#17952)
## Summary
Added a visual time picker dropdown for DateTime fields, replacing the
previous text input. Users can now select hours and minutes through an
intuitive scrollable interface. (Fixes #15057

 ## Changes
- **New component**: Add a `TimePickerDropdown` - Visual picker with
scrollable hour/minute columns
- **Updated**: `DateTimePickerHeader` - Implemented time picker dropdown
in `DateTimePickerHeader` and Move month/year picker to right side
  ## Snapshots
  
<img width="493" height="421" alt="image"
src="https://github.com/user-attachments/assets/3bd1f0a0-0ac2-473d-935e-d9f28b0e40e2"
/>


https://github.com/user-attachments/assets/daa5cba5-c86c-46aa-a634-0f5c04523af1



**If there is no enough place at right, auto-move month/year selector to
the left side**

Hi, @Bonapara I followed the Figma you shared to complete this feature.
Could you please review it for me? Thanks a lot.

---------

Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
2026-03-10 13:39:46 +00:00
Lucas BordeauandGitHub dee55b635f Table refactor : removed z-index dynamic logic completely and flex-wrap (#18466)
This PR removes the leftovers from the z-index dynamic logic removal.

It also removes the flex-wrap mechanism that was used to have all the
cells in the same div, and instead creates a container for each part of
the table : header, body and footer, so that z-index management becomes
straighforward.

We also fix some minor bugs.

## Demo 


https://github.com/user-attachments/assets/29dc4966-376d-4eb1-9e37-99769e77f4f4



https://github.com/user-attachments/assets/78218517-812a-4531-84c3-067700b46b59
2026-03-10 13:31:43 +00:00
Thomas TrompetteandGitHub 045faf018a Design fixes batch post linaria migration (#18509)
- Currency input
- Edit email button full height
- Full width ai field
- Missing borders
- Cmd+K icon button centered
2026-03-10 13:30:16 +00:00
Thomas TrompetteandGitHub 4370788023 Cancel in progress only if different event than merge (#18530)
As title
2026-03-10 14:25:35 +01:00
1656bb5568 [Feat] : add source to actor fields (#18118)
fixes #18099 

Simple implementation of the matchingSourceValues to be searched in the
ACTOR case in turnRecordFilterIntoRecordGqlOperationFilter

<img width="1239" height="494" alt="Screenshot 2026-02-20 at 5 21 14 PM"
src="https://github.com/user-attachments/assets/20ee076e-dccd-4747-a1ab-38d649f6591e"
/>

---------

Co-authored-by: Arun kumar <arunkumar@Aruns-MacBook-Air.local>
Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
2026-03-10 13:08:18 +00:00
59e9563fc7 i18n - docs translations (#18528)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
Co-authored-by: Charles Bochet <charles@twenty.com>
2026-03-10 14:15:51 +01:00
Thomas TrompetteandGitHub c73a660e46 Fix task rows (#18522)
Before
<img width="409" height="350" alt="Capture d’écran 2026-03-10 à 10 41
52"
src="https://github.com/user-attachments/assets/7466af53-103a-483e-826c-9946fd758525"
/>

After
<img width="409" height="350" alt="Capture d’écran 2026-03-10 à 10 42
11"
src="https://github.com/user-attachments/assets/b87c50a1-688c-4870-b629-e1767fb6bf44"
/>
2026-03-10 12:35:29 +00:00
Félix MalfaitandGitHub 05c2da2d0f Improve SSRF IP validation and add protocol allowlist (#18518)
## Summary

- Replace regex-based private IP detection in `isPrivateIp` with Node.js
`net.BlockList` for CIDR-based range checking, which properly handles
all IPv4-mapped IPv6 representations (both dotted-decimal and hex forms)
- Add missing non-routable IP ranges: carrier-grade NAT
(`100.64.0.0/10`), IANA special purpose, documentation networks,
benchmarking, multicast, and reserved ranges
- Add protocol allowlist (http/https only) as an axios request
interceptor in `SecureHttpClientService` and as a Zod refinement in the
HTTP tool schema

## Test plan

- [x] All 100 existing + new tests pass across 4 secure-http-client test
suites
- [x] New tests cover carrier-grade NAT range boundaries (100.64.0.0 –
100.127.255.255)
- [x] New tests cover documentation, benchmarking, multicast, and
reserved ranges
- [x] New tests cover hex-form IPv4-mapped IPv6 addresses (the form
Node.js URL parser actually produces)
- [x] New tests verify protocol interceptor blocks `ftp:` and `file:`
schemes
- [x] New tests verify protocol interceptor is only active when safe
mode is enabled


Made with [Cursor](https://cursor.com)
2026-03-10 13:14:49 +01:00
Félix MalfaitandGitHub 6995420b71 Remove IS_APPLICATION_INSTALLATION_FROM_TARBALL_ENABLED feature flag (#18520)
## Summary
- Removes the `IS_APPLICATION_INSTALLATION_FROM_TARBALL_ENABLED` feature
flag, consolidating tarball-based app installation under the existing
`IS_APPLICATION_ENABLED` flag
- Removes the runtime feature flag check in `runWorkspaceMigration`
resolver (the `@RequireFeatureFlag(IS_APPLICATION_ENABLED)` decorator
already gates this endpoint)
- Cleans up related integration test setup/teardown and mock feature
flag maps

## Test plan
- [ ] Verify tarball-based app installation still works when
`IS_APPLICATION_ENABLED` is true
- [ ] Verify app installation is blocked when `IS_APPLICATION_ENABLED`
is false
- [ ] Run `failing-install-application.integration-spec.ts` to confirm
it passes without the removed flag


Made with [Cursor](https://cursor.com)
2026-03-10 13:13:22 +01:00
f9f7e2f929 i18n - translations (#18508)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-03-10 12:00:20 +01:00
Paul RastoinandGitHub 8bbda86eb6 [CREATE_APP] Generate basic CI workflow (#18511) 2026-03-10 09:35:41 +00:00
martmullandGitHub 6f6a9a55fb Add doc on standard object u ids (#18519)
as title
2026-03-10 10:14:46 +01:00
882e9fd231 Docs: restructure Extend section with API, Webhooks, and Apps pages (#18517)
## Summary
- Restructures the developer Extend documentation: moves API and
Webhooks to top-level pages, creates dedicated Apps section with Getting
Started, Building, and Publishing pages
- Updates navigation structure (`docs.json`, `base-structure.json`,
`navigation.template.json`)
- Updates translated docs for all locales and LLMS.md references across
app packages

## Test plan
- [ ] Run `mintlify dev` locally and verify navigation structure
- [ ] Check that all links in the Extend section work correctly
- [ ] Verify translated pages render properly


Made with [Cursor](https://cursor.com)

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-03-10 10:00:20 +01:00
Abdul RahmanandGitHub ae122f4bb1 Add draft message persistence for AI chat threads (#18371) 2026-03-10 00:42:02 +00:00
WeikoandGitHub c433b2b73f Implement page layout override (#18472) 2026-03-09 17:21:08 +00:00
Baptiste DevessierandGitHub 309fd7a526 feat: unpin action (#18505)
## Before 

<img width="1628" height="1140" alt="image"
src="https://github.com/user-attachments/assets/8d406745-88ea-4d19-806e-8170daa2a295"
/>


## After

<img width="3456" height="2160" alt="CleanShot 2026-03-09 at 17 03
29@2x"
src="https://github.com/user-attachments/assets/741dbab5-3b9e-4f7a-83a9-27ddff0b37a2"
/>
2026-03-09 16:27:44 +00:00
FelipeandGitHub 5ab3eeb830 fix: throw clear error on invalid LOG_LEVELS (#18495)
Fixes #18356

## Summary

Setting `LOG_LEVELS=debug,info,error,warn` crashes with `TypeError:
logLevels.map is not a function` because `CastToLogLevelArray` silently
returns `undefined` for invalid levels.

Now it throws a clear error message listing the invalid levels and valid
options:

```
Invalid log level(s): info. Valid levels are: log, error, warn, debug, verbose
```

## Changes

- Throw descriptive `Error` when invalid log levels are provided instead
of returning `undefined`
- Updated tests to verify the error message

## Test plan

- [x] All 8 existing tests passing
- [x] `"toto"` → throws `Invalid log level(s): toto. Valid levels are:
log, error, warn, debug, verbose`
- [x] `"verbose,error,toto"` → throws listing only `toto` as invalid
- [x] Valid levels (`log,error,warn,debug,verbose`) continue working as
before
2026-03-09 15:41:15 +00:00
Paul RastoinandGitHub 75bb3a904d [SDK] Refactor clients (#18433)
# Intoduction

Closes https://github.com/twentyhq/core-team-issues/issues/2289

In this PR all the clients becomes available under `twenty-sdk/clients`,
this is a breaking change but generated was too vague and thats still
the now or never best timing to do so

## CoreClient
The core client is now shipped with a default stub empty class for both
the schema and the client
Allowing its import, will still raises typescript errors when consumed
as generated but not generated

## MetadataClient
The metadata client is workspace agnostic, it's now generated and
commited in the repo. added a ci that prevents any schema desync due to
twenty-server additions

Same behavior than for the twenty-front generated graphql schema
2026-03-09 15:32:13 +00:00
Thomas TrompetteandGitHub 82a1179e23 Fix empty record index page (#18500)
Before
<img width="1507" height="851" alt="Capture d’écran 2026-03-09 à 15 27
17"
src="https://github.com/user-attachments/assets/6f93686d-a3f9-4fb1-a02e-d6b1a8347120"
/>

After
<img width="1507" height="851" alt="Capture d’écran 2026-03-09 à 15 26
53"
src="https://github.com/user-attachments/assets/68a44260-0332-48c7-92bf-7bef466a7a05"
/>
2026-03-09 15:16:04 +00:00
Charles BochetandGitHub 662de17644 ci: replace 4-core runners with ubuntu-latest (#18503)
## Summary

- Replace all `ubuntu-latest-4-cores` (paid larger runners) with
`ubuntu-latest` across CI workflows
- The free `ubuntu-latest` runner for public repos already provides **4
vCPUs + 16 GB RAM** — identical specs to the paid 4-core larger runner
- Affects 4 workflow files: `ci-server.yaml`, `ci-front.yaml`,
`ci-sdk.yaml`, `ci-zapier.yaml` (8 job definitions total, including the
10-shard integration test matrix)
- The `ubuntu-latest-8-cores` runners are intentionally **kept** for
memory-heavy jobs (frontend build, storybook build, E2E tests) where the
extra capacity (8 vCPUs, 32 GB RAM) is needed
2026-03-09 16:21:03 +01:00
1993614637 i18n - docs translations (#18496)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-03-09 16:11:29 +01:00
a3aae5c857 i18n - translations (#18498)
Created by Github action

Co-authored-by: github-actions <github-actions@twenty.com>
2026-03-09 16:06:10 +01:00
martmullandGitHub 22a203680e Fix wrong type usage (#18499)
fix wrong type usage + add tests
2026-03-09 14:51:46 +00:00
36bcc71f3d refactor(command-menu-item): rename Actions to CommandMenuItem (#18489)
actions are being renamed to command menu item, they will be migrated to
server and will be served as headless front components

---------

Co-authored-by: Raphaël Bosi <71827178+bosiraphael@users.noreply.github.com>
2026-03-09 14:03:12 +00:00
Paul RastoinandGitHub 06bdb5ad6a [SDK] Agent in manifest (#18431)
# Introduction
Adding agent in the manifest, required for twenty standard app
extraction out of twenty-server
2026-03-09 11:10:14 +00:00
Baptiste DevessierandGitHub a9696705c1 Make all widgets of record page layouts non-editable except Fields widgets (#18471)
## Demo


https://github.com/user-attachments/assets/d5746f81-beae-4c46-abfe-9723da9bcc1d
2026-03-09 09:06:07 +00:00
0acc08c333 i18n - translations (#18494)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-03-09 09:45:50 +01:00
c2a79fc0c2 i18n - translations (#18475)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-03-09 09:28:49 +01:00
Abdul RahmanandGitHub 5b28e59ca7 Navbar drag drop using dnd kit (#18288) 2026-03-08 20:25:50 +00:00
Félix MalfaitandGitHub 66d93c4d28 Fix app:dev CLI by removing deleted createOneApplication mutation (#18460)
## Summary
- The `createOneApplication` GraphQL mutation was removed from the
server during the application architecture refactor (#18432), but the
SDK CLI (`app:dev`, `app:build --sync`) still called it, causing
failures.
- Simplified the SDK to use `syncApplication` (which now internally
creates the `ApplicationEntity` via `ensureApplicationExists`) instead
of a separate create step.
- On first run (clean install), the orchestrator now runs an initial
sync before initializing the file uploader, so file uploads can proceed
(they require the `ApplicationEntity` to exist).

## Test plan
- [x] Typecheck passes for both `twenty-sdk` and `twenty-server`
- [x] `app:dev` tested locally with existing app (finds app, uploads,
syncs)
- [x] `app:dev` tested locally after `app:uninstall` (creates app via
sync, uploads, syncs)
- [x] SDK unit tests pass (23/26 files, 3 pre-existing failures
unrelated)

Made with [Cursor](https://cursor.com)
2026-03-06 18:37:54 +01:00
2c69102f15 i18n - translations (#18474)
Created by Github action

Co-authored-by: github-actions <github-actions@twenty.com>
2026-03-06 18:26:14 +01:00
Charles BochetandGitHub fea47aa9f8 Add twenty/folder-structure custom oxlint rule (#18467)
## Summary

- Re-implements `eslint-plugin-project-structure`'s folder structure
enforcement as a custom oxlint rule (`twenty/folder-structure`),
recovering functionality lost during the ESLint → Oxlint migration
- Validates `src/modules/` structure: kebab-case module folder names,
allowed subdirectories (hooks, utils, components, states, types,
graphql, etc.), hook file naming (`use{PascalCase}.(ts|tsx)`), util file
naming (`{camelCase}.(ts|tsx)`), and module nesting depth (max 4 levels)
- Enabled as `"warn"` in twenty-front with 403 pre-existing violations
to address incrementally

## What the rule checks

| Check | Example valid | Example invalid |
|-------|-------------|-----------------|
| Module names kebab-case | `object-record/` | `graphWidgetBarChart/` |
| Allowed subdirs only | `hooks/`, `components/`, `utils/` |
`random-stuff/` |
| Hook file naming | `useMyHook.ts` | `badName.ts` |
| Util file naming | `buildQuery.ts` | `build-query.ts` |
| Max nesting depth 4 | `a/b/c/d/hooks/` | `a/b/c/d/e/hooks/` |
| Utils kebab-case subfolders | `utils/cron-to-human/` |
`utils/camelCase/` |

## Pre-existing violations (403 total)

| Category | Count | Examples |
|----------|-------|---------|
| Non-kebab-case module names | 160 | `graphWidgetBarChart`,
`AIChatThreads` |
| Module depth > 4 | 215 |
`settings/roles/role-permissions/object-level-permissions/field-permissions`
|
| Util file naming | 22 | `.util.ts` suffix, kebab-case, PascalCase
filenames |
| Misc (hooks, tests) | 6 | Non-hook files in hooks/, folders in test
dirs |
2026-03-06 17:02:46 +00:00
Lucas BordeauandGitHub 73268535dc Added record filter hidden fields in query (#18149)
Fixes https://github.com/twentyhq/twenty/issues/17506

Hidden fields are now queried when they are in record filters, to avoid
optimistic and filtering bugs with hidden fields.
2026-03-06 16:33:09 +00:00
02bd71052b i18n - docs translations (#18468)
Created by Github action

Co-authored-by: github-actions <github-actions@twenty.com>
2026-03-06 17:33:24 +01:00
Abdullah.andGitHub faee5ee63d fix: morph relation persist uses wrong foreign key naming, producing invalid field parentObjectId. (#18352)
Solves [Sonarly Issue 8116](https://sonarly.com/issue/8116).


### Problem

Editing a morph relation field (e.g. "Parent Object" on Task) via the
field widget was broken in two ways:

1. **Setting a value** sent the wrong foreign key name (`parentObjectId`
instead of target-specific keys like `parentObjectCompanyId`), causing
the relation to not save.
2. **Detaching** never sent a request at all — the early return check
`valueToPersist?.id === currentValue?.id` evaluated to `undefined ===
undefined` when the morph field wasn't loaded in the store, silently
skipping the update.

The record detail section worked fine because it uses a separate hook
(`useMorphPersistManyToOne`).

### Fix

Added proper morph relation handling in `usePersistField` so all
persistence goes through this single hook consistently:

- Compute the correct FK name using `computeMorphRelationFieldName`
(e.g. `parentObjectCompanyId`) instead of deriving it from the field
name directly.
- Null all morph FK columns before setting the target one, ensuring only
one FK is non-null at a time (consistent with
`useMorphPersistManyToOne`).
- Fix the early return to only skip when **setting** a value that
matches the current one — detach always proceeds.
- Derive `currentRelationId` via a type guard instead of an `as` cast.
2026-03-06 15:16:23 +00:00
06451407ce i18n - docs translations (#18464)
Created by Github action

Co-authored-by: github-actions <github-actions@twenty.com>
2026-03-06 15:44:31 +01:00
59029a0035 [Fix] : Dragged element is considered to be part of a dropdown in dashboard tab list (#18414)
Fixes #15327

The issue occurred because the drag clone's visual state was previously
tied strictly to hovering over the `VISIBLE_TABS` boundaries. When a tab
was dragged outside this area (such as the last tab naturally crossing
into the `MORE_BUTTON` hover zone), the drag clone incorrectly fell back
to the dropdown menu item style.

We fixed this by making the `isHoveringTabList` logic more robust.
Instead of enforcing the tab style only within the `VISIBLE_TABS`
boundary, the dropdown style is now strictly restricted to the
`OVERFLOW_TABS` boundary.

With this change:
- Visible tabs successfully maintain their appearance when dragged
anywhere outside the dropdown.
- Dropdown tabs correctly transition to the normal tab style when
dragged out of the dropdown area, improving UX.



https://github.com/user-attachments/assets/9474e4c1-26a8-46e3-b9ee-4c6dbd8a4ea6

---------

Co-authored-by: Arun kumar <arunkumar@Aruns-MacBook-Air.local>
Co-authored-by: ehconitin <nitinkoche03@gmail.com>
Co-authored-by: nitin <142569587+ehconitin@users.noreply.github.com>
Co-authored-by: Charles Bochet <charles@twenty.com>
2026-03-06 14:35:32 +00:00
cac4999e9f fix: handle Escape in date/datetime pickers and remove ValidationStep any (#18107)
## Summary
- **DatePicker / DateTimePicker:** Call onEscape when user presses
Escape (fixes FIXME).
- **FormDateFieldInput:** Revert input/picker on Escape; handle Escape
in text input.
- **FormDateTimeFieldInput:** Remove FIXME.
- **ValidationStep:** Replace any with typed callback.

---------

Co-authored-by: Thomas Trompette <thomas.trompette@sfr.fr>
2026-03-06 14:19:23 +00:00
martmullandGitHub 403db7ad3f Add default viewField when creating object (#18441)
as title
2026-03-06 14:59:31 +01:00
Charles BochetandGitHub ef499b6d47 Re-enable disabled lint rules and right-size CI runners (#18461)
## Summary

- Re-enable one lint rule that was temporarily disabled during the
ESLint-to-Oxlint migration:
- **`twenty/sort-css-properties-alphabetically`** in twenty-front — 578
violations auto-fixed across 390 files
- Document why **`typescript/consistent-type-imports`** cannot be
auto-fixed in twenty-server: NestJS relies on `emitDecoratorMetadata`
for DI, so converting constructor parameter imports to `import type`
erases them at compile time and breaks dependency injection at runtime
- Right-size CI runners, reducing 8-core usage from 18 jobs to 3:

| Change | Jobs | Rationale |
|--------|------|-----------|
| **Keep 8-core** | `ci-merge-queue/e2e-test`,
`ci-front/front-sb-build`, `ci-front/front-build` | Heavy builds needing
max CPU + memory (10GB NODE_OPTIONS, full Storybook webpack bundling) |
| **8-core → 4-core** | `ci-server` (build, lint-typecheck, validation,
test, integration-test), `ci-front/front-sb-test`,
`ci-zapier/server-setup`, `ci-sdk/sdk-e2e-test` | Already sharded into
10-12 parallel instances, I/O-bound (DB/Redis), or moderate single
builds |
| **8-core → 2-core** | `ci-emails/emails-test` | Trivially lightweight
(build + curl health check) |
| **Removed** | `ci-front/front-chromatic-deployment` | Dead code —
permanently disabled with `if: false` |

- Fix merge queue CI issues:
- **Concurrency**: Use `merge_group.base_ref` instead of unique merge
group ref so new queue entries cancel previous runs
- **Required status checks**: Add `merge_group` trigger to all 6
required CI workflows (front, server, shared, website, docker-compose,
sdk) with `changed-files-check` auto-skipped for merge_group events —
status check jobs auto-pass without re-running full CI
- **Build caching**: Add Nx build cache restore/save to E2E test job
with fallback to `main` branch cache for faster frontend and server
builds

## Test plan

- [ ] CI passes on this PR (verifies lint rule auto-fix works)
- [ ] Verify 4-core runner jobs complete within their 30-minute timeouts
- [ ] Verify merge queue status checks auto-pass (ci-front-status-check,
ci-server-status-check, etc.)
- [ ] Verify merge queue E2E concurrency cancels previous runs when a
new PR enters the queue
2026-03-06 13:33:02 +00:00
Abdullah.andGitHub 9f9a6a45dd fix: enforce the user to pass in property with id suffix for morph relations in Rest API (#18335)
REST API allowed users to pass in targetOpportunity, targetPerson,
targetCompany etc when trying to create a noteTarget or a taskTarget.
The request went through, we got back a 201, the record was created, but
the relationship was never established since the FK was empty in the
database.

This PR enforces users to send in the property with the `Id` suffix for
consistency. So, the user sends in targetOpportunityId, targetPersonId,
targetCompanyId etc.

<p align="center">
<img width="854" height="480" alt="image"
src="https://github.com/user-attachments/assets/ed50a623-68d4-4266-baf1-e94a657c3fd4"
/>
</p>

If the users try to send without the "Id" suffix, they get an error
explaining what to do.

<p align="center">
<img width="854" height="480" alt="image"
src="https://github.com/user-attachments/assets/8bab399b-7a04-4b6a-86b5-6f0e0b1ecd5d"
/>
</p>

Additionally, the documentation itself contains the correct property
names.

<p align="center">
<img width="854" height="480" alt="image"
src="https://github.com/user-attachments/assets/83e51cd6-8ef7-4a4d-8696-ab37cc4a9dd6"
/>
</p>

Finally, the filters also enforce this "Id" suffix convention in the GET
request.

<p align="center">
<img width="854" height="480" alt="image"
src="https://github.com/user-attachments/assets/168a2f09-1242-40fa-bd84-1f7d9c60357c"
/>
</p>

Edit: Updated error messages after the screenshots were taken to make
them a little generic. Secondly, this PR also fixes the issue of morph
relation ids and objects not appearing in the response (when depth is
1).
2026-03-06 14:21:19 +01:00
Thomas TrompetteandGitHub 1f1da901ea Bug fixes batch (#18457)
Fixes https://github.com/twentyhq/twenty/issues/18181

Fixes https://github.com/twentyhq/twenty/issues/16842
Iterators remain running, which prevent the stopping state to eventually
become stopped

Fixes https://github.com/twentyhq/twenty/issues/18186
2026-03-06 14:20:54 +01:00
f65aafe96b i18n - translations (#18462)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-03-06 14:13:03 +01:00
Baptiste DevessierandGitHub a79b816117 Allow users to set where new fields must be created in a record page layout (#18420)
## Demo


https://github.com/user-attachments/assets/eaf89d0c-96e0-4e49-ac58-290c8e7403ff
2026-03-06 14:04:57 +01:00
Charles BochetandGitHub d37ed7e07c Optimize merge queue to only run E2E and integrate prettier into lint (#18459)
## Summary

- **Merge queue optimization**: Created a dedicated
`ci-merge-queue.yaml` workflow that only runs Playwright E2E tests on
`ubuntu-latest-8-cores`. Removed `merge_group` trigger from all 7
existing CI workflows (front, server, shared, website, sdk, zapier,
docker-compose). The merge queue goes from ~30+ parallel jobs to a
single focused E2E job.
- **Label-based merge queue simulation**: Added `run-merge-queue` label
support so developers can trigger the exact merge queue E2E pipeline on
any open PR before it enters the queue.
- **Prettier in lint**: Chained `prettier --check` into `lint` and
`prettier --write` into `lint --configuration=fix` across `nx.json`
defaults, `twenty-front`, and `twenty-server`. Prettier formatting
errors are now caught by `lint` and fixed by `lint:fix` /
`lint:diff-with-main --configuration=fix`.

## After merge (manual repo settings)

Update GitHub branch protection required status checks:
1. Remove old per-workflow merge queue checks (`ci-front-status-check`,
`ci-e2e-status-check`, `ci-server-status-check`, etc.)
2. Add `ci-merge-queue-status-check` as the required check for the merge
queue
2026-03-06 13:20:57 +01:00
WeikoandGitHub d825ac06dd fix server production build (#18458)
## Context
- fuse.js was imported in navigate-app-tool.ts but only declared in the
root package.json (has been like this for months but for the first time
being used in the server)
- This works locally due to yarn hoisting, but breaks in Docker
production builds

```bash
Error: Cannot find module 'fuse.js'
Require stack:
- /app/packages/twenty-server/dist/engine/core-modules/tool/tools/navigate-tool/navigate-app-tool.js
- /app/packages/twenty-server/dist/engine/core-modules/tool-provider/providers/action-tool.provider.js
- /app/packages/twenty-server/dist/engine/core-modules/tool-provider/tool-provider.module.js
- /app/packages/twenty-server/dist/engine/metadata-modules/ai/ai-agent-execution/ai-agent-execution.module.js
- /app/packages/twenty-server/dist/engine/metadata-modules/ai/ai-agent-monitor/ai-agent-monitor.module.js
- /app/packages/twenty-server/dist/engine/metadata-modules/metadata-engine.module.js
- /app/packages/twenty-server/dist/engine/api/graphql/core-graphql-api.module.js
- /app/packages/twenty-server/dist/app.module.js
- /app/packages/twenty-server/dist/main.js
    at Module._resolveFilename (node:internal/modules/cjs/loader:1456:15)
    at defaultResolveImpl (node:internal/modules/cjs/loader:1066:19)
    at resolveForCJSWithHooks (node:internal/modules/cjs/loader:1071:22)
    at Module._load (node:internal/modules/cjs/loader:1242:25)
    at wrapModuleLoad (node:internal/modules/cjs/loader:255:19)
    at Module.require (node:internal/modules/cjs/loader:1556:12)
    at require (node:internal/modules/helpers:152:16)
    at Object.<anonymous> (/app/packages/twenty-server/dist/engine/core-modules/tool/tools/navigate-tool/navigate-app-tool.js:13:54)
    at Module._compile (node:internal/modules/cjs/loader:1812:14)
    at Object..js (node:internal/modules/cjs/loader:1943:10) {
  code: 'MODULE_NOT_FOUND',
  ```

Fix: Adding the dependency in twenty-server package.json to make it available in production builds
2026-03-06 11:44:18 +01:00
BugIsGodandGitHub 1c898f36d6 Fix workflow nodes color in dark mode (#18456)
## Why
`ThemeProvider` already exposes `colorScheme` (`'light' | 'dark'`) via
`ThemeContext`, but `WorkflowDiagramCanvasBase` was only
extracting`theme` and never passing `colorMode` to `ReactFlow`.
Fix: #18453 

## Before
<img width="1384" height="745" alt="image"
src="https://github.com/user-attachments/assets/e50db287-c27b-4157-a5c6-59f2d6eab656"
/>

## After

<img width="1413" height="747" alt="image"
src="https://github.com/user-attachments/assets/db98f048-29fd-41bc-a3ae-cec7435dc93b"
/>
2026-03-06 11:06:52 +01:00
Charles BochetandGitHub 364c944ca6 Improve build performance 2x (#18449)
## Summary

Front Before:
<img width="1199" height="670" alt="image"
src="https://github.com/user-attachments/assets/b978f67c-c0a6-49fc-bedd-a443f11c365d"
/>

Front After:
<img width="1199" height="670" alt="image"
src="https://github.com/user-attachments/assets/a4939dbb-a8b4-4c74-978c-daa7f27d00f3"
/>


Server Before:
<img width="1199" height="670" alt="image"
src="https://github.com/user-attachments/assets/da53e97f-ec65-4224-a656-ca41040aef6e"
/>


Server After:
<img width="1199" height="670" alt="image"
src="https://github.com/user-attachments/assets/8cdf3885-f515-4d6c-989f-a421a4e8206c"
/>


### CI Server Pipeline Restructuring
- Split monolithic `server-setup` job into three parallel jobs:
`server-build`, `server-lint-typecheck`, and `server-validation`
- `server-build` only handles build + Nx cache save (~1m vs old 3.5m),
unblocking downstream jobs faster
- `server-lint-typecheck` runs in parallel with no DB dependency
- `server-validation` handles DB setup, migration checks, and GraphQL
generation checks in parallel with tests
- Make `server-test` (unit tests) fully independent — no longer waits
for server-setup, builds its own artifacts
- Increase integration test shards from 8 to 10 for better parallelism
- Expected critical path reduction: ~10m → ~7m (~30% faster)

### CI Front Pipeline Improvements
- Use artifact upload/download for storybook build instead of rebuilding
in test shards
- Serve pre-built storybook via `http-server` in test jobs, with
`STORYBOOK_URL` env var
- Update `vitest.config.ts` to use `storybookUrl` when `STORYBOOK_URL`
is set
- Remove redundant `twenty-shared`, `twenty-ui`, `twenty-sdk` builds
from storybook test shards

### Vite Build Optimizations
- Conditionally enable `rollup-plugin-visualizer` behind `ANALYZE=true`
env var (not loaded by default)
- Broaden Istanbul coverage exclusions to skip test files, stories,
mocks, and decorators
- Remove `@tabler/icons-react` alias from twenty-front and storybook
configs
- Bundle `@tabler/icons-react` into twenty-ui instead of treating it as
an external dependency
- Add lazy loading with `React.lazy` + `Suspense` for all page-level
route components in `useCreateAppRouter`
2026-03-06 11:02:26 +01:00
aa062644c8 Fixed scrollbar height issue in Kanban view and adjusted the calendar view to adjust with the new change (#18367)
fix for #18331 

## Issue 
The height of the container for the kanban board and calendar was set
after calculating the offset from the top bar which contains the
filters. The main issue was that it was calculated wrong. To fix this, I
have added flex:1 to ensure that the board and calendar will grow into
the empty space

## Proof of successful change
The video below shows that the scrollbar is accessible now and that the
calendar view is also adjusted to not cause any errors because of the
new change introduced.


https://github.com/user-attachments/assets/7f58342f-6cbf-4d30-878a-ec57f1e6666a

---------

Co-authored-by: Thomas Trompette <thomas.trompette@sfr.fr>
2026-03-06 10:24:49 +01:00
3d7cb4499f i18n - translations (#18454)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-03-06 08:53:00 +01:00
9d808302aa i18n - docs translations (#18452)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-03-06 08:52:50 +01:00
Félix MalfaitandGitHub 514d0017ea Refactor application module architecture for clarity and explicitness (#18432)
## Summary

- **Module reorganization**: Moved `ApplicationUpgradeService` and cron
jobs to `application-upgrade/`, `ApplicationSyncService` to
`application-manifest/`, and
`runWorkspaceMigration`/`uninstallApplication` mutations to the manifest
resolver — each module now has a single clear responsibility.
- **Explicit install flow**: Removed implicit `ApplicationEntity`
creation from `ApplicationSyncService`. The install service and dev
resolver now explicitly create the `ApplicationEntity` before syncing.
npm packages are resolved at registration time to extract manifest
metadata (universalIdentifier, name, description, etc.), eliminating the
`reconcileUniversalIdentifier` hack.
- **Better error handling**: Frontend hooks now surface actual server
error messages in snackbars instead of swallowing them. Replaced the
ugly `ConfirmationModal` for transfer ownership with a proper form
modal. Fixed `SettingsAdminTableCard` row height overflow and corrected
the `yarn-engine` asset path.

## Test plan
- [ ] Register an npm package — verify manifest metadata (name,
description, universalIdentifier) is extracted correctly
- [ ] Install a registered npm app on a workspace — verify
ApplicationEntity is created and sync succeeds
- [ ] Test `app:dev` CLI flow — verify local app registration and sync
work
- [ ] Upload a tarball — verify registration and install flow
- [ ] Transfer ownership — verify the new modal UX works
- [ ] Verify error messages appear correctly in snackbars when
operations fail


Made with [Cursor](https://cursor.com)
2026-03-06 08:45:08 +01:00
90cced0e74 i18n - docs translations (#18451)
Created by Github action

Co-authored-by: github-actions <github-actions@twenty.com>
2026-03-06 02:24:32 +01:00
4d0b8a8644 i18n - translations (#18450)
Created by Github action

Co-authored-by: github-actions <github-actions@twenty.com>
2026-03-06 02:23:59 +01:00
Charles BochetandGitHub 9d57bc39e5 Migrate from ESLint to OxLint (#18443)
## Summary

Fully replaces ESLint with OxLint across the entire monorepo:

- **Replaced all ESLint configs** (`eslint.config.mjs`) with OxLint
configs (`.oxlintrc.json`) for every package: `twenty-front`,
`twenty-server`, `twenty-emails`, `twenty-ui`, `twenty-shared`,
`twenty-sdk`, `twenty-zapier`, `twenty-docs`, `twenty-website`,
`twenty-apps/*`, `create-twenty-app`
- **Migrated custom lint rules** from ESLint plugin format to OxLint JS
plugin system (`@oxlint/plugins`), including
`styled-components-prefixed-with-styled`, `no-hardcoded-colors`,
`sort-css-properties-alphabetically`,
`graphql-resolvers-should-be-guarded`,
`rest-api-methods-should-be-guarded`, `max-consts-per-file`, and
Jotai-related rules
- **Migrated custom rule tests** from ESLint `RuleTester` + Jest to
`oxlint/plugins-dev` `RuleTester` + Vitest
- **Removed all ESLint dependencies** from `package.json` files and
regenerated lockfiles
- **Updated Nx targets** (`lint`, `lint:diff-with-main`, `fmt`) in
`nx.json` and per-project `project.json` to use `oxlint` commands with
proper `dependsOn` for plugin builds
- **Updated CI workflows** (`.github/workflows/ci-*.yaml`) — no more
ESLint executor
- **Updated IDE setup**: replaced `dbaeumer.vscode-eslint` with
`oxc.oxc-vscode` extension, configured `source.fixAll.oxc` and
format-on-save with Prettier
- **Replaced all `eslint-disable` comments** with `oxlint-disable`
equivalents across the codebase
- **Updated docs** (`twenty-docs`) to reference OxLint instead of ESLint
- **Renamed** `twenty-eslint-rules` package to `twenty-oxlint-rules`

### Temporarily disabled rules (tracked in `OXLINT_MIGRATION_TODO.md`)

| Rule | Package | Violations | Auto-fixable |
|------|---------|-----------|-------------|
| `twenty/sort-css-properties-alphabetically` | twenty-front | 578 | Yes
|
| `typescript/consistent-type-imports` | twenty-server | 3814 | Yes |
| `twenty/max-consts-per-file` | twenty-server | 94 | No |

### Dropped plugins (no OxLint equivalent)

`eslint-plugin-project-structure`, `lingui/*`, `@stylistic/*`,
`import/order`, `prefer-arrow/prefer-arrow-functions`,
`eslint-plugin-mdx`, `@next/eslint-plugin-next`,
`eslint-plugin-storybook`, `eslint-plugin-react-refresh`. Partial
coverage for `jsx-a11y` and `unused-imports`.

### Additional fixes (pre-existing issues exposed by merge)

- Fixed `EmailThreadPreview.tsx` broken import from main rename
(`useOpenEmailThreadInSidePanel`)
- Restored truthiness guard in `getActivityTargetObjectRecords.ts`
- Fixed `AgentTurnResolver` return types to match entity (virtual
`fileMediaType`/`fileUrl` are resolved via `@ResolveField()`)

## Test plan

- [x] `npx nx lint twenty-front` passes
- [x] `npx nx lint twenty-server` passes
- [x] `npx nx lint twenty-docs` passes
- [x] Custom oxlint rules validated with Vitest: `npx nx test
twenty-oxlint-rules`
- [x] `npx nx typecheck twenty-front` passes
- [x] `npx nx typecheck twenty-server` passes
- [x] CI workflows trigger correctly with `dependsOn:
["twenty-oxlint-rules:build"]`
- [x] IDE linting works with `oxc.oxc-vscode` extension
2026-03-06 01:03:50 +01:00
Charles BochetandGitHub b421efbff7 fix: remove add record on workflow runs/versions (#18448)
## Summary

- Disable manual record creation (add button, + header button, add new
row) for **WorkflowRun** and **WorkflowVersion** objects since these are
system-managed and should not be created manually
- Fix vertical centering of the record table empty state placeholder
(regression from `styled(Component)` refactor in #18430 — the wrapper
lost `height: 100%` / `width: 100%`)

## Test plan

- [ ] Navigate to Workflow Runs index page → empty state should show
centered placeholder **without** "Add a Workflow Run" button
- [ ] Navigate to Workflow Versions index page → empty state should show
centered placeholder **without** "Add a Workflow Version" button
- [ ] Navigate to any other object index page (e.g. People, Companies) →
empty state should still show the "Add a ..." button and be centered
- [ ] Verify the + button in the record table header is hidden for
workflow runs/versions
- [ ] Verify the "Add New" row at the bottom of the table is hidden for
workflow runs/versions
2026-03-05 23:57:34 +01:00
Charles BochetandGitHub e27a8b5107 Fix Workflow layout show page (#18447)
## Summary

Fixes the workflow show page being blank after the `styled(Component)`
removal in #18430.

- PR #18430 replaced `styled(PageBody)` with a plain `div` wrapper
(`StyledPageBodyForDesktopContainer`) around `PageBody`, but the wrapper
defaulted to `display: block`
- `PageBody`'s internal container uses `flex: 1 1 auto` to size itself,
which requires a flex parent — the block wrapper broke height
propagation, causing React Flow's container to have 0 height
- Added `display: flex; flex-direction: column` to both
`StyledPageBodyForDesktopContainer` and
`StyledPageBodyForMobileContainer` to restore the flex chain

## Test plan

- [x] Open a workflow record show page → diagram nodes are visible
- [x] Open a company/person record show page → fields, tabs, and content
render correctly
- [x] Lint and typecheck pass
2026-03-05 23:02:32 +01:00
Charles BochetandGitHub 4797f97a95 fix: vertical alignment of +N More tab overflow button (#18446)
## Summary
- Add `align-items: center` to tab list `StyledContainer` so the
overflow button aligns vertically with tabs
- Remove ineffective `> * { height }` hack from `TabMoreButton` (was
being reset by `all: unset` in `StyledTabButton`)

## Test plan
- Open a record detail page with enough tabs to trigger the "+N More"
overflow
- Verify the overflow button is vertically centered with the visible
tabs
2026-03-05 22:20:14 +01:00
2f3399fd5f i18n - docs translations (#18445)
Created by Github action

Co-authored-by: github-actions <github-actions@twenty.com>
2026-03-05 21:33:59 +01:00
61f9cf9260 i18n - translations (#18442)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-03-05 20:37:14 +01:00
ef003fb929 fix blocklist (#18332)
- The schema generator marked both the FK scalar and connect relation
input as required for non-nullable `MANY_TO_ONE` relations, but the
resolver rejects when both are provided making create mutations
impossible
- Fixed by making the connect input always optional in create input
types (the FK scalar still enforces the constraint)
- Added `createOne` pre-query hook for blocklist with ownership
validation


https://github.com/user-attachments/assets/aaae83d4-4747-4d16-a87c-8d8cad79d25d

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
2026-03-05 20:26:04 +01:00
Baptiste DevessierandGitHub 62a634831d feat: create specialized component for header (#18438)
## Before



https://github.com/user-attachments/assets/afb6ff1d-2489-42b8-80ea-8f6dbc032629



## After


https://github.com/user-attachments/assets/6cf87609-bb0f-4bc2-8273-bce2f226aec2
2026-03-05 19:57:46 +01:00
1ec7244d1b i18n - docs translations (#18440)
Created by Github action

Co-authored-by: github-actions <github-actions@twenty.com>
2026-03-05 19:54:32 +01:00
Lucas BordeauandGitHub 13e569eaac Removed z-index dynamic logic for table (#18436)
This PR removes the complex logic that was used to manage z-index
switching to have the hovered cell portal correctly behave when its
borders were overlaping cells ones.

We now have the hovered portal inside a cell, thus removing the need for
a z-index dynamic logic.

The code has been simplified in the parts where the logic was
implemented and the constant that holds the all z indices for the tables
is still needed but with less options.
2026-03-05 19:38:05 +01:00
Charles BochetandGitHub 1affa1e004 chore(front): remove vite-plugin-checker background TS/ESLint checks (#18437)
## Summary

Removes `vite-plugin-checker` and all references to
`VITE_DISABLE_TYPESCRIPT_CHECKER` / `VITE_DISABLE_ESLINT_CHECKER`.

These background checks are no longer needed because our dev experience
now relies on **independent** linters and type-checkers:
- `npx nx lint:diff-with-main twenty-front` for ESLint
- `npx nx typecheck twenty-front` for TypeScript

Running these as separate processes (rather than inside Vite) is faster,
gives cleaner output, and avoids the significant memory overhead that
`vite-plugin-checker` introduces during `vite dev` and `vite build`. The
old env vars to disable them are removed from `vite.config.ts`,
`package.json` scripts, `nx.json`, `.env.example`, and all translated
docs.
2026-03-05 18:55:04 +01:00
Charles BochetandGitHub c53a13417e Remove all styled(Component) patterns in favor of parent wrappers and props (#18430)
## Summary

Eliminates all ~350 `styled(Component)` usages across `twenty-front` and
`twenty-ui` (212 files changed). Each was replaced following these
rules:

- **Margin/layout CSS** (margin, padding, flex, align-self, width) →
wrapped in a `styled.div`/`styled.span` parent container
- **Third-party components** (Link, TextareaAutosize,
ReactPhoneNumberInput, Handle, etc.) → parent container with child CSS
selectors (`> a`, `> textarea`, `> input`, etc.)
- **Intrinsic behavior via existing props** (TableRow
`gridTemplateColumns`, TableCell `color`/`align`) → replaced
`styled(TableRow)` / `styled(TableCell)` with direct prop usage
- **Other visual overrides on twenty-ui components** (Card, Section,
TabList, Button, MenuItem, ScrollWrapper, etc.) → parent wrappers with
`> div` / `> *` child selectors
- **Extending styled.div/span** → merged all CSS into a single
`styled.div`/`styled.span`

Also adds `overflow: hidden` to parent containers wrapping
`ScrollWrapper` so scroll activates correctly with the new wrapper
structure.

### Migration patterns

| Before | After |
|--------|-------|
| `styled(Avatar)` with `margin-right` | `<StyledAvatarContainer><Avatar
/></StyledAvatarContainer>` |
| `styled(Link)` with `text-decoration: none` |
`<StyledLinkContainer><Link /></StyledLinkContainer>` with `> a { ... }`
|
| `styled(TableRow)` with `grid-template-columns` | `<TableRow
gridTemplateColumns="..." />` |
| `styled(TableCell)` with `color` / `align` | `<TableCell color={...}
align="right" />` |
| `styled(Card)` with `margin-top` | `<StyledCardContainer><Card
/></StyledCardContainer>` |
| `styled(TabList)` with `background` |
`<StyledTabListContainer><TabList /></StyledTabListContainer>` with `>
div { ... }` |
| `styled(StyledBase)` extending a `styled.div` | Single merged
`styled.div` with all styles inlined |
2026-03-05 18:16:25 +01:00
Lucas BordeauandGitHub e5e3132ddd Add ESLint rules to disallow jotaiStore and direct atomFamily usage in selectors (#18422)
Introduce two new ESLint rules that prevent the use of `jotaiStore` and
direct calls to `.atomFamily()` or `.selectorFamily()` within component
selector `get` callbacks.

These rules promote cleaner and more reactive code practices.

Fixed file touched by those new rules :
`calendarDayRecordIdsComponentFamilySelector`
2026-03-05 17:58:52 +01:00
Baptiste DevessierandGitHub 4965790ecc Backfill record page layouts for custom objects (#18428) 2026-03-05 17:52:21 +01:00
fc2b1de860 fix: composite field sub-menu not showing in advanced filter (#18395)
## Problem
While working on the IS/IS_NOT filter feature (#15317 ), I found this
problem. So I want to submit a separate pr to fix it at first.
In the advanced filter, clicking on a composite field (Emails, Phones,
Links) was not showing the sub-field selection menu.

  ## Root cause
Related to #18178 (Recoil → Jotai migration).
`AdvancedFilterFieldSelectMenu` was writing composite field states using
`advancedFilterFieldSelectDropdownId` as the instance ID. But the reader
components (`AdvancedFilterFieldSelectDropdownContent`,
`AdvancedFilterSubFieldSelectMenu`) resolve the instance ID from React
context, which has a different value — so they were reading from a
different Jotai atom instance and `isSelectingCompositeField` was always
`false`.

  ## Fix
Remove the 3 explicit instance IDs so the writer uses context, matching
the readers.

 ## Before


https://github.com/user-attachments/assets/dde54077-0eaf-453c-a638-bec6d6fe4d55

## After


https://github.com/user-attachments/assets/01916bcf-0ee8-49ad-bb54-9d8f10571069


Hope I understood it correctly.

---------

Co-authored-by: Etienne <45695613+etiennejouan@users.noreply.github.com>
2026-03-05 17:51:59 +01:00
12257f4cc7 i18n - translations (#18429)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-03-05 17:51:21 +01:00
Paul RastoinandGitHub 57d8954973 [SDK] Pure ESM (#18427)
# Introduction
While testing the sdk and overall apps in
https://github.com/prastoin/twenty-app-hello-world
Faced a lot of pure `CJS` external dependencies import issue

Replaced all the cjs deps to either esm equivalent or node native
replacement
2026-03-05 17:19:01 +01:00
WeikoandGitHub cfeea43eaf Improve workspace auth context surface (#18164) 2026-03-05 15:13:59 +00:00
nitinandGitHub 5853891b02 refactor!: rename Command Menu page/navigation layer to Side Panel (#18393) 2026-03-05 15:46:31 +01:00
Paul RastoinandGitHub 38ad0820c0 Fix server logs leak (#18423)
# Introduction

Previously the auth jwt stragegy would lod the whole user entity in the
auth user context
On an exception it would completely get logged on the pods


## Security layer
- 0/ Updating the type system ( devxp only though )
- 1/ The jwt auth stragegy only load a specific sub set of the user
entity
- 2/ Sanitizing at the exception log level directly in case of a user
context
- 3/ Sanitizing at the console driver

The last two sanitization could sound a bit redundant though they're
still good fallback to keep in case new path occurs in the cb
2026-03-05 14:40:23 +01:00
Charles BochetandGitHub 647c32ff3e Deprecate runtime theme objects in favor of CSS variables (#18402)
## Summary

- **Eliminate `ICON_SIZES` / `ICON_STROKES` constants**: all icon
dimensions are now resolved at runtime via
`resolveThemeVariableAsNumber(themeCssVariables.icon.size.X)`, ensuring
values always come from computed CSS variables
- **No more consumer imports from `twenty-ui/theme`**: moved
`ColorSchemeContext`, `ColorSchemeProvider`, `ThemeColor`,
`MAIN_COLOR_NAMES`, `getNextThemeColor`, `AnimationDuration` to
`twenty-ui/theme-constants`
- **Remove `ThemeContext` / `ThemeContextProvider` / `ThemeProvider` /
`ThemeType`**: replaced across ~300 files with `themeCssVariables` (for
CSS contexts) or `resolveThemeVariable` / `resolveThemeVariableAsNumber`
(for JS runtime values)
- **Simplify provider chain**: only `ColorSchemeProvider` remains — it
toggles `light`/`dark` class on `document.documentElement` and provides
`colorScheme` via React context
- **Fix pre-existing test failures**: `useIcons.test.ts`
(non-configurable ES module spy) and
`turnRecordFilterGroupIntoGqlOperationFilter.test.ts`
(`Omit<RecordFilter, 'id'>` type mismatch)

### Theme access pattern (before → after)

| Context | Before | After |
|---------|--------|-------|
| CSS (Linaria) | `${({ theme }) => theme.font.color.primary}` |
`${themeCssVariables.font.color.primary}` |
| JS runtime (icon size, animation) | `theme.icon.size.md` /
`ICON_SIZES.md` |
`resolveThemeVariableAsNumber(themeCssVariables.icon.size.md)` |
| Color scheme check | `theme.name === 'dark'` |
`useContext(ColorSchemeContext).colorScheme === 'dark'` |
2026-03-05 14:39:01 +01:00
martmullandGitHub 7293d4c1f8 Fix missing test input values (#18424)
- refactor
- fix issue
2026-03-05 14:36:36 +01:00
martmullandGitHub 1acbf28316 Only update value at creation (#18350)
as title
2026-03-05 13:08:17 +00:00
Charles BochetandGitHub 1decd40eea Remove unecessary queries for aggregate (#18421)
As per title.
2026-03-05 13:24:56 +01:00
1b9d188e4a Added SSE effect for view relations objects (#18386)
This PR adds what is necessary for having SSE working for view relations
: fields, filters, filter groups and sorts.

This should allow to have AI working well while creating views with
detailed filtering and sorting.

## Demo


https://github.com/user-attachments/assets/026c7fb5-8e1a-4498-b7f4-d16993e5a7c4

## Fixes

Also fixed in this PR while working on the filter area : 
- Advanced filter does not update
- Advanced filter sub field selection is broken (due to Jotai migration)
- No view fields when creating a new view
- Error on advanced filter deletion (cascade delete wasn't taken into
account on the frontend)
- Bug advanced filter creation

---------

Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
2026-03-05 12:20:33 +01:00
Baptiste DevessierandGitHub 57499342f1 Set widget position's type according to parent tab (#18411)
Fixes workspaces seeded a few weeks ago and containing position=NULL
widgets
2026-03-05 11:48:40 +01:00
ecbc0ac013 i18n - translations (#18419)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-03-05 11:47:29 +01:00
Abdullah.andGitHub c571473d67 fix: SVGO DoS through entity expansion in DOCTYPE (#18416)
Resolves [Dependabot Alert
604](https://github.com/twentyhq/twenty/security/dependabot/604) and
[Dependabot Alert
605](https://github.com/twentyhq/twenty/security/dependabot/605).
2026-03-05 11:43:17 +01:00
2a82df7073 AI tools to create a demo workspace (#18236)
This PR adds the necessary tool to create a demo workspace with :
relevant custom objects and fields, mock data and a real dashboard with
graph widgets.

It is still a bit under-optimized and slow but it works.

This PR also adds an AI tool that allows to see what happens in real
time, it navigates the app and waits when necessary.

---------

Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
2026-03-05 11:39:31 +01:00
Thomas TrompetteandGitHub a2f80d882b Stop catching all workflow errors (#18392)
Steps now throw WorkflowStepExecutorException. Then workflow executor
decides if error should be catch or not.

Since tools are not only used in workflow and these do not throw, we may
still miss errors here.

Workflow jobs now only catch errors to end the workflow run and throw.
2026-03-05 11:24:36 +01:00
Raphaël BosiandGitHub abd9709291 Update Command Menu Item entity (#18391)
Closes https://github.com/twentyhq/core-team-issues/issues/2256
2026-03-05 11:21:56 +01:00
9d4ff7820d i18n - translations (#18415)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-03-05 11:14:03 +01:00
nitinandGitHub 4cfd738312 Headless action modal (#18270)
https://github.com/user-attachments/assets/809a281f-3c38-41df-99db-e780941acf9f
2026-03-05 11:13:46 +01:00
Félix MalfaitandGitHub 0e89c96170 feat: add npm and tarball app distribution with upgrade mechanism (#18358)
## Summary

- **npm + tarball app distribution**: Apps can be installed from the npm
registry (public or private) or uploaded as `.tar.gz` tarballs, with
`AppRegistrationSourceType` tracking the origin
- **Upgrade mechanism**: `AppUpgradeService` checks for newer versions,
supports rollback for npm-sourced apps, and a cron job runs every 6
hours to update `latestAvailableVersion` on registrations
- **Security hardening**: Tarball extraction uses path traversal
protection, and `enableScripts: false` in `.yarnrc.yml` disables all
lifecycle scripts during `yarn install` to prevent RCE
- **Frontend**: "Install from npm" and "Upload tarball" modals, upgrade
button on app detail page, blue "Update" badge on installed apps table
when a newer version is available
- **Marketplace catalog sync**: Hourly cron job syncs a hardcoded
catalog index into `ApplicationRegistration` entities
- **Integration tests**: Coverage for install, upgrade, tarball upload,
and catalog sync flows

## Backend changes

| Area | Files |
|------|-------|
| Entity & migration | `ApplicationRegistrationEntity` (sourceType,
sourcePackage, latestAvailableVersion), `ApplicationEntity`
(applicationRegistrationId), migration |
| Services | `AppPackageResolverService`, `ApplicationInstallService`,
`AppUpgradeService`, `MarketplaceCatalogSyncService` |
| Cron jobs | `MarketplaceCatalogSyncCronJob` (hourly),
`AppVersionCheckCronJob` (every 6h) |
| REST endpoint | `AppRegistrationUploadController` — tarball upload
with secure extraction |
| Resolver | `MarketplaceResolver` — simplified `installMarketplaceApp`
(removed redundant `sourcePackage` arg) |
| Security | `.yarnrc.yml` — `enableScripts: false` to block postinstall
RCE |

## Frontend changes

| Area | Files |
|------|-------|
| Modals | `SettingsInstallNpmAppModal`, `SettingsUploadTarballModal`,
`SettingsAppModalLayout` |
| Hooks | `useUploadAppTarball`, `useInstallMarketplaceApp` (cleaned up)
|
| Upgrade UI | `SettingsApplicationVersionContainer`,
`SettingsApplicationDetailAboutTab` |
| Badge | `SettingsApplicationTableRow` — blue "Update" tag,
`SettingsApplicationsInstalledTab` — fetches registrations for version
comparison |
| Styling | Migrated to Linaria (matching main) |

## Test plan

- [ ] Install an app from npm via the "Install from npm" modal
- [ ] Upload a `.tar.gz` tarball via the "Upload tarball" modal
- [ ] Verify upgrade badge appears when `latestAvailableVersion >
version`
- [ ] Verify upgrade flow from app detail page
- [ ] Run integration tests: `app-distribution.integration-spec.ts`,
`marketplace-catalog-sync.integration-spec.ts`
- [ ] Verify `enableScripts: false` blocks postinstall scripts during
yarn install


Made with [Cursor](https://cursor.com)
2026-03-05 10:34:08 +01:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>Abdullah
bfa50f566e Bump @clickhouse/client from 1.11.0 to 1.18.1 (#18410)
Bumps [@clickhouse/client](https://github.com/ClickHouse/clickhouse-js)
from 1.11.0 to 1.18.1.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/ClickHouse/clickhouse-js/releases"><code>@​clickhouse/client</code>'s
releases</a>.</em></p>
<blockquote>
<h2>1.18.1</h2>
<h2>Improvements</h2>
<ul>
<li>Setting <code>log.level</code> default value to
<code>ClickHouseLogLevel.WARN</code> instead of
<code>ClickHouseLogLevel.OFF</code> to provide better visibility into
potential issues without overwhelming users with too much information by
default.</li>
</ul>
<pre lang="ts"><code>const client = createClient({
  // ...
  log: {
level: ClickHouseLogLevel.WARN, // default is now
ClickHouseLogLevel.WARN instead of ClickHouseLogLevel.OFF
  },
})
</code></pre>
<ul>
<li>Logging is now lazy, which means that the log messages will only be
constructed if the log level is appropriate for the message. This can
improve performance in cases where constructing the log message is
expensive, and the log level is set to ignore such messages. See
<code>ClickHouseLogLevel</code> enum for the complete list of log
levels. (<a
href="https://redirect.github.com/ClickHouse/clickhouse-js/issues/520">#520</a>)</li>
</ul>
<pre lang="ts"><code>const client = createClient({
  // ...
  log: {
level: ClickHouseLogLevel.TRACE, // to log everything available down to
the network level events
  },
})
</code></pre>
<ul>
<li>Enhanced the logging of the HTTP request / socket lifecycle with
additional trace messages and context such as Connection ID (UUID) and
Request ID and Socket ID that embed the connection ID for ease of
tracing the logs of a particular request across the connection
lifecycle. To enable such logs, set the <code>log.level</code> config
option to <code>ClickHouseLogLevel.TRACE</code>. (<a
href="https://redirect.github.com/ClickHouse/clickhouse-js/issues/567">#567</a>)</li>
</ul>
<pre
lang="console"><code>[2026-02-25T09:19:13.511Z][TRACE][@clickhouse/client][Connection]
Insert: received 'close' event, 'free' listener removed
Arguments: {
  operation: 'Insert',
  connection_id: 'da3c9796-5dc5-46ef-83b0-ed1f4422094c',
  query_id: '9dfda627-39a2-41a6-9fc9-8f8716574826',
  request_id: 'da3c9796-5dc5-46ef-83b0-ed1f4422094c:3',
  socket_id: 'da3c9796-5dc5-46ef-83b0-ed1f4422094c:2',
  event: 'close'
}
[2026-02-25T09:19:13.502Z][TRACE][@clickhouse/client][Connection] Query:
reusing socket
Arguments: {
  operation: 'Query',
  connection_id: 'da3c9796-5dc5-46ef-83b0-ed1f4422094c',
  query_id: 'ad0127e8-b1c7-4ed6-9681-c0162f7a0ea9',
  request_id: 'da3c9796-5dc5-46ef-83b0-ed1f4422094c:4',
  socket_id: 'da3c9796-5dc5-46ef-83b0-ed1f4422094c:2',
  usage_count: 1
}
</code></pre>
<ul>
<li>A step towards structured logging: the client now passes rich
context to the logger <code>args</code> parameter (e.g.
<code>connection_id</code>, <code>query_id</code>,
<code>request_id</code>, <code>socket_id</code>). (<a
href="https://redirect.github.com/ClickHouse/clickhouse-js/issues/576">#576</a>)</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/ClickHouse/clickhouse-js/blob/main/CHANGELOG.md"><code>@​clickhouse/client</code>'s
changelog</a>.</em></p>
<blockquote>
<h1>1.18.1</h1>
<h2>Improvements</h2>
<ul>
<li>Setting <code>log.level</code> default value to
<code>ClickHouseLogLevel.WARN</code> instead of
<code>ClickHouseLogLevel.OFF</code> to provide better visibility into
potential issues without overwhelming users with too much information by
default.</li>
</ul>
<pre lang="ts"><code>const client = createClient({
  // ...
  log: {
level: ClickHouseLogLevel.WARN, // default is now
ClickHouseLogLevel.WARN instead of ClickHouseLogLevel.OFF
  },
})
</code></pre>
<ul>
<li>Logging is now lazy, which means that the log messages will only be
constructed if the log level is appropriate for the message. This can
improve performance in cases where constructing the log message is
expensive, and the log level is set to ignore such messages. See
<code>ClickHouseLogLevel</code> enum for the complete list of log
levels. (<a
href="https://redirect.github.com/ClickHouse/clickhouse-js/issues/520">#520</a>)</li>
</ul>
<pre lang="ts"><code>const client = createClient({
  // ...
  log: {
level: ClickHouseLogLevel.TRACE, // to log everything available down to
the network level events
  },
})
</code></pre>
<ul>
<li>Enhanced the logging of the HTTP request / socket lifecycle with
additional trace messages and context such as Connection ID (UUID) and
Request ID and Socket ID that embed the connection ID for ease of
tracing the logs of a particular request across the connection
lifecycle. To enable such logs, set the <code>log.level</code> config
option to <code>ClickHouseLogLevel.TRACE</code>. (<a
href="https://redirect.github.com/ClickHouse/clickhouse-js/issues/567">#567</a>)</li>
</ul>
<pre
lang="console"><code>[2026-02-25T09:19:13.511Z][TRACE][@clickhouse/client][Connection]
Insert: received 'close' event, 'free' listener removed
Arguments: {
  operation: 'Insert',
  connection_id: 'da3c9796-5dc5-46ef-83b0-ed1f4422094c',
  query_id: '9dfda627-39a2-41a6-9fc9-8f8716574826',
  request_id: 'da3c9796-5dc5-46ef-83b0-ed1f4422094c:3',
  socket_id: 'da3c9796-5dc5-46ef-83b0-ed1f4422094c:2',
  event: 'close'
}
[2026-02-25T09:19:13.502Z][TRACE][@clickhouse/client][Connection] Query:
reusing socket
Arguments: {
  operation: 'Query',
  connection_id: 'da3c9796-5dc5-46ef-83b0-ed1f4422094c',
  query_id: 'ad0127e8-b1c7-4ed6-9681-c0162f7a0ea9',
  request_id: 'da3c9796-5dc5-46ef-83b0-ed1f4422094c:4',
  socket_id: 'da3c9796-5dc5-46ef-83b0-ed1f4422094c:2',
  usage_count: 1
}
</code></pre>
<ul>
<li>A step towards structured logging: the client now passes rich
context to the logger <code>args</code> parameter (e.g.
<code>connection_id</code>, <code>query_id</code>,
<code>request_id</code>, <code>socket_id</code>). (<a
href="https://redirect.github.com/ClickHouse/clickhouse-js/issues/576">#576</a>)</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/ClickHouse/clickhouse-js/commit/cbdd7bf20904626956e0ff7808d17015813400c1"><code>cbdd7bf</code></a>
Release 1.18.1 (<a
href="https://redirect.github.com/ClickHouse/clickhouse-js/issues/590">#590</a>)</li>
<li><a
href="https://github.com/ClickHouse/clickhouse-js/commit/c9f61ebb3a2ec6201f87417e30c4fc4271451ae8"><code>c9f61eb</code></a>
Beta 1.18.0 (<a
href="https://redirect.github.com/ClickHouse/clickhouse-js/issues/588">#588</a>)</li>
<li><a
href="https://github.com/ClickHouse/clickhouse-js/commit/d0f67b71ef896d47fc3d8d0942612ced22aa79dc"><code>d0f67b7</code></a>
Split public and internal <code>drainStream</code> and cover with tests
(<a
href="https://redirect.github.com/ClickHouse/clickhouse-js/issues/578">#578</a>)</li>
<li><a
href="https://github.com/ClickHouse/clickhouse-js/commit/535e9b726e328ce8468c159f935c27910731f4bb"><code>535e9b7</code></a>
Remove <code>unsafeLogUnredactedQueries</code> for now (<a
href="https://redirect.github.com/ClickHouse/clickhouse-js/issues/580">#580</a>)</li>
<li><a
href="https://github.com/ClickHouse/clickhouse-js/commit/44e73c73019a3956c1fac67ce0f4f170b3a4f19a"><code>44e73c7</code></a>
Default log level to <code>WARN</code> (<a
href="https://redirect.github.com/ClickHouse/clickhouse-js/issues/581">#581</a>)</li>
<li><a
href="https://github.com/ClickHouse/clickhouse-js/commit/5146fbc13e5c23d08e2cc5773bea0adf58d83a5c"><code>5146fbc</code></a>
Focus AI on security and API stability (<a
href="https://redirect.github.com/ClickHouse/clickhouse-js/issues/579">#579</a>)</li>
<li><a
href="https://github.com/ClickHouse/clickhouse-js/commit/b7b1d8d7ffe9b6786c9e883379d3edc5a5ed5c58"><code>b7b1d8d</code></a>
Trivial E2E test against <code>beta</code> (<a
href="https://redirect.github.com/ClickHouse/clickhouse-js/issues/577">#577</a>)</li>
<li><a
href="https://github.com/ClickHouse/clickhouse-js/commit/761e29ebb5d1bd7a107d2535b385b6565b7120f5"><code>761e29e</code></a>
Structured logs, part 1 (<a
href="https://redirect.github.com/ClickHouse/clickhouse-js/issues/576">#576</a>)</li>
<li><a
href="https://github.com/ClickHouse/clickhouse-js/commit/fd23dd7fc9e91ff810a7bb45984a24682ba25482"><code>fd23dd7</code></a>
Provide more context in logs for connection and request handling (<a
href="https://redirect.github.com/ClickHouse/clickhouse-js/issues/567">#567</a>)</li>
<li><a
href="https://github.com/ClickHouse/clickhouse-js/commit/a7866e72e356244cae9d20d9fef38a6aafe68ba8"><code>a7866e7</code></a>
Adjusting CI DevX (<a
href="https://redirect.github.com/ClickHouse/clickhouse-js/issues/574">#574</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/ClickHouse/clickhouse-js/compare/1.11.0...1.18.1">compare
view</a></li>
</ul>
</details>
<details>
<summary>Maintainer changes</summary>
<p>This version was pushed to npm by <a
href="https://www.npmjs.com/~4b819b88c84b">4b819b88c84b</a>, a new
releaser for <code>@​clickhouse/client</code> since your current
version.</p>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=@clickhouse/client&package-manager=npm_and_yarn&previous-version=1.11.0&new-version=1.18.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Abdullah <125115953+mabdullahabaid@users.noreply.github.com>
2026-03-05 10:11:32 +01:00
Abdullah.andGitHub 338a38682d feat: upgrade nx to latest (#18404)
Upgraded NX to resolve some dependabot alerts caused by transitive
dependencies, but after the upgrade, it appears those transitive
dependency issues were not fixed by NX in the first place.

Creating this PR with the upgrade regardless to avoid wasted work. Used
`npx nx@latest migrate latest` from the documentation to automate the
upgrade and it bumped all the dependencies changed in `package.json` for
compatibility - `react-router-dom` and `swc` ones too.

Ran tests, ran builds, started the development server and used the
application - everything looks good after the upgrade.
2026-03-05 09:36:33 +01:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>Abdullah
6a2e0182ab Bump @blocknote/server-util from 0.47.0 to 0.47.1 (#18408)
Bumps
[@blocknote/server-util](https://github.com/TypeCellOS/BlockNote/tree/HEAD/packages/server-util)
from 0.47.0 to 0.47.1.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/TypeCellOS/BlockNote/releases"><code>@​blocknote/server-util</code>'s
releases</a>.</em></p>
<blockquote>
<h2>v0.47.1</h2>
<h2>0.47.1 (2026-03-02)</h2>
<h3>🩹 Fixes</h3>
<ul>
<li>typeerror cannot read properties of undefined (<a
href="https://redirect.github.com/TypeCellOS/BlockNote/pull/2522">#2522</a>)</li>
<li>handle more delete key cases (<a
href="https://redirect.github.com/TypeCellOS/BlockNote/pull/2126">#2126</a>)</li>
<li>add delay for <code>data-active</code> in collab cursors (<a
href="https://redirect.github.com/TypeCellOS/BlockNote/pull/2383">#2383</a>)</li>
<li>disable slash menu in table content <a
href="https://github.com/TypeCellOS/BlockNote/tree/HEAD/packages/server-util/issues/2408">#2408</a>
(<a
href="https://redirect.github.com/TypeCellOS/BlockNote/pull/2504">#2504</a>,
<a
href="https://redirect.github.com/TypeCellOS/BlockNote/issues/2408">#2408</a>)</li>
<li><strong>ai:</strong> selections broken due to floating-ui focus
manager (<a
href="https://redirect.github.com/TypeCellOS/BlockNote/pull/2527">#2527</a>)</li>
</ul>
<h3>❤️ Thank You</h3>
<ul>
<li>Matthew Lipski <a
href="https://github.com/matthewlipski"><code>@​matthewlipski</code></a></li>
<li>Nick Perez</li>
<li>Yousef</li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/TypeCellOS/BlockNote/blob/main/CHANGELOG.md"><code>@​blocknote/server-util</code>'s
changelog</a>.</em></p>
<blockquote>
<h2>0.47.1 (2026-03-02)</h2>
<h3>🩹 Fixes</h3>
<ul>
<li>typeerror cannot read properties of undefined (<a
href="https://redirect.github.com/TypeCellOS/BlockNote/pull/2522">#2522</a>)</li>
<li>handle more delete key cases (<a
href="https://redirect.github.com/TypeCellOS/BlockNote/pull/2126">#2126</a>)</li>
<li>add delay for <code>data-active</code> in collab cursors (<a
href="https://redirect.github.com/TypeCellOS/BlockNote/pull/2383">#2383</a>)</li>
<li>disable slash menu in table content <a
href="https://github.com/TypeCellOS/BlockNote/tree/HEAD/packages/server-util/issues/2408">#2408</a>
(<a
href="https://redirect.github.com/TypeCellOS/BlockNote/pull/2504">#2504</a>,
<a
href="https://redirect.github.com/TypeCellOS/BlockNote/issues/2408">#2408</a>)</li>
<li><strong>ai:</strong> selections broken due to floating-ui focus
manager (<a
href="https://redirect.github.com/TypeCellOS/BlockNote/pull/2527">#2527</a>)</li>
</ul>
<h3>❤️ Thank You</h3>
<ul>
<li>Matthew Lipski <a
href="https://github.com/matthewlipski"><code>@​matthewlipski</code></a></li>
<li>Nick Perez</li>
<li>Yousef</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/TypeCellOS/BlockNote/commit/d5d056fe3d5362e73fb72e3e3bf1f839aee3e875"><code>d5d056f</code></a>
chore(release): publish 0.47.1</li>
<li>See full diff in <a
href="https://github.com/TypeCellOS/BlockNote/commits/v0.47.1/packages/server-util">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=@blocknote/server-util&package-manager=npm_and_yarn&previous-version=0.47.0&new-version=0.47.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Abdullah <125115953+mabdullahabaid@users.noreply.github.com>
2026-03-05 09:22:06 +01:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>Abdullah
72086fe111 Bump @dagrejs/dagre from 1.1.3 to 1.1.8 (#18409)
Bumps [@dagrejs/dagre](https://github.com/dagrejs/dagre) from 1.1.3 to
1.1.8.
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/dagrejs/dagre/commit/7e4d15f191678f7f05f3c86d9071a193230e7e00"><code>7e4d15f</code></a>
Building for release</li>
<li><a
href="https://github.com/dagrejs/dagre/commit/d3908e2c13148c9143db585accc10ae0b6634657"><code>d3908e2</code></a>
Bumping version</li>
<li><a
href="https://github.com/dagrejs/dagre/commit/ce295f8e073c4fe96c9e36ecf08ae2940e5e6a10"><code>ce295f8</code></a>
Build for release</li>
<li><a
href="https://github.com/dagrejs/dagre/commit/b64b9057726eee17f24f73579ba0668527276448"><code>b64b905</code></a>
Bumping the version</li>
<li><a
href="https://github.com/dagrejs/dagre/commit/de169d24c13d06c1e9c560f4f4f8f98650109b94"><code>de169d2</code></a>
Merge pull request <a
href="https://redirect.github.com/dagrejs/dagre/issues/481">#481</a>
from Nathan-Fenner/nf/improve-network-simplex-perform...</li>
<li><a
href="https://github.com/dagrejs/dagre/commit/065e0d8374f4c1c35a7cb4b84df37aaa31598d86"><code>065e0d8</code></a>
improve performance of graph node ranking</li>
<li><a
href="https://github.com/dagrejs/dagre/commit/00d3178d671e49de9c032e3abd281dc9f2739e73"><code>00d3178</code></a>
Typo</li>
<li><a
href="https://github.com/dagrejs/dagre/commit/3982a69d2b323b06aa969a4ec09829d37fe6e7bd"><code>3982a69</code></a>
Bump version and set as pre-release</li>
<li><a
href="https://github.com/dagrejs/dagre/commit/1339f5516508dba0cbcc4ef1c0587e7384bec23d"><code>1339f55</code></a>
Building for release</li>
<li><a
href="https://github.com/dagrejs/dagre/commit/9459f01bc815f16b87db727821d8401acbad2cd3"><code>9459f01</code></a>
Bumping the version</li>
<li>Additional commits viewable in <a
href="https://github.com/dagrejs/dagre/compare/v1.1.3...v1.1.8">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=@dagrejs/dagre&package-manager=npm_and_yarn&previous-version=1.1.3&new-version=1.1.8)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Abdullah <125115953+mabdullahabaid@users.noreply.github.com>
2026-03-05 09:21:45 +01:00
228865bd94 i18n - translations (#18398)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-03-04 23:55:59 +01:00
Abdullah.andGitHub 2493adbb87 fix: minimatch related dependabot alerts (#18396)
This PR fixes a good number of dependabot alerts associated to minimatch
transitive import.

There are a total of 28 alerts, merging this shall confirm which ones
remain open afterwards and make it easier to diagnose.
2026-03-04 23:47:02 +01:00
26f0a416a1 File storage cleaning (#18381)
- Remove feature flag
- Remove legacy methods in file-upload and file-service
- Migrate AI Chat to new file management

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
2026-03-04 23:46:03 +01:00
Charles BochetandGitHub c41a8e2b23 [DevXP] Simplify twenty-ui theme system: replace auto-generated files with static CSS variables (#18389)
## Summary

Now that Twenty has fully migrated from Emotion to Linaria, the theme
system has been simplified to remove unnecessary complexity that existed
only to support the old runtime injection pattern.

### What changed

- **Deleted** `generateThemeConstants.ts` script and the entire
`generated/` directory — no more auto-generation
- **Added** `theme-light.css` and `theme-dark.css`: static CSS files
with 991 custom properties each, scoped under `.light` and `.dark`
selectors respectively
- **Moved** `themeCssVariables.ts` out of `generated/` and hand-maintain
it as a static `as const` object of `var(--t-*)` references (Linaria can
statically evaluate these at build time)
- **Extracted** numeric constants (`MOBILE_VIEWPORT`, `ICON_SIZES`,
`ICON_STROKES`) into a new `constants.ts` — CSS variables can't be used
in media queries or as numeric icon size props
- **Simplified** `ThemeContextProvider`: removed
`ThemeCssVariableInjectorEffect` entirely; now uses a single
`useLayoutEffect` to toggle `.light`/`.dark` class on `<html>`
- **Added** `class="light"` to `index.html` as default to prevent FOUC
before React hydration

### Why

The previous setup maintained a dual system: JS theme objects
(`THEME_LIGHT`/`THEME_DARK`) used at runtime, plus a generation script
that produced CSS variable entry arrays, which were then injected into
the DOM by `ThemeCssVariableInjectorEffect`. With Linaria, theme values
only need to be CSS custom properties — the JS objects were redundant.
This PR removes ~250 lines of infrastructure while keeping the same
theming capabilities.
2026-03-04 23:30:25 +01:00
neo773GitHubgreptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>Charles Bochetcubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
4c001778c2 fix google signup edge case (#18365)
Fixes an edge case when a user signs up with Google and the profile
avatar network request times out, we crash instead of creating the user
without an avatar.

Added `axios-retry` to retry max 2 times and if it still fails we
gracefully skip avatar image instead of crashing

Fixes
Sentry TWENTY-SERVER-FDQ
Sonarly https://sonarly.com/issue/6564

---------

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
Co-authored-by: Charles Bochet <charles@twenty.com>
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
2026-03-04 22:51:55 +01:00
Thomas TrompetteandGitHub 911a46aa45 Improve workflow perfs (#18376)
Workflow crons take a few minutes to run. Loading each repo takes ~200
to 300ms locally. Adding a lite mode so it takes less than 100ms.
Also doing batch promises.

Finally, cleaning runs timeout when there are too many. Doing batches as
well.
2026-03-04 18:29:12 +01:00
Paul RastoinandGitHub c53d281960 Fix invalid universal identifier format command cache flush (#18385)
# Introduction
Invalidating command impacted metadata and related metadata caches
entries
2026-03-04 17:43:01 +01:00
7f6b270a76 i18n - translations (#18388)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-03-04 17:32:14 +01:00
Abdul RahmanandGitHub c94657dc0a Navbar AI chats followup (#18336)
Addresses review comments from
[PR#18161](https://github.com/twentyhq/twenty/pull/18161)
2026-03-04 15:15:43 +00:00
aeedcf3353 Enable password reset from app.twenty.com with workspace fallback (#18271)
## Summary
- add a working `Forgot your password?` flow on `app.twenty.com` sign-in
- keep existing workspace-domain reset behavior
- when triggered without workspace context, resolve a workspace from the
user when possible, otherwise fallback to `app.twenty.com` reset URL

## Backend
- make `workspaceId` optional in `emailPasswordResetLink` input
- allow nullable `workspaceId` in password reset token DTO
- update reset token generation to accept optional `workspaceId`
- when missing, resolve first workspace by user membership
- if no workspace is found, persist token with `workspaceId = null`
- send reset links via:
  - workspace URL when `workspaceId` exists
  - app front URL + reset path when `workspaceId` is null

## Frontend
- make reset-link mutation `workspaceId` variable optional
- regenerate/patched generated metadata types accordingly
- add `Forgot your password?` in global password step
- allow reset request without workspace context in
`useHandleResetPassword`
- make reset page auto sign-in domain-aware (`workspace` vs `app`)
- apply design-system spacing above the global forgot-password link
(`theme.spacing(4)`)

## Tests
- extend reset-password service tests for:
  - explicit workspace id
  - inferred workspace when workspace id is missing
  - app-domain fallback when no workspace is found
- extend reset-password hook tests for with/without workspace context
- add focused global form test for forgot-password link rendering/click
behavior

## Product behavior for users with multiple workspaces
- no workspace chooser is shown in this flow
- backend uses the first resolvable workspace membership for the
reset-link domain
- password change remains account-level and works across all workspaces


Feature has been tested and is working 

<img width="3268" height="2106" alt="CleanShot 2026-02-26 at 14 09
14@2x"
src="https://github.com/user-attachments/assets/b5db3bed-f3aa-4d35-b54e-66e4d99141f9"
/>

---------

Co-authored-by: Etienne <45695613+etiennejouan@users.noreply.github.com>
2026-03-04 15:14:38 +00:00
4208 changed files with 155372 additions and 66460 deletions
-18
View File
@@ -1,18 +0,0 @@
{
"install": "curl -fsSL https://deb.nodesource.com/setup_24.x | sudo -E bash - && sudo apt-get install -y nodejs && node --version && yarn install && echo 'Setting up Docker Compose environment...' && cd packages/twenty-docker && cp -n docker-compose.yml docker-compose.dev.yml || true && echo 'Dependencies installed and docker-compose prepared'",
"start": "sudo service docker start && echo 'Docker service started' && cd packages/twenty-docker && echo 'Installing yq for YAML processing...' && sudo apt-get update -qq && sudo apt-get install -y wget && wget -qO /usr/local/bin/yq https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64 && sudo chmod +x /usr/local/bin/yq && echo 'Patching docker-compose for local development...' && yq eval 'del(.services.server.image)' -i docker-compose.dev.yml && yq eval '.services.server.build.context = \"../../\"' -i docker-compose.dev.yml && yq eval '.services.server.build.dockerfile = \"./packages/twenty-docker/twenty/Dockerfile\"' -i docker-compose.dev.yml && yq eval 'del(.services.worker.image)' -i docker-compose.dev.yml && yq eval '.services.worker.build.context = \"../../\"' -i docker-compose.dev.yml && yq eval '.services.worker.build.dockerfile = \"./packages/twenty-docker/twenty/Dockerfile\"' -i docker-compose.dev.yml && echo 'Setting up .env file with database configuration...' && echo 'SERVER_URL=http://localhost:3000' > .env && echo 'APP_SECRET='$(openssl rand -base64 32) >> .env && echo 'PG_DATABASE_PASSWORD='$(openssl rand -hex 16) >> .env && echo 'PG_DATABASE_URL=postgres://postgres:password@localhost:5432/postgres' >> .env && echo 'SIGN_IN_PREFILLED=true' >> .env && echo 'Building and starting services...' && docker-compose -f docker-compose.dev.yml up -d --build && echo 'Waiting for services to initialize...' && sleep 30 && echo 'Checking service health...' && docker-compose -f docker-compose.dev.yml ps && echo 'Environment setup complete!'",
"terminals": [
{
"name": "Database Setup & Seed",
"command": "sleep 40 && cd packages/twenty-docker && echo 'Waiting for PostgreSQL to be ready...' && until docker-compose -f docker-compose.dev.yml exec -T db pg_isready -U postgres; do echo 'Waiting for PostgreSQL...'; sleep 5; done && echo 'PostgreSQL is ready!' && echo 'Waiting for Twenty server to be healthy...' && until docker-compose -f docker-compose.dev.yml exec -T server curl --fail http://localhost:3000/healthz 2>/dev/null; do echo 'Waiting for server...'; sleep 5; done && echo 'Server is healthy!' && echo 'Running database setup and seeding...' && docker-compose -f docker-compose.dev.yml exec -T server npx nx database:reset twenty-server && echo 'Database seeded successfully!' && bash"
},
{
"name": "Application Logs",
"command": "sleep 35 && cd packages/twenty-docker && echo 'Following application logs...' && docker-compose -f docker-compose.dev.yml logs -f server worker"
},
{
"name": "Service Monitor",
"command": "sleep 15 && cd packages/twenty-docker && echo '=== Service Status Monitor ===' && while true; do clear; echo '=== Service Status at $(date) ===' && docker-compose -f docker-compose.dev.yml ps && echo '\\n=== Health Status ===' && (docker-compose -f docker-compose.dev.yml exec -T server curl -s http://localhost:3000/healthz 2>/dev/null && echo '✅ Twenty Server: Healthy') || echo '❌ Twenty Server: Not Ready' && (docker-compose -f docker-compose.dev.yml exec -T db pg_isready -U postgres 2>/dev/null && echo '✅ PostgreSQL: Ready') || echo '❌ PostgreSQL: Not Ready' && echo '\\n=== Database Connection Test ===' && docker-compose -f docker-compose.dev.yml exec -T server node -e \"const { Client } = require('pg'); const client = new Client({connectionString: process.env.PG_DATABASE_URL}); client.connect().then(() => {console.log('✅ Database Connection: OK'); client.end();}).catch(e => console.log('❌ Database Connection: Failed -', e.message));\" || echo 'Connection test failed' && sleep 45; done"
}
]
}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"install": "yarn install",
"start": "sudo service docker start && sleep 2 && (docker start twenty_pg 2>/dev/null || make -C packages/twenty-docker postgres-on-docker) && (docker start twenty_redis 2>/dev/null || make -C packages/twenty-docker redis-on-docker) && until docker exec twenty_pg pg_isready -U postgres -h localhost 2>/dev/null; do sleep 1; done && echo 'PostgreSQL ready' && until docker exec twenty_redis redis-cli ping 2>/dev/null | grep -q PONG; do sleep 1; done && echo 'Redis ready' && bash packages/twenty-utils/setup-dev-env.sh && npx nx database:reset twenty-server",
"start": "(sudo service docker start || service docker start || true) && bash packages/twenty-utils/setup-dev-env.sh && npx nx database:reset twenty-server",
"terminals": [
{
"name": "Development Server",
+1 -1
View File
@@ -8,7 +8,7 @@ alwaysApply: true
## Formatting Standards
- **Prettier**: 2-space indentation, single quotes, trailing commas, semicolons
- **Print width**: 80 characters
- **ESLint**: No unused imports, consistent import ordering, prefer const over let
- **Oxlint**: No unused imports, consistent import ordering, prefer const over let
## Naming Conventions
```typescript
+53
View File
@@ -0,0 +1,53 @@
---
description: ESM dependency guidelines for twenty-sdk and create-twenty-app packages
globs: ["packages/twenty-sdk/**", "packages/create-twenty-app/**"]
alwaysApply: false
---
# ESM Dependency Guidelines
## Context
`twenty-sdk` and `create-twenty-app` are published as dual-format npm packages (ESM `.mjs` + CJS `.cjs`). Dependencies listed in `dependencies` are **externalized** by the Vite/Rollup build — they are not bundled, and consumers resolve them from `node_modules` at runtime.
This means **CJS-only dependencies break the ESM output**. When Rollup emits `import { foo } from 'cjs-package'`, Node.js ESM cannot resolve named exports from CommonJS modules, causing `SyntaxError: Named export 'foo' not found`.
## Rules
### Only add ESM-compatible dependencies
Before adding a new dependency to `package.json`, verify it supports ESM:
- Check for `"type": "module"` in its `package.json`
- Or check for an `"exports"` map with ESM entries
- Or check for a `"module"` field pointing to an ESM build
### Use native `node:fs/promises` for standard fs operations
```typescript
// ✅ Import native fs functions directly
import { readFile, writeFile, mkdir, rm, cp } from 'node:fs/promises';
import { createWriteStream, existsSync } from 'node:fs';
// ✅ Import only custom helpers from fs-utils (no native re-exports)
import { pathExists, ensureDir, emptyDir, copy, move, remove, readJson, writeJson, ensureFile } from '@/cli/utilities/file/fs-utils';
// ❌ Don't use fs-extra (CJS-only, breaks ESM bundle)
import * as fs from 'fs-extra';
// ❌ Don't use import * as fs from fs-utils (it doesn't re-export native fs)
import * as fs from '@/cli/utilities/file/fs-utils';
```
### Use `@/cli/utilities/string/kebab-case` instead of lodash
```typescript
// ✅ Use internal utility
import { kebabCase } from '@/cli/utilities/string/kebab-case';
// ❌ Don't use lodash single-function packages (CJS-only, unmaintained)
import kebabCase from 'lodash.kebabcase';
```
### When no ESM alternative exists
If a CJS-only package has no ESM replacement (e.g. `archiver`), add it to the `cjsOnlyPackages` list in `vite.config.node.ts` so it gets inlined into the bundle instead of externalized.
+8
View File
@@ -15,8 +15,16 @@ inputs:
runs:
using: "composite"
steps:
- name: Fetch main branch for diff
shell: bash
run: git fetch origin main --depth=1
- name: Get last successful commit
if: env.NX_BASE == ''
uses: nrwl/nx-set-shas@v4
- name: Fallback to origin/main if no base found
if: env.NX_BASE == ''
shell: bash
run: echo "NX_BASE=$(git rev-parse origin/main)" >> $GITHUB_ENV
- name: Run affected command
shell: bash
env:
+19
View File
@@ -0,0 +1,19 @@
storage: /tmp/verdaccio-storage
auth:
htpasswd:
file: /tmp/verdaccio-htpasswd
max_users: 100
uplinks:
npmjs:
url: https://registry.npmjs.org/
packages:
'twenty-sdk':
access: $all
publish: $all
'create-twenty-app':
access: $all
publish: $all
'**':
access: $all
proxy: npmjs
log: { type: stdout, format: pretty, level: warn }
+1 -1
View File
@@ -23,7 +23,7 @@ jobs:
- name: Fetch custom Github Actions and base branch history
uses: actions/checkout@v4
with:
fetch-depth: 0
fetch-depth: 10
- name: Check for changed files
id: changed-files
uses: tj-actions/changed-files@v45
+1 -1
View File
@@ -70,7 +70,7 @@ jobs:
- name: Checkout current branch
uses: actions/checkout@v4
with:
fetch-depth: 0
fetch-depth: 10
- name: Try to merge main into current branch
id: merge_attempt
+182
View File
@@ -0,0 +1,182 @@
name: CI Create App E2E
on:
push:
branches:
- main
pull_request:
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
jobs:
changed-files-check:
uses: ./.github/workflows/changed-files.yaml
with:
files: |
packages/create-twenty-app/**
packages/twenty-sdk/**
packages/twenty-shared/**
packages/twenty-server/**
!packages/create-twenty-app/package.json
!packages/twenty-sdk/package.json
!packages/twenty-shared/package.json
!packages/twenty-server/package.json
create-app-e2e:
needs: changed-files-check
if: needs.changed-files-check.outputs.any_changed == 'true'
timeout-minutes: 30
runs-on: ubuntu-latest-4-cores
services:
postgres:
image: twentycrm/twenty-postgres-spilo
env:
PGUSER_SUPERUSER: postgres
PGPASSWORD_SUPERUSER: postgres
ALLOW_NOSSL: 'true'
SPILO_PROVIDER: 'local'
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
redis:
image: redis
ports:
- 6379:6379
steps:
- name: Fetch custom Github Actions and base branch history
uses: actions/checkout@v4
with:
fetch-depth: 10
- name: Install dependencies
uses: ./.github/actions/yarn-install
- name: Set CI version and prepare packages for publish
run: |
CI_VERSION="0.0.0-ci.$(date +%s)"
echo "CI_VERSION=$CI_VERSION" >> $GITHUB_ENV
npx nx run-many -t set-local-version -p twenty-sdk create-twenty-app --releaseVersion=$CI_VERSION
- name: Build packages
run: |
npx nx build twenty-sdk
npx nx build create-twenty-app
- name: Install and start Verdaccio
run: |
npx verdaccio --config .github/verdaccio-config.yaml &
for i in $(seq 1 30); do
if curl -s http://localhost:4873 > /dev/null 2>&1; then
echo "Verdaccio is ready"
break
fi
echo "Waiting for Verdaccio... ($i/30)"
sleep 1
done
- name: Publish packages to local registry
run: |
npm set //localhost:4873/:_authToken "ci-auth-token"
for pkg in twenty-sdk create-twenty-app; do
cd packages/$pkg
npm publish --registry http://localhost:4873 --tag ci
cd ../..
done
- name: Scaffold app using published create-twenty-app
run: |
npm install -g create-twenty-app@$CI_VERSION --registry http://localhost:4873
create-twenty-app --version
mkdir -p /tmp/e2e-test-workspace
cd /tmp/e2e-test-workspace
create-twenty-app test-app --exhaustive --display-name "Test App" --description "E2E test app"
- name: Install scaffolded app dependencies
run: |
cd /tmp/e2e-test-workspace/test-app
echo 'npmRegistryServer: "http://localhost:4873"' >> .yarnrc.yml
echo 'unsafeHttpWhitelist: ["localhost"]' >> .yarnrc.yml
YARN_ENABLE_IMMUTABLE_INSTALLS=false yarn install --no-immutable
- name: Verify installed app versions
run: |
cd /tmp/e2e-test-workspace/test-app
echo "--- Checking package.json references correct SDK version ---"
node -e "
const pkg = require('./package.json');
const sdkVersion = pkg.devDependencies['twenty-sdk'];
if (!sdkVersion.startsWith('0.0.0-ci.')) {
console.error('Expected twenty-sdk version to start with 0.0.0-ci., got:', sdkVersion);
process.exit(1);
}
console.log('SDK version in scaffolded app:', sdkVersion);
"
- name: Verify SDK CLI is available
run: |
cd /tmp/e2e-test-workspace/test-app
npx --no-install twenty --version
- name: Setup server environment
run: npx nx reset:env:e2e-testing-server twenty-server
- name: Build server
run: npx nx build twenty-server
- name: Create and setup database
run: |
PGPASSWORD=postgres psql -h localhost -p 5432 -U postgres -d postgres -c 'CREATE DATABASE "default";'
PGPASSWORD=postgres psql -h localhost -p 5432 -U postgres -d postgres -c 'CREATE DATABASE "test";'
npx nx run twenty-server:database:reset
- name: Start server
run: |
npx nx start twenty-server &
echo "Waiting for server to be ready..."
timeout 60 bash -c 'until curl -s http://localhost:3000/health; do sleep 2; done'
- name: Authenticate with twenty-server
env:
SEED_API_KEY: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIyMDIwMjAyMC1lNmI1LTQ2ODAtOGEzMi1iODIwOTczNzE1NmIiLCJ1c2VySWQiOiIyMDIwMjAyMC1lNmI1LTQ2ODAtOGEzMi1iODIwOTczNzE1NmIiLCJ3b3Jrc3BhY2VJZCI6IjIwMjAyMDIwLTFjMjUtNGQwMi1iZjI1LTZhZWNjZjdlYTQxOSIsIndvcmtzcGFjZU1lbWJlcklkIjoiMjAyMDIwMjAtNDYzZi00MzViLTgyOGMtMTA3ZTAwN2EyNzExIiwidXNlcldvcmtzcGFjZUlkIjoiMjAyMDIwMjAtMWU3Yy00M2Q5LWE1ZGItNjg1YjUwNjlkODE2IiwidHlwZSI6IkFDQ0VTUyIsImF1dGhQcm92aWRlciI6InBhc3N3b3JkIiwiaWF0IjoxNzUxMjgxNzA0LCJleHAiOjIwNjY4NTc3MDR9.HMGqCsVlOAPVUBhKSGlD1X86VoHKt4LIUtET3CGIdik'
run: |
cd /tmp/e2e-test-workspace/test-app
npx --no-install twenty auth:login --api-key $SEED_API_KEY --api-url http://localhost:3000
- name: Build scaffolded app
run: |
cd /tmp/e2e-test-workspace/test-app
npx --no-install twenty app:build
test -d .twenty/output
- name: Execute hello-world logic function
run: |
cd /tmp/e2e-test-workspace/test-app
EXEC_OUTPUT=$(npx --no-install twenty function:execute --functionName hello-world-logic-function)
echo "$EXEC_OUTPUT"
echo "$EXEC_OUTPUT" | grep -q "Hello, World!"
- name: Run scaffolded app integration test
run: |
cd /tmp/e2e-test-workspace/test-app
yarn test
ci-create-app-e2e-status-check:
if: always() && !cancelled()
timeout-minutes: 5
runs-on: ubuntu-latest
needs: [changed-files-check, create-app-e2e]
steps:
- name: Fail job if any needs failed
if: contains(needs.*.result, 'failure')
run: exit 1
+1 -1
View File
@@ -37,7 +37,7 @@ jobs:
- name: Fetch custom Github Actions and base branch history
uses: actions/checkout@v4
with:
fetch-depth: 0
fetch-depth: 10
- name: Install dependencies
uses: ./.github/actions/yarn-install
- name: Build
+3 -4
View File
@@ -21,7 +21,6 @@ jobs:
files: |
package.json
packages/twenty-docs/**
eslint.config.mjs
docs-lint:
needs: changed-files-check
@@ -37,11 +36,11 @@ jobs:
- name: Fetch local actions
uses: actions/checkout@v4
with:
fetch-depth: 0
fetch-depth: 10
- name: Install dependencies
uses: ./.github/actions/yarn-install
- name: Docs / Lint English MDX files
run: npx eslint "packages/twenty-docs/{developers,user-guide,twenty-ui,getting-started,snippets}/**/*.mdx" --max-warnings 0
- name: Docs / Lint
run: npx nx lint twenty-docs
+2 -2
View File
@@ -25,12 +25,12 @@ jobs:
needs: changed-files-check
if: needs.changed-files-check.outputs.any_changed == 'true'
timeout-minutes: 10
runs-on: ubuntu-latest-8-cores
runs-on: ubuntu-latest
steps:
- name: Fetch custom Github Actions and base branch history
uses: actions/checkout@v4
with:
fetch-depth: 0
fetch-depth: 10
- name: Install dependencies
uses: ./.github/actions/yarn-install
- name: Build twenty-emails
+32 -165
View File
@@ -1,8 +1,7 @@
name: CI Front and E2E
name: CI Front
on:
pull_request:
merge_group:
permissions:
@@ -19,6 +18,7 @@ env:
jobs:
changed-files-check:
if: github.event_name != 'merge_group'
uses: ./.github/workflows/changed-files.yaml
with:
files: |
@@ -29,15 +29,6 @@ jobs:
packages/twenty-shared/**
packages/twenty-sdk/**
!packages/twenty-sdk/package.json
changed-files-check-e2e:
uses: ./.github/workflows/changed-files.yaml
with:
files: |
packages/**
!packages/create-twenty-app/package.json
!packages/twenty-sdk/package.json
playwright.config.ts
.github/workflows/ci-front.yaml
front-sb-build:
needs: changed-files-check
if: needs.changed-files-check.outputs.any_changed == 'true'
@@ -53,7 +44,7 @@ jobs:
- name: Fetch local actions
uses: actions/checkout@v4
with:
fetch-depth: 0
fetch-depth: 10
- name: Install dependencies
uses: ./.github/actions/yarn-install
- name: Diagnostic disk space issue
@@ -62,13 +53,19 @@ jobs:
run: npx nx reset:env twenty-front
- name: Front / Build storybook
run: npx nx storybook:build twenty-front
- name: Upload storybook build
uses: actions/upload-artifact@v4
with:
name: storybook-static
path: packages/twenty-front/storybook-static
retention-days: 1
- name: Save storybook build cache
uses: ./.github/actions/save-cache
with:
key: ${{ env.STORYBOOK_BUILD_CACHE_KEY_FOR_SAVE_ACTION }}
front-sb-test:
timeout-minutes: 30
runs-on: ubuntu-latest-8-cores
runs-on: ubuntu-latest
needs: front-sb-build
strategy:
fail-fast: false
@@ -78,26 +75,41 @@ jobs:
env:
SHARD_COUNTER: 4
REACT_APP_SERVER_BASE_URL: http://localhost:3000
STORYBOOK_URL: http://localhost:6006
steps:
- name: Fetch local actions
uses: actions/checkout@v4
with:
fetch-depth: 0
fetch-depth: 10
- name: Install dependencies
uses: ./.github/actions/yarn-install
- name: Restore storybook build cache
uses: ./.github/actions/restore-cache
with:
key: ${{ env.STORYBOOK_BUILD_CACHE_KEY_FOR_RESTORE_ACTION }}
- name: Clean stale storybook vitest cache
run: rm -rf packages/twenty-front/node_modules/.cache/storybook
- name: Build dependencies
run: |
npx nx build twenty-shared
npx nx build twenty-ui
npx nx build twenty-sdk
- name: Download storybook build
uses: actions/download-artifact@v4
with:
name: storybook-static
path: packages/twenty-front/storybook-static
- name: Install Playwright
run: |
cd packages/twenty-front
npx playwright install
- name: Front / Write .env
run: npx nx reset:env twenty-front
- name: Run storybook tests
run: npx nx storybook:test twenty-front --configuration=${{ matrix.storybook_scope }} --shard=${{ matrix.shard }}/${{ env.SHARD_COUNTER }}
- name: Serve storybook & run tests
run: |
npx http-server packages/twenty-front/storybook-static --port 6006 --silent &
timeout 30 bash -c 'until curl -sf http://localhost:6006 > /dev/null 2>&1; do sleep 1; done'
npx nx storybook:test twenty-front --configuration=${{ matrix.storybook_scope }} --shard=${{ matrix.shard }}/${{ env.SHARD_COUNTER }}
# - name: Rename coverage file
# run: |
# if [ -f "packages/twenty-front/coverage/storybook/coverage-final.json" ]; then
@@ -125,7 +137,7 @@ jobs:
# steps:
# - uses: actions/checkout@v4
# with:
# fetch-depth: 0
# fetch-depth: 10
# - name: Install dependencies
# uses: ./.github/actions/yarn-install
# - uses: actions/download-artifact@v4
@@ -139,32 +151,6 @@ jobs:
# npx nyc merge coverage-artifacts ${{ env.PATH_TO_COVERAGE }}/coverage-storybook.json
# - name: Checking coverage
# run: npx nx storybook:coverage twenty-front --checkCoverage=true --configuration=${{ matrix.storybook_scope }}
front-chromatic-deployment:
timeout-minutes: 30
if: false
needs: front-sb-build
runs-on: ubuntu-latest-8-cores
env:
REACT_APP_SERVER_BASE_URL: http://127.0.0.1:3000
CHROMATIC_PROJECT_TOKEN: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install dependencies
uses: ./.github/actions/yarn-install
- name: Restore storybook build cache
uses: ./.github/actions/restore-cache
with:
key: ${{ env.STORYBOOK_BUILD_CACHE_KEY_FOR_RESTORE_ACTION }}
- name: Front / Write .env
run: |
cd packages/twenty-front
touch .env
echo "" >> .env
echo "REACT_APP_SERVER_BASE_URL=$REACT_APP_SERVER_BASE_URL" >> .env
- name: Publish to Chromatic
run: npx nx run twenty-front:chromatic:ci
front-task:
needs: changed-files-check
if: needs.changed-files-check.outputs.any_changed == 'true'
@@ -184,7 +170,7 @@ jobs:
- name: Fetch custom Github Actions and base branch history
uses: actions/checkout@v4
with:
fetch-depth: 0
fetch-depth: 10
- name: Install dependencies
uses: ./.github/actions/yarn-install
- name: Restore ${{ matrix.task }} cache
@@ -214,6 +200,7 @@ jobs:
runs-on: ubuntu-latest-8-cores
env:
NODE_OPTIONS: "--max-old-space-size=10240"
ANALYZE: "true"
steps:
- name: Cancel Previous Runs
uses: styfle/cancel-workflow-action@0.11.0
@@ -222,7 +209,7 @@ jobs:
- name: Fetch custom Github Actions and base branch history
uses: actions/checkout@v4
with:
fetch-depth: 0
fetch-depth: 10
- name: Install dependencies
uses: ./.github/actions/yarn-install
- name: Front / Write .env
@@ -235,117 +222,6 @@ jobs:
# name: frontend-build
# path: packages/twenty-front/build
# retention-days: 1
e2e-test:
runs-on: ubuntu-latest
needs: [changed-files-check-e2e, front-build]
if: |
always() &&
needs.changed-files-check-e2e.outputs.any_changed == 'true' &&
(needs.front-build.result == 'success' || needs.front-build.result == 'skipped') &&
(github.event_name == 'push' || github.event_name == 'merge_group' || (github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'run-e2e')))
timeout-minutes: 30
env:
NODE_OPTIONS: "--max-old-space-size=10240"
services:
postgres:
image: twentycrm/twenty-postgres-spilo
env:
PGUSER_SUPERUSER: postgres
PGPASSWORD_SUPERUSER: postgres
ALLOW_NOSSL: "true"
SPILO_PROVIDER: "local"
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
redis:
image: redis
ports:
- 6379:6379
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-node@v4
with:
node-version: lts/*
- name: Check system resources
run: |
echo "Available memory:"
free -h
echo "Available disk space:"
df -h
echo "CPU info:"
lscpu
- name: Install dependencies
uses: ./.github/actions/yarn-install
- name: Build twenty-shared
run: npx nx build twenty-shared
- name: Install Playwright Browsers
run: npx nx setup twenty-e2e-testing
- name: Setup environment files
run: |
cp packages/twenty-front/.env.example packages/twenty-front/.env
npx nx reset:env:e2e-testing-server twenty-server
# - name: Download frontend build artifact
# if: needs.front-build.result == 'success'
# uses: actions/download-artifact@v4
# with:
# name: frontend-build
# path: packages/twenty-front/build
# - name: Build frontend (if not available from front-build)
# if: needs.front-build.result == 'skipped'
# run: NODE_ENV=production NODE_OPTIONS="--max-old-space-size=10240" npx nx build twenty-front
- name: Build frontend
run: NODE_ENV=production NODE_OPTIONS="--max-old-space-size=10240" npx nx build twenty-front
- name: Build server
run: npx nx build twenty-server
- name: Create and setup database
run: |
PGPASSWORD=postgres psql -h localhost -p 5432 -U postgres -d postgres -c 'CREATE DATABASE "default";'
PGPASSWORD=postgres psql -h localhost -p 5432 -U postgres -d postgres -c 'CREATE DATABASE "test";'
npx nx run twenty-server:database:reset
- name: Start server
run: |
npx nx start twenty-server &
echo "Waiting for server to be ready..."
timeout 60 bash -c 'until curl -s http://localhost:3000/health; do sleep 2; done'
- name: Start frontend
run: |
npm_config_yes=true npx serve -s packages/twenty-front/build -l 3001 &
echo "Waiting for frontend to be ready..."
timeout 60 bash -c 'until curl -s http://localhost:3001; do sleep 2; done'
- name: Start worker
run: |
npx nx run twenty-server:worker &
echo "Worker started"
- name: Run Playwright tests
run: npx nx test twenty-e2e-testing
# - uses: actions/upload-artifact@v4
# if: always()
# with:
# name: playwright-report
# path: packages/twenty-e2e-testing/run_results/
# retention-days: 30
ci-front-status-check:
if: always() && !cancelled()
timeout-minutes: 5
@@ -363,12 +239,3 @@ jobs:
- name: Fail job if any needs failed
if: contains(needs.*.result, 'failure')
run: exit 1
ci-e2e-status-check:
if: always() && !cancelled()
timeout-minutes: 5
runs-on: ubuntu-latest
needs: [changed-files-check-e2e, e2e-test]
steps:
- name: Fail job if any needs failed
if: contains(needs.*.result, 'failure')
run: exit 1
+140
View File
@@ -0,0 +1,140 @@
name: CI Merge Queue
on:
merge_group:
pull_request:
types: [labeled, synchronize, opened, reopened]
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.event_name == 'merge_group' && github.event.merge_group.base_ref || github.ref }}
cancel-in-progress: ${{ github.event_name != 'merge_group' }}
jobs:
e2e-test:
if: >
github.event_name == 'merge_group' ||
(github.event_name == 'pull_request' &&
contains(github.event.pull_request.labels.*.name, 'run-merge-queue'))
runs-on: ubuntu-latest-8-cores
timeout-minutes: 30
env:
NODE_OPTIONS: "--max-old-space-size=10240"
services:
postgres:
image: twentycrm/twenty-postgres-spilo
env:
PGUSER_SUPERUSER: postgres
PGPASSWORD_SUPERUSER: postgres
ALLOW_NOSSL: "true"
SPILO_PROVIDER: "local"
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
redis:
image: redis
ports:
- 6379:6379
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 10
- uses: actions/setup-node@v4
with:
node-version: lts/*
- name: Install dependencies
uses: ./.github/actions/yarn-install
- name: Restore Nx build cache
uses: actions/cache/restore@v4
with:
key: v4-e2e-build-${{ github.ref_name }}-${{ github.sha }}
restore-keys: |
v4-e2e-build-${{ github.ref_name }}-
v4-e2e-build-main-
path: |
.nx
node_modules/.cache
packages/*/node_modules/.cache
- name: Build twenty-shared
run: npx nx build twenty-shared
- name: Install Playwright Browsers
run: npx nx setup twenty-e2e-testing
- name: Setup environment files
run: |
cp packages/twenty-front/.env.example packages/twenty-front/.env
npx nx reset:env:e2e-testing-server twenty-server
- name: Build frontend
run: NODE_ENV=production npx nx build twenty-front
- name: Build server
run: npx nx build twenty-server
- name: Save Nx build cache
if: always()
uses: actions/cache/save@v4
with:
key: v4-e2e-build-${{ github.ref_name }}-${{ github.sha }}
path: |
.nx
node_modules/.cache
packages/*/node_modules/.cache
- name: Create and setup database
run: |
PGPASSWORD=postgres psql -h localhost -p 5432 -U postgres -d postgres -c 'CREATE DATABASE "default";'
PGPASSWORD=postgres psql -h localhost -p 5432 -U postgres -d postgres -c 'CREATE DATABASE "test";'
npx nx run twenty-server:database:reset
- name: Start server
run: |
npx nx start twenty-server &
echo "Waiting for server to be ready..."
timeout 60 bash -c 'until curl -s http://localhost:3000/health; do sleep 2; done'
- name: Start frontend
run: |
npm_config_yes=true npx serve -s packages/twenty-front/build -l 3001 &
echo "Waiting for frontend to be ready..."
timeout 60 bash -c 'until curl -s http://localhost:3001; do sleep 2; done'
- name: Start worker
run: |
npx nx run twenty-server:worker &
echo "Worker started"
- name: Run Playwright tests
run: npx nx test twenty-e2e-testing
- name: Upload Playwright results
if: always()
uses: actions/upload-artifact@v4
with:
name: playwright-results
path: |
packages/twenty-e2e-testing/run_results/
packages/twenty-e2e-testing/test-results/
retention-days: 7
ci-merge-queue-status-check:
if: always() && !cancelled()
timeout-minutes: 5
runs-on: ubuntu-latest
needs: [e2e-test]
steps:
- name: Fail job if any needs failed
if: contains(needs.*.result, 'failure')
run: exit 1
+6 -5
View File
@@ -1,9 +1,8 @@
name: CI SDK
on:
merge_group:
pull_request:
merge_group:
permissions:
contents: read
@@ -14,10 +13,12 @@ concurrency:
jobs:
changed-files-check:
if: github.event_name != 'merge_group'
uses: ./.github/workflows/changed-files.yaml
with:
files: |
packages/twenty-sdk/**
packages/twenty-server/**
!packages/twenty-sdk/package.json
sdk-test:
needs: changed-files-check
@@ -35,7 +36,7 @@ jobs:
- name: Fetch custom Github Actions and base branch history
uses: actions/checkout@v4
with:
fetch-depth: 0
fetch-depth: 10
- name: Install dependencies
uses: ./.github/actions/yarn-install
- name: Build
@@ -50,7 +51,7 @@ jobs:
tasks: ${{ matrix.task }}
sdk-e2e-test:
timeout-minutes: 30
runs-on: ubuntu-latest-8-cores
runs-on: ubuntu-latest
needs: [changed-files-check, sdk-test]
if: needs.changed-files-check.outputs.any_changed == 'true'
services:
@@ -78,7 +79,7 @@ jobs:
- name: Fetch custom Github Actions and base branch history
uses: actions/checkout@v4
with:
fetch-depth: 0
fetch-depth: 10
- name: Install dependencies
uses: ./.github/actions/yarn-install
- name: Build
+99 -36
View File
@@ -2,7 +2,6 @@ name: CI Server
on:
pull_request:
merge_group:
permissions:
@@ -13,10 +12,11 @@ concurrency:
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
env:
SERVER_SETUP_CACHE_KEY: server-setup
SERVER_BUILD_CACHE_KEY: server-build
jobs:
changed-files-check:
if: github.event_name != 'merge_group'
uses: ./.github/workflows/changed-files.yaml
with:
files: |
@@ -25,13 +25,62 @@ jobs:
packages/twenty-server/**
packages/twenty-front/src/generated/**
packages/twenty-front/src/generated-metadata/**
packages/twenty-sdk/src/clients/generated/metadata/**
packages/twenty-emails/**
packages/twenty-shared/**
server-setup:
server-build:
needs: changed-files-check
if: needs.changed-files-check.outputs.any_changed == 'true'
timeout-minutes: 30
runs-on: ubuntu-latest-8-cores
runs-on: ubuntu-latest
steps:
- name: Fetch custom Github Actions and base branch history
uses: actions/checkout@v4
with:
fetch-depth: 10
- name: Install dependencies
uses: ./.github/actions/yarn-install
- name: Restore server build cache
id: restore-server-build-cache
uses: ./.github/actions/restore-cache
with:
key: ${{ env.SERVER_BUILD_CACHE_KEY }}
- name: Build twenty-shared
run: npx nx build twenty-shared
- name: Server / Write .env
run: npx nx reset:env twenty-server
- name: Server / Build
run: npx nx build twenty-server
- name: Save server build cache
uses: ./.github/actions/save-cache
with:
key: ${{ steps.restore-server-build-cache.outputs.cache-primary-key }}
server-lint-typecheck:
needs: changed-files-check
if: needs.changed-files-check.outputs.any_changed == 'true'
timeout-minutes: 30
runs-on: ubuntu-latest
steps:
- name: Fetch custom Github Actions and base branch history
uses: actions/checkout@v4
with:
fetch-depth: 10
- name: Install dependencies
uses: ./.github/actions/yarn-install
- name: Build twenty-shared
run: npx nx build twenty-shared
- name: Server / Run lint & typecheck
uses: ./.github/actions/nx-affected
with:
tag: scope:backend
tasks: lint,typecheck
server-validation:
needs: server-build
timeout-minutes: 30
runs-on: ubuntu-latest
services:
postgres:
image: twentycrm/twenty-postgres-spilo
@@ -55,21 +104,15 @@ jobs:
- name: Fetch custom Github Actions and base branch history
uses: actions/checkout@v4
with:
fetch-depth: 0
fetch-depth: 10
- name: Install dependencies
uses: ./.github/actions/yarn-install
- name: Restore server setup
id: restore-server-setup-cache
- name: Restore server build cache
uses: ./.github/actions/restore-cache
with:
key: ${{ env.SERVER_SETUP_CACHE_KEY }}
key: ${{ env.SERVER_BUILD_CACHE_KEY }}
- name: Build twenty-shared
run: npx nx build twenty-shared
- name: Server / Run lint & typecheck
uses: ./.github/actions/nx-affected
with:
tag: scope:backend
tasks: lint,typecheck
- name: Server / Write .env
run: npx nx reset:env twenty-server
- name: Server / Build
@@ -84,10 +127,8 @@ jobs:
run: |
timeout 30s npx nx run twenty-server:worker || exit_code=$?
if [ $exit_code -eq 124 ]; then
# If timeout was reached (exit code 124), consider it a success
exit 0
elif [ $exit_code -ne 0 ]; then
# If worker failed for other reasons, fail the build
exit $exit_code
fi
- name: Server / Start
@@ -118,13 +159,13 @@ jobs:
exit 1
fi
- name: GraphQL / Check for Pending Generation
- name: Check for Pending Code Generation
run: |
# Run GraphQL generation commands
HAS_ERRORS=false
npx nx run twenty-front:graphql:generate
npx nx run twenty-front:graphql:generate --configuration=metadata
# Check if GraphQL generated files were modified
if ! git diff --quiet -- packages/twenty-front/src/generated packages/twenty-front/src/generated-metadata; then
echo "::error::GraphQL schema changes detected. Please run 'npx nx run twenty-front:graphql:generate' and 'npx nx run twenty-front:graphql:generate --configuration=metadata' and commit the changes."
echo ""
@@ -133,29 +174,42 @@ jobs:
git diff -- packages/twenty-front/src/generated packages/twenty-front/src/generated-metadata
echo "==================================================="
echo ""
echo "Please run 'npx nx run twenty-front:graphql:generate' and 'npx nx run twenty-front:graphql:generate --configuration=metadata' and commit the changes."
HAS_ERRORS=true
fi
npx nx run twenty-sdk:generate-metadata-client
if ! git diff --quiet -- packages/twenty-sdk/src/clients/generated/metadata; then
echo "::error::SDK metadata client changes detected. Please run 'npx nx run twenty-sdk:generate-metadata-client' and commit the changes."
echo ""
echo "The following SDK metadata client changes were detected:"
echo "==================================================="
git diff -- packages/twenty-sdk/src/clients/generated/metadata
echo "==================================================="
echo ""
HAS_ERRORS=true
fi
if [ "$HAS_ERRORS" = true ]; then
exit 1
fi
- name: Save server setup
uses: ./.github/actions/save-cache
with:
key: ${{ steps.restore-server-setup-cache.outputs.cache-primary-key }}
server-test:
needs: server-build
timeout-minutes: 30
runs-on: ubuntu-latest-8-cores
needs: server-setup
runs-on: ubuntu-latest
steps:
- name: Fetch custom Github Actions and base branch history
uses: actions/checkout@v4
with:
fetch-depth: 0
fetch-depth: 10
- name: Install dependencies
uses: ./.github/actions/yarn-install
- name: Restore server setup
- name: Restore server build cache
uses: ./.github/actions/restore-cache
with:
key: ${{ env.SERVER_SETUP_CACHE_KEY }}
key: ${{ env.SERVER_BUILD_CACHE_KEY }}
- name: Build twenty-shared
run: npx nx build twenty-shared
- name: Server / Run Tests
uses: ./.github/actions/nx-affected
with:
@@ -164,12 +218,12 @@ jobs:
server-integration-test:
timeout-minutes: 30
runs-on: ubuntu-latest-8-cores
needs: server-setup
runs-on: ubuntu-latest
needs: server-build
strategy:
fail-fast: false
matrix:
shard: [1, 2, 3, 4, 5, 6, 7, 8]
shard: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
services:
postgres:
image: twentycrm/twenty-postgres-spilo
@@ -207,12 +261,12 @@ jobs:
ANALYTICS_ENABLED: true
CLICKHOUSE_URL: "http://default:clickhousePassword@localhost:8123/twenty"
CLICKHOUSE_PASSWORD: clickhousePassword
SHARD_COUNTER: 8
SHARD_COUNTER: 10
steps:
- name: Fetch custom Github Actions and base branch history
uses: actions/checkout@v4
with:
fetch-depth: 0
fetch-depth: 10
- name: Install dependencies
uses: ./.github/actions/yarn-install
- name: Update .env.test for integrations tests
@@ -223,10 +277,10 @@ jobs:
echo "BILLING_STRIPE_BASE_PLAN_PRODUCT_ID=test-base-plan-product-id" >> .env.test
echo "BILLING_STRIPE_WEBHOOK_SECRET=test-webhook-secret" >> .env.test
echo "BILLING_PLAN_REQUIRED_LINK=http://localhost:3001/stripe-redirection" >> .env.test
- name: Restore server setup
- name: Restore server build cache
uses: ./.github/actions/restore-cache
with:
key: ${{ env.SERVER_SETUP_CACHE_KEY }}
key: ${{ env.SERVER_BUILD_CACHE_KEY }}
- name: Server / Build
run: npx nx build twenty-server
- name: Build dependencies
@@ -247,11 +301,20 @@ jobs:
tasks: 'test:integration'
configuration: 'with-db-reset'
args: --shard=${{ matrix.shard }}/${{ env.SHARD_COUNTER }}
ci-server-status-check:
if: always() && !cancelled()
timeout-minutes: 5
runs-on: ubuntu-latest
needs: [changed-files-check, server-setup, server-test, server-integration-test]
needs:
[
changed-files-check,
server-build,
server-lint-typecheck,
server-validation,
server-test,
server-integration-test,
]
steps:
- name: Fail job if any needs failed
if: contains(needs.*.result, 'failure')
+4 -4
View File
@@ -1,9 +1,8 @@
name: CI Shared
on:
merge_group:
pull_request:
merge_group:
permissions:
contents: read
@@ -14,6 +13,7 @@ concurrency:
jobs:
changed-files-check:
if: github.event_name != 'merge_group'
uses: ./.github/workflows/changed-files.yaml
with:
files: |
@@ -36,13 +36,13 @@ jobs:
- name: Fetch custom Github Actions and base branch history
uses: actions/checkout@v4
with:
fetch-depth: 0
fetch-depth: 10
- name: Install dependencies
uses: ./.github/actions/yarn-install
- name: Run ${{ matrix.task }} task
uses: ./.github/actions/nx-affected
with:
tag: scope:frontend
tag: scope:shared
tasks: ${{ matrix.task }}
ci-shared-status-check:
if: always() && !cancelled()
@@ -4,9 +4,8 @@ permissions:
contents: read
on:
merge_group:
pull_request:
merge_group:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
@@ -14,6 +13,7 @@ concurrency:
jobs:
changed-files-check:
if: github.event_name != 'merge_group'
uses: ./.github/workflows/changed-files.yaml
with:
files: |
+3 -3
View File
@@ -4,9 +4,8 @@ permissions:
contents: read
on:
merge_group:
pull_request:
merge_group:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
@@ -14,6 +13,7 @@ concurrency:
jobs:
changed-files-check:
if: github.event_name != 'merge_group'
uses: ./.github/workflows/changed-files.yaml
with:
files: |
@@ -42,7 +42,7 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
fetch-depth: 10
- name: Install dependencies
uses: ./.github/actions/yarn-install
+3 -5
View File
@@ -3,8 +3,6 @@ name: CI Zapier
on:
pull_request:
merge_group:
permissions:
contents: read
@@ -28,7 +26,7 @@ jobs:
needs: changed-files-check
if: needs.changed-files-check.outputs.any_changed == 'true'
timeout-minutes: 30
runs-on: ubuntu-latest-8-cores
runs-on: ubuntu-latest
services:
postgres:
image: twentycrm/twenty-postgres-spilo
@@ -52,7 +50,7 @@ jobs:
- name: Fetch custom Github Actions and base branch history
uses: actions/checkout@v4
with:
fetch-depth: 0
fetch-depth: 10
- name: Install dependencies
uses: ./.github/actions/yarn-install
@@ -107,7 +105,7 @@ jobs:
- name: Fetch custom Github Actions and base branch history
uses: actions/checkout@v4
with:
fetch-depth: 0
fetch-depth: 10
- name: Install dependencies
uses: ./.github/actions/yarn-install
- name: Build
+1 -1
View File
@@ -164,4 +164,4 @@ jobs:
token: ${{ secrets.CI_PRIVILEGED_DISPATCH_TOKEN }}
repository: twentyhq/ci-privileged
event-type: claude-cross-repo-response
client-payload: '{"repo": ${{ toJSON(steps.prompt.outputs.repo) }}, "issue_number": ${{ toJSON(steps.prompt.outputs.issue_number) }}, "run_id": ${{ toJSON(github.run_id) }}, "run_url": ${{ toJSON(format('{0}/{1}/actions/runs/{2}', github.server_url, github.repository, github.run_id)) }}}'
client-payload: '{"repo": ${{ toJSON(steps.prompt.outputs.repo) }}, "issue_number": ${{ toJSON(steps.prompt.outputs.issue_number) }}, "run_id": ${{ toJSON(github.run_id) }}, "run_url": "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"}'
+1 -1
View File
@@ -28,7 +28,7 @@ coverage
dist
storybook-static
*.tsbuildinfo
.eslintcache
.oxlintcache
.nyc_output
test-results/
dump.rdb
+1 -1
View File
@@ -1,7 +1,7 @@
{
"recommendations": [
"arcanis.vscode-zipfs",
"dbaeumer.vscode-eslint",
"oxc.oxc-vscode",
"esbenp.prettier-vscode",
"figma.figma-vscode-extension",
"firsttris.vscode-jest-runner",
+10 -7
View File
@@ -4,25 +4,28 @@
"files.insertFinalNewline": true,
"files.trimTrailingWhitespace": true,
"[typescript]": {
"editor.formatOnSave": false,
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit",
"source.fixAll.oxc": "explicit",
"source.addMissingImports": "always",
"source.organizeImports": "always"
}
},
"[javascript]": {
"editor.formatOnSave": false,
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit",
"source.fixAll.oxc": "explicit",
"source.addMissingImports": "always",
"source.organizeImports": "always"
}
},
"[typescriptreact]": {
"editor.formatOnSave": false,
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit",
"source.fixAll.oxc": "explicit",
"source.addMissingImports": "always",
"source.organizeImports": "always"
}
@@ -48,7 +51,7 @@
"search.exclude": {
"**/.yarn": true
},
"eslint.debug": true,
"oxc.lint.enable": true,
"files.associations": {
".cursorrules": "markdown"
},
+13 -9
View File
@@ -37,8 +37,8 @@
"path": "../packages/twenty-zapier"
},
{
"name": "tools/eslint-rules",
"path": "../tools/eslint-rules"
"name": "packages/twenty-oxlint-rules",
"path": "../packages/twenty-oxlint-rules"
},
{
"name": "packages/twenty-e2e-testing",
@@ -49,23 +49,26 @@
"editor.formatOnSave": false,
"files.eol": "auto",
"[typescript]": {
"editor.formatOnSave": false,
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit",
"source.fixAll.oxc": "explicit",
"source.addMissingImports": "always"
}
},
"[javascript]": {
"editor.formatOnSave": false,
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit",
"source.fixAll.oxc": "explicit",
"source.addMissingImports": "always"
}
},
"[typescriptreact]": {
"editor.formatOnSave": false,
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit",
"source.fixAll.oxc": "explicit",
"source.addMissingImports": "always"
}
},
@@ -88,7 +91,7 @@
"typescript.preferences.importModuleSpecifier": "non-relative",
"[javascript][typescript][typescriptreact]": {
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit",
"source.fixAll.oxc": "explicit",
"source.addMissingImports": "always"
}
},
@@ -98,6 +101,7 @@
"files.exclude": {
"packages/": true
},
"oxc.lint.enable": true,
"jest.runMode": "on-demand",
"jest.disabledWorkspaceFolders": [
"ROOT",
+13 -4
View File
@@ -188,13 +188,22 @@ IMPORTANT: Use Context7 for code generation, setup or configuration steps, or li
- Descriptive test names: "should [behavior] when [condition]"
- Clear mocks between tests with `jest.clearAllMocks()`
## CI Environment (GitHub Actions)
## Dev Environment Setup
When running in CI, the dev environment is **not** pre-configured. Dependencies are installed but builds, env files, and databases are not set up.
All dev environments (Claude Code web, Cursor, local) use one script:
- **Before running tests, builds, lint, type checks, or DB operations**, run: `bash packages/twenty-utils/setup-dev-env.sh`
```bash
bash packages/twenty-utils/setup-dev-env.sh
```
This handles everything: starts Postgres + Redis (auto-detects local services vs Docker), creates databases, and copies `.env` files. Idempotent — safe to run multiple times.
- `--docker` — force Docker mode (uses `packages/twenty-docker/docker-compose.dev.yml`)
- `--down` — stop services
- `--reset` — wipe data and restart fresh
- **Skip the setup script** for tasks that only read code — architecture questions, code review, documentation, etc.
- The script is idempotent and safe to run multiple times.
**Note:** CI workflows (GitHub Actions) manage services via Actions service containers and run setup steps individually — they don't use this script.
## Important Files
- `nx.json` - Nx workspace configuration with task definitions
-226
View File
@@ -1,226 +0,0 @@
import js from '@eslint/js';
import nxPlugin from '@nx/eslint-plugin';
import typescriptEslint from '@typescript-eslint/eslint-plugin';
import typescriptParser from '@typescript-eslint/parser';
import importPlugin from 'eslint-plugin-import';
import linguiPlugin from 'eslint-plugin-lingui';
import * as mdxPlugin from 'eslint-plugin-mdx';
import preferArrowPlugin from 'eslint-plugin-prefer-arrow';
import prettierPlugin from 'eslint-plugin-prettier';
import unicornPlugin from 'eslint-plugin-unicorn';
import unusedImportsPlugin from 'eslint-plugin-unused-imports';
import jsoncParser from 'jsonc-eslint-parser';
const twentyRules = await nxPlugin.loadWorkspaceRules(
'packages/twenty-eslint-rules',
);
export default [
// Base JavaScript configuration
js.configs.recommended,
// Lingui recommended rules
linguiPlugin.configs['flat/recommended'],
// Global ignores
{
ignores: ['**/node_modules/**'],
},
// Base configuration for all files
{
files: ['**/*.{js,jsx,ts,tsx}'],
plugins: {
prettier: prettierPlugin,
lingui: linguiPlugin,
'@nx': nxPlugin,
'prefer-arrow': preferArrowPlugin,
import: importPlugin,
'unused-imports': unusedImportsPlugin,
unicorn: unicornPlugin,
},
rules: {
// General rules
'func-style': ['error', 'declaration', { allowArrowFunctions: true }],
'no-console': [
'warn',
{ allow: ['group', 'groupCollapsed', 'groupEnd'] },
],
'no-control-regex': 0,
'no-debugger': 'error',
'no-duplicate-imports': 'error',
'no-undef': 'off',
'no-unused-vars': 'off',
// Nx rules
'@nx/enforce-module-boundaries': [
'error',
{
enforceBuildableLibDependency: true,
allow: [],
depConstraints: [
{
sourceTag: 'scope:apps',
onlyDependOnLibsWithTags: ['scope:apps', 'scope:sdk'],
},
{
sourceTag: 'scope:sdk',
onlyDependOnLibsWithTags: ['scope:sdk', 'scope:shared'],
},
{
sourceTag: 'scope:create-app',
onlyDependOnLibsWithTags: ['scope:create-app', 'scope:shared'],
},
{
sourceTag: 'scope:shared',
onlyDependOnLibsWithTags: ['scope:shared'],
},
{
sourceTag: 'scope:backend',
onlyDependOnLibsWithTags: ['scope:shared', 'scope:backend'],
},
{
sourceTag: 'scope:frontend',
onlyDependOnLibsWithTags: ['scope:shared', 'scope:frontend'],
},
{
sourceTag: 'scope:zapier',
onlyDependOnLibsWithTags: ['scope:shared', 'scope:zapier'],
},
],
},
],
// Import rules
'import/no-relative-packages': 'error',
'import/no-useless-path-segments': 'error',
'import/no-duplicates': ['error', { considerQueryString: true }],
// Prefer arrow functions
'prefer-arrow/prefer-arrow-functions': [
'error',
{
disallowPrototype: true,
singleReturnOnly: false,
classPropertiesAllowed: false,
},
],
// Unused imports
'unused-imports/no-unused-imports': 'warn',
'unused-imports/no-unused-vars': [
'warn',
{
vars: 'all',
varsIgnorePattern: '^_',
args: 'after-used',
argsIgnorePattern: '^_',
},
],
},
},
// TypeScript specific configuration
{
files: ['**/*.{ts,tsx}'],
languageOptions: {
parser: typescriptParser,
parserOptions: {
ecmaFeatures: {
jsx: true,
},
},
},
plugins: {
'@typescript-eslint': typescriptEslint,
},
rules: {
// TypeScript rules
'no-redeclare': 'off', // Turn off base rule for TypeScript
'@typescript-eslint/no-redeclare': 'error', // Use TypeScript-aware version
'@typescript-eslint/ban-ts-comment': 'error',
'@typescript-eslint/consistent-type-imports': [
'error',
{
prefer: 'type-imports',
fixStyle: 'inline-type-imports',
},
],
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/interface-name-prefix': 'off',
'@typescript-eslint/no-empty-object-type': [
'error',
{
allowInterfaces: 'with-single-extends',
},
],
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-empty-function': 'off',
'@typescript-eslint/no-unused-vars': 'off',
},
},
// JavaScript specific configuration
{
files: ['*.{js,jsx}'],
rules: {
// JavaScript-specific rules if needed
},
},
// Test files
{
files: [
'*.spec.@(ts|tsx|js|jsx)',
'*.integration-spec.@(ts|tsx|js|jsx)',
'*.test.@(ts|tsx|js|jsx)',
],
languageOptions: {
globals: {
jest: true,
describe: true,
it: true,
expect: true,
beforeEach: true,
afterEach: true,
beforeAll: true,
afterAll: true,
},
},
rules: {
'@typescript-eslint/no-non-null-assertion': 'off',
},
},
// JSON files
{
files: ['**/*.json'],
languageOptions: {
parser: jsoncParser,
},
},
// MDX files
{
...mdxPlugin.flat,
plugins: {
...mdxPlugin.flat.plugins,
'@nx': nxPlugin,
twenty: { rules: twentyRules },
},
},
mdxPlugin.flatCodeBlocks,
{
files: ['**/*.mdx'],
rules: {
'no-unused-vars': 'off',
'unused-imports/no-unused-imports': 'off',
'unused-imports/no-unused-vars': 'off',
// Enforce JSX tags on separate lines to prevent Crowdin translation issues
'twenty/mdx-component-newlines': 'error',
// Disallow angle bracket placeholders to prevent Crowdin translation errors
'twenty/no-angle-bracket-placeholders': 'error',
},
},
];
+17 -23
View File
@@ -40,34 +40,30 @@
"dependsOn": ["^build"]
},
"lint": {
"executor": "@nx/eslint:lint",
"executor": "nx:run-commands",
"cache": true,
"outputs": ["{options.outputFile}"],
"options": {
"eslintConfig": "{projectRoot}/eslint.config.mjs",
"cache": true,
"cacheLocation": "{workspaceRoot}/.cache/eslint"
"cwd": "{projectRoot}",
"command": "npx oxlint -c .oxlintrc.json . && (prettier . --check --cache --cache-location ../../.cache/prettier/{projectRoot} --cache-strategy metadata || (echo 'ERROR: Prettier formatting check failed! Fix with: npx nx lint --configuration=fix' && false))"
},
"configurations": {
"ci": {
"cacheStrategy": "content"
},
"ci": {},
"fix": {
"fix": true
"command": "npx oxlint --fix -c .oxlintrc.json . && prettier . --write --cache --cache-location ../../.cache/prettier/{projectRoot} --cache-strategy metadata"
}
},
"dependsOn": ["^build"]
"dependsOn": ["^build", "twenty-oxlint-rules:build"]
},
"lint:diff-with-main": {
"executor": "nx:run-commands",
"cache": false,
"options": {
"command": "git diff --name-only --diff-filter=d main | grep -E '{args.pattern}' | grep '^{projectRoot}/' | xargs sh -c 'if [ $# -gt 0 ]; then npx eslint --config {projectRoot}/eslint.config.mjs \"$@\"; fi' _",
"command": "FILES=$(git diff --name-only --diff-filter=d main -- {projectRoot}/ | grep -E '{args.pattern}'); [ -z \"$FILES\" ] && echo 'No changed files.' || (npx oxlint -c {projectRoot}/.oxlintrc.json $FILES && (prettier --check $FILES || (echo 'ERROR: Prettier formatting check failed! Fix with: npx nx lint:diff-with-main --configuration=fix' && false)))",
"pattern": "\\.(ts|tsx|js|jsx)$"
},
"configurations": {
"fix": {
"command": "git diff --name-only --diff-filter=d main | grep -E '{args.pattern}' | grep '^{projectRoot}/' | xargs sh -c 'if [ $# -gt 0 ]; then npx eslint --config {projectRoot}/eslint.config.mjs --fix \"$@\"; fi' _"
"command": "FILES=$(git diff --name-only --diff-filter=d main -- {projectRoot}/ | grep -E '{args.pattern}'); [ -z \"$FILES\" ] && echo 'No changed files.' || (npx oxlint --fix -c {projectRoot}/.oxlintrc.json $FILES && prettier --write $FILES)"
}
}
},
@@ -140,6 +136,14 @@
"cache": true,
"dependsOn": ["^build"]
},
"set-local-version": {
"executor": "nx:run-commands",
"cache": false,
"options": {
"cwd": "{projectRoot}",
"command": "npm pkg set version={args.releaseVersion}"
}
},
"storybook:build": {
"executor": "nx:run-commands",
"cache": true,
@@ -147,7 +151,7 @@
"outputs": ["{projectRoot}/{options.output-dir}"],
"options": {
"cwd": "{projectRoot}",
"command": "NODE_OPTIONS='--max-old-space-size=10240' VITE_DISABLE_TYPESCRIPT_CHECKER=true storybook build --test",
"command": "NODE_OPTIONS='--max-old-space-size=10240' storybook build --test",
"output-dir": "storybook-static",
"config-dir": ".storybook"
},
@@ -255,14 +259,6 @@
}
}
},
"@nx/eslint:lint": {
"cache": true,
"inputs": [
"default",
"{workspaceRoot}/eslint.config.mjs",
"{workspaceRoot}/packages/twenty-eslint-rules/**/*"
]
},
"@nx/vite:build": {
"cache": true,
"dependsOn": ["^build"],
@@ -277,7 +273,6 @@
"@nx/react": {
"application": {
"style": "@linaria/react",
"linter": "eslint",
"bundler": "vite",
"compiler": "swc",
"unitTestRunner": "jest",
@@ -285,7 +280,6 @@
},
"library": {
"style": "@linaria/react",
"linter": "eslint",
"bundler": "vite",
"compiler": "swc",
"unitTestRunner": "jest",
+15 -35
View File
@@ -46,7 +46,7 @@
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-responsive": "^9.0.2",
"react-router-dom": "^6.4.4",
"react-router-dom": "^6.30.3",
"react-tooltip": "^5.13.1",
"remark-gfm": "^4.0.1",
"rxjs": "^7.2.0",
@@ -72,14 +72,13 @@
"@graphql-codegen/typescript": "^3.0.4",
"@graphql-codegen/typescript-operations": "^3.0.4",
"@graphql-codegen/typescript-react-apollo": "^3.3.7",
"@nx/eslint": "22.3.3",
"@nx/eslint-plugin": "22.3.3",
"@nx/jest": "22.3.3",
"@nx/js": "22.3.3",
"@nx/react": "22.3.3",
"@nx/storybook": "22.3.3",
"@nx/vite": "22.3.3",
"@nx/web": "22.3.3",
"@nx/jest": "22.5.4",
"@nx/js": "22.5.4",
"@nx/react": "22.5.4",
"@nx/storybook": "22.5.4",
"@nx/vite": "22.5.4",
"@nx/web": "22.5.4",
"@oxlint/plugins": "^1.51.0",
"@sentry/types": "^8",
"@storybook-community/storybook-addon-cookie": "^5.0.0",
"@storybook/addon-coverage": "^3.0.0",
@@ -89,11 +88,10 @@
"@storybook/icons": "^2.0.1",
"@storybook/react-vite": "^10.2.13",
"@storybook/test-runner": "^0.24.2",
"@stylistic/eslint-plugin": "^1.5.0",
"@swc-node/register": "1.11.1",
"@swc/cli": "^0.3.12",
"@swc/core": "1.15.11",
"@swc/helpers": "~0.5.18",
"@swc-node/register": "^1.11.1",
"@swc/cli": "^0.7.10",
"@swc/core": "^1.15.11",
"@swc/helpers": "~0.5.19",
"@swc/jest": "^0.2.39",
"@testing-library/dom": "^10.4.0",
"@testing-library/jest-dom": "^6.6.3",
@@ -132,9 +130,6 @@
"@types/react-dom": "^18.2.15",
"@types/supertest": "^2.0.11",
"@types/uuid": "^9.0.2",
"@typescript-eslint/eslint-plugin": "^8.39.0",
"@typescript-eslint/parser": "^8.39.0",
"@typescript-eslint/utils": "^8.39.0",
"@typescript/native-preview": "^7.0.0-dev.20260116.1",
"@vitejs/plugin-react-swc": "4.2.3",
"@vitest/browser-playwright": "^4.0.18",
@@ -146,22 +141,6 @@
"danger": "^13.0.4",
"dotenv-cli": "^7.4.4",
"esbuild": "^0.25.10",
"eslint": "^9.32.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-import": "^2.31.0",
"eslint-plugin-jsx-a11y": "^6.10.2",
"eslint-plugin-lingui": "^0.9.0",
"eslint-plugin-mdx": "^3.6.2",
"eslint-plugin-prefer-arrow": "^1.2.3",
"eslint-plugin-prettier": "^5.1.2",
"eslint-plugin-project-structure": "^3.9.1",
"eslint-plugin-react": "^7.37.2",
"eslint-plugin-react-hooks": "^5.0.0",
"eslint-plugin-react-refresh": "^0.4.4",
"eslint-plugin-simple-import-sort": "^10.0.0",
"eslint-plugin-storybook": "^10.2.13",
"eslint-plugin-unicorn": "^56.0.1",
"eslint-plugin-unused-imports": "^3.0.0",
"http-server": "^14.1.1",
"jest": "29.7.0",
"jest-environment-jsdom": "30.0.0-beta.3",
@@ -170,7 +149,7 @@
"jsdom": "~22.1.0",
"msw": "^2.12.7",
"msw-storybook-addon": "^2.0.6",
"nx": "22.3.3",
"nx": "22.5.4",
"prettier": "^3.1.1",
"raw-loader": "^4.0.2",
"rimraf": "^5.0.5",
@@ -185,6 +164,7 @@
"tsc-alias": "^1.8.16",
"tsconfig-paths": "^4.2.0",
"tsx": "^4.17.0",
"verdaccio": "^6.3.1",
"vite": "^7.0.0",
"vitest": "^4.0.18"
},
@@ -230,7 +210,7 @@
"packages/twenty-apps",
"packages/twenty-cli",
"packages/create-twenty-app",
"packages/twenty-eslint-rules"
"packages/twenty-oxlint-rules"
]
},
"prettier": {
+47
View File
@@ -0,0 +1,47 @@
{
"$schema": "./node_modules/oxlint/configuration_schema.json",
"plugins": ["typescript", "import", "unicorn"],
"categories": {
"correctness": "off"
},
"ignorePatterns": ["node_modules", "dist"],
"rules": {
"func-style": ["error", "declaration", { "allowArrowFunctions": true }],
"no-console": "off",
"no-control-regex": "off",
"no-debugger": "error",
"no-duplicate-imports": "error",
"no-undef": "off",
"no-unused-vars": "off",
"no-redeclare": "off",
"import/no-duplicates": "error",
"typescript/no-redeclare": "error",
"typescript/ban-ts-comment": "error",
"typescript/consistent-type-imports": [
"error",
{
"prefer": "type-imports",
"fixStyle": "inline-type-imports"
}
],
"typescript/explicit-function-return-type": "off",
"typescript/explicit-module-boundary-types": "off",
"typescript/no-empty-object-type": [
"error",
{
"allowInterfaces": "with-single-extends"
}
],
"typescript/no-empty-function": "off",
"typescript/no-explicit-any": "off",
"typescript/no-unused-vars": [
"warn",
{
"vars": "all",
"varsIgnorePattern": "^_",
"args": "after-used",
"argsIgnorePattern": "^_"
}
]
}
}
@@ -0,0 +1 @@
dist
+15 -22
View File
@@ -19,9 +19,11 @@ Create Twenty App is the official scaffolding CLI for building apps on top of [T
- Strong TypeScript support and typed client generation
## Documentation
See Twenty application documentation https://docs.twenty.com/developers/extend/capabilities/apps
## Prerequisites
- Node.js 24+ (recommended) and Yarn 4
- A Twenty workspace and an API key (create one at https://app.twenty.com/settings/api-webhooks)
@@ -41,7 +43,7 @@ yarn twenty auth:login
yarn twenty entity:add
# Start dev mode: watches, builds, and syncs local changes to your workspace
# (also auto-generates typed API clients — CoreApiClient and MetadataApiClient — in node_modules/twenty-sdk/generated)
# (also auto-generates typed CoreApiClient MetadataApiClient ships pre-built with the SDK — both available via `twenty-sdk/clients`)
yarn twenty app:dev
# Watch your application's function logs
@@ -64,11 +66,10 @@ yarn twenty app:uninstall
Control which example files are included when creating a new app:
| Flag | Behavior |
|------|----------|
| `-e, --exhaustive` | **(default)** Creates all example files without prompting |
| `-m, --minimal` | Creates only core files (`application-config.ts` and `default-role.ts`) |
| `-i, --interactive` | Prompts you to select which examples to include |
| Flag | Behavior |
| ------------------ | ----------------------------------------------------------------------- |
| `-e, --exhaustive` | **(default)** Creates all example files |
| `-m, --minimal` | Creates only core files (`application-config.ts` and `default-role.ts`) |
```bash
# Default: all examples included
@@ -76,32 +77,21 @@ npx create-twenty-app@latest my-app
# Minimal: only core files
npx create-twenty-app@latest my-app -m
# Interactive: choose which examples to include
npx create-twenty-app@latest my-app -i
```
In interactive mode, you can pick from:
- **Example object** — a custom CRM object definition (`objects/example-object.ts`)
- **Example field** — a custom field on the example object (`fields/example-field.ts`)
- **Example logic function** — a server-side handler with HTTP trigger (`logic-functions/hello-world.ts`)
- **Example front component** — a React UI component (`front-components/hello-world.tsx`)
- **Example view** — a saved view for the example object (`views/example-view.ts`)
- **Example navigation menu item** — a sidebar link (`navigation-menu-items/example-navigation-menu-item.ts`)
- **Example skill** — an AI agent skill definition (`skills/example-skill.ts`)
- **Integration test** — a vitest integration test verifying app installation (`__tests__/app-install.integration-test.ts`)
## What gets scaffolded
**Core files (always created):**
- `application-config.ts` — Application metadata configuration
- `roles/default-role.ts` — Default role for logic functions
- `logic-functions/pre-install.ts` — Pre-install logic function (runs before app installation)
- `logic-functions/post-install.ts` — Post-install logic function (runs after app installation)
- TypeScript configuration, ESLint, package.json, .gitignore
- TypeScript configuration, Oxlint, package.json, .gitignore
- A prewired `twenty` script that delegates to the `twenty` CLI from twenty-sdk
**Example files (controlled by scaffolding mode):**
- `objects/example-object.ts` — Example custom object with a text field
- `fields/example-field.ts` — Example standalone field extending the example object
- `logic-functions/hello-world.ts` — Example logic function with HTTP trigger
@@ -112,14 +102,15 @@ In interactive mode, you can pick from:
- `__tests__/app-install.integration-test.ts` — Integration test that builds, installs, and verifies the app (includes `vitest.config.ts`, `tsconfig.spec.json`, and a setup file)
## Next steps
- Run `yarn twenty help` to see all available commands.
- Use `yarn twenty auth:login` to authenticate with your Twenty workspace.
- Explore the generated project and add your first entity with `yarn twenty entity:add` (logic functions, front components, objects, roles, views, navigation menu items, skills).
- Use `yarn twenty app:dev` while you iterate — it watches, builds, and syncs changes to your workspace in real time.
- Two typed API clients are autogenerated by `yarn twenty app:dev` and stored in `node_modules/twenty-sdk/generated`: `CoreApiClient` (for workspace data via `/graphql`) and `MetadataApiClient` (for workspace configuration and file uploads via `/metadata`).
- `CoreApiClient` (for workspace data via `/graphql`) is auto-generated by `yarn twenty app:dev`. `MetadataApiClient` (for workspace configuration and file uploads via `/metadata`) ships pre-built with the SDK. Both are available via `import { CoreApiClient, MetadataApiClient } from 'twenty-sdk/clients'`.
## Publish your application
Applications are currently stored in `twenty/packages/twenty-apps`.
You can share your application with all Twenty users:
@@ -144,9 +135,11 @@ git push
Our team reviews contributions for quality, security, and reusability before merging.
## Troubleshooting
- Auth prompts not appearing: run `yarn twenty auth:login` again and verify the API key permissions.
- Types not generated: ensure `yarn twenty app:dev` is running — it autogenerates the typed client.
## Contributing
- See our [GitHub](https://github.com/twentyhq/twenty)
- Join our [Discord](https://discord.gg/cx5n4Jzs57)
@@ -1,20 +0,0 @@
import baseConfig from '../../eslint.config.mjs';
export default [
...baseConfig,
{
ignores: ['**/dist/**'],
},
{
files: ['**/*.{js,jsx,ts,tsx}'],
rules: {
'prettier/prettier': 'error',
},
},
{
rules: {
'no-console': 'off',
},
ignores: ['src/**/*.ts', '!src/cli/**/*.ts'],
},
];
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "create-twenty-app",
"version": "0.6.3",
"version": "0.7.0-canary.0",
"description": "Command-line interface to create Twenty application",
"main": "dist/cli.cjs",
"bin": "dist/cli.cjs",
+2 -13
View File
@@ -24,20 +24,9 @@
"command": "node dist/cli.cjs"
}
},
"set-local-version": {},
"typecheck": {},
"lint": {
"options": {
"lintFilePatterns": ["{projectRoot}/src/**/*.{ts,json}"],
"maxWarnings": 0
},
"configurations": {
"ci": {
"lintFilePatterns": ["{projectRoot}/src/**/*.{ts,json}"],
"maxWarnings": 0
},
"fix": {}
}
},
"lint": {},
"test": {
"executor": "@nx/jest:jest",
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
+25 -15
View File
@@ -18,9 +18,14 @@ const program = new Command(packageJson.name)
'-m, --minimal',
'Create only core entities (application-config and default-role)',
)
.option('-n, --name <name>', 'Application name (skips prompt)')
.option(
'-i, --interactive',
'Interactively choose which entity examples to include',
'-d, --display-name <displayName>',
'Application display name (skips prompt)',
)
.option(
'--description <description>',
'Application description (skips prompt)',
)
.helpOption('-h, --help', 'Display this help message.')
.action(
@@ -29,19 +34,17 @@ const program = new Command(packageJson.name)
options?: {
exhaustive?: boolean;
minimal?: boolean;
interactive?: boolean;
name?: string;
displayName?: string;
description?: string;
},
) => {
const modeFlags = [
options?.exhaustive,
options?.minimal,
options?.interactive,
].filter(Boolean);
const modeFlags = [options?.exhaustive, options?.minimal].filter(Boolean);
if (modeFlags.length > 1) {
console.error(
chalk.red(
'Error: --exhaustive, --minimal, and --interactive are mutually exclusive.',
'Error: --exhaustive and --minimal are mutually exclusive.',
),
);
process.exit(1);
@@ -56,13 +59,20 @@ const program = new Command(packageJson.name)
process.exit(1);
}
const mode: ScaffoldingMode = options?.minimal
? 'minimal'
: options?.interactive
? 'interactive'
: 'exhaustive';
if (options?.name !== undefined && options.name.trim().length === 0) {
console.error(chalk.red('Error: --name cannot be empty.'));
process.exit(1);
}
await new CreateAppCommand().execute(directory, mode);
const mode: ScaffoldingMode = options?.minimal ? 'minimal' : 'exhaustive';
await new CreateAppCommand().execute({
directory,
mode,
name: options?.name,
displayName: options?.displayName,
description: options?.description,
});
},
);
@@ -0,0 +1,19 @@
{
"$schema": "./node_modules/oxlint/configuration_schema.json",
"plugins": ["typescript"],
"categories": {
"correctness": "off"
},
"ignorePatterns": ["node_modules", "dist"],
"rules": {
"no-unused-vars": "off",
"typescript/no-unused-vars": [
"warn",
{
"argsIgnorePattern": "^_"
}
],
"typescript/no-explicit-any": "off"
}
}
@@ -1,9 +1,10 @@
## Base documentation
- Documentation: https://docs.twenty.com/developers/extend/capabilities/apps
- Rich app example: https://github.com/twentyhq/twenty/tree/main/packages/twenty-sdk/src/cli/__tests__/apps/rich-app
- Rich app example: https://github.com/twentyhq/twenty/tree/main/packages/twenty-sdk/src/app-seeds/rich-app
## UUID requirement
- All generated UUIDs must be valid UUID v4.
## Common Pitfalls
@@ -1,29 +0,0 @@
import js from '@eslint/js';
import tseslint from 'typescript-eslint';
export default [
// Base JS recommended rules
js.configs.recommended,
// TypeScript recommended rules
...tseslint.configs.recommended,
{
files: ['**/*.ts', '**/*.tsx'],
languageOptions: {
parserOptions: {
project: true,
tsconfigRootDir: import.meta.dirname,
},
},
rules: {
// Common TypeScript-friendly tweaks
'@typescript-eslint/no-unused-vars': [
'warn',
{ argsIgnorePattern: '^_' },
],
'@typescript-eslint/no-explicit-any': 'off',
'no-unused-vars': 'off', // handled by TS rule
},
},
];
@@ -27,5 +27,11 @@
"~/*": ["./*"]
}
},
"exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts", "**/*.integration-test.ts"]
"exclude": [
"node_modules",
"dist",
"**/*.test.ts",
"**/*.spec.ts",
"**/*.integration-test.ts"
]
}
@@ -7,6 +7,7 @@ import * as fs from 'fs-extra';
import inquirer from 'inquirer';
import kebabCase from 'lodash.kebabcase';
import * as path from 'path';
import { isDefined } from 'twenty-shared/utils';
import {
type ExampleOptions,
@@ -15,16 +16,23 @@ import {
const CURRENT_EXECUTION_DIRECTORY = process.env.INIT_CWD || process.cwd();
type CreateAppOptions = {
directory?: string;
mode?: ScaffoldingMode;
name?: string;
displayName?: string;
description?: string;
};
export class CreateAppCommand {
async execute(
directory?: string,
mode: ScaffoldingMode = 'exhaustive',
): Promise<void> {
async execute(options: CreateAppOptions = {}): Promise<void> {
try {
const { appName, appDisplayName, appDirectory, appDescription } =
await this.getAppInfos(directory);
await this.getAppInfos(options);
const exampleOptions = await this.resolveExampleOptions(mode);
const exampleOptions = this.resolveExampleOptions(
options.mode ?? 'exhaustive',
);
await this.validateDirectory(appDirectory);
@@ -54,19 +62,25 @@ export class CreateAppCommand {
}
}
private async getAppInfos(directory?: string): Promise<{
private async getAppInfos(options: CreateAppOptions): Promise<{
appName: string;
appDisplayName: string;
appDescription: string;
appDirectory: string;
}> {
const { directory } = options;
const hasName = isDefined(options.name) || isDefined(directory);
const hasDisplayName = isDefined(options.displayName);
const hasDescription = isDefined(options.description);
const { name, displayName, description } = await inquirer.prompt([
{
type: 'input',
name: 'name',
message: 'Application name:',
when: () => !directory,
default: 'my-awesome-app',
when: () => !hasName,
default: 'my-twenty-app',
validate: (input) => {
if (input.length === 0) return 'Application name is required';
return true;
@@ -76,25 +90,33 @@ export class CreateAppCommand {
type: 'input',
name: 'displayName',
message: 'Application display name:',
default: (answers: any) => {
return convertToLabel(answers?.name ?? directory);
when: () => !hasDisplayName,
default: (answers: { name?: string }) => {
return convertToLabel(
answers?.name ?? options.name ?? directory ?? '',
);
},
},
{
type: 'input',
name: 'description',
message: 'Application description (optional):',
when: () => !hasDescription,
default: '',
},
]);
const computedName = name ?? directory;
const appName = (
options.name ??
name ??
directory ??
'my-twenty-app'
).trim();
const appName = computedName.trim();
const appDisplayName =
(options.displayName ?? displayName)?.trim() || convertToLabel(appName);
const appDisplayName = displayName.trim();
const appDescription = description.trim();
const appDescription = (options.description ?? description ?? '').trim();
const appDirectory = directory
? path.join(CURRENT_EXECUTION_DIRECTORY, directory)
@@ -103,9 +125,7 @@ export class CreateAppCommand {
return { appName, appDisplayName, appDirectory, appDescription };
}
private async resolveExampleOptions(
mode: ScaffoldingMode,
): Promise<ExampleOptions> {
private resolveExampleOptions(mode: ScaffoldingMode): ExampleOptions {
if (mode === 'minimal') {
return {
includeExampleObject: false,
@@ -115,98 +135,21 @@ export class CreateAppCommand {
includeExampleView: false,
includeExampleNavigationMenuItem: false,
includeExampleSkill: false,
includeExampleAgent: false,
includeExampleIntegrationTest: false,
};
}
if (mode === 'exhaustive') {
return {
includeExampleObject: true,
includeExampleField: true,
includeExampleLogicFunction: true,
includeExampleFrontComponent: true,
includeExampleView: true,
includeExampleNavigationMenuItem: true,
includeExampleSkill: true,
includeExampleIntegrationTest: true,
};
}
const { selectedExamples } = await inquirer.prompt([
{
type: 'checkbox',
name: 'selectedExamples',
message: 'Select which example files to include:',
choices: [
{
name: 'Example object (custom object definition)',
value: 'object',
checked: true,
},
{
name: 'Example field (custom field on the example object)',
value: 'field',
checked: true,
},
{
name: 'Example logic function (server-side handler)',
value: 'logicFunction',
checked: true,
},
{
name: 'Example front component (React UI component)',
value: 'frontComponent',
checked: true,
},
{
name: 'Example view (saved view for the example object)',
value: 'view',
checked: true,
},
{
name: 'Example navigation menu item (sidebar link)',
value: 'navigationMenuItem',
checked: true,
},
{
name: 'Example skill (AI agent skill definition)',
value: 'skill',
checked: true,
},
{
name: 'Integration test (vitest test verifying app installation)',
value: 'integrationTest',
checked: true,
},
],
},
]);
const includeField = selectedExamples.includes('field');
const includeView = selectedExamples.includes('view');
const includeExampleIntegrationTest =
selectedExamples.includes('integrationTest');
const includeObject =
selectedExamples.includes('object') || includeField || includeView;
if ((includeField || includeView) && !selectedExamples.includes('object')) {
console.log(
chalk.yellow(
'Note: Example object auto-included because example field/view depends on it.',
),
);
}
return {
includeExampleObject: includeObject,
includeExampleField: includeField,
includeExampleLogicFunction: selectedExamples.includes('logicFunction'),
includeExampleFrontComponent: selectedExamples.includes('frontComponent'),
includeExampleView: includeView,
includeExampleNavigationMenuItem:
selectedExamples.includes('navigationMenuItem'),
includeExampleSkill: selectedExamples.includes('skill'),
includeExampleIntegrationTest,
includeExampleObject: true,
includeExampleField: true,
includeExampleLogicFunction: true,
includeExampleFrontComponent: true,
includeExampleView: true,
includeExampleNavigationMenuItem: true,
includeExampleSkill: true,
includeExampleIntegrationTest: true,
includeExampleAgent: true,
};
}
@@ -1,4 +1,4 @@
export type ScaffoldingMode = 'exhaustive' | 'minimal' | 'interactive';
export type ScaffoldingMode = 'exhaustive' | 'minimal';
export type ExampleOptions = {
includeExampleObject: boolean;
@@ -8,5 +8,6 @@ export type ExampleOptions = {
includeExampleView: boolean;
includeExampleNavigationMenuItem: boolean;
includeExampleSkill: boolean;
includeExampleAgent: boolean;
includeExampleIntegrationTest: boolean;
};
@@ -25,6 +25,7 @@ const ALL_EXAMPLES: ExampleOptions = {
includeExampleView: true,
includeExampleNavigationMenuItem: true,
includeExampleSkill: true,
includeExampleAgent: true,
includeExampleIntegrationTest: true,
};
@@ -41,6 +42,7 @@ const NO_EXAMPLES: ExampleOptions = {
includeExampleObject: false,
includeExampleField: false,
includeExampleSkill: false,
includeExampleAgent: false,
includeExampleLogicFunction: false,
includeExampleFrontComponent: false,
includeExampleView: false,
@@ -384,6 +386,12 @@ describe('copyBaseApplicationProject', () => {
),
).toBe(true);
expect(
await fs.pathExists(
join(testAppDirectory, '.github', 'workflows', 'ci.yml'),
),
).toBe(true);
// Install functions should always exist
expect(
await fs.pathExists(
@@ -462,6 +470,12 @@ describe('copyBaseApplicationProject', () => {
join(srcPath, '__tests__', 'app-install.integration-test.ts'),
),
).toBe(false);
expect(
await fs.pathExists(
join(testAppDirectory, '.github', 'workflows', 'ci.yml'),
),
).toBe(false);
});
});
@@ -476,6 +490,7 @@ describe('copyBaseApplicationProject', () => {
includeExampleObject: false,
includeExampleField: false,
includeExampleSkill: false,
includeExampleAgent: false,
includeExampleLogicFunction: false,
includeExampleFrontComponent: true,
includeExampleView: false,
@@ -513,6 +528,7 @@ describe('copyBaseApplicationProject', () => {
exampleOptions: {
includeExampleObject: false,
includeExampleSkill: false,
includeExampleAgent: false,
includeExampleField: false,
includeExampleLogicFunction: true,
includeExampleFrontComponent: false,
@@ -673,15 +689,24 @@ describe('copyBaseApplicationProject', () => {
const content = await fs.readFile(viewPath, 'utf8');
expect(content).toContain("import { defineView } from 'twenty-sdk'");
expect(content).toContain(
"import { EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER } from 'src/objects/example-object'",
"import { defineView, ViewKey } from 'twenty-sdk'",
);
expect(content).toContain(
"import { EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER, NAME_FIELD_UNIVERSAL_IDENTIFIER } from 'src/objects/example-object'",
);
expect(content).toContain('export default defineView({');
expect(content).toContain(
'objectUniversalIdentifier: EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER',
);
expect(content).toContain("name: 'example-view'");
expect(content).toContain("name: 'All example items'");
expect(content).toContain('fields: [');
expect(content).toContain(
'fieldMetadataUniversalIdentifier: NAME_FIELD_UNIVERSAL_IDENTIFIER',
);
expect(content).toContain('isVisible: true');
expect(content).toContain('key: ViewKey.INDEX');
expect(content).toContain('size: 200');
});
});
@@ -712,6 +737,7 @@ describe('copyBaseApplicationProject', () => {
expect(content).toContain('export default defineNavigationMenuItem({');
expect(content).toContain("name: 'example-navigation-menu-item'");
expect(content).toContain("icon: 'IconList'");
expect(content).toContain("color: 'blue'");
expect(content).toContain('position: 0');
});
});
@@ -50,15 +50,14 @@ describe('scaffoldIntegrationTest', () => {
"import { appBuild, appUninstall } from 'twenty-sdk/cli'",
);
expect(content).toContain(
"import { MetadataApiClient } from 'twenty-sdk/generated'",
"import { MetadataApiClient } from 'twenty-sdk/clients'",
);
expect(content).toContain(
"import { APPLICATION_UNIVERSAL_IDENTIFIER } from 'src/application-config'",
);
expect(content).toContain('TWENTY_TEST_API_KEY');
expect(content).toContain('assertServerIsReachable');
expect(content).toContain('appBuild');
expect(content).toContain('appUninstall');
expect(content).toContain('new MetadataApiClient()');
expect(content).toContain('findManyApplications');
expect(content).toContain('APPLICATION_UNIVERSAL_IDENTIFIER');
});
@@ -84,7 +83,8 @@ describe('scaffoldIntegrationTest', () => {
expect(content).toContain('.twenty-sdk-test');
expect(content).toContain('config.json');
expect(content).toContain('process.env.TWENTY_API_URL');
expect(content).toContain('process.env.TWENTY_TEST_API_KEY');
expect(content).toContain('process.env.TWENTY_API_KEY');
expect(content).toContain('assertServerIsReachable');
});
});
@@ -101,7 +101,8 @@ describe('scaffoldIntegrationTest', () => {
const content = await fs.readFile(vitestConfigPath, 'utf8');
expect(content).toContain('TWENTY_TEST_API_KEY');
expect(content).toContain('TWENTY_API_KEY');
expect(content).not.toContain('TWENTY_TEST_API_KEY');
expect(content).toContain('TWENTY_API_URL');
expect(content).toContain('setup-test.ts');
expect(content).toContain('tsconfig.spec.json');
@@ -109,6 +110,37 @@ describe('scaffoldIntegrationTest', () => {
});
});
describe('github workflow', () => {
it('should create .github/workflows/ci.yml with correct structure', async () => {
await scaffoldIntegrationTest({
appDirectory: testAppDirectory,
sourceFolderPath,
});
const workflowPath = join(
testAppDirectory,
'.github',
'workflows',
'ci.yml',
);
expect(await fs.pathExists(workflowPath)).toBe(true);
const content = await fs.readFile(workflowPath, 'utf8');
expect(content).toContain('name: CI');
expect(content).toContain('TWENTY_VERSION: latest');
expect(content).toContain('twenty-version: ${{ env.TWENTY_VERSION }}');
expect(content).toContain('actions/checkout@v4');
expect(content).toContain('spawn-twenty-docker-image@main');
expect(content).toContain('actions/setup-node@v4');
expect(content).toContain('yarn install --immutable');
expect(content).toContain('yarn test');
expect(content).toContain('TWENTY_API_URL');
expect(content).toContain('TWENTY_TEST_API_KEY');
});
});
describe('tsconfig.spec.json', () => {
it('should create tsconfig.spec.json extending the base tsconfig', async () => {
await scaffoldIntegrationTest({
@@ -30,12 +30,12 @@ export const copyBaseApplicationProject = async ({
includeExampleIntegrationTest: exampleOptions.includeExampleIntegrationTest,
});
await createYarnLock(appDirectory);
await createGitignore(appDirectory);
await createPublicAssetDirectory(appDirectory);
await createYarnLock(appDirectory);
const sourceFolderPath = join(appDirectory, SRC_FOLDER);
await fs.ensureDir(sourceFolderPath);
@@ -103,6 +103,14 @@ export const copyBaseApplicationProject = async ({
});
}
if (exampleOptions.includeExampleAgent) {
await createExampleAgent({
appDirectory: sourceFolderPath,
fileFolder: 'agents',
fileName: 'example-agent.ts',
});
}
if (exampleOptions.includeExampleIntegrationTest) {
await scaffoldIntegrationTest({
appDirectory,
@@ -134,13 +142,6 @@ const createPublicAssetDirectory = async (appDirectory: string) => {
await fs.ensureDir(join(appDirectory, ASSETS_DIR));
};
const createYarnLock = async (appDirectory: string) => {
const yarnLockContent = `# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
`;
await fs.writeFile(join(appDirectory, 'yarn.lock'), yarnLockContent);
};
const createGitignore = async (appDirectory: string) => {
const gitignoreContent = `# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
@@ -431,16 +432,29 @@ const createExampleView = async ({
fileName: string;
}) => {
const universalIdentifier = v4();
const viewFieldUniversalIdentifier = v4();
const content = `import { defineView } from 'twenty-sdk';
import { EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER } from 'src/objects/example-object';
const content = `import { defineView, ViewKey } from 'twenty-sdk';
import { EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER, NAME_FIELD_UNIVERSAL_IDENTIFIER } from 'src/objects/example-object';
export const EXAMPLE_VIEW_UNIVERSAL_IDENTIFIER = '${universalIdentifier}';
export default defineView({
universalIdentifier: '${universalIdentifier}',
name: 'example-view',
universalIdentifier: EXAMPLE_VIEW_UNIVERSAL_IDENTIFIER,
name: 'All example items',
objectUniversalIdentifier: EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER,
icon: 'IconList',
key: ViewKey.INDEX,
position: 0,
fields: [
{
universalIdentifier: '${viewFieldUniversalIdentifier}',
fieldMetadataUniversalIdentifier: NAME_FIELD_UNIVERSAL_IDENTIFIER,
position: 0,
isVisible: true,
size: 200,
},
],
});
`;
@@ -460,18 +474,15 @@ const createExampleNavigationMenuItem = async ({
const universalIdentifier = v4();
const content = `import { defineNavigationMenuItem } from 'twenty-sdk';
import { EXAMPLE_VIEW_UNIVERSAL_IDENTIFIER } from 'src/views/example-view';
export default defineNavigationMenuItem({
universalIdentifier: '${universalIdentifier}',
name: 'example-navigation-menu-item',
icon: 'IconList',
color: 'blue',
position: 0,
// Link to a view:
// viewUniversalIdentifier: '...',
// Or link to an object:
// targetObjectUniversalIdentifier: '...',
// Or link to an external URL:
// link: 'https://example.com',
viewUniversalIdentifier: EXAMPLE_VIEW_UNIVERSAL_IDENTIFIER,
});
`;
@@ -509,6 +520,36 @@ export default defineSkill({
await fs.writeFile(join(appDirectory, fileFolder ?? '', fileName), content);
};
const createExampleAgent = async ({
appDirectory,
fileFolder,
fileName,
}: {
appDirectory: string;
fileFolder?: string;
fileName: string;
}) => {
const universalIdentifier = v4();
const content = `import { defineAgent } from 'twenty-sdk';
export const EXAMPLE_AGENT_UNIVERSAL_IDENTIFIER =
'${universalIdentifier}';
export default defineAgent({
universalIdentifier: EXAMPLE_AGENT_UNIVERSAL_IDENTIFIER,
name: 'example-agent',
label: 'Example Agent',
description: 'A sample AI agent for your application',
icon: 'IconRobot',
prompt: 'You are a helpful assistant. Help users with their questions and tasks.',
});
`;
await fs.ensureDir(join(appDirectory, fileFolder ?? ''));
await fs.writeFile(join(appDirectory, fileFolder ?? '', fileName), content);
};
const createApplicationConfig = async ({
displayName,
description,
@@ -542,6 +583,14 @@ export default defineApplication({
await fs.writeFile(join(appDirectory, fileFolder ?? '', fileName), content);
};
const createYarnLock = async (appDirectory: string) => {
const yarnLockContent = `# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
`;
await fs.writeFile(join(appDirectory, 'yarn.lock'), yarnLockContent);
};
const createPackageJson = async ({
appName,
appDirectory,
@@ -553,8 +602,8 @@ const createPackageJson = async ({
}) => {
const scripts: Record<string, string> = {
twenty: 'twenty',
lint: 'eslint',
'lint:fix': 'eslint --fix',
lint: 'oxlint -c .oxlintrc.json .',
'lint:fix': 'oxlint --fix -c .oxlintrc.json .',
};
const devDependencies: Record<string, string> = {
@@ -562,8 +611,7 @@ const createPackageJson = async ({
'@types/node': '^24.7.2',
'@types/react': '^18.2.0',
react: '^18.2.0',
eslint: '^9.32.0',
'typescript-eslint': '^8.50.0',
oxlint: '^0.16.0',
'twenty-sdk': createTwentyAppPackageJson.version,
};
@@ -25,6 +25,7 @@ export const scaffoldIntegrationTest = async ({
await createVitestConfig(appDirectory);
await createTsconfigSpec(appDirectory);
await createGithubWorkflow(appDirectory);
};
const createVitestConfig = async (appDirectory: string) => {
@@ -45,7 +46,7 @@ export default defineConfig({
setupFiles: ['src/__tests__/setup-test.ts'],
env: {
TWENTY_API_URL: 'http://localhost:3000',
TWENTY_TEST_API_KEY:
TWENTY_API_KEY:
'${SEED_API_KEY}',
},
},
@@ -93,16 +94,36 @@ import * as os from 'os';
import * as path from 'path';
import { beforeAll } from 'vitest';
const TWENTY_API_URL = process.env.TWENTY_API_URL ?? 'http://localhost:3000';
const TEST_CONFIG_DIR = path.join(os.tmpdir(), '.twenty-sdk-test');
beforeAll(() => {
const assertServerIsReachable = async () => {
let response: Response;
try {
response = await fetch(\`\${TWENTY_API_URL}/healthz\`);
} catch {
throw new Error(
\`Twenty server is not reachable at \${TWENTY_API_URL}. \` +
'Make sure the server is running before executing integration tests.',
);
}
if (!response.ok) {
throw new Error(\`Server at \${TWENTY_API_URL} returned \${response.status}\`);
}
};
beforeAll(async () => {
await assertServerIsReachable();
fs.mkdirSync(TEST_CONFIG_DIR, { recursive: true });
const configFile = {
profiles: {
default: {
apiUrl: process.env.TWENTY_API_URL,
apiKey: process.env.TWENTY_TEST_API_KEY,
apiKey: process.env.TWENTY_API_KEY,
},
},
};
@@ -129,35 +150,15 @@ const createIntegrationTest = async ({
}) => {
const content = `import { APPLICATION_UNIVERSAL_IDENTIFIER } from 'src/application-config';
import { appBuild, appUninstall } from 'twenty-sdk/cli';
import { MetadataApiClient } from 'twenty-sdk/generated';
import { MetadataApiClient } from 'twenty-sdk/clients';
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
const APP_PATH = process.cwd();
const TWENTY_API_URL = process.env.TWENTY_API_URL ?? 'http://localhost:3000';
const assertServerIsReachable = async () => {
let response: Response;
try {
response = await fetch(\`\${TWENTY_API_URL}/healthz\`);
} catch {
throw new Error(
\`Twenty server is not reachable at \${TWENTY_API_URL}. \` +
'Make sure the server is running before executing integration tests.',
);
}
if (!response.ok) {
throw new Error(\`Server at \${TWENTY_API_URL} returned \${response.status}\`);
}
};
describe('App installation', () => {
let appInstalled = false;
beforeAll(async () => {
await assertServerIsReachable();
const buildResult = await appBuild({
appPath: APP_PATH,
onProgress: (message: string) => console.log(\`[build] \${message}\`),
@@ -165,7 +166,7 @@ describe('App installation', () => {
if (!buildResult.success) {
throw new Error(
\`App build failed: \${buildResult.error?.message ?? 'Unknown error'}\`,
\`Build failed: \${buildResult.error?.message ?? 'Unknown error'}\`,
);
}
@@ -187,20 +188,7 @@ describe('App installation', () => {
});
it('should find the installed app in the applications list', async () => {
const apiKey = process.env.TWENTY_TEST_API_KEY;
if (!apiKey) {
throw new Error(
'No API key found. Set TWENTY_TEST_API_KEY in your vitest config env.',
);
}
const metadataClient = new MetadataApiClient({
url: \`\${TWENTY_API_URL}/metadata\`,
headers: {
Authorization: \`Bearer \${apiKey}\`,
},
});
const metadataClient = new MetadataApiClient();
const result = await metadataClient.query({
findManyApplications: {
@@ -224,3 +212,56 @@ describe('App installation', () => {
await fs.ensureDir(join(appDirectory, fileFolder ?? ''));
await fs.writeFile(join(appDirectory, fileFolder ?? '', fileName), content);
};
const DEFAULT_TWENTY_VERSION = 'latest';
const createGithubWorkflow = async (appDirectory: string) => {
const content = `name: CI
on:
push:
branches:
- main
pull_request: {}
env:
TWENTY_VERSION: ${DEFAULT_TWENTY_VERSION}
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Spawn Twenty instance
id: twenty
uses: twentyhq/twenty/.github/actions/spawn-twenty-docker-image@main
with:
twenty-version: \${{ env.TWENTY_VERSION }}
github-token: \${{ secrets.GITHUB_TOKEN }}
- name: Enable Corepack
run: corepack enable
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'yarn'
- name: Install dependencies
run: yarn install --immutable
- name: Run integration tests
run: yarn test
env:
TWENTY_API_URL: \${{ steps.twenty.outputs.server-url }}
TWENTY_TEST_API_KEY: \${{ steps.twenty.outputs.access-token }}
`;
const workflowDir = join(appDirectory, '.github', 'workflows');
await fs.ensureDir(workflowDir);
await fs.writeFile(join(workflowDir, 'ci.yml'), content);
};
+1 -3
View File
@@ -24,7 +24,5 @@
"vite.config.ts",
"jest.config.mjs"
],
"exclude": [
"src/constants/base-application/vitest.config.ts"
]
"exclude": ["src/constants/base-application/vitest.config.ts"]
}
+1
View File
@@ -1,2 +1,3 @@
generated
.twenty
@@ -0,0 +1,38 @@
{
"$schema": "./node_modules/oxlint/configuration_schema.json",
"plugins": ["typescript", "import", "unicorn"],
"categories": {
"correctness": "off"
},
"ignorePatterns": ["node_modules"],
"rules": {
"func-style": ["error", "declaration", { "allowArrowFunctions": true }],
"no-console": ["warn", { "allow": ["group", "groupCollapsed", "groupEnd"] }],
"no-control-regex": "off",
"no-debugger": "error",
"no-duplicate-imports": "error",
"no-undef": "off",
"no-unused-vars": "off",
"no-redeclare": "off",
"import/no-duplicates": "error",
"typescript/no-redeclare": "error",
"typescript/ban-ts-comment": "error",
"typescript/consistent-type-imports": ["error", {
"prefer": "type-imports",
"fixStyle": "inline-type-imports"
}],
"typescript/explicit-function-return-type": "off",
"typescript/explicit-module-boundary-types": "off",
"typescript/no-empty-object-type": ["error", {
"allowInterfaces": "with-single-extends"
}],
"typescript/no-empty-function": "off",
"typescript/no-explicit-any": "off",
"typescript/no-unused-vars": ["warn", {
"vars": "all",
"varsIgnorePattern": "^_",
"args": "after-used",
"argsIgnorePattern": "^_"
}]
}
}
@@ -1,7 +1,7 @@
## Base documentation
- Documentation: https://docs.twenty.com/developers/extend/capabilities/apps
- Rich app example: https://github.com/twentyhq/twenty/tree/main/packages/twenty-sdk/src/cli/__tests__/apps/rich-app
- Rich app example: https://github.com/twentyhq/twenty/tree/main/packages/twenty-sdk/src/app-seeds/rich-app
## UUID requirement
- All generated UUIDs must be valid UUID v4.
@@ -1,29 +0,0 @@
import js from '@eslint/js';
import tseslint from 'typescript-eslint';
export default [
// Base JS recommended rules
js.configs.recommended,
// TypeScript recommended rules
...tseslint.configs.recommended,
{
files: ['**/*.ts', '**/*.tsx'],
languageOptions: {
parserOptions: {
project: true,
tsconfigRootDir: import.meta.dirname,
},
},
rules: {
// Common TypeScript-friendly tweaks
'@typescript-eslint/no-unused-vars': [
'warn',
{ argsIgnorePattern: '^_' },
],
'@typescript-eslint/no-explicit-any': 'off',
'no-unused-vars': 'off', // handled by TS rule
},
},
];
@@ -10,8 +10,8 @@
"packageManager": "yarn@4.9.2",
"scripts": {
"twenty": "twenty",
"lint": "eslint",
"lint:fix": "eslint --fix"
"lint": "oxlint -c .oxlintrc.json .",
"lint:fix": "oxlint --fix -c .oxlintrc.json ."
},
"dependencies": {
"twenty-sdk": "latest"
@@ -19,9 +19,8 @@
"devDependencies": {
"@types/node": "^24.7.2",
"@types/react": "^18.2.0",
"eslint": "^9.32.0",
"oxlint": "^0.16.0",
"react": "^18.2.0",
"typescript": "^5.9.3",
"typescript-eslint": "^8.50.0"
"typescript": "^5.9.3"
}
}
@@ -1,6 +1,5 @@
import { defineLogicFunction, RoutePayload } from "twenty-sdk";
import { MetadataApiClient } from 'twenty-sdk/generated';
import { MetadataApiClient } from 'twenty-sdk/clients';
export const OAUTH_TOKEN_PAIRS_PATH = '/oauth/token-pairs';
@@ -53,16 +52,8 @@ const handler = async (event: RoutePayload): Promise<any> => {
const apolloClientSecret = process.env.APOLLO_CLIENT_SECRET ?? '';
const applicationId = process.env.APPLICATION_ID ?? '';
const metadataClient = new MetadataApiClient({});
const tokenPairs = await getAuthenticationTokenPairs(
code,
apolloClientId,
@@ -3,9 +3,7 @@ import {
type DatabaseEventPayload,
type ObjectRecordUpdateEvent,
} from 'twenty-sdk';
import { CoreApiClient } from 'twenty-sdk/generated';
import { CoreApiClient } from 'twenty-sdk/clients';
type CompanyRecord = {
id: string;
@@ -38,8 +36,6 @@ type ApolloEnrichResponse = {
organization?: ApolloOrganization;
};
const extractDomain = (
domainName?: CompanyRecord['domainName'],
): string | undefined => {
@@ -172,7 +168,6 @@ const updateCompanyInTwenty = async (
}
};
type CompanyUpdateEvent = DatabaseEventPayload<
ObjectRecordUpdateEvent<CompanyRecord>
>;
@@ -181,7 +176,6 @@ const handler = async (
event: CompanyUpdateEvent,
): Promise<object | undefined> => {
const { recordId, properties } = event;
const { after: companyAfter } = properties;
@@ -206,7 +200,6 @@ const handler = async (
return { skipped: true, reason: 'no enrichment data to apply' };
}
await updateCompanyInTwenty(recordId, updateData);
const result = {
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,40 @@
{
"$schema": "./node_modules/oxlint/configuration_schema.json",
"plugins": ["typescript", "import", "unicorn"],
"categories": {
"correctness": "off"
},
"ignorePatterns": ["node_modules"],
"rules": {
"func-style": ["error", "declaration", { "allowArrowFunctions": true }],
"no-console": ["warn", { "allow": ["group", "groupCollapsed", "groupEnd"] }],
"no-control-regex": "off",
"no-debugger": "error",
"no-duplicate-imports": "error",
"no-undef": "off",
"no-unused-vars": "off",
"no-redeclare": "off",
"import/no-duplicates": "error",
"typescript/no-redeclare": "error",
"typescript/ban-ts-comment": "error",
"typescript/consistent-type-imports": ["error", {
"prefer": "type-imports",
"fixStyle": "inline-type-imports"
}],
"typescript/explicit-function-return-type": "off",
"typescript/explicit-module-boundary-types": "off",
"typescript/no-empty-object-type": ["error", {
"allowInterfaces": "with-single-extends"
}],
"typescript/no-empty-function": "off",
"typescript/no-explicit-any": "off",
"typescript/no-unused-vars": ["warn", {
"vars": "all",
"varsIgnorePattern": "^_",
"args": "after-used",
"argsIgnorePattern": "^_"
}]
}
}
@@ -26,21 +26,6 @@
"typecheck": {
"dependsOn": ["^build"]
},
"lint": {
"executor": "@nx/eslint:lint",
"outputs": [
"{options.outputFile}"
],
"options": {
"lintFilePatterns": [
"packages/twenty-apps/community/fireflies/**/*.{ts,tsx,js,jsx}"
]
},
"configurations": {
"fix": {
"fix": true
}
}
}
"lint": {}
}
}
@@ -5,7 +5,7 @@
* Usage: yarn setup:fields
*/
/* eslint-disable no-console */
/* oxlint-disable no-console */
import * as dotenv from 'dotenv';
import * as path from 'path';
@@ -1,4 +1,4 @@
/* eslint-disable no-console */
/* oxlint-disable no-console */
import * as dotenv from 'dotenv';
import * as path from 'path';
import { fileURLToPath } from 'url';
@@ -1,4 +1,4 @@
/* eslint-disable no-console */
/* oxlint-disable no-console */
import * as dotenv from 'dotenv';
import * as path from 'path';
import { fileURLToPath } from 'url';
@@ -1,4 +1,4 @@
/* eslint-disable no-console */
/* oxlint-disable no-console */
/**
* Fetch historical Fireflies meetings and insert into Twenty.
*
@@ -219,4 +219,3 @@ main().catch((error) => {
process.exit(1);
});
@@ -1,4 +1,4 @@
/* eslint-disable no-console */
/* oxlint-disable no-console */
/**
* Fetch a Fireflies meeting by ID and insert it into Twenty using the same path
* as the webhook handler.
@@ -1,4 +1,4 @@
/* eslint-disable no-console */
/* oxlint-disable no-console */
/**
* Test script for Fireflies webhook against local Twenty instance
*
@@ -81,4 +81,3 @@ describe('HistoricalImporter', () => {
});
});
@@ -158,4 +158,3 @@ export class HistoricalImporter {
}
}
@@ -72,7 +72,7 @@ export class AppLogger {
debug(message: string, ...args: unknown[]): void {
this.captureLog('debug', message, ...args);
if (this.shouldLog('debug')) {
// eslint-disable-next-line no-console
// oxlint-disable-next-line no-console
console.log(`[${this.context}] ${message}`, ...args);
}
}
@@ -80,7 +80,7 @@ export class AppLogger {
info(message: string, ...args: unknown[]): void {
this.captureLog('info', message, ...args);
if (this.shouldLog('info')) {
// eslint-disable-next-line no-console
// oxlint-disable-next-line no-console
console.log(`[${this.context}] ${message}`, ...args);
}
}
@@ -88,7 +88,7 @@ export class AppLogger {
warn(message: string, ...args: unknown[]): void {
this.captureLog('warn', message, ...args);
if (this.shouldLog('warn')) {
// eslint-disable-next-line no-console
// oxlint-disable-next-line no-console
console.warn(`[${this.context}] ${message}`, ...args);
}
}
@@ -96,7 +96,7 @@ export class AppLogger {
error(message: string, ...args: unknown[]): void {
this.captureLog('error', message, ...args);
if (this.shouldLog('error')) {
// eslint-disable-next-line no-console
// oxlint-disable-next-line no-console
console.error(`[${this.context}] ${message}`, ...args);
}
}
@@ -104,7 +104,7 @@ export class AppLogger {
// For fatal errors, security issues, or data corruption - always visible
critical(message: string, ...args: unknown[]): void {
this.captureLog('error', `CRITICAL: ${message}`, ...args);
// eslint-disable-next-line no-console
// oxlint-disable-next-line no-console
console.error(`[${this.context}] CRITICAL: ${message}`, ...args);
}
@@ -1,3 +1,2 @@
export { Meeting } from './meeting';
@@ -330,7 +330,6 @@ export class WebhookHandler {
}
}
private async createFailedMeetingRecord(params: unknown, error: string): Promise<void> {
try {
const twentyApiKey = process.env.TWENTY_API_KEY || '';
@@ -0,0 +1,54 @@
{
"$schema": "./node_modules/oxlint/configuration_schema.json",
"plugins": ["react", "typescript", "import", "unicorn"],
"categories": {
"correctness": "off"
},
"ignorePatterns": [
"node_modules"
],
"rules": {
"func-style": ["error", "declaration", { "allowArrowFunctions": true }],
"no-console": ["warn", { "allow": ["group", "groupCollapsed", "groupEnd"] }],
"no-control-regex": "off",
"no-debugger": "error",
"no-duplicate-imports": "error",
"no-undef": "off",
"no-unused-vars": "off",
"no-redeclare": "off",
"import/no-duplicates": "error",
"react/no-unescaped-entities": "off",
"react/prop-types": "off",
"react/jsx-key": "off",
"react/display-name": "off",
"react/jsx-uses-react": "off",
"react/react-in-jsx-scope": "off",
"react/jsx-no-useless-fragment": "off",
"react/jsx-props-no-spreading": ["error", { "explicitSpread": "ignore" }],
"react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive-deps": "warn",
"typescript/no-redeclare": "error",
"typescript/ban-ts-comment": "error",
"typescript/consistent-type-imports": ["error", {
"prefer": "type-imports",
"fixStyle": "inline-type-imports"
}],
"typescript/explicit-function-return-type": "off",
"typescript/explicit-module-boundary-types": "off",
"typescript/no-empty-object-type": ["error", {
"allowInterfaces": "with-single-extends"
}],
"typescript/no-empty-function": "off",
"typescript/no-explicit-any": "off",
"typescript/no-unused-vars": ["warn", {
"vars": "all",
"varsIgnorePattern": "^_",
"args": "after-used",
"argsIgnorePattern": "^_"
}]
}
}
@@ -1,69 +0,0 @@
import typescriptParser from '@typescript-eslint/parser';
import path from 'path';
import { fileURLToPath } from 'url';
import reactConfig from '../../../../twenty-eslint-rules/eslint.config.react.mjs';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
export default [
// Extend shared React configuration
...reactConfig,
// Global ignores
{
ignores: [
'**/node_modules/**',
],
},
// TypeScript project-specific configuration
{
files: ['**/*.{ts,tsx}'],
languageOptions: {
parser: typescriptParser,
parserOptions: {
project: [path.resolve(__dirname, 'tsconfig.*.json')],
ecmaFeatures: {
jsx: true,
},
},
},
rules: {
'@nx/enforce-module-boundaries': [
'error',
{
enforceBuildableLibDependency: true,
allow: [],
depConstraints: [
{
sourceTag: 'scope:sdk',
onlyDependOnLibsWithTags: ['scope:sdk'],
},
{
sourceTag: 'scope:shared',
onlyDependOnLibsWithTags: ['scope:shared'],
},
{
sourceTag: 'scope:backend',
onlyDependOnLibsWithTags: ['scope:shared', 'scope:backend'],
},
{
sourceTag: 'scope:frontend',
onlyDependOnLibsWithTags: ['scope:shared', 'scope:frontend'],
},
{
sourceTag: 'scope:zapier',
onlyDependOnLibsWithTags: ['scope:shared'],
},
{
sourceTag: 'scope:browser-extension',
onlyDependOnLibsWithTags: ['scope:twenty-ui', 'scope:browser-extension']
}
],
},
],
}
},
];
@@ -43,7 +43,6 @@ export default defineContentScript({
},
});
ctx.addEventListener(window, 'wxt:locationchange', ({newUrl, }) => {
const injectedBtn = document.querySelector('[data-id="twenty-btn"]');
if(personPattern.includes(newUrl) && !injectedBtn) ui.mount();
@@ -8,7 +8,6 @@ const StyledButton = styled.button`
--text-color: #0a66c2;
--bg-color: #00000000;
font-size: ${({theme}) => theme.spacing(3.5)};
font-weight: ${({theme}) => theme.font.weight.semiBold};
font-family: ${({theme}) => theme.font.family};
@@ -26,7 +25,6 @@ const StyledButton = styled.button`
transition-timing-function: cubic-bezier(.4, 0, .2, 1);
transition-duration: 167ms;
&:hover {
background-color: var(--hover-bg-color);
color: var(--hover-color);
@@ -1,6 +1,5 @@
import { ThemeProvider } from '@emotion/react';
import type React from 'react';
import { THEME_DARK, ThemeContextProvider } from 'twenty-ui/theme';
import { ColorSchemeProvider } from 'twenty-ui/theme-constants';
type ThemeContextProps = {
children: React.ReactNode;
@@ -8,10 +7,6 @@ type ThemeContextProps = {
export const ThemeContext = ({ children }: ThemeContextProps) => {
return (
<ThemeProvider theme={THEME_DARK}>
<ThemeContextProvider theme={THEME_DARK}>
{children}
</ThemeContextProvider>
</ThemeProvider>
<ColorSchemeProvider colorScheme="dark">{children}</ColorSchemeProvider>
);
};
@@ -1,5 +0,0 @@
import type { ThemeType } from 'twenty-ui/theme';
declare module '@emotion/react' {
export interface Theme extends ThemeType {}
}
@@ -1,4 +1,4 @@
import { defineApplication } from '@/sdk';
import { defineApplication } from 'twenty-sdk';
import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from './my.role';
@@ -1,4 +1,4 @@
import { defineRole } from '@/sdk';
import { defineRole } from 'twenty-sdk';
export const DEFAULT_ROLE_UNIVERSAL_IDENTIFIER =
'9f992b6c-17e9-4381-bab5-b6150b574304';
@@ -11,7 +11,9 @@
"scripts": {
"app:dev": "twenty app:dev",
"function:execute": "twenty function:execute",
"app:uninstall": "twenty app:uninstall"
"app:uninstall": "twenty app:uninstall",
"lint": "oxlint -c .oxlintrc.json .",
"lint:fix": "oxlint --fix -c .oxlintrc.json ."
},
"dependencies": {
"twenty-sdk": "latest"
@@ -21,7 +23,6 @@
"@types/node": "^24.7.2",
"@types/react": "^19.0.2",
"react": "^19.0.2",
"eslint": "^9.32.0",
"typescript-eslint": "^8.50.0"
"oxlint": "^0.16.0"
}
}
@@ -1,4 +1,4 @@
import { defineLogicFunction } from '@/sdk';
import { defineLogicFunction } from 'twenty-sdk';
export const ADD_NUMBERS_UNIVERSAL_IDENTIFIER =
'f9e5589c-e951-4d99-85db-0a305ab53502';
@@ -9,7 +9,7 @@
"jsx": "react-jsx",
"baseUrl": ".",
"paths": {
"@/*": ["../../../../../src/*"]
"src/*": ["./src/*"]
}
},
"include": ["**/*"],
@@ -1,4 +1,4 @@
import { defineApplication } from '@/sdk';
import { defineApplication } from 'twenty-sdk';
export default defineApplication({
universalIdentifier: 'invalid-app-0000-0000-0000-000000000001',
@@ -20,8 +20,8 @@
"function:execute": "twenty function:execute",
"app:uninstall": "twenty app:uninstall",
"help": "twenty help",
"lint": "eslint",
"lint:fix": "eslint --fix"
"lint": "oxlint -c .oxlintrc.json .",
"lint:fix": "oxlint --fix -c .oxlintrc.json ."
},
"dependencies": {
"twenty-sdk": "latest"
@@ -31,7 +31,6 @@
"@types/node": "^24.7.2",
"@types/react": "^19.0.2",
"react": "^19.0.2",
"eslint": "^9.32.0",
"typescript-eslint": "^8.50.0"
"oxlint": "^0.16.0"
}
}
@@ -1,4 +1,4 @@
import { defineObject, FieldType } from '@/sdk';
import { defineObject, FieldType } from 'twenty-sdk';
const DUPLICATE_ID = 'duplicate-id-0000-0000-000000000001';
@@ -1,4 +1,4 @@
import { defineObject, FieldType } from '@/sdk';
import { defineObject, FieldType } from 'twenty-sdk';
const DUPLICATE_ID = 'duplicate-id-0000-0000-000000000001';
@@ -3,13 +3,13 @@
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "bundler",
"esModuleInterop": true,
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"jsx": "react-jsx",
"baseUrl": ".",
"paths": {
"@/*": ["../../../../../src/*"]
"src/*": ["./src/*"]
}
},
"include": ["**/*"],
@@ -1,4 +1,4 @@
import { defineApplication } from '@/sdk';
import { defineApplication } from 'twenty-sdk';
export default defineApplication({
universalIdentifier: 'e1e2e3e4-e5e6-4000-8000-000000000001',
@@ -1,4 +1,4 @@
import { defineFrontComponent } from '@/sdk';
import { defineFrontComponent } from 'twenty-sdk';
export const MyComponent = () => {
return (
@@ -1,4 +1,4 @@
import { defineLogicFunction } from '@/sdk';
import { defineLogicFunction } from 'twenty-sdk';
const myHandler = () => {
return 'my-function-result';
@@ -1,4 +1,4 @@
import { FieldType, defineObject } from '@/sdk';
import { FieldType, defineObject } from 'twenty-sdk';
export default defineObject({
universalIdentifier: 'e1e2e3e4-e5e6-4000-8000-000000000030',
@@ -1,4 +1,4 @@
import { defineRole } from '@/sdk';
import { defineRole } from 'twenty-sdk';
export default defineRole({
universalIdentifier: 'e1e2e3e4-e5e6-4000-8000-000000000040',
@@ -1,5 +1,5 @@
{
"name": "root-app",
"name": "minimal-app",
"version": "0.1.0",
"license": "MIT",
"engines": {
@@ -20,8 +20,8 @@
"function:execute": "twenty function:execute",
"app:uninstall": "twenty app:uninstall",
"help": "twenty help",
"lint": "eslint",
"lint:fix": "eslint --fix"
"lint": "oxlint -c .oxlintrc.json .",
"lint:fix": "oxlint --fix -c .oxlintrc.json ."
},
"dependencies": {
"twenty-sdk": "latest"
@@ -31,7 +31,6 @@
"@types/node": "^24.7.2",
"@types/react": "^19.0.2",
"react": "^19.0.2",
"eslint": "^9.32.0",
"typescript-eslint": "^8.50.0"
"oxlint": "^0.16.0"
}
}
@@ -9,8 +9,9 @@
"jsx": "react-jsx",
"baseUrl": ".",
"paths": {
"@/*": ["../../../../../src/*"]
"src/*": ["./src/*"]
}
},
"include": ["**/*"]
"include": ["**/*"],
"exclude": ["node_modules", "dist", ".twenty"]
}
@@ -1,4 +1,4 @@
import { defineApplication } from '@/sdk';
import { defineApplication } from 'twenty-sdk';
import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from './src/roles/default-function.role';
export default defineApplication({
@@ -1,5 +1,5 @@
{
"name": "rich-app",
"name": "postcard-app",
"version": "0.1.0",
"license": "MIT",
"engines": {
@@ -20,8 +20,8 @@
"function:execute": "twenty function:execute",
"app:uninstall": "twenty app:uninstall",
"help": "twenty help",
"lint": "eslint",
"lint:fix": "eslint --fix"
"lint": "oxlint -c .oxlintrc.json .",
"lint:fix": "oxlint --fix -c .oxlintrc.json ."
},
"dependencies": {
"twenty-sdk": "latest"
@@ -31,7 +31,6 @@
"@types/node": "^24.7.2",
"@types/react": "^19.0.2",
"react": "^19.0.2",
"eslint": "^9.32.0",
"typescript-eslint": "^8.50.0"
"oxlint": "^0.16.0"
}
}

Before

Width:  |  Height:  |  Size: 570 B

After

Width:  |  Height:  |  Size: 570 B

@@ -1,4 +1,4 @@
import { defineFrontComponent } from '@/sdk';
import { defineFrontComponent } from 'twenty-sdk';
import { CardDisplay } from '../utils/card-display.component';
export default defineFrontComponent({
@@ -1,4 +1,4 @@
import { defineFrontComponent } from '@/sdk';
import { defineFrontComponent } from 'twenty-sdk';
import { DEFAULT_NAME, formatGreeting } from '../utils/greeting.util';
const GreetingComponent = () => {
@@ -1,4 +1,4 @@
import { defineFrontComponent } from '@/sdk';
import { defineFrontComponent } from 'twenty-sdk';
export const TestComponent = () => {
return (

Some files were not shown because too many files have changed in this diff Show More