@@ -21,10 +21,16 @@ This wizard will help you:
2121 1. Create a Google Cloud Service Account
2222 2. Enable the Google Play Android Developer API
2323 3. Download and configure your credentials
24- 4. Link the service account in Play Console` ,
24+ 4. Link the service account in Play Console
25+
26+ Use --auto to automate steps 1-4 using the gcloud CLI.` ,
2527 Run : runSetup ,
2628}
2729
30+ func init () {
31+ SetupCmd .Flags ().Bool ("auto" , false , "Automate setup using gcloud CLI (requires gcloud installed and authenticated)" )
32+ }
33+
2834type step struct {
2935 title string
3036 description string
@@ -60,6 +66,12 @@ var setupSteps = []step{
6066}
6167
6268func runSetup (cmd * cobra.Command , args []string ) {
69+ autoMode , _ := cmd .Flags ().GetBool ("auto" )
70+ if autoMode {
71+ runAutoSetup (cmd )
72+ return
73+ }
74+
6375 clearScreen ()
6476 printHeader ()
6577
@@ -346,3 +358,273 @@ func copyToClipboard(text string) error {
346358 cmd .Stdin = strings .NewReader (text )
347359 return cmd .Run ()
348360}
361+
362+ // runGcloud executes a gcloud command and returns trimmed stdout.
363+ func runGcloud (args ... string ) (string , error ) {
364+ cmd := exec .Command ("gcloud" , args ... )
365+ var stdout , stderr strings.Builder
366+ cmd .Stdout = & stdout
367+ cmd .Stderr = & stderr
368+ if err := cmd .Run (); err != nil {
369+ return "" , fmt .Errorf ("%s: %s" , err , strings .TrimSpace (stderr .String ()))
370+ }
371+ return strings .TrimSpace (stdout .String ()), nil
372+ }
373+
374+ const serviceAccountName = "play-console-cli"
375+
376+ // installGcloud attempts to install the gcloud CLI for the current platform.
377+ func installGcloud () bool {
378+ fmt .Println ()
379+ switch runtime .GOOS {
380+ case "darwin" :
381+ // Try Homebrew first
382+ if _ , err := exec .LookPath ("brew" ); err == nil {
383+ fmt .Println (" Installing via Homebrew..." )
384+ cmd := exec .Command ("brew" , "install" , "--cask" , "google-cloud-sdk" )
385+ cmd .Stdout = os .Stdout
386+ cmd .Stderr = os .Stderr
387+ if err := cmd .Run (); err != nil {
388+ fmt .Printf (" Homebrew install failed: %v\n " , err )
389+ fmt .Println (" Install manually: https://cloud.google.com/sdk/install" )
390+ return false
391+ }
392+ fmt .Println (" ✓ gcloud installed via Homebrew" )
393+ return true
394+ }
395+ // Fall through to curl installer
396+ fmt .Println (" Installing via Google Cloud SDK installer..." )
397+ cmd := exec .Command ("bash" , "-c" , "curl -fsSL https://sdk.cloud.google.com | bash -s -- --disable-prompts" )
398+ cmd .Stdout = os .Stdout
399+ cmd .Stderr = os .Stderr
400+ if err := cmd .Run (); err != nil {
401+ fmt .Printf (" Install failed: %v\n " , err )
402+ fmt .Println (" Install manually: https://cloud.google.com/sdk/install" )
403+ return false
404+ }
405+ fmt .Println (" ✓ gcloud installed" )
406+ return true
407+
408+ case "linux" :
409+ fmt .Println (" Installing via Google Cloud SDK installer..." )
410+ cmd := exec .Command ("bash" , "-c" , "curl -fsSL https://sdk.cloud.google.com | bash -s -- --disable-prompts" )
411+ cmd .Stdout = os .Stdout
412+ cmd .Stderr = os .Stderr
413+ if err := cmd .Run (); err != nil {
414+ fmt .Printf (" Install failed: %v\n " , err )
415+ fmt .Println (" Install manually: https://cloud.google.com/sdk/install" )
416+ return false
417+ }
418+ fmt .Println (" ✓ gcloud installed" )
419+ return true
420+
421+ default :
422+ fmt .Println (" Auto-install not supported on this platform." )
423+ fmt .Println (" Install manually: https://cloud.google.com/sdk/install" )
424+ return false
425+ }
426+ }
427+
428+ func runAutoSetup (cmd * cobra.Command ) {
429+ clearScreen ()
430+
431+ fmt .Println ("╔═══════════════════════════════════════════════════════════════╗" )
432+ fmt .Println ("║ MAGIC SETUP — AUTOMATED MODE ║" )
433+ fmt .Println ("╚═══════════════════════════════════════════════════════════════╝" )
434+ fmt .Println ()
435+
436+ reader := bufio .NewReader (os .Stdin )
437+
438+ // --- Check gcloud ---
439+ fmt .Print ("==> Checking gcloud CLI... " )
440+ gcloudPath , lookErr := exec .LookPath ("gcloud" )
441+ if lookErr != nil {
442+ fmt .Println ("not found" )
443+ fmt .Println ()
444+ fmt .Print (" Install gcloud now? [Y/n]: " )
445+ if input := readInput (reader ); input == "n" || input == "no" {
446+ fmt .Println ()
447+ fmt .Println ("Run 'gpc setup' without --auto for the manual wizard." )
448+ return
449+ }
450+ if ! installGcloud () {
451+ return
452+ }
453+ gcloudPath , lookErr = exec .LookPath ("gcloud" )
454+ if lookErr != nil {
455+ fmt .Println ()
456+ fmt .Println ("gcloud still not found. You may need to restart your terminal." )
457+ fmt .Println ("Then try 'gpc setup --auto' again." )
458+ return
459+ }
460+ }
461+ _ = gcloudPath
462+ fmt .Println ("✓" )
463+
464+ // --- Check auth ---
465+ fmt .Print ("==> Checking gcloud auth... " )
466+ account , err := runGcloud ("auth" , "list" , "--filter=status:ACTIVE" , "--format=value(account)" )
467+ if err != nil || account == "" {
468+ fmt .Println ("not logged in" )
469+ fmt .Println ()
470+ fmt .Println (" Launching gcloud login..." )
471+ loginCmd := exec .Command ("gcloud" , "auth" , "login" )
472+ loginCmd .Stdin = os .Stdin
473+ loginCmd .Stdout = os .Stdout
474+ loginCmd .Stderr = os .Stderr
475+ if err := loginCmd .Run (); err != nil {
476+ fmt .Println ()
477+ fmt .Println ("Login failed. Try running 'gcloud auth login' manually," )
478+ fmt .Println ("then 'gpc setup --auto' again." )
479+ return
480+ }
481+ // Re-check
482+ account , _ = runGcloud ("auth" , "list" , "--filter=status:ACTIVE" , "--format=value(account)" )
483+ if account == "" {
484+ fmt .Println ("Login did not complete. Try 'gpc setup --auto' again." )
485+ return
486+ }
487+ }
488+ fmt .Printf ("✓ (%s)\n " , account )
489+ fmt .Println ()
490+
491+ // --- Step 1: Project ---
492+ fmt .Print ("==> [1/5] Google Cloud Project... " )
493+ project , _ := runGcloud ("config" , "get-value" , "project" )
494+ if project == "" || project == "(unset)" {
495+ fmt .Println ()
496+ fmt .Print (" No project set. Enter your GCP project ID: " )
497+ project = readInput (reader )
498+ if project == "" {
499+ fmt .Println (" Project ID is required. Aborting." )
500+ return
501+ }
502+ runGcloud ("config" , "set" , "project" , project )
503+ fmt .Printf (" Set project: %s\n " , project )
504+ } else {
505+ fmt .Printf ("✓ (%s)\n " , project )
506+ }
507+
508+ // --- Step 2: Enable API ---
509+ fmt .Print ("==> [2/5] Enabling Google Play Android Developer API... " )
510+ _ , err = runGcloud ("services" , "enable" , "androidpublisher.googleapis.com" , "--project" , project )
511+ if err != nil {
512+ fmt .Println ("✗" )
513+ fmt .Printf (" Error: %v\n " , err )
514+ return
515+ }
516+ fmt .Println ("✓" )
517+
518+ // --- Step 3: Create service account ---
519+ saEmail := fmt .Sprintf ("%s@%s.iam.gserviceaccount.com" , serviceAccountName , project )
520+ fmt .Print ("==> [3/5] Creating service account... " )
521+ _ , err = runGcloud ("iam" , "service-accounts" , "create" , serviceAccountName ,
522+ "--display-name" , "Play Console CLI" ,
523+ "--project" , project )
524+ if err != nil {
525+ if strings .Contains (err .Error (), "already exists" ) {
526+ fmt .Println ("✓ (already exists)" )
527+ } else {
528+ fmt .Println ("✗" )
529+ fmt .Printf (" Error: %v\n " , err )
530+ return
531+ }
532+ } else {
533+ fmt .Println ("✓" )
534+ }
535+ fmt .Printf (" Email: %s\n " , saEmail )
536+
537+ // --- Step 4: Download key ---
538+ homeDir , _ := os .UserHomeDir ()
539+ configDir := filepath .Join (homeDir , ".config" , "gpc" )
540+ keyPath := filepath .Join (configDir , "service-account.json" )
541+
542+ fmt .Print ("==> [4/5] Downloading credentials... " )
543+
544+ // Check if key file already exists
545+ if _ , err := os .Stat (keyPath ); err == nil {
546+ fmt .Println ()
547+ fmt .Printf (" Credentials already exist at %s\n " , keyPath )
548+ fmt .Print (" Overwrite? [y/N]: " )
549+ if input := readInput (reader ); input != "y" && input != "yes" {
550+ fmt .Println (" Keeping existing credentials." )
551+ goto step5
552+ }
553+ }
554+
555+ os .MkdirAll (configDir , 0700 )
556+ _ , err = runGcloud ("iam" , "service-accounts" , "keys" , "create" , keyPath ,
557+ "--iam-account" , saEmail ,
558+ "--project" , project )
559+ if err != nil {
560+ fmt .Println ("✗" )
561+ fmt .Printf (" Error: %v\n " , err )
562+ return
563+ }
564+ os .Chmod (keyPath , 0600 )
565+ fmt .Println ("✓" )
566+ fmt .Printf (" Saved to %s\n " , keyPath )
567+
568+ step5:
569+ // --- Step 5: Manual — Grant access in Play Console ---
570+ playConsoleURL := "https://play.google.com/console/developers/api-access"
571+ fmt .Println ()
572+ fmt .Println ("==> [5/5] Grant access in Play Console" )
573+ fmt .Println ()
574+ fmt .Println (" Almost done! This is the one step that can't be automated." )
575+ fmt .Println (" Opening Play Console now..." )
576+ fmt .Println ()
577+ fmt .Println (" Just do this:" )
578+ fmt .Println (" 1. Click \" Link\" next to your Cloud project" )
579+ fmt .Printf (" 2. Find: %s\n " , saEmail )
580+ fmt .Println (" 3. Click \" Grant access\" → set permissions → \" Invite user\" " )
581+ fmt .Println ()
582+ copyToClipboard (saEmail )
583+ fmt .Println (" (Service account email copied to clipboard)" )
584+ openBrowser (playConsoleURL )
585+ waitForUser (reader , " Press ENTER when done..." )
586+
587+ // --- Configure CLI profile ---
588+ execPath , _ := os .Executable ()
589+
590+ fmt .Println ()
591+ fmt .Print ("Enter your app's package name (or ENTER to skip): " )
592+ packageName := readInput (reader )
593+
594+ fmt .Print ("==> Configuring CLI profile... " )
595+
596+ authArgs := []string {"auth" , "login" , "--name" , "default" , "--credentials" , keyPath }
597+ if packageName != "" {
598+ authArgs = append (authArgs , "--default-package" , packageName )
599+ }
600+
601+ authCmd := exec .Command (execPath , authArgs ... )
602+ authCmd .Stdout = nil
603+ authCmd .Stderr = nil
604+
605+ if err := authCmd .Run (); err != nil {
606+ fmt .Println ("✗" )
607+ fmt .Printf (" Run manually: gpc auth login --name default --credentials %s\n " , keyPath )
608+ } else {
609+ fmt .Println ("✓" )
610+ }
611+
612+ // --- Done ---
613+ fmt .Println ()
614+ fmt .Println ("╔═══════════════════════════════════════════════════════════════╗" )
615+ fmt .Println ("║ SETUP COMPLETE! ║" )
616+ fmt .Println ("╚═══════════════════════════════════════════════════════════════╝" )
617+ fmt .Println ()
618+ fmt .Println ("You can now use gpc commands:" )
619+ fmt .Println (" gpc tracks list" )
620+ fmt .Println (" gpc bundles upload --file app.aab --track internal" )
621+ fmt .Println (" gpc reviews list" )
622+ fmt .Println ()
623+ if packageName != "" {
624+ fmt .Printf ("Default package: %s\n " , packageName )
625+ } else {
626+ fmt .Println ("Tip: Use --package <name> or set GPC_PACKAGE env var" )
627+ }
628+ fmt .Println ()
629+ fmt .Println ("Run 'gpc doctor' to verify everything is connected." )
630+ }
0 commit comments