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**
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 >> $GITHUB_STEP_SUMMARY rm -f "$temp_coverage" cat >> $GITHUB_STEP_SUMMARY << 'EOF'
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