Compare commits
No commits in common. "042339948ccd897ea26ec06b7d1ab98cdfdb20d1" and "9cd12ceb79187a5077a7e42f2db14e9d3c296c8d" have entirely different histories.
042339948c
...
9cd12ceb79
1
.gitignore
vendored
|
|
@ -44,7 +44,6 @@ TestResults/
|
||||||
# React Native / Mobile
|
# React Native / Mobile
|
||||||
mobile/ios/Pods/
|
mobile/ios/Pods/
|
||||||
mobile/ios/build/
|
mobile/ios/build/
|
||||||
mobile/ios/.xcode.env.local
|
|
||||||
mobile/android/.gradle/
|
mobile/android/.gradle/
|
||||||
mobile/android/app/build/
|
mobile/android/app/build/
|
||||||
mobile/android/build/
|
mobile/android/build/
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,8 @@
|
||||||
require 'openssl'
|
require 'openssl'
|
||||||
require 'base64'
|
|
||||||
require 'tempfile'
|
|
||||||
|
|
||||||
# OpenSSL 3.x rejects PKCS#8 keys via EC.new ("invalid curve name").
|
# Fastlane 2.234.0 uses OpenSSL::PKey::EC.new which rejects PKCS#8 keys on
|
||||||
# Prepend a wrapper that falls back to PKey.read for both formats.
|
# OpenSSL 3.x ("invalid curve name"). Prepend a wrapper that falls back to
|
||||||
|
# OpenSSL::PKey.read, which handles both PKCS#8 and SEC1 formats.
|
||||||
module OpenSSLECNewFix
|
module OpenSSLECNewFix
|
||||||
def new(arg = nil, *rest)
|
def new(arg = nil, *rest)
|
||||||
super
|
super
|
||||||
|
|
@ -14,124 +13,30 @@ module OpenSSLECNewFix
|
||||||
end
|
end
|
||||||
OpenSSL::PKey::EC.singleton_class.prepend(OpenSSLECNewFix)
|
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)
|
default_platform(:ios)
|
||||||
|
|
||||||
platform :ios do
|
platform :ios do
|
||||||
desc "Build and upload to TestFlight"
|
desc "Build and upload to TestFlight"
|
||||||
lane :beta do
|
lane :beta do
|
||||||
setup_ci(force: true)
|
setup_ci
|
||||||
|
|
||||||
key_pem = normalize_p8_key(ENV["ASC_KEY_CONTENT"])
|
raw = ENV["ASC_KEY_CONTENT"]
|
||||||
|
if raw.include?("BEGIN")
|
||||||
key_path = File.join(Dir.tmpdir, "asc_api_key.p8")
|
key_content = raw.gsub('\n', "\n")
|
||||||
File.write(key_path, key_pem)
|
is_base64 = false
|
||||||
|
else
|
||||||
|
key_content = raw
|
||||||
|
is_base64 = true
|
||||||
|
end
|
||||||
|
|
||||||
app_store_connect_api_key(
|
app_store_connect_api_key(
|
||||||
key_id: ENV["ASC_KEY_ID"],
|
key_id: ENV["ASC_KEY_ID"],
|
||||||
issuer_id: ENV["ASC_ISSUER_ID"],
|
issuer_id: ENV["ASC_ISSUER_ID"],
|
||||||
key_filepath: key_path
|
key_content: key_content,
|
||||||
|
is_key_content_base64: is_base64
|
||||||
)
|
)
|
||||||
|
|
||||||
File.delete(key_path) if File.exist?(key_path)
|
match(type: "appstore", readonly: true)
|
||||||
|
|
||||||
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
|
|
||||||
xcode_env_path = File.join(__dir__, "..", "ios", ".xcode.env.local")
|
|
||||||
File.write(xcode_env_path, "export NODE_BINARY=#{node_path}\n")
|
|
||||||
UI.message("NODE_BINARY set to #{node_path} at #{xcode_env_path}")
|
|
||||||
|
|
||||||
increment_build_number(
|
increment_build_number(
|
||||||
build_number: ENV["GITHUB_RUN_NUMBER"],
|
build_number: ENV["GITHUB_RUN_NUMBER"],
|
||||||
|
|
@ -143,10 +48,8 @@ platform :ios do
|
||||||
scheme: "ProposalSystem",
|
scheme: "ProposalSystem",
|
||||||
configuration: "Release",
|
configuration: "Release",
|
||||||
export_method: "app-store",
|
export_method: "app-store",
|
||||||
export_team_id: "9KAQYC653W",
|
|
||||||
output_directory: "build",
|
output_directory: "build",
|
||||||
output_name: "ProposalSystem.ipa",
|
output_name: "ProposalSystem.ipa"
|
||||||
xcodebuild_formatter: "cat"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
upload_to_testflight(skip_waiting_for_build_processing: true)
|
upload_to_testflight(skip_waiting_for_build_processing: true)
|
||||||
|
|
|
||||||
|
|
@ -1,55 +1,46 @@
|
||||||
{
|
{
|
||||||
"images" : [
|
"images" : [
|
||||||
{
|
{
|
||||||
"filename" : "Icon-40.png",
|
|
||||||
"idiom" : "iphone",
|
"idiom" : "iphone",
|
||||||
"scale" : "2x",
|
"scale" : "2x",
|
||||||
"size" : "20x20"
|
"size" : "20x20"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"filename" : "Icon-60.png",
|
|
||||||
"idiom" : "iphone",
|
"idiom" : "iphone",
|
||||||
"scale" : "3x",
|
"scale" : "3x",
|
||||||
"size" : "20x20"
|
"size" : "20x20"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"filename" : "Icon-58.png",
|
|
||||||
"idiom" : "iphone",
|
"idiom" : "iphone",
|
||||||
"scale" : "2x",
|
"scale" : "2x",
|
||||||
"size" : "29x29"
|
"size" : "29x29"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"filename" : "Icon-87.png",
|
|
||||||
"idiom" : "iphone",
|
"idiom" : "iphone",
|
||||||
"scale" : "3x",
|
"scale" : "3x",
|
||||||
"size" : "29x29"
|
"size" : "29x29"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"filename" : "Icon-80.png",
|
|
||||||
"idiom" : "iphone",
|
"idiom" : "iphone",
|
||||||
"scale" : "2x",
|
"scale" : "2x",
|
||||||
"size" : "40x40"
|
"size" : "40x40"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"filename" : "Icon-120.png",
|
|
||||||
"idiom" : "iphone",
|
"idiom" : "iphone",
|
||||||
"scale" : "3x",
|
"scale" : "3x",
|
||||||
"size" : "40x40"
|
"size" : "40x40"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"filename" : "Icon-120.png",
|
|
||||||
"idiom" : "iphone",
|
"idiom" : "iphone",
|
||||||
"scale" : "2x",
|
"scale" : "2x",
|
||||||
"size" : "60x60"
|
"size" : "60x60"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"filename" : "Icon-180.png",
|
|
||||||
"idiom" : "iphone",
|
"idiom" : "iphone",
|
||||||
"scale" : "3x",
|
"scale" : "3x",
|
||||||
"size" : "60x60"
|
"size" : "60x60"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"filename" : "Icon-1024.png",
|
|
||||||
"idiom" : "ios-marketing",
|
"idiom" : "ios-marketing",
|
||||||
"scale" : "1x",
|
"scale" : "1x",
|
||||||
"size" : "1024x1024"
|
"size" : "1024x1024"
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 6 KiB |
|
Before Width: | Height: | Size: 292 B |
|
Before Width: | Height: | Size: 695 B |
|
Before Width: | Height: | Size: 105 B |
|
Before Width: | Height: | Size: 133 B |
|
Before Width: | Height: | Size: 139 B |
|
Before Width: | Height: | Size: 174 B |
|
Before Width: | Height: | Size: 192 B |
|
|
@ -84,7 +84,5 @@
|
||||||
</array>
|
</array>
|
||||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||||
<false/>
|
<false/>
|
||||||
<key>CFBundleIconName</key>
|
|
||||||
<string>AppIcon</string>
|
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|
|
||||||
1375
mobile/package-lock.json
generated
|
|
@ -35,8 +35,8 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@react-native-community/cli": "^20.1.3",
|
"@react-native-community/cli": "^20.1.3",
|
||||||
"@react-native-community/cli-platform-ios": "^20.1.3",
|
"@react-native-community/cli-platform-ios": "^20.1.3",
|
||||||
"@react-native/babel-preset": "^0.85.3",
|
"@react-native/babel-preset": "^0.79.0",
|
||||||
"@react-native/metro-config": "^0.85.3",
|
"@react-native/metro-config": "^0.79.0",
|
||||||
"@types/react": "^19.0.0",
|
"@types/react": "^19.0.0",
|
||||||
"@types/react-native-vector-icons": "^6.4.18",
|
"@types/react-native-vector-icons": "^6.4.18",
|
||||||
"typescript": "~5.7.0"
|
"typescript": "~5.7.0"
|
||||||
|
|
|
||||||