Compare commits

..
4 Commits
Author SHA1 Message Date
Charles Bochet cad56e1df5 Fix 2024-08-05 23:18:06 +02:00
Charles Bochet 6752d43d36 Fix 2024-08-05 23:11:57 +02:00
Charles Bochet cbb1454ef8 Fix 2024-08-05 23:05:12 +02:00
Charles Bochet 72a69d5bd7 Release 0.23 2024-08-05 19:59:59 +02:00
6493 changed files with 124564 additions and 412361 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 -19
View File
@@ -1,16 +1,8 @@
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: {
'lingui/no-single-variables-to-translate': 'off',
'func-style': ['error', 'declaration', { allowArrowFunctions: true }],
'no-console': ['warn', { allow: ['group', 'groupCollapsed', 'groupEnd'] }],
'no-control-regex': 0,
@@ -37,10 +29,6 @@ module.exports = {
sourceTag: 'scope:frontend',
onlyDependOnLibsWithTags: ['scope:shared', 'scope:frontend'],
},
{
sourceTag: 'scope:zapier',
onlyDependOnLibsWithTags: ['scope:shared'],
},
],
},
],
@@ -82,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',
{
@@ -107,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,
},
+1 -1
View File
@@ -2,7 +2,7 @@
## Twenty's Pledge
The contributors and maintainers of this project pledge to ensure a harassment-free experience for everyone in the community.
The contributors and maintainers of this project pledge to ensure a harassment-free experience for everyone in the community. This commitment applies to individuals of all backgrounds, including age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity, experience, education, socio-economic status, nationality, appearance, race, religion. It also applies to individuals of all sexual identities and orientations.
The focus of both contributors and maintainers is on acting and interacting in ways that promote an open, welcoming, friendly, diverse, inclusive, and healthy community.
+42 -17
View File
@@ -1,38 +1,47 @@
# Contributing to Twenty
Thanks for considering contributing to Twenty!
Please make sure to go through the [documentation](https://docs.twenty.com) before.
Thank you for considering contributing to Twenty! All community contributions are welcome.
This guide outlines the process for contributing to this project. Please make sure to go through the [documentation](https://docs.twenty.com) before making your contribution.
> And if you like the project, but just don't have time to contribute, that's fine. There are other easy ways to support the project and show your appreciation:
> - Star the project
> - Tweet about it
<br>
## Good first issues
## Getting Started
Good first issues are a great way to start contributing and get familiar with the codebase. You can find them on by filtering on the [good first issue](https://github.com/twentyhq/twenty/labels/good%20first%20issue) label.
Good first issues are a great way to start contributing to the project and get familiar with the codebase. Here's how to find them:
## Issue assignment
1. Visit the "[Issues](https://github.com/twentyhq/twenty/issues)" tab on the main [repository](https://github.com/twentyhq/twenty).
2. Use the "Labels" filter and select "[Good First Issue](https://github.com/twentyhq/twenty/labels/good%20first%20issue)" to see a list of beginner-friendly tasks.
3. Choose an issue that interests you, fork the project, and start working on it. Once you solve and test the issue, open a PR for review.
Having multiple contributors address the same issue can cause frustration.
Note: We are aware that having multiple contributors address the same issue can cause frustration. To prevent this, we adhere to a specific guideline: if a core team member has assigned an issue to a contributor, either as the issue assignee or through explicit assignment in the issue comments within the past three days, that contributor's pull request takes precedence. Otherwise, the first PR submitted will be given priority. This delay is reduced to one day for PR tagged with "size: minutes" and extended to a week for PR tagged "size: days".
To avoid conflicts, we follow these guidelines:
1. If a core team member assigned you the issue within the last three days, your PR takes priority.
2. Otherwise, the first submitted PR is prioritized.
3. For "size: long" PRs, the assignment period extends to one week.
Therefore, ensure you are assigned to an issue before beginning work on it.
Please ensure you're assigned to an issue before starting work.
<br>
## Contributing Guidelines
## How to Contribute
1. **Fork the Repository:** Click on the 'Fork' button in the upper right corner of the repository's GitHub page. This will create a copy of the repository in your GitHub account.
2. **Clone the Repository:** Clone your forked repository to your local machine using `git clone`.
```shell
git clone https://github.com/yourusername/twenty.git
cd twenty
```
3. **Create a New Branch:** Create a new branch for your changes instead of using the main branch.
```shell
@@ -47,24 +56,40 @@ git checkout -b your-branch-name
6. **Commit Changes:** Commit your changes with a clear and concise commit message.
```shell
git commit -m "Add your detailed description here"
```
7. **Push Changes:** Push your changes to your forked repository.
```shell
git push origin your-branch-name
```
8. **Create a Pull Request:** Go to the original Twenty repository and create a pull request. Please provide a detailed description of your changes. Submitting a PR means you agree to the CLA.
9. **Code Review:** Your pull request will undergo a code review.
8. **Create a Pull Request:** Go to the original Twenty repository and create a pull request. Please provide a detailed description of your changes. To have your pull request accepted, you must sign a CLA.
9. **Code Review:** Your pull request will undergo a code review. Note that you might need to make any necessary adjustments based on feedback.
10. **Merge:** Once approved, maintainers will merge your pull request into the main repository.
<br>
## Code of Conduct
Please note that by contributing to this project, you're expected to follow Twenty's [Code of Conduct](./CODE_OF_CONDUCT.md). All maintainers strive to maintain a welcoming, friendly, and inclusive community for all contributors.
<br>
## Reporting Issues
If you face any issues or have suggestions, please feel free to (create an issue on Twenty's GitHub repository)[https://github.com/twentyhq/twenty/issues/new]. Please provide as much detail as possible.
If you encounter any issues or have suggestions for improvements, please feel free to (create an issue on Twenty's GitHub repository)[https://github.com/twentyhq/twenty/issues/new]. When reporting issues, please provide as much detail as possible to help in understanding and addressing the problem effectively.
---
Thank you for considering contributing to Twenty. Your contributions help make Twenty's CRM platform even better!
@@ -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 -107
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,62 @@ 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 +95,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 +108,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 +125,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 +139,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:
+21 -184
View File
@@ -3,51 +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
server-test:
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:
- 6379:6379
steps:
- name: Fetch custom Github Actions and base branch history
uses: actions/checkout@v4
@@ -55,167 +36,23 @@ 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 Tasks 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:
tag: scope:backend
tasks: lint,typecheck
- name: Server / Build
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 }}
server-test:
timeout-minutes: 30
runs-on: depot-ubuntu-24.04-8
needs: server-setup
env:
NX_REJECT_UNKNOWN_LOCAL_CACHE: 0
steps:
- 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: Restore server setup
uses: ./.github/workflows/actions/restore-cache
with:
key: ${{ env.SERVER_SETUP_CACHE_KEY }}
- name: Server / Run Tests
- 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
needs: server-setup
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
env:
NX_REJECT_UNKNOWN_LOCAL_CACHE: 0
steps:
- 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: 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
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
env:
NODE_ENV: test
run: |
PGPASSWORD=postgres psql -h localhost -p 5432 -U postgres -d postgres -c 'CREATE DATABASE "test";'
- name: Server / Run Integration Tests
uses: ./.github/workflows/actions/nx-affected
with:
tag: scope:backend
tasks: 'test:integration:with-db-reset'
- 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
- name: Server / Write .env
run: npx nx reset:env twenty-server
- name: Worker / Run
run: MESSAGE_QUEUE_TYPE=sync npx nx worker twenty-server
-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
-136
View File
@@ -1,136 +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
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: false
skip_ref_checkout: true
dryrun_action: false
env:
GITHUB_TOKEN: ${{ github.token }}
CROWDIN_PROJECT_ID: '1'
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
# As the files are extracted from a Docker container, they belong to root:root
# We need to fix this before the next steps
- name: Fix file permissions
run: sudo chown -R runner:docker .
- 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: Debug git status
run: git status
- 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"
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: |
if git diff --name-only origin/main..HEAD | grep -q .; then
gh pr create -B main -H i18n --title 'i18n - translations' --body 'Created by Github action' || true
else
echo "No file differences between branches, skipping PR creation"
fi
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-103
View File
@@ -1,103 +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: Check and commit extracted files
id: check_extract_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: 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 compiled files
id: check_compile_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"
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_extract_changes.outputs.changes_detected == 'true' || steps.check_compile_changes.outputs.changes_detected == 'true'
run: git push origin HEAD:i18n
- name: Upload missing translations
if: steps.check_extract_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_extract_changes.outputs.changes_detected == 'true' || steps.check_compile_changes.outputs.changes_detected == 'true'
run: |
if git diff --name-only origin/main..HEAD | grep -q .; then
gh pr create -B main -H i18n --title 'i18n - translations' --body 'Created by Github action' || true
else
echo "No file differences between branches, skipping PR creation"
fi
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+1 -16
View File
@@ -18,7 +18,6 @@
!.yarn/sdks
!.yarn/versions
.vercel
.swc
**/**/logs/**
@@ -27,19 +26,5 @@ dist
storybook-static
*.tsbuildinfo
.eslintcache
.cache
.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 -10
View File
@@ -21,12 +21,8 @@
"path": "../packages/twenty-ui"
},
{
"name": "packages/twenty-emails",
"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 +40,6 @@
"name": "tools/eslint-rules",
"path": "../tools/eslint-rules"
},
{
"name": "packages/twenty-e2e-testing",
"path": "../packages/twenty-e2e-testing"
}
],
"settings": {
"editor.formatOnSave": false,
+894
View File
File diff suppressed because one or more lines are too long
-925
View File
File diff suppressed because one or more lines are too long
+1 -3
View File
@@ -1,7 +1,5 @@
enableHardenedMode: true
enableInlineHunks: true
nodeLinker: node-modules
yarnPath: .yarn/releases/yarn-4.4.0.cjs
yarnPath: .yarn/releases/yarn-4.3.1.cjs
-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
+32 -24
View File
@@ -7,8 +7,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 +22,44 @@
</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:
```
email: noah@demo.dev
password: Applecar2025
```
See:
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)
@@ -55,7 +67,6 @@ Below are a few features we have implemented to date:
+ [Create tasks on records](#create-tasks-on-records)
+ [Navigate quickly through the app using keyboard shortcuts and search](#navigate-quickly-through-the-app-using-keyboard-shortcuts-and-search)
## Add, filter, sort, edit, and track customers:
<p align="center">
@@ -136,27 +147,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"
@@ -110,10 +112,8 @@ if command -v nc &> /dev/null; then
read -p "Enter a new port number: " new_port
if [[ $(uname) == "Darwin" ]]; then
sed -i '' "s/$port:$port/$new_port:$port/g" docker-compose.yml
sed -E -i '' "s|^SERVER_URL=http://localhost:[0-9]+|SERVER_URL=http://localhost:$new_port|g" .env
else
sed -i'' "s/$port:$port/$new_port:$port/g" docker-compose.yml
sed -E -i'' "s|^SERVER_URL=http://localhost:[0-9]+|SERVER_URL=http://localhost:$new_port|g" .env
fi
port=$new_port
done
+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
}
},
+39 -47
View File
@@ -5,13 +5,16 @@
"@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/core": "^0.15.3",
"@blocknote/mantine": "^0.15.3",
"@blocknote/react": "^0.15.3",
"@chakra-ui/accordion": "^2.3.0",
"@chakra-ui/system": "^2.6.0",
"@codesandbox/sandpack-react": "^2.13.5",
"@dagrejs/dagre": "^1.1.2",
"@docusaurus/core": "^3.1.0",
"@docusaurus/preset-classic": "^3.1.0",
"@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0",
"@envelop/on-resolve": "^4.1.0",
@@ -24,10 +27,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",
@@ -39,19 +39,23 @@
"@nestjs/passport": "^9.0.3",
"@nestjs/platform-express": "^9.0.0",
"@nestjs/serve-static": "^4.0.1",
"@nestjs/terminus": "^11.0.0",
"@nestjs/terminus": "^9.2.2",
"@nestjs/typeorm": "^10.0.0",
"@nivo/calendar": "^0.84.0",
"@nivo/core": "^0.84.0",
"@nx/eslint-plugin": "^17.2.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",
"@sentry/node": "^8",
"@sentry/profiling-node": "^8",
"@sentry/react": "^8",
"@react-email/components": "0.0.12",
"@react-email/render": "0.0.10",
"@sentry/node": "^7.99.0",
"@sentry/profiling-node": "^1.3.4",
"@sentry/react": "^7.88.0",
"@sentry/tracing": "^7.99.0",
"@sniptt/guards": "^0.2.0",
"@stoplight/elements": "^8.0.5",
"@storybook/icons": "^1.2.9",
"@swc/jest": "^0.2.29",
"@tabler/icons-react": "^2.44.0",
"@types/dompurify": "^3.0.5",
@@ -63,37 +67,37 @@
"@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",
"apollo-server-express": "^3.12.0",
"apollo-upload-client": "^17.0.0",
"archiver": "^7.0.1",
"aws-sdk": "^2.1658.0",
"axios": "^1.6.2",
"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",
"dataloader": "^2.2.2",
"date-fns": "^2.30.0",
"date-fns-tz": "^2.0.0",
"debounce": "^2.0.0",
"deep-equal": "^2.2.2",
"docusaurus-node-polyfills": "^1.0.0",
"dompurify": "^3.0.11",
"dotenv-cli": "^7.2.1",
"drizzle-orm": "^0.29.3",
"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",
"fs-extra": "^11.2.0",
"googleapis": "105",
"graphiql": "^3.1.1",
"graphql": "16.8.0",
@@ -119,7 +123,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 +170,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,12 +187,12 @@
"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",
"tsup": "^8.0.1",
"type-fest": "4.10.1",
"typeorm": "^0.3.20",
"typescript": "5.3.3",
"use-context-selector": "^2.0.0",
"use-debounce": "^10.0.0",
@@ -202,15 +206,13 @@
"@babel/preset-react": "^7.14.5",
"@babel/preset-typescript": "^7.24.6",
"@crxjs/vite-plugin": "^1.0.14",
"@docusaurus/module-type-aliases": "^3.1.0",
"@docusaurus/tsconfig": "3.1.0",
"@graphql-codegen/cli": "^3.3.1",
"@graphql-codegen/client-preset": "^4.1.0",
"@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",
@@ -223,7 +225,6 @@
"@nx/storybook": "18.3.3",
"@nx/vite": "18.3.3",
"@nx/web": "18.3.3",
"@playwright/test": "^1.46.0",
"@sentry/types": "^7.109.0",
"@storybook/addon-actions": "^7.6.3",
"@storybook/addon-coverage": "^1.0.0",
@@ -233,7 +234,6 @@
"@storybook/addon-onboarding": "^1.0.10",
"@storybook/blocks": "^7.6.3",
"@storybook/core-server": "7.6.3",
"@storybook/icons": "^1.2.9",
"@storybook/jest": "^0.2.3",
"@storybook/react": "^7.6.3",
"@storybook/react-vite": "^7.6.3",
@@ -243,7 +243,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 +255,13 @@
"@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",
@@ -280,7 +278,6 @@
"@types/node": "18.19.26",
"@types/passport-google-oauth20": "^2.0.11",
"@types/passport-jwt": "^3.0.8",
"@types/pluralize": "^0.0.33",
"@types/react": "^18.2.39",
"@types/react-datepicker": "^6.2.0",
"@types/react-dom": "^18.2.15",
@@ -299,16 +296,14 @@
"danger": "^11.3.0",
"dotenv-cli": "^7.2.1",
"drizzle-kit": "^0.20.14",
"esbuild": "^0.23.0",
"esbuild": "^0.20.2",
"eslint": "^8.53.0",
"eslint-config-next": "14.0.4",
"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",
@@ -325,7 +320,7 @@
"msw": "^2.0.11",
"msw-storybook-addon": "2.0.0--canary.122.b3ed3b1.0",
"nx": "18.3.3",
"playwright": "^1.46.0",
"playwright": "^1.40.1",
"prettier": "^3.1.1",
"raw-loader": "^4.0.2",
"rimraf": "^5.0.5",
@@ -333,14 +328,14 @@
"storybook": "^7.6.3",
"storybook-addon-cookie": "^3.2.0",
"storybook-addon-pseudo-states": "^2.1.2",
"storybook-dark-mode": "^3.0.3",
"storybook-dark-mode": "^4.0.1",
"supertest": "^6.1.3",
"ts-jest": "^29.1.1",
"ts-loader": "^9.2.3",
"ts-node": "10.9.1",
"tsconfig-paths": "^4.2.0",
"tsx": "^4.17.0",
"vite": "^5.4.0",
"tsx": "^4.7.2",
"vite": "^5.0.0",
"vite-plugin-checker": "^0.6.2",
"vite-plugin-dts": "3.8.1",
"vite-plugin-svgr": "^4.2.0",
@@ -353,17 +348,16 @@
},
"license": "AGPL-3.0",
"name": "twenty",
"packageManager": "yarn@4.4.0",
"packageManager": "yarn@4.3.1",
"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": [
@@ -375,8 +369,6 @@
"packages/twenty-utils",
"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,
@@ -2527,7 +2527,6 @@ export enum FieldMetadataType {
Number = 'NUMBER',
Numeric = 'NUMERIC',
Phone = 'PHONE',
Phones = 'PHONES',
Position = 'POSITION',
Rating = 'RATING',
RawJson = 'RAW_JSON',
@@ -7456,7 +7455,7 @@ export type Workspace = {
billingSubscriptions?: Maybe<Array<BillingSubscription>>;
createdAt: Scalars['DateTime'];
currentBillingSubscription?: Maybe<BillingSubscription>;
metadataVersion?: Maybe<Scalars['String']>;
currentCacheVersion?: Maybe<Scalars['String']>;
deletedAt?: Maybe<Scalars['DateTime']>;
displayName?: Maybe<Scalars['String']>;
domainName?: Maybe<Scalars['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';
@@ -15,7 +15,7 @@ type ContainerProps = {
const StyledContainer = styled.div<ContainerProps>`
align-items: center;
background-color: ${({ theme, isOn, color }) =>
isOn ? (color ?? theme.color.blue) : theme.background.quaternary};
isOn ? color ?? theme.color.blue : theme.background.quaternary};
border-radius: 10px;
cursor: pointer;
display: flex;
@@ -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 */
+13 -7
View File
@@ -1,19 +1,25 @@
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
# Uncoment if you are serving your front on another server than the API (eg. bucket)
# FRONT_BASE_URL=http://localhost:3000
# 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
# STORAGE_S3_REGION=eu-west3
# STORAGE_S3_NAME=my-bucket
# STORAGE_S3_ENDPOINT=
MESSAGE_QUEUE_TYPE=pg-boss
+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 -
+27 -23
View File
@@ -1,3 +1,4 @@
version: "3.9"
name: twenty
services:
@@ -13,24 +14,30 @@ 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}
MESSAGE_QUEUE_TYPE: ${MESSAGE_QUEUE_TYPE}
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 +51,24 @@ 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}
MESSAGE_QUEUE_TYPE: ${MESSAGE_QUEUE_TYPE}
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,25 +77,18 @@ 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
restart: always
redis:
image: redis
restart: always
volumes:
docker-data:
db-data:
@@ -22,37 +22,33 @@ spec:
app: twentycrm-db
spec:
volumes:
- name: twentycrm-db-data
persistentVolumeClaim:
claimName: twentycrm-db-pvc
- name: twentycrm-db-data
persistentVolumeClaim:
claimName: twentycrm-db-pvc
containers:
- name: twentycrm
image: twentycrm/twenty-postgres-spilo:latest
imagePullPolicy: Always
env:
- name: PGUSER_SUPERUSER
value: "postgres"
- name: PGPASSWORD_SUPERUSER
value: "postgres"
- name: SPILO_PROVIDER
value: "local"
- name: ALLOW_NOSSL
value: "true"
ports:
- containerPort: 5432
name: tcp
protocol: TCP
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "1024Mi"
cpu: "1000m"
stdin: true
tty: true
volumeMounts:
- mountPath: /home/postgres/pgdata
name: twentycrm-db-data
- env:
- name: POSTGRES_PASSWORD
value: "twenty"
- name: BITNAMI_DEBUG
value: "true"
- image: twentycrm/twenty-postgres:latest
imagePullPolicy: Always
name: twentycrm
ports:
- containerPort: 5432
name: tcp
protocol: TCP
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "1024Mi"
cpu: "1000m"
stdin: true
tty: true
volumeMounts:
- mountPath: /bitnami/postgresql
name: twentycrm-db-data
dnsPolicy: ClusterFirst
restartPolicy: Always
@@ -1,44 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: twentycrm-redis
name: twentycrm-redis
namespace: twentycrm
spec:
progressDeadlineSeconds: 600
replicas: 1
strategy:
rollingUpdate:
maxSurge: 1
maxUnavailable: 1
type: RollingUpdate
selector:
matchLabels:
app: twentycrm-redis
template:
metadata:
labels:
app: twentycrm-redis
spec:
containers:
- name: redis
image: redis/redis-stack-server:latest
imagePullPolicy: Always
env:
- name: PORT
value: 6379
ports:
- containerPort: 6379
name: redis
protocol: TCP
resources:
requests:
memory: "1024Mi"
cpu: "250m"
limits:
memory: "2048Mi"
cpu: "500m"
dnsPolicy: ClusterFirst
restartPolicy: Always
@@ -22,55 +22,61 @@ spec:
app: twentycrm-server
spec:
volumes:
- name: twentycrm-server-data
persistentVolumeClaim:
claimName: twentycrm-server-pvc
- name: twentycrm-docker-data
persistentVolumeClaim:
claimName: twentycrm-docker-data-pvc
- name: twentycrm-server-data
persistentVolumeClaim:
claimName: twentycrm-server-pvc
containers:
- name: twentycrm
image: twentycrm/twenty:latest
imagePullPolicy: Always
env:
- name: NODE_PORT
value: 3000
- name: SERVER_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"
- name: SIGN_IN_PREFILLED
value: "false"
- name: STORAGE_TYPE
value: "local"
- name: "ACCESS_TOKEN_EXPIRES_IN"
value: "7d"
- name: "LOGIN_TOKEN_EXPIRES_IN"
value: "1h"
- name: APP_SECRET
valueFrom:
secretKeyRef:
name: tokens
key: accessToken
ports:
- containerPort: 3000
name: http-tcp
protocol: TCP
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "1024Mi"
cpu: "1000m"
stdin: true
tty: true
volumeMounts:
- mountPath: /app/docker-data
name: twentycrm-docker-data
- mountPath: /app/packages/twenty-server/.local-storage
name: twentycrm-server-data
- env:
- name: PORT
value: 3000
- name: SERVER_URL
value: "https://crm.example.com:443"
- name: PG_DATABASE_URL
value: "postgres://twenty:twenty@twenty-db.twentycrm.svc.cluster.local/default"
- name: ENABLE_DB_MIGRATIONS
value: "true"
- name: SIGN_IN_PREFILLED
value: "true"
- name: STORAGE_TYPE
value: "local"
- 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
- image: twentycrm/twenty:latest
imagePullPolicy: Always
name: twentycrm
ports:
- containerPort: 3000
name: http-tcp
protocol: TCP
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "1024Mi"
cpu: "1000m"
stdin: true
tty: true
volumeMounts:
- mountPath: /app/.local-storage
name: twentycrm-server-data
dnsPolicy: ClusterFirst
restartPolicy: Always
@@ -1,57 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: twentycrm-worker
name: twentycrm-worker
namespace: twentycrm
spec:
progressDeadlineSeconds: 600
replicas: 1
strategy:
rollingUpdate:
maxSurge: 1
maxUnavailable: 1
type: RollingUpdate
selector:
matchLabels:
app: twentycrm-worker
template:
metadata:
labels:
app: twentycrm-worker
spec:
containers:
- name: twentycrm
image: twentycrm/twenty:latest
imagePullPolicy: Always
env:
- name: SERVER_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: "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
valueFrom:
secretKeyRef:
name: tokens
key: accessToken
command:
- yarn
- worker:prod
resources:
requests:
memory: "1024Mi"
cpu: "250m"
limits:
memory: "2048Mi"
cpu: "1000m"
stdin: true
tty: true
dnsPolicy: ClusterFirst
restartPolicy: Always
@@ -4,21 +4,21 @@ metadata:
name: twentycrm
namespace: twentycrm
annotations:
nginx.ingress.kubernetes.io/configuration-snippet: |
nginx.ingress.kubernetes.io/configuration-snippet: |
more_set_headers "X-Forwarded-For $http_x_forwarded_for";
nginx.ingress.kubernetes.io/force-ssl-redirect: "false"
kubernetes.io/ingress.class: "nginx"
nginx.ingress.kubernetes.io/backend-protocol: "HTTP"
nginx.ingress.kubernetes.io/force-ssl-redirect: "false"
kubernetes.io/ingress.class: "nginx"
nginx.ingress.kubernetes.io/backend-protocol: "HTTP"
spec:
ingressClassName: nginx
rules:
- host: crm.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: twentycrm-server
port:
name: http-tcp
- host: crm.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: twentycrm-server
port:
name: http-tcp
@@ -1,11 +0,0 @@
apiVersion: v1
kind: PersistentVolume
metadata:
name: twentycrm-docker-data-pv
spec:
storageClassName: default
capacity:
storage: 100Mi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
@@ -1,13 +0,0 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: twentycrm-docker-data-pvc
namespace: twentycrm
spec:
storageClassName: default
volumeName: twentycrm-docker-data-pv
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 100Mi
@@ -6,9 +6,9 @@ metadata:
spec:
internalTrafficPolicy: Cluster
ports:
- port: 5432
protocol: TCP
targetPort: 5432
- port: 5432
protocol: TCP
targetPort: 5432
selector:
app: twentycrm-db
sessionAffinity: ClientIP
@@ -1,18 +0,0 @@
apiVersion: v1
kind: Service
metadata:
name: twentycrm-redis
namespace: twentycrm
spec:
internalTrafficPolicy: Cluster
ports:
- port: 6379
protocol: TCP
targetPort: 6379
selector:
app: twentycrm-redis
sessionAffinity: ClientIP
sessionAffinityConfig:
clientIP:
timeoutSeconds: 10800
type: ClusterIP
@@ -6,10 +6,10 @@ metadata:
spec:
internalTrafficPolicy: Cluster
ports:
- name: http-tcp
port: 3000
protocol: TCP
targetPort: 3000
- name: http-tcp
port: 3000
protocol: TCP
targetPort: 3000
selector:
app: twentycrm-server
sessionAffinity: ClientIP
@@ -1,48 +0,0 @@
formatter: "markdown table" # this is required
version: ""
header-from: main.tf
recursive:
enabled: false
path: modules
output:
file: "README.md"
mode: inject
template: |-
<!-- BEGIN_TF_DOCS -->
# TwentyCRM Terraform Docs
This file was generated by [terraform-docs](https://terraform-docs.io/), for more information on how to install, configure, and use visit their website.
To update this `README.md` after changes to the Terraform code in this folder, run: `terraform-docs -c `./.terraform-docs.yml .`
To make configuration changes to how this doc is generated, see `./.terraform-docs.yml`
{{ .Content }}
<!-- END_TF_DOCS -->
output-values:
enabled: false
from: "outputs.tf"
sort:
enabled: true
by: required
settings:
anchor: true
color: true
default: true
description: true
escape: true
hide-empty: true
html: true
indent: 2
lockfile: true
read-comments: true
required: true
sensitive: true
type: true
@@ -1,73 +0,0 @@
<!-- BEGIN_TF_DOCS -->
# TwentyCRM Terraform Docs
This file was generated by [terraform-docs](https://terraform-docs.io/), for more information on how to install, configure, and use visit their website.
To update this `README.md` after changes to the Terraform code in this folder, run: `terraform-docs -c `./.terraform-docs.yml .`
To make configuration changes to how this doc is generated, see `./.terraform-docs.yml`
## Requirements
| Name | Version |
|------|---------|
| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 1.9.2 |
| <a name="requirement_kubernetes"></a> [kubernetes](#requirement\_kubernetes) | >= 2.32.0 |
| <a name="requirement_random"></a> [random](#requirement\_random) | >= 3.6.3 |
## Providers
| Name | Version |
|------|---------|
| <a name="provider_kubernetes"></a> [kubernetes](#provider\_kubernetes) | >= 2.32.0 |
| <a name="provider_random"></a> [random](#provider\_random) | >= 3.6.3 |
## Resources
| Name | Type |
|------|------|
| [kubernetes_deployment.twentycrm_db](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/deployment) | resource |
| [kubernetes_deployment.twentycrm_redis](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/deployment) | resource |
| [kubernetes_deployment.twentycrm_server](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/deployment) | resource |
| [kubernetes_deployment.twentycrm_worker](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/deployment) | resource |
| [kubernetes_ingress.twentycrm](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/ingress) | resource |
| [kubernetes_namespace.twentycrm](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/namespace) | resource |
| [kubernetes_persistent_volume.db](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/persistent_volume) | resource |
| [kubernetes_persistent_volume.docker_data](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/persistent_volume) | resource |
| [kubernetes_persistent_volume.server](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/persistent_volume) | resource |
| [kubernetes_persistent_volume_claim.db](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/persistent_volume_claim) | resource |
| [kubernetes_persistent_volume_claim.docker_data](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/persistent_volume_claim) | resource |
| [kubernetes_persistent_volume_claim.server](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/persistent_volume_claim) | resource |
| [kubernetes_secret.twentycrm_tokens](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/secret) | resource |
| [kubernetes_service.twentycrm_db](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/service) | resource |
| [kubernetes_service.twentycrm_redis](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/service) | resource |
| [kubernetes_service.twentycrm_server](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/service) | resource |
| [random_bytes.this](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/bytes) | resource |
## Inputs
| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:--------:|
| <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_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 |
| <a name="input_twentycrm_db_replicas"></a> [twentycrm\_db\_replicas](#input\_twentycrm\_db\_replicas) | Number of replicas for the TwentyCRM database deployment. This defaults to 1. | `number` | `1` | no |
| <a name="input_twentycrm_docker_data_mount_path"></a> [twentycrm\_docker\_data\_mount\_path](#input\_twentycrm\_docker\_data\_mount\_path) | TwentyCRM mount path for servers application data. Defaults to '/app/docker-data'. | `string` | `"/app/docker-data"` | no |
| <a name="input_twentycrm_docker_data_pv_capacity"></a> [twentycrm\_docker\_data\_pv\_capacity](#input\_twentycrm\_docker\_data\_pv\_capacity) | Storage capacity provisioned for server persistent volume. | `string` | `"10Gi"` | no |
| <a name="input_twentycrm_docker_data_pv_path"></a> [twentycrm\_docker\_data\_pv\_path](#input\_twentycrm\_docker\_data\_pv\_path) | Local path to use to store the physical volume if using local storage on nodes. | `string` | `""` | no |
| <a name="input_twentycrm_docker_data_pvc_requests"></a> [twentycrm\_docker\_data\_pvc\_requests](#input\_twentycrm\_docker\_data\_pvc\_requests) | Storage capacity reservation for server persistent volume claim. | `string` | `"10Gi"` | no |
| <a name="input_twentycrm_namespace"></a> [twentycrm\_namespace](#input\_twentycrm\_namespace) | Namespace for all TwentyCRM resources | `string` | `"twentycrm"` | no |
| <a name="input_twentycrm_redis_image"></a> [twentycrm\_redis\_image](#input\_twentycrm\_redis\_image) | TwentyCRM image for Redis deployment. This defaults to latest. | `string` | `"redis/redis-stack-server:latest"` | no |
| <a name="input_twentycrm_redis_replicas"></a> [twentycrm\_redis\_replicas](#input\_twentycrm\_redis\_replicas) | Number of replicas for the TwentyCRM Redis deployment. This defaults to 1. | `number` | `1` | no |
| <a name="input_twentycrm_server_data_mount_path"></a> [twentycrm\_server\_data\_mount\_path](#input\_twentycrm\_server\_data\_mount\_path) | TwentyCRM mount path for servers application data. Defaults to '/app/packages/twenty-server/.local-storage'. | `string` | `"/app/packages/twenty-server/.local-storage"` | no |
| <a name="input_twentycrm_server_image"></a> [twentycrm\_server\_image](#input\_twentycrm\_server\_image) | TwentyCRM server image for the server deployment. This defaults to latest. This value is also used for the workers image. | `string` | `"twentycrm/twenty:latest"` | no |
| <a name="input_twentycrm_server_pv_capacity"></a> [twentycrm\_server\_pv\_capacity](#input\_twentycrm\_server\_pv\_capacity) | Storage capacity provisioned for server persistent volume. | `string` | `"10Gi"` | no |
| <a name="input_twentycrm_server_pv_path"></a> [twentycrm\_server\_pv\_path](#input\_twentycrm\_server\_pv\_path) | Local path to use to store the physical volume if using local storage on nodes. | `string` | `""` | no |
| <a name="input_twentycrm_server_pvc_requests"></a> [twentycrm\_server\_pvc\_requests](#input\_twentycrm\_server\_pvc\_requests) | Storage capacity reservation for server persistent volume claim. | `string` | `"10Gi"` | no |
| <a name="input_twentycrm_server_replicas"></a> [twentycrm\_server\_replicas](#input\_twentycrm\_server\_replicas) | Number of replicas for the TwentyCRM server deployment. This defaults to 1. | `number` | `1` | no |
| <a name="input_twentycrm_worker_replicas"></a> [twentycrm\_worker\_replicas](#input\_twentycrm\_worker\_replicas) | Number of replicas for the TwentyCRM worker deployment. This defaults to 1. | `number` | `1` | no |
<!-- END_TF_DOCS -->
@@ -1,17 +1,17 @@
resource "kubernetes_deployment" "twentycrm_db" {
metadata {
name = "${var.twentycrm_app_name}-db"
name = "${local.twentycrm_app_name}-db"
namespace = kubernetes_namespace.twentycrm.metadata.0.name
labels = {
app = "${var.twentycrm_app_name}-db"
app = "${local.twentycrm_app_name}-db"
}
}
spec {
replicas = var.twentycrm_db_replicas
replicas = 1
selector {
match_labels = {
app = "${var.twentycrm_app_name}-db"
app = "${local.twentycrm_app_name}-db"
}
}
@@ -26,14 +26,17 @@ resource "kubernetes_deployment" "twentycrm_db" {
template {
metadata {
labels = {
app = "${var.twentycrm_app_name}-db"
app = "${local.twentycrm_app_name}-db"
}
}
spec {
# security_context {
# fs_group = 0
# }
container {
image = var.twentycrm_db_image
name = var.twentycrm_app_name
image = local.twentycrm_db_image
name = local.twentycrm_app_name
stdin = true
tty = true
security_context {
@@ -42,7 +45,7 @@ resource "kubernetes_deployment" "twentycrm_db" {
env {
name = "POSTGRES_PASSWORD"
value = var.twentycrm_pgdb_admin_password
value = "twenty"
}
env {
name = "BITNAMI_DEBUG"
@@ -66,16 +69,16 @@ resource "kubernetes_deployment" "twentycrm_db" {
}
volume_mount {
name = "db-data"
name = "nfs-twentycrm-db-data"
mount_path = "/bitnami/postgresql"
}
}
volume {
name = "db-data"
name = "nfs-twentycrm-db-data"
persistent_volume_claim {
claim_name = kubernetes_persistent_volume_claim.db.metadata.0.name
claim_name = "nfs-twentycrm-db-data-pvc"
}
}
@@ -1,60 +0,0 @@
resource "kubernetes_deployment" "twentycrm_redis" {
metadata {
name = "${var.twentycrm_app_name}-redis"
namespace = kubernetes_namespace.twentycrm.metadata.0.name
labels = {
app = "${var.twentycrm_app_name}-redis"
}
}
spec {
replicas = var.twentycrm_redis_replicas
selector {
match_labels = {
app = "${var.twentycrm_app_name}-redis"
}
}
strategy {
type = "RollingUpdate"
rolling_update {
max_surge = "1"
max_unavailable = "1"
}
}
template {
metadata {
labels = {
app = "${var.twentycrm_app_name}-redis"
}
}
spec {
container {
image = var.twentycrm_redis_image
name = "redis"
port {
container_port = 6379
protocol = "TCP"
}
resources {
requests = {
cpu = "250m"
memory = "1024Mi"
}
limits = {
cpu = "500m"
memory = "2048Mi"
}
}
}
dns_policy = "ClusterFirst"
restart_policy = "Always"
}
}
}
}
@@ -1,17 +1,17 @@
resource "kubernetes_deployment" "twentycrm_server" {
metadata {
name = "${var.twentycrm_app_name}-server"
name = "${local.twentycrm_app_name}-server"
namespace = kubernetes_namespace.twentycrm.metadata.0.name
labels = {
app = "${var.twentycrm_app_name}-server"
app = "${local.twentycrm_app_name}-server"
}
}
spec {
replicas = var.twentycrm_server_replicas
replicas = 1
selector {
match_labels = {
app = "${var.twentycrm_app_name}-server"
app = "${local.twentycrm_app_name}-server"
}
}
@@ -26,54 +26,69 @@ resource "kubernetes_deployment" "twentycrm_server" {
template {
metadata {
labels = {
app = "${var.twentycrm_app_name}-server"
app = "${local.twentycrm_app_name}-server"
}
}
spec {
container {
image = var.twentycrm_server_image
name = var.twentycrm_app_name
image = local.twentycrm_server_image
name = local.twentycrm_app_name
stdin = true
tty = true
security_context {
allow_privilege_escalation = true
privileged = true
run_as_user = 1000
}
env {
name = "NODE_PORT"
name = "PORT"
value = "3000"
}
env {
name = "DEBUG_MODE"
value = false
}
env {
name = "SERVER_URL"
value = var.twentycrm_app_hostname
value = "https://crm.example.com:443"
}
env {
name = "FRONT_BASE_URL"
value = "https://crm.example.com:443"
}
env {
name = "BACKEND_SERVER_URL"
value = "https://crm.example.com:443"
}
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"
value = "postgres://twenty:twenty@twentycrm-db.twentycrm.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 = "ENABLE_DB_MIGRATIONS"
value = "true"
}
env {
name = "DISABLE_DB_MIGRATIONS"
value = "false"
name = "SIGN_IN_PREFILLED"
value = "true"
}
env {
name = "STORAGE_TYPE"
value = "local"
}
env {
name = "ACCESS_TOKEN_EXPIRES_IN"
value = "7d"
}
env {
name = "LOGIN_TOKEN_EXPIRES_IN"
value = "1h"
}
env {
name = "APP_SECRET"
name = "ACCESS_TOKEN_SECRET"
value_from {
secret_key_ref {
name = "tokens"
@@ -82,6 +97,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"
@@ -99,29 +144,16 @@ resource "kubernetes_deployment" "twentycrm_server" {
}
volume_mount {
name = "server-data"
mount_path = var.twentycrm_server_data_mount_path
}
volume_mount {
name = "docker-data"
mount_path = var.twentycrm_docker_data_mount_path
name = "nfs-twentycrm-server-data"
mount_path = "/app/.local-storage"
}
}
volume {
name = "server-data"
name = "nfs-twentycrm-server-data"
persistent_volume_claim {
claim_name = kubernetes_persistent_volume_claim.server.metadata.0.name
}
}
volume {
name = "docker-data"
persistent_volume_claim {
claim_name = kubernetes_persistent_volume_claim.docker_data.metadata.0.name
claim_name = "nfs-twentycrm-server-data-pvc"
}
}
@@ -132,7 +164,6 @@ resource "kubernetes_deployment" "twentycrm_server" {
}
depends_on = [
kubernetes_deployment.twentycrm_db,
kubernetes_deployment.twentycrm_redis,
kubernetes_secret.twentycrm_tokens
]
}
@@ -1,99 +0,0 @@
resource "kubernetes_deployment" "twentycrm_worker" {
metadata {
name = "${var.twentycrm_app_name}-worker"
namespace = kubernetes_namespace.twentycrm.metadata.0.name
labels = {
app = "${var.twentycrm_app_name}-worker"
}
}
spec {
replicas = var.twentycrm_worker_replicas
selector {
match_labels = {
app = "${var.twentycrm_app_name}-worker"
}
}
strategy {
type = "RollingUpdate"
rolling_update {
max_surge = "1"
max_unavailable = "1"
}
}
template {
metadata {
labels = {
app = "${var.twentycrm_app_name}-worker"
}
}
spec {
container {
image = var.twentycrm_server_image
name = var.twentycrm_app_name
stdin = true
tty = true
command = ["yarn", "worker:prod"]
env {
name = "SERVER_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"
}
env {
name = "DISABLE_DB_MIGRATIONS"
value = "true" #it already runs on the server
}
env {
name = "STORAGE_TYPE"
value = "local"
}
env {
name = "APP_SECRET"
value_from {
secret_key_ref {
name = "tokens"
key = "accessToken"
}
}
}
resources {
requests = {
cpu = "250m"
memory = "1024Mi"
}
limits = {
cpu = "1000m"
memory = "2048Mi"
}
}
}
dns_policy = "ClusterFirst"
restart_policy = "Always"
}
}
}
depends_on = [
kubernetes_deployment.twentycrm_db,
kubernetes_deployment.twentycrm_redis,
kubernetes_deployment.twentycrm_server,
kubernetes_secret.twentycrm_tokens,
]
}
@@ -1,7 +1,7 @@
resource "kubernetes_ingress" "twentycrm" {
wait_for_load_balancer = true
metadata {
name = "${var.twentycrm_app_name}-ingress"
name = "${local.twentycrm_app_name}-ingress"
namespace = kubernetes_namespace.twentycrm.metadata.0.name
annotations = {
"kubernetes.io/ingress.class" = "nginx"
@@ -15,7 +15,7 @@ resource "kubernetes_ingress" "twentycrm" {
spec {
ingress_class_name = "nginx"
rule {
host = var.twentycrm_app_hostname
host = local.twentycrm_app_hostname
http {
path {
path = "/*"
+19 -6
View File
@@ -5,19 +5,32 @@ provider "kubernetes" {
config_path = "~/.kube/config"
}
#################
# Global Locals #
#################
locals {
twentycrm_app_name = "twentycrm"
twentycrm_app_hostname = "crm.example.com"
twentycrm_server_image = "twentycrm/twenty:v0.10.4"
twentycrm_db_image = "twentycrm/twenty-postgres:v0.10.4"
twentycrm_db_pv_path = "/path/to/mystorage"
twentycrm_db_pv_capacity = "10Gi"
twentycrm_db_pvc_requests = "10Gi"
twentycrm_server_pv_path = "/path/to/mystorage"
twentycrm_server_pv_capacity = "10Gi"
twentycrm_server_pvc_requests = "10Gi"
}
####################
# Terraform Config #
####################
terraform {
required_version = ">= 1.9.2"
required_version = ">= 1.7.4"
required_providers {
kubernetes = {
source = "hashicorp/kubernetes"
version = ">= 2.32.0"
}
random = {
source = "hashicorp/random"
version = ">= 3.6.3"
version = ">= 2.23.0"
}
}
}
@@ -1,9 +1,9 @@
resource "kubernetes_namespace" "twentycrm" {
metadata {
annotations = {
name = var.twentycrm_namespace
name = "twentycrm"
}
name = var.twentycrm_namespace
name = "twentycrm"
}
}
@@ -1,18 +1,18 @@
resource "kubernetes_persistent_volume" "db" {
metadata {
name = "${var.twentycrm_app_name}-db-pv"
name = "${local.twentycrm_app_name}-db-pv"
}
spec {
storage_class_name = "default"
capacity = {
storage = var.twentycrm_db_pv_capacity
storage = local.twentycrm_db_pv_capacity
}
access_modes = ["ReadWriteOnce"]
# refer to Terraform Docs for your specific implementation requirements
# refer to Terraform Docs for your specific implementation requirements
# https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/persistent_volume
persistent_volume_source {
local {
path = var.twentycrm_db_pv_path
path = local.twentycrm_db_pv_path
}
}
}
@@ -1,19 +0,0 @@
resource "kubernetes_persistent_volume" "docker_data" {
metadata {
name = "${var.twentycrm_app_name}-docker-data-pv"
}
spec {
storage_class_name = "default"
capacity = {
storage = var.twentycrm_docker_data_pv_capacity
}
access_modes = ["ReadWriteOnce"]
# refer to Terraform Docs for your specific implementation requirements
# https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/persistent_volume
persistent_volume_source {
local {
path = var.twentycrm_docker_data_pv_path
}
}
}
}
@@ -1,18 +1,18 @@
resource "kubernetes_persistent_volume" "server" {
metadata {
name = "${var.twentycrm_app_name}-server-pv"
name = "${local.twentycrm_app_name}-server-pv"
}
spec {
storage_class_name = "default"
capacity = {
storage = var.twentycrm_server_pv_capacity
storage = local.twentycrm_server_pv_capacity
}
access_modes = ["ReadWriteOnce"]
# refer to Terraform Docs for your specific implementation requirements
# refer to Terraform Docs for your specific implementation requirements
# https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/persistent_volume
persistent_volume_source {
local {
path = var.twentycrm_server_pv_path
path = local.twentycrm_server_pv_path
}
}
}
@@ -1,13 +1,13 @@
resource "kubernetes_persistent_volume_claim" "db" {
metadata {
name = "${var.twentycrm_app_name}-db-pvc"
name = "${local.twentycrm_app_name}-db-pvc"
namespace = kubernetes_namespace.twentycrm.metadata.0.name
}
spec {
access_modes = ["ReadWriteOnce"]
resources {
requests = {
storage = var.twentycrm_db_pvc_requests
storage = local.twentycrm_db_pvc_requests
}
}
volume_name = kubernetes_persistent_volume.db.metadata.0.name
@@ -1,15 +0,0 @@
resource "kubernetes_persistent_volume_claim" "docker_data" {
metadata {
name = "${var.twentycrm_app_name}-docker-data-pvc"
namespace = kubernetes_namespace.twentycrm.metadata.0.name
}
spec {
access_modes = ["ReadWriteOnce"]
resources {
requests = {
storage = var.twentycrm_docker_data_pvc_requests
}
}
volume_name = kubernetes_persistent_volume.docker_data.metadata.0.name
}
}
@@ -1,13 +1,13 @@
resource "kubernetes_persistent_volume_claim" "server" {
metadata {
name = "${var.twentycrm_app_name}-server-pvc"
name = "${local.twentycrm_app_name}-server-pvc"
namespace = kubernetes_namespace.twentycrm.metadata.0.name
}
spec {
access_modes = ["ReadWriteOnce"]
resources {
requests = {
storage = var.twentycrm_server_pvc_requests
storage = local.twentycrm_server_pvc_requests
}
}
volume_name = kubernetes_persistent_volume.server.metadata.0.name
+6 -19
View File
@@ -1,18 +1,3 @@
locals {
tokens = [
"accessToken",
"loginToken",
"refreshToken",
"fileToken"
]
}
resource "random_bytes" "this" {
for_each = toset(local.tokens)
length = 32
}
resource "kubernetes_secret" "twentycrm_tokens" {
metadata {
name = "tokens"
@@ -20,9 +5,11 @@ resource "kubernetes_secret" "twentycrm_tokens" {
}
data = {
accessToken = random_bytes.this["accessToken"].base64
loginToken = random_bytes.this["loginToken"].base64
refreshToken = random_bytes.this["refreshToken"].base64
fileToken = random_bytes.this["fileToken"].base64
accessToken = var.twentycrm_token_accessToken
loginToken = var.twentycrm_token_loginToken
refreshToken = var.twentycrm_token_refreshToken
fileToken = var.twentycrm_token_fileToken
}
# type = "kubernetes.io/basic-auth"
}
@@ -1,11 +1,11 @@
resource "kubernetes_service" "twentycrm_db" {
metadata {
name = "${var.twentycrm_app_name}-db"
name = "${local.twentycrm_app_name}-db"
namespace = kubernetes_namespace.twentycrm.metadata.0.name
}
spec {
selector = {
app = "${var.twentycrm_app_name}-db"
app = "${local.twentycrm_app_name}-db"
}
session_affinity = "ClientIP"
port {
@@ -1,18 +0,0 @@
resource "kubernetes_service" "twentycrm_redis" {
metadata {
name = "${var.twentycrm_app_name}-redis"
namespace = kubernetes_namespace.twentycrm.metadata.0.name
}
spec {
selector = {
app = "${var.twentycrm_app_name}-redis"
}
session_affinity = "ClientIP"
port {
port = 6379
target_port = 6379
}
type = "ClusterIP"
}
}
@@ -1,11 +1,11 @@
resource "kubernetes_service" "twentycrm_server" {
metadata {
name = "${var.twentycrm_app_name}-server"
name = "${local.twentycrm_app_name}-server"
namespace = kubernetes_namespace.twentycrm.metadata.0.name
}
spec {
selector = {
app = "${var.twentycrm_app_name}-server"
app = "${local.twentycrm_app_name}-server"
}
session_affinity = "ClientIP"
port {
+21 -133
View File
@@ -1,136 +1,24 @@
######################
# Required Variables #
######################
variable "twentycrm_token_accessToken" {
type = string
description = "TwentyCRM access Token"
}
variable "twentycrm_token_loginToken" {
type = string
description = "TwentyCRM login Token"
}
variable "twentycrm_token_refreshToken" {
type = string
description = "TwentyCRM refresh Token"
}
variable "twentycrm_token_fileToken" {
type = string
description = "TwentyCRM file Token"
}
variable "twentycrm_pgdb_admin_password" {
type = string
description = "TwentyCRM password for postgres database."
sensitive = true
}
variable "twentycrm_app_hostname" {
type = string
description = "The protocol, DNS fully qualified hostname, and port used to access TwentyCRM in your environment. Ex: https://crm.example.com:443"
}
######################
# Optional Variables #
######################
variable "twentycrm_app_name" {
type = string
default = "twentycrm"
description = "A friendly name prefix to use for every component deployed."
}
variable "twentycrm_server_image" {
type = string
default = "twentycrm/twenty:latest"
description = "TwentyCRM server image for the server deployment. This defaults to latest. This value is also used for the workers image."
}
variable "twentycrm_db_image" {
type = string
default = "twentycrm/twenty-postgres-spilo:latest"
description = "TwentyCRM image for database deployment. This defaults to latest."
}
variable "twentycrm_server_replicas" {
type = number
default = 1
description = "Number of replicas for the TwentyCRM server deployment. This defaults to 1."
}
variable "twentycrm_worker_replicas" {
type = number
default = 1
description = "Number of replicas for the TwentyCRM worker deployment. This defaults to 1."
}
variable "twentycrm_db_replicas" {
type = number
default = 1
description = "Number of replicas for the TwentyCRM database deployment. This defaults to 1."
}
variable "twentycrm_server_data_mount_path" {
type = string
default = "/app/packages/twenty-server/.local-storage"
description = "TwentyCRM mount path for servers application data. Defaults to '/app/packages/twenty-server/.local-storage'."
}
variable "twentycrm_db_pv_path" {
type = string
default = ""
description = "Local path to use to store the physical volume if using local storage on nodes."
}
variable "twentycrm_server_pv_path" {
type = string
default = ""
description = "Local path to use to store the physical volume if using local storage on nodes."
}
variable "twentycrm_db_pv_capacity" {
type = string
default = "10Gi"
description = "Storage capacity provisioned for database persistent volume."
}
variable "twentycrm_db_pvc_requests" {
type = string
default = "10Gi"
description = "Storage capacity reservation for database persistent volume claim."
}
variable "twentycrm_server_pv_capacity" {
type = string
default = "10Gi"
description = "Storage capacity provisioned for server persistent volume."
}
variable "twentycrm_server_pvc_requests" {
type = string
default = "10Gi"
description = "Storage capacity reservation for server persistent volume claim."
}
variable "twentycrm_namespace" {
type = string
default = "twentycrm"
description = "Namespace for all TwentyCRM resources"
}
variable "twentycrm_redis_replicas" {
type = number
default = 1
description = "Number of replicas for the TwentyCRM Redis deployment. This defaults to 1."
}
variable "twentycrm_redis_image" {
type = string
default = "redis/redis-stack-server:latest"
description = "TwentyCRM image for Redis deployment. This defaults to latest."
}
variable "twentycrm_docker_data_mount_path" {
type = string
default = "/app/docker-data"
description = "TwentyCRM mount path for servers application data. Defaults to '/app/docker-data'."
}
variable "twentycrm_docker_data_pv_path" {
type = string
default = ""
description = "Local path to use to store the physical volume if using local storage on nodes."
}
variable "twentycrm_docker_data_pv_capacity" {
type = string
default = "100Mi"
description = "Storage capacity provisioned for server persistent volume."
}
variable "twentycrm_docker_data_pvc_requests" {
type = string
default = "100Mi"
description = "Storage capacity reservation for server persistent volume claim."
description = "TwentyCRM password for postgres database"
}

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