Skip to content

Commit 8868c5e

Browse files
authored
Merge pull request #4 from CatholicOS/feature/ci-deploy-pipeline
Add CI/CD pipeline for deploying governance docs
2 parents b8a7775 + 72f4bb0 commit 8868c5e

27 files changed

Lines changed: 6317 additions & 367 deletions

.github/workflows/deploy-docs.yml

Lines changed: 277 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,277 @@
1+
name: Deploy Governance Docs to WordPress
2+
3+
on:
4+
push:
5+
tags:
6+
- 'v*'
7+
workflow_dispatch:
8+
9+
permissions:
10+
contents: write
11+
12+
jobs:
13+
deploy:
14+
name: Convert & publish governance docs
15+
runs-on: ubuntu-latest
16+
env:
17+
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
18+
19+
steps:
20+
- name: Checkout repository
21+
uses: actions/checkout@v4
22+
with:
23+
fetch-depth: 0
24+
25+
- name: Detect changed documents
26+
id: changes
27+
run: |
28+
if [[ "$GITHUB_REF" == refs/tags/* ]]; then
29+
PREV_TAG=$(git tag --sort=-creatordate | head -2 | tail -1)
30+
if [ -n "$PREV_TAG" ] && [ "$PREV_TAG" != "$GITHUB_REF_NAME" ]; then
31+
DIFF_BASE="$PREV_TAG"
32+
else
33+
DIFF_BASE="HEAD~1"
34+
fi
35+
else
36+
DIFF_BASE="HEAD~1"
37+
fi
38+
39+
echo "Comparing against: $DIFF_BASE"
40+
CHANGED=$(git diff --name-only "$DIFF_BASE" HEAD 2>/dev/null || echo "FIRST_RUN")
41+
42+
# All governed documents: directory/filename (without .md)
43+
DOCS=(
44+
"ai-governance/ai-vetting-criteria"
45+
"ai-governance/fragmented-catholic-ai-governance"
46+
"ai-governance/governance-as-code-catholic-ai"
47+
"ai-governance/trusted-synthetic-data-ministry-ai"
48+
"project-governance/definitions"
49+
"project-governance/project-types"
50+
"project-governance/lifecycle"
51+
"project-governance/project-vetting-criteria"
52+
"project-governance/committees"
53+
"standards/overview"
54+
"standards/committees"
55+
)
56+
57+
CHANGED_DOCS=""
58+
for DOC in "${DOCS[@]}"; do
59+
if echo "$CHANGED" | grep -q "${DOC}.md"; then
60+
BASENAME=$(basename "$DOC")
61+
echo " ${DOC}.md changed"
62+
CHANGED_DOCS="${CHANGED_DOCS:+$CHANGED_DOCS }${DOC}"
63+
else
64+
echo " ${DOC}.md unchanged"
65+
fi
66+
done
67+
68+
echo "changed_docs=$CHANGED_DOCS" >> "$GITHUB_OUTPUT"
69+
70+
- name: Install pandoc
71+
if: steps.changes.outputs.changed_docs != ''
72+
uses: pandoc/actions/setup@v1
73+
74+
- name: Deploy changed documents to WordPress
75+
if: steps.changes.outputs.changed_docs != ''
76+
id: deploy
77+
env:
78+
WP_REST_URL: ${{ vars.WP_REST_URL }}
79+
WP_APP_USERNAME: ${{ secrets.WP_APP_USERNAME }}
80+
WP_APP_PASSWORD: ${{ secrets.WP_APP_PASSWORD }}
81+
run: |
82+
# Document titles keyed by directory/basename
83+
declare -A DOC_TITLES=(
84+
["ai-governance/ai-vetting-criteria"]="CDCF Project Vetting Criteria: AI Tools"
85+
["ai-governance/fragmented-catholic-ai-governance"]="Fragmented Catholic AI Governance at Scale"
86+
["ai-governance/governance-as-code-catholic-ai"]="Governance-as-Code for Catholic AI Agent Deployment"
87+
["ai-governance/trusted-synthetic-data-ministry-ai"]="Trusted Synthetic Data for Ministry-Scale AI"
88+
["project-governance/definitions"]="CDCF Governance Definitions"
89+
["project-governance/project-types"]="CDCF Project Types: Foundation Projects and Community Projects"
90+
["project-governance/lifecycle"]="CDCF Project Lifecycle"
91+
["project-governance/project-vetting-criteria"]="CDCF Project Vetting Criteria: General Framework"
92+
["project-governance/committees"]="CDCF Governance Bodies"
93+
["standards/overview"]="CDCF Standards: Overview"
94+
["standards/committees"]="CDCF Standards Committees"
95+
)
96+
97+
# Parent page slugs for each section
98+
declare -A SECTION_PARENTS=(
99+
["ai-governance"]="ai-governance"
100+
["project-governance"]="project-governance"
101+
["standards"]="standards"
102+
)
103+
104+
# Cache resolved parent page IDs
105+
declare -A PARENT_IDS
106+
107+
DEPLOYED_IDS=""
108+
109+
for DOC in ${{ steps.changes.outputs.changed_docs }}; do
110+
TITLE="${DOC_TITLES[$DOC]}"
111+
# Derive slug — prefix with section name to avoid collisions
112+
# (e.g. both project-governance/committees and standards/committees)
113+
SECTION=$(dirname "$DOC")
114+
BASENAME=$(basename "$DOC")
115+
if [ "$SECTION" = "standards" ]; then
116+
SLUG="standards-${BASENAME}"
117+
else
118+
SLUG="$BASENAME"
119+
fi
120+
PARENT_SLUG="${SECTION_PARENTS[$SECTION]}"
121+
122+
echo "Converting ${DOC}.md..."
123+
pandoc "${DOC}.md" --wrap=none -f markdown -t html5 -o "${SLUG}.html"
124+
125+
HTML_CONTENT=$(cat "${SLUG}.html")
126+
127+
# Resolve parent page ID (cached per section)
128+
if [ -z "${PARENT_IDS[$PARENT_SLUG]+x}" ]; then
129+
PARENT_PAGE=$(curl -s \
130+
-u "$WP_APP_USERNAME:$WP_APP_PASSWORD" \
131+
"$WP_REST_URL/wp/v2/pages?slug=${PARENT_SLUG}&status=publish,draft")
132+
PID=$(echo "$PARENT_PAGE" | jq -r '.[0].id // empty')
133+
if [ -n "$PID" ] && [ "$PID" != "null" ]; then
134+
PARENT_IDS[$PARENT_SLUG]="$PID"
135+
echo " Resolved parent '${PARENT_SLUG}' (ID: $PID)"
136+
else
137+
PARENT_IDS[$PARENT_SLUG]="0"
138+
echo " Warning: parent page '${PARENT_SLUG}' not found — publishing without parent."
139+
fi
140+
fi
141+
PARENT_ID="${PARENT_IDS[$PARENT_SLUG]}"
142+
143+
echo "Deploying '${TITLE}' (slug: ${SLUG}, parent: ${PARENT_ID})..."
144+
145+
# Check if a page with this slug already exists
146+
EXISTING=$(curl -s \
147+
-u "$WP_APP_USERNAME:$WP_APP_PASSWORD" \
148+
"$WP_REST_URL/wp/v2/pages?slug=${SLUG}&status=publish,draft")
149+
150+
PAGE_ID=$(echo "$EXISTING" | jq -r '.[0].id // empty')
151+
152+
PAYLOAD=$(jq -n \
153+
--arg title "$TITLE" \
154+
--arg slug "$SLUG" \
155+
--arg content "$HTML_CONTENT" \
156+
--argjson parent "$PARENT_ID" \
157+
'{title: $title, slug: $slug, content: $content, parent: $parent, status: "publish"}')
158+
159+
if [ -n "$PAGE_ID" ] && [ "$PAGE_ID" != "null" ]; then
160+
echo " Updating existing page (ID: $PAGE_ID)..."
161+
RESPONSE=$(curl -s -f -X POST \
162+
-u "$WP_APP_USERNAME:$WP_APP_PASSWORD" \
163+
-H "Content-Type: application/json" \
164+
-d "$PAYLOAD" \
165+
"$WP_REST_URL/wp/v2/pages/$PAGE_ID")
166+
else
167+
echo " Creating new page..."
168+
RESPONSE=$(curl -s -f -X POST \
169+
-u "$WP_APP_USERNAME:$WP_APP_PASSWORD" \
170+
-H "Content-Type: application/json" \
171+
-d "$PAYLOAD" \
172+
"$WP_REST_URL/wp/v2/pages")
173+
PAGE_ID=$(echo "$RESPONSE" | jq -r '.id')
174+
fi
175+
176+
echo " Deployed (page ID: $PAGE_ID)."
177+
DEPLOYED_IDS="${DEPLOYED_IDS:+$DEPLOYED_IDS }${PAGE_ID}"
178+
done
179+
180+
echo "deployed_ids=$DEPLOYED_IDS" >> "$GITHUB_OUTPUT"
181+
echo ""
182+
echo "All changed documents deployed."
183+
184+
- name: Translate deployed pages
185+
if: steps.deploy.outputs.deployed_ids != ''
186+
env:
187+
WP_REST_URL: ${{ vars.WP_REST_URL }}
188+
WP_APP_USERNAME: ${{ secrets.WP_APP_USERNAME }}
189+
WP_APP_PASSWORD: ${{ secrets.WP_APP_PASSWORD }}
190+
run: |
191+
DEPLOYED_IDS="${{ steps.deploy.outputs.deployed_ids }}"
192+
TARGET_LANGS=("it" "es" "fr" "pt" "de")
193+
194+
for PAGE_ID in $DEPLOYED_IDS; do
195+
echo "Translating page ID $PAGE_ID..."
196+
for LANG in "${TARGET_LANGS[@]}"; do
197+
RESPONSE=$(curl -s -f -X POST \
198+
-u "$WP_APP_USERNAME:$WP_APP_PASSWORD" \
199+
-H "Content-Type: application/json" \
200+
-d "{\"source_id\": ${PAGE_ID}, \"target_lang\": \"${LANG}\"}" \
201+
"$WP_REST_URL/cdcf/v1/translate")
202+
203+
TRANSLATED_ID=$(echo "$RESPONSE" | jq -r '.post_id // empty')
204+
MESSAGE=$(echo "$RESPONSE" | jq -r '.message // empty')
205+
206+
if [ -n "$TRANSLATED_ID" ]; then
207+
echo " ${LANG}: page ID ${TRANSLATED_ID} -- ${MESSAGE}"
208+
else
209+
echo " ${LANG}: translation failed"
210+
echo " Response: $RESPONSE"
211+
fi
212+
done
213+
done
214+
215+
echo ""
216+
echo "All translations complete."
217+
218+
- name: Setup Node.js
219+
uses: actions/setup-node@v4
220+
with:
221+
node-version: '24'
222+
cache: 'npm'
223+
224+
- name: Install npm dependencies
225+
run: npm ci
226+
env:
227+
PUPPETEER_SKIP_DOWNLOAD: 'true'
228+
229+
- name: Build standalone HTML pages
230+
run: npm run build:html
231+
232+
- name: Build combined PDF
233+
run: npm run build:pdf
234+
env:
235+
PUPPETEER_EXECUTABLE_PATH: /usr/bin/google-chrome-stable
236+
237+
- name: Create GitHub release
238+
env:
239+
GH_TOKEN: ${{ github.token }}
240+
run: |
241+
TAG="${GITHUB_REF_NAME}"
242+
243+
# For manual triggers, auto-determine tag
244+
if [[ "$TAG" != v* ]]; then
245+
LATEST_TAG=$(git tag --list 'v*' --sort=-version:refname | head -1)
246+
if [ -z "$LATEST_TAG" ]; then
247+
TAG="v0.1"
248+
else
249+
MAJOR=$(echo "$LATEST_TAG" | sed 's/^v//' | cut -d. -f1)
250+
MINOR=$(echo "$LATEST_TAG" | sed 's/^v//' | cut -d. -f2)
251+
if [ "$MINOR" -ge 9 ]; then
252+
TAG="v$((MAJOR + 1)).0"
253+
else
254+
TAG="v${MAJOR}.$((MINOR + 1))"
255+
fi
256+
fi
257+
258+
gh api repos/${{ github.repository }}/git/refs \
259+
-f ref="refs/tags/$TAG" \
260+
-f sha="$(git rev-parse HEAD)"
261+
fi
262+
263+
# Collect all build artifacts
264+
ASSETS=()
265+
for f in dist/*.html dist/*.pdf; do
266+
[ -f "$f" ] && ASSETS+=("$f")
267+
done
268+
269+
if gh release view "$TAG" &>/dev/null; then
270+
echo "Release $TAG already exists — uploading assets."
271+
gh release upload "$TAG" "${ASSETS[@]}" --clobber
272+
else
273+
gh release create "$TAG" \
274+
--title "Governance Docs $TAG" \
275+
--notes "CDCF governance documentation, $TAG." \
276+
"${ASSETS[@]}"
277+
fi
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
name: Markdown Lint
2+
3+
on:
4+
push:
5+
paths:
6+
- '**/*.md'
7+
- '.markdownlint.yml'
8+
- '.github/workflows/markdown-lint.yml'
9+
pull_request:
10+
paths:
11+
- '**/*.md'
12+
- '.markdownlint.yml'
13+
- '.github/workflows/markdown-lint.yml'
14+
15+
jobs:
16+
lint:
17+
name: Lint Markdown
18+
runs-on: ubuntu-latest
19+
env:
20+
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
21+
22+
steps:
23+
- name: Checkout repository
24+
uses: actions/checkout@v4
25+
26+
- name: Setup Node.js
27+
uses: actions/setup-node@v4
28+
with:
29+
node-version: '24'
30+
31+
- name: Install dependencies
32+
run: npm ci
33+
env:
34+
PUPPETEER_SKIP_DOWNLOAD: 'true'
35+
36+
- name: Run markdownlint
37+
run: npm run lint:md

.markdownlint.yml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
MD013:
2+
line_length: 180
3+
code_blocks: false
4+
tables: false
5+
MD024:
6+
siblings_only: true
7+
MD025:
8+
front_matter_title: ""
9+
MD029:
10+
style: one_or_ordered
11+
MD033:
12+
allowed_elements:
13+
- img
14+
- a
15+
- div
16+
- table
17+
- thead
18+
- tbody
19+
- tr
20+
- th
21+
- td
22+
- li
23+
- ul
24+
- ol
25+
- kbd
26+
- style
27+
MD034: false
28+
MD036: false
29+
MD041: false
30+
MD046:
31+
style: fenced
32+
MD060:
33+
style: aligned

0 commit comments

Comments
 (0)