Repository navigation
Publish Release #1460
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Publish Release | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| version: | |
| description: The version to release | |
| required: true | |
| permissions: | |
| contents: read # actions/checkout only; commits + release writes use the App token | |
| jobs: | |
| create-release: | |
| name: Create release | |
| runs-on: ubuntu-latest | |
| continue-on-error: true | |
| steps: | |
| - name: Generate a token | |
| id: generate-token | |
| uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 | |
| with: | |
| client-id: ${{ vars.ESPHOME_GITHUB_APP_CLIENT_ID }} | |
| private-key: ${{ secrets.ESPHOME_GITHUB_APP_PRIVATE_KEY }} | |
| # contents: write covers both GraphQL createCommitOnBranch and | |
| # ``gh release create`` (which creates the release + lightweight tag). | |
| permission-contents: write | |
| - name: Checkout | |
| uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| - name: Set up Python | |
| uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 | |
| with: | |
| python-version: "3.11" | |
| - name: Install Python dependencies | |
| run: pip install -r script/requirements.txt | |
| - name: Bump version | |
| run: script/bump-version.py ${{ github.event.inputs.version }} | |
| - name: Extract major version | |
| id: extract_version | |
| run: | | |
| # Extract major version (e.g., 2025.6.0 from 2025.6.2 or 2025.6.0b3) | |
| MAJOR_VERSION=$(echo "${{ github.event.inputs.version }}" | sed -E 's/^([0-9]+\.[0-9]+)\.[0-9]+.*/\1.0/') | |
| echo "major_version=${MAJOR_VERSION}" >> $GITHUB_OUTPUT | |
| - name: Fetch release body | |
| # Stored in $RUNNER_TEMP so ``git status --porcelain`` in the commit | |
| # step doesn't pick it up. Consumed by the changelog writes and the | |
| # ``Create a Release`` step. | |
| if: ${{ !contains(github.event.inputs.version, 'dev') }} | |
| env: | |
| GH_TOKEN: ${{ steps.generate-token.outputs.token }} | |
| VERSION: ${{ github.event.inputs.version }} | |
| run: | | |
| gh release view "$VERSION" \ | |
| --repo esphome/esphome \ | |
| --json body \ | |
| --jq .body \ | |
| > "$RUNNER_TEMP/release_body.md" | |
| - name: Write Beta changelog | |
| if: ${{ !contains(github.event.inputs.version, 'dev') }} | |
| env: | |
| VERSION: ${{ github.event.inputs.version }} | |
| MAJOR: ${{ steps.extract_version.outputs.major_version }} | |
| run: | | |
| { | |
| printf '## %s\n\n[**Read release announcement**](https://beta.esphome.io/changelog/%s)\n\n' "$VERSION" "$MAJOR" | |
| cat "$RUNNER_TEMP/release_body.md" | |
| } > esphome-beta/CHANGELOG.md | |
| - name: Write Stable changelog | |
| if: ${{ !contains(github.event.inputs.version, 'b') && !contains(github.event.inputs.version, 'dev') }} | |
| env: | |
| VERSION: ${{ github.event.inputs.version }} | |
| MAJOR: ${{ steps.extract_version.outputs.major_version }} | |
| run: | | |
| { | |
| printf '## %s\n\n[**Read release announcement**](https://esphome.io/changelog/%s)\n\n' "$VERSION" "$MAJOR" | |
| cat "$RUNNER_TEMP/release_body.md" | |
| } > esphome/CHANGELOG.md | |
| - name: Commit version bump | |
| id: commit | |
| uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 | |
| env: | |
| VERSION: ${{ github.event.inputs.version }} | |
| with: | |
| github-token: ${{ steps.generate-token.outputs.token }} | |
| script: | | |
| const fs = require('fs'); | |
| const { execSync } = require('child_process'); | |
| // bump-version.py + the changelog writes only touch files with | |
| // simple ASCII names, so the default porcelain format (no -z) | |
| // is sufficient. | |
| const lines = execSync('git status --porcelain', { encoding: 'utf8' }) | |
| .split('\n') | |
| .filter(line => line.length > 0); | |
| const additions = []; | |
| const deletions = []; | |
| for (const line of lines) { | |
| const status = line.substring(0, 2); | |
| const path = line.substring(3); | |
| if (status.includes('D')) { | |
| deletions.push({ path }); | |
| } else { | |
| additions.push({ | |
| path, | |
| contents: fs.readFileSync(path).toString('base64'), | |
| }); | |
| } | |
| } | |
| if (additions.length === 0 && deletions.length === 0) { | |
| core.setFailed('No file changes to commit'); | |
| return; | |
| } | |
| const headOid = execSync('git rev-parse HEAD', { encoding: 'utf8' }).trim(); | |
| const repo = context.payload.repository; | |
| const result = await github.graphql( | |
| `mutation($input: CreateCommitOnBranchInput!) { | |
| createCommitOnBranch(input: $input) { | |
| commit { oid } | |
| } | |
| }`, | |
| { | |
| input: { | |
| branch: { | |
| repositoryNameWithOwner: repo.full_name, | |
| branchName: repo.default_branch, | |
| }, | |
| message: { headline: `Bump version to ${process.env.VERSION}` }, | |
| fileChanges: { additions, deletions }, | |
| expectedHeadOid: headOid, | |
| }, | |
| } | |
| ); | |
| core.setOutput('sha', result.createCommitOnBranch.commit.oid); | |
| - name: Create a Release | |
| continue-on-error: true | |
| if: ${{ !contains(github.event.inputs.version, 'dev') }} | |
| env: | |
| GH_TOKEN: ${{ steps.generate-token.outputs.token }} | |
| VERSION: ${{ github.event.inputs.version }} | |
| TARGET_SHA: ${{ steps.commit.outputs.sha }} | |
| run: | | |
| flags=() | |
| if [[ "$VERSION" == *b* ]]; then | |
| flags+=(--prerelease) | |
| fi | |
| gh release create "$VERSION" \ | |
| --title "$VERSION" \ | |
| --notes-file "$RUNNER_TEMP/release_body.md" \ | |
| --target "$TARGET_SHA" \ | |
| "${flags[@]}" |