├── .gitignore
├── internal
├── errorhandler
│ ├── error.go
│ └── messages.go
├── utils
│ ├── message.go
│ ├── file.go
│ ├── find.go
│ ├── flag.go
│ ├── stack.go
│ └── utils.go
└── constants
│ └── constants.go
├── main.go
├── .pre-commit-config.yaml
├── pickyhelpers
├── delete_git.go
├── progress_bar.go
├── convert_dbconfig.go
├── commands.go
├── update_package_json.go
├── clone_repo.go
├── sources
│ ├── create_ci_source.go
│ └── create_cd_source.go
├── create_ci.go
├── update_dockercompose.go
├── create_cd.go
├── convert_template_database.go
├── update_db_config.go
├── get_stack_info.go
├── create_docker_files.go
├── convert_dbtests.go
├── create_dockercompose.go
├── create_infra.go
├── update_env_files.go
└── convert_queries.go
├── .golangci.yml
├── cmd
├── service.go
├── test.go
├── stacks.go
├── root.go
├── infra.go
├── init.go
└── create.go
├── prompt
├── database.go
├── home.go
├── git.go
├── service.go
├── cicd.go
├── init.go
├── promptutils.go
├── dockercompose.go
├── infra.go
├── deploy.go
└── prompt.go
├── LICENSE
├── go.mod
├── flagcmd
├── init.go
└── stacks.go
├── .github
└── workflows
│ ├── ci.yml
│ └── product-release.yml
├── hbs
├── hbs.go
└── hbsregister.go
├── README.md
└── go.sum
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | coverage.out
3 |
--------------------------------------------------------------------------------
/internal/errorhandler/error.go:
--------------------------------------------------------------------------------
1 | package errorhandler
2 |
3 | import (
4 | "fmt"
5 | "os"
6 | )
7 |
8 | func CheckNilErr(err error) {
9 | if err != nil {
10 | // Check if the user clicks the Control + C button for exiting.
11 | if err.Error() == ErrInterrupt.Error() {
12 | err = ExitMessage
13 | }
14 | fmt.Print(err)
15 | os.Exit(1)
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/wednesday-solutions/picky/cmd"
5 | "github.com/wednesday-solutions/picky/internal/errorhandler"
6 | )
7 |
8 | func main() {
9 | err := cmd.Execute()
10 | if err != nil {
11 | if err.Error() == errorhandler.ErrInterrupt.Error() {
12 | err = errorhandler.ExitMessage
13 | }
14 | errorhandler.CheckNilErr(err)
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/.pre-commit-config.yaml:
--------------------------------------------------------------------------------
1 | fail_fast: true
2 | repos:
3 | - repo: https://github.com/pre-commit/pre-commit-hooks
4 | rev: v4.4.0
5 | hooks:
6 | - id: trailing-whitespace
7 | - id: end-of-file-fixer
8 | - id: check-yaml
9 | - id: check-added-large-files
10 | - repo: https://github.com/dnephin/pre-commit-golang
11 | rev: v0.5.1
12 | hooks:
13 | - id: go-fmt
14 | - id: go-imports
15 | - id: no-go-testing
16 | - id: golangci-lint
17 | - id: go-unit-tests
18 |
--------------------------------------------------------------------------------
/internal/errorhandler/messages.go:
--------------------------------------------------------------------------------
1 | package errorhandler
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 |
7 | "github.com/enescakir/emoji"
8 | )
9 |
10 | var (
11 | ErrInterrupt = errors.New("^C")
12 | ErrExist = errors.New("already exist")
13 | )
14 |
15 | var (
16 | ExitMessage = fmt.Errorf("\n%s%s\n", "Program Exited", emoji.Parse(":exclamation:"))
17 | DoneMessage = fmt.Errorf("\n%s%s\n\n", "Done", emoji.Parse(":sparkles:"))
18 | CompleteMessage = fmt.Errorf("%s%s\n\n", "Completed", emoji.Parse(":sparkles:"))
19 | WaveMessage = emoji.WavingHand
20 | Exclamation = emoji.ExclamationMark
21 | )
22 |
--------------------------------------------------------------------------------
/pickyhelpers/delete_git.go:
--------------------------------------------------------------------------------
1 | package pickyhelpers
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/wednesday-solutions/picky/internal/constants"
7 | "github.com/wednesday-solutions/picky/internal/errorhandler"
8 | "github.com/wednesday-solutions/picky/internal/utils"
9 | )
10 |
11 | // DeleteDotGitFolder deletes .git folder from stack folder.
12 | func (s StackDetails) DeleteDotGitFolder() error {
13 |
14 | path := fmt.Sprintf("%s/%s/%s", utils.CurrentDirectory(),
15 | s.DirName,
16 | constants.DotGitFolder,
17 | )
18 | status, _ := utils.IsExists(path)
19 | if status {
20 | err := utils.RemoveAll(path)
21 | errorhandler.CheckNilErr(err)
22 | }
23 | return nil
24 | }
25 |
--------------------------------------------------------------------------------
/pickyhelpers/progress_bar.go:
--------------------------------------------------------------------------------
1 | package pickyhelpers
2 |
3 | import (
4 | "fmt"
5 | "time"
6 |
7 | "github.com/schollz/progressbar/v3"
8 | "github.com/wednesday-solutions/picky/internal/errorhandler"
9 | )
10 |
11 | func ProgressBar(max int, description string, done chan bool) {
12 |
13 | bar := progressbar.NewOptions(max,
14 | progressbar.OptionEnableColorCodes(true),
15 | progressbar.OptionShowBytes(false),
16 | progressbar.OptionSetWidth(75),
17 | progressbar.OptionSetDescription(fmt.Sprintf("[cyan][1/1][reset] %s...", description)),
18 | progressbar.OptionSetTheme(progressbar.Theme{
19 | Saucer: "[green]=[reset]",
20 | SaucerHead: "[green]>[reset]",
21 | SaucerPadding: " ",
22 | BarStart: "[",
23 | BarEnd: "]",
24 | }),
25 | )
26 |
27 | for i := 0; i < max; i++ {
28 | err := bar.Add(1)
29 | errorhandler.CheckNilErr(err)
30 |
31 | time.Sleep(200 * time.Millisecond)
32 | }
33 | done <- true
34 | }
35 |
--------------------------------------------------------------------------------
/pickyhelpers/convert_dbconfig.go:
--------------------------------------------------------------------------------
1 | package pickyhelpers
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/wednesday-solutions/picky/internal/constants"
7 | "github.com/wednesday-solutions/picky/internal/utils"
8 | )
9 |
10 | func ConvertDBConfig(stack, dirName string) error {
11 |
12 | if stack == constants.NodeExpressGraphqlTemplate {
13 |
14 | file := fmt.Sprintf("%s/%s/%s/%s/%s/%s",
15 | utils.CurrentDirectory(),
16 | dirName,
17 | "server",
18 | "utils",
19 | "testUtils",
20 | "dbConfig.js",
21 | )
22 | dbUri := "mysql://user:password@host:3306/table"
23 | source := fmt.Sprintf(`export const DB_ENV = {
24 | DB_URI: '%s',
25 | %s: 'host',
26 | %s: 'user',
27 | %s: 'password',
28 | %s: 'table'
29 | };
30 | `,
31 | dbUri,
32 | constants.MysqlHost,
33 | constants.MysqlUser,
34 | "MYSQL_PASSWORD",
35 | constants.MysqlDatabase,
36 | )
37 | err := utils.WriteToFile(file, source)
38 | return err
39 | }
40 | return nil
41 | }
42 |
--------------------------------------------------------------------------------
/.golangci.yml:
--------------------------------------------------------------------------------
1 | linters-settings:
2 | errcheck:
3 | check-type-assertions: true
4 | goconst:
5 | min-len: 2
6 | min-occurrences: 3
7 | gocritic:
8 | enabled-tags:
9 | - diagnostic
10 | - experimental
11 | - opinionated
12 | - performance
13 | - style
14 | govet:
15 | check-shadowing: true
16 | enable:
17 | - fieldalignment
18 | nolintlint:
19 | require-explanation: true
20 | require-specific: true
21 |
22 | linters:
23 | disable-all: true
24 | enable:
25 | - bodyclose
26 | # - depguard
27 | # - dogsled
28 | - dupl
29 | - errcheck
30 | - exportloopref
31 | - exhaustive
32 | - goconst
33 | - gofmt
34 | - goimports
35 | - gocyclo
36 | - gosimple
37 | - ineffassign
38 | - misspell
39 | - nolintlint
40 | - nakedret
41 | - prealloc
42 | - predeclared
43 | - staticcheck
44 | - thelper
45 | - tparallel
46 | - unconvert
47 | - unparam
48 |
49 | run:
50 | issues-exit-code: 1
51 |
--------------------------------------------------------------------------------
/cmd/service.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import (
4 | "github.com/spf13/cobra"
5 | "github.com/wednesday-solutions/picky/internal/constants"
6 | "github.com/wednesday-solutions/picky/prompt"
7 | )
8 |
9 | var ServiceCmd = ServiceCmdFn()
10 |
11 | func RunService(*cobra.Command, []string) error {
12 | prompt.PromptHome()
13 | return nil
14 | }
15 |
16 | // ServiceCmdFn represents the ServiceCmd command
17 | func ServiceCmdFn() *cobra.Command {
18 |
19 | var ServiceCmd = &cobra.Command{
20 | Use: constants.Service,
21 | Short: "Pick a Service",
22 | // Long: `Pick a service for your:
23 |
24 | // 1. Web
25 | // 2. Mobile
26 | // 3. Backend
27 |
28 | // from the list of @wednesday-solutions's open source projects.
29 | // `,
30 | Long: `Pick a service for your:
31 |
32 | 1. Web
33 | 2. Backend
34 |
35 | from the list of @wednesday-solutions's open source projects.
36 | `,
37 | RunE: RunService,
38 | }
39 | return ServiceCmd
40 | }
41 |
42 | func init() {
43 |
44 | RootCmd.AddCommand(ServiceCmd)
45 |
46 | ServiceCmd.Flags().BoolP("help", "h", false, "Help for service selection")
47 | }
48 |
--------------------------------------------------------------------------------
/prompt/database.go:
--------------------------------------------------------------------------------
1 | package prompt
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/wednesday-solutions/picky/internal/constants"
7 | "github.com/wednesday-solutions/picky/internal/errorhandler"
8 | )
9 |
10 | func (i *InitInfo) PromptSelectStackDatabase() {
11 | i.SelectDatabase()
12 | }
13 |
14 | func (i *InitInfo) SelectDatabase() {
15 | var p PromptInput
16 | p.Label = "Choose a database"
17 | p.GoBack = PromptSelectService
18 | switch i.Stack {
19 | case constants.NodeHapiTemplate, constants.NodeExpressGraphqlTemplate, constants.GolangEchoTemplate:
20 | p.Items = []string{constants.PostgreSQL, constants.MySQL}
21 | case constants.NodeExpressTemplate:
22 | p.Items = []string{constants.MongoDB}
23 | default:
24 | errorhandler.CheckNilErr(fmt.Errorf("\nSelected stack is invalid%s\n", errorhandler.Exclamation))
25 | }
26 | i.Database, _ = p.PromptSelect()
27 | }
28 |
29 | func PromptAllDatabases() string {
30 | var p PromptInput
31 | p.Label = "Choose a database"
32 | p.GoBack = PromptSelectService
33 | p.Items = []string{constants.PostgreSQL, constants.MySQL, constants.MongoDB}
34 | db, _ := p.PromptSelect()
35 | return db
36 | }
37 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023-Present Wednesday Solutions
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/pickyhelpers/commands.go:
--------------------------------------------------------------------------------
1 | package pickyhelpers
2 |
3 | import (
4 | "fmt"
5 | "path/filepath"
6 |
7 | "github.com/wednesday-solutions/picky/internal/constants"
8 | "github.com/wednesday-solutions/picky/internal/utils"
9 | )
10 |
11 | func InstallDependencies(pkgManager string, path ...string) error {
12 | filePath := filepath.Join(path...)
13 | err := utils.RunCommandWithLogs(filePath, pkgManager, "install")
14 | return err
15 | }
16 |
17 | func BuildSST(pkgManager string) error {
18 | err := utils.RunCommandWithLogs("", "yarn", "build")
19 | return err
20 | }
21 |
22 | func DeploySST(pkgManager, environment string) error {
23 | environment = utils.GetShortEnvName(environment)
24 | arg := fmt.Sprintf("deploy:%s", environment)
25 | err := utils.RunCommandWithLogs("", pkgManager, arg)
26 | return err
27 | }
28 |
29 | func RemoveDeploy(pkgManager, environment string) error {
30 | environment = utils.GetShortEnvName(environment)
31 | arg := fmt.Sprintf("remove:%s", environment)
32 | err := utils.RunCommandWithLogs("", pkgManager, "run", arg)
33 | return err
34 | }
35 |
36 | func ParseDeployOutputs() error {
37 | err := utils.RunCommandWithoutLogs("", "node", constants.ParseSstOutputs)
38 | return err
39 | }
40 |
--------------------------------------------------------------------------------
/pickyhelpers/update_package_json.go:
--------------------------------------------------------------------------------
1 | package pickyhelpers
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/wednesday-solutions/picky/internal/constants"
7 | "github.com/wednesday-solutions/picky/internal/errorhandler"
8 | "github.com/wednesday-solutions/picky/internal/utils"
9 | )
10 |
11 | func UpdatePackageDotJson(stack, dirName string) error {
12 | var pkgManager string
13 | var dependencies []string
14 | var updateCommands []string
15 |
16 | switch stack {
17 | case constants.NodeHapiTemplate:
18 | // postgres database support for mysql templates
19 | dependencies = []string{constants.Pg, constants.PgNative}
20 |
21 | case constants.NodeExpressGraphqlTemplate:
22 | // mysql database support for postgres templates
23 | dependencies = []string{constants.Mysql2}
24 | }
25 | pkgManager = utils.GetPackageManagerOfUser()
26 | if pkgManager == constants.Yarn {
27 | updateCommands = []string{"add"}
28 | updateCommands = append(updateCommands, dependencies...)
29 | } else if pkgManager == constants.Npm {
30 | updateCommands = []string{"install", "--legacy-peer-deps", "--save"}
31 | updateCommands = append(updateCommands, dependencies...)
32 | }
33 | path := fmt.Sprintf("%s/%s", utils.CurrentDirectory(), dirName)
34 | err := utils.RunCommandWithoutLogs(path, pkgManager, updateCommands...)
35 | errorhandler.CheckNilErr(err)
36 | return nil
37 | }
38 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/wednesday-solutions/picky
2 |
3 | go 1.20
4 |
5 | require (
6 | github.com/aymerick/raymond v2.0.2+incompatible
7 | github.com/enescakir/emoji v1.0.0
8 | github.com/fatih/color v1.15.0
9 | github.com/iancoleman/strcase v0.3.0
10 | github.com/rivo/tview v0.0.0-20230814110005-ccc2c8119703
11 | github.com/schollz/progressbar/v3 v3.13.1
12 | github.com/spaceweasel/promptui v0.8.1
13 | github.com/spf13/cobra v1.7.0
14 | )
15 |
16 | require (
17 | github.com/chzyer/readline v1.5.1 // indirect
18 | github.com/gdamore/encoding v1.0.0 // indirect
19 | github.com/gdamore/tcell/v2 v2.6.0 // indirect
20 | github.com/inconshreveable/mousetrap v1.1.0 // indirect
21 | github.com/juju/ansiterm v1.0.0 // indirect
22 | github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
23 | github.com/lunixbochs/vtclean v1.0.0 // indirect
24 | github.com/mattn/go-colorable v0.1.13 // indirect
25 | github.com/mattn/go-isatty v0.0.19 // indirect
26 | github.com/mattn/go-runewidth v0.0.15 // indirect
27 | github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
28 | github.com/rivo/uniseg v0.4.4 // indirect
29 | github.com/spf13/pflag v1.0.5 // indirect
30 | github.com/stretchr/testify v1.8.4 // indirect
31 | golang.org/x/sys v0.11.0 // indirect
32 | golang.org/x/term v0.11.0 // indirect
33 | golang.org/x/text v0.12.0 // indirect
34 | gopkg.in/yaml.v2 v2.4.0 // indirect
35 | )
36 |
--------------------------------------------------------------------------------
/cmd/test.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/rivo/tview"
7 | "github.com/spf13/cobra"
8 | "github.com/wednesday-solutions/picky/internal/errorhandler"
9 | )
10 |
11 | var TestCommand = TestCommandFn()
12 |
13 | func RunTest(*cobra.Command, []string) error {
14 |
15 | var firstName string
16 | var region string
17 |
18 | app := tview.NewApplication()
19 | form := tview.NewForm()
20 | form.AddInputField("Project Infra Name", "", 20, nil, func(text string) {
21 | firstName = text
22 | })
23 | form.AddDropDown("Select Infra Region", []string{"india", "usa"}, 0, func(option string, optionIndex int) {
24 | region = option
25 | })
26 |
27 | form.AddButton("Save", func() {
28 | app.Stop()
29 | fmt.Println("First Name: ", firstName, "\nInfra Region: ", region)
30 | })
31 | form.AddButton("Quit", func() {
32 | app.Stop()
33 | })
34 |
35 | form.SetBorder(true).SetTitle("Enter infra details").SetTitleAlign(tview.AlignLeft)
36 | if err := app.SetRoot(form, true).EnableMouse(true).Run(); err != nil {
37 | errorhandler.CheckNilErr(err)
38 | }
39 |
40 | return nil
41 | }
42 |
43 | func TestCommandFn() *cobra.Command {
44 | testCommand := &cobra.Command{
45 | Use: "test",
46 | Short: "This command is for testing",
47 | Long: "This command is for testing",
48 | RunE: RunTest,
49 | }
50 | return testCommand
51 | }
52 |
53 | func init() {
54 | RootCmd.AddCommand(TestCommand)
55 | }
56 |
--------------------------------------------------------------------------------
/prompt/home.go:
--------------------------------------------------------------------------------
1 | package prompt
2 |
3 | import (
4 | "github.com/wednesday-solutions/picky/internal/constants"
5 | "github.com/wednesday-solutions/picky/internal/utils"
6 | )
7 |
8 | func PromptHome() {
9 | var p PromptInput
10 | p.Label = "Pick an option"
11 | p.GoBack = PromptAlertMessage
12 | var initService bool
13 | stacks, _, _ := utils.GetExistingStacksDatabasesAndDirectories()
14 | if len(stacks) > 0 {
15 | p.Items = []string{constants.InitService}
16 | if ShowPromptGitInit() {
17 | p.Items = append(p.Items, constants.GitInit)
18 | }
19 | p.Items = append(p.Items,
20 | constants.CICD,
21 | constants.DockerCompose,
22 | constants.SetupInfra,
23 | constants.Deploy,
24 | )
25 | if ShowRemoveDeploy() {
26 | p.Items = append(p.Items, constants.RemoveDeploy)
27 | }
28 | p.Items = append(p.Items, constants.Exit)
29 | response, _ := p.PromptSelect()
30 | switch response {
31 | case constants.InitService:
32 | initService = true
33 | case constants.DockerCompose:
34 | PromptDockerCompose()
35 | case constants.CICD:
36 | PromptCICD()
37 | case constants.SetupInfra:
38 | PromptSetupInfra()
39 | case constants.Deploy:
40 | PromptDeploy()
41 | case constants.RemoveDeploy:
42 | PromptRemoveDeploy()
43 | case constants.GitInit:
44 | PromptGitInit()
45 | case constants.Exit:
46 | PromptExit()
47 | }
48 | }
49 | if len(stacks) == 0 || initService {
50 | PromptSelectService()
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/pickyhelpers/clone_repo.go:
--------------------------------------------------------------------------------
1 | package pickyhelpers
2 |
3 | import (
4 | "github.com/wednesday-solutions/picky/internal/constants"
5 | "github.com/wednesday-solutions/picky/internal/errorhandler"
6 | "github.com/wednesday-solutions/picky/internal/utils"
7 | )
8 |
9 | // StackDetails is the collection of all stack details.
10 | type StackDetails struct {
11 | // Service refers to the service type of the stack.
12 | Service string
13 |
14 | // Stacks refers to Wednesday Solutions open source templates.
15 | Stack string
16 |
17 | // DirName refers to the name of directory of stack.
18 | DirName string
19 |
20 | // CurrentDir refers to the root directory.
21 | CurrentDir string
22 |
23 | // Database refers to the database of selected stack.
24 | Database string
25 |
26 | // Environment refers to the environment
27 | Environment string
28 |
29 | // StackInfo consist of all the details about stacks.
30 | StackInfo map[string]interface{}
31 | }
32 |
33 | func (s StackDetails) CloneRepo() error {
34 |
35 | // Download the selected stack.
36 | err := utils.RunCommandWithoutLogs("", "git", "clone", constants.Repos()[s.Stack], s.DirName)
37 | errorhandler.CheckNilErr(err)
38 |
39 | // Delete cd.yml file from the cloned repo.
40 | cdFilePatch := s.CurrentDir + "/" + s.DirName + constants.CDFilePathURL
41 | status, _ := utils.IsExists(cdFilePatch)
42 | if status {
43 | err = utils.RemoveFile(cdFilePatch)
44 | errorhandler.CheckNilErr(err)
45 | }
46 | return nil
47 | }
48 |
--------------------------------------------------------------------------------
/pickyhelpers/sources/create_ci_source.go:
--------------------------------------------------------------------------------
1 | package sources
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/wednesday-solutions/picky/internal/utils"
7 | )
8 |
9 | func CISource(stack, stackDir, environment string) string {
10 | envName := utils.GetShortEnvName(environment)
11 | source := fmt.Sprintf(`name: CI %s
12 | on:
13 | push:
14 | branches:
15 | - dev
16 | - qa
17 | - master
18 | paths: "%s/**"
19 |
20 | pull_request:
21 | paths: "%s/**"
22 |
23 | # Allows to run this workflow manually from the Actions tab
24 | workflow_dispatch:
25 |
26 | jobs:
27 | build-and-test:
28 | name: Build & Test
29 | runs-on: ubuntu-latest
30 | defaults:
31 | run:
32 | working-directory: ./%s
33 | strategy:
34 | matrix:
35 | node-version: [16.14.x]
36 |
37 | steps:
38 | - uses: actions/checkout@v3
39 | - name: Use Node.js ${{ matrix.node-version }}
40 | uses: actions/setup-node@v2
41 | with:
42 | node-version: ${{ matrix.node-version }}
43 | cache: "yarn"
44 | cache-dependency-path: ./%s/package.json
45 |
46 | - name: Install dependencies
47 | run: yarn
48 |
49 | - name: Lint
50 | run: yarn lint
51 |
52 | - name: Build
53 | run: yarn build:%s
54 |
55 | - name: Test
56 | run: yarn run test
57 | `,
58 | stackDir, stackDir, stackDir, stackDir,
59 | stackDir, envName,
60 | )
61 | return source
62 | }
63 |
--------------------------------------------------------------------------------
/flagcmd/init.go:
--------------------------------------------------------------------------------
1 | package flagcmd
2 |
3 | import (
4 | "fmt"
5 | "strings"
6 |
7 | "github.com/wednesday-solutions/picky/internal/constants"
8 | "github.com/wednesday-solutions/picky/internal/errorhandler"
9 | "github.com/wednesday-solutions/picky/internal/utils"
10 | "github.com/wednesday-solutions/picky/prompt"
11 | )
12 |
13 | type InitInfo struct {
14 | Service string
15 | Stack string
16 | Database string
17 | Directory string
18 | }
19 |
20 | func (i *InitInfo) FlagInit() error {
21 |
22 | i.Database = utils.GetDatabase(i.Database)
23 |
24 | i.Stack = utils.GetStackByFlags(i.Stack)
25 | if i.Stack == "" {
26 | return fmt.Errorf("Entered stack is invalid")
27 | }
28 | if i.Stack == constants.GolangEchoTemplate {
29 | i.Stack = fmt.Sprintf("%s-%s", strings.Split(i.Stack, " ")[0], i.Database)
30 | }
31 | i.Directory = utils.CreateStackDirectory(i.Directory, i.Stack, i.Database)
32 |
33 | status, _ := utils.IsExists(fmt.Sprintf("%s/%s", utils.CurrentDirectory(), i.Directory))
34 | if status {
35 | return fmt.Errorf("Entered directory %s already exists\n", i.Directory)
36 | }
37 | err := utils.MakeDirectory(utils.CurrentDirectory(), i.Directory)
38 | errorhandler.CheckNilErr(err)
39 |
40 | fmt.Printf("\nService: %s\nStack: %s\nDatabase: %s\nDirectory: %s\n\n",
41 | i.Service, i.Stack, i.Database, i.Directory,
42 | )
43 |
44 | var ii prompt.InitInfo
45 | ii.Service = i.Service
46 | ii.Stack = i.Stack
47 | ii.Database = i.Database
48 | ii.DirName = i.Directory
49 | err = ii.StackInitialize()
50 | return err
51 | }
52 |
--------------------------------------------------------------------------------
/pickyhelpers/create_ci.go:
--------------------------------------------------------------------------------
1 | package pickyhelpers
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/wednesday-solutions/picky/internal/constants"
7 | "github.com/wednesday-solutions/picky/internal/errorhandler"
8 | "github.com/wednesday-solutions/picky/internal/utils"
9 | "github.com/wednesday-solutions/picky/pickyhelpers/sources"
10 | )
11 |
12 | func CreateCI(stackDirs []string) error {
13 | workflowsPath := fmt.Sprintf("%s/%s", utils.CurrentDirectory(),
14 | constants.GithubWorkflowsDir)
15 |
16 | utils.CreateGithubWorkflowDir()
17 | var stackCIPath, stack string
18 | var status bool
19 | for _, dir := range stackDirs {
20 | stackCIPath = fmt.Sprintf("%s/ci-%s.yml", workflowsPath, dir)
21 | status, _ = utils.IsExists(stackCIPath)
22 | if !status {
23 | stack, _ = utils.FindStackAndDatabase(dir)
24 | err := CreateStackCI(stackCIPath, dir, stack)
25 | errorhandler.CheckNilErr(err)
26 | }
27 | }
28 | return nil
29 | }
30 |
31 | // CreateStackCI creates and writes CI for the given stack.
32 | func CreateStackCI(path, stackDir, stack string) error {
33 | var environment, source string
34 | if stack == constants.NodeExpressGraphqlTemplate {
35 | environment = constants.Development
36 | } else {
37 | environment = constants.Dev
38 | }
39 | if stack != constants.GolangEchoTemplate {
40 | source = sources.CISource(stack, stackDir, environment)
41 |
42 | err := utils.WriteToFile(path, source)
43 | errorhandler.CheckNilErr(err)
44 | } else {
45 | err := utils.PrintWarningMessage(fmt.Sprintf(
46 | "CI of '%s' is in work in progress..!", stack,
47 | ))
48 | errorhandler.CheckNilErr(err)
49 | }
50 | return nil
51 | }
52 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: service-picker
2 |
3 | on:
4 | pull_request:
5 | branches:
6 | - main
7 | - develop
8 |
9 | jobs:
10 | lint-test-build:
11 | runs-on: ubuntu-latest
12 | steps:
13 | - uses: actions/checkout@v3
14 |
15 | - uses: actions/setup-python@v4
16 | with:
17 | python-version: "3.x"
18 |
19 | - name: Set up Go
20 | uses: actions/setup-go@v4
21 | with:
22 | go-version: "1.21"
23 | cache: false
24 |
25 | - name: Install pre-commit dependencies
26 | run: |
27 | go install github.com/fzipp/gocyclo/cmd/gocyclo@latest
28 | go install golang.org/x/tools/cmd/goimports@latest
29 | go install github.com/go-critic/go-critic/cmd/gocritic@latest
30 | go install golang.org/x/lint/golint@latest
31 | go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
32 |
33 | - name: Run pre-commit
34 | uses: pre-commit/action@v3.0.0
35 |
36 | - name: Build
37 | run: go build ./...
38 |
39 | - name: Test
40 | run: go test ./... -gcflags=all=-l -coverprofile=coverage.out
41 |
42 | golangci-lint:
43 | runs-on: ubuntu-latest
44 | steps:
45 | - uses: actions/checkout@v3
46 |
47 | - name: Install Go
48 | uses: actions/setup-go@v4
49 | with:
50 | go-version: "1.20"
51 | cache: false
52 |
53 | - name: golangci-lint
54 | uses: golangci/golangci-lint-action@v3
55 | with:
56 | version: v1.54
57 | args: --timeout=30m --config=./.golangci.yml --issues-exit-code=0
58 |
--------------------------------------------------------------------------------
/cmd/stacks.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/spf13/cobra"
7 | "github.com/wednesday-solutions/picky/flagcmd"
8 | "github.com/wednesday-solutions/picky/internal/constants"
9 | "github.com/wednesday-solutions/picky/internal/errorhandler"
10 | )
11 |
12 | var StacksCmd = StacksCmdFn()
13 |
14 | func RunStacks(cmd *cobra.Command, args []string) error {
15 |
16 | var err error
17 | var flag flagcmd.StackFlag
18 | flag.A, err = cmd.Flags().GetBool("allstacks")
19 | errorhandler.CheckNilErr(err)
20 |
21 | flag.E, err = cmd.Flags().GetBool("existingstacks")
22 | errorhandler.CheckNilErr(err)
23 |
24 | flag.W, err = cmd.Flags().GetBool("webstacks")
25 | errorhandler.CheckNilErr(err)
26 |
27 | flag.M, err = cmd.Flags().GetBool("mobilestacks")
28 | errorhandler.CheckNilErr(err)
29 |
30 | flag.B, err = cmd.Flags().GetBool("backendstacks")
31 | errorhandler.CheckNilErr(err)
32 |
33 | userOutput := flag.FlagStacks()
34 | fmt.Println(userOutput)
35 |
36 | return nil
37 | }
38 |
39 | func StacksCmdFn() *cobra.Command {
40 | stacksCmd := &cobra.Command{
41 | Use: constants.Stacks,
42 | Short: "See stacks",
43 | Long: "See stacks",
44 | Args: cobra.NoArgs,
45 | RunE: RunStacks,
46 | }
47 | return stacksCmd
48 | }
49 |
50 | func init() {
51 | RootCmd.AddCommand(StacksCmd)
52 | // declaring flags
53 | StacksCmd.Flags().BoolP("allstacks", "a", false, "all available stacks")
54 | StacksCmd.Flags().BoolP("existingstacks", "e", true, "all existing stacks")
55 | StacksCmd.Flags().BoolP("webstacks", "w", false, "web stacks")
56 | StacksCmd.Flags().BoolP("mobilestacks", "m", false, "mobile stacks")
57 | StacksCmd.Flags().BoolP("backendstacks", "b", false, "backend stacks")
58 | }
59 |
--------------------------------------------------------------------------------
/cmd/root.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/spf13/cobra"
7 | "github.com/wednesday-solutions/picky/internal/constants"
8 | "github.com/wednesday-solutions/picky/internal/errorhandler"
9 | )
10 |
11 | // RootCmd is the command variable of root command picky.
12 | var RootCmd = RootCmdFn()
13 | var version = "0.0.9"
14 |
15 | // RootCmd represents the base command when called without any subcommands
16 | func RootCmdFn() *cobra.Command {
17 |
18 | var cmd = &cobra.Command{
19 | Use: constants.Picky,
20 | Version: version,
21 | Short: "Service Picker",
22 | Long: fmt.Sprintf(`
23 | Hello%s
24 | Welcome to Service Picker.
25 |
26 | It contains a number of @wednesday-solutions's open source projects, connected and working together. Pick whatever you need and build your own ecosystem.
27 |
28 | This repo will have support for production applications using the following tech stacks
29 | - Frontend
30 | - react
31 | - next
32 | - Backend
33 | - Node (Hapi - REST API)
34 | - Node (Express - GraphQL API)
35 | - Databases
36 | - MySQL
37 | - PostgreSQL
38 | - Cache
39 | - Redis
40 | - Infrastructure Provider
41 | - AWS
42 |
43 | Wednesday Solutions`, errorhandler.WaveMessage),
44 | }
45 | return cmd
46 | }
47 |
48 | // Execute adds all child commands to the root command and sets flags appropriately.
49 | // This is called by main.main(). It only needs to happen once to the rootCmd.
50 | func Execute() error {
51 | RootCmd.CompletionOptions.DisableDefaultCmd = true
52 |
53 | err := RootCmd.Execute()
54 | if err != nil {
55 | if err.Error() == errorhandler.ErrInterrupt.Error() {
56 | err = errorhandler.ExitMessage
57 | }
58 | errorhandler.CheckNilErr(err)
59 | }
60 | return nil
61 | }
62 |
--------------------------------------------------------------------------------
/pickyhelpers/update_dockercompose.go:
--------------------------------------------------------------------------------
1 | package pickyhelpers
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/iancoleman/strcase"
7 | "github.com/wednesday-solutions/picky/hbs"
8 | "github.com/wednesday-solutions/picky/internal/constants"
9 | "github.com/wednesday-solutions/picky/internal/errorhandler"
10 | "github.com/wednesday-solutions/picky/internal/utils"
11 | )
12 |
13 | func UpdateDockerCompose(stack, dirName string, stackInfo map[string]interface{}) error {
14 | var updateDockerCompose bool
15 | snakeCaseDirName := strcase.ToSnake(dirName)
16 | source := fmt.Sprintf(`version: '3'
17 | services:
18 | %s_db:
19 | image: {{dbVersion database}}
20 | ports:
21 | - {{portConnection database}}
22 | restart: always
23 | env_file:
24 | - .env.docker
25 |
26 | redis:
27 | image: 'redis:6-alpine'
28 | ports:
29 | - {{portConnection redis}}
30 | command: ['redis-server', '--bind', 'redis', '--port', '6379']
31 |
32 | app:
33 | build:
34 | context: .
35 | args:
36 | ENVIRONMENT_NAME: docker
37 | depends_on:
38 | - redis
39 | - %s_db
40 | restart: always
41 | ports:
42 | - {{portConnection backend}}
43 | env_file:
44 | - .env.docker
45 | `, snakeCaseDirName, snakeCaseDirName)
46 | switch stack {
47 | case constants.NodeExpressGraphqlTemplate, constants.NodeHapiTemplate:
48 | updateDockerCompose = true
49 | default:
50 | updateDockerCompose = false
51 | }
52 | if updateDockerCompose {
53 | path := fmt.Sprintf("%s/%s/%s", utils.CurrentDirectory(),
54 | dirName,
55 | constants.DockerComposeFile,
56 | )
57 | err := hbs.ParseAndWriteToFile(source, path, stackInfo)
58 | errorhandler.CheckNilErr(err)
59 | }
60 | return nil
61 | }
62 |
--------------------------------------------------------------------------------
/prompt/git.go:
--------------------------------------------------------------------------------
1 | package prompt
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/wednesday-solutions/picky/internal/constants"
7 | "github.com/wednesday-solutions/picky/internal/errorhandler"
8 | "github.com/wednesday-solutions/picky/internal/utils"
9 | )
10 |
11 | func ShowPromptGitInit() bool {
12 | status, _ := utils.IsExists(fmt.Sprintf("%s/%s",
13 | utils.CurrentDirectory(),
14 | constants.DotGitFolder),
15 | )
16 | if status {
17 | return false
18 | } else {
19 | return true
20 | }
21 | }
22 |
23 | func PromptGitInit() {
24 | var p PromptInput
25 | p.Label = "Do you want to initialize git"
26 | p.GoBack = PromptHome
27 | response := p.PromptYesOrNoSelect()
28 | if response {
29 | err := GitInit()
30 | errorhandler.CheckNilErr(err)
31 | }
32 | PromptHome()
33 | }
34 |
35 | func GitInit() error {
36 | err := utils.RunCommandWithLogs("", constants.Git, constants.Init)
37 | if err != nil {
38 | return err
39 | }
40 | // create .gitignore file
41 | file := fmt.Sprintf("%s/%s",
42 | utils.CurrentDirectory(),
43 | constants.DotGitIgnore,
44 | )
45 | err = utils.CreateFile(file)
46 | errorhandler.CheckNilErr(err)
47 |
48 | err = WriteDotGitignoreFile(file)
49 | return err
50 | }
51 |
52 | func WriteDotGitignoreFile(file string) error {
53 | source := constants.NodeModules
54 | _, _, directories := utils.GetExistingStacksDatabasesAndDirectories()
55 | for _, dir := range directories {
56 | source = fmt.Sprintf("%s\n%s/%s", source, dir, constants.NodeModules)
57 | }
58 | source = fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n%s\n",
59 | source,
60 | constants.DotSst,
61 | constants.OutputsJson,
62 | "cdk.context.json",
63 | "build",
64 | "out",
65 | )
66 | err := utils.WriteToFile(file, source)
67 | return err
68 | }
69 |
--------------------------------------------------------------------------------
/pickyhelpers/create_cd.go:
--------------------------------------------------------------------------------
1 | package pickyhelpers
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/wednesday-solutions/picky/internal/constants"
7 | "github.com/wednesday-solutions/picky/internal/errorhandler"
8 | "github.com/wednesday-solutions/picky/internal/utils"
9 | "github.com/wednesday-solutions/picky/pickyhelpers/sources"
10 | )
11 |
12 | func (s StackDetails) CreateCDFile() error {
13 |
14 | utils.CreateGithubWorkflowDir()
15 | cdDestination := fmt.Sprintf("%s/%s/cd-%s.yml",
16 | utils.CurrentDirectory(),
17 | constants.GithubWorkflowsDir,
18 | s.DirName,
19 | )
20 | status, _ := utils.IsExists(cdDestination)
21 | if !status {
22 |
23 | done := make(chan bool)
24 | go ProgressBar(20, "Generating", done)
25 |
26 | // Create CD File
27 | err := utils.CreateFile(cdDestination)
28 | errorhandler.CheckNilErr(err)
29 |
30 | var cdSource string
31 | if s.Service == constants.Backend {
32 | cdSource = sources.CDBackendSource(s.Stack, s.DirName, s.Environment)
33 | } else if s.Service == constants.Web {
34 | cdSource = sources.CDWebSource(s.Stack, s.DirName)
35 | }
36 |
37 | // Write CDFileData to CD File
38 | err = utils.WriteToFile(cdDestination, cdSource)
39 | errorhandler.CheckNilErr(err)
40 |
41 | <-done
42 | fmt.Printf("\n%s %s", "Generating", errorhandler.CompleteMessage)
43 |
44 | } else {
45 | fmt.Println("The", s.Service, s.Stack, "CD you are looking to create already exists")
46 | return errorhandler.ErrExist
47 | }
48 | return nil
49 | }
50 |
51 | func CreateTaskDefinition(stackDir, environment string) error {
52 | environment = utils.GetShortEnvName(environment)
53 |
54 | file := fmt.Sprintf("%s/%s/%s-%s.json",
55 | utils.CurrentDirectory(),
56 | stackDir,
57 | "task-definition",
58 | environment,
59 | )
60 | source := sources.TaskDefinitionSource(environment, stackDir)
61 | var err error
62 | if source != "" {
63 | err = utils.WriteToFile(file, source)
64 | }
65 | return err
66 | }
67 |
--------------------------------------------------------------------------------
/prompt/service.go:
--------------------------------------------------------------------------------
1 | package prompt
2 |
3 | import (
4 | "github.com/wednesday-solutions/picky/internal/constants"
5 | "github.com/wednesday-solutions/picky/internal/errorhandler"
6 | "github.com/wednesday-solutions/picky/internal/utils"
7 | )
8 |
9 | var services = []string{
10 | constants.Web,
11 | // constants.Mobile,
12 | constants.Backend,
13 | }
14 |
15 | func PromptSelectService() {
16 | var i InitInfo
17 | var p PromptInput
18 | p.Label = "Pick a service"
19 | p.Items = services
20 | p.GoBack = PromptAlertMessage
21 |
22 | var index int
23 | i.Service, index = p.PromptSelect()
24 | services = ResetItems(p.Items, &index)
25 | i.PromptSelectStack()
26 | }
27 |
28 | func (i *InitInfo) PromptSelectStack() {
29 |
30 | i.Stack = i.PromptStack()
31 | _ = DisplayMultipleStackWarningMessage(i.Service)
32 | if i.Service == constants.Backend {
33 | i.PromptSelectStackDatabase()
34 | }
35 | if DisplayMultipleStackWarningMessage(i.Service) {
36 | // It will redirect to home if the selected service already exists.
37 | PromptHome()
38 | }
39 | i.DirName = i.PromptGetDirectoryName()
40 | i.PromptSelectInit()
41 | }
42 |
43 | // DisplayMultipleStackWarningMessage prints message if the selected service is already exists.
44 | func DisplayMultipleStackWarningMessage(service string) bool {
45 | var serviceExist bool
46 | backendExist, webExist, _ := utils.IsBackendWebAndMobileExist()
47 | var err error
48 | if service == constants.Web {
49 | if webExist {
50 | err = utils.PrintWarningMessage("We are working on supporting multiple frontend stacks in the upcoming releases!")
51 | errorhandler.CheckNilErr(err)
52 | serviceExist = true
53 | }
54 | } else if service == constants.Backend {
55 | if backendExist {
56 | err = utils.PrintWarningMessage("We are working on supporting multiple backend stacks in the upcoming releases!")
57 | errorhandler.CheckNilErr(err)
58 | serviceExist = true
59 | }
60 | }
61 | return serviceExist
62 | }
63 |
--------------------------------------------------------------------------------
/pickyhelpers/convert_template_database.go:
--------------------------------------------------------------------------------
1 | package pickyhelpers
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/wednesday-solutions/picky/internal/constants"
7 | "github.com/wednesday-solutions/picky/internal/errorhandler"
8 | "github.com/wednesday-solutions/picky/internal/utils"
9 | )
10 |
11 | func (s StackDetails) ConvertTemplateDatabase() error {
12 |
13 | isDatabaseSupported := true
14 |
15 | switch s.Stack {
16 | case constants.NodeHapiTemplate:
17 | if s.Database == constants.PostgreSQL {
18 | isDatabaseSupported = false
19 | }
20 |
21 | case constants.NodeExpressGraphqlTemplate:
22 | if s.Database == constants.MySQL {
23 | isDatabaseSupported = false
24 | }
25 | default:
26 | return nil
27 | }
28 |
29 | if !isDatabaseSupported {
30 | // Add new dependencies to package.json
31 | err := UpdatePackageDotJson(s.Stack, s.DirName)
32 | errorhandler.CheckNilErr(err)
33 |
34 | // Update env files with respect to new database
35 | err = UpdateEnvFiles(s.Stack, s.DirName)
36 | errorhandler.CheckNilErr(err)
37 |
38 | // Convert DB Connection into MySQL.
39 | dbConfigFile := fmt.Sprintf("%s/%s/%s", utils.CurrentDirectory(), s.DirName, "config/db.js")
40 | err = UpdateDBConfig(s.Stack, dbConfigFile, s.StackInfo)
41 | errorhandler.CheckNilErr(err)
42 |
43 | // Convert queries
44 | err = ConvertQueries(s.Stack, s.DirName)
45 | errorhandler.CheckNilErr(err)
46 |
47 | // Convert testDBConfig
48 | err = ConvertDBConfig(s.Stack, s.DirName)
49 | errorhandler.CheckNilErr(err)
50 |
51 | // Convert dbTests
52 | err = ConvertDBTests(s.Stack, s.DirName)
53 | errorhandler.CheckNilErr(err)
54 |
55 | // Update docker-compose file
56 | err = UpdateDockerCompose(s.Stack, s.DirName, s.StackInfo)
57 | errorhandler.CheckNilErr(err)
58 | } else {
59 | // Update DB_HOST in .env.docker file
60 | err := UpdateEnvDockerFileForDefaultDBInTemplate(s.Stack, s.DirName)
61 | errorhandler.CheckNilErr(err)
62 | }
63 | return nil
64 | }
65 |
--------------------------------------------------------------------------------
/cmd/infra.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/spf13/cobra"
7 | "github.com/wednesday-solutions/picky/internal/constants"
8 | "github.com/wednesday-solutions/picky/internal/errorhandler"
9 | "github.com/wednesday-solutions/picky/internal/utils"
10 | "github.com/wednesday-solutions/picky/prompt"
11 | )
12 |
13 | var InfraCmd = InfraCmdFn()
14 | var (
15 | stacks []string
16 | cloudProvider string
17 | environment string
18 | )
19 |
20 | func InfraSetup(cmd *cobra.Command, args []string) error {
21 | var (
22 | err error
23 | stacks []string
24 | cloudProvider string
25 | environment string
26 | )
27 | stacks, err = cmd.Flags().GetStringSlice(constants.Stacks)
28 | errorhandler.CheckNilErr(err)
29 |
30 | err = utils.CheckStacksExist(stacks)
31 | if err != nil {
32 | return err
33 | }
34 | cloudProvider, err = cmd.Flags().GetString(constants.CloudProvider)
35 | errorhandler.CheckNilErr(err)
36 | environment, err = cmd.Flags().GetString(constants.Environment)
37 | errorhandler.CheckNilErr(err)
38 |
39 | cloudProvider = utils.GetCloudProvider(cloudProvider)
40 | environment = utils.GetEnvironmentValue(environment)
41 |
42 | userOutput := "\nSelected stacks:"
43 | for idx, stack := range stacks {
44 | userOutput = fmt.Sprintf("%s\n %d. %s", userOutput, idx+1, stack)
45 | }
46 | fmt.Printf("%s\nCloud Provider: %s\nEnvironment: %s\n\n",
47 | userOutput, cloudProvider, environment,
48 | )
49 | err = prompt.CreateInfra(stacks, cloudProvider, environment)
50 | errorhandler.CheckNilErr(err)
51 | return err
52 | }
53 |
54 | func InfraCmdFn() *cobra.Command {
55 | var InfraCmd = &cobra.Command{
56 | Use: constants.Infra,
57 | RunE: InfraSetup,
58 | }
59 | return InfraCmd
60 | }
61 |
62 | func init() {
63 | ServiceCmd.AddCommand(InfraCmd)
64 | InfraCmd.Flags().StringSliceVarP(
65 | &stacks, constants.Stacks, "t", utils.GetExistingStacks(), utils.UseInfraStacks(),
66 | )
67 | InfraCmd.Flags().StringVarP(
68 | &cloudProvider, constants.CloudProvider, "p", constants.AWS, utils.UseCloudProvider(),
69 | )
70 | InfraCmd.Flags().StringVarP(
71 | &environment, constants.Environment, "e", constants.Development, utils.UseEnvironment(),
72 | )
73 | }
74 |
--------------------------------------------------------------------------------
/pickyhelpers/update_db_config.go:
--------------------------------------------------------------------------------
1 | package pickyhelpers
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/wednesday-solutions/picky/hbs"
7 | "github.com/wednesday-solutions/picky/internal/constants"
8 | "github.com/wednesday-solutions/picky/internal/errorhandler"
9 | )
10 |
11 | func UpdateDBConfig(stack, dbFile string, stackInfo map[string]interface{}) error {
12 |
13 | var dbConfigSource string
14 |
15 | switch stack {
16 | case constants.NodeHapiTemplate:
17 | dbConfigSource = fmt.Sprintf(`const pg = require('pg');
18 |
19 | module.exports = {
20 | url:
21 | process.env.DB_URI ||
22 | %spostgres://${process.env.POSTGRES_USER}:${process.env.POSTGRES_PASSWORD}@${process.env.POSTGRES_HOST}/${
23 | process.env.POSTGRES_DB
24 | }%s,
25 | host: process.env.POSTGRES_HOST,
26 | dialectModule: pg,
27 | dialect: 'postgres',
28 | pool: {
29 | min: 0,
30 | max: 10,
31 | idle: 10000,
32 | },
33 | define: {
34 | underscored: true,
35 | timestamps: false,
36 | },
37 | };
38 | `, "`", "`")
39 |
40 | case constants.NodeExpressGraphqlTemplate:
41 | dbConfigSource = fmt.Sprintf(`const Sequelize = require('sequelize');
42 | const mysql2 = require('mysql2');
43 | const dotenv = require('dotenv');
44 |
45 | dotenv.config({ path: {{envEnvironmentName}} });
46 |
47 | module.exports = {
48 | url:
49 | process.env.DB_URI ||
50 | %smysql://${process.env.MYSQL_USER}:${process.env.MYSQL_PASSWORD}@${process.env.MYSQL_HOST}/${
51 | process.env.MYSQL_DATABASE
52 | }%s,
53 | host: process.env.MYSQL_HOST,
54 | dialectModule: mysql2,
55 | dialect: 'mysql',
56 | pool: {
57 | min: 0,
58 | max: 10,
59 | idle: 10000
60 | },
61 | define: {
62 | underscored: true,
63 | timestamps: false
64 | },
65 | retry: {
66 | match: [
67 | 'unknown timed out',
68 | Sequelize.TimeoutError,
69 | 'timed',
70 | 'timeout',
71 | 'TimeoutError',
72 | 'Operation timeout',
73 | 'refuse',
74 | 'SQLITE_BUSY'
75 | ],
76 | max: 10 // maximum amount of tries.
77 | }
78 | };
79 | `, "`", "`")
80 |
81 | default:
82 | return fmt.Errorf("Selected stack is invalid")
83 | }
84 |
85 | err := hbs.ParseAndWriteToFile(dbConfigSource, dbFile, stackInfo)
86 | errorhandler.CheckNilErr(err)
87 |
88 | return nil
89 | }
90 |
--------------------------------------------------------------------------------
/pickyhelpers/get_stack_info.go:
--------------------------------------------------------------------------------
1 | package pickyhelpers
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/iancoleman/strcase"
7 | "github.com/wednesday-solutions/picky/internal/constants"
8 | "github.com/wednesday-solutions/picky/internal/utils"
9 | )
10 |
11 | func (s StackDetails) GetStackInfo() map[string]interface{} {
12 |
13 | var webDir, mobileDir, backendDir string
14 | var webDirectories, backendPgDirectories, backendMysqlDirectories []string
15 | currentDir := utils.CurrentDirectory()
16 | projectName := strcase.ToSnake(utils.GetProjectName())
17 |
18 | _, databases, directories := utils.GetExistingStacksDatabasesAndDirectories()
19 | for i, dirName := range directories {
20 | s.Service = utils.FindService(dirName)
21 | switch s.Service {
22 | case constants.Web:
23 | webDir = dirName
24 | webDirectories = append(webDirectories, dirName)
25 | case constants.Mobile:
26 | mobileDir = dirName
27 | case constants.Backend:
28 | backendDir = dirName
29 | if databases[i] == constants.PostgreSQL {
30 | backendPgDirectories = append(backendPgDirectories, dirName)
31 | } else if databases[i] == constants.MySQL {
32 | backendMysqlDirectories = append(backendMysqlDirectories, dirName)
33 | }
34 | }
35 | }
36 | stackDestination := map[string]string{
37 | constants.WebStatus: currentDir + "/" + webDir,
38 | constants.MobileStatus: currentDir + "/" + mobileDir,
39 | constants.BackendStatus: currentDir + "/" + backendDir,
40 | }
41 | stackInfo := make(map[string]interface{})
42 |
43 | for status, destination := range stackDestination {
44 | if destination != fmt.Sprintf("%s/", currentDir) {
45 | stackInfo[status], _ = utils.IsExists(destination)
46 | } else {
47 | stackInfo[status] = false
48 | }
49 | }
50 | stackInfo[constants.Stack] = s.Stack
51 | stackInfo[constants.Database] = s.Database
52 | stackInfo[constants.ProjectName] = projectName
53 | stackInfo[constants.WebDirName] = webDir
54 | stackInfo[constants.MobileDirName] = mobileDir
55 | stackInfo[constants.BackendDirName] = backendDir
56 | stackInfo[constants.ExistingDirectories] = directories
57 | stackInfo[constants.Environment] = s.Environment
58 | stackInfo[constants.WebDirectories] = webDirectories
59 | stackInfo[constants.BackendPgDirectories] = backendPgDirectories
60 | stackInfo[constants.BackendMysqlDirectories] = backendMysqlDirectories
61 |
62 | return stackInfo
63 | }
64 |
--------------------------------------------------------------------------------
/cmd/init.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/spf13/cobra"
7 | "github.com/wednesday-solutions/picky/flagcmd"
8 | "github.com/wednesday-solutions/picky/internal/constants"
9 | "github.com/wednesday-solutions/picky/internal/errorhandler"
10 | "github.com/wednesday-solutions/picky/internal/utils"
11 | )
12 |
13 | func InitStack(cmd *cobra.Command, args []string) error {
14 |
15 | var (
16 | i flagcmd.InitInfo
17 | err error
18 | errorMessage string
19 | )
20 |
21 | i.Service, err = cmd.Flags().GetString(constants.Service)
22 | errorhandler.CheckNilErr(err)
23 | i.Stack, err = cmd.Flags().GetString(constants.Stack)
24 | errorhandler.CheckNilErr(err)
25 | i.Database, err = cmd.Flags().GetString(constants.Database)
26 | errorhandler.CheckNilErr(err)
27 | i.Directory, err = cmd.Flags().GetString(constants.Directory)
28 | errorhandler.CheckNilErr(err)
29 |
30 | allFlagsExist := true
31 | if i.Service == "" {
32 | allFlagsExist = false
33 | errorMessage = fmt.Sprintf("%s%s\n", errorMessage,
34 | "add service with the flag of '--service'",
35 | )
36 | }
37 | if i.Stack == "" {
38 | allFlagsExist = false
39 | errorMessage = fmt.Sprintf("%s%s\n", errorMessage,
40 | "add stack with the flag of '--stack'",
41 | )
42 | }
43 | if i.Database == "" && service == constants.Backend {
44 | allFlagsExist = false
45 | errorMessage = fmt.Sprintf("%s%s\n", errorMessage,
46 | "add database with the flag of '--database'",
47 | )
48 | }
49 | if i.Directory == "" {
50 | allFlagsExist = false
51 | errorMessage = fmt.Sprintf("%s%s\n", errorMessage,
52 | "add directory with the flag of '--directory'",
53 | )
54 | }
55 | if !allFlagsExist {
56 | return fmt.Errorf("%s", errorMessage)
57 | }
58 | err = i.FlagInit()
59 | return err
60 | }
61 |
62 | func InitCmdFn() *cobra.Command {
63 | var InitCommand = &cobra.Command{
64 | Use: constants.Init,
65 | RunE: InitStack,
66 | }
67 | return InitCommand
68 | }
69 |
70 | var InitCmd = InitCmdFn()
71 | var (
72 | service string
73 | stack string
74 | database string
75 | directory string
76 | )
77 |
78 | func init() {
79 | ServiceCmd.AddCommand(InitCmd)
80 | InitCmd.Flags().StringVarP(&service, constants.Service, "s", "", utils.UseService())
81 | InitCmd.Flags().StringVarP(&stack, constants.Stack, "t", "", utils.UseStack())
82 | InitCmd.Flags().StringVarP(&database, constants.Database, "d", "", utils.UseDatabase())
83 | InitCmd.Flags().StringVarP(&directory, constants.Directory, "f", "", utils.UseDirectory())
84 | }
85 |
--------------------------------------------------------------------------------
/internal/utils/message.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "fmt"
5 | "os"
6 | "text/template"
7 |
8 | "github.com/fatih/color"
9 | "github.com/wednesday-solutions/picky/internal/constants"
10 | "github.com/wednesday-solutions/picky/internal/errorhandler"
11 | )
12 |
13 | // CreateMessageTemplate creates new text template for printing colorful logs.
14 | func CreateMessageTemplate(name, text string) *template.Template {
15 | tpl, err := template.New(name).Parse(text)
16 | errorhandler.CheckNilErr(err)
17 | tpl = template.Must(tpl, err)
18 | return tpl
19 | }
20 |
21 | // PrintMultiSelectMessage prints multi selected options.
22 | func PrintMultiSelectMessage(messages []string) error {
23 | var message, coloredMessage string
24 | var tpl *template.Template
25 | if len(messages) > constants.Zero {
26 | var templateText string
27 | if len(messages) == constants.One {
28 | templateText = fmt.Sprintf("%s %d option selected: {{ . }}\n",
29 | constants.IconSelect,
30 | len(messages))
31 | } else {
32 | templateText = fmt.Sprintf("%s %d options selected: {{ . }}\n",
33 | constants.IconSelect,
34 | len(messages))
35 | }
36 | for _, option := range messages {
37 | message = fmt.Sprintf("%s%s ", message, option)
38 | }
39 | coloredMessage = color.GreenString("%s", message)
40 | tpl = CreateMessageTemplate("message", templateText)
41 | } else {
42 | message = "No options selected, please select atleast one."
43 | coloredMessage = color.YellowString("%s", message)
44 | tpl = CreateMessageTemplate("responseMessage", fmt.Sprintf("%s {{ . }}\n", constants.IconWarn))
45 | }
46 | err := tpl.Execute(os.Stdout, coloredMessage)
47 | return err
48 | }
49 |
50 | // PrintWarningMessage prints given message in yellow color as warning message in terminal.
51 | func PrintWarningMessage(message string) error {
52 | tpl := CreateMessageTemplate("warningMessage", fmt.Sprintf("\n%s {{ . }}\n", constants.IconWarn))
53 | message = color.YellowString("%s", message)
54 | err := tpl.Execute(os.Stdout, message)
55 | return err
56 | }
57 |
58 | // PrintInfoMessage prints given message in cyan color as info message in terminal.
59 | func PrintInfoMessage(message string) error {
60 | tpl := CreateMessageTemplate("InfoMessage", fmt.Sprintf("\n%s {{ . }}\n", constants.IconChoose))
61 | message = color.CyanString("%s", message)
62 | err := tpl.Execute(os.Stdout, message)
63 | return err
64 | }
65 |
66 | // PrintErrorMessage prints the given message in red color as error message
67 | func PrintErrorMessage(message string) error {
68 | tpl := CreateMessageTemplate("errorMessage", fmt.Sprintf("\n{{ . }}%s\n", errorhandler.Exclamation))
69 | message = color.RedString("%s", message)
70 | err := tpl.Execute(os.Stdout, message)
71 | return err
72 | }
73 |
--------------------------------------------------------------------------------
/flagcmd/stacks.go:
--------------------------------------------------------------------------------
1 | package flagcmd
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/wednesday-solutions/picky/internal/utils"
7 | )
8 |
9 | type StackFlag struct {
10 | // all available stacks status
11 | A bool
12 | // all existing stacks status
13 | E bool
14 | // web stacks status
15 | W bool
16 | // mobile stacks status
17 | M bool
18 | // backend stacks status
19 | B bool
20 | }
21 |
22 | func (f StackFlag) FlagStacks() string {
23 |
24 | var stacks []string
25 | var userOutput string
26 | if f.A {
27 | f.E = false
28 | userOutput = utils.AllStacksString()
29 | userOutput = fmt.Sprintf("\nAll stacks: %s", userOutput)
30 | if f.B || f.M || f.W {
31 | userOutput = ""
32 | }
33 | if f.B {
34 | userOutput = fmt.Sprintf("%s%s", utils.AllBackendStacksString(), userOutput)
35 | }
36 | if f.M {
37 | userOutput = fmt.Sprintf("%s%s", utils.AllMobileStacksString(), userOutput)
38 | }
39 | if f.W {
40 | userOutput = fmt.Sprintf("%s%s", utils.AllWebStacksString(), userOutput)
41 | }
42 | }
43 | if f.E {
44 | stacks = utils.GetExistingStacks()
45 | userOutput = utils.ConvertStacksIntoString(stacks)
46 | userOutput = fmt.Sprintf("\nAll existing stacks: %s", userOutput)
47 | if f.B || f.M || f.W {
48 | userOutput = ""
49 | }
50 | if f.B {
51 | var backendStacks []string
52 | backendStack, status := "", false
53 | for _, stack := range stacks {
54 | backendStack, status = utils.IsBackendStack(stack)
55 | if status {
56 | backendStacks = append(backendStacks, backendStack)
57 | }
58 | }
59 | if len(backendStacks) == 0 {
60 | userOutput = fmt.Sprintf("\n\tNo backend stacks exist.%s", userOutput)
61 | } else {
62 | userOutput = fmt.Sprintf("%s%s", utils.ConvertStacksIntoString(backendStacks), userOutput)
63 | }
64 | userOutput = fmt.Sprintf("\nAll existing backend stacks: %s", userOutput)
65 | }
66 | if f.M {
67 | var mobileStacks []string
68 | mobileStack, status := "", false
69 | for _, stack := range stacks {
70 | mobileStack, status = utils.IsMobileStack(stack)
71 | if status {
72 | mobileStacks = append(mobileStacks, mobileStack)
73 | }
74 | }
75 | if len(mobileStacks) == 0 {
76 | userOutput = fmt.Sprintf("\n\tNo mobile stacks exist.%s", userOutput)
77 | } else {
78 | userOutput = fmt.Sprintf("%s%s", utils.ConvertStacksIntoString(mobileStacks), userOutput)
79 | }
80 | userOutput = fmt.Sprintf("\nAll existing mobile stacks: %s", userOutput)
81 | }
82 | if f.W {
83 | var webStacks []string
84 | webStack, status := "", false
85 | for _, stack := range stacks {
86 | webStack, status = utils.IsWebStack(stack)
87 | if status {
88 | webStacks = append(webStacks, webStack)
89 | }
90 | }
91 | if len(webStacks) == 0 {
92 | userOutput = fmt.Sprintf("\n\tNo web stacks exist.%s", userOutput)
93 | } else {
94 | userOutput = fmt.Sprintf("%s%s", utils.ConvertStacksIntoString(webStacks), userOutput)
95 | }
96 | userOutput = fmt.Sprintf("\nAll existing web stacks: %s", userOutput)
97 | }
98 | }
99 | return userOutput
100 | }
101 |
--------------------------------------------------------------------------------
/cmd/create.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import (
4 | "fmt"
5 | "strings"
6 |
7 | "github.com/spf13/cobra"
8 | "github.com/wednesday-solutions/picky/internal/constants"
9 | "github.com/wednesday-solutions/picky/internal/errorhandler"
10 | "github.com/wednesday-solutions/picky/internal/utils"
11 | "github.com/wednesday-solutions/picky/pickyhelpers"
12 | "github.com/wednesday-solutions/picky/prompt"
13 | )
14 |
15 | func CreateService(cmd *cobra.Command, args []string) error {
16 |
17 | var (
18 | flagDockercompose bool
19 | flagCI bool
20 | flagCD bool
21 | flagPlatform string
22 | flagStacks []string
23 | flagEnv string
24 | err error
25 | )
26 |
27 | flagDockercompose, err = cmd.Flags().GetBool(constants.DockerComposeFlag)
28 | errorhandler.CheckNilErr(err)
29 |
30 | flagCI, err = cmd.Flags().GetBool(constants.CIFlag)
31 | errorhandler.CheckNilErr(err)
32 |
33 | flagCD, err = cmd.Flags().GetBool(constants.CDFlag)
34 | errorhandler.CheckNilErr(err)
35 |
36 | flagPlatform, err = cmd.Flags().GetString(constants.Platform)
37 | errorhandler.CheckNilErr(err)
38 |
39 | flagStacks, err = cmd.Flags().GetStringSlice(constants.Stacks)
40 | errorhandler.CheckNilErr(err)
41 |
42 | flagEnv, err = cmd.Flags().GetString(constants.Environment)
43 | errorhandler.CheckNilErr(err)
44 |
45 | err = utils.CheckStacksExist(flagStacks)
46 | if err != nil {
47 | return err
48 | }
49 |
50 | fmt.Printf("\nDocker Compose: %v\nCI: %v\nCD: %v\nPlatform: %s\nStacks: %v\n",
51 | flagDockercompose, flagCI, flagCD, flagPlatform, flagStacks,
52 | )
53 |
54 | if !strings.EqualFold(flagPlatform, constants.GitHub) {
55 | return fmt.Errorf("Only GitHub is available now.\n")
56 | }
57 | if flagDockercompose {
58 | err = prompt.GenerateDockerCompose()
59 | errorhandler.CheckNilErr(err)
60 | }
61 | if flagCI {
62 | err = pickyhelpers.CreateCI(flagStacks)
63 | errorhandler.CheckNilErr(err)
64 | }
65 | if flagCD {
66 | err = prompt.CreateCD(flagStacks, flagEnv)
67 | errorhandler.CheckNilErr(err)
68 | }
69 | return err
70 | }
71 |
72 | func CreateCmdFn() *cobra.Command {
73 | var CreateCommand = &cobra.Command{
74 | Use: constants.Create,
75 | RunE: CreateService,
76 | }
77 | return CreateCommand
78 | }
79 |
80 | var CreateCmd = CreateCmdFn()
81 | var (
82 | ci bool
83 | cd bool
84 | dockerCompose bool
85 | platform string
86 | flagEnvironment string
87 | )
88 |
89 | func init() {
90 | ServiceCmd.AddCommand(CreateCmd)
91 | CreateCmd.Flags().BoolVarP(&ci, constants.CIFlag, "i", false, utils.UseCI())
92 | CreateCmd.Flags().BoolVarP(&cd, constants.CDFlag, "d", false, utils.UseCD())
93 | CreateCmd.Flags().BoolVarP(
94 | &dockerCompose, constants.DockerComposeFlag, "c", false, utils.UseDockerCompose(),
95 | )
96 | CreateCmd.Flags().StringVarP(
97 | &platform, constants.Platform, "p", constants.Github, utils.UsePlatform(),
98 | )
99 | CreateCmd.Flags().StringSliceVarP(
100 | &stacks, constants.Stacks, "t", utils.GetExistingStacks(), utils.UseInfraStacks(),
101 | )
102 | CreateCmd.Flags().StringVarP(
103 | &flagEnvironment, constants.Environment, "e", constants.Development, utils.UseEnvironment(),
104 | )
105 | }
106 |
--------------------------------------------------------------------------------
/hbs/hbs.go:
--------------------------------------------------------------------------------
1 | package hbs
2 |
3 | import (
4 | "github.com/aymerick/raymond"
5 | "github.com/wednesday-solutions/picky/internal/constants"
6 | "github.com/wednesday-solutions/picky/internal/errorhandler"
7 | "github.com/wednesday-solutions/picky/internal/utils"
8 | )
9 |
10 | func init() {
11 | raymond.RegisterHelper("databaseVolumeConnection", DatabaseVolumeConnection)
12 | raymond.RegisterHelper("dbVersion", DBVersion)
13 | raymond.RegisterHelper("portConnection", PortConnection)
14 | raymond.RegisterHelper("globalAddDependencies", GlobalAddDependencies)
15 | raymond.RegisterHelper("addDependencies", AddDependencies)
16 | raymond.RegisterHelper("runBuildEnvironment", RunBuildEnvironment)
17 | raymond.RegisterHelper("waitForDBService", WaitForDBService)
18 | raymond.RegisterHelper("dependsOnFieldOfGo", DependsOnFieldOfGo)
19 | raymond.RegisterHelper("cmdDockerfile", CmdDockerfile)
20 | raymond.RegisterHelper("envEnvironmentName", EnvEnvironmentName)
21 | raymond.RegisterHelper("deployStacks", DeployStacks)
22 | raymond.RegisterHelper("sstImportStacks", SstImportStacks)
23 | }
24 |
25 | func ParseAndWriteToFile(source, filePath string, stackInfo map[string]interface{}) error {
26 |
27 | ctx := map[string]interface{}{
28 | constants.Frontend: constants.Frontend,
29 | constants.Web: constants.Web,
30 | constants.Mobile: constants.Mobile,
31 | constants.Backend: constants.Backend,
32 | constants.Redis: constants.Redis,
33 | constants.Postgres: constants.Postgres,
34 | constants.PostgreSQL: constants.PostgreSQL,
35 | constants.Mysql: constants.Mysql,
36 | constants.MySQL: constants.MySQL,
37 | constants.GolangMySQLTemplate: constants.GolangMySQLTemplate,
38 | constants.GolangPostgreSQLTemplate: constants.GolangPostgreSQLTemplate,
39 | constants.Stack: stackInfo[constants.Stack].(string),
40 | constants.Database: stackInfo[constants.Database].(string),
41 | constants.ProjectName: stackInfo[constants.ProjectName].(string),
42 | constants.WebStatus: stackInfo[constants.WebStatus].(bool),
43 | constants.MobileStatus: stackInfo[constants.MobileStatus].(bool),
44 | constants.BackendStatus: stackInfo[constants.BackendStatus].(bool),
45 | constants.WebDirName: stackInfo[constants.WebDirName].(string),
46 | constants.MobileDirName: stackInfo[constants.MobileDirName].(string),
47 | constants.BackendDirName: stackInfo[constants.BackendDirName].(string),
48 | constants.ExistingDirectories: stackInfo[constants.ExistingDirectories].([]string),
49 | constants.WebDirectories: stackInfo[constants.WebDirectories].([]string),
50 | constants.BackendPgDirectories: stackInfo[constants.BackendPgDirectories].([]string),
51 | constants.BackendMysqlDirectories: stackInfo[constants.BackendMysqlDirectories].([]string),
52 | }
53 | // Parse the source string into template
54 | tpl, err := raymond.Parse(source)
55 | errorhandler.CheckNilErr(err)
56 |
57 | // Execute the template into string
58 | executedTemplate, err := tpl.Exec(ctx)
59 | errorhandler.CheckNilErr(err)
60 |
61 | err = utils.WriteToFile(filePath, executedTemplate)
62 | errorhandler.CheckNilErr(err)
63 |
64 | return nil
65 | }
66 |
--------------------------------------------------------------------------------
/pickyhelpers/create_docker_files.go:
--------------------------------------------------------------------------------
1 | package pickyhelpers
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/wednesday-solutions/picky/hbs"
7 | "github.com/wednesday-solutions/picky/internal/constants"
8 | "github.com/wednesday-solutions/picky/internal/errorhandler"
9 | "github.com/wednesday-solutions/picky/internal/utils"
10 | )
11 |
12 | func (s StackDetails) CreateDockerFiles() error {
13 |
14 | var (
15 | path string
16 | source string
17 | fileFound bool
18 | err error
19 | )
20 |
21 | if s.StackInfo[constants.WebStatus].(bool) {
22 |
23 | path = fmt.Sprintf("%s/%s/%s", utils.CurrentDirectory(),
24 | s.DirName,
25 | constants.DockerFile,
26 | )
27 | fileFound, _ = utils.IsExists(path)
28 | if !fileFound {
29 | source = `FROM node:14-alpine as baseimage
30 | RUN mkdir app/
31 | ADD . app/
32 | WORKDIR /app
33 |
34 | RUN npm install
35 |
36 | FROM baseimage
37 | CMD {{{cmdDockerfile stack}}}
38 | EXPOSE 3000`
39 |
40 | err = hbs.ParseAndWriteToFile(source, path, s.StackInfo)
41 | errorhandler.CheckNilErr(err)
42 | }
43 | path = fmt.Sprintf("%s/%s/%s", utils.CurrentDirectory(),
44 | s.DirName,
45 | constants.DockerEnvFile,
46 | )
47 | fileFound, _ = utils.IsExists(path)
48 | if !fileFound {
49 | source = `GITHUB_URL=https://api.github.com/`
50 |
51 | err = utils.WriteToFile(path, source)
52 | errorhandler.CheckNilErr(err)
53 | }
54 | path = fmt.Sprintf("%s/%s/%s", utils.CurrentDirectory(),
55 | s.DirName,
56 | constants.DockerIgnoreFile,
57 | )
58 | fileFound, _ = utils.IsExists(path)
59 | if !fileFound {
60 | source = "node_modules\n.git\nbadges"
61 | err = utils.WriteToFile(path, source)
62 | errorhandler.CheckNilErr(err)
63 | }
64 | }
65 |
66 | // Add mobile related files.
67 | // if stackInfo[constants.MobileStatus].(bool) {}
68 |
69 | if s.StackInfo[constants.BackendStatus].(bool) {
70 |
71 | switch s.StackInfo[constants.Stack] {
72 | case constants.NodeExpressGraphqlTemplate, constants.NodeHapiTemplate:
73 |
74 | path = fmt.Sprintf("%s/%s/%s", utils.CurrentDirectory(),
75 | s.DirName,
76 | constants.DockerIgnoreFile,
77 | )
78 | fileFound, _ = utils.IsExists(path)
79 | if !fileFound {
80 | source = "node_modules\n.git\nbadges"
81 | err = utils.WriteToFile(path, source)
82 | errorhandler.CheckNilErr(err)
83 | }
84 |
85 | path = fmt.Sprintf("%s/%s/%s", utils.CurrentDirectory(),
86 | s.DirName,
87 | constants.DockerFile,
88 | )
89 | fileFound, _ = utils.IsExists(path)
90 | backendPortNumber := utils.FetchExistingPortNumber(s.DirName, constants.BackendPort)
91 | if fileFound {
92 | source = fmt.Sprintf(`FROM node:16
93 | ARG ENVIRONMENT_NAME
94 | ARG BUILD_NAME
95 | RUN mkdir -p /app-build
96 | ADD . /app-build
97 | WORKDIR /app-build
98 | RUN --mount=type=cache,target=/root/.yarn YARN_CACHE_FOLDER=/root/.yarn yarn --frozen-lockfile
99 | RUN yarn
100 | RUN yarn {{runBuildEnvironment stack}}
101 |
102 | FROM node:16-alpine
103 | ARG ENVIRONMENT_NAME
104 | ARG BUILD_NAME
105 | RUN mkdir -p /dist
106 | RUN apk add yarn
107 | RUN yarn global add {{globalAddDependencies database}}
108 | RUN yarn add {{addDependencies database}}
109 | ADD scripts/migrate-and-run.sh /
110 | ADD package.json /
111 | ADD . /
112 | COPY --from=0 /app-build/dist ./dist
113 |
114 | CMD ["sh", "./migrate-and-run.sh"]
115 | EXPOSE %s`, backendPortNumber)
116 |
117 | err = hbs.ParseAndWriteToFile(source, path, s.StackInfo)
118 | errorhandler.CheckNilErr(err)
119 | }
120 | }
121 | }
122 | return nil
123 | }
124 |
--------------------------------------------------------------------------------
/prompt/cicd.go:
--------------------------------------------------------------------------------
1 | package prompt
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/wednesday-solutions/picky/internal/constants"
7 | "github.com/wednesday-solutions/picky/internal/errorhandler"
8 | "github.com/wednesday-solutions/picky/internal/utils"
9 | "github.com/wednesday-solutions/picky/pickyhelpers"
10 | )
11 |
12 | func PromptCICD() {
13 | var p PromptInput
14 | platform := p.PromptPlatform()
15 | p.Label = "Select option"
16 | p.Items = []string{constants.CreateCI, constants.CreateCD}
17 | p.GoBack = PromptHome
18 | selectedOptions, _ := p.PromptMultiSelect()
19 | for len(selectedOptions) == 0 {
20 | selectedOptions, _ = p.PromptMultiSelect()
21 | }
22 | // Uncomment the below line if we environment prompt.
23 | // environment := PromptEnvironment()
24 | environment := constants.Development
25 | stacks := PromptSelectExistingStacks()
26 |
27 | if platform == constants.GitHub {
28 | isCreateCD := false
29 | for _, option := range selectedOptions {
30 | if option == constants.CreateCI {
31 |
32 | err := pickyhelpers.CreateCI(stacks)
33 | errorhandler.CheckNilErr(err)
34 |
35 | } else if option == constants.CreateCD {
36 |
37 | isCreateCD = true
38 | err := CreateCD(stacks, environment)
39 | errorhandler.CheckNilErr(err)
40 | }
41 | }
42 | if isCreateCD {
43 | var backendExist, webExist bool
44 | for _, stackDir := range stacks {
45 | service := utils.FindService(stackDir)
46 | if service == constants.Backend {
47 | // Create task-definition.json if the stack is backend.
48 | err := pickyhelpers.CreateTaskDefinition(stackDir, environment)
49 | errorhandler.CheckNilErr(err)
50 | backendExist = true
51 |
52 | // Update existing env file of selected environment.
53 | err = pickyhelpers.UpdateEnvByEnvironment(stackDir, environment)
54 | errorhandler.CheckNilErr(err)
55 |
56 | } else if service == constants.Web {
57 | webExist = true
58 | }
59 | }
60 | fmt.Printf("%s", errorhandler.DoneMessage)
61 | PrintGitHubSecretsInfo(backendExist, webExist)
62 | }
63 | }
64 | PromptHome()
65 | }
66 |
67 | func (p PromptInput) PromptPlatform() string {
68 | p.Label = "Choose a platform"
69 | p.Items = []string{constants.GitHub}
70 | p.GoBack = PromptHome
71 | platform, _ := p.PromptSelect()
72 | return platform
73 | }
74 |
75 | func PrintGitHubSecretsInfo(backendExist, webExist bool) {
76 | err := utils.PrintInfoMessage("Save the following config data in GitHub secrets after the deployment.")
77 | errorhandler.CheckNilErr(err)
78 | secrets, count := "\n", 1
79 |
80 | secretKeys := []string{
81 | constants.AwsRegion,
82 | constants.AwsAccessKeyId,
83 | constants.AwsSecretAccessKey,
84 | constants.AwsEcrRepository,
85 | }
86 | if backendExist {
87 | for _, key := range secretKeys {
88 | secrets = fmt.Sprintf("%s %d. %s\n", secrets, count, key)
89 | count++
90 | }
91 | }
92 | if webExist {
93 | secrets = fmt.Sprintf("%s %d. %s\n", secrets, count, constants.DistributionId)
94 | }
95 | fmt.Printf("%s\n", secrets)
96 | }
97 |
98 | func CreateCD(directories []string, environment string) error {
99 | for _, dirName := range directories {
100 | var s pickyhelpers.StackDetails
101 | s.DirName = dirName
102 | s.Service = utils.FindService(dirName)
103 | s.Environment = environment
104 | s.Stack, s.Database = utils.FindStackAndDatabase(dirName)
105 | err := s.CreateCDFile()
106 | if err != nil {
107 | if err.Error() != errorhandler.ErrExist.Error() {
108 | errorhandler.CheckNilErr(err)
109 | }
110 | }
111 | }
112 | return nil
113 | }
114 |
--------------------------------------------------------------------------------
/prompt/init.go:
--------------------------------------------------------------------------------
1 | package prompt
2 |
3 | import (
4 | "fmt"
5 | "path/filepath"
6 | "strings"
7 |
8 | "github.com/wednesday-solutions/picky/internal/constants"
9 | "github.com/wednesday-solutions/picky/internal/errorhandler"
10 | "github.com/wednesday-solutions/picky/internal/utils"
11 | "github.com/wednesday-solutions/picky/pickyhelpers"
12 | )
13 |
14 | type InitInfo struct {
15 | Service string
16 | Stack string
17 | Database string
18 | DirName string
19 | }
20 |
21 | func (i *InitInfo) PromptSelectInit() {
22 | var p PromptInput
23 | p.GoBack = PromptSelectService
24 | response := true
25 | for response {
26 | p.Label = fmt.Sprintf("Do you want to initialize '%s' as '%s'", i.Stack, i.DirName)
27 | response = p.PromptYesOrNoSelect()
28 | if response {
29 | i.Init()
30 | } else {
31 | response = PromptConfirm()
32 | if response {
33 | PromptHome()
34 | } else {
35 | response = true
36 | }
37 | }
38 | }
39 | }
40 |
41 | func (i *InitInfo) Init() {
42 | var p PromptInput
43 | p.GoBack = PromptSelectService
44 | currentDir := utils.CurrentDirectory()
45 | if i.Stack == constants.GolangEchoTemplate {
46 | i.Stack = fmt.Sprintf("%s-%s", strings.Split(i.Stack, " ")[0], i.Database)
47 | }
48 | destination := filepath.Join(currentDir, i.DirName)
49 | status, _ := utils.IsExists(destination)
50 | var response bool
51 | for status {
52 | p.Label = fmt.Sprintf("The '%s' already exists, do you want to update it", i.DirName)
53 | response = p.PromptYesOrNoSelect()
54 | if response {
55 | // Delete all contents of existing directory.
56 | err := utils.RemoveAllContents(destination)
57 | errorhandler.CheckNilErr(err)
58 | break
59 | } else {
60 | PromptHome()
61 | }
62 | }
63 | if !status {
64 | // Create directory with directory name we got.
65 | err := utils.MakeDirectory(currentDir, i.DirName)
66 | errorhandler.CheckNilErr(err)
67 | response = true
68 | }
69 | if response {
70 | err := i.StackInitialize()
71 | errorhandler.CheckNilErr(err)
72 | }
73 | p.Label = "Do you want to initialize another service"
74 | response = p.PromptYesOrNoSelect()
75 | if response {
76 | PromptSelectService()
77 | } else {
78 | PromptHome()
79 | }
80 | }
81 |
82 | func (i InitInfo) StackInitialize() error {
83 |
84 | done := make(chan bool)
85 | go pickyhelpers.ProgressBar(100, "Downloading", done)
86 |
87 | // Clone the selected repo into service directory.
88 | var s pickyhelpers.StackDetails
89 | s.Service = i.Service
90 | s.Stack = i.Stack
91 | s.DirName = i.DirName
92 | s.CurrentDir = utils.CurrentDirectory()
93 | s.Database = i.Database
94 | err := s.CloneRepo()
95 | errorhandler.CheckNilErr(err)
96 |
97 | // Delete .git folder inside the cloned repo.
98 | err = s.DeleteDotGitFolder()
99 | errorhandler.CheckNilErr(err)
100 |
101 | // stackInfo gives the information about the stacks which is present in the root.
102 | s.Environment = constants.Environment
103 | s.StackInfo = s.GetStackInfo()
104 | // Database conversion
105 | if i.Service == constants.Backend {
106 | err = s.ConvertTemplateDatabase()
107 | errorhandler.CheckNilErr(err)
108 | }
109 | // create and update docker files
110 | err = s.CreateDockerFiles()
111 | errorhandler.CheckNilErr(err)
112 |
113 | if !ShowPromptGitInit() {
114 | file := fmt.Sprintf("%s/%s",
115 | utils.CurrentDirectory(),
116 | constants.DotGitIgnore,
117 | )
118 | err = WriteDotGitignoreFile(file)
119 | errorhandler.CheckNilErr(err)
120 | }
121 |
122 | <-done
123 | fmt.Printf("\nDownloading %s", errorhandler.CompleteMessage)
124 |
125 | return err
126 | }
127 |
--------------------------------------------------------------------------------
/pickyhelpers/convert_dbtests.go:
--------------------------------------------------------------------------------
1 | package pickyhelpers
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/wednesday-solutions/picky/internal/constants"
7 | "github.com/wednesday-solutions/picky/internal/utils"
8 | )
9 |
10 | func ConvertDBTests(stack, dirName string) error {
11 |
12 | if stack == constants.NodeExpressGraphqlTemplate {
13 |
14 | file := fmt.Sprintf("%s/%s/%s/%s/%s/%s",
15 | utils.CurrentDirectory(),
16 | dirName,
17 | "server",
18 | constants.Database,
19 | "tests",
20 | "index.test.js",
21 | )
22 | source := `import SequelizeMock from 'sequelize-mock';
23 | import mysql2 from 'mysql2';
24 | import { resetAndMockDB } from '@utils/testUtils';
25 | import { DB_ENV } from '@server/utils/testUtils/dbConfig';
26 |
27 | const mocks = {};
28 | describe('getClient', () => {
29 | afterAll(() => {
30 | resetAndMockDB();
31 | });
32 | it('successfully get DB Client', async () => {
33 | jest.unmock('@database');
34 | mocks.sequelize = SequelizeMock;
35 | jest.doMock('sequelize', () => mocks.sequelize);
36 | jest.spyOn(mocks, 'sequelize');
37 | const { getClient } = require('../../database');
38 | const client = await getClient();
39 | await expect(client).toBeInstanceOf(mocks.sequelize);
40 |
41 | expect(mocks.sequelize.mock.calls.length).toEqual(1);
42 | expect(mocks.sequelize.mock.calls[0][0]).toEqual(DB_ENV.DB_URI);
43 | expect(mocks.sequelize.mock.calls[0][1]).toEqual({
44 | url: DB_ENV.DB_URI,
45 | host: DB_ENV.MYSQL_HOST,
46 | dialectModule: mysql2,
47 | dialect: 'mysql',
48 | logging: false,
49 | pool: {
50 | min: 0,
51 | max: 10,
52 | idle: 10000
53 | },
54 | define: {
55 | underscored: true,
56 | timestamps: false
57 | },
58 | retry: {
59 | match: [
60 | 'unknown timed out',
61 | mocks.sequelize.TimeoutError,
62 | 'timed',
63 | 'timeout',
64 | 'TimeoutError',
65 | 'Operation timeout',
66 | 'refuse',
67 | 'SQLITE_BUSY'
68 | ],
69 | max: 10
70 | }
71 | });
72 | });
73 | it('throw error on failure', async () => {
74 | jest.unmock('@database');
75 | mocks.sequelize = SequelizeMock;
76 | jest.doMock('sequelize', () => new Error());
77 | jest.spyOn(mocks, 'sequelize');
78 |
79 | const { getClient } = require('../../database');
80 | await expect(getClient).toThrow(expect.any(Error));
81 | });
82 | });
83 |
84 | describe('connect', () => {
85 | it('successfully connect to the database', async () => {
86 | jest.unmock('@database');
87 | mocks.sequelize = SequelizeMock;
88 | jest.doMock('sequelize', () => mocks.sequelize);
89 |
90 | const { getClient, connect } = require('../../database');
91 | const client = await getClient();
92 | jest.spyOn(client, 'authenticate');
93 | jest.spyOn(console, 'log');
94 | await connect();
95 | expect(client.authenticate.mock.calls.length).toBe(1);
96 | expect(console.log.mock.calls.length).toBe(1);
97 | expect(console.log.mock.calls.length).toBe(1);
98 | expect(console.log.mock.calls[0][0]).toBe('Connection has been established successfully.\n');
99 | expect(console.log.mock.calls[0][1]).toEqual({
100 | db_uri: process.env.DB_URI
101 | });
102 | });
103 |
104 | it('should throw an error if connection fails', async () => {
105 | jest.unmock('@database');
106 | mocks.sequelize = SequelizeMock;
107 | jest.doMock('sequelize', () => mocks.sequelize);
108 |
109 | const { getClient, connect } = require('../../database');
110 | const client = await getClient();
111 | const error = new Error('failed');
112 | client.authenticate = async () => {
113 | await expect(connect()).rejects.toEqual(error);
114 | jest.spyOn(client, 'authenticate');
115 | jest.spyOn(console, 'log');
116 | throw error;
117 | };
118 | });
119 | });
120 | `
121 | err := utils.WriteToFile(file, source)
122 | return err
123 | }
124 | return nil
125 | }
126 |
--------------------------------------------------------------------------------
/internal/utils/file.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "fmt"
5 | "os"
6 | "path/filepath"
7 |
8 | "github.com/wednesday-solutions/picky/internal/errorhandler"
9 | )
10 |
11 | // IsExists will check if the path exists or no.
12 | func IsExists(path string) (bool, error) {
13 | _, err := os.Stat(path)
14 | if err == nil {
15 | return true, nil
16 | } else if os.IsNotExist(err) {
17 | return false, nil
18 | } else {
19 | return false, err
20 | }
21 | }
22 |
23 | // CurrentDirectory will give the root directory.
24 | func CurrentDirectory() string {
25 | path, err := filepath.Abs(".")
26 | if err != nil {
27 | return ""
28 | } else {
29 | return path
30 | }
31 | }
32 |
33 | // MakeDirectory will make directory according to input.
34 | func MakeDirectory(path string, dirName string) error {
35 | err := os.Mkdir(path+"/"+dirName, 0755)
36 | if err != nil {
37 | return err
38 | } else {
39 | return nil
40 | }
41 | }
42 |
43 | // CreateDirectory creates directory according to the input.
44 | func CreateDirectory(filePath string) error {
45 | err := os.Mkdir(filePath, 0755)
46 | return err
47 | }
48 |
49 | // MakeFile will create new file according to input path and file name.
50 | func MakeFile(path, fileName string) error {
51 | _, err := os.Create(path + "/" + fileName)
52 | if err != nil {
53 | return err
54 | }
55 | return nil
56 | }
57 |
58 | func CreateFile(file string) error {
59 | _, err := os.Create(file)
60 | errorhandler.CheckNilErr(err)
61 |
62 | return nil
63 | }
64 |
65 | // Remove a single file
66 | func RemoveFile(path string) error {
67 | return os.Remove(path)
68 | }
69 |
70 | // Remove all the files existing in the path directory and the path.
71 | func RemoveAll(path string) error {
72 | return os.RemoveAll(path)
73 | }
74 |
75 | // Remove all existing files in the given path.
76 | func RemoveAllContents(path string) error {
77 | dirNames, err := ReadAllContents(path)
78 | errorhandler.CheckNilErr(err)
79 | for _, dir := range dirNames {
80 | err = RemoveAll(filepath.Join(path, dir))
81 | }
82 | return err
83 | }
84 |
85 | // Read all existing in the given path.
86 | func ReadAllContents(path string) ([]string, error) {
87 | file, err := os.Open(path)
88 | errorhandler.CheckNilErr(err)
89 | return file.Readdirnames(-1)
90 | }
91 |
92 | // TruncateAndWriteToFile will delete all the existing data and write input data into the file.
93 | func TruncateAndWriteToFile(path, file, data string) error {
94 | // Opens file with read and write permissions.
95 | openFile, err := os.OpenFile(fmt.Sprintf("%s/%s", path, file), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0755)
96 | if err != nil {
97 | return err
98 | }
99 | defer openFile.Close()
100 |
101 | _, err = openFile.WriteString(data)
102 | if err != nil {
103 | return err
104 | }
105 |
106 | err = openFile.Sync()
107 | if err != nil {
108 | return err
109 | }
110 | return nil
111 | }
112 |
113 | // WriteToFile will delete all the existing data and write input data into the file.
114 | func WriteToFile(file, data string) error {
115 | // Opens file with read and write permissions.
116 | openFile, err := os.OpenFile(file, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0755)
117 | if err != nil {
118 | return err
119 | }
120 | defer openFile.Close()
121 |
122 | _, err = openFile.WriteString(data)
123 | if err != nil {
124 | return err
125 | }
126 |
127 | err = openFile.Sync()
128 | if err != nil {
129 | return err
130 | }
131 | return nil
132 | }
133 |
134 | // AppendToFile will append given string to existing file.
135 | func AppendToFile(path, data string) error {
136 | openFile, err := os.OpenFile(path, os.O_APPEND|os.O_RDWR, 0644)
137 | errorhandler.CheckNilErr(err)
138 |
139 | defer openFile.Close()
140 |
141 | _, err = openFile.WriteString(data)
142 | errorhandler.CheckNilErr(err)
143 |
144 | err = openFile.Sync()
145 | errorhandler.CheckNilErr(err)
146 |
147 | return nil
148 | }
149 |
--------------------------------------------------------------------------------
/prompt/promptutils.go:
--------------------------------------------------------------------------------
1 | package prompt
2 |
3 | import (
4 | "fmt"
5 | "path/filepath"
6 |
7 | "github.com/wednesday-solutions/picky/internal/constants"
8 | "github.com/wednesday-solutions/picky/internal/errorhandler"
9 | "github.com/wednesday-solutions/picky/internal/utils"
10 | )
11 |
12 | // ResetItems reset the values of items. index means last selected item's index.
13 | func ResetItems(items []string, index *int) []string {
14 | if index != nil {
15 | var updatedItems []string
16 | var selectedItem string
17 | for idx, item := range items {
18 | if idx != *index {
19 | updatedItems = append(updatedItems, item)
20 | } else {
21 | selectedItem = item
22 | }
23 | }
24 | updatedItems = append(updatedItems, selectedItem)
25 | items = updatedItems
26 | }
27 | return items
28 | }
29 |
30 | func (i *InitInfo) PromptGetDirectoryName() string {
31 | var p PromptInput
32 | suffix := utils.GetSuffixOfStack(i.Stack, i.Database)
33 | exampleLabel := fmt.Sprintf("('-%s' suffix will be added). Eg: test-%s ", suffix, suffix)
34 | p.Label = fmt.Sprintf("Please enter a name for the '%s' stack %s", i.Stack, exampleLabel)
35 | p.GoBack = PromptSelectService
36 | i.DirName = p.PromptGetInput()
37 | i.DirName = utils.CreateStackDirectory(i.DirName, i.Stack, i.Database)
38 | status := true
39 | var err error
40 | for status {
41 | status, err = utils.IsExists(filepath.Join(utils.CurrentDirectory(), i.DirName))
42 | errorhandler.CheckNilErr(err)
43 | if status {
44 | p.Label = "Entered name already exists. Please enter another name"
45 | i.DirName = p.PromptGetInput()
46 | i.DirName = utils.CreateStackDirectory(i.DirName, i.Stack, i.Database)
47 | }
48 | }
49 | return i.DirName
50 | }
51 |
52 | func PromptExit() {
53 | response := PromptConfirm()
54 | if response {
55 | Exit()
56 | } else {
57 | PromptHome()
58 | }
59 | }
60 |
61 | func PromptConfirm() bool {
62 | var p PromptInput
63 | p.Label = "Are you sure"
64 | p.GoBack = PromptHome
65 | return p.PromptYesOrNoSelect()
66 | }
67 |
68 | func Exit() {
69 | errorhandler.CheckNilErr(errorhandler.ErrInterrupt)
70 | }
71 |
72 | // PromptSelectExistingStacks is a prompt function will ask for selecting available stacks.
73 | func PromptSelectExistingStacks() []string {
74 | var p PromptInput
75 | p.Label = "Select available stacks"
76 | p.GoBack = PromptHome
77 | _, _, directories := utils.GetExistingStacksDatabasesAndDirectories()
78 | p.Items = directories
79 | if len(directories) > 1 {
80 | p.Items = append(p.Items, "All")
81 | }
82 | var results []string
83 | var responses []int
84 | count := 0
85 | for {
86 | if len(responses) == 0 {
87 | results, responses = p.PromptMultiSelect()
88 | } else {
89 | break
90 | }
91 | count++
92 | if count > 2 {
93 | PromptHome()
94 | }
95 | }
96 | for _, respIdx := range responses {
97 | if respIdx == len(p.Items)-1 {
98 | return directories
99 | }
100 | }
101 | return results
102 | }
103 |
104 | // GetDetailsTemplatesOfStacks return the details template for the given service.
105 | func (i *InitInfo) GetDetailsTemplatesOfStacks() string {
106 | details := fmt.Sprintf(`
107 | -------- %s --------
108 | {{ "Name:" | faint }} {{ .Name }}
109 | {{ "Language:" | faint }} {{ .Language }}
110 | {{ "Framework:" | faint }} {{ .Framework }}
111 | {{ "Type:" | faint }} {{ .Type }}`, i.Service)
112 |
113 | if i.Service == constants.Backend {
114 | details = fmt.Sprintf(`%s
115 | {{ "Databases:" | faint }} {{ .Databases }}
116 | `, details)
117 | }
118 | return details
119 | }
120 |
121 | func PromptAlreadyExist(existingFile string) bool {
122 | var p PromptInput
123 | p.Label = fmt.Sprintf("'%s' already exists, do you want to rewrite it", existingFile)
124 | p.GoBack = PromptHome
125 | return p.PromptYesOrNoConfirm()
126 | }
127 |
128 | func PromptAlertMessage() {
129 | err := utils.PrintWarningMessage("Click Ctrl+C to exit.")
130 | errorhandler.CheckNilErr(err)
131 | PromptHome()
132 | }
133 |
--------------------------------------------------------------------------------
/prompt/dockercompose.go:
--------------------------------------------------------------------------------
1 | package prompt
2 |
3 | import (
4 | "fmt"
5 | "path/filepath"
6 |
7 | "github.com/wednesday-solutions/picky/internal/constants"
8 | "github.com/wednesday-solutions/picky/internal/errorhandler"
9 | "github.com/wednesday-solutions/picky/internal/utils"
10 | "github.com/wednesday-solutions/picky/pickyhelpers"
11 | )
12 |
13 | func PromptDockerCompose() {
14 | var p PromptInput
15 | p.Label = "Choose an option"
16 | p.GoBack = PromptHome
17 | p.Items = []string{constants.CreateDockerCompose, constants.RunDockerCompose}
18 | response, _ := p.PromptSelect()
19 | if response == constants.CreateDockerCompose {
20 | err := GenerateDockerCompose()
21 | errorhandler.CheckNilErr(err)
22 | PromptRunDockerCompose()
23 | } else if response == constants.RunDockerCompose {
24 | err := RunDockerCompose()
25 | errorhandler.CheckNilErr(err)
26 | }
27 | PromptHome()
28 | }
29 |
30 | // PromptCreateDockerCompose is a prompt function for create docker-compose.
31 | func PromptCreateDockerCompose() {
32 | var p PromptInput
33 | p.Label = fmt.Sprintf("Do you want to create '%s' file for this project", constants.DockerComposeFile)
34 | p.GoBack = PromptHome
35 | response := p.PromptYesOrNoSelect()
36 | if response {
37 | err := GenerateDockerCompose()
38 | errorhandler.CheckNilErr(err)
39 | } else {
40 | PromptDockerCompose()
41 | }
42 | PromptRunDockerCompose()
43 | }
44 |
45 | // PromptRunDockerCompose is a prompt function for run docker-compose.
46 | func PromptRunDockerCompose() {
47 | var p PromptInput
48 | p.Label = fmt.Sprintf("Do you want to run '%s' ", constants.DockerComposeFile)
49 | p.GoBack = PromptDockerCompose
50 | response := p.PromptYesOrNoSelect()
51 | if response {
52 | err := RunDockerCompose()
53 | errorhandler.CheckNilErr(err)
54 | }
55 | PromptHome()
56 | }
57 |
58 | // GenerateDockerCompose generates docker-compose file for all the existing
59 | // stacks as a monorepo in the root directory.
60 | func GenerateDockerCompose() error {
61 | var p PromptInput
62 | var s pickyhelpers.StackDetails
63 |
64 | p.GoBack = PromptDockerCompose
65 | response := true
66 | status, _ := utils.IsExists(filepath.Join(utils.CurrentDirectory(), constants.DockerComposeFile))
67 | if status {
68 | p.Label = fmt.Sprintf("'%s' already exist, do you want to update it", constants.DockerComposeFile)
69 | response = p.PromptYesOrNoConfirm()
70 | }
71 | if response {
72 | stacks, databases, _ := utils.GetExistingStacksDatabasesAndDirectories()
73 | for i, db := range databases {
74 | if db != "" {
75 | s.Database = db
76 | s.Stack = stacks[i]
77 | break
78 | }
79 | }
80 | s.Environment = constants.Environment
81 | s.StackInfo = s.GetStackInfo()
82 | err := pickyhelpers.CreateDockerComposeFile(s.StackInfo)
83 | errorhandler.CheckNilErr(err)
84 | fmt.Printf("%s", errorhandler.DoneMessage)
85 | }
86 | return nil
87 | }
88 |
89 | // RunDockerCompose runs 'docker-compose up' from the root directory.
90 | func RunDockerCompose() error {
91 | status, _ := utils.IsExists(filepath.Join(utils.CurrentDirectory(), constants.DockerComposeFile))
92 | if status {
93 | err := utils.PrintInfoMessage("Running docker-compose..")
94 | errorhandler.CheckNilErr(err)
95 | err = utils.RunCommandWithLogs("", "docker", "compose", "up")
96 | errorhandler.CheckNilErr(err)
97 | } else {
98 | err := utils.PrintWarningMessage(fmt.Sprintf("%s file is not exist in the root directory.", constants.DockerComposeFile))
99 | errorhandler.CheckNilErr(err)
100 | PromptCreateDockerCompose()
101 | }
102 | return nil
103 | }
104 |
105 | // ShowCreateDockerCompose returns true if a backend service exists.
106 | // func ShowCreateDockerCompose(databases []string) bool {
107 | // var backendStatus, frontendStatus bool
108 | // for _, db := range databases {
109 | // if db == "" {
110 | // frontendStatus = true
111 | // } else {
112 | // backendStatus = true
113 | // }
114 | // if backendStatus && frontendStatus {
115 | // return true
116 | // }
117 | // }
118 | // return false
119 | // }
120 |
--------------------------------------------------------------------------------
/.github/workflows/product-release.yml:
--------------------------------------------------------------------------------
1 | name: Publish package in go-pkg
2 |
3 | on:
4 | push:
5 | branches: [main]
6 |
7 | jobs:
8 | Release-go-pkg:
9 | runs-on: ubuntu-latest
10 | steps:
11 | - name: Checkout
12 | uses: actions/checkout@v3
13 |
14 | - name: Install Go
15 | uses: actions/setup-go@v3
16 | with:
17 | go-version: 1.21
18 |
19 | - name: Actions Ecosystem Action Get Merged Pull Request
20 | uses: actions-ecosystem/action-get-merged-pull-request@v1.0.1
21 | id: getMergedPR
22 | with:
23 | github_token: ${{ secrets.GITHUB_TOKEN }}
24 |
25 | - name: Fetching all tags
26 | run: |
27 | git fetch --prune --unshallow --tags
28 |
29 | - name: Get Commit Message
30 | run: |
31 | declare -A category=( [fix]="" [chore]="" [revert]="" [build]="" [docs]="" [feat]="" [perf]="" [refactor]="" [style]="" [temp]="" [test]="" [ci]="" [others]="")
32 | declare -A categoryTitle=( [fix]="
Bug Fixes
" [chore]="Changes to build process or aux tools
" [revert]="Revert Commits
" [build]="Build
" [docs]="Documentation
" [feat]="New Features
" [perf]="Performace Enhancements
" [refactor]="Refactored
" [style]="Changed Style
" [temp]="Temporary Commit
" [test]="Added Tests
" [ci]="Changes to CI config
" [others]="Others
")
33 | msg="#${{ steps.getMergedPR.outputs.number }} ${{ steps.getMergedPR.outputs.title }}"
34 | for i in $(git log --format=%h $(git merge-base HEAD^1 HEAD^2)..HEAD^2)
35 | do
36 | IFS=":" read -r type cmmsg <<< $(git log --format=%B -n 1 $i)
37 | type="${type}" | xargs
38 | text_msg=" • $i - ${cmmsg}
"
39 | flag=1
40 | for i in "${!category[@]}"
41 | do
42 | if [ "${type}" == "$i" ]
43 | then
44 | category[$i]+="${text_msg}"
45 | flag=0
46 | break
47 | fi
48 | done
49 | if [ $flag -eq 1 ]
50 | then
51 | category[others]+="${text_msg}"
52 | fi
53 | done
54 | for i in "${!category[@]}"
55 | do
56 | if [ ! -z "${category[$i]}" ] && [ "others" != "$i" ]
57 | then
58 | msg+="${categoryTitle[$i]}${category[$i]}"
59 | fi
60 | done
61 | if [ ! -z "${category[others]}" ]
62 | then
63 | msg+="${categoryTitle[others]}${category[others]}"
64 | fi
65 | echo "New Release Note: $msg"
66 | echo "COMMIT_MESSAGE=${msg}" >> $GITHUB_ENV
67 |
68 | - name: Bump Version
69 | # DEFAULT_BUMP values: major, minor, patch
70 | uses: anothrNick/github-tag-action@v1
71 | id: bump
72 | env:
73 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
74 | DEFAULT_BUMP: patch
75 | DRY_RUN: true
76 | WITH_V: true
77 | VERBOSE: true
78 |
79 | - name: Push tag to GitHub
80 | id: new-tag
81 | run: |
82 | git config --global user.email "$GITHUB_ACTOR@users.noreply.github.com"
83 | git config --global user.name "GITHUB_ACTOR"
84 | git config --global push.followTags true
85 | NEW_TAG=${{ steps.bump.outputs.new_tag }}
86 | echo "New Tag: $NEW_TAG"
87 | git tag $NEW_TAG
88 | git push origin $NEW_TAG
89 | echo "$NEW_TAG Tag pushed successfully."
90 | echo "new_tag=$NEW_TAG" >> $GITHUB_OUTPUT
91 |
92 | - name: Publish go-pkg
93 | run: |
94 | VERSION=${{ steps.new-tag.outputs.new_tag }}
95 | echo "New version: $VERSION"
96 | PKG="github.com/wednesday-solutions/picky@$VERSION"
97 | echo "New package version: $PKG"
98 | GOPROXY=proxy.golang.org go list -m $PKG
99 |
100 | - name: Create Product Release
101 | uses: actions/create-release@latest
102 | env:
103 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
104 | with:
105 | tag_name: ${{ steps.bump.outputs.new_tag }}
106 | RELEASE_NAME: ${{ steps.bump.outputs.new_tag }}
107 | body: |
108 | ${{ env.COMMIT_MESSAGE }}
109 | @alichherawalla
110 | @praveenkumar1798
111 | @ijas-ws
112 | draft: false
113 | prerelease: false
114 |
--------------------------------------------------------------------------------
/hbs/hbsregister.go:
--------------------------------------------------------------------------------
1 | package hbs
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/wednesday-solutions/picky/internal/constants"
7 | "github.com/wednesday-solutions/picky/internal/utils"
8 | )
9 |
10 | func DatabaseVolumeConnection(db string) string {
11 | if db == constants.PostgreSQL {
12 | return fmt.Sprintf("-db-volume:/var/lib/%s", constants.PostgresqlData)
13 | } else if db == constants.MySQL {
14 | return fmt.Sprintf("-db-volume:/var/lib/%s", constants.Mysql)
15 | } else {
16 | return db
17 | }
18 | }
19 |
20 | func DBVersion(db string) string {
21 | if db == constants.PostgreSQL {
22 | return "postgres:15"
23 | } else if db == constants.MySQL {
24 | return "mysql:5.7"
25 | } else {
26 | return ""
27 | }
28 | }
29 |
30 | func PortConnection(stack string) string {
31 | var portConnectionStr string
32 | backendPortNumber := utils.GetPortNumber(constants.BackendPortNumber)
33 | webPortNumber := utils.GetPortNumber(constants.WebPortNumber)
34 | postgresPortNumber := utils.GetDatabasePortNumber(constants.PostgreSQL)
35 | mysqlPortNumber := utils.GetDatabasePortNumber(constants.MySQL)
36 | redisPortNumber := constants.RedisPortNumber
37 | switch stack {
38 | case constants.PostgreSQL:
39 | portConnectionStr = fmt.Sprintf("%d:%d", postgresPortNumber, constants.PostgresPortNumber)
40 | return portConnectionStr
41 | case constants.MySQL:
42 | portConnectionStr = fmt.Sprintf("%d:%d", mysqlPortNumber, constants.MysqlPortNumber)
43 | return portConnectionStr
44 | case constants.MongoDB:
45 | return "27017:27017"
46 | case constants.Web, constants.Mobile:
47 | portConnectionStr = fmt.Sprintf("%d:%d", webPortNumber, constants.WebPortNumber)
48 | return portConnectionStr
49 | case constants.Backend:
50 | portConnectionStr = fmt.Sprintf("%d:%d", backendPortNumber, constants.BackendPortNumber)
51 | return portConnectionStr
52 | case constants.Redis:
53 | portConnectionStr = fmt.Sprintf("%d:%d", redisPortNumber, constants.RedisPortNumber)
54 | return portConnectionStr
55 | default:
56 | return portConnectionStr
57 | }
58 | }
59 |
60 | func GlobalAddDependencies(database string) string {
61 | switch database {
62 | case constants.PostgreSQL, constants.MySQL:
63 | return "sequelize-cli@6.2.0"
64 | default:
65 | return ""
66 | }
67 | }
68 |
69 | func AddDependencies(database string) string {
70 | switch database {
71 | case constants.PostgreSQL:
72 | return "shelljs bull dotenv pg sequelize@6.6.5"
73 | case constants.MySQL:
74 | return "shelljs bull dotenv mysql2 sequelize@6.6.5"
75 | default:
76 | return ""
77 | }
78 | }
79 |
80 | func RunBuildEnvironment(stack string) string {
81 | switch stack {
82 | case constants.NodeExpressGraphqlTemplate:
83 | return "build:$BUILD_NAME"
84 | case constants.NodeHapiTemplate:
85 | return "build:$BUILD_NAME"
86 | default:
87 | return ""
88 | }
89 | }
90 |
91 | func WaitForDBService(database string) string {
92 | var portNumber string
93 | if database == constants.PostgreSQL {
94 | portNumber = "5432"
95 | } else if database == constants.MySQL {
96 | portNumber = "3306"
97 | }
98 | return fmt.Sprintf(` wait-for-db:
99 | image: atkrad/wait4x
100 | depends_on:
101 | - db
102 | command: tcp db:%s -t 30s -i 250ms`, portNumber)
103 | }
104 |
105 | func DependsOnFieldOfGo(stack string) string {
106 | output := ` depends_on:
107 | wait-for-db:
108 | condition: service_completed_successfully
109 | `
110 | if stack == constants.GolangPostgreSQLTemplate || stack == constants.GolangMySQLTemplate {
111 | return output
112 | } else {
113 | return ""
114 | }
115 | }
116 |
117 | func CmdDockerfile(stack string) string {
118 | switch stack {
119 | case constants.ReactJS:
120 | return `["yarn", "start"]`
121 | case constants.NextJS:
122 | return `["yarn", "start:dev"]`
123 | default:
124 | return ""
125 | }
126 | }
127 |
128 | func EnvEnvironmentName() string {
129 | return "`.env.${process.env.ENVIRONMENT_NAME}`"
130 | }
131 |
132 | func DeployStacks(stackFiles []string) string {
133 | var deployStackSource string
134 | for _, stackFile := range stackFiles {
135 | // will append all the selected stack files to deploy in sst.config.js
136 | deployStackSource = fmt.Sprintf("%s.stack(%s)", deployStackSource, stackFile)
137 | }
138 | deployStackSource = fmt.Sprintf("app%s;", deployStackSource)
139 | return deployStackSource
140 | }
141 |
142 | func SstImportStacks(stackFiles []string) string {
143 | var importStackSource string
144 | // import all existing stacks in sst.config.js
145 | for _, stackFile := range stackFiles {
146 | importStackSource = fmt.Sprintf("%simport { %s } from %s./stacks/%s%s;\n",
147 | importStackSource, stackFile, `"`, stackFile, `"`)
148 | }
149 | return importStackSource
150 | }
151 |
--------------------------------------------------------------------------------
/pickyhelpers/create_dockercompose.go:
--------------------------------------------------------------------------------
1 | package pickyhelpers
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/iancoleman/strcase"
7 | "github.com/wednesday-solutions/picky/hbs"
8 | "github.com/wednesday-solutions/picky/internal/constants"
9 | "github.com/wednesday-solutions/picky/internal/errorhandler"
10 | "github.com/wednesday-solutions/picky/internal/utils"
11 | )
12 |
13 | func CreateDockerComposeFile(stackInfo map[string]interface{}) error {
14 |
15 | filePath := fmt.Sprintf("%s/%s", utils.CurrentDirectory(),
16 | constants.DockerComposeFile,
17 | )
18 | var (
19 | backendMysqlDirectories []string
20 | backendPgDirectories []string
21 | backendMysqlSnakeCased []string
22 | backendPgSnakeCased []string
23 | )
24 | var snakeCaseDirName string
25 | _, databases, directories := utils.GetExistingStacksDatabasesAndDirectories()
26 | for index, directory := range directories {
27 | service := utils.FindService(directory)
28 | if service == constants.Backend {
29 | if databases[index] == constants.MySQL {
30 | backendMysqlDirectories = append(backendMysqlDirectories, directory)
31 | snakeCaseDirName = strcase.ToSnake(directory)
32 | backendMysqlSnakeCased = append(backendMysqlSnakeCased, snakeCaseDirName)
33 | } else if databases[index] == constants.PostgreSQL {
34 | backendPgDirectories = append(backendPgDirectories, directory)
35 | snakeCaseDirName = strcase.ToSnake(directory)
36 | backendPgSnakeCased = append(backendPgSnakeCased, snakeCaseDirName)
37 | }
38 | }
39 | }
40 |
41 | var backendPortNumber string
42 | var dbPortNumber string
43 | // Don't make any changes in the below source string.
44 | source := `version: '3'
45 | services:`
46 |
47 | for i, d := range backendPgDirectories {
48 | backendPortNumber = utils.FetchExistingPortNumber(d, constants.BackendPort)
49 | dbPortNumber = utils.FetchExistingPortNumber(d, constants.PostgresPort)
50 | source = fmt.Sprintf(`%s
51 | # Setup {{PostgreSQL}}
52 | %s_db:
53 | image: '{{dbVersion PostgreSQL}}'
54 | ports:
55 | - %s:%d
56 | restart: always # This will make sure that the container comes up post unexpected shutdowns
57 | env_file:
58 | - ./%s/.env.docker
59 | volumes:
60 | - %s{{databaseVolumeConnection PostgreSQL}}
61 | {{#equal stack GolangPostgreSQL}}
62 | environment:
63 | POSTGRES_USER: ${PSQL_USER}
64 | POSTGRES_PASSWORD: ${PSQL_PASS}
65 | POSTGRES_DB: ${PSQL_DBNAME}
66 | POSTGRES_PORT: ${PSQL_PORT}
67 | {{/equal}}
68 |
69 | {{#equal stack GolangPostgreSQL}}
70 | {{{waitForDBService PostgreSQL}}}
71 |
72 | {{/equal}}
73 | # Setup %s api
74 | %s:
75 | build:
76 | context: './%s'
77 | args:
78 | ENVIRONMENT_NAME: docker
79 | ports:
80 | - %s:%d
81 | env_file:
82 | - ./%s/.env.docker
83 | environment:
84 | ENVIRONMENT_NAME: docker
85 | {{dependsOnFieldOfGo stack}}
86 | `,
87 | source, backendPgSnakeCased[i], dbPortNumber,
88 | constants.PostgresPortNumber, d, d, d, d, d,
89 | backendPortNumber, constants.BackendPortNumber, d,
90 | )
91 | }
92 |
93 | for i, d := range backendMysqlDirectories {
94 | backendPortNumber = utils.FetchExistingPortNumber(d, constants.BackendPort)
95 | dbPortNumber = utils.FetchExistingPortNumber(d, constants.MysqlPort)
96 | source = fmt.Sprintf(`%s
97 | # Setup {{MySQL}}
98 | %s_db:
99 | image: '{{dbVersion MySQL}}'
100 | ports:
101 | - %s:%d
102 | restart: always # This will make sure that the container comes up post unexpected shutdowns
103 | env_file:
104 | - ./%s/.env.docker
105 | volumes:
106 | - %s{{databaseVolumeConnection MySQL}}
107 | {{#equal stack GolangMySQL}}
108 | environment:
109 | MYSQL_DATABASE: ${MYSQL_DBNAME}
110 | MYSQL_PASSWORD: ${MYSQL_PASS}
111 | MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
112 | {{/equal}}
113 |
114 | {{#equal stack GolangMySQL}}
115 | {{{waitForDBService MySQL}}}
116 |
117 | {{/equal}}
118 | # Setup %s api
119 | %s:
120 | build:
121 | context: './%s'
122 | args:
123 | ENVIRONMENT_NAME: docker
124 | ports:
125 | - %s:%d
126 | env_file:
127 | - ./%s/.env.docker
128 | environment:
129 | ENVIRONMENT_NAME: docker
130 | {{dependsOnFieldOfGo stack}}
131 | `,
132 | source, backendMysqlSnakeCased[i], dbPortNumber,
133 | constants.MysqlPortNumber, d, d, d, d, d,
134 | backendPortNumber, constants.BackendPortNumber, d,
135 | )
136 | }
137 |
138 | source = fmt.Sprintf(`%s
139 | {{#if backendStatus}}
140 | # Setup Redis
141 | redis:
142 | image: 'redis:6-alpine'
143 | ports:
144 | - {{portConnection redis}}
145 | # Default command that redis will execute at start
146 | command: ['redis-server']
147 |
148 | {{/if}}
149 | {{#each webDirectories}}
150 | # Setup {{this}} web
151 | {{this}}:
152 | build:
153 | context: './{{this}}'
154 | ports:
155 | - {{portConnection web}}
156 | env_file:
157 | - ./{{this}}/.env.docker
158 |
159 | {{else}}
160 | # No web stacks present
161 |
162 | {{/each}}{{#if backendStatus}}
163 | # Setup Volumes
164 | volumes:
165 | {{#each backendPgDirectories}}
166 | {{this}}-db-volume:
167 | {{/each}}
168 | {{#each backendMysqlDirectories}}
169 | {{this}}-db-volume:
170 | {{/each}}{{/if}}
171 | `, source)
172 |
173 | err := hbs.ParseAndWriteToFile(source, filePath, stackInfo)
174 | errorhandler.CheckNilErr(err)
175 |
176 | return nil
177 | }
178 |
--------------------------------------------------------------------------------
/pickyhelpers/create_infra.go:
--------------------------------------------------------------------------------
1 | package pickyhelpers
2 |
3 | import (
4 | "fmt"
5 | "path/filepath"
6 |
7 | "github.com/iancoleman/strcase"
8 | "github.com/wednesday-solutions/picky/hbs"
9 | "github.com/wednesday-solutions/picky/internal/constants"
10 | "github.com/wednesday-solutions/picky/internal/errorhandler"
11 | "github.com/wednesday-solutions/picky/internal/utils"
12 | "github.com/wednesday-solutions/picky/pickyhelpers/sources"
13 | )
14 |
15 | // IsInfraFilesExist checks the infra related files are exist of not.
16 | func IsInfraFilesExist() bool {
17 | path := utils.CurrentDirectory()
18 | files := []string{
19 | constants.PackageDotJsonFile,
20 | constants.EnvFile,
21 | constants.SstConfigFile,
22 | constants.Stacks,
23 | constants.ParseSstOutputs,
24 | }
25 | for _, file := range files {
26 | status, _ := utils.IsExists(filepath.Join(path, file))
27 | if !status {
28 | return false
29 | }
30 | }
31 | return true
32 | }
33 |
34 | // CreateInfraSetup creates package.json and .env files for infra setup.
35 | func CreateInfraSetup() error {
36 |
37 | infraFiles := make(map[string]string)
38 | // package.json file
39 | infraFiles[constants.PackageDotJsonFile] = sources.PackageDotJsonSource()
40 | // .env file
41 | infraFiles[constants.EnvFile] = sources.EnvFileSource()
42 |
43 | var err error
44 | var path string
45 | for file, source := range infraFiles {
46 | path = fmt.Sprintf("%s/%s", utils.CurrentDirectory(), file)
47 | err = utils.WriteToFile(path, source)
48 | errorhandler.CheckNilErr(err)
49 | }
50 | return nil
51 | }
52 |
53 | type Infra struct {
54 | Service string
55 | Stack string
56 | Database string
57 | DirName string
58 | CamelCaseDirName string
59 | Environment string
60 | ForceCreate bool
61 | }
62 |
63 | // CreateInfraStack creates the infra stack file of existing stack in the stacks directory.
64 | func (i Infra) CreateInfraStack() error {
65 | var err error
66 | var stackFileName string
67 | path := fmt.Sprintf("%s/%s", utils.CurrentDirectory(), constants.Stacks)
68 | folderExist, _ := utils.IsExists(path)
69 | if !folderExist {
70 | err = utils.MakeDirectory(utils.CurrentDirectory(), constants.Stacks)
71 | errorhandler.CheckNilErr(err)
72 | }
73 | stackFileName = fmt.Sprintf("%s%s", i.CamelCaseDirName, ".js")
74 | path = fmt.Sprintf("%s/%s/%s", utils.CurrentDirectory(), constants.Stacks, stackFileName)
75 | response := true
76 | if !i.ForceCreate {
77 | stackFileExist, _ := utils.IsExists(path)
78 | if stackFileExist {
79 | return errorhandler.ErrExist
80 | }
81 | }
82 | var source string
83 | if response {
84 | switch i.Service {
85 | case constants.Web:
86 | source = sources.WebStackSource(i.DirName, i.CamelCaseDirName, i.Environment)
87 | case constants.Backend:
88 | source = sources.BackendStackSource(i.Database, i.DirName, i.Environment)
89 | default:
90 | err := utils.PrintErrorMessage("Selected stack is invalid")
91 | return err
92 | }
93 | err = utils.WriteToFile(path, source)
94 | }
95 | return err
96 | }
97 |
98 | // CreateSstConfigFile creates sst.config.js file.
99 | func CreateSstConfigFile(stackInfo map[string]interface{}, directories []string,
100 | ) error {
101 | sstConfigSource := sources.SstConfigSource()
102 | path := fmt.Sprintf("%s/%s", utils.CurrentDirectory(), constants.SstConfigFile)
103 |
104 | // SST config file for all selected existing stacks.
105 | camelCaseDirectories := utils.ConvertToCamelCase(directories)
106 | stackInfo[constants.ExistingDirectories] = camelCaseDirectories
107 |
108 | err := hbs.ParseAndWriteToFile(sstConfigSource, path, stackInfo)
109 | errorhandler.CheckNilErr(err)
110 |
111 | // create parseSstOutputs.js file
112 | parseSstOutputsSource := sources.ParseSstOutputsSource()
113 | path = fmt.Sprintf("%s/%s", utils.CurrentDirectory(), constants.ParseSstOutputs)
114 |
115 | err = utils.WriteToFile(path, parseSstOutputsSource)
116 | errorhandler.CheckNilErr(err)
117 | return err
118 | }
119 |
120 | // UpdateEnvByEnvironment updates the env file with respect to environment.
121 | func UpdateEnvByEnvironment(dirName, environment string) error {
122 | path := fmt.Sprintf("%s/%s/%s", utils.CurrentDirectory(),
123 | dirName,
124 | fmt.Sprintf("%s.%s", constants.EnvFile, environment),
125 | )
126 | _, database := utils.FindStackAndDatabase(dirName)
127 |
128 | taskDefinition := utils.GetOutputsBackendObject(environment, dirName)
129 |
130 | envSource := sources.EnvSource(dirName, environment, database, taskDefinition.BackendObj)
131 | err := utils.WriteToFile(path, envSource)
132 | errorhandler.CheckNilErr(err)
133 |
134 | return nil
135 | }
136 |
137 | // IsInfraStacksExist will return non existing stacks in the stacks directory.
138 | // It will check the stack function is exists in the stacks directory.
139 | func GetNonExistingInfraStacks(stacksDirectories []string) []string {
140 | var status bool
141 | var nonExistingStacks []string
142 | for _, stack := range stacksDirectories {
143 | status = IsInfraStackExist(stack)
144 | if !status {
145 | nonExistingStacks = append(nonExistingStacks, stack)
146 | }
147 | }
148 | return nonExistingStacks
149 | }
150 |
151 | func IsInfraStackExist(stackDirName string) bool {
152 | camelCaseStack := fmt.Sprintf("%s%s", strcase.ToCamel(stackDirName), ".js")
153 | path := fmt.Sprintf("%s/%s/%s", utils.CurrentDirectory(), constants.Stacks, camelCaseStack)
154 | status, _ := utils.IsExists(path)
155 | return status
156 | }
157 |
--------------------------------------------------------------------------------
/internal/utils/find.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/wednesday-solutions/picky/internal/constants"
7 | "github.com/wednesday-solutions/picky/internal/errorhandler"
8 | )
9 |
10 | // FindStackAndDatabase return stack and database of given directory name.
11 | func FindStackAndDatabase(dirName string) (string, string) {
12 | var stack, database string
13 | _, langSuffix, stackSuffix, lastSuffix := SplitStackDirectoryName(dirName)
14 |
15 | switch lastSuffix {
16 | case constants.Pg:
17 | database = constants.PostgreSQL
18 | if stackSuffix == "hapi" {
19 | stack = constants.NodeHapiTemplate
20 | } else if stackSuffix == constants.Graphql {
21 | if langSuffix == "node" {
22 | stack = constants.NodeExpressGraphqlTemplate
23 | } else if langSuffix == constants.Golang {
24 | stack = constants.GolangEchoTemplate
25 | }
26 | }
27 | case constants.Mysql:
28 | database = constants.MySQL
29 | if stackSuffix == "hapi" {
30 | stack = constants.NodeHapiTemplate
31 | } else if stackSuffix == constants.Graphql {
32 | if langSuffix == "node" {
33 | stack = constants.NodeExpressGraphqlTemplate
34 | } else if langSuffix == constants.Golang {
35 | stack = constants.GolangEchoTemplate
36 | }
37 | }
38 | case constants.Web:
39 | if stackSuffix == "js" {
40 | if langSuffix == "react" {
41 | stack = constants.ReactJS
42 | } else if langSuffix == "next" {
43 | stack = constants.NextJS
44 | }
45 | } else if stackSuffix == constants.Graphql {
46 | if langSuffix == "react" {
47 | stack = constants.ReactGraphqlTS
48 | }
49 | }
50 | case constants.Mobile:
51 | if stackSuffix == "reactnative" {
52 | stack = constants.ReactNative
53 | } else if stackSuffix == "android" {
54 | stack = constants.Android
55 | } else if stackSuffix == "ios" {
56 | stack = constants.IOS
57 | } else if stackSuffix == "flutter" {
58 | stack = constants.Flutter
59 | }
60 | }
61 | return stack, database
62 | }
63 |
64 | // GetExistingStacksDatabasesAndDirectories return existing stacks details.
65 | // Stack details contain stack name, database, and the directory name.
66 | func GetExistingStacksDatabasesAndDirectories() ([]string, []string, []string) {
67 | var stacks, databases, dirNames []string
68 | var stack, database string
69 | directories, err := ReadAllContents(CurrentDirectory())
70 | errorhandler.CheckNilErr(err)
71 |
72 | for _, dirName := range directories {
73 | stack, database = FindStackAndDatabase(dirName)
74 | if stack != "" {
75 | stacks = append(stacks, stack)
76 | databases = append(databases, database)
77 | dirNames = append(dirNames, dirName)
78 | }
79 | }
80 | return stacks, databases, dirNames
81 | }
82 |
83 | // FindUserInputStackName return user-input of the given stack directory name.
84 | func FindUserInputStackName(dirName string) string {
85 | userInput, _, _, _ := SplitStackDirectoryName(dirName)
86 | return userInput
87 | }
88 |
89 | // ExistingStackAndDatabase return stack and database of the given stack directory name.
90 | func ExistingStackAndDatabase(dirName string) (string, string) {
91 | stack, database := FindStackAndDatabase(dirName)
92 | return stack, database
93 | }
94 |
95 | // FindService return service of the given stack directory name.
96 | func FindService(dirName string) string {
97 | _, _, _, lastSuffix := SplitStackDirectoryName(dirName)
98 | switch lastSuffix {
99 | case constants.Pg, constants.Mysql, constants.Mongo:
100 | return constants.Backend
101 | default:
102 | return lastSuffix
103 | }
104 | }
105 |
106 | // FindStackDirectoriesByConfigStacks will return an array of stack directories present
107 | // in the root directory. Eg: [api-node-hapi-mysql, fe-react-web]
108 | func FindStackDirectoriesByConfigStacks(configStacks []string) []string {
109 | var stacks []string
110 |
111 | _, _, directories := GetExistingStacksDatabasesAndDirectories()
112 | camelCaseDirectories := ConvertToCamelCase(directories)
113 |
114 | for _, configStack := range configStacks {
115 | for idx, camelCaseDirName := range camelCaseDirectories {
116 | camelCaseDirName = fmt.Sprintf("%s%s", camelCaseDirName, ".js")
117 | if configStack == camelCaseDirName {
118 | stacks = append(stacks, directories[idx])
119 | }
120 | }
121 | }
122 | return stacks
123 | }
124 |
125 | // IsBackendWebAndMobileExist checks backend, web and mobile stacks is exist or not.
126 | func IsBackendWebAndMobileExist() (bool, bool, bool) {
127 | var service string
128 | backendExist, webExist, mobileExist := false, false, false
129 | _, _, stackDirectories := GetExistingStacksDatabasesAndDirectories()
130 | for _, stackDir := range stackDirectories {
131 | service = FindService(stackDir)
132 | if service == constants.Web {
133 | webExist = true
134 | } else if service == constants.Backend {
135 | backendExist = true
136 | } else if service == constants.Mobile {
137 | mobileExist = true
138 | }
139 | }
140 | return backendExist, webExist, mobileExist
141 | }
142 |
143 | func FindExistingGraphqlBackendAndWebStacks() ([]string, []string) {
144 | var backendGraphqlStacks, webGraphqlStacks []string
145 | var stack, service string
146 | _, _, stackDirectories := GetExistingStacksDatabasesAndDirectories()
147 | for _, stackDir := range stackDirectories {
148 | service = FindService(stackDir)
149 | if service == constants.Backend {
150 | stack, _ = FindStackAndDatabase(stackDir)
151 | if stack == constants.NodeExpressGraphqlTemplate ||
152 | stack == constants.GolangEchoTemplate {
153 | backendGraphqlStacks = append(backendGraphqlStacks, stackDir)
154 | }
155 | } else if service == constants.Web {
156 | stack, _ = FindStackAndDatabase(stackDir)
157 | if stack == constants.ReactGraphqlTS {
158 | webGraphqlStacks = append(webGraphqlStacks, stackDir)
159 | }
160 | }
161 | }
162 | return backendGraphqlStacks, webGraphqlStacks
163 | }
164 |
--------------------------------------------------------------------------------
/prompt/infra.go:
--------------------------------------------------------------------------------
1 | package prompt
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/iancoleman/strcase"
7 | "github.com/wednesday-solutions/picky/internal/constants"
8 | "github.com/wednesday-solutions/picky/internal/errorhandler"
9 | "github.com/wednesday-solutions/picky/internal/utils"
10 | "github.com/wednesday-solutions/picky/pickyhelpers"
11 | )
12 |
13 | // PromptSetupInfra is the prompt for the setup infra option of Home prompt.
14 | func PromptSetupInfra() {
15 | var p PromptInput
16 | p.Label = "Do you want to setup infrastructure for your project"
17 | p.GoBack = PromptHome
18 | response := p.PromptYesOrNoSelect()
19 | if response {
20 | cloudProvider := PromptCloudProvider()
21 | environment := PromptEnvironment()
22 | response := true
23 | // stack will get infra stack files
24 | stacks := utils.GetExistingInfraStacks()
25 | if len(stacks) > 0 {
26 | // change infra stack files into stack directory files.
27 | stacks = utils.FindStackDirectoriesByConfigStacks(stacks)
28 | message := "Infra setup is already exist for the following stacks,\n\n"
29 | for i, stack := range stacks {
30 | message = fmt.Sprintf("%s %d. %s\n", message, i+1, stack)
31 | }
32 | fmt.Printf("%s\n", message)
33 | p.Label = "Do you want to change the existing stacks"
34 | p.GoBack = PromptSetupInfra
35 | response = p.PromptYesOrNoSelect()
36 | }
37 | if response {
38 | stacks = PromptSelectExistingStacks()
39 | err := CreateInfra(stacks, cloudProvider, environment)
40 | errorhandler.CheckNilErr(err)
41 | }
42 | if ShowPromptGitInit() {
43 | p.Label = "Do you want to initialize git"
44 | response = p.PromptYesOrNoSelect()
45 | if response {
46 | err := GitInit()
47 | errorhandler.CheckNilErr(err)
48 | }
49 | }
50 | err := PromptDeployAfterInfra(stacks, environment)
51 | errorhandler.CheckNilErr(err)
52 | }
53 | PromptHome()
54 | }
55 |
56 | // PromptCloudProvider is a prompt for selecting a cloud provider.
57 | func PromptCloudProvider() string {
58 | var p PromptInput
59 | p.Label = "Choose a cloud provider"
60 | p.Items = []string{constants.AWS}
61 | p.GoBack = PromptHome
62 | cp, _ := p.PromptSelect()
63 | return cp
64 | }
65 |
66 | // PromptEnvironment is a prompt for selecting an environment.
67 | func PromptEnvironment() string {
68 | var p PromptInput
69 | p.Label = "Choose an environment"
70 | p.Items = []string{constants.Development, constants.Production}
71 | p.GoBack = PromptHome
72 | env, _ := p.PromptSelect()
73 | return env
74 | }
75 |
76 | // CreateInfra execute all the functionalities of infra setup.
77 | func CreateInfra(directories []string, cloudProvider string, environment string) error {
78 | switch cloudProvider {
79 | case constants.AWS:
80 | status := pickyhelpers.IsInfraFilesExist()
81 |
82 | var err error
83 | if !status {
84 | err = pickyhelpers.CreateInfraSetup()
85 | errorhandler.CheckNilErr(err)
86 | }
87 | var response bool
88 | var s pickyhelpers.StackDetails
89 | for _, dirName := range directories {
90 | var infra pickyhelpers.Infra
91 | infra.Service = utils.FindService(dirName)
92 | infra.Stack, infra.Database = utils.FindStackAndDatabase(dirName)
93 | infra.DirName = dirName
94 | infra.CamelCaseDirName = strcase.ToCamel(dirName)
95 | infra.Environment = environment
96 | infra.ForceCreate = false
97 |
98 | s.Service = infra.Service
99 | s.Stack = infra.Stack
100 | s.Database = infra.Database
101 | s.Environment = environment
102 | s.StackInfo = s.GetStackInfo()
103 |
104 | err = infra.CreateInfraStack()
105 | if err != nil {
106 | if err.Error() == errorhandler.ErrExist.Error() {
107 | response = PromptAlreadyExist(dirName)
108 | if response {
109 | infra.ForceCreate = true
110 | err = infra.CreateInfraStack()
111 | errorhandler.CheckNilErr(err)
112 | }
113 | } else {
114 | errorhandler.CheckNilErr(err)
115 | }
116 | }
117 | if infra.Service == constants.Backend {
118 | err = pickyhelpers.UpdateEnvByEnvironment(dirName, environment)
119 | errorhandler.CheckNilErr(err)
120 | }
121 | }
122 | err = pickyhelpers.CreateSstConfigFile(s.StackInfo, directories)
123 | errorhandler.CheckNilErr(err)
124 |
125 | fmt.Printf("\n%s %s", "Generating", errorhandler.CompleteMessage)
126 | default:
127 | fmt.Printf("\nSelected stack is invalid.\n")
128 | }
129 | return nil
130 | }
131 |
132 | // PromptCreateInfraStacksWhenDeploy will setup the infra of stacks which are not already set up.
133 | func PromptCreateInfraStacksWhenDeploy(directories []string, environment string) error {
134 | count := 0
135 | for {
136 | var p PromptInput
137 | p.Label = "Do you want to setup infra for newly selected stacks"
138 | p.GoBack = PromptDeploy
139 | response := p.PromptYesOrNoSelect()
140 | if response {
141 | var err error
142 | status := pickyhelpers.IsInfraFilesExist()
143 | if !status {
144 | err = pickyhelpers.CreateInfraSetup()
145 | errorhandler.CheckNilErr(err)
146 | }
147 | var infra pickyhelpers.Infra
148 | for _, dirName := range directories {
149 | infra.DirName = dirName
150 | infra.CamelCaseDirName = strcase.ToCamel(dirName)
151 | infra.Service = utils.FindService(dirName)
152 | infra.Stack, infra.Database = utils.FindStackAndDatabase(dirName)
153 | infra.Environment = environment
154 | infra.ForceCreate = false
155 | err = infra.CreateInfraStack()
156 | if err != nil {
157 | if err.Error() == errorhandler.ErrExist.Error() {
158 | response := PromptAlreadyExist(infra.DirName)
159 | if response {
160 | infra.ForceCreate = true
161 | err = infra.CreateInfraStack()
162 | errorhandler.CheckNilErr(err)
163 | }
164 | }
165 | errorhandler.CheckNilErr(err)
166 | }
167 | }
168 | fmt.Println(errorhandler.DoneMessage)
169 | break
170 | }
171 | count++
172 | if count > 1 {
173 | break
174 | }
175 | }
176 | return nil
177 | }
178 |
--------------------------------------------------------------------------------
/prompt/deploy.go:
--------------------------------------------------------------------------------
1 | package prompt
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/wednesday-solutions/picky/internal/constants"
7 | "github.com/wednesday-solutions/picky/internal/errorhandler"
8 | "github.com/wednesday-solutions/picky/internal/utils"
9 | "github.com/wednesday-solutions/picky/pickyhelpers"
10 | )
11 |
12 | // PromptDeploy is the prompt for the deploy option of Home prompt.
13 | func PromptDeploy() {
14 | var p PromptInput
15 | p.Label = "Do you want to deploy your project"
16 | p.GoBack = PromptHome
17 | response := p.PromptYesOrNoSelect()
18 | if response {
19 | err := DeployStacks([]string{}, "")
20 | errorhandler.CheckNilErr(err)
21 | }
22 | PromptHome()
23 | }
24 |
25 | // DeployStacks will deploy the infrastructure.
26 | func DeployStacks(stacks []string, environment string) error {
27 | if environment == "" {
28 | environment = PromptEnvironment()
29 | }
30 | afterInfra := false
31 | if len(stacks) == 0 {
32 | stacks = utils.GetExistingInfraStacks()
33 | } else {
34 | // afterInfra will become true if the DeployStacks function is called from DeployAfterInfra
35 | afterInfra = true
36 | }
37 | var selectedOption string
38 | if len(stacks) > 0 {
39 | if !afterInfra {
40 | stacks = utils.FindStackDirectoriesByConfigStacks(stacks)
41 | }
42 | // Prints the existing infra stacks.
43 | message := "Existing stacks are,\n\n"
44 | for i, stack := range stacks {
45 | message = fmt.Sprintf("%s %d. %s\n", message, i+1, stack)
46 | }
47 | fmt.Printf("%s\n", message)
48 |
49 | selectedOption, _ = PromptDeployNow()
50 | if selectedOption == constants.GoBack {
51 | PromptDeploy()
52 | }
53 | }
54 | if selectedOption == constants.ChangeStacks || len(stacks) == 0 {
55 |
56 | stacks = PromptSelectExistingStacks()
57 | nonExistingStacks := pickyhelpers.GetNonExistingInfraStacks(stacks)
58 | if len(nonExistingStacks) > 0 {
59 | message := "Didn't setup Infrastructure for the following stacks,\n\n"
60 | for i, stack := range nonExistingStacks {
61 | message = fmt.Sprintf("%s %d. %s\n", message, i+1, stack)
62 | }
63 | fmt.Printf("%s\n", message)
64 | // create infra stacks for non existing stacks.
65 | err := PromptCreateInfraStacksWhenDeploy(nonExistingStacks, environment)
66 | errorhandler.CheckNilErr(err)
67 | }
68 | PromptDeploy()
69 | }
70 | var s pickyhelpers.StackDetails
71 | s.Environment = environment
72 | s.StackInfo = s.GetStackInfo()
73 | err := pickyhelpers.CreateSstConfigFile(s.StackInfo, stacks)
74 | errorhandler.CheckNilErr(err)
75 |
76 | // Deploy infrastructure
77 | err = InstallDependenciesAndDeploy(stacks, environment)
78 | return err
79 | }
80 |
81 | func PromptDeployNow() (string, int) {
82 | var p PromptInput
83 | p.Items = []string{
84 | constants.DeployNow,
85 | constants.ChangeStacks,
86 | constants.GoBack,
87 | }
88 | p.Label = "Pick an option"
89 | p.GoBack = PromptDeploy
90 | return p.PromptSelect()
91 | }
92 |
93 | // PromptDeployAfterInfra will come up after setting up the infrastructure.
94 | func PromptDeployAfterInfra(configStacks []string, environment string) error {
95 | var p PromptInput
96 | p.Label = "Do you want to deploy your project"
97 | p.GoBack = PromptHome
98 | response := p.PromptYesOrNoSelect()
99 | if response {
100 | err := DeployStacks(configStacks, environment)
101 | return err
102 | }
103 | return nil
104 | }
105 |
106 | // PromptBuildSST runs 'yarn build'
107 | func PromptBuildSST(pkgManager string) error {
108 | var p PromptInput
109 | p.Label = "Do you want to build"
110 | p.GoBack = PromptDeploy
111 | response := p.PromptYesOrNoSelect()
112 | if response {
113 | err := pickyhelpers.BuildSST(pkgManager)
114 | return err
115 | } else {
116 | PromptDeploy()
117 | }
118 | return nil
119 | }
120 |
121 | // InstallDependenciesAndDeploy install dependencies of each file, then deploy.
122 | func InstallDependenciesAndDeploy(configStacks []string, environment string) error {
123 | pkgManager := utils.GetPackageManagerOfUser()
124 |
125 | // install sst dependencies(root directory)
126 | err := utils.PrintInfoMessage("Installing sst dependencies")
127 | errorhandler.CheckNilErr(err)
128 | err = pickyhelpers.InstallDependencies(pkgManager)
129 | errorhandler.CheckNilErr(err)
130 |
131 | // install selected stacks dependencies(respected stack directory)
132 | for _, configStackDir := range configStacks {
133 | err := utils.PrintInfoMessage(fmt.Sprintf("Installing %s dependencies", configStackDir))
134 | errorhandler.CheckNilErr(err)
135 | err = pickyhelpers.InstallDependencies(
136 | pkgManager,
137 | utils.CurrentDirectory(),
138 | configStackDir,
139 | )
140 | errorhandler.CheckNilErr(err)
141 | }
142 | err = utils.PrintInfoMessage("Deploying...")
143 | errorhandler.CheckNilErr(err)
144 | err = pickyhelpers.DeploySST(pkgManager, environment)
145 | errorhandler.CheckNilErr(err)
146 |
147 | err = pickyhelpers.ParseDeployOutputs()
148 | errorhandler.CheckNilErr(err)
149 |
150 | err = utils.CreateInfraOutputsJson(environment)
151 | return err
152 | }
153 |
154 | func ShowRemoveDeploy() bool {
155 | path := fmt.Sprintf("%s/%s", utils.CurrentDirectory(), constants.DotSstDirectory)
156 | status, _ := utils.IsExists(path)
157 | return status
158 | }
159 |
160 | // PromptRemoveDeploy is the prompt for the remove deploy option of Home prompt.
161 | func PromptRemoveDeploy() {
162 | var p PromptInput
163 | p.Label = "Do you want to remove the deployed infrastructure"
164 | p.GoBack = PromptHome
165 | response := p.PromptYesOrNoSelect()
166 | if response {
167 | environment := PromptEnvironment()
168 | err := RemoveDeploy(environment)
169 | errorhandler.CheckNilErr(err)
170 | }
171 | PromptHome()
172 | }
173 |
174 | func RemoveDeploy(environment string) error {
175 | pkgManager := utils.GetPackageManagerOfUser()
176 | environment = utils.GetShortEnvName(environment)
177 |
178 | err := utils.PrintInfoMessage("Removing deployed infrastructure..")
179 | errorhandler.CheckNilErr(err)
180 | arg := fmt.Sprintf("%s:%s", "remove", environment)
181 | err = utils.RunCommandWithLogs("", pkgManager, "run", arg)
182 | return err
183 | }
184 |
--------------------------------------------------------------------------------
/prompt/prompt.go:
--------------------------------------------------------------------------------
1 | package prompt
2 |
3 | import (
4 | "fmt"
5 | "strings"
6 |
7 | "github.com/spaceweasel/promptui"
8 | "github.com/wednesday-solutions/picky/internal/constants"
9 | "github.com/wednesday-solutions/picky/internal/errorhandler"
10 | "github.com/wednesday-solutions/picky/internal/utils"
11 | )
12 |
13 | type PromptInput struct {
14 | Label string
15 | Items []string
16 | GoBack func()
17 | }
18 |
19 | func (p PromptInput) PromptSelect() (string, int) {
20 | templates := &promptui.SelectTemplates{
21 | Active: fmt.Sprintf("%s {{ . | magenta | underline }}", constants.IconChoose),
22 | Selected: fmt.Sprintf("%s {{ . | cyan }}", constants.IconSelect),
23 | }
24 | prompt := promptui.Select{
25 | Label: p.Label,
26 | Items: p.Items,
27 | Templates: templates,
28 | IsVimMode: false,
29 | Pointer: promptui.DefaultCursor,
30 | Size: constants.SizeOfPromptSelect,
31 | }
32 | index, result, err := prompt.Run()
33 | if err != nil {
34 | if err.Error() == errorhandler.ErrInterrupt.Error() {
35 | err = errorhandler.ExitMessage
36 | } else if err == promptui.ErrEOF {
37 | p.GoBack()
38 | fmt.Printf("\nSomething error happened in GoBack.\n")
39 | }
40 | errorhandler.CheckNilErr(err)
41 | }
42 | return result, index
43 | }
44 |
45 | type Label struct {
46 | InvalidPrefix string
47 | ValidPrefix string
48 | Question string
49 | Suffix string
50 | }
51 |
52 | func (p PromptInput) PromptYesOrNoConfirm() bool {
53 | var l Label
54 | l.Question = p.Label
55 | l.InvalidPrefix = constants.IconQuestion
56 | l.ValidPrefix = constants.IconSelect
57 | l.Suffix = "[y/N]"
58 |
59 | validateFn := func(input string) error {
60 | if len(input) < 1 {
61 | return fmt.Errorf("Please enter 'y' or 'n'")
62 | }
63 | return nil
64 | }
65 | templates := &promptui.PromptTemplates{
66 | Valid: "{{ .ValidPrefix | bold }} {{ .Question | bold }} {{ .Suffix | faint }} ",
67 | Invalid: "{{ .InvalidPrefix | bold }} {{ .Question | bold }} {{ .Suffix | faint }} ",
68 | Success: "{{ .Question | faint }} {{ .Suffix | faint }} ",
69 | }
70 | // Refer official doc of promptui for prompt label templates.
71 | prompt := promptui.Prompt{
72 | Label: l,
73 | Templates: templates,
74 | Validate: validateFn,
75 | }
76 | for {
77 | result, err := prompt.Run()
78 | if err != nil {
79 | if err.Error() == errorhandler.ErrInterrupt.Error() {
80 | err = errorhandler.ExitMessage
81 | } else if err == promptui.ErrEOF {
82 | p.GoBack()
83 | fmt.Printf("\nSomething error happened in GoBack.\n")
84 | }
85 | errorhandler.CheckNilErr(err)
86 | }
87 | result = strings.ToLower(result)
88 | if result == "y" || result == "yes" {
89 | return true
90 | } else if result == "n" || result == "no" {
91 | return false
92 | } else {
93 | err := utils.PrintWarningMessage("Please enter 'y' or 'n'.")
94 | errorhandler.CheckNilErr(err)
95 | }
96 | }
97 | }
98 |
99 | func (p PromptInput) PromptGetInput() string {
100 |
101 | validate := func(input string) error {
102 | if len(input) <= 1 {
103 | return fmt.Errorf("Length should be greater than 1%s\n", errorhandler.Exclamation)
104 | }
105 | return nil
106 | }
107 | templates := &promptui.PromptTemplates{
108 | Prompt: "{{ . }}",
109 | Valid: fmt.Sprintf("%s {{ . | green }}", constants.IconSelect),
110 | Invalid: fmt.Sprintf("%s {{ . | red }}", constants.IconWrong),
111 | }
112 | prompt := promptui.Prompt{
113 | Label: p.Label,
114 | Validate: validate,
115 | Templates: templates,
116 | IsVimMode: true,
117 | }
118 | result, err := prompt.Run()
119 | if err != nil {
120 | if err.Error() == errorhandler.ErrInterrupt.Error() {
121 | err = errorhandler.ExitMessage
122 | } else if err == promptui.ErrEOF {
123 | p.GoBack()
124 | fmt.Printf("\nSomething error happened in GoBack.\n")
125 | }
126 | errorhandler.CheckNilErr(err)
127 | }
128 | return result
129 | }
130 |
131 | func (p PromptInput) PromptYesOrNoSelect() bool {
132 | p.Items = []string{constants.Yes, constants.No}
133 |
134 | response, _ := p.PromptSelect()
135 | if response == constants.Yes {
136 | return true
137 | } else {
138 | return false
139 | }
140 | }
141 |
142 | func (p PromptInput) PromptMultiSelect() ([]string, []int) {
143 | templates := &promptui.MultiSelectTemplates{
144 | Selected: fmt.Sprintf("%s {{ . | green }}", constants.IconSelect),
145 | }
146 | prompt := promptui.MultiSelect{
147 | Label: p.Label,
148 | Items: p.Items,
149 | Templates: templates,
150 | Size: constants.SizeOfPromptSelect,
151 | }
152 | results, err := prompt.Run()
153 | if err != nil {
154 | if err.Error() == errorhandler.ErrInterrupt.Error() {
155 | err = errorhandler.ExitMessage
156 | } else if err == promptui.ErrEOF {
157 | p.GoBack()
158 | fmt.Printf("\nSomething error happened in GoBack.\n")
159 | }
160 | errorhandler.CheckNilErr(err)
161 | return nil, nil
162 | }
163 | selected := []string{}
164 | for _, result := range results {
165 | selected = append(selected, p.Items[result])
166 | }
167 | err = utils.PrintMultiSelectMessage(selected)
168 | errorhandler.CheckNilErr(err)
169 | return selected, results
170 | }
171 |
172 | // PromptStack is prompt for selecting stack. It will come up after user selecting the service.
173 | func (i *InitInfo) PromptStack() string {
174 | stacksWithDetails := utils.GetStackDetails(i.Service)
175 | templates := &promptui.SelectTemplates{
176 | Active: fmt.Sprintf("%s {{ .Name | magenta | underline }}", constants.IconChoose),
177 | Inactive: "{{ .Name }}",
178 | Selected: fmt.Sprintf("%s {{ .Name | cyan }}", constants.IconSelect),
179 | Details: i.GetDetailsTemplatesOfStacks(),
180 | }
181 |
182 | prompt := promptui.Select{
183 | Label: "Pick a stack",
184 | Items: stacksWithDetails,
185 | Templates: templates,
186 | Size: constants.SizeOfPromptSelect,
187 | }
188 | idx, _, err := prompt.Run()
189 | if err != nil {
190 | if err.Error() == errorhandler.ErrInterrupt.Error() {
191 | err = errorhandler.ExitMessage
192 | } else if err == promptui.ErrEOF {
193 | PromptSelectService()
194 | fmt.Printf("\nSomething error happened in GoBack.\n")
195 | }
196 | errorhandler.CheckNilErr(err)
197 | }
198 | return stacksWithDetails[idx].Name
199 | }
200 |
--------------------------------------------------------------------------------
/internal/utils/flag.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "fmt"
5 | "strings"
6 |
7 | "github.com/wednesday-solutions/picky/internal/constants"
8 | )
9 |
10 | func UseService() string {
11 | usageString := fmt.Sprintf(`choose a service
12 | %d. %s
13 | %d. %s
14 | %d. %s
15 | `,
16 | 1, constants.Web,
17 | 2, constants.Mobile,
18 | 3, constants.Backend)
19 | return usageString
20 | }
21 |
22 | func AllWebStacksString() string {
23 | usage := fmt.Sprintf(`
24 | Web stacks:
25 | %d. %s -> %s
26 | %d. %s -> %s
27 | %d. %s -> %s
28 | `,
29 | 1, constants.ReactJS, constants.ReactjsLower,
30 | 2, constants.NextJS, constants.NextjsLower,
31 | 3, constants.ReactGraphqlTS, constants.ReactGraphqlLower,
32 | )
33 | return usage
34 | }
35 |
36 | func AllMobileStacksString() string {
37 | usage := fmt.Sprintf(`
38 | Mobile stacks:
39 | %d. %s -> %s
40 | %d. %s -> %s
41 | %d. %s -> %s
42 | %d. %s -> %s
43 | `,
44 | 1, constants.ReactNative, constants.ReactNativeLower,
45 | 2, constants.Android, constants.AndroidLower,
46 | 3, constants.IOS, constants.IOSLower,
47 | 4, constants.Flutter, constants.FlutterLower,
48 | )
49 | return usage
50 | }
51 | func AllBackendStacksString() string {
52 | usage := fmt.Sprintf(`
53 | Backend stacks:
54 | %d. %s -> %s
55 | %d. %s -> %s
56 | %d. %s -> %s
57 | %d. %s -> %s
58 | `,
59 | 1, constants.NodeHapiTemplate, constants.NodeHapi,
60 | 2, constants.NodeExpressGraphqlTemplate, constants.NodeGraphql,
61 | 3, constants.NodeExpressTemplate, constants.NodeExpress,
62 | 4, constants.GolangEchoTemplate, constants.Golang,
63 | )
64 | return usage
65 | }
66 |
67 | func AllStacksString() string {
68 | usage := fmt.Sprintf(`%s%s%s`,
69 | AllWebStacksString(),
70 | AllMobileStacksString(),
71 | AllBackendStacksString(),
72 | )
73 | return usage
74 | }
75 |
76 | func UseStack() string {
77 | usageString := fmt.Sprintf("choose a stack (select the second name)\n%s", AllStacksString())
78 | return usageString
79 | }
80 |
81 | func UseDatabase() string {
82 | usageString := fmt.Sprintf(`Choose a database
83 | %d. %s
84 | %d. %s
85 | %d. %s
86 | `,
87 | 1, constants.Postgresql,
88 | 2, constants.Mysql,
89 | 3, constants.Mongodb,
90 | )
91 | return usageString
92 | }
93 |
94 | func UseDirectory() string {
95 | return `provide a directory prefix name (suffix will be added)
96 | Eg: example-react-js-web | example-node-hapi-pg`
97 | }
98 |
99 | func GetStackByFlags(stack string) string {
100 | switch stack {
101 | case constants.ReactjsLower, constants.ReactJS:
102 | return constants.ReactJS
103 | case constants.NextjsLower, constants.NextJS:
104 | return constants.NextJS
105 | case constants.ReactGraphqlLower, constants.ReactGraphqlTS:
106 | return constants.ReactGraphqlTS
107 |
108 | case constants.ReactNativeLower, constants.ReactNative:
109 | return constants.ReactNative
110 | case constants.AndroidLower, constants.Android:
111 | return constants.Android
112 | case constants.IOSLower, constants.IOS:
113 | return constants.IOS
114 | case constants.Flutter, constants.FlutterLower:
115 | return constants.FlutterLower
116 |
117 | case constants.NodeHapi, constants.NodeHapiTemplate:
118 | return constants.NodeHapiTemplate
119 | case constants.NodeGraphql, constants.NodeExpressGraphqlTemplate:
120 | return constants.NodeExpressGraphqlTemplate
121 | case constants.NodeExpress, constants.NodeExpressTemplate:
122 | return constants.NodeExpressTemplate
123 | case constants.Golang, constants.GolangEchoTemplate:
124 | return constants.GolangEchoTemplate
125 |
126 | default:
127 | return ""
128 | }
129 | }
130 |
131 | func GetDatabase(db string) string {
132 | db = strings.ToLower(db)
133 | if db == constants.Postgresql || db == constants.Postgres {
134 | return constants.PostgreSQL
135 | } else if db == constants.Mysql {
136 | return constants.MySQL
137 | } else if db == constants.Mongodb {
138 | return constants.MongoDB
139 | } else {
140 | return db
141 | }
142 | }
143 |
144 | func UseInfraStacks() string {
145 | _, _, directories := GetExistingStacksDatabasesAndDirectories()
146 | var usageString string
147 | if len(directories) == 0 {
148 | usageString = "Stacks not exist. Existing stacks see here.\n"
149 | return usageString
150 | }
151 | usageString = "existing stacks are\n"
152 | for idx, dir := range directories {
153 | usageString = fmt.Sprintf("%s %d. %s\n", usageString, idx+1, dir)
154 | }
155 | return usageString
156 | }
157 |
158 | func GetExistingStacks() []string {
159 | _, _, directories := GetExistingStacksDatabasesAndDirectories()
160 | return directories
161 | }
162 |
163 | func UseCloudProvider() string {
164 | usageString := fmt.Sprintf(`choose a cloud provider
165 | %d. %s
166 | `, 1, constants.AWS)
167 | return usageString
168 | }
169 |
170 | func UseEnvironment() string {
171 | usageString := fmt.Sprintf(`choose an environment
172 | %d. %s
173 | %d. %s
174 | %d. %s
175 | `, 1, constants.Development,
176 | 2, constants.QA,
177 | 3, constants.Production)
178 | return usageString
179 | }
180 |
181 | func GetCloudProvider(cp string) string {
182 | cp = strings.ToLower(cp)
183 | if cp == "aws" {
184 | return constants.AWS
185 | }
186 | return cp
187 | }
188 |
189 | func GetEnvironmentValue(env string) string {
190 | env = strings.ToLower(env)
191 | if env == constants.Development || env == constants.Develop || env == constants.Dev {
192 | return constants.Development
193 | }
194 | return env
195 | }
196 |
197 | func UseDockerCompose() string {
198 | return "create Docker Compose file for the stacks which are present in the root directory."
199 | }
200 |
201 | func UseCI() string {
202 | return "create CI file for the stacks which are present in the root directory."
203 | }
204 |
205 | func UseCD() string {
206 | return "create CD file for the stacks which are present in the root directory."
207 | }
208 |
209 | func UsePlatform() string {
210 | usage := fmt.Sprintf(`choose a platform
211 | %d. %s
212 | `,
213 | 1, constants.Github)
214 | return usage
215 | }
216 |
217 | func ConvertStacksIntoString(stacks []string) string {
218 | var response string
219 | for idx, stack := range stacks {
220 | response = fmt.Sprintf("%s\n %d. %s", response, idx+1, stack)
221 | }
222 | return response
223 | }
224 |
--------------------------------------------------------------------------------
/pickyhelpers/update_env_files.go:
--------------------------------------------------------------------------------
1 | package pickyhelpers
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/iancoleman/strcase"
7 | "github.com/wednesday-solutions/picky/internal/constants"
8 | "github.com/wednesday-solutions/picky/internal/errorhandler"
9 | "github.com/wednesday-solutions/picky/internal/utils"
10 | )
11 |
12 | func UpdateEnvFiles(stack, dirName string) error {
13 |
14 | var envFileSources []string
15 | snakeCaseDirName := strcase.ToSnake(dirName)
16 | envFiles := []string{".env.local", ".env.development", constants.DockerEnvFile}
17 | for idx, file := range envFiles {
18 | envFiles[idx] = fmt.Sprintf("%s/%s", dirName, file)
19 | }
20 | var envLocalSource, envDevSource, envDockerSource string
21 | backendPortNumber := utils.GetPortNumber(constants.BackendPortNumber)
22 | redisPortNumber := utils.GetPortNumber(constants.RedisPortNumber)
23 | switch stack {
24 | case constants.NodeHapiTemplate:
25 |
26 | postgresPortNumber := utils.GetDatabasePortNumber(constants.PostgreSQL)
27 | envLocalSource = fmt.Sprintf(`NAME=Node Template
28 | NODE_ENV=development
29 | ENVIRONMENT_NAME=local
30 | PORT=%d
31 | DB_URI=postgres://root:password@localhost:5432/temp_dev
32 | POSTGRES_HOST=0.0.0.0
33 | POSTGRES_PORT=%d
34 | POSTGRES_DB=temp_dev
35 | POSTGRES_USER=root
36 | POSTGRES_PASSWORD=password
37 | REDIS_HOST=localhost
38 | REDIS_PORT=%d`,
39 | backendPortNumber,
40 | postgresPortNumber,
41 | redisPortNumber,
42 | )
43 |
44 | envDevSource = fmt.Sprintf(`NAME=Node Template (DEV)
45 | NODE_ENV=development
46 | ENVIRONMENT_NAME=development
47 | PORT=%d
48 | DB_URI=postgres://root:password@db_postgres:5432/temp_dev
49 | POSTGRES_HOST=db_postgres
50 | POSTGRES_PORT=%d
51 | POSTGRES_DB=temp_dev
52 | POSTGRES_USER=root
53 | POSTGRES_PASSWORD=password
54 | REDIS_HOST=redis
55 | REDIS_PORT=%d`,
56 | backendPortNumber,
57 | postgresPortNumber,
58 | redisPortNumber,
59 | )
60 |
61 | envDockerSource = fmt.Sprintf(`NAME=Node Template
62 | NODE_ENV=production
63 | ENVIRONMENT_NAME=docker
64 | PORT=%d
65 | DB_URI=postgres://root:password@%s_db:5432/temp_dev
66 | POSTGRES_HOST=%s_db
67 | POSTGRES_DB=temp_dev
68 | POSTGRES_USER=root
69 | POSTGRES_PASSWORD=password
70 | POSTGRES_PORT=%d
71 | REDIS_HOST=redis
72 | REDIS_PORT=%d`,
73 | backendPortNumber,
74 | snakeCaseDirName,
75 | snakeCaseDirName,
76 | postgresPortNumber,
77 | redisPortNumber,
78 | )
79 |
80 | envFileSources = []string{envLocalSource, envDevSource, envDockerSource}
81 |
82 | case constants.NodeExpressGraphqlTemplate:
83 |
84 | mysqlPortNumber := utils.GetDatabasePortNumber(constants.MySQL)
85 | envLocalSource = fmt.Sprintf(`DB_URI=mysql://root:password@localhost:3306/reporting_dashboard_dev
86 | MYSQL_HOST=0.0.0.0
87 | MYSQL_PORT=%d
88 | MYSQL_DATABASE=reporting_dashboard_dev
89 | MYSQL_USER=root
90 | MYSQL_PASSWORD=password
91 | PORT=%d
92 | NODE_ENV=local
93 | ACCESS_TOKEN_SECRET=4cd7234152590dcfe77e1b6fc52e84f4d30c06fddadd0dd2fb42cbc51fa14b1bb195bbe9d72c9599ba0c6b556f9bd1607a8478be87e5a91b697c74032e0ae7af
94 | REDIS_HOST=localhost
95 | REDIS_PORT=%d`,
96 | mysqlPortNumber,
97 | backendPortNumber,
98 | redisPortNumber,
99 | )
100 |
101 | envDevSource = fmt.Sprintf(`DB_URI=mysql://root:password@db_mysql:3306/reporting_dashboard_dev
102 | MYSQL_HOST=db_mysql
103 | MYSQL_PORT=%d
104 | MYSQL_DATABASE=reporting_dashboard_dev
105 | MYSQL_USER=root
106 | MYSQL_PASSWORD=password
107 | PORT=%d
108 | ACCESS_TOKEN_SECRET=4cd7234152590dcfe77e1b6fc52e84f4d30c06fddadd0dd2fb42cbc51fa14b1bb195bbe9d72c9599ba0c6b556f9bd1607a8478be87e5a91b697c74032e0ae7af`,
109 | mysqlPortNumber,
110 | backendPortNumber,
111 | )
112 |
113 | envDockerSource = fmt.Sprintf(`DB_URI=mysql://root:password@%s_db:3306/reporting_dashboard_dev
114 | MYSQL_HOST=%s_db
115 | MYSQL_DATABASE=reporting_dashboard_dev
116 | MYSQL_USER=reporting_dashboard_role
117 | MYSQL_PASSWORD=password
118 | MYSQL_ROOT_PASSWORD=password
119 | MYSQL_PORT=%d
120 | PORT=%d
121 | NODE_ENV=local
122 | ENVIRONMENT_NAME=docker
123 | ACCESS_TOKEN_SECRET=4cd7234152590dcfe77e1b6fc52e84f4d30c06fddadd0dd2fb42cbc51fa14b1bb195bbe9d72c9599ba0c6b556f9bd1607a8478be87e5a91b697c74032e0ae7af
124 | REDIS_HOST=redis
125 | REDIS_PORT=%d
126 | APP_NAME=app`,
127 | snakeCaseDirName,
128 | snakeCaseDirName,
129 | mysqlPortNumber,
130 | backendPortNumber,
131 | redisPortNumber,
132 | )
133 |
134 | envFileSources = []string{envLocalSource, envDevSource, envDockerSource}
135 |
136 | default:
137 | return fmt.Errorf("Selected stack is invalid")
138 | }
139 |
140 | for idx, envFile := range envFiles {
141 | envFile = fmt.Sprintf("%s/%s", utils.CurrentDirectory(), envFile)
142 | err := utils.WriteToFile(envFile, envFileSources[idx])
143 | errorhandler.CheckNilErr(err)
144 | }
145 |
146 | return nil
147 | }
148 |
149 | func UpdateEnvDockerFileForDefaultDBInTemplate(stack, dirName string) error {
150 | snakeCaseDirName := strcase.ToSnake(dirName)
151 | var envDockerSource string
152 | backendPortNumber := utils.GetPortNumber(constants.BackendPortNumber)
153 | redisPortNumber := utils.GetPortNumber(constants.RedisPortNumber)
154 | switch stack {
155 | case constants.NodeExpressGraphqlTemplate:
156 | postgresPortNumber := utils.GetDatabasePortNumber(constants.PostgreSQL)
157 | envDockerSource = fmt.Sprintf(`DB_URI=postgres://reporting_dashboard_role:reportingdashboard123@%s_db:5432/reporting_dashboard_dev
158 | POSTGRES_HOST=%s_db
159 | POSTGRES_DB=reporting_dashboard_dev
160 | POSTGRES_USER=reporting_dashboard_role
161 | POSTGRES_PASSWORD=reportingdashboard123
162 | POSTGRES_PORT=%d
163 | ACCESS_TOKEN_SECRET=4cd7234152590dcfe77e1b6fc52e84f4d30c06fddadd0dd2fb42cbc51fa14b1bb195bbe9d72c9599ba0c6b556f9bd1607a8478be87e5a91b697c74032e0ae7af
164 | PORT=%d
165 | NODE_ENV=production
166 | ENVIRONMENT_NAME=docker
167 | REDIS_DOMAIN=redis
168 | REDIS_PORT=%d`,
169 | snakeCaseDirName,
170 | snakeCaseDirName,
171 | postgresPortNumber,
172 | backendPortNumber,
173 | redisPortNumber,
174 | )
175 |
176 | case constants.NodeHapiTemplate:
177 | mysqlPortNumber := utils.GetDatabasePortNumber(constants.MySQL)
178 | envDockerSource = fmt.Sprintf(`NAME=Node Template (DEV)
179 | NODE_ENV=production
180 | ENVIRONMENT_NAME=docker
181 | PORT=%d
182 | DB_URI=mysql://root:password@%s_db:3306/temp_dev
183 | MYSQL_HOST=%s_db
184 | MYSQL_DATABASE=temp_dev
185 | MYSQL_USER=def_user
186 | MYSQL_PASSWORD=password
187 | MYSQL_ROOT_PASSWORD=password
188 | MYSQL_PORT=%d
189 | REDIS_HOST=redis
190 | REDIS_PORT=%d`,
191 | backendPortNumber,
192 | snakeCaseDirName,
193 | snakeCaseDirName,
194 | mysqlPortNumber,
195 | redisPortNumber,
196 | )
197 | default:
198 | return fmt.Errorf("Selected stack is invalid.")
199 | }
200 | file := fmt.Sprintf("%s/%s/%s", utils.CurrentDirectory(),
201 | dirName, constants.DockerEnvFile,
202 | )
203 | err := utils.WriteToFile(file, envDockerSource)
204 | errorhandler.CheckNilErr(err)
205 |
206 | return nil
207 | }
208 |
--------------------------------------------------------------------------------
/internal/utils/stack.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/wednesday-solutions/picky/internal/constants"
7 | "github.com/wednesday-solutions/picky/internal/errorhandler"
8 | )
9 |
10 | // CreateStackDirectory will create a a directory based on the user input, stack and the database selected.
11 | func CreateStackDirectory(dirName, stack, database string) string {
12 | switch stack {
13 | case constants.NodeHapiTemplate:
14 | if database == constants.PostgreSQL {
15 | dirName = fmt.Sprintf("%s-%s", dirName, constants.NodeHapiPgTemplate)
16 | } else if database == constants.MySQL {
17 | dirName = fmt.Sprintf("%s-%s", dirName, constants.NodeHapiMySqlTemplate)
18 | }
19 | case constants.NodeExpressGraphqlTemplate:
20 | if database == constants.PostgreSQL {
21 | dirName = fmt.Sprintf("%s-%s", dirName, constants.NodeGraphqlPgTemplate)
22 | } else if database == constants.MySQL {
23 | dirName = fmt.Sprintf("%s-%s", dirName, constants.NodeGraphqlMySqlTemplate)
24 | }
25 | case constants.NodeExpressTemplate:
26 | if database == constants.MongoDB {
27 | dirName = fmt.Sprintf("%s-%s", dirName, constants.NodeExpressMongoTemplate)
28 | }
29 | case constants.GolangEchoTemplate:
30 | if database == constants.PostgreSQL {
31 | dirName = fmt.Sprintf("%s-%s", dirName, constants.GolangPgTemplate)
32 | } else if database == constants.MySQL {
33 | dirName = fmt.Sprintf("%s-%s", dirName, constants.GolangMySqlTemplate)
34 | }
35 | case constants.ReactJS:
36 | dirName = fmt.Sprintf("%s-%s", dirName, constants.ReactTemplate)
37 | case constants.NextJS:
38 | dirName = fmt.Sprintf("%s-%s", dirName, constants.NextTemplate)
39 | case constants.ReactGraphqlTS:
40 | dirName = fmt.Sprintf("%s-%s", dirName, constants.ReactGraphqlTemplate)
41 | case constants.ReactNative:
42 | dirName = fmt.Sprintf("%s-%s", dirName, constants.ReactNativeTemplate)
43 | case constants.Android:
44 | dirName = fmt.Sprintf("%s-%s", dirName, constants.AndroidTemplate)
45 | case constants.IOS:
46 | dirName = fmt.Sprintf("%s-%s", dirName, constants.IOSTemplate)
47 | case constants.Flutter:
48 | dirName = fmt.Sprintf("%s-%s", dirName, constants.FlutterTemplate)
49 | }
50 | return dirName
51 | }
52 |
53 | type stackDetails struct {
54 | Name string
55 | Language string
56 | Framework string
57 | Type string
58 | Databases string
59 | }
60 |
61 | // GetStackDetails returns an array of StackDetails for showing details
62 | // when user selects stacks prompt.
63 | func GetStackDetails(service string) []stackDetails {
64 | var stacksDetails []stackDetails
65 | switch service {
66 | case constants.Backend:
67 | nodeHapi := stackDetails{
68 | Name: constants.NodeHapiTemplate,
69 | Language: "JavaScript",
70 | Framework: "Node JS & Hapi",
71 | Type: "REST API",
72 | Databases: fmt.Sprintf("%s & %s", constants.PostgreSQL, constants.MySQL),
73 | }
74 | nodeGraphql := stackDetails{
75 | Name: constants.NodeExpressGraphqlTemplate,
76 | Language: "JavaScript",
77 | Framework: "Node JS & Express",
78 | Type: "GraphQL API",
79 | Databases: fmt.Sprintf("%s & %s", constants.PostgreSQL, constants.MySQL),
80 | }
81 | // nodeExpress := stackDetails{
82 | // Name: constants.NodeExpressTemplate,
83 | // Language: "JavaScript",
84 | // Framework: "Node JS & Express",
85 | // Type: "REST API",
86 | // Databases: constants.MongoDB,
87 | // }
88 | // golangGraphql := stackDetails{
89 | // Name: constants.GolangEchoTemplate,
90 | // Language: "Golang",
91 | // Framework: "Echo",
92 | // Type: "GraphQL API",
93 | // Databases: fmt.Sprintf("%s & %s", constants.PostgreSQL, constants.MySQL),
94 | // }
95 | _, _, mobileStackExist := IsBackendWebAndMobileExist()
96 | if mobileStackExist {
97 | stacksDetails = []stackDetails{
98 | nodeHapi,
99 | // nodeExpress,
100 | }
101 | } else {
102 | stacksDetails = []stackDetails{
103 | nodeHapi,
104 | nodeGraphql,
105 | // nodeExpress,
106 | // golangGraphql,
107 | }
108 | }
109 | case constants.Web:
110 | reactGraphqlTs := stackDetails{
111 | Name: constants.ReactGraphqlTS,
112 | Language: "TypeScript",
113 | Framework: "React",
114 | Type: "GraphQL API",
115 | }
116 | reactJs := stackDetails{
117 | Name: constants.ReactJS,
118 | Language: "JavaScript",
119 | Framework: "React",
120 | Type: "REST API",
121 | }
122 | nextJs := stackDetails{
123 | Name: constants.NextJS,
124 | Language: "JavaScript",
125 | Framework: "Next",
126 | Type: "REST API",
127 | }
128 | stacksDetails = []stackDetails{reactJs, nextJs, reactGraphqlTs}
129 |
130 | case constants.Mobile:
131 | stacksDetails = []stackDetails{
132 | {
133 | Name: constants.ReactNative,
134 | Language: "JavaScript",
135 | Framework: "React Native",
136 | Type: "REST API",
137 | },
138 | {
139 | Name: constants.Android,
140 | Language: "Kotlin",
141 | Framework: "-",
142 | Type: "REST API",
143 | },
144 | {
145 | Name: constants.IOS,
146 | Language: "Swift",
147 | Framework: "-",
148 | Type: "REST API",
149 | },
150 | {
151 | Name: constants.Flutter,
152 | Language: "Dart",
153 | Framework: "Flutter",
154 | Type: "REST API",
155 | },
156 | }
157 | }
158 | return stacksDetails
159 | }
160 |
161 | // GetExistingInfraStacks fetch stack files inside the stacks directory.
162 | func GetExistingInfraStacks() []string {
163 | path := fmt.Sprintf("%s/%s", CurrentDirectory(), constants.Stacks)
164 | status, _ := IsExists(path)
165 | if !status {
166 | return []string{}
167 | }
168 | files, err := ReadAllContents(path)
169 | errorhandler.CheckNilErr(err)
170 | return files
171 | }
172 |
173 | // GetSuffixOfStack returns suffix name for the given stack and database.
174 | func GetSuffixOfStack(stack, database string) string {
175 | var suffix string
176 | switch stack {
177 | case constants.ReactJS:
178 | suffix = constants.ReactTemplate
179 | case constants.NextJS:
180 | suffix = constants.NextTemplate
181 | case constants.ReactGraphqlTS:
182 | suffix = constants.ReactGraphqlTemplate
183 | case constants.NodeHapiTemplate:
184 | if database == constants.PostgreSQL {
185 | suffix = constants.NodeHapiPgTemplate
186 | } else if database == constants.MySQL {
187 | suffix = constants.NodeHapiMySqlTemplate
188 | }
189 | case constants.NodeExpressGraphqlTemplate:
190 | if database == constants.PostgreSQL {
191 | suffix = constants.NodeGraphqlPgTemplate
192 | } else if database == constants.MySQL {
193 | suffix = constants.NodeGraphqlMySqlTemplate
194 | }
195 | case constants.NodeExpressTemplate:
196 | if database == constants.MongoDB {
197 | suffix = constants.NodeExpressMongoTemplate
198 | }
199 | case constants.GolangEchoTemplate:
200 | if database == constants.PostgreSQL {
201 | suffix = constants.GolangPgTemplate
202 | } else if database == constants.MySQL {
203 | suffix = constants.GolangMySqlTemplate
204 | }
205 | case constants.ReactNative:
206 | suffix = constants.ReactNativeTemplate
207 | case constants.Android:
208 | suffix = constants.AndroidTemplate
209 | case constants.IOS:
210 | suffix = constants.IOSTemplate
211 | case constants.Flutter:
212 | suffix = constants.FlutterTemplate
213 | }
214 | return suffix
215 | }
216 |
217 | func CheckStacksExist(stacks []string) error {
218 | var stackExist bool
219 | if len(stacks) == 0 {
220 | return fmt.Errorf("No stacks exist.\n")
221 | }
222 | _, _, directories := GetExistingStacksDatabasesAndDirectories()
223 | for _, stack := range stacks {
224 | for _, dir := range directories {
225 | if stack == dir {
226 | stackExist = true
227 | }
228 | }
229 | if !stackExist {
230 | return fmt.Errorf("Entered stack '%s' not exists.\n", stack)
231 | }
232 | }
233 | return nil
234 | }
235 |
236 | func IsWebStack(stack string) (string, bool) {
237 | _, _, _, lastSuffix := SplitStackDirectoryName(stack)
238 | if lastSuffix == constants.Web {
239 | return stack, true
240 | }
241 | return "", false
242 | }
243 |
244 | func IsMobileStack(stack string) (string, bool) {
245 | _, _, _, lastSuffix := SplitStackDirectoryName(stack)
246 | if lastSuffix == constants.Mobile {
247 | return stack, true
248 | }
249 | return "", false
250 | }
251 |
252 | func IsBackendStack(stack string) (string, bool) {
253 | _, _, _, lastSuffix := SplitStackDirectoryName(stack)
254 | if lastSuffix == constants.Pg || lastSuffix == constants.Mysql {
255 | return stack, true
256 | }
257 | return "", false
258 | }
259 |
--------------------------------------------------------------------------------
/internal/constants/constants.go:
--------------------------------------------------------------------------------
1 | package constants
2 |
3 | import "github.com/spaceweasel/promptui"
4 |
5 | var Repos = func() map[string]string {
6 | return map[string]string{
7 | ReactJS: "https://github.com/wednesday-solutions/react-template",
8 | NextJS: "https://github.com/wednesday-solutions/nextjs-template",
9 | ReactGraphqlTS: "https://github.com/wednesday-solutions/react-graphql-ts-template",
10 | NodeHapiTemplate: "https://github.com/wednesday-solutions/nodejs-hapi-template",
11 | NodeExpressGraphqlTemplate: "https://github.com/wednesday-solutions/node-express-graphql-template",
12 | NodeExpressTemplate: "https://github.com/wednesday-solutions/node-mongo-express",
13 | GolangPostgreSQLTemplate: "https://github.com/wednesday-solutions/go-template",
14 | GolangMySQLTemplate: "https://github.com/wednesday-solutions/go-template-mysql",
15 | ReactNative: "https://github.com/wednesday-solutions/react-native-template",
16 | Android: "https://github.com/wednesday-solutions/android-template",
17 | IOS: "https://github.com/wednesday-solutions/ios-template",
18 | Flutter: "https://github.com/wednesday-solutions/flutter_template",
19 | }
20 | }
21 |
22 | // CLI options
23 | const (
24 | Picky = "picky"
25 | Service = "service"
26 | Test = "test"
27 | Init = "init"
28 | Create = "create"
29 | Infra = "infra"
30 | )
31 |
32 | // Home options
33 | const (
34 | InitService = "Init Service"
35 | DockerCompose = "Docker Compose"
36 | CICD = "CI/CD"
37 | SetupInfra = "Setup Infra"
38 | Deploy = "Deploy"
39 | RemoveDeploy = "Remove Deploy"
40 | GitInit = "Git Init"
41 | Exit = "Exit"
42 | )
43 |
44 | // Services
45 | const (
46 | Web = "web"
47 | Mobile = "mobile"
48 | Backend = "backend"
49 | )
50 |
51 | // Frontend stacks
52 | const (
53 | ReactJS = "React JS"
54 | NextJS = "Next JS"
55 | ReactGraphqlTS = "React GraphQL TS"
56 | )
57 |
58 | // Backend stacks
59 | const (
60 | NodeHapiTemplate = "Node (Hapi- REST API)"
61 | NodeExpressGraphqlTemplate = "Node (Express- GraphQL API)"
62 | NodeExpressTemplate = "Node (Express- REST API)"
63 | GolangEchoTemplate = "Golang (Echo- GraphQL API)"
64 | )
65 |
66 | // Mobile stacks
67 | const (
68 | ReactNative = "React Native"
69 | Android = "Android"
70 | IOS = "IOS"
71 | Flutter = "Flutter"
72 | )
73 |
74 | // Databases
75 | const (
76 | PostgreSQL = "PostgreSQL"
77 | MySQL = "MySQL"
78 | MongoDB = "MongoDB"
79 | )
80 |
81 | // Docker compose actions
82 | const (
83 | CreateDockerCompose = "Create Docker Compose"
84 | RunDockerCompose = "Run Docker Compose"
85 | )
86 |
87 | // CICD actions
88 | const (
89 | CreateCI = "Create CI"
90 | CreateCD = "Create CD"
91 | )
92 |
93 | // Infra Files
94 | const (
95 | PackageDotJsonFile = "package.json"
96 | SstConfigFile = "sst.config.js"
97 | EnvFile = ".env"
98 | EnvDevFile = ".env.development"
99 | ParseSstOutputs = "parseSstOutputs.js"
100 | )
101 |
102 | // Cloud Providers
103 | const (
104 | AWS = "AWS"
105 | )
106 |
107 | // helpers
108 | const (
109 | Yes = "Yes"
110 | No = "No"
111 | Stack = "stack"
112 | Stacks = "stacks"
113 | DB = "db"
114 | Pg = "pg"
115 | Mysql = "mysql"
116 | Postgresql = "postgresql"
117 | Mongodb = "mongodb"
118 | Mongo = "mongo"
119 | Redis = "redis"
120 | Postgres = "postgres"
121 | Frontend = "frontend"
122 | Database = "database"
123 | ProjectName = "projectName"
124 | PgNative = "pg-native"
125 | Mysql2 = "mysql2"
126 | WebStatus = "webStatus"
127 | MobileStatus = "mobileStatus"
128 | BackendStatus = "backendStatus"
129 | GolangPostgreSQLTemplate = "Golang-PostgreSQL"
130 | GolangMySQLTemplate = "Golang-MySQL"
131 | WebDirName = "webDirName"
132 | MobileDirName = "mobileDirName"
133 | BackendDirName = "backendDirName"
134 | SizeOfPromptSelect = 8
135 | All = "All"
136 | SstConfigStack = "sstConfigStack"
137 | ExistingDirectories = "existingDirectories"
138 | Yarn = "yarn"
139 | Npm = "npm"
140 | WebDirectories = "webDirectories"
141 | BackendPgDirectories = "backendPgDirectories"
142 | BackendMysqlDirectories = "backendMysqlDirectories"
143 | Zero = 0
144 | One = 1
145 | Two = 2
146 | Three = 3
147 | BackendSuffixSize = 3
148 | WebSuffixSize = 3
149 | MobileSuffixSize = 2
150 | DotSstDirectory = ".sst"
151 | PostgresqlData = "postgresql/data"
152 | GithubWorkflowsDir = ".github/workflows"
153 | DotGitFolder = ".git"
154 | CDFilePathURL = "/.github/workflows/cd.yml"
155 | GitHub = "GitHub"
156 | Graphql = "graphql"
157 | Git = "git"
158 | DotGitIgnore = ".gitignore"
159 | NodeModules = "node_modules"
160 | CloudProvider = "cloudprovider"
161 | Directory = "directory"
162 | DockerComposeFlag = "dockercompose"
163 | CIFlag = "ci"
164 | CDFlag = "cd"
165 | Platform = "platform"
166 | Github = "github"
167 | DotSst = ".sst"
168 | OutputsJson = "outputs.json"
169 | DBUsername = "username"
170 | DeployNow = "Deploy Now"
171 | ChangeStacks = "Change Stacks"
172 | GoBack = "Go Back"
173 | )
174 |
175 | // Docker related files
176 | const (
177 | DockerComposeFile = "docker-compose.yml"
178 | DockerFile = "Dockerfile"
179 | DockerEnvFile = ".env.docker"
180 | DockerIgnoreFile = ".dockerignore"
181 | )
182 |
183 | // Template directory name
184 | const (
185 | NodeHapiPgTemplate = "node-hapi-pg"
186 | NodeHapiMySqlTemplate = "node-hapi-mysql"
187 | NodeGraphqlPgTemplate = "node-graphql-pg"
188 | NodeGraphqlMySqlTemplate = "node-graphql-mysql"
189 | NodeExpressMongoTemplate = "node-express-mongo"
190 | GolangPgTemplate = "golang-graphql-pg"
191 | GolangMySqlTemplate = "golang-graphql-mysql"
192 | ReactTemplate = "react-js-web"
193 | NextTemplate = "next-js-web"
194 | ReactGraphqlTemplate = "react-graphql-web"
195 | ReactNativeTemplate = "reactnative-mobile"
196 | AndroidTemplate = "android-mobile"
197 | IOSTemplate = "ios-mobile"
198 | FlutterTemplate = "flutter-mobile"
199 | )
200 |
201 | // Environments
202 | const (
203 | Environment = "environment"
204 | Dev = "dev"
205 | QA = "qa"
206 | Prod = "prod"
207 | Development = "development"
208 | Production = "production"
209 | Develop = "develop"
210 | )
211 |
212 | // UI icons
213 | var (
214 | IconChoose = promptui.Styler(promptui.FGBold)("▸")
215 | IconSelect = promptui.Styler(promptui.FGGreen)("✔")
216 | IconWarn = promptui.Styler(promptui.FGYellow)("⚠")
217 | IconWrong = promptui.Styler(promptui.FGRed)("✗")
218 | IconQuestion = promptui.Styler(promptui.FGMagenta)("?")
219 | )
220 |
221 | // Stack short name for flags
222 | const (
223 | ReactjsLower = "reactjs"
224 | NextjsLower = "nextjs"
225 | ReactGraphqlLower = "reactgraphql"
226 | ReactNativeLower = "reactnative"
227 | AndroidLower = "android"
228 | IOSLower = "ios"
229 | FlutterLower = "flutter"
230 | NodeHapi = "nodehapi"
231 | NodeGraphql = "nodegraphql"
232 | NodeExpress = "nodeexpress"
233 | Golang = "golang"
234 | )
235 |
236 | // GitHub secrets
237 | const (
238 | DistributionId = "DISTRIBUTION_ID"
239 | AwsRegion = "AWS_REGION"
240 | AwsAccessKeyId = "AWS_ACCESS_KEY_ID"
241 | AwsSecretAccessKey = "AWS_SECRET_ACCESS_KEY"
242 | AwsEcrRepository = "AWS_ECR_REPOSITORY"
243 | )
244 |
245 | // Port numbers for infra setup
246 | const (
247 | WebPortNumber = 3000
248 | BackendPortNumber = 9000
249 | PostgresPortNumber = 5432
250 | MysqlPortNumber = 3306
251 | RedisPortNumber = 6379
252 | )
253 |
254 | // Env values
255 | const (
256 | PostgresUser = "POSTGRES_USER"
257 | PostgresHost = "POSTGRES_HOST"
258 | PostgresDB = "POSTGRES_DB"
259 | MysqlUser = "MYSQL_USER"
260 | MysqlHost = "MYSQL_HOST"
261 | MysqlDatabase = "MYSQL_DATABASE"
262 | RedisHost = "REDIS_HOST"
263 | RedisPort = "REDIS_PORT"
264 | )
265 |
266 | // Port number names
267 | const (
268 | BackendPort = "PORT"
269 | PostgresPort = "POSTGRES_PORT"
270 | MysqlPort = "MYSQL_PORT"
271 | )
272 |
--------------------------------------------------------------------------------
/pickyhelpers/convert_queries.go:
--------------------------------------------------------------------------------
1 | package pickyhelpers
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/wednesday-solutions/picky/internal/constants"
7 | "github.com/wednesday-solutions/picky/internal/errorhandler"
8 | "github.com/wednesday-solutions/picky/internal/utils"
9 | )
10 |
11 | func ConvertQueries(stack, dirName string) error {
12 |
13 | var queries []string
14 | var files []string
15 |
16 | switch stack {
17 | case constants.NodeHapiTemplate:
18 |
19 | oauthClients := `create sequence oauth_clients_seq;
20 |
21 | create type grant_type_enum as ENUM('CLIENT_CREDENTIALS');
22 |
23 | create table oauth_clients (
24 | id INT NOT NULL PRIMARY KEY DEFAULT NEXTVAL ('oauth_clients_seq'),
25 | client_id VARCHAR(320) NOT NULL,
26 | client_secret VARCHAR(36) NOT NULL,
27 | grant_type grant_type_enum,
28 | created_at TIMESTAMP(0) DEFAULT CURRENT_TIMESTAMP NOT NULL,
29 | updated_at TIMESTAMP(0) NULL,
30 | CONSTRAINT oauth_clients_client_id UNIQUE (client_id)
31 | );
32 |
33 | CREATE INDEX oauth_clients_client_id_idx ON oauth_clients(client_id);
34 | CREATE INDEX oauth_clients_client_secret_idx ON oauth_clients(client_secret);`
35 |
36 | users := `CREATE SEQUENCE users_seq;
37 |
38 | CREATE TABLE users
39 | (
40 | id INT NOT NULL DEFAULT NEXTVAL ('users_seq') PRIMARY KEY,
41 | oauth_client_id INT NOT NULL,
42 | first_name VARCHAR (32) NOT NULL,
43 | last_name VARCHAR(32) NOT NULL,
44 | email VARCHAR(32) NOT NULL,
45 | created_at TIMESTAMP(0) DEFAULT CURRENT_TIMESTAMP NOT NULL,
46 | CONSTRAINT users_oauth_clients_id_fk FOREIGN KEY (oauth_client_id)
47 | REFERENCES oauth_clients (id) ON UPDATE CASCADE
48 | );`
49 |
50 | oauthAccessTokens := `create sequence oauth_access_tokens_seq;
51 |
52 | CREATE OR REPLACE FUNCTION public.is_json_valid(json_data json)
53 | RETURNS boolean
54 | LANGUAGE plpgsql
55 | AS $function$
56 | DECLARE
57 | json_type TEXT;
58 | BEGIN
59 | json_type := json_typeof(json_data);
60 | IF json_type = 'array' AND json_array_length(json_data) > 0 THEN
61 | RETURN TRUE;
62 | ELSIF json_type = 'object' AND json_data::text <> '{}'::TEXT THEN
63 | RETURN TRUE;
64 | END IF;
65 | RETURN FALSE;
66 | END;
67 | $function$;
68 |
69 | create table oauth_access_tokens (
70 | id INT NOT NULL PRIMARY KEY DEFAULT NEXTVAL ('oauth_access_tokens_seq'),
71 | oauth_client_id INT NOT NULL,
72 | access_token VARCHAR(64) NOT NULL,
73 | expires_in INTEGER CHECK (expires_in > 0) NOT NULL,
74 | expires_on TIMESTAMP(0) NOT NULL,
75 | metadata JSON NOT NULL,
76 | created_at TIMESTAMP(0) DEFAULT CURRENT_TIMESTAMP NOT NULL,
77 | updated_at TIMESTAMP(0) NULL,
78 | CONSTRAINT oauth_access_tokens_access_token_uindex UNIQUE (access_token),
79 | CONSTRAINT oauth_access_tokens_oauth_clients_id_fk FOREIGN KEY (oauth_client_id) REFERENCES oauth_clients (id) ON UPDATE CASCADE,
80 | CONSTRAINT oauth_access_tokens_check_metadata CHECK(is_json_valid(metadata))
81 | );`
82 |
83 | oauthClientResources := `create sequence oauth_client_resources_seq;
84 |
85 | create table oauth_client_resources (
86 | id INT NOT NULL PRIMARY KEY DEFAULT NEXTVAL ('oauth_client_resources_seq'),
87 | oauth_client_id INT NOT NULL,
88 | resource_type VARCHAR(36) NOT NULL,
89 | resource_id VARCHAR(36) NOT NULL,
90 | created_at TIMESTAMP(0) DEFAULT CURRENT_TIMESTAMP NOT NULL,
91 | updated_at TIMESTAMP(0) NULL,
92 | CONSTRAINT oauth_client_resources_oauth_client_id_resource_uindex UNIQUE (
93 | oauth_client_id, resource_type, resource_id
94 | ),
95 | CONSTRAINT oauth_client_resources_oauth_clients_id_fk FOREIGN KEY (oauth_client_id) REFERENCES oauth_clients (id) ON UPDATE CASCADE
96 | );
97 |
98 | CREATE INDEX oauth_client_resources_resource_type ON oauth_client_resources(resource_type);
99 | CREATE INDEX oauth_client_resources_resource_id ON oauth_client_resources(resource_id);`
100 |
101 | oauthClientScopes := `create sequence oauth_client_scopes_seq;
102 |
103 | create table oauth_client_scopes (
104 | id INT NOT NULL DEFAULT NEXTVAL ('oauth_client_scopes_seq') PRIMARY KEY,
105 | oauth_client_id INT NOT NULL,
106 | scope VARCHAR (36) NOT NULL,
107 | created_at TIMESTAMP(0) DEFAULT CURRENT_TIMESTAMP NOT NULL,
108 | updated_at TIMESTAMP(0) NULL,
109 | constraint oauth_client_scopes_uindex UNIQUE (oauth_client_id),
110 | constraint oauth_client_scopes_oauth_clients_id_fk FOREIGN KEY (oauth_client_id) REFERENCES oauth_clients (id) ON UPDATE CASCADE
111 | );`
112 |
113 | queries = []string{oauthClients,
114 | users,
115 | oauthAccessTokens,
116 | oauthClientResources,
117 | oauthClientScopes,
118 | }
119 |
120 | files = []string{"01_oauth_clients.sql",
121 | "02_users.sql",
122 | "03_oauth_access_tokens.sql",
123 | "04_oauth_client_resources.sql",
124 | "05_oauth_client_scopes.sql",
125 | }
126 |
127 | case constants.NodeExpressGraphqlTemplate:
128 |
129 | products := `CREATE TABLE products (
130 | id INT AUTO_INCREMENT NOT NULL PRIMARY KEY,
131 | name VARCHAR(255) NOT NULL,
132 | category VARCHAR(255) NOT NULL,
133 | amount BIGINT NOT NULL,
134 | created_at DATETIME DEFAULT NOW(),
135 | updated_at DATETIME NULL on UPDATE NOW(),
136 | deleted_at DATETIME,
137 | INDEX(name),
138 | INDEX(category)
139 | );`
140 |
141 | addresses := `CREATE TABLE addresses (
142 | id INT AUTO_INCREMENT PRIMARY KEY,
143 | address_1 VARCHAR(255) NOT NULL,
144 | address_2 VARCHAR(255) NOT NULL,
145 | city VARCHAR(255) NOT NULL,
146 | country VARCHAR(255) NOT NULL,
147 | latitude FLOAT NOT NULL,
148 | longitude FLOAT NOT NULL,
149 | created_at DATETIME DEFAULT NOW(),
150 | updated_at DATETIME NULL on UPDATE NOW(),
151 | deleted_at DATETIME,
152 | INDEX(latitude),
153 | INDEX(longitude)
154 | );`
155 |
156 | stores := `CREATE TABLE stores (
157 | id INT AUTO_INCREMENT PRIMARY KEY,
158 | name VARCHAR(255) NOT NULL,
159 | address_id INT NOT NULL,
160 | created_at DATETIME DEFAULT NOW(),
161 | updated_at DATETIME NULL on UPDATE NOW(),
162 | deleted_at DATETIME,
163 | CONSTRAINT stores_address_id FOREIGN KEY (address_id) REFERENCES addresses (id),
164 | INDEX(name)
165 | );`
166 |
167 | suppliers := `CREATE TABLE suppliers (
168 | id INT AUTO_INCREMENT PRIMARY KEY,
169 | name VARCHAR(255) NOT NULL,
170 | address_id INT NOT NULL,
171 | created_at DATETIME DEFAULT NOW(),
172 | updated_at DATETIME NULL on UPDATE NOW(),
173 | deleted_at DATETIME,
174 | CONSTRAINT suppliers_address_id FOREIGN KEY (address_id) REFERENCES addresses (id),
175 | INDEX(name)
176 | );`
177 |
178 | supplierProducts := `CREATE TABLE supplier_products (
179 | id INT AUTO_INCREMENT PRIMARY KEY,
180 | product_id INT NOT NULL,
181 | supplier_id INT NOT NULL,
182 | created_at DATETIME DEFAULT NOW(),
183 | updated_at DATETIME NULL on UPDATE NOW(),
184 | deleted_at DATETIME,
185 | CONSTRAINT suppliers_product_products_id FOREIGN KEY (product_id) REFERENCES products (id),
186 | CONSTRAINT suppliers_product_supplier_id FOREIGN KEY (supplier_id) REFERENCES suppliers (id)
187 | );`
188 |
189 | storeProducts := `CREATE TABLE store_products (
190 | id INT AUTO_INCREMENT PRIMARY KEY,
191 | product_id INT NOT NULL,
192 | store_id INT NOT NULL,
193 | created_at DATETIME DEFAULT NOW(),
194 | updated_at DATETIME NULL on UPDATE NOW(),
195 | deleted_at DATETIME,
196 | CONSTRAINT store_products_product_id FOREIGN KEY (product_id) REFERENCES products (id),
197 | CONSTRAINT store_products_store_id FOREIGN KEY (store_id) REFERENCES stores (id)
198 | );`
199 |
200 | purchasedProducts := `CREATE TABLE purchased_products (
201 | id INT AUTO_INCREMENT PRIMARY KEY,
202 | product_id INT NOT NULL,
203 | price INT NOT NULL,
204 | discount INT NOT NULL,
205 | store_id INT NOT NULL,
206 | delivery_date DATETIME NOT NULL,
207 | created_at DATETIME DEFAULT NOW(),
208 | updated_at DATETIME NULL on UPDATE NOW(),
209 | deleted_at DATETIME,
210 | CONSTRAINT purchased_products_product_id FOREIGN KEY (product_id) REFERENCES products (id),
211 | CONSTRAINT purchased_products_store_id FOREIGN KEY (store_id) REFERENCES stores (id),
212 | INDEX(delivery_date),
213 | INDEX(store_id)
214 | );`
215 |
216 | users := `CREATE TABLE users (
217 | id INT AUTO_INCREMENT PRIMARY KEY,
218 | first_name VARCHAR(255) NOT NULL,
219 | last_name VARCHAR(255) NOT NULL,
220 | email VARCHAR(255) NOT NULL UNIQUE,
221 | password VARCHAR(255) NOT NULL,
222 | created_at DATETIME DEFAULT NOW(),
223 | updated_at DATETIME NULL on UPDATE NOW(),
224 | deleted_at DATETIME,
225 | INDEX(email)
226 | );`
227 |
228 | queries = []string{products,
229 | addresses,
230 | stores,
231 | suppliers,
232 | supplierProducts,
233 | storeProducts,
234 | purchasedProducts,
235 | users,
236 | }
237 |
238 | files = []string{"01_products.sql",
239 | "02_addresses.sql",
240 | "03_stores.sql",
241 | "04_supplier.sql",
242 | "05_supplier_products.sql",
243 | "06_store_products.sql",
244 | "07_purchased_products.sql",
245 | "08_users.sql",
246 | }
247 |
248 | default:
249 | return fmt.Errorf("Selected stack is invalid")
250 | }
251 |
252 | for idx, file := range files {
253 | err := utils.WriteToFile(
254 | fmt.Sprintf("%s/%s/%s/%s", utils.CurrentDirectory(),
255 | dirName, "resources/v1", file),
256 | queries[idx],
257 | )
258 | errorhandler.CheckNilErr(err)
259 | }
260 |
261 | return nil
262 | }
263 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
Service Picker
9 |
10 |
11 |
12 |
13 | A CLI tool that helps to setup full-stack javascript applications without having to touch any code. You'll be able to pick templates and databases of your choice, integrate it, set up automation pipelines and create infrastructure in AWS with ease.
14 | It contains a number of Wednesday Solutions's open source projects, connected and working together. Pick whatever you need and build your own ecosystem.
15 |
16 |
17 | ---
18 |
19 |
20 |
21 | Expert teams of digital product strategists, developers, and designers.
22 |
23 |
24 |
25 |
33 |
34 | ---
35 |
36 |
We're always looking for people who value their work, so come and join us. We are hiring!
37 |
38 |
39 |
40 |
41 |
42 | ## Table of contents
43 |
44 | - [Table of contents](#table-of-contents)
45 | - [Overview](#overview)
46 | - [Tech Stacks](#tech-stacks)
47 | - [Setup and Configuration.](#setup-and-configuration)
48 | - [Pre-requisites](#pre-requisites)
49 | - [Installation](#installation)
50 | - [Creating a Project](#creating-a-project)
51 | - [User Guide](#user-guide)
52 | - [Project Structure](#project-structure)
53 | - [Feedback](#feedback)
54 | - [License](#license)
55 | - [Future Plans](#future-plans)
56 |
57 | ## Overview
58 |
59 | Once business gives the sign-off and it's time for execution, the question thats most frequently asked is "What's the tech stack that we should use for this?"
60 |
61 | Fast-forward past the long debates where engineers are pitching their favourite languages and frameworks, we kick "git init" a bunch of repos for the frontend, infrastructure & backend. We then spend some time creating some boilerplate, or use some templates, setting up the CI/CD pipelines, IaC etc. The next thing you know, it's 6 weeks later and you're still configuring the connection between your database and your ec2 instance. The amount of business logic that you've written is minimal cause all of your team's time was spent configuring repos, environments, security groups, and other nitty-grittys.
62 |
63 | Thats where the service-picker comes in. We're working on building a cli tool that allows you to scaffold batteries included code, IaC & CI/CD pipelines, across environments and stacks, that is completely integrated with each other and ready to go.
64 |
65 | This means that setting up the infra and codebase for your next project which needs a React web app, with a node.js backend and a postgreSQL db is as simple as a hitting a few arrow buttons, and enter a couple of times.
66 |
67 | Service picker works on macOS, Windows and Linux.
68 | If something doesn't work, please file an [issue](https://github.com/wednesday-solutions/service-picker/issues).
69 | If you have questions, suggestions or need help, please ask in [GitHub Discussions](https://github.com/wednesday-solutions/service-picker/discussions)
70 |
71 | ## Tech Stacks
72 |
73 | This tool will have support for production applications using the following tech stacks.
74 |
75 | **Web:**
76 |
77 | - [React JS](https://github.com/wednesday-solutions/react-template)
78 | - [Next JS](https://github.com/wednesday-solutions/nextjs-template)
79 |
80 | **Backend:**
81 |
82 | - [Node (Hapi - REST API)](https://github.com/wednesday-solutions/nodejs-hapi-template)
83 | - [Node (Express - GraphQL API)](https://github.com/wednesday-solutions/node-express-graphql-template)
84 |
85 | **Databases:**
86 |
87 | - MySQL
88 | - PostgreSQL
89 |
90 | **Cache:**
91 |
92 | - Redis
93 |
94 | **Infrastructure Provider:**
95 |
96 | - [AWS](https://aws.amazon.com/)
97 |
98 | ## Setup and Configuration.
99 |
100 | ### Pre-requisites
101 |
102 | - [Golang](https://go.dev/doc/install)
103 | - [Node JS](https://nodejs.org/en/download)
104 | - Package Manager([npm](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) or [yarn](https://classic.yarnpkg.com/lang/en/docs/install/#mac-stable))
105 | - [Docker](https://docs.docker.com/engine/install/) - Install and have it running in your local to docker compose applications and setup infrastructures in AWS.
106 | - [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html) - Configure to your AWS account.
107 | ```bash
108 | $ aws configure
109 | AWS Access Key ID: MYACCESSKEYID
110 | AWS Secret Access Key: MYSECRETKEY
111 | Default region name [us-west-2]: MYAWSREGION
112 | Default output format [None]:
113 | ```
114 | - Create a repository in your [AWS ECR](https://aws.amazon.com/ecr/).
115 |
116 | ```bash
117 | $ aws ecr create-repository --repository-name cdk-hnb659fds-container-assets-MYAWSACCOUNTID-MYAWSREGION
118 | ```
119 |
120 |
121 | ### Installation
122 |
123 | Using Picky is easy. First use `go install` to install the latest version of the library (`go` should be installed in your system).
124 |
125 | ```bash
126 | go install github.com/wednesday-solutions/picky@latest
127 | ```
128 |
129 | Please make sure the installation is successful by running the following command.
130 |
131 | ```bash
132 | picky -v
133 | ```
134 |
135 | ## Creating a Project
136 |
137 | To create a new project, you need to pick stacks which are mentioned in [tech stacks](#tech-stacks)
138 | To start using `picky`
139 |
140 | ```bash
141 | mkdir my-project
142 | cd my-project
143 | ```
144 |
145 | ```bash
146 | picky service
147 | ```
148 |
149 | 
150 |
151 | Use the arrow keys to navigate and pick a service you want.
152 |
153 | The complete stack initialization tutorial is given below.
154 |
155 | 
156 |
157 | You can see `picky`'s home page if you initialized atleast one stack. You can choose any option in the following.
158 |
159 |
160 |
161 | ***Tips:***
162 | - If you want to go back from the prompt, click `Ctrl + D`
163 | - If you want to exit from the prompt, click `Ctrl + C`
164 |
165 | ## User Guide
166 |
167 | | Option | Use |
168 | | ---------------- | ----------------------------------------------------------------------------------------- |
169 | | `Init Service` | Initialize a stack. |
170 | | `CI/CD` | Create CI/CD Pipeline in GitHub. |
171 | | `Docker Compose` | Create Docker Compose file for the mono-repo. It consist of all the selected stacks. |
172 | | `Setup Infra` | Setup infrastructure for initialized stacks. |
173 | | `Deploy` | Deploy the infrastructure in AWS. It can deploy Frontend, Backend or Full stack projects. |
174 | | `Remove Deploy` | Remove the deployed infrastructure. |
175 | | `Git Init` | Initialize empty git repository in the current directory. |
176 | | `Exit` | Exit from the tool. |
177 |
178 | ## Project Structure
179 |
180 | It will be like the following in the current directory.
181 |
182 | ```
183 | my-project
184 | ├── .github
185 | │ └── workflows
186 | │ ├── cd-backend-node-hapi-pg.yml
187 | │ ├── cd-frontend-next-js-web.yml
188 | │ ├── ci-backend-node-hapi-pg.yml
189 | │ └── ci-frontend-next-js-web.yml
190 | ├── .sst
191 | │ ├── artifacts
192 | │ ├── dist
193 | │ ├── types
194 | │ ├── debug.log
195 | │ └── outputs.json
196 | ├── node_modules
197 | ├── stacks
198 | │ ├── BackendNodeHapiPg.js
199 | │ └── FrontendNextJsWeb.js
200 | ├── backend-node-hapi-pg
201 | │ └── ...
202 | ├── frontend-next-js-web
203 | │ └── ...
204 | ├── .env
205 | ├── .git
206 | ├── .gitignore
207 | ├── cdk.context.json
208 | ├── docker-compose.yml
209 | ├── backend-node-hapi-outputs.json
210 | ├── frontend-next-js-web-outputs.json
211 | ├── package.json
212 | ├── parseSstOutputs.js
213 | ├── sst.config.js
214 | └── yarn.lock
215 | ```
216 |
217 | ## Feedback
218 |
219 | If you have any feedback, please reach out to us at [GitHub Discussions](https://github.com/wednesday-solutions/service-picker/discussions)
220 |
221 | ## License
222 |
223 | This project is under the [MIT License](https://github.com/wednesday-solutions/service-picker/blob/main/LICENSE).
224 |
225 | ## Future Plans
226 |
227 | Currently the service-picker is capable of setting up full-stack javascript applications. In it's end state the service picker will allow you to choose right from your cloud infra structure provider (GCP, AWS, AZURE) to different backends and databases that you'd like to use, to your caching strategy, message broker, mobile app release tooling and any other tooling choice that you must make along your product development journey.
228 |
--------------------------------------------------------------------------------
/pickyhelpers/sources/create_cd_source.go:
--------------------------------------------------------------------------------
1 | package sources
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/wednesday-solutions/picky/internal/constants"
7 | "github.com/wednesday-solutions/picky/internal/utils"
8 | )
9 |
10 | func CDBackendSource(stack, stackDir, environment string) string {
11 |
12 | userInput := utils.FindUserInputStackName(stackDir)
13 | source := fmt.Sprintf(`# CD pipeline for %s for %s branch
14 |
15 | name: CD %s - %s
16 |
17 | on:
18 | push:
19 | branches:
20 | - dev
21 | - qa
22 | - master
23 | # paths: "%s/**"
24 | workflow_dispatch:
25 |
26 | jobs:
27 | docker-build-and-push:
28 | name: Docker build image and push
29 | runs-on: ubuntu-latest
30 | defaults:
31 | run:
32 | working-directory: ./%s
33 | strategy:
34 | matrix:
35 | node-version: [16.14.x]
36 |
37 | steps:
38 | # Checkout
39 | - name: Checkout to branch
40 | uses: actions/checkout@v2
41 |
42 | - name: Use Node.js ${{ matrix.node-version }}
43 | uses: actions/setup-node@v2
44 | with:
45 | node-version: ${{ matrix.node-version }}
46 |
47 | - name: Get branch name
48 | id: vars
49 | run: echo ::set-output name=short_ref::${GITHUB_REF_NAME}
50 |
51 | - name: Set env.ENV_NAME and env.BUILD_NAME
52 | run: |
53 | if [[ ${{ steps.vars.outputs.short_ref }} == master ]]; then
54 | echo "BUILD_NAME=prod" >> "$GITHUB_ENV"
55 | else
56 | echo "ENV_NAME=.development" >> "$GITHUB_ENV"
57 | echo "BUILD_NAME=dev" >> "$GITHUB_ENV"
58 | fi
59 |
60 | # Configure AWS with credentials
61 | - name: Configure AWS Credentials
62 | uses: aws-actions/configure-aws-credentials@v1
63 | with:
64 | aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
65 | aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
66 | aws-region: ${{ secrets.AWS_REGION }}
67 |
68 | # Login to Amazon ECR
69 | - name: Login to Amazon ECR
70 | id: login-ecr
71 | uses: aws-actions/amazon-ecr-login@v1
72 |
73 | # Build, tag, and push image to Amazon ECR
74 | - name: Build, tag, and push image to ECR
75 | env:
76 | ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
77 | ECR_REPOSITORY: ${{ secrets.AWS_ECR_REPOSITORY }}
78 | AWS_REGION: ${{ secrets.AWS_REGION }}
79 | IMAGE_TAG: ${{ github.sha }}
80 | DOCKER_BUILDKIT: 1
81 | run: |
82 | docker build --no-cache -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG . --build-arg BUILD_NAME=${{ env.BUILD_NAME }} --build-arg ENVIRONMENT_NAME=${{ env.ENV_NAME }}
83 | docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
84 |
85 | # Create and configure Amazon ECS task definition
86 | - name: Render Amazon ECS task definition
87 | id: %s-container
88 | uses: aws-actions/amazon-ecs-render-task-definition@v1
89 | with:
90 | task-definition: %s/task-definition-${{ env.BUILD_NAME }}.json
91 | container-name: %s-container-${{ env.BUILD_NAME }}
92 | image: ${{ steps.login-ecr.outputs.registry }}/${{ secrets.AWS_ECR_REPOSITORY }}:${{ github.sha }}
93 |
94 | # Deploy to Amazon ECS
95 | - name: Deploy to Amazon ECS
96 | uses: aws-actions/amazon-ecs-deploy-task-definition@v1
97 | with:
98 | task-definition: ${{ steps.%s-container.outputs.task-definition }}
99 | service: %s-service-${{ env.BUILD_NAME }}
100 | cluster: %s-cluster-${{ env.BUILD_NAME }}
101 |
102 | # Logout of Amazon
103 | - name: Logout of Amazon ECR
104 | if: always()
105 | run: docker logout ${{ steps.login-ecr.outputs.registry }}
106 | `,
107 | stackDir, environment, stackDir, environment, stackDir, stackDir,
108 | userInput, stackDir, userInput, userInput, userInput, userInput,
109 | )
110 | return source
111 | }
112 |
113 | func TaskDefinitionSource(environment, stackDir string) string {
114 |
115 | taskDefinition := utils.GetOutputsBackendObject(environment, stackDir)
116 |
117 | taskDefinitionSource := fmt.Sprintf(`{
118 | "taskRoleArn": "%s",
119 | "executionRoleArn": "%s",
120 | "taskDefinitionArn": "%s",
121 | "family": "%s",
122 | "containerDefinitions": [
123 | {
124 | "name": "%s",
125 | "image": "%s",
126 | "logConfiguration": {
127 | "logDriver": "%s",
128 | "secretOptions": null,
129 | "options": {
130 | "awslogs-group": "%s",
131 | "awslogs-stream-prefix": "%s",
132 | "awslogs-region": "%s"
133 | }
134 | },
135 | "portMappings": [
136 | {
137 | "hostPort": "9000",
138 | "protocol": "tcp",
139 | "containerPort": "%s"
140 | }
141 | ],
142 | "environment": [
143 | {
144 | "name": "BUILD_NAME",
145 | "value": "%s"
146 | },
147 | {
148 | "name": "ENVIRONMENT_NAME",
149 | "value": "%s"
150 | }
151 | ],
152 | "secrets": [
153 | {
154 | "name": "%s",
155 | "valueFrom": "%s:password::"
156 | }
157 | ],
158 | "cpu": 0,
159 | "memory": null,
160 | "command": null,
161 | "entryPoint": null,
162 | "dnsSearchDomains": null,
163 | "linuxParameters": null,
164 | "resourceRequirements": null,
165 | "ulimits": null,
166 | "dnsServers": null,
167 | "mountPoints": [],
168 | "workingDirectory": null,
169 | "dockerSecurityOptions": null,
170 | "memoryReservation": null,
171 | "volumesFrom": [],
172 | "stopTimeout": null,
173 | "startTimeout": null,
174 | "firelensConfiguration": null,
175 | "dependsOn": null,
176 | "disableNetworking": null,
177 | "interactive": null,
178 | "healthCheck": null,
179 | "essential": true,
180 | "links": null,
181 | "hostname": null,
182 | "extraHosts": null,
183 | "pseudoTerminal": null,
184 | "user": null,
185 | "readonlyRootFilesystem": null,
186 | "dockerLabels": null,
187 | "systemControls": null,
188 | "privileged": null
189 | }
190 | ],
191 | "placementConstraints": [],
192 | "memory": "2048",
193 | "compatibilities": ["EC2", "FARGATE"],
194 | "requiresAttributes": [
195 | {
196 | "name": "com.amazonaws.ecs.capability.logging-driver.awslogs"
197 | },
198 | {
199 | "name": "ecs.capability.execution-role-awslogs"
200 | },
201 | {
202 | "name": "com.amazonaws.ecs.capability.ecr-auth"
203 | },
204 | {
205 | "name": "com.amazonaws.ecs.capability.docker-remote-api.1.19"
206 | },
207 | {
208 | "name": "ecs.capability.execution-role-ecr-pull"
209 | },
210 | {
211 | "name": "com.amazonaws.ecs.capability.docker-remote-api.1.18"
212 | },
213 | {
214 | "name": "ecs.capability.task-eni",
215 | "targetId": null,
216 | "targetType": null,
217 | "value": null
218 | }
219 | ],
220 | "ipcMode": null,
221 | "pidMode": null,
222 | "requiresCompatibilities": ["FARGATE"],
223 | "networkMode": "awsvpc",
224 | "cpu": "1024",
225 | "revision": 17,
226 | "status": "ACTIVE",
227 | "inferenceAccelerators": null,
228 | "proxyConfiguration": null,
229 | "volumes": [],
230 | "tags": [
231 | {
232 | "key": "sst:app",
233 | "value": "web-app"
234 | },
235 | {
236 | "key": "sst:stage",
237 | "value": "dev"
238 | }
239 | ]
240 | }
241 | `,
242 | taskDefinition.BackendObj.TaskRole,
243 | taskDefinition.BackendObj.ExecutionRole,
244 | taskDefinition.BackendObj.TaskDefinition,
245 | taskDefinition.BackendObj.Family,
246 | taskDefinition.BackendObj.ContainerName,
247 | taskDefinition.BackendObj.Image,
248 | taskDefinition.BackendObj.LogDriver,
249 | taskDefinition.BackendObj.LogDriverOptions.AwsLogsGroup,
250 | taskDefinition.BackendObj.LogDriverOptions.AwsLogsStreamPrefix,
251 | taskDefinition.BackendObj.LogDriverOptions.AwsLogsRegion,
252 | taskDefinition.BackendObj.ContainerPort,
253 | taskDefinition.Environment,
254 | taskDefinition.EnvName,
255 | taskDefinition.SecretName,
256 | taskDefinition.BackendObj.SecretArn,
257 | )
258 | return taskDefinitionSource
259 | }
260 |
261 | func CDWebSource(stack, dirName string) string {
262 | var sourceDir string
263 | if stack == constants.ReactJS {
264 | sourceDir = "build"
265 | } else if stack == constants.NextJS {
266 | sourceDir = "out"
267 | }
268 | source := fmt.Sprintf(`name: CD %s
269 | on:
270 | push:
271 | branches:
272 | - dev
273 | - qa
274 | - master
275 | # paths: "%s/**"
276 | workflow_dispatch:
277 |
278 | jobs:
279 | deploy:
280 | name: Deploy
281 | runs-on: ubuntu-latest
282 | if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/qa'
283 | defaults:
284 | run:
285 | working-directory: ./%s
286 | strategy:
287 | matrix:
288 | node-version: [16.13.0]
289 | env:
290 | SOURCE_DIR: "./%s/%s/"
291 | AWS_REGION: ${{ secrets.AWS_REGION }}
292 | AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
293 | AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
294 | PATHS: "/*"
295 |
296 | steps:
297 | - name: Checkout to branch
298 | uses: actions/checkout@v2
299 |
300 | - name: Use Node.js ${{ matrix.node-version }}
301 | uses: actions/setup-node@v2
302 | with:
303 | node-version: ${{ matrix.node-version }}
304 |
305 | - name: Get branch name
306 | id: vars
307 | run: echo ::set-output name=short_ref::${GITHUB_REF_NAME}
308 |
309 | - name: Set short environment name
310 | id: environment
311 | run: |
312 | if [[ ${{ steps.vars.outputs.short_ref }} == master ]]; then
313 | echo ::set-output name=short_env::prod
314 | elif [[ ${{ steps.vars.outputs.short_ref }} == qa ]]; then
315 | echo ::set-output name=short_env::qa
316 | else
317 | echo ::set-output name=short_env::dev
318 | fi
319 |
320 | - name: Install dependencies
321 | run: yarn
322 |
323 | - name: Build
324 | run: yarn build:${{ steps.environment.outputs.short_env }}
325 |
326 | - name: AWS Deploy to S3
327 | uses: jakejarvis/s3-sync-action@v0.5.1
328 | with:
329 | args: --follow-symlinks --delete
330 | env:
331 | AWS_S3_BUCKET: %s-${{ steps.environment.outputs.short_env }}
332 |
333 | - name: Invalidate CloudFront
334 | uses: chetan/invalidate-cloudfront-action@v2.4
335 | env:
336 | DISTRIBUTION: ${{ secrets.DISTRIBUTION_ID }}
337 | `,
338 | dirName, dirName, dirName, dirName, sourceDir, dirName)
339 | return source
340 | }
341 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/aymerick/raymond v2.0.2+incompatible h1:VEp3GpgdAnv9B2GFyTvqgcKvY+mfKMjPOA3SbKLtnU0=
2 | github.com/aymerick/raymond v2.0.2+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g=
3 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
4 | github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM=
5 | github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ=
6 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
7 | github.com/chzyer/readline v1.5.1 h1:upd/6fQk4src78LMRzh5vItIt361/o4uq553V8B5sGI=
8 | github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk=
9 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
10 | github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04=
11 | github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8=
12 | github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
13 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
14 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
15 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
16 | github.com/enescakir/emoji v1.0.0 h1:W+HsNql8swfCQFtioDGDHCHri8nudlK1n5p2rHCJoog=
17 | github.com/enescakir/emoji v1.0.0/go.mod h1:Bt1EKuLnKDTYpLALApstIkAjdDrS/8IAgTkKp+WKFD0=
18 | github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
19 | github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
20 | github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
21 | github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
22 | github.com/gdamore/tcell/v2 v2.6.0 h1:OKbluoP9VYmJwZwq/iLb4BxwKcwGthaa1YNBJIyCySg=
23 | github.com/gdamore/tcell/v2 v2.6.0/go.mod h1:be9omFATkdr0D9qewWW3d+MEvl5dha+Etb5y65J2H8Y=
24 | github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI=
25 | github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
26 | github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
27 | github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
28 | github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a/go.mod h1:UJSiEoRfvx3hP73CvoARgeLjaIOjybY9vj8PUPPFGeU=
29 | github.com/juju/ansiterm v1.0.0 h1:gmMvnZRq7JZJx6jkfSq9/+2LMrVEwGwt7UR6G+lmDEg=
30 | github.com/juju/ansiterm v1.0.0/go.mod h1:PyXUpnI3olx3bsPcHt98FGPX/KCFZ1Fi+hw1XLI6384=
31 | github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw=
32 | github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
33 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
34 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
35 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
36 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
37 | github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
38 | github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
39 | github.com/lunixbochs/vtclean v1.0.0 h1:xu2sLAri4lGiovBDQKxl5mrXyESr3gUr5m5SM5+LVb8=
40 | github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
41 | github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
42 | github.com/mattn/go-colorable v0.1.10/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
43 | github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
44 | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
45 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
46 | github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
47 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
48 | github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
49 | github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
50 | github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
51 | github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
52 | github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
53 | github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
54 | github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ=
55 | github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw=
56 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
57 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
58 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
59 | github.com/rivo/tview v0.0.0-20230814110005-ccc2c8119703 h1:ZyM/+FYnpbZsFWuCohniM56kRoHRB4r5EuIzXEYkpxo=
60 | github.com/rivo/tview v0.0.0-20230814110005-ccc2c8119703/go.mod h1:nVwGv4MP47T0jvlk7KuTTjjuSmrGO4JF0iaiNt4bufE=
61 | github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
62 | github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
63 | github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
64 | github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
65 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
66 | github.com/schollz/progressbar/v3 v3.13.1 h1:o8rySDYiQ59Mwzy2FELeHY5ZARXZTVJC7iHD6PEFUiE=
67 | github.com/schollz/progressbar/v3 v3.13.1/go.mod h1:xvrbki8kfT1fzWzBT/UZd9L6GA+jdL7HAgq2RFnO6fQ=
68 | github.com/spaceweasel/promptui v0.8.1 h1:DVxYGbaUcDRzZwpp+qifsTBKvGLupSBl3RUvxYw+laY=
69 | github.com/spaceweasel/promptui v0.8.1/go.mod h1:HOb/JMVBMo99PCMihjeHmfpIPQnwfOebaR1bFGfry8s=
70 | github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
71 | github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
72 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
73 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
74 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
75 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
76 | github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
77 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
78 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
79 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
80 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
81 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
82 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
83 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
84 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
85 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
86 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
87 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
88 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
89 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
90 | golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
91 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
92 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
93 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
94 | golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
95 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
96 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
97 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
98 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
99 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
100 | golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
101 | golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
102 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
103 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
104 | golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
105 | golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
106 | golang.org/x/term v0.11.0 h1:F9tnn/DA/Im8nCwm+fX+1/eBwi4qFjRT++MhtVC4ZX0=
107 | golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
108 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
109 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
110 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
111 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
112 | golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc=
113 | golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
114 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
115 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
116 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
117 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
118 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
119 | gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
120 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
121 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
122 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
123 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
124 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
125 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
126 |
--------------------------------------------------------------------------------
/internal/utils/utils.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "io"
7 | "os"
8 | "os/exec"
9 | "strconv"
10 | "strings"
11 |
12 | "github.com/iancoleman/strcase"
13 | "github.com/wednesday-solutions/picky/internal/constants"
14 | "github.com/wednesday-solutions/picky/internal/errorhandler"
15 | )
16 |
17 | // SplitStackDirectoryName returns user-input, stack-suffix and last-suffix of the given stack directory name.
18 | func SplitStackDirectoryName(dirName string) (string, string, string, string) {
19 | var userInput, langSuffix, stackSuffix, lastSuffix string
20 | var splitDirName []string
21 | var isBackendStack, isWebStack, isMobileStack bool
22 | splitDirName = strings.Split(dirName, "-")
23 | if len(splitDirName) > constants.Two {
24 | lastSuffix = splitDirName[len(splitDirName)-constants.One]
25 | stackSuffix = splitDirName[len(splitDirName)-constants.Two]
26 | langSuffix = splitDirName[len(splitDirName)-constants.Three]
27 | if lastSuffix == constants.Pg || lastSuffix == constants.Mysql || lastSuffix == constants.Mongo {
28 | isBackendStack = true
29 | } else if lastSuffix == constants.Web {
30 | isWebStack = true
31 | } else if lastSuffix == constants.Mobile {
32 | isMobileStack = true
33 | }
34 | var suffixSize int
35 | if isBackendStack {
36 | suffixSize = constants.BackendSuffixSize
37 | } else if isWebStack {
38 | suffixSize = constants.WebSuffixSize
39 | } else if isMobileStack {
40 | suffixSize = constants.MobileSuffixSize
41 | }
42 | userInput = splitDirName[constants.Zero]
43 | for _, split := range splitDirName[constants.One : len(splitDirName)-suffixSize] {
44 | userInput = fmt.Sprintf("%s-%s", userInput, split)
45 | }
46 | }
47 | return userInput, langSuffix, stackSuffix, lastSuffix
48 | }
49 |
50 | // CovertToCamelCase return camel cased array of string of the given array of string.
51 | func ConvertToCamelCase(slice []string) []string {
52 | camelSlice := []string{}
53 | for _, str := range slice {
54 | camelSlice = append(camelSlice, strcase.ToCamel(str))
55 | }
56 | return camelSlice
57 | }
58 |
59 | // RunCommandWithLogs runs the given command with logs.
60 | func RunCommandWithLogs(path string, name string, args ...string) error {
61 | cmd := exec.Command(name, args...)
62 | cmd.Stdout = os.Stdout
63 | cmd.Stderr = os.Stderr
64 | if path != "" {
65 | cmd.Dir = path
66 | }
67 | err := cmd.Run()
68 | fmt.Printf("\n")
69 | return err
70 | }
71 |
72 | // RunCommandWithLogs runs the given command without logs.
73 | func RunCommandWithoutLogs(path string, name string, args ...string) error {
74 | cmd := exec.Command(name, args...)
75 | if path != "" {
76 | cmd.Dir = path
77 | }
78 | err := cmd.Run()
79 | return err
80 | }
81 |
82 | // GetPackageManagerOfUser checks whether yarn or npm is installed in the user's machine.
83 | // If both are not installed, then the system will throw error.
84 | func GetPackageManagerOfUser() string {
85 | var pkgManager string
86 | err := RunCommandWithoutLogs("", constants.Yarn, "-v")
87 | if err != nil {
88 | err = RunCommandWithoutLogs("", constants.Npm, "-v")
89 | if err != nil {
90 | // Throw error either yarn or npm not installed
91 | errorhandler.CheckNilErr(fmt.Errorf("Please install 'yarn' or 'npm' in your machine.\n"))
92 | } else {
93 | pkgManager = constants.Npm
94 | }
95 | } else {
96 | pkgManager = constants.Yarn
97 | }
98 | return pkgManager
99 | }
100 |
101 | // GetProjectName returns projectName
102 | func GetProjectName() string {
103 | currentDir := CurrentDirectory()
104 | splitDirs := strings.Split(currentDir, "/")
105 | projectName := splitDirs[len(splitDirs)-1]
106 | return projectName
107 | }
108 |
109 | func CreateGithubWorkflowDir() {
110 | currentDir := CurrentDirectory()
111 | workflowsPath := fmt.Sprintf("%s/%s", currentDir,
112 | constants.GithubWorkflowsDir,
113 | )
114 | workflowStatus, _ := IsExists(workflowsPath)
115 | if !workflowStatus {
116 | githubFolderPath := fmt.Sprintf("%s/%s", currentDir, ".github")
117 | githubStatus, _ := IsExists(githubFolderPath)
118 | if !githubStatus {
119 | err := CreateDirectory(githubFolderPath)
120 | errorhandler.CheckNilErr(err)
121 | }
122 | err := CreateDirectory(workflowsPath)
123 | errorhandler.CheckNilErr(err)
124 | }
125 | }
126 |
127 | func EndsWith(inputString, endString string) bool {
128 | if len(inputString) < len(endString) {
129 | return false
130 | } else if len(inputString) == len(endString) {
131 | return inputString == endString
132 | } else {
133 | for i, j := len(endString)-1, len(inputString)-1; i >= 0; i, j = i-1, j-1 {
134 | if endString[i] != inputString[j] {
135 | return false
136 | }
137 | }
138 | return true
139 | }
140 | }
141 |
142 | func StartsWith(inputString, startString string) bool {
143 | if len(inputString) < len(startString) {
144 | return false
145 | } else if len(inputString) == len(startString) {
146 | return inputString == startString
147 | } else {
148 | for i, j := 0, 0; i < len(startString); i, j = i+1, j+1 {
149 | if startString[i] != inputString[j] {
150 | return false
151 | }
152 | }
153 | return true
154 | }
155 | }
156 |
157 | type LogDriverOptionsKeys struct {
158 | AwsLogsGroup string `json:"awsLogsGroup"`
159 | AwsLogsStreamPrefix string `json:"awsLogsStreamPrefix"`
160 | AwsLogsRegion string `json:"awsLogsRegion"`
161 | }
162 |
163 | type WebOutputKeys struct {
164 | DistributionId string `json:"distributionId"`
165 | BucketName string `json:"bucketName"`
166 | SiteUrl string `json:"siteUrl"`
167 | }
168 |
169 | type BackendOutputKeys struct {
170 | TaskRole string `json:"taskRole"`
171 | Image string `json:"image"`
172 | ContainerName string `json:"containerName"`
173 | ContainerPort string `json:"containerPort"`
174 | ExecutionRole string `json:"executionRole"`
175 | TaskDefinition string `json:"taskDefinition"`
176 | LogDriver string `json:"logDriver"`
177 | LogDriverOptions LogDriverOptionsKeys
178 | Family string `json:"family"`
179 | AwsRegion string `json:"awsRegion"`
180 | RedisHost string `json:"redisHost"`
181 | SecretName string `json:"secretName"`
182 | DatabaseHost string `json:"databaseHost"`
183 | DatabaseName string `json:"databaseName"`
184 | SecretArn string `json:"secretArn"`
185 | LoadBalancerDns string `json:"loadBalancerDns"`
186 | ServiceName string `json:"serviceName"`
187 | ClusterName string `json:"clusterName"`
188 | ElasticContainerRegistryRepo string `json:"elasticContainerRegistryRepo"`
189 | }
190 |
191 | type TaskDefinitionDetails struct {
192 | BackendObj BackendOutputKeys
193 | Environment string
194 | EnvName string
195 | SecretName string
196 | }
197 |
198 | func ReadJsonDataInSstOutputs() map[string]interface{} {
199 | file := fmt.Sprintf(
200 | "%s/%s/%s", CurrentDirectory(), constants.DotSst, constants.OutputsJson,
201 | )
202 | status, _ := IsExists(file)
203 | if !status {
204 | return nil
205 | }
206 | sstOutputFile, err := os.Open(file)
207 | errorhandler.CheckNilErr(err)
208 |
209 | fileContent, err := io.ReadAll(sstOutputFile)
210 | errorhandler.CheckNilErr(err)
211 |
212 | var data interface{}
213 | err = json.Unmarshal(fileContent, &data)
214 | errorhandler.CheckNilErr(err)
215 |
216 | if jsonData, ok := data.(map[string]interface{}); ok {
217 | return jsonData
218 | }
219 | return nil
220 | }
221 |
222 | func GetOutputsBackendObject(environment, stackDir string) TaskDefinitionDetails {
223 | camelCaseDir := strcase.ToCamel(stackDir)
224 | jsonData := ReadJsonDataInSstOutputs()
225 | // if jsonData == nil {
226 | // errorhandler.CheckNilErr(fmt.Errorf("outputs.json is not valid."))
227 | // }
228 | var td TaskDefinitionDetails
229 | td.Environment = environment
230 | td.EnvName = GetShortEnvName(environment)
231 | key := fmt.Sprintf("%s-web-app-%s", td.EnvName, camelCaseDir)
232 |
233 | if value, ok := jsonData[key]; ok {
234 |
235 | jsonOutput, err := json.Marshal(value)
236 | errorhandler.CheckNilErr(err)
237 |
238 | var backendObj BackendOutputKeys
239 | err = json.Unmarshal(jsonOutput, &backendObj)
240 | errorhandler.CheckNilErr(err)
241 |
242 | td.BackendObj = backendObj
243 | return td
244 | }
245 | return TaskDefinitionDetails{}
246 | }
247 |
248 | func CreateInfraOutputsJson(environment string) error {
249 |
250 | jsonData := ReadJsonDataInSstOutputs()
251 | if jsonData == nil {
252 | errorhandler.CheckNilErr(fmt.Errorf("outputs.json is not valid"))
253 | }
254 | envName := GetShortEnvName(environment)
255 | _, _, directories := GetExistingStacksDatabasesAndDirectories()
256 | for _, stackDir := range directories {
257 | camelCaseDir := strcase.ToCamel(stackDir)
258 |
259 | key := fmt.Sprintf("%s-web-app-%s", envName, camelCaseDir)
260 |
261 | if value, ok := jsonData[key]; ok {
262 | outputFile := fmt.Sprintf("%s/%s-%s", CurrentDirectory(), stackDir, constants.OutputsJson)
263 |
264 | status, _ := IsExists(outputFile)
265 | if !status {
266 | err := CreateFile(outputFile)
267 | errorhandler.CheckNilErr(err)
268 | }
269 |
270 | jsonOutput, err := json.MarshalIndent(value, "", "\t")
271 | errorhandler.CheckNilErr(err)
272 |
273 | err = WriteToFile(outputFile, string(jsonOutput))
274 | errorhandler.CheckNilErr(err)
275 | }
276 | }
277 | return nil
278 | }
279 |
280 | // GetShortEnvName return short environment name for the given environment.
281 | func GetShortEnvName(environment string) string {
282 | var shortEnv string
283 | if environment == constants.Dev || environment == constants.Develop || environment == constants.Development {
284 | shortEnv = constants.Dev
285 | } else if environment == constants.QA {
286 | shortEnv = constants.QA
287 | } else if environment == constants.Prod || environment == constants.Production {
288 | shortEnv = constants.Prod
289 | }
290 | return shortEnv
291 | }
292 |
293 | func GetPortNumber(defaultPN int) int {
294 | _, _, stackDirs := GetExistingStacksDatabasesAndDirectories()
295 | var backendServices []string
296 | for _, dir := range stackDirs {
297 | service := FindService(dir)
298 | if service == constants.Backend {
299 | backendServices = append(backendServices, service)
300 | }
301 | }
302 | currentPN := defaultPN + len(backendServices) - 1
303 | return currentPN
304 | }
305 |
306 | func GetDatabasePortNumber(driver string) int {
307 | _, _, stackDirs := GetExistingStacksDatabasesAndDirectories()
308 | var postgresStacks, mysqlStacks []string
309 | for _, dir := range stackDirs {
310 | service := FindService(dir)
311 | if service == constants.Backend {
312 | _, database := FindStackAndDatabase(dir)
313 | if database == constants.PostgreSQL {
314 | postgresStacks = append(postgresStacks, dir)
315 | } else if database == constants.MySQL {
316 | mysqlStacks = append(mysqlStacks, dir)
317 | }
318 | }
319 | }
320 | var currentPortNumber int
321 | if driver == constants.PostgreSQL {
322 | currentPortNumber = constants.PostgresPortNumber + len(postgresStacks) - 1
323 | } else if driver == constants.MySQL {
324 | currentPortNumber = constants.MysqlPortNumber + len(mysqlStacks) - 1
325 | }
326 | return currentPortNumber
327 | }
328 |
329 | func FetchExistingPortNumber(stackDir, portName string) string {
330 | envFile := fmt.Sprintf("%s/%s/%s",
331 | CurrentDirectory(),
332 | stackDir,
333 | constants.DockerEnvFile,
334 | )
335 | var portNumber string
336 | content, err := os.ReadFile(envFile)
337 | errorhandler.CheckNilErr(err)
338 | lines := strings.Split(string(content), "\n")
339 | for _, line := range lines {
340 | if StartsWith(line, portName) {
341 | portLine := strings.Split(line, "=")
342 | portNumber = portLine[len(portLine)-1]
343 | return portNumber
344 | }
345 | }
346 | if portName == constants.BackendPort {
347 | portNumber = strconv.Itoa(constants.BackendPortNumber)
348 | } else if portName == constants.PostgresPort {
349 | portNumber = strconv.Itoa(constants.PostgresPortNumber)
350 | } else if portName == constants.MysqlPort {
351 | portNumber = strconv.Itoa(constants.MysqlPortNumber)
352 | } else if portName == constants.RedisPort {
353 | portNumber = strconv.Itoa(constants.RedisPortNumber)
354 | }
355 | return portNumber
356 | }
357 |
--------------------------------------------------------------------------------