11package main
22
33import (
4+ "bufio"
5+ "context"
46 "fmt"
57 "os"
68 "path/filepath"
9+ "strings"
710
811 "github.com/spf13/cobra"
912 "github.com/steveyegge/beads/internal/beads"
1013 "github.com/steveyegge/beads/internal/config"
1114 "github.com/steveyegge/beads/internal/configfile"
1215 "github.com/steveyegge/beads/internal/doltserver"
16+ "github.com/steveyegge/beads/internal/storage/dolt"
17+ "golang.org/x/term"
1318)
1419
1520var bootstrapCmd = & cobra.Command {
@@ -77,7 +82,10 @@ Examples:
7782 }
7883
7984 // Execute the plan
80- executeBootstrapPlan (plan , cfg )
85+ if err := executeBootstrapPlan (plan , cfg ); err != nil {
86+ fmt .Fprintf (os .Stderr , "Bootstrap failed: %v\n " , err )
87+ os .Exit (1 )
88+ }
8189 },
8290}
8391
@@ -153,19 +161,120 @@ func printBootstrapPlan(plan BootstrapPlan) {
153161 }
154162}
155163
156- func executeBootstrapPlan (plan BootstrapPlan , cfg * configfile.Config ) {
164+ // confirmPrompt asks the user to confirm an action. Returns true if the user
165+ // confirms or if stdin is not a terminal (non-interactive/CI contexts).
166+ func confirmPrompt (message string ) bool {
167+ if ! term .IsTerminal (int (os .Stdin .Fd ())) {
168+ return true
169+ }
170+ fmt .Fprintf (os .Stderr , "%s [Y/n] " , message )
171+ reader := bufio .NewReader (os .Stdin )
172+ line , _ := reader .ReadString ('\n' )
173+ line = strings .TrimSpace (strings .ToLower (line ))
174+ return line == "" || line == "y" || line == "yes"
175+ }
176+
177+ func executeBootstrapPlan (plan BootstrapPlan , cfg * configfile.Config ) error {
178+ if ! confirmPrompt ("Proceed?" ) {
179+ fmt .Fprintf (os .Stderr , "Aborted.\n " )
180+ return nil
181+ }
182+
183+ ctx := context .Background ()
184+
157185 switch plan .Action {
158186 case "sync" :
159- fmt .Printf ("Syncing from %s...\n " , plan .SyncRemote )
160- fmt .Printf ("Run: bd init --prefix %s\n " , inferPrefix (cfg ))
161- fmt .Printf ("(bd init detects sync.git-remote and bootstraps non-destructively)\n " )
187+ return executeSyncAction (ctx , plan , cfg )
162188 case "restore" :
163- fmt .Printf ("Restoring from backup...\n " )
164- fmt .Printf ("Run: bd backup restore\n " )
189+ return executeRestoreAction (ctx , plan , cfg )
165190 case "init" :
166- fmt .Printf ("Creating fresh database...\n " )
167- fmt .Printf ("Run: bd init --prefix %s\n " , inferPrefix (cfg ))
191+ return executeInitAction (ctx , plan , cfg )
192+ }
193+ return nil
194+ }
195+
196+ func executeInitAction (ctx context.Context , plan BootstrapPlan , cfg * configfile.Config ) error {
197+ doltDir := doltserver .ResolveDoltDir (plan .BeadsDir )
198+ if err := os .MkdirAll (doltDir , 0o750 ); err != nil {
199+ return fmt .Errorf ("create dolt directory: %w" , err )
200+ }
201+
202+ prefix := inferPrefix (cfg )
203+ dbName := cfg .GetDoltDatabase ()
204+
205+ store , err := dolt .New (ctx , & dolt.Config {
206+ Path : doltDir ,
207+ Database : dbName ,
208+ CreateIfMissing : true ,
209+ AutoStart : true ,
210+ BeadsDir : plan .BeadsDir ,
211+ })
212+ if err != nil {
213+ return fmt .Errorf ("create database: %w" , err )
214+ }
215+ defer func () { _ = store .Close () }()
216+
217+ if err := store .SetConfig (ctx , "issue_prefix" , prefix ); err != nil {
218+ return fmt .Errorf ("set issue prefix: %w" , err )
219+ }
220+ if err := store .Commit (ctx , "bd bootstrap" ); err != nil {
221+ return fmt .Errorf ("commit: %w" , err )
222+ }
223+
224+ fmt .Fprintf (os .Stderr , "Created fresh database with prefix %q\n " , prefix )
225+ return nil
226+ }
227+
228+ func executeRestoreAction (ctx context.Context , plan BootstrapPlan , cfg * configfile.Config ) error {
229+ doltDir := doltserver .ResolveDoltDir (plan .BeadsDir )
230+ if err := os .MkdirAll (doltDir , 0o750 ); err != nil {
231+ return fmt .Errorf ("create dolt directory: %w" , err )
232+ }
233+
234+ prefix := inferPrefix (cfg )
235+ dbName := cfg .GetDoltDatabase ()
236+
237+ store , err := dolt .New (ctx , & dolt.Config {
238+ Path : doltDir ,
239+ Database : dbName ,
240+ CreateIfMissing : true ,
241+ AutoStart : true ,
242+ BeadsDir : plan .BeadsDir ,
243+ })
244+ if err != nil {
245+ return fmt .Errorf ("create database: %w" , err )
246+ }
247+ defer func () { _ = store .Close () }()
248+
249+ if err := store .SetConfig (ctx , "issue_prefix" , prefix ); err != nil {
250+ return fmt .Errorf ("set issue prefix: %w" , err )
251+ }
252+ if err := store .Commit (ctx , "bd bootstrap: init" ); err != nil {
253+ return fmt .Errorf ("commit init: %w" , err )
254+ }
255+
256+ result , err := runBackupRestore (ctx , store , plan .BackupDir , false )
257+ if err != nil {
258+ return fmt .Errorf ("restore from backup: %w" , err )
259+ }
260+
261+ fmt .Fprintf (os .Stderr , "Restored from backup: %d issues, %d comments, %d dependencies, %d labels\n " ,
262+ result .Issues , result .Comments , result .Dependencies , result .Labels )
263+ return nil
264+ }
265+
266+ func executeSyncAction (ctx context.Context , plan BootstrapPlan , cfg * configfile.Config ) error {
267+ doltDir := doltserver .ResolveDoltDir (plan .BeadsDir )
268+ dbName := cfg .GetDoltDatabase ()
269+
270+ synced , err := dolt .BootstrapFromGitRemoteWithDB (ctx , doltDir , plan .SyncRemote , dbName )
271+ if err != nil {
272+ return fmt .Errorf ("sync from remote: %w" , err )
273+ }
274+ if synced {
275+ fmt .Fprintf (os .Stderr , "Synced database from %s\n " , plan .SyncRemote )
168276 }
277+ return nil
169278}
170279
171280func inferPrefix (cfg * configfile.Config ) string {
@@ -180,5 +289,4 @@ func inferPrefix(cfg *configfile.Config) string {
180289func init () {
181290 bootstrapCmd .Flags ().Bool ("dry-run" , false , "Show what would be done without doing it" )
182291 rootCmd .AddCommand (bootstrapCmd )
183- readOnlyCommands ["bootstrap" ] = true
184292}
0 commit comments