release

package
v0.0.4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 7, 2026 License: MIT Imports: 11 Imported by: 0

Documentation

Overview

Package release provides version management, changelog generation, git helpers, and plugin registry operations for pasture-release.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func CompareVersions added in v0.0.3

func CompareVersions(a, b string) (int, error)

CompareVersions parses two bare "X.Y.Z" semver strings and compares them. It returns -1 when a < b, 0 when equal, and +1 when a > b.

It returns an actionable error naming WHICH side failed to parse when either string is not a valid semver, so callers reconciling a plugin.json version against a marketplace entry can report exactly which manifest is malformed.

func DefaultRegistryPath

func DefaultRegistryPath() string

DefaultRegistryPath returns the user-global plugin registry path.

func GenerateChangelog

func GenerateChangelog(commits []ConventionalCommit, version SemVer) string

GenerateChangelog generates a Keep-a-Changelog markdown entry from commits. Commits that do not parse as conventional commits are placed in "Other".

func GitAllCommits

func GitAllCommits(dir string) ([]string, error)

GitAllCommits returns the subject lines of all commits reachable from HEAD.

func GitCommit

func GitCommit(dir string, files []string, message string) error

GitCommit stages files and creates a commit with message in dir.

func GitCommitsSince

func GitCommitsSince(dir, ref string) ([]string, error)

GitCommitsSince returns the subject lines of commits since ref (exclusive).

func GitIsDetachedHead

func GitIsDetachedHead(dir string) (bool, error)

GitIsDetachedHead reports whether the repository at dir has a detached HEAD (i.e. HEAD points at a commit rather than a branch). It mirrors aura-release's is_detached_head, which runs `git symbolic-ref --quiet HEAD`.

The probe distinguishes three exit states, because not every non-zero exit means "detached":

  • exit 0 → HEAD resolves to a branch ref → (false, nil)
  • exit 1 → HEAD is valid but resolves to no branch → (true, nil) detached
  • exit 128 → not a git repository (or git unusable) → (false, nil)

Exit 128 is deliberately treated as "not detached" rather than an error: the caller's subsequent working-tree validation (GitStatus) surfaces genuine "not a git repo" problems for real releases, while dry-run flows that operate on a non-repo directory must not be blocked by this pre-flight guard. Only a genuine detached HEAD (exit 1) should block a release.

func GitLatestVersionTag

func GitLatestVersionTag(dir string) (string, error)

GitLatestVersionTag returns the most recent vX.Y.Z tag reachable from HEAD, or ("", nil) if none exists.

func GitRollback

func GitRollback(dir, tag string) error

GitRollback restores the named tag and resets modified files to HEAD. Used to undo a partial release on error.

func GitStatus

func GitStatus(dir string) (string, error)

GitStatus returns the short status output for the repository at dir.

func GitTag

func GitTag(dir, tag, message string) error

GitTag creates an annotated git tag at HEAD in dir.

func ReadPluginVersion added in v0.0.3

func ReadPluginVersion(path, pluginName string) (string, error)

ReadPluginVersion reads the version of a single entry in a marketplace.json "plugins" array, matched by plugin name, WITHOUT touching "metadata.version" (the marketplace's own version).

It is the read-side counterpart to WritePluginVersion: sync-versions uses it to learn the version the cross-repo marketplace currently advertises for a plugin (mv) so it can be compared against the plugin's own .claude-plugin/plugin.json version (pv).

Parameters:

  • path: absolute path to the marketplace.json to read.
  • pluginName: the "name" field of the plugins[] entry to read.

It returns an actionable error when: the file cannot be read; the JSON is malformed; the top-level "plugins" array is missing or not an array; the matched entry has no string "version" field; or no entry with the given name exists (the absent-name error lists the available entries).

func RunRelease

func RunRelease(opts ReleaseOptions) error

RunRelease executes the full release workflow for a single repository.

Workflow:

  1. Discover version files.
  2. Pre-flight: refuse to release from a detached HEAD.
  3. Validate working tree (unless dry-run).
  4. Optionally sync version drift.
  5. Bump version across all files.
  6. Generate changelog.
  7. Git commit.
  8. Git tag.
  9. Optionally sync a plugin's entry in a cross-repo marketplace (--plugin).

func TopologicalSort

func TopologicalSort(nodes []string, edges map[string][]string) ([]string, error)

TopologicalSort performs a Kahn's-algorithm topological sort over a generic dependency graph. Returns an error if a cycle is detected.

nodes is the set of node IDs; edges maps each node to its dependencies (things that must be released before it).

func WritePluginVersion

func WritePluginVersion(path, pluginName, version string, dryRun bool) error

WritePluginVersion sets the version of a single entry in a marketplace.json "plugins" array, matched by plugin name, leaving "metadata.version" (the marketplace's own version) completely untouched.

This is the per-plugin counterpart to MarketplaceVersionFile.Write (which only touches metadata.version). It exists so a release in one repository can keep a CROSS-REPO marketplace's plugins[<name>] entry in sync — e.g. the parent aura-plugins marketplace tracks pasture as plugins[pasture] while pasture itself lives in a separate submodule.

Parameters:

  • path: absolute path to the marketplace.json to update.
  • pluginName: the "name" field of the plugins[] entry to update.
  • version: the new version string to write into that entry.
  • dryRun: when true, prints the intended change and writes nothing.

It returns an actionable error when: the file cannot be read; the JSON is malformed; the top-level "plugins" array is missing or not an array; or no entry with the given name exists.

Types

type ConventionalCommit

type ConventionalCommit struct {
	// Type is the commit type (feat, fix, refactor, …).
	Type string
	// Scope is the optional scope in parentheses (may be empty).
	Scope string
	// Description is the rest of the subject line.
	Description string
	// Raw is the original unmodified subject line.
	Raw string
}

ConventionalCommit is a parsed conventional commit message.

func ParseConventionalCommit

func ParseConventionalCommit(message string) (*ConventionalCommit, error)

ParseConventionalCommit parses a conventional commit subject line. Returns an error when the line does not match the "type: description" format.

type DriftAction added in v0.0.3

type DriftAction int

DriftAction is the strongly-typed action a single VersionDrift entry represents. The CLI preview switches on Action (not on a Got/Want comparison) to render the correct line, per the canonical sync-versions output format.

const (
	// DriftWriteFile is an intra-plugin version-file fix: a non-canonical
	// version file in the plugin is rewritten to the plugin's canonical version.
	DriftWriteFile DriftAction = iota
	// DriftWriteMarketplace pushes the plugin's plugin.json version into the
	// cross-repo marketplace entry (plugin newer than marketplace; pv > mv).
	DriftWriteMarketplace
	// DriftPullPlugin git-pulls the plugin repo because the marketplace
	// advertises a newer released version than the local checkout (mv > pv).
	DriftPullPlugin
	// DriftConsistent is a display-only, no-op entry: the plugin's plugin.json
	// version already matches its marketplace entry (pv == mv). It is emitted so
	// the reconciliation preview can render the FULL roster (every registered
	// plugin, not just drifted ones), but it is NEVER an action — it is excluded
	// from the pending-change count and is never written or pulled.
	DriftConsistent
)

func (DriftAction) IsChange added in v0.0.3

func (a DriftAction) IsChange() bool

IsChange reports whether a VersionDrift represents an actionable pending change (a marketplace write, a plugin pull, or an intra-plugin file fix) as opposed to a display-only DriftConsistent row. The CLI counts only changes for the "N change(s) pending" footer and the apply/no-op gating.

type JsonVersionFile

type JsonVersionFile struct {
	// contains filtered or unexported fields
}

JsonVersionFile reads/writes the top-level "version" field of a JSON file. Used for package.json and .claude-plugin/plugin.json.

func NewJsonVersionFile

func NewJsonVersionFile(name, path string) *JsonVersionFile

NewJsonVersionFile constructs a JsonVersionFile.

func (*JsonVersionFile) Name

func (f *JsonVersionFile) Name() string

func (*JsonVersionFile) Path

func (f *JsonVersionFile) Path() string

func (*JsonVersionFile) Read

func (f *JsonVersionFile) Read() (string, error)

func (*JsonVersionFile) Write

func (f *JsonVersionFile) Write(version string, dryRun bool) error

type MarketplaceEntry

type MarketplaceEntry struct {
	Path    string        `json:"path"`
	Plugins []PluginEntry `json:"plugins"`
}

MarketplaceEntry groups plugins that share a marketplace.json.

type MarketplaceVersionFile

type MarketplaceVersionFile struct {
	// contains filtered or unexported fields
}

MarketplaceVersionFile reads/writes the metadata.version field in a marketplace.json file.

func NewMarketplaceVersionFile

func NewMarketplaceVersionFile(name, path string) *MarketplaceVersionFile

NewMarketplaceVersionFile constructs a MarketplaceVersionFile.

func (*MarketplaceVersionFile) Name

func (f *MarketplaceVersionFile) Name() string

func (*MarketplaceVersionFile) Path

func (f *MarketplaceVersionFile) Path() string

func (*MarketplaceVersionFile) Read

func (f *MarketplaceVersionFile) Read() (string, error)

func (*MarketplaceVersionFile) Write

func (f *MarketplaceVersionFile) Write(version string, dryRun bool) error

type PluginEntry

type PluginEntry struct {
	Name   string `json:"name"`
	Path   string `json:"path"`
	Remote string `json:"remote"`
}

PluginEntry describes a single registered plugin.

type PluginRegistry

type PluginRegistry struct {
	Marketplaces []MarketplaceEntry `json:"marketplaces"`
	// contains filtered or unexported fields
}

PluginRegistry is the top-level registry structure stored on disk.

func (*PluginRegistry) Exec

func (r *PluginRegistry) Exec(cmd string, args ...string) error

Exec runs cmd with args in each registered plugin directory. It prints each working directory before running and collects all errors.

func (*PluginRegistry) FindPlugin

func (r *PluginRegistry) FindPlugin(name, cwd string) (*PluginEntry, *MarketplaceEntry)

FindPlugin finds a plugin by name or (when name is empty) by matching the resolved absolute path against cwd. Returns nil, nil when not found.

func (*PluginRegistry) Load

func (r *PluginRegistry) Load(path string) error

Load reads the registry from path. Returns an empty registry (not nil) when the file does not exist yet.

func (*PluginRegistry) ReleaseOrder

func (r *PluginRegistry) ReleaseOrder() ([]PluginEntry, error)

ReleaseOrder returns the plugins in topological dependency order (leaves first). Currently there is no explicit dependency graph in the registry, so all plugins are returned in their declaration order. Cycle detection is included for future use when dependency edges are added.

Each plugin entry is returned once; the result is safe to release in order.

func (*PluginRegistry) Save

func (r *PluginRegistry) Save(path string, dryRun bool) error

Save writes the registry to path. When dryRun is true, the JSON is printed but not written.

func (*PluginRegistry) SyncVersions

func (r *PluginRegistry) SyncVersions(dryRun bool) ([]VersionDrift, error)

SyncVersions detects every pending version change across all registered plugins and, when dryRun is false, applies them.

It performs two independent reconciliations per plugin:

  1. INTRA-PLUGIN file drift — the canonical version (first discovered file, usually pyproject.toml) is propagated to any sibling version file that disagrees (DriftWriteFile).

  2. CROSS-REPO marketplace reconciliation (NEWEST-WINS) — the plugin's own .claude-plugin/plugin.json version (pv, selected EXPLICITLY) is compared against the version the registered marketplace advertises for it (mv). pv > mv writes the marketplace entry (DriftWriteMarketplace); mv > pv fast-forward-pulls the plugin repo (DriftPullPlugin); pv == mv is a no-op. The marketplace's own metadata.version is NEVER touched.

When dryRun is true, nothing is written or pulled — the returned slice is the plan of pending changes. Per-plugin errors (e.g. a malformed manifest or a non-fast-forwardable pull) are aggregated: the offending plugin is skipped and the others continue; the aggregated error is returned alongside the drift that was still detected.

func (*PluginRegistry) WithGitPull added in v0.0.3

func (r *PluginRegistry) WithGitPull(fn func(dir string) error) *PluginRegistry

WithGitPull injects the function SyncVersions uses to pull a plugin repo when the marketplace is ahead of the local checkout (PULL_PLUGIN). It mutates the receiver and returns it for chaining, so it composes with struct-literal construction:

var r release.PluginRegistry
r.WithGitPull(func(dir string) error { pulled = dir; return nil })

Passing nil restores the lazy default (defaultGitPull).

func (*PluginRegistry) WithOutput added in v0.0.3

func (r *PluginRegistry) WithOutput(w io.Writer) *PluginRegistry

WithOutput sets the writer SyncVersions uses for non-error progress/warning output. It mutates the receiver and returns it for chaining. Passing nil restores the lazy default (os.Stdout).

type PyprojectVersionFile

type PyprojectVersionFile struct {
	// contains filtered or unexported fields
}

PyprojectVersionFile reads/writes the version in a pyproject.toml [project] section.

func NewPyprojectVersionFile

func NewPyprojectVersionFile(name, path string) *PyprojectVersionFile

NewPyprojectVersionFile constructs a PyprojectVersionFile.

func (*PyprojectVersionFile) Name

func (f *PyprojectVersionFile) Name() string

func (*PyprojectVersionFile) Path

func (f *PyprojectVersionFile) Path() string

func (*PyprojectVersionFile) Read

func (f *PyprojectVersionFile) Read() (string, error)

func (*PyprojectVersionFile) Write

func (f *PyprojectVersionFile) Write(version string, dryRun bool) error

type ReleaseOptions

type ReleaseOptions struct {
	// BumpKind specifies which semver component to increment (major, minor, patch).
	BumpKind types.BumpKind
	// DryRun, when true, prints what would happen without making changes.
	DryRun bool
	// Sync, when true, aligns all version files to the canonical before bumping.
	Sync bool
	// NoChangelog skips changelog generation.
	NoChangelog bool
	// NoCommit skips git commit.
	NoCommit bool
	// NoTag skips git tag.
	NoTag bool
	// RepoRoot is the absolute path to the repository root.
	RepoRoot string
	// Plugin, when non-empty, names the plugin whose entry in a registered
	// CROSS-REPO marketplace.json should be synced to the new version AFTER
	// the in-repo commit/tag succeed (mirrors aura-release --plugin). The
	// marketplace path is looked up via the plugin registry (see RegistryPath).
	Plugin string
	// RegistryPath overrides the plugin registry location used to resolve the
	// cross-repo marketplace path for Plugin. When empty, DefaultRegistryPath()
	// is used. Exposed primarily so integration tests can point at a temp
	// registry; production callers leave it empty.
	RegistryPath string
}

ReleaseOptions controls the release workflow for a single repository.

type SemVer

type SemVer struct {
	Major int
	Minor int
	Patch int
}

SemVer represents a semantic version number (major.minor.patch).

func ParseSemVer

func ParseSemVer(s string) (SemVer, error)

ParseSemVer parses a semver string of the form "X.Y.Z". Returns an error if the string does not match.

func (SemVer) Bump

func (v SemVer) Bump(kind types.BumpKind) SemVer

Bump returns a new SemVer with the specified component incremented. Minor and Patch are reset to 0 on Major bump; Patch is reset on Minor bump.

func (SemVer) Compare added in v0.0.3

func (v SemVer) Compare(o SemVer) int

Compare orders v against o by (Major, Minor, Patch). It returns -1 when v < o, 0 when equal, and +1 when v > o.

func (SemVer) String

func (v SemVer) String() string

String returns the dotted "X.Y.Z" representation.

type VersionDrift

type VersionDrift struct {
	Plugin string
	File   string
	Want   string // target/winning version
	Got    string // current/losing version
	Action DriftAction

	// PluginVersion (pv) and MarketplaceVersion (mv) are populated only for the
	// cross-repo marketplace reconciliation actions.
	PluginVersion      string
	MarketplaceVersion string
}

VersionDrift describes a single pending change detected by SyncVersions.

For DriftWriteFile (intra-plugin), File is the drifted version file, Got its current version, and Want the canonical version it will be rewritten to.

For DriftWriteMarketplace / DriftPullPlugin (cross-repo marketplace reconciliation), PluginVersion (pv, from .claude-plugin/plugin.json) and MarketplaceVersion (mv, from the marketplace entry) carry the two compared versions, File names the artefact that changes (the marketplace.json for a write, the plugin repo dir for a pull), and Want/Got mirror the winning and losing version for uniform reporting.

type VersionFile

type VersionFile interface {
	// Name returns the display name / relative path of the file.
	Name() string
	// Path returns the absolute filesystem path.
	Path() string
	// Read extracts the current version string from the file.
	Read() (string, error)
	// Write persists a new version string to the file.
	// When dryRun is true, the change is printed but not applied.
	Write(version string, dryRun bool) error
}

VersionFile is implemented by any file that stores a semver version string. Each implementation knows how to locate the version within its own format.

func DiscoverVersionFiles

func DiscoverVersionFiles(root string) ([]VersionFile, error)

DiscoverVersionFiles auto-discovers all version-bearing files under root. For each spec with subdirs=true, root is checked first, then immediate child directories (hidden dirs and skip dirs are excluded).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL