diff --git a/api/api.go b/api/api.go index 8bf5e155d..bf16260ae 100644 --- a/api/api.go +++ b/api/api.go @@ -341,8 +341,8 @@ type createCodespaceRequest struct { SkuName string `json:"sku_name"` } -func (a *API) CreateCodespace(ctx context.Context, user *User, repository *Repository, sku *Sku, branch, location string) (*Codespace, error) { - requestBody, err := json.Marshal(createCodespaceRequest{repository.ID, branch, location, sku.Name}) +func (a *API) CreateCodespace(ctx context.Context, user *User, repository *Repository, sku, branch, location string) (*Codespace, error) { + requestBody, err := json.Marshal(createCodespaceRequest{repository.ID, branch, location, sku}) if err != nil { return nil, fmt.Errorf("error marshaling request: %v", err) } diff --git a/cmd/ghcs/create.go b/cmd/ghcs/create.go index b179b5dba..385e5d957 100644 --- a/cmd/ghcs/create.go +++ b/cmd/ghcs/create.go @@ -12,29 +12,26 @@ import ( "github.com/spf13/cobra" ) -var createCmd = &cobra.Command{ - Use: "create", - Short: "Create a GitHub Codespace.", - RunE: func(cmd *cobra.Command, args []string) error { - return Create() - }, +var repo, branch, machine string + +func newCreateCmd() *cobra.Command { + createCmd := &cobra.Command{ + Use: "create", + Short: "Create a GitHub Codespace.", + RunE: func(cmd *cobra.Command, args []string) error { + return Create() + }, + } + + createCmd.Flags().StringVarP(&repo, "repo", "r", "", "repository name with owner: user/repo") + createCmd.Flags().StringVarP(&branch, "branch", "b", "", "repository branch") + createCmd.Flags().StringVarP(&machine, "machine", "m", "", "hardware specifications for the VM") + + return createCmd } func init() { - rootCmd.AddCommand(createCmd) -} - -var createSurvey = []*survey.Question{ - { - Name: "repository", - Prompt: &survey.Input{Message: "Repository"}, - Validate: survey.Required, - }, - { - Name: "branch", - Prompt: &survey.Input{Message: "Branch"}, - Validate: survey.Required, - }, + rootCmd.AddCommand(newCreateCmd()) } func Create() error { @@ -43,16 +40,16 @@ func Create() error { locationCh := getLocation(ctx, apiClient) userCh := getUser(ctx, apiClient) - answers := struct { - Repository string - Branch string - }{} - - if err := survey.Ask(createSurvey, &answers); err != nil { - return fmt.Errorf("error getting answers: %v", err) + repo, err := getRepoName() + if err != nil { + return fmt.Errorf("error getting repository name: %v", err) + } + branch, err := getBranchName() + if err != nil { + return fmt.Errorf("error getting branch name: %v", err) } - repository, err := apiClient.GetRepository(ctx, answers.Repository) + repository, err := apiClient.GetRepository(ctx, repo) if err != nil { return fmt.Errorf("error getting repository: %v", err) } @@ -67,47 +64,18 @@ func Create() error { return fmt.Errorf("error getting codespace user: %v", userResult.Err) } - skus, err := apiClient.GetCodespacesSkus(ctx, userResult.User, repository, locationResult.Location) + machine, err := getMachineName(ctx, userResult.User, repository, locationResult.Location, apiClient) if err != nil { - return fmt.Errorf("error getting codespace skus: %v", err) + return fmt.Errorf("error getting machine type: %v", err) } - - if len(skus) == 0 { + if machine == "" { fmt.Println("There are no available machine types for this repository") return nil } - skuNames := make([]string, 0, len(skus)) - skuByName := make(map[string]*api.Sku) - for _, sku := range skus { - nameParts := camelcase.Split(sku.Name) - machineName := strings.Title(strings.ToLower(nameParts[0])) - skuName := fmt.Sprintf("%s - %s", machineName, sku.DisplayName) - skuNames = append(skuNames, skuName) - skuByName[skuName] = sku - } - - skuSurvey := []*survey.Question{ - { - Name: "sku", - Prompt: &survey.Select{ - Message: "Choose Machine Type:", - Options: skuNames, - Default: skuNames[0], - }, - Validate: survey.Required, - }, - } - - skuAnswers := struct{ SKU string }{} - if err := survey.Ask(skuSurvey, &skuAnswers); err != nil { - return fmt.Errorf("error getting SKU: %v", err) - } - - sku := skuByName[skuAnswers.SKU] fmt.Println("Creating your codespace...") - codespace, err := apiClient.CreateCodespace(ctx, userResult.User, repository, sku, answers.Branch, locationResult.Location) + codespace, err := apiClient.CreateCodespace(ctx, userResult.User, repository, machine, branch, locationResult.Location) if err != nil { return fmt.Errorf("error creating codespace: %v", err) } @@ -144,3 +112,93 @@ func getLocation(ctx context.Context, apiClient *api.API) <-chan locationResult }() return ch } + +func getRepoName() (string, error) { + if repo != "" { + return repo, nil + } + + repoSurvey := []*survey.Question{ + { + Name: "repository", + Prompt: &survey.Input{Message: "Repository"}, + Validate: survey.Required, + }, + } + err := survey.Ask(repoSurvey, &repo) + return repo, err +} + +func getBranchName() (string, error) { + if branch != "" { + return branch, nil + } + + branchSurvey := []*survey.Question{ + { + Name: "branch", + Prompt: &survey.Input{Message: "Branch"}, + Validate: survey.Required, + }, + } + err := survey.Ask(branchSurvey, &branch) + return branch, err +} + +func getMachineName(ctx context.Context, user *api.User, repo *api.Repository, location string, apiClient *api.API) (string, error) { + skus, err := apiClient.GetCodespacesSkus(ctx, user, repo, location) + if err != nil { + return "", fmt.Errorf("error getting codespace skus: %v", err) + } + + // if user supplied a machine type, it must be valid + // if no machine type was supplied, we don't error if there are no machine types for the current repo + if machine != "" { + for _, sku := range skus { + if machine == sku.Name { + return machine, nil + } + } + + availableSkus := make([]string, len(skus)) + for i := 0; i < len(skus); i++ { + availableSkus[i] = skus[i].Name + } + + return "", fmt.Errorf("there are is no such machine for the repository: %s\nAvailable machines: %v", machine, availableSkus) + } else if len(skus) == 0 { + return "", nil + } + + skuNames := make([]string, 0, len(skus)) + skuByName := make(map[string]*api.Sku) + for _, sku := range skus { + nameParts := camelcase.Split(sku.Name) + machineName := strings.Title(strings.ToLower(nameParts[0])) + skuName := fmt.Sprintf("%s - %s", machineName, sku.DisplayName) + skuNames = append(skuNames, skuName) + skuByName[skuName] = sku + } + + skuSurvey := []*survey.Question{ + { + Name: "sku", + Prompt: &survey.Select{ + Message: "Choose Machine Type:", + Options: skuNames, + Default: skuNames[0], + }, + Validate: survey.Required, + }, + } + + skuAnswers := struct{ SKU string }{} + if err := survey.Ask(skuSurvey, &skuAnswers); err != nil { + return "", fmt.Errorf("error getting SKU: %v", err) + } + + sku := skuByName[skuAnswers.SKU] + machine = sku.Name + + return machine, nil +} diff --git a/cmd/ghcs/main.go b/cmd/ghcs/main.go index a89a544b4..0b5c001b2 100644 --- a/cmd/ghcs/main.go +++ b/cmd/ghcs/main.go @@ -15,7 +15,7 @@ var rootCmd = &cobra.Command{ Use: "ghcs", Short: "Unofficial GitHub Codespaces CLI.", Long: "Unofficial CLI tool to manage and interact with GitHub Codespaces.", - Version: "0.6.0", + Version: "0.7.0", } func Execute() {