From 3e18d37b8a79295670aa0c44f185efb7f2ebe56a Mon Sep 17 00:00:00 2001 From: Erin O'Connell Date: Mon, 2 Nov 2026 23:05:53 -0700 Subject: [PATCH] ci(staging): invalidate CloudFront on deploy Prepare staging for caching HTML at the edge. Upload hashed assets first, sync everything else, invalidate paths with stable URLs and wait, then delete old assets, so live pages never reference a missing asset. Requires CloudFront invalidation permissions for the staging deploy credentials. --- .github/workflows/deploy-staging.yml | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/.github/workflows/deploy-staging.yml b/.github/workflows/deploy-staging.yml index 3c71729e..e620cd6a 100644 --- a/.github/workflows/deploy-staging.yml +++ b/.github/workflows/deploy-staging.yml @@ -52,8 +52,30 @@ jobs: # AWS_DEFAULT_REGION: ${{ secrets.STAGING_AWS_REGION }} - name: Deploy - run: aws s3 sync ./dist s3://${{ secrets.STAGING_AWS_S3_BUCKET }}/tech-docs --delete + # Order matters: live pages must never reference an asset that has not + # been uploaded yet, or one that has already been deleted. + run: | + # Hashed filenames change with their content, so size is enough to skip existing files. + aws s3 sync ./dist/_astro "$S3_PREFIX/_astro" --size-only + aws s3 sync ./dist "$S3_PREFIX" --exclude "_astro/*" --delete + + # CloudFront caches rewritten URIs (/x/index.html), which these wildcards also match. + # Read top-level folders from the build so new sections are never missed. + # _astro is skipped because its filenames change with their content. + mapfile -t DIR_PATHS < <(cd dist && find . -mindepth 1 -maxdepth 1 -type d ! -name _astro ! -name '.*' -printf '/tech-docs/%P*\n') + INVALIDATION_ID=$(aws cloudfront create-invalidation \ + --distribution-id "$DISTRIBUTION_ID" \ + --paths "/tech-docs" "/tech-docs/" "/tech-docs/index.html" \ + "${DIR_PATHS[@]}" \ + "/tech-docs/sitemap-*" "/tech-docs/favicon.png" \ + --query Invalidation.Id --output text) + aws cloudfront wait invalidation-completed \ + --distribution-id "$DISTRIBUTION_ID" --id "$INVALIDATION_ID" + + aws s3 sync ./dist/_astro "$S3_PREFIX/_astro" --size-only --delete env: + S3_PREFIX: s3://${{ secrets.STAGING_AWS_S3_BUCKET }}/tech-docs + DISTRIBUTION_ID: ${{ secrets.STAGING_AWS_CLOUDFRONT_DISTRIBUTION_ID }} AWS_ACCESS_KEY_ID: ${{ secrets.STAGING_AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.STAGING_AWS_SECRET_ACCESS_KEY }} AWS_DEFAULT_REGION: ${{ secrets.STAGING_AWS_REGION }}