Skip to content

Commit e48ad86

Browse files
fix: upload bugs
1 parent 8a4cad8 commit e48ad86

3 files changed

Lines changed: 96 additions & 38 deletions

File tree

cmd/projects/add.go

Lines changed: 44 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,10 @@ all required flags:
125125
if err != nil {
126126
return fmt.Errorf("could not read input: %w", err)
127127
}
128-
displayName = result
128+
displayName = strings.TrimSpace(result)
129+
if displayName == "" {
130+
return fmt.Errorf("display name is required — please provide a name for your project")
131+
}
129132
}
130133

131134
if uniqueName == "" {
@@ -135,7 +138,22 @@ all required flags:
135138
if err != nil {
136139
return fmt.Errorf("could not read input: %w", err)
137140
}
138-
uniqueName = result
141+
uniqueName = strings.TrimSpace(result)
142+
if uniqueName == "" {
143+
return fmt.Errorf("unique name is required — must be lowercase, 4-32 characters")
144+
}
145+
if len(uniqueName) < 4 || len(uniqueName) > 32 {
146+
return fmt.Errorf("unique name must be between 4 and 32 characters (got %d)", len(uniqueName))
147+
}
148+
if uniqueName != strings.ToLower(uniqueName) {
149+
return fmt.Errorf("unique name must be lowercase — try %q", strings.ToLower(uniqueName))
150+
}
151+
available, err := client.CheckUniqueNameAvailable(uniqueName)
152+
if err != nil {
153+
pterm.Warning.Printf("Could not verify name availability: %s\n", err)
154+
} else if !available {
155+
return fmt.Errorf("the unique name %q is already taken — please choose a different one", uniqueName)
156+
}
139157
}
140158

141159
if projectType == "" {
@@ -519,11 +537,12 @@ func promptFrameworkRuntime(client *api.APIClient, projectType string) (framewor
519537
}
520538
sel.runtime = runtimeValue
521539

522-
// For upload projects, standalone runtimes (dockerfile, build-ai) are fine alone.
523-
// Regular runtimes need a framework too.
540+
// For upload projects, some runtimes (like nodejs) have compatible frameworks.
541+
// If compatible frameworks exist, prompt the user to pick one.
542+
// Standalone runtimes (dockerfile, build-ai) and runtimes with no compatible
543+
// frameworks (golang, rust, python, bun) are fine alone.
524544
standaloneRuntimes := map[string]bool{"dockerfile": true, "build-ai": true}
525545
if projectType == "upload" && !standaloneRuntimes[entry.Name] {
526-
// Need to also pick a framework that's compatible with this runtime
527546
var frameworks []api.SupportedProjectType
528547
for _, s := range supported {
529548
if s.Type != "framework" {
@@ -537,30 +556,28 @@ func promptFrameworkRuntime(client *api.APIClient, projectType string) (framewor
537556
}
538557
}
539558

540-
if len(frameworks) == 0 {
541-
return frameworkSelection{}, fmt.Errorf("no compatible frameworks found for runtime %q\n\n For upload projects, try selecting a framework instead", entry.Name)
542-
}
543-
544-
fwOptions := make([]string, len(frameworks))
545-
for i, fw := range frameworks {
546-
fwOptions[i] = fw.Name
547-
}
559+
if len(frameworks) > 0 {
560+
fwOptions := make([]string, len(frameworks))
561+
for i, fw := range frameworks {
562+
fwOptions[i] = fw.Name
563+
}
548564

549-
fwSelected, err := pterm.DefaultInteractiveSelect.
550-
WithDefaultText("Framework (required for upload projects)").
551-
WithOptions(fwOptions).
552-
WithFilter(true).
553-
Show()
554-
if err != nil {
555-
return frameworkSelection{}, fmt.Errorf("could not read input: %w", err)
556-
}
565+
fwSelected, err := pterm.DefaultInteractiveSelect.
566+
WithDefaultText("Framework (required for upload projects)").
567+
WithOptions(fwOptions).
568+
WithFilter(true).
569+
Show()
570+
if err != nil {
571+
return frameworkSelection{}, fmt.Errorf("could not read input: %w", err)
572+
}
557573

558-
sel.framework = fwSelected
559-
// Use the framework's editables since they're more specific
560-
for _, fw := range frameworks {
561-
if fw.Name == fwSelected {
562-
sel.editables = fw.Editables
563-
break
574+
sel.framework = fwSelected
575+
// Use the framework's editables since they're more specific
576+
for _, fw := range frameworks {
577+
if fw.Name == fwSelected {
578+
sel.editables = fw.Editables
579+
break
580+
}
564581
}
565582
}
566583
}

cmd/projects/get.go

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,18 @@ import (
1313
"github.com/NodeOps-app/createos-cli/internal/output"
1414
)
1515

16+
// parseVCSSource extracts the repository full name from a VCS project's source field.
17+
func parseVCSSource(raw json.RawMessage) string {
18+
if len(raw) == 0 {
19+
return ""
20+
}
21+
var src api.VCSSource
22+
if err := json.Unmarshal(raw, &src); err != nil {
23+
return ""
24+
}
25+
return src.VCSFullName
26+
}
27+
1628
func newGetCommand() *cli.Command {
1729
return &cli.Command{
1830
Name: "get",
@@ -51,6 +63,13 @@ func newGetCommand() *cli.Command {
5163
cyan.Printf("Type: ")
5264
fmt.Println(project.Type)
5365

66+
if project.Type == "vcs" {
67+
if repo := parseVCSSource(project.Source); repo != "" {
68+
cyan.Printf("Repository: ")
69+
fmt.Println(repo)
70+
}
71+
}
72+
5473
cyan.Printf("Description: ")
5574
if project.Description != nil {
5675
fmt.Println(*project.Description)
@@ -64,9 +83,6 @@ func newGetCommand() *cli.Command {
6483
cyan.Printf("Created At: ")
6584
fmt.Println(project.CreatedAt.Format("2006-01-02 15:04:05"))
6685

67-
cyan.Printf("Updated At: ")
68-
fmt.Println(project.UpdatedAt.Format("2006-01-02 15:04:05"))
69-
7086
return nil
7187
},
7288
}

internal/api/methods.go

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,21 @@ import (
99

1010
// Project represents a CreateOS project
1111
type Project struct {
12-
ID string `json:"id"`
13-
UniqueName string `json:"uniqueName"`
14-
DisplayName string `json:"displayName"`
15-
Description *string `json:"description"`
16-
Status string `json:"status"`
17-
Type string `json:"type"`
18-
CreatedAt time.Time `json:"createdAt"`
19-
UpdatedAt time.Time `json:"updatedAt"`
12+
ID string `json:"id"`
13+
UniqueName string `json:"uniqueName"`
14+
DisplayName string `json:"displayName"`
15+
Description *string `json:"description"`
16+
Status string `json:"status"`
17+
Type string `json:"type"`
18+
Source json.RawMessage `json:"source,omitempty"`
19+
CreatedAt time.Time `json:"createdAt"`
20+
UpdatedAt time.Time `json:"updatedAt"`
21+
}
22+
23+
// VCSSource represents the source details for a VCS project.
24+
type VCSSource struct {
25+
VCSName string `json:"vcsName"`
26+
VCSFullName string `json:"vcsFullName"`
2027
}
2128

2229
// CreateProjectRequest is the request body for creating a new project.
@@ -47,6 +54,24 @@ func (c *APIClient) CreateProject(req CreateProjectRequest) (string, error) {
4754
return result.Data.ID, nil
4855
}
4956

57+
// CheckUniqueNameAvailable checks whether a project unique name is available.
58+
func (c *APIClient) CheckUniqueNameAvailable(uniqueName string) (bool, error) {
59+
var result Response[struct {
60+
IsAvailable bool `json:"isAvailable"`
61+
}]
62+
resp, err := c.Client.R().
63+
SetResult(&result).
64+
SetBody(map[string]string{"uniqueName": uniqueName}).
65+
Post("/v1/projects/available-unique-name")
66+
if err != nil {
67+
return false, err
68+
}
69+
if resp.IsError() {
70+
return false, ParseAPIError(resp.StatusCode(), resp.Body())
71+
}
72+
return result.Data.IsAvailable, nil
73+
}
74+
5075
// GithubInstallation represents a connected GitHub account.
5176
type GithubInstallation struct {
5277
InstallationID int64 `json:"installationId"`

0 commit comments

Comments
 (0)