Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion cmd/xc/interactive.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"fmt"
"io"
"os"
"strings"

"github.com/charmbracelet/bubbles/list"
Expand Down Expand Up @@ -129,7 +130,11 @@ func interactivePicker(ctx context.Context, tasks []models.Task, dir string) err
if task == nil {
return nil
}
runner, err := run.NewRunner(tasks, dir)
cwd, err := os.Getwd()
if err != nil {
return fmt.Errorf("error getting current directory: %w", err)
}
runner, err := run.NewRunner(tasks, dir, cwd)
if err != nil {
return fmt.Errorf("xc parse error: %w", err)
}
Expand Down
6 changes: 5 additions & 1 deletion cmd/xc/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,11 @@ func runMain() error {
return nil
}
// xc task1
runner, err := run.NewRunner(tasks, dir)
cwd, err := os.Getwd()
if err != nil {
return fmt.Errorf("error getting current directory: %w", err)
}
runner, err := run.NewRunner(tasks, dir, cwd)
if err != nil {
return fmt.Errorf("xc parse error: %w", err)
}
Expand Down
13 changes: 13 additions & 0 deletions doc/content/task-syntax/directory.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,16 @@ directory: ./src
sh build.sh
```
````

## Using the caller's working directory

By default, tasks run in the directory where the README file is located, even if `xc` is invoked from a subdirectory. Setting `directory` to `$PWD` will instead run the task in the directory where `xc` was invoked.

````markdown
## Tasks
### List
directory: $PWD
```
ls
```
````
7 changes: 6 additions & 1 deletion run/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ type Runner struct {
scriptRunner ScriptRunner
tasks models.Tasks
dir string
cwd string
alreadyRan map[string]bool
alreadyRanMu sync.Mutex
trace bool
Expand All @@ -38,14 +39,15 @@ type Runner struct {
//
// NewRunner will return an error in the case that Dependent tasks are cyclical,
// invalid or at a larger depth than 50.
func NewRunner(ts models.Tasks, dir string) (runner Runner, err error) {
func NewRunner(ts models.Tasks, dir, cwd string) (runner Runner, err error) {
trace := !slices.Contains(
[]string{"false", "no", "0"},
strings.ToLower(os.Getenv("XC_TRACE")))
runner = Runner{
scriptRunner: newInterpreter(),
tasks: ts,
dir: dir,
cwd: cwd,
alreadyRan: map[string]bool{},
trace: trace,
}
Expand Down Expand Up @@ -218,6 +220,9 @@ func (r *Runner) getExecutionPath(task models.Task) string {
if task.Dir == "" {
return r.dir
}
if task.Dir == "$PWD" {
return r.cwd
}
if filepath.IsAbs(task.Dir) {
return task.Dir
}
Expand Down
71 changes: 61 additions & 10 deletions run/run_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ func TestRunAsync(t *testing.T) {
for i := range tt.tasks {
tt.tasks[i].DepsBehaviour = models.DependencyBehaviourAsync
}
runner, err := NewRunner(tt.tasks, "")
runner, err := NewRunner(tt.tasks, "", "")
if (err != nil) != tt.expectedParseError {
t.Fatalf("expected error %v, got %v", tt.expectedParseError, err)
}
Expand All @@ -206,7 +206,7 @@ func TestRun(t *testing.T) {
for _, tt := range testCases() {
tt := tt
t.Run(tt.name, func(t *testing.T) {
runner, err := NewRunner(tt.tasks, "")
runner, err := NewRunner(tt.tasks, "", "")
if (err != nil) != tt.expectedParseError {
t.Fatalf("expected error %v, got %v", tt.expectedParseError, err)
}
Expand Down Expand Up @@ -234,7 +234,7 @@ func TestRunWithInputs(t *testing.T) {
Script: "somecmd",
Inputs: []string{"FOO"},
},
}, "")
}, "", "")
if err != nil {
t.Fatal(err)
}
Expand All @@ -250,7 +250,7 @@ func TestRunWithInputs(t *testing.T) {
Script: "somecmd",
Inputs: []string{"FOO"},
},
}, "")
}, "", "")
if err != nil {
t.Fatal(err)
}
Expand All @@ -271,7 +271,7 @@ func TestRunWithInputs(t *testing.T) {
Script: "somecmd",
Inputs: []string{"FOO"},
},
}, "")
}, "", "")
if err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -317,7 +317,7 @@ func TestOptionalInputPrecedence(t *testing.T) {
return val, found
}
t.Run("env default is used when no cli arg or os env is set", func(t *testing.T) {
runner, err := NewRunner(makeTask(), "")
runner, err := NewRunner(makeTask(), "", "")
if err != nil {
t.Fatal(err)
}
Expand All @@ -340,7 +340,7 @@ func TestOptionalInputPrecedence(t *testing.T) {
})

t.Run("cli arg overrides env default", func(t *testing.T) {
runner, err := NewRunner(makeTask(), "")
runner, err := NewRunner(makeTask(), "", "")
if err != nil {
t.Fatal(err)
}
Expand All @@ -364,7 +364,7 @@ func TestOptionalInputPrecedence(t *testing.T) {

t.Run("os env overrides env default when input is declared", func(t *testing.T) {
t.Setenv("MY_VAR", "from_shell")
runner, err := NewRunner(makeTask(), "")
runner, err := NewRunner(makeTask(), "", "")
if err != nil {
t.Fatal(err)
}
Expand All @@ -388,7 +388,7 @@ func TestOptionalInputPrecedence(t *testing.T) {

t.Run("cli arg overrides os env", func(t *testing.T) {
t.Setenv("MY_VAR", "from_shell")
runner, err := NewRunner(makeTask(), "")
runner, err := NewRunner(makeTask(), "", "")
if err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -420,7 +420,7 @@ func TestOptionalInputPrecedence(t *testing.T) {
Script: "somecmd",
Env: []string{"MY_VAR=forced_value"},
},
}, "")
}, "", "")
if err != nil {
t.Fatal(err)
}
Expand All @@ -442,3 +442,54 @@ func TestOptionalInputPrecedence(t *testing.T) {
}
})
}

func TestGetExecutionPath(t *testing.T) {
tests := []struct {
name string
dir string
cwd string
taskDir string
expected string
}{
{
name: "empty dir uses runner dir",
dir: "/repo",
cwd: "/repo/subdir",
taskDir: "",
expected: "/repo",
},
{
name: "relative dir is joined with runner dir",
dir: "/repo",
cwd: "/repo/subdir",
taskDir: "./src",
expected: "/repo/src",
},
{
name: "absolute dir is used as-is",
dir: "/repo",
cwd: "/repo/subdir",
taskDir: "/tmp/build",
expected: "/tmp/build",
},
{
name: "$PWD uses caller working directory",
dir: "/repo",
cwd: "/repo/subdir",
taskDir: "$PWD",
expected: "/repo/subdir",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := Runner{
dir: tt.dir,
cwd: tt.cwd,
}
got := r.getExecutionPath(models.Task{Dir: tt.taskDir})
if got != tt.expected {
t.Fatalf("expected %q, got %q", tt.expected, got)
}
})
}
}