chore(ax): propagate caller context through release workflows
This commit is contained in:
parent
750a4d0ce0
commit
4b57a2bd7d
11 changed files with 156 additions and 53 deletions
|
|
@ -71,7 +71,7 @@ var fromPathCmd = &cli.Command{
|
|||
if fromPath == "" {
|
||||
return errPathRequired
|
||||
}
|
||||
return runBuild(fromPath)
|
||||
return runBuild(cmd.Context(), fromPath)
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -81,14 +81,14 @@ var pwaCmd = &cli.Command{
|
|||
if pwaURL == "" {
|
||||
return errURLRequired
|
||||
}
|
||||
return runPwaBuild(pwaURL)
|
||||
return runPwaBuild(cmd.Context(), pwaURL)
|
||||
},
|
||||
}
|
||||
|
||||
var sdkBuildCmd = &cli.Command{
|
||||
Use: "sdk",
|
||||
RunE: func(cmd *cli.Command, args []string) error {
|
||||
return runBuildSDK(sdkSpec, sdkLang, sdkVersion, sdkDryRun)
|
||||
return runBuildSDK(cmd.Context(), sdkSpec, sdkLang, sdkVersion, sdkDryRun)
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ var (
|
|||
)
|
||||
|
||||
// runPwaBuild downloads a PWA from URL and builds it.
|
||||
func runPwaBuild(pwaURL string) error {
|
||||
func runPwaBuild(ctx context.Context, pwaURL string) error {
|
||||
core.Print(nil, "%s %s", i18n.T("cmd.build.pwa.starting"), pwaURL)
|
||||
|
||||
tempDir, err := ax.TempDir("core-pwa-build-*")
|
||||
|
|
@ -38,17 +38,17 @@ func runPwaBuild(pwaURL string) error {
|
|||
// defer os.RemoveAll(tempDir) // Keep temp dir for debugging
|
||||
core.Print(nil, "%s %s", i18n.T("cmd.build.pwa.downloading_to"), tempDir)
|
||||
|
||||
if err := downloadPWA(pwaURL, tempDir); err != nil {
|
||||
if err := downloadPWA(ctx, pwaURL, tempDir); err != nil {
|
||||
return coreerr.E("pwa.runPwaBuild", i18n.T("common.error.failed", map[string]any{"Action": "download PWA"}), err)
|
||||
}
|
||||
|
||||
return runBuild(tempDir)
|
||||
return runBuild(ctx, tempDir)
|
||||
}
|
||||
|
||||
// downloadPWA fetches a PWA from a URL and saves assets locally.
|
||||
func downloadPWA(baseURL, destDir string) error {
|
||||
func downloadPWA(ctx context.Context, baseURL, destDir string) error {
|
||||
// Fetch the main HTML page
|
||||
resp, err := http.Get(baseURL)
|
||||
resp, err := getWithContext(ctx, baseURL)
|
||||
if err != nil {
|
||||
return coreerr.E("pwa.downloadPWA", i18n.T("common.error.failed", map[string]any{"Action": "fetch URL"})+" "+baseURL, err)
|
||||
}
|
||||
|
|
@ -73,7 +73,7 @@ func downloadPWA(baseURL, destDir string) error {
|
|||
core.Print(nil, "%s %s", i18n.T("cmd.build.pwa.found_manifest"), manifestURL)
|
||||
|
||||
// Fetch and parse the manifest
|
||||
manifest, err := fetchManifest(manifestURL)
|
||||
manifest, err := fetchManifest(ctx, manifestURL)
|
||||
if err != nil {
|
||||
return coreerr.E("pwa.downloadPWA", i18n.T("common.error.failed", map[string]any{"Action": "fetch or parse manifest"}), err)
|
||||
}
|
||||
|
|
@ -81,7 +81,10 @@ func downloadPWA(baseURL, destDir string) error {
|
|||
// Download all assets listed in the manifest
|
||||
assets := collectAssets(manifest, manifestURL)
|
||||
for _, assetURL := range assets {
|
||||
if err := downloadAsset(assetURL, destDir); err != nil {
|
||||
if err := downloadAsset(ctx, assetURL, destDir); err != nil {
|
||||
if ctx.Err() != nil {
|
||||
return coreerr.E("pwa.downloadPWA", "download cancelled", ctx.Err())
|
||||
}
|
||||
core.Print(nil, "%s %s %s: %v", i18n.T("common.label.warning"), i18n.T("common.error.failed", map[string]any{"Action": "download asset"}), assetURL, err)
|
||||
}
|
||||
}
|
||||
|
|
@ -144,8 +147,8 @@ func findManifestURL(htmlContent, baseURL string) (string, error) {
|
|||
}
|
||||
|
||||
// fetchManifest downloads and parses a PWA manifest.
|
||||
func fetchManifest(manifestURL string) (map[string]any, error) {
|
||||
resp, err := http.Get(manifestURL)
|
||||
func fetchManifest(ctx context.Context, manifestURL string) (map[string]any, error) {
|
||||
resp, err := getWithContext(ctx, manifestURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -192,8 +195,8 @@ func collectAssets(manifest map[string]any, manifestURL string) []string {
|
|||
}
|
||||
|
||||
// downloadAsset fetches a single asset and saves it locally.
|
||||
func downloadAsset(assetURL, destDir string) error {
|
||||
resp, err := http.Get(assetURL)
|
||||
func downloadAsset(ctx context.Context, assetURL, destDir string) error {
|
||||
resp, err := getWithContext(ctx, assetURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -221,7 +224,7 @@ func downloadAsset(assetURL, destDir string) error {
|
|||
}
|
||||
|
||||
// runBuild builds a desktop application from a local directory.
|
||||
func runBuild(fromPath string) error {
|
||||
func runBuild(ctx context.Context, fromPath string) error {
|
||||
core.Print(nil, "%s %s", i18n.T("cmd.build.from_path.starting"), fromPath)
|
||||
|
||||
if !ax.IsDir(fromPath) {
|
||||
|
|
@ -266,12 +269,12 @@ func runBuild(fromPath string) error {
|
|||
core.Println(i18n.T("cmd.build.from_path.compiling"))
|
||||
|
||||
// Run go mod tidy
|
||||
if err := ax.ExecDir(context.Background(), buildDir, "go", "mod", "tidy"); err != nil {
|
||||
if err := ax.ExecDir(ctx, buildDir, "go", "mod", "tidy"); err != nil {
|
||||
return coreerr.E("pwa.runBuild", i18n.T("cmd.build.from_path.error.go_mod_tidy"), err)
|
||||
}
|
||||
|
||||
// Run go build
|
||||
if err := ax.ExecDir(context.Background(), buildDir, "go", "build", "-o", outputExe); err != nil {
|
||||
if err := ax.ExecDir(ctx, buildDir, "go", "build", "-o", outputExe); err != nil {
|
||||
return coreerr.E("pwa.runBuild", i18n.T("cmd.build.from_path.error.go_build"), err)
|
||||
}
|
||||
|
||||
|
|
@ -280,6 +283,14 @@ func runBuild(fromPath string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func getWithContext(ctx context.Context, targetURL string) (*http.Response, error) {
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, targetURL, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return http.DefaultClient.Do(req)
|
||||
}
|
||||
|
||||
// copyDir recursively copies a directory from src to dst.
|
||||
func copyDir(src, dst string) error {
|
||||
if err := ax.MkdirAll(dst, 0o755); err != nil {
|
||||
|
|
|
|||
|
|
@ -17,9 +17,7 @@ import (
|
|||
)
|
||||
|
||||
// runBuildSDK handles the `core build sdk` command.
|
||||
func runBuildSDK(specPath, lang, version string, dryRun bool) error {
|
||||
ctx := context.Background()
|
||||
|
||||
func runBuildSDK(ctx context.Context, specPath, lang, version string, dryRun bool) error {
|
||||
projectDir, err := ax.Getwd()
|
||||
if err != nil {
|
||||
return coreerr.E("build.SDK", "failed to get working directory", err)
|
||||
|
|
|
|||
29
cmd/ci/ci.go
29
cmd/ci/ci.go
|
|
@ -38,7 +38,7 @@ var ciCmd = &cli.Command{
|
|||
Use: "ci",
|
||||
RunE: func(cmd *cli.Command, args []string) error {
|
||||
dryRun := !ciGoForLaunch
|
||||
return runCIPublish(dryRun, ciVersion, ciDraft, ciPrerelease)
|
||||
return runCIPublish(cmd.Context(), dryRun, ciVersion, ciDraft, ciPrerelease)
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -52,14 +52,14 @@ var ciInitCmd = &cli.Command{
|
|||
var ciChangelogCmd = &cli.Command{
|
||||
Use: "changelog",
|
||||
RunE: func(cmd *cli.Command, args []string) error {
|
||||
return runChangelog(changelogFromRef, changelogToRef)
|
||||
return runChangelog(cmd.Context(), changelogFromRef, changelogToRef)
|
||||
},
|
||||
}
|
||||
|
||||
var ciVersionCmd = &cli.Command{
|
||||
Use: "version",
|
||||
RunE: func(cmd *cli.Command, args []string) error {
|
||||
return runCIReleaseVersion()
|
||||
return runCIReleaseVersion(cmd.Context())
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -92,9 +92,7 @@ func initCIFlags() {
|
|||
}
|
||||
|
||||
// runCIPublish publishes pre-built artifacts from dist/.
|
||||
func runCIPublish(dryRun bool, version string, draft, prerelease bool) error {
|
||||
ctx := context.Background()
|
||||
|
||||
func runCIPublish(ctx context.Context, dryRun bool, version string, draft, prerelease bool) error {
|
||||
projectDir, err := ax.Getwd()
|
||||
if err != nil {
|
||||
return cli.WrapVerb(err, "get", "working directory")
|
||||
|
|
@ -182,14 +180,14 @@ func runCIReleaseInit() error {
|
|||
}
|
||||
|
||||
// runChangelog generates a changelog between two git refs.
|
||||
func runChangelog(fromRef, toRef string) error {
|
||||
func runChangelog(ctx context.Context, fromRef, toRef string) error {
|
||||
cwd, err := ax.Getwd()
|
||||
if err != nil {
|
||||
return cli.Err("%s: %w", i18n.T("i18n.fail.get", "working directory"), err)
|
||||
}
|
||||
|
||||
if fromRef == "" || toRef == "" {
|
||||
tag, err := latestTag(cwd)
|
||||
tag, err := latestTagWithContext(ctx, cwd)
|
||||
if err == nil {
|
||||
if fromRef == "" {
|
||||
fromRef = tag
|
||||
|
|
@ -198,6 +196,9 @@ func runChangelog(fromRef, toRef string) error {
|
|||
toRef = "HEAD"
|
||||
}
|
||||
} else {
|
||||
if ctx.Err() != nil {
|
||||
return ctx.Err()
|
||||
}
|
||||
cli.Text(i18n.T("cmd.ci.changelog.no_tags"))
|
||||
return nil
|
||||
}
|
||||
|
|
@ -205,7 +206,7 @@ func runChangelog(fromRef, toRef string) error {
|
|||
|
||||
cli.Print("%s %s..%s\n\n", dimStyle.Render(i18n.T("cmd.ci.changelog.generating")), fromRef, toRef)
|
||||
|
||||
changelog, err := release.Generate(cwd, fromRef, toRef)
|
||||
changelog, err := release.GenerateWithContext(ctx, cwd, fromRef, toRef)
|
||||
if err != nil {
|
||||
return cli.Err("%s: %w", i18n.T("i18n.fail.generate", "changelog"), err)
|
||||
}
|
||||
|
|
@ -215,13 +216,13 @@ func runChangelog(fromRef, toRef string) error {
|
|||
}
|
||||
|
||||
// runCIReleaseVersion shows the determined version.
|
||||
func runCIReleaseVersion() error {
|
||||
func runCIReleaseVersion(ctx context.Context) error {
|
||||
projectDir, err := ax.Getwd()
|
||||
if err != nil {
|
||||
return cli.WrapVerb(err, "get", "working directory")
|
||||
}
|
||||
|
||||
version, err := release.DetermineVersion(projectDir)
|
||||
version, err := release.DetermineVersionWithContext(ctx, projectDir)
|
||||
if err != nil {
|
||||
return cli.WrapVerb(err, "determine", "version")
|
||||
}
|
||||
|
|
@ -231,7 +232,11 @@ func runCIReleaseVersion() error {
|
|||
}
|
||||
|
||||
func latestTag(dir string) (string, error) {
|
||||
out, err := ax.RunDir(context.Background(), dir, "git", "describe", "--tags", "--abbrev=0")
|
||||
return latestTagWithContext(context.Background(), dir)
|
||||
}
|
||||
|
||||
func latestTagWithContext(ctx context.Context, dir string) (string, error) {
|
||||
out, err := ax.RunDir(ctx, dir, "git", "describe", "--tags", "--abbrev=0")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -275,7 +275,7 @@ func (p *BuildProvider) triggerBuild(c *gin.Context) {
|
|||
}
|
||||
|
||||
// Determine version
|
||||
version, verr := release.DetermineVersion(dir)
|
||||
version, verr := release.DetermineVersionWithContext(c.Request.Context(), dir)
|
||||
if verr != nil {
|
||||
version = "dev"
|
||||
}
|
||||
|
|
@ -406,7 +406,7 @@ func (p *BuildProvider) getVersion(c *gin.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
version, err := release.DetermineVersion(dir)
|
||||
version, err := release.DetermineVersionWithContext(c.Request.Context(), dir)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, api.Fail("version_failed", err.Error()))
|
||||
return
|
||||
|
|
@ -428,7 +428,7 @@ func (p *BuildProvider) getChangelog(c *gin.Context) {
|
|||
fromRef := c.Query("from")
|
||||
toRef := c.Query("to")
|
||||
|
||||
changelog, err := release.Generate(dir, fromRef, toRef)
|
||||
changelog, err := release.GenerateWithContext(c.Request.Context(), dir, fromRef, toRef)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, api.Fail("changelog_failed", err.Error()))
|
||||
return
|
||||
|
|
|
|||
|
|
@ -64,14 +64,25 @@ var conventionalCommitRegex = regexp.MustCompile(`^(\w+)(?:\(([^)]+)\))?(!)?:\s*
|
|||
// If toRef is empty, it uses HEAD.
|
||||
// Usage example: call release.Generate(...) from integrating code.
|
||||
func Generate(dir, fromRef, toRef string) (string, error) {
|
||||
return GenerateWithContext(context.Background(), dir, fromRef, toRef)
|
||||
}
|
||||
|
||||
// GenerateWithContext generates a markdown changelog while honouring caller cancellation.
|
||||
// If fromRef is empty, it uses the previous tag or initial commit.
|
||||
// If toRef is empty, it uses HEAD.
|
||||
// Usage example: call release.GenerateWithContext(...) from integrating code.
|
||||
func GenerateWithContext(ctx context.Context, dir, fromRef, toRef string) (string, error) {
|
||||
if toRef == "" {
|
||||
toRef = "HEAD"
|
||||
}
|
||||
|
||||
// If fromRef is empty, try to find previous tag
|
||||
if fromRef == "" {
|
||||
prevTag, err := getPreviousTag(dir, toRef)
|
||||
prevTag, err := getPreviousTagWithContext(ctx, dir, toRef)
|
||||
if err != nil {
|
||||
if ctx.Err() != nil {
|
||||
return "", coreerr.E("changelog.Generate", "generation cancelled", ctx.Err())
|
||||
}
|
||||
// No previous tag, use initial commit
|
||||
fromRef = ""
|
||||
} else {
|
||||
|
|
@ -80,7 +91,7 @@ func Generate(dir, fromRef, toRef string) (string, error) {
|
|||
}
|
||||
|
||||
// Get commits between refs
|
||||
commits, err := getCommits(dir, fromRef, toRef)
|
||||
commits, err := getCommitsWithContext(ctx, dir, fromRef, toRef)
|
||||
if err != nil {
|
||||
return "", coreerr.E("changelog.Generate", "failed to get commits", err)
|
||||
}
|
||||
|
|
@ -101,14 +112,23 @@ func Generate(dir, fromRef, toRef string) (string, error) {
|
|||
// GenerateWithConfig generates a changelog with filtering based on config.
|
||||
// Usage example: call release.GenerateWithConfig(...) from integrating code.
|
||||
func GenerateWithConfig(dir, fromRef, toRef string, cfg *ChangelogConfig) (string, error) {
|
||||
return GenerateWithConfigWithContext(context.Background(), dir, fromRef, toRef, cfg)
|
||||
}
|
||||
|
||||
// GenerateWithConfigWithContext generates a filtered changelog while honouring caller cancellation.
|
||||
// Usage example: call release.GenerateWithConfigWithContext(...) from integrating code.
|
||||
func GenerateWithConfigWithContext(ctx context.Context, dir, fromRef, toRef string, cfg *ChangelogConfig) (string, error) {
|
||||
if toRef == "" {
|
||||
toRef = "HEAD"
|
||||
}
|
||||
|
||||
// If fromRef is empty, try to find previous tag
|
||||
if fromRef == "" {
|
||||
prevTag, err := getPreviousTag(dir, toRef)
|
||||
prevTag, err := getPreviousTagWithContext(ctx, dir, toRef)
|
||||
if err != nil {
|
||||
if ctx.Err() != nil {
|
||||
return "", coreerr.E("changelog.GenerateWithConfig", "generation cancelled", ctx.Err())
|
||||
}
|
||||
fromRef = ""
|
||||
} else {
|
||||
fromRef = prevTag
|
||||
|
|
@ -116,7 +136,7 @@ func GenerateWithConfig(dir, fromRef, toRef string, cfg *ChangelogConfig) (strin
|
|||
}
|
||||
|
||||
// Get commits between refs
|
||||
commits, err := getCommits(dir, fromRef, toRef)
|
||||
commits, err := getCommitsWithContext(ctx, dir, fromRef, toRef)
|
||||
if err != nil {
|
||||
return "", coreerr.E("changelog.GenerateWithConfig", "failed to get commits", err)
|
||||
}
|
||||
|
|
@ -155,7 +175,11 @@ func GenerateWithConfig(dir, fromRef, toRef string, cfg *ChangelogConfig) (strin
|
|||
|
||||
// getPreviousTag returns the tag before the given ref.
|
||||
func getPreviousTag(dir, ref string) (string, error) {
|
||||
output, err := ax.RunDir(context.Background(), dir, "git", "describe", "--tags", "--abbrev=0", ref+"^")
|
||||
return getPreviousTagWithContext(context.Background(), dir, ref)
|
||||
}
|
||||
|
||||
func getPreviousTagWithContext(ctx context.Context, dir, ref string) (string, error) {
|
||||
output, err := ax.RunDir(ctx, dir, "git", "describe", "--tags", "--abbrev=0", ref+"^")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
|
@ -165,6 +189,10 @@ func getPreviousTag(dir, ref string) (string, error) {
|
|||
// getCommits returns a slice of commit strings between two refs.
|
||||
// Format: "hash subject"
|
||||
func getCommits(dir, fromRef, toRef string) ([]string, error) {
|
||||
return getCommitsWithContext(context.Background(), dir, fromRef, toRef)
|
||||
}
|
||||
|
||||
func getCommitsWithContext(ctx context.Context, dir, fromRef, toRef string) ([]string, error) {
|
||||
var args []string
|
||||
if fromRef == "" {
|
||||
// All commits up to toRef
|
||||
|
|
@ -174,7 +202,7 @@ func getCommits(dir, fromRef, toRef string) ([]string, error) {
|
|||
args = []string{"log", "--oneline", "--no-merges", fromRef + ".." + toRef}
|
||||
}
|
||||
|
||||
output, err := ax.RunDir(context.Background(), dir, "git", args...)
|
||||
output, err := ax.RunDir(ctx, dir, "git", args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package release
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"dappco.re/go/core/build/internal/ax"
|
||||
|
|
@ -378,6 +379,18 @@ func TestChangelog_Generate_Bad(t *testing.T) {
|
|||
_, err := Generate(dir, "", "HEAD")
|
||||
assert.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("returns error when context is cancelled", func(t *testing.T) {
|
||||
dir := setupChangelogGitRepo(t)
|
||||
createChangelogCommit(t, dir, "feat: add new feature")
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
cancel()
|
||||
|
||||
_, err := GenerateWithContext(ctx, dir, "", "HEAD")
|
||||
require.Error(t, err)
|
||||
assert.ErrorIs(t, err, context.Canceled)
|
||||
})
|
||||
}
|
||||
|
||||
func TestChangelog_GenerateWithConfig_Good(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ func Publish(ctx context.Context, cfg *Config, dryRun bool) (*Release, error) {
|
|||
// Step 1: Determine version
|
||||
version := cfg.version
|
||||
if version == "" {
|
||||
version, err = DetermineVersion(absProjectDir)
|
||||
version, err = DetermineVersionWithContext(ctx, absProjectDir)
|
||||
if err != nil {
|
||||
return nil, coreerr.E("release.Publish", "failed to determine version", err)
|
||||
}
|
||||
|
|
@ -73,8 +73,11 @@ func Publish(ctx context.Context, cfg *Config, dryRun bool) (*Release, error) {
|
|||
}
|
||||
|
||||
// Step 3: Generate changelog
|
||||
changelog, err := Generate(absProjectDir, "", version)
|
||||
changelog, err := GenerateWithContext(ctx, absProjectDir, "", version)
|
||||
if err != nil {
|
||||
if ctx.Err() != nil {
|
||||
return nil, coreerr.E("release.Publish", "changelog generation cancelled", ctx.Err())
|
||||
}
|
||||
// Non-fatal: continue with empty changelog
|
||||
changelog = core.Sprintf("Release %s", version)
|
||||
}
|
||||
|
|
@ -167,15 +170,18 @@ func Run(ctx context.Context, cfg *Config, dryRun bool) (*Release, error) {
|
|||
// Step 1: Determine version
|
||||
version := cfg.version
|
||||
if version == "" {
|
||||
version, err = DetermineVersion(absProjectDir)
|
||||
version, err = DetermineVersionWithContext(ctx, absProjectDir)
|
||||
if err != nil {
|
||||
return nil, coreerr.E("release.Run", "failed to determine version", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Step 2: Generate changelog
|
||||
changelog, err := Generate(absProjectDir, "", version)
|
||||
changelog, err := GenerateWithContext(ctx, absProjectDir, "", version)
|
||||
if err != nil {
|
||||
if ctx.Err() != nil {
|
||||
return nil, coreerr.E("release.Run", "changelog generation cancelled", ctx.Err())
|
||||
}
|
||||
// Non-fatal: continue with empty changelog
|
||||
changelog = core.Sprintf("Release %s", version)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ func RunSDK(ctx context.Context, cfg *Config, dryRun bool) (*SDKRelease, error)
|
|||
version := cfg.version
|
||||
if version == "" {
|
||||
var err error
|
||||
version, err = DetermineVersion(projectDir)
|
||||
version, err = DetermineVersionWithContext(ctx, projectDir)
|
||||
if err != nil {
|
||||
return nil, coreerr.E("release.RunSDK", "failed to determine version", err)
|
||||
}
|
||||
|
|
@ -48,8 +48,11 @@ func RunSDK(ctx context.Context, cfg *Config, dryRun bool) (*SDKRelease, error)
|
|||
|
||||
// Run diff check if enabled
|
||||
if cfg.SDK.Diff.Enabled {
|
||||
breaking, err := checkBreakingChanges(projectDir, cfg.SDK)
|
||||
breaking, err := checkBreakingChanges(ctx, projectDir, cfg.SDK)
|
||||
if err != nil {
|
||||
if ctx.Err() != nil {
|
||||
return nil, coreerr.E("release.RunSDK", "diff check cancelled", ctx.Err())
|
||||
}
|
||||
// Non-fatal: warn and continue
|
||||
core.Print(nil, "Warning: diff check failed: %v", err)
|
||||
} else if breaking {
|
||||
|
|
@ -89,9 +92,9 @@ func RunSDK(ctx context.Context, cfg *Config, dryRun bool) (*SDKRelease, error)
|
|||
}
|
||||
|
||||
// checkBreakingChanges runs oasdiff to detect breaking changes.
|
||||
func checkBreakingChanges(projectDir string, cfg *SDKConfig) (bool, error) {
|
||||
func checkBreakingChanges(ctx context.Context, projectDir string, cfg *SDKConfig) (bool, error) {
|
||||
// Get previous tag for comparison (uses getPreviousTag from changelog.go)
|
||||
prevTag, err := getPreviousTag(projectDir, "HEAD")
|
||||
prevTag, err := getPreviousTagWithContext(ctx, projectDir, "HEAD")
|
||||
if err != nil {
|
||||
return false, coreerr.E("release.checkBreakingChanges", "no previous tag found", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,14 +22,31 @@ var semverRegex = regexp.MustCompile(`^v?(\d+)\.(\d+)\.(\d+)(?:-([a-zA-Z0-9.-]+)
|
|||
//
|
||||
// Usage example: call release.DetermineVersion(...) from integrating code.
|
||||
func DetermineVersion(dir string) (string, error) {
|
||||
return DetermineVersionWithContext(context.Background(), dir)
|
||||
}
|
||||
|
||||
// DetermineVersionWithContext determines the version while honouring caller cancellation.
|
||||
// It checks in order:
|
||||
// 1. Git tag on HEAD
|
||||
// 2. Most recent tag + increment patch
|
||||
// 3. Default to v0.0.1 if no tags exist
|
||||
//
|
||||
// Usage example: call release.DetermineVersionWithContext(...) from integrating code.
|
||||
func DetermineVersionWithContext(ctx context.Context, dir string) (string, error) {
|
||||
// Check if HEAD has a tag
|
||||
headTag, err := getTagOnHead(dir)
|
||||
headTag, err := getTagOnHeadWithContext(ctx, dir)
|
||||
if err == nil && headTag != "" {
|
||||
return normalizeVersion(headTag), nil
|
||||
}
|
||||
if err != nil && ctx.Err() != nil {
|
||||
return "", coreerr.E("release.DetermineVersionWithContext", "version lookup cancelled", ctx.Err())
|
||||
}
|
||||
|
||||
// Get most recent tag
|
||||
latestTag, err := getLatestTag(dir)
|
||||
latestTag, err := getLatestTagWithContext(ctx, dir)
|
||||
if err != nil && ctx.Err() != nil {
|
||||
return "", coreerr.E("release.DetermineVersionWithContext", "version lookup cancelled", ctx.Err())
|
||||
}
|
||||
if err != nil || latestTag == "" {
|
||||
// No tags exist, return default
|
||||
return "v0.0.1", nil
|
||||
|
|
@ -138,7 +155,11 @@ func normalizeVersion(version string) string {
|
|||
|
||||
// getTagOnHead returns the tag on HEAD, if any.
|
||||
func getTagOnHead(dir string) (string, error) {
|
||||
output, err := ax.RunDir(context.Background(), dir, "git", "describe", "--tags", "--exact-match", "HEAD")
|
||||
return getTagOnHeadWithContext(context.Background(), dir)
|
||||
}
|
||||
|
||||
func getTagOnHeadWithContext(ctx context.Context, dir string) (string, error) {
|
||||
output, err := ax.RunDir(ctx, dir, "git", "describe", "--tags", "--exact-match", "HEAD")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
|
@ -147,7 +168,11 @@ func getTagOnHead(dir string) (string, error) {
|
|||
|
||||
// getLatestTag returns the most recent tag in the repository.
|
||||
func getLatestTag(dir string) (string, error) {
|
||||
output, err := ax.RunDir(context.Background(), dir, "git", "describe", "--tags", "--abbrev=0")
|
||||
return getLatestTagWithContext(context.Background(), dir)
|
||||
}
|
||||
|
||||
func getLatestTagWithContext(ctx context.Context, dir string) (string, error) {
|
||||
output, err := ax.RunDir(ctx, dir, "git", "describe", "--tags", "--abbrev=0")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package release
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"dappco.re/go/core/build/internal/ax"
|
||||
|
|
@ -109,6 +110,19 @@ func TestVersion_DetermineVersion_Bad(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
assert.Equal(t, "v0.0.1", version)
|
||||
})
|
||||
|
||||
t.Run("returns error when context is cancelled", func(t *testing.T) {
|
||||
dir := setupGitRepo(t)
|
||||
createCommit(t, dir, "feat: initial commit")
|
||||
createTag(t, dir, "v1.0.0")
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
cancel()
|
||||
|
||||
_, err := DetermineVersionWithContext(ctx, dir)
|
||||
require.Error(t, err)
|
||||
assert.ErrorIs(t, err, context.Canceled)
|
||||
})
|
||||
}
|
||||
|
||||
func TestVersion_GetTagOnHead_Good(t *testing.T) {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue