diff --git a/config/config.go b/config/config.go index 5608b77..7b79092 100644 --- a/config/config.go +++ b/config/config.go @@ -3,19 +3,10 @@ package config import ( "fmt" "kode-starter/utils" - "kode-starter/version" - "os" "github.com/spf13/cobra" ) -// var version string = "0.0.1" -// var fullVersion string = version.FullVersion() - -// var fullVersion, err = version.ReadVersionFile() - -// var fullVersion string - var author string = "Bruno Charest" var modifDate string = "2023-06-12" @@ -26,22 +17,26 @@ var verboseFlag bool = false var jsonFlag bool = false +// variables for URL var urlBase string = "https://git.bcmaison.cf" var urlApiBase string = urlBase + "/api/v1" var urlApiOrgs string = urlApiBase + "/orgs" -func GetVersion() *version.Version { +// variables for list command +var listOrganization string - fullVersion, err := version.NewVersionFromString(version.VersionString) - if err != nil { - fmt.Println("Error:", err) - os.Exit(1) - } +var listOrganizationFlag bool = false - return fullVersion -} +// variables for create command +var createName string + +var createOrganisation string + +var createDescription string + +var createPrivatge string func GetAuthor() string { return author @@ -87,7 +82,63 @@ func SetVerboseFlag(flag bool) { verboseFlag = flag } -func SetInformations(cmd *cobra.Command, args []string, userToken string) { +// functions for list command +// -------------------------- +func SetListOrganisation(name string) { + listOrganization = name +} + +func GetListOrganisation() string { + return listOrganization +} + +func SetListOrganisationFlag(flag bool) { + listOrganizationFlag = flag +} + +func GetListOrganisationFlag() bool { + return listOrganizationFlag +} + +// functions for create command +// ---------------------------- +func GetCreateName() string { + return createName +} + +func SetCreateName(name string) { + createName = name +} + +func GetCreateOrganisation() string { + return createOrganisation +} + +func SetCeateOrganisation(organisation string) { + createOrganisation = organisation +} + +func GetCreateDescription() string { + return createDescription +} + +func SetCeateDescription(description string) { + createDescription = description +} + +func GetCreatePrivate() string { + return createPrivatge +} + +func SetCreatePrivate(private string) { + createPrivatge = private +} + +// SetInformations - set informations from command line +func SetInformations(cmd *cobra.Command, args []string) { + + // fmt.Println("SetInformations - start:") + // Check if the verbose flag is set if cmd.Flags().Changed("verbose") || cmd.Flags().Changed("v") { SetVerboseFlag(true) @@ -102,10 +153,49 @@ func SetInformations(cmd *cobra.Command, args []string, userToken string) { SetJsonFlag(false) } + // check if the cmd come from list or create + if GetVerboseFlag() { + if cmd.Name() == "list" { + fmt.Println("SetInformations - progress list option:") + fmt.Println("SetInformations - org:" + cmd.Flag("org").Value.String()) + fmt.Println("SetInformations - cmd Usertoken:" + cmd.Flag("token").Value.String()) + } + if cmd.Name() == "create" { + fmt.Println("SetInformations - progress create option:") + fmt.Println("SetInformations - name:" + cmd.Flag("name").Value.String()) + fmt.Println("SetInformations - description:" + cmd.Flag("desc").Value.String()) + fmt.Println("SetInformations - private:" + cmd.Flag("private").Value.String()) + fmt.Println("SetInformations - cmd Usertoken:" + cmd.Flag("token").Value.String()) + fmt.Println("SetInformations - org:" + cmd.Flag("org").Value.String()) + } + } + + if cmd.Flags().Changed("name") || cmd.Flags().Changed("n") { + SetCreateName(cmd.Flag("name").Value.String()) + } + + if cmd.Flags().Changed("org") || cmd.Flags().Changed("o") { + if cmd.Name() == "list" { + SetListOrganisation(cmd.Flag("org").Value.String()) + SetListOrganisationFlag(true) + } else { + SetCeateOrganisation(cmd.Flag("org").Value.String()) + } + + } + + if cmd.Flags().Changed("desc") || cmd.Flags().Changed("d") { + SetCeateDescription(cmd.Flag("desc").Value.String()) + } + + if cmd.Flags().Changed("private") || cmd.Flags().Changed("p") { + SetCreatePrivate(cmd.Flag("private").Value.String()) + } + // Check if token come from flag or env if cmd.Flags().Changed("token") || cmd.Flags().Changed("t") { - if utils.IsValidToken(userToken) { - SetToken(userToken) + if utils.IsValidToken(cmd.Flag("token").Value.String()) { + SetToken(cmd.Flag("token").Value.String()) } else { utils.ExitWithError(10, "Invalid token, format must be 40 characters UUID.") } diff --git a/create/create.go b/create/create.go index 48d62ae..15e66f0 100644 --- a/create/create.go +++ b/create/create.go @@ -1,2 +1,138 @@ package create +import ( + "bytes" + "encoding/json" + "fmt" + "kode-starter/config" + "kode-starter/structures" + "kode-starter/utils" + "log" + "net/http" + "os" + "os/exec" + + "github.com/joho/godotenv" + "github.com/spf13/cobra" +) + +var token, org, name, private, description string + +var CreateCmd = &cobra.Command{ + Use: "create", + Short: "Create Github project", + Long: `A simple CLI app to create a startup project on Github`, + Run: func(cmd *cobra.Command, args []string) { + + config.SetInformations(cmd, args) + + err := CreateProject() + if err != nil { + fmt.Println("Error:", err) + return + } + }, +} + +func CreateProject() error { + + godotenv.Load() + + fmt.Printf("Token: %s\n", config.GetToken()) + fmt.Printf("Name: %s\n", config.GetCreateName()) + fmt.Printf("Description: %s\n", config.GetCreateDescription()) + fmt.Printf("Private: %s\n", config.GetCreatePrivate()) + fmt.Printf("Org: %s\n", config.GetCreateOrganisation()) + + // Construct POST data + data := structures.Project{ + Name: name, + Description: description, + Private: private == "true", + } + + data.Name = config.GetCreateName() + data.Description = config.GetCreateDescription() + + fmt.Println("Structure Name:" + data.Name) + + // Make API request to create Github project + jsonData, _ := json.Marshal(data) + + req, err := http.NewRequest("POST", + fmt.Sprintf("%s/%s/repos", config.GetUrlApiOrgs(), config.GetCreateOrganisation()), + bytes.NewBuffer(jsonData)) + req.Header.Set("Authorization", "token "+config.GetToken()) + req.Header.Set("Content-Type", "application/json") + client := &http.Client{} + res, err := client.Do(req) + if err != nil { + log.Fatalln(err) + } + + var dataReceived structures.GitOrgsRepoResponse + err = json.NewDecoder(res.Body).Decode(&dataReceived) + if err != nil { + log.Fatal(err) + } + fmt.Println(dataReceived) + fmt.Printf("==> Created project '%s' URL: '%s'\n", name, dataReceived.CloneURL) + + defer res.Body.Close() + + // Create directory of the project + fmt.Println("==> Creating directory...") + utils.CreateDir(name) + fmt.Println("==> Created directory") + + // Change to project directory + err = os.Chdir(name) + if err != nil { + log.Fatal(err) + } + + isCreated := CreateReadme(name, description, "BC", "") + if isCreated { + fmt.Println("==> Created README.md") + } + + // Git commands + gitCmd := exec.Command("git", "init") + err = gitCmd.Run() + if err != nil { + log.Fatal(err) + } else { + fmt.Printf("==> Initialized empty Git repository in %s\n", name) + } + + gitCmd = exec.Command("git", "checkout", "-b", "main") + err = gitCmd.Run() + if err != nil { + log.Fatal(err) + } else { + fmt.Println("==> Switched to a new branch 'main'") + } + + gitCmd = exec.Command("git", "add", "-A") + err = gitCmd.Run() + if err != nil { + log.Fatal(err) + } else { + fmt.Println("==> Switched to a new branch 'main'") + } + + gitCmd = exec.Command("git", "commit", "-m", "first commit from project creator !") + err = gitCmd.Run() + if err != nil { + log.Fatal(err) + } else { + fmt.Println("==> first commit from Kode-Creator !") + } + + // Get project info from API response + var project structures.Project + json.NewDecoder(res.Body).Decode(&project) + fmt.Println(project) + + return nil +} diff --git a/create/readme.go b/create/readme.go new file mode 100644 index 0000000..08e3ca7 --- /dev/null +++ b/create/readme.go @@ -0,0 +1,39 @@ +package create + +import ( + "bufio" + "log" + "os" + "strings" +) + +func CreateReadme(projectName, description, author, url string) bool { + file, err := os.Open("../readme.template") + if err != nil { + log.Fatal(err) + } + defer file.Close() + + template := "" + scanner := bufio.NewScanner(file) + for scanner.Scan() { + line := scanner.Text() + line = strings.Replace(line, "", projectName, -1) + line = strings.Replace(line, "", description, -1) + line = strings.Replace(line, "", author, -1) + line = strings.Replace(line, "", url, -1) + template += line + "\n" + } + + readmeFile, err := os.Create("README.md") + if err != nil { + log.Fatal(err) + } + defer readmeFile.Close() + + _, err = readmeFile.WriteString(template) + if err != nil { + log.Fatal(err) + } + return true +} diff --git a/kode-starter b/kode-starter deleted file mode 100644 index 3aee170..0000000 Binary files a/kode-starter and /dev/null differ diff --git a/kode-starter.exe b/kode-starter.exe deleted file mode 100644 index 2643def..0000000 Binary files a/kode-starter.exe and /dev/null differ diff --git a/kode-starter.go b/kode-starter.go index f2f380d..d905e0b 100644 --- a/kode-starter.go +++ b/kode-starter.go @@ -1,76 +1,27 @@ package main import ( - "bufio" - "bytes" - "encoding/json" "fmt" "kode-starter/config" + "kode-starter/create" "kode-starter/lists" - "kode-starter/structures" "kode-starter/utils" - "log" - "net/http" - "os" - "os/exec" - "strings" + "kode-starter/version" - "github.com/joho/godotenv" "github.com/spf13/cobra" ) -func createReadme(projectName, description, author, url string) bool { - file, err := os.Open("../readme.template") - if err != nil { - log.Fatal(err) - } - defer file.Close() - - template := "" - scanner := bufio.NewScanner(file) - for scanner.Scan() { - line := scanner.Text() - line = strings.Replace(line, "", projectName, -1) - line = strings.Replace(line, "", description, -1) - line = strings.Replace(line, "", author, -1) - line = strings.Replace(line, "", url, -1) - template += line + "\n" - } - - readmeFile, err := os.Create("README.md") - if err != nil { - log.Fatal(err) - } - defer readmeFile.Close() - - _, err = readmeFile.WriteString(template) - if err != nil { - log.Fatal(err) - } - return true -} - var rootCmd = &cobra.Command{ - Use: "prj-creator.exe", + Use: "kode-creator.exe", Short: "Simple cli app create startup project", Long: `A simple CLI app to work with Github, Gitea`, Run: func(cmd *cobra.Command, args []string) { - config.SetInformations(cmd, args, token) + config.SetInformations(cmd, args) mainProgram(cmd, args) }, } -var createCmd = &cobra.Command{ - Use: "create", - Short: "Create Github project", - Long: `A simple CLI app to create a startup project on Github`, - Run: func(cmd *cobra.Command, args []string) { - config.SetInformations(cmd, args, token) - createProject() - }, -} - var token, org, name, private, description string var verbose, createFlag bool @@ -84,140 +35,38 @@ func init() { lists.ListCmd.Flags().StringVarP(&token, "token", "t", "", "Github token") lists.ListCmd.Flags().StringVarP(&org, "org", "o", "", "Github organization") - createCmd.Flags().StringVarP(&token, "token", "t", "", "Github token") - createCmd.Flags().StringVarP(&org, "org", "o", "", "Github organization") - createCmd.Flags().StringVarP(&name, "name", "n", "", "Project name") - createCmd.Flags().StringVarP(&description, "desc", "d", "", "Description") - createCmd.Flags().StringVarP(&private, "private", "p", "", "true/false") + create.CreateCmd.Flags().StringVarP(&token, "token", "t", "", "Github token") + create.CreateCmd.Flags().StringVarP(&org, "org", "o", "", "Github organization") + create.CreateCmd.Flags().StringVarP(&name, "name", "n", "", "Project name") + create.CreateCmd.Flags().StringVarP(&description, "desc", "d", "", "Description") + create.CreateCmd.Flags().StringVarP(&private, "private", "p", "", "true/false") rootCmd.AddCommand(lists.ListCmd) - rootCmd.AddCommand(createCmd) + rootCmd.AddCommand(create.CreateCmd) } func mainProgram(cmd *cobra.Command, args []string) { + config.SetInformations(cmd, args) + // Check if the help flag is set if cmd.Flags().Changed("help") || cmd.Flags().Changed("h") { - printHelpFormated(cmd) + utils.PrintHelpFormated(version.GetFullVersion(), config.GetAuthor(), config.GetBuildDate(), cmd) return } else { // If no flag is set, show help - printHelpFormated(cmd) + utils.PrintHelpFormated(version.GetFullVersion(), config.GetAuthor(), config.GetBuildDate(), cmd) return } } -func createProject() { - - godotenv.Load() - - fmt.Printf("Token: %s\n", config.GetToken()) - - // Construct POST data - data := structures.Project{ - Name: name, - Description: description, - Private: private == "true", - } - - // Make API request to create Github project - jsonData, _ := json.Marshal(data) - req, err := http.NewRequest("POST", - fmt.Sprintf("%s/%s/repos", config.GetUrlApiOrgs(), org), - bytes.NewBuffer(jsonData)) - req.Header.Set("Authorization", "token "+config.GetToken()) - req.Header.Set("Content-Type", "application/json") - client := &http.Client{} - res, err := client.Do(req) - if err != nil { - log.Fatalln(err) - } - - var dataReceived structures.GitOrgsRepoResponse - err = json.NewDecoder(res.Body).Decode(&dataReceived) - if err != nil { - log.Fatal(err) - } - fmt.Printf("==> Created project '%s' URL: '%s'\n", name, dataReceived.CloneURL) - - defer res.Body.Close() - - // Create directory of the project - fmt.Println("==> Creating directory...") - utils.CreateDir(name) - fmt.Println("==> Created directory") - - // Change to project directory - err = os.Chdir(name) - if err != nil { - log.Fatal(err) - } - - isCreated := createReadme(name, description, "BC", "") - if isCreated { - fmt.Println("==> Created README.md") - } - - // Git commands - gitCmd := exec.Command("git", "init") - err = gitCmd.Run() - if err != nil { - log.Fatal(err) - } else { - fmt.Printf("==> Initialized empty Git repository in %s\n", name) - } - - gitCmd = exec.Command("git", "checkout", "-b", "main") - err = gitCmd.Run() - if err != nil { - log.Fatal(err) - } else { - fmt.Println("==> Switched to a new branch 'main'") - } - - gitCmd = exec.Command("git", "add", "-A") - err = gitCmd.Run() - if err != nil { - log.Fatal(err) - } else { - fmt.Println("==> Switched to a new branch 'main'") - } - - gitCmd = exec.Command("git", "commit", "-m", "first commit from project creator !") - err = gitCmd.Run() - if err != nil { - log.Fatal(err) - } else { - fmt.Println("==> first commit from project creator !") - } - - // Get project info from API response - var project structures.Project - json.NewDecoder(res.Body).Decode(&project) - fmt.Println(project) - -} - -func printHelpFormated(cmd *cobra.Command) { - - var line string = "--- --- --- --- --- --- --- --- --- --- --- --- ---" - fmt.Println("") - cmd.Help() - fmt.Println("") - fmt.Println(line) - fmt.Println("| Version: " + config.GetVersion().String() + " || Author: " + config.GetAuthor() + " || Build date: " + config.GetBuildDate() + " |") - fmt.Println(line) - fmt.Println("") -} - func main() { - fmt.Println("==> Project Creator CLI") - versionString := config.GetVersion() - fmt.Println(versionString) - + fmt.Println(utils.CreateLine(15)) + utils.PrintHeader() + fmt.Println(utils.CreateLine(15)) rootCmd.Execute() } diff --git a/lists/lists.go b/lists/lists.go index 6950310..8801a57 100644 --- a/lists/lists.go +++ b/lists/lists.go @@ -1,49 +1,71 @@ package lists import ( - "kode-starter/config" - "kode-starter/utils" "encoding/json" "fmt" - "io/ioutil" + "io" + "kode-starter/config" + "kode-starter/utils" "net/http" + "strconv" "github.com/joho/godotenv" "github.com/spf13/cobra" ) -var token string - +// This is a variable declaration that creates a new Cobra command called `ListCmd`. The command has a +// `Use` field that specifies the name of the command, a `Short` field that provides a brief +// description of the command, and a `Long` field that provides a more detailed description of the +// command. The `Run` field is a function that is executed when the command is run. It sets some +// configuration information based on the command line arguments, and then either lists the projects +// for a specified organization or lists all organizations, depending on the command line arguments. var ListCmd = &cobra.Command{ Use: "list", Short: "List Github, Gitea orgs, project", Long: `A simple CLI app to list Github, Gitea orgs, project`, Run: func(cmd *cobra.Command, args []string) { - config.SetInformations(cmd, args, token) + config.SetInformations(cmd, args) - orgs, err := ListItems() - if err != nil { - fmt.Println("Error:", err) - return - } - fmt.Println("List of ") - for _, org := range orgs { - fmt.Print(org) + // if -o flag is set, list projects for that org + if config.GetListOrganisationFlag() { + fmt.Println("\nList projects for org: " + config.GetListOrganisation() + "\n") + fmt.Println(utils.CreateLine(6)) + projects, err := ListProjects(config.GetListOrganisation()) + if err != nil { + fmt.Println("Error:", err) + return + } + for _, project := range projects { + fmt.Print(project) + } + } else { + orgs, err := ListOrganization() + if err != nil { + fmt.Println("Error:", err) + return + } + fmt.Println("\nList of Organizations :") + fmt.Println(utils.CreateLine(4)) + for _, org := range orgs { + fmt.Print(org) + } } }, } -func ListItems() ([]string, error) { +// The ListOrganization function retrieves a list of organizations from a specified URL and returns +// their names and URLs. +func ListOrganization() ([]string, error) { godotenv.Load() if config.GetVerboseFlag() { - fmt.Println("execute list items...") + fmt.Println("execute ListOrganization ...") fmt.Println("URL: " + config.GetUrlApiOrgs()) fmt.Println("Token: " + config.GetToken()) } - body := UrlGetGithub(config.GetUrlApiOrgs(), config.GetToken()) + body := UrlGetBody(config.GetUrlApiOrgs()) if config.GetJsonFlag() { return utils.BytesToStrings(body), nil } else { @@ -55,17 +77,60 @@ func ListItems() ([]string, error) { } else { var orgNames []string for i, org := range orgs { - orgNames = append(orgNames, fmt.Sprintf("%d. %s\n", i+1, org["username"])) + nb := strconv.FormatInt(int64(i+1), 10) + orgName := fmt.Sprintf("%s", org["username"]) + orgUrl := fmt.Sprintf("[ %s/%s ]\n", config.GetUrlBase(), org["username"]) + orgNames = append( + orgNames, + utils.PadString(nb, 4), + utils.PadString(orgName, 30), + orgUrl) } - return orgNames, nil } - } - } -func UrlGetGithub(url string, token string) []byte { +// This function lists the projects of a given organization and returns their names and clone URLs. +func ListProjects(org string) ([]string, error) { + godotenv.Load() + + if config.GetVerboseFlag() { + fmt.Println("execute ListProjects ...") + fmt.Println("URL: " + config.GetUrlApiOrgs()) + fmt.Println("Token: " + config.GetToken()) + } + + body := UrlGetBody(config.GetUrlApiOrgs() + "/" + org + "/repos") + if config.GetJsonFlag() { + return utils.BytesToStrings(body), nil + } else { + var prjs []map[string]interface{} + err := json.Unmarshal(body, &prjs) + if err != nil { + return nil, err + } else { + var orgNames []string + + for i, prj := range prjs { + nb := strconv.FormatInt(int64(i+1), 10) + projectName := fmt.Sprintf("%s", prj["name"]) + cloneUrl := fmt.Sprintf("[ %s ]\n", prj["clone_url"]) + orgNames = append( + orgNames, + utils.PadString(nb, 4), + utils.PadString(projectName, 40), + cloneUrl, + ) + } + return orgNames, nil + } + } +} + +// This function sends a GET request to a specified URL with authorization and content-type headers, +// and returns the response body as a byte array. +func UrlGetBody(url string) []byte { req, err := http.NewRequest("GET", url, nil) if err != nil { @@ -74,21 +139,18 @@ func UrlGetGithub(url string, token string) []byte { } req.Header.Set("Authorization", "token "+config.GetToken()) req.Header.Set("Content-Type", "application/json") - // req.Header.Add("Accept", "application/vnd.github.v3+json") - fmt.Println(req.Header.Get("Authorization")) + // fmt.Println(req.Header.Get("Authorization")) resp, err := http.DefaultClient.Do(req) if err != nil { fmt.Println("Error making request:", err) } defer resp.Body.Close() - body, err := ioutil.ReadAll(resp.Body) + body, err := io.ReadAll(resp.Body) if err != nil { fmt.Println("Error reading response body:", err) } - return body - } diff --git a/utils/utils.go b/utils/utils.go index d38fe0f..fcf288f 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -4,8 +4,41 @@ import ( "fmt" "os" "path/filepath" + "strings" + + "github.com/spf13/cobra" ) +func PrintHeader() { + + fmt.Println(" __ __ __ ______ __ ") + fmt.Println(" / //_/ ____ ____/ / ___ / ____/ _____ ___ ____ _ / /_ ____ _____") + fmt.Println(" / ,< / __ \\ / __ / / _ \\ ______ / / / ___/ / _ \\ / __ `/ / __/ / __ \\ / ___/") + fmt.Println(" / /| | / /_/ // /_/ / / __//_____// /___ / / / __// /_/ / / /_ / /_/ / / / ") + fmt.Println("/_/ |_| \\____/ \\__,_/ \\___/ \\____/ /_/ \\___/ \\__,_/ \\__/ \\____/ /_/ ") + fmt.Println("") + +} + +func CreateLine(weight int) string { + var line string + for i := 0; i < weight; i++ { + line += "--- " + } + return line +} + +func PrintHelpFormated(version string, author string, buildDate string, cmd *cobra.Command) { + + fmt.Println("") + cmd.Help() + fmt.Println("") + fmt.Println(CreateLine(15)) + fmt.Println("| Version: " + version + " || Author: " + author + " || Build date: " + buildDate + " |") + fmt.Println(CreateLine(15)) + fmt.Println("") +} + func BytesToStrings(bytes []byte) []string { var strings []string for _, b := range bytes { @@ -60,6 +93,7 @@ func CreateDir(dirName string) { panic(err) } fmt.Println("Current working directory: ", path) + fmt.Println("Directory to create: ", dirName) dirPath := filepath.Join(path, dirName) @@ -70,3 +104,10 @@ func CreateDir(dirName string) { fmt.Printf("Created directory '%s' \n", dirName) } + +func PadString(s string, n int) string { + if len(s) >= n { + return s + } + return s + strings.Repeat(" ", n-len(s)) +} diff --git a/version/version-number.go b/version/version-number.go index 95fa41d..5cff710 100644 --- a/version/version-number.go +++ b/version/version-number.go @@ -1,3 +1,32 @@ package version -var VersionString string = "1.2.3.4.v1" +import "fmt" + +type Version struct { + Major int + Minor int + Patch int + Build int +} + +var versionNumber Version = Version{0, 1, 0, 0} + +func GetFullVersion() string { + return fmt.Sprintf("%d.%d.%d.%d", versionNumber.Major, versionNumber.Minor, versionNumber.Patch, versionNumber.Build) +} + +func GetMajorVersion() string { + return fmt.Sprintf("%d", versionNumber.Major) +} + +func GetMinorVersion() string { + return fmt.Sprintf("%d", versionNumber.Minor) +} + +func GetPatchVersion() string { + return fmt.Sprintf("%d", versionNumber.Patch) +} + +func GetBuildVersion() string { + return fmt.Sprintf("%d", versionNumber.Build) +} diff --git a/version/version.go b/version/version.go index a4c3cc2..45cd3df 100644 --- a/version/version.go +++ b/version/version.go @@ -6,14 +6,6 @@ import ( "strings" ) -type Version struct { - Major int - Minor int - Patch int - Build int - VersionNumberToIncrement string // New field -} - func (v Version) String() string { return fmt.Sprintf("%d.%d.%d.%d", v.Major, v.Minor, v.Patch, v.Build) } @@ -44,5 +36,5 @@ func NewVersionFromString(versionString string) (*Version, error) { return nil, fmt.Errorf("invalid build version: %s", parts[3]) } - return &Version{major, minor, patch, build, parts[4]}, nil // Initialize new field -} \ No newline at end of file + return &Version{major, minor, patch, build}, nil // Initialize new field +}