11package cli
22
33import (
4+ "bufio"
45 "fmt"
6+ "io"
57 "os"
68 "path/filepath"
9+ "strings"
710
811 "github.com/acmore/okdev/internal/config"
912)
1013
1114// InitOverrides holds flag-provided values that skip prompting.
1215type InitOverrides struct {
13- Name string
14- Namespace string
16+ Name string
17+ Namespace string
18+ SidecarImage string
19+ SyncLocal string
20+ SyncRemote string
21+ SSHUser string
1522}
1623
1724// applyOverrides applies non-empty flag values to template vars.
@@ -22,6 +29,18 @@ func applyOverrides(vars *config.TemplateVars, o InitOverrides) {
2229 if o .Namespace != "" {
2330 vars .Namespace = o .Namespace
2431 }
32+ if o .SidecarImage != "" {
33+ vars .SidecarImage = o .SidecarImage
34+ }
35+ if o .SyncLocal != "" {
36+ vars .SyncLocal = o .SyncLocal
37+ }
38+ if o .SyncRemote != "" {
39+ vars .SyncRemote = o .SyncRemote
40+ }
41+ if o .SSHUser != "" {
42+ vars .SSHUser = o .SSHUser
43+ }
2544}
2645
2746// detectDefaultName returns a sensible default for the environment name
@@ -34,59 +53,101 @@ func detectDefaultName() string {
3453 return filepath .Base (wd )
3554}
3655
56+ func (o InitOverrides ) hasName () bool { return o .Name != "" }
57+ func (o InitOverrides ) hasNamespace () bool { return o .Namespace != "" }
58+ func (o InitOverrides ) hasSidecarImage () bool { return o .SidecarImage != "" }
59+ func (o InitOverrides ) hasSyncLocal () bool { return o .SyncLocal != "" }
60+ func (o InitOverrides ) hasSyncRemote () bool { return o .SyncRemote != "" }
61+ func (o InitOverrides ) hasSSHUser () bool { return o .SSHUser != "" }
62+
3763// promptInteractive runs interactive prompts to fill in template vars.
3864// Only prompts for values not already set by flags.
3965// When nonInteractive is true, uses defaults without prompting.
40- func promptInteractive (vars * config.TemplateVars , nonInteractive bool ) error {
66+ func promptInteractive (vars * config.TemplateVars , overrides InitOverrides , in io. Reader , out io. Writer , nonInteractive bool , interactive bool ) error {
4167 if vars .Name == "" {
4268 vars .Name = detectDefaultName ()
4369 }
4470 if nonInteractive {
4571 return nil
4672 }
73+ if ! interactive {
74+ return fmt .Errorf ("interactive init requires a TTY; rerun with --yes or pass explicit flags" )
75+ }
4776
48- // Interactive prompts using basic fmt.Scanln.
49- // Each prompt shows the current default and allows the user to accept or override.
50- var input string
77+ reader := bufio .NewReader (in )
5178
52- input = promptString ("Environment name" , vars .Name )
53- if input != "" {
54- vars .Name = input
79+ if ! overrides .hasName () {
80+ input , err := promptString (reader , out , "Environment name" , vars .Name )
81+ if err != nil {
82+ return err
83+ }
84+ if input != "" {
85+ vars .Name = input
86+ }
5587 }
5688
57- input = promptString ("Namespace" , vars .Namespace )
58- if input != "" {
59- vars .Namespace = input
89+ if ! overrides .hasNamespace () {
90+ input , err := promptString (reader , out , "Namespace" , vars .Namespace )
91+ if err != nil {
92+ return err
93+ }
94+ if input != "" {
95+ vars .Namespace = input
96+ }
6097 }
6198
62- input = promptString ("Sidecar image" , vars .SidecarImage )
63- if input != "" {
64- vars .SidecarImage = input
99+ if ! overrides .hasSidecarImage () {
100+ input , err := promptString (reader , out , "Sidecar image" , vars .SidecarImage )
101+ if err != nil {
102+ return err
103+ }
104+ if input != "" {
105+ vars .SidecarImage = input
106+ }
65107 }
66108
67- input = promptString ("Sync local path" , vars .SyncLocal )
68- if input != "" {
69- vars .SyncLocal = input
109+ if ! overrides .hasSyncLocal () {
110+ input , err := promptString (reader , out , "Sync local path" , vars .SyncLocal )
111+ if err != nil {
112+ return err
113+ }
114+ if input != "" {
115+ vars .SyncLocal = input
116+ }
70117 }
71118
72- input = promptString ("Sync remote path" , vars .SyncRemote )
73- if input != "" {
74- vars .SyncRemote = input
119+ if ! overrides .hasSyncRemote () {
120+ input , err := promptString (reader , out , "Sync remote path" , vars .SyncRemote )
121+ if err != nil {
122+ return err
123+ }
124+ if input != "" {
125+ vars .SyncRemote = input
126+ }
75127 }
76128
77- input = promptString ("SSH user" , vars .SSHUser )
78- if input != "" {
79- vars .SSHUser = input
129+ if ! overrides .hasSSHUser () {
130+ input , err := promptString (reader , out , "SSH user" , vars .SSHUser )
131+ if err != nil {
132+ return err
133+ }
134+ if input != "" {
135+ vars .SSHUser = input
136+ }
80137 }
81138
82139 return nil
83140}
84141
85142// promptString prints a prompt with a default and reads user input.
86143// Returns empty string if user accepts default (just presses Enter).
87- func promptString (label , defaultVal string ) string {
88- fmt .Printf ("? %s: (%s) " , label , defaultVal )
89- var input string
90- fmt .Scanln (& input )
91- return input
144+ func promptString (reader * bufio.Reader , out io.Writer , label , defaultVal string ) (string , error ) {
145+ if _ , err := fmt .Fprintf (out , "? %s: (%s) " , label , defaultVal ); err != nil {
146+ return "" , err
147+ }
148+ line , err := reader .ReadString ('\n' )
149+ if err != nil {
150+ return "" , fmt .Errorf ("read %s: %w" , strings .ToLower (label ), err )
151+ }
152+ return strings .TrimSpace (line ), nil
92153}
0 commit comments