mysigner 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f8cd24e9a29c341d204f6cc2d9c5cde99c6248b655bf004fa370daa78abc9cd1
4
- data.tar.gz: 92e37eff120a9a3ca8325d40b720c2878339735a368fae5cbe917017682825d7
3
+ metadata.gz: 37696e7f0c5c14aa16dab283a20bf46b35f9b0fb18d3ff6f41930c3842fca963
4
+ data.tar.gz: ee6ad5f6063feaf786513352b95903c56c5a80f3b1eb244a18bf263b24578ffe
5
5
  SHA512:
6
- metadata.gz: b81c29b7b84b9357d0419c9333d0f8b769e475366fb5d73d5baa278c7d37e0e5f2a788b2e742f9dff731a5df821de09f1b651498ec496520b7600988d60c22de
7
- data.tar.gz: f035dbf8191e61be0f411a3ed8f8894f1cb725ed3b804f3dc6a33e9be466870af2a74cb150e1551b42a4e3862b6cd4feb9858c27d0a3e36fcea72a55a372df74
6
+ metadata.gz: 1a347b8525f359d9974fc6d8846756dd82a73ae3872d2b30d539421f1939237d7a589cd128694830bb5fee48880f780c2ccfb771cc62094bf7d2089838a42ac7
7
+ data.tar.gz: efe0009decff514149523ff6c5177625496b4139aa2682f090cff5b54bab7ce51712d74886e7b92e99d6d0845f6ff228e7cbc28058bd3b1b3ccbe1832c89b35f
data/.gitignore CHANGED
@@ -1,3 +1,4 @@
1
+ *.gem
1
2
  /.bundle/
2
3
  /.yardoc
3
4
  /_yardoc/
data/README.md CHANGED
@@ -229,6 +229,76 @@ mysigner keystore activate ID # Set as active keystore
229
229
  mysigner keystore delete ID # Delete keystore
230
230
  ```
231
231
 
232
+ ### Google Play Credentials
233
+
234
+ ```bash
235
+ mysigner gp-credential list # List all credentials
236
+ mysigner gp-credential activate ID # Set as active credential
237
+ mysigner gp-credential test ID # Test Google Play connection
238
+ mysigner gp-credential delete ID # Delete credential
239
+ ```
240
+
241
+ ### App Store Releases
242
+
243
+ ```bash
244
+ mysigner release list # List release configurations
245
+ mysigner release list --bundle-id com.app # Filter by bundle ID
246
+ mysigner release show ID # Show release details
247
+ mysigner release create --bundle-id-id 42 # Create release config
248
+ mysigner release update ID --auto-submit # Update release config
249
+ mysigner release update ID --whats-new "Bug fixes"
250
+ ```
251
+
252
+ ### Google Play Tracks
253
+
254
+ ```bash
255
+ mysigner tracks com.example.app # List tracks for an app
256
+ mysigner tracks com.example.app --sort # Sort alphabetically
257
+ mysigner track com.example.app production # Show track details
258
+ mysigner track com.example.app beta # Show beta track details
259
+ ```
260
+
261
+ ### Android Apps
262
+
263
+ ```bash
264
+ mysigner android init # Detect and register Android app
265
+ mysigner android add com.example.app # Register app manually
266
+ mysigner android build # Build AAB file
267
+ mysigner android list # List registered Android apps
268
+ ```
269
+
270
+ ### Apps
271
+
272
+ ```bash
273
+ mysigner apps # List all apps (iOS + Android)
274
+ mysigner apps --platform ios # iOS apps only
275
+ mysigner apps --platform android # Android apps only
276
+ mysigner apps -q "myapp" # Search by name
277
+ ```
278
+
279
+ ### Merchant IDs
280
+
281
+ ```bash
282
+ mysigner merchant-ids # List Apple Pay Merchant IDs
283
+ mysigner merchant-id create IDENTIFIER # Create a Merchant ID
284
+ mysigner merchant-id delete IDENTIFIER # Delete a Merchant ID
285
+ ```
286
+
287
+ ### App Groups
288
+
289
+ ```bash
290
+ mysigner app-groups # List App Groups
291
+ mysigner app-group register IDENTIFIER # Register an App Group
292
+ mysigner app-group delete IDENTIFIER # Delete an App Group
293
+ ```
294
+
295
+ ### Validate Signing
296
+
297
+ ```bash
298
+ mysigner validate -b com.example.app -t development # Validate signing config
299
+ mysigner validate --type appstore # Auto-detect bundle ID
300
+ ```
301
+
232
302
  ### Sync
233
303
 
234
304
  ```bash
@@ -280,11 +350,14 @@ mysigner config set KEY VAL # Update configuration value
280
350
  - ✅ **iOS Build & Ship** (`mysigner ship testflight`, `mysigner ship appstore`)
281
351
  - ✅ **Android Build & Ship** (`mysigner ship internal/alpha/beta/production`)
282
352
  - ✅ Android keystore management (`mysigner keystore upload/download/activate`)
353
+ - ✅ Google Play credential management (`mysigner gp-credential list/activate/test/delete`)
283
354
  - ✅ Automatic version code increment for Android
284
355
  - ✅ App Store submission with release types (AFTER_APPROVAL, MANUAL, SCHEDULED)
356
+ - ✅ App Store release configuration (`mysigner release list/show/create/update`)
357
+ - ✅ Server-side signing validation (`mysigner validate`)
285
358
  - ✅ Project detection (Native iOS/Android, React Native, Flutter, Capacitor/Ionic)
286
359
  - ✅ `mysigner doctor` health check with auto-fix capabilities
287
- - ✅ 90+ RSpec tests
360
+ - ✅ 260+ RSpec tests
288
361
  - ✅ Interactive prompts and wizards
289
362
 
290
363
  📅 **Future**:
@@ -292,7 +365,6 @@ mysigner config set KEY VAL # Update configuration value
292
365
  - Progress spinners (TTY::Spinner)
293
366
  - `--json` flag for scripting
294
367
  - CI/CD templates (GitHub Actions, GitLab CI)
295
- - Phased release support
296
368
 
297
369
  See the [main project roadmap](https://github.com/jurgenleka/my-signer/blob/main/ROADMAP.md) for detailed plans.
298
370
 
@@ -97,8 +97,8 @@ module Mysigner
97
97
  title: "Bundle ID Issue",
98
98
  suggestions: [
99
99
  "Verify Bundle ID in Xcode matches Apple Developer Portal",
100
- "List Bundle IDs: mysigner bundleids",
101
- "Register new Bundle ID: mysigner bundleids create <ID>",
100
+ "List Bundle IDs: mysigner bundleid list",
101
+ "Register new Bundle ID: mysigner bundleid register <ID>",
102
102
  "Check My Signer dashboard for registered apps"
103
103
  ]
104
104
  },
@@ -193,7 +193,7 @@ module Mysigner
193
193
  title: "Keystore Not Found",
194
194
  suggestions: [
195
195
  "Upload keystore: mysigner keystore upload <path>",
196
- "List keystores: mysigner keystores",
196
+ "List keystores: mysigner keystore list",
197
197
  "Download keystore: mysigner keystore download <ID>",
198
198
  "Check keystore path in build.gradle"
199
199
  ]
@@ -324,7 +324,7 @@ module Mysigner
324
324
  suggestions: [
325
325
  "AAB is signed with a different key than expected",
326
326
  "Verify keystore matches what's registered in Play Console",
327
- "Check active keystore: mysigner keystores --active",
327
+ "Check active keystore: mysigner keystore list --active",
328
328
  "If using Google Play App Signing, upload the correct upload key"
329
329
  ]
330
330
  }
@@ -482,7 +482,7 @@ module Mysigner
482
482
  },
483
483
  profiles: "mysigner profiles",
484
484
  certificates: "mysigner certificates",
485
- keystores: "mysigner keystores",
485
+ keystores: "mysigner keystore list",
486
486
  doctor: "mysigner doctor",
487
487
  onboard: "mysigner onboard",
488
488
  login: "mysigner login"
@@ -311,7 +311,7 @@ module Mysigner
311
311
  say "💡 Keystore Not Found: How to fix", :cyan
312
312
  say ""
313
313
  say " → Upload keystore: mysigner keystore upload <path>", :yellow
314
- say " → List keystores: mysigner keystores", :yellow
314
+ say " → List keystores: mysigner keystore list", :yellow
315
315
  say " → Download keystore: mysigner keystore download <ID>", :yellow
316
316
  say " → Check keystore path in build.gradle", :yellow
317
317
  say ""
@@ -407,7 +407,7 @@ module Mysigner
407
407
  say "💡 General troubleshooting:", :cyan
408
408
  say ""
409
409
  say " → Run 'mysigner doctor' to check your setup", :yellow
410
- say " → List keystores: mysigner keystores", :yellow
410
+ say " → List keystores: mysigner keystore list", :yellow
411
411
  say " → Check Play Console: https://play.google.com/console", :yellow
412
412
  say ""
413
413
  end
@@ -823,7 +823,7 @@ module Mysigner
823
823
  say ""
824
824
  say " 📋 Quick fix:", :cyan
825
825
  say " • Get UDID: Connect device → Finder → Click serial number", :green
826
- say " • Run: mysigner device add <UDID> <NAME>", :green
826
+ say " • Run: mysigner device add <NAME> <UDID>", :green
827
827
  say " • Or add in: #{client.api_url}/organizations/#{config.current_organization_id}", :green
828
828
  say ""
829
829
  else
@@ -931,7 +931,7 @@ module Mysigner
931
931
  say "✓ Android sync started!", :green
932
932
  say ""
933
933
  say "Sync runs in the background. Check status with:", :cyan
934
- say " mysigner android-apps", :green
934
+ say " mysigner apps --platform android", :green
935
935
  say ""
936
936
 
937
937
  # Optionally wait and show status
@@ -1017,7 +1017,7 @@ module Mysigner
1017
1017
  invoke :help, ['certificate']
1018
1018
  else
1019
1019
  error "Unknown action: #{action}"
1020
- say "Available actions: download, help", :yellow
1020
+ say "Available actions: check, download, help", :yellow
1021
1021
  exit 1
1022
1022
  end
1023
1023
  end
@@ -1190,7 +1190,7 @@ module Mysigner
1190
1190
  unless keystore_id
1191
1191
  error "Usage: mysigner keystore download ID"
1192
1192
  say ""
1193
- say "Run 'mysigner keystores' to see available IDs", :yellow
1193
+ say "Run 'mysigner keystore list' to see available IDs", :yellow
1194
1194
  exit 1
1195
1195
  end
1196
1196
 
@@ -1220,7 +1220,7 @@ module Mysigner
1220
1220
  say ""
1221
1221
  say "💡 Keystore Not Found: How to fix", :cyan
1222
1222
  say ""
1223
- say " → List available keystores: mysigner keystores", :yellow
1223
+ say " → List available keystores: mysigner keystore list", :yellow
1224
1224
  say " → Upload a keystore: mysigner keystore upload <path>", :yellow
1225
1225
  say " → Check the ID is correct (IDs are numeric)", :yellow
1226
1226
  say ""
@@ -1254,7 +1254,7 @@ module Mysigner
1254
1254
  say ""
1255
1255
  say "💡 Keystore Not Found: How to fix", :cyan
1256
1256
  say ""
1257
- say " → List available keystores: mysigner keystores", :yellow
1257
+ say " → List available keystores: mysigner keystore list", :yellow
1258
1258
  say " → Upload a keystore: mysigner keystore upload <path>", :yellow
1259
1259
  say ""
1260
1260
  exit 1
@@ -1298,7 +1298,7 @@ module Mysigner
1298
1298
  say ""
1299
1299
  say "💡 Keystore Not Found: How to fix", :cyan
1300
1300
  say ""
1301
- say " → List available keystores: mysigner keystores", :yellow
1301
+ say " → List available keystores: mysigner keystore list", :yellow
1302
1302
  say " → Upload a keystore: mysigner keystore upload <path>", :yellow
1303
1303
  say ""
1304
1304
  exit 1
@@ -1307,7 +1307,7 @@ module Mysigner
1307
1307
  say ""
1308
1308
  say "💡 Activation Failed: Try these steps", :cyan
1309
1309
  say ""
1310
- say " → Verify keystore ID is correct: mysigner keystores", :yellow
1310
+ say " → Verify keystore ID is correct: mysigner keystore list", :yellow
1311
1311
  say " → Check API token is valid: mysigner status", :yellow
1312
1312
  say ""
1313
1313
  exit 1
@@ -2847,6 +2847,472 @@ module Mysigner
2847
2847
  exit 1
2848
2848
  end
2849
2849
  end
2850
+
2851
+ # ==================== GOOGLE PLAY CREDENTIALS ====================
2852
+
2853
+ desc "gp-credential SUBCOMMAND", "Manage Google Play credentials (list, delete, activate, test)"
2854
+ long_desc <<~DESC
2855
+ Manage Google Play API credentials for Android app distribution.
2856
+
2857
+ SUBCOMMANDS:
2858
+
2859
+ mysigner gp-credential list
2860
+ List all Google Play credentials
2861
+
2862
+ mysigner gp-credential delete ID
2863
+ Delete a credential
2864
+
2865
+ mysigner gp-credential activate ID
2866
+ Set a credential as the active one
2867
+
2868
+ mysigner gp-credential test ID
2869
+ Test connection to Google Play API
2870
+
2871
+ EXAMPLES:
2872
+
2873
+ # List all credentials
2874
+ mysigner gp-credential list
2875
+
2876
+ # Activate credential ID 2
2877
+ mysigner gp-credential activate 2
2878
+
2879
+ # Test if credential can connect to Google Play
2880
+ mysigner gp-credential test 1
2881
+
2882
+ # Delete old credential
2883
+ mysigner gp-credential delete 3
2884
+ DESC
2885
+ def gp_credential(action, *args)
2886
+ config = load_config
2887
+ client = create_client(config)
2888
+
2889
+ case action
2890
+ when 'list'
2891
+ say "🔑 Google Play Credentials", :cyan
2892
+ say ""
2893
+
2894
+ begin
2895
+ response = client.get("/api/v1/organizations/#{config.current_organization_id}/google_play_credentials")
2896
+ credentials = response[:data]['google_play_credentials'] || []
2897
+
2898
+ if credentials.empty?
2899
+ say "No Google Play credentials found", :yellow
2900
+ say ""
2901
+ say "Set up credentials with: mysigner onboard", :yellow
2902
+ return
2903
+ end
2904
+
2905
+ credentials.each do |cred|
2906
+ active_icon = cred['active'] ? '✓' : '○'
2907
+ active_color = cred['active'] ? :green : :white
2908
+
2909
+ say " #{active_icon} #{cred['name']} (ID: #{cred['id']})", active_color
2910
+ say " Developer Account: #{cred['developer_account_id'] || 'N/A'}"
2911
+ say " Active: #{cred['active'] ? 'Yes' : 'No'}"
2912
+ if cred['last_synced_at']
2913
+ synced = Time.parse(cred['last_synced_at']).strftime('%Y-%m-%d %H:%M')
2914
+ say " Last Synced: #{synced}"
2915
+ end
2916
+ if cred['last_sync_status']
2917
+ sync_color = cred['last_sync_status'] == 'success' ? :green : :red
2918
+ say " Sync Status: #{cred['last_sync_status']}", sync_color
2919
+ end
2920
+ say ""
2921
+ end
2922
+
2923
+ say "Total: #{credentials.count} credential(s)", :yellow
2924
+ rescue Mysigner::ClientError => e
2925
+ error "Failed to fetch credentials: #{e.message}"
2926
+ exit 1
2927
+ end
2928
+
2929
+ when 'delete'
2930
+ credential_id = args[0]
2931
+
2932
+ unless credential_id
2933
+ error "Usage: mysigner gp-credential delete ID"
2934
+ say ""
2935
+ say "Run 'mysigner gp-credential list' to see available IDs", :yellow
2936
+ exit 1
2937
+ end
2938
+
2939
+ say "⚠️ You are about to delete Google Play credential ID: #{credential_id}", :yellow
2940
+ say ""
2941
+
2942
+ if yes?("Are you sure? This cannot be undone. (y/n)")
2943
+ begin
2944
+ client.delete("/api/v1/organizations/#{config.current_organization_id}/google_play_credentials/#{credential_id}")
2945
+ say ""
2946
+ say "✓ Google Play credential deleted", :green
2947
+ rescue Mysigner::NotFoundError
2948
+ error "Credential not found with ID: #{credential_id}"
2949
+ say ""
2950
+ say "Run 'mysigner gp-credential list' to see available IDs", :yellow
2951
+ exit 1
2952
+ rescue Mysigner::ClientError => e
2953
+ error "Delete failed: #{e.message}"
2954
+ exit 1
2955
+ end
2956
+ else
2957
+ say "Deletion cancelled", :yellow
2958
+ end
2959
+
2960
+ when 'activate'
2961
+ credential_id = args[0]
2962
+
2963
+ unless credential_id
2964
+ error "Usage: mysigner gp-credential activate ID"
2965
+ say ""
2966
+ say "Run 'mysigner gp-credential list' to see available IDs", :yellow
2967
+ exit 1
2968
+ end
2969
+
2970
+ say "🔑 Activating credential...", :cyan
2971
+
2972
+ begin
2973
+ response = client.post("/api/v1/organizations/#{config.current_organization_id}/google_play_credentials/#{credential_id}/activate")
2974
+ credential = response[:data]['google_play_credential'] || response[:data]
2975
+ say "✓ Credential activated!", :green
2976
+ say ""
2977
+ say "#{credential['name']} is now the active Google Play credential", :cyan
2978
+ rescue Mysigner::NotFoundError
2979
+ error "Credential not found with ID: #{credential_id}"
2980
+ say ""
2981
+ say "Run 'mysigner gp-credential list' to see available IDs", :yellow
2982
+ exit 1
2983
+ rescue Mysigner::ClientError => e
2984
+ error "Activation failed: #{e.message}"
2985
+ exit 1
2986
+ end
2987
+
2988
+ when 'test'
2989
+ credential_id = args[0]
2990
+
2991
+ unless credential_id
2992
+ error "Usage: mysigner gp-credential test ID"
2993
+ say ""
2994
+ say "Run 'mysigner gp-credential list' to see available IDs", :yellow
2995
+ exit 1
2996
+ end
2997
+
2998
+ say "🔑 Testing credential connection...", :cyan
2999
+ say ""
3000
+
3001
+ begin
3002
+ response = client.post("/api/v1/organizations/#{config.current_organization_id}/google_play_credentials/#{credential_id}/test")
3003
+ result = response[:data]
3004
+
3005
+ if result['success']
3006
+ say "✓ Connection successful!", :green
3007
+ say ""
3008
+ say " Google Play API is reachable with this credential", :cyan
3009
+ else
3010
+ say "✗ Connection failed", :red
3011
+ say ""
3012
+ say " Error: #{result['error']}", :red if result['error']
3013
+ say ""
3014
+ say "💡 Check that:", :cyan
3015
+ say " → The service account JSON key is valid", :yellow
3016
+ say " → The service account has Google Play Console access", :yellow
3017
+ say " → API access is enabled in Google Play Console", :yellow
3018
+ exit 1
3019
+ end
3020
+ rescue Mysigner::NotFoundError
3021
+ error "Credential not found with ID: #{credential_id}"
3022
+ say ""
3023
+ say "Run 'mysigner gp-credential list' to see available IDs", :yellow
3024
+ exit 1
3025
+ rescue Mysigner::ClientError => e
3026
+ error "Test failed: #{e.message}"
3027
+ exit 1
3028
+ end
3029
+
3030
+ when 'help'
3031
+ invoke :help, ['gp-credential']
3032
+ else
3033
+ error "Unknown action: #{action}"
3034
+ say "Available actions: list, delete, activate, test, help", :yellow
3035
+ exit 1
3036
+ end
3037
+ end
3038
+
3039
+ # ==================== APP STORE RELEASES ====================
3040
+
3041
+ desc "release SUBCOMMAND", "Manage App Store release configurations (list, show, create, update)"
3042
+ long_desc <<~DESC
3043
+ Manage App Store release configurations for iOS app distribution.
3044
+
3045
+ Release configurations control how your app is submitted and released on the
3046
+ App Store, including auto-submit, phased release, and release notes.
3047
+
3048
+ SUBCOMMANDS:
3049
+
3050
+ mysigner release list [--bundle-id BUNDLE_ID]
3051
+ List all release configurations
3052
+
3053
+ mysigner release show ID
3054
+ Show details for a release configuration
3055
+
3056
+ mysigner release create --bundle-id-id ID [OPTIONS]
3057
+ Create a new release configuration
3058
+
3059
+ mysigner release update ID [OPTIONS]
3060
+ Update an existing release configuration
3061
+
3062
+ OPTIONS FOR CREATE/UPDATE:
3063
+
3064
+ --bundle-id-id ID Bundle ID record ID (required for create)
3065
+ --whats-new TEXT Release notes / What's New text
3066
+ --support-url URL Support URL
3067
+ --marketing-url URL Marketing URL
3068
+ --privacy-url URL Privacy policy URL
3069
+ --auto-submit Auto-submit for review after upload
3070
+ --phased-release Enable phased release (7-day rollout)
3071
+ --release-type TYPE Release type: manual, after_approval, scheduled
3072
+ --scheduled-date DATE Scheduled release date (ISO 8601)
3073
+
3074
+ EXAMPLES:
3075
+
3076
+ # List all release configs
3077
+ mysigner release list
3078
+
3079
+ # List releases for a specific bundle ID
3080
+ mysigner release list --bundle-id com.example.app
3081
+
3082
+ # Show details
3083
+ mysigner release show 1
3084
+
3085
+ # Create a release config
3086
+ mysigner release create --bundle-id-id 42 --auto-submit --phased-release
3087
+
3088
+ # Update release notes
3089
+ mysigner release update 1 --whats-new "Bug fixes and improvements"
3090
+
3091
+ # Set up scheduled release
3092
+ mysigner release update 1 --release-type scheduled --scheduled-date 2025-03-01T10:00:00Z
3093
+ DESC
3094
+ method_option :bundle_id, type: :string, aliases: '-b', desc: 'Filter by bundle identifier'
3095
+ method_option :bundle_id_id, type: :numeric, desc: 'Bundle ID record ID (for create)'
3096
+ method_option :whats_new, type: :string, desc: "What's New text"
3097
+ method_option :support_url, type: :string, desc: 'Support URL'
3098
+ method_option :marketing_url, type: :string, desc: 'Marketing URL'
3099
+ method_option :privacy_url, type: :string, desc: 'Privacy policy URL'
3100
+ method_option :auto_submit, type: :boolean, desc: 'Auto-submit for review'
3101
+ method_option :phased_release, type: :boolean, desc: 'Enable phased release'
3102
+ method_option :release_type, type: :string, desc: 'Release type: manual, after_approval, scheduled'
3103
+ method_option :scheduled_date, type: :string, desc: 'Scheduled release date (ISO 8601)'
3104
+ def release(action, *args)
3105
+ config = load_config
3106
+ client = create_client(config)
3107
+
3108
+ case action
3109
+ when 'list'
3110
+ say "🚀 App Store Releases", :cyan
3111
+ say ""
3112
+
3113
+ params = {}
3114
+ params[:bundle_id] = options[:bundle_id] if options[:bundle_id]
3115
+
3116
+ begin
3117
+ response = client.get(
3118
+ "/api/v1/organizations/#{config.current_organization_id}/app_store_releases",
3119
+ params: params
3120
+ )
3121
+ releases = response[:data]['app_store_releases'] || []
3122
+
3123
+ if releases.empty?
3124
+ say "No release configurations found", :yellow
3125
+ say ""
3126
+ say "Create one with: mysigner release create --bundle-id-id ID", :yellow
3127
+ return
3128
+ end
3129
+
3130
+ releases.each do |rel|
3131
+ say " • #{rel['app_name'] || rel['bundle_identifier']} (ID: #{rel['id']})", :green
3132
+ say " Bundle ID: #{rel['bundle_identifier']}" if rel['bundle_identifier']
3133
+ say " Release Type: #{rel['release_type'] || 'N/A'}"
3134
+ say " Auto Submit: #{rel['auto_submit'] ? 'Yes' : 'No'}"
3135
+ say " Phased Release: #{rel['phased_release'] ? 'Yes' : 'No'}"
3136
+ say " Version: #{rel['version_string']}" if rel['version_string']
3137
+ say ""
3138
+ end
3139
+
3140
+ say "Total: #{releases.count} release(s)", :yellow
3141
+ rescue Mysigner::ClientError => e
3142
+ error "Failed to fetch releases: #{e.message}"
3143
+ exit 1
3144
+ end
3145
+
3146
+ when 'show'
3147
+ release_id = args[0]
3148
+
3149
+ unless release_id
3150
+ error "Usage: mysigner release show ID"
3151
+ say ""
3152
+ say "Run 'mysigner release list' to see available IDs", :yellow
3153
+ exit 1
3154
+ end
3155
+
3156
+ begin
3157
+ response = client.get("/api/v1/organizations/#{config.current_organization_id}/app_store_releases/#{release_id}")
3158
+ rel = response[:data]['app_store_release'] || response[:data]
3159
+
3160
+ say "🚀 Release Configuration (ID: #{rel['id']})", :cyan
3161
+ say ""
3162
+ say "Details:", :bold
3163
+ say " App Name: #{rel['app_name'] || 'N/A'}"
3164
+ say " Bundle ID: #{rel['bundle_identifier'] || 'N/A'}"
3165
+ say " Version: #{rel['version_string'] || 'N/A'}"
3166
+ say " Release Type: #{rel['release_type'] || 'N/A'}"
3167
+ say " Auto Submit: #{rel['auto_submit'] ? 'Yes' : 'No'}"
3168
+ say " Phased Release: #{rel['phased_release'] ? 'Yes' : 'No'}"
3169
+ say ""
3170
+ if rel['whats_new'] && !rel['whats_new'].empty?
3171
+ say "What's New:", :bold
3172
+ say " #{rel['whats_new']}"
3173
+ say ""
3174
+ end
3175
+ say "URLs:", :bold
3176
+ say " Support: #{rel['support_url'] || 'N/A'}"
3177
+ say " Marketing: #{rel['marketing_url'] || 'N/A'}"
3178
+ say " Privacy: #{rel['privacy_url'] || 'N/A'}"
3179
+ if rel['scheduled_date']
3180
+ say ""
3181
+ say "Scheduled Date: #{rel['scheduled_date']}"
3182
+ end
3183
+ rescue Mysigner::NotFoundError
3184
+ error "Release not found with ID: #{release_id}"
3185
+ say ""
3186
+ say "Run 'mysigner release list' to see available IDs", :yellow
3187
+ exit 1
3188
+ rescue Mysigner::ClientError => e
3189
+ error "Failed to fetch release: #{e.message}"
3190
+ exit 1
3191
+ end
3192
+
3193
+ when 'create'
3194
+ say "🚀 Creating release configuration...", :cyan
3195
+ say ""
3196
+
3197
+ body = {}
3198
+ body[:bundle_id_id] = options[:bundle_id_id] if options[:bundle_id_id]
3199
+ body[:whats_new] = options[:whats_new] if options[:whats_new]
3200
+ body[:support_url] = options[:support_url] if options[:support_url]
3201
+ body[:marketing_url] = options[:marketing_url] if options[:marketing_url]
3202
+ body[:privacy_url] = options[:privacy_url] if options[:privacy_url]
3203
+ body[:auto_submit] = options[:auto_submit] unless options[:auto_submit].nil?
3204
+ body[:phased_release] = options[:phased_release] unless options[:phased_release].nil?
3205
+ body[:release_type] = options[:release_type] if options[:release_type]
3206
+ body[:scheduled_date] = options[:scheduled_date] if options[:scheduled_date]
3207
+
3208
+ begin
3209
+ response = client.post(
3210
+ "/api/v1/organizations/#{config.current_organization_id}/app_store_releases",
3211
+ body: body
3212
+ )
3213
+ rel = response[:data]['app_store_release'] || response[:data]
3214
+
3215
+ say "✓ Release configuration created!", :green
3216
+ say ""
3217
+ say "Details:", :bold
3218
+ say " ID: #{rel['id']}"
3219
+ say " Bundle ID: #{rel['bundle_identifier'] || 'N/A'}"
3220
+ say " Release Type: #{rel['release_type'] || 'N/A'}"
3221
+ say " Auto Submit: #{rel['auto_submit'] ? 'Yes' : 'No'}"
3222
+ say " Phased Release: #{rel['phased_release'] ? 'Yes' : 'No'}"
3223
+ rescue Mysigner::ValidationError => e
3224
+ if e.message.include?('already exists') || (e.error_code && e.error_code.to_s == '409')
3225
+ error "Release configuration already exists for this bundle ID"
3226
+ say ""
3227
+ say "💡 Use 'mysigner release update ID' to modify it", :cyan
3228
+ say " Run 'mysigner release list' to find the ID", :yellow
3229
+ else
3230
+ error "Validation failed: #{e.message}"
3231
+ if e.details
3232
+ e.details.each do |field, errors|
3233
+ errors_text = errors.is_a?(Array) ? errors.join(', ') : errors.to_s
3234
+ say " #{field}: #{errors_text}", :red
3235
+ end
3236
+ end
3237
+ end
3238
+ exit 1
3239
+ rescue Mysigner::ClientError => e
3240
+ if e.message.include?('409') || e.message.include?('already exists')
3241
+ error "Release configuration already exists for this bundle ID"
3242
+ say ""
3243
+ say "💡 Use 'mysigner release update ID' to modify it", :cyan
3244
+ say " Run 'mysigner release list' to find the ID", :yellow
3245
+ else
3246
+ error "Failed to create release: #{e.message}"
3247
+ end
3248
+ exit 1
3249
+ end
3250
+
3251
+ when 'update'
3252
+ release_id = args[0]
3253
+
3254
+ unless release_id
3255
+ error "Usage: mysigner release update ID [OPTIONS]"
3256
+ say ""
3257
+ say "Run 'mysigner release list' to see available IDs", :yellow
3258
+ exit 1
3259
+ end
3260
+
3261
+ body = {}
3262
+ body[:whats_new] = options[:whats_new] if options[:whats_new]
3263
+ body[:support_url] = options[:support_url] if options[:support_url]
3264
+ body[:marketing_url] = options[:marketing_url] if options[:marketing_url]
3265
+ body[:privacy_url] = options[:privacy_url] if options[:privacy_url]
3266
+ body[:auto_submit] = options[:auto_submit] unless options[:auto_submit].nil?
3267
+ body[:phased_release] = options[:phased_release] unless options[:phased_release].nil?
3268
+ body[:release_type] = options[:release_type] if options[:release_type]
3269
+ body[:scheduled_date] = options[:scheduled_date] if options[:scheduled_date]
3270
+
3271
+ say "🚀 Updating release configuration...", :cyan
3272
+ say ""
3273
+
3274
+ begin
3275
+ response = client.patch(
3276
+ "/api/v1/organizations/#{config.current_organization_id}/app_store_releases/#{release_id}",
3277
+ body: body
3278
+ )
3279
+ rel = response[:data]['app_store_release'] || response[:data]
3280
+
3281
+ say "✓ Release configuration updated!", :green
3282
+ say ""
3283
+ say "Details:", :bold
3284
+ say " ID: #{rel['id']}"
3285
+ say " Bundle ID: #{rel['bundle_identifier'] || 'N/A'}"
3286
+ say " Release Type: #{rel['release_type'] || 'N/A'}"
3287
+ say " Auto Submit: #{rel['auto_submit'] ? 'Yes' : 'No'}"
3288
+ say " Phased Release: #{rel['phased_release'] ? 'Yes' : 'No'}"
3289
+ rescue Mysigner::NotFoundError
3290
+ error "Release not found with ID: #{release_id}"
3291
+ say ""
3292
+ say "Run 'mysigner release list' to see available IDs", :yellow
3293
+ exit 1
3294
+ rescue Mysigner::ValidationError => e
3295
+ error "Validation failed: #{e.message}"
3296
+ if e.details
3297
+ e.details.each do |field, errors|
3298
+ errors_text = errors.is_a?(Array) ? errors.join(', ') : errors.to_s
3299
+ say " #{field}: #{errors_text}", :red
3300
+ end
3301
+ end
3302
+ exit 1
3303
+ rescue Mysigner::ClientError => e
3304
+ error "Failed to update release: #{e.message}"
3305
+ exit 1
3306
+ end
3307
+
3308
+ when 'help'
3309
+ invoke :help, ['release']
3310
+ else
3311
+ error "Unknown action: #{action}"
3312
+ say "Available actions: list, show, create, update, help", :yellow
3313
+ exit 1
3314
+ end
3315
+ end
2850
3316
  end
2851
3317
  end
2852
3318
  end
@@ -0,0 +1,161 @@
1
+ module Mysigner
2
+ class CLI < Thor
3
+ module ValidateCommands
4
+ def self.included(base)
5
+ base.class_eval do
6
+ desc "validate", "Validate signing configuration on the server"
7
+ long_desc <<~DESC
8
+ Check if your bundle ID, certificate, and provisioning profile exist and
9
+ are valid on the My Signer server.
10
+
11
+ WHY VALIDATE?
12
+
13
+ The CLI does local keychain/certificate validation, but doesn't check if
14
+ your signing assets exist on the server. This catches "forgot to sync"
15
+ or "profile expired" errors before a build starts.
16
+
17
+ OPTIONS:
18
+
19
+ --bundle-id / -b Bundle identifier (e.g., com.example.app)
20
+ Auto-detected from Xcode project if not provided
21
+
22
+ --type / -t Signing type: development, appstore, adhoc, inhouse
23
+
24
+ EXAMPLES:
25
+
26
+ # Validate development signing for an app
27
+ mysigner validate --bundle-id com.example.app --type development
28
+
29
+ # Validate App Store signing
30
+ mysigner validate -b com.example.app -t appstore
31
+
32
+ # Auto-detect bundle ID from current project
33
+ mysigner validate --type development
34
+ DESC
35
+ method_option :bundle_id, type: :string, aliases: '-b', desc: 'Bundle identifier (e.g., com.example.app)'
36
+ method_option :type, type: :string, aliases: '-t', desc: 'Signing type: development, appstore, adhoc, inhouse'
37
+ def validate
38
+ config = load_config
39
+ client = create_client(config)
40
+
41
+ bundle_id = options[:bundle_id] || detect_bundle_id_from_project
42
+ signing_type = options[:type]
43
+
44
+ unless bundle_id
45
+ error "Bundle ID is required. Use --bundle-id or run from an Xcode project directory."
46
+ say ""
47
+ say "Example: mysigner validate --bundle-id com.example.app --type development", :yellow
48
+ exit 1
49
+ end
50
+
51
+ unless signing_type
52
+ error "Signing type is required. Use --type with one of: development, appstore, adhoc, inhouse"
53
+ say ""
54
+ say "Example: mysigner validate --bundle-id #{bundle_id} --type development", :yellow
55
+ exit 1
56
+ end
57
+
58
+ valid_types = %w[development appstore adhoc inhouse]
59
+ unless valid_types.include?(signing_type)
60
+ error "Invalid signing type: #{signing_type}"
61
+ say "Valid types: #{valid_types.join(', ')}", :yellow
62
+ exit 1
63
+ end
64
+
65
+ say "🔍 Validating signing configuration...", :cyan
66
+ say ""
67
+ say " Bundle ID: #{bundle_id}", :white
68
+ say " Type: #{signing_type}", :white
69
+ say ""
70
+
71
+ begin
72
+ response = client.post(
73
+ "/api/v1/organizations/#{config.current_organization_id}/validate",
74
+ body: {
75
+ bundle_id: bundle_id,
76
+ type: signing_type
77
+ }
78
+ )
79
+
80
+ result = response[:data]
81
+ checks = result['checks'] || {}
82
+ valid = result['valid']
83
+
84
+ # Display each check
85
+ %w[bundle_id certificate profile].each do |check_name|
86
+ check = checks[check_name]
87
+ next unless check
88
+
89
+ if check['status'] == 'pass'
90
+ say " ✓ #{check_name.tr('_', ' ').capitalize}: #{check['message']}", :green
91
+ else
92
+ say " ✗ #{check_name.tr('_', ' ').capitalize}: #{check['message']}", :red
93
+ end
94
+ end
95
+
96
+ say ""
97
+
98
+ if valid
99
+ say "✓ All checks passed! Signing configuration is valid.", :green
100
+ else
101
+ say "✗ Validation failed. Some checks did not pass.", :red
102
+
103
+ suggestions = result['suggestions'] || []
104
+ if suggestions.any?
105
+ say ""
106
+ say "💡 Suggestions:", :cyan
107
+ suggestions.each do |suggestion|
108
+ say " → #{suggestion}", :yellow
109
+ end
110
+ end
111
+
112
+ exit 1
113
+ end
114
+ rescue Mysigner::NotFoundError => e
115
+ error "Not found: #{e.message}"
116
+ say ""
117
+ say "💡 Make sure your bundle ID is synced:", :cyan
118
+ say " → Run 'mysigner sync ios' to sync from Apple Developer Portal", :yellow
119
+ say " → Run 'mysigner bundleid list' to list registered bundle IDs", :yellow
120
+ exit 1
121
+ rescue Mysigner::ValidationError => e
122
+ error "Validation error: #{e.message}"
123
+ if e.details
124
+ e.details.each do |field, errors|
125
+ errors_text = errors.is_a?(Array) ? errors.join(', ') : errors.to_s
126
+ say " #{field}: #{errors_text}", :red
127
+ end
128
+ end
129
+ exit 1
130
+ rescue Mysigner::ClientError => e
131
+ error "Validation request failed: #{e.message}"
132
+ say ""
133
+ say "💡 Try these steps:", :cyan
134
+ say " → Check your network connection", :yellow
135
+ say " → Verify API token: mysigner status", :yellow
136
+ exit 1
137
+ end
138
+ end
139
+
140
+ private
141
+
142
+ def detect_bundle_id_from_project
143
+ # Try to find bundle ID from Xcode project in current directory
144
+ pbxproj_files = Dir.glob('**/*.pbxproj')
145
+ return nil if pbxproj_files.empty?
146
+
147
+ pbxproj_files.each do |file|
148
+ content = File.read(file)
149
+ match = content.match(/PRODUCT_BUNDLE_IDENTIFIER\s*=\s*"?([^;"]+)"?/)
150
+ return match[1].strip if match
151
+ end
152
+
153
+ nil
154
+ rescue StandardError
155
+ nil
156
+ end
157
+ end
158
+ end
159
+ end
160
+ end
161
+ end
data/lib/mysigner/cli.rb CHANGED
@@ -11,6 +11,7 @@ require_relative 'cli/auth_commands'
11
11
  require_relative 'cli/diagnostic_commands'
12
12
  require_relative 'cli/build_commands'
13
13
  require_relative 'cli/resource_commands'
14
+ require_relative 'cli/validate_commands'
14
15
 
15
16
  module Mysigner
16
17
  class CLI < Thor
@@ -31,6 +32,7 @@ module Mysigner
31
32
  include DiagnosticCommands
32
33
  include BuildCommands
33
34
  include ResourceCommands
35
+ include ValidateCommands
34
36
 
35
37
  # Command aliases for power users
36
38
  map 's' => :ship
@@ -1,3 +1,3 @@
1
1
  module Mysigner
2
- VERSION = "0.1.1"
2
+ VERSION = "0.1.2"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mysigner
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jurgen Leka
@@ -234,6 +234,7 @@ files:
234
234
  - lib/mysigner/cli/concerns/helpers.rb
235
235
  - lib/mysigner/cli/diagnostic_commands.rb
236
236
  - lib/mysigner/cli/resource_commands.rb
237
+ - lib/mysigner/cli/validate_commands.rb
237
238
  - lib/mysigner/client.rb
238
239
  - lib/mysigner/config.rb
239
240
  - lib/mysigner/export/exporter.rb