Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 78 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,84 @@ pver stage increment --git --npm
pver setup github
```

### Usage in Github Actions
## Working with Protected Branches

`pver` supports pushing to protected branches using GitHub's Git Data API. This is useful when your repository has branch protection rules that prevent direct pushes.

### Automatic Fallback

By default, `pver` will try a normal `git push` first. If it fails due to branch protection, it will automatically fall back to using the GitHub Git Data API:

```bash
pver release --git --npm
```

### Explicit Git Data API Usage

You can explicitly use the Git Data API with the `--push-via-api` flag:

```bash
pver release --git --npm --push-via-api
```

### Authentication

The Git Data API requires a GitHub token with appropriate permissions. Set one of these environment variables:

- `PVER_GITHUB_TOKEN` (preferred)
- `GITHUB_TOKEN` (fallback)

The token needs:
- `repo` scope for private repositories
- `public_repo` scope for public repositories
- For protected branches: the token must have "bypass branch protections" permission (typically requires admin access or a GitHub App)

### GitHub Actions Example

```yml
name: Publish to npm
on:
push:
branches:
- main
jobs:
publish:
runs-on: ubuntu-latest
permissions:
contents: write # Required for Git Data API
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0 # Fetch full history for version analysis
- uses: actions/setup-node@v3
with:
node-version: 20
registry-url: https://registry.npmjs.org/
- run: npm install -g pver
- run: npm ci
- run: pver release
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
PVER_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
```

### How Git Data API Differs from Other Approaches

The Git Data API approach is fundamentally different from:

1. **SSH Key Normalization**: Other solutions modify the git remote URL to use SSH with authentication. This still uses `git push` under the hood, which respects branch protection rules.

2. **Contents API (PR #18)**: The Contents API is designed for single-file updates and creates commits through a different mechanism. It's limited to individual file operations.

3. **Git Data API (this implementation)**: Uses GitHub's low-level Git Data API to:
- Create blobs for changed files
- Create a new tree object
- Create a new commit object
- Update the branch reference directly

This bypasses the normal push flow and works with tokens that have "bypass branch protections" permission.

### Usage in GitHub Actions

Drop this into `.github/workflows/publish.yml`

Expand Down
4 changes: 4 additions & 0 deletions cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,10 @@ yargs(hideBin(process.argv))
describe: "Disable pushing to main branch (overrides --push-main)",
type: "boolean",
})
.option("push-via-api", {
describe: "Use GitHub Git Data API to push (bypasses branch protection)",
type: "boolean",
})
.option("readme", {
describe: "Increment version in README files",
type: "boolean",
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 5 additions & 3 deletions src/app-context/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import fs from "fs/promises"
import { promises as fs } from "fs"

/**
* The AppContext stores directory, arg, flag and config information and is
Expand All @@ -24,6 +24,7 @@ export type AppContext = {
transition_method: "auto" | "simplegit"
release_methods: Array<ReleaseMethod>
readme_files: string[]
push_via_api?: boolean
}

export const getAppContext = async ({
Expand Down Expand Up @@ -83,7 +84,8 @@ export const getAppContext = async ({
current_directory: process.cwd(),
current_method: argv.current ?? "auto",
transition_method: argv.transition ?? "auto",
release_methods: [...new Set(release_methods)],
readme_files: [...new Set(readme_files)],
release_methods: Array.from(new Set(release_methods)),
readme_files: Array.from(new Set(readme_files)),
push_via_api: argv.pushViaApi ?? false,
}
}
Loading