adsedare 0.0.1 → 0.0.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: 97608657ad78aff5e5e2eb17331c109a2da49dd51a21896a62d46b17fae1f97c
4
- data.tar.gz: f937d2f43ac6d13e3801326a88f2b947e7a0a24fce276524c2390a476e1d9085
3
+ metadata.gz: de6a221a878c99d756c8cb9779cc91145f061084c64f816e5476d4d16e297c84
4
+ data.tar.gz: 0c9afea95fdd8fa86ad25831528ee11591cdcf4a5557738767a7ad5d8dbdcd26
5
5
  SHA512:
6
- metadata.gz: 312a6acc6df402f344f6c25b6fb83e04a447f78d3f0df550eb014f1a8897a0075efba50dd4d2813f3823f1ffd9e7c244dbdde790d63bfbe958b76aabedd60890
7
- data.tar.gz: 48ac5c37a7c40278bc1c05fad1dba09e1e545d41887658863a8f0353e44bd332cdcff5914a9864aa855d1cc5a75c07d4825f20c127ff76bc41c5855f4de71c27
6
+ metadata.gz: 9178bc2fae2c7e1ff727702a9cd1fcc9b4fdc4b1ff51ae9e714a8ba44619108ce31b686a6e1e460d9f509d60b25e95d38b8de7f68d3a1c2053d7a291074f0bb0
7
+ data.tar.gz: aa57db0c849da84a65a18a58bb821a98e72facc91ef81df28f51d5050b4be08b927944d0b89b47a15c0562a09528f70b9785b3dd5d10f1f1d58fbdcc72fe22ee
data/adsedare.gemspec CHANGED
@@ -31,6 +31,6 @@ Gem::Specification.new do |spec|
31
31
  spec.add_dependency "xcodeproj", "~> 1.27.0"
32
32
  spec.add_dependency "jwt", "~> 2.7"
33
33
  spec.add_dependency "faraday", "~> 2.7"
34
- spec.add_dependency "faraday-cookie_jar", "~> 0.0.7"
34
+ spec.add_dependency "faraday-cookie_jar", "~> 0.0.7"
35
35
  spec.add_dependency "plist", "~> 3.2.0"
36
36
  end
@@ -28,59 +28,60 @@ module Adsedare
28
28
  "com.apple.developer.family-controls" => "FAMILY_CONTROLS",
29
29
  "com.apple.developer.devicecheck.appattest-environment" => "APP_ATTEST",
30
30
  "com.apple.developer.game-center" => "GAME_CENTER",
31
- "com.apple.developer.carplay-maps" => "CARPLAY_NAVIGATION"
31
+ "com.apple.developer.carplay-maps" => "CARPLAY_NAVIGATION",
32
32
  }
33
33
 
34
34
  class << self
35
35
  private
36
-
36
+
37
37
  def parse_entitlements(path)
38
38
  raise Error, "Entitlements file not found: #{path}" unless File.exist?(path)
39
-
39
+
40
40
  entitlements = Plist.parse_xml(path)
41
41
  capabilities = []
42
-
42
+
43
43
  entitlements.each do |key, value|
44
44
  capability_type = ENTITLEMENTS_MAPPING[key]
45
45
  next unless capability_type
46
-
46
+
47
47
  if key == "com.apple.security.application-groups"
48
48
  capabilities << AppGroupsCapability.new(capability_type, value)
49
49
  else
50
50
  capabilities << SimpleCapability.new(capability_type)
51
51
  end
52
52
  end
53
-
53
+
54
54
  capabilities
55
55
  end
56
56
  end
57
-
57
+
58
58
  class Capability
59
59
  attr_reader :type
60
-
60
+
61
61
  def initialize(type)
62
62
  @type = type
63
63
  end
64
-
64
+
65
65
  def check?(bundle_info)
66
- bundle_info["included"].any? {
67
- |cap| cap["type"] == "capabilities" && cap["id"] == @type
66
+ bundle_info["included"].any? {
67
+ |cap|
68
+ cap["type"] == "capabilities" && cap["id"] == @type
68
69
  }
69
70
  end
70
-
71
+
71
72
  def to_bundle_capability(bundle_info, team_id)
72
73
  return {
73
- "type" => "bundleIdCapabilities",
74
- "attributes" => {
75
- "enabled" => true,
76
- "settings" => []
77
- },
78
- "relationships" => {
79
- "capability" => {
80
- "data" => { "type" => "capabilities", "id" => @type }
81
- }
82
- }
83
- }
74
+ "type" => "bundleIdCapabilities",
75
+ "attributes" => {
76
+ "enabled" => true,
77
+ "settings" => [],
78
+ },
79
+ "relationships" => {
80
+ "capability" => {
81
+ "data" => { "type" => "capabilities", "id" => @type },
82
+ },
83
+ },
84
+ }
84
85
  end
85
86
  end
86
87
 
@@ -89,34 +90,38 @@ module Adsedare
89
90
 
90
91
  class AppGroupsCapability < Capability
91
92
  attr_reader :groups
92
-
93
+
93
94
  def initialize(type, groups)
94
95
  super(type)
95
96
  @groups = groups
96
97
  end
97
-
98
+
98
99
  def check?(bundle_info)
99
- have_capability = bundle_info["included"].any? {
100
- |cap| cap["type"] == "capabilities" && cap["id"] == @type
100
+ have_capability = bundle_info["included"].any? {
101
+ |cap|
102
+ cap["type"] == "capabilities" && cap["id"] == @type
101
103
  }
102
104
  return false unless have_capability
103
105
 
104
- registered_groups = bundle_info["included"].select {
105
- |cap| cap["type"] == "appGroups"
106
- }.map {
107
- |cap| cap["attributes"]["identifier"]
106
+ registered_groups = bundle_info["included"].select {
107
+ |cap|
108
+ cap["type"] == "appGroups"
109
+ }.map {
110
+ |cap|
111
+ cap["attributes"]["identifier"]
108
112
  }
109
-
113
+
110
114
  return @groups.all? { |group| registered_groups.include?(group) }
111
115
  end
112
-
116
+
113
117
  def to_bundle_capability(bundle_info, team_id)
114
118
  registered_app_groups = {}
115
-
119
+
116
120
  Starship::Client.get_app_groups(team_id).each {
117
- |app_group| registered_app_groups[app_group["identifier"]] = app_group["applicationGroup"]
121
+ |app_group|
122
+ registered_app_groups[app_group["identifier"]] = app_group["applicationGroup"]
118
123
  }
119
-
124
+
120
125
  @groups.each do |group|
121
126
  if not registered_app_groups.include?(group)
122
127
  new_app_group = Starship::Client.create_app_group(group, team_id)
@@ -127,11 +132,11 @@ module Adsedare
127
132
  bundle_capability = super(bundle_info, team_id)
128
133
 
129
134
  app_groups = {
130
- "data" => @groups.map { |group| { "type" => "appGroups", "id" => registered_app_groups[group] } }
135
+ "data" => @groups.map { |group| { "type" => "appGroups", "id" => registered_app_groups[group] } },
131
136
  }
132
137
 
133
138
  bundle_capability["relationships"]["appGroups"] = app_groups
134
-
139
+
135
140
  return bundle_capability
136
141
  end
137
142
  end
@@ -0,0 +1,90 @@
1
+ require "xcodeproj"
2
+ require "plist"
3
+
4
+ require_relative "../appstoreconnect"
5
+
6
+ module Adsedare
7
+ class << self
8
+ def make_export_options(project_path = nil, export_path = nil, team_id = nil, options = {})
9
+ raise "Project path is not set" unless project_path
10
+ raise "Export path is not set" unless export_path
11
+
12
+ project = Xcodeproj::Project.open(project_path)
13
+ export_options = {
14
+ "method" => "ad-hoc",
15
+ "destination" => "export",
16
+ "signingStyle" => "manual",
17
+ "signingCertificate" => "Apple Distribution",
18
+ "provisioningProfiles" => {},
19
+ }.merge(options)
20
+
21
+ project_bundles = []
22
+
23
+ project.targets.each do |target|
24
+ target.build_configurations.each do |config|
25
+ team_id ||= config.build_settings["DEVELOPMENT_TEAM"]
26
+ project_bundles << config.build_settings["PRODUCT_BUNDLE_IDENTIFIER"]
27
+ end
28
+ end
29
+
30
+ export_options["teamID"] = team_id
31
+
32
+ logger.info "Fetching bundles with profiles for team ID '#{team_id}'"
33
+
34
+ bundles_with_profiles = AppStoreConnect::Client.get_bundles_with_profiles(project_bundles)
35
+ bundle_by_identifier = {}
36
+ profiles_by_id = {}
37
+
38
+ bundles_with_profiles["data"].each do |bundle_id|
39
+ bundle_by_identifier[bundle_id["attributes"]["identifier"]] = bundle_id
40
+ end
41
+
42
+ bundles_with_profiles["included"].each do |profile|
43
+ profiles_by_id[profile["id"]] = profile
44
+ end
45
+
46
+ project_bundles.each do |bundle_identifier|
47
+ bundle_id = bundle_by_identifier[bundle_identifier]
48
+ unless bundle_id
49
+ logger.warn "Bundle '#{bundle_identifier}' is missing in App Store Connect. Skipping."
50
+ next
51
+ end
52
+
53
+ logger.info "Bundle '#{bundle_identifier}' resolved to Bundle ID '#{bundle_id["id"]}'"
54
+
55
+ profiles = bundle_id["relationships"]["profiles"]["data"]
56
+ unless profiles
57
+ logger.warn "Profile for Bundle ID '#{bundle_id["id"]}' is missing in App Store Connect. Skipping."
58
+ next
59
+ end
60
+
61
+ ad_hoc_profile = nil
62
+ profiles.each do |profile|
63
+ profile_id = profile["id"]
64
+ profile = profiles_by_id[profile_id]
65
+
66
+ if profile["attributes"]["profileType"] == "IOS_APP_ADHOC" && profile["attributes"]["profileState"] == "ACTIVE"
67
+ ad_hoc_profile = profile
68
+ break
69
+ end
70
+ end
71
+
72
+ unless ad_hoc_profile
73
+ logger.warn "Profile for Bundle ID '#{bundle_id["id"]}' is missing in App Store Connect. Skipping."
74
+ next
75
+ end
76
+
77
+ logger.info "Profile for Bundle ID '#{bundle_id["id"]}' resolved to Profile '#{ad_hoc_profile["attributes"]["name"]}'"
78
+
79
+ profile_name = ad_hoc_profile["attributes"]["name"]
80
+
81
+ export_options["provisioningProfiles"][bundle_identifier] = profile_name
82
+ end
83
+
84
+ options_plist = Plist::Emit.dump(export_options)
85
+ File.write(File.expand_path(export_path), options_plist)
86
+
87
+ return export_options
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,131 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "xcodeproj"
4
+ require "base64"
5
+ require "fileutils"
6
+ require "tempfile"
7
+
8
+ module Adsedare
9
+ APPLE_CERTS = [
10
+ "AppleWWDRCAG2.cer",
11
+ "AppleWWDRCAG3.cer",
12
+ "AppleWWDRCAG4.cer",
13
+ "AppleWWDRCAG5.cer",
14
+ "AppleWWDRCAG6.cer",
15
+ "AppleWWDRCAG7.cer",
16
+ "AppleWWDRCAG8.cer",
17
+ "DeveloperIDG2CA.cer",
18
+ ]
19
+ APPLE_CERTS_URL = "https://www.apple.com/certificateauthority"
20
+
21
+ APPLE_WWDRCA = "https://developer.apple.com/certificationauthority/AppleWWDRCA.cer"
22
+
23
+ class << self
24
+ def create_keychain(keychain_path = nil, keychain_password = nil, make_default = true)
25
+ raise "Keychain path is not set" unless keychain_path
26
+ raise "Keychain password is not set" unless keychain_password
27
+
28
+ keychain_path = File.expand_path(keychain_path)
29
+
30
+ logger.info "Creating keychain at '#{keychain_path}'"
31
+
32
+ FileUtils.mkdir_p(File.dirname(keychain_path))
33
+ status = system("security create-keychain -p #{keychain_password} #{keychain_path}")
34
+ unless status
35
+ logger.error "Failed to create keychain at '#{keychain_path}'"
36
+ return
37
+ end
38
+
39
+ APPLE_CERTS.each do |cert|
40
+ logger.info "Downloading certificate '#{cert}'"
41
+
42
+ response = Faraday.get(
43
+ "#{APPLE_CERTS_URL}/#{cert}"
44
+ )
45
+ unless response.status == 200
46
+ logger.error "Failed to download certificate '#{cert}'"
47
+ next
48
+ end
49
+
50
+ file = Tempfile.new(cert)
51
+ file.write(response.body)
52
+ file.close
53
+
54
+ install_certificate(file.path, keychain_path)
55
+
56
+ file.unlink
57
+ end
58
+
59
+ logger.info "Downloading certificate 'AppleWWDRCA.cer'"
60
+ response = Faraday.get(
61
+ APPLE_WWDRCA
62
+ )
63
+ unless response.status == 200
64
+ logger.error "Failed to download certificate 'AppleWWDRCA.cer'"
65
+ else
66
+ file = Tempfile.new("AppleWWDRCA.cer")
67
+ file.write(response.body)
68
+ file.close
69
+
70
+ install_certificate(file.path, keychain_path)
71
+
72
+ file.unlink
73
+ end
74
+
75
+ ad_hoc_certificate = ENV["AD_HOC_CERTIFICATE"]
76
+ ad_hoc_private_key = ENV["AD_HOC_PRIVATE_KEY"]
77
+ ad_hoc_key_password = ENV["AD_HOC_KEY_PASSWORD"]
78
+
79
+ unless ad_hoc_certificate || ad_hoc_private_key || ad_hoc_key_password
80
+ logger.warn "AD_HOC_CERTIFICATE, AD_HOC_PRIVATE_KEY, or AD_HOC_KEY_PASSWORD is not set"
81
+ return
82
+ end
83
+
84
+ install_certificate(ad_hoc_private_key, keychain_path, ad_hoc_key_password, "priv")
85
+ install_certificate(ad_hoc_certificate, keychain_path, "", "cert")
86
+
87
+ if make_default
88
+ status = system("security default-keychain -d user -s #{keychain_path}")
89
+ unless status
90
+ logger.warn "Failed to set default keychain"
91
+ return
92
+ end
93
+ end
94
+
95
+ status = system("security set-keychain-settings #{keychain_path}")
96
+ unless status
97
+ logger.error "Failed to set keychain settings"
98
+ return
99
+ end
100
+
101
+ status = system("security set-key-partition-list -S apple-tool:,apple: -k #{keychain_password} #{keychain_path}")
102
+ unless status
103
+ logger.error "Failed to set keychain partition list"
104
+ return
105
+ end
106
+
107
+ status = system("security unlock-keychain -p #{keychain_password} #{keychain_path}")
108
+ unless status
109
+ logger.error "Failed to unlock keychain"
110
+ return
111
+ end
112
+
113
+ logger.info "Keychain created at '#{keychain_path}'"
114
+ end
115
+
116
+ private
117
+
118
+ def install_certificate(certificate_path, keychain_path, certificate_password = "", certificate_type = "cert")
119
+ certificate_name = File.basename(certificate_path)
120
+ logger.info "Installing certificate '#{certificate_name}' to keychain '#{keychain_path}'"
121
+
122
+ status = system("security import #{certificate_path} -k #{keychain_path} -t #{certificate_type} -A -P #{certificate_password} -T /usr/bin/codesign -T /usr/bin/security -T /usr/bin/productbuild")
123
+ unless status
124
+ logger.error "Failed to install certificate '#{certificate_name}' to keychain '#{keychain_path}'"
125
+ return
126
+ end
127
+
128
+ logger.info "Certificate '#{certificate_name}' installed to keychain '#{keychain_path}'"
129
+ end
130
+ end
131
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Adsedare
4
- VERSION = "0.0.1"
4
+ VERSION = "0.0.2"
5
5
  end
@@ -0,0 +1,76 @@
1
+ require "xcodeproj"
2
+ require "plist"
3
+
4
+ require_relative "../appstoreconnect"
5
+
6
+ module Adsedare
7
+ class << self
8
+ def patch_project(project_path, team_id = nil)
9
+ raise "Project path is not set" unless project_path
10
+
11
+ project = Xcodeproj::Project.open(project_path)
12
+
13
+ project_bundles = project.targets.map do |target|
14
+ target.build_configurations.map do |config|
15
+ config.build_settings["PRODUCT_BUNDLE_IDENTIFIER"]
16
+ end
17
+ end.flatten.uniq
18
+
19
+ bundles_with_profiles = AppStoreConnect::Client.get_bundles_with_profiles(project_bundles)
20
+ bundle_by_identifier = {}
21
+ profiles_by_id = {}
22
+
23
+ bundles_with_profiles["data"].each do |bundle_id|
24
+ bundle_by_identifier[bundle_id["attributes"]["identifier"]] = bundle_id
25
+ end
26
+
27
+ bundles_with_profiles["included"].each do |profile|
28
+ profiles_by_id[profile["id"]] = profile
29
+ end
30
+
31
+ project.targets.each do |target|
32
+ target.build_configurations.each do |config|
33
+ bundle_identifier = config.build_settings["PRODUCT_BUNDLE_IDENTIFIER"]
34
+ bundle_id = bundle_by_identifier[bundle_identifier]
35
+ unless bundle_id
36
+ logger.warn "Bundle '#{bundle_identifier}' is missing in App Store Connect. Skipping."
37
+ next
38
+ end
39
+
40
+ logger.info "Bundle '#{bundle_identifier}' resolved to Bundle ID '#{bundle_id["id"]}'"
41
+
42
+ profiles = bundle_id["relationships"]["profiles"]["data"]
43
+ unless profiles
44
+ logger.warn "Profile for Bundle ID '#{bundle_id["id"]}' is missing in App Store Connect. Skipping."
45
+ next
46
+ end
47
+
48
+ ad_hoc_profile = nil
49
+ profiles.each do |profile|
50
+ profile_id = profile["id"]
51
+ profile = profiles_by_id[profile_id]
52
+
53
+ if profile["attributes"]["profileType"] == "IOS_APP_ADHOC" && profile["attributes"]["profileState"] == "ACTIVE"
54
+ ad_hoc_profile = profile
55
+ break
56
+ end
57
+ end
58
+
59
+ unless ad_hoc_profile
60
+ logger.warn "Profile for Bundle ID '#{bundle_id["id"]}' is missing in App Store Connect. Skipping."
61
+ next
62
+ end
63
+
64
+ config.build_settings["CODE_SIGN_IDENTITY"] = "iPhone Distribution"
65
+ config.build_settings["CODE_SIGN_STYLE"] = "Manual"
66
+ if team_id
67
+ config.build_settings["DEVELOPMENT_TEAM"] = team_id
68
+ end
69
+ config.build_settings["PROVISIONING_PROFILE_SPECIFIER"] = ad_hoc_profile["attributes"]["name"]
70
+ end
71
+ end
72
+
73
+ project.save
74
+ end
75
+ end
76
+ end