Skip to content

Deploy Documentation #925

Deploy Documentation

Deploy Documentation #925

Workflow file for this run

name: Deploy Documentation
on:
push:
branches: [main]
schedule:
# Once daily at 02:17 UTC. Was hourly (24 × ~76 min ≈ 1,800 min/day).
# push-to-main covers same-day doc edits; scheduled run picks up addon changes.
- cron: '17 2 * * *'
workflow_dispatch:
concurrency:
group: deploy-docs
cancel-in-progress: false
jobs:
# ── Change detection ────────────────────────────────────────────────────────
# Checks the docs repo and all addon repos via the GitHub API before cloning
# anything. If nothing has changed since the last successful deploy the build
# job is skipped entirely. push/workflow_dispatch events always build.
check-changes:
runs-on: ubuntu-latest
outputs:
changed: ${{ steps.check.outputs.changed }}
steps:
- uses: actions/checkout@v6
- name: Detect changes since last successful deploy
id: check
env:
GH_TOKEN: ${{ secrets.DOCS_DISPATCH_TOKEN }}
run: |
# Default: build. Overridden to 'false' only when all checks pass clean.
echo "changed=true" >> "$GITHUB_OUTPUT"
set -euo pipefail
# ── Last successful deploy timestamp ───────────────────────────────
# status=success excludes the current (in_progress) run, so [0] is
# the most recently completed successful deploy.
LAST_RUN=$(gh api \
"repos/${{ github.repository }}/actions/workflows/deploy-docs.yml/runs?status=success&per_page=2" \
--jq '.workflow_runs[0].created_at // empty' 2>/dev/null || true)
if [ -z "$LAST_RUN" ]; then
echo "No previous successful run found — building unconditionally."
exit 0
fi
echo "Last successful deploy: $LAST_RUN"
# ── Docs repo ──────────────────────────────────────────────────────
DOCS_COMMITS=$(git log --since="$LAST_RUN" --oneline | wc -l | xargs)
echo "Docs repo commits since last deploy: $DOCS_COMMITS"
if [ "$DOCS_COMMITS" -gt 0 ]; then
echo "Docs repo changed — building."
exit 0
fi
# ── Addon repos (API check — no cloning) ──────────────────────────
ADDON_REPOS=(
ultimate-multisite
ultimate-multisite-woocommerce
multisite-ultimate-site-exporter
multisite-ultimate-affiliatewp
multisite-ultimate-ai-site-builder
ultimate-multisite-analytics
multisite-ultimate-captcha
ultimate-multisite-chuck-norris-facts
ultimate-multisite-content-sync
multisite-ultimate-domain-seller
ultimate-multisite-fluent-forms
multisite-ultimate-gocardless
ultimate-multisite-gravity-forms
multisite-ultimate-language-selector
ultimate-multisite-loco-translate
multisite-ultimate-mailchimp
ultimate-multisite-mailster
multisite-ultimate-metered-plans
ultimate-multisite-multinetwork
ultimate-multisite-multi-tenancy
multisite-ultimate-payfast
multisite-ultimate-plugin-and-theme-manager
multisite-ultimate-support-agents
multisite-ultimate-support-tickets
multisite-ultimate-vat
multisite-ultimate-admin-page-creator
ultimate-multisite-addon-template
ultimate-multisite-emails
multisite-ultimate-metrics-and-onboarding
tutor-multisite-compatibilty
material-wp
)
for repo in "${ADDON_REPOS[@]}"; do
LATEST=$(gh api "repos/Ultimate-Multisite/$repo/commits?per_page=1" \
--jq '.[0].commit.committer.date' 2>/dev/null || true)
if [ -z "$LATEST" ]; then
echo " $repo: not accessible — skipping"
continue
fi
# ISO 8601 strings compare correctly as plain strings
if [[ "$LATEST" > "$LAST_RUN" ]]; then
echo " $repo: changed (latest commit: $LATEST) — building."
exit 0
fi
echo " $repo: unchanged (latest commit: $LATEST)"
done
echo "No changes detected across docs repo or any addon since $LAST_RUN — skipping build."
echo "changed=false" >> "$GITHUB_OUTPUT"
# ── Locale chunk planning ────────────────────────────────────────────────────
plan-locale-chunks:
needs: check-changes
# Always build on push/manual; skip scheduled runs when nothing changed.
if: needs.check-changes.outputs.changed == 'true' || github.event_name != 'schedule'
runs-on: ubuntu-latest
outputs:
chunks: ${{ steps.plan.outputs.chunks }}
steps:
- uses: actions/checkout@v6
- name: Plan locale chunks
id: plan
shell: bash
run: |
set -euo pipefail
python3 <<'PY'
import json
import os
import re
from pathlib import Path
chunk_size = 10
config = Path('docusaurus.config.js').read_text(encoding='utf-8')
match = re.search(r'locales:\s*\[(.*?)\]\s*,\s*localeConfigs', config, re.S)
if not match:
raise SystemExit('Unable to find i18n.locales in docusaurus.config.js')
locales = re.findall(r"'([^']+)'", match.group(1))
if not locales or locales[0] != 'en':
raise SystemExit('Expected en to be the first Docusaurus locale')
chunks = [{'chunk': '00-en', 'locales': 'en'}]
translated_locales = locales[1:]
for index in range(0, len(translated_locales), chunk_size):
chunk_number = (index // chunk_size) + 1
chunk_locales = translated_locales[index:index + chunk_size]
chunks.append({
'chunk': f'{chunk_number:02d}',
'locales': ' '.join(chunk_locales),
})
with open(os.environ['GITHUB_OUTPUT'], 'a', encoding='utf-8') as output:
output.write(f'chunks={json.dumps(chunks)}\n')
with open(os.environ['GITHUB_STEP_SUMMARY'], 'a', encoding='utf-8') as summary:
summary.write('## Locale build chunks\n\n')
for chunk in chunks:
summary.write(f"- `{chunk['chunk']}`: `{chunk['locales']}`\n")
PY
# ── Generated source preparation ─────────────────────────────────────────────
prepare-docs-source:
needs: check-changes
# Always build on push/manual; skip scheduled runs when nothing changed.
if: needs.check-changes.outputs.changed == 'true' || github.event_name != 'schedule'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: '20'
cache: 'npm'
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.3'
- name: Install dependencies
run: npm ci
- name: Checkout main plugin
uses: actions/checkout@v6
with:
repository: Ultimate-Multisite/ultimate-multisite
path: ultimate-multisite
token: ${{ secrets.DOCS_DISPATCH_TOKEN }}
- name: Checkout addons
run: |
mkdir -p addons
# Format: github-repo-name=local-dir-name
declare -A ADDONS=(
[ultimate-multisite-woocommerce]=ultimate-multisite-woocommerce
[multisite-ultimate-site-exporter]=ultimate-multisite-site-exporter
[multisite-ultimate-affiliatewp]=ultimate-multisite-affiliatewp
[multisite-ultimate-ai-site-builder]=ultimate-multisite-ai-site-builder
[ultimate-multisite-analytics]=ultimate-multisite-analytics
[multisite-ultimate-captcha]=ultimate-multisite-captcha
[ultimate-multisite-chuck-norris-facts]=ultimate-multisite-chuck-norris-facts
[ultimate-multisite-content-sync]=ultimate-multisite-content-sync
[multisite-ultimate-domain-seller]=ultimate-multisite-domain-seller
[ultimate-multisite-fluent-forms]=ultimate-multisite-fluent-forms
[multisite-ultimate-gocardless]=ultimate-multisite-gocardless
[ultimate-multisite-gravity-forms]=ultimate-multisite-gravity-forms
[multisite-ultimate-language-selector]=ultimate-multisite-language-selector
[ultimate-multisite-loco-translate]=ultimate-multisite-loco-translate
[multisite-ultimate-mailchimp]=ultimate-multisite-mailchimp
[ultimate-multisite-mailster]=ultimate-multisite-mailster
[multisite-ultimate-metered-plans]=ultimate-multisite-metered-plans
[ultimate-multisite-multinetwork]=ultimate-multisite-multinetwork
[ultimate-multisite-multi-tenancy]=ultimate-multisite-multi-tenancy
[multisite-ultimate-payfast]=ultimate-multisite-payfast
[multisite-ultimate-plugin-and-theme-manager]=ultimate-multisite-plugin-and-theme-manager
[multisite-ultimate-support-agents]=ultimate-multisite-support-agents
[multisite-ultimate-support-tickets]=ultimate-multisite-support-tickets
[multisite-ultimate-vat]=ultimate-multisite-vat
[multisite-ultimate-admin-page-creator]=ultimate-multisite-admin-page-creator
[ultimate-multisite-addon-template]=ultimate-multisite-addon-template
[ultimate-multisite-emails]=ultimate-multisite-emails
[multisite-ultimate-metrics-and-onboarding]=ultimate-multisite-metrics-and-onboarding
[tutor-multisite-compatibilty]=tutor-multisite-compatibility
[material-wp]=material-wp
)
for repo in "${!ADDONS[@]}"; do
local_dir="${ADDONS[$repo]}"
echo "Cloning $repo -> addons/$local_dir"
git clone --depth 1 "https://x-access-token:${{ secrets.DOCS_DISPATCH_TOKEN }}@github.com/Ultimate-Multisite/$repo.git" "addons/$local_dir" 2>/dev/null || echo " Skipped $repo (not found)"
done
shell: bash
- name: Update addon changelogs
run: python3 scripts/update-changelogs.py
continue-on-error: true
- name: Generate hooks documentation
run: bash scripts/generate-hooks.sh
continue-on-error: true
- name: Upload prepared docs source
uses: actions/upload-artifact@v4
with:
name: prepared-docs-source
path: |
docs
i18n
scripts
src
static
docusaurus.config.js
sidebars.js
package.json
package-lock.json
if-no-files-found: error
retention-days: 1
compression-level: 3
# ── Parallel locale builds ───────────────────────────────────────────────────
build-locale-chunk:
name: Build locale chunk ${{ matrix.chunk }}
needs:
- check-changes
- plan-locale-chunks
- prepare-docs-source
# Always build on push/manual; skip scheduled runs when nothing changed.
if: needs.check-changes.outputs.changed == 'true' || github.event_name != 'schedule'
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include: ${{ fromJSON(needs.plan-locale-chunks.outputs.chunks) }}
steps:
- name: Download prepared docs source
uses: actions/download-artifact@v4
with:
name: prepared-docs-source
path: .
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build locale chunk
env:
LOCALES: ${{ matrix.locales }}
shell: bash
run: |
set -euo pipefail
locale_args=()
for locale in $LOCALES; do
echo "Building locale: $locale"
locale_args+=(--locale "$locale")
done
rm -rf build .docusaurus
npx docusaurus build "${locale_args[@]}"
- name: Upload locale build artifact
uses: actions/upload-artifact@v4
with:
name: docs-build-${{ matrix.chunk }}
path: build/
if-no-files-found: error
retention-days: 1
compression-level: 3
# ── Build assembly & deploy ──────────────────────────────────────────────────
assemble-and-deploy:
needs:
- check-changes
- build-locale-chunk
# Always assemble on push/manual; skip scheduled runs when nothing changed.
if: needs.check-changes.outputs.changed == 'true' || github.event_name != 'schedule'
runs-on: ubuntu-latest
steps:
- name: Download locale build artifacts
uses: actions/download-artifact@v4
with:
pattern: docs-build-*
path: locale-builds
- name: Assemble documentation site
shell: bash
run: |
set -euo pipefail
mkdir -p build
for artifact_dir in locale-builds/docs-build-*; do
if [ ! -d "$artifact_dir" ] || [ "$(basename "$artifact_dir")" = "docs-build-00-en" ]; then
continue
fi
rsync -a "$artifact_dir"/ build/
done
if [ ! -d locale-builds/docs-build-00-en ]; then
echo "Missing default English build artifact" >&2
exit 1
fi
rsync -a locale-builds/docs-build-00-en/ build/
test -f build/index.html
- name: Deploy to server
if: github.ref == 'refs/heads/main'
run: |
mkdir -p ~/.ssh
echo "${{ secrets.DEPLOY_SSH_KEY }}" > ~/.ssh/id_ed25519
chmod 600 ~/.ssh/id_ed25519
ssh-keyscan -H ${{ secrets.DEPLOY_HOST }} >> ~/.ssh/known_hosts 2>/dev/null
rsync -avz --delete build/ ${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }}:/srv/www/static-sites/ultimatemultisite.com/docs/