mirror of
https://github.com/wailsapp/wails.git
synced 2025-05-05 02:40:32 +08:00
374 lines
8.2 KiB
Go
374 lines
8.2 KiB
Go
package templates
|
|
|
|
import (
|
|
"embed"
|
|
"encoding/json"
|
|
"fmt"
|
|
"github.com/go-git/go-git/v5"
|
|
"github.com/go-git/go-git/v5/plumbing"
|
|
"github.com/pkg/errors"
|
|
"github.com/pterm/pterm"
|
|
"github.com/wailsapp/wails/v3/internal/debug"
|
|
"io/fs"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/wailsapp/wails/v3/internal/flags"
|
|
|
|
"github.com/leaanthony/gosod"
|
|
|
|
"github.com/samber/lo"
|
|
)
|
|
|
|
//go:embed lit
|
|
var lit embed.FS
|
|
|
|
//go:embed lit-ts
|
|
var litTS embed.FS
|
|
|
|
//go:embed vue
|
|
var vue embed.FS
|
|
|
|
//go:embed vue-ts
|
|
var vueTS embed.FS
|
|
|
|
//go:embed react
|
|
var react embed.FS
|
|
|
|
//go:embed react-ts
|
|
var reactTS embed.FS
|
|
|
|
//go:embed react-swc
|
|
var reactSWC embed.FS
|
|
|
|
//go:embed react-swc-ts
|
|
var reactSWCTS embed.FS
|
|
|
|
//go:embed svelte
|
|
var svelte embed.FS
|
|
|
|
//go:embed svelte-ts
|
|
var svelteTS embed.FS
|
|
|
|
//go:embed preact
|
|
var preact embed.FS
|
|
|
|
//go:embed preact-ts
|
|
var preactTS embed.FS
|
|
|
|
//go:embed vanilla
|
|
var vanilla embed.FS
|
|
|
|
//go:embed vanilla-ts
|
|
var vanillaTS embed.FS
|
|
|
|
type TemplateData struct {
|
|
Name string
|
|
Description string
|
|
FS embed.FS
|
|
}
|
|
|
|
var defaultTemplates = []TemplateData{
|
|
{
|
|
Name: "lit",
|
|
Description: "Template using Lit Web Components: https://lit.dev",
|
|
FS: lit,
|
|
},
|
|
{
|
|
Name: "lit-ts",
|
|
Description: "Template using Lit Web Components (TypeScript) : https://lit.dev",
|
|
FS: litTS,
|
|
},
|
|
{
|
|
Name: "vue",
|
|
Description: "Template using Vue: https://vuejs.org",
|
|
FS: vue,
|
|
},
|
|
{
|
|
Name: "vue-ts",
|
|
Description: "Template using Vue (TypeScript): https://vuejs.org",
|
|
FS: vueTS,
|
|
},
|
|
{
|
|
Name: "react",
|
|
Description: "Template using React: https://reactjs.org",
|
|
FS: react,
|
|
},
|
|
{
|
|
Name: "react-ts",
|
|
Description: "Template using React (TypeScript): https://reactjs.org",
|
|
FS: reactTS,
|
|
},
|
|
{
|
|
Name: "react-swc",
|
|
Description: "Template using React with SWC: https://reactjs.org & https://swc.rs",
|
|
FS: reactSWC,
|
|
},
|
|
{
|
|
Name: "react-swc-ts",
|
|
Description: "Template using React with SWC (TypeScript): https://reactjs.org & https://swc.rs",
|
|
FS: reactSWCTS,
|
|
},
|
|
{
|
|
Name: "svelte",
|
|
Description: "Template using Svelte: https://svelte.dev",
|
|
FS: svelte,
|
|
},
|
|
{
|
|
Name: "svelte-ts",
|
|
Description: "Template using Svelte (TypeScript): https://svelte.dev",
|
|
FS: svelteTS,
|
|
},
|
|
{
|
|
Name: "preact",
|
|
Description: "Template using Preact: https://preactjs.com",
|
|
FS: preact,
|
|
},
|
|
{
|
|
Name: "preact-ts",
|
|
Description: "Template using Preact (TypeScript): https://preactjs.com",
|
|
FS: preactTS,
|
|
},
|
|
{
|
|
Name: "vanilla",
|
|
Description: "Template using Vanilla JS",
|
|
FS: vanilla,
|
|
},
|
|
{
|
|
Name: "vanilla-ts",
|
|
Description: "Template using Vanilla JS (TypeScript)",
|
|
FS: vanillaTS,
|
|
},
|
|
}
|
|
|
|
func ValidTemplateName(name string) bool {
|
|
return lo.ContainsBy(defaultTemplates, func(template TemplateData) bool {
|
|
return template.Name == name
|
|
})
|
|
}
|
|
|
|
func GetDefaultTemplates() []TemplateData {
|
|
return defaultTemplates
|
|
}
|
|
|
|
type TemplateOptions struct {
|
|
*flags.Init
|
|
LocalModulePath string
|
|
}
|
|
|
|
func getInternalTemplate(templateName string) (*Template, error) {
|
|
templateData, found := lo.Find(defaultTemplates, func(template TemplateData) bool {
|
|
return template.Name == templateName
|
|
})
|
|
|
|
if !found {
|
|
return nil, nil
|
|
}
|
|
|
|
template, err := parseTemplate(templateData.FS, templateData.Name)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &template, nil
|
|
}
|
|
|
|
func getLocalTemplate(templateName string) (*Template, error) {
|
|
var template Template
|
|
var err error
|
|
_, err = os.Stat(templateName)
|
|
if err != nil {
|
|
return nil, nil
|
|
}
|
|
|
|
template, err = parseTemplate(os.DirFS(templateName), templateName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &template, nil
|
|
}
|
|
|
|
// Template holds data relating to a template including the metadata stored in template.yaml
|
|
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"`
|
|
Version int8 `json:"version"`
|
|
|
|
// Other data
|
|
FS fs.FS `json:"-"`
|
|
}
|
|
|
|
func parseTemplate(template fs.FS, templateName string) (Template, error) {
|
|
var result Template
|
|
jsonFile := "template.json"
|
|
if templateName != "" {
|
|
jsonFile = templateName + "/template.json"
|
|
}
|
|
data, err := fs.ReadFile(template, jsonFile)
|
|
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
|
|
|
|
// We need to do a version check here
|
|
if result.Version == 0 {
|
|
return result, fmt.Errorf("template not supported by wails 3. This template is probably for wails 2")
|
|
}
|
|
if result.Version != 3 {
|
|
return result, fmt.Errorf("template version %d is not supported by wails 3. Ensure 'version' is set to 3 in the `template.json` file", result.Version)
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
// Clones the given uri and returns the temporary cloned directory
|
|
func gitclone(uri string) (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(uri, "@")
|
|
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 getRemoteTemplate(uri string) (template *Template, err error) {
|
|
// git clone to temporary dir
|
|
var tempDir string
|
|
tempDir, err = gitclone(uri)
|
|
|
|
defer func(path string) {
|
|
_ = os.RemoveAll(path)
|
|
}(tempDir)
|
|
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
// Remove the .git directory
|
|
err = os.RemoveAll(filepath.Join(tempDir, ".git"))
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
templateFS := os.DirFS(tempDir)
|
|
var parsedTemplate Template
|
|
parsedTemplate, err = parseTemplate(templateFS, "")
|
|
if err != nil {
|
|
return
|
|
}
|
|
return &parsedTemplate, nil
|
|
}
|
|
|
|
func Install(options *flags.Init) error {
|
|
|
|
templateData := TemplateOptions{
|
|
options,
|
|
filepath.FromSlash(debug.LocalModulePath + "/"),
|
|
}
|
|
|
|
defer func() {
|
|
// if `template.json` exists, remove it
|
|
_ = os.Remove(filepath.Join(templateData.ProjectDir, "template.json"))
|
|
}()
|
|
|
|
var err error
|
|
var template *Template
|
|
template, err = getInternalTemplate(options.TemplateName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if template == nil {
|
|
template, err = getLocalTemplate(options.TemplateName)
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if template == nil {
|
|
template, err = getRemoteTemplate(options.TemplateName)
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if template == nil {
|
|
return fmt.Errorf("invalid template name: %s. Use -l flag to view available templates or use a valid filepath / url to a template", options.TemplateName)
|
|
}
|
|
|
|
if options.ProjectDir == "." || options.ProjectDir == "" {
|
|
templateData.ProjectDir = lo.Must(os.Getwd())
|
|
}
|
|
templateData.ProjectDir = filepath.Join(options.ProjectDir, options.ProjectName)
|
|
|
|
// If project directory already exists and is not empty, error
|
|
if _, err := os.Stat(templateData.ProjectDir); !os.IsNotExist(err) {
|
|
// Check if the directory is empty
|
|
files := lo.Must(os.ReadDir(templateData.ProjectDir))
|
|
if len(files) > 0 {
|
|
return fmt.Errorf("project directory '%s' already exists and is not empty", templateData.ProjectDir)
|
|
}
|
|
}
|
|
|
|
pterm.Printf("Creating project\n")
|
|
pterm.Printf("----------------\n\n")
|
|
table := pterm.TableData{
|
|
{"Project Name", options.ProjectName},
|
|
{"Project Directory", filepath.FromSlash(options.ProjectDir)},
|
|
{"Template", template.Name},
|
|
{"Template Source", template.HelpURL},
|
|
}
|
|
err = pterm.DefaultTable.WithData(table).Render()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
tfs, err := fs.Sub(template.FS, options.TemplateName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = gosod.New(tfs).Extract(options.ProjectDir, templateData)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Change to project directory
|
|
err = os.Chdir(templateData.ProjectDir)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// Run `go mod tidy`
|
|
err = exec.Command("go", "mod", "tidy").Run()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
pterm.Printf("\nProject '%s' created successfully.\n", options.ProjectName)
|
|
|
|
return nil
|
|
|
|
}
|