snapshot of https://github.com/twentyhq/twenty.git (main, 2026-09-17; blobs > 131072B omitted)
Some checks failed
App Prod-Parity E2E Dispatch / dispatch (push) Failing after 10s
CD Storybook UI / build (push) Failing after 3m25s
CD Storybook UI / dispatch (push) Has been skipped
CD deploy main / website-changed-files (push) Successful in 18m8s
CI Agent Skills / changed-files-check (push) Successful in 18m9s
CI Create App E2E minimal / changed-files-check (push) Successful in 24s
CI Create App / changed-files-check (push) Successful in 26s
CI Docs / changed-files-check (push) Successful in 26s
CI Emails / changed-files-check (push) Successful in 28s
CI Example App Hello World / changed-files-check (push) Successful in 23s
CI Front / changed-files-check (push) Has been skipped
CI Example App Postcard / changed-files-check (push) Successful in 27s
CI UI / changed-files-check (push) Has been skipped
CI Utils Test / changed-files-check (push) Successful in 45s
CI Twenty Apps / discover (push) Successful in 1m3s
CD deploy main / deploy-main (push) Failing after 3s
CI Agent Skills / agent-skills-validate (push) Has been skipped
CI Create App / create-app-test (lint) (push) Has been skipped
CI Emails / emails-test (push) Has been skipped
CI Front / front-task (lingui:compile) (push) Has been skipped
CI Front / front-task (lint) (push) Has been skipped
CI Example App Postcard / example-app-postcard (push) Has been skipped
CI UI / ui-task (lint) (push) Has been skipped
CI UI / ui-task (pack-size) (push) Has been skipped
CI UI / ui-task (size) (push) Has been skipped
CI UI / ui-task (test) (push) Has been skipped
CI UI / ui-task (typecheck) (push) Has been skipped
CI Utils Test / utils-test (push) Has been skipped
CI Twenty Apps / ${{ matrix.app.name }} (push) Has been skipped
CI Emails / ci-emails-status-check (push) Successful in 2s
CI Example App Hello World / ci-example-app-hello-world-status-check (push) Successful in 2s
CI Example App Postcard / ci-example-app-postcard-status-check (push) Successful in 3s
CI UI / ui-sb-test (push) Has been skipped
CI Utils Test / ci-utils-test-status-check (push) Successful in 2s
CI Twenty Apps / ci-twenty-apps-status-check (push) Successful in 2s
CI UI / ci-ui-status-check (push) Successful in 2s
CI Desktop / desktop (push) Failing after 1m8s
Push docs to Crowdin / Push documentation to Crowdin (push) Failing after 55s
Push translations to Crowdin / Extract and upload translations (push) Failing after 5s
Push website translations to Crowdin / Extract and upload website translations (push) Failing after 5s
CI Create App / create-app-test (test) (push) Has been skipped
CI Create App / create-app-test (typecheck) (push) Has been skipped
CI Docs / docs-lint (push) Has been skipped
CI Example App Hello World / example-app-hello-world (push) Has been skipped
CI Front / front-task (lingui:extract) (push) Has been skipped
CI Front / front-task (test) (push) Has been skipped
CI Front / front-task (typecheck) (push) Has been skipped
CI UI / ui-sb-build (push) Failing after 1m32s
CI Twenty Apps / ${{ matrix.app.name }} (${{ matrix.server-source }}) (push) Has been skipped
CI Agent Skills / agent-skills-publish (push) Has been skipped
CI Create App / ci-create-app-status-check (push) Successful in 2s
CI Agent Skills / agent-skills-remote-source (push) Has been skipped
CI Create App E2E minimal / create-app-e2e-minimal (push) Has been cancelled
CI E2E Main / e2e-test (push) Has been cancelled
CI Front / front-sb-build (push) Has been cancelled
CI Front / front-build (push) Has been cancelled
CI Create App E2E minimal / ci-create-app-e2e-minimal-status-check (push) Has been cancelled
CI E2E Main / ci-e2e-main-status-check (push) Has been cancelled
CI E2E Main / notify-main-ci-failure (push) Has been cancelled
CI E2E Main / qa-scout-comment (push) Has been cancelled
CI Front / front-sb-test (push) Has been cancelled
CI Front / front-sb-screenshots (push) Has been cancelled
CI Front / ci-front-status-check (push) Has been cancelled
DPA Sub-Processor Sync / sync-subprocessors (push) Failing after 3s
AI Catalog Sync / sync-catalog (push) Failing after 2s

This commit is contained in:
upstream-mirror
2026-09-17 23:28:13 +05:00
commit e7da97194a
32688 changed files with 4208681 additions and 0 deletions

View File

@@ -0,0 +1,365 @@
name: CI - Cross-Version Upgrade
run-name: cross-version-upgrade ${{ inputs.from_version || 'v1.22' }} → current (from source)
on:
workflow_call:
inputs:
skip:
type: boolean
required: false
default: false
description: 'Set to true to skip the upgrade check and report success immediately'
from_version:
type: string
required: false
description: 'Old Twenty version to upgrade from (defaults to oldest supported: v1.22)'
workflow_dispatch:
inputs:
from_version:
type: string
required: false
default: ''
description: 'Old Twenty version to upgrade from (defaults to oldest supported: v1.22)'
permissions:
contents: read
defaults:
run:
shell: bash --noprofile --norc -euo pipefail {0}
env:
SERVER_BUILD_CACHE_KEY: server-build
OLDEST_SUPPORTED_VERSION: v1.22
# Local CI containers only — not real secrets. APP_SECRET is generated fresh
# per run (see "Compute run config") so nothing reconstructible is committed.
OLD_PG_DATABASE_URL: postgres://postgres:postgres@postgres:5432/default
OLD_REDIS_URL: redis://redis:6379
# Standard dev-seed workspace id (workspace:seed:dev) — public, not a secret.
SEED_WORKSPACE_ID: 20202020-1c25-4d02-bf25-6aeccf7ea419
jobs:
no-op:
if: format('{0}', inputs.skip) == 'true'
runs-on: ubuntu-latest
steps:
- run: echo "Cross-version upgrade check skipped (upgrade path not touched)"
cross-version-upgrade:
if: format('{0}', inputs.skip) != 'true'
timeout-minutes: 45
runs-on: ubuntu-latest
steps:
- name: Compute run config
env:
INPUT_FROM_VERSION: ${{ inputs.from_version }}
run: |
FROM_VERSION="${INPUT_FROM_VERSION:-}"
if [ -z "$FROM_VERSION" ]; then
FROM_VERSION="$OLDEST_SUPPORTED_VERSION"
fi
printf 'FROM_VERSION=%s\n' "$FROM_VERSION" >> "$GITHUB_ENV"
# Random per-run secret shared by the old container and the from-source
# server within this job. Never committed, never reused across runs.
GENERATED_APP_SECRET="$(openssl rand -hex 32)"
echo "::add-mask::${GENERATED_APP_SECRET}"
printf 'APP_SECRET=%s\n' "$GENERATED_APP_SECRET" >> "$GITHUB_ENV"
echo "Testing upgrade from ${FROM_VERSION} to current (built from source)"
- name: Create Docker network
run: docker network create twenty-net
- name: Start Postgres
run: |
docker run -d \
--name postgres \
--network twenty-net \
-e POSTGRES_USER=postgres \
-e POSTGRES_PASSWORD=postgres \
-e POSTGRES_DB=default \
-p 5432:5432 \
postgres:16
for i in $(seq 1 30); do
if docker exec postgres pg_isready -U postgres 2>/dev/null; then
echo "Postgres is ready"
exit 0
fi
echo "Waiting for Postgres… ($i/30)"
sleep 2
done
echo "::error::Postgres did not become ready in time"
docker logs postgres 2>&1 | tail -50 || true
exit 1
- name: Start Redis
run: |
docker run -d \
--name redis \
--network twenty-net \
-p 6379:6379 \
redis:7-alpine
for i in $(seq 1 10); do
if docker exec redis redis-cli ping 2>/dev/null | grep -q PONG; then
echo "Redis is ready"
exit 0
fi
echo "Waiting for Redis… ($i/10)"
sleep 1
done
echo "::error::Redis did not become ready in time"
docker logs redis 2>&1 | tail -50 || true
exit 1
# ================================================================
# Phase 1 — Old version (from Docker Hub): seed and sanity check
# ================================================================
- name: Old version / Start container
run: |
docker run -d \
--name twenty-old \
--network twenty-net \
-p 3000:3000 \
-e NODE_PORT=3000 \
-e SERVER_URL=http://localhost:3000 \
-e PG_DATABASE_URL=${OLD_PG_DATABASE_URL} \
-e REDIS_URL=${OLD_REDIS_URL} \
-e APP_SECRET=${APP_SECRET} \
"twentycrm/twenty:${FROM_VERSION}"
- name: Old version / Wait for API ready
run: |
TIMEOUT=600
CONTAINER="twenty-old"
ELAPSED=0
echo "Waiting for Twenty on port 3000 (timeout ${TIMEOUT}s)…"
while true; do
healthz=$(curl -s -o /dev/null -w '%{http_code}' "http://localhost:3000/healthz" 2>/dev/null || echo "000")
graphql=$(curl -s -o /dev/null -w '%{http_code}' "http://localhost:3000/graphql" \
-H "Content-Type: application/json" \
-d '{"query":"{ __typename }"}' 2>/dev/null || echo "000")
if [ "$healthz" = "200" ] && [ "$graphql" != "000" ] && [ "$graphql" != "404" ]; then
echo "Twenty is ready! healthz=${healthz} graphql=${graphql} (took ~${ELAPSED}s)"
break
fi
container_status=$(docker inspect --format='{{.State.Status}}' "$CONTAINER" 2>/dev/null || echo "unknown")
if [ "$container_status" = "exited" ]; then
echo "::error::Container ${CONTAINER} exited unexpectedly"
docker logs "$CONTAINER"
exit 1
fi
ELAPSED=$((ELAPSED + 5))
if [ "$ELAPSED" -ge "$TIMEOUT" ]; then
echo "::error::Twenty did not become ready within ${TIMEOUT}s (healthz=${healthz} graphql=${graphql})"
docker logs "$CONTAINER" 2>&1 | tail -100
exit 1
fi
echo " … waited ${ELAPSED}s [healthz=${healthz} graphql=${graphql}]"
sleep 5
done
- name: Old version / Seed dev data
run: |
docker exec twenty-old sh -c "cd /app/packages/twenty-server && yarn command:prod workspace:seed:dev"
- name: Old version / Sanity check seeded data
# Verify the seed populated the database directly via psql — avoids
# needing an API token on the old version (v1.22 has no token-mint command).
run: |
COUNT=$(docker exec -e PGPASSWORD=postgres postgres \
psql -U postgres -d default -tAc \
"SELECT count(*) FROM core.workspace WHERE id = '${SEED_WORKSPACE_ID}';")
echo "Seed workspaces found: ${COUNT}"
if [ "${COUNT:-0}" -lt 1 ]; then
echo "Sanity check failed: seed workspace ${SEED_WORKSPACE_ID} not found"
docker logs twenty-old
exit 1
fi
- name: Old version / Stop container
run: |
mkdir -p upgrade-logs
docker logs twenty-old > upgrade-logs/twenty-old.log 2>&1 || true
docker stop twenty-old
docker rm twenty-old
# ================================================================
# Phase 2 — Current version (from source): upgrade on same database
# ================================================================
- name: Fetch custom Github Actions and base branch history
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
with:
fetch-depth: 10
- name: Install dependencies
uses: ./.github/actions/yarn-install
- name: Restore server build cache
# Best-effort: a hit skips the cold rebuild; a miss simply builds from scratch below.
uses: ./.github/actions/restore-cache
with:
key: ${{ env.SERVER_BUILD_CACHE_KEY }}
- name: Build twenty-shared
run: npx nx build twenty-shared
- name: Server / Write .env
run: npx nx reset:env twenty-server
- name: Server / Align .env with seeded database
run: |
ENV_FILE="packages/twenty-server/.env"
# Must match the old container's APP_SECRET (same DB, same run-generated secret).
sed -i "s|^APP_SECRET=.*|APP_SECRET=${APP_SECRET}|" "$ENV_FILE"
# PG_DATABASE_URL / REDIS_URL come from .env.example (postgres@localhost:5432/default,
# redis@localhost:6379). APP_SECRET is intentionally not echoed.
grep -E '^(PG_DATABASE_URL|REDIS_URL)=' "$ENV_FILE"
- name: Server / Build
run: npx nx build twenty-server
- name: Server / Flush cache
run: npx nx run twenty-server:command-no-deps -- cache:flush
- name: Server / Run upgrade command
run: npx nx run twenty-server:command-no-deps -- upgrade --verbose
- name: Server / Start
run: |
mkdir -p upgrade-logs
npx nx start:ci twenty-server > upgrade-logs/twenty-new.log 2>&1 &
- name: Waiting for server starting...
run: |
for i in {1..30}; do
if curl -f -s http://localhost:3000/healthz > /dev/null; then
echo "Server ready!"
exit 0
fi
echo "Waiting..."
sleep 5
done
echo "::error::Server did not become healthy in time"
tail -100 upgrade-logs/twenty-new.log || true
exit 1
# ================================================================
# Phase 3 — Smoke tests on upgraded instance
# ================================================================
# Health is already asserted by the "Waiting for server starting..." loop.
- name: Smoke / Generate API token
# Mint a token against the running upgraded server (signed with this run's
# APP_SECRET). No token is committed to the repo.
run: |
OUTPUT=$(NO_COLOR=1 npx nx run twenty-server:command-no-deps -- \
workspace:generate-api-key -w "${SEED_WORKSPACE_ID}" -n "cross-version-upgrade-ci")
echo "$OUTPUT"
TOKEN=$(echo "$OUTPUT" | grep -o 'TOKEN:[A-Za-z0-9._-]*' | head -1 | cut -d: -f2-)
if [ -z "$TOKEN" ]; then
echo "::error::Failed to generate API token"
exit 1
fi
echo "::add-mask::$TOKEN"
printf 'API_KEY=%s\n' "$TOKEN" >> "$GITHUB_ENV"
- name: Smoke / Upgrade status
run: |
RAW=$(FORCE_COLOR=0 NO_COLOR=1 npx nx run twenty-server:command-no-deps -- upgrade:status 2>&1)
# chalk colorizes even with NO_COLOR under nx — strip ANSI codes before asserting.
OUTPUT=$(printf '%s\n' "$RAW" | sed -E 's/\x1b\[[0-9;]*m//g')
echo "$OUTPUT"
echo "$OUTPUT" > upgrade-logs/upgrade-status.log
echo "$OUTPUT" | grep -q "Instance: Up to date" \
|| { echo "FAIL: Instance is not up to date"; exit 1; }
echo "$OUTPUT" | grep -q "0 behind, 0 failed" \
|| { echo "FAIL: Some workspaces are behind or failed"; exit 1; }
- name: Smoke / Query companies
run: |
HTTP_CODE=$(curl -s -o /tmp/response.json -w '%{http_code}' http://localhost:3000/graphql \
-H "Authorization: Bearer ${API_KEY}" \
-H "Content-Type: application/json" \
-d '{"query":"{ companies { edges { node { id name } } } }"}' || true)
echo "HTTP status: ${HTTP_CODE}"
jq . /tmp/response.json 2>/dev/null || cat /tmp/response.json
if [ -z "$HTTP_CODE" ] || [ "$HTTP_CODE" -lt 200 ] || [ "$HTTP_CODE" -ge 300 ]; then
echo "FAIL: API returned HTTP ${HTTP_CODE}"; exit 1
fi
jq -e '.data.companies.edges | length > 0' /tmp/response.json \
|| { echo "FAIL: expected companies with edges"; exit 1; }
- name: Smoke / Query people
run: |
HTTP_CODE=$(curl -s -o /tmp/response.json -w '%{http_code}' http://localhost:3000/graphql \
-H "Authorization: Bearer ${API_KEY}" \
-H "Content-Type: application/json" \
-d '{"query":"{ people { edges { node { id } } } }"}' || true)
echo "HTTP status: ${HTTP_CODE}"
jq . /tmp/response.json 2>/dev/null || cat /tmp/response.json
if [ -z "$HTTP_CODE" ] || [ "$HTTP_CODE" -lt 200 ] || [ "$HTTP_CODE" -ge 300 ]; then
echo "FAIL: API returned HTTP ${HTTP_CODE}"; exit 1
fi
jq -e '.data.people.edges != null' /tmp/response.json \
|| { echo "FAIL: expected people edges to be non-null"; exit 1; }
- name: Smoke / Query metadata objects
run: |
HTTP_CODE=$(curl -s -o /tmp/response.json -w '%{http_code}' http://localhost:3000/metadata \
-H "Authorization: Bearer ${API_KEY}" \
-H "Content-Type: application/json" \
-d '{"query":"{ objects { edges { node { id nameSingular } } } }"}' || true)
echo "HTTP status: ${HTTP_CODE}"
jq . /tmp/response.json 2>/dev/null || cat /tmp/response.json
if [ -z "$HTTP_CODE" ] || [ "$HTTP_CODE" -lt 200 ] || [ "$HTTP_CODE" -ge 300 ]; then
echo "FAIL: API returned HTTP ${HTTP_CODE}"; exit 1
fi
jq -e '.data.objects.edges | length > 0' /tmp/response.json \
|| { echo "FAIL: expected metadata objects"; exit 1; }
# ================================================================
# Phase 4 — Collect logs & cleanup
# ================================================================
- name: Upload upgrade logs
if: always()
id: upload-logs
uses: actions/upload-artifact@v4
with:
name: cross-version-upgrade-logs
path: upgrade-logs/
if-no-files-found: ignore
- name: Job summary
if: always()
env:
RESULT: ${{ job.status }}
ARTIFACT_URL: ${{ steps.upload-logs.outputs.artifact-url }}
run: |
{
echo "## Cross-Version Upgrade: ${FROM_VERSION} → current (from source)"
echo ""
echo "| | |"
echo "|---|---|"
echo "| **Result** | \`${RESULT}\` |"
echo "| **Old version** | \`${FROM_VERSION}\` (Docker Hub) |"
echo "| **New version** | current branch (built from source) |"
if [ -n "${ARTIFACT_URL:-}" ]; then
echo ""
echo "**[Download full logs](${ARTIFACT_URL})**"
fi
} >> "$GITHUB_STEP_SUMMARY"
- name: Cleanup
if: always()
run: |
docker stop twenty-old 2>/dev/null || true
docker rm twenty-old 2>/dev/null || true
docker stop postgres 2>/dev/null || true
docker rm postgres 2>/dev/null || true
docker stop redis 2>/dev/null || true
docker rm redis 2>/dev/null || true
docker network rm twenty-net 2>/dev/null || true