motion-sparkle-sandbox 2.1.0 → 2.1.1

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: df7c1845f67138c6774aab881d396058ee2041c1ed541ba0cc824bb48fac8839
4
- data.tar.gz: eaaa9add1f9755ebf2858da7ee56781836c5aeb63cddb4483f2326ad7c5751bc
3
+ metadata.gz: ca50d516f00ac1b85b4212983fbe11e53f1d859ebbb18b2d3e4fad974a055523
4
+ data.tar.gz: 263ad26c09854be232dd7a19485270b02d33b148838925f09f06c874eef2f993
5
5
  SHA512:
6
- metadata.gz: 47b4434bdfdaa48004bdd4e70f4c1608548c204b001246609a8a25356aa9f5cbad502e6b0c14198e1161a0d319599b53361fb25f6de1284e0553914c13f9db96
7
- data.tar.gz: 62a6a7bbee1cd82ebc2a414300db3d0307b75a052c9d480167cefc5401182cff2adcb7394eb119d2d7eafac52b7c2fb3bca6e9f31cc3d15fea2b9656691d58e1
6
+ metadata.gz: 85a7857cc96ef6e0a54bca54bdb416316781101cd7a85510c4905d8ecca79c3558795c89f83225df83db2f914052be24a570ddf03d093f80ea0121b75324141a
7
+ data.tar.gz: fecced623e72e228bc0f64025c40aae21bd89819de2a7c8d655e8f04043be0af6d68820372a5018dbad69302d67678b1feccacd6efe683b84d583ff23f899309
data/bin/bundle CHANGED
@@ -11,7 +11,7 @@
11
11
  require "rubygems"
12
12
 
13
13
  m = Module.new do
14
- module_function
14
+ module_function
15
15
 
16
16
  def invoked_as_script?
17
17
  File.expand_path($0) == File.expand_path(__FILE__)
@@ -31,7 +31,7 @@ m = Module.new do
31
31
  bundler_version = a
32
32
  end
33
33
  next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/
34
- bundler_version = $1 || ">= 0.a"
34
+ bundler_version = $1
35
35
  update_index = i
36
36
  end
37
37
  bundler_version
@@ -61,32 +61,41 @@ m = Module.new do
61
61
  end
62
62
 
63
63
  def bundler_version
64
- @bundler_version ||= begin
64
+ @bundler_version ||=
65
65
  env_var_version || cli_arg_version ||
66
- lockfile_version || "#{Gem::Requirement.default}.a"
67
- end
66
+ lockfile_version
67
+ end
68
+
69
+ def bundler_requirement
70
+ return "#{Gem::Requirement.default}.a" unless bundler_version
71
+
72
+ bundler_gem_version = Gem::Version.new(bundler_version)
73
+
74
+ requirement = bundler_gem_version.approximate_recommendation
75
+
76
+ return requirement unless Gem::Version.new(Gem::VERSION) < Gem::Version.new("2.7.0")
77
+
78
+ requirement += ".a" if bundler_gem_version.prerelease?
79
+
80
+ requirement
68
81
  end
69
82
 
70
83
  def load_bundler!
71
84
  ENV["BUNDLE_GEMFILE"] ||= gemfile
72
85
 
73
- # must dup string for RG < 1.8 compatibility
74
- activate_bundler(bundler_version.dup)
86
+ activate_bundler
75
87
  end
76
88
 
77
- def activate_bundler(bundler_version)
78
- if Gem::Version.correct?(bundler_version) && Gem::Version.new(bundler_version).release < Gem::Version.new("2.0")
79
- bundler_version = "< 2"
80
- end
89
+ def activate_bundler
81
90
  gem_error = activation_error_handling do
82
- gem "bundler", bundler_version
91
+ gem "bundler", bundler_requirement
83
92
  end
84
93
  return if gem_error.nil?
85
94
  require_error = activation_error_handling do
86
95
  require "bundler/version"
87
96
  end
88
- return if require_error.nil? && Gem::Requirement.new(bundler_version).satisfied_by?(Gem::Version.new(Bundler::VERSION))
89
- warn "Activating bundler (#{bundler_version}) failed:\n#{gem_error.message}\n\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_version}'`"
97
+ return if require_error.nil? && Gem::Requirement.new(bundler_requirement).satisfied_by?(Gem::Version.new(Bundler::VERSION))
98
+ warn "Activating bundler (#{bundler_requirement}) failed:\n#{gem_error.message}\n\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_requirement}'`"
90
99
  exit 42
91
100
  end
92
101
 
@@ -1,45 +1,179 @@
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_eddsa_key: '-s',
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_eddsa_key(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_eddsa_key(args)
131
+ if cli_options[:private_eddsa_key].present?
132
+ args << "-s=#{cli_options[:private_eddsa_key]}"
133
+ elsif use_exported_private_key && File.exist?(private_key_path)
134
+ private_key = File.read(private_key_path)
135
+ args << "-s=#{private_key}"
136
+ end
28
137
  end
29
138
 
30
139
  # --download-url-prefix <url> A URL that will be used as prefix for the URL from
31
140
  # where updates will be downloaded.
32
- args << "--download-url-prefix=#{appcast.package_base_url}" if appcast.package_base_url.present?
141
+ def download_url_prefix(args)
142
+ if cli_options[:download_url_prefix].present?
143
+ args << "--download-url-prefix=#{cli_options[:download_url_prefix]}"
144
+ elsif package_base_url.present?
145
+ args << "--download-url-prefix=#{package_base_url}"
146
+ end
147
+ end
33
148
 
34
149
  # --release-notes-url-prefix <url> A URL that will be used as prefix for constructing
35
150
  # URLs for release notes.
36
- args << "--release-notes-url-prefix=#{appcast.notes_base_url}" if appcast.notes_base_url.present?
151
+ def release_notes_url_prefix(args)
152
+ if cli_options[:release_notes_url_prefix].present?
153
+ args << "--release-notes-url-prefix=#{cli_options[:release_notes_url_prefix]}"
154
+ elsif notes_base_url.present?
155
+ args << "--release-notes-url-prefix=#{notes_base_url}"
156
+ end
157
+ end
158
+
159
+ # --full-release-notes-url <url>
160
+ # A URL that will be used for the full release notes.
161
+ def full_release_notes_url(args)
162
+ return unless cli_options[:full_release_notes_url].present?
163
+
164
+ args << "--full-release-notes-url=#{cli_options[:full_release_notes_url]}"
165
+ end
37
166
 
38
167
  # --link <link> A URL to the application's website which Sparkle may
39
168
  # use for directing users to if they cannot download a
40
169
  # new update from within the application. This will be
41
170
  # used for new generated update items. By default, no
42
171
  # product link is used.
172
+ def link(args)
173
+ return unless cli_options[:link].present?
174
+
175
+ args << "--link=#{cli_options[:link]}"
176
+ end
43
177
 
44
178
  # --versions <versions> An optional comma delimited list of application
45
179
  # versions (specified by CFBundleVersion) to generate
@@ -47,27 +181,88 @@ module Motion
47
181
  # are inferred from the available archives and are only
48
182
  # generated if they are in the latest 5 updates in the
49
183
  # appcast.
184
+ def versions(args)
185
+ return unless cli_options[:versions].present?
186
+
187
+ args << "--versions=#{cli_options[:versions]}"
188
+ end
50
189
 
51
190
  # --maximum-deltas <maximum-deltas>
52
191
  # The maximum number of delta items to create for the
53
192
  # latest update for each minimum required operating
54
193
  # system. (default: 5)
194
+ def maximum_deltas(args)
195
+ return unless cli_options[:maximum_deltas].present?
196
+
197
+ args << "--maximum-deltas=#{cli_options[:maximum_deltas]}"
198
+ end
199
+
200
+ # --delta-compression <delta-compression>
201
+ # The compression method to use for generating delta
202
+ # updates. Supported methods for version 3 delta files
203
+ # are 'lzma', 'bzip2', 'zlib', 'lzfse', 'lz4', 'none',
204
+ # and 'default'. Note that version 2 delta files only
205
+ # support 'bzip2', 'none', and 'default' so other
206
+ # methods will be ignored if version 2 files are being
207
+ # generated. The 'default' compression for version 3
208
+ # delta files is currently lzma. (default: default)
209
+ def delta_compression(args)
210
+ return unless cli_options[:delta_compression].present?
211
+
212
+ args << "--delta-compression=#{cli_options[:delta_compression]}"
213
+ end
214
+
215
+ # --delta-compression-level <delta-compression-level>
216
+ # The compression level to use for generating delta
217
+ # updates. This only applies if the compression method
218
+ # used is bzip2 which accepts values from 1 - 9. A
219
+ # special value of 0 will use the default compression
220
+ # level. (default: 0)
221
+ def delta_compression_level(args)
222
+ return unless cli_options[:delta_compression_level].present?
223
+
224
+ args << "--delta-compression-level=#{cli_options[:delta_compression_level]}"
225
+ end
55
226
 
56
227
  # --channel <channel-name>
57
228
  # The Sparkle channel name that will be used for
58
229
  # generating new updates. By default, no channel is
59
230
  # used. Old applications need to be using Sparkle 2 to
60
231
  # use this feature.
232
+ def channel(args)
233
+ return unless cli_options[:channel].present?
234
+
235
+ args << "--channel=#{cli_options[:channel]}"
236
+ end
61
237
 
62
238
  # --major-version <major-version>
63
239
  # The last major or minimum autoupdate sparkle:version
64
240
  # that will be used for generating new updates. By
65
241
  # default, no last major version is used.
242
+ def major_version(args)
243
+ return unless cli_options[:major_version].present?
244
+
245
+ args << "--major-version=#{cli_options[:major_version]}"
246
+ end
247
+
248
+ # --ignore-skipped-upgrades-below-version <below-version>
249
+ # Ignore skipped major upgrades below this specified
250
+ # version. Only applicable for major upgrades.
251
+ def ignore_skipped_upgrades_below_version(args)
252
+ return unless cli_options[:ignore_skipped_upgrades_below_version].present?
253
+
254
+ args << "--ignore-skipped-upgrades-below-version=#{cli_options[:ignore_skipped_upgrades_below_version]}"
255
+ end
66
256
 
67
257
  # --phased-rollout-interval <phased-rollout-interval>
68
258
  # The phased rollout interval in seconds that will be
69
259
  # used for generating new updates. By default, no
70
260
  # phased rollout interval is used.
261
+ def phased_rollout_interval(args)
262
+ return unless cli_options[:phased_rollout_interval].present?
263
+
264
+ args << "--phased-rollout-interval=#{cli_options[:phased_rollout_interval]}"
265
+ end
71
266
 
72
267
  # --critical-update-version <critical-update-version>
73
268
  # The last critical update sparkle:version that will be
@@ -76,125 +271,40 @@ module Motion
76
271
  # from any application version. By default, no last
77
272
  # critical update version is used. Old applications
78
273
  # need to be using Sparkle 2 to use this feature.
274
+ def critical_update_version(args)
275
+ return unless cli_options[:critical_update_version].present?
276
+
277
+ args << "--critical-update-version=#{cli_options[:critical_update_version]}"
278
+ end
79
279
 
80
280
  # --informational-update-versions <informational-update-versions>
81
281
  # A comma delimited list of application
82
282
  # sparkle:version's that will see newly generated
83
283
  # updates as being informational only. An empty string
84
284
  # 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.
285
+ # coming from any application version. Prefix a version
286
+ # string with '<' to indicate (eg "<2.5") to indicate
287
+ # older versions than the one specified should treat
288
+ # the update as informational only. By default, updates
289
+ # are not informational only. --link must also be
290
+ # provided. Old applications need to be using Sparkle 2
291
+ # to use this feature, and 2.1 or later to use the '<'
292
+ # upper bound feature.
293
+ def informational_update_versions(args)
294
+ return unless cli_options[:informational_update_versions].present?
295
+
296
+ args << "--informational-update-versions=#{cli_options[:informational_update_versions]}"
297
+ end
89
298
 
90
299
  # -o <output-path> Path to filename for the generated appcast (allowed
91
300
  # when only one will be created).
301
+ def output_path(args)
302
+ return unless cli_options[:output_path].present?
92
303
 
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
304
+ args << "-o=#{cli_options[:output_path]}"
196
305
  end
197
306
  end
198
307
  end
199
308
  end
200
309
  end
310
+ # 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
@@ -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`"
@@ -15,29 +15,22 @@ module Motion
15
15
  # verify_installation
16
16
  end
17
17
 
18
+ def after_initialize
19
+ self.feed_url = appcast.feed_url
20
+ end
21
+
18
22
  def appcast
19
- @appcast ||= Appcast.new
23
+ @appcast ||= Appcast.new(self)
20
24
  end
21
25
 
22
26
  def publish(key, value)
27
+ return if appcast.process_option(key, value)
28
+
23
29
  case key
24
30
  when :public_key
25
- self.public_EdDSA_key = value
26
- when :base_url
27
- appcast.base_url = value
28
- self.feed_url = appcast.feed_url
29
- when :feed_base_url
30
- appcast.feed_base_url = value
31
- self.feed_url = appcast.feed_url
32
- when :feed_filename
33
- appcast.feed_filename = value
34
- self.feed_url = appcast.feed_url
31
+ self.public_ed_dsa_key = value
35
32
  when :version
36
- version value
37
- when :package_base_url, :package_filename, :notes_base_url, :notes_filename, :use_exported_private_key
38
- appcast.send "#{key}=", value
39
- when :archive_folder
40
- appcast.archive_folder = value
33
+ version(value)
41
34
  else
42
35
  raise "Unknown Sparkle config option #{key}"
43
36
  end
@@ -61,15 +54,13 @@ module Motion
61
54
  @config.info_plist['SUFeedURL'] = url
62
55
  end
63
56
 
64
- # rubocop:disable Naming/MethodName
65
- def public_EdDSA_key
57
+ def public_ed_dsa_key
66
58
  @config.info_plist['SUPublicEDKey']
67
59
  end
68
60
 
69
- def public_EdDSA_key=(key)
61
+ def public_ed_dsa_key=(key)
70
62
  @config.info_plist['SUPublicEDKey'] = key
71
63
  end
72
- # rubocop:enable Naming/MethodName
73
64
 
74
65
  # File manipulation and certificates
75
66
 
@@ -109,7 +100,7 @@ module Motion
109
100
 
110
101
  if appcast.use_exported_private_key && File.exist?(private_key_path)
111
102
  App.info 'Sparkle', "Private key already exported at `#{private_key_path}` and will be used."
112
- if public_EdDSA_key.present?
103
+ if public_ed_dsa_key.present?
113
104
  App.info '', <<~EXISTS
114
105
  SUPublicEDKey already set
115
106
 
@@ -139,12 +130,12 @@ module Motion
139
130
  return
140
131
  end
141
132
 
142
- results, status = Open3.capture2e(generate_keys_app, '-p')
133
+ results, status = Open3.capture2e(generate_keys_app, '-p', '--account', appcast.cli_options[:account])
143
134
 
144
135
  if status.success?
145
- App.info 'Sparkle', 'Public/private keys found in the keychain'
136
+ App.info 'Sparkle', "Public/private keys found in the keychain for account #{appcast.cli_options[:account]}"
146
137
 
147
- if results.strip == public_EdDSA_key
138
+ if results.strip == public_ed_dsa_key
148
139
  App.info 'Sparkle', 'Keychain public key matches `SUPublicEDKey`'
149
140
 
150
141
  if appcast.use_exported_private_key && !File.exist?(private_key_path)
@@ -155,7 +146,7 @@ module Motion
155
146
  Keychain public key DOES NOT match `SUPublicEDKey`
156
147
 
157
148
  Keychain public key: #{results.strip}
158
- SUPublicEDKey public key: #{public_EdDSA_key}
149
+ SUPublicEDKey public key: #{public_ed_dsa_key}
159
150
 
160
151
  NOT_MATCHED
161
152
  .indent(11, skip_first_line: true)
@@ -173,7 +164,7 @@ module Motion
173
164
  def create_private_key
174
165
  App.info 'Sparkle',
175
166
  'Generating a new signing key into the Keychain. This may take a moment, depending on your machine.'
176
- results, status = Open3.capture2e(generate_keys_app)
167
+ results, status = Open3.capture2e(generate_keys_app, '--account', appcast.cli_options[:account])
177
168
 
178
169
  App.fail 'Sparkle could not generate keys' unless status.success?
179
170
 
@@ -181,7 +172,7 @@ module Motion
181
172
  puts results.lines[1..].join.indent(11)
182
173
 
183
174
  # Extract the public key so we can use it in message
184
- results, status = Open3.capture2e(generate_keys_app, '-p')
175
+ results, status = Open3.capture2e(generate_keys_app, '-p', '--account', appcast.cli_options[:account])
185
176
 
186
177
  App.fail 'Unable to read public key' unless status.success?
187
178
 
@@ -199,7 +190,7 @@ module Motion
199
190
 
200
191
  # Export the private key from the keychain
201
192
  def export_private_key
202
- _results, status = Open3.capture2e(generate_keys_app, '-x', private_key_path.to_s)
193
+ _results, status = Open3.capture2e(generate_keys_app, '-x', private_key_path.to_s, '--account', appcast.cli_options[:account])
203
194
 
204
195
  App.fail 'Unable to export private key' unless status.success?
205
196
 
@@ -216,6 +207,92 @@ module Motion
216
207
  .indent(11)
217
208
  end
218
209
 
210
+ # copy the release notes and zip archive into the releases_folder,
211
+ # where the appcast will get built
212
+ def copy_to_release
213
+ path = (project_path + releases_folder).realpath
214
+ zip_file_path = (sparkle_release_path + zip_file).realpath
215
+
216
+ [release_notes_path, zip_file_path].each do |file|
217
+ FileUtils.cp(file, "#{path}/")
218
+ App.info 'Copied', "./#{path}/#{file}"
219
+ end
220
+ end
221
+
222
+ # Generate the appcast.
223
+ # Note: We do not support the old DSA keys, only the newer EdDSA keys.
224
+ # See https://sparkle-project.org/documentation/eddsa-migration
225
+ def generate_appcast
226
+ generate_appcast_app = "#{vendored_sparkle_path}/bin/generate_appcast"
227
+ path = (project_path + releases_folder).realpath
228
+ appcast_filename = (path + appcast.feed_filename)
229
+ appcast.cli_options[:output_path] = appcast_filename
230
+
231
+ FileUtils.mkdir_p(path) unless File.exist?(path)
232
+
233
+ App.info('Sparkle', "Generating appcast using `#{generate_appcast_app}`")
234
+ puts "from files in `#{path}`...".indent(11)
235
+
236
+ args = appcast.prepare_args
237
+
238
+ App.info 'Executing', [generate_appcast_app, *args, path.to_s].join(' ')
239
+
240
+ results, status = Open3.capture2e(generate_appcast_app, *args, path.to_s)
241
+
242
+ App.info('Sparkle', "Saved appcast to `#{appcast_filename}`") if status.success?
243
+ puts results.indent(11)
244
+
245
+ return unless status.success?
246
+
247
+ puts
248
+ puts "SUFeedURL : #{feed_url}".indent(11)
249
+ puts "SUPublicEDKey : #{public_ed_dsa_key}".indent(11)
250
+ end
251
+
252
+ def generate_appcast_help
253
+ generate_appcast_app = "#{vendored_sparkle_path}/bin/generate_appcast"
254
+ results, _status = Open3.capture2e(generate_appcast_app, '--help')
255
+
256
+ puts results
257
+ end
258
+
259
+ def create_release_notes
260
+ App.fail "Release notes template not found as expected at ./#{release_notes_template_path}" unless File.exist?(release_notes_template_path)
261
+
262
+ create_release_folder
263
+
264
+ File.open(release_notes_path.to_s, 'w') do |f|
265
+ template = File.read(release_notes_template_path)
266
+ f << ERB.new(template).result(binding)
267
+ end
268
+
269
+ App.info 'Create', "./#{release_notes_path}"
270
+ end
271
+
272
+ def release_notes_template_path
273
+ sparkle_config_path.join('release_notes.template.erb')
274
+ end
275
+
276
+ def release_notes_content_path
277
+ sparkle_config_path.join('release_notes.content.html')
278
+ end
279
+
280
+ def release_notes_path
281
+ sparkle_release_path + (appcast.notes_filename || "#{app_name}.#{@config.short_version}.html")
282
+ end
283
+
284
+ def release_notes_content
285
+ if File.exist?(release_notes_content_path)
286
+ File.read(release_notes_content_path)
287
+ else
288
+ App.fail "Missing #{release_notes_content_path}"
289
+ end
290
+ end
291
+
292
+ def release_notes_html
293
+ release_notes_content
294
+ end
295
+
219
296
  # A few helpers
220
297
 
221
298
  def project_path
@@ -242,10 +319,6 @@ module Motion
242
319
  sparkle_config_path.join(EDDSA_PRIV_KEY)
243
320
  end
244
321
 
245
- def legacy_private_key_path
246
- sparkle_config_path.join(DSA_PRIV_KEY)
247
- end
248
-
249
322
  def app_bundle_path
250
323
  Pathname.new(@config.app_bundle_raw('MacOSX'))
251
324
  end
@@ -262,8 +335,8 @@ module Motion
262
335
  appcast.package_filename || "#{app_name}.#{@config.short_version}.zip"
263
336
  end
264
337
 
265
- def archive_folder
266
- appcast.archive_folder
338
+ def releases_folder
339
+ appcast.releases_folder
267
340
  end
268
341
 
269
342
  def app_file
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MotionSparkleSandbox
4
- VERSION = '2.1.0'
4
+ VERSION = '2.1.1'
5
5
  end
@@ -7,3 +7,4 @@ vendor
7
7
 
8
8
  sparkle/release
9
9
  sparkle/config
10
+ sparkle/config/eddsa_priv.key
data/sample-app/Rakefile CHANGED
@@ -25,8 +25,8 @@ Motion::Project::App.setup do |app|
25
25
 
26
26
  app.sparkle do
27
27
  publish :base_url, 'http://example.com/your_app_folder/releases/'
28
- publish :public_key, '06Iq4HGJFToJrA8lqWWG0SCqqtGXLuEN1Wgy/CVJBnI='
29
- publish :archive_folder, '../published/releases/v2.2/'
28
+ publish :public_key, '1/eJupmw4JH+iFQIlh99nS2qvwYealJsNzFN3LL6FFE=x'
29
+ publish :releases_folder, 'tmp/releases/v2.2/'
30
30
 
31
31
  # Appcast Feed
32
32
  # publish :feed_base_url, 'http://downloads.example.com/releases' # defaults to base_url
@@ -40,5 +40,8 @@ Motion::Project::App.setup do |app|
40
40
  # publish :package_base_url, 'http://downloads.example.com/releases/' # defaults to base_url
41
41
 
42
42
  # publish :use_exported_private_key, true
43
+
44
+ publish :account, 'motion-sparkle-sandbox.sample-app'
45
+ publish :full_release_notes_url, 'http://example.com/full_release_notes_changed_again.html'
43
46
  end
44
47
  end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ require File.expand_path('spec_utils', __dir__)
4
+
5
+ describe 'Appcast' do
6
+ describe 'arguments' do
7
+ before do
8
+ @appcast = Motion::Project::Sparkle::Appcast.new(nil)
9
+ end
10
+
11
+ it 'defaults to empty' do
12
+ args = @appcast.prepare_args
13
+
14
+ expect(args).to eq []
15
+ end
16
+
17
+ it 'sets all command line options' do
18
+ expected_results = [
19
+ '--account=test1',
20
+ '-s=test2',
21
+ '--download-url-prefix=test3',
22
+ '--release-notes-url-prefix=test4',
23
+ '--full-release-notes-url=test5',
24
+ '--link=test6',
25
+ '--versions=test7',
26
+ '--maximum-deltas=test8',
27
+ '--delta-compression=test9',
28
+ '--delta-compression-level=test10',
29
+ '--channel=test11',
30
+ '--major-version=test12',
31
+ '--ignore-skipped-upgrades-below-version=test13',
32
+ '--phased-rollout-interval=test14',
33
+ '--critical-update-version=test15',
34
+ '--informational-update-versions=test16',
35
+ '-o=test17'
36
+ ]
37
+
38
+ @appcast.process_option(:account, 'test1')
39
+ @appcast.process_option(:private_eddsa_key, 'test2')
40
+ @appcast.process_option(:download_url_prefix, 'test3')
41
+ @appcast.process_option(:release_notes_url_prefix, 'test4')
42
+ @appcast.process_option(:full_release_notes_url, 'test5')
43
+ @appcast.process_option(:link, 'test6')
44
+ @appcast.process_option(:versions, 'test7')
45
+ @appcast.process_option(:maximum_deltas, 'test8')
46
+ @appcast.process_option(:delta_compression, 'test9')
47
+ @appcast.process_option(:delta_compression_level, 'test10')
48
+ @appcast.process_option(:channel, 'test11')
49
+ @appcast.process_option(:major_version, 'test12')
50
+ @appcast.process_option(:ignore_skipped_upgrades_below_version, 'test13')
51
+ @appcast.process_option(:phased_rollout_interval, 'test14')
52
+ @appcast.process_option(:critical_update_version, 'test15')
53
+ @appcast.process_option(:informational_update_versions, 'test16')
54
+ @appcast.process_option(:output_path, 'test17')
55
+ args = @appcast.prepare_args
56
+
57
+ expect(args).to match_array expected_results
58
+ end
59
+ end
60
+ end
data/spec/sparkle_spec.rb CHANGED
@@ -29,6 +29,7 @@ describe 'motion-sparkle-sandbox' do
29
29
 
30
30
  it 'uses feed_base_url' do
31
31
  @config.sparkle.publish(:feed_base_url, 'http://rss.example.com/')
32
+ @config.sparkle.after_initialize
32
33
 
33
34
  expect(@config.info_plist['SUFeedURL']).to eq 'http://rss.example.com/releases.xml'
34
35
  end
@@ -36,6 +37,7 @@ describe 'motion-sparkle-sandbox' do
36
37
  it 'uses feed_filename' do
37
38
  @config.sparkle.publish(:feed_base_url, 'http://rss.example.com/')
38
39
  @config.sparkle.publish(:feed_filename, 'example.xml')
40
+ @config.sparkle.after_initialize
39
41
 
40
42
  expect(@config.info_plist['SUFeedURL']).to eq 'http://rss.example.com/example.xml'
41
43
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: motion-sparkle-sandbox
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.0
4
+ version: 2.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brett Walker
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2022-05-28 00:00:00.000000000 Z
12
+ date: 2022-06-12 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: motion-cocoapods
@@ -95,6 +95,7 @@ files:
95
95
  - sample-app/resources/Assets.xcassets/Contents.json
96
96
  - sample-app/resources/Credits.rtf
97
97
  - sample-app/spec/main_spec.rb
98
+ - spec/appcast_spec.rb
98
99
  - spec/setup_spec.rb
99
100
  - spec/sparkle_spec.rb
100
101
  - spec/spec_helper.rb
@@ -124,6 +125,7 @@ signing_key:
124
125
  specification_version: 4
125
126
  summary: Sparkle (sandboxed) integration for Rubymotion projects
126
127
  test_files:
128
+ - spec/appcast_spec.rb
127
129
  - spec/spec_helper.rb
128
130
  - spec/sparkle_spec.rb
129
131
  - spec/setup_spec.rb