github.com/wiselike/revel-cmd@v1.2.1/revel/new.go (about)

     1  // Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
     2  // Revel Framework source code and usage is governed by a MIT style
     3  // license that can be found in the LICENSE file.
     4  
     5  package main
     6  
     7  import (
     8  	"fmt"
     9  	"go/build"
    10  	"math/rand"
    11  	"net/url"
    12  	"os"
    13  	"os/exec"
    14  	"path/filepath"
    15  	"strings"
    16  
    17  	"github.com/wiselike/revel-cmd/model"
    18  	"github.com/wiselike/revel-cmd/utils"
    19  )
    20  
    21  const ErrNoSkeleton Error = "failed to find skeleton in filepath"
    22  
    23  var cmdNew = &Command{
    24  	UsageLine: "new -i [path] -s [skeleton] -p [package name]",
    25  	Short:     "create a skeleton Revel application",
    26  	Long: `
    27  New creates a few files to get a new Revel application running quickly.
    28  
    29  It puts all of the files in the given import path, taking the final element in
    30  the path to be the app name.
    31  
    32  Skeleton is an optional argument, provided as an import path
    33  
    34  For example:
    35  
    36      revel new -a import/path/helloworld
    37  
    38      revel new -a import/path/helloworld -s import/path/skeleton
    39  
    40  `,
    41  }
    42  
    43  func init() {
    44  	cmdNew.RunWith = newApp
    45  	cmdNew.UpdateConfig = updateNewConfig
    46  }
    47  
    48  // Called when unable to parse the command line automatically and assumes an old launch.
    49  func updateNewConfig(c *model.CommandConfig, args []string) bool {
    50  	c.Index = model.NEW
    51  	if len(c.New.Package) > 0 {
    52  		c.New.NotVendored = false
    53  	}
    54  	c.Vendored = !c.New.NotVendored
    55  
    56  	if len(args) == 0 {
    57  		if len(c.New.ImportPath) == 0 {
    58  			fmt.Fprintf(os.Stderr, cmdNew.Long)
    59  			return false
    60  		}
    61  		return true
    62  	}
    63  	c.New.ImportPath = args[0]
    64  	if len(args) > 1 {
    65  		c.New.SkeletonPath = args[1]
    66  	}
    67  
    68  	return true
    69  }
    70  
    71  // Call to create a new application.
    72  func newApp(c *model.CommandConfig) (err error) {
    73  	// Check for an existing folder so we don't clobber it
    74  	_, err = build.Import(c.ImportPath, "", build.FindOnly)
    75  	if err == nil || !utils.Empty(c.AppPath) {
    76  		return utils.NewBuildError("Abort: Import path already exists.", "path", c.ImportPath, "apppath", c.AppPath)
    77  	}
    78  
    79  	// checking and setting skeleton
    80  	if err = setSkeletonPath(c); err != nil {
    81  		return
    82  	}
    83  
    84  	// Create application path
    85  	if err := os.MkdirAll(c.AppPath, os.ModePerm); err != nil {
    86  		return utils.NewBuildError("Abort: Unable to create app path.", "path", c.AppPath)
    87  	}
    88  
    89  	// checking and setting application
    90  	if err = setApplicationPath(c); err != nil {
    91  		return err
    92  	}
    93  
    94  	// This kicked off the download of the revel app, not needed for vendor
    95  	if !c.Vendored {
    96  		// At this point the versions can be set
    97  		if err = c.SetVersions(); err != nil {
    98  			return
    99  		}
   100  	}
   101  
   102  	// copy files to new app directory
   103  	if err = copyNewAppFiles(c); err != nil {
   104  		return
   105  	}
   106  
   107  	// Run the vendor tool if needed
   108  	if c.Vendored {
   109  		if err = createModVendor(c); err != nil {
   110  			return
   111  		}
   112  	}
   113  
   114  	// goodbye world
   115  	fmt.Fprintln(os.Stdout, "Your application has been created in:\n  ", c.AppPath)
   116  	// Check to see if it should be run right off
   117  	if c.New.Run {
   118  		// Need to prep the run command
   119  		c.Run.ImportPath = c.ImportPath
   120  		updateRunConfig(c, nil)
   121  
   122  		if err = c.UpdateImportPath(); err != nil {
   123  			return
   124  		}
   125  
   126  		if err = runApp(c); err != nil {
   127  			return
   128  		}
   129  	} else {
   130  		fmt.Fprintln(os.Stdout, "\nYou can run it with:\n   revel run -a", c.ImportPath)
   131  	}
   132  
   133  	return
   134  }
   135  
   136  func createModVendor(c *model.CommandConfig) (err error) {
   137  	utils.Logger.Info("Creating a new mod app")
   138  	goModCmd := exec.Command("go", "mod", "init", filepath.Join(c.New.Package, c.AppName))
   139  
   140  	utils.CmdInit(goModCmd, !c.Vendored, c.AppPath)
   141  
   142  	utils.Logger.Info("Exec:", "args", goModCmd.Args, "env", goModCmd.Env, "workingdir", goModCmd.Dir)
   143  
   144  	getOutput, err := goModCmd.CombinedOutput()
   145  	if c.New.Callback != nil {
   146  		err = c.New.Callback()
   147  	}
   148  
   149  	if err != nil {
   150  		return utils.NewBuildIfError(err, string(getOutput))
   151  	}
   152  
   153  	return
   154  }
   155  
   156  // Used to generate a new secret key.
   157  const alphaNumeric = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
   158  
   159  // Generate a secret key.
   160  func generateSecret() string {
   161  	chars := make([]byte, 64)
   162  	for i := 0; i < 64; i++ {
   163  		chars[i] = alphaNumeric[rand.Intn(len(alphaNumeric))]
   164  	}
   165  
   166  	return string(chars)
   167  }
   168  
   169  // Sets the application path.
   170  func setApplicationPath(c *model.CommandConfig) (err error) {
   171  	// revel/revel#1014 validate relative path, we cannot use built-in functions
   172  	// since Go import path is valid relative path too.
   173  	// so check basic part of the path, which is "."
   174  
   175  	// If we are running a vendored version of Revel we do not need to check for it.
   176  	if !c.Vendored {
   177  		if filepath.IsAbs(c.ImportPath) || strings.HasPrefix(c.ImportPath, ".") {
   178  			utils.Logger.Fatalf("Abort: '%s' looks like a directory.  Please provide a Go import path instead.",
   179  				c.ImportPath)
   180  		}
   181  		_, err = build.Import(model.RevelImportPath, "", build.FindOnly)
   182  		if err != nil {
   183  			// Go get the revel project
   184  			err = c.PackageResolver(model.RevelImportPath)
   185  			if err != nil {
   186  				return utils.NewBuildIfError(err, "Failed to fetch revel "+model.RevelImportPath)
   187  			}
   188  		}
   189  	}
   190  
   191  	c.AppName = filepath.Base(c.AppPath)
   192  
   193  	return nil
   194  }
   195  
   196  // Set the skeleton path.
   197  func setSkeletonPath(c *model.CommandConfig) (err error) {
   198  	if len(c.New.SkeletonPath) == 0 {
   199  		c.New.SkeletonPath = "https://" + RevelSkeletonsImportPath + ":basic/bootstrap4"
   200  	}
   201  
   202  	// First check to see the protocol of the string
   203  	sp, err := url.Parse(c.New.SkeletonPath)
   204  	if err == nil {
   205  		utils.Logger.Info("Detected skeleton path", "path", sp)
   206  
   207  		switch strings.ToLower(sp.Scheme) {
   208  		// TODO Add support for ftp, sftp, scp ??
   209  		case "":
   210  			sp.Scheme = "file"
   211  			fallthrough
   212  		case "file":
   213  			fullpath := sp.String()[7:]
   214  			if !filepath.IsAbs(fullpath) {
   215  				fullpath, err = filepath.Abs(fullpath)
   216  				if err != nil {
   217  					return
   218  				}
   219  			}
   220  			c.New.SkeletonPath = fullpath
   221  			utils.Logger.Info("Set skeleton path to ", fullpath)
   222  			if !utils.DirExists(fullpath) {
   223  				return fmt.Errorf("%w %s %s", ErrNoSkeleton, fullpath, sp.String())
   224  			}
   225  		case "git":
   226  			fallthrough
   227  		case "http":
   228  			fallthrough
   229  		case "https":
   230  			if err := newLoadFromGit(c, sp); err != nil {
   231  				return err
   232  			}
   233  		default:
   234  			utils.Logger.Fatal("Unsupported skeleton schema ", "path", c.New.SkeletonPath)
   235  		}
   236  		// TODO check to see if the path needs to be extracted
   237  	} else {
   238  		utils.Logger.Fatal("Invalid skeleton path format", "path", c.New.SkeletonPath)
   239  	}
   240  	return
   241  }
   242  
   243  // Load skeleton from git.
   244  func newLoadFromGit(c *model.CommandConfig, sp *url.URL) (err error) {
   245  	// This method indicates we need to fetch from a repository using git
   246  	// Execute "git clone get <pkg>"
   247  	targetPath := filepath.Join(os.TempDir(), "revel", "skeleton")
   248  	os.RemoveAll(targetPath)
   249  	pathpart := strings.Split(sp.Path, ":")
   250  	getCmd := exec.Command("git", "clone", sp.Scheme+"://"+sp.Host+pathpart[0], targetPath)
   251  	utils.Logger.Info("Exec:", "args", getCmd.Args)
   252  	getOutput, err := getCmd.CombinedOutput()
   253  	if err != nil {
   254  		utils.Logger.Fatal("Abort: could not clone the  Skeleton  source code: ", "output", string(getOutput), "path", c.New.SkeletonPath)
   255  	}
   256  	outputPath := targetPath
   257  	if len(pathpart) > 1 {
   258  		outputPath = filepath.Join(targetPath, filepath.Join(strings.Split(pathpart[1], string('/'))...))
   259  	}
   260  	outputPath, _ = filepath.Abs(outputPath)
   261  	if !strings.HasPrefix(outputPath, targetPath) {
   262  		utils.Logger.Fatal("Unusual target path outside root path", "target", outputPath, "root", targetPath)
   263  	}
   264  
   265  	c.New.SkeletonPath = outputPath
   266  	return
   267  }
   268  
   269  func copyNewAppFiles(c *model.CommandConfig) (err error) {
   270  	err = os.MkdirAll(c.AppPath, 0777)
   271  	if err != nil {
   272  		return utils.NewBuildIfError(err, "MKDIR failed")
   273  	}
   274  
   275  	err = utils.CopyDir(c.AppPath, c.New.SkeletonPath, map[string]interface{}{
   276  		// app.conf
   277  		"AppName":  c.AppName,
   278  		"BasePath": c.AppPath,
   279  		"Secret":   generateSecret(),
   280  	})
   281  	if err != nil {
   282  		fmt.Printf("err %v", err)
   283  		return utils.NewBuildIfError(err, "Copy Dir failed")
   284  	}
   285  
   286  	// Dotfiles are skipped by mustCopyDir, so we have to explicitly copy the .gitignore.
   287  	gitignore := ".gitignore"
   288  	return utils.CopyFile(filepath.Join(c.AppPath, gitignore), filepath.Join(c.New.SkeletonPath, gitignore))
   289  }