Node.js CD #3
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Continuous Deployment for Node.js | |
| # Triggered only on version tags with football terminology names | |
| # Example tag: v1.0.0-assist | |
| name: Node.js CD | |
| on: | |
| push: | |
| tags: | |
| - "v*.*.*-*" | |
| env: | |
| REGISTRY: ghcr.io | |
| IMAGE_NAME: ${{ github.repository }} | |
| jobs: | |
| test: | |
| name: Test before deployment | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v6 | |
| - name: Set up Node.js | |
| uses: actions/setup-node@v6.3.0 | |
| with: | |
| node-version-file: ".nvmrc" | |
| cache: "npm" | |
| cache-dependency-path: package-lock.json | |
| - name: Install dependencies | |
| run: npm ci | |
| - name: Compile application | |
| run: npm run build | |
| - name: Run tests with coverage | |
| run: npm run coverage | |
| release: | |
| name: Build and publish Docker image | |
| needs: test | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| packages: write | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 | |
| - name: Extract version and term name from tag | |
| id: extract_tag | |
| run: | | |
| # Tag format: v1.0.0-assist | |
| TAG_NAME=${GITHUB_REF#refs/tags/} | |
| # Extract semantic version (e.g., v1.0.0-assist -> 1.0.0) | |
| SEMVER=$(echo "$TAG_NAME" | sed -E 's/^v([0-9]+\.[0-9]+\.[0-9]+)-.*/\1/') | |
| # Extract term name (e.g., v1.0.0-assist -> assist) | |
| TERM=$(echo "$TAG_NAME" | sed -E 's/^v[0-9]+\.[0-9]+\.[0-9]+-//') | |
| # Validate semver format (X.Y.Z) | |
| if ! echo "$SEMVER" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+$'; then | |
| echo "❌ Error: Invalid semantic version '$SEMVER' extracted from tag '$TAG_NAME'" | |
| echo "Expected format: v{MAJOR}.{MINOR}.{PATCH}-{TERM} (e.g., v1.0.0-assist)" | |
| exit 1 | |
| fi | |
| # Valid football terminology terms (A-Z from CHANGELOG.md) | |
| VALID_TERMS="assist bicyclekick corner dribble equalizer foul goal header interception juggle keeper lob marking nutmeg offside penalty quickthrow redcard save tackle upset volley wing xpass yellowcard zonedefense" | |
| # Validate term name against the list | |
| if [ -z "$TERM" ]; then | |
| echo "❌ Error: Term name is empty in tag '$TAG_NAME'" | |
| echo "Expected format: v{MAJOR}.{MINOR}.{PATCH}-{TERM} (e.g., v1.0.0-assist)" | |
| exit 1 | |
| fi | |
| if ! echo "$VALID_TERMS" | grep -qw "$TERM"; then | |
| echo "❌ Error: Invalid term name '$TERM' in tag '$TAG_NAME'" | |
| echo "Valid terms (A-Z): $VALID_TERMS" | |
| echo "See CHANGELOG.md for the complete list" | |
| exit 1 | |
| fi | |
| # Export validated outputs | |
| echo "tag_name=$TAG_NAME" >> $GITHUB_OUTPUT | |
| echo "semver=$SEMVER" >> $GITHUB_OUTPUT | |
| echo "term=$TERM" >> $GITHUB_OUTPUT | |
| echo "📦 Release version: $SEMVER" | |
| echo "⚽ Term name: $TERM" | |
| - name: Log in to GitHub Container Registry | |
| uses: docker/login-action@v4.0.0 | |
| with: | |
| registry: ${{ env.REGISTRY }} | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v4.0.0 | |
| - name: Build and push Docker image | |
| uses: docker/build-push-action@v7.0.0 | |
| with: | |
| context: . | |
| push: true | |
| platforms: linux/amd64 | |
| provenance: false | |
| cache-from: type=gha | |
| cache-to: type=gha,mode=max | |
| tags: | | |
| ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest | |
| ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.extract_tag.outputs.semver }} | |
| ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.extract_tag.outputs.term }} | |
| - name: Generate changelog | |
| id: changelog | |
| run: | | |
| # Get previous tag | |
| PREVIOUS_TAG=$(git tag --sort=-version:refname | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+-' | sed -n '2p') | |
| if [ -z "$PREVIOUS_TAG" ]; then | |
| echo "📝 First release - no previous tag found" | |
| CHANGELOG="Initial release" | |
| else | |
| echo "📝 Generating changelog from $PREVIOUS_TAG to ${{ steps.extract_tag.outputs.tag_name }}" | |
| CHANGELOG=$(git log --pretty=format:"- %s (%h)" ${PREVIOUS_TAG}..${{ steps.extract_tag.outputs.tag_name }}) | |
| # Guard against empty changelog (e.g., re-tagging same commit) | |
| if [ -z "$CHANGELOG" ]; then | |
| CHANGELOG="No new changes since $PREVIOUS_TAG" | |
| fi | |
| fi | |
| # Write changelog to file | |
| echo "$CHANGELOG" > changelog.txt | |
| cat changelog.txt | |
| # Set output for use in release body | |
| { | |
| echo "changelog<<EOF" | |
| echo "$CHANGELOG" | |
| echo "EOF" | |
| } >> $GITHUB_OUTPUT | |
| - name: Create GitHub Release | |
| uses: softprops/action-gh-release@v2.6.1 | |
| with: | |
| name: "v${{ steps.extract_tag.outputs.semver }} - ${{ steps.extract_tag.outputs.term }} ⚽" | |
| tag_name: ${{ steps.extract_tag.outputs.tag_name }} | |
| body: | | |
| # Release ${{ steps.extract_tag.outputs.semver }} - ${{ steps.extract_tag.outputs.term }} ⚽ | |
| ## Docker Images | |
| Pull this release using any of the following tags: | |
| ```bash | |
| # By semantic version (recommended) | |
| docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.extract_tag.outputs.semver }} | |
| # By term name | |
| docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.extract_tag.outputs.term }} | |
| # Latest | |
| docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest | |
| ``` | |
| ## Quick Start | |
| ```bash | |
| docker run -p 9000:9000 ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.extract_tag.outputs.semver }} | |
| ``` | |
| Visit http://localhost:9000/swagger/ for API documentation. | |
| ## Changelog | |
| ${{ steps.changelog.outputs.changelog }} | |
| --- | |
| 📦 **Package:** [${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}](https://github.com/${{ github.repository }}/pkgs/container/ts-node-samples-express-restful) | |
| draft: false | |
| prerelease: false | |
| generate_release_notes: true |