Skip to content

Commit 00927f8

Browse files
fix: issue #29
1 parent 62ce157 commit 00927f8

2 files changed

Lines changed: 159 additions & 52 deletions

File tree

cmd/deploy/deploy.go

Lines changed: 113 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -127,29 +127,71 @@ func NewDeployCommand() *cli.Command {
127127
}
128128
}
129129

130-
// If still no project, try to auto-detect from git remote
131-
if projectID == "" {
130+
// If still no project, try auto-detect then interactive picker
131+
if projectID == "" && terminal.IsInteractive() {
132+
projects, err := client.ListProjects()
133+
if err != nil {
134+
return err
135+
}
136+
137+
// Filter to active projects
138+
activeProjects := make([]api.Project, 0, len(projects))
139+
for _, p := range projects {
140+
if p.Status == "active" {
141+
activeProjects = append(activeProjects, p)
142+
}
143+
}
144+
145+
if len(activeProjects) == 0 {
146+
return fmt.Errorf("you don't have any active projects yet\n\n Create one first:\n createos projects add")
147+
}
148+
149+
// Try to auto-detect from git remote
132150
dir, _ := os.Getwd()
133151
repoFullName := git.GetRemoteFullName(dir)
134152
if repoFullName != "" {
135-
projects, err := client.ListProjects()
136-
if err == nil {
137-
for _, p := range projects {
138-
if p.Status != "active" {
139-
continue
140-
}
141-
if p.Type != "vcs" && p.Type != "githubImport" {
142-
continue
143-
}
144-
var src api.VCSSource
145-
if err := json.Unmarshal(p.Source, &src); err != nil {
146-
continue
147-
}
148-
if src.VCSFullName == repoFullName {
149-
pterm.Info.Printf("Detected project %s from git remote (%s)\n", p.DisplayName, repoFullName)
153+
for _, p := range activeProjects {
154+
if p.Type != "vcs" && p.Type != "githubImport" {
155+
continue
156+
}
157+
var src api.VCSSource
158+
if err := json.Unmarshal(p.Source, &src); err != nil {
159+
continue
160+
}
161+
if src.VCSFullName == repoFullName {
162+
pterm.Info.Printf("Detected project %s from git remote (%s)\n", p.DisplayName, repoFullName)
163+
useDetected, _ := pterm.DefaultInteractiveConfirm.
164+
WithDefaultText(fmt.Sprintf("Deploy %s?", p.DisplayName)).
165+
WithDefaultValue(true).
166+
Show()
167+
if useDetected {
150168
projectID = p.ID
151-
break
152169
}
170+
break
171+
}
172+
}
173+
}
174+
175+
// Fall back to interactive selection
176+
if projectID == "" {
177+
options := make([]string, len(activeProjects))
178+
for i, p := range activeProjects {
179+
options[i] = fmt.Sprintf("%s (%s) [%s]", p.DisplayName, p.ID, p.Type)
180+
}
181+
182+
selected, err := pterm.DefaultInteractiveSelect.
183+
WithDefaultText("Select a project to deploy").
184+
WithOptions(options).
185+
WithFilter(true).
186+
Show()
187+
if err != nil {
188+
return fmt.Errorf("selection cancelled")
189+
}
190+
191+
for i, opt := range options {
192+
if opt == selected {
193+
projectID = activeProjects[i].ID
194+
break
153195
}
154196
}
155197
}
@@ -217,6 +259,59 @@ func deployVCS(c *cli.Context, client *api.APIClient, project *api.Project) erro
217259
return waitForDeployment(client, project.ID, deployment)
218260
}
219261

262+
// UploadDir zips a directory and uploads it as a deployment.
263+
// Exported so other packages (e.g. projects add) can trigger an upload deploy.
264+
func UploadDir(client *api.APIClient, projectID, displayName, dir string) error {
265+
absDir, err := filepath.Abs(dir)
266+
if err != nil {
267+
return err
268+
}
269+
270+
info, err := os.Stat(absDir)
271+
if err != nil || !info.IsDir() {
272+
return fmt.Errorf("directory %q not found", dir)
273+
}
274+
275+
pterm.Info.Printf("Deploying %s from %s...\n", displayName, absDir)
276+
277+
zipFile, err := os.CreateTemp("", "createos-deploy-*.zip")
278+
if err != nil {
279+
return fmt.Errorf("could not create temp file: %w", err)
280+
}
281+
defer os.Remove(zipFile.Name()) //nolint:errcheck
282+
defer zipFile.Close() //nolint:errcheck
283+
284+
spinner, _ := pterm.DefaultSpinner.Start("Packaging files...")
285+
286+
if err := createZip(zipFile, absDir); err != nil {
287+
spinner.Fail("Packaging failed")
288+
return err
289+
}
290+
291+
stat, _ := zipFile.Stat()
292+
if stat != nil && stat.Size() > maxZipSize {
293+
spinner.Fail("Package too large")
294+
return fmt.Errorf("deployment package is %d MB (max %d MB)\n\n Tip: check that node_modules, .git, and build artifacts are excluded",
295+
stat.Size()/(1024*1024), maxZipSize/(1024*1024))
296+
}
297+
298+
spinner.UpdateText("Uploading...")
299+
300+
if err := zipFile.Close(); err != nil { //nolint:govet
301+
return fmt.Errorf("could not flush deployment package: %w", err)
302+
}
303+
304+
deployment, err := client.UploadDeploymentZip(projectID, zipFile.Name())
305+
if err != nil {
306+
spinner.Fail("Upload failed")
307+
return err
308+
}
309+
310+
spinner.Success("Uploaded")
311+
312+
return waitForDeployment(client, projectID, deployment)
313+
}
314+
220315
// deployUpload zips the local directory and uploads it.
221316
func deployUpload(c *cli.Context, client *api.APIClient, project *api.Project) error {
222317
dir := c.String("dir")

cmd/projects/add.go

Lines changed: 46 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"github.com/pterm/pterm"
1212
"github.com/urfave/cli/v2"
1313

14+
"github.com/NodeOps-app/createos-cli/cmd/deploy"
1415
"github.com/NodeOps-app/createos-cli/internal/api"
1516
"github.com/NodeOps-app/createos-cli/internal/config"
1617
"github.com/NodeOps-app/createos-cli/internal/terminal"
@@ -217,7 +218,7 @@ all required flags:
217218

218219
// Prompt for editable settings based on the selected framework/runtime
219220
if sel.editables != nil {
220-
prompted, err := promptEditableSettings(c, sel.editables)
221+
prompted, err := promptEditableSettings(c, sel.framework, sel.editables)
221222
if err != nil {
222223
return err
223224
}
@@ -321,42 +322,25 @@ all required flags:
321322
pterm.Success.Printf("Project created successfully!\n")
322323
pterm.Println(pterm.Gray(" ID: " + id))
323324

324-
// Prompt to link the current directory to the new project
325-
if terminal.IsInteractive() {
326-
fmt.Println()
327-
link, err := pterm.DefaultInteractiveConfirm.
328-
WithDefaultText("Link this directory to the new project?").
329-
WithDefaultValue(true).
330-
Show()
331-
if err == nil && link {
332-
dir, err := os.Getwd()
333-
if err != nil {
334-
return fmt.Errorf("could not determine current directory: %w", err)
335-
}
336-
337-
cfg := config.ProjectConfig{
338-
ProjectID: id,
339-
ProjectName: displayName,
340-
}
325+
// Auto-link the current directory to the new project
326+
dir, err := os.Getwd()
327+
if err == nil {
328+
cfg := config.ProjectConfig{
329+
ProjectID: id,
330+
ProjectName: displayName,
331+
}
341332

342-
envs, err := client.ListEnvironments(id)
343-
if err == nil && len(envs) == 1 {
344-
cfg.EnvironmentID = envs[0].ID
345-
}
333+
envs, err := client.ListEnvironments(id)
334+
if err == nil && len(envs) == 1 {
335+
cfg.EnvironmentID = envs[0].ID
336+
}
346337

347-
if err := config.SaveProjectConfig(dir, cfg); err != nil {
348-
return fmt.Errorf("could not save project config: %w", err)
349-
}
338+
if err := config.SaveProjectConfig(dir, cfg); err != nil {
339+
pterm.Warning.Printf("Could not link directory: %s\n", err)
340+
} else {
350341
_ = config.EnsureGitignore(dir)
351-
352342
pterm.Success.Printf("Linked to %s\n", displayName)
353-
} else {
354-
pterm.Println(pterm.Gray(" To link this directory, run:"))
355-
pterm.Println(pterm.Gray(" createos init --project " + id))
356343
}
357-
} else {
358-
pterm.Println(pterm.Gray(" To link this directory, run:"))
359-
pterm.Println(pterm.Gray(" createos init --project " + id))
360344
}
361345

362346
// Prompt to trigger first deployment for VCS and image projects
@@ -407,7 +391,18 @@ all required flags:
407391
}
408392
}
409393

410-
if (projectType == "vcs" || projectType == "image") && !terminal.IsInteractive() {
394+
if projectType == "upload" && terminal.IsInteractive() {
395+
fmt.Println()
396+
deployNow, err := pterm.DefaultInteractiveConfirm.
397+
WithDefaultText("Deploy current directory now?").
398+
WithDefaultValue(true).
399+
Show()
400+
if err == nil && deployNow {
401+
return deploy.UploadDir(client, id, displayName, ".")
402+
}
403+
}
404+
405+
if !terminal.IsInteractive() {
411406
pterm.Println(pterm.Gray(" To trigger a deployment, run:"))
412407
pterm.Println(pterm.Gray(" createos deploy --project " + id))
413408
}
@@ -588,8 +583,9 @@ func promptFrameworkRuntime(client *api.APIClient, projectType string) (framewor
588583
// promptEditableSettings prompts the user for each editable setting of the
589584
// selected framework/runtime. It skips object-type fields (buildVars, runEnvs)
590585
// and any field already set via a CLI flag.
591-
func promptEditableSettings(c *cli.Context, editables map[string]api.EditableField) (map[string]any, error) {
586+
func promptEditableSettings(c *cli.Context, framework string, editables map[string]api.EditableField) (map[string]any, error) {
592587
result := map[string]any{}
588+
isVanillaJS := framework == "vanilla-js"
593589

594590
for _, field := range promptOrder {
595591
editable, ok := editables[field]
@@ -613,6 +609,22 @@ func promptEditableSettings(c *cli.Context, editables map[string]api.EditableFie
613609
defaultVal = fmt.Sprintf("%v", editable.Default)
614610
}
615611

612+
// For vanilla-js, skip all prompts — use defaults silently (null defaults are skipped).
613+
if isVanillaJS {
614+
if defaultVal != "" {
615+
switch editable.Type {
616+
case "number":
617+
n, err := strconv.Atoi(defaultVal)
618+
if err == nil {
619+
result[field] = n
620+
}
621+
case "string":
622+
result[field] = defaultVal
623+
}
624+
}
625+
continue
626+
}
627+
616628
promptLabel := label
617629
if defaultVal != "" {
618630
promptLabel = fmt.Sprintf("%s [default: %s]", label, defaultVal)

0 commit comments

Comments
 (0)