Commit Graph

8 Commits

Author SHA1 Message Date
Paul Rastoin fe442fe5fe chore(apps): bump sdk to 2.19.0-alpha.1 and require twenty server >=2.19.0 (#22601)
## Summary

- Bumps `twenty-sdk` / `twenty-client-sdk` to the exact `2.19.0-alpha.1`
prerelease for the apps under `packages/twenty-apps` that actually
target a mutated standard identifier, and refreshes their lockfiles.
- Declares `"engines": { "twenty": ">=2.19.0" }` in those apps so
pre-2.19 servers refuse to install or upgrade to the rebuilt packages.

Only apps that reference a standard object's **system-field** universal
identifier, define a **relation into** a standard object, or call the
field-UID derivation helper need 2.19 (the identifiers those touch
changed from hardcoded UUIDs to deterministic hashes). Apps that only
define their own custom objects, or add plain scalar fields to a
standard object via its stable object-level id, were left on their prior
SDK pins. Currently bumped: `postcard`, `self-hosting`,
`twenty-partners`, `call-recorder`, `people-data-labs`,
`twenty-last-contact`.

## Context

Follow-up to #22565 (deterministic system field universal identifiers)
and #22599 (SDK prerelease bump).

Packages built with SDK ≤ 2.18 carry legacy system field identifiers and
are rejected by servers running `main`. Rebuilding with the 2.19 SDK
fixes that — but a rebuilt package must not be *upgraded into* by a 2.18
server, since 2.18 has no deterministic-identifier validation and would
diff the changed system field identifiers as a destructive delete +
create (the 2.19 backfill has not run there yet).

The `engines.twenty` constraint closes that gap: `doInstallApplication`
validates it via `validateServerCompatibility` before any mutation, and
this check has shipped since ~2.10, so every 2.18 server enforces it.
Resulting matrix:

- 2.18 fresh install of a rebuilt app: works, converges as a no-op once
the 2.19 backfill runs
- 2.18 upgrade of an existing install: rejected with
`SERVER_VERSION_INCOMPATIBLE` before any mutation
- 2.19 (post-backfill) install/upgrade: syncs cleanly

## Expected CI failures

**The `CI Twenty Apps` integration-test jobs are expected to fail on
this PR** (e.g. `people-data-labs`, `twenty-partners`). This is a
server-version mismatch, not an app bug — lint, typecheck and unit tests
all pass:

- The integration step spawns a real Twenty server from Docker Hub
`twentycrm/twenty-app-dev:latest` and runs `twenty dev` to sync each
app's metadata into it.
- `latest` currently resolves to **v2.18.5** — no `2.19` image is
published to Docker Hub yet.
- These apps now reference 2.19's **deterministic system-field universal
identifiers** (e.g. `company.createdBy`, `opportunity.createdAt`). A
2.18 server still carries the legacy identifiers, so the sync rejects
every 2.19-derived reference with `INVALID_VIEW_DATA` /
`FIELD_METADATA_NOT_FOUND` ("Field metadata not found").
- The failure surfaces as low-level field errors rather than a clean
`SERVER_VERSION_INCOMPATIBLE` because the `engines.twenty` gate
(`validateServerCompatibility`) only runs on the `app:install` / publish
paths — **not** on the `twenty dev` dev-sync path the integration tests
use.

These jobs will go green automatically once `twenty-app-dev:2.19` is
published to Docker Hub (or once CI pins the spawn action's
`twenty-version` to a 2.19 tag).

## Intentionally not included

- App `version` fields are untouched; they'll be bumped at publish time.

## Test plan

- [x] Refresh each bumped app's `yarn.lock` (`2.19.0-alpha.1` is now on
npm)
- [ ] Rebuild one app manifest and verify default field identifiers
match `getFieldUniversalIdentifier`
- [ ] Verify a 2.18 server rejects an upgrade to a rebuilt package with
`SERVER_VERSION_INCOMPATIBLE`
- [ ] Re-run `CI Twenty Apps` integration jobs once a
`twenty-app-dev:2.19` image is available

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

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22601?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-light.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
2026-07-07 13:03:43 +02:00
Paul Rastoin f04db9751f fix(client-sdk): bundle metadata client into a single self-contained file (#22085)
## Problem

A front component that imports `MetadataApiClient` from
`twenty-client-sdk/metadata` crashes at render time:

```
FrontComponent error: Failed to resolve module specifier "./chunk-Dqa2HsxW.mjs".
Invalid relative url or base scheme isn't hierarchical.
```

(hash differs per build). The equivalent component using `CoreApiClient`
from `twenty-client-sdk/core` works fine.

## Root cause

The front-component renderer loads each SDK client as a **single
in-memory blob-URL module** and only rewrites the two bare specifiers it
knows (`twenty-client-sdk/core`, `twenty-client-sdk/metadata`). A
blob-URL module cannot resolve a **relative** `import … from
"./chunk-*.mjs"` (blob URLs aren't hierarchical), and that chunk isn't
served anyway.

Only two entrypoints are externalized by the front-component build
(`FRONT_COMPONENT_EXTERNAL_MODULES`) and thus served as blob modules:
`core` and `metadata`. Everything else (`rest`, `generate`) is bundled
into the component and is unaffected. Of those two:

| client | how `dist/*.mjs` is produced | self-contained? |
|---|---|---|
| **core** | esbuild single-file bundle (`compileGeneratedClient`),
re-run per workspace at server runtime by `replaceCoreClient` |  |
| **metadata** | the shared multi-entry Vite build, which hoists shared
code into a relative `chunk-*.mjs` |  |

The metadata client is built once at package-build time (it is not
workspace-specific) and was shipped straight from the multi-entry Vite
output, keeping the unresolvable relative chunk import.

## Regression trace

This was **not** broken on arrival — it regressed via a transitive
bundler swap:

| Date | Commit | Event |
|---|---|---|
| 2026-05-20 | `a26fe3bb65` | Metadata-client-in-front-components
shipped; `twenty-client-sdk` on **Vite 7 (Rollup)** |
| 2026-06-08 | `d2e7dc0e74` (#21309, *"security: bump vulnerable direct
dependencies"*) | Bumped **Vite 7 → 8**, introducing **Rolldown 1.0.3**
(no rolldown entries in the lockfile before this commit) |

Vite 7 is Rollup-based; Vite 8 uses Rolldown. The breaking artifact is
literally a `\0rolldown/runtime.js` shared chunk — a Rolldown construct
that could not have existed before the bump. So the metadata
front-component path worked from 2026-05-20 until the 2026-06-08
security dependency bump silently changed the bundler and split out the
shared runtime chunk.

## Fix

Build the metadata client as its **own single-entry Vite library**
(`vite.metadata.config.ts`) so its output is a single self-contained
file with no shared chunk. `core` / `rest` / `generate` stay in the main
multi-entry build (`vite.config.ts`); shared config (`isExternal`,
`entryFileNames`) is factored into `vite.shared.ts`. The build pipeline
runs `vite build && vite build -c vite.metadata.config.ts`.

The server picks this up automatically: `SdkClientGenerationService`
ships the pre-built package `dist/` and only regenerates the **core**
client; it never regenerates metadata. No server-side change required.

## Regression guard (e2e)

The postcard example's `card.front-component.tsx` previously used
`CoreApiClient` only, so this metadata-only regression had no e2e
coverage. It now loads and round-trips all three SDK clients (`Core`,
`Metadata`, `Rest`) via an SDK health panel, and the e2e asserts the
blob-served `core` + `metadata` probes reach `ok` — which only happens
if those bundles resolve and function. A future chunk-import regression
in either blob module would crash the component on load and fail the
test.

## Verification

- `npx nx build twenty-client-sdk` succeeds.
- `dist/metadata.mjs` / `dist/metadata.cjs`: **0** `chunk-*` imports,
**0** relative imports; both load and export `MetadataApiClient` +
`MetadataSchema`.
- `dist/metadata/index.d.ts` types still emitted.
- `npx nx typecheck` + `npx nx lint twenty-client-sdk` pass; postcard
app typecheck + lint pass.

## Notes

- `dist/` is not committed (CI builds it); a running server must rebuild
`twenty-client-sdk` for the fix to take effect.
- The e2e was validated statically (typecheck + lint); running it
end-to-end requires a live stack with a seeded postcard record.
2026-06-25 10:19:18 +02:00
Paul Rastoin e7488deb58 Remove twenty-shared import from postcard app (#21786)
<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/21786?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
2026-06-18 14:48:31 +02:00
Paul Rastoin d1ba63d4a4 Postcard app e2e front component rendering test (#21600)
# Introduction
Creating a playwright test, quite granular and verbose that will verify
that the post card preview front component is rendered as expected on
the tested twenty instance

This covers everything e2e from twenty front, front comp renderer,
assets cdn rendered redirection etc
Style bridge etc

## Note

The playwright test setup assumes the application has already been
installed once, it's mainly used by the merge queue as a high level
front component and logic function ( will be in the same ci ) regression
bottleneck
The goal isn't for this test to be run locally
2026-06-15 13:02:19 +00:00
martmull 617f571400 20215 convert application variable to a syncable entity (#20269)
##  Summary

- Converts applicationVariable from a bespoke sync path to a proper
SyncableEntity,
unifying it with the workspace migration pipeline used by all other
manifest-managed
  entities (agent, skill, frontComponent, webhook, etc.)
- Removes the upsertManyApplicationVariableEntities method and its
direct-DB-mutation
approach in favor of the standard validate → build → run action handler
pipeline
- Adds universalIdentifier, deletedAt columns and makes applicationId
NOT NULL via an
  instance command migration

##  Motivation

Before this change, applicationVariable was the only manifest-managed
entity that bypassed
ApplicationManifestMigrationService.syncMetadataFromManifest(). It used
a bespoke service
method called directly from syncApplication(), creating two mental
models, two validation
styles, and two cache invalidation patterns. Now there's one unified
pipeline for all
  manifest entities.

##  What changed

###  Entity refactor:
- ApplicationVariableEntity now extends SyncableEntity (gains
universalIdentifier,
  non-nullable applicationId with CASCADE, soft-delete via deletedAt)

###  New flat entity layer (flat-application-variable/):
- Type, maps type, editable properties constant, entity-to-flat
converter, cache service,
  module

###  New migration pipeline wiring:
- Manifest converter
(fromApplicationVariableManifestToUniversalFlatApplicationVariable)
  - Validator service (FlatApplicationVariableValidatorService)
- Builder service
(WorkspaceMigrationApplicationVariableActionsBuilderService)
  - Create/Update/Delete action handlers with secret encryption hooks
- Registered in orchestrator, builder module, runner module, and all
type registries

###  Removed bespoke path:
- Deleted upsertManyApplicationVariableEntities from
ApplicationVariableEntityService
  - Removed its call from ApplicationSyncService.syncApplication()
- Kept update() (operator-set value at runtime) and getDisplayValue()
(runtime display)

###  Database migration:
- Instance command to add columns, backfill universalIdentifier, enforce
NOT NULL
  constraints, and update indexes

##  Test plan

  - npx nx typecheck twenty-server passes (0 errors)
- Unit tests pass (application-variable.service.spec.ts,
build-env-var.spec.ts)
- Install an app with applicationVariables in its manifest → variables
appear with correct
  universalIdentifier
- Update app manifest (add/remove/modify a variable) → migration
pipeline handles diff
  correctly
- Operator-set value via update endpoint persists correctly with
encryption
  - Uninstall app → variables cascade-deleted
  - app dev --once on example app syncs without errors
2026-05-06 08:23:53 +00:00
Paul Rastoin 820f97f53d [Headless Front component] Support multiple selected record (#20268)
# Introduction

Support multiple selected record ids for headless front components

### Changes

**Added:**
- `recordIds: string[]` field to `FrontComponentExecutionContext`
- `useRecordIds()` hook to get all selected record IDs

**Deprecated:**
- `recordId` field - use `recordIds` instead
- `useRecordId()` hook - use `useRecordIds()` instead

Backward compatibility is preserved
2026-05-05 14:53:22 +02:00
Charles Bochet eb1ca1b9ec perf(sdk): split twenty-sdk barrel into per-purpose subpaths to cut logic-function bundle ~700x (#19834)
## Summary

Logic-function bundles produced by the twenty-sdk CLI were ~1.18 MB even
for a one-line handler. Root cause: the SDK shipped as a single bundled
barrel (`twenty-sdk` → `dist/index.mjs`) that co-mingled server-side
definition factories with the front-component runtime, validation (zod),
and React. With no `\"sideEffects\"` declaration on the SDK package,
esbuild had to assume every module-level statement could have side
effects and refused to drop unused code.

This PR restructures the SDK so consumers' bundlers can tree-shake at
the leaf level:

- **Reorganized SDK source.** All server-side definition factories now
  live under `src/sdk/define/` (agents, application, fields,
  logic-functions, objects, page-layouts, roles, skills, views,
  navigation-menu-items, etc.). All front-component runtime
  (components, hooks, host APIs, command primitives) lives under
  `src/sdk/front-component/`. The legacy bare `src/sdk/index.ts` is
  removed; the bare `twenty-sdk` entry no longer exists.

- **Split the build configs by purpose / runtime env.** Replaced
  `vite.config.sdk.ts` with two purpose-specific configs:
  - `vite.config.define.ts` — node target, externals from package
    `dependencies`, emits to `dist/define/**`
  - `vite.config.front-component.ts` — browser/React target, emits to
    `dist/front-component/**`
  Both use `preserveModules: true` so each leaf ships as its own `.mjs`.

- **\`\"sideEffects\": false\`** on `twenty-sdk` so esbuild can drop
  unreferenced re-exports.

- **\`package.json\` exports + \`typesVersions\`** updated: dropped the
bare \`.\` entry, added \`./front-component\`, and pointed \`./define\`
  at the new per-module dist layout.

- **Migrated every internal/example/community app** to the new subpath
  imports (`twenty-sdk/define`, `twenty-sdk/front-component`,
  `twenty-sdk/ui`).

- **Added \`bundle-investigation\` internal app** that reproduces the
  bundle bloat and demonstrates the fix.

- Cleaned up dead \`twenty-sdk/dist/sdk/...\` references in the
  front-component story builder, the call-recording app, and the SDK
  tsconfig.

## Bundle size impact

Measured with esbuild using the same options as the SDK CLI
(\`packages/twenty-apps/internal/bundle-investigation\`):

| Variant | Imports | Before | After |
| ----------------------- |
------------------------------------------------------- | ---------- |
--------- |
| \`01-bare\` | \`defineLogicFunction\` from \`twenty-sdk/define\` |
1177 KB | **1.6 KB** |
| \`02-with-sdk-client\` | + \`CoreApiClient\` from
\`twenty-client-sdk/core\` | 1177 KB | **1.9 KB** |
| \`03-fetch-issues\` | + GitHub GraphQL fetch + JWT signing + 2
mutations | 1181 KB | **5.8 KB** |
| \`05-via-define-subpath\` | same as \`01\`, via the public subpath |
1177 KB | **1.7 KB** |

That's a ~735× reduction on the bare baseline. Knock-on benefits for
Lambda warm + cold starts, S3 upload size, and \`/tmp\` disk usage in
warm containers.

## Test plan

- [x] \`npx nx run twenty-sdk:build\` succeeds
- [x] \`npx nx run twenty-sdk:typecheck\` passes
- [x] \`npx nx run twenty-sdk:test:unit\` passes (31 files / 257 tests)
- [x] \`npx nx run-many -t typecheck
--projects=twenty-front,twenty-server,twenty-front-component-renderer,twenty-sdk,twenty-shared,bundle-investigation\`
passes
- [x] \`node
packages/twenty-apps/internal/bundle-investigation/scripts/build-variants.mjs\`
produces the sizes above
- [ ] CI green

Made with [Cursor](https://cursor.com)
2026-04-18 19:38:34 +02: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