Skip to content

refactor(exporter): rewrite HTML exporter to use Go templates #125

refactor(exporter): rewrite HTML exporter to use Go templates

refactor(exporter): rewrite HTML exporter to use Go templates #125

Workflow file for this run

name: CI
on:
push:
branches: ['master', 'develop']
pull_request:
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
golangci:
name: lint
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read
steps:
- uses: actions/checkout@v5
- uses: actions/setup-go@v6
with:
go-version: stable
- name: golangci-lint
uses: golangci/golangci-lint-action@v8
with: { version: latest }
test:
name: Test
needs: [golangci]
runs-on: ubuntu-latest
permissions:
contents: write
strategy:
matrix:
go:
- 1.21.x
- 1.22.x
- 1.23.x
- 1.24.x
- 1.25.x
steps:
- uses: actions/checkout@v5
- name: Set up Go ${{ matrix.go }}
uses: actions/setup-go@v6
with:
go-version: ${{ matrix.go }}
check-latest: true
- name: Install Task
uses: go-task/setup-task@v1
- name: Show build info
run: task info
- name: Download dependencies
run: task deps
- name: Build
run: task build
- name: Run tests with enhanced reporting
id: test
run: |
cat >> $GITHUB_STEP_SUMMARY << EOF
## 🔧 Test Environment
- **Go Version:** ${{ matrix.go }}
- **OS:** ubuntu-latest
- **Timestamp:** $(date -u)
EOF
echo "Running tests with coverage..."
task test:coverage 2>&1 | tee test-output.log
# Extract test results for summary
TEST_STATUS=$?
TOTAL_TESTS=$(grep -c "=== RUN" test-output.log || echo "0")
PASSED_TESTS=$(grep -c "--- PASS:" test-output.log || echo "0")
FAILED_TESTS=$(grep -c "--- FAIL:" test-output.log || echo "0")
SKIPPED_TESTS=$(grep -c "--- SKIP:" test-output.log || echo "0")
# Generate test summary
cat >> $GITHUB_STEP_SUMMARY << EOF
## 🧪 Test Results (Go ${{ matrix.go }})
| Metric | Value |
| ----------- | ----------------------------------------------------------- |
| Total Tests | $TOTAL_TESTS |
| Passed | $PASSED_TESTS |
| Failed | $FAILED_TESTS |
| Skipped | $SKIPPED_TESTS |
| Status | $([ $TEST_STATUS -eq 0 ] && echo "PASSED" || echo "FAILED") |
### 📦 Package Test Results
| Package | Status |
|---------|--------|
EOF
# Extract package results
grep "^ok\|^FAIL" test-output.log | while read -r line; do
if [[ $line == ok* ]]; then
pkg=$(echo "$line" | awk '{print $2}')
echo "| $pkg | ✅ PASS |" >> $GITHUB_STEP_SUMMARY
elif [[ $line == FAIL* ]]; then
pkg=$(echo "$line" | awk '{print $2}')
echo "| $pkg | ❌ FAIL |" >> $GITHUB_STEP_SUMMARY
fi
done
echo "" >> $GITHUB_STEP_SUMMARY
# Add detailed results if tests failed
if [ $TEST_STATUS -ne 0 ]; then
cat >> $GITHUB_STEP_SUMMARY << 'EOF'
### ❌ Failed Tests Details
```
EOF
grep -A 10 "--- FAIL:" test-output.log | head -100 >> $GITHUB_STEP_SUMMARY
cat >> $GITHUB_STEP_SUMMARY << 'EOF'
```
EOF
fi
# Set outputs for other steps
cat >> $GITHUB_OUTPUT << EOF
test-status=$TEST_STATUS
total-tests=$TOTAL_TESTS
passed-tests=$PASSED_TESTS
failed-tests=$FAILED_TESTS
EOF
# Exit with the original test status
exit $TEST_STATUS
- name: Generate coverage report
if: always()
run: |
if [ -f coverage/coverage.out ]; then
COVERAGE=$(go tool cover -func=coverage/coverage.out | grep total | awk '{print $3}')
cat >> $GITHUB_STEP_SUMMARY << EOF
## 📊 Code Coverage (Go ${{ matrix.go }})
**Total Coverage: $COVERAGE**
<details>
<summary>Click to expand 📋 Coverage by Package details</summary>
| Package | Coverage |
| ------- | -------- |
EOF
# Create temporary file for package coverage aggregation
temp_coverage=$(mktemp)
# Extract package-level coverage data
go tool cover -func=coverage/coverage.out | grep -v total | while read -r line; do
if [[ $line == *".go:"* ]]; then
# Extract package path from file path (everything before the filename)
filepath=$(echo "$line" | awk '{print $1}')
pkg_path=$(dirname "$filepath" | sed 's|github.com/kjanat/articulate-parser/||; s|^\./||')
coverage=$(echo "$line" | awk '{print $3}' | sed 's/%//')
# Use root package if no subdirectory
[[ "$pkg_path" == "." || "$pkg_path" == "" ]] && pkg_path="root"
echo "$pkg_path $coverage" >> "$temp_coverage"
fi
done
# Aggregate coverage by package (average)
awk '{
packages[$1] += $2
counts[$1]++
}
END {
for (pkg in packages) {
avg = packages[pkg] / counts[pkg]
printf "| %s | %.1f%% |\n", pkg, avg
}
}' "$temp_coverage" | sort >> $GITHUB_STEP_SUMMARY
rm -f "$temp_coverage"
cat >> $GITHUB_STEP_SUMMARY << 'EOF'
</details>
EOF
else
cat >> $GITHUB_STEP_SUMMARY << 'EOF'
## ⚠️ Coverage Report
No coverage file generated
EOF
fi
- name: Upload test artifacts
if: failure()
uses: actions/upload-artifact@v5
with:
name: test-results-go-${{ matrix.go }}
path: |
test-output.log
coverage/
retention-days: 7
- name: Run linters
run: |
# Initialize summary
cat >> $GITHUB_STEP_SUMMARY << EOF
## 🔍 Static Analysis (Go ${{ matrix.go }})
EOF
# Run go vet
VET_OUTPUT=$(task lint:vet 2>&1 || echo "")
VET_STATUS=$?
if [ $VET_STATUS -eq 0 ]; then
echo "✅ **go vet:** No issues found" >> $GITHUB_STEP_SUMMARY
else
cat >> $GITHUB_STEP_SUMMARY << 'EOF'
❌ **go vet:** Issues found
```
EOF
echo "$VET_OUTPUT" >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
fi
echo "" >> $GITHUB_STEP_SUMMARY
# Run go fmt check
FMT_OUTPUT=$(task lint:fmt 2>&1 || echo "")
FMT_STATUS=$?
if [ $FMT_STATUS -eq 0 ]; then
echo "✅ **go fmt:** All files properly formatted" >> $GITHUB_STEP_SUMMARY
else
cat >> $GITHUB_STEP_SUMMARY << 'EOF'
❌ **go fmt:** Files need formatting
```
EOF
echo "$FMT_OUTPUT" >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
fi
# Exit with error if any linter failed
[ $VET_STATUS -eq 0 ] && [ $FMT_STATUS -eq 0 ] || exit 1
- name: Job Summary
if: always()
run: |
cat >> $GITHUB_STEP_SUMMARY << 'EOF'
## 📋 Job Summary (Go ${{ matrix.go }})
| Step | Status |
| --------------- | --------------------------------------------------------------- |
| Dependencies | Success |
| Build | Success |
| Tests | ${{ steps.test.outcome == 'success' && 'Success' || 'Failed' }} |
| Coverage | ${{ job.status == 'success' && 'Generated' || 'Partial' }} |
| Static Analysis | ${{ job.status == 'success' && 'Clean' || 'Issues' }} |
| Code Formatting | ${{ job.status == 'success' && 'Clean' || 'Issues' }} |
EOF
- name: Upload coverage reports to Codecov
uses: codecov/codecov-action@v5
with:
files: ./coverage/coverage.out
flags: Go ${{ matrix.go }}
slug: kjanat/articulate-parser
token: ${{ secrets.CODECOV_TOKEN }}
- name: Upload test results to Codecov
if: ${{ !cancelled() }}
uses: codecov/test-results-action@v1
with:
flags: Go ${{ matrix.go }}
token: ${{ secrets.CODECOV_TOKEN }}
docker-test:
name: Docker Build Test
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
permissions:
contents: read
steps:
- name: Checkout repository
uses: actions/checkout@v5
- name: Set up Go
uses: actions/setup-go@v6
with:
go-version-file: go.mod
check-latest: true
- name: Install Task
uses: go-task/setup-task@v1
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build Docker image using Task
run: task docker:build
- name: Test Docker image using Task
run: |
cat >> $GITHUB_STEP_SUMMARY << 'EOF'
## 🧪 Docker Image Tests
EOF
# Run Task docker test
task docker:test
echo "**Testing help command:**" >> $GITHUB_STEP_SUMMARY
echo '```terminaloutput' >> $GITHUB_STEP_SUMMARY
docker run --rm articulate-parser:latest --help >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
# Test image size
IMAGE_SIZE=$(docker image inspect articulate-parser:latest --format='{{.Size}}' | numfmt --to=iec-i --suffix=B)
echo "**Image size:** $IMAGE_SIZE" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
dependency-review:
name: Dependency Review
runs-on: ubuntu-latest
permissions:
contents: read
if: github.event_name == 'pull_request'
steps:
- name: 'Checkout Repository'
uses: actions/checkout@v5
- name: 'Dependency Review'
uses: actions/dependency-review-action@v4
with:
fail-on-severity: moderate
comment-summary-in-pr: always
docker:
name: Docker Build & Push
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
needs: [test, docker-test, dependency-review]
if: |
github.event_name == 'push' && (github.ref == 'refs/heads/master' ||
github.ref == 'refs/heads/develop' ||
startsWith(github.ref, 'refs/heads/feature/docker'))
steps:
- name: Checkout repository
uses: actions/checkout@v5
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: |
${{ env.IMAGE_NAME }}
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=raw,value=latest,enable={{is_default_branch}}
labels: |
org.opencontainers.image.title=Articulate Parser
org.opencontainers.image.description=A powerful CLI tool to parse Articulate Rise courses and export them to multiple formats including Markdown HTML and DOCX. Supports media extraction content cleaning and batch processing for educational content conversion.
org.opencontainers.image.vendor=kjanat
org.opencontainers.image.licenses=MIT
org.opencontainers.image.url=https://github.com/${{ github.repository }}
org.opencontainers.image.source=https://github.com/${{ github.repository }}
org.opencontainers.image.documentation=https://github.com/${{ github.repository }}/blob/master/DOCKER.md
- name: Build and push Docker image
uses: docker/build-push-action@v6
with:
context: .
# Multi-architecture build - Docker automatically provides TARGETOS, TARGETARCH, etc.
# Based on Go's supported platforms from 'go tool dist list'
platforms: |
linux/amd64
linux/arm64
linux/arm/v7
linux/386
linux/ppc64le
linux/s390x
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
annotations: ${{ steps.meta.outputs.labels }}
build-args: |
VERSION=${{ github.ref_type == 'tag' && github.ref_name || github.sha }}
BUILD_TIME=${{ github.event.head_commit.timestamp }}
GIT_COMMIT=${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
outputs: type=image,name=target,annotation-index.org.opencontainers.image.description=A powerful CLI tool to parse Articulate Rise courses and export them to multiple formats including Markdown HTML and DOCX. Supports media extraction content cleaning and batch processing for educational content conversion.
sbom: true
provenance: true
- name: Generate Docker summary
run: |
cat >> $GITHUB_STEP_SUMMARY << 'EOF'
## 🐳 Docker Build Summary
**Image:** `ghcr.io/${{ github.repository }}`
**Tags built:**
```text
${{ steps.meta.outputs.tags }}
```
**Features:**
- **Platforms:** linux/amd64, linux/arm64, linux/arm/v7, linux/386, linux/ppc64le, linux/s390x
- **Architecture optimization:** Native compilation for each platform
- **Multi-arch image description:** Enabled
- **SBOM (Software Bill of Materials):** Generated
- **Provenance attestation:** Generated
- **Security scanning:** Ready for vulnerability analysis
**Usage:**
```bash
# Pull the image
docker pull ghcr.io/${{ github.repository }}:latest
# Run with help
docker run --rm ghcr.io/${{ github.repository }}:latest --help
# Process a local file (mount current directory)
docker run --rm -v $(pwd):/workspace ghcr.io/${{ github.repository }}:latest /workspace/input.json markdown /workspace/output.md
```
EOF
# Security and quality analysis workflows
codeql-analysis:
name: CodeQL Analysis
uses: ./.github/workflows/codeql.yml
permissions:
security-events: write
packages: read
actions: read
contents: read