Compare commits
92 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4f3c45aff3 | |||
| 803e3ece5a | |||
| c4e897a7b5 | |||
| 70bb011daa | |||
| 9e515afb13 | |||
| a34bf11dae | |||
| ce54f69a4e | |||
| 3904addeb0 | |||
| d53f3e4ccc | |||
| 565995e715 | |||
| 79b612ee12 | |||
| 5003fcbbf2 | |||
| b6b4824104 | |||
| 8d54ff6ca0 | |||
| 1adb59f7f8 | |||
| 93df64b9b0 | |||
| a3b0a34207 | |||
| 009f597eec | |||
| b0413575f5 | |||
| b03f044d0f | |||
| 75c22a2119 | |||
| 3d8207af0f | |||
| c227b0d06a | |||
| f634a4a0c0 | |||
| 689ec16f50 | |||
| 1b09c69c39 | |||
| b1f7a2c544 | |||
| e7032d0638 | |||
| 5f3407878d | |||
| 7c053716ae | |||
| 6c18bacb93 | |||
| 93d83b2e36 | |||
| 0c5aec9c73 | |||
| ed75fc8a25 | |||
| 9813467cee | |||
| 1ea7c7ecc4 | |||
| 626455b534 | |||
| 2c3e81960c | |||
| 487112d438 | |||
| 5c1fe45760 | |||
| e72a907baa | |||
| 8909badc59 | |||
| c81019965d | |||
| 58dd5d3561 | |||
| c611a7ac20 | |||
| 50a4fe5040 | |||
| 34b927ff23 | |||
| 086830f81b | |||
| a962cdc34f | |||
| a15bf1e608 | |||
| 524e5d8cf7 | |||
| 2e5ccd9b86 | |||
| 459c64f642 | |||
| 3420d63b7a | |||
| 4da8878697 | |||
| 23aa859502 | |||
| 773245fa65 | |||
| 0f8ee5714f | |||
| 7f4f2e932c | |||
| 0d05788547 | |||
| 837a946b5f | |||
| cb0b71dbdc | |||
| 546ab0a036 | |||
| 89eeb34ca7 | |||
| ee8004922e | |||
| 18b9cc5281 | |||
| 284ebeb12d | |||
| fa903c6971 | |||
| e294d74e07 | |||
| 59f2b1c724 | |||
| 4aca4d1143 | |||
| 1553ff2857 | |||
| 9441e0456c | |||
| 7999cd3dde | |||
| 86eab9ac24 | |||
| 95bc8aea28 | |||
| 24e64350ee | |||
| 40da6f605d | |||
| 9fc5be1c4c | |||
| ca58c7f15e | |||
| a3224880e0 | |||
| e2afcac076 | |||
| 2b4fa9d8cf | |||
| 2eae25dc34 | |||
| 1179357bc7 | |||
| f720186122 | |||
| 9f930aa366 | |||
| e49673df79 | |||
| 900f70fb9e | |||
| 027331c893 | |||
| 02e7c54043 | |||
| 66d088a89d |
@@ -0,0 +1,72 @@
|
||||
---
|
||||
description: GitHub Actions security guidelines for supply chain protection
|
||||
globs: **/.github/**/*.yml, **/.github/**/*.yaml
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
# GitHub Actions Security
|
||||
|
||||
## Pin Third-Party Actions to Commit SHAs
|
||||
|
||||
Always reference external actions and reusable workflows by their full commit SHA, never by a mutable tag or branch. Tags can be force-pushed by a compromised maintainer account.
|
||||
|
||||
```yaml
|
||||
# ❌ Mutable tag — vulnerable to supply chain attacks
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/setup-node@v4
|
||||
|
||||
# ✅ Pinned to commit SHA with tag comment for readability
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
```
|
||||
|
||||
## Prefer `gh api` Over Third-Party Dispatch Actions
|
||||
|
||||
For repository dispatch calls, use `gh api` directly instead of third-party actions like `peter-evans/repository-dispatch`. This eliminates a supply-chain dependency entirely.
|
||||
|
||||
```yaml
|
||||
# ✅ Use env vars + bracket notation to prevent injection
|
||||
- name: Dispatch to target repo
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.DISPATCH_TOKEN }}
|
||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||
BRANCH: ${{ github.event.workflow_run.head_branch }}
|
||||
run: |
|
||||
gh api repos/org/repo/dispatches \
|
||||
-f event_type=my-event \
|
||||
-f "client_payload[pr_number]=$PR_NUMBER" \
|
||||
-f "client_payload[branch]=$BRANCH"
|
||||
|
||||
# ✅ Simple dispatch without payload
|
||||
- name: Trigger workflow
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.DISPATCH_TOKEN }}
|
||||
run: |
|
||||
gh api repos/org/repo/dispatches -f event_type=my-event
|
||||
|
||||
# ❌ Third-party action dependency
|
||||
- uses: peter-evans/repository-dispatch@v2
|
||||
with:
|
||||
token: ${{ secrets.DISPATCH_TOKEN }}
|
||||
repository: org/repo
|
||||
event-type: my-event
|
||||
|
||||
# ❌ Inline ${{ }} in shell — vulnerable to injection
|
||||
- run: |
|
||||
gh api repos/org/repo/dispatches --input - <<EOF
|
||||
{"event_type": "x", "client_payload": {"branch": "${{ github.head_ref }}"}}
|
||||
EOF
|
||||
```
|
||||
|
||||
## Minimal Permissions
|
||||
|
||||
Always declare explicit `permissions` at the job level with the least privilege required. Never rely on the default `GITHUB_TOKEN` permissions.
|
||||
|
||||
```yaml
|
||||
# ✅ Explicit minimal permissions
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
# ❌ Overly broad or implicit permissions
|
||||
permissions: write-all
|
||||
```
|
||||
@@ -0,0 +1,6 @@
|
||||
/.github/ @charlesBochet @FelixMalfait @Weiko @prastoin @bosiraphael @etiennejouan @ijreilly @martmull @thomtrp
|
||||
/.github/CODEOWNERS @charlesBochet @FelixMalfait @Weiko @prastoin @bosiraphael @etiennejouan @ijreilly @martmull @thomtrp
|
||||
/.github/workflows/ @charlesBochet @FelixMalfait @Weiko @prastoin @bosiraphael @etiennejouan @ijreilly @martmull @thomtrp
|
||||
/.github/actions/ @charlesBochet @FelixMalfait @Weiko @prastoin @bosiraphael @etiennejouan @ijreilly @martmull @thomtrp
|
||||
/.github/dependabot.yml @charlesBochet @FelixMalfait @Weiko @prastoin @bosiraphael @etiennejouan @ijreilly @martmull @thomtrp
|
||||
/.yarnrc.yml @charlesBochet @FelixMalfait @Weiko @prastoin @bosiraphael @etiennejouan @ijreilly @martmull @thomtrp
|
||||
@@ -21,7 +21,7 @@ runs:
|
||||
run: corepack enable
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version-file: '${{ inputs.app-path }}/.nvmrc'
|
||||
cache: yarn
|
||||
|
||||
@@ -21,7 +21,7 @@ runs:
|
||||
run: corepack enable
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version-file: '${{ inputs.app-path }}/.nvmrc'
|
||||
cache: yarn
|
||||
|
||||
@@ -20,7 +20,7 @@ runs:
|
||||
run: git fetch origin main --depth=1
|
||||
- name: Get last successful commit
|
||||
if: env.NX_BASE == ''
|
||||
uses: nrwl/nx-set-shas@v4
|
||||
uses: nrwl/nx-set-shas@3e9ad7370203c1e93d109be57f3b72eb0eb511b1 # v4
|
||||
- name: Fallback to origin/main if no base found
|
||||
if: env.NX_BASE == ''
|
||||
shell: bash
|
||||
|
||||
@@ -25,7 +25,7 @@ runs:
|
||||
run: |
|
||||
echo "CACHE_PRIMARY_KEY_PREFIX=v4-${CACHE_KEY}-${REF_NAME}" >> "${GITHUB_OUTPUT}"
|
||||
- name: Restore cache
|
||||
uses: actions/cache/restore@v4
|
||||
uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 (restore)
|
||||
id: restore-cache
|
||||
with:
|
||||
key: ${{ steps.cache-primary-key-builder.outputs.CACHE_PRIMARY_KEY_PREFIX }}-${{ github.sha }}
|
||||
|
||||
@@ -9,8 +9,11 @@ inputs:
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
# Fork PRs on pull_request already can't write to the base repo's cache (GitHub built-in).
|
||||
# The fork guard is defense-in-depth for pull_request_target, which does have write access.
|
||||
- name: Save cache
|
||||
uses: actions/cache/save@v4
|
||||
if: ${{ format('{0}', github.event.pull_request.head.repo.fork) != 'true' }}
|
||||
uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 (save)
|
||||
with:
|
||||
key: ${{ inputs.key }}
|
||||
path: |
|
||||
|
||||
@@ -48,7 +48,7 @@ runs:
|
||||
fi
|
||||
|
||||
- name: Checkout docker compose files
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
||||
with:
|
||||
repository: ${{ inputs.twenty-repository }}
|
||||
ref: ${{ steps.resolve.outputs.git-ref }}
|
||||
|
||||
@@ -29,12 +29,12 @@ runs:
|
||||
echo "packages/*/node_modules" >> $GITHUB_OUTPUT
|
||||
echo 'EOF' >> $GITHUB_OUTPUT
|
||||
- name: Setup Node.js and get yarn cache
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version: ${{ inputs.node-version }}
|
||||
- name: Restore node_modules
|
||||
id: cache-node-modules
|
||||
uses: actions/cache/restore@v4
|
||||
uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 (restore)
|
||||
with:
|
||||
key: v4-${{ steps.globals.outputs.CACHE_KEY_PREFIX }}-${{github.sha}}
|
||||
restore-keys: v4-${{ steps.globals.outputs.CACHE_KEY_PREFIX }}-
|
||||
@@ -44,10 +44,13 @@ runs:
|
||||
shell: ${{ steps.globals.outputs.ACTION_SHELL }}
|
||||
run: |
|
||||
yarn config set enableHardenedMode true
|
||||
yarn config set enableScripts false
|
||||
yarn --immutable --check-cache
|
||||
# Fork PRs on pull_request already can't write to the base repo's cache (GitHub built-in).
|
||||
# The fork guard is defense-in-depth for pull_request_target, which does have write access.
|
||||
- name: Save cache
|
||||
if: ${{ steps.cache-node-modules.outputs.cache-hit != 'true' && steps.cache-node-modules.outputs.cache-matched-key == '' }}
|
||||
uses: actions/cache/save@v4
|
||||
if: ${{ steps.cache-node-modules.outputs.cache-hit != 'true' && steps.cache-node-modules.outputs.cache-matched-key == '' && format('{0}', github.event.pull_request.head.repo.fork) != 'true' }}
|
||||
uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 (save)
|
||||
with:
|
||||
key: ${{ steps.cache-node-modules.outputs.cache-primary-key }}
|
||||
path: ${{ steps.globals.outputs.PATH_TO_CACHE }}
|
||||
|
||||
@@ -14,9 +14,8 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Repository Dispatch
|
||||
uses: peter-evans/repository-dispatch@v2
|
||||
with:
|
||||
token: ${{ secrets.TWENTY_INFRA_TOKEN }}
|
||||
repository: twentyhq/twenty-infra
|
||||
event-type: auto-deploy-main
|
||||
client-payload: '{"github": ${{ toJson(github) }}}' # Passes the entire github context to the downstream workflow
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.TWENTY_INFRA_TOKEN }}
|
||||
run: |
|
||||
gh api repos/twentyhq/twenty-infra/dispatches \
|
||||
-f event_type=auto-deploy-main
|
||||
|
||||
@@ -14,9 +14,10 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Repository Dispatch
|
||||
uses: peter-evans/repository-dispatch@v2
|
||||
with:
|
||||
token: ${{ secrets.TWENTY_INFRA_TOKEN }}
|
||||
repository: twentyhq/twenty-infra
|
||||
event-type: auto-deploy-tag
|
||||
client-payload: '{"github": ${{ toJson(github) }}}' # Passes the entire github context to the downstream workflow
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.TWENTY_INFRA_TOKEN }}
|
||||
REF_NAME: ${{ github.ref_name }}
|
||||
run: |
|
||||
gh api repos/twentyhq/twenty-infra/dispatches \
|
||||
-f event_type=auto-deploy-tag \
|
||||
-f "client_payload[github][ref_name]=$REF_NAME"
|
||||
|
||||
@@ -21,11 +21,11 @@ jobs:
|
||||
any_changed: ${{ steps.changed-files.outputs.any_changed }}
|
||||
steps:
|
||||
- name: Fetch custom Github Actions and base branch history
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
||||
with:
|
||||
fetch-depth: 10
|
||||
- name: Check for changed files
|
||||
id: changed-files
|
||||
uses: tj-actions/changed-files@v45
|
||||
uses: tj-actions/changed-files@48d8f15b2aaa3d255ca5af3eba4870f807ce6b3c # v45.0.9
|
||||
with:
|
||||
files: ${{ inputs.files }}
|
||||
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
NODE_OPTIONS: '--max-old-space-size=4096'
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
||||
with:
|
||||
ref: main
|
||||
|
||||
@@ -41,7 +41,7 @@ jobs:
|
||||
|
||||
- name: Create pull request
|
||||
if: steps.changes.outputs.changed == 'true'
|
||||
uses: peter-evans/create-pull-request@v7
|
||||
uses: peter-evans/create-pull-request@22a9089034f40e5a961c8808d113e2c98fb63676 # v7.0.6
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
commit-message: 'chore: sync AI model catalog from models.dev'
|
||||
@@ -61,8 +61,7 @@ jobs:
|
||||
|
||||
- name: Trigger automerge
|
||||
if: steps.changes.outputs.changed == 'true'
|
||||
uses: peter-evans/repository-dispatch@v2
|
||||
with:
|
||||
token: ${{ secrets.TWENTY_INFRA_TOKEN }}
|
||||
repository: twentyhq/twenty-infra
|
||||
event-type: automated-pr-ready
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.TWENTY_INFRA_TOKEN }}
|
||||
run: |
|
||||
gh api repos/twentyhq/twenty-infra/dispatches -f event_type=automated-pr-ready
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
name: GraphQL and OpenAPI Breaking Changes Detection
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
pull_request:
|
||||
types: [opened, synchronize, edited]
|
||||
branches:
|
||||
- main
|
||||
@@ -54,7 +54,7 @@ jobs:
|
||||
image: clickhouse/clickhouse-server:25.8.8
|
||||
env:
|
||||
CLICKHOUSE_PASSWORD: clickhousePassword
|
||||
CLICKHOUSE_URL: "http://default:clickhousePassword@localhost:8123/twenty"
|
||||
CLICKHOUSE_URL: 'http://default:clickhousePassword@localhost:8123/twenty'
|
||||
ports:
|
||||
- 8123:8123
|
||||
- 9000:9000
|
||||
@@ -66,7 +66,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout current branch
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
||||
with:
|
||||
fetch-depth: 10
|
||||
|
||||
@@ -164,9 +164,17 @@ jobs:
|
||||
interval=5
|
||||
elapsed=0
|
||||
|
||||
ADMIN_TOKEN=$(jq -r '.APPLE_JANE_ADMIN_ACCESS_TOKEN' packages/twenty-server/test/integration/constants/test-tokens.json)
|
||||
|
||||
while [ $elapsed -lt $timeout ]; do
|
||||
if curl -s "http://localhost:${{ env.CURRENT_SERVER_PORT }}/graphql" > /dev/null 2>&1 && \
|
||||
curl -s "http://localhost:${{ env.CURRENT_SERVER_PORT }}/rest/open-api/core" > /dev/null 2>&1; then
|
||||
GRAPHQL_RESPONSE=$(curl -s -X POST "http://localhost:${{ env.CURRENT_SERVER_PORT }}/graphql" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer ${ADMIN_TOKEN}" \
|
||||
-d '{"query":"{ __schema { queryType { name } } }"}' 2>/dev/null || echo '{}')
|
||||
|
||||
if echo "$GRAPHQL_RESPONSE" | jq -e '.data.__schema' > /dev/null 2>&1 && \
|
||||
curl -fsS "http://localhost:${{ env.CURRENT_SERVER_PORT }}/rest/open-api/core" \
|
||||
-H "Authorization: Bearer ${ADMIN_TOKEN}" > /dev/null 2>&1; then
|
||||
echo "Current branch server is ready!"
|
||||
break
|
||||
fi
|
||||
@@ -177,10 +185,9 @@ jobs:
|
||||
done
|
||||
|
||||
if [ $elapsed -ge $timeout ]; then
|
||||
echo "Timeout waiting for current branch server to start"
|
||||
echo "::warning::Timed out waiting for current branch server to serve a valid schema. Validation will skip the API diff."
|
||||
echo "Current server log:"
|
||||
cat /tmp/current-server.log || echo "No current server log found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Download GraphQL and REST responses from current branch
|
||||
@@ -228,7 +235,6 @@ jobs:
|
||||
echo "Current branch files downloaded:"
|
||||
ls -la current-*
|
||||
|
||||
|
||||
- name: Preserve current branch files
|
||||
run: |
|
||||
# Create a temp directory to store current branch files
|
||||
@@ -298,7 +304,7 @@ jobs:
|
||||
|
||||
set_env_var "PG_DATABASE_URL" "postgres://postgres:postgres@localhost:5432/main_branch"
|
||||
set_env_var "NODE_PORT" "${{ env.MAIN_SERVER_PORT }}"
|
||||
set_env_var "REDIS_URL" "redis://localhost:6379"
|
||||
set_env_var "REDIS_URL" "redis://localhost:6379/1"
|
||||
set_env_var "CLICKHOUSE_URL" "http://default:clickhousePassword@localhost:8123/twenty"
|
||||
set_env_var "CLICKHOUSE_PASSWORD" "clickhousePassword"
|
||||
|
||||
@@ -324,9 +330,17 @@ jobs:
|
||||
interval=5
|
||||
elapsed=0
|
||||
|
||||
ADMIN_TOKEN=$(jq -r '.APPLE_JANE_ADMIN_ACCESS_TOKEN' packages/twenty-server/test/integration/constants/test-tokens.json)
|
||||
|
||||
while [ $elapsed -lt $timeout ]; do
|
||||
if curl -s "http://localhost:${{ env.MAIN_SERVER_PORT }}/graphql" > /dev/null 2>&1 && \
|
||||
curl -s "http://localhost:${{ env.MAIN_SERVER_PORT }}/rest/open-api/core" > /dev/null 2>&1; then
|
||||
GRAPHQL_RESPONSE=$(curl -s -X POST "http://localhost:${{ env.MAIN_SERVER_PORT }}/graphql" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer ${ADMIN_TOKEN}" \
|
||||
-d '{"query":"{ __schema { queryType { name } } }"}' 2>/dev/null || echo '{}')
|
||||
|
||||
if echo "$GRAPHQL_RESPONSE" | jq -e '.data.__schema' > /dev/null 2>&1 && \
|
||||
curl -fsS "http://localhost:${{ env.MAIN_SERVER_PORT }}/rest/open-api/core" \
|
||||
-H "Authorization: Bearer ${ADMIN_TOKEN}" > /dev/null 2>&1; then
|
||||
echo "Main branch server is ready!"
|
||||
break
|
||||
fi
|
||||
@@ -337,10 +351,9 @@ jobs:
|
||||
done
|
||||
|
||||
if [ $elapsed -ge $timeout ]; then
|
||||
echo "Timeout waiting for main branch server to start"
|
||||
echo "::warning::Timed out waiting for main branch server to serve a valid schema. Validation will skip the API diff."
|
||||
echo "Main server log:"
|
||||
cat /tmp/main-server.log || echo "No main server log found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Download GraphQL and REST responses from main branch
|
||||
@@ -388,7 +401,6 @@ jobs:
|
||||
echo "Main branch files downloaded:"
|
||||
ls -la main-*
|
||||
|
||||
|
||||
- name: Restore current branch files
|
||||
run: |
|
||||
# Move current branch files back to working directory
|
||||
@@ -407,11 +419,33 @@ jobs:
|
||||
valid=true
|
||||
|
||||
for file in main-schema-introspection.json current-schema-introspection.json \
|
||||
main-metadata-schema-introspection.json current-metadata-schema-introspection.json \
|
||||
main-rest-api.json current-rest-api.json \
|
||||
main-rest-metadata-api.json current-rest-metadata-api.json; do
|
||||
main-metadata-schema-introspection.json current-metadata-schema-introspection.json; do
|
||||
if [ ! -f "$file" ] || ! jq empty "$file" 2>/dev/null; then
|
||||
echo "::warning::Invalid or missing schema file: $file"
|
||||
elif ! jq -e '.data.__schema' "$file" >/dev/null 2>&1; then
|
||||
echo "::warning::File $file is not a valid GraphQL introspection result. First 200 bytes: $(head -c 200 "$file")"
|
||||
valid=false
|
||||
fi
|
||||
done
|
||||
|
||||
for file in main-rest-api.json current-rest-api.json \
|
||||
main-rest-metadata-api.json current-rest-metadata-api.json; do
|
||||
if [ ! -f "$file" ]; then
|
||||
echo "::warning::Missing OpenAPI spec file: $file"
|
||||
valid=false
|
||||
elif ! jq -e '.openapi // .swagger' "$file" >/dev/null 2>&1; then
|
||||
echo "::warning::File $file is not a valid OpenAPI spec. First 200 bytes: $(head -c 200 "$file")"
|
||||
valid=false
|
||||
elif ! jq -e '.data.__schema' "$file" > /dev/null 2>&1; then
|
||||
echo "::warning::Schema file is not a valid GraphQL introspection response: $file"
|
||||
valid=false
|
||||
fi
|
||||
done
|
||||
|
||||
for file in main-rest-api.json current-rest-api.json \
|
||||
main-rest-metadata-api.json current-rest-metadata-api.json; do
|
||||
if [ ! -f "$file" ] || ! jq empty "$file" 2>/dev/null; then
|
||||
echo "::warning::Invalid or missing REST API file: $file"
|
||||
valid=false
|
||||
fi
|
||||
done
|
||||
@@ -608,7 +642,7 @@ jobs:
|
||||
|
||||
- name: Upload breaking changes report
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: breaking-changes-report
|
||||
path: |
|
||||
@@ -626,5 +660,3 @@ jobs:
|
||||
if [ -f /tmp/main-server.pid ]; then
|
||||
kill $(cat /tmp/main-server.pid) || true
|
||||
fi
|
||||
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ jobs:
|
||||
TWENTY_API_KEY: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIyMDIwMjAyMC1lNmI1LTQ2ODAtOGEzMi1iODIwOTczNzE1NmIiLCJ1c2VySWQiOiIyMDIwMjAyMC1lNmI1LTQ2ODAtOGEzMi1iODIwOTczNzE1NmIiLCJ3b3Jrc3BhY2VJZCI6IjIwMjAyMDIwLTFjMjUtNGQwMi1iZjI1LTZhZWNjZjdlYTQxOSIsIndvcmtzcGFjZU1lbWJlcklkIjoiMjAyMDIwMjAtNDYzZi00MzViLTgyOGMtMTA3ZTAwN2EyNzExIiwidXNlcldvcmtzcGFjZUlkIjoiMjAyMDIwMjAtMWU3Yy00M2Q5LWE1ZGItNjg1YjUwNjlkODE2IiwidHlwZSI6IkFDQ0VTUyIsImF1dGhQcm92aWRlciI6InBhc3N3b3JkIiwiaWF0IjoxNzUxMjgxNzA0LCJleHAiOjIwNjY4NTc3MDR9.HMGqCsVlOAPVUBhKSGlD1X86VoHKt4LIUtET3CGIdik
|
||||
steps:
|
||||
- name: Fetch custom Github Actions and base branch history
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
||||
with:
|
||||
fetch-depth: 10
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ jobs:
|
||||
TWENTY_API_KEY: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIyMDIwMjAyMC1lNmI1LTQ2ODAtOGEzMi1iODIwOTczNzE1NmIiLCJ1c2VySWQiOiIyMDIwMjAyMC1lNmI1LTQ2ODAtOGEzMi1iODIwOTczNzE1NmIiLCJ3b3Jrc3BhY2VJZCI6IjIwMjAyMDIwLTFjMjUtNGQwMi1iZjI1LTZhZWNjZjdlYTQxOSIsIndvcmtzcGFjZU1lbWJlcklkIjoiMjAyMDIwMjAtNDYzZi00MzViLTgyOGMtMTA3ZTAwN2EyNzExIiwidXNlcldvcmtzcGFjZUlkIjoiMjAyMDIwMjAtMWU3Yy00M2Q5LWE1ZGItNjg1YjUwNjlkODE2IiwidHlwZSI6IkFDQ0VTUyIsImF1dGhQcm92aWRlciI6InBhc3N3b3JkIiwiaWF0IjoxNzUxMjgxNzA0LCJleHAiOjIwNjY4NTc3MDR9.HMGqCsVlOAPVUBhKSGlD1X86VoHKt4LIUtET3CGIdik
|
||||
steps:
|
||||
- name: Fetch custom Github Actions and base branch history
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
||||
with:
|
||||
fetch-depth: 10
|
||||
|
||||
@@ -105,7 +105,7 @@ jobs:
|
||||
create-twenty-app --version
|
||||
mkdir -p /tmp/e2e-test-workspace
|
||||
cd /tmp/e2e-test-workspace
|
||||
create-twenty-app test-app --display-name "Test scaffolded app" --description "E2E test scaffolded app" --skip-local-instance
|
||||
create-twenty-app test-app --display-name "Test scaffolded app" --description "E2E test scaffolded app" --skip-local-instance --yes
|
||||
|
||||
- name: Install scaffolded app dependencies
|
||||
run: |
|
||||
|
||||
@@ -57,7 +57,7 @@ jobs:
|
||||
TWENTY_API_KEY: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIyMDIwMjAyMC1lNmI1LTQ2ODAtOGEzMi1iODIwOTczNzE1NmIiLCJ1c2VySWQiOiIyMDIwMjAyMC1lNmI1LTQ2ODAtOGEzMi1iODIwOTczNzE1NmIiLCJ3b3Jrc3BhY2VJZCI6IjIwMjAyMDIwLTFjMjUtNGQwMi1iZjI1LTZhZWNjZjdlYTQxOSIsIndvcmtzcGFjZU1lbWJlcklkIjoiMjAyMDIwMjAtNDYzZi00MzViLTgyOGMtMTA3ZTAwN2EyNzExIiwidXNlcldvcmtzcGFjZUlkIjoiMjAyMDIwMjAtMWU3Yy00M2Q5LWE1ZGItNjg1YjUwNjlkODE2IiwidHlwZSI6IkFDQ0VTUyIsImF1dGhQcm92aWRlciI6InBhc3N3b3JkIiwiaWF0IjoxNzUxMjgxNzA0LCJleHAiOjIwNjY4NTc3MDR9.HMGqCsVlOAPVUBhKSGlD1X86VoHKt4LIUtET3CGIdik
|
||||
steps:
|
||||
- name: Fetch custom Github Actions and base branch history
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
||||
with:
|
||||
fetch-depth: 10
|
||||
|
||||
|
||||
@@ -30,12 +30,8 @@ jobs:
|
||||
matrix:
|
||||
task: [lint, typecheck, test]
|
||||
steps:
|
||||
- name: Cancel Previous Runs
|
||||
uses: styfle/cancel-workflow-action@0.11.0
|
||||
with:
|
||||
access_token: ${{ github.token }}
|
||||
- name: Fetch custom Github Actions and base branch history
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
||||
with:
|
||||
fetch-depth: 10
|
||||
- name: Install dependencies
|
||||
|
||||
@@ -28,13 +28,8 @@ jobs:
|
||||
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
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
||||
with:
|
||||
fetch-depth: 10
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Fetch custom Github Actions and base branch history
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
||||
with:
|
||||
fetch-depth: 10
|
||||
- name: Install dependencies
|
||||
|
||||
@@ -54,7 +54,7 @@ jobs:
|
||||
TWENTY_API_KEY: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIyMDIwMjAyMC1lNmI1LTQ2ODAtOGEzMi1iODIwOTczNzE1NmIiLCJ1c2VySWQiOiIyMDIwMjAyMC1lNmI1LTQ2ODAtOGEzMi1iODIwOTczNzE1NmIiLCJ3b3Jrc3BhY2VJZCI6IjIwMjAyMDIwLTFjMjUtNGQwMi1iZjI1LTZhZWNjZjdlYTQxOSIsIndvcmtzcGFjZU1lbWJlcklkIjoiMjAyMDIwMjAtNDYzZi00MzViLTgyOGMtMTA3ZTAwN2EyNzExIiwidXNlcldvcmtzcGFjZUlkIjoiMjAyMDIwMjAtMWU3Yy00M2Q5LWE1ZGItNjg1YjUwNjlkODE2IiwidHlwZSI6IkFDQ0VTUyIsImF1dGhQcm92aWRlciI6InBhc3N3b3JkIiwiaWF0IjoxNzUxMjgxNzA0LCJleHAiOjIwNjY4NTc3MDR9.HMGqCsVlOAPVUBhKSGlD1X86VoHKt4LIUtET3CGIdik
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
||||
|
||||
- name: Install dependencies
|
||||
uses: ./.github/actions/yarn-install
|
||||
|
||||
@@ -54,7 +54,7 @@ jobs:
|
||||
TWENTY_API_KEY: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIyMDIwMjAyMC1lNmI1LTQ2ODAtOGEzMi1iODIwOTczNzE1NmIiLCJ1c2VySWQiOiIyMDIwMjAyMC1lNmI1LTQ2ODAtOGEzMi1iODIwOTczNzE1NmIiLCJ3b3Jrc3BhY2VJZCI6IjIwMjAyMDIwLTFjMjUtNGQwMi1iZjI1LTZhZWNjZjdlYTQxOSIsIndvcmtzcGFjZU1lbWJlcklkIjoiMjAyMDIwMjAtNDYzZi00MzViLTgyOGMtMTA3ZTAwN2EyNzExIiwidXNlcldvcmtzcGFjZUlkIjoiMjAyMDIwMjAtMWU3Yy00M2Q5LWE1ZGItNjg1YjUwNjlkODE2IiwidHlwZSI6IkFDQ0VTUyIsImF1dGhQcm92aWRlciI6InBhc3N3b3JkIiwiaWF0IjoxNzUxMjgxNzA0LCJleHAiOjIwNjY4NTc3MDR9.HMGqCsVlOAPVUBhKSGlD1X86VoHKt4LIUtET3CGIdik
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
||||
|
||||
- name: Install dependencies
|
||||
uses: ./.github/actions/yarn-install
|
||||
|
||||
@@ -32,12 +32,8 @@ jobs:
|
||||
matrix:
|
||||
task: [build, typecheck, lint]
|
||||
steps:
|
||||
- name: Cancel Previous Runs
|
||||
uses: styfle/cancel-workflow-action@0.11.0
|
||||
with:
|
||||
access_token: ${{ github.token }}
|
||||
- name: Fetch custom Github Actions and base branch history
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
||||
with:
|
||||
fetch-depth: 10
|
||||
- name: Install dependencies
|
||||
@@ -50,12 +46,8 @@ jobs:
|
||||
timeout-minutes: 30
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Cancel Previous Runs
|
||||
uses: styfle/cancel-workflow-action@0.11.0
|
||||
with:
|
||||
access_token: ${{ github.token }}
|
||||
- name: Fetch custom Github Actions and base branch history
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
||||
with:
|
||||
fetch-depth: 10
|
||||
- name: Install dependencies
|
||||
@@ -63,7 +55,7 @@ jobs:
|
||||
- name: Build storybook
|
||||
run: npx nx storybook:build twenty-front-component-renderer
|
||||
- name: Upload storybook build
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: storybook-twenty-front-component-renderer
|
||||
path: packages/twenty-front-component-renderer/storybook-static
|
||||
@@ -76,7 +68,7 @@ jobs:
|
||||
STORYBOOK_URL: http://localhost:6008
|
||||
steps:
|
||||
- name: Fetch custom Github Actions and base branch history
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
||||
with:
|
||||
fetch-depth: 10
|
||||
- name: Install dependencies
|
||||
@@ -84,7 +76,7 @@ jobs:
|
||||
- name: Build dependencies
|
||||
run: npx nx build twenty-sdk
|
||||
- name: Download storybook build
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
with:
|
||||
name: storybook-twenty-front-component-renderer
|
||||
path: packages/twenty-front-component-renderer/storybook-static
|
||||
|
||||
@@ -38,12 +38,8 @@ jobs:
|
||||
env:
|
||||
REACT_APP_SERVER_BASE_URL: http://localhost:3000
|
||||
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
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
||||
with:
|
||||
fetch-depth: 10
|
||||
- name: Install dependencies
|
||||
@@ -55,7 +51,7 @@ jobs:
|
||||
- name: Front / Build storybook
|
||||
run: npx nx storybook:build twenty-front
|
||||
- name: Upload storybook build
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: storybook-static
|
||||
path: packages/twenty-front/storybook-static
|
||||
@@ -79,7 +75,7 @@ jobs:
|
||||
STORYBOOK_URL: http://localhost:6006
|
||||
steps:
|
||||
- name: Fetch local actions
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
||||
with:
|
||||
fetch-depth: 10
|
||||
- name: Install dependencies
|
||||
@@ -96,7 +92,7 @@ jobs:
|
||||
npx nx build twenty-ui
|
||||
npx nx build twenty-front-component-renderer
|
||||
- name: Download storybook build
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
with:
|
||||
name: storybook-static
|
||||
path: packages/twenty-front/storybook-static
|
||||
@@ -121,7 +117,7 @@ jobs:
|
||||
# exit 1
|
||||
# fi
|
||||
# - name: Upload coverage artifact
|
||||
# uses: actions/upload-artifact@v4
|
||||
# uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
# with:
|
||||
# retention-days: 1
|
||||
# name: coverage-artifacts-${{ matrix.storybook_scope }}-${{ github.run_id }}-${{ matrix.shard }}
|
||||
@@ -136,12 +132,12 @@ jobs:
|
||||
# matrix:
|
||||
# storybook_scope: [modules, pages, performance]
|
||||
# steps:
|
||||
# - uses: actions/checkout@v4
|
||||
# - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
||||
# with:
|
||||
# fetch-depth: 10
|
||||
# - name: Install dependencies
|
||||
# uses: ./.github/actions/yarn-install
|
||||
# - uses: actions/download-artifact@v4
|
||||
# - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
# with:
|
||||
# pattern: coverage-artifacts-${{ matrix.storybook_scope }}-${{ github.run_id }}-*
|
||||
# merge-multiple: true
|
||||
@@ -164,12 +160,8 @@ jobs:
|
||||
matrix:
|
||||
task: [lint, typecheck, test]
|
||||
steps:
|
||||
- name: Cancel Previous Runs
|
||||
uses: styfle/cancel-workflow-action@0.11.0
|
||||
with:
|
||||
access_token: ${{ github.token }}
|
||||
- name: Fetch custom Github Actions and base branch history
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
||||
with:
|
||||
fetch-depth: 10
|
||||
- name: Install dependencies
|
||||
@@ -203,12 +195,8 @@ jobs:
|
||||
NODE_OPTIONS: "--max-old-space-size=10240"
|
||||
ANALYZE: "true"
|
||||
steps:
|
||||
- name: Cancel Previous Runs
|
||||
uses: styfle/cancel-workflow-action@0.11.0
|
||||
with:
|
||||
access_token: ${{ github.token }}
|
||||
- name: Fetch custom Github Actions and base branch history
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
||||
with:
|
||||
fetch-depth: 10
|
||||
- name: Install dependencies
|
||||
@@ -218,7 +206,7 @@ jobs:
|
||||
- name: Build frontend
|
||||
run: npx nx build twenty-front
|
||||
# - name: Upload frontend build artifact
|
||||
# uses: actions/upload-artifact@v4
|
||||
# uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
# with:
|
||||
# name: frontend-build
|
||||
# path: packages/twenty-front/build
|
||||
|
||||
@@ -41,11 +41,11 @@ jobs:
|
||||
ports:
|
||||
- 6379:6379
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
||||
with:
|
||||
fetch-depth: 10
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version: lts/*
|
||||
|
||||
@@ -53,7 +53,7 @@ jobs:
|
||||
uses: ./.github/actions/yarn-install
|
||||
|
||||
- name: Restore Nx build cache
|
||||
uses: actions/cache/restore@v4
|
||||
uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 (restore)
|
||||
with:
|
||||
key: v4-e2e-build-${{ github.ref_name }}-${{ github.sha }}
|
||||
restore-keys: |
|
||||
@@ -83,7 +83,7 @@ jobs:
|
||||
|
||||
- name: Save Nx build cache
|
||||
if: always()
|
||||
uses: actions/cache/save@v4
|
||||
uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 (save)
|
||||
with:
|
||||
key: v4-e2e-build-${{ github.ref_name }}-${{ github.sha }}
|
||||
path: |
|
||||
@@ -119,7 +119,7 @@ jobs:
|
||||
|
||||
- name: Upload Playwright results
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: playwright-results
|
||||
path: |
|
||||
|
||||
@@ -28,7 +28,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
||||
with:
|
||||
ref: ${{ github.event.inputs.ref }}
|
||||
|
||||
@@ -47,7 +47,7 @@ jobs:
|
||||
printf '%s\n' "$VERSION" > version.txt
|
||||
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
uses: peter-evans/create-pull-request@c5a7806660adbe173f04e3e038b0ccdcd758773c # v6.1.0
|
||||
with:
|
||||
branch: release/${{ steps.sanitize.outputs.version }}
|
||||
commit-message: "chore: release v${{ steps.sanitize.outputs.version }}"
|
||||
|
||||
@@ -30,7 +30,7 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
||||
with:
|
||||
ref: main
|
||||
|
||||
@@ -55,7 +55,7 @@ jobs:
|
||||
git tag "v${{ env.VERSION }}"
|
||||
git push origin "v${{ env.VERSION }}"
|
||||
|
||||
- uses: release-drafter/release-drafter@v5
|
||||
- uses: release-drafter/release-drafter@09c613e259eb8d4e7c81c2cb00618eb5fc4575a7 # v5
|
||||
if: contains(github.event.pull_request.labels.*.name, 'create_release')
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@@ -30,12 +30,8 @@ jobs:
|
||||
matrix:
|
||||
task: [lint, typecheck, test:unit, test:integration]
|
||||
steps:
|
||||
- name: Cancel Previous Runs
|
||||
uses: styfle/cancel-workflow-action@0.11.0
|
||||
with:
|
||||
access_token: ${{ github.token }}
|
||||
- name: Fetch custom Github Actions and base branch history
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
||||
with:
|
||||
fetch-depth: 10
|
||||
- name: Install dependencies
|
||||
@@ -74,7 +70,7 @@ jobs:
|
||||
TWENTY_API_KEY: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIyMDIwMjAyMC1lNmI1LTQ2ODAtOGEzMi1iODIwOTczNzE1NmIiLCJ1c2VySWQiOiIyMDIwMjAyMC1lNmI1LTQ2ODAtOGEzMi1iODIwOTczNzE1NmIiLCJ3b3Jrc3BhY2VJZCI6IjIwMjAyMDIwLTFjMjUtNGQwMi1iZjI1LTZhZWNjZjdlYTQxOSIsIndvcmtzcGFjZU1lbWJlcklkIjoiMjAyMDIwMjAtNDYzZi00MzViLTgyOGMtMTA3ZTAwN2EyNzExIiwidXNlcldvcmtzcGFjZUlkIjoiMjAyMDIwMjAtMWU3Yy00M2Q5LWE1ZGItNjg1YjUwNjlkODE2IiwidHlwZSI6IkFDQ0VTUyIsImF1dGhQcm92aWRlciI6InBhc3N3b3JkIiwiaWF0IjoxNzUxMjgxNzA0LCJleHAiOjIwNjY4NTc3MDR9.HMGqCsVlOAPVUBhKSGlD1X86VoHKt4LIUtET3CGIdik
|
||||
steps:
|
||||
- name: Fetch custom Github Actions and base branch history
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
||||
with:
|
||||
fetch-depth: 10
|
||||
- name: Install dependencies
|
||||
|
||||
@@ -37,7 +37,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Fetch custom Github Actions and base branch history
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
||||
with:
|
||||
fetch-depth: 10
|
||||
- name: Install dependencies
|
||||
@@ -65,7 +65,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Fetch custom Github Actions and base branch history
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
||||
with:
|
||||
fetch-depth: 10
|
||||
- name: Install dependencies
|
||||
@@ -83,12 +83,12 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Fetch custom Github Actions and base branch history
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
||||
with:
|
||||
fetch-depth: 10
|
||||
- name: Get changed upgrade-version-command files
|
||||
id: changed-files
|
||||
uses: tj-actions/changed-files@v45
|
||||
uses: tj-actions/changed-files@48d8f15b2aaa3d255ca5af3eba4870f807ce6b3c # v45.0.9
|
||||
with:
|
||||
files: |
|
||||
packages/twenty-server/src/database/commands/upgrade-version-command/**
|
||||
@@ -178,7 +178,7 @@ jobs:
|
||||
- 6379:6379
|
||||
steps:
|
||||
- name: Fetch custom Github Actions and base branch history
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
||||
with:
|
||||
fetch-depth: 10
|
||||
- name: Install dependencies
|
||||
@@ -278,7 +278,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Fetch custom Github Actions and base branch history
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
||||
with:
|
||||
fetch-depth: 10
|
||||
- name: Install dependencies
|
||||
@@ -341,7 +341,7 @@ jobs:
|
||||
SHARD_COUNTER: 10
|
||||
steps:
|
||||
- name: Fetch custom Github Actions and base branch history
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
||||
with:
|
||||
fetch-depth: 10
|
||||
- name: Install dependencies
|
||||
|
||||
@@ -29,12 +29,8 @@ jobs:
|
||||
matrix:
|
||||
task: [lint, typecheck, test]
|
||||
steps:
|
||||
- name: Cancel Previous Runs
|
||||
uses: styfle/cancel-workflow-action@0.11.0
|
||||
with:
|
||||
access_token: ${{ github.token }}
|
||||
- name: Fetch custom Github Actions and base branch history
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
||||
with:
|
||||
fetch-depth: 10
|
||||
- name: Install dependencies
|
||||
|
||||
@@ -26,9 +26,9 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
|
||||
with:
|
||||
username: ${{ vars.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||
@@ -101,9 +101,9 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
|
||||
with:
|
||||
username: ${{ vars.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||
|
||||
@@ -30,12 +30,8 @@ jobs:
|
||||
matrix:
|
||||
task: [lint, typecheck, test]
|
||||
steps:
|
||||
- name: Cancel Previous Runs
|
||||
uses: styfle/cancel-workflow-action@0.11.0
|
||||
with:
|
||||
access_token: ${{ github.token }}
|
||||
- name: Fetch custom Github Actions and base branch history
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
||||
with:
|
||||
fetch-depth: 10
|
||||
- name: Install dependencies
|
||||
@@ -48,12 +44,8 @@ jobs:
|
||||
timeout-minutes: 30
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Cancel Previous Runs
|
||||
uses: styfle/cancel-workflow-action@0.11.0
|
||||
with:
|
||||
access_token: ${{ github.token }}
|
||||
- name: Fetch custom Github Actions and base branch history
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
||||
with:
|
||||
fetch-depth: 10
|
||||
- name: Install dependencies
|
||||
@@ -61,7 +53,7 @@ jobs:
|
||||
- name: Build storybook
|
||||
run: npx nx storybook:build twenty-ui
|
||||
- name: Upload storybook build
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: storybook-twenty-ui
|
||||
path: packages/twenty-ui/storybook-static
|
||||
@@ -74,7 +66,7 @@ jobs:
|
||||
STORYBOOK_URL: http://localhost:6007
|
||||
steps:
|
||||
- name: Fetch custom Github Actions and base branch history
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
||||
with:
|
||||
fetch-depth: 10
|
||||
- name: Install dependencies
|
||||
@@ -82,7 +74,7 @@ jobs:
|
||||
- name: Build dependencies
|
||||
run: npx nx build twenty-shared
|
||||
- name: Download storybook build
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
with:
|
||||
name: storybook-twenty-ui
|
||||
path: packages/twenty-ui/storybook-static
|
||||
|
||||
@@ -25,7 +25,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event.action != 'closed'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
||||
- name: Install dependencies
|
||||
uses: ./.github/actions/yarn-install
|
||||
- name: Utils / Run Danger.js
|
||||
@@ -38,7 +38,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event.action == 'closed' && github.event.pull_request.merged == true
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
||||
- name: Install dependencies
|
||||
uses: ./.github/actions/yarn-install
|
||||
- name: Run congratulate-dangerfile.js
|
||||
|
||||
@@ -32,12 +32,8 @@ jobs:
|
||||
matrix:
|
||||
task: [lint, typecheck, test]
|
||||
steps:
|
||||
- name: Cancel Previous Runs
|
||||
uses: styfle/cancel-workflow-action@0.11.0
|
||||
with:
|
||||
access_token: ${{ github.token }}
|
||||
- name: Fetch custom Github Actions and base branch history
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
||||
with:
|
||||
fetch-depth: 10
|
||||
- name: Install dependencies
|
||||
|
||||
@@ -46,7 +46,7 @@ jobs:
|
||||
- 6379:6379
|
||||
steps:
|
||||
- name: Fetch custom Github Actions and base branch history
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
||||
with:
|
||||
fetch-depth: 10
|
||||
- name: Install dependencies
|
||||
@@ -97,12 +97,8 @@ jobs:
|
||||
matrix:
|
||||
task: [lint, typecheck, validate]
|
||||
steps:
|
||||
- name: Cancel Previous Runs
|
||||
uses: styfle/cancel-workflow-action@0.11.0
|
||||
with:
|
||||
access_token: ${{ github.token }}
|
||||
- name: Fetch custom Github Actions and base branch history
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
||||
with:
|
||||
fetch-depth: 10
|
||||
- name: Install dependencies
|
||||
|
||||
@@ -8,10 +8,12 @@ on:
|
||||
pull_request_review:
|
||||
types: [submitted]
|
||||
issues:
|
||||
types: [opened, assigned]
|
||||
types: [opened]
|
||||
repository_dispatch:
|
||||
types: [claude-core-team-issues]
|
||||
|
||||
permissions: {}
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.issue.number || github.event.pull_request.number || github.event.client_payload.issue_number }}
|
||||
cancel-in-progress: false
|
||||
@@ -19,17 +21,36 @@ concurrency:
|
||||
jobs:
|
||||
claude:
|
||||
if: |
|
||||
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude') && github.event.comment.user.type != 'Bot') ||
|
||||
(github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude') && github.event.comment.user.type != 'Bot') ||
|
||||
(github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude') && github.event.review.user.type != 'Bot') ||
|
||||
(github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')))
|
||||
(
|
||||
github.event_name == 'issue_comment' &&
|
||||
contains(github.event.comment.body, '@claude') &&
|
||||
github.event.comment.user.type != 'Bot' &&
|
||||
contains(fromJson('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association)
|
||||
) ||
|
||||
(
|
||||
github.event_name == 'pull_request_review_comment' &&
|
||||
contains(github.event.comment.body, '@claude') &&
|
||||
github.event.comment.user.type != 'Bot' &&
|
||||
contains(fromJson('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association)
|
||||
) ||
|
||||
(
|
||||
github.event_name == 'pull_request_review' &&
|
||||
contains(github.event.review.body, '@claude') &&
|
||||
github.event.review.user.type != 'Bot' &&
|
||||
contains(fromJson('["OWNER","MEMBER","COLLABORATOR"]'), github.event.review.author_association)
|
||||
) ||
|
||||
(
|
||||
github.event_name == 'issues' &&
|
||||
github.event.action == 'opened' &&
|
||||
(contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')) &&
|
||||
contains(fromJson('["OWNER","MEMBER","COLLABORATOR"]'), github.event.issue.author_association)
|
||||
)
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
issues: write
|
||||
id-token: write
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:16
|
||||
@@ -50,14 +71,14 @@ jobs:
|
||||
- 6379:6379
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Install dependencies
|
||||
uses: ./.github/actions/yarn-install
|
||||
- name: Run Claude Code
|
||||
id: claude-code
|
||||
uses: anthropics/claude-code-action@v1
|
||||
uses: anthropics/claude-code-action@dde2242db6af13460b916652159b6ba19a598f30 # v1
|
||||
with:
|
||||
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
||||
additional_permissions: |
|
||||
@@ -102,7 +123,6 @@ jobs:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
issues: write
|
||||
id-token: write
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:16
|
||||
@@ -122,14 +142,14 @@ jobs:
|
||||
ports:
|
||||
- 6379:6379
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Install dependencies
|
||||
uses: ./.github/actions/yarn-install
|
||||
- name: Build prompt from dispatch payload
|
||||
id: prompt
|
||||
uses: actions/github-script@v7
|
||||
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0
|
||||
with:
|
||||
script: |
|
||||
const p = context.payload.client_payload;
|
||||
@@ -144,7 +164,7 @@ jobs:
|
||||
core.setOutput('issue_number', p.issue_number);
|
||||
- name: Run Claude Code
|
||||
id: claude
|
||||
uses: anthropics/claude-code-action@v1
|
||||
uses: anthropics/claude-code-action@dde2242db6af13460b916652159b6ba19a598f30 # v1
|
||||
with:
|
||||
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
||||
prompt: ${{ steps.prompt.outputs.prompt }}
|
||||
@@ -159,9 +179,16 @@ jobs:
|
||||
}
|
||||
- name: Dispatch response to ci-privileged
|
||||
if: always()
|
||||
uses: peter-evans/repository-dispatch@v2
|
||||
with:
|
||||
token: ${{ secrets.CI_PRIVILEGED_DISPATCH_TOKEN }}
|
||||
repository: twentyhq/ci-privileged
|
||||
event-type: claude-cross-repo-response
|
||||
client-payload: '{"repo": ${{ toJSON(steps.prompt.outputs.repo) }}, "issue_number": ${{ toJSON(steps.prompt.outputs.issue_number) }}, "run_id": ${{ toJSON(github.run_id) }}, "run_url": "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"}'
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.CI_PRIVILEGED_DISPATCH_TOKEN }}
|
||||
REPO: ${{ steps.prompt.outputs.repo }}
|
||||
ISSUE_NUMBER: ${{ steps.prompt.outputs.issue_number }}
|
||||
RUN_ID: ${{ github.run_id }}
|
||||
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
||||
run: |
|
||||
gh api repos/twentyhq/ci-privileged/dispatches \
|
||||
-f event_type=claude-cross-repo-response \
|
||||
-f "client_payload[repo]=$REPO" \
|
||||
-f "client_payload[issue_number]=$ISSUE_NUMBER" \
|
||||
-f "client_payload[run_id]=$RUN_ID" \
|
||||
-f "client_payload[run_url]=$RUN_URL"
|
||||
|
||||
@@ -37,7 +37,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
||||
with:
|
||||
token: ${{ github.token }}
|
||||
repository: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name || github.repository }}
|
||||
@@ -153,8 +153,7 @@ jobs:
|
||||
|
||||
- name: Trigger i18n automerge
|
||||
if: github.event_name != 'pull_request' && steps.check_changes.outputs.changes_detected == 'true'
|
||||
uses: peter-evans/repository-dispatch@v2
|
||||
with:
|
||||
token: ${{ secrets.TWENTY_INFRA_TOKEN }}
|
||||
repository: twentyhq/twenty-infra
|
||||
event-type: i18n-pr-ready
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.TWENTY_INFRA_TOKEN }}
|
||||
run: |
|
||||
gh api repos/twentyhq/twenty-infra/dispatches -f event_type=i18n-pr-ready
|
||||
|
||||
@@ -24,7 +24,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
||||
with:
|
||||
token: ${{ github.token }}
|
||||
ref: ${{ github.ref }}
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
run: yarn docs:generate-navigation-template
|
||||
|
||||
- name: Upload docs to Crowdin
|
||||
uses: crowdin/github-action@v2
|
||||
uses: crowdin/github-action@8868a33591d21088edfc398968173a3b98d51706 # v2
|
||||
with:
|
||||
upload_sources: true
|
||||
upload_translations: false
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
name: Auto-Draft External PRs
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [opened]
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
dispatch:
|
||||
if: |
|
||||
github.event.pull_request.draft == false &&
|
||||
github.event.pull_request.author_association != 'MEMBER' &&
|
||||
github.event.pull_request.author_association != 'OWNER' &&
|
||||
github.event.pull_request.author_association != 'COLLABORATOR'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- name: Dispatch to ci-privileged
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.CI_PRIVILEGED_DISPATCH_TOKEN }}
|
||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||
PR_NODE_ID: ${{ github.event.pull_request.node_id }}
|
||||
run: |
|
||||
gh api repos/twentyhq/ci-privileged/dispatches \
|
||||
-f event_type=convert-pr-to-draft \
|
||||
-f "client_payload[pr_number]=$PR_NUMBER" \
|
||||
-f "client_payload[pr_node_id]=$PR_NODE_ID"
|
||||
@@ -35,7 +35,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
||||
with:
|
||||
token: ${{ github.token }}
|
||||
ref: ${{ github.head_ref || github.ref_name }}
|
||||
@@ -69,7 +69,7 @@ jobs:
|
||||
|
||||
- name: Pull translations from Crowdin
|
||||
if: inputs.force_pull || steps.compile_translations_strict.outcome == 'failure'
|
||||
uses: crowdin/github-action@v2
|
||||
uses: crowdin/github-action@8868a33591d21088edfc398968173a3b98d51706 # v2
|
||||
with:
|
||||
upload_sources: false
|
||||
upload_translations: false
|
||||
@@ -139,8 +139,7 @@ jobs:
|
||||
|
||||
- name: Trigger i18n automerge
|
||||
if: steps.compile_translations.outputs.changes_detected == 'true'
|
||||
uses: peter-evans/repository-dispatch@v2
|
||||
with:
|
||||
token: ${{ secrets.TWENTY_INFRA_TOKEN }}
|
||||
repository: twentyhq/twenty-infra
|
||||
event-type: i18n-pr-ready
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.TWENTY_INFRA_TOKEN }}
|
||||
run: |
|
||||
gh api repos/twentyhq/twenty-infra/dispatches -f event_type=i18n-pr-ready
|
||||
|
||||
@@ -20,7 +20,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
||||
with:
|
||||
token: ${{ github.token }}
|
||||
ref: main
|
||||
@@ -80,7 +80,7 @@ jobs:
|
||||
|
||||
- name: Upload missing translations
|
||||
if: steps.check_extract_changes.outputs.changes_detected == 'true'
|
||||
uses: crowdin/github-action@v2
|
||||
uses: crowdin/github-action@8868a33591d21088edfc398968173a3b98d51706 # v2
|
||||
with:
|
||||
upload_sources: true
|
||||
upload_translations: true
|
||||
@@ -105,8 +105,7 @@ jobs:
|
||||
|
||||
- name: Trigger i18n automerge
|
||||
if: steps.check_extract_changes.outputs.changes_detected == 'true' || steps.check_compile_changes.outputs.changes_detected == 'true'
|
||||
uses: peter-evans/repository-dispatch@v2
|
||||
with:
|
||||
token: ${{ secrets.TWENTY_INFRA_TOKEN }}
|
||||
repository: twentyhq/twenty-infra
|
||||
event-type: i18n-pr-ready
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.TWENTY_INFRA_TOKEN }}
|
||||
run: |
|
||||
gh api repos/twentyhq/twenty-infra/dispatches -f event_type=i18n-pr-ready
|
||||
|
||||
@@ -16,7 +16,7 @@ jobs:
|
||||
steps:
|
||||
- name: Get PR number from workflow run
|
||||
id: pr-info
|
||||
uses: actions/github-script@v7
|
||||
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0
|
||||
with:
|
||||
script: |
|
||||
const runId = context.payload.workflow_run.id;
|
||||
@@ -63,9 +63,16 @@ jobs:
|
||||
|
||||
- name: Dispatch to ci-privileged
|
||||
if: steps.pr-info.outputs.has_pr == 'true'
|
||||
uses: peter-evans/repository-dispatch@v2
|
||||
with:
|
||||
token: ${{ secrets.CI_PRIVILEGED_DISPATCH_TOKEN }}
|
||||
repository: twentyhq/ci-privileged
|
||||
event-type: breaking-changes-report
|
||||
client-payload: '{"pr_number": ${{ toJSON(steps.pr-info.outputs.pr_number) }}, "run_id": ${{ toJSON(steps.pr-info.outputs.run_id) }}, "repo": ${{ toJSON(github.repository) }}, "branch_state": ${{ toJSON(github.event.workflow_run.head_branch) }}}'
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.CI_PRIVILEGED_DISPATCH_TOKEN }}
|
||||
PR_NUMBER: ${{ steps.pr-info.outputs.pr_number }}
|
||||
RUN_ID: ${{ steps.pr-info.outputs.run_id }}
|
||||
REPOSITORY: ${{ github.repository }}
|
||||
BRANCH_STATE: ${{ github.event.workflow_run.head_branch }}
|
||||
run: |
|
||||
gh api repos/twentyhq/ci-privileged/dispatches \
|
||||
-f event_type=breaking-changes-report \
|
||||
-f "client_payload[pr_number]=$PR_NUMBER" \
|
||||
-f "client_payload[run_id]=$RUN_ID" \
|
||||
-f "client_payload[repo]=$REPOSITORY" \
|
||||
-f "client_payload[branch_state]=$BRANCH_STATE"
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
name: PR Review Dispatch
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [ready_for_review, synchronize]
|
||||
|
||||
permissions: {}
|
||||
|
||||
concurrency:
|
||||
group: pr-review-${{ github.event.pull_request.number }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
dispatch:
|
||||
if: github.event.pull_request.draft == false
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- name: Dispatch to ci-privileged
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.CI_PRIVILEGED_DISPATCH_TOKEN }}
|
||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||
run: |
|
||||
gh api repos/twentyhq/ci-privileged/dispatches \
|
||||
-f event_type=pr-review \
|
||||
-f "client_payload[pr_number]=$PR_NUMBER"
|
||||
@@ -36,17 +36,27 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Trigger preview environment workflow
|
||||
uses: peter-evans/repository-dispatch@v2
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
repository: ${{ github.repository }}
|
||||
event-type: preview-environment
|
||||
client-payload: '{"pr_number": "${{ github.event.pull_request.number }}", "pr_head_sha": "${{ github.event.pull_request.head.sha }}", "repo_full_name": "${{ github.repository }}"}'
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||
PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
|
||||
REPOSITORY: ${{ github.repository }}
|
||||
run: |
|
||||
gh api repos/"$REPOSITORY"/dispatches \
|
||||
-f event_type=preview-environment \
|
||||
-f "client_payload[pr_number]=$PR_NUMBER" \
|
||||
-f "client_payload[pr_head_sha]=$PR_HEAD_SHA" \
|
||||
-f "client_payload[repo_full_name]=$REPOSITORY"
|
||||
|
||||
- name: Dispatch to ci-privileged for PR comment
|
||||
uses: peter-evans/repository-dispatch@v2
|
||||
with:
|
||||
token: ${{ secrets.CI_PRIVILEGED_DISPATCH_TOKEN }}
|
||||
repository: twentyhq/ci-privileged
|
||||
event-type: preview-env-url
|
||||
client-payload: '{"pr_number": ${{ toJSON(github.event.pull_request.number) }}, "keepalive_dispatch_time": ${{ toJSON(github.event.pull_request.updated_at) }}, "repo": ${{ toJSON(github.repository) }}}'
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.CI_PRIVILEGED_DISPATCH_TOKEN }}
|
||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||
KEEPALIVE_DISPATCH_TIME: ${{ github.event.pull_request.updated_at }}
|
||||
REPOSITORY: ${{ github.repository }}
|
||||
run: |
|
||||
gh api repos/twentyhq/ci-privileged/dispatches \
|
||||
-f event_type=preview-env-url \
|
||||
-f "client_payload[pr_number]=$PR_NUMBER" \
|
||||
-f "client_payload[keepalive_dispatch_time]=$KEEPALIVE_DISPATCH_TIME" \
|
||||
-f "client_payload[repo]=$REPOSITORY"
|
||||
|
||||
@@ -13,12 +13,12 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout PR
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
||||
with:
|
||||
ref: ${{ github.event.client_payload.pr_head_sha }}
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
|
||||
with:
|
||||
username: ${{ vars.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||
@@ -56,10 +56,54 @@ jobs:
|
||||
|
||||
- name: Create Tunnel
|
||||
id: expose-tunnel
|
||||
uses: codetalkio/expose-tunnel@v1.5.0
|
||||
with:
|
||||
service: bore.pub
|
||||
port: 3000
|
||||
env:
|
||||
CLOUDFLARED_VERSION: '2026.3.0'
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
# Install cloudflared (pinned for reproducibility)
|
||||
sudo curl -fsSL -o /usr/local/bin/cloudflared \
|
||||
"https://github.com/cloudflare/cloudflared/releases/download/${CLOUDFLARED_VERSION}/cloudflared-linux-amd64"
|
||||
sudo chmod +x /usr/local/bin/cloudflared
|
||||
cloudflared --version
|
||||
|
||||
# Start an account-less "quick tunnel" pointing at the server container.
|
||||
# Cloudflare prints the assigned https://*.trycloudflare.com URL into the log.
|
||||
log_file="$RUNNER_TEMP/cloudflared.log"
|
||||
: > "$log_file"
|
||||
|
||||
cloudflared tunnel \
|
||||
--url http://localhost:3000 \
|
||||
--no-autoupdate \
|
||||
--logfile "$log_file" \
|
||||
--loglevel info \
|
||||
> "$RUNNER_TEMP/cloudflared.stdout" 2>&1 &
|
||||
|
||||
pid=$!
|
||||
echo "$pid" > "$RUNNER_TEMP/cloudflared.pid"
|
||||
echo "cloudflared PID: $pid"
|
||||
|
||||
# Wait up to 2 minutes for the URL to appear; fail fast if cloudflared exits.
|
||||
url=''
|
||||
for _ in $(seq 1 60); do
|
||||
url=$(grep -oE 'https://[a-zA-Z0-9-]+\.trycloudflare\.com' "$log_file" 2>/dev/null | head -n1 || true)
|
||||
[ -n "$url" ] && break
|
||||
if ! kill -0 "$pid" 2>/dev/null; then
|
||||
echo "cloudflared exited before producing a URL"
|
||||
cat "$log_file" || true
|
||||
exit 1
|
||||
fi
|
||||
sleep 2
|
||||
done
|
||||
|
||||
if [ -z "$url" ]; then
|
||||
echo "Timed out waiting for tunnel URL"
|
||||
cat "$log_file" || true
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Tunnel URL: $url"
|
||||
echo "tunnel-url=$url" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Start services with correct SERVER_URL
|
||||
env:
|
||||
@@ -99,9 +143,9 @@ jobs:
|
||||
- name: Seed Dev Workspace
|
||||
run: |
|
||||
cd packages/twenty-docker/
|
||||
echo "Seeding full dev workspace..."
|
||||
if ! docker compose exec -T server yarn command:prod -- workspace:seed:dev; then
|
||||
echo "❌ Seeding full dev workspace failed. Dumping server logs..."
|
||||
echo "Seeding light dev workspace (Apple only)..."
|
||||
if ! docker compose exec -T server yarn command:prod workspace:seed:dev --light; then
|
||||
echo "❌ Seeding light dev workspace failed. Dumping server logs..."
|
||||
docker compose logs server
|
||||
exit 1
|
||||
fi
|
||||
@@ -122,7 +166,7 @@ jobs:
|
||||
echo "$TUNNEL_URL" > tunnel-url.txt
|
||||
|
||||
- name: Upload tunnel URL artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: tunnel-url
|
||||
path: tunnel-url.txt
|
||||
@@ -134,6 +178,9 @@ jobs:
|
||||
- name: Cleanup
|
||||
if: always()
|
||||
run: |
|
||||
if [ -f "$RUNNER_TEMP/cloudflared.pid" ]; then
|
||||
kill "$(cat "$RUNNER_TEMP/cloudflared.pid")" 2>/dev/null || true
|
||||
fi
|
||||
cd packages/twenty-docker/
|
||||
docker compose down -v
|
||||
working-directory: ./
|
||||
|
||||
@@ -23,7 +23,7 @@ jobs:
|
||||
steps:
|
||||
- name: Determine project and artifact name
|
||||
id: project
|
||||
uses: actions/github-script@v7
|
||||
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0
|
||||
with:
|
||||
script: |
|
||||
const workflowName = context.payload.workflow_run.name;
|
||||
@@ -43,7 +43,7 @@ jobs:
|
||||
|
||||
- name: Check if storybook artifact exists
|
||||
id: check-artifact
|
||||
uses: actions/github-script@v7
|
||||
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0
|
||||
with:
|
||||
script: |
|
||||
const artifactName = '${{ steps.project.outputs.artifact_name }}';
|
||||
@@ -65,7 +65,7 @@ jobs:
|
||||
- name: Get PR number
|
||||
if: steps.check-artifact.outputs.exists == 'true'
|
||||
id: pr-info
|
||||
uses: actions/github-script@v7
|
||||
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0
|
||||
with:
|
||||
script: |
|
||||
const headBranch = context.payload.workflow_run.head_branch;
|
||||
@@ -107,7 +107,7 @@ jobs:
|
||||
|
||||
- name: Download storybook artifact from triggering run
|
||||
if: steps.check-artifact.outputs.exists == 'true' && steps.pr-info.outputs.has_pr == 'true'
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
with:
|
||||
name: ${{ steps.project.outputs.artifact_name }}
|
||||
path: storybook-static
|
||||
@@ -120,7 +120,7 @@ jobs:
|
||||
|
||||
- name: Upload storybook tarball
|
||||
if: steps.check-artifact.outputs.exists == 'true' && steps.pr-info.outputs.has_pr == 'true'
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: ${{ steps.project.outputs.tarball_name }}
|
||||
path: /tmp/${{ steps.project.outputs.tarball_file }}
|
||||
@@ -128,17 +128,20 @@ jobs:
|
||||
|
||||
- name: Dispatch to ci-privileged
|
||||
if: steps.check-artifact.outputs.exists == 'true' && steps.pr-info.outputs.has_pr == 'true'
|
||||
uses: peter-evans/repository-dispatch@v2
|
||||
with:
|
||||
token: ${{ secrets.CI_PRIVILEGED_DISPATCH_TOKEN }}
|
||||
repository: twentyhq/ci-privileged
|
||||
event-type: visual-regression
|
||||
client-payload: >-
|
||||
{
|
||||
"pr_number": "${{ steps.pr-info.outputs.pr_number }}",
|
||||
"run_id": "${{ github.run_id }}",
|
||||
"repo": "${{ github.repository }}",
|
||||
"project": "${{ steps.project.outputs.project }}",
|
||||
"branch": "${{ github.event.workflow_run.head_branch }}",
|
||||
"commit": "${{ github.event.workflow_run.head_sha }}"
|
||||
}
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.CI_PRIVILEGED_DISPATCH_TOKEN }}
|
||||
PR_NUMBER: ${{ steps.pr-info.outputs.pr_number }}
|
||||
RUN_ID: ${{ github.run_id }}
|
||||
REPOSITORY: ${{ github.repository }}
|
||||
PROJECT: ${{ steps.project.outputs.project }}
|
||||
BRANCH: ${{ github.event.workflow_run.head_branch }}
|
||||
COMMIT: ${{ github.event.workflow_run.head_sha }}
|
||||
run: |
|
||||
gh api repos/twentyhq/ci-privileged/dispatches \
|
||||
-f event_type=visual-regression \
|
||||
-f "client_payload[pr_number]=$PR_NUMBER" \
|
||||
-f "client_payload[run_id]=$RUN_ID" \
|
||||
-f "client_payload[repo]=$REPOSITORY" \
|
||||
-f "client_payload[project]=$PROJECT" \
|
||||
-f "client_payload[branch]=$BRANCH" \
|
||||
-f "client_payload[commit]=$COMMIT"
|
||||
|
||||
@@ -35,7 +35,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
||||
with:
|
||||
token: ${{ github.token }}
|
||||
ref: ${{ github.head_ref || github.ref_name }}
|
||||
@@ -66,7 +66,7 @@ jobs:
|
||||
|
||||
- name: Pull website translations from Crowdin
|
||||
if: inputs.force_pull || steps.compile_translations_strict.outcome == 'failure'
|
||||
uses: crowdin/github-action@v2
|
||||
uses: crowdin/github-action@8868a33591d21088edfc398968173a3b98d51706 # v2
|
||||
with:
|
||||
upload_sources: false
|
||||
upload_translations: false
|
||||
@@ -128,8 +128,7 @@ jobs:
|
||||
|
||||
- name: Trigger i18n automerge
|
||||
if: steps.compile_translations.outputs.changes_detected == 'true'
|
||||
uses: peter-evans/repository-dispatch@v2
|
||||
with:
|
||||
token: ${{ secrets.TWENTY_INFRA_TOKEN }}
|
||||
repository: twentyhq/twenty-infra
|
||||
event-type: i18n-pr-ready
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.TWENTY_INFRA_TOKEN }}
|
||||
run: |
|
||||
gh api repos/twentyhq/twenty-infra/dispatches -f event_type=i18n-pr-ready
|
||||
|
||||
@@ -24,7 +24,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
||||
with:
|
||||
token: ${{ github.token }}
|
||||
ref: main
|
||||
@@ -78,7 +78,7 @@ jobs:
|
||||
|
||||
- name: Upload missing website translations
|
||||
if: steps.check_extract_changes.outputs.changes_detected == 'true'
|
||||
uses: crowdin/github-action@v2
|
||||
uses: crowdin/github-action@8868a33591d21088edfc398968173a3b98d51706 # v2
|
||||
with:
|
||||
upload_sources: true
|
||||
upload_translations: true
|
||||
@@ -104,8 +104,7 @@ jobs:
|
||||
|
||||
- name: Trigger i18n automerge
|
||||
if: steps.check_extract_changes.outputs.changes_detected == 'true' || steps.check_compile_changes.outputs.changes_detected == 'true'
|
||||
uses: peter-evans/repository-dispatch@v2
|
||||
with:
|
||||
token: ${{ secrets.TWENTY_INFRA_TOKEN }}
|
||||
repository: twentyhq/twenty-infra
|
||||
event-type: i18n-pr-ready
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.TWENTY_INFRA_TOKEN }}
|
||||
run: |
|
||||
gh api repos/twentyhq/twenty-infra/dispatches -f event_type=i18n-pr-ready
|
||||
|
||||
@@ -6,4 +6,6 @@ enableInlineHunks: true
|
||||
|
||||
nodeLinker: node-modules
|
||||
|
||||
npmMinimalAgeGate: "3d"
|
||||
|
||||
yarnPath: .yarn/releases/yarn-4.13.0.cjs
|
||||
|
||||
@@ -6,14 +6,14 @@
|
||||
|
||||
<h2 align="center" >The #1 Open-Source CRM</h2>
|
||||
|
||||
<p align="center"><a href="https://twenty.com"><img src="./packages/twenty-website-new/public/images/readme/globe-icon.svg" width="12" height="12"/> Website</a> · <a href="https://docs.twenty.com"><img src="./packages/twenty-website-new/public/images/readme/book-icon.svg" width="12" height="12"/> Documentation</a> · <a href="https://github.com/orgs/twentyhq/projects/1"><img src="./packages/twenty-website-new/public/images/readme/map-icon.svg" width="12" height="12"/> Roadmap </a> · <a href="https://discord.gg/cx5n4Jzs57"><img src="./packages/twenty-website-new/public/images/readme/discord-icon.svg" width="12" height="12"/> Discord</a> · <a href="https://www.figma.com/file/xt8O9mFeLl46C5InWwoMrN/Twenty"><img src="./packages/twenty-website-new/public/images/readme/figma-icon.png" width="12" height="12"/> Figma</a></p>
|
||||
<p align="center"><a href="https://twenty.com"><img src="./packages/twenty-website-new/public/images/readme/globe-icon.svg" width="12" height="12"/> Website</a> · <a href="https://docs.twenty.com"><img src="./packages/twenty-website-new/public/images/readme/book-icon.svg" width="12" height="12"/> Documentation</a> · <a href="https://github.com/orgs/twentyhq/projects/1"><img src="./packages/twenty-website-new/public/images/readme/map-icon.svg" width="12" height="12"/> Roadmap </a> · <a href="https://discord.gg/cx5n4Jzs57"><img src="./packages/twenty-website-new/public/images/readme/discord-icon.svg" width="12" height="12"/> Discord</a> · <a href="https://www.figma.com/file/xt8O9mFeLl46C5InWwoMrN/Twenty"><img src="./packages/twenty-website-new/public/images/readme/figma-icon.webp" width="12" height="12"/> Figma</a></p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://www.twenty.com">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="./packages/twenty-website-new/public/images/readme/github-cover-dark.png" />
|
||||
<source media="(prefers-color-scheme: light)" srcset="./packages/twenty-website-new/public/images/readme/github-cover-light.png" />
|
||||
<img src="./packages/twenty-website-new/public/images/readme/github-cover-light.png" alt="Twenty banner" />
|
||||
<source media="(prefers-color-scheme: dark)" srcset="./packages/twenty-website-new/public/images/readme/github-cover-dark.webp" />
|
||||
<source media="(prefers-color-scheme: light)" srcset="./packages/twenty-website-new/public/images/readme/github-cover-light.webp" />
|
||||
<img src="./packages/twenty-website-new/public/images/readme/github-cover-light.webp" alt="Twenty banner" />
|
||||
</picture>
|
||||
</a>
|
||||
</p>
|
||||
@@ -24,7 +24,7 @@
|
||||
|
||||
Twenty gives technical teams the building blocks for a custom CRM that meets complex business needs and quickly adapts as the business evolves. Twenty is the CRM you build, ship, and version like the rest of your stack.
|
||||
|
||||
<a href="https://twenty.com/why-twenty"><img src="./packages/twenty-website-new/public/images/readme/star-icon.svg" width="14" height="14"/> Learn more about why we built Twenty</a>
|
||||
<a href="https://twenty.com/resources/why-twenty"><img src="./packages/twenty-website-new/public/images/readme/star-icon.svg" width="14" height="14"/> Learn more about why we built Twenty</a>
|
||||
|
||||
<br />
|
||||
|
||||
@@ -85,17 +85,17 @@ Want to go deeper? Read the <a href="https://docs.twenty.com/user-guide/introduc
|
||||
<tr>
|
||||
<td width="50%">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="./packages/twenty-website-new/public/images/readme/v2-build-apps-dark.png" />
|
||||
<source media="(prefers-color-scheme: light)" srcset="./packages/twenty-website-new/public/images/readme/v2-build-apps-light.png" />
|
||||
<img src="./packages/twenty-website-new/public/images/readme/v2-build-apps-light.png" alt="Create your apps" />
|
||||
<source media="(prefers-color-scheme: dark)" srcset="./packages/twenty-website-new/public/images/readme/v2-build-apps-dark.webp" />
|
||||
<source media="(prefers-color-scheme: light)" srcset="./packages/twenty-website-new/public/images/readme/v2-build-apps-light.webp" />
|
||||
<img src="./packages/twenty-website-new/public/images/readme/v2-build-apps-light.webp" alt="Create your apps" />
|
||||
</picture>
|
||||
<p align="center"><a href="https://docs.twenty.com/developers/extend/apps/getting-started"><img src="./packages/twenty-website-new/public/images/readme/code-icon.svg" width="16" height="16"/> Learn more about apps in doc</a></p>
|
||||
</td>
|
||||
<td width="50%">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="./packages/twenty-website-new/public/images/readme/v2-version-control-dark.png" />
|
||||
<source media="(prefers-color-scheme: light)" srcset="./packages/twenty-website-new/public/images/readme/v2-version-control-light.png" />
|
||||
<img src="./packages/twenty-website-new/public/images/readme/v2-version-control-light.png" alt="Stay on top with version control" />
|
||||
<source media="(prefers-color-scheme: dark)" srcset="./packages/twenty-website-new/public/images/readme/v2-version-control-dark.webp" />
|
||||
<source media="(prefers-color-scheme: light)" srcset="./packages/twenty-website-new/public/images/readme/v2-version-control-light.webp" />
|
||||
<img src="./packages/twenty-website-new/public/images/readme/v2-version-control-light.webp" alt="Stay on top with version control" />
|
||||
</picture>
|
||||
<p align="center"><a href="https://docs.twenty.com/developers/extend/apps/publishing"><img src="./packages/twenty-website-new/public/images/readme/monitor-icon.svg" width="16" height="16"/> Learn more about version control in doc</a></p>
|
||||
</td>
|
||||
@@ -103,17 +103,17 @@ Want to go deeper? Read the <a href="https://docs.twenty.com/user-guide/introduc
|
||||
<tr>
|
||||
<td width="50%">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="./packages/twenty-website-new/public/images/readme/v2-all-tools-dark.png" />
|
||||
<source media="(prefers-color-scheme: light)" srcset="./packages/twenty-website-new/public/images/readme/v2-all-tools-light.png" />
|
||||
<img src="./packages/twenty-website-new/public/images/readme/v2-all-tools-light.png" alt="All the tools you need to build anything" />
|
||||
<source media="(prefers-color-scheme: dark)" srcset="./packages/twenty-website-new/public/images/readme/v2-all-tools-dark.webp" />
|
||||
<source media="(prefers-color-scheme: light)" srcset="./packages/twenty-website-new/public/images/readme/v2-all-tools-light.webp" />
|
||||
<img src="./packages/twenty-website-new/public/images/readme/v2-all-tools-light.webp" alt="All the tools you need to build anything" />
|
||||
</picture>
|
||||
<p align="center"><a href="https://docs.twenty.com/developers/extend/apps/building"><img src="./packages/twenty-website-new/public/images/readme/rocket-icon.svg" width="16" height="16"/> Learn more about primitives in doc</a></p>
|
||||
</td>
|
||||
<td width="50%">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="./packages/twenty-website-new/public/images/readme/v2-tools-dark.png" />
|
||||
<source media="(prefers-color-scheme: light)" srcset="./packages/twenty-website-new/public/images/readme/v2-tools-light.png" />
|
||||
<img src="./packages/twenty-website-new/public/images/readme/v2-tools-light.png" alt="Customize your layouts" />
|
||||
<source media="(prefers-color-scheme: dark)" srcset="./packages/twenty-website-new/public/images/readme/v2-tools-dark.webp" />
|
||||
<source media="(prefers-color-scheme: light)" srcset="./packages/twenty-website-new/public/images/readme/v2-tools-light.webp" />
|
||||
<img src="./packages/twenty-website-new/public/images/readme/v2-tools-light.webp" alt="Customize your layouts" />
|
||||
</picture>
|
||||
<p align="center"><a href="https://docs.twenty.com/user-guide/layout/overview"><img src="./packages/twenty-website-new/public/images/readme/planner-icon.svg" width="16" height="16"/> Learn more about layouts in doc</a></p>
|
||||
</td>
|
||||
@@ -121,17 +121,17 @@ Want to go deeper? Read the <a href="https://docs.twenty.com/user-guide/introduc
|
||||
<tr>
|
||||
<td width="50%">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="./packages/twenty-website-new/public/images/readme/v2-ai-agents-dark.png" />
|
||||
<source media="(prefers-color-scheme: light)" srcset="./packages/twenty-website-new/public/images/readme/v2-ai-agents-light.png" />
|
||||
<img src="./packages/twenty-website-new/public/images/readme/v2-ai-agents-light.png" alt="AI agents and chats" />
|
||||
<source media="(prefers-color-scheme: dark)" srcset="./packages/twenty-website-new/public/images/readme/v2-ai-agents-dark.webp" />
|
||||
<source media="(prefers-color-scheme: light)" srcset="./packages/twenty-website-new/public/images/readme/v2-ai-agents-light.webp" />
|
||||
<img src="./packages/twenty-website-new/public/images/readme/v2-ai-agents-light.webp" alt="AI agents and chats" />
|
||||
</picture>
|
||||
<p align="center"><a href="https://docs.twenty.com/user-guide/ai/overview"><img src="./packages/twenty-website-new/public/images/readme/message-icon.svg" width="16" height="16"/> Learn more about AI in doc</a></p>
|
||||
</td>
|
||||
<td width="50%">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="./packages/twenty-website-new/public/images/readme/v2-crm-tools-dark.png" />
|
||||
<source media="(prefers-color-scheme: light)" srcset="./packages/twenty-website-new/public/images/readme/v2-crm-tools-light.png" />
|
||||
<img src="./packages/twenty-website-new/public/images/readme/v2-crm-tools-light.png" alt="Plus all the tools of a good CRM" />
|
||||
<source media="(prefers-color-scheme: dark)" srcset="./packages/twenty-website-new/public/images/readme/v2-crm-tools-dark.webp" />
|
||||
<source media="(prefers-color-scheme: light)" srcset="./packages/twenty-website-new/public/images/readme/v2-crm-tools-light.webp" />
|
||||
<img src="./packages/twenty-website-new/public/images/readme/v2-crm-tools-light.webp" alt="Plus all the tools of a good CRM" />
|
||||
</picture>
|
||||
<p align="center"><a href="https://docs.twenty.com/user-guide/introduction"><img src="./packages/twenty-website-new/public/images/readme/star-icon.svg" width="16" height="16"/> Learn more about CRM features in doc</a></p>
|
||||
</td>
|
||||
@@ -152,13 +152,13 @@ Want to go deeper? Read the <a href="https://docs.twenty.com/user-guide/introduc
|
||||
# Thanks
|
||||
|
||||
<p align="center">
|
||||
<a href="https://www.chromatic.com/"><img src="./packages/twenty-website-new/public/images/readme/chromatic.png" height="28" alt="Chromatic" /></a>
|
||||
<a href="https://www.chromatic.com/"><img src="./packages/twenty-website-new/public/images/readme/chromatic.webp" height="28" alt="Chromatic" /></a>
|
||||
|
||||
<a href="https://greptile.com"><img src="./packages/twenty-website-new/public/images/readme/greptile.png" height="28" alt="Greptile" /></a>
|
||||
<a href="https://greptile.com"><img src="./packages/twenty-website-new/public/images/readme/greptile.webp" height="28" alt="Greptile" /></a>
|
||||
|
||||
<a href="https://sentry.io/"><img src="./packages/twenty-website-new/public/images/readme/sentry.png" height="28" alt="Sentry" /></a>
|
||||
<a href="https://sentry.io/"><img src="./packages/twenty-website-new/public/images/readme/sentry.webp" height="28" alt="Sentry" /></a>
|
||||
|
||||
<a href="https://crowdin.com/"><img src="./packages/twenty-website-new/public/images/readme/crowdin.png" height="28" alt="Crowdin" /></a>
|
||||
<a href="https://crowdin.com/"><img src="./packages/twenty-website-new/public/images/readme/crowdin.webp" height="28" alt="Crowdin" /></a>
|
||||
</p>
|
||||
|
||||
Thanks to these amazing services that we use and recommend for UI testing (Chromatic), code review (Greptile), catching bugs (Sentry) and translating (Crowdin).
|
||||
|
||||
@@ -57,6 +57,7 @@
|
||||
"lint:diff-with-main": {
|
||||
"executor": "nx:run-commands",
|
||||
"cache": false,
|
||||
"dependsOn": ["twenty-oxlint-rules:build"],
|
||||
"options": {
|
||||
"command": "FILES=$(git diff --name-only --diff-filter=d main -- {projectRoot}/ | grep -E '{args.pattern}'); [ -z \"$FILES\" ] && echo 'No changed files.' || (npx oxlint -c {projectRoot}/.oxlintrc.json $FILES && (prettier --check $FILES || (echo 'ERROR: Prettier formatting check failed! Fix with: npx nx lint:diff-with-main --configuration=fix' && false)))",
|
||||
"pattern": "\\.(ts|tsx|js|jsx)$"
|
||||
|
||||
+2
-2
@@ -60,11 +60,11 @@
|
||||
"packages/twenty-sdk",
|
||||
"packages/twenty-front-component-renderer",
|
||||
"packages/twenty-client-sdk",
|
||||
"packages/twenty-apps",
|
||||
"packages/twenty-cli",
|
||||
"packages/create-twenty-app",
|
||||
"packages/twenty-oxlint-rules",
|
||||
"packages/twenty-companion"
|
||||
"packages/twenty-companion",
|
||||
"packages/twenty-claude-skills"
|
||||
]
|
||||
},
|
||||
"prettier": {
|
||||
|
||||
@@ -48,11 +48,11 @@ Examples are sourced from [twentyhq/twenty/packages/twenty-apps/examples](https:
|
||||
|
||||
## Documentation
|
||||
|
||||
Full documentation is available at **[docs.twenty.com/developers/extend/apps](https://docs.twenty.com/developers/extend/apps/getting-started)**:
|
||||
Full documentation is available at **[docs.twenty.com/developers/extend/apps](https://docs.twenty.com/developers/extend/apps/getting-started/quick-start)**:
|
||||
|
||||
- [Getting Started](https://docs.twenty.com/developers/extend/apps/getting-started) — step-by-step setup, project structure, server management, CI
|
||||
- [Building Apps](https://docs.twenty.com/developers/extend/apps/building) — entity definitions, API clients, testing
|
||||
- [Publishing](https://docs.twenty.com/developers/extend/apps/publishing) — deploy, npm publish, marketplace
|
||||
- [Quick Start](https://docs.twenty.com/developers/extend/apps/getting-started/quick-start) — scaffold, run a local server, sync your code
|
||||
- [Concepts](https://docs.twenty.com/developers/extend/apps/getting-started/concepts) — how apps work: entity model, sandboxing, lifecycle
|
||||
- [Operations](https://docs.twenty.com/developers/extend/apps/operations/overview) — CLI, testing, CI, deploy and publish
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "create-twenty-app",
|
||||
"version": "2.3.0",
|
||||
"version": "2.4.0",
|
||||
"description": "Command-line interface to create Twenty application",
|
||||
"main": "dist/cli.cjs",
|
||||
"bin": "dist/cli.cjs",
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
## Base documentation
|
||||
|
||||
- Getting started:
|
||||
- https://docs.twenty.com/developers/extend/apps/getting-started/quick-start.md
|
||||
- https://docs.twenty.com/developers/extend/apps/getting-started/concepts.md
|
||||
- https://docs.twenty.com/developers/extend/apps/getting-started/project-structure.md
|
||||
- https://docs.twenty.com/developers/extend/apps/getting-started/local-server.md
|
||||
- https://docs.twenty.com/developers/extend/apps/getting-started/scaffolding.md
|
||||
- https://docs.twenty.com/developers/extend/apps/getting-started/troubleshooting.md
|
||||
- Config:
|
||||
- https://docs.twenty.com/developers/extend/apps/config/overview.md
|
||||
- https://docs.twenty.com/developers/extend/apps/config/application.md
|
||||
- https://docs.twenty.com/developers/extend/apps/config/roles.md
|
||||
- https://docs.twenty.com/developers/extend/apps/config/install-hooks.md
|
||||
- https://docs.twenty.com/developers/extend/apps/config/public-assets.md
|
||||
- Data:
|
||||
- https://docs.twenty.com/developers/extend/apps/data/overview.md
|
||||
- https://docs.twenty.com/developers/extend/apps/data/objects.md
|
||||
- https://docs.twenty.com/developers/extend/apps/data/extending-objects.md
|
||||
- https://docs.twenty.com/developers/extend/apps/data/relations.md
|
||||
- Logic:
|
||||
- https://docs.twenty.com/developers/extend/apps/logic/overview.md
|
||||
- https://docs.twenty.com/developers/extend/apps/logic/logic-functions.md
|
||||
- https://docs.twenty.com/developers/extend/apps/logic/skills-and-agents.md
|
||||
- https://docs.twenty.com/developers/extend/apps/logic/connections.md
|
||||
- Layout:
|
||||
- https://docs.twenty.com/developers/extend/apps/layout/overview.md
|
||||
- https://docs.twenty.com/developers/extend/apps/layout/views.md
|
||||
- https://docs.twenty.com/developers/extend/apps/layout/navigation-menu-items.md
|
||||
- https://docs.twenty.com/developers/extend/apps/layout/page-layouts.md
|
||||
- https://docs.twenty.com/developers/extend/apps/layout/front-components.md
|
||||
- https://docs.twenty.com/developers/extend/apps/layout/command-menu-items.md
|
||||
- Operations:
|
||||
- https://docs.twenty.com/developers/extend/apps/operations/overview.md
|
||||
- https://docs.twenty.com/developers/extend/apps/operations/cli.md
|
||||
- https://docs.twenty.com/developers/extend/apps/operations/testing.md
|
||||
- https://docs.twenty.com/developers/extend/apps/operations/publishing.md
|
||||
- Rich app example: https://github.com/twentyhq/twenty/tree/main/packages/twenty-apps/examples/postcard
|
||||
|
||||
## UUID requirement
|
||||
|
||||
- All generated UUIDs must be valid UUID v4.
|
||||
|
||||
## Common Pitfalls
|
||||
|
||||
- Creating an object without an index view associated. Unless this is a technical object, user will need to visualize it.
|
||||
- Creating a view without a navigationMenuItem associated. This will make the view available on the left sidebar.
|
||||
- Creating a front-end component that has a scroll instead of being responsive to its fixed widget height and width, unless it is specifically meant to be used in a canvas tab.
|
||||
|
||||
## Best practice
|
||||
|
||||
It's highly recommended to create new app entities using `yarn twenty add`. These are the options:
|
||||
|
||||
| Entity type | Command | Generated file |
|
||||
| -------------------- | ------------------------------------ | ------------------------------------- |
|
||||
| Object | `yarn twenty add object` | `src/objects/<name>.ts` |
|
||||
| Field | `yarn twenty add field` | `src/fields/<name>.ts` |
|
||||
| Logic function | `yarn twenty add logicFunction` | `src/logic-functions/<name>.ts` |
|
||||
| Front component | `yarn twenty add frontComponent` | `src/front-components/<name>.tsx` |
|
||||
| Role | `yarn twenty add role` | `src/roles/<name>.ts` |
|
||||
| Skill | `yarn twenty add skill` | `src/skills/<name>.ts` |
|
||||
| Agent | `yarn twenty add agent` | `src/agents/<name>.ts` |
|
||||
| View | `yarn twenty add view` | `src/views/<name>.ts` |
|
||||
| Navigation menu item | `yarn twenty add navigationMenuItem` | `src/navigation-menu-items/<name>.ts` |
|
||||
| Page layout | `yarn twenty add pageLayout` | `src/page-layouts/<name>.ts` |
|
||||
|
||||
This helps automatically generate required IDs etc.
|
||||
@@ -6,6 +6,6 @@ Run `yarn twenty help` to list all available commands.
|
||||
|
||||
## Learn More
|
||||
|
||||
- [Twenty Apps documentation](https://docs.twenty.com/developers/extend/apps/getting-started)
|
||||
- [Twenty Apps documentation](https://docs.twenty.com/developers/extend/apps/getting-started/quick-start)
|
||||
- [twenty-sdk CLI reference](https://www.npmjs.com/package/twenty-sdk)
|
||||
- [Discord](https://discord.gg/cx5n4Jzs57)
|
||||
|
||||
@@ -4,12 +4,10 @@ import {
|
||||
APP_DESCRIPTION,
|
||||
APP_DISPLAY_NAME,
|
||||
APPLICATION_UNIVERSAL_IDENTIFIER,
|
||||
DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
|
||||
} from 'src/constants/universal-identifiers';
|
||||
|
||||
export default defineApplication({
|
||||
universalIdentifier: APPLICATION_UNIVERSAL_IDENTIFIER,
|
||||
displayName: APP_DISPLAY_NAME,
|
||||
description: APP_DESCRIPTION,
|
||||
defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
|
||||
});
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { defineRole } from 'twenty-sdk/define';
|
||||
import { defineApplicationRole } from 'twenty-sdk/define';
|
||||
|
||||
import {
|
||||
APP_DISPLAY_NAME,
|
||||
DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
|
||||
} from 'src/constants/universal-identifiers';
|
||||
|
||||
export default defineRole({
|
||||
export default defineApplicationRole({
|
||||
universalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
|
||||
label: `${APP_DISPLAY_NAME} default function role`,
|
||||
description: `${APP_DISPLAY_NAME} default function role`,
|
||||
|
||||
@@ -16,7 +16,6 @@ import {
|
||||
containerExists,
|
||||
detectLocalServer,
|
||||
serverStart,
|
||||
type ServerStartResult,
|
||||
} from 'twenty-sdk/cli';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
@@ -33,6 +32,8 @@ type CreateAppOptions = {
|
||||
};
|
||||
|
||||
export class CreateAppCommand {
|
||||
private static TOTAL_STEPS = 4;
|
||||
|
||||
async execute(options: CreateAppOptions = {}): Promise<void> {
|
||||
const { appName, appDisplayName, appDirectory, appDescription } =
|
||||
await this.getAppInfos(options);
|
||||
@@ -40,9 +41,26 @@ export class CreateAppCommand {
|
||||
try {
|
||||
await this.validateDirectory(appDirectory);
|
||||
|
||||
this.logCreationInfo({ appDirectory, appName });
|
||||
const confirmed = await this.promptScaffoldConfirmation({
|
||||
appName,
|
||||
appDisplayName,
|
||||
appDescription,
|
||||
appDirectory,
|
||||
autoConfirm: options.yes,
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
console.log(chalk.gray('\nScaffolding cancelled.'));
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
console.log('');
|
||||
|
||||
this.logStep(1, 'Creating project directory');
|
||||
await fs.ensureDir(appDirectory);
|
||||
this.logDetail(appDirectory);
|
||||
|
||||
this.logStep(2, 'Scaffolding project files');
|
||||
|
||||
if (options.example) {
|
||||
const exampleSucceeded = await this.tryDownloadExample(
|
||||
@@ -56,6 +74,7 @@ export class CreateAppCommand {
|
||||
appDisplayName,
|
||||
appDescription,
|
||||
appDirectory,
|
||||
onProgress: (message) => this.logDetail(message),
|
||||
});
|
||||
}
|
||||
} else {
|
||||
@@ -64,33 +83,59 @@ export class CreateAppCommand {
|
||||
appDisplayName,
|
||||
appDescription,
|
||||
appDirectory,
|
||||
onProgress: (message) => this.logDetail(message),
|
||||
});
|
||||
}
|
||||
|
||||
await install(appDirectory);
|
||||
this.logStep(3, 'Installing dependencies');
|
||||
await install(appDirectory, (message) => this.logDetail(message));
|
||||
|
||||
await tryGitInit(appDirectory);
|
||||
this.logStep(4, 'Initializing Git repository');
|
||||
const gitInitialized = await tryGitInit(appDirectory);
|
||||
|
||||
let serverResult: ServerStartResult | undefined;
|
||||
if (gitInitialized) {
|
||||
this.logDetail('Initialized on branch main');
|
||||
this.logDetail('Created initial commit');
|
||||
} else {
|
||||
this.logDetail(
|
||||
'Skipped (Git unavailable, initialization failed, or already in a repository)',
|
||||
);
|
||||
}
|
||||
|
||||
console.log('');
|
||||
|
||||
let hasLocalServer = false;
|
||||
let authSucceeded = false;
|
||||
|
||||
if (!options.skipLocalInstance) {
|
||||
const shouldStartServer = await this.shouldStartServer(options.yes);
|
||||
const existingServerUrl = await detectLocalServer();
|
||||
|
||||
if (shouldStartServer) {
|
||||
const startResult = await serverStart({
|
||||
onProgress: (message: string) => console.log(chalk.gray(message)),
|
||||
});
|
||||
if (existingServerUrl) {
|
||||
hasLocalServer = true;
|
||||
authSucceeded = await this.promptConnectToLocal(existingServerUrl);
|
||||
} else {
|
||||
const shouldStart = await this.shouldStartServer(options.yes);
|
||||
|
||||
if (startResult.success) {
|
||||
serverResult = startResult.data;
|
||||
await this.promptConnectToLocal(serverResult.url);
|
||||
if (shouldStart) {
|
||||
const startResult = await serverStart({
|
||||
onProgress: (message: string) => console.log(chalk.gray(message)),
|
||||
});
|
||||
|
||||
if (startResult.success) {
|
||||
hasLocalServer = true;
|
||||
authSucceeded = await this.promptConnectToLocal(
|
||||
startResult.data.url,
|
||||
);
|
||||
} else {
|
||||
console.log(chalk.yellow(`\n${startResult.error.message}`));
|
||||
}
|
||||
} else {
|
||||
console.log(chalk.yellow(`\n${startResult.error.message}`));
|
||||
this.logServerSkipped();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.logSuccess(appDirectory, serverResult);
|
||||
this.logSuccess(appDirectory, hasLocalServer, authSucceeded);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
chalk.red('\nCreate application failed:'),
|
||||
@@ -213,25 +258,80 @@ export class CreateAppCommand {
|
||||
}
|
||||
}
|
||||
|
||||
private logCreationInfo({
|
||||
appDirectory,
|
||||
private async promptScaffoldConfirmation({
|
||||
appName,
|
||||
appDisplayName,
|
||||
appDescription,
|
||||
appDirectory,
|
||||
autoConfirm,
|
||||
}: {
|
||||
appDirectory: string;
|
||||
appName: string;
|
||||
}): void {
|
||||
appDisplayName: string;
|
||||
appDescription: string;
|
||||
appDirectory: string;
|
||||
autoConfirm?: boolean;
|
||||
}): Promise<boolean> {
|
||||
console.log(chalk.blue('\nCreating Twenty Application\n'));
|
||||
console.log(chalk.white(` Name: ${appName}`));
|
||||
console.log(chalk.white(` Display name: ${appDisplayName}`));
|
||||
|
||||
if (appDescription) {
|
||||
console.log(chalk.white(` Description: ${appDescription}`));
|
||||
}
|
||||
|
||||
console.log(chalk.white(` Directory: ${appDirectory}`));
|
||||
|
||||
console.log(chalk.white('\nThe following steps will be performed:\n'));
|
||||
console.log(chalk.gray(' 1. Create project directory'));
|
||||
console.log(
|
||||
chalk.blue('\n', 'Creating Twenty Application\n'),
|
||||
chalk.gray(`- Directory: ${appDirectory}\n`, `- Name: ${appName}\n`),
|
||||
chalk.gray(
|
||||
' 2. Scaffold project files from base template\n' +
|
||||
' - Copy template files\n' +
|
||||
' - Configure dotfiles (.gitignore, .github)\n' +
|
||||
' - Generate unique application identifiers\n' +
|
||||
' - Update package.json with app name and SDK versions',
|
||||
),
|
||||
);
|
||||
console.log(chalk.gray(' 3. Install dependencies (yarn)'));
|
||||
console.log(
|
||||
chalk.gray(' 4. Initialize Git repository with initial commit'),
|
||||
);
|
||||
console.log('');
|
||||
|
||||
if (autoConfirm) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const { proceed } = await inquirer.prompt([
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'proceed',
|
||||
message: 'Proceed?',
|
||||
default: true,
|
||||
},
|
||||
]);
|
||||
|
||||
return proceed;
|
||||
}
|
||||
|
||||
private logStep(step: number, title: string): void {
|
||||
console.log(
|
||||
chalk.blue(`\n[${step}/${CreateAppCommand.TOTAL_STEPS}]`) +
|
||||
chalk.white(` ${title}...`),
|
||||
);
|
||||
}
|
||||
|
||||
private async shouldStartServer(autoConfirm?: boolean): Promise<boolean> {
|
||||
const existingServerUrl = await detectLocalServer();
|
||||
private logDetail(message: string): void {
|
||||
console.log(chalk.gray(` → ${message}`));
|
||||
}
|
||||
|
||||
if (existingServerUrl) {
|
||||
return true;
|
||||
}
|
||||
private async shouldStartServer(autoConfirm?: boolean): Promise<boolean> {
|
||||
console.log(
|
||||
chalk.white(
|
||||
'\n A local Twenty instance is required for app development.\n' +
|
||||
' It provides the API and schema your application connects to.\n',
|
||||
),
|
||||
);
|
||||
|
||||
if (checkDockerRunning() && containerExists()) {
|
||||
if (autoConfirm) {
|
||||
@@ -268,12 +368,31 @@ export class CreateAppCommand {
|
||||
return startDocker;
|
||||
}
|
||||
|
||||
private async promptConnectToLocal(serverUrl: string): Promise<void> {
|
||||
private logServerSkipped(): void {
|
||||
console.log(
|
||||
chalk.gray(
|
||||
'\n To start a Twenty instance later:\n' +
|
||||
' yarn twenty server start\n\n' +
|
||||
' To connect to a remote instance instead:\n' +
|
||||
' yarn twenty remote add\n',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
private async promptConnectToLocal(serverUrl: string): Promise<boolean> {
|
||||
console.log(
|
||||
chalk.white(
|
||||
'\n Authentication links your app to a Twenty instance so you can\n' +
|
||||
' sync custom objects, fields, and roles during development.\n' +
|
||||
' This will open a browser window to complete the OAuth flow.\n',
|
||||
),
|
||||
);
|
||||
|
||||
const { shouldAuthenticate } = await inquirer.prompt([
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'shouldAuthenticate',
|
||||
message: `Would you like to authenticate to the local Twenty instance (${serverUrl})?`,
|
||||
message: `Authenticate to the local Twenty instance (${serverUrl})?`,
|
||||
default: true,
|
||||
},
|
||||
]);
|
||||
@@ -281,13 +400,22 @@ export class CreateAppCommand {
|
||||
if (!shouldAuthenticate) {
|
||||
console.log(
|
||||
chalk.gray(
|
||||
'Authentication skipped. Run `yarn twenty remote add --local` manually.',
|
||||
'\n Authentication skipped. To authenticate later:\n' +
|
||||
` yarn twenty remote add --local\n`,
|
||||
),
|
||||
);
|
||||
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
await inquirer.prompt([
|
||||
{
|
||||
type: 'input',
|
||||
name: 'confirm',
|
||||
message: 'Press Enter to open the browser for authentication...',
|
||||
},
|
||||
]);
|
||||
|
||||
try {
|
||||
const result = await authLoginOAuth({
|
||||
apiUrl: serverUrl,
|
||||
@@ -298,12 +426,16 @@ export class CreateAppCommand {
|
||||
const configService = new ConfigService();
|
||||
|
||||
await configService.setDefaultRemote('local');
|
||||
|
||||
return true;
|
||||
} else {
|
||||
console.log(
|
||||
chalk.yellow(
|
||||
'Authentication failed. Run `yarn twenty remote add --local` manually.',
|
||||
),
|
||||
);
|
||||
|
||||
return false;
|
||||
}
|
||||
} catch {
|
||||
console.log(
|
||||
@@ -311,28 +443,44 @@ export class CreateAppCommand {
|
||||
'Authentication failed. Run `yarn twenty remote add` manually.',
|
||||
),
|
||||
);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private logSuccess(
|
||||
appDirectory: string,
|
||||
serverResult?: ServerStartResult,
|
||||
hasLocalServer: boolean,
|
||||
authSucceeded: boolean,
|
||||
): void {
|
||||
const dirName = basename(appDirectory);
|
||||
|
||||
console.log(chalk.blue('\nApplication created. Next steps:'));
|
||||
console.log(chalk.gray(`- cd ${dirName}`));
|
||||
console.log(chalk.green('\n✔ Application created successfully!\n'));
|
||||
console.log(chalk.white(' Next steps:\n'));
|
||||
|
||||
if (!serverResult) {
|
||||
console.log(
|
||||
chalk.gray(
|
||||
'- yarn twenty remote add # Authenticate with Twenty',
|
||||
),
|
||||
);
|
||||
let stepNumber = 1;
|
||||
|
||||
console.log(chalk.white(` ${stepNumber}. Navigate to your project`));
|
||||
console.log(chalk.cyan(` cd ${dirName}\n`));
|
||||
stepNumber++;
|
||||
|
||||
if (!authSucceeded) {
|
||||
const remoteCommand = hasLocalServer
|
||||
? 'yarn twenty remote add --local'
|
||||
: 'yarn twenty remote add';
|
||||
|
||||
console.log(chalk.white(` ${stepNumber}. Connect to a Twenty instance`));
|
||||
console.log(chalk.cyan(` ${remoteCommand}\n`));
|
||||
stepNumber++;
|
||||
}
|
||||
|
||||
console.log(chalk.white(` ${stepNumber}. Start developing`));
|
||||
console.log(chalk.cyan(' yarn twenty dev\n'));
|
||||
|
||||
console.log(
|
||||
chalk.gray('- yarn twenty dev # Start dev mode'),
|
||||
chalk.gray(
|
||||
' Documentation: https://docs.twenty.com/developers/extend/capabilities/apps',
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,11 +76,16 @@ describe('copyBaseApplicationProject', () => {
|
||||
appDirectory: testAppDirectory,
|
||||
});
|
||||
|
||||
expect(fs.copy).toHaveBeenCalledTimes(1);
|
||||
// Two fs.copy calls: (1) the template directory, (2) AGENTS.md → CLAUDE.md
|
||||
expect(fs.copy).toHaveBeenCalledTimes(2);
|
||||
expect(fs.copy).toHaveBeenCalledWith(
|
||||
expect.stringContaining('template'),
|
||||
testAppDirectory,
|
||||
);
|
||||
expect(fs.copy).toHaveBeenCalledWith(
|
||||
join(testAppDirectory, 'AGENTS.md'),
|
||||
join(testAppDirectory, 'CLAUDE.md'),
|
||||
);
|
||||
});
|
||||
|
||||
it('should replace placeholders in universal-identifiers.ts with real values', async () => {
|
||||
|
||||
@@ -3,7 +3,6 @@ import { join } from 'path';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import createTwentyAppPackageJson from 'package.json';
|
||||
import chalk from 'chalk';
|
||||
|
||||
const SRC_FOLDER = 'src';
|
||||
|
||||
@@ -12,25 +11,33 @@ export const copyBaseApplicationProject = async ({
|
||||
appDisplayName,
|
||||
appDescription,
|
||||
appDirectory,
|
||||
onProgress,
|
||||
}: {
|
||||
appName: string;
|
||||
appDisplayName: string;
|
||||
appDescription: string;
|
||||
appDirectory: string;
|
||||
onProgress?: (message: string) => void;
|
||||
}) => {
|
||||
console.log(chalk.gray('Generating application project...'));
|
||||
onProgress?.('Copying base template');
|
||||
await fs.copy(join(__dirname, './constants/template'), appDirectory);
|
||||
|
||||
onProgress?.('Configuring dotfiles (.gitignore, .github)');
|
||||
await renameDotfiles({ appDirectory });
|
||||
|
||||
onProgress?.('Mirroring AGENTS.md to CLAUDE.md');
|
||||
await mirrorAgentsToClaude({ appDirectory });
|
||||
|
||||
await addEmptyPublicDirectory({ appDirectory });
|
||||
|
||||
onProgress?.('Generating unique application identifiers');
|
||||
await generateUniversalIdentifiers({
|
||||
appDisplayName,
|
||||
appDescription,
|
||||
appDirectory,
|
||||
});
|
||||
|
||||
onProgress?.('Updating package.json');
|
||||
await updatePackageJson({ appName, appDirectory });
|
||||
};
|
||||
|
||||
@@ -51,6 +58,19 @@ const renameDotfiles = async ({ appDirectory }: { appDirectory: string }) => {
|
||||
}
|
||||
};
|
||||
|
||||
// AGENTS.md is the cross-tool standard; Claude Code prefers CLAUDE.md and only
|
||||
// falls back to AGENTS.md, so we mirror the file to keep a single source of truth.
|
||||
const mirrorAgentsToClaude = async ({
|
||||
appDirectory,
|
||||
}: {
|
||||
appDirectory: string;
|
||||
}) => {
|
||||
await fs.copy(
|
||||
join(appDirectory, 'AGENTS.md'),
|
||||
join(appDirectory, 'CLAUDE.md'),
|
||||
);
|
||||
};
|
||||
|
||||
const addEmptyPublicDirectory = async ({
|
||||
appDirectory,
|
||||
}: {
|
||||
|
||||
@@ -4,14 +4,18 @@ import { exec } from 'child_process';
|
||||
|
||||
const execPromise = promisify(exec);
|
||||
|
||||
export const install = async (root: string) => {
|
||||
console.log(chalk.gray('Installing yarn dependencies...'));
|
||||
export const install = async (
|
||||
root: string,
|
||||
onProgress?: (message: string) => void,
|
||||
) => {
|
||||
onProgress?.('Enabling corepack');
|
||||
try {
|
||||
await execPromise('corepack enable', { cwd: root });
|
||||
} catch (error: any) {
|
||||
console.warn(chalk.yellow('corepack enabled failed:'), error.stderr);
|
||||
console.warn(chalk.yellow('corepack enable failed:'), error.stderr);
|
||||
}
|
||||
|
||||
onProgress?.('Running yarn install');
|
||||
try {
|
||||
await execPromise('yarn install', { cwd: root });
|
||||
} catch (error: any) {
|
||||
|
||||
Binary file not shown.
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "twenty-linear",
|
||||
"version": "0.1.5",
|
||||
"version": "0.1.6",
|
||||
"description": "Linear integration for Twenty. Connect a user's Linear account and create issues from logic functions.",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -20,7 +20,7 @@
|
||||
"test:watch": "vitest --config vitest.unit.config.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"twenty-sdk": "2.1.0"
|
||||
"twenty-sdk": "2.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^24.7.2",
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 198 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 427 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 255 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 420 KiB |
@@ -1,9 +1,6 @@
|
||||
import { defineApplication } from 'twenty-sdk/define';
|
||||
|
||||
import {
|
||||
APPLICATION_UNIVERSAL_IDENTIFIER,
|
||||
DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
|
||||
} from 'src/constants/universal-identifiers';
|
||||
import { ABOUT_DESCRIPTION } from './constants/ABOUT_DESCRIPTION.md';
|
||||
import { APPLICATION_UNIVERSAL_IDENTIFIER } from 'src/constants/universal-identifiers';
|
||||
|
||||
export default defineApplication({
|
||||
universalIdentifier: APPLICATION_UNIVERSAL_IDENTIFIER,
|
||||
@@ -11,10 +8,19 @@ export default defineApplication({
|
||||
description:
|
||||
'Connect Linear to Twenty. Each workspace member connects their own Linear account; logic functions can then create issues and read team data on their behalf.',
|
||||
logoUrl: 'public/linear-logomark.svg',
|
||||
defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
|
||||
// OAuth client_id/secret live at the registration level (one OAuth app per
|
||||
// Twenty server, configured by the server admin) — not per-workspace —
|
||||
// so they're declared as serverVariables, not applicationVariables.
|
||||
aboutDescription: ABOUT_DESCRIPTION,
|
||||
applicationVariables: undefined,
|
||||
author: 'Twenty',
|
||||
category: 'Product management',
|
||||
emailSupport: 'contact@twenty.com',
|
||||
screenshots: [
|
||||
'public/gallery/command-menu-item-1.png',
|
||||
'public/gallery/command-menu-item-2.png',
|
||||
'public/gallery/command-menu-item-3.png',
|
||||
'public/gallery/command-menu-item-4.png',
|
||||
],
|
||||
termsUrl: 'https://github.com/twentyhq/twenty?tab=License-1-ov-file#readme',
|
||||
websiteUrl: 'https://www.twenty.com',
|
||||
serverVariables: {
|
||||
LINEAR_CLIENT_ID: {
|
||||
description:
|
||||
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
import { defineCommandMenuItem } from 'twenty-sdk/define';
|
||||
|
||||
import {
|
||||
CREATE_ISSUE_COMMAND_UNIVERSAL_IDENTIFIER,
|
||||
CREATE_ISSUE_FORM_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER,
|
||||
} from 'src/constants/universal-identifiers';
|
||||
|
||||
export default defineCommandMenuItem({
|
||||
universalIdentifier: CREATE_ISSUE_COMMAND_UNIVERSAL_IDENTIFIER,
|
||||
label: 'Create Linear issue',
|
||||
shortLabel: 'Linear issue',
|
||||
icon: 'IconPlaylistAdd',
|
||||
isPinned: false,
|
||||
availabilityType: 'GLOBAL',
|
||||
frontComponentUniversalIdentifier:
|
||||
CREATE_ISSUE_FORM_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER,
|
||||
});
|
||||
+1015
File diff suppressed because it is too large
Load Diff
+1
-3
@@ -15,8 +15,6 @@ export default defineConnectionProvider({
|
||||
clientIdVariable: 'LINEAR_CLIENT_ID',
|
||||
clientSecretVariable: 'LINEAR_CLIENT_SECRET',
|
||||
tokenRequestContentType: 'form-urlencoded',
|
||||
// Linear supports PKCE but doesn't require it for confidential clients.
|
||||
// Disabled to keep the test surface minimal.
|
||||
usePkce: false,
|
||||
usePkce: true,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
export const ABOUT_DESCRIPTION = `Connect your Linear account to Twenty to create issues and look up teams straight from your workflows or the AI chat.
|
||||
|
||||
## What you can do
|
||||
|
||||
Once installed and connected, two tools become available:
|
||||
|
||||
- **Create Linear issue**
|
||||
- From the AI chat, ask something like *"create a Linear issue in the Engineering team titled 'Fix login bug'"* and the AI will file it for you.
|
||||
- From a workflow, add it as a step with \`teamId\` + \`title\` (and optional \`description\`).
|
||||
|
||||
- **List Linear teams** — discovers the teams in your Linear workspace, useful when you need to pick a \`teamId\` for the create-issue step.
|
||||
|
||||
## Installing
|
||||
|
||||
1. Open **Settings → Applications** in your Twenty workspace.
|
||||
2. Find **Linear** in the available apps and click **Install**.
|
||||
3. Open the app, go to the **Connections** tab, and click **Add connection**.
|
||||
4. Choose **Just for me** (your personal Linear account) or **Workspace shared** (a team-managed Linear account anyone in this workspace can act through), then complete the Linear sign-in.
|
||||
|
||||
That's it — you can now use the tools above.`;
|
||||
@@ -17,3 +17,24 @@ export const CREATE_LINEAR_ISSUE_UNIVERSAL_IDENTIFIER =
|
||||
|
||||
export const LIST_LINEAR_TEAMS_UNIVERSAL_IDENTIFIER =
|
||||
'15824bbc-9c64-4f97-b45f-d0a44b402bb8';
|
||||
|
||||
export const LIST_LINEAR_TEAMS_ROUTE_UNIVERSAL_IDENTIFIER =
|
||||
'a1d4e7b3-5c2f-4a89-9b0e-6f3d8c1a7e5b';
|
||||
|
||||
export const CREATE_LINEAR_ISSUE_ROUTE_UNIVERSAL_IDENTIFIER =
|
||||
'c8f2a6d1-3b7e-4e09-8d5c-2a9f0b4e6c3d';
|
||||
|
||||
export const CREATE_ISSUE_FORM_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER =
|
||||
'd4a1c8e5-7f3b-4d62-a90e-1b5c3f8d2a6e';
|
||||
|
||||
export const LIST_LINEAR_WORKFLOW_STATES_ROUTE_UNIVERSAL_IDENTIFIER =
|
||||
'f5a2d8c1-4b7e-4f93-a6d0-9c3e1b8f5a2d';
|
||||
|
||||
export const LIST_LINEAR_ISSUE_OPTIONS_ROUTE_UNIVERSAL_IDENTIFIER =
|
||||
'a3b7e1d9-8c4f-4a26-b5d0-7e9f2c6a8b3d';
|
||||
|
||||
export const LIST_LINEAR_ISSUE_OPTIONS_UNIVERSAL_IDENTIFIER =
|
||||
'c2b99086-6c1b-43e4-9685-de4ade6bda63';
|
||||
|
||||
export const CREATE_ISSUE_COMMAND_UNIVERSAL_IDENTIFIER =
|
||||
'e7b3d9f2-6a1c-4e85-b4d0-8f2c5a7e3b19';
|
||||
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
import { defineLogicFunction } from 'twenty-sdk/define';
|
||||
import type { RoutePayload } from 'twenty-sdk/define';
|
||||
|
||||
import { CREATE_LINEAR_ISSUE_ROUTE_UNIVERSAL_IDENTIFIER } from 'src/constants/universal-identifiers';
|
||||
import { createLinearIssueHandler } from 'src/logic-functions/handlers/create-linear-issue-handler';
|
||||
|
||||
const handler = async (event: RoutePayload) => {
|
||||
const body = event.body as Record<string, unknown> | null;
|
||||
|
||||
return createLinearIssueHandler({
|
||||
teamId: body?.teamId as string | undefined,
|
||||
title: body?.title as string | undefined,
|
||||
description: body?.description as string | undefined,
|
||||
priority: body?.priority as number | undefined,
|
||||
stateId: body?.stateId as string | undefined,
|
||||
assigneeId: body?.assigneeId as string | undefined,
|
||||
projectId: body?.projectId as string | undefined,
|
||||
estimate: body?.estimate as number | undefined,
|
||||
labelIds: body?.labelIds as string[] | undefined,
|
||||
cycleId: body?.cycleId as string | undefined,
|
||||
dueDate: body?.dueDate as string | undefined,
|
||||
});
|
||||
};
|
||||
|
||||
export default defineLogicFunction({
|
||||
universalIdentifier: CREATE_LINEAR_ISSUE_ROUTE_UNIVERSAL_IDENTIFIER,
|
||||
name: 'create-linear-issue-route',
|
||||
timeoutSeconds: 30,
|
||||
handler,
|
||||
httpRouteTriggerSettings: {
|
||||
path: '/linear/issues',
|
||||
httpMethod: 'POST',
|
||||
isAuthRequired: true,
|
||||
},
|
||||
});
|
||||
+78
@@ -27,8 +27,86 @@ export default defineLogicFunction({
|
||||
type: 'string',
|
||||
description: 'Optional issue description (Markdown supported).',
|
||||
},
|
||||
priority: {
|
||||
type: 'integer',
|
||||
description:
|
||||
'Issue priority: 0 = No priority, 1 = Urgent, 2 = High, 3 = Medium, 4 = Low.',
|
||||
minimum: 0,
|
||||
maximum: 4,
|
||||
},
|
||||
stateId: {
|
||||
type: 'string',
|
||||
description:
|
||||
'The workflow state ID for the issue status. Use list-linear-issue-options to discover available states for a team.',
|
||||
},
|
||||
assigneeId: {
|
||||
type: 'string',
|
||||
description:
|
||||
'The user ID to assign the issue to. Use list-linear-issue-options to discover team members.',
|
||||
},
|
||||
projectId: {
|
||||
type: 'string',
|
||||
description: 'The project ID to associate the issue with.',
|
||||
},
|
||||
estimate: {
|
||||
type: 'number',
|
||||
description:
|
||||
'The estimate value for the issue. Must match the team estimate scale.',
|
||||
},
|
||||
labelIds: {
|
||||
type: 'array',
|
||||
items: { type: 'string' },
|
||||
description: 'Array of label IDs to apply to the issue.',
|
||||
},
|
||||
cycleId: {
|
||||
type: 'string',
|
||||
description: 'The cycle ID to add the issue to.',
|
||||
},
|
||||
dueDate: {
|
||||
type: 'string',
|
||||
description: 'Due date in YYYY-MM-DD format.',
|
||||
},
|
||||
},
|
||||
required: ['teamId', 'title'],
|
||||
},
|
||||
},
|
||||
workflowActionTriggerSettings: {
|
||||
label: 'Create Linear Issue',
|
||||
inputSchema: [
|
||||
{
|
||||
type: 'object',
|
||||
properties: {
|
||||
teamId: { type: 'string' },
|
||||
title: { type: 'string' },
|
||||
description: { type: 'string' },
|
||||
priority: { type: 'number' },
|
||||
stateId: { type: 'string' },
|
||||
assigneeId: { type: 'string' },
|
||||
projectId: { type: 'string' },
|
||||
estimate: { type: 'number' },
|
||||
labelIds: { type: 'array', items: { type: 'string' } },
|
||||
cycleId: { type: 'string' },
|
||||
dueDate: { type: 'string' },
|
||||
},
|
||||
},
|
||||
],
|
||||
outputSchema: [
|
||||
{
|
||||
type: 'object',
|
||||
properties: {
|
||||
success: { type: 'boolean' },
|
||||
issue: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string' },
|
||||
identifier: { type: 'string' },
|
||||
title: { type: 'string' },
|
||||
url: { type: 'string' },
|
||||
},
|
||||
},
|
||||
error: { type: 'string' },
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
+11
@@ -49,6 +49,17 @@ export const createLinearIssueHandler = async (
|
||||
teamId: input.teamId,
|
||||
title: input.title,
|
||||
description: input.description,
|
||||
...(input.priority !== undefined && { priority: input.priority }),
|
||||
...(input.stateId !== undefined && { stateId: input.stateId }),
|
||||
...(input.assigneeId !== undefined && {
|
||||
assigneeId: input.assigneeId,
|
||||
}),
|
||||
...(input.projectId !== undefined && { projectId: input.projectId }),
|
||||
...(input.estimate !== undefined && { estimate: input.estimate }),
|
||||
...(input.labelIds !== undefined &&
|
||||
input.labelIds.length > 0 && { labelIds: input.labelIds }),
|
||||
...(input.cycleId !== undefined && { cycleId: input.cycleId }),
|
||||
...(input.dueDate !== undefined && { dueDate: input.dueDate }),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
+129
@@ -0,0 +1,129 @@
|
||||
import { listConnections } from 'twenty-sdk/logic-function';
|
||||
|
||||
import { callLinearGraphQL } from 'src/logic-functions/utils/call-linear-graphql';
|
||||
|
||||
type LinearMember = {
|
||||
id: string;
|
||||
name: string;
|
||||
displayName: string;
|
||||
};
|
||||
|
||||
type LinearProject = {
|
||||
id: string;
|
||||
name: string;
|
||||
};
|
||||
|
||||
type LinearLabel = {
|
||||
id: string;
|
||||
name: string;
|
||||
color: string;
|
||||
};
|
||||
|
||||
type LinearCycle = {
|
||||
id: string;
|
||||
name: string | null;
|
||||
number: number;
|
||||
startsAt: string;
|
||||
endsAt: string;
|
||||
};
|
||||
|
||||
type LinearWorkflowState = {
|
||||
id: string;
|
||||
name: string;
|
||||
type: string;
|
||||
position: number;
|
||||
};
|
||||
|
||||
type IssueOptionsQueryResult = {
|
||||
team: {
|
||||
states: { nodes: LinearWorkflowState[] };
|
||||
members: { nodes: LinearMember[] };
|
||||
cycles: { nodes: LinearCycle[] };
|
||||
issueEstimationType: string;
|
||||
issueEstimationAllowZero: boolean;
|
||||
};
|
||||
projects: { nodes: LinearProject[] };
|
||||
issueLabels: { nodes: LinearLabel[] };
|
||||
};
|
||||
|
||||
type IssueOptions = {
|
||||
states: LinearWorkflowState[];
|
||||
members: LinearMember[];
|
||||
projects: LinearProject[];
|
||||
labels: LinearLabel[];
|
||||
cycles: LinearCycle[];
|
||||
estimationType: string;
|
||||
estimationAllowZero: boolean;
|
||||
};
|
||||
|
||||
type HandlerResult =
|
||||
| { success: true; options: IssueOptions }
|
||||
| { success: false; error: string };
|
||||
|
||||
export const listLinearIssueOptionsHandler = async (input: {
|
||||
teamId?: string;
|
||||
}): Promise<HandlerResult> => {
|
||||
if (!input.teamId) {
|
||||
return { success: false, error: '`teamId` is required.' };
|
||||
}
|
||||
|
||||
const connections = await listConnections({ providerName: 'linear' });
|
||||
const connection =
|
||||
connections.find((c) => c.visibility === 'workspace') ?? connections[0];
|
||||
|
||||
if (!connection) {
|
||||
return {
|
||||
success: false,
|
||||
error:
|
||||
'Linear is not connected. Open the app settings and click "Add connection" first.',
|
||||
};
|
||||
}
|
||||
|
||||
const result = await callLinearGraphQL<IssueOptionsQueryResult>({
|
||||
accessToken: connection.accessToken,
|
||||
query: `
|
||||
query IssueOptions($teamId: String!) {
|
||||
team(id: $teamId) {
|
||||
states { nodes { id name type position } }
|
||||
members { nodes { id name displayName } }
|
||||
cycles { nodes { id name number startsAt endsAt } }
|
||||
issueEstimationType
|
||||
issueEstimationAllowZero
|
||||
}
|
||||
projects { nodes { id name } }
|
||||
issueLabels { nodes { id name color } }
|
||||
}
|
||||
`,
|
||||
variables: { teamId: input.teamId },
|
||||
});
|
||||
|
||||
if (result.errors || !result.data) {
|
||||
return {
|
||||
success: false,
|
||||
error: result.errors?.[0]?.message ?? 'Unknown Linear API error',
|
||||
};
|
||||
}
|
||||
|
||||
const { team, projects, issueLabels } = result.data;
|
||||
|
||||
const now = new Date().toISOString();
|
||||
const activeCycles = team.cycles.nodes.filter((c) => c.endsAt >= now);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
options: {
|
||||
states: team.states.nodes.sort((a, b) => a.position - b.position),
|
||||
members: team.members.nodes.sort((a, b) =>
|
||||
a.displayName.localeCompare(b.displayName),
|
||||
),
|
||||
projects: projects.nodes.sort((a, b) => a.name.localeCompare(b.name)),
|
||||
labels: issueLabels.nodes.sort((a, b) => a.name.localeCompare(b.name)),
|
||||
cycles: activeCycles.sort(
|
||||
(a, b) =>
|
||||
new Date(a.startsAt).getTime() - new Date(b.startsAt).getTime(),
|
||||
),
|
||||
estimationType: team.issueEstimationType,
|
||||
estimationAllowZero: team.issueEstimationAllowZero,
|
||||
},
|
||||
};
|
||||
};
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
import { listConnections } from 'twenty-sdk/logic-function';
|
||||
|
||||
import { callLinearGraphQL } from 'src/logic-functions/utils/call-linear-graphql';
|
||||
|
||||
type LinearWorkflowState = {
|
||||
id: string;
|
||||
name: string;
|
||||
type: string;
|
||||
position: number;
|
||||
};
|
||||
|
||||
type WorkflowStatesQueryResult = {
|
||||
team: { states: { nodes: LinearWorkflowState[] } };
|
||||
};
|
||||
|
||||
type HandlerResult =
|
||||
| { success: true; states: LinearWorkflowState[] }
|
||||
| { success: false; error: string };
|
||||
|
||||
export const listLinearWorkflowStatesHandler = async (input: {
|
||||
teamId?: string;
|
||||
}): Promise<HandlerResult> => {
|
||||
if (!input.teamId) {
|
||||
return { success: false, error: '`teamId` is required.' };
|
||||
}
|
||||
|
||||
const connections = await listConnections({ providerName: 'linear' });
|
||||
const connection =
|
||||
connections.find((c) => c.visibility === 'workspace') ?? connections[0];
|
||||
|
||||
if (!connection) {
|
||||
return {
|
||||
success: false,
|
||||
error:
|
||||
'Linear is not connected. Open the app settings and click "Add connection" first.',
|
||||
};
|
||||
}
|
||||
|
||||
const result = await callLinearGraphQL<WorkflowStatesQueryResult>({
|
||||
accessToken: connection.accessToken,
|
||||
query: `
|
||||
query WorkflowStates($teamId: String!) {
|
||||
team(id: $teamId) {
|
||||
states { nodes { id name type position } }
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: { teamId: input.teamId },
|
||||
});
|
||||
|
||||
if (result.errors || !result.data) {
|
||||
return {
|
||||
success: false,
|
||||
error: result.errors?.[0]?.message ?? 'Unknown Linear API error',
|
||||
};
|
||||
}
|
||||
|
||||
const states = result.data.team.states.nodes.sort(
|
||||
(a, b) => a.position - b.position,
|
||||
);
|
||||
|
||||
return { success: true, states };
|
||||
};
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
import { defineLogicFunction } from 'twenty-sdk/define';
|
||||
import type { RoutePayload } from 'twenty-sdk/define';
|
||||
|
||||
import { LIST_LINEAR_ISSUE_OPTIONS_ROUTE_UNIVERSAL_IDENTIFIER } from 'src/constants/universal-identifiers';
|
||||
import { listLinearIssueOptionsHandler } from 'src/logic-functions/handlers/list-linear-issue-options-handler';
|
||||
|
||||
const handler = async (event: RoutePayload) => {
|
||||
return listLinearIssueOptionsHandler({
|
||||
teamId: event.queryStringParameters?.teamId,
|
||||
});
|
||||
};
|
||||
|
||||
export default defineLogicFunction({
|
||||
universalIdentifier: LIST_LINEAR_ISSUE_OPTIONS_ROUTE_UNIVERSAL_IDENTIFIER,
|
||||
name: 'list-linear-issue-options-route',
|
||||
timeoutSeconds: 15,
|
||||
handler,
|
||||
httpRouteTriggerSettings: {
|
||||
path: '/linear/issue-options',
|
||||
httpMethod: 'GET',
|
||||
isAuthRequired: true,
|
||||
},
|
||||
});
|
||||
+110
@@ -0,0 +1,110 @@
|
||||
import { defineLogicFunction } from 'twenty-sdk/define';
|
||||
|
||||
import { LIST_LINEAR_ISSUE_OPTIONS_UNIVERSAL_IDENTIFIER } from 'src/constants/universal-identifiers';
|
||||
import { listLinearIssueOptionsHandler } from 'src/logic-functions/handlers/list-linear-issue-options-handler';
|
||||
|
||||
export default defineLogicFunction({
|
||||
universalIdentifier: LIST_LINEAR_ISSUE_OPTIONS_UNIVERSAL_IDENTIFIER,
|
||||
name: 'list-linear-issue-options',
|
||||
description:
|
||||
'Returns available options for creating a Linear issue in a specific team: workflow states, members, projects, labels, cycles, and estimation settings. Requires a teamId (call list-linear-teams to discover one).',
|
||||
timeoutSeconds: 15,
|
||||
handler: listLinearIssueOptionsHandler,
|
||||
toolTriggerSettings: {
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
teamId: {
|
||||
type: 'string',
|
||||
description:
|
||||
'The Linear team ID to fetch issue options for. Use list-linear-teams to discover available teams.',
|
||||
},
|
||||
},
|
||||
required: ['teamId'],
|
||||
},
|
||||
},
|
||||
workflowActionTriggerSettings: {
|
||||
label: 'List Linear Issue Options',
|
||||
inputSchema: [
|
||||
{
|
||||
type: 'object',
|
||||
properties: {
|
||||
teamId: { type: 'string' },
|
||||
},
|
||||
},
|
||||
],
|
||||
outputSchema: [
|
||||
{
|
||||
type: 'object',
|
||||
properties: {
|
||||
success: { type: 'boolean' },
|
||||
options: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
states: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string' },
|
||||
name: { type: 'string' },
|
||||
type: { type: 'string' },
|
||||
position: { type: 'number' },
|
||||
},
|
||||
},
|
||||
},
|
||||
members: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string' },
|
||||
name: { type: 'string' },
|
||||
displayName: { type: 'string' },
|
||||
},
|
||||
},
|
||||
},
|
||||
projects: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string' },
|
||||
name: { type: 'string' },
|
||||
},
|
||||
},
|
||||
},
|
||||
labels: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string' },
|
||||
name: { type: 'string' },
|
||||
color: { type: 'string' },
|
||||
},
|
||||
},
|
||||
},
|
||||
cycles: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string' },
|
||||
name: { type: 'string' },
|
||||
number: { type: 'number' },
|
||||
startsAt: { type: 'string' },
|
||||
endsAt: { type: 'string' },
|
||||
},
|
||||
},
|
||||
},
|
||||
estimationType: { type: 'string' },
|
||||
estimationAllowZero: { type: 'boolean' },
|
||||
},
|
||||
},
|
||||
error: { type: 'string' },
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
import { defineLogicFunction } from 'twenty-sdk/define';
|
||||
import type { RoutePayload } from 'twenty-sdk/define';
|
||||
|
||||
import { LIST_LINEAR_TEAMS_ROUTE_UNIVERSAL_IDENTIFIER } from 'src/constants/universal-identifiers';
|
||||
import { listLinearTeamsHandler } from 'src/logic-functions/handlers/list-linear-teams-handler';
|
||||
|
||||
const handler = async (_event: RoutePayload) => {
|
||||
return listLinearTeamsHandler();
|
||||
};
|
||||
|
||||
export default defineLogicFunction({
|
||||
universalIdentifier: LIST_LINEAR_TEAMS_ROUTE_UNIVERSAL_IDENTIFIER,
|
||||
name: 'list-linear-teams-route',
|
||||
timeoutSeconds: 15,
|
||||
handler,
|
||||
httpRouteTriggerSettings: {
|
||||
path: '/linear/teams',
|
||||
httpMethod: 'GET',
|
||||
isAuthRequired: true,
|
||||
},
|
||||
});
|
||||
@@ -16,4 +16,33 @@ export default defineLogicFunction({
|
||||
properties: {},
|
||||
},
|
||||
},
|
||||
workflowActionTriggerSettings: {
|
||||
label: 'List Linear Teams',
|
||||
inputSchema: [
|
||||
{
|
||||
type: 'object',
|
||||
properties: {},
|
||||
},
|
||||
],
|
||||
outputSchema: [
|
||||
{
|
||||
type: 'object',
|
||||
properties: {
|
||||
success: { type: 'boolean' },
|
||||
teams: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string' },
|
||||
name: { type: 'string' },
|
||||
key: { type: 'string' },
|
||||
},
|
||||
},
|
||||
},
|
||||
error: { type: 'string' },
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
import { defineLogicFunction } from 'twenty-sdk/define';
|
||||
import type { RoutePayload } from 'twenty-sdk/define';
|
||||
|
||||
import { LIST_LINEAR_WORKFLOW_STATES_ROUTE_UNIVERSAL_IDENTIFIER } from 'src/constants/universal-identifiers';
|
||||
import { listLinearWorkflowStatesHandler } from 'src/logic-functions/handlers/list-linear-workflow-states-handler';
|
||||
|
||||
const handler = async (event: RoutePayload) => {
|
||||
return listLinearWorkflowStatesHandler({
|
||||
teamId: event.queryStringParameters?.teamId,
|
||||
});
|
||||
};
|
||||
|
||||
export default defineLogicFunction({
|
||||
universalIdentifier: LIST_LINEAR_WORKFLOW_STATES_ROUTE_UNIVERSAL_IDENTIFIER,
|
||||
name: 'list-linear-workflow-states-route',
|
||||
timeoutSeconds: 15,
|
||||
handler,
|
||||
httpRouteTriggerSettings: {
|
||||
path: '/linear/workflow-states',
|
||||
httpMethod: 'GET',
|
||||
isAuthRequired: true,
|
||||
},
|
||||
});
|
||||
+8
@@ -2,4 +2,12 @@ export type CreateIssueInput = {
|
||||
teamId?: string;
|
||||
title?: string;
|
||||
description?: string;
|
||||
priority?: number;
|
||||
stateId?: string;
|
||||
assigneeId?: string;
|
||||
projectId?: string;
|
||||
estimate?: number;
|
||||
labelIds?: string[];
|
||||
cycleId?: string;
|
||||
dueDate?: string;
|
||||
};
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import { defineRole } from 'twenty-sdk/define';
|
||||
import { defineApplicationRole } from 'twenty-sdk/define';
|
||||
|
||||
import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from 'src/constants/universal-identifiers';
|
||||
|
||||
// The Linear logic functions never read workspace data — they only call
|
||||
// Linear's GraphQL API on behalf of the connected user.
|
||||
export default defineRole({
|
||||
export default defineApplicationRole({
|
||||
universalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
|
||||
label: 'Linear function role',
|
||||
description: 'No-op role for Linear logic functions',
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
"declaration": true,
|
||||
"outDir": "./dist",
|
||||
"rootDir": ".",
|
||||
"jsx": "react-jsx",
|
||||
"moduleResolution": "bundler",
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
@@ -17,7 +18,7 @@
|
||||
"strictBindCallApply": false,
|
||||
"target": "es2020",
|
||||
"module": "esnext",
|
||||
"lib": ["es2020"],
|
||||
"lib": ["es2020", "dom"],
|
||||
"skipLibCheck": true,
|
||||
"skipDefaultLibCheck": true,
|
||||
"resolveJsonModule": true,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1 +0,0 @@
|
||||
{"tags": ["scope:apps"]}
|
||||
@@ -0,0 +1,9 @@
|
||||
# twenty-claude-skills
|
||||
|
||||
Claude skills for working with Twenty.
|
||||
|
||||
Add skills under `skills/<skill-name>/SKILL.md`.
|
||||
|
||||
## Skills
|
||||
|
||||
- `twenty-record-presentation`: Retrieve and present Twenty CRM records as readable summaries or tables.
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"name": "twenty-claude-skills",
|
||||
"private": true,
|
||||
"version": "0.1.0",
|
||||
"description": "Claude skills for working with Twenty.",
|
||||
"license": "AGPL-3.0",
|
||||
"files": [
|
||||
"skills"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
---
|
||||
name: twenty-record-presentation
|
||||
description: "Retrieve and present Twenty CRM records as readable summaries or tables, using the connected Twenty MCP server to discover fields, fetch relevant data, format dates and values, build record links, and avoid raw API output."
|
||||
---
|
||||
|
||||
# Twenty Record Presentation
|
||||
|
||||
## Overview
|
||||
|
||||
Retrieve the Twenty records needed to answer the user's question, then present them as a useful answer, not as raw API output. Always translate technical fields, timestamps, IDs, and nested structures into readable summaries that help the user scan, compare, and act.
|
||||
|
||||
## Retrieval Workflow
|
||||
|
||||
Use the selected connected Twenty MCP server when it is available
|
||||
|
||||
- `get_tool_catalog` → `learn_tools` → `execute_tool`
|
||||
- Discover the relevant object, fields, filters, and sort options instead of guessing exact API names.
|
||||
- Retrieve only the fields needed for the answer, plus the fields needed for ordering or disambiguation.
|
||||
- For "latest", "most recent", or "recent" requests, include the relevant timestamp field used for sorting.
|
||||
- If the user asks for a broad list, apply a practical limit and state how many records are shown.
|
||||
- If required context is missing and cannot be discovered from the tools, ask one concise clarifying question.
|
||||
- If no Twenty MCP tools are available, say that no callable Twenty MCP server is available in the current thread and ask the user to connect or expose the intended workspace.
|
||||
|
||||
## Response Shape
|
||||
|
||||
Start with the answer or count, then show the records in the clearest compact shape:
|
||||
|
||||
- For one record, use a labeled block.
|
||||
- For 2 to 10 comparable records, use a Markdown table.
|
||||
- For larger sets, show the most relevant rows first, mention the total, and offer the next useful filter or page only when needed.
|
||||
- For nested records, summarize the important nested values instead of dumping JSON.
|
||||
- When comparing records across workspaces, prefer one combined table with a Workspace column if it improves scanning. Use separate sections only when each workspace needs different columns.
|
||||
|
||||
Use English labels and prose. Keep user-provided names, record values, emails, URLs, and proper nouns unchanged.
|
||||
|
||||
## Record Links
|
||||
|
||||
Link records back to their original Twenty context whenever the workspace origin and record identity are known.
|
||||
|
||||
- Build record links with the Twenty show-page path: `/object/:objectNameSingular/:objectRecordId`.
|
||||
- For absolute links, combine the workspace origin with that path, for example `https://example.twenty.com/object/person/record-id`.
|
||||
- Use `recordReferences` from MCP responses when available to get `objectNameSingular`, `recordId`, and `displayName`.
|
||||
- If `recordReferences` is missing, use the record's `id` and the object name from the tool that returned it.
|
||||
- Prefer linking the record display name in tables and summaries instead of adding a raw ID column.
|
||||
- When showing records from multiple workspaces, generate links with each record's own workspace origin.
|
||||
- If the workspace origin is unknown, do not invent a hostname. Add a compact Record column with the object name and record ID, or say that direct links need the workspace URL.
|
||||
|
||||
## Dates and Times
|
||||
|
||||
Never expose ISO/RFC3339 timestamps as the main date display.
|
||||
|
||||
- Parse common technical formats such as `2026-05-05T09:43:18.123Z`, `2026-05-05T09:43:18+02:00`, Unix seconds, and Unix milliseconds.
|
||||
- Convert instants with `Z` or an explicit offset to the user's timezone when known. If timezone is unknown, keep the source timezone or ask only when it changes the meaning.
|
||||
- Preserve date-only values as dates. Do not shift date-only values across timezones.
|
||||
- Display absolute dates. Use relative words such as "today", "yesterday", or "last week" only as a supplement when helpful.
|
||||
- Include the year unless it is truly redundant in a small same-year table.
|
||||
- Show seconds and milliseconds only when they matter for debugging, audit logs, or ordering events with near-identical times.
|
||||
|
||||
Examples, with user timezone Europe/Paris, UTC+2 in May:
|
||||
|
||||
- Timestamp: `2026-05-05T09:43:18.123Z` → May 5, 2026, 11:43 AM
|
||||
- Date-only value: `2026-05-05` → May 5, 2026
|
||||
|
||||
If the exact raw timestamp is relevant, put it after the readable value:
|
||||
|
||||
- Created: May 5, 2026, 11:43 AM (raw: `2026-05-05T09:43:18.123Z`)
|
||||
|
||||
## Field Labels
|
||||
|
||||
Convert raw field names into user-facing labels:
|
||||
|
||||
- `createdAt` → Created
|
||||
- `updatedAt` → Last updated
|
||||
- `deletedAt` → Deleted
|
||||
- `createdBy` → Created by
|
||||
- `workspaceMemberId` → Workspace member
|
||||
- `opportunityStage` → Opportunity stage
|
||||
|
||||
Prefer the label users see in Twenty when it is available from metadata. Otherwise, split camelCase, snake_case, and kebab-case into normal words.
|
||||
|
||||
## Value Formatting
|
||||
|
||||
Format values by meaning:
|
||||
|
||||
- **Empty or null**: Not set, or omit if the field is irrelevant.
|
||||
- **Booleans**: Yes / No.
|
||||
- **Money**: include currency and grouping, for example EUR 12,450 or USD 12,450 based on the record currency.
|
||||
- **Percentages**: use `%`, round only enough to stay meaningful.
|
||||
- **URLs and emails**: make them clickable Markdown links when useful.
|
||||
- **IDs and UUIDs**: hide by default unless the user asks for identifiers, deduplication, debugging, or exact references.
|
||||
- **Arrays**: show the count and the most important names, not the full serialized array.
|
||||
|
||||
## Record Ordering
|
||||
|
||||
When the user asks for "latest", "recent", or "last records":
|
||||
|
||||
- State which date field was used when it is not obvious, for example *sorted by Last updated*.
|
||||
- Prefer `updatedAt` for "recent activity" and `createdAt` for "newest records" unless the user's wording or object semantics points to another date.
|
||||
- Display the chosen date column in readable form.
|
||||
- If multiple records share the same date, keep a deterministic secondary order such as name or ID.
|
||||
|
||||
## Table Alignment
|
||||
|
||||
Make tables easy to scan before making them visually decorative.
|
||||
|
||||
- Use Markdown alignment markers intentionally: text columns left-aligned (`:---`), numeric money/count columns right-aligned (`---:`), and short status columns centered only when that actually improves scanning (`:---:`).
|
||||
- Keep record names on a stable left edge. If rows have favicons, use a dedicated narrow Icon column followed by a linked record-name column.
|
||||
- If the table is compact and the image is known to be consistently small, it is acceptable to put ` [Name](record-url)` in one cell. Do not also add emoji or extra symbols before the name.
|
||||
- Keep fixed-format fields such as Created, Updated, Amount, and Source to the right of variable-width fields such as Name, Company, Person, and Domain.
|
||||
- Use a consistent date format within a table so rows line up visually, for example *May 5, 2026, 11:43 AM* or *May 5, 11:43*.
|
||||
- Prefer natural links over extra link columns: link the record name to Twenty, and link the domain or email only when that external destination is useful.
|
||||
- Avoid raw ID columns in normal user-facing tables. IDs are long, visually dominant, and destroy alignment unless the user asks for them.
|
||||
|
||||
## Markdown Patterns
|
||||
|
||||
### Compact table
|
||||
|
||||
Use a compact table for comparable records:
|
||||
|
||||
```markdown
|
||||
I found 5 recent opportunities, sorted by last updated date.
|
||||
|
||||
| Name | Stage | Amount | Last updated |
|
||||
| :--- | :--- | ---: | :--- |
|
||||
| [Acme renewal](https://example.twenty.com/object/opportunity/record-id-1) | Negotiation | EUR 12,450 | May 5, 2026, 11:43 AM |
|
||||
| [Globex expansion](https://example.twenty.com/object/opportunity/record-id-2) | Discovery | EUR 8,000 | May 4, 2026, 4:10 PM |
|
||||
```
|
||||
|
||||
### Labeled block
|
||||
|
||||
Use a labeled block for one important record:
|
||||
|
||||
```markdown
|
||||
**[Acme renewal](https://example.twenty.com/object/opportunity/record-id-1)**
|
||||
|
||||
- Stage: Negotiation
|
||||
- Amount: EUR 12,450
|
||||
- Next action: Not set
|
||||
- Last updated: May 5, 2026, 11:43 AM
|
||||
```
|
||||
|
||||
## Raw Data Exceptions
|
||||
|
||||
Show raw JSON, raw timestamps, internal IDs, or full nested objects only when the user asks for debugging, export, exact API payloads, schema inspection, or reproducible commands. Even then, put a readable summary before the raw block.
|
||||
|
||||
Example:
|
||||
|
||||
> Acme renewal — Negotiation stage, EUR 12,450, last updated May 5, 2026, 11:43 AM. Full payload below:
|
||||
>
|
||||
> ```json
|
||||
> {
|
||||
> "id": "record-id-1",
|
||||
> "name": "Acme renewal",
|
||||
> "stage": "NEGOTIATION",
|
||||
> "amountMicros": "12450000000",
|
||||
> "currencyCode": "EUR",
|
||||
> "updatedAt": "2026-05-05T09:43:18.123Z"
|
||||
> }
|
||||
> ```
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "twenty-client-sdk",
|
||||
"version": "2.3.0",
|
||||
"version": "2.4.0",
|
||||
"sideEffects": false,
|
||||
"license": "AGPL-3.0",
|
||||
"scripts": {
|
||||
|
||||
@@ -932,6 +932,7 @@ type Workspace {
|
||||
isMicrosoftAuthEnabled: Boolean!
|
||||
isMicrosoftAuthBypassEnabled: Boolean!
|
||||
isCustomDomainEnabled: Boolean!
|
||||
isInternalMessagesImportEnabled: Boolean!
|
||||
editableProfileFields: [String!]
|
||||
defaultRole: Role
|
||||
fastModel: String!
|
||||
@@ -1484,6 +1485,7 @@ enum BillingUsageType {
|
||||
"""The different billing products available"""
|
||||
enum BillingProductKey {
|
||||
BASE_PRODUCT
|
||||
RESOURCE_CREDIT
|
||||
WORKFLOW_NODE_EXECUTION
|
||||
}
|
||||
|
||||
@@ -1492,6 +1494,7 @@ type BillingPriceLicensed {
|
||||
unitAmount: Float!
|
||||
stripePriceId: String!
|
||||
priceUsageType: BillingUsageType!
|
||||
creditAmount: Float
|
||||
}
|
||||
|
||||
enum SubscriptionInterval {
|
||||
@@ -1590,7 +1593,8 @@ type BillingMeteredProductUsage {
|
||||
|
||||
type BillingPlan {
|
||||
planKey: BillingPlanKey!
|
||||
licensedProducts: [BillingLicensedProduct!]!
|
||||
baseProducts: [BillingLicensedProduct!]!
|
||||
resourceCreditProducts: [BillingLicensedProduct!]!
|
||||
meteredProducts: [BillingMeteredProduct!]!
|
||||
}
|
||||
|
||||
@@ -1744,16 +1748,14 @@ type FeatureFlag {
|
||||
enum FeatureFlagKey {
|
||||
IS_UNIQUE_INDEXES_ENABLED
|
||||
IS_JSON_FILTER_ENABLED
|
||||
IS_COMMAND_MENU_ITEM_ENABLED
|
||||
IS_MARKETPLACE_SETTING_TAB_VISIBLE
|
||||
IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED
|
||||
IS_PUBLIC_DOMAIN_ENABLED
|
||||
IS_EMAILING_DOMAIN_ENABLED
|
||||
IS_EMAIL_GROUP_ENABLED
|
||||
IS_JUNCTION_RELATIONS_ENABLED
|
||||
IS_CONNECTED_ACCOUNT_MIGRATED
|
||||
IS_RICH_TEXT_V1_MIGRATED
|
||||
IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED
|
||||
IS_DATASOURCE_MIGRATED
|
||||
IS_BILLING_V2_ENABLED
|
||||
}
|
||||
|
||||
type WorkspaceUrls {
|
||||
@@ -1922,6 +1924,7 @@ type ClientConfig {
|
||||
isGoogleCalendarEnabled: Boolean!
|
||||
isConfigVariablesInDbEnabled: Boolean!
|
||||
isImapSmtpCaldavEnabled: Boolean!
|
||||
isEmailGroupEnabled: Boolean!
|
||||
allowRequestsToTwentyIcons: Boolean!
|
||||
calendarBookingPageId: String
|
||||
isCloudflareIntegrationEnabled: Boolean!
|
||||
@@ -1964,74 +1967,16 @@ type RotateClientSecret {
|
||||
clientSecret: String!
|
||||
}
|
||||
|
||||
type ResendEmailVerificationToken {
|
||||
success: Boolean!
|
||||
}
|
||||
|
||||
type DeleteSso {
|
||||
identityProviderId: UUID!
|
||||
}
|
||||
|
||||
type EditSso {
|
||||
type ApplicationRegistrationVariableDTO {
|
||||
id: UUID!
|
||||
type: IdentityProviderType!
|
||||
issuer: String!
|
||||
name: String!
|
||||
status: SSOIdentityProviderStatus!
|
||||
}
|
||||
|
||||
type WorkspaceNameAndId {
|
||||
displayName: String
|
||||
id: UUID!
|
||||
}
|
||||
|
||||
type FindAvailableSSOIDP {
|
||||
type: IdentityProviderType!
|
||||
id: UUID!
|
||||
issuer: String!
|
||||
name: String!
|
||||
status: SSOIdentityProviderStatus!
|
||||
workspace: WorkspaceNameAndId!
|
||||
}
|
||||
|
||||
type SetupSso {
|
||||
id: UUID!
|
||||
type: IdentityProviderType!
|
||||
issuer: String!
|
||||
name: String!
|
||||
status: SSOIdentityProviderStatus!
|
||||
}
|
||||
|
||||
type SSOConnection {
|
||||
type: IdentityProviderType!
|
||||
id: UUID!
|
||||
issuer: String!
|
||||
name: String!
|
||||
status: SSOIdentityProviderStatus!
|
||||
}
|
||||
|
||||
type AvailableWorkspace {
|
||||
id: UUID!
|
||||
displayName: String
|
||||
loginToken: String
|
||||
personalInviteToken: String
|
||||
inviteHash: String
|
||||
workspaceUrls: WorkspaceUrls!
|
||||
logo: String
|
||||
sso: [SSOConnection!]!
|
||||
}
|
||||
|
||||
type AvailableWorkspaces {
|
||||
availableWorkspacesForSignIn: [AvailableWorkspace!]!
|
||||
availableWorkspacesForSignUp: [AvailableWorkspace!]!
|
||||
}
|
||||
|
||||
type DeletedWorkspaceMember {
|
||||
id: UUID!
|
||||
name: FullName!
|
||||
userEmail: String!
|
||||
avatarUrl: String
|
||||
userWorkspaceId: UUID
|
||||
key: String!
|
||||
value: String
|
||||
description: String!
|
||||
isSecret: Boolean!
|
||||
isRequired: Boolean!
|
||||
isFilled: Boolean!
|
||||
createdAt: DateTime!
|
||||
updatedAt: DateTime!
|
||||
}
|
||||
|
||||
type Relation {
|
||||
@@ -2155,6 +2100,76 @@ type FieldConnection {
|
||||
edges: [FieldEdge!]!
|
||||
}
|
||||
|
||||
type ResendEmailVerificationToken {
|
||||
success: Boolean!
|
||||
}
|
||||
|
||||
type DeleteSso {
|
||||
identityProviderId: UUID!
|
||||
}
|
||||
|
||||
type EditSso {
|
||||
id: UUID!
|
||||
type: IdentityProviderType!
|
||||
issuer: String!
|
||||
name: String!
|
||||
status: SSOIdentityProviderStatus!
|
||||
}
|
||||
|
||||
type WorkspaceNameAndId {
|
||||
displayName: String
|
||||
id: UUID!
|
||||
}
|
||||
|
||||
type FindAvailableSSOIDP {
|
||||
type: IdentityProviderType!
|
||||
id: UUID!
|
||||
issuer: String!
|
||||
name: String!
|
||||
status: SSOIdentityProviderStatus!
|
||||
workspace: WorkspaceNameAndId!
|
||||
}
|
||||
|
||||
type SetupSso {
|
||||
id: UUID!
|
||||
type: IdentityProviderType!
|
||||
issuer: String!
|
||||
name: String!
|
||||
status: SSOIdentityProviderStatus!
|
||||
}
|
||||
|
||||
type SSOConnection {
|
||||
type: IdentityProviderType!
|
||||
id: UUID!
|
||||
issuer: String!
|
||||
name: String!
|
||||
status: SSOIdentityProviderStatus!
|
||||
}
|
||||
|
||||
type AvailableWorkspace {
|
||||
id: UUID!
|
||||
displayName: String
|
||||
loginToken: String
|
||||
personalInviteToken: String
|
||||
inviteHash: String
|
||||
workspaceUrls: WorkspaceUrls!
|
||||
logo: String
|
||||
sso: [SSOConnection!]!
|
||||
}
|
||||
|
||||
type AvailableWorkspaces {
|
||||
availableWorkspacesForSignIn: [AvailableWorkspace!]!
|
||||
availableWorkspacesForSignUp: [AvailableWorkspace!]!
|
||||
}
|
||||
|
||||
type DeletedWorkspaceMember {
|
||||
id: UUID!
|
||||
name: FullName!
|
||||
userEmail: String!
|
||||
avatarUrl: String
|
||||
userWorkspaceId: UUID
|
||||
}
|
||||
|
||||
type BillingEntitlement {
|
||||
key: BillingEntitlementKey!
|
||||
value: Boolean!
|
||||
@@ -2350,6 +2365,7 @@ type PublicDomain {
|
||||
id: UUID!
|
||||
domain: String!
|
||||
isValidated: Boolean!
|
||||
applicationId: UUID
|
||||
createdAt: DateTime!
|
||||
}
|
||||
|
||||
@@ -2773,6 +2789,7 @@ type MessageChannel {
|
||||
connectedAccountId: UUID!
|
||||
createdAt: DateTime!
|
||||
updatedAt: DateTime!
|
||||
connectedAccount: ConnectedAccountPublicDTO
|
||||
}
|
||||
|
||||
enum MessageChannelVisibility {
|
||||
@@ -2784,6 +2801,7 @@ enum MessageChannelVisibility {
|
||||
enum MessageChannelType {
|
||||
EMAIL
|
||||
SMS
|
||||
EMAIL_GROUP
|
||||
}
|
||||
|
||||
enum MessageChannelContactAutoCreationPolicy {
|
||||
@@ -2822,6 +2840,11 @@ enum MessageChannelSyncStage {
|
||||
FAILED
|
||||
}
|
||||
|
||||
type CreateEmailGroupChannelOutput {
|
||||
messageChannel: MessageChannel!
|
||||
forwardingAddress: String!
|
||||
}
|
||||
|
||||
type MessageFolder {
|
||||
id: UUID!
|
||||
name: String
|
||||
@@ -3023,7 +3046,7 @@ type Query {
|
||||
findManyApplicationRegistrations: [ApplicationRegistration!]!
|
||||
findOneApplicationRegistration(id: String!): ApplicationRegistration!
|
||||
findApplicationRegistrationStats(id: String!): ApplicationRegistrationStats!
|
||||
findApplicationRegistrationVariables(applicationRegistrationId: String!): [ApplicationRegistrationVariable!]!
|
||||
findApplicationRegistrationVariables(applicationRegistrationId: String!): [ApplicationRegistrationVariableDTO!]!
|
||||
applicationRegistrationTarballUrl(id: String!): String
|
||||
currentUser: User!
|
||||
currentWorkspace: Workspace!
|
||||
@@ -3239,6 +3262,8 @@ type Mutation {
|
||||
updateMessageFolder(input: UpdateMessageFolderInput!): MessageFolder!
|
||||
updateMessageFolders(input: UpdateMessageFoldersInput!): [MessageFolder!]!
|
||||
updateMessageChannel(input: UpdateMessageChannelInput!): MessageChannel!
|
||||
createEmailGroupChannel(input: CreateEmailGroupChannelInput!): CreateEmailGroupChannelOutput!
|
||||
deleteEmailGroupChannel(id: UUID!): MessageChannel!
|
||||
deleteConnectedAccount(id: UUID!): ConnectedAccountDTO!
|
||||
updateCalendarChannel(input: UpdateCalendarChannelInput!): CalendarChannel!
|
||||
createWebhook(input: CreateWebhookInput!): Webhook!
|
||||
@@ -3312,7 +3337,8 @@ type Mutation {
|
||||
updateLabPublicFeatureFlag(input: UpdateLabPublicFeatureFlagInput!): FeatureFlag!
|
||||
enablePostgresProxy: PostgresCredentials!
|
||||
disablePostgresProxy: PostgresCredentials!
|
||||
createPublicDomain(domain: String!): PublicDomain!
|
||||
createPublicDomain(domain: String!, applicationId: String): PublicDomain!
|
||||
updatePublicDomain(domain: String!, applicationId: String): PublicDomain!
|
||||
deletePublicDomain(domain: String!): Boolean!
|
||||
checkPublicDomainValidRecords(domain: String!): DomainValidRecords
|
||||
createEmailingDomain(domain: String!, driver: EmailingDomainDriver!): EmailingDomain!
|
||||
@@ -4167,6 +4193,10 @@ input UpdateMessageChannelInputUpdates {
|
||||
excludeGroupEmails: Boolean
|
||||
}
|
||||
|
||||
input CreateEmailGroupChannelInput {
|
||||
handle: String!
|
||||
}
|
||||
|
||||
input UpdateCalendarChannelInput {
|
||||
id: UUID!
|
||||
update: UpdateCalendarChannelInputUpdates!
|
||||
@@ -4297,6 +4327,7 @@ input UpdateWorkspaceInput {
|
||||
editableProfileFields: [String!]
|
||||
enabledAiModelIds: [String!]
|
||||
useRecommendedModels: Boolean
|
||||
isInternalMessagesImportEnabled: Boolean
|
||||
}
|
||||
|
||||
input WorkspaceMigrationInput {
|
||||
|
||||
@@ -652,6 +652,7 @@ export interface Workspace {
|
||||
isMicrosoftAuthEnabled: Scalars['Boolean']
|
||||
isMicrosoftAuthBypassEnabled: Scalars['Boolean']
|
||||
isCustomDomainEnabled: Scalars['Boolean']
|
||||
isInternalMessagesImportEnabled: Scalars['Boolean']
|
||||
editableProfileFields?: Scalars['String'][]
|
||||
defaultRole?: Role
|
||||
fastModel: Scalars['String']
|
||||
@@ -1145,13 +1146,14 @@ export type BillingUsageType = 'METERED' | 'LICENSED'
|
||||
|
||||
|
||||
/** The different billing products available */
|
||||
export type BillingProductKey = 'BASE_PRODUCT' | 'WORKFLOW_NODE_EXECUTION'
|
||||
export type BillingProductKey = 'BASE_PRODUCT' | 'RESOURCE_CREDIT' | 'WORKFLOW_NODE_EXECUTION'
|
||||
|
||||
export interface BillingPriceLicensed {
|
||||
recurringInterval: SubscriptionInterval
|
||||
unitAmount: Scalars['Float']
|
||||
stripePriceId: Scalars['String']
|
||||
priceUsageType: BillingUsageType
|
||||
creditAmount?: Scalars['Float']
|
||||
__typename: 'BillingPriceLicensed'
|
||||
}
|
||||
|
||||
@@ -1244,7 +1246,8 @@ export interface BillingMeteredProductUsage {
|
||||
|
||||
export interface BillingPlan {
|
||||
planKey: BillingPlanKey
|
||||
licensedProducts: BillingLicensedProduct[]
|
||||
baseProducts: BillingLicensedProduct[]
|
||||
resourceCreditProducts: BillingLicensedProduct[]
|
||||
meteredProducts: BillingMeteredProduct[]
|
||||
__typename: 'BillingPlan'
|
||||
}
|
||||
@@ -1386,7 +1389,7 @@ export interface FeatureFlag {
|
||||
__typename: 'FeatureFlag'
|
||||
}
|
||||
|
||||
export type FeatureFlagKey = 'IS_UNIQUE_INDEXES_ENABLED' | 'IS_JSON_FILTER_ENABLED' | 'IS_COMMAND_MENU_ITEM_ENABLED' | 'IS_MARKETPLACE_SETTING_TAB_VISIBLE' | 'IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED' | 'IS_PUBLIC_DOMAIN_ENABLED' | 'IS_EMAILING_DOMAIN_ENABLED' | 'IS_JUNCTION_RELATIONS_ENABLED' | 'IS_CONNECTED_ACCOUNT_MIGRATED' | 'IS_RICH_TEXT_V1_MIGRATED' | 'IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED' | 'IS_DATASOURCE_MIGRATED'
|
||||
export type FeatureFlagKey = 'IS_UNIQUE_INDEXES_ENABLED' | 'IS_JSON_FILTER_ENABLED' | 'IS_MARKETPLACE_SETTING_TAB_VISIBLE' | 'IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED' | 'IS_PUBLIC_DOMAIN_ENABLED' | 'IS_EMAILING_DOMAIN_ENABLED' | 'IS_EMAIL_GROUP_ENABLED' | 'IS_JUNCTION_RELATIONS_ENABLED' | 'IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED' | 'IS_BILLING_V2_ENABLED'
|
||||
|
||||
export interface WorkspaceUrls {
|
||||
customUrl?: Scalars['String']
|
||||
@@ -1552,6 +1555,7 @@ export interface ClientConfig {
|
||||
isGoogleCalendarEnabled: Scalars['Boolean']
|
||||
isConfigVariablesInDbEnabled: Scalars['Boolean']
|
||||
isImapSmtpCaldavEnabled: Scalars['Boolean']
|
||||
isEmailGroupEnabled: Scalars['Boolean']
|
||||
allowRequestsToTwentyIcons: Scalars['Boolean']
|
||||
calendarBookingPageId?: Scalars['String']
|
||||
isCloudflareIntegrationEnabled: Scalars['Boolean']
|
||||
@@ -1601,84 +1605,17 @@ export interface RotateClientSecret {
|
||||
__typename: 'RotateClientSecret'
|
||||
}
|
||||
|
||||
export interface ResendEmailVerificationToken {
|
||||
success: Scalars['Boolean']
|
||||
__typename: 'ResendEmailVerificationToken'
|
||||
}
|
||||
|
||||
export interface DeleteSso {
|
||||
identityProviderId: Scalars['UUID']
|
||||
__typename: 'DeleteSso'
|
||||
}
|
||||
|
||||
export interface EditSso {
|
||||
export interface ApplicationRegistrationVariableDTO {
|
||||
id: Scalars['UUID']
|
||||
type: IdentityProviderType
|
||||
issuer: Scalars['String']
|
||||
name: Scalars['String']
|
||||
status: SSOIdentityProviderStatus
|
||||
__typename: 'EditSso'
|
||||
}
|
||||
|
||||
export interface WorkspaceNameAndId {
|
||||
displayName?: Scalars['String']
|
||||
id: Scalars['UUID']
|
||||
__typename: 'WorkspaceNameAndId'
|
||||
}
|
||||
|
||||
export interface FindAvailableSSOIDP {
|
||||
type: IdentityProviderType
|
||||
id: Scalars['UUID']
|
||||
issuer: Scalars['String']
|
||||
name: Scalars['String']
|
||||
status: SSOIdentityProviderStatus
|
||||
workspace: WorkspaceNameAndId
|
||||
__typename: 'FindAvailableSSOIDP'
|
||||
}
|
||||
|
||||
export interface SetupSso {
|
||||
id: Scalars['UUID']
|
||||
type: IdentityProviderType
|
||||
issuer: Scalars['String']
|
||||
name: Scalars['String']
|
||||
status: SSOIdentityProviderStatus
|
||||
__typename: 'SetupSso'
|
||||
}
|
||||
|
||||
export interface SSOConnection {
|
||||
type: IdentityProviderType
|
||||
id: Scalars['UUID']
|
||||
issuer: Scalars['String']
|
||||
name: Scalars['String']
|
||||
status: SSOIdentityProviderStatus
|
||||
__typename: 'SSOConnection'
|
||||
}
|
||||
|
||||
export interface AvailableWorkspace {
|
||||
id: Scalars['UUID']
|
||||
displayName?: Scalars['String']
|
||||
loginToken?: Scalars['String']
|
||||
personalInviteToken?: Scalars['String']
|
||||
inviteHash?: Scalars['String']
|
||||
workspaceUrls: WorkspaceUrls
|
||||
logo?: Scalars['String']
|
||||
sso: SSOConnection[]
|
||||
__typename: 'AvailableWorkspace'
|
||||
}
|
||||
|
||||
export interface AvailableWorkspaces {
|
||||
availableWorkspacesForSignIn: AvailableWorkspace[]
|
||||
availableWorkspacesForSignUp: AvailableWorkspace[]
|
||||
__typename: 'AvailableWorkspaces'
|
||||
}
|
||||
|
||||
export interface DeletedWorkspaceMember {
|
||||
id: Scalars['UUID']
|
||||
name: FullName
|
||||
userEmail: Scalars['String']
|
||||
avatarUrl?: Scalars['String']
|
||||
userWorkspaceId?: Scalars['UUID']
|
||||
__typename: 'DeletedWorkspaceMember'
|
||||
key: Scalars['String']
|
||||
value?: Scalars['String']
|
||||
description: Scalars['String']
|
||||
isSecret: Scalars['Boolean']
|
||||
isRequired: Scalars['Boolean']
|
||||
isFilled: Scalars['Boolean']
|
||||
createdAt: Scalars['DateTime']
|
||||
updatedAt: Scalars['DateTime']
|
||||
__typename: 'ApplicationRegistrationVariableDTO'
|
||||
}
|
||||
|
||||
export interface Relation {
|
||||
@@ -1800,6 +1737,86 @@ export interface FieldConnection {
|
||||
__typename: 'FieldConnection'
|
||||
}
|
||||
|
||||
export interface ResendEmailVerificationToken {
|
||||
success: Scalars['Boolean']
|
||||
__typename: 'ResendEmailVerificationToken'
|
||||
}
|
||||
|
||||
export interface DeleteSso {
|
||||
identityProviderId: Scalars['UUID']
|
||||
__typename: 'DeleteSso'
|
||||
}
|
||||
|
||||
export interface EditSso {
|
||||
id: Scalars['UUID']
|
||||
type: IdentityProviderType
|
||||
issuer: Scalars['String']
|
||||
name: Scalars['String']
|
||||
status: SSOIdentityProviderStatus
|
||||
__typename: 'EditSso'
|
||||
}
|
||||
|
||||
export interface WorkspaceNameAndId {
|
||||
displayName?: Scalars['String']
|
||||
id: Scalars['UUID']
|
||||
__typename: 'WorkspaceNameAndId'
|
||||
}
|
||||
|
||||
export interface FindAvailableSSOIDP {
|
||||
type: IdentityProviderType
|
||||
id: Scalars['UUID']
|
||||
issuer: Scalars['String']
|
||||
name: Scalars['String']
|
||||
status: SSOIdentityProviderStatus
|
||||
workspace: WorkspaceNameAndId
|
||||
__typename: 'FindAvailableSSOIDP'
|
||||
}
|
||||
|
||||
export interface SetupSso {
|
||||
id: Scalars['UUID']
|
||||
type: IdentityProviderType
|
||||
issuer: Scalars['String']
|
||||
name: Scalars['String']
|
||||
status: SSOIdentityProviderStatus
|
||||
__typename: 'SetupSso'
|
||||
}
|
||||
|
||||
export interface SSOConnection {
|
||||
type: IdentityProviderType
|
||||
id: Scalars['UUID']
|
||||
issuer: Scalars['String']
|
||||
name: Scalars['String']
|
||||
status: SSOIdentityProviderStatus
|
||||
__typename: 'SSOConnection'
|
||||
}
|
||||
|
||||
export interface AvailableWorkspace {
|
||||
id: Scalars['UUID']
|
||||
displayName?: Scalars['String']
|
||||
loginToken?: Scalars['String']
|
||||
personalInviteToken?: Scalars['String']
|
||||
inviteHash?: Scalars['String']
|
||||
workspaceUrls: WorkspaceUrls
|
||||
logo?: Scalars['String']
|
||||
sso: SSOConnection[]
|
||||
__typename: 'AvailableWorkspace'
|
||||
}
|
||||
|
||||
export interface AvailableWorkspaces {
|
||||
availableWorkspacesForSignIn: AvailableWorkspace[]
|
||||
availableWorkspacesForSignUp: AvailableWorkspace[]
|
||||
__typename: 'AvailableWorkspaces'
|
||||
}
|
||||
|
||||
export interface DeletedWorkspaceMember {
|
||||
id: Scalars['UUID']
|
||||
name: FullName
|
||||
userEmail: Scalars['String']
|
||||
avatarUrl?: Scalars['String']
|
||||
userWorkspaceId?: Scalars['UUID']
|
||||
__typename: 'DeletedWorkspaceMember'
|
||||
}
|
||||
|
||||
export interface BillingEntitlement {
|
||||
key: BillingEntitlementKey
|
||||
value: Scalars['Boolean']
|
||||
@@ -2023,6 +2040,7 @@ export interface PublicDomain {
|
||||
id: Scalars['UUID']
|
||||
domain: Scalars['String']
|
||||
isValidated: Scalars['Boolean']
|
||||
applicationId?: Scalars['UUID']
|
||||
createdAt: Scalars['DateTime']
|
||||
__typename: 'PublicDomain'
|
||||
}
|
||||
@@ -2457,12 +2475,13 @@ export interface MessageChannel {
|
||||
connectedAccountId: Scalars['UUID']
|
||||
createdAt: Scalars['DateTime']
|
||||
updatedAt: Scalars['DateTime']
|
||||
connectedAccount?: ConnectedAccountPublicDTO
|
||||
__typename: 'MessageChannel'
|
||||
}
|
||||
|
||||
export type MessageChannelVisibility = 'METADATA' | 'SUBJECT' | 'SHARE_EVERYTHING'
|
||||
|
||||
export type MessageChannelType = 'EMAIL' | 'SMS'
|
||||
export type MessageChannelType = 'EMAIL' | 'SMS' | 'EMAIL_GROUP'
|
||||
|
||||
export type MessageChannelContactAutoCreationPolicy = 'SENT_AND_RECEIVED' | 'SENT' | 'NONE'
|
||||
|
||||
@@ -2474,6 +2493,12 @@ export type MessageChannelSyncStatus = 'NOT_SYNCED' | 'ONGOING' | 'ACTIVE' | 'FA
|
||||
|
||||
export type MessageChannelSyncStage = 'PENDING_CONFIGURATION' | 'MESSAGE_LIST_FETCH_PENDING' | 'MESSAGE_LIST_FETCH_SCHEDULED' | 'MESSAGE_LIST_FETCH_ONGOING' | 'MESSAGES_IMPORT_PENDING' | 'MESSAGES_IMPORT_SCHEDULED' | 'MESSAGES_IMPORT_ONGOING' | 'FAILED'
|
||||
|
||||
export interface CreateEmailGroupChannelOutput {
|
||||
messageChannel: MessageChannel
|
||||
forwardingAddress: Scalars['String']
|
||||
__typename: 'CreateEmailGroupChannelOutput'
|
||||
}
|
||||
|
||||
export interface MessageFolder {
|
||||
id: Scalars['UUID']
|
||||
name?: Scalars['String']
|
||||
@@ -2621,7 +2646,7 @@ export interface Query {
|
||||
findManyApplicationRegistrations: ApplicationRegistration[]
|
||||
findOneApplicationRegistration: ApplicationRegistration
|
||||
findApplicationRegistrationStats: ApplicationRegistrationStats
|
||||
findApplicationRegistrationVariables: ApplicationRegistrationVariable[]
|
||||
findApplicationRegistrationVariables: ApplicationRegistrationVariableDTO[]
|
||||
applicationRegistrationTarballUrl?: Scalars['String']
|
||||
currentUser: User
|
||||
currentWorkspace: Workspace
|
||||
@@ -2770,6 +2795,8 @@ export interface Mutation {
|
||||
updateMessageFolder: MessageFolder
|
||||
updateMessageFolders: MessageFolder[]
|
||||
updateMessageChannel: MessageChannel
|
||||
createEmailGroupChannel: CreateEmailGroupChannelOutput
|
||||
deleteEmailGroupChannel: MessageChannel
|
||||
deleteConnectedAccount: ConnectedAccountDTO
|
||||
updateCalendarChannel: CalendarChannel
|
||||
createWebhook: Webhook
|
||||
@@ -2844,6 +2871,7 @@ export interface Mutation {
|
||||
enablePostgresProxy: PostgresCredentials
|
||||
disablePostgresProxy: PostgresCredentials
|
||||
createPublicDomain: PublicDomain
|
||||
updatePublicDomain: PublicDomain
|
||||
deletePublicDomain: Scalars['Boolean']
|
||||
checkPublicDomainValidRecords?: DomainValidRecords
|
||||
createEmailingDomain: EmailingDomain
|
||||
@@ -3552,6 +3580,7 @@ export interface WorkspaceGenqlSelection{
|
||||
isMicrosoftAuthEnabled?: boolean | number
|
||||
isMicrosoftAuthBypassEnabled?: boolean | number
|
||||
isCustomDomainEnabled?: boolean | number
|
||||
isInternalMessagesImportEnabled?: boolean | number
|
||||
editableProfileFields?: boolean | number
|
||||
defaultRole?: RoleGenqlSelection
|
||||
fastModel?: boolean | number
|
||||
@@ -4079,6 +4108,7 @@ export interface BillingPriceLicensedGenqlSelection{
|
||||
unitAmount?: boolean | number
|
||||
stripePriceId?: boolean | number
|
||||
priceUsageType?: boolean | number
|
||||
creditAmount?: boolean | number
|
||||
__typename?: boolean | number
|
||||
__scalar?: boolean | number
|
||||
}
|
||||
@@ -4177,7 +4207,8 @@ export interface BillingMeteredProductUsageGenqlSelection{
|
||||
|
||||
export interface BillingPlanGenqlSelection{
|
||||
planKey?: boolean | number
|
||||
licensedProducts?: BillingLicensedProductGenqlSelection
|
||||
baseProducts?: BillingLicensedProductGenqlSelection
|
||||
resourceCreditProducts?: BillingLicensedProductGenqlSelection
|
||||
meteredProducts?: BillingMeteredProductGenqlSelection
|
||||
__typename?: boolean | number
|
||||
__scalar?: boolean | number
|
||||
@@ -4491,6 +4522,7 @@ export interface ClientConfigGenqlSelection{
|
||||
isGoogleCalendarEnabled?: boolean | number
|
||||
isConfigVariablesInDbEnabled?: boolean | number
|
||||
isImapSmtpCaldavEnabled?: boolean | number
|
||||
isEmailGroupEnabled?: boolean | number
|
||||
allowRequestsToTwentyIcons?: boolean | number
|
||||
calendarBookingPageId?: boolean | number
|
||||
isCloudflareIntegrationEnabled?: boolean | number
|
||||
@@ -4547,92 +4579,16 @@ export interface RotateClientSecretGenqlSelection{
|
||||
__scalar?: boolean | number
|
||||
}
|
||||
|
||||
export interface ResendEmailVerificationTokenGenqlSelection{
|
||||
success?: boolean | number
|
||||
__typename?: boolean | number
|
||||
__scalar?: boolean | number
|
||||
}
|
||||
|
||||
export interface DeleteSsoGenqlSelection{
|
||||
identityProviderId?: boolean | number
|
||||
__typename?: boolean | number
|
||||
__scalar?: boolean | number
|
||||
}
|
||||
|
||||
export interface EditSsoGenqlSelection{
|
||||
export interface ApplicationRegistrationVariableDTOGenqlSelection{
|
||||
id?: boolean | number
|
||||
type?: boolean | number
|
||||
issuer?: boolean | number
|
||||
name?: boolean | number
|
||||
status?: boolean | number
|
||||
__typename?: boolean | number
|
||||
__scalar?: boolean | number
|
||||
}
|
||||
|
||||
export interface WorkspaceNameAndIdGenqlSelection{
|
||||
displayName?: boolean | number
|
||||
id?: boolean | number
|
||||
__typename?: boolean | number
|
||||
__scalar?: boolean | number
|
||||
}
|
||||
|
||||
export interface FindAvailableSSOIDPGenqlSelection{
|
||||
type?: boolean | number
|
||||
id?: boolean | number
|
||||
issuer?: boolean | number
|
||||
name?: boolean | number
|
||||
status?: boolean | number
|
||||
workspace?: WorkspaceNameAndIdGenqlSelection
|
||||
__typename?: boolean | number
|
||||
__scalar?: boolean | number
|
||||
}
|
||||
|
||||
export interface SetupSsoGenqlSelection{
|
||||
id?: boolean | number
|
||||
type?: boolean | number
|
||||
issuer?: boolean | number
|
||||
name?: boolean | number
|
||||
status?: boolean | number
|
||||
__typename?: boolean | number
|
||||
__scalar?: boolean | number
|
||||
}
|
||||
|
||||
export interface SSOConnectionGenqlSelection{
|
||||
type?: boolean | number
|
||||
id?: boolean | number
|
||||
issuer?: boolean | number
|
||||
name?: boolean | number
|
||||
status?: boolean | number
|
||||
__typename?: boolean | number
|
||||
__scalar?: boolean | number
|
||||
}
|
||||
|
||||
export interface AvailableWorkspaceGenqlSelection{
|
||||
id?: boolean | number
|
||||
displayName?: boolean | number
|
||||
loginToken?: boolean | number
|
||||
personalInviteToken?: boolean | number
|
||||
inviteHash?: boolean | number
|
||||
workspaceUrls?: WorkspaceUrlsGenqlSelection
|
||||
logo?: boolean | number
|
||||
sso?: SSOConnectionGenqlSelection
|
||||
__typename?: boolean | number
|
||||
__scalar?: boolean | number
|
||||
}
|
||||
|
||||
export interface AvailableWorkspacesGenqlSelection{
|
||||
availableWorkspacesForSignIn?: AvailableWorkspaceGenqlSelection
|
||||
availableWorkspacesForSignUp?: AvailableWorkspaceGenqlSelection
|
||||
__typename?: boolean | number
|
||||
__scalar?: boolean | number
|
||||
}
|
||||
|
||||
export interface DeletedWorkspaceMemberGenqlSelection{
|
||||
id?: boolean | number
|
||||
name?: FullNameGenqlSelection
|
||||
userEmail?: boolean | number
|
||||
avatarUrl?: boolean | number
|
||||
userWorkspaceId?: boolean | number
|
||||
key?: boolean | number
|
||||
value?: boolean | number
|
||||
description?: boolean | number
|
||||
isSecret?: boolean | number
|
||||
isRequired?: boolean | number
|
||||
isFilled?: boolean | number
|
||||
createdAt?: boolean | number
|
||||
updatedAt?: boolean | number
|
||||
__typename?: boolean | number
|
||||
__scalar?: boolean | number
|
||||
}
|
||||
@@ -4766,6 +4722,96 @@ export interface FieldConnectionGenqlSelection{
|
||||
__scalar?: boolean | number
|
||||
}
|
||||
|
||||
export interface ResendEmailVerificationTokenGenqlSelection{
|
||||
success?: boolean | number
|
||||
__typename?: boolean | number
|
||||
__scalar?: boolean | number
|
||||
}
|
||||
|
||||
export interface DeleteSsoGenqlSelection{
|
||||
identityProviderId?: boolean | number
|
||||
__typename?: boolean | number
|
||||
__scalar?: boolean | number
|
||||
}
|
||||
|
||||
export interface EditSsoGenqlSelection{
|
||||
id?: boolean | number
|
||||
type?: boolean | number
|
||||
issuer?: boolean | number
|
||||
name?: boolean | number
|
||||
status?: boolean | number
|
||||
__typename?: boolean | number
|
||||
__scalar?: boolean | number
|
||||
}
|
||||
|
||||
export interface WorkspaceNameAndIdGenqlSelection{
|
||||
displayName?: boolean | number
|
||||
id?: boolean | number
|
||||
__typename?: boolean | number
|
||||
__scalar?: boolean | number
|
||||
}
|
||||
|
||||
export interface FindAvailableSSOIDPGenqlSelection{
|
||||
type?: boolean | number
|
||||
id?: boolean | number
|
||||
issuer?: boolean | number
|
||||
name?: boolean | number
|
||||
status?: boolean | number
|
||||
workspace?: WorkspaceNameAndIdGenqlSelection
|
||||
__typename?: boolean | number
|
||||
__scalar?: boolean | number
|
||||
}
|
||||
|
||||
export interface SetupSsoGenqlSelection{
|
||||
id?: boolean | number
|
||||
type?: boolean | number
|
||||
issuer?: boolean | number
|
||||
name?: boolean | number
|
||||
status?: boolean | number
|
||||
__typename?: boolean | number
|
||||
__scalar?: boolean | number
|
||||
}
|
||||
|
||||
export interface SSOConnectionGenqlSelection{
|
||||
type?: boolean | number
|
||||
id?: boolean | number
|
||||
issuer?: boolean | number
|
||||
name?: boolean | number
|
||||
status?: boolean | number
|
||||
__typename?: boolean | number
|
||||
__scalar?: boolean | number
|
||||
}
|
||||
|
||||
export interface AvailableWorkspaceGenqlSelection{
|
||||
id?: boolean | number
|
||||
displayName?: boolean | number
|
||||
loginToken?: boolean | number
|
||||
personalInviteToken?: boolean | number
|
||||
inviteHash?: boolean | number
|
||||
workspaceUrls?: WorkspaceUrlsGenqlSelection
|
||||
logo?: boolean | number
|
||||
sso?: SSOConnectionGenqlSelection
|
||||
__typename?: boolean | number
|
||||
__scalar?: boolean | number
|
||||
}
|
||||
|
||||
export interface AvailableWorkspacesGenqlSelection{
|
||||
availableWorkspacesForSignIn?: AvailableWorkspaceGenqlSelection
|
||||
availableWorkspacesForSignUp?: AvailableWorkspaceGenqlSelection
|
||||
__typename?: boolean | number
|
||||
__scalar?: boolean | number
|
||||
}
|
||||
|
||||
export interface DeletedWorkspaceMemberGenqlSelection{
|
||||
id?: boolean | number
|
||||
name?: FullNameGenqlSelection
|
||||
userEmail?: boolean | number
|
||||
avatarUrl?: boolean | number
|
||||
userWorkspaceId?: boolean | number
|
||||
__typename?: boolean | number
|
||||
__scalar?: boolean | number
|
||||
}
|
||||
|
||||
export interface BillingEntitlementGenqlSelection{
|
||||
key?: boolean | number
|
||||
value?: boolean | number
|
||||
@@ -5020,6 +5066,7 @@ export interface PublicDomainGenqlSelection{
|
||||
id?: boolean | number
|
||||
domain?: boolean | number
|
||||
isValidated?: boolean | number
|
||||
applicationId?: boolean | number
|
||||
createdAt?: boolean | number
|
||||
__typename?: boolean | number
|
||||
__scalar?: boolean | number
|
||||
@@ -5483,6 +5530,14 @@ export interface MessageChannelGenqlSelection{
|
||||
connectedAccountId?: boolean | number
|
||||
createdAt?: boolean | number
|
||||
updatedAt?: boolean | number
|
||||
connectedAccount?: ConnectedAccountPublicDTOGenqlSelection
|
||||
__typename?: boolean | number
|
||||
__scalar?: boolean | number
|
||||
}
|
||||
|
||||
export interface CreateEmailGroupChannelOutputGenqlSelection{
|
||||
messageChannel?: MessageChannelGenqlSelection
|
||||
forwardingAddress?: boolean | number
|
||||
__typename?: boolean | number
|
||||
__scalar?: boolean | number
|
||||
}
|
||||
@@ -5654,7 +5709,7 @@ export interface QueryGenqlSelection{
|
||||
findManyApplicationRegistrations?: ApplicationRegistrationGenqlSelection
|
||||
findOneApplicationRegistration?: (ApplicationRegistrationGenqlSelection & { __args: {id: Scalars['String']} })
|
||||
findApplicationRegistrationStats?: (ApplicationRegistrationStatsGenqlSelection & { __args: {id: Scalars['String']} })
|
||||
findApplicationRegistrationVariables?: (ApplicationRegistrationVariableGenqlSelection & { __args: {applicationRegistrationId: Scalars['String']} })
|
||||
findApplicationRegistrationVariables?: (ApplicationRegistrationVariableDTOGenqlSelection & { __args: {applicationRegistrationId: Scalars['String']} })
|
||||
applicationRegistrationTarballUrl?: { __args: {id: Scalars['String']} }
|
||||
currentUser?: UserGenqlSelection
|
||||
currentWorkspace?: WorkspaceGenqlSelection
|
||||
@@ -5824,6 +5879,8 @@ export interface MutationGenqlSelection{
|
||||
updateMessageFolder?: (MessageFolderGenqlSelection & { __args: {input: UpdateMessageFolderInput} })
|
||||
updateMessageFolders?: (MessageFolderGenqlSelection & { __args: {input: UpdateMessageFoldersInput} })
|
||||
updateMessageChannel?: (MessageChannelGenqlSelection & { __args: {input: UpdateMessageChannelInput} })
|
||||
createEmailGroupChannel?: (CreateEmailGroupChannelOutputGenqlSelection & { __args: {input: CreateEmailGroupChannelInput} })
|
||||
deleteEmailGroupChannel?: (MessageChannelGenqlSelection & { __args: {id: Scalars['UUID']} })
|
||||
deleteConnectedAccount?: (ConnectedAccountDTOGenqlSelection & { __args: {id: Scalars['UUID']} })
|
||||
updateCalendarChannel?: (CalendarChannelGenqlSelection & { __args: {input: UpdateCalendarChannelInput} })
|
||||
createWebhook?: (WebhookGenqlSelection & { __args: {input: CreateWebhookInput} })
|
||||
@@ -5897,7 +5954,8 @@ export interface MutationGenqlSelection{
|
||||
updateLabPublicFeatureFlag?: (FeatureFlagGenqlSelection & { __args: {input: UpdateLabPublicFeatureFlagInput} })
|
||||
enablePostgresProxy?: PostgresCredentialsGenqlSelection
|
||||
disablePostgresProxy?: PostgresCredentialsGenqlSelection
|
||||
createPublicDomain?: (PublicDomainGenqlSelection & { __args: {domain: Scalars['String']} })
|
||||
createPublicDomain?: (PublicDomainGenqlSelection & { __args: {domain: Scalars['String'], applicationId?: (Scalars['String'] | null)} })
|
||||
updatePublicDomain?: (PublicDomainGenqlSelection & { __args: {domain: Scalars['String'], applicationId?: (Scalars['String'] | null)} })
|
||||
deletePublicDomain?: { __args: {domain: Scalars['String']} }
|
||||
checkPublicDomainValidRecords?: (DomainValidRecordsGenqlSelection & { __args: {domain: Scalars['String']} })
|
||||
createEmailingDomain?: (EmailingDomainGenqlSelection & { __args: {domain: Scalars['String'], driver: EmailingDomainDriver} })
|
||||
@@ -6202,6 +6260,8 @@ export interface UpdateMessageChannelInput {id: Scalars['UUID'],update: UpdateMe
|
||||
|
||||
export interface UpdateMessageChannelInputUpdates {visibility?: (MessageChannelVisibility | null),isContactAutoCreationEnabled?: (Scalars['Boolean'] | null),contactAutoCreationPolicy?: (MessageChannelContactAutoCreationPolicy | null),messageFolderImportPolicy?: (MessageFolderImportPolicy | null),isSyncEnabled?: (Scalars['Boolean'] | null),excludeNonProfessionalEmails?: (Scalars['Boolean'] | null),excludeGroupEmails?: (Scalars['Boolean'] | null)}
|
||||
|
||||
export interface CreateEmailGroupChannelInput {handle: Scalars['String']}
|
||||
|
||||
export interface UpdateCalendarChannelInput {id: Scalars['UUID'],update: UpdateCalendarChannelInputUpdates}
|
||||
|
||||
export interface UpdateCalendarChannelInputUpdates {visibility?: (CalendarChannelVisibility | null),isContactAutoCreationEnabled?: (Scalars['Boolean'] | null),contactAutoCreationPolicy?: (CalendarChannelContactAutoCreationPolicy | null),isSyncEnabled?: (Scalars['Boolean'] | null)}
|
||||
@@ -6238,7 +6298,7 @@ export interface UpdateWorkspaceMemberSettingsInput {workspaceMemberId: Scalars[
|
||||
|
||||
export interface ActivateWorkspaceInput {displayName?: (Scalars['String'] | null)}
|
||||
|
||||
export interface UpdateWorkspaceInput {subdomain?: (Scalars['String'] | null),customDomain?: (Scalars['String'] | null),displayName?: (Scalars['String'] | null),logo?: (Scalars['String'] | null),inviteHash?: (Scalars['String'] | null),isPublicInviteLinkEnabled?: (Scalars['Boolean'] | null),allowImpersonation?: (Scalars['Boolean'] | null),isGoogleAuthEnabled?: (Scalars['Boolean'] | null),isMicrosoftAuthEnabled?: (Scalars['Boolean'] | null),isPasswordAuthEnabled?: (Scalars['Boolean'] | null),isGoogleAuthBypassEnabled?: (Scalars['Boolean'] | null),isMicrosoftAuthBypassEnabled?: (Scalars['Boolean'] | null),isPasswordAuthBypassEnabled?: (Scalars['Boolean'] | null),defaultRoleId?: (Scalars['UUID'] | null),isTwoFactorAuthenticationEnforced?: (Scalars['Boolean'] | null),trashRetentionDays?: (Scalars['Float'] | null),eventLogRetentionDays?: (Scalars['Float'] | null),fastModel?: (Scalars['String'] | null),smartModel?: (Scalars['String'] | null),aiAdditionalInstructions?: (Scalars['String'] | null),editableProfileFields?: (Scalars['String'][] | null),enabledAiModelIds?: (Scalars['String'][] | null),useRecommendedModels?: (Scalars['Boolean'] | null)}
|
||||
export interface UpdateWorkspaceInput {subdomain?: (Scalars['String'] | null),customDomain?: (Scalars['String'] | null),displayName?: (Scalars['String'] | null),logo?: (Scalars['String'] | null),inviteHash?: (Scalars['String'] | null),isPublicInviteLinkEnabled?: (Scalars['Boolean'] | null),allowImpersonation?: (Scalars['Boolean'] | null),isGoogleAuthEnabled?: (Scalars['Boolean'] | null),isMicrosoftAuthEnabled?: (Scalars['Boolean'] | null),isPasswordAuthEnabled?: (Scalars['Boolean'] | null),isGoogleAuthBypassEnabled?: (Scalars['Boolean'] | null),isMicrosoftAuthBypassEnabled?: (Scalars['Boolean'] | null),isPasswordAuthBypassEnabled?: (Scalars['Boolean'] | null),defaultRoleId?: (Scalars['UUID'] | null),isTwoFactorAuthenticationEnforced?: (Scalars['Boolean'] | null),trashRetentionDays?: (Scalars['Float'] | null),eventLogRetentionDays?: (Scalars['Float'] | null),fastModel?: (Scalars['String'] | null),smartModel?: (Scalars['String'] | null),aiAdditionalInstructions?: (Scalars['String'] | null),editableProfileFields?: (Scalars['String'][] | null),enabledAiModelIds?: (Scalars['String'][] | null),useRecommendedModels?: (Scalars['Boolean'] | null),isInternalMessagesImportEnabled?: (Scalars['Boolean'] | null)}
|
||||
|
||||
export interface WorkspaceMigrationInput {actions: WorkspaceMigrationDeleteActionInput[]}
|
||||
|
||||
@@ -7367,82 +7427,10 @@ export interface LogicFunctionLogsInput {applicationId?: (Scalars['UUID'] | null
|
||||
|
||||
|
||||
|
||||
const ResendEmailVerificationToken_possibleTypes: string[] = ['ResendEmailVerificationToken']
|
||||
export const isResendEmailVerificationToken = (obj?: { __typename?: any } | null): obj is ResendEmailVerificationToken => {
|
||||
if (!obj?.__typename) throw new Error('__typename is missing in "isResendEmailVerificationToken"')
|
||||
return ResendEmailVerificationToken_possibleTypes.includes(obj.__typename)
|
||||
}
|
||||
|
||||
|
||||
|
||||
const DeleteSso_possibleTypes: string[] = ['DeleteSso']
|
||||
export const isDeleteSso = (obj?: { __typename?: any } | null): obj is DeleteSso => {
|
||||
if (!obj?.__typename) throw new Error('__typename is missing in "isDeleteSso"')
|
||||
return DeleteSso_possibleTypes.includes(obj.__typename)
|
||||
}
|
||||
|
||||
|
||||
|
||||
const EditSso_possibleTypes: string[] = ['EditSso']
|
||||
export const isEditSso = (obj?: { __typename?: any } | null): obj is EditSso => {
|
||||
if (!obj?.__typename) throw new Error('__typename is missing in "isEditSso"')
|
||||
return EditSso_possibleTypes.includes(obj.__typename)
|
||||
}
|
||||
|
||||
|
||||
|
||||
const WorkspaceNameAndId_possibleTypes: string[] = ['WorkspaceNameAndId']
|
||||
export const isWorkspaceNameAndId = (obj?: { __typename?: any } | null): obj is WorkspaceNameAndId => {
|
||||
if (!obj?.__typename) throw new Error('__typename is missing in "isWorkspaceNameAndId"')
|
||||
return WorkspaceNameAndId_possibleTypes.includes(obj.__typename)
|
||||
}
|
||||
|
||||
|
||||
|
||||
const FindAvailableSSOIDP_possibleTypes: string[] = ['FindAvailableSSOIDP']
|
||||
export const isFindAvailableSSOIDP = (obj?: { __typename?: any } | null): obj is FindAvailableSSOIDP => {
|
||||
if (!obj?.__typename) throw new Error('__typename is missing in "isFindAvailableSSOIDP"')
|
||||
return FindAvailableSSOIDP_possibleTypes.includes(obj.__typename)
|
||||
}
|
||||
|
||||
|
||||
|
||||
const SetupSso_possibleTypes: string[] = ['SetupSso']
|
||||
export const isSetupSso = (obj?: { __typename?: any } | null): obj is SetupSso => {
|
||||
if (!obj?.__typename) throw new Error('__typename is missing in "isSetupSso"')
|
||||
return SetupSso_possibleTypes.includes(obj.__typename)
|
||||
}
|
||||
|
||||
|
||||
|
||||
const SSOConnection_possibleTypes: string[] = ['SSOConnection']
|
||||
export const isSSOConnection = (obj?: { __typename?: any } | null): obj is SSOConnection => {
|
||||
if (!obj?.__typename) throw new Error('__typename is missing in "isSSOConnection"')
|
||||
return SSOConnection_possibleTypes.includes(obj.__typename)
|
||||
}
|
||||
|
||||
|
||||
|
||||
const AvailableWorkspace_possibleTypes: string[] = ['AvailableWorkspace']
|
||||
export const isAvailableWorkspace = (obj?: { __typename?: any } | null): obj is AvailableWorkspace => {
|
||||
if (!obj?.__typename) throw new Error('__typename is missing in "isAvailableWorkspace"')
|
||||
return AvailableWorkspace_possibleTypes.includes(obj.__typename)
|
||||
}
|
||||
|
||||
|
||||
|
||||
const AvailableWorkspaces_possibleTypes: string[] = ['AvailableWorkspaces']
|
||||
export const isAvailableWorkspaces = (obj?: { __typename?: any } | null): obj is AvailableWorkspaces => {
|
||||
if (!obj?.__typename) throw new Error('__typename is missing in "isAvailableWorkspaces"')
|
||||
return AvailableWorkspaces_possibleTypes.includes(obj.__typename)
|
||||
}
|
||||
|
||||
|
||||
|
||||
const DeletedWorkspaceMember_possibleTypes: string[] = ['DeletedWorkspaceMember']
|
||||
export const isDeletedWorkspaceMember = (obj?: { __typename?: any } | null): obj is DeletedWorkspaceMember => {
|
||||
if (!obj?.__typename) throw new Error('__typename is missing in "isDeletedWorkspaceMember"')
|
||||
return DeletedWorkspaceMember_possibleTypes.includes(obj.__typename)
|
||||
const ApplicationRegistrationVariableDTO_possibleTypes: string[] = ['ApplicationRegistrationVariableDTO']
|
||||
export const isApplicationRegistrationVariableDTO = (obj?: { __typename?: any } | null): obj is ApplicationRegistrationVariableDTO => {
|
||||
if (!obj?.__typename) throw new Error('__typename is missing in "isApplicationRegistrationVariableDTO"')
|
||||
return ApplicationRegistrationVariableDTO_possibleTypes.includes(obj.__typename)
|
||||
}
|
||||
|
||||
|
||||
@@ -7559,6 +7547,86 @@ export interface LogicFunctionLogsInput {applicationId?: (Scalars['UUID'] | null
|
||||
|
||||
|
||||
|
||||
const ResendEmailVerificationToken_possibleTypes: string[] = ['ResendEmailVerificationToken']
|
||||
export const isResendEmailVerificationToken = (obj?: { __typename?: any } | null): obj is ResendEmailVerificationToken => {
|
||||
if (!obj?.__typename) throw new Error('__typename is missing in "isResendEmailVerificationToken"')
|
||||
return ResendEmailVerificationToken_possibleTypes.includes(obj.__typename)
|
||||
}
|
||||
|
||||
|
||||
|
||||
const DeleteSso_possibleTypes: string[] = ['DeleteSso']
|
||||
export const isDeleteSso = (obj?: { __typename?: any } | null): obj is DeleteSso => {
|
||||
if (!obj?.__typename) throw new Error('__typename is missing in "isDeleteSso"')
|
||||
return DeleteSso_possibleTypes.includes(obj.__typename)
|
||||
}
|
||||
|
||||
|
||||
|
||||
const EditSso_possibleTypes: string[] = ['EditSso']
|
||||
export const isEditSso = (obj?: { __typename?: any } | null): obj is EditSso => {
|
||||
if (!obj?.__typename) throw new Error('__typename is missing in "isEditSso"')
|
||||
return EditSso_possibleTypes.includes(obj.__typename)
|
||||
}
|
||||
|
||||
|
||||
|
||||
const WorkspaceNameAndId_possibleTypes: string[] = ['WorkspaceNameAndId']
|
||||
export const isWorkspaceNameAndId = (obj?: { __typename?: any } | null): obj is WorkspaceNameAndId => {
|
||||
if (!obj?.__typename) throw new Error('__typename is missing in "isWorkspaceNameAndId"')
|
||||
return WorkspaceNameAndId_possibleTypes.includes(obj.__typename)
|
||||
}
|
||||
|
||||
|
||||
|
||||
const FindAvailableSSOIDP_possibleTypes: string[] = ['FindAvailableSSOIDP']
|
||||
export const isFindAvailableSSOIDP = (obj?: { __typename?: any } | null): obj is FindAvailableSSOIDP => {
|
||||
if (!obj?.__typename) throw new Error('__typename is missing in "isFindAvailableSSOIDP"')
|
||||
return FindAvailableSSOIDP_possibleTypes.includes(obj.__typename)
|
||||
}
|
||||
|
||||
|
||||
|
||||
const SetupSso_possibleTypes: string[] = ['SetupSso']
|
||||
export const isSetupSso = (obj?: { __typename?: any } | null): obj is SetupSso => {
|
||||
if (!obj?.__typename) throw new Error('__typename is missing in "isSetupSso"')
|
||||
return SetupSso_possibleTypes.includes(obj.__typename)
|
||||
}
|
||||
|
||||
|
||||
|
||||
const SSOConnection_possibleTypes: string[] = ['SSOConnection']
|
||||
export const isSSOConnection = (obj?: { __typename?: any } | null): obj is SSOConnection => {
|
||||
if (!obj?.__typename) throw new Error('__typename is missing in "isSSOConnection"')
|
||||
return SSOConnection_possibleTypes.includes(obj.__typename)
|
||||
}
|
||||
|
||||
|
||||
|
||||
const AvailableWorkspace_possibleTypes: string[] = ['AvailableWorkspace']
|
||||
export const isAvailableWorkspace = (obj?: { __typename?: any } | null): obj is AvailableWorkspace => {
|
||||
if (!obj?.__typename) throw new Error('__typename is missing in "isAvailableWorkspace"')
|
||||
return AvailableWorkspace_possibleTypes.includes(obj.__typename)
|
||||
}
|
||||
|
||||
|
||||
|
||||
const AvailableWorkspaces_possibleTypes: string[] = ['AvailableWorkspaces']
|
||||
export const isAvailableWorkspaces = (obj?: { __typename?: any } | null): obj is AvailableWorkspaces => {
|
||||
if (!obj?.__typename) throw new Error('__typename is missing in "isAvailableWorkspaces"')
|
||||
return AvailableWorkspaces_possibleTypes.includes(obj.__typename)
|
||||
}
|
||||
|
||||
|
||||
|
||||
const DeletedWorkspaceMember_possibleTypes: string[] = ['DeletedWorkspaceMember']
|
||||
export const isDeletedWorkspaceMember = (obj?: { __typename?: any } | null): obj is DeletedWorkspaceMember => {
|
||||
if (!obj?.__typename) throw new Error('__typename is missing in "isDeletedWorkspaceMember"')
|
||||
return DeletedWorkspaceMember_possibleTypes.includes(obj.__typename)
|
||||
}
|
||||
|
||||
|
||||
|
||||
const BillingEntitlement_possibleTypes: string[] = ['BillingEntitlement']
|
||||
export const isBillingEntitlement = (obj?: { __typename?: any } | null): obj is BillingEntitlement => {
|
||||
if (!obj?.__typename) throw new Error('__typename is missing in "isBillingEntitlement"')
|
||||
@@ -8159,6 +8227,14 @@ export interface LogicFunctionLogsInput {applicationId?: (Scalars['UUID'] | null
|
||||
|
||||
|
||||
|
||||
const CreateEmailGroupChannelOutput_possibleTypes: string[] = ['CreateEmailGroupChannelOutput']
|
||||
export const isCreateEmailGroupChannelOutput = (obj?: { __typename?: any } | null): obj is CreateEmailGroupChannelOutput => {
|
||||
if (!obj?.__typename) throw new Error('__typename is missing in "isCreateEmailGroupChannelOutput"')
|
||||
return CreateEmailGroupChannelOutput_possibleTypes.includes(obj.__typename)
|
||||
}
|
||||
|
||||
|
||||
|
||||
const MessageFolder_possibleTypes: string[] = ['MessageFolder']
|
||||
export const isMessageFolder = (obj?: { __typename?: any } | null): obj is MessageFolder => {
|
||||
if (!obj?.__typename) throw new Error('__typename is missing in "isMessageFolder"')
|
||||
@@ -8629,6 +8705,7 @@ export const enumBillingUsageType = {
|
||||
|
||||
export const enumBillingProductKey = {
|
||||
BASE_PRODUCT: 'BASE_PRODUCT' as const,
|
||||
RESOURCE_CREDIT: 'RESOURCE_CREDIT' as const,
|
||||
WORKFLOW_NODE_EXECUTION: 'WORKFLOW_NODE_EXECUTION' as const
|
||||
}
|
||||
|
||||
@@ -8681,16 +8758,14 @@ export const enumLogicFunctionExecutionStatus = {
|
||||
export const enumFeatureFlagKey = {
|
||||
IS_UNIQUE_INDEXES_ENABLED: 'IS_UNIQUE_INDEXES_ENABLED' as const,
|
||||
IS_JSON_FILTER_ENABLED: 'IS_JSON_FILTER_ENABLED' as const,
|
||||
IS_COMMAND_MENU_ITEM_ENABLED: 'IS_COMMAND_MENU_ITEM_ENABLED' as const,
|
||||
IS_MARKETPLACE_SETTING_TAB_VISIBLE: 'IS_MARKETPLACE_SETTING_TAB_VISIBLE' as const,
|
||||
IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED: 'IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED' as const,
|
||||
IS_PUBLIC_DOMAIN_ENABLED: 'IS_PUBLIC_DOMAIN_ENABLED' as const,
|
||||
IS_EMAILING_DOMAIN_ENABLED: 'IS_EMAILING_DOMAIN_ENABLED' as const,
|
||||
IS_EMAIL_GROUP_ENABLED: 'IS_EMAIL_GROUP_ENABLED' as const,
|
||||
IS_JUNCTION_RELATIONS_ENABLED: 'IS_JUNCTION_RELATIONS_ENABLED' as const,
|
||||
IS_CONNECTED_ACCOUNT_MIGRATED: 'IS_CONNECTED_ACCOUNT_MIGRATED' as const,
|
||||
IS_RICH_TEXT_V1_MIGRATED: 'IS_RICH_TEXT_V1_MIGRATED' as const,
|
||||
IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED: 'IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED' as const,
|
||||
IS_DATASOURCE_MIGRATED: 'IS_DATASOURCE_MIGRATED' as const
|
||||
IS_BILLING_V2_ENABLED: 'IS_BILLING_V2_ENABLED' as const
|
||||
}
|
||||
|
||||
export const enumIdentityProviderType = {
|
||||
@@ -8784,7 +8859,8 @@ export const enumMessageChannelVisibility = {
|
||||
|
||||
export const enumMessageChannelType = {
|
||||
EMAIL: 'EMAIL' as const,
|
||||
SMS: 'SMS' as const
|
||||
SMS: 'SMS' as const,
|
||||
EMAIL_GROUP: 'EMAIL_GROUP' as const
|
||||
}
|
||||
|
||||
export const enumMessageChannelContactAutoCreationPolicy = {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -37,7 +37,7 @@ Both are available as REST and GraphQL. GraphQL adds batch upserts and the abili
|
||||
Authorization: Bearer YOUR_API_KEY
|
||||
```
|
||||
|
||||
Create an API key in **Settings → API & Webhooks → + Create key**. Copy it immediately — it's shown once. Keys can be scoped to a specific role under **Settings → Roles → Assignment tab** to limit what they can access.
|
||||
Create an API key in **Settings → API & Webhooks → + Create key**. Copy it immediately — it's shown once. Keys can be scoped to a specific role under **Settings → Members → Roles → Assignment tab** to limit what they can access.
|
||||
|
||||
<VimeoEmbed videoId="928786722" title="Creating API key" />
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user