release

package
v0.14.0 Latest Latest
Warning

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

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

Documentation

Overview

Package release: secret-rotations subcommands.

This file ports the two bun-executed TypeScript scripts that formerly lived under .github/scripts/:

  • check-secret-rotations.ts: walks the per-secret docs, computes (today - lastRotated) for each, and opens a labelled GitHub issue when a secret is within the reminder window (or already overdue).
  • record-rotation.ts: rewrites the `lastRotated:` line in one per-secret file in place.

Both are invoked by .github/workflows/{secret-rotation- reminder.yml, record-secret-rotation.yml}. Bringing them into the existing mdsmith-release Go CLI removes the bun runtime and per-script package manifest from the repo, and lets the pure logic ride the same `go test` suite the rest of the release tooling does.

Package release provides the helper logic the release workflow invokes via cmd/mdsmith-release/. The package was originally a set of bash scripts under scripts/, but JSON/TOML editing in perl regex and indirect testing through `bash <script>` made them brittle. The Go port runs under one toolchain, has direct tests, and reports actionable errors without shelling out.

Production code constructs a Toolkit via New(); tests can substitute a fake FS via NewWithFS to exercise IO error branches the real OS cannot reliably trigger.

Index

Constants

View Source
const DevSentinel = "0.0.0-dev"

DevSentinel is the version every tracked manifest carries between releases. Stamp rewrites it to the cleaned tag during release; Check fails the version-guard CI job when a hand-edit pushes anything else to main.

View Source
const IssueLabel = "secret-rotation"

IssueLabel is the GitHub label every reminder issue carries so the `record-rotation` workflow can find and close it.

View Source
const ReminderWindowDays = 30

ReminderWindowDays is the lead time before `lastRotated + periodDays` at which the reminder workflow opens a labelled issue. Matches the value the previous TS scripts used.

View Source
const RotationsDirName = "docs/development/secret-rotations"

RotationsDirName is the path (relative to the repo root) that holds one markdown file per tracked secret. The per-secret files carry the canonical front matter consumed by both CheckSecretRotations and RecordRotation.

Variables

View Source
var PlatformPackages = []string{
	"@mdsmith/linux-x64",
	"@mdsmith/linux-arm64",
	"@mdsmith/darwin-x64",
	"@mdsmith/darwin-arm64",
	"@mdsmith/win32-x64",
}

PlatformPackages enumerates the @mdsmith/<platform> optional- dependency keys the npm root must list. Stays in lock-step with the build matrix in .github/workflows/release.yml.

Functions

func BuildNpmPlatforms

func BuildNpmPlatforms(rootDir, artifactsDir, outDir string) error

BuildNpmPlatforms delegates to a default-OS Toolkit (see Stamp).

func BuildWheels

func BuildWheels(rootDir, artifactsDir, outDir string) error

BuildWheels delegates to a default-OS Toolkit (see Stamp).

func Check

func Check(root string) error

Check delegates to a default-OS Toolkit (see Stamp).

func DaysBetween added in v0.14.0

func DaysBetween(later, earlier time.Time) int

DaysBetween returns the integer day count between two UTC midnights. Positive when later > earlier.

func ExecGH added in v0.14.0

func ExecGH(args []string) ([]byte, error)

ExecGH is the default GHRunner: shells out to `gh` on PATH. On a non-zero exit, the returned error embeds the gh stderr text so the workflow log shows what gh actually said rather than the bare "exit status N" message.

func IsISODate added in v0.14.0

func IsISODate(s string) bool

IsISODate reports whether s is a real calendar date in YYYY-MM-DD form. Round-trips the parsed components so normalized invalid dates are rejected.

func IssueBody added in v0.14.0

func IssueBody(entry RotationEntry, fileBasename string, due DueResult, env Environ) string

IssueBody renders the GitHub issue body for one due/overdue rotation. The format mirrors the previous TS implementation byte-for-byte so a switch-over does not flip existing labelled issues' bodies on the next reminder run.

func ParseFrontMatter added in v0.14.0

func ParseFrontMatter(text, path string) (map[string]any, error)

ParseFrontMatter extracts and YAML-parses the front matter block of a markdown file. The block must be fenced by `---\n` on both sides and the root must be a YAML mapping.

func RecordRotation added in v0.14.0

func RecordRotation(repoRoot, entryTitle, date string) (changed bool, err error)

RecordRotation updates the `lastRotated` field of the per-secret file whose `title` matches entryTitle. Returns (true, nil) if the file was rewritten, (false, nil) if the date was already at the requested value (no-op), or (false, err) on any validation failure.

func Stamp

func Stamp(root, version string) error

Stamp delegates to a default-OS Toolkit so callers without an explicit Toolkit (for example, the cmd binary) can stay terse. Tests that need fault injection construct a Toolkit via NewWithFS instead.

func UTCToday added in v0.14.0

func UTCToday(now time.Time) time.Time

UTCToday returns today's date as UTC midnight. Cron schedules run in UTC, so the due-state computation must match.

func UpdateLastRotated added in v0.14.0

func UpdateLastRotated(yamlBlock, date, path string) (string, error)

UpdateLastRotated rewrites the `lastRotated:` line in a YAML front-matter block to the new date. Quoting is normalized to double quotes regardless of source quoting. Returns the rewritten block, or an error if no `lastRotated:` line was found in the input.

func ValidateSemver

func ValidateSemver(v string) error

ValidateSemver rejects empty strings, leading-v tags ("v1.2.3"), and any version that isn't conforming SemVer.

Types

type CheckRotationsOptions added in v0.14.0

type CheckRotationsOptions struct {
	Now time.Time
	GH  GHRunner
	Env Environ
}

CheckRotationsOptions configures CheckSecretRotations. Now is the wall-clock time to compute due-state from; GH is the gh runner (overridable for tests). Env is the environment reader used to render issue bodies (GITHUB_SERVER_URL etc).

type CheckSecretRotationsResult added in v0.14.0

type CheckSecretRotationsResult struct {
	Opened  []string
	Skipped []string
}

CheckSecretRotationsResult records the per-run outcome the workflow log surfaces.

func CheckSecretRotations added in v0.14.0

func CheckSecretRotations(repoRoot string, opts CheckRotationsOptions) (CheckSecretRotationsResult, error)

CheckSecretRotations is the body of the scheduled reminder workflow. Walks the per-secret docs, classifies each, and opens or skips a labelled issue per entry. The `gh` CLI is shelled out via opts.GH for the existing-issue lookup and the create/label calls — same as the prior TS implementation, no new go-github dependency. Tests inject a fake GHRunner so they never touch the real `gh` binary.

type DueResult added in v0.14.0

type DueResult struct {
	Status       DueState
	DaysUntilDue int
}

DueResult pairs the classification with a signed day count. DaysUntilDue is positive while the rotation is still in the future, zero on the due date, negative once past due. Callers format the value differently per status: "due in N days" / "due today" / "OVERDUE by N days" (negate).

func ComputeDueState added in v0.14.0

func ComputeDueState(today, lastRotated time.Time, periodDays int) DueResult

ComputeDueState returns the due-state of a rotation given the current UTC date, the parsed lastRotated date, and the period.

type DueState added in v0.14.0

type DueState string

DueState classifies a rotation: not yet due, within the reminder window, or overdue.

const (
	DueOK      DueState = "ok"
	DueDue     DueState = "due"
	DueOverdue DueState = "overdue"
)

Possible DueState values returned by ComputeDueState.

type Environ added in v0.14.0

type Environ interface {
	Getenv(key string) string
}

Environ abstracts os.Getenv for testability. The default (osEnviron) reads the process environment.

type FS

type FS interface {
	// Stat returns the FileInfo for the named file, mirroring
	// os.Stat.
	Stat(name string) (os.FileInfo, error)
	// ReadFile reads the named file, mirroring os.ReadFile.
	ReadFile(name string) ([]byte, error)
	// WriteFile writes data to the named file, mirroring
	// os.WriteFile.
	WriteFile(name string, data []byte, perm fs.FileMode) error
	// ReadDir reads the named directory, mirroring os.ReadDir.
	ReadDir(name string) ([]os.DirEntry, error)
	// MkdirAll creates name and any parents, mirroring
	// os.MkdirAll.
	MkdirAll(path string, perm fs.FileMode) error
	// MkdirTemp creates a new temporary directory, mirroring
	// os.MkdirTemp.
	MkdirTemp(dir, pattern string) (string, error)
	// Rename renames (moves) oldpath to newpath, mirroring
	// os.Rename.
	Rename(oldpath, newpath string) error
	// RemoveAll removes path and any children, mirroring
	// os.RemoveAll.
	RemoveAll(path string) error
}

FS is the small filesystem surface the release toolkit uses. Production paths use osFS (delegating to the real syscalls); tests inject a fault-injecting fake to exercise IO error branches that real filesystems don't reliably trigger (mid-pipeline mkdir failure, rename target on a non-directory, disk-full WriteFile, etc.).

We intentionally keep this independent of stdlib's read-only `io/fs.FS`. The release toolkit needs Mkdir/Write/Rename alongside Read/Stat, and stdlib's interface only covers reads.

type FindEntryResult added in v0.14.0

type FindEntryResult struct {
	Path   string
	Titles []string
}

FindEntryResult is what FindEntry returns: the matched path and the sorted set of titles seen during the scan.

func FindEntry added in v0.14.0

func FindEntry(rotationsDir, entryTitle string) (FindEntryResult, error)

FindEntry locates the per-secret file whose front matter `title` matches entryTitle. Fails loudly on any malformed per-secret file and on duplicate titles across files, so a rewrite of `lastRotated` cannot mutate the wrong file or silently shadow an unrelated entry.

type FrontMatterError added in v0.14.0

type FrontMatterError struct {
	Path string
	Msg  string
}

FrontMatterError describes a malformed per-secret file. Tests match on the wrapped error's message; the path prefix gives the maintainer a clear pointer to the bad file.

func (*FrontMatterError) Error added in v0.14.0

func (e *FrontMatterError) Error() string

type GHRunner added in v0.14.0

type GHRunner func(args []string) ([]byte, error)

GHRunner is the injection point for the `gh` CLI. Production uses ExecGH (shells out to the `gh` binary on PATH); tests pass a fake function that records invocations and returns canned output. Returns the stdout bytes; on a non-zero exit the error wraps the gh stderr so workflow logs can diagnose the failure.

type LoadedRotation added in v0.14.0

type LoadedRotation struct {
	Entry        RotationEntry
	FileBasename string
}

LoadedRotation pairs a parsed entry with the base filename it came from. The filename feeds the issue body's "rotation procedure" link.

func LoadRotations added in v0.14.0

func LoadRotations(rotationsDir string) ([]LoadedRotation, error)

LoadRotations walks rotationsDir, parses every *.md file, and returns them sorted by title for deterministic iteration order.

type Manifest

type Manifest struct {
	Path         string
	Kind         ManifestKind
	OptionalDeps bool
}

Manifest is one tracked file. OptionalDeps marks the npm root — only that file carries @mdsmith/* pins.

func TrackedManifests

func TrackedManifests(root string) []Manifest

TrackedManifests is a thin wrapper over the default Toolkit so callers without an explicit Toolkit can still introspect the tracked set.

type ManifestKind

type ManifestKind int

ManifestKind tells the rewrite/check helpers which syntax to expect: JSON for npm package manifests, TOML for pyproject.

const (
	// ManifestJSON marks JSON manifests (npm package.json files).
	ManifestJSON ManifestKind = iota
	// ManifestTOML marks TOML manifests (python pyproject.toml).
	ManifestTOML
)

type MapEnviron added in v0.14.0

type MapEnviron map[string]string

MapEnviron is an in-memory Environ for tests.

func (MapEnviron) Getenv added in v0.14.0

func (m MapEnviron) Getenv(key string) string

Getenv returns the value of key in the map, or empty string.

type RotationEntry added in v0.14.0

type RotationEntry struct {
	Title       string
	LastRotated string
	PeriodDays  int
	Provider    string
	IssuerURL   string
	UsedBy      string
	Scope       string
}

RotationEntry is the parsed per-secret front matter. Field names match the YAML keys exactly (camelCase per the existing docs).

func ValidateRotationEntry added in v0.14.0

func ValidateRotationEntry(fm map[string]any, path string) (RotationEntry, error)

ValidateRotationEntry projects a front-matter map into a RotationEntry, failing loudly on missing keys, malformed lastRotated, non-integer periodDays, and non-positive periodDays. Separated from LoadRotations so the validation is reachable from unit tests without filesystem fixtures.

type Runner

type Runner interface {
	// RunCommand executes name+args in dir, with stdout/stderr
	// inherited from the calling process. Mirrors exec.Cmd.Run
	// semantics: a non-zero exit returns *exec.ExitError; other
	// failures (binary not found, IO) return their underlying
	// error.
	RunCommand(dir, name string, args ...string) error
}

Runner runs an external command. The release toolkit shells out to `python -m build` and `python -m wheel tags` for the PyPI publish path; tests inject a fake Runner to cover those branches without putting python on PATH.

type Toolkit

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

Toolkit owns a configured FS and Runner and exposes the release helpers (Stamp, Check, BuildNpmPlatforms, BuildWheels) as methods. `New()` returns a Toolkit backed by the real OS for both; tests can use `NewWithFS(fakeFS)` or `NewWithDeps` to drive error paths that the OS does not expose.

func New

func New() *Toolkit

New returns a Toolkit backed by the real OS filesystem and command runner.

func NewWithDeps

func NewWithDeps(fsys FS, runner Runner) *Toolkit

NewWithDeps returns a Toolkit with custom FS and Runner. Used by tests that exercise both IO and command-execution faults.

func NewWithFS

func NewWithFS(fsys FS) *Toolkit

NewWithFS returns a Toolkit with a custom FS and the OS-backed Runner. Convenience helper for tests that only need IO faults.

func (*Toolkit) BuildNpmPlatforms

func (t *Toolkit) BuildNpmPlatforms(rootDir, artifactsDir, outDir string) error

BuildNpmPlatforms emits one ready-to-publish npm sub-package directory per supported platform under outDir, copying the matching release artifact from artifactsDir. Stamp must run first because the version is taken from rootDir/npm/mdsmith/package.json.

func (*Toolkit) BuildWheels

func (t *Toolkit) BuildWheels(rootDir, artifactsDir, outDir string) error

BuildWheels builds one platform-tagged wheel per supported host from prebuilt binaries in artifactsDir, writing the wheels to outDir. The python source tree at rootDir/python is staged per build with the matching binary embedded under mdsmith/_bin/, then `python -m build` produces a py3-none-any wheel which `python -m wheel tags` retags to the correct platform tag (in both the filename and the dist-info/WHEEL metadata).

Requires `python -m build`, `python -m wheel`, and the hatchling build backend on PATH. Stamp must run first so pyproject.toml carries the published version.

func (*Toolkit) Check

func (t *Toolkit) Check(root string) error

Check accumulates every problem with the tracked manifests (missing file, missing field, drifted version, missing or drifted @mdsmith/* pin) into one multi-line error. The version-guard CI job uses Check; reporting all problems at once is more useful than failing on the first.

func (*Toolkit) Stamp

func (t *Toolkit) Stamp(root, version string) error

Stamp rewrites every tracked manifest under root from the dev sentinel to version. Idempotent: running with the same version twice produces no further change. A required manifest that's missing a version field — or, for the npm root, missing the @mdsmith/* optionalDependencies block — is a hard error.

func (*Toolkit) TrackedManifests

func (t *Toolkit) TrackedManifests(root string) []Manifest

TrackedManifests returns the set of manifests Stamp rewrites and Check verifies via the Toolkit's FS. The npm/platforms/ directory is not in source control — BuildNpmPlatforms emits the platform sub-packages under its own outDir argument (npm/dist in the release workflow). Anything that has already materialised npm/platforms/<plat>/package.json (a previous local Stamp+BuildNpmPlatforms invocation, a custom staging step) is stamped here too; if the directory is absent the helper just returns the four checked-in manifests.

Jump to

Keyboard shortcuts

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