diff --git a/cmd/cli.go b/cmd/cli.go index a28a38c4d..d52a44513 100644 --- a/cmd/cli.go +++ b/cmd/cli.go @@ -96,6 +96,7 @@ type Command struct { flagCount int log *Logger helpFlag bool + hidden bool } // NewCommand creates a new Command @@ -106,6 +107,7 @@ func NewCommand(name string, description string, app *Cli, parentCommandPath str SubCommandsMap: make(map[string]*Command), App: app, log: NewLogger(), + hidden: false, } // Set up command path @@ -212,6 +214,9 @@ func (c *Command) PrintHelp() { c.log.White("Available commands:") fmt.Println("") for _, subcommand := range c.SubCommands { + if subcommand.isHidden() { + continue + } spacer := strings.Repeat(" ", 3+c.longestSubcommand-len(subcommand.Name)) isDefault := "" if subcommand.isDefaultCommand() { @@ -237,6 +242,16 @@ func (c *Command) isDefaultCommand() bool { return c.App.defaultCommand == c } +// isHidden returns true if the command is a hidden command +func (c *Command) isHidden() bool { + return c.hidden +} + +// Hidden hides the command from the Help system +func (c *Command) Hidden() { + c.hidden = true +} + // Command - Defines a subcommand func (c *Command) Command(name, description string) *Command { result := NewCommand(name, description, c.App, c.CommandPath) diff --git a/cmd/fs.go b/cmd/fs.go index 37a638577..83c1b2e57 100644 --- a/cmd/fs.go +++ b/cmd/fs.go @@ -1,7 +1,9 @@ package cmd import ( + "bytes" "crypto/md5" + "encoding/json" "fmt" "io" "io/ioutil" @@ -174,6 +176,22 @@ func (fs *FSHelper) MkDir(dir string) error { return os.Mkdir(dir, 0700) } +// SaveAsJSON saves the JSON representation of the given data to the given filename +func (fs *FSHelper) SaveAsJSON(data interface{}, filename string) error { + + var buf bytes.Buffer + e := json.NewEncoder(&buf) + e.SetEscapeHTML(false) + e.SetIndent("", " ") + e.Encode(data) + + err := ioutil.WriteFile(filename, buf.Bytes(), 0755) + if err != nil { + return err + } + return nil +} + // LoadAsString will attempt to load the given file and return // its contents as a string func (fs *FSHelper) LoadAsString(filename string) (string, error) { diff --git a/cmd/system.go b/cmd/system.go index 311b8978a..af28058f6 100644 --- a/cmd/system.go +++ b/cmd/system.go @@ -68,6 +68,17 @@ func (s *SystemHelper) ConfigFileIsValid() bool { return err == nil } +// GetAuthor returns a formatted string of the user's name and email +func (s *SystemHelper) GetAuthor() (string, error) { + var config *SystemConfig + config, err := s.LoadConfig() + if err != nil { + return "", err + } + + return fmt.Sprintf("%s <%s>", config.Name, config.Email), nil +} + // BackupConfig attempts to backup the system config file func (s *SystemHelper) BackupConfig() (string, error) { now := strconv.FormatInt(time.Now().UTC().UnixNano(), 10) diff --git a/cmd/templates.go b/cmd/templates.go index f82fc8bee..4a609af4e 100644 --- a/cmd/templates.go +++ b/cmd/templates.go @@ -3,12 +3,14 @@ package cmd import ( "bytes" "encoding/json" + "fmt" "io/ioutil" "log" "path/filepath" "strings" "text/template" + "github.com/kennygrant/sanitize" "github.com/leaanthony/slicer" ) @@ -57,6 +59,36 @@ func NewTemplateHelper() *TemplateHelper { } } +// IsValidTemplate returns true if the given tempalte name resides on disk +func (t *TemplateHelper) IsValidTemplate(templateName string) bool { + pathToTemplate := filepath.Join(t.templateDir.fullPath, templateName) + return t.fs.DirExists(pathToTemplate) +} + +// SanitizeFilename sanitizes the given string to make a valid filename +func (t *TemplateHelper) SanitizeFilename(name string) string { + return sanitize.Name(name) +} + +// CreateNewTemplate creates a new template based on the given directory name and string +func (t *TemplateHelper) CreateNewTemplate(dirname string, details *TemplateMetadata) (string, error) { + + // Check if this template has already been created + if t.IsValidTemplate(dirname) { + return "", fmt.Errorf("cannot create template in directory '%s' - already exists", dirname) + } + + targetDir := filepath.Join(t.templateDir.fullPath, dirname) + err := t.fs.MkDir(targetDir) + if err != nil { + return "", err + } + targetMetadata := filepath.Join(targetDir, t.metadataFilename) + err = t.fs.SaveAsJSON(details, targetMetadata) + + return targetDir, err +} + // LoadMetadata loads the template's 'metadata.json' file func (t *TemplateHelper) LoadMetadata(dir string) (*TemplateMetadata, error) { templateFile := filepath.Join(dir, t.metadataFilename) diff --git a/cmd/templates/vuebasic/frontend/.gitignore b/cmd/templates/vuebasic/frontend/.gitignore new file mode 100644 index 000000000..185e66319 --- /dev/null +++ b/cmd/templates/vuebasic/frontend/.gitignore @@ -0,0 +1,21 @@ +.DS_Store +node_modules +/dist + +# local env files +.env.local +.env.*.local + +# Log files +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Editor directories and files +.idea +.vscode +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw* diff --git a/cmd/wails/10.1_dev_newtemplate.go b/cmd/wails/10.1_dev_newtemplate.go new file mode 100644 index 000000000..3c52b45a9 --- /dev/null +++ b/cmd/wails/10.1_dev_newtemplate.go @@ -0,0 +1,121 @@ +// +build dev + +package main + +import ( + "fmt" + "time" + + "github.com/wailsapp/wails/cmd" + "gopkg.in/AlecAivazis/survey.v1" +) + +var templateHelper = cmd.NewTemplateHelper() + +var qs = []*survey.Question{ + { + Name: "Name", + Prompt: &survey.Input{Message: "Please enter the name of your template (eg: React/Webpack Basic):"}, + Validate: survey.Required, + }, + { + Name: "ShortDescription", + Prompt: &survey.Input{Message: "Please enter a short description for the template (eg: React with Webpack 4):"}, + Validate: survey.Required, + }, + { + Name: "Description", + Prompt: &survey.Input{Message: "Please enter a long description:"}, + Validate: survey.Required, + }, + { + Name: "FrontendDir", + Prompt: &survey.Input{Message: "Please enter the name of the directory the frontend code resides (eg: frontend):"}, + Validate: survey.Required, + }, + { + Name: "Install", + Prompt: &survey.Input{Message: "Please enter the install command (eg: npm install):"}, + Validate: survey.Required, + }, + { + Name: "Build", + Prompt: &survey.Input{Message: "Please enter the build command (eg: npm run build):"}, + Validate: survey.Required, + }, + { + Name: "Serve", + Prompt: &survey.Input{Message: "Please enter the serve command (eg: npm run serve):"}, + Validate: survey.Required, + }, + { + Name: "Bridge", + Prompt: &survey.Input{Message: "Please enter the name of the directory to copy the wails bridge runtime (eg: src):"}, + Validate: survey.Required, + }, +} + +func newTemplate(devCommand *cmd.Command) { + + commandDescription := `This command scaffolds everything needed to develop a new template.` + newTemplate := devCommand.Command("newtemplate", "Generate a new template"). + LongDescription(commandDescription) + + newTemplate.Action(func() error { + logger.PrintSmallBanner("Generating new project template") + fmt.Println() + + var answers cmd.TemplateMetadata + + // perform the questions + err := survey.Ask(qs, &answers) + if err != nil { + fmt.Println(err.Error()) + return err + } + + dirname := templateHelper.SanitizeFilename(answers.Name) + prompt := []*survey.Question{{ + Prompt: &survey.Input{ + Message: "Please enter a directory name for the template:", + Default: dirname, + }, + Validate: func(val interface{}) error { + err := survey.Required(val) + if err != nil { + return err + } + if templateHelper.IsValidTemplate(val.(string)) { + return fmt.Errorf("template directory already exists") + } + if templateHelper.SanitizeFilename(val.(string)) != val.(string) { + return fmt.Errorf("invalid directory name '%s'", val.(string)) + } + return nil + }, + }} + err = survey.Ask(prompt, &dirname) + if err != nil { + return err + } + + answers.Version = "1.0.0" + answers.Created = time.Now().String() + + // Get Author info from system info + system := cmd.NewSystemHelper() + author, err := system.GetAuthor() + if err == nil { + answers.Author = author + } + + templateDirectory, err := templateHelper.CreateNewTemplate(dirname, &answers) + if err != nil { + return err + } + + logger.Green("Created new template '%s' in directory '%s'", answers.Name, templateDirectory) + + return nil + }) +} diff --git a/cmd/wails/10_dev.go b/cmd/wails/10_dev.go new file mode 100644 index 000000000..3d237cb98 --- /dev/null +++ b/cmd/wails/10_dev.go @@ -0,0 +1,18 @@ +// +build dev + +package main + +func init() { + + commandDescription := `This command provides access to developer tooling.` + devCommand := app.Command("dev", "A selection of developer tools"). + LongDescription(commandDescription) + + // Add subcommands + newTemplate(devCommand) + + devCommand.Action(func() error { + devCommand.PrintHelp() + return nil + }) +}