Documentation
¶
Overview ¶
Package release provides version management, changelog generation, git helpers, and plugin registry operations for pasture-release.
Index ¶
- func CompareVersions(a, b string) (int, error)
- func DefaultRegistryPath() string
- func GenerateChangelog(commits []ConventionalCommit, version SemVer) string
- func GitAllCommits(dir string) ([]string, error)
- func GitCommit(dir string, files []string, message string) error
- func GitCommitsSince(dir, ref string) ([]string, error)
- func GitIsDetachedHead(dir string) (bool, error)
- func GitLatestVersionTag(dir string) (string, error)
- func GitRollback(dir, tag string) error
- func GitStatus(dir string) (string, error)
- func GitTag(dir, tag, message string) error
- func ReadPluginVersion(path, pluginName string) (string, error)
- func RunRelease(opts ReleaseOptions) error
- func TopologicalSort(nodes []string, edges map[string][]string) ([]string, error)
- func WritePluginVersion(path, pluginName, version string, dryRun bool) error
- type ConventionalCommit
- type DriftAction
- type JsonVersionFile
- type MarketplaceEntry
- type MarketplaceVersionFile
- type PluginEntry
- type PluginRegistry
- func (r *PluginRegistry) Exec(cmd string, args ...string) error
- func (r *PluginRegistry) FindPlugin(name, cwd string) (*PluginEntry, *MarketplaceEntry)
- func (r *PluginRegistry) Load(path string) error
- func (r *PluginRegistry) ReleaseOrder() ([]PluginEntry, error)
- func (r *PluginRegistry) Save(path string, dryRun bool) error
- func (r *PluginRegistry) SyncVersions(dryRun bool) ([]VersionDrift, error)
- func (r *PluginRegistry) WithGitPull(fn func(dir string) error) *PluginRegistry
- func (r *PluginRegistry) WithOutput(w io.Writer) *PluginRegistry
- type PyprojectVersionFile
- type ReleaseOptions
- type SemVer
- type VersionDrift
- type VersionFile
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func CompareVersions ¶ added in v0.0.3
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 ¶
GitAllCommits returns the subject lines of all commits reachable from HEAD.
func GitCommitsSince ¶
GitCommitsSince returns the subject lines of commits since ref (exclusive).
func GitIsDetachedHead ¶
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 ¶
GitLatestVersionTag returns the most recent vX.Y.Z tag reachable from HEAD, or ("", nil) if none exists.
func GitRollback ¶
GitRollback restores the named tag and resets modified files to HEAD. Used to undo a partial release on error.
func ReadPluginVersion ¶ added in v0.0.3
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:
- Discover version files.
- Pre-flight: refuse to release from a detached HEAD.
- Validate working tree (unless dry-run).
- Optionally sync version drift.
- Bump version across all files.
- Generate changelog.
- Git commit.
- Git tag.
- Optionally sync a plugin's entry in a cross-repo marketplace (--plugin).
func TopologicalSort ¶
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 ¶
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)
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)
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:
INTRA-PLUGIN file drift — the canonical version (first discovered file, usually pyproject.toml) is propagated to any sibling version file that disagrees (DriftWriteFile).
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)
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 ¶
SemVer represents a semantic version number (major.minor.patch).
func ParseSemVer ¶
ParseSemVer parses a semver string of the form "X.Y.Z". Returns an error if the string does not match.
func (SemVer) Bump ¶
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.
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).