diff --git a/cmd/releasekit/main.go b/cmd/releasekit/main.go index c0948bd..b68cb27 100644 --- a/cmd/releasekit/main.go +++ b/cmd/releasekit/main.go @@ -45,102 +45,9 @@ func run(ctx context.Context, args []string, stdout, stderr io.Writer) int { fmt.Fprintf(stdout, "releasekit %s\n", buildinfo.Version()) return 0 case cli.CheckAction, cli.PublishAction: - settings, err := cli.LoadSettings(invocation.Repository, invocation.ConfigPath) - if err != nil { - fmt.Fprintf(stderr, "load profile: %v\n", err) - return 2 - } - request := candidateRequest(invocation, settings, stderr) - if invocation.Command == cli.PublishAction { - if err := checkExpectedModule(ctx, invocation.Repository, invocation.ExpectedModule, settings.ModuleDir); err != nil { - fmt.Fprintf(stderr, "profile module selection: %v\n", err) - return 2 - } - request.ExpectedPushURL = settings.ExpectedPushURL - fmt.Fprintf(stderr, "validating and publishing module %s version %s\n", selectedModuleLabel(settings.ModuleDir), invocation.Version) - result, err := releasekit.Publish(ctx, request) - if err != nil { - fmt.Fprintf(stderr, "publication state=%s phase=%s module=%s module-dir=%s version=%s tag=%s commit=%s tag-object=%s endpoint=%q: %s\n", result.State, result.Phase, result.ModulePath, result.ModuleDir, result.Version, result.Tag, result.CandidateCommit, result.TagObject, git.DisplayURL(result.Endpoint), candidateFailureText(err)) - if errors.Is(err, releasekit.ErrInvalidVersion) { - return 2 - } - if result.State == releasekit.PublicationUnknown { - return 3 - } - return 1 - } - fmt.Fprintf(stdout, "published %s as %s at %s (tag object %s)\n", result.Version, result.Tag, result.CandidateCommit, result.TagObject) - return 0 - } - candidate, err := releasekit.Check(ctx, request) - if err != nil { - fmt.Fprintf(stderr, "check failed module=%s module-dir=%s version=%s tag=%s commit=%s: %s\n", candidate.ModulePath, candidate.ModuleDir, candidate.Version, candidate.Tag, candidate.Commit, candidateFailureText(err)) - if errors.Is(err, releasekit.ErrInvalidVersion) { - return 2 - } - return 1 - } - fmt.Fprintf(stdout, "local candidate module %s version %s tag %s at %s is valid\n", candidate.ModulePath, candidate.Version, candidate.Tag, candidate.Commit) - return 0 + return runCheckOrPublish(ctx, invocation, stdout, stderr) case cli.BuildAction: - settings, err := cli.LoadSettings(invocation.Repository, invocation.ConfigPath) - if err != nil { - fmt.Fprintf(stderr, "load profile: %v\n", err) - return 2 - } - if settings.Build == nil { - fmt.Fprintln(stderr, "build requires build settings in the profile") - return 2 - } - result, err := releasekit.Build(ctx, releasekit.BuildRequest{ - RepositoryDir: invocation.Repository, - ModuleDir: settings.ModuleDir, - Version: invocation.Version, - Spec: *configuredBuildSpec(settings.Build), - OutputDir: invocation.OutputDir, - Output: stderr, - }) - if result.OutputDir != "" && len(result.Targets) != 0 { - if receiptErr := cli.RemoveBuildReceipt(result.OutputDir); receiptErr != nil { - fmt.Fprintf(stderr, "%v\n", receiptErr) - return 1 - } - } - if err != nil { - fmt.Fprintf(stderr, "build failed module=%s module-dir=%s version=%s tag=%s commit=%s: %v\n", result.ModulePath, result.ModuleDir, result.Version, result.Tag, result.CandidateCommit, err) - for _, artifact := range result.Artifacts { - fmt.Fprintf(stderr, "built %s %s\n", artifact.Filename, artifact.SHA256) - } - if errors.Is(err, releasekit.ErrInvalidVersion) { - return 2 - } - return 1 - } - if len(result.Targets) == 0 { - fmt.Fprintf(stdout, "no build targets selected for module %s version %s tag %s at %s\n", result.ModulePath, result.Version, result.Tag, result.CandidateCommit) - return 0 - } - receipt := receiptFromBuild(result) - receiptPath, err := cli.WriteBuildReceipt(result.OutputDir, receipt) - if err != nil { - fmt.Fprintf(stderr, "write build receipt: %v\n", err) - return 1 - } - stored, err := cli.LoadBuildReceipt(receiptPath) - if err != nil { - fmt.Fprintf(stderr, "verify build receipt: %v\n", err) - return 1 - } - if !stored.MatchesBuild(result.RepositoryDir, result.CandidateCommit, result.Version, *settings.Build) { - fmt.Fprintln(stderr, "build receipt does not match the selected source and build settings") - return 1 - } - fmt.Fprintf(stdout, "built %d artifacts for module %s version %s tag %s at %s with %s\n", len(result.Artifacts), result.ModulePath, result.Version, result.Tag, result.CandidateCommit, result.Compiler) - for _, artifact := range result.Artifacts { - fmt.Fprintf(stdout, "%s %s\n", artifact.Filename, artifact.SHA256) - } - fmt.Fprintf(stdout, "build receipt: %s\n", receiptPath) - return 0 + return runBuild(ctx, invocation, stdout, stderr) case cli.DeliveryAction: return runDelivery(ctx, invocation, stdout, stderr) case cli.CIDeliveryAction: @@ -149,6 +56,107 @@ func run(ctx context.Context, args []string, stdout, stderr io.Writer) int { return 2 } +func runCheckOrPublish(ctx context.Context, invocation cli.Invocation, stdout, stderr io.Writer) int { + settings, err := cli.LoadSettings(invocation.Repository, invocation.ConfigPath) + if err != nil { + fmt.Fprintf(stderr, "load profile: %v\n", err) + return 2 + } + request := candidateRequest(invocation, settings, stderr) + if invocation.Command == cli.PublishAction { + if err := checkExpectedModule(ctx, invocation.Repository, invocation.ExpectedModule, settings.ModuleDir); err != nil { + fmt.Fprintf(stderr, "profile module selection: %v\n", err) + return 2 + } + request.ExpectedPushURL = settings.ExpectedPushURL + fmt.Fprintf(stderr, "validating and publishing module %s version %s\n", selectedModuleLabel(settings.ModuleDir), invocation.Version) + result, err := releasekit.Publish(ctx, request) + if err != nil { + fmt.Fprintf(stderr, "publication state=%s phase=%s module=%s module-dir=%s version=%s tag=%s commit=%s tag-object=%s endpoint=%q: %s\n", result.State, result.Phase, result.ModulePath, result.ModuleDir, result.Version, result.Tag, result.CandidateCommit, result.TagObject, git.DisplayURL(result.Endpoint), candidateFailureText(err)) + if errors.Is(err, releasekit.ErrInvalidVersion) { + return 2 + } + if result.State == releasekit.PublicationUnknown { + return 3 + } + return 1 + } + fmt.Fprintf(stdout, "published %s as %s at %s (tag object %s)\n", result.Version, result.Tag, result.CandidateCommit, result.TagObject) + return 0 + } + candidate, err := releasekit.Check(ctx, request) + if err != nil { + fmt.Fprintf(stderr, "check failed module=%s module-dir=%s version=%s tag=%s commit=%s: %s\n", candidate.ModulePath, candidate.ModuleDir, candidate.Version, candidate.Tag, candidate.Commit, candidateFailureText(err)) + if errors.Is(err, releasekit.ErrInvalidVersion) { + return 2 + } + return 1 + } + fmt.Fprintf(stdout, "local candidate module %s version %s tag %s at %s is valid\n", candidate.ModulePath, candidate.Version, candidate.Tag, candidate.Commit) + return 0 +} + +func runBuild(ctx context.Context, invocation cli.Invocation, stdout, stderr io.Writer) int { + settings, err := cli.LoadSettings(invocation.Repository, invocation.ConfigPath) + if err != nil { + fmt.Fprintf(stderr, "load profile: %v\n", err) + return 2 + } + if settings.Build == nil { + fmt.Fprintln(stderr, "build requires build settings in the profile") + return 2 + } + result, err := releasekit.Build(ctx, releasekit.BuildRequest{ + RepositoryDir: invocation.Repository, + ModuleDir: settings.ModuleDir, + Version: invocation.Version, + Spec: *configuredBuildSpec(settings.Build), + OutputDir: invocation.OutputDir, + Output: stderr, + }) + if result.OutputDir != "" && len(result.Targets) != 0 { + if receiptErr := cli.RemoveBuildReceipt(result.OutputDir); receiptErr != nil { + fmt.Fprintf(stderr, "%v\n", receiptErr) + return 1 + } + } + if err != nil { + fmt.Fprintf(stderr, "build failed module=%s module-dir=%s version=%s tag=%s commit=%s: %v\n", result.ModulePath, result.ModuleDir, result.Version, result.Tag, result.CandidateCommit, err) + for _, artifact := range result.Artifacts { + fmt.Fprintf(stderr, "built %s %s\n", artifact.Filename, artifact.SHA256) + } + if errors.Is(err, releasekit.ErrInvalidVersion) { + return 2 + } + return 1 + } + if len(result.Targets) == 0 { + fmt.Fprintf(stdout, "no build targets selected for module %s version %s tag %s at %s\n", result.ModulePath, result.Version, result.Tag, result.CandidateCommit) + return 0 + } + receipt := receiptFromBuild(result) + receiptPath, err := cli.WriteBuildReceipt(result.OutputDir, receipt) + if err != nil { + fmt.Fprintf(stderr, "write build receipt: %v\n", err) + return 1 + } + stored, err := cli.LoadBuildReceipt(receiptPath) + if err != nil { + fmt.Fprintf(stderr, "verify build receipt: %v\n", err) + return 1 + } + if !stored.MatchesBuild(result.RepositoryDir, result.CandidateCommit, result.Version, *settings.Build) { + fmt.Fprintln(stderr, "build receipt does not match the selected source and build settings") + return 1 + } + fmt.Fprintf(stdout, "built %d artifacts for module %s version %s tag %s at %s with %s\n", len(result.Artifacts), result.ModulePath, result.Version, result.Tag, result.CandidateCommit, result.Compiler) + for _, artifact := range result.Artifacts { + fmt.Fprintf(stdout, "%s %s\n", artifact.Filename, artifact.SHA256) + } + fmt.Fprintf(stdout, "build receipt: %s\n", receiptPath) + return 0 +} + func candidateRequest(invocation cli.Invocation, settings cli.Settings, output io.Writer) releasekit.ReleaseRequest { request := releasekit.ReleaseRequest{ RepositoryDir: invocation.Repository, diff --git a/docs/roadmap/implementation.md b/docs/roadmap/implementation.md index 195e1fb..cc0a58e 100644 --- a/docs/roadmap/implementation.md +++ b/docs/roadmap/implementation.md @@ -51,7 +51,7 @@ the agent policy; an intermediate compilation dependency is recorded accurately. | 13 | A09 | Complete — bounded reads, size and digest mismatches, stream failures, cancellation, reconciliation, and public prebuilt delivery checks passed. | | 14 | A10 | Complete — bounded and full capture, forwarding, error identity, concurrency, and benchmark modes passed. | | 15 | A13 | Complete — validation ordering, scope, tree lifecycle, diagnostics, and public callback checks passed. | -| 16 | A15 | Pending | +| 16 | A15 | Complete — the full CLI command suite passed after workflow extraction. | | 17 | Remaining A19, A20 | Pending | | 18 | A11, A17: assessment only | Pending | | 19 | Integrated verification and current documentation | Pending |