Adopt ReleaseKit module publishing

This commit is contained in:
2026-10-11 01:54:04 +00:00
parent e89cc2220f
commit 18e81a5579
9 changed files with 254 additions and 131 deletions

View File

@@ -5,9 +5,7 @@ import (
"bytes"
"context"
"errors"
"flag"
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
@@ -30,7 +28,9 @@ func TestReleaseLauncherArgumentHandling(t *testing.T) {
args []string
}{
{name: "no arguments"},
{name: "multiple arguments", args: []string{"v1.2.3", "extra"}},
{name: "one argument", args: []string{"core"}},
{name: "multiple arguments", args: []string{"core", "v1.2.3", "extra"}},
{name: "unknown module", args: []string{"unknown", "v1.2.3"}},
} {
t.Run(test.name, func(t *testing.T) {
fixture := newFixture(t)
@@ -42,7 +42,7 @@ func TestReleaseLauncherArgumentHandling(t *testing.T) {
if result.stdout != "" {
t.Fatalf("stdout = %q, want empty", result.stdout)
}
if result.stderr != "usage: ./scripts/release VERSION\n" {
if result.stderr != "usage: ./scripts/release MODULE VERSION\n" {
t.Fatalf("stderr = %q, want usage", result.stderr)
}
if _, err := os.Stat(fixture.goLog); !errors.Is(err, os.ErrNotExist) {
@@ -54,63 +54,43 @@ func TestReleaseLauncherArgumentHandling(t *testing.T) {
func TestReleaseLauncherDelegatesPublication(t *testing.T) {
shell := requirePOSIXShell(t)
for _, status := range []int{0, 1, 2, 3} {
t.Run(fmt.Sprintf("exit status %d", status), func(t *testing.T) {
fixture := newFixture(t)
result := runLauncher(t, shell, fixture, []string{releaseVersion}, map[string]string{
"CDPATH": "/tmp",
"GOOS": "windows",
"GOARCH": "arm64",
"PUBLISH_EXIT": strconv.Itoa(status),
"PUBLISH_STDOUT": "publisher stdout",
"PUBLISH_STDERR": "publisher stderr",
for _, module := range []string{"core", "ckconfig", "ckprompt"} {
for _, status := range []int{0, 1, 2, 3} {
t.Run(fmt.Sprintf("%s exit status %d", module, status), func(t *testing.T) {
fixture := newFixture(t)
result := runLauncher(t, shell, fixture, []string{module, releaseVersion}, map[string]string{
"CDPATH": "/tmp",
"GOOS": "windows",
"GOARCH": "arm64",
"GOWORK": "auto",
"GOTOOLCHAIN": "auto",
"PUBLISH_EXIT": strconv.Itoa(status),
"PUBLISH_STDOUT": "publisher stdout",
"PUBLISH_STDERR": "publisher stderr",
})
if result.status != status {
t.Fatalf("status = %d, want %d; stderr: %q", result.status, status, result.stderr)
}
if result.stdout != "publisher stdout\n" {
t.Fatalf("stdout = %q, want publisher output", result.stdout)
}
if result.stderr != "publisher stderr\n" {
t.Fatalf("stderr = %q, want publisher error output", result.stderr)
}
if err := checkPublication(readLines(t, fixture.publishLog), fixture.repoRoot, module); err != nil {
t.Fatal(err)
}
if got := readLines(t, fixture.publishEnvLog); !equalStrings(got, []string{"GOWORK=off", "GOTOOLCHAIN=local"}) {
t.Fatalf("publisher environment = %#v, want workspace disabled and local toolchain", got)
}
assertNativeBootstrap(t, fixture)
})
if result.status != status {
t.Fatalf("status = %d, want %d; stderr: %q", result.status, status, result.stderr)
}
if result.stdout != "publisher stdout\n" {
t.Fatalf("stdout = %q, want publisher output", result.stdout)
}
if result.stderr != "publisher stderr\n" {
t.Fatalf("stderr = %q, want publisher error output", result.stderr)
}
if err := checkPublication(readLines(t, fixture.publishLog), fixture.repoRoot); err != nil {
t.Fatal(err)
}
assertNativeBootstrap(t, fixture)
})
}
}
}
func TestReleaseLauncherAcceptsEquivalentCommands(t *testing.T) {
shell := requirePOSIXShell(t)
fixture := newFixture(t)
// Change only the fixture script: the production launcher need not be refactored.
writeFile(t, fixture.launcher, `#!/bin/sh
set -eu
version=$1
root=$(CDPATH= cd "$(dirname "$0")/.." && pwd)
CDPATH= cd "$root"
mkdir -p .releasekit/bin
set -- $(GOWORK=off GOTOOLCHAIN=local go env GOHOSTOS GOHOSTARCH)
GOOS="$1" GOARCH="$2" GOWORK=off GOTOOLCHAIN=local go build \
-o=.releasekit/bin/releasekit -mod readonly \
gitea.maximumdirect.net/eric/ReleaseKit/cmd/releasekit
exec .releasekit/bin/releasekit publish \
--config="$root/releasekit.json" --repo "$root" "$version"
`, 0o755)
result := runLauncher(t, shell, fixture, []string{releaseVersion}, nil)
if result.status != 0 {
t.Fatalf("equivalent launcher failed: status=%d stderr=%q", result.status, result.stderr)
}
if err := checkPublication(readLines(t, fixture.publishLog), fixture.repoRoot); err != nil {
t.Fatal(err)
}
assertNativeBootstrap(t, fixture)
}
func TestLauncherChecksRejectContractViolations(t *testing.T) {
root := filepath.Join(t.TempDir(), "repository with spaces")
output := filepath.Join(root, ".releasekit", "bin", "releasekit")
@@ -136,13 +116,13 @@ func TestLauncherChecksRejectContractViolations(t *testing.T) {
})
}
for _, args := range [][]string{
{"check", "--repo", root, "--config", filepath.Join(root, "releasekit.json"), releaseVersion},
{"publish", "--repo", "wrong", "--config", filepath.Join(root, "releasekit.json"), releaseVersion},
{"publish", "--repo", root, "--config", "wrong", releaseVersion},
{"publish", "--repo", root, "--config", filepath.Join(root, "releasekit.json"), "wrong-version"},
{"publish", releaseVersion, "--repo", root, "--config", filepath.Join(root, "releasekit.json")},
{"check", "--repo", root, "--config", filepath.Join(root, "releasekit.json"), "--expected-module", ".", releaseVersion},
{"publish", "--repo", "wrong", "--config", filepath.Join(root, "releasekit.json"), "--expected-module", ".", releaseVersion},
{"publish", "--repo", root, "--config", "wrong", "--expected-module", ".", releaseVersion},
{"publish", "--repo", root, "--config", filepath.Join(root, "releasekit.json"), "--expected-module", ".", "wrong-version"},
{"publish", "--repo", root, "--config", filepath.Join(root, "releasekit.json"), releaseVersion, "--expected-module", "."},
} {
if err := checkPublication(args, root); err == nil {
if err := checkPublication(args, root, "core"); err == nil {
t.Errorf("invalid publication accepted: %q", args)
}
}
@@ -151,7 +131,7 @@ func TestLauncherChecksRejectContractViolations(t *testing.T) {
func TestReleaseLauncherStopsAfterBootstrapFailure(t *testing.T) {
shell := requirePOSIXShell(t)
fixture := newFixture(t)
result := runLauncher(t, shell, fixture, []string{releaseVersion}, map[string]string{
result := runLauncher(t, shell, fixture, []string{"core", releaseVersion}, map[string]string{
"FAKE_GO_BUILD_EXIT": "23",
})
@@ -173,13 +153,14 @@ type launcherResult struct {
}
type fixture struct {
repoRoot string
launcher string
outside string
fakeBin string
goLog string
publishLog string
publisher string
repoRoot string
launcher string
outside string
fakeBin string
goLog string
publishLog string
publishEnvLog string
publisher string
}
func newFixture(t *testing.T) fixture {
@@ -205,22 +186,34 @@ func newFixture(t *testing.T) fixture {
}
}
copyFile(t, filepath.Join(repo, "scripts", "release"), filepath.Join(repoRoot, "scripts", "release"), 0o755)
copyFile(t, filepath.Join(repo, "releasekit.json"), filepath.Join(repoRoot, "releasekit.json"), 0o644)
for _, profile := range []string{
"releasekit.json",
"integrations/ckconfig/releasekit.json",
"integrations/ckprompt/releasekit.json",
} {
target := filepath.Join(repoRoot, profile)
if err := os.MkdirAll(filepath.Dir(target), 0o755); err != nil {
t.Fatal(err)
}
copyFile(t, filepath.Join(repo, profile), target, 0o644)
}
goLog := filepath.Join(root, "go calls.log")
publishLog := filepath.Join(root, "publisher arguments.log")
publishEnvLog := filepath.Join(root, "publisher environment.log")
publisher := filepath.Join(root, "fake publisher")
writeFile(t, publisher, fakePublisher, 0o644)
writeFile(t, filepath.Join(fakeBin, "go"), fakeGo, 0o755)
return fixture{
repoRoot: repoRoot,
launcher: filepath.Join(repoRoot, "scripts", "release"),
outside: outside,
fakeBin: fakeBin,
goLog: goLog,
publishLog: publishLog,
publisher: publisher,
repoRoot: repoRoot,
launcher: filepath.Join(repoRoot, "scripts", "release"),
outside: outside,
fakeBin: fakeBin,
goLog: goLog,
publishLog: publishLog,
publishEnvLog: publishEnvLog,
publisher: publisher,
}
}
@@ -255,6 +248,7 @@ func fixtureEnvironment(fixture fixture, overrides map[string]string) []string {
"PATH": fixture.fakeBin + string(os.PathListSeparator) + os.Getenv("PATH"),
"GO_LOG": fixture.goLog,
"PUBLISH_ARGS_LOG": fixture.publishLog,
"PUBLISH_ENV_LOG": fixture.publishEnvLog,
"FAKE_GOHOSTOS": "linux",
"FAKE_GOHOSTARCH": "amd64",
"FAKE_PUBLISHER_SOURCE": fixture.publisher,
@@ -335,22 +329,30 @@ func argumentValue(args []string, name string) string {
return value
}
func checkPublication(args []string, root string) error {
if len(args) == 0 || args[0] != "publish" {
return fmt.Errorf("incorrect publication command: %q", args)
func checkPublication(args []string, root, selector string) error {
modules := map[string]string{
"core": ".",
"ckconfig": "integrations/ckconfig",
"ckprompt": "integrations/ckprompt",
}
flags := flag.NewFlagSet("publish", flag.ContinueOnError)
flags.SetOutput(io.Discard)
repo := flags.String("repo", "", "")
config := flags.String("config", "", "")
if err := flags.Parse(args[1:]); err != nil {
return err
module, ok := modules[selector]
if !ok {
return fmt.Errorf("unknown module selector %q", selector)
}
if *repo != root || *config != filepath.Join(root, "releasekit.json") {
return fmt.Errorf("incorrect publication paths: repo=%q config=%q", *repo, *config)
profile := map[string]string{
"core": "releasekit.json",
"ckconfig": "integrations/ckconfig/releasekit.json",
"ckprompt": "integrations/ckprompt/releasekit.json",
}[selector]
want := []string{
"publish",
"--repo", root,
"--config", filepath.Join(root, profile),
"--expected-module", module,
releaseVersion,
}
if !equalStrings(flags.Args(), []string{releaseVersion}) {
return fmt.Errorf("incorrect publication operands: %q", flags.Args())
if !equalStrings(args, want) {
return fmt.Errorf("publication arguments = %q, want %q", args, want)
}
return nil
}
@@ -497,6 +499,7 @@ exit 92
const fakePublisher = `#!/bin/sh
set -eu
printf '%s\n' "$@" > "$PUBLISH_ARGS_LOG"
printf 'GOWORK=%s\nGOTOOLCHAIN=%s\n' "${GOWORK-}" "${GOTOOLCHAIN-}" > "$PUBLISH_ENV_LOG"
printf '%s\n' "$PUBLISH_STDOUT"
printf '%s\n' "$PUBLISH_STDERR" >&2
exit "$PUBLISH_EXIT"