# releasearoni
[![latest version](https://img.shields.io/npm/v/releasearoni.svg)](https://www.npmjs.com/package/releasearoni)
[![Actions Status](https://github.com/bcomnes/releasearoni/workflows/tests/badge.svg)](https://github.com/bcomnes/releasearoni/actions)
[![downloads](https://img.shields.io/npm/dm/releasearoni.svg)](https://npmtrends.com/releasearoni)
![Types in JS](https://img.shields.io/badge/types_in_js-yes-brightgreen)
[![neostandard javascript style](https://img.shields.io/badge/code_style-neostandard-7fffff?style=flat&labelColor=ff80ff)](https://github.com/neostandard/neostandard)
[![Socket Badge](https://socket.dev/api/badge/npm/package/releasearoni)](https://socket.dev/npm/package/releasearoni)

<img width="256" height="256" alt="Image" src="https://github.com/user-attachments/assets/1426b820-6ba0-4387-8e66-a819c07855df" />

Publish GitHub releases from the command line or Node.js. Reads defaults from `package.json` and `CHANGELOG.md` (keepachangelog format) so you rarely need to pass any flags. Ships two bins: `releasearoni` uses the GitHub API directly via `@octokit/rest`, and `releasearoni-gh` is a thin wrapper around the `gh` CLI.

```console
npm install releasearoni
```

## CLI

Both bins share the same flags and read the same defaults. Run either from a directory that contains `package.json` and `CHANGELOG.md`.

### `releasearoni`

Uses `@octokit/rest` + `ghauth` for authentication. No `gh` CLI required.

```console
Usage: releasearoni [options]

    Example: releasearoni

    --tag-name, -t        Tag for this release
    --target-commitish, -c Commitish value for tag
    --name, -n            Release title
    --body, -b            Release body text
    --owner, -o           Repo owner
    --repo, -r            Repo name
    --draft, -d           Publish as draft (default: false)
    --prerelease, -p      Publish as prerelease (default: false)
    --workpath, -w        Path to working directory (default: cwd)
    --endpoint, -e        GitHub API endpoint URL (default: https://api.github.com)
    --assets, -a          Comma-delimited list of assets to upload
    --dry-run             Preview release without creating it (default: false)
    --no-upsert           Fail if a release for this tag already exists (default: false)
    --prompt              Prompt for confirmation before publishing (default: false)
    --no-npm-check        Skip npm auth check before publishing (default: false)
    --no-push             Skip git push --follow-tags before publishing (default: false)
    --no-build            Skip npm run build even if a build script is present (default: false)
    --major-branch        After release, create or force-update the major version branch ref (e.g. v1) and push it — required for GitHub Actions consumers that pin to a major version (default: false)
    --help, -h            Show help
    --version, -v         Show version

releasearoni (v0.0.0)
```

### `releasearoni-gh`

Delegates to `gh release create`. Requires the [gh CLI](https://cli.github.com) to be installed and authenticated.

```console
Usage: releasearoni-gh [options]

    Example: releasearoni-gh

    --tag-name, -t        Tag for this release
    --target-commitish, -c Commitish value for tag
    --name, -n            Release title
    --body, -b            Release body text
    --owner, -o           Repo owner
    --repo, -r            Repo name
    --draft, -d           Publish as draft (default: false)
    --prerelease, -p      Publish as prerelease (default: false)
    --workpath, -w        Path to working directory (default: cwd)
    --endpoint, -e        GitHub API endpoint URL (default: https://api.github.com)
    --assets, -a          Comma-delimited list of assets to upload
    --dry-run             Preview release without creating it (default: false)
    --no-upsert           Fail if a release for this tag already exists (default: false)
    --prompt              Prompt for confirmation before publishing (default: false)
    --no-npm-check        Skip npm auth check before publishing (default: false)
    --no-push             Skip git push --follow-tags before publishing (default: false)
    --no-build            Skip npm run build even if a build script is present (default: false)
    --major-branch        After release, create or force-update the major version branch ref (e.g. v1) and push it — required for GitHub Actions consumers that pin to a major version (default: false)
    --help, -h            Show help
    --version, -v         Show version

releasearoni-gh (v0.0.0)
```

### Defaults

Both bins derive release defaults automatically:

| Field | Default source |
|---|---|
| `tag_name` | `v` + `version` from `package.json` |
| `name` | Same as `tag_name` |
| `body` | Latest versioned entry from `CHANGELOG.md` |
| `target_commitish` | Current `HEAD` commit SHA (`git rev-parse HEAD`) |
| `owner` / `repo` | Parsed from `repository` field in `package.json` |
| `draft` | `false` |
| `prerelease` | `false` |
| `endpoint` | `https://api.github.com` |

The `CHANGELOG.md` must be in [keepachangelog](https://keepachangelog.com) format. Releases are blocked if an `[Unreleased]` section contains content.

If a release for the tag already exists (e.g. when re-running a failed publish), both bins will update the existing release in place rather than failing. Pass `--no-upsert` to get a hard failure instead, which is useful for enforcing that a tag is never accidentally re-released.

### `--major-branch`

When publishing a GitHub Action, consumers typically pin to a major version ref rather than a specific tag:

```yaml
- uses: my-org/my-action@v1  # receives v1.x.x updates automatically
```

Pass `--major-branch` to have releasearoni maintain that ref automatically after each release. After the GitHub release is created, it:

1. Resolves the release tag to its exact commit SHA (`git rev-parse <tag>^{commit}`)
2. Creates or hard-resets the major version branch (e.g. `v1`) to that commit
3. Force-pushes the branch to origin

The major version is parsed from the release tag — `v1.2.3` and `1.2.3` both produce `v1`.

```json
{
  "scripts": {
    "prepublishOnly": "releasearoni --major-branch"
  }
}
```

This flag is a no-op for regular npm packages. It's only meaningful when the repository is consumed as a GitHub Action.

### `releasearoni npm-check`

Checks whether you are logged in to npm. If not logged in and the `CI` environment variable is not set, runs `npm login` interactively. In CI, exits non-zero with an actionable error if not authenticated. Designed to run at the top of a `prepublishOnly` script so login issues are caught before any build, push, or release work has started.

```console
Usage: releasearoni npm-check
```

Typical `package.json` setup:

```json
{
  "scripts": {
    "prepublishOnly": "releasearoni"
  }
}
```

### `releasearoni version`

Generates `CHANGELOG.md` via [auto-changelog](https://github.com/CookPete/auto-changelog) and stages it with `git add`. Designed for use as the npm [`version` lifecycle script](https://docs.npmjs.com/cli/v10/using-npm/scripts#life-cycle-scripts).

```console
Usage: releasearoni version [options]

    --changelog, -c       Changelog file to generate and stage (default: CHANGELOG.md)
    --add, -a             Additional files to stage after changelog (repeatable)
    --breaking-pattern    Regex for breaking changes (default: 'BREAKING CHANGE:')
    --template            auto-changelog template (default: keepachangelog)
    --workpath, -w        Working directory (default: cwd)
    --help, -h            Show help
```

Typical `package.json` setup — no separate `auto-changelog` install needed:

```json
{
  "scripts": {
    "version": "releasearoni version",
    "prepublishOnly": "releasearoni"
  }
}
```

Run `npm version patch` (or `minor`/`major`) and npm will:
1. Bump the version in `package.json`
2. Run `releasearoni version` → regenerates `CHANGELOG.md` and stages it
3. Commit and tag

Then `npm publish` triggers `prepublishOnly`, which checks npm auth, pushes the tag, and creates the GitHub release.

To stage extra files (e.g. a lock file your project manages separately):

```json
"version": "releasearoni version --add package-lock.json"
```

## Example setup

A complete `package.json` wired up for versioning and publishing via GitHub Actions:

```jsonc
{
  "name": "my-package",
  "version": "1.0.0",
  "repository": {
    "type": "git",
    "url": "git+https://github.com/my-org/my-package.git"
  },
  "scripts": {
    // Runs during `npm version`: regenerates CHANGELOG.md and stages it
    // Use --add to stage additional files that should be part of the version commit
    "version": "releasearoni version --add dist/manifest.json --add src/generated/version.js",
    // Runs before `npm publish`:
    //   1. checks npm login / OIDC status
    //   2. runs `npm run build` if a build script is present
    //   3. pushes the version commit + tag to GitHub
    //   4. creates the GitHub release
    // Use releasearoni-gh instead if you prefer the gh CLI.
    "prepublishOnly": "releasearoni",
    // Clean up build artifacts after npm has published them
    "postpublish": "npm run clean"
  },
  "devDependencies": {
    "releasearoni": "^0.1.0"
  }
}
```

`releasearoni` and `releasearoni-gh` are interchangeable in the scripts above — swap in `releasearoni-gh` if you prefer to delegate to the `gh` CLI.

A matching GitHub Actions workflow that triggers on manual dispatch:

```yaml
# .github/workflows/release.yml
name: npm bump

on:
  workflow_dispatch:
    inputs:
      version_type:
        description: 'Version type'
        type: choice
        options: [major, minor, patch]
        default: patch
        required: true

permissions:
  contents: write # required to push the version commit, tag, and create the GitHub release
  id-token: write # required for OIDC publishing to npm (provenance)

jobs:
  version_and_release:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
        with:
          fetch-depth: 0 # fetch full history so things like auto-changelog work properly
      - uses: actions/setup-node@v6
        with:
          node-version-file: package.json
      - run: npm install
      - run: npm test
      - name: Configure git author
        run: |
          git config user.name "${{ github.actor }}"
          git config user.email "${{ github.actor }}@users.noreply.github.com"
      - name: npm version
        run: npm version "${{ github.event.inputs.version_type }}"
      - name: npm publish
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} # used by releasearoni to create the GitHub release
        run: npm publish
```

`npm version` runs the `version` lifecycle script (changelog + commit + tag), then `npm publish` triggers `prepublishOnly` which pushes the tag and creates the GitHub release.

### Environment variables (`releasearoni` only)

Authentication for the direct API bin is resolved in this order:

- `GH_TOKEN`
- `GITHUB_TOKEN`
- Interactive [GitHub device flow](https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/authorizing-oauth-apps#device-flow) via `ghauth` — opens a browser prompt, stores the token in the OS keychain for future runs. Press <kbd>Enter</kbd> at the device flow prompt to fall back to pasting a personal access token instead.

## API

### `import { createRelease } from 'releasearoni'`

Programmatic release creation using `@octokit/rest`.

### `release = await createRelease(options)`

Create a GitHub release. Reads `package.json` and `CHANGELOG.md` from `workpath` to fill in defaults, then calls the GitHub Releases API.

```js
import { createRelease } from 'releasearoni'

const release = await createRelease({
  auth: { token: process.env.GITHUB_TOKEN },
  workpath: process.cwd(), // optional, defaults to cwd
})

console.log(release.html_url)
```

Options:

| Option | Type | Description |
|---|---|---|
| `auth` | `{ token: string }` | **Required.** GitHub API token. |
| `workpath` | `string` | Path to directory with `package.json` and `CHANGELOG.md`. Default: `cwd`. |
| `owner` | `string` | Repo owner. Default: parsed from `package.json`. |
| `repo` | `string` | Repo name. Default: parsed from `package.json`. |
| `tag_name` | `string` | Tag to create. Default: `v` + package version. |
| `target_commitish` | `string` | Branch or SHA for the tag. Default: current `HEAD`. |
| `name` | `string` | Release title. Default: same as `tag_name`. |
| `body` | `string` | Release body. Default: latest CHANGELOG entry. |
| `draft` | `boolean` | Publish as draft. Default: `false`. |
| `prerelease` | `boolean` | Mark as prerelease. Default: `false`. |
| `endpoint` | `string` | GitHub API base URL. Default: `https://api.github.com`. |
| `assets` | `string[] \| {name, path}[]` | Files to upload as release assets. |
| `upsert` | `boolean` | Update existing release if tag already exists. Default: `true`. Pass `false` to fail instead. |

Returns the GitHub release object from the API response.

## See also

- [gh-release](https://github.com/bcomnes/gh-release) — the original CLI this was forked from
- [gh CLI](https://cli.github.com) — official GitHub CLI used by `releasearoni-gh`
- [keepachangelog](https://keepachangelog.com) — CHANGELOG format expected by releasearoni
- [ghauth](https://github.com/nicolo-ribaudo/ghauth) — OAuth token management used by `releasearoni`

## License

MIT
