Skip to content

Deploy product website #241

Deploy product website

Deploy product website #241

Workflow file for this run

name: Deploy product website
on:
push:
branches: [ main ]
paths:
- "landing/**"
- "scripts/validate-product-source.py"
- "scripts/build-product-site.py"
- "scripts/validate-product-build.py"
- "scripts/apply-search-authority.py"
- "scripts/validate-search-authority.py"
- "scripts/build-responsive-media.py"
- "scripts/validate-responsive-media.py"
- "scripts/validate-adoption-proof.py"
- "scripts/verify-adoption-deployment.py"
- "scripts/submit-indexnow.py"
- "scripts/inject-site-measurement.py"
- "scripts/validate-site-measurement.py"
- "scripts/check-site-health.py"
- "scripts/generate-privacy-pages.py"
- "scripts/stamp-site-build.py"
- "scripts/verify-pages-deployment.py"
- "scripts/resolve-site-source-commit.py"
- ".github/ISSUE_TEMPLATE/**"
- ".github/workflows/pages.yml"
- ".github/workflows/release-supply-chain.yml"
pull_request:
branches: [ main ]
paths:
- "landing/**"
- "scripts/validate-product-source.py"
- "scripts/build-product-site.py"
- "scripts/validate-product-build.py"
- "scripts/apply-search-authority.py"
- "scripts/validate-search-authority.py"
- "scripts/build-responsive-media.py"
- "scripts/validate-responsive-media.py"
- "scripts/validate-adoption-proof.py"
- "scripts/verify-adoption-deployment.py"
- "scripts/submit-indexnow.py"
- "scripts/inject-site-measurement.py"
- "scripts/validate-site-measurement.py"
- "scripts/check-site-health.py"
- "scripts/generate-privacy-pages.py"
- "scripts/stamp-site-build.py"
- "scripts/verify-pages-deployment.py"
- "scripts/resolve-site-source-commit.py"
- ".github/ISSUE_TEMPLATE/**"
- ".github/workflows/pages.yml"
- ".github/workflows/release-supply-chain.yml"
workflow_dispatch:
permissions:
contents: read
concurrency:
group: pages
cancel-in-progress: true
env:
CANONICAL_ROOT: https://masarray.github.io/arsas/
jobs:
validate:
name: Validate ARSAS product website
runs-on: ubuntu-latest
steps:
- name: Checkout complete history
uses: actions/checkout@v4
with:
fetch-depth: 0
show-progress: false
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install responsive media dependency
run: python -m pip install --disable-pip-version-check --quiet pillow
- name: Resolve deployable website source
id: site_source
run: python scripts/resolve-site-source-commit.py --output _validation/site-source.json
- name: Prepare verified stable release evidence
env:
GH_TOKEN: ${{ github.token }}
shell: bash
run: |
set -euo pipefail
if [ "$GITHUB_EVENT_NAME" = "pull_request" ]; then
cp landing/latest.json /tmp/arsas-published.json
else
gh api "repos/$GITHUB_REPOSITORY/contents/published.json?ref=release-evidence" --jq .content | base64 -d > /tmp/arsas-published.json
fi
python - <<'PY'
import json
import re
from pathlib import Path
evidence = json.loads(Path('/tmp/arsas-published.json').read_text())
notes = json.loads(Path('landing/release-notes.json').read_text())
if evidence.get('product') not in (None, 'ARSAS'):
raise SystemExit('Stable release evidence is for another product')
if evidence.get('channel') != 'stable':
raise SystemExit('Only stable release evidence may be deployed')
if evidence.get('version') != notes.get('version'):
raise SystemExit('Release notes and stable evidence versions differ')
if not re.fullmatch(r'\d+\.\d+\.\d+', str(evidence.get('version', ''))):
raise SystemExit('Stable release version is invalid')
for name in ('installer', 'portable'):
package = evidence.get(name, {})
if not re.fullmatch(r'[0-9a-fA-F]{64}', str(package.get('sha256', ''))):
raise SystemExit(f'{name} SHA-256 is invalid')
if int(package.get('sizeBytes', 0)) < 1_000_000:
raise SystemExit(f'{name} size is invalid')
PY
- name: Validate product source, SEO, adoption and field-proof contracts
id: source_validation
continue-on-error: true
shell: bash
run: |
mkdir -p _validation
python scripts/validate-product-source.py > _validation/source.log 2>&1
python scripts/validate-adoption-proof.py >> _validation/source.log 2>&1
- name: Upload source validation report
if: always()
uses: actions/upload-artifact@v4
with:
name: product-source-validation
path: |
_validation/source.log
_validation/site-source.json
if-no-files-found: error
- name: Enforce source validation
if: steps.source_validation.outcome != 'success'
shell: bash
run: |
cat _validation/source.log
exit 1
- name: Build deterministic product website
run: python scripts/build-product-site.py --output _site --release-evidence /tmp/arsas-published.json
- name: Apply contextual search authority graph
run: python scripts/apply-search-authority.py _site
- name: Generate bilingual privacy pages
env:
GA4_MEASUREMENT_ID: ${{ vars.GA4_MEASUREMENT_ID }}
run: python scripts/generate-privacy-pages.py --output _site --release-evidence /tmp/arsas-published.json --measurement-id "$GA4_MEASUREMENT_ID"
- name: Generate responsive screenshot variants
run: python scripts/build-responsive-media.py _site
- name: Configure optional client measurement
env:
GA4_MEASUREMENT_ID: ${{ vars.GA4_MEASUREMENT_ID }}
run: python scripts/inject-site-measurement.py _site --measurement-id "$GA4_MEASUREMENT_ID"
- name: Stamp source and workflow attestation
shell: bash
run: |
set -euo pipefail
python scripts/stamp-site-build.py _site \
--source-commit "${{ steps.site_source.outputs.site_source_commit }}" \
--source-ref "$GITHUB_REF" \
--commit-timestamp "${{ steps.site_source.outputs.site_source_timestamp }}" \
--workflow-run-id "$GITHUB_RUN_ID" \
--workflow-run-attempt "$GITHUB_RUN_ATTEMPT"
- name: Validate consent, privacy and measurement contract
env:
GA4_MEASUREMENT_ID: ${{ vars.GA4_MEASUREMENT_ID }}
run: python scripts/validate-site-measurement.py _site --measurement-id "$GA4_MEASUREMENT_ID"
- name: Validate contextual authority and guide discovery
run: python scripts/validate-search-authority.py _site
- name: Validate adoption and field-proof output
run: python scripts/validate-adoption-proof.py --site _site
- name: Validate responsive screenshot output
run: python scripts/validate-responsive-media.py _site
- name: Check internal links and fragments
run: python scripts/check-site-health.py --site _site --output _validation/site-health
- name: Validate IndexNow payload without network submission
run: python scripts/submit-indexnow.py --sitemap _site/sitemap.xml --dry-run
- name: Validate rendered product website
id: build_validation
continue-on-error: true
shell: bash
run: |
mkdir -p _validation
python scripts/validate-product-build.py _site > _validation/build.log 2>&1
- name: Upload rendered validation report
if: always()
uses: actions/upload-artifact@v4
with:
name: product-build-validation
path: |
_validation/build.log
_validation/site-health/
if-no-files-found: warn
- name: Enforce rendered validation
if: steps.build_validation.outcome != 'success'
shell: bash
run: |
cat _validation/build.log
exit 1
- name: Verify public website boundaries
shell: bash
run: |
set -euo pipefail
! grep -R --line-number --fixed-strings 'LICENSE-APACHE-2.0' _site
! grep -R --line-number -E '(href|src|content)="http://' _site --include='*.html'
! grep -R --line-number --fixed-strings 'raw.githubusercontent.com/masarray/arsas/main/Assets/screenshot' _site --include='*.html'
! grep -R --line-number --fixed-strings '__ARSAS_GA4_MEASUREMENT_ID__' _site --include='*.html'
test ! -e _site/search-authority.json
- name: Upload website artifact
if: github.event_name != 'pull_request'
uses: actions/upload-pages-artifact@v3
with:
path: _site
deploy:
if: github.event_name != 'pull_request'
needs: validate
runs-on: ubuntu-latest
permissions:
pages: write
id-token: write
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
verify-production:
name: Verify public Pages attestation
if: github.event_name != 'pull_request'
needs: deploy
runs-on: ubuntu-latest
steps:
- name: Checkout complete history
uses: actions/checkout@v4
with:
fetch-depth: 0
show-progress: false
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Resolve deployed website source
id: site_source
run: python scripts/resolve-site-source-commit.py --output _validation/site-source.json
- name: Verify deployed commit, privacy, authority and measurement state
env:
GA4_MEASUREMENT_ID: ${{ vars.GA4_MEASUREMENT_ID }}
shell: bash
run: |
set -euo pipefail
stable_version="$(python -c 'import json; print(json.load(open("landing/latest.json"))["version"])')"
if [ -n "${GA4_MEASUREMENT_ID:-}" ]; then measurement_enabled=true; else measurement_enabled=false; fi
python scripts/verify-pages-deployment.py \
--base-url "$CANONICAL_ROOT" \
--source-commit "${{ steps.site_source.outputs.site_source_commit }}" \
--stable-version "$stable_version" \
--measurement-enabled "$measurement_enabled" \
--output _validation/production-attestation.md
- name: Verify public adoption, evidence and responsive media surfaces
run: python scripts/verify-adoption-deployment.py --base-url "$CANONICAL_ROOT" --source-commit "${{ steps.site_source.outputs.site_source_commit }}"
- name: Upload production attestation
if: always()
uses: actions/upload-artifact@v4
with:
name: production-pages-attestation
path: |
_validation/production-attestation.*
_validation/site-source.json
if-no-files-found: error
retention-days: 90
notify-indexing:
name: Notify search engines
if: github.event_name != 'pull_request'
needs: verify-production
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
show-progress: false
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Prepare stable release evidence
env:
GH_TOKEN: ${{ github.token }}
shell: bash
run: gh api "repos/$GITHUB_REPOSITORY/contents/published.json?ref=release-evidence" --jq .content | base64 -d > /tmp/arsas-published.json
- name: Build current sitemap
run: python scripts/build-product-site.py --output _site --release-evidence /tmp/arsas-published.json
- name: Submit deployed URLs through IndexNow
id: indexnow
continue-on-error: true
shell: bash
run: |
mkdir -p _validation
python scripts/submit-indexnow.py --sitemap _site/sitemap.xml > _validation/indexnow.log 2>&1
- name: Upload IndexNow submission report
if: always()
uses: actions/upload-artifact@v4
with:
name: indexnow-submission
path: _validation/indexnow.log
if-no-files-found: warn
- name: Report non-blocking submission result
if: steps.indexnow.outcome != 'success'
run: echo "IndexNow submission was not accepted; deployment remains valid and the generated sitemap is still available to crawlers."