Files
twenty/.github/workflows/ci-breaking-changes.yaml
Félix Malfait 9b8cb610c3 Flush Redis between server runs in breaking changes CI (#19491)
## Summary
Added a Redis cache flush step in the breaking changes CI workflow to
prevent stale data contamination between consecutive server runs.

## Key Changes
- Added a new workflow step that executes `redis-cli FLUSHALL` between
the current branch server run and the main branch server run
- Includes error handling to log a warning if the Redis flush fails,
without blocking the workflow
- Added explanatory comments documenting why this step is necessary

## Implementation Details
The Redis flush is necessary because both the current branch and main
branch servers share the same Redis instance during CI testing. The
`CoreEntityCacheService` and `WorkspaceCacheService` persist cached
entities across process restarts, which can cause stale data from the
current branch server to contaminate the main branch server comparison.
This step ensures a clean cache state before switching branches.

https://claude.ai/code/session_01BVMacfAXDMNx5WAFtP7GgW

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-09 12:56:24 +02:00

631 lines
27 KiB
YAML

name: GraphQL and OpenAPI Breaking Changes Detection
on:
pull_request:
types: [opened, synchronize, edited]
branches:
- main
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
env:
MAIN_SERVER_PORT: 3000
CURRENT_SERVER_PORT: 3002
permissions:
contents: read
jobs:
changed-files-check:
uses: ./.github/workflows/changed-files.yaml
with:
files: |
package.json
packages/twenty-server/**
packages/twenty-emails/**
packages/twenty-shared/**
.github/workflows/ci-breaking-changes.yaml
api-breaking-changes:
needs: changed-files-check
if: needs.changed-files-check.outputs.any_changed == 'true'
timeout-minutes: 45
runs-on: ubuntu-latest
services:
postgres:
image: postgres:18
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
redis:
image: redis
ports:
- 6379:6379
clickhouse:
image: clickhouse/clickhouse-server:25.8.8
env:
CLICKHOUSE_PASSWORD: clickhousePassword
CLICKHOUSE_URL: "http://default:clickhousePassword@localhost:8123/twenty"
ports:
- 8123:8123
- 9000:9000
options: >-
--health-cmd "clickhouse-client --host=localhost --port=9000 --user=default --password=clickhousePassword --query='SELECT 1'"
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- name: Checkout current branch
uses: actions/checkout@v4
with:
fetch-depth: 10
- name: Try to merge main into current branch
id: merge_attempt
run: |
echo "Attempting to merge main into current branch..."
git config user.email "ci@twenty.com"
git config user.name "CI"
git fetch origin main
CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
echo "Current branch: $CURRENT_BRANCH"
if git merge origin/main --no-edit; then
echo "✅ Successfully merged main into current branch"
echo "merged=true" >> $GITHUB_OUTPUT
echo "BRANCH_STATE=merged" >> $GITHUB_ENV
else
echo "❌ Merge failed due to conflicts"
echo "⚠️ Falling back to comparing current branch against main without merge"
# Abort the failed merge (may not exist if merge never started)
git merge --abort 2>/dev/null || true
echo "merged=false" >> $GITHUB_OUTPUT
echo "BRANCH_STATE=conflicts" >> $GITHUB_ENV
fi
- name: Install dependencies
uses: ./.github/actions/yarn-install
- name: Build shared dependencies
run: |
npx nx build twenty-shared
npx nx build twenty-emails
- name: Build current branch server
run: npx nx build twenty-server
- name: Setup databases
run: |
PGPASSWORD=postgres psql -h localhost -p 5432 -U postgres -d postgres -c 'CREATE DATABASE "current_branch";'
PGPASSWORD=postgres psql -h localhost -p 5432 -U postgres -d postgres -c 'CREATE DATABASE "main_branch";'
- name: Run ClickHouse migrations
run: npx nx clickhouse:migrate twenty-server
env:
CLICKHOUSE_URL: http://default:clickhousePassword@localhost:8123/twenty
CLICKHOUSE_PASSWORD: clickhousePassword
- name: Setup current branch database
run: |
npx nx reset:env twenty-server
set_env_var() {
local var_name="$1"
local var_value="$2"
local env_file="packages/twenty-server/.env"
echo "" >> "$env_file"
if grep -q "^${var_name}=" "$env_file" 2>/dev/null; then
sed -i "s|^${var_name}=.*|${var_name}=${var_value}|" "$env_file"
else
echo "${var_name}=${var_value}" >> "$env_file"
fi
}
set_env_var "PG_DATABASE_URL" "postgres://postgres:postgres@localhost:5432/current_branch"
set_env_var "NODE_PORT" "${{ env.CURRENT_SERVER_PORT }}"
set_env_var "REDIS_URL" "redis://localhost:6379"
set_env_var "CLICKHOUSE_URL" "http://default:clickhousePassword@localhost:8123/twenty"
set_env_var "CLICKHOUSE_PASSWORD" "clickhousePassword"
npx nx run twenty-server:database:init:prod
- name: Seed current branch database with test data
run: |
npx nx command-no-deps twenty-server -- workspace:seed:dev
- name: Start current branch server in background
run: |
echo "=== Current branch .env file contents ==="
cat packages/twenty-server/.env
echo "=== Starting current branch server ==="
nohup npx nx run twenty-server:start:prod > /tmp/current-server.log 2>&1 &
echo $! > /tmp/current-server.pid
echo "Current server PID: $(cat /tmp/current-server.pid)"
- name: Wait for current branch server to be ready
run: |
echo "Waiting for current branch server to start..."
timeout=300
interval=5
elapsed=0
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
echo "Current branch server is ready!"
break
fi
echo "Current branch server not ready yet, waiting ${interval}s..."
sleep $interval
elapsed=$((elapsed + interval))
done
if [ $elapsed -ge $timeout ]; then
echo "Timeout waiting for current branch server to start"
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
run: |
# Read admin token from shared test tokens file (single source of truth)
ADMIN_TOKEN=$(jq -r '.APPLE_JANE_ADMIN_ACCESS_TOKEN' packages/twenty-server/test/integration/constants/test-tokens.json)
# Load introspection query from file
INTROSPECTION_QUERY=$(cat packages/twenty-utils/graphql-introspection-query.graphql)
# Prepare the query payload
QUERY_PAYLOAD=$(echo "$INTROSPECTION_QUERY" | tr '\n' ' ' | sed 's/"/\\"/g')
echo "Downloading GraphQL schema from current server..."
curl -X POST "http://localhost:${{ env.CURRENT_SERVER_PORT }}/graphql" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${ADMIN_TOKEN}" \
-d "{\"query\":\"${QUERY_PAYLOAD}\"}" \
-o current-schema-introspection.json \
-w "HTTP Status: %{http_code}\n" \
-s
echo "Downloading GraphQL metadata schema from current server..."
curl -X POST "http://localhost:${{ env.CURRENT_SERVER_PORT }}/metadata" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${ADMIN_TOKEN}" \
-d "{\"query\":\"${QUERY_PAYLOAD}\"}" \
-o current-metadata-schema-introspection.json \
-w "HTTP Status: %{http_code}\n" \
-s
# Download current branch OpenAPI specs
echo "Downloading OpenAPI specifications from current server..."
curl -s "http://localhost:${{ env.CURRENT_SERVER_PORT }}/rest/open-api/core" \
-H "Authorization: Bearer ${ADMIN_TOKEN}" \
-o current-rest-api.json \
-w "HTTP Status: %{http_code}\n"
curl -s "http://localhost:${{ env.CURRENT_SERVER_PORT }}/rest/open-api/metadata" \
-H "Authorization: Bearer ${ADMIN_TOKEN}" \
-o current-rest-metadata-api.json \
-w "HTTP Status: %{http_code}\n"
# Verify the downloads
echo "Current branch files downloaded:"
ls -la current-*
- name: Preserve current branch files
run: |
# Create a temp directory to store current branch files
mkdir -p /tmp/current-branch-files
# Move current branch files to temp directory
mv current-* /tmp/current-branch-files/ 2>/dev/null || echo "No current-* files to preserve"
echo "Preserved current branch files for later restoration"
- name: Stop current branch server
run: |
if [ -f /tmp/current-server.pid ]; then
echo "Stopping current branch server..."
kill $(cat /tmp/current-server.pid) || true
# Wait a bit for graceful shutdown
sleep 5
# Force kill if still running
kill -9 $(cat /tmp/current-server.pid) 2>/dev/null || true
rm -f /tmp/current-server.pid
fi
- name: Flush Redis between server runs
run: |
# Clear all Redis caches to prevent stale data from the current branch
# server contaminating the main branch server. Both servers share the
# same Redis instance, and CoreEntityCacheService/WorkspaceCacheService
# persist cached entities across process restarts.
redis-cli -h localhost -p 6379 FLUSHALL || echo "::warning::Failed to flush Redis"
- name: Checkout main branch
run: |
git stash
git checkout origin/main
git reset --hard
git clean -xfd -ff
rm -rf node_modules packages/*/node_modules packages/*/dist dist .nx/cache
- name: Install dependencies for main branch
uses: ./.github/actions/yarn-install
- name: Build main branch dependencies
run: |
npx nx reset
npx nx build twenty-shared
npx nx build twenty-emails
- name: Build main branch server
run: npx nx build twenty-server
- name: Setup main branch database
run: |
npx nx reset:env twenty-server
set_env_var() {
local var_name="$1"
local var_value="$2"
local env_file="packages/twenty-server/.env"
echo "" >> "$env_file"
if grep -q "^${var_name}=" "$env_file" 2>/dev/null; then
sed -i "s|^${var_name}=.*|${var_name}=${var_value}|" "$env_file"
else
echo "${var_name}=${var_value}" >> "$env_file"
fi
}
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 "CLICKHOUSE_URL" "http://default:clickhousePassword@localhost:8123/twenty"
set_env_var "CLICKHOUSE_PASSWORD" "clickhousePassword"
npx nx run twenty-server:database:init:prod
- name: Seed main branch database with test data
run: |
npx nx command-no-deps twenty-server -- workspace:seed:dev
- name: Start main branch server in background
run: |
echo "=== Main branch .env file contents ==="
cat packages/twenty-server/.env
echo "=== Starting main branch server ==="
nohup npx nx run twenty-server:start:prod > /tmp/main-server.log 2>&1 &
echo $! > /tmp/main-server.pid
echo "Main server PID: $(cat /tmp/main-server.pid)"
- name: Wait for main branch server to be ready
run: |
echo "Waiting for main branch server to start..."
timeout=300
interval=5
elapsed=0
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
echo "Main branch server is ready!"
break
fi
echo "Main branch server not ready yet, waiting ${interval}s..."
sleep $interval
elapsed=$((elapsed + interval))
done
if [ $elapsed -ge $timeout ]; then
echo "Timeout waiting for main branch server to start"
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
run: |
# Read admin token from shared test tokens file (single source of truth)
ADMIN_TOKEN=$(jq -r '.APPLE_JANE_ADMIN_ACCESS_TOKEN' packages/twenty-server/test/integration/constants/test-tokens.json)
# Load introspection query from file
INTROSPECTION_QUERY=$(cat packages/twenty-utils/graphql-introspection-query.graphql)
# Prepare the query payload
QUERY_PAYLOAD=$(echo "$INTROSPECTION_QUERY" | tr '\n' ' ' | sed 's/"/\\"/g')
echo "Downloading GraphQL schema from main server..."
curl -X POST "http://localhost:${{ env.MAIN_SERVER_PORT }}/graphql" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${ADMIN_TOKEN}" \
-d "{\"query\":\"${QUERY_PAYLOAD}\"}" \
-o main-schema-introspection.json \
-w "HTTP Status: %{http_code}\n" \
-s
echo "Downloading GraphQL metadata schema from main server..."
curl -X POST "http://localhost:${{ env.MAIN_SERVER_PORT }}/metadata" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${ADMIN_TOKEN}" \
-d "{\"query\":\"${QUERY_PAYLOAD}\"}" \
-o main-metadata-schema-introspection.json \
-w "HTTP Status: %{http_code}\n" \
-s
# Download main branch OpenAPI specs
echo "Downloading OpenAPI specifications from main server..."
curl -s "http://localhost:${{ env.MAIN_SERVER_PORT }}/rest/open-api/core" \
-H "Authorization: Bearer ${ADMIN_TOKEN}" \
-o main-rest-api.json \
-w "HTTP Status: %{http_code}\n"
curl -s "http://localhost:${{ env.MAIN_SERVER_PORT }}/rest/open-api/metadata" \
-H "Authorization: Bearer ${ADMIN_TOKEN}" \
-o main-rest-metadata-api.json \
-w "HTTP Status: %{http_code}\n"
# Verify the downloads
echo "Main branch files downloaded:"
ls -la main-*
- name: Restore current branch files
run: |
# Move current branch files back to working directory
mv /tmp/current-branch-files/* . 2>/dev/null || echo "No files to restore"
# Verify all files are present
echo "All API files restored:"
ls -la current-* main-* 2>/dev/null || echo "Some files may be missing"
# Clean up temp directory
rm -rf /tmp/current-branch-files
- name: Validate downloaded schema files
id: validate-schemas
run: |
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
if [ ! -f "$file" ] || ! jq empty "$file" 2>/dev/null; then
echo "::warning::Invalid or missing schema file: $file"
valid=false
fi
done
echo "valid=$valid" >> $GITHUB_OUTPUT
- name: Install OpenAPI Diff Tool
run: |
# Using the Java-based OpenAPITools/openapi-diff via Docker
echo "Using OpenAPITools/openapi-diff via Docker"
- name: Generate GraphQL Schema Diff Reports
if: steps.validate-schemas.outputs.valid == 'true'
run: |
echo "=== INSTALLING GRAPHQL INSPECTOR CLI ==="
npm install -g @graphql-inspector/cli
echo "=== GENERATING GRAPHQL DIFF REPORTS ==="
# Check if GraphQL schema has changes
echo "Checking GraphQL schema for changes..."
if graphql-inspector diff main-schema-introspection.json current-schema-introspection.json >/dev/null 2>&1; then
echo "✅ No changes in GraphQL schema"
else
echo "⚠️ Changes detected in GraphQL schema, generating report..."
echo "# GraphQL Schema Changes" > graphql-schema-diff.md
echo "" >> graphql-schema-diff.md
graphql-inspector diff main-schema-introspection.json current-schema-introspection.json >> graphql-schema-diff.md 2>&1 || {
echo "⚠️ **Breaking changes or errors detected in GraphQL schema**" >> graphql-schema-diff.md
echo "" >> graphql-schema-diff.md
echo "\`\`\`" >> graphql-schema-diff.md
graphql-inspector diff main-schema-introspection.json current-schema-introspection.json 2>&1 >> graphql-schema-diff.md || echo "Error generating diff" >> graphql-schema-diff.md
echo "\`\`\`" >> graphql-schema-diff.md
}
fi
# Check if GraphQL metadata schema has changes
echo "Checking GraphQL metadata schema for changes..."
if graphql-inspector diff main-metadata-schema-introspection.json current-metadata-schema-introspection.json >/dev/null 2>&1; then
echo "✅ No changes in GraphQL metadata schema"
else
echo "⚠️ Changes detected in GraphQL metadata schema, generating report..."
echo "# GraphQL Metadata Schema Changes" > graphql-metadata-diff.md
echo "" >> graphql-metadata-diff.md
graphql-inspector diff main-metadata-schema-introspection.json current-metadata-schema-introspection.json >> graphql-metadata-diff.md 2>&1 || {
echo "⚠️ **Breaking changes or errors detected in GraphQL metadata schema**" >> graphql-metadata-diff.md
echo "" >> graphql-metadata-diff.md
echo "\`\`\`" >> graphql-metadata-diff.md
graphql-inspector diff main-metadata-schema-introspection.json current-metadata-schema-introspection.json 2>&1 >> graphql-metadata-diff.md || echo "Error generating diff" >> graphql-metadata-diff.md
echo "\`\`\`" >> graphql-metadata-diff.md
}
fi
# Show summary
echo "Generated diff files:"
ls -la *-diff.md 2>/dev/null || echo "No diff files generated (no changes detected)"
- name: Check REST API Breaking Changes
if: steps.validate-schemas.outputs.valid == 'true'
run: |
echo "=== CHECKING REST API FOR BREAKING CHANGES ==="
# Use the Java-based openapi-diff via Docker
docker run --rm -v "$(pwd):/specs" openapitools/openapi-diff:latest \
--json /specs/rest-api-diff.json \
/specs/main-rest-api.json /specs/current-rest-api.json || echo "OpenAPI diff completed with exit code $?"
# Check if the output file was created and is valid JSON
if [ -f "rest-api-diff.json" ] && jq empty rest-api-diff.json 2>/dev/null; then
# Check for breaking changes using Java openapi-diff JSON structure
incompatible=$(jq -r '.incompatible // false' rest-api-diff.json)
different=$(jq -r '.different // false' rest-api-diff.json)
# Count changes
new_endpoints=$(jq -r '.newEndpoints | length' rest-api-diff.json 2>/dev/null || echo "0")
missing_endpoints=$(jq -r '.missingEndpoints | length' rest-api-diff.json 2>/dev/null || echo "0")
changed_operations=$(jq -r '.changedOperations | length' rest-api-diff.json 2>/dev/null || echo "0")
if [ "$incompatible" = "true" ]; then
echo "❌ Breaking changes detected in REST API"
# Generate breaking changes report
echo "# REST API Breaking Changes" > rest-api-diff.md
echo "" >> rest-api-diff.md
echo "⚠️ **Breaking changes detected that may affect existing API consumers**" >> rest-api-diff.md
echo "" >> rest-api-diff.md
# Parse and format the changes from Java openapi-diff
jq -r '
if (.missingEndpoints | length) > 0 then
"## 🚨 Removed Endpoints (" + (.missingEndpoints | length | tostring) + ")\n" +
(.missingEndpoints | map("- **" + .method + " " + .pathUrl + "**: " + (.summary // "")) | join("\n"))
else "" end,
if (.changedOperations | length) > 0 then
"\n## ⚠️ Changed Operations (" + (.changedOperations | length | tostring) + ")\n" +
(.changedOperations | map("- **" + .method + " " + .pathUrl + "**: " + (.summary // "Modified operation")) | join("\n"))
else "" end,
if (.newEndpoints | length) > 0 then
"\n## ✅ New Endpoints (" + (.newEndpoints | length | tostring) + ")\n" +
(.newEndpoints | map("- " + .method + " " + .pathUrl + ": " + (.summary // "")) | join("\n"))
else "" end
' rest-api-diff.json >> rest-api-diff.md
elif [ "$different" = "true" ]; then
echo "📝 Non-breaking changes detected ($new_endpoints new endpoints, $missing_endpoints removed, $changed_operations changed) - no PR comment will be posted"
# Don't create markdown file for non-breaking changes to avoid PR comments
else
echo "✅ No changes detected in REST API"
# Don't create diff file for no changes
fi
else
echo "⚠️ OpenAPI diff tool could not process the files"
echo "# REST API Analysis Error" > rest-api-diff.md
echo "" >> rest-api-diff.md
echo "⚠️ **Error occurred while analyzing REST API changes**" >> rest-api-diff.md
echo "" >> rest-api-diff.md
echo "## Error Output" >> rest-api-diff.md
echo "\`\`\`" >> rest-api-diff.md
docker run --rm -v "$(pwd):/specs" openapitools/openapi-diff:latest /specs/main-rest-api.json /specs/current-rest-api.json 2>&1 >> rest-api-diff.md || echo "Could not capture error output"
echo "\`\`\`" >> rest-api-diff.md
# Don't fail the workflow for tool errors
echo "::warning::REST API analysis tool error - continuing workflow"
fi
- name: Check REST Metadata API Breaking Changes
if: steps.validate-schemas.outputs.valid == 'true'
run: |
echo "=== CHECKING REST METADATA API FOR BREAKING CHANGES ==="
# Use the Java-based openapi-diff for metadata API as well
docker run --rm -v "$(pwd):/specs" openapitools/openapi-diff:latest \
--json /specs/rest-metadata-api-diff.json \
/specs/main-rest-metadata-api.json /specs/current-rest-metadata-api.json || echo "OpenAPI diff completed with exit code $?"
# Check if the output file was created and is valid JSON
if [ -f "rest-metadata-api-diff.json" ] && jq empty rest-metadata-api-diff.json 2>/dev/null; then
# Check for breaking changes using Java openapi-diff JSON structure
incompatible=$(jq -r '.incompatible // false' rest-metadata-api-diff.json)
different=$(jq -r '.different // false' rest-metadata-api-diff.json)
# Count changes
new_endpoints=$(jq -r '.newEndpoints | length' rest-metadata-api-diff.json 2>/dev/null || echo "0")
missing_endpoints=$(jq -r '.missingEndpoints | length' rest-metadata-api-diff.json 2>/dev/null || echo "0")
changed_operations=$(jq -r '.changedOperations | length' rest-metadata-api-diff.json 2>/dev/null || echo "0")
if [ "$incompatible" = "true" ]; then
echo "❌ Breaking changes detected in REST Metadata API"
# Generate breaking changes report (only for breaking changes)
echo "# REST Metadata API Breaking Changes" > rest-metadata-api-diff.md
echo "" >> rest-metadata-api-diff.md
echo "⚠️ **Breaking changes detected that may affect existing API consumers**" >> rest-metadata-api-diff.md
echo "" >> rest-metadata-api-diff.md
# Parse and format the changes from Java openapi-diff
jq -r '
if (.missingEndpoints | length) > 0 then
"## 🚨 Removed Endpoints (" + (.missingEndpoints | length | tostring) + ")\n" +
(.missingEndpoints | map("- **" + .method + " " + .pathUrl + "**: " + (.summary // "")) | join("\n"))
else "" end,
if (.changedOperations | length) > 0 then
"\n## ⚠️ Changed Operations (" + (.changedOperations | length | tostring) + ")\n" +
(.changedOperations | map("- **" + .method + " " + .pathUrl + "**: " + (.summary // "Modified operation")) | join("\n"))
else "" end,
if (.newEndpoints | length) > 0 then
"\n## ✅ New Endpoints (" + (.newEndpoints | length | tostring) + ")\n" +
(.newEndpoints | map("- " + .method + " " + .pathUrl + ": " + (.summary // "")) | join("\n"))
else "" end
' rest-metadata-api-diff.json >> rest-metadata-api-diff.md
elif [ "$different" = "true" ]; then
echo "📝 Non-breaking changes detected ($new_endpoints new endpoints, $missing_endpoints removed, $changed_operations changed) - no PR comment will be posted"
# Don't create markdown file for non-breaking changes to avoid PR comments
else
echo "✅ No changes detected in REST Metadata API"
fi
else
echo "⚠️ OpenAPI diff tool could not process the metadata API files"
echo "# REST Metadata API Analysis Error" > rest-metadata-api-diff.md
echo "" >> rest-metadata-api-diff.md
echo "⚠️ **Error occurred while analyzing REST Metadata API changes**" >> rest-metadata-api-diff.md
echo "" >> rest-metadata-api-diff.md
echo "## Error Output" >> rest-metadata-api-diff.md
echo "\`\`\`" >> rest-metadata-api-diff.md
docker run --rm -v "$(pwd):/specs" openapitools/openapi-diff:latest /specs/main-rest-metadata-api.json /specs/current-rest-metadata-api.json 2>&1 >> rest-metadata-api-diff.md || echo "Could not capture error output"
echo "\`\`\`" >> rest-metadata-api-diff.md
# Don't fail the workflow for tool errors
echo "::warning::REST Metadata API analysis tool error - continuing workflow"
fi
- name: Upload breaking changes report
if: always()
uses: actions/upload-artifact@v4
with:
name: breaking-changes-report
path: |
*-diff.md
*-diff.json
if-no-files-found: ignore
retention-days: 3
- name: Cleanup servers
if: always()
run: |
if [ -f /tmp/current-server.pid ]; then
kill $(cat /tmp/current-server.pid) || true
fi
if [ -f /tmp/main-server.pid ]; then
kill $(cat /tmp/main-server.pid) || true
fi