5
0
mirror of https://github.com/wailsapp/wails.git synced 2025-05-02 22:13:36 +08:00

Refactored build command (#2123)

* Refactored build command

* Update v2/cmd/wails/build.go

Co-authored-by: stffabi <stffabi@users.noreply.github.com>

* WIP

* Refactor `wails doctor`

* Refactor `wails dev`

* Refactor `wails dev`

* Fix merge conflict

* Fix test

* Update build_and_test.yml

Co-authored-by: stffabi <stffabi@users.noreply.github.com>
This commit is contained in:
Lea Anthony 2022-12-01 18:18:02 +11:00 committed by GitHub
parent 9d53db4281
commit ea6aee91f1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
72 changed files with 2311 additions and 2420 deletions

View File

@ -29,6 +29,7 @@ jobs:
go-version: ${{ matrix.go-version }} go-version: ${{ matrix.go-version }}
- name: Run tests - name: Run tests
working-directory: ./v2
run: go test -v ./... run: go test -v ./...
test_templates: test_templates:

254
v2/cmd/wails/build.go Normal file
View File

@ -0,0 +1,254 @@
package main
import (
"fmt"
"github.com/leaanthony/slicer"
"github.com/pterm/pterm"
"github.com/wailsapp/wails/v2/cmd/wails/flags"
"github.com/wailsapp/wails/v2/cmd/wails/internal/gomod"
"github.com/wailsapp/wails/v2/internal/colour"
"github.com/wailsapp/wails/v2/internal/project"
"github.com/wailsapp/wails/v2/pkg/clilogger"
"github.com/wailsapp/wails/v2/pkg/commands/build"
"os"
"runtime"
"strings"
"time"
)
func buildApplication(f *flags.Build) error {
if f.NoColour {
pterm.DisableColor()
colour.ColourEnabled = false
}
quiet := f.Verbosity == flags.Quiet
// Create logger
logger := clilogger.New(os.Stdout)
logger.Mute(quiet)
if quiet {
pterm.DisableOutput()
} else {
app.PrintBanner()
}
err := f.Process()
if err != nil {
return err
}
cwd, err := os.Getwd()
if err != nil {
return err
}
projectOptions, err := project.Load(cwd)
if err != nil {
return err
}
// Create BuildOptions
buildOptions := &build.Options{
Logger: logger,
OutputType: "desktop",
OutputFile: f.OutputFilename,
CleanBinDirectory: f.Clean,
Mode: f.GetBuildMode(),
Pack: !f.NoPackage,
LDFlags: f.LdFlags,
Compiler: f.Compiler,
SkipModTidy: f.SkipModTidy,
Verbosity: f.Verbosity,
ForceBuild: f.ForceBuild,
IgnoreFrontend: f.SkipFrontend,
Compress: f.Upx,
CompressFlags: f.UpxFlags,
UserTags: f.GetTags(),
WebView2Strategy: f.GetWebView2Strategy(),
TrimPath: f.TrimPath,
RaceDetector: f.RaceDetector,
WindowsConsole: f.WindowsConsole,
Obfuscated: f.Obfuscated,
GarbleArgs: f.GarbleArgs,
SkipBindings: f.SkipBindings,
ProjectData: projectOptions,
}
tableData := pterm.TableData{
{"Platform(s)", f.Platform},
{"Compiler", f.GetCompilerPath()},
{"Skip Bindings", bool2Str(f.SkipBindings)},
{"Build Mode", f.GetBuildModeAsString()},
{"Frontend Directory", projectOptions.GetFrontendDir()},
{"Obfuscated", bool2Str(f.Obfuscated)},
}
if f.Obfuscated {
tableData = append(tableData, []string{"Garble Args", f.GarbleArgs})
}
tableData = append(tableData, pterm.TableData{
{"Skip Frontend", bool2Str(f.SkipFrontend)},
{"Compress", bool2Str(f.Upx)},
{"Package", bool2Str(!f.NoPackage)},
{"Clean Bin Dir", bool2Str(f.Clean)},
{"LDFlags", f.LdFlags},
{"Tags", "[" + strings.Join(f.GetTags(), ",") + "]"},
{"Race Detector", bool2Str(f.RaceDetector)},
}...)
if len(buildOptions.OutputFile) > 0 && f.GetTargets().Length() == 1 {
tableData = append(tableData, []string{"Output File", f.OutputFilename})
}
pterm.DefaultSection.Println("Build Options")
err = pterm.DefaultTable.WithData(tableData).Render()
if err != nil {
return err
}
err = gomod.SyncGoMod(logger, f.UpdateWailsVersionGoMod)
if err != nil {
return err
}
// Check platform
validPlatformArch := slicer.String([]string{
"darwin",
"darwin/amd64",
"darwin/arm64",
"darwin/universal",
"linux",
"linux/amd64",
"linux/arm64",
"linux/arm",
"windows",
"windows/amd64",
"windows/arm64",
"windows/386",
})
outputBinaries := map[string]string{}
// Allows cancelling the build after the first error. It would be nice if targets.Each would support funcs
// returning an error.
var targetErr error
targets := f.GetTargets()
targets.Each(func(platform string) {
if targetErr != nil {
return
}
if !validPlatformArch.Contains(platform) {
buildOptions.Logger.Println("platform '%s' is not supported - skipping. Supported platforms: %s", platform, validPlatformArch.Join(","))
return
}
desiredFilename := projectOptions.OutputFilename
if desiredFilename == "" {
desiredFilename = projectOptions.Name
}
desiredFilename = strings.TrimSuffix(desiredFilename, ".exe")
// Calculate platform and arch
platformSplit := strings.Split(platform, "/")
buildOptions.Platform = platformSplit[0]
buildOptions.Arch = f.GetDefaultArch()
if len(platformSplit) > 1 {
buildOptions.Arch = platformSplit[1]
}
banner := "Building target: " + buildOptions.Platform + "/" + buildOptions.Arch
pterm.DefaultSection.Println(banner)
if f.Upx && platform == "darwin/universal" {
pterm.Warning.Println("Warning: compress flag unsupported for universal binaries. Ignoring.")
f.Upx = false
}
switch buildOptions.Platform {
case "linux":
if runtime.GOOS != "linux" {
pterm.Warning.Println("Crosscompiling to Linux not currently supported.\n")
return
}
case "darwin":
if runtime.GOOS != "darwin" {
pterm.Warning.Println("Crosscompiling to Mac not currently supported.\n")
return
}
macTargets := targets.Filter(func(platform string) bool {
return strings.HasPrefix(platform, "darwin")
})
if macTargets.Length() == 2 {
buildOptions.BundleName = fmt.Sprintf("%s-%s.app", desiredFilename, buildOptions.Arch)
}
}
if targets.Length() > 1 {
// target filename
switch buildOptions.Platform {
case "windows":
desiredFilename = fmt.Sprintf("%s-%s", desiredFilename, buildOptions.Arch)
case "linux", "darwin":
desiredFilename = fmt.Sprintf("%s-%s-%s", desiredFilename, buildOptions.Platform, buildOptions.Arch)
}
}
if buildOptions.Platform == "windows" {
desiredFilename += ".exe"
}
buildOptions.OutputFile = desiredFilename
if f.OutputFilename != "" {
buildOptions.OutputFile = f.OutputFilename
}
if f.Obfuscated && f.SkipBindings {
pterm.Warning.Println("obfuscated flag overrides skipbindings flag.")
buildOptions.SkipBindings = false
}
if !f.DryRun {
// Start Time
start := time.Now()
compiledBinary, err := build.Build(buildOptions)
if err != nil {
pterm.Error.Println(err.Error())
targetErr = err
return
}
buildOptions.IgnoreFrontend = true
buildOptions.CleanBinDirectory = false
// Output stats
buildOptions.Logger.Println(fmt.Sprintf("Built '%s' in %s.\n", compiledBinary, time.Since(start).Round(time.Millisecond).String()))
outputBinaries[buildOptions.Platform+"/"+buildOptions.Arch] = compiledBinary
} else {
pterm.Info.Println("Dry run: skipped build.")
}
})
if targetErr != nil {
return targetErr
}
if f.DryRun {
return nil
}
if f.NSIS {
amd64Binary := outputBinaries["windows/amd64"]
arm64Binary := outputBinaries["windows/arm64"]
if amd64Binary == "" && arm64Binary == "" {
return fmt.Errorf("cannot build nsis installer - no windows targets")
}
if err := build.GenerateNSISInstaller(buildOptions, amd64Binary, arm64Binary); err != nil {
return err
}
}
return nil
}

38
v2/cmd/wails/dev.go Normal file
View File

@ -0,0 +1,38 @@
package main
import (
"github.com/pterm/pterm"
"github.com/wailsapp/wails/v2/cmd/wails/flags"
"github.com/wailsapp/wails/v2/cmd/wails/internal/dev"
"github.com/wailsapp/wails/v2/internal/colour"
"github.com/wailsapp/wails/v2/pkg/clilogger"
"os"
)
func devApplication(f *flags.Dev) error {
if f.NoColour {
pterm.DisableColor()
colour.ColourEnabled = false
}
quiet := f.Verbosity == flags.Quiet
// Create logger
logger := clilogger.New(os.Stdout)
logger.Mute(quiet)
if quiet {
pterm.DisableOutput()
} else {
app.PrintBanner()
}
err := f.Process()
if err != nil {
return err
}
return dev.Application(f, logger)
}

165
v2/cmd/wails/doctor.go Normal file
View File

@ -0,0 +1,165 @@
package main
import (
"github.com/pterm/pterm"
"github.com/wailsapp/wails/v2/cmd/wails/flags"
"github.com/wailsapp/wails/v2/internal/colour"
"github.com/wailsapp/wails/v2/internal/system"
"github.com/wailsapp/wails/v2/internal/system/packagemanager"
"runtime"
"runtime/debug"
"strings"
)
func diagnoseEnvironment(f *flags.Doctor) error {
if f.NoColour {
pterm.DisableColor()
colour.ColourEnabled = false
}
app.PrintBanner()
pterm.Print("Scanning system - Please wait (this may take a long time)...")
// Get system info
info, err := system.GetInfo()
if err != nil {
pterm.Println("Failed.")
return err
}
pterm.Println("Done.")
pterm.DefaultSection.Println("System")
systemTabledata := [][]string{
{"OS", info.OS.Name},
{"Version", info.OS.Version},
{"ID", info.OS.ID},
{"Go Version", runtime.Version()},
{"Platform", runtime.GOOS},
{"Architecture", runtime.GOARCH},
}
err = pterm.DefaultTable.WithData(systemTabledata).Render()
if err != nil {
return err
}
pterm.DefaultSection.Println("Wails")
wailsTableData := [][]string{
{"Version", app.Version()},
}
if buildInfo, _ := debug.ReadBuildInfo(); buildInfo != nil {
buildSettingToName := map[string]string{
"vcs.revision": "Revision",
"vcs.modified": "Modified",
}
for _, buildSetting := range buildInfo.Settings {
name := buildSettingToName[buildSetting.Key]
if name == "" {
continue
}
wailsTableData = append(wailsTableData, []string{name, buildSetting.Value})
}
}
// Exit early if PM not found
if info.PM != nil {
wailsTableData = append(wailsTableData, []string{"Package Manager", info.PM.Name()})
}
err = pterm.DefaultTable.WithData(wailsTableData).Render()
if err != nil {
return err
}
pterm.DefaultSection.Println("Dependencies")
// Output Dependencies Status
var dependenciesMissing = []string{}
var externalPackages = []*packagemanager.Dependency{}
var dependenciesAvailableRequired = 0
var dependenciesAvailableOptional = 0
dependenciesTableData := [][]string{
{"Dependency", "Package Name", "Status", "Version"},
}
hasOptionalDependencies := false
// Loop over dependencies
for _, dependency := range info.Dependencies {
name := dependency.Name
if dependency.Optional {
name = "*" + name
hasOptionalDependencies = true
}
packageName := "Unknown"
status := "Not Found"
// If we found the package
if dependency.PackageName != "" {
packageName = dependency.PackageName
// If it's installed, update the status
if dependency.Installed {
status = "Installed"
} else {
// Generate meaningful status text
status = "Available"
if dependency.Optional {
dependenciesAvailableOptional++
} else {
dependenciesAvailableRequired++
}
}
} else {
if !dependency.Optional {
dependenciesMissing = append(dependenciesMissing, dependency.Name)
}
if dependency.External {
externalPackages = append(externalPackages, dependency)
}
}
dependenciesTableData = append(dependenciesTableData, []string{name, packageName, status, dependency.Version})
}
err = pterm.DefaultTable.WithHasHeader(true).WithData(dependenciesTableData).Render()
if hasOptionalDependencies {
pterm.Println("* - Optional Dependency")
}
pterm.DefaultSection.Println("Diagnosis")
// Generate an appropriate diagnosis
if len(dependenciesMissing) == 0 && dependenciesAvailableRequired == 0 {
pterm.Println("Your system is ready for Wails development!")
} else {
pterm.Println("Your system has missing dependencies!\n")
}
if dependenciesAvailableRequired != 0 {
pterm.Println("Required package(s) installation details: \n" + info.Dependencies.InstallAllRequiredCommand())
}
if dependenciesAvailableOptional != 0 {
pterm.Println("Optional package(s) installation details: \n" + info.Dependencies.InstallAllOptionalCommand())
}
if len(dependenciesMissing) != 0 {
pterm.Println("Fatal:")
pterm.Println("Required dependencies missing: " + strings.Join(dependenciesMissing, " "))
pterm.Println("Please read this article on how to resolve this: https://wails.io/guides/resolving-missing-packages")
}
return nil
}

166
v2/cmd/wails/flags/build.go Normal file
View File

@ -0,0 +1,166 @@
package flags
import (
"fmt"
"github.com/leaanthony/slicer"
"github.com/wailsapp/wails/v2/internal/system"
"github.com/wailsapp/wails/v2/pkg/commands/build"
"github.com/wailsapp/wails/v2/pkg/commands/buildtags"
"os"
"os/exec"
"runtime"
"strings"
)
const (
Quiet int = 0
Normal int = 1
Verbose int = 2
)
// TODO: unify this and `build.Options`
type Build struct {
Common
BuildCommon
NoPackage bool `name:"noPackage" description:"Skips platform specific packaging"`
SkipModTidy bool `name:"m" description:"Skip mod tidy before compile"`
Upx bool `description:"Compress final binary with UPX (if installed)"`
UpxFlags string `description:"Flags to pass to upx"`
Platform string `description:"Platform to target. Comma separate multiple platforms"`
OutputFilename string `name:"o" description:"Output filename"`
Clean bool `description:"Clean the bin directory before building"`
WebView2 string `description:"WebView2 installer strategy: download,embed,browser,error"`
ForceBuild bool `name:"f" description:"Force build of application"`
UpdateWailsVersionGoMod bool `name:"u" description:"Updates go.mod to use the same Wails version as the CLI"`
Debug bool `description:"Builds the application in debug mode"`
NSIS bool `description:"Generate NSIS installer for Windows"`
TrimPath bool `description:"Remove all file system paths from the resulting executable"`
WindowsConsole bool `description:"Keep the console when building for Windows"`
Obfuscated bool `description:"Code obfuscation of bound Wails methods"`
GarbleArgs string `description:"Arguments to pass to garble"`
DryRun bool `description:"Prints the build command without executing it"`
// Build Specific
// Internal state
compilerPath string
userTags []string
wv2rtstrategy string // WebView2 runtime strategy
defaultArch string // Default architecture
}
func (b *Build) Default() *Build {
defaultPlatform := os.Getenv("GOOS")
if defaultPlatform == "" {
defaultPlatform = runtime.GOOS
}
defaultArch := os.Getenv("GOARCH")
if defaultArch == "" {
if system.IsAppleSilicon {
defaultArch = "arm64"
} else {
defaultArch = runtime.GOARCH
}
}
b.defaultArch = defaultArch
platform := defaultPlatform + "/" + defaultArch
result := &Build{
Platform: platform,
WebView2: "download",
GarbleArgs: "-literals -tiny -seed=random",
}
result.BuildCommon = result.BuildCommon.Default()
return result
}
func (b *Build) GetBuildMode() build.Mode {
if b.Debug {
return build.Debug
}
return build.Production
}
func (b *Build) GetWebView2Strategy() string {
return b.wv2rtstrategy
}
func (b *Build) GetTargets() *slicer.StringSlicer {
var targets slicer.StringSlicer
targets.AddSlice(strings.Split(b.Platform, ","))
targets.Deduplicate()
return &targets
}
func (b *Build) GetCompilerPath() string {
return b.compilerPath
}
func (b *Build) GetTags() []string {
return b.userTags
}
func (b *Build) Process() error {
// Lookup compiler path
var err error
b.compilerPath, err = exec.LookPath(b.Compiler)
if err != nil {
return fmt.Errorf("unable to find compiler: %s", b.Compiler)
}
// Process User Tags
b.userTags, err = buildtags.Parse(b.Tags)
if err != nil {
return err
}
// WebView2 installer strategy (download by default)
b.WebView2 = strings.ToLower(b.WebView2)
if b.WebView2 != "" {
validWV2Runtime := slicer.String([]string{"download", "embed", "browser", "error"})
if !validWV2Runtime.Contains(b.WebView2) {
return fmt.Errorf("invalid option for flag 'webview2': %s", b.WebView2)
}
b.wv2rtstrategy = "wv2runtime." + b.WebView2
}
return nil
}
func bool2Str(b bool) string {
if b {
return "true"
}
return "false"
}
func (b *Build) GetBuildModeAsString() string {
if b.Debug {
return "debug"
}
return "production"
}
func (b *Build) GetDefaultArch() string {
return b.defaultArch
}
/*
_, _ = fmt.Fprintf(w, "Frontend Directory: \t%s\n", projectOptions.GetFrontendDir())
_, _ = fmt.Fprintf(w, "Obfuscated: \t%t\n", buildOptions.Obfuscated)
if buildOptions.Obfuscated {
_, _ = fmt.Fprintf(w, "Garble Args: \t%s\n", buildOptions.GarbleArgs)
}
_, _ = fmt.Fprintf(w, "Skip Frontend: \t%t\n", skipFrontend)
_, _ = fmt.Fprintf(w, "Compress: \t%t\n", buildOptions.Compress)
_, _ = fmt.Fprintf(w, "Package: \t%t\n", buildOptions.Pack)
_, _ = fmt.Fprintf(w, "Clean Bin Dir: \t%t\n", buildOptions.CleanBinDirectory)
_, _ = fmt.Fprintf(w, "LDFlags: \t\"%s\"\n", buildOptions.LDFlags)
_, _ = fmt.Fprintf(w, "Tags: \t[%s]\n", strings.Join(buildOptions.UserTags, ","))
_, _ = fmt.Fprintf(w, "Race Detector: \t%t\n", buildOptions.RaceDetector)
if len(buildOptions.OutputFile) > 0 && targets.Length() == 1 {
_, _ = fmt.Fprintf(w, "Output File: \t%s\n", buildOptions.OutputFile)
}
*/

View File

@ -0,0 +1,18 @@
package flags
type BuildCommon struct {
LdFlags string `description:"Additional ldflags to pass to the compiler"`
Compiler string `description:"Use a different go compiler to build, eg go1.15beta1"`
SkipBindings bool `description:"Skips generation of bindings"`
RaceDetector bool `name:"race" description:"Build with Go's race detector"`
SkipFrontend bool `name:"s" description:"Skips building the frontend"`
Verbosity int `name:"v" description:"Verbosity level (0 = quiet, 1 = normal, 2 = verbose)"`
Tags string `description:"Build tags to pass to Go compiler. Must be quoted. Space or comma (but not both) separated"`
}
func (c BuildCommon) Default() BuildCommon {
return BuildCommon{
Compiler: "go",
Verbosity: 1,
}
}

View File

@ -0,0 +1,5 @@
package flags
type Common struct {
NoColour bool `description:"Disable colour in output"`
}

144
v2/cmd/wails/flags/dev.go Normal file
View File

@ -0,0 +1,144 @@
package flags
import (
"fmt"
"github.com/samber/lo"
"github.com/wailsapp/wails/v2/internal/project"
"github.com/wailsapp/wails/v2/pkg/commands/build"
"net"
"net/url"
"os"
"path/filepath"
"runtime"
)
type Dev struct {
BuildCommon
AssetDir string `flag:"assetdir" description:"Serve assets from the given directory instead of using the provided asset FS"`
Extensions string `flag:"e" description:"Extensions to trigger rebuilds (comma separated) eg go"`
ReloadDirs string `flag:"reloaddirs" description:"Additional directories to trigger reloads (comma separated)"`
Browser bool `flag:"browser" description:"Open the application in a browser"`
NoReload bool `flag:"noreload" description:"Disable reload on asset change"`
NoColour bool `flag:"nocolor" description:"Disable colour in output"`
WailsJSDir string `flag:"wailsjsdir" description:"Directory to generate the Wails JS modules"`
LogLevel string `flag:"loglevel" description:"LogLevel to use - Trace, Debug, Info, Warning, Error)"`
ForceBuild bool `flag:"f" description:"Force build of application"`
Debounce int `flag:"debounce" description:"The amount of time to wait to trigger a reload on change"`
DevServer string `flag:"devserver" description:"The address of the wails dev server"`
AppArgs string `flag:"appargs" description:"arguments to pass to the underlying app (quoted and space separated)"`
Save bool `flag:"save" description:"Save the given flags as defaults"`
FrontendDevServerURL string `flag:"frontenddevserverurl" description:"The url of the external frontend dev server to use"`
// Internal state
devServerURL *url.URL
projectConfig *project.Project
}
func (*Dev) Default() *Dev {
result := &Dev{
Extensions: "go",
Debounce: 100,
}
result.BuildCommon = result.BuildCommon.Default()
return result
}
func (d *Dev) Process() error {
var err error
err = d.loadAndMergeProjectConfig()
if err != nil {
return err
}
if _, _, err := net.SplitHostPort(d.DevServer); err != nil {
return fmt.Errorf("DevServer is not of the form 'host:port', please check your wails.json")
}
d.devServerURL, err = url.Parse("http://" + d.DevServer)
if err != nil {
return err
}
return nil
}
func (d *Dev) loadAndMergeProjectConfig() error {
var err error
cwd, err := os.Getwd()
if err != nil {
return err
}
d.projectConfig, err = project.Load(cwd)
if err != nil {
return err
}
d.AssetDir, _ = lo.Coalesce(d.AssetDir, d.projectConfig.AssetDirectory)
d.projectConfig.AssetDirectory = filepath.ToSlash(d.AssetDir)
if d.AssetDir != "" {
d.AssetDir, err = filepath.Abs(d.AssetDir)
if err != nil {
return err
}
}
d.ReloadDirs, _ = lo.Coalesce(d.ReloadDirs, d.projectConfig.ReloadDirectories)
d.projectConfig.ReloadDirectories = filepath.ToSlash(d.ReloadDirs)
d.DevServer, _ = lo.Coalesce(d.DevServer, d.projectConfig.DevServer)
d.projectConfig.DevServer = d.DevServer
d.FrontendDevServerURL, _ = lo.Coalesce(d.FrontendDevServerURL, d.projectConfig.FrontendDevServerURL)
d.projectConfig.FrontendDevServerURL = d.FrontendDevServerURL
d.WailsJSDir, _ = lo.Coalesce(d.WailsJSDir, d.projectConfig.GetWailsJSDir(), d.projectConfig.GetFrontendDir())
d.projectConfig.WailsJSDir = filepath.ToSlash(d.WailsJSDir)
if d.Debounce == 100 && d.projectConfig.DebounceMS != 100 {
if d.projectConfig.DebounceMS == 0 {
d.projectConfig.DebounceMS = 100
}
d.Debounce = d.projectConfig.DebounceMS
}
d.projectConfig.DebounceMS = d.Debounce
d.AppArgs, _ = lo.Coalesce(d.AppArgs, d.projectConfig.AppArgs)
if d.Save {
err = d.projectConfig.Save()
if err != nil {
return err
}
}
return nil
}
// GenerateBuildOptions creates a build.Options using the flags
func (d *Dev) GenerateBuildOptions() *build.Options {
result := &build.Options{
OutputType: "dev",
Mode: build.Dev,
Arch: runtime.GOARCH,
Pack: true,
Platform: runtime.GOOS,
LDFlags: d.LdFlags,
Compiler: d.Compiler,
ForceBuild: d.ForceBuild,
IgnoreFrontend: d.SkipFrontend,
Verbosity: d.Verbosity,
WailsJSDir: d.WailsJSDir,
RaceDetector: d.RaceDetector,
ProjectData: d.projectConfig,
}
return result
}
func (d *Dev) ProjectConfig() *project.Project {
return d.projectConfig
}
func (d *Dev) DevServerURL() *url.URL {
return d.devServerURL
}

View File

@ -0,0 +1,9 @@
package flags
type Doctor struct {
Common
}
func (b *Doctor) Default() *Doctor {
return &Doctor{}
}

View File

@ -0,0 +1,14 @@
package flags
type GenerateModule struct {
Common
Tags string `description:"Build tags to pass to Go compiler. Must be quoted. Space or comma (but not both) separated"`
Verbosity int `name:"v" description:"Verbosity level (0 = quiet, 1 = normal, 2 = verbose)"`
}
type GenerateTemplate struct {
Common
Name string `description:"Name of the template to generate"`
Frontend string `description:"Frontend to use for the template"`
Quiet bool `description:"Suppress output"`
}

View File

@ -0,0 +1,22 @@
package flags
type Init struct {
Common
TemplateName string `name:"t" description:"Name of built-in template to use, path to template or template url"`
ProjectName string `name:"n" description:"Name of project"`
CIMode bool `name:"ci" description:"CI Mode"`
ProjectDir string `name:"d" description:"Project directory"`
Quiet bool `name:"q" description:"Suppress output to console"`
InitGit bool `name:"g" description:"Initialise git repository"`
IDE string `name:"ide" description:"Generate IDE project files"`
List bool `name:"l" description:"List templates"`
}
func (i *Init) Default() *Init {
result := &Init{
TemplateName: "vanilla",
}
return result
}

View File

@ -0,0 +1,6 @@
package flags
type ShowReleaseNotes struct {
Common
Version string `description:"The version to show the release notes for"`
}

View File

@ -0,0 +1,7 @@
package flags
type Update struct {
Common
Version string `description:"The version to update to"`
PreRelease bool `name:"pre" description:"Update to latest pre-release"`
}

245
v2/cmd/wails/generate.go Normal file
View File

@ -0,0 +1,245 @@
package main
import (
"fmt"
"github.com/leaanthony/debme"
"github.com/leaanthony/gosod"
"github.com/pterm/pterm"
"github.com/tidwall/sjson"
"github.com/wailsapp/wails/v2/cmd/wails/flags"
"github.com/wailsapp/wails/v2/cmd/wails/internal/template"
"github.com/wailsapp/wails/v2/internal/colour"
"github.com/wailsapp/wails/v2/internal/fs"
"github.com/wailsapp/wails/v2/internal/project"
"github.com/wailsapp/wails/v2/pkg/clilogger"
"github.com/wailsapp/wails/v2/pkg/commands/bindings"
"github.com/wailsapp/wails/v2/pkg/commands/buildtags"
"os"
"path/filepath"
)
func generateModule(f *flags.GenerateModule) error {
if f.NoColour {
pterm.DisableColor()
colour.ColourEnabled = false
}
quiet := f.Verbosity == flags.Quiet
logger := clilogger.New(os.Stdout)
logger.Mute(quiet)
buildTags, err := buildtags.Parse(f.Tags)
if err != nil {
return err
}
cwd, err := os.Getwd()
if err != nil {
return err
}
projectConfig, err := project.Load(cwd)
if err != nil {
return err
}
_, err = bindings.GenerateBindings(bindings.Options{
Tags: buildTags,
TsPrefix: projectConfig.Bindings.TsGeneration.Prefix,
TsSuffix: projectConfig.Bindings.TsGeneration.Suffix,
})
if err != nil {
return err
}
return nil
}
func generateTemplate(f *flags.GenerateTemplate) error {
if f.NoColour {
pterm.DisableColor()
colour.ColourEnabled = false
}
quiet := f.Quiet
logger := clilogger.New(os.Stdout)
logger.Mute(quiet)
// name is mandatory
if f.Name == "" {
return fmt.Errorf("please provide a template name using the -name flag")
}
// If the current directory is not empty, we create a new directory
cwd, err := os.Getwd()
if err != nil {
return err
}
templateDir := filepath.Join(cwd, f.Name)
if !fs.DirExists(templateDir) {
err := os.MkdirAll(templateDir, 0755)
if err != nil {
return err
}
}
empty, err := fs.DirIsEmpty(templateDir)
if err != nil {
return err
}
pterm.DefaultSection.Println("Generating template")
if !empty {
templateDir = filepath.Join(cwd, f.Name)
printBulletPoint("Creating new template directory:", f.Name)
err = fs.Mkdir(templateDir)
if err != nil {
return err
}
}
// Create base template
baseTemplate, err := debme.FS(template.Base, "base")
if err != nil {
return err
}
g := gosod.New(baseTemplate)
g.SetTemplateFilters([]string{".template"})
err = os.Chdir(templateDir)
if err != nil {
return err
}
type templateData struct {
Name string
Description string
TemplateDir string
WailsVersion string
}
printBulletPoint("Extracting base template files...")
err = g.Extract(templateDir, &templateData{
Name: f.Name,
TemplateDir: templateDir,
WailsVersion: app.Version(),
})
if err != nil {
return err
}
err = os.Chdir(cwd)
if err != nil {
return err
}
// If we aren't migrating the files, just exit
if f.Frontend == "" {
pterm.Println()
pterm.Println()
pterm.Info.Println("No frontend specified to migrate. Template created.")
pterm.Println()
return nil
}
// Remove frontend directory
frontendDir := filepath.Join(templateDir, "frontend")
err = os.RemoveAll(frontendDir)
if err != nil {
return err
}
// Copy the files into a new frontend directory
printBulletPoint("Migrating existing project files to frontend directory...")
sourceDir, err := filepath.Abs(f.Frontend)
if err != nil {
return err
}
newFrontendDir := filepath.Join(templateDir, "frontend")
err = fs.CopyDirExtended(sourceDir, newFrontendDir, []string{f.Name, "node_modules"})
if err != nil {
return err
}
// Process package.json
err = processPackageJSON(frontendDir)
if err != nil {
return err
}
// Process package-lock.json
err = processPackageLockJSON(frontendDir)
if err != nil {
return err
}
// Remove node_modules - ignore error, eg it doesn't exist
_ = os.RemoveAll(filepath.Join(frontendDir, "node_modules"))
return nil
}
func processPackageJSON(frontendDir string) error {
var err error
packageJSON := filepath.Join(frontendDir, "package.json")
if !fs.FileExists(packageJSON) {
return fmt.Errorf("no package.json found - cannot process")
}
json, err := os.ReadFile(packageJSON)
if err != nil {
return err
}
// We will ignore these errors - it's not critical
printBulletPoint("Updating package.json data...")
json, _ = sjson.SetBytes(json, "name", "{{.ProjectName}}")
json, _ = sjson.SetBytes(json, "author", "{{.AuthorName}}")
err = os.WriteFile(packageJSON, json, 0644)
if err != nil {
return err
}
baseDir := filepath.Dir(packageJSON)
printBulletPoint("Renaming package.json -> package.tmpl.json...")
err = os.Rename(packageJSON, filepath.Join(baseDir, "package.tmpl.json"))
if err != nil {
return err
}
return nil
}
func processPackageLockJSON(frontendDir string) error {
var err error
filename := filepath.Join(frontendDir, "package-lock.json")
if !fs.FileExists(filename) {
return fmt.Errorf("no package-lock.json found - cannot process")
}
data, err := os.ReadFile(filename)
if err != nil {
return err
}
json := string(data)
// We will ignore these errors - it's not critical
printBulletPoint("Updating package-lock.json data...")
json, _ = sjson.Set(json, "name", "{{.ProjectName}}")
err = os.WriteFile(filename, []byte(json), 0644)
if err != nil {
return err
}
baseDir := filepath.Dir(filename)
printBulletPoint("Renaming package-lock.json -> package-lock.tmpl.json...")
err = os.Rename(filename, filepath.Join(baseDir, "package-lock.tmpl.json"))
if err != nil {
return err
}
return nil
}

278
v2/cmd/wails/init.go Normal file
View File

@ -0,0 +1,278 @@
package main
import (
"bufio"
"fmt"
"github.com/flytam/filenamify"
"github.com/leaanthony/slicer"
"github.com/pkg/errors"
"github.com/pterm/pterm"
"github.com/wailsapp/wails/v2/cmd/wails/flags"
"github.com/wailsapp/wails/v2/internal/colour"
"github.com/wailsapp/wails/v2/pkg/buildassets"
"github.com/wailsapp/wails/v2/pkg/clilogger"
"github.com/wailsapp/wails/v2/pkg/git"
"github.com/wailsapp/wails/v2/pkg/templates"
"os"
"os/exec"
"path/filepath"
"strings"
"time"
)
func initProject(f *flags.Init) error {
if f.NoColour {
pterm.DisableColor()
colour.ColourEnabled = false
}
quiet := f.Quiet
// Create logger
logger := clilogger.New(os.Stdout)
logger.Mute(quiet)
// Are we listing templates?
if f.List {
app.PrintBanner()
templateList, err := templates.List()
if err != nil {
return err
}
pterm.DefaultSection.Println("Available templates")
table := pterm.TableData{{"Template", "Short Name", "Description"}}
for _, template := range templateList {
table = append(table, []string{template.Name, template.ShortName, template.Description})
}
err = pterm.DefaultTable.WithHasHeader(true).WithBoxed(true).WithData(table).Render()
pterm.Println()
return err
}
// Validate name
if len(f.ProjectName) == 0 {
return fmt.Errorf("please provide a project name using the -n flag")
}
// Validate IDE option
supportedIDEs := slicer.String([]string{"vscode", "goland"})
ide := strings.ToLower(f.IDE)
if ide != "" {
if !supportedIDEs.Contains(ide) {
return fmt.Errorf("ide '%s' not supported. Valid values: %s", ide, supportedIDEs.Join(" "))
}
}
if !quiet {
app.PrintBanner()
}
pterm.DefaultSection.Printf("Initialising Project '%s'", f.ProjectName)
projectFilename, err := filenamify.Filenamify(f.ProjectName, filenamify.Options{
Replacement: "_",
MaxLength: 255,
})
if err != nil {
return err
}
goBinary, err := exec.LookPath("go")
if err != nil {
return fmt.Errorf("unable to find Go compiler. Please download and install Go: https://golang.org/dl/")
}
// Get base path and convert to forward slashes
goPath := filepath.ToSlash(filepath.Dir(goBinary))
// Trim bin directory
goSDKPath := strings.TrimSuffix(goPath, "/bin")
// Create Template Options
options := &templates.Options{
ProjectName: f.ProjectName,
TargetDir: f.ProjectDir,
TemplateName: f.TemplateName,
Logger: logger,
IDE: ide,
InitGit: f.InitGit,
ProjectNameFilename: projectFilename,
WailsVersion: app.Version(),
GoSDKPath: goSDKPath,
}
// Try to discover author details from git config
findAuthorDetails(options)
// Start Time
start := time.Now()
// Install the template
remote, template, err := templates.Install(options)
if err != nil {
return err
}
// Install the default assets
err = buildassets.Install(options.TargetDir)
if err != nil {
return err
}
err = os.Chdir(options.TargetDir)
if err != nil {
return err
}
if !f.CIMode {
// Run `go mod tidy` to ensure `go.sum` is up to date
cmd := exec.Command("go", "mod", "tidy")
cmd.Dir = options.TargetDir
cmd.Stderr = os.Stderr
if !quiet {
cmd.Stdout = os.Stdout
}
err = cmd.Run()
if err != nil {
return err
}
} else {
// Update go mod
workspace := os.Getenv("GITHUB_WORKSPACE")
pterm.Println("GitHub workspace:", workspace)
if workspace == "" {
os.Exit(1)
}
updateReplaceLine(workspace)
}
// Remove the `.git`` directory in the template project
err = os.RemoveAll(".git")
if err != nil {
return err
}
if options.InitGit {
err = initGit(options)
if err != nil {
return err
}
}
if quiet {
return nil
}
// Output stats
elapsed := time.Since(start)
// Create pterm table
table := pterm.TableData{
{"Project Name", options.ProjectName},
{"Project Directory", options.TargetDir},
{"Template", template.Name},
{"Template Source", template.HelpURL},
}
err = pterm.DefaultTable.WithData(table).Render()
if err != nil {
return err
}
// IDE message
switch options.IDE {
case "vscode":
pterm.Println()
pterm.Info.Println("VSCode config files generated.")
case "goland":
pterm.Println()
pterm.Info.Println("Goland config files generated.")
}
if options.InitGit {
pterm.Info.Println("Git repository initialised.")
}
if remote {
pterm.Warning.Println("NOTE: You have created a project using a remote template. The Wails project takes no responsibility for 3rd party templates. Only use remote templates that you trust.")
}
pterm.Println("")
pterm.Printf("Initialised project '%s' in %s.\n", options.ProjectName, elapsed.Round(time.Millisecond).String())
pterm.Println("")
return nil
}
func initGit(options *templates.Options) error {
err := git.InitRepo(options.TargetDir)
if err != nil {
return errors.Wrap(err, "Unable to initialise git repository:")
}
ignore := []string{
"build/bin",
"frontend/dist",
"frontend/node_modules",
}
err = os.WriteFile(filepath.Join(options.TargetDir, ".gitignore"), []byte(strings.Join(ignore, "\n")), 0644)
if err != nil {
return errors.Wrap(err, "Unable to create gitignore")
}
return nil
}
// findAuthorDetails tries to find the user's name and email
// from gitconfig. If it finds them, it stores them in the project options
func findAuthorDetails(options *templates.Options) {
if git.IsInstalled() {
name, err := git.Name()
if err == nil {
options.AuthorName = strings.TrimSpace(name)
}
email, err := git.Email()
if err == nil {
options.AuthorEmail = strings.TrimSpace(email)
}
}
}
func updateReplaceLine(targetPath string) {
file, err := os.Open("go.mod")
if err != nil {
fatal(err.Error())
}
var lines []string
scanner := bufio.NewScanner(file)
for scanner.Scan() {
lines = append(lines, scanner.Text())
}
err = file.Close()
if err != nil {
fatal(err.Error())
}
if err := scanner.Err(); err != nil {
fatal(err.Error())
}
for i, line := range lines {
println(line)
if strings.HasPrefix(line, "// replace") {
pterm.Println("Found replace line")
splitLine := strings.Split(line, " ")
splitLine[5] = targetPath + "/v2"
lines[i] = strings.Join(splitLine[1:], " ")
continue
}
}
err = os.WriteFile("go.mod", []byte(strings.Join(lines, "\n")), 0644)
if err != nil {
fatal(err.Error())
}
}

View File

@ -1,46 +0,0 @@
# Build
The build command processes the Wails project and generates an application binary.
## Usage
`wails build <flags>`
### Flags
| Flag | Details | Default |
| :------------- | :----------- | :------ |
| -clean | Clean the bin directory before building | |
| -compiler path/to/compiler | Use a different go compiler, eg go1.15beta1 | go |
| -ldflags "custom ld flags" | Use given ldflags | |
| -o path/to/binary | Compile to given path/filename | |
| -k | Keep generated assets | |
| -tags | Build tags to pass to Go compiler (quoted and space separated) | |
| -upx | Compress final binary with UPX (if installed) | |
| -upxflags "custom flags" | Flags to pass to upx | |
| -v int | Verbosity level (0 - silent, 1 - default, 2 - verbose) | 1 |
| -delve | If true, runs delve on the compiled binary | false |
## The Build Process
The build process is as follows:
- The flags are processed, and an Options struct built containing the build context.
- The type of target is determined, and a custom build process is followed for target.
### Desktop Target
- The frontend dependencies are installed. The command is read from the project file `wails.json` under the key `frontend:install` and executed in the `frontend` directory. If this is not defined, it is ignored.
- The frontend is then built. This command is read from the project file `wails.json` under the key `frontend:install` and executed in the `frontend` directory. If this is not defined, it is ignored.
- The project directory is checked to see if the `build` directory exists. If not, it is created and default project assets are copied to it.
- An asset bundle is then created by reading the `html` key from `wails.json` and loading the referenced file. This is then parsed, looking for local Javascript and CSS references. Those files are in turn loaded into memory, converted to C data and saved into the asset bundle located at `build/assets.h`, which also includes the original HTML.
- The application icon is then processed: if there is no `build/appicon.png`, a default icon is copied. On Windows,
an `app.ico` file is generated from this png. On Mac, `icons.icns` is generated.
- The platform assets in the `build/<platform>` directory are processed: manifest + icons compiled to a `.syso` file (
deleted after compilation), `info.plist` copied to `.app` on Mac.
- If we are building a universal binary for Mac, the application is compiled for both `arm64` and `amd64`. The `lipo`
tool is then executed to create the universal binary.
- If we are not building a universal binary for Mac, the application is built using `go build`, using build tags to indicate type of application and build mode (debug/production).
- If the `-upx` flag was provided, `upx` is invoked to compress the binary. Custom flags may be provided using the `-upxflags` flag.

View File

@ -1,401 +0,0 @@
package build
import (
"fmt"
"io"
"os"
"os/exec"
"runtime"
"strings"
"text/tabwriter"
"time"
"github.com/wailsapp/wails/v2/pkg/commands/buildtags"
"github.com/wailsapp/wails/v2/internal/colour"
"github.com/wailsapp/wails/v2/internal/project"
"github.com/wailsapp/wails/v2/internal/system"
"github.com/leaanthony/clir"
"github.com/leaanthony/slicer"
"github.com/wailsapp/wails/v2/pkg/clilogger"
"github.com/wailsapp/wails/v2/pkg/commands/build"
)
// AddBuildSubcommand adds the `build` command for the Wails application
func AddBuildSubcommand(app *clir.Cli, w io.Writer) {
outputType := "desktop"
validTargetTypes := slicer.String([]string{"desktop", "hybrid", "server"})
command := app.NewSubCommand("build", "Builds the application")
// Setup noPackage flag
noPackage := false
command.BoolFlag("noPackage", "Skips platform specific packaging", &noPackage)
compilerCommand := "go"
command.StringFlag("compiler", "Use a different go compiler to build, eg go1.15beta1", &compilerCommand)
skipModTidy := false
command.BoolFlag("m", "Skip mod tidy before compile", &skipModTidy)
compress := false
command.BoolFlag("upx", "Compress final binary with UPX (if installed)", &compress)
compressFlags := ""
command.StringFlag("upxflags", "Flags to pass to upx", &compressFlags)
defaultPlatform := os.Getenv("GOOS")
if defaultPlatform == "" {
defaultPlatform = runtime.GOOS
}
defaultArch := os.Getenv("GOARCH")
if defaultArch == "" {
if system.IsAppleSilicon {
defaultArch = "arm64"
} else {
defaultArch = runtime.GOARCH
}
}
platform := defaultPlatform + "/" + defaultArch
command.StringFlag("platform", "Platform to target. Comma separate multiple platforms", &platform)
// Verbosity
verbosity := 1
command.IntFlag("v", "Verbosity level (0 - silent, 1 - default, 2 - verbose)", &verbosity)
// ldflags to pass to `go`
ldflags := ""
command.StringFlag("ldflags", "optional ldflags", &ldflags)
// tags to pass to `go`
tags := ""
command.StringFlag("tags", "Build tags to pass to Go compiler. Must be quoted. Space or comma (but not both) separated", &tags)
outputFilename := ""
command.StringFlag("o", "Output filename", &outputFilename)
// Clean bin directory
cleanBinDirectory := false
command.BoolFlag("clean", "Clean the bin directory before building", &cleanBinDirectory)
webview2 := "download"
command.StringFlag("webview2", "WebView2 installer strategy: download,embed,browser,error.", &webview2)
skipFrontend := false
command.BoolFlag("s", "Skips building the frontend", &skipFrontend)
forceBuild := false
command.BoolFlag("f", "Force build application", &forceBuild)
updateGoModWailsVersion := false
command.BoolFlag("u", "Updates go.mod to use the same Wails version as the CLI", &updateGoModWailsVersion)
debug := false
command.BoolFlag("debug", "Retains debug data in the compiled application", &debug)
nsis := false
command.BoolFlag("nsis", "Generate NSIS installer for Windows", &nsis)
trimpath := false
command.BoolFlag("trimpath", "Remove all file system paths from the resulting executable", &trimpath)
raceDetector := false
command.BoolFlag("race", "Build with Go's race detector", &raceDetector)
windowsConsole := false
command.BoolFlag("windowsconsole", "Keep the console when building for Windows", &windowsConsole)
obfuscated := false
command.BoolFlag("obfuscated", "Code obfuscation of bound Wails methods", &obfuscated)
garbleargs := "-literals -tiny -seed=random"
command.StringFlag("garbleargs", "Arguments to pass to garble", &garbleargs)
dryRun := false
command.BoolFlag("dryrun", "Dry run, prints the config for the command that would be executed", &dryRun)
skipBindings := false
command.BoolFlag("skipbindings", "Skips generation of bindings", &skipBindings)
command.Action(func() error {
quiet := verbosity == 0
// Create logger
logger := clilogger.New(w)
logger.Mute(quiet)
// Validate output type
if !validTargetTypes.Contains(outputType) {
return fmt.Errorf("output type '%s' is not valid", outputType)
}
if !quiet {
app.PrintBanner()
}
// Lookup compiler path
compilerPath, err := exec.LookPath(compilerCommand)
if err != nil {
return fmt.Errorf("unable to find compiler: %s", compilerCommand)
}
// Process User Tags
userTags, err := buildtags.Parse(tags)
if err != nil {
return err
}
// Webview2 installer strategy (download by default)
wv2rtstrategy := ""
webview2 = strings.ToLower(webview2)
if webview2 != "" {
validWV2Runtime := slicer.String([]string{"download", "embed", "browser", "error"})
if !validWV2Runtime.Contains(webview2) {
return fmt.Errorf("invalid option for flag 'webview2': %s", webview2)
}
// These are the build tags associated with the strategies
switch webview2 {
case "embed":
wv2rtstrategy = "wv2runtime.embed"
case "error":
wv2rtstrategy = "wv2runtime.error"
case "browser":
wv2rtstrategy = "wv2runtime.browser"
}
}
mode := build.Production
modeString := "Production"
if debug {
mode = build.Debug
modeString = "Debug"
}
var targets slicer.StringSlicer
targets.AddSlice(strings.Split(platform, ","))
targets.Deduplicate()
cwd, err := os.Getwd()
if err != nil {
return err
}
projectOptions, err := project.Load(cwd)
if err != nil {
return err
}
// Create BuildOptions
buildOptions := &build.Options{
Logger: logger,
OutputType: outputType,
OutputFile: outputFilename,
CleanBinDirectory: cleanBinDirectory,
Mode: mode,
Pack: !noPackage,
LDFlags: ldflags,
Compiler: compilerCommand,
SkipModTidy: skipModTidy,
Verbosity: verbosity,
ForceBuild: forceBuild,
IgnoreFrontend: skipFrontend,
Compress: compress,
CompressFlags: compressFlags,
UserTags: userTags,
WebView2Strategy: wv2rtstrategy,
TrimPath: trimpath,
RaceDetector: raceDetector,
WindowsConsole: windowsConsole,
Obfuscated: obfuscated,
GarbleArgs: garbleargs,
SkipBindings: skipBindings,
ProjectData: projectOptions,
}
// Start a new tabwriter
if !quiet {
w := new(tabwriter.Writer)
w.Init(os.Stdout, 8, 8, 0, '\t', 0)
// Write out the system information
_, _ = fmt.Fprintf(w, "App Type: \t%s\n", buildOptions.OutputType)
_, _ = fmt.Fprintf(w, "Platforms: \t%s\n", platform)
_, _ = fmt.Fprintf(w, "Compiler: \t%s\n", compilerPath)
_, _ = fmt.Fprintf(w, "Skip Bindings: \t%t\n", skipBindings)
_, _ = fmt.Fprintf(w, "Build Mode: \t%s\n", modeString)
_, _ = fmt.Fprintf(w, "Frontend Directory: \t%s\n", projectOptions.GetFrontendDir())
_, _ = fmt.Fprintf(w, "Obfuscated: \t%t\n", buildOptions.Obfuscated)
if buildOptions.Obfuscated {
_, _ = fmt.Fprintf(w, "Garble Args: \t%s\n", buildOptions.GarbleArgs)
}
_, _ = fmt.Fprintf(w, "Skip Frontend: \t%t\n", skipFrontend)
_, _ = fmt.Fprintf(w, "Compress: \t%t\n", buildOptions.Compress)
_, _ = fmt.Fprintf(w, "Package: \t%t\n", buildOptions.Pack)
_, _ = fmt.Fprintf(w, "Clean Bin Dir: \t%t\n", buildOptions.CleanBinDirectory)
_, _ = fmt.Fprintf(w, "LDFlags: \t\"%s\"\n", buildOptions.LDFlags)
_, _ = fmt.Fprintf(w, "Tags: \t[%s]\n", strings.Join(buildOptions.UserTags, ","))
_, _ = fmt.Fprintf(w, "Race Detector: \t%t\n", buildOptions.RaceDetector)
if len(buildOptions.OutputFile) > 0 && targets.Length() == 1 {
_, _ = fmt.Fprintf(w, "Output File: \t%s\n", buildOptions.OutputFile)
}
_, _ = fmt.Fprintf(w, "\n")
err = w.Flush()
if err != nil {
return err
}
}
err = SyncGoMod(logger, updateGoModWailsVersion)
if err != nil {
return err
}
// Check platform
validPlatformArch := slicer.String([]string{
"darwin",
"darwin/amd64",
"darwin/arm64",
"darwin/universal",
"linux",
"linux/amd64",
"linux/arm64",
"linux/arm",
"windows",
"windows/amd64",
"windows/arm64",
"windows/386",
})
outputBinaries := map[string]string{}
// Allows cancelling the build after the first error. It would be nice if targets.Each would support funcs
// returning an error.
var targetErr error
targets.Each(func(platform string) {
if targetErr != nil {
return
}
if !validPlatformArch.Contains(platform) {
buildOptions.Logger.Println("platform '%s' is not supported - skipping. Supported platforms: %s", platform, validPlatformArch.Join(","))
return
}
desiredFilename := projectOptions.OutputFilename
if desiredFilename == "" {
desiredFilename = projectOptions.Name
}
desiredFilename = strings.TrimSuffix(desiredFilename, ".exe")
// Calculate platform and arch
platformSplit := strings.Split(platform, "/")
buildOptions.Platform = platformSplit[0]
buildOptions.Arch = defaultArch
if len(platformSplit) > 1 {
buildOptions.Arch = platformSplit[1]
}
banner := "Building target: " + buildOptions.Platform + "/" + buildOptions.Arch
logger.Println(banner)
logger.Println(strings.Repeat("-", len(banner)))
if compress && platform == "darwin/universal" {
logger.Println("Warning: compress flag unsupported for universal binaries. Ignoring.")
compress = false
}
switch buildOptions.Platform {
case "linux":
if runtime.GOOS != "linux" {
logger.Println("Crosscompiling to Linux not currently supported.\n")
return
}
case "darwin":
if runtime.GOOS != "darwin" {
logger.Println("Crosscompiling to Mac not currently supported.\n")
return
}
macTargets := targets.Filter(func(platform string) bool {
return strings.HasPrefix(platform, "darwin")
})
if macTargets.Length() == 2 {
buildOptions.BundleName = fmt.Sprintf("%s-%s.app", desiredFilename, buildOptions.Arch)
}
}
if targets.Length() > 1 {
// target filename
switch buildOptions.Platform {
case "windows":
desiredFilename = fmt.Sprintf("%s-%s", desiredFilename, buildOptions.Arch)
case "linux", "darwin":
desiredFilename = fmt.Sprintf("%s-%s-%s", desiredFilename, buildOptions.Platform, buildOptions.Arch)
}
}
if buildOptions.Platform == "windows" {
desiredFilename += ".exe"
}
buildOptions.OutputFile = desiredFilename
if outputFilename != "" {
buildOptions.OutputFile = outputFilename
}
if obfuscated && skipBindings {
logger.Println("Warning: obfuscated flag overrides skipbindings flag.")
buildOptions.SkipBindings = false
}
if !dryRun {
// Start Time
start := time.Now()
compiledBinary, err := build.Build(buildOptions)
if err != nil {
logger.Println("Error: %s", err.Error())
targetErr = err
return
}
buildOptions.IgnoreFrontend = true
buildOptions.CleanBinDirectory = false
// Output stats
buildOptions.Logger.Println(fmt.Sprintf("Built '%s' in %s.\n", compiledBinary, time.Since(start).Round(time.Millisecond).String()))
outputBinaries[buildOptions.Platform+"/"+buildOptions.Arch] = compiledBinary
} else {
logger.Println("Dry run: skipped build.")
}
})
if targetErr != nil {
return targetErr
}
if dryRun {
return nil
}
if nsis {
amd64Binary := outputBinaries["windows/amd64"]
arm64Binary := outputBinaries["windows/arm64"]
if amd64Binary == "" && arm64Binary == "" {
return fmt.Errorf("cannot build nsis installer - no windows targets")
}
if err := build.GenerateNSISInstaller(buildOptions, amd64Binary, arm64Binary); err != nil {
return err
}
}
return nil
})
}
func LogGreen(message string, args ...interface{}) {
text := fmt.Sprintf(message, args...)
println(colour.Green(text))
}

View File

@ -1,22 +0,0 @@
# Dev
The dev command allows you to develop your application through a standard browser.
## Usage
`wails dev <flags>`
### Flags
| Flag | Details | Default |
| :------------- | :----------- | :------ |
| -compiler path/to/compiler | Use a different go compiler, eg go1.15beta1 | go |
| -ldflags "custom ld flags" | Use given ldflags | |
| -e list,of,extensions | File extensions to trigger rebuilds | go |
| -w | Show warnings | false |
| -v int | Verbosity level (0 - silent, 1 - default, 2 - verbose) | 1 |
| -loglevel | Loglevel to pass to the application - Trace, Debug, Info, Warning, Error | Debug |
## How it works
The project is built using a special mode that starts a webserver and starts listening to port 34115. When the frontend project is run independently, so long as the JS is wrapped with the runtime method `ready`, then the frontend will connect to the backend code via websockets. The interface should be present in your browser, and you should be able to interact with the backend as you would in a desktop app.

View File

@ -1,694 +0,0 @@
package dev
import (
"context"
"errors"
"fmt"
"io"
"net"
"net/http"
"net/url"
"os"
"os/exec"
"os/signal"
"path"
"path/filepath"
"runtime"
"strings"
"sync"
"sync/atomic"
"syscall"
"time"
"github.com/wailsapp/wails/v2/pkg/commands/bindings"
"github.com/wailsapp/wails/v2/pkg/commands/buildtags"
"github.com/google/shlex"
buildcmd "github.com/wailsapp/wails/v2/cmd/wails/internal/commands/build"
"github.com/wailsapp/wails/v2/internal/project"
"github.com/pkg/browser"
"github.com/wailsapp/wails/v2/internal/colour"
"github.com/fsnotify/fsnotify"
"github.com/leaanthony/clir"
"github.com/wailsapp/wails/v2/cmd/wails/internal/logutils"
"github.com/wailsapp/wails/v2/internal/fs"
"github.com/wailsapp/wails/v2/internal/process"
"github.com/wailsapp/wails/v2/pkg/clilogger"
"github.com/wailsapp/wails/v2/pkg/commands/build"
)
func sliceToMap(input []string) map[string]struct{} {
result := map[string]struct{}{}
for _, value := range input {
result[value] = struct{}{}
}
return result
}
type devFlags struct {
ldflags string
compilerCommand string
assetDir string
extensions string
reloadDirs string
openBrowser bool
noReload bool
skipBindings bool
wailsjsdir string
tags string
verbosity int
loglevel string
forceBuild bool
debounceMS int
devServer string
appargs string
saveConfig bool
raceDetector bool
frontendDevServerURL string
skipFrontend bool
noColour bool
}
// AddSubcommand adds the `dev` command for the Wails application
func AddSubcommand(app *clir.Cli, w io.Writer) error {
command := app.NewSubCommand("dev", "Development mode")
flags := defaultDevFlags()
command.StringFlag("ldflags", "optional ldflags", &flags.ldflags)
command.StringFlag("compiler", "Use a different go compiler to build, eg go1.15beta1", &flags.compilerCommand)
command.StringFlag("assetdir", "Serve assets from the given directory instead of using the provided asset FS", &flags.assetDir)
command.StringFlag("e", "Extensions to trigger rebuilds (comma separated) eg go", &flags.extensions)
command.StringFlag("reloaddirs", "Additional directories to trigger reloads (comma separated)", &flags.reloadDirs)
command.BoolFlag("browser", "Open application in browser", &flags.openBrowser)
command.BoolFlag("noreload", "Disable reload on asset change", &flags.noReload)
command.BoolFlag("nocolour", "Turn off colour cli output", &flags.noColour)
command.BoolFlag("skipbindings", "Skip bindings generation", &flags.skipBindings)
command.StringFlag("wailsjsdir", "Directory to generate the Wails JS modules", &flags.wailsjsdir)
command.StringFlag("tags", "Build tags to pass to Go compiler. Must be quoted. Space or comma (but not both) separated", &flags.tags)
command.IntFlag("v", "Verbosity level (0 - silent, 1 - standard, 2 - verbose)", &flags.verbosity)
command.StringFlag("loglevel", "Loglevel to use - Trace, Debug, Info, Warning, Error", &flags.loglevel)
command.BoolFlag("f", "Force build application", &flags.forceBuild)
command.IntFlag("debounce", "The amount of time to wait to trigger a reload on change", &flags.debounceMS)
command.StringFlag("devserver", "The address of the wails dev server", &flags.devServer)
command.StringFlag("frontenddevserverurl", "The url of the external frontend dev server to use", &flags.frontendDevServerURL)
command.StringFlag("appargs", "arguments to pass to the underlying app (quoted and space separated)", &flags.appargs)
command.BoolFlag("save", "Save given flags as defaults", &flags.saveConfig)
command.BoolFlag("race", "Build with Go's race detector", &flags.raceDetector)
command.BoolFlag("s", "Skips building the frontend", &flags.skipFrontend)
command.Action(func() error {
if flags.noColour {
colour.ColourEnabled = false
}
// Create logger
logger := clilogger.New(w)
app.PrintBanner()
cwd, err := os.Getwd()
if err != nil {
return err
}
projectConfig, err := loadAndMergeProjectConfig(cwd, &flags)
if err != nil {
return err
}
devServer := flags.devServer
if _, _, err := net.SplitHostPort(devServer); err != nil {
return fmt.Errorf("DevServer is not of the form 'host:port', please check your wails.json")
}
devServerURL, err := url.Parse("http://" + devServer)
if err != nil {
return err
}
// Update go.mod to use current wails version
err = buildcmd.SyncGoMod(logger, true)
if err != nil {
return err
}
// Run go mod tidy to ensure we're up-to-date
err = runCommand(cwd, false, "go", "mod", "tidy")
if err != nil {
return err
}
buildOptions := generateBuildOptions(flags)
buildOptions.ProjectData = projectConfig
buildOptions.SkipBindings = flags.skipBindings
buildOptions.Logger = logger
userTags, err := buildtags.Parse(flags.tags)
if err != nil {
return err
}
buildOptions.UserTags = userTags
err = build.CreateEmbedDirectories(cwd, buildOptions)
if err != nil {
return err
}
if !buildOptions.SkipBindings {
if flags.verbosity == build.VERBOSE {
logutils.LogGreen("Generating Bindings...")
}
stdout, err := bindings.GenerateBindings(bindings.Options{
Tags: buildOptions.UserTags,
TsPrefix: projectConfig.Bindings.TsGeneration.Prefix,
TsSuffix: projectConfig.Bindings.TsGeneration.Suffix,
})
if err != nil {
return err
}
if flags.verbosity == build.VERBOSE {
logutils.LogGreen(stdout)
}
}
// Setup signal handler
quitChannel := make(chan os.Signal, 1)
signal.Notify(quitChannel, os.Interrupt, os.Kill, syscall.SIGTERM)
exitCodeChannel := make(chan int, 1)
// Build the frontend if requested, but ignore building the application itself.
ignoreFrontend := buildOptions.IgnoreFrontend
if !ignoreFrontend {
buildOptions.IgnoreApplication = true
if _, err := build.Build(buildOptions); err != nil {
return err
}
buildOptions.IgnoreApplication = false
}
// frontend:dev:watcher command.
frontendDevAutoDiscovery := projectConfig.IsFrontendDevServerURLAutoDiscovery()
if command := projectConfig.DevWatcherCommand; command != "" {
closer, devServerURL, err := runFrontendDevWatcherCommand(projectConfig.GetFrontendDir(), command, frontendDevAutoDiscovery)
if err != nil {
return err
}
if devServerURL != "" {
projectConfig.FrontendDevServerURL = devServerURL
flags.frontendDevServerURL = devServerURL
}
defer closer()
} else if frontendDevAutoDiscovery {
return fmt.Errorf("Unable to auto discover frontend:dev:serverUrl without a frontend:dev:watcher command, please either set frontend:dev:watcher or remove the auto discovery from frontend:dev:serverUrl")
}
// Do initial build but only for the application.
logger.Println("Building application for development...")
buildOptions.IgnoreFrontend = true
debugBinaryProcess, appBinary, err := restartApp(buildOptions, nil, flags, exitCodeChannel)
buildOptions.IgnoreFrontend = ignoreFrontend || flags.frontendDevServerURL != ""
if err != nil {
return err
}
defer func() {
if err := killProcessAndCleanupBinary(debugBinaryProcess, appBinary); err != nil {
logutils.LogDarkYellow("Unable to kill process and cleanup binary: %s", err)
}
}()
// open browser
if flags.openBrowser {
err = browser.OpenURL(devServerURL.String())
if err != nil {
return err
}
}
// create the project files watcher
watcher, err := initialiseWatcher(cwd)
if err != nil {
return err
}
defer func(watcher *fsnotify.Watcher) {
err := watcher.Close()
if err != nil {
logger.Fatal(err.Error())
}
}(watcher)
logutils.LogGreen("Watching (sub)/directory: %s", cwd)
logutils.LogGreen("Using DevServer URL: %s", devServerURL)
if flags.frontendDevServerURL != "" {
logutils.LogGreen("Using Frontend DevServer URL: %s", flags.frontendDevServerURL)
}
logutils.LogGreen("Using reload debounce setting of %d milliseconds", flags.debounceMS)
// Show dev server URL in terminal after 3 seconds
go func() {
time.Sleep(3 * time.Second)
logutils.LogGreen("\n\nTo develop in the browser and call your bound Go methods from Javascript, navigate to: %s", devServerURL)
}()
// Watch for changes and trigger restartApp()
debugBinaryProcess = doWatcherLoop(buildOptions, debugBinaryProcess, flags, watcher, exitCodeChannel, quitChannel, devServerURL)
// Kill the current program if running and remove dev binary
if err := killProcessAndCleanupBinary(debugBinaryProcess, appBinary); err != nil {
return err
}
// Reset the process and the binary so defer knows about it and is a nop.
debugBinaryProcess = nil
appBinary = ""
logutils.LogGreen("Development mode exited")
return nil
})
return nil
}
func killProcessAndCleanupBinary(process *process.Process, binary string) error {
if process != nil && process.Running {
if err := process.Kill(); err != nil {
return err
}
}
if binary != "" {
err := os.Remove(binary)
if err != nil && !errors.Is(err, os.ErrNotExist) {
return err
}
}
return nil
}
func runCommand(dir string, exitOnError bool, command string, args ...string) error {
logutils.LogGreen("Executing: " + command + " " + strings.Join(args, " "))
cmd := exec.Command(command, args...)
cmd.Dir = dir
output, err := cmd.CombinedOutput()
if err != nil {
println(string(output))
println(err.Error())
if exitOnError {
os.Exit(1)
}
return err
}
return nil
}
// defaultDevFlags generates devFlags with default options
func defaultDevFlags() devFlags {
return devFlags{
compilerCommand: "go",
verbosity: 1,
extensions: "go",
debounceMS: 100,
}
}
// generateBuildOptions creates a build.Options using the flags
func generateBuildOptions(flags devFlags) *build.Options {
result := &build.Options{
OutputType: "dev",
Mode: build.Dev,
Arch: runtime.GOARCH,
Pack: true,
Platform: runtime.GOOS,
LDFlags: flags.ldflags,
Compiler: flags.compilerCommand,
ForceBuild: flags.forceBuild,
IgnoreFrontend: flags.skipFrontend,
Verbosity: flags.verbosity,
WailsJSDir: flags.wailsjsdir,
RaceDetector: flags.raceDetector,
}
return result
}
// loadAndMergeProjectConfig reconciles flags passed to the CLI with project config settings and updates
// the project config if necessary
func loadAndMergeProjectConfig(projectFileDirectory string, flags *devFlags) (*project.Project, error) {
projectConfig, err := project.Load(projectFileDirectory)
if err != nil {
return nil, err
}
if flags.assetDir == "" && projectConfig.AssetDirectory != "" {
flags.assetDir = projectConfig.AssetDirectory
}
if flags.assetDir != projectConfig.AssetDirectory {
projectConfig.AssetDirectory = filepath.ToSlash(flags.assetDir)
}
if flags.assetDir != "" {
flags.assetDir, err = filepath.Abs(flags.assetDir)
if err != nil {
return nil, err
}
}
if flags.reloadDirs == "" && projectConfig.ReloadDirectories != "" {
flags.reloadDirs = projectConfig.ReloadDirectories
}
if flags.reloadDirs != projectConfig.ReloadDirectories {
projectConfig.ReloadDirectories = filepath.ToSlash(flags.reloadDirs)
}
if flags.devServer == "" && projectConfig.DevServer != "" {
flags.devServer = projectConfig.DevServer
}
if flags.frontendDevServerURL == "" && projectConfig.FrontendDevServerURL != "" {
flags.frontendDevServerURL = projectConfig.FrontendDevServerURL
}
if flags.wailsjsdir == "" && projectConfig.WailsJSDir != "" {
flags.wailsjsdir = projectConfig.GetWailsJSDir()
}
if flags.wailsjsdir == "" {
flags.wailsjsdir = projectConfig.GetFrontendDir()
}
if flags.wailsjsdir != projectConfig.WailsJSDir {
projectConfig.WailsJSDir = filepath.ToSlash(flags.wailsjsdir)
}
if flags.debounceMS == 100 && projectConfig.DebounceMS != 100 {
if projectConfig.DebounceMS == 0 {
projectConfig.DebounceMS = 100
}
flags.debounceMS = projectConfig.DebounceMS
}
if flags.debounceMS != projectConfig.DebounceMS {
projectConfig.DebounceMS = flags.debounceMS
}
if flags.appargs == "" && projectConfig.AppArgs != "" {
flags.appargs = projectConfig.AppArgs
}
if flags.saveConfig {
err = projectConfig.Save()
if err != nil {
return nil, err
}
}
return projectConfig, nil
}
// runFrontendDevWatcherCommand will run the `frontend:dev:watcher` command if it was given, ex- `npm run dev`
func runFrontendDevWatcherCommand(frontendDirectory string, devCommand string, discoverViteServerURL bool) (func(), string, error) {
ctx, cancel := context.WithCancel(context.Background())
scanner := NewStdoutScanner()
cmdSlice := strings.Split(devCommand, " ")
cmd := exec.CommandContext(ctx, cmdSlice[0], cmdSlice[1:]...)
cmd.Stderr = os.Stderr
cmd.Stdout = scanner
cmd.Dir = frontendDirectory
setParentGID(cmd)
if err := cmd.Start(); err != nil {
cancel()
return nil, "", fmt.Errorf("unable to start frontend DevWatcher: %w", err)
}
var viteServerURL string
if discoverViteServerURL {
select {
case serverURL := <-scanner.ViteServerURLChan:
viteServerURL = serverURL
case <-time.After(time.Second * 10):
cancel()
return nil, "", errors.New("failed to find Vite server URL")
}
}
logutils.LogGreen("Running frontend DevWatcher command: '%s'", devCommand)
var wg sync.WaitGroup
wg.Add(1)
const (
stateRunning int32 = 0
stateCanceling = 1
stateStopped = 2
)
state := stateRunning
go func() {
if err := cmd.Wait(); err != nil {
wasRunning := atomic.CompareAndSwapInt32(&state, stateRunning, stateStopped)
if err.Error() != "exit status 1" && wasRunning {
logutils.LogRed("Error from DevWatcher '%s': %s", devCommand, err.Error())
}
}
atomic.StoreInt32(&state, stateStopped)
wg.Done()
}()
return func() {
if atomic.CompareAndSwapInt32(&state, stateRunning, stateCanceling) {
killProc(cmd, devCommand)
}
cancel()
wg.Wait()
}, viteServerURL, nil
}
// restartApp does the actual rebuilding of the application when files change
func restartApp(buildOptions *build.Options, debugBinaryProcess *process.Process, flags devFlags, exitCodeChannel chan int) (*process.Process, string, error) {
appBinary, err := build.Build(buildOptions)
println()
if err != nil {
logutils.LogRed("Build error - " + err.Error())
msg := "Continuing to run current version"
if debugBinaryProcess == nil {
msg = "No version running, build will be retriggered as soon as changes have been detected"
}
logutils.LogDarkYellow(msg)
return nil, "", nil
}
// Kill existing binary if need be
if debugBinaryProcess != nil {
killError := debugBinaryProcess.Kill()
if killError != nil {
buildOptions.Logger.Fatal("Unable to kill debug binary (PID: %d)!", debugBinaryProcess.PID())
}
debugBinaryProcess = nil
}
// parse appargs if any
args, err := shlex.Split(flags.appargs)
if err != nil {
buildOptions.Logger.Fatal("Unable to parse appargs: %s", err.Error())
}
// Set environment variables accordingly
os.Setenv("loglevel", flags.loglevel)
os.Setenv("assetdir", flags.assetDir)
os.Setenv("devserver", flags.devServer)
os.Setenv("frontenddevserverurl", flags.frontendDevServerURL)
// Start up new binary with correct args
newProcess := process.NewProcess(appBinary, args...)
err = newProcess.Start(exitCodeChannel)
if err != nil {
// Remove binary
if fs.FileExists(appBinary) {
deleteError := fs.DeleteFile(appBinary)
if deleteError != nil {
buildOptions.Logger.Fatal("Unable to delete app binary: " + appBinary)
}
}
buildOptions.Logger.Fatal("Unable to start application: %s", err.Error())
}
return newProcess, appBinary, nil
}
// doWatcherLoop is the main watch loop that runs while dev is active
func doWatcherLoop(buildOptions *build.Options, debugBinaryProcess *process.Process, flags devFlags, watcher *fsnotify.Watcher, exitCodeChannel chan int, quitChannel chan os.Signal, devServerURL *url.URL) *process.Process {
// Main Loop
var extensionsThatTriggerARebuild = sliceToMap(strings.Split(flags.extensions, ","))
var dirsThatTriggerAReload []string
for _, dir := range strings.Split(flags.reloadDirs, ",") {
if dir == "" {
continue
}
thePath, err := filepath.Abs(dir)
if err != nil {
logutils.LogRed("Unable to expand reloadDir '%s': %s", dir, err)
continue
}
dirsThatTriggerAReload = append(dirsThatTriggerAReload, thePath)
}
quit := false
interval := time.Duration(flags.debounceMS) * time.Millisecond
timer := time.NewTimer(interval)
rebuild := false
reload := false
assetDir := ""
changedPaths := map[string]struct{}{}
// If we are using an external dev server, the reloading of the frontend part can be skipped or if the user requested it
skipAssetsReload := (flags.frontendDevServerURL != "" || flags.noReload)
assetDirURL := joinPath(devServerURL, "/wails/assetdir")
reloadURL := joinPath(devServerURL, "/wails/reload")
for quit == false {
// reload := false
select {
case exitCode := <-exitCodeChannel:
if exitCode == 0 {
quit = true
}
case err := <-watcher.Errors:
logutils.LogDarkYellow(err.Error())
case item := <-watcher.Events:
isEligibleFile := func(fileName string) bool {
// Iterate all file patterns
ext := filepath.Ext(fileName)
if ext != "" {
ext = ext[1:]
if _, exists := extensionsThatTriggerARebuild[ext]; exists {
return true
}
}
return false
}
// Handle write operations
if item.Op&fsnotify.Write == fsnotify.Write {
// Ignore directories
itemName := item.Name
if fs.DirExists(itemName) {
continue
}
if isEligibleFile(itemName) {
rebuild = true
timer.Reset(interval)
continue
}
for _, reloadDir := range dirsThatTriggerAReload {
if strings.HasPrefix(itemName, reloadDir) {
reload = true
break
}
}
if !reload {
changedPaths[filepath.Dir(itemName)] = struct{}{}
}
timer.Reset(interval)
}
// Handle new fs entries that are created
if item.Op&fsnotify.Create == fsnotify.Create {
// If this is a folder, add it to our watch list
if fs.DirExists(item.Name) {
// node_modules is BANNED!
if !strings.Contains(item.Name, "node_modules") {
err := watcher.Add(item.Name)
if err != nil {
buildOptions.Logger.Fatal("%s", err.Error())
}
logutils.LogGreen("Added new directory to watcher: %s", item.Name)
}
} else if isEligibleFile(item.Name) {
// Handle creation of new file.
// Note: On some platforms an update to a file is represented as
// REMOVE -> CREATE instead of WRITE, so this is not only new files
// but also updates to existing files
rebuild = true
timer.Reset(interval)
continue
}
}
case <-timer.C:
if rebuild {
rebuild = false
logutils.LogGreen("[Rebuild triggered] files updated")
// Try and build the app
newBinaryProcess, _, err := restartApp(buildOptions, debugBinaryProcess, flags, exitCodeChannel)
if err != nil {
logutils.LogRed("Error during build: %s", err.Error())
continue
}
// If we have a new process, saveConfig it
if newBinaryProcess != nil {
debugBinaryProcess = newBinaryProcess
}
}
if !skipAssetsReload && len(changedPaths) != 0 {
if assetDir == "" {
resp, err := http.Get(assetDirURL)
if err != nil {
logutils.LogRed("Error during retrieving assetdir: %s", err.Error())
} else {
content, err := io.ReadAll(resp.Body)
if err != nil {
logutils.LogRed("Error reading assetdir from devserver: %s", err.Error())
} else {
assetDir = string(content)
}
resp.Body.Close()
}
}
if assetDir != "" {
for thePath := range changedPaths {
if strings.HasPrefix(thePath, assetDir) {
reload = true
break
}
}
} else if len(dirsThatTriggerAReload) == 0 {
logutils.LogRed("Reloading couldn't be triggered: Please specify -assetdir or -reloaddirs")
}
}
if reload {
reload = false
_, err := http.Get(reloadURL)
if err != nil {
logutils.LogRed("Error during refresh: %s", err.Error())
}
}
changedPaths = map[string]struct{}{}
case <-quitChannel:
logutils.LogGreen("\nCaught quit")
quit = true
}
}
return debugBinaryProcess
}
func joinPath(url *url.URL, subPath string) string {
u := *url
u.Path = path.Join(u.Path, subPath)
return u.String()
}

View File

@ -1,174 +0,0 @@
package doctor
import (
"fmt"
"io"
"os"
"runtime"
"runtime/debug"
"strings"
"text/tabwriter"
"github.com/leaanthony/clir"
"github.com/wailsapp/wails/v2/internal/system"
"github.com/wailsapp/wails/v2/internal/system/packagemanager"
"github.com/wailsapp/wails/v2/pkg/clilogger"
)
// AddSubcommand adds the `doctor` command for the Wails application
func AddSubcommand(app *clir.Cli, w io.Writer) error {
command := app.NewSubCommand("doctor", "Diagnose your environment")
command.Action(func() error {
logger := clilogger.New(w)
app.PrintBanner()
logger.Print("Scanning system - Please wait (this may take a long time)...")
// Get system info
info, err := system.GetInfo()
if err != nil {
logger.Println("Failed.")
return err
}
logger.Println("Done.")
logger.Println("")
// Start a new tabwriter
w := new(tabwriter.Writer)
w.Init(os.Stdout, 8, 8, 0, '\t', 0)
// Write out the system information
fmt.Fprintf(w, "System\n")
fmt.Fprintf(w, "------\n")
fmt.Fprintf(w, "%s\t%s\n", "OS:", info.OS.Name)
fmt.Fprintf(w, "%s\t%s\n", "Version: ", info.OS.Version)
fmt.Fprintf(w, "%s\t%s\n", "ID:", info.OS.ID)
// Output Go Information
fmt.Fprintf(w, "%s\t%s\n", "Go Version:", runtime.Version())
fmt.Fprintf(w, "%s\t%s\n", "Platform:", runtime.GOOS)
fmt.Fprintf(w, "%s\t%s\n", "Architecture:", runtime.GOARCH)
// Write out the wails information
fmt.Fprintf(w, "\n")
fmt.Fprintf(w, "Wails\n")
fmt.Fprintf(w, "------\n")
fmt.Fprintf(w, "%s\t%s\n", "Version: ", app.Version())
printBuildSettings(w)
// Exit early if PM not found
if info.PM != nil {
fmt.Fprintf(w, "%s\t%s\n", "Package Manager: ", info.PM.Name())
}
// Output Dependencies Status
var dependenciesMissing = []string{}
var externalPackages = []*packagemanager.Dependency{}
var dependenciesAvailableRequired = 0
var dependenciesAvailableOptional = 0
fmt.Fprintf(w, "\n")
fmt.Fprintf(w, "Dependency\tPackage Name\tStatus\tVersion\n")
fmt.Fprintf(w, "----------\t------------\t------\t-------\n")
hasOptionalDependencies := false
// Loop over dependencies
for _, dependency := range info.Dependencies {
name := dependency.Name
if dependency.Optional {
name = "*" + name
hasOptionalDependencies = true
}
packageName := "Unknown"
status := "Not Found"
// If we found the package
if dependency.PackageName != "" {
packageName = dependency.PackageName
// If it's installed, update the status
if dependency.Installed {
status = "Installed"
} else {
// Generate meaningful status text
status = "Available"
if dependency.Optional {
dependenciesAvailableOptional++
} else {
dependenciesAvailableRequired++
}
}
} else {
if !dependency.Optional {
dependenciesMissing = append(dependenciesMissing, dependency.Name)
}
if dependency.External {
externalPackages = append(externalPackages, dependency)
}
}
fmt.Fprintf(w, "%s \t%s \t%s \t%s\n", name, packageName, status, dependency.Version)
}
if hasOptionalDependencies {
fmt.Fprintf(w, "\n")
fmt.Fprintf(w, "* - Optional Dependency\n")
}
w.Flush()
logger.Println("")
logger.Println("Diagnosis")
logger.Println("---------")
// Generate an appropriate diagnosis
if len(dependenciesMissing) == 0 && dependenciesAvailableRequired == 0 {
logger.Println("Your system is ready for Wails development!")
} else {
logger.Println("Your system has missing dependencies!\n")
}
if dependenciesAvailableRequired != 0 {
logger.Println("Required package(s) installation details: \n" + info.Dependencies.InstallAllRequiredCommand())
}
if dependenciesAvailableOptional != 0 {
logger.Println("Optional package(s) installation details: \n" + info.Dependencies.InstallAllOptionalCommand())
}
if len(dependenciesMissing) != 0 {
logger.Println("Fatal:")
logger.Println("Required dependencies missing: " + strings.Join(dependenciesMissing, " "))
logger.Println("Please read this article on how to resolve this: https://wails.io/guides/resolving-missing-packages")
}
logger.Println("")
return nil
})
return nil
}
func printBuildSettings(w *tabwriter.Writer) {
if buildInfo, _ := debug.ReadBuildInfo(); buildInfo != nil {
buildSettingToName := map[string]string{
"vcs.revision": "Revision",
"vcs.modified": "Modified",
}
for _, buildSetting := range buildInfo.Settings {
name := buildSettingToName[buildSetting.Key]
if name == "" {
continue
}
_, _ = fmt.Fprintf(w, "%s:\t%s\n", name, buildSetting.Value)
}
}
}

View File

@ -1,25 +0,0 @@
# Generate
The `generate` command provides the ability to generate various Wails related components.
## Usage
`wails generate [subcommand] [options]`
## Template
`wails generate template -name <name> [-frontend] [-q]`
Generate a starter template for you to customise.
| Flag | Details |
| :------------- | :----------- |
| -frontend | Copies all the files from the current directory into the template's `frontend` directory. Useful for converting frontend projects created by boilerplate generators. |
| -q | Suppress output |
## Module
`wails generate module [-h]`
Generate TS module for your frontend

View File

@ -1,23 +0,0 @@
package generate
import (
"github.com/wailsapp/wails/v2/cmd/wails/internal/commands/generate/template"
"io"
"github.com/leaanthony/clir"
)
// AddSubcommand adds the `generate` command for the Wails application
func AddSubcommand(app *clir.Cli, w io.Writer) error {
command := app.NewSubCommand("generate", "Code Generation Tools")
err := AddModuleCommand(app, command, w)
if err != nil {
return err
}
template.AddSubCommand(app, command, w)
return nil
}

View File

@ -1,51 +0,0 @@
package generate
import (
"io"
"os"
"github.com/leaanthony/clir"
"github.com/wailsapp/wails/v2/internal/project"
"github.com/wailsapp/wails/v2/pkg/commands/bindings"
"github.com/wailsapp/wails/v2/pkg/commands/buildtags"
)
type generateFlags struct {
tags string
}
// AddModuleCommand adds the `module` subcommand for the `generate` command
func AddModuleCommand(app *clir.Cli, parent *clir.Command, w io.Writer) error {
command := parent.NewSubCommand("module", "Generate wailsjs modules")
genFlags := generateFlags{}
command.StringFlag("tags", "tags to pass to Go compiler (quoted and space separated)", &genFlags.tags)
command.Action(func() error {
cwd, err := os.Getwd()
if err != nil {
return err
}
projectConfig, err := project.Load(cwd)
if err != nil {
return err
}
buildTags, err := buildtags.Parse(genFlags.tags)
if err != nil {
return err
}
_, err = bindings.GenerateBindings(bindings.Options{
Tags: buildTags,
TsPrefix: projectConfig.Bindings.TsGeneration.Prefix,
TsSuffix: projectConfig.Bindings.TsGeneration.Suffix,
})
if err != nil {
return err
}
return nil
})
return nil
}

View File

@ -1,214 +0,0 @@
package template
import (
"embed"
"fmt"
"io"
"os"
"path/filepath"
"github.com/leaanthony/debme"
"github.com/leaanthony/gosod"
"github.com/wailsapp/wails/v2/internal/fs"
"github.com/leaanthony/clir"
"github.com/tidwall/sjson"
)
//go:embed base
var base embed.FS
func AddSubCommand(app *clir.Cli, parent *clir.Command, w io.Writer) {
// command
command := parent.NewSubCommand("template", "Generates a wails template")
name := ""
command.StringFlag("name", "The name of the template", &name)
existingProjectDir := ""
command.StringFlag("frontend", "A path to an existing frontend project to include in the template", &existingProjectDir)
// Quiet Init
quiet := false
command.BoolFlag("q", "Suppress output to console", &quiet)
command.Action(func() error {
// name is mandatory
if name == "" {
command.PrintHelp()
return fmt.Errorf("no template name given")
}
// If the current directory is not empty, we create a new directory
cwd, err := os.Getwd()
if err != nil {
return err
}
templateDir := filepath.Join(cwd, name)
if !fs.DirExists(templateDir) {
err := os.MkdirAll(templateDir, 0755)
if err != nil {
return err
}
}
empty, err := fs.DirIsEmpty(templateDir)
if err != nil {
return err
}
if !empty {
templateDir = filepath.Join(cwd, name)
println("Creating new template directory:", name)
err = fs.Mkdir(templateDir)
if err != nil {
return err
}
}
// Create base template
baseTemplate, err := debme.FS(base, "base")
if err != nil {
return err
}
g := gosod.New(baseTemplate)
g.SetTemplateFilters([]string{".template"})
err = os.Chdir(templateDir)
if err != nil {
return err
}
type templateData struct {
Name string
Description string
TemplateDir string
WailsVersion string
}
println("Extracting base template files...")
err = g.Extract(templateDir, &templateData{
Name: name,
TemplateDir: templateDir,
WailsVersion: app.Version(),
})
if err != nil {
return err
}
err = os.Chdir(cwd)
if err != nil {
return err
}
// If we aren't migrating the files, just exit
if existingProjectDir == "" {
return nil
}
// Remove frontend directory
frontendDir := filepath.Join(templateDir, "frontend")
err = os.RemoveAll(frontendDir)
if err != nil {
return err
}
// Copy the files into a new frontend directory
println("Migrating existing project files to frontend directory...")
sourceDir, err := filepath.Abs(existingProjectDir)
if err != nil {
return err
}
newFrontendDir := filepath.Join(templateDir, "frontend")
err = fs.CopyDirExtended(sourceDir, newFrontendDir, []string{name, "node_modules"})
if err != nil {
return err
}
// Process package.json
err = processPackageJSON(frontendDir)
if err != nil {
return err
}
// Process package-lock.json
err = processPackageLockJSON(frontendDir)
if err != nil {
return err
}
// Remove node_modules - ignore error, eg it doesn't exist
_ = os.RemoveAll(filepath.Join(frontendDir, "node_modules"))
return nil
})
}
func processPackageJSON(frontendDir string) error {
var err error
packageJSON := filepath.Join(frontendDir, "package.json")
if !fs.FileExists(packageJSON) {
println("No package.json found - cannot process.")
return nil
}
json, err := os.ReadFile(packageJSON)
if err != nil {
return err
}
// We will ignore these errors - it's not critical
println("Updating package.json data...")
json, _ = sjson.SetBytes(json, "name", "{{.ProjectName}}")
json, _ = sjson.SetBytes(json, "author", "{{.AuthorName}}")
err = os.WriteFile(packageJSON, json, 0644)
if err != nil {
return err
}
baseDir := filepath.Dir(packageJSON)
println("Renaming package.json -> package.tmpl.json...")
err = os.Rename(packageJSON, filepath.Join(baseDir, "package.tmpl.json"))
if err != nil {
return err
}
return nil
}
func processPackageLockJSON(frontendDir string) error {
var err error
filename := filepath.Join(frontendDir, "package-lock.json")
if !fs.FileExists(filename) {
println("No package-lock.json found - cannot process.")
return nil
}
data, err := os.ReadFile(filename)
if err != nil {
return err
}
json := string(data)
// We will ignore these errors - it's not critical
println("Updating package-lock.json data...")
json, _ = sjson.Set(json, "name", "{{.ProjectName}}")
err = os.WriteFile(filename, []byte(json), 0644)
if err != nil {
return err
}
baseDir := filepath.Dir(filename)
println("Renaming package-lock.json -> package-lock.tmpl.json...")
err = os.Rename(filename, filepath.Join(baseDir, "package-lock.tmpl.json"))
if err != nil {
return err
}
return nil
}

View File

@ -1,310 +0,0 @@
package initialise
import (
"bufio"
"fmt"
"io"
"log"
"os"
"os/exec"
"path/filepath"
"strings"
"time"
"github.com/flytam/filenamify"
"github.com/leaanthony/slicer"
"github.com/wailsapp/wails/v2/pkg/buildassets"
"github.com/wailsapp/wails/v2/pkg/templates"
"github.com/leaanthony/clir"
"github.com/pkg/errors"
"github.com/wailsapp/wails/v2/pkg/clilogger"
"github.com/wailsapp/wails/v2/pkg/git"
)
// AddSubcommand adds the `init` command for the Wails application
func AddSubcommand(app *clir.Cli, w io.Writer) error {
command := app.NewSubCommand("init", "Initialise a new Wails project")
// Setup template name flag
templateName := "vanilla"
description := "Name of built-in template to use, path to template or template url."
command.StringFlag("t", description, &templateName)
// Setup project name
projectName := ""
command.StringFlag("n", "Name of project", &projectName)
// For CI
ciMode := false
command.BoolFlag("ci", "CI Mode", &ciMode)
// Setup project directory
projectDirectory := ""
command.StringFlag("d", "Project directory", &projectDirectory)
// Quiet Init
quiet := false
command.BoolFlag("q", "Suppress output to console", &quiet)
initGit := false
gitInstalled := git.IsInstalled()
if gitInstalled {
// Git Init
command.BoolFlag("g", "Initialise git repository", &initGit)
}
// VSCode project files
ide := ""
command.StringFlag("ide", "Generate IDE project files", &ide)
// List templates
list := false
command.BoolFlag("l", "List templates", &list)
command.Action(func() error {
// Create logger
logger := clilogger.New(w)
logger.Mute(quiet)
// Are we listing templates?
if list {
app.PrintBanner()
err := templates.OutputList(logger)
logger.Println("")
return err
}
// Validate name
if len(projectName) == 0 {
logger.Println("ERROR: Project name required")
logger.Println("")
command.PrintHelp()
return nil
}
// Validate IDE option
supportedIDEs := slicer.String([]string{"vscode", "goland"})
ide = strings.ToLower(ide)
if ide != "" {
if !supportedIDEs.Contains(ide) {
return fmt.Errorf("ide '%s' not supported. Valid values: %s", ide, supportedIDEs.Join(" "))
}
}
if !quiet {
app.PrintBanner()
}
task := fmt.Sprintf("Initialising Project '%s'", projectName)
logger.Println(task)
logger.Println(strings.Repeat("-", len(task)))
projectFilename, err := filenamify.Filenamify(projectName, filenamify.Options{
Replacement: "_",
MaxLength: 255,
})
if err != nil {
return err
}
goBinary, err := exec.LookPath("go")
if err != nil {
return fmt.Errorf("unable to find Go compiler. Please download and install Go: https://golang.org/dl/")
}
// Get base path and convert to forward slashes
goPath := filepath.ToSlash(filepath.Dir(goBinary))
// Trim bin directory
goSDKPath := strings.TrimSuffix(goPath, "/bin")
// Create Template Options
options := &templates.Options{
ProjectName: projectName,
TargetDir: projectDirectory,
TemplateName: templateName,
Logger: logger,
IDE: ide,
InitGit: initGit,
ProjectNameFilename: projectFilename,
WailsVersion: app.Version(),
GoSDKPath: goSDKPath,
}
// Try to discover author details from git config
findAuthorDetails(options)
return initProject(options, quiet, ciMode)
})
return nil
}
// initProject is our main init command
func initProject(options *templates.Options, quiet bool, ciMode bool) error {
// Start Time
start := time.Now()
// Install the template
remote, template, err := templates.Install(options)
if err != nil {
return err
}
// Install the default assets
err = buildassets.Install(options.TargetDir)
if err != nil {
return err
}
err = os.Chdir(options.TargetDir)
if err != nil {
return err
}
if !ciMode {
// Run `go mod tidy` to ensure `go.sum` is up to date
cmd := exec.Command("go", "mod", "tidy")
cmd.Dir = options.TargetDir
cmd.Stderr = os.Stderr
if !quiet {
println("")
cmd.Stdout = os.Stdout
}
err = cmd.Run()
if err != nil {
return err
}
} else {
// Update go mod
workspace := os.Getenv("GITHUB_WORKSPACE")
println("GitHub workspace:", workspace)
if workspace == "" {
os.Exit(1)
}
updateReplaceLine(workspace)
}
// Remove the `.git`` directory in the template project
err = os.RemoveAll(".git")
if err != nil {
return err
}
if options.InitGit {
err = initGit(options)
if err != nil {
return err
}
}
if quiet {
return nil
}
// Output stats
elapsed := time.Since(start)
options.Logger.Println("Project Name: " + options.ProjectName)
options.Logger.Println("Project Directory: " + options.TargetDir)
options.Logger.Println("Project Template: " + options.TemplateName)
options.Logger.Println("Template Support: " + template.HelpURL)
// IDE message
switch options.IDE {
case "vscode":
options.Logger.Println("VSCode config files generated.")
case "goland":
options.Logger.Println("Goland config files generated.")
}
if options.InitGit {
options.Logger.Println("Git repository initialised.")
}
if remote {
options.Logger.Println("\nNOTE: You have created a project using a remote template. The Wails project takes no responsibility for 3rd party templates. Only use remote templates that you trust.")
}
options.Logger.Println("")
options.Logger.Println(fmt.Sprintf("Initialised project '%s' in %s.", options.ProjectName, elapsed.Round(time.Millisecond).String()))
options.Logger.Println("")
return nil
}
func initGit(options *templates.Options) error {
err := git.InitRepo(options.TargetDir)
if err != nil {
return errors.Wrap(err, "Unable to initialise git repository:")
}
ignore := []string{
"build/bin",
"frontend/dist",
"frontend/node_modules",
}
err = os.WriteFile(filepath.Join(options.TargetDir, ".gitignore"), []byte(strings.Join(ignore, "\n")), 0644)
if err != nil {
return errors.Wrap(err, "Unable to create gitignore")
}
return nil
}
// findAuthorDetails tries to find the user's name and email
// from gitconfig. If it finds them, it stores them in the project options
func findAuthorDetails(options *templates.Options) {
if git.IsInstalled() {
name, err := git.Name()
if err == nil {
options.AuthorName = strings.TrimSpace(name)
}
email, err := git.Email()
if err == nil {
options.AuthorEmail = strings.TrimSpace(email)
}
}
}
func updateReplaceLine(targetPath string) {
file, err := os.Open("go.mod")
if err != nil {
log.Fatal(err)
}
var lines []string
scanner := bufio.NewScanner(file)
for scanner.Scan() {
lines = append(lines, scanner.Text())
}
err = file.Close()
if err != nil {
log.Fatal(err)
}
if err := scanner.Err(); err != nil {
log.Fatal(err)
}
for i, line := range lines {
println(line)
if strings.HasPrefix(line, "// replace") {
println("Found replace line")
splitLine := strings.Split(line, " ")
splitLine[5] = targetPath + "/v2"
lines[i] = strings.Join(splitLine[1:], " ")
continue
}
}
err = os.WriteFile("go.mod", []byte(strings.Join(lines, "\n")), 0644)
if err != nil {
log.Fatal(err)
}
}

View File

@ -1,88 +0,0 @@
package main
import (
"embed"
"log"
"github.com/wailsapp/wails/v2/pkg/options/mac"
"github.com/wailsapp/wails/v2"
"github.com/wailsapp/wails/v2/pkg/logger"
"github.com/wailsapp/wails/v2/pkg/options"
"github.com/wailsapp/wails/v2/pkg/options/assetserver"
"github.com/wailsapp/wails/v2/pkg/options/windows"
)
//go:embed all:frontend/src
var assets embed.FS
//go:embed build/appicon.png
var icon []byte
func main() {
// Create an instance of the app structure
app := NewApp()
// Create application with options
err := wails.Run(&options.App{
Title: "{{.ProjectName}}",
Width: 1024,
Height: 768,
MinWidth: 1024,
MinHeight: 768,
MaxWidth: 1280,
MaxHeight: 800,
DisableResize: false,
Fullscreen: false,
Frameless: false,
StartHidden: false,
HideWindowOnClose: false,
BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 1},
AssetServer: &assetserver.Options{
Assets: assets,
},
Menu: nil,
Logger: nil,
LogLevel: logger.DEBUG,
OnStartup: app.startup,
OnDomReady: app.domReady,
OnBeforeClose: app.beforeClose,
OnShutdown: app.shutdown,
WindowStartState: options.Normal,
Bind: []interface{}{
app,
},
// Windows platform specific options
Windows: &windows.Options{
WebviewIsTransparent: false,
WindowIsTranslucent: false,
DisableWindowIcon: false,
// DisableFramelessWindowDecorations: false,
WebviewUserDataPath: "",
IsZoomControlEnabled: false,
ZoomFactor: float64,
},
Mac: &mac.Options{
TitleBar: &mac.TitleBar{
TitlebarAppearsTransparent: true,
HideTitle: false,
HideTitleBar: false,
FullSizeContent: false,
UseToolbar: false,
HideToolbarSeparator: true,
},
Appearance: mac.NSAppearanceNameDarkAqua,
WebviewIsTransparent: true,
WindowIsTranslucent: true,
About: &mac.AboutInfo{
Title: "Plain Template",
Message: "Part of the Wails projects",
Icon: icon,
},
},
})
if err != nil {
log.Fatal(err)
}
}

View File

@ -1,36 +0,0 @@
package main
import (
"embed"
"github.com/wailsapp/wails/v2"
"github.com/wailsapp/wails/v2/pkg/options"
"github.com/wailsapp/wails/v2/pkg/options/assetserver"
)
//go:embed all:frontend/dist
var assets embed.FS
func main() {
// Create an instance of the app structure
app := NewApp()
// Create application with options
err := wails.Run(&options.App{
Title: "{{.ProjectName}}",
Width: 1024,
Height: 768,
AssetServer: &assetserver.Options{
Assets: assets,
},
BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 1},
OnStartup: app.startup,
Bind: []interface{}{
app,
},
})
if err != nil {
println("Error:", err.Error())
}
}

View File

@ -1,24 +0,0 @@
package show
import (
"fmt"
"github.com/leaanthony/clir"
"github.com/wailsapp/wails/v2/cmd/wails/internal"
"github.com/wailsapp/wails/v2/internal/github"
"io"
)
// AddSubcommand adds the `show` command for the Wails application
func AddSubcommand(app *clir.Cli, w io.Writer) {
showCommand := app.NewSubCommand("show", "Shows various information")
version := internal.Version
releaseNotes := showCommand.NewSubCommand("releasenotes", "Shows the release notes for the current version")
releaseNotes.StringFlag("version", "The version to show the release notes for", &version)
releaseNotes.Action(func() error {
app.PrintBanner()
releaseNotes := github.GetReleaseNotes(version)
_, _ = fmt.Fprintln(w, releaseNotes)
return nil
})
}

View File

@ -1,172 +0,0 @@
package update
import (
"fmt"
"github.com/labstack/gommon/color"
"github.com/wailsapp/wails/v2/internal/shell"
"io"
"log"
"os"
"github.com/wailsapp/wails/v2/internal/github"
"github.com/leaanthony/clir"
"github.com/wailsapp/wails/v2/pkg/clilogger"
)
// AddSubcommand adds the `init` command for the Wails application
func AddSubcommand(app *clir.Cli, w io.Writer, currentVersion string) error {
command := app.NewSubCommand("update", "Update the Wails CLI")
command.LongDescription(`This command allows you to update your version of the Wails CLI.`)
// Setup flags
var prereleaseRequired bool
command.BoolFlag("pre", "Update CLI to latest Prerelease", &prereleaseRequired)
var specificVersion string
command.StringFlag("version", "Install a specific version (Overrides other flags) of the CLI", &specificVersion)
command.Action(func() error {
// Create logger
logger := clilogger.New(w)
// Print banner
app.PrintBanner()
logger.Println("Checking for updates...")
var desiredVersion *github.SemanticVersion
var err error
var valid bool
if len(specificVersion) > 0 {
// Check if this is a valid version
valid, err = github.IsValidTag(specificVersion)
if err == nil {
if !valid {
err = fmt.Errorf("version '%s' is invalid", specificVersion)
} else {
desiredVersion, err = github.NewSemanticVersion(specificVersion)
}
}
} else {
if prereleaseRequired {
desiredVersion, err = github.GetLatestPreRelease()
} else {
desiredVersion, err = github.GetLatestStableRelease()
if err != nil {
println("")
println("No stable release found for this major version. To update to the latest pre-release (eg beta), run:")
println(" wails update -pre")
return nil
}
}
}
if err != nil {
return err
}
fmt.Println()
fmt.Println(" Current Version : " + currentVersion)
if len(specificVersion) > 0 {
fmt.Printf(" Desired Version : v%s\n", desiredVersion)
} else {
if prereleaseRequired {
fmt.Printf(" Latest Prerelease : v%s\n", desiredVersion)
} else {
fmt.Printf(" Latest Release : v%s\n", desiredVersion)
}
}
return updateToVersion(logger, desiredVersion, len(specificVersion) > 0, currentVersion)
})
return nil
}
func updateToVersion(logger *clilogger.CLILogger, targetVersion *github.SemanticVersion, force bool, currentVersion string) error {
var targetVersionString = "v" + targetVersion.String()
// Early exit
if targetVersionString == currentVersion {
logger.Println("\nLooks like you're up to date!")
return nil
}
var desiredVersion string
if !force {
compareVersion := currentVersion
currentVersion, err := github.NewSemanticVersion(compareVersion)
if err != nil {
return err
}
var success bool
// Release -> Pre-Release = Massage current version to prerelease format
if targetVersion.IsPreRelease() && currentVersion.IsRelease() {
testVersion, err := github.NewSemanticVersion(compareVersion + "-0")
if err != nil {
return err
}
success, _ = targetVersion.IsGreaterThan(testVersion)
}
// Pre-Release -> Release = Massage target version to prerelease format
if targetVersion.IsRelease() && currentVersion.IsPreRelease() {
// We are ok with greater than or equal
mainversion := currentVersion.MainVersion()
targetVersion, err = github.NewSemanticVersion(targetVersion.String())
if err != nil {
return err
}
success, _ = targetVersion.IsGreaterThanOrEqual(mainversion)
}
// Release -> Release = Standard check
if (targetVersion.IsRelease() && currentVersion.IsRelease()) ||
(targetVersion.IsPreRelease() && currentVersion.IsPreRelease()) {
success, _ = targetVersion.IsGreaterThan(currentVersion)
}
// Compare
if !success {
logger.Println("Error: The requested version is lower than the current version.")
logger.Println("If this is what you really want to do, use `wails update -version %s`", targetVersionString)
return nil
}
desiredVersion = "v" + targetVersion.String()
} else {
desiredVersion = "v" + targetVersion.String()
}
fmt.Println()
logger.Print("Installing Wails CLI " + desiredVersion + "...")
// Run command in non module directory
homeDir, err := os.UserHomeDir()
if err != nil {
log.Fatal("Cannot find home directory! Please file a bug report!")
}
sout, serr, err := shell.RunCommand(homeDir, "go", "install", "github.com/wailsapp/wails/v2/cmd/wails@"+desiredVersion)
if err != nil {
logger.Println("Failed.")
logger.Println(sout + `\n` + serr)
return err
}
logger.Println("Done.")
logger.Println(color.Green("\nMake sure you update your project go.mod file to use " + desiredVersion + ":"))
logger.Println(color.Green(" require github.com/wailsapp/wails/v2 " + desiredVersion))
logger.Println(color.Red("\nTo view the release notes, please run `wails show releasenotes`"))
return nil
}

View File

@ -0,0 +1,530 @@
package dev
import (
"context"
"errors"
"fmt"
"github.com/samber/lo"
"github.com/wailsapp/wails/v2/cmd/wails/flags"
"github.com/wailsapp/wails/v2/cmd/wails/internal/gomod"
"github.com/wailsapp/wails/v2/cmd/wails/internal/logutils"
"io"
"net/http"
"net/url"
"os"
"os/exec"
"os/signal"
"path"
"path/filepath"
"strings"
"sync"
"sync/atomic"
"syscall"
"time"
"github.com/wailsapp/wails/v2/pkg/commands/bindings"
"github.com/wailsapp/wails/v2/pkg/commands/buildtags"
"github.com/google/shlex"
"github.com/pkg/browser"
"github.com/fsnotify/fsnotify"
"github.com/wailsapp/wails/v2/internal/fs"
"github.com/wailsapp/wails/v2/internal/process"
"github.com/wailsapp/wails/v2/pkg/clilogger"
"github.com/wailsapp/wails/v2/pkg/commands/build"
)
func sliceToMap(input []string) map[string]struct{} {
result := map[string]struct{}{}
for _, value := range input {
result[value] = struct{}{}
}
return result
}
type devFlags struct {
ldflags string
compilerCommand string
assetDir string
extensions string
reloadDirs string
openBrowser bool
noReload bool
skipBindings bool
wailsjsdir string
tags string
verbosity int
loglevel string
forceBuild bool
debounceMS int
devServer string
appargs string
saveConfig bool
raceDetector bool
frontendDevServerURL string
skipFrontend bool
noColour bool
}
// Application runs the application in dev mode
func Application(f *flags.Dev, logger *clilogger.CLILogger) error {
cwd := lo.Must(os.Getwd())
// Update go.mod to use current wails version
err := gomod.SyncGoMod(logger, true)
if err != nil {
return err
}
// Run go mod tidy to ensure we're up-to-date
err = runCommand(cwd, false, "go", "mod", "tidy")
if err != nil {
return err
}
buildOptions := f.GenerateBuildOptions()
buildOptions.Logger = logger
userTags, err := buildtags.Parse(f.Tags)
if err != nil {
return err
}
buildOptions.UserTags = userTags
err = build.CreateEmbedDirectories(cwd, buildOptions)
if err != nil {
return err
}
projectConfig := f.ProjectConfig()
if !buildOptions.SkipBindings {
if f.Verbosity == build.VERBOSE {
logutils.LogGreen("Generating Bindings...")
}
stdout, err := bindings.GenerateBindings(bindings.Options{
Tags: buildOptions.UserTags,
TsPrefix: projectConfig.Bindings.TsGeneration.Prefix,
TsSuffix: projectConfig.Bindings.TsGeneration.Suffix,
})
if err != nil {
return err
}
if f.Verbosity == build.VERBOSE {
logutils.LogGreen(stdout)
}
}
// Setup signal handler
quitChannel := make(chan os.Signal, 1)
signal.Notify(quitChannel, os.Interrupt, os.Kill, syscall.SIGTERM)
exitCodeChannel := make(chan int, 1)
// Build the frontend if requested, but ignore building the application itself.
ignoreFrontend := buildOptions.IgnoreFrontend
if !ignoreFrontend {
buildOptions.IgnoreApplication = true
if _, err := build.Build(buildOptions); err != nil {
return err
}
buildOptions.IgnoreApplication = false
}
// frontend:dev:watcher command.
frontendDevAutoDiscovery := projectConfig.IsFrontendDevServerURLAutoDiscovery()
if command := projectConfig.DevWatcherCommand; command != "" {
closer, devServerURL, err := runFrontendDevWatcherCommand(projectConfig.GetFrontendDir(), command, frontendDevAutoDiscovery)
if err != nil {
return err
}
if devServerURL != "" {
projectConfig.FrontendDevServerURL = devServerURL
f.FrontendDevServerURL = devServerURL
}
defer closer()
} else if frontendDevAutoDiscovery {
return fmt.Errorf("unable to auto discover frontend:dev:serverUrl without a frontend:dev:watcher command, please either set frontend:dev:watcher or remove the auto discovery from frontend:dev:serverUrl")
}
// Do initial build but only for the application.
logger.Println("Building application for development...")
buildOptions.IgnoreFrontend = true
debugBinaryProcess, appBinary, err := restartApp(buildOptions, nil, f, exitCodeChannel)
buildOptions.IgnoreFrontend = ignoreFrontend || f.FrontendDevServerURL != ""
if err != nil {
return err
}
defer func() {
if err := killProcessAndCleanupBinary(debugBinaryProcess, appBinary); err != nil {
logutils.LogDarkYellow("Unable to kill process and cleanup binary: %s", err)
}
}()
// open browser
if f.Browser {
err = browser.OpenURL(f.DevServerURL().String())
if err != nil {
return err
}
}
// create the project files watcher
watcher, err := initialiseWatcher(cwd)
if err != nil {
return err
}
defer func(watcher *fsnotify.Watcher) {
err := watcher.Close()
if err != nil {
logger.Fatal(err.Error())
}
}(watcher)
logutils.LogGreen("Watching (sub)/directory: %s", cwd)
logutils.LogGreen("Using DevServer URL: %s", f.DevServerURL())
if f.FrontendDevServerURL != "" {
logutils.LogGreen("Using Frontend DevServer URL: %s", f.FrontendDevServerURL)
}
logutils.LogGreen("Using reload debounce setting of %d milliseconds", f.Debounce)
// Show dev server URL in terminal after 3 seconds
go func() {
time.Sleep(3 * time.Second)
logutils.LogGreen("\n\nTo develop in the browser and call your bound Go methods from Javascript, navigate to: %s", f.DevServerURL())
}()
// Watch for changes and trigger restartApp()
debugBinaryProcess = doWatcherLoop(buildOptions, debugBinaryProcess, f, watcher, exitCodeChannel, quitChannel, f.DevServerURL())
// Kill the current program if running and remove dev binary
if err := killProcessAndCleanupBinary(debugBinaryProcess, appBinary); err != nil {
return err
}
// Reset the process and the binary so defer knows about it and is a nop.
debugBinaryProcess = nil
appBinary = ""
logutils.LogGreen("Development mode exited")
return nil
}
func killProcessAndCleanupBinary(process *process.Process, binary string) error {
if process != nil && process.Running {
if err := process.Kill(); err != nil {
return err
}
}
if binary != "" {
err := os.Remove(binary)
if err != nil && !errors.Is(err, os.ErrNotExist) {
return err
}
}
return nil
}
func runCommand(dir string, exitOnError bool, command string, args ...string) error {
logutils.LogGreen("Executing: " + command + " " + strings.Join(args, " "))
cmd := exec.Command(command, args...)
cmd.Dir = dir
output, err := cmd.CombinedOutput()
if err != nil {
println(string(output))
println(err.Error())
if exitOnError {
os.Exit(1)
}
return err
}
return nil
}
// runFrontendDevWatcherCommand will run the `frontend:dev:watcher` command if it was given, ex- `npm run dev`
func runFrontendDevWatcherCommand(frontendDirectory string, devCommand string, discoverViteServerURL bool) (func(), string, error) {
ctx, cancel := context.WithCancel(context.Background())
scanner := NewStdoutScanner()
cmdSlice := strings.Split(devCommand, " ")
cmd := exec.CommandContext(ctx, cmdSlice[0], cmdSlice[1:]...)
cmd.Stderr = os.Stderr
cmd.Stdout = scanner
cmd.Dir = frontendDirectory
setParentGID(cmd)
if err := cmd.Start(); err != nil {
cancel()
return nil, "", fmt.Errorf("unable to start frontend DevWatcher: %w", err)
}
var viteServerURL string
if discoverViteServerURL {
select {
case serverURL := <-scanner.ViteServerURLChan:
viteServerURL = serverURL
case <-time.After(time.Second * 10):
cancel()
return nil, "", errors.New("failed to find Vite server URL")
}
}
logutils.LogGreen("Running frontend DevWatcher command: '%s'", devCommand)
var wg sync.WaitGroup
wg.Add(1)
const (
stateRunning int32 = 0
stateCanceling = 1
stateStopped = 2
)
state := stateRunning
go func() {
if err := cmd.Wait(); err != nil {
wasRunning := atomic.CompareAndSwapInt32(&state, stateRunning, stateStopped)
if err.Error() != "exit status 1" && wasRunning {
logutils.LogRed("Error from DevWatcher '%s': %s", devCommand, err.Error())
}
}
atomic.StoreInt32(&state, stateStopped)
wg.Done()
}()
return func() {
if atomic.CompareAndSwapInt32(&state, stateRunning, stateCanceling) {
killProc(cmd, devCommand)
}
cancel()
wg.Wait()
}, viteServerURL, nil
}
// restartApp does the actual rebuilding of the application when files change
func restartApp(buildOptions *build.Options, debugBinaryProcess *process.Process, f *flags.Dev, exitCodeChannel chan int) (*process.Process, string, error) {
appBinary, err := build.Build(buildOptions)
println()
if err != nil {
logutils.LogRed("Build error - " + err.Error())
msg := "Continuing to run current version"
if debugBinaryProcess == nil {
msg = "No version running, build will be retriggered as soon as changes have been detected"
}
logutils.LogDarkYellow(msg)
return nil, "", nil
}
// Kill existing binary if need be
if debugBinaryProcess != nil {
killError := debugBinaryProcess.Kill()
if killError != nil {
buildOptions.Logger.Fatal("Unable to kill debug binary (PID: %d)!", debugBinaryProcess.PID())
}
debugBinaryProcess = nil
}
// parse appargs if any
args, err := shlex.Split(f.AppArgs)
if err != nil {
buildOptions.Logger.Fatal("Unable to parse appargs: %s", err.Error())
}
// Set environment variables accordingly
os.Setenv("loglevel", f.LogLevel)
os.Setenv("assetdir", f.AssetDir)
os.Setenv("devserver", f.DevServer)
os.Setenv("frontenddevserverurl", f.FrontendDevServerURL)
// Start up new binary with correct args
newProcess := process.NewProcess(appBinary, args...)
err = newProcess.Start(exitCodeChannel)
if err != nil {
// Remove binary
if fs.FileExists(appBinary) {
deleteError := fs.DeleteFile(appBinary)
if deleteError != nil {
buildOptions.Logger.Fatal("Unable to delete app binary: " + appBinary)
}
}
buildOptions.Logger.Fatal("Unable to start application: %s", err.Error())
}
return newProcess, appBinary, nil
}
// doWatcherLoop is the main watch loop that runs while dev is active
func doWatcherLoop(buildOptions *build.Options, debugBinaryProcess *process.Process, f *flags.Dev, watcher *fsnotify.Watcher, exitCodeChannel chan int, quitChannel chan os.Signal, devServerURL *url.URL) *process.Process {
// Main Loop
var extensionsThatTriggerARebuild = sliceToMap(strings.Split(f.Extensions, ","))
var dirsThatTriggerAReload []string
for _, dir := range strings.Split(f.ReloadDirs, ",") {
if dir == "" {
continue
}
thePath, err := filepath.Abs(dir)
if err != nil {
logutils.LogRed("Unable to expand reloadDir '%s': %s", dir, err)
continue
}
dirsThatTriggerAReload = append(dirsThatTriggerAReload, thePath)
}
quit := false
interval := time.Duration(f.Debounce) * time.Millisecond
timer := time.NewTimer(interval)
rebuild := false
reload := false
assetDir := ""
changedPaths := map[string]struct{}{}
// If we are using an external dev server, the reloading of the frontend part can be skipped or if the user requested it
skipAssetsReload := f.FrontendDevServerURL != "" || f.NoReload
assetDirURL := joinPath(devServerURL, "/wails/assetdir")
reloadURL := joinPath(devServerURL, "/wails/reload")
for quit == false {
// reload := false
select {
case exitCode := <-exitCodeChannel:
if exitCode == 0 {
quit = true
}
case err := <-watcher.Errors:
logutils.LogDarkYellow(err.Error())
case item := <-watcher.Events:
isEligibleFile := func(fileName string) bool {
// Iterate all file patterns
ext := filepath.Ext(fileName)
if ext != "" {
ext = ext[1:]
if _, exists := extensionsThatTriggerARebuild[ext]; exists {
return true
}
}
return false
}
// Handle write operations
if item.Op&fsnotify.Write == fsnotify.Write {
// Ignore directories
itemName := item.Name
if fs.DirExists(itemName) {
continue
}
if isEligibleFile(itemName) {
rebuild = true
timer.Reset(interval)
continue
}
for _, reloadDir := range dirsThatTriggerAReload {
if strings.HasPrefix(itemName, reloadDir) {
reload = true
break
}
}
if !reload {
changedPaths[filepath.Dir(itemName)] = struct{}{}
}
timer.Reset(interval)
}
// Handle new fs entries that are created
if item.Op&fsnotify.Create == fsnotify.Create {
// If this is a folder, add it to our watch list
if fs.DirExists(item.Name) {
// node_modules is BANNED!
if !strings.Contains(item.Name, "node_modules") {
err := watcher.Add(item.Name)
if err != nil {
buildOptions.Logger.Fatal("%s", err.Error())
}
logutils.LogGreen("Added new directory to watcher: %s", item.Name)
}
} else if isEligibleFile(item.Name) {
// Handle creation of new file.
// Note: On some platforms an update to a file is represented as
// REMOVE -> CREATE instead of WRITE, so this is not only new files
// but also updates to existing files
rebuild = true
timer.Reset(interval)
continue
}
}
case <-timer.C:
if rebuild {
rebuild = false
logutils.LogGreen("[Rebuild triggered] files updated")
// Try and build the app
newBinaryProcess, _, err := restartApp(buildOptions, debugBinaryProcess, f, exitCodeChannel)
if err != nil {
logutils.LogRed("Error during build: %s", err.Error())
continue
}
// If we have a new process, saveConfig it
if newBinaryProcess != nil {
debugBinaryProcess = newBinaryProcess
}
}
if !skipAssetsReload && len(changedPaths) != 0 {
if assetDir == "" {
resp, err := http.Get(assetDirURL)
if err != nil {
logutils.LogRed("Error during retrieving assetdir: %s", err.Error())
} else {
content, err := io.ReadAll(resp.Body)
if err != nil {
logutils.LogRed("Error reading assetdir from devserver: %s", err.Error())
} else {
assetDir = string(content)
}
resp.Body.Close()
}
}
if assetDir != "" {
for thePath := range changedPaths {
if strings.HasPrefix(thePath, assetDir) {
reload = true
break
}
}
} else if len(dirsThatTriggerAReload) == 0 {
logutils.LogRed("Reloading couldn't be triggered: Please specify -assetdir or -reloaddirs")
}
}
if reload {
reload = false
_, err := http.Get(reloadURL)
if err != nil {
logutils.LogRed("Error during refresh: %s", err.Error())
}
}
changedPaths = map[string]struct{}{}
case <-quitChannel:
logutils.LogGreen("\nCaught quit")
quit = true
}
}
return debugBinaryProcess
}
func joinPath(url *url.URL, subPath string) string {
u := *url
u.Path = path.Join(u.Path, subPath)
return u.String()
}

View File

@ -1,8 +1,9 @@
package build package gomod
import ( import (
"fmt" "fmt"
"github.com/wailsapp/wails/v2/cmd/wails/internal" "github.com/wailsapp/wails/v2/cmd/wails/internal"
"github.com/wailsapp/wails/v2/internal/colour"
"github.com/wailsapp/wails/v2/internal/fs" "github.com/wailsapp/wails/v2/internal/fs"
"github.com/wailsapp/wails/v2/internal/gomod" "github.com/wailsapp/wails/v2/internal/gomod"
"github.com/wailsapp/wails/v2/internal/goversion" "github.com/wailsapp/wails/v2/internal/goversion"
@ -57,3 +58,8 @@ func SyncGoMod(logger *clilogger.CLILogger, updateWailsVersion bool) error {
return nil return nil
} }
func LogGreen(message string, args ...interface{}) {
text := fmt.Sprintf(message, args...)
println(colour.Green(text))
}

View File

@ -0,0 +1,8 @@
package template
import (
"embed"
)
//go:embed base
var Base embed.FS

View File

@ -2,83 +2,100 @@ package main
import ( import (
"fmt" "fmt"
"github.com/pterm/pterm"
"github.com/wailsapp/wails/v2/cmd/wails/internal" "github.com/wailsapp/wails/v2/cmd/wails/internal"
"github.com/wailsapp/wails/v2/cmd/wails/internal/commands/show"
"os" "os"
"strings"
"github.com/wailsapp/wails/v2/internal/colour" "github.com/wailsapp/wails/v2/internal/colour"
"github.com/wailsapp/wails/v2/cmd/wails/internal/commands/update"
"github.com/leaanthony/clir" "github.com/leaanthony/clir"
"github.com/wailsapp/wails/v2/cmd/wails/internal/commands/build"
"github.com/wailsapp/wails/v2/cmd/wails/internal/commands/dev"
"github.com/wailsapp/wails/v2/cmd/wails/internal/commands/doctor"
"github.com/wailsapp/wails/v2/cmd/wails/internal/commands/generate"
"github.com/wailsapp/wails/v2/cmd/wails/internal/commands/initialise"
) )
func fatal(message string) {
println(message)
os.Exit(1)
}
func banner(_ *clir.Cli) string { func banner(_ *clir.Cli) string {
return fmt.Sprintf("%s %s", return fmt.Sprintf("%s %s",
colour.Green("Wails CLI"), colour.Green("Wails CLI"),
colour.DarkRed(internal.Version)) colour.DarkRed(internal.Version))
} }
func printFooter() { func fatal(message string) {
println(colour.Green("\nIf Wails is useful to you or your company, please consider sponsoring the project:\nhttps://github.com/sponsors/leaanthony\n")) printer := pterm.PrefixPrinter{
MessageStyle: &pterm.ThemeDefault.FatalMessageStyle,
Prefix: pterm.Prefix{
Style: &pterm.ThemeDefault.FatalPrefixStyle,
Text: " FATAL ",
},
}
printer.Println(message)
os.Exit(1)
} }
func printBulletPoint(text string, args ...any) {
item := pterm.BulletListItem{
Level: 2,
Text: text,
}
t, err := pterm.DefaultBulletList.WithItems([]pterm.BulletListItem{item}).Srender()
if err != nil {
fatal(err.Error())
}
t = strings.Trim(t, "\n\r")
pterm.Printf(t, args...)
}
func printFooter() {
printer := pterm.PrefixPrinter{
MessageStyle: pterm.NewStyle(pterm.FgLightGreen),
Prefix: pterm.Prefix{
Style: pterm.NewStyle(pterm.FgRed, pterm.BgLightWhite),
Text: "♥ ",
},
}
printer.Println("If Wails is useful to you or your company, please consider sponsoring the project:")
pterm.Println("https://github.com/sponsors/leaanthony\n")
}
func bool2Str(b bool) string {
if b {
return "true"
}
return "false"
}
var app *clir.Cli
func main() { func main() {
var err error var err error
app := clir.NewCli("Wails", "Go/HTML Appkit", internal.Version) app = clir.NewCli("Wails", "Go/HTML Appkit", internal.Version)
app.SetBannerFunction(banner) app.SetBannerFunction(banner)
defer printFooter() defer printFooter()
build.AddBuildSubcommand(app, os.Stdout) app.NewSubCommandFunction("build", "Builds the application", buildApplication)
err = initialise.AddSubcommand(app, os.Stdout) app.NewSubCommandFunction("dev", "Runs the application in development mode", devApplication)
if err != nil { app.NewSubCommandFunction("doctor", "Diagnose your environment", diagnoseEnvironment)
fatal(err.Error()) app.NewSubCommandFunction("init", "Initialises a new Wails project", initProject)
} app.NewSubCommandFunction("update", "Update the Wails CLI", update)
err = doctor.AddSubcommand(app, os.Stdout) show := app.NewSubCommand("show", "Shows various information")
if err != nil { show.NewSubCommandFunction("releasenotes", "Shows the release notes for the current version", showReleaseNotes)
fatal(err.Error())
}
err = dev.AddSubcommand(app, os.Stdout) generate := app.NewSubCommand("generate", "Code Generation Tools")
if err != nil { generate.NewSubCommandFunction("module", "Generates a new Wails module", generateModule)
fatal(err.Error()) generate.NewSubCommandFunction("template", "Generates a new Wails template", generateTemplate)
}
err = generate.AddSubcommand(app, os.Stdout)
if err != nil {
fatal(err.Error())
}
show.AddSubcommand(app, os.Stdout)
err = update.AddSubcommand(app, os.Stdout, internal.Version)
if err != nil {
fatal(err.Error())
}
command := app.NewSubCommand("version", "The Wails CLI version") command := app.NewSubCommand("version", "The Wails CLI version")
command.Action(func() error { command.Action(func() error {
println(internal.Version) pterm.Println(internal.Version)
return nil return nil
}) })
err = app.Run() err = app.Run()
if err != nil { if err != nil {
println("\n\nERROR: " + err.Error()) pterm.Println()
pterm.Error.Println(err.Error())
printFooter() printFooter()
os.Exit(1) os.Exit(1)
} }

27
v2/cmd/wails/show.go Normal file
View File

@ -0,0 +1,27 @@
package main
import (
"github.com/pterm/pterm"
"github.com/wailsapp/wails/v2/cmd/wails/flags"
"github.com/wailsapp/wails/v2/cmd/wails/internal"
"github.com/wailsapp/wails/v2/internal/colour"
"github.com/wailsapp/wails/v2/internal/github"
)
func showReleaseNotes(f *flags.ShowReleaseNotes) error {
if f.NoColour {
pterm.DisableColor()
colour.ColourEnabled = false
}
version := internal.Version
if f.Version != "" {
version = f.Version
}
app.PrintBanner()
releaseNotes := github.GetReleaseNotes(version, f.NoColour)
pterm.Println(releaseNotes)
return nil
}

156
v2/cmd/wails/update.go Normal file
View File

@ -0,0 +1,156 @@
package main
import (
"fmt"
"github.com/labstack/gommon/color"
"github.com/pterm/pterm"
"github.com/wailsapp/wails/v2/cmd/wails/flags"
"github.com/wailsapp/wails/v2/internal/colour"
"github.com/wailsapp/wails/v2/internal/shell"
"os"
"github.com/wailsapp/wails/v2/internal/github"
)
// AddSubcommand adds the `init` command for the Wails application
func update(f *flags.Update) error {
if f.NoColour {
colour.ColourEnabled = false
pterm.DisableColor()
}
// Print banner
app.PrintBanner()
pterm.Println("Checking for updates...")
var desiredVersion *github.SemanticVersion
var err error
var valid bool
if len(f.Version) > 0 {
// Check if this is a valid version
valid, err = github.IsValidTag(f.Version)
if err == nil {
if !valid {
err = fmt.Errorf("version '%s' is invalid", f.Version)
} else {
desiredVersion, err = github.NewSemanticVersion(f.Version)
}
}
} else {
if f.PreRelease {
desiredVersion, err = github.GetLatestPreRelease()
} else {
desiredVersion, err = github.GetLatestStableRelease()
if err != nil {
pterm.Println("")
pterm.Println("No stable release found for this major version. To update to the latest pre-release (eg beta), run:")
pterm.Println(" wails update -pre")
return nil
}
}
}
if err != nil {
return err
}
pterm.Println()
pterm.Println(" Current Version : " + app.Version())
if len(f.Version) > 0 {
fmt.Printf(" Desired Version : v%s\n", desiredVersion)
} else {
if f.PreRelease {
fmt.Printf(" Latest Prerelease : v%s\n", desiredVersion)
} else {
fmt.Printf(" Latest Release : v%s\n", desiredVersion)
}
}
return updateToVersion(desiredVersion, len(f.Version) > 0, app.Version())
}
func updateToVersion(targetVersion *github.SemanticVersion, force bool, currentVersion string) error {
var targetVersionString = "v" + targetVersion.String()
if targetVersionString == currentVersion {
pterm.Println("\nLooks like you're up to date!\n")
return nil
}
var desiredVersion string
if !force {
compareVersion := currentVersion
currentVersion, err := github.NewSemanticVersion(compareVersion)
if err != nil {
return err
}
var success bool
// Release -> Pre-Release = Massage current version to prerelease format
if targetVersion.IsPreRelease() && currentVersion.IsRelease() {
testVersion, err := github.NewSemanticVersion(compareVersion + "-0")
if err != nil {
return err
}
success, _ = targetVersion.IsGreaterThan(testVersion)
}
// Pre-Release -> Release = Massage target version to prerelease format
if targetVersion.IsRelease() && currentVersion.IsPreRelease() {
// We are ok with greater than or equal
mainversion := currentVersion.MainVersion()
targetVersion, err = github.NewSemanticVersion(targetVersion.String())
if err != nil {
return err
}
success, _ = targetVersion.IsGreaterThanOrEqual(mainversion)
}
// Release -> Release = Standard check
if (targetVersion.IsRelease() && currentVersion.IsRelease()) ||
(targetVersion.IsPreRelease() && currentVersion.IsPreRelease()) {
success, _ = targetVersion.IsGreaterThan(currentVersion)
}
// Compare
if !success {
pterm.Println("Error: The requested version is lower than the current version.")
pterm.Println("If this is what you really want to do, use `wails update -version %s`", targetVersionString)
return nil
}
desiredVersion = "v" + targetVersion.String()
} else {
desiredVersion = "v" + targetVersion.String()
}
pterm.Println()
pterm.Print("Installing Wails CLI " + desiredVersion + "...")
// Run command in non module directory
homeDir, err := os.UserHomeDir()
if err != nil {
fatal("Cannot find home directory! Please file a bug report!")
}
sout, serr, err := shell.RunCommand(homeDir, "go", "install", "github.com/wailsapp/wails/v2/cmd/wails@"+desiredVersion)
if err != nil {
pterm.Println("Failed.")
pterm.Error.Println(sout + `\n` + serr)
return err
}
pterm.Println("Done.")
pterm.Println(color.Green("\nMake sure you update your project go.mod file to use " + desiredVersion + ":"))
pterm.Println(color.Green(" require github.com/wailsapp/wails/v2 " + desiredVersion))
pterm.Println(color.Red("\nTo view the release notes, please run `wails show releasenotes`"))
return nil
}

View File

@ -14,14 +14,13 @@ require (
github.com/jackmordaunt/icns v1.0.0 github.com/jackmordaunt/icns v1.0.0
github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e
github.com/labstack/echo/v4 v4.9.0 github.com/labstack/echo/v4 v4.9.0
github.com/leaanthony/clir v1.0.4 github.com/leaanthony/clir v1.3.0
github.com/leaanthony/debme v1.2.1 github.com/leaanthony/debme v1.2.1
github.com/leaanthony/go-ansi-parser v1.0.1 github.com/leaanthony/go-ansi-parser v1.0.1
github.com/leaanthony/gosod v1.0.3 github.com/leaanthony/gosod v1.0.3
github.com/leaanthony/slicer v1.5.0 github.com/leaanthony/slicer v1.5.0
github.com/leaanthony/winicon v1.0.0 github.com/leaanthony/winicon v1.0.0
github.com/matryer/is v1.4.0 github.com/matryer/is v1.4.0
github.com/olekukonko/tablewriter v0.0.5
github.com/pkg/browser v0.0.0-20210706143420-7d21f8c997e2 github.com/pkg/browser v0.0.0-20210706143420-7d21f8c997e2
github.com/pkg/errors v0.9.1 github.com/pkg/errors v0.9.1
github.com/tc-hib/winres v0.1.5 github.com/tc-hib/winres v0.1.5
@ -41,25 +40,31 @@ require (
github.com/charmbracelet/glamour v0.5.0 github.com/charmbracelet/glamour v0.5.0
github.com/go-ole/go-ole v1.2.6 github.com/go-ole/go-ole v1.2.6
github.com/labstack/gommon v0.3.1 github.com/labstack/gommon v0.3.1
github.com/pterm/pterm v0.12.49
github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06 github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06
github.com/samber/lo v1.27.1 github.com/samber/lo v1.27.1
github.com/stretchr/testify v1.7.1 github.com/stretchr/testify v1.8.0
golang.org/x/tools v0.1.12 golang.org/x/tools v0.1.12
) )
require ( require (
atomicgo.dev/cursor v0.1.1 // indirect
atomicgo.dev/keyboard v0.2.8 // indirect
bitbucket.org/creachadair/shell v0.0.7 // indirect bitbucket.org/creachadair/shell v0.0.7 // indirect
github.com/Microsoft/go-winio v0.4.16 // indirect github.com/Microsoft/go-winio v0.4.16 // indirect
github.com/alecthomas/chroma v0.10.0 // indirect github.com/alecthomas/chroma v0.10.0 // indirect
github.com/aymerick/douceur v0.2.0 // indirect github.com/aymerick/douceur v0.2.0 // indirect
github.com/containerd/console v1.0.3 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dlclark/regexp2 v1.4.0 // indirect github.com/dlclark/regexp2 v1.4.0 // indirect
github.com/emirpasic/gods v1.12.0 // indirect github.com/emirpasic/gods v1.12.0 // indirect
github.com/go-git/gcfg v1.5.0 // indirect github.com/go-git/gcfg v1.5.0 // indirect
github.com/gookit/color v1.5.2 // indirect
github.com/gorilla/css v1.0.0 // indirect github.com/gorilla/css v1.0.0 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 // indirect github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 // indirect
github.com/kr/pretty v0.3.0 // indirect github.com/kr/pretty v0.3.0 // indirect
github.com/lithammer/fuzzysearch v1.1.5 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mattn/go-colorable v0.1.11 // indirect github.com/mattn/go-colorable v0.1.11 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect github.com/mattn/go-isatty v0.0.14 // indirect
@ -69,9 +74,10 @@ require (
github.com/muesli/reflow v0.3.0 // indirect github.com/muesli/reflow v0.3.0 // indirect
github.com/muesli/termenv v0.9.0 // indirect github.com/muesli/termenv v0.9.0 // indirect
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect
github.com/olekukonko/tablewriter v0.0.5 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rivo/uniseg v0.2.0 // indirect github.com/rivo/uniseg v0.2.0 // indirect
github.com/sergi/go-diff v1.1.0 // indirect github.com/sergi/go-diff v1.2.0 // indirect
github.com/tidwall/gjson v1.8.0 // indirect github.com/tidwall/gjson v1.8.0 // indirect
github.com/tidwall/match v1.0.3 // indirect github.com/tidwall/match v1.0.3 // indirect
github.com/tidwall/pretty v1.1.0 // indirect github.com/tidwall/pretty v1.1.0 // indirect
@ -79,11 +85,13 @@ require (
github.com/valyala/fasttemplate v1.2.1 // indirect github.com/valyala/fasttemplate v1.2.1 // indirect
github.com/wzshiming/winseq v0.0.0-20200112104235-db357dc107ae // indirect github.com/wzshiming/winseq v0.0.0-20200112104235-db357dc107ae // indirect
github.com/xanzy/ssh-agent v0.3.0 // indirect github.com/xanzy/ssh-agent v0.3.0 // indirect
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect
github.com/yuin/goldmark v1.4.13 // indirect github.com/yuin/goldmark v1.4.13 // indirect
github.com/yuin/goldmark-emoji v1.0.1 // indirect github.com/yuin/goldmark-emoji v1.0.1 // indirect
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 // indirect golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 // indirect
golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 // indirect golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 // indirect
golang.org/x/image v0.0.0-20201208152932-35266b937fa6 // indirect golang.org/x/image v0.0.0-20201208152932-35266b937fa6 // indirect
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
golang.org/x/text v0.3.7 // indirect golang.org/x/text v0.3.7 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect

View File

@ -1,5 +1,17 @@
atomicgo.dev/cursor v0.1.1 h1:0t9sxQomCTRh5ug+hAMCs59x/UmC9QL6Ci5uosINKD4=
atomicgo.dev/cursor v0.1.1/go.mod h1:Lr4ZJB3U7DfPPOkbH7/6TOtJ4vFGHlgj1nc+n900IpU=
atomicgo.dev/keyboard v0.2.8 h1:Di09BitwZgdTV1hPyX/b9Cqxi8HVuJQwWivnZUEqlj4=
atomicgo.dev/keyboard v0.2.8/go.mod h1:BC4w9g00XkxH/f1HXhW2sXmJFOCWbKn9xrOunSFtExQ=
bitbucket.org/creachadair/shell v0.0.7 h1:Z96pB6DkSb7F3Y3BBnJeOZH2gazyMTWlvecSD4vDqfk= bitbucket.org/creachadair/shell v0.0.7 h1:Z96pB6DkSb7F3Y3BBnJeOZH2gazyMTWlvecSD4vDqfk=
bitbucket.org/creachadair/shell v0.0.7/go.mod h1:oqtXSSvSYr4624lnnabXHaBsYW6RD80caLi2b3hJk0U= bitbucket.org/creachadair/shell v0.0.7/go.mod h1:oqtXSSvSYr4624lnnabXHaBsYW6RD80caLi2b3hJk0U=
github.com/MarvinJWendt/testza v0.1.0/go.mod h1:7AxNvlfeHP7Z/hDQ5JtE3OKYT3XFUeLCDE2DQninSqs=
github.com/MarvinJWendt/testza v0.2.1/go.mod h1:God7bhG8n6uQxwdScay+gjm9/LnO4D3kkcZX4hv9Rp8=
github.com/MarvinJWendt/testza v0.2.8/go.mod h1:nwIcjmr0Zz+Rcwfh3/4UhBp7ePKVhuBExvZqnKYWlII=
github.com/MarvinJWendt/testza v0.2.10/go.mod h1:pd+VWsoGUiFtq+hRKSU1Bktnn+DMCSrDrXDpX2bG66k=
github.com/MarvinJWendt/testza v0.2.12/go.mod h1:JOIegYyV7rX+7VZ9r77L/eH6CfJHHzXjB69adAhzZkI=
github.com/MarvinJWendt/testza v0.3.0/go.mod h1:eFcL4I0idjtIx8P9C6KkAuLgATNKpX4/2oUqKc6bF2c=
github.com/MarvinJWendt/testza v0.4.2/go.mod h1:mSdhXiKH8sg/gQehJ63bINcCKp7RtYewEjXsvsVUPbE=
github.com/MarvinJWendt/testza v0.4.3 h1:u2XaM4IqGp9dsdUmML8/Z791fu4yjQYzOiufOtJwTII=
github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
@ -15,6 +27,7 @@ github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/atomicgo/cursor v0.0.1/go.mod h1:cBON2QmmrysudxNBFthvMtN32r3jxVRIvzkUiF/RuIk=
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY= github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY=
@ -23,6 +36,8 @@ github.com/bitfield/script v0.19.0 h1:W24f+FQuPab9gXcW8bhcbo5qO8AtrXyu3XOnR4zhHN
github.com/bitfield/script v0.19.0/go.mod h1:ana6F8YOSZ3ImT8SauIzuYSqXgFVkSUJ6kgja+WMmIY= github.com/bitfield/script v0.19.0/go.mod h1:ana6F8YOSZ3ImT8SauIzuYSqXgFVkSUJ6kgja+WMmIY=
github.com/charmbracelet/glamour v0.5.0 h1:wu15ykPdB7X6chxugG/NNfDUbyyrCLV9XBalj5wdu3g= github.com/charmbracelet/glamour v0.5.0 h1:wu15ykPdB7X6chxugG/NNfDUbyyrCLV9XBalj5wdu3g=
github.com/charmbracelet/glamour v0.5.0/go.mod h1:9ZRtG19AUIzcTm7FGLGbq3D5WKQ5UyZBbQsMQN0XIqc= github.com/charmbracelet/glamour v0.5.0/go.mod h1:9ZRtG19AUIzcTm7FGLGbq3D5WKQ5UyZBbQsMQN0XIqc=
github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw=
github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@ -58,6 +73,10 @@ github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaU
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gookit/color v1.4.2/go.mod h1:fqRyamkC1W8uxl+lxCQxOT09l/vYfZ+QeiX3rKQHCoQ=
github.com/gookit/color v1.5.0/go.mod h1:43aQb+Zerm/BWh2GnrgOQm7ffz7tvQXEKV6BFMl7wAo=
github.com/gookit/color v1.5.2 h1:uLnfXcaFjlrDnQDT+NCBcfhrXqYTx/rcCa6xn01Y8yI=
github.com/gookit/color v1.5.2/go.mod h1:w8h4bGiHeeBpvQVePTutdbERIUf3oJE5lZ8HM0UgXyg=
github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY= github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
@ -71,6 +90,10 @@ github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e/go.mod h1:alcuEE
github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 h1:DowS9hvgyYSX4TO5NpyC606/Z4SxnNYbT+WX27or6Ck= github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 h1:DowS9hvgyYSX4TO5NpyC606/Z4SxnNYbT+WX27or6Ck=
github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.0.10/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
github.com/klauspost/cpuid/v2 v2.1.0 h1:eyi1Ad2aNJMW95zcSbmGg7Cg6cq3ADwLpMAP96d8rF0=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
@ -84,8 +107,9 @@ github.com/labstack/echo/v4 v4.9.0 h1:wPOF1CE6gvt/kmbMR4dGzWvHMPT+sAEUJOwOTtvITV
github.com/labstack/echo/v4 v4.9.0/go.mod h1:xkCDAdFCIf8jsFQ5NnbK7oqaF/yU1A1X20Ltm0OvSks= github.com/labstack/echo/v4 v4.9.0/go.mod h1:xkCDAdFCIf8jsFQ5NnbK7oqaF/yU1A1X20Ltm0OvSks=
github.com/labstack/gommon v0.3.1 h1:OomWaJXm7xR6L1HmEtGyQf26TEn7V6X88mktX9kee9o= github.com/labstack/gommon v0.3.1 h1:OomWaJXm7xR6L1HmEtGyQf26TEn7V6X88mktX9kee9o=
github.com/labstack/gommon v0.3.1/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM= github.com/labstack/gommon v0.3.1/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM=
github.com/leaanthony/clir v1.0.4 h1:Dov2y9zWJmZr7CjaCe86lKa4b5CSxskGAt2yBkoDyiU=
github.com/leaanthony/clir v1.0.4/go.mod h1:k/RBkdkFl18xkkACMCLt09bhiZnrGORoxmomeMvDpE0= github.com/leaanthony/clir v1.0.4/go.mod h1:k/RBkdkFl18xkkACMCLt09bhiZnrGORoxmomeMvDpE0=
github.com/leaanthony/clir v1.3.0 h1:L9nPDWrmc/qU9UWZZvRaFajWYuO0np9V5p+5gxyYno0=
github.com/leaanthony/clir v1.3.0/go.mod h1:k/RBkdkFl18xkkACMCLt09bhiZnrGORoxmomeMvDpE0=
github.com/leaanthony/debme v1.2.1 h1:9Tgwf+kjcrbMQ4WnPcEIUcQuIZYqdWftzZkBr+i/oOc= github.com/leaanthony/debme v1.2.1 h1:9Tgwf+kjcrbMQ4WnPcEIUcQuIZYqdWftzZkBr+i/oOc=
github.com/leaanthony/debme v1.2.1/go.mod h1:3V+sCm5tYAgQymvSOfYQ5Xx2JCr+OXiD9Jkw3otUjiA= github.com/leaanthony/debme v1.2.1/go.mod h1:3V+sCm5tYAgQymvSOfYQ5Xx2JCr+OXiD9Jkw3otUjiA=
github.com/leaanthony/go-ansi-parser v1.0.1 h1:97v6c5kYppVsbScf4r/VZdXyQ21KQIfeQOk2DgKxGG4= github.com/leaanthony/go-ansi-parser v1.0.1 h1:97v6c5kYppVsbScf4r/VZdXyQ21KQIfeQOk2DgKxGG4=
@ -96,6 +120,8 @@ github.com/leaanthony/slicer v1.5.0 h1:aHYTN8xbCCLxJmkNKiLB6tgcMARl4eWmH9/F+S/0H
github.com/leaanthony/slicer v1.5.0/go.mod h1:FwrApmf8gOrpzEWM2J/9Lh79tyq8KTX5AzRtwV7m4AY= github.com/leaanthony/slicer v1.5.0/go.mod h1:FwrApmf8gOrpzEWM2J/9Lh79tyq8KTX5AzRtwV7m4AY=
github.com/leaanthony/winicon v1.0.0 h1:ZNt5U5dY71oEoKZ97UVwJRT4e+5xo5o/ieKuHuk8NqQ= github.com/leaanthony/winicon v1.0.0 h1:ZNt5U5dY71oEoKZ97UVwJRT4e+5xo5o/ieKuHuk8NqQ=
github.com/leaanthony/winicon v1.0.0/go.mod h1:en5xhijl92aphrJdmRPlh4NI1L6wq3gEm0LpXAPghjU= github.com/leaanthony/winicon v1.0.0/go.mod h1:en5xhijl92aphrJdmRPlh4NI1L6wq3gEm0LpXAPghjU=
github.com/lithammer/fuzzysearch v1.1.5 h1:Ag7aKU08wp0R9QCfF4GoGST9HbmAIeLP7xwMrOBEp1c=
github.com/lithammer/fuzzysearch v1.1.5/go.mod h1:1R1LRNk7yKid1BaQkmuLQaHruxcC4HmAH30Dh61Ih1Q=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE= github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE=
@ -129,6 +155,15 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pterm/pterm v0.12.27/go.mod h1:PhQ89w4i95rhgE+xedAoqous6K9X+r6aSOI2eFF7DZI=
github.com/pterm/pterm v0.12.29/go.mod h1:WI3qxgvoQFFGKGjGnJR849gU0TsEOvKn5Q8LlY1U7lg=
github.com/pterm/pterm v0.12.30/go.mod h1:MOqLIyMOgmTDz9yorcYbcw+HsgoZo3BQfg2wtl3HEFE=
github.com/pterm/pterm v0.12.31/go.mod h1:32ZAWZVXD7ZfG0s8qqHXePte42kdz8ECtRyEejaWgXU=
github.com/pterm/pterm v0.12.33/go.mod h1:x+h2uL+n7CP/rel9+bImHD5lF3nM9vJj80k9ybiiTTE=
github.com/pterm/pterm v0.12.36/go.mod h1:NjiL09hFhT/vWjQHSj1athJpx6H8cjpHXNAK5bUw8T8=
github.com/pterm/pterm v0.12.40/go.mod h1:ffwPLwlbXxP+rxT0GsgDTzS3y3rmpAO1NMjUkGTYf8s=
github.com/pterm/pterm v0.12.49 h1:qeNm0wTWawy6WhKoY8ZKq6qTXFr0s2UtUyRW0yVztEg=
github.com/pterm/pterm v0.12.49/go.mod h1:D4OBoWNqAfXkm5QLTjIgjNiMXPHemLJHnIreGUsWzWg=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
@ -138,17 +173,20 @@ github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06 h1:OkMGxebDj
github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06/go.mod h1:+ePHsJ1keEjQtpvf9HHw0f4ZeJ0TLRsxhunSI2hYJSs= github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06/go.mod h1:+ePHsJ1keEjQtpvf9HHw0f4ZeJ0TLRsxhunSI2hYJSs=
github.com/samber/lo v1.27.1 h1:sTXwkRiIFIQG+G0HeAvOEnGjqWeWtI9cg5/n51KrxPg= github.com/samber/lo v1.27.1 h1:sTXwkRiIFIQG+G0HeAvOEnGjqWeWtI9cg5/n51KrxPg=
github.com/samber/lo v1.27.1/go.mod h1:it33p9UtPMS7z72fP4gw/EIfQB2eI8ke7GR2wc6+Rhg= github.com/samber/lo v1.27.1/go.mod h1:it33p9UtPMS7z72fP4gw/EIfQB2eI8ke7GR2wc6+Rhg=
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ=
github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/tc-hib/winres v0.1.5 h1:2dA5yfjdoEA3UyRaOC92HNMt3jap66pLzoW4MjpC/0M= github.com/tc-hib/winres v0.1.5 h1:2dA5yfjdoEA3UyRaOC92HNMt3jap66pLzoW4MjpC/0M=
github.com/tc-hib/winres v0.1.5/go.mod h1:pe6dOR40VOrGz8PkzreVKNvEKnlE8t4yR8A8naL+t7A= github.com/tc-hib/winres v0.1.5/go.mod h1:pe6dOR40VOrGz8PkzreVKNvEKnlE8t4yR8A8naL+t7A=
github.com/thoas/go-funk v0.9.1 h1:O549iLZqPpTUQ10ykd26sZhzD+rmR5pWhuElrhbC20M= github.com/thoas/go-funk v0.9.1 h1:O549iLZqPpTUQ10ykd26sZhzD+rmR5pWhuElrhbC20M=
@ -174,6 +212,8 @@ github.com/wzshiming/winseq v0.0.0-20200112104235-db357dc107ae h1:tpXvBXC3hpQBDC
github.com/wzshiming/winseq v0.0.0-20200112104235-db357dc107ae/go.mod h1:VTAq37rkGeV+WOybvZwjXiJOicICdpLCN8ifpISjK20= github.com/wzshiming/winseq v0.0.0-20200112104235-db357dc107ae/go.mod h1:VTAq37rkGeV+WOybvZwjXiJOicICdpLCN8ifpISjK20=
github.com/xanzy/ssh-agent v0.3.0 h1:wUMzuKtKilRgBAD1sUb8gOwwRr2FGoBVumcjoOACClI= github.com/xanzy/ssh-agent v0.3.0 h1:wUMzuKtKilRgBAD1sUb8gOwwRr2FGoBVumcjoOACClI=
github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0= github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0=
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 h1:QldyIu/L63oPpyvQmHgvgickp1Yw510KJOqX7H24mg8=
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.4/go.mod h1:rmuwmfZ0+bvzB24eSC//bk1R1Zp3hM0OXYv/G2LIilg= github.com/yuin/goldmark v1.4.4/go.mod h1:rmuwmfZ0+bvzB24eSC//bk1R1Zp3hM0OXYv/G2LIilg=
github.com/yuin/goldmark v1.4.13 h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE= github.com/yuin/goldmark v1.4.13 h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE=
@ -206,17 +246,25 @@ golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211013075003-97ac67df715c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=

View File

@ -341,7 +341,7 @@ func (f *Frontend) WindowSetBackgroundColour(col *options.RGBA) {
B: col.B, B: col.B,
} }
// Webview2 only has 0 and 255 as valid values. // WebView2 only has 0 and 255 as valid values.
if backgroundCol.A > 0 && backgroundCol.A < 255 { if backgroundCol.A > 0 && backgroundCol.A < 255 {
backgroundCol.A = 255 backgroundCol.A = 255
} }

View File

@ -75,7 +75,7 @@ func GetAvailableCoreWebView2BrowserVersionString(path string) (string, error) {
// feature-complete and remove the use of the native DLL and go-winloader. // feature-complete and remove the use of the native DLL and go-winloader.
version, err := goGetAvailableCoreWebView2BrowserVersionString(path) version, err := goGetAvailableCoreWebView2BrowserVersionString(path)
if errors.Is(err, errNoClientDLLFound) { if errors.Is(err, errNoClientDLLFound) {
// Webview2 is not found // WebView2 is not found
return "", nil return "", nil
} else if err != nil { } else if err != nil {
return "", err return "", err
@ -105,7 +105,7 @@ func GetAvailableCoreWebView2BrowserVersionString(path string) (string, error) {
if res != 0 { if res != 0 {
if res == E_FILENOTFOUND { if res == E_FILENOTFOUND {
// Webview2 is not installed // WebView2 is not installed
return "", nil return "", nil
} }

View File

@ -35,7 +35,7 @@ func GetAvailableCoreWebView2BrowserVersionString(browserExecutableFolder string
if browserExecutableFolder != "" { if browserExecutableFolder != "" {
clientPath, err := findEmbeddedClientDll(browserExecutableFolder) clientPath, err := findEmbeddedClientDll(browserExecutableFolder)
if errors.Is(err, errNoClientDLLFound) { if errors.Is(err, errNoClientDLLFound) {
// Webview2 is not found // WebView2 is not found
return "", nil return "", nil
} else if err != nil { } else if err != nil {
return "", err return "", err

View File

@ -12,7 +12,7 @@ import (
"strings" "strings"
) )
func GetReleaseNotes(tagVersion string) string { func GetReleaseNotes(tagVersion string, noColour bool) string {
resp, err := http.Get("https://api.github.com/repos/wailsapp/wails/releases/tags/" + url.PathEscape(tagVersion)) resp, err := http.Get("https://api.github.com/repos/wailsapp/wails/releases/tags/" + url.PathEscape(tagVersion))
if err != nil { if err != nil {
return "Unable to retrieve release notes. Please check your network connection" return "Unable to retrieve release notes. Please check your network connection"
@ -34,11 +34,16 @@ func GetReleaseNotes(tagVersion string) string {
result := "# Release Notes for " + tagVersion + "\n" + data["body"].(string) result := "# Release Notes for " + tagVersion + "\n" + data["body"].(string)
var renderer *glamour.TermRenderer var renderer *glamour.TermRenderer
if runtime.GOOS == "windows" {
renderer, err = glamour.NewTermRenderer(glamour.WithStyles(glamour.NoTTYStyleConfig)) var termRendererOpts []glamour.TermRendererOption
if runtime.GOOS == "windows" || noColour {
termRendererOpts = append(termRendererOpts, glamour.WithStyles(glamour.NoTTYStyleConfig))
} else { } else {
renderer, err = glamour.NewTermRenderer(glamour.WithAutoStyle()) termRendererOpts = append(termRendererOpts, glamour.WithAutoStyle())
} }
renderer, err = glamour.NewTermRenderer(termRendererOpts...)
if err != nil { if err != nil {
return result return result
} }

View File

@ -10,7 +10,7 @@ import (
"github.com/wailsapp/wails/v2/pkg/options/windows" "github.com/wailsapp/wails/v2/pkg/options/windows"
) )
const MinimumRuntimeVersion string = "94.0.992.31" // Webview2 SDK 1.0.992.28 const MinimumRuntimeVersion string = "94.0.992.31" // WebView2 SDK 1.0.992.28
type installationStatus int type installationStatus int

View File

@ -3,6 +3,7 @@ package build
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"github.com/pterm/pterm"
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
@ -283,7 +284,7 @@ func (b *BaseBuilder) CompileProject(options *Options) error {
cmd := exec.Command(compiler, commands.AsSlice()...) cmd := exec.Command(compiler, commands.AsSlice()...)
cmd.Stderr = os.Stderr cmd.Stderr = os.Stderr
if verbose { if verbose {
println(" Build command:", compiler, commandPrettifier(commands.AsSlice())) pterm.Info.Println("Build command:", compiler, commandPrettifier(commands.AsSlice()))
cmd.Stdout = os.Stdout cmd.Stdout = os.Stdout
} }
// Set the directory // Set the directory
@ -359,7 +360,7 @@ func (b *BaseBuilder) CompileProject(options *Options) error {
}) })
if verbose { if verbose {
println(" Environment:", strings.Join(cmd.Env, " ")) printBulletPoint("Environment:", strings.Join(cmd.Env, " "))
} }
// Run command // Run command
@ -373,7 +374,7 @@ func (b *BaseBuilder) CompileProject(options *Options) error {
stdErr := string(output) stdErr := string(output)
if strings.Contains(err.Error(), "ld: framework not found UniformTypeIdentifiers") || if strings.Contains(err.Error(), "ld: framework not found UniformTypeIdentifiers") ||
strings.Contains(stdErr, "ld: framework not found UniformTypeIdentifiers") { strings.Contains(stdErr, "ld: framework not found UniformTypeIdentifiers") {
println(` pterm.Warning.Println(`
NOTE: It would appear that you do not have the latest Xcode cli tools installed. NOTE: It would appear that you do not have the latest Xcode cli tools installed.
Please reinstall by doing the following: Please reinstall by doing the following:
1. Remove the current installation located at "xcode-select -p", EG: sudo rm -rf /Library/Developer/CommandLineTools 1. Remove the current installation located at "xcode-select -p", EG: sudo rm -rf /Library/Developer/CommandLineTools
@ -388,11 +389,11 @@ Please reinstall by doing the following:
return nil return nil
} }
fmt.Printf("Compressing application: ") printBulletPoint("Compressing application: ")
// Do we have upx installed? // Do we have upx installed?
if !shell.CommandExists("upx") { if !shell.CommandExists("upx") {
println("Warning: Cannot compress binary: upx not found") pterm.Warning.Println("Warning: Cannot compress binary: upx not found")
return nil return nil
} }
@ -404,16 +405,16 @@ Please reinstall by doing the following:
} }
if verbose { if verbose {
println("upx", strings.Join(args, " ")) pterm.Info.Println("upx", strings.Join(args, " "))
} }
output, err := exec.Command("upx", args...).Output() output, err := exec.Command("upx", args...).Output()
if err != nil { if err != nil {
return errors.Wrap(err, "Error during compression:") return errors.Wrap(err, "Error during compression:")
} }
println("Done.") pterm.Println("Done.")
if verbose { if verbose {
println(string(output)) pterm.Info.Println(string(output))
} }
return nil return nil
@ -488,7 +489,7 @@ func (b *BaseBuilder) NpmInstallUsingCommand(sourceDir string, installCommand st
// Shortcut installation // Shortcut installation
if install == false { if install == false {
if verbose { if verbose {
println("Skipping npm install") pterm.Println("Skipping npm install")
} }
return nil return nil
} }
@ -498,10 +499,10 @@ func (b *BaseBuilder) NpmInstallUsingCommand(sourceDir string, installCommand st
stdout, stderr, err := shell.RunCommand(sourceDir, cmd[0], cmd[1:]...) stdout, stderr, err := shell.RunCommand(sourceDir, cmd[0], cmd[1:]...)
if verbose || err != nil { if verbose || err != nil {
for _, l := range strings.Split(stdout, "\n") { for _, l := range strings.Split(stdout, "\n") {
fmt.Printf(" %s\n", l) pterm.Printf(" %s\n", l)
} }
for _, l := range strings.Split(stderr, "\n") { for _, l := range strings.Split(stderr, "\n") {
fmt.Printf(" %s\n", l) pterm.Printf(" %s\n", l)
} }
} }
@ -513,10 +514,10 @@ func (b *BaseBuilder) NpmRun(projectDir, buildTarget string, verbose bool) error
stdout, stderr, err := shell.RunCommand(projectDir, "npm", "run", buildTarget) stdout, stderr, err := shell.RunCommand(projectDir, "npm", "run", buildTarget)
if verbose || err != nil { if verbose || err != nil {
for _, l := range strings.Split(stdout, "\n") { for _, l := range strings.Split(stdout, "\n") {
fmt.Printf(" %s\n", l) pterm.Printf(" %s\n", l)
} }
for _, l := range strings.Split(stderr, "\n") { for _, l := range strings.Split(stderr, "\n") {
fmt.Printf(" %s\n", l) pterm.Printf(" %s\n", l)
} }
} }
return err return err
@ -532,10 +533,10 @@ func (b *BaseBuilder) NpmRunWithEnvironment(projectDir, buildTarget string, verb
err := cmd.Run() err := cmd.Run()
if verbose || err != nil { if verbose || err != nil {
for _, l := range strings.Split(stdo.String(), "\n") { for _, l := range strings.Split(stdo.String(), "\n") {
fmt.Printf(" %s\n", l) pterm.Printf(" %s\n", l)
} }
for _, l := range strings.Split(stde.String(), "\n") { for _, l := range strings.Split(stde.String(), "\n") {
fmt.Printf(" %s\n", l) pterm.Printf(" %s\n", l)
} }
} }
return err return err
@ -558,13 +559,13 @@ func (b *BaseBuilder) BuildFrontend(outputLogger *clilogger.CLILogger) error {
} }
if installCommand == "" { if installCommand == "" {
// No - don't install // No - don't install
outputLogger.Println(" - No Install command. Skipping.") printBulletPoint("No Install command. Skipping.")
} else { } else {
// Do install if needed // Do install if needed
outputLogger.Print(" - Installing frontend dependencies: ") printBulletPoint("Installing frontend dependencies: ")
if verbose { if verbose {
outputLogger.Println("") pterm.Println("")
outputLogger.Println(" Install command: '" + installCommand + "'") pterm.Info.Println("Install command: '" + installCommand + "'")
} }
if err := b.NpmInstallUsingCommand(frontendDir, installCommand, verbose); err != nil { if err := b.NpmInstallUsingCommand(frontendDir, installCommand, verbose); err != nil {
return err return err
@ -578,31 +579,31 @@ func (b *BaseBuilder) BuildFrontend(outputLogger *clilogger.CLILogger) error {
buildCommand = b.projectData.GetDevBuildCommand() buildCommand = b.projectData.GetDevBuildCommand()
} }
if buildCommand == "" { if buildCommand == "" {
outputLogger.Println(" - No Build command. Skipping.") printBulletPoint("No Build command. Skipping.")
// No - ignore // No - ignore
return nil return nil
} }
outputLogger.Print(" - Compiling frontend: ") printBulletPoint("Compiling frontend: ")
cmd := strings.Split(buildCommand, " ") cmd := strings.Split(buildCommand, " ")
if verbose { if verbose {
outputLogger.Println("") pterm.Println("")
outputLogger.Println(" Build command: '" + buildCommand + "'") pterm.Info.Println("Build command: '" + buildCommand + "'")
} }
stdout, stderr, err := shell.RunCommand(frontendDir, cmd[0], cmd[1:]...) stdout, stderr, err := shell.RunCommand(frontendDir, cmd[0], cmd[1:]...)
if verbose || err != nil { if verbose || err != nil {
for _, l := range strings.Split(stdout, "\n") { for _, l := range strings.Split(stdout, "\n") {
fmt.Printf(" %s\n", l) pterm.Printf(" %s\n", l)
} }
for _, l := range strings.Split(stderr, "\n") { for _, l := range strings.Split(stderr, "\n") {
fmt.Printf(" %s\n", l) pterm.Printf(" %s\n", l)
} }
} }
if err != nil { if err != nil {
return err return err
} }
outputLogger.Println("Done.") pterm.Println("Done.")
return nil return nil
} }

View File

@ -2,14 +2,13 @@ package build
import ( import (
"fmt" "fmt"
"log" "github.com/pterm/pterm"
"os" "os"
"path/filepath" "path/filepath"
"runtime" "runtime"
"strings" "strings"
"github.com/samber/lo" "github.com/samber/lo"
"github.com/wailsapp/wails/v2/internal/colour"
"github.com/wailsapp/wails/v2/internal/staticanalysis" "github.com/wailsapp/wails/v2/internal/staticanalysis"
"github.com/wailsapp/wails/v2/pkg/commands/bindings" "github.com/wailsapp/wails/v2/pkg/commands/bindings"
@ -185,14 +184,39 @@ func CreateEmbedDirectories(cwd string, buildOptions *Options) error {
} }
func fatal(message string) {
printer := pterm.PrefixPrinter{
MessageStyle: &pterm.ThemeDefault.FatalMessageStyle,
Prefix: pterm.Prefix{
Style: &pterm.ThemeDefault.FatalPrefixStyle,
Text: " FATAL ",
},
}
printer.Println(message)
os.Exit(1)
}
func printBulletPoint(text string, args ...any) {
item := pterm.BulletListItem{
Level: 2,
Text: text,
}
t, err := pterm.DefaultBulletList.WithItems([]pterm.BulletListItem{item}).Srender()
if err != nil {
fatal(err.Error())
}
t = strings.Trim(t, "\n\r")
pterm.Printf(t, args...)
}
func GenerateBindings(buildOptions *Options) error { func GenerateBindings(buildOptions *Options) error {
obfuscated := buildOptions.Obfuscated obfuscated := buildOptions.Obfuscated
if obfuscated { if obfuscated {
buildOptions.Logger.Print(" - Generating obfuscated bindings: ") printBulletPoint("Generating obfuscated bindings: ")
buildOptions.UserTags = append(buildOptions.UserTags, "obfuscated") buildOptions.UserTags = append(buildOptions.UserTags, "obfuscated")
} else { } else {
buildOptions.Logger.Print(" - Generating bindings: ") printBulletPoint("Generating bindings: ")
} }
// Generate Bindings // Generate Bindings
@ -207,39 +231,36 @@ func GenerateBindings(buildOptions *Options) error {
} }
if buildOptions.Verbosity == VERBOSE { if buildOptions.Verbosity == VERBOSE {
buildOptions.Logger.Println(output) pterm.Info.Println(output)
} }
buildOptions.Logger.Println("Done.") pterm.Println("Done.")
return nil return nil
} }
func execBuildApplication(builder Builder, options *Options) (string, error) { func execBuildApplication(builder Builder, options *Options) (string, error) {
// Extract logger
outputLogger := options.Logger
// If we are building for windows, we will need to generate the asset bundle before // If we are building for windows, we will need to generate the asset bundle before
// compilation. This will be a .syso file in the project root // compilation. This will be a .syso file in the project root
if options.Pack && options.Platform == "windows" { if options.Pack && options.Platform == "windows" {
outputLogger.Print(" - Generating bundle assets: ") printBulletPoint("Generating application assets: ")
err := packageApplicationForWindows(options) err := packageApplicationForWindows(options)
if err != nil { if err != nil {
return "", err return "", err
} }
outputLogger.Println("Done.") pterm.Println("Done.")
// When we finish, we will want to remove the syso file // When we finish, we will want to remove the syso file
defer func() { defer func() {
err := os.Remove(filepath.Join(options.ProjectData.Path, options.ProjectData.Name+"-res.syso")) err := os.Remove(filepath.Join(options.ProjectData.Path, options.ProjectData.Name+"-res.syso"))
if err != nil { if err != nil {
log.Fatal(err) fatal(err.Error())
} }
}() }()
} }
// Compile the application // Compile the application
outputLogger.Print(" - Compiling application: ") printBulletPoint("Compiling application: ")
if options.Platform == "darwin" && options.Arch == "universal" { if options.Platform == "darwin" && options.Arch == "universal" {
outputFile := builder.OutputFilename(options) outputFile := builder.OutputFilename(options)
@ -251,7 +272,7 @@ func execBuildApplication(builder Builder, options *Options) (string, error) {
options.OutputFile = amd64Filename options.OutputFile = amd64Filename
options.CleanBinDirectory = false options.CleanBinDirectory = false
if options.Verbosity == VERBOSE { if options.Verbosity == VERBOSE {
outputLogger.Println("\nBuilding AMD64 Target: %s", filepath.Join(options.BinDirectory, options.OutputFile)) pterm.Println("Building AMD64 Target: " + filepath.Join(options.BinDirectory, options.OutputFile))
} }
err := builder.CompileProject(options) err := builder.CompileProject(options)
if err != nil { if err != nil {
@ -262,7 +283,7 @@ func execBuildApplication(builder Builder, options *Options) (string, error) {
options.OutputFile = arm64Filename options.OutputFile = arm64Filename
options.CleanBinDirectory = false options.CleanBinDirectory = false
if options.Verbosity == VERBOSE { if options.Verbosity == VERBOSE {
outputLogger.Println("Building ARM64 Target: %s", filepath.Join(options.BinDirectory, options.OutputFile)) pterm.Println("Building ARM64 Target: " + filepath.Join(options.BinDirectory, options.OutputFile))
} }
err = builder.CompileProject(options) err = builder.CompileProject(options)
@ -271,7 +292,7 @@ func execBuildApplication(builder Builder, options *Options) (string, error) {
} }
// Run lipo // Run lipo
if options.Verbosity == VERBOSE { if options.Verbosity == VERBOSE {
outputLogger.Println(" Running lipo: lipo -create -output %s %s %s", outputFile, amd64Filename, arm64Filename) pterm.Println("Running lipo: lipo -create -output %s %s %s", outputFile, amd64Filename, arm64Filename)
} }
_, stderr, err := shell.RunCommand(options.BinDirectory, "lipo", "-create", "-output", outputFile, amd64Filename, arm64Filename) _, stderr, err := shell.RunCommand(options.BinDirectory, "lipo", "-create", "-output", outputFile, amd64Filename, arm64Filename)
if err != nil { if err != nil {
@ -295,19 +316,19 @@ func execBuildApplication(builder Builder, options *Options) (string, error) {
} }
} }
outputLogger.Println("Done.") pterm.Println("Done.")
// Do we need to pack the app for non-windows? // Do we need to pack the app for non-windows?
if options.Pack && options.Platform != "windows" { if options.Pack && options.Platform != "windows" {
outputLogger.Print(" - Packaging application: ") printBulletPoint("Packaging application: ")
// TODO: Allow cross platform build // TODO: Allow cross platform build
err := packageProject(options, runtime.GOOS) err := packageProject(options, runtime.GOOS)
if err != nil { if err != nil {
return "", err return "", err
} }
outputLogger.Println("Done.") pterm.Println("Done.")
} }
if options.Platform == "windows" { if options.Platform == "windows" {
@ -321,7 +342,7 @@ func execBuildApplication(builder Builder, options *Options) (string, error) {
tags = append(tags, expWebView2Loader) tags = append(tags, expWebView2Loader)
message = fmt.Sprintf("An experimental Go native WebView2Loader is available. We would love to hear your feedback about it and invite you to test it by building with `-tags %s`", strings.Join(tags, ",")) message = fmt.Sprintf("An experimental Go native WebView2Loader is available. We would love to hear your feedback about it and invite you to test it by building with `-tags %s`", strings.Join(tags, ","))
} }
println(colour.Green(" - " + message)) pterm.Info.Println(message)
} }
return options.CompiledBinary, nil return options.CompiledBinary, nil
@ -358,13 +379,13 @@ func executeBuildHook(outputLogger *clilogger.CLILogger, options *Options, hookI
// The hook is for host platform // The hook is for host platform
} else { } else {
// Skip a hook which is not native // Skip a hook which is not native
outputLogger.Println(" - Non native build hook '%s': Skipping.", hookIdentifier) printBulletPoint(fmt.Sprintf("Non native build hook '%s': Skipping.", hookIdentifier))
return nil return nil
} }
} }
} }
outputLogger.Print(" - Executing %s build hook '%s': ", hookName, hookIdentifier) printBulletPoint("Executing %s build hook '%s': ", hookName, hookIdentifier)
args := strings.Split(buildHook, " ") args := strings.Split(buildHook, " ")
for i, arg := range args { for i, arg := range args {
newArg := argReplacements[arg] newArg := argReplacements[arg]
@ -375,17 +396,17 @@ func executeBuildHook(outputLogger *clilogger.CLILogger, options *Options, hookI
} }
if options.Verbosity == VERBOSE { if options.Verbosity == VERBOSE {
outputLogger.Println("%s", strings.Join(args, " ")) pterm.Info.Println("%s", strings.Join(args, " "))
} }
stdout, stderr, err := shell.RunCommand(options.BinDirectory, args[0], args[1:]...) stdout, stderr, err := shell.RunCommand(options.BinDirectory, args[0], args[1:]...)
if options.Verbosity == VERBOSE { if options.Verbosity == VERBOSE {
println(stdout) pterm.Info.Println(stdout)
} }
if err != nil { if err != nil {
return fmt.Errorf("%s - %s", err.Error(), stderr) return fmt.Errorf("%s - %s", err.Error(), stderr)
} }
outputLogger.Println("Done.") pterm.Println("Done.")
return nil return nil
} }

View File

@ -47,7 +47,7 @@ func GenerateNSISInstaller(options *Options, amd64Binary string, arm64Binary str
} }
if err := webview2runtime.WriteInstallerToFile(webviewSetup); err != nil { if err := webview2runtime.WriteInstallerToFile(webviewSetup); err != nil {
return fmt.Errorf("Unable to write Webview2 Bootstrapper Setup: %w", err) return fmt.Errorf("Unable to write WebView2 Bootstrapper Setup: %w", err)
} }
if !shell.CommandExists("makensis") { if !shell.CommandExists("makensis") {

View File

@ -17,7 +17,6 @@ import (
"github.com/leaanthony/debme" "github.com/leaanthony/debme"
"github.com/leaanthony/gosod" "github.com/leaanthony/gosod"
"github.com/olekukonko/tablewriter"
"github.com/wailsapp/wails/v2/internal/fs" "github.com/wailsapp/wails/v2/internal/fs"
"github.com/wailsapp/wails/v2/pkg/clilogger" "github.com/wailsapp/wails/v2/pkg/clilogger"
) )
@ -313,33 +312,6 @@ func gitclone(options *Options) (string, error) {
} }
// OutputList prints the list of available tempaltes to the given logger
func OutputList(logger *clilogger.CLILogger) error {
templates, err := List()
if err != nil {
return err
}
table := tablewriter.NewWriter(logger.Writer)
table.SetHeader([]string{"Template", "Short Name", "Description"})
table.SetAutoWrapText(false)
table.SetAutoFormatHeaders(true)
table.SetHeaderAlignment(tablewriter.ALIGN_LEFT)
table.SetAlignment(tablewriter.ALIGN_LEFT)
table.SetCenterSeparator("")
table.SetColumnSeparator("")
table.SetRowSeparator("")
table.SetHeaderLine(false)
table.SetBorder(false)
table.SetTablePadding("\t") // pad with tabs
table.SetNoWhiteSpace(true)
for _, template := range templates {
table.Append([]string{template.Name, template.ShortName, template.Description})
}
table.Render()
return nil
}
func generateIDEFiles(options *Options) error { func generateIDEFiles(options *Options) error {
switch options.IDE { switch options.IDE {