From 11e89270563f81ea7e7b22c80867b8448fc19be6 Mon Sep 17 00:00:00 2001 From: kushvinth Date: Wed, 14 Jan 2026 22:56:16 +0530 Subject: [PATCH 1/2] feat(added): license generation --- cmd/init.go | 89 +++++++++++++++++++++++++++++- internal/app.d.go | 3 + internal/app.go | 15 +++++ internal/license/license.go | 106 ++++++++++++++++++++++++++++++++++++ 4 files changed, 212 insertions(+), 1 deletion(-) create mode 100644 internal/license/license.go diff --git a/cmd/init.go b/cmd/init.go index b1ab94c..8c1bf35 100644 --- a/cmd/init.go +++ b/cmd/init.go @@ -4,12 +4,14 @@ import ( "errors" "fmt" "os" + "time" "github.com/charmbracelet/huh" "github.com/charmbracelet/huh/spinner" "github.com/spf13/cobra" app "github.com/vg006/vgo/internal" asset "github.com/vg006/vgo/internal/assets" + "github.com/vg006/vgo/internal/license" ) var initCmd = &cobra.Command{ @@ -18,6 +20,55 @@ var initCmd = &cobra.Command{ Run: func(cmd *cobra.Command, args []string) { var p app.Project accessible, _ := cmd.Flags().GetBool("accessible") + + // Fetch licenses from GitHub API + var licenses []license.GitHubLicense + var licenseOptions []huh.Option[string] + + err := spinner. + New(). + Title("Loading licenses from GitHub..."). + Action(func() { + var fetchErr error + licenses, fetchErr = license.FetchLicenses() + if fetchErr != nil { + // If fetch fails, provide some basic options + licenses = []license.GitHubLicense{ + {Key: "mit", Name: "MIT License"}, + {Key: "apache-2.0", Name: "Apache License 2.0"}, + {Key: "gpl-3.0", Name: "GNU General Public License v3.0"}, + } + } + // Convert licenses to huh options + licenseOptions = make([]huh.Option[string], 0, len(licenses)+1) + licenseOptions = append(licenseOptions, huh.NewOption("None", "none")) + for _, lic := range licenses { + licenseOptions = append(licenseOptions, huh.NewOption(lic.Name, lic.Key)) + } + }). + Style(asset.Text). + Accessible(accessible). + Run() + + if err != nil { + // Fallback to basic options if spinner fails + licenseOptions = []huh.Option[string]{ + huh.NewOption("None", "none"), + huh.NewOption("MIT License", "mit"), + huh.NewOption("Apache License 2.0", "apache-2.0"), + huh.NewOption("GNU General Public License v3.0", "gpl-3.0"), + } + } + + // Get git username for default author + gitUsername := license.GetGitUsername() + if gitUsername == "" { + gitUsername = "Your Name" + } + + // Set default year + p.Year = fmt.Sprintf("%d", time.Now().Year()) + form := huh.NewForm( huh.NewGroup( huh.NewInput(). @@ -87,13 +138,49 @@ var initCmd = &cobra.Command{ huh.NewOption("MongoDB", "mongodb"), ), ), + huh.NewGroup( + huh.NewSelect[string](). + Value(&p.License). + Title("License"). + Description("Select a license for the project"). + Options(licenseOptions...), + ), + huh.NewGroup( + huh.NewInput(). + Value(&p.Author). + Title("Author"). + Description("Enter the author name for the license"). + Placeholder(gitUsername). + Validate(func(s string) error { + if s == "" && p.License != "none" { + p.Author = gitUsername + } + return nil + }), + ).WithHideFunc(func() bool { + return p.License == "none" + }), + huh.NewGroup( + huh.NewInput(). + Value(&p.Year). + Title("Year"). + Description("Enter the year for the license"). + Validate(func(s string) error { + if s == "" { + p.Year = fmt.Sprintf("%d", time.Now().Year()) + } + return nil + }), + ).WithHideFunc(func() bool { + return p.License == "none" + }), ). WithAccessible(accessible). WithTheme(asset.SetTheme()) fmt.Println(asset.VgoLogo) - err := form.Run() + err = form.Run() if err != nil { fmt.Println(asset.Text.Foreground(asset.Red). Render(fmt.Sprintf("%s Hey! Why stopped?", asset.EmojiConfused))) diff --git a/internal/app.d.go b/internal/app.d.go index bf9d8d4..a8b5322 100644 --- a/internal/app.d.go +++ b/internal/app.d.go @@ -8,6 +8,9 @@ type Project struct { ModName string FrameWork string Database string + License string + Author string + Year string } type AppError struct { diff --git a/internal/app.go b/internal/app.go index 9cb3016..38787b3 100644 --- a/internal/app.go +++ b/internal/app.go @@ -7,6 +7,7 @@ import ( "sync" "text/template" + "github.com/vg006/vgo/internal/license" tmpl "github.com/vg006/vgo/internal/templates" ) @@ -83,6 +84,20 @@ func (p *Project) ScaffoldProject() error { errChan <- err } + // Creates LICENSE file if a license is selected + if p.License != "" && p.License != "none" { + licenseContent, err := license.GetLicenseContent(p.License) + if err != nil { + errChan <- err + } else { + renderedLicense := license.RenderLicense(licenseContent, p.Author, p.Year) + err = os.WriteFile("LICENSE", []byte(renderedLicense), 0644) + if err != nil { + errChan <- err + } + } + } + // Creates other project directories // ----------------------------------------------------------------- // Creates the cmd directory diff --git a/internal/license/license.go b/internal/license/license.go new file mode 100644 index 0000000..ec6f887 --- /dev/null +++ b/internal/license/license.go @@ -0,0 +1,106 @@ +package license + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "os/exec" + "strings" + "time" +) + +type GitHubLicense struct { + Key string `json:"key"` + Name string `json:"name"` + Body string `json:"body"` +} + +// GetGitUsername retrieves the git user.name from global config +func GetGitUsername() string { + cmd := exec.Command("git", "config", "--global", "--get", "user.name") + output, err := cmd.Output() + if err != nil { + return "" + } + return strings.TrimSpace(string(output)) +} + +// FetchLicenses retrieves the list of available licenses from GitHub API +func FetchLicenses() ([]GitHubLicense, error) { + client := &http.Client{ + Timeout: 10 * time.Second, + } + + resp, err := client.Get("https://api.github.com/licenses") + if err != nil { + return nil, fmt.Errorf("failed to fetch licenses: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("github API returned status: %d", resp.StatusCode) + } + + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("failed to read response: %w", err) + } + + var licenses []GitHubLicense + err = json.Unmarshal(body, &licenses) + if err != nil { + return nil, fmt.Errorf("failed to parse licenses: %w", err) + } + + return licenses, nil +} + +// GetLicenseContent retrieves the full license content from GitHub API +func GetLicenseContent(key string) (string, error) { + client := &http.Client{ + Timeout: 10 * time.Second, + } + + url := fmt.Sprintf("https://api.github.com/licenses/%s", key) + resp, err := client.Get(url) + if err != nil { + return "", fmt.Errorf("failed to fetch license content: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return "", fmt.Errorf("github API returned status: %d", resp.StatusCode) + } + + body, err := io.ReadAll(resp.Body) + if err != nil { + return "", fmt.Errorf("failed to read response: %w", err) + } + + var license GitHubLicense + err = json.Unmarshal(body, &license) + if err != nil { + return "", fmt.Errorf("failed to parse license: %w", err) + } + + return license.Body, nil +} + +// RenderLicense replaces placeholders in license content with actual values +func RenderLicense(content, author, year string) string { + replacements := map[string]string{ + "[year]": year, + "[fullname]": author, + "[yyyy]": year, + "[name of copyright owner]": author, + "[NAME OF COPYRIGHT OWNER]": author, + } + + result := content + for placeholder, value := range replacements { + result = strings.ReplaceAll(result, placeholder, value) + } + + return result +} From c38b3e7abe0f07affe2cc8d3bc194681624d9a78 Mon Sep 17 00:00:00 2001 From: vg006 Date: Thu, 15 Jan 2026 22:43:23 +0530 Subject: [PATCH 2/2] =?UTF-8?q?=F0=9F=93=9D=20docs:=20Add=20attribution=20?= =?UTF-8?q?to=20project=20lic?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a35fb05..70cbae8 100644 --- a/README.md +++ b/README.md @@ -125,8 +125,8 @@ If you encounter any issues or have questions, feel free to open an issue on [Gi ## 🙏 Acknowledgments Special thanks to, - - the Go community, for their invaluable resources and inspiration. - [MelkeyDev](https://github.com/MelkeyDev), for the inspiration to build this tool. - [Cobra CLI](https://github.com/spf13/cobra), for helping to build command-line interface. - [Charm\_](https://github.com/charmbracelet), for building beautiful and interactive CLI components. +- [lic](https://github.com/kushvinth/lic), for inspiring the license generation feature.