Skip to content

Commit

Permalink
feat(scaffold): use prompt to guide users in setting up new project (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
cayter authored Jun 30, 2020
1 parent f3543ec commit 65a7965
Show file tree
Hide file tree
Showing 94 changed files with 362 additions and 313 deletions.
37 changes: 10 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ An opinionated productive web framework that helps scaling business easier, i.e.
- utilise `configs/.env.<APPY_ENV>` to support multiple environments deployment

## Table Of Contents

- [Overview](#overview)
- [Features](#features)
- [package `cmd`](#package-cmd)
Expand All @@ -41,6 +40,8 @@ An opinionated productive web framework that helps scaling business easier, i.e.
- [package `view`](#package-view)
- [package `worker`](#package-worker)
- [Getting Started](#getting-started)
- [Prerequisites](#prerequisites)
- [Quick Start](#quick-start)
- [Acknowledgement](#acknowledgement)
- [Contribution](#contribution)
- [License](#license)
Expand Down Expand Up @@ -315,14 +316,15 @@ $ mkdir <PROJECT_NAME> && cd $_ && go mod init $_ && git init
package main
import (
"github.com/appist/appy/support"
"github.com/appist/appy/cmd"
)
func main() {
support.Scaffold(support.ScaffoldOption{
DBAdapter: "postgres", // only "mysql" and "postgres" are supported
Description: "my first awesome app", // used in HTML's description meta tag, package.json and CLI help
})
err := cmd.Scaffold()
if err != nil {
panic(err)
}
}
```
Expand All @@ -332,37 +334,18 @@ func main() {
$ go run .
```
#### Step 4: Install project dependencies for backend and frontend.
```bash
$ go mod download
$ npm install
```
#### Step 5: Setup your local environment with databases running in docker compose cluster.
#### Step 4: Setup the databases using Docker Compose.
```bash
$ go run . setup
```
#### Step 6: Start developing your application locally.
#### Step 5: Start developing your application locally.
```bash
$ go run . start
```
#### Step 7: Build the application binary with release mode.
```bash
$ go run . build
```
#### Step 8: Tear down everything once you're done testing.
```bash
$ go run . teardown
```
## Acknowledgement
- [asynq](https://github.com/hibiken/asynq) - For processing background jobs
Expand Down
41 changes: 8 additions & 33 deletions cmd/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,6 @@ func newBuildCommand(asset *support.Asset, logger *support.Logger, server *pack.
static bool
)

goExe := "go"
if runtime.GOOS == "windows" {
goExe += ".exe"
}

cmd := &Command{
Use: "build",
Short: "Compile the static assets into go files and build the release build binary (only available in debug build)",
Expand All @@ -42,7 +37,7 @@ func newBuildCommand(asset *support.Asset, logger *support.Logger, server *pack.
}

if platform != "" && !support.ArrayContains(platforms, platform) {
logger.Fatalf("the '%s' platform isn't supported, refer to `%s tool dist list` for the supported value", platform, goExe)
logger.Fatalf("the '%s' platform isn't supported, refer to `go tool dist list` for the supported value", platform)
}

wd, err := os.Getwd()
Expand Down Expand Up @@ -77,7 +72,7 @@ func newBuildCommand(asset *support.Asset, logger *support.Logger, server *pack.
},
}

cmd.Flags().StringVar(&platform, "platform", "", fmt.Sprintf("The platform for the binary to run on, see `%s tool dist list` for full list", goExe))
cmd.Flags().StringVar(&platform, "platform", "", "The platform for the binary to run on, see `go tool dist list` for full list")
cmd.Flags().BoolVar(&static, "static", false, "Specify if the binary should statically be built")
return cmd
}
Expand All @@ -87,16 +82,6 @@ func buildCompressedBinary(logger *support.Logger, platform string, static bool,

logger.Info("Building the binary...")

goExe := "go"
if runtime.GOOS == "windows" {
goExe += ".exe"
}

goPath, err := exec.LookPath(goExe)
if err != nil {
return err
}

buildCmdArgs := []string{"build", "-a", "-tags", "netgo jsoniter", "-ldflags", "-X github.com/appist/appy/support.Build=release -s -w"}
if static {
buildCmdArgs[len(buildCmdArgs)-1] += " -extldflags '-static'"
Expand All @@ -112,12 +97,12 @@ func buildCompressedBinary(logger *support.Logger, platform string, static bool,
}
}

buildCmd := exec.Command(goPath, append(buildCmdArgs, []string{"-o", name, "."}...)...)
buildCmd := exec.Command("go", append(buildCmdArgs, []string{"-o", name, "."}...)...)
buildCmd.Env = os.Environ()
buildCmd.Env = append(buildCmd.Env, buildCmdEnv...)

buildCmd.Stderr = os.Stderr
if err = buildCmd.Run(); err != nil {
if err := buildCmd.Run(); err != nil {
return err
}
fi, _ := os.Stat(name)
Expand All @@ -129,7 +114,7 @@ func buildCompressedBinary(logger *support.Logger, platform string, static bool,
upxExe += ".exe"
}

_, err = exec.LookPath(upxExe)
_, err := exec.LookPath(upxExe)
if err == nil {
logger.Info("Compressing the binary with upx...")

Expand Down Expand Up @@ -253,25 +238,15 @@ var assets http.FileSystem
func getPlatforms() ([]string, error) {
var data []byte

goExe := "go"
if runtime.GOOS == "windows" {
goExe += ".exe"
}

goPath, err := exec.LookPath(goExe)
if err != nil {
return nil, err
}

cmd := exec.Command(goPath, "tool", "dist", "list")
cmd := exec.Command("go", "tool", "dist", "list")
cmd.Env = os.Environ()
cmd.Stderr = os.Stderr
stdout, _ := cmd.StdoutPipe()
if err = cmd.Start(); err != nil {
if err := cmd.Start(); err != nil {
return nil, err
}

data, err = ioutil.ReadAll(stdout)
data, err := ioutil.ReadAll(stdout)
if err != nil {
return nil, err
}
Expand Down
120 changes: 88 additions & 32 deletions support/scaffold.go → cmd/scaffold.go
Original file line number Diff line number Diff line change
@@ -1,49 +1,63 @@
package support
package cmd

import (
"encoding/hex"
"fmt"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"regexp"
"runtime"
"strings"
"text/template"

"github.com/pkg/errors"
"github.com/AlecAivazis/survey/v2"
"github.com/appist/appy/support"
)

// ScaffoldOption contains the information of how a new application should be
// created.
type ScaffoldOption struct {
// DBAdapter indicates the database adapter to use. By default, it is
// "postgres". Possible values are "mysql" and "postgres".
DBAdapter string

// Description indicates the project description that will be used in HTML's
// description meta tag, package.json and CLI help.
Description string
}

// Scaffold creates a new application.
func Scaffold(opt ScaffoldOption) error {
if opt.DBAdapter == "" {
opt.DBAdapter = "postgres"
var (
scaffoldQ = []*survey.Question{
{
Name: "description",
Prompt: &survey.Input{
Message: "What does your app do?",
Help: "Note: This will be used in HTML's description meta tag, package.json and CLI help message.",
Default: "",
},
},
{
Name: "dbAdapter",
Prompt: &survey.Select{
Message: "Which database adapter would you like to use?",
Options: support.SupportedDBAdapters,
Default: "postgres",
},
},
}
)

if !ArrayContains(SupportedDBAdapters, opt.DBAdapter) {
return errors.Errorf("DBAdapter '%s' is not supported, only '%s' are supported", opt.DBAdapter, SupportedDBAdapters)
// Scaffold creates a new application.
func Scaffold() error {
answers := struct {
Description string `survey:"description"`
DBAdapter string `survey:"dbAdapter"`
}{}

err := survey.Ask(scaffoldQ, &answers)
if err != nil {
return err
}

moduleName := ModuleName()
moduleName := support.ModuleName()
_, dirname, _, _ := runtime.Caller(0)
tplPath := filepath.Dir(dirname) + "/templates/scaffold"

masterKeyDev := hex.EncodeToString(GenerateRandomBytes(32))
masterKeyTest := hex.EncodeToString(GenerateRandomBytes(32))
masterKeyDev := hex.EncodeToString(support.GenerateRandomBytes(32))
masterKeyTest := hex.EncodeToString(support.GenerateRandomBytes(32))

var dbURIPrimaryDev, dbURIPrimaryTest string
switch opt.DBAdapter {
switch answers.DBAdapter {
case "mysql":
dbURIPrimaryDev = getEncryptedValue(fmt.Sprintf("mysql://root:whatever@0.0.0.0:23306/%s", moduleName), masterKeyDev)
dbURIPrimaryTest = getEncryptedValue(fmt.Sprintf("mysql://root:whatever@0.0.0.0:23306/%s_test", moduleName), masterKeyTest)
Expand All @@ -52,12 +66,12 @@ func Scaffold(opt ScaffoldOption) error {
dbURIPrimaryTest = getEncryptedValue(fmt.Sprintf("postgresql://postgres:whatever@0.0.0.0:25432/%s_test?sslmode=disable&connect_timeout=5", moduleName), masterKeyTest)
}

httpCSRFSecretDev := getEncryptedValue(hex.EncodeToString(GenerateRandomBytes(32)), masterKeyDev)
httpSessionSecretsDev := getEncryptedValue(hex.EncodeToString(GenerateRandomBytes(32)), masterKeyDev)
httpCSRFSecretDev := getEncryptedValue(hex.EncodeToString(support.GenerateRandomBytes(32)), masterKeyDev)
httpSessionSecretsDev := getEncryptedValue(hex.EncodeToString(support.GenerateRandomBytes(32)), masterKeyDev)
workerRedisAddrDev := getEncryptedValue("0.0.0.0:26379", masterKeyDev)

httpCSRFSecretTest := getEncryptedValue(hex.EncodeToString(GenerateRandomBytes(32)), masterKeyTest)
httpSessionSecretsTest := getEncryptedValue(hex.EncodeToString(GenerateRandomBytes(32)), masterKeyTest)
httpCSRFSecretTest := getEncryptedValue(hex.EncodeToString(support.GenerateRandomBytes(32)), masterKeyTest)
httpSessionSecretsTest := getEncryptedValue(hex.EncodeToString(support.GenerateRandomBytes(32)), masterKeyTest)
workerRedisAddrTest := getEncryptedValue("0.0.0.0:26379", masterKeyTest)

if err := filepath.Walk(tplPath,
Expand Down Expand Up @@ -96,7 +110,7 @@ func Scaffold(opt ScaffoldOption) error {
"blockHead": "{{block head()}}",
"blockBody": "{{block body()}}",
"blockEnd": "{{end}}",
"dbAdapter": opt.DBAdapter,
"dbAdapter": answers.DBAdapter,
"dbURIPrimaryDev": dbURIPrimaryDev,
"httpCSRFSecretDev": httpCSRFSecretDev,
"httpSessionSecretsDev": httpSessionSecretsDev,
Expand All @@ -107,7 +121,7 @@ func Scaffold(opt ScaffoldOption) error {
"workerRedisAddrTest": workerRedisAddrTest,
"extendApplicationLayout": "{{extends \"../layouts/application.html\"}}",
"projectName": moduleName,
"projectDesc": opt.Description,
"projectDesc": answers.Description,
"masterKeyDev": masterKeyDev,
"masterKeyTest": masterKeyTest,
"translateWelcome": "{{t(\"welcome\", `{\"Name\": \"John Doe\", \"Title\": \"` + t(\"title\") + `\"}`)}}",
Expand All @@ -119,9 +133,11 @@ func Scaffold(opt ScaffoldOption) error {
}

version := strings.ReplaceAll(runtime.Version(), "go", "")
re := regexp.MustCompile(`[a-zA-Z].*`)
version = re.ReplaceAllString(version, "")
versionSplits := strings.Split(version, ".")

return ioutil.WriteFile(
err = ioutil.WriteFile(
"go.mod",
[]byte(`module `+moduleName+`
Expand All @@ -134,11 +150,51 @@ require (
)`),
0777,
)

if err != nil {
return err
}

err = installBackend()
if err != nil {
return err
}

err = installFrontend()
if err != nil {
return err
}

return nil
}

func getEncryptedValue(value string, masterKey string) string {
plaintext := []byte(value)
ciphertext, _ := AESEncrypt(plaintext, []byte(masterKey))
ciphertext, _ := support.AESEncrypt(plaintext, []byte(masterKey))

return hex.EncodeToString(ciphertext)
}

func installBackend() error {
cmd := exec.Command("go", "mod", "download")
cmd.Env = os.Environ()
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
return err
}

return nil
}

func installFrontend() error {
cmd := exec.Command("npm", "install")
cmd.Env = os.Environ()
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
return err
}

return nil
}
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ import (
_ "{{.projectName}}/db/migrate/primary"
_ "{{.projectName}}/db/seed/primary"

// Import GraphQL handler.
_ "{{.projectName}}/pkg/graphql"

// Import HTTP handlers.
_ "{{.projectName}}/pkg/handler"

// Import GraphQL handler.
_ "{{.projectName}}/pkg/graphql"

// Import background jobs.
_ "{{.projectName}}/pkg/job"

Expand Down
Loading

0 comments on commit 65a7965

Please sign in to comment.