Class: Fastlane::Helper::PharenHelper
- Inherits:
-
Object
- Object
- Fastlane::Helper::PharenHelper
- Defined in:
- lib/fastlane/plugin/pharen/helper/pharen_helper.rb
Defined Under Namespace
Classes: Failure
Constant Summary collapse
- INFO_PLIST_ENTRY =
The app's own Info.plist sits exactly at Payload/
.app/Info.plist. [^/]+ cannot span "/", so nested bundles (Watch apps, app extensions, frameworks) never match. %r{\APayload/[^/]+\.app/Info\.plist\z}
Class Method Summary collapse
-
.capture(argv, stdin: nil, chdir: nil) ⇒ Object
argv -> [stdout, stderr, exitstatus]; binmode because plists are binary.
-
.cli_failure_message(step, result) ⇒ Object
One human line for a failed run_pharen result.
-
.expand_dsym_inputs(paths) ⇒ Object
gym's DSYM_OUTPUT_PATH and download_dsyms' DSYM_PATHS are .zip files; the CLI takes .dSYM bundles (or raw DWARF binaries).
-
.fail_or_warn(strict, message) ⇒ Object
strict: abort the lane; default: warn loudly and let the build continue — a telemetry step should never be the reason a release build fails.
-
.git_commit_sha ⇒ Object
HEAD sha for
releases new --commit; nil outside a git checkout (fine — the flag is optional provenance). -
.info_plist_entry(ipa_path) ⇒ Object
Zip entry name of the app's Info.plist (shallowest Payload/*.app only).
-
.project_dir ⇒ Object
Where to run the CLI so it finds the customer's committed .pharen.yml.
-
.read_ipa_info(ipa_path) ⇒ Object
IPA -> { version:, build:, bundle_id: } (CFBundleShortVersionString, CFBundleVersion, CFBundleIdentifier), so the customer passes nothing.
-
.run_pharen(binary, args, chdir: nil) ⇒ Object
Run
<binary> <args...>and interpret the CLI's contract: JSON on stdout, exit 0 on success. -
.unzip_dsyms(zip_path) ⇒ Object
Unpack a dSYM zip into a fresh temp dir and return the .dSYM bundles inside.
Class Method Details
.capture(argv, stdin: nil, chdir: nil) ⇒ Object
argv -> [stdout, stderr, exitstatus]; binmode because plists are binary.
177 178 179 180 181 182 183 |
# File 'lib/fastlane/plugin/pharen/helper/pharen_helper.rb', line 177 def capture(argv, stdin: nil, chdir: nil) = { binmode: true } [:stdin_data] = stdin unless stdin.nil? [:chdir] = chdir unless chdir.nil? out, err, status = Open3.capture3(*argv, **) [out, err, status.exitstatus] end |
.cli_failure_message(step, result) ⇒ Object
One human line for a failed run_pharen result. Prefers the CLI's own { error: { code, message } } JSON; falls back to stderr, then the exit code.
119 120 121 122 123 124 125 126 127 128 129 130 131 132 |
# File 'lib/fastlane/plugin/pharen/helper/pharen_helper.rb', line 119 def (step, result) error = result[:json].is_a?(Hash) ? result[:json]["error"] : nil if error.is_a?(Hash) && error["message"] "#{step} failed: #{error['message']} (#{error['code']})" elsif result[:not_found] "#{step} failed: #{result[:stderr]} — install the Pharen CLI and ensure `pharen` is on PATH " \ "(see https://pharen.ai/docs for install instructions), or set the pharen_binary option" else detail = result[:stderr].to_s.strip detail = result[:stdout].to_s.strip if detail.empty? suffix = detail.empty? ? "" : ": #{detail.split("\n").last(3).join(' / ')}" "#{step} failed (exit #{result[:status]})#{suffix}" end end |
.expand_dsym_inputs(paths) ⇒ Object
gym's DSYM_OUTPUT_PATH and download_dsyms' DSYM_PATHS are .zip files; the CLI takes .dSYM bundles (or raw DWARF binaries). Expand zips, absolutize the rest — paths must be absolute because run_pharen may chdir for .pharen.yml resolution.
79 80 81 82 83 84 85 |
# File 'lib/fastlane/plugin/pharen/helper/pharen_helper.rb', line 79 def (paths) Array(paths).flat_map do |path| abs = File.(path.to_s) raise Failure, "dSYM path not found: #{path}" unless File.exist?(abs) abs.end_with?(".zip") ? unzip_dsyms(abs) : [abs] end end |
.fail_or_warn(strict, message) ⇒ Object
strict: abort the lane; default: warn loudly and let the build continue — a telemetry step should never be the reason a release build fails.
166 167 168 169 170 171 172 |
# File 'lib/fastlane/plugin/pharen/helper/pharen_helper.rb', line 166 def fail_or_warn(strict, ) if strict UI.user_error!() else UI.important("#{} — continuing because strict is false") end end |
.git_commit_sha ⇒ Object
HEAD sha for releases new --commit; nil outside a git checkout (fine — the
flag is optional provenance).
157 158 159 160 161 162 |
# File 'lib/fastlane/plugin/pharen/helper/pharen_helper.rb', line 157 def git_commit_sha out, _err, status = capture(%w[git rev-parse HEAD]) return nil unless status == 0 sha = out.strip sha.empty? ? nil : sha end |
.info_plist_entry(ipa_path) ⇒ Object
Zip entry name of the app's Info.plist (shallowest Payload/*.app only).
65 66 67 68 69 70 71 72 |
# File 'lib/fastlane/plugin/pharen/helper/pharen_helper.rb', line 65 def info_plist_entry(ipa_path) listing, err, status = capture(["unzip", "-Z1", ipa_path]) raise Failure, "could not list #{ipa_path} (unzip -Z1 exit #{status}): #{err.to_s.strip}" unless status == 0 entry = listing.split("\n").find { |line| line.match?(INFO_PLIST_ENTRY) } raise Failure, "no Payload/*.app/Info.plist inside #{ipa_path} — is this an IPA?" unless entry entry end |
.project_dir ⇒ Object
Where to run the CLI so it finds the customer's committed .pharen.yml. fastlane executes lanes from the fastlane/ directory (Dir.chdir in LaneManager), but .pharen.yml lives at the repo root by convention — step up one level when that is clearly the layout. Explicit --org/--app flags are the escape hatch for exotic layouts. (The CLI itself also walks up from its cwd looking for .pharen.yml, so this special-case is now a belt-and-suspenders optimization, not the only path — kept as-is since it still resolves correctly and needs no change.)
142 143 144 145 146 147 148 149 150 151 |
# File 'lib/fastlane/plugin/pharen/helper/pharen_helper.rb', line 142 def project_dir cwd = Dir.pwd return cwd if File.exist?(File.join(cwd, ".pharen.yml")) parent = File.("..", cwd) if File.basename(cwd) == "fastlane" && File.exist?(File.join(parent, ".pharen.yml")) parent else cwd end end |
.read_ipa_info(ipa_path) ⇒ Object
IPA -> { version:, build:, bundle_id: } (CFBundleShortVersionString, CFBundleVersion, CFBundleIdentifier), so the customer passes nothing. unzip streams the (usually binary) plist; plutil converts it to JSON on stdout.
41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
# File 'lib/fastlane/plugin/pharen/helper/pharen_helper.rb', line 41 def read_ipa_info(ipa_path) raise Failure, "IPA not found at #{ipa_path}" unless File.exist?(ipa_path) entry = info_plist_entry(ipa_path) raw, _err, status = capture(["unzip", "-p", ipa_path, entry]) raise Failure, "could not read #{entry} from #{ipa_path} (unzip exit #{status})" unless status == 0 json, perr, pstatus = capture(["plutil", "-convert", "json", "-o", "-", "-"], stdin: raw) raise Failure, "plutil could not parse Info.plist from #{ipa_path}: #{perr.to_s.strip}" unless pstatus == 0 plist = JSON.parse(json.force_encoding("UTF-8")) info = { version: plist["CFBundleShortVersionString"], build: plist["CFBundleVersion"], bundle_id: plist["CFBundleIdentifier"] } missing = info.select { |_k, v| v.nil? || v.to_s.empty? }.keys unless missing.empty? raise Failure, "Info.plist in #{ipa_path} is missing #{missing.join(', ')}" end info end |
.run_pharen(binary, args, chdir: nil) ⇒ Object
Run <binary> <args...> and interpret the CLI's contract: JSON on stdout,
exit 0 on success. binary is Shellwords-split so pharen_binary may carry a
command with arguments (e.g. "node /path/to/cli.js"). Never raises on CLI
failure — callers decide strict-vs-warn from the returned hash.
105 106 107 108 109 110 111 112 113 114 115 |
# File 'lib/fastlane/plugin/pharen/helper/pharen_helper.rb', line 105 def run_pharen(binary, args, chdir: nil) argv = Shellwords.split(binary.to_s) + args.map(&:to_s) dir = chdir || project_dir UI.command(Shellwords.join(argv)) # never contains PHAREN_AUTH_TOKEN (env-only) out, err, status = capture(argv, chdir: dir) json = parse_json(out) { ok: status == 0, status: status, stdout: out, stderr: err, json: json } rescue Errno::ENOENT => e # Spawn failed: binary (or chdir target) does not exist. { ok: false, status: nil, stdout: "", stderr: e., json: nil, not_found: true } end |
.unzip_dsyms(zip_path) ⇒ Object
Unpack a dSYM zip into a fresh temp dir and return the .dSYM bundles inside. The temp dir is left for the OS to reap — the CLI reads from it right after.
89 90 91 92 93 94 95 96 97 |
# File 'lib/fastlane/plugin/pharen/helper/pharen_helper.rb', line 89 def unzip_dsyms(zip_path) dest = Dir.mktmpdir("pharen-dsyms-") _out, err, status = capture(["unzip", "-q", "-o", zip_path, "-d", dest]) raise Failure, "could not unzip #{zip_path}: #{err.to_s.strip}" unless status == 0 found = Dir.glob(File.join(dest, "**", "*.dSYM")).sort raise Failure, "no .dSYM bundles inside #{zip_path}" if found.empty? found end |