├── .github └── workflows │ └── build.yaml ├── .gitignore ├── .templates ├── jsbase │ ├── .env │ ├── .eslintrc.js │ ├── .gitignore │ ├── README.md │ ├── package.json │ └── src │ │ ├── app.js │ │ └── server.js └── tsbase │ ├── .env │ ├── .eslintrc.js │ ├── .gitignore │ ├── README.md │ ├── package.json │ └── src │ ├── app.ts │ └── server.ts ├── LICENSE.md ├── Makefile ├── README.md ├── assets └── expressify-logo.png ├── cmd └── expressify │ └── main.go ├── go.mod ├── go.sum └── internal ├── cli_model └── cli_model.go ├── coding_styles └── coding_styles.go ├── configs └── configs.go ├── databases └── databases.go ├── languages └── languages.go ├── loggers └── loggers.go ├── orms └── orms.go ├── package_managers └── package_managers.go ├── selector └── selector.go ├── structure └── structure.go └── test_frameworks └── test_frameworks.go /.github/workflows/build.yaml: -------------------------------------------------------------------------------- 1 | # This workflow will build a golang project 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go 3 | 4 | name: Go 5 | 6 | on: 7 | push: 8 | branches: [ "main" ] 9 | pull_request: 10 | branches: [ "main" ] 11 | 12 | jobs: 13 | 14 | build: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v3 18 | 19 | - name: Set up Go 20 | uses: actions/setup-go@v4 21 | with: 22 | go-version: '1.21.3' 23 | 24 | - name: Build 25 | run: go build -o bin/expressify cmd/expressify/main.go 26 | 27 | - name: Test 28 | run: go test -v ./... 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bin 2 | .DS_Store 3 | .vscode 4 | .expressify -------------------------------------------------------------------------------- /.templates/jsbase/.env: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codersgyan/expressify/6c939f6e2b12090e9b9ea252ebd63396b9ea9aae/.templates/jsbase/.env -------------------------------------------------------------------------------- /.templates/jsbase/.eslintrc.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codersgyan/expressify/6c939f6e2b12090e9b9ea252ebd63396b9ea9aae/.templates/jsbase/.eslintrc.js -------------------------------------------------------------------------------- /.templates/jsbase/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codersgyan/expressify/6c939f6e2b12090e9b9ea252ebd63396b9ea9aae/.templates/jsbase/.gitignore -------------------------------------------------------------------------------- /.templates/jsbase/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codersgyan/expressify/6c939f6e2b12090e9b9ea252ebd63396b9ea9aae/.templates/jsbase/README.md -------------------------------------------------------------------------------- /.templates/jsbase/package.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codersgyan/expressify/6c939f6e2b12090e9b9ea252ebd63396b9ea9aae/.templates/jsbase/package.json -------------------------------------------------------------------------------- /.templates/jsbase/src/app.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codersgyan/expressify/6c939f6e2b12090e9b9ea252ebd63396b9ea9aae/.templates/jsbase/src/app.js -------------------------------------------------------------------------------- /.templates/jsbase/src/server.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codersgyan/expressify/6c939f6e2b12090e9b9ea252ebd63396b9ea9aae/.templates/jsbase/src/server.js -------------------------------------------------------------------------------- /.templates/tsbase/.env: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codersgyan/expressify/6c939f6e2b12090e9b9ea252ebd63396b9ea9aae/.templates/tsbase/.env -------------------------------------------------------------------------------- /.templates/tsbase/.eslintrc.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codersgyan/expressify/6c939f6e2b12090e9b9ea252ebd63396b9ea9aae/.templates/tsbase/.eslintrc.js -------------------------------------------------------------------------------- /.templates/tsbase/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codersgyan/expressify/6c939f6e2b12090e9b9ea252ebd63396b9ea9aae/.templates/tsbase/.gitignore -------------------------------------------------------------------------------- /.templates/tsbase/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codersgyan/expressify/6c939f6e2b12090e9b9ea252ebd63396b9ea9aae/.templates/tsbase/README.md -------------------------------------------------------------------------------- /.templates/tsbase/package.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codersgyan/expressify/6c939f6e2b12090e9b9ea252ebd63396b9ea9aae/.templates/tsbase/package.json -------------------------------------------------------------------------------- /.templates/tsbase/src/app.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codersgyan/expressify/6c939f6e2b12090e9b9ea252ebd63396b9ea9aae/.templates/tsbase/src/app.ts -------------------------------------------------------------------------------- /.templates/tsbase/src/server.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codersgyan/expressify/6c939f6e2b12090e9b9ea252ebd63396b9ea9aae/.templates/tsbase/src/server.ts -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Coder's Gyan 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 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | BINARY_NAME=expressify 2 | ENTRY_POINT=cmd/expressify/main.go 3 | 4 | all: test build 5 | 6 | build: 7 | @go build -o bin/$(BINARY_NAME) $(ENTRY_POINT) 8 | 9 | run: 10 | @go run $(ENTRY_POINT) 11 | 12 | test: 13 | @go test ./... 14 | 15 | clean: 16 | @go clean 17 | @rm -f bin/$(BINARY_NAME) 18 | 19 | .PHONY: all build run test clean 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### Expressify-CLI 🚀
2 | 3 |
4 | 5 | **Expressify-CLI** is a command-line tool designed to generate a **production-grade** scaffold for Express applications in a single command. This tool streamlines the setup process for developing Express applications with JavaScript/TypeScript, making it faster and more efficient. 6 | 7 | #### Inspiration 💡 8 | 9 | Setting up a new Express project can often feel like reinventing the wheel. Developers frequently encounter the challenge of integrating a myriad of components such as TypeScript configurations, testing frameworks, linters, Docker configurations, and more. This not only consumes valuable time but also poses a risk of inconsistency and configuration errors. 10 | 11 | #### How Expressify-CLI Solves the Problem 🛠️ 12 | 13 | Expressify-CLI tackles these challenges head-on by automating the creation of a comprehensive and robust scaffold for Express applications. With just one command, developers can now generate a project structure that includes: 14 | 15 | - TypeScript (TS) configuration for robust typing. 📐 16 | - Test configurations for reliable code testing. ✅ 17 | - Linters for maintaining code quality and consistency. 🔍 18 | - Docker files for easy containerization. 🐳 19 | - Logger setup for effective logging and monitoring. 📝 20 | - Graceful shutdowns for better resource management. 🧘 21 | - Error handling middleware for improved error management. 🚫 22 | - Optional authentication module with JWT for secure access. 🔒 23 | - Pre-configured Git setup with README, license file, and other production-level settings. 📄 24 | 25 | #### Run Locally (Development) 🚀 26 | 27 | Running Expressify-CLI locally is straightforward with the help of the provided Makefile. 28 | Follow these steps to set up and run the project in a development environment: 29 | 30 | 31 | ##### Prerequisites: 32 | 33 | Ensure that Go is installed on your system. Verify this with `go version`. 34 | Familiarity with basic make commands can be helpful. 35 | 36 | **_Notes:_** 37 | The Makefile simplifies common tasks but can be modified if your workflow requires it. 38 | Additional configuration or steps might be necessary depending on the specific setup of your project. 39 | 40 | ##### Clone the Repository 41 | 42 | Start with cloning the repository to your local machine using Git: 43 | 44 | ```bash 45 | git clone https://github.com/codersgyan/expressify.git 46 | cd expressify 47 | ``` 48 | 49 | ##### Using the Makefile 50 | 51 | The Makefile includes commands to simplify the build and test process. Here's how you can use it: 52 | 53 | ###### Build the Project: 54 | 55 | Compiles the project and creates an executable. 56 | 57 | ```bash 58 | make build 59 | ``` 60 | 61 | ###### Run the Tool: 62 | 63 | Executes the compiled binary. 64 | 65 | ```bash 66 | make run 67 | ``` 68 | 69 | ###### Run Tests: 70 | 71 | Runs the automated tests. 72 | 73 | ```bash 74 | make test 75 | ``` 76 | 77 | ###### Clean Up: 78 | 79 | Removes the generated binary and any other temporary files. 80 | 81 | ```bash 82 | make clean 83 | ``` 84 | 85 | #### Open Source Contribution 🤝 86 | 87 | Expressify-CLI is an open source project, and contributions are greatly appreciated! If you have ideas for improvements or have found a bug, feel free to open an issue. We also warmly welcome pull requests. Let's build a stronger tool together! 🌍🛠️ 88 | -------------------------------------------------------------------------------- /assets/expressify-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codersgyan/expressify/6c939f6e2b12090e9b9ea252ebd63396b9ea9aae/assets/expressify-logo.png -------------------------------------------------------------------------------- /cmd/expressify/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | tea "github.com/charmbracelet/bubbletea" 8 | "github.com/codersgyan/expressify/internal/cli_model" 9 | ) 10 | 11 | func main() { 12 | 13 | // cwd, err := os.Getwd() 14 | // if err != nil { 15 | // fmt.Printf("unable to get current working directory: %w", err) 16 | // os.Exit(1) 17 | // } 18 | // srcPath := cwd + "/.templates/jsbase" 19 | // dstPath := cwd + "/.expressify/auth-service" 20 | 21 | // cpErr := structure.CopyDir(srcPath, dstPath) 22 | // if cpErr != nil { 23 | // fmt.Printf("Error copying directory: %s\n", cpErr) 24 | // } else { 25 | // fmt.Println("Directory copied successfully.") 26 | // } 27 | // return n 28 | 29 | p := tea.NewProgram(cli_model.InitialModel()) 30 | if _, err := p.Run(); err != nil { 31 | fmt.Printf("Alas, there's been an error: %v", err) 32 | os.Exit(1) 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/codersgyan/expressify 2 | 3 | go 1.21.3 4 | 5 | require ( 6 | github.com/charmbracelet/bubbles v0.17.1 7 | github.com/charmbracelet/bubbletea v0.25.0 8 | ) 9 | 10 | require ( 11 | github.com/atotto/clipboard v0.1.4 // indirect 12 | github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect 13 | github.com/charmbracelet/lipgloss v0.9.1 // indirect 14 | github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect 15 | github.com/lucasb-eyer/go-colorful v1.2.0 // indirect 16 | github.com/mattn/go-isatty v0.0.18 // indirect 17 | github.com/mattn/go-localereader v0.0.1 // indirect 18 | github.com/mattn/go-runewidth v0.0.15 // indirect 19 | github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b // indirect 20 | github.com/muesli/cancelreader v0.2.2 // indirect 21 | github.com/muesli/reflow v0.3.0 // indirect 22 | github.com/muesli/termenv v0.15.2 // indirect 23 | github.com/rivo/uniseg v0.2.0 // indirect 24 | github.com/sahilm/fuzzy v0.1.1-0.20230530133925-c48e322e2a8f // indirect 25 | golang.org/x/sync v0.1.0 // indirect 26 | golang.org/x/sys v0.12.0 // indirect 27 | golang.org/x/term v0.6.0 // indirect 28 | golang.org/x/text v0.3.8 // indirect 29 | ) 30 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= 2 | github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= 3 | github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= 4 | github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= 5 | github.com/charmbracelet/bubbles v0.17.1 h1:0SIyjOnkrsfDo88YvPgAWvZMwXe26TP6drRvmkjyUu4= 6 | github.com/charmbracelet/bubbles v0.17.1/go.mod h1:9HxZWlkCqz2PRwsCbYl7a3KXvGzFaDHpYbSYMJ+nE3o= 7 | github.com/charmbracelet/bubbletea v0.25.0 h1:bAfwk7jRz7FKFl9RzlIULPkStffg5k6pNt5dywy4TcM= 8 | github.com/charmbracelet/bubbletea v0.25.0/go.mod h1:EN3QDR1T5ZdWmdfDzYcqOCAps45+QIJbLOBxmVNWNNg= 9 | github.com/charmbracelet/lipgloss v0.9.1 h1:PNyd3jvaJbg4jRHKWXnCj1akQm4rh8dbEzN1p/u1KWg= 10 | github.com/charmbracelet/lipgloss v0.9.1/go.mod h1:1mPmG4cxScwUQALAAnacHaigiiHB9Pmr+v1VEawJl6I= 11 | github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 h1:q2hJAaP1k2wIvVRd/hEHD7lacgqrCPS+k8g1MndzfWY= 12 | github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk= 13 | github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= 14 | github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= 15 | github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98= 16 | github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 17 | github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= 18 | github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= 19 | github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= 20 | github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= 21 | github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= 22 | github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b h1:1XF24mVaiu7u+CFywTdcDo2ie1pzzhwjt6RHqzpMU34= 23 | github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho= 24 | github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= 25 | github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= 26 | github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= 27 | github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= 28 | github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo= 29 | github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= 30 | github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 31 | github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= 32 | github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 33 | github.com/sahilm/fuzzy v0.1.1-0.20230530133925-c48e322e2a8f h1:MvTmaQdww/z0Q4wrYjDSCcZ78NoftLQyHBSLW/Cx79Y= 34 | github.com/sahilm/fuzzy v0.1.1-0.20230530133925-c48e322e2a8f/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y= 35 | golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= 36 | golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 37 | golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 38 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 39 | golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= 40 | golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 41 | golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw= 42 | golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= 43 | golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY= 44 | golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= 45 | -------------------------------------------------------------------------------- /internal/cli_model/cli_model.go: -------------------------------------------------------------------------------- 1 | package cli_model 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/charmbracelet/bubbles/list" 7 | "github.com/charmbracelet/bubbles/textinput" 8 | tea "github.com/charmbracelet/bubbletea" 9 | "github.com/charmbracelet/lipgloss" 10 | "github.com/codersgyan/expressify/internal/coding_styles" 11 | "github.com/codersgyan/expressify/internal/configs" 12 | "github.com/codersgyan/expressify/internal/databases" 13 | "github.com/codersgyan/expressify/internal/languages" 14 | "github.com/codersgyan/expressify/internal/loggers" 15 | "github.com/codersgyan/expressify/internal/orms" 16 | "github.com/codersgyan/expressify/internal/package_managers" 17 | "github.com/codersgyan/expressify/internal/selector" 18 | "github.com/codersgyan/expressify/internal/structure" 19 | "github.com/codersgyan/expressify/internal/test_frameworks" 20 | ) 21 | 22 | var quitTextStyle = lipgloss.NewStyle().Margin(1, 0, 2, 4) 23 | 24 | type AppState int 25 | 26 | const ( 27 | StateWelcome AppState = iota 28 | StateProjectName 29 | StateLanguage 30 | StatePackageManager 31 | StateTestFramework 32 | StateLoggerLibrary 33 | StateDatabase 34 | StateORM 35 | StateConfig 36 | StateCodingStyle 37 | StateFolderStructure 38 | // StateAuth 39 | // StateMainWork 40 | // StateDone 41 | ) 42 | 43 | type CliModel struct { 44 | CurrentState AppState 45 | ProjectNameInput textinput.Model 46 | WelcomeMessage string 47 | LanguageList list.Model 48 | SelectedLanguage string 49 | PackageManagerList list.Model 50 | SelectedPackageManager string 51 | TestFrameworkList list.Model 52 | SelectedTestFramework string 53 | LoggerLibraryList list.Model 54 | SelectedLoggerLibrary string 55 | DatabaseList list.Model 56 | SelectedDatabase string 57 | ORMList list.Model 58 | SelectedORM string 59 | ConfigList list.Model 60 | SelectedConfig string 61 | CodingStyleList list.Model 62 | SelectedCodingStyle string 63 | FolderStructureCreated bool 64 | Error error 65 | } 66 | 67 | func (m CliModel) Init() tea.Cmd { 68 | return nil 69 | } 70 | 71 | type ( 72 | errMsg error 73 | ) 74 | 75 | func (m CliModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { 76 | var cmd tea.Cmd 77 | 78 | switch msg := msg.(type) { 79 | case tea.WindowSizeMsg: 80 | m.LanguageList.SetWidth(msg.Width) 81 | m.ProjectNameInput.Width = msg.Width 82 | return m, nil 83 | case tea.KeyMsg: 84 | switch msg.Type { 85 | case tea.KeyEnter: 86 | // here, only changing the state 87 | if m.CurrentState == StateWelcome { 88 | m.CurrentState = StateProjectName 89 | m.ProjectNameInput.Focus() 90 | return m, nil 91 | } 92 | if m.CurrentState == StateProjectName { 93 | m.CurrentState = StateLanguage 94 | return m, nil 95 | } 96 | 97 | if m.CurrentState == StateLanguage { 98 | i, ok := m.LanguageList.SelectedItem().(selector.Item) 99 | if ok { 100 | m.SelectedLanguage = string(i) 101 | } 102 | m.CurrentState = StatePackageManager 103 | return m, nil 104 | } 105 | 106 | if m.CurrentState == StatePackageManager { 107 | i, ok := m.PackageManagerList.SelectedItem().(selector.Item) 108 | if ok { 109 | m.SelectedPackageManager = string(i) 110 | } 111 | m.CurrentState = StateTestFramework 112 | return m, nil 113 | } 114 | 115 | if m.CurrentState == StateTestFramework { 116 | i, ok := m.TestFrameworkList.SelectedItem().(selector.Item) 117 | if ok { 118 | m.SelectedTestFramework = string(i) 119 | } 120 | m.CurrentState = StateLoggerLibrary 121 | return m, nil 122 | } 123 | 124 | if m.CurrentState == StateLoggerLibrary { 125 | i, ok := m.LoggerLibraryList.SelectedItem().(selector.Item) 126 | if ok { 127 | m.SelectedLoggerLibrary = string(i) 128 | } 129 | m.CurrentState = StateDatabase 130 | return m, nil 131 | } 132 | 133 | if m.CurrentState == StateDatabase { 134 | i, ok := m.DatabaseList.SelectedItem().(selector.Item) 135 | if ok { 136 | m.SelectedDatabase = string(i) 137 | } 138 | m.CurrentState = StateORM 139 | return m, nil 140 | } 141 | 142 | if m.CurrentState == StateORM { 143 | i, ok := m.ORMList.SelectedItem().(selector.Item) 144 | if ok { 145 | m.SelectedORM = string(i) 146 | } 147 | m.CurrentState = StateConfig 148 | return m, nil 149 | } 150 | 151 | if m.CurrentState == StateConfig { 152 | i, ok := m.ConfigList.SelectedItem().(selector.Item) 153 | if ok { 154 | m.SelectedConfig = string(i) 155 | } 156 | m.CurrentState = StateCodingStyle 157 | return m, nil 158 | } 159 | 160 | if m.CurrentState == StateCodingStyle { 161 | i, ok := m.CodingStyleList.SelectedItem().(selector.Item) 162 | if ok { 163 | m.SelectedCodingStyle = string(i) 164 | } 165 | m.CurrentState = StateFolderStructure 166 | return m, nil 167 | } 168 | 169 | if m.CurrentState == StateFolderStructure { 170 | // Create folder structure 171 | err := structure.CreateBaseFileStructure(m.ProjectNameInput.Value(), m.SelectedLanguage) 172 | if err != nil { 173 | fmt.Printf("error creating folder structure: %v", err) 174 | return m, tea.Quit 175 | } 176 | // todo: transition to next state 177 | // m.CurrentState = StateFolderStructure 178 | return m, nil 179 | } 180 | 181 | case tea.KeyEsc, tea.KeyCtrlC: 182 | return m, tea.Quit 183 | } 184 | case errMsg: 185 | m.Error = msg 186 | return m, tea.Quit 187 | } 188 | 189 | if m.CurrentState == StateProjectName { 190 | m.ProjectNameInput, cmd = m.ProjectNameInput.Update(msg) 191 | return m, cmd 192 | } 193 | 194 | if m.CurrentState == StateLanguage { 195 | m.LanguageList, cmd = m.LanguageList.Update(msg) 196 | return m, cmd 197 | } 198 | 199 | if m.CurrentState == StatePackageManager { 200 | m.PackageManagerList, cmd = m.PackageManagerList.Update(msg) 201 | return m, cmd 202 | } 203 | 204 | if m.CurrentState == StateTestFramework { 205 | m.TestFrameworkList, cmd = m.TestFrameworkList.Update(msg) 206 | return m, cmd 207 | } 208 | 209 | if m.CurrentState == StateLoggerLibrary { 210 | m.LoggerLibraryList, cmd = m.LoggerLibraryList.Update(msg) 211 | return m, cmd 212 | } 213 | 214 | if m.CurrentState == StateDatabase { 215 | m.DatabaseList, cmd = m.DatabaseList.Update(msg) 216 | return m, cmd 217 | } 218 | 219 | if m.CurrentState == StateORM { 220 | m.ORMList, cmd = m.ORMList.Update(msg) 221 | return m, cmd 222 | } 223 | 224 | if m.CurrentState == StateConfig { 225 | m.ConfigList, cmd = m.ConfigList.Update(msg) 226 | return m, cmd 227 | } 228 | 229 | if m.CurrentState == StateCodingStyle { 230 | m.CodingStyleList, cmd = m.CodingStyleList.Update(msg) 231 | return m, cmd 232 | } 233 | 234 | return m, nil 235 | } 236 | 237 | func (m CliModel) View() string { 238 | var s string 239 | 240 | switch m.CurrentState { 241 | case StateWelcome: 242 | s = m.WelcomeMessage 243 | case StateProjectName: 244 | s = "\n📗 Enter your project name:\n\n" + m.ProjectNameInput.View() 245 | case StateLanguage: 246 | if m.SelectedLanguage != "" { 247 | var str string 248 | if m.SelectedLanguage == string(languages.JavaScript) { 249 | str = "🎉 Awesome choice! JavaScript brings flexibility and dynamism to your project. Let's get coding! 🚀" 250 | } else if m.SelectedLanguage == string(languages.TypeScript) { 251 | str = "👍 Great pick! TypeScript adds type safety and robustness to your application. Time to build! 🏗️" 252 | } 253 | return quitTextStyle.Render(fmt.Sprintf(str)) 254 | } 255 | return m.LanguageList.View() 256 | case StatePackageManager: 257 | if m.SelectedPackageManager != "" { 258 | var str string 259 | if m.SelectedPackageManager == string(package_managers.NPM) { 260 | str = "🎉 Awesome choice! NPM is the world's most popular package manager. Let's get coding! 🚀" 261 | } else if m.SelectedPackageManager == string(package_managers.PNPM) { 262 | str = "👍 Great pick! PNPM is a fast, disk space efficient package manager. Time to build! 🏗️" 263 | } 264 | return quitTextStyle.Render(fmt.Sprintf(str)) 265 | } 266 | return m.PackageManagerList.View() 267 | case StateTestFramework: 268 | if m.SelectedTestFramework != "" { 269 | var str string 270 | if m.SelectedTestFramework == string(test_frameworks.SuperTestWithJest) { 271 | str = "🎉 Awesome choice! Supertest is best framework 🚀" 272 | } else if m.SelectedTestFramework == string(test_frameworks.MochaWithChaiHTTP) { 273 | str = "👍 Great pick! Mocha with Chai is powerful framework" 274 | } 275 | return quitTextStyle.Render(fmt.Sprintf(str)) 276 | } 277 | return m.TestFrameworkList.View() 278 | 279 | case StateLoggerLibrary: 280 | if m.SelectedLoggerLibrary != "" { 281 | var str string 282 | if m.SelectedLoggerLibrary == string(loggers.Winston) { 283 | str = "🎉 Awesome choice! Winston is best logger out there 🚀" 284 | } else if m.SelectedLoggerLibrary == string(loggers.Bunyan) { 285 | str = "👍 Great pick! Bunyan is powerful logger" 286 | } else if m.SelectedLoggerLibrary == string(loggers.Pino) { 287 | str = "👍 Great pick! Pino is powerful logger" 288 | } 289 | return quitTextStyle.Render(fmt.Sprintf(str)) 290 | } 291 | return m.LoggerLibraryList.View() 292 | 293 | case StateDatabase: 294 | if m.SelectedDatabase != "" { 295 | var str string 296 | if m.SelectedDatabase == string(databases.MongoDB) { 297 | str = "🎉 Awesome choice! MongoDB is powerful NoSQL database 🚀" 298 | } else if m.SelectedDatabase == string(databases.PostgreSQL) { 299 | str = "👍 Great pick! PostgreSQL is powerful relational database" 300 | } else if m.SelectedDatabase == string(databases.MySQL) { 301 | str = "👍 Great pick! MySQL is powerful database" 302 | } 303 | return quitTextStyle.Render(fmt.Sprintf(str)) 304 | } 305 | return m.DatabaseList.View() 306 | 307 | case StateORM: 308 | if m.SelectedORM != "" { 309 | var str string 310 | if m.SelectedORM == string(orms.Mongoose) { 311 | str = "🎉 Awesome choice! 🚀" 312 | } else if m.SelectedORM == string(orms.Sequelize) { 313 | str = "👍 Great pick!" 314 | } else if m.SelectedORM == string(orms.TypeORM) { 315 | str = "👍 Great pick!" 316 | } else if m.SelectedORM == string(orms.Prisma) { 317 | str = "👍 Great pick!" 318 | } else if m.SelectedORM == string(orms.None) { 319 | str = "🔥 Loving hardcore, yeah 💪" 320 | } 321 | return quitTextStyle.Render(fmt.Sprintf(str)) 322 | } 323 | return m.ORMList.View() 324 | 325 | case StateConfig: 326 | if m.SelectedConfig != "" { 327 | var str string 328 | if m.SelectedConfig == string(configs.ENV) { 329 | str = "🎉 Awesome choice! 🚀" 330 | } else if m.SelectedConfig == string(configs.JSON) { 331 | str = "👍 Great pick!" 332 | } else if m.SelectedConfig == string(configs.YAML) { 333 | str = "👍 Great pick!" 334 | } 335 | return quitTextStyle.Render(fmt.Sprintf(str)) 336 | } 337 | return m.ConfigList.View() 338 | 339 | case StateCodingStyle: 340 | if m.SelectedCodingStyle != "" { 341 | var str string 342 | if m.SelectedCodingStyle == string(coding_styles.Functional) { 343 | str = "🎉 Awesome choice! 🚀" 344 | } else if m.SelectedCodingStyle == string(coding_styles.ObjectOriented) { 345 | str = "👍 Great pick!" 346 | } 347 | return quitTextStyle.Render(fmt.Sprintf(str)) 348 | } 349 | return m.CodingStyleList.View() 350 | } 351 | return s 352 | } 353 | 354 | func InitialModel() CliModel { 355 | projectNameInput := textinput.New() 356 | projectNameInput.Placeholder = "my-expressify-app" 357 | return CliModel{ 358 | CurrentState: StateWelcome, 359 | ProjectNameInput: projectNameInput, 360 | WelcomeMessage: ` 361 | 🌟🚀 Welcome to Expressify! 🚀🌟 362 | We're thrilled to have you on board for a seamless and efficient Express.js application setup. 363 | Get ready to supercharge your development process with our intuitive CLI tool. 364 | 365 | Let's create something amazing together! 🎉👨‍💻👩‍💻 366 | 367 | Press Enter to begin... (or ESC to quit) 368 | `, 369 | LanguageList: languages.NewLanguageSelector().List, 370 | PackageManagerList: package_managers.NewPManagerSelector().List, 371 | TestFrameworkList: test_frameworks.NewTestFrameworkSelector().List, 372 | LoggerLibraryList: loggers.NewLoggerSelector().List, 373 | DatabaseList: databases.NewDatabaseSelector().List, 374 | ORMList: orms.NewORMSelector().List, 375 | ConfigList: configs.NewConfigSelector().List, 376 | CodingStyleList: coding_styles.NewCodingStyleSelector().List, 377 | } 378 | } 379 | -------------------------------------------------------------------------------- /internal/coding_styles/coding_styles.go: -------------------------------------------------------------------------------- 1 | package coding_styles 2 | 3 | import ( 4 | "github.com/charmbracelet/bubbles/list" 5 | "github.com/codersgyan/expressify/internal/selector" 6 | ) 7 | 8 | type CodingStyle string 9 | 10 | const ( 11 | Functional CodingStyle = "Functional" 12 | ObjectOriented CodingStyle = "Object Oriented" 13 | ) 14 | 15 | var codingStyles = []selector.Item{ 16 | selector.Item(Functional), 17 | selector.Item(ObjectOriented), 18 | } 19 | 20 | func NewCodingStyleSelector() *selector.Selector { 21 | var items []list.Item 22 | for _, item := range codingStyles { 23 | items = append(items, list.Item(item)) 24 | } 25 | return selector.NewSelector("\n😎 Choose a coding style", items) 26 | } 27 | -------------------------------------------------------------------------------- /internal/configs/configs.go: -------------------------------------------------------------------------------- 1 | package configs 2 | 3 | import ( 4 | "github.com/charmbracelet/bubbles/list" 5 | "github.com/codersgyan/expressify/internal/selector" 6 | ) 7 | 8 | type Config string 9 | 10 | const ( 11 | ENV Config = "ENV based" 12 | JSON Config = "File based (JSON)" 13 | YAML Config = "File based (YAML)" 14 | ) 15 | 16 | var configs = []selector.Item{ 17 | selector.Item(ENV), 18 | selector.Item(JSON), 19 | selector.Item(YAML), 20 | } 21 | 22 | func NewConfigSelector() *selector.Selector { 23 | var items []list.Item 24 | for _, item := range configs { 25 | items = append(items, list.Item(item)) 26 | } 27 | return selector.NewSelector("\n😎 Choose a config style", items) 28 | } 29 | -------------------------------------------------------------------------------- /internal/databases/databases.go: -------------------------------------------------------------------------------- 1 | package databases 2 | 3 | import ( 4 | "github.com/charmbracelet/bubbles/list" 5 | "github.com/codersgyan/expressify/internal/selector" 6 | ) 7 | 8 | type Database string 9 | 10 | const ( 11 | MongoDB Database = "MongoDB" 12 | PostgreSQL Database = "PostgreSQL" 13 | MySQL Database = "MySQL" 14 | ) 15 | 16 | var databases = []selector.Item{ 17 | selector.Item(MongoDB), 18 | selector.Item(PostgreSQL), 19 | selector.Item(MySQL), 20 | } 21 | 22 | func NewDatabaseSelector() *selector.Selector { 23 | var items []list.Item 24 | for _, item := range databases { 25 | items = append(items, list.Item(item)) 26 | } 27 | return selector.NewSelector("\n😎 Choose a database", items) 28 | } 29 | -------------------------------------------------------------------------------- /internal/languages/languages.go: -------------------------------------------------------------------------------- 1 | package languages 2 | 3 | import ( 4 | "github.com/charmbracelet/bubbles/list" 5 | "github.com/codersgyan/expressify/internal/selector" 6 | ) 7 | 8 | type Language string 9 | 10 | const ( 11 | JavaScript Language = "JavaScript" 12 | TypeScript Language = "TypeScript" 13 | ) 14 | 15 | var languageList = []selector.Item{ 16 | selector.Item(JavaScript), 17 | selector.Item(TypeScript), 18 | // selector.Item("JavaScript"), 19 | // selector.Item("TypeScript"), 20 | } 21 | 22 | func NewLanguageSelector() *selector.Selector { 23 | var items []list.Item 24 | for _, item := range languageList { 25 | items = append(items, list.Item(item)) 26 | } 27 | return selector.NewSelector("\n😎 Choose a language", items) 28 | } 29 | -------------------------------------------------------------------------------- /internal/loggers/loggers.go: -------------------------------------------------------------------------------- 1 | package loggers 2 | 3 | import ( 4 | "github.com/charmbracelet/bubbles/list" 5 | "github.com/codersgyan/expressify/internal/selector" 6 | ) 7 | 8 | type Logger string 9 | 10 | const ( 11 | Winston Logger = "Winston" 12 | Bunyan Logger = "Bunyan" 13 | Pino Logger = "Pino" 14 | ) 15 | 16 | var loggers = []selector.Item{ 17 | selector.Item(Winston), 18 | selector.Item(Bunyan), 19 | selector.Item(Pino), 20 | } 21 | 22 | func NewLoggerSelector() *selector.Selector { 23 | var items []list.Item 24 | for _, item := range loggers { 25 | items = append(items, list.Item(item)) 26 | } 27 | return selector.NewSelector("\n😎 Choose a logger", items) 28 | } 29 | -------------------------------------------------------------------------------- /internal/orms/orms.go: -------------------------------------------------------------------------------- 1 | package orms 2 | 3 | import ( 4 | "github.com/charmbracelet/bubbles/list" 5 | "github.com/codersgyan/expressify/internal/selector" 6 | ) 7 | 8 | type ORM string 9 | 10 | const ( 11 | Mongoose ORM = "Mongoose" 12 | Sequelize ORM = "Sequelize" 13 | TypeORM ORM = "TypeORM" 14 | Prisma ORM = "Prisma" 15 | None ORM = "None" 16 | ) 17 | 18 | var orms = []selector.Item{ 19 | selector.Item(Mongoose), 20 | selector.Item(Sequelize), 21 | selector.Item(TypeORM), 22 | selector.Item(Prisma), 23 | selector.Item(None), 24 | } 25 | 26 | func NewORMSelector() *selector.Selector { 27 | var items []list.Item 28 | for _, item := range orms { 29 | items = append(items, list.Item(item)) 30 | } 31 | return selector.NewSelector("\n😎 Choose an ORM", items) 32 | } 33 | -------------------------------------------------------------------------------- /internal/package_managers/package_managers.go: -------------------------------------------------------------------------------- 1 | package package_managers 2 | 3 | import ( 4 | "github.com/charmbracelet/bubbles/list" 5 | "github.com/codersgyan/expressify/internal/selector" 6 | ) 7 | 8 | type PackageManager string 9 | 10 | const ( 11 | NPM PackageManager = "NPM" 12 | PNPM PackageManager = "PNPM" 13 | ) 14 | 15 | var packageManagers = []selector.Item{ 16 | selector.Item(NPM), 17 | selector.Item(PNPM), 18 | } 19 | 20 | func NewPManagerSelector() *selector.Selector { 21 | var items []list.Item 22 | for _, item := range packageManagers { 23 | items = append(items, list.Item(item)) 24 | } 25 | return selector.NewSelector("\n😎 Choose a package manager", items) 26 | } 27 | -------------------------------------------------------------------------------- /internal/selector/selector.go: -------------------------------------------------------------------------------- 1 | package selector 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "strings" 7 | 8 | "github.com/charmbracelet/bubbles/list" 9 | tea "github.com/charmbracelet/bubbletea" 10 | "github.com/charmbracelet/lipgloss" 11 | ) 12 | 13 | type Item string 14 | 15 | func (i Item) FilterValue() string { return "" } 16 | 17 | type Selector struct { 18 | List list.Model 19 | } 20 | 21 | func NewSelector(title string, items []list.Item) *Selector { 22 | titleStyle := lipgloss.NewStyle().MarginLeft(2) 23 | paginationStyle := list.DefaultStyles().PaginationStyle.PaddingLeft(4) 24 | helpStyle := list.DefaultStyles().HelpStyle.PaddingLeft(4).PaddingBottom(1) 25 | 26 | l := list.New(items, itemDelegate{}, defaultWidth, listHeight) 27 | l.Title = title 28 | l.SetShowStatusBar(false) 29 | l.SetFilteringEnabled(false) 30 | l.Styles.Title = titleStyle 31 | l.Styles.PaginationStyle = paginationStyle 32 | l.Styles.HelpStyle = helpStyle 33 | 34 | return &Selector{ 35 | List: l, 36 | } 37 | } 38 | 39 | const listHeight = 14 40 | const defaultWidth = 100 41 | 42 | var ( 43 | titleStyle = lipgloss.NewStyle().MarginLeft(2) 44 | itemStyle = lipgloss.NewStyle().PaddingLeft(4) 45 | selectedItemStyle = lipgloss.NewStyle().PaddingLeft(2).Foreground(lipgloss.Color("170")) 46 | paginationStyle = list.DefaultStyles().PaginationStyle.PaddingLeft(4) 47 | helpStyle = list.DefaultStyles().HelpStyle.PaddingLeft(4).PaddingBottom(1) 48 | ) 49 | 50 | type itemDelegate struct{} 51 | 52 | func (d itemDelegate) Height() int { return 1 } 53 | func (d itemDelegate) Spacing() int { return 0 } 54 | func (d itemDelegate) Update(_ tea.Msg, _ *list.Model) tea.Cmd { return nil } 55 | func (d itemDelegate) Render(w io.Writer, m list.Model, index int, listItem list.Item) { 56 | i, ok := listItem.(Item) 57 | if !ok { 58 | return 59 | } 60 | 61 | str := fmt.Sprintf(" %d. %s", index+1, i) 62 | 63 | fn := itemStyle.Render 64 | if index == m.Index() { 65 | fn = func(s ...string) string { 66 | return selectedItemStyle.Render("👉" + strings.Join(s, " ")) 67 | } 68 | } 69 | 70 | fmt.Fprint(w, fn(str)) 71 | } 72 | -------------------------------------------------------------------------------- /internal/structure/structure.go: -------------------------------------------------------------------------------- 1 | package structure 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "os" 7 | "path/filepath" 8 | 9 | "github.com/codersgyan/expressify/internal/languages" 10 | ) 11 | 12 | func CreateBaseFileStructure(projectName string, language string) error { 13 | cwd, err := os.Getwd() 14 | fmt.Println("Current Working Directory:", cwd) 15 | 16 | if err != nil { 17 | return fmt.Errorf("unable to get current working directory: %w", err) 18 | } 19 | 20 | projectPath := filepath.Join(cwd, ".expressify", projectName) 21 | if _, err := os.Stat(projectPath); !os.IsNotExist(err) { 22 | return fmt.Errorf("folder \"%s\" already exists or cannot check existence", projectPath) 23 | } 24 | mkdirProjectDirErr := os.Mkdir(projectPath, 0755) // 0755 is commonly used permissions 25 | if mkdirProjectDirErr != nil { 26 | return fmt.Errorf("unable to create project folder: %w", err) 27 | } 28 | 29 | var languageWisePath string 30 | if language == string(languages.JavaScript) { 31 | languageWisePath = "jsbase" 32 | } else { 33 | languageWisePath = "tsbase" 34 | } 35 | 36 | srcPath := filepath.Join(cwd, ".templates", languageWisePath) 37 | dstPath := filepath.Join(projectPath) 38 | 39 | cpErr := CopyDir(srcPath, dstPath) 40 | if cpErr != nil { 41 | fmt.Printf("Error copying directory: %s\n", cpErr) 42 | } else { 43 | fmt.Println("Directory copied successfully.") 44 | } 45 | return nil 46 | } 47 | 48 | func CopyFile(src, dst string) error { 49 | sourceFile, err := os.Open(src) 50 | if err != nil { 51 | return err 52 | } 53 | defer sourceFile.Close() 54 | 55 | destFile, err := os.Create(dst) 56 | if err != nil { 57 | return err 58 | } 59 | defer destFile.Close() 60 | 61 | _, err = io.Copy(destFile, sourceFile) 62 | return err 63 | } 64 | 65 | // copyDir recursively copies a directory from src to dst. 66 | func CopyDir(src, dst string) error { 67 | // Get properties of source dir 68 | info, err := os.Stat(src) 69 | if err != nil { 70 | return err 71 | } 72 | 73 | // Create the destination directory 74 | err = os.MkdirAll(dst, info.Mode()) 75 | if err != nil { 76 | return err 77 | } 78 | 79 | // Read directory contents 80 | entries, err := os.ReadDir(src) 81 | if err != nil { 82 | return err 83 | } 84 | 85 | for _, entry := range entries { 86 | srcPath := filepath.Join(src, entry.Name()) 87 | dstPath := filepath.Join(dst, entry.Name()) 88 | 89 | if entry.IsDir() { 90 | // Recursive call for directories 91 | err = CopyDir(srcPath, dstPath) 92 | if err != nil { 93 | return err 94 | } 95 | } else { 96 | // Copy files 97 | err = CopyFile(srcPath, dstPath) 98 | if err != nil { 99 | return err 100 | } 101 | } 102 | } 103 | return nil 104 | } 105 | -------------------------------------------------------------------------------- /internal/test_frameworks/test_frameworks.go: -------------------------------------------------------------------------------- 1 | package test_frameworks 2 | 3 | import ( 4 | "github.com/charmbracelet/bubbles/list" 5 | "github.com/codersgyan/expressify/internal/selector" 6 | ) 7 | 8 | type TestFramwork string 9 | 10 | const ( 11 | SuperTestWithJest TestFramwork = "SuperTest with Jest" 12 | MochaWithChaiHTTP TestFramwork = "Mocha with Chai HTTP" 13 | ) 14 | 15 | var testFrameworks = []selector.Item{ 16 | selector.Item(SuperTestWithJest), 17 | selector.Item(MochaWithChaiHTTP), 18 | } 19 | 20 | func NewTestFrameworkSelector() *selector.Selector { 21 | var items []list.Item 22 | for _, item := range testFrameworks { 23 | items = append(items, list.Item(item)) 24 | } 25 | return selector.NewSelector("\n😎 Choose a test framework", items) 26 | } 27 | --------------------------------------------------------------------------------