strobe 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,57 @@
1
+ require "excon"
2
+ require "timeout"
3
+ require "thor"
4
+ require "child_labor"
5
+
6
+ class Strobe
7
+ module Spec
8
+ module CLIExecution
9
+ def strobe(command, &block)
10
+ run("strobe #{command}", &block)
11
+ end
12
+
13
+ def run(command)
14
+ merged_env = env.merge("HOME" => system_home, "STROBE_URL" => "http://localhost:3000/")
15
+ env_string = merged_env.inject([]) { |env_str,env| env_str << %{#{env.first}="#{env.last}"} }
16
+
17
+ self.task = ChildLabor.subprocess("cd #{dir} && #{env_string.join(" ")} #{command}")
18
+
19
+ if block_given?
20
+ yield task
21
+ task.terminate unless task.terminated?
22
+ task.wait unless task.terminated?
23
+ end
24
+ end
25
+
26
+ def task
27
+ Thread.current[:task]
28
+ end
29
+
30
+ def task=(child)
31
+ Thread.current[:task] = child
32
+ end
33
+
34
+ def out
35
+ task.read
36
+ end
37
+
38
+ def err
39
+ task.read_stderr
40
+ end
41
+
42
+ def out_until_block
43
+ # read 1 first so we wait until the process is done processing the last write
44
+ chars = task.stdout.read(1)
45
+
46
+ loop do
47
+ chars << task.stdout.read_nonblock(1000)
48
+ sleep 0.05
49
+ end
50
+ rescue Errno::EAGAIN, EOFError
51
+ @last_out = chars
52
+ end
53
+ end
54
+ end
55
+ end
56
+
57
+ World(Strobe::Spec::CLIExecution)
@@ -0,0 +1,118 @@
1
+ require "pathname"
2
+ require "yaml"
3
+
4
+ class Strobe
5
+ class << self
6
+
7
+ # this isn't currently needed, but why start with globals when you could
8
+ # just as easily start with a singleton instance, which may be helpful
9
+ # later
10
+ def main
11
+ @main ||= new
12
+ end
13
+
14
+ def ui
15
+ main.ui
16
+ end
17
+
18
+ def ui=(ui)
19
+ main.ui = ui
20
+ end
21
+
22
+ def root
23
+ main.root
24
+ end
25
+
26
+ def app_root
27
+ main.app_root
28
+ end
29
+
30
+ def account
31
+ main.account
32
+ end
33
+
34
+ def settings
35
+ main.settings
36
+ end
37
+
38
+ def config_dir
39
+ main.config_dir
40
+ end
41
+
42
+ def local_config_dir
43
+ main.local_config_dir
44
+ end
45
+ end
46
+
47
+ attr_writer :ui
48
+
49
+ def ui
50
+ @ui ||= UI::Basic.new
51
+ end
52
+
53
+ def root
54
+ sproutcore = "#{app_root}/tmp/build"
55
+ File.directory?(sproutcore) ? sproutcore : app_root
56
+ end
57
+
58
+ def app_root
59
+ @app_root ||= Pathname.new(Dir.pwd)
60
+ end
61
+
62
+ def config_dir
63
+ @config_dir ||= begin
64
+ dir = Pathname.new(File.join(Strobe.user_home, ".strobe"))
65
+ FileUtils.mkdir_p(dir) unless dir.exist?
66
+ dir
67
+ end
68
+ end
69
+
70
+ def local_config_dir
71
+ @local_config_dir ||= begin
72
+ dir = Pathname.new(File.join(app_root, ".strobe"))
73
+ FileUtils.mkdir_p(dir) unless dir.exist?
74
+ dir
75
+ end
76
+ end
77
+
78
+ def settings
79
+ @settings ||= Settings.new
80
+ end
81
+
82
+ autoload :Account, "strobe/account"
83
+ autoload :CLI, "strobe/cli"
84
+ autoload :Deploy, "strobe/deploy"
85
+ autoload :Error, "strobe/errors"
86
+ autoload :HTTP, "strobe/http"
87
+ autoload :Server, "strobe/server"
88
+ autoload :Settings, "strobe/settings"
89
+ autoload :UI, "strobe/ui"
90
+ autoload :User, "strobe/user"
91
+ autoload :Version, "strobe/version"
92
+
93
+ # ERRORS
94
+ autoload :AccountError, "strobe/errors"
95
+ autoload :ApplicationError, "strobe/errors"
96
+
97
+ private
98
+ # sadly, copied from Rubygems
99
+ def self.user_home
100
+ unless RUBY_VERSION > '1.9' then
101
+ ['HOME', 'USERPROFILE'].each do |homekey|
102
+ return File.expand_path(ENV[homekey]) if ENV[homekey]
103
+ end
104
+
105
+ if ENV['HOMEDRIVE'] && ENV['HOMEPATH'] then
106
+ return File.expand_path("#{ENV['HOMEDRIVE']}#{ENV['HOMEPATH']}")
107
+ end
108
+ end
109
+
110
+ File.expand_path "~"
111
+ rescue
112
+ if File::ALT_SEPARATOR then
113
+ "C:/"
114
+ else
115
+ "/"
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,67 @@
1
+ require "excon"
2
+ require "json"
3
+
4
+ class Strobe
5
+ class Account
6
+ def self.signup_or_login
7
+ Strobe.ui.info "You aren't logged in with a Strobe user on this machine"
8
+ Strobe.ui.info " 1. Create a new Strobe user"
9
+ Strobe.ui.info " 2. Log in with an existing Strobe user"
10
+ option = Strobe.ui.ask "Please select an option: "
11
+
12
+ case option
13
+ when "1"
14
+ signup
15
+ when "2"
16
+ login
17
+ end
18
+ end
19
+
20
+ def self.signup
21
+ while true
22
+ email, pass = ask_for_login
23
+ passc = Strobe.ui.password "Repeat Password: "
24
+ break if pass == passc
25
+
26
+ Strobe.ui.info "The password and confirmation do not match. Please try again."
27
+ end
28
+
29
+ response = HTTP.new.signup(email, pass)
30
+ persist_details(*response)
31
+
32
+ Strobe.ui.info "Your account was created successfully."
33
+ end
34
+
35
+ def self.login
36
+ email, pass = ask_for_login
37
+
38
+ response = HTTP.new.login(email, pass)
39
+ persist_details(*response)
40
+
41
+ Strobe.ui.info "You are now logged in as #{email}"
42
+ end
43
+
44
+ # we don't need the account for anything yet, so leave it
45
+ def initialize(json)
46
+ end
47
+
48
+ private
49
+ def self.ask_for_login
50
+ Strobe.ui.info "Please enter your Strobe username and password."
51
+
52
+ email = Strobe.ui.ask "Email: "
53
+ pass = Strobe.ui.password "Password: "
54
+ return email, pass
55
+ end
56
+
57
+ def self.persist_details(user, account = nil, errors = nil)
58
+ if account && errors
59
+ raise Strobe::AccountError, "Account creation failed. Please try again.\n#{errors}"
60
+ elsif errors
61
+ raise Strobe::AccountError, "Log in failed. Please try again.\n#{errors}"
62
+ else
63
+ Strobe.settings[:token] = user.token
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,78 @@
1
+ require "thor"
2
+
3
+ class Strobe
4
+ class CLI < Thor
5
+ check_unknown_options!
6
+
7
+ class_option "no-color", :type => :boolean, :banner => "Disable colorization in output"
8
+ class_option "path", :type => :string, :banner => "The path to your application"
9
+
10
+ def initialize(*)
11
+ super
12
+
13
+ opts = {"path" => Strobe.root}.merge(options)
14
+ self.options = Thor::CoreExt::HashWithIndifferentAccess.new(opts)
15
+
16
+ use_shell = options["no-color"] ? Thor::Shell::Basic.new : shell
17
+ Strobe.ui = UI::Shell.new(use_shell)
18
+ end
19
+
20
+ desc "signup", "sign up for a new Strobe account"
21
+ def signup
22
+ Account.signup
23
+ end
24
+
25
+ desc "deploy", "deploy your application to Strobe"
26
+ def deploy
27
+ Deploy.new(options[:path]).deploy
28
+ end
29
+
30
+ desc "preview", "preview your application locally"
31
+ def preview
32
+ Server.new(options[:path]).start
33
+ end
34
+
35
+ def help(command)
36
+ case command
37
+ when "gemfile" then command = "gemfile.5"
38
+ when nil then command = "bundle"
39
+ else command = "bundle-#{cli}"
40
+ end
41
+
42
+ root = File.expand_path("../man", __FILE__)
43
+
44
+ if manpages.include?(command)
45
+ if have_groff?
46
+ groff = "groff -Wall -mtty-char -mandoc -Tascii"
47
+ pager = ENV['MANPAGER'] || ENV['PAGER'] || 'more'
48
+
49
+ Kernel.exec "#{groff} #{root}/#{command} | #{pager}"
50
+ else
51
+ puts File.read("#{root}/#{command}.txt")
52
+ end
53
+ else
54
+ super
55
+ end
56
+ end
57
+
58
+ private
59
+ def manpages
60
+ man_root = File.expand_path("../man", __FILE__)
61
+ return [] unless File.directory?(man_root)
62
+
63
+ man_pages = []
64
+ Dir["#{man_root}/*"].each do |file|
65
+ base = File.basename(file)
66
+ next if base.include?(".")
67
+ man_pages << base
68
+ end
69
+ man_pages
70
+ end
71
+
72
+ def have_groff?
73
+ `which groff 2>#{NULL}`
74
+ $? == 0
75
+ end
76
+ end
77
+ end
78
+
@@ -0,0 +1,131 @@
1
+ require "excon"
2
+ require "json"
3
+ require "mime/types"
4
+ require "active_support/secure_random"
5
+
6
+ cache_manifest = MIME::Type.new("text/cache-manifest") { |m| m.extensions = %w(manifest) }
7
+ MIME::Types.add(cache_manifest)
8
+
9
+ class Strobe
10
+ class Deploy
11
+ def initialize(path)
12
+ @path = path
13
+ @connection, @method = Strobe.settings.app_connection_and_method
14
+ end
15
+
16
+ def deploy
17
+ Strobe::Account.signup_or_login unless Strobe.settings[:token]
18
+
19
+ application, errors = HTTP.new.deploy(multipart)
20
+
21
+ if errors
22
+ raise Strobe::ApplicationError, "Application creation failed. Please try again.\n#{errors}"
23
+ else
24
+ # Update this every time for now, in case the app has changed
25
+ # on the server. This assumes that the server returns JSON
26
+ Strobe.settings.set_local(:application, application)
27
+ Strobe.ui.confirm "Your app was successfully deployed on #{application["url"]}"
28
+ end
29
+ end
30
+
31
+ def multipart
32
+ Multipart.new do |m|
33
+ if application = Strobe.settings[:application]
34
+ name = application["name"]
35
+ url = application["url"]
36
+ else
37
+ name = Strobe.ui.ask "Please enter your application's name: "
38
+ url = Strobe.ui.ask "URL to deploy to: "
39
+ end
40
+
41
+ m.name name
42
+ m.url url
43
+
44
+ Dir["#{@path}/**/*"].each do |file|
45
+ next unless File.file?(file)
46
+
47
+ filename = file.sub(/^#{@path}/, '')
48
+ mime_type = MIME::Types.type_for(file).first
49
+ m.file filename, mime_type ? mime_type.to_s : "application/octet-stream", File.read(file)
50
+ end
51
+
52
+ apps = []
53
+
54
+ Dir["#{@path}/static/*"].each do |app|
55
+ next if File.file?(app)
56
+
57
+ dir = File.basename(app)
58
+ index = Dir["#{app}/*/*/index.html"].first
59
+
60
+ next if !index || !File.file?(index)
61
+
62
+ index = File.read(index)
63
+ apps << index
64
+
65
+ m.file "#{dir}/index.html", "text/html", index
66
+ end
67
+
68
+ m.file "index.html", "text/html", apps.first if apps.size == 1
69
+ end
70
+ end
71
+
72
+ class Multipart
73
+ def initialize
74
+ @name = nil
75
+ @url = nil
76
+ @files = []
77
+ yield self if block_given?
78
+ end
79
+
80
+ def name(name)
81
+ @name = name
82
+ end
83
+
84
+ def url(url)
85
+ @url = url
86
+ end
87
+
88
+ def file(path, content_type, body)
89
+ @files << [path, content_type, body]
90
+ end
91
+
92
+ def headers
93
+ { "Content-Type" => %[multipart/form-data; boundary="#{boundary}"] }
94
+ end
95
+
96
+ def body
97
+ b = "--#{boundary}\r\n"
98
+ b << part("metadata", "application/json", metadata)
99
+ b << "--#{boundary}\r\n"
100
+
101
+ @files.each do |path, type, body|
102
+ b << part("files[]", type, body)
103
+ b << "--#{boundary}\r\n"
104
+ end
105
+
106
+ b
107
+ end
108
+
109
+ private
110
+
111
+ def boundary
112
+ @boundary ||= ActiveSupport::SecureRandom.hex(25)
113
+ end
114
+
115
+ def metadata
116
+ hash = { "name" => @name, "url" => @url }
117
+ hash.merge!( "paths" => @files.map { |path,*| path } )
118
+ hash.to_json
119
+ end
120
+
121
+ def part(name, type, body)
122
+ b = ""
123
+ b << %[Content-Disposition: form-data; name="#{name}"; filename="array"\r\n]
124
+ b << %[Content-Type: #{type}\r\n]
125
+ b << %[\r\n]
126
+ b << body
127
+ b << %[\r\n]
128
+ end
129
+ end
130
+ end
131
+ end