From fdb684ccf0efd632f88238aa2625a0dcdfa73bab Mon Sep 17 00:00:00 2001 From: Virgil Date: Wed, 1 Apr 2026 23:46:51 +0000 Subject: [PATCH] feat(build): resolve workflow output aliases --- cmd/build/cmd_workflow.go | 70 +++++++++++++++++++++++++++++----- cmd/build/cmd_workflow_test.go | 32 ++++++++++++++++ 2 files changed, 92 insertions(+), 10 deletions(-) diff --git a/cmd/build/cmd_workflow.go b/cmd/build/cmd_workflow.go index 13f07b8..4c1ef30 100644 --- a/cmd/build/cmd_workflow.go +++ b/cmd/build/cmd_workflow.go @@ -4,6 +4,7 @@ package buildcmd import ( "context" + "strings" "dappco.re/go/core/build/internal/ax" "dappco.re/go/core/build/pkg/build" @@ -14,14 +15,22 @@ import ( ) var ( - releaseWorkflowPathInput string - releaseWorkflowOutputPathInput string + releaseWorkflowPathInput string + releaseWorkflowOutputPathInput string + releaseWorkflowOutputPathSnakeInput string + releaseWorkflowOutputLegacyInput string ) var releaseWorkflowCmd = &cli.Command{ Use: "workflow", RunE: func(cmd *cli.Command, args []string) error { - return runReleaseWorkflow(cmd.Context(), releaseWorkflowPathInput, releaseWorkflowOutputPathInput) + return runReleaseWorkflow( + cmd.Context(), + releaseWorkflowPathInput, + releaseWorkflowOutputPathInput, + releaseWorkflowOutputPathSnakeInput, + releaseWorkflowOutputLegacyInput, + ) }, } @@ -33,8 +42,8 @@ func setWorkflowI18n() { func initWorkflowFlags() { releaseWorkflowCmd.Flags().StringVar(&releaseWorkflowPathInput, "path", "", i18n.T("cmd.build.workflow.flag.path")) releaseWorkflowCmd.Flags().StringVar(&releaseWorkflowOutputPathInput, "output-path", "", i18n.T("cmd.build.workflow.flag.output_path")) - releaseWorkflowCmd.Flags().StringVar(&releaseWorkflowOutputPathInput, "output_path", "", i18n.T("cmd.build.workflow.flag.output_path")) - releaseWorkflowCmd.Flags().StringVar(&releaseWorkflowOutputPathInput, "output", "", i18n.T("cmd.build.workflow.flag.output")) + releaseWorkflowCmd.Flags().StringVar(&releaseWorkflowOutputPathSnakeInput, "output_path", "", i18n.T("cmd.build.workflow.flag.output_path")) + releaseWorkflowCmd.Flags().StringVar(&releaseWorkflowOutputLegacyInput, "output", "", i18n.T("cmd.build.workflow.flag.output")) } // buildCmd := &cli.Command{Use: "build"} @@ -48,17 +57,58 @@ func AddWorkflowCommand(buildCmd *cli.Command) { // runReleaseWorkflow writes the embedded release workflow into the current project directory. // // buildcmd.AddWorkflowCommand(buildCmd) -// runReleaseWorkflow(ctx, "", "") // writes to .github/workflows/release.yml -// runReleaseWorkflow(ctx, "ci/release.yml", "") // writes to ./ci/release.yml under the project root -// runReleaseWorkflow(ctx, "", "ci/release.yml") // output-path, output_path, and output are aliases for the output path input -func runReleaseWorkflow(_ context.Context, workflowPathInput, workflowOutputPathInput string) error { +// runReleaseWorkflow(ctx, "", "", "", "") // writes to .github/workflows/release.yml +// runReleaseWorkflow(ctx, "ci/release.yml", "", "", "") // writes to ./ci/release.yml under the project root +// runReleaseWorkflow(ctx, "", "ci/release.yml", "", "") // uses the preferred explicit output path +// runReleaseWorkflow(ctx, "", "", "ci/release.yml", "") // uses the snake_case alias +// runReleaseWorkflow(ctx, "", "", "", "ci/release.yml") // uses the legacy output alias +func runReleaseWorkflow(_ context.Context, workflowPathInput, workflowOutputPathInput, workflowOutputPathSnakeInput, workflowOutputLegacyInput string) error { + resolvedOutputPathInput, err := resolveReleaseWorkflowOutputPathInput( + workflowOutputPathInput, + workflowOutputPathSnakeInput, + workflowOutputLegacyInput, + ) + if err != nil { + return err + } projectDir, err := ax.Getwd() if err != nil { return coreerr.E("build.runReleaseWorkflow", "failed to get working directory", err) } - return runReleaseWorkflowInDir(projectDir, workflowPathInput, workflowOutputPathInput) + return runReleaseWorkflowInDir(projectDir, workflowPathInput, resolvedOutputPathInput) +} + +// resolveReleaseWorkflowOutputPathInput chooses the workflow output path alias +// with deterministic precedence. +// +// resolveReleaseWorkflowOutputPathInput("ci/release.yml", "", "") // "ci/release.yml" +// resolveReleaseWorkflowOutputPathInput("", "ci/release.yml", "") // "ci/release.yml" +// resolveReleaseWorkflowOutputPathInput("", "", "ci/release.yml") // "ci/release.yml" +// resolveReleaseWorkflowOutputPathInput("ci/release.yml", "ops/release.yml", "") // error +func resolveReleaseWorkflowOutputPathInput(outputPathInput, outputPathSnakeInput, outputLegacyInput string) (string, error) { + values := []string{ + strings.TrimSpace(outputPathInput), + strings.TrimSpace(outputPathSnakeInput), + strings.TrimSpace(outputLegacyInput), + } + + var resolved string + for _, value := range values { + if value == "" { + continue + } + if resolved == "" { + resolved = value + continue + } + if resolved != value { + return "", coreerr.E("build.resolveReleaseWorkflowOutputPathInput", "output aliases specify different locations", nil) + } + } + + return resolved, nil } // runReleaseWorkflowInDir writes the embedded release workflow into projectDir. diff --git a/cmd/build/cmd_workflow_test.go b/cmd/build/cmd_workflow_test.go index e0fb277..1bbc31e 100644 --- a/cmd/build/cmd_workflow_test.go +++ b/cmd/build/cmd_workflow_test.go @@ -12,6 +12,38 @@ import ( "github.com/stretchr/testify/require" ) +func TestBuildCmd_resolveReleaseWorkflowOutputPathInput_Good(t *testing.T) { + t.Run("accepts the preferred output path", func(t *testing.T) { + path, err := resolveReleaseWorkflowOutputPathInput("ci/release.yml", "", "") + require.NoError(t, err) + assert.Equal(t, "ci/release.yml", path) + }) + + t.Run("accepts the snake_case output path alias", func(t *testing.T) { + path, err := resolveReleaseWorkflowOutputPathInput("", "ci/release.yml", "") + require.NoError(t, err) + assert.Equal(t, "ci/release.yml", path) + }) + + t.Run("accepts the legacy output alias", func(t *testing.T) { + path, err := resolveReleaseWorkflowOutputPathInput("", "", "ci/release.yml") + require.NoError(t, err) + assert.Equal(t, "ci/release.yml", path) + }) + + t.Run("accepts matching output aliases", func(t *testing.T) { + path, err := resolveReleaseWorkflowOutputPathInput("ci/release.yml", "ci/release.yml", "ci/release.yml") + require.NoError(t, err) + assert.Equal(t, "ci/release.yml", path) + }) +} + +func TestBuildCmd_resolveReleaseWorkflowOutputPathInput_Bad(t *testing.T) { + _, err := resolveReleaseWorkflowOutputPathInput("ci/release.yml", "ops/release.yml", "") + require.Error(t, err) + assert.Contains(t, err.Error(), "output aliases specify different locations") +} + func TestBuildCmd_RunReleaseWorkflow_Good(t *testing.T) { projectDir := t.TempDir()