diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..219c13a --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,6 @@ +version: 2 +updates: + - package-ecosystem: "cargo" + directory: "/" + schedule: + interval: "daily" diff --git a/.github/workflows/create_release.yaml b/.github/workflows/create_release.yaml new file mode 100644 index 0000000..73e9680 --- /dev/null +++ b/.github/workflows/create_release.yaml @@ -0,0 +1,112 @@ +name: Create Release + +on: + workflow_dispatch: + inputs: + bumpVersion: + description: "Bump version" + required: true + default: patch + type: choice + options: + - patch + - minor + - major + +env: + CARGO_TERM_COLOR: always + RUST_VERSION: 1.66.0 + +jobs: + bump_version: + runs-on: ubuntu-latest + outputs: + new_version: ${{ steps.bv.outputs.new_version }} + steps: + - uses: actions/checkout@v3 + - name: Prepare branch + run: | + if [[ ! `git branch --show-current` = 'master' ]] ; then + echo "Releases can only be created from the \`master\` branch" >&2 + exit 1 + fi + git fetch --tags + git checkout -b feature + - name: Bump version + id: bv + run: | + new_version=`scripts/bumb_version.py ${{ inputs.bumpVersion }}` + echo "new_version=$new_version" >> $GITHUB_OUTPUT + - name: Update lock file + uses: actions-rs/cargo@v1 + with: + command: update + args: --package hoc + - name: Create release branch + id: crb + run: | + release_branch="release/v${NEW_VERSION}" + git checkout -b "$release_branch" + git \ + -c author.name=${{ github.actor }} \ + -c author.email=${{ github.actor }}@users.noreply.github.com \ + -c committer.name=Github \ + -c committer.email=noreply@github.com \ + commit -a -m "Prepare release v${NEW_VERSION}" + git push --set-upstream origin "$release_branch" + echo "release_branch=$release_branch" >> $GITHUB_OUTPUT + env: + NEW_VERSION: ${{ steps.bv.outputs.new_version }} + - name: Create pull request + id: cpr + uses: octokit/request-action@v2.x + with: + route: POST /repos/${{ github.repository }}/pulls + title: Release v${{ env.NEW_VERSION }} + body: Bump to version v${{ env.NEW_VERSION }}. + base: master + head: ${{ env.RELEASE_BRANCH }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + RELEASE_BRANCH: ${{ steps.crb.outputs.release_branch }} + NEW_VERSION: ${{ steps.bv.outputs.new_version }} + - name: Merge pull request + uses: octokit/request-action@v2.x + with: + route: PUT /repos/${{ github.repository }}/pulls/${{ env.PULL_NUMBER }}/merge + commit_title: Prepare release v${{ env.NEW_VERSION }} (#${{ env.PULL_NUMBER }}) + merge_method: squash + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PULL_NUMBER: ${{ fromJson(steps.cpr.outputs.data).number }} + NEW_VERSION: ${{ steps.bv.outputs.new_version }} + + create_release: + needs: [bump_version] + env: + NEW_VERSION: ${{ needs.bump_version.outputs.new_version }} + ARTIFACT_NAME: ${{ needs.build.outputs.artifact_name }} + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + ref: master + - name: Get changelog + id: gcb + run: | + changelog_body=`scripts/get_changelog.py` + echo "changelog_body=${changelog_body}" >> $GITHUB_OUTPUT + - name: Publish release + id: crd + uses: octokit/request-action@v2.x + with: + route: POST /repos/${{ github.repository }}/releases + tag_name: v${{ env.NEW_VERSION }} + target_commitish: master + name: v${{ env.NEW_VERSION }} + body: |- + ${{ steps.gcb.outputs.changelog_body }} + draft: false + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml new file mode 100644 index 0000000..edc70c3 --- /dev/null +++ b/.github/workflows/test.yaml @@ -0,0 +1,47 @@ +name: Test + +on: + push: + branches: [ master ] + paths-ignore: + - '**.md' + - 'scripts/**' + - '.gitignore' + - '.github/**' + - '!.github/workflows/test.yml' + pull_request: + branches: [ master ] + paths-ignore: + - '**.md' + - 'scripts/**' + - '.gitignore' + - '.github/**' + - '!.github/workflows/test.yml' + +env: + CARGO_TERM_COLOR: always + RUST_VERSION: 1.66.0 + +jobs: + test: + runs-on: macos-latest + steps: + - uses: actions/checkout@v3 + - name: Install Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: ${{ env.RUST_VERSION }} + default: true + components: clippy + - name: Cache Rust + uses: Swatinem/rust-cache@v2 + - name: Lint + uses: actions-rs/cargo@v1 + with: + command: clippy + args: --verbose --release -- -D warnings + - name: Run tests + uses: actions-rs/cargo@v1 + with: + command: test + args: --verbose --release diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..6738c8a --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,12 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project +adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +### Added + +- Initial release of game-box. diff --git a/Cargo.toml b/Cargo.toml index 0f9c998..d529219 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,4 +1,4 @@ [package] name = "game-box" -version = "0.0.1" +version = "0.0.0" edition = "2021" diff --git a/scripts/__pycache__/get_version.cpython-310.pyc b/scripts/__pycache__/get_version.cpython-310.pyc new file mode 100644 index 0000000..cae0a2d Binary files /dev/null and b/scripts/__pycache__/get_version.cpython-310.pyc differ diff --git a/scripts/__pycache__/util.cpython-310.pyc b/scripts/__pycache__/util.cpython-310.pyc new file mode 100644 index 0000000..40d885a Binary files /dev/null and b/scripts/__pycache__/util.cpython-310.pyc differ diff --git a/scripts/bumb_version.py b/scripts/bumb_version.py new file mode 100755 index 0000000..fa33933 --- /dev/null +++ b/scripts/bumb_version.py @@ -0,0 +1,107 @@ +#!/usr/bin/env python3 + +from get_version import get_version as get_current_version +from util import error +import datetime +import os +import subprocess +import sys + + +SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__)) +REPO_DIR = os.path.realpath(os.path.join(SCRIPT_DIR, "..")) + + +def run(*cmd): + output = subprocess.run(cmd, capture_output=True) + return output.stdout.decode("utf-8").strip(), output.stderr.decode("utf-8").strip() + + +def get_next_version(bump_comp_idx, current_version): + components = current_version.split(".") + + all_digits = lambda c: all(map(str.isdigit, c)) + is_empty = lambda c: len(c) == 0 + is_int = lambda c: not is_empty(c) and all_digits(c) + + has_three_components = len(components) == 3 + all_components_are_ints = all(map(is_int, components)) + + if not (has_three_components and all_components_are_ints): + error(f'"{version}" version number invalid') + + components[bump_comp_idx] = int(components[bump_comp_idx]) + 1 + [major, minor, patch] = [*components[:bump_comp_idx+1], 0, 0, 0][:3] + + return f"{major}.{minor}.{patch}" + + +def update_manifest(bump_comp_idx): + manifest_path = os.path.join(REPO_DIR, "Cargo.toml") + with open(manifest_path, "r") as f: + content = f.read() + + current_version = get_current_version(content) + next_version = get_next_version(bump_comp_idx, current_version) + content = content.replace( + f'version = "{current_version}"', + f'version = "{next_version}"') + + with open(manifest_path, "w") as f: + f.write(content) + + return next_version + + +def update_changelog(next_version): + HEADER_KEY = "## [Unreleased]" + CHANGELOG_NAME = "CHANGELOG.md" + + changelog_path = os.path.join(REPO_DIR, CHANGELOG_NAME) + last_version, _ = run("git", "-C", REPO_DIR, "rev-list", "--date-order", "--tags", "--max-count=1") + stdout, stderr = run("git", "-C", REPO_DIR, "diff", last_version, "HEAD", "--", CHANGELOG_NAME) + + if len(stderr) > 0: + error(stderr.strip()) + if len(stdout) == 0: + error("no changes have been made to the changelog") + + with open(changelog_path, "r") as f: + content = f.read() + + current_date = datetime.datetime.now(datetime.timezone.utc) + formatted_date = current_date.strftime("%Y-%m-%d") + replacement = f"## [{next_version}] - {formatted_date}" + new_content = content.replace(HEADER_KEY, replacement, 1) + + if content == new_content: + error("no unreleased version section found") + if new_content != new_content.replace(HEADER_KEY, replacement): + error("multiple unreleased version sections found") + + new_content = new_content.replace(replacement, f"{HEADER_KEY}\n\n{replacement}") + with open(changelog_path, "w") as f: + f.write(new_content) + + +def bump_version(bump_comp_idx): + next_version = update_manifest(bump_comp_idx) + update_changelog(next_version) + return next_version + + +if __name__ == "__main__": + if len(sys.argv) < 2: + error("component argument missing") + + component = sys.argv[1] + if component == "major": + bump_comp_idx = 0 + elif component == "minor": + bump_comp_idx = 1 + elif component == "patch": + bump_comp_idx = 2 + else: + error(f'"major", "minor" or "patch" expected') + + print(bump_version(bump_comp_idx)) diff --git a/scripts/get_changelog.py b/scripts/get_changelog.py new file mode 100755 index 0000000..127f40e --- /dev/null +++ b/scripts/get_changelog.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python3 + +from get_version import get_version +from util import error +import json +import os +import sys + + +SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__)) +REPO_DIR = os.path.realpath(os.path.join(SCRIPT_DIR, "..")) + + +def get_changelog_body(): + manifest_path = os.path.join(REPO_DIR, "CHANGELOG.md") + with open(manifest_path, "r") as f: + content = f.read() + + version = get_version() + version_line = f"## [{version}]" + version_split= content.split(version_line) + + if len(version_split) < 2: + error("version changelog section found") + if len(version_split) > 2: + error("multiple version changelog sections found") + + title_suffix_with_body_split = version_split[1].split("## [", 1) + body_split = title_suffix_with_body_split[0].split("\n", 1) + + if len(body_split) == 1: + error("invalid changelog format") + + stripped_body = body_split[1].strip() + return json.dumps(stripped_body) + + +if __name__ == "__main__": + print(get_changelog_body()) diff --git a/scripts/get_version.py b/scripts/get_version.py new file mode 100755 index 0000000..508fe21 --- /dev/null +++ b/scripts/get_version.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python3 + +import os +from util import error + + +SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__)) +REPO_DIR = os.path.realpath(os.path.join(SCRIPT_DIR, "..")) + + +def split(content, sep, desc=None): + parts = content.split(sep, 1) + if len(parts) == 1: + if desc is not None: + error(desc) + else: + error(f'"{sep}" separator not found') + return parts + + +def get_version(content = None): + HEADER_KEY = "[package]" + VERSION_KEY = "version" + EQUALS = "=" + NEW_LINE = "\n" + + if content is None: + manifest_path = os.path.join(REPO_DIR, "Cargo.toml") + with open(manifest_path, "r") as f: + content = f.read() + + [parsed, content] = split(content, HEADER_KEY) + parsed += HEADER_KEY + while len(content) > 0: + [line, content] = split(content, NEW_LINE) + + if len(line.strip()) == 0: + parsed += line + NEW_LINE + continue + + [key, line] = split(line, EQUALS, f'"{VERSION_KEY}" key not found') + parsed += key + EQUALS + + if key.strip() != VERSION_KEY: + parsed += line + NEW_LINE + continue + + return line.strip().strip('"') + + +if __name__ == "__main__": + print(get_version()) diff --git a/scripts/util.py b/scripts/util.py new file mode 100644 index 0000000..21bbb1c --- /dev/null +++ b/scripts/util.py @@ -0,0 +1,10 @@ +import sys + + +def eprint(*args, **kwargs): + print(*args, file=sys.stderr, **kwargs) + + +def error(msg): + eprint("error:", msg) + sys.exit(1)