Prepare Release #6
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: Prepare Release | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| module: | |
| description: 'Module to release (must match a key in .github/modules.json)' | |
| required: true | |
| type: string | |
| version: | |
| description: 'Release version (e.g., 1.0.0 or 0.2.0-beta.1)' | |
| required: true | |
| type: string | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| concurrency: | |
| group: prepare-release-${{ inputs.module }} | |
| cancel-in-progress: false | |
| jobs: | |
| prepare: | |
| name: "Prepare ${{ inputs.module }} ${{ inputs.version }}" | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| fetch-depth: 0 | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Validate inputs | |
| env: | |
| MODULE: ${{ inputs.module }} | |
| VERSION: ${{ inputs.version }} | |
| run: | | |
| if ! [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?$ ]]; then | |
| echo "::error::Invalid version format: $VERSION" | |
| exit 1 | |
| fi | |
| jq -e --arg m "$MODULE" '.[$m]' .github/modules.json > /dev/null || { | |
| echo "::error::Unknown module '$MODULE'. Valid modules: $(jq -r 'keys | join(", ")' .github/modules.json)" | |
| exit 1 | |
| } | |
| - name: Resolve module config | |
| id: config | |
| env: | |
| MODULE: ${{ inputs.module }} | |
| VERSION: ${{ inputs.version }} | |
| run: | | |
| MODULE_CONFIG=$(jq -e --arg m "$MODULE" '.[$m]' .github/modules.json) | |
| TAG_PREFIX=$(echo "$MODULE_CONFIG" | jq -r '.tag_prefix') | |
| echo "tag=${TAG_PREFIX}${VERSION}" >> "$GITHUB_OUTPUT" | |
| echo "pubspec=$(echo "$MODULE_CONFIG" | jq -r '.pubspec')" >> "$GITHUB_OUTPUT" | |
| echo "version_files=$(echo "$MODULE_CONFIG" | jq -c '.version_files // []')" >> "$GITHUB_OUTPUT" | |
| echo "changelog=$(echo "$MODULE_CONFIG" | jq -r '.changelog')" >> "$GITHUB_OUTPUT" | |
| echo "readme=$(echo "$MODULE_CONFIG" | jq -r '.readme')" >> "$GITHUB_OUTPUT" | |
| echo "package_name=$(echo "$MODULE_CONFIG" | jq -r '.package_name')" >> "$GITHUB_OUTPUT" | |
| echo "branch=release/${MODULE}/${VERSION}" >> "$GITHUB_OUTPUT" | |
| - name: Validate version not already released | |
| env: | |
| TAG: ${{ steps.config.outputs.tag }} | |
| run: | | |
| if git tag -l "$TAG" | grep -q .; then | |
| echo "::error::Tag $TAG already exists" | |
| exit 1 | |
| fi | |
| - name: Clean up existing release branch and PR | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| BRANCH: ${{ steps.config.outputs.branch }} | |
| run: | | |
| EXISTING_PR=$(gh pr list --head "$BRANCH" --json number --jq '.[0].number' 2>/dev/null || true) | |
| if [[ -n "$EXISTING_PR" ]]; then | |
| echo "Closing existing PR #$EXISTING_PR and deleting branch" | |
| gh pr close "$EXISTING_PR" --delete-branch | |
| elif git ls-remote --exit-code origin "refs/heads/$BRANCH" &>/dev/null; then | |
| echo "Deleting existing remote branch $BRANCH" | |
| git push origin --delete "$BRANCH" | |
| fi | |
| - name: Create release branch | |
| env: | |
| BRANCH: ${{ steps.config.outputs.branch }} | |
| run: git checkout -b "$BRANCH" | |
| - name: Bump version in pubspec and version files | |
| env: | |
| VERSION: ${{ inputs.version }} | |
| PUBSPEC: ${{ steps.config.outputs.pubspec }} | |
| VERSION_FILES: ${{ steps.config.outputs.version_files }} | |
| PACKAGE_NAME: ${{ steps.config.outputs.package_name }} | |
| run: | | |
| OLD_VERSION=$(grep '^version:' "$PUBSPEC" | awk '{print $2}') | |
| if [ -z "$OLD_VERSION" ]; then | |
| echo "::error::Could not read current version from $PUBSPEC" | |
| exit 1 | |
| fi | |
| echo "Bumping ${OLD_VERSION} -> ${VERSION}" | |
| sed -i "s/^version: .*/version: ${VERSION}/" "$PUBSPEC" | |
| # Replace any literal occurrence of the previous version in each | |
| # version file. Works for plain `X.Y.Z` constants and embedded forms | |
| # like `X.Y.Z-flutter` because the suffix is preserved verbatim. | |
| while IFS= read -r FILE; do | |
| [ -z "$FILE" ] && continue | |
| if [ ! -f "$FILE" ]; then | |
| echo "::error::version_files entry not found: $FILE" | |
| exit 1 | |
| fi | |
| sed -i "s/${OLD_VERSION}/${VERSION}/g" "$FILE" | |
| done < <(echo "$VERSION_FILES" | jq -r '.[]') | |
| - name: Update README install snippet and version header | |
| env: | |
| REPO_URL: ${{ github.server_url }}/${{ github.repository }} | |
| TAG: ${{ steps.config.outputs.tag }} | |
| README: ${{ steps.config.outputs.readme }} | |
| PACKAGE_NAME: ${{ steps.config.outputs.package_name }} | |
| VERSION: ${{ inputs.version }} | |
| run: | | |
| DATE=$(date +"%B %d, %Y") | |
| # Replace the install snippet line: `<package>: <version>`. | |
| sed -i -E \ | |
| "s|^( *${PACKAGE_NAME}): .*|\1: ${VERSION}|" \ | |
| "$README" | |
| # Replace the runbook-style version header line. | |
| # The `1,/pat/` address range bounds the substitution to lines from | |
| # the start of the file through the first match, so a README that | |
| # accidentally contains a second matching line is left untouched. | |
| sed -i -E \ | |
| "1,/^##### _.*_ - \[.*\]\(.*\)\$/ s|^##### _.*_ - \[.*\]\(.*\)\$|##### _${DATE}_ - [${TAG}](${REPO_URL}/releases/tag/${TAG})|" \ | |
| "$README" | |
| - name: Generate changelog | |
| env: | |
| REPO_URL: ${{ github.server_url }}/${{ github.repository }} | |
| MODULE: ${{ inputs.module }} | |
| TAG: ${{ steps.config.outputs.tag }} | |
| CHANGELOG_FILE: ${{ steps.config.outputs.changelog }} | |
| run: | | |
| CHANGELOG=$(.github/scripts/generate-changelog.sh \ | |
| "$MODULE" "$TAG" "$REPO_URL" HEAD) | |
| if [ -f "$CHANGELOG_FILE" ]; then | |
| { | |
| printf '# Changelog\n\n%s\n' "$CHANGELOG" | |
| sed '1{/^# Changelog$/d;}' "$CHANGELOG_FILE" | |
| } > CHANGELOG.new.md | |
| mv CHANGELOG.new.md "$CHANGELOG_FILE" | |
| else | |
| printf '# Changelog\n\n%s\n' "$CHANGELOG" > "$CHANGELOG_FILE" | |
| fi | |
| - name: Commit and push | |
| env: | |
| MODULE: ${{ inputs.module }} | |
| VERSION: ${{ inputs.version }} | |
| BRANCH: ${{ steps.config.outputs.branch }} | |
| PUBSPEC: ${{ steps.config.outputs.pubspec }} | |
| VERSION_FILES: ${{ steps.config.outputs.version_files }} | |
| CHANGELOG_FILE: ${{ steps.config.outputs.changelog }} | |
| README: ${{ steps.config.outputs.readme }} | |
| run: | | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| git add "$PUBSPEC" "$CHANGELOG_FILE" "$README" | |
| while IFS= read -r FILE; do | |
| [ -z "$FILE" ] && continue | |
| git add "$FILE" | |
| done < <(echo "$VERSION_FILES" | jq -r '.[]') | |
| git commit -m "release: prepare ${MODULE} ${VERSION}" | |
| git push origin "$BRANCH" | |
| - name: Create pull request | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| MODULE: ${{ inputs.module }} | |
| VERSION: ${{ inputs.version }} | |
| TAG: ${{ steps.config.outputs.tag }} | |
| PUBSPEC: ${{ steps.config.outputs.pubspec }} | |
| CHANGELOG_FILE: ${{ steps.config.outputs.changelog }} | |
| README: ${{ steps.config.outputs.readme }} | |
| BRANCH: ${{ steps.config.outputs.branch }} | |
| run: | | |
| gh pr create \ | |
| --title "release: prepare ${MODULE} ${VERSION}" \ | |
| --body "$(cat <<EOF | |
| ## Release ${MODULE} ${VERSION} | |
| This PR prepares the ${MODULE} module for release. | |
| ### Changes | |
| - Bumps \`version\` to \`${VERSION}\` in \`${PUBSPEC}\` and any \`version_files\` listed in \`.github/modules.json\` | |
| - Updates \`${CHANGELOG_FILE}\` with a new section since the last \`${TAG%${VERSION}}*\` tag | |
| - Updates \`${README}\` install snippet and version header | |
| ### After merging | |
| 1. Push tag \`${TAG}\` from the merge commit on \`main\` to trigger the publish workflow: | |
| \`\`\` | |
| git checkout main && git pull | |
| git tag ${TAG} | |
| git push origin ${TAG} | |
| \`\`\` | |
| 2. The publish workflow creates a draft GitHub release and publishes to pub.dev. Review and publish the GitHub release after the workflow finishes. | |
| EOF | |
| )" \ | |
| --base main \ | |
| --head "$BRANCH" |