Sync OpenAPI Specs #316
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
| # Sync OpenAPI Specifications and Update Navigation | |
| # | |
| # Detects changed OpenAPI YAMLs in the source repo, then fans out | |
| # a matrix job per changed file. Each matrix job syncs its one YAML, | |
| # regenerates the nav entry in docs.json, and opens a dedicated PR. | |
| name: Sync OpenAPI Specs | |
| # ============================================================================ | |
| # CONFIGURATION - Update these values for your setup | |
| # ============================================================================ | |
| env: | |
| # Source repository (where OpenAPI YAMLs live) | |
| SOURCE_OWNER: domoinc | |
| SOURCE_REPO: internal-domo-apis | |
| SOURCE_YAML_PATH: api-docs/public | |
| # Destination paths (in this repo) | |
| DEST_YAML_PATH: openapi/product | |
| DOCS_JSON_PATH: ./docs.json | |
| OPENAPI_BASE_PATH: openapi/product | |
| # Git settings | |
| BASE_BRANCH: main | |
| # ============================================================================ | |
| on: | |
| schedule: | |
| - cron: "0 */6 * * *" # Every 6 hours | |
| workflow_dispatch: | |
| inputs: | |
| force_sync: | |
| description: "Force sync all YAML files" | |
| required: false | |
| type: boolean | |
| default: false | |
| repository_dispatch: | |
| types: [openapi-updated] | |
| jobs: | |
| detect: | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| outputs: | |
| has_changes: ${{ steps.check.outputs.has_changes }} | |
| files: ${{ steps.detect.outputs.files_json }} | |
| steps: | |
| - name: Generate Source Repo Token | |
| uses: actions/create-github-app-token@v1 | |
| id: source-token | |
| with: | |
| app-id: ${{ secrets.APP_ID }} | |
| private-key: ${{ secrets.APP_PRIVATE_KEY }} | |
| owner: ${{ env.SOURCE_OWNER }} | |
| repositories: ${{ env.SOURCE_REPO }} | |
| - name: Checkout Destination Repo | |
| uses: actions/checkout@v4 | |
| - name: Clone Source Repo | |
| uses: actions/checkout@v4 | |
| with: | |
| repository: ${{ env.SOURCE_OWNER }}/${{ env.SOURCE_REPO }} | |
| token: ${{ steps.source-token.outputs.token }} | |
| path: source-repo | |
| - name: Set up Python | |
| uses: actions/setup-python@v4 | |
| with: | |
| python-version: "3.10" | |
| - name: Detect Changed YAML Files | |
| id: detect | |
| run: | | |
| python .github/scripts/detect_yaml_changes.py \ | |
| --source "source-repo/${{ env.SOURCE_YAML_PATH }}" \ | |
| --dest "${{ env.DEST_YAML_PATH }}" \ | |
| --force ${{ github.event.inputs.force_sync || 'false' }} | |
| # Emit the changed file list as a JSON array so the matrix can fan out | |
| if [ -f changed_files.txt ] && [ -s changed_files.txt ]; then | |
| FILES_JSON=$(jq -R -s -c 'split("\n") | map(select(length > 0))' changed_files.txt) | |
| else | |
| FILES_JSON="[]" | |
| fi | |
| echo "files_json=$FILES_JSON" >> $GITHUB_OUTPUT | |
| - name: Check for Changes | |
| id: check | |
| run: | | |
| if [ -f changed_files.txt ] && [ -s changed_files.txt ]; then | |
| echo "has_changes=true" >> $GITHUB_OUTPUT | |
| echo "Changed files:" | |
| cat changed_files.txt | |
| else | |
| echo "has_changes=false" >> $GITHUB_OUTPUT | |
| echo "No changes detected" | |
| fi | |
| sync-file: | |
| needs: detect | |
| if: needs.detect.outputs.has_changes == 'true' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| strategy: | |
| fail-fast: false | |
| # Cap parallelism to reduce the chance of docs.json merge conflicts | |
| # between concurrent PRs. Sequential merges by the reviewer still | |
| # require rebases if docs.json regions overlap. | |
| max-parallel: 3 | |
| matrix: | |
| file: ${{ fromJSON(needs.detect.outputs.files) }} | |
| steps: | |
| - name: Generate Source Repo Token | |
| uses: actions/create-github-app-token@v1 | |
| id: source-token | |
| with: | |
| app-id: ${{ secrets.APP_ID }} | |
| private-key: ${{ secrets.APP_PRIVATE_KEY }} | |
| owner: ${{ env.SOURCE_OWNER }} | |
| repositories: ${{ env.SOURCE_REPO }} | |
| - name: Generate Destination Repo Token | |
| uses: actions/create-github-app-token@v1 | |
| id: dest-token | |
| with: | |
| app-id: ${{ secrets.APP_ID }} | |
| private-key: ${{ secrets.APP_PRIVATE_KEY }} | |
| - name: Checkout Destination Repo | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Clone Source Repo | |
| uses: actions/checkout@v4 | |
| with: | |
| repository: ${{ env.SOURCE_OWNER }}/${{ env.SOURCE_REPO }} | |
| token: ${{ steps.source-token.outputs.token }} | |
| path: source-repo | |
| - name: Copy file to destination | |
| id: copy | |
| run: | | |
| set -euo pipefail | |
| SRC="${{ matrix.file }}" | |
| BASENAME=$(basename "$SRC") | |
| DEST_DIR="${{ env.DEST_YAML_PATH }}" | |
| DEST="$DEST_DIR/$BASENAME" | |
| mkdir -p "$DEST_DIR" | |
| # If a case-variant of this filename exists in dest, remove it so the | |
| # new casing wins. `find -iname` is case-insensitive. | |
| find "$DEST_DIR" -maxdepth 1 -type f -iname "$BASENAME" ! -name "$BASENAME" -print -delete || true | |
| cp "$SRC" "$DEST" | |
| echo "Copied $SRC -> $DEST" | |
| STEM="${BASENAME%.*}" | |
| # Slugify the stem for use in branch/ref names | |
| SLUG=$(echo "$STEM" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/--*/-/g' | sed 's/^-//; s/-$//') | |
| echo "basename=$BASENAME" >> $GITHUB_OUTPUT | |
| echo "dest=$DEST" >> $GITHUB_OUTPUT | |
| echo "stem=$STEM" >> $GITHUB_OUTPUT | |
| echo "slug=$SLUG" >> $GITHUB_OUTPUT | |
| - name: Update API Navigation | |
| uses: DomoApps/documentation-generator-action@main | |
| with: | |
| yaml_input_path: "./${{ env.DEST_YAML_PATH }}" | |
| docs_json_path: "${{ env.DOCS_JSON_PATH }}" | |
| openapi_base_path: "${{ env.OPENAPI_BASE_PATH }}" | |
| process_changed_only: "true" | |
| changed_files: ${{ steps.copy.outputs.dest }} | |
| # Remove any OpenAPI nav entries in docs.json that reference YAMLs no | |
| # longer on disk (e.g. from upstream deletes or case-only renames like | |
| # Filesets.yaml -> filesets.yaml). Without this, preview deploys fail | |
| # because Mintlify tries to resolve a missing spec. | |
| - name: Prune stale OpenAPI nav entries | |
| run: | | |
| python .github/scripts/prune_stale_nav.py \ | |
| --docs-json "${{ env.DOCS_JSON_PATH }}" \ | |
| --repo-root . | |
| - name: Create Pull Request | |
| uses: peter-evans/create-pull-request@v5 | |
| with: | |
| token: ${{ steps.dest-token.outputs.token }} | |
| base: ${{ env.BASE_BRANCH }} | |
| commit-message: "docs: Update OpenAPI spec ${{ steps.copy.outputs.basename }}" | |
| title: "docs: Update OpenAPI spec ${{ steps.copy.outputs.basename }}" | |
| body: | | |
| ## OpenAPI Specification Update | |
| Synced from `${{ env.SOURCE_OWNER }}/${{ env.SOURCE_REPO }}` and regenerated navigation. | |
| ### File | |
| - `${{ steps.copy.outputs.basename }}` | |
| ### Changes | |
| - Updated `${{ steps.copy.outputs.dest }}` | |
| - Updated `docs.json` navigation entry for this spec | |
| --- | |
| *This PR was created automatically by the OpenAPI sync workflow.* | |
| branch: openapi-sync/${{ steps.copy.outputs.slug }} | |
| delete-branch: true | |
| # Include the whole dest dir (not just the new file) so that | |
| # deletions of case-variant duplicates are staged in the PR. | |
| # Nothing else in this job touches openapi/product/, so this is safe. | |
| add-paths: | | |
| ${{ env.DEST_YAML_PATH }} | |
| ${{ env.DOCS_JSON_PATH }} | |
| summary: | |
| needs: [detect, sync-file] | |
| if: always() | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Summary | |
| run: | | |
| echo "## OpenAPI Sync Summary" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| if [ "${{ needs.detect.outputs.has_changes }}" == "true" ]; then | |
| echo "Fanned out one PR per changed file." >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "Files:" >> $GITHUB_STEP_SUMMARY | |
| echo '```json' >> $GITHUB_STEP_SUMMARY | |
| echo '${{ needs.detect.outputs.files }}' >> $GITHUB_STEP_SUMMARY | |
| echo '```' >> $GITHUB_STEP_SUMMARY | |
| else | |
| echo "No changes detected - YAML files are up to date" >> $GITHUB_STEP_SUMMARY | |
| fi |