plan

package
v1.0.0 Latest Latest
Warning

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

Go to latest
Published: May 4, 2026 License: MIT Imports: 7 Imported by: 0

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.

Jump to

Keyboard shortcuts

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