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.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 env: CGO_ENABLED: 1 run: | { cat << EOF ## ๐Ÿ”ง Test Environment - **Go Version:** ${{ matrix.go }} - **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 ${{ 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 |" 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 ${{ matrix.go }}) **Total Coverage: $COVERAGE**
Click to expand ๐Ÿ“‹ Coverage by Package details | 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'
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@v5 with: name: test-results-go-${{ matrix.go }} path: | test-output.log coverage/ retention-days: 7 - name: Run linters run: | # Initialize summary { cat << 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" 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: | 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 << '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 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] 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