From 18e81a55798e8ab90f8284096692f9f371a7b294 Mon Sep 17 00:00:00 2001 From: Eric Rakestraw Date: Sun, 11 Oct 2026 01:54:04 +0000 Subject: [PATCH] Adopt ReleaseKit module publishing --- docs/roadmap/implementation.md | 16 ++- go.mod | 2 +- go.sum | 4 +- integrations/ckconfig/releasekit.json | 13 ++ integrations/ckprompt/releasekit.json | 13 ++ releasekit.json | 10 +- scripts/release | 32 ++++- scripts/release_integration_test.go | 108 +++++++++++---- scripts/release_test.go | 187 +++++++++++++------------- 9 files changed, 254 insertions(+), 131 deletions(-) create mode 100644 integrations/ckconfig/releasekit.json create mode 100644 integrations/ckprompt/releasekit.json diff --git a/docs/roadmap/implementation.md b/docs/roadmap/implementation.md index ab3f5e5..4a60816 100644 --- a/docs/roadmap/implementation.md +++ b/docs/roadmap/implementation.md @@ -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 diff --git a/go.mod b/go.mod index 7572384..05cb2dc 100644 --- a/go.mod +++ b/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 diff --git a/go.sum b/go.sum index 12b4c7e..0aa5377 100644 --- a/go.sum +++ b/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= diff --git a/integrations/ckconfig/releasekit.json b/integrations/ckconfig/releasekit.json new file mode 100644 index 0000000..c727dc3 --- /dev/null +++ b/integrations/ckconfig/releasekit.json @@ -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" + ] + } +} diff --git a/integrations/ckprompt/releasekit.json b/integrations/ckprompt/releasekit.json new file mode 100644 index 0000000..6f43b14 --- /dev/null +++ b/integrations/ckprompt/releasekit.json @@ -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" + ] + } +} diff --git a/releasekit.json b/releasekit.json index c4852b9..81e35eb 100644 --- a/releasekit.json +++ b/releasekit.json @@ -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" + ] + } } diff --git a/scripts/release b/scripts/release index b3ae9e6..4b43e0a 100755 --- a/scripts/release +++ b/scripts/release @@ -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" diff --git a/scripts/release_integration_test.go b/scripts/release_integration_test.go index 8857ad8..af2b0ef 100644 --- a/scripts/release_integration_test.go +++ b/scripts/release_integration_test.go @@ -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 diff --git a/scripts/release_test.go b/scripts/release_test.go index 868ca51..413ffec 100644 --- a/scripts/release_test.go +++ b/scripts/release_test.go @@ -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"