adsedare 0.0.1 → 0.0.3

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: 4ecc75a51b623a2c9319dee4fb2ed9f276ec02789876663375bc73dc3abd7bae
4
+ data.tar.gz: 6715ec7cf9f373608e442ba4916a097b1094efee050a05639fe14dfaf1c0dead
5
5
  SHA512:
6
- metadata.gz: 312a6acc6df402f344f6c25b6fb83e04a447f78d3f0df550eb014f1a8897a0075efba50dd4d2813f3823f1ffd9e7c244dbdde790d63bfbe958b76aabedd60890
7
- data.tar.gz: 48ac5c37a7c40278bc1c05fad1dba09e1e545d41887658863a8f0353e44bd332cdcff5914a9864aa855d1cc5a75c07d4825f20c127ff76bc41c5855f4de71c27
6
+ metadata.gz: 1251ab137c4071f94587385a0211273f71e93652d3714f7da8ac20a87df8caf5e55d8003a1b6a419b7ac33f9dccae7e89f1d6e71ae8bde1f7a7512c7dc171229
7
+ data.tar.gz: 5cbb5c9c206799a6a7d70923e42494ef4ba9deb8ede37afc4fe5a8a6e8ed94847d7587a76afbc64aa7a4a547aab9a81e49cb04ef91e85c20fe3d1cb940307682
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,93 @@
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
+ logger.info "Creating export options for project'"
13
+
14
+ project = Xcodeproj::Project.open(project_path)
15
+ export_options = {
16
+ "method" => "ad-hoc",
17
+ "destination" => "export",
18
+ "signingStyle" => "manual",
19
+ "signingCertificate" => "Apple Distribution",
20
+ "provisioningProfiles" => {},
21
+ }.merge(options)
22
+
23
+ project_bundles = []
24
+
25
+ project.targets.each do |target|
26
+ target.build_configurations.each do |config|
27
+ team_id ||= config.build_settings["DEVELOPMENT_TEAM"]
28
+ project_bundles << config.build_settings["PRODUCT_BUNDLE_IDENTIFIER"]
29
+ end
30
+ end
31
+
32
+ export_options["teamID"] = team_id
33
+
34
+ logger.info "Fetching bundles with profiles for team ID '#{team_id}'"
35
+
36
+ bundles_with_profiles = AppStoreConnect::Client.get_bundles_with_profiles(project_bundles)
37
+ bundle_by_identifier = {}
38
+ profiles_by_id = {}
39
+
40
+ bundles_with_profiles["data"].each do |bundle_id|
41
+ bundle_by_identifier[bundle_id["attributes"]["identifier"]] = bundle_id
42
+ end
43
+
44
+ bundles_with_profiles["included"].each do |profile|
45
+ profiles_by_id[profile["id"]] = profile
46
+ end
47
+
48
+ project_bundles.each do |bundle_identifier|
49
+ bundle_id = bundle_by_identifier[bundle_identifier]
50
+ unless bundle_id
51
+ logger.warn "Bundle '#{bundle_identifier}' is missing in App Store Connect. Skipping."
52
+ next
53
+ end
54
+
55
+ logger.info "Bundle '#{bundle_identifier}' resolved to Bundle ID '#{bundle_id["id"]}'"
56
+
57
+ profiles = bundle_id["relationships"]["profiles"]["data"]
58
+ unless profiles
59
+ logger.warn "Profile for Bundle ID '#{bundle_id["id"]}' is missing in App Store Connect. Skipping."
60
+ next
61
+ end
62
+
63
+ ad_hoc_profile = nil
64
+ profiles.each do |profile|
65
+ profile_id = profile["id"]
66
+ profile = profiles_by_id[profile_id]
67
+
68
+ if profile["attributes"]["profileType"] == "IOS_APP_ADHOC" && profile["attributes"]["profileState"] == "ACTIVE"
69
+ ad_hoc_profile = profile
70
+ break
71
+ end
72
+ end
73
+
74
+ unless ad_hoc_profile
75
+ logger.warn "Profile for Bundle ID '#{bundle_id["id"]}' is missing in App Store Connect. Skipping."
76
+ next
77
+ end
78
+
79
+ logger.info "Profile for Bundle ID '#{bundle_id["id"]}' resolved to Profile '#{ad_hoc_profile["attributes"]["name"]}'"
80
+
81
+ profile_name = ad_hoc_profile["attributes"]["name"]
82
+
83
+ export_options["provisioningProfiles"][bundle_identifier] = profile_name
84
+ end
85
+
86
+ options_plist = Plist::Emit.dump(export_options)
87
+ export_path = File.expand_path(export_path)
88
+ File.write(export_path, options_plist)
89
+
90
+ logger.info "Export options created at '#{export_path}'"
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,76 @@
1
+ require "xcodeproj"
2
+ require "base64"
3
+ require "fileutils"
4
+
5
+ require_relative "../appstoreconnect"
6
+
7
+ module Adsedare
8
+ class << self
9
+ def install_profiles(project_path = nil)
10
+ raise "Project path is not set" unless project_path
11
+
12
+ project = Xcodeproj::Project.open(project_path)
13
+
14
+ project_bundles = project.targets.map do |target|
15
+ target.build_configurations.map do |config|
16
+ config.build_settings["PRODUCT_BUNDLE_IDENTIFIER"]
17
+ end
18
+ end.flatten.uniq
19
+
20
+ bundles_with_profiles = AppStoreConnect::Client.get_bundles_with_profiles(project_bundles)
21
+ bundle_by_identifier = {}
22
+ profiles_by_id = {}
23
+
24
+ bundles_with_profiles["data"].each do |bundle_id|
25
+ bundle_by_identifier[bundle_id["attributes"]["identifier"]] = bundle_id
26
+ end
27
+
28
+ bundles_with_profiles["included"].each do |profile|
29
+ profiles_by_id[profile["id"]] = profile
30
+ end
31
+
32
+ project_bundles.each do |bundle_identifier|
33
+ bundle_id = bundle_by_identifier[bundle_identifier]
34
+ unless bundle_id
35
+ logger.warn "Bundle '#{bundle_identifier}' is missing in App Store Connect. Skipping."
36
+ next
37
+ end
38
+
39
+ logger.info "Bundle '#{bundle_identifier}' resolved to Bundle ID '#{bundle_id["id"]}'"
40
+
41
+ profiles = bundle_id["relationships"]["profiles"]["data"]
42
+ unless profiles
43
+ logger.warn "Profile for Bundle ID '#{bundle_id["id"]}' is missing in App Store Connect. Skipping."
44
+ next
45
+ end
46
+
47
+ ad_hoc_profile = nil
48
+ profiles.each do |profile|
49
+ profile_id = profile["id"]
50
+ profile = profiles_by_id[profile_id]
51
+
52
+ if profile["attributes"]["profileType"] == "IOS_APP_ADHOC" && profile["attributes"]["profileState"] == "ACTIVE"
53
+ ad_hoc_profile = profile
54
+ break
55
+ end
56
+ end
57
+
58
+ unless ad_hoc_profile
59
+ logger.warn "Profile for Bundle ID '#{bundle_id["id"]}' is missing in App Store Connect. Skipping."
60
+ next
61
+ end
62
+
63
+ logger.info "Profile for Bundle ID '#{bundle_id["id"]}' resolved to Profile '#{ad_hoc_profile["attributes"]["name"]}'"
64
+
65
+ uuid = ad_hoc_profile["attributes"]["uuid"]
66
+ profile_content = Base64.decode64(ad_hoc_profile["attributes"]["profileContent"])
67
+ profile_path = "#{Dir.home}/Library/MobileDevice/Provisioning Profiles/#{uuid}.mobileprovision"
68
+
69
+ FileUtils.mkdir_p(File.dirname(profile_path))
70
+ File.write(profile_path, profile_content)
71
+
72
+ logger.info "Profile '#{ad_hoc_profile["attributes"]["name"]}' installed to '#{profile_path}'"
73
+ end
74
+ end
75
+ end
76
+ 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.3"
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