Adopt ReleaseKit module publishing
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# Implement the optional integration modules and captured registration API
|
||||
|
||||
Status: implementation in progress. Stages 1 through 13 are complete; subsequent work follows the ordered roadmap.
|
||||
Status: implementation in progress. Stages 1 through 14 are complete; subsequent work follows the ordered roadmap.
|
||||
Execute the stages in order. Each stage is sized as a separate Luna coding-agent
|
||||
assignment. This document authorizes no publication or changes to sibling
|
||||
repositories.
|
||||
@@ -687,10 +687,16 @@ required.
|
||||
exercise to include a nested module with a distinct prefixed tag and module
|
||||
note. Default tests remain offline and do not publish.
|
||||
|
||||
**Completion evidence:** ordinary launcher tests pass without network/publication.
|
||||
The optional disposable exercise demonstrates one root and one nested module;
|
||||
run it only when its dependencies are prepared. A sibling checkout or ad hoc
|
||||
ReleaseKit source build is not the selected consumer tool.
|
||||
**Completion evidence — complete:** offline launcher tests pass for all three
|
||||
selectors, usage failures before bootstrap, paths containing spaces, exact
|
||||
profile/module routing, delegated environment settings, and delegated exit
|
||||
statuses. The opt-in disposable exercise passed for both a root module tag and
|
||||
the prefixed `integrations/ckconfig/v0.0.1` tag with its module release note;
|
||||
the endpoint guard also stopped a later publication before validation. ReleaseKit
|
||||
v0.4.0 was resolved from its tagged module, and the workspace-off root module
|
||||
graph contains no ConfigKit or PromptKit dependency. No publication occurred.
|
||||
A sibling checkout or ad hoc ReleaseKit source build is not the selected
|
||||
consumer tool.
|
||||
|
||||
## Stage 15: Reconcile documentation, licensing, and release preparation
|
||||
|
||||
|
||||
2
go.mod
2
go.mod
@@ -9,7 +9,7 @@ require (
|
||||
)
|
||||
|
||||
require (
|
||||
gitea.maximumdirect.net/eric/ReleaseKit v0.3.0 // indirect
|
||||
gitea.maximumdirect.net/eric/ReleaseKit v0.4.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/yuin/goldmark/v2 v2.1.6 // indirect
|
||||
golang.org/x/mod v0.40.0 // indirect
|
||||
|
||||
4
go.sum
4
go.sum
@@ -1,5 +1,5 @@
|
||||
gitea.maximumdirect.net/eric/ReleaseKit v0.3.0 h1:aTMn+50MEpHpHI2RoCCmJYsy7Ohrpa+RSva63Kq0/tU=
|
||||
gitea.maximumdirect.net/eric/ReleaseKit v0.3.0/go.mod h1:LVodqNQ3OIKv9NBq+fFeyMByd1lHY25YOmmpah/NaNI=
|
||||
gitea.maximumdirect.net/eric/ReleaseKit v0.4.0 h1:0FDgysQAA8FJSB57x6kjLtcZexb3sUjRGIuo2OuGI6Q=
|
||||
gitea.maximumdirect.net/eric/ReleaseKit v0.4.0/go.mod h1:LVodqNQ3OIKv9NBq+fFeyMByd1lHY25YOmmpah/NaNI=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
|
||||
13
integrations/ckconfig/releasekit.json
Normal file
13
integrations/ckconfig/releasekit.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"product": "CommandKit ckconfig",
|
||||
"module_dir": "integrations/ckconfig",
|
||||
"remote": "origin",
|
||||
"expected_push_url": "https://gitea.maximumdirect.net/eric/CommandKit.git",
|
||||
"validation": {
|
||||
"shared_files": [
|
||||
"COPYRIGHT",
|
||||
"LICENSE",
|
||||
"docs/oss/attribution.md"
|
||||
]
|
||||
}
|
||||
}
|
||||
13
integrations/ckprompt/releasekit.json
Normal file
13
integrations/ckprompt/releasekit.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"product": "CommandKit ckprompt",
|
||||
"module_dir": "integrations/ckprompt",
|
||||
"remote": "origin",
|
||||
"expected_push_url": "https://gitea.maximumdirect.net/eric/CommandKit.git",
|
||||
"validation": {
|
||||
"shared_files": [
|
||||
"COPYRIGHT",
|
||||
"LICENSE",
|
||||
"docs/oss/attribution.md"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,13 @@
|
||||
{
|
||||
"product": "CommandKit",
|
||||
"module_dir": ".",
|
||||
"remote": "origin",
|
||||
"expected_push_url": "https://gitea.maximumdirect.net/eric/CommandKit.git"
|
||||
"expected_push_url": "https://gitea.maximumdirect.net/eric/CommandKit.git",
|
||||
"validation": {
|
||||
"shared_files": [
|
||||
"COPYRIGHT",
|
||||
"LICENSE",
|
||||
"docs/oss/attribution.md"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,35 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
if [ "$#" -ne 1 ]; then
|
||||
printf '%s\n' 'usage: ./scripts/release VERSION' >&2
|
||||
if [ "$#" -ne 2 ]; then
|
||||
printf '%s\n' 'usage: ./scripts/release MODULE VERSION' >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
release_version=$1
|
||||
case "$1" in
|
||||
core)
|
||||
commandkit_module_dir=.
|
||||
commandkit_profile=releasekit.json
|
||||
;;
|
||||
ckconfig)
|
||||
commandkit_module_dir=integrations/ckconfig
|
||||
commandkit_profile=integrations/ckconfig/releasekit.json
|
||||
;;
|
||||
ckprompt)
|
||||
commandkit_module_dir=integrations/ckprompt
|
||||
commandkit_profile=integrations/ckprompt/releasekit.json
|
||||
;;
|
||||
*)
|
||||
printf '%s\n' 'usage: ./scripts/release MODULE VERSION' >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
|
||||
release_version=$2
|
||||
commandkit_root=$(CDPATH= cd "$(dirname "$0")/.." && pwd)
|
||||
CDPATH= cd "$commandkit_root"
|
||||
|
||||
mkdir -p .releasekit/bin
|
||||
mkdir -p "$commandkit_root/.releasekit/bin"
|
||||
|
||||
commandkit_host_os=$(GOWORK=off GOTOOLCHAIN=local go env GOHOSTOS)
|
||||
commandkit_host_arch=$(GOWORK=off GOTOOLCHAIN=local go env GOHOSTARCH)
|
||||
@@ -23,7 +42,8 @@ go build \
|
||||
-o "$commandkit_root/.releasekit/bin/releasekit" \
|
||||
gitea.maximumdirect.net/eric/ReleaseKit/cmd/releasekit
|
||||
|
||||
exec "$commandkit_root/.releasekit/bin/releasekit" publish \
|
||||
GOWORK=off GOTOOLCHAIN=local exec "$commandkit_root/.releasekit/bin/releasekit" publish \
|
||||
--repo "$commandkit_root" \
|
||||
--config "$commandkit_root/releasekit.json" \
|
||||
--config "$commandkit_root/$commandkit_profile" \
|
||||
--expected-module "$commandkit_module_dir" \
|
||||
"$release_version"
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -36,15 +37,15 @@ func TestReleaseIntegration(t *testing.T) {
|
||||
env := fixture.releaseEnv(marker)
|
||||
|
||||
candidate := gitRun(t, ctx, git, fixture.worktree, env, "rev-parse", "HEAD")
|
||||
stdout, stderr, status := runPublisher(t, ctx, shell, fixture, env, "v0.0.1")
|
||||
stdout, stderr, status := runPublisher(t, ctx, shell, fixture, env, "core", "v0.0.1")
|
||||
if status != 0 {
|
||||
t.Fatalf("publish v0.0.1 status = %d\nstdout:\n%s\nstderr:\n%s", status, stdout, stderr)
|
||||
t.Fatalf("publish core v0.0.1 status = %d\nstdout:\n%s\nstderr:\n%s", status, stdout, stderr)
|
||||
}
|
||||
if !strings.Contains(stdout, "v0.0.1") || !strings.Contains(stdout, candidate) {
|
||||
t.Fatalf("publication output does not identify version and candidate %s:\n%s", candidate, stdout)
|
||||
}
|
||||
assertTag(t, ctx, git, fixture, "v0.0.1", candidate)
|
||||
assertOnlyTag(t, ctx, git, fixture, "v0.0.1")
|
||||
assertTags(t, ctx, git, fixture, "v0.0.1")
|
||||
if got := readLines(t, marker); !equalStrings(got, []string{"candidate suite ran"}) {
|
||||
t.Fatalf("candidate suite marker = %#v, want one run", got)
|
||||
}
|
||||
@@ -58,28 +59,41 @@ func TestReleaseIntegration(t *testing.T) {
|
||||
t.Fatalf("expected ignored bootstrap executable: %v", err)
|
||||
}
|
||||
|
||||
nestedCandidate := gitRun(t, ctx, git, fixture.worktree, env, "rev-parse", "HEAD")
|
||||
stdout, stderr, status = runPublisher(t, ctx, shell, fixture, env, "ckconfig", "v0.0.1")
|
||||
if status != 0 {
|
||||
t.Fatalf("publish ckconfig v0.0.1 status = %d\nstdout:\n%s\nstderr:\n%s", status, stdout, stderr)
|
||||
}
|
||||
nestedTag := "integrations/ckconfig/v0.0.1"
|
||||
if !strings.Contains(stdout, nestedTag) || !strings.Contains(stdout, nestedCandidate) {
|
||||
t.Fatalf("nested publication output does not identify tag %s and candidate %s:\n%s", nestedTag, nestedCandidate, stdout)
|
||||
}
|
||||
assertTag(t, ctx, git, fixture, nestedTag, nestedCandidate)
|
||||
assertTags(t, ctx, git, fixture, "v0.0.1", nestedTag)
|
||||
|
||||
fixture.prepareSecondCandidate(t, ctx, git)
|
||||
stdout, stderr, status = runPublisher(t, ctx, shell, fixture, env, "v0.0.2")
|
||||
stdout, stderr, status = runPublisher(t, ctx, shell, fixture, env, "core", "v0.0.2")
|
||||
if status != 1 {
|
||||
t.Fatalf("publish with mismatched endpoint status = %d, want 1\nstdout:\n%s\nstderr:\n%s", status, stdout, stderr)
|
||||
}
|
||||
if !strings.Contains(strings.ToLower(stderr), "unexpected publication push url") {
|
||||
t.Fatalf("endpoint mismatch diagnostic not found in stderr:\n%s", stderr)
|
||||
}
|
||||
assertOnlyTag(t, ctx, git, fixture, "v0.0.1")
|
||||
assertTags(t, ctx, git, fixture, "v0.0.1", nestedTag)
|
||||
if got := readLines(t, marker); !equalStrings(got, []string{"candidate suite ran"}) {
|
||||
t.Fatalf("candidate suite ran after endpoint preflight failure: %#v", got)
|
||||
}
|
||||
}
|
||||
|
||||
type publicationFixture struct {
|
||||
root string
|
||||
worktree string
|
||||
bare string
|
||||
outside string
|
||||
profile string
|
||||
gitConfig string
|
||||
gitEnv []string
|
||||
root string
|
||||
worktree string
|
||||
bare string
|
||||
outside string
|
||||
profile string
|
||||
nestedProfile string
|
||||
gitConfig string
|
||||
gitEnv []string
|
||||
}
|
||||
|
||||
func newPublicationFixture(t *testing.T, ctx context.Context, git string) publicationFixture {
|
||||
@@ -124,6 +138,13 @@ func newPublicationFixture(t *testing.T, ctx context.Context, git string) public
|
||||
for _, name := range []string{"go.mod", "go.sum", ".gitignore"} {
|
||||
copyFile(t, filepath.Join(commandkitRoot, name), filepath.Join(worktree, name), 0o644)
|
||||
}
|
||||
for _, name := range []string{"COPYRIGHT", "LICENSE", "docs/oss/attribution.md"} {
|
||||
target := filepath.Join(worktree, name)
|
||||
if err := os.MkdirAll(filepath.Dir(target), 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
copyFile(t, filepath.Join(commandkitRoot, name), target, 0o644)
|
||||
}
|
||||
script := filepath.Join(worktree, "scripts", "release")
|
||||
if err := os.MkdirAll(filepath.Dir(script), 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -147,6 +168,35 @@ func newPublicationFixture(t *testing.T, ctx context.Context, git string) public
|
||||
t.Fatal(err)
|
||||
}
|
||||
writeFile(t, profile, string(profileData)+"\n", 0o644)
|
||||
nestedModule := filepath.Join(worktree, "integrations", "ckconfig")
|
||||
if err := os.MkdirAll(nestedModule, 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
writeFile(t, filepath.Join(nestedModule, "go.mod"), "module gitea.maximumdirect.net/eric/CommandKit/integrations/ckconfig\n\ngo 1.26.0\n", 0o644)
|
||||
writeFile(t, filepath.Join(nestedModule, "fixture.go"), "package fixture\n\nfunc Ready() bool { return true }\n", 0o644)
|
||||
writeFile(t, filepath.Join(nestedModule, "fixture_test.go"), "package fixture\n\nimport \"testing\"\n\nfunc TestReady(t *testing.T) { if !Ready() { t.Fatal(\"not ready\") } }\n", 0o644)
|
||||
if err := os.MkdirAll(filepath.Join(nestedModule, "docs", "releases"), 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
writeFile(t, filepath.Join(nestedModule, "docs", "releases", "v0.0.1.md"), releaseNoteFor("CommandKit ckconfig", "v0.0.1"), 0o644)
|
||||
nestedProfile := filepath.Join(nestedModule, "releasekit.json")
|
||||
nestedProfileData, err := os.ReadFile(filepath.Join(commandkitRoot, "integrations", "ckconfig", "releasekit.json"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var nestedFields map[string]json.RawMessage
|
||||
if err := json.Unmarshal(nestedProfileData, &nestedFields); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
nestedFields["expected_push_url"], err = json.Marshal(bare)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
nestedProfileData, err = json.MarshalIndent(nestedFields, "", " ")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
writeFile(t, nestedProfile, string(nestedProfileData)+"\n", 0o644)
|
||||
writeFile(t, filepath.Join(worktree, "fixture_test.go"), fixtureSuiteTest, 0o644)
|
||||
if err := os.MkdirAll(filepath.Join(worktree, "docs", "releases"), 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -158,13 +208,14 @@ func newPublicationFixture(t *testing.T, ctx context.Context, git string) public
|
||||
gitRun(t, ctx, git, worktree, gitEnv, "push", "origin", "HEAD:refs/heads/main")
|
||||
|
||||
return publicationFixture{
|
||||
root: root,
|
||||
worktree: worktree,
|
||||
bare: bare,
|
||||
outside: outside,
|
||||
profile: profile,
|
||||
gitConfig: configPath,
|
||||
gitEnv: gitEnv,
|
||||
root: root,
|
||||
worktree: worktree,
|
||||
bare: bare,
|
||||
outside: outside,
|
||||
profile: profile,
|
||||
nestedProfile: nestedProfile,
|
||||
gitConfig: configPath,
|
||||
gitEnv: gitEnv,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -209,9 +260,9 @@ func (fixture publicationFixture) prepareSecondCandidate(t *testing.T, ctx conte
|
||||
gitRun(t, ctx, git, fixture.worktree, fixture.gitEnv, "push", "origin", "HEAD:refs/heads/main")
|
||||
}
|
||||
|
||||
func runPublisher(t *testing.T, ctx context.Context, shell string, fixture publicationFixture, env []string, version string) (string, string, int) {
|
||||
func runPublisher(t *testing.T, ctx context.Context, shell string, fixture publicationFixture, env []string, module, version string) (string, string, int) {
|
||||
t.Helper()
|
||||
cmd := exec.CommandContext(ctx, shell, filepath.Join(fixture.worktree, "scripts", "release"), version)
|
||||
cmd := exec.CommandContext(ctx, shell, filepath.Join(fixture.worktree, "scripts", "release"), module, version)
|
||||
cmd.Dir = fixture.outside
|
||||
cmd.Env = env
|
||||
var stdout, stderr strings.Builder
|
||||
@@ -251,11 +302,16 @@ func assertTag(t *testing.T, ctx context.Context, git string, fixture publicatio
|
||||
}
|
||||
}
|
||||
|
||||
func assertOnlyTag(t *testing.T, ctx context.Context, git string, fixture publicationFixture, version string) {
|
||||
func assertTags(t *testing.T, ctx context.Context, git string, fixture publicationFixture, versions ...string) {
|
||||
t.Helper()
|
||||
local := gitRun(t, ctx, git, fixture.worktree, fixture.gitEnv, "for-each-ref", "--format=%(refname)", "refs/tags")
|
||||
remote := gitRun(t, ctx, git, fixture.root, fixture.gitEnv, "--git-dir", fixture.bare, "for-each-ref", "--format=%(refname)", "refs/tags")
|
||||
want := "refs/tags/" + version
|
||||
wantRefs := make([]string, len(versions))
|
||||
for i, version := range versions {
|
||||
wantRefs[i] = "refs/tags/" + version
|
||||
}
|
||||
sort.Strings(wantRefs)
|
||||
want := strings.Join(wantRefs, "\n")
|
||||
if local != want || remote != want {
|
||||
t.Fatalf("tag refs local=%q remote=%q, want only %q", local, remote, want)
|
||||
}
|
||||
@@ -309,7 +365,11 @@ func isolatedProcessEnv(overrides map[string]string, cleared ...string) []string
|
||||
}
|
||||
|
||||
func releaseNote(version string) string {
|
||||
return "# CommandKit " + version + "\n\nLocal fixture release candidate.\n"
|
||||
return releaseNoteFor("CommandKit", version)
|
||||
}
|
||||
|
||||
func releaseNoteFor(product, version string) string {
|
||||
return "# " + product + " " + version + "\n\nLocal fixture release candidate.\n"
|
||||
}
|
||||
|
||||
const fixtureSuiteTest = `package fixture
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user