Harden release publication plumbing
This commit is contained in:
@@ -2,8 +2,50 @@ when:
|
|||||||
- event: tag
|
- event: tag
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
- name: validate-release
|
||||||
|
image: golang:1.26.5
|
||||||
|
commands:
|
||||||
|
- |
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
version="$CI_COMMIT_TAG"
|
||||||
|
release_note="docs/releases/$version.md"
|
||||||
|
|
||||||
|
if ! printf '%s\n' "$version" |
|
||||||
|
grep -Eq '^v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$'
|
||||||
|
then
|
||||||
|
printf '%s\n' "invalid release tag: $version" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
test -s "$release_note"
|
||||||
|
test -z "$(git ls-files go.work go.work.sum)"
|
||||||
|
test ! -e vendor
|
||||||
|
if grep -Eq '^[[:space:]]*replace([[:space:]]|\()' go.mod
|
||||||
|
then
|
||||||
|
printf '%s\n' 'go.mod contains a replacement' >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
GOWORK=off go test -count=1 ./...
|
||||||
|
GOWORK=off go test -race -count=1 ./...
|
||||||
|
GOWORK=off go vet ./...
|
||||||
|
GOWORK=off go build ./...
|
||||||
|
GOWORK=off go mod tidy -diff
|
||||||
|
|
||||||
|
unformatted=$(
|
||||||
|
git ls-files '*.go' |
|
||||||
|
while IFS= read -r go_file
|
||||||
|
do
|
||||||
|
gofmt -l "$go_file"
|
||||||
|
done
|
||||||
|
)
|
||||||
|
test -z "$unformatted"
|
||||||
|
git diff --check
|
||||||
|
|
||||||
- name: build-release-assets
|
- name: build-release-assets
|
||||||
image: golang:1.25
|
image: golang:1.26.5
|
||||||
|
depends_on:
|
||||||
|
- validate-release
|
||||||
commands:
|
commands:
|
||||||
- |
|
- |
|
||||||
set -eu
|
set -eu
|
||||||
@@ -33,8 +75,11 @@ steps:
|
|||||||
build_binary windows amd64 ".exe"
|
build_binary windows amd64 ".exe"
|
||||||
build_binary windows arm64 ".exe"
|
build_binary windows arm64 ".exe"
|
||||||
|
|
||||||
|
host_binary="$dist/weatherreporter-$version-$(go env GOOS)-$(go env GOARCH)"
|
||||||
|
test "$("$host_binary" --version)" = "weatherreporter $version"
|
||||||
|
|
||||||
- name: publish-release
|
- name: publish-release
|
||||||
image: woodpeckerci/plugin-release
|
image: woodpeckerci/plugin-release:0.3.1
|
||||||
depends_on:
|
depends_on:
|
||||||
- build-release-assets
|
- build-release-assets
|
||||||
settings:
|
settings:
|
||||||
@@ -42,6 +87,8 @@ steps:
|
|||||||
from_secret: GITEA_RELEASE_TOKEN
|
from_secret: GITEA_RELEASE_TOKEN
|
||||||
files:
|
files:
|
||||||
- dist/weatherreporter-*
|
- dist/weatherreporter-*
|
||||||
|
title: Weatherreporter ${CI_COMMIT_TAG}
|
||||||
|
note: docs/releases/${CI_COMMIT_TAG}.md
|
||||||
checksum: sha256
|
checksum: sha256
|
||||||
checksum-file: SHA256SUMS
|
checksum-file: SHA256SUMS
|
||||||
checksum-flatten: true
|
checksum-flatten: true
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ required Weather API endpoint.
|
|||||||
|
|
||||||
```text
|
```text
|
||||||
weatherreporter --help
|
weatherreporter --help
|
||||||
|
weatherreporter --version
|
||||||
weatherreporter generate daily --date YYYY-MM-DD [--config PATH] [--units VALUE] [--tz NAME] [--out PATH] [--llm-debug-dir PATH] [--quiet]
|
weatherreporter generate daily --date YYYY-MM-DD [--config PATH] [--units VALUE] [--tz NAME] [--out PATH] [--llm-debug-dir PATH] [--quiet]
|
||||||
weatherreporter generate today [--config PATH] [--units VALUE] [--tz NAME] [--out PATH] [--date YYYY-MM-DD] [--llm-debug-dir PATH] [--quiet]
|
weatherreporter generate today [--config PATH] [--units VALUE] [--tz NAME] [--out PATH] [--date YYYY-MM-DD] [--llm-debug-dir PATH] [--quiet]
|
||||||
weatherreporter generate tomorrow [--config PATH] [--units VALUE] [--tz NAME] [--out PATH] [--llm-debug-dir PATH] [--quiet]
|
weatherreporter generate tomorrow [--config PATH] [--units VALUE] [--tz NAME] [--out PATH] [--llm-debug-dir PATH] [--quiet]
|
||||||
@@ -31,6 +32,10 @@ weatherreporter inspect prior [--config PATH] RUN_ID
|
|||||||
weatherreporter inspect sources [--config PATH] RUN_ID
|
weatherreporter inspect sources [--config PATH] RUN_ID
|
||||||
```
|
```
|
||||||
|
|
||||||
|
`weatherreporter --version` prints the version embedded in the executable.
|
||||||
|
Tagged release binaries report their semantic version tag; ordinary local
|
||||||
|
builds report `development`.
|
||||||
|
|
||||||
| Command | Contract |
|
| Command | Contract |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| `generate daily` | Requires `--date YYYY-MM-DD`; the date is interpreted in the effective report timezone. |
|
| `generate daily` | Requires `--date YYYY-MM-DD`; the date is interpreted in the effective report timezone. |
|
||||||
|
|||||||
@@ -4,6 +4,10 @@
|
|||||||
requests, and translates app results to bounded JSON summaries. The user
|
requests, and translates app results to bounded JSON summaries. The user
|
||||||
contract belongs in the [CLI reference](../cli.md).
|
contract belongs in the [CLI reference](../cli.md).
|
||||||
|
|
||||||
|
The root `--version` flag reports the build version supplied by
|
||||||
|
`internal/buildinfo`. Tagged release builds replace its development default at
|
||||||
|
link time.
|
||||||
|
|
||||||
For each `generate` or `run` action, `Runner` constructs one project-owned
|
For each `generate` or `run` action, `Runner` constructs one project-owned
|
||||||
Promptkit executor after configuration loads. It passes the executor and any
|
Promptkit executor after configuration loads. It passes the executor and any
|
||||||
`--llm-debug-dir` request into the app. `run` accepts the debug flag as well
|
`--llm-debug-dir` request into the app. `run` accepts the debug flag as well
|
||||||
|
|||||||
6
internal/buildinfo/buildinfo.go
Normal file
6
internal/buildinfo/buildinfo.go
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
// Package buildinfo exposes release metadata injected by the build pipeline.
|
||||||
|
package buildinfo
|
||||||
|
|
||||||
|
// Version identifies this Weatherreporter build. Release builds replace the
|
||||||
|
// development value with their semantic version tag through the Go linker.
|
||||||
|
var Version = "development"
|
||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
|
|
||||||
"gitea.maximumdirect.net/eric/weatherreporter/internal/app"
|
"gitea.maximumdirect.net/eric/weatherreporter/internal/app"
|
||||||
|
"gitea.maximumdirect.net/eric/weatherreporter/internal/buildinfo"
|
||||||
"gitea.maximumdirect.net/eric/weatherreporter/internal/config"
|
"gitea.maximumdirect.net/eric/weatherreporter/internal/config"
|
||||||
"gitea.maximumdirect.net/eric/weatherreporter/internal/report"
|
"gitea.maximumdirect.net/eric/weatherreporter/internal/report"
|
||||||
"gitea.maximumdirect.net/eric/weatherreporter/internal/timeutil"
|
"gitea.maximumdirect.net/eric/weatherreporter/internal/timeutil"
|
||||||
@@ -16,6 +17,7 @@ const helpText = `weatherreporter prepares weather reports from normalized forec
|
|||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
weatherreporter --help
|
weatherreporter --help
|
||||||
|
weatherreporter --version
|
||||||
weatherreporter generate daily --date YYYY-MM-DD [--config PATH] [--units VALUE] [--tz NAME] [--out PATH] [--llm-debug-dir PATH] [--quiet]
|
weatherreporter generate daily --date YYYY-MM-DD [--config PATH] [--units VALUE] [--tz NAME] [--out PATH] [--llm-debug-dir PATH] [--quiet]
|
||||||
weatherreporter generate today [--config PATH] [--units VALUE] [--tz NAME] [--out PATH] [--date YYYY-MM-DD] [--llm-debug-dir PATH] [--quiet]
|
weatherreporter generate today [--config PATH] [--units VALUE] [--tz NAME] [--out PATH] [--date YYYY-MM-DD] [--llm-debug-dir PATH] [--quiet]
|
||||||
weatherreporter generate tomorrow [--config PATH] [--units VALUE] [--tz NAME] [--out PATH] [--llm-debug-dir PATH] [--quiet]
|
weatherreporter generate tomorrow [--config PATH] [--units VALUE] [--tz NAME] [--out PATH] [--llm-debug-dir PATH] [--quiet]
|
||||||
@@ -31,6 +33,7 @@ Usage:
|
|||||||
|
|
||||||
Options:
|
Options:
|
||||||
-h, --help Show this help message.
|
-h, --help Show this help message.
|
||||||
|
--version Show the Weatherreporter version.
|
||||||
--config PATH Load configuration from PATH instead of /usr/local/etc/weatherreporter/config.yml.
|
--config PATH Load configuration from PATH instead of /usr/local/etc/weatherreporter/config.yml.
|
||||||
--units VALUE Override weather API units.
|
--units VALUE Override weather API units.
|
||||||
--tz NAME Override weather API timezone.
|
--tz NAME Override weather API timezone.
|
||||||
@@ -43,6 +46,7 @@ Options:
|
|||||||
type Runner struct {
|
type Runner struct {
|
||||||
Clock timeutil.Clock
|
Clock timeutil.Clock
|
||||||
ExecutorFactory ExecutorFactory
|
ExecutorFactory ExecutorFactory
|
||||||
|
Version string
|
||||||
}
|
}
|
||||||
|
|
||||||
func Run(ctx context.Context, args []string, stdout io.Writer, stderr io.Writer) error {
|
func Run(ctx context.Context, args []string, stdout io.Writer, stderr io.Writer) error {
|
||||||
@@ -57,6 +61,17 @@ func (r Runner) Run(ctx context.Context, args []string, stdout io.Writer, stderr
|
|||||||
_, err := fmt.Fprint(stdout, helpText)
|
_, err := fmt.Fprint(stdout, helpText)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if args[0] == "--version" {
|
||||||
|
if len(args) != 1 {
|
||||||
|
return fmt.Errorf("--version does not accept arguments")
|
||||||
|
}
|
||||||
|
version := r.Version
|
||||||
|
if version == "" {
|
||||||
|
version = buildinfo.Version
|
||||||
|
}
|
||||||
|
_, err := fmt.Fprintf(stdout, "weatherreporter %s\n", version)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
switch args[0] {
|
switch args[0] {
|
||||||
case "generate":
|
case "generate":
|
||||||
|
|||||||
@@ -88,6 +88,7 @@ func TestRunnerHelpListsOnlySupportedCommands(t *testing.T) {
|
|||||||
t.Fatalf("Run(--help) error = %v", err)
|
t.Fatalf("Run(--help) error = %v", err)
|
||||||
}
|
}
|
||||||
for _, command := range []string{
|
for _, command := range []string{
|
||||||
|
"--version",
|
||||||
"generate daily", "generate today", "generate tomorrow", "generate hourly", "run morning", "run evening",
|
"generate daily", "generate today", "generate tomorrow", "generate hourly", "run morning", "run evening",
|
||||||
"inspect reports", "inspect metadata", "inspect modules", "inspect data-package", "inspect prior", "inspect sources",
|
"inspect reports", "inspect metadata", "inspect modules", "inspect data-package", "inspect prior", "inspect sources",
|
||||||
} {
|
} {
|
||||||
@@ -102,6 +103,30 @@ func TestRunnerHelpListsOnlySupportedCommands(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRunnerVersion(t *testing.T) {
|
||||||
|
for _, test := range []struct {
|
||||||
|
name string
|
||||||
|
runner Runner
|
||||||
|
version string
|
||||||
|
}{
|
||||||
|
{name: "development default", runner: Runner{}, version: "development"},
|
||||||
|
{name: "injected release", runner: Runner{Version: "v0.9.0-test"}, version: "v0.9.0-test"},
|
||||||
|
} {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
output, err := runCLICommand(test.runner, "--version")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Run(--version) error = %v", err)
|
||||||
|
}
|
||||||
|
if output.stdout != "weatherreporter "+test.version+"\n" || output.stderr != "" {
|
||||||
|
t.Fatalf("Run(--version) output = stdout %q stderr %q", output.stdout, output.stderr)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if _, err := runCLICommand(Runner{Version: "v0.9.0-test"}, "--version", "extra"); err == nil {
|
||||||
|
t.Fatal("Run(--version extra) error = nil")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestResolveSupportedCommandsAndFlags(t *testing.T) {
|
func TestResolveSupportedCommandsAndFlags(t *testing.T) {
|
||||||
configPath := writeCLIConfig(t, t.TempDir(), "")
|
configPath := writeCLIConfig(t, t.TempDir(), "")
|
||||||
runner, constructions := countingRunner(cliExecutor{})
|
runner, constructions := countingRunner(cliExecutor{})
|
||||||
|
|||||||
Reference in New Issue
Block a user