Compare commits

..
1 Commits
Author SHA1 Message Date
Charles Bochet 03c44811ae Release 0.31.1 2024-10-12 17:47:02 +02:00
5414 changed files with 83980 additions and 342292 deletions
-99
View File
@@ -1,99 +0,0 @@
# Twenty Development Rules
## General
- Twenty is an open source CRM built with Typescript (React, NestJS)
### Main Packages / folders
- packages/twenty-front: Main Frontend (React)
- packages/twenty-server: Main Backend (NestJS)
- packages/twenty-website: Marketing website + docs (NextJS)
- packages/twenty-ui: UI library (React)
- packages/twenty-shared: Shared utils, constants, types
### Development Stack
- Package Manager: yarn
- Monorepo: nx
- Database: PostgreSQL + TypeORM (core, metadata schemas)
- State Management: Recoil
- Data Management: Apollo / GraphQL
- Cache: redis
- Auth: JWT
- Queue: BullMQ
- Storage: S3/local filesystem
- Testing Backend: Jest, Supertest
- Testing Frontend: Jest, Storybook, MSW
- Testing E2E: Playwright
## Styling
- Use @emotion/styled, never CSS classes/Tailwind
- Prefix styled components with 'Styled'
- Keep styled components at top of file
- Use Theme object for colors/spacing/typography
- Use Theme values instead of px/rem
- Use mq helper for media queries
## TypeScript
- No 'any' type - use proper typing
- Use type over interface
- String literals over enums (except GraphQL)
- Props suffix for component props types
- Use type inference when possible
- Enable strict mode and noImplicitAny
## React
- Only functional components
- Named exports only
- No useEffect, prefer event handlers
- Small focused components
- Proper prop naming (onClick, isActive)
- Destructure props with types
## State Management
- Recoil for global state
- Apollo Client for GraphQL/server state
- Atoms in states/ directory
- Multiple small atoms over prop drilling
- No useRef for state
- Extract data fetching to siblings
- useRecoilValue/useRecoilState appropriately
## File Structure
- One component per file
- Features in modules/
- Hooks in hooks/
- States in states/
- Types in types/
- PascalCase components, camelCase others
## Translation
- Use @lingui/react/macro
- Use <Trans> within components
- Use t`string` elsewhere (from useLingui hook)
- Don't translate metadata (field names, object names, etc)
- Don't translate mocks
## Code Style
- Early returns
- No nested ternaries
- No else-if
- Optional chaining over &&
- Small focused functions
- Clear variable names
- No console.logs in commits
- Few comments, prefer code readability
## GraphQL
- Use gql tag
- Separate operation files
- Proper codegen typing
- Consistent naming (getX, updateX)
- Use fragments
- Use generated types
## Testing
- Test every feature
- React Testing Library
- Test behavior not implementation
- Mock external deps
- Use data-testid
- Follow naming conventions
+4 -18
View File
@@ -1,14 +1,7 @@
module.exports = {
root: true,
extends: ['plugin:prettier/recommended', 'plugin:lingui/recommended'],
plugins: [
'@nx',
'prefer-arrow',
'import',
'unused-imports',
'unicorn',
'lingui',
],
extends: ['plugin:prettier/recommended'],
plugins: ['@nx', 'prefer-arrow', 'import', 'unused-imports', 'unicorn'],
rules: {
'func-style': ['error', 'declaration', { allowArrowFunctions: true }],
'no-console': ['warn', { allow: ['group', 'groupCollapsed', 'groupEnd'] }],
@@ -36,10 +29,6 @@ module.exports = {
sourceTag: 'scope:frontend',
onlyDependOnLibsWithTags: ['scope:shared', 'scope:frontend'],
},
{
sourceTag: 'scope:zapier',
onlyDependOnLibsWithTags: ['scope:shared'],
},
],
},
],
@@ -81,6 +70,7 @@ module.exports = {
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/interface-name-prefix': 'off',
'@typescript-eslint/no-empty-function': 'off',
'@typescript-eslint/no-empty-interface': [
'error',
{
@@ -106,11 +96,7 @@ module.exports = {
rules: {},
},
{
files: [
'*.spec.@(ts|tsx|js|jsx)',
'*.integration-spec.@(ts|tsx|js|jsx)',
'*.test.@(ts|tsx|js|jsx)',
],
files: ['*.spec.@(ts|tsx|js|jsx)', '*.test.@(ts|tsx|js|jsx)'],
env: {
jest: true,
},
@@ -2,11 +2,14 @@ name: Nx Affected CI
inputs:
parallel:
required: false
default: '3'
types: [number]
default: 3
tag:
required: false
types: [string]
tasks:
required: true
types: [string]
runs:
using: "composite"
@@ -1,35 +0,0 @@
name: Restore cache
inputs:
key:
required: true
description: Prefix to the cache key
additional-paths:
required: false
outputs:
cache-primary-key:
description: actions/cache/restore cache-primary-key outputs proxy
value: ${{ steps.restore-cache.outputs.cache-primary-key }}
cache-hit:
description: String bool indicating whether cache has been directly or indirectly hit
value: ${{ steps.restore-cache.outputs.cache-hit == 'true' || steps.restore-cache.outputs.cache-matched-key != '' }}
runs:
using: composite
steps:
- name: Cache primary key builder
id: cache-primary-key-builder
shell: bash
run: |
echo "CACHE_PRIMARY_KEY_PREFIX=${{ inputs.key }}-${{ github.ref_name }}" >> "${GITHUB_OUTPUT}"
- name: Restore cache
uses: actions/cache/restore@v4
id: restore-cache
with:
key: ${{ steps.cache-primary-key-builder.outputs.CACHE_PRIMARY_KEY_PREFIX }}-${{ github.sha }}
restore-keys: ${{ steps.cache-primary-key-builder.outputs.CACHE_PRIMARY_KEY_PREFIX }}-
path: |
.cache
.nx/cache
node_modules/.cache
packages/*/node_modules/.cache
${{ inputs.additional-paths }}
@@ -1,21 +0,0 @@
name: Save cache
inputs:
key:
required: true
description: Primary key to the cache, should be retrieved from `cache-restore` composite action outputs.
additional-paths:
required: false
runs:
using: "composite"
steps:
- name: Save cache
uses: actions/cache/save@v4
with:
key: ${{ inputs.key }}
path: |
.cache
.nx/cache
node_modules/.cache
packages/*/node_modules/.cache
${{ inputs.additional-paths }}
@@ -0,0 +1,31 @@
name: Restore Tasks Cache CI
inputs:
tag:
required: false
types: [string]
tasks:
required: false
types: [string]
default: all
suffix:
required: false
types: [string]
runs:
using: "composite"
steps:
- name: Compute tasks key
id: tasks-key
shell: bash
run: echo "key=${{ inputs.tasks }}" | tr , - >> $GITHUB_OUTPUT
- name: Restore tasks cache
uses: actions/cache@v3
with:
path: |
.cache
.nx/cache
node_modules/.cache
packages/*/node_modules/.cache
key: tasks-cache-${{ github.ref_name }}-${{ inputs.tag }}-${{ steps.tasks-key.outputs.key }}${{ inputs.suffix }}-${{ github.sha }}
restore-keys: |
tasks-cache-${{ github.ref_name }}-${{ inputs.tag }}-${{ steps.tasks-key.outputs.key }}${{ inputs.suffix }}-
@@ -1,43 +1,23 @@
name: Yarn Install
inputs:
node-version:
required: false
default: '18'
runs:
using: "composite"
steps:
- name: Cache primary key builder
id: globals
shell: bash
run: |
echo "ACTION_SHELL=bash" >> "${GITHUB_OUTPUT}"
echo "CACHE_KEY_PREFIX=node_modules-cache-node-${{ inputs.node-version }}-${{ hashFiles('yarn.lock') }}" >> "${GITHUB_OUTPUT}"
echo 'PATH_TO_CACHE<<EOF' >> $GITHUB_OUTPUT
echo "node_modules" >> $GITHUB_OUTPUT
echo "packages/*/node_modules" >> $GITHUB_OUTPUT
echo 'EOF' >> $GITHUB_OUTPUT
- name: Setup Node.js and get yarn cache
uses: actions/setup-node@v4
uses: actions/setup-node@v3
with:
node-version: ${{ inputs.node-version }}
- name: Restore node_modules
id: cache-node-modules
uses: actions/cache/restore@v4
node-version: "18"
cache: yarn
- name: Cache node modules
id: node-modules-cache
uses: actions/cache@v3
with:
key: ${{ steps.globals.outputs.CACHE_KEY_PREFIX }}-${{github.sha}}
restore-keys: ${{ steps.globals.outputs.CACHE_KEY_PREFIX }}-
path: ${{ steps.globals.outputs.PATH_TO_CACHE }}
path: |
node_modules
packages/*/node_modules
key: root-node_modules-${{ hashFiles('yarn.lock') }}
restore-keys: root-node_modules-
- name: Install Dependencies
if: ${{ steps.cache-node-modules.outputs.cache-hit != 'true' && steps.cache-node-modules.outputs.cache-matched-key == '' }}
shell: ${{ steps.globals.outputs.ACTION_SHELL }}
run: |
yarn config set enableHardenedMode true
yarn --immutable --check-cache
- name: Save cache
if: ${{ steps.cache-node-modules.outputs.cache-hit != 'true' && steps.cache-node-modules.outputs.cache-matched-key == '' }}
uses: actions/cache/save@v4
with:
key: ${{ steps.cache-node-modules.outputs.cache-primary-key }}
path: ${{ steps.globals.outputs.PATH_TO_CACHE }}
shell: bash
run: yarn --immutable --check-cache
if: steps.node-modules-cache.outputs.cache-hit != 'true'
-1
View File
@@ -5,7 +5,6 @@ on:
- main
jobs:
deploy-main:
timeout-minutes: 3
runs-on: ubuntu-latest
steps:
- name: Repository Dispatch
-1
View File
@@ -5,7 +5,6 @@ on:
- 'v*'
jobs:
deploy-tag:
timeout-minutes: 3
runs-on: ubuntu-latest
steps:
- name: Repository Dispatch
-27
View File
@@ -1,27 +0,0 @@
name: Changed files reusable workflow
on:
workflow_call:
inputs:
files:
required: true
type: string
outputs:
any_changed:
value: ${{ jobs.changed-files.outputs.any_changed }}
jobs:
changed-files:
timeout-minutes: 5
runs-on: ubuntu-latest
outputs:
any_changed: ${{ steps.changed-files.outputs.any_changed }}
steps:
- name: Fetch custom Github Actions and base branch history
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Check for changed files
id: changed-files
uses: tj-actions/changed-files@v45
with:
files: ${{ inputs.files }}
+6 -22
View File
@@ -3,24 +3,19 @@ on:
push:
branches:
- main
paths:
- 'package.json'
- 'packages/twenty-chrome-extension/**'
pull_request:
paths:
- 'package.json'
- 'packages/twenty-chrome-extension/**'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
changed-files-check:
uses: ./.github/workflows/changed-files.yaml
with:
files: |
package.json
packages/twenty-chrome-extension/**
chrome-extension-build:
needs: changed-files-check
if: needs.changed-files-check.outputs.any_changed == 'true'
timeout-minutes: 15
runs-on: ubuntu-latest
env:
VITE_SERVER_BASE_URL: http://localhost:3000
@@ -31,18 +26,7 @@ jobs:
with:
access_token: ${{ github.token }}
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install dependencies
uses: ./.github/workflows/actions/yarn-install
- name: Chrome Extension / Run build
run: npx nx build twenty-chrome-extension
ci-chrome-extension-status-check:
if: always() && !cancelled()
timeout-minutes: 1
runs-on: ubuntu-latest
needs: [changed-files-check, chrome-extension-build]
steps:
- name: Fail job if any needs failed
if: contains(needs.*.result, 'failure')
run: exit 1
-63
View File
@@ -1,63 +0,0 @@
name: CI Demo check
on:
schedule:
- cron: '30 7,19 * * *'
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
test:
timeout-minutes: 15
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./packages/twenty-e2e-testing
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-node@v4
with:
node-version: lts/*
- name: Install dependencies
uses: ./.github/workflows/actions/yarn-install
- name: Install Playwright Browsers
run: yarn playwright install --with-deps
- name: Run Playwright tests
id: test
run: yarn playwright test --grep "@demo-only"
- name: Upload report after tests
uses: actions/upload-artifact@v4
with:
name: playwright-report
path: playwright-report/
retention-days: 90
- name: Send Discord notification
env:
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
uses: Ilshidur/action-discord@0.3.2
with:
args: 'Demo check ${{ steps.test.outcome }} - check ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}'
- name: Send email if demo is not working
if: steps.test.outcome == 'failure'
uses: dawidd6/action-send-mail@v3.12.0
with:
connection_url: ${{ secrets.MAIL_CONNECTION }}
server_address: smtp.gmail.com
server_port: 465
secure: true
username: ${{ secrets.MAIL_USERNAME }}
subject: 'Demo is not working'
from: 'Github CI Demo check'
to: ${{ secrets.RECIPIENTS }}
body: '<a href="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}">Link</a>'
priority: high
-133
View File
@@ -1,133 +0,0 @@
name: CI E2E Playwright Tests
on:
push:
branches:
- main
pull_request:
types: [opened, synchronize, reopened, labeled]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
changed-files-check:
uses: ./.github/workflows/changed-files.yaml
with:
files: |
packages/**
playwright.config.ts
.github/workflows/ci-e2e.yaml
test:
runs-on: ubuntu-latest
needs: changed-files-check
if: needs.changed-files-check.outputs.any_changed == 'true' && ( github.event_name == 'push' || (github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'run-e2e')))
timeout-minutes: 30
env:
NX_REJECT_UNKNOWN_LOCAL_CACHE: 0
# https://github.com/actions/runner-images/issues/70#issuecomment-589562148
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/workflows/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 twenty-server
- 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
- uses: actions/upload-artifact@v4
if: always()
with:
name: playwright-report
path: packages/twenty-e2e-testing/playwright-report/
retention-days: 30
ci-e2e-status-check:
if: always() && !cancelled()
timeout-minutes: 1
runs-on: ubuntu-latest
needs: [changed-files-check, test]
steps:
- name: Fail job if any needs failed
if: contains(needs.*.result, 'failure')
run: exit 1
+43 -105
View File
@@ -3,30 +3,22 @@ on:
push:
branches:
- main
paths:
- 'package.json'
- 'packages/twenty-front/**'
- 'packages/twenty-ui/**'
pull_request:
paths:
- 'package.json'
- 'packages/twenty-front/**'
- 'packages/twenty-ui/**'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
STORYBOOK_BUILD_CACHE_KEY: storybook-build-depot-ubuntu-24.04-8-runner
jobs:
changed-files-check:
uses: ./.github/workflows/changed-files.yaml
with:
files: |
package.json
packages/twenty-front/**
packages/twenty-ui/**
packages/twenty-shared/**
front-sb-build:
needs: [changed-files-check]
if: needs.changed-files-check.outputs.any_changed == 'true'
timeout-minutes: 30
runs-on: depot-ubuntu-24.04-8
runs-on: ubuntu-latest
env:
REACT_APP_SERVER_BASE_URL: http://localhost:3000
NX_REJECT_UNKNOWN_LOCAL_CACHE: 0
@@ -37,94 +29,64 @@ jobs:
access_token: ${{ github.token }}
- name: Fetch local actions
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install dependencies
uses: ./.github/workflows/actions/yarn-install
- name: Diagnostic disk space issue
run: df -h
- name: Restore storybook build cache
id: restore-storybook-build-cache
uses: ./.github/workflows/actions/restore-cache
- name: Front / Restore Storybook Task Cache
uses: ./.github/workflows/actions/task-cache
with:
key: ${{ env.STORYBOOK_BUILD_CACHE_KEY }}
tag: scope:frontend
tasks: storybook:build
- name: Front / Write .env
run: npx nx reset:env twenty-front
- name: Front / Build storybook
run: npx nx storybook:build twenty-front
- name: Save storybook build cache
uses: ./.github/workflows/actions/save-cache
with:
key: ${{ steps.restore-storybook-build-cache.outputs.cache-primary-key }}
front-sb-test:
timeout-minutes: 30
runs-on: depot-ubuntu-24.04-8
runs-on: ci-8-cores
needs: front-sb-build
strategy:
fail-fast: false
matrix:
shard: [1, 2, 3, 4]
storybook_scope: [modules, pages, performance]
storybook_scope: [pages, modules]
env:
SHARD_COUNTER: 4
REACT_APP_SERVER_BASE_URL: http://localhost:3000
NX_REJECT_UNKNOWN_LOCAL_CACHE: 0
steps:
- name: Fetch local actions
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install dependencies
uses: ./.github/workflows/actions/yarn-install
- name: Install Playwright
run: cd packages/twenty-front && npx playwright install
- name: Restore storybook build cache
uses: ./.github/workflows/actions/restore-cache
- name: Front / Restore Storybook Task Cache
uses: ./.github/workflows/actions/task-cache
with:
key: ${{ env.STORYBOOK_BUILD_CACHE_KEY }}
tag: scope:frontend
tasks: storybook:build
- name: Front / Write .env
run: npx nx reset:env twenty-front
- name: Run storybook tests
run: npx nx storybook:serve-and-test:static twenty-front --configuration=${{ matrix.storybook_scope }} --shard=${{ matrix.shard }}/${{ env.SHARD_COUNTER }} --checkCoverage=false
- name: Rename coverage file
run: mv packages/twenty-front/coverage/storybook/coverage-storybook.json packages/twenty-front/coverage/storybook/coverage-shard-${{matrix.shard}}.json
- name: Upload coverage artifact
uses: actions/upload-artifact@v4
with:
retention-days: 1
name: coverage-artifacts-${{ matrix.storybook_scope }}-${{ github.run_id }}-${{ matrix.shard }}
path: packages/twenty-front/coverage/storybook/coverage-shard-${{matrix.shard}}.json
merge-reports-and-check-coverage:
timeout-minutes: 30
runs-on: depot-ubuntu-24.04-8
needs: front-sb-test
run: npx nx storybook:serve-and-test:static twenty-front --configuration=${{ matrix.storybook_scope }}
front-sb-test-performance:
runs-on: ci-8-cores
env:
PATH_TO_COVERAGE: packages/twenty-front/coverage/storybook
strategy:
matrix:
storybook_scope: [modules, pages, performance]
REACT_APP_SERVER_BASE_URL: http://localhost:3000
NX_REJECT_UNKNOWN_LOCAL_CACHE: 0
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Fetch local actions
uses: actions/checkout@v4
- name: Install dependencies
uses: ./.github/workflows/actions/yarn-install
- uses: actions/download-artifact@v4
with:
pattern: coverage-artifacts-${{ matrix.storybook_scope }}-${{ github.run_id }}-*
merge-multiple: true
path: coverage-artifacts
- name: Merge coverage reports
run: |
mkdir -p ${{ env.PATH_TO_COVERAGE }}
npx nyc merge coverage-artifacts ${{ env.PATH_TO_COVERAGE }}/coverage-storybook.json
- name: Checking coverage
run: npx nx storybook:coverage twenty-front --checkCoverage=true --configuration=${{ matrix.storybook_scope }}
- name: Install Playwright
run: cd packages/twenty-front && npx playwright install
- name: Front / Write .env
run: npx nx reset:env twenty-front
- name: Run storybook tests
run: npx nx storybook:serve-and-test:static:performance twenty-front
front-chromatic-deployment:
timeout-minutes: 30
if: contains(github.event.pull_request.labels.*.name, 'run-chromatic') || github.event_name == 'push'
needs: front-sb-build
runs-on: depot-ubuntu-24.04-8
runs-on: ubuntu-latest
env:
REACT_APP_SERVER_BASE_URL: http://127.0.0.1:3000
CHROMATIC_PROJECT_TOKEN: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
@@ -135,10 +97,11 @@ jobs:
fetch-depth: 0
- name: Install dependencies
uses: ./.github/workflows/actions/yarn-install
- name: Restore storybook build cache
uses: ./.github/workflows/actions/restore-cache
- name: Front / Restore Storybook Task Cache
uses: ./.github/workflows/actions/task-cache
with:
key: ${{ env.STORYBOOK_BUILD_CACHE_KEY }}
tag: scope:frontend
tasks: storybook:build
- name: Front / Write .env
run: |
cd packages/twenty-front
@@ -147,13 +110,9 @@ jobs:
- name: Publish to Chromatic
run: npx nx run twenty-front:chromatic:ci
front-task:
needs: changed-files-check
if: needs.changed-files-check.outputs.any_changed == 'true'
timeout-minutes: 30
runs-on: depot-ubuntu-24.04-8
runs-on: ubuntu-latest
env:
NX_REJECT_UNKNOWN_LOCAL_CACHE: 0
TASK_CACHE_KEY: front-task-${{ matrix.task }}
strategy:
matrix:
task: [lint, typecheck, test]
@@ -168,11 +127,11 @@ jobs:
fetch-depth: 0
- name: Install dependencies
uses: ./.github/workflows/actions/yarn-install
- name: Restore ${{ matrix.task }} cache
id: restore-task-cache
uses: ./.github/workflows/actions/restore-cache
- name: Front / Restore ${{ matrix.task }} task cache
uses: ./.github/workflows/actions/task-cache
with:
key: ${{ env.TASK_CACHE_KEY }}
tag: scope:frontend
tasks: ${{ matrix.task }}
- name: Reset .env
uses: ./.github/workflows/actions/nx-affected
with:
@@ -182,25 +141,4 @@ jobs:
uses: ./.github/workflows/actions/nx-affected
with:
tag: scope:frontend
tasks: ${{ matrix.task }}
- name: Save ${{ matrix.task }} cache
uses: ./.github/workflows/actions/save-cache
with:
key: ${{ steps.restore-task-cache.outputs.cache-primary-key }}
ci-front-status-check:
if: always() && !cancelled()
timeout-minutes: 1
runs-on: depot-ubuntu-24.04-8
needs:
[
changed-files-check,
front-task,
front-chromatic-deployment,
merge-reports-and-check-coverage,
front-sb-test,
front-sb-build,
]
steps:
- name: Fail job if any needs failed
if: contains(needs.*.result, 'failure')
run: exit 1
tasks: ${{ matrix.task }}
-1
View File
@@ -15,7 +15,6 @@ on:
jobs:
create_pr:
timeout-minutes: 10
runs-on: ubuntu-latest
steps:
- name: Checkout
-1
View File
@@ -6,7 +6,6 @@ on:
jobs:
tag_and_release:
timeout-minutes: 10
runs-on: ubuntu-latest
if: github.event.pull_request.merged == true && contains(github.event.pull_request.labels.*.name, 'release')
steps:
+29 -131
View File
@@ -3,47 +3,32 @@ on:
push:
branches:
- main
paths:
- 'package.json'
- 'packages/twenty-server/**'
- 'packages/twenty-emails/**'
pull_request:
paths:
- 'package.json'
- 'packages/twenty-server/**'
- 'packages/twenty-emails/**'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
SERVER_SETUP_CACHE_KEY: server-setup
jobs:
changed-files-check:
uses: ./.github/workflows/changed-files.yaml
with:
files: |
package.json
packages/twenty-server/**
packages/twenty-emails/**
packages/twenty-shared/**
server-setup:
needs: changed-files-check
if: needs.changed-files-check.outputs.any_changed == 'true'
timeout-minutes: 30
runs-on: depot-ubuntu-24.04-8
runs-on: ubuntu-latest
env:
NX_REJECT_UNKNOWN_LOCAL_CACHE: 0
services:
postgres:
image: twentycrm/twenty-postgres-spilo
image: twentycrm/twenty-postgres
env:
PGUSER_SUPERUSER: postgres
PGPASSWORD_SUPERUSER: postgres
ALLOW_NOSSL: 'true'
SPILO_PROVIDER: 'local'
POSTGRES_PASSWORD: postgres
POSTGRES_USER: postgres
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
redis:
image: redis
ports:
@@ -55,13 +40,10 @@ jobs:
fetch-depth: 0
- name: Install dependencies
uses: ./.github/workflows/actions/yarn-install
- name: Restore server setup
id: restore-server-setup-cache
uses: ./.github/workflows/actions/restore-cache
- name: Server / Restore Task Cache
uses: ./.github/workflows/actions/task-cache
with:
key: ${{ env.SERVER_SETUP_CACHE_KEY }}
- name: Build twenty-shared
run: npx nx build twenty-shared
tag: scope:backend
- name: Server / Run lint & typecheck
uses: ./.github/workflows/actions/nx-affected
with:
@@ -71,61 +53,11 @@ jobs:
run: npx nx build twenty-server
- name: Server / Write .env
run: npx nx reset:env twenty-server
- name: Server / Create DB
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:init:prod
npx nx run twenty-server:database:migrate:prod
- name: Worker / Run
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 / Check for Pending Migrations
run: |
METADATA_MIGRATION_OUTPUT=$(npx nx run twenty-server:typeorm migration:generate metadata-migration-check -d src/database/typeorm/metadata/metadata.datasource.ts || true)
CORE_MIGRATION_OUTPUT=$(npx nx run twenty-server:typeorm migration:generate core-migration-check -d src/database/typeorm/core/core.datasource.ts || true)
METADATA_MIGRATION_FILE=$(ls packages/twenty-server/*metadata-migration-check.ts 2>/dev/null || echo "")
CORE_MIGRATION_FILE=$(ls packages/twenty-server/*core-migration-check.ts 2>/dev/null || echo "")
if [ -n "$METADATA_MIGRATION_FILE" ] || [ -n "$CORE_MIGRATION_FILE" ]; then
echo "::error::Unexpected migration files were generated. Please create a proper migration manually."
echo "$METADATA_MIGRATION_OUTPUT"
echo "$CORE_MIGRATION_OUTPUT"
rm -f packages/twenty-server/*metadata-migration-check.ts packages/twenty-server/*core-migration-check.ts
exit 1
fi
- name: GraphQL / Check for Pending Generation
if: steps.changed-files.outputs.any_changed == 'true'
run: |
GRAPHQL_GENERATE_OUTPUT=$(npx nx run twenty-front:graphql:generate || true)
GRAPHQL_METADATA_OUTPUT=$(npx nx run twenty-front:graphql:generate --configuration=metadata || true)
if [[ $GRAPHQL_GENERATE_OUTPUT == *"No changes detected"* && $GRAPHQL_METADATA_OUTPUT == *"No changes detected"* ]]; then
echo "GraphQL generation check passed."
else
echo "::error::Unexpected GraphQL changes detected. Please run the required commands and commit the changes."
echo "$GRAPHQL_GENERATE_OUTPUT"
echo "$GRAPHQL_METADATA_OUTPUT"
exit 1
fi
- name: Save server setup
uses: ./.github/workflows/actions/save-cache
with:
key: ${{ steps.restore-server-setup-cache.outputs.cache-primary-key }}
run: npx nx run twenty-server:worker:ci
server-test:
timeout-minutes: 30
runs-on: depot-ubuntu-24.04-8
runs-on: ubuntu-latest
needs: server-setup
env:
NX_REJECT_UNKNOWN_LOCAL_CACHE: 0
@@ -136,35 +68,27 @@ jobs:
fetch-depth: 0
- name: Install dependencies
uses: ./.github/workflows/actions/yarn-install
- name: Restore server setup
uses: ./.github/workflows/actions/restore-cache
- name: Server / Restore Task Cache
uses: ./.github/workflows/actions/task-cache
with:
key: ${{ env.SERVER_SETUP_CACHE_KEY }}
- name: Server / Run Tests
tag: scope:backend
- name: Server / Run Tests
uses: ./.github/workflows/actions/nx-affected
with:
tag: scope:backend
tasks: test
server-integration-test:
timeout-minutes: 30
runs-on: depot-ubuntu-24.04-8
runs-on: ubuntu-latest
needs: server-setup
services:
postgres:
image: twentycrm/twenty-postgres-spilo
image: twentycrm/twenty-postgres
env:
PGUSER_SUPERUSER: postgres
PGPASSWORD_SUPERUSER: postgres
ALLOW_NOSSL: 'true'
SPILO_PROVIDER: 'local'
POSTGRES_PASSWORD: postgres
POSTGRES_USER: postgres
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
redis:
image: redis
ports:
@@ -178,44 +102,18 @@ jobs:
fetch-depth: 0
- name: Install dependencies
uses: ./.github/workflows/actions/yarn-install
- name: Update .env.test for billing
run: |
echo "IS_BILLING_ENABLED=true" >> .env.test
echo "BILLING_STRIPE_API_KEY=test-api-key" >> .env.test
echo "BILLING_STRIPE_BASE_PLAN_PRODUCT_ID=test-base-plan-product-id" >> .env.test
echo "BILLING_STRIPE_WEBHOOK_SECRET=test-webhook-secret" >> .env.test
- name: Restore server setup
uses: ./.github/workflows/actions/restore-cache
- name: Server / Restore Task Cache
uses: ./.github/workflows/actions/task-cache
with:
key: ${{ env.SERVER_SETUP_CACHE_KEY }}
- name: Server / Build
run: npx nx build twenty-server
- name: Build dependencies
run: |
npx nx build twenty-shared
npx nx build twenty-emails
- name: Server / Create Test DB
run: |
PGPASSWORD=postgres psql -h localhost -p 5432 -U postgres -d postgres -c 'CREATE DATABASE "test";'
PG_DATABASE_URL=postgres://postgres:postgres@localhost:5432/test npx nx run twenty-server:database:init:prod
PG_DATABASE_URL=postgres://postgres:postgres@localhost:5432/test npx nx run twenty-server:database:migrate:prod
tag: scope:backend
- name: Server / Run Integration Tests
uses: ./.github/workflows/actions/nx-affected
with:
tag: scope:backend
tasks: 'test:integration --configuration=with-db-reset'
tasks: "test:integration"
- name: Server / Upload reset-logs file
if: always()
uses: actions/upload-artifact@v4
with:
name: reset-logs
path: reset-logs.log
ci-server-status-check:
if: always() && !cancelled()
timeout-minutes: 1
runs-on: depot-ubuntu-24.04-8
needs: [changed-files-check, server-setup, server-test, server-integration-test]
steps:
- name: Fail job if any needs failed
if: contains(needs.*.result, 'failure')
run: exit 1
-53
View File
@@ -1,53 +0,0 @@
name: CI Shared
on:
push:
branches:
- main
pull_request:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
changed-files-check:
uses: ./.github/workflows/changed-files.yaml
with:
files: |
packages/twenty-shared/**
shared-test:
needs: changed-files-check
if: needs.changed-files-check.outputs.any_changed == 'true'
timeout-minutes: 30
runs-on: ubuntu-latest
env:
NX_REJECT_UNKNOWN_LOCAL_CACHE: 0
strategy:
matrix:
task: [lint, typecheck, test]
steps:
- name: Cancel Previous Runs
uses: styfle/cancel-workflow-action@0.11.0
with:
access_token: ${{ github.token }}
- name: Fetch custom Github Actions and base branch history
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install dependencies
uses: ./.github/workflows/actions/yarn-install
- name: Run ${{ matrix.task }} task
uses: ./.github/workflows/actions/nx-affected
with:
tag: scope:frontend
tasks: ${{ matrix.task }}
ci-shared-status-check:
if: always() && !cancelled()
timeout-minutes: 1
runs-on: ubuntu-latest
needs: [changed-files-check, shared-test]
steps:
- name: Fail job if any needs failed
if: contains(needs.*.result, 'failure')
run: exit 1
+43 -81
View File
@@ -1,97 +1,59 @@
name: 'Test Docker Compose'
on:
pull_request:
paths:
- 'packages/twenty-docker/**'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
changed-files-check:
uses: ./.github/workflows/changed-files.yaml
with:
files: |
packages/twenty-docker/**
docker-compose.yml
test:
needs: changed-files-check
if: needs.changed-files-check.outputs.any_changed == 'true'
timeout-minutes: 30
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Run compose
run: |
echo "Patching docker-compose.yml..."
# change image to localbuild using yq
yq eval 'del(.services.server.image)' -i docker-compose.yml
yq eval '.services.server.build.context = "../../"' -i docker-compose.yml
yq eval '.services.server.build.dockerfile = "./packages/twenty-docker/twenty/Dockerfile"' -i docker-compose.yml
yq eval '.services.server.restart = "no"' -i docker-compose.yml
- name: Checkout
uses: actions/checkout@v2
yq eval 'del(.services.db.image)' -i docker-compose.yml
yq eval '.services.db.build.context = "../../"' -i docker-compose.yml
yq eval '.services.db.build.dockerfile = "./packages/twenty-docker/twenty-postgres-spilo/Dockerfile"' -i docker-compose.yml
- name: Run compose
run: |
echo "Patching docker-compose.yml..."
# change image to localbuild using yq
yq eval 'del(.services.server.image)' -i docker-compose.yml
yq eval '.services.server.build.context = "../../"' -i docker-compose.yml
yq eval '.services.server.build.dockerfile = "./packages/twenty-docker/twenty/Dockerfile"' -i docker-compose.yml
yq eval '.services.server.restart = "no"' -i docker-compose.yml
echo "Setting up .env file..."
cp .env.example .env
echo "Generating secrets..."
echo "# === Randomly generated secrets ===" >>.env
echo "APP_SECRET=$(openssl rand -base64 32)" >>.env
echo "PGPASSWORD_SUPERUSER=$(openssl rand -hex 16)" >>.env
yq eval 'del(.services.db.image)' -i docker-compose.yml
yq eval '.services.db.build.context = "../../"' -i docker-compose.yml
yq eval '.services.db.build.dockerfile = "./packages/twenty-docker/twenty-postgres/Dockerfile"' -i docker-compose.yml
echo "Docker compose up..."
docker compose up -d || {
echo "Docker compose failed to start"
docker compose logs
exit 1
}
docker compose logs db server -f &
pid=$!
echo "Setting up .env file..."
cp .env.example .env
echo "Generating secrets..."
echo "# === Randomly generated secrets ===" >>.env
echo "ACCESS_TOKEN_SECRET=$(openssl rand -base64 32)" >>.env
echo "LOGIN_TOKEN_SECRET=$(openssl rand -base64 32)" >>.env
echo "REFRESH_TOKEN_SECRET=$(openssl rand -base64 32)" >>.env
echo "FILE_TOKEN_SECRET=$(openssl rand -base64 32)" >>.env
echo "POSTGRES_ADMIN_PASSWORD=$(openssl rand -base64 32)" >>.env
echo "Waiting for database to start..."
count=0
while [ ! $(docker inspect --format='{{.State.Health.Status}}' twenty-db-1) = "healthy" ]; do
sleep 1;
count=$((count+1));
if [ $(docker inspect --format='{{.State.Status}}' twenty-db-1) = "exited" ]; then
echo "Database exited"
docker compose logs db
exit 1
fi
if [ $count -gt 300 ]; then
echo "Failed to start database after 5 minutes"
docker compose logs db
exit 1
fi
echo "Still waiting for database... (${count}/60)"
done
echo "Starting server..."
docker compose up -d
docker compose logs db server -f &
pid=$!
echo "Waiting for server to start..."
count=0
while [ ! $(docker inspect --format='{{.State.Health.Status}}' twenty-server-1) = "healthy" ]; do
sleep 1;
count=$((count+1));
if [ $(docker inspect --format='{{.State.Status}}' twenty-server-1) = "exited" ]; then
echo "Server exited"
docker compose logs server
exit 1
fi
if [ $count -gt 300 ]; then
echo "Failed to start server after 5 minutes"
docker compose logs server
exit 1
fi
echo "Still waiting for server... (${count}/300s)"
done
working-directory: ./packages/twenty-docker/
ci-test-docker-compose-status-check:
if: always() && !cancelled()
timeout-minutes: 1
runs-on: ubuntu-latest
needs: [changed-files-check, test]
steps:
- name: Fail job if any needs failed
if: contains(needs.*.result, 'failure')
run: exit 1
echo "Waiting for server to start..."
count=0
while [ ! $(docker inspect --format='{{.State.Health.Status}}' twenty-server-1) = "healthy" ]; do
sleep 1;
count=$((count+1));
if [ $(docker inspect --format='{{.State.Status}}' twenty-server-1) = "exited" ]; then
echo "Server exited"
exit 1
fi
if [ $count -gt 300 ]; then
echo "Failed to start server"
exit 1
fi
done
working-directory: ./packages/twenty-docker/
-26
View File
@@ -1,26 +0,0 @@
name: CI Tinybird
on:
push:
branches:
- main
paths:
- 'package.json'
- 'packages/twenty-tinybird/**'
pull_request:
paths:
- 'package.json'
- 'packages/twenty-tinybird/**'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
ci:
uses: tinybirdco/ci/.github/workflows/ci.yml@main
with:
data_project_dir: packages/twenty-tinybird
secrets:
tb_admin_token: ${{ secrets.TB_ADMIN_TOKEN }}
tb_host: https://api.eu-central-1.aws.tinybird.co
+1 -5
View File
@@ -15,13 +15,10 @@ permissions:
statuses: write
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
# We don't cancel in-progress because this workflow is triggered on
# pull_request_target, which means the ref can be the same accross two PRs.
# cancel-in-progress: true
cancel-in-progress: true
jobs:
danger-js:
timeout-minutes: 3
runs-on: ubuntu-latest
if: github.event.action != 'closed'
steps:
@@ -34,7 +31,6 @@ jobs:
DANGER_GITHUB_API_TOKEN: ${{ github.token }}
congratulate:
timeout-minutes: 3
runs-on: ubuntu-latest
if: github.event.action == 'closed' && github.event.pull_request.merged == true
steps:
+12 -40
View File
@@ -3,65 +3,37 @@ on:
push:
branches:
- main
paths:
- 'package.json'
- 'packages/twenty-website/**'
pull_request:
paths:
- 'package.json'
- 'packages/twenty-website/**'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
changed-files-check:
uses: ./.github/workflows/changed-files.yaml
with:
files: |
package.json
packages/twenty-website/**
website-build:
needs: changed-files-check
if: needs.changed-files-check.outputs.any_changed == 'true'
timeout-minutes: 3
runs-on: ubuntu-latest
services:
postgres:
image: twentycrm/twenty-postgres-spilo
image: twentycrm/twenty-postgres
env:
PGUSER_SUPERUSER: postgres
PGPASSWORD_SUPERUSER: postgres
ALLOW_NOSSL: 'true'
SPILO_PROVIDER: 'local'
POSTGRES_PASSWORD: twenty
POSTGRES_USER: twenty
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install dependencies
uses: ./.github/workflows/actions/yarn-install
- name: Server / Create DB
run: PGPASSWORD=postgres psql -h localhost -p 5432 -U postgres -d postgres -c 'CREATE DATABASE "default";'
- name: Website / Run migrations
run: npx nx database:migrate twenty-website
run: npx nx database:migrate twenty-website
env:
DATABASE_PG_URL: postgres://postgres:postgres@localhost:5432/default
DATABASE_PG_URL: postgres://twenty:twenty@localhost:5432/default
- name: Website / Build Website
run: npx nx build twenty-website
env:
DATABASE_PG_URL: postgres://postgres:postgres@localhost:5432/default
ci-website-status-check:
if: always() && !cancelled()
timeout-minutes: 1
runs-on: ubuntu-latest
needs: [changed-files-check, website-build]
steps:
- name: Fail job if any needs failed
if: contains(needs.*.result, 'failure')
run: exit 1
DATABASE_PG_URL: postgres://twenty:twenty@localhost:5432/default
-128
View File
@@ -1,128 +0,0 @@
# Pull down translations from Crowdin every two hours or when triggered manually.
# When force_pull input is true, translations will be pulled regardless of compilation status.
name: 'Pull translations'
on:
schedule:
- cron: '0 */2 * * *' # Every two hours.
workflow_dispatch:
inputs:
force_pull:
description: 'Force pull translations regardless of compilation status'
required: false
type: boolean
default: false
workflow_call:
inputs:
force_pull:
description: 'Force pull translations regardless of compilation status'
required: false
type: boolean
default: false
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
pull_translations:
name: Pull translations
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- name: Checkout
uses: actions/checkout@v4
with:
token: ${{ github.token }}
ref: ${{ github.head_ref || github.ref_name }}
- name: Setup i18n branch
run: |
git fetch origin i18n || true
git checkout -B i18n origin/i18n || git checkout -b i18n
- name: Install dependencies
uses: ./.github/workflows/actions/yarn-install
- name: Build twenty-shared
run: npx nx build twenty-shared
# Strict mode fails if there are missing translations.
- name: Compile translations
id: compile_translations
run: |
npx nx run twenty-server:lingui:compile --strict
npx nx run twenty-emails:lingui:compile --strict
npx nx run twenty-front:lingui:compile --strict
continue-on-error: true
- name: Stash any changes before pulling translations
run: |
git config --global user.name 'github-actions'
git config --global user.email 'github-actions@twenty.com'
git add .
git stash
- name: Pull translations from Crowdin
if: inputs.force_pull || steps.compile_translations.outcome == 'failure'
uses: crowdin/github-action@v2
with:
upload_sources: false
upload_translations: false
download_translations: true
export_only_approved: false
localization_branch_name: i18n
commit_message: 'i18n - translations'
pull_request_title: 'i18n - translations'
base_url: 'https://twenty.api.crowdin.com'
auto_approve_imported: false
import_eq_suggestions: false
download_sources: false
push_sources: false
skip_untranslated_strings: false
skip_untranslated_files: false
push_translations: true
create_pull_request: true
skip_ref_checkout: true
dryrun_action: false
github_base_url: 'github.com'
github_user_name: 'Crowdin Bot'
github_user_email: 'support+bot@crowdin.com'
env:
GITHUB_TOKEN: ${{ github.token }}
CROWDIN_PROJECT_ID: '1'
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
- name: Compile translations
if: inputs.force_pull || steps.compile_translations.outcome == 'failure'
run: |
npx nx run twenty-server:lingui:compile
npx nx run twenty-emails:lingui:compile
npx nx run twenty-front:lingui:compile
- name: Check and commit compiled files
id: check_changes
run: |
git config --global user.name 'github-actions'
git config --global user.email 'github-actions@twenty.com'
git add .
if ! git diff --staged --quiet --exit-code; then
git commit -m "chore: compile translations [skip ci]"
echo "changes_detected=true" >> $GITHUB_OUTPUT
else
echo "changes_detected=false" >> $GITHUB_OUTPUT
fi
- name: Push changes
if: steps.check_changes.outputs.changes_detected == 'true'
run: git push origin HEAD:i18n
- name: Create pull request
if: steps.check_changes.outputs.changes_detected == 'true'
run: gh pr create -B main -H i18n --title 'i18n - translations' --body 'Created by Github action' || true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-86
View File
@@ -1,86 +0,0 @@
name: 'Extract translations when there is a push to main, and upload them to Crowdin'
on:
workflow_dispatch:
workflow_call:
push:
branches: ['main']
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
extract_translations:
name: Extract and upload translations
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout
uses: actions/checkout@v4
with:
token: ${{ github.token }}
ref: main
- name: Setup i18n branch
run: |
git fetch origin i18n || true
git checkout -B i18n origin/i18n || git checkout -b i18n
- name: Install dependencies
uses: ./.github/workflows/actions/yarn-install
- name: Build dependencies
run: npx nx build twenty-shared
- name: Extract translations
run: |
npx nx run twenty-server:lingui:extract
npx nx run twenty-emails:lingui:extract
npx nx run twenty-front:lingui:extract
- name: Compile translations
run: |
npx nx run twenty-server:lingui:compile
npx nx run twenty-emails:lingui:compile
npx nx run twenty-front:lingui:compile
- name: Check and commit any files created
id: check_changes
run: |
git config --global user.name 'github-actions'
git config --global user.email 'github-actions@twenty.com'
git add .
if ! git diff --staged --quiet --exit-code; then
git commit -m "chore: extract translations"
echo "changes_detected=true" >> $GITHUB_OUTPUT
else
echo "changes_detected=false" >> $GITHUB_OUTPUT
fi
- name: Push changes and create remote branch if needed
if: steps.check_changes.outputs.changes_detected == 'true'
run: git push origin HEAD:i18n
- name: Upload missing translations
if: steps.check_changes.outputs.changes_detected == 'true'
uses: crowdin/github-action@v2
with:
upload_sources: true
upload_translations: true
download_translations: false
localization_branch_name: i18n
base_url: 'https://twenty.api.crowdin.com'
env:
# A numeric ID, found at https://crowdin.com/project/<projectName>/tools/api
CROWDIN_PROJECT_ID: 1
# Visit https://crowdin.com/settings#api-key to create this token
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
- name: Create a pull request
if: steps.check_changes.outputs.changes_detected == 'true'
run: gh pr create -B main -H i18n --title 'i18n - translations' --body 'Created by Github action' || true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+27
View File
@@ -0,0 +1,27 @@
name: Playwright Tests
on:
push:
branches: [ main, master ]
pull_request:
branches: [ main, master ]
jobs:
test:
timeout-minutes: 60
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: lts/*
- name: Install dependencies
run: npm install -g yarn && yarn
- name: Install Playwright Browsers
run: yarn playwright install --with-deps
- name: Run Playwright tests
run: yarn test:e2e companies
- uses: actions/upload-artifact@v4
if: always()
with:
name: playwright-report
path: playwright-report/
retention-days: 30
-14
View File
@@ -18,7 +18,6 @@
!.yarn/sdks
!.yarn/versions
.vercel
.swc
**/**/logs/**
@@ -30,16 +29,3 @@ storybook-static
.nyc_output
test-results/
dump.rdb
.tinyb
.notes
/data/
/.devenv/
/.direnv/
/.pre-commit-config.yaml
/.envrc
/devenv.nix
/flake.lock
/flake.nix
.crowdin.yml
+1 -2
View File
@@ -14,7 +14,6 @@
"styled-components.vscode-styled-components",
"unifiedjs.vscode-mdx",
"xyc.vscode-mdx-preview",
"yoavbls.pretty-ts-errors",
"ms-playwright.playwright"
"yoavbls.pretty-ts-errors"
]
}
+7 -37
View File
@@ -6,11 +6,12 @@
"name": "twenty-server - start debug",
"type": "node",
"request": "launch",
"runtimeExecutable": "npx",
"runtimeExecutable": "yarn",
"runtimeVersion": "18",
"runtimeArgs": [
"nx",
"run",
"twenty-server:start"
"twenty-server:start",
],
"outputCapture": "std",
"internalConsoleOptions": "openOnSessionStart",
@@ -21,11 +22,12 @@
"name": "twenty-server - worker debug",
"type": "node",
"request": "launch",
"runtimeExecutable": "npx",
"runtimeExecutable": "yarn",
"runtimeVersion": "18",
"runtimeArgs": [
"nx",
"run",
"twenty-server:worker"
"twenty-server:worker",
],
"outputCapture": "std",
"internalConsoleOptions": "openOnSessionStart",
@@ -43,44 +45,12 @@
"run",
"twenty-server:command",
"my-command",
"--my-parameter value"
"--my-parameter value",
],
"outputCapture": "std",
"internalConsoleOptions": "openOnSessionStart",
"console": "internalConsole",
"cwd": "${workspaceFolder}/packages/twenty-server/"
},
{
"name": "Playwright Test current file",
"type": "node",
"request": "launch",
"runtimeExecutable": "npx",
"runtimeArgs": ["nx", "test", "twenty-e2e-testing", "${file}"],
"console": "integratedTerminal",
"cwd": "${workspaceFolder}",
"internalConsoleOptions": "neverOpen",
"envFile": "${workspaceFolder}/packages/twenty-e2e-testing/.env"
},
{
"type": "node",
"request": "launch",
"name": "twenty-server - debug integration test file (to launch with test file open)",
"runtimeExecutable": "npx",
"runtimeArgs": [
"nx",
"run",
"twenty-server:jest",
"--",
"--config",
"./jest-integration.config.ts",
"${relativeFile}"
],
"cwd": "${workspaceFolder}/packages/twenty-server",
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen",
"env": {
"NODE_ENV": "test"
},
}
]
}
+1 -6
View File
@@ -45,10 +45,5 @@
"search.exclude": {
"**/.yarn": true,
},
"eslint.debug": true,
"files.associations": {
".cursorrules": "markdown"
},
"jestrunner.codeLensSelector": "**/*.{test,spec,integration-spec}.{js,jsx,ts,tsx}"
}
"eslint.debug": true
}
+2 -6
View File
@@ -25,8 +25,8 @@
"path": "../packages/twenty-emails"
},
{
"name": "packages/twenty-shared",
"path": "../packages/twenty-shared"
"name": "packages/twenty-postgres",
"path": "../packages/twenty-postgres"
},
{
"name": "packages/twenty-server",
@@ -44,10 +44,6 @@
"name": "tools/eslint-rules",
"path": "../tools/eslint-rules"
},
{
"name": "packages/twenty-e2e-testing",
"path": "../packages/twenty-e2e-testing"
}
],
"settings": {
"editor.formatOnSave": false,
-2
View File
@@ -1,5 +1,3 @@
enableHardenedMode: true
enableInlineHunks: true
nodeLinker: node-modules
-49
View File
@@ -1,8 +1,3 @@
This project is mostly licensed under the GNU General Public License (GPL) as described below. However, certain files within this project are licensed under a different commercial license. These files are clearly marked with the following comment at the top of the file: /* @license Enterprise */
Files with this comment are not licensed under the aGPL v3, but instead are subject to the commercial license terms defined later in this file.
GNU AFFERO GENERAL PUBLIC LICENSE
Version 3, 19 November 2007
@@ -664,47 +659,3 @@ specific requirements.
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU AGPL, see
<https://www.gnu.org/licenses/>.
------------------------------------
The Twenty.com Commercial License (the “Commercial License”)
Copyright (c) 2023-present Twenty.com, PBC
With regard to Twenty's Software:
This part of the software and associated documentation files (the "Software") may only be
used in production, if you (and any entity that you represent) have agreed to,
and are in compliance with, the Terms available
at https://twenty.com/legal/terms, or other agreements governing
the use of the Software, as mutually agreed by you and Twenty.com, PBC ("Twenty"),
and otherwise have a valid Twenty Enterprise Edition subscription
for the correct number of hosts and seats as defined in the Commercial Terms.
Subject to the foregoing sentence,
you are free to modify this Software and publish patches to the Software. You agree
that Twenty and/or its licensors (as applicable) retain all right, title and interest in
and to all such modifications and/or patches, and all such modifications and/or
patches may only be used, copied, modified, displayed, distributed, or otherwise
exploited with a valid Commercial Subscription for the correct number of hosts and seats.
Notwithstanding the foregoing, you may copy and modify the Software for development
and testing purposes, without requiring a subscription. You agree that Twenty.Com and/or
its licensors (as applicable) retain all right, title and interest in and to all such
modifications. You are not granted any other rights beyond what is expressly stated herein.
Subject to the foregoing, it is forbidden to copy, merge, publish, distribute, sublicense,
and/or sell the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
For all third party components incorporated into the Twenty Software, those
components are licensed under the original license provided by the owner of the
applicable component.
+25 -19
View File
@@ -1,20 +1,26 @@
postgres-on-docker:
docker run -d \
--name twenty_pg \
-e PGUSER_SUPERUSER=postgres \
-e PGPASSWORD_SUPERUSER=postgres \
-e ALLOW_NOSSL=true \
-v twenty_db_data:/home/postgres/pgdata \
-p 5432:5432 \
twentycrm/twenty-postgres-spilo:latest
@echo "Waiting for PostgreSQL to be ready..."
@until docker exec twenty_pg psql -U postgres -d postgres \
-c 'SELECT pg_is_in_recovery();' 2>/dev/null | grep -q 'f'; do \
sleep 1; \
done
docker exec twenty_pg psql -U postgres -d postgres \
-c "CREATE DATABASE \"default\" WITH OWNER postgres;" \
-c "CREATE DATABASE \"test\" WITH OWNER postgres;"
docker-dev-build:
make -C packages/twenty-docker dev-build
redis-on-docker:
docker run -d --name twenty_redis -p 6379:6379 redis/redis-stack-server:latest
docker-dev-up:
make -C packages/twenty-docker dev-up
docker-dev-start:
make -C packages/twenty-docker dev-start
docker-dev-stop:
make -C packages/twenty-docker dev-stop
docker-dev-sh:
make -C packages/twenty-docker dev-sh
postgres-on-docker:
make -C packages/twenty-postgres provision-on-docker
postgres-on-macos-arm:
make -C packages/twenty-postgres provision-on-macos-arm
postgres-on-macos-intel:
make -C packages/twenty-postgres provision-on-macos-intel
postgres-on-linux:
make -C packages/twenty-postgres provision-on-linux
+42 -23
View File
@@ -1,3 +1,12 @@
<p align="center">
<a href="https://oss.gg/">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://github.com/twentyhq/twenty/blob/33be2dbbe14eea00445010ecb9cd53ed603c01d5/packages/twenty-website/public/images/readme/Github%20Read-me%20banner.png">
<source media="(prefers-color-scheme: light)" srcset="https://github.com/twentyhq/twenty/blob/33be2dbbe14eea00445010ecb9cd53ed603c01d5/packages/twenty-website/public/images/readme/Github%20Read-me%20banner.png">
<img src="./packages/twenty-website/public/images/readme/Github%20Read-me%20banner.png" alt="Hacktoberfest" />
</picture>
</a>
</p>
<br>
<p align="center">
@@ -7,8 +16,9 @@
</p>
<h2 align="center" >The #1 Open-Source CRM </h3>
<p align="center">Tailored to your unique business needs</p>
<p align="center"><a href="https://twenty.com">🌐 Website</a> · <a href="https://twenty.com/developers">📚 Documentation</a> · <a href="https://github.com/orgs/twentyhq/projects/1"><img src="./packages/twenty-website/public/images/readme/planner-icon.svg" width="12" height="12"/> Roadmap </a> · <a href="https://discord.gg/cx5n4Jzs57"><img src="./packages/twenty-website/public/images/readme/discord-icon.svg" width="12" height="12"/> Discord</a> · <a href="https://www.figma.com/file/xt8O9mFeLl46C5InWwoMrN/Twenty"><img src="./packages/twenty-website/public/images/readme/figma-icon.png" width="12" height="12"/> Figma</a><p>
<p align="center"><a href="https://twenty.com">🌐 Website</a> · <a href="https://twenty.com/developers">📚 Documentation</a> · <a href="https://discord.gg/cx5n4Jzs57"><img src="./packages/twenty-website/public/images/readme/discord-icon.svg" width="12" height="12"/> Discord</a> · <a href="https://www.figma.com/file/xt8O9mFeLl46C5InWwoMrN/Twenty"><img src="./packages/twenty-website/public/images/readme/figma-icon.png" width="12" height="12"/> Figma</a><p>
<br />
@@ -21,33 +31,45 @@
</picture>
</a>
</p>
<br>
Weve spent thousands of hours grappling with traditional CRMs like Pipedrive and Salesforce to align them with our business needs, only to end up frustrated — customizations are complex and the closed ecosystems of these platforms can feel restrictive.
We felt the need for a CRM platform that empowers rather than constrains. We believe the next great CRM will come from the open-source community. Weve packed Twenty with powerful features to give you full control and help you run your business efficiently.
<br>
# Installation
# Demo
Go to <a href="https://demo.twenty.com/">demo.twenty.com</a> and login with the following credentials:
See:
```
email: tim@apple.dev
password: Applecar2025
```
See also:
🚀 [Self-hosting](https://twenty.com/developers/section/self-hosting)
🖥️ [Local Setup](https://twenty.com/developers/local-setup)
# Does the world need another CRM?
# Why Choose Twenty?
We understand that the CRM landscape is vast. So why should you choose us?
We built Twenty for three reasons:
⛓️ **Full control, Full Freedom:** Contribute, self-host, fork. Break free from vendor lock-in and join us in shaping the open future of CRM.
**CRMs are too expensive, and users are trapped.** Companies use locked-in customer data to hike prices. It shouldn't be that way.
📊 **Data, Your Way:** The days when the role of CRM platforms was to shift manual data entries to a database are over. Now, the data is already there. CRM 2.0 should be built around your data, allowing you to access and visualize any existing sources, not forcing you to retrofit your data into predefined objects on a remote cloud.
**A fresh start is required to build a better experience.** We can learn from past mistakes and craft a cohesive experience inspired by new UX patterns from tools like Notion, Airtable or Linear.
🎨 **Effortlessly Intuitive:** We set out to create something that we ourselves would always enjoy using. The main application draws inspiration from Notion, a tool known for its user-friendly interface and customization capabilities.
<br>
**We believe in Open-source and community.** Hundreds of developers are already building Twenty together. Once we have plugin capabilities, a whole ecosystem will grow around it.
<br>
# What You Can Do With Twenty
We're currently developing Twenty's beta version.
We're currently in the development phase of Twenty's alpha version.
Please feel free to flag any specific needs you have by creating an issue.
Please feel free to flag any specific need you have need by creating an issue.
Below are a few features we have implemented to date:
Below are some features we have implemented to date:
+ [Add, filter, sort, edit, and track customers](#add-filter-sort-edit-and-track-customers)
+ [Create one or several opportunities for each company](#create-one-or-several-opportunities-for-each-company)
@@ -136,27 +158,24 @@ Below are a few features we have implemented to date:
</picture>
</p>
<br>
# Stack
- [TypeScript](https://www.typescriptlang.org/)
- [Nx](https://nx.dev/)
- [NestJS](https://nestjs.com/), with [BullMQ](https://bullmq.io/), [PostgreSQL](https://www.postgresql.org/), [Redis](https://redis.io/)
- [React](https://reactjs.org/), with [Recoil](https://recoiljs.org/) and [Emotion](https://emotion.sh/)
- [Greptile](https://greptile.com) for code reviews.
- [Lingui](https://lingui.dev/) and [Crowdin](https://twenty.crowdin.com/twenty) for translations.
# What's In Store
Heres what you can look forward to:
**Frequent updates:** Were shipping fast! Expect regular updates and new features that enhance your experience.
🔗 **Extensibility:** Were putting the power in your hands. Soon, youll have the tools to extend and customize Twenty with plugins and more.
<br>
# Join the Community
- Star the repo
- Subscribe to releases (watch -> custom -> releases)
- Join [discussions](https://github.com/twentyhq/twenty/discussions) and track [issues](https://github.com/twentyhq/twenty/issues)
- Follow us on [Twitter](https://twitter.com/twentycrm) or [LinkedIn](https://www.linkedin.com/company/twenty/)
- Join our [Discord](https://discord.gg/cx5n4Jzs57)
- Improve translations on [Crowdin](https://twenty.crowdin.com/twenty)
- [Contributions](https://github.com/twentyhq/twenty/contribute) are, of course, most welcome!
-31
View File
@@ -1,31 +0,0 @@
#
# Basic Crowdin CLI configuration
# See https://crowdin.github.io/crowdin-cli/configuration for more information
# See https://support.crowdin.com/developer/configuration-file/ for all available options
#
#
# Defines whether to preserve the original directory structure in the Crowdin project
# Recommended to set to true
#
"preserve_hierarchy": true
#
# Files configuration.
# See https://support.crowdin.com/developer/configuration-file/ for all available options
#
files: [
{
#
# Source files filter
# e.g. "/resources/en/*.json"
#
"source": "**/en.po",
#
# Translation files filter
# e.g. "/resources/%two_letters_code%/%original_file_name%"
#
"translation": "%original_path%/%locale%.po",
}
]
@@ -44,10 +44,10 @@ function on_exit {
trap on_exit EXIT
# Use environment variables VERSION and BRANCH, with defaults if not set
version=${VERSION:-$(curl -s "https://hub.docker.com/v2/repositories/twentycrm/twenty/tags" | grep -o '"name":"[^"]*"' | grep -v 'latest' | cut -d'"' -f4 | sort -V | tail -n1)}
branch=${BRANCH:-$(curl -s https://api.github.com/repos/twentyhq/twenty/tags | grep '"name":' | head -n 1 | cut -d '"' -f 4)}
version=${VERSION:-$(curl -s https://api.github.com/repos/twentyhq/twenty/releases/latest | grep '"tag_name":' | cut -d '"' -f 4)}
branch=${BRANCH:-main}
echo "🚀 Using docker version $version and Github branch $branch"
echo "🚀 Using version $version and branch $branch"
dir_name="twenty"
function ask_directory {
@@ -72,7 +72,7 @@ done
echo "📁 Creating directory '$dir_name'"
mkdir -p "$dir_name" && cd "$dir_name" || { echo "❌ Failed to create/access directory '$dir_name'"; exit 1; }
# Copy twenty/packages/twenty-docker/docker-compose.yml in it
# Copy the twenty/packages/twenty-docker/docker-compose.yml file in it
echo -e "\t• Copying docker-compose.yml"
curl -sLo docker-compose.yml https://raw.githubusercontent.com/twentyhq/twenty/$branch/packages/twenty-docker/docker-compose.yml
@@ -90,11 +90,13 @@ else
fi
# Generate random strings for secrets
echo "# === Randomly generated secret ===" >> .env
echo "APP_SECRET=$(openssl rand -base64 32)" >> .env
echo "" >> .env
echo "PG_DATABASE_PASSWORD=$(openssl rand -hex 16)" >> .env
echo "# === Randomly generated secrets ===" >>.env
echo "ACCESS_TOKEN_SECRET=$(openssl rand -base64 32)" >>.env
echo "LOGIN_TOKEN_SECRET=$(openssl rand -base64 32)" >>.env
echo "REFRESH_TOKEN_SECRET=$(openssl rand -base64 32)" >>.env
echo "FILE_TOKEN_SECRET=$(openssl rand -base64 32)" >>.env
echo "" >>.env
echo "POSTGRES_ADMIN_PASSWORD=$(openssl rand -base64 32)" >>.env
echo -e "\t• .env configuration completed"
+21 -21
View File
@@ -14,7 +14,6 @@
"!{projectRoot}/**/tsconfig.spec.json",
"!{projectRoot}/**/*.test.(ts|tsx)",
"!{projectRoot}/**/*.spec.(ts|tsx)",
"!{projectRoot}/**/*.integration-spec.ts",
"!{projectRoot}/**/__tests__/*"
],
"production": [
@@ -48,8 +47,7 @@
"configurations": {
"ci": { "cacheStrategy": "content" },
"fix": { "fix": true }
},
"dependsOn": ["^build"]
}
},
"fmt": {
"executor": "nx:run-commands",
@@ -65,8 +63,7 @@
"configurations": {
"ci": { "cacheStrategy": "content" },
"fix": { "write": true }
},
"dependsOn": ["^build"]
}
},
"typecheck": {
"executor": "nx:run-commands",
@@ -77,8 +74,7 @@
},
"configurations": {
"watch": { "watch": true }
},
"dependsOn": ["^build"]
}
},
"test": {
"executor": "@nx/jest:jest",
@@ -112,15 +108,15 @@
"storybook:build": {
"executor": "nx:run-commands",
"cache": true,
"dependsOn": ["^build"],
"inputs": ["^default", "excludeTests"],
"outputs": ["{projectRoot}/{options.output-dir}"],
"options": {
"cwd": "{projectRoot}",
"command": "VITE_DISABLE_TYPESCRIPT_CHECKER=true VITE_DISABLE_ESLINT_CHECKER=true storybook build --test",
"command": "storybook build",
"output-dir": "storybook-static",
"config-dir": ".storybook"
},
"dependsOn": ["^build"]
}
},
"storybook:serve:dev": {
"executor": "nx:run-commands",
@@ -152,14 +148,12 @@
"options": {
"cwd": "{projectRoot}",
"commands": [
"test-storybook --url http://localhost:{args.port} --maxWorkers=3 --coverage --coverageDirectory={args.coverageDir} --shard={args.shard}",
"nx storybook:coverage {projectName} --coverageDir={args.coverageDir} --checkCoverage={args.checkCoverage}"
"test-storybook --url http://localhost:{args.port} --maxWorkers=3 --coverage --coverageDirectory={args.coverageDir}",
"nx storybook:coverage {projectName} --coverageDir={args.coverageDir}"
],
"shard": "1/1",
"parallel": false,
"coverageDir": "coverage/storybook",
"port": 6006,
"checkCoverage": true
"port": 6006
}
},
"storybook:test:no-coverage": {
@@ -186,10 +180,9 @@
"!{projectRoot}/coverage/storybook/coverage-storybook.json"
],
"options": {
"command": "npx nyc report --reporter={args.reporter} --reporter=text-summary -t {args.coverageDir} --report-dir {args.coverageDir} --check-coverage={args.checkCoverage} --cwd={projectRoot}",
"command": "npx nyc report --reporter={args.reporter} --reporter=text-summary -t {args.coverageDir} --report-dir {args.coverageDir} --check-coverage --cwd={projectRoot}",
"coverageDir": "coverage/storybook",
"reporter": "lcov",
"checkCoverage": true
"reporter": "lcov"
},
"configurations": {
"text": { "reporter": "text" }
@@ -199,10 +192,17 @@
"executor": "nx:run-commands",
"options": {
"commands": [
"npx concurrently --kill-others --success=first -n SB,TEST 'nx storybook:serve:static {projectName} --port={args.port}' 'npx wait-on tcp:{args.port} && nx storybook:test {projectName} --shard={args.shard} --checkCoverage={args.checkCoverage} --port={args.port} --configuration={args.scope}'"
"npx concurrently --kill-others --success=first -n SB,TEST 'nx storybook:serve:static {projectName} --port={args.port}' 'npx wait-on tcp:{args.port} && nx storybook:test {projectName} --port={args.port}'"
],
"port": 6006
}
},
"storybook:serve-and-test:static:performance": {
"executor": "nx:run-commands",
"options": {
"commands": [
"npx concurrently --kill-others --success=first -n SB,TEST 'nx storybook:serve:dev {projectName} --configuration=performance --port={args.port}' 'npx wait-on tcp:{args.port} && nx storybook:test:no-coverage {projectName} --port={args.port} --configuration=performance'"
],
"shard": "1/1",
"checkCoverage": true,
"port": 6006
}
},
@@ -0,0 +1,21 @@
**Side Quest**: Create a YouTube Video about Twenty showcasing a specific way to use Twenty effectively.
**Points**: 750 Points
**Proof**: Add your oss handle and YouTube video link to the list below.
Please follow the following schema:
---
» 05-April-2024 by YOUR oss.gg HANDLE » YouTube Link: https://link.to/content
---
////////////////////////////
Your turn 👇
////////////////////////////
» 02-October-2024 by [yourhandle](https://oss.gg/yourhandle) YouTube Link: [YouTube](https://twenty.com/)
---
@@ -0,0 +1,21 @@
**Side Quest**: Write a blog post about sharing your experience using Twenty in a detailed format on any platform.
**Points**: 750 Points
**Proof**: Add your oss handle and blog link to the list below.
Please follow the following schema:
---
» 05-April-2024 by YOUR oss.gg HANDLE » blog Link: https://link.to/content
---
////////////////////////////
Your turn 👇
////////////////////////////
» 02-October-2024 by [yourhandle](https://oss.gg/yourhandle) blog Link: [blog](https://twenty.com/)
---
@@ -0,0 +1,21 @@
**Side Quest**: Write a blog post about self-hosting Twenty in a detailed format on any platform.
**Points**: 750 Points
**Proof**: Add your oss handle and blog link to the list below.
Please follow the following schema:
---
» 05-April-2024 by YOUR oss.gg HANDLE » blog Link: https://link.to/content
---
////////////////////////////
Your turn 👇
////////////////////////////
» 02-October-2024 by [yourhandle](https://oss.gg/yourhandle) blog Link: [blog](https://twenty.com/)
---
@@ -0,0 +1,21 @@
**Side Quest**: Create a promotional video for Twenty and share it on social media.
**Points**: 750 Points
**Proof**: Add your oss handle and video link to the list below.
Please follow the following schema:
---
» 05-April-2024 by YOUR oss.gg HANDLE » video Link: https://link.to/content
---
////////////////////////////
Your turn 👇
////////////////////////////
» 02-October-2024 by [yourhandle](https://oss.gg/yourhandle) video Link: [video](https://twenty.com/)
---
@@ -0,0 +1,23 @@
**Side Quest**: Design a promotional poster of Twenty and share it on social media.
**Points**: 300 Points
**Proof**: Add your oss handle and poster link to the list below.
Please follow the following schema:
---
» 05-April-2024 by YOUR oss.gg HANDLE » poster Link: https://link.to/content
---
////////////////////////////
Your turn 👇
////////////////////////////
» 02-October-2024 by [yourhandle](https://oss.gg/yourhandle) poster Link: [poster](https://twenty.com/)
» 11-October-2024 by [thefool76](https://oss.gg/thefool76) poster Link: [poster](https://drive.google.com/file/d/1cIC1eitvY6zKVTXKq2LnVrS_2Ho9H8-P/view?usp=sharing)
---
@@ -0,0 +1,24 @@
**Side Quest**: Design/Create new Twenty logo, tweet your design, and mention @twentycrm.
**Points**: 300 Points
**Proof**: Create a logo uploade it on any of the platform and add your oss handle and logo link to the list below.
Please follow the following schema:
---
» 05-April-2024 by YOUR oss.gg HANDLE » Logo Link: https://link.to/content » tweet Link: https://link.to/content
---
////////////////////////////
Your turn 👇
////////////////////////////
» 08-October-2024 by [adityadeshlahre](https://oss.gg/adityadeshlahre) Logo Link: [logo](https://drive.google.com/drive/folders/13k22xMnX2fhnWK94vas_hO1t-ImqXcHZ?usp=drive_link) » tweet Link: [tweet](https://x.com/adityadeshlahre/status/1843354963176718374)
» 11-October-2024 by [thefool76](https://oss.gg/thefool76) Logo Link: [logo](https://drive.google.com/file/d/1DxSwNY_i90kGgWzPQj5SxScBz_6r02l4/view?usp=sharing) » tweet Link: [tweet](https://x.com/thefool1135/status/1844693487067034008)
---
@@ -0,0 +1,21 @@
**Side Quest**: Duplicate the Figma file from the main repo and customize the variables to create a unique interface theme for Twenty.
**Points**: 750 Points
**Proof**: Add your oss handle and Figma link to the list below.
Please follow the following schema:
---
» 05-April-2024 by YOUR oss.gg HANDLE » Figma Link: https://link.to/content
---
////////////////////////////
Your turn 👇
////////////////////////////
» 02-October-2024 by [yourhandle](https://oss.gg/yourhandle) Figma Link: [Figma](https://twenty.com/)
---
@@ -0,0 +1,21 @@
**Side Quest**: Develop a script to facilitate the migration of data from another CRM to Twenty.
**Points**: 750 Points
**Proof**: Add your oss handle and record video and share link to the list below. In video show the working proof of your created script.
Please follow the following schema:
---
» 05-April-2024 by YOUR oss.gg HANDLE » video Link: https://link.to/content
---
////////////////////////////
Your turn 👇
////////////////////////////
» 02-October-2024 by [yourhandle](https://oss.gg/yourhandle) video Link: [video](https://twenty.com/)
---
@@ -0,0 +1,21 @@
**Side Quest**: Develop an integration for Raycast that enables users to create records on any object within Twenty directly from Raycast.
**Points**: 1500 Points
**Proof**: Add your oss handle and record video and share link to the list below. In video show the workflow of the your integration created and perform some task.
Please follow the following schema:
---
» 05-April-2024 by YOUR oss.gg HANDLE » video Link: https://link.to/content
---
////////////////////////////
Your turn 👇
////////////////////////////
» 02-October-2024 by [yourhandle](https://oss.gg/yourhandle) video Link: [video](https://twenty.com/)
---
@@ -0,0 +1,21 @@
**Side Quest**: Create an n8n workflow that empowers Twenty by connecting it to another tool.
**Points**: 750 Points
**Proof**: Add your oss handle and template link to the list below.
Please follow the following schema:
---
» 05-April-2024 by YOUR oss.gg HANDLE » template Link: https://link.to/content
---
////////////////////////////
Your turn 👇
////////////////////////////
» 02-October-2024 by [yourhandle](https://oss.gg/yourhandle) template Link: [template](https://twenty.com/)
---
@@ -0,0 +1,21 @@
**Side Quest**: Write a comprehensive guide on how to integrate Twenty with marketing automation tool (n8n, Zapier). Include a concrete use case and explain how to leverage AI to write API requests for non-developers and share it.
**Points**: 1500 Points
**Proof**: Add your oss handle and guide link to the list below.
Please follow the following schema:
---
» 05-April-2024 by YOUR oss.gg HANDLE » guide Link: https://link.to/content
---
////////////////////////////
Your turn 👇
////////////////////////////
» 02-October-2024 by [yourhandle](https://oss.gg/yourhandle) guide Link: [guide](https://twenty.com/)
---
@@ -0,0 +1,30 @@
**Side Quest**: Like & Re-Tweet oss.gg Launch Tweet. Quote-tweet it tagging @twentycrm to say youll be contributing.
**Points**: 50 Points
**Proof**: Add a screenshot of the retweet to the PR description. Add a link to your retweet in the list below.
Please follow the following schema:
---
» 05-April-2024 by YOUR NAME
» Link to Tweet: https://x.com/...
---
////////////////////////////
Your turn 👇
////////////////////////////
» 10-October-2024 by Devansh Baghel
» Link to Tweet: https://x.com/DevanshBaghel5/status/1844359648037748954
» 11-October-2024 by Bhavesh Mishra
» Link to Tweet: https://x.com/thefool1135/status/1844453425188405326
» 11-October-2024 by Chirag Arora
» Link to Tweet: https://x.com/Chirag8023/status/1844689900668682699
» 11-October-2024 by Aritra Sadhukhan
» Link to Tweet: https://x.com/AritraDevelops/status/1844670236512878646
@@ -0,0 +1,26 @@
**Side Quest**: Share a tweet about your favorite feature in Twenty. Tweet about your favorite feature in Twenty and mention @twentycrm.
**Points**: 50 Points
**Proof**: Add a screenshot of the tweet to the PR description. Add a link to your tweet in the list below.
Please follow the following schema:
---
» 05-April-2024 by YOUR NAME
» Link to Tweet: https://x.com/...
---
////////////////////////////
Your turn 👇
////////////////////////////
» 10-October-2024 by Devansh Baghel
» Link to Tweet: https://x.com/DevanshBaghel5/status/1844384722119704972
» 11-October-2024 by Bhavesh Mishra
» Link to Tweet: https://x.com/thefool1135/status/1844456500380696969
---
+23
View File
@@ -0,0 +1,23 @@
**Side Quest**: Create a bug report. Use the Twenty bug issue template to report a bug in detail, including steps to reproduce it.
**Points**: 50-150 Points
**Proof**: Add a link to your bug report in the list below.
Please follow the following schema:
---
» 05-April-2024 by YOUR NAME
» Link to bug report: https://github.com/twentyhq/twenty/issues/...
---
////////////////////////////
Your turn 👇
////////////////////////////
» 10-October-2024 by Devansh Baghel
» Link to bug report: https://github.com/twentyhq/twenty/issues/7560
---
+26
View File
@@ -0,0 +1,26 @@
**Side Quest**: Meme Magic: Craft a meme where the number twenty plays a role. Tweet it, and tag @twentycrm.
**Points**: 150 Points
**Proof**: Add a screenshot of meme to the PR description. Add a link to your tweet in the list below.
Please follow the following schema:
---
» 05-April-2024 by YOUR NAME
» Link to Tweet: https://x.com/...
---
////////////////////////////
Your turn 👇
////////////////////////////
» 10-October-2024 by Teddy ASSIH
» Link to Tweet: https://x.com/ion_finisher/status/1844389252253299173
» 11-October-2024 by Bhavesh Mishra
» Link to Tweet: https://x.com/thefool1135/status/1844458836402503931
---
+26
View File
@@ -0,0 +1,26 @@
**Side Quest**: Gif Magic: Create a gif related to Twenty. Tweet it, and tag @twentycrm.
**Points**: 150 Points
**Proof**: Add a screenshot of GIF on Giphy to the PR description. Add a link to your GIPHY in the list below.
Please follow the following schema:
---
» 05-April-2024 by YOUR NAME
» Link to gif: https://giphy.com/...
---
////////////////////////////
Your turn 👇
////////////////////////////
» 10-October-2024 by Teddy ASSIH
» Link to gif: https://giphy.com/gifs/oss-crm-twenty-VWDHAIlGTbc6Nqdza9
» 11-October-2024 by Bhavesh Mishra
» Link to gif: https://shorturl.at/yln9H
---
@@ -0,0 +1,19 @@
**Side Quest**: Complete all Twenty side quests
**Points**: 300 Points
**Proof**: Add screenshots for each side quest to the PR description. Add your name to the list below.
Please follow the following schema:
---
» 05-April-2024 by YOUR NAME
////////////////////////////
Your turn 👇
////////////////////////////
» 01-October-2024 by X
---
+13 -30
View File
@@ -5,11 +5,9 @@
"@apollo/server": "^4.7.3",
"@aws-sdk/client-lambda": "^3.614.0",
"@aws-sdk/client-s3": "^3.363.0",
"@aws-sdk/client-sts": "^3.744.0",
"@aws-sdk/credential-providers": "^3.363.0",
"@blocknote/mantine": "^0.22.0",
"@blocknote/react": "^0.22.0",
"@blocknote/server-util": "0.17.1",
"@blocknote/mantine": "^0.15.3",
"@blocknote/react": "^0.15.3",
"@codesandbox/sandpack-react": "^2.13.5",
"@dagrejs/dagre": "^1.1.2",
"@emotion/react": "^11.11.1",
@@ -24,10 +22,7 @@
"@jsdevtools/rehype-toc": "^3.0.2",
"@linaria/core": "^6.2.0",
"@linaria/react": "^6.2.1",
"@lingui/core": "^5.1.2",
"@lingui/react": "^5.1.2",
"@mdx-js/react": "^3.0.0",
"@microsoft/microsoft-graph-client": "^3.0.7",
"@nestjs/apollo": "^11.0.5",
"@nestjs/axios": "^3.0.1",
"@nestjs/cli": "^9.0.0",
@@ -45,8 +40,8 @@
"@octokit/graphql": "^7.0.2",
"@ptc-org/nestjs-query-core": "^4.2.0",
"@ptc-org/nestjs-query-typeorm": "4.2.1-alpha.2",
"@react-email/components": "0.0.32",
"@react-email/render": "0.0.17",
"@react-email/components": "0.0.12",
"@react-email/render": "0.0.10",
"@sentry/node": "^8",
"@sentry/profiling-node": "^8",
"@sentry/react": "^8",
@@ -63,7 +58,6 @@
"@types/nodemailer": "^6.4.14",
"@types/passport-microsoft": "^1.0.3",
"@wyw-in-js/vite": "^0.5.3",
"@xyflow/react": "^12.4.2",
"add": "^2.0.6",
"addressparser": "^1.0.1",
"afterframe": "^1.0.2",
@@ -74,11 +68,10 @@
"bcrypt": "^5.1.1",
"better-sqlite3": "^9.2.2",
"body-parser": "^1.20.2",
"bullmq": "^5.40.0",
"bullmq": "^4.14.0",
"bytes": "^3.1.2",
"class-transformer": "^0.5.1",
"clsx": "^2.1.1",
"cron-validate": "^1.4.5",
"cross-env": "^7.0.3",
"css-loader": "^7.1.2",
"danger-plugin-todos": "^1.3.1",
@@ -93,7 +86,7 @@
"esbuild-plugin-svgr": "^2.1.0",
"facepaint": "^1.2.1",
"file-type": "16.5.4",
"framer-motion": "^11.18.0",
"framer-motion": "^10.12.17",
"googleapis": "105",
"graphiql": "^3.1.1",
"graphql": "16.8.0",
@@ -119,7 +112,6 @@
"lodash.chunk": "^4.2.0",
"lodash.compact": "^3.0.1",
"lodash.debounce": "^4.0.8",
"lodash.escaperegexp": "^4.1.2",
"lodash.groupby": "^4.6.0",
"lodash.identity": "^3.0.0",
"lodash.isempty": "^4.4.0",
@@ -167,13 +159,14 @@
"react-hotkeys-hook": "^4.4.4",
"react-icons": "^4.12.0",
"react-imask": "^7.6.0",
"react-intersection-observer": "^9.15.1",
"react-intersection-observer": "^9.5.2",
"react-loading-skeleton": "^3.3.1",
"react-phone-number-input": "^3.3.4",
"react-responsive": "^9.0.2",
"react-router-dom": "^6.4.4",
"react-textarea-autosize": "^8.4.1",
"react-tooltip": "^5.13.1",
"reactflow": "^11.11.3",
"recoil": "^0.7.7",
"rehype-slug": "^6.0.0",
"remark-behead": "^3.1.0",
@@ -183,8 +176,7 @@
"scroll-into-view": "^1.16.2",
"semver": "^7.5.4",
"sharp": "^0.32.1",
"slash": "^5.1.0",
"stripe": "^17.3.1",
"stripe": "^14.17.0",
"ts-key-enum": "^2.0.12",
"tslib": "^2.3.0",
"tsup": "^8.2.4",
@@ -207,10 +199,6 @@
"@graphql-codegen/typescript": "^3.0.4",
"@graphql-codegen/typescript-operations": "^3.0.4",
"@graphql-codegen/typescript-react-apollo": "^3.3.7",
"@lingui/cli": "^5.1.2",
"@lingui/swc-plugin": "^5.1.0",
"@lingui/vite-plugin": "^5.1.2",
"@microsoft/microsoft-graph-types": "^2.40.0",
"@nestjs/cli": "^9.0.0",
"@nestjs/schematics": "^9.0.0",
"@nestjs/testing": "^9.0.0",
@@ -243,7 +231,7 @@
"@stylistic/eslint-plugin": "^1.5.0",
"@swc-node/register": "1.8.0",
"@swc/cli": "^0.3.12",
"@swc/core": "1.7.42",
"@swc/core": "~1.3.100",
"@swc/helpers": "~0.5.2",
"@testing-library/jest-dom": "^6.1.5",
"@testing-library/react": "14.0.0",
@@ -255,15 +243,14 @@
"@types/chrome": "^0.0.267",
"@types/deep-equal": "^1.0.1",
"@types/express": "^4.17.13",
"@types/file-saver": "^2.0.7",
"@types/graphql-fields": "^1.3.6",
"@types/graphql-upload": "^8.0.12",
"@types/jest": "^29.5.11",
"@types/js-cookie": "^3.0.3",
"@types/js-levenshtein": "^1.1.3",
"@types/lodash.camelcase": "^4.3.7",
"@types/lodash.compact": "^3.0.9",
"@types/lodash.debounce": "^4.0.7",
"@types/lodash.escaperegexp": "^4.1.9",
"@types/lodash.groupby": "^4.6.9",
"@types/lodash.identity": "^3.0.9",
"@types/lodash.isempty": "^4.4.7",
@@ -305,10 +292,8 @@
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-import": "2.29.1",
"eslint-plugin-jsx-a11y": "^6.8.0",
"eslint-plugin-lingui": "^0.9.0",
"eslint-plugin-prefer-arrow": "^1.2.3",
"eslint-plugin-prettier": "^5.1.2",
"eslint-plugin-project-structure": "^3.9.1",
"eslint-plugin-react": "^7.33.2",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.4",
@@ -357,13 +342,12 @@
"resolutions": {
"graphql": "16.8.0",
"type-fest": "4.10.1",
"typescript": "5.3.3",
"prosemirror-model": "1.23.0"
"typescript": "5.3.3"
},
"version": "0.2.1",
"nx": {},
"scripts": {
"start": "npx concurrently --kill-others 'npx nx run-many -t start -p twenty-server twenty-front' 'npx wait-on tcp:3000 && npx nx run twenty-server:worker'"
"start": "npx nx run-many -t start -p twenty-server twenty-front"
},
"workspaces": {
"packages": [
@@ -376,7 +360,6 @@
"packages/twenty-zapier",
"packages/twenty-website",
"packages/twenty-e2e-testing",
"packages/twenty-shared",
"tools/eslint-rules"
]
}
@@ -6,8 +6,5 @@
"type": "module",
"scripts": {
"build": "npx vite build"
},
"dependencies": {
"twenty-shared": "workspace:*"
}
}
@@ -8,8 +8,7 @@
"outputs": ["{options.outputPath}"],
"options": {
"outputPath": "{projectRoot}/dist"
},
"dependsOn": ["^build"]
}
},
"start": {
"executor": "nx:run-commands",
@@ -1,4 +1,4 @@
import { isDefined } from 'twenty-shared';
import { isDefined } from '~/utils/isDefined';
// Open options page programmatically in a new tab.
// chrome.runtime.onInstalled.addListener((details) => {
@@ -1,4 +1,4 @@
import { isDefined } from 'twenty-shared';
import { isDefined } from '~/utils/isDefined';
interface CustomDiv extends HTMLDivElement {
onClickHandler: (newHandler: () => void) => void;
@@ -1,10 +1,10 @@
import { isDefined } from 'twenty-shared';
import { createDefaultButton } from '~/contentScript/createButton';
import changeSidePanelUrl from '~/contentScript/utils/changeSidepanelUrl';
import extractCompanyLinkedinLink from '~/contentScript/utils/extractCompanyLinkedinLink';
import extractDomain from '~/contentScript/utils/extractDomain';
import { createCompany, fetchCompany } from '~/db/company.db';
import { CompanyInput } from '~/db/types/company.types';
import { isDefined } from '~/utils/isDefined';
export const checkIfCompanyExists = async () => {
const { tab: activeTab } = await chrome.runtime.sendMessage({
@@ -1,9 +1,9 @@
import { isDefined } from 'twenty-shared';
import { createDefaultButton } from '~/contentScript/createButton';
import changeSidePanelUrl from '~/contentScript/utils/changeSidepanelUrl';
import extractFirstAndLastName from '~/contentScript/utils/extractFirstAndLastName';
import { createPerson, fetchPerson } from '~/db/person.db';
import { PersonInput } from '~/db/types/person.types';
import { isDefined } from '~/utils/isDefined';
export const checkIfPersonExists = async () => {
const { tab: activeTab } = await chrome.runtime.sendMessage({
@@ -1,6 +1,6 @@
import { isDefined } from 'twenty-shared';
import { insertButtonForCompany } from '~/contentScript/extractCompanyProfile';
import { insertButtonForPerson } from '~/contentScript/extractPersonProfile';
import { isDefined } from '~/utils/isDefined';
// Inject buttons into the DOM when SPA is reloaded on the resource url.
// e.g. reload the page when on https://www.linkedin.com/in/mabdullahabaid/
@@ -1,4 +1,4 @@
import { isDefined } from 'twenty-shared';
import { isDefined } from '~/utils/isDefined';
const btn = document.getElementById('twenty-settings-btn');
if (!isDefined(btn)) {
@@ -1,4 +1,4 @@
import { isDefined } from 'twenty-shared';
import { isDefined } from '~/utils/isDefined';
const changeSidePanelUrl = async (url: string) => {
if (isDefined(url)) {
@@ -1,6 +1,6 @@
// Extract "https://www.linkedin.com/company/twenty/" from any of the following urls, which the user can visit while on the company page.
import { isDefined } from 'twenty-shared';
import { isDefined } from '~/utils/isDefined';
// "https://www.linkedin.com/company/twenty/" "https://www.linkedin.com/company/twenty/about/" "https://www.linkedin.com/company/twenty/people/".
const extractCompanyLinkedinLink = (activeTabUrl: string) => {
@@ -1,10 +1,10 @@
import { isDefined } from 'twenty-shared';
import {
ExchangeAuthCodeInput,
ExchangeAuthCodeResponse,
Tokens,
} from '~/db/types/auth.types';
import { EXCHANGE_AUTHORIZATION_CODE } from '~/graphql/auth/mutations';
import { isDefined } from '~/utils/isDefined';
import { callMutation } from '~/utils/requestDb';
export const exchangeAuthorizationCode = async (
@@ -1,4 +1,3 @@
import { isDefined } from 'twenty-shared';
import {
CompanyInput,
CreateCompanyResponse,
@@ -7,6 +6,7 @@ import {
import { Company, CompanyFilterInput } from '~/generated/graphql';
import { CREATE_COMPANY } from '~/graphql/company/mutations';
import { FIND_COMPANY } from '~/graphql/company/queries';
import { isDefined } from '~/utils/isDefined';
import { callMutation, callQuery } from '../utils/requestDb';
@@ -1,4 +1,3 @@
import { isDefined } from 'twenty-shared';
import {
CreatePersonResponse,
FindPersonResponse,
@@ -7,6 +6,7 @@ import {
import { Person, PersonFilterInput } from '~/generated/graphql';
import { CREATE_PERSON } from '~/graphql/person/mutations';
import { FIND_PERSON } from '~/graphql/person/queries';
import { isDefined } from '~/utils/isDefined';
import { callMutation, callQuery } from '../utils/requestDb';
@@ -1,8 +1,8 @@
import { ApolloClient, InMemoryCache } from '@apollo/client';
import { isDefined } from 'twenty-shared';
import { Tokens } from '~/db/types/auth.types';
import { RENEW_TOKEN } from '~/graphql/auth/mutations';
import { isDefined } from '~/utils/isDefined';
export const renewToken = async (
appToken: string,
@@ -1,8 +1,8 @@
import { useEffect, useState } from 'react';
import { isDefined } from 'twenty-shared';
import Settings from '~/options/Settings';
import Sidepanel from '~/options/Sidepanel';
import { isDefined } from '~/utils/isDefined';
const App = () => {
const [currentScreen, setCurrentScreen] = useState('');
@@ -1,6 +1,7 @@
import { Loader } from '@/ui/display/loader/components/Loader';
import styled from '@emotion/styled';
import { Loader } from '@/ui/display/loader/components/Loader';
const StyledContainer = styled.div`
align-items: center;
background: ${({ theme }) => theme.background.noisy};
@@ -1,10 +1,10 @@
import styled from '@emotion/styled';
import { useEffect, useState } from 'react';
import styled from '@emotion/styled';
import { MainButton } from '@/ui/input/button/MainButton';
import { TextInput } from '@/ui/input/components/TextInput';
import { isDefined } from 'twenty-shared';
import { clearStore } from '~/utils/apolloClient';
import { isDefined } from '~/utils/isDefined';
const StyledWrapper = styled.div`
align-items: center;
@@ -1,8 +1,8 @@
import styled from '@emotion/styled';
import { useCallback, useEffect, useRef, useState } from 'react';
import styled from '@emotion/styled';
import { MainButton } from '@/ui/input/button/MainButton';
import { isDefined } from 'twenty-shared';
import { isDefined } from '~/utils/isDefined';
const StyledIframe = styled.iframe`
display: block;
@@ -3,7 +3,7 @@ import styled from '@emotion/styled';
type H2TitleProps = {
title: string;
description?: string;
adornment?: React.ReactNode;
addornment?: React.ReactNode;
};
const StyledContainer = styled.div`
@@ -33,11 +33,11 @@ const StyledDescription = styled.h3`
margin-top: ${({ theme }) => theme.spacing(3)};
`;
export const H2Title = ({ title, description, adornment }: H2TitleProps) => (
export const H2Title = ({ title, description, addornment }: H2TitleProps) => (
<StyledContainer>
<StyledTitleContainer>
<StyledTitle>{title}</StyledTitle>
{adornment}
{addornment}
</StyledTitleContainer>
{description && <StyledDescription>{description}</StyledDescription>}
</StyledContainer>
@@ -1,5 +1,5 @@
import React from 'react';
import styled from '@emotion/styled';
import React, { useId } from 'react';
interface TextInputProps {
label?: string;
@@ -18,7 +18,7 @@ const StyledContainer = styled.div<{ fullWidth?: boolean }>`
margin-bottom: ${({ theme }) => theme.spacing(4)};
`;
const StyledLabel = styled.label`
const StyledLabel = styled.span`
color: ${({ theme }) => theme.font.color.light};
font-size: ${({ theme }) => theme.font.size.xs};
font-weight: ${({ theme }) => theme.font.weight.semiBold};
@@ -65,15 +65,12 @@ const TextInput: React.FC<TextInputProps> = ({
placeholder,
icon,
}) => {
const inputId = useId();
return (
<StyledContainer fullWidth={fullWidth}>
{label && <StyledLabel htmlFor={inputId}>{label}</StyledLabel>}
{label && <StyledLabel>{label}</StyledLabel>}
<StyledInputContainer>
{icon && <StyledIcon>{icon}</StyledIcon>}
<StyledInput
id={inputId}
type="text"
value={value}
onChange={(e) => onChange(e.target.value)}
@@ -1,8 +1,8 @@
import { useEffect, useState } from 'react';
import styled from '@emotion/styled';
import { motion } from 'framer-motion';
import { useEffect, useState } from 'react';
import { isDefined } from 'twenty-shared';
import { isDefined } from '~/utils/isDefined';
export type ToggleSize = 'small' | 'medium';
@@ -2,7 +2,7 @@ import { ApolloClient, from, HttpLink, InMemoryCache } from '@apollo/client';
import { setContext } from '@apollo/client/link/context';
import { onError } from '@apollo/client/link/error';
import { isDefined } from 'twenty-shared';
import { isDefined } from '~/utils/isDefined';
export const clearStore = () => {
chrome.storage.local.remove([
@@ -1,8 +1,8 @@
import { OperationVariables } from '@apollo/client';
import { DocumentNode } from 'graphql';
import { isDefined } from 'twenty-shared';
import getApolloClient from '~/utils/apolloClient';
import { isDefined } from '~/utils/isDefined';
export const callQuery = async <T>(
query: DocumentNode,
@@ -9,8 +9,8 @@
"skipLibCheck": true,
"esModuleInterop": true,
"paths": {
"@/*": ["./src/options/modules/*"],
"~/*": ["./src/*"]
"@/*": ["packages/twenty-chrome-extension/src/options/modules/*"],
"~/*": ["packages/twenty-chrome-extension/src/*"]
},
/* Bundler mode */
+11 -7
View File
@@ -1,16 +1,20 @@
TAG=latest
#PG_DATABASE_USER=postgres
#PG_DATABASE_PASSWORD=replace_me_with_a_strong_password_without_special_characters
#PG_DATABASE_HOST=db
#PG_DATABASE_PORT=5432
#REDIS_URL=redis://redis:6379
# POSTGRES_ADMIN_PASSWORD=replace_me_with_a_strong_password
PG_DATABASE_HOST=db:5432
SERVER_URL=http://localhost:3000
SIGN_IN_PREFILLED=false
# REDIS_HOST=redis
# REDIS_PORT=6379
# Use openssl rand -base64 32 for each secret
# APP_SECRET=replace_me_with_a_random_string
# ACCESS_TOKEN_SECRET=replace_me_with_a_random_string_access
# LOGIN_TOKEN_SECRET=replace_me_with_a_random_string_login
# REFRESH_TOKEN_SECRET=replace_me_with_a_random_string_refresh
# FILE_TOKEN_SECRET=replace_me_with_a_random_string_refresh
SIGN_IN_PREFILLED=true
STORAGE_TYPE=local
+36
View File
@@ -1,9 +1,37 @@
dev-build:
@docker compose -f dev/docker-compose.yml down -v
@docker compose -f dev/docker-compose.yml build
dev-up:
@docker compose -f dev/docker-compose.yml up -d
dev-start:
@docker compose -f dev/docker-compose.yml start
dev-stop:
@docker compose -f dev/docker-compose.yml stop
dev-down:
@docker compose -f dev/docker-compose.yml down -v
dev-sh:
@docker compose -f dev/docker-compose.yml exec twenty-dev sh
dev-postgres-build:
@docker stop twenty_postgres || true
@docker rm twenty_postgres || true
@docker volume rm twenty_db_data || true
@docker compose -f dev/docker-compose.yml up --build postgres -d
prod-build:
@cd ../.. && docker build -f ./packages/twenty-docker/twenty/Dockerfile --tag twenty . && cd -
prod-run:
@docker run -d -p 3000:3000 --name twenty twenty
prod-postgres-build:
@cd ../.. && docker build -f ./packages/twenty-docker/twenty-postgres/Dockerfile --tag twenty-postgres . && cd -
prod-postgres-run:
@docker run -d -p 5432:5432 -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=postgres --name twenty-postgres twenty-postgres
@@ -12,3 +40,11 @@ prod-website-build:
prod-website-run:
@docker run -d -p 3000:3000 --name twenty-website twenty-website
release-postgres:
@cd ../.. && docker buildx build \
--push \
--no-cache \
--platform linux/amd64,linux/arm64 \
-f ./packages/twenty-docker/twenty-postgres/Dockerfile -t twentycrm/twenty-postgres:$(version) -t twentycrm/twenty-postgres:latest . \
&& cd -
+31 -17
View File
@@ -1,3 +1,4 @@
version: "3.9"
name: twenty
services:
@@ -13,24 +14,32 @@ services:
&& chown -R 1000:1000 /tmp/docker-data"
server:
image: twentycrm/twenty:${TAG:-latest}
image: twentycrm/twenty:${TAG}
volumes:
- server-local-data:/app/packages/twenty-server/${STORAGE_LOCAL_PATH:-.local-storage}
- docker-data:/app/docker-data
ports:
- "3000:3000"
environment:
NODE_PORT: 3000
PG_DATABASE_URL: postgres://${PG_DATABASE_USER:-postgres}:${PG_DATABASE_PASSWORD:-postgres}@${PG_DATABASE_HOST:-db}:${PG_DATABASE_PORT:-5432}/default
PORT: 3000
PG_DATABASE_URL: postgres://twenty:twenty@${PG_DATABASE_HOST}/default
SERVER_URL: ${SERVER_URL}
REDIS_URL: ${REDIS_URL:-redis://redis:6379}
FRONT_BASE_URL: ${FRONT_BASE_URL:-$SERVER_URL}
REDIS_PORT: ${REDIS_PORT:-6379}
REDIS_HOST: ${REDIS_HOST:-redis}
ENABLE_DB_MIGRATIONS: "true"
SIGN_IN_PREFILLED: ${SIGN_IN_PREFILLED}
STORAGE_TYPE: ${STORAGE_TYPE}
STORAGE_S3_REGION: ${STORAGE_S3_REGION}
STORAGE_S3_NAME: ${STORAGE_S3_NAME}
STORAGE_S3_ENDPOINT: ${STORAGE_S3_ENDPOINT}
APP_SECRET: ${APP_SECRET}
ACCESS_TOKEN_SECRET: ${ACCESS_TOKEN_SECRET}
LOGIN_TOKEN_SECRET: ${LOGIN_TOKEN_SECRET}
REFRESH_TOKEN_SECRET: ${REFRESH_TOKEN_SECRET}
FILE_TOKEN_SECRET: ${FILE_TOKEN_SECRET}
depends_on:
change-vol-ownership:
condition: service_completed_successfully
@@ -44,20 +53,26 @@ services:
restart: always
worker:
image: twentycrm/twenty:${TAG:-latest}
image: twentycrm/twenty:${TAG}
command: ["yarn", "worker:prod"]
environment:
PG_DATABASE_URL: postgres://${PG_DATABASE_USER:-postgres}:${PG_DATABASE_PASSWORD:-postgres}@${PG_DATABASE_HOST:-db}:${PG_DATABASE_PORT:-5432}/default
PG_DATABASE_URL: postgres://twenty:twenty@${PG_DATABASE_HOST}/default
SERVER_URL: ${SERVER_URL}
REDIS_URL: ${REDIS_URL:-redis://redis:6379}
DISABLE_DB_MIGRATIONS: "true" # it already runs on the server
FRONT_BASE_URL: ${FRONT_BASE_URL:-$SERVER_URL}
REDIS_PORT: ${REDIS_PORT:-6379}
REDIS_HOST: ${REDIS_HOST:-redis}
ENABLE_DB_MIGRATIONS: "false" # it already runs on the server
STORAGE_TYPE: ${STORAGE_TYPE}
STORAGE_S3_REGION: ${STORAGE_S3_REGION}
STORAGE_S3_NAME: ${STORAGE_S3_NAME}
STORAGE_S3_ENDPOINT: ${STORAGE_S3_ENDPOINT}
APP_SECRET: ${APP_SECRET}
ACCESS_TOKEN_SECRET: ${ACCESS_TOKEN_SECRET}
LOGIN_TOKEN_SECRET: ${LOGIN_TOKEN_SECRET}
REFRESH_TOKEN_SECRET: ${REFRESH_TOKEN_SECRET}
FILE_TOKEN_SECRET: ${FILE_TOKEN_SECRET}
depends_on:
db:
condition: service_healthy
@@ -66,16 +81,13 @@ services:
restart: always
db:
image: twentycrm/twenty-postgres-spilo:${TAG:-latest}
image: twentycrm/twenty-postgres:${TAG}
volumes:
- db-data:/home/postgres/pgdata
- db-data:/bitnami/postgresql
environment:
PGUSER_SUPERUSER: ${PG_DATABASE_USER:-postgres}
PGPASSWORD_SUPERUSER: ${PG_DATABASE_PASSWORD:-postgres}
ALLOW_NOSSL: "true"
SPILO_PROVIDER: "local"
POSTGRES_PASSWORD: ${POSTGRES_ADMIN_PASSWORD}
healthcheck:
test: pg_isready -U ${PG_DATABASE_USER:-postgres} -h localhost -d postgres
test: pg_isready -U twenty -d default
interval: 5s
timeout: 5s
retries: 10
@@ -83,6 +95,8 @@ services:
redis:
image: redis
ports:
- "6379:6379"
restart: always
volumes:
@@ -27,16 +27,12 @@ spec:
claimName: twentycrm-db-pvc
containers:
- name: twentycrm
image: twentycrm/twenty-postgres-spilo:latest
image: twentycrm/twenty-postgres:latest
imagePullPolicy: Always
env:
- name: PGUSER_SUPERUSER
value: "postgres"
- name: PGPASSWORD_SUPERUSER
value: "postgres"
- name: SPILO_PROVIDER
value: "local"
- name: ALLOW_NOSSL
- name: POSTGRES_PASSWORD
value: "twenty"
- name: BITNAMI_DEBUG
value: "true"
ports:
- containerPort: 5432
@@ -52,7 +48,7 @@ spec:
stdin: true
tty: true
volumeMounts:
- mountPath: /home/postgres/pgdata
- mountPath: /bitnami/postgresql
name: twentycrm-db-data
dnsPolicy: ClusterFirst
restartPolicy: Always
@@ -33,27 +33,50 @@ spec:
image: twentycrm/twenty:latest
imagePullPolicy: Always
env:
- name: NODE_PORT
- name: PORT
value: 3000
- name: SERVER_URL
value: "https://crm.example.com:443"
- name: FRONT_BASE_URL
value: "https://crm.example.com:443"
- name: "PG_DATABASE_URL"
value: "postgres://postgres:postgres@twentycrm-db.twentycrm.svc.cluster.local/default"
- name: "REDIS_URL"
value: "redis://twentycrm-redis.twentycrm.svc.cluster.local:6379"
value: "postgres://twenty:twenty@twenty-db.twentycrm.svc.cluster.local/default"
- name: "REDIS_HOST"
value: "twentycrm-redis.twentycrm.svc.cluster.local"
- name: "REDIS_PORT"
value: 6379
- name: ENABLE_DB_MIGRATIONS
value: "true"
- name: SIGN_IN_PREFILLED
value: "false"
value: "true"
- name: STORAGE_TYPE
value: "local"
- name: "MESSAGE_QUEUE_TYPE"
value: "bull-mq"
- name: "ACCESS_TOKEN_EXPIRES_IN"
value: "7d"
- name: "LOGIN_TOKEN_EXPIRES_IN"
value: "1h"
- name: APP_SECRET
- name: ACCESS_TOKEN_SECRET
valueFrom:
secretKeyRef:
name: tokens
key: accessToken
- name: LOGIN_TOKEN_SECRET
valueFrom:
secretKeyRef:
name: tokens
key: loginToken
- name: REFRESH_TOKEN_SECRET
valueFrom:
secretKeyRef:
name: tokens
key: refreshToken
- name: FILE_TOKEN_SECRET
valueFrom:
secretKeyRef:
name: tokens
key: fileToken
ports:
- containerPort: 3000
name: http-tcp
@@ -28,19 +28,42 @@ spec:
env:
- name: SERVER_URL
value: "https://crm.example.com:443"
- name: FRONT_BASE_URL
value: "https://crm.example.com:443"
- name: PG_DATABASE_URL
value: "postgres://postgres:postgres@twentycrm-db.twentycrm.svc.cluster.local/default"
- name: DISABLE_DB_MIGRATIONS
value: "postgres://twenty:twenty@twenty-db.twentycrm.svc.cluster.local/default"
- name: ENABLE_DB_MIGRATIONS
value: "false" # it already runs on the server
- name: STORAGE_TYPE
value: "local"
- name: "REDIS_URL"
value: "redis://twentycrm-redis.twentycrm.svc.cluster.local:6379"
- name: APP_SECRET
- name: "MESSAGE_QUEUE_TYPE"
value: "bull-mq"
- name: "CACHE_STORAGE_TYPE"
value: "redis"
- name: "REDIS_HOST"
value: "twentycrm-redis.twentycrm.svc.cluster.local"
- name: "REDIS_PORT"
value: 6379
- name: ACCESS_TOKEN_SECRET
valueFrom:
secretKeyRef:
name: tokens
key: accessToken
- name: LOGIN_TOKEN_SECRET
valueFrom:
secretKeyRef:
name: tokens
key: loginToken
- name: REFRESH_TOKEN_SECRET
valueFrom:
secretKeyRef:
name: tokens
key: refreshToken
- name: FILE_TOKEN_SECRET
valueFrom:
secretKeyRef:
name: tokens
key: fileToken
command:
- yarn
- worker:prod
@@ -51,7 +51,7 @@ To make configuration changes to how this doc is generated, see `./.terraform-do
| <a name="input_twentycrm_app_hostname"></a> [twentycrm\_app\_hostname](#input\_twentycrm\_app\_hostname) | The protocol, DNS fully qualified hostname, and port used to access TwentyCRM in your environment. Ex: https://crm.example.com:443 | `string` | n/a | yes |
| <a name="input_twentycrm_pgdb_admin_password"></a> [twentycrm\_pgdb\_admin\_password](#input\_twentycrm\_pgdb\_admin\_password) | TwentyCRM password for postgres database. | `string` | n/a | yes |
| <a name="input_twentycrm_app_name"></a> [twentycrm\_app\_name](#input\_twentycrm\_app\_name) | A friendly name prefix to use for every component deployed. | `string` | `"twentycrm"` | no |
| <a name="input_twentycrm_db_image"></a> [twentycrm\_db\_image](#input\_twentycrm\_db\_image) | TwentyCRM image for database deployment. This defaults to latest. | `string` | `"twentycrm/twenty-postgres-spilo:latest"` | no |
| <a name="input_twentycrm_db_image"></a> [twentycrm\_db\_image](#input\_twentycrm\_db\_image) | TwentyCRM image for database deployment. This defaults to latest. | `string` | `"twentycrm/twenty-postgres:latest"` | no |
| <a name="input_twentycrm_db_pv_capacity"></a> [twentycrm\_db\_pv\_capacity](#input\_twentycrm\_db\_pv\_capacity) | Storage capacity provisioned for database persistent volume. | `string` | `"10Gi"` | no |
| <a name="input_twentycrm_db_pv_path"></a> [twentycrm\_db\_pv\_path](#input\_twentycrm\_db\_pv\_path) | Local path to use to store the physical volume if using local storage on nodes. | `string` | `""` | no |
| <a name="input_twentycrm_db_pvc_requests"></a> [twentycrm\_db\_pvc\_requests](#input\_twentycrm\_db\_pvc\_requests) | Storage capacity reservation for database persistent volume claim. | `string` | `"10Gi"` | no |
@@ -38,32 +38,54 @@ resource "kubernetes_deployment" "twentycrm_server" {
tty = true
env {
name = "NODE_PORT"
name = "PORT"
value = "3000"
}
# env {
# name = "DEBUG_MODE"
# value = false
# }
env {
name = "SERVER_URL"
value = var.twentycrm_app_hostname
}
env {
name = "FRONT_BASE_URL"
value = var.twentycrm_app_hostname
}
env {
name = "PG_DATABASE_URL"
value = "postgres://twenty:${var.twentycrm_pgdb_admin_password}@${kubernetes_service.twentycrm_db.metadata.0.name}.${kubernetes_namespace.twentycrm.metadata.0.name}.svc.cluster.local/default"
}
env {
name = "REDIS_URL"
value = "redis://${kubernetes_service.twentycrm_redis.metadata.0.name}.${kubernetes_namespace.twentycrm.metadata.0.name}.svc.cluster.local:6379"
name = "REDIS_HOST"
value = "${kubernetes_service.twentycrm_redis.metadata.0.name}.${kubernetes_namespace.twentycrm.metadata.0.name}.svc.cluster.local"
}
env {
name = "DISABLE_DB_MIGRATIONS"
value = "false"
name = "REDIS_PORT"
value = 6379
}
env {
name = "ENABLE_DB_MIGRATIONS"
value = "true"
}
env {
name = "SIGN_IN_PREFILLED"
value = "true"
}
env {
name = "STORAGE_TYPE"
value = "local"
}
env {
name = "MESSAGE_QUEUE_TYPE"
value = "bull-mq"
}
env {
name = "ACCESS_TOKEN_EXPIRES_IN"
value = "7d"
@@ -73,7 +95,7 @@ resource "kubernetes_deployment" "twentycrm_server" {
value = "1h"
}
env {
name = "APP_SECRET"
name = "ACCESS_TOKEN_SECRET"
value_from {
secret_key_ref {
name = "tokens"
@@ -82,6 +104,36 @@ resource "kubernetes_deployment" "twentycrm_server" {
}
}
env {
name = "LOGIN_TOKEN_SECRET"
value_from {
secret_key_ref {
name = "tokens"
key = "loginToken"
}
}
}
env {
name = "REFRESH_TOKEN_SECRET"
value_from {
secret_key_ref {
name = "tokens"
key = "refreshToken"
}
}
}
env {
name = "FILE_TOKEN_SECRET"
value_from {
secret_key_ref {
name = "tokens"
key = "fileToken"
}
}
}
port {
container_port = 3000
protocol = "TCP"
@@ -43,28 +43,47 @@ resource "kubernetes_deployment" "twentycrm_worker" {
value = var.twentycrm_app_hostname
}
env {
name = "FRONT_BASE_URL"
value = var.twentycrm_app_hostname
}
env {
name = "PG_DATABASE_URL"
value = "postgres://twenty:${var.twentycrm_pgdb_admin_password}@${kubernetes_service.twentycrm_db.metadata.0.name}.${kubernetes_namespace.twentycrm.metadata.0.name}.svc.cluster.local/default"
}
env {
name = "REDIS_URL"
value = "redis://${kubernetes_service.twentycrm_redis.metadata.0.name}.${kubernetes_namespace.twentycrm.metadata.0.name}.svc.cluster.local:6379"
name = "CACHE_STORAGE_TYPE"
value = "redis"
}
env {
name = "DISABLE_DB_MIGRATIONS"
value = "true" #it already runs on the server
name = "REDIS_HOST"
value = "${kubernetes_service.twentycrm_redis.metadata.0.name}.${kubernetes_namespace.twentycrm.metadata.0.name}.svc.cluster.local"
}
env {
name = "REDIS_PORT"
value = 6379
}
env {
name = "ENABLE_DB_MIGRATIONS"
value = "false" #it already runs on the server
}
env {
name = "STORAGE_TYPE"
value = "local"
}
env {
name = "MESSAGE_QUEUE_TYPE"
value = "bull-mq"
}
env {
name = "APP_SECRET"
name = "ACCESS_TOKEN_SECRET"
value_from {
secret_key_ref {
name = "tokens"
@@ -73,6 +92,36 @@ resource "kubernetes_deployment" "twentycrm_worker" {
}
}
env {
name = "LOGIN_TOKEN_SECRET"
value_from {
secret_key_ref {
name = "tokens"
key = "loginToken"
}
}
}
env {
name = "REFRESH_TOKEN_SECRET"
value_from {
secret_key_ref {
name = "tokens"
key = "refreshToken"
}
}
}
env {
name = "FILE_TOKEN_SECRET"
value_from {
secret_key_ref {
name = "tokens"
key = "fileToken"
}
}
}
resources {
requests = {
cpu = "250m"
@@ -29,7 +29,7 @@ variable "twentycrm_server_image" {
variable "twentycrm_db_image" {
type = string
default = "twentycrm/twenty-postgres-spilo:latest"
default = "twentycrm/twenty-postgres:latest"
description = "TwentyCRM image for database deployment. This defaults to latest."
}
-22
View File
@@ -1,22 +0,0 @@
pull_version=${VERSION:-$(curl -s https://api.github.com/repos/twentyhq/twenty/tags | grep '"name":' | head -n 1 | cut -d '"' -f 4)}
if [[ -z "$pull_version" ]]; then
echo "Error: Unable to fetch the latest version tag. Please check your network connection or the GitHub API response."
exit 1
fi
pull_branch=${BRANCH:-$pull_version}
version_num=${pull_version#v}
target_version="0.32.4"
# We moved the install script to a different location in v0.32.4
if [[ -n "$BRANCH" ]] || [[ "$(printf '%s\n' "$target_version" "$version_num" | sort -V | head -n1)" != "$version_num" ]]; then
curl -sL "https://raw.githubusercontent.com/twentyhq/twenty/$pull_branch/packages/twenty-docker/scripts/install.sh" -o twenty_install.sh
else
curl -sL "https://raw.githubusercontent.com/twentyhq/twenty/$pull_branch/install.sh" -o twenty_install.sh
fi
chmod +x twenty_install.sh
VERSION="$VERSION" BRANCH="$BRANCH" ./twenty_install.sh
rm twenty_install.sh
@@ -1,12 +1,13 @@
ARG POSTGRES_VERSION=15
ARG SPILO_VERSION=3.2-p1
ARG PG_GRAPHQL_VERSION=1.5.6
ARG WRAPPERS_VERSION=0.2.0
# Build the mysql_fdw extension
FROM debian:bookworm AS build-mysql_fdw
FROM debian:bookworm as build-mysql_fdw
ARG POSTGRES_VERSION
ENV DEBIAN_FRONTEND=noninteractive
ENV DEBIAN_FRONTEND noninteractive
RUN apt update && \
apt install -y \
build-essential \
@@ -17,14 +18,14 @@ RUN apt update && \
# Install mysql_fdw
RUN git clone https://github.com/EnterpriseDB/mysql_fdw.git
WORKDIR /mysql_fdw
WORKDIR mysql_fdw
RUN make USE_PGXS=1
# Build libssl for wrappers
FROM ubuntu:22.04 AS build-libssl
FROM ubuntu:22.04 as build-libssl
ENV DEBIAN_FRONTEND=noninteractive
ENV DEBIAN_FRONTEND noninteractive
RUN apt update && \
apt install -y \
build-essential \
@@ -37,9 +38,10 @@ WORKDIR /build/openssl
RUN ./config && make && make install
# Extend the Spilo image with the mysql_fdw extensions
# Extend the Spilo image with the pg_graphql and mysql_fdw extensions
FROM ghcr.io/zalando/spilo-${POSTGRES_VERSION}:${SPILO_VERSION}
ARG POSTGRES_VERSION
ARG PG_GRAPHQL_VERSION
ARG WRAPPERS_VERSION
ARG TARGETARCH
@@ -61,6 +63,14 @@ RUN curl -L "https://github.com/supabase/wrappers/releases/download/v${WRAPPERS_
COPY --from=build-libssl /usr/local/lib/libssl* /usr/local/lib/libcrypto* /usr/lib/
COPY --from=build-libssl /usr/local/lib/engines-1.1 /usr/lib/engines-1.1
# Copy pg_graphql
COPY ./packages/twenty-postgres/linux/${TARGETARCH}/${POSTGRES_VERSION}/pg_graphql/${PG_GRAPHQL_VERSION}/pg_graphql--${PG_GRAPHQL_VERSION}.sql \
/usr/share/postgresql/${POSTGRES_VERSION}/extension
COPY ./packages/twenty-postgres/linux/${TARGETARCH}/${POSTGRES_VERSION}/pg_graphql/${PG_GRAPHQL_VERSION}/pg_graphql.control \
/usr/share/postgresql/${POSTGRES_VERSION}/extension
COPY ./packages/twenty-postgres/linux/${TARGETARCH}/${POSTGRES_VERSION}/pg_graphql/${PG_GRAPHQL_VERSION}/pg_graphql.so \
/usr/lib/postgresql/${POSTGRES_VERSION}/lib/pg_graphql.so
# Copy mysql_fdw
COPY --from=build-mysql_fdw /mysql_fdw/mysql_fdw.so \
/usr/lib/postgresql/${POSTGRES_VERSION}/lib/mysql_fdw.so
@@ -0,0 +1,54 @@
ARG IMAGE_TAG='15.5.0-debian-11-r15'
FROM bitnami/postgresql:${IMAGE_TAG}
ARG PG_MAIN_VERSION=15
ARG PG_GRAPHQL_VERSION=1.5.6
ARG WRAPPERS_VERSION=0.2.0
ARG TARGETARCH
USER root
RUN set -eux; \
ARCH="$(dpkg --print-architecture)"; \
case "${ARCH}" in \
aarch64|arm64) \
TARGETARCH='arm64'; \
;; \
amd64|x86_64) \
TARGETARCH='amd64'; \
;; \
*) \
echo "Unsupported arch: ${ARCH}"; \
exit 1; \
;; \
esac;
RUN apt update && apt install build-essential git curl default-libmysqlclient-dev -y
# Install precompiled pg_graphql extensions
COPY ./packages/twenty-postgres/linux/${TARGETARCH}/${PG_MAIN_VERSION}/pg_graphql/${PG_GRAPHQL_VERSION}/pg_graphql--${PG_GRAPHQL_VERSION}.sql \
/opt/bitnami/postgresql/share/extension/
COPY ./packages/twenty-postgres/linux/${TARGETARCH}/${PG_MAIN_VERSION}/pg_graphql/${PG_GRAPHQL_VERSION}/pg_graphql.control \
/opt/bitnami/postgresql/share/extension/
COPY ./packages/twenty-postgres/linux/${TARGETARCH}/${PG_MAIN_VERSION}/pg_graphql/${PG_GRAPHQL_VERSION}/pg_graphql.so \
/opt/bitnami/postgresql/lib/
# Install precompiled supabase wrappers extensions
RUN curl -L "https://github.com/supabase/wrappers/releases/download/v${WRAPPERS_VERSION}/wrappers-v${WRAPPERS_VERSION}-pg${PG_MAIN_VERSION}-${TARGETARCH}-linux-gnu.deb" -o wrappers.deb
RUN dpkg --install wrappers.deb
RUN cp /usr/share/postgresql/${PG_MAIN_VERSION}/extension/wrappers* /opt/bitnami/postgresql/share/extension/
RUN cp /usr/lib/postgresql/${PG_MAIN_VERSION}/lib/wrappers* /opt/bitnami/postgresql/lib/
RUN export PATH=/usr/local/pgsql/bin/:$PATH
RUN export PATH=/usr/local/mysql/bin/:$PATH
RUN git clone https://github.com/EnterpriseDB/mysql_fdw.git
WORKDIR mysql_fdw
RUN make USE_PGXS=1
RUN make USE_PGXS=1 install
COPY ./packages/twenty-docker/twenty-postgres/init.sql /docker-entrypoint-initdb.d/
USER 1001
ENTRYPOINT ["/opt/bitnami/scripts/postgresql/entrypoint.sh"]
CMD ["/opt/bitnami/scripts/postgresql/run.sh"]
@@ -0,0 +1,4 @@
CREATE DATABASE "default";
CREATE DATABASE "test";
CREATE USER twenty PASSWORD 'twenty';
ALTER ROLE twenty superuser;
+2 -6
View File
@@ -11,7 +11,6 @@ COPY ./packages/twenty-emails/package.json /app/packages/twenty-emails/
COPY ./packages/twenty-server/package.json /app/packages/twenty-server/
COPY ./packages/twenty-server/patches /app/packages/twenty-server/patches
COPY ./packages/twenty-ui/package.json /app/packages/twenty-ui/
COPY ./packages/twenty-shared/package.json /app/packages/twenty-shared/
COPY ./packages/twenty-front/package.json /app/packages/twenty-front/
# Install all dependencies
@@ -23,7 +22,6 @@ FROM common-deps as twenty-server-build
# Copy sourcecode after installing dependences to accelerate subsequents builds
COPY ./packages/twenty-emails /app/packages/twenty-emails
COPY ./packages/twenty-shared /app/packages/twenty-shared
COPY ./packages/twenty-server /app/packages/twenty-server
RUN npx nx run twenty-server:build
@@ -33,7 +31,7 @@ RUN mv /app/packages/twenty-server/dist/package.json /app/packages/twenty-server
RUN rm -rf /app/packages/twenty-server/dist
RUN mv /app/packages/twenty-server/build /app/packages/twenty-server/dist
RUN yarn workspaces focus --production twenty-emails twenty-shared twenty-server
RUN yarn workspaces focus --production twenty-emails twenty-server
# Build the front
@@ -43,7 +41,6 @@ ARG REACT_APP_SERVER_BASE_URL
COPY ./packages/twenty-front /app/packages/twenty-front
COPY ./packages/twenty-ui /app/packages/twenty-ui
COPY ./packages/twenty-shared /app/packages/twenty-shared
RUN npx nx build twenty-front
@@ -55,10 +52,9 @@ RUN apk add --no-cache curl jq
RUN npm install -g tsx
RUN apk add --no-cache postgresql-client
COPY ./packages/twenty-docker/twenty/entrypoint.sh /app/entrypoint.sh
RUN chmod +x /app/entrypoint.sh
WORKDIR /app/packages/twenty-server
ARG REACT_APP_SERVER_BASE_URL
+1 -10
View File
@@ -1,18 +1,9 @@
#!/bin/sh
set -e
# Check if the initialization has already been done and that we enabled automatic migration
if [ "${DISABLE_DB_MIGRATIONS}" != "true" ] && [ ! -f /app/docker-data/db_status ]; then
if [ "${ENABLE_DB_MIGRATIONS}" = "true" ] && [ ! -f /app/docker-data/db_status ]; then
echo "Running database setup and migrations..."
# Creating the database if it doesn't exist
PGUSER=$(echo $PG_DATABASE_URL | awk -F '//' '{print $2}' | awk -F ':' '{print $1}')
PGPASS=$(echo $PG_DATABASE_URL | awk -F ':' '{print $3}' | awk -F '@' '{print $1}')
PGHOST=$(echo $PG_DATABASE_URL | awk -F '@' '{print $2}' | awk -F ':' '{print $1}')
PGPORT=$(echo $PG_DATABASE_URL | awk -F ':' '{print $4}' | awk -F '/' '{print $1}')
PGPASSWORD=${PGPASS} psql -h ${PGHOST} -p ${PGPORT} -U ${PGUSER} -d postgres -tc "SELECT 1 FROM pg_database WHERE datname = 'default'" | grep -q 1 || \
PGPASSWORD=${PGPASS} psql -h ${PGHOST} -p ${PGPORT} -U ${PGUSER} -d postgres -c "CREATE DATABASE \"default\""
# Run setup and migration scripts
NODE_OPTIONS="--max-old-space-size=1500" tsx ./scripts/setup-db.ts
yarn database:migrate:prod
+3 -1
View File
@@ -1,7 +1,9 @@
# Note that provide always without trailing forward slash to have expected behaviour
FRONTEND_BASE_URL=http://localhost:3001
BACKEND_BASE_URL=http://localhost:3000
CI_DEFAULT_BASE_URL=https://demo.twenty.com
DEFAULT_LOGIN=tim@apple.dev
NEW_WORKSPACE_LOGIN=test@apple.dev
DEMO_DEFAULT_LOGIN=noah@demo.dev
DEFAULT_PASSWORD=Applecar2025
WEBSITE_URL=https://twenty.com
+12 -16
View File
@@ -1,43 +1,39 @@
# Twenty end-to-end (E2E) Testing
# Twenty e2e Testing
## Prerequisite
Installing the browsers:
```
npx nx setup twenty-e2e-testing
yarn playwright install
```
### Run end-to-end tests
```
npx nx test twenty-e2e-testing
yarn run test:e2e
```
### Start the interactive UI mode
```
npx nx test:ui twenty-e2e-testing
yarn run test:e2e:ui
```
### Run test only on Desktop Chrome
```
yarn run test:e2e:chrome
```
### Run test in specific file
```
npx nx test twenty-e2e-testing <filename>
```
Example (location of the test must be specified from the root of `twenty-e2e-testing` package):
```
npx nx test twenty-e2e-testing tests/login.spec.ts
yarn run test:e2e <filename>
```
### Runs the tests in debug mode.
```
npx nx test:debug twenty-e2e-testing
```
### Show report after tests
```
npx nx test:report twenty-e2e-testing
yarn run test:e2e:debug
```
## Q&A
@@ -0,0 +1,33 @@
import {
Reporter,
FullConfig,
Suite,
TestCase,
TestResult,
FullResult,
} from '@playwright/test/reporter';
class CustomReporter implements Reporter {
constructor(options: { customOption?: string } = {}) {
console.log(
`my-awesome-reporter setup with customOption set to ${options.customOption}`,
);
}
onBegin(config: FullConfig, suite: Suite) {
console.log(`Starting the run with ${suite.allTests().length} tests`);
}
onTestBegin(test: TestCase) {
console.log(`Starting test ${test.title}`);
}
onTestEnd(test: TestCase, result: TestResult) {
console.log(`Finished test ${test.title}: ${result.status}`);
}
onEnd(result: FullResult) {
console.log(`Finished the run: ${result.status}`);
}
}
export default CustomReporter;
@@ -0,0 +1,13 @@
import { exec } from 'child_process';
export async function sh(cmd) {
return new Promise((resolve, reject) => {
exec(cmd, (err, stdout, stderr) => {
if (err) {
reject(err);
} else {
resolve({ stdout, stderr });
}
});
});
}

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