add documentation i18n workflows for Crowdin (#15538)

Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
Co-authored-by: Félix Malfait <felix@twenty.com>
This commit is contained in:
Abdul Rahman 2025-11-08 15:54:07 +05:30 committed by GitHub
parent 154fb4665e
commit f740bac988
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
31 changed files with 1302 additions and 725 deletions

47
.github/workflows/ci-docs.yaml vendored Normal file
View file

@ -0,0 +1,47 @@
name: CI Docs
on:
push:
branches:
- main
pull_request:
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
jobs:
changed-files-check:
uses: ./.github/workflows/changed-files.yaml
with:
files: |
package.json
packages/twenty-docs/**
eslint.config.mjs
docs-lint:
needs: changed-files-check
if: needs.changed-files-check.outputs.any_changed == 'true'
timeout-minutes: 10
runs-on: ubuntu-latest
steps:
- name: Cancel Previous Runs
uses: styfle/cancel-workflow-action@0.11.0
with:
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: Docs / Lint English MDX files
run: npx eslint "packages/twenty-docs/{developers,user-guide,twenty-ui,getting-started,snippets}/**/*.mdx" --max-warnings 0

106
.github/workflows/docs-i18n-pull.yaml vendored Normal file
View file

@ -0,0 +1,106 @@
name: 'Pull docs translations from Crowdin'
permissions:
contents: write
pull-requests: write
on:
schedule:
- cron: '0 */2 * * *' # Every two hours
workflow_dispatch:
inputs:
force_pull:
description: 'Force pull translations regardless of status'
required: false
type: boolean
default: false
workflow_call:
inputs:
force_pull:
description: 'Force pull translations regardless of status'
required: false
type: boolean
default: false
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
jobs:
pull_docs_translations:
name: Pull docs translations
runs-on: ubuntu-latest
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: Configure git
run: |
git config --global user.name 'github-actions'
git config --global user.email 'github-actions@twenty.com'
- name: Stash any changes before pulling translations
run: |
git add .
git stash || true
- name: Pull translated docs from Crowdin
if: inputs.force_pull == true || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
uses: crowdin/github-action@v2
with:
upload_sources: false
upload_translations: false
download_translations: true
download_language: fr
source: 'packages/twenty-docs/**/*.mdx'
translation: 'packages/twenty-docs/%two_letters_code%/**/%original_file_name%'
export_only_approved: false
localization_branch_name: i18n
base_url: 'https://twenty.api.crowdin.com'
skip_untranslated_files: true
push_translations: false
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 }}
- name: Fix file permissions
run: sudo chown -R runner:docker . || true
- name: Check for changes and commit
id: check_changes
run: |
git add .
if ! git diff --staged --quiet --exit-code; then
git commit -m "chore: update docs translations from Crowdin"
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 - docs translations' --body 'Created by Github action' || true
else
echo "No file differences between branches, skipping PR creation"
fi
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

42
.github/workflows/docs-i18n-push.yaml vendored Normal file
View file

@ -0,0 +1,42 @@
name: 'Push docs to Crowdin'
permissions:
contents: write
on:
workflow_dispatch:
workflow_call:
push:
branches: ['main']
paths:
- 'packages/twenty-docs/**/*.mdx'
- '!packages/twenty-docs/fr/**'
- 'crowdin.yml'
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
jobs:
push_docs:
name: Push documentation to Crowdin
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
token: ${{ github.token }}
ref: main
- name: Upload docs to Crowdin
uses: crowdin/github-action@v2
with:
upload_sources: true
upload_translations: false
download_translations: false
localization_branch_name: i18n
base_url: 'https://twenty.api.crowdin.com'
env:
CROWDIN_PROJECT_ID: 1
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}

View file

@ -91,7 +91,7 @@ jobs:
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
@ -103,7 +103,7 @@ jobs:
# if: inputs.force_pull || steps.compile_translations_strict.outcome == 'failure'
run: |
npx nx run twenty-server:lingui:compile
npx nx run twenty-emails:lingui:compile
npx nx run twenty-emails:lingui:compile
npx nx run twenty-front:lingui:compile
git status
git config --global user.name 'github-actions'

View file

@ -27,5 +27,30 @@ files: [
# e.g. "/resources/%two_letters_code%/%original_file_name%"
#
"translation": "%original_path%/%locale%.po",
},
{
#
# MDX documentation files - user-guide
# Using md type to preserve JSX component structure
# This prevents Crowdin from reformatting <Warning>, <Accordion>, etc.
#
"source": "packages/twenty-docs/user-guide/**/*.mdx",
"translation": "packages/twenty-docs/%two_letters_code%/user-guide/**/%original_file_name%",
},
{
#
# MDX documentation files - developers
# Using md type to preserve JSX component structure
#
"source": "packages/twenty-docs/developers/**/*.mdx",
"translation": "packages/twenty-docs/%two_letters_code%/developers/**/%original_file_name%",
},
{
#
# MDX documentation files - twenty-ui
# Using md type to preserve JSX component structure
#
"source": "packages/twenty-docs/twenty-ui/**/*.mdx",
"translation": "packages/twenty-docs/%two_letters_code%/twenty-ui/**/%original_file_name%",
}
]
]

View file

@ -4,6 +4,7 @@ import typescriptEslint from '@typescript-eslint/eslint-plugin';
import typescriptParser from '@typescript-eslint/parser';
import importPlugin from 'eslint-plugin-import';
import linguiPlugin from 'eslint-plugin-lingui';
import * as mdxPlugin from 'eslint-plugin-mdx';
import preferArrowPlugin from 'eslint-plugin-prefer-arrow';
import prettierPlugin from 'eslint-plugin-prettier';
import unicornPlugin from 'eslint-plugin-unicorn';
@ -191,4 +192,24 @@ export default [
parser: jsoncParser,
},
},
// MDX files
{
...mdxPlugin.flat,
plugins: {
...mdxPlugin.flat.plugins,
'@nx': nxPlugin,
},
},
mdxPlugin.flatCodeBlocks,
{
files: ['**/*.mdx'],
rules: {
'no-unused-vars': 'off',
'unused-imports/no-unused-imports': 'off',
'unused-imports/no-unused-vars': 'off',
// Enforce JSX tags on separate lines to prevent Crowdin translation issues
'@nx/workspace-mdx-component-newlines': 'error',
},
},
];

View file

@ -158,6 +158,7 @@
"eslint-plugin-import": "^2.31.0",
"eslint-plugin-jsx-a11y": "^6.10.2",
"eslint-plugin-lingui": "^0.9.0",
"eslint-plugin-mdx": "^3.6.2",
"eslint-plugin-prefer-arrow": "^1.2.3",
"eslint-plugin-prettier": "^5.1.2",
"eslint-plugin-project-structure": "^3.9.1",

View file

@ -26,225 +26,472 @@
"eyebrows": "breadcrumbs"
},
"navigation": {
"tabs": [
"languages": [
{
"tab": "User Guide",
"groups": [
"language": "en",
"tabs": [
{
"group": "Getting Started",
"icon": "rocket",
"pages": [
"user-guide/introduction",
"user-guide/getting-started/what-is-twenty",
"user-guide/getting-started/create-workspace",
"user-guide/getting-started/getting-around-twenty",
"user-guide/getting-started/configure-your-workspace",
"user-guide/getting-started/implementation-services",
"user-guide/getting-started/migrating-from-other-crms",
"user-guide/getting-started/import-export-data"
]
},
{
"group": "Data Model",
"icon": "database",
"pages": [
"user-guide/data-model/customize-your-data-model",
"user-guide/data-model/objects",
"user-guide/data-model/fields",
"user-guide/data-model/creating-records",
"user-guide/data-model/relation-fields",
"user-guide/data-model/data-model-faq"
]
},
{
"group": "CRM Essentials",
"icon": "users",
"pages": [
"user-guide/crm-essentials/contact-and-account-management",
"user-guide/crm-essentials/pipeline",
"user-guide/crm-essentials/view-management",
"user-guide/crm-essentials/sales-use-cases"
]
},
{
"group": "Views",
"icon": "table",
"pages": [
"user-guide/views/kanban-views",
"user-guide/views/views-sort-filter"
]
},
{
"group": "Workflows",
"icon": "bolt",
"pages": [
"user-guide/workflows/getting-started-workflows",
"user-guide/workflows/workflow-features",
"user-guide/workflows/internal-automations",
"user-guide/workflows/external-tool-integration",
"user-guide/workflows/workflow-troubleshooting",
"user-guide/workflows/workflow-credits",
"user-guide/workflows/professional-services"
]
},
{
"group": "Collaboration",
"icon": "envelope",
"pages": [
"user-guide/collaboration/emails-and-calendars",
"user-guide/collaboration/notes",
"user-guide/collaboration/tasks"
]
},
{
"group": "Integrations & API",
"icon": "plug",
"pages": [
"user-guide/integrations-api/apis-overview",
"user-guide/integrations-api/api-webhooks",
"user-guide/integrations-api/integrations"
]
},
{
"group": "Reporting",
"icon": "chart-bar",
"pages": ["user-guide/reporting/reporting-overview"]
},
{
"group": "Settings",
"icon": "gear",
"pages": [
"user-guide/settings/profile-settings",
"user-guide/settings/experience-settings",
"user-guide/settings/email-calendar-setup",
"user-guide/settings/workspace-settings",
"user-guide/settings/member-management",
"user-guide/settings/permissions",
"user-guide/settings/domains-settings",
"user-guide/settings/releases-settings",
"user-guide/settings/settings-faq"
]
},
{
"group": "Pricing",
"icon": "credit-card",
"pages": ["user-guide/pricing/billing-and-pricing-faq"]
},
{
"group": "Resources",
"icon": "book-open",
"pages": [
"user-guide/resources/glossary",
"user-guide/resources/github"
]
}
]
},
{
"tab": "Developers",
"groups": [
{
"group": "Developers"
},
{
"group": "Getting Started",
"icon": "rocket",
"pages": [
"developers/introduction",
"developers/local-setup",
"tab": "User Guide",
"groups": [
{
"group": "Self-Hosting",
"group": "Getting Started",
"icon": "rocket",
"pages": [
"developers/self-hosting/docker-compose",
"developers/self-hosting/setup",
"developers/self-hosting/upgrade-guide",
"developers/self-hosting/cloud-providers",
"developers/self-hosting/troubleshooting"
"user-guide/introduction",
"user-guide/getting-started/what-is-twenty",
"user-guide/getting-started/create-workspace",
"user-guide/getting-started/getting-around-twenty",
"user-guide/getting-started/configure-your-workspace",
"user-guide/getting-started/implementation-services",
"user-guide/getting-started/migrating-from-other-crms",
"user-guide/getting-started/import-export-data"
]
},
{
"group": "API and Webhooks",
"group": "Data Model",
"icon": "database",
"pages": [
"developers/api-and-webhooks/api",
"developers/api-and-webhooks/webhooks"
"user-guide/data-model/customize-your-data-model",
"user-guide/data-model/objects",
"user-guide/data-model/fields",
"user-guide/data-model/creating-records",
"user-guide/data-model/relation-fields",
"user-guide/data-model/data-model-faq"
]
},
{
"group": "CRM Essentials",
"icon": "users",
"pages": [
"user-guide/crm-essentials/contact-and-account-management",
"user-guide/crm-essentials/pipeline",
"user-guide/crm-essentials/view-management",
"user-guide/crm-essentials/sales-use-cases"
]
},
{
"group": "Views",
"icon": "table",
"pages": [
"user-guide/views/kanban-views",
"user-guide/views/views-sort-filter"
]
},
{
"group": "Workflows",
"icon": "bolt",
"pages": [
"user-guide/workflows/getting-started-workflows",
"user-guide/workflows/workflow-features",
"user-guide/workflows/internal-automations",
"user-guide/workflows/external-tool-integration",
"user-guide/workflows/workflow-troubleshooting",
"user-guide/workflows/workflow-credits",
"user-guide/workflows/professional-services"
]
},
{
"group": "Collaboration",
"icon": "envelope",
"pages": [
"user-guide/collaboration/emails-and-calendars",
"user-guide/collaboration/notes",
"user-guide/collaboration/tasks"
]
},
{
"group": "Integrations & API",
"icon": "plug",
"pages": [
"user-guide/integrations-api/apis-overview",
"user-guide/integrations-api/api-webhooks",
"user-guide/integrations-api/integrations"
]
},
{
"group": "Reporting",
"icon": "chart-bar",
"pages": [
"user-guide/reporting/reporting-overview"
]
},
{
"group": "Settings",
"icon": "gear",
"pages": [
"user-guide/settings/profile-settings",
"user-guide/settings/experience-settings",
"user-guide/settings/email-calendar-setup",
"user-guide/settings/workspace-settings",
"user-guide/settings/member-management",
"user-guide/settings/permissions",
"user-guide/settings/domains-settings",
"user-guide/settings/releases-settings",
"user-guide/settings/settings-faq"
]
},
{
"group": "Pricing",
"icon": "credit-card",
"pages": [
"user-guide/pricing/billing-and-pricing-faq"
]
},
{
"group": "Resources",
"icon": "book-open",
"pages": [
"user-guide/resources/glossary",
"user-guide/resources/github"
]
}
]
},
{
"group": "Contributing",
"icon": "code-branch",
"pages": [
"developers/bug-and-requests",
"tab": "Developers",
"groups": [
{
"group": "Frontend Development",
"group": "Developers"
},
{
"group": "Getting Started",
"icon": "rocket",
"pages": [
"developers/frontend-development/storybook",
"developers/introduction",
"developers/local-setup",
{
"group": "Twenty UI",
"group": "Self-Hosting",
"pages": [
"twenty-ui/introduction",
{
"group": "Display",
"pages": [
"twenty-ui/display/checkmark",
"twenty-ui/display/chip",
"twenty-ui/display/icons",
"twenty-ui/display/soon-pill",
"twenty-ui/display/tag",
"twenty-ui/display/app-tooltip"
]
},
{
"group": "Feedback",
"pages": ["twenty-ui/progress-bar"]
},
{
"group": "Input",
"pages": [
"twenty-ui/input/buttons",
"twenty-ui/input/color-scheme",
"twenty-ui/input/text",
"twenty-ui/input/checkbox",
"twenty-ui/input/icon-picker",
"twenty-ui/input/image-input",
"twenty-ui/input/radio",
"twenty-ui/input/select",
"twenty-ui/input/toggle",
"twenty-ui/input/block-editor"
]
},
{
"group": "Navigation",
"pages": [
"twenty-ui/navigation",
"twenty-ui/navigation/breadcrumb",
"twenty-ui/navigation/links",
"twenty-ui/navigation/menu-item",
"twenty-ui/navigation/navigation-bar",
"twenty-ui/navigation/step-bar"
]
}
"developers/self-hosting/docker-compose",
"developers/self-hosting/setup",
"developers/self-hosting/upgrade-guide",
"developers/self-hosting/cloud-providers",
"developers/self-hosting/troubleshooting"
]
},
"developers/frontend-development/frontend-commands",
"developers/frontend-development/work-with-figma",
"developers/frontend-development/best-practices-front",
"developers/frontend-development/style-guide",
"developers/frontend-development/folder-architecture-front",
"developers/frontend-development/hotkeys"
{
"group": "API and Webhooks",
"pages": [
"developers/api-and-webhooks/api",
"developers/api-and-webhooks/webhooks"
]
}
]
},
{
"group": "Backend Development",
"group": "Contributing",
"icon": "code-branch",
"pages": [
"developers/backend-development/server-commands",
"developers/backend-development/feature-flags",
"developers/backend-development/folder-architecture-server",
"developers/backend-development/zapier",
"developers/backend-development/best-practices-server",
"developers/backend-development/custom-objects",
"developers/backend-development/queue"
"developers/bug-and-requests",
{
"group": "Frontend Development",
"pages": [
"developers/frontend-development/storybook",
{
"group": "Twenty UI",
"pages": [
"twenty-ui/introduction",
{
"group": "Display",
"pages": [
"twenty-ui/display/checkmark",
"twenty-ui/display/chip",
"twenty-ui/display/icons",
"twenty-ui/display/soon-pill",
"twenty-ui/display/tag",
"twenty-ui/display/app-tooltip"
]
},
{
"group": "Feedback",
"pages": [
"twenty-ui/progress-bar"
]
},
{
"group": "Input",
"pages": [
"twenty-ui/input/buttons",
"twenty-ui/input/color-scheme",
"twenty-ui/input/text",
"twenty-ui/input/checkbox",
"twenty-ui/input/icon-picker",
"twenty-ui/input/image-input",
"twenty-ui/input/radio",
"twenty-ui/input/select",
"twenty-ui/input/toggle",
"twenty-ui/input/block-editor"
]
},
{
"group": "Navigation",
"pages": [
"twenty-ui/navigation",
"twenty-ui/navigation/breadcrumb",
"twenty-ui/navigation/links",
"twenty-ui/navigation/menu-item",
"twenty-ui/navigation/navigation-bar",
"twenty-ui/navigation/step-bar"
]
}
]
},
"developers/frontend-development/frontend-commands",
"developers/frontend-development/work-with-figma",
"developers/frontend-development/best-practices-front",
"developers/frontend-development/style-guide",
"developers/frontend-development/folder-architecture-front",
"developers/frontend-development/hotkeys"
]
},
{
"group": "Backend Development",
"pages": [
"developers/backend-development/server-commands",
"developers/backend-development/feature-flags",
"developers/backend-development/folder-architecture-server",
"developers/backend-development/zapier",
"developers/backend-development/best-practices-server",
"developers/backend-development/custom-objects",
"developers/backend-development/queue"
]
}
]
}
]
}
]
},
{
"language": "fr",
"tabs": [
{
"tab": "User Guide",
"groups": [
{
"group": "Getting Started",
"icon": "rocket",
"pages": [
"fr/user-guide/introduction",
"fr/user-guide/getting-started/what-is-twenty",
"fr/user-guide/getting-started/create-workspace",
"fr/user-guide/getting-started/getting-around-twenty",
"fr/user-guide/getting-started/configure-your-workspace",
"fr/user-guide/getting-started/implementation-services",
"fr/user-guide/getting-started/migrating-from-other-crms",
"fr/user-guide/getting-started/import-export-data"
]
},
{
"group": "Data Model",
"icon": "database",
"pages": [
"fr/user-guide/data-model/customize-your-data-model",
"fr/user-guide/data-model/objects",
"fr/user-guide/data-model/fields",
"fr/user-guide/data-model/creating-records",
"fr/user-guide/data-model/relation-fields",
"fr/user-guide/data-model/data-model-faq"
]
},
{
"group": "CRM Essentials",
"icon": "users",
"pages": [
"fr/user-guide/crm-essentials/contact-and-account-management",
"fr/user-guide/crm-essentials/pipeline",
"fr/user-guide/crm-essentials/view-management",
"fr/user-guide/crm-essentials/sales-use-cases"
]
},
{
"group": "Views",
"icon": "table",
"pages": [
"fr/user-guide/views/kanban-views",
"fr/user-guide/views/views-sort-filter"
]
},
{
"group": "Workflows",
"icon": "bolt",
"pages": [
"fr/user-guide/workflows/getting-started-workflows",
"fr/user-guide/workflows/workflow-features",
"fr/user-guide/workflows/internal-automations",
"fr/user-guide/workflows/external-tool-integration",
"fr/user-guide/workflows/workflow-troubleshooting",
"fr/user-guide/workflows/workflow-credits",
"fr/user-guide/workflows/professional-services"
]
},
{
"group": "Collaboration",
"icon": "envelope",
"pages": [
"fr/user-guide/collaboration/emails-and-calendars",
"fr/user-guide/collaboration/notes",
"fr/user-guide/collaboration/tasks"
]
},
{
"group": "Integrations & API",
"icon": "plug",
"pages": [
"fr/user-guide/integrations-api/apis-overview",
"fr/user-guide/integrations-api/api-webhooks",
"fr/user-guide/integrations-api/integrations"
]
},
{
"group": "Reporting",
"icon": "chart-bar",
"pages": [
"fr/user-guide/reporting/reporting-overview"
]
},
{
"group": "Settings",
"icon": "gear",
"pages": [
"fr/user-guide/settings/profile-settings",
"fr/user-guide/settings/experience-settings",
"fr/user-guide/settings/email-calendar-setup",
"fr/user-guide/settings/workspace-settings",
"fr/user-guide/settings/member-management",
"fr/user-guide/settings/permissions",
"fr/user-guide/settings/domains-settings",
"fr/user-guide/settings/releases-settings",
"fr/user-guide/settings/settings-faq"
]
},
{
"group": "Pricing",
"icon": "credit-card",
"pages": [
"fr/user-guide/pricing/billing-and-pricing-faq"
]
},
{
"group": "Resources",
"icon": "book-open",
"pages": [
"fr/user-guide/resources/glossary",
"fr/user-guide/resources/github"
]
}
]
},
{
"tab": "Developers",
"groups": [
{
"group": "Developers",
"pages": []
},
{
"group": "Getting Started",
"icon": "rocket",
"pages": [
"fr/developers/introduction",
"fr/developers/local-setup",
{
"group": "Self-Hosting",
"pages": [
"fr/developers/self-hosting/docker-compose",
"fr/developers/self-hosting/setup",
"fr/developers/self-hosting/upgrade-guide",
"fr/developers/self-hosting/cloud-providers",
"fr/developers/self-hosting/troubleshooting"
]
},
{
"group": "API and Webhooks",
"pages": [
"fr/developers/api-and-webhooks/api",
"fr/developers/api-and-webhooks/webhooks"
]
}
]
},
{
"group": "Contributing",
"icon": "code-branch",
"pages": [
"fr/developers/bug-and-requests",
{
"group": "Frontend Development",
"pages": [
"fr/developers/frontend-development/storybook",
{
"group": "Twenty UI",
"pages": [
"fr/twenty-ui/introduction",
{
"group": "Display",
"pages": [
"fr/twenty-ui/display/checkmark",
"fr/twenty-ui/display/chip",
"fr/twenty-ui/display/icons",
"fr/twenty-ui/display/soon-pill",
"fr/twenty-ui/display/tag",
"fr/twenty-ui/display/app-tooltip"
]
},
{
"group": "Feedback",
"pages": [
"fr/twenty-ui/progress-bar"
]
},
{
"group": "Input",
"pages": [
"fr/twenty-ui/input/buttons",
"fr/twenty-ui/input/color-scheme",
"fr/twenty-ui/input/text",
"fr/twenty-ui/input/checkbox",
"fr/twenty-ui/input/icon-picker",
"fr/twenty-ui/input/image-input",
"fr/twenty-ui/input/radio",
"fr/twenty-ui/input/select",
"fr/twenty-ui/input/toggle",
"fr/twenty-ui/input/block-editor"
]
},
{
"group": "Navigation",
"pages": [
"fr/twenty-ui/navigation",
"fr/twenty-ui/navigation/breadcrumb",
"fr/twenty-ui/navigation/links",
"fr/twenty-ui/navigation/menu-item",
"fr/twenty-ui/navigation/navigation-bar",
"fr/twenty-ui/navigation/step-bar"
]
}
]
},
"fr/developers/frontend-development/frontend-commands",
"fr/developers/frontend-development/work-with-figma",
"fr/developers/frontend-development/best-practices-front",
"fr/developers/frontend-development/style-guide",
"fr/developers/frontend-development/folder-architecture-front",
"fr/developers/frontend-development/hotkeys"
]
},
{
"group": "Backend Development",
"pages": [
"fr/developers/backend-development/server-commands",
"fr/developers/backend-development/feature-flags",
"fr/developers/backend-development/folder-architecture-server",
"fr/developers/backend-development/zapier",
"fr/developers/backend-development/best-practices-server",
"fr/developers/backend-development/custom-objects",
"fr/developers/backend-development/queue"
]
}
]
}
]

View file

@ -1,12 +1,4 @@
import baseConfig from '../../eslint.config.mjs';
export default [
...baseConfig,
{
files: ['**/*.mdx'],
rules: {
// MDX-specific rules if needed
},
},
];
export default [...baseConfig];

View file

@ -20,7 +20,9 @@ Pas encore. L'ordre des objets dans la navigation est actuellement fixe, mais ce
Tous les objets actifs apparaissent dans la navigation. Vous pouvez désactiver les objets dont vous n'avez pas besoin sous **Paramètres → Modèle de données**.
</Accordion>
<Accordion title="Can I delete standard objects (People, Companies, etc.)?">Vous pouvez désactiver les objets standard, mais vous ne pouvez pas les supprimer définitivement.</Accordion>
<Accordion title="Can I delete standard objects (People, Companies, etc.)?">
Vous pouvez désactiver les objets standard, mais vous ne pouvez pas les supprimer définitivement.
</Accordion>
</AccordionGroup>
## Capacités des champs
@ -43,7 +45,8 @@ Notre API GraphQL utilise les deux formes pour différentes opérations :
- `createPerson` (singulier) pour les actions sur un seul enregistrement
- `createPeople` (pluriel) pour les opérations en masse
Cela crée des limitations lorsque les formes singulier et pluriel sont identiques, mais cela améliore l'expérience développeur. </Accordion>
Cela crée des limitations lorsque les formes singulier et pluriel sont identiques, mais cela améliore l'expérience développeur.
</Accordion>
<Accordion title="Why are some field names protected?">
Certains noms de champs comme `Type` ou `Application` sont réservés à l'usage du système. Choisissez des noms alternatifs comme `Catégorie` ou `Classification`.
@ -69,13 +72,17 @@ Le réagencement des champs sera disponible avec les mises en page personnalisé
## Accès et Permissions
<AccordionGroup>
<Accordion title="Where can I see and edit my data model?">Vous pouvez accéder à votre modèle de données sous **Paramètres → Modèle de données**.</Accordion>
<Accordion title="Where can I see and edit my data model?">
Vous pouvez accéder à votre modèle de données sous **Paramètres → Modèle de données**.
</Accordion>
<Accordion title="Why can't I see the Data Model under Settings?">
Contactez votre administrateur d'espace de travail. L'accès au modèle de données est généralement limité aux administrateurs uniquement.
</Accordion>
<Accordion title="How many custom objects or fields can I create?">Vous pouvez créer autant d'objets et de champs personnalisés que vous le souhaitez - le prix ne changera pas.</Accordion>
<Accordion title="How many custom objects or fields can I create?">
Vous pouvez créer autant d'objets et de champs personnalisés que vous le souhaitez - le prix ne changera pas.
</Accordion>
</AccordionGroup>
## Besoin de plus d'aide ?

View file

@ -25,7 +25,9 @@ Vous pouvez **voir, modifier, supprimer des enregistrements** à partir de là a
- Utilisez la **barre de recherche** (appuyez sur `/` pour y accéder instantanément)
- Ouvrir la section **Paramètres**
<Warning>Veuillez noter que notre documentation API est accessible sous la section Paramètres et non dans le guide de l'utilisateur.</Warning>
<Warning>
Veuillez noter que notre documentation API est accessible sous la section Paramètres et non dans le guide de l'utilisateur.
</Warning>
- Ayez un accès direct à vos **vues favorites**. Les favoris sont uniques pour chaque utilisateur.
- Alternez entre différents objets
- **Créez des automatisations** en utilisant des workflows

View file

@ -35,10 +35,10 @@ Voici quelques éléments à vérifier avant de charger votre fichier.
- Exemple : Vous souhaitez attacher une personne à une entreprise. Ajoutez une colonne dans le fichier contenant tous les enregistrements `Personnes` qui contient l'`id` de l'entreprise — ou son `domaine`. Vous pourrez mapper ce champ lors du téléversement.
<Warning>
**Note importante :**
- Les relations entre les objets dans Twenty sont "Un à Plusieurs". Cela signifie que chaque enregistrement de l'objet A peut être attaché à plusieurs enregistrements de l'objet B. Mais chaque enregistrement de l'objet B peut appartenir à un seul enregistrement de l'objet A.
- Les relations entre les objets dans Twenty sont "Un à Plusieurs". Cela signifie que chaque enregistrement de l'objet A peut être attaché à plusieurs enregistrements de l'objet B. Mais chaque enregistrement de l'objet B peut appartenir à un seul enregistrement de l'objet A.
_Par exemple, une entreprise peut être attachée à plusieurs personnes. Et une personne ne peut appartenir qu'à une seule entreprise._
- Pour téléverser des relations via la fonction d'importation, vous devez fournir l'`id` (ou tout autre champ unique) de l'objet attaché dans le fichier contenant les enregistrements du "côté Multiples" de la relation.
- Pour téléverser des relations via la fonction d'importation, vous devez fournir l'`id` (ou tout autre champ unique) de l'objet attaché dans le fichier contenant les enregistrements du "côté Multiples" de la relation.
_Par exemple, vous fournissez l'`id` ou le `domaine` de l'entreprise lors du téléversement d'enregistrements de personnes. Vous ne fournissez pas l'`id` (ou `email`) des personnes lors du téléversement du fichier des entreprises._
</Warning>
@ -60,27 +60,31 @@ _Par exemple, vous fournissez l'`id` ou le `domaine` de l'entreprise lors du té
### FAQ Importation
<details><summary>Je vois des problèmes de doublons lors du téléversement de mon fichier, que dois-je faire?</summary>
<details>
<summary>Je vois des problèmes de doublons lors du téléversement de mon fichier, que dois-je faire?</summary>
Veuillez vous référer à la section **Préparer votre csv** ci-dessus dans cet article, elle contient des instructions sur ce qui sera considéré comme un doublon.
</details>
<details><summary>Puis-je importer des relations entre objets?</summary>
<details>
<summary>Puis-je importer des relations entre objets?</summary>
Oui, veuillez vous référer à la section **Préparer votre csv** ci-dessus dans cet article, elle contient une section sur l'importation des relations.
</details>
<details><summary>Puis-je mettre à jour des enregistrements existants en utilisant la fonction d'importation?</summary>
<details>
<summary>Puis-je mettre à jour des enregistrements existants en utilisant la fonction d'importation?</summary>
Oui, vous pouvez mettre à jour des enregistrements existants en utilisant la fonction d'importation. Assurez-vous de fournir l'id (ou tout autre champ unique) lors du re-téléversement de vos enregistrements.
</details>
<details><summary>Puis-je migrer l'`id` de mon (mes) autre(s) outil(s)?</summary>
<details>
<summary>Puis-je migrer l'`id` de mon (mes) autre(s) outil(s)?</summary>
Oui. Vous devez créer un champ que vous définissez comme unique dans votre modèle de données qui contiendra l'`id` de votre (vos) autre(s) outil(s). Veuillez noter que le nom `id` est protégé car il est utilisé pour l'ID Twenty.
Oui. Vous devez créer un champ que vous définissez comme unique dans votre modèle de données qui contiendra l'`id` de votre (vos) autre(s) outil(s). Veuillez noter que le nom `id` est protégé car il est utilisé pour l'ID Twenty.
Si vous souhaitez créer des relations entre objets à l'aide de ce champ, consultez la section **Préparer votre csv** ci-dessus dans cet article. Elle contient une section sur l'importation des relations.
</details>
@ -97,17 +101,17 @@ Pour exporter des données à partir d'un objet :
5. Sélectionnez l'emplacement d'enregistrement pour les données CSV. Notez que l'exportation peut prendre du temps avec un grand nombre d'enregistrements.
<div style={{padding:'71.24% 0 0 0', position:'relative', margin: '32px 0px 0px'}}>
<iframe
src="https://player.vimeo.com/video/926226303?autoplay=1&loop=1&autopause=0&background=1&amp;app_id=58479"
frameBorder="0"
allow="autoplay; fullscreen; picture-in-picture; clipboard-write"
<iframe
src="https://player.vimeo.com/video/926226303?autoplay=1&loop=1&autopause=0&background=1&amp;app_id=58479"
frameBorder="0"
allow="autoplay; fullscreen; picture-in-picture; clipboard-write"
style={{
position:'absolute',
top:0,
left:0,
width:'100%',
height:'100%',
borderRadius: '16px',
borderRadius: '16px',
border:'2px solid black'
}}
title="Export data"

View file

@ -16,12 +16,17 @@ sectionInfo: Configurez les paramètres et préférences de votre espace de trav
Absolument. Vous pouvez créer un nouvel espace de travail en cliquant sur le menu déroulant en haut à gauche de l'écran (celui qui contient le nom de votre espace de travail), sur les trois points, puis sur `Créer un espace de travail`.
</Accordion>
<Accordion title="I accidentally created multiple workspaces but only need one. What should I do?">Il suffit de supprimer les espaces de travail dont vous n'avez plus besoin, vous pouvez le faire sous `Paramètres → Paramètres de l'espace de travail`.
<Accordion title="I accidentally created multiple workspaces but only need one. What should I do?">
Il suffit de supprimer les espaces de travail dont vous n'avez plus besoin, vous pouvez le faire sous `Paramètres → Paramètres de l'espace de travail`.
<Warning>Ne supprimez pas votre compte (accessible sous Paramètres → Paramètres de profil): votre compte est partagé entre les différents espaces de travail.</Warning>
<Warning>
Ne supprimez pas votre compte (accessible sous Paramètres → Paramètres de profil): votre compte est partagé entre les différents espaces de travail.
</Warning>
</Accordion>
<Accordion title="How can I disable my workspace?">Si vous souhaitez juste désactiver votre espace de travail (et non le supprimer), allez dans `Paramètres → Facturation` et cliquez sur `Annuler le plan`.</Accordion>
<Accordion title="How can I disable my workspace?">
Si vous souhaitez juste désactiver votre espace de travail (et non le supprimer), allez dans `Paramètres → Facturation` et cliquez sur `Annuler le plan`.
</Accordion>
<Accordion title="How can I delete my workspace?">
Vous pouvez le faire dans `Paramètres → Paramètres de l'espace de travail`. Nous espérons vous revoir bientôt, merci d'avoir essayé Twenty !
@ -64,7 +69,8 @@ Oui! Rendez-vous dans `Paramètres → Domaines` pour configurer un domaine pers
Les fonctionnalités de Lab sont des capacités expérimentales que vous pouvez tester avant leur sortie officielle. Accédez-y sous `Paramètres → Versions → Lab`. Des fonctionnalités comme la sélection de Dossier de Messages sont stables et utiles, mais rappelez-vous que les fonctionnalités de lab peuvent changer ou être supprimées dans les versions futures.
</Accordion>
<Accordion title="How do I change my workspace appearance and regional settings?">Allez dans `Paramètres → Expérience` pour personnaliser :
<Accordion title="How do I change my workspace appearance and regional settings?">
Allez dans `Paramètres → Expérience` pour personnaliser :
- **Thème** : Clair, sombre ou basé sur le système
- **Paramètres régionaux** : Langue, fuseau horaire, formats de date/numéro
- **Format du calendrier** : Premier jour de la semaine, format de l'heure (12/24 heures)

View file

@ -57,7 +57,9 @@ Les formulaires sont actuellement conçus uniquement pour les déclencheurs manu
**Problème** : Atteindre la limite de 100 flux de travail concurrents par espace de travail.
<Warning>Vous ne pouvez pas exécuter plus de 100 flux de travail en parallèle à tout moment par espace de travail.</Warning>
<Warning>
Vous ne pouvez pas exécuter plus de 100 flux de travail en parallèle à tout moment par espace de travail.
</Warning>
**Solutions** :

View file

@ -0,0 +1,20 @@
export const VimeoEmbed = ({ videoId, title = 'Video' }) => (
<div style={{padding:'69.01% 0 0 0', position:'relative', margin: '32px 0px'}}>
<iframe
src={`https://player.vimeo.com/video/${videoId}?autoplay=1&loop=1&autopause=0&background=1&app_id=58479`}
frameBorder="0"
allow="autoplay; fullscreen; picture-in-picture; clipboard-write"
style={{
position:'absolute',
top:0,
left:0,
width:'100%',
height:'100%',
borderRadius: '16px',
border:'2px solid black'
}}
title={title}
/>
</div>
);

View file

@ -4,6 +4,9 @@ info: Explore how to efficiently manage notes within record pages in Twenty.
image: /images/user-guide/notes/notes_header.png
sectionInfo: Discover how to leverage Notes and Tasks to better collaborate with your team.
---
import { VimeoEmbed } from '/snippets/vimeo-embed.mdx';
<Frame>
<img src="/images/user-guide/notes/notes_header.png" alt="Header" />
</Frame>
@ -68,24 +71,7 @@ Highlight the text to see more formatting options like bold, italics, and alignm
You can also change the background color and text color of each block to highlight important things in your note. To do so, hover over the block you want to format and click on the `⋮` icon besides the `+` icon. Click on `Colors` to open up all color options for both the text and the background.
<div style={{padding:'69.01% 0 0 0', position:'relative', margin: '32px 0px 0px'}}>
<iframe
src="https://player.vimeo.com/video/927896302?autoplay=1&loop=1&autopause=0&background=1&amp;app_id=58479"
frameBorder="0"
allow="autoplay; fullscreen; picture-in-picture; clipboard-write"
style={{
position:'absolute',
top:0,
left:0,
width:'100%',
height:'100%',
borderRadius: '16px',
border:'2px solid black'
}}
title="Export data"
></iframe>
</div>
<script src="https://player.vimeo.com/api/player.js"></script>
<VimeoEmbed videoId="927896302" title="Format note content" />
## Viewing Notes

View file

@ -4,6 +4,9 @@ info: Understand how to effectively manage tasks in Twenty.
image: /images/user-guide/tasks/tasks_header.png
sectionInfo: Discover how to leverage Notes and Tasks to better collaborate with your team.
---
import { VimeoEmbed } from '/snippets/vimeo-embed.mdx';
<Frame>
<img src="/images/user-guide/tasks/tasks_header.png" alt="Header" />
</Frame>
@ -60,24 +63,7 @@ Creating tasks in Twenty is seamless. You can either:
- Use the search function by pressing `cmd/ctrl + k`, then select 'Create task' from the list of quick actions.
- Go to a `Record page` and press `+` at the top right of the page, or go to the Task tab and press the `Add Task` button.
<div style={{padding:'70.59% 0 0 0', position:'relative', margin: '32px 0px 0px'}}>
<iframe
src="https://player.vimeo.com/video/928786754?autoplay=1&loop=1&autopause=0&background=1&amp;app_id=58479"
frameBorder="0"
allow="autoplay; fullscreen; picture-in-picture; clipboard-write"
style={{
position:'absolute',
top:0,
left:0,
width:'100%',
height:'100%',
borderRadius: '16px',
border:'2px solid black'
}}
title="Export data"
></iframe>
</div>
<script src="https://player.vimeo.com/api/player.js"></script>
<VimeoEmbed videoId="928786754" title="Creating tasks" />
### Adding Task Content
@ -92,24 +78,7 @@ The **Tasks** page displays all your tasks across your workspace. Here you can:
You can also see tasks for a given Record on its `Record page`.
<div style={{padding:'69.01% 0 0 0', position:'relative', margin: '32px 0px 0px'}}>
<iframe
src="https://player.vimeo.com/video/927908280?autoplay=1&loop=1&autopause=0&background=1&amp;app_id=58479"
frameBorder="0"
allow="autoplay; fullscreen; picture-in-picture; clipboard-write"
style={{
position:'absolute',
top:0,
left:0,
width:'100%',
height:'100%',
borderRadius: '16px',
border:'2px solid black'
}}
title="Export data"
></iframe>
</div>
<script src="https://player.vimeo.com/api/player.js"></script>
<VimeoEmbed videoId="927908280" title="Viewing tasks" />
## Editing Tasks
@ -129,24 +98,7 @@ To mark a task as complete:
This procedure will help keep an updated record of your accomplishments.
<div style={{padding:'69.01% 0 0 0', position:'relative', margin: '32px 0px 0px'}}>
<iframe
src="https://player.vimeo.com/video/927910083?autoplay=1&loop=1&autopause=0&background=1&amp;app_id=58479"
frameBorder="0"
allow="autoplay; fullscreen; picture-in-picture; clipboard-write"
style={{
position:'absolute',
top:0,
left:0,
width:'100%',
height:'100%',
borderRadius: '16px',
border:'2px solid black'
}}
title="Export data"
></iframe>
</div>
<script src="https://player.vimeo.com/api/player.js"></script>
<VimeoEmbed videoId="927910083" title="Marking tasks as complete" />
## Delete a task

View file

@ -4,6 +4,9 @@ info: "Track and manage your sales opportunities through customizable pipeline s
image: /images/user-guide/kanban-views/kanban.png
sectionInfo: "Essential CRM features for managing leads, sales, and customers"
---
import { VimeoEmbed } from '/snippets/vimeo-embed.mdx';
<Frame>
<img src="/images/user-guide/kanban-views/kanban.png" alt="Header" />
</Frame>
@ -22,24 +25,7 @@ Kanban views visually map out your pipeline, where each column represents a stag
You can move each opportunity between stages as it progresses through your sales process by dragging and dropping. Hold your click on a card and move it to the next stage.
<div style={{padding:'69.01% 0 0 0', position:'relative', margin: '32px 0px 0px'}}>
<iframe
src="https://player.vimeo.com/video/927888627?autoplay=1&loop=1&autopause=0&background=1&amp;app_id=58479"
frameBorder="0"
allow="autoplay; fullscreen; picture-in-picture; clipboard-write"
style={{
position:'absolute',
top:0,
left:0,
width:'100%',
height:'100%',
borderRadius: '16px',
border:'2px solid black'
}}
title="Export data"
></iframe>
</div>
<script src="https://player.vimeo.com/api/player.js"></script>
<VimeoEmbed videoId="927888627" title="Video demonstration" />
### Customizing your pipeline stages
@ -49,24 +35,7 @@ You can tailor your pipeline to suit your specific sales process. Stages represe
To add a stage, access the Select Field Settings by navigating to Settings → Data Model, selecting your object, and then the field your Kanban board depends on.
<div style={{padding:'69.01% 0 0 0', position:'relative', margin: '32px 0px 0px'}}>
<iframe
src="https://player.vimeo.com/video/927890428?autoplay=1&loop=1&autopause=0&background=1&amp;app_id=58479"
frameBorder="0"
allow="autoplay; fullscreen; picture-in-picture; clipboard-write"
style={{
position:'absolute',
top:0,
left:0,
width:'100%',
height:'100%',
borderRadius: '16px',
border:'2px solid black'
}}
title="Export data"
></iframe>
</div>
<script src="https://player.vimeo.com/api/player.js"></script>
<VimeoEmbed videoId="927890428" title="Video demonstration" />
#### Removing stages

View file

@ -4,6 +4,9 @@ info: "Create and customize views to organize your data with filters, sorting, a
image: /images/user-guide/table-views/table.png
sectionInfo: "Essential CRM features for managing leads, sales, and customers"
---
import { VimeoEmbed } from '/snippets/vimeo-embed.mdx';
<Frame>
<img src="/images/user-guide/table-views/table.png" alt="Header" />
</Frame>
@ -25,24 +28,7 @@ Organizes your records by grouping them based on a select field. For example, yo
### Kanban Layout
A visual board where each column represents a stage and each record appears as a card. This layout is ideal for managing pipelines and workflows where records move through different stages. For more details on using Kanban views for pipeline management, see our [Pipeline](/user-guide/crm-essentials/pipeline) article.
<div style={{padding:'69.01% 0 0 0', position:'relative', margin: '32px 0px 0px'}}>
<iframe
src="https://player.vimeo.com/video/927888627?autoplay=1&loop=1&autopause=0&background=1&amp;app_id=58479"
frameBorder="0"
allow="autoplay; fullscreen; picture-in-picture; clipboard-write"
style={{
position:'absolute',
top:0,
left:0,
width:'100%',
height:'100%',
borderRadius: '16px',
border:'2px solid black'
}}
title="Export data"
></iframe>
</div>
<script src="https://player.vimeo.com/api/player.js"></script>
<VimeoEmbed videoId="927888627" title="Video demonstration" />
## Creating New Views
@ -61,47 +47,13 @@ Important: You need to first select the List layout and then add a Group By. You
</Warning>
- For Kanban views, select which select field to use as column headers
<div style={{padding:'69.01% 0 0 0', position:'relative', margin: '32px 0px 0px'}}>
<iframe
src="https://player.vimeo.com/video/927639721?autoplay=1&loop=1&autopause=0&background=1&amp;app_id=58479"
frameBorder="0"
allow="autoplay; fullscreen; picture-in-picture; clipboard-write"
style={{
position:'absolute',
top:0,
left:0,
width:'100%',
height:'100%',
borderRadius: '16px',
border:'2px solid black'
}}
title="Export data"
></iframe>
</div>
<script src="https://player.vimeo.com/api/player.js"></script>
<VimeoEmbed videoId="927639721" title="Video demonstration" />
### Creating Views from Existing Filters
When you modify the sorting and filtering of an existing view, a `Save as new view` button appears. This lets you create a new view based on your current customizations.
<div style={{padding:'69.01% 0 0 0', position:'relative', margin: '32px 0px 0px'}}>
<iframe
src="https://player.vimeo.com/video/927643495?autoplay=1&loop=1&autopause=0&background=1&amp;app_id=58479"
frameBorder="0"
allow="autoplay; fullscreen; picture-in-picture; clipboard-write"
style={{
position:'absolute',
top:0,
left:0,
width:'100%',
height:'100%',
borderRadius: '16px',
border:'2px solid black'
}}
title="Export data"
></iframe>
</div>
<script src="https://player.vimeo.com/api/player.js"></script>
<VimeoEmbed videoId="927643495" title="Video demonstration" />
## Making Views Actionable
@ -126,48 +78,14 @@ All layouts support the same customization options: sorting, filtering, and fiel
You can apply filters to show only the records that match your criteria. Click `Filter` in the toolbar, select a field, choose your condition, and set the value. You can add multiple filters for advanced filtering based on several conditions.
<div style={{padding:'71.24% 0 0 0', position:'relative', margin: '32px 0px 0px'}}>
<iframe
src="https://player.vimeo.com/video/926282262?autoplay=1&loop=1&autopause=0&background=1&amp;app_id=58479"
frameBorder="0"
allow="autoplay; fullscreen; picture-in-picture; clipboard-write"
style={{
position:'absolute',
top:0,
left:0,
width:'100%',
height:'100%',
borderRadius: '16px',
border:'2px solid black'
}}
title="Export data"
></iframe>
</div>
<script src="https://player.vimeo.com/api/player.js"></script>
<VimeoEmbed videoId="926282262" title="Video demonstration" />
### Sorting Your Records
Control the order of your records by clicking on any column header to sort by that field. Click again to reverse the sort order. You can apply multiple sorts for complex organization.
<div style={{padding:'69.01% 0 0 0', position:'relative', margin: '32px 0px 0px'}}>
<iframe
src="https://player.vimeo.com/video/927885588?autoplay=1&loop=1&autopause=0&background=1&amp;app_id=58479"
frameBorder="0"
allow="autoplay; fullscreen; picture-in-picture; clipboard-write"
style={{
position:'absolute',
top:0,
left:0,
width:'100%',
height:'100%',
borderRadius: '16px',
border:'2px solid black'
}}
title="Export data"
></iframe>
</div>
<script src="https://player.vimeo.com/api/player.js"></script>
<VimeoEmbed videoId="927885588" title="Video demonstration" />
### Managing Fields and Columns

View file

@ -4,6 +4,9 @@ info: "Learn how to customize and navigate Table Views."
image: /images/user-guide/table-views/table.png
sectionInfo: Discover how to use standard and custom objects in your workspace.
---
import { VimeoEmbed } from '/snippets/vimeo-embed.mdx';
<Frame>
<img src="/images/user-guide/table-views/table.png" alt="Header" />
</Frame>
@ -18,24 +21,7 @@ Add records as needed, without limits. To add a record, you can either click on
Enter the record name then press `Enter` to save. To edit a record name, click on its name on its detail page.
<div style={{padding:'69.01% 0 0 0', position:'relative', margin: '32px 0px 0px'}}>
<iframe
src="https://player.vimeo.com/video/927071691?autoplay=1&loop=1&autopause=0&background=1&amp;app_id=58479"
frameBorder="0"
allow="autoplay; fullscreen; picture-in-picture; clipboard-write"
style={{
position:'absolute',
top:0,
left:0,
width:'100%',
height:'100%',
borderRadius: '16px',
border:'2px solid black'
}}
title="Export data"
></iframe>
</div>
<script src="https://player.vimeo.com/api/player.js"></script>
<VimeoEmbed videoId="927071691" title="Video demonstration" />
## Delete record
@ -45,24 +31,7 @@ Enter the record name then press `Enter` to save. To edit a record name, click o
**Record Page:** Tap the `⋮` icon in the top right corner, then select delete.
<div style={{padding:'69.01% 0 0 0', position:'relative', margin: '32px 0px 0px'}}>
<iframe
src="https://player.vimeo.com/video/927073570?autoplay=1&loop=1&autopause=0&background=1&amp;app_id=58479"
frameBorder="0"
allow="autoplay; fullscreen; picture-in-picture; clipboard-write"
style={{
position:'absolute',
top:0,
left:0,
width:'100%',
height:'100%',
borderRadius: '16px',
border:'2px solid black'
}}
title="Export data"
></iframe>
</div>
<script src="https://player.vimeo.com/api/player.js"></script>
<VimeoEmbed videoId="927073570" title="Video demonstration" />
## Add a Custom Field

View file

@ -1,6 +1,6 @@
---
title: Data Model FAQ
info: Frequently asked questions about data model configuration, limitations, and upcoming features.
info: "Frequently asked questions about data model configuration, limitations, and upcoming features."
image: /images/user-guide/what-is-twenty/faq.png
sectionInfo: Flexible data model designed to support your unique business processes
---

View file

@ -4,6 +4,9 @@ info: "Understand the role of fields and how to handle them."
image: /images/user-guide/fields/field.png
sectionInfo: Flexible data model designed to support your unique business processes
---
import { VimeoEmbed } from '/snippets/vimeo-embed.mdx';
<Frame>
<img src="/images/user-guide/fields/field.png" alt="Header" />
</Frame>
@ -42,24 +45,7 @@ To add a custom field to any object, follow these steps:
Your newly created field is now available within the application's fields. To display it on a specific view, click on the options menu, then select `Fields`.
<div style={{padding:'71.15% 0 0 0', position:'relative', margin: '32px 0px 0px'}}>
<iframe
src="https://player.vimeo.com/video/927628219?autoplay=1&loop=1&autopause=0&background=1&amp;app_id=58479"
frameBorder="0"
allow="autoplay; fullscreen; picture-in-picture; clipboard-write"
style={{
position:'absolute',
top:0,
left:0,
width:'100%',
height:'100%',
borderRadius: '16px',
border:'2px solid black'
}}
title="Export data"
></iframe>
</div>
<script src="https://player.vimeo.com/api/player.js"></script>
<VimeoEmbed videoId="927628219" title="Video demonstration" />
**Quick way:** Click the **+** button at the top right of any object table, then select `Customize fields`. This takes you directly to the Data Model settings.

View file

@ -4,6 +4,9 @@ info: "Learn about standard objects and how to create custom ones for your busin
image: /images/user-guide/objects/objects_orange.png
sectionInfo: Flexible data model designed to support your unique business processes
---
import { VimeoEmbed } from '/snippets/vimeo-embed.mdx';
<Frame>
<img src="/images/user-guide/objects/objects_orange.png" alt="Header" />
</Frame>
@ -40,24 +43,7 @@ To create a new custom object:
2. Under Workspace, go to Data model. Here you'll be able to see an overview of all your existing Standard and Custom objects (both active and disabled).
<div style={{padding:'71.24% 0 0 0', position:'relative', margin: '32px 0px 0px'}}>
<iframe
src="https://player.vimeo.com/video/926288174?autoplay=1&loop=1&autopause=0&background=1&amp;app_id=58479"
frameBorder="0"
allow="autoplay; fullscreen; picture-in-picture; clipboard-write"
style={{
position:'absolute',
top:0,
left:0,
width:'100%',
height:'100%',
borderRadius: '16px',
border:'2px solid black'
}}
title="Export data"
></iframe>
</div>
<script src="https://player.vimeo.com/api/player.js"></script>
<VimeoEmbed videoId="926288174" title="Video demonstration" />
3. Click on `+ New object` at the top. Enter the name (both singular and plural), choose an icon, and add a description for your custom object and hit Save (at the top right). Using Listing as an example of custom object, the singular would be "listing" and the plural would be "listings" along with a description like "Listings that hosts created to showcase their property."
@ -65,24 +51,7 @@ To create a new custom object:
The singular and plural names must be different. This is required for our GraphQL API to work properly.
</Warning>
<div style={{padding:'71.24% 0 0 0', position:'relative', margin: '32px 0px 0px'}}>
<iframe
src="https://player.vimeo.com/video/926293493?autoplay=1&loop=1&autopause=0&background=1&amp;app_id=58479"
frameBorder="0"
allow="autoplay; fullscreen; picture-in-picture; clipboard-write"
style={{
position:'absolute',
top:0,
left:0,
width:'100%',
height:'100%',
borderRadius: '16px',
border:'2px solid black'
}}
title="Export data"
></iframe>
</div>
<script src="https://player.vimeo.com/api/player.js"></script>
<VimeoEmbed videoId="926293493" title="Video demonstration" />
4. Once you create your custom object, you'll be able to manage it. You can edit the name, icon and description, view the different fields, and add more fields.

View file

@ -4,6 +4,9 @@ info: "Follow a step-by-step guide on how to register on Twenty, choose a subscr
image: /images/user-guide/create-workspace/workspace-cover.png
sectionInfo: Discover Twenty, an open-source CRM.
---
import { VimeoEmbed } from '/snippets/vimeo-embed.mdx';
<Frame>
<img src="/images/user-guide/create-workspace/workspace-cover.png" alt="Header" />
</Frame>
@ -15,24 +18,7 @@ sectionInfo: Discover Twenty, an open-source CRM.
- **Continue with Microsoft** for Microsoft account registration.
- Or, **Continue With Email** for email registration.
<div style={{padding:'69.01% 0 0 0', position:'relative', margin: '32px 0px 0px'}}>
<iframe
src="https://player.vimeo.com/video/927066829?autoplay=1&loop=1&autopause=0&background=1&amp;app_id=58479"
frameBorder="0"
allow="autoplay; fullscreen; picture-in-picture; clipboard-write"
style={{
position:'absolute',
top:0,
left:0,
width:'100%',
height:'100%',
borderRadius: '16px',
border:'2px solid black'
}}
title="Export data"
></iframe>
</div>
<script src="https://player.vimeo.com/api/player.js"></script>
<VimeoEmbed videoId="927066829" title="Video demonstration" />
## Step 2: Choosing a Trial Period
Choose between two trial periods:

View file

@ -4,6 +4,9 @@ info: "Learn how to import and export data."
image: /images/user-guide/import-export-data/cloud.png
sectionInfo: Discover Twenty, an open-source CRM.
---
import { VimeoEmbed } from '/snippets/vimeo-embed.mdx';
<Frame>
<img src="/images/user-guide/import-export-data/cloud.png" alt="Header" />
</Frame>
@ -95,23 +98,6 @@ To export data from an object:
4. Click on `Export view`.
5. Select the save location for the CSV data. Note that exporting may take time with a large record count.
<div style={{padding:'71.24% 0 0 0', position:'relative', margin: '32px 0px 0px'}}>
<iframe
src="https://player.vimeo.com/video/926226303?autoplay=1&loop=1&autopause=0&background=1&amp;app_id=58479"
frameBorder="0"
allow="autoplay; fullscreen; picture-in-picture; clipboard-write"
style={{
position:'absolute',
top:0,
left:0,
width:'100%',
height:'100%',
borderRadius: '16px',
border:'2px solid black'
}}
title="Export data"
></iframe>
</div>
<script src="https://player.vimeo.com/api/player.js"></script>
<VimeoEmbed videoId="926226303" title="Video demonstration" />

View file

@ -4,13 +4,16 @@ info: "Create and manage API keys for authentication and set up webhooks for rea
image: /images/user-guide/api/api.png
sectionInfo: Learn how to connect Twenty to your other tools.
---
import { VimeoEmbed } from '/snippets/vimeo-embed.mdx';
<Frame>
<img src="/images/user-guide/api/api.png" alt="Header" />
</Frame>
## API Keys
API keys allow automated access to your CRM data, synchronize data with other systems, and create custom integrations or solutions.
API keys allow automated access to your CRM data, synchronize data with other systems, and create custom integrations or solutions.
### Create an API Key
@ -22,29 +25,12 @@ API keys allow automated access to your CRM data, synchronize data with other sy
4. Click **Save** to generate your API key
5. **Important**: Copy and store your API key immediately, it's only shown once
Once created, your API key provides access to your custom API documentation and playground where you can test endpoints with your actual data model.
Once created, your API key provides access to your custom API documentation and playground where you can test endpoints with your actual data model.
<Warning>
Since your API key gives access to sensitive information, you shouldn't share it with services you don't fully trust. If leaked, someone can use it maliciously. If your API key's security is compromised, immediately disable it and generate a new one.
</Warning>
<div style={{padding:'70.59% 0 0 0', position:'relative', margin: '32px 0px 0px' }}>
<iframe
src="https://player.vimeo.com/video/928786722?autoplay=1&loop=1&autopause=0&background=1&amp;app_id=58479"
frameBorder="0"
allow="autoplay; fullscreen; picture-in-picture; clipboard-write"
style={{
position:'absolute',
top:0,
left:0,
width:'100%',
height:'100%',
borderRadius: '16px',
border:'2px solid black'
}}
title="Export data"
></iframe>
</div>
<script src="https://player.vimeo.com/api/player.js"></script>
<VimeoEmbed videoId="928786722" title="Creating API key" />
### Manage API Keys
@ -77,24 +63,7 @@ Webhooks are ideal for integrating with external systems, while Workflows suppor
Your webhook will immediately start receiving real-time notifications about changes to your CRM data.
<div style={{padding:'70.59% 0 0 0', position:'relative', margin: '32px 0px 0px'}}>
<iframe
src="https://player.vimeo.com/video/928786708?autoplay=1&loop=1&autopause=0&background=1&amp;app_id=58479"
frameBorder="0"
allow="autoplay; fullscreen; picture-in-picture; clipboard-write"
style={{
position:'absolute',
top:0,
left:0,
width:'100%',
height:'100%',
borderRadius: '16px',
border:'2px solid black'
}}
title="Export data"
></iframe>
</div>
<script src="https://player.vimeo.com/api/player.js"></script>
<VimeoEmbed videoId="928786708" title="Video demonstration" />
### Manage Webhooks

View file

@ -4,6 +4,9 @@ info: "Learn how to customize and navigate Kanban Views."
image: /images/user-guide/kanban-views/kanban.png
sectionInfo: Discover how to use standard and custom objects in your workspace.
---
import { VimeoEmbed } from '/snippets/vimeo-embed.mdx';
<Frame>
<img src="/images/user-guide/kanban-views/kanban.png" alt="Header" />
</Frame>
@ -16,24 +19,7 @@ Kanban views visually map out process flows, where each column stands for a dist
You can move each card between stages as it goes through your workflow by dragging and dropping. To proceed, hold your click on a card and move it to the next stage.
<div style={{padding:'69.01% 0 0 0', position:'relative', margin: '32px 0px 0px'}}>
<iframe
src="https://player.vimeo.com/video/927888627?autoplay=1&loop=1&autopause=0&background=1&amp;app_id=58479"
frameBorder="0"
allow="autoplay; fullscreen; picture-in-picture; clipboard-write"
style={{
position:'absolute',
top:0,
left:0,
width:'100%',
height:'100%',
borderRadius: '16px',
border:'2px solid black'
}}
title="Export data"
></iframe>
</div>
<script src="https://player.vimeo.com/api/player.js"></script>
<VimeoEmbed videoId="927888627" title="Video demonstration" />
## Add and delete stages
@ -43,24 +29,7 @@ You can tailor your workflow to suit your needs using stages, which represent a
To add a stage, access the Select field settings by navigating to Settings > Data Model, selecting your object, and then the field your Kanban board depends on.
<div style={{padding:'69.01% 0 0 0', position:'relative', margin: '32px 0px 0px'}}>
<iframe
src="https://player.vimeo.com/video/927890428?autoplay=1&loop=1&autopause=0&background=1&amp;app_id=58479"
frameBorder="0"
allow="autoplay; fullscreen; picture-in-picture; clipboard-write"
style={{
position:'absolute',
top:0,
left:0,
width:'100%',
height:'100%',
borderRadius: '16px',
border:'2px solid black'
}}
title="Export data"
></iframe>
</div>
<script src="https://player.vimeo.com/api/player.js"></script>
<VimeoEmbed videoId="927890428" title="Video demonstration" />
### Remove Stages

View file

@ -4,6 +4,9 @@ info: "Find out how to create, manage and delete Object Views."
image: /images/user-guide/views/filter.png
sectionInfo: Discover how to use standard and custom objects in your workspace.
---
import { VimeoEmbed } from '/snippets/vimeo-embed.mdx';
<Frame>
<img src="/images/user-guide/views/filter.png" alt="Header" />
</Frame>
@ -37,47 +40,13 @@ There are two ways to create a new view. Either directly from the `View Switcher
The newly created view opens automatically.
<div style={{padding:'69.01% 0 0 0', position:'relative', margin: '32px 0px 0px'}}>
<iframe
src="https://player.vimeo.com/video/927639721?autoplay=1&loop=1&autopause=0&background=1&amp;app_id=58479"
frameBorder="0"
allow="autoplay; fullscreen; picture-in-picture; clipboard-write"
style={{
position:'absolute',
top:0,
left:0,
width:'100%',
height:'100%',
borderRadius: '16px',
border:'2px solid black'
}}
title="Export data"
></iframe>
</div>
<script src="https://player.vimeo.com/api/player.js"></script>
<VimeoEmbed videoId="927639721" title="Video demonstration" />
#### From Sorting and Filtering
When you change the `Sorting` and `Filtering` of an existing view, a `Save as new view` button will appear at the right edge of the `View Bar`. This will open the New View menu mentioned above, allowing you to create a new view out of an existing one.
<div style={{padding:'69.01% 0 0 0', position:'relative', margin: '32px 0px 0px'}}>
<iframe
src="https://player.vimeo.com/video/927643495?autoplay=1&loop=1&autopause=0&background=1&amp;app_id=58479"
frameBorder="0"
allow="autoplay; fullscreen; picture-in-picture; clipboard-write"
style={{
position:'absolute',
top:0,
left:0,
width:'100%',
height:'100%',
borderRadius: '16px',
border:'2px solid black'
}}
title="Export data"
></iframe>
</div>
<script src="https://player.vimeo.com/api/player.js"></script>
<VimeoEmbed videoId="927643495" title="Video demonstration" />
### Editing and Deleting a View
@ -88,24 +57,7 @@ To Edit or Delete a view:
Upon clicking on edit, you can change the icon and the name of the view or delete the view completely.
<div style={{padding:'69.01% 0 0 0', position:'relative', margin: '32px 0px 0px'}}>
<iframe
src="https://player.vimeo.com/video/927645774?autoplay=1&loop=1&autopause=0&background=1&amp;app_id=58479"
frameBorder="0"
allow="autoplay; fullscreen; picture-in-picture; clipboard-write"
style={{
position:'absolute',
top:0,
left:0,
width:'100%',
height:'100%',
borderRadius: '16px',
border:'2px solid black'
}}
title="Export data"
></iframe>
</div>
<script src="https://player.vimeo.com/api/player.js"></script>
<VimeoEmbed videoId="927645774" title="Video demonstration" />
## Navigating Between Views
@ -124,48 +76,14 @@ To filter a view:
- Add more filters with `+ Add filter` or remove them with **X**.
<div style={{padding:'71.24% 0 0 0', position:'relative', margin: '32px 0px 0px'}}>
<iframe
src="https://player.vimeo.com/video/926282262?autoplay=1&loop=1&autopause=0&background=1&amp;app_id=58479"
frameBorder="0"
allow="autoplay; fullscreen; picture-in-picture; clipboard-write"
style={{
position:'absolute',
top:0,
left:0,
width:'100%',
height:'100%',
borderRadius: '16px',
border:'2px solid black'
}}
title="Export data"
></iframe>
</div>
<script src="https://player.vimeo.com/api/player.js"></script>
<VimeoEmbed videoId="926282262" title="Video demonstration" />
### Sorting a View
Order your fields data in ascending or descending order:
- Select **Sort**, choose a field, and define the sort order you desire.
- You can apply and arrange several sorts as needed.
<div style={{padding:'69.01% 0 0 0', position:'relative', margin: '32px 0px 0px'}}>
<iframe
src="https://player.vimeo.com/video/927885588?autoplay=1&loop=1&autopause=0&background=1&amp;app_id=58479"
frameBorder="0"
allow="autoplay; fullscreen; picture-in-picture; clipboard-write"
style={{
position:'absolute',
top:0,
left:0,
width:'100%',
height:'100%',
borderRadius: '16px',
border:'2px solid black'
}}
title="Export data"
></iframe>
</div>
<script src="https://player.vimeo.com/api/player.js"></script>
<VimeoEmbed videoId="927885588" title="Video demonstration" />
### Field display

View file

@ -26,6 +26,10 @@ import {
rule as maxConstsPerFile,
RULE_NAME as maxConstsPerFileName,
} from './rules/max-consts-per-file';
import {
rule as mdxComponentNewlines,
RULE_NAME as mdxComponentNewlinesName,
} from './rules/mdx-component-newlines';
import {
rule as noHardcodedColors,
RULE_NAME as noHardcodedColorsName,
@ -103,5 +107,6 @@ module.exports = {
[injectWorkspaceRepositoryName]: injectWorkspaceRepository,
[restApiMethodsShouldBeGuardedName]: restApiMethodsShouldBeGuarded,
[graphqlResolversShouldBeGuardedName]: graphqlResolversShouldBeGuarded,
[mdxComponentNewlinesName]: mdxComponentNewlines,
},
};

View file

@ -0,0 +1,97 @@
import type { TSESTree } from '@typescript-eslint/utils';
import type { Rule } from 'eslint';
export const RULE_NAME = 'mdx-component-newlines';
export const rule: Rule.RuleModule = {
meta: {
type: 'layout',
docs: {
description:
'Enforce JSX/HTML component tags are on separate lines in MDX files to prevent Crowdin translation issues',
recommended: true,
},
fixable: 'whitespace',
messages: {
tagOnSameLine:
'JSX/HTML tag should be on its own line. This prevents Crowdin from merging tags with content during translation.',
},
schema: [],
},
create: (context) => {
const sourceCode = context.sourceCode || context.getSourceCode();
return {
// Check JSX opening tags that have content on the same line
JSXOpeningElement: (node: TSESTree.JSXOpeningElement) => {
const tokenAfter = sourceCode.getTokenAfter(node as any);
if (!tokenAfter) {
return;
}
// Check if there's content on the same line after the opening tag
if (node.loc.end.line === tokenAfter.loc.start.line) {
// Allow if it's a closing tag immediately after (self-closing pattern)
const nextNode = (sourceCode as any).getNodeByRangeIndex?.(
tokenAfter.range[0],
);
if (nextNode?.type === 'JSXClosingElement') {
return;
}
// Check if it's actual content (not whitespace)
const textBetween = sourceCode.text.slice(
node.range[1],
tokenAfter.range[0],
);
if (textBetween.trim() === '') {
return; // Only whitespace, that's fine
}
context.report({
node: node as any,
messageId: 'tagOnSameLine',
fix: (fixer) => fixer.insertTextAfter(node as any, '\n'),
});
}
},
// Check JSX closing tags that have content on the same line before them
JSXClosingElement: (node: TSESTree.JSXClosingElement) => {
const tokenBefore = sourceCode.getTokenBefore(node as any);
if (!tokenBefore) {
return;
}
// Check if there's content on the same line before the closing tag
if (node.loc.start.line === tokenBefore.loc.end.line) {
// Check if it's actual content (not whitespace or opening tag)
const prevNode = (sourceCode as any).getNodeByRangeIndex?.(
tokenBefore.range[0],
);
if (prevNode?.type === 'JSXOpeningElement') {
return; // This is handled by the opening tag check
}
const textBetween = sourceCode.text.slice(
tokenBefore.range[1],
node.range[0],
);
// If there's any non-whitespace content before the closing tag on same line
if (textBetween.trim() !== '' || tokenBefore.type === 'Punctuator') {
context.report({
node: node as any,
messageId: 'tagOnSameLine',
fix: (fixer) => fixer.insertTextBefore(node as any, '\n'),
});
}
}
},
};
},
};

388
yarn.lock
View file

@ -2577,7 +2577,7 @@ __metadata:
languageName: node
linkType: hard
"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.10.4, @babel/code-frame@npm:^7.12.13, @babel/code-frame@npm:^7.16.7, @babel/code-frame@npm:^7.24.7, @babel/code-frame@npm:^7.27.1":
"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.10.4, @babel/code-frame@npm:^7.12.13, @babel/code-frame@npm:^7.16.7, @babel/code-frame@npm:^7.21.4, @babel/code-frame@npm:^7.24.7, @babel/code-frame@npm:^7.27.1":
version: 7.27.1
resolution: "@babel/code-frame@npm:7.27.1"
dependencies:
@ -12185,6 +12185,22 @@ __metadata:
languageName: node
linkType: hard
"@npmcli/config@npm:^8.0.0":
version: 8.3.4
resolution: "@npmcli/config@npm:8.3.4"
dependencies:
"@npmcli/map-workspaces": "npm:^3.0.2"
"@npmcli/package-json": "npm:^5.1.1"
ci-info: "npm:^4.0.0"
ini: "npm:^4.1.2"
nopt: "npm:^7.2.1"
proc-log: "npm:^4.2.0"
semver: "npm:^7.3.5"
walk-up-path: "npm:^3.0.1"
checksum: 10c0/f44af54bd2cdb32b132a861863bfe7936599a4706490136082585ab71e37ef47f201f8d2013b9902b3ff30cc8264f5da70f834c80f0a29953b52a28da20f5ea7
languageName: node
linkType: hard
"@npmcli/fs@npm:^1.0.0":
version: 1.1.1
resolution: "@npmcli/fs@npm:1.1.1"
@ -12246,6 +12262,23 @@ __metadata:
languageName: node
linkType: hard
"@npmcli/git@npm:^5.0.0":
version: 5.0.8
resolution: "@npmcli/git@npm:5.0.8"
dependencies:
"@npmcli/promise-spawn": "npm:^7.0.0"
ini: "npm:^4.1.3"
lru-cache: "npm:^10.0.1"
npm-pick-manifest: "npm:^9.0.0"
proc-log: "npm:^4.0.0"
promise-inflight: "npm:^1.0.1"
promise-retry: "npm:^2.0.1"
semver: "npm:^7.3.5"
which: "npm:^4.0.0"
checksum: 10c0/892441c968404950809c7b515a93b78167ea1db2252f259f390feae22a2c5477f3e1629e105e19a084c05afc56e585bf3f13c2f13b54a06bfd6786f0c8429532
languageName: node
linkType: hard
"@npmcli/installed-package-contents@npm:^1.0.6, @npmcli/installed-package-contents@npm:^1.0.7":
version: 1.0.7
resolution: "@npmcli/installed-package-contents@npm:1.0.7"
@ -12282,6 +12315,18 @@ __metadata:
languageName: node
linkType: hard
"@npmcli/map-workspaces@npm:^3.0.2":
version: 3.0.6
resolution: "@npmcli/map-workspaces@npm:3.0.6"
dependencies:
"@npmcli/name-from-folder": "npm:^2.0.0"
glob: "npm:^10.2.2"
minimatch: "npm:^9.0.0"
read-package-json-fast: "npm:^3.0.0"
checksum: 10c0/6bfcf8ca05ab9ddc2bd19c0fd91e9982f03cc6e67b0c03f04ba4d2f29b7d83f96e759c0f8f1f4b6dbe3182272483643a0d1269788352edd0c883d6fbfa2f3f14
languageName: node
linkType: hard
"@npmcli/metavuln-calculator@npm:^1.1.0":
version: 1.1.1
resolution: "@npmcli/metavuln-calculator@npm:1.1.1"
@ -12320,6 +12365,13 @@ __metadata:
languageName: node
linkType: hard
"@npmcli/name-from-folder@npm:^2.0.0":
version: 2.0.0
resolution: "@npmcli/name-from-folder@npm:2.0.0"
checksum: 10c0/1aa551771d98ab366d4cb06b33efd3bb62b609942f6d9c3bb667c10e5bb39a223d3e330022bc980a44402133e702ae67603862099ac8254dad11f90e77409827
languageName: node
linkType: hard
"@npmcli/node-gyp@npm:^1.0.1, @npmcli/node-gyp@npm:^1.0.2":
version: 1.0.3
resolution: "@npmcli/node-gyp@npm:1.0.3"
@ -12343,6 +12395,21 @@ __metadata:
languageName: node
linkType: hard
"@npmcli/package-json@npm:^5.1.1":
version: 5.2.1
resolution: "@npmcli/package-json@npm:5.2.1"
dependencies:
"@npmcli/git": "npm:^5.0.0"
glob: "npm:^10.2.2"
hosted-git-info: "npm:^7.0.0"
json-parse-even-better-errors: "npm:^3.0.0"
normalize-package-data: "npm:^6.0.0"
proc-log: "npm:^4.0.0"
semver: "npm:^7.5.3"
checksum: 10c0/b852e31e3121a0afe5fa20bbf4faa701a59dbc9d9dd7141f7fd57b8e919ce22c1285dcdfea490851fe410fa0f7bc9c397cafba0d268aaa53420a12d7c561dde1
languageName: node
linkType: hard
"@npmcli/promise-spawn@npm:^1.2.0, @npmcli/promise-spawn@npm:^1.3.2":
version: 1.3.2
resolution: "@npmcli/promise-spawn@npm:1.3.2"
@ -12361,6 +12428,15 @@ __metadata:
languageName: node
linkType: hard
"@npmcli/promise-spawn@npm:^7.0.0":
version: 7.0.2
resolution: "@npmcli/promise-spawn@npm:7.0.2"
dependencies:
which: "npm:^4.0.0"
checksum: 10c0/8f2af5bc2c1b1ccfb9bcd91da8873ab4723616d8bd5af877c0daa40b1e2cbfa4afb79e052611284179cae918c945a1b99ae1c565d78a355bec1a461011e89f71
languageName: node
linkType: hard
"@npmcli/run-script@npm:^1.8.2":
version: 1.8.6
resolution: "@npmcli/run-script@npm:1.8.6"
@ -23375,6 +23451,15 @@ __metadata:
languageName: node
linkType: hard
"@types/concat-stream@npm:^2.0.0":
version: 2.0.3
resolution: "@types/concat-stream@npm:2.0.3"
dependencies:
"@types/node": "npm:*"
checksum: 10c0/dd8bdf8061d275f30dc602e04c63ebc001d3a260e722c867916667a45f90fd22da62a2de0919a35f35969b84a14cb94c69d15bdb2c8a518ce8abf3a0e1a16e5d
languageName: node
linkType: hard
"@types/connect@npm:*, @types/connect@npm:3.4.38":
version: 3.4.38
resolution: "@types/connect@npm:3.4.38"
@ -23858,6 +23943,13 @@ __metadata:
languageName: node
linkType: hard
"@types/is-empty@npm:^1.0.0":
version: 1.2.3
resolution: "@types/is-empty@npm:1.2.3"
checksum: 10c0/2ca9af27ce93cc0abe277178a69803e641d755152bf4fc415e1789451ff62f6e39cf15dbdc111d490171d757669937ad4789c7395af55f5e7d261f6bfe416974
languageName: node
linkType: hard
"@types/is-hotkey@npm:^0.1.1":
version: 0.1.10
resolution: "@types/is-hotkey@npm:0.1.10"
@ -24457,6 +24549,15 @@ __metadata:
languageName: node
linkType: hard
"@types/node@npm:^22.0.0":
version: 22.19.0
resolution: "@types/node@npm:22.19.0"
dependencies:
undici-types: "npm:~6.21.0"
checksum: 10c0/66b98fcd38efb4ae58c628d61fed2b8f17c830eb665d8bceabc4174cae1dd81b8e4caac4c70d7dc929f9f76a3dc46cb21f480e904d0b898297bd12c0a2d1571a
languageName: node
linkType: hard
"@types/node@npm:^22.5.5, @types/node@npm:^22.7.5":
version: 22.18.1
resolution: "@types/node@npm:22.18.1"
@ -24830,6 +24931,13 @@ __metadata:
languageName: node
linkType: hard
"@types/supports-color@npm:^8.0.0":
version: 8.1.3
resolution: "@types/supports-color@npm:8.1.3"
checksum: 10c0/03aa3616b403f3deaeb774df6d3a3969845b0c9f449814a83c2c53eb6818f5f9b571ba205330b0ebe8e46f41fd550f581a34b4310b13f0e0448694cfff37ddbf
languageName: node
linkType: hard
"@types/tedious@npm:^4.0.14":
version: 4.0.14
resolution: "@types/tedious@npm:4.0.14"
@ -31204,6 +31312,18 @@ __metadata:
languageName: node
linkType: hard
"concat-stream@npm:^2.0.0":
version: 2.0.0
resolution: "concat-stream@npm:2.0.0"
dependencies:
buffer-from: "npm:^1.0.0"
inherits: "npm:^2.0.3"
readable-stream: "npm:^3.0.2"
typedarray: "npm:^0.0.6"
checksum: 10c0/29565dd9198fe1d8cf57f6cc71527dbc6ad67e12e4ac9401feb389c53042b2dceedf47034cbe702dfc4fd8df3ae7e6bfeeebe732cc4fa2674e484c13f04c219a
languageName: node
linkType: hard
"concurrently@npm:^8.2.2":
version: 8.2.2
resolution: "concurrently@npm:8.2.2"
@ -33525,6 +33645,13 @@ __metadata:
languageName: node
linkType: hard
"emoji-regex@npm:^10.2.1":
version: 10.6.0
resolution: "emoji-regex@npm:10.6.0"
checksum: 10c0/1e4aa097bb007301c3b4b1913879ae27327fdc48e93eeefefe3b87e495eb33c5af155300be951b4349ff6ac084f4403dc9eff970acba7c1c572d89396a9a32d7
languageName: node
linkType: hard
"emoji-regex@npm:^10.3.0":
version: 10.4.0
resolution: "emoji-regex@npm:10.4.0"
@ -33693,6 +33820,15 @@ __metadata:
languageName: node
linkType: hard
"error-ex@npm:^1.3.2":
version: 1.3.4
resolution: "error-ex@npm:1.3.4"
dependencies:
is-arrayish: "npm:^0.2.1"
checksum: 10c0/b9e34ff4778b8f3b31a8377e1c654456f4c41aeaa3d10a1138c3b7635d8b7b2e03eb2475d46d8ae055c1f180a1063e100bffabf64ea7e7388b37735df5328664
languageName: node
linkType: hard
"error@npm:^10.4.0":
version: 10.4.0
resolution: "error@npm:10.4.0"
@ -34702,6 +34838,33 @@ __metadata:
languageName: node
linkType: hard
"eslint-mdx@npm:^3.6.2":
version: 3.6.2
resolution: "eslint-mdx@npm:3.6.2"
dependencies:
acorn: "npm:^8.15.0"
acorn-jsx: "npm:^5.3.2"
espree: "npm:^9.6.1 || ^10.4.0"
estree-util-visit: "npm:^2.0.0"
remark-mdx: "npm:^3.1.0"
remark-parse: "npm:^11.0.0"
remark-stringify: "npm:^11.0.0"
synckit: "npm:^0.11.8"
unified: "npm:^11.0.5"
unified-engine: "npm:^11.2.2"
unist-util-visit: "npm:^5.0.0"
uvu: "npm:^0.5.6"
vfile: "npm:^6.0.3"
peerDependencies:
eslint: ">=8.0.0"
remark-lint-file-extension: "*"
peerDependenciesMeta:
remark-lint-file-extension:
optional: true
checksum: 10c0/07238395e932f5caf47a0301fc61f350a24c57e4bf923601f3e767f02d5f7ac18d708dd59819bcc1af54f16e5e6cc6aabb37a08614faade2a5e3fc317e802424
languageName: node
linkType: hard
"eslint-module-utils@npm:^2.12.1, eslint-module-utils@npm:^2.7.4":
version: 2.12.1
resolution: "eslint-module-utils@npm:2.12.1"
@ -34780,6 +34943,26 @@ __metadata:
languageName: node
linkType: hard
"eslint-plugin-mdx@npm:^3.6.2":
version: 3.6.2
resolution: "eslint-plugin-mdx@npm:3.6.2"
dependencies:
eslint-mdx: "npm:^3.6.2"
mdast-util-from-markdown: "npm:^2.0.2"
mdast-util-mdx: "npm:^3.0.0"
micromark-extension-mdxjs: "npm:^3.0.0"
remark-mdx: "npm:^3.1.0"
remark-parse: "npm:^11.0.0"
remark-stringify: "npm:^11.0.0"
synckit: "npm:^0.11.8"
unified: "npm:^11.0.5"
vfile: "npm:^6.0.3"
peerDependencies:
eslint: ">=8.0.0"
checksum: 10c0/16be144348fc6fe66d8a2d7d0f739084991aea5f79c3c4b4b20232ead0e826a5e26806b0fcdbfcda4cef325369a6ad1630bdb1d66a3b3dce9d5c0f765e110ee5
languageName: node
linkType: hard
"eslint-plugin-prefer-arrow@npm:^1.2.3":
version: 1.2.3
resolution: "eslint-plugin-prefer-arrow@npm:1.2.3"
@ -35044,7 +35227,7 @@ __metadata:
languageName: node
linkType: hard
"espree@npm:^10.0.1, espree@npm:^10.4.0":
"espree@npm:^10.0.1, espree@npm:^10.4.0, espree@npm:^9.6.1 || ^10.4.0":
version: 10.4.0
resolution: "espree@npm:10.4.0"
dependencies:
@ -38883,6 +39066,13 @@ __metadata:
languageName: node
linkType: hard
"ignore@npm:^6.0.0":
version: 6.0.2
resolution: "ignore@npm:6.0.2"
checksum: 10c0/9a38feac1861906a78ba0f03e8ef3cd6b0526dce2a1a84e1009324b557763afeb9c3ebcc04666b21f7bbf71adda45e76781bb9e2eaa0903d45dcaded634454f5
languageName: node
linkType: hard
"ignore@npm:^7.0.0":
version: 7.0.5
resolution: "ignore@npm:7.0.5"
@ -39018,6 +39208,13 @@ __metadata:
languageName: node
linkType: hard
"import-meta-resolve@npm:^4.0.0":
version: 4.2.0
resolution: "import-meta-resolve@npm:4.2.0"
checksum: 10c0/3ee8aeecb61d19b49d2703987f977e9d1c7d4ba47db615a570eaa02fe414f40dfa63f7b953e842cbe8470d26df6371332bfcf21b2fd92b0112f9fea80dde2c4c
languageName: node
linkType: hard
"imurmurhash@npm:^0.1.4":
version: 0.1.4
resolution: "imurmurhash@npm:0.1.4"
@ -39091,6 +39288,13 @@ __metadata:
languageName: node
linkType: hard
"ini@npm:^4.1.2, ini@npm:^4.1.3":
version: 4.1.3
resolution: "ini@npm:4.1.3"
checksum: 10c0/0d27eff094d5f3899dd7c00d0c04ea733ca03a8eb6f9406ce15daac1a81de022cb417d6eaff7e4342451ffa663389c565ffc68d6825eaf686bf003280b945764
languageName: node
linkType: hard
"ini@npm:^5.0.0":
version: 5.0.0
resolution: "ini@npm:5.0.0"
@ -39662,6 +39866,13 @@ __metadata:
languageName: node
linkType: hard
"is-empty@npm:^1.0.0":
version: 1.2.0
resolution: "is-empty@npm:1.2.0"
checksum: 10c0/f0dd6534716f2749586c35f1dcf37a0a5ac31e91d629ae2652b36c7f72c0ce71f0b68f082a6eed95b1af6f84ba31cd757c2343b19507878ed1e532a3383ebaaa
languageName: node
linkType: hard
"is-extendable@npm:^0.1.0":
version: 0.1.1
resolution: "is-extendable@npm:0.1.1"
@ -42428,6 +42639,13 @@ __metadata:
languageName: node
linkType: hard
"lines-and-columns@npm:^2.0.3":
version: 2.0.4
resolution: "lines-and-columns@npm:2.0.4"
checksum: 10c0/4db28bf065cd7ad897c0700f22d3d0d7c5ed6777e138861c601c496d545340df3fc19e18bd04ff8d95a246a245eb55685b82ca2f8c2ca53a008e9c5316250379
languageName: node
linkType: hard
"linkify-it@npm:5.0.0, linkify-it@npm:^5.0.0":
version: 5.0.0
resolution: "linkify-it@npm:5.0.0"
@ -42465,6 +42683,16 @@ __metadata:
languageName: node
linkType: hard
"load-plugin@npm:^6.0.0":
version: 6.0.3
resolution: "load-plugin@npm:6.0.3"
dependencies:
"@npmcli/config": "npm:^8.0.0"
import-meta-resolve: "npm:^4.0.0"
checksum: 10c0/cbbd4e18472a0ed543b6d60e867a1e2aae385205fcaa76d300ab5a72697e057422cd1e6ff2ba19755c55a86b3d53e53b81a814c757be720895ba525d05f75797
languageName: node
linkType: hard
"load-yaml-file@npm:^0.2.0":
version: 0.2.0
resolution: "load-yaml-file@npm:0.2.0"
@ -46364,7 +46592,7 @@ __metadata:
languageName: node
linkType: hard
"nopt@npm:^7.0.0":
"nopt@npm:^7.0.0, nopt@npm:^7.2.1":
version: 7.2.1
resolution: "nopt@npm:7.2.1"
dependencies:
@ -46399,6 +46627,17 @@ __metadata:
languageName: node
linkType: hard
"normalize-package-data@npm:^6.0.0":
version: 6.0.2
resolution: "normalize-package-data@npm:6.0.2"
dependencies:
hosted-git-info: "npm:^7.0.0"
semver: "npm:^7.3.5"
validate-npm-package-license: "npm:^3.0.4"
checksum: 10c0/7e32174e7f5575ede6d3d449593247183880122b4967d4ae6edb28cea5769ca025defda54fc91ec0e3c972fdb5ab11f9284606ba278826171b264cb16a9311ef
languageName: node
linkType: hard
"normalize-path@npm:3.0.0, normalize-path@npm:^3.0.0, normalize-path@npm:~3.0.0":
version: 3.0.0
resolution: "normalize-path@npm:3.0.0"
@ -46519,6 +46758,18 @@ __metadata:
languageName: node
linkType: hard
"npm-package-arg@npm:^11.0.0":
version: 11.0.3
resolution: "npm-package-arg@npm:11.0.3"
dependencies:
hosted-git-info: "npm:^7.0.0"
proc-log: "npm:^4.0.0"
semver: "npm:^7.3.5"
validate-npm-package-name: "npm:^5.0.0"
checksum: 10c0/e18333485e05c3a8774f4b5701ef74f4799533e650b70a68ca8dd697666c9a8d46932cb765fc593edce299521033bd4025a40323d5240cea8a393c784c0c285a
languageName: node
linkType: hard
"npm-package-arg@npm:^8.0.0, npm-package-arg@npm:^8.0.1, npm-package-arg@npm:^8.1.2, npm-package-arg@npm:^8.1.5":
version: 8.1.5
resolution: "npm-package-arg@npm:8.1.5"
@ -46577,6 +46828,18 @@ __metadata:
languageName: node
linkType: hard
"npm-pick-manifest@npm:^9.0.0":
version: 9.1.0
resolution: "npm-pick-manifest@npm:9.1.0"
dependencies:
npm-install-checks: "npm:^6.0.0"
npm-normalize-package-bin: "npm:^3.0.0"
npm-package-arg: "npm:^11.0.0"
semver: "npm:^7.3.5"
checksum: 10c0/8765f4199755b381323da2bff2202b4b15b59f59dba0d1be3f2f793b591321cd19e1b5a686ef48d9753a6bd4868550da632541a45dfb61809d55664222d73e44
languageName: node
linkType: hard
"npm-registry-fetch@npm:^11.0.0":
version: 11.0.0
resolution: "npm-registry-fetch@npm:11.0.0"
@ -47687,6 +47950,19 @@ __metadata:
languageName: node
linkType: hard
"parse-json@npm:^7.0.0":
version: 7.1.1
resolution: "parse-json@npm:7.1.1"
dependencies:
"@babel/code-frame": "npm:^7.21.4"
error-ex: "npm:^1.3.2"
json-parse-even-better-errors: "npm:^3.0.0"
lines-and-columns: "npm:^2.0.3"
type-fest: "npm:^3.8.0"
checksum: 10c0/a85ebc7430af7763fa52eb456d7efd35c35be5b06f04d8d80c37d0d33312ac6cdff12647acb9c95448dcc8b907dfafa81fb126e094aa132b0abc2a71b9df51d5
languageName: node
linkType: hard
"parse-latin@npm:^7.0.0":
version: 7.0.0
resolution: "parse-latin@npm:7.0.0"
@ -48868,7 +49144,7 @@ __metadata:
languageName: node
linkType: hard
"proc-log@npm:^4.1.0, proc-log@npm:^4.2.0":
"proc-log@npm:^4.0.0, proc-log@npm:^4.1.0, proc-log@npm:^4.2.0":
version: 4.2.0
resolution: "proc-log@npm:4.2.0"
checksum: 10c0/17db4757c2a5c44c1e545170e6c70a26f7de58feb985091fb1763f5081cab3d01b181fb2dd240c9f4a4255a1d9227d163d5771b7e69c9e49a561692db865efb9
@ -50381,7 +50657,7 @@ __metadata:
languageName: node
linkType: hard
"readable-stream@npm:3, readable-stream@npm:^3.0.6, readable-stream@npm:^3.1.1, readable-stream@npm:^3.4.0, readable-stream@npm:^3.5.0, readable-stream@npm:^3.6.0":
"readable-stream@npm:3, readable-stream@npm:^3.0.2, readable-stream@npm:^3.0.6, readable-stream@npm:^3.1.1, readable-stream@npm:^3.4.0, readable-stream@npm:^3.5.0, readable-stream@npm:^3.6.0":
version: 3.6.2
resolution: "readable-stream@npm:3.6.2"
dependencies:
@ -53652,6 +53928,17 @@ __metadata:
languageName: node
linkType: hard
"string-width@npm:^6.0.0":
version: 6.1.0
resolution: "string-width@npm:6.1.0"
dependencies:
eastasianwidth: "npm:^0.2.0"
emoji-regex: "npm:^10.2.1"
strip-ansi: "npm:^7.0.1"
checksum: 10c0/7b2991ea7c946a43042070787b85af454079116dfd6d853aab4ff8a6d4ac717cdc18656cfee15b7a7a78286669202a4a56385728f0740cb1e15001c71807b361
languageName: node
linkType: hard
"string-width@npm:^7.0.0, string-width@npm:^7.2.0":
version: 7.2.0
resolution: "string-width@npm:7.2.0"
@ -54185,6 +54472,13 @@ __metadata:
languageName: node
linkType: hard
"supports-color@npm:^9.0.0":
version: 9.4.0
resolution: "supports-color@npm:9.4.0"
checksum: 10c0/6c24e6b2b64c6a60e5248490cfa50de5924da32cf09ae357ad8ebbf305cc5d2717ba705a9d4cb397d80bbf39417e8fdc8d7a0ce18bd0041bf7b5b456229164e4
languageName: node
linkType: hard
"supports-hyperlinks@npm:^1.0.1":
version: 1.0.1
resolution: "supports-hyperlinks@npm:1.0.1"
@ -55972,6 +56266,7 @@ __metadata:
eslint-plugin-import: "npm:^2.31.0"
eslint-plugin-jsx-a11y: "npm:^6.10.2"
eslint-plugin-lingui: "npm:^0.9.0"
eslint-plugin-mdx: "npm:^3.6.2"
eslint-plugin-prefer-arrow: "npm:^1.2.3"
eslint-plugin-prettier: "npm:^5.1.2"
eslint-plugin-project-structure: "npm:^3.9.1"
@ -56569,6 +56864,35 @@ __metadata:
languageName: node
linkType: hard
"unified-engine@npm:^11.2.2":
version: 11.2.2
resolution: "unified-engine@npm:11.2.2"
dependencies:
"@types/concat-stream": "npm:^2.0.0"
"@types/debug": "npm:^4.0.0"
"@types/is-empty": "npm:^1.0.0"
"@types/node": "npm:^22.0.0"
"@types/unist": "npm:^3.0.0"
concat-stream: "npm:^2.0.0"
debug: "npm:^4.0.0"
extend: "npm:^3.0.0"
glob: "npm:^10.0.0"
ignore: "npm:^6.0.0"
is-empty: "npm:^1.0.0"
is-plain-obj: "npm:^4.0.0"
load-plugin: "npm:^6.0.0"
parse-json: "npm:^7.0.0"
trough: "npm:^2.0.0"
unist-util-inspect: "npm:^8.0.0"
vfile: "npm:^6.0.0"
vfile-message: "npm:^4.0.0"
vfile-reporter: "npm:^8.0.0"
vfile-statistics: "npm:^3.0.0"
yaml: "npm:^2.0.0"
checksum: 10c0/daac3b2bf18fb79a052129958e104bddfb8241ef5ea51696a214864906a61a375c4d95b42958b7ed300ebaa028172f1e8b6515f1664a0fa765eb11ca06b891ee
languageName: node
linkType: hard
"unified@npm:^10.0.0":
version: 10.1.2
resolution: "unified@npm:10.1.2"
@ -56697,6 +57021,15 @@ __metadata:
languageName: node
linkType: hard
"unist-util-inspect@npm:^8.0.0":
version: 8.1.0
resolution: "unist-util-inspect@npm:8.1.0"
dependencies:
"@types/unist": "npm:^3.0.0"
checksum: 10c0/d3dff256ffd77a1e8dd583be89070dc1ab124d424794fcc1105a38c2f0bb0538afc686e592699807c7d9fa612821961033fe38e26c11ba0bb51d19e8ae7c4119
languageName: node
linkType: hard
"unist-util-is@npm:^5.0.0":
version: 5.2.1
resolution: "unist-util-is@npm:5.2.1"
@ -57392,7 +57725,7 @@ __metadata:
languageName: node
linkType: hard
"uvu@npm:^0.5.0":
"uvu@npm:^0.5.0, uvu@npm:^0.5.6":
version: 0.5.6
resolution: "uvu@npm:0.5.6"
dependencies:
@ -57540,6 +57873,42 @@ __metadata:
languageName: node
linkType: hard
"vfile-reporter@npm:^8.0.0":
version: 8.1.1
resolution: "vfile-reporter@npm:8.1.1"
dependencies:
"@types/supports-color": "npm:^8.0.0"
string-width: "npm:^6.0.0"
supports-color: "npm:^9.0.0"
unist-util-stringify-position: "npm:^4.0.0"
vfile: "npm:^6.0.0"
vfile-message: "npm:^4.0.0"
vfile-sort: "npm:^4.0.0"
vfile-statistics: "npm:^3.0.0"
checksum: 10c0/5da85c67e4a26762d64d65d0aac5ef339a413cc051470d970eea7352f07afd24577d42780c3af93c109177078df1bbbdbcc3e82adcc34e1bb96d2665f3f0c2a1
languageName: node
linkType: hard
"vfile-sort@npm:^4.0.0":
version: 4.0.0
resolution: "vfile-sort@npm:4.0.0"
dependencies:
vfile: "npm:^6.0.0"
vfile-message: "npm:^4.0.0"
checksum: 10c0/fe1a4cbe24d03b81a7e7486be107eb029ac2631a3575e55a3f1d25cf54bcf2d60b3f76694dedf8a2f60793877e1d192234157cdfd50d1a0d18b9a4c1487cdf65
languageName: node
linkType: hard
"vfile-statistics@npm:^3.0.0":
version: 3.0.0
resolution: "vfile-statistics@npm:3.0.0"
dependencies:
vfile: "npm:^6.0.0"
vfile-message: "npm:^4.0.0"
checksum: 10c0/3de51670329701e2cff75d979564087578844444d9b9d8619a2fdd2a904bc970bf4d05b58e7cee71e0f6f34087f1f7f2ea85cdfa5bf58f572c777432c156bd8f
languageName: node
linkType: hard
"vfile@npm:^5.0.0, vfile@npm:^5.3.0":
version: 5.3.7
resolution: "vfile@npm:5.3.7"
@ -57936,6 +58305,13 @@ __metadata:
languageName: node
linkType: hard
"walk-up-path@npm:^3.0.1":
version: 3.0.1
resolution: "walk-up-path@npm:3.0.1"
checksum: 10c0/3184738e0cf33698dd58b0ee4418285b9c811e58698f52c1f025435a85c25cbc5a63fee599f1a79cb29ca7ef09a44ec9417b16bfd906b1a37c305f7aa20ee5bc
languageName: node
linkType: hard
"walker@npm:^1.0.8":
version: 1.0.8
resolution: "walker@npm:1.0.8"