diff --git a/cmd/build/cmd_workflow_test.go b/cmd/build/cmd_workflow_test.go index 1833c9c..67cc0c0 100644 --- a/cmd/build/cmd_workflow_test.go +++ b/cmd/build/cmd_workflow_test.go @@ -90,6 +90,16 @@ func TestBuildCmd_RunReleaseWorkflow_Good(t *testing.T) { assert.Contains(t, content, "workflow_dispatch:") }) + t.Run("writes release.yml inside a current-directory-prefixed directory-style path", func(t *testing.T) { + err := runReleaseWorkflowInDir(projectDir, "./ci", "") + require.NoError(t, err) + + content, err := io.Local.Read(ax.Join(projectDir, "ci", "release.yml")) + require.NoError(t, err) + assert.Contains(t, content, "workflow_call:") + assert.Contains(t, content, "workflow_dispatch:") + }) + t.Run("writes to the output alias", func(t *testing.T) { customPath := "ci/alias-release.yml" err := runReleaseWorkflowInDir(projectDir, "", customPath) diff --git a/pkg/api/provider_test.go b/pkg/api/provider_test.go index 27ddbf0..7b68141 100644 --- a/pkg/api/provider_test.go +++ b/pkg/api/provider_test.go @@ -281,6 +281,30 @@ func TestProvider_GenerateReleaseWorkflow_BareDirectoryPath_Good(t *testing.T) { assert.Contains(t, content, "workflow_dispatch:") } +func TestProvider_GenerateReleaseWorkflow_CurrentDirectoryPrefixedPath_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(`{"path":"./ci"}`)) + 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", "release.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_ExistingDirectoryPath_Good(t *testing.T) { gin.SetMode(gin.TestMode) diff --git a/pkg/build/workflow.go b/pkg/build/workflow.go index f3a8420..905dfaf 100644 --- a/pkg/build/workflow.go +++ b/pkg/build/workflow.go @@ -27,6 +27,7 @@ const DefaultReleaseWorkflowFileName = "release.yml" // // build.WriteReleaseWorkflow(io.Local, "") // writes .github/workflows/release.yml // build.WriteReleaseWorkflow(io.Local, "ci") // writes ./ci/release.yml under the project root +// build.WriteReleaseWorkflow(io.Local, "./ci") // writes ./ci/release.yml under the project root // build.WriteReleaseWorkflow(io.Local, "ci/release.yml") // writes ./ci/release.yml under the project root // build.WriteReleaseWorkflow(io.Local, "/tmp/repo/.github/workflows/release.yml") // writes the absolute path unchanged func WriteReleaseWorkflow(medium io_interface.Medium, outputPath string) error { @@ -69,6 +70,7 @@ func ReleaseWorkflowPath(projectDir string) string { // project directory when the caller supplies a relative path. // // build.ResolveReleaseWorkflowPath("/tmp/project", "") // /tmp/project/.github/workflows/release.yml +// build.ResolveReleaseWorkflowPath("/tmp/project", "./ci") // /tmp/project/ci/release.yml // build.ResolveReleaseWorkflowPath("/tmp/project", "ci/release.yml") // /tmp/project/ci/release.yml // build.ResolveReleaseWorkflowPath("/tmp/project", "ci") // /tmp/project/ci/release.yml // build.ResolveReleaseWorkflowPath("/tmp/project", "/tmp/release.yml") // /tmp/release.yml @@ -92,6 +94,7 @@ func ResolveReleaseWorkflowPath(projectDir, outputPath string) string { // `path` field and its `output` alias. // // build.ResolveReleaseWorkflowInputPath("/tmp/project", "", "") // /tmp/project/.github/workflows/release.yml +// build.ResolveReleaseWorkflowInputPath("/tmp/project", "./ci", "") // /tmp/project/ci/release.yml // build.ResolveReleaseWorkflowInputPath("/tmp/project", "ci/release.yml", "") // /tmp/project/ci/release.yml // build.ResolveReleaseWorkflowInputPath("/tmp/project", "", "ci/release.yml") // /tmp/project/ci/release.yml // build.ResolveReleaseWorkflowInputPath("/tmp/project", "ci/release.yml", "ci.yml") // error @@ -125,6 +128,7 @@ func ResolveReleaseWorkflowInputPath(projectDir, path, outputPath string) (strin // trailing slash. // // build.ResolveReleaseWorkflowInputPathWithMedium(io.Local, "/tmp/project", "ci", "") // /tmp/project/ci/release.yml when /tmp/project/ci exists +// build.ResolveReleaseWorkflowInputPathWithMedium(io.Local, "/tmp/project", "./ci", "") // /tmp/project/ci/release.yml func ResolveReleaseWorkflowInputPathWithMedium(medium io_interface.Medium, projectDir, path, outputPath string) (string, error) { resolve := func(input string) string { return resolveReleaseWorkflowInputPath(projectDir, input, medium) @@ -189,7 +193,8 @@ func isWorkflowDirectoryPath(path string) bool { // isWorkflowDirectoryInput reports whether a workflow input should be treated // as a directory target. This includes explicit directory paths and bare names -// without path separators or a file extension. +// without path separators or a file extension, plus current-directory-prefixed +// directory targets like "./ci". func isWorkflowDirectoryInput(path string) bool { if isWorkflowDirectoryPath(path) { return true @@ -197,5 +202,14 @@ func isWorkflowDirectoryInput(path string) bool { if path == "" || ax.Ext(path) != "" { return false } - return !strings.ContainsAny(path, "/\\") + if !strings.ContainsAny(path, "/\\") { + return true + } + + if strings.HasPrefix(path, "./") || strings.HasPrefix(path, ".\\") { + trimmed := strings.TrimPrefix(strings.TrimPrefix(path, "./"), ".\\") + return trimmed != "" && !strings.ContainsAny(trimmed, "/\\") + } + + return false } diff --git a/pkg/build/workflow_test.go b/pkg/build/workflow_test.go index 397d3b7..102ec70 100644 --- a/pkg/build/workflow_test.go +++ b/pkg/build/workflow_test.go @@ -124,6 +124,10 @@ func TestWorkflow_ResolveReleaseWorkflowPath_Good(t *testing.T) { assert.Equal(t, "/tmp/project/ci/release.yml", ResolveReleaseWorkflowPath("/tmp/project", "ci")) }) + t.Run("treats current-directory-prefixed directory names as directories", func(t *testing.T) { + assert.Equal(t, "/tmp/project/ci/release.yml", ResolveReleaseWorkflowPath("/tmp/project", "./ci")) + }) + t.Run("keeps nested extensionless paths as files", func(t *testing.T) { assert.Equal(t, "/tmp/project/ci/release", ResolveReleaseWorkflowPath("/tmp/project", "ci/release")) }) @@ -164,6 +168,12 @@ func TestWorkflow_ResolveReleaseWorkflowInputPath_Good(t *testing.T) { assert.Equal(t, "/tmp/project/ci/release.yml", path) }) + t.Run("accepts current-directory-prefixed directory-style path as the primary input", func(t *testing.T) { + path, err := ResolveReleaseWorkflowInputPath("/tmp/project", "./ci", "") + require.NoError(t, err) + assert.Equal(t, "/tmp/project/ci/release.yml", path) + }) + t.Run("keeps nested extensionless paths as files", func(t *testing.T) { path, err := ResolveReleaseWorkflowInputPath("/tmp/project", "ci/release", "") require.NoError(t, err) @@ -222,6 +232,14 @@ func TestWorkflow_ResolveReleaseWorkflowInputPathWithMedium_Good(t *testing.T) { assert.Equal(t, "/tmp/project/ci/release.yml", path) }) + t.Run("treats a current-directory-prefixed directory-style path as a workflow directory", func(t *testing.T) { + fs := io.NewMockMedium() + + path, err := ResolveReleaseWorkflowInputPathWithMedium(fs, "/tmp/project", "./ci", "") + require.NoError(t, err) + assert.Equal(t, "/tmp/project/ci/release.yml", path) + }) + t.Run("keeps a file path unchanged when the target is not a directory", func(t *testing.T) { fs := io.NewMockMedium()