1
0
Fork 0
forked from goffee/goffee

initial commits 1

This commit is contained in:
Zeni Kim 2024-09-12 17:14:29 -05:00
parent 61645c1459
commit c97afa7564
19 changed files with 1612 additions and 142 deletions

82
cmd/generate-event.go Normal file
View 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
View 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
View 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")
}

View 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
View 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
View 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
View 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
View 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
View 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
View 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
View file

@ -0,0 +1,7 @@
{
"cliReleasedVersion": "dummyVersion",
"releaseUrl": "dummyReleaseurl",
"paths": [
"dummyPath"
]
}