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
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,26 @@ jobs:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
```

### Protected Branch Setup

If `main` is a protected branch, the default `GITHUB_TOKEN` cannot push directly.
Use `PVER_GITHUB_TOKEN` — a Personal Access Token (classic) with `repo` scope, or
a fine-grained PAT with *Contents: Read & Write* — added to repository secrets and
listed as a **bypass actor** in the branch protection rules.

```yml
- uses: actions/checkout@v4
with:
token: ${{ secrets.PVER_GITHUB_TOKEN }} # checkout with the bypass token
- run: pver release
env:
PVER_GITHUB_TOKEN: ${{ secrets.PVER_GITHUB_TOKEN }}
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
```

`pver` prefers `PVER_GITHUB_TOKEN` over `GITHUB_TOKEN` when configuring the push
remote, so adding the secret and updating the workflow is all that is required.

## Analysis

Analysis has two steps. Getting the `current_version` and using the `transition_method` to
Expand Down
46 changes: 24 additions & 22 deletions src/release/configure-origin.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,28 @@
// Configures the git remote origin URL to include a GitHub token so that
// pver can push commits and tags. Prefers PVER_GITHUB_TOKEN (a PAT that can
// bypass branch-protection rules) and falls back to GITHUB_TOKEN.
export const configureOrigin = async (git: any) => {
if (process.env.GITHUB_TOKEN) {
// This doesn't work- it's possible the checkout command needs to use the
// github token (in the workflow yaml)
const remote_url = await git.remote(["get-url", "origin"])
const token = process.env.PVER_GITHUB_TOKEN || process.env.GITHUB_TOKEN
if (!token) return

// if the remote is in the form of git@github.com:foo/bar.git, convert it to
// https://github.com/foo/bar.git
if (remote_url.includes("git@")) {
const new_url = remote_url!.replace("git@", "https://")
await git.removeRemote("origin")
await git.addRemote("origin", new_url)
}
const encodedToken = encodeURIComponent(token.trim())
const remote_url: string = (await git.remote(["get-url", "origin"])).trim()

if (!remote_url.includes("oauth2:")) {
const new_url = remote_url!
.replace(
"https://",
`https://oauth2:${process.env.GITHUB_TOKEN.trim()}@`
)
.replace(/\n/g, "")
await git.removeRemote("origin")
await git.addRemote("origin", new_url)
}
}
// Normalise SSH → HTTPS before embedding credentials
let https_url = remote_url.startsWith("git@")
? remote_url
.replace("git@github.com:", "https://github.com/")
.replace(/\.git$/, "")
: remote_url

// Strip any existing embedded credentials so we don't double-embed
https_url = https_url.replace(/https:\/\/[^@]+@/, "https://")

const authenticated_url = https_url.replace(
"https://",
`https://oauth2:${encodedToken}@`,
)

await git.removeRemote("origin")
await git.addRemote("origin", authenticated_url)
}