Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
e08c3eb
initial versions
ecraig12345 Mar 6, 2026
441166c
more tests and some fixes
ecraig12345 Mar 6, 2026
ecc59e6
linting and formatting
ecraig12345 Mar 6, 2026
fb5bc41
rust test improvements
ecraig12345 Mar 6, 2026
312c8f5
pin actions
ecraig12345 Mar 6, 2026
9293c6b
add missing change impl and tests
ecraig12345 Mar 6, 2026
e1c792f
format ubuntu only
ecraig12345 Mar 6, 2026
3844355
Update package_groups.go with the latest content
ecraig12345 Mar 6, 2026
ffadef3
Update scoped_packages.go with new content
ecraig12345 Mar 7, 2026
d8f4875
test path updates
ecraig12345 Mar 7, 2026
2ba1334
config
ecraig12345 Mar 7, 2026
ce96e9e
use go test assert library
ecraig12345 Mar 7, 2026
2674012
Change files
ecraig12345 Mar 7, 2026
9be875c
go test helpers
ecraig12345 Mar 7, 2026
1399add
go logging
ecraig12345 Mar 7, 2026
89a0396
config etc
ecraig12345 Mar 7, 2026
833af9b
rust logging framework
ecraig12345 Mar 7, 2026
21e6e9d
rust log tests and format
ecraig12345 Mar 10, 2026
0bb3d16
go logging and log tests
ecraig12345 Mar 10, 2026
acc42a2
go fixes
ecraig12345 Mar 11, 2026
89cba38
go: pseudo enums
ecraig12345 Mar 11, 2026
bb6fbe9
go modernizer
ecraig12345 Mar 11, 2026
af9cd18
more go modernization and fixes
ecraig12345 Mar 11, 2026
2af9f9a
go: add verbose logging
ecraig12345 Mar 11, 2026
f595230
go: missing tests and comments
ecraig12345 Mar 11, 2026
854b520
go: remove product code duplication in tests
ecraig12345 Mar 11, 2026
2533af9
go: simplify test options
ecraig12345 Mar 11, 2026
e114a46
fix go tests
ecraig12345 Mar 11, 2026
f403198
go: better findProjectRoot (still not quite matching workspace-tools)
ecraig12345 Mar 11, 2026
1fcf83c
rust: add more tests
ecraig12345 Mar 11, 2026
219e985
rust: improve project root logic
ecraig12345 Mar 11, 2026
4c2777d
rust: more logging and test fixes
ecraig12345 Mar 11, 2026
a2c26f7
rust: modernize
ecraig12345 Mar 11, 2026
21add58
formatting
ecraig12345 Mar 11, 2026
7326dd6
fix git commands
ecraig12345 Mar 12, 2026
02607ad
format
ecraig12345 Mar 12, 2026
736bcd6
skills and formatting
ecraig12345 Mar 12, 2026
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
7 changes: 7 additions & 0 deletions .claude/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"enabledPlugins": {
"gopls-lsp@claude-plugins-official": true,
"typescript-lsp@claude-plugins-official": true,
"rust-analyzer-lsp@claude-plugins-official": true
}
}
51 changes: 51 additions & 0 deletions .claude/shared/rust-and-go.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<!-- This is referenced from the rust and go skills -->

## Rust and Go Implementation Guidelines

The `rust/` and `go/` directories contain parallel re-implementations of beachball's `check` and `change` commands and the corresponding tests.

### Scope

Implemented:

- CLI args (as relevant for supported commands)
- JSON config loading (`.beachballrc.json` and `package.json` `"beachball"` field)
- workspaces detection (npm, yarn, pnpm, rush, lerna)
- `getChangedPackages` (git diff + file-to-package mapping + change file dedup)
- `validate()` (minus `bumpInMemory`/dependency validation)
- non-interactive `change` command (`--type` + `--message`)
- `check` command.

Not implemented:

- JS config files
- interactive prompts
- all bumping and publishing operations
- sync

### Implementation requirements

The behavior, log messages, and tests as specified in the TypeScript code MUST BE MATCHED in the Go/Rust code.

- Do not change behavior or logs or remove tests, unless it's exclusively related to features which you've been asked not to implement yet.
- If a different pattern would be more idiomatic in the target language, or it's not possible to implement the exact same behavior in the target language, ask the user before changing anything.
- Don't make assumptions about the implementation of functions from `workspace-tools`. Check the JS implementation in `node_modules` and exactly follow that.

When porting tests, add a comment by each Rust/Go test with the name of the corresponding TS test. If any TS tests have been omitted or combined, add a comment indicating which tests and why.

Use syntax and helpers from the newest version of the language where it makes sense. If a particular scenario is most commonly handled in this language by some external library, and the library would meaningfully simplify the code, ask the user about adding the library as a dependency.

After making changes, run the commands to build, test, lint, and format.

### Key Implementation Details

**Git commands**: Both shell out to `git` (matching the TS approach via workspace-tools). The git flags used should exactly match the workspace-tools code. Three-dot range (`branch...`) is used for diffs.

**Config loading**: Searches `.beachballrc.json` then `package.json` `"beachball"` field, walking up from cwd but stopping at git root.

**Glob matching**: Two modes matching the TS behavior — `matchBase` (patterns without `/` match basename) for `ignorePatterns`, full path matching for `scope`/`groups`.

### Known Gotchas

- **macOS `/tmp` symlink**: `/tmp` is a symlink to `/private/tmp`. `git rev-parse --show-toplevel` resolves symlinks but `tempfile`/`os.MkdirTemp` does not. Both implementations canonicalize paths (`std::fs::canonicalize` in Rust, `filepath.EvalSymlinks` in Go) when comparing git-returned paths with filesystem paths.
- **Default branch name**: Modern git defaults to `main`. Test fixtures use `--initial-branch=master` for bare repo init to match the `origin/master` refs used in tests.
11 changes: 11 additions & 0 deletions .claude/skills/go-impl.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
name: go-impl
description: |
Go implementation guide for beachball's Go port.
TRIGGER when: user asks to edit, write, test, or debug Go code under the go/ directory, or opens/references go/*.go files.
DO NOT TRIGGER when: working only with TypeScript or Rust code.
---

!`cat go/README.md`

!`cat .claude/skills/rust-and-go.md`
11 changes: 11 additions & 0 deletions .claude/skills/rust-impl.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
name: rust-impl
description: |
Rust implementation guide for beachball's Rust port.
TRIGGER when: user asks to edit, write, test, or debug Rust code under the rust/ directory, or opens/references rust/*.rs files.
DO NOT TRIGGER when: working only with TypeScript or Go code.
---

!`cat rust/README.md`

!`cat .claude/skills/rust-and-go.md`
89 changes: 89 additions & 0 deletions .github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ jobs:

- run: yarn --frozen-lockfile

- run: yarn format:check
if: ${{ matrix.os == 'ubuntu-latest' }}

- run: yarn build

- run: yarn checkchange --verbose
Expand Down Expand Up @@ -84,3 +87,89 @@ jobs:

- run: yarn docs:build
working-directory: ./docs

rust:
strategy:
matrix:
os: [ubuntu-latest, windows-latest]

name: build rust (${{ matrix.os }})

runs-on: ${{ matrix.os }}

steps:
- name: Check out code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6

- name: Install Rust
uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable
with:
components: clippy, rustfmt

- name: Cache cargo
uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5
with:
path: |
~/.cargo/registry
~/.cargo/git
rust/target
key: ${{ runner.os }}-cargo-${{ hashFiles('rust/Cargo.lock') }}

- name: Format
run: cargo fmt --check
working-directory: ./rust
if: ${{ matrix.os == 'ubuntu-latest' }}

- name: Build
run: cargo build
working-directory: ./rust

- name: Lint
run: cargo clippy --all-targets -- -D warnings
working-directory: ./rust

- name: Test
run: cargo test
working-directory: ./rust

go:
strategy:
matrix:
os: [ubuntu-latest, windows-latest]

name: build go (${{ matrix.os }})

runs-on: ${{ matrix.os }}

steps:
- name: Check out code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6

- name: Install Go
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6
with:
go-version-file: go/go.mod
cache-dependency-path: go/go.sum

- name: Format
run: |
unformatted=$(gofmt -l .)
if [ -n "$unformatted" ]; then
echo "The following files need formatting (run 'gofmt -w .'):"
echo "$unformatted"
exit 1
fi
working-directory: ./go
if: ${{ matrix.os == 'ubuntu-latest' }}

- name: Build
run: go build ./...
working-directory: ./go

- name: Lint
run: go vet ./...
working-directory: ./go

- name: Test
run: go test ./...
working-directory: ./go
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,13 @@ lib/
package-lock.json
# ignore when switching between yarn 1/4 branches
/.yarn
rust/target
go/beachball
.claude/settings.local.json

docs/.vuepress/.cache
docs/.vuepress/.temp
docs/.yarn/*
!docs/.yarn/patches/
!docs/.yarn/releases/

7 changes: 6 additions & 1 deletion .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,15 @@
.nojekyll
.nvmrc
docs/.vuepress/dist/
**/.yarn
/change/
/CHANGELOG.*
/lib/
LICENSE
node_modules/
SECURITY.md
yarn.lock
yarn.lock
rust/**
!rust/README.md
go/**
!go/README.md
11 changes: 10 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,16 @@
"**/node_modules": true,
"**/lib": true,
"**/*.svg": true,
"**/.yarn": true
"**/.yarn": true,
"**/target": true
},
"[rust]": {
"editor.defaultFormatter": "rust-lang.rust-analyzer",
"editor.formatOnSave": true
},
"[go]": {
"editor.defaultFormatter": "golang.go",
"editor.formatOnSave": true
},
"files.associations": {
// VS Code doesn't have json5 support, so handle .json5 files as jsonc.
Expand Down
165 changes: 165 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project Overview

Beachball is a semantic version bumping tool for JS repos and monorepos. It manages change files, calculates version bumps, generates changelogs, and publishes packages to npm registries.

## Common Commands

### Building

```bash
yarn build # Compile TypeScript to lib/
```

### Testing

```bash
yarn test:all # Run all tests in order: unit, functional, then e2e
yarn test:unit # Unit tests only (__tests__ directories)
yarn test:func # Functional tests only (__functional__ directories)
yarn test:e2e # E2E tests only (__e2e__ directories)
yarn update-snapshots # Update all test snapshots
```

To run a single test file:

```bash
yarn jest path/to/test.test.ts
```

To run a single test within a file:

```bash
yarn jest path/to/test.test.ts -t "test name pattern"
```

### Linting

```bash
yarn lint # Run all linting (deps + code)
yarn lint:code # ESLint only
yarn lint:deps # Depcheck only
yarn format # Format with Prettier
```

### Final steps before PR

```bash
yarn change --type minor|patch --message "message" # Create a change file
```

## Architecture Overview

### Core Processing Flow

Beachball's bump process follows a **two-phase architecture** (calculate in-memory, then apply to disk):

1. **Configuration Layer** (`src/options/`) - Merges CLI args, repo config (beachball.config.js), and defaults into `BeachballOptions`
2. **Discovery Layer** (`src/monorepo/`) - Discovers packages, builds dependency graphs, applies scoping
3. **Validation Layer** (`src/validation/`) - Validates setup, reads change files, pre-calculates bump info
4. **Calculation Phase** (`bumpInMemory()`) - Computes all version changes without side effects
5. **Application Phase** (`performBump()`) - Writes package.json, changelogs, deletes change files
6. **Publishing Layer** (`src/publish/`) - Registry operations and git push with retries

### Key Components

**Entry Point:** `src/cli.ts`

- Single async IIFE that validates git repo, parses options, routes to commands

**Commands** (`src/commands/`):

- `change` - Interactive prompts to create change files
- `check` - Validates change files exist when needed
- `bump` - Calculates and applies version bumps (no publish/push)
- `publish` - Full workflow: bump → publish to registry → git push with tags
- `canary` - Canary/prerelease publishing
- `sync` - Synchronizes versions from registry
- `init` - Repository initialization

**Bump Logic** (`src/bump/bumpInMemory.ts`):
Multi-pass algorithm that calculates version bumps. See comments in file.

**Change Files** (`src/changefile/`):

- Change files stored in `change/` directory track intended version changes
- See `src/types/ChangeInfo.ts` `ChangeFileInfo` for the info stored in each change file. For grouped change files (config has `groupChanges: true`), the type will be `ChangeInfoMultiple`.
- Folder contains helper to read, write, and unlink change files

**Publishing** (`src/publish/`):

- `publishToRegistry()` - Validates, applies publishConfig, runs hooks, publishes (respects dependency order)
- `bumpAndPush()` - Git operations: creates temp `publish_*` branch, fetches, merges, bumps, commits, tags, pushes
- Pre/post hooks available (see `HooksOptions` in `src/types/BeachballOptions.ts`)

**Context Passing:**
`CommandContext` aggregates reusable data (packages, version groups, change files, and more) to avoid repeated calculations. See source in `src/types/CommandContext.ts`.

### Important Patterns

**Immutable-First Design:**

- `bumpInMemory` makes a copy of its input objects before making changes in memory
- Separate calculation (`bumpInMemory`) from on-disk application (`performBump`)

**Validation-First:**

- `validate()` runs before most commands to validate config and repo state
- Pre-calculates expensive operations when needed

**Package Groups:**

- Multiple packages can be versioned together (synchronized versions)
- All packages in a group receive the maximum change type
- Configured via `groups` option in beachball.config.js

**Dependent Versioning:**

- When package A changes, dependents of A can be auto-bumped
- Controlled by `dependentChangeType` in change files and `bumpDeps` option
- Propagation respects package groups

**Change Type Hierarchy:**

- Defined in `src/changefile/changeTypes.ts` `SortedChangeTypes`
- Packages, groups, and the repo config can specify `disallowedChangeTypes`

**Scoping:**

- Filters which packages participate in operations
- Based on git changes, explicit inclusion/exclusion, or package patterns
- Affects change file validation, bumping, and publishing

**Git Operations:**

- All git commands use `gitAsync()` wrapper with logging
- Push retries 5 times with fetch/merge between attempts
- Temporary branches ensure safety during publish

**Testing Structure:**

- Fixtures and helpers: `__fixtures__/` directory
- Unit tests: `__tests__/` directory
- Functional tests: `__functional__/` directory
- E2E tests: `__e2e__/` directory
- Uses Jest projects to separate test types
- Verdaccio (local npm registry) used for e2e testing
- Many of the tests cover log output since the logs are Beachball's UI, so we need to verify its correctness and readability

## TypeScript Configuration

- Current target: ES2020 (Node 14+ compatible)
- Strict mode enabled with `noUnusedLocals`

## Important Notes

- Change files (not git commits) are the source of truth for version bumps
- Beachball is not intended to have a public API (only the CLI and configuration options are supported). However, some of the command functions and `gatherBumpInfo` have been used directly by other teams, so we try to maintain compatibility with old signatures.
- Package/workspace manager is auto-detected (supports npm, yarn, pnpm, rush, lerna)

## Experimental: Rust and Go Implementations

The `rust/` and `go/` directories contain experimental parallel re-implementations. See the `go-impl` and `rust-impl` custom skills as relevant.
Loading
Loading