fix: capture and display WordPress API error responses #5
Workflow file for this run
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: Deploy Governance Docs to WordPress | |
| on: | |
| push: | |
| tags: | |
| - 'v*' | |
| workflow_dispatch: | |
| permissions: | |
| contents: write | |
| jobs: | |
| deploy: | |
| name: Convert & publish governance docs | |
| runs-on: ubuntu-latest | |
| env: | |
| FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Detect changed documents | |
| id: changes | |
| run: | | |
| # All governed documents: directory/filename (without .md) | |
| DOCS=( | |
| "ai-governance/ai-vetting-criteria" | |
| "ai-governance/fragmented-catholic-ai-governance" | |
| "ai-governance/governance-as-code-catholic-ai" | |
| "ai-governance/trusted-synthetic-data-ministry-ai" | |
| "project-governance/definitions" | |
| "project-governance/project-types" | |
| "project-governance/lifecycle" | |
| "project-governance/project-vetting-criteria" | |
| "project-governance/committees" | |
| "standards/overview" | |
| "standards/committees" | |
| ) | |
| # Determine diff base | |
| DEPLOY_ALL=false | |
| if [[ "$GITHUB_REF" == refs/tags/* ]]; then | |
| PREV_TAG=$(git tag --sort=-creatordate | head -2 | tail -1) | |
| if [ -n "$PREV_TAG" ] && [ "$PREV_TAG" != "$GITHUB_REF_NAME" ]; then | |
| DIFF_BASE="$PREV_TAG" | |
| else | |
| # First tag ever — deploy everything | |
| DEPLOY_ALL=true | |
| fi | |
| else | |
| # Manual trigger — deploy everything | |
| DEPLOY_ALL=true | |
| fi | |
| CHANGED_DOCS="" | |
| if [ "$DEPLOY_ALL" = true ]; then | |
| echo "First deployment or manual trigger — deploying all documents." | |
| CHANGED_DOCS="${DOCS[*]}" | |
| else | |
| echo "Comparing against: $DIFF_BASE" | |
| CHANGED=$(git diff --name-only "$DIFF_BASE" HEAD 2>/dev/null || echo "") | |
| for DOC in "${DOCS[@]}"; do | |
| if echo "$CHANGED" | grep -q "${DOC}.md"; then | |
| echo " ${DOC}.md changed" | |
| CHANGED_DOCS="${CHANGED_DOCS:+$CHANGED_DOCS }${DOC}" | |
| else | |
| echo " ${DOC}.md unchanged" | |
| fi | |
| done | |
| fi | |
| echo "changed_docs=$CHANGED_DOCS" >> "$GITHUB_OUTPUT" | |
| - name: Install pandoc | |
| uses: pandoc/actions/setup@v1 | |
| - name: Deploy changed documents to WordPress | |
| if: steps.changes.outputs.changed_docs != '' | |
| id: deploy | |
| env: | |
| WP_REST_URL: ${{ vars.WP_REST_URL }} | |
| WP_APP_USERNAME: ${{ secrets.WP_APP_USERNAME }} | |
| WP_APP_PASSWORD: ${{ secrets.WP_APP_PASSWORD }} | |
| run: | | |
| # Document titles keyed by directory/basename | |
| declare -A DOC_TITLES=( | |
| ["ai-governance/ai-vetting-criteria"]="CDCF Project Vetting Criteria: AI Tools" | |
| ["ai-governance/fragmented-catholic-ai-governance"]="Fragmented Catholic AI Governance at Scale" | |
| ["ai-governance/governance-as-code-catholic-ai"]="Governance-as-Code for Catholic AI Agent Deployment" | |
| ["ai-governance/trusted-synthetic-data-ministry-ai"]="Trusted Synthetic Data for Ministry-Scale AI" | |
| ["project-governance/definitions"]="CDCF Governance Definitions" | |
| ["project-governance/project-types"]="CDCF Project Types: Foundation Projects and Community Projects" | |
| ["project-governance/lifecycle"]="CDCF Project Lifecycle" | |
| ["project-governance/project-vetting-criteria"]="CDCF Project Vetting Criteria: General Framework" | |
| ["project-governance/committees"]="CDCF Governance Bodies" | |
| ["standards/overview"]="CDCF Standards: Overview" | |
| ["standards/committees"]="CDCF Standards Committees" | |
| ) | |
| # Parent page slugs for each section | |
| declare -A SECTION_PARENTS=( | |
| ["ai-governance"]="ai-governance" | |
| ["project-governance"]="project-governance" | |
| ["standards"]="standards" | |
| ) | |
| # Cache resolved parent page IDs | |
| declare -A PARENT_IDS | |
| DEPLOYED_IDS="" | |
| for DOC in ${{ steps.changes.outputs.changed_docs }}; do | |
| TITLE="${DOC_TITLES[$DOC]}" | |
| # Derive slug — prefix with section name to avoid collisions | |
| # (e.g. both project-governance/committees and standards/committees) | |
| SECTION=$(dirname "$DOC") | |
| BASENAME=$(basename "$DOC") | |
| if [ "$SECTION" = "standards" ]; then | |
| SLUG="standards-${BASENAME}" | |
| else | |
| SLUG="$BASENAME" | |
| fi | |
| PARENT_SLUG="${SECTION_PARENTS[$SECTION]}" | |
| echo "Converting ${DOC}.md..." | |
| pandoc "${DOC}.md" --wrap=none -f markdown -t html5 -o "${SLUG}.html" | |
| HTML_CONTENT=$(cat "${SLUG}.html") | |
| # Resolve parent page ID (cached per section) | |
| if [ -z "${PARENT_IDS[$PARENT_SLUG]+x}" ]; then | |
| PARENT_PAGE=$(curl -s \ | |
| -u "$WP_APP_USERNAME:$WP_APP_PASSWORD" \ | |
| "$WP_REST_URL/wp/v2/pages?slug=${PARENT_SLUG}&status=publish,draft") | |
| PID=$(echo "$PARENT_PAGE" | jq -r 'if type == "array" then .[0].id // empty else empty end') | |
| if [ -n "$PID" ] && [ "$PID" != "null" ]; then | |
| PARENT_IDS[$PARENT_SLUG]="$PID" | |
| echo " Resolved parent '${PARENT_SLUG}' (ID: $PID)" | |
| else | |
| PARENT_IDS[$PARENT_SLUG]="0" | |
| echo " Warning: parent page '${PARENT_SLUG}' not found — publishing without parent." | |
| fi | |
| fi | |
| PARENT_ID="${PARENT_IDS[$PARENT_SLUG]}" | |
| echo "Deploying '${TITLE}' (slug: ${SLUG}, parent: ${PARENT_ID})..." | |
| # Check if a page with this slug already exists | |
| EXISTING=$(curl -s \ | |
| -u "$WP_APP_USERNAME:$WP_APP_PASSWORD" \ | |
| "$WP_REST_URL/wp/v2/pages?slug=${SLUG}&status=publish,draft") | |
| PAGE_ID=$(echo "$EXISTING" | jq -r 'if type == "array" then .[0].id // empty else empty end') | |
| PAYLOAD=$(jq -n \ | |
| --arg title "$TITLE" \ | |
| --arg slug "$SLUG" \ | |
| --arg content "$HTML_CONTENT" \ | |
| --argjson parent "$PARENT_ID" \ | |
| '{title: $title, slug: $slug, content: $content, parent: $parent, status: "publish"}') | |
| if [ -n "$PAGE_ID" ] && [ "$PAGE_ID" != "null" ]; then | |
| echo " Updating existing page (ID: $PAGE_ID)..." | |
| HTTP_CODE=$(curl -s -o /tmp/wp-response.json -w '%{http_code}' -X POST \ | |
| -u "$WP_APP_USERNAME:$WP_APP_PASSWORD" \ | |
| -H "Content-Type: application/json" \ | |
| -d "$PAYLOAD" \ | |
| "$WP_REST_URL/wp/v2/pages/$PAGE_ID") | |
| else | |
| echo " Creating new page..." | |
| HTTP_CODE=$(curl -s -o /tmp/wp-response.json -w '%{http_code}' -X POST \ | |
| -u "$WP_APP_USERNAME:$WP_APP_PASSWORD" \ | |
| -H "Content-Type: application/json" \ | |
| -d "$PAYLOAD" \ | |
| "$WP_REST_URL/wp/v2/pages") | |
| fi | |
| RESPONSE=$(cat /tmp/wp-response.json) | |
| if [[ "$HTTP_CODE" -ge 400 ]]; then | |
| echo " ERROR: HTTP $HTTP_CODE" | |
| echo " Response: $RESPONSE" | |
| exit 1 | |
| fi | |
| PAGE_ID=$(echo "$RESPONSE" | jq -r '.id') | |
| echo " Deployed (page ID: $PAGE_ID)." | |
| DEPLOYED_IDS="${DEPLOYED_IDS:+$DEPLOYED_IDS }${PAGE_ID}" | |
| done | |
| echo "deployed_ids=$DEPLOYED_IDS" >> "$GITHUB_OUTPUT" | |
| echo "" | |
| echo "All changed documents deployed." | |
| - name: Translate deployed pages | |
| if: steps.deploy.outputs.deployed_ids != '' | |
| env: | |
| WP_REST_URL: ${{ vars.WP_REST_URL }} | |
| WP_APP_USERNAME: ${{ secrets.WP_APP_USERNAME }} | |
| WP_APP_PASSWORD: ${{ secrets.WP_APP_PASSWORD }} | |
| run: | | |
| DEPLOYED_IDS="${{ steps.deploy.outputs.deployed_ids }}" | |
| TARGET_LANGS=("it" "es" "fr" "pt" "de") | |
| for PAGE_ID in $DEPLOYED_IDS; do | |
| echo "Translating page ID $PAGE_ID..." | |
| for LANG in "${TARGET_LANGS[@]}"; do | |
| RESPONSE=$(curl -s -f -X POST \ | |
| -u "$WP_APP_USERNAME:$WP_APP_PASSWORD" \ | |
| -H "Content-Type: application/json" \ | |
| -d "{\"source_id\": ${PAGE_ID}, \"target_lang\": \"${LANG}\"}" \ | |
| "$WP_REST_URL/cdcf/v1/translate") | |
| TRANSLATED_ID=$(echo "$RESPONSE" | jq -r '.post_id // empty') | |
| MESSAGE=$(echo "$RESPONSE" | jq -r '.message // empty') | |
| if [ -n "$TRANSLATED_ID" ]; then | |
| echo " ${LANG}: page ID ${TRANSLATED_ID} -- ${MESSAGE}" | |
| else | |
| echo " ${LANG}: translation failed" | |
| echo " Response: $RESPONSE" | |
| fi | |
| done | |
| done | |
| echo "" | |
| echo "All translations complete." | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: '24' | |
| cache: 'npm' | |
| - name: Install npm dependencies | |
| run: npm ci | |
| env: | |
| PUPPETEER_SKIP_DOWNLOAD: 'true' | |
| - name: Build standalone HTML pages | |
| run: npm run build:html | |
| - name: Build combined PDF | |
| run: npm run build:pdf | |
| env: | |
| PUPPETEER_EXECUTABLE_PATH: /usr/bin/google-chrome-stable | |
| - name: Create GitHub release | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| TAG="${GITHUB_REF_NAME}" | |
| # For manual triggers, auto-determine tag | |
| if [[ "$TAG" != v* ]]; then | |
| LATEST_TAG=$(git tag --list 'v*' --sort=-version:refname | head -1) | |
| if [ -z "$LATEST_TAG" ]; then | |
| TAG="v0.1" | |
| else | |
| MAJOR=$(echo "$LATEST_TAG" | sed 's/^v//' | cut -d. -f1) | |
| MINOR=$(echo "$LATEST_TAG" | sed 's/^v//' | cut -d. -f2) | |
| if [ "$MINOR" -ge 9 ]; then | |
| TAG="v$((MAJOR + 1)).0" | |
| else | |
| TAG="v${MAJOR}.$((MINOR + 1))" | |
| fi | |
| fi | |
| gh api repos/${{ github.repository }}/git/refs \ | |
| -f ref="refs/tags/$TAG" \ | |
| -f sha="$(git rev-parse HEAD)" | |
| fi | |
| # Collect all build artifacts | |
| ASSETS=() | |
| for f in dist/*.html dist/*.pdf; do | |
| [ -f "$f" ] && ASSETS+=("$f") | |
| done | |
| if gh release view "$TAG" &>/dev/null; then | |
| echo "Release $TAG already exists — uploading assets." | |
| gh release upload "$TAG" "${ASSETS[@]}" --clobber | |
| else | |
| gh release create "$TAG" \ | |
| --title "Governance Docs $TAG" \ | |
| --notes "CDCF governance documentation, $TAG." \ | |
| "${ASSETS[@]}" | |
| fi |