| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 996867a commit b6ec710
2 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -58,6 +58,24 @@ After install, you can use either `playconsole-cli` or the shorter alias `gpc`. | |||
| 58 | 58 | ||
| 59 | 59 | ## ⚡ Quick Start | |
| 60 | 60 | ||
| 61 | + ### One-command setup (recommended) | ||
| 62 | + | ||
| 63 | + ```bash | ||
| 64 | + gpc setup --auto | ||
| 65 | + ``` | ||
| 66 | + | ||
| 67 | + That's it. This will: | ||
| 68 | + - Install `gcloud` if needed (via Homebrew on macOS, or curl on Linux) | ||
| 69 | + - Log you into Google Cloud | ||
| 70 | + - Create a service account and download credentials | ||
| 71 | + - Open Play Console for the one manual step (granting access) | ||
| 72 | + - Configure everything automatically | ||
| 73 | + | ||
| 74 | + ### Manual setup | ||
| 75 | + | ||
| 76 | + <details> | ||
| 77 | + <summary>Prefer to do it yourself?</summary> | ||
| 78 | + | ||
| 61 | 79 | **1. Create a service account** ([Google Cloud Console](https://console.cloud.google.com/iam-admin/serviceaccounts)) | |
| 62 | 80 | ```bash | |
| 63 | 81 | mkdir -p ~/.config/gpc | |
@@ -75,7 +93,9 @@ gpc auth login --credentials ~/.config/gpc/service-account.json | |||
| 75 | 93 | gpc apps list # See your apps | |
| 76 | 94 | ``` | |
| 77 | 95 | ||
| 78 | - **5. Deploy!** 🎉 | ||
| 96 | + </details> | ||
| 97 | + | ||
| 98 | + ### Deploy! 🎉 | ||
| 79 | 99 | ```bash | |
| 80 | 100 | gpc bundles upload --file app.aab --track internal | |
| 81 | 101 | gpc tracks promote --from internal --to production --rollout 10 | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -21,10 +21,16 @@ This wizard will help you: | |||
| 21 | 21 | 1. Create a Google Cloud Service Account | |
| 22 | 22 | 2. Enable the Google Play Android Developer API | |
| 23 | 23 | 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.`, | ||
| 25 | 27 | Run: runSetup, | |
| 26 | 28 | } | |
| 27 | 29 | ||
| 30 | + func init() { | ||
| 31 | + SetupCmd.Flags().Bool("auto", false, "Automate setup using gcloud CLI (requires gcloud installed and authenticated)") | ||
| 32 | + } | ||
| 33 | + | ||
| 28 | 34 | type step struct { | |
| 29 | 35 | title string | |
| 30 | 36 | description string | |
@@ -60,6 +66,12 @@ var setupSteps = []step{ | |||
| 60 | 66 | } | |
| 61 | 67 | ||
| 62 | 68 | func runSetup(cmd *cobra.Command, args []string) { | |
| 69 | + autoMode, _ := cmd.Flags().GetBool("auto") | ||
| 70 | + if autoMode { | ||
| 71 | + runAutoSetup(cmd) | ||
| 72 | + return | ||
| 73 | + } | ||
| 74 | + | ||
| 63 | 75 | clearScreen() | |
| 64 | 76 | printHeader() | |
| 65 | 77 | ||
@@ -346,3 +358,273 @@ func copyToClipboard(text string) error { | |||
| 346 | 358 | cmd.Stdin = strings.NewReader(text) | |
| 347 | 359 | return cmd.Run() | |
| 348 | 360 | } | |
| 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 | + } | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments