141 lines
5.6 KiB
YAML
141 lines
5.6 KiB
YAML
name: Publish to CloudFront
|
|
description: Syncs frontend assets to S3 and invalidates the CloudFront distribution
|
|
|
|
inputs:
|
|
dist_dir:
|
|
description: Path to the frontend dist directory
|
|
required: false
|
|
default: frontend/dist
|
|
s3_bucket_name:
|
|
description: Name of the S3 bucket to sync assets to
|
|
required: true
|
|
cloudfront_distribution_ids:
|
|
description: Space-separated list of CloudFront distribution IDs to invalidate
|
|
required: false
|
|
default: ''
|
|
versioning:
|
|
description: >
|
|
When set to true, enables versioned builds: assets are synced with
|
|
"public, max-age=31536000, immutable" and old versions older than 7 days
|
|
are deleted, keeping at least the 2 newest.
|
|
required: false
|
|
default: 'false'
|
|
versioning_prefix:
|
|
description: >
|
|
S3 prefix under which versioned builds are stored
|
|
(e.g. "_static" → _static/1234567890/). When omitted, versions are
|
|
stored at the bucket root. Requires versioning to be true.
|
|
required: false
|
|
default: ''
|
|
cache_rules:
|
|
description: >
|
|
JSON array of per-file cache overrides. Each matched file is uploaded
|
|
individually with the given headers and excluded from the bulk sync.
|
|
content_type is optional. Applied independently of versioning.
|
|
When versioning is enabled and cache_rules is empty, defaults to
|
|
short-caching index.html — the standard SPA behaviour.
|
|
Example:
|
|
cache_rules: |
|
|
[
|
|
{"file": "index.html", "cache_control": "no-cache, no-store, must-revalidate", "content_type": "text/html"},
|
|
{"file": "sw.js", "cache_control": "no-cache, no-store, must-revalidate", "content_type": "application/javascript"},
|
|
{"file": "manifest.webmanifest", "cache_control": "no-cache, max-age=300", "content_type": "application/manifest+json"}
|
|
]
|
|
required: false
|
|
default: '[]'
|
|
|
|
runs:
|
|
using: composite
|
|
steps:
|
|
- name: Install AWS CLI
|
|
shell: bash
|
|
run: |
|
|
if ! command -v aws &> /dev/null; then
|
|
curl -fsSL "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o /tmp/awscliv2.zip
|
|
unzip -q /tmp/awscliv2.zip -d /tmp
|
|
sudo /tmp/aws/install
|
|
rm -rf /tmp/awscliv2.zip /tmp/aws
|
|
fi
|
|
|
|
- name: Publish frontend assets
|
|
shell: bash
|
|
env:
|
|
INPUT_DIST_DIR: ${{ inputs.dist_dir }}
|
|
INPUT_S3_BUCKET_NAME: ${{ inputs.s3_bucket_name }}
|
|
INPUT_VERSIONING: ${{ inputs.versioning }}
|
|
INPUT_VERSIONING_PREFIX: ${{ inputs.versioning_prefix }}
|
|
INPUT_CACHE_RULES: ${{ inputs.cache_rules }}
|
|
run: |
|
|
# When versioning is active and no cache_rules provided, short-cache index.html by default
|
|
EFFECTIVE_CACHE_RULES="${INPUT_CACHE_RULES}"
|
|
if [[ "${INPUT_VERSIONING}" == "true" && "${EFFECTIVE_CACHE_RULES}" == "[]" && -f "${INPUT_DIST_DIR}/index.html" ]]; then
|
|
EFFECTIVE_CACHE_RULES='[{"file": "index.html", "cache_control": "no-cache, max-age=300", "content_type": "text/html"}]'
|
|
fi
|
|
|
|
# Upload each file from cache_rules with its own headers,
|
|
# and collect --exclude args so the bulk sync skips them
|
|
EXCLUDE_ARGS=()
|
|
if [[ "${EFFECTIVE_CACHE_RULES}" != "[]" ]]; then
|
|
while IFS= read -r rule; do
|
|
file=$(echo "$rule" | jq -r '.file')
|
|
cache_control=$(echo "$rule" | jq -r '.cache_control')
|
|
content_type=$(echo "$rule" | jq -r '.content_type // empty')
|
|
|
|
EXCLUDE_ARGS+=("--exclude" "$file")
|
|
|
|
if [[ -f "${INPUT_DIST_DIR}/${file}" ]]; then
|
|
CT_ARG=()
|
|
if [[ -n "$content_type" ]]; then
|
|
CT_ARG=("--content-type" "$content_type")
|
|
fi
|
|
aws s3 cp "${INPUT_DIST_DIR}/${file}" "s3://${INPUT_S3_BUCKET_NAME}/${file}" \
|
|
--cache-control "$cache_control" \
|
|
--metadata-directive REPLACE \
|
|
"${CT_ARG[@]}"
|
|
fi
|
|
done < <(echo "$EFFECTIVE_CACHE_RULES" | jq -c '.[]')
|
|
fi
|
|
|
|
# Bulk sync remaining files; versioned builds get immutable cache headers
|
|
CACHE_CONTROL_ARG=()
|
|
if [[ "${INPUT_VERSIONING}" == "true" ]]; then
|
|
CACHE_CONTROL_ARG=("--cache-control" "public, max-age=31536000, immutable")
|
|
fi
|
|
|
|
aws s3 sync "${INPUT_DIST_DIR}" "s3://${INPUT_S3_BUCKET_NAME}" "${CACHE_CONTROL_ARG[@]}" "${EXCLUDE_ARGS[@]}"
|
|
|
|
- name: Clean up old versioned static builds
|
|
if: ${{ inputs.versioning == 'true' }}
|
|
shell: bash
|
|
env:
|
|
INPUT_S3_BUCKET_NAME: ${{ inputs.s3_bucket_name }}
|
|
INPUT_VERSIONING_PREFIX: ${{ inputs.versioning_prefix }}
|
|
run: |
|
|
S3_PATH="s3://$INPUT_S3_BUCKET_NAME"
|
|
if [[ -n "${INPUT_VERSIONING_PREFIX}" ]]; then
|
|
S3_PATH="s3://$INPUT_S3_BUCKET_NAME/$INPUT_VERSIONING_PREFIX"
|
|
fi
|
|
|
|
aws s3 ls "${S3_PATH}/" \
|
|
| grep -oP '(?<=PRE )[0-9]+' \
|
|
| sort --stable --reverse \
|
|
| tail -n +3 \
|
|
| while read version; do
|
|
now=$(($(date +%s%N)/1000000))
|
|
diff=$(($now-$version))
|
|
# delete if older than 7 days
|
|
if [ $diff -gt 604800000 ]; then
|
|
echo "Deleting $version"
|
|
aws s3 rm --recursive "${S3_PATH}/$version"
|
|
fi
|
|
done
|
|
|
|
- name: Invalidate CloudFront
|
|
if: ${{ inputs.cloudfront_distribution_ids != '' }}
|
|
shell: bash
|
|
env:
|
|
INPUT_CLOUDFRONT_DISTRIBUTION_IDS: ${{ inputs.cloudfront_distribution_ids }}
|
|
run: |
|
|
echo "$INPUT_CLOUDFRONT_DISTRIBUTION_IDS" \
|
|
| tr ' ' '\n' \
|
|
| xargs -I% aws cloudfront create-invalidation --distribution-id % --paths '/*'
|