Compare commits

..
Author SHA1 Message Date
Félix MalfaitandGitHub aac099f2e8 Merge branch 'main' into feat/seed-apps-for-dev-workspaces 2026-03-12 08:55:50 +01:00
f262437da6 Refactor dev environment setup with auto-detection and Docker support (#18564)
## Summary
Completely rewrites the development environment setup script to be more
robust, idempotent, and flexible. The new implementation auto-detects
available services (local PostgreSQL/Redis vs Docker), provides multiple
operational modes, and includes comprehensive health checks and error
handling.

## Key Changes

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

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

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

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

## Implementation Details

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

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

https://claude.ai/code/session_01UDxa2Kp1ub9tTL3pnpBVFs

---------

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


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

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

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

---

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

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


</details>

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


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

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

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

---

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

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


</details>

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


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

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

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

---

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

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


</details>

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

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

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


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

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

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

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

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

### The fix

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

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

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

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

## Changes

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

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

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

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

---------

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

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

---------

Co-authored-by: github-actions <[email protected]>
2026-03-11 15:57:19 +01:00
Félix MalfaitandGitHub a3c0c62506 Merge branch 'main' into feat/seed-apps-for-dev-workspaces 2026-03-11 15:52:06 +01:00
martmullandGitHub d9b3507866 Run vulnerable operation in isolated environment (#18523)
When driver = LAMBDA:
- run esbuild ts transpilation on dedicated lambda
- run yarn install on app dependencies on a dedicated lambda
2026-03-11 14:08:47 +00:00
Thomas TrompetteandGitHub b346f4fb59 Add common loader (#18556)
To avoid white screens on reload, building a shared skeleton.

Before

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

After

https://github.com/user-attachments/assets/e6031a72-2e25-47e3-a873-b89aaddfbd3a
2026-03-11 14:56:49 +01:00
Félix MalfaitandGitHub 390ae34ed8 Merge branch 'main' into feat/seed-apps-for-dev-workspaces 2026-03-11 14:52:19 +01:00
nitinandGitHub 2c5af2654d Separate code pathways for IS_COMMAND_MENU_ITEM_ENABLED flag (#18542) 2026-03-11 13:30:22 +00:00
WeikoandGitHub 6cbc7725b7 fix standard app for server build (#18558) 2026-03-11 14:37:18 +01:00
744ef3aa9d i18n - translations (#18555)
Created by Github action

---------

Co-authored-by: github-actions <[email protected]>
2026-03-11 14:21:20 +01:00
Félix Malfait fe59c333ac Merge remote-tracking branch 'origin/main' into feat/seed-apps-for-dev-workspaces
Made-with: Cursor

# Conflicts:
#	packages/twenty-sdk/src/clients/generated/metadata/types.ts
2026-03-11 14:11:16 +01:00
Félix Malfait 6c0da189cb Fix prettier formatting and regenerate GraphQL/SDK metadata
Made-with: Cursor
2026-03-11 14:03:35 +01:00
WeikoandGitHub ef92d2d321 Backfill standard views command (#18540) 2026-03-11 13:48:59 +01:00
WeikoandGitHub 00c3cd1051 Add system view fallback (#18536)
## Context
The goal is to add a "See records" button in all objects that would
redirect to that view (this will be done in a later PR). See screenshot
below.
<img width="665" height="312" alt="Screenshot 2026-03-10 at 15 54 36"
src="https://github.com/user-attachments/assets/6e23a75b-cff0-4d93-bce8-b5481b05c6f6"
/>

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

---------

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

---------

Co-authored-by: Félix Malfait <[email protected]>
Co-authored-by: Félix Malfait <[email protected]>
2026-03-11 13:35:09 +01:00
Félix Malfait 5aea16f070 Setup 2026-03-11 13:26:41 +01:00
ab5fb1f658 Replace newFieldDefaultConfiguration with newFieldDefaultVisibility (#18539)
https://github.com/user-attachments/assets/365092cb-0fe1-44f7-9ae6-c6fc5edb98b2

---------

Co-authored-by: Weiko <[email protected]>
2026-03-11 12:14:30 +00:00
Félix MalfaitandGitHub 2f542d6763 Merge branch 'main' into feat/seed-apps-for-dev-workspaces 2026-03-11 12:44:43 +01:00
Félix Malfait 4ac38449cb Use require.resolve instead of findMonorepoRoot for fixture paths
Follow the existing pattern from standard-front-component-upload.service.ts:
resolve package location via Node module resolution rather than traversing
the filesystem looking for nx.json.

Made-with: Cursor
2026-03-11 12:40:46 +01:00
Félix Malfait dab4cbcb7d Extract shared install logic into installFromExtractedDir
Both installFromLocalDirectory and doInstallApplication now delegate to
a single private method, eliminating the duplicated ensureApplicationExists
+ writeFilesToStorage + synchronizeFromManifest sequence.

Made-with: Cursor
2026-03-11 12:38:42 +01:00
Félix Malfait a73f586619 Revert hello-world view to use ViewKey enum instead of string literal
Made-with: Cursor
2026-03-11 12:37:32 +01:00
Félix Malfait d1126f507d Refactor app seeding to use committed fixture manifests
Instead of inlining manifest JSON in app-seeds.constant.ts, the seeder
now reads manifest.json directly from fixture app .twenty/output/
directories. Adds findMonorepoRoot() for robust path resolution and
graceful error handling so one failing app doesn't block others.

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

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


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

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

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

---------

Co-authored-by: github-actions <[email protected]>
2026-03-11 09:55:15 +01:00
Félix Malfait 3a1f10e572 Change strat 2026-03-10 20:46:54 +01:00
Thomas des FrancsandGitHub 0fe7e9d5fe small ai chat fix (#18547)
Summary
- capture the updated page structure in a new Playwright YAML artifact
to document the small-ai-chat-fix workstream
- store the generated snapshot under
`.playwright-cli/page-2026-03-09T13-12-30-691Z.yml` for reference

Testing
- Not run (not requested)
2026-03-10 19:27:34 +00:00
Félix MalfaitandGitHub d16299a624 Merge branch 'main' into feat/seed-apps-for-dev-workspaces 2026-03-10 20:07:17 +01:00
Félix Malfait 61d058bb1f Seed dev workspaces with three fixture apps
Add installFromLocalDirectory() to ApplicationInstallService so apps can
be installed from a local directory without network access.  The seeder
creates an ApplicationRegistration, writes the manifest and package.json
to a temp dir, then calls the standard install pipeline
(ensureApplicationExists → writeFilesToStorage → synchronizeFromManifest).

Three apps are seeded into both dev workspaces:
- Hello World (NPM source) — full manifest with objects, fields, roles,
  skills, logic functions, views, and navigation menu items
- Postcard App (tarball source) — minimal manifest
- Minimal App (local source) — minimal manifest

Also adds a hello-world integration test to the SDK that validates the
generated manifest against an expected snapshot, which keeps the seed
manifest in sync with the app source.

Made-with: Cursor
2026-03-10 20:05:07 +01:00
Raphaël BosiandGitHub 2de022afcf Add standard command menu items (#18527)
## Add standard command menu items

### Summary

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

### Description

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

## Next steps

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

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

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

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

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

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

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

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

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

---------

Co-authored-by: root <[email protected]>
Co-authored-by: Lucas Bordeau <[email protected]>
2026-03-10 16:59:31 +00:00
Thomas TrompetteandGitHub 6e628bb751 Fix add more tab bottom separator (#18529)
Adding a full height prop

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

After
<img width="409" height="139" alt="Capture d’écran 2026-03-10 à 14 06
33"
src="https://github.com/user-attachments/assets/1d3551fe-0b4b-4bbf-88e4-fd466c9bb704"
/>
2026-03-10 18:08:51 +01:00
e7fe435f60 i18n - docs translations (#18541)
Created by Github action

Co-authored-by: github-actions <[email protected]>
2026-03-10 17:44:34 +01:00
40a8d18d38 feat : Added "Quarter" as a time unity to filter on date (#18289)
fixes #16674 

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

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

---------

Co-authored-by: Arun kumar <[email protected]>
Co-authored-by: Lucas Bordeau <[email protected]>
2026-03-10 16:42:10 +00:00
Félix MalfaitandGitHub 621962e049 Move fixture apps from twenty-sdk to twenty-apps/fixtures (#18531)
## Summary

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

## Why

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

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

## Test plan

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

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

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

---------

Co-authored-by: Charles Bochet <[email protected]>
2026-03-10 15:39:21 +00:00
ec1f08bc3b i18n - translations (#18537)
Created by Github action

---------

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

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

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

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


before



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


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

before

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


after (fixed missing border on :active state)

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

---------

Co-authored-by: Charles Bochet <[email protected]>
2026-03-10 16:26:53 +01:00
Baptiste DevessierandGitHub 926dd545f4 Create fake hidden fields group (#18525)
## Demo


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

## Ungrouped fields

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

---------

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

---------

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

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


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



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

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

---------

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

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

We also fix some minor bugs.

## Demo 


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



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

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

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

---------

Co-authored-by: Arun kumar <[email protected]>
Co-authored-by: Lucas Bordeau <[email protected]>
2026-03-10 13:08:18 +00:00
59e9563fc7 i18n - docs translations (#18528)
Created by Github action

---------

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

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

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

## Test plan

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


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

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


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

---------

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

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


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

---------

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

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


## After

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

## Summary

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

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

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

## Changes

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

## Test plan

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

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

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

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

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

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

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

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

---------

Co-authored-by: github-actions <[email protected]>
2026-03-09 16:11:29 +01:00
a3aae5c857 i18n - translations (#18498)
Created by Github action

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

---------

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


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

---------

Co-authored-by: github-actions <[email protected]>
2026-03-09 09:45:50 +01:00
c2a79fc0c2 i18n - translations (#18475)
Created by Github action

---------

Co-authored-by: github-actions <[email protected]>
2026-03-09 09:28:49 +01:00
Abdul RahmanandGitHub 5b28e59ca7 Navbar drag drop using dnd kit (#18288) 2026-03-08 20:25:50 +00:00
1462 changed files with 87370 additions and 24400 deletions
-18
View File
@@ -1,18 +0,0 @@
{
"install": "curl -fsSL https://deb.nodesource.com/setup_24.x | sudo -E bash - && sudo apt-get install -y nodejs && node --version && yarn install && echo 'Setting up Docker Compose environment...' && cd packages/twenty-docker && cp -n docker-compose.yml docker-compose.dev.yml || true && echo 'Dependencies installed and docker-compose prepared'",
"start": "sudo service docker start && echo 'Docker service started' && cd packages/twenty-docker && echo 'Installing yq for YAML processing...' && sudo apt-get update -qq && sudo apt-get install -y wget && wget -qO /usr/local/bin/yq https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64 && sudo chmod +x /usr/local/bin/yq && echo 'Patching docker-compose for local development...' && yq eval 'del(.services.server.image)' -i docker-compose.dev.yml && yq eval '.services.server.build.context = \"../../\"' -i docker-compose.dev.yml && yq eval '.services.server.build.dockerfile = \"./packages/twenty-docker/twenty/Dockerfile\"' -i docker-compose.dev.yml && yq eval 'del(.services.worker.image)' -i docker-compose.dev.yml && yq eval '.services.worker.build.context = \"../../\"' -i docker-compose.dev.yml && yq eval '.services.worker.build.dockerfile = \"./packages/twenty-docker/twenty/Dockerfile\"' -i docker-compose.dev.yml && echo 'Setting up .env file with database configuration...' && echo 'SERVER_URL=http://localhost:3000' > .env && echo 'APP_SECRET='$(openssl rand -base64 32) >> .env && echo 'PG_DATABASE_PASSWORD='$(openssl rand -hex 16) >> .env && echo 'PG_DATABASE_URL=postgres://postgres:password@localhost:5432/postgres' >> .env && echo 'SIGN_IN_PREFILLED=true' >> .env && echo 'Building and starting services...' && docker-compose -f docker-compose.dev.yml up -d --build && echo 'Waiting for services to initialize...' && sleep 30 && echo 'Checking service health...' && docker-compose -f docker-compose.dev.yml ps && echo 'Environment setup complete!'",
"terminals": [
{
"name": "Database Setup & Seed",
"command": "sleep 40 && cd packages/twenty-docker && echo 'Waiting for PostgreSQL to be ready...' && until docker-compose -f docker-compose.dev.yml exec -T db pg_isready -U postgres; do echo 'Waiting for PostgreSQL...'; sleep 5; done && echo 'PostgreSQL is ready!' && echo 'Waiting for Twenty server to be healthy...' && until docker-compose -f docker-compose.dev.yml exec -T server curl --fail http://localhost:3000/healthz 2>/dev/null; do echo 'Waiting for server...'; sleep 5; done && echo 'Server is healthy!' && echo 'Running database setup and seeding...' && docker-compose -f docker-compose.dev.yml exec -T server npx nx database:reset twenty-server && echo 'Database seeded successfully!' && bash"
},
{
"name": "Application Logs",
"command": "sleep 35 && cd packages/twenty-docker && echo 'Following application logs...' && docker-compose -f docker-compose.dev.yml logs -f server worker"
},
{
"name": "Service Monitor",
"command": "sleep 15 && cd packages/twenty-docker && echo '=== Service Status Monitor ===' && while true; do clear; echo '=== Service Status at $(date) ===' && docker-compose -f docker-compose.dev.yml ps && echo '\\n=== Health Status ===' && (docker-compose -f docker-compose.dev.yml exec -T server curl -s http://localhost:3000/healthz 2>/dev/null && echo '✅ Twenty Server: Healthy') || echo '❌ Twenty Server: Not Ready' && (docker-compose -f docker-compose.dev.yml exec -T db pg_isready -U postgres 2>/dev/null && echo '✅ PostgreSQL: Ready') || echo '❌ PostgreSQL: Not Ready' && echo '\\n=== Database Connection Test ===' && docker-compose -f docker-compose.dev.yml exec -T server node -e \"const { Client } = require('pg'); const client = new Client({connectionString: process.env.PG_DATABASE_URL}); client.connect().then(() => {console.log('✅ Database Connection: OK'); client.end();}).catch(e => console.log('❌ Database Connection: Failed -', e.message));\" || echo 'Connection test failed' && sleep 45; done"
}
]
}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"install": "yarn install",
"start": "sudo service docker start && sleep 2 && (docker start twenty_pg 2>/dev/null || make -C packages/twenty-docker postgres-on-docker) && (docker start twenty_redis 2>/dev/null || make -C packages/twenty-docker redis-on-docker) && until docker exec twenty_pg pg_isready -U postgres -h localhost 2>/dev/null; do sleep 1; done && echo 'PostgreSQL ready' && until docker exec twenty_redis redis-cli ping 2>/dev/null | grep -q PONG; do sleep 1; done && echo 'Redis ready' && bash packages/twenty-utils/setup-dev-env.sh && npx nx database:reset twenty-server",
"start": "(sudo service docker start || service docker start || true) && bash packages/twenty-utils/setup-dev-env.sh && npx nx database:reset twenty-server",
"terminals": [
{
"name": "Development Server",
+19
View File
@@ -0,0 +1,19 @@
storage: /tmp/verdaccio-storage
auth:
htpasswd:
file: /tmp/verdaccio-htpasswd
max_users: 100
uplinks:
npmjs:
url: https://registry.npmjs.org/
packages:
'twenty-sdk':
access: $all
publish: $all
'create-twenty-app':
access: $all
publish: $all
'**':
access: $all
proxy: npmjs
log: { type: stdout, format: pretty, level: warn }
+182
View File
@@ -0,0 +1,182 @@
name: CI Create App E2E
on:
push:
branches:
- main
pull_request:
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
jobs:
changed-files-check:
uses: ./.github/workflows/changed-files.yaml
with:
files: |
packages/create-twenty-app/**
packages/twenty-sdk/**
packages/twenty-shared/**
packages/twenty-server/**
!packages/create-twenty-app/package.json
!packages/twenty-sdk/package.json
!packages/twenty-shared/package.json
!packages/twenty-server/package.json
create-app-e2e:
needs: changed-files-check
if: needs.changed-files-check.outputs.any_changed == 'true'
timeout-minutes: 30
runs-on: ubuntu-latest-4-cores
services:
postgres:
image: twentycrm/twenty-postgres-spilo
env:
PGUSER_SUPERUSER: postgres
PGPASSWORD_SUPERUSER: postgres
ALLOW_NOSSL: 'true'
SPILO_PROVIDER: 'local'
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
redis:
image: redis
ports:
- 6379:6379
steps:
- name: Fetch custom Github Actions and base branch history
uses: actions/checkout@v4
with:
fetch-depth: 10
- name: Install dependencies
uses: ./.github/actions/yarn-install
- name: Set CI version and prepare packages for publish
run: |
CI_VERSION="0.0.0-ci.$(date +%s)"
echo "CI_VERSION=$CI_VERSION" >> $GITHUB_ENV
npx nx run-many -t set-local-version -p twenty-sdk create-twenty-app --releaseVersion=$CI_VERSION
- name: Build packages
run: |
npx nx build twenty-sdk
npx nx build create-twenty-app
- name: Install and start Verdaccio
run: |
npx verdaccio --config .github/verdaccio-config.yaml &
for i in $(seq 1 30); do
if curl -s http://localhost:4873 > /dev/null 2>&1; then
echo "Verdaccio is ready"
break
fi
echo "Waiting for Verdaccio... ($i/30)"
sleep 1
done
- name: Publish packages to local registry
run: |
npm set //localhost:4873/:_authToken "ci-auth-token"
for pkg in twenty-sdk create-twenty-app; do
cd packages/$pkg
npm publish --registry http://localhost:4873 --tag ci
cd ../..
done
- name: Scaffold app using published create-twenty-app
run: |
npm install -g create-twenty-app@$CI_VERSION --registry http://localhost:4873
create-twenty-app --version
mkdir -p /tmp/e2e-test-workspace
cd /tmp/e2e-test-workspace
create-twenty-app test-app --exhaustive --display-name "Test App" --description "E2E test app"
- name: Install scaffolded app dependencies
run: |
cd /tmp/e2e-test-workspace/test-app
echo 'npmRegistryServer: "http://localhost:4873"' >> .yarnrc.yml
echo 'unsafeHttpWhitelist: ["localhost"]' >> .yarnrc.yml
YARN_ENABLE_IMMUTABLE_INSTALLS=false yarn install --no-immutable
- name: Verify installed app versions
run: |
cd /tmp/e2e-test-workspace/test-app
echo "--- Checking package.json references correct SDK version ---"
node -e "
const pkg = require('./package.json');
const sdkVersion = pkg.devDependencies['twenty-sdk'];
if (!sdkVersion.startsWith('0.0.0-ci.')) {
console.error('Expected twenty-sdk version to start with 0.0.0-ci., got:', sdkVersion);
process.exit(1);
}
console.log('SDK version in scaffolded app:', sdkVersion);
"
- name: Verify SDK CLI is available
run: |
cd /tmp/e2e-test-workspace/test-app
npx --no-install twenty --version
- name: Setup server environment
run: npx nx reset:env:e2e-testing-server twenty-server
- name: Build server
run: npx nx build twenty-server
- name: Create and setup database
run: |
PGPASSWORD=postgres psql -h localhost -p 5432 -U postgres -d postgres -c 'CREATE DATABASE "default";'
PGPASSWORD=postgres psql -h localhost -p 5432 -U postgres -d postgres -c 'CREATE DATABASE "test";'
npx nx run twenty-server:database:reset
- name: Start server
run: |
npx nx start twenty-server &
echo "Waiting for server to be ready..."
timeout 60 bash -c 'until curl -s http://localhost:3000/health; do sleep 2; done'
- name: Authenticate with twenty-server
env:
SEED_API_KEY: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIyMDIwMjAyMC1lNmI1LTQ2ODAtOGEzMi1iODIwOTczNzE1NmIiLCJ1c2VySWQiOiIyMDIwMjAyMC1lNmI1LTQ2ODAtOGEzMi1iODIwOTczNzE1NmIiLCJ3b3Jrc3BhY2VJZCI6IjIwMjAyMDIwLTFjMjUtNGQwMi1iZjI1LTZhZWNjZjdlYTQxOSIsIndvcmtzcGFjZU1lbWJlcklkIjoiMjAyMDIwMjAtNDYzZi00MzViLTgyOGMtMTA3ZTAwN2EyNzExIiwidXNlcldvcmtzcGFjZUlkIjoiMjAyMDIwMjAtMWU3Yy00M2Q5LWE1ZGItNjg1YjUwNjlkODE2IiwidHlwZSI6IkFDQ0VTUyIsImF1dGhQcm92aWRlciI6InBhc3N3b3JkIiwiaWF0IjoxNzUxMjgxNzA0LCJleHAiOjIwNjY4NTc3MDR9.HMGqCsVlOAPVUBhKSGlD1X86VoHKt4LIUtET3CGIdik'
run: |
cd /tmp/e2e-test-workspace/test-app
npx --no-install twenty auth:login --api-key $SEED_API_KEY --api-url http://localhost:3000
- name: Build scaffolded app
run: |
cd /tmp/e2e-test-workspace/test-app
npx --no-install twenty app:build
test -d .twenty/output
- name: Execute hello-world logic function
run: |
cd /tmp/e2e-test-workspace/test-app
EXEC_OUTPUT=$(npx --no-install twenty function:execute --functionName hello-world-logic-function)
echo "$EXEC_OUTPUT"
echo "$EXEC_OUTPUT" | grep -q "Hello, World!"
- name: Run scaffolded app integration test
run: |
cd /tmp/e2e-test-workspace/test-app
yarn test
ci-create-app-e2e-status-check:
if: always() && !cancelled()
timeout-minutes: 5
runs-on: ubuntu-latest
needs: [changed-files-check, create-app-e2e]
steps:
- name: Fail job if any needs failed
if: contains(needs.*.result, 'failure')
run: exit 1
+1 -1
View File
@@ -65,7 +65,7 @@ jobs:
key: ${{ env.STORYBOOK_BUILD_CACHE_KEY_FOR_SAVE_ACTION }}
front-sb-test:
timeout-minutes: 30
runs-on: ubuntu-latest-4-cores
runs-on: ubuntu-latest
needs: front-sb-build
strategy:
fail-fast: false
+1 -1
View File
@@ -11,7 +11,7 @@ permissions:
concurrency:
group: ${{ github.workflow }}-${{ github.event_name == 'merge_group' && github.event.merge_group.base_ref || github.ref }}
cancel-in-progress: true
cancel-in-progress: ${{ github.event_name != 'merge_group' }}
jobs:
e2e-test:
+1 -1
View File
@@ -51,7 +51,7 @@ jobs:
tasks: ${{ matrix.task }}
sdk-e2e-test:
timeout-minutes: 30
runs-on: ubuntu-latest-4-cores
runs-on: ubuntu-latest
needs: [changed-files-check, sdk-test]
if: needs.changed-files-check.outputs.any_changed == 'true'
services:
+26 -9
View File
@@ -25,6 +25,7 @@ jobs:
packages/twenty-server/**
packages/twenty-front/src/generated/**
packages/twenty-front/src/generated-metadata/**
packages/twenty-sdk/src/clients/generated/metadata/**
packages/twenty-emails/**
packages/twenty-shared/**
@@ -32,7 +33,7 @@ jobs:
needs: changed-files-check
if: needs.changed-files-check.outputs.any_changed == 'true'
timeout-minutes: 30
runs-on: ubuntu-latest-4-cores
runs-on: ubuntu-latest
steps:
- name: Fetch custom Github Actions and base branch history
uses: actions/checkout@v4
@@ -60,7 +61,7 @@ jobs:
needs: changed-files-check
if: needs.changed-files-check.outputs.any_changed == 'true'
timeout-minutes: 30
runs-on: ubuntu-latest-4-cores
runs-on: ubuntu-latest
steps:
- name: Fetch custom Github Actions and base branch history
uses: actions/checkout@v4
@@ -79,7 +80,7 @@ jobs:
server-validation:
needs: server-build
timeout-minutes: 30
runs-on: ubuntu-latest-4-cores
runs-on: ubuntu-latest
services:
postgres:
image: twentycrm/twenty-postgres-spilo
@@ -158,8 +159,10 @@ jobs:
exit 1
fi
- name: GraphQL / Check for Pending Generation
- name: Check for Pending Code Generation
run: |
HAS_ERRORS=false
npx nx run twenty-front:graphql:generate
npx nx run twenty-front:graphql:generate --configuration=metadata
@@ -171,15 +174,29 @@ jobs:
git diff -- packages/twenty-front/src/generated packages/twenty-front/src/generated-metadata
echo "==================================================="
echo ""
echo "Please run 'npx nx run twenty-front:graphql:generate' and 'npx nx run twenty-front:graphql:generate --configuration=metadata' and commit the changes."
echo ""
exit 1
HAS_ERRORS=true
fi
npx nx run twenty-sdk:generate-metadata-client
if ! git diff --quiet -- packages/twenty-sdk/src/clients/generated/metadata; then
echo "::error::SDK metadata client changes detected. Please run 'npx nx run twenty-sdk:generate-metadata-client' and commit the changes."
echo ""
echo "The following SDK metadata client changes were detected:"
echo "==================================================="
git diff -- packages/twenty-sdk/src/clients/generated/metadata
echo "==================================================="
echo ""
HAS_ERRORS=true
fi
if [ "$HAS_ERRORS" = true ]; then
exit 1
fi
server-test:
needs: server-build
timeout-minutes: 30
runs-on: ubuntu-latest-4-cores
runs-on: ubuntu-latest
steps:
- name: Fetch custom Github Actions and base branch history
uses: actions/checkout@v4
@@ -201,7 +218,7 @@ jobs:
server-integration-test:
timeout-minutes: 30
runs-on: ubuntu-latest-4-cores
runs-on: ubuntu-latest
needs: server-build
strategy:
fail-fast: false
@@ -0,0 +1,66 @@
name: CI Twenty Standard Front Component
on:
pull_request:
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
jobs:
changed-files-check:
uses: ./.github/workflows/changed-files.yaml
with:
files: |
package.json
yarn.lock
packages/twenty-standard-application/**
packages/twenty-sdk/**
packages/twenty-shared/**
standard-front-component-build-check:
needs: changed-files-check
if: needs.changed-files-check.outputs.any_changed == 'true'
timeout-minutes: 30
runs-on: ubuntu-latest
steps:
- name: Fetch custom Github Actions and base branch history
uses: actions/checkout@v4
with:
fetch-depth: 10
- name: Install dependencies
uses: ./.github/actions/yarn-install
- name: Build twenty-shared
run: npx nx build twenty-shared
- name: Build twenty-sdk
run: npx nx build twenty-sdk
- name: Build twenty-standard-application
run: npx nx build twenty-standard-application
- name: Check for pending standard front component build
run: |
if ! git diff --quiet -- packages/twenty-standard-application/src/build packages/twenty-standard-application/src/standard-front-component-build-manifest.ts; then
echo "::error::Standard front component build output is out of date. Please run 'npx nx build twenty-standard-application' and commit the changes."
echo ""
echo "The following changes were detected:"
echo "==================================================="
git diff -- packages/twenty-standard-application/src/build packages/twenty-standard-application/src/standard-front-component-build-manifest.ts
echo "==================================================="
exit 1
fi
ci-twenty-standard-front-component-status-check:
if: always() && !cancelled()
timeout-minutes: 5
runs-on: ubuntu-latest
needs:
[
changed-files-check,
standard-front-component-build-check,
]
steps:
- name: Fail job if any needs failed
if: contains(needs.*.result, 'failure')
run: exit 1
+1 -1
View File
@@ -26,7 +26,7 @@ jobs:
needs: changed-files-check
if: needs.changed-files-check.outputs.any_changed == 'true'
timeout-minutes: 30
runs-on: ubuntu-latest-4-cores
runs-on: ubuntu-latest
services:
postgres:
image: twentycrm/twenty-postgres-spilo
+13 -4
View File
@@ -188,13 +188,22 @@ IMPORTANT: Use Context7 for code generation, setup or configuration steps, or li
- Descriptive test names: "should [behavior] when [condition]"
- Clear mocks between tests with `jest.clearAllMocks()`
## CI Environment (GitHub Actions)
## Dev Environment Setup
When running in CI, the dev environment is **not** pre-configured. Dependencies are installed but builds, env files, and databases are not set up.
All dev environments (Claude Code web, Cursor, local) use one script:
- **Before running tests, builds, lint, type checks, or DB operations**, run: `bash packages/twenty-utils/setup-dev-env.sh`
```bash
bash packages/twenty-utils/setup-dev-env.sh
```
This handles everything: starts Postgres + Redis (auto-detects local services vs Docker), creates databases, and copies `.env` files. Idempotent — safe to run multiple times.
- `--docker` — force Docker mode (uses `packages/twenty-docker/docker-compose.dev.yml`)
- `--down` — stop services
- `--reset` — wipe data and restart fresh
- **Skip the setup script** for tasks that only read code — architecture questions, code review, documentation, etc.
- The script is idempotent and safe to run multiple times.
**Note:** CI workflows (GitHub Actions) manage services via Actions service containers and run setup steps individually — they don't use this script.
## Important Files
- `nx.json` - Nx workspace configuration with task definitions
+8
View File
@@ -136,6 +136,14 @@
"cache": true,
"dependsOn": ["^build"]
},
"set-local-version": {
"executor": "nx:run-commands",
"cache": false,
"options": {
"cwd": "{projectRoot}",
"command": "npm pkg set version={args.releaseVersion}"
}
},
"storybook:build": {
"executor": "nx:run-commands",
"cache": true,
+2
View File
@@ -164,6 +164,7 @@
"tsc-alias": "^1.8.16",
"tsconfig-paths": "^4.2.0",
"tsx": "^4.17.0",
"verdaccio": "^6.3.1",
"vite": "^7.0.0",
"vitest": "^4.0.18"
},
@@ -206,6 +207,7 @@
"packages/twenty-e2e-testing",
"packages/twenty-shared",
"packages/twenty-sdk",
"packages/twenty-standard-application",
"packages/twenty-apps",
"packages/twenty-cli",
"packages/create-twenty-app",
+2 -2
View File
@@ -43,7 +43,7 @@ yarn twenty auth:login
yarn twenty entity:add
# Start dev mode: watches, builds, and syncs local changes to your workspace
# (also auto-generates typed API clients — CoreApiClient and MetadataApiClient — in node_modules/twenty-sdk/generated)
# (also auto-generates typed CoreApiClient MetadataApiClient ships pre-built with the SDK — both available via `twenty-sdk/clients`)
yarn twenty app:dev
# Watch your application's function logs
@@ -107,7 +107,7 @@ npx create-twenty-app@latest my-app -m
- Use `yarn twenty auth:login` to authenticate with your Twenty workspace.
- Explore the generated project and add your first entity with `yarn twenty entity:add` (logic functions, front components, objects, roles, views, navigation menu items, skills).
- Use `yarn twenty app:dev` while you iterate — it watches, builds, and syncs changes to your workspace in real time.
- Two typed API clients are autogenerated by `yarn twenty app:dev` and stored in `node_modules/twenty-sdk/generated`: `CoreApiClient` (for workspace data via `/graphql`) and `MetadataApiClient` (for workspace configuration and file uploads via `/metadata`).
- `CoreApiClient` (for workspace data via `/graphql`) is auto-generated by `yarn twenty app:dev`. `MetadataApiClient` (for workspace configuration and file uploads via `/metadata`) ships pre-built with the SDK. Both are available via `import { CoreApiClient, MetadataApiClient } from 'twenty-sdk/clients'`.
## Publish your application
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "create-twenty-app",
"version": "0.6.4",
"version": "0.7.0-canary.0",
"description": "Command-line interface to create Twenty application",
"main": "dist/cli.cjs",
"bin": "dist/cli.cjs",
+1
View File
@@ -24,6 +24,7 @@
"command": "node dist/cli.cjs"
}
},
"set-local-version": {},
"typecheck": {},
"lint": {},
"test": {
+24 -1
View File
@@ -18,6 +18,15 @@ const program = new Command(packageJson.name)
'-m, --minimal',
'Create only core entities (application-config and default-role)',
)
.option('-n, --name <name>', 'Application name (skips prompt)')
.option(
'-d, --display-name <displayName>',
'Application display name (skips prompt)',
)
.option(
'--description <description>',
'Application description (skips prompt)',
)
.helpOption('-h, --help', 'Display this help message.')
.action(
async (
@@ -25,6 +34,9 @@ const program = new Command(packageJson.name)
options?: {
exhaustive?: boolean;
minimal?: boolean;
name?: string;
displayName?: string;
description?: string;
},
) => {
const modeFlags = [options?.exhaustive, options?.minimal].filter(Boolean);
@@ -47,9 +59,20 @@ const program = new Command(packageJson.name)
process.exit(1);
}
if (options?.name !== undefined && options.name.trim().length === 0) {
console.error(chalk.red('Error: --name cannot be empty.'));
process.exit(1);
}
const mode: ScaffoldingMode = options?.minimal ? 'minimal' : 'exhaustive';
await new CreateAppCommand().execute(directory, mode);
await new CreateAppCommand().execute({
directory,
mode,
name: options?.name,
displayName: options?.displayName,
description: options?.description,
});
},
);
@@ -1,7 +1,7 @@
## Base documentation
- Documentation: https://docs.twenty.com/developers/extend/capabilities/apps
- Rich app example: https://github.com/twentyhq/twenty/tree/main/packages/twenty-sdk/src/cli/__tests__/apps/rich-app
- Rich app example: https://github.com/twentyhq/twenty/tree/main/packages/twenty-sdk/src/app-seeds/rich-app
## UUID requirement
@@ -7,6 +7,7 @@ import * as fs from 'fs-extra';
import inquirer from 'inquirer';
import kebabCase from 'lodash.kebabcase';
import * as path from 'path';
import { isDefined } from 'twenty-shared/utils';
import {
type ExampleOptions,
@@ -15,16 +16,23 @@ import {
const CURRENT_EXECUTION_DIRECTORY = process.env.INIT_CWD || process.cwd();
type CreateAppOptions = {
directory?: string;
mode?: ScaffoldingMode;
name?: string;
displayName?: string;
description?: string;
};
export class CreateAppCommand {
async execute(
directory?: string,
mode: ScaffoldingMode = 'exhaustive',
): Promise<void> {
async execute(options: CreateAppOptions = {}): Promise<void> {
try {
const { appName, appDisplayName, appDirectory, appDescription } =
await this.getAppInfos(directory);
await this.getAppInfos(options);
const exampleOptions = this.resolveExampleOptions(mode);
const exampleOptions = this.resolveExampleOptions(
options.mode ?? 'exhaustive',
);
await this.validateDirectory(appDirectory);
@@ -54,19 +62,25 @@ export class CreateAppCommand {
}
}
private async getAppInfos(directory?: string): Promise<{
private async getAppInfos(options: CreateAppOptions): Promise<{
appName: string;
appDisplayName: string;
appDescription: string;
appDirectory: string;
}> {
const { directory } = options;
const hasName = isDefined(options.name) || isDefined(directory);
const hasDisplayName = isDefined(options.displayName);
const hasDescription = isDefined(options.description);
const { name, displayName, description } = await inquirer.prompt([
{
type: 'input',
name: 'name',
message: 'Application name:',
when: () => !directory,
default: 'my-awesome-app',
when: () => !hasName,
default: 'my-twenty-app',
validate: (input) => {
if (input.length === 0) return 'Application name is required';
return true;
@@ -76,25 +90,33 @@ export class CreateAppCommand {
type: 'input',
name: 'displayName',
message: 'Application display name:',
default: (answers: any) => {
return convertToLabel(answers?.name ?? directory);
when: () => !hasDisplayName,
default: (answers: { name?: string }) => {
return convertToLabel(
answers?.name ?? options.name ?? directory ?? '',
);
},
},
{
type: 'input',
name: 'description',
message: 'Application description (optional):',
when: () => !hasDescription,
default: '',
},
]);
const computedName = name ?? directory;
const appName = (
options.name ??
name ??
directory ??
'my-twenty-app'
).trim();
const appName = computedName.trim();
const appDisplayName =
(options.displayName ?? displayName)?.trim() || convertToLabel(appName);
const appDisplayName = displayName.trim();
const appDescription = description.trim();
const appDescription = (options.description ?? description ?? '').trim();
const appDirectory = directory
? path.join(CURRENT_EXECUTION_DIRECTORY, directory)
@@ -113,6 +135,7 @@ export class CreateAppCommand {
includeExampleView: false,
includeExampleNavigationMenuItem: false,
includeExampleSkill: false,
includeExampleAgent: false,
includeExampleIntegrationTest: false,
};
}
@@ -126,6 +149,7 @@ export class CreateAppCommand {
includeExampleNavigationMenuItem: true,
includeExampleSkill: true,
includeExampleIntegrationTest: true,
includeExampleAgent: true,
};
}
@@ -8,5 +8,6 @@ export type ExampleOptions = {
includeExampleView: boolean;
includeExampleNavigationMenuItem: boolean;
includeExampleSkill: boolean;
includeExampleAgent: boolean;
includeExampleIntegrationTest: boolean;
};
@@ -25,6 +25,7 @@ const ALL_EXAMPLES: ExampleOptions = {
includeExampleView: true,
includeExampleNavigationMenuItem: true,
includeExampleSkill: true,
includeExampleAgent: true,
includeExampleIntegrationTest: true,
};
@@ -41,6 +42,7 @@ const NO_EXAMPLES: ExampleOptions = {
includeExampleObject: false,
includeExampleField: false,
includeExampleSkill: false,
includeExampleAgent: false,
includeExampleLogicFunction: false,
includeExampleFrontComponent: false,
includeExampleView: false,
@@ -384,6 +386,12 @@ describe('copyBaseApplicationProject', () => {
),
).toBe(true);
expect(
await fs.pathExists(
join(testAppDirectory, '.github', 'workflows', 'ci.yml'),
),
).toBe(true);
// Install functions should always exist
expect(
await fs.pathExists(
@@ -462,6 +470,12 @@ describe('copyBaseApplicationProject', () => {
join(srcPath, '__tests__', 'app-install.integration-test.ts'),
),
).toBe(false);
expect(
await fs.pathExists(
join(testAppDirectory, '.github', 'workflows', 'ci.yml'),
),
).toBe(false);
});
});
@@ -476,6 +490,7 @@ describe('copyBaseApplicationProject', () => {
includeExampleObject: false,
includeExampleField: false,
includeExampleSkill: false,
includeExampleAgent: false,
includeExampleLogicFunction: false,
includeExampleFrontComponent: true,
includeExampleView: false,
@@ -513,6 +528,7 @@ describe('copyBaseApplicationProject', () => {
exampleOptions: {
includeExampleObject: false,
includeExampleSkill: false,
includeExampleAgent: false,
includeExampleField: false,
includeExampleLogicFunction: true,
includeExampleFrontComponent: false,
@@ -47,18 +47,17 @@ describe('scaffoldIntegrationTest', () => {
const content = await fs.readFile(testPath, 'utf8');
expect(content).toContain(
"import { appGenerateClient, appUninstall } from 'twenty-sdk/cli'",
"import { appBuild, appUninstall } from 'twenty-sdk/cli'",
);
expect(content).toContain(
"import { MetadataApiClient } from 'twenty-sdk/generated'",
"import { MetadataApiClient } from 'twenty-sdk/clients'",
);
expect(content).toContain(
"import { APPLICATION_UNIVERSAL_IDENTIFIER } from 'src/application-config'",
);
expect(content).toContain('TWENTY_TEST_API_KEY');
expect(content).toContain('assertServerIsReachable');
expect(content).toContain('appGenerateClient');
expect(content).toContain('appBuild');
expect(content).toContain('appUninstall');
expect(content).toContain('new MetadataApiClient()');
expect(content).toContain('findManyApplications');
expect(content).toContain('APPLICATION_UNIVERSAL_IDENTIFIER');
});
@@ -84,7 +83,8 @@ describe('scaffoldIntegrationTest', () => {
expect(content).toContain('.twenty-sdk-test');
expect(content).toContain('config.json');
expect(content).toContain('process.env.TWENTY_API_URL');
expect(content).toContain('process.env.TWENTY_TEST_API_KEY');
expect(content).toContain('process.env.TWENTY_API_KEY');
expect(content).toContain('assertServerIsReachable');
});
});
@@ -101,7 +101,8 @@ describe('scaffoldIntegrationTest', () => {
const content = await fs.readFile(vitestConfigPath, 'utf8');
expect(content).toContain('TWENTY_TEST_API_KEY');
expect(content).toContain('TWENTY_API_KEY');
expect(content).not.toContain('TWENTY_TEST_API_KEY');
expect(content).toContain('TWENTY_API_URL');
expect(content).toContain('setup-test.ts');
expect(content).toContain('tsconfig.spec.json');
@@ -109,6 +110,37 @@ describe('scaffoldIntegrationTest', () => {
});
});
describe('github workflow', () => {
it('should create .github/workflows/ci.yml with correct structure', async () => {
await scaffoldIntegrationTest({
appDirectory: testAppDirectory,
sourceFolderPath,
});
const workflowPath = join(
testAppDirectory,
'.github',
'workflows',
'ci.yml',
);
expect(await fs.pathExists(workflowPath)).toBe(true);
const content = await fs.readFile(workflowPath, 'utf8');
expect(content).toContain('name: CI');
expect(content).toContain('TWENTY_VERSION: latest');
expect(content).toContain('twenty-version: ${{ env.TWENTY_VERSION }}');
expect(content).toContain('actions/checkout@v4');
expect(content).toContain('spawn-twenty-docker-image@main');
expect(content).toContain('actions/setup-node@v4');
expect(content).toContain('yarn install --immutable');
expect(content).toContain('yarn test');
expect(content).toContain('TWENTY_API_URL');
expect(content).toContain('TWENTY_TEST_API_KEY');
});
});
describe('tsconfig.spec.json', () => {
it('should create tsconfig.spec.json extending the base tsconfig', async () => {
await scaffoldIntegrationTest({
@@ -30,12 +30,12 @@ export const copyBaseApplicationProject = async ({
includeExampleIntegrationTest: exampleOptions.includeExampleIntegrationTest,
});
await createYarnLock(appDirectory);
await createGitignore(appDirectory);
await createPublicAssetDirectory(appDirectory);
await createYarnLock(appDirectory);
const sourceFolderPath = join(appDirectory, SRC_FOLDER);
await fs.ensureDir(sourceFolderPath);
@@ -103,6 +103,14 @@ export const copyBaseApplicationProject = async ({
});
}
if (exampleOptions.includeExampleAgent) {
await createExampleAgent({
appDirectory: sourceFolderPath,
fileFolder: 'agents',
fileName: 'example-agent.ts',
});
}
if (exampleOptions.includeExampleIntegrationTest) {
await scaffoldIntegrationTest({
appDirectory,
@@ -134,13 +142,6 @@ const createPublicAssetDirectory = async (appDirectory: string) => {
await fs.ensureDir(join(appDirectory, ASSETS_DIR));
};
const createYarnLock = async (appDirectory: string) => {
const yarnLockContent = `# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
`;
await fs.writeFile(join(appDirectory, 'yarn.lock'), yarnLockContent);
};
const createGitignore = async (appDirectory: string) => {
const gitignoreContent = `# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
@@ -519,6 +520,36 @@ export default defineSkill({
await fs.writeFile(join(appDirectory, fileFolder ?? '', fileName), content);
};
const createExampleAgent = async ({
appDirectory,
fileFolder,
fileName,
}: {
appDirectory: string;
fileFolder?: string;
fileName: string;
}) => {
const universalIdentifier = v4();
const content = `import { defineAgent } from 'twenty-sdk';
export const EXAMPLE_AGENT_UNIVERSAL_IDENTIFIER =
'${universalIdentifier}';
export default defineAgent({
universalIdentifier: EXAMPLE_AGENT_UNIVERSAL_IDENTIFIER,
name: 'example-agent',
label: 'Example Agent',
description: 'A sample AI agent for your application',
icon: 'IconRobot',
prompt: 'You are a helpful assistant. Help users with their questions and tasks.',
});
`;
await fs.ensureDir(join(appDirectory, fileFolder ?? ''));
await fs.writeFile(join(appDirectory, fileFolder ?? '', fileName), content);
};
const createApplicationConfig = async ({
displayName,
description,
@@ -552,6 +583,14 @@ export default defineApplication({
await fs.writeFile(join(appDirectory, fileFolder ?? '', fileName), content);
};
const createYarnLock = async (appDirectory: string) => {
const yarnLockContent = `# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
`;
await fs.writeFile(join(appDirectory, 'yarn.lock'), yarnLockContent);
};
const createPackageJson = async ({
appName,
appDirectory,
@@ -25,6 +25,7 @@ export const scaffoldIntegrationTest = async ({
await createVitestConfig(appDirectory);
await createTsconfigSpec(appDirectory);
await createGithubWorkflow(appDirectory);
};
const createVitestConfig = async (appDirectory: string) => {
@@ -45,7 +46,7 @@ export default defineConfig({
setupFiles: ['src/__tests__/setup-test.ts'],
env: {
TWENTY_API_URL: 'http://localhost:3000',
TWENTY_TEST_API_KEY:
TWENTY_API_KEY:
'${SEED_API_KEY}',
},
},
@@ -93,16 +94,36 @@ import * as os from 'os';
import * as path from 'path';
import { beforeAll } from 'vitest';
const TWENTY_API_URL = process.env.TWENTY_API_URL ?? 'http://localhost:3000';
const TEST_CONFIG_DIR = path.join(os.tmpdir(), '.twenty-sdk-test');
beforeAll(() => {
const assertServerIsReachable = async () => {
let response: Response;
try {
response = await fetch(\`\${TWENTY_API_URL}/healthz\`);
} catch {
throw new Error(
\`Twenty server is not reachable at \${TWENTY_API_URL}. \` +
'Make sure the server is running before executing integration tests.',
);
}
if (!response.ok) {
throw new Error(\`Server at \${TWENTY_API_URL} returned \${response.status}\`);
}
};
beforeAll(async () => {
await assertServerIsReachable();
fs.mkdirSync(TEST_CONFIG_DIR, { recursive: true });
const configFile = {
profiles: {
default: {
apiUrl: process.env.TWENTY_API_URL,
apiKey: process.env.TWENTY_TEST_API_KEY,
apiKey: process.env.TWENTY_API_KEY,
},
},
};
@@ -128,44 +149,24 @@ const createIntegrationTest = async ({
fileName: string;
}) => {
const content = `import { APPLICATION_UNIVERSAL_IDENTIFIER } from 'src/application-config';
import { appGenerateClient, appUninstall } from 'twenty-sdk/cli';
import { MetadataApiClient } from 'twenty-sdk/generated';
import { appBuild, appUninstall } from 'twenty-sdk/cli';
import { MetadataApiClient } from 'twenty-sdk/clients';
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
const APP_PATH = process.cwd();
const TWENTY_API_URL = process.env.TWENTY_API_URL ?? 'http://localhost:3000';
const assertServerIsReachable = async () => {
let response: Response;
try {
response = await fetch(\`\${TWENTY_API_URL}/healthz\`);
} catch {
throw new Error(
\`Twenty server is not reachable at \${TWENTY_API_URL}. \` +
'Make sure the server is running before executing integration tests.',
);
}
if (!response.ok) {
throw new Error(\`Server at \${TWENTY_API_URL} returned \${response.status}\`);
}
};
describe('App installation', () => {
let appInstalled = false;
beforeAll(async () => {
await assertServerIsReachable();
const generateResult = await appGenerateClient({
const buildResult = await appBuild({
appPath: APP_PATH,
onProgress: (message: string) => console.log(\`[generate-client] \${message}\`),
onProgress: (message: string) => console.log(\`[build] \${message}\`),
});
if (!generateResult.success) {
if (!buildResult.success) {
throw new Error(
\`Client generation failed: \${generateResult.error?.message ?? 'Unknown error'}\`,
\`Build failed: \${buildResult.error?.message ?? 'Unknown error'}\`,
);
}
@@ -187,20 +188,7 @@ describe('App installation', () => {
});
it('should find the installed app in the applications list', async () => {
const apiKey = process.env.TWENTY_TEST_API_KEY;
if (!apiKey) {
throw new Error(
'No API key found. Set TWENTY_TEST_API_KEY in your vitest config env.',
);
}
const metadataClient = new MetadataApiClient({
url: \`\${TWENTY_API_URL}/metadata\`,
headers: {
Authorization: \`Bearer \${apiKey}\`,
},
});
const metadataClient = new MetadataApiClient();
const result = await metadataClient.query({
findManyApplications: {
@@ -224,3 +212,56 @@ describe('App installation', () => {
await fs.ensureDir(join(appDirectory, fileFolder ?? ''));
await fs.writeFile(join(appDirectory, fileFolder ?? '', fileName), content);
};
const DEFAULT_TWENTY_VERSION = 'latest';
const createGithubWorkflow = async (appDirectory: string) => {
const content = `name: CI
on:
push:
branches:
- main
pull_request: {}
env:
TWENTY_VERSION: ${DEFAULT_TWENTY_VERSION}
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Spawn Twenty instance
id: twenty
uses: twentyhq/twenty/.github/actions/spawn-twenty-docker-image@main
with:
twenty-version: \${{ env.TWENTY_VERSION }}
github-token: \${{ secrets.GITHUB_TOKEN }}
- name: Enable Corepack
run: corepack enable
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'yarn'
- name: Install dependencies
run: yarn install --immutable
- name: Run integration tests
run: yarn test
env:
TWENTY_API_URL: \${{ steps.twenty.outputs.server-url }}
TWENTY_TEST_API_KEY: \${{ steps.twenty.outputs.access-token }}
`;
const workflowDir = join(appDirectory, '.github', 'workflows');
await fs.ensureDir(workflowDir);
await fs.writeFile(join(workflowDir, 'ci.yml'), content);
};
+11
View File
@@ -1,2 +1,13 @@
generated
# Ignore .twenty build output for all apps
.twenty
# Allow committed fixture metadata (used by dev seeder)
!fixtures/**/.twenty
fixtures/**/.twenty/*
!fixtures/**/.twenty/output
fixtures/**/.twenty/output/**
!fixtures/**/.twenty/output/manifest.json
!fixtures/**/.twenty/output/package.json
!fixtures/**/.twenty/output/yarn.lock
@@ -1,7 +1,7 @@
## Base documentation
- Documentation: https://docs.twenty.com/developers/extend/capabilities/apps
- Rich app example: https://github.com/twentyhq/twenty/tree/main/packages/twenty-sdk/src/cli/__tests__/apps/rich-app
- Rich app example: https://github.com/twentyhq/twenty/tree/main/packages/twenty-sdk/src/app-seeds/rich-app
## UUID requirement
- All generated UUIDs must be valid UUID v4.
@@ -1,5 +1,5 @@
import { defineLogicFunction, RoutePayload } from "twenty-sdk";
import { MetadataApiClient } from 'twenty-sdk/generated';
import { MetadataApiClient } from 'twenty-sdk/clients';
export const OAUTH_TOKEN_PAIRS_PATH = '/oauth/token-pairs';
@@ -3,7 +3,7 @@ import {
type DatabaseEventPayload,
type ObjectRecordUpdateEvent,
} from 'twenty-sdk';
import { CoreApiClient } from 'twenty-sdk/generated';
import { CoreApiClient } from 'twenty-sdk/clients';
type CompanyRecord = {
id: string;
@@ -0,0 +1,54 @@
import path from 'path';
import { buildAndValidateManifest } from '@/cli/utilities/build/manifest/build-and-validate-manifest';
import { buildApplication } from '@/cli/utilities/build/common/build-application';
import { manifestUpdateChecksums } from '@/cli/utilities/build/manifest/manifest-update-checksums';
import { writeManifestToOutput } from '@/cli/utilities/build/manifest/manifest-writer';
const FIXTURE_APPS = ['hello-world-app', 'postcard-app', 'minimal-app'];
const buildFixture = async (fixtureName: string): Promise<void> => {
const appPath = path.resolve(__dirname, fixtureName);
console.log(`\n--- Building fixture: ${fixtureName} ---`);
console.log(` App path: ${appPath}`);
const manifestResult = await buildAndValidateManifest(appPath);
if (!manifestResult.success) {
console.error(` FAILED to build manifest: ${manifestResult.errors.join(', ')}`);
return;
}
const { manifest, filePaths } = manifestResult;
for (const warning of manifestResult.warnings) {
console.warn(` Warning: ${warning}`);
}
const buildResult = await buildApplication({ appPath, manifest, filePaths });
const updatedManifest = manifestUpdateChecksums({
manifest,
builtFileInfos: buildResult.builtFileInfos,
});
await writeManifestToOutput(appPath, updatedManifest);
console.log(` Built ${buildResult.builtFileInfos.size} files`);
console.log(` Output: ${path.join(appPath, '.twenty', 'output')}`);
};
const main = async () => {
for (const fixture of FIXTURE_APPS) {
await buildFixture(fixture);
}
console.log('\nDone.');
};
main().catch((error) => {
console.error('Fatal error:', error);
process.exit(1);
});
@@ -1,4 +1,4 @@
import { defineApplication } from '@/sdk';
import { defineApplication } from 'twenty-sdk';
import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from './my.role';
@@ -1,4 +1,4 @@
import { defineRole } from '@/sdk';
import { defineRole } from 'twenty-sdk';
export const DEFAULT_ROLE_UNIVERSAL_IDENTIFIER =
'9f992b6c-17e9-4381-bab5-b6150b574304';
@@ -1,4 +1,4 @@
import { defineLogicFunction } from '@/sdk';
import { defineLogicFunction } from 'twenty-sdk';
export const ADD_NUMBERS_UNIVERSAL_IDENTIFIER =
'f9e5589c-e951-4d99-85db-0a305ab53502';
@@ -3,13 +3,13 @@
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "bundler",
"esModuleInterop": true,
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"jsx": "react-jsx",
"baseUrl": ".",
"paths": {
"@/*": ["../../../../../src/*"]
"src/*": ["./src/*"]
}
},
"include": ["**/*"],
@@ -0,0 +1,2 @@
generated
node_modules
@@ -0,0 +1,404 @@
{
"application": {
"universalIdentifier": "6563e091-9f5b-4026-a3ea-7e3b3d09e218",
"displayName": "Hello world",
"description": "",
"defaultRoleUniversalIdentifier": "9238bc7b-d38f-4a1c-9d19-31ab7bc67a2f",
"yarnLockChecksum": null,
"packageJsonChecksum": null,
"apiClientChecksum": "6db455b2541abbd2d4e9f85469bdc532",
"preInstallLogicFunctionUniversalIdentifier": "1272ffdb-8e2f-492c-ab37-66c2b97e9c23",
"postInstallLogicFunctionUniversalIdentifier": "7a3f4684-51db-494d-833b-a747a3b90507"
},
"objects": [
{
"universalIdentifier": "dfd43356-39b3-4b55-b4a7-279bec689928",
"nameSingular": "exampleItem",
"namePlural": "exampleItems",
"labelSingular": "Example item",
"labelPlural": "Example items",
"description": "A sample custom object",
"icon": "IconBox",
"labelIdentifierFieldMetadataUniversalIdentifier": "d2d7f6cd-33f6-456f-bf00-17adeca926ba",
"fields": [
{
"universalIdentifier": "d2d7f6cd-33f6-456f-bf00-17adeca926ba",
"type": "TEXT",
"name": "name",
"label": "Name",
"description": "Name of the example item",
"icon": "IconAbc"
},
{
"name": "id",
"label": "Id",
"description": "Id",
"icon": "Icon123",
"isNullable": false,
"defaultValue": "uuid",
"type": "UUID",
"universalIdentifier": "5ba62991-6034-555c-b984-5e84897eaa9f"
},
{
"name": "createdAt",
"label": "Creation date",
"description": "Creation date",
"icon": "IconCalendar",
"isNullable": false,
"defaultValue": "now",
"type": "DATE_TIME",
"universalIdentifier": "8b616045-237c-574d-8689-73f06acff8d8"
},
{
"name": "updatedAt",
"label": "Last update",
"description": "Last time the record was changed",
"icon": "IconCalendarClock",
"isNullable": false,
"defaultValue": "now",
"type": "DATE_TIME",
"universalIdentifier": "ced5587d-d9de-51bf-9ad0-74af6198fca4"
},
{
"name": "deletedAt",
"label": "Deleted at",
"description": "Deletion date",
"icon": "IconCalendarClock",
"isNullable": true,
"defaultValue": null,
"type": "DATE_TIME",
"universalIdentifier": "674ee694-0b0f-557f-993c-2b1404db0811"
},
{
"name": "createdBy",
"label": "Created by",
"description": "The creator of the record",
"icon": "IconCreativeCommonsSa",
"isNullable": false,
"defaultValue": {
"name": "''",
"source": "'MANUAL'"
},
"type": "ACTOR",
"universalIdentifier": "5ed1503f-2803-59d1-9b27-e4f1701b8c2a"
},
{
"name": "updatedBy",
"label": "Updated by",
"description": "The workspace member who last updated the record",
"icon": "IconUserCircle",
"isNullable": false,
"defaultValue": {
"name": "''",
"source": "'MANUAL'"
},
"type": "ACTOR",
"universalIdentifier": "3d3a80b4-14a0-55a0-b534-1b23f043c1d5"
},
{
"name": "position",
"label": "Position",
"description": "Position",
"icon": "IconHierarchy2",
"isNullable": false,
"defaultValue": 0,
"type": "POSITION",
"universalIdentifier": "a714b336-d765-5dff-b34b-fcc6ab037e3b"
},
{
"name": "searchVector",
"label": "Search vector",
"icon": "IconSearch",
"description": "Search vector",
"isNullable": true,
"defaultValue": null,
"type": "TS_VECTOR",
"universalIdentifier": "068064e1-c9da-5772-9293-9e57183cee67"
},
{
"name": "timelineActivities",
"label": "Timeline Activities",
"description": "Example items tied to the ExampleItem",
"icon": "IconBuildingSkyscraper",
"isNullable": true,
"type": "RELATION",
"universalSettings": {
"relationType": "ONE_TO_MANY"
},
"universalIdentifier": "e5feca94-536c-5779-8c33-05e39705bcd7",
"relationTargetFieldMetadataUniversalIdentifier": "3d140ea1-fc57-5c8c-96fd-7d1525ed6a69",
"relationTargetObjectMetadataUniversalIdentifier": "20202020-6736-4337-b5c4-8b39fae325a5"
},
{
"name": "favorites",
"label": "Favorites",
"description": "Example items tied to the ExampleItem",
"icon": "IconBuildingSkyscraper",
"isNullable": true,
"type": "RELATION",
"universalSettings": {
"relationType": "ONE_TO_MANY"
},
"universalIdentifier": "3781cc69-9d3f-58d0-927c-c1c552f37a1d",
"relationTargetFieldMetadataUniversalIdentifier": "3043fec0-091c-59ff-95ef-b9d95fbfa3e6",
"relationTargetObjectMetadataUniversalIdentifier": "20202020-ab56-4e05-92a3-e2414a499860"
},
{
"name": "attachments",
"label": "Attachments",
"description": "Example items tied to the ExampleItem",
"icon": "IconBuildingSkyscraper",
"isNullable": true,
"type": "RELATION",
"universalSettings": {
"relationType": "ONE_TO_MANY"
},
"universalIdentifier": "3ad98da8-42e1-50e3-b73b-8c06fa720e2a",
"relationTargetFieldMetadataUniversalIdentifier": "e1232464-8f83-5459-aa94-a40cda8d949a",
"relationTargetObjectMetadataUniversalIdentifier": "20202020-bd3d-4c60-8dca-571c71d4447a"
},
{
"name": "noteTargets",
"label": "Note Targets",
"description": "Example items tied to the ExampleItem",
"icon": "IconBuildingSkyscraper",
"isNullable": true,
"type": "RELATION",
"universalSettings": {
"relationType": "ONE_TO_MANY"
},
"universalIdentifier": "67cae4ab-7d3d-5a66-8d06-ca24568ebe76",
"relationTargetFieldMetadataUniversalIdentifier": "9eeb7b0d-37e4-5a93-97c9-3dbb77fdb318",
"relationTargetObjectMetadataUniversalIdentifier": "20202020-fff0-4b44-be82-bda313884400"
},
{
"name": "taskTargets",
"label": "Task Targets",
"description": "Example items tied to the ExampleItem",
"icon": "IconBuildingSkyscraper",
"isNullable": true,
"type": "RELATION",
"universalSettings": {
"relationType": "ONE_TO_MANY"
},
"universalIdentifier": "a3b9f10c-f780-58cc-bbc9-402ed5f136f6",
"relationTargetFieldMetadataUniversalIdentifier": "5d46d303-452d-5c9c-ab64-0a9768b87956",
"relationTargetObjectMetadataUniversalIdentifier": "20202020-5a9a-44e8-95df-771cd06d0fb1"
}
]
}
],
"fields": [
{
"objectUniversalIdentifier": "dfd43356-39b3-4b55-b4a7-279bec689928",
"universalIdentifier": "770d32c2-cf12-4ab2-b66d-73f92dc239b5",
"type": "NUMBER",
"name": "priority",
"label": "Priority",
"description": "Priority level for the example item (1-10)"
},
{
"name": "targetExampleItem",
"label": "ExampleItem",
"description": "ExampleItem Example item",
"icon": "IconTimelineEvent",
"isNullable": true,
"universalSettings": {
"relationType": "MANY_TO_ONE",
"onDelete": "SET_NULL",
"joinColumnName": "targetExampleItemId"
},
"universalIdentifier": "3d140ea1-fc57-5c8c-96fd-7d1525ed6a69",
"objectUniversalIdentifier": "20202020-6736-4337-b5c4-8b39fae325a5",
"relationTargetFieldMetadataUniversalIdentifier": "e5feca94-536c-5779-8c33-05e39705bcd7",
"relationTargetObjectMetadataUniversalIdentifier": "dfd43356-39b3-4b55-b4a7-279bec689928",
"type": "MORPH_RELATION",
"morphId": "20202020-9a2b-4c3d-a4e5-f6a7b8c9d0e1"
},
{
"name": "targetExampleItem",
"label": "ExampleItem",
"description": "ExampleItem Example item",
"icon": "IconHeart",
"isNullable": true,
"universalSettings": {
"relationType": "MANY_TO_ONE",
"onDelete": "SET_NULL",
"joinColumnName": "targetExampleItemId"
},
"universalIdentifier": "3043fec0-091c-59ff-95ef-b9d95fbfa3e6",
"objectUniversalIdentifier": "20202020-ab56-4e05-92a3-e2414a499860",
"relationTargetFieldMetadataUniversalIdentifier": "3781cc69-9d3f-58d0-927c-c1c552f37a1d",
"relationTargetObjectMetadataUniversalIdentifier": "dfd43356-39b3-4b55-b4a7-279bec689928",
"type": "RELATION"
},
{
"name": "targetExampleItem",
"label": "ExampleItem",
"description": "ExampleItem Example item",
"icon": "IconFileImport",
"isNullable": true,
"universalSettings": {
"relationType": "MANY_TO_ONE",
"onDelete": "SET_NULL",
"joinColumnName": "targetExampleItemId"
},
"universalIdentifier": "e1232464-8f83-5459-aa94-a40cda8d949a",
"objectUniversalIdentifier": "20202020-bd3d-4c60-8dca-571c71d4447a",
"relationTargetFieldMetadataUniversalIdentifier": "3ad98da8-42e1-50e3-b73b-8c06fa720e2a",
"relationTargetObjectMetadataUniversalIdentifier": "dfd43356-39b3-4b55-b4a7-279bec689928",
"type": "MORPH_RELATION",
"morphId": "20202020-f634-435d-ab8d-e1168b375c69"
},
{
"name": "targetExampleItem",
"label": "ExampleItem",
"description": "ExampleItem Example item",
"icon": "IconCheckbox",
"isNullable": true,
"universalSettings": {
"relationType": "MANY_TO_ONE",
"onDelete": "SET_NULL",
"joinColumnName": "targetExampleItemId"
},
"universalIdentifier": "9eeb7b0d-37e4-5a93-97c9-3dbb77fdb318",
"objectUniversalIdentifier": "20202020-fff0-4b44-be82-bda313884400",
"relationTargetFieldMetadataUniversalIdentifier": "67cae4ab-7d3d-5a66-8d06-ca24568ebe76",
"relationTargetObjectMetadataUniversalIdentifier": "dfd43356-39b3-4b55-b4a7-279bec689928",
"type": "MORPH_RELATION",
"morphId": "20202020-f635-435d-ab8d-e1168b375c70"
},
{
"name": "targetExampleItem",
"label": "ExampleItem",
"description": "ExampleItem Example item",
"icon": "IconCheckbox",
"isNullable": true,
"universalSettings": {
"relationType": "MANY_TO_ONE",
"onDelete": "SET_NULL",
"joinColumnName": "targetExampleItemId"
},
"universalIdentifier": "5d46d303-452d-5c9c-ab64-0a9768b87956",
"objectUniversalIdentifier": "20202020-5a9a-44e8-95df-771cd06d0fb1",
"relationTargetFieldMetadataUniversalIdentifier": "a3b9f10c-f780-58cc-bbc9-402ed5f136f6",
"relationTargetObjectMetadataUniversalIdentifier": "dfd43356-39b3-4b55-b4a7-279bec689928",
"type": "MORPH_RELATION",
"morphId": "20202020-f636-435d-ab8d-e1168b375c71"
}
],
"roles": [
{
"universalIdentifier": "9238bc7b-d38f-4a1c-9d19-31ab7bc67a2f",
"label": "Hello world default function role",
"description": "Hello world default function role",
"canReadAllObjectRecords": true,
"canUpdateAllObjectRecords": true,
"canSoftDeleteAllObjectRecords": true,
"canDestroyAllObjectRecords": false
}
],
"skills": [
{
"universalIdentifier": "d0940029-9d3c-40be-903a-52d65393028f",
"name": "example-skill",
"label": "Example Skill",
"description": "A sample skill for your application",
"icon": "IconBrain",
"content": "Add your skill instructions here. Skills provide context and capabilities to AI agents."
}
],
"agents": [],
"logicFunctions": [
{
"universalIdentifier": "2baa26eb-9aaf-4856-a4f4-30d6fd6480ee",
"name": "hello-world-logic-function",
"description": "A simple logic function",
"timeoutSeconds": 5,
"httpRouteTriggerSettings": {
"path": "/hello-world-logic-function",
"httpMethod": "GET",
"isAuthRequired": false
},
"toolInputSchema": {
"type": "object",
"properties": {}
},
"handlerName": "default.config.handler",
"sourceHandlerPath": "src/logic-functions/hello-world.ts",
"builtHandlerPath": "src/logic-functions/hello-world.mjs",
"builtHandlerChecksum": "f1a0565e18366e6c531438631137d23b"
},
{
"universalIdentifier": "7a3f4684-51db-494d-833b-a747a3b90507",
"name": "post-install",
"description": "Runs after installation to set up the application.",
"timeoutSeconds": 300,
"toolInputSchema": {
"type": "object",
"properties": {}
},
"handlerName": "default.config.handler",
"sourceHandlerPath": "src/logic-functions/post-install.ts",
"builtHandlerPath": "src/logic-functions/post-install.mjs",
"builtHandlerChecksum": "7b016a1819c4e2355a80a895464032a5"
},
{
"universalIdentifier": "1272ffdb-8e2f-492c-ab37-66c2b97e9c23",
"name": "pre-install",
"description": "Runs before installation to prepare the application.",
"timeoutSeconds": 300,
"toolInputSchema": {
"type": "object",
"properties": {}
},
"handlerName": "default.config.handler",
"sourceHandlerPath": "src/logic-functions/pre-install.ts",
"builtHandlerPath": "src/logic-functions/pre-install.mjs",
"builtHandlerChecksum": "e2151e31924929390a5933ebf7419465"
}
],
"frontComponents": [
{
"universalIdentifier": "d371f098-5b2c-42f0-898d-94459f1ee337",
"name": "hello-world-front-component",
"description": "A sample front component",
"componentName": "HelloWorld",
"sourceComponentPath": "src/front-components/hello-world.tsx",
"builtComponentPath": "src/front-components/hello-world.mjs",
"builtComponentChecksum": "826d5d3f633496d7c2a4f902589b83ed",
"isHeadless": false
}
],
"publicAssets": [],
"views": [
{
"universalIdentifier": "e004df40-29f3-47ba-b39d-d3a5c444367a",
"name": "All example items",
"objectUniversalIdentifier": "dfd43356-39b3-4b55-b4a7-279bec689928",
"icon": "IconList",
"key": "INDEX",
"position": 0,
"fields": [
{
"universalIdentifier": "496c40c2-5766-419c-93bf-20fdad3f34bb",
"fieldMetadataUniversalIdentifier": "d2d7f6cd-33f6-456f-bf00-17adeca926ba",
"position": 0,
"isVisible": true,
"size": 200
}
]
}
],
"navigationMenuItems": [
{
"universalIdentifier": "10f90627-e9c2-44b7-9742-bed77e3d1b17",
"name": "example-navigation-menu-item",
"icon": "IconList",
"color": "blue",
"position": 0,
"viewUniversalIdentifier": "e004df40-29f3-47ba-b39d-d3a5c444367a"
}
],
"pageLayouts": []
}
@@ -0,0 +1,26 @@
{
"name": "hello-world-app",
"version": "0.1.0",
"license": "MIT",
"engines": {
"node": "^24.5.0",
"npm": "please-use-yarn",
"yarn": ">=4.0.2"
},
"packageManager": "[email protected]",
"scripts": {
"app:dev": "twenty app:dev",
"lint": "oxlint -c .oxlintrc.json .",
"lint:fix": "oxlint --fix -c .oxlintrc.json ."
},
"dependencies": {
"twenty-sdk": "latest"
},
"devDependencies": {
"typescript": "^5.9.3",
"@types/node": "^24.7.2",
"@types/react": "^19.0.2",
"react": "^19.0.2",
"oxlint": "^0.16.0"
}
}
@@ -0,0 +1,12 @@
import { defineApplication } from 'twenty-sdk';
import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from './src/roles/default-role';
export const APPLICATION_UNIVERSAL_IDENTIFIER =
'6563e091-9f5b-4026-a3ea-7e3b3d09e218';
export default defineApplication({
universalIdentifier: APPLICATION_UNIVERSAL_IDENTIFIER,
displayName: 'Hello world',
description: '',
defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
});
@@ -0,0 +1,26 @@
{
"name": "hello-world-app",
"version": "0.1.0",
"license": "MIT",
"engines": {
"node": "^24.5.0",
"npm": "please-use-yarn",
"yarn": ">=4.0.2"
},
"packageManager": "[email protected]",
"scripts": {
"app:dev": "twenty app:dev",
"lint": "oxlint -c .oxlintrc.json .",
"lint:fix": "oxlint --fix -c .oxlintrc.json ."
},
"dependencies": {
"twenty-sdk": "latest"
},
"devDependencies": {
"typescript": "^5.9.3",
"@types/node": "^24.7.2",
"@types/react": "^19.0.2",
"react": "^19.0.2",
"oxlint": "^0.16.0"
}
}
@@ -0,0 +1,11 @@
import { defineField, FieldType } from 'twenty-sdk';
import { EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER } from 'src/objects/example-object';
export default defineField({
objectUniversalIdentifier: EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER,
universalIdentifier: '770d32c2-cf12-4ab2-b66d-73f92dc239b5',
type: FieldType.NUMBER,
name: 'priority',
label: 'Priority',
description: 'Priority level for the example item (1-10)',
});
@@ -0,0 +1,17 @@
import { defineFrontComponent } from 'twenty-sdk';
export const HelloWorld = () => {
return (
<div style={{ padding: '20px', fontFamily: 'sans-serif' }}>
<h1>Hello, World!</h1>
<p>This is your first front component.</p>
</div>
);
};
export default defineFrontComponent({
universalIdentifier: 'd371f098-5b2c-42f0-898d-94459f1ee337',
name: 'hello-world-front-component',
description: 'A sample front component',
component: HelloWorld,
});
@@ -0,0 +1,18 @@
import { defineLogicFunction } from 'twenty-sdk';
const handler = async (): Promise<{ message: string }> => {
return { message: 'Hello, World!' };
};
export default defineLogicFunction({
universalIdentifier: '2baa26eb-9aaf-4856-a4f4-30d6fd6480ee',
name: 'hello-world-logic-function',
description: 'A simple logic function',
timeoutSeconds: 5,
handler,
httpRouteTriggerSettings: {
path: '/hello-world-logic-function',
httpMethod: 'GET',
isAuthRequired: false,
},
});
@@ -0,0 +1,13 @@
import { definePostInstallLogicFunction, type InstallLogicFunctionPayload } from 'twenty-sdk';
const handler = async (payload: InstallLogicFunctionPayload): Promise<void> => {
console.log('Post install logic function executed successfully!', payload.previousVersion);
};
export default definePostInstallLogicFunction({
universalIdentifier: '7a3f4684-51db-494d-833b-a747a3b90507',
name: 'post-install',
description: 'Runs after installation to set up the application.',
timeoutSeconds: 300,
handler,
});
@@ -0,0 +1,13 @@
import { definePreInstallLogicFunction, type InstallLogicFunctionPayload } from 'twenty-sdk';
const handler = async (payload: InstallLogicFunctionPayload): Promise<void> => {
console.log('Pre install logic function executed successfully!', payload.previousVersion);
};
export default definePreInstallLogicFunction({
universalIdentifier: '1272ffdb-8e2f-492c-ab37-66c2b97e9c23',
name: 'pre-install',
description: 'Runs before installation to prepare the application.',
timeoutSeconds: 300,
handler,
});
@@ -0,0 +1,11 @@
import { defineNavigationMenuItem } from 'twenty-sdk';
import { EXAMPLE_VIEW_UNIVERSAL_IDENTIFIER } from 'src/views/example-view';
export default defineNavigationMenuItem({
universalIdentifier: '10f90627-e9c2-44b7-9742-bed77e3d1b17',
name: 'example-navigation-menu-item',
icon: 'IconList',
color: 'blue',
position: 0,
viewUniversalIdentifier: EXAMPLE_VIEW_UNIVERSAL_IDENTIFIER,
});
@@ -0,0 +1,28 @@
import { defineObject, FieldType } from 'twenty-sdk';
export const EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER =
'dfd43356-39b3-4b55-b4a7-279bec689928';
export const NAME_FIELD_UNIVERSAL_IDENTIFIER =
'd2d7f6cd-33f6-456f-bf00-17adeca926ba';
export default defineObject({
universalIdentifier: EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER,
nameSingular: 'exampleItem',
namePlural: 'exampleItems',
labelSingular: 'Example item',
labelPlural: 'Example items',
description: 'A sample custom object',
icon: 'IconBox',
labelIdentifierFieldMetadataUniversalIdentifier: NAME_FIELD_UNIVERSAL_IDENTIFIER,
fields: [
{
universalIdentifier: NAME_FIELD_UNIVERSAL_IDENTIFIER,
type: FieldType.TEXT,
name: 'name',
label: 'Name',
description: 'Name of the example item',
icon: 'IconAbc',
},
],
});
@@ -0,0 +1,14 @@
import { defineRole } from 'twenty-sdk';
export const DEFAULT_ROLE_UNIVERSAL_IDENTIFIER =
'9238bc7b-d38f-4a1c-9d19-31ab7bc67a2f';
export default defineRole({
universalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
label: 'Hello world default function role',
description: 'Hello world default function role',
canReadAllObjectRecords: true,
canUpdateAllObjectRecords: true,
canSoftDeleteAllObjectRecords: true,
canDestroyAllObjectRecords: false,
});
@@ -0,0 +1,13 @@
import { defineSkill } from 'twenty-sdk';
export const EXAMPLE_SKILL_UNIVERSAL_IDENTIFIER =
'd0940029-9d3c-40be-903a-52d65393028f';
export default defineSkill({
universalIdentifier: EXAMPLE_SKILL_UNIVERSAL_IDENTIFIER,
name: 'example-skill',
label: 'Example Skill',
description: 'A sample skill for your application',
icon: 'IconBrain',
content: 'Add your skill instructions here. Skills provide context and capabilities to AI agents.',
});
@@ -0,0 +1,22 @@
import { defineView } from 'twenty-sdk';
import { EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER, NAME_FIELD_UNIVERSAL_IDENTIFIER } from 'src/objects/example-object';
export const EXAMPLE_VIEW_UNIVERSAL_IDENTIFIER = 'e004df40-29f3-47ba-b39d-d3a5c444367a';
export default defineView({
universalIdentifier: EXAMPLE_VIEW_UNIVERSAL_IDENTIFIER,
name: 'All example items',
objectUniversalIdentifier: EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER,
icon: 'IconList',
key: 'INDEX',
position: 0,
fields: [
{
universalIdentifier: '496c40c2-5766-419c-93bf-20fdad3f34bb',
fieldMetadataUniversalIdentifier: NAME_FIELD_UNIVERSAL_IDENTIFIER,
position: 0,
isVisible: true,
size: 200,
},
],
});
@@ -9,7 +9,7 @@
"jsx": "react-jsx",
"baseUrl": ".",
"paths": {
"@/*": ["../../../../../src/*"]
"src/*": ["./src/*"]
}
},
"include": ["**/*"],
@@ -1,4 +1,4 @@
import { defineApplication } from '@/sdk';
import { defineApplication } from 'twenty-sdk';
export default defineApplication({
universalIdentifier: 'invalid-app-0000-0000-0000-000000000001',
@@ -1,4 +1,4 @@
import { defineObject, FieldType } from '@/sdk';
import { defineObject, FieldType } from 'twenty-sdk';
const DUPLICATE_ID = 'duplicate-id-0000-0000-000000000001';
@@ -1,4 +1,4 @@
import { defineObject, FieldType } from '@/sdk';
import { defineObject, FieldType } from 'twenty-sdk';
const DUPLICATE_ID = 'duplicate-id-0000-0000-000000000001';
@@ -9,8 +9,9 @@
"jsx": "react-jsx",
"baseUrl": ".",
"paths": {
"@/*": ["../../../../../src/*"]
"src/*": ["./src/*"]
}
},
"include": ["**/*"]
"include": ["**/*"],
"exclude": ["node_modules", "dist", ".twenty"]
}
@@ -0,0 +1,342 @@
{
"application": {
"universalIdentifier": "e1e2e3e4-e5e6-4000-8000-000000000001",
"displayName": "Root App",
"description": "An app with all entities at root level",
"icon": "IconFolder",
"defaultRoleUniversalIdentifier": "e1e2e3e4-e5e6-4000-8000-000000000002",
"yarnLockChecksum": "d41d8cd98f00b204e9800998ecf8427e",
"packageJsonChecksum": "ac87abb5113d9d09be71481efc123800",
"apiClientChecksum": null
},
"objects": [
{
"universalIdentifier": "e1e2e3e4-e5e6-4000-8000-000000000030",
"nameSingular": "myNote",
"namePlural": "myNotes",
"labelSingular": "My note",
"labelPlural": "My notes",
"description": "A simple root-level object",
"icon": "IconNote",
"labelIdentifierFieldMetadataUniversalIdentifier": "e1e2e3e4-e5e6-4000-8000-000000000031",
"fields": [
{
"universalIdentifier": "e1e2e3e4-e5e6-4000-8000-000000000031",
"type": "TEXT",
"label": "Title",
"name": "title"
},
{
"name": "id",
"label": "Id",
"description": "Id",
"icon": "Icon123",
"isNullable": false,
"defaultValue": "uuid",
"type": "UUID",
"universalIdentifier": "5bdb4f57-a349-56d0-b421-845fd55b3026"
},
{
"name": "name",
"label": "Name",
"description": "Name",
"icon": "IconAbc",
"isNullable": true,
"defaultValue": null,
"type": "TEXT",
"universalIdentifier": "86cc7b06-26de-5b7b-bf4a-31fa246dfd90"
},
{
"name": "createdAt",
"label": "Creation date",
"description": "Creation date",
"icon": "IconCalendar",
"isNullable": false,
"defaultValue": "now",
"type": "DATE_TIME",
"universalIdentifier": "abe3bc69-1a51-5814-a722-74f5a025e92a"
},
{
"name": "updatedAt",
"label": "Last update",
"description": "Last time the record was changed",
"icon": "IconCalendarClock",
"isNullable": false,
"defaultValue": "now",
"type": "DATE_TIME",
"universalIdentifier": "c296d408-9720-5a86-8586-3490fbf04b86"
},
{
"name": "deletedAt",
"label": "Deleted at",
"description": "Deletion date",
"icon": "IconCalendarClock",
"isNullable": true,
"defaultValue": null,
"type": "DATE_TIME",
"universalIdentifier": "488333a2-f4ff-5790-88ef-21f07f8f1985"
},
{
"name": "createdBy",
"label": "Created by",
"description": "The creator of the record",
"icon": "IconCreativeCommonsSa",
"isNullable": false,
"defaultValue": {
"name": "''",
"source": "'MANUAL'"
},
"type": "ACTOR",
"universalIdentifier": "fba6e01b-6579-554e-ad64-a676003c8e68"
},
{
"name": "updatedBy",
"label": "Updated by",
"description": "The workspace member who last updated the record",
"icon": "IconUserCircle",
"isNullable": false,
"defaultValue": {
"name": "''",
"source": "'MANUAL'"
},
"type": "ACTOR",
"universalIdentifier": "fa60f87d-15c8-5ff7-9da0-191fa14a922f"
},
{
"name": "position",
"label": "Position",
"description": "Position",
"icon": "IconHierarchy2",
"isNullable": false,
"defaultValue": 0,
"type": "POSITION",
"universalIdentifier": "4a5d7791-ddc9-53ee-bc46-699da7373290"
},
{
"name": "searchVector",
"label": "Search vector",
"icon": "IconSearch",
"description": "Search vector",
"isNullable": true,
"defaultValue": null,
"type": "TS_VECTOR",
"universalIdentifier": "0f00bc3b-f313-5206-8496-842af20a538a"
},
{
"name": "timelineActivities",
"label": "Timeline Activities",
"description": "My notes tied to the MyNote",
"icon": "IconBuildingSkyscraper",
"isNullable": true,
"type": "RELATION",
"universalSettings": {
"relationType": "ONE_TO_MANY"
},
"universalIdentifier": "12c84c1c-6e3f-54c5-a9a8-e93c7b46ac35",
"relationTargetFieldMetadataUniversalIdentifier": "7e28b1ea-9a94-5d36-bc03-3f12f476867a",
"relationTargetObjectMetadataUniversalIdentifier": "20202020-6736-4337-b5c4-8b39fae325a5"
},
{
"name": "favorites",
"label": "Favorites",
"description": "My notes tied to the MyNote",
"icon": "IconBuildingSkyscraper",
"isNullable": true,
"type": "RELATION",
"universalSettings": {
"relationType": "ONE_TO_MANY"
},
"universalIdentifier": "ed80c73b-25ff-5a3c-99c7-e27fa2f2611e",
"relationTargetFieldMetadataUniversalIdentifier": "698f12fe-3feb-55e3-b6cc-73375f1a8ae6",
"relationTargetObjectMetadataUniversalIdentifier": "20202020-ab56-4e05-92a3-e2414a499860"
},
{
"name": "attachments",
"label": "Attachments",
"description": "My notes tied to the MyNote",
"icon": "IconBuildingSkyscraper",
"isNullable": true,
"type": "RELATION",
"universalSettings": {
"relationType": "ONE_TO_MANY"
},
"universalIdentifier": "56a872c8-7a1e-563d-8772-3978c2c5c220",
"relationTargetFieldMetadataUniversalIdentifier": "721b12d8-7a3d-5e13-b8d5-62b3ad8cb8b5",
"relationTargetObjectMetadataUniversalIdentifier": "20202020-bd3d-4c60-8dca-571c71d4447a"
},
{
"name": "noteTargets",
"label": "Note Targets",
"description": "My notes tied to the MyNote",
"icon": "IconBuildingSkyscraper",
"isNullable": true,
"type": "RELATION",
"universalSettings": {
"relationType": "ONE_TO_MANY"
},
"universalIdentifier": "011595e8-7524-5ec7-9fbc-4bd3c42d7d24",
"relationTargetFieldMetadataUniversalIdentifier": "94b6f269-fc9a-5bcd-af5b-2bdac69f276b",
"relationTargetObjectMetadataUniversalIdentifier": "20202020-fff0-4b44-be82-bda313884400"
},
{
"name": "taskTargets",
"label": "Task Targets",
"description": "My notes tied to the MyNote",
"icon": "IconBuildingSkyscraper",
"isNullable": true,
"type": "RELATION",
"universalSettings": {
"relationType": "ONE_TO_MANY"
},
"universalIdentifier": "dd734fa3-26c3-53dd-91aa-577c313e1fa4",
"relationTargetFieldMetadataUniversalIdentifier": "50da3cfa-dea5-5c9b-b1b7-cb615e1df4c3",
"relationTargetObjectMetadataUniversalIdentifier": "20202020-5a9a-44e8-95df-771cd06d0fb1"
}
]
}
],
"fields": [
{
"name": "targetMyNote",
"label": "MyNote",
"description": "MyNote My note",
"icon": "IconTimelineEvent",
"isNullable": true,
"universalSettings": {
"relationType": "MANY_TO_ONE",
"onDelete": "SET_NULL",
"joinColumnName": "targetMyNoteId"
},
"universalIdentifier": "7e28b1ea-9a94-5d36-bc03-3f12f476867a",
"objectUniversalIdentifier": "20202020-6736-4337-b5c4-8b39fae325a5",
"relationTargetFieldMetadataUniversalIdentifier": "12c84c1c-6e3f-54c5-a9a8-e93c7b46ac35",
"relationTargetObjectMetadataUniversalIdentifier": "e1e2e3e4-e5e6-4000-8000-000000000030",
"type": "MORPH_RELATION",
"morphId": "20202020-9a2b-4c3d-a4e5-f6a7b8c9d0e1"
},
{
"name": "targetMyNote",
"label": "MyNote",
"description": "MyNote My note",
"icon": "IconHeart",
"isNullable": true,
"universalSettings": {
"relationType": "MANY_TO_ONE",
"onDelete": "SET_NULL",
"joinColumnName": "targetMyNoteId"
},
"universalIdentifier": "698f12fe-3feb-55e3-b6cc-73375f1a8ae6",
"objectUniversalIdentifier": "20202020-ab56-4e05-92a3-e2414a499860",
"relationTargetFieldMetadataUniversalIdentifier": "ed80c73b-25ff-5a3c-99c7-e27fa2f2611e",
"relationTargetObjectMetadataUniversalIdentifier": "e1e2e3e4-e5e6-4000-8000-000000000030",
"type": "RELATION"
},
{
"name": "targetMyNote",
"label": "MyNote",
"description": "MyNote My note",
"icon": "IconFileImport",
"isNullable": true,
"universalSettings": {
"relationType": "MANY_TO_ONE",
"onDelete": "SET_NULL",
"joinColumnName": "targetMyNoteId"
},
"universalIdentifier": "721b12d8-7a3d-5e13-b8d5-62b3ad8cb8b5",
"objectUniversalIdentifier": "20202020-bd3d-4c60-8dca-571c71d4447a",
"relationTargetFieldMetadataUniversalIdentifier": "56a872c8-7a1e-563d-8772-3978c2c5c220",
"relationTargetObjectMetadataUniversalIdentifier": "e1e2e3e4-e5e6-4000-8000-000000000030",
"type": "MORPH_RELATION",
"morphId": "20202020-f634-435d-ab8d-e1168b375c69"
},
{
"name": "targetMyNote",
"label": "MyNote",
"description": "MyNote My note",
"icon": "IconCheckbox",
"isNullable": true,
"universalSettings": {
"relationType": "MANY_TO_ONE",
"onDelete": "SET_NULL",
"joinColumnName": "targetMyNoteId"
},
"universalIdentifier": "94b6f269-fc9a-5bcd-af5b-2bdac69f276b",
"objectUniversalIdentifier": "20202020-fff0-4b44-be82-bda313884400",
"relationTargetFieldMetadataUniversalIdentifier": "011595e8-7524-5ec7-9fbc-4bd3c42d7d24",
"relationTargetObjectMetadataUniversalIdentifier": "e1e2e3e4-e5e6-4000-8000-000000000030",
"type": "MORPH_RELATION",
"morphId": "20202020-f635-435d-ab8d-e1168b375c70"
},
{
"name": "targetMyNote",
"label": "MyNote",
"description": "MyNote My note",
"icon": "IconCheckbox",
"isNullable": true,
"universalSettings": {
"relationType": "MANY_TO_ONE",
"onDelete": "SET_NULL",
"joinColumnName": "targetMyNoteId"
},
"universalIdentifier": "50da3cfa-dea5-5c9b-b1b7-cb615e1df4c3",
"objectUniversalIdentifier": "20202020-5a9a-44e8-95df-771cd06d0fb1",
"relationTargetFieldMetadataUniversalIdentifier": "dd734fa3-26c3-53dd-91aa-577c313e1fa4",
"relationTargetObjectMetadataUniversalIdentifier": "e1e2e3e4-e5e6-4000-8000-000000000030",
"type": "MORPH_RELATION",
"morphId": "20202020-f636-435d-ab8d-e1168b375c71"
}
],
"roles": [
{
"universalIdentifier": "e1e2e3e4-e5e6-4000-8000-000000000040",
"label": "My role",
"description": "A simple root-level role",
"canReadAllObjectRecords": true,
"canUpdateAllObjectRecords": false,
"canSoftDeleteAllObjectRecords": false,
"canDestroyAllObjectRecords": false,
"canUpdateAllSettings": false,
"canBeAssignedToAgents": false,
"canBeAssignedToUsers": true,
"canBeAssignedToApiKeys": false
}
],
"skills": [],
"agents": [],
"logicFunctions": [
{
"universalIdentifier": "e1e2e3e4-e5e6-4000-8000-000000000010",
"name": "my-function",
"timeoutSeconds": 5,
"httpRouteTriggerSettings": {
"path": "/my-function",
"httpMethod": "GET",
"isAuthRequired": false
},
"toolInputSchema": {
"type": "object",
"properties": {}
},
"handlerName": "default.config.handler",
"sourceHandlerPath": "my.function.ts",
"builtHandlerPath": "my.function.mjs",
"builtHandlerChecksum": "71f0af6b9dfeef884cc81ae75ca6ecb0"
}
],
"frontComponents": [
{
"universalIdentifier": "e1e2e3e4-e5e6-4000-8000-000000000020",
"name": "my-component",
"description": "A root-level front component",
"componentName": "MyComponent",
"sourceComponentPath": "my.front-component.tsx",
"builtComponentPath": "my.front-component.mjs",
"builtComponentChecksum": "37b905c5467584f03fa5ab2c8a05bc88",
"isHeadless": false
}
],
"publicAssets": [],
"views": [],
"navigationMenuItems": [],
"pageLayouts": []
}
@@ -1,5 +1,5 @@
{
"name": "root-app",
"name": "minimal-app",
"version": "0.1.0",
"license": "MIT",
"engines": {
@@ -1,4 +1,4 @@
import { defineApplication } from '@/sdk';
import { defineApplication } from 'twenty-sdk';
export default defineApplication({
universalIdentifier: 'e1e2e3e4-e5e6-4000-8000-000000000001',
@@ -1,4 +1,4 @@
import { defineFrontComponent } from '@/sdk';
import { defineFrontComponent } from 'twenty-sdk';
export const MyComponent = () => {
return (
@@ -1,4 +1,4 @@
import { defineLogicFunction } from '@/sdk';
import { defineLogicFunction } from 'twenty-sdk';
const myHandler = () => {
return 'my-function-result';
@@ -1,4 +1,4 @@
import { FieldType, defineObject } from '@/sdk';
import { FieldType, defineObject } from 'twenty-sdk';
export default defineObject({
universalIdentifier: 'e1e2e3e4-e5e6-4000-8000-000000000030',
@@ -1,4 +1,4 @@
import { defineRole } from '@/sdk';
import { defineRole } from 'twenty-sdk';
export default defineRole({
universalIdentifier: 'e1e2e3e4-e5e6-4000-8000-000000000040',
@@ -1,5 +1,5 @@
{
"name": "rich-app",
"name": "minimal-app",
"version": "0.1.0",
"license": "MIT",
"engines": {
@@ -9,8 +9,9 @@
"jsx": "react-jsx",
"baseUrl": ".",
"paths": {
"@/*": ["../../../../../src/*"]
"src/*": ["./src/*"]
}
},
"include": ["**/*"]
"include": ["**/*"],
"exclude": ["node_modules", "dist", ".twenty"]
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,36 @@
{
"name": "postcard-app",
"version": "0.1.0",
"license": "MIT",
"engines": {
"node": "^24.5.0",
"npm": "please-use-yarn",
"yarn": ">=4.0.2"
},
"packageManager": "[email protected]",
"scripts": {
"auth:login": "twenty auth:login",
"auth:logout": "twenty auth:logout",
"auth:status": "twenty auth:status",
"auth:switch": "twenty auth:switch",
"auth:list": "twenty auth:list",
"app:dev": "twenty app:dev",
"entity:add": "twenty entity:add",
"function:logs": "twenty function:logs",
"function:execute": "twenty function:execute",
"app:uninstall": "twenty app:uninstall",
"help": "twenty help",
"lint": "oxlint -c .oxlintrc.json .",
"lint:fix": "oxlint --fix -c .oxlintrc.json ."
},
"dependencies": {
"twenty-sdk": "latest"
},
"devDependencies": {
"typescript": "^5.9.3",
"@types/node": "^24.7.2",
"@types/react": "^19.0.2",
"react": "^19.0.2",
"oxlint": "^0.16.0"
}
}
@@ -1,4 +1,4 @@
import { defineApplication } from '@/sdk';
import { defineApplication } from 'twenty-sdk';
import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from './src/roles/default-function.role';
export default defineApplication({
@@ -0,0 +1,36 @@
{
"name": "postcard-app",
"version": "0.1.0",
"license": "MIT",
"engines": {
"node": "^24.5.0",
"npm": "please-use-yarn",
"yarn": ">=4.0.2"
},
"packageManager": "[email protected]",
"scripts": {
"auth:login": "twenty auth:login",
"auth:logout": "twenty auth:logout",
"auth:status": "twenty auth:status",
"auth:switch": "twenty auth:switch",
"auth:list": "twenty auth:list",
"app:dev": "twenty app:dev",
"entity:add": "twenty entity:add",
"function:logs": "twenty function:logs",
"function:execute": "twenty function:execute",
"app:uninstall": "twenty app:uninstall",
"help": "twenty help",
"lint": "oxlint -c .oxlintrc.json .",
"lint:fix": "oxlint --fix -c .oxlintrc.json ."
},
"dependencies": {
"twenty-sdk": "latest"
},
"devDependencies": {
"typescript": "^5.9.3",
"@types/node": "^24.7.2",
"@types/react": "^19.0.2",
"react": "^19.0.2",
"oxlint": "^0.16.0"
}
}

Before

Width:  |  Height:  |  Size: 570 B

After

Width:  |  Height:  |  Size: 570 B

@@ -1,4 +1,4 @@
import { defineFrontComponent } from '@/sdk';
import { defineFrontComponent } from 'twenty-sdk';
import { CardDisplay } from '../utils/card-display.component';
export default defineFrontComponent({
@@ -1,4 +1,4 @@
import { defineFrontComponent } from '@/sdk';
import { defineFrontComponent } from 'twenty-sdk';
import { DEFAULT_NAME, formatGreeting } from '../utils/greeting.util';
const GreetingComponent = () => {
@@ -1,4 +1,4 @@
import { defineFrontComponent } from '@/sdk';
import { defineFrontComponent } from 'twenty-sdk';
export const TestComponent = () => {
return (
@@ -1,10 +1,10 @@
import {
POST_CARD_ON_POST_CARD_RECIPIENT_ID,
POST_CARD_RECIPIENTS_ON_POST_CARD_ID,
} from '@/cli/__tests__/apps/rich-app/src/fields/post-card-recipients-on-post-card.field';
import { POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER } from '@/cli/__tests__/apps/rich-app/src/objects/post-card-recipient.object';
import { defineField, FieldType, OnDeleteAction, RelationType } from '@/sdk';
import { POST_CARD_UNIVERSAL_IDENTIFIER } from '@/cli/__tests__/apps/rich-app/src/objects/post-card.object';
} from './post-card-recipients-on-post-card.field';
import { POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER } from '../objects/post-card-recipient.object';
import { defineField, FieldType, OnDeleteAction, RelationType } from 'twenty-sdk';
import { POST_CARD_UNIVERSAL_IDENTIFIER } from '../objects/post-card.object';
export default defineField({
universalIdentifier: POST_CARD_ON_POST_CARD_RECIPIENT_ID,
@@ -1,6 +1,6 @@
import { POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER } from '@/cli/__tests__/apps/rich-app/src/objects/post-card-recipient.object';
import { defineField, FieldType, RelationType } from '@/sdk';
import { POST_CARD_UNIVERSAL_IDENTIFIER } from '@/cli/__tests__/apps/rich-app/src/objects/post-card.object';
import { POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER } from '../objects/post-card-recipient.object';
import { defineField, FieldType, RelationType } from 'twenty-sdk';
import { POST_CARD_UNIVERSAL_IDENTIFIER } from '../objects/post-card.object';
export const POST_CARD_RECIPIENTS_ON_POST_CARD_ID =
'a1a2b3c4-0001-4a7b-8c9d-0e1f2a3b4c5d';
@@ -1,6 +1,6 @@
import { defineField, FieldType, RelationType } from '@/sdk';
import { RECIPIENT_UNIVERSAL_IDENTIFIER } from '@/cli/__tests__/apps/rich-app/src/objects/recipient.object';
import { POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER } from '@/cli/__tests__/apps/rich-app/src/objects/post-card-recipient.object';
import { defineField, FieldType, RelationType } from 'twenty-sdk';
import { RECIPIENT_UNIVERSAL_IDENTIFIER } from '../objects/recipient.object';
import { POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER } from '../objects/post-card-recipient.object';
export const POST_CARD_RECIPIENTS_ON_RECIPIENT_ID =
'a1a2b3c4-0003-4a7b-8c9d-0e1f2a3b4c5d';
@@ -1,5 +1,5 @@
import { defineField, FieldType } from '@/sdk';
import { POST_CARD_UNIVERSAL_IDENTIFIER } from '@/cli/__tests__/apps/rich-app/src/objects/post-card.object';
import { defineField, FieldType } from 'twenty-sdk';
import { POST_CARD_UNIVERSAL_IDENTIFIER } from '../objects/post-card.object';
export default defineField({
objectUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER,
@@ -1,5 +1,5 @@
import { defineField, FieldType } from '@/sdk';
import { POST_CARD_UNIVERSAL_IDENTIFIER } from '@/cli/__tests__/apps/rich-app/src/objects/post-card.object';
import { defineField, FieldType } from 'twenty-sdk';
import { POST_CARD_UNIVERSAL_IDENTIFIER } from '../objects/post-card.object';
export default defineField({
objectUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER,
@@ -1,10 +1,10 @@
import { defineField, FieldType, RelationType, OnDeleteAction } from '@/sdk';
import { RECIPIENT_UNIVERSAL_IDENTIFIER } from '@/cli/__tests__/apps/rich-app/src/objects/recipient.object';
import { POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER } from '@/cli/__tests__/apps/rich-app/src/objects/post-card-recipient.object';
import { defineField, FieldType, RelationType, OnDeleteAction } from 'twenty-sdk';
import { RECIPIENT_UNIVERSAL_IDENTIFIER } from '../objects/recipient.object';
import { POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER } from '../objects/post-card-recipient.object';
import {
POST_CARD_RECIPIENTS_ON_RECIPIENT_ID,
RECIPIENT_ON_POST_CARD_RECIPIENT_ID,
} from '@/cli/__tests__/apps/rich-app/src/fields/post-card-recipients-on-recipient.field';
} from './post-card-recipients-on-recipient.field';
export default defineField({
universalIdentifier: RECIPIENT_ON_POST_CARD_RECIPIENT_ID,
@@ -1,4 +1,4 @@
import { defineLogicFunction } from '@/sdk';
import { defineLogicFunction } from 'twenty-sdk';
import { DEFAULT_NAME, formatGreeting } from '../utils/greeting.util';
const greetingHandler = () => {
@@ -1,4 +1,4 @@
import { defineLogicFunction } from '@/sdk';
import { defineLogicFunction } from 'twenty-sdk';
const handler = async (params: { recipientName: string }) => {
return { found: true, name: params.recipientName };
@@ -1,4 +1,4 @@
import { defineLogicFunction } from '@/sdk';
import { defineLogicFunction } from 'twenty-sdk';
const handler = async () => {
return { processed: true };
@@ -1,4 +1,4 @@
import { defineLogicFunction } from '@/sdk';
import { defineLogicFunction } from 'twenty-sdk';
import { testFunction2 } from '../utils/test-function-2.util';
export default defineLogicFunction({
@@ -1,4 +1,4 @@
import { defineLogicFunction } from '@/sdk';
import { defineLogicFunction } from 'twenty-sdk';
import { formatFarewell } from '../utils/greeting.util';
const handler = () => {
@@ -1,5 +1,5 @@
import { defineNavigationMenuItem } from '@/sdk';
import { ALL_POST_CARD_RECIPIENTS_VIEW_ID } from '@/cli/__tests__/apps/rich-app/src/views/all-post-card-recipients.view';
import { defineNavigationMenuItem } from 'twenty-sdk';
import { ALL_POST_CARD_RECIPIENTS_VIEW_ID } from '../views/all-post-card-recipients.view';
export default defineNavigationMenuItem({
universalIdentifier: 'c1a2b3c4-0003-4a7b-8c9d-0e1f2a3b4c5d',
@@ -1,5 +1,5 @@
import { defineNavigationMenuItem } from '@/sdk';
import { ALL_POST_CARDS_VIEW_ID } from '@/cli/__tests__/apps/rich-app/src/views/all-post-cards.view';
import { defineNavigationMenuItem } from 'twenty-sdk';
import { ALL_POST_CARDS_VIEW_ID } from '../views/all-post-cards.view';
export default defineNavigationMenuItem({
universalIdentifier: 'c1a2b3c4-0001-4a7b-8c9d-0e1f2a3b4c5d',
@@ -1,5 +1,5 @@
import { defineNavigationMenuItem } from '@/sdk';
import { ALL_RECIPIENTS_VIEW_ID } from '@/cli/__tests__/apps/rich-app/src/views/all-recipients.view';
import { defineNavigationMenuItem } from 'twenty-sdk';
import { ALL_RECIPIENTS_VIEW_ID } from '../views/all-recipients.view';
export default defineNavigationMenuItem({
universalIdentifier: 'c1a2b3c4-0002-4a7b-8c9d-0e1f2a3b4c5d',
@@ -1,4 +1,4 @@
import { defineObject, FieldType } from '@/sdk';
import { defineObject, FieldType } from 'twenty-sdk';
export const POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER =
'e1a2b3c4-5e6f-4a7b-8c9d-0e1f2a3b4c5e';
@@ -1,4 +1,4 @@
import { defineObject, FieldType } from '@/sdk';
import { defineObject, FieldType } from 'twenty-sdk';
enum PostCardStatus {
DRAFT = 'DRAFT',
@@ -1,4 +1,4 @@
import { defineObject, FieldType } from '@/sdk';
import { defineObject, FieldType } from 'twenty-sdk';
export const RECIPIENT_UNIVERSAL_IDENTIFIER =
'd1a2b3c4-5e6f-4a7b-8c9d-0e1f2a3b4c5d';
@@ -1,8 +1,8 @@
import { PermissionFlag, defineRole } from '@/sdk';
import { PermissionFlag, defineRole } from 'twenty-sdk';
import {
CONTENT_FIELD_UNIVERSAL_IDENTIFIER,
POST_CARD_UNIVERSAL_IDENTIFIER,
} from '@/cli/__tests__/apps/rich-app/src/objects/post-card.object';
} from '../objects/post-card.object';
export const DEFAULT_ROLE_UNIVERSAL_IDENTIFIER =
'b648f87b-1d26-4961-b974-0908fd991061';
@@ -1,4 +1,4 @@
import { defineFrontComponent } from '@/sdk';
import { defineFrontComponent } from 'twenty-sdk';
export const RootComponent = () => {
return (
@@ -1,4 +1,4 @@
import { defineLogicFunction } from '@/sdk';
import { defineLogicFunction } from 'twenty-sdk';
const rootHandler = () => {
return 'root-function-result';
@@ -1,4 +1,4 @@
import { defineObject, FieldType } from '@/sdk';
import { defineObject, FieldType } from 'twenty-sdk';
export default defineObject({
universalIdentifier: 'b0b1b2b3-b4b5-4000-8000-000000000001',
@@ -1,4 +1,4 @@
import { defineRole } from '@/sdk';
import { defineRole } from 'twenty-sdk';
export default defineRole({
universalIdentifier: 'c0c1c2c3-c4c5-4000-8000-000000000001',

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