skypager 0.0.4 → 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/ARCHITECTURE.md +34 -0
- data/CONTRIBUTING.md +7 -0
- data/README.md +94 -36
- data/Rakefile +1 -1
- data/lib/skypager.rb +6 -0
- data/lib/skypager/builder.rb +158 -0
- data/lib/skypager/builder/server.rb +63 -0
- data/lib/skypager/builder/webhook_handler.rb +73 -0
- data/lib/skypager/cli/commands/build.rb +48 -0
- data/lib/skypager/cli/commands/config.rb +1 -1
- data/lib/skypager/cli/commands/setup.rb +13 -0
- data/lib/skypager/cli/commands/site.rb +22 -0
- data/lib/skypager/cli/commands/sync.rb +1 -1
- data/lib/skypager/configuration.rb +32 -7
- data/lib/skypager/core_ext.rb +6 -0
- data/lib/skypager/data/google_spreadsheet.rb +3 -3
- data/lib/skypager/data/source.rb +24 -1
- data/lib/skypager/extension.rb +84 -21
- data/lib/skypager/proxy.rb +0 -1
- data/lib/skypager/router.rb +15 -0
- data/lib/skypager/site.rb +104 -21
- data/lib/skypager/sync/dropbox/delta.rb +1 -1
- data/lib/skypager/sync/folder.rb +7 -1
- data/lib/skypager/sync/github.rb +55 -0
- data/lib/skypager/version.rb +1 -1
- data/skypager.gemspec +21 -13
- data/spec/dummy/site-one/.gitignore +18 -0
- data/spec/dummy/site-one/Gemfile +14 -0
- data/spec/dummy/site-one/config.rb +13 -0
- data/spec/dummy/site-one/source/images/background.png +0 -0
- data/spec/dummy/site-one/source/images/middleman.png +0 -0
- data/spec/dummy/site-one/source/index.html.erb +10 -0
- data/spec/dummy/site-one/source/javascripts/all.js +1 -0
- data/spec/dummy/site-one/source/layouts/layout.erb +19 -0
- data/spec/dummy/site-one/source/stylesheets/all.css +55 -0
- data/spec/dummy/site-one/source/stylesheets/normalize.css +375 -0
- data/spec/lib/skypager/builder/server_spec.rb +5 -0
- data/spec/lib/skypager/configuration_spec.rb +7 -0
- data/spec/lib/skypager/site_spec.rb +40 -1
- data/spec/lib/skypager_spec.rb +9 -0
- data/spec/skypager-test-config.rb.example +22 -0
- data/spec/spec_helper.rb +38 -3
- data/spec/support/fixtures/cwd_config.json +3 -0
- data/spec/support/fixtures/home_config.json +1 -0
- metadata +112 -33
- data/lib/skypager/build_server.rb +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 02aa518232f9553e53c8ecdddbdb55d1118e3697
|
4
|
+
data.tar.gz: 269856d146d98cf16044c83874af17ddf705b054
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c8cd34b2ad8b1bf61a9637094e2bed9fe95c061efb398051783536be1b58cc76590b290791b2a52d7274e9f0f04e8891d9e7298d22624ff5ba8a109f38ef9ff3
|
7
|
+
data.tar.gz: 530be140aba47770a87067f8eef4f761d607e5d7c93c9c7a78926420a5a5c66bd4078a8fc1e9431ba67f95a2c9491d29b888ac90e7f7b6123ecd1a10c8c2a94f
|
data/.gitignore
CHANGED
data/ARCHITECTURE.md
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
### Middleman Extension
|
2
|
+
|
3
|
+
The middleman extension provides configuration to do things like
|
4
|
+
declare mappings between dropbox folders and your local folders, or to
|
5
|
+
define an external data source based on a google spreadsheet.
|
6
|
+
|
7
|
+
the extension takes care of keeping everything in sync for you between
|
8
|
+
builds, or more frequently in development
|
9
|
+
|
10
|
+
### Skypager CLI
|
11
|
+
|
12
|
+
Creates new data sources, shared folders, etc. Walks you through
|
13
|
+
setting up AWS, DNSimple, etc. Can be used to deploy.
|
14
|
+
|
15
|
+
### Skypager Site
|
16
|
+
|
17
|
+
This represents a single middleman application which uses the skypager
|
18
|
+
etension.
|
19
|
+
|
20
|
+
### Syncable Folders
|
21
|
+
|
22
|
+
syncable folders (folders which are tied to external file hosting
|
23
|
+
services, such as Dropbox or Google Drive)
|
24
|
+
|
25
|
+
### Data Sources
|
26
|
+
|
27
|
+
data sources .external services such as Google Drive, or Dropox, will
|
28
|
+
hold spreadsheets or csv files, which are basically just editable
|
29
|
+
tables. These get pulled in to the `data` folder and stored as json
|
30
|
+
files.
|
31
|
+
|
32
|
+
middleman provides that json files stored in the data folder, will be
|
33
|
+
available to the config.rb as well as the template helpers. this is
|
34
|
+
how skypager data sources get used by the middleman project.
|
data/CONTRIBUTING.md
ADDED
data/README.md
CHANGED
@@ -1,49 +1,107 @@
|
|
1
|
-
#
|
1
|
+
# Static websites are awesome.
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
3
|
+
This is a fact: Static websites with no backend server are the cheapest, fastest,
|
4
|
+
and most secure websites out there. You can compress things to the max,
|
5
|
+
cache them on a CDN distributed across the entire world, and guarantee
|
6
|
+
the fastest page loads time for your users.
|
7
7
|
|
8
|
-
|
9
|
-
|
8
|
+
If you are starting out developing a website or a web based application, constraining yourself to
|
9
|
+
keeping as much of your user facing web assets static will pay off.
|
10
10
|
|
11
|
-
|
12
|
-
by a Content Management System and a database, and supported by a version
|
13
|
-
control system, among many other completely valid reasons.
|
11
|
+
### Dynamic sites are practical
|
14
12
|
|
15
|
-
|
16
|
-
|
13
|
+
But a static HTML only website with no backend or content management just doesn't suit the needs of most people, so people
|
14
|
+
turn to hosted solutions like Wordpress or Drupal, maybe they develop their own Rails app.
|
17
15
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
example.) Skypager then takes the static build, and deploys it to an Amazon S3
|
22
|
-
Bucket, and triggers expirations of your Cloudfront CDN or other Caches.
|
16
|
+
The problem with dynamic sites powered by servers and application code and databases is you need to scale them,
|
17
|
+
secure them, host them, or hire people to do those things for you. Or you can pay for hosting for some generic
|
18
|
+
content management platform, but you'll be constrained by these platforms and limited in the ways you can customize them.
|
23
19
|
|
24
|
-
Skypager
|
25
|
-
DNS configuration.
|
20
|
+
### Skypager is the best of both worlds
|
26
21
|
|
27
|
-
|
22
|
+
If we eliminate the sites that developers make for themselves and for
|
23
|
+
other developers, most dynamic websites are going to be relying on content that
|
24
|
+
comes from people who aren't developers. Whether that is blog posts,
|
25
|
+
marketing copy, landing pages, images, videos, or a large dataset which
|
26
|
+
contains records that will be represented by their own URL.
|
28
27
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
static pages and pushes the assets to S3.
|
28
|
+
What tools are our non-developer teammates already using? They're using Dropbox, Google
|
29
|
+
Drive, Excel Spreadsheets, Word Documents, Photos, Videos, and things of
|
30
|
+
that nature.
|
33
31
|
|
34
|
-
|
35
|
-
|
36
|
-
Skypager generates the product page and deploys it for you.
|
32
|
+
Skypager provides ways to sync up with these other tools, and regenerate
|
33
|
+
your site's content whenever changes are made to these file systems.
|
37
34
|
|
38
|
-
|
39
|
-
|
40
|
-
|
35
|
+
This opens up a lot of interesting possibilities for a wide range of
|
36
|
+
different kinds of websites, and ways of collaborating with people to
|
37
|
+
build them.
|
41
38
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
terrible approach. Skypager can watch a REST endpoint in your app for
|
46
|
-
changes, or you can POST to the Skypager Build Server's Webhooks and
|
47
|
-
trigger a build on your own.
|
39
|
+
Best of all, you get the complete control and freedom provided to
|
40
|
+
you by the Ruby programming language and the many great site template authoring
|
41
|
+
tools provided by the community.
|
48
42
|
|
43
|
+
### What about user generated content?
|
49
44
|
|
45
|
+
Serverless apps which rely on storing and user generated content will need to get
|
46
|
+
creative with how they handle these requirements. When you are constraining yourself to
|
47
|
+
using a static site, this is one of the tradeoffs.
|
48
|
+
|
49
|
+
We have a number of guides and examples for how you can accomplish this. Coming soon.
|
50
|
+
|
51
|
+
### Hassle free deployment, DNS Configuration, and CDN hosting setup
|
52
|
+
|
53
|
+
Skypager automates all of the boring but necessary steps that somebody
|
54
|
+
needs to take if they wanted to configure their own Static website
|
55
|
+
hosting using Amazon S3, Cloudfront, and it even takes care of the DNS configuration
|
56
|
+
you'll need to do to make this setup work on your own domain.
|
57
|
+
|
58
|
+
### Sync with Dropbox, Google Drive, and Github
|
59
|
+
|
60
|
+
Store your source code on Github so your developer can work on it.
|
61
|
+
|
62
|
+
Store your photo library on Dropbox or Google Drive so your designer
|
63
|
+
and photographer can manage those assets using the tools they work with.
|
64
|
+
|
65
|
+
Store an excel spreadsheet containing all of your product information on Dropbox,
|
66
|
+
or in a Google spreadsheet, so people from your business team can manage
|
67
|
+
that content.
|
68
|
+
|
69
|
+
Skypager will make sure these external sources of content are always
|
70
|
+
available, and incorporate them into the latest build of the site.
|
71
|
+
|
72
|
+
### Powered by Middleman
|
73
|
+
|
74
|
+
Skypager is an extension to the [Middleman](http://middlemanapp.com) static site generator.
|
75
|
+
Middleman allows for you to develop your site using templates, and
|
76
|
+
enhanced CSS and Javascript authoring languages like Sass, Less,
|
77
|
+
or Coffeescript.
|
78
|
+
|
79
|
+
### Getting Started: Standalone Mode
|
80
|
+
|
81
|
+
Skypager in standlone mode is what you would want to use if you are
|
82
|
+
working on a single site, and you are comfortable with the command line
|
83
|
+
and ruby. Instructions for doing this can be found in our [Getting
|
84
|
+
Started Guide](https://github.com/architects/skypager)
|
85
|
+
|
86
|
+
### The Skypager Build Server
|
87
|
+
|
88
|
+
If you are managing a large number of Skypager sites, and would like to set it
|
89
|
+
up so that all of your sites stay up to date and in sync, you can run your
|
90
|
+
own instance of the Skypager Build Server.
|
91
|
+
|
92
|
+
The build server will do things like: accept webhooks from Github,
|
93
|
+
Dropbox, or Google Drive, which will happen whenever content changes.
|
94
|
+
|
95
|
+
This will trigger a re-build of your site, and the updated content gets automatically pushed
|
96
|
+
to the hosting environment and to the CDN.
|
97
|
+
|
98
|
+
### Skypager.io: Hosted option with a Web UI
|
99
|
+
|
100
|
+
The easiest way to use Skypager in production is to subscribe to the Skypager.io
|
101
|
+
hosting service, which provides you with a web based front end for
|
102
|
+
managing all of your sites and handles all of the hosting service
|
103
|
+
configuration and such for you.
|
104
|
+
|
105
|
+
Just point us to the source code, whether it is on Dropbox or Github,
|
106
|
+
and use the rest of the features which you can configure using our webfront end, or our
|
107
|
+
Command line interface (CLI).
|
data/Rakefile
CHANGED
data/lib/skypager.rb
CHANGED
@@ -59,6 +59,11 @@ module Skypager
|
|
59
59
|
Skypager::Sync::Dropbox.client
|
60
60
|
end
|
61
61
|
|
62
|
+
def self.github
|
63
|
+
require 'octokit' unless defined?(Octokit)
|
64
|
+
Skypager::Sync::Github.client
|
65
|
+
end
|
66
|
+
|
62
67
|
def self.dns
|
63
68
|
require 'dnsimple'
|
64
69
|
require 'skypager/dns'
|
@@ -105,5 +110,6 @@ require 'skypager/sync/dropbox/delta'
|
|
105
110
|
require 'skypager/sync/google'
|
106
111
|
require 'skypager/sync/folder'
|
107
112
|
require 'skypager/sync/amazon'
|
113
|
+
require 'skypager/sync/github'
|
108
114
|
require 'skypager/dns'
|
109
115
|
require 'skypager/extension'
|
@@ -0,0 +1,158 @@
|
|
1
|
+
module Skypager
|
2
|
+
class Builder
|
3
|
+
|
4
|
+
attr_reader :app,
|
5
|
+
:source,
|
6
|
+
:logger,
|
7
|
+
:options
|
8
|
+
|
9
|
+
def initialize(app, options={})
|
10
|
+
@app = app
|
11
|
+
@options = options.reverse_merge(clean: true, verbose: true)
|
12
|
+
@source_dir = app.source_path
|
13
|
+
@build_dir = app.build_path
|
14
|
+
@to_clean = Set.new
|
15
|
+
|
16
|
+
@logger = app.logger
|
17
|
+
@rack = ::Rack::Test::Session.new(app.class.to_rack_app)
|
18
|
+
end
|
19
|
+
|
20
|
+
def build(force=false)
|
21
|
+
return unless force || needs_build?
|
22
|
+
|
23
|
+
queue_current_paths if should_clean?
|
24
|
+
execute!
|
25
|
+
clean! if should_clean?
|
26
|
+
|
27
|
+
app.site.requires_build!(false)
|
28
|
+
app.deploy! if app.auto_deploy?
|
29
|
+
end
|
30
|
+
|
31
|
+
protected
|
32
|
+
|
33
|
+
def clean!
|
34
|
+
@to_clean.each do |f|
|
35
|
+
FileUtils.rm_rf(f)
|
36
|
+
end
|
37
|
+
|
38
|
+
Dir[@build_dir.join('**', '*')].select { |d| File.directory?(d) }.each do |d|
|
39
|
+
FileUtils.rm_rf(d) if Pathname(d).children.empty?
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def should_clean?
|
44
|
+
options[:clean]
|
45
|
+
end
|
46
|
+
|
47
|
+
def queue_current_paths
|
48
|
+
return unless app.build_path.exist?
|
49
|
+
|
50
|
+
paths = ::Middleman::Util.all_files_under(app.build_path).map(&:realpath).select(&:file?)
|
51
|
+
|
52
|
+
@to_clean += paths.select do |path|
|
53
|
+
path.to_s !~ /\/\./ || path.to_s =~ /\.(htaccess|htpasswd)/
|
54
|
+
end
|
55
|
+
|
56
|
+
return unless RUBY_PLATFORM =~ /darwin/
|
57
|
+
|
58
|
+
# handle UTF-8-MAC filename on MacOS
|
59
|
+
@to_clean = @to_clean.map { |path| path.to_s.encode('UTF-8', 'UTF-8-MAC') }
|
60
|
+
end
|
61
|
+
|
62
|
+
def needs_build?
|
63
|
+
@needs_build = app.data_sources.values.any? do |ds|
|
64
|
+
ds.fresh_on_server?
|
65
|
+
end
|
66
|
+
|
67
|
+
# TODO
|
68
|
+
# Add support for google drive folders, github folder changes
|
69
|
+
@needs_build = @needs_build || app.syncables.any? do |syncable|
|
70
|
+
syncable.dropbox? && syncable.has_remote_changes?
|
71
|
+
end
|
72
|
+
|
73
|
+
@needs_build
|
74
|
+
end
|
75
|
+
|
76
|
+
def execute!
|
77
|
+
sort_order = %w(.png .jpeg .jpg .gif .bmp .svg .svgz .ico .webp .woff .otf .ttf .eot .js .css)
|
78
|
+
|
79
|
+
app.sitemap.resources.select do |resource|
|
80
|
+
resource.ext == '.css'
|
81
|
+
end.each(&method(:build_resource))
|
82
|
+
|
83
|
+
app.files.find_new_files((@source_dir + app.images_dir).relative_path_from(app.root_path))
|
84
|
+
app.sitemap.ensure_resource_list_updated!
|
85
|
+
|
86
|
+
resources = app.sitemap.resources.sort_by do |r|
|
87
|
+
sort_order.index(r.ext) || 100
|
88
|
+
end
|
89
|
+
|
90
|
+
if @build_dir.expand_path.relative_path_from(@source_dir).to_s =~ /\A[.\/]+\Z/
|
91
|
+
raise ":build_dir (#{@build_dir}) cannot be a parent of :source_dir (#{@source_dir})"
|
92
|
+
end
|
93
|
+
|
94
|
+
resources.reject do |resource|
|
95
|
+
resource.ext == '.css'
|
96
|
+
end.each(&method(:build_resource))
|
97
|
+
|
98
|
+
end
|
99
|
+
|
100
|
+
def build_resource(resource)
|
101
|
+
return if options[:glob] && !File.fnmatch(options[:glob], resource.destination_path)
|
102
|
+
|
103
|
+
output_path = render_to_file(resource)
|
104
|
+
|
105
|
+
return unless should_clean? && output_path.exist?
|
106
|
+
|
107
|
+
if RUBY_PLATFORM =~ /darwin/
|
108
|
+
# handle UTF-8-MAC filename on MacOS
|
109
|
+
|
110
|
+
@to_clean.delete(output_path.realpath.to_s.encode('UTF-8', 'UTF-8-MAC'))
|
111
|
+
else
|
112
|
+
@to_clean.delete(output_path.realpath)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def render_to_file(resource)
|
117
|
+
output_file = @build_dir + resource.destination_path.gsub('%20', ' ')
|
118
|
+
|
119
|
+
if resource.binary?
|
120
|
+
if !output_file.exist?
|
121
|
+
#base.say_status :create, output_file, :green
|
122
|
+
elsif FileUtils.compare_file(resource.source_file, output_file)
|
123
|
+
#base.say_status :identical, output_file, :blue
|
124
|
+
return output_file
|
125
|
+
else
|
126
|
+
#base.say_status :update, output_file, :yellow
|
127
|
+
end
|
128
|
+
|
129
|
+
output_file.dirname.mkpath
|
130
|
+
FileUtils.cp(resource.source_file, output_file)
|
131
|
+
else
|
132
|
+
begin
|
133
|
+
response = @rack.get(URI.escape(resource.request_path))
|
134
|
+
|
135
|
+
if response.status == 200
|
136
|
+
Pathname(output_file).open("w+") {|fh| fh.write binary_encode(response.body) }
|
137
|
+
else
|
138
|
+
handle_error(output_file, response.body)
|
139
|
+
end
|
140
|
+
rescue => e
|
141
|
+
handle_error(output_file, "#{e}\n#{e.backtrace.join("\n")}", e)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
output_file
|
146
|
+
end
|
147
|
+
|
148
|
+
def handle_error(file_name, response, *args)
|
149
|
+
|
150
|
+
end
|
151
|
+
|
152
|
+
def binary_encode(string)
|
153
|
+
string.force_encoding('ascii-8bit') if string.respond_to?(:force_encoding)
|
154
|
+
string
|
155
|
+
end
|
156
|
+
|
157
|
+
end
|
158
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# Skypager::Builder::Server
|
2
|
+
#
|
3
|
+
# A Rack mountable server which can receive Webhook notifications
|
4
|
+
# from services like Dropbox, Google Drive, or our own Rest APIs
|
5
|
+
# and trigger builds for the various Skypager::Site that are referenced
|
6
|
+
# in the Skypager::Site.directory
|
7
|
+
#
|
8
|
+
# Currently, the Builder::Server just finds sites and marks them as requiring
|
9
|
+
# a build. Some external process will poll the sites directory to find the sites
|
10
|
+
# which require a build, and run that for us. (Cron, perhaps)
|
11
|
+
module Skypager
|
12
|
+
class Builder
|
13
|
+
class Server
|
14
|
+
attr_reader :options
|
15
|
+
|
16
|
+
def initialize(*args)
|
17
|
+
@options = args.extract_options!
|
18
|
+
end
|
19
|
+
|
20
|
+
def call(env)
|
21
|
+
request = Rack::Request.new(env)
|
22
|
+
respond_to(request)
|
23
|
+
rescue => e
|
24
|
+
[500, {}, [e.message]]
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def webhooks
|
30
|
+
require "skypager/builder/webhook_handler" unless defined?(Skypager::Builder::WebhookHandler)
|
31
|
+
Skypager::Builder::WebhookHandler
|
32
|
+
end
|
33
|
+
|
34
|
+
def respond_to_webhook(request)
|
35
|
+
case
|
36
|
+
when request.path.match(/dropbox/)
|
37
|
+
puts "== Found dropbox webhook"
|
38
|
+
webhooks.handle(:dropbox, request)
|
39
|
+
when request.path.match(/github/)
|
40
|
+
puts "== Found github webhook"
|
41
|
+
webhooks.handle(:github, request)
|
42
|
+
when request.path.match(/google/)
|
43
|
+
puts "== Found google webhook"
|
44
|
+
webhooks.handle(:google, request)
|
45
|
+
else
|
46
|
+
puts "== Found custom webhook"
|
47
|
+
webhooks.handle(:custom, request)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def respond_to(request)
|
52
|
+
puts "== Responding to: #{ request.path }"
|
53
|
+
case
|
54
|
+
when request.path.match(/hooks/)
|
55
|
+
puts "== Found hooks path"
|
56
|
+
respond_to_webhook(request)
|
57
|
+
else
|
58
|
+
[404, {}, [""]]
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|