name: CI on: push: branches: ['master', 'develop'] pull_request: branches: ['master', 'develop', 'feature/*'] env: REGISTRY: ghcr.io IMAGE_NAME: ${{ github.repository }} concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: test: name: Test runs-on: ubuntu-latest permissions: contents: write strategy: matrix: go: - 1.21.x - 1.22.x - 1.23.x - 1.24.x steps: - uses: actions/checkout@v4 - name: Set up Go ${{ matrix.go }} uses: actions/setup-go@v5 with: go-version: ${{ matrix.go }} check-latest: true - name: Download dependencies with retry run: | set -e echo "Downloading Go dependencies..." # Function to download with retry download_with_retry() { local attempt=1 local max_attempts=3 while [ $attempt -le $max_attempts ]; do echo "Attempt $attempt of $max_attempts" if go mod download; then echo "Download successful on attempt $attempt" return 0 else echo "Download failed on attempt $attempt" if [ $attempt -lt $max_attempts ]; then echo "Cleaning cache and retrying..." go clean -modcache go clean -cache sleep 2 fi attempt=$((attempt + 1)) fi done echo "All download attempts failed" return 1 } # Try download with retry logic download_with_retry echo "Verifying module dependencies..." go mod verify echo "Dependencies verified successfully" - name: Build run: go build -v ./... - name: Run tests with enhanced reporting id: test run: | echo "## ๐Ÿ”ง Test Environment" >> $GITHUB_STEP_SUMMARY echo "- **Go Version:** ${{ matrix.go }}" >> $GITHUB_STEP_SUMMARY echo "- **OS:** ubuntu-latest" >> $GITHUB_STEP_SUMMARY echo "- **Timestamp:** $(date -u)" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "Running tests with coverage..." go test -v -race -coverprofile=coverage.out ./... 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 echo "## ๐Ÿงช Test Results (Go ${{ matrix.go }})" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "| Metric | Value |" >> $GITHUB_STEP_SUMMARY echo "|--------|-------|" >> $GITHUB_STEP_SUMMARY echo "| Total Tests | $TOTAL_TESTS |" >> $GITHUB_STEP_SUMMARY echo "| Passed | โœ… $PASSED_TESTS |" >> $GITHUB_STEP_SUMMARY echo "| Failed | โŒ $FAILED_TESTS |" >> $GITHUB_STEP_SUMMARY echo "| Skipped | โญ๏ธ $SKIPPED_TESTS |" >> $GITHUB_STEP_SUMMARY echo "| Status | $([ $TEST_STATUS -eq 0 ] && echo "โœ… PASSED" || echo "โŒ FAILED") |" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY # Add package breakdown echo "### ๐Ÿ“ฆ Package Test Results" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "| Package | Status |" >> $GITHUB_STEP_SUMMARY echo "|---------|--------|" >> $GITHUB_STEP_SUMMARY # Extract package results grep "^ok\|^FAIL" test-output.log | while read 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 echo "### โŒ Failed Tests Details" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "\`\`\`" >> $GITHUB_STEP_SUMMARY grep -A 10 "--- FAIL:" test-output.log | head -100 >> $GITHUB_STEP_SUMMARY echo "\`\`\`" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY fi # Set outputs for other steps echo "test-status=$TEST_STATUS" >> $GITHUB_OUTPUT echo "total-tests=$TOTAL_TESTS" >> $GITHUB_OUTPUT echo "passed-tests=$PASSED_TESTS" >> $GITHUB_OUTPUT 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.out ]; then go tool cover -html=coverage.out -o coverage.html COVERAGE=$(go tool cover -func=coverage.out | grep total | awk '{print $3}') echo "## ๐Ÿ“Š Code Coverage (Go ${{ matrix.go }})" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "**Total Coverage: $COVERAGE**" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY # Add coverage by package echo "
" >> $GITHUB_STEP_SUMMARY echo "Click to expand ๐Ÿ“‹ Coverage by Package details" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "| Package | Coverage |" >> $GITHUB_STEP_SUMMARY echo "|---------|----------|" >> $GITHUB_STEP_SUMMARY # Create temporary file for package coverage aggregation temp_coverage=$(mktemp) # Extract package-level coverage data go tool cover -func=coverage.out | grep -v total | while read 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/||' | sed 's|^\./||') coverage=$(echo "$line" | awk '{print $3}' | sed 's/%//') # Use root package if no subdirectory if [[ "$pkg_path" == "." || "$pkg_path" == "" ]]; then pkg_path="root" fi 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 echo "
" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY else echo "## โš ๏ธ Coverage Report" >> $GITHUB_STEP_SUMMARY echo "No coverage file generated" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY fi - name: Upload test artifacts if: failure() uses: actions/upload-artifact@v4 with: name: test-results-go-${{ matrix.go }} path: | test-output.log coverage.out coverage.html retention-days: 7 - name: Run go vet run: | echo "## ๐Ÿ” Static Analysis (Go ${{ matrix.go }})" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY VET_OUTPUT=$(go vet ./... 2>&1 || echo "") VET_STATUS=$? if [ $VET_STATUS -eq 0 ]; then echo "โœ… **go vet:** No issues found" >> $GITHUB_STEP_SUMMARY else echo "โŒ **go vet:** Issues found" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "\`\`\`" >> $GITHUB_STEP_SUMMARY echo "$VET_OUTPUT" >> $GITHUB_STEP_SUMMARY echo "\`\`\`" >> $GITHUB_STEP_SUMMARY fi echo "" >> $GITHUB_STEP_SUMMARY exit $VET_STATUS - name: Run go fmt run: | FMT_OUTPUT=$(gofmt -s -l . 2>&1 || echo "") if [ -z "$FMT_OUTPUT" ]; then echo "โœ… **go fmt:** All files properly formatted" >> $GITHUB_STEP_SUMMARY else echo "โŒ **go fmt:** Files need formatting" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "\`\`\`" >> $GITHUB_STEP_SUMMARY echo "$FMT_OUTPUT" >> $GITHUB_STEP_SUMMARY echo "\`\`\`" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY exit 1 fi - name: Job Summary if: always() run: | echo "## ๐Ÿ“‹ Job Summary (Go ${{ matrix.go }})" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "| Step | Status |" >> $GITHUB_STEP_SUMMARY echo "|------|--------|" >> $GITHUB_STEP_SUMMARY echo "| Dependencies | โœ… Success |" >> $GITHUB_STEP_SUMMARY echo "| Build | โœ… Success |" >> $GITHUB_STEP_SUMMARY echo "| Tests | ${{ steps.test.outcome == 'success' && 'โœ… Success' || 'โŒ Failed' }} |" >> $GITHUB_STEP_SUMMARY echo "| Coverage | ${{ job.status == 'success' && 'โœ… Generated' || 'โš ๏ธ Partial' }} |" >> $GITHUB_STEP_SUMMARY echo "| Static Analysis | ${{ job.status == 'success' && 'โœ… Clean' || 'โŒ Issues' }} |" >> $GITHUB_STEP_SUMMARY echo "| Code Formatting | ${{ job.status == 'success' && 'โœ… Clean' || 'โŒ Issues' }} |" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY - name: Upload coverage reports to Codecov uses: codecov/codecov-action@v5 with: 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@v4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Capture build date run: echo "BUILD_TIME=$(git log -1 --format=%cd --date=iso-strict)" >> $GITHUB_ENV - name: Build Docker image (test) uses: docker/build-push-action@v6 with: context: . push: false load: true tags: test:latest build-args: | VERSION=test BUILD_TIME=${{ env.BUILD_TIME }} GIT_COMMIT=${{ github.sha }} cache-from: type=gha cache-to: type=gha,mode=max - name: Test Docker image run: | echo "## ๐Ÿงช Docker Image Tests" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY # Test that the image runs and shows help echo "**Testing help command:**" >> $GITHUB_STEP_SUMMARY echo "\`\`\`" >> $GITHUB_STEP_SUMMARY docker run --rm test:latest --help >> $GITHUB_STEP_SUMMARY echo "\`\`\`" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY # Test image size IMAGE_SIZE=$(docker image inspect test: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@v4 - 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@v4 - 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: | echo "## ๐Ÿณ Docker Build Summary" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "**Image:** \`ghcr.io/${{ github.repository }}\`" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "**Tags built:**" >> $GITHUB_STEP_SUMMARY echo "\`\`\`" >> $GITHUB_STEP_SUMMARY echo "${{ steps.meta.outputs.tags }}" >> $GITHUB_STEP_SUMMARY echo "\`\`\`" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "**Features:**" >> $GITHUB_STEP_SUMMARY echo "- **Platforms:** linux/amd64, linux/arm64, linux/arm/v7, linux/386, linux/ppc64le, linux/s390x" >> $GITHUB_STEP_SUMMARY echo "- **Architecture optimization:** โœ… Native compilation for each platform" >> $GITHUB_STEP_SUMMARY echo "- **Multi-arch image description:** โœ… Enabled" >> $GITHUB_STEP_SUMMARY echo "- **SBOM (Software Bill of Materials):** โœ… Generated" >> $GITHUB_STEP_SUMMARY echo "- **Provenance attestation:** โœ… Generated" >> $GITHUB_STEP_SUMMARY echo "- **Security scanning:** Ready for vulnerability analysis" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "**Usage:**" >> $GITHUB_STEP_SUMMARY echo "\`\`\`bash" >> $GITHUB_STEP_SUMMARY echo "# Pull the image" >> $GITHUB_STEP_SUMMARY echo "docker pull ghcr.io/${{ github.repository }}:latest" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "# Run with help" >> $GITHUB_STEP_SUMMARY echo "docker run --rm ghcr.io/${{ github.repository }}:latest --help" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "# Process a local file (mount current directory)" >> $GITHUB_STEP_SUMMARY echo "docker run --rm -v \$(pwd):/workspace ghcr.io/${{ github.repository }}:latest /workspace/input.json markdown /workspace/output.md" >> $GITHUB_STEP_SUMMARY echo "\`\`\`" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY # 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