mysigner 0.3.3 → 0.3.4

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: dd7914051f5e940662b457a809c0b0ec9baee91e2e64162a132c0f17aa96b26d
4
- data.tar.gz: ea5c0fcbf2c30caa927febbf4df5901c0c575158960b6b79bc7480ff6c0bc232
3
+ metadata.gz: c8d828d29516e7df219e7a43cc21f66fe5dcfdce881f7ffb2168217626b6e204
4
+ data.tar.gz: 75444b456a228e57784a3b5376b57da4abe1963550b53b735d32836de3d0e981
5
5
  SHA512:
6
- metadata.gz: a0d0082a42826381b281ee0c62f757958e0a3a0b9ddac498545c28622238ab3ca4e9c52840b8c24012852e959a610fce7d24e3a432fd76b18ba919813fc12585
7
- data.tar.gz: a611e25b8d2b07357f9eed035ab7c457ff73226ffce9b98ac862e185d3eebfe5bc5c84eb8ea3130b1bf874ecce95024c18113af33d97dddd5954f580aa2141c1
6
+ metadata.gz: 6aa433a444788a4205115f0165d4d264137921b6c06d00af81b4096272297141f1c275f848a798e8327e64d0151b50611ba7b313d7ceaa7a10ead7854a70863f
7
+ data.tar.gz: 18c7b935f4d59817d9286fbb95c9b9d21803c17930b74bb770fda57494c862a064764abfd3cd2000e5a2ec1c5738823d5df83021febc00adb90d2a39727db909
data/CHANGELOG.md CHANGED
@@ -5,6 +5,16 @@ All notable changes to My Signer CLI will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.3.4] - 2026-06-25
9
+
10
+ ### Added
11
+ - Smart dependency setup for Expo / React-Native projects: if `node_modules` is missing, `mysigner ship` / `android build` now detect the project's package manager from the lockfile (npm/yarn/pnpm/bun), suggest the EXACT install command, and offer to run it interactively — or auto-run with `--setup` / `MYSIGNER_AUTO_SETUP=1` for CI. It never silently installs (a wrong package manager can corrupt the lockfile).
12
+
13
+ ### Fixed
14
+ - No more raw backtraces: a top-level safety net turns any unhandled error into one clean line (full trace under `DEBUG=1`). Specific crash fixes: `onboard --local-only` on a mistyped key/keystore path, `switch` on non-interactive input, and the startup legacy-key cleanup on a read-only HOME.
15
+ - On an Expo prebuild failure, the precise "run `<pm> install`" message is no longer followed by a generic, irrelevant "No Android Project Found → check build.gradle" checklist; that checklist now appears only when there is genuinely no Android project.
16
+ - React-Native pre-build dependency install now uses the project's own package manager (not a blind `npm install` that can corrupt a yarn/pnpm lockfile), shows its output, and fails loudly instead of dying later with a cryptic Gradle error.
17
+
8
18
  ## [0.3.3] - 2026-06-25
9
19
 
10
20
  ### Fixed
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- mysigner (0.3.3)
4
+ mysigner (0.3.4)
5
5
  base64 (~> 0.2)
6
6
  faraday (~> 2.14)
7
7
  faraday-retry (~> 2.2)
data/exe/mysigner CHANGED
@@ -108,4 +108,13 @@ rescue Errno::ENOENT => e
108
108
  warn 'On Linux or Windows you can still build Android: mysigner ship internal --platform android'
109
109
  warn e.full_message(highlight: false) if ENV['DEBUG']
110
110
  exit 1
111
+ rescue StandardError => e
112
+ # Last-resort safety net: a published CLI should never dump a raw Ruby
113
+ # backtrace at the user. Turn any unhandled error into one clean line; show the
114
+ # trace only under DEBUG. (SystemExit / Interrupt are not StandardError, so
115
+ # normal `exit` and Ctrl-C still pass through untouched.)
116
+ warn "✗ Something went wrong: #{e.message}"
117
+ warn 'Run the same command with DEBUG=1 for details, or `mysigner doctor` to check your setup.'
118
+ warn e.full_message(highlight: false) if ENV['DEBUG']
119
+ exit 1
111
120
  end
@@ -250,14 +250,17 @@ module Mysigner
250
250
  system('npx cap sync android > /dev/null 2>&1')
251
251
  end
252
252
  when :react_native
253
- # React Native: might need to install dependencies
254
- if File.exist?(File.join(@project_info[:directory], 'node_modules'))
255
- # Dependencies already installed
256
- else
257
- puts '📦 Installing npm dependencies...'
258
- Dir.chdir(@project_info[:directory]) do
259
- system('npm install > /dev/null 2>&1') || system('yarn install > /dev/null 2>&1')
260
- end
253
+ # React Native: install JS deps if missing. Use the project's OWN
254
+ # package manager (blindly running `npm install` on a yarn/pnpm project
255
+ # corrupts the lockfile), keep output visible, and fail loud — a silent
256
+ # install failure otherwise surfaces as a cryptic Gradle autolink error.
257
+ dir = @project_info[:directory]
258
+ unless File.exist?(File.join(dir, 'node_modules'))
259
+ require 'mysigner/build/detector'
260
+ cmd = Detector.install_command(Detector.detect_package_manager(dir))
261
+ puts "📦 Installing JavaScript dependencies (#{cmd})..."
262
+ ok = Dir.chdir(dir) { system(*cmd.split) }
263
+ raise BuildError, "`#{cmd}` failed — install dependencies manually, then re-run." unless ok
261
264
  end
262
265
  when :flutter
263
266
  # Flutter: ensure dependencies are fetched
@@ -83,9 +83,9 @@ module Mysigner
83
83
  # Fail fast (before shelling out) for the two common prebuild blockers.
84
84
  def self.ensure_expo_prereqs!(directory, platform)
85
85
  unless Dir.exist?("#{directory}/node_modules/expo")
86
+ install = install_command(detect_package_manager(directory))
86
87
  raise NoProjectError, expo_prebuild_failed_message(
87
- platform, hint: 'The `expo` package is not installed. Run `npm install` ' \
88
- '(or yarn/pnpm install) in the project first.'
88
+ platform, hint: "JavaScript dependencies aren't installed. Run `#{install}` in the project first."
89
89
  )
90
90
  end
91
91
 
@@ -106,6 +106,22 @@ module Mysigner
106
106
  nil
107
107
  end
108
108
 
109
+ # Detect the JS package manager from the lockfile so we suggest the EXACT
110
+ # install command for this project. Running the wrong one (e.g. `npm
111
+ # install` on a yarn/pnpm project) can corrupt the dependency tree, so we
112
+ # never guess blindly.
113
+ def self.detect_package_manager(directory)
114
+ return :yarn if File.exist?("#{directory}/yarn.lock")
115
+ return :pnpm if File.exist?("#{directory}/pnpm-lock.yaml")
116
+ return :bun if File.exist?("#{directory}/bun.lockb") || File.exist?("#{directory}/bun.lock")
117
+
118
+ :npm
119
+ end
120
+
121
+ def self.install_command(pkg_manager)
122
+ { yarn: 'yarn install', pnpm: 'pnpm install', bun: 'bun install' }.fetch(pkg_manager, 'npm install')
123
+ end
124
+
109
125
  def self.expo_prebuild_failed_message(platform, hint: nil)
110
126
  native = platform == :android ? 'Android' : 'iOS'
111
127
  parts = ["Failed to generate #{native} project with expo prebuild."]
@@ -34,6 +34,11 @@ module Mysigner
34
34
 
35
35
  FileUtils.mkdir_p(File.dirname(marker_path))
36
36
  FileUtils.touch(marker_path)
37
+ rescue SystemCallError => e
38
+ # A read-only / unwritable HOME must never abort startup (this runs at
39
+ # require time, before any command). The purger is idempotent, so it
40
+ # simply retries next run. Surface only under verbose.
41
+ warn "mysigner: skipped legacy-key cleanup (#{e.class})" if ENV['MYSIGNER_VERBOSE'] == '1'
37
42
  end
38
43
  end
39
44
  end
@@ -227,7 +227,16 @@ module Mysigner
227
227
  # untouched for backward compatibility.
228
228
  if local_only?
229
229
  emit_local_only_banner
230
- return onboard_local_only
230
+ begin
231
+ return onboard_local_only
232
+ rescue LocalOnlyOnboardError => e
233
+ # Ordinary user mistakes (mistyped key path, empty alias, etc.)
234
+ # must read as a clean message, not a raw backtrace.
235
+ error e.message
236
+ say ''
237
+ say "Re-run 'mysigner --local-only onboard' once that's fixed.", :yellow
238
+ exit 1
239
+ end
231
240
  end
232
241
 
233
242
  say '🚀 My Signer Setup Guide', :cyan
@@ -971,7 +980,13 @@ module Mysigner
971
980
  end
972
981
  match
973
982
  else
974
- org_index = ask("Select organization (1-#{organizations_list.length}, or 'q' to cancel):")
983
+ org_index = ask("Select organization (1-#{organizations_list.length}, or 'q' to cancel):").to_s.strip
984
+
985
+ if org_index.empty?
986
+ say 'No selection. To switch non-interactively, pass an org ID:', :yellow
987
+ say ' mysigner switch <ID>', :cyan
988
+ return
989
+ end
975
990
 
976
991
  if org_index.downcase == 'q'
977
992
  say 'Cancelled', :yellow
@@ -1003,7 +1018,9 @@ module Mysigner
1003
1018
  say ' 3. Paste it below'
1004
1019
  say ''
1005
1020
 
1006
- new_token = ask("Paste API token for '#{selected_org[:name]}' (or 'q' to cancel):", echo: false)
1021
+ # .to_s.strip so a non-tty/empty paste can't crash on nil.downcase
1022
+ # (and stray whitespace around a pasted token is trimmed).
1023
+ new_token = ask("Paste API token for '#{selected_org[:name]}' (or 'q' to cancel):", echo: false).to_s.strip
1007
1024
  say ''
1008
1025
 
1009
1026
  if new_token.downcase == 'q' || new_token.empty?
@@ -113,6 +113,8 @@ module Mysigner
113
113
  # (e.g. https://appstoreconnect.apple.com/apps/<APPLE_APP_ID>/...).
114
114
  method_option :apple_id, type: :string, banner: 'APPLE_APP_ID',
115
115
  desc: 'App Store Connect app id (overrides bundleId lookup in --local-only mode)'
116
+ method_option :setup, type: :boolean, default: false,
117
+ desc: 'Auto-install missing JS dependencies (npm/yarn/pnpm) without prompting'
116
118
  def ship(target)
117
119
  ios_targets = %w[testflight appstore]
118
120
  android_targets = %w[internal alpha beta production]
@@ -921,6 +923,10 @@ module Mysigner
921
923
  config = load_config
922
924
  client = create_client(config)
923
925
 
926
+ # Expo / React-Native projects need JS deps installed before the
927
+ # native build — offer to install them (or auto with --setup).
928
+ maybe_install_node_deps!(Dir.pwd)
929
+
924
930
  overall_start = Time.now
925
931
  timings = {}
926
932
  aab_path = nil
@@ -1334,13 +1340,18 @@ module Mysigner
1334
1340
  say ''
1335
1341
  say "Error: #{e.message}", :red
1336
1342
  say ''
1337
- say '💡 No Android Project Found: How to fix', :cyan
1338
- say ''
1339
- say " Make sure you're in an Android project directory", :yellow
1340
- say ' Check for build.gradle or build.gradle.kts file', :yellow
1341
- say ' For React Native: cd android && check build.gradle exists', :yellow
1342
- say ' → For Flutter: check android/app/build.gradle exists', :yellow
1343
- say ''
1343
+ # The framework-specific detector errors (Expo/Capacitor/RN/Flutter)
1344
+ # already name the exact fix; only show the generic "where's your
1345
+ # android project" checklist for the truly-generic case.
1346
+ if e.message.include?('No Android project found')
1347
+ say '💡 No Android Project Found: How to fix', :cyan
1348
+ say ''
1349
+ say " → Make sure you're in an Android project directory", :yellow
1350
+ say ' → Check for build.gradle or build.gradle.kts file', :yellow
1351
+ say ' → For React Native: cd android && check build.gradle exists', :yellow
1352
+ say ' → For Flutter: check android/app/build.gradle exists', :yellow
1353
+ say ''
1354
+ end
1344
1355
  exit 1
1345
1356
  rescue Upload::PlayStoreUploader::MissingLocalCredentialsError => e
1346
1357
  # mysigner-43 — local-only requested but no credentials stored.
@@ -187,6 +187,68 @@ module Mysigner
187
187
  exit 1
188
188
  end
189
189
 
190
+ # For an Expo / React-Native project, the native android build can't run
191
+ # until JS dependencies are installed. Rather than failing deep inside
192
+ # `expo prebuild`, detect it up front and: auto-run with --setup /
193
+ # MYSIGNER_AUTO_SETUP, OR offer to run it interactively, OR print the
194
+ # EXACT install command for this project's package manager and exit.
195
+ # We never silently run a package install (it's slow and the wrong
196
+ # manager can corrupt the lockfile) — only with consent or an opt-in.
197
+ def maybe_install_node_deps!(project_dir = Dir.pwd)
198
+ require 'json'
199
+ require 'mysigner/build/detector'
200
+
201
+ pkg_path = File.join(project_dir, 'package.json')
202
+ return unless File.exist?(pkg_path)
203
+ return if Dir.exist?(File.join(project_dir, 'node_modules'))
204
+
205
+ pkg = begin
206
+ JSON.parse(File.read(pkg_path))
207
+ rescue StandardError
208
+ {}
209
+ end
210
+ deps = {}
211
+ deps.merge!(pkg['dependencies']) if pkg['dependencies'].is_a?(Hash)
212
+ deps.merge!(pkg['devDependencies']) if pkg['devDependencies'].is_a?(Hash)
213
+ # Only Expo / React-Native projects need node deps to produce a native
214
+ # Android build; a plain native/Flutter project does not.
215
+ return unless deps.key?('expo') || deps.key?('react-native')
216
+
217
+ cmd = Mysigner::Build::Detector.install_command(
218
+ Mysigner::Build::Detector.detect_package_manager(project_dir)
219
+ )
220
+
221
+ auto = setup_requested?
222
+ auto ||= $stdin.tty? && yes_with_default?(
223
+ "JavaScript dependencies aren't installed (no node_modules). Run `#{cmd}` now?", :cyan
224
+ )
225
+
226
+ unless auto
227
+ error "JavaScript dependencies aren't installed (no node_modules)."
228
+ say 'Install them first, then re-run:', :yellow
229
+ say " #{cmd}", :cyan
230
+ say '(or pass --setup / set MYSIGNER_AUTO_SETUP=1 to let mysigner run it for you)', :yellow
231
+ exit 1
232
+ end
233
+
234
+ say "📦 Installing JavaScript dependencies: #{cmd}", :cyan
235
+ ok = Dir.chdir(project_dir) { system(*cmd.split) }
236
+ unless ok
237
+ error "`#{cmd}` failed — install dependencies manually, then re-run."
238
+ exit 1
239
+ end
240
+ say '✓ Dependencies installed.', :green
241
+ say ''
242
+ end
243
+
244
+ # Whether the user opted into automatic setup (package install / prebuild)
245
+ # via the --setup flag or MYSIGNER_AUTO_SETUP=1. Commands without a
246
+ # --setup option still honour the env var.
247
+ def setup_requested?
248
+ flag = respond_to?(:options) && options.respond_to?(:[]) ? options[:setup] : nil
249
+ flag == true || ENV['MYSIGNER_AUTO_SETUP'] == '1'
250
+ end
251
+
190
252
  # Local-only mode is active when any of, in precedence order:
191
253
  # 1. --local-only / --no-local-only flag on this invocation
192
254
  # 2. MYSIGNER_LOCAL_ONLY env var
@@ -1647,6 +1647,10 @@ module Mysigner
1647
1647
  project_dir = Dir.pwd
1648
1648
  is_expo = expo_project?(project_dir)
1649
1649
 
1650
+ # Expo / React-Native: offer to install JS deps (or auto with
1651
+ # --setup / MYSIGNER_AUTO_SETUP) before the native build.
1652
+ maybe_install_node_deps!(project_dir)
1653
+
1650
1654
  # For Expo, we may need to regenerate android folder with correct versionCode
1651
1655
  # Get package name from app.json first if Expo
1652
1656
  if is_expo
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Mysigner
4
- VERSION = '0.3.3'
4
+ VERSION = '0.3.4'
5
5
  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.3.3
4
+ version: 0.3.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jurgen Leka