diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..943183b --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,174 @@ +name: "Release" +# concurrency: Ensures that for one branch the workflow is not running multiple +# times at the same time as we will get trouble with the versions and pushes. +concurrency: ci-${{ github.ref }} + +on: + workflow_dispatch: + inputs: + release_type: + description: | + Release Type: "minor" is typical for license update release. More details in README. + required: true + default: "minor" + type: choice + options: + - major + - minor + - patch + +run-name: | + ${{ github.workflow }} (${{ github.event.inputs.release_type }}) + +permissions: + contents: write + pull-requests: write + +jobs: + release-prepare: + runs-on: ubuntu-latest + + outputs: + prev-version: ${{ steps.version.outputs.prev_version }} + next-version: ${{ steps.version.outputs.next_version }} + pull-request-branch: ${{ steps.create-pull-request.outputs.pull-request-branch }} + pull-request-number: ${{ steps.create-pull-request.outputs.pull-request-number }} + + steps: + - name: Checkout repository + id: checkout-prepare + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Update Version + id: version + run: | + echo "prev_version=$(cat .version)" >> $GITHUB_OUTPUT + bin/version.sh ${{ github.event.inputs.release_type }} + echo "next_version=$(cat .version)" >> $GITHUB_OUTPUT + - name: Create pull request + id: create-pull-request + # https://github.com/peter-evans/create-pull-request + uses: peter-evans/create-pull-request@v7 + with: + token: ${{ secrets.GITHUB_TOKEN }} + commit-message: "ci(release): Update version to ${{ steps.version.outputs.next_version }}" + branch: release/${{ steps.version.outputs.next_version }} + title: "Release ${{ steps.version.outputs.next_version }}" + body: "This pull request updates the version to ${{ steps.version.outputs.next_version }}." + base: main + release-await: + runs-on: ubuntu-latest + needs: release-prepare + steps: + - name: Wait for pull request to be merged + # https://github.com/actions/github-script + uses: actions/github-script@v7 + with: + script: | + const pr = await github.pulls.get({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: ${{ needs.release-prepare.outputs.pull-request-number }} + }); + while (pr.data.merged_at === null) { + await new Promise(resolve => setTimeout(resolve, 10000)); + pr = await github.pulls.get({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: ${{ needs.release-prepare.outputs.pull-request-number }} + }); + } + release-tag: + runs-on: ubuntu-latest + needs: + - release-prepare + - release-await + outputs: + tag-name: ${{ steps.tag-release.outputs.tag_name }} + steps: + - name: Checkout repository + id: checkout-tag + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Set Git user + run: | + git config --global user.name "${{ github.actor }}" + git config --global user.email "${{ github.actor }}@users.noreply.github.com" + - name: Tag Release + id: tag-release + run: | + next_version="${{ needs.release-prepare.outputs.next-version }}" + tag_name="v${next_version}" + git tag -a -m "Release ${next_version}" "${tag_name}" + git push origin "${tag_name}" + echo "tag_name=${tag_name}" >> $GITHUB_OUTPUT + release-artifacts: + # The Pull Request is merged. Now we can perform the release. + runs-on: ubuntu-latest + needs: + - release-prepare + - release-tag + outputs: + release-artifacts-url: ${{ steps.upload-artifacts.outputs.artifact-url }} + steps: + - name: Checkout repository + id: checkout-perform + uses: actions/checkout@v4 + with: + fetch-depth: 0 + ref: ${{ needs.release-tag.outputs.tag-name }} + - name: Upload Release Artifacts + id: upload-artifacts + uses: actions/upload-artifact@v4 + with: + name: lib-bash-${{ needs.release-prepare.outputs.next-version }} + include-hidden-files: true + path: | + .version + README.md + UNLICENSE + lib_*.sh + release-complete: + runs-on: ubuntu-latest + needs: + - release-tag + - release-artifacts + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + outputs: + release-url: ${{ steps.create-release.outputs.url }} + steps: + - name: Create Release + id: create-release + # https://github.com/softprops/action-gh-release + uses: softprops/action-gh-release@v2 + with: + files: | + ${{ needs.release-artifacts.outputs.release-artifacts-url }} + tag_name: ${{ needs.release-tag.outputs.tag-name }} + name: ${{ needs.release-tag.outputs.tag-name }} + body: "Release ${{ needs.release-tag.outputs.tag-name }}" + draft: false + prerelease: false + release-summary: + # Output Markdown Report to $GITHUB_STEP_SUMMARY + runs-on: ubuntu-latest + needs: + - release-prepare + - release-tag + - release-complete + steps: + - name: summary + id: summary + run: | + cat <> $GITHUB_STEP_SUMMARY + # ${{ github.workflow }} (${{ github.event.inputs.release_type }}) + + * **Version** + * Previous Version: ${{ needs.release-prepare.outputs.prev-version }} + * Next Version: ${{ needs.release-prepare.outputs.next-version }} + * [Release ${{ needs.release-tag.outputs.tag-name }}]( + ${{ needs.release-complete.outputs.release-url }} + ) diff --git a/.gitignore b/.gitignore index 455f534..43e9878 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,11 @@ /tmp/ +## Build Artifacts + +/build/ +/dist/ + ## Logs & Output *.log diff --git a/.version b/.version new file mode 100644 index 0000000..8acdd82 --- /dev/null +++ b/.version @@ -0,0 +1 @@ +0.0.1 diff --git a/bin/build.sh b/bin/build.sh new file mode 100755 index 0000000..1873d5f --- /dev/null +++ b/bin/build.sh @@ -0,0 +1,122 @@ +#!/usr/bin/env bash +### +### Script to build this project. +### +### Creates a ZIP and TAR.GZ archive of the project, that includes the +### relevant artifacts for local usage (if not using as Git submodule or +### subtree). +### +### The archive will contain: +### +### - .version +### - README.md +### - UNLICENSE (thus, the license) +### - lib_*.sh files +### +### within a directory named `lib-bash` within that archive. +### +### The archived will be named `lib-bash-.zip` and +### `lib-bash-.tar.gz`. +### +### Directories: +### +### - build/ - working directory for the build process +### - dist/ - final location of the archives +### + +set -o errexit # abort on nonzero exit status +set -o nounset # abort on unbound variable +set -o pipefail # don't hide errors within pipes + +MY_PATH="$(realpath "${BASH_SOURCE[0]}")" +readonly MY_PATH +SCRIPT_DIR="$(dirname "${MY_PATH}")" +readonly SCRIPT_DIR + +WORKSPACE_ROOT="$(realpath "${SCRIPT_DIR}/..")" +readonly WORKSPACE_ROOT +declare -r VERSION_FILE=".version" +declare -r README_FILE="README.md" +declare -r LICENSE_FILE="UNLICENSE" +declare -r BUILD_DIR="build" +declare -r DIST_DIR="dist" + +function main() { + local result_format="${1:-}" + local version="" + local zip_file="" + local tar_file="" + + # Validate dependencies + if ! command -v zip &>/dev/null; then + echo "Error: 'zip' is required. Please install it first." + exit 1 + fi + + if ! command -v tar &>/dev/null; then + echo "Error: 'tar' is required. Please install it first." + exit 1 + fi + + cd "${WORKSPACE_ROOT}" + + # Fail if version file is missing + if [[ ! -f "${VERSION_FILE}" ]]; then + echo "Error: Missing version file '${VERSION_FILE}'." + exit 1 + fi + + # Read the version from the file + version=$(<"${VERSION_FILE}") + + zip_file="${DIST_DIR}/lib-bash-${version}.zip" + tar_file="${DIST_DIR}/lib-bash-${version}.tar.gz" + + # Validate the README file + if [[ ! -f "${README_FILE}" ]]; then + echo "Error: Missing README file '${README_FILE}'." + exit 1 + fi + + # Validate the LICENSE file + if [[ ! -f "${LICENSE_FILE}" ]]; then + echo "Error: Missing LICENSE file '${LICENSE_FILE}'." + exit 1 + fi + + # Create the build directory + rm -rf "${BUILD_DIR}" + mkdir -p "${BUILD_DIR}/lib-bash" + + # Copy the files to the build directory + cp "${VERSION_FILE}" "${BUILD_DIR}/lib-bash" + cp "${README_FILE}" "${BUILD_DIR}/lib-bash" + cp "${LICENSE_FILE}" "${BUILD_DIR}/lib-bash" + + # Copy the library files to the build directory + cp "lib_"*.sh "${BUILD_DIR}/lib-bash" + + rm -rf "${DIST_DIR}" + mkdir -p "${DIST_DIR}" + + # Create the ZIP archive + rm -f "${zip_file}" + (cd "${BUILD_DIR}" && zip -r9 "../${zip_file}" lib-bash &>/dev/null) + + # Create the TAR.GZ archive + rm -f "${tar_file}" + tar -czf "${tar_file}" -C "${BUILD_DIR}" lib-bash + + if [[ "${result_format}" == "json" ]]; then + echo "{" + echo " \"zip\": \"${zip_file}\"," + echo " \"tar\": \"${tar_file}\"" + echo "}" + else + echo "Build successful." + echo "ZIP archive: ${zip_file}" + echo "TAR.GZ archive: ${tar_file}" + fi +} + +main "${@}" diff --git a/bin/version.sh b/bin/version.sh new file mode 100755 index 0000000..c4c26ed --- /dev/null +++ b/bin/version.sh @@ -0,0 +1,71 @@ +#!/usr/bin/env bash +### +### Script read and update the version of the project. +### +### Usage: +### bin/version.sh +### bin/version.sh +### bin/version.sh major +### bin/version.sh minor +### bin/version.sh patch +### + +set -o errexit # abort on nonzero exit status +set -o nounset # abort on unbound variable +set -o pipefail # don't hide errors within pipes + +MY_PATH="$(realpath "${BASH_SOURCE[0]}")" +readonly MY_PATH +SCRIPT_DIR="$(dirname "${MY_PATH}")" +readonly SCRIPT_DIR + +readonly VERSION_FILE="${SCRIPT_DIR}/../.version" + +function main() { + local version_argument="${1:-}" + local version="" + local major=0 + local minor=0 + local patch=0 + + # Read the version from the file + if [[ -f "${VERSION_FILE}" ]]; then + version=$(<"${VERSION_FILE}") + major=$(echo "${version}" | cut -d. -f1) + minor=$(echo "${version}" | cut -d. -f2) + patch=$(echo "${version}" | cut -d. -f3) + fi + + # Increment the version number + case "${version_argument}" in + major) + major=$((major + 1)) + minor=0 + patch=0 + ;; + minor) + minor=$((minor + 1)) + patch=0 + ;; + patch) + patch=$((patch + 1)) + ;; + "") + # No arguments provided, keep the version number as is + ;; + *) + # Set the version number + major=$(echo "${1}" | cut -d. -f1) + minor=$(echo "${1}" | cut -d. -f2) + patch=$(echo "${1}" | cut -d. -f3) + ;; + esac + + if [[ -z "${version_argument}" ]]; then + echo "Current version: ${version}" + else + echo "New version: ${major}.${minor}.${patch}" + fi +} + +main "${@}"