Documentation
¶
Overview ¶
Package plan computes a ReleasePlan from the static inputs that drive a monorel release: the parsed monorel.toml, the set of pending changesets, and the list of existing git tags.
Plan is a pure function. It performs no I/O; callers wire git and filesystem reads into the inputs. This is deliberate: the planner is the load-bearing logic of the tool, and "pure function over plain data" is the cheapest way to give it exhaustive table-driven test coverage.
Higher layers feed Plan's output to the changelog writer, the release applier, and the GitHub Action orchestrator. None of those layers re-derive versions from changesets — the plan is the single source of truth for "what should release."
Index ¶
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func LatestStableTagVersion ¶ added in v0.10.0
func LatestStableTagVersion(tags []string, pkg config.PackageConfig) (string, bool)
LatestStableTagVersion finds the highest semver-valid, non-pre-release tag for pkg's configured tag_prefix and returns its version part (e.g. "v1.6.2"). The bool reports whether any qualifying tag existed.
Tags that don't parse as semver are silently ignored; pre-release tags are ignored at this layer (pre-release-mode handling lives in a layer above the planner).
Types ¶
type PackageRelease ¶
type PackageRelease struct {
// Name is the package name as it appears in monorel.toml's
// [packages] table and in changeset frontmatter keys.
Name string
// Config is the corresponding [config.PackageConfig] copy. The
// release applier reads Path, Changelog, and TagPrefix from it.
Config config.PackageConfig
// From is the previous version this release bumps from, or "" if
// the package has never been released. Excludes any pre-release
// suffix; see [Plan]'s "pre-release tags" note.
From string
// To is the new version this release produces. Includes the
// pre-release suffix when [PackageRelease.Prerelease] is true,
// e.g. "v1.7.0-rc.2".
To string
// Bump is the level computed from the package's affecting
// changesets (max across them).
Bump semver.BumpLevel
// Tag is the full git tag To corresponds to under the package's
// configured tag_prefix, e.g. "transports/zerolog/v1.7.0" or
// "v1.7.0" for a bare-tag root module.
Tag string
// Changesets are the changesets that contributed to this
// release, sorted by name. A given changeset may appear in
// multiple PackageRelease.Changesets if it targets multiple
// packages.
Changesets []*changeset.Changeset
// Initial is true when this is the package's first release (no
// prior tag exists for its tag_prefix).
Initial bool
// Prerelease is true when this release is part of a pre-release
// window (a [changeset.PreState] was passed to [Plan]). The
// release applier increments the per-package counter on the
// PreState after a successful release.
Prerelease bool
// PrereleaseCounter is the counter value baked into [To] when
// Prerelease is true. Zero for a stable release.
PrereleaseCounter int
}
PackageRelease is one package's slice of a ReleasePlan.
type ReleasePlan ¶
type ReleasePlan struct {
// Releases is one entry per package that has at least one
// affecting changeset, sorted by package name. Packages with no
// changesets don't appear.
Releases []PackageRelease
// Consumed is the set of changesets the plan covers. Sorted by
// changeset name. The release applier deletes the corresponding
// .changeset/<name>.md files after committing the release.
//
// A single changeset that targets multiple packages appears once
// here, not once per affected package.
Consumed []*changeset.Changeset
}
ReleasePlan is the result of Plan: every package that needs to be released this run, with its computed version transition and the changesets that contributed.
A plan with no Releases is a valid no-op outcome: it means there are no pending changesets, or every changeset was a no-op for some reason. Callers handle this case (close the release PR, exit 0).
func Plan ¶
func Plan(cfg *config.Config, changesets []*changeset.Changeset, tags []string, pre *changeset.PreState) (*ReleasePlan, error)
Plan computes the ReleasePlan from the given inputs.
Inputs:
- cfg: the parsed monorel.toml. Must already be validated.
- changesets: pending changesets (typically loaded from .changeset/ via changeset.LoadAll).
- tags: every git tag in the repository, regardless of prefix. Tags that aren't valid semver are silently ignored. Pre-release tags (e.g. "v1.0.0-rc.1") are also ignored when picking the "current stable version" for a package — pre-release flow uses pre to override From/To after the stable computation.
- pre: optional pre-release state (nil means stable-mode planning). When non-nil, To is suffixed with "-<channel>.<counter>" and the [PackageRelease.Prerelease] / [PackageRelease.PrereleaseCounter] fields are populated. The counter for each package is read from pre.Counters; the planner does NOT mutate pre (the release applier increments after a successful release).
Errors:
- A changeset names a package not in cfg.Packages.
- InitialFromBump fails (shouldn't happen with valid bump levels).
- semver.Apply fails (shouldn't happen with valid existing tags).
Determinism: Releases and Consumed are returned in sorted order so repeated calls with the same inputs produce identical output.
Example ¶
ExamplePlan computes the next-release plan from the three static inputs that drive every monorel run: the parsed monorel.toml, the pending changesets, and the existing tag list. The result is a pure function of these inputs; nothing in the plan package touches the filesystem or the network.
package main
import (
"fmt"
"strings"
"monorel.disaresta.com/changeset"
"monorel.disaresta.com/config"
"monorel.disaresta.com/plan"
)
func main() {
cfg := &config.Config{
Provider: config.ProviderConfig{Name: "github", Owner: "acme", Repo: "widget"},
Packages: map[string]config.PackageConfig{
"transports/zerolog": {
TagPrefix: "transports/zerolog",
Path: "transports/zerolog",
Changelog: "transports/zerolog/CHANGELOG.md",
},
},
}
cs, _ := changeset.Parse(strings.NewReader(`---
"transports/zerolog": minor
---
Adds Lazy() helper.
`), "quick-otter")
tags := []string{"transports/zerolog/v1.6.0", "transports/zerolog/v1.6.1"}
p, err := plan.Plan(cfg, []*changeset.Changeset{cs}, tags, nil)
if err != nil {
fmt.Println("error:", err)
return
}
for _, r := range p.Releases {
fmt.Printf("%s: %s -> %s (tag %s)\n", r.Name, r.From, r.To, r.Tag)
}
}
Output: transports/zerolog: v1.6.1 -> v1.7.0 (tag transports/zerolog/v1.7.0)
Example (PreRelease) ¶
ExamplePlan_preRelease shows the planner under pre-release mode. When a changeset.PreState is supplied, the planner appends a channel suffix and increments the per-package counter rather than producing the next stable version. Counters are read-only here; the release applier writes them back. This is the path that powers `monorel pre enter rc` -> `monorel release` cycles.
package main
import (
"fmt"
"strings"
"monorel.disaresta.com/changeset"
"monorel.disaresta.com/config"
"monorel.disaresta.com/plan"
)
func main() {
cfg := &config.Config{
Provider: config.ProviderConfig{Name: "github", Owner: "acme", Repo: "widget"},
Packages: map[string]config.PackageConfig{
"transports/zerolog": {
TagPrefix: "transports/zerolog",
Path: "transports/zerolog",
Changelog: "transports/zerolog/CHANGELOG.md",
},
},
}
cs, _ := changeset.Parse(strings.NewReader(`---
"transports/zerolog": minor
---
Adds Lazy() helper.
`), "quick-otter")
tags := []string{"transports/zerolog/v1.6.1"}
pre := &changeset.PreState{
Mode: "pre",
Channel: "rc",
Counters: map[string]int{"transports/zerolog": 0},
}
p, err := plan.Plan(cfg, []*changeset.Changeset{cs}, tags, pre)
if err != nil {
fmt.Println("error:", err)
return
}
for _, r := range p.Releases {
fmt.Printf("%s: -> %s (tag %s)\n", r.Name, r.To, r.Tag)
}
}
Output: transports/zerolog: -> v1.7.0-rc.0 (tag transports/zerolog/v1.7.0-rc.0)
func (*ReleasePlan) IsEmpty ¶
func (p *ReleasePlan) IsEmpty() bool
IsEmpty reports whether the plan has nothing to release.