Skip to content

Commit fc1b87e

Browse files
committed
initial commit for go scaffold
1 parent 66f83aa commit fc1b87e

File tree

25 files changed

+538
-1
lines changed

25 files changed

+538
-1
lines changed

.github/workflows/ci.yml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
name: Go
2+
3+
on:
4+
push:
5+
branches: [ main ]
6+
pull_request:
7+
branches: [ main ]
8+
9+
jobs:
10+
build-and-test:
11+
runs-on: ubuntu-latest
12+
steps:
13+
- name: Checkout code
14+
uses: actions/checkout@v4
15+
16+
- name: Set up Go
17+
uses: actions/setup-go@v5
18+
with:
19+
go-version: '1.22' # Adjust to match your go.mod
20+
21+
- name: Install dependencies
22+
run: go mod tidy
23+
24+
- name: Run tests
25+
run: go test -v ./...

.gitignore

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# Compiled Go binaries
2+
# Replace 'bootstrapper' with your actual executable name
3+
bootstrap
4+
c.out
5+
6+
# Project folders generated by your tool
7+
# This pattern will ignore any folders starting with "new_project"
8+
new_project*/
9+
10+
# IDE and OS specific files
11+
.vscode/
12+
.idea/
13+
.DS_Store
14+
*.log

LICENSE

Whitespace-only changes.

README.md

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,20 @@
1-
# bootstrapper
1+
# Go Bootstrapper 🐹⚡
2+
> A scaffold CLI project for Golang project enabling creation of new project without worring about installing dependencies, setting up linters
3+
just use CLI tools to generate the pre-configured templates.
4+
5+
---
6+
7+
## ✨ Features
8+
- 🏗 **create new golang projects instantly**
9+
-**Framework-ready** templates
10+
- 📂 **Standardized structure**: `cmd/`, `internal/`
11+
- 🔮 Designed to be **extensible** (bring your own templates)
12+
13+
---
14+
15+
## 📦 Installation
16+
17+
```bash
18+
go install github.com/upsaurav12/bootstrap@latest
19+
20+

cmd/new.go

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
/*
2+
3+
Copyright © 2025 NAME HERE <EMAIL ADDRESS>
4+
5+
*/
6+
7+
package cmd
8+
9+
import (
10+
"fmt"
11+
"io"
12+
"io/fs"
13+
"os"
14+
"path/filepath"
15+
"strings"
16+
"text/template"
17+
18+
"github.com/spf13/cobra"
19+
"github.com/upsaurav12/bootstrap/templates"
20+
)
21+
22+
// newCmd represents the new command
23+
var newCmd = &cobra.Command{
24+
Use: "new",
25+
Short: "command for creating a new project.",
26+
Long: `command for creating a new project.`,
27+
Run: func(cmd *cobra.Command, args []string) {
28+
// Check if the project name is provided
29+
if len(args) < 1 {
30+
fmt.Fprintln(cmd.OutOrStdout(), "Error: project name is required")
31+
return
32+
}
33+
34+
// Get the template flag value from the command context
35+
tmpl, _ := cmd.Flags().GetString("type")
36+
37+
// Get the project name (first argument)
38+
dirName := args[0]
39+
40+
// Create the new project
41+
createNewProject(dirName, projectRouter, tmpl, cmd.OutOrStdout())
42+
},
43+
}
44+
45+
var projectType string
46+
var projectPort string
47+
var projectRouter string
48+
49+
func init() {
50+
// Add the new command to the rootCmd
51+
rootCmd.AddCommand(newCmd)
52+
53+
// Define the --template flag for this command
54+
newCmd.Flags().StringVar(&projectType, "type", "", "type of the project")
55+
newCmd.Flags().StringVar(&projectPort, "port", "", "port of the project")
56+
newCmd.Flags().StringVar(&projectRouter, "router", "", "router of the project")
57+
}
58+
59+
func createNewProject(projectName string, projectRouter string, template string, out io.Writer) {
60+
err := os.Mkdir(projectName, 0755)
61+
if err != nil {
62+
fmt.Fprintf(out, "Error creating directory: %v\n", err)
63+
return
64+
}
65+
// Print the template that was passed
66+
67+
// Always add README + Makefile from common
68+
renderTemplateDir("common", projectName, TemplateData{
69+
ModuleName: projectName,
70+
PortName: projectPort,
71+
})
72+
73+
renderTemplateDir("rest"+"/"+projectRouter, projectName, TemplateData{
74+
ModuleName: projectName,
75+
PortName: projectPort,
76+
})
77+
78+
if err != nil {
79+
fmt.Fprintf(out, "Error rendering templates: %v\n", err)
80+
return
81+
}
82+
83+
fmt.Fprintf(out, "Created '%s' successfully\n", projectName)
84+
}
85+
86+
type TemplateData struct {
87+
ModuleName string
88+
PortName string
89+
}
90+
91+
func renderTemplateDir(templatePath, destinationPath string, data TemplateData) error {
92+
return fs.WalkDir(templates.FS, templatePath, func(path string, d fs.DirEntry, err error) error {
93+
if err != nil {
94+
return err
95+
}
96+
97+
// Compute relative path (remove the base templatePath)
98+
relPath, _ := filepath.Rel(templatePath, path)
99+
targetPath := filepath.Join(destinationPath, strings.TrimSuffix(relPath, ".tmpl"))
100+
101+
if d.IsDir() {
102+
return os.MkdirAll(targetPath, 0755)
103+
}
104+
105+
// ✅ Important: use full `path` for ReadFile
106+
content, err := templates.FS.ReadFile(path)
107+
if err != nil {
108+
return err
109+
}
110+
111+
// Parse template
112+
tmpl, err := template.New(filepath.Base(path)).Parse(string(content))
113+
if err != nil {
114+
return err
115+
}
116+
117+
// Write file
118+
outFile, err := os.Create(targetPath)
119+
if err != nil {
120+
return err
121+
}
122+
defer outFile.Close()
123+
124+
return tmpl.Execute(outFile, data)
125+
})
126+
}

cmd/new_test.go

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package cmd
2+
3+
import (
4+
"bytes"
5+
"fmt"
6+
"os"
7+
"path/filepath"
8+
"testing"
9+
10+
"github.com/stretchr/testify/assert"
11+
)
12+
13+
func TestCreateNewProject_Success(t *testing.T) {
14+
tempDir := t.TempDir()
15+
projectName := "test-project"
16+
fullPath := filepath.Join(tempDir, projectName)
17+
18+
// Change to temp directory to create projectName relative to it
19+
oldDir, err := os.Getwd()
20+
assert.NoError(t, err, "Failed to get working directory")
21+
defer os.Chdir(oldDir)
22+
err = os.Chdir(tempDir)
23+
assert.NoError(t, err, "Failed to change to temp directory")
24+
25+
var out bytes.Buffer
26+
createNewProject(projectName, "gin", "go", &out)
27+
28+
// Check if directory was created
29+
_, err = os.Stat(fullPath)
30+
assert.NoError(t, err, "Expected project directory to be created")
31+
32+
// Check output
33+
expected := fmt.Sprintf("Created '%s' successfully\n", projectName)
34+
fmt.Println(out.String())
35+
assert.Equal(t, expected, out.String(), "Unexpected output")
36+
}
37+
38+
/*
39+
40+
func TestCreateNewProject_DirectoryAlreadyExists(t *testing.T) {
41+
tempDir := t.TempDir()
42+
projectName := "test-project"
43+
fullPath := filepath.Join(tempDir, projectName)
44+
45+
err := os.Mkdir(fullPath, 0755)
46+
assert.NoError(t, err, "Failed to set-up directory")
47+
var out bytes.Buffer
48+
createNewProject(projectName, "go", &out)
49+
50+
_, err = os.Stat(fullPath)
51+
assert.NoError(t, err, "Expected directory to still exists")
52+
53+
assert.Contains(t, out.String(), "Error creating directory", "Expected error message")
54+
}*/
55+
56+
func TestCreateNewProject_InvalidPath(t *testing.T) {
57+
tempDir := t.TempDir()
58+
projectName := "invalid\000name"
59+
invalidPath := filepath.Join(tempDir, projectName)
60+
61+
var out bytes.Buffer
62+
createNewProject(projectName, "gin", "go ", &out)
63+
64+
_, err := os.Stat(invalidPath)
65+
assert.Error(t, err, "Expected no directory to be created")
66+
67+
assert.Contains(t, out.String(), "Error creating directory", "Expected error message")
68+
}

cmd/root.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
Copyright © 2025 NAME HERE <EMAIL ADDRESS>
3+
*/
4+
package cmd
5+
6+
import (
7+
"os"
8+
9+
"github.com/spf13/cobra"
10+
)
11+
12+
// rootCmd represents the base command when called without any subcommands
13+
var rootCmd = &cobra.Command{
14+
Use: "bootstrap",
15+
Short: "CLI project for building building your golang project faster.",
16+
Long: `CLI project for building building your golang project faster.`,
17+
}
18+
19+
func Execute() {
20+
err := rootCmd.Execute()
21+
if err != nil {
22+
os.Exit(1)
23+
}
24+
}
25+
26+
func init() {
27+
rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
28+
}

cmd/root_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
package cmd

go.mod

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
module github.com/upsaurav12/bootstrap
2+
3+
go 1.23.6
4+
5+
require (
6+
github.com/davecgh/go-spew v1.1.1 // indirect
7+
github.com/inconshreveable/mousetrap v1.1.0 // indirect
8+
github.com/pmezard/go-difflib v1.0.0 // indirect
9+
github.com/spf13/cobra v1.9.1 // indirect
10+
github.com/spf13/pflag v1.0.6 // indirect
11+
github.com/stretchr/testify v1.10.0 // indirect
12+
gopkg.in/yaml.v3 v3.0.1 // indirect
13+
)

go.sum

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
2+
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
3+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
4+
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
5+
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
6+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
7+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
8+
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
9+
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
10+
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
11+
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
12+
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
13+
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
14+
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
15+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
16+
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
17+
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

0 commit comments

Comments
 (0)