Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
123 changes: 123 additions & 0 deletions .github/WORKFLOW_ARCHITECTURE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
# GitHub Actions Workflow Architecture

## Overview

This repository uses a modular GitHub Actions workflow architecture to ensure consistency between PR validation and deployment while preventing accidental deployments.

## Workflow Files

### 1. `.github/workflows/build.yml` (Reusable Workflow)
**Purpose**: Core build and validation logic used by both PR validation and deployment

**Features**:
- Reusable workflow that can be called by other workflows
- Accepts optional `checkout-ref` input for specific git references
- Performs all validation steps:
- Linting (ESLint)
- Spell checking (source files)
- TypeScript checking and Astro build
- Spell checking (generated HTML)
- Internal link validation
- Upload pages artifact for deployment

**Outputs**:
- `artifact-uploaded`: Boolean indicating if the build artifact was successfully created

### 2. `.github/workflows/deploy.yml`
**Purpose**: Deploy the site to GitHub Pages

**Triggers**:
- Automatic: Push to `main` branch
- Manual: `workflow_dispatch` with optional `deploy` flag

**Safety Features**:
- Only deploys from `main` branch
- Manual trigger requires explicit `deploy: true` flag
- Uses concurrency group to prevent parallel deployments
- Conditional deployment logic prevents accidental deploys

**Jobs**:
1. `build`: Calls reusable build workflow
2. `deploy`: Conditionally deploys to GitHub Pages (only if conditions are met)

### 3. `.github/workflows/pr-validation.yml`
**Purpose**: Validate pull requests before merge

**Features**:
- Uses the same build workflow as deployment (ensures parity)
- Provides detailed status comments on PRs
- Acts as a complete dry-run of the deployment process
- Reports all validation results clearly

## Key Design Decisions

### 1. Single Source of Truth
All build and validation logic lives in `build.yml`, ensuring PR validation and deployment use identical processes.

### 2. Deployment Safety
Multiple safeguards prevent accidental deployment:
- Branch restrictions (`main` only)
- Explicit flags for manual deployment
- Conditional job execution

### 3. Complete PR Validation
PRs undergo the exact same validation as deployment, including:
- All linting and type checking
- Spell checking (both source and generated HTML)
- Full site build
- Link validation

This prevents the "passes CI but fails deployment" scenario.

## Workflow Execution Patterns

### Pattern 1: Normal Development (PR → Merge → Deploy)
1. Developer creates PR → `pr-validation.yml` runs → Full validation
2. PR approved and merged → Push to `main` triggers `deploy.yml`
3. `deploy.yml` runs build → Automatically deploys

### Pattern 2: Manual Deployment Dry-Run
1. Run `deploy.yml` manually from any branch
2. Set `deploy: false` (or leave default)
3. Build runs but deployment is skipped
4. Useful for testing workflow changes

### Pattern 3: Emergency Manual Deployment
1. Run `deploy.yml` manually from `main` branch
2. Set `deploy: true`
3. Full build and deployment executes
4. Useful if automatic deployment fails

## Maintenance Notes

### Adding New Validation Steps
Add new validation steps to `build.yml` only. They will automatically be included in both PR validation and deployment.

### Modifying Deployment Conditions
Edit the `if` condition in the `deploy` job of `deploy.yml`. Current logic:
```yaml
if: |
(github.event_name == 'push' && github.ref == 'refs/heads/main') ||
(github.event_name == 'workflow_dispatch' && inputs.deploy == true && github.ref == 'refs/heads/main')
```

### Debugging Workflow Issues
1. Check the workflow run logs in GitHub Actions tab
2. Use `workflow_dispatch` to manually test workflows
3. The PR validation comment provides a summary of what checks ran

## Security Considerations

- Deployment requires `pages: write` and `id-token: write` permissions (only in deploy.yml)
- PR validation has minimal permissions:
- `contents: read` for checking out code
- `pull-requests: write` for posting status comments
- No write access to Pages (follows principle of least privilege)
- The `configure-pages` action was removed as it's not needed (we don't use its outputs)
- Concurrency groups prevent race conditions during deployment
- Branch protection rules should be configured to require PR validation before merge

### Permission Model
- **PR Validation**: Read-only access (can't modify repository or deploy)
- **Deployment**: Write access only when pushing to main branch
- **Manual Workflow**: Deployment only allowed from main branch with explicit flag
84 changes: 84 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
name: Build and Validate

on:
workflow_call:
inputs:
checkout-ref:
description: 'Git ref to checkout (defaults to default branch)'
type: string
required: false
default: ''
outputs:
artifact-uploaded:
description: 'Whether the pages artifact was uploaded successfully'
value: ${{ jobs.build.outputs.artifact-uploaded }}

jobs:
build:
runs-on: ubuntu-latest
outputs:
artifact-uploaded: ${{ steps.upload.outputs.artifact-uploaded }}

steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: ${{ inputs.checkout-ref }}

- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm'

- name: Install Dependencies
run: npm ci

- name: Run Linting
run: npm run lint

- name: Run Spell Check (Source)
run: npm run spellcheck

- name: Build Site
run: npm run build

- name: Run Spell Check (HTML)
id: spellcheck-html
run: |
echo "Checking HTML files in dist directory..."
npm run spellcheck:html
echo "HTML spell check completed successfully"
continue-on-error: true

- name: Debug spell check failure
if: steps.spellcheck-html.outcome == 'failure'
run: |
echo "::warning::HTML spell check failed - debugging information:"
echo "Files in dist directory:"
find dist -name "*.html" -type f 2>/dev/null | head -10 || echo "No HTML files found"
echo ""
echo "CSpell ignore paths:"
cat cspell.json | grep -A10 '"ignorePaths"' || true
echo ""
echo "Re-running spell check with verbose output:"
npx cspell "dist/**/*.html" --no-progress --verbose 2>&1 | head -30 || true
exit 1

- name: Validate Internal Links
run: npm run validate:links

- name: Upload Pages Artifact
id: upload
uses: actions/upload-pages-artifact@v3
with:
path: './dist'

- name: Set Output
if: always()
run: |
if [ "${{ steps.upload.outcome }}" == "success" ]; then
echo "artifact-uploaded=true" >> $GITHUB_OUTPUT
else
echo "artifact-uploaded=false" >> $GITHUB_OUTPUT
fi
52 changes: 20 additions & 32 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ on:
push:
branches: [main]
workflow_dispatch:
inputs:
deploy:
description: 'Deploy to GitHub Pages (only works from main branch)'
type: boolean
required: false
default: false

permissions:
contents: read
Expand All @@ -16,45 +22,27 @@ concurrency:

jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Pages
uses: actions/configure-pages@v5

- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 20

- name: Install Dependencies
run: npm ci

- name: Run Linting
run: npm run lint

- name: Run Spell Check (Source)
run: npm run spellcheck

- name: Build Site
run: npm run build

- name: Run Spell Check (HTML)
run: npm run spellcheck:html

- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: './dist'
uses: ./.github/workflows/build.yml
permissions:
contents: read
pages: write
id-token: write

deploy:
# Only deploy if:
# 1. Push to main branch (automatic deployment)
# 2. Manual workflow dispatch with deploy=true AND on main branch
if: |
(github.event_name == 'push' && github.ref == 'refs/heads/main') ||
(github.event_name == 'workflow_dispatch' && inputs.deploy == true && github.ref == 'refs/heads/main')

environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}

runs-on: ubuntu-latest
needs: build

steps:
- name: Deploy to GitHub Pages
id: deployment
Expand Down
64 changes: 28 additions & 36 deletions .github/workflows/pr-validation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,56 +12,48 @@ permissions:
jobs:
validate:
name: Validate PR
uses: ./.github/workflows/build.yml
with:
checkout-ref: ${{ github.event.pull_request.head.sha }}
permissions:
contents: read

report-status:
name: Report Validation Status
runs-on: ubuntu-latest
needs: validate
if: always()
permissions:
pull-requests: write

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm'

- name: Install Dependencies
run: npm ci

- name: Run Linting
run: npm run lint
continue-on-error: false

- name: Type Check and Build
run: npm run build
continue-on-error: false

- name: Check Build Output
run: |
if [ ! -d "dist" ]; then
echo "Error: Build output directory 'dist' not found"
exit 1
fi
echo "Build successful - dist directory created"
echo "Files generated: $(find dist -type f | wc -l)"

- name: Validate Internal Links
run: npm run validate:links
continue-on-error: false

- name: Report Status
if: always()
uses: actions/github-script@v7
with:
script: |
const status = '${{ job.status }}';
const status = '${{ needs.validate.result }}';
const icon = status === 'success' ? '✅' : '❌';
const message = status === 'success'
? 'All checks passed! Ready for review.'
: 'Some checks failed. Please review the errors above.';

// Build check list with actual results
const checks = [
'✓ Linting',
'✓ Spell check (source)',
'✓ Type checking & Build',
'✓ Spell check (HTML)',
'✓ Internal link validation',
'✓ Artifact upload'
];

const checkList = status === 'success'
? checks.join('\n')
: 'Please check the workflow logs for details on which checks failed.';

github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: `## PR Validation ${icon}\n\n${message}\n\n### Checks Performed:\n- ✓ Linting\n- ✓ Type checking\n- ✓ Build verification\n- ✓ Internal link validation`
body: `## PR Validation ${icon}\n\n${message}\n\n### Checks Performed:\n${checkList}\n\n*This is a complete dry-run of the deployment process, ensuring your changes will deploy successfully when merged.*`
});
Loading