Compare commits

..
Author SHA1 Message Date
Sonarly Claude Code 0764eb55d7 Relaunch job doesn't reset throttleFailureCount, causing fail-relaunch loop on Gmail rate limits
https://sonarly.com/issue/3777?type=bug

When Gmail returns a user-rate limit error (HTTP 429) and a message channel exhausts 5 retry attempts, it's marked as FAILED_UNKNOWN. The auto-relaunch cron (every 30 min) restores the channel without resetting throttleFailureCount, so any subsequent rate-limit hit immediately re-fails the channel and fires another Sentry alert, creating an infinite fail→relaunch→fail cycle.

Fix: The fix adds `throttleFailureCount: 0` and `throttleRetryAfter: null` to the update query in the relaunch job, so that when a failed channel is relaunched, its throttle state is fully reset alongside `syncStage` and `syncStatus`.

Without this fix, after relaunch the channel still has `throttleFailureCount >= 5`, so the very next Gmail rate-limit response immediately re-triggers `handleTemporaryException`'s failure path (`count >= MESSAGING_THROTTLE_MAX_ATTEMPTS`), marks the channel `FAILED_UNKNOWN` again, fires another Sentry alert, and perpetuates the infinite fail→relaunch→fail cycle.

```typescript file=packages/twenty-server/src/modules/messaging/message-import-manager/jobs/messaging-relaunch-failed-message-channel.job.ts lines=61-66
await messageChannelRepository.update(messageChannelId, {
  syncStage: MessageChannelSyncStage.MESSAGE_LIST_FETCH_PENDING,
  syncStatus: MessageChannelSyncStatus.ACTIVE,
  throttleFailureCount: 0,
  throttleRetryAfter: null,
});
```
2026-03-04 20:50:36 +00:00
2855 changed files with 41810 additions and 57395 deletions
+1 -1
View File
@@ -8,7 +8,7 @@ alwaysApply: true
## Formatting Standards
- **Prettier**: 2-space indentation, single quotes, trailing commas, semicolons
- **Print width**: 80 characters
- **Oxlint**: No unused imports, consistent import ordering, prefer const over let
- **ESLint**: No unused imports, consistent import ordering, prefer const over let
## Naming Conventions
```typescript
-53
View File
@@ -1,53 +0,0 @@
---
description: ESM dependency guidelines for twenty-sdk and create-twenty-app packages
globs: ["packages/twenty-sdk/**", "packages/create-twenty-app/**"]
alwaysApply: false
---
# ESM Dependency Guidelines
## Context
`twenty-sdk` and `create-twenty-app` are published as dual-format npm packages (ESM `.mjs` + CJS `.cjs`). Dependencies listed in `dependencies` are **externalized** by the Vite/Rollup build — they are not bundled, and consumers resolve them from `node_modules` at runtime.
This means **CJS-only dependencies break the ESM output**. When Rollup emits `import { foo } from 'cjs-package'`, Node.js ESM cannot resolve named exports from CommonJS modules, causing `SyntaxError: Named export 'foo' not found`.
## Rules
### Only add ESM-compatible dependencies
Before adding a new dependency to `package.json`, verify it supports ESM:
- Check for `"type": "module"` in its `package.json`
- Or check for an `"exports"` map with ESM entries
- Or check for a `"module"` field pointing to an ESM build
### Use native `node:fs/promises` for standard fs operations
```typescript
// ✅ Import native fs functions directly
import { readFile, writeFile, mkdir, rm, cp } from 'node:fs/promises';
import { createWriteStream, existsSync } from 'node:fs';
// ✅ Import only custom helpers from fs-utils (no native re-exports)
import { pathExists, ensureDir, emptyDir, copy, move, remove, readJson, writeJson, ensureFile } from '@/cli/utilities/file/fs-utils';
// ❌ Don't use fs-extra (CJS-only, breaks ESM bundle)
import * as fs from 'fs-extra';
// ❌ Don't use import * as fs from fs-utils (it doesn't re-export native fs)
import * as fs from '@/cli/utilities/file/fs-utils';
```
### Use `@/cli/utilities/string/kebab-case` instead of lodash
```typescript
// ✅ Use internal utility
import { kebabCase } from '@/cli/utilities/string/kebab-case';
// ❌ Don't use lodash single-function packages (CJS-only, unmaintained)
import kebabCase from 'lodash.kebabcase';
```
### When no ESM alternative exists
If a CJS-only package has no ESM replacement (e.g. `archiver`), add it to the `cjsOnlyPackages` list in `vite.config.node.ts` so it gets inlined into the bundle instead of externalized.
-3
View File
@@ -15,9 +15,6 @@ inputs:
runs:
using: "composite"
steps:
- name: Fetch main branch for diff
shell: bash
run: git fetch origin main --depth=1
- name: Get last successful commit
uses: nrwl/nx-set-shas@v4
- name: Run affected command
+1 -1
View File
@@ -23,7 +23,7 @@ jobs:
- name: Fetch custom Github Actions and base branch history
uses: actions/checkout@v4
with:
fetch-depth: 10
fetch-depth: 0
- name: Check for changed files
id: changed-files
uses: tj-actions/changed-files@v45
+1 -1
View File
@@ -70,7 +70,7 @@ jobs:
- name: Checkout current branch
uses: actions/checkout@v4
with:
fetch-depth: 10
fetch-depth: 0
- name: Try to merge main into current branch
id: merge_attempt
+1 -1
View File
@@ -37,7 +37,7 @@ jobs:
- name: Fetch custom Github Actions and base branch history
uses: actions/checkout@v4
with:
fetch-depth: 10
fetch-depth: 0
- name: Install dependencies
uses: ./.github/actions/yarn-install
- name: Build
+4 -3
View File
@@ -21,6 +21,7 @@ jobs:
files: |
package.json
packages/twenty-docs/**
eslint.config.mjs
docs-lint:
needs: changed-files-check
@@ -36,11 +37,11 @@ jobs:
- name: Fetch local actions
uses: actions/checkout@v4
with:
fetch-depth: 10
fetch-depth: 0
- name: Install dependencies
uses: ./.github/actions/yarn-install
- name: Docs / Lint
run: npx nx lint twenty-docs
- name: Docs / Lint English MDX files
run: npx eslint "packages/twenty-docs/{developers,user-guide,twenty-ui,getting-started,snippets}/**/*.mdx" --max-warnings 0
+1 -1
View File
@@ -30,7 +30,7 @@ jobs:
- name: Fetch custom Github Actions and base branch history
uses: actions/checkout@v4
with:
fetch-depth: 10
fetch-depth: 0
- name: Install dependencies
uses: ./.github/actions/yarn-install
- name: Build twenty-emails
+140 -31
View File
@@ -1,8 +1,10 @@
name: CI Front
name: CI Front and E2E
on:
pull_request:
merge_group:
permissions:
contents: read
@@ -27,6 +29,15 @@ jobs:
packages/twenty-shared/**
packages/twenty-sdk/**
!packages/twenty-sdk/package.json
changed-files-check-e2e:
uses: ./.github/workflows/changed-files.yaml
with:
files: |
packages/**
!packages/create-twenty-app/package.json
!packages/twenty-sdk/package.json
playwright.config.ts
.github/workflows/ci-front.yaml
front-sb-build:
needs: changed-files-check
if: needs.changed-files-check.outputs.any_changed == 'true'
@@ -42,7 +53,7 @@ jobs:
- name: Fetch local actions
uses: actions/checkout@v4
with:
fetch-depth: 10
fetch-depth: 0
- name: Install dependencies
uses: ./.github/actions/yarn-install
- name: Diagnostic disk space issue
@@ -51,12 +62,6 @@ jobs:
run: npx nx reset:env twenty-front
- name: Front / Build storybook
run: npx nx storybook:build twenty-front
- name: Upload storybook build
uses: actions/upload-artifact@v4
with:
name: storybook-static
path: packages/twenty-front/storybook-static
retention-days: 1
- name: Save storybook build cache
uses: ./.github/actions/save-cache
with:
@@ -73,41 +78,26 @@ jobs:
env:
SHARD_COUNTER: 4
REACT_APP_SERVER_BASE_URL: http://localhost:3000
STORYBOOK_URL: http://localhost:6006
steps:
- name: Fetch local actions
uses: actions/checkout@v4
with:
fetch-depth: 10
fetch-depth: 0
- name: Install dependencies
uses: ./.github/actions/yarn-install
- name: Restore storybook build cache
uses: ./.github/actions/restore-cache
with:
key: ${{ env.STORYBOOK_BUILD_CACHE_KEY_FOR_RESTORE_ACTION }}
- name: Clean stale storybook vitest cache
run: rm -rf packages/twenty-front/node_modules/.cache/storybook
- name: Build dependencies
run: |
npx nx build twenty-shared
npx nx build twenty-ui
npx nx build twenty-sdk
- name: Download storybook build
uses: actions/download-artifact@v4
with:
name: storybook-static
path: packages/twenty-front/storybook-static
- name: Install Playwright
run: |
cd packages/twenty-front
npx playwright install
- name: Front / Write .env
run: npx nx reset:env twenty-front
- name: Serve storybook & run tests
run: |
npx http-server packages/twenty-front/storybook-static --port 6006 --silent &
timeout 30 bash -c 'until curl -sf http://localhost:6006 > /dev/null 2>&1; do sleep 1; done'
npx nx storybook:test twenty-front --configuration=${{ matrix.storybook_scope }} --shard=${{ matrix.shard }}/${{ env.SHARD_COUNTER }}
- name: Run storybook tests
run: npx nx storybook:test twenty-front --configuration=${{ matrix.storybook_scope }} --shard=${{ matrix.shard }}/${{ env.SHARD_COUNTER }}
# - name: Rename coverage file
# run: |
# if [ -f "packages/twenty-front/coverage/storybook/coverage-final.json" ]; then
@@ -135,7 +125,7 @@ jobs:
# steps:
# - uses: actions/checkout@v4
# with:
# fetch-depth: 10
# fetch-depth: 0
# - name: Install dependencies
# uses: ./.github/actions/yarn-install
# - uses: actions/download-artifact@v4
@@ -160,7 +150,7 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 10
fetch-depth: 0
- name: Install dependencies
uses: ./.github/actions/yarn-install
- name: Restore storybook build cache
@@ -194,7 +184,7 @@ jobs:
- name: Fetch custom Github Actions and base branch history
uses: actions/checkout@v4
with:
fetch-depth: 10
fetch-depth: 0
- name: Install dependencies
uses: ./.github/actions/yarn-install
- name: Restore ${{ matrix.task }} cache
@@ -224,7 +214,6 @@ jobs:
runs-on: ubuntu-latest-8-cores
env:
NODE_OPTIONS: "--max-old-space-size=10240"
ANALYZE: "true"
steps:
- name: Cancel Previous Runs
uses: styfle/cancel-workflow-action@0.11.0
@@ -233,7 +222,7 @@ jobs:
- name: Fetch custom Github Actions and base branch history
uses: actions/checkout@v4
with:
fetch-depth: 10
fetch-depth: 0
- name: Install dependencies
uses: ./.github/actions/yarn-install
- name: Front / Write .env
@@ -246,6 +235,117 @@ jobs:
# name: frontend-build
# path: packages/twenty-front/build
# retention-days: 1
e2e-test:
runs-on: ubuntu-latest
needs: [changed-files-check-e2e, front-build]
if: |
always() &&
needs.changed-files-check-e2e.outputs.any_changed == 'true' &&
(needs.front-build.result == 'success' || needs.front-build.result == 'skipped') &&
(github.event_name == 'push' || github.event_name == 'merge_group' || (github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'run-e2e')))
timeout-minutes: 30
env:
NODE_OPTIONS: "--max-old-space-size=10240"
services:
postgres:
image: twentycrm/twenty-postgres-spilo
env:
PGUSER_SUPERUSER: postgres
PGPASSWORD_SUPERUSER: postgres
ALLOW_NOSSL: "true"
SPILO_PROVIDER: "local"
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
redis:
image: redis
ports:
- 6379:6379
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-node@v4
with:
node-version: lts/*
- name: Check system resources
run: |
echo "Available memory:"
free -h
echo "Available disk space:"
df -h
echo "CPU info:"
lscpu
- name: Install dependencies
uses: ./.github/actions/yarn-install
- name: Build twenty-shared
run: npx nx build twenty-shared
- name: Install Playwright Browsers
run: npx nx setup twenty-e2e-testing
- name: Setup environment files
run: |
cp packages/twenty-front/.env.example packages/twenty-front/.env
npx nx reset:env:e2e-testing-server twenty-server
# - name: Download frontend build artifact
# if: needs.front-build.result == 'success'
# uses: actions/download-artifact@v4
# with:
# name: frontend-build
# path: packages/twenty-front/build
# - name: Build frontend (if not available from front-build)
# if: needs.front-build.result == 'skipped'
# run: NODE_ENV=production NODE_OPTIONS="--max-old-space-size=10240" npx nx build twenty-front
- name: Build frontend
run: NODE_ENV=production NODE_OPTIONS="--max-old-space-size=10240" npx nx build twenty-front
- name: Build server
run: npx nx build twenty-server
- name: Create and setup database
run: |
PGPASSWORD=postgres psql -h localhost -p 5432 -U postgres -d postgres -c 'CREATE DATABASE "default";'
PGPASSWORD=postgres psql -h localhost -p 5432 -U postgres -d postgres -c 'CREATE DATABASE "test";'
npx nx run twenty-server:database:reset
- name: Start server
run: |
npx nx start twenty-server &
echo "Waiting for server to be ready..."
timeout 60 bash -c 'until curl -s http://localhost:3000/health; do sleep 2; done'
- name: Start frontend
run: |
npm_config_yes=true npx serve -s packages/twenty-front/build -l 3001 &
echo "Waiting for frontend to be ready..."
timeout 60 bash -c 'until curl -s http://localhost:3001; do sleep 2; done'
- name: Start worker
run: |
npx nx run twenty-server:worker &
echo "Worker started"
- name: Run Playwright tests
run: npx nx test twenty-e2e-testing
# - uses: actions/upload-artifact@v4
# if: always()
# with:
# name: playwright-report
# path: packages/twenty-e2e-testing/run_results/
# retention-days: 30
ci-front-status-check:
if: always() && !cancelled()
timeout-minutes: 5
@@ -263,3 +363,12 @@ jobs:
- name: Fail job if any needs failed
if: contains(needs.*.result, 'failure')
run: exit 1
ci-e2e-status-check:
if: always() && !cancelled()
timeout-minutes: 5
runs-on: ubuntu-latest
needs: [changed-files-check-e2e, e2e-test]
steps:
- name: Fail job if any needs failed
if: contains(needs.*.result, 'failure')
run: exit 1
-118
View File
@@ -1,118 +0,0 @@
name: CI Merge Queue
on:
merge_group:
pull_request:
types: [labeled, synchronize, opened, reopened]
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
e2e-test:
if: >
github.event_name == 'merge_group' ||
(github.event_name == 'pull_request' &&
contains(github.event.pull_request.labels.*.name, 'run-merge-queue'))
runs-on: ubuntu-latest-8-cores
timeout-minutes: 30
env:
NODE_OPTIONS: "--max-old-space-size=10240"
services:
postgres:
image: twentycrm/twenty-postgres-spilo
env:
PGUSER_SUPERUSER: postgres
PGPASSWORD_SUPERUSER: postgres
ALLOW_NOSSL: "true"
SPILO_PROVIDER: "local"
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
redis:
image: redis
ports:
- 6379:6379
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 10
- uses: actions/setup-node@v4
with:
node-version: lts/*
- name: Install dependencies
uses: ./.github/actions/yarn-install
- name: Build twenty-shared
run: npx nx build twenty-shared
- name: Install Playwright Browsers
run: npx nx setup twenty-e2e-testing
- name: Setup environment files
run: |
cp packages/twenty-front/.env.example packages/twenty-front/.env
npx nx reset:env:e2e-testing-server twenty-server
- name: Build frontend
run: NODE_ENV=production npx nx build twenty-front
- name: Build server
run: npx nx build twenty-server
- name: Create and setup database
run: |
PGPASSWORD=postgres psql -h localhost -p 5432 -U postgres -d postgres -c 'CREATE DATABASE "default";'
PGPASSWORD=postgres psql -h localhost -p 5432 -U postgres -d postgres -c 'CREATE DATABASE "test";'
npx nx run twenty-server:database:reset
- name: Start server
run: |
npx nx start twenty-server &
echo "Waiting for server to be ready..."
timeout 60 bash -c 'until curl -s http://localhost:3000/health; do sleep 2; done'
- name: Start frontend
run: |
npm_config_yes=true npx serve -s packages/twenty-front/build -l 3001 &
echo "Waiting for frontend to be ready..."
timeout 60 bash -c 'until curl -s http://localhost:3001; do sleep 2; done'
- name: Start worker
run: |
npx nx run twenty-server:worker &
echo "Worker started"
- name: Run Playwright tests
run: npx nx test twenty-e2e-testing
- name: Upload Playwright results
if: always()
uses: actions/upload-artifact@v4
with:
name: playwright-results
path: |
packages/twenty-e2e-testing/run_results/
packages/twenty-e2e-testing/test-results/
retention-days: 7
ci-merge-queue-status-check:
if: always() && !cancelled()
timeout-minutes: 5
runs-on: ubuntu-latest
needs: [e2e-test]
steps:
- name: Fail job if any needs failed
if: contains(needs.*.result, 'failure')
run: exit 1
+4 -2
View File
@@ -1,6 +1,8 @@
name: CI SDK
on:
merge_group:
pull_request:
permissions:
@@ -33,7 +35,7 @@ jobs:
- name: Fetch custom Github Actions and base branch history
uses: actions/checkout@v4
with:
fetch-depth: 10
fetch-depth: 0
- name: Install dependencies
uses: ./.github/actions/yarn-install
- name: Build
@@ -76,7 +78,7 @@ jobs:
- name: Fetch custom Github Actions and base branch history
uses: actions/checkout@v4
with:
fetch-depth: 10
fetch-depth: 0
- name: Install dependencies
uses: ./.github/actions/yarn-install
- name: Build
+32 -76
View File
@@ -3,6 +3,8 @@ name: CI Server
on:
pull_request:
merge_group:
permissions:
contents: read
@@ -11,7 +13,7 @@ concurrency:
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
env:
SERVER_BUILD_CACHE_KEY: server-build
SERVER_SETUP_CACHE_KEY: server-setup
jobs:
changed-files-check:
@@ -25,59 +27,11 @@ jobs:
packages/twenty-front/src/generated-metadata/**
packages/twenty-emails/**
packages/twenty-shared/**
server-build:
server-setup:
needs: changed-files-check
if: needs.changed-files-check.outputs.any_changed == 'true'
timeout-minutes: 30
runs-on: ubuntu-latest-8-cores
steps:
- name: Fetch custom Github Actions and base branch history
uses: actions/checkout@v4
with:
fetch-depth: 10
- name: Install dependencies
uses: ./.github/actions/yarn-install
- name: Restore server build cache
id: restore-server-build-cache
uses: ./.github/actions/restore-cache
with:
key: ${{ env.SERVER_BUILD_CACHE_KEY }}
- name: Build twenty-shared
run: npx nx build twenty-shared
- name: Server / Write .env
run: npx nx reset:env twenty-server
- name: Server / Build
run: npx nx build twenty-server
- name: Save server build cache
uses: ./.github/actions/save-cache
with:
key: ${{ steps.restore-server-build-cache.outputs.cache-primary-key }}
server-lint-typecheck:
needs: changed-files-check
if: needs.changed-files-check.outputs.any_changed == 'true'
timeout-minutes: 30
runs-on: ubuntu-latest-8-cores
steps:
- name: Fetch custom Github Actions and base branch history
uses: actions/checkout@v4
with:
fetch-depth: 10
- name: Install dependencies
uses: ./.github/actions/yarn-install
- name: Build twenty-shared
run: npx nx build twenty-shared
- name: Server / Run lint & typecheck
uses: ./.github/actions/nx-affected
with:
tag: scope:backend
tasks: lint,typecheck
server-validation:
needs: server-build
timeout-minutes: 30
runs-on: ubuntu-latest-8-cores
services:
postgres:
image: twentycrm/twenty-postgres-spilo
@@ -101,15 +55,21 @@ jobs:
- name: Fetch custom Github Actions and base branch history
uses: actions/checkout@v4
with:
fetch-depth: 10
fetch-depth: 0
- name: Install dependencies
uses: ./.github/actions/yarn-install
- name: Restore server build cache
- name: Restore server setup
id: restore-server-setup-cache
uses: ./.github/actions/restore-cache
with:
key: ${{ env.SERVER_BUILD_CACHE_KEY }}
key: ${{ env.SERVER_SETUP_CACHE_KEY }}
- name: Build twenty-shared
run: npx nx build twenty-shared
- name: Server / Run lint & typecheck
uses: ./.github/actions/nx-affected
with:
tag: scope:backend
tasks: lint,typecheck
- name: Server / Write .env
run: npx nx reset:env twenty-server
- name: Server / Build
@@ -124,8 +84,10 @@ jobs:
run: |
timeout 30s npx nx run twenty-server:worker || exit_code=$?
if [ $exit_code -eq 124 ]; then
# If timeout was reached (exit code 124), consider it a success
exit 0
elif [ $exit_code -ne 0 ]; then
# If worker failed for other reasons, fail the build
exit $exit_code
fi
- name: Server / Start
@@ -158,9 +120,11 @@ jobs:
fi
- name: GraphQL / Check for Pending Generation
run: |
# Run GraphQL generation commands
npx nx run twenty-front:graphql:generate
npx nx run twenty-front:graphql:generate --configuration=metadata
# Check if GraphQL generated files were modified
if ! git diff --quiet -- packages/twenty-front/src/generated packages/twenty-front/src/generated-metadata; then
echo "::error::GraphQL schema changes detected. Please run 'npx nx run twenty-front:graphql:generate' and 'npx nx run twenty-front:graphql:generate --configuration=metadata' and commit the changes."
echo ""
@@ -173,24 +137,25 @@ jobs:
echo ""
exit 1
fi
- name: Save server setup
uses: ./.github/actions/save-cache
with:
key: ${{ steps.restore-server-setup-cache.outputs.cache-primary-key }}
server-test:
needs: server-build
timeout-minutes: 30
runs-on: ubuntu-latest-8-cores
needs: server-setup
steps:
- name: Fetch custom Github Actions and base branch history
uses: actions/checkout@v4
with:
fetch-depth: 10
fetch-depth: 0
- name: Install dependencies
uses: ./.github/actions/yarn-install
- name: Restore server build cache
- name: Restore server setup
uses: ./.github/actions/restore-cache
with:
key: ${{ env.SERVER_BUILD_CACHE_KEY }}
- name: Build twenty-shared
run: npx nx build twenty-shared
key: ${{ env.SERVER_SETUP_CACHE_KEY }}
- name: Server / Run Tests
uses: ./.github/actions/nx-affected
with:
@@ -200,11 +165,11 @@ jobs:
server-integration-test:
timeout-minutes: 30
runs-on: ubuntu-latest-8-cores
needs: server-build
needs: server-setup
strategy:
fail-fast: false
matrix:
shard: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
shard: [1, 2, 3, 4, 5, 6, 7, 8]
services:
postgres:
image: twentycrm/twenty-postgres-spilo
@@ -242,12 +207,12 @@ jobs:
ANALYTICS_ENABLED: true
CLICKHOUSE_URL: "http://default:clickhousePassword@localhost:8123/twenty"
CLICKHOUSE_PASSWORD: clickhousePassword
SHARD_COUNTER: 10
SHARD_COUNTER: 8
steps:
- name: Fetch custom Github Actions and base branch history
uses: actions/checkout@v4
with:
fetch-depth: 10
fetch-depth: 0
- name: Install dependencies
uses: ./.github/actions/yarn-install
- name: Update .env.test for integrations tests
@@ -258,10 +223,10 @@ jobs:
echo "BILLING_STRIPE_BASE_PLAN_PRODUCT_ID=test-base-plan-product-id" >> .env.test
echo "BILLING_STRIPE_WEBHOOK_SECRET=test-webhook-secret" >> .env.test
echo "BILLING_PLAN_REQUIRED_LINK=http://localhost:3001/stripe-redirection" >> .env.test
- name: Restore server build cache
- name: Restore server setup
uses: ./.github/actions/restore-cache
with:
key: ${{ env.SERVER_BUILD_CACHE_KEY }}
key: ${{ env.SERVER_SETUP_CACHE_KEY }}
- name: Server / Build
run: npx nx build twenty-server
- name: Build dependencies
@@ -282,20 +247,11 @@ jobs:
tasks: 'test:integration'
configuration: 'with-db-reset'
args: --shard=${{ matrix.shard }}/${{ env.SHARD_COUNTER }}
ci-server-status-check:
if: always() && !cancelled()
timeout-minutes: 5
runs-on: ubuntu-latest
needs:
[
changed-files-check,
server-build,
server-lint-typecheck,
server-validation,
server-test,
server-integration-test,
]
needs: [changed-files-check, server-setup, server-test, server-integration-test]
steps:
- name: Fail job if any needs failed
if: contains(needs.*.result, 'failure')
+4 -2
View File
@@ -1,6 +1,8 @@
name: CI Shared
on:
merge_group:
pull_request:
permissions:
@@ -34,13 +36,13 @@ jobs:
- name: Fetch custom Github Actions and base branch history
uses: actions/checkout@v4
with:
fetch-depth: 10
fetch-depth: 0
- name: Install dependencies
uses: ./.github/actions/yarn-install
- name: Run ${{ matrix.task }} task
uses: ./.github/actions/nx-affected
with:
tag: scope:shared
tag: scope:frontend
tasks: ${{ matrix.task }}
ci-shared-status-check:
if: always() && !cancelled()
@@ -4,6 +4,8 @@ permissions:
contents: read
on:
merge_group:
pull_request:
concurrency:
+3 -1
View File
@@ -4,6 +4,8 @@ permissions:
contents: read
on:
merge_group:
pull_request:
concurrency:
@@ -40,7 +42,7 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 10
fetch-depth: 0
- name: Install dependencies
uses: ./.github/actions/yarn-install
+4 -2
View File
@@ -3,6 +3,8 @@ name: CI Zapier
on:
pull_request:
merge_group:
permissions:
contents: read
@@ -50,7 +52,7 @@ jobs:
- name: Fetch custom Github Actions and base branch history
uses: actions/checkout@v4
with:
fetch-depth: 10
fetch-depth: 0
- name: Install dependencies
uses: ./.github/actions/yarn-install
@@ -105,7 +107,7 @@ jobs:
- name: Fetch custom Github Actions and base branch history
uses: actions/checkout@v4
with:
fetch-depth: 10
fetch-depth: 0
- name: Install dependencies
uses: ./.github/actions/yarn-install
- name: Build
+1 -1
View File
@@ -164,4 +164,4 @@ jobs:
token: ${{ secrets.CI_PRIVILEGED_DISPATCH_TOKEN }}
repository: twentyhq/ci-privileged
event-type: claude-cross-repo-response
client-payload: '{"repo": ${{ toJSON(steps.prompt.outputs.repo) }}, "issue_number": ${{ toJSON(steps.prompt.outputs.issue_number) }}, "run_id": ${{ toJSON(github.run_id) }}, "run_url": "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"}'
client-payload: '{"repo": ${{ toJSON(steps.prompt.outputs.repo) }}, "issue_number": ${{ toJSON(steps.prompt.outputs.issue_number) }}, "run_id": ${{ toJSON(github.run_id) }}, "run_url": ${{ toJSON(format('{0}/{1}/actions/runs/{2}', github.server_url, github.repository, github.run_id)) }}}'
+1 -1
View File
@@ -28,7 +28,7 @@ coverage
dist
storybook-static
*.tsbuildinfo
.oxlintcache
.eslintcache
.nyc_output
test-results/
dump.rdb
+1 -1
View File
@@ -1,7 +1,7 @@
{
"recommendations": [
"arcanis.vscode-zipfs",
"oxc.oxc-vscode",
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"figma.figma-vscode-extension",
"firsttris.vscode-jest-runner",
+7 -10
View File
@@ -4,28 +4,25 @@
"files.insertFinalNewline": true,
"files.trimTrailingWhitespace": true,
"[typescript]": {
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": false,
"editor.codeActionsOnSave": {
"source.fixAll.oxc": "explicit",
"source.fixAll.eslint": "explicit",
"source.addMissingImports": "always",
"source.organizeImports": "always"
}
},
"[javascript]": {
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": false,
"editor.codeActionsOnSave": {
"source.fixAll.oxc": "explicit",
"source.fixAll.eslint": "explicit",
"source.addMissingImports": "always",
"source.organizeImports": "always"
}
},
"[typescriptreact]": {
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": false,
"editor.codeActionsOnSave": {
"source.fixAll.oxc": "explicit",
"source.fixAll.eslint": "explicit",
"source.addMissingImports": "always",
"source.organizeImports": "always"
}
@@ -51,7 +48,7 @@
"search.exclude": {
"**/.yarn": true
},
"oxc.lint.enable": true,
"eslint.debug": true,
"files.associations": {
".cursorrules": "markdown"
},
+9 -13
View File
@@ -37,8 +37,8 @@
"path": "../packages/twenty-zapier"
},
{
"name": "packages/twenty-oxlint-rules",
"path": "../packages/twenty-oxlint-rules"
"name": "tools/eslint-rules",
"path": "../tools/eslint-rules"
},
{
"name": "packages/twenty-e2e-testing",
@@ -49,26 +49,23 @@
"editor.formatOnSave": false,
"files.eol": "auto",
"[typescript]": {
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": false,
"editor.codeActionsOnSave": {
"source.fixAll.oxc": "explicit",
"source.fixAll.eslint": "explicit",
"source.addMissingImports": "always"
}
},
"[javascript]": {
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": false,
"editor.codeActionsOnSave": {
"source.fixAll.oxc": "explicit",
"source.fixAll.eslint": "explicit",
"source.addMissingImports": "always"
}
},
"[typescriptreact]": {
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": false,
"editor.codeActionsOnSave": {
"source.fixAll.oxc": "explicit",
"source.fixAll.eslint": "explicit",
"source.addMissingImports": "always"
}
},
@@ -91,7 +88,7 @@
"typescript.preferences.importModuleSpecifier": "non-relative",
"[javascript][typescript][typescriptreact]": {
"editor.codeActionsOnSave": {
"source.fixAll.oxc": "explicit",
"source.fixAll.eslint": "explicit",
"source.addMissingImports": "always"
}
},
@@ -101,7 +98,6 @@
"files.exclude": {
"packages/": true
},
"oxc.lint.enable": true,
"jest.runMode": "on-demand",
"jest.disabledWorkspaceFolders": [
"ROOT",
+226
View File
@@ -0,0 +1,226 @@
import js from '@eslint/js';
import nxPlugin from '@nx/eslint-plugin';
import typescriptEslint from '@typescript-eslint/eslint-plugin';
import typescriptParser from '@typescript-eslint/parser';
import importPlugin from 'eslint-plugin-import';
import linguiPlugin from 'eslint-plugin-lingui';
import * as mdxPlugin from 'eslint-plugin-mdx';
import preferArrowPlugin from 'eslint-plugin-prefer-arrow';
import prettierPlugin from 'eslint-plugin-prettier';
import unicornPlugin from 'eslint-plugin-unicorn';
import unusedImportsPlugin from 'eslint-plugin-unused-imports';
import jsoncParser from 'jsonc-eslint-parser';
const twentyRules = await nxPlugin.loadWorkspaceRules(
'packages/twenty-eslint-rules',
);
export default [
// Base JavaScript configuration
js.configs.recommended,
// Lingui recommended rules
linguiPlugin.configs['flat/recommended'],
// Global ignores
{
ignores: ['**/node_modules/**'],
},
// Base configuration for all files
{
files: ['**/*.{js,jsx,ts,tsx}'],
plugins: {
prettier: prettierPlugin,
lingui: linguiPlugin,
'@nx': nxPlugin,
'prefer-arrow': preferArrowPlugin,
import: importPlugin,
'unused-imports': unusedImportsPlugin,
unicorn: unicornPlugin,
},
rules: {
// General rules
'func-style': ['error', 'declaration', { allowArrowFunctions: true }],
'no-console': [
'warn',
{ allow: ['group', 'groupCollapsed', 'groupEnd'] },
],
'no-control-regex': 0,
'no-debugger': 'error',
'no-duplicate-imports': 'error',
'no-undef': 'off',
'no-unused-vars': 'off',
// Nx rules
'@nx/enforce-module-boundaries': [
'error',
{
enforceBuildableLibDependency: true,
allow: [],
depConstraints: [
{
sourceTag: 'scope:apps',
onlyDependOnLibsWithTags: ['scope:apps', 'scope:sdk'],
},
{
sourceTag: 'scope:sdk',
onlyDependOnLibsWithTags: ['scope:sdk', 'scope:shared'],
},
{
sourceTag: 'scope:create-app',
onlyDependOnLibsWithTags: ['scope:create-app', 'scope:shared'],
},
{
sourceTag: 'scope:shared',
onlyDependOnLibsWithTags: ['scope:shared'],
},
{
sourceTag: 'scope:backend',
onlyDependOnLibsWithTags: ['scope:shared', 'scope:backend'],
},
{
sourceTag: 'scope:frontend',
onlyDependOnLibsWithTags: ['scope:shared', 'scope:frontend'],
},
{
sourceTag: 'scope:zapier',
onlyDependOnLibsWithTags: ['scope:shared', 'scope:zapier'],
},
],
},
],
// Import rules
'import/no-relative-packages': 'error',
'import/no-useless-path-segments': 'error',
'import/no-duplicates': ['error', { considerQueryString: true }],
// Prefer arrow functions
'prefer-arrow/prefer-arrow-functions': [
'error',
{
disallowPrototype: true,
singleReturnOnly: false,
classPropertiesAllowed: false,
},
],
// Unused imports
'unused-imports/no-unused-imports': 'warn',
'unused-imports/no-unused-vars': [
'warn',
{
vars: 'all',
varsIgnorePattern: '^_',
args: 'after-used',
argsIgnorePattern: '^_',
},
],
},
},
// TypeScript specific configuration
{
files: ['**/*.{ts,tsx}'],
languageOptions: {
parser: typescriptParser,
parserOptions: {
ecmaFeatures: {
jsx: true,
},
},
},
plugins: {
'@typescript-eslint': typescriptEslint,
},
rules: {
// TypeScript rules
'no-redeclare': 'off', // Turn off base rule for TypeScript
'@typescript-eslint/no-redeclare': 'error', // Use TypeScript-aware version
'@typescript-eslint/ban-ts-comment': 'error',
'@typescript-eslint/consistent-type-imports': [
'error',
{
prefer: 'type-imports',
fixStyle: 'inline-type-imports',
},
],
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/interface-name-prefix': 'off',
'@typescript-eslint/no-empty-object-type': [
'error',
{
allowInterfaces: 'with-single-extends',
},
],
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-empty-function': 'off',
'@typescript-eslint/no-unused-vars': 'off',
},
},
// JavaScript specific configuration
{
files: ['*.{js,jsx}'],
rules: {
// JavaScript-specific rules if needed
},
},
// Test files
{
files: [
'*.spec.@(ts|tsx|js|jsx)',
'*.integration-spec.@(ts|tsx|js|jsx)',
'*.test.@(ts|tsx|js|jsx)',
],
languageOptions: {
globals: {
jest: true,
describe: true,
it: true,
expect: true,
beforeEach: true,
afterEach: true,
beforeAll: true,
afterAll: true,
},
},
rules: {
'@typescript-eslint/no-non-null-assertion': 'off',
},
},
// JSON files
{
files: ['**/*.json'],
languageOptions: {
parser: jsoncParser,
},
},
// MDX files
{
...mdxPlugin.flat,
plugins: {
...mdxPlugin.flat.plugins,
'@nx': nxPlugin,
twenty: { rules: twentyRules },
},
},
mdxPlugin.flatCodeBlocks,
{
files: ['**/*.mdx'],
rules: {
'no-unused-vars': 'off',
'unused-imports/no-unused-imports': 'off',
'unused-imports/no-unused-vars': 'off',
// Enforce JSX tags on separate lines to prevent Crowdin translation issues
'twenty/mdx-component-newlines': 'error',
// Disallow angle bracket placeholders to prevent Crowdin translation errors
'twenty/no-angle-bracket-placeholders': 'error',
},
},
];
+23 -9
View File
@@ -40,30 +40,34 @@
"dependsOn": ["^build"]
},
"lint": {
"executor": "nx:run-commands",
"executor": "@nx/eslint:lint",
"cache": true,
"outputs": ["{options.outputFile}"],
"options": {
"cwd": "{projectRoot}",
"command": "npx oxlint -c .oxlintrc.json . && (prettier . --check --cache --cache-location ../../.cache/prettier/{projectRoot} --cache-strategy metadata || (echo 'ERROR: Prettier formatting check failed! Fix with: npx nx lint --configuration=fix' && false))"
"eslintConfig": "{projectRoot}/eslint.config.mjs",
"cache": true,
"cacheLocation": "{workspaceRoot}/.cache/eslint"
},
"configurations": {
"ci": {},
"ci": {
"cacheStrategy": "content"
},
"fix": {
"command": "npx oxlint --fix -c .oxlintrc.json . && prettier . --write --cache --cache-location ../../.cache/prettier/{projectRoot} --cache-strategy metadata"
"fix": true
}
},
"dependsOn": ["^build", "twenty-oxlint-rules:build"]
"dependsOn": ["^build"]
},
"lint:diff-with-main": {
"executor": "nx:run-commands",
"cache": false,
"options": {
"command": "FILES=$(git diff --name-only --diff-filter=d main -- {projectRoot}/ | grep -E '{args.pattern}'); [ -z \"$FILES\" ] && echo 'No changed files.' || (npx oxlint -c {projectRoot}/.oxlintrc.json $FILES && (prettier --check $FILES || (echo 'ERROR: Prettier formatting check failed! Fix with: npx nx lint:diff-with-main --configuration=fix' && false)))",
"command": "git diff --name-only --diff-filter=d main | grep -E '{args.pattern}' | grep '^{projectRoot}/' | xargs sh -c 'if [ $# -gt 0 ]; then npx eslint --config {projectRoot}/eslint.config.mjs \"$@\"; fi' _",
"pattern": "\\.(ts|tsx|js|jsx)$"
},
"configurations": {
"fix": {
"command": "FILES=$(git diff --name-only --diff-filter=d main -- {projectRoot}/ | grep -E '{args.pattern}'); [ -z \"$FILES\" ] && echo 'No changed files.' || (npx oxlint --fix -c {projectRoot}/.oxlintrc.json $FILES && prettier --write $FILES)"
"command": "git diff --name-only --diff-filter=d main | grep -E '{args.pattern}' | grep '^{projectRoot}/' | xargs sh -c 'if [ $# -gt 0 ]; then npx eslint --config {projectRoot}/eslint.config.mjs --fix \"$@\"; fi' _"
}
}
},
@@ -143,7 +147,7 @@
"outputs": ["{projectRoot}/{options.output-dir}"],
"options": {
"cwd": "{projectRoot}",
"command": "NODE_OPTIONS='--max-old-space-size=10240' storybook build --test",
"command": "NODE_OPTIONS='--max-old-space-size=10240' VITE_DISABLE_TYPESCRIPT_CHECKER=true storybook build --test",
"output-dir": "storybook-static",
"config-dir": ".storybook"
},
@@ -251,6 +255,14 @@
}
}
},
"@nx/eslint:lint": {
"cache": true,
"inputs": [
"default",
"{workspaceRoot}/eslint.config.mjs",
"{workspaceRoot}/packages/twenty-eslint-rules/**/*"
]
},
"@nx/vite:build": {
"cache": true,
"dependsOn": ["^build"],
@@ -265,6 +277,7 @@
"@nx/react": {
"application": {
"style": "@linaria/react",
"linter": "eslint",
"bundler": "vite",
"compiler": "swc",
"unitTestRunner": "jest",
@@ -272,6 +285,7 @@
},
"library": {
"style": "@linaria/react",
"linter": "eslint",
"bundler": "vite",
"compiler": "swc",
"unitTestRunner": "jest",
+35 -14
View File
@@ -46,7 +46,7 @@
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-responsive": "^9.0.2",
"react-router-dom": "^6.30.3",
"react-router-dom": "^6.4.4",
"react-tooltip": "^5.13.1",
"remark-gfm": "^4.0.1",
"rxjs": "^7.2.0",
@@ -72,13 +72,14 @@
"@graphql-codegen/typescript": "^3.0.4",
"@graphql-codegen/typescript-operations": "^3.0.4",
"@graphql-codegen/typescript-react-apollo": "^3.3.7",
"@nx/jest": "22.5.4",
"@nx/js": "22.5.4",
"@nx/react": "22.5.4",
"@nx/storybook": "22.5.4",
"@nx/vite": "22.5.4",
"@nx/web": "22.5.4",
"@oxlint/plugins": "^1.51.0",
"@nx/eslint": "22.3.3",
"@nx/eslint-plugin": "22.3.3",
"@nx/jest": "22.3.3",
"@nx/js": "22.3.3",
"@nx/react": "22.3.3",
"@nx/storybook": "22.3.3",
"@nx/vite": "22.3.3",
"@nx/web": "22.3.3",
"@sentry/types": "^8",
"@storybook-community/storybook-addon-cookie": "^5.0.0",
"@storybook/addon-coverage": "^3.0.0",
@@ -88,10 +89,11 @@
"@storybook/icons": "^2.0.1",
"@storybook/react-vite": "^10.2.13",
"@storybook/test-runner": "^0.24.2",
"@swc-node/register": "^1.11.1",
"@swc/cli": "^0.7.10",
"@swc/core": "^1.15.11",
"@swc/helpers": "~0.5.19",
"@stylistic/eslint-plugin": "^1.5.0",
"@swc-node/register": "1.11.1",
"@swc/cli": "^0.3.12",
"@swc/core": "1.15.11",
"@swc/helpers": "~0.5.18",
"@swc/jest": "^0.2.39",
"@testing-library/dom": "^10.4.0",
"@testing-library/jest-dom": "^6.6.3",
@@ -130,6 +132,9 @@
"@types/react-dom": "^18.2.15",
"@types/supertest": "^2.0.11",
"@types/uuid": "^9.0.2",
"@typescript-eslint/eslint-plugin": "^8.39.0",
"@typescript-eslint/parser": "^8.39.0",
"@typescript-eslint/utils": "^8.39.0",
"@typescript/native-preview": "^7.0.0-dev.20260116.1",
"@vitejs/plugin-react-swc": "4.2.3",
"@vitest/browser-playwright": "^4.0.18",
@@ -141,6 +146,22 @@
"danger": "^13.0.4",
"dotenv-cli": "^7.4.4",
"esbuild": "^0.25.10",
"eslint": "^9.32.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-import": "^2.31.0",
"eslint-plugin-jsx-a11y": "^6.10.2",
"eslint-plugin-lingui": "^0.9.0",
"eslint-plugin-mdx": "^3.6.2",
"eslint-plugin-prefer-arrow": "^1.2.3",
"eslint-plugin-prettier": "^5.1.2",
"eslint-plugin-project-structure": "^3.9.1",
"eslint-plugin-react": "^7.37.2",
"eslint-plugin-react-hooks": "^5.0.0",
"eslint-plugin-react-refresh": "^0.4.4",
"eslint-plugin-simple-import-sort": "^10.0.0",
"eslint-plugin-storybook": "^10.2.13",
"eslint-plugin-unicorn": "^56.0.1",
"eslint-plugin-unused-imports": "^3.0.0",
"http-server": "^14.1.1",
"jest": "29.7.0",
"jest-environment-jsdom": "30.0.0-beta.3",
@@ -149,7 +170,7 @@
"jsdom": "~22.1.0",
"msw": "^2.12.7",
"msw-storybook-addon": "^2.0.6",
"nx": "22.5.4",
"nx": "22.3.3",
"prettier": "^3.1.1",
"raw-loader": "^4.0.2",
"rimraf": "^5.0.5",
@@ -209,7 +230,7 @@
"packages/twenty-apps",
"packages/twenty-cli",
"packages/create-twenty-app",
"packages/twenty-oxlint-rules"
"packages/twenty-eslint-rules"
]
},
"prettier": {
-47
View File
@@ -1,47 +0,0 @@
{
"$schema": "./node_modules/oxlint/configuration_schema.json",
"plugins": ["typescript", "import", "unicorn"],
"categories": {
"correctness": "off"
},
"ignorePatterns": ["node_modules", "dist"],
"rules": {
"func-style": ["error", "declaration", { "allowArrowFunctions": true }],
"no-console": "off",
"no-control-regex": "off",
"no-debugger": "error",
"no-duplicate-imports": "error",
"no-undef": "off",
"no-unused-vars": "off",
"no-redeclare": "off",
"import/no-duplicates": "error",
"typescript/no-redeclare": "error",
"typescript/ban-ts-comment": "error",
"typescript/consistent-type-imports": [
"error",
{
"prefer": "type-imports",
"fixStyle": "inline-type-imports"
}
],
"typescript/explicit-function-return-type": "off",
"typescript/explicit-module-boundary-types": "off",
"typescript/no-empty-object-type": [
"error",
{
"allowInterfaces": "with-single-extends"
}
],
"typescript/no-empty-function": "off",
"typescript/no-explicit-any": "off",
"typescript/no-unused-vars": [
"warn",
{
"vars": "all",
"varsIgnorePattern": "^_",
"args": "after-used",
"argsIgnorePattern": "^_"
}
]
}
}
@@ -1 +0,0 @@
dist
+7 -15
View File
@@ -19,11 +19,9 @@ Create Twenty App is the official scaffolding CLI for building apps on top of [T
- Strong TypeScript support and typed client generation
## Documentation
See Twenty application documentation https://docs.twenty.com/developers/extend/capabilities/apps
## Prerequisites
- Node.js 24+ (recommended) and Yarn 4
- A Twenty workspace and an API key (create one at https://app.twenty.com/settings/api-webhooks)
@@ -66,11 +64,11 @@ yarn twenty app:uninstall
Control which example files are included when creating a new app:
| Flag | Behavior |
| ------------------- | ----------------------------------------------------------------------- |
| `-e, --exhaustive` | **(default)** Creates all example files without prompting |
| `-m, --minimal` | Creates only core files (`application-config.ts` and `default-role.ts`) |
| `-i, --interactive` | Prompts you to select which examples to include |
| Flag | Behavior |
|------|----------|
| `-e, --exhaustive` | **(default)** Creates all example files without prompting |
| `-m, --minimal` | Creates only core files (`application-config.ts` and `default-role.ts`) |
| `-i, --interactive` | Prompts you to select which examples to include |
```bash
# Default: all examples included
@@ -84,7 +82,6 @@ npx create-twenty-app@latest my-app -i
```
In interactive mode, you can pick from:
- **Example object** — a custom CRM object definition (`objects/example-object.ts`)
- **Example field** — a custom field on the example object (`fields/example-field.ts`)
- **Example logic function** — a server-side handler with HTTP trigger (`logic-functions/hello-world.ts`)
@@ -97,16 +94,14 @@ In interactive mode, you can pick from:
## What gets scaffolded
**Core files (always created):**
- `application-config.ts` — Application metadata configuration
- `roles/default-role.ts` — Default role for logic functions
- `logic-functions/pre-install.ts` — Pre-install logic function (runs before app installation)
- `logic-functions/post-install.ts` — Post-install logic function (runs after app installation)
- TypeScript configuration, Oxlint, package.json, .gitignore
- TypeScript configuration, ESLint, package.json, .gitignore
- A prewired `twenty` script that delegates to the `twenty` CLI from twenty-sdk
**Example files (controlled by scaffolding mode):**
- `objects/example-object.ts` — Example custom object with a text field
- `fields/example-field.ts` — Example standalone field extending the example object
- `logic-functions/hello-world.ts` — Example logic function with HTTP trigger
@@ -117,15 +112,14 @@ In interactive mode, you can pick from:
- `__tests__/app-install.integration-test.ts` — Integration test that builds, installs, and verifies the app (includes `vitest.config.ts`, `tsconfig.spec.json`, and a setup file)
## Next steps
- Run `yarn twenty help` to see all available commands.
- Use `yarn twenty auth:login` to authenticate with your Twenty workspace.
- Explore the generated project and add your first entity with `yarn twenty entity:add` (logic functions, front components, objects, roles, views, navigation menu items, skills).
- Use `yarn twenty app:dev` while you iterate — it watches, builds, and syncs changes to your workspace in real time.
- Two typed API clients are autogenerated by `yarn twenty app:dev` and stored in `node_modules/twenty-sdk/generated`: `CoreApiClient` (for workspace data via `/graphql`) and `MetadataApiClient` (for workspace configuration and file uploads via `/metadata`).
## Publish your application
## Publish your application
Applications are currently stored in `twenty/packages/twenty-apps`.
You can share your application with all Twenty users:
@@ -150,11 +144,9 @@ git push
Our team reviews contributions for quality, security, and reusability before merging.
## Troubleshooting
- Auth prompts not appearing: run `yarn twenty auth:login` again and verify the API key permissions.
- Types not generated: ensure `yarn twenty app:dev` is running — it autogenerates the typed client.
## Contributing
- See our [GitHub](https://github.com/twentyhq/twenty)
- Join our [Discord](https://discord.gg/cx5n4Jzs57)
@@ -0,0 +1,20 @@
import baseConfig from '../../eslint.config.mjs';
export default [
...baseConfig,
{
ignores: ['**/dist/**'],
},
{
files: ['**/*.{js,jsx,ts,tsx}'],
rules: {
'prettier/prettier': 'error',
},
},
{
rules: {
'no-console': 'off',
},
ignores: ['src/**/*.ts', '!src/cli/**/*.ts'],
},
];
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "create-twenty-app",
"version": "0.6.4",
"version": "0.6.3",
"description": "Command-line interface to create Twenty application",
"main": "dist/cli.cjs",
"bin": "dist/cli.cjs",
+13 -1
View File
@@ -25,7 +25,19 @@
}
},
"typecheck": {},
"lint": {},
"lint": {
"options": {
"lintFilePatterns": ["{projectRoot}/src/**/*.{ts,json}"],
"maxWarnings": 0
},
"configurations": {
"ci": {
"lintFilePatterns": ["{projectRoot}/src/**/*.{ts,json}"],
"maxWarnings": 0
},
"fix": {}
}
},
"test": {
"executor": "@nx/jest:jest",
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
@@ -1,19 +0,0 @@
{
"$schema": "./node_modules/oxlint/configuration_schema.json",
"plugins": ["typescript"],
"categories": {
"correctness": "off"
},
"ignorePatterns": ["node_modules", "dist"],
"rules": {
"no-unused-vars": "off",
"typescript/no-unused-vars": [
"warn",
{
"argsIgnorePattern": "^_"
}
],
"typescript/no-explicit-any": "off"
}
}
@@ -4,7 +4,6 @@
- Rich app example: https://github.com/twentyhq/twenty/tree/main/packages/twenty-sdk/src/cli/__tests__/apps/rich-app
## UUID requirement
- All generated UUIDs must be valid UUID v4.
## Common Pitfalls
@@ -0,0 +1,29 @@
import js from '@eslint/js';
import tseslint from 'typescript-eslint';
export default [
// Base JS recommended rules
js.configs.recommended,
// TypeScript recommended rules
...tseslint.configs.recommended,
{
files: ['**/*.ts', '**/*.tsx'],
languageOptions: {
parserOptions: {
project: true,
tsconfigRootDir: import.meta.dirname,
},
},
rules: {
// Common TypeScript-friendly tweaks
'@typescript-eslint/no-unused-vars': [
'warn',
{ argsIgnorePattern: '^_' },
],
'@typescript-eslint/no-explicit-any': 'off',
'no-unused-vars': 'off', // handled by TS rule
},
},
];
@@ -27,11 +27,5 @@
"~/*": ["./*"]
}
},
"exclude": [
"node_modules",
"dist",
"**/*.test.ts",
"**/*.spec.ts",
"**/*.integration-test.ts"
]
"exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts", "**/*.integration-test.ts"]
}
@@ -553,8 +553,8 @@ const createPackageJson = async ({
}) => {
const scripts: Record<string, string> = {
twenty: 'twenty',
lint: 'oxlint -c .oxlintrc.json .',
'lint:fix': 'oxlint --fix -c .oxlintrc.json .',
lint: 'eslint',
'lint:fix': 'eslint --fix',
};
const devDependencies: Record<string, string> = {
@@ -562,7 +562,8 @@ const createPackageJson = async ({
'@types/node': '^24.7.2',
'@types/react': '^18.2.0',
react: '^18.2.0',
oxlint: '^0.16.0',
eslint: '^9.32.0',
'typescript-eslint': '^8.50.0',
'twenty-sdk': createTwentyAppPackageJson.version,
};
+3 -1
View File
@@ -24,5 +24,7 @@
"vite.config.ts",
"jest.config.mjs"
],
"exclude": ["src/constants/base-application/vitest.config.ts"]
"exclude": [
"src/constants/base-application/vitest.config.ts"
]
}
@@ -1,38 +0,0 @@
{
"$schema": "./node_modules/oxlint/configuration_schema.json",
"plugins": ["typescript", "import", "unicorn"],
"categories": {
"correctness": "off"
},
"ignorePatterns": ["node_modules"],
"rules": {
"func-style": ["error", "declaration", { "allowArrowFunctions": true }],
"no-console": ["warn", { "allow": ["group", "groupCollapsed", "groupEnd"] }],
"no-control-regex": "off",
"no-debugger": "error",
"no-duplicate-imports": "error",
"no-undef": "off",
"no-unused-vars": "off",
"no-redeclare": "off",
"import/no-duplicates": "error",
"typescript/no-redeclare": "error",
"typescript/ban-ts-comment": "error",
"typescript/consistent-type-imports": ["error", {
"prefer": "type-imports",
"fixStyle": "inline-type-imports"
}],
"typescript/explicit-function-return-type": "off",
"typescript/explicit-module-boundary-types": "off",
"typescript/no-empty-object-type": ["error", {
"allowInterfaces": "with-single-extends"
}],
"typescript/no-empty-function": "off",
"typescript/no-explicit-any": "off",
"typescript/no-unused-vars": ["warn", {
"vars": "all",
"varsIgnorePattern": "^_",
"args": "after-used",
"argsIgnorePattern": "^_"
}]
}
}
@@ -0,0 +1,29 @@
import js from '@eslint/js';
import tseslint from 'typescript-eslint';
export default [
// Base JS recommended rules
js.configs.recommended,
// TypeScript recommended rules
...tseslint.configs.recommended,
{
files: ['**/*.ts', '**/*.tsx'],
languageOptions: {
parserOptions: {
project: true,
tsconfigRootDir: import.meta.dirname,
},
},
rules: {
// Common TypeScript-friendly tweaks
'@typescript-eslint/no-unused-vars': [
'warn',
{ argsIgnorePattern: '^_' },
],
'@typescript-eslint/no-explicit-any': 'off',
'no-unused-vars': 'off', // handled by TS rule
},
},
];
@@ -10,8 +10,8 @@
"packageManager": "yarn@4.9.2",
"scripts": {
"twenty": "twenty",
"lint": "oxlint -c .oxlintrc.json .",
"lint:fix": "oxlint --fix -c .oxlintrc.json ."
"lint": "eslint",
"lint:fix": "eslint --fix"
},
"dependencies": {
"twenty-sdk": "latest"
@@ -19,8 +19,9 @@
"devDependencies": {
"@types/node": "^24.7.2",
"@types/react": "^18.2.0",
"oxlint": "^0.16.0",
"eslint": "^9.32.0",
"react": "^18.2.0",
"typescript": "^5.9.3"
"typescript": "^5.9.3",
"typescript-eslint": "^8.50.0"
}
}
@@ -1,6 +1,7 @@
import { defineLogicFunction, RoutePayload } from "twenty-sdk";
import { MetadataApiClient } from 'twenty-sdk/generated';
export const OAUTH_TOKEN_PAIRS_PATH = '/oauth/token-pairs';
type ApolloTokenResponse = {
@@ -52,8 +53,16 @@ const handler = async (event: RoutePayload): Promise<any> => {
const apolloClientSecret = process.env.APOLLO_CLIENT_SECRET ?? '';
const applicationId = process.env.APPLICATION_ID ?? '';
const metadataClient = new MetadataApiClient({});
const tokenPairs = await getAuthenticationTokenPairs(
code,
apolloClientId,
@@ -5,6 +5,8 @@ import {
} from 'twenty-sdk';
import { CoreApiClient } from 'twenty-sdk/generated';
type CompanyRecord = {
id: string;
name?: string;
@@ -36,6 +38,8 @@ type ApolloEnrichResponse = {
organization?: ApolloOrganization;
};
const extractDomain = (
domainName?: CompanyRecord['domainName'],
): string | undefined => {
@@ -168,6 +172,7 @@ const updateCompanyInTwenty = async (
}
};
type CompanyUpdateEvent = DatabaseEventPayload<
ObjectRecordUpdateEvent<CompanyRecord>
>;
@@ -176,6 +181,7 @@ const handler = async (
event: CompanyUpdateEvent,
): Promise<object | undefined> => {
const { recordId, properties } = event;
const { after: companyAfter } = properties;
@@ -200,6 +206,7 @@ const handler = async (
return { skipped: true, reason: 'no enrichment data to apply' };
}
await updateCompanyInTwenty(recordId, updateData);
const result = {
File diff suppressed because it is too large Load Diff
@@ -1,40 +0,0 @@
{
"$schema": "./node_modules/oxlint/configuration_schema.json",
"plugins": ["typescript", "import", "unicorn"],
"categories": {
"correctness": "off"
},
"ignorePatterns": ["node_modules"],
"rules": {
"func-style": ["error", "declaration", { "allowArrowFunctions": true }],
"no-console": ["warn", { "allow": ["group", "groupCollapsed", "groupEnd"] }],
"no-control-regex": "off",
"no-debugger": "error",
"no-duplicate-imports": "error",
"no-undef": "off",
"no-unused-vars": "off",
"no-redeclare": "off",
"import/no-duplicates": "error",
"typescript/no-redeclare": "error",
"typescript/ban-ts-comment": "error",
"typescript/consistent-type-imports": ["error", {
"prefer": "type-imports",
"fixStyle": "inline-type-imports"
}],
"typescript/explicit-function-return-type": "off",
"typescript/explicit-module-boundary-types": "off",
"typescript/no-empty-object-type": ["error", {
"allowInterfaces": "with-single-extends"
}],
"typescript/no-empty-function": "off",
"typescript/no-explicit-any": "off",
"typescript/no-unused-vars": ["warn", {
"vars": "all",
"varsIgnorePattern": "^_",
"args": "after-used",
"argsIgnorePattern": "^_"
}]
}
}
@@ -26,6 +26,21 @@
"typecheck": {
"dependsOn": ["^build"]
},
"lint": {}
"lint": {
"executor": "@nx/eslint:lint",
"outputs": [
"{options.outputFile}"
],
"options": {
"lintFilePatterns": [
"packages/twenty-apps/community/fireflies/**/*.{ts,tsx,js,jsx}"
]
},
"configurations": {
"fix": {
"fix": true
}
}
}
}
}
@@ -5,7 +5,7 @@
* Usage: yarn setup:fields
*/
/* oxlint-disable no-console */
/* eslint-disable no-console */
import * as dotenv from 'dotenv';
import * as path from 'path';
@@ -1,4 +1,4 @@
/* oxlint-disable no-console */
/* eslint-disable no-console */
import * as dotenv from 'dotenv';
import * as path from 'path';
import { fileURLToPath } from 'url';
@@ -1,4 +1,4 @@
/* oxlint-disable no-console */
/* eslint-disable no-console */
import * as dotenv from 'dotenv';
import * as path from 'path';
import { fileURLToPath } from 'url';
@@ -1,4 +1,4 @@
/* oxlint-disable no-console */
/* eslint-disable no-console */
/**
* Fetch historical Fireflies meetings and insert into Twenty.
*
@@ -219,3 +219,4 @@ main().catch((error) => {
process.exit(1);
});
@@ -1,4 +1,4 @@
/* oxlint-disable no-console */
/* eslint-disable no-console */
/**
* Fetch a Fireflies meeting by ID and insert it into Twenty using the same path
* as the webhook handler.
@@ -1,4 +1,4 @@
/* oxlint-disable no-console */
/* eslint-disable no-console */
/**
* Test script for Fireflies webhook against local Twenty instance
*
@@ -81,3 +81,4 @@ describe('HistoricalImporter', () => {
});
});
@@ -158,3 +158,4 @@ export class HistoricalImporter {
}
}
@@ -72,7 +72,7 @@ export class AppLogger {
debug(message: string, ...args: unknown[]): void {
this.captureLog('debug', message, ...args);
if (this.shouldLog('debug')) {
// oxlint-disable-next-line no-console
// eslint-disable-next-line no-console
console.log(`[${this.context}] ${message}`, ...args);
}
}
@@ -80,7 +80,7 @@ export class AppLogger {
info(message: string, ...args: unknown[]): void {
this.captureLog('info', message, ...args);
if (this.shouldLog('info')) {
// oxlint-disable-next-line no-console
// eslint-disable-next-line no-console
console.log(`[${this.context}] ${message}`, ...args);
}
}
@@ -88,7 +88,7 @@ export class AppLogger {
warn(message: string, ...args: unknown[]): void {
this.captureLog('warn', message, ...args);
if (this.shouldLog('warn')) {
// oxlint-disable-next-line no-console
// eslint-disable-next-line no-console
console.warn(`[${this.context}] ${message}`, ...args);
}
}
@@ -96,7 +96,7 @@ export class AppLogger {
error(message: string, ...args: unknown[]): void {
this.captureLog('error', message, ...args);
if (this.shouldLog('error')) {
// oxlint-disable-next-line no-console
// eslint-disable-next-line no-console
console.error(`[${this.context}] ${message}`, ...args);
}
}
@@ -104,7 +104,7 @@ export class AppLogger {
// For fatal errors, security issues, or data corruption - always visible
critical(message: string, ...args: unknown[]): void {
this.captureLog('error', `CRITICAL: ${message}`, ...args);
// oxlint-disable-next-line no-console
// eslint-disable-next-line no-console
console.error(`[${this.context}] CRITICAL: ${message}`, ...args);
}
@@ -1,2 +1,3 @@
export { Meeting } from './meeting';
@@ -330,6 +330,7 @@ export class WebhookHandler {
}
}
private async createFailedMeetingRecord(params: unknown, error: string): Promise<void> {
try {
const twentyApiKey = process.env.TWENTY_API_KEY || '';
@@ -1,54 +0,0 @@
{
"$schema": "./node_modules/oxlint/configuration_schema.json",
"plugins": ["react", "typescript", "import", "unicorn"],
"categories": {
"correctness": "off"
},
"ignorePatterns": [
"node_modules"
],
"rules": {
"func-style": ["error", "declaration", { "allowArrowFunctions": true }],
"no-console": ["warn", { "allow": ["group", "groupCollapsed", "groupEnd"] }],
"no-control-regex": "off",
"no-debugger": "error",
"no-duplicate-imports": "error",
"no-undef": "off",
"no-unused-vars": "off",
"no-redeclare": "off",
"import/no-duplicates": "error",
"react/no-unescaped-entities": "off",
"react/prop-types": "off",
"react/jsx-key": "off",
"react/display-name": "off",
"react/jsx-uses-react": "off",
"react/react-in-jsx-scope": "off",
"react/jsx-no-useless-fragment": "off",
"react/jsx-props-no-spreading": ["error", { "explicitSpread": "ignore" }],
"react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive-deps": "warn",
"typescript/no-redeclare": "error",
"typescript/ban-ts-comment": "error",
"typescript/consistent-type-imports": ["error", {
"prefer": "type-imports",
"fixStyle": "inline-type-imports"
}],
"typescript/explicit-function-return-type": "off",
"typescript/explicit-module-boundary-types": "off",
"typescript/no-empty-object-type": ["error", {
"allowInterfaces": "with-single-extends"
}],
"typescript/no-empty-function": "off",
"typescript/no-explicit-any": "off",
"typescript/no-unused-vars": ["warn", {
"vars": "all",
"varsIgnorePattern": "^_",
"args": "after-used",
"argsIgnorePattern": "^_"
}]
}
}
@@ -0,0 +1,69 @@
import typescriptParser from '@typescript-eslint/parser';
import path from 'path';
import { fileURLToPath } from 'url';
import reactConfig from '../../../../twenty-eslint-rules/eslint.config.react.mjs';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
export default [
// Extend shared React configuration
...reactConfig,
// Global ignores
{
ignores: [
'**/node_modules/**',
],
},
// TypeScript project-specific configuration
{
files: ['**/*.{ts,tsx}'],
languageOptions: {
parser: typescriptParser,
parserOptions: {
project: [path.resolve(__dirname, 'tsconfig.*.json')],
ecmaFeatures: {
jsx: true,
},
},
},
rules: {
'@nx/enforce-module-boundaries': [
'error',
{
enforceBuildableLibDependency: true,
allow: [],
depConstraints: [
{
sourceTag: 'scope:sdk',
onlyDependOnLibsWithTags: ['scope:sdk'],
},
{
sourceTag: 'scope:shared',
onlyDependOnLibsWithTags: ['scope:shared'],
},
{
sourceTag: 'scope:backend',
onlyDependOnLibsWithTags: ['scope:shared', 'scope:backend'],
},
{
sourceTag: 'scope:frontend',
onlyDependOnLibsWithTags: ['scope:shared', 'scope:frontend'],
},
{
sourceTag: 'scope:zapier',
onlyDependOnLibsWithTags: ['scope:shared'],
},
{
sourceTag: 'scope:browser-extension',
onlyDependOnLibsWithTags: ['scope:twenty-ui', 'scope:browser-extension']
}
],
},
],
}
},
];
@@ -43,6 +43,7 @@ export default defineContentScript({
},
});
ctx.addEventListener(window, 'wxt:locationchange', ({newUrl, }) => {
const injectedBtn = document.querySelector('[data-id="twenty-btn"]');
if(personPattern.includes(newUrl) && !injectedBtn) ui.mount();
@@ -8,6 +8,7 @@ const StyledButton = styled.button`
--text-color: #0a66c2;
--bg-color: #00000000;
font-size: ${({theme}) => theme.spacing(3.5)};
font-weight: ${({theme}) => theme.font.weight.semiBold};
font-family: ${({theme}) => theme.font.family};
@@ -25,6 +26,7 @@ const StyledButton = styled.button`
transition-timing-function: cubic-bezier(.4, 0, .2, 1);
transition-duration: 167ms;
&:hover {
background-color: var(--hover-bg-color);
color: var(--hover-color);
@@ -1,5 +1,6 @@
import { ThemeProvider } from '@emotion/react';
import type React from 'react';
import { ColorSchemeProvider } from 'twenty-ui/theme-constants';
import { THEME_DARK, ThemeContextProvider } from 'twenty-ui/theme';
type ThemeContextProps = {
children: React.ReactNode;
@@ -7,6 +8,10 @@ type ThemeContextProps = {
export const ThemeContext = ({ children }: ThemeContextProps) => {
return (
<ColorSchemeProvider colorScheme="dark">{children}</ColorSchemeProvider>
<ThemeProvider theme={THEME_DARK}>
<ThemeContextProvider theme={THEME_DARK}>
{children}
</ThemeContextProvider>
</ThemeProvider>
);
};
@@ -0,0 +1,5 @@
import type { ThemeType } from 'twenty-ui/theme';
declare module '@emotion/react' {
export interface Theme extends ThemeType {}
}
@@ -1,38 +0,0 @@
{
"$schema": "./node_modules/oxlint/configuration_schema.json",
"plugins": ["typescript", "import", "unicorn"],
"categories": {
"correctness": "off"
},
"ignorePatterns": ["node_modules"],
"rules": {
"func-style": ["error", "declaration", { "allowArrowFunctions": true }],
"no-console": ["warn", { "allow": ["group", "groupCollapsed", "groupEnd"] }],
"no-control-regex": "off",
"no-debugger": "error",
"no-duplicate-imports": "error",
"no-undef": "off",
"no-unused-vars": "off",
"no-redeclare": "off",
"import/no-duplicates": "error",
"typescript/no-redeclare": "error",
"typescript/ban-ts-comment": "error",
"typescript/consistent-type-imports": ["error", {
"prefer": "type-imports",
"fixStyle": "inline-type-imports"
}],
"typescript/explicit-function-return-type": "off",
"typescript/explicit-module-boundary-types": "off",
"typescript/no-empty-object-type": ["error", {
"allowInterfaces": "with-single-extends"
}],
"typescript/no-empty-function": "off",
"typescript/no-explicit-any": "off",
"typescript/no-unused-vars": ["warn", {
"vars": "all",
"varsIgnorePattern": "^_",
"args": "after-used",
"argsIgnorePattern": "^_"
}]
}
}
@@ -0,0 +1,29 @@
import js from '@eslint/js';
import tseslint from 'typescript-eslint';
export default [
// Base JS recommended rules
js.configs.recommended,
// TypeScript recommended rules
...tseslint.configs.recommended,
{
files: ['**/*.ts', '**/*.tsx'],
languageOptions: {
parserOptions: {
project: true,
tsconfigRootDir: import.meta.dirname,
},
},
rules: {
// Common TypeScript-friendly tweaks
'@typescript-eslint/no-unused-vars': [
'warn',
{ argsIgnorePattern: '^_' },
],
'@typescript-eslint/no-explicit-any': 'off',
'no-unused-vars': 'off', // handled by TS rule
},
},
];
@@ -1,6 +1,6 @@
{
"name": "@twentyhq/hello-world",
"version": "0.2.2",
"name": "hello-world",
"version": "0.1.0",
"license": "MIT",
"engines": {
"node": "^24.5.0",
@@ -10,18 +10,19 @@
"packageManager": "yarn@4.9.2",
"scripts": {
"twenty": "twenty",
"lint": "oxlint -c .oxlintrc.json .",
"lint:fix": "oxlint --fix -c .oxlintrc.json .",
"lint": "eslint",
"lint:fix": "eslint --fix",
"test": "vitest run",
"test:watch": "vitest"
},
"devDependencies": {
"@types/node": "^24.7.2",
"@types/react": "^18.2.0",
"oxlint": "^0.16.0",
"eslint": "^9.32.0",
"react": "^18.2.0",
"twenty-sdk": "0.6.3",
"typescript": "^5.9.3",
"typescript-eslint": "^8.50.0",
"vite-tsconfig-paths": "^4.2.1",
"vitest": "^3.1.1"
}
File diff suppressed because it is too large Load Diff
@@ -1,38 +0,0 @@
{
"$schema": "./node_modules/oxlint/configuration_schema.json",
"plugins": ["typescript", "import", "unicorn"],
"categories": {
"correctness": "off"
},
"ignorePatterns": ["node_modules"],
"rules": {
"func-style": ["error", "declaration", { "allowArrowFunctions": true }],
"no-console": ["warn", { "allow": ["group", "groupCollapsed", "groupEnd"] }],
"no-control-regex": "off",
"no-debugger": "error",
"no-duplicate-imports": "error",
"no-undef": "off",
"no-unused-vars": "off",
"no-redeclare": "off",
"import/no-duplicates": "error",
"typescript/no-redeclare": "error",
"typescript/ban-ts-comment": "error",
"typescript/consistent-type-imports": ["error", {
"prefer": "type-imports",
"fixStyle": "inline-type-imports"
}],
"typescript/explicit-function-return-type": "off",
"typescript/explicit-module-boundary-types": "off",
"typescript/no-empty-object-type": ["error", {
"allowInterfaces": "with-single-extends"
}],
"typescript/no-empty-function": "off",
"typescript/no-explicit-any": "off",
"typescript/no-unused-vars": ["warn", {
"vars": "all",
"varsIgnorePattern": "^_",
"args": "after-used",
"argsIgnorePattern": "^_"
}]
}
}
@@ -0,0 +1,29 @@
import js from '@eslint/js';
import tseslint from 'typescript-eslint';
export default [
// Base JS recommended rules
js.configs.recommended,
// TypeScript recommended rules
...tseslint.configs.recommended,
{
files: ['**/*.ts', '**/*.tsx'],
languageOptions: {
parserOptions: {
project: true,
tsconfigRootDir: import.meta.dirname,
},
},
rules: {
// Common TypeScript-friendly tweaks
'@typescript-eslint/no-unused-vars': [
'warn',
{ argsIgnorePattern: '^_' },
],
'@typescript-eslint/no-explicit-any': 'off',
'no-unused-vars': 'off', // handled by TS rule
},
},
];
@@ -10,8 +10,8 @@
"packageManager": "yarn@4.9.2",
"scripts": {
"twenty": "twenty",
"lint": "oxlint -c .oxlintrc.json .",
"lint:fix": "oxlint --fix -c .oxlintrc.json ."
"lint": "eslint",
"lint:fix": "eslint --fix"
},
"dependencies": {
"@emotion/react": "^11.11.1",
@@ -23,8 +23,9 @@
"devDependencies": {
"@types/node": "^24.7.2",
"@types/react": "^18.2.0",
"oxlint": "^0.16.0",
"eslint": "^9.32.0",
"react": "^18.2.0",
"typescript": "^5.9.3"
"typescript": "^5.9.3",
"typescript-eslint": "^8.50.0"
}
}
File diff suppressed because it is too large Load Diff
@@ -1,40 +0,0 @@
{
"$schema": "./node_modules/oxlint/configuration_schema.json",
"plugins": ["typescript", "import", "unicorn"],
"categories": {
"correctness": "off"
},
"ignorePatterns": ["node_modules"],
"rules": {
"func-style": ["error", "declaration", { "allowArrowFunctions": true }],
"no-console": ["warn", { "allow": ["group", "groupCollapsed", "groupEnd"] }],
"no-control-regex": "off",
"no-debugger": "error",
"no-duplicate-imports": "error",
"no-undef": "off",
"no-unused-vars": "off",
"no-redeclare": "off",
"import/no-duplicates": "error",
"typescript/no-redeclare": "error",
"typescript/ban-ts-comment": "error",
"typescript/consistent-type-imports": ["error", {
"prefer": "type-imports",
"fixStyle": "inline-type-imports"
}],
"typescript/explicit-function-return-type": "off",
"typescript/explicit-module-boundary-types": "off",
"typescript/no-empty-object-type": ["error", {
"allowInterfaces": "with-single-extends"
}],
"typescript/no-empty-function": "off",
"typescript/no-explicit-any": "off",
"typescript/no-unused-vars": ["warn", {
"vars": "all",
"varsIgnorePattern": "^_",
"args": "after-used",
"argsIgnorePattern": "^_"
}]
}
}
@@ -10,12 +10,11 @@
"packageManager": "yarn@4.9.2",
"scripts": {
"twenty": "twenty",
"lint": "oxlint -c .oxlintrc.json .",
"lint:fix": "oxlint --fix -c .oxlintrc.json ."
"lint": "eslint",
"lint:fix": "eslint --fix"
},
"devDependencies": {
"@types/node": "^24.7.2",
"oxlint": "^0.16.0",
"twenty-sdk": "0.6.2"
},
"$schema": "https://raw.githubusercontent.com/twentyhq/twenty/main/packages/twenty-cli/src/constants/schemas/appManifest.schema.json",
+2 -2
View File
@@ -45,8 +45,8 @@ typings/
# Optional npm cache directory
.npm
# Optional oxlint cache
.oxlintcache
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
File diff suppressed because it is too large Load Diff
@@ -8,7 +8,7 @@ COPY ./yarn.lock .
COPY ./.yarnrc.yml .
COPY ./.yarn/releases /app/.yarn/releases
COPY ./.yarn/patches /app/.yarn/patches
COPY ./packages/twenty-oxlint-rules /app/packages/twenty-oxlint-rules
COPY ./packages/twenty-eslint-rules /app/packages/twenty-eslint-rules
COPY ./packages/twenty-ui/package.json /app/packages/twenty-ui/
COPY ./packages/twenty-shared/package.json /app/packages/twenty-shared/
COPY ./packages/twenty-website/package.json /app/packages/twenty-website/package.json
-34
View File
@@ -1,34 +0,0 @@
{
"$schema": "./node_modules/oxlint/configuration_schema.json",
"plugins": ["typescript", "import", "unicorn"],
"ignorePatterns": ["node_modules", ".next", "storybook-static"],
"rules": {
"func-style": ["error", "declaration", { "allowArrowFunctions": true }],
"no-console": ["warn", { "allow": ["group", "groupCollapsed", "groupEnd"] }],
"no-control-regex": "off",
"no-debugger": "error",
"no-duplicate-imports": "error",
"no-undef": "off",
"no-unused-vars": "off",
"no-redeclare": "off",
"import/no-duplicates": "error",
"typescript/no-redeclare": "error",
"typescript/consistent-type-imports": ["error", {
"prefer": "type-imports",
"fixStyle": "inline-type-imports"
}],
"typescript/explicit-function-return-type": "off",
"typescript/explicit-module-boundary-types": "off",
"typescript/no-empty-object-type": ["error", {
"allowInterfaces": "with-single-extends"
}],
"typescript/no-empty-function": "off",
"typescript/no-explicit-any": "off",
"typescript/no-unused-vars": ["warn", {
"vars": "all",
"varsIgnorePattern": "^_",
"args": "after-used",
"argsIgnorePattern": "^_"
}]
}
}
@@ -92,7 +92,7 @@ Here's what the tech stack now looks like.
**Tooling**
- [Yarn](https://yarnpkg.com/)
- [Oxlint](https://oxc.rs/docs/guide/usage/linter.html)
- [ESLint](https://eslint.org/)
**Development**
- [AWS EKS](https://aws.amazon.com/eks/)
@@ -64,7 +64,7 @@ The project has a clean and simple stack, with minimal boilerplate code.
- [Yarn](https://yarnpkg.com/)
- [Craco](https://craco.js.org/docs/)
- [Oxlint](https://oxc.rs/docs/guide/usage/linter.html)
- [ESLint](https://eslint.org/)
## Architecture
@@ -260,7 +260,7 @@ const StyledButton = styled.button`
```
## Enforcing No-Type Imports
Avoid type imports. To enforce this standard, an Oxlint rule checks for and reports any type imports. This helps maintain consistency and readability in the TypeScript code.
Avoid type imports. To enforce this standard, an ESLint rule checks for and reports any type imports. This helps maintain consistency and readability in the TypeScript code.
```tsx
// ❌ Bad
@@ -281,11 +281,11 @@ import { Meta, StoryObj } from '@storybook/react';
- **Maintainability**: It enhances codebase maintainability because developers can identify and locate type-only imports when reviewing or modifying code.
### Oxlint Rule
### ESLint Rule
An Oxlint rule, `typescript/consistent-type-imports`, enforces the no-type import standard. This rule will generate errors or warnings for any type import violations.
An ESLint rule, `@typescript-eslint/consistent-type-imports`, enforces the no-type import standard. This rule will generate errors or warnings for any type import violations.
Please note that this rule specifically addresses rare edge cases where unintentional type imports occur. TypeScript itself discourages this practice, as mentioned in the [TypeScript 3.8 release notes](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-8.html). In most situations, you should not need to use type-only imports.
To ensure your code complies with this rule, make sure to run Oxlint as part of your development workflow.
To ensure your code complies with this rule, make sure to run ESLint as part of your development workflow.
@@ -95,7 +95,7 @@ my-twenty-app/
.yarnrc.yml
.yarn/
install-state.gz
.oxlintrc.json
eslint.config.mjs
tsconfig.json
README.md
public/ # Public assets folder (images, fonts, etc.)
@@ -129,7 +129,7 @@ At a high level:
- **.gitignore**: Ignores common artifacts such as `node_modules`, `.yarn`, `generated/` (typed client), `dist/`, `build/`, coverage folders, log files, and `.env*` files.
- **yarn.lock**, **.yarnrc.yml**, **.yarn/**: Lock and configure the Yarn 4 toolchain used by the project.
- **.nvmrc**: Pins the Node.js version expected by the project.
- **.oxlintrc.json** and **tsconfig.json**: Provide linting and TypeScript configuration for your app's TypeScript sources.
- **eslint.config.mjs** and **tsconfig.json**: Provide linting and TypeScript configuration for your app's TypeScript sources.
- **README.md**: A short README in the app root with basic instructions.
- **public/**: A folder for storing public assets (images, fonts, static files) that will be served with your application. Files placed here are uploaded during sync and accessible at runtime.
- **src/**: The main place where you define your application-as-code
@@ -54,18 +54,21 @@ Make sure to run yarn in the root directory and then run `npx nx server:dev twen
#### Lint on Save not working
This should work out of the box with the Oxc extension (`oxc.oxc-vscode`) installed. If this doesn't work try adding this to your vscode setting (on the dev container scope):
This should work out of the box with the eslint extension installed. If this doesn't work try adding this to your vscode setting (on the dev container scope):
```
"editor.codeActionsOnSave": {
"source.fixAll.oxc": "explicit"
"source.fixAll.eslint": "explicit"
}
```
#### While running `npx nx start` or `npx nx start twenty-front`, Out of memory error is thrown
In `packages/twenty-front/.env` uncomment `VITE_DISABLE_TYPESCRIPT_CHECKER=true` to disable background checks thus reducing amount of needed RAM.
**If it does not work:**
Run only the services you need, instead of `npx nx start`. For instance, if you work on the server, run only `npx nx worker twenty-server`
**If it does not work:**
+4
View File
@@ -0,0 +1,4 @@
import baseConfig from '../../eslint.config.mjs';
export default [...baseConfig];
@@ -96,7 +96,7 @@ Prisma كان أول ORM استخدمناه. ولكن للسماح للمستخ
**الأدوات**
* [Yarn](https://yarnpkg.com/)
* [Oxlint](https://oxc.rs/docs/guide/usage/linter.html)
* [ESLint](https://eslint.org/)
**التطوير**
@@ -61,7 +61,7 @@ title: أوامر الواجهة الأمامية
* [Yarn](https://yarnpkg.com/)
* "[Craco](https://craco.js.org/docs/)"
* [Oxlint](https://oxc.rs/docs/guide/usage/linter.html)
* [ESLint](https://eslint.org/)
## "الهيكلية"
@@ -260,7 +260,7 @@ const StyledButton = styled.button`
## تطبيق قاعدة "عدم استيراد الأنواع"
تجنب استيراد الأنواع. لفرض هذا المعيار، تتحقق قاعدة Oxlint وتبلغ عن أي استيرادات للأنواع. يساعد هذا على الحفاظ على الاتساق وقابلية القراءة في كود TypeScript.
تجنب استيراد الأنواع. للحد من هذه الممارسة، تتحقق قاعدة ESLint وتبلغ عن أي استيرادات من هذا النوع. يساعد هذا على الحفاظ على الاتساق وقابلية القراءة في كود TypeScript.
```tsx
// ❌ Bad
@@ -281,10 +281,10 @@ import { Meta, StoryObj } from '@storybook/react';
* **الصيانة**: يعزز الصيانة داخل قاعدة الكود لأن المطوّرين يمكنهم تحديد مواقع استيرادات الأنواع فقط عند استعراض أو تعديل الكود.
### قاعدة Oxlint
### قاعدة ESLint
تفرض قاعدة Oxlint، `typescript/consistent-type-imports`, معيار عدم استيراد الأنواع. ستولد هذه القاعدة تحذيرات أو أخطاء عن أي انتهاكات لاستيراد الأنواع.
تفرض قاعدة ESLint، `@typescript-eslint/consistent-type-imports`, معيار عدم استيراد الأنواع. ستولد هذه القاعدة تحذيرات أو أخطاء عن أي انتهاكات لاستيراد الأنواع.
يرجى ملاحظة أن هذه القاعدة تتناول بشكل خاص حالات الحافة النادرة حيث تحدث استيرادات الأنواع دون قصد. يمنع TypeScript نفسه هذه الممارسة، كما هو موضح في [ملاحظات إصدار TypeScript 3.8](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-8.html). في معظم الحالات، لا ينبغي لك أن تستخدم استيرادات الأنواع وحدها.
لضمان امتثال الكود الخاص بك لهذه القاعدة، تأكد من تشغيل Oxlint كجزء من سير العمل الخاص بالتطوير لديك.
لضمان امتثال الكود الخاص بك لهذه القاعدة، تأكد من تشغيل ESLint كجزء من سير العمل الخاص بالتطوير لديك.
@@ -96,7 +96,7 @@ my-twenty-app/
.yarnrc.yml
.yarn/
install-state.gz
.oxlintrc.json
eslint.config.mjs
tsconfig.json
README.md
public/ # مجلد الأصول العامة (صور، خطوط، إلخ)
@@ -119,7 +119,7 @@ my-twenty-app/
├── navigation-menu-items/
│ └── example-navigation-menu-item.ts # رابط تنقّل في الشريط الجانبي — مثال
└── skills/
└── example-skill.ts # تعريف مهارة لوكيل الذكاء الاصطناعي — مثال},{
└── example-skill.ts # تعريف مهارة لوكيل الذكاء الاصطناعي — مثال
```
مع `--minimal`، سيتم إنشاء الملفات الأساسية فقط (`application-config.ts`، `roles/default-role.ts`، `logic-functions/pre-install.ts`، و`logic-functions/post-install.ts`). مع `--interactive`، تختار ملفات الأمثلة التي تريد تضمينها.
@@ -130,7 +130,7 @@ my-twenty-app/
* **.gitignore**: يتجاهل العناصر الشائعة مثل `node_modules` و`.yarn` و`generated/` (عميل مضبوط الأنواع) و`dist/` و`build/` ومجلدات التغطية وملفات السجلات وملفات `.env*`.
* **yarn.lock**، **.yarnrc.yml**، **.yarn/**: تقوم بقفل وتكوين حزمة أدوات Yarn 4 المستخدمة في المشروع.
* **.nvmrc**: يثبّت إصدار Node.js المتوقع للمشروع.
* **.oxlintrc.json** و **tsconfig.json**: يقدّمان إعدادات الفحص والتهيئة لـ TypeScript لمصادر TypeScript في تطبيقك.
* **eslint.config.mjs** و**tsconfig.json**: يقدّمان إعدادات الفحص والتهيئة لـ TypeScript لمصادر TypeScript في تطبيقك.
* **README.md**: ملف README قصير في جذر التطبيق يتضمن تعليمات أساسية.
* **public/**: مجلد لتخزين الأصول العامة (صور، خطوط، ملفات ثابتة) التي سيتم تقديمها مع تطبيقك. الملفات الموضوعة هنا تُرفع أثناء المزامنة وتكون متاحة أثناء وقت التشغيل.
* **src/**: المكان الرئيسي حيث تعرّف تطبيقك ككود
@@ -53,19 +53,22 @@ git config --global core.autocrlf false
#### التحقق من العمليات عند الحفظ لا يعمل
هذا يجب أن يعمل تلقائيًا مع تثبيت إضافة Oxc (`oxc.oxc-vscode`). إذا لم يعمل ذلك، حاول إضافة هذا إلى إعدادات vscode (ضمن نطاق حاوية التطوير):
هذا يجب أن يعمل تلقائيًا مع تثبيت إضافة eslint. إذا لم يعمل ذلك، حاول إضافة هذا إلى إعدادات vscode (ضمن نطاق حاوية التطوير):
```
"editor.codeActionsOnSave": {
"source.fixAll.oxc": "explicit"
"source.fixAll.eslint": "explicit"
}
```
#### أثناء تشغيل `npx nx start` أو `npx nx start twenty-front`، ظهرت خطأ نفاد الذاكرة
Run only the services you need, instead of `npx nx start`. على سبيل المثال، إذا كنت تعمل على الخادم، قم بتشغيل `npx nx worker twenty-server` فقط
في `packages/twenty-front/.env` قم بإزالة تعليق على `VITE_DISABLE_TYPESCRIPT_CHECKER=true` و`VITE_DISABLE_ESLINT_CHECKER=true` لتعطيل فحوصات الخلفية مما يقلل من كمية الذاكرة المطلوبة.
**إذا لم يعمل ذلك:**
قم بتشغيل الخدمات التي تحتاجها فقط، بدلاً من `npx nx start`. على سبيل المثال، إذا كنت تعمل على الخادم، قم بتشغيل `npx nx worker twenty-server` فقط
**إذا لم يعمل ذلك:**
إذا حاولت تشغيل `npx nx run twenty-server:start` فقط على WSL وفشل مع خطأ الذاكرة أدناه:
@@ -95,7 +95,7 @@ Takto nyní vypadá technologický stack.
**Nástroje**
* [Yarn](https://yarnpkg.com/)
* [Oxlint](https://oxc.rs/docs/guide/usage/linter.html)
* [ESLint](https://eslint.org/)
**Vývoj**
@@ -65,7 +65,7 @@ Projekt má čistý a jednoduchý stack s minimálním počtem šablonových kó
* [Yarn](https://yarnpkg.com/)
* [Craco](https://craco.js.org/docs/)
* [Oxlint](https://oxc.rs/docs/guide/usage/linter.html)
* [ESLint](https://eslint.org/)
## Architektura
@@ -260,7 +260,7 @@ const StyledButton = styled.button`
## Prosazování Zákazu Importů Typů
Vyhýbejte se typovým importům. K prosazení tohoto standardu pravidlo Oxlint kontroluje a hlásí jakékoli typové importy. To pomáhá udržovat konzistenci a čitelnost v TypeScript kódu.
Vyhýbejte se typovým importům. K prosazení tohoto standardu pravidlo ESLint kontroluje a hlásí jakékoli typové importy. To pomáhá udržovat konzistenci a čitelnost v TypeScript kódu.
```tsx
// ❌ Bad
@@ -281,10 +281,10 @@ import { Meta, StoryObj } from '@storybook/react';
* **Udržovatelnost**: Zvyšuje udržovatelnost kódové základny, protože vývojáři mohou při revizi nebo úpravách kódu identifikovat a najít importy pouze typů.
### Pravidlo Oxlint
### Pravidlo ESLint
Pravidlo Oxlint, `typescript/consistent-type-imports`, prosazuje standard zákazu importů typů. Toto pravidlo generuje chyby nebo varování pro jakékoli porušení typového importu.
Pravidlo ESLint, `@typescript-eslint/consistent-type-imports`, prosazuje standard zákazu importů typů. Toto pravidlo generuje chyby nebo varování pro jakékoli porušení typového importu.
Upozorňujeme, že toto pravidlo konkrétně řeší vzácné okrajové případy, kdy dochází k neúmyslným typovým importům. TypeScript sám odrazuje tuto praxi, jak je uvedeno v [poznámkách k verzi TypeScript 3.8](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-8.html). Ve většině případů byste neměli potřebovat používat pouze typové importy.
Aby váš kód splňoval toto pravidlo, spusťte Oxlint jako součást svého vývojového pracovního postupu.
Aby váš kód splňoval toto pravidlo, spusťte ESLint jako součást svého vývojového pracovního postupu.
@@ -96,7 +96,7 @@ my-twenty-app/
.yarnrc.yml
.yarn/
install-state.gz
.oxlintrc.json
eslint.config.mjs
tsconfig.json
README.md
public/ # Složka s veřejnými prostředky (obrázky, písma apod.)
@@ -130,7 +130,7 @@ V kostce:
* **.gitignore**: Ignoruje běžné artefakty jako `node_modules`, `.yarn`, `generated/` (typovaný klient), `dist/`, `build/`, složky s coverage, logy a soubory `.env*`.
* **yarn.lock**, **.yarnrc.yml**, **.yarn/**: Zamykají a konfigurují nástrojový řetězec Yarn 4 používaný projektem.
* **.nvmrc**: Fixuje verzi Node.js požadovanou projektem.
* **.oxlintrc.json** and **tsconfig.json**: Provide linting and TypeScript configuration for your app's TypeScript sources.
* **eslint.config.mjs** a **tsconfig.json**: Poskytují lintování a konfiguraci TypeScriptu pro zdrojové soubory vaší aplikace v TypeScriptu.
* **README.md**: Krátké README v kořeni aplikace se základními pokyny.
* **public/**: Složka pro ukládání veřejných prostředků (obrázky, písma, statické soubory), které bude vaše aplikace poskytovat. Soubory umístěné zde se během synchronizace nahrají a jsou za běhu dostupné.
* **src/**: Hlavní místo, kde definujete svou aplikaci jako kód
@@ -54,18 +54,21 @@ Ujistěte se, že v kořenovém adresáři spouštíte `yarn` a poté spusťte `
#### Lint při ukládání nefunguje
Toto by mělo fungovat přímo s nainstalovaným rozšířením Oxc (`oxc.oxc-vscode`). Pokud to nefunguje, zkuste přidat toto do svého nastavení vscode (v rozsahu dev containeru):
Toto by mělo fungovat přímo s nainstalovaným rozšířením eslint. Pokud to nefunguje, zkuste přidat toto do svého nastavení vscode (v rozsahu dev containeru):
```
"editor.codeActionsOnSave": {
"source.fixAll.oxc": "explicit"
"source.fixAll.eslint": "explicit"
}
```
#### Při spuštění `npx nx start` nebo `npx nx start twenty-front` došlo k chybě nedostatku paměti
In `packages/twenty-front/.env` uncomment `VITE_DISABLE_TYPESCRIPT_CHECKER=true` and `VITE_DISABLE_ESLINT_CHECKER=true` to disable background checks thus reducing amount of needed RAM.
**Pokud to nefunguje:**
Spusťte pouze služby, které potřebujete, místo `npx nx start`. Například pokud pracujete na serveru, spusťte pouze `npx nx worker twenty-server`
**Pokud to nefunguje:**
@@ -96,7 +96,7 @@ So sieht der Tech-Stack jetzt aus.
**Tools**
* [Yarn](https://yarnpkg.com/)
* [Oxlint](https://oxc.rs/docs/guide/usage/linter.html)
* [ESLint](https://eslint.org/)
**Entwicklung**
@@ -65,7 +65,7 @@ Das Projekt hat einen sauberen und einfachen Stack mit minimalem Boilerplate-Cod
* [Yarn](https://yarnpkg.com/)
* [Craco](https://craco.js.org/docs/)
* [Oxlint](https://oxc.rs/docs/guide/usage/linter.html)
* [ESLint](https://eslint.org/)
## Architektur
@@ -260,7 +260,7 @@ const StyledButton = styled.button`
## Durchsetzung von No-Type Imports
Vermeiden Sie Typ-Importe. Um diesen Standard durchzusetzen, überprüft eine Oxlint-Regel alle Typ-Importe und meldet sie. Dies trägt zur Konsistenz und Lesbarkeit des TypeScript-Codes bei.
Vermeiden Sie Typ-Importe. Um diesen Standard durchzusetzen, überprüft eine ESLint-Regel alle Typ-Importe und meldet sie. Dies trägt zur Konsistenz und Lesbarkeit des TypeScript-Codes bei.
```tsx
// ❌ Bad
@@ -281,10 +281,10 @@ import { Meta, StoryObj } from '@storybook/react';
* **Wartbarkeit**: Es verbessert die Wartbarkeit der Codebasis, da Entwickler Typ-Only-Imports beim Überprüfen oder Ändern von Code identifizieren und lokalisieren können.
### Oxlint-Regel
### ESLint-Regel
Eine Oxlint-Regel, `typescript/consistent-type-imports`, setzt den No-Type-Import-Standard durch. Diese Regel generiert Fehler oder Warnungen bei Verstößen gegen Typ-Importe.
Eine ESLint-Regel, `@typescript-eslint/consistent-type-imports`, setzt den No-Type-Import-Standard durch. Diese Regel generiert Fehler oder Warnungen bei Verstößen gegen Typ-Importe.
Bitte beachten Sie, dass diese Regel speziell seltene Randfälle behandelt, in denen unbeabsichtigte Typ-Importe auftreten. TypeScript selbst lehnt diese Praxis ab, wie in den [TypeScript 3.8 Release Notes](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-8.html) erwähnt. In den meisten Situationen sollten Typ-Only-Imports nicht benötigt werden.
Um sicherzustellen, dass Ihr Code mit dieser Regel übereinstimmt, achten Sie darauf, Oxlint als Teil Ihres Entwicklungsworkflows auszuführen.
Um sicherzustellen, dass Ihr Code mit dieser Regel übereinstimmt, achten Sie darauf, ESLint als Teil Ihres Entwicklungsworkflows auszuführen.
@@ -96,30 +96,30 @@ my-twenty-app/
.yarnrc.yml
.yarn/
install-state.gz
.oxlintrc.json
eslint.config.mjs
tsconfig.json
README.md
public/ # Public assets folder (images, fonts, etc.)
public/ # Ordner für öffentliche Assets (Bilder, Schriftarten usw.)
src/
├── application-config.ts # Required - main application configuration
├── application-config.ts # Erforderlich - Hauptkonfiguration der Anwendung
├── roles/
│ └── default-role.ts # Default role for logic functions
│ └── default-role.ts # Standardrolle für Logikfunktionen
├── objects/
│ └── example-object.ts # Example custom object definition
│ └── example-object.ts # Beispiel für eine benutzerdefinierte Objektdefinition
├── fields/
│ └── example-field.ts # Example standalone field definition
│ └── example-field.ts # Beispiel für eine eigenständige Felddefinition
├── logic-functions/
│ ├── hello-world.ts # Example logic function
│ ├── pre-install.ts # Pre-install logic function
│ └── post-install.ts # Post-install logic function
│ ├── hello-world.ts # Beispiel für eine Logikfunktion
│ ├── pre-install.ts # Pre-Installations-Logikfunktion
│ └── post-install.ts # Post-Installations-Logikfunktion
├── front-components/
│ └── hello-world.tsx # Example front component
│ └── hello-world.tsx # Beispiel für eine Frontend-Komponente
├── views/
│ └── example-view.ts # Example saved view definition
│ └── example-view.ts # Beispiel für eine gespeicherte View-Definition
├── navigation-menu-items/
│ └── example-navigation-menu-item.ts # Example sidebar navigation link
│ └── example-navigation-menu-item.ts # Beispiel für einen Navigationslink in der Seitenleiste
└── skills/
└── example-skill.ts # Example AI agent skill definition
└── example-skill.ts # Beispiel für eine Skill-Definition eines KI-Agenten
```
Mit `--minimal` werden nur die Kerndateien erstellt (`application-config.ts`, `roles/default-role.ts`, `logic-functions/pre-install.ts` und `logic-functions/post-install.ts`). Mit `--interactive` wählst du aus, welche Beispieldateien enthalten sein sollen.
@@ -130,7 +130,7 @@ Auf hoher Ebene:
* **.gitignore**: Ignoriert übliche Artefakte wie `node_modules`, `.yarn`, `generated/` (typisierter Client), `dist/`, `build/`, Coverage-Ordner, Logdateien und `.env*`-Dateien.
* **yarn.lock**, **.yarnrc.yml**, **.yarn/**: Fixieren und konfigurieren die vom Projekt verwendete Yarn-4-Toolchain.
* **.nvmrc**: Legt die vom Projekt erwartete Node.js-Version fest.
* **.oxlintrc.json** and **tsconfig.json**: Provide linting and TypeScript configuration for your app's TypeScript sources.
* **eslint.config.mjs** und **tsconfig.json**: Stellen Linting und TypeScript-Konfiguration für die TypeScript-Quellen Ihrer App bereit.
* **README.md**: Ein kurzes README im App-Root mit grundlegenden Anweisungen.
* **public/**: Ein Ordner zum Speichern öffentlicher Assets (Bilder, Schriftarten, statische Dateien), die zusammen mit Ihrer Anwendung bereitgestellt werden. Hier abgelegte Dateien werden während der Synchronisierung hochgeladen und sind zur Laufzeit zugänglich.
* **src/**: Der Hauptort, an dem Sie Ihre Anwendung als Code definieren
@@ -53,18 +53,21 @@ Stellen Sie sicher, dass Sie `yarn` im Root-Verzeichnis ausführen und dann `npx
#### Lint beim Speichern funktioniert nicht
Normalerweise sollte dies sofort mit der installierten Oxc-Erweiterung (`oxc.oxc-vscode`) funktionieren. Wenn es nicht funktioniert, versuchen Sie, dies zu Ihren vscode-Einstellungen hinzuzufügen (im Entwicklungscontainer-Bereich):
Normalerweise sollte dies sofort mit der installierten eslint-Erweiterung funktionieren. Wenn es nicht funktioniert, versuchen Sie, dies zu Ihren vscode-Einstellungen hinzuzufügen (im Entwicklungscontainer-Bereich):
```
"editor.codeActionsOnSave": {
"source.fixAll.oxc": "explicit"
"source.fixAll.eslint": "explicit"
}
```
#### Beim Ausführen von `npx nx start` oder `npx nx start twenty-front` wird ein Speicherfehler angezeigt
Kommentieren Sie in `packages/twenty-front/.env` `VITE_DISABLE_TYPESCRIPT_CHECKER=true` und `VITE_DISABLE_ESLINT_CHECKER=true` aus, um Hintergrundprüfungen zu deaktivieren und den RAM-Bedarf zu reduzieren.
**Wenn es nicht funktioniert:**
Führen Sie nur die Services aus, die Sie benötigen, anstatt `npx nx start`. Wenn Sie zum Beispiel am Server arbeiten, führen Sie nur `npx nx worker twenty-server` aus.
**Wenn es nicht funktioniert:**
@@ -93,7 +93,7 @@ Así es como se ve la pila tecnológica ahora.
**Herramientas**
* [Yarn](https://yarnpkg.com/)
* [Oxlint](https://oxc.rs/docs/guide/usage/linter.html)
* [ESLint](https://eslint.org/)
**Desarrollo**
@@ -65,7 +65,7 @@ El proyecto tiene un stack limpio y sencillo, con un código boilerplate mínimo
* [Yarn](https://yarnpkg.com/)
* [Craco](https://craco.js.org/docs/)
* [Oxlint](https://oxc.rs/docs/guide/usage/linter.html)
* [ESLint](https://eslint.org/)
## Arquitectura
@@ -260,7 +260,7 @@ const StyledButton = styled.button`
## Aplicando No-Type Imports
Evita las importaciones de tipo. Para reforzar este estándar, una regla de Oxlint verifica y reporta cualquier importación de tipo. Esto ayuda a mantener la consistencia y la legibilidad en el código TypeScript.
Evita las importaciones de tipo. Para reforzar este estándar, una regla de ESLint verifica y reporta cualquier importación de tipo. Esto ayuda a mantener la consistencia y la legibilidad en el código TypeScript.
```tsx
// ❌ Bad
@@ -281,10 +281,10 @@ import { Meta, StoryObj } from '@storybook/react';
* **Mantenibilidad**: Mejora la mantenibilidad de la base de código porque los desarrolladores pueden identificar y localizar importaciones solo de tipo al revisar o modificar el código.
### regla de Oxlint
### Regla de ESLint
Una regla de Oxlint, `typescript/consistent-type-imports`, impone el estándar de no usar "type" en las importaciones. Esta regla generará errores o advertencias sobre cualquier violación de importación de tipo.
Una regla de ESLint, `@typescript-eslint/consistent-type-imports`, impone el estándar de no usar "type" en las importaciones. Esta regla generará errores o advertencias sobre cualquier violación de importación de tipo.
Por favor, ten en cuenta que esta regla específicamente aborda extraños casos límite donde ocurren importaciones de tipo no intencionadas. TypeScript en sí mismo desaconseja esta práctica, como se menciona en las [notas de lanzamiento de TypeScript 3.8](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-8.html). En la mayoría de situaciones, no deberías necesitar usar importaciones solo de tipo.
Para asegurarte de que tu código cumpla con esta regla, asegúrate de ejecutar Oxlint como parte de tu flujo de trabajo de desarrollo.
Para asegurarte de que tu código cumpla con esta regla, asegúrate de ejecutar ESLint como parte de tu flujo de trabajo de desarrollo.
@@ -87,7 +87,7 @@ my-twenty-app/
.yarnrc.yml
.yarn/
install-state.gz
.oxlintrc.json
eslint.config.mjs
tsconfig.json
README.md
public/ # Carpeta de recursos públicos (imágenes, fuentes, etc.)
@@ -158,7 +158,7 @@ A grandes rasgos:
* **.gitignore**: Ignora artefactos comunes como `node_modules`, `.yarn`, `generated/` (cliente tipado), `dist/`, `build/`, carpetas de cobertura, archivos de registro y archivos `.env*`.
* **yarn.lock**, **.yarnrc.yml**, **.yarn/**: Bloquean y configuran la cadena de herramientas Yarn 4 utilizada por el proyecto.
* **.nvmrc**: Fija la versión de Node.js esperada por el proyecto.
* **.oxlintrc.json** y **tsconfig.json**: Proporcionan linting y configuración de TypeScript para las fuentes de TypeScript de tu aplicación.
* **eslint.config.mjs** y **tsconfig.json**: Proporcionan linting y configuración de TypeScript para las fuentes de TypeScript de tu aplicación.
* **README.md**: Un README breve en la raíz de la aplicación con instrucciones básicas.
* **public/**: Una carpeta para almacenar recursos públicos (imágenes, fuentes, archivos estáticos) que se servirán con tu aplicación. Los archivos colocados aquí se cargan durante la sincronización y son accesibles en tiempo de ejecución.
* **src/**: El lugar principal donde defines tu aplicación como código:

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