feat(build): add outputPath workflow alias

Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
Virgil 2026-04-02 02:15:59 +00:00
parent ce1de925be
commit 597e7a678a
5 changed files with 56 additions and 28 deletions

View file

@ -20,6 +20,7 @@ var (
releaseWorkflowWorkflowPathSnakeInput string
releaseWorkflowOutputPathHyphenInput string
releaseWorkflowOutputPathSnakeInput string
releaseWorkflowOutputPathInput string
releaseWorkflowOutputLegacyInput string
releaseWorkflowWorkflowOutputPathInput string
releaseWorkflowWorkflowOutputPathHyphenInput string
@ -33,6 +34,7 @@ type releaseWorkflowInputs struct {
workflowPathInput string
workflowPathHyphenInput string
workflowPathSnakeInput string
outputPathInput string
outputPathHyphenInput string
outputPathSnakeInput string
outputLegacyInput string
@ -49,6 +51,7 @@ var releaseWorkflowCmd = &cli.Command{
workflowPathInput: releaseWorkflowWorkflowPathInput,
workflowPathHyphenInput: releaseWorkflowWorkflowPathHyphenInput,
workflowPathSnakeInput: releaseWorkflowWorkflowPathSnakeInput,
outputPathInput: releaseWorkflowOutputPathInput,
outputPathHyphenInput: releaseWorkflowOutputPathHyphenInput,
outputPathSnakeInput: releaseWorkflowOutputPathSnakeInput,
outputLegacyInput: releaseWorkflowOutputLegacyInput,
@ -69,6 +72,7 @@ func initWorkflowFlags() {
releaseWorkflowCmd.Flags().StringVar(&releaseWorkflowWorkflowPathInput, "workflowPath", "", i18n.T("cmd.build.workflow.flag.path"))
releaseWorkflowCmd.Flags().StringVar(&releaseWorkflowWorkflowPathHyphenInput, "workflow-path", "", i18n.T("cmd.build.workflow.flag.path"))
releaseWorkflowCmd.Flags().StringVar(&releaseWorkflowWorkflowPathSnakeInput, "workflow_path", "", i18n.T("cmd.build.workflow.flag.path"))
releaseWorkflowCmd.Flags().StringVar(&releaseWorkflowOutputPathInput, "outputPath", "", i18n.T("cmd.build.workflow.flag.output_path"))
releaseWorkflowCmd.Flags().StringVar(&releaseWorkflowOutputPathHyphenInput, "output-path", "", i18n.T("cmd.build.workflow.flag.output_path"))
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"))
@ -93,6 +97,7 @@ func AddWorkflowCommand(buildCmd *cli.Command) {
// runReleaseWorkflow(ctx, releaseWorkflowInputs{workflowPathInput: "ci/release.yml"}) // uses the workflowPath alias
// runReleaseWorkflow(ctx, releaseWorkflowInputs{workflowPathHyphenInput: "ci/release.yml"}) // uses the workflow-path alias
// runReleaseWorkflow(ctx, releaseWorkflowInputs{workflowPathSnakeInput: "ci/release.yml"}) // uses the workflow_path alias
// runReleaseWorkflow(ctx, releaseWorkflowInputs{outputPathInput: "ci/release.yml"}) // uses the outputPath alias
// runReleaseWorkflow(ctx, releaseWorkflowInputs{workflowOutputPathInput: "ci/release.yml"}) // uses the workflowOutputPath alias
func runReleaseWorkflow(_ context.Context, inputs releaseWorkflowInputs) error {
projectDir, err := ax.Getwd()
@ -113,6 +118,7 @@ func runReleaseWorkflow(_ context.Context, inputs releaseWorkflowInputs) error {
resolvedWorkflowOutputPath, err := resolveReleaseWorkflowOutputPathAliases(
projectDir,
inputs.outputPathInput,
inputs.outputPathHyphenInput,
inputs.outputPathSnakeInput,
inputs.outputLegacyInput,
@ -147,9 +153,10 @@ func resolveReleaseWorkflowInputPathAliases(projectDir, pathInput, workflowPathI
// resolveReleaseWorkflowOutputPathAliases keeps the CLI error wording stable while
// delegating the conflict detection to the shared build helper.
func resolveReleaseWorkflowOutputPathAliases(projectDir, outputPathHyphenInput, outputPathSnakeInput, outputLegacyInput, workflowOutputPathInput, workflowOutputPathHyphenInput, workflowOutputPathSnakeInput string) (string, error) {
func resolveReleaseWorkflowOutputPathAliases(projectDir, outputPathInput, outputPathHyphenInput, outputPathSnakeInput, outputLegacyInput, workflowOutputPathInput, workflowOutputPathHyphenInput, workflowOutputPathSnakeInput string) (string, error) {
resolvedWorkflowOutputPath, err := build.ResolveReleaseWorkflowOutputPathAliasesInProject(
projectDir,
outputPathInput,
outputPathHyphenInput,
outputPathSnakeInput,
outputLegacyInput,

View file

@ -47,7 +47,7 @@ func TestBuildCmd_resolveReleaseWorkflowOutputPathInput_Bad(t *testing.T) {
func TestBuildCmd_resolveReleaseWorkflowOutputPathAliases_Good(t *testing.T) {
projectDir := t.TempDir()
path, err := resolveReleaseWorkflowOutputPathAliases(projectDir, "ci/release.yml", "", "", "", "./ci/release.yml", "ci/release.yml")
path, err := resolveReleaseWorkflowOutputPathAliases(projectDir, "ci/release.yml", "", "", "", "", "./ci/release.yml", "ci/release.yml")
require.NoError(t, err)
assert.Equal(t, ax.Join(projectDir, "ci", "release.yml"), path)
}
@ -55,7 +55,15 @@ func TestBuildCmd_resolveReleaseWorkflowOutputPathAliases_Good(t *testing.T) {
func TestBuildCmd_resolveReleaseWorkflowOutputPathAliases_CamelCaseGood(t *testing.T) {
projectDir := t.TempDir()
path, err := resolveReleaseWorkflowOutputPathAliases(projectDir, "", "", "", "ci/release.yml", "", "")
path, err := resolveReleaseWorkflowOutputPathAliases(projectDir, "ci/release.yml", "", "", "", "", "", "")
require.NoError(t, err)
assert.Equal(t, ax.Join(projectDir, "ci", "release.yml"), path)
}
func TestBuildCmd_resolveReleaseWorkflowOutputPathAliases_WorkflowCamelCaseGood(t *testing.T) {
projectDir := t.TempDir()
path, err := resolveReleaseWorkflowOutputPathAliases(projectDir, "", "", "", "", "ci/release.yml", "", "")
require.NoError(t, err)
assert.Equal(t, ax.Join(projectDir, "ci", "release.yml"), path)
}
@ -63,7 +71,7 @@ func TestBuildCmd_resolveReleaseWorkflowOutputPathAliases_CamelCaseGood(t *testi
func TestBuildCmd_resolveReleaseWorkflowOutputPathAliases_Bad(t *testing.T) {
projectDir := t.TempDir()
_, err := resolveReleaseWorkflowOutputPathAliases(projectDir, "ci/release.yml", "", "", "ops/release.yml", "", "")
_, err := resolveReleaseWorkflowOutputPathAliases(projectDir, "ci/release.yml", "", "", "", "ops/release.yml", "", "")
require.Error(t, err)
assert.Contains(t, err.Error(), "workflow output aliases specify different locations")
}
@ -71,7 +79,7 @@ func TestBuildCmd_resolveReleaseWorkflowOutputPathAliases_Bad(t *testing.T) {
func TestBuildCmd_resolveReleaseWorkflowOutputPathAliases_HyphenatedGood(t *testing.T) {
projectDir := t.TempDir()
path, err := resolveReleaseWorkflowOutputPathAliases(projectDir, "", "", "", "", "ci/release.yml", "")
path, err := resolveReleaseWorkflowOutputPathAliases(projectDir, "", "ci/release.yml", "", "", "", "", "")
require.NoError(t, err)
assert.Equal(t, ax.Join(projectDir, "ci", "release.yml"), path)
}
@ -80,7 +88,7 @@ func TestBuildCmd_resolveReleaseWorkflowOutputPathAliases_AbsoluteEquivalent_Goo
projectDir := t.TempDir()
absolutePath := ax.Join(projectDir, "ci", "release.yml")
path, err := resolveReleaseWorkflowOutputPathAliases(projectDir, "ci/release.yml", "", "", "", "", absolutePath)
path, err := resolveReleaseWorkflowOutputPathAliases(projectDir, "ci/release.yml", "", "", "", "", "", absolutePath)
require.NoError(t, err)
assert.Equal(t, absolutePath, path)
}
@ -134,6 +142,7 @@ func TestBuildCmd_RunReleaseWorkflow_Good(t *testing.T) {
workflowPathCamelFlag := releaseWorkflowCmd.Flags().Lookup("workflowPath")
workflowPathFlag := releaseWorkflowCmd.Flags().Lookup("workflow-path")
workflowPathSnakeFlag := releaseWorkflowCmd.Flags().Lookup("workflow_path")
outputPathCamelFlag := releaseWorkflowCmd.Flags().Lookup("outputPath")
outputPathFlag := releaseWorkflowCmd.Flags().Lookup("output-path")
outputPathSnakeFlag := releaseWorkflowCmd.Flags().Lookup("output_path")
outputFlag := releaseWorkflowCmd.Flags().Lookup("output")
@ -145,6 +154,7 @@ func TestBuildCmd_RunReleaseWorkflow_Good(t *testing.T) {
assert.NotNil(t, workflowPathCamelFlag)
assert.NotNil(t, workflowPathFlag)
assert.NotNil(t, workflowPathSnakeFlag)
assert.NotNil(t, outputPathCamelFlag)
assert.NotNil(t, outputPathFlag)
assert.NotNil(t, outputPathSnakeFlag)
assert.NotNil(t, outputFlag)
@ -154,6 +164,7 @@ func TestBuildCmd_RunReleaseWorkflow_Good(t *testing.T) {
assert.NotEmpty(t, workflowPathCamelFlag.Usage)
assert.NotEmpty(t, workflowPathFlag.Usage)
assert.NotEmpty(t, workflowPathSnakeFlag.Usage)
assert.NotEmpty(t, outputPathCamelFlag.Usage)
assert.NotEmpty(t, outputPathFlag.Usage)
assert.NotEmpty(t, outputPathSnakeFlag.Usage)
assert.NotEmpty(t, outputFlag.Usage)
@ -164,6 +175,7 @@ func TestBuildCmd_RunReleaseWorkflow_Good(t *testing.T) {
assert.Equal(t, pathFlag.Usage, workflowPathCamelFlag.Usage)
assert.Equal(t, pathFlag.Usage, workflowPathFlag.Usage)
assert.Equal(t, workflowPathFlag.Usage, workflowPathSnakeFlag.Usage)
assert.Equal(t, outputPathFlag.Usage, outputPathCamelFlag.Usage)
assert.NotEqual(t, outputPathFlag.Usage, outputFlag.Usage)
assert.Equal(t, outputPathFlag.Usage, outputPathSnakeFlag.Usage)
assert.Equal(t, workflowOutputPathFlag.Usage, workflowOutputPathCamelFlag.Usage)

View file

@ -608,16 +608,10 @@ func (r ReleaseWorkflowRequest) resolvedWorkflowPath(dir string, medium io.Mediu
// resolvedOutputPath resolves the workflow output aliases with the same
// conflict rules as the CLI.
func (r ReleaseWorkflowRequest) resolvedOutputPath(dir string) (string, error) {
outputPath := r.OutputPath
if outputPath == "" {
outputPath = r.OutputPathHyphen
} else if r.OutputPathHyphen != "" && outputPath != r.OutputPathHyphen {
return "", coreerr.E("api.ReleaseWorkflowRequest", "workflow output aliases specify different locations", nil)
}
resolvedOutputPath, err := build.ResolveReleaseWorkflowOutputPathAliasesInProject(
dir,
outputPath,
r.OutputPath,
r.OutputPathHyphen,
r.OutputPathSnake,
r.LegacyOutputPath,
r.WorkflowOutputPath,

View file

@ -160,6 +160,7 @@ func ResolveReleaseWorkflowInputPathAliases(filesystem io_interface.Medium, proj
func ResolveReleaseWorkflowOutputPath(outputPathInput, outputPathSnakeInput, outputLegacyInput string) (string, error) {
return ResolveReleaseWorkflowOutputPathAliases(
outputPathInput,
"",
outputPathSnakeInput,
outputLegacyInput,
"",
@ -171,12 +172,13 @@ func ResolveReleaseWorkflowOutputPath(outputPathInput, outputPathSnakeInput, out
// ResolveReleaseWorkflowOutputPathAliases resolves every public workflow output
// alias across the CLI, API, and UI layers.
//
// build.ResolveReleaseWorkflowOutputPathAliases("ci/release.yml", "", "", "", "", "") // "ci/release.yml"
// build.ResolveReleaseWorkflowOutputPathAliases("", "", "", "ci/release.yml", "", "") // "ci/release.yml"
// build.ResolveReleaseWorkflowOutputPathAliases("", "", "", "", "", "ci/release.yml") // "ci/release.yml"
// build.ResolveReleaseWorkflowOutputPathAliases("", "", "", "", "ci/release.yml", "") // "ci/release.yml"
// build.ResolveReleaseWorkflowOutputPathAliases("ci/release.yml", "", "", "", "", "", "") // "ci/release.yml"
// build.ResolveReleaseWorkflowOutputPathAliases("", "ci/release.yml", "", "", "", "", "") // "ci/release.yml"
// build.ResolveReleaseWorkflowOutputPathAliases("", "", "", "", "ci/release.yml", "", "") // "ci/release.yml"
// build.ResolveReleaseWorkflowOutputPathAliases("", "", "", "", "", "", "ci/release.yml") // "ci/release.yml"
func ResolveReleaseWorkflowOutputPathAliases(
outputPathInput,
outputPathHyphenInput,
outputPathSnakeInput,
outputLegacyInput,
workflowOutputPathInput,
@ -185,6 +187,7 @@ func ResolveReleaseWorkflowOutputPathAliases(
) (string, error) {
return resolveReleaseWorkflowOutputAliasSet(
outputPathInput,
outputPathHyphenInput,
outputPathSnakeInput,
outputLegacyInput,
workflowOutputPathInput,
@ -197,11 +200,12 @@ func ResolveReleaseWorkflowOutputPathAliases(
// ResolveReleaseWorkflowOutputPathAliasesInProject resolves the workflow output
// aliases relative to a project directory before checking for conflicts.
//
// build.ResolveReleaseWorkflowOutputPathAliasesInProject("/tmp/project", "ci/release.yml", "", "", "", "", "") // "/tmp/project/ci/release.yml"
// build.ResolveReleaseWorkflowOutputPathAliasesInProject("/tmp/project", "", "", "", "/tmp/project/ci/release.yml", "", "") // "/tmp/project/ci/release.yml"
// build.ResolveReleaseWorkflowOutputPathAliasesInProject("/tmp/project", "ci/release.yml", "", "", "", "", "", "") // "/tmp/project/ci/release.yml"
// build.ResolveReleaseWorkflowOutputPathAliasesInProject("/tmp/project", "", "", "", "", "/tmp/project/ci/release.yml", "", "") // "/tmp/project/ci/release.yml"
func ResolveReleaseWorkflowOutputPathAliasesInProject(
projectDir,
outputPathInput,
outputPathHyphenInput,
outputPathSnakeInput,
outputLegacyInput,
workflowOutputPathInput,
@ -211,6 +215,7 @@ func ResolveReleaseWorkflowOutputPathAliasesInProject(
return resolveReleaseWorkflowOutputAliasSetInProject(
projectDir,
outputPathInput,
outputPathHyphenInput,
outputPathSnakeInput,
outputLegacyInput,
workflowOutputPathInput,
@ -252,6 +257,7 @@ func resolveReleaseWorkflowInputPathPair(pathInput, outputPathInput string, reso
// value when aliases agree.
func resolveReleaseWorkflowOutputAliasSet(
outputPathInput,
outputPathHyphenInput,
outputPathSnakeInput,
outputLegacyInput,
workflowOutputPathInput,
@ -261,6 +267,7 @@ func resolveReleaseWorkflowOutputAliasSet(
) (string, error) {
values := []string{
normalizeWorkflowOutputAlias(outputPathInput),
normalizeWorkflowOutputAlias(outputPathHyphenInput),
normalizeWorkflowOutputAlias(outputPathSnakeInput),
normalizeWorkflowOutputAlias(outputLegacyInput),
normalizeWorkflowOutputAlias(workflowOutputPathInput),
@ -290,6 +297,7 @@ func resolveReleaseWorkflowOutputAliasSet(
func resolveReleaseWorkflowOutputAliasSetInProject(
projectDir,
outputPathInput,
outputPathHyphenInput,
outputPathSnakeInput,
outputLegacyInput,
workflowOutputPathInput,
@ -299,6 +307,7 @@ func resolveReleaseWorkflowOutputAliasSetInProject(
) (string, error) {
values := []string{
cleanWorkflowInput(outputPathInput),
cleanWorkflowInput(outputPathHyphenInput),
cleanWorkflowInput(outputPathSnakeInput),
cleanWorkflowInput(outputLegacyInput),
cleanWorkflowInput(workflowOutputPathInput),

View file

@ -385,6 +385,12 @@ func TestWorkflow_ResolveReleaseWorkflowOutputPath_Good(t *testing.T) {
assert.Equal(t, "ci/release.yml", path)
})
t.Run("accepts the hyphenated output path alias", func(t *testing.T) {
path, err := ResolveReleaseWorkflowOutputPathAliases("", "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 := ResolveReleaseWorkflowOutputPath("", "", "ci/release.yml")
require.NoError(t, err)
@ -419,19 +425,19 @@ func TestWorkflow_ResolveReleaseWorkflowOutputPath_Bad(t *testing.T) {
func TestWorkflow_ResolveReleaseWorkflowOutputPathAliases_Good(t *testing.T) {
t.Run("accepts workflowOutputPath aliases", func(t *testing.T) {
path, err := ResolveReleaseWorkflowOutputPathAliases("", "", "", "ci/release.yml", "", "")
path, err := ResolveReleaseWorkflowOutputPathAliases("", "", "", "", "ci/release.yml", "", "")
require.NoError(t, err)
assert.Equal(t, "ci/release.yml", path)
})
t.Run("accepts the hyphenated workflowOutputPath alias", func(t *testing.T) {
path, err := ResolveReleaseWorkflowOutputPathAliases("", "", "", "", "", "ci/release.yml")
path, err := ResolveReleaseWorkflowOutputPathAliases("", "", "", "", "", "", "ci/release.yml")
require.NoError(t, err)
assert.Equal(t, "ci/release.yml", path)
})
t.Run("normalises matching workflow output aliases", func(t *testing.T) {
path, err := ResolveReleaseWorkflowOutputPathAliases("ci/release.yml", "", "", "./ci/release.yml", "ci/release.yml", "")
path, err := ResolveReleaseWorkflowOutputPathAliases("ci/release.yml", "", "", "./ci/release.yml", "ci/release.yml", "", "")
require.NoError(t, err)
assert.Equal(t, "ci/release.yml", path)
})
@ -442,19 +448,19 @@ func TestWorkflow_ResolveReleaseWorkflowOutputPathAliasesInProject_Good(t *testi
absolutePath := ax.Join(projectDir, "ci", "release.yml")
t.Run("accepts the preferred output path", func(t *testing.T) {
path, err := ResolveReleaseWorkflowOutputPathAliasesInProject(projectDir, "ci/release.yml", "", "", "", "", "")
path, err := ResolveReleaseWorkflowOutputPathAliasesInProject(projectDir, "ci/release.yml", "", "", "", "", "", "")
require.NoError(t, err)
assert.Equal(t, absolutePath, path)
})
t.Run("accepts an absolute workflow output alias equivalent to the project path", func(t *testing.T) {
path, err := ResolveReleaseWorkflowOutputPathAliasesInProject(projectDir, "", "", "", absolutePath, "", "")
path, err := ResolveReleaseWorkflowOutputPathAliasesInProject(projectDir, "", "", "", "", absolutePath, "", "")
require.NoError(t, err)
assert.Equal(t, absolutePath, path)
})
t.Run("accepts matching relative and absolute aliases", func(t *testing.T) {
path, err := ResolveReleaseWorkflowOutputPathAliasesInProject(projectDir, "ci/release.yml", "", "", "", "", absolutePath)
path, err := ResolveReleaseWorkflowOutputPathAliasesInProject(projectDir, "ci/release.yml", "", "", "", "", "", absolutePath)
require.NoError(t, err)
assert.Equal(t, absolutePath, path)
})
@ -463,14 +469,14 @@ func TestWorkflow_ResolveReleaseWorkflowOutputPathAliasesInProject_Good(t *testi
func TestWorkflow_ResolveReleaseWorkflowOutputPathAliasesInProject_Bad(t *testing.T) {
projectDir := t.TempDir()
path, err := ResolveReleaseWorkflowOutputPathAliasesInProject(projectDir, "ci/release.yml", "", "", "", "", ax.Join(projectDir, "ops", "release.yml"))
path, err := ResolveReleaseWorkflowOutputPathAliasesInProject(projectDir, "ci/release.yml", "", "", "", "", "", ax.Join(projectDir, "ops", "release.yml"))
assert.Error(t, err)
assert.Empty(t, path)
assert.Contains(t, err.Error(), "output aliases specify different locations")
}
func TestWorkflow_ResolveReleaseWorkflowOutputPathAliases_Bad(t *testing.T) {
path, err := ResolveReleaseWorkflowOutputPathAliases("ci/release.yml", "", "", "ops/release.yml", "", "")
path, err := ResolveReleaseWorkflowOutputPathAliases("ci/release.yml", "", "", "", "ops/release.yml", "", "")
assert.Error(t, err)
assert.Empty(t, path)
assert.Contains(t, err.Error(), "output aliases specify different locations")