mirror of
https://github.com/Sea-Haven-Industries/proposal-system.git
synced 2026-09-30 07:43:14 +00:00
Write .xcode.env.local with explicit node path so the "Bundle React Native code and images" build phase can find node. Xcode build phases don't inherit the GitHub Actions PATH. Also disable xcbeautify for this run to see raw xcodebuild output for debugging.
153 lines
5.1 KiB
Ruby
153 lines
5.1 KiB
Ruby
require 'openssl'
|
|
require 'base64'
|
|
require 'tempfile'
|
|
|
|
# OpenSSL 3.x rejects PKCS#8 keys via EC.new ("invalid curve name").
|
|
# Prepend a wrapper that falls back to PKey.read for both formats.
|
|
module OpenSSLECNewFix
|
|
def new(arg = nil, *rest)
|
|
super
|
|
rescue OpenSSL::PKey::ECError
|
|
raise if arg.nil? || !arg.is_a?(String)
|
|
OpenSSL::PKey.read(arg)
|
|
end
|
|
end
|
|
OpenSSL::PKey::EC.singleton_class.prepend(OpenSSLECNewFix)
|
|
|
|
def normalize_p8_key(raw)
|
|
candidates = []
|
|
|
|
cleaned = raw.gsub("\r", "")
|
|
with_newlines = cleaned.gsub('\n', "\n")
|
|
candidates << ["raw (newlines normalized)", with_newlines]
|
|
|
|
if with_newlines.include?("BEGIN")
|
|
b64_body = with_newlines.gsub(/-----(?:BEGIN|END)[^-]+-----/, '').gsub(/\s+/, '')
|
|
rewrapped = "-----BEGIN PRIVATE KEY-----\n#{b64_body.scan(/.{1,64}/).join("\n")}\n-----END PRIVATE KEY-----\n"
|
|
candidates << ["rewrapped PEM", rewrapped]
|
|
end
|
|
|
|
unless with_newlines.include?("BEGIN")
|
|
body = cleaned.strip.gsub(/\s+/, '')
|
|
pem = "-----BEGIN PRIVATE KEY-----\n#{body.scan(/.{1,64}/).join("\n")}\n-----END PRIVATE KEY-----\n"
|
|
candidates << ["headerless body → PEM", pem]
|
|
end
|
|
|
|
begin
|
|
decoded = Base64.decode64(cleaned.strip)
|
|
if decoded.include?("BEGIN")
|
|
decoded_clean = decoded.gsub("\r", "").gsub('\n', "\n")
|
|
candidates << ["base64→PEM", decoded_clean]
|
|
b64_body = decoded_clean.gsub(/-----(?:BEGIN|END)[^-]+-----/, '').gsub(/\s+/, '')
|
|
rewrapped = "-----BEGIN PRIVATE KEY-----\n#{b64_body.scan(/.{1,64}/).join("\n")}\n-----END PRIVATE KEY-----\n"
|
|
candidates << ["base64→PEM rewrapped", rewrapped]
|
|
elsif decoded.length.between?(32, 256)
|
|
candidates << ["base64→DER", decoded]
|
|
der_pem = "-----BEGIN PRIVATE KEY-----\n#{Base64.strict_encode64(decoded).scan(/.{1,64}/).join("\n")}\n-----END PRIVATE KEY-----\n"
|
|
candidates << ["base64→DER→PEM", der_pem]
|
|
end
|
|
rescue StandardError
|
|
# not valid base64
|
|
end
|
|
|
|
begin
|
|
double = Base64.decode64(Base64.decode64(cleaned.strip).strip)
|
|
if double.include?("BEGIN")
|
|
candidates << ["double-base64→PEM", double.gsub("\r", "")]
|
|
elsif double.length.between?(32, 256)
|
|
der_pem = "-----BEGIN PRIVATE KEY-----\n#{Base64.strict_encode64(double).scan(/.{1,64}/).join("\n")}\n-----END PRIVATE KEY-----\n"
|
|
candidates << ["double-base64→DER→PEM", der_pem]
|
|
end
|
|
rescue StandardError
|
|
# not double-encoded
|
|
end
|
|
|
|
candidates.each do |name, content|
|
|
begin
|
|
OpenSSL::PKey.read(content)
|
|
UI.success("ASC key parsed with strategy: #{name}")
|
|
return content
|
|
rescue StandardError => e
|
|
UI.message("Strategy '#{name}' failed: #{e.class} — #{e.message}")
|
|
end
|
|
end
|
|
|
|
UI.error("=== ASC KEY DIAGNOSTIC (no key material shown) ===")
|
|
UI.error("Raw byte length: #{raw.bytesize}")
|
|
UI.error("Starts with BEGIN: #{raw.strip.start_with?('-----BEGIN')}")
|
|
UI.error("Ends with -----: #{raw.strip.end_with?('-----')}")
|
|
UI.error("Has real newlines: #{raw.include?("\n")}")
|
|
UI.error("Has literal backslash-n: #{raw.include?('\\n')}")
|
|
UI.error("Has carriage returns: #{raw.include?("\r")}")
|
|
UI.error("Printable ASCII ratio: #{(raw.count(' -~').to_f / raw.bytesize * 100).round(1)}%")
|
|
UI.error("Header (first 27 chars): #{raw[0..26]}")
|
|
begin
|
|
d = Base64.decode64(raw.strip)
|
|
hex = d.bytes[0..15].map { |b| format('%02x', b) }.join(' ')
|
|
UI.error("After base64 decode — length: #{d.bytesize}, first 16 bytes hex: #{hex}")
|
|
rescue StandardError
|
|
UI.error("base64 decode raised an exception")
|
|
end
|
|
UI.error("=== END DIAGNOSTIC ===")
|
|
|
|
raise "Could not parse ASC_KEY_CONTENT in any known format. See diagnostics above."
|
|
end
|
|
|
|
default_platform(:ios)
|
|
|
|
platform :ios do
|
|
desc "Build and upload to TestFlight"
|
|
lane :beta do
|
|
setup_ci(force: true)
|
|
|
|
key_pem = normalize_p8_key(ENV["ASC_KEY_CONTENT"])
|
|
|
|
key_path = File.join(Dir.tmpdir, "asc_api_key.p8")
|
|
File.write(key_path, key_pem)
|
|
|
|
app_store_connect_api_key(
|
|
key_id: ENV["ASC_KEY_ID"],
|
|
issuer_id: ENV["ASC_ISSUER_ID"],
|
|
key_filepath: key_path
|
|
)
|
|
|
|
File.delete(key_path) if File.exist?(key_path)
|
|
|
|
match(
|
|
type: "appstore",
|
|
readonly: true,
|
|
keychain_name: "fastlane_tmp_keychain",
|
|
keychain_password: ""
|
|
)
|
|
|
|
update_code_signing_settings(
|
|
use_automatic_signing: false,
|
|
team_id: "9KAQYC653W",
|
|
code_sign_identity: "Apple Distribution",
|
|
profile_name: "match AppStore com.seahavenind.proposals",
|
|
path: "ios/ProposalSystem.xcodeproj"
|
|
)
|
|
|
|
node_path = sh("which node").strip
|
|
File.write("ios/.xcode.env.local", "export NODE_BINARY=#{node_path}\n")
|
|
UI.message("NODE_BINARY set to #{node_path}")
|
|
|
|
increment_build_number(
|
|
build_number: ENV["GITHUB_RUN_NUMBER"],
|
|
xcodeproj: "ios/ProposalSystem.xcodeproj"
|
|
)
|
|
|
|
build_app(
|
|
workspace: "ios/ProposalSystem.xcworkspace",
|
|
scheme: "ProposalSystem",
|
|
configuration: "Release",
|
|
export_method: "app-store",
|
|
export_team_id: "9KAQYC653W",
|
|
output_directory: "build",
|
|
output_name: "ProposalSystem.ipa",
|
|
xcodebuild_formatter: ""
|
|
)
|
|
|
|
upload_to_testflight(skip_waiting_for_build_processing: true)
|
|
end
|
|
end
|