diff --git a/.github/workflows/release-asset.yml b/.github/workflows/release-asset.yml deleted file mode 100644 index 9aa53f6..0000000 --- a/.github/workflows/release-asset.yml +++ /dev/null @@ -1,67 +0,0 @@ -name: Release asset - -on: - release: - types: [published] - workflow_dispatch: - inputs: - tag: - description: 'Existing release tag to (re)build and upload an asset for.' - required: true - -permissions: - contents: write - -jobs: - build-release-asset: - runs-on: ubuntu-latest - steps: - - name: Resolve release tag - id: tag - run: | - if [ -n "${{ github.event.release.tag_name }}" ]; then - echo "tag=${{ github.event.release.tag_name }}" >> "$GITHUB_OUTPUT" - else - echo "tag=${{ github.event.inputs.tag }}" >> "$GITHUB_OUTPUT" - fi - - - uses: actions/checkout@v4 - with: - ref: ${{ steps.tag.outputs.tag }} - - - uses: shivammathur/setup-php@v2 - with: - php-version: '8.3' - tools: composer:v2 - - - run: composer install --no-dev --optimize-autoloader --prefer-dist --no-interaction - - - name: Build plugin ZIP - run: | - mkdir -p dist/data-machine-code - rsync -a ./ dist/data-machine-code/ \ - --exclude='.git' \ - --exclude='.github' \ - --exclude='.claude' \ - --exclude='.datamachine' \ - --exclude='AGENTS.md' \ - --exclude='dist' \ - --exclude='docs' \ - --exclude='node_modules' \ - --exclude='tests' \ - --exclude='*.zip' \ - --exclude='*.tar.gz' \ - --exclude='*.log' \ - --exclude='*.tmp' \ - --exclude='*.temp' \ - --exclude='*.cache' \ - --exclude='*.bak' \ - --exclude='*.backup' \ - --exclude='phpstan-baseline.neon' \ - --exclude='phpunit.xml*' - cd dist - zip -r data-machine-code.zip data-machine-code - - - run: gh release upload "${{ steps.tag.outputs.tag }}" dist/data-machine-code.zip --clobber - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..a077e7f --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,146 @@ +# Continuous release pipeline for Data Machine Code. +# +# Triggers on push to main (immediate release) and manual dispatch. +# Checks for releasable conventional commits since the last tag. If found: +# 1. Version bump + changelog generation +# 2. Tag + push + GitHub Release creation +# 3. WordPress release.package builds the vendor-bundled ZIP +# 4. WordPress release.publish uploads the ZIP to the Release and +# mirrors the tree to the release-latest branch (Playground CORS path) +# +# No human input needed — version is computed from commit types: +# fix: → patch, feat: → minor, BREAKING CHANGE → major +# chore:/ci:/docs:/test: → no release + +name: Release + +permissions: + contents: write + issues: write + pull-requests: write + +on: + push: + branches: [main] + workflow_dispatch: + inputs: + dry-run: + description: 'Preview the release without making changes' + type: boolean + default: false + +jobs: + # ── Step 1: Check for releasable commits ── + check: + name: Check for releasable commits + runs-on: ubuntu-latest + outputs: + should-release: ${{ steps.check.outputs.should-release }} + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Restore failure cache + id: failure-cache + uses: actions/cache/restore@v5 + with: + path: ${{ runner.temp }}/homeboy-release-last-failed + key: release-last-failed-${{ github.ref_name }}-${{ github.sha }} + restore-keys: | + release-last-failed-${{ github.ref_name }}- + + - name: Dry-run release check + id: release-check + uses: Extra-Chill/homeboy-action@v2 + with: + commands: release + release-dry-run: 'true' + + - name: Decide whether to release + id: check + run: | + HEAD_SHA="$(git rev-parse HEAD)" + FAILURE_MARKER="${RUNNER_TEMP}/homeboy-release-last-failed" + if [ -f "${FAILURE_MARKER}" ]; then + LAST_FAILED="$(tr -d '[:space:]' < "${FAILURE_MARKER}")" + if [ "${HEAD_SHA}" = "${LAST_FAILED}" ]; then + echo "::notice::HEAD ${HEAD_SHA:0:8} matches last failed release attempt — skipping until new commits" + echo "should-release=false" >> "$GITHUB_OUTPUT" + exit 0 + fi + fi + + RELEASE_VERSION="${{ steps.release-check.outputs.release-version }}" + BUMP_TYPE="${{ steps.release-check.outputs.release-bump-type }}" + + if [ -z "${RELEASE_VERSION}" ]; then + echo "should-release=false" >> "$GITHUB_OUTPUT" + echo "::notice::No releasable commits at HEAD ${HEAD_SHA:0:8}" + else + echo "should-release=true" >> "$GITHUB_OUTPUT" + echo "::notice::Release dry-run predicts v${RELEASE_VERSION} (${BUMP_TYPE})" + fi + + # ── Step 2: Release ── + release: + name: Release + needs: check + if: needs.check.outputs.should-release == 'true' + runs-on: ubuntu-latest + steps: + - name: Generate GitHub App token + id: app-token + uses: actions/create-github-app-token@v3 + continue-on-error: true + with: + app-id: ${{ secrets.HOMEBOY_APP_ID }} + private-key: ${{ secrets.HOMEBOY_APP_PRIVATE_KEY }} + + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + persist-credentials: false + token: ${{ steps.app-token.outputs.token || secrets.GITHUB_TOKEN }} + + - uses: Extra-Chill/homeboy-action@v2 + id: release + with: + commands: release + release-dry-run: ${{ inputs.dry-run || 'false' }} + app-token: ${{ steps.app-token.outputs.token || '' }} + + # ── Record failed SHA to prevent retry loops ── + record-failure: + name: Record failure + needs: + - check + - release + if: ${{ always() && needs.check.outputs.should-release == 'true' && needs.release.result == 'failure' }} + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + - name: Save failed SHA + run: git rev-parse HEAD > "${RUNNER_TEMP}/homeboy-release-last-failed" + + - name: Cache failed SHA + uses: actions/cache/save@v5 + with: + path: ${{ runner.temp }}/homeboy-release-last-failed + key: release-last-failed-${{ github.ref_name }}-${{ github.sha }} + + # ── Clear failure cache on success ── + clear-failure: + name: Clear failure cache + needs: release + if: ${{ needs.release.result == 'success' }} + runs-on: ubuntu-latest + steps: + - name: Clear failure cache + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh cache list --json id,key --jq '.[] | select(.key | startswith("release-last-failed-${{ github.ref_name }}-")) | .id' | while read -r id; do + gh cache delete "$id" 2>/dev/null || true + done diff --git a/homeboy.json b/homeboy.json index de1242a..113d735 100644 --- a/homeboy.json +++ b/homeboy.json @@ -2,7 +2,9 @@ "auto_cleanup": false, "changelog_target": "docs/CHANGELOG.md", "extensions": { - "wordpress": {} + "wordpress": { + "release_latest_branch": "release-latest" + } }, "id": "data-machine-code", "remote_path": "wp-content/plugins/data-machine-code",