mirror of
https://github.com/wailsapp/wails.git
synced 2025-05-03 00:41:59 +08:00
399 lines
9.4 KiB
Go
399 lines
9.4 KiB
Go
package templates
|
|
|
|
import (
|
|
"embed"
|
|
"encoding/json"
|
|
"fmt"
|
|
gofs "io/fs"
|
|
"log"
|
|
"os"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strings"
|
|
|
|
"github.com/go-git/go-git/v5"
|
|
"github.com/go-git/go-git/v5/plumbing"
|
|
"github.com/pkg/errors"
|
|
|
|
"github.com/leaanthony/debme"
|
|
"github.com/leaanthony/gosod"
|
|
"github.com/wailsapp/wails/v2/internal/fs"
|
|
"github.com/wailsapp/wails/v2/pkg/clilogger"
|
|
)
|
|
|
|
//go:embed all:templates
|
|
var templates embed.FS
|
|
|
|
//go:embed all:ides/*
|
|
var ides embed.FS
|
|
|
|
// Cahce for the templates
|
|
// We use this because we need different views of the same data
|
|
var templateCache []Template = nil
|
|
|
|
// Data contains the data we wish to embed during template installation
|
|
type Data struct {
|
|
ProjectName string
|
|
BinaryName string
|
|
WailsVersion string
|
|
NPMProjectName string
|
|
AuthorName string
|
|
AuthorEmail string
|
|
AuthorNameAndEmail string
|
|
WailsDirectory string
|
|
GoSDKPath string
|
|
WindowsFlags string
|
|
CGOEnabled string
|
|
OutputFile string
|
|
}
|
|
|
|
// Options for installing a template
|
|
type Options struct {
|
|
ProjectName string
|
|
TemplateName string
|
|
BinaryName string
|
|
TargetDir string
|
|
Logger *clilogger.CLILogger
|
|
PathToDesktopBinary string
|
|
PathToServerBinary string
|
|
InitGit bool
|
|
AuthorName string
|
|
AuthorEmail string
|
|
IDE string
|
|
ProjectNameFilename string // The project name but as a valid filename
|
|
WailsVersion string
|
|
GoSDKPath string
|
|
WindowsFlags string
|
|
CGOEnabled string
|
|
CGOLDFlags string
|
|
OutputFile string
|
|
}
|
|
|
|
// Template holds data relating to a template
|
|
// including the metadata stored in template.json
|
|
type Template struct {
|
|
// Template details
|
|
Name string `json:"name"`
|
|
ShortName string `json:"shortname"`
|
|
Author string `json:"author"`
|
|
Description string `json:"description"`
|
|
HelpURL string `json:"helpurl"`
|
|
|
|
// Other data
|
|
FS gofs.FS `json:"-"`
|
|
}
|
|
|
|
func parseTemplate(template gofs.FS) (Template, error) {
|
|
var result Template
|
|
data, err := gofs.ReadFile(template, "template.json")
|
|
if err != nil {
|
|
return result, errors.Wrap(err, "Error parsing template")
|
|
}
|
|
err = json.Unmarshal(data, &result)
|
|
if err != nil {
|
|
return result, err
|
|
}
|
|
result.FS = template
|
|
return result, nil
|
|
}
|
|
|
|
// List returns the list of available templates
|
|
func List() ([]Template, error) {
|
|
// If the cache isn't loaded, load it
|
|
if templateCache == nil {
|
|
err := loadTemplateCache()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
return templateCache, nil
|
|
}
|
|
|
|
// getTemplateByShortname returns the template with the given short name
|
|
func getTemplateByShortname(shortname string) (Template, error) {
|
|
var result Template
|
|
|
|
// If the cache isn't loaded, load it
|
|
if templateCache == nil {
|
|
err := loadTemplateCache()
|
|
if err != nil {
|
|
return result, err
|
|
}
|
|
}
|
|
|
|
for _, template := range templateCache {
|
|
if template.ShortName == shortname {
|
|
return template, nil
|
|
}
|
|
}
|
|
|
|
return result, fmt.Errorf("shortname '%s' is not a valid template shortname", shortname)
|
|
}
|
|
|
|
// Loads the template cache
|
|
func loadTemplateCache() error {
|
|
templatesFS, err := debme.FS(templates, "templates")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Get directories
|
|
files, err := templatesFS.ReadDir(".")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Reset cache
|
|
templateCache = []Template{}
|
|
|
|
for _, file := range files {
|
|
if file.IsDir() {
|
|
templateFS, err := templatesFS.FS(file.Name())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
template, err := parseTemplate(templateFS)
|
|
if err != nil {
|
|
// Cannot parse this template, continue
|
|
continue
|
|
}
|
|
templateCache = append(templateCache, template)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Install the given template. Returns true if the template is remote.
|
|
func Install(options *Options) (bool, *Template, error) {
|
|
// Get cwd
|
|
cwd, err := os.Getwd()
|
|
if err != nil {
|
|
return false, nil, err
|
|
}
|
|
|
|
// Did the user want to install in current directory?
|
|
if options.TargetDir == "" {
|
|
options.TargetDir = filepath.Join(cwd, options.ProjectName)
|
|
if fs.DirExists(options.TargetDir) {
|
|
return false, nil, fmt.Errorf("cannot create project directory. Dir exists: %s", options.TargetDir)
|
|
}
|
|
} else {
|
|
// Get the absolute path of the given directory
|
|
targetDir, err := filepath.Abs(options.TargetDir)
|
|
if err != nil {
|
|
return false, nil, err
|
|
}
|
|
options.TargetDir = targetDir
|
|
if !fs.DirExists(options.TargetDir) {
|
|
err := fs.Mkdir(options.TargetDir)
|
|
if err != nil {
|
|
return false, nil, err
|
|
}
|
|
}
|
|
}
|
|
|
|
// Flag to indicate remote template
|
|
remoteTemplate := false
|
|
|
|
// Is this a shortname?
|
|
template, err := getTemplateByShortname(options.TemplateName)
|
|
if err != nil {
|
|
// Is this a filepath?
|
|
templatePath, err := filepath.Abs(options.TemplateName)
|
|
if fs.DirExists(templatePath) {
|
|
templateFS := os.DirFS(templatePath)
|
|
template, err = parseTemplate(templateFS)
|
|
if err != nil {
|
|
return false, nil, errors.Wrap(err, "Error installing template")
|
|
}
|
|
} else {
|
|
// git clone to temporary dir
|
|
tempdir, err := gitclone(options)
|
|
defer func(path string) {
|
|
err := os.RemoveAll(path)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
}(tempdir)
|
|
if err != nil {
|
|
return false, nil, err
|
|
}
|
|
// Remove the .git directory
|
|
err = os.RemoveAll(filepath.Join(tempdir, ".git"))
|
|
if err != nil {
|
|
return false, nil, err
|
|
}
|
|
|
|
templateFS := os.DirFS(tempdir)
|
|
template, err = parseTemplate(templateFS)
|
|
if err != nil {
|
|
return false, nil, err
|
|
}
|
|
remoteTemplate = true
|
|
}
|
|
}
|
|
|
|
// Use Gosod to install the template
|
|
installer := gosod.New(template.FS)
|
|
|
|
// Ignore template.json files
|
|
installer.IgnoreFile("template.json")
|
|
|
|
// Setup the data.
|
|
// We use the directory name for the binary name, like Go
|
|
BinaryName := filepath.Base(options.TargetDir)
|
|
NPMProjectName := strings.ToLower(strings.ReplaceAll(BinaryName, " ", ""))
|
|
localWailsDirectory := fs.RelativePath("../../../../../..")
|
|
|
|
templateData := &Data{
|
|
ProjectName: options.ProjectName,
|
|
BinaryName: filepath.Base(options.TargetDir),
|
|
NPMProjectName: NPMProjectName,
|
|
WailsDirectory: localWailsDirectory,
|
|
AuthorEmail: options.AuthorEmail,
|
|
AuthorName: options.AuthorName,
|
|
WailsVersion: options.WailsVersion,
|
|
GoSDKPath: options.GoSDKPath,
|
|
}
|
|
|
|
// Create a formatted name and email combo.
|
|
if options.AuthorName != "" {
|
|
templateData.AuthorNameAndEmail = options.AuthorName + " "
|
|
}
|
|
if options.AuthorEmail != "" {
|
|
templateData.AuthorNameAndEmail += "<" + options.AuthorEmail + ">"
|
|
}
|
|
templateData.AuthorNameAndEmail = strings.TrimSpace(templateData.AuthorNameAndEmail)
|
|
|
|
installer.RenameFiles(map[string]string{
|
|
"gitignore.txt": ".gitignore",
|
|
})
|
|
|
|
// Extract the template
|
|
err = installer.Extract(options.TargetDir, templateData)
|
|
if err != nil {
|
|
return false, nil, err
|
|
}
|
|
|
|
err = generateIDEFiles(options)
|
|
if err != nil {
|
|
return false, nil, err
|
|
}
|
|
|
|
return remoteTemplate, &template, nil
|
|
}
|
|
|
|
// Clones the given uri and returns the temporary cloned directory
|
|
func gitclone(options *Options) (string, error) {
|
|
// Create temporary directory
|
|
dirname, err := os.MkdirTemp("", "wails-template-*")
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
// Parse remote template url and version number
|
|
templateInfo := strings.Split(options.TemplateName, "@")
|
|
cloneOption := &git.CloneOptions{
|
|
URL: templateInfo[0],
|
|
}
|
|
if len(templateInfo) > 1 {
|
|
cloneOption.ReferenceName = plumbing.NewTagReferenceName(templateInfo[1])
|
|
}
|
|
|
|
_, err = git.PlainClone(dirname, false, cloneOption)
|
|
|
|
return dirname, err
|
|
}
|
|
|
|
func generateIDEFiles(options *Options) error {
|
|
switch options.IDE {
|
|
case "vscode":
|
|
return generateVSCodeFiles(options)
|
|
case "goland":
|
|
return generateGolandFiles(options)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
type ideOptions struct {
|
|
name string
|
|
targetDir string
|
|
options *Options
|
|
renameFiles map[string]string
|
|
ignoredFiles []string
|
|
}
|
|
|
|
func generateGolandFiles(options *Options) error {
|
|
ideoptions := ideOptions{
|
|
name: "goland",
|
|
targetDir: filepath.Join(options.TargetDir, ".idea"),
|
|
options: options,
|
|
renameFiles: map[string]string{
|
|
"projectname.iml": options.ProjectNameFilename + ".iml",
|
|
"gitignore.txt": ".gitignore",
|
|
"name": ".name",
|
|
},
|
|
}
|
|
if !options.InitGit {
|
|
ideoptions.ignoredFiles = []string{"vcs.xml"}
|
|
}
|
|
err := installIDEFiles(ideoptions)
|
|
if err != nil {
|
|
return errors.Wrap(err, "generating Goland IDE files")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func generateVSCodeFiles(options *Options) error {
|
|
ideoptions := ideOptions{
|
|
name: "vscode",
|
|
targetDir: filepath.Join(options.TargetDir, ".vscode"),
|
|
options: options,
|
|
}
|
|
return installIDEFiles(ideoptions)
|
|
}
|
|
|
|
func installIDEFiles(o ideOptions) error {
|
|
source, err := debme.FS(ides, "ides/"+o.name)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Use gosod to install the template
|
|
installer := gosod.New(source)
|
|
|
|
if o.renameFiles != nil {
|
|
installer.RenameFiles(o.renameFiles)
|
|
}
|
|
|
|
for _, ignoreFile := range o.ignoredFiles {
|
|
installer.IgnoreFile(ignoreFile)
|
|
}
|
|
|
|
binaryName := filepath.Base(o.options.TargetDir)
|
|
o.options.WindowsFlags = ""
|
|
o.options.CGOEnabled = "1"
|
|
|
|
switch runtime.GOOS {
|
|
case "windows":
|
|
binaryName += ".exe"
|
|
o.options.WindowsFlags = " -H windowsgui"
|
|
o.options.CGOEnabled = "0"
|
|
case "darwin":
|
|
o.options.CGOLDFlags = "-framework UniformTypeIdentifiers"
|
|
}
|
|
|
|
o.options.PathToDesktopBinary = filepath.ToSlash(filepath.Join("build", "bin", binaryName))
|
|
|
|
err = installer.Extract(o.targetDir, o.options)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|