feat(build): add workflow output aliases
This commit is contained in:
parent
355be6ee31
commit
bfc88dc00c
6 changed files with 189 additions and 16 deletions
|
|
@ -4,6 +4,7 @@ package buildcmd
|
|||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"dappco.re/go/core/build/internal/ax"
|
||||
"dappco.re/go/core/build/pkg/build"
|
||||
|
|
@ -14,10 +15,12 @@ import (
|
|||
)
|
||||
|
||||
var (
|
||||
releaseWorkflowPathInput string
|
||||
releaseWorkflowOutputPathInput string
|
||||
releaseWorkflowOutputPathSnakeInput string
|
||||
releaseWorkflowOutputLegacyInput string
|
||||
releaseWorkflowPathInput string
|
||||
releaseWorkflowOutputPathInput string
|
||||
releaseWorkflowOutputPathSnakeInput string
|
||||
releaseWorkflowOutputLegacyInput string
|
||||
releaseWorkflowWorkflowOutputPathInput string
|
||||
releaseWorkflowWorkflowOutputPathSnakeInput string
|
||||
)
|
||||
|
||||
var releaseWorkflowCmd = &cli.Command{
|
||||
|
|
@ -29,6 +32,8 @@ var releaseWorkflowCmd = &cli.Command{
|
|||
releaseWorkflowOutputPathInput,
|
||||
releaseWorkflowOutputPathSnakeInput,
|
||||
releaseWorkflowOutputLegacyInput,
|
||||
releaseWorkflowWorkflowOutputPathInput,
|
||||
releaseWorkflowWorkflowOutputPathSnakeInput,
|
||||
)
|
||||
},
|
||||
}
|
||||
|
|
@ -43,6 +48,8 @@ func initWorkflowFlags() {
|
|||
releaseWorkflowCmd.Flags().StringVar(&releaseWorkflowOutputPathInput, "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"))
|
||||
releaseWorkflowCmd.Flags().StringVar(&releaseWorkflowWorkflowOutputPathInput, "workflow-output-path", "", i18n.T("cmd.build.workflow.flag.workflow_output_path"))
|
||||
releaseWorkflowCmd.Flags().StringVar(&releaseWorkflowWorkflowOutputPathSnakeInput, "workflow_output_path", "", i18n.T("cmd.build.workflow.flag.workflow_output_path"))
|
||||
}
|
||||
|
||||
// buildCmd := &cli.Command{Use: "build"}
|
||||
|
|
@ -56,12 +63,14 @@ 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", "", "") // 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 {
|
||||
// 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
|
||||
// runReleaseWorkflow(ctx, "", "", "", "", "ci/release.yml", "") // uses the workflow-output-path alias
|
||||
// runReleaseWorkflow(ctx, "", "", "", "", "ci/release.yml", "ci/release.yml") // uses the snake_case workflow-output-path alias
|
||||
func runReleaseWorkflow(_ context.Context, workflowPathInput, workflowOutputPathInput, workflowOutputPathSnakeInput, workflowOutputLegacyInput, workflowWorkflowOutputPathInput, workflowWorkflowOutputPathSnakeInput string) error {
|
||||
resolvedOutputPathInput, err := build.ResolveReleaseWorkflowOutputPath(
|
||||
workflowOutputPathInput,
|
||||
workflowOutputPathSnakeInput,
|
||||
|
|
@ -71,6 +80,16 @@ func runReleaseWorkflow(_ context.Context, workflowPathInput, workflowOutputPath
|
|||
return err
|
||||
}
|
||||
|
||||
resolvedOutputPathInput, err = resolveWorkflowOutputAliases(
|
||||
resolvedOutputPathInput,
|
||||
workflowWorkflowOutputPathInput,
|
||||
workflowWorkflowOutputPathSnakeInput,
|
||||
"build.runReleaseWorkflow",
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
projectDir, err := ax.Getwd()
|
||||
if err != nil {
|
||||
return coreerr.E("build.runReleaseWorkflow", "failed to get working directory", err)
|
||||
|
|
@ -96,3 +115,27 @@ func runReleaseWorkflowInDir(projectDir, workflowPathInput, workflowOutputPathIn
|
|||
|
||||
return build.WriteReleaseWorkflow(io.Local, resolvedPath)
|
||||
}
|
||||
|
||||
// resolveWorkflowOutputAliases merges the preferred output path with extra
|
||||
// aliases while rejecting conflicting values.
|
||||
func resolveWorkflowOutputAliases(primaryInput, workflowOutputPathInput, workflowOutputPathSnakeInput, errorName string) (string, error) {
|
||||
primaryInput = strings.TrimSpace(primaryInput)
|
||||
workflowOutputPathInput = strings.TrimSpace(workflowOutputPathInput)
|
||||
workflowOutputPathSnakeInput = strings.TrimSpace(workflowOutputPathSnakeInput)
|
||||
|
||||
resolved := primaryInput
|
||||
for _, value := range []string{workflowOutputPathInput, workflowOutputPathSnakeInput} {
|
||||
if value == "" {
|
||||
continue
|
||||
}
|
||||
if resolved == "" {
|
||||
resolved = value
|
||||
continue
|
||||
}
|
||||
if resolved != value {
|
||||
return "", coreerr.E(errorName, "workflow output aliases specify different locations", nil)
|
||||
}
|
||||
}
|
||||
|
||||
return resolved, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -69,18 +69,25 @@ func TestBuildCmd_RunReleaseWorkflow_Good(t *testing.T) {
|
|||
outputPathFlag := releaseWorkflowCmd.Flags().Lookup("output-path")
|
||||
outputPathSnakeFlag := releaseWorkflowCmd.Flags().Lookup("output_path")
|
||||
outputFlag := releaseWorkflowCmd.Flags().Lookup("output")
|
||||
workflowOutputPathFlag := releaseWorkflowCmd.Flags().Lookup("workflow-output-path")
|
||||
workflowOutputPathSnakeFlag := releaseWorkflowCmd.Flags().Lookup("workflow_output_path")
|
||||
|
||||
assert.NotNil(t, pathFlag)
|
||||
assert.NotNil(t, outputPathFlag)
|
||||
assert.NotNil(t, outputPathSnakeFlag)
|
||||
assert.NotNil(t, outputFlag)
|
||||
assert.NotNil(t, workflowOutputPathFlag)
|
||||
assert.NotNil(t, workflowOutputPathSnakeFlag)
|
||||
assert.NotEmpty(t, pathFlag.Usage)
|
||||
assert.NotEmpty(t, outputPathFlag.Usage)
|
||||
assert.NotEmpty(t, outputPathSnakeFlag.Usage)
|
||||
assert.NotEmpty(t, outputFlag.Usage)
|
||||
assert.NotEmpty(t, workflowOutputPathFlag.Usage)
|
||||
assert.NotEmpty(t, workflowOutputPathSnakeFlag.Usage)
|
||||
assert.NotEqual(t, pathFlag.Usage, outputFlag.Usage)
|
||||
assert.NotEqual(t, outputPathFlag.Usage, outputFlag.Usage)
|
||||
assert.Equal(t, outputPathFlag.Usage, outputPathSnakeFlag.Usage)
|
||||
assert.Equal(t, workflowOutputPathFlag.Usage, workflowOutputPathSnakeFlag.Usage)
|
||||
})
|
||||
|
||||
t.Run("writes to a custom relative path", func(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -113,10 +113,11 @@
|
|||
},
|
||||
"workflow": {
|
||||
"short": "Generate the release workflow",
|
||||
"long": "Write the embedded GitHub Actions release workflow into .github/workflows/release.yml, or pass --path/--output-path/--output_path/--output for a custom location.",
|
||||
"long": "Write the embedded GitHub Actions release workflow into .github/workflows/release.yml, or pass --path/--output-path/--output_path/--output/--workflow-output-path/--workflow_output_path for a custom location.",
|
||||
"flag": {
|
||||
"path": "Preferred workflow path input.",
|
||||
"output_path": "Preferred explicit workflow output path.",
|
||||
"workflow_output_path": "Predictable workflow output path alias.",
|
||||
"output": "Legacy alias for --output-path."
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import (
|
|||
stdio "io"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"dappco.re/go/core/api"
|
||||
"dappco.re/go/core/api/pkg/provider"
|
||||
|
|
@ -20,6 +21,7 @@ import (
|
|||
"dappco.re/go/core/build/pkg/release"
|
||||
"dappco.re/go/core/build/pkg/sdk"
|
||||
"dappco.re/go/core/io"
|
||||
coreerr "dappco.re/go/core/log"
|
||||
"dappco.re/go/core/ws"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
|
@ -182,6 +184,14 @@ func (p *BuildProvider) Describe() []api.RouteDescription {
|
|||
"type": "string",
|
||||
"description": "Preferred output path for the workflow file, relative to the project directory or absolute.",
|
||||
},
|
||||
"workflowOutputPath": map[string]any{
|
||||
"type": "string",
|
||||
"description": "Predictable alias for outputPath, relative to the project directory or absolute.",
|
||||
},
|
||||
"workflow_output_path": map[string]any{
|
||||
"type": "string",
|
||||
"description": "Snake_case alias for workflowOutputPath.",
|
||||
},
|
||||
"outputPath": map[string]any{
|
||||
"type": "string",
|
||||
"description": "Preferred explicit workflow output path, relative to the project directory or absolute.",
|
||||
|
|
@ -545,16 +555,22 @@ func (p *BuildProvider) triggerRelease(c *gin.Context) {
|
|||
//
|
||||
// req := ReleaseWorkflowRequest{Path: "ci/release.yml"}
|
||||
type ReleaseWorkflowRequest struct {
|
||||
Path string `json:"path"`
|
||||
OutputPath string `json:"outputPath"`
|
||||
OutputPathSnake string `json:"output_path"`
|
||||
LegacyOutputPath string `json:"output"`
|
||||
Path string `json:"path"`
|
||||
OutputPath string `json:"outputPath"`
|
||||
OutputPathSnake string `json:"output_path"`
|
||||
LegacyOutputPath string `json:"output"`
|
||||
WorkflowOutputPath string `json:"workflowOutputPath"`
|
||||
WorkflowOutputPathSnake string `json:"workflow_output_path"`
|
||||
}
|
||||
|
||||
// resolvedOutputPath resolves the workflow output aliases with the same
|
||||
// conflict rules as the CLI.
|
||||
func (r ReleaseWorkflowRequest) resolvedOutputPath() (string, error) {
|
||||
return build.ResolveReleaseWorkflowOutputPath(r.OutputPath, r.OutputPathSnake, r.LegacyOutputPath)
|
||||
resolved, err := build.ResolveReleaseWorkflowOutputPath(r.OutputPath, r.OutputPathSnake, r.LegacyOutputPath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return mergeWorkflowOutputAliases(resolved, r.WorkflowOutputPath, r.WorkflowOutputPathSnake, "api.ReleaseWorkflowRequest")
|
||||
}
|
||||
|
||||
func (p *BuildProvider) generateReleaseWorkflow(c *gin.Context) {
|
||||
|
|
@ -601,6 +617,30 @@ func (p *BuildProvider) generateReleaseWorkflow(c *gin.Context) {
|
|||
}))
|
||||
}
|
||||
|
||||
// mergeWorkflowOutputAliases combines an existing resolved output path with
|
||||
// additional alias values and rejects conflicts.
|
||||
func mergeWorkflowOutputAliases(primaryInput, workflowOutputPathInput, workflowOutputPathSnakeInput, errorName string) (string, error) {
|
||||
primaryInput = strings.TrimSpace(primaryInput)
|
||||
workflowOutputPathInput = strings.TrimSpace(workflowOutputPathInput)
|
||||
workflowOutputPathSnakeInput = strings.TrimSpace(workflowOutputPathSnakeInput)
|
||||
|
||||
resolved := primaryInput
|
||||
for _, value := range []string{workflowOutputPathInput, workflowOutputPathSnakeInput} {
|
||||
if value == "" {
|
||||
continue
|
||||
}
|
||||
if resolved == "" {
|
||||
resolved = value
|
||||
continue
|
||||
}
|
||||
if resolved != value {
|
||||
return "", coreerr.E(errorName, "workflow output aliases specify different locations", nil)
|
||||
}
|
||||
}
|
||||
|
||||
return resolved, nil
|
||||
}
|
||||
|
||||
// -- SDK Handlers -------------------------------------------------------------
|
||||
|
||||
func (p *BuildProvider) getSdkDiff(c *gin.Context) {
|
||||
|
|
|
|||
|
|
@ -101,6 +101,16 @@ func TestProvider_BuildProviderDescribe_Good(t *testing.T) {
|
|||
assert.Equal(t, "string", outputPathSchema["type"])
|
||||
assert.Equal(t, "Preferred explicit workflow output path, relative to the project directory or absolute.", outputPathSchema["description"])
|
||||
|
||||
workflowOutputPathSchema, ok := properties["workflowOutputPath"].(map[string]any)
|
||||
require.True(t, ok)
|
||||
assert.Equal(t, "string", workflowOutputPathSchema["type"])
|
||||
assert.Equal(t, "Predictable alias for outputPath, relative to the project directory or absolute.", workflowOutputPathSchema["description"])
|
||||
|
||||
workflowOutputPathSnakeSchema, ok := properties["workflow_output_path"].(map[string]any)
|
||||
require.True(t, ok)
|
||||
assert.Equal(t, "string", workflowOutputPathSnakeSchema["type"])
|
||||
assert.Equal(t, "Snake_case alias for workflowOutputPath.", workflowOutputPathSnakeSchema["description"])
|
||||
|
||||
outputPathSnakeSchema, ok := properties["output_path"].(map[string]any)
|
||||
require.True(t, ok)
|
||||
assert.Equal(t, "string", outputPathSnakeSchema["type"])
|
||||
|
|
@ -315,6 +325,76 @@ func TestProvider_GenerateReleaseWorkflow_OutputPathSnake_Good(t *testing.T) {
|
|||
assert.Contains(t, content, "workflow_dispatch:")
|
||||
}
|
||||
|
||||
func TestProvider_GenerateReleaseWorkflow_WorkflowOutputPath_Good(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
|
||||
projectDir := t.TempDir()
|
||||
p := NewProvider(projectDir, nil)
|
||||
|
||||
recorder := httptest.NewRecorder()
|
||||
request := httptest.NewRequest(http.MethodPost, "/release/workflow", bytes.NewBufferString(`{"workflowOutputPath":"ci/workflow-output-path.yml"}`))
|
||||
request.Header.Set("Content-Type", "application/json")
|
||||
|
||||
ctx, _ := gin.CreateTestContext(recorder)
|
||||
ctx.Request = request
|
||||
|
||||
p.generateReleaseWorkflow(ctx)
|
||||
|
||||
assert.Equal(t, http.StatusOK, recorder.Code)
|
||||
|
||||
path := ax.Join(projectDir, "ci", "workflow-output-path.yml")
|
||||
content, err := io.Local.Read(path)
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, content, "workflow_call:")
|
||||
assert.Contains(t, content, "workflow_dispatch:")
|
||||
}
|
||||
|
||||
func TestProvider_GenerateReleaseWorkflow_WorkflowOutputPathSnake_Good(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
|
||||
projectDir := t.TempDir()
|
||||
p := NewProvider(projectDir, nil)
|
||||
|
||||
recorder := httptest.NewRecorder()
|
||||
request := httptest.NewRequest(http.MethodPost, "/release/workflow", bytes.NewBufferString(`{"workflow_output_path":"ci/workflow-output-path.yml"}`))
|
||||
request.Header.Set("Content-Type", "application/json")
|
||||
|
||||
ctx, _ := gin.CreateTestContext(recorder)
|
||||
ctx.Request = request
|
||||
|
||||
p.generateReleaseWorkflow(ctx)
|
||||
|
||||
assert.Equal(t, http.StatusOK, recorder.Code)
|
||||
|
||||
path := ax.Join(projectDir, "ci", "workflow-output-path.yml")
|
||||
content, err := io.Local.Read(path)
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, content, "workflow_call:")
|
||||
assert.Contains(t, content, "workflow_dispatch:")
|
||||
}
|
||||
|
||||
func TestProvider_GenerateReleaseWorkflow_ConflictingWorkflowOutputAliases_Bad(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
|
||||
projectDir := t.TempDir()
|
||||
p := NewProvider(projectDir, nil)
|
||||
|
||||
recorder := httptest.NewRecorder()
|
||||
request := httptest.NewRequest(http.MethodPost, "/release/workflow", bytes.NewBufferString(`{"outputPath":"ci/output-path.yml","workflowOutputPath":"ops/output-path.yml"}`))
|
||||
request.Header.Set("Content-Type", "application/json")
|
||||
|
||||
ctx, _ := gin.CreateTestContext(recorder)
|
||||
ctx.Request = request
|
||||
|
||||
p.generateReleaseWorkflow(ctx)
|
||||
|
||||
assert.Equal(t, http.StatusBadRequest, recorder.Code)
|
||||
|
||||
path := build.ReleaseWorkflowPath(projectDir)
|
||||
_, err := io.Local.Read(path)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestProvider_GenerateReleaseWorkflow_ConflictingOutputAliases_Bad(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
|
||||
|
|
|
|||
|
|
@ -61,6 +61,8 @@ export class BuildApi {
|
|||
outputPath?: string;
|
||||
output_path?: string;
|
||||
output?: string;
|
||||
workflowOutputPath?: string;
|
||||
workflow_output_path?: string;
|
||||
} = {}) {
|
||||
return this.request<any>('/release/workflow', {
|
||||
method: 'POST',
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue