fix: Replace angle bracket placeholders with curly braces in docs to fix crowdin's tags mismatch errors (#15751)
This commit is contained in:
@@ -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',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
@@ -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://<your-domain>/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).
|
||||
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).
|
||||
|
||||
@@ -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 <command> twenty-server` (or `npx nx run twenty-server:<command>`).
|
||||
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/)
|
||||
|
||||
|
||||
|
||||
@@ -49,10 +49,9 @@ git config --global user.email "youremail@domain.com"
|
||||
```
|
||||
|
||||
3. Install nvm, node.js and yarn
|
||||
|
||||
<Warning>
|
||||
|
||||
Use `nvm` to install the correct `node` version. The `.nvmrc` ensures all contributors use the same version.
|
||||
|
||||
</Warning>
|
||||
|
||||
```bash
|
||||
|
||||
@@ -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://<your-domain>/auth/google/redirect` (for SSO)
|
||||
- `https://<your-domain>/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=<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`
|
||||
- `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`
|
||||
|
||||
<Warning>
|
||||
**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://<your-domain>/auth/microsoft/redirect` if you want to use Microsoft SSO
|
||||
- `https://<your-domain>/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=<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`
|
||||
- `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`
|
||||
|
||||
<Warning>
|
||||
**Environment-only mode:** If you set `IS_CONFIG_VARIABLES_IN_DB_ENABLED=false`, add these variables to your `.env` file instead.
|
||||
|
||||
@@ -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 <db_container_name_or_id> pg_dumpall -U <postgres_user> > 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 <db_container_name_or_id> psql -U <postgres_user>`.
|
||||
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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -24,6 +24,7 @@ On the left side, from the top to the bottom, you’ll be able to:
|
||||
<Warning>
|
||||
Please note that our API documentation is accessible under the Settings section and not the User Guide.
|
||||
</Warning>
|
||||
|
||||
- Have direct access to your **Favourites views**. Favourites are unique for each user.
|
||||
- Switch between different objects
|
||||
- **Create automations** using workflows
|
||||
|
||||
@@ -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)
|
||||
|
||||
<Warning>
|
||||
Please note that domain URLs created by the synchronization with your mailbox and calendar have the following format ```https://domain.com```
|
||||
</Warning>
|
||||
|
||||
- **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
|
||||
|
||||
<input type="checkbox" /> All data imported successfully
|
||||
<input type="checkbox" /> Custom fields working correctly
|
||||
<input type="checkbox" /> User permissions configured
|
||||
<input type="checkbox" /> Email/calendar sync connected
|
||||
<input type="checkbox" /> Critical workflows recreated and tested
|
||||
<input type="checkbox" /> Team trained on new system
|
||||
<input type="checkbox" /> All data imported successfully
|
||||
<input type="checkbox" /> Custom fields working correctly
|
||||
<input type="checkbox" /> User permissions configured
|
||||
<input type="checkbox" /> Email/calendar sync connected
|
||||
<input type="checkbox" /> Critical workflows recreated and tested
|
||||
<input type="checkbox" /> Team trained on new system
|
||||
|
||||
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -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://<your-domain>/)
|
||||
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,
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user