# yaml-language-server: $schema=https://json.schemastore.org/github-action.json # permissions: # - contents: write # Required for creating releases --- name: Do Monthly Release description: 'Creates a release for the current month, incrementing patch number if necessary.' author: 'Ismo Vuorinen' branding: icon: calendar color: blue inputs: token: description: 'GitHub token with permission to create releases.' required: true default: ${{ github.token }} dry-run: description: 'Run in dry-run mode without creating the release.' required: false default: 'false' prefix: description: 'Optional prefix for release tags.' required: false default: '' outputs: release-tag: description: 'The tag of the created release' value: ${{ steps.create-release.outputs.release_tag }} release-url: description: 'The URL of the created release' value: ${{ steps.create-release.outputs.release_url }} previous-tag: description: 'The previous release tag' value: ${{ steps.create-release.outputs.previous_tag }} runs: using: 'composite' steps: - name: Validate Inputs shell: sh env: INPUT_TOKEN: ${{ inputs.token }} INPUT_DRY_RUN: ${{ inputs.dry-run }} INPUT_PREFIX: ${{ inputs.prefix }} run: | set -eu # Validate token if [ -z "$INPUT_TOKEN" ]; then echo "::error::GitHub token is required" exit 1 fi # Validate dry-run option if [ "$INPUT_DRY_RUN" != "true" ] && [ "$INPUT_DRY_RUN" != "false" ]; then echo "::error::dry-run must be either 'true' or 'false'" exit 1 fi # Validate prefix format if provided (alphanumeric, dots, underscores, hyphens) if [ -n "$INPUT_PREFIX" ]; then # Use case pattern matching for validation case "$INPUT_PREFIX" in *[!a-zA-Z0-9_.-]*) echo "::error::Invalid prefix format. Only alphanumeric characters, dots, underscores, and hyphens are allowed" exit 1 ;; esac fi # Write validated values to GITHUB_ENV for use in subsequent steps { echo "VALIDATED_TOKEN=$INPUT_TOKEN" echo "VALIDATED_DRY_RUN=$INPUT_DRY_RUN" echo "VALIDATED_PREFIX=$INPUT_PREFIX" } >> "$GITHUB_ENV" - name: Checkout Repository uses: actions/checkout@71cf2267d89c5cb81562390fa70a37fa40b1305e # v6-beta with: token: ${{ env.VALIDATED_TOKEN }} fetch-depth: 0 # Fetch all history for tag comparison - name: Create Release id: create-release shell: sh run: | set -eu # Use validated environment variables from GITHUB_ENV GITHUB_TOKEN="$VALIDATED_TOKEN" PREFIX="$VALIDATED_PREFIX" DRY_RUN="$VALIDATED_DRY_RUN" # Function to validate version format (YYYY.M.N or YYYY.MM.N) validate_version() { version=$1 # Check basic format with case statement case "$version" in [0-9][0-9][0-9][0-9].[0-9].[0-9]*|[0-9][0-9][0-9][0-9].[0-9][0-9].[0-9]*) # Extract parts for detailed validation year="${version%%.*}" rest="${version#*.}" month="${rest%%.*}" patch="${rest#*.}" # Validate year (4 digits) if [ ${#year} -ne 4 ]; then echo "::error::Invalid version format: $version" return 1 fi # Validate month (1 or 2 digits) if [ ${#month} -gt 2 ] || [ ${#month} -lt 1 ]; then echo "::error::Invalid version format: $version" return 1 fi # Validate patch is numeric case "$patch" in ''|*[!0-9]*) echo "::error::Invalid version format: $version" return 1 ;; esac ;; *) echo "::error::Invalid version format: $version" return 1 ;; esac } # Function to get previous release tag with error handling get_previous_tag() { local previous_tag previous_tag=$(gh release list --limit 1 2>/dev/null | awk '{ print $1 }') || { echo "::warning::No previous releases found" return 1 } echo "$previous_tag" } # Get previous release tag previous_tag=$(get_previous_tag) || previous_tag="" echo "previous_tag=${previous_tag}" >> $GITHUB_OUTPUT if [ -n "$previous_tag" ]; then previous_major="${previous_tag%%\.*}" previous_minor="${previous_tag#*.}" previous_minor="${previous_minor%.*}" previous_patch="${previous_tag##*.}" # Validate previous tag format validate_version "$previous_tag" || { echo "::error::Invalid previous tag format: $previous_tag" exit 1 } fi # Determine next release tag next_major_minor="$(date +'%Y').$(date +'%-m')" if [ -n "$previous_tag" ] && [ "${previous_major}.${previous_minor}" = "${next_major_minor}" ]; then echo "Month release already exists for year, incrementing patch number by 1" next_patch="$((previous_patch + 1))" else echo "Month release does not exist for year, setting patch number to 0" next_patch="0" fi # Construct release tag release_tag="${next_major_minor}.${next_patch}" if [ -n "$PREFIX" ]; then release_tag="${PREFIX}${release_tag}" fi # Validate final release tag validate_version "${release_tag#$PREFIX}" || { echo "::error::Invalid release tag format: $release_tag" exit 1 } echo "release_tag=${release_tag}" >> $GITHUB_OUTPUT # Create release if not in dry-run mode if [ "$DRY_RUN" = "false" ]; then echo "Creating release ${release_tag}..." release_url=$(gh release create "${release_tag}" \ --repo="${GITHUB_REPOSITORY}" \ --title="${release_tag}" \ --generate-notes 2>/dev/null) || { echo "::error::Failed to create release" exit 1 } echo "release_url=${release_url}" >> $GITHUB_OUTPUT echo "Release created successfully: ${release_url}" else echo "Dry run mode - would create release: ${release_tag}" echo "release_url=dry-run" >> $GITHUB_OUTPUT fi - name: Verify Release if: inputs.dry-run == 'false' shell: sh env: RELEASE_TAG: ${{ steps.create-release.outputs.release_tag }} run: |- set -eu # Use validated environment variables from GITHUB_ENV GITHUB_TOKEN="$VALIDATED_TOKEN" # Verify the release was created if ! gh release view "$RELEASE_TAG" >/dev/null 2>&1; then echo "::error::Failed to verify release creation" exit 1 fi echo "Release verification successful"