forked from goffee/goffee
initial commits 1
This commit is contained in:
parent
61645c1459
commit
c97afa7564
19 changed files with 1612 additions and 142 deletions
82
cmd/generate-event.go
Normal file
82
cmd/generate-event.go
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
// Copyright © Harran Ali <harran.m@gmail.com>. All rights reserved.
|
||||
// Use of this source code is governed by MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var GenerateEventCmd = &cobra.Command{
|
||||
Use: "gen:event [event-name]",
|
||||
Short: "Create an event",
|
||||
Long: `Helps you generate a boilderplate code for events
|
||||
example:
|
||||
goffee gen:event my-event-name --job EventJobName
|
||||
|
||||
`,
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
pwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
eventName := args[0]
|
||||
eventJobName, err := cmd.Flags().GetString("job")
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
fPath := filepath.Join(pwd, "events/event-names.go")
|
||||
eventNamesFile, err := os.Open(fPath)
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
eventNamesFileContent, err := io.ReadAll(eventNamesFile)
|
||||
eventNamesFile.Close()
|
||||
eventNamesFileContentStr := string(eventNamesFileContent)
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
eventNamesFileContentStr = strings.TrimSuffix(eventNamesFileContentStr, "\n")
|
||||
eventNamesFileContentStr = eventNamesFileContentStr + "\n" + getEventNameStatement(prepareEventNameConst(eventName), eventName)
|
||||
os.WriteFile(fPath, []byte(eventNamesFileContentStr), 666)
|
||||
jfn := camelCaseToSnake(eventJobName, "-") + ".go"
|
||||
ffnp := filepath.Join(pwd, "events/eventjobs", jfn)
|
||||
jfs, err := os.Stat(ffnp)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
fmt.Printf("problem creating the file: %v\n", ffnp)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if jfs != nil {
|
||||
fmt.Printf("file \"%v\" already exist\n", jfn)
|
||||
os.Exit(1)
|
||||
}
|
||||
jobFile, err := os.Create(ffnp)
|
||||
if err != err {
|
||||
fmt.Println(err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
jobFile.WriteString(prepareJobContent(eventJobName))
|
||||
jobFile.Close()
|
||||
|
||||
fmt.Println("event generated successfully")
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(GenerateEventCmd)
|
||||
GenerateEventCmd.Flags().StringP("job", "j", "", "the name of the job to be executed when the event is fired")
|
||||
GenerateEventCmd.MarkFlagRequired("job")
|
||||
}
|
||||
61
cmd/generate-eventjob.go
Normal file
61
cmd/generate-eventjob.go
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
// Copyright © Harran Ali <harran.m@gmail.com>. All rights reserved.
|
||||
// Use of this source code is governed by MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var GenerateEventJobCmd = &cobra.Command{
|
||||
Use: "gen:eventjob [JobName]",
|
||||
Short: "Create an event job",
|
||||
Long: `Helps you generate a boilderplate code for event jobs
|
||||
example:
|
||||
goffee gen:eventjob EventJobName
|
||||
|
||||
`,
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
pwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
eventJobName := args[0]
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
jfn := camelCaseToSnake(eventJobName, "-") + ".go"
|
||||
ffnp := filepath.Join(pwd, "events/eventjobs", jfn)
|
||||
jfs, err := os.Stat(ffnp)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
fmt.Printf("problem creating the file: %v\n", ffnp)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if jfs != nil {
|
||||
fmt.Printf("file \"%v\" already exist\n", jfn)
|
||||
os.Exit(1)
|
||||
}
|
||||
jobFile, err := os.Create(ffnp)
|
||||
if err != err {
|
||||
fmt.Println(err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
jobFile.WriteString(prepareJobContent(eventJobName))
|
||||
jobFile.Close()
|
||||
fmt.Println("event job generated successfully")
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(GenerateEventJobCmd)
|
||||
}
|
||||
65
cmd/generate-handler.go
Normal file
65
cmd/generate-handler.go
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
// Copyright © Harran Ali <harran.m@gmail.com>. All rights reserved.
|
||||
// Use of this source code is governed by MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var GenerateHandlerCmd = &cobra.Command{
|
||||
Use: "gen:handler [HandlerName]",
|
||||
Short: "Create a handler",
|
||||
Long: `Helps you generate a boilderplate code for handlers
|
||||
|
||||
example:
|
||||
goffee gen:handler ListUsers --file users.go
|
||||
|
||||
`,
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
pwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
handlerName := args[0]
|
||||
handlersFileName, err := cmd.Flags().GetString("file")
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
handlersFileName = strings.ToLower(handlersFileName)
|
||||
var handlersFile *os.File
|
||||
handlersFilePath := filepath.Join(pwd, "handlers", handlersFileName)
|
||||
hfs, err := os.Stat(handlersFilePath)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
fmt.Printf("problem reading the file: %v\n", handlersFilePath)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if hfs == nil {
|
||||
handlersFile, err = createHandlerFile(handlersFilePath)
|
||||
} else {
|
||||
handlersFile, err = os.OpenFile(handlersFilePath, os.O_RDWR|os.O_APPEND, 766)
|
||||
}
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
handlersFile.WriteString(prepareHandlerContent(handlerName))
|
||||
fmt.Println("handler generated successfully")
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(GenerateHandlerCmd)
|
||||
GenerateHandlerCmd.Flags().StringP("file", "f", "", "the file name within 'handlers/' directory in which to put the handler, if its not there a new one will be created")
|
||||
GenerateHandlerCmd.MarkFlagRequired("file")
|
||||
}
|
||||
61
cmd/generate-middleware.go
Normal file
61
cmd/generate-middleware.go
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
// Copyright © Harran Ali <harran.m@gmail.com>. All rights reserved.
|
||||
// Use of this source code is governed by MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var GenerateMiddlewareCmd = &cobra.Command{
|
||||
Use: "gen:middleware [MiddlewareName]",
|
||||
Short: "Create a middleware",
|
||||
Long: `Helps you generate a boilderplate code for middlewares
|
||||
example:
|
||||
goffee gen:middleware MyMiddleware
|
||||
|
||||
`,
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
pwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
middlewareName := args[0]
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
mfn := camelCaseToSnake(middlewareName, "-") + ".go"
|
||||
mfnp := filepath.Join(pwd, "middlewares", mfn)
|
||||
jfs, err := os.Stat(mfnp)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
fmt.Printf("problem creating the file: %v\n", mfnp)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if jfs != nil {
|
||||
fmt.Printf("file \"%v\" already exist\n", mfn)
|
||||
os.Exit(1)
|
||||
}
|
||||
mwFile, err := os.Create(mfnp)
|
||||
if err != err {
|
||||
fmt.Println(err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
mwFile.WriteString(prepareMiddlewareContent(middlewareName))
|
||||
mwFile.Close()
|
||||
fmt.Println("middleware generated successfully")
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(GenerateMiddlewareCmd)
|
||||
}
|
||||
63
cmd/generate-model.go
Normal file
63
cmd/generate-model.go
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
// Copyright © Harran Ali <harran.m@gmail.com>. All rights reserved.
|
||||
// Use of this source code is governed by MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var GenerateModelCmd = &cobra.Command{
|
||||
Use: "gen:model [ModelName]",
|
||||
Short: "Create a database model",
|
||||
Long: `Helps you generate a boilderplate code for database model
|
||||
example:
|
||||
goffee gen:model User
|
||||
|
||||
`,
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
pwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
modelName := args[0]
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
mfn := camelCaseToSnake(modelName, "-") + ".go"
|
||||
mfnp := filepath.Join(pwd, "models/", mfn)
|
||||
mfs, err := os.Stat(mfnp)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
fmt.Printf("problem creating the file: %v\n", mfnp)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if mfs != nil {
|
||||
fmt.Printf("file \"%v\" already exist\n", mfn)
|
||||
os.Exit(1)
|
||||
}
|
||||
ModelFile, err := os.Create(mfnp)
|
||||
if err != err {
|
||||
fmt.Println(err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
tableName := camelCaseToSnake(modelName, "_")
|
||||
tableName = singleToPlural(tableName)
|
||||
ModelFile.WriteString(prepareModelContent(modelName, tableName))
|
||||
ModelFile.Close()
|
||||
fmt.Println("model generated successfully")
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(GenerateModelCmd)
|
||||
}
|
||||
184
cmd/helper_.go
Normal file
184
cmd/helper_.go
Normal file
|
|
@ -0,0 +1,184 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
func getEventNameStatement(constName string, eventName string) string {
|
||||
t := `const {constName} = "{eventName}"`
|
||||
r := strings.Replace(t, "{constName}", constName, 1)
|
||||
r = strings.Replace(r, "{eventName}", eventName, 1)
|
||||
return r
|
||||
}
|
||||
|
||||
func prepareEventNameConst(eventName string) string {
|
||||
var res string
|
||||
words := strings.Split(eventName, "-")
|
||||
for k, v := range words {
|
||||
if k == 0 {
|
||||
res = strings.ToUpper(v) + "_"
|
||||
} else if k < (len(words) - 1) {
|
||||
res = res + strings.ToUpper(v) + "_"
|
||||
} else {
|
||||
res = res + strings.ToUpper(v)
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func camelCaseToSnake(name string, sep string) string {
|
||||
var res string
|
||||
namesB := []byte(name)
|
||||
for i, v := range namesB {
|
||||
if i == 0 {
|
||||
res = res + strings.ToLower(string(v))
|
||||
} else {
|
||||
if !unicode.IsUpper(rune(v)) {
|
||||
res = res + string(v)
|
||||
} else {
|
||||
res = res + sep
|
||||
res = res + strings.ToLower(string(v))
|
||||
}
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func prepareJobContent(jobName string) string {
|
||||
t := `package eventjobs
|
||||
|
||||
import (
|
||||
"github.com/goffee/core"
|
||||
)
|
||||
|
||||
var {JobName} core.EventJob = func(event *core.Event, c *core.Context) {
|
||||
// logic implementation goes here...
|
||||
}
|
||||
`
|
||||
res := strings.Replace(t, "{JobName}", jobName, 1)
|
||||
return res
|
||||
}
|
||||
|
||||
func prepareModelContent(modelName string, tableName string) string {
|
||||
t := `package models
|
||||
|
||||
import "gorm.io/gorm"
|
||||
|
||||
type {modelName} struct {
|
||||
gorm.Model
|
||||
// add your field here...
|
||||
}
|
||||
|
||||
// Override the table name
|
||||
func ({modelName}) TableName() string {
|
||||
return "{tableName}"
|
||||
}
|
||||
`
|
||||
res := strings.Replace(t, "{modelName}", modelName, 2)
|
||||
res = strings.Replace(res, "{tableName}", tableName, 1)
|
||||
return res
|
||||
}
|
||||
|
||||
func prepareHandlerContent(HandlerName string) string {
|
||||
t := `
|
||||
func {HandlerName}(c *core.Context) *core.Response {
|
||||
// logic implementation goes here...
|
||||
|
||||
return nil
|
||||
}
|
||||
`
|
||||
res := strings.Replace(t, "{HandlerName}", HandlerName, 1)
|
||||
return res
|
||||
}
|
||||
|
||||
func createHandlerFile(handlersFilePath string) (*os.File, error) {
|
||||
file, err := os.Create(handlersFilePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = file.WriteString(`package handlers
|
||||
|
||||
import (
|
||||
"github.com/goffee/core"
|
||||
)
|
||||
`)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return file, nil
|
||||
}
|
||||
|
||||
func prepareMiddlewareContent(middlewareName string) string {
|
||||
t := `package middlewares
|
||||
|
||||
import (
|
||||
"github.com/goffee/core"
|
||||
)
|
||||
|
||||
var {middlewareName} core.Middleware = func (c *core.Context) {
|
||||
c.Next()
|
||||
}
|
||||
`
|
||||
res := strings.Replace(t, "{middlewareName}", middlewareName, 1)
|
||||
return res
|
||||
}
|
||||
|
||||
func singleToPlural(word string) string {
|
||||
lastOne := word[len(word)-1:]
|
||||
lastTwo := word[len(word)-2:]
|
||||
if lastOne == "s" || lastOne == "x" || lastOne == "z" || lastTwo == "sh" || lastTwo == "ch" {
|
||||
return word + "es"
|
||||
}
|
||||
|
||||
return word + "s"
|
||||
}
|
||||
|
||||
func CopyFile(sourceFilePath string, destFolderPath string, newFileName string) error {
|
||||
o := syscall.Umask(0)
|
||||
defer syscall.Umask(o)
|
||||
// newFileName := filepath.Base(sourceFilePath)
|
||||
os.MkdirAll(destFolderPath, 766)
|
||||
srcFileInfo, err := os.Stat(sourceFilePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !srcFileInfo.Mode().IsRegular() {
|
||||
return errors.New("can not move file, not in a regular mode")
|
||||
}
|
||||
srcFile, err := os.Open(sourceFilePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer srcFile.Close()
|
||||
destFilePath := filepath.Join(destFolderPath, newFileName)
|
||||
destFile, err := os.Create(destFilePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
buff := make([]byte, 1024*8)
|
||||
for {
|
||||
n, err := srcFile.Read(buff)
|
||||
if err != nil && err != io.EOF {
|
||||
panic(fmt.Sprintf("error moving file %v", sourceFilePath))
|
||||
}
|
||||
if n == 0 {
|
||||
break
|
||||
}
|
||||
_, err = destFile.Write(buff[:n])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
destFile.Close()
|
||||
|
||||
return nil
|
||||
}
|
||||
300
cmd/new.go
Normal file
300
cmd/new.go
Normal file
|
|
@ -0,0 +1,300 @@
|
|||
// Copyright © Harran Ali <harran.m@gmail.com>. All rights reserved.
|
||||
// Copyright (c) 2024 Zeni Kim <zenik@smarteching.com>
|
||||
// Use of this source code is governed by MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/briandowns/spinner"
|
||||
"github.com/c4milo/unpackit"
|
||||
"github.com/karrick/godirwalk"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/thanhpk/randstr"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
ReleaseUrl string `json:"releaseUrl"`
|
||||
CliReleasedVersion string `json:"cliReleasedVersion"`
|
||||
Paths []string `json:"paths"`
|
||||
}
|
||||
|
||||
type RepoMeta struct {
|
||||
TagName string `json:"tag_name"`
|
||||
TarBallUrl string `json:"tarball_url"`
|
||||
}
|
||||
|
||||
// Config file
|
||||
const CONFIG_URL string = "https://raw.githubusercontent.com/gocondor/gaffer/main/config.json"
|
||||
|
||||
const REPO_URL string = "https://git.smarteching.com/goffee/goffee/cup/releases/latest"
|
||||
|
||||
// Temporary file name
|
||||
var tempName string
|
||||
|
||||
// Current verson of the installer
|
||||
var version string = rootCmd.Version
|
||||
|
||||
// struct for creating new project command
|
||||
type CmdNew struct{}
|
||||
|
||||
// newCmd represents the new command
|
||||
var newCmd = &cobra.Command{
|
||||
Use: "new [project-name] [project-repository]",
|
||||
Short: "Create a new cup projects",
|
||||
Long: `Create new cup projects,
|
||||
|
||||
Example:
|
||||
goffee new myapp git.smarteching.com/my-organization/myapp
|
||||
`,
|
||||
Args: cobra.ExactArgs(2),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cn := CmdNew{}
|
||||
|
||||
// Extract the args
|
||||
projectName := args[0]
|
||||
projectRepo := args[1]
|
||||
|
||||
// show the spinner
|
||||
s := spinner.New(spinner.CharSets[9], 100*time.Millisecond)
|
||||
s.Start()
|
||||
|
||||
// Check if a directory with the given name exist
|
||||
_, err := os.Stat(projectName)
|
||||
if !os.IsNotExist(err) {
|
||||
fmt.Println("\nA directory with the given projct name alerady exist!")
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
// Download the config from github
|
||||
fmt.Println("Preparing ...")
|
||||
|
||||
var config Config
|
||||
cn.DownloadConfig(&http.Client{}, CONFIG_URL, &config)
|
||||
// Check for update
|
||||
if yes := cn.IsUpdatedRequired(config.CliReleasedVersion); yes {
|
||||
fmt.Println(`
|
||||
This version of goffee is outdated!
|
||||
Please update by running the following commands:
|
||||
|
||||
go install git.smarteching.com/goffee/goffee@latest
|
||||
|
||||
`)
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
repoMeta := FetchRepoMeta(REPO_URL)
|
||||
downloadUrl := strings.Replace(config.ReleaseUrl, "{name}", repoMeta.TagName, 1)
|
||||
|
||||
// Download the cup release
|
||||
fmt.Println("Downloading a cup ...")
|
||||
filePath := cn.DownloadGoCondor(&http.Client{}, downloadUrl, cn.GenerateTempName())
|
||||
//Unpack file
|
||||
fmt.Println("Unpacking ...")
|
||||
pwd, _ := os.Getwd()
|
||||
cn.Unpack(filePath, pwd)
|
||||
|
||||
// Rename to the user's given project name
|
||||
os.Rename("./cup-"+removeFirstCHar(repoMeta.TagName), "./"+projectName) //first char is `v`
|
||||
// Remove the downloaded cup archive
|
||||
os.Remove(filePath)
|
||||
|
||||
projectPath := pwd + "/" + projectName
|
||||
|
||||
//remove .github folder
|
||||
os.RemoveAll(projectPath + "/.github")
|
||||
|
||||
// copy the env file
|
||||
CopyFile(filepath.Join(projectPath, ".env-example"), projectPath, ".env")
|
||||
|
||||
// Fix imports
|
||||
fixImports(projectPath, projectRepo, config.Paths)
|
||||
|
||||
// Run go mod tidy
|
||||
command := exec.Command("go", "mod", "tidy")
|
||||
command.Dir = projectPath
|
||||
stdout, err := command.StdoutPipe()
|
||||
command.Start()
|
||||
|
||||
oneByte := make([]byte, 100)
|
||||
num := 1
|
||||
for {
|
||||
_, err := stdout.Read(oneByte)
|
||||
|
||||
if err != nil {
|
||||
fmt.Printf(err.Error())
|
||||
break
|
||||
}
|
||||
r := bufio.NewReader(stdout)
|
||||
line, _, _ := r.ReadLine()
|
||||
fmt.Println(string(line))
|
||||
num = num + 1
|
||||
|
||||
if num > 3 {
|
||||
os.Exit(0)
|
||||
}
|
||||
}
|
||||
command.Wait()
|
||||
|
||||
// Hide the spinner
|
||||
s.Stop()
|
||||
|
||||
fmt.Println(projectName + " created successfully")
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(newCmd)
|
||||
}
|
||||
|
||||
// Download cup archive
|
||||
func (cn *CmdNew) DownloadGoCondor(http *http.Client, url string, tempName string) string {
|
||||
tempFilePath := os.TempDir() + "/" + tempName
|
||||
response, err := http.Get(url)
|
||||
if err != nil {
|
||||
fmt.Println("error downloading the GoCondor release")
|
||||
os.Exit(1)
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
file, err := os.Create(tempFilePath)
|
||||
if err != nil {
|
||||
fmt.Println("error creating temp file")
|
||||
os.Exit(1)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
_, err = io.Copy(file, response.Body)
|
||||
if err != nil {
|
||||
fmt.Println("error writing the GoCondor release to file")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
return tempFilePath
|
||||
}
|
||||
|
||||
// Download config
|
||||
func (cn *CmdNew) DownloadConfig(http *http.Client, url string, conf *Config) *Config {
|
||||
response, err := http.Get(url)
|
||||
if err != nil {
|
||||
fmt.Println("error downloading config")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
fmt.Println("error reading config")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
err = json.Unmarshal(body, &conf)
|
||||
if err != nil {
|
||||
fmt.Println("error unmarshaling config")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
return conf
|
||||
}
|
||||
|
||||
// Check for updates
|
||||
func (cn *CmdNew) IsUpdatedRequired(LatestReleasedVersion string) bool {
|
||||
if LatestReleasedVersion != version {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func fixImports(dirName string, projectRepo string, paths []string) {
|
||||
err := godirwalk.Walk(dirName, &godirwalk.Options{
|
||||
Callback: func(osPathname string, de *godirwalk.Dirent) error {
|
||||
if !de.IsDir() && strings.Contains(osPathname, ".go") {
|
||||
file, err := ioutil.ReadFile(osPathname)
|
||||
if err != nil {
|
||||
fmt.Printf("error reading %s", osPathname)
|
||||
os.Exit(1)
|
||||
}
|
||||
newContent := strings.Replace(string(file), paths[0], projectRepo, -1)
|
||||
ioutil.WriteFile(osPathname, []byte(newContent), 0)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
Unsorted: true,
|
||||
})
|
||||
if err != nil {
|
||||
fmt.Println("error scaning updating import paths")
|
||||
fmt.Println(err)
|
||||
}
|
||||
|
||||
file, err := ioutil.ReadFile(dirName + "/go.mod")
|
||||
if err != nil {
|
||||
fmt.Println("error reading go.mod file")
|
||||
os.Exit(1)
|
||||
}
|
||||
newContent := strings.Replace(string(file), paths[0], projectRepo, -1)
|
||||
err = ioutil.WriteFile(dirName+"/go.mod", []byte(newContent), 0)
|
||||
if err != nil {
|
||||
fmt.Println("error writing to go.mod file")
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// Unpack GoCondor
|
||||
func (cn *CmdNew) Unpack(filePath string, destPath string) {
|
||||
// Open file
|
||||
file, err := os.Open(filePath)
|
||||
if err != nil {
|
||||
fmt.Println("error opening the downloaded file")
|
||||
os.Exit(1)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
// Unpack it
|
||||
_, err = unpackit.Unpack(file, destPath)
|
||||
if err != nil {
|
||||
fmt.Println("error unpacking the downloaded release")
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// Generate random name
|
||||
func (cn *CmdNew) GenerateTempName() string {
|
||||
return "cup_temp_" + randstr.Hex(8) + ".tar.gz"
|
||||
}
|
||||
|
||||
func FetchRepoMeta(url string) RepoMeta {
|
||||
var repoMeta RepoMeta
|
||||
|
||||
// get the latest released version number
|
||||
res, err := http.Get(url)
|
||||
if err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
json.Unmarshal(body, &repoMeta)
|
||||
|
||||
return repoMeta
|
||||
}
|
||||
|
||||
func removeFirstCHar(str string) string {
|
||||
_, i := utf8.DecodeRuneInString(str)
|
||||
|
||||
return str[i:]
|
||||
}
|
||||
102
cmd/new_test.go
Normal file
102
cmd/new_test.go
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
package cmd_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
. "git.smarteching.com/goffee/goffee/cmd"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/thanhpk/randstr"
|
||||
)
|
||||
|
||||
func TestDownloadConfig(t *testing.T) {
|
||||
// Prepare
|
||||
newCmd := CmdNew{}
|
||||
var config Config
|
||||
server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
res, err := os.ReadFile("testdata/config.json")
|
||||
if err != nil {
|
||||
t.Fatal("error reading test data", err)
|
||||
}
|
||||
rw.Write(res)
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
// Execute
|
||||
newCmd.DownloadConfig(server.Client(), server.URL, &config)
|
||||
|
||||
// Assert
|
||||
assert.Equal(t, "dummyVersion", config.CliReleasedVersion)
|
||||
assert.Equal(t, "dummyReleaseurl", config.ReleaseUrl)
|
||||
}
|
||||
func TestDownloadGoCondor(t *testing.T) {
|
||||
// Prepare
|
||||
newCmd := CmdNew{}
|
||||
fileName := "gocondor_temp_" + randstr.Hex(8) + ".tar.gz"
|
||||
server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
res, err := os.ReadFile("testdata/gocondor.tar.gz")
|
||||
if err != nil {
|
||||
t.Fatal("error reading test data", err)
|
||||
}
|
||||
rw.Write(res)
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
// Execute
|
||||
filePath := newCmd.DownloadGoCondor(server.Client(), server.URL, fileName)
|
||||
|
||||
// Assert
|
||||
_, err := os.Stat(filePath)
|
||||
if os.IsNotExist(err) {
|
||||
t.Fatal("downloaded file not exist", filePath)
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
os.Remove(filePath)
|
||||
}
|
||||
|
||||
func TestIsUpdatedRequired(t *testing.T) {
|
||||
// Prepare
|
||||
cn := CmdNew{}
|
||||
var config Config
|
||||
res, _ := os.ReadFile("testdata/config.json")
|
||||
json.Unmarshal(res, &config)
|
||||
|
||||
// Execute
|
||||
check := cn.IsUpdatedRequired(config.CliReleasedVersion)
|
||||
|
||||
// Assert
|
||||
if check != true { // supposed to be true
|
||||
t.Fatal("failed to assert check for update")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnpack(t *testing.T) {
|
||||
// Prepare
|
||||
cn := CmdNew{}
|
||||
filepath := "./testdata/gocondor.tar.gz"
|
||||
folderName := "gocondor-0.3-alpha.6"
|
||||
destPath := os.TempDir()
|
||||
os.RemoveAll(destPath + "/" + folderName)
|
||||
|
||||
// Execute
|
||||
cn.Unpack(filepath, destPath)
|
||||
|
||||
// Assert
|
||||
_, err := os.Stat(destPath + "/" + folderName)
|
||||
if os.IsNotExist(err) {
|
||||
t.Fatal("failed to assert unpack")
|
||||
}
|
||||
|
||||
files, err := ioutil.ReadDir(destPath + "/" + folderName)
|
||||
if len(files) <= 0 {
|
||||
t.Fatal("failed to assert unpack")
|
||||
}
|
||||
|
||||
// remove the temp dir
|
||||
os.RemoveAll(destPath + "/" + folderName)
|
||||
}
|
||||
50
cmd/root.go
Normal file
50
cmd/root.go
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
// Use of this source code is governed by MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
homedir "github.com/mitchellh/go-homedir"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
var cfgFile string
|
||||
|
||||
// rootCmd represents the base command when called without any subcommands
|
||||
var rootCmd = &cobra.Command{
|
||||
Use: "goffee",
|
||||
Version: "v1.6.1",
|
||||
Short: "Goffee is the cli tool",
|
||||
Long: `Goffee is the cli tool for creating new projects and performing other tasks`,
|
||||
|
||||
// Run: func(cmd *cobra.Command, args []string) {
|
||||
|
||||
// },
|
||||
}
|
||||
|
||||
func Execute() {
|
||||
cobra.CheckErr(rootCmd.Execute())
|
||||
}
|
||||
|
||||
func init() {
|
||||
cobra.OnInitialize(initConfig)
|
||||
}
|
||||
|
||||
// initConfig reads in config file and ENV variables if set.
|
||||
func initConfig() {
|
||||
if cfgFile != "" {
|
||||
// Use config file from the flag.
|
||||
viper.SetConfigFile(cfgFile)
|
||||
} else {
|
||||
// Find home directory.
|
||||
home, err := homedir.Dir()
|
||||
cobra.CheckErr(err)
|
||||
|
||||
// Search config in home directory with name ".goffee" (without extension).
|
||||
viper.AddConfigPath(home)
|
||||
viper.SetConfigName(".goffee")
|
||||
}
|
||||
|
||||
viper.AutomaticEnv() // read in environment variables that match
|
||||
}
|
||||
208
cmd/run.go
Normal file
208
cmd/run.go
Normal file
|
|
@ -0,0 +1,208 @@
|
|||
// Use of this source code is governed by MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"path"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/radovskyb/watcher"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var pid int
|
||||
|
||||
// runCmd represents the run command
|
||||
var runCmd = &cobra.Command{
|
||||
Use: "run:dev",
|
||||
Short: "Start the app in hot reloading mode",
|
||||
Long: `To Start the app in hot reloading mode for development run the following command:
|
||||
|
||||
goffee run:dev
|
||||
|
||||
`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
pwd, _ := os.Getwd()
|
||||
fileChangeChan := make(chan bool, 1)
|
||||
startAppChan := make(chan bool, 1)
|
||||
pidChan := make(chan int, 1)
|
||||
termSigsChan := make(chan os.Signal, 1)
|
||||
|
||||
fileChangeChan <- false
|
||||
startAppChan <- false
|
||||
w := watcher.New()
|
||||
w.SetMaxEvents(1)
|
||||
w.IgnoreHiddenFiles(true)
|
||||
w.Ignore(
|
||||
pwd+"/logs/app.log",
|
||||
pwd+"/tmp",
|
||||
)
|
||||
w.FilterOps(watcher.Rename, watcher.Move, watcher.Create, watcher.Write)
|
||||
if err := w.AddRecursive(pwd); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
go func(fileChangeChan chan bool) {
|
||||
for {
|
||||
select {
|
||||
case <-w.Event:
|
||||
fileChangeChan <- true
|
||||
case err := <-w.Error:
|
||||
log.Fatalln(err)
|
||||
case <-w.Closed:
|
||||
return
|
||||
}
|
||||
}
|
||||
}(fileChangeChan)
|
||||
go func() {
|
||||
startAppChan <- true
|
||||
}()
|
||||
go startServerJob(pidChan, startAppChan)
|
||||
go startRestartControllerJob(fileChangeChan, pidChan, startAppChan)
|
||||
signal.Notify(termSigsChan, syscall.SIGINT, syscall.SIGTERM)
|
||||
go func(termSigsChan chan os.Signal) {
|
||||
for {
|
||||
<-termSigsChan
|
||||
if pid == 0 {
|
||||
os.Exit(0)
|
||||
} else {
|
||||
pgid, err := syscall.Getpgid(pid)
|
||||
if err != nil {
|
||||
fmt.Println("error getting pgid: ", err)
|
||||
}
|
||||
err = syscall.Kill(-pgid, syscall.SIGKILL)
|
||||
if err != nil {
|
||||
fmt.Println("error stopping process: ", err)
|
||||
}
|
||||
os.Exit(0)
|
||||
}
|
||||
}
|
||||
|
||||
}(termSigsChan)
|
||||
|
||||
func() {
|
||||
if err := w.Start(time.Millisecond * 100); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
}()
|
||||
},
|
||||
}
|
||||
|
||||
func startRestartControllerJob(fileChangeChan chan bool, pidChan chan int, startAppChan chan bool) {
|
||||
for {
|
||||
fileChanged := <-fileChangeChan
|
||||
if fileChanged {
|
||||
fmt.Println("Restarting...")
|
||||
pid := <-pidChan
|
||||
if pid != 0 {
|
||||
if runtime.GOOS == "windows" {
|
||||
killCmd := exec.Command("taskkill", "/T", "/F", "/PID", strconv.Itoa(pid))
|
||||
err := killCmd.Run()
|
||||
if err != nil {
|
||||
fmt.Println("error stopping the dev server", err.Error())
|
||||
}
|
||||
} else if runtime.GOOS == "darwin" {
|
||||
pgid, err := syscall.Getpgid(pid)
|
||||
if err != nil {
|
||||
fmt.Println("error getting pgid: ", err.Error())
|
||||
}
|
||||
err = syscall.Kill(-pgid, syscall.SIGKILL)
|
||||
if err != nil {
|
||||
fmt.Println("error stopping process: ", err.Error())
|
||||
}
|
||||
} else {
|
||||
fmt.Println("not implemented for os: ", runtime.GOOS)
|
||||
}
|
||||
go func() {
|
||||
startAppChan <- true
|
||||
}()
|
||||
} else {
|
||||
go func() {
|
||||
startAppChan <- true
|
||||
}()
|
||||
}
|
||||
go func() {
|
||||
fileChangeChan <- false
|
||||
}()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func startServerJob(pidChan chan int, startAppChan chan bool) {
|
||||
for {
|
||||
shouldStartApp := <-startAppChan
|
||||
if shouldStartApp {
|
||||
fmt.Println("\n\nBuilding...")
|
||||
err := compileApp()
|
||||
if err != nil {
|
||||
fmt.Println("error building: ", err.Error())
|
||||
go func() {
|
||||
pidChan <- 0
|
||||
}()
|
||||
} else {
|
||||
fmt.Println("Starting...")
|
||||
pwd, _ := os.Getwd()
|
||||
if runtime.GOOS == "darwin" {
|
||||
execFile := pwd + "/tmp/" + path.Base(pwd)
|
||||
binary, lookErr := exec.LookPath("/bin/sh")
|
||||
if lookErr != nil {
|
||||
panic(lookErr)
|
||||
}
|
||||
args := []string{"/bin/sh", "-c", execFile}
|
||||
execSpec := &syscall.ProcAttr{
|
||||
Dir: pwd,
|
||||
Env: os.Environ(),
|
||||
Files: []uintptr{os.Stdin.Fd(), os.Stdout.Fd(), os.Stderr.Fd()},
|
||||
Sys: &syscall.SysProcAttr{
|
||||
Setpgid: true,
|
||||
},
|
||||
}
|
||||
pid, _, err = syscall.StartProcess(binary, args, execSpec)
|
||||
if err != nil {
|
||||
fmt.Println("error starting process: ", err.Error())
|
||||
go func() {
|
||||
pidChan <- 0
|
||||
}()
|
||||
} else {
|
||||
go func() {
|
||||
pidChan <- pid
|
||||
}()
|
||||
}
|
||||
} else {
|
||||
fmt.Println("not implemented for os: ", runtime.GOOS)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func compileApp() error {
|
||||
pwd, _ := os.Getwd()
|
||||
var command *exec.Cmd
|
||||
command = exec.Command("/bin/sh", "-c", fmt.Sprintf("go build -o %v/tmp/", pwd))
|
||||
command.Env = os.Environ()
|
||||
command.SysProcAttr = &syscall.SysProcAttr{
|
||||
Setpgid: true,
|
||||
}
|
||||
command.Dir = pwd
|
||||
o, err := command.CombinedOutput()
|
||||
if string(o) != "" {
|
||||
fmt.Println(string(o))
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(runCmd)
|
||||
}
|
||||
7
cmd/testdata/config.json
vendored
Normal file
7
cmd/testdata/config.json
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"cliReleasedVersion": "dummyVersion",
|
||||
"releaseUrl": "dummyReleaseurl",
|
||||
"paths": [
|
||||
"dummyPath"
|
||||
]
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue