11package engine
22
33import (
4+ "bufio"
45 "context"
56 "fmt"
7+ "io"
8+ "log/slog"
9+ "os"
10+ "path/filepath"
11+
12+ "golang.org/x/term"
613
714 "github.com/warriorscode/deck/config"
815)
@@ -14,13 +21,71 @@ func RunBootstrap(ctx context.Context, dir string, steps []config.BootstrapStep,
1421 if CheckShell (ctx , d , step .Check , env ) {
1522 continue
1623 }
17- if err := RunShell (ctx , d , step .Run , env ); err != nil {
24+ stepEnv := env
25+ if step .Prompt != "" {
26+ extra , err := handlePrompt (step )
27+ if err != nil {
28+ return fmt .Errorf ("bootstrap %q: %w" , step .Name , err )
29+ }
30+ stepEnv = append (append ([]string {}, env ... ), extra ... )
31+ }
32+ if err := RunShell (ctx , d , step .Run , stepEnv ); err != nil {
1833 return fmt .Errorf ("bootstrap %q: %w" , step .Name , err )
1934 }
2035 }
2136 return nil
2237}
2338
39+ // handlePrompt displays the prompt, reads multi-line input, writes it to a temp file,
40+ // and returns env vars pointing to the input.
41+ func handlePrompt (step config.BootstrapStep ) ([]string , error ) {
42+ if ! term .IsTerminal (int (os .Stdin .Fd ())) {
43+ slog .Warn ("bootstrap prompt skipped (not a terminal)" , "step" , step .Name )
44+ return nil , fmt .Errorf ("prompt requires an interactive terminal" )
45+ }
46+
47+ fmt .Fprintf (os .Stderr , "\n %s\n " , step .Prompt )
48+
49+ input , err := readMultiLine (os .Stdin )
50+ if err != nil {
51+ return nil , fmt .Errorf ("reading input: %w" , err )
52+ }
53+
54+ tmpFile , err := os .CreateTemp ("" , "deck-prompt-*" )
55+ if err != nil {
56+ return nil , fmt .Errorf ("creating temp file: %w" , err )
57+ }
58+ if _ , err := tmpFile .WriteString (input ); err != nil {
59+ tmpFile .Close ()
60+ os .Remove (tmpFile .Name ())
61+ return nil , fmt .Errorf ("writing temp file: %w" , err )
62+ }
63+ tmpFile .Close ()
64+
65+ absPath , _ := filepath .Abs (tmpFile .Name ())
66+ return []string {
67+ "DECK_INPUT=" + input ,
68+ "DECK_INPUT_FILE=" + absPath ,
69+ }, nil
70+ }
71+
72+ // readMultiLine reads lines until an empty line or EOF.
73+ func readMultiLine (r io.Reader ) (string , error ) {
74+ scanner := bufio .NewScanner (r )
75+ var result string
76+ for scanner .Scan () {
77+ line := scanner .Text ()
78+ if line == "" {
79+ break
80+ }
81+ if result != "" {
82+ result += "\n "
83+ }
84+ result += line
85+ }
86+ return result , scanner .Err ()
87+ }
88+
2489func stepDir (base , override string ) string {
2590 if override != "" {
2691 return override
0 commit comments