github.com/yuki2006/cmd@v0.12.1-0.20240406021944-1e791c677ee9/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/yuki2006/cmd/model" 18 "github.com/yuki2006/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 }