motion-sparkle-sandbox 2.1.0 → 2.1.1

Sign up to get free protection for your applications and to get access to all the features.
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