Merge pull request #363 from rush-db/feat/update-release-and-deploy-p… #325
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Release and Deploy | |
| # Nothing ships when a feature/fix branch lands on main. Shipping is triggered by | |
| # merging the changesets "version packages" PR, which is the only commit that | |
| # changes package.json versions — see .github/scripts/detect-version-bumps.mjs. | |
| # | |
| # Each artifact is versioned and shipped on its own: | |
| # rushdb/platform ← rushdb-core's version, rebuilt when core or dashboard bumps | |
| # rushdb/mcp-server ← @rushdb/mcp-server's version, rebuilt when it bumps | |
| # npm packages ← published by changesets when a public package bumps | |
| # | |
| # So a core-only patch redeploys the platform without touching npm, and an | |
| # sdk-only patch flows into both images via the dependent bumps changesets makes | |
| # (updateInternalDependencies: patch). Minor releases land on one shared version | |
| # for everything they touch (`linked` in .changeset/config.json). | |
| on: | |
| push: | |
| branches: | |
| - main | |
| workflow_dispatch: | |
| inputs: | |
| force_platform: | |
| description: 'Rebuild and redeploy rushdb/platform at its current version' | |
| type: boolean | |
| default: false | |
| force_mcp: | |
| description: 'Rebuild and redeploy rushdb/mcp-server at its current version' | |
| type: boolean | |
| default: false | |
| permissions: | |
| contents: write | |
| issues: write | |
| pull-requests: write | |
| id-token: write | |
| # Releases must not overlap; a second push waits rather than cancelling a | |
| # half-finished publish/deploy. | |
| concurrency: | |
| group: release-${{ github.ref }} | |
| cancel-in-progress: false | |
| env: | |
| ECS_SERVICE_NEST: 'rushdb-ecs-service' | |
| ECS_SERVICE_MCP: 'rushdb-mcp-service' | |
| ECS_CLUSTER: 'rushdb-ecs-cluster' | |
| jobs: | |
| release: | |
| name: Release | |
| runs-on: ubuntu-latest | |
| outputs: | |
| platform_changed: ${{ steps.bumps.outputs.platform_changed }} | |
| platform_version: ${{ steps.bumps.outputs.platform_version }} | |
| mcp_changed: ${{ steps.bumps.outputs.mcp_image_changed }} | |
| mcp_version: ${{ steps.bumps.outputs.mcp_version }} | |
| sdk_version: ${{ steps.bumps.outputs.sdk_version }} | |
| any_changed: ${{ steps.bumps.outputs.any_changed }} | |
| published: ${{ steps.changesets.outputs.published }} | |
| published_packages: ${{ steps.changesets.outputs.publishedPackages }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| # Runs before changesets so the working tree it may rewrite cannot affect | |
| # the comparison (the script reads git objects, not the tree, either way). | |
| - name: Detect version bumps | |
| id: bumps | |
| run: node .github/scripts/detect-version-bumps.mjs | |
| env: | |
| BASE_SHA: ${{ github.event.before }} | |
| HEAD_SHA: ${{ github.sha }} | |
| FORCE_PLATFORM: ${{ inputs.force_platform }} | |
| FORCE_MCP: ${{ inputs.force_mcp }} | |
| - name: Setup pnpm | |
| uses: pnpm/action-setup@v4 | |
| with: | |
| version: 10.1.0 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 22 | |
| cache: pnpm | |
| registry-url: 'https://registry.npmjs.org' | |
| - name: Install Dependencies | |
| run: pnpm install --frozen-lockfile | |
| - name: Build packages | |
| run: pnpm --filter ./packages/javascript-sdk... --filter ./packages/mcp-server... build | |
| - name: Setup .npmrc | |
| run: | | |
| cat << EOF > ~/.npmrc | |
| registry=https://registry.npmjs.org/ | |
| //registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }} | |
| access=public | |
| always-auth=true | |
| EOF | |
| # Opens/updates the "version packages" PR when changesets are pending, | |
| # publishes to npm when the versioned commit lands. `changeset publish` | |
| # covers every public workspace package (sdk, mcp-server, skills). | |
| - name: Create Release PR or Publish | |
| id: changesets | |
| if: github.event_name == 'push' | |
| uses: changesets/action@v1 | |
| with: | |
| version: pnpm run version | |
| publish: pnpm run release | |
| commit: 'chore(release): version packages' | |
| title: 'chore(release): version packages' | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} | |
| NPM_TOKEN: ${{ secrets.NPM_TOKEN }} | |
| platform-image: | |
| name: Platform image | |
| needs: release | |
| if: needs.release.outputs.platform_changed == 'true' | |
| uses: ./.github/workflows/docker-image.yml | |
| with: | |
| image: rushdb/platform | |
| slug: platform | |
| dockerfile: platform/Dockerfile | |
| version: ${{ needs.release.outputs.platform_version }} | |
| secrets: inherit | |
| mcp-image: | |
| name: MCP image | |
| needs: release | |
| if: needs.release.outputs.mcp_changed == 'true' | |
| uses: ./.github/workflows/docker-image.yml | |
| with: | |
| image: rushdb/mcp-server | |
| slug: mcp-server | |
| dockerfile: packages/mcp-server/Dockerfile | |
| version: ${{ needs.release.outputs.mcp_version }} | |
| secrets: inherit | |
| deploy-platform: | |
| name: Deploy platform to ECS | |
| needs: [release, platform-image] | |
| runs-on: ubuntu-22.04 | |
| environment: production | |
| steps: | |
| - name: Configure AWS credentials | |
| uses: aws-actions/configure-aws-credentials@v1 | |
| with: | |
| aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} | |
| aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} | |
| aws-region: ${{ vars.AWS_REGION }} | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Render task definition | |
| run: | | |
| cat <<EOF > task-definition.json | |
| { | |
| "containerDefinitions": [ | |
| { | |
| "name": "rushdb", | |
| "image": "${{ needs.platform-image.outputs.image }}", | |
| "cpu": 1024, | |
| "memory": 2048, | |
| "portMappings": [ | |
| { | |
| "containerPort": 3000, | |
| "hostPort": 3000, | |
| "protocol": "tcp", | |
| "appProtocol": "http" | |
| } | |
| ], | |
| "essential": true, | |
| "logConfiguration": { | |
| "logDriver": "awslogs", | |
| "options": { | |
| "awslogs-group": "/ecs/rushdb", | |
| "awslogs-region": "${{ vars.AWS_REGION }}", | |
| "awslogs-stream-prefix": "ecs-rushdb" | |
| } | |
| }, | |
| "environment": [ | |
| { "name": "NODE_ENV", "value": "production" }, | |
| { "name": "LOG_LEVEL", "value": "info" }, | |
| { "name": "RUSHDB_DASHBOARD_URL", "value": "${{ vars.RUSHDB_DASHBOARD_URL }}" }, | |
| { "name": "RUSHDB_AES_256_ENCRYPTION_KEY", "value": "${{ secrets.RUSHDB_AES_256_ENCRYPTION_KEY }}" }, | |
| { "name": "RUSHDB_JWT_PRIVATE_KEY_BASE64", "value": "${{ secrets.RUSHDB_JWT_PRIVATE_KEY_BASE64 }}" }, | |
| { "name": "RUSHDB_JWT_PUBLIC_KEY_BASE64", "value": "${{ secrets.RUSHDB_JWT_PUBLIC_KEY_BASE64 }}" }, | |
| { "name": "RUSHDB_JWT_KID", "value": "${{ secrets.RUSHDB_JWT_KID }}" }, | |
| { "name": "RUSHDB_SELF_HOSTED", "value": "false" }, | |
| { "name": "RUSHDB_SERVE_STATIC", "value": "false" }, | |
| { "name": "NEO4J_URL", "value": "${{ secrets.NEO4J_URL }}" }, | |
| { "name": "NEO4J_USERNAME", "value": "${{ secrets.NEO4J_USERNAME }}" }, | |
| { "name": "NEO4J_PASSWORD", "value": "${{ secrets.NEO4J_PASSWORD }}" }, | |
| { "name": "GOOGLE_CLIENT_ID", "value": "${{ secrets.GOOGLE_CLIENT_ID }}" }, | |
| { "name": "GOOGLE_SECRET", "value": "${{ secrets.GOOGLE_SECRET }}" }, | |
| { "name": "GH_CLIENT_ID", "value": "${{ secrets.GH_CLIENT_ID }}" }, | |
| { "name": "GH_SECRET", "value": "${{ secrets.GH_SECRET }}" }, | |
| { "name": "MAIL_HOST", "value": "${{ secrets.MAIL_HOST }}" }, | |
| { "name": "MAIL_USER", "value": "${{ secrets.MAIL_USER }}" }, | |
| { "name": "MAIL_PASSWORD", "value": "${{ secrets.MAIL_PASSWORD }}" }, | |
| { "name": "MAIL_FROM", "value": "${{ secrets.MAIL_FROM }}" }, | |
| { "name": "SERVICE_CAPTCHA_KEY", "value": "${{ secrets.SERVICE_CAPTCHA_KEY }}" }, | |
| { "name": "BILLING_SERVICE_URL", "value": "${{ secrets.BILLING_SERVICE_URL }}" }, | |
| { "name": "RUSHDB_BILLING_SECRET", "value": "${{ secrets.RUSHDB_BILLING_SECRET }}" }, | |
| { "name": "SQL_DB_TYPE", "value": "postgres" }, | |
| { "name": "SQL_DB_URL", "value": "${{ secrets.SQL_DB_URL }}" }, | |
| { "name": "RUSHDB_PUBLIC_URL", "value": "${{ vars.RUSHDB_PUBLIC_URL }}" }, | |
| { "name": "RUSHDB_OAUTH_ISSUER", "value": "${{ vars.RUSHDB_PUBLIC_URL }}" }, | |
| { "name": "RUSHDB_EMBEDDING_BASE_URL", "value": "${{ vars.RUSHDB_EMBEDDING_BASE_URL }}" }, | |
| { "name": "RUSHDB_EMBEDDING_API_KEY", "value": "${{ secrets.RUSHDB_EMBEDDING_API_KEY }}" }, | |
| { "name": "RUSHDB_EMBEDDING_MODEL", "value": "${{ vars.RUSHDB_EMBEDDING_MODEL }}" }, | |
| { "name": "RUSHDB_EMBEDDING_DIMENSIONS", "value": "${{ vars.RUSHDB_EMBEDDING_DIMENSIONS }}" }, | |
| { "name": "RUSHDB_LLM_BASE_URL", "value": "${{ vars.RUSHDB_LLM_BASE_URL }}" }, | |
| { "name": "RUSHDB_LLM_API_KEY", "value": "${{ secrets.RUSHDB_LLM_API_KEY }}" }, | |
| { "name": "RUSHDB_LLM_MODEL", "value": "${{ vars.RUSHDB_LLM_MODEL }}" } | |
| ] | |
| } | |
| ], | |
| "family": "rushdb-task-definition", | |
| "networkMode": "awsvpc", | |
| "requiresCompatibilities": ["FARGATE"], | |
| "cpu": "1024", | |
| "memory": "2048", | |
| "executionRoleArn": "${{ secrets.ECS_EXECUTION_ROLE_ARN }}" | |
| } | |
| EOF | |
| - name: Deploy platform to ECS | |
| uses: aws-actions/amazon-ecs-deploy-task-definition@v1 | |
| with: | |
| task-definition: task-definition.json | |
| service: ${{ env.ECS_SERVICE_NEST }} | |
| cluster: ${{ env.ECS_CLUSTER }} | |
| wait-for-service-stability: false | |
| deploy-mcp: | |
| name: Deploy MCP server to ECS | |
| needs: [release, mcp-image] | |
| runs-on: ubuntu-22.04 | |
| environment: production | |
| steps: | |
| - name: Configure AWS credentials | |
| uses: aws-actions/configure-aws-credentials@v1 | |
| with: | |
| aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} | |
| aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} | |
| aws-region: ${{ vars.AWS_REGION }} | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Render MCP task definition | |
| run: | | |
| cat <<EOF > mcp-task-definition.json | |
| { | |
| "containerDefinitions": [ | |
| { | |
| "name": "rushdb-mcp", | |
| "image": "${{ needs.mcp-image.outputs.image }}", | |
| "cpu": 512, | |
| "memory": 1024, | |
| "portMappings": [ | |
| { | |
| "containerPort": 3001, | |
| "hostPort": 3001, | |
| "protocol": "tcp", | |
| "appProtocol": "http" | |
| } | |
| ], | |
| "essential": true, | |
| "logConfiguration": { | |
| "logDriver": "awslogs", | |
| "options": { | |
| "awslogs-group": "/ecs/rushdb-mcp", | |
| "awslogs-region": "${{ vars.AWS_REGION }}", | |
| "awslogs-stream-prefix": "ecs-rushdb-mcp" | |
| } | |
| }, | |
| "environment": [ | |
| { "name": "MCP_TRANSPORT", "value": "http" }, | |
| { "name": "PORT", "value": "3001" }, | |
| { "name": "RUSHDB_API_URL", "value": "https://api.rushdb.com/api/v1" }, | |
| { "name": "MCP_RESOURCE_URL", "value": "https://mcp.rushdb.com" }, | |
| { "name": "RUSHDB_OAUTH_ISSUER", "value": "${{ vars.RUSHDB_PUBLIC_URL }}" } | |
| ] | |
| } | |
| ], | |
| "family": "rushdb-mcp-task-definition", | |
| "networkMode": "awsvpc", | |
| "requiresCompatibilities": ["FARGATE"], | |
| "cpu": "512", | |
| "memory": "1024", | |
| "executionRoleArn": "${{ secrets.ECS_EXECUTION_ROLE_ARN }}" | |
| } | |
| EOF | |
| - name: Deploy MCP server to ECS | |
| uses: aws-actions/amazon-ecs-deploy-task-definition@v1 | |
| with: | |
| task-definition: mcp-task-definition.json | |
| service: ${{ env.ECS_SERVICE_MCP }} | |
| cluster: ${{ env.ECS_CLUSTER }} | |
| wait-for-service-stability: false | |
| notify-content: | |
| name: Notify content control plane | |
| needs: [release, platform-image, mcp-image, deploy-platform, deploy-mcp] | |
| # Fires once the release has fully landed. A skipped artifact is fine (it had | |
| # no version bump); a failed or cancelled one is not — and a failed image | |
| # build must not slip through on the strength of its deploy being skipped. | |
| if: | | |
| always() && | |
| needs.release.outputs.any_changed == 'true' && | |
| !contains(fromJSON('["failure", "cancelled"]'), needs.platform-image.result) && | |
| !contains(fromJSON('["failure", "cancelled"]'), needs.mcp-image.result) && | |
| !contains(fromJSON('["failure", "cancelled"]'), needs.deploy-platform.result) && | |
| !contains(fromJSON('["failure", "cancelled"]'), needs.deploy-mcp.result) | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Send signed release event | |
| env: | |
| CONTENT_WEBHOOK_URL: ${{ secrets.CONTENT_WEBHOOK_URL }} | |
| CONTENT_WEBHOOK_SECRET: ${{ secrets.CONTENT_WEBHOOK_SECRET }} | |
| RELEASE_VERSION: ${{ needs.release.outputs.sdk_version || needs.release.outputs.platform_version }} | |
| PLATFORM_VERSION: ${{ needs.release.outputs.platform_version }} | |
| PUBLISHED_PACKAGES: ${{ needs.release.outputs.published_packages }} | |
| RELEASE_SHA: ${{ github.sha }} | |
| RELEASE_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} | |
| run: node .github/scripts/notify-content-release.mjs |