Initial resurrection kit — 81 scripts, 62 references, configs, systemd units, crons, Docker compose files, Caddy config, master README

This commit is contained in:
root
2026-07-15 17:45:36 -04:00
commit ae056eaf83
197 changed files with 26127 additions and 0 deletions
+22
View File
@@ -0,0 +1,22 @@
{
"mcpServers": {
"postgres": {
"type": "stdio",
"command": "bash",
"args": ["-c", "source packages/twenty-server/.env && npx -y @modelcontextprotocol/server-postgres \"$PG_DATABASE_URL\""],
"env": {}
},
"playwright": {
"type": "stdio",
"command": "npx",
"args": ["@playwright/mcp@latest", "--no-sandbox", "--headless"],
"env": {}
},
"context7": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@upstash/context7-mcp"],
"env": {}
}
}
}
+224
View File
@@ -0,0 +1,224 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
Twenty is an open-source CRM built with modern technologies in a monorepo structure. The codebase is organized as an Nx workspace with multiple packages.
## Key Commands
### Development
```bash
# Start development environment (frontend + backend + worker)
yarn start
# Individual package development
npx nx start twenty-front # Start frontend dev server
npx nx start twenty-server # Start backend server
npx nx run twenty-server:worker # Start background worker
```
### Testing
```bash
# Preferred: run a single test file (fast)
npx jest path/to/test.test.ts --config=packages/PROJECT/jest.config.mjs
# Run all tests for a package
npx nx test twenty-front # Frontend unit tests
npx nx test twenty-server # Backend unit tests
npx nx run twenty-server:test:integration:with-db-reset # Integration tests with DB reset
# To run an individual test or a pattern of tests, use the following command:
cd packages/{workspace} && npx jest "pattern or filename"
# Storybook
npx nx storybook:build twenty-front
npx nx storybook:test twenty-front
# When testing the UI end to end, click on "Continue with Email" and use the prefilled credentials.
```
### Code Quality
```bash
# Linting (diff with main - fastest, always prefer this)
npx nx lint:diff-with-main twenty-front
npx nx lint:diff-with-main twenty-server
npx nx lint:diff-with-main twenty-front --configuration=fix # Auto-fix
# Linting (full project - slower, use only when needed)
npx nx lint twenty-front
npx nx lint twenty-server
# Type checking
npx nx typecheck twenty-front
npx nx typecheck twenty-server
# Format code
npx nx fmt twenty-front
npx nx fmt twenty-server
```
### Build
```bash
# Build packages (twenty-shared must be built first)
npx nx build twenty-shared
npx nx build twenty-front
npx nx build twenty-server
```
### Database Operations
```bash
# Database management
npx nx database:reset twenty-server # Reset database
npx nx run twenty-server:database:init:prod # Initialize database
npx nx run twenty-server:database:migrate:prod # Run instance commands (fast only)
# Generate an instance command (fast or slow)
npx nx run twenty-server:database:migrate:generate --name <name> --type <fast|slow>
```
### Database Inspection (Postgres MCP)
A read-only Postgres MCP server is configured in `.mcp.json`. Use it to:
- Inspect workspace data, metadata, and object definitions while developing
- Verify migration results (columns, types, constraints) after running migrations
- Explore the multi-tenant schema structure (core, metadata, workspace-specific schemas)
- Debug issues by querying raw data to confirm whether a bug is frontend, backend, or data-level
- Inspect metadata tables to debug GraphQL schema generation issues
This server is read-only — for write operations (reset, migrations, sync), use the CLI commands above.
### GraphQL
```bash
# Generate GraphQL types (run after schema changes)
npx nx run twenty-front:graphql:generate
npx nx run twenty-front:graphql:generate --configuration=metadata
```
## Architecture Overview
### Tech Stack
- **Frontend**: React 18, TypeScript, Jotai (state management), Linaria (styling), Vite
- **Backend**: NestJS, TypeORM, PostgreSQL, Redis, GraphQL (with GraphQL Yoga)
- **Monorepo**: Nx workspace managed with Yarn 4
### Package Structure
```
packages/
├── twenty-front/ # React frontend application
├── twenty-server/ # NestJS backend API
├── twenty-ui/ # Shared UI components library
├── twenty-shared/ # Common types and utilities
├── twenty-emails/ # Email templates with React Email
├── twenty-website/ # Next.js marketing website
├── twenty-docs/ # Documentation website
├── twenty-zapier/ # Zapier integration
└── twenty-e2e-testing/ # Playwright E2E tests
```
### Key Development Principles
- **Functional components only** (no class components)
- **Named exports only** (no default exports)
- **Types over interfaces** (except when extending third-party interfaces)
- **String literals over enums** (except for GraphQL enums)
- **No 'any' type allowed** — strict TypeScript enforced
- **Event handlers preferred over useEffect** for state updates
- **Props down, events up** — unidirectional data flow
- **Composition over inheritance**
- **No abbreviations** in variable names (`user` not `u`, `fieldMetadata` not `fm`)
### Naming Conventions
- **Variables/functions**: camelCase
- **Constants**: SCREAMING_SNAKE_CASE
- **Types/Classes**: PascalCase (suffix component props with `Props`, e.g. `ButtonProps`)
- **Files/directories**: kebab-case with descriptive suffixes (`.component.tsx`, `.service.ts`, `.entity.ts`, `.dto.ts`, `.module.ts`)
- **TypeScript generics**: descriptive names (`TData` not `T`)
### File Structure
- Components under 300 lines, services under 500 lines
- Components in their own directories with tests and stories
- Use `index.ts` barrel exports for clean imports
- Import order: external libraries first, then internal (`@/`), then relative
### Comments
- Use short-form comments (`//`), not JSDoc blocks
- Explain WHY (business logic), not WHAT
- Do not comment obvious code
- Multi-line comments use multiple `//` lines, not `/** */`
### State Management
- **Jotai** for global state: atoms for primitive state, selectors for derived state, atom families for dynamic collections
- Component-specific state with React hooks (`useState`, `useReducer` for complex logic)
- GraphQL cache managed by Apollo Client
- Use functional state updates: `setState(prev => prev + 1)`
### Backend Architecture
- **NestJS modules** for feature organization
- **TypeORM** for database ORM with PostgreSQL
- **GraphQL** API with code-first approach
- **Redis** for caching and session management
- **BullMQ** for background job processing
### Database & Upgrade Commands
- **PostgreSQL** as primary database
- **Redis** for caching and sessions
- **ClickHouse** for analytics (when enabled)
- When changing entity files, generate an **instance command** (`database:migrate:generate --name <name> --type <fast|slow>`)
- **Fast** instance commands handle schema changes; **slow** ones add a `runDataMigration` step for data backfills
- **Workspace commands** iterate over all active/suspended workspaces for per-workspace upgrades
- Commands use `@RegisteredInstanceCommand` and `@RegisteredWorkspaceCommand` decorators for automatic discovery
- Include both `up` and `down` logic in instance commands
- Never delete or rewrite committed instance command `up`/`down` logic
- See `packages/twenty-server/docs/UPGRADE_COMMANDS.md` for full documentation
### Utility Helpers
Use existing helpers from `twenty-shared` instead of manual type guards:
- `isDefined()`, `isNonEmptyString()`, `isNonEmptyArray()`
## Development Workflow
IMPORTANT: Use Context7 for code generation, setup or configuration steps, or library/API documentation. Automatically use the Context7 MCP tools to resolve library IDs and get library docs without waiting for explicit requests.
### Before Making Changes
1. Always run linting (`lint:diff-with-main`) and type checking after code changes
2. Test changes with relevant test suites (prefer single-file test runs)
3. Ensure instance commands are generated for entity changes (`database:migrate:generate`)
4. Check that GraphQL schema changes are backward compatible
5. Run `graphql:generate` after any GraphQL schema changes
### Code Style Notes
- Use **Linaria** for styling with zero-runtime CSS-in-JS (styled-components pattern)
- Follow **Nx** workspace conventions for imports
- Use **Lingui** for internationalization
- Apply security first, then formatting (sanitize before format)
### Testing Strategy
- **Test behavior, not implementation** — focus on user perspective
- **Test pyramid**: 70% unit, 20% integration, 10% E2E
- Query by user-visible elements (text, roles, labels) over test IDs
- Use `@testing-library/user-event` for realistic interactions
- Descriptive test names: "should [behavior] when [condition]"
- Clear mocks between tests with `jest.clearAllMocks()`
## Dev Environment Setup
All dev environments (Claude Code web, Cursor, local) use one script:
```bash
bash packages/twenty-utils/setup-dev-env.sh
```
This handles everything: starts Postgres + Redis (auto-detects local services vs Docker), creates databases, copies `.env` files, and initializes the database schema (runs migrations) on a fresh database. Idempotent — safe to run multiple times.
- `--docker` — force Docker mode (uses `packages/twenty-docker/docker-compose.dev.yml`)
- `--down` — stop services
- `--reset` — wipe data and restart fresh
- **Skip the setup script** for tasks that only read code — architecture questions, code review, documentation, etc.
**Note:** CI workflows (GitHub Actions) manage services via Actions service containers and run setup steps individually — they don't use this script.
## Important Files
- `nx.json` - Nx workspace configuration with task definitions
- `tsconfig.base.json` - Base TypeScript configuration
- `package.json` - Root package with workspace definitions
- `.cursor/rules/` - Detailed development guidelines and best practices
+159
View File
@@ -0,0 +1,159 @@
# Twenty Website — DESIGN.md
> Visual system for the Twenty marketing site. Distilled from `packages/twenty-website/src/theme/`. Loaded by every `impeccable` invocation alongside PRODUCT.md.
## Theme
**Light by default.** A founder browsing a partner profile in daylight on a 1427 inch monitor is the default scene. The site does ship a `data-scheme="dark"` override (see `css-variables.ts`), but no current public page opts into it. Treat dark as a deferred surface.
## Color
Palette is OKLCH-equivalent neutrals at the surface level. The brand accents (blue, pink, yellow, green) are present in the token system but used sparingly — none of them appear on the partner pages.
### Strategy: Restrained
Tinted neutrals + one accent ≤10%. The accent for partner pages is the deep ink black (`var(--color-black-100)`) used in CTAs and hover states. Anything beyond a hairline border, an icon glyph, or a primary CTA should question whether it needs color at all.
### Tokens (from `src/theme/colors.ts` + `css-variables.ts`)
Neutrals (the workhorses):
| Token | Hex (computed) | Role |
| --- | --- | --- |
| `colors.primary.background[100]` | `#ffffff` | Page + card surface |
| `colors.primary.text[100]` | `#1c1c1c` | Headlines, primary text |
| `colors.primary.text[80]` | `#1c1c1ccc` | Body text |
| `colors.primary.text[60]` | `#1c1c1c99` | Eyebrows, meta, captions |
| `colors.primary.text[40]` | `#1c1c1c66` | Disabled / placeholder |
| `colors.primary.text[20]` | `#1c1c1c33` | Subtle separators |
| `colors.primary.text[10]` | `#1c1c1c1a` | Hairline borders |
| `colors.primary.text[5]` | `#1c1c1c0d` | Subtle fills (rates panel, skill chips) |
| `colors.primary.border[10]` | `#1c1c1c1a` | Default border |
| `colors.primary.border[20]` | `#1c1c1c33` | Hover border |
Reverse palette (for dark CTAs):
| Token | Role |
| --- | --- |
| `colors.secondary.background[100]` | Filled CTA background (deep ink) |
| `colors.secondary.text[100]` | Filled CTA text (white) |
Brand accents (currently absent from partner pages; available if needed):
- `colors.accent.blue``#4a38f5` / `#8174f8`
- `colors.accent.pink``#ed87fc` / `#f3abfd`
- `colors.accent.yellow``#feffb7` / `#feffd9`
- `colors.accent.green``#89fc9a` / `#b0fdbe`
- `colors.highlight` — same hue as blue accent
**Do not introduce gradients, glass blurs, or saturated fills on partner pages.** Color is conviction here, not decoration.
## Typography
Three families, each load-balanced via CSS variables:
| Family | Var | Use |
| --- | --- | --- |
| `theme.font.family.serif` | `--font-serif` | Headlines, partner names, headline values |
| `theme.font.family.sans` | `--font-sans` | Body, prose, interactive labels |
| `theme.font.family.mono` | `--font-mono` | Eyebrows, meta, currency labels, tabular numerics |
| `theme.font.family.retro` | `--font-retro` | Reserved (not used on partner pages) |
### Weight + Size Contrast
Weights: `light: 300`, `regular: 400`, `medium: 500`. No bold. Hierarchy is driven by scale and family contrast, never by weight alone.
Scale (`theme.font.size(n)``calc(var(--font-base) * n)`, where `--font-base: 0.25rem` ≈ 4px):
- Display / h1: size 912 (3648px)
- h2 / section heads: size 78 (2832px)
- h3 / card heads: size 56 (2024px)
- Body / prose: size 45 (1620px)
- Eyebrow / meta: size 3 (12px) with `letter-spacing: 0.060.08em` and `text-transform: uppercase`
Body line length: cap at 6575ch (the existing `PartnerProfileIntro` uses `max-width: 62ch` — keep that order of magnitude).
### Hierarchy contract
- A serif `<h1>` at size 9 light reads as a partner's name on the detail page.
- A mono eyebrow above or below it locates the partner (region · city · country).
- A serif size 6 light reads as a section head.
- Body prose is sans regular.
- Currency values are serif (they read as headline numbers, not stats).
- Currency labels and meta are mono.
## Spacing & Layout
Base unit `4px`. Spacing helper `theme.spacing(n)` returns `n * 4px`. Common rhythms on the partner pages:
- Inter-section gap on the detail page: `theme.spacing(1014)` — generous, editorial breathing room.
- Inter-element gap inside a section: `theme.spacing(35)`.
- Card padding: `theme.spacing(6)`.
- Page horizontal padding: `theme.spacing(4)` mobile, `theme.spacing(10)` ≥ md breakpoint.
### Radius
`theme.radius(n)` returns `n * 2px`. The default card radius is `theme.radius(2)` = 4px. Pills use `999px`. No softer rounding than that.
### Borders
Borders are hairline (`1px solid theme.colors.primary.border[10]`). They define edges quietly. On hover they step to `border[20]`. Never use a chunky border as decoration.
## Components
### Card (PartnerCard, RatesPanel)
White surface, hairline border, 4px radius, 24px padding, soft shadow on hover only:
```css
background-color: ${theme.colors.primary.background[100]};
border: 1px solid ${theme.colors.primary.border[10]};
border-radius: ${theme.radius(2)};
padding: ${theme.spacing(6)};
&:hover {
border-color: ${theme.colors.primary.border[20]};
box-shadow: 0 12px 32px -16px rgba(0, 0, 0, 0.18);
transform: translateY(-2px);
}
```
### Chip / Pill
Rounded `999px`, 1px border, subtle background fill (`primary.text[5]` for filter pills, transparent for chip rows), `text[80]` color, mono or sans font.
### Button / LinkButton
Lives in `@/design-system/components`. Two color modes: `primary` (deep ink fill, white text) and `secondary` (transparent fill, ink text + 1px border). `variant="contained"` is what partner pages use.
### Avatar
`PartnerAvatar` is a deterministic generated mark from name + slug. Used as fallback when `profilePictureUrl` is missing. The real photo overlays it at 120px circle on the detail page, 56px on the list card.
## Motion
- Hover transitions: 250ms, ease-out (cubic-bezier curve in `PartnerCard`: `0.25s ease`).
- Card entrance: 700ms cubic-bezier `0.22, 1, 0.36, 1` (ease-out-quart), 90ms stagger per index.
- All motion respects `@media (prefers-reduced-motion: reduce)` — animations stop, hover translate disabled.
- **No bounce, no elastic, no parallax.** Editorial restraint.
## Iconography
`@tabler/icons-react`, 1416px on body-level chips, 1824px on buttons. Always `aria-hidden="true"` when decorative. Stroke width `2` (default).
## Accessibility Defaults
- Focus ring: `outline: 2px solid theme.colors.primary.text[100]; outline-offset: 4px` (already used on the card link).
- Touch target ≥ 40×40px on mobile.
- `aria-label` on icon-only buttons, `aria-labelledby` on sectioned regions.
- All `<a target="_blank">` includes `rel="noopener noreferrer"`.
- Color is never the sole carrier of meaning. The money pills carry both an icon and a text label.
## Anti-patterns (project-specific)
In addition to the impeccable shared absolute bans:
- **Do not use the brand accent colors (blue/pink/yellow/green) on partner pages** unless we have a stronger reason than "to add color".
- **No skeuomorphic shadows on cards.** The hover shadow is `0 12px 32px -16px rgba(0,0,0,0.18)` — that's the ceiling.
- **No gradients on anything.** Including text, borders, and backgrounds.
- **No floating "Trusted by" logo bars** on partner pages.
+80
View File
@@ -0,0 +1,80 @@
# Twenty Website — Product & Brand Context
> Strategic context for design work on the Twenty marketing site (`packages/twenty-website`). Loaded by every `impeccable` invocation.
## Register
**Brand.** The marketing site is a public-facing surface where the design itself is part of the credibility argument. Prospects evaluate Twenty partly by how the site feels. The product app (`packages/twenty-front`) is a separate product-register surface, governed elsewhere.
## Users & Purpose
The primary audience varies by route, but the working assumption for partner-related pages is:
- **Who:** A budget-holding decision maker (founder, RevOps lead, or COO) shopping for a CRM implementation partner. Already on Twenty's site, evaluating a shortlist of partners.
- **Context:** Doing a side-by-side comparison across 25 candidates over a single browsing session. Will spend 3090 seconds on each profile before deciding whether to book a call.
- **Decision being made:** "Is this partner credible, the right size, the right specialty, and within budget? Do I trust them enough to commit 30 minutes to a discovery call?"
What the partner pages must do, in priority order:
1. Communicate credibility (real firm, real person, real work).
2. Surface fit signals fast (skills, region, languages, deployment expertise, budget range).
3. Give the visitor a confident "next step" affordance (book a call or vet via LinkedIn) without pressure.
## Desired Outcome
The redesign should make `/partners/profile/[slug]` feel like a *thoughtfully curated profile of a top-tier partner*, not a generic templated card. A visitor should leave thinking "this firm is serious" even if they don't book a call this session.
Specifically:
- **Confidence over information density.** A short, well-typeset profile beats a packed-but-busy one.
- **Editorial restraint.** White space, deliberate type hierarchy, and a few well-chosen details say more than dozens of small components.
- **Quiet conviction.** No hype copy, no growth-hack patterns, no "Trusted by" logo strips. The partner's own work and intro speak for themselves.
## Brand Personality
**Editorial · Founder-led · Considered.**
The site reads like a thoughtful indie publication, not a SaaS landing page. Serif headlines, plenty of whitespace, deliberate typographic rhythm. Quietly opinionated — Twenty has a point of view about CRM (open-source, customizable, well-designed) and the site reflects that without shouting.
Tonal anchors:
- Stripe's documentation for clarity, Linear's marketing for restraint, an editorial print magazine for typography choices.
## Anti-references
**Reject these patterns. They make the work read as generic AI / generic SaaS:**
- **Generic SaaS landing.** Big-number heroes, identical icon-grid cards, gradient text, navy + lime accent color schemes, "supercharge your workflow" language.
- **Corporate enterprise tone.** Stock photos of diverse handshakes. "Trusted by Fortune 500" logo strips as the primary credibility move. Trust-badge bars.
- **Bento templates.** Repetitive same-size cards. Vercel-style scroll-pin animations on every section.
- **Side-stripe borders, gradient text, glassmorphism, hero-metric templates, identical card grids** — see impeccable's shared absolute bans.
## Strategic Design Principles
1. **Typography carries the design.** The brand has a serif/sans/mono trio. Hierarchy is set by scale + weight contrast, not by color or borders.
2. **Restrained palette.** Tinted neutrals (black/white via CSS variables, with alpha-tone variants for text and borders) carry 90%+ of the surface. Accent color used sparingly when it appears at all.
3. **Whitespace is a feature.** Tight cards feel cheap. Pages should breathe.
4. **Asymmetry over grid.** A 12-col bento is the wrong shape for a profile page. Use asymmetric two-column layouts where one column does heavy lifting.
5. **One opinionated detail per page.** Each surface should have one moment of editorial conviction (a typographic flourish, a precise micro-interaction, a deliberate space) rather than five generic flourishes.
## Accessibility
**WCAG AA + keyboard + screen reader baseline:**
- All interactive elements reachable by keyboard, focus visible (`outline: 2px solid`, not just color shift).
- Semantic landmarks: `<header>`, `<main>`, `<nav>`, `<section aria-labelledby=…>`, headings in order.
- All images with informational content have alt text. Decorative icons have `aria-hidden="true"`.
- Body text ≥ 4.5:1 contrast; large text (≥18pt or 14pt bold) ≥ 3:1.
- Respect `prefers-reduced-motion`. Animations stop, don't slow.
- Forms have explicit labels. Errors are announced.
## Tech & Constraints
- Next.js 16 app router (Server Components by default, `'use client'` for interactivity).
- Linaria styled-components (`@linaria/react`) for zero-runtime CSS-in-JS.
- Lingui (`@lingui/react`) for i18n; never hardcode user-visible strings.
- Theme tokens in `packages/twenty-website/src/theme/`. Colors are CSS variables resolved to OKLCH-tinted neutrals.
- `@tabler/icons-react` for iconography (no Heroicons, no custom SVGs unless purposeful).
- `@radix-ui/react-*` for primitives (Popover etc) where headless behavior is needed.
## Out of Scope for This File
- Detailed visual tokens (colors, type scale, motion specs) live in `DESIGN.md`.
- Per-page IA decisions live in shape briefs (`docs/superpowers/specs/`).
+164
View File
@@ -0,0 +1,164 @@
<p align="center">
<a href="https://www.twenty.com">
<img src="./packages/twenty-website/public/images/core/logo.svg" width="100px" alt="Twenty logo" />
</a>
</p>
<h2 align="center">The #1 Open-Source CRM</h2>
<p align="center"><a href="https://twenty.com"><img src="./packages/twenty-website/public/images/readme/globe-icon.svg" width="12" height="12"/> Website</a> · <a href="https://docs.twenty.com"><img src="./packages/twenty-website/public/images/readme/book-icon.svg" width="12" height="12"/> Documentation</a> · <a href="https://github.com/orgs/twentyhq/projects/1"><img src="./packages/twenty-website/public/images/readme/map-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.webp" width="12" height="12"/> Figma</a></p>
<p align="center">
<a href="https://www.twenty.com">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="./packages/twenty-website/public/images/readme/github-cover-dark.webp" />
<source media="(prefers-color-scheme: light)" srcset="./packages/twenty-website/public/images/readme/github-cover-light.webp" />
<img src="./packages/twenty-website/public/images/readme/github-cover-light.webp" alt="Twenty banner" />
</picture>
</a>
</p>
<br />
# Why Twenty
Twenty gives technical teams the building blocks for a custom CRM that meets complex business needs and quickly adapts as the business evolves. Twenty is the CRM you build, ship, and version like the rest of your stack.
<a href="https://twenty.com/resources/why-twenty"><img src="./packages/twenty-website/public/images/readme/star-icon.svg" width="14" height="14"/> Learn more about why we built Twenty</a>
<br />
# Installation
### <img src="./packages/twenty-website/public/images/readme/globe-icon.svg" width="14" height="14"/> Cloud
The fastest way to get started. Sign up at [twenty.com](https://twenty.com) and spin up a workspace in under a minute, with no infrastructure to manage and always up to date.
### <img src="./packages/twenty-website/public/images/readme/book-icon.svg" width="14" height="14"/> Build an app
Scaffold a new app with the Twenty CLI:
```bash
npx create-twenty-app my-app
```
Define objects, fields, and views as code:
```ts
import { defineObject, FieldType } from 'twenty-sdk/define';
export default defineObject({
nameSingular: 'deal',
namePlural: 'deals',
labelSingular: 'Deal',
labelPlural: 'Deals',
fields: [
{ name: 'name', label: 'Name', type: FieldType.TEXT },
{ name: 'amount', label: 'Amount', type: FieldType.CURRENCY },
{ name: 'closeDate', label: 'Close Date', type: FieldType.DATE_TIME },
],
});
```
Then ship it to your workspace:
```bash
npx twenty app:publish --private
```
See the [app development guide](https://docs.twenty.com/developers/extend/apps/getting-started) for objects, views, agents, and logic functions.
### <img src="./packages/twenty-website/public/images/readme/rocket-icon.svg" width="14" height="14"/> Self-hosting
Run Twenty on your own infrastructure with [Docker Compose](https://docs.twenty.com/developers/self-host/capabilities/docker-compose), or contribute locally via the [local setup guide](https://docs.twenty.com/developers/contribute/capabilities/local-setup).
<br />
<br />
# Everything you need
Twenty gives you the building blocks of a modern CRM (objects, views, workflows, and agents) and lets you extend them as code. Here's a tour of what's in the box.
Want to go deeper? Read the <a href="https://docs.twenty.com/user-guide/introduction"><img src="./packages/twenty-website/public/images/readme/planner-icon.svg" width="14" height="14"/> User Guide</a> for product walkthroughs, or the <a href="https://docs.twenty.com"><img src="./packages/twenty-website/public/images/readme/book-icon.svg" width="14" height="14"/> Documentation</a> for developer reference.
<table align="center">
<tr>
<td width="50%">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="./packages/twenty-website/public/images/readme/v2-build-apps-dark.webp" />
<source media="(prefers-color-scheme: light)" srcset="./packages/twenty-website/public/images/readme/v2-build-apps-light.webp" />
<img src="./packages/twenty-website/public/images/readme/v2-build-apps-light.webp" alt="Create your apps" />
</picture>
<p align="center"><a href="https://docs.twenty.com/developers/extend/apps/getting-started"><img src="./packages/twenty-website/public/images/readme/code-icon.svg" width="16" height="16"/> Learn more about apps in doc</a></p>
</td>
<td width="50%">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="./packages/twenty-website/public/images/readme/v2-version-control-dark.webp" />
<source media="(prefers-color-scheme: light)" srcset="./packages/twenty-website/public/images/readme/v2-version-control-light.webp" />
<img src="./packages/twenty-website/public/images/readme/v2-version-control-light.webp" alt="Stay on top with version control" />
</picture>
<p align="center"><a href="https://docs.twenty.com/developers/extend/apps/publishing"><img src="./packages/twenty-website/public/images/readme/monitor-icon.svg" width="16" height="16"/> Learn more about version control in doc</a></p>
</td>
</tr>
<tr>
<td width="50%">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="./packages/twenty-website/public/images/readme/v2-all-tools-dark.webp" />
<source media="(prefers-color-scheme: light)" srcset="./packages/twenty-website/public/images/readme/v2-all-tools-light.webp" />
<img src="./packages/twenty-website/public/images/readme/v2-all-tools-light.webp" alt="All the tools you need to build anything" />
</picture>
<p align="center"><a href="https://docs.twenty.com/developers/extend/apps/building"><img src="./packages/twenty-website/public/images/readme/rocket-icon.svg" width="16" height="16"/> Learn more about primitives in doc</a></p>
</td>
<td width="50%">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="./packages/twenty-website/public/images/readme/v2-tools-dark.webp" />
<source media="(prefers-color-scheme: light)" srcset="./packages/twenty-website/public/images/readme/v2-tools-light.webp" />
<img src="./packages/twenty-website/public/images/readme/v2-tools-light.webp" alt="Customize your layouts" />
</picture>
<p align="center"><a href="https://docs.twenty.com/user-guide/layout/overview"><img src="./packages/twenty-website/public/images/readme/planner-icon.svg" width="16" height="16"/> Learn more about layouts in doc</a></p>
</td>
</tr>
<tr>
<td width="50%">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="./packages/twenty-website/public/images/readme/v2-ai-agents-dark.webp" />
<source media="(prefers-color-scheme: light)" srcset="./packages/twenty-website/public/images/readme/v2-ai-agents-light.webp" />
<img src="./packages/twenty-website/public/images/readme/v2-ai-agents-light.webp" alt="AI agents and chats" />
</picture>
<p align="center"><a href="https://docs.twenty.com/user-guide/ai/overview"><img src="./packages/twenty-website/public/images/readme/message-icon.svg" width="16" height="16"/> Learn more about AI in doc</a></p>
</td>
<td width="50%">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="./packages/twenty-website/public/images/readme/v2-crm-tools-dark.webp" />
<source media="(prefers-color-scheme: light)" srcset="./packages/twenty-website/public/images/readme/v2-crm-tools-light.webp" />
<img src="./packages/twenty-website/public/images/readme/v2-crm-tools-light.webp" alt="Plus all the tools of a good CRM" />
</picture>
<p align="center"><a href="https://docs.twenty.com/user-guide/introduction"><img src="./packages/twenty-website/public/images/readme/star-icon.svg" width="16" height="16"/> Learn more about CRM features in doc</a></p>
</td>
</tr>
</table>
<br />
# Stack
- <a href="https://www.typescriptlang.org/"><img src="./packages/twenty-website/public/images/readme/stack-typescript.svg" width="14" height="14"/> TypeScript</a>
- <a href="https://nx.dev/"><img src="./packages/twenty-website/public/images/readme/stack-nx.svg" width="14" height="14"/> Nx</a>
- <a href="https://nestjs.com/"><img src="./packages/twenty-website/public/images/readme/stack-nestjs.svg" width="14" height="14"/> NestJS</a>, with <a href="https://bullmq.io/">BullMQ</a>, <a href="https://www.postgresql.org/"><img src="./packages/twenty-website/public/images/readme/stack-postgresql.svg" width="14" height="14"/> PostgreSQL</a>, <a href="https://redis.io/"><img src="./packages/twenty-website/public/images/readme/stack-redis.svg" width="14" height="14"/> Redis</a>
- <a href="https://reactjs.org/"><img src="./packages/twenty-website/public/images/readme/stack-react.svg" width="14" height="14"/> React</a>, with <a href="https://jotai.org/">Jotai</a>, <a href="https://linaria.dev/">Linaria</a> and <a href="https://lingui.dev/">Lingui</a>
# Thanks
<p align="center">
<a href="https://greptile.com"><img src="./packages/twenty-website/public/images/readme/greptile.webp" height="28" alt="Greptile" /></a>
&nbsp;&nbsp;&nbsp;&nbsp;
<a href="https://sentry.io/"><img src="./packages/twenty-website/public/images/readme/sentry.webp" height="28" alt="Sentry" /></a>
&nbsp;&nbsp;&nbsp;&nbsp;
<a href="https://crowdin.com/"><img src="./packages/twenty-website/public/images/readme/crowdin.webp" height="28" alt="Crowdin" /></a>
</p>
Thanks to these amazing services that we use and recommend for code review (Greptile), catching bugs (Sentry) and translating (Crowdin).
# Join the Community
<p><a href="https://github.com/twentyhq/twenty"><img src="./packages/twenty-website/public/images/readme/star-icon.svg" width="12" height="12"/> Star the repo</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://github.com/twentyhq/twenty/discussions"><img src="./packages/twenty-website/public/images/readme/message-icon.svg" width="12" height="12"/> Feature requests</a> · <a href="https://github.com/orgs/twentyhq/projects/1/views/35"><img src="./packages/twenty-website/public/images/readme/rocket-icon.svg" width="12" height="12"/> Releases</a> · <a href="https://twitter.com/twentycrm"><img src="./packages/twenty-website/public/images/readme/x-icon.svg" width="12" height="12"/> X</a> · <a href="https://www.linkedin.com/company/twenty/"><img src="./packages/twenty-website/public/images/readme/linkedin-icon.svg" width="12" height="12"/> LinkedIn</a> · <a href="https://twenty.crowdin.com/twenty"><img src="./packages/twenty-website/public/images/readme/language-icon.svg" width="12" height="12"/> Crowdin</a> · <a href="https://github.com/twentyhq/twenty/contribute"><img src="./packages/twenty-website/public/images/readme/code-icon.svg" width="12" height="12"/> Contribute</a></p>
+139
View File
@@ -0,0 +1,139 @@
name: twenty
services:
server:
image: twentycrm/twenty:${TAG:-latest}
volumes:
- server-local-data:/app/packages/twenty-server/.local-storage
ports:
- "3001: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
SERVER_URL: ${SERVER_URL}
REACT_APP_SERVER_BASE_URL: ${SERVER_URL}
REDIS_URL: ${REDIS_URL:-redis://redis:6379}
DISABLE_DB_MIGRATIONS: ${DISABLE_DB_MIGRATIONS}
DISABLE_CRON_JOBS_REGISTRATION: ${DISABLE_CRON_JOBS_REGISTRATION}
STORAGE_TYPE: ${STORAGE_TYPE}
STORAGE_S3_REGION: ${STORAGE_S3_REGION}
STORAGE_S3_NAME: ${STORAGE_S3_NAME}
STORAGE_S3_ENDPOINT: ${STORAGE_S3_ENDPOINT}
ENCRYPTION_KEY: ${ENCRYPTION_KEY}
FALLBACK_ENCRYPTION_KEY: ${FALLBACK_ENCRYPTION_KEY}
APP_SECRET: ${APP_SECRET:-}
# MESSAGING_PROVIDER_GMAIL_ENABLED: ${MESSAGING_PROVIDER_GMAIL_ENABLED}
# CALENDAR_PROVIDER_GOOGLE_ENABLED: ${CALENDAR_PROVIDER_GOOGLE_ENABLED}
# AUTH_GOOGLE_CLIENT_ID: ${AUTH_GOOGLE_CLIENT_ID}
# AUTH_GOOGLE_CLIENT_SECRET: ${AUTH_GOOGLE_CLIENT_SECRET}
# AUTH_GOOGLE_CALLBACK_URL: ${AUTH_GOOGLE_CALLBACK_URL}
# AUTH_GOOGLE_APIS_CALLBACK_URL: ${AUTH_GOOGLE_APIS_CALLBACK_URL}
# CALENDAR_PROVIDER_MICROSOFT_ENABLED: ${CALENDAR_PROVIDER_MICROSOFT_ENABLED}
# MESSAGING_PROVIDER_MICROSOFT_ENABLED: ${MESSAGING_PROVIDER_MICROSOFT_ENABLED}
# AUTH_MICROSOFT_ENABLED: ${AUTH_MICROSOFT_ENABLED}
# AUTH_MICROSOFT_CLIENT_ID: ${AUTH_MICROSOFT_CLIENT_ID}
# AUTH_MICROSOFT_CLIENT_SECRET: ${AUTH_MICROSOFT_CLIENT_SECRET}
# AUTH_MICROSOFT_CALLBACK_URL: ${AUTH_MICROSOFT_CALLBACK_URL}
# AUTH_MICROSOFT_APIS_CALLBACK_URL: ${AUTH_MICROSOFT_APIS_CALLBACK_URL}
# EMAIL_FROM_ADDRESS: ${EMAIL_FROM_ADDRESS:-contact@yourdomain.com}
# EMAIL_FROM_NAME: ${EMAIL_FROM_NAME:-"John from YourDomain"}
# EMAIL_DRIVER: ${EMAIL_DRIVER:-smtp}
# EMAIL_SMTP_HOST: ${EMAIL_SMTP_HOST:-smtp.gmail.com}
# EMAIL_SMTP_PORT: ${EMAIL_SMTP_PORT:-465}
# EMAIL_SMTP_USER: ${EMAIL_SMTP_USER:-}
# EMAIL_SMTP_PASSWORD: ${EMAIL_SMTP_PASSWORD:-}
depends_on:
db:
condition: service_healthy
redis:
condition: service_healthy
healthcheck:
test: curl --fail http://localhost:3000/healthz
interval: 5s
timeout: 5s
retries: 20
restart: always
worker:
image: twentycrm/twenty:${TAG:-latest}
volumes:
- server-local-data:/app/packages/twenty-server/.local-storage
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
SERVER_URL: ${SERVER_URL}
REDIS_URL: ${REDIS_URL:-redis://redis:6379}
DISABLE_DB_MIGRATIONS: "true" # it already runs on the server
DISABLE_CRON_JOBS_REGISTRATION: "true" # 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}
ENCRYPTION_KEY: ${ENCRYPTION_KEY}
FALLBACK_ENCRYPTION_KEY: ${FALLBACK_ENCRYPTION_KEY}
APP_SECRET: ${APP_SECRET:-}
# MESSAGING_PROVIDER_GMAIL_ENABLED: ${MESSAGING_PROVIDER_GMAIL_ENABLED}
# CALENDAR_PROVIDER_GOOGLE_ENABLED: ${CALENDAR_PROVIDER_GOOGLE_ENABLED}
# AUTH_GOOGLE_CLIENT_ID: ${AUTH_GOOGLE_CLIENT_ID}
# AUTH_GOOGLE_CLIENT_SECRET: ${AUTH_GOOGLE_CLIENT_SECRET}
# AUTH_GOOGLE_CALLBACK_URL: ${AUTH_GOOGLE_CALLBACK_URL}
# AUTH_GOOGLE_APIS_CALLBACK_URL: ${AUTH_GOOGLE_APIS_CALLBACK_URL}
# CALENDAR_PROVIDER_MICROSOFT_ENABLED: ${CALENDAR_PROVIDER_MICROSOFT_ENABLED}
# MESSAGING_PROVIDER_MICROSOFT_ENABLED: ${MESSAGING_PROVIDER_MICROSOFT_ENABLED}
# AUTH_MICROSOFT_ENABLED: ${AUTH_MICROSOFT_ENABLED}
# AUTH_MICROSOFT_CLIENT_ID: ${AUTH_MICROSOFT_CLIENT_ID}
# AUTH_MICROSOFT_CLIENT_SECRET: ${AUTH_MICROSOFT_CLIENT_SECRET}
# AUTH_MICROSOFT_CALLBACK_URL: ${AUTH_MICROSOFT_CALLBACK_URL}
# AUTH_MICROSOFT_APIS_CALLBACK_URL: ${AUTH_MICROSOFT_APIS_CALLBACK_URL}
# EMAIL_FROM_ADDRESS: ${EMAIL_FROM_ADDRESS:-contact@yourdomain.com}
# EMAIL_FROM_NAME: ${EMAIL_FROM_NAME:-"John from YourDomain"}
# EMAIL_DRIVER: ${EMAIL_DRIVER:-smtp}
# EMAIL_SMTP_HOST: ${EMAIL_SMTP_HOST:-smtp.gmail.com}
# EMAIL_SMTP_PORT: ${EMAIL_SMTP_PORT:-465}
# EMAIL_SMTP_USER: ${EMAIL_SMTP_USER:-}
# EMAIL_SMTP_PASSWORD: ${EMAIL_SMTP_PASSWORD:-}
depends_on:
db:
condition: service_healthy
server:
condition: service_healthy
restart: always
db:
image: postgres:16
volumes:
- db-data:/var/lib/postgresql/data
environment:
POSTGRES_DB: ${PG_DATABASE_NAME:-default}
POSTGRES_PASSWORD: ${PG_DATABASE_PASSWORD:-postgres}
POSTGRES_USER: ${PG_DATABASE_USER:-postgres}
healthcheck:
test: pg_isready -U ${PG_DATABASE_USER:-postgres} -h localhost -d postgres
interval: 5s
timeout: 5s
retries: 10
restart: always
redis:
image: redis
restart: always
command: ["--maxmemory-policy", "noeviction"]
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 5s
retries: 10
volumes:
db-data:
server-local-data:
+284
View File
@@ -0,0 +1,284 @@
{
"workspaceLayout": {
"appsDir": "packages",
"libsDir": "packages"
},
"namedInputs": {
"default": ["{projectRoot}/**/*"],
"excludeStories": [
"default",
"!{projectRoot}/.storybook/*",
"!{projectRoot}/**/tsconfig.storybook.json",
"!{projectRoot}/**/*.stories.(ts|tsx)",
"!{projectRoot}/**/__stories__/*"
],
"excludeTests": [
"default",
"!{projectRoot}/**/jest.config.(js|ts)",
"!{projectRoot}/**/tsconfig.spec.json",
"!{projectRoot}/**/*.test.(ts|tsx)",
"!{projectRoot}/**/*.spec.(ts|tsx)",
"!{projectRoot}/**/*.integration-spec.ts",
"!{projectRoot}/**/__tests__/*"
],
"production": [
"default",
"excludeStories",
"excludeTests",
"!{projectRoot}/**/__mocks__/*",
"!{projectRoot}/**/testing/*"
]
},
"targetDefaults": {
"build": {
"cache": true,
"inputs": ["^production", "production"],
"dependsOn": ["^build"]
},
"start": {
"cache": false,
"dependsOn": ["^build"]
},
"lint": {
"executor": "nx:run-commands",
"cache": true,
"options": {
"cwd": "{projectRoot}",
"command": "npx oxlint -c .oxlintrc.json . && (npx oxfmt --check . || (echo 'ERROR: oxfmt formatting check failed! Fix with: npx nx lint --configuration=fix' && false))"
},
"configurations": {
"ci": {},
"fix": {
"command": "npx oxlint --fix -c .oxlintrc.json . && npx oxfmt ."
}
},
"dependsOn": ["^build", "twenty-oxlint-rules:build"]
},
"lint:diff-with-main": {
"executor": "nx:run-commands",
"cache": false,
"dependsOn": ["twenty-oxlint-rules:build"],
"options": {
"command": "FILES=$(git diff --name-only --diff-filter=d main -- {projectRoot}/ | grep -E '{args.pattern}'); [ -z \"$FILES\" ] && echo 'No changed files.' || (npx oxlint -c {projectRoot}/.oxlintrc.json $FILES && (npx oxfmt --check $FILES || (echo 'ERROR: oxfmt formatting check failed! Fix with: npx nx lint:diff-with-main --configuration=fix' && false)))",
"pattern": "\\.(ts|tsx|js|jsx)$"
},
"configurations": {
"fix": {
"command": "FILES=$(git diff --name-only --diff-filter=d main -- {projectRoot}/ | grep -E '{args.pattern}'); [ -z \"$FILES\" ] && echo 'No changed files.' || (npx oxlint --fix -c {projectRoot}/.oxlintrc.json $FILES && npx oxfmt $FILES)"
}
}
},
"fmt": {
"executor": "nx:run-commands",
"cache": true,
"options": {
"cwd": "{projectRoot}",
"command": "npx oxfmt --check {args.files} {args.write}",
"files": ".",
"write": ""
},
"configurations": {
"ci": {},
"fix": {
"command": "npx oxfmt {args.files}"
}
},
"dependsOn": ["^build"]
},
"typecheck": {
"executor": "nx:run-commands",
"cache": true,
"options": {
"cwd": "{projectRoot}",
"command": "tsgo -p tsconfig.json"
},
"configurations": {
"watch": {
"command": "tsgo -p tsconfig.json --watch"
}
},
"dependsOn": ["^build"]
},
"test": {
"executor": "@nx/jest:jest",
"cache": true,
"dependsOn": ["^build"],
"inputs": [
"^default",
"excludeStories",
"{workspaceRoot}/jest.preset.js"
],
"outputs": ["{projectRoot}/coverage"],
"options": {
"jestConfig": "{projectRoot}/jest.config.mjs",
"silent": true,
"coverage": true,
"coverageReporters": ["text-summary"],
"cacheDirectory": "../../.cache/jest/{projectRoot}"
},
"configurations": {
"ci": {
"ci": true,
"maxWorkers": 1
},
"coverage": {
"coverageReporters": ["lcov", "text"]
},
"watch": {
"watch": true
}
}
},
"test:e2e": {
"cache": true,
"dependsOn": ["^build"]
},
"set-local-version": {
"executor": "nx:run-commands",
"cache": false,
"options": {
"cwd": "{projectRoot}",
"command": "node -e \"const fs=require('fs'),p=JSON.parse(fs.readFileSync('package.json','utf8'));p.version='{args.releaseVersion}';fs.writeFileSync('package.json',JSON.stringify(p,null,2)+'\\n');\""
}
},
"storybook:build": {
"executor": "nx:run-commands",
"cache": true,
"inputs": ["^default", "excludeTests"],
"outputs": ["{projectRoot}/{options.output-dir}"],
"options": {
"cwd": "{projectRoot}",
"command": "NODE_OPTIONS='--max-old-space-size=10240' storybook build --test",
"output-dir": "storybook-static",
"config-dir": ".storybook"
},
"dependsOn": ["^build"]
},
"storybook:serve:dev": {
"executor": "nx:run-commands",
"cache": true,
"dependsOn": ["^build"],
"options": {
"cwd": "{projectRoot}",
"command": "storybook dev",
"config-dir": ".storybook"
}
},
"storybook:serve:static": {
"executor": "nx:run-commands",
"dependsOn": ["storybook:build"],
"options": {
"cwd": "{projectRoot}",
"command": "npx http-server {args.staticDir} -a={args.host} --port={args.port} --silent={args.silent}",
"staticDir": "storybook-static",
"host": "localhost",
"port": 6006,
"silent": true
}
},
"storybook:test": {
"executor": "nx:run-commands",
"cache": true,
"inputs": ["^default", "excludeTests"],
"outputs": ["{projectRoot}/coverage/storybook"],
"options": {
"cwd": "{projectRoot}",
"command": "vitest run --coverage --shard={args.shard}",
"shard": "1/1"
}
},
"storybook:test:no-coverage": {
"executor": "nx:run-commands",
"inputs": ["^default", "excludeTests"],
"options": {
"cwd": "{projectRoot}",
"command": "vitest run --shard={args.shard}",
"shard": "1/1"
}
},
"storybook:coverage": {
"executor": "nx:run-commands",
"cache": true,
"inputs": [
"^default",
"excludeTests",
"{projectRoot}/coverage/storybook/coverage-storybook.json"
],
"outputs": [
"{projectRoot}/coverage/storybook",
"!{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}",
"coverageDir": "coverage/storybook",
"reporter": "lcov",
"checkCoverage": true
},
"configurations": {
"text": {
"reporter": "text"
}
}
},
"@nx/jest:jest": {
"cache": true,
"inputs": [
"^default",
"excludeStories",
"{workspaceRoot}/jest.preset.js"
],
"options": {
"passWithNoTests": true
},
"configurations": {
"ci": {
"ci": true,
"codeCoverage": true
}
}
},
"@nx/vite:build": {
"cache": true,
"dependsOn": ["^build"],
"inputs": ["default", "^default"]
},
"@nx/vitest:test": {
"cache": true,
"inputs": ["default", "^default"]
}
},
"generators": {
"@nx/react": {
"application": {
"style": "@linaria/react",
"bundler": "vite",
"compiler": "swc",
"unitTestRunner": "jest",
"projectNameAndRootFormat": "derived"
},
"library": {
"style": "@linaria/react",
"bundler": "vite",
"compiler": "swc",
"unitTestRunner": "jest",
"projectNameAndRootFormat": "derived"
},
"component": {
"style": "@linaria/react"
}
}
},
"tasksRunnerOptions": {
"default": {
"options": {
"cacheableOperations": ["storybook:build"]
}
}
},
"useInferencePlugins": false,
"defaultBase": "main",
"tui": {
"enabled": false
},
"analytics": false
}
File diff suppressed because one or more lines are too long
+19
View File
@@ -0,0 +1,19 @@
{
"compileOnSave": false,
"compilerOptions": {
"rootDir": ".",
"sourceMap": true,
"declaration": false,
"moduleResolution": "node",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"importHelpers": true,
"target": "es2018",
"module": "esnext",
"lib": ["es2020", "dom"],
"skipLibCheck": true,
"skipDefaultLibCheck": true,
"resolveJsonModule": true
},
"exclude": ["node_modules", "tmp"]
}