Stage Build #476
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: Stage Build | |
| # NOTE! This is the *STAGE* workflow. | |
| # Keep in mind that much of the configuration is repeated in `prod-build.yml` | |
| # and `dev-build.yml` | |
| # | |
| # For a complete picture of all environments, see: | |
| # | |
| # https://docs.google.com/spreadsheets/d/1VnnEl-iTtKYmlyN02FiEXygxZCgE4o_ZO8wSleebne4/edit?usp=sharing | |
| # | |
| on: | |
| schedule: | |
| - cron: "0 0 * * *" | |
| workflow_dispatch: | |
| inputs: | |
| notes: | |
| description: "Notes" | |
| required: false | |
| default: "" | |
| invalidate: | |
| description: "Invalidate CDN (use only in exceptional circumstances)" | |
| type: boolean | |
| required: false | |
| default: false | |
| workflow_call: | |
| secrets: | |
| GCP_PROJECT_NAME: | |
| required: true | |
| WIP_PROJECT_ID: | |
| required: true | |
| permissions: | |
| contents: read | |
| id-token: write | |
| jobs: | |
| trigger: | |
| runs-on: ubuntu-latest | |
| # When run from `main` branch (schedule or manual), trigger workflow on `next` branch instead. | |
| if: ${{ github.repository == 'mdn/dex' && github.ref_name == 'main' }} | |
| steps: | |
| - run: gh workflow run "${{ github.workflow }}" --repo "${{ github.repository }}" --ref "next" | |
| env: | |
| GH_TOKEN: ${{ secrets.AUTOMERGE_TOKEN }} | |
| build: | |
| environment: stage | |
| runs-on: ubuntu-latest-dex-builder | |
| # We only ever want to deploy the `next` branch to stage. | |
| if: ${{ github.repository == 'mdn/dex' && github.ref_name == 'next' }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| fetch-depth: 0 | |
| path: mdn/dex | |
| persist-credentials: false | |
| - name: Merge main | |
| working-directory: mdn/dex | |
| run: | | |
| git config --global user.email "108879845+mdn-bot@users.noreply.github.com" | |
| git config --global user.name "mdn-bot" | |
| git status | |
| git checkout main | |
| git status | |
| git checkout - | |
| git merge main --no-edit || git merge --abort | |
| - name: Checkout (content) | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| if: ${{ ! vars.SKIP_BUILD || ! vars.SKIP_FUNCTION }} | |
| with: | |
| repository: mdn/content | |
| path: mdn/content | |
| # Yes, this means fetch EVERY COMMIT EVER. | |
| # It's probably not sustainable in the far future (e.g. past 2021) | |
| # but for now it's good enough. We'll need all the history | |
| # so we can figure out each document's last-modified date. | |
| fetch-depth: 0 | |
| persist-credentials: false | |
| - name: Checkout (blog) | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| if: ${{ ! vars.SKIP_BUILD }} | |
| with: | |
| repository: mdn/blog | |
| path: mdn/blog | |
| lfs: true | |
| token: ${{ secrets.MDN_STUDIO_PAT }} | |
| persist-credentials: false | |
| - name: Checkout (generic-content) | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| if: ${{ ! vars.SKIP_BUILD }} | |
| with: | |
| repository: mdn/generic-content | |
| path: mdn/generic-content | |
| persist-credentials: false | |
| - name: Checkout (curriculum) | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| if: ${{ ! vars.SKIP_BUILD }} | |
| with: | |
| repository: mdn/curriculum | |
| path: mdn/curriculum | |
| persist-credentials: false | |
| - name: Checkout (translated-content) | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| if: ${{ ! vars.SKIP_BUILD || ! vars.SKIP_FUNCTION }} | |
| with: | |
| repository: mdn/translated-content | |
| path: mdn/translated-content | |
| # See matching warning for mdn/content checkout step | |
| fetch-depth: 0 | |
| persist-credentials: false | |
| - name: Checkout (translated-content-de) | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| if: ${{ ! vars.SKIP_BUILD || ! vars.SKIP_FUNCTION }} | |
| with: | |
| repository: mdn/translated-content-de | |
| path: mdn/translated-content-de | |
| persist-credentials: false | |
| - name: Move de into translated-content | |
| if: ${{ ! vars.SKIP_BUILD || ! vars.SKIP_FUNCTION }} | |
| run: | | |
| mv mdn/translated-content-de/files/de mdn/translated-content/files/ | |
| rm -rf mdn/translated-content-de | |
| - name: Clean and commit de | |
| if: ${{ ! vars.SKIP_BUILD || ! vars.SKIP_FUNCTION }} | |
| working-directory: mdn/translated-content | |
| run: | | |
| git add files/de | |
| git -c user.name='MDN' -c user.email='mdn-dev@mozilla.com' commit -m 'de' | |
| - name: Checkout (mdn-contributor-spotlight) | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| if: ${{ ! vars.SKIP_BUILD }} | |
| with: | |
| repository: mdn/mdn-contributor-spotlight | |
| path: mdn/mdn-contributor-spotlight | |
| persist-credentials: false | |
| - name: Checkout (fred) | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| if: ${{ ! vars.SKIP_BUILD }} | |
| with: | |
| repository: mdn/fred | |
| path: mdn/fred | |
| persist-credentials: false | |
| - name: Setup Node (fred) | |
| if: ${{ ! vars.SKIP_BUILD || ! vars.SKIP_FUNCTION }} | |
| uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 | |
| with: | |
| node-version-file: mdn/fred/.nvmrc | |
| package-manager-cache: false | |
| - name: Install (fred) | |
| if: ${{ ! vars.SKIP_BUILD }} | |
| working-directory: mdn/fred | |
| env: | |
| # Use a GITHUB_TOKEN to bypass rate limiting for rari. | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: npm ci | |
| - name: Setup Python | |
| if: ${{ ! vars.SKIP_BUILD }} | |
| uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 | |
| with: | |
| python-version: "3.10" | |
| - name: Setup Poetry | |
| if: ${{ ! vars.SKIP_BUILD }} | |
| uses: snok/install-poetry@76e04a911780d5b312d89783f7b1cd627778900a # v1.4.1 | |
| - name: Install (deployer) | |
| if: ${{ ! vars.SKIP_BUILD }} | |
| working-directory: mdn/dex/deployer | |
| run: poetry install | |
| - name: Display Python & Poetry version | |
| if: ${{ ! vars.SKIP_BUILD }} | |
| working-directory: mdn/dex/deployer | |
| run: | | |
| python --version | |
| poetry --version | |
| - name: Print information about build | |
| env: | |
| NOTES: ${{ github.event.inputs.notes }} | |
| run: | | |
| echo "notes: $NOTES" | |
| - name: Print information about CPU | |
| run: cat /proc/cpuinfo | |
| - name: Build (rari) | |
| if: ${{ ! vars.SKIP_BUILD }} | |
| working-directory: mdn/fred | |
| env: | |
| # Remember, the mdn/content repo got cloned into `pwd` into a | |
| # sub-folder called "mdn/content" | |
| CONTENT_ROOT: ${{ github.workspace }}/mdn/content/files | |
| CONTENT_TRANSLATED_ROOT: ${{ github.workspace }}/mdn/translated-content/files | |
| CONTRIBUTOR_SPOTLIGHT_ROOT: ${{ github.workspace }}/mdn/mdn-contributor-spotlight/contributors | |
| BLOG_ROOT: ${{ github.workspace }}/mdn/blog/content/posts | |
| CURRICULUM_ROOT: ${{ github.workspace }}/mdn/curriculum | |
| GENERIC_CONTENT_ROOT: ${{ github.workspace }}/mdn/generic-content/files | |
| # rari | |
| BASE_URL: "https://developer.allizom.org" | |
| BUILD_OUT_ROOT: "out" | |
| LIVE_SAMPLES_BASE_URL: https://live.mdnyalp.dev | |
| ADDITIONAL_LOCALES_FOR_GENERICS_AND_SPAS: de | |
| # Sentry. | |
| SENTRY_DSN_BUILD: ${{ secrets.SENTRY_DSN_BUILD }} | |
| SENTRY_ENVIRONMENT: stage | |
| SENTRY_RELEASE: ${{ github.sha }} | |
| # Increase GitHub API rate limit. | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| set -eo pipefail | |
| npm run rari content sync-translated-content | |
| npm run rari git-history | |
| npm run rari build -- --all --issues "$BUILD_OUT_ROOT/issues.json" --templ-stats | |
| - name: Build (fred) | |
| if: ${{ ! vars.SKIP_BUILD }} | |
| working-directory: mdn/fred | |
| env: | |
| DEX_ROOT: ${{ github.workspace }}/mdn/dex | |
| BUILD_OUT_ROOT: "out" | |
| FRED_ROBOTS_GLOBAL_ALLOW: false | |
| FRED_BCD_BASE_URL: https://bcd.developer.allizom.org | |
| # This enables the Plus call-to-action banner and the Plus landing page | |
| REACT_APP_ENABLE_PLUS: true | |
| # This adds the ability to sign in (stage only for now) | |
| REACT_APP_DISABLE_AUTH: false | |
| # Offline updates | |
| REACT_APP_UPDATES_BASE_URL: https://updates.developer.allizom.org | |
| # Firefox Accounts and SubPlat settings | |
| REACT_APP_FXA_SIGNIN_URL: /users/fxa/login/authenticate/ | |
| REACT_APP_FXA_SETTINGS_URL: https://accounts.stage.mozaws.net/settings/ | |
| REACT_APP_MDN_PLUS_SUBSCRIBE_URL: https://accounts.stage.mozaws.net/subscriptions/products/prod_Jtbg9tyGyLRuB0 | |
| REACT_APP_MDN_PLUS_5M_PLAN: price_1JFoTYKb9q6OnNsLalexa03p | |
| REACT_APP_MDN_PLUS_5Y_PLAN: price_1JpIPwKb9q6OnNsLJLsIqMp7 | |
| REACT_APP_MDN_PLUS_10M_PLAN: price_1K6X7gKb9q6OnNsLi44HdLcC | |
| REACT_APP_MDN_PLUS_10Y_PLAN: price_1K6X8VKb9q6OnNsLFlUcEiu4 | |
| # Support for SP3 | |
| REACT_APP_MDN_PLUS_SUBSCRIBE_URL_SP3_BASE: https://payments-next.allizom.org | |
| REACT_APP_MDN_PLUS_5M_SP3_ID: mdnplus5mstage | |
| REACT_APP_MDN_PLUS_5Y_SP3_ID: mdnplus5ystage | |
| REACT_APP_MDN_PLUS_10M_SP3_ID: mdnsupporter10mstage | |
| REACT_APP_MDN_PLUS_10Y_SP3_ID: mdnsupporter10ystage | |
| # Surveys. | |
| REACT_APP_SURVEY_START_JS_PROPOSALS_2025: 0 # stage | |
| REACT_APP_SURVEY_END_JS_PROPOSALS_2025: 1745971200000 # new Date("2025-04-30Z").getTime() | |
| REACT_APP_SURVEY_RATE_FROM_JS_PROPOSALS_2025: 0.0 | |
| REACT_APP_SURVEY_RATE_TILL_JS_PROPOSALS_20255: 0.05 # 5% | |
| # Telemetry. | |
| REACT_APP_GLEAN_CHANNEL: stage | |
| REACT_APP_GLEAN_ENABLED: true | |
| FRED_GLEAN_CHANNEL: stage | |
| FRED_GLEAN_ENABLED: true | |
| # Transcend Consent Management | |
| FRED_TRANSCEND_AIRGAP_URL: https://transcend-cdn.com/cm-test/${{ secrets.TRANSCEND_BUNDLE_ID }}/airgap.js | |
| FRED_TRANSCEND_BUNDLE_ID: ${{ secrets.TRANSCEND_BUNDLE_ID }} | |
| # Newsletter | |
| REACT_APP_NEWSLETTER_ENABLED: true | |
| # Placement | |
| REACT_APP_PLACEMENT_ENABLED: true | |
| # Playground | |
| REACT_APP_PLAYGROUND_BASE_HOST: mdnyalp.dev | |
| FRED_PLAYGROUND_BASE_HOST: mdnyalp.dev | |
| # Observatory | |
| REACT_APP_OBSERVATORY_API_URL: https://observatory-api.mdn.allizom.net | |
| FRED_OBSERVATORY_API_URL: https://observatory-api.mdn.allizom.net | |
| # Sentry. | |
| SENTRY_DSN_BUILD: ${{ secrets.SENTRY_DSN_BUILD }} | |
| SENTRY_ENVIRONMENT: stage | |
| SENTRY_RELEASE: ${{ github.sha }} | |
| # Increase GitHub API rate limit. | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| set -eo pipefail | |
| # SSR all pages | |
| npm run build | |
| node build/ssr.js | |
| cp -r "$DEX_ROOT/client/public/assets" "$BUILD_OUT_ROOT" | |
| cp "$DEX_ROOT/assets/nonprod/robots.txt" "$BUILD_OUT_ROOT/robots.txt" | |
| - name: Setup Node (dex) | |
| if: ${{ ! vars.SKIP_BUILD }} | |
| uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 | |
| with: | |
| node-version-file: mdn/dex/.nvmrc | |
| package-manager-cache: false | |
| - name: Install (dex) | |
| if: ${{ ! vars.SKIP_BUILD }} | |
| working-directory: mdn/dex | |
| run: npm ci | |
| env: | |
| # Use a GITHUB_TOKEN to bypass rate limiting for rari. | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Run dex scripts | |
| if: ${{ ! vars.SKIP_BUILD }} | |
| working-directory: mdn/dex | |
| env: | |
| FRED_ROOT: ${{ github.workspace }}/mdn/fred | |
| run: | | |
| set -eo pipefail | |
| # Generate whatsdeployed files. | |
| npm run tool:legacy -- whatsdeployed --output "$FRED_ROOT/out/_whatsdeployed/code.json" | |
| npm run tool:legacy -- whatsdeployed $CONTENT_ROOT --output "$FRED_ROOT/out/_whatsdeployed/content.json" | |
| npm run tool:legacy -- whatsdeployed $CONTENT_TRANSLATED_ROOT --output "$FRED_ROOT/out/_whatsdeployed/translated-content.json" | |
| # Sort DE search index by en-US popularity. | |
| node scripts/reorder-search-index.mjs "$FRED_ROOT/out/en-us/search-index.json" "$FRED_ROOT/out/de/search-index.json" | |
| - name: Update search index | |
| if: ${{ ! vars.SKIP_BUILD }} | |
| working-directory: mdn/dex/deployer | |
| env: | |
| DEPLOYER_ELASTICSEARCH_URL: ${{ secrets.DEPLOYER_STAGE_ELASTICSEARCH_URL }} | |
| FRED_ROOT: ${{ github.workspace }}/mdn/fred | |
| run: poetry run deployer search-index "$FRED_ROOT/out" | |
| - name: Authenticate with GCP | |
| if: ${{ ! vars.SKIP_BUILD }} | |
| uses: google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093 # v3.0.0 | |
| with: | |
| token_format: access_token | |
| service_account: deploy-stage-content@${{ secrets.GCP_PROJECT_NAME }}.iam.gserviceaccount.com | |
| workload_identity_provider: projects/${{ secrets.WIP_PROJECT_ID }}/locations/global/workloadIdentityPools/github-actions/providers/github-actions | |
| - name: Setup gcloud | |
| if: ${{ ! vars.SKIP_BUILD }} | |
| uses: google-github-actions/setup-gcloud@aa5489c8933f4cc7a4f7d45035b3b1440c9c10db # v3.0.1 | |
| - name: Sync build | |
| if: ${{ ! vars.SKIP_BUILD }} | |
| working-directory: mdn/fred | |
| run: |- | |
| time gsutil -q -m -h "Cache-Control: public, max-age=3600" cp -r out/static gs://${{ vars.GCP_BUCKET_NAME }}/main/ | |
| time gsutil -q -m -h "Cache-Control: public, max-age=3600" rsync -cr gs://${{ vars.GCP_BUCKET_NAME }}/main/static/ gs://${{ vars.GCP_BUCKET_NAME }}/fred/static/ | |
| time gsutil -q -m -h "Cache-Control: public, max-age=3600" rsync -cdrj html,json,txt -y "^static/" out gs://${{ vars.GCP_BUCKET_NAME }}/fred | |
| - name: Authenticate with GCP | |
| if: ${{ ! vars.SKIP_FUNCTION }} | |
| uses: google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093 # v3.0.0 | |
| with: | |
| token_format: access_token | |
| service_account: deploy-stage-nonprod-mdn-ingre@${{ secrets.GCP_PROJECT_NAME }}.iam.gserviceaccount.com | |
| workload_identity_provider: projects/${{ secrets.WIP_PROJECT_ID }}/locations/global/workloadIdentityPools/github-actions/providers/github-actions | |
| - name: Setup gcloud | |
| if: ${{ ! vars.SKIP_FUNCTION }} | |
| uses: google-github-actions/setup-gcloud@aa5489c8933f4cc7a4f7d45035b3b1440c9c10db # v3.0.1 | |
| with: | |
| install_components: "beta" | |
| - name: Generate redirects map | |
| if: ${{ ! vars.SKIP_FUNCTION }} | |
| working-directory: mdn/dex/cloud-function | |
| env: | |
| CONTENT_ROOT: ${{ github.workspace }}/mdn/content/files | |
| CONTENT_TRANSLATED_ROOT: ${{ github.workspace }}/mdn/translated-content/files | |
| FRED_ROOT: ${{ github.workspace }}/mdn/fred | |
| run: | | |
| mkdir -p ../client/build/ | |
| cp "$FRED_ROOT/out/sitemap.txt" ../client/build/ | |
| npm ci | |
| npm run build-redirects | |
| npm run build-canonicals | |
| - name: Deploy Function | |
| if: ${{ ! vars.SKIP_FUNCTION }} | |
| working-directory: mdn/dex | |
| run: |- | |
| set -eo pipefail | |
| for region in europe-west1 us-west1 asia-east1; do | |
| gcloud beta functions deploy mdn-nonprod-stage-$region \ | |
| --gen2 \ | |
| --runtime=nodejs24 \ | |
| --region=$region \ | |
| --source=cloud-function \ | |
| --trigger-http \ | |
| --allow-unauthenticated \ | |
| --entry-point=mdnHandler \ | |
| --concurrency=100 \ | |
| --min-instances=1 \ | |
| --max-instances=100 \ | |
| --memory=2GB \ | |
| --timeout=120s \ | |
| --run-service-account=run-nonprod-stage-functions@${{ secrets.GCP_PROJECT_NAME }}.iam.gserviceaccount.com \ | |
| --set-env-vars="IGNORED_ROUTES=" \ | |
| --set-env-vars="ORIGIN_MAIN=developer.allizom.org" \ | |
| --set-env-vars="ORIGIN_LIVE_SAMPLES=live.mdnyalp.dev" \ | |
| --set-env-vars="ORIGIN_PLAY=mdnyalp.dev" \ | |
| --set-env-vars="SOURCE_CONTENT=https://storage.googleapis.com/${{ vars.GCP_BUCKET_NAME }}/fred/" \ | |
| --set-env-vars="SOURCE_API=https://api.developer.allizom.org/" \ | |
| --set-env-vars="SENTRY_DSN=${{ secrets.SENTRY_DSN_CLOUD_FUNCTION }}" \ | |
| --set-env-vars="SENTRY_ENVIRONMENT=stage" \ | |
| --set-env-vars="SENTRY_TRACES_SAMPLE_RATE=${{ vars.SENTRY_TRACES_SAMPLE_RATE }}" \ | |
| --set-env-vars="SENTRY_RELEASE=${{ github.sha }}" \ | |
| --set-secrets="SIGN_SECRET=projects/${{ secrets.GCP_PROJECT_NAME }}/secrets/stage-sign-secret/versions/latest" \ | |
| --set-secrets="BSA_ZONE_KEYS=projects/${{ secrets.GCP_PROJECT_NAME }}/secrets/stage-bsa-zone-keys/versions/latest" \ | |
| 2>&1 | sed "s/^/[$region] /" & | |
| pids+=($!) | |
| done | |
| for pid in "${pids[@]}"; do | |
| wait $pid | |
| done | |
| - name: Update AI Help index with macros | |
| working-directory: mdn/dex | |
| run: npm run ai-help-macros -- update-index "$FRED_ROOT/out/en-us/docs" | |
| env: | |
| OPENAI_KEY: ${{ secrets.OPENAI_KEY }} | |
| PG_URI: ${{ secrets.PG_URI }} | |
| FRED_ROOT: ${{ github.workspace }}/mdn/fred | |
| - name: Slack Notification | |
| if: failure() | |
| uses: rtCamp/action-slack-notify@e31e87e03dd19038e411e38ae27cbad084a90661 # v2 | |
| env: | |
| SLACK_CHANNEL: mdn-notifications | |
| SLACK_COLOR: ${{ job.status }} | |
| SLACK_ICON: https://avatars.slack-edge.com/2020-11-17/1513880588420_fedd7f0e9456888e69ff_96.png | |
| SLACK_TITLE: "Stage" | |
| SLACK_MESSAGE: "Build failed :collision:" | |
| SLACK_FOOTER: "Powered by stage-build.yml" | |
| SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} | |
| - name: Invalidate CDN | |
| if: ${{ github.event.inputs.invalidate }} | |
| run: gcloud compute url-maps invalidate-cdn-cache ${{ secrets.GCP_LOAD_BALANCER_NAME }} --path "/*" --async |