glitter 1.0.0 → 2.0.0.alpha1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -3,4 +3,7 @@ bin/Glitterfile
3
3
  *.gem
4
4
  .bundle
5
5
  Gemfile.lock
6
- pkg/*
6
+ pkg/*
7
+ tmp
8
+ .env
9
+ .rbenv-version
data/README.md CHANGED
@@ -1,72 +1,88 @@
1
1
  # About Glitter
2
2
 
3
- Glitter is an easy way to publish Mac software updates to an Amazon S3 bucket using the Sparkle framework (http://sparkle.andymatuschak.org/). It was created at Poll Everywhere to eliminate the need maintaining additional server infrastructure for rolling out Mac desktop software updates.
3
+ Glitter is an easy way to publish native application software updates to an Amazon S3 bucket. It was created at Poll Everywhere to eliminate the need maintaining additional server infrastructure for rolling out native desktop software updates. Glitter works with various "Sparkle" frameworks including:
4
4
 
5
- It should also be noted that Glitter uses HTTPS S3 URLs to eliminate the need for the maintiance of public/private keys for Sparkle, which further simplifies the publishing process.
5
+ * Sparkle - http://sparkle.andymatuschak.org/
6
+ * NetSparkle - http://netsparkle.codeplex.com/
6
7
 
7
- # Getting started
8
-
9
- 1. Install the gem
10
-
11
- $ gem install glitter
12
-
13
- 2. Generate a Glitterfile in your project directory
8
+ Glitter also supports the concepts of release channels, which makes it possible to have development, nightly, staging, beta, or production releases all from the command line interface. Release channels make it easier for developers to collaborate with customers and product owners about product features.
14
9
 
15
- $ glitter init .
10
+ # Getting started
16
11
 
17
- 3. Edit the Glitterfile
12
+ 1. Install the gem.
18
13
 
19
- # After you configure this file, deploy your app with `glitter push`
20
- name "My App"
21
- version "1.0.0" # Don't forget, its ruby! This could read a version from a plist file
22
- archive "my_app.zip"
23
-
24
- s3 {
25
- bucket_name "my_app"
26
- access_key "access"
27
- secret_access_key "sekret"
28
- # Set this to true to use an EU style bucket which uses a subdomain
29
- # subdomain_bucket true
30
- }
14
+ ```sh
15
+ $ gem install glitter
16
+ ```
31
17
 
32
- 4. Publish your app to the web
18
+ 2. Publish your app to the web.
33
19
 
34
- $ glitter push -m 'Added some really cool stuff to the mix!'
35
- Pushing app my-app-1.0.0.zip
36
- Asset pushed to https://s3.amazonaws.com/my_app/my-app-1.0.0.zip
37
- Updated head https://s3.amazonaws.com/my_app/my-app-head.zip to https://s3.amazonaws.com/my_app/my-app-1.0.0.zip
38
- Updated https://s3.amazonaws.com/my_app/appcast.xml
20
+ ```sh
21
+ $ AWS_ACCESS_KEY_ID=secret_access_key \
22
+ AWS_SECRET_ACCESS_KEY=access_key_id \
23
+ AWS_BUCKET_NAME=my-app-bucket \
24
+ glitter push my-app.dmg -v 1.2.5 -c "mac-edge" \
25
+ -m 'Added some really cool stuff to the mix!'
26
+
27
+ Pushing app my-app.dmg to https://s3.amazonaws.com/mac-edge/1.2.5/my-app.dmg
28
+ Updated head https://s3.amazonaws.com/mac-edge/my-app.dmg to https://s3.amazonaws.com/mac-edge/1.2.5/my-app.dmg
29
+ ```
39
30
 
40
- 5. Distribute the link to your app
31
+ 3. Distribute the link to your app.
41
32
 
42
- https://s3.amazonaws.com/my_app/my-app-head.zip is the "current" version of your application and a history is maintained with https://s3.amazonaws.com/my_app/my-app-1.0.0.zip assets. You'll probably want to link to the "head" asset of your app and keep the older builds around for troubleshooting purposes.
33
+ https://s3.amazonaws.com/mac-edge/my-app.dmg is the "current" version of your application and a history is maintained with https://s3.amazonaws.com/mac-edge/1.2.5/my-app.dmg assets. You'll probably want to link to the "head" asset of your app and keep the older builds around for troubleshooting purposes.
43
34
 
44
35
  If you want a vanity URL to distribte your app, setup a redirect like this in nginx:
45
36
 
46
- rewrite ^/my-app.zip$ https://s3.amazonaws.com/my_app/my-app-head.zip;
37
+ rewrite ^/my-app.zip$ https://s3.amazonaws.com/mac-edge/my-app.dmg;
47
38
 
48
39
  Now send your users to mydomain.com/my-app.zip and they'll get the latest version of your app. I don't recommend using a CNAME with your application because it won't work with Amazon's HTTPS servers and you'll have to jump through some hoops to sign your app distributions with a DSA signature. Not worth it in my opinion.
49
40
 
50
- That's it!
41
+ # Contribute
42
+
43
+ Want to hack on glitter? Awesome! You'll need to setup an S3 bucket and run specs with the `AWS_URL` env var specified:
44
+
45
+ ```sh
46
+ $ AWS_ACCESS_KEY_ID=secret_access_key \
47
+ AWS_SECRET_ACCESS_KEY=access_key_id \
48
+ AWS_BUCKET_NAME=my-app-bucket \
49
+ bundle exec rspec
50
+ ```
51
+
52
+ Prefer to use Foreman? Create a `.env` file in the root of the project, then paste:
53
+
54
+ ```sh
55
+ AWS_ACCESS_KEY_ID=secret_access_key
56
+ AWS_SECRET_ACCESS_KEY=access_key_id
57
+ AWS_BUCKET_NAME=my-app-bucket
58
+ ```
59
+
60
+ and run specs via:
61
+
62
+ ```sh
63
+ foreman run bundle exec rspec
64
+ ```
65
+
66
+ Pretty sweet eh? That's it!
51
67
 
52
68
  # License
53
69
 
54
- Copyright (C) 2011 by Brad Gessler
55
-
56
- Permission is hereby granted, free of charge, to any person obtaining a copy
57
- of this software and associated documentation files (the "Software"), to deal
58
- in the Software without restriction, including without limitation the rights
59
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
60
- copies of the Software, and to permit persons to whom the Software is
61
- furnished to do so, subject to the following conditions:
62
-
63
- The above copyright notice and this permission notice shall be included in
64
- all copies or substantial portions of the Software.
65
-
66
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
67
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
68
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
69
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
70
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
71
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
72
- THE SOFTWARE.
70
+ Copyright (C) 2011 by Brad Gessler
71
+
72
+ Permission is hereby granted, free of charge, to any person obtaining a copy
73
+ of this software and associated documentation files (the "Software"), to deal
74
+ in the Software without restriction, including without limitation the rights
75
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
76
+ copies of the Software, and to permit persons to whom the Software is
77
+ furnished to do so, subject to the following conditions:
78
+
79
+ The above copyright notice and this permission notice shall be included in
80
+ all copies or substantial portions of the Software.
81
+
82
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
83
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
84
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
85
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
86
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
87
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
88
+ THE SOFTWARE.
data/lib/glitter.rb CHANGED
@@ -1,209 +1,16 @@
1
- require 's3'
2
- require 'thor'
3
- require 'erb'
1
+ require 'glitter/version'
4
2
 
5
3
  module Glitter
6
- # This mix-in Creates a DSL for configuring a class.
7
- module Configurable
8
- def self.included(base)
9
- base.send :extend, ClassMethods
10
- base.send :include, InstanceMethods
11
- end
12
-
13
- module InstanceMethods
14
- def configure(path=nil,&block)
15
- path ? instance_eval(File.read(path), path) : instance_eval(&block)
16
- self
17
- end
18
- end
19
-
20
- module ClassMethods
21
- def attr_configurable(*attrs)
22
- attrs.each do |attr|
23
- class_eval %(
24
- attr_writer :#{attr}
25
- attr_overloaded :#{attr})
26
- end
27
- end
28
-
29
- def attr_overloaded(*attrs)
30
- attrs.each do |attr|
31
- class_eval(%{
32
- def #{attr}(val=nil)
33
- val ? instance_variable_set('@#{attr}', val) : instance_variable_get('@#{attr}')
34
- end})
35
- end
36
- end
37
-
38
- def configure(*args, &block)
39
- new.configure(*args, &block)
40
- end
41
- end
4
+ ExistingReleaseError = Class.new(RuntimeError)
5
+
6
+ # Relative paths for glitter. Mostly our templating engines use this.
7
+ def self.path(*segments)
8
+ File.join File.expand_path('../glitter', __FILE__), *segments
42
9
  end
43
10
 
44
- # The App class that a release uses to deploy
45
- class App
46
- TemplatePath = File.expand_path('../glitter/templates/Glitterfile', __FILE__).to_s.freeze
47
- RssTemplate = File.expand_path('../glitter/templates/rss.xml.erb', __FILE__).to_s.freeze
48
- AppcastXml = 'appcast.xml'
49
-
50
- include Configurable
51
- attr_configurable :name, :version, :short_version_string, :release_notes_link, :notes, :archive, :s3
52
-
53
- class S3
54
- include Configurable
55
- attr_configurable :bucket_name, :access_key, :secret_access_key, :subdomain_bucket
56
-
57
- def url_for(path)
58
- if subdomain_bucket
59
- "https://#{bucket_name}.s3.amazonaws.com/#{path}"
60
- else
61
- "https://s3.amazonaws.com/#{bucket_name}/#{path}"
62
- end
63
- end
64
-
65
- def service
66
- @service ||= ::S3::Service.new(:access_key_id => access_key, :secret_access_key => secret_access_key, :use_ssl => true)
67
- end
68
-
69
- def bucket
70
- service.buckets.find(bucket_name)
71
- end
72
- end
73
-
74
- def s3(&block)
75
- @s3 ||= S3.configure(&block)
76
- end
77
-
78
- class Appcast
79
- ObjectName = 'appcast.xml'
80
-
81
- attr_reader :app
82
-
83
- def initialize(app)
84
- @app = app
85
- end
86
-
87
- def url
88
- app.s3.url_for ObjectName
89
- end
90
-
91
- def push
92
- object.content = rss
93
- object.save
94
- end
95
-
96
- def rss
97
- @rss ||= ERB.new(File.read(RssTemplate)).result(binding)
98
- end
99
-
100
- private
101
- def object
102
- @object ||= app.s3.bucket.objects.build(ObjectName)
103
- end
104
- end
105
-
106
- def appcast
107
- @appcast ||= Appcast.new(self)
108
- end
109
-
110
- def latest
111
- assets[version]
112
- end
113
-
114
- def assets
115
- @assets ||= Hash.new do |hash,key|
116
- hash[key] = Asset.new do |r|
117
- r.version = key
118
- r.short_version_string = short_version_string
119
- r.release_notes_link = release_notes_link
120
- r.notes = notes
121
- r.app = self
122
- end
123
- end
124
- end
125
- end
126
-
127
- class Asset
128
- attr_accessor :app, :version, :short_version_string, :release_notes_link, :notes, :published_at, :object
129
-
130
- def initialize
131
- @published_at = Time.now
132
- yield self if block_given?
133
- end
134
-
135
- def name
136
- "#{app.name} #{version}"
137
- end
138
-
139
- def object_name
140
- "#{name.gsub(/\s/,'-').downcase}#{File.extname(file.path)}"
141
- end
142
-
143
- def object
144
- @object ||= app.s3.bucket.objects.build(object_name)
145
- end
146
-
147
- def url
148
- app.s3.url_for object_name
149
- end
150
-
151
- def push
152
- object.content = file
153
- object.save
154
- self
155
- end
156
-
157
- # Sets this asset as the head on the S3 bucket
158
- def head
159
- @head ||= self.class.new do |a|
160
- a.app = app
161
- a.version = "head"
162
- a.short_version_string = short_version_string
163
- a.release_notes_link = release_notes_link
164
- a.notes = app.notes
165
- a.published_at = published_at
166
- a.object = object.copy(:key => a.object_name)
167
- end
168
- end
169
-
170
- def file
171
- File.new(app.archive)
172
- end
173
- end
174
-
175
- # Command line interface for cutting glitter builds
176
- class CLI < Thor
177
- desc "init PATH", "Generate a Glitterfile for the path"
178
- def init(path)
179
- glitterfile_path = File.join(path, 'Glitterfile')
180
- puts "Writing new Glitterfile to #{File.expand_path glitterfile_path}"
181
-
182
- File.open glitterfile_path, 'w+' do |file|
183
- file.write File.read(App::TemplatePath)
184
- end
185
- end
186
-
187
- desc "push", "pushes a build to S3 with release notes."
188
- method_option :release_notes, :type => :string, :aliases => "-m"
189
- def push
190
- puts "Pushing app #{app.latest.object_name}"
191
- # Push the latest release with release notes
192
- puts "Notes are the following"
193
- puts app.notes
194
- #app.latest.notes = options[:release_notes] if options[:release_notes]
195
- app.latest.push
196
- puts "Asset pushed to #{app.latest.url}"
197
- app.latest.head # Sets this release as the head.
198
- puts "Updated head #{app.latest.head.url} to #{app.latest.url}"
199
- # Update the appcast file
200
- app.appcast.push
201
- puts "Updated #{app.appcast.url}"
202
- end
203
-
204
- private
205
- def app
206
- @config ||= App.configure('./Glitterfile')
207
- end
208
- end
11
+ autoload :Configurable, Glitter.path('configurable')
12
+ autoload :Channel, Glitter.path('channel')
13
+ autoload :Release, Glitter.path('release')
14
+ autoload :Server, Glitter.path('server')
15
+ autoload :CLI, Glitter.path('cli')
209
16
  end
@@ -0,0 +1,19 @@
1
+ module Glitter
2
+ # A Channel is where multiple releases are pushed and incremented monotonically.
3
+ # Sparkle enabled native applications should be pointed at the head of a channel.
4
+ class Channel
5
+ attr_reader :name, :server
6
+
7
+ def initialize(name, server)
8
+ @name, @server = name, server
9
+ end
10
+
11
+ def release(&block)
12
+ Release.new(self, &block)
13
+ end
14
+
15
+ def versions
16
+ server.channel_versions[name]
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,26 @@
1
+ require 'thor'
2
+
3
+ module Glitter
4
+ # Command line interface for cutting glitter builds
5
+ class CLI < Thor
6
+ desc "push", "pushes a build to a channel with release notes."
7
+ method_option :notes, :type => :string, :aliases => "-n"
8
+ method_option :version, :type => :string, :aliases => "-v"
9
+ method_option :channel, :type => :string, :aliases => "-c"
10
+
11
+ def push(executable_path)
12
+ server = Server.new.channel(options.channel)
13
+ release = Release::Sparkle.new(server, options.version)
14
+ release.notes = options.notes
15
+ release.executable = File.open executable_path
16
+ release.push.head
17
+ end
18
+
19
+ # desc "yank", "remove a build from a release channel"
20
+ # method_option :version, :type => :string, :aliases => "-v"
21
+ # method_option :channel, :type => :string, :aliases => "-c"
22
+ # def yank
23
+ # end
24
+
25
+ end
26
+ end
@@ -0,0 +1,46 @@
1
+ module Glitter
2
+ # This mix-in Creates a DSL for configuring a class so that we don't have to litter
3
+ # our application with `self.attr = "val"` all over the place or from config files.
4
+ module Configurable
5
+ def self.included(base)
6
+ base.send :extend, ClassMethods
7
+ base.send :include, InstanceMethods
8
+ end
9
+
10
+ module InstanceMethods
11
+ def configure(path=nil,&block)
12
+ if path
13
+ # Read the config file up in thar.
14
+ instance_eval(File.read(path), path)
15
+ else
16
+ # Figure out how we want to call this thing.
17
+ block.arity.zero? ? instance_eval(&block) : block.call(self)
18
+ end
19
+ self
20
+ end
21
+ end
22
+
23
+ module ClassMethods
24
+ def attr_configurable(*attrs)
25
+ attrs.each do |attr|
26
+ class_eval %(
27
+ attr_writer :#{attr}
28
+ attr_overloaded :#{attr})
29
+ end
30
+ end
31
+
32
+ def attr_overloaded(*attrs)
33
+ attrs.each do |attr|
34
+ class_eval(%{
35
+ def #{attr}(val=nil)
36
+ val ? instance_variable_set('@#{attr}', val) : instance_variable_get('@#{attr}')
37
+ end})
38
+ end
39
+ end
40
+
41
+ def configure(*args, &block)
42
+ new.configure(*args, &block)
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,118 @@
1
+ require 'erb'
2
+ require 'logger'
3
+
4
+ module Glitter
5
+ module Release
6
+ # Break apart an S3 bucket key by /:channel/:version/:object-key
7
+ def self.object_segments(key)
8
+ if match = key.match(/\/?(.+)\/(.+)\/(.+)/)
9
+ match.captures
10
+ end
11
+ end
12
+
13
+ class Base
14
+ attr_reader :channel, :version, :logger
15
+
16
+ # Initialize a release and yield the block for configuration.
17
+ def initialize(channel, version, logger = Logger.new($stdout), &block)
18
+ @channel, @version, @logger = channel, version, logger
19
+ block.call self if block_given?
20
+ self
21
+ end
22
+
23
+ # Push assets up to the S3 bucket path `/:channel/:version/*`.
24
+ def push
25
+ raise ExistingReleaseError.new("Existing build at version #{version}. Increment the version and push again.") if version_exists?
26
+ logger.info "Pushing version #{version} to #{channel.name}"
27
+ assets.each do |key, object|
28
+ logger.info " PUT #{key} to #{object.url}"
29
+ object.save
30
+ end
31
+ logger.info "Version #{version} to #{channel.name} pushed!"
32
+ self
33
+ end
34
+
35
+ # Promote the release as the head releae. This copies the contents of the release to `/:channel/*`.
36
+ def head
37
+ logger.info "Promoting version #{version} to HEAD"
38
+ assets.map do |_, object|
39
+ channel, _, key = Release.object_segments(object.key)
40
+ object = object.copy key: File.join(channel, key)
41
+ logger.info " Copying #{key} to #{object.url}"
42
+ object.save
43
+ end
44
+ logger.info "Version #{version} promoted to HEAD!"
45
+ end
46
+
47
+ # Registry of assets that are S3 objects. The key is `/:channel/:version/:object-key`. The hash returns an S3 object.
48
+ def assets
49
+ @assets ||= Hash.new { |h,k| h[k] = channel.server.bucket.objects.build object_key(k) } # /win-dev/1.1/appcast.xml
50
+ end
51
+
52
+ private
53
+ # Figure out if the version already exists on the remote bucket.
54
+ def version_exists?
55
+ channel.versions.include? version
56
+ end
57
+
58
+ # Build a key that we'll use to store these objects into S3.
59
+ def object_key(*segments)
60
+ File.join(channel.name, version, segments)
61
+ end
62
+ end
63
+
64
+ # A release consists of a binary asset, notes, a monotonically increasing version number, and
65
+ # lives inside of a channel.
66
+ class Sparkle < Base
67
+ attr_accessor :notes, :executable, :filename
68
+ attr_writer :published_at
69
+
70
+ # Yeah, lets publish this shiz NOW.
71
+ def published_at
72
+ @published_at ||= Time.now
73
+ end
74
+
75
+ # Generate assets and push
76
+ def push
77
+ notes_asset
78
+ appcast_asset
79
+ executable_asset
80
+ super
81
+ end
82
+
83
+ private
84
+ # Generates an HTML file of the release notes.
85
+ def notes_asset
86
+ assets['notes.html'].tap do |a|
87
+ a.content = render_template 'notes.html.erb'
88
+ a.content_type = 'text/html'
89
+ end
90
+ end
91
+
92
+ # Generates the XML appcast file needed to publish zie document
93
+ def appcast_asset
94
+ assets['appcast.xml'].tap do |a|
95
+ a.content = render_template 'appcast.xml.erb'
96
+ a.content_type = 'application/xml'
97
+ end
98
+ end
99
+
100
+ # Package up the installer executable and add to the release.
101
+ def executable_asset
102
+ assets[executable_key].tap do |a|
103
+ a.content = executable
104
+ a.content_type = 'application/octet-stream'
105
+ end
106
+ end
107
+
108
+ def executable_key
109
+ File.basename(executable)
110
+ end
111
+
112
+ def render_template(path)
113
+ ERB.new(File.read(Glitter.path('templates', path))).result(self.send(:binding))
114
+ end
115
+ end
116
+
117
+ end
118
+ end
@@ -0,0 +1,41 @@
1
+ require 's3'
2
+ require 'uri'
3
+
4
+ # Glitter servers currently work with Amazon S3 buckets. They contain multiple channels which
5
+ # contain multiple releases of software.
6
+ module Glitter
7
+ class Server
8
+ attr_reader :access_key_id, :secret_access_key, :bucket_name
9
+
10
+ def initialize(access_key_id = ENV['AWS_ACCESS_KEY_ID'], secret_access_key = ENV['AWS_SECRET_ACCESS_KEY'], bucket_name = ENV['AWS_BUCKET_NAME'])
11
+ @access_key_id, @secret_access_key, @bucket_name = access_key_id, secret_access_key, bucket_name
12
+ end
13
+
14
+ def channel(name)
15
+ channels[name]
16
+ end
17
+
18
+ def bucket
19
+ @bucket ||= s3.buckets.find bucket_name
20
+ end
21
+
22
+ # Iterate through the objects in S3 and return a hash of channels containing their
23
+ # respective released versions.
24
+ def channel_versions
25
+ bucket.objects.inject Hash.new { |h,k| h[k] = Set.new } do |hash, object|
26
+ channel, version, _ = Release.object_segments(URI(object.url).path)
27
+ hash[channel].add(version) if channel and version
28
+ hash
29
+ end
30
+ end
31
+
32
+ private
33
+ def channels
34
+ Hash.new { |h,k| h[k] = Channel.new(k, self) }
35
+ end
36
+
37
+ def s3
38
+ @s3 ||= ::S3::Service.new(:access_key_id => access_key_id, :secret_access_key => secret_access_key, :use_ssl => true)
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,21 @@
1
+ <?xml version="1.0"?>
2
+ <rss xmlns:sparkle="http://www.andymatuschak.org/xml-namespaces/sparkle" xmlns:dc="http://purl.org/dc/elements/1.1/" version="2.0">
3
+ <channel>
4
+ <title><%= channel.name %></title>
5
+ <link><%= executable_asset.url %></link>
6
+ <description>Software updates for <%= channel.name %></description>
7
+ <language>en</language>
8
+ <item>
9
+ <title>Version <%= version %></title>
10
+ <sparkle:releaseNotesLink><%= notes_asset.url %></sparkle:releaseNotesLink>
11
+ <description>
12
+ <![CDATA[
13
+ <%= notes %>
14
+ ]]>
15
+ </description>
16
+ <pubDate><%= published_at.strftime("%a, %d %b %Y %H:%M:%S %z") %></pubDate>
17
+ <enclosure url="<%= executable_asset.url %>" sparkle:version="<%= version %>" sparkle:shortVersionString="<%= version %>" length="<%= executable.size %>" type="application/octet-stream" />
18
+ <sparkle:releaseNotesLink><%= notes %></sparkle:releaseNotesLink>
19
+ </item>
20
+ </channel>
21
+ </rss>
@@ -0,0 +1,24 @@
1
+ <html lang="en-us">
2
+ <head>
3
+ <meta charset="utf-8">
4
+ <title><%= version %></title>
5
+ <style>
6
+ body {
7
+ font-family: sans-serif;
8
+ font-size: 11.5pt;
9
+ padding: 0.5em;
10
+ }
11
+ body > * {
12
+ margin-bottom: 0.5em;
13
+ }
14
+ h1 {
15
+ font-size: 1em;
16
+ font-weight: bold;
17
+ }
18
+ </style>
19
+ </head>
20
+ <body class="release">
21
+ <h1 class="version"><%= version %></h1>
22
+ <p class="notes"><%= notes %></p>
23
+ </body>
24
+ </html>
@@ -1,3 +1,3 @@
1
1
  module Glitter
2
- VERSION = "1.0.0"
2
+ VERSION = "2.0.0.alpha1"
3
3
  end
@@ -1,112 +1,15 @@
1
1
  require 'spec_helper'
2
2
  require 'rexml/document'
3
3
 
4
- describe Glitter::App do
5
- before(:all) do
6
- @app = Glitter::App.configure do
7
- name "My App"
8
- version "1.0.0"
9
- short_version_string "1.0"
10
- release_notes_link "http://www.google.com"
11
- archive "lib/glitter.rb"
12
-
13
- s3 {
14
- bucket_name "my_app"
15
- access_key "access"
16
- secret_access_key "sekret"
17
- }
18
- end
19
-
20
- # Leave this in this order to test sorting.
21
- @app.assets["3.0"].notes = "I'm the newest and greatest of them all. 3.0 I am!"
22
- @app.assets["1.0"].notes = "Hi dude, 1.0"
23
- @app.assets["2.0"].notes = "I'm way better than 2.0"
24
- end
25
-
26
- it "should have latest" do
27
- @app.latest.version.should eql("1.0.0")
28
- end
29
-
30
- # TODO this should be speced out better using moching.
31
- it "should have head" do
32
- @app.latest.should respond_to(:head)
33
- end
34
-
35
- it "should generate rss" do
36
- REXML::Document.new(@app.appcast.rss).root.attributes["sparkle"].should eql('http://www.andymatuschak.org/xml-namespaces/sparkle')
37
- end
38
-
39
- shared_examples_for "configuration" do
40
- it "should read name" do
41
- @config.name.should eql("My App")
42
- end
43
-
44
- it "should read version" do
45
- @config.version.should eql("1.0.0")
46
- end
47
-
48
- it "should read short_version_string" do
49
- @config.short_version_string.should eql("1.0")
50
- end
51
-
52
- it "should read release_notes_link" do
53
- @config.release_notes_link.should eql("http://www.google.com")
54
- end
55
-
56
- it "should read archive" do
57
- @config.archive.should eql("my_app.zip")
58
- end
59
-
60
- it "should read notes" do
61
- @config.notes.should eql("notes go here")
62
- end
63
-
64
- context "s3" do
65
- it "should read bucket_name" do
66
- @config.s3.bucket_name.should eql("my_app")
67
- end
68
-
69
- it "should read access_key" do
70
- @config.s3.access_key.should eql("access")
71
- end
72
-
73
- it "should read secret_access_key" do
74
- @config.s3.secret_access_key.should eql("sekret")
75
- end
76
- end
77
-
78
- it "should have a valid Glitterfile template path" do
79
- File.exists?(Glitter::App::TemplatePath).should be_true
80
- end
81
- end
82
-
83
- context "block configuration" do
84
- before(:all) do
85
- @config = Glitter::App.configure do
86
- name "My App"
87
- version "1.0.0"
88
- short_version_string "1.0"
89
- release_notes_link "http://www.google.com"
90
- archive "my_app.zip"
91
- notes "notes go here"
92
-
93
- s3 {
94
- bucket_name "my_app"
95
- access_key "access"
96
- secret_access_key "sekret"
97
- }
98
- end
99
- end
100
-
101
- it_should_behave_like "configuration"
4
+ describe Glitter do
5
+ let(:server) do
6
+ Glitter::Server.new
102
7
  end
103
8
 
104
- context "file configuration" do
105
- before(:all) do
106
- @config = Glitter::App.configure File.expand_path('../../../lib/glitter/templates/Glitterfile', __FILE__)
107
- end
108
-
109
- it_should_behave_like "configuration"
9
+ it "should release to channel" do
10
+ Glitter::Release::Sparkle.new server.channel('test-channel'), "1.1.2-#{rand}" do |r|
11
+ r.executable = File.open(__FILE__)
12
+ r.notes = %[Did you know that its #{Time.now}? Wait, you can only answer yes to that question.]
13
+ end.push.head
110
14
  end
111
-
112
15
  end
metadata CHANGED
@@ -1,19 +1,19 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: glitter
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
5
- prerelease:
4
+ version: 2.0.0.alpha1
5
+ prerelease: 6
6
6
  platform: ruby
7
7
  authors:
8
8
  - Brad Gessler, Thomas Hanley
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-08-19 00:00:00.000000000 Z
12
+ date: 2013-04-01 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: s3
16
- requirement: &70135839126620 !ruby/object:Gem::Requirement
16
+ requirement: &70321671996660 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: '0'
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *70135839126620
24
+ version_requirements: *70321671996660
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: haml
27
- requirement: &70135839126100 !ruby/object:Gem::Requirement
27
+ requirement: &70321671996200 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: '0'
33
33
  type: :runtime
34
34
  prerelease: false
35
- version_requirements: *70135839126100
35
+ version_requirements: *70321671996200
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: thor
38
- requirement: &70135839152220 !ruby/object:Gem::Requirement
38
+ requirement: &70321671995640 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ! '>='
@@ -43,7 +43,7 @@ dependencies:
43
43
  version: '0'
44
44
  type: :runtime
45
45
  prerelease: false
46
- version_requirements: *70135839152220
46
+ version_requirements: *70321671995640
47
47
  description: Glitter makes it easy to publish software updates via the Sparkle framework
48
48
  by using S3 buckets.
49
49
  email:
@@ -61,8 +61,13 @@ files:
61
61
  - bin/glitter
62
62
  - glitter.gemspec
63
63
  - lib/glitter.rb
64
- - lib/glitter/templates/Glitterfile
65
- - lib/glitter/templates/rss.xml.erb
64
+ - lib/glitter/channel.rb
65
+ - lib/glitter/cli.rb
66
+ - lib/glitter/configurable.rb
67
+ - lib/glitter/release.rb
68
+ - lib/glitter/server.rb
69
+ - lib/glitter/templates/appcast.xml.erb
70
+ - lib/glitter/templates/notes.html.erb
66
71
  - lib/glitter/version.rb
67
72
  - spec/lib/glitter_spec.rb
68
73
  - spec/spec_helper.rb
@@ -81,12 +86,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
81
86
  required_rubygems_version: !ruby/object:Gem::Requirement
82
87
  none: false
83
88
  requirements:
84
- - - ! '>='
89
+ - - ! '>'
85
90
  - !ruby/object:Gem::Version
86
- version: '0'
91
+ version: 1.3.1
87
92
  requirements: []
88
93
  rubyforge_project: glitter
89
- rubygems_version: 1.8.11
94
+ rubygems_version: 1.8.3
90
95
  signing_key:
91
96
  specification_version: 3
92
97
  summary: Publish Mac software updates with the Sparkle framework and Amazon S3.
@@ -1,13 +0,0 @@
1
- # After you configure this file, deploy your app with `glitter push`
2
- name "My App"
3
- version "1.0.0"
4
- short_version_string "1.0"
5
- release_notes_link "http://www.google.com"
6
- archive "my_app.zip"
7
- notes "notes go here"
8
-
9
- s3 {
10
- bucket_name "my_app"
11
- access_key "access"
12
- secret_access_key "sekret"
13
- }
@@ -1,22 +0,0 @@
1
- <rss xmlns:sparkle="http://www.andymatuschak.org/xml-namespaces/sparkle" xmlns:dc="http://purl.org/dc/elements/1.1/" version="2.0">
2
- <channel>
3
- <title><%= app.name %></title>
4
- <link><%= app.appcast.url %></link>
5
- <description>Software updates for <%= app.name %></description>
6
- <language>en</language>
7
- <% app.assets.each do |version, asset| %>
8
- <item>
9
- <title>Version <%= asset.short_version_string %></title>
10
- <description>
11
- <![CDATA[
12
- <h2>New Features</h2>
13
- <p><%= app.notes %></p>
14
- ]]>
15
- </description>
16
- <pubDate><%= asset.published_at.strftime("%a, %d %b %Y %H:%M:%S %z") %></pubDate>
17
- <enclosure url="<%= asset.url %>" sparkle:version="<%= version %>" sparkle:shortVersionString="<%= asset.short_version_string %>" length="<%= asset.file.stat.size %>" type="application/octet-stream" />
18
- <sparkle:releaseNotesLink><%= asset.release_notes_link %></sparkle:releaseNotesLink>
19
- </item>
20
- <% end %>
21
- </channel>
22
- </rss>