From d3d7a08193e37f5ecf5d5259c2c02b45fc4759eb Mon Sep 17 00:00:00 2001 From: Genevieve Nuebel Date: Wed, 18 Feb 2026 13:36:29 -0700 Subject: [PATCH 1/4] Add versioned-sdk-dispatch.yml (dry-run mode) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New workflow that detects changes to versioned OAS files (openapi/v*.yml) on PR merge, builds api_versions list, reads version label (minor/patch), and dispatches to opted-in SDK repos only. Currently in DRY RUN mode — logs what it would send without dispatching. Includes workflow_dispatch for manual runs with version/api_versions inputs. Only mx-platform-node opted in initially. --- .github/workflows/versioned-sdk-dispatch.yml | 188 +++++++++++++++++++ 1 file changed, 188 insertions(+) create mode 100644 .github/workflows/versioned-sdk-dispatch.yml diff --git a/.github/workflows/versioned-sdk-dispatch.yml b/.github/workflows/versioned-sdk-dispatch.yml new file mode 100644 index 0000000..3b9b2e9 --- /dev/null +++ b/.github/workflows/versioned-sdk-dispatch.yml @@ -0,0 +1,188 @@ +name: Versioned SDK Dispatch + +# Dispatches repository_dispatch to opted-in SDK repos when versioned OAS files change. +# Currently in DRY RUN mode — logs what it would dispatch without sending anything. +# See PR #XX for context. Replaces update.yml (now update.yml.bak). + +on: + pull_request: + types: [closed] + paths: + - 'openapi/v*.yml' + workflow_dispatch: + inputs: + version_level: + description: "Bump version" + required: true + default: "minor" + type: choice + options: + - minor + - patch + api_versions: + description: "Comma-separated API versions (e.g., v20111101,v20250224)" + required: true + type: string + +jobs: + # ────────────────────────────────────────────────── + # Job: Detect which versioned OAS files changed (PR merge only) + # ────────────────────────────────────────────────── + detect-changes: + name: Detect Changed OAS Files + runs-on: ubuntu-latest + if: github.event_name == 'pull_request' && github.event.pull_request.merged == true + outputs: + api_versions: ${{ steps.build-versions.outputs.api_versions }} + version: ${{ steps.read-label.outputs.version }} + has_changes: ${{ steps.build-versions.outputs.has_changes }} + steps: + - uses: actions/checkout@v3 + + - name: Get changed versioned OAS files + id: changed-files + uses: tj-actions/changed-files@v41 + with: + files: openapi/v*.yml + + - name: Build api_versions from changed files + id: build-versions + run: | + CHANGED="${{ steps.changed-files.outputs.all_changed_files }}" + echo "Changed files: $CHANGED" + + if [ -z "$CHANGED" ]; then + echo "No versioned OAS files changed" + echo "has_changes=false" >> $GITHUB_OUTPUT + echo "api_versions=" >> $GITHUB_OUTPUT + exit 0 + fi + + # Extract version names from filenames (e.g., openapi/v20111101.yml → v20111101) + API_VERSIONS="" + for file in $CHANGED; do + # Get filename without path and extension + basename=$(basename "$file" .yml) + if [ -n "$API_VERSIONS" ]; then + API_VERSIONS="${API_VERSIONS},${basename}" + else + API_VERSIONS="${basename}" + fi + done + + echo "api_versions=$API_VERSIONS" >> $GITHUB_OUTPUT + echo "has_changes=true" >> $GITHUB_OUTPUT + echo "Detected API versions: $API_VERSIONS" + + - name: Read version label from PR + id: read-label + run: | + LABELS='${{ toJson(github.event.pull_request.labels.*.name) }}' + echo "PR labels: $LABELS" + + if echo "$LABELS" | jq -e 'map(select(. == "patch")) | length > 0' > /dev/null 2>&1; then + echo "version=patch" >> $GITHUB_OUTPUT + echo "Version label: patch" + else + # Default to minor if no patch label found + echo "version=minor" >> $GITHUB_OUTPUT + echo "Version label: minor (default)" + fi + + # ────────────────────────────────────────────────── + # Job: Dispatch to opted-in SDK repos (PR merge) + # ────────────────────────────────────────────────── + dispatch: + name: "[DRY RUN] Dispatch to ${{ matrix.repo }}" + needs: detect-changes + if: needs.detect-changes.outputs.has_changes == 'true' + runs-on: ubuntu-latest + strategy: + matrix: + repo: + - mxenabled/mx-platform-node + # Add more repos here as they are migrated to multi-version support: + # - mxenabled/mx-platform-ruby + # - mxenabled/mx-platform-python + # - mxenabled/mx-platform-java + # - mxenabled/mx-platform-csharp + # - mxenabled/mx-platform-go + steps: + # ────────────────────────────────────────────── + # DRY RUN: Replace this step with actual repository_dispatch in PR 2 + # ────────────────────────────────────────────── + - name: "[DRY RUN] Would dispatch to ${{ matrix.repo }}" + run: | + echo "============================================" + echo " DRY RUN — No dispatch sent" + echo "============================================" + echo "" + echo "Repository: ${{ matrix.repo }}" + echo "Event type: automated_push_to_master" + echo "Version: ${{ needs.detect-changes.outputs.version }}" + echo "Commit SHA: ${{ github.sha }}" + echo "API versions: ${{ needs.detect-changes.outputs.api_versions }}" + echo "" + echo "Payload that would be sent:" + echo ' {"version":"${{ needs.detect-changes.outputs.version }}","commit_sha":"${{ github.sha }}","api_versions":"${{ needs.detect-changes.outputs.api_versions }}"}' + echo "" + echo "============================================" + + - name: Slack notification + uses: ravsamhq/notify-slack-action@v2 + if: failure() + with: + status: ${{ job.status }} + token: ${{ secrets.GITHUB_TOKEN }} + notification_title: "{repo}: {workflow} workflow" + message_format: "{emoji} *{workflow}* {status_message} in <{repo_url}|{repo}>" + footer: "<{workflow_url}|View Workflow>" + notify_when: "failure" + env: + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} + + # ────────────────────────────────────────────────── + # Job: Manual dispatch (workflow_dispatch) + # ────────────────────────────────────────────────── + manual-dispatch: + name: "[DRY RUN] Manual dispatch to ${{ matrix.repo }}" + runs-on: ubuntu-latest + if: github.event_name == 'workflow_dispatch' + strategy: + matrix: + repo: + - mxenabled/mx-platform-node + # Add more repos here as they are migrated to multi-version support + steps: + # ────────────────────────────────────────────── + # DRY RUN: Replace this step with actual repository_dispatch in PR 2 + # ────────────────────────────────────────────── + - name: "[DRY RUN] Would dispatch to ${{ matrix.repo }}" + run: | + echo "============================================" + echo " DRY RUN — No dispatch sent (manual)" + echo "============================================" + echo "" + echo "Repository: ${{ matrix.repo }}" + echo "Event type: automated_push_to_master" + echo "Version: ${{ github.event.inputs.version_level }}" + echo "Commit SHA: ${{ github.sha }}" + echo "API versions: ${{ github.event.inputs.api_versions }}" + echo "" + echo "Payload that would be sent:" + echo ' {"version":"${{ github.event.inputs.version_level }}","commit_sha":"${{ github.sha }}","api_versions":"${{ github.event.inputs.api_versions }}"}' + echo "" + echo "============================================" + + - name: Slack notification + uses: ravsamhq/notify-slack-action@v2 + if: failure() + with: + status: ${{ job.status }} + token: ${{ secrets.GITHUB_TOKEN }} + notification_title: "{repo}: {workflow} workflow" + message_format: "{emoji} *{workflow}* {status_message} in <{repo_url}|{repo}>" + footer: "<{workflow_url}|View Workflow>" + notify_when: "failure" + env: + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} From a0f3159906be6e55a55539d41b7007dc853431a9 Mon Sep 17 00:00:00 2001 From: Genevieve Nuebel Date: Wed, 18 Feb 2026 13:36:44 -0700 Subject: [PATCH 2/4] =?UTF-8?q?Deprecate=20update.yml=20=E2=80=94=20rename?= =?UTF-8?q?=20to=20update.yml.bak?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Old workflow dispatched to all 6 SDK repos on any mx_platform_api.yml change. Replaced by versioned-sdk-dispatch.yml which detects versioned OAS files and dispatches only to opted-in repos. Kept as .bak for reference. --- .github/workflows/{update.yml => update.yml.bak} | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) rename .github/workflows/{update.yml => update.yml.bak} (80%) diff --git a/.github/workflows/update.yml b/.github/workflows/update.yml.bak similarity index 80% rename from .github/workflows/update.yml rename to .github/workflows/update.yml.bak index de126e2..58ed482 100644 --- a/.github/workflows/update.yml +++ b/.github/workflows/update.yml.bak @@ -1,4 +1,16 @@ -name: Update +# ────────────────────────────────────────────────────────────────────── +# DEPRECATED — February 18, 2026 +# Replaced by: versioned-sdk-dispatch.yml +# +# This workflow dispatched to ALL 6 SDK repos whenever mx_platform_api.yml +# changed. It has been replaced by versioned-sdk-dispatch.yml which: +# - Detects changes to versioned OAS files (openapi/v*.yml) +# - Dispatches only to opted-in SDK repos +# - Passes api_versions in the payload +# +# Kept as .bak for reference. GitHub Actions ignores .bak files. +# ────────────────────────────────────────────────────────────────────── +name: "[DEPRECATED] Update" on: pull_request: From c52dab482af21d33bdd498159702a032dc3e9fd4 Mon Sep 17 00:00:00 2001 From: Genevieve Nuebel Date: Wed, 18 Feb 2026 13:36:51 -0700 Subject: [PATCH 3/4] =?UTF-8?q?Update=20version.yml=20=E2=80=94=20enforce?= =?UTF-8?q?=20labels=20on=20versioned=20OAS=20files?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Changed-files check now includes openapi/v*.yml in addition to mx_platform_api.yml. PRs that modify versioned OAS files now require a minor or patch label before merge. --- .github/workflows/version.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/version.yml b/.github/workflows/version.yml index 0d870b6..e80d2f8 100644 --- a/.github/workflows/version.yml +++ b/.github/workflows/version.yml @@ -12,11 +12,13 @@ jobs: pull-requests: write steps: - uses: actions/checkout@v3 - - name: Get openapi file + + - name: Check for changed OAS files id: changed-files-specific uses: tj-actions/changed-files@v41 with: - files: openapi/mx_platform_api.yml + files: | + openapi/v*.yml - name: Require version label if openapi spec changed if: steps.changed-files-specific.outputs.any_changed == 'true' From 63e9568a0a6a68ca9cacf40578baf4e93f012717 Mon Sep 17 00:00:00 2001 From: Genevieve Nuebel Date: Wed, 18 Feb 2026 13:36:59 -0700 Subject: [PATCH 4/4] =?UTF-8?q?Update=20sdk-generation-validation.yml=20?= =?UTF-8?q?=E2=80=94=20use=20versioned=20OAS=20files?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaced static mx_platform_api.yml reference with dynamic discovery of changed versioned OAS files (openapi/v*.yml). Uses a discover job to build a matrix from changed files, then cross-products with the language matrix. Only runs generation validation for OAS files actually modified in the PR. --- .../workflows/sdk-generation-validation.yml | 135 ++++++++++++------ 1 file changed, 88 insertions(+), 47 deletions(-) diff --git a/.github/workflows/sdk-generation-validation.yml b/.github/workflows/sdk-generation-validation.yml index fb45a4b..71d49fa 100644 --- a/.github/workflows/sdk-generation-validation.yml +++ b/.github/workflows/sdk-generation-validation.yml @@ -2,101 +2,142 @@ name: Test SDK Generation Validation on: pull_request: - paths: - - 'openapi/mx_platform_api.yml' + paths: + - 'openapi/v*.yml' jobs: + # ────────────────────────────────────────────────── + # Discover which versioned OAS files changed in this PR + # ────────────────────────────────────────────────── + discover-changed-files: + name: Discover Changed OAS Files + runs-on: ubuntu-latest + outputs: + matrix: ${{ steps.build-matrix.outputs.matrix }} + has_files: ${{ steps.build-matrix.outputs.has_files }} + steps: + - uses: actions/checkout@v3 + + - name: Get changed versioned OAS files + id: changed-files + uses: tj-actions/changed-files@v41 + with: + files: openapi/v*.yml + + - name: Build matrix from changed files + id: build-matrix + run: | + CHANGED="${{ steps.changed-files.outputs.all_changed_files }}" + echo "Changed versioned OAS files: $CHANGED" + + if [ -z "$CHANGED" ]; then + echo "has_files=false" >> $GITHUB_OUTPUT + echo "matrix=[]" >> $GITHUB_OUTPUT + exit 0 + fi + + # Build a JSON array of changed OAS files + MATRIX="[" + FIRST=true + for file in $CHANGED; do + filename=$(basename "$file" .yml) + if [ "$FIRST" = true ]; then + FIRST=false + else + MATRIX="${MATRIX}," + fi + MATRIX="${MATRIX}{\"openapi_file\":\"${file}\",\"version_name\":\"${filename}\"}" + done + MATRIX="${MATRIX}]" + + echo "matrix=${MATRIX}" >> $GITHUB_OUTPUT + echo "has_files=true" >> $GITHUB_OUTPUT + echo "Generated matrix: ${MATRIX}" + + # ────────────────────────────────────────────────── + # Run SDK generation validation for each changed OAS file x each language + # ────────────────────────────────────────────────── validate-sdk-generation: + name: "${{ matrix.lang.display_name }} (${{ matrix.version.version_name }})" + needs: discover-changed-files + if: needs.discover-changed-files.outputs.has_files == 'true' runs-on: ubuntu-latest strategy: fail-fast: false matrix: - include: - - language: java - generator: java - display_name: Java - - language: ruby - generator: ruby - display_name: Ruby - - language: python - generator: python - display_name: Python - - language: node - generator: typescript-axios - display_name: Node - - language: csharp - generator: csharp - display_name: C# - - language: go - generator: go - display_name: Go - - name: ${{ matrix.display_name }} SDK Generation - + version: ${{ fromJson(needs.discover-changed-files.outputs.matrix) }} + lang: + - { language: java, generator: java, display_name: Java } + - { language: ruby, generator: ruby, display_name: Ruby } + - { language: python, generator: python, display_name: Python } + - { language: node, generator: "typescript-axios", display_name: Node } + - { language: csharp, generator: csharp, display_name: "C#" } + - { language: go, generator: go, display_name: Go } + steps: - name: Checkout repository uses: actions/checkout@v3 - + - name: Set up Node.js uses: actions/setup-node@v3 with: node-version: '20' - + - name: Install OpenAPI Generator CLI run: npm install -g @openapitools/openapi-generator-cli - + - name: Create output directory - run: mkdir -p ./sdk-output/${{ matrix.language }} - - - name: Generate Test SDK + run: mkdir -p ./sdk-output/${{ matrix.lang.language }} + + - name: Generate Test SDK from ${{ matrix.version.version_name }} run: | openapi-generator-cli generate \ - -i openapi/mx_platform_api.yml \ - -g ${{ matrix.generator }} \ - -o ./sdk-output/${{ matrix.language }} \ + -i ${{ matrix.version.openapi_file }} \ + -g ${{ matrix.lang.generator }} \ + -o ./sdk-output/${{ matrix.lang.language }} \ --skip-validate-spec - + - name: Verify expected files were generated run: | - echo "Checking for generated files in ./sdk-output/${{ matrix.language }}" - ls -la ./sdk-output/${{ matrix.language }} - - cd ./sdk-output/${{ matrix.language }} - + echo "Checking for generated files in ./sdk-output/${{ matrix.lang.language }}" + ls -la ./sdk-output/${{ matrix.lang.language }} + + cd ./sdk-output/${{ matrix.lang.language }} + # Check for key files based on language - case "${{ matrix.language }}" in + case "${{ matrix.lang.language }}" in "java") find . -name "pom.xml" -quit > /dev/null || (echo "❌ Missing pom.xml" && exit 1) find . -name "*.java" -quit > /dev/null || (echo "❌ No Java files found" && exit 1) - echo "✅ Java SDK structure validated" + echo "✅ Java SDK structure validated (${{ matrix.version.version_name }})" ;; "ruby") find . -name "*.gemspec" -quit > /dev/null || (echo "❌ Missing gemspec file" && exit 1) find . -name "*.rb" -quit > /dev/null || (echo "❌ No Ruby files found" && exit 1) - echo "✅ Ruby SDK structure validated" + echo "✅ Ruby SDK structure validated (${{ matrix.version.version_name }})" ;; "python") find . -name "setup.py" -quit > /dev/null || (echo "❌ Missing setup.py" && exit 1) find . -name "*.py" -quit > /dev/null || (echo "❌ No Python files found" && exit 1) - echo "✅ Python SDK structure validated" + echo "✅ Python SDK structure validated (${{ matrix.version.version_name }})" ;; "node") find . -name "package.json" -quit > /dev/null || (echo "❌ Missing package.json" && exit 1) find . \( -name "*.ts" -o -name "*.js" \) -quit > /dev/null || (echo "❌ No TypeScript/JavaScript files found" && exit 1) - echo "✅ Node SDK structure validated" + echo "✅ Node SDK structure validated (${{ matrix.version.version_name }})" ;; "csharp") find . -name "*.csproj" -quit > /dev/null || (echo "❌ Missing csproj file" && exit 1) find . -name "*.cs" -quit > /dev/null || (echo "❌ No C# files found" && exit 1) - echo "✅ C# SDK structure validated" + echo "✅ C# SDK structure validated (${{ matrix.version.version_name }})" ;; "go") find . -name "go.mod" -quit > /dev/null || (echo "❌ Missing go.mod" && exit 1) find . -name "*.go" -quit > /dev/null || (echo "❌ No Go files found" && exit 1) - echo "✅ Go SDK structure validated" + echo "✅ Go SDK structure validated (${{ matrix.version.version_name }})" ;; esac - name: Clean up if: always() - run: rm -rf ./sdk-output/${{ matrix.language }} \ No newline at end of file + run: rm -rf ./sdk-output/${{ matrix.lang.language }} \ No newline at end of file