Metadata-Version: 2.4
Name: rlsbl
Version: 0.5.0
Summary: Release orchestration and project scaffolding for npm, PyPI, and Go
Project-URL: Repository, https://github.com/smm-h/rlsbl
Author: smm-h
License-Expression: MIT
License-File: LICENSE
Keywords: cli,go,goreleaser,npm,publish,pypi,release,rlsbl,scaffold
Requires-Python: >=3.11
Description-Content-Type: text/markdown

<p align="center">
  <img src="logo.svg" alt="rlsbl" width="336" height="105">
</p>

# rlsbl

Release orchestration and project scaffolding CLI for npm, PyPI, and Go.

## Install

From npm:

```
npm i -g rlsbl
```

From PyPI (requires Node.js 18+):

```
uv tool install rlsbl
```

## Quick start

```
rlsbl scaffold
rlsbl release minor
```

## Commands

All commands work at the top level -- registries are auto-detected from project files (`package.json`, `pyproject.toml`, `go.mod`). Use `--registry <npm|pypi|go>` when you need to target a specific registry.

### scaffold [--force] [--update]

Scaffolds CI/CD infrastructure and release tooling for all detected registries.

```
rlsbl scaffold
rlsbl scaffold --registry npm          # target npm only
rlsbl scaffold --registry pypi --force # overwrite existing files
rlsbl scaffold --registry go           # target Go only
```

Context-aware behavior when files already exist (without `--force`):

| File | Behavior |
|---|---|
| `CLAUDE.md` | Appends rlsbl sections if the marker is not present |
| `.gitignore` | Merges missing entries from the template |
| `.github/workflows/ci.yml` | Preserves existing file, prints a note to review manually |
| All others | Skipped |

### release [patch|minor|major] [--dry-run] [--quiet]

Bumps version, commits, pushes, and creates a GitHub Release. Defaults to `patch`.

```
rlsbl release minor
rlsbl release major --dry-run --registry npm
```

The version is synced across all detected project files (`package.json`, `pyproject.toml`, `VERSION`) regardless of which registry is primary. Go projects use a plain `VERSION` file as the version source.

If `scripts/pre-release.sh` exists, it runs before any changes are made. A non-zero exit aborts the release.

### status

Shows project status: package name, version (per registry), git branch, last tag, working tree state, changelog coverage, and CI workflow presence.

```
rlsbl status
rlsbl status --registry pypi
```

### check \<name\>

Checks name availability on both npm and PyPI, and warns about confusingly similar names.

```
rlsbl check my-cool-lib
rlsbl check my-cool-lib --registry npm   # npm only
```

npm checks variant spellings (hyphens, underscores, dots, no separator). PyPI normalizes per PEP 503 and checks common alternatives.

### discover [--mine]

Lists all projects in the rlsbl ecosystem by querying GitHub for repositories with the `rlsbl` topic.

```
rlsbl discover
rlsbl discover --mine              # only your repos
```

Uses GitHub token if available (higher rate limit). Works unauthenticated for public repos.

### Ecosystem tagging

By default, `scaffold` and `release` add an `"rlsbl"` keyword to `package.json` and/or `pyproject.toml`, and set the `rlsbl` topic on the GitHub repository. This makes projects discoverable via `rlsbl discover`.

To disable tagging:

| Method | Scope |
|---|---|
| `--no-tag` flag | Single invocation |
| `{"tag": false}` in `.rlsbl/config.json` | This project |
| `{"tag": false}` in `~/.rlsbl/config.json` | All your projects |

Precedence: CLI flag > project config > user config > default (enabled).

Global flags: `--help`, `--version`.

## Release flow

When you run `release`, the following happens in order:

1. Verifies `gh` CLI is installed and authenticated
2. Checks that the git working tree is clean
3. Reads the current version from the primary project file
4. Computes the new version and confirms the git tag does not already exist
5. Validates that `CHANGELOG.md` contains a `## <new-version>` section
6. Runs `scripts/pre-release.sh` if present (non-zero exit aborts)
7. Writes the new version to the primary project file
8. Syncs the new version to all other detected project files
9. Commits the version bump (uses `safegit` if available, otherwise `git`)
10. Pushes the branch to `origin`
11. Creates a GitHub Release tagged `v<new-version>` with the changelog entry as notes
12. The GitHub Release triggers `publish.yml`, which publishes to the registry

## What scaffold creates

| File | Source | Purpose |
|---|---|---|
| `.github/workflows/ci.yml` | Registry-specific | CI workflow (lint, test) |
| `.github/workflows/publish.yml` | Registry-specific | Publish on GitHub Release (OIDC) |
| `CHANGELOG.md` | Shared | Version changelog |
| `LICENSE` | Shared | MIT license (author and year filled in) |
| `.gitignore` | Shared | Standard ignores for the ecosystem |
| `CLAUDE.md` | Shared | AI assistant instructions |
| `.claude/settings.json` | Shared | Claude Code settings |
| `scripts/check-prs.sh` | Shared | PR review helper |
| `scripts/pre-release.sh` | Shared | Pre-release hook (runs before each release) |
| `scripts/record-gif.sh` | Shared | Terminal recording helper |
| `scripts/pre-push-hook.sh` | Shared | Pre-push changelog enforcement |

All `.sh` files in `scripts/` are made executable automatically. The pre-push hook is installed into `.git/hooks/pre-push` during scaffold.

## Pre-push hook

The scaffolded `scripts/pre-push-hook.sh` is installed as a git pre-push hook during `scaffold`. It prevents pushing when `CHANGELOG.md` lacks an entry for the current version.

How it works:

1. Detects project type (`package.json`, `pyproject.toml`, or `VERSION`)
2. Extracts the current version
3. Checks that `CHANGELOG.md` contains a heading `## <version>`
4. Blocks the push with an error if the entry is missing

To reinstall manually:

```
cp scripts/pre-push-hook.sh .git/hooks/pre-push && chmod +x .git/hooks/pre-push
```

## First publish

The first version must be published manually before CI can take over:

| Registry | Manual first publish | Then configure |
|---|---|---|
| npm | Add an `NPM_TOKEN` secret to your GitHub repo (Settings > Secrets > Actions), then push a release | CI handles subsequent publishes |
| PyPI | Run `uv publish` | Set up [Trusted Publishing](https://docs.pypi.org/trusted-publishers/) on pypi.org |
| Go | Push to GitHub and create a release -- Go modules are published by the tag itself | No secrets needed; `pkg.go.dev` indexes automatically |

After configuration, all subsequent releases are handled by CI when `rlsbl release` creates a GitHub Release. Go projects use GoReleaser in CI (via GitHub Actions) to build cross-platform binaries.

## Environment variables

| Variable | Default | Description |
|----------|---------|-------------|
| `RLSBL_PUSH_TIMEOUT` | `120` | Timeout in seconds for `git push` operations. Increase if your pre-push hooks (e.g. test suites) take longer than 2 minutes. |

## Requirements

- Node 18+
- [GitHub CLI](https://cli.github.com) (`gh`), installed and authenticated
- git

## License

MIT
