Commit Graph

426 Commits

Author SHA1 Message Date
Charles Bochet
badeaddd37 fix(ci): ensure ui-sb-test runs on push to main (#21222)
## Summary

- Add explicit `if: always() && needs.ui-sb-build.result == 'success'`
to `ui-sb-test` job

## Context

After merging #21217, the main-branch Argos baseline pipeline doesn't
work: `ui-sb-test` is silently skipped on push to main, so no
`argos-screenshots-twenty-ui` artifact is produced.

**Root cause:** `changed-files-check` is skipped on push events
(PR-only). `ui-sb-build` handles this with `if: always() && ...`, but
`ui-sb-test` has no explicit `if` — GitHub Actions propagates the skip
through the transitive dependency chain (`changed-files-check` →
`ui-sb-build` → `ui-sb-test`).

## Test plan

- Merge this PR and verify the next push to main produces the
`argos-screenshots-twenty-ui` artifact
- Verify `dispatch-main` successfully triggers ci-privileged with the
artifact
2026-06-04 15:11:38 +02:00
Charles Bochet
9042e8a542 feat(ci): Argos main baselines + local visual diff support (#21217)
## Summary

**CI: Main-branch Argos baselines**
- Run storybook build + screenshot capture on `push` to `main` in CI UI
workflow
- Add `dispatch-main` job in visual regression dispatch to forward
main-branch screenshots to ci-privileged
- Simplify `dispatch-pr` by inlining the artifact name and removing
unused `project` output

**Local visual diff support**
- Add `scripts/visual-diff.sh` for running Argos uploads locally via
tunnel
- Add `storybook:visual-diff` Nx target wrapping the script (depends on
`storybook:build`)
- Honor `STORYBOOK_URL` env in `vitest.config.ts` to reuse pre-served
static builds (mirrors twenty-front pattern)
- Support `ARGOS_BUILD_NAME`, `ARGOS_REFERENCE_BRANCH` env overrides in
vitest plugin config

## Context

Argos builds on PRs are all "Orphan" because there's no reference build
on `main` to compare against. The CI changes add the missing piece:
every merge to main now produces screenshots and uploads them to Argos
as reference builds.

The local visual diff script enables developers to run visual regression
checks from their machine against the self-hosted Argos instance via
`kubectl port-forward` (set up by the twenty-infra `argos-tunnel`
command).

## Related

- twentyhq/twenty-argos#1 (backend config for self-hosted HTTPS
redirect)
- twentyhq/twenty-infra#709 (argos-tunnel super CLI command +
self-hosted mode)

## Test plan

- [ ] Verify CI UI runs on next push to main and produces the
`argos-screenshots-twenty-ui` artifact
- [ ] Verify `dispatch-main` triggers and uploads screenshots to Argos
- [ ] Verify subsequent PR builds show diffs against the main baseline
instead of "Orphan"
- [ ] Run `ARGOS_TOKEN=<token> npx nx storybook:visual-diff twenty-ui`
locally with tunnel active
2026-06-04 14:55:08 +02:00
Charles Bochet
b53f1832d8 feat(ci): simplify visual regression dispatch to twenty-ui only (#21215)
## Summary

- Only trigger visual regression on `CI UI` workflow (drop `CI Front`)
- Remove tarball re-packaging step — `ci-privileged` now downloads the
artifact directly via GitHub API
- Remove `mode`/`project` parameters from the dispatch payload
(hardcoded to twenty-ui in ci-privileged)
- Pass `run_id` of the triggering CI UI workflow so ci-privileged can
fetch the correct artifact

## Context

Part of the fast visual regression CI initiative. The `ci-privileged`
workflow has been simplified to only handle `twenty-ui` screenshots
uploaded directly to Argos.

## Test plan

- [x] Full E2E verified on production: screenshots → Argos build → diff
results → PR comment
2026-06-04 13:49:44 +02:00
Félix Malfait
2ac515894b feat(settings): add Logs as a dedicated tab in General settings (#21180)
## What & why

The audit-log viewer lived as a full-screen page reachable only via a
"View Logs" button buried in the **Security** tab. This surfaces it as
the **third tab in General settings** (`General | Security | Logs`),
consistent with the other tabs.

## Changes

- **Relocated** the event-logs module
`pages/settings/security/event-logs/` → `modules/settings/event-logs/`
and render it as tab content instead of a `FullScreenContainer` page.
Dropped `SettingsPath.EventLogs`, its route, and the fullscreen handling
in favor of the `general#logs` hash tab.
- **Security tab:** removed the "View Logs" entry; kept the
log-retention setting there.
- **In-tab gating** (shown to users with the Security permission):
Enterprise upgrade card when not entitled, a clear "ClickHouse not
configured" placeholder otherwise (derived from client config), and the
query is skipped when disabled. Replaces a bespoke error component that
string-matched error messages with the shared `SettingsEmptyPlaceholder`
/ `SettingsEnterpriseFeatureGateCard`.
- **Layout:** boxed content column with the table selector + filters
grouped in a `Card` and the results table below, matching settings
conventions. Kept the existing fixed filters (page/event name, member,
period) rather than recreating the record-view filter chips (those are
tightly coupled to record/view context).

Frontend + `twenty-shared` only — no changes to the log query or data.

## Test plan

- [x] `npx nx typecheck twenty-front` and `npx nx lint twenty-front`
pass
- [x] Settings → General shows three tabs; Logs is the third; breadcrumb
stays "Workspace / General"
- [x] With Enterprise + ClickHouse: table selector, filters, refresh,
and the paginated table work
- [x] Non-Enterprise: Enterprise upgrade card shown; no failing query
fires
- [ ] Enterprise without ClickHouse: shows the "ClickHouse not
configured" placeholder
- [ ] Security tab still shows the log-retention setting and the "View
Logs" button is gone
- [ ] A user without the Security permission sees neither the Security
nor Logs tab
2026-06-04 08:47:23 +02:00
Charles Bochet
e6614299c6 feat(ci): integrate Argos visual regression via vitest screenshots (#21210)
## Summary

- Adds `@argos-ci/storybook` vitest plugin to `twenty-ui` for automatic
screenshot capture during vitest storybook tests
- Uploads captured screenshots (PNG, ~5MB) as a CI artifact instead of
passing the full storybook build
- Updates the visual regression dispatch workflow to pass
`mode=argos-screenshots` to ci-privileged, which then uploads
screenshots to Argos via CLI

This replaces the 10-minute Storybook screenshot capture with a ~30s
vitest browser-mode approach. The heavy screenshot work happens on free
public runners, while ci-privileged only handles the Argos API upload
(keeping secrets private).

## Architecture

```
twenty (public, free runners)          ci-privileged (private)
─────────────────────────────          ────────────────────────
1. Build storybook-static              4. Download screenshots artifact
2. Vitest captures screenshots         5. `argos upload` → Argos API
3. Upload screenshots artifact         6. Poll for results
                                       7. Post PR comment
```

## Test plan

- [x] Verified locally: vitest captures 225 screenshots in ~28s
- [x] Verified `@argos-ci/cli upload` successfully creates Argos build
from captured screenshots
- [x] Argos diffs computed and results visible via API
- [ ] CI runs end-to-end on a PR
2026-06-04 08:46:37 +02:00
martmull
0671ff3de5 Fix lambda error (#21179)
- move twenty-sdk from dependencies to devDependencies
- add documentation about breaking
- add warning about moving the package to dev dependencies
2026-06-03 16:45:51 +00:00
Thomas des Francs
1642be86f5 Bonapara/twenty codex plugin (#20857)
@martmull v2.0 ;)

---------

Co-authored-by: martmull <martmull@hotmail.fr>
Co-authored-by: bosiraphael <raphael.bosi@gmail.com>
2026-06-02 14:39:14 +00:00
Weiko
9b54200d8c Fix playwright CI (#21024)
## Context
The Install Playwright step ran npx playwright install with no
arguments, which downloads all browsers (Chromium + Firefox + WebKit +
ffmpeg, ~500MB+) on every run with no caching.

Fix:
- Install Chromium only — npx playwright install chromium instead of all
browsers.
- Cache the browser binaries — actions/cache on ~/.cache/ms-playwright,
keyed on the resolved Playwright version (v4-playwright-browsers-${{
runner.os }}-<version>). On a cache hit the install step is skipped
entirely; the cache invalidates automatically when the Playwright
version bumps.
2026-05-28 14:19:58 +00:00
Charles Bochet
2f4ebf8160 Move preview environment workflow to ci-privileged (#20872)
## Summary

Slims `preview-env-dispatch.yaml` to a single dispatch and deletes
`preview-env-keepalive.yaml`. The actual preview-env work moves to
**twentyhq/ci-privileged#22** (must merge as a pair).

## Why

Context: PR #20867 was a credential-exfil attempt against our workflows.
GitHub's default fork-PR-no-secrets policy + our existing gates
(`author_association` checks, `pull_request_target` checking out base,
`enableScripts: false`) neutralized the actual attack — but the audit
surfaced one workflow that *would* have given a malicious external PR
access to a real secret if a maintainer had applied the `preview-app`
label: `preview-env-keepalive.yaml`.

That workflow checked out the PR head SHA, did `docker login` with
`DOCKERHUB_PASSWORD`, then ran the PR's `docker-compose.yml`. A
malicious compose could have mounted `~/.docker/config.json` and
exfiltrated the Dockerhub credential.

After this PR, that workflow lives in `twentyhq/ci-privileged` instead,
paired with a rename of the credential to `DOCKERHUB_RO_TOKEN`
(Dockerhub PAT with `Public Repo Read-only` scope). A read-only PAT has
no exfiltration value — it's equivalent to anonymous Dockerhub access
plus rate-limit headroom — so the credential lives safely on the runner
without further hygiene tricks.

## What this PR does

- **Modifies** `.github/workflows/preview-env-dispatch.yaml`:
- Single dispatch to `twentyhq/ci-privileged` (was: self-dispatch to
twenty for the env + a separate dispatch to ci-privileged for the PR
comment).
  - `permissions: {}` (was: `contents: write`).
  - Drops `preview-env-keepalive.yaml` from the path-trigger list.
- **Deletes** `.github/workflows/preview-env-keepalive.yaml`. The
207-line workflow now lives in
`twentyhq/ci-privileged/.github/workflows/preview-env.yaml`.

Net `twenty` repo change: **-204 lines / +3 lines**.

## Companion PR

twentyhq/ci-privileged#22 — adds the new `preview-env.yaml`, deletes the
now-redundant `post-preview-comment.yaml`.

## Secrets fallout in this repo

After this PR, `DOCKERHUB_PASSWORD` in `twentyhq/twenty` secrets is only
used by `ci-test-docker-compose.yaml`, where:
- It evaluates to empty for fork PRs (GitHub default — secrets aren't
passed to fork-PR workflows).
- It's only needed for internal / merge_queue runs, for Dockerhub
rate-limit headroom on base-image pulls.

Recommend (separate change): also convert the twenty-side
`DOCKERHUB_PASSWORD` to a `Public Repo Read-only` Dockerhub PAT, and
rename it to `DOCKERHUB_RO_TOKEN` for consistency with ci-privileged.
The workflow change for `ci-test-docker-compose.yaml` would just be a
rename — login flow is identical for password vs. PAT.

## Test plan

- [ ] Merge twentyhq/ci-privileged#22 first (so the dispatched event has
a handler)
- [ ] Open an internal PR touching `packages/twenty-docker/**`, confirm
`Preview Environment Dispatch` runs and ci-privileged's `Preview
Environment` workflow runs the docker compose + posts the URL
- [ ] On an external contributor PR, apply the `preview-app` label,
confirm the same flow
- [ ] Confirm closing the PR doesn't break (no cleanup workflow was
changed)
2026-05-23 11:37:37 +00:00
martmull
056e3a4cd8 Add check for breaking api changes (#20848)
- update ci-breaking-changes.yaml so it check for api contrat breaks
- check fails properly when removing fix
https://github.com/twentyhq/twenty/pull/20825
- check it turns green again when adding fix back
2026-05-23 08:50:05 +00:00
martmull
237a943947 Update twenty sdk commands (#20735)
Performs twenty-sdk cli command migration:

Summary

``` ┌─────┬──────────────────────────┬────────────────────────────┬───────────────────────┐
 │  #  │       Old command        │        New command         │        Status         │
 ├─────┼──────────────────────────┼────────────────────────────┼───────────────────────┤
 │ 1   │ twenty dev [appPath]     │ twenty dev [appPath]       │ Unchanged (now also   │
 │     │                          │                            │ DEFAULT)              │
 ├─────┼──────────────────────────┼────────────────────────────┼───────────────────────┤
 │ 2   │ twenty dev --once        │ twenty dev --once          │ Unchanged             │
 │     │ [appPath]                │ [appPath]                  │                       │
 ├─────┼──────────────────────────┼────────────────────────────┼───────────────────────┤
 │ 3   │ twenty dev --watch       │ twenty dev [appPath]       │ --watch flag removed  │
 │     │ [appPath]                │                            │ (was default)         │
 ├─────┼──────────────────────────┼────────────────────────────┼───────────────────────┤
 │ 4   │ twenty dev --verbose     │ twenty dev --verbose       │ Unchanged             │
 │     │ [appPath]                │ [appPath]                  │                       │
 ├─────┼──────────────────────────┼────────────────────────────┼───────────────────────┤
 │ 5   │ twenty dev --debug       │ twenty dev --debug         │ Unchanged             │
 │     │ [appPath]                │ [appPath]                  │                       │
 ├─────┼──────────────────────────┼────────────────────────────┼───────────────────────┤
 │ 6   │ twenty dev --debounceMs  │ twenty dev --debounceMs    │ Unchanged             │
 │     │ <ms> [appPath]           │ <ms> [appPath]             │                       │
 ├─────┼──────────────────────────┼────────────────────────────┼───────────────────────┤
 │ 7   │ twenty build [appPath]   │ twenty dev:build [appPath] │ Deprecated → colon    │
 │     │                          │                            │ command               │
 ├─────┼──────────────────────────┼────────────────────────────┼───────────────────────┤
 │ 8   │ twenty build --tarball   │ twenty dev:build --tarball │ Deprecated → colon    │
 │     │ [appPath]                │  [appPath]                 │ command               │
 ├─────┼──────────────────────────┼────────────────────────────┼───────────────────────┤
 │ 9   │ twenty typecheck         │ twenty dev:typecheck       │ Deprecated → colon    │
 │     │ [appPath]                │ [appPath]                  │ command               │
 ├─────┼──────────────────────────┼────────────────────────────┼───────────────────────┤
 │ 10  │ twenty logs [appPath]    │ twenty dev:fn-logs         │ Deprecated → colon    │
 │     │                          │ [appPath]                  │ command               │
 ├─────┼──────────────────────────┼────────────────────────────┼───────────────────────┤
 │ 11  │ twenty logs -n <name>    │ twenty dev:fn-logs -n      │ Deprecated → colon    │
 │     │ [appPath]                │ <name> [appPath]           │ command               │
 ├─────┼──────────────────────────┼────────────────────────────┼───────────────────────┤
 │ 12  │ twenty logs -u <id>      │ twenty dev:fn-logs -u <id> │ Deprecated → colon    │
 │     │ [appPath]                │  [appPath]                 │ command               │
 ├─────┼──────────────────────────┼────────────────────────────┼───────────────────────┤
 │ 13  │ twenty exec [appPath]    │ twenty dev:fn-exec         │ Deprecated → colon    │
 │     │                          │ [appPath]                  │ command               │
 ├─────┼──────────────────────────┼────────────────────────────┼───────────────────────┤
 │ 14  │ twenty exec -n <name>    │ twenty dev:fn-exec -n      │ Deprecated → colon    │
 │     │ [appPath]                │ <name> [appPath]           │ command               │
 ├─────┼──────────────────────────┼────────────────────────────┼───────────────────────┤
 │ 15  │ twenty exec -u <id>      │ twenty dev:fn-exec -u <id> │ Deprecated → colon    │
 │     │ [appPath]                │  [appPath]                 │ command               │
 ├─────┼──────────────────────────┼────────────────────────────┼───────────────────────┤
 │ 16  │ twenty exec -p <json>    │ twenty dev:fn-exec -p      │ Deprecated → colon    │
 │     │ [appPath]                │ <json> [appPath]           │ command               │
 ├─────┼──────────────────────────┼────────────────────────────┼───────────────────────┤
 │ 17  │ twenty exec              │ twenty dev:fn-exec         │ Deprecated → colon    │
 │     │ --postInstall [appPath]  │ --postInstall [appPath]    │ command               │
 ├─────┼──────────────────────────┼────────────────────────────┼───────────────────────┤
 │ 18  │ twenty exec --preInstall │ twenty dev:fn-exec         │ Deprecated → colon    │
 │     │  [appPath]               │ --preInstall [appPath]     │ command               │
 ├─────┼──────────────────────────┼────────────────────────────┼───────────────────────┤
 │ 19  │ twenty add [entityType]  │ twenty dev:add             │ Deprecated → colon    │
 │     │                          │ [entityType]               │ command               │
 ├─────┼──────────────────────────┼────────────────────────────┼───────────────────────┤
 │ 20  │ twenty add --path <path> │ twenty dev:add --path      │ Deprecated → colon    │
 │     │  [entityType]            │ <path> [entityType]        │ command               │
 └─────┴──────────────────────────┴────────────────────────────┴───────────────────────┘

 App lifecycle commands

 ┌─────┬────────────────────────┬────────────────────────────┬─────────────────────────┐
 │  #  │      Old command       │        New command         │         Status          │
 ├─────┼────────────────────────┼────────────────────────────┼─────────────────────────┤
 │ 21  │ twenty publish         │ twenty app:publish         │ Deprecated → colon      │
 │     │ [appPath]              │ [appPath]                  │ command                 │
 ├─────┼────────────────────────┼────────────────────────────┼─────────────────────────┤
 │ 22  │ twenty publish --tag   │ twenty app:publish --tag   │ Deprecated → colon      │
 │     │ <tag> [appPath]        │ <tag> [appPath]            │ command                 │
 ├─────┼────────────────────────┼────────────────────────────┼─────────────────────────┤
 │ 23  │ twenty deploy          │ twenty app:publish         │ Deprecated → colon      │
 │     │ [appPath]              │ --private [appPath]        │ command + --private     │
 ├─────┼────────────────────────┼────────────────────────────┼─────────────────────────┤
 │ 24  │ twenty install         │ twenty app:install         │ Deprecated → colon      │
 │     │ [appPath]              │ [appPath]                  │ command                 │
 ├─────┼────────────────────────┼────────────────────────────┼─────────────────────────┤
 │ 25  │ twenty uninstall       │ twenty app:uninstall       │ Deprecated → colon      │
 │     │ [appPath]              │ [appPath]                  │ command                 │
 ├─────┼────────────────────────┼────────────────────────────┼─────────────────────────┤
 │ 26  │ twenty uninstall -y    │ twenty app:uninstall -y    │ Deprecated → colon      │
 │     │ [appPath]              │ [appPath]                  │ command                 │
 └─────┴────────────────────────┴────────────────────────────┴─────────────────────────┘

 Server commands

 ┌─────┬─────────────────────────┬─────────────────────────────┬──────────────────────┐
 │  #  │       Old command       │         New command         │        Status        │
 ├─────┼─────────────────────────┼─────────────────────────────┼──────────────────────┤
 │ 27  │ twenty server start     │ twenty docker:start         │ Deprecated → colon   │
 │     │                         │                             │ syntax               │
 ├─────┼─────────────────────────┼─────────────────────────────┼──────────────────────┤
 │ 28  │ twenty server start -p  │ twenty docker:start -p      │ Deprecated → colon   │
 │     │ <port>                  │ <port>                      │ syntax               │
 ├─────┼─────────────────────────┼─────────────────────────────┼──────────────────────┤
 │ 29  │ twenty server start     │ twenty docker:start --test  │ Deprecated → colon   │
 │     │ --test                  │                             │ syntax               │
 ├─────┼─────────────────────────┼─────────────────────────────┼──────────────────────┤
 │ 30  │ twenty server stop      │ twenty docker:stop          │ Deprecated → colon   │
 │     │                         │                             │ syntax               │
 ├─────┼─────────────────────────┼─────────────────────────────┼──────────────────────┤
 │ 31  │ twenty server stop      │ twenty docker:stop --test   │ Deprecated → colon   │
 │     │ --test                  │                             │ syntax               │
 ├─────┼─────────────────────────┼─────────────────────────────┼──────────────────────┤
 │ 32  │ twenty server status    │ twenty docker:status        │ Deprecated → colon   │
 │     │                         │                             │ syntax               │
 ├─────┼─────────────────────────┼─────────────────────────────┼──────────────────────┤
 │ 33  │ twenty server status    │ twenty docker:status --test │ Deprecated → colon   │
 │     │ --test                  │                             │ syntax               │
 ├─────┼─────────────────────────┼─────────────────────────────┼──────────────────────┤
 │ 34  │ twenty server logs      │ twenty docker:logs          │ Deprecated → colon   │
 │     │                         │                             │ syntax               │
 ├─────┼─────────────────────────┼─────────────────────────────┼──────────────────────┤
 │ 35  │ twenty server logs -n   │ twenty docker:logs -n       │ Deprecated → colon   │
 │     │ <lines>                 │ <lines>                     │ syntax               │
 ├─────┼─────────────────────────┼─────────────────────────────┼──────────────────────┤
 │ 36  │ twenty server logs      │ twenty docker:logs --test   │ Deprecated → colon   │
 │     │ --test                  │                             │ syntax               │
 ├─────┼─────────────────────────┼─────────────────────────────┼──────────────────────┤
 │ 37  │ twenty server reset     │ twenty docker:reset         │ Deprecated → colon   │
 │     │                         │                             │ syntax               │
 ├─────┼─────────────────────────┼─────────────────────────────┼──────────────────────┤
 │ 38  │ twenty server reset     │ twenty docker:reset --test  │ Deprecated → colon   │
 │     │ --test                  │                             │ syntax               │
 ├─────┼─────────────────────────┼─────────────────────────────┼──────────────────────┤
 │ 39  │ twenty server upgrade   │ twenty docker:upgrade       │ Deprecated → colon   │
 │     │ [version]               │ [version]                   │ syntax               │
 ├─────┼─────────────────────────┼─────────────────────────────┼──────────────────────┤
 │ 40  │ twenty server upgrade   │ twenty docker:upgrade       │ Deprecated → colon   │
 │     │ --test [version]        │ --test [version]            │ syntax               │
 ├─────┼─────────────────────────┼─────────────────────────────┼──────────────────────┤
 │ 41  │ twenty server           │ twenty app:catalog-sync  │ Deprecated → colon   │
 │     │ catalog-sync            │                             │ syntax               │
 ├─────┼─────────────────────────┼─────────────────────────────┼──────────────────────┤
 │ 42  │ twenty server           │ twenty app:catalog-sync  │ Deprecated → colon   │
 │     │ catalog-sync -r <name>  │ -r <name>                   │ syntax               │
 ├─────┼─────────────────────────┼─────────────────────────────┼──────────────────────┤
 │ 43  │ twenty catalog-sync     │ (removed)                   │ Removed (was already │
 │     │                         │                             │  deprecated)         │
 └─────┴─────────────────────────┴─────────────────────────────┴──────────────────────┘

 Remote commands

 ┌─────┬────────────────────────┬──────────────────────────┬──────────────────────────┐
 │  #  │      Old command       │       New command        │          Status          │
 ├─────┼────────────────────────┼──────────────────────────┼──────────────────────────┤
 │ 44  │ twenty remote add      │ twenty remote:add        │ Deprecated → colon       │
 │     │                        │                          │ syntax                   │
 ├─────┼────────────────────────┼──────────────────────────┼──────────────────────────┤
 │ 45  │ twenty remote add --as │ twenty remote:add --as   │ Deprecated → colon       │
 │     │  <name>                │ <name>                   │ syntax                   │
 ├─────┼────────────────────────┼──────────────────────────┼──────────────────────────┤
 │ 46  │ twenty remote add      │ twenty remote:add        │ Deprecated → colon       │
 │     │ --api-key <key>        │ --api-key <key>          │ syntax                   │
 ├─────┼────────────────────────┼──────────────────────────┼──────────────────────────┤
 │ 47  │ twenty remote add      │ twenty remote:add        │ Deprecated → colon       │
 │     │ --api-url <url>        │ --api-url <url>          │ syntax                   │
 ├─────┼────────────────────────┼──────────────────────────┼──────────────────────────┤
 │ 48  │ twenty remote add      │ twenty remote:add        │ Deprecated → colon       │
 │     │ --local                │ --local                  │ syntax                   │
 ├─────┼────────────────────────┼──────────────────────────┼──────────────────────────┤
 │ 49  │ twenty remote add      │ twenty remote:add --test │ Deprecated → colon       │
 │     │ --test                 │                          │ syntax                   │
 ├─────┼────────────────────────┼──────────────────────────┼──────────────────────────┤
 │ 50  │ twenty remote list     │ twenty remote:list       │ Deprecated → colon       │
 │     │                        │                          │ syntax                   │
 ├─────┼────────────────────────┼──────────────────────────┼──────────────────────────┤
 │ 51  │ twenty remote switch   │ twenty remote:use [name] │ Deprecated → colon       │
 │     │ [name]                 │                          │ syntax + renamed         │
 ├─────┼────────────────────────┼──────────────────────────┼──────────────────────────┤
 │ 52  │ twenty remote status   │ twenty remote:status     │ Deprecated → colon       │
 │     │                        │                          │ syntax                   │
 ├─────┼────────────────────────┼──────────────────────────┼──────────────────────────┤
 │ 53  │ twenty remote remove   │ twenty remote:remove     │ Deprecated → colon       │
 │     │ <name>                 │ <name>                   │ syntax                   │
 └─────┴────────────────────────┴──────────────────────────┴──────────────────────────┘
```

---------

Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
2026-05-20 15:12:39 +00:00
Félix Malfait
a2acf88a57 feat(website): per-PR preview deploys via Worker versions (#20762)
## Summary
Adds review apps for the marketing site. Every PR that touches
`packages/twenty-website/**` or `packages/twenty-shared/**` gets a
per-version Worker preview URL, sticky-commented on the PR, auto-cleaned
up when the PR closes.

Same Cloudflare machinery skew protection rides on, just used for
previews — no extra plan, no extra services. Cleaner than the
GitHub-Actions-runner + Cloudflare-tunnel pattern: previews persist for
the life of the version, accessible from anywhere, no warm-up.

## Files
- **`.github/workflows/website-pr-preview.yaml`** — on PR
open/sync/reopen: builds the Worker with a per-PR `DEPLOYMENT_ID`, runs
`wrangler versions upload --tag pr-<N>` (no production traffic),
sticky-comments the preview URL. Skipped on fork PRs because GitHub
doesn't pass secrets to forks anyway.
- **`.github/workflows/website-pr-preview-cleanup.yaml`** — on PR close:
walks the Worker version list via the CF API, deletes anything tagged
`pr-<N>` (with message-based fallback if the annotation key changes),
updates the sticky comment.
- **`open-next.config.ts`** — `maxNumberOfVersions: 10 → 50` to leave
room for PR previews on top of skew protection's prod-version retention.

## How it looks on a PR

The bot leaves a sticky comment like:

> 🔍 **Website preview** is up at
**https://abc12345-twenty-website-dev.twentyhq.workers.dev**
>
> | | |
> |---|---|
> | Version | `abc12345-...` |
> | Commit | `<sha>` |
> | Bindings | shared with the `dev` Worker (R2 cache + secrets) |
>
> Updates on every push. Auto-deleted when the PR closes.

On close it becomes:

> 🧹 Website preview for this PR was cleaned up after close.

## Twenty repo credentials already provisioned
- `secret CLOUDFLARE_API_TOKEN` — same scoped token the `twenty-infra`
workflow uses
- `var CLOUDFLARE_ACCOUNT_ID` = `67b2bbe4381006564d2b0aa6ce6177be`
- `var CF_PREVIEW_DOMAIN` = `twentyhq` (no `.workers.dev` suffix —
OpenNext appends it;
[opennextjs-cloudflare#811](https://github.com/opennextjs/opennextjs-cloudflare/issues/811))

## Known limitations
- **Shared dev bindings**: PR previews use the dev Worker's R2 bucket +
secrets (Stripe test key, JWT private key). Fine for a read-mostly
marketing site; if two simultaneous PRs ever fight over ISR cache state
we can prefix R2 keys per-PR later.
- **Fork PRs don't get previews**. GitHub Actions doesn't pass
`secrets.*` to fork-PR runs (security), and the wrangler upload requires
the CF token. To enable forks, would need to switch to
`pull_request_target` and gate on a maintainer label — not done here
because the security tradeoff isn't worth it for a marketing-site
preview.
- **Version cap**: 50 versions is the new ceiling, and
`maxVersionAgeDays: 14` auto-prunes anything older. Cleanup-on-close
should keep us well under in steady state.

## Test plan
- [ ] CI on this PR triggers the preview workflow itself; check that the
sticky comment appears with a working URL
- [ ] Hit the URL, click around — should look like a fresh
marketing-site build with this PR's changes
- [ ] Close (don't merge) → cleanup workflow should run; sticky comment
switches to the "cleaned up" message; the version is gone from `wrangler
versions list --name twenty-website-dev`
2026-05-20 14:59:42 +02:00
Félix Malfait
c002bc52bd fix(ci): repair preview-environment dispatch (use PAT, not GITHUB_TOKEN) (#20773)
## What
One-line token swap on the same-repo dispatch step in
[`preview-env-dispatch.yaml`](.github/workflows/preview-env-dispatch.yaml#L40):
`secrets.GITHUB_TOKEN` → `secrets.CI_PRIVILEGED_DISPATCH_TOKEN`.

## Why
Regression from [#20476](https://github.com/twentyhq/twenty/pull/20476)
("security: harden CI against supply-chain attacks"), merged 2026-05-12.
That PR replaced

```yaml
uses: peter-evans/repository-dispatch@v2
with:
  token: ${{ secrets.GITHUB_TOKEN }}
  ...
```

with a raw `gh api` call but kept `GITHUB_TOKEN`:

```yaml
env:
  GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
  gh api repos/"$REPOSITORY"/dispatches -f event_type=preview-environment ...
```

The auto-provisioned `GITHUB_TOKEN` can't fire `repository_dispatch` via
`gh api` even when the workflow declares `permissions: contents: write`.
The action used a different code path that worked; the CLI requires a
token with `repo` scope. So every dispatch from this workflow has
returned `403 Resource not accessible by integration` since that PR
merged — except for runs the `author_association` / `preview-app` label
gate skips entirely (which then show "success" because no jobs ran).

Recent failed example:
https://github.com/twentyhq/twenty/actions/runs/26162974597/job/76959379235?pr=20769

## The fix
`secrets.CI_PRIVILEGED_DISPATCH_TOKEN` already exists in repo secrets
and is **already used** by the immediately-following cross-repo dispatch
step in the same file. Using it for the same-repo dispatch too matches
the surrounding code and is consistent with the original hardening
intent (use a scoped PAT, not the auto-provisioned token).

## Test plan
- [ ] Merge this PR
- [ ] Next PR open / sync / reopen on a member's branch → check that
`Preview Environment Dispatch` succeeds (no 403)
- [ ] Confirm `Preview Environment Keep Alive` workflow gets triggered
(the downstream effect of the dispatch)
- [ ] Confirm the tunnel URL sticky comment lands on the PR

Discovered while testing an unrelated PR
([#20762](https://github.com/twentyhq/twenty/pull/20762)). Independent
fix.
2026-05-20 14:57:57 +02:00
Félix Malfait
658bdf3e57 chore(website): rename twenty-website-new → twenty-website (#20745)
## Summary
Follow-up to the Cloudflare/OpenNext migration (#20741). Now that the
legacy `twenty-website` package was already removed in #20270, the
`-new` suffix on the marketing site package is no longer meaningful.

## What changes
- **Directory rename**: `git mv packages/twenty-website-new
packages/twenty-website` (1213 files moved, no content change)
- **Package + nx config**: `package.json` and `project.json` name fields
updated, `sourceRoot` repointed
- **Source refs**: `load-local-articles.ts` and
`load-local-release-notes.ts` had a hardcoded `'twenty-website-new'`
segment in their monorepo-root fallback path;
`app/[locale]/releases/page.tsx` had display strings showing where to
add content
- **External refs**: root `package.json` workspaces, root `CLAUDE.md` /
`README.md`, `twenty-sdk` + `create-twenty-app` READMEs,
`.vscode/twenty.code-workspace`, `.cursor/rules/changelog-process.mdc`,
Crowdin config + the three `website-i18n-*` CI workflows +
`ci-website.yaml`
- **Docker cleanup**:
`packages/twenty-docker/twenty-website-new/Dockerfile` deleted; the two
Makefile targets (`prod-website-new-build` / `prod-website-new-run`)
that referenced it removed — EKS deploy was retired in the Cloudflare
migration
- **`yarn.lock`** regenerated against the new workspace path

## What's deliberately not in this PR
The dev hostname `website-new.twenty-main.com` in `wrangler.jsonc` stays
for now. Migrating it to `website.twenty-main.com` needs coordinated DNS
deletion (current CNAME points at the legacy Docusaurus NLB and serves
503s) and removal of the matching legacy `website` Helm chart in
`twenty-infra`. Flagged as a separate cleanup.

Companion infra PR: https://github.com/twentyhq/twenty-infra/pull/682
(workflow paths + Terraform ECR + docs)

## Test plan
- [x] `yarn install --immutable` resolves clean against the new path
- [x] `npx nx typecheck twenty-website` passes
- [x] `npx nx lint twenty-website` passes
- [ ] CI on this PR confirms the same on a fresh checkout
- [ ] After merge: trigger `Deploy Website` workflow against
`environment=dev` to confirm the renamed working-directory deploys
correctly
2026-05-19 23:42:09 +02:00
martmull
d5ff9eb515 Create twenty app improvements (#20688)
create-twenty-app updates:
- remove --example option
- sync --once when scaffolding an applicaiton
- rename --api-url option to --workspace-url
- create a standalone page when scaffolding an app
<img width="1494" height="765" alt="image"
src="https://github.com/user-attachments/assets/0e35ed0c-b0aa-466c-9f56-7939294fd2cf"
/>

---------

Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
2026-05-19 13:12:47 +00:00
Charles Bochet
cf4b4455d3 fix(server): normalize composite defaultValues in manifest converter (unblock app re-install on 2.5-normalized workspaces) (#20615)
## Context

The runtime create-field path and the v2.5
`NormalizeCompositeFieldDefaultsCommand` workspace upgrade both run
composite `defaultValue`s through `nullifyEmptyCompositeDefaultValue`.
The manifest install/sync path was the only write path that skipped it:
[`fromFieldManifestToUniversalFlatFieldMetadata`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-server/src/engine/core-modules/application/application-manifest/converters/from-field-manifest-to-universal-flat-field-metadata.util.ts)
passed `fieldManifest.defaultValue` through verbatim.

For the SDK-emitted ACTOR system fields (`createdBy` / `updatedBy`),
`twenty-sdk` ships `{ name: "''", source: "'MANUAL'" }`. After the
runtime or the 2.5 normalize command stores them, the workspace row
holds the canonical four-key form `{ context: null, name: null, source:
"'MANUAL'", workspaceMemberId: null }`. The next install computes its TO
map from the manifest, still gets the raw two-key shape, and diffs it
against the normalized FROM. The dispatcher emits a `defaultValue`
update on each system actor field; the flat-field-metadata validator
rejects it with `FIELD_MUTATION_NOT_ALLOWED`, blocking every re-install
of any application that defines a custom object on a v2.5-normalized
workspace.


## Fix

Normalize composite `defaultValue`s inside the converter, reusing the
same `nullifyEmptyCompositeDefaultValue` helper the three other write
paths already share:

-
[`get-default-flat-field-metadata-from-create-field-input.util.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/get-default-flat-field-metadata-from-create-field-input.util.ts)
— `createOneObject` and `createOneField` GraphQL paths.
-
[`sanitize-raw-update-field-input.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/sanitize-raw-update-field-input.ts)
— `updateOneField` GraphQL path.
-
[`2-5-workspace-command-1778000001000-normalize-composite-field-defaults.command.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-server/src/database/commands/upgrade-version-command/2-5/2-5-workspace-command-1778000001000-normalize-composite-field-defaults.command.ts)
— the upgrade backfill that introduced the divergence.

After the fix, the four write paths agree on the canonical shape, so
re-installs are no-ops on system actor fields regardless of when the 2.5
normalize command ran. Non-composite types pass through unchanged.

## Test

New spec
`from-field-manifest-to-universal-flat-field-metadata.util.spec.ts`
covers:

- Empty-name actor defaults are normalized to the four-key canonical
shape.
- The converter is idempotent: feeding its own output back in produces
the same result (so two consecutive syncs of the same manifest never
emit a `defaultValue` update).
- When the manifest omits `defaultValue`, the converter falls back to
`generateDefaultValue` and normalizes the result.
- Non-composite defaults pass through unchanged.

```
PASS  src/engine/core-modules/application/application-manifest/converters/__tests__/from-field-manifest-to-universal-flat-field-metadata.util.spec.ts
  fromFieldManifestToUniversalFlatFieldMetadata
    composite defaultValue normalization
      ✓ normalizes empty-name actor defaults to the canonical four-key shape
      ✓ is idempotent: re-running the converter on its own output yields the same defaultValue
      ✓ falls back to the generated default and normalizes it when defaultValue is omitted
      ✓ leaves non-composite defaults untouched
Tests: 4 passed
```

## CI gap that let this through

The integration suites covering manifest install (`appDevOnce` against
the test workspace) never re-installed an existing app on a workspace
whose composite fields had already been put through the 2.5 normalize
command. They synced once, then ran assertions on the resulting state;
the second sync that would have re-triggered the `defaultValue` diff was
never exercised.

If we want to catch this class of regression at the integration level
too, we'd add a test that (1) syncs an app whose manifest includes an
ACTOR system field with the raw SDK shape, (2) invokes
`NormalizeCompositeFieldDefaultsCommand` directly on the test workspace,
(3) re-syncs the same manifest, and (4) asserts no
`FIELD_MUTATION_NOT_ALLOWED` errors. The unit-level idempotency check in
this PR is the minimal version of that same coverage. Happy to ship that
integration spec in a follow-up if it'd help.
2026-05-15 18:31:48 +02:00
Paul Rastoin
a754a95d38 Regrant id token write to claude for oidc swap (#20564) 2026-05-14 07:19:08 +00:00
martmull
0cc2194399 Simplify create-twenty-app command (#20512)
## Simplify `create-twenty-app` for zero-interaction use

Makes `npx create-twenty-app@latest my-app` a fully non-interactive,
single-command experience suitable for automated environments (Codex,
Claude plugins).

  ### Changes

- **Remove all interactive prompts** — app name, display name,
description, and scaffold confirmation are now derived from CLI args
with sensible defaults. `inquirer` dependency removed
   entirely.
- **Replace OAuth with API key auth** — use the seeded dev API key
(`DEV_API_KEY`) to authenticate against the Docker instance as
`tim@apple.dev`, eliminating the browser-based OAuth
  flow.
- **Docker-first with early validation** — check Docker is installed
before scaffolding; if missing, print the install URL and exit. Detect
alternative runtimes (Podman, nerdctl).
- **Parallel image pull** — `docker pull` runs in the background during
scaffold + dependency install, saving 10-30s on typical runs.
- **Always pull latest image** — ensures the dev server is up-to-date on
every run.
- **Stop detecting port 3000** — only check port 2020 (Docker instance).
- **Update CLI flags** — remove `--skip-local-instance` and `--yes`; add
`--skip-docker`.
- **Update CI workflows and docs** — align e2e workflows, package
README, and template README/cd.yml with the new flow.
2026-05-13 16:44:27 +00:00
neo773
565995e715 security: harden CI against supply-chain attacks (#20476)
- Pin all third-party actions to SHA
- Gate claude.yml triggers to internal authors with Harden-Runner egress
audit
- Ignore fork-PR lifecycle scripts
- Narrow cross-repo dispatch payloads
- Add 7d npm release-age gate
- Add CODEOWNERS on .github/** and .yarnrc.yml

---------

Co-authored-by: prastoin <paul@twenty.com>
2026-05-12 12:20:29 +00:00
Félix Malfait
b6b4824104 ci(preview-env): drop yarn -- separator so --light reaches the seed command (#20479)
## Summary

Follow-up to #20464. That PR added `--light` to the preview env seed
command but left the `--` between `yarn command:prod` and the script
args. After yarn strips its own `--`, nest-commander still sees `argv:
[..., '--', 'workspace:seed:dev', '--light']`. Commander.js treats `--`
as the end-of-options marker, so `--light` is parsed as a positional arg
and silently ignored — the seed runs in full mode (Apple + YCombinator +
Empty3 + Empty4) and Empty4 still ends up as the default workspace.

## Evidence

In the preview run on `f706cc052b` (which had #20464's `--light` flag),
the seed step took only ~40s but the `GqlTypeGenerator` log emits four
regenerations across two workspaces with custom objects:

- 28 standard → 28 + 5 custom (`rocket, surveyResult, employmentHistory,
petCareAgreement, pet`) — matches Apple
- 28 standard → 28 + 1 custom (`surveyResult`) — matches YCombinator

With `--light` actually applied, `getLightConfig` returns `{ objects:
[], fields: [] }` so no custom objects should be generated.

The working `twenty-app-dev` invocation in
`packages/twenty-docker/twenty-app-dev/rootfs/etc/s6-overlay/scripts/init-db.sh:66`
is `yarn command:prod workspace:seed:dev --light` — no `--`. Matching
that fixes it.

## Test plan

- [ ] Trigger the preview-app label on a PR, confirm only the Apple
workspace is created and `tim@apple.dev` signs in there
- [ ] Confirm the seed step still passes

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-12 10:50:08 +02:00
Félix Malfait
8d54ff6ca0 fix(ci): probe real schema in breaking-changes server readiness check (#20465)
## Summary

The `GraphQL and OpenAPI Breaking Changes Detection` workflow has been
posting graphql-inspector stack traces as PR comments — see [#20445
comment](https://github.com/twentyhq/twenty/pull/20445#issuecomment-4421142635)
for an example.

### Root cause

- The wait step probed readiness with `curl -s URL > /dev/null 2>&1`,
which exits 0 for **any** HTTP response — including 5xx and GraphQL
error JSON. NestJS opens the HTTP listener before the workspace schema
cache is fully populated, so the wait often completed while the server
still served auth/metadata error JSON.
- The introspection download therefore wrote a small (~154-byte) error
payload instead of the real schema. `jq empty` in the validation step
only checks JSON *syntax*, so `{"errors":[...]}` passed validation.
- `graphql-inspector diff` then failed with `Unable to read JSON file:
... Not valid JSON content`, the workflow swallowed the error into the
diff markdown, and the bot posted that stack trace verbatim on the PR.

In the failing run, the main-branch files were 154 B (GraphQL) and 112 B
(REST 500); the current-branch files in the same run were 600 KB–2.8 MB.

### Fix

- Wait steps now POST an authenticated introspection (`{ __schema {
queryType { name } } }`) and require `.data.__schema` plus a 2xx
response from `/rest/open-api/core` (`curl -f`) before declaring the
server ready.
- Validation step now checks for the expected shape (`.data.__schema`
for GraphQL, `.openapi`/`.swagger` for OpenAPI) and includes the first
200 bytes of any bad payload in the warning, so when something genuinely
goes wrong the next debugger has a real lead instead of a generic stack
trace.

## Test plan

- [ ] CI runs against this branch — the workflow's own readiness probes
are now exercised against the real server, so a green run validates the
new check.
- [ ] If the readiness probe still passes but downloads regress, the
strengthened validation step will surface the payload in the workflow
logs instead of posting a graphql-inspector stack trace on the PR.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-05-12 06:43:35 +00:00
Félix Malfait
009f597eec ci(preview-env): use --light seed so Apple is the default workspace (#20464)
## Summary

- Pass `--light` to `workspace:seed:dev` in the preview env keepalive
workflow so only the Apple workspace is created
- Avoids `Empty4` being picked as the default workspace at sign-in
(which has no users), making the prefilled `tim@apple.dev` credentials
land on a useful workspace

## Why

`workspace:seed:dev` (no flag) seeds Apple + YCombinator + Empty3 +
Empty4. Preview envs run in single-workspace mode
(`IS_MULTIWORKSPACE_ENABLED=false`), so
[`WorkspaceDomainsService.getDefaultWorkspace`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-server/src/engine/core-modules/domain/workspace-domains/services/workspace-domains.service.ts)
returns the most recently created workspace — Empty4 — which has no
users. Users hitting the preview URL therefore see "Welcome, Empty4."
and can't sign in. Same failure mode #19822 fixed for `twenty-app-dev`.

## Test plan

- [ ] Trigger the `preview-app` label on a PR and confirm the preview
URL signs in to the Apple workspace, not Empty4
- [ ] Confirm the seed step still passes (no `Empty3`/`Empty4`
references break it)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-11 22:46:54 +02:00
Félix Malfait
3d8207af0f ci(preview-env): replace bore.pub with Cloudflare quick tunnel (#20459)
## Summary

`bore.pub`'s public server has been increasingly unreliable: tunnels
register fine on the runner side (our `Create Tunnel` step always
succeeds), but the bore.pub side later stops accepting inbound traffic,
leaving the preview environment unreachable for the rest of the 5h
keep-alive window with no signal back to the runner. Recent symptom:
`curl http://bore.pub:50422` → `Couldn't connect to server`, while the
corresponding action keeps sleeping.

This PR replaces the `codetalkio/expose-tunnel` action with a direct
invocation of `cloudflared` running an account-less [Cloudflare quick
tunnel](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/do-more-with-tunnels/trycloudflare/).
The tunnel is served from Cloudflare's edge so reliability is materially
better, and the URL is HTTPS by default (`https://*.trycloudflare.com`),
which also eliminates the mixed-content issues we'd hit when
`SERVER_URL` was `http://bore.pub:port`.

## What changes

- `Create Tunnel` step now:
  - Downloads a pinned `cloudflared` binary (`2026.3.0`)
- Starts `cloudflared tunnel --url http://localhost:3000` in the
background, logging to `$RUNNER_TEMP/cloudflared.log`
- Polls the log for `https://<name>.trycloudflare.com` (up to 2
minutes), failing fast if the process exits
- Writes the URL to the `tunnel-url` step output — same name as before,
so no downstream changes needed
- `Cleanup` step kills the `cloudflared` process for hygiene

## What stays the same

- `SERVER_URL` plumbing through `.env` → `docker compose up`
- `tunnel-url` artifact
- `$GITHUB_STEP_SUMMARY` formatting
- PR-comment dispatch (`twentyhq/ci-privileged`)
- 5h keep-alive sleep

## Trade-offs

- Quick tunnels are explicitly labelled by Cloudflare for
"testing/development" use without an SLA. For our preview-env use case
(ephemeral, per-PR) that fits, but if we ever need stable URLs on a
custom domain we'd move to *named* tunnels — same `cloudflared` binary,
plus a free Cloudflare account + delegated domain + a service token
stored as a repo secret. Strictly additive when we want it.
- `cloudflared` is pinned to `2026.3.0` to avoid surprise breakage from
upstream releases. Bumping is a one-line change.

## Testing

**Locally (macOS) — verified end-to-end:**
- `cloudflared tunnel --url http://localhost:18080` against a `python3
-m http.server`
- Regex `https://[a-zA-Z0-9-]+\.trycloudflare\.com` correctly extracts
the URL from the log
- `curl $URL/` returns the upstream server's response (HTTP 200, ~0.5s)
- Process supervision: if `cloudflared` dies mid-wait, the step fails
fast instead of hitting the 2-min timeout

**Validation:**
- `actionlint` passes (the remaining shellcheck warnings are in
pre-existing steps, not my changes)
- `shellcheck` on the new Create Tunnel script: clean

**What's not testable from a PR (and why):**
- The full keep-alive workflow runs on `repository_dispatch`, which
always uses the workflow file from `main`. So the cloudflared logic only
runs against PR contents *after* merge.
- I'll trigger a one-off Ubuntu-runner test of just the install + URL
extraction logic via a throwaway branch (`workflow_dispatch`-only) and
link the run here before this merges.

## Test plan

- [ ] Throwaway run validates: cloudflared installs on `ubuntu-latest`,
prints the URL, regex matches, tunnel is reachable from outside the
runner.
- [ ] After merge, the next PR's preview environment uses
`*.trycloudflare.com` instead of `bore.pub:port`, and the URL stays
reachable for the full 5h window.
- [ ] PR-comment bot still posts the preview URL correctly (link should
now be `https://*.trycloudflare.com`).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-11 21:39:22 +02:00
Paul Rastoin
7f4f2e932c Simplify dispatch pr review (#20397)
# Introduction
Sending minimal information for required metadata to be fetched
afterwards
2026-05-08 12:53:59 +02:00
martmull
e294d74e07 Detail steps during create twenty app (#20374)
## Before
<img width="391" height="188" alt="image"
src="https://github.com/user-attachments/assets/78a0139f-d992-49cb-98ff-531bdbadc64b"
/>

## After
<img width="419" height="773" alt="image"
src="https://github.com/user-attachments/assets/da9b36a4-74e0-4445-b8c5-7a2329eb4f46"
/>
2026-05-07 20:51:24 +00:00
Paul Rastoin
ca58c7f15e Fix auto draft workflow (#20357)
# Introduction
Cannot use github graphql mutation with a `GITHUB_TOKEN` needs a
authenticated one
Dispatching to ci-priv to do so
2026-05-07 14:57:17 +00:00
Paul Rastoin
a3224880e0 External contributor auto-draft and dispatch pr-review event type (#20329)
# Introduction

## Auto draft
On external contributor PR creation auto draft it and comment stating
that it needs to be marked as ready for review when it is

## Caveats / future improvement
Lets iterate first but we can imagine future pain points such as:
- Cubic only runs on ready for review PRs
- Expected green ci before turning ready to be review ? ( we could
invoke cubic ourselves )\
- Auto close external contributors draft PR after x duration

## Auto review
Once a PR started to be review or is being synchronized then auto
dispatch auto review
2026-05-07 13:28:43 +00:00
Charles Bochet
c983ac9f82 ci: add ci-website workflow for twenty-website-new (#20281)
## Summary
- Recreates the `ci-website.yaml` workflow that was removed alongside
`twenty-website` in #20270, now scoped to `twenty-website-new`.
- Replaces the old build-only job with a `[lint, typecheck, test]`
matrix run via `./.github/actions/nx-affected` on `tag:scope:website` —
same idiom used by `ci-shared.yaml`.
- Path filter watches `packages/twenty-website-new/**` and
`packages/twenty-shared/**` (since website-new depends on
`twenty-shared`), plus `package.json` / `yarn.lock`.

## Test plan
- [ ] CI Website workflow appears on this PR and the `lint`,
`typecheck`, `test` matrix jobs all pass
- [ ] `ci-website-status-check` is green
2026-05-05 14:54:11 +02:00
Abdullah.
59107b5b23 Remove twenty-website package. (#20270) 2026-05-05 12:45:02 +02:00
Abdullah.
d2cfbf319b [Website] Implement translations. (#20171)
As title.

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-04 10:41:46 +00:00
dependabot[bot]
5bdbfe651e chore(deps): bump postal-mime from 2.6.1 to 2.7.4 (#20150)
Bumps [postal-mime](https://github.com/postalsys/postal-mime) from 2.6.1
to 2.7.4.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/postalsys/postal-mime/releases">postal-mime's
releases</a>.</em></p>
<blockquote>
<h2>v2.7.4</h2>
<h2><a
href="https://github.com/postalsys/postal-mime/compare/v2.7.3...v2.7.4">2.7.4</a>
(2026-03-17)</h2>
<h3>Bug Fixes</h3>
<ul>
<li>add missing originalKey to Header type and Uint8Array to Attachment
content (<a
href="92cc91c1c8">92cc91c</a>)</li>
<li>include originalKey in parsed headers output (<a
href="83521c87f6">83521c8</a>)</li>
<li>preserve __esModule and .default in CJS build for bundler interop
(<a
href="1466910e31">1466910</a>)</li>
<li>prevent RFC 2047 encoded-word address fabrication (<a
href="844f92023d">844f920</a>)</li>
</ul>
<h2>v2.7.3</h2>
<h2><a
href="https://github.com/postalsys/postal-mime/compare/v2.7.2...v2.7.3">2.7.3</a>
(2026-01-09)</h2>
<h3>Bug Fixes</h3>
<ul>
<li>correct TypeScript type definitions to match implementation (<a
href="b225d7cca4">b225d7c</a>)</li>
</ul>
<h2>v2.7.2</h2>
<h2><a
href="https://github.com/postalsys/postal-mime/compare/v2.7.1...v2.7.2">2.7.2</a>
(2026-01-08)</h2>
<h3>Bug Fixes</h3>
<ul>
<li>add null checks for contentType.parsed access (<a
href="ad8f4c62e0">ad8f4c6</a>)</li>
<li>improve RFC compliance for MIME parsing (<a
href="e004c3acb2">e004c3a</a>)</li>
</ul>
<h2>v2.7.1</h2>
<h2><a
href="https://github.com/postalsys/postal-mime/compare/v2.7.0...v2.7.1">2.7.1</a>
(2025-12-22)</h2>
<h3>Bug Fixes</h3>
<ul>
<li>Add null checks for contentDisposition.parsed access (<a
href="fd54c37093">fd54c37</a>)</li>
</ul>
<h2>v2.7.0</h2>
<h2><a
href="https://github.com/postalsys/postal-mime/compare/v2.6.1...v2.7.0">2.7.0</a>
(2025-12-22)</h2>
<h3>Features</h3>
<ul>
<li>add headerLines property exposing raw header lines (<a
href="c79a02ab05">c79a02a</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/postalsys/postal-mime/blob/master/CHANGELOG.md">postal-mime's
changelog</a>.</em></p>
<blockquote>
<h2><a
href="https://github.com/postalsys/postal-mime/compare/v2.7.3...v2.7.4">2.7.4</a>
(2026-03-17)</h2>
<h3>Bug Fixes</h3>
<ul>
<li>add missing originalKey to Header type and Uint8Array to Attachment
content (<a
href="92cc91c1c8">92cc91c</a>)</li>
<li>include originalKey in parsed headers output (<a
href="83521c87f6">83521c8</a>)</li>
<li>preserve __esModule and .default in CJS build for bundler interop
(<a
href="1466910e31">1466910</a>)</li>
<li>prevent RFC 2047 encoded-word address fabrication (<a
href="844f92023d">844f920</a>)</li>
</ul>
<h2><a
href="https://github.com/postalsys/postal-mime/compare/v2.7.2...v2.7.3">2.7.3</a>
(2026-01-09)</h2>
<h3>Bug Fixes</h3>
<ul>
<li>correct TypeScript type definitions to match implementation (<a
href="b225d7cca4">b225d7c</a>)</li>
</ul>
<h2><a
href="https://github.com/postalsys/postal-mime/compare/v2.7.1...v2.7.2">2.7.2</a>
(2026-01-08)</h2>
<h3>Bug Fixes</h3>
<ul>
<li>add null checks for contentType.parsed access (<a
href="ad8f4c62e0">ad8f4c6</a>)</li>
<li>improve RFC compliance for MIME parsing (<a
href="e004c3acb2">e004c3a</a>)</li>
</ul>
<h2><a
href="https://github.com/postalsys/postal-mime/compare/v2.7.0...v2.7.1">2.7.1</a>
(2025-12-22)</h2>
<h3>Bug Fixes</h3>
<ul>
<li>Add null checks for contentDisposition.parsed access (<a
href="fd54c37093">fd54c37</a>)</li>
</ul>
<h2><a
href="https://github.com/postalsys/postal-mime/compare/v2.6.1...v2.7.0">2.7.0</a>
(2025-12-22)</h2>
<h3>Features</h3>
<ul>
<li>add headerLines property exposing raw header lines (<a
href="c79a02ab05">c79a02a</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="178f1ef0b1"><code>178f1ef</code></a>
chore(master): release 2.7.4 (<a
href="https://redirect.github.com/postalsys/postal-mime/issues/88">#88</a>)</li>
<li><a
href="1f7ba618d4"><code>1f7ba61</code></a>
chore: bump devDependencies</li>
<li><a
href="83521c87f6"><code>83521c8</code></a>
fix: include originalKey in parsed headers output</li>
<li><a
href="b0d7b11550"><code>b0d7b11</code></a>
test: improve test coverage across codebase</li>
<li><a
href="ebc5ce6196"><code>ebc5ce6</code></a>
refactor: simplify and clean up codebase</li>
<li><a
href="1466910e31"><code>1466910</code></a>
fix: preserve __esModule and .default in CJS build for bundler
interop</li>
<li><a
href="844f92023d"><code>844f920</code></a>
fix: prevent RFC 2047 encoded-word address fabrication</li>
<li><a
href="24dc6c64df"><code>24dc6c6</code></a>
test: update type check test with originalKey property</li>
<li><a
href="92cc91c1c8"><code>92cc91c</code></a>
fix: add missing originalKey to Header type and Uint8Array to Attachment
content</li>
<li><a
href="aa5baeafa6"><code>aa5baea</code></a>
docs: add link to full documentation site</li>
<li>Additional commits viewable in <a
href="https://github.com/postalsys/postal-mime/compare/v2.6.1...v2.7.4">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=postal-mime&package-manager=npm_and_yarn&previous-version=2.6.1&new-version=2.7.4)](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: Charles Bochet <charles@twenty.com>
2026-04-30 11:38:44 +00:00
Abdullah.
a90895e167 [Website] locale-segment routing and shared Lingui factory (#20079)
**1. Shared Lingui factory in `twenty-shared`**

- Extracted `createI18nInstanceFactory` into
`packages/twenty-shared/src/i18n/create-i18n-instance-factory.ts` so
every package gets the same per-render Lingui bootstrap with a
per-locale singleton cache and a `SOURCE_LOCALE` fallback.
- `twenty-emails/src/utils/i18n.utils.ts` now consumes the shared
factory.

**2. `twenty-website-new` Lingui bootstrap + Crowdin wiring**

- `lingui.config.ts`, `src/lib/i18n/*`, `nx run
twenty-website-new:lingui:{extract,compile}`.
- 31 locale PO files generated; minified compiled output kept out of
Prettier and Oxlint.
- `i18n-{push,pull}.yaml` workflows updated to include
`twenty-website-new` in Crowdin sync.

**3. `app/[locale]/...` segment routing with English at the root**

- All marketing routes moved under `src/app/[locale]/`; static
generation preserved (15 routes × 31 locales = 465 prerendered URLs).
- Middleware behavior:
  - `/{en}/...`         → 301 redirect to unprefixed canonical.
  - `/{non-en}/...`     → pass through, set `NEXT_LOCALE` cookie.

### What this PR explicitly does not do (deferred)

- Lingui-wrapping the actual marketing copy. Keys, build pipeline, and
  runtime are wired; copy migration is a separate, reviewer-friendlier
  PR.
2026-04-27 12:11:30 +00:00
Paul Rastoin
1e7c1691f4 [CI] Prevent previous version upgrade sequence mutation (#20075)
# Introduction
Prevent any PR to target a previous already released twenty version by
mistake.

Especially useful for existing opened PR introducing commands into an
upgrade that has just been released leading to a
`TWENTY_CURRENT_VERSION` bump

<img width="3150" height="1158" alt="image"
src="https://github.com/user-attachments/assets/b83d211f-a061-4d63-ae7a-354d7851ec08"
/>

## Bypass
If intentional add `ci:allow-previous-version-upgrade-mutation` label to
the PR and re-run the failed job

<img width="3150" height="1158" alt="image"
src="https://github.com/user-attachments/assets/f94ee630-d87b-4477-9e50-bf6773a8a280"
/>

This will require a brand new ci from a commit introduced after the
label has been added
2026-04-27 11:15:49 +00:00
Paul Rastoin
66857ca77b Remove cross version upgrade placeholder (#19940)
Moving to somewhere else to leverage docker cache
2026-04-21 16:16:47 +00:00
Paul Rastoin
65e01400c0 Cross version ci placeholder (#19932)
Created this empty workflow so it appears on main and is pickable from a
different branch to start testing the whole flow
2026-04-21 14:19:28 +02:00
Félix Malfait
75848ff8ea feat: move admin panel to dedicated /admin-panel GraphQL endpoint (#19852)
## Summary

Splits admin-panel resolvers off the shared `/metadata` GraphQL endpoint
onto a dedicated `/admin-panel` endpoint. The backend plumbing mirrors
the existing `metadata` / `core` pattern (new scope, decorator, module,
factory), and admin types now live in their own
`generated-admin/graphql.ts` on the frontend — dropping 877 lines of
admin noise from `generated-metadata`.

## Why

- **Smaller attack surface on `/metadata`** — every authenticated user
hits that endpoint; admin ops don't belong there.
- **Independent complexity limits and monitoring** per endpoint.
- **Cleaner module boundaries** — admin is a cross-cutting concern that
doesn't match the "shared-schema configuration" meaning of `/metadata`.
- **Deploy / blast-radius isolation** — a broken admin query can't
affect `/metadata`.

Runtime behavior, auth, and authorization are unchanged — this is a
relocation, not a re-permissioning. All existing guards
(`WorkspaceAuthGuard`, `UserAuthGuard`,
`SettingsPermissionGuard(SECURITY)` at class level; `AdminPanelGuard` /
`ServerLevelImpersonateGuard` at method level) remain on
`AdminPanelResolver`.

## What changed

### Backend
- `@AdminResolver()` decorator with scope `'admin'`, naming parallels
`CoreResolver` / `MetadataResolver`.
- `AdminPanelGraphQLApiModule` + `adminPanelModuleFactory` registered at
`/admin-panel`, same Yoga hook set as the metadata factory (Sentry
tracing, error handler, introspection-disabling in prod, complexity
validation).
- Middleware chain on `/admin-panel` is identical to `/metadata`.
- `@nestjs/graphql` patch extended: `resolverSchemaScope?: 'core' |
'metadata' | 'admin'`.
- `AdminPanelResolver` class decorator swapped from
`@MetadataResolver()` to `@AdminResolver()` — no other changes.

### Frontend
- `codegen-admin.cjs` → `src/generated-admin/graphql.ts` (982 lines).
- `codegen-metadata.cjs` excludes admin paths; metadata file shrinks by
877 lines.
- `ApolloAdminProvider` / `useApolloAdminClient` follow the existing
`ApolloCoreProvider` / `useApolloCoreClient` pattern, wired inside
`AppRouterProviders` alongside the core provider.
- 37 admin consumer files migrated: imports switched to
`~/generated-admin/graphql` and `client: useApolloAdminClient()` is
passed to `useQuery` / `useMutation`.
- Three files intentionally kept on `generated-metadata` because they
consume non-admin Documents: `useHandleImpersonate.ts`,
`SettingsAdminApplicationRegistrationDangerZone.tsx`,
`SettingsAdminApplicationRegistrationGeneralToggles.tsx`.

### CI
- `ci-server.yaml` runs all three `graphql:generate` configurations and
diff-checks all three generated dirs.

## Authorization (unchanged, but audited while reviewing)

Every one of the 38 methods on `AdminPanelResolver` has a method-level
guard:
- `AdminPanelGuard` (32 methods) — requires `canAccessFullAdminPanel ===
true`
- `ServerLevelImpersonateGuard` (6 methods: user/workspace lookup + chat
thread views) — requires `canImpersonate === true`

On top of the class-level guards above. No resolver method is accessible
without these flags + `SECURITY` permission in the workspace.

## Test plan

- [ ] Dev server boots; `/graphql`, `/metadata`, `/admin-panel` all
mapped as separate GraphQL routes (confirmed locally during
development).
- [ ] `nx typecheck twenty-server` passes.
- [ ] `nx typecheck twenty-front` passes.
- [ ] `nx lint:diff-with-main twenty-server` and `twenty-front` both
clean.
- [ ] Manual smoke test: log in with a user who has
`canAccessFullAdminPanel=true`, open the admin panel at
`/settings/admin-panel`, verify each tab loads (General, Health, Config
variables, AI, Apps, Workspace details, User details, chat threads).
- [ ] Manual smoke test: log in with a user who has
`canImpersonate=false` and `canAccessFullAdminPanel=false`, hit
`/admin-panel` directly with a raw GraphQL request, confirm permission
error on every operation.
- [ ] Production deploy note: reverse proxy / ingress must route the new
`/admin-panel` path to the Nest server. If the proxy has an explicit
allowlist, infra change required before cutover.

## Follow-ups (out of scope here)

- Consider cutting over the three
`SettingsAdminApplicationRegistration*` components to admin-scope
versions of the app-registration operations so the admin page is fully
on the admin endpoint.
- The `renderGraphiQL` double-assignment in
`admin-panel.module-factory.ts` is copied from
`metadata.module-factory.ts` — worth cleaning up in both.
2026-04-19 20:55:10 +02:00
Félix Malfait
69d228d8a1 Deprecate IS_RECORD_TABLE_WIDGET_ENABLED feature flag (#19662)
## Summary
- Removes the `IS_RECORD_TABLE_WIDGET_ENABLED` feature flag, making the
record table widget unconditionally available in dashboard widget type
selection
- The flag was already seeded as `true` for all new workspaces and only
gated UI visibility in one component
(`SidePanelPageLayoutDashboardWidgetTypeSelect`)
- Cleans up the flag from `FeatureFlagKey` enum, dev seeder, and test
mocks

## Analysis
The flag only controlled whether the "View" (Record Table) widget option
appeared in the dashboard widget type selector. The entire record table
widget infrastructure (rendering, creation hooks, GraphQL types,
`RECORD_TABLE` enum in `WidgetType`) is independent of the flag and
fully implemented. No backend logic depends on this flag.

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Charles Bochet <charles@twenty.com>
2026-04-13 21:13:15 +00:00
Charles Bochet
b37ef3e7da Add app-path input to deploy and install composite actions (#19589)
Supports monorepo layouts where the app isn't at the repo root. Defaults
to '.' for backward compatibility.

Made-with: Cursor
2026-04-11 17:45:42 +02:00
Charles Bochet
baf2fc4cc9 Fix sdk-e2e-test: ensure DB is ready before server starts (#19583)
## Summary

- The `sdk-e2e-test` CI job has been failing since at least April 9th
because the nx dependency graph runs `database:reset` and
`start:ci-if-needed` in **parallel** — neither depends on the other. The
server starts before the DB tables are created, crashes with `relation
"core.keyValuePair" does not exist`, and `wait-on` times out after 10
minutes.
- Replace the `nx-affected` orchestration for e2e with explicit
**sequential** CI steps: build → create DB → reset DB → start server →
wait for health → run tests.
- Add server log dump on failure for easier future debugging.

## Root cause

In `packages/twenty-sdk/project.json`, the `test:e2e` target has:

```json
"dependsOn": [
  "build",
  { "target": "database:reset", "projects": "twenty-server" },
  { "target": "start:ci-if-needed", "projects": "twenty-server" }
]
```

Since `database:reset` and `start:ci-if-needed` don't depend on each
other, nx can (and does) run them concurrently. `start:ci-if-needed`
fires `nohup nest start &` and immediately completes. The server process
tries to query `core.keyValuePair` before `database:reset` creates it →
crash → wait-on timeout → job failure.
2026-04-11 16:02:12 +02:00
Charles Bochet
c26c0b9d71 Use app's own OAuth credentials for CoreApiClient generation (#19563)
## Summary

- **SDK (`dev` & `dev --once`)**: After app registration, the CLI now
obtains an `APPLICATION_ACCESS` token via `client_credentials` grant
using the app's own `clientId`/`clientSecret`, and uses that token for
CoreApiClient schema introspection — instead of the user's
`config.accessToken` which returns the full unscoped schema.
- **Config**: `oauthClientSecret` is now persisted alongside
`oauthClientId` in `~/.twenty/config.json` when creating a new app
registration, so subsequent `dev`/`dev --once` runs can obtain fresh app
tokens without re-registration.
- **CI action**: `spawn-twenty-app-dev-test` now outputs a proper
`API_KEY` JWT (signed with the seeded dev workspace secret) instead of
the previous hardcoded `ACCESS` token — giving consumers a real API key
rather than a user session token.

## Motivation

When developing Twenty apps, `yarn twenty dev` was using the CLI user's
OAuth token for GraphQL schema introspection during CoreApiClient
generation. This token (type `ACCESS`) has no `applicationId` claim, so
the server returns the **full workspace schema** — including all objects
— rather than the scoped schema the app should see at runtime (filtered
by `applicationId`).

This caused a discrepancy: the generated CoreApiClient contained fields
the app couldn't actually query at runtime with its `APPLICATION_ACCESS`
token.

By switching to `client_credentials` grant, the SDK now introspects with
the same token type the app will use in production, ensuring the
generated client accurately reflects the app's runtime capabilities.
2026-04-11 11:24:28 +02:00
Paul Rastoin
c99db7d9c6 Fix server-validation ci pending instance command detection (#19558)
https://github.com/twentyhq/twenty/actions/runs/24246052659/job/70792870185
2026-04-10 13:53:56 +00:00
Charles Bochet
36fbfca069 Add application-logs module with driver pattern for logic function log persistence (#19486)
## Summary

- Introduces a new `application-logs` core module with a driver pattern
(disabled/console/clickhouse) to capture and persist logic function
execution logs
- Adds a ClickHouse `applicationLog` table with per-line log storage,
30-day TTL, and `ORDER BY (workspaceId, timestamp, applicationId,
logicFunctionId)`
- Surfaces application logs in the existing frontend audit logs table as
a new "Application Logs" source with dedicated columns (Function,
Timestamp, Level, Message, Execution ID)

## Details

**Write path**: `LogicFunctionExecutorService.handleExecutionResult()`
parses the multi-line log string from driver output into individual `{
timestamp, level, message }` entries, generates an execution UUID, and
passes them to `ApplicationLogsService.writeLogs()` which delegates to
the configured driver.

**Driver pattern**: Follows the exception-handler module style (Symbol
injection token + `forRootAsync` dynamic module). Three drivers:
- `DISABLED` (default) — no-op, prevents information leaking
- `CONSOLE` — structured stdout logging with level-based `console.*`
calls
- `CLICKHOUSE` — inserts rows into the `applicationLog` ClickHouse table

**Read path**: Extends the existing event-logs module by adding
`APPLICATION_LOG` to the `EventLogTable` enum, table name mapping, and
normalization logic.

**Config**: New `APPLICATION_LOG_DRIVER_TYPE` environment variable
(default: `DISABLED`).
2026-04-09 14:35:24 +00:00
Félix Malfait
9b8cb610c3 Flush Redis between server runs in breaking changes CI (#19491)
## Summary
Added a Redis cache flush step in the breaking changes CI workflow to
prevent stale data contamination between consecutive server runs.

## Key Changes
- Added a new workflow step that executes `redis-cli FLUSHALL` between
the current branch server run and the main branch server run
- Includes error handling to log a warning if the Redis flush fails,
without blocking the workflow
- Added explanatory comments documenting why this step is necessary

## Implementation Details
The Redis flush is necessary because both the current branch and main
branch servers share the same Redis instance during CI testing. The
`CoreEntityCacheService` and `WorkspaceCacheService` persist cached
entities across process restarts, which can cause stale data from the
current branch server to contaminate the main branch server comparison.
This step ensures a clean cache state before switching branches.

https://claude.ai/code/session_01BVMacfAXDMNx5WAFtP7GgW

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-09 12:56:24 +02:00
Charles Bochet
bc7b5aee58 chore: centralize deploy/install CD actions in twentyhq/twenty (#19454)
## Summary

- Adds `deploy-twenty-app` and `install-twenty-app` composite actions to
`.github/actions/` so app repos can reference them remotely — same
pattern as `spawn-twenty-app-dev-test` for CI
- Updates `cd.yml` in template, hello-world, and postcard to use
`twentyhq/twenty/.github/actions/deploy-twenty-app@main` /
`install-twenty-app@main` instead of local `./.github/actions/` copies
- Removes the 6 local action files that were duplicated across template
and example apps

**Before** (each app repo carried its own action copies):
```yaml
uses: ./.github/actions/deploy
```

**After** (centralized, like CI):
```yaml
uses: twentyhq/twenty/.github/actions/deploy-twenty-app@main
```


Made with [Cursor](https://cursor.com)
2026-04-08 15:25:51 +02:00
Charles Bochet
6cd3f2db2b chore: replace spawn-twenty-app-dev-test with native postgres/redis services (#19449)
## Summary

- Replaces the `spawn-twenty-app-dev-test` Docker action with native
GitHub Actions services (`postgres:18` + `redis`) and direct server
startup (`npx nx start:ci twenty-server`)
- Aligns with the pattern already used by `ci-sdk.yaml` for e2e tests
- Removes dependency on the `twenty-app-dev` Docker image for CI

Updated workflows:
- `ci-example-app-postcard`
- `ci-example-app-hello-world`
- `ci-create-app-e2e-minimal`
- `ci-create-app-e2e-hello-world`
- `ci-create-app-e2e-postcard`

Server setup pattern:
1. Postgres 18 + Redis as job services
2. `CREATE DATABASE "test"` via psql
3. `npx nx run twenty-server:database:reset` (migrations + seed)
4. `nohup npx nx start:ci twenty-server &`
5. `npx wait-on http://localhost:3000/healthz`


Made with [Cursor](https://cursor.com)
2026-04-08 13:03:35 +00:00
Charles Bochet
15eb3e7edc feat(sdk): use config file as single source of truth, remove env var fallbacks (#19409)
## Summary

- **Config as source of truth**: `~/.twenty/config.json` is now the
single source of truth for SDK authentication — env var fallbacks have
been removed from the config resolution chain.
- **Test instance support**: `twenty server start --test` spins up a
dedicated Docker instance on port 2021 with its own config
(`config.test.json`), so integration tests don't interfere with the dev
environment.
- **API key auth for marketplace**: Removed `UserAuthGuard` from
`MarketplaceResolver` so API key tokens (workspace-scoped) can call
`installMarketplaceApp`.
- **CI for example apps**: Added monorepo CI workflows for `hello-world`
and `postcard` example apps to catch regressions.
- **Simplified CI**: All `ci-create-app-e2e` and example app workflows
now use a shared `spawn-twenty-app-dev-test` action (Docker-based)
instead of building the server from source. Consolidated auth env vars
to `TWENTY_API_URL` + `TWENTY_API_KEY`.
- **Template publishing fix**: `create-twenty-app` template now
correctly preserves `.github/` and `.gitignore` through npm publish
(stored without leading dot, renamed after copy).

## Test plan

- [x] CI SDK (lint, typecheck, unit, integration, e2e) — all green
- [x] CI Example App Hello World — green
- [x] CI Example App Postcard — green
- [x] CI Create App E2E minimal — green
- [x] CI Front, CI Server, CI Shared — green
2026-04-08 06:49:10 +02:00
Félix Malfait
83d30f8b76 feat: Send email from UI — inline reply composer & SendEmail mutation (#19363)
## Summary

- **Inline email reply**: Replace external email client redirects
(Gmail/Outlook deeplinks) with an in-app email composer. Users can reply
to email threads directly from the email thread widget or via the
command menu.
- **SendEmail GraphQL mutation**: New backend mutation that reuses
`EmailComposerService` for body sanitization, recipient validation, and
SMTP dispatch via the existing outbound messaging infrastructure.
- **Side panel compose page**: Command menu "Reply" action now opens a
side-panel compose email page with pre-filled To, Subject, and
In-Reply-To fields.

### Backend
- `SendEmailResolver` with `SendEmailInput` / `SendEmailOutputDTO`
- `SendEmailModule` wired into `CoreEngineModule`
- Reuses `EmailComposerService` + `MessagingMessageOutboundService`

### Frontend
- `EmailComposer` / `EmailComposerFields` components
- `useSendEmail`, `useReplyContext`, `useEmailComposerState` hooks
- `useOpenComposeEmailInSidePanel` + `SidePanelComposeEmailPage`
- `EmailThreadWidget` inline Reply bar with toggle composer
- `ReplyToEmailThreadCommand` now opens side-panel instead of external
links

### Seeds
- Added `handle` field to message participant seeds for realistic email
addresses
- Seed `connectedAccount` and `messageChannel` in correct batch order

## Test plan

- [ ] Open an email thread on a person/company record → verify
"Reply..." bar appears below the last message
- [ ] Click "Reply..." → composer opens inline with pre-filled To and
Subject
- [ ] Type a message and click Send → email is sent via SMTP, composer
closes
- [ ] Use command menu Reply action → side panel opens with compose
email page
- [ ] Verify Send/Cancel buttons work correctly in side panel
- [ ] Test with Cc/Bcc toggle in composer fields
- [ ] Verify error handling: invalid recipients, missing connected
account


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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-07 08:43:48 +02:00
Paul Rastoin
e062343802 Refactor typeorm migration lifecycle and generation (#19275)
# Introduction

Typeorm migration are now associated to a given twenty-version from the
`UPGRADE_COMMAND_SUPPORTED_VERSIONS` that the current twenty core engine
handles

This way when we upgrade we retrieve the migrations that need to be run,
this will be useful for the cross-version incremental upgrade so we
preserve sequentiality

## What's new

To generate
```sh
npx nx database:migrate:generate twenty-server -- --name add-index-to-users
```

To apply all
```sh
npx nx database:migrate twenty-server
```

## Next
Introduce slow and fast typeorm migration in order to get rid of the
save point pattern in our code base
Create a clean and dedicated `InstanceUpgradeService` abstraction
2026-04-06 07:11:47 +00:00
martmull
119014f86d Improve apps (#19256)
- simplify the base application template
- remove --exhaustive option and replace by a --example option like in
next.js https://nextjs.org/docs/app/api-reference/cli
- Fix some bugs and logs
- add a post-card app in twenty-apps/examples/
2026-04-03 12:44:03 +00:00
Paul Rastoin
331c223e73 Fix create e2e app ci set version flakiness (#19278)
Calling npm version in // with interdependent packages fait des
chocapics

<img width="225" height="225" alt="image"
src="https://github.com/user-attachments/assets/6d10e72a-87dc-4745-b078-7dd1b4706203"
/>
2026-04-02 16:05:02 +00:00