mirror of
https://github.com/kjanat/articulate-parser.git
synced 2026-08-05 15:24:07 +02:00
144125d355
* fix(lint): resolve golangci-lint failures and modernize string handling
CI's golangci-lint (v2.12.2) job was failing with 14 issues, which blocked
the dependent test job. This addresses all of them:
- goconst: extract repeated string literals into constants
- format aliases ("md", "word", "htm") in the exporter factory
- "section" lesson type shared by markdown and HTML exporters
- default Articulate Rise base URL and host in the parser
- reuse existing itemType* constants in the markdown switch
- staticcheck (QF1012): replace buf.WriteString(fmt.Sprintf(...)) with
fmt.Fprintf(...) in the markdown exporter
Also drop the hardcoded `go: "1.24"` from .golangci.yml so the target Go
version is autodetected from go.mod.
* ci: drop test matrix, run a single Go version from go.mod
The test job ran a 1.24.x/1.25.x matrix, but go.mod requires go 1.25.0, so the
1.24 entry just auto-downloaded the 1.25 toolchain and tested the same thing
twice. Replace the matrix with a single job that sources its Go version from
go.mod via go-version-file, and reference the resolved version through the
setup-go step output in summaries, artifact names, and Codecov flags.
* docs: remove emojis from README
* ci: pin modernize tool to gopls v0.21.x for Go 1.25 compatibility
The autofix workflow's `task modernize` step installed the modernize
analyzer from gopls@latest, which as of v0.22.0 requires Go 1.26. The
project targets Go 1.25 (go.mod) and CI runs with GOTOOLCHAIN=local, so
the install failed. Pin to the v0.21.x line, which supports Go >= 1.25.
* ci: keep modernize@latest, fetch its toolchain via GOTOOLCHAIN=auto
Replaces the earlier v0.21.0 pin. The modernize analyzer (gopls v0.22+)
requires Go 1.26, which the project doesn't target yet — and bumping the
module to 1.26 isn't viable because the current golangci-lint release is
built with Go 1.25 and refuses to lint a newer target. Instead, let the
modernize task fetch the toolchain it needs on demand via GOTOOLCHAIN=auto
(setup-go pins GOTOOLCHAIN=local in CI), so we stay on the latest analyzer
without touching the module's Go version.
Also bump golang.org/x/image v0.34.0 -> v0.42.0 via `go get -u ./...`.
* ci: set GOTOOLCHAIN=auto inline for the modernize task
A task-level env: entry does not override GOTOOLCHAIN when setup-go has
already exported GOTOOLCHAIN=local job-wide, so the autofix job still
failed. Set GOTOOLCHAIN=auto inline on the modernize command itself, which
reliably overrides the inherited value and lets Go fetch the toolchain the
modernize analyzer requires.
* ci: bump to Go 1.26 and address PR review feedback
The autofix failure is fixed properly by moving to Go 1.26 instead of the
GOTOOLCHAIN workaround. The official golangci-lint v2.12.2 binary is built
with go1.26.2 and lints a Go 1.26 target fine (the earlier "not ready"
claim was from a locally go-installed binary compiled with Go 1.25), and
the Dockerfiles already use golang:1.26-alpine, so this also aligns the
module with the images.
- go.mod: go 1.25.0 -> 1.26.0, drop the toolchain pin (keeps the lint
target at the go directive).
- Taskfile: revert modernize to plain modernize@latest; on Go 1.26 the
GOTOOLCHAIN dance and its comments are unnecessary.
- ci.yml: pass the resolved Go version through an env var (GO_VERSION)
instead of interpolating steps.setup-go.outputs.go-version directly into
shell scripts (script-injection hygiene); grant the dependency-review job
pull-requests: write so it can post its summary.
- parser.go: derive defaultBaseURL from riseHost instead of duplicating the
host string; drop the redundant per-const comments.
- .golangci.yml: remove the redundant go-version comment.
- regenerate internal/exporters/output.docx.
496 lines
16 KiB
YAML
496 lines
16 KiB
YAML
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@v6
|
|
- uses: actions/setup-go@v6
|
|
with:
|
|
go-version: stable
|
|
- name: golangci-lint
|
|
uses: golangci/golangci-lint-action@v9
|
|
with: { version: latest }
|
|
|
|
test:
|
|
name: Test
|
|
needs: [golangci]
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
contents: write
|
|
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
|
|
- name: Set up Go
|
|
id: setup-go
|
|
uses: actions/setup-go@v6
|
|
with:
|
|
go-version-file: go.mod
|
|
check-latest: true
|
|
|
|
# Expose the resolved Go version as an env var so it is never
|
|
# interpolated directly into a shell script (avoids script injection).
|
|
- name: Export Go version
|
|
env:
|
|
GO_VERSION: ${{ steps.setup-go.outputs.go-version }}
|
|
run: echo "GO_VERSION=$GO_VERSION" >> "$GITHUB_ENV"
|
|
|
|
- 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
|
|
env:
|
|
CGO_ENABLED: 1
|
|
run: |
|
|
{
|
|
cat << EOF
|
|
## 🔧 Test Environment
|
|
- **Go Version:** $GO_VERSION
|
|
- **OS:** ubuntu-latest
|
|
- **Timestamp:** $(date -u)
|
|
|
|
EOF
|
|
} >> "$GITHUB_STEP_SUMMARY"
|
|
|
|
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 << EOF
|
|
## 🧪 Test Results (Go $GO_VERSION)
|
|
|
|
| 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 |"
|
|
elif [[ $line == FAIL* ]]; then
|
|
pkg=$(echo "$line" | awk '{print $2}')
|
|
echo "| $pkg | ❌ FAIL |"
|
|
fi
|
|
done
|
|
|
|
echo ""
|
|
|
|
# Add detailed results if tests failed
|
|
if [ "$TEST_STATUS" -ne 0 ]; then
|
|
cat << 'EOF'
|
|
### ❌ Failed Tests Details
|
|
```
|
|
EOF
|
|
grep -A 10 -- "--- FAIL:" test-output.log | head -100
|
|
cat << 'EOF'
|
|
```
|
|
|
|
EOF
|
|
fi
|
|
} >> "$GITHUB_STEP_SUMMARY"
|
|
|
|
# Set outputs for other steps
|
|
{
|
|
echo "test-status=$TEST_STATUS"
|
|
echo "total-tests=$TOTAL_TESTS"
|
|
echo "passed-tests=$PASSED_TESTS"
|
|
echo "failed-tests=$FAILED_TESTS"
|
|
} >> "$GITHUB_OUTPUT"
|
|
|
|
# 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 << EOF
|
|
## 📊 Code Coverage (Go $GO_VERSION)
|
|
|
|
**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
|
|
|
|
rm -f "$temp_coverage"
|
|
|
|
cat << 'EOF'
|
|
|
|
</details>
|
|
|
|
EOF
|
|
} >> "$GITHUB_STEP_SUMMARY"
|
|
else
|
|
cat >> "$GITHUB_STEP_SUMMARY" << 'EOF'
|
|
## ⚠️ Coverage Report
|
|
No coverage file generated
|
|
|
|
EOF
|
|
fi
|
|
|
|
- name: Upload test artifacts
|
|
if: failure()
|
|
uses: actions/upload-artifact@v6
|
|
with:
|
|
name: test-results-go-${{ env.GO_VERSION }}
|
|
path: |
|
|
test-output.log
|
|
coverage/
|
|
retention-days: 7
|
|
|
|
- name: Run linters
|
|
run: |
|
|
{
|
|
cat << EOF
|
|
## 🔍 Static Analysis (Go $GO_VERSION)
|
|
|
|
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"
|
|
else
|
|
cat << 'EOF'
|
|
❌ **go vet:** Issues found
|
|
|
|
```
|
|
EOF
|
|
echo "$VET_OUTPUT"
|
|
echo '```'
|
|
fi
|
|
echo ""
|
|
|
|
# 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"
|
|
else
|
|
cat << 'EOF'
|
|
❌ **go fmt:** Files need formatting
|
|
|
|
```
|
|
EOF
|
|
echo "$FMT_OUTPUT"
|
|
echo '```'
|
|
fi
|
|
} >> "$GITHUB_STEP_SUMMARY"
|
|
|
|
# Exit with error if any linter failed
|
|
[ "$VET_STATUS" -eq 0 ] && [ "$FMT_STATUS" -eq 0 ] || exit 1
|
|
|
|
- name: Job Summary
|
|
if: always()
|
|
run: |
|
|
{
|
|
echo "## 📋 Job Summary (Go $GO_VERSION)"
|
|
echo ""
|
|
cat << 'EOF'
|
|
| 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
|
|
} >> "$GITHUB_STEP_SUMMARY"
|
|
|
|
- name: Upload coverage reports to Codecov
|
|
uses: codecov/codecov-action@v5
|
|
with:
|
|
files: ./coverage/coverage.out
|
|
flags: Go ${{ env.GO_VERSION }}
|
|
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 ${{ env.GO_VERSION }}
|
|
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@v6
|
|
|
|
- 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 << 'EOF'
|
|
## 🧪 Docker Image Tests
|
|
|
|
EOF
|
|
|
|
# Run Task docker test
|
|
task docker:test
|
|
|
|
echo "**Testing help command:**"
|
|
echo '```terminaloutput'
|
|
docker run --rm articulate-parser:latest --help
|
|
echo '```'
|
|
echo ""
|
|
|
|
# Test image size
|
|
IMAGE_SIZE=$(docker image inspect articulate-parser:latest --format='{{.Size}}' | numfmt --to=iec-i --suffix=B)
|
|
echo "**Image size:** $IMAGE_SIZE"
|
|
echo ""
|
|
} >> "$GITHUB_STEP_SUMMARY"
|
|
|
|
dependency-review:
|
|
name: Dependency Review
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
contents: read
|
|
pull-requests: write
|
|
if: github.event_name == 'pull_request'
|
|
steps:
|
|
- name: "Checkout Repository"
|
|
uses: actions/checkout@v6
|
|
|
|
- name: "Dependency Review"
|
|
uses: actions/dependency-review-action@v5
|
|
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]
|
|
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@v6
|
|
|
|
- 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@v4
|
|
|
|
- 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
|