diff --git a/.github/scripts/compute-semver.sh b/.github/scripts/compute-semver.sh new file mode 100755 index 0000000..7368cd7 --- /dev/null +++ b/.github/scripts/compute-semver.sh @@ -0,0 +1,80 @@ +#!/usr/bin/env bash + +set -euo pipefail + +echo "Determining next SemVer..." + +if [ -z "${GITHUB_TOKEN:-}" ]; then + echo "GITHUB_TOKEN not set" + exit 1 +fi + +OWNER="${GITHUB_REPOSITORY%%/*}" +REPO="${GITHUB_REPOSITORY##*/}" +SHA="${GITHUB_SHA}" + +git fetch --tags +LATEST_TAG=$(git tag --list 'v*.*.*' --sort=-v:refname | head -n1 || true) + +if [ -z "$LATEST_TAG" ]; then + LATEST_TAG="v0.0.0" +fi + +echo "Latest tag: $LATEST_TAG" + +VERSION="${LATEST_TAG#v}" +IFS='.' read -r MAJOR MINOR PATCH <<< "$VERSION" + +PRS_JSON=$(curl -s \ + -H "Authorization: Bearer $GITHUB_TOKEN" \ + -H "Accept: application/vnd.github+json" \ + "https://api.github.com/repos/$OWNER/$REPO/commits/$SHA/pulls") + +echo "$PRS_JSON" | jq . + +PR_COUNT=$(echo "$PRS_JSON" | jq length) + +if [ "$PR_COUNT" -eq 0 ]; then + echo "No PR associated with this commit." + exit 1 +fi + +LABELS=$(echo "$PRS_JSON" | jq -r '.[0].labels[].name' | tr '[:upper:]' '[:lower:]') + +BUMP="patch" + +COUNT=0 +for LABEL in major minor patch; do + if echo "$LABELS" | grep -qx "$LABEL"; then + BUMP="$LABEL" + COUNT=$((COUNT+1)) + fi +done + +if [ "$COUNT" -gt 1 ]; then + echo "Multiple SemVer labels applied. Use only one of: major, minor, patch." + exit 1 +fi + +echo "Version bump type: $BUMP" + +case "$BUMP" in + major) + MAJOR=$((MAJOR+1)) + MINOR=0 + PATCH=0 + ;; + minor) + MINOR=$((MINOR+1)) + PATCH=0 + ;; + patch) + PATCH=$((PATCH+1)) + ;; +esac + +NEW_TAG="v${MAJOR}.${MINOR}.${PATCH}" + +echo "New tag: $NEW_TAG" + +echo "tag=$NEW_TAG" >> "$GITHUB_OUTPUT" diff --git a/.github/scripts/get-pr-details.sh b/.github/scripts/get-pr-details.sh new file mode 100644 index 0000000..ab7601d --- /dev/null +++ b/.github/scripts/get-pr-details.sh @@ -0,0 +1,47 @@ +#!/usr/bin/env bash + +set -euo pipefail + +echo "Retrieving PR information..." + +if [ -z "${GITHUB_TOKEN:-}" ]; then + echo "GITHUB_TOKEN not set" + exit 1 +fi + +OWNER="${GITHUB_REPOSITORY%%/*}" +REPO="${GITHUB_REPOSITORY##*/}" +SHA="${GITHUB_SHA}" + +PRS_JSON=$(curl -s \ + -H "Authorization: Bearer $GITHUB_TOKEN" \ + -H "Accept: application/vnd.github+json" \ + "https://api.github.com/repos/$OWNER/$REPO/commits/$SHA/pulls") + +PR_COUNT=$(echo "$PRS_JSON" | jq length) + +if [ "$PR_COUNT" -eq 0 ]; then + echo "No PR associated with this commit." + echo "title=" >> "$GITHUB_OUTPUT" + echo "body=" >> "$GITHUB_OUTPUT" + exit 0 +fi + +TITLE=$(echo "$PRS_JSON" | jq -r '.[0].title // ""') +BODY=$(echo "$PRS_JSON" | jq -r '.[0].body // ""') + +{ + echo "title<> "$GITHUB_OUTPUT" + +{ + echo "body<> "$GITHUB_OUTPUT" + +echo "PR details captured." +echo "- Title = $TITLE." +echo "- Description = $BODY." diff --git a/.github/workflows/build-ci.yaml b/.github/workflows/build-ci.yaml deleted file mode 100644 index 277b161..0000000 --- a/.github/workflows/build-ci.yaml +++ /dev/null @@ -1,134 +0,0 @@ -name: Build - -on: - pull_request: - types: [opened, synchronize, reopened] - push: - branches: - - main - -permissions: - contents: write - -jobs: - pr_build_and_test: - if: github.event_name == 'pull_request' - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Cache pip and PlatformIO - uses: actions/cache@v4 - with: - path: | - ~/.cache/pip - ~/.platformio/.cache - key: ${{ runner.os }}-pio - - - name: Setup Python - uses: actions/setup-python@v5 - with: - python-version: '3.11' - - - name: Inject credentials - run: python3 ${{ github.workspace }}/tools/setup_dev_env.py - - - name: Install platformio core libraries - run: pip install --upgrade platformio - - - name: Build LumenLab - run: pio run - - - name: Run tests - run: pio test --environment native - - release_on_main: - if: github.event_name == 'push' && github.ref == 'refs/heads/main' - runs-on: ubuntu-latest - needs: [] - steps: - - name: Checkout (full) - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Cache pip and PlatformIO - uses: actions/cache@v4 - with: - path: | - ~/.cache/pip - ~/.platformio/.cache - key: ${{ runner.os }}-pio - - - name: Setup Python - uses: actions/setup-python@v5 - with: - python-version: '3.11' - - - name: Inject credentials - run: python3 ${{ github.workspace }}/tools/setup_dev_env.py - - - name: Install platformio core libraries - run: pip install --upgrade platformio - - - name: Build release - run: pio run -e release - - - name: Run tests - run: pio test --environment native - - - name: Create and push tag for release - id: tag_release - run: | - git fetch --tags - latest_tag=$(git tag --list --sort=-v:refname | grep -E '^v?[0-9]+\.[0-9]+\.[0-9]+$' | head -n1 || true) - if [ -z "$latest_tag" ]; then - new_tag=v0.0.1 - else - t=${latest_tag#v} - IFS='.' read -r major minor patch <<< "$t" - patch=$((patch+1)) - new_tag="v${major}.${minor}.${patch}" - fi - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git tag -a "$new_tag" -m "Release $new_tag" - git push origin "$new_tag" - echo "tag=$new_tag" >> $GITHUB_OUTPUT - - - name: Create GitHub Release - id: create_release - uses: actions/create-release@v1 - with: - token: ${{ secrets.GITHUB_TOKEN }} - tag_name: ${{ steps.tag_release.outputs.tag }} - release_name: Release ${{ steps.tag_release.outputs.tag }} - draft: false - prerelease: false - - - name: Upload firmware.bin - uses: actions/upload-release-asset@v1 - with: - upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: .pio/build/release/firmware.bin - asset_name: firmware.bin - asset_content_type: application/octet-stream - - - name: Upload bootloader.bin - uses: actions/upload-release-asset@v1 - with: - upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: .pio/build/release/bootloader.bin - asset_name: bootloader.bin - asset_content_type: application/octet-stream - - - name: Upload partitions.bin - uses: actions/upload-release-asset@v1 - with: - upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: .pio/build/release/partitions.bin - asset_name: partitions.bin - asset_content_type: application/octet-stream \ No newline at end of file diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..9aed8c4 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,40 @@ +name: CI - Build and Run Test + +on: + pull_request: + types: [opened, synchronize, reopened] + +permissions: + contents: read + +jobs: + build_and_test: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Cache pip and PlatformIO + uses: actions/cache@v4 + with: + path: | + ~/.cache/pip + ~/.platformio/.cache + key: ${{ runner.os }}-pio-${{ hashFiles('**/platformio.ini') }} + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: Install PlatformIO + run: pip install --upgrade platformio + + - name: Build LumenLab + run: pio run + + - name: Run tests + run: pio test --environment native diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 0000000..480d78e --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,70 @@ +name: CD - Deploy, Label, and Create Release + +on: + push: + branches: + - main + +permissions: + contents: write + +concurrency: + group: release-main + cancel-in-progress: false + +jobs: + release: + runs-on: ubuntu-latest + + steps: + - name: Checkout (full history) + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Cache pip and PlatformIO + uses: actions/cache@v4 + with: + path: | + ~/.cache/pip + ~/.platformio/.cache + key: ${{ runner.os }}-pio-${{ hashFiles('**/platformio.ini') }} + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: Install PlatformIO + run: pip install --upgrade platformio + + - name: Build release + run: pio run -e release + + - name: Run tests + run: pio test --environment native + + - name: Determine next SemVer from PR labels + id: semver + run: bash .github/scripts/compute-semver.sh + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Get release description notes from PR + id: pr_body + run: bash .github/scripts/get-pr-body.sh + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Create GitHub Release + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ steps.semver.outputs.tag }} + name: Release ${{ steps.semver.outputs.tag }} + body: ${{ steps.pr_body.outputs.result }} + files: | + .pio/build/release/firmware.bin + .pio/build/release/bootloader.bin + .pio/build/release/partitions.bin + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 3fe5307..342cb4f 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -124,31 +124,6 @@ "focus": true } }, - // { - // "label": "LumenLab - Upload (Debug)", - // "type": "shell", - // "command": "platformio", - // "args": [ - // "run", - // "-e", - // "debug", - // "--target", - // "upload" - // ], - // "problemMatcher": [ - // "$platformio" - // ], - // "options": { - // "statusbar": { - // "hide": true - // } - // }, - // "presentation": { - // "panel": "shared", - // "clear": false, - // "reveal": "always" - // } - // }, { "label": "LumenLab - Upload (release)", "type": "shell",