diff --git a/eslint.config.mjs b/eslint.config.mjs index 9a94eeee30..454dfda53d 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -210,6 +210,8 @@ export default [ 'unused-imports/no-unused-vars': 'off', // Enforce JSX tags on separate lines to prevent Crowdin translation issues '@nx/workspace-mdx-component-newlines': 'error', + // Disallow angle bracket placeholders to prevent Crowdin translation errors + '@nx/workspace-no-angle-bracket-placeholders': 'error', }, }, ]; diff --git a/packages/twenty-docs/developers/api-and-webhooks/api.mdx b/packages/twenty-docs/developers/api-and-webhooks/api.mdx index 585a24da44..6fd70229ab 100644 --- a/packages/twenty-docs/developers/api-and-webhooks/api.mdx +++ b/packages/twenty-docs/developers/api-and-webhooks/api.mdx @@ -20,7 +20,7 @@ Twenty’s API uses API keys for authentication. Every request to protected endp * **Auth Header:** Once you have an API key, include it in the `Authorization` header of your HTTP requests. Use the Bearer token scheme. For example: ``` Authorization: Bearer YOUR_API_KEY - ``` + ``` Replace `YOUR_API_KEY` with the key you obtained. This header must be present on **all API requests**. If the token is missing or invalid, the API will respond with an authentication error (HTTP 401 Unauthorized). @@ -28,13 +28,13 @@ Twenty’s API uses API keys for authentication. Every request to protected endp All resources can be accessed and via REST or GraphQL. * **Cloud:** `https://api.twenty.com/` or your custom domain / sub-domain -* **Self-Hosted Instances:** If you are running Twenty on your own server, use your own domain in place of `api.twenty.com` (for example, `https:///rest/`). +* **Self-Hosted Instances:** If you are running Twenty on your own server, use your own domain in place of `api.twenty.com` (for example, `https://{your-domain}/rest/`). Endpoints are grouped into two categories: **Core API** and **Metadata API**. The **Core API** deals with primary CRM data (e.g. people, companies, notes, tasks), while the **Metadata API** covers configuration data (like custom fields or object definitions). Most integrations will primarily use the Core API. ### Core API Accessed on `/rest/` or `/graphql/`. -The **Core API** serves as a unified interface for managing core CRM entities (people, companies, notes, tasks) and their relationships, offering **both REST and GraphQL** interaction models. +The **Core API** serves as a unified interface for managing core CRM entities (people, companies, notes, tasks) and their relationships, offering **both REST and GraphQL** interaction models. ### Metadata API Accessed on `/rest/metadata/` or `/metadata/`. @@ -46,4 +46,4 @@ The Metadata API endpoints allow you to retrieve information about your schema a * `GET /rest/metadata/objects/{objectName}` – Get metadata for a specific object (e.g., `people`, `companies`). * `GET /rest/metadata/picklists` – Retrieve picklist (dropdown) field options defined in the CRM. -Typically, the metadata endpoints are used to understand the structure of data (for dynamic integrations or form-building) rather than to manage actual records. They are read-only in most cases. Authentication is required for these as well (use your API key). \ No newline at end of file +Typically, the metadata endpoints are used to understand the structure of data (for dynamic integrations or form-building) rather than to manage actual records. They are read-only in most cases. Authentication is required for these as well (use your API key). diff --git a/packages/twenty-docs/developers/backend-development/server-commands.mdx b/packages/twenty-docs/developers/backend-development/server-commands.mdx index 77614c52a6..ee9b74896b 100644 --- a/packages/twenty-docs/developers/backend-development/server-commands.mdx +++ b/packages/twenty-docs/developers/backend-development/server-commands.mdx @@ -9,7 +9,7 @@ image: /images/user-guide/kanban-views/kanban.png ## Useful commands These commands should be executed from packages/twenty-server folder. -From any other folder you can run `npx nx twenty-server` (or `npx nx run twenty-server:`). +From any other folder you can run `npx nx {command} twenty-server` (or `npx nx run twenty-server:{command}`). ### First time setup @@ -72,11 +72,11 @@ Make sure to back up any data you want to keep before running this command. ## Tech Stack -Twenty primarily uses NestJS for the backend. +Twenty primarily uses NestJS for the backend. -Prisma was the first ORM we used. But in order to allow users to create custom fields and custom objects, a lower-level made more sense as we need to have fine-grained control. The project now uses TypeORM. +Prisma was the first ORM we used. But in order to allow users to create custom fields and custom objects, a lower-level made more sense as we need to have fine-grained control. The project now uses TypeORM. -Here's what the tech stack now looks like. +Here's what the tech stack now looks like. **Core** @@ -98,6 +98,6 @@ Here's what the tech stack now looks like. - [ESLint](https://eslint.org/) **Development** -- [AWS EKS](https://aws.amazon.com/eks/) +- [AWS EKS](https://aws.amazon.com/eks/) diff --git a/packages/twenty-docs/developers/local-setup.mdx b/packages/twenty-docs/developers/local-setup.mdx index 664ab5f883..7640f3837d 100644 --- a/packages/twenty-docs/developers/local-setup.mdx +++ b/packages/twenty-docs/developers/local-setup.mdx @@ -49,10 +49,9 @@ git config --global user.email "youremail@domain.com" ``` 3. Install nvm, node.js and yarn + - Use `nvm` to install the correct `node` version. The `.nvmrc` ensures all contributors use the same version. - ```bash diff --git a/packages/twenty-docs/developers/self-hosting/setup.mdx b/packages/twenty-docs/developers/self-hosting/setup.mdx index 8796e951a7..4d534c4348 100644 --- a/packages/twenty-docs/developers/self-hosting/setup.mdx +++ b/packages/twenty-docs/developers/self-hosting/setup.mdx @@ -81,8 +81,8 @@ IS_CONFIG_VARIABLES_IN_DB_ENABLED=false 1. Go to [Credentials](https://console.cloud.google.com/apis/credentials) 2. Create OAuth 2.0 Client ID 3. Add these redirect URIs: - - `https:///auth/google/redirect` (for SSO) - - `https:///auth/google-apis/get-access-token` (for integrations) + - `https://{your-domain}/auth/google/redirect` (for SSO) + - `https://{your-domain}/auth/google-apis/get-access-token` (for integrations) ### Configure in Twenty @@ -91,10 +91,10 @@ IS_CONFIG_VARIABLES_IN_DB_ENABLED=false 3. Set these variables: - `MESSAGING_PROVIDER_GMAIL_ENABLED=true` - `CALENDAR_PROVIDER_GOOGLE_ENABLED=true` - - `AUTH_GOOGLE_CLIENT_ID=` - - `AUTH_GOOGLE_CLIENT_SECRET=` - - `AUTH_GOOGLE_CALLBACK_URL=https:///auth/google/redirect` - - `AUTH_GOOGLE_APIS_CALLBACK_URL=https:///auth/google-apis/get-access-token` + - `AUTH_GOOGLE_CLIENT_ID={client-id}` + - `AUTH_GOOGLE_CLIENT_SECRET={client-secret}` + - `AUTH_GOOGLE_CALLBACK_URL=https://{your-domain}/auth/google/redirect` + - `AUTH_GOOGLE_APIS_CALLBACK_URL=https://{your-domain}/auth/google-apis/get-access-token` **Environment-only mode:** If you set `IS_CONFIG_VARIABLES_IN_DB_ENABLED=false`, add these variables to your `.env` file instead. @@ -140,8 +140,8 @@ Note: "Mail.ReadWrite" and "Mail.Send" are only mandatory if you want to send em ### Authorized redirect URIs You need to add the following redirect URIs to your project: -- `https:///auth/microsoft/redirect` if you want to use Microsoft SSO -- `https:///auth/microsoft-apis/get-access-token` +- `https://{your-domain}/auth/microsoft/redirect` if you want to use Microsoft SSO +- `https://{your-domain}/auth/microsoft-apis/get-access-token` ### Configure in Twenty @@ -151,10 +151,10 @@ You need to add the following redirect URIs to your project: - `MESSAGING_PROVIDER_MICROSOFT_ENABLED=true` - `CALENDAR_PROVIDER_MICROSOFT_ENABLED=true` - `AUTH_MICROSOFT_ENABLED=true` - - `AUTH_MICROSOFT_CLIENT_ID=` - - `AUTH_MICROSOFT_CLIENT_SECRET=` - - `AUTH_MICROSOFT_CALLBACK_URL=https:///auth/microsoft/redirect` - - `AUTH_MICROSOFT_APIS_CALLBACK_URL=https:///auth/microsoft-apis/get-access-token` + - `AUTH_MICROSOFT_CLIENT_ID={client-id}` + - `AUTH_MICROSOFT_CLIENT_SECRET={client-secret}` + - `AUTH_MICROSOFT_CALLBACK_URL=https://{your-domain}/auth/microsoft/redirect` + - `AUTH_MICROSOFT_APIS_CALLBACK_URL=https://{your-domain}/auth/microsoft-apis/get-access-token` **Environment-only mode:** If you set `IS_CONFIG_VARIABLES_IN_DB_ENABLED=false`, add these variables to your `.env` file instead. diff --git a/packages/twenty-docs/developers/self-hosting/upgrade-guide.mdx b/packages/twenty-docs/developers/self-hosting/upgrade-guide.mdx index e2e192e4a7..7beccc9fc3 100644 --- a/packages/twenty-docs/developers/self-hosting/upgrade-guide.mdx +++ b/packages/twenty-docs/developers/self-hosting/upgrade-guide.mdx @@ -8,9 +8,9 @@ image: /images/user-guide/notes/notes_header.png ## General guidelines -**Always make sure to back up your database before starting the upgrade process** by running `docker exec -it pg_dumpall -U > databases_backup.sql`. +**Always make sure to back up your database before starting the upgrade process** by running `docker exec -it {db_container_name_or_id} pg_dumpall -U {postgres_user} > databases_backup.sql`. -To restore backup, run `cat databases_backup.sql | docker exec -i psql -U `. +To restore backup, run `cat databases_backup.sql | docker exec -i {db_container_name_or_id} psql -U {postgres_user}`. If you used Docker Compose, follow these steps: @@ -156,7 +156,7 @@ If you want to migrate your database to the new postgres:16 image, please follow 1. Dump your database from the old postgres-spilo container ``` -docker exec -it twenty-db-1 sh +docker exec -it twenty-db-1 sh pg_dump -U {YOUR_POSTGRES_USER} -d {YOUR_POSTGRES_DB} > databases_backup.sql exit docker cp twenty-db-1:/home/postgres/databases_backup.sql . @@ -249,7 +249,7 @@ The `yarn command:prod upgrade-0.34` takes care of the data migration of all wor - Removed: `FRONT_BASE_URL` - Added: `FRONT_DOMAIN`, `FRONT_PROTOCOL`, `FRONT_PORT` -We have updated the way we handle the frontend URL. +We have updated the way we handle the frontend URL. You can now set the frontend URL using the `FRONT_DOMAIN`, `FRONT_PROTOCOL` and `FRONT_PORT` variables. If FRONT_DOMAIN is not set, the frontend URL will fall back to `SERVER_URL`. @@ -321,7 +321,7 @@ The `yarn command:prod upgrade-0.31` takes care of the data migration of all wor Upgrade your Twenty instance to use v0.30.0 image -**Breaking change**: +**Breaking change**: To enhance performances, Twenty now requires redis cache to be configured. We have updated our [docker-compose.yml](https://raw.githubusercontent.com/twentyhq/twenty/main/packages/twenty-docker/docker-compose.yml) to reflect this. Make sure to update your configuration and to update your environment variables accordingly: ``` @@ -330,7 +330,7 @@ REDIS_PORT={your-redis-port} CACHE_STORAGE_TYPE=redis ``` -**Schema and data migration**: +**Schema and data migration**: ``` yarn database:migrate:prod yarn command:prod upgrade-0.30 diff --git a/packages/twenty-docs/package.json b/packages/twenty-docs/package.json index cc9fa18b12..9753a1a065 100644 --- a/packages/twenty-docs/package.json +++ b/packages/twenty-docs/package.json @@ -7,7 +7,8 @@ "scripts": { "dev": "mintlify dev", "build": "mintlify build", - "fix-links": "bash scripts/fix-translated-links.sh" + "fix-links": "bash scripts/fix-translated-links.sh", + "lint": "eslint **/*.mdx --max-warnings=0" }, "dependencies": { "mintlify": "latest" diff --git a/packages/twenty-docs/user-guide/getting-started/getting-around-twenty.mdx b/packages/twenty-docs/user-guide/getting-started/getting-around-twenty.mdx index 3424b15cfb..c5e561b9b4 100644 --- a/packages/twenty-docs/user-guide/getting-started/getting-around-twenty.mdx +++ b/packages/twenty-docs/user-guide/getting-started/getting-around-twenty.mdx @@ -24,6 +24,7 @@ On the left side, from the top to the bottom, you’ll be able to: Please note that our API documentation is accessible under the Settings section and not the User Guide. + - Have direct access to your **Favourites views**. Favourites are unique for each user. - Switch between different objects - **Create automations** using workflows diff --git a/packages/twenty-docs/user-guide/getting-started/migrating-from-other-crms.mdx b/packages/twenty-docs/user-guide/getting-started/migrating-from-other-crms.mdx index 86fe7665ad..3f941cf214 100644 --- a/packages/twenty-docs/user-guide/getting-started/migrating-from-other-crms.mdx +++ b/packages/twenty-docs/user-guide/getting-started/migrating-from-other-crms.mdx @@ -42,19 +42,20 @@ Use the CSV import via the Command Menu `Cmd + K` (Mac) or `Ctrl + K` (Windows) ### Data Formatting Issues - **Email addresses** - remove duplicates (People object requirement) - **Domain** - remove duplicates (Companies object requirement) + Please note that domain URLs created by the synchronization with your mailbox and calendar have the following format ```https://domain.com``` - **Date formats** - ensure consistent formatting (YYYY-MM-DD) or edit this format under Settings → Experience -- **Phone numbers** - use international format (+1234567890) +- **Phone numbers** - use international format (+1234567890) ### Relationship Mapping To import relations between records using the csv import function, you can use the following fields - **Use Twenty IDs** for complex relationships - **Use email addresses** to link People records - **Use domain names** to link Company records -- **Use any other field you set as unique**, which can be done in the Data Model section. +- **Use any other field you set as unique**, which can be done in the Data Model section. Read our [import-export data guide](/user-guide/getting-started/import-export-data) for detailed instructions on creating relationships during CSV import. ## Professional Help @@ -64,7 +65,7 @@ Read our [import-export data guide](/user-guide/getting-started/import-export-da Discover our [implementation services](/user-guide/getting-started/implementation-services). -## Migrating from Self-Hosted to Cloud +## Migrating from Self-Hosted to Cloud If you're moving from Twenty self-hosted to Twenty Cloud: 1. **Export your data** from your self-hosted instance @@ -73,11 +74,11 @@ If you're moving from Twenty self-hosted to Twenty Cloud: ## Post-Migration Checklist - All data imported successfully - Custom fields working correctly - User permissions configured - Email/calendar sync connected - Critical workflows recreated and tested - Team trained on new system + All data imported successfully + Custom fields working correctly + User permissions configured + Email/calendar sync connected + Critical workflows recreated and tested + Team trained on new system diff --git a/tools/eslint-rules/index.ts b/tools/eslint-rules/index.ts index b1bd6178f1..e95e43de18 100644 --- a/tools/eslint-rules/index.ts +++ b/tools/eslint-rules/index.ts @@ -30,6 +30,10 @@ import { rule as mdxComponentNewlines, RULE_NAME as mdxComponentNewlinesName, } from './rules/mdx-component-newlines'; +import { + rule as noAngleBracketPlaceholders, + RULE_NAME as noAngleBracketPlaceholdersName, +} from './rules/no-angle-bracket-placeholders'; import { rule as noHardcodedColors, RULE_NAME as noHardcodedColorsName, @@ -108,5 +112,6 @@ module.exports = { [restApiMethodsShouldBeGuardedName]: restApiMethodsShouldBeGuarded, [graphqlResolversShouldBeGuardedName]: graphqlResolversShouldBeGuarded, [mdxComponentNewlinesName]: mdxComponentNewlines, + [noAngleBracketPlaceholdersName]: noAngleBracketPlaceholders, }, }; diff --git a/tools/eslint-rules/rules/no-angle-bracket-placeholders.ts b/tools/eslint-rules/rules/no-angle-bracket-placeholders.ts new file mode 100644 index 0000000000..f612de3d96 --- /dev/null +++ b/tools/eslint-rules/rules/no-angle-bracket-placeholders.ts @@ -0,0 +1,168 @@ +import type { Rule } from 'eslint'; + +export const RULE_NAME = 'no-angle-bracket-placeholders'; + +const ALLOWED_TAGS = [ + 'img', + 'div', + 'span', + 'p', + 'a', + 'b', + 'strong', + 'em', + 'i', + 'code', + 'pre', + 'br', + 'hr', + 'ul', + 'ol', + 'li', + 'h1', + 'h2', + 'h3', + 'h4', + 'h5', + 'h6', + 'input', + 'button', + 'summary', + 'details', + // MDX/Documentation components + 'Frame', + 'Warning', + 'Tip', + 'Info', + 'Note', + 'Card', + 'Step', + 'Tab', + 'Tabs', + 'ArticleTab', + 'ArticleTabs', + 'Accordion', + 'AccordionGroup', + 'Router', + 'BrowserRouter', + 'RecoilRoot', +]; + +export const rule: Rule.RuleModule = { + meta: { + type: 'problem', + docs: { + description: + 'Disallow angle bracket placeholders in MDX files to prevent Crowdin translation errors. Use curly braces {placeholder} instead.', + recommended: true, + }, + messages: { + noAngleBracketPlaceholder: + 'Avoid angle bracket placeholders like "<{{name}}>" in documentation. Use curly braces "{{{name}}}" instead to prevent Crowdin translation errors.', + }, + schema: [], + fixable: 'code', + }, + + create: (context) => { + const sourceCode = context.sourceCode || context.getSourceCode(); + const filename = context.filename || context.getFilename(); + + if (!filename.endsWith('.mdx')) { + return {}; + } + + return { + Program: (node) => { + const text = sourceCode.getText(); + + // check for URL patterns with angle brackets (e.g., https:///) + const urlPlaceholderRegex = /(https?:\/\/)<([a-z][a-z0-9_-]+)>/g; + let urlMatch; + const foundPositions = new Set(); + + while ((urlMatch = urlPlaceholderRegex.exec(text)) !== null) { + const tagName = urlMatch[2]; + const startPos = urlMatch.index + urlMatch[1].length; // Start after http(s):// + const endPos = startPos + tagName.length + 2; // +2 for < and > + + foundPositions.add(startPos); + + // Check if we're inside a code block (between backticks) + const beforeText = text.substring(0, startPos); + const backticksBefore = (beforeText.match(/`/g) || []).length; + const insideCodeBlock = backticksBefore % 2 !== 0; + + context.report({ + node: node as any, + loc: { + start: sourceCode.getLocFromIndex(startPos), + end: sourceCode.getLocFromIndex(endPos), + }, + messageId: 'noAngleBracketPlaceholder', + data: { + name: tagName, + }, + fix: insideCodeBlock + ? (fixer) => { + return fixer.replaceTextRange( + [startPos, endPos], + `{${tagName}}`, + ); + } + : undefined, + }); + } + + const placeholderRegex = /<([a-z][a-z0-9_-]+)>/g; + let match; + + while ((match = placeholderRegex.exec(text)) !== null) { + const tagName = match[1]; + const startPos = match.index; + + if (foundPositions.has(startPos)) { + continue; + } + + if (ALLOWED_TAGS.includes(tagName)) { + continue; + } + + const beforeMatch = text.charAt(match.index - 1); + if (beforeMatch === '/') { + continue; + } + + const endPos = startPos + match[0].length; + + // Check if we're inside a code block (between backticks) + const beforeText = text.substring(0, match.index); + const backticksBefore = (beforeText.match(/`/g) || []).length; + const insideCodeBlock = backticksBefore % 2 !== 0; + + context.report({ + node: node as any, + loc: { + start: sourceCode.getLocFromIndex(startPos), + end: sourceCode.getLocFromIndex(endPos), + }, + messageId: 'noAngleBracketPlaceholder', + data: { + name: tagName, + }, + fix: insideCodeBlock + ? (fixer) => { + return fixer.replaceTextRange( + [startPos, endPos], + `{${tagName}}`, + ); + } + : undefined, + }); + } + }, + }; + }, +}; +