motion-sparkle-sandbox 2.0.1 → 2.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.
@@ -1,45 +1,178 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # rubocop:disable Metrics/ClassLength
3
4
  module Motion
4
5
  module Project
5
6
  class Sparkle
6
- # Generate the appcast.
7
- # Note: We do not support the old DSA keys, only the newer EdDSA keys.
8
- # See https://sparkle-project.org/documentation/eddsa-migration
9
- # rubocop:disable Metrics/CyclomaticComplexity
10
- def generate_appcast
11
- generate_appcast_app = "#{vendored_sparkle_path}/bin/generate_appcast"
12
- path = (project_path + archive_folder).realpath
13
- appcast_filename = (path + appcast.feed_filename)
7
+ class Appcast
8
+ PARAMS = %i[
9
+ package_base_url
10
+ package_filename
11
+ notes_base_url
12
+ notes_filename
13
+ use_exported_private_key
14
+ base_url
15
+ releases_folder
16
+ feed_base_url
17
+ feed_filename
18
+ ].freeze
19
+
20
+ CLI_OPTIONS = {
21
+ account: '--account',
22
+ private_ed_key_file: '--ed-key-file',
23
+ download_url_prefix: '--download-url-prefix',
24
+ release_notes_url_prefix: '--release-notes-url-prefix',
25
+ full_release_notes_url: '--full-release-notes-url',
26
+ link: '--link',
27
+ versions: '--versions',
28
+ maximum_deltas: '--maximum-deltas',
29
+ delta_compression: '--delta-compression',
30
+ delta_compression_level: '--delta-compression-level',
31
+ channel: '--channel',
32
+ major_version: '--major-version',
33
+ ignore_skipped_upgrades_below_version: '--ignore-skipped-upgrades-below-version',
34
+ phased_rollout_interval: '--phased-rollout-interval',
35
+ critical_update_version: '--critical-update-version',
36
+ informational_update_versions: '--informational-update-versions',
37
+ output_path: '-o'
38
+ }.freeze
39
+
40
+ attr_accessor :base_url,
41
+ :feed_base_url,
42
+ :feed_filename,
43
+ :notes_filename,
44
+ :package_filename,
45
+ :releases_folder,
46
+ :use_exported_private_key,
47
+ :cli_options
48
+ attr_writer :notes_base_url,
49
+ :package_base_url
50
+
51
+ def initialize(sparkle_object)
52
+ @sparkle = sparkle_object
53
+ @cli_options = {
54
+ account: 'ed25519' # Sparkle's default account
55
+ }
56
+
57
+ @feed_base_url = nil
58
+ @feed_filename = 'releases.xml'
59
+ @notes_base_url = nil
60
+ @notes_filename = nil
61
+ @package_base_url = nil
62
+ @package_filename = nil
63
+ @base_url = nil
64
+ @releases_folder = nil
65
+ @use_exported_private_key = false
66
+ end
67
+
68
+ def process_option(key, value)
69
+ if CLI_OPTIONS.keys.include?(key)
70
+ cli_options[key] = value
71
+ elsif PARAMS.include?(key)
72
+ send("#{key}=", value)
73
+ else
74
+ return false
75
+ end
76
+
77
+ true
78
+ end
79
+
80
+ def feed_url
81
+ "#{feed_base_url || base_url}#{feed_filename}"
82
+ end
83
+
84
+ def notes_base_url
85
+ @notes_base_url || base_url
86
+ end
14
87
 
15
- args = []
88
+ def package_base_url
89
+ @package_base_url || base_url
90
+ end
91
+
92
+ def prepare_args
93
+ args = []
94
+
95
+ account(args)
96
+ private_ed_key_file(args)
97
+ download_url_prefix(args)
98
+ release_notes_url_prefix(args)
99
+ full_release_notes_url(args)
100
+ link(args)
101
+ versions(args)
102
+ maximum_deltas(args)
103
+ delta_compression(args)
104
+ delta_compression_level(args)
105
+ channel(args)
106
+ major_version(args)
107
+ ignore_skipped_upgrades_below_version(args)
108
+ phased_rollout_interval(args)
109
+ critical_update_version(args)
110
+ informational_update_versions(args)
111
+ output_path(args)
112
+
113
+ args
114
+ end
115
+
116
+ private
16
117
 
17
- FileUtils.mkdir_p(path) unless File.exist?(path)
118
+ # --account <account> The account name in your keychain associated with
119
+ # your private EdDSA (ed25519) key to use for signing
120
+ # new updates. (default: ed25519)
121
+ def account(args)
122
+ return unless cli_options[:account].present?
18
123
 
19
- App.info('Sparkle', "Generating appcast using `#{generate_appcast_app}`")
20
- puts "from files in `#{path}`...".indent(11)
124
+ args << "--account=#{cli_options[:account]}"
125
+ end
21
126
 
22
- if appcast.use_exported_private_key && File.exist?(private_key_path)
23
- # -s <private-EdDSA-key> The private EdDSA string (128 characters). If not
24
- # specified, the private EdDSA key will be read from
25
- # the Keychain instead.
26
- private_key = File.read(private_key_path)
27
- args << "-s=#{private_key}"
127
+ # -s <private-EdDSA-key> The private EdDSA string (128 characters). If not
128
+ # specified, the private EdDSA key will be read from
129
+ # the Keychain instead.
130
+ def private_ed_key_file(args)
131
+ if cli_options[:private_ed_key_file].present?
132
+ args << "--ed-key-file=#{cli_options[:private_ed_key_file]}"
133
+ elsif use_exported_private_key && File.exist?(@sparkle.private_key_path)
134
+ args << "--ed-key-file=#{@sparkle.private_key_path}"
135
+ end
28
136
  end
29
137
 
30
138
  # --download-url-prefix <url> A URL that will be used as prefix for the URL from
31
139
  # where updates will be downloaded.
32
- args << "--download-url-prefix=#{appcast.package_base_url}" if appcast.package_base_url.present?
140
+ def download_url_prefix(args)
141
+ if cli_options[:download_url_prefix].present?
142
+ args << "--download-url-prefix=#{cli_options[:download_url_prefix]}"
143
+ elsif package_base_url.present?
144
+ args << "--download-url-prefix=#{package_base_url}"
145
+ end
146
+ end
33
147
 
34
148
  # --release-notes-url-prefix <url> A URL that will be used as prefix for constructing
35
149
  # URLs for release notes.
36
- args << "--release-notes-url-prefix=#{appcast.notes_base_url}" if appcast.notes_base_url.present?
150
+ def release_notes_url_prefix(args)
151
+ if cli_options[:release_notes_url_prefix].present?
152
+ args << "--release-notes-url-prefix=#{cli_options[:release_notes_url_prefix]}"
153
+ elsif notes_base_url.present?
154
+ args << "--release-notes-url-prefix=#{notes_base_url}"
155
+ end
156
+ end
157
+
158
+ # --full-release-notes-url <url>
159
+ # A URL that will be used for the full release notes.
160
+ def full_release_notes_url(args)
161
+ return unless cli_options[:full_release_notes_url].present?
162
+
163
+ args << "--full-release-notes-url=#{cli_options[:full_release_notes_url]}"
164
+ end
37
165
 
38
166
  # --link <link> A URL to the application's website which Sparkle may
39
167
  # use for directing users to if they cannot download a
40
168
  # new update from within the application. This will be
41
169
  # used for new generated update items. By default, no
42
170
  # product link is used.
171
+ def link(args)
172
+ return unless cli_options[:link].present?
173
+
174
+ args << "--link=#{cli_options[:link]}"
175
+ end
43
176
 
44
177
  # --versions <versions> An optional comma delimited list of application
45
178
  # versions (specified by CFBundleVersion) to generate
@@ -47,27 +180,88 @@ module Motion
47
180
  # are inferred from the available archives and are only
48
181
  # generated if they are in the latest 5 updates in the
49
182
  # appcast.
183
+ def versions(args)
184
+ return unless cli_options[:versions].present?
185
+
186
+ args << "--versions=#{cli_options[:versions]}"
187
+ end
50
188
 
51
189
  # --maximum-deltas <maximum-deltas>
52
190
  # The maximum number of delta items to create for the
53
191
  # latest update for each minimum required operating
54
192
  # system. (default: 5)
193
+ def maximum_deltas(args)
194
+ return unless cli_options[:maximum_deltas].present?
195
+
196
+ args << "--maximum-deltas=#{cli_options[:maximum_deltas]}"
197
+ end
198
+
199
+ # --delta-compression <delta-compression>
200
+ # The compression method to use for generating delta
201
+ # updates. Supported methods for version 3 delta files
202
+ # are 'lzma', 'bzip2', 'zlib', 'lzfse', 'lz4', 'none',
203
+ # and 'default'. Note that version 2 delta files only
204
+ # support 'bzip2', 'none', and 'default' so other
205
+ # methods will be ignored if version 2 files are being
206
+ # generated. The 'default' compression for version 3
207
+ # delta files is currently lzma. (default: default)
208
+ def delta_compression(args)
209
+ return unless cli_options[:delta_compression].present?
210
+
211
+ args << "--delta-compression=#{cli_options[:delta_compression]}"
212
+ end
213
+
214
+ # --delta-compression-level <delta-compression-level>
215
+ # The compression level to use for generating delta
216
+ # updates. This only applies if the compression method
217
+ # used is bzip2 which accepts values from 1 - 9. A
218
+ # special value of 0 will use the default compression
219
+ # level. (default: 0)
220
+ def delta_compression_level(args)
221
+ return unless cli_options[:delta_compression_level].present?
222
+
223
+ args << "--delta-compression-level=#{cli_options[:delta_compression_level]}"
224
+ end
55
225
 
56
226
  # --channel <channel-name>
57
227
  # The Sparkle channel name that will be used for
58
228
  # generating new updates. By default, no channel is
59
229
  # used. Old applications need to be using Sparkle 2 to
60
230
  # use this feature.
231
+ def channel(args)
232
+ return unless cli_options[:channel].present?
233
+
234
+ args << "--channel=#{cli_options[:channel]}"
235
+ end
61
236
 
62
237
  # --major-version <major-version>
63
238
  # The last major or minimum autoupdate sparkle:version
64
239
  # that will be used for generating new updates. By
65
240
  # default, no last major version is used.
241
+ def major_version(args)
242
+ return unless cli_options[:major_version].present?
243
+
244
+ args << "--major-version=#{cli_options[:major_version]}"
245
+ end
246
+
247
+ # --ignore-skipped-upgrades-below-version <below-version>
248
+ # Ignore skipped major upgrades below this specified
249
+ # version. Only applicable for major upgrades.
250
+ def ignore_skipped_upgrades_below_version(args)
251
+ return unless cli_options[:ignore_skipped_upgrades_below_version].present?
252
+
253
+ args << "--ignore-skipped-upgrades-below-version=#{cli_options[:ignore_skipped_upgrades_below_version]}"
254
+ end
66
255
 
67
256
  # --phased-rollout-interval <phased-rollout-interval>
68
257
  # The phased rollout interval in seconds that will be
69
258
  # used for generating new updates. By default, no
70
259
  # phased rollout interval is used.
260
+ def phased_rollout_interval(args)
261
+ return unless cli_options[:phased_rollout_interval].present?
262
+
263
+ args << "--phased-rollout-interval=#{cli_options[:phased_rollout_interval]}"
264
+ end
71
265
 
72
266
  # --critical-update-version <critical-update-version>
73
267
  # The last critical update sparkle:version that will be
@@ -76,125 +270,40 @@ module Motion
76
270
  # from any application version. By default, no last
77
271
  # critical update version is used. Old applications
78
272
  # need to be using Sparkle 2 to use this feature.
273
+ def critical_update_version(args)
274
+ return unless cli_options[:critical_update_version].present?
275
+
276
+ args << "--critical-update-version=#{cli_options[:critical_update_version]}"
277
+ end
79
278
 
80
279
  # --informational-update-versions <informational-update-versions>
81
280
  # A comma delimited list of application
82
281
  # sparkle:version's that will see newly generated
83
282
  # updates as being informational only. An empty string
84
283
  # argument will treat this update as informational
85
- # coming from any application version. By default,
86
- # updates are not informational only. --link must also
87
- # be provided. Old applications need to be using
88
- # Sparkle 2 to use this feature.
284
+ # coming from any application version. Prefix a version
285
+ # string with '<' to indicate (eg "<2.5") to indicate
286
+ # older versions than the one specified should treat
287
+ # the update as informational only. By default, updates
288
+ # are not informational only. --link must also be
289
+ # provided. Old applications need to be using Sparkle 2
290
+ # to use this feature, and 2.1 or later to use the '<'
291
+ # upper bound feature.
292
+ def informational_update_versions(args)
293
+ return unless cli_options[:informational_update_versions].present?
294
+
295
+ args << "--informational-update-versions=#{cli_options[:informational_update_versions]}"
296
+ end
89
297
 
90
298
  # -o <output-path> Path to filename for the generated appcast (allowed
91
299
  # when only one will be created).
300
+ def output_path(args)
301
+ return unless cli_options[:output_path].present?
92
302
 
93
- # -f <private-dsa-key-file> Path to the private DSA key file. Only use this
94
- # option for transitioning to EdDSA from older updates.
95
- # Note: only for supporting a legacy app that used DSA keys. Check if the
96
- # default DSA key exists in `sparkle/config/dsa_priv.pem` and if it does,
97
- # add it to the command.
98
- if File.exist?(legacy_private_key_path)
99
- App.info 'Sparkle', "Also signing with legacy DSA key at #{legacy_private_key_path}"
100
- args << "-f=#{legacy_private_key_path}"
101
- end
102
-
103
- args << "-o=#{appcast_filename}" if appcast_filename.present?
104
-
105
- App.info 'Executing', [generate_appcast_app, *args, path.to_s].join(' ')
106
-
107
- results, status = Open3.capture2e(generate_appcast_app, *args, path.to_s)
108
-
109
- App.info('Sparkle', "Saved appcast to `#{appcast_filename}`") if status.success?
110
- puts results.indent(11)
111
-
112
- return unless status.success?
113
-
114
- puts
115
- puts "SUFeedURL : #{feed_url}".indent(11)
116
- puts "SUPublicEDKey : #{public_EdDSA_key}".indent(11)
117
- end
118
- # rubocop:enable Metrics/CyclomaticComplexity
119
-
120
- def generate_appcast_help
121
- generate_appcast_app = "#{vendored_sparkle_path}/bin/generate_appcast"
122
- results, _status = Open3.capture2e(generate_appcast_app, '--help')
123
- puts results
124
- end
125
-
126
- def create_release_notes
127
- App.fail "Release notes template not found as expected at ./#{release_notes_template_path}" unless File.exist?(release_notes_template_path)
128
-
129
- create_release_folder
130
-
131
- File.open(release_notes_path.to_s, 'w') do |f|
132
- template = File.read(release_notes_template_path)
133
- f << ERB.new(template).result(binding)
134
- end
135
-
136
- App.info 'Create', "./#{release_notes_path}"
137
- end
138
-
139
- def release_notes_template_path
140
- sparkle_config_path.join('release_notes.template.erb')
141
- end
142
-
143
- def release_notes_content_path
144
- sparkle_config_path.join('release_notes.content.html')
145
- end
146
-
147
- def release_notes_path
148
- sparkle_release_path + (appcast.notes_filename || "#{app_name}.#{@config.short_version}.html")
149
- end
150
-
151
- def release_notes_content
152
- if File.exist?(release_notes_content_path)
153
- File.read(release_notes_content_path)
154
- else
155
- App.fail "Missing #{release_notes_content_path}"
156
- end
157
- end
158
-
159
- def release_notes_html
160
- release_notes_content
161
- end
162
-
163
- class Appcast
164
- attr_accessor :base_url,
165
- :feed_base_url,
166
- :feed_filename,
167
- :notes_filename,
168
- :package_filename,
169
- :archive_folder,
170
- :use_exported_private_key
171
- attr_writer :notes_base_url,
172
- :package_base_url
173
-
174
- def initialize
175
- @feed_base_url = nil
176
- @feed_filename = 'releases.xml'
177
- @notes_base_url = nil
178
- @notes_filename = nil
179
- @package_base_url = nil
180
- @package_filename = nil
181
- @base_url = nil
182
- @archive_folder = nil
183
- @use_exported_private_key = false
184
- end
185
-
186
- def feed_url
187
- "#{feed_base_url || base_url}#{feed_filename}"
188
- end
189
-
190
- def notes_base_url
191
- @notes_base_url || base_url
192
- end
193
-
194
- def package_base_url
195
- @package_base_url || base_url
303
+ args << "-o=#{cli_options[:output_path]}"
196
304
  end
197
305
  end
198
306
  end
199
307
  end
200
308
  end
309
+ # rubocop:enable Metrics/ClassLength
@@ -10,6 +10,7 @@ module Motion
10
10
  def sparkle(&block)
11
11
  @sparkle ||= Motion::Project::Sparkle.new(self)
12
12
  @sparkle.instance_eval(&block) if block
13
+ @sparkle.after_initialize
13
14
  @sparkle
14
15
  end
15
16
  end
@@ -39,10 +40,18 @@ module Motion
39
40
  bundle_path = App.config.app_bundle('MacOSX')
40
41
  sparkle_path = File.join(bundle_path, 'Frameworks', 'Sparkle.framework')
41
42
 
43
+ # strip out any header files, as they are not needed for a built product, and Xcode
44
+ # typically does this. Their permissions are set such that it conflicts
45
+ # with building the deltas. See https://github.com/sparkle-project/Sparkle/issues/1972
46
+ `rm -fr "#{sparkle_path}/Versions/B/PrivateHeaders"`
47
+ `rm "#{sparkle_path}/PrivateHeaders"`
48
+ `rm -fr "#{sparkle_path}/Versions/B/Headers"`
49
+ `rm "#{sparkle_path}/Headers"`
50
+
42
51
  `/usr/bin/codesign -f -s "#{config.codesign_certificate}" -o runtime "#{sparkle_path}/Versions/B/Autoupdate"`
43
52
  `/usr/bin/codesign -f -s "#{config.codesign_certificate}" -o runtime "#{sparkle_path}/Versions/B/Updater.app"`
44
- `/usr/bin/codesign -f -s "#{config.codesign_certificate}" -o runtime "#{sparkle_path}/Versions/B/XPCServices/org.sparkle-project.InstallerLauncher.xpc"`
45
- `/usr/bin/codesign -f -s "#{config.codesign_certificate}" -o runtime --entitlements "./vendor/Pods/Sparkle/Entitlements/org.sparkle-project.Downloader.entitlements" "#{sparkle_path}/Versions/B/XPCServices/org.sparkle-project.Downloader.xpc"` # rubocop:disable Layout/LineLength
53
+ `/usr/bin/codesign -f -s "#{config.codesign_certificate}" -o runtime "#{sparkle_path}/Versions/B/XPCServices/Installer.xpc"`
54
+ `/usr/bin/codesign -f -s "#{config.codesign_certificate}" -o runtime --entitlements "./vendor/Pods/Sparkle/Entitlements/Downloader.entitlements" "#{sparkle_path}/Versions/B/XPCServices/Downloader.xpc"` # rubocop:disable Layout/LineLength
46
55
 
47
56
  `/usr/bin/codesign -f -s "#{config.codesign_certificate}" -o runtime "#{sparkle_path}"`
48
57
  end
@@ -1,17 +1,23 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Sparkle specific rake tasks
4
+ # rubocop:disable Metrics/BlockLength
4
5
  namespace :sparkle do
5
6
  desc 'Sparkle Help'
6
7
  task :help do
7
8
  puts <<~HELP
9
+
10
+
8
11
  During initial Sparkle setup, run these rake tasks:
9
12
 
10
13
  1. `rake sparkle:setup_certificates`
11
14
  2. `rake sparkle:setup`
12
15
 
13
16
  Then after running `rake build:release`, you can run
14
- `rake sparkle:package`
17
+
18
+ 3. `rake sparkle:package` create the zipped package and release notes
19
+ 4. `rake sparkle:copy_to_release` copy package/notes into release folder
20
+ 5. `rake sparkle:generate_appcast` generate the appcast
15
21
  HELP
16
22
  end
17
23
 
@@ -40,6 +46,13 @@ namespace :sparkle do
40
46
  sparkle.sign_package
41
47
  end
42
48
 
49
+ desc 'Copy the release notes and zip archive to the release folder'
50
+ task :copy_to_release do
51
+ App.config_without_setup.build_mode = :release
52
+ sparkle = App.config.sparkle
53
+ sparkle.copy_to_release
54
+ end
55
+
43
56
  desc "Generate the appcast xml feed using Sparkle's `generate_appcast`"
44
57
  task :generate_appcast do
45
58
  App.config_without_setup.build_mode = :release
@@ -83,3 +96,4 @@ namespace :clean do
83
96
  end
84
97
  end
85
98
  end
99
+ # rubocop:enable Metrics/BlockLength
@@ -27,7 +27,7 @@ module Motion
27
27
  end
28
28
 
29
29
  def check_public_key
30
- return true if public_EdDSA_key.present?
30
+ return true if public_ed_dsa_key.present?
31
31
 
32
32
  App.fail 'Sparkle :public_key is nil or blank. Please check your Rakefile.'
33
33
  end
@@ -45,7 +45,7 @@ module Motion
45
45
  App.fail "Missing `#{private_key_path}`. Please run `rake sparkle:setup_certificates` or check the docs to know where to put them."
46
46
  end
47
47
 
48
- unless public_EdDSA_key.present?
48
+ unless public_ed_dsa_key.present?
49
49
  return false if silence
50
50
 
51
51
  App.fail "Missing `#{public_key_path}`. Did you configure `release :public_key` correctly in the Rakefile? Advanced: recreate your public key with `rake sparkle:recreate_public_key`"