diff --git a/docs/src/content/docs/guides/cli.mdx b/docs/src/content/docs/guides/cli.mdx index dc163f5e3..f6c147b6c 100644 --- a/docs/src/content/docs/guides/cli.mdx +++ b/docs/src/content/docs/guides/cli.mdx @@ -1,6 +1,7 @@ --- title: CLI Reference description: Complete reference for the Wails CLI commands +sidebar: sidebar: order: 1 --- @@ -9,7 +10,7 @@ The Wails CLI provides a comprehensive set of commands to help you develop, buil ## Core Commands -Core commands are the primary commands used for project creation, development, and building. +Core commands are the primary commands used for project creation, development, and building. All CLI commands are of the following format: `wails3 `. @@ -52,7 +53,7 @@ When provided, this flag will: 4. Add all files ### `dev` -Runs the application in development mode. This will give you a live view of your frontend code, and you can make changes and see them reflected +Runs the application in development mode. This will give you a live view of your frontend code, and you can make changes and see them reflected in the running application without having to rebuild the entire application. Changes to your Go code will also be detected and the application will automatically rebuild and relaunch. @@ -76,22 +77,30 @@ wails3 dev [flags] ### `build` Builds a debug version of your application. It defaults to building for the current platform and architecture. - ```bash -wails3 build +wails3 build [CLI variables...] +``` + +You can pass CLI variables to customize the build: +```bash +wails3 build PLATFORM=linux CONFIG=production ``` :::note - This is equivalent to running `wails3 task build` which runs the `build` task in the project's main Taskfile. You can customise the build process by editing the `Taskfile.yml` file. + This is equivalent to running `wails3 task build` which runs the `build` task in the project's main Taskfile. Any CLI variables passed to `build` are forwarded to the underlying task. You can customise the build process by editing the `Taskfile.yml` file. ::: ### `package` Creates platform-specific packages for distribution. - ```bash -wails3 package +wails3 package [CLI variables...] +``` + +You can pass CLI variables to customize the packaging: +```bash +wails3 package VERSION=2.0.0 OUTPUT=myapp.pkg ``` #### Package Types @@ -104,13 +113,80 @@ The following package types are available for each platform: | macOS | `.app`, | | Linux | `.AppImage`, `.deb`, `.rpm`, `.archlinux` | - - :::note - This is equivalent to `wails3 task package` which runs the `package` task in the project's main Taskfile. You can customise the packaging process by editing the `Taskfile.yml` file. + This is equivalent to `wails3 task package` which runs the `package` task in the project's main Taskfile. Any CLI variables passed to `package` are forwarded to the underlying task. You can customise the packaging process by editing the `Taskfile.yml` file. ::: +### `task` +Runs tasks defined in your project's Taskfile.yml. This is an embedded version of [Taskfile](https://taskfile.dev) that allows you to define and run custom build, test, and deployment tasks. + +```bash +wails3 task [taskname] [CLI variables...] [flags] +``` + +#### CLI Variables +You can pass variables to tasks in the format `KEY=VALUE`: +```bash +wails3 task build PLATFORM=linux CONFIG=production +wails3 task deploy ENV=staging VERSION=1.2.3 +``` + +These variables can be accessed in your Taskfile.yml using Go template syntax: +```yaml +tasks: + build: + cmds: + - echo "Building for {{.PLATFORM | default "darwin"}}" + - echo "Config: {{.CONFIG | default "debug"}}" +``` + +#### Flags +| Flag | Description | Default | +|--------------|-------------------------------------------|----------| +| `-h` | Shows Task usage | `false` | +| `-i` | Creates a new Taskfile.yml | `false` | +| `-list` | Lists tasks with descriptions | `false` | +| `-list-all` | Lists all tasks (with or without descriptions) | `false` | +| `-json` | Formats task list as JSON | `false` | +| `-status` | Exits with non-zero if task is not up-to-date | `false` | +| `-f` | Forces execution even when task is up-to-date | `false` | +| `-w` | Enables watch mode for the given task | `false` | +| `-v` | Enables verbose mode | `false` | +| `-version` | Prints Task version | `false` | +| `-s` | Disables echoing | `false` | +| `-p` | Executes tasks in parallel | `false` | +| `-dry` | Compiles and prints tasks without executing | `false` | +| `-summary` | Shows summary about a task | `false` | +| `-x` | Pass-through the exit code of the task | `false` | +| `-dir` | Sets directory of execution | | +| `-taskfile` | Choose which Taskfile to run | | +| `-output` | Sets output style: [interleaved|group|prefixed] | | +| `-c` | Colored output (enabled by default) | `true` | +| `-C` | Limit number of tasks to run concurrently | | +| `-interval` | Interval to watch for changes (in seconds) | | + +#### Examples +```bash +# Run the default task +wails3 task + +# Run a specific task +wails3 task test + +# Run a task with variables +wails3 task build PLATFORM=windows ARCH=amd64 + +# List all available tasks +wails3 task --list + +# Run multiple tasks in parallel +wails3 task -p task1 task2 task3 + +# Watch for changes and re-run task +wails3 task -w dev +``` + ### `doctor` Performs a system check and displays a status report. diff --git a/docs/src/content/docs/learn/build.mdx b/docs/src/content/docs/learn/build.mdx index e45c6e3b9..c3ace7ea4 100644 --- a/docs/src/content/docs/learn/build.mdx +++ b/docs/src/content/docs/learn/build.mdx @@ -138,6 +138,29 @@ commands, Wails internally translates them to the appropriate task execution: - `wails3 build` → `wails3 task build` - `wails3 package` → `wails3 task package` +### Passing Parameters to Tasks + +You can pass CLI variables to tasks using the `KEY=VALUE` format. These variables are forwarded through the alias commands: + +```bash +# These are equivalent: +wails3 build PLATFORM=linux CONFIG=production +wails3 task build PLATFORM=linux CONFIG=production + +# Package with custom version: +wails3 package VERSION=2.0.0 OUTPUT=myapp.pkg +``` + +In your `Taskfile.yml`, you can access these variables using Go template syntax: + +```yaml +tasks: + build: + cmds: + - echo "Building for {{.PLATFORM | default "darwin"}}" + - go build -tags {{.CONFIG | default "debug"}} -o myapp +``` + ## Common Build Process Across all platforms, the build process typically includes the following steps: diff --git a/v3/UNRELEASED_CHANGELOG.md b/v3/UNRELEASED_CHANGELOG.md index 4e7d68f66..231a4856f 100644 --- a/v3/UNRELEASED_CHANGELOG.md +++ b/v3/UNRELEASED_CHANGELOG.md @@ -18,6 +18,7 @@ After processing, the content will be moved to the main changelog and this file ## Added - Add Content Protection on Windows/Mac by [@leaanthony](https://github.com/leaanthony) based on the original work of [@Taiterbase](https://github.com/Taiterbase) in this [PR](https://github.com/wailsapp/wails/pull/4241) +- Add support for passing CLI variables to Task commands through `wails3 build` and `wails3 package` aliases (#4422) by @leaanthony in [PR](https://github.com/wailsapp/wails/pull/4488) ## Changed diff --git a/v3/cmd/wails3/main.go b/v3/cmd/wails3/main.go index 945a865df..6800134ed 100644 --- a/v3/cmd/wails3/main.go +++ b/v3/cmd/wails3/main.go @@ -33,9 +33,22 @@ func main() { app := clir.NewCli("wails", "The Wails3 CLI", "v3") app.NewSubCommand("docs", "Open the docs").Action(openDocs) app.NewSubCommandFunction("init", "Initialise a new project", commands.Init) - app.NewSubCommandFunction("build", "Build the project", commands.Build) + + build := app.NewSubCommand("build", "Build the project") + var buildFlags flags.Build + build.AddFlags(&buildFlags) + build.Action(func() error { + return commands.Build(&buildFlags, build.OtherArgs()) + }) + app.NewSubCommandFunction("dev", "Run in Dev mode", commands.Dev) - app.NewSubCommandFunction("package", "Package application", commands.Package) + + pkg := app.NewSubCommand("package", "Package application") + var pkgFlags flags.Package + pkg.AddFlags(&pkgFlags) + pkg.Action(func() error { + return commands.Package(&pkgFlags, pkg.OtherArgs()) + }) app.NewSubCommandFunction("doctor", "System status report", commands.Doctor) app.NewSubCommandFunction("releasenotes", "Show release notes", commands.ReleaseNotes) diff --git a/v3/go.mod b/v3/go.mod index 3684bbff2..e515a8b1a 100644 --- a/v3/go.mod +++ b/v3/go.mod @@ -32,6 +32,7 @@ require ( github.com/pkg/errors v0.9.1 github.com/pterm/pterm v0.12.80 github.com/samber/lo v1.49.1 + github.com/stretchr/testify v1.10.0 github.com/tc-hib/winres v0.3.1 github.com/wailsapp/go-webview2 v1.0.21 github.com/wailsapp/mimetype v1.4.1 @@ -50,6 +51,7 @@ require ( github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect github.com/charmbracelet/x/term v0.2.1 // indirect github.com/ncruces/go-strftime v0.1.9 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect ) require ( diff --git a/v3/internal/commands/task.go b/v3/internal/commands/task.go index 9672b2f27..98f2e6dca 100644 --- a/v3/internal/commands/task.go +++ b/v3/internal/commands/task.go @@ -123,29 +123,62 @@ func RunTask(options *RunTaskOptions, otherArgs []string) error { return nil } - var index int - var arg string - for index, arg = range os.Args[2:] { - if !strings.HasPrefix(arg, "-") { - break - } - } - + // Parse task name and CLI variables from otherArgs or os.Args var tasksAndVars []string - for _, taskAndVar := range os.Args[index+2:] { - if taskAndVar == "--" { - break + + // Check if we have a task name specified in options + if options.Name != "" { + // If task name is provided via options, use it and treat otherArgs as CLI variables + tasksAndVars = append([]string{options.Name}, otherArgs...) + } else if len(otherArgs) > 0 { + // Use otherArgs directly if provided + tasksAndVars = otherArgs + } else { + // Fall back to parsing os.Args for backward compatibility + var index int + var arg string + for index, arg = range os.Args[2:] { + if !strings.HasPrefix(arg, "-") { + break + } } - tasksAndVars = append(tasksAndVars, taskAndVar) - } - if len(tasksAndVars) > 0 && len(otherArgs) > 0 { - if tasksAndVars[0] == otherArgs[0] { - otherArgs = otherArgs[1:] + for _, taskAndVar := range os.Args[index+2:] { + if taskAndVar == "--" { + break + } + tasksAndVars = append(tasksAndVars, taskAndVar) } } - if err := e.RunTask(context.Background(), &ast.Call{Task: tasksAndVars[0]}); err != nil { + // Default task + if len(tasksAndVars) == 0 { + tasksAndVars = []string{"default"} + } + + // Parse task name and CLI variables + taskName := tasksAndVars[0] + cliVars := tasksAndVars[1:] + + // Create call with CLI variables + call := &ast.Call{ + Task: taskName, + Vars: &ast.Vars{}, + } + + // Parse CLI variables (format: KEY=VALUE) + for _, v := range cliVars { + if strings.Contains(v, "=") { + parts := strings.SplitN(v, "=", 2) + if len(parts) == 2 { + call.Vars.Set(parts[0], ast.Var{ + Value: parts[1], + }) + } + } + } + + if err := e.RunTask(context.Background(), call); err != nil { fatal(err.Error()) } return nil diff --git a/v3/internal/commands/task_integration_test.go b/v3/internal/commands/task_integration_test.go new file mode 100644 index 000000000..fc7ff6607 --- /dev/null +++ b/v3/internal/commands/task_integration_test.go @@ -0,0 +1,289 @@ +package commands + +import ( + "bytes" + "os" + "path/filepath" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/wailsapp/task/v3/taskfile/ast" +) + +func TestTaskParameterPassing(t *testing.T) { + // Skip if running in CI without proper environment + if os.Getenv("CI") == "true" && os.Getenv("SKIP_INTEGRATION_TESTS") == "true" { + t.Skip("Skipping integration test in CI") + } + + // Create a temporary directory for test + tmpDir, err := os.MkdirTemp("", "wails-task-test-*") + require.NoError(t, err) + defer os.RemoveAll(tmpDir) + + // Create a test Taskfile + taskfileContent := `version: '3' + +tasks: + build: + cmds: + - echo "PLATFORM={{.PLATFORM | default "default-platform"}}" + - echo "CONFIG={{.CONFIG | default "default-config"}}" + silent: true + + package: + cmds: + - echo "VERSION={{.VERSION | default "1.0.0"}}" + - echo "OUTPUT={{.OUTPUT | default "output.pkg"}}" + silent: true + + test: + cmds: + - echo "ENV={{.ENV | default "test"}}" + - echo "FLAGS={{.FLAGS | default "none"}}" + silent: true +` + + taskfilePath := filepath.Join(tmpDir, "Taskfile.yml") + err = os.WriteFile(taskfilePath, []byte(taskfileContent), 0644) + require.NoError(t, err) + + // Save current directory + originalWd, err := os.Getwd() + require.NoError(t, err) + defer os.Chdir(originalWd) + + // Change to test directory + err = os.Chdir(tmpDir) + require.NoError(t, err) + + tests := []struct { + name string + options *RunTaskOptions + otherArgs []string + expectedOutput []string + }{ + { + name: "Build task with parameters", + options: &RunTaskOptions{Name: "build"}, + otherArgs: []string{"PLATFORM=linux", "CONFIG=production"}, + expectedOutput: []string{ + "PLATFORM=linux", + "CONFIG=production", + }, + }, + { + name: "Package task with parameters", + options: &RunTaskOptions{Name: "package"}, + otherArgs: []string{"VERSION=2.5.0", "OUTPUT=myapp.pkg"}, + expectedOutput: []string{ + "VERSION=2.5.0", + "OUTPUT=myapp.pkg", + }, + }, + { + name: "Task with default values", + options: &RunTaskOptions{Name: "build"}, + otherArgs: []string{}, + expectedOutput: []string{ + "PLATFORM=default-platform", + "CONFIG=default-config", + }, + }, + { + name: "Task with partial parameters", + options: &RunTaskOptions{Name: "test"}, + otherArgs: []string{"ENV=staging"}, + expectedOutput: []string{ + "ENV=staging", + "FLAGS=none", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Capture output + output := captureTaskOutput(t, tt.options, tt.otherArgs) + + // Verify expected output + for _, expected := range tt.expectedOutput { + assert.Contains(t, output, expected, "Output should contain: %s", expected) + } + }) + } +} + +func TestCLIParameterFormats(t *testing.T) { + tests := []struct { + name string + otherArgs []string + expectError bool + expectedVars map[string]string + }{ + { + name: "Standard KEY=VALUE format", + otherArgs: []string{"build", "KEY1=value1", "KEY2=value2"}, + expectedVars: map[string]string{ + "KEY1": "value1", + "KEY2": "value2", + }, + }, + { + name: "Values with equals signs", + otherArgs: []string{"build", "URL=https://example.com?key=value", "FORMULA=a=b+c"}, + expectedVars: map[string]string{ + "URL": "https://example.com?key=value", + "FORMULA": "a=b+c", + }, + }, + { + name: "Values with spaces (quoted)", + otherArgs: []string{"build", "MESSAGE=Hello World", "PATH=/usr/local/bin"}, + expectedVars: map[string]string{ + "MESSAGE": "Hello World", + "PATH": "/usr/local/bin", + }, + }, + { + name: "Mixed valid and invalid arguments", + otherArgs: []string{"build", "VALID=yes", "invalid-arg", "ANOTHER=value", "--flag"}, + expectedVars: map[string]string{ + "VALID": "yes", + "ANOTHER": "value", + }, + }, + { + name: "Empty value", + otherArgs: []string{"build", "EMPTY=", "KEY=value"}, + expectedVars: map[string]string{ + "EMPTY": "", + "KEY": "value", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + call := parseTaskCall(&RunTaskOptions{}, tt.otherArgs) + + // Verify variables + for key, expectedValue := range tt.expectedVars { + var actualValue string + found := false + if call.Vars != nil { + call.Vars.Range(func(k string, v ast.Var) error { + if k == key { + actualValue = v.Value.(string) + found = true + } + return nil + }) + } + assert.True(t, found, "Variable %s not found", key) + assert.Equal(t, expectedValue, actualValue, "Variable %s mismatch", key) + } + }) + } +} + +// Helper function to capture task output +func captureTaskOutput(t *testing.T, options *RunTaskOptions, otherArgs []string) string { + // Save original stdout and stderr + oldStdout := os.Stdout + oldStderr := os.Stderr + defer func() { + os.Stdout = oldStdout + os.Stderr = oldStderr + }() + + // Create pipe to capture output + r, w, err := os.Pipe() + require.NoError(t, err) + + os.Stdout = w + os.Stderr = w + + // Run task in a goroutine + done := make(chan bool) + var taskErr error + go func() { + // Note: This is a simplified version for testing + // In real tests, you might want to mock the Task executor + taskErr = RunTask(options, otherArgs) + w.Close() + done <- true + }() + + // Read output + var buf bytes.Buffer + _, err = buf.ReadFrom(r) + require.NoError(t, err) + + // Wait for task to complete + <-done + + // Check for errors (might be expected in some tests) + if taskErr != nil && !strings.Contains(taskErr.Error(), "expected") { + t.Logf("Task error (might be expected): %v", taskErr) + } + + return buf.String() +} + +func TestBackwardCompatibility(t *testing.T) { + // Test that the old way of calling tasks still works + tests := []struct { + name string + osArgs []string + expectedTask string + expectedVars map[string]string + }{ + { + name: "Legacy os.Args parsing", + osArgs: []string{"wails3", "task", "build", "PLATFORM=windows"}, + expectedTask: "build", + expectedVars: map[string]string{ + "PLATFORM": "windows", + }, + }, + { + name: "Legacy with flags before task", + osArgs: []string{"wails3", "task", "--verbose", "test", "ENV=prod"}, + expectedTask: "test", + expectedVars: map[string]string{ + "ENV": "prod", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Save original os.Args + originalArgs := os.Args + defer func() { os.Args = originalArgs }() + + os.Args = tt.osArgs + + // Parse using the backward compatibility path + call := parseTaskCall(&RunTaskOptions{}, []string{}) + + assert.Equal(t, tt.expectedTask, call.Task) + + for key, expectedValue := range tt.expectedVars { + var actualValue string + if call.Vars != nil { + call.Vars.Range(func(k string, v ast.Var) error { + if k == key { + actualValue = v.Value.(string) + } + return nil + }) + } + assert.Equal(t, expectedValue, actualValue, "Variable %s mismatch", key) + } + }) + } +} \ No newline at end of file diff --git a/v3/internal/commands/task_test.go b/v3/internal/commands/task_test.go new file mode 100644 index 000000000..dadee8647 --- /dev/null +++ b/v3/internal/commands/task_test.go @@ -0,0 +1,188 @@ +package commands + +import ( + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/wailsapp/task/v3/taskfile/ast" +) + +func TestParseTaskAndVars(t *testing.T) { + tests := []struct { + name string + options *RunTaskOptions + otherArgs []string + osArgs []string + expectedTask string + expectedVars map[string]string + }{ + { + name: "Task name in options with CLI variables", + options: &RunTaskOptions{Name: "build"}, + otherArgs: []string{"PLATFORM=linux", "CONFIG=production"}, + expectedTask: "build", + expectedVars: map[string]string{ + "PLATFORM": "linux", + "CONFIG": "production", + }, + }, + { + name: "Task name and variables in otherArgs", + options: &RunTaskOptions{}, + otherArgs: []string{"test", "ENV=staging", "DEBUG=true"}, + expectedTask: "test", + expectedVars: map[string]string{ + "ENV": "staging", + "DEBUG": "true", + }, + }, + { + name: "Only task name, no variables", + options: &RunTaskOptions{}, + otherArgs: []string{"deploy"}, + expectedTask: "deploy", + expectedVars: map[string]string{}, + }, + { + name: "Default task when no args provided", + options: &RunTaskOptions{}, + otherArgs: []string{}, + osArgs: []string{"wails3", "task"}, // Set explicit os.Args to avoid test framework interference + expectedTask: "default", + expectedVars: map[string]string{}, + }, + { + name: "Variables with equals signs in values", + options: &RunTaskOptions{Name: "build"}, + otherArgs: []string{"URL=https://example.com?key=value", "CONFIG=key1=val1,key2=val2"}, + expectedTask: "build", + expectedVars: map[string]string{ + "URL": "https://example.com?key=value", + "CONFIG": "key1=val1,key2=val2", + }, + }, + { + name: "Skip non-variable arguments", + options: &RunTaskOptions{Name: "build"}, + otherArgs: []string{"PLATFORM=linux", "some-arg", "CONFIG=debug", "--flag"}, + expectedTask: "build", + expectedVars: map[string]string{ + "PLATFORM": "linux", + "CONFIG": "debug", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Save original os.Args + originalArgs := os.Args + defer func() { os.Args = originalArgs }() + + if tt.osArgs != nil { + os.Args = tt.osArgs + } + + // Parse the task and variables + call := parseTaskCall(tt.options, tt.otherArgs) + + // Verify task name + assert.Equal(t, tt.expectedTask, call.Task) + + // Verify variables + if len(tt.expectedVars) > 0 { + require.NotNil(t, call.Vars) + + // Check each expected variable + for key, expectedValue := range tt.expectedVars { + var actualValue string + call.Vars.Range(func(k string, v ast.Var) error { + if k == key { + actualValue = v.Value.(string) + } + return nil + }) + assert.Equal(t, expectedValue, actualValue, "Variable %s mismatch", key) + } + } else if call.Vars != nil { + // Ensure no variables were set when none expected + count := 0 + call.Vars.Range(func(k string, v ast.Var) error { + count++ + return nil + }) + assert.Equal(t, 0, count, "Expected no variables but found %d", count) + } + }) + } +} + +// Helper function to extract the task parsing logic for testing +func parseTaskCall(options *RunTaskOptions, otherArgs []string) *ast.Call { + var tasksAndVars []string + + // Check if we have a task name specified in options + if options.Name != "" { + // If task name is provided via options, use it and treat otherArgs as CLI variables + tasksAndVars = append([]string{options.Name}, otherArgs...) + } else if len(otherArgs) > 0 { + // Use otherArgs directly if provided + tasksAndVars = otherArgs + } else { + // Fall back to parsing os.Args for backward compatibility + var index int + var arg string + for index, arg = range os.Args[2:] { + if len(arg) > 0 && arg[0] != '-' { + break + } + } + + for _, taskAndVar := range os.Args[index+2:] { + if taskAndVar == "--" { + break + } + tasksAndVars = append(tasksAndVars, taskAndVar) + } + } + + // Default task + if len(tasksAndVars) == 0 { + tasksAndVars = []string{"default"} + } + + // Parse task name and CLI variables + taskName := tasksAndVars[0] + cliVars := tasksAndVars[1:] + + // Create call with CLI variables + call := &ast.Call{ + Task: taskName, + Vars: &ast.Vars{}, + } + + // Parse CLI variables (format: KEY=VALUE) + for _, v := range cliVars { + if idx := findEquals(v); idx != -1 { + key := v[:idx] + value := v[idx+1:] + call.Vars.Set(key, ast.Var{ + Value: value, + }) + } + } + + return call +} + +// Helper to find the first equals sign +func findEquals(s string) int { + for i, r := range s { + if r == '=' { + return i + } + } + return -1 +} \ No newline at end of file diff --git a/v3/internal/commands/task_wrapper.go b/v3/internal/commands/task_wrapper.go index c4e55855e..4e4073090 100644 --- a/v3/internal/commands/task_wrapper.go +++ b/v3/internal/commands/task_wrapper.go @@ -7,16 +7,23 @@ import ( "github.com/wailsapp/wails/v3/internal/flags" ) -func Build(_ *flags.Build) error { - return wrapTask("build") +// runTaskFunc is a variable to allow mocking in tests +var runTaskFunc = RunTask + +func Build(_ *flags.Build, otherArgs []string) error { + return wrapTask("build", otherArgs) } -func Package(_ *flags.Package) error { - return wrapTask("package") +func Package(_ *flags.Package, otherArgs []string) error { + return wrapTask("package", otherArgs) } -func wrapTask(command string) error { +func wrapTask(command string, otherArgs []string) error { term.Warningf("`wails3 %s` is an alias for `wails3 task %s`. Use `wails task` for better control and more options.\n", command, command) - os.Args = []string{"wails3", "task", command} - return RunTask(&RunTaskOptions{}, []string{}) + // Rebuild os.Args to include the command and all additional arguments + newArgs := []string{"wails3", "task", command} + newArgs = append(newArgs, otherArgs...) + os.Args = newArgs + // Pass the task name via options and otherArgs as CLI variables + return runTaskFunc(&RunTaskOptions{Name: command}, otherArgs) } diff --git a/v3/internal/commands/task_wrapper_test.go b/v3/internal/commands/task_wrapper_test.go new file mode 100644 index 000000000..feae13a56 --- /dev/null +++ b/v3/internal/commands/task_wrapper_test.go @@ -0,0 +1,145 @@ +package commands + +import ( + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/wailsapp/wails/v3/internal/flags" +) + +func TestWrapTask(t *testing.T) { + tests := []struct { + name string + command string + otherArgs []string + expectedOsArgs []string + }{ + { + name: "Build with parameters", + command: "build", + otherArgs: []string{"PLATFORM=linux", "CONFIG=debug"}, + expectedOsArgs: []string{"wails3", "task", "build", "PLATFORM=linux", "CONFIG=debug"}, + }, + { + name: "Package with parameters", + command: "package", + otherArgs: []string{"VERSION=1.0.0", "OUTPUT=app.pkg"}, + expectedOsArgs: []string{"wails3", "task", "package", "VERSION=1.0.0", "OUTPUT=app.pkg"}, + }, + { + name: "Build without parameters", + command: "build", + otherArgs: []string{}, + expectedOsArgs: []string{"wails3", "task", "build"}, + }, + { + name: "Build with complex parameter values", + command: "build", + otherArgs: []string{"URL=https://example.com?key=value", "TAGS=tag1,tag2,tag3"}, + expectedOsArgs: []string{"wails3", "task", "build", "URL=https://example.com?key=value", "TAGS=tag1,tag2,tag3"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Save original os.Args + originalArgs := os.Args + defer func() { os.Args = originalArgs }() + + // Mock RunTask to capture the arguments + originalRunTask := runTaskFunc + var capturedOptions *RunTaskOptions + var capturedOtherArgs []string + runTaskFunc = func(options *RunTaskOptions, otherArgs []string) error { + capturedOptions = options + capturedOtherArgs = otherArgs + return nil + } + defer func() { runTaskFunc = originalRunTask }() + + // Execute wrapTask + err := wrapTaskInternal(tt.command, tt.otherArgs) + assert.NoError(t, err) + + // Verify os.Args was set correctly + assert.Equal(t, tt.expectedOsArgs, os.Args) + + // Verify RunTask was called with correct parameters + assert.Equal(t, tt.command, capturedOptions.Name) + assert.Equal(t, tt.otherArgs, capturedOtherArgs) + }) + } +} + +func TestBuildCommand(t *testing.T) { + // Save original RunTask + originalRunTask := runTaskFunc + defer func() { runTaskFunc = originalRunTask }() + + // Mock RunTask to capture the arguments + var capturedOptions *RunTaskOptions + var capturedOtherArgs []string + runTaskFunc = func(options *RunTaskOptions, otherArgs []string) error { + capturedOptions = options + capturedOtherArgs = otherArgs + return nil + } + + // Save original os.Args + originalArgs := os.Args + defer func() { os.Args = originalArgs }() + + // Test Build command + buildFlags := &flags.Build{} + otherArgs := []string{"PLATFORM=darwin", "CONFIG=release"} + + err := Build(buildFlags, otherArgs) + assert.NoError(t, err) + assert.Equal(t, "build", capturedOptions.Name) + assert.Equal(t, otherArgs, capturedOtherArgs) +} + +func TestPackageCommand(t *testing.T) { + // Save original RunTask + originalRunTask := runTaskFunc + defer func() { runTaskFunc = originalRunTask }() + + // Mock RunTask to capture the arguments + var capturedOptions *RunTaskOptions + var capturedOtherArgs []string + runTaskFunc = func(options *RunTaskOptions, otherArgs []string) error { + capturedOptions = options + capturedOtherArgs = otherArgs + return nil + } + + // Save original os.Args + originalArgs := os.Args + defer func() { os.Args = originalArgs }() + + // Test Package command + packageFlags := &flags.Package{} + otherArgs := []string{"VERSION=2.0.0", "OUTPUT=myapp.dmg"} + + err := Package(packageFlags, otherArgs) + assert.NoError(t, err) + assert.Equal(t, "package", capturedOptions.Name) + assert.Equal(t, otherArgs, capturedOtherArgs) +} + +// Variables to enable mocking in tests +var ( + wrapTaskFunc = wrapTask +) + +// Internal version that uses the function variables for testing +func wrapTaskInternal(command string, otherArgs []string) error { + // Note: We skip the warning message in tests + // Rebuild os.Args to include the command and all additional arguments + newArgs := []string{"wails3", "task", command} + newArgs = append(newArgs, otherArgs...) + os.Args = newArgs + // Pass the task name via options and otherArgs as CLI variables + return runTaskFunc(&RunTaskOptions{Name: command}, otherArgs) +} \ No newline at end of file