Upgrade ReleaseKit and simplify release tooling

This commit is contained in:
2026-10-09 04:09:26 +00:00
parent 11c349d9ae
commit 6debe7e641
10 changed files with 147 additions and 247 deletions

View File

@@ -1,6 +1,6 @@
matrix: matrix:
include: include:
- GO_IMAGE: golang:1.25.5 - GO_IMAGE: golang:1.26.8-bookworm
when: when:
- event: tag - event: tag

View File

@@ -6,7 +6,7 @@ reports for review and subprocess integration.
## Quickstart ## Quickstart
From a checkout, build with Go 1.25.5 or newer: From a checkout, build with Go 1.26.0 or newer:
```sh ```sh
go build -o ./bin/audita ./cmd/audita go build -o ./bin/audita ./cmd/audita

View File

@@ -41,7 +41,9 @@ ownership. Commit discipline and agent Git restrictions are in the
## Baseline Validation ## Baseline Validation
Use Go 1.25.5 or newer. From the repository root: Use Go 1.26.0 or newer for source builds. The selected release toolchain is
documented in the [release procedure](release.md#prerequisites). From the
repository root:
```sh ```sh
go build -o ./bin/audita ./cmd/audita go build -o ./bin/audita ./cmd/audita

View File

@@ -38,7 +38,7 @@ License and notices: [LGPL v3](licenses/LGPL-3.0.txt) |
## ReleaseKit ## ReleaseKit
Selected module: `gitea.maximumdirect.net/eric/ReleaseKit` at `v0.1.0`. Selected module: `gitea.maximumdirect.net/eric/ReleaseKit` at `v0.3.0`.
Copyright (C) 2026 Eric Rakestraw Copyright (C) 2026 Eric Rakestraw
@@ -46,79 +46,79 @@ SPDX-License-Identifier: LGPL-3.0-or-later
License and notices: [LGPL v3](licenses/LGPL-3.0.txt) | License and notices: [LGPL v3](licenses/LGPL-3.0.txt) |
[GPL v3](licenses/GPL-3.0.txt) | [GPL v3](licenses/GPL-3.0.txt) |
[COPYRIGHT](https://gitea.maximumdirect.net/eric/ReleaseKit/src/commit/5e60fe3113c69f95c42fbbe32a8ca4b51f5b83c1/COPYRIGHT). [COPYRIGHT](https://gitea.maximumdirect.net/eric/ReleaseKit/src/commit/e43df4416e0ed987349b2ea04a2c68c25e552772/COPYRIGHT).
[Source](https://gitea.maximumdirect.net/eric/ReleaseKit/src/commit/5e60fe3113c69f95c42fbbe32a8ca4b51f5b83c1). [Source](https://gitea.maximumdirect.net/eric/ReleaseKit/src/commit/e43df4416e0ed987349b2ea04a2c68c25e552772).
## Go runtime and standard library ## Go runtime and standard library
Selected toolchain: Go 1.25.5 for the distributed Linux binaries. Selected toolchain: Go 1.26.8 for the distributed Linux binaries.
Copyright 2009 The Go Authors. Copyright 2009 The Go Authors.
SPDX-License-Identifier: BSD-3-Clause SPDX-License-Identifier: BSD-3-Clause
License and notices: [BSD 3-Clause](licenses/BSD-3-Clause-Go.txt) | License and notices: [BSD 3-Clause](licenses/BSD-3-Clause-Go.txt) |
[LICENSE](https://github.com/golang/go/blob/fefb02adf45c4bcc879bd406a8d61f2a292c26a9/LICENSE) | [LICENSE](https://github.com/golang/go/blob/c293dd49cbe25e1fe8d97d94a5cb618e7b6d831e/LICENSE) |
[PATENTS](https://github.com/golang/go/blob/fefb02adf45c4bcc879bd406a8d61f2a292c26a9/PATENTS). [PATENTS](https://github.com/golang/go/blob/c293dd49cbe25e1fe8d97d94a5cb618e7b6d831e/PATENTS).
[Source](https://github.com/golang/go/commit/fefb02adf45c4bcc879bd406a8d61f2a292c26a9). [Source](https://github.com/golang/go/commit/c293dd49cbe25e1fe8d97d94a5cb618e7b6d831e).
## Go vendored golang.org/x/crypto ## Go vendored golang.org/x/crypto
Material: `golang.org/x/crypto` at `v0.39.0` in the Go 1.25.5 standard library. Material: `golang.org/x/crypto` at `v0.46.1-0.20251210140736-7dacc380ba00` in the Go 1.26.8 standard library.
Copyright 2009 The Go Authors. Copyright 2009 The Go Authors.
SPDX-License-Identifier: BSD-3-Clause SPDX-License-Identifier: BSD-3-Clause
License and notices: [BSD 3-Clause](licenses/BSD-3-Clause-Go.txt) | License and notices: [BSD 3-Clause](licenses/BSD-3-Clause-Go.txt) |
[LICENSE](https://github.com/golang/go/blob/fefb02adf45c4bcc879bd406a8d61f2a292c26a9/src/vendor/golang.org/x/crypto/LICENSE) | [LICENSE](https://github.com/golang/go/blob/c293dd49cbe25e1fe8d97d94a5cb618e7b6d831e/src/vendor/golang.org/x/crypto/LICENSE) |
[PATENTS](https://github.com/golang/go/blob/fefb02adf45c4bcc879bd406a8d61f2a292c26a9/src/vendor/golang.org/x/crypto/PATENTS). [PATENTS](https://github.com/golang/go/blob/c293dd49cbe25e1fe8d97d94a5cb618e7b6d831e/src/vendor/golang.org/x/crypto/PATENTS).
[Source](https://github.com/golang/go/commit/fefb02adf45c4bcc879bd406a8d61f2a292c26a9). [Source](https://github.com/golang/go/commit/c293dd49cbe25e1fe8d97d94a5cb618e7b6d831e).
## Go vendored golang.org/x/net ## Go vendored golang.org/x/net
Material: `golang.org/x/net` at `v0.41.0` in the Go 1.25.5 standard library. Material: `golang.org/x/net` at `v0.47.1-0.20260731170545-cb1d721f7fb3` in the Go 1.26.8 standard library.
Copyright 2009 The Go Authors. Copyright 2009 The Go Authors.
SPDX-License-Identifier: BSD-3-Clause SPDX-License-Identifier: BSD-3-Clause
License and notices: [BSD 3-Clause](licenses/BSD-3-Clause-Go.txt) | License and notices: [BSD 3-Clause](licenses/BSD-3-Clause-Go.txt) |
[LICENSE](https://github.com/golang/go/blob/fefb02adf45c4bcc879bd406a8d61f2a292c26a9/src/vendor/golang.org/x/net/LICENSE) | [LICENSE](https://github.com/golang/go/blob/c293dd49cbe25e1fe8d97d94a5cb618e7b6d831e/src/vendor/golang.org/x/net/LICENSE) |
[PATENTS](https://github.com/golang/go/blob/fefb02adf45c4bcc879bd406a8d61f2a292c26a9/src/vendor/golang.org/x/net/PATENTS). [PATENTS](https://github.com/golang/go/blob/c293dd49cbe25e1fe8d97d94a5cb618e7b6d831e/src/vendor/golang.org/x/net/PATENTS).
[Source](https://github.com/golang/go/commit/fefb02adf45c4bcc879bd406a8d61f2a292c26a9). [Source](https://github.com/golang/go/commit/c293dd49cbe25e1fe8d97d94a5cb618e7b6d831e).
## Go vendored golang.org/x/sys ## Go vendored golang.org/x/sys
Material: `golang.org/x/sys` at `v0.33.0` in the Go 1.25.5 standard library. Material: `golang.org/x/sys` at `v0.39.0` in the Go 1.26.8 standard library.
Copyright 2009 The Go Authors. Copyright 2009 The Go Authors.
SPDX-License-Identifier: BSD-3-Clause SPDX-License-Identifier: BSD-3-Clause
License and notices: [BSD 3-Clause](licenses/BSD-3-Clause-Go.txt) | License and notices: [BSD 3-Clause](licenses/BSD-3-Clause-Go.txt) |
[LICENSE](https://github.com/golang/go/blob/fefb02adf45c4bcc879bd406a8d61f2a292c26a9/src/vendor/golang.org/x/sys/LICENSE) | [LICENSE](https://github.com/golang/go/blob/c293dd49cbe25e1fe8d97d94a5cb618e7b6d831e/src/vendor/golang.org/x/sys/LICENSE) |
[PATENTS](https://github.com/golang/go/blob/fefb02adf45c4bcc879bd406a8d61f2a292c26a9/src/vendor/golang.org/x/sys/PATENTS). [PATENTS](https://github.com/golang/go/blob/c293dd49cbe25e1fe8d97d94a5cb618e7b6d831e/src/vendor/golang.org/x/sys/PATENTS).
[Source](https://github.com/golang/go/commit/fefb02adf45c4bcc879bd406a8d61f2a292c26a9). [Source](https://github.com/golang/go/commit/c293dd49cbe25e1fe8d97d94a5cb618e7b6d831e).
## Go vendored golang.org/x/text ## Go vendored golang.org/x/text
Material: `golang.org/x/text` at `v0.26.0` in the Go 1.25.5 standard library. Material: `golang.org/x/text` at `v0.32.0` in the Go 1.26.8 standard library.
Copyright 2009 The Go Authors. Copyright 2009 The Go Authors.
SPDX-License-Identifier: BSD-3-Clause SPDX-License-Identifier: BSD-3-Clause
License and notices: [BSD 3-Clause](licenses/BSD-3-Clause-Go.txt) | License and notices: [BSD 3-Clause](licenses/BSD-3-Clause-Go.txt) |
[LICENSE](https://github.com/golang/go/blob/fefb02adf45c4bcc879bd406a8d61f2a292c26a9/src/vendor/golang.org/x/text/LICENSE) | [LICENSE](https://github.com/golang/go/blob/c293dd49cbe25e1fe8d97d94a5cb618e7b6d831e/src/vendor/golang.org/x/text/LICENSE) |
[PATENTS](https://github.com/golang/go/blob/fefb02adf45c4bcc879bd406a8d61f2a292c26a9/src/vendor/golang.org/x/text/PATENTS). [PATENTS](https://github.com/golang/go/blob/c293dd49cbe25e1fe8d97d94a5cb618e7b6d831e/src/vendor/golang.org/x/text/PATENTS).
[Source](https://github.com/golang/go/commit/fefb02adf45c4bcc879bd406a8d61f2a292c26a9). [Source](https://github.com/golang/go/commit/c293dd49cbe25e1fe8d97d94a5cb618e7b6d831e).
## go-yaml v4 ## go-yaml v4

View File

@@ -2,8 +2,8 @@
The [release policy](policy/release.md) owns release requirements. This runbook The [release policy](policy/release.md) owns release requirements. This runbook
uses the native commands in [scripts/release.sh](../scripts/release.sh), which uses the native commands in [scripts/release.sh](../scripts/release.sh), which
builds and invokes ReleaseKit. A confirmed source tag completes the release; invokes the module-selected ReleaseKit tool. A confirmed source tag completes
Woodpecker reports binary delivery separately. the release; Woodpecker reports binary delivery separately.
## Prerequisites ## Prerequisites
@@ -12,16 +12,16 @@ which must point to `https://gitea.maximumdirect.net/eric/audita.git`; the
primary branch is `main`. Source publication needs no delivery credential. primary branch is `main`. Source publication needs no delivery credential.
Woodpecker uses the existing `GITEA_RELEASE_TOKEN` secret for delivery. Woodpecker uses the existing `GITEA_RELEASE_TOKEN` secret for delivery.
The source-build minimum is Go 1.25.5, declared in [go.mod](../go.mod). The source-build minimum is Go 1.26.0, declared in [go.mod](../go.mod).
This patch minimum is required by the selected PromptKit dependency. This minimum is required by the selected ReleaseKit dependency.
Distributed executables do not require a Go installation. The selected release Distributed executables do not require a Go installation. The selected release
toolchain is Go 1.25.5, matching the current Woodpecker image and distributed toolchain is Go 1.26.8, matching the current Woodpecker image and distributed
runtime notices. Use that local installation for tool bootstrap, candidate runtime notices. Use that local installation for tool bootstrap, candidate
validation, and release builds. Replace the installation path below with its validation, and release builds. Replace the installation path below with its
actual directory: actual directory:
```sh ```sh
export PATH="/path/to/go1.25.5/bin:$PATH" export PATH="/path/to/go1.26.8/bin:$PATH"
export GOTOOLCHAIN=local export GOTOOLCHAIN=local
go version go version
``` ```
@@ -40,10 +40,12 @@ GOWORK=off GOTOOLCHAIN=local go mod download
``` ```
If dependency preparation changes `go.mod` or `go.sum`, include those changes in If dependency preparation changes `go.mod` or `go.sum`, include those changes in
the committed candidate. The release wrapper separately builds the ReleaseKit the committed candidate. ReleaseKit's CLI is registered as a Go tool dependency;
version selected by `go.mod` into ignored `bin/releasekit`. It uses temporary `go mod tidy` retains its dependencies and checksums. The release wrapper changes
copies of `go.mod` and `go.sum` for tool dependency preparation; that build is to the repository root and invokes `go tool releasekit` with workspace discovery
tool preparation, not candidate validation. and automatic toolchain downloads disabled. Go builds or reuses the selected
tool from its normal build cache. Tool preparation precedes offline candidate
validation.
## Prepare and publish the candidate ## Prepare and publish the candidate
@@ -106,7 +108,7 @@ out detached, and confirms the source tree is clean. It copies the tagged
`go.mod` and `go.sum` into temporary module metadata outside the checkout. Go `go.mod` and `go.sum` into temporary module metadata outside the checkout. Go
confirms that this configuration selects ReleaseKit, then builds its CLI using confirms that this configuration selects ReleaseKit, then builds its CLI using
an alternate `-modfile`. The bootstrap executable and temporary files remain an alternate `-modfile`. The bootstrap executable and temporary files remain
outside the tagged source. The workflow runs with the existing Go 1.25.5 image, outside the tagged source. The workflow runs with the selected Go 1.26.8 image,
`GOWORK=off`, and `GOTOOLCHAIN=local`. `GOWORK=off`, and `GOTOOLCHAIN=local`.
The resulting executable receives: The resulting executable receives:
@@ -125,8 +127,8 @@ after source publication; its status and logs report delivery progress or
failure. failure.
ReleaseKit handles content-aware retries and retained-artifact recovery. See ReleaseKit handles content-aware retries and retained-artifact recovery. See
the pinned [publication recovery guide](https://gitea.maximumdirect.net/eric/ReleaseKit/src/tag/v0.1.0/docs/consumers/publish.md) the pinned [publication recovery guide](https://gitea.maximumdirect.net/eric/ReleaseKit/src/tag/v0.3.0/docs/consumers/publish.md)
and [delivery guide](https://gitea.maximumdirect.net/eric/ReleaseKit/src/tag/v0.1.0/docs/consumers/delivery.md) and [delivery guide](https://gitea.maximumdirect.net/eric/ReleaseKit/src/tag/v0.3.0/docs/consumers/delivery.md)
for those mechanics. A delivery failure does not undo a confirmed source for those mechanics. A delivery failure does not undo a confirmed source
release. Retry the same immutable tag after resolving the reported state; do not release. Retry the same immutable tag after resolving the reported state; do not
replace published files or a checksum manifest. replace published files or a checksum manifest.
@@ -139,11 +141,11 @@ captured authoritative endpoint and publication state. Resolve the result
against that endpoint before retrying. An exact remote tag-object match, with against that endpoint before retrying. An exact remote tag-object match, with
its source available on remote `main`, can confirm publication. If the remote its source available on remote `main`, can confirm publication. If the remote
state cannot be read, the result remains unknown; do not remove or replace the state cannot be read, the result remains unknown; do not remove or replace the
tag while uncertain. Follow the pinned [publication recovery guide](https://gitea.maximumdirect.net/eric/ReleaseKit/src/tag/v0.1.0/docs/consumers/publish.md) tag while uncertain. Follow the pinned [publication recovery guide](https://gitea.maximumdirect.net/eric/ReleaseKit/src/tag/v0.3.0/docs/consumers/publish.md)
for object-level recovery details. for object-level recovery details.
A source-release success and artifact-delivery status are separate. For a A source-release success and artifact-delivery status are separate. For a
partial or uncertain delivery, inspect the job output and follow the pinned partial or uncertain delivery, inspect the job output and follow the pinned
[delivery recovery guidance](https://gitea.maximumdirect.net/eric/ReleaseKit/src/tag/v0.1.0/docs/consumers/delivery.md). [delivery recovery guidance](https://gitea.maximumdirect.net/eric/ReleaseKit/src/tag/v0.3.0/docs/consumers/delivery.md).
A source or published-content correction requires a new appropriate version; A source or published-content correction requires a new appropriate version;
leave the original tag and its contents intact. leave the original tag and its contents intact.

View File

@@ -1,7 +1,8 @@
# ReleaseKit v0.3.0 Upgrade Roadmap # ReleaseKit v0.3.0 Upgrade Roadmap
**Last updated:** 2026-10-08 **Last updated:** 2026-10-08
**Status:** Release policy adopted; library and toolchain upgrade pending. **Status:** Implementation complete; first upgraded release and live delivery
observation pending.
## Purpose ## Purpose
@@ -11,9 +12,9 @@ ReleaseKit; Audita supplies product facts, compatibility decisions, release
notes, and thin integration wiring. Standardization and less locally owned notes, and thin integration wiring. Standardization and less locally owned
release code remain the reasons for adoption. release code remain the reasons for adoption.
This roadmap replaces the completed initial-adoption roadmap. It defines the This roadmap replaces the completed initial-adoption roadmap. It records the
upgrade's target state and required work; a later implementation plan can own upgrade's target state and completed scope. The upgrade was implemented in one
ordered execution steps. pass without a separate staged plan.
## Current Baseline ## Current Baseline
@@ -41,10 +42,11 @@ At Audita commit `17421bc5f352f3fdeb67561b2b60b49979822228`:
The initial adoption's [review findings](remediation.md) are resolved. They are The initial adoption's [review findings](remediation.md) are resolved. They are
historical context, not additional work for this upgrade. historical context, not additional work for this upgrade.
Since that baseline, Audita has adopted release policy 1.6 and updated its Since that baseline, Audita has adopted release policy 1.6, registered and
provenance, policy attribution, and release runbook. The current Go minimum and upgraded the ReleaseKit tool to v0.3.0, simplified the local wrapper, and
selected release toolchain remain 1.25.5. The dependency, tool registration, updated CI and notices. The current Go minimum is 1.26.0; the selected release
bootstrap simplification, and Go 1.26 upgrade described below remain pending. toolchain is 1.26.8. The scope below records the requirements fulfilled by this
implementation.
## Selected Upstream Revisions ## Selected Upstream Revisions
@@ -94,7 +96,7 @@ increases in minor releases, with the increase and required source-consumer
action explained in the release note. Patch releases preserve the consumer action explained in the release note. Patch releases preserve the consumer
minimum. Other compatibility changes remain governed by the adopted policy. minimum. Other compatibility changes remain governed by the adopted policy.
## Work Required ## Implemented Scope
### Module Selection and Local Tooling ### Module Selection and Local Tooling
@@ -208,3 +210,22 @@ asynchronously when the first upgraded release is published and address any
deployment failures through the shared recovery procedure. No significant deployment failures through the shared recovery procedure. No significant
feature decisions remain open; implementation details and final release feature decisions remain open; implementation details and final release
preparation can be resolved within the agreed scope. preparation can be resolved within the agreed scope.
## Implementation Completion
Completed with Go 1.26.8. The readonly offline full suite passed. The real local
wrapper selected ReleaseKit v0.3.0; disposable Go-tool fixtures verified native
argument boundaries, statuses 0/2/3, and unchanged module files. The CI workflow
matches the v0.3.0 maintained template with Audita's existing secret mapping.
The real ReleaseKit builder produced both required Linux artifacts using the
test identifier v0.0.0, passed its native version smoke check, and emitted
matching artifact digests in its build receipt. Temporary output was removed.
The new release-tool dependencies are absent from Audita's runtime dependency
list. ReleaseKit and Go runtime/vendor attribution identify their selected
source revisions, with existing applicable license texts reused.
This verification built modified local source and did not claim a clean,
published release candidate. Release preparation will validate its final
committed candidate through the ordinary publication command. No tags, source
pushes, hosted uploads, or LLM calls were made during implementation; live
artifact delivery remains a separate observation for the next release.

8
go.mod
View File

@@ -1,11 +1,12 @@
module gitea.maximumdirect.net/eric/audita module gitea.maximumdirect.net/eric/audita
go 1.25.5 go 1.26.0
require ( require (
gitea.maximumdirect.net/eric/ConfigKit v0.5.0 gitea.maximumdirect.net/eric/ConfigKit v0.5.0
gitea.maximumdirect.net/eric/ReleaseKit v0.1.0 gitea.maximumdirect.net/eric/ReleaseKit v0.3.0
gitea.maximumdirect.net/eric/promptkit v0.12.0 gitea.maximumdirect.net/eric/promptkit v0.12.0
golang.org/x/mod v0.40.0
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
) )
@@ -15,7 +16,10 @@ require (
github.com/kr/pretty v0.3.1 // indirect github.com/kr/pretty v0.3.1 // indirect
github.com/rogpeppe/go-internal v1.14.1 // indirect github.com/rogpeppe/go-internal v1.14.1 // indirect
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 // indirect github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 // indirect
github.com/yuin/goldmark/v2 v2.1.6 // indirect
go.yaml.in/yaml/v4 v4.0.0-rc.6 // indirect go.yaml.in/yaml/v4 v4.0.0-rc.6 // indirect
golang.org/x/text v0.14.0 // indirect golang.org/x/text v0.14.0 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
) )
tool gitea.maximumdirect.net/eric/ReleaseKit/cmd/releasekit

8
go.sum
View File

@@ -1,7 +1,7 @@
gitea.maximumdirect.net/eric/ConfigKit v0.5.0 h1:2KwG10+kApWM3avOoYA+p768xcbHUp7tQ2rqizBNpyg= gitea.maximumdirect.net/eric/ConfigKit v0.5.0 h1:2KwG10+kApWM3avOoYA+p768xcbHUp7tQ2rqizBNpyg=
gitea.maximumdirect.net/eric/ConfigKit v0.5.0/go.mod h1:tYWpsrbLyu05r8+1MapWp5fN6ZrX9I8hU2xEPedYmYk= gitea.maximumdirect.net/eric/ConfigKit v0.5.0/go.mod h1:tYWpsrbLyu05r8+1MapWp5fN6ZrX9I8hU2xEPedYmYk=
gitea.maximumdirect.net/eric/ReleaseKit v0.1.0 h1:Ejk85+9Of+A8M7Mod91iB3KxrrQYgnJcz9hPA6fL1a4= gitea.maximumdirect.net/eric/ReleaseKit v0.3.0 h1:aTMn+50MEpHpHI2RoCCmJYsy7Ohrpa+RSva63Kq0/tU=
gitea.maximumdirect.net/eric/ReleaseKit v0.1.0/go.mod h1:KmH607Vc/RK/zKrCB96cWh0QZYvkbxBFvtqV+BkKaxs= gitea.maximumdirect.net/eric/ReleaseKit v0.3.0/go.mod h1:LVodqNQ3OIKv9NBq+fFeyMByd1lHY25YOmmpah/NaNI=
gitea.maximumdirect.net/eric/promptkit v0.12.0 h1:oIFWEg6GPxtmX3Plqv0euP+FnK1LCk/t4xDy6oKCpP4= gitea.maximumdirect.net/eric/promptkit v0.12.0 h1:oIFWEg6GPxtmX3Plqv0euP+FnK1LCk/t4xDy6oKCpP4=
gitea.maximumdirect.net/eric/promptkit v0.12.0/go.mod h1:pFOOy5v1zH17stEi9rmHYmfbuLjnfVMcUhfmk+4VtJY= gitea.maximumdirect.net/eric/promptkit v0.12.0/go.mod h1:pFOOy5v1zH17stEi9rmHYmfbuLjnfVMcUhfmk+4VtJY=
gitea.maximumdirect.net/eric/promptkit-backend-openrouter/v3 v3.20260930.0 h1:lzfCDbzJQRGzIlRPRprnAV9hVVL5zUQy80VMQ0kbYa0= gitea.maximumdirect.net/eric/promptkit-backend-openrouter/v3 v3.20260930.0 h1:lzfCDbzJQRGzIlRPRprnAV9hVVL5zUQy80VMQ0kbYa0=
@@ -24,8 +24,12 @@ github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0t
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ= github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ=
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU= github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU=
github.com/yuin/goldmark/v2 v2.1.6 h1:igy0q9JcqovoFXFx/XjtU8sHVhcUHavPj2sA3EoKJUE=
github.com/yuin/goldmark/v2 v2.1.6/go.mod h1:G6M4/qOFtfn01/o14BU1UR2Lo5N3S9Qo7xCuz/sHjGQ=
go.yaml.in/yaml/v4 v4.0.0-rc.6 h1:1h7H1ohdUh93/FyE4YaDa1Zh64K6VVbjF4K6WUxMtH4= go.yaml.in/yaml/v4 v4.0.0-rc.6 h1:1h7H1ohdUh93/FyE4YaDa1Zh64K6VVbjF4K6WUxMtH4=
go.yaml.in/yaml/v4 v4.0.0-rc.6/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0= go.yaml.in/yaml/v4 v4.0.0-rc.6/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0=
golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs=
golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

View File

@@ -3,23 +3,4 @@ set -euo pipefail
script_dir=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd) script_dir=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
cd "$script_dir/.." cd "$script_dir/.."
GOWORK=off GOTOOLCHAIN=local exec go tool releasekit "$@"
releasekit_module=gitea.maximumdirect.net/eric/ReleaseKit
bootstrap_dir=$(mktemp -d "${TMPDIR:-/tmp}/releasekit-bootstrap.XXXXXX")
trap 'rm -rf "$bootstrap_dir"' EXIT
cp go.mod "$bootstrap_dir/go.mod"
if [[ -f go.sum ]]; then
cp go.sum "$bootstrap_dir/go.sum"
else
: > "$bootstrap_dir/go.sum"
fi
cp "$bootstrap_dir/go.mod" "$bootstrap_dir/releasekit.mod"
cp "$bootstrap_dir/go.sum" "$bootstrap_dir/releasekit.sum"
mkdir -p bin
GOWORK=off GOTOOLCHAIN=local go list -mod=mod -modfile="$bootstrap_dir/releasekit.mod" -m "$releasekit_module" >/dev/null
GOWORK=off GOTOOLCHAIN=local go build -mod=mod -modfile="$bootstrap_dir/releasekit.mod" \
-o ./bin/releasekit "$releasekit_module/cmd/releasekit"
rm -rf "$bootstrap_dir"
trap - EXIT
exec ./bin/releasekit "$@"

View File

@@ -2,64 +2,30 @@ package scripts
import ( import (
"encoding/json" "encoding/json"
"go/version"
"maps" "maps"
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"reflect" "reflect"
"slices"
"strconv" "strconv"
"strings" "strings"
"testing" "testing"
"golang.org/x/mod/modfile"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
const goStub = `#!/usr/bin/env bash const goStub = `#!/usr/bin/env bash
set -euo pipefail set -euo pipefail
case ${1:?missing Go command} in printf '%s\0' "$PWD" "GOWORK=$GOWORK" "GOTOOLCHAIN=$GOTOOLCHAIN" "$@" > "$RELEASEKIT_GO_LOG"
list) if [[ ${RELEASEKIT_BOOTSTRAP_STATUS:-0} != 0 ]]; then
log=$RELEASEKIT_LIST_LOG
printf '%s\0' "$PWD" "GOWORK=$GOWORK" "GOTOOLCHAIN=$GOTOOLCHAIN" "$@" > "$log"
modfile=
for argument in "$@"; do
case $argument in -modfile=*) modfile=${argument#-modfile=} ;; esac
done
[[ -n $modfile && -f $modfile && -f ${modfile%.mod}.sum ]]
cp "$modfile" "$RELEASEKIT_MODULE_LOG"
cp "${modfile%.mod}.sum" "$RELEASEKIT_SUM_LOG"
if [[ ${RELEASEKIT_BOOTSTRAP_STATUS:-0} != 0 ]]; then
exit "$RELEASEKIT_BOOTSTRAP_STATUS" exit "$RELEASEKIT_BOOTSTRAP_STATUS"
fi fi
printf '%s\n' v0.1.0 [[ $1 == tool && $2 == releasekit ]]
;; shift 2
build)
printf '%s\0' "$PWD" "GOWORK=$GOWORK" "GOTOOLCHAIN=$GOTOOLCHAIN" "$@" > "$RELEASEKIT_BUILD_LOG"
if [[ ${RELEASEKIT_BOOTSTRAP_STATUS:-0} != 0 ]]; then
exit "$RELEASEKIT_BOOTSTRAP_STATUS"
fi
output=
while (($#)); do
case $1 in
-o)
shift
output=${1:?missing output path}
;;
-o=*) output=${1#-o=} ;;
esac
shift
done
[[ -n $output ]]
cat > "$output" <<'RELEASEKIT_TOOL'
#!/usr/bin/env bash
set -euo pipefail
printf '%s\0' "$PWD" "$@" > "$RELEASEKIT_ARGS_LOG" printf '%s\0' "$PWD" "$@" > "$RELEASEKIT_ARGS_LOG"
exit "${RELEASEKIT_TOOL_STATUS:-0}" exit "${RELEASEKIT_TOOL_STATUS:-0}"
RELEASEKIT_TOOL
chmod +x "$output"
;;
*) exit 64 ;;
esac
` `
func TestReleaseWrapperForwardsNativeArgumentsAndStatus(t *testing.T) { func TestReleaseWrapperForwardsNativeArgumentsAndStatus(t *testing.T) {
@@ -71,10 +37,7 @@ func TestReleaseWrapperForwardsNativeArgumentsAndStatus(t *testing.T) {
cmd.Dir = layout.caller cmd.Dir = layout.caller
cmd.Env = layout.environment(map[string]string{ cmd.Env = layout.environment(map[string]string{
"RELEASEKIT_ARGS_LOG": layout.argsLog, "RELEASEKIT_ARGS_LOG": layout.argsLog,
"RELEASEKIT_BUILD_LOG": layout.buildLog, "RELEASEKIT_GO_LOG": layout.goLog,
"RELEASEKIT_LIST_LOG": layout.listLog,
"RELEASEKIT_MODULE_LOG": layout.moduleLog,
"RELEASEKIT_SUM_LOG": layout.sumLog,
"RELEASEKIT_TOOL_STATUS": strconv.Itoa(toolStatus), "RELEASEKIT_TOOL_STATUS": strconv.Itoa(toolStatus),
}) })
output, err := cmd.CombinedOutput() output, err := cmd.CombinedOutput()
@@ -84,58 +47,13 @@ func TestReleaseWrapperForwardsNativeArgumentsAndStatus(t *testing.T) {
if len(output) != 0 { if len(output) != 0 {
t.Fatalf("unexpected output: %q", output) t.Fatalf("unexpected output: %q", output)
} }
wantGo := append([]string{layout.repo, "GOWORK=off", "GOTOOLCHAIN=local", "tool", "releasekit"}, args...)
listInvocation := readNULArguments(t, layout.listLog) if got := readNULArguments(t, layout.goLog); !reflect.DeepEqual(got, wantGo) {
buildInvocation := readNULArguments(t, layout.buildLog) t.Fatalf("Go invocation=%q, want %q", got, wantGo)
for name, invocation := range map[string][]string{"list": listInvocation, "build": buildInvocation} {
if len(invocation) < 4 || invocation[0] != layout.repo {
t.Fatalf("%s invocation=%q, want repository %q", name, invocation, layout.repo)
} }
if invocation[1] != "GOWORK=off" || invocation[2] != "GOTOOLCHAIN=local" { wantTool := append([]string{layout.repo}, args...)
t.Fatalf("%s environment=%q", name, invocation[1:3]) if got := readNULArguments(t, layout.argsLog); !reflect.DeepEqual(got, wantTool) {
} t.Fatalf("tool invocation=%q, want %q", got, wantTool)
}
listArgs := listInvocation[3:]
buildArgs := buildInvocation[3:]
modfile := argumentValue(listArgs, "-modfile=")
if listArgs[0] != "list" || !slices.Contains(listArgs, "-mod=mod") || !slices.Contains(listArgs, "-m") || !slices.Contains(listArgs, "gitea.maximumdirect.net/eric/ReleaseKit") {
t.Fatalf("unexpected module-selection arguments: %q", listArgs)
}
if modfile == "" || argumentValue(buildArgs, "-modfile=") != modfile || !slices.Contains(buildArgs, "-mod=mod") {
t.Fatalf("list/build did not share alternate module metadata: list=%q build=%q", listArgs, buildArgs)
}
if buildArgs[0] != "build" || !slices.Contains(buildArgs, "gitea.maximumdirect.net/eric/ReleaseKit/cmd/releasekit") {
t.Fatalf("unexpected build arguments: %q", buildArgs)
}
if relative, err := filepath.Rel(layout.repo, modfile); err != nil || relative == "." || (!strings.HasPrefix(relative, ".."+string(filepath.Separator)) && relative != "..") {
t.Fatalf("alternate module file %q is not outside repository %q: relative=%q error=%v", modfile, layout.repo, relative, err)
}
if module := readText(t, layout.moduleLog); !strings.Contains(module, "gitea.maximumdirect.net/eric/ReleaseKit v0.1.0") {
t.Fatalf("temporary module file does not retain selected ReleaseKit version: %q", module)
}
if sum := readText(t, layout.sumLog); !strings.Contains(sum, "fixture checksum") {
t.Fatalf("temporary sum file did not copy source checksums: %q", sum)
}
if sourceModule := readText(t, filepath.Join(layout.repo, "go.mod")); !strings.Contains(sourceModule, "gitea.maximumdirect.net/eric/ReleaseKit v0.1.0") {
t.Fatalf("source go.mod changed: %q", sourceModule)
}
if sourceSum := readText(t, filepath.Join(layout.repo, "go.sum")); sourceSum != "fixture checksum\n" {
t.Fatalf("source go.sum changed: %q", sourceSum)
}
if _, err := os.Stat(modfile); !os.IsNotExist(err) {
t.Fatalf("temporary module file remains after wrapper: %v", err)
}
if _, err := os.Stat(filepath.Join(filepath.Dir(modfile), "releasekit.sum")); !os.IsNotExist(err) {
t.Fatalf("temporary sum file remains after wrapper: %v", err)
}
if info, err := os.Stat(filepath.Join(layout.repo, "bin", "releasekit")); err != nil || !info.Mode().IsRegular() {
t.Fatalf("ReleaseKit executable was not built in bin: %v", err)
}
toolInvocation := readNULArguments(t, layout.argsLog)
wantToolInvocation := append([]string{layout.repo}, args...)
if !reflect.DeepEqual(toolInvocation, wantToolInvocation) {
t.Fatalf("tool invocation=%q, want %q", toolInvocation, wantToolInvocation)
} }
}) })
} }
@@ -166,8 +84,22 @@ func TestDeliveryWorkflowUsesModuleSelectedReleaseKit(t *testing.T) {
if err := yaml.Unmarshal(data, &workflow); err != nil { if err := yaml.Unmarshal(data, &workflow); err != nil {
t.Fatal(err) t.Fatal(err)
} }
if len(workflow.Matrix.Include) != 1 || workflow.Matrix.Include[0]["GO_IMAGE"] != "golang:1.25.5" { goMod, err := modfile.Parse("go.mod", []byte(readText(t, "../go.mod")), nil)
t.Fatalf("workflow Go image = %v, want existing Go 1.25.5 image", workflow.Matrix.Include) if err != nil {
t.Fatal(err)
}
if goMod.Go == nil {
t.Fatal("module does not declare its Go minimum")
}
if len(workflow.Matrix.Include) != 1 {
t.Fatalf("workflow Go matrix = %v", workflow.Matrix.Include)
}
image := workflow.Matrix.Include[0]["GO_IMAGE"]
_, tag, _ := strings.Cut(image, ":")
patch, _, _ := strings.Cut(tag, "-")
imageVersion := "go" + patch
if !version.IsValid(imageVersion) || version.Compare(imageVersion, "go"+goMod.Go.Version) < 0 {
t.Fatalf("workflow Go image %q cannot satisfy module minimum %s", image, goMod.Go.Version)
} }
if len(workflow.When) != 1 || workflow.When[0].Event != "tag" || workflow.When[0].Ref != "refs/tags/v*" { if len(workflow.When) != 1 || workflow.When[0].Event != "tag" || workflow.When[0].Ref != "refs/tags/v*" {
t.Fatalf("workflow trigger = %+v", workflow.When) t.Fatalf("workflow trigger = %+v", workflow.When)
@@ -183,41 +115,30 @@ func TestDeliveryWorkflowUsesModuleSelectedReleaseKit(t *testing.T) {
} }
command := workflow.Steps[0].Commands[0] command := workflow.Steps[0].Commands[0]
for _, fragment := range []string{ for _, fragment := range []string{
"git fetch origin \"refs/tags/$${CI_COMMIT_TAG}:refs/tags/$${CI_COMMIT_TAG}\"", "git fetch origin", "refs/tags/$${CI_COMMIT_TAG}",
"git checkout --detach \"$${tag_commit}\"", "git checkout --detach", "git status --porcelain",
"git status --porcelain --untracked-files=all", "go list", "-modfile=", "-m", "go build", "$${releasekit_module}/cmd/releasekit",
"mktemp -d \"$${CI_WORKSPACE%/*}/.releasekit-bootstrap.XXXXXX\"", "ci-deliver", "--repo \"$${CI_WORKSPACE}\"", "--commit \"$${CI_COMMIT_SHA}\"",
"trap 'rm -rf \"$${bootstrap_dir}\"' EXIT",
"cp go.mod \"$${bootstrap_dir}/go.mod\"",
"cp go.sum \"$${bootstrap_dir}/go.sum\"",
"export GOWORK=off",
"export GOTOOLCHAIN=local",
"releasekit_module=gitea.maximumdirect.net/eric/ReleaseKit",
"go list -mod=mod -modfile=",
"-m \"$${releasekit_module}\"",
"go build -mod=mod -modfile=",
"-o \"$${bootstrap_dir}/releasekit\" \"$${releasekit_module}/cmd/releasekit\"",
"\"$${bootstrap_dir}/releasekit\" ci-deliver",
"--repo \"$${CI_WORKSPACE}\"",
"--commit \"$${CI_COMMIT_SHA}\"",
"\"$${CI_COMMIT_TAG}\"", "\"$${CI_COMMIT_TAG}\"",
} { } {
if !strings.Contains(command, fragment) { if !strings.Contains(command, fragment) {
t.Fatalf("workflow command is missing %q: %s", fragment, command) t.Fatalf("workflow command is missing %q: %s", fragment, command)
} }
} }
if strings.Index(command, "git checkout --detach") > strings.Index(command, "go list -mod=mod") || strings.Index(command, "git status --porcelain") > strings.Index(command, "go list -mod=mod") { if strings.Index(command, "git checkout --detach") > strings.Index(command, "go list") || strings.Index(command, "git status --porcelain") > strings.Index(command, "go list") {
t.Fatalf("workflow resolves the tool before verifying the tagged source: %s", command) t.Fatalf("workflow resolves the tool before verifying the tagged source: %s", command)
} }
if strings.Contains(command, "RELEASEKIT_VERSION") || strings.Contains(command, "cmd/releasekit@") { if strings.Contains(command, "RELEASEKIT_VERSION") || strings.Contains(command, "cmd/releasekit@") {
t.Fatalf("workflow retains a separate tool version: %s", command) t.Fatalf("workflow retains a separate tool version: %s", command)
} }
goMod, err := os.ReadFile("../go.mod") toolSelected := false
if err != nil { for _, tool := range goMod.Tool {
t.Fatal(err) if tool.Path == "gitea.maximumdirect.net/eric/ReleaseKit/cmd/releasekit" {
toolSelected = true
} }
if !strings.Contains(string(goMod), "gitea.maximumdirect.net/eric/ReleaseKit v0.1.0") { }
t.Fatalf("go.mod does not retain selected supported ReleaseKit version: %s", goMod) if !toolSelected {
t.Fatal("module does not register the ReleaseKit CLI tool")
} }
} }
@@ -227,10 +148,7 @@ func TestReleaseWrapperStopsWhenBootstrapFails(t *testing.T) {
cmd.Dir = layout.caller cmd.Dir = layout.caller
cmd.Env = layout.environment(map[string]string{ cmd.Env = layout.environment(map[string]string{
"RELEASEKIT_ARGS_LOG": layout.argsLog, "RELEASEKIT_ARGS_LOG": layout.argsLog,
"RELEASEKIT_BUILD_LOG": layout.buildLog, "RELEASEKIT_GO_LOG": layout.goLog,
"RELEASEKIT_LIST_LOG": layout.listLog,
"RELEASEKIT_MODULE_LOG": layout.moduleLog,
"RELEASEKIT_SUM_LOG": layout.sumLog,
"RELEASEKIT_BOOTSTRAP_STATUS": "42", "RELEASEKIT_BOOTSTRAP_STATUS": "42",
}) })
output, err := cmd.CombinedOutput() output, err := cmd.CombinedOutput()
@@ -240,17 +158,6 @@ func TestReleaseWrapperStopsWhenBootstrapFails(t *testing.T) {
if _, err := os.Stat(layout.argsLog); !os.IsNotExist(err) { if _, err := os.Stat(layout.argsLog); !os.IsNotExist(err) {
t.Fatalf("ReleaseKit executed after failed bootstrap; stat error=%v", err) t.Fatalf("ReleaseKit executed after failed bootstrap; stat error=%v", err)
} }
if _, err := os.Stat(layout.buildLog); !os.IsNotExist(err) {
t.Fatalf("ReleaseKit build ran after module selection failed; stat error=%v", err)
}
listInvocation := readNULArguments(t, layout.listLog)
modfile := argumentValue(listInvocation[3:], "-modfile=")
if modfile == "" {
t.Fatalf("module selection did not use temporary metadata: %q", listInvocation)
}
if _, err := os.Stat(filepath.Dir(modfile)); !os.IsNotExist(err) {
t.Fatalf("temporary bootstrap directory remains after failed selection: %v", err)
}
} }
func TestReleaseKitProfileDeclaresProductAndDeliverySettings(t *testing.T) { func TestReleaseKitProfileDeclaresProductAndDeliverySettings(t *testing.T) {
@@ -331,10 +238,7 @@ type releaseTestLayout struct {
caller string caller string
wrapper string wrapper string
argsLog string argsLog string
buildLog string goLog string
listLog string
moduleLog string
sumLog string
path string path string
} }
@@ -345,20 +249,13 @@ func newReleaseTestLayout(t *testing.T) releaseTestLayout {
repo: filepath.Join(root, "repository"), repo: filepath.Join(root, "repository"),
caller: filepath.Join(root, "caller"), caller: filepath.Join(root, "caller"),
argsLog: filepath.Join(root, "tool-arguments"), argsLog: filepath.Join(root, "tool-arguments"),
buildLog: filepath.Join(root, "build-arguments"), goLog: filepath.Join(root, "go-arguments"),
listLog: filepath.Join(root, "list-arguments"),
moduleLog: filepath.Join(root, "temporary-go.mod"),
sumLog: filepath.Join(root, "temporary-go.sum"),
path: filepath.Join(root, "bin"), path: filepath.Join(root, "bin"),
} }
if err := os.MkdirAll(filepath.Join(layout.repo, "scripts"), 0755); err != nil { for _, directory := range []string{filepath.Join(layout.repo, "scripts"), layout.caller, layout.path} {
if err := os.MkdirAll(directory, 0755); err != nil {
t.Fatal(err) t.Fatal(err)
} }
if err := os.MkdirAll(layout.caller, 0755); err != nil {
t.Fatal(err)
}
if err := os.MkdirAll(layout.path, 0755); err != nil {
t.Fatal(err)
} }
wrapper, err := os.ReadFile("release.sh") wrapper, err := os.ReadFile("release.sh")
if err != nil { if err != nil {
@@ -367,8 +264,6 @@ func newReleaseTestLayout(t *testing.T) releaseTestLayout {
layout.wrapper = filepath.Join(layout.repo, "scripts", "release.sh") layout.wrapper = filepath.Join(layout.repo, "scripts", "release.sh")
writeReleaseTestFile(t, layout.wrapper, wrapper, 0755) writeReleaseTestFile(t, layout.wrapper, wrapper, 0755)
writeReleaseTestFile(t, filepath.Join(layout.path, "go"), []byte(goStub), 0755) writeReleaseTestFile(t, filepath.Join(layout.path, "go"), []byte(goStub), 0755)
writeReleaseTestFile(t, filepath.Join(layout.repo, "go.mod"), []byte("module example.com/audita-fixture\n\ngo 1.25.5\n\nrequire gitea.maximumdirect.net/eric/ReleaseKit v0.1.0\n"), 0644)
writeReleaseTestFile(t, filepath.Join(layout.repo, "go.sum"), []byte("fixture checksum\n"), 0644)
return layout return layout
} }
@@ -381,15 +276,6 @@ func readText(t *testing.T, path string) string {
return string(data) return string(data)
} }
func argumentValue(arguments []string, prefix string) string {
for _, argument := range arguments {
if strings.HasPrefix(argument, prefix) {
return strings.TrimPrefix(argument, prefix)
}
}
return ""
}
func (layout releaseTestLayout) environment(values map[string]string) []string { func (layout releaseTestLayout) environment(values map[string]string) []string {
filtered := make([]string, 0, len(os.Environ())+len(values)+1) filtered := make([]string, 0, len(os.Environ())+len(values)+1)
for _, value := range os.Environ() { for _, value := range os.Environ() {