motion-sparkle-sandbox 2.0.1 → 2.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+ destination_path = (project_path + releases_folder).realpath
214
+ zip_file_path = (sparkle_release_path + zip_file)
215
+
216
+ [release_notes_path, zip_file_path].each do |file|
217
+ FileUtils.cp(file, "#{destination_path}/")
218
+ App.info 'Copied', "#{destination_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.0.1'
4
+ VERSION = '2.2'
5
5
  end
@@ -16,7 +16,7 @@ require 'motion/project/indent_string'
16
16
 
17
17
  lib_dir_path = File.dirname(File.expand_path(__FILE__))
18
18
 
19
- POD_VERSION = '~> 2.0.0'
19
+ POD_VERSION = '~> 2.2.0'
20
20
 
21
21
  unless @running_specs
22
22
  Motion::Project::App.setup do |app|
@@ -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 ['--account=ed25519']
15
+ end
16
+
17
+ it 'sets all command line options' do
18
+ expected_results = [
19
+ '--account=test1',
20
+ '--ed-key-file=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_ed_key_file, '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/setup_spec.rb CHANGED
@@ -2,66 +2,41 @@
2
2
 
3
3
  require File.expand_path('spec_utils', __dir__)
4
4
 
5
- module Motion
6
- module Project
7
- class Config
8
- attr_writer :project_dir
9
- end
10
- end
11
- end
12
-
13
5
  describe 'Sparkle setup' do
14
6
  before(:all) do
15
- SpecUtils::TemporaryDirectory.teardown
16
- SpecUtils::TemporaryDirectory.setup
17
-
18
- FileUtils.mkdir_p("#{SpecUtils::TemporaryDirectory.directory}resources")
19
- FileUtils.mkdir_p("#{SpecUtils::TemporaryDirectory.directory}vendor")
20
- FileUtils.touch("#{SpecUtils::TemporaryDirectory.directory}.gitignore")
21
- end
22
-
23
- context 'something' do
24
- before do
25
- @config = App.config
26
- @config.project_dir = SpecUtils::TemporaryDirectory.directory.to_s
27
- @config.instance_eval do
28
- pods do
29
- pod 'Sparkle', POD_VERSION
30
- end
31
-
32
- sparkle do
33
- release :base_url, 'http://example.com/'
34
- # release :public_key, 'public_key.pem'
35
- publish :public_key, '<YOUR-EDDSA-PUBLIC-KEY>'
36
- release :version, '1.0'
37
-
38
- # Optional config options
39
- release :feed_base_url, 'http://rss.example.com/'
40
- release :feed_filename, 'example.xml'
41
- release :notes_base_url, 'http://www.example.com/'
42
- release :notes_filename, 'example.html'
43
- release :package_base_url, 'http://download.example.com/'
44
- release :package_filename, 'example.zip'
45
- # publish :use_exported_private_key, true
46
- end
7
+ @config = App.config
8
+ @config.project_dir = SpecUtils::TemporaryDirectory.directory.to_s
9
+ @config.instance_eval do
10
+ sparkle do
11
+ release :base_url, 'http://example.com/'
12
+ # release :public_key, 'public_key.pem'
13
+ publish :public_key, '<YOUR-EDDSA-PUBLIC-KEY>'
14
+ release :version, '1.0'
15
+
16
+ # Optional config options
17
+ release :feed_base_url, 'http://rss.example.com/'
18
+ release :feed_filename, 'example.xml'
19
+ release :notes_base_url, 'http://www.example.com/'
20
+ release :notes_filename, 'example.html'
21
+ release :package_base_url, 'http://download.example.com/'
22
+ release :package_filename, 'example.zip'
23
+ # publish :use_exported_private_key, true
47
24
  end
48
-
49
- Rake::Task['pod:install'].invoke
50
- Rake::Task['sparkle:setup'].invoke
51
- # Rake::Task['sparkle:setup_certificates'].invoke
52
- end
53
-
54
- it 'should create private certificate' do
55
- expect(File.exist?(@config.sparkle.private_key_path.to_s)).to be_truthy
56
25
  end
57
26
 
58
- it 'should create public certificate' do
59
- expect(File.exist?(@config.sparkle.public_key_path.to_s)).to be_truthy
60
- end
27
+ Rake::Task['sparkle:setup'].invoke
28
+ end
61
29
 
62
- it 'should add files to gitignore' do
63
- a = `cat .gitignore`
64
- expect(a.strip).not_to eq ''
65
- end
30
+ # it 'should create private certificate' do
31
+ # expect(File.exist?(@config.sparkle.private_key_path.to_s)).to be_truthy
32
+ # end
33
+ #
34
+ # it 'should create public certificate' do
35
+ # expect(File.exist?(@config.sparkle.public_key_path.to_s)).to be_truthy
36
+ # end
37
+
38
+ it 'should add files to gitignore' do
39
+ a = `cat .gitignore`
40
+ expect(a.strip).not_to eq ''
66
41
  end
67
42
  end
data/spec/sparkle_spec.rb CHANGED
@@ -2,37 +2,20 @@
2
2
 
3
3
  require File.expand_path('spec_utils', __dir__)
4
4
 
5
- module Motion
6
- module Project
7
- class Config
8
- attr_writer :project_dir
9
- end
10
- end
11
- end
12
-
13
5
  # rubocop:disable Metrics/BlockLength
14
6
  describe 'motion-sparkle-sandbox' do
15
7
  before(:all) do
16
- SpecUtils::TemporaryDirectory.teardown
17
- SpecUtils::TemporaryDirectory.setup
18
-
19
- FileUtils.mkdir_p("#{SpecUtils::TemporaryDirectory.directory}resources")
20
- FileUtils.mkdir_p("#{SpecUtils::TemporaryDirectory.directory}vendor")
21
- FileUtils.touch("#{SpecUtils::TemporaryDirectory.directory}.gitignore")
22
- end
23
-
24
- context 'configuration' do
25
- before do
26
- @config = App.config
27
- @config.sparkle = nil
28
- @config.project_dir = SpecUtils::TemporaryDirectory.directory.to_s
29
- @config.instance_eval do
30
- sparkle do
31
- release :base_url, 'http://example.com/'
32
- end
8
+ @config = App.config
9
+ @config.sparkle = nil
10
+ @config.project_dir = SpecUtils::TemporaryDirectory.directory.to_s
11
+ @config.instance_eval do
12
+ sparkle do
13
+ release :base_url, 'http://example.com/'
33
14
  end
34
15
  end
16
+ end
35
17
 
18
+ context 'configuration' do
36
19
  describe 'base url' do
37
20
  it 'base url should be set correctly' do
38
21
  expect(@config.sparkle.appcast.base_url).to eq 'http://example.com/'
@@ -46,6 +29,7 @@ describe 'motion-sparkle-sandbox' do
46
29
 
47
30
  it 'uses feed_base_url' do
48
31
  @config.sparkle.publish(:feed_base_url, 'http://rss.example.com/')
32
+ @config.sparkle.after_initialize
49
33
 
50
34
  expect(@config.info_plist['SUFeedURL']).to eq 'http://rss.example.com/releases.xml'
51
35
  end
@@ -53,6 +37,7 @@ describe 'motion-sparkle-sandbox' do
53
37
  it 'uses feed_filename' do
54
38
  @config.sparkle.publish(:feed_base_url, 'http://rss.example.com/')
55
39
  @config.sparkle.publish(:feed_filename, 'example.xml')
40
+ @config.sparkle.after_initialize
56
41
 
57
42
  expect(@config.info_plist['SUFeedURL']).to eq 'http://rss.example.com/example.xml'
58
43
  end
@@ -115,22 +100,10 @@ describe 'motion-sparkle-sandbox' do
115
100
  end
116
101
 
117
102
  context 'cocoapod' do
118
- before do
119
- @config = App.config
120
- @config.project_dir = SpecUtils::TemporaryDirectory.directory.to_s
121
- @config.instance_eval do
122
- pods do
123
- pod 'Sparkle', POD_VERSION
124
- end
125
- end
126
-
127
- Rake::Task['pod:install'].invoke
128
- end
129
-
130
103
  it 'Sparkle framework pod should be embedded' do
131
104
  sparkle_framework_path = 'vendor/Pods/Sparkle/Sparkle.framework'
132
- @config.pods.pods_libraries
133
105
 
106
+ @config.pods.pods_libraries
134
107
  expect(@config.embedded_frameworks.first.end_with?(sparkle_framework_path)).to be_truthy
135
108
  end
136
109
  end
data/spec/spec_helper.rb CHANGED
@@ -97,4 +97,12 @@ RSpec.configure do |config|
97
97
  # # test failures related to randomization by passing the same `--seed` value
98
98
  # # as the one that triggered the failure.
99
99
  # Kernel.srand config.seed
100
+
101
+ config.before(:suite) do
102
+ SpecUtils::SparkleSetup.initial_install
103
+ end
104
+
105
+ config.after(:suite) do
106
+ SpecUtils::SparkleSetup.final_deinstall
107
+ end
100
108
  end
data/spec/spec_utils.rb CHANGED
@@ -13,7 +13,43 @@ $:.unshift("#{ROOT}lib".to_s)
13
13
  require 'motion/project/template/osx'
14
14
  require 'motion-sparkle-sandbox'
15
15
 
16
+ # necessary for us to be able to overwrite the `project_dir`
17
+ module Motion
18
+ module Project
19
+ class Config
20
+ attr_writer :project_dir
21
+ end
22
+ end
23
+ end
24
+
16
25
  module SpecUtils
26
+ module SparkleSetup
27
+ # run from a before(:suite)
28
+ def self.initial_install
29
+ SpecUtils::TemporaryDirectory.setup
30
+
31
+ FileUtils.mkdir_p("#{SpecUtils::TemporaryDirectory.directory}/resources")
32
+ FileUtils.mkdir_p("#{SpecUtils::TemporaryDirectory.directory}/vendor")
33
+ FileUtils.touch("#{SpecUtils::TemporaryDirectory.directory}/.gitignore")
34
+
35
+ @config = App.config
36
+ @config.sparkle = nil
37
+ @config.project_dir = SpecUtils::TemporaryDirectory.directory.to_s
38
+ @config.instance_eval do
39
+ pods do
40
+ pod 'Sparkle', POD_VERSION
41
+ end
42
+ end
43
+
44
+ Rake::Task['pod:install'].invoke
45
+ end
46
+
47
+ # run from an after(:suite)
48
+ def self.final_deinstall
49
+ SpecUtils::TemporaryDirectory.teardown
50
+ end
51
+ end
52
+
17
53
  module TemporaryDirectory
18
54
  TEMPORARY_DIRECTORY = ROOT + 'tmp' # rubocop:disable Style/StringConcatenation
19
55
 
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.0.1
4
+ version: '2.2'
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-27 00:00:00.000000000 Z
12
+ date: 2022-07-16 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