Route CLI delivery through the facade

This commit is contained in:
2026-10-06 18:35:45 +00:00
parent a67a00a733
commit 579daa9485
9 changed files with 108 additions and 69 deletions

View File

@@ -14,7 +14,6 @@ import (
"strings"
"gitea.maximumdirect.net/eric/ReleaseKit/internal/process"
"gitea.maximumdirect.net/eric/ReleaseKit/internal/tempdir"
)
const Usage = "usage: releasekit version\n releasekit check [--repo DIR] [--config FILE] VERSION\n releasekit publish [--repo DIR] [--config FILE] VERSION\n releasekit build [--repo DIR] [--config FILE] [--output DIR] VERSION\n releasekit deliver [--repo DIR] [--config FILE] [--assets DIR] VERSION"
@@ -203,8 +202,3 @@ func RunChecks(ctx context.Context, repoDir string, checks []CheckCommand, owned
}
return nil
}
// TemporaryBuildDir allocates command-owned storage outside its source checkout.
func TemporaryBuildDir(repository string) (string, error) {
return tempdir.Outside(repository, "releasekit-delivery-")
}

View File

@@ -153,6 +153,12 @@ func runDelivery(ctx context.Context, invocation cli.Invocation, stdout, stderr
fmt.Fprintf(stderr, "load profile: %v\n", err)
return 2
}
return deliverWithSettings(ctx, invocation, settings, stdout, stderr)
}
// deliverWithSettings adapts prepared CLI state to the public delivery facade.
// CI entry points can reuse it after preparing the checkout and loading its profile.
func deliverWithSettings(ctx context.Context, invocation cli.Invocation, settings cli.Settings, stdout, stderr io.Writer) int {
delivery := settings.Delivery
if delivery == nil || strings.TrimSpace(delivery.BaseURL) == "" || strings.TrimSpace(delivery.Owner) == "" || strings.TrimSpace(delivery.Repository) == "" {
fmt.Fprintln(stderr, "deliver requires base_url, owner, and repository in the profile delivery settings")
@@ -173,8 +179,11 @@ func runDelivery(ctx context.Context, invocation cli.Invocation, stdout, stderr
fmt.Fprintln(stderr, "--assets requires build targets in the profile")
return 2
}
var build *releasekit.BuildResult
var temporaryDir string
var buildSpec *releasekit.BuildSpec
var prebuilt *releasekit.BuildResult
if invocation.AssetsDir == "" && settings.Build != nil {
buildSpec = configuredBuildSpec(settings.Build)
}
if hasTargets {
if invocation.AssetsDir != "" {
assetsDir := invocation.AssetsDir
@@ -190,31 +199,7 @@ func runDelivery(ctx context.Context, invocation cli.Invocation, stdout, stderr
fmt.Fprintln(stderr, "build receipt does not match the selected repository, version, or build settings")
return 1
}
build = buildResultFromReceipt(receipt)
} else {
repositoryDir, resolveErr := filepath.Abs(invocation.Repository)
if resolveErr != nil {
fmt.Fprintf(stderr, "resolve repository directory: %v\n", resolveErr)
return 1
}
temporaryDir, err = cli.TemporaryBuildDir(repositoryDir)
if err != nil {
fmt.Fprintf(stderr, "create temporary build directory: %v\n", err)
return 1
}
defer os.RemoveAll(temporaryDir)
result, buildErr := releasekit.Build(ctx, releasekit.BuildRequest{
RepositoryDir: invocation.Repository,
Version: invocation.Version,
Spec: *configuredBuildSpec(settings.Build),
OutputDir: filepath.Join(temporaryDir, "assets"),
Output: stderr,
})
if buildErr != nil {
fmt.Fprintf(stderr, "build tagged-source assets: %v\n", buildErr)
return 1
}
build = &result
prebuilt = buildResultFromReceipt(receipt)
}
}
@@ -225,7 +210,9 @@ func runDelivery(ctx context.Context, invocation cli.Invocation, stdout, stderr
Destination: releasekit.GiteaDestination{
BaseURL: delivery.BaseURL, Owner: delivery.Owner, Repository: delivery.Repository, Token: token,
},
Prebuilt: build,
Build: buildSpec,
Prebuilt: prebuilt,
Output: stderr,
})
if err != nil {
fmt.Fprintf(stderr, "delivery state=%s version=%s commit=%s url=%s: %v\n", result.State, result.Version, result.CandidateCommit, result.ReleaseURL, err)

View File

@@ -286,6 +286,8 @@ func TestDeliverPageOnlyCreatesAndReusesRelease(t *testing.T) {
func TestDeliverBuildsTaggedArtifactsAndReusesAssets(t *testing.T) {
repository := newDeliveryBuildRepository(t)
temporaryRoot := t.TempDir()
t.Setenv("TMPDIR", temporaryRoot)
state := &deliveryServerState{}
server := newDeliveryServer(t, state)
defer server.Close()
@@ -305,6 +307,9 @@ func TestDeliverBuildsTaggedArtifactsAndReusesAssets(t *testing.T) {
if status != 0 {
t.Fatalf("delivery attempt %d status=%d stdout=%q stderr=%q", attempt+1, status, stdout.String(), stderr.String())
}
if attempt == 1 && (!strings.Contains(stdout.String(), "matching helper-v1.2.3-linux-amd64") || !strings.Contains(stdout.String(), "matching SHA256SUMS")) {
t.Fatalf("second delivery did not report matching assets: %q", stdout.String())
}
}
if state.releaseCreates != 1 || state.assetUploads != 2 {
t.Fatalf("delivery calls = creates:%d asset uploads:%d", state.releaseCreates, state.assetUploads)
@@ -315,6 +320,33 @@ func TestDeliverBuildsTaggedArtifactsAndReusesAssets(t *testing.T) {
if _, ok := state.assets["SHA256SUMS"]; !ok {
t.Fatalf("checksum manifest was not uploaded: %#v", state.assets)
}
if entries, err := os.ReadDir(temporaryRoot); err != nil || len(entries) != 0 {
t.Fatalf("facade-owned build output remains after delivery: entries=%v error=%v", entries, err)
}
}
func TestDeliverRejectsInvalidTaggedSourceBeforeManagedBuild(t *testing.T) {
repository := newDeliveryBuildRepository(t)
state := &deliveryServerState{}
server := newDeliveryServer(t, state)
defer server.Close()
settings := cli.Settings{
Product: "Example Product",
Build: &cli.BuildSettings{MainPackage: ".", BinaryName: "helper", Targets: []string{"linux/amd64"}},
Delivery: &cli.DeliverySettings{BaseURL: server.URL, Owner: "owner", Repository: "example"},
}
writeDeliveryProfile(t, repository, settings)
state.commit = cliGitOutput(t, repository, "rev-parse", "HEAD")
cliGitRun(t, repository, "tag", "v1.2.3")
if err := os.WriteFile(filepath.Join(repository, "uncommitted.txt"), []byte("dirty"), 0600); err != nil {
t.Fatal(err)
}
t.Setenv("RELEASEKIT_GITEA_TOKEN", "fixture-token")
var stdout, stderr bytes.Buffer
status := run(context.Background(), []string{"deliver", "--repo", repository, "v1.2.3"}, &stdout, &stderr)
if status != 1 || state.apiRequests != 0 || strings.Contains(stderr.String(), "building ") {
t.Fatalf("invalid-source delivery status=%d requests=%d stdout=%q stderr=%q", status, state.apiRequests, stdout.String(), stderr.String())
}
}
func TestDeliverFailureReportsRecoveryForPartialConflictAndUnknownStates(t *testing.T) {
@@ -478,6 +510,9 @@ func TestDeliverRejectsTamperedAssetsAndStaleReceiptBeforeRemoteWrites(t *testin
if status != 0 || state.releaseCreates != 1 || state.assetUploads != 2 {
t.Fatalf("valid receipt status=%d creates=%d uploads=%d stdout=%q stderr=%q", status, state.releaseCreates, state.assetUploads, stdout.String(), stderr.String())
}
if _, err := os.Stat(artifactPath); err != nil {
t.Fatalf("prebuilt artifact was not retained: %v", err)
}
return
}
if status != 1 || state.apiRequests != 0 || state.releaseCreates != 0 || state.assetUploads != 0 {

View File

@@ -36,6 +36,7 @@ func TestReleaseWrapperPreservesNativeExitStatuses(t *testing.T) {
"internal/gitea/upload.go", "internal/gitea/upload_test.go",
"delivery_test.go", "cmd/releasekit/main_test.go", "cmd/releasekit/release_wrapper_test.go",
"cmd/releasekit/internal/cli/config_test.go", "examples/releasekit.json",
"cmd/releasekit/internal/cli/config.go",
} {
contents, err := os.ReadFile(filepath.Join(root, path))
if err != nil {