diff --git a/.github/workflows/README.md b/.github/workflows/README.md index 358e859..0a99f43 100644 --- a/.github/workflows/README.md +++ b/.github/workflows/README.md @@ -61,16 +61,17 @@ See [scripts/README.md](../scripts/README.md) for detailed documentation. **Steps**: -1. Run full test suite to ensure code quality -2. Bump version in root `package.json` and all workspace packages (in dependency order) +1. Install/build the workspace +2. Bump versions in root `package.json` and workspace packages (dependency order) 3. **Automatically detect and update cross-package dependencies**: - Scans all packages for dependencies on the package being version bumped - Updates `dependencies`, `devDependencies`, and `peerDependencies` automatically - Works for any number of packages and dependency relationships -4. Update `pnpm-lock.yaml` to reflect new versions -5. Commit changes to main branch -6. Create and push git tag (e.g., `v1.2.3` or `v1.2.3-beta.1`) -7. Create GitHub release (marked as prerelease for beta versions) +4. Update `pnpm-lock.yaml` +5. Create a release branch (`release/x.y.z`) and commit changes +6. Open a pull request to `main` +7. After merge, `tag-on-release-pr-merge.yml` creates and pushes the version tag, then creates a GitHub Release +8. `publish.yml` runs from the tag push and publishes packages **Version Types**: @@ -106,13 +107,7 @@ See [scripts/README.md](../scripts/README.md) for detailed documentation. ### 1. Required Secrets -Add these secrets in your GitHub repository settings: - -- `NPM_AUTH_TOKEN`: Your npm access token with publish permissions - - Go to [npm Access Tokens](https://www.npmjs.com/settings/tokens) - - Generate a **Granular Access Token** or **Classic Token** - - Ensure it has publish access to your packages - - Add it to GitHub repository secrets +No additional repository secret is required for version bump PR creation when `GITHUB_TOKEN` is allowed to create PRs. ### 2. npm Package Access @@ -123,10 +118,13 @@ Ensure your npm token has permission to publish to: ### 3. Repository Permissions -The workflows need these permissions (automatically granted): +The workflows need these permissions: + +- `contents: write` - to push release branches and tags +- `pull-requests: write` - to create release PRs +- `id-token: write` - for npm trusted publishing -- `contents: write` - to create releases and push tags -- `id-token: write` - for npm publishing +Enable **Settings → Actions → General → Workflow permissions → Allow GitHub Actions to create and approve pull requests**. ## Usage Examples @@ -212,6 +210,14 @@ If the version bump workflow fails with npm dependency errors: **Testing**: The workflow has been tested to ensure dependency updates work correctly with pnpm workspaces. +### Pull Request Creation Fails + +**Error**: `GraphQL: GitHub Actions is not permitted to create or approve pull requests (createPullRequest)` + +**Cause**: The workflow is attempting to create a PR with `GITHUB_TOKEN`, but repository policy blocks this action. + +**Fix**: Enable GitHub Actions PR creation in repository Actions settings. + ## Recommended Branching Strategy ### đŸŽ¯ **Recommended: Main Branch Strategy** diff --git a/.github/workflows/tag-on-release-pr-merge.yml b/.github/workflows/tag-on-release-pr-merge.yml index 3fc30ed..fdbe5ca 100644 --- a/.github/workflows/tag-on-release-pr-merge.yml +++ b/.github/workflows/tag-on-release-pr-merge.yml @@ -32,17 +32,46 @@ jobs: git config --local user.name "GitHub Action" - name: Create and push tag + id: tag run: | VERSION=$(node -p "require('./package.json').version") TAG="v$VERSION" + echo "tag=$TAG" >> "$GITHUB_OUTPUT" # Ensure local tags are up to date before existence check. git fetch --tags if git rev-parse "$TAG" >/dev/null 2>&1; then - echo "Tag $TAG already exists, skipping." + echo "Tag $TAG already exists, skipping tag creation." + echo "created=false" >> "$GITHUB_OUTPUT" + else + git tag "$TAG" + git push origin "$TAG" + echo "created=true" >> "$GITHUB_OUTPUT" + fi + + - name: Create GitHub release + env: + GH_TOKEN: ${{ github.token }} + run: | + TAG="${{ steps.tag.outputs.tag }}" + + # Mark release as prerelease for tags like v1.2.3-beta.1, v1.2.3-rc.1, etc. + if [[ "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+-(alpha|beta|rc|dev|canary|next)(\.[0-9]+)?$ ]]; then + PRERELEASE_FLAG="--prerelease" + RELEASE_KIND="prerelease" + else + PRERELEASE_FLAG="" + RELEASE_KIND="stable release" + fi + + if gh release view "$TAG" >/dev/null 2>&1; then + echo "Release for $TAG already exists, skipping release creation." exit 0 fi - git tag "$TAG" - git push origin "$TAG" + gh release create "$TAG" \ + --title "$TAG" \ + --notes "Automated $RELEASE_KIND created from merged release PR #${{ github.event.pull_request.number }}." \ + $PRERELEASE_FLAG + echo "Created GitHub release for $TAG." diff --git a/.github/workflows/version-bump.yml b/.github/workflows/version-bump.yml index 5a89bb3..93eae02 100644 --- a/.github/workflows/version-bump.yml +++ b/.github/workflows/version-bump.yml @@ -180,33 +180,66 @@ jobs: run: pnpm install --lockfile-only - name: Commit version changes + id: commit-changes run: | NEW_VERSION="${{ steps.version-root.outputs.new_version }}" git add . + if git diff --cached --quiet; then + echo "No version changes detected; skipping commit." + echo "has_changes=false" >> $GITHUB_OUTPUT + exit 0 + fi git commit -m "chore: bump version to $NEW_VERSION - Updated all workspace packages to $NEW_VERSION - Updated cross-package dependencies automatically" + echo "has_changes=true" >> $GITHUB_OUTPUT - name: Push release branch id: push-branch + if: steps.commit-changes.outputs.has_changes == 'true' run: | NEW_VERSION="${{ steps.version-root.outputs.new_version }}" RELEASE_BRANCH="release/${NEW_VERSION#v}" git checkout -b "$RELEASE_BRANCH" - git push origin "$RELEASE_BRANCH" + git push --set-upstream origin "$RELEASE_BRANCH" echo "release_branch=$RELEASE_BRANCH" >> $GITHUB_OUTPUT + - name: Check for existing PR + id: existing-pr + if: steps.commit-changes.outputs.has_changes == 'true' + env: + GH_TOKEN: ${{ github.token }} + run: | + RELEASE_BRANCH="${{ steps.push-branch.outputs.release_branch }}" + EXISTING_PR_URL=$(gh pr list \ + --base main \ + --head "$RELEASE_BRANCH" \ + --state open \ + --json url \ + --jq '.[0].url') + + if [ -n "$EXISTING_PR_URL" ]; then + echo "Found existing PR: $EXISTING_PR_URL" + echo "exists=true" >> $GITHUB_OUTPUT + echo "url=$EXISTING_PR_URL" >> $GITHUB_OUTPUT + else + echo "No existing PR found." + echo "exists=false" >> $GITHUB_OUTPUT + fi + - name: Create Pull Request + if: steps.commit-changes.outputs.has_changes == 'true' && steps.existing-pr.outputs.exists == 'false' env: GH_TOKEN: ${{ github.token }} run: | NEW_VERSION="${{ steps.version-root.outputs.new_version }}" RELEASE_BRANCH="${{ steps.push-branch.outputs.release_branch }}" - gh pr create \ + set +e + PR_OUTPUT=$(gh pr create \ --base main \ --head "$RELEASE_BRANCH" \ --title "chore: bump version to $NEW_VERSION" \ @@ -225,4 +258,31 @@ jobs: 2. Tag is created automatically after merge 3. Publish workflow runs from the tag push EOF - )" + )" 2>&1) + EXIT_CODE=$? + set -e + + echo "$PR_OUTPUT" + + if [ "$EXIT_CODE" -ne 0 ]; then + if echo "$PR_OUTPUT" | grep -q "GitHub Actions is not permitted to create or approve pull requests"; then + echo "❌ Pull request creation is blocked for GITHUB_TOKEN in this repository." + echo "â„šī¸ Enable 'Allow GitHub Actions to create and approve pull requests' in repository Actions settings." + fi + exit "$EXIT_CODE" + fi + + - name: Pull Request summary + if: steps.commit-changes.outputs.has_changes == 'true' + run: | + if [ "${{ steps.existing-pr.outputs.exists }}" = "true" ]; then + echo "## Release PR" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "Using existing PR: ${{ steps.existing-pr.outputs.url }}" >> $GITHUB_STEP_SUMMARY + else + NEW_VERSION="${{ steps.version-root.outputs.new_version }}" + RELEASE_BRANCH="${{ steps.push-branch.outputs.release_branch }}" + echo "## Release PR" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "Created PR for $NEW_VERSION from branch \`$RELEASE_BRANCH\`." >> $GITHUB_STEP_SUMMARY + fi