jets 0.0.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/.ruby-version +1 -0
  3. data/CHANGELOG.md +7 -0
  4. data/Gemfile.lock +63 -0
  5. data/Guardfile +19 -9
  6. data/README.md +9 -16
  7. data/jets.gemspec +11 -7
  8. data/lib/jets.rb +11 -0
  9. data/lib/jets/base_controller.rb +54 -0
  10. data/lib/jets/build.rb +46 -0
  11. data/lib/jets/build/handler_generator.rb +46 -0
  12. data/lib/jets/build/lambda_deducer.rb +23 -0
  13. data/lib/jets/build/templates/handler.js +149 -0
  14. data/lib/jets/build/traveling_ruby.rb +133 -0
  15. data/lib/jets/cfn.rb +5 -0
  16. data/lib/jets/cfn/base.rb +17 -0
  17. data/lib/jets/cfn/builder.rb +53 -0
  18. data/lib/jets/cfn/namer.rb +30 -0
  19. data/lib/jets/cli.rb +10 -6
  20. data/lib/jets/cli/help.rb +8 -2
  21. data/lib/jets/process.rb +18 -0
  22. data/lib/jets/process/base_processor.rb +23 -0
  23. data/lib/jets/process/controller_processor.rb +36 -0
  24. data/lib/jets/process/help.rb +11 -0
  25. data/lib/jets/process/processor_deducer.rb +51 -0
  26. data/lib/jets/project.rb +23 -0
  27. data/lib/jets/util.rb +13 -0
  28. data/lib/jets/version.rb +1 -1
  29. data/notes/design.md +107 -0
  30. data/notes/faq.md +3 -0
  31. data/notes/lambda_ruby_info.md +34 -0
  32. data/notes/traveling-ruby-packaging-jets.md +26 -0
  33. data/notes/traveling-ruby-packaging.md +103 -0
  34. data/notes/traveling-ruby-structure.md +6 -0
  35. data/notes/traveling-ruby.md +82 -0
  36. data/spec/fixtures/classes.rb +5 -0
  37. data/spec/fixtures/project/.gitignore +3 -0
  38. data/spec/fixtures/project/.ruby-version +1 -0
  39. data/spec/fixtures/project/Gemfile +4 -0
  40. data/spec/fixtures/project/Gemfile.lock +23 -0
  41. data/spec/fixtures/project/app/controllers/application_controller.rb +2 -0
  42. data/spec/fixtures/project/app/controllers/posts_controller.rb +12 -0
  43. data/spec/fixtures/project/bin/jets +22 -0
  44. data/spec/fixtures/project/config/routes.rb +6 -0
  45. data/spec/fixtures/project/handlers/controllers/posts.js +212 -0
  46. data/spec/lib/cli_spec.rb +5 -4
  47. data/spec/lib/jets/base_controller_spec.rb +18 -0
  48. data/spec/lib/jets/build/handler_generator_spec.rb +20 -0
  49. data/spec/lib/jets/build/lambda_deducer_spec.rb +15 -0
  50. data/spec/lib/jets/build_spec.rb +34 -0
  51. data/spec/lib/jets/cfn/builder_spec.rb +18 -0
  52. data/spec/lib/jets/cfn/namer_spec.rb +16 -0
  53. data/spec/lib/jets/process/controller_processor_spec.rb +22 -0
  54. data/spec/lib/jets/process/infer_spec.rb +24 -0
  55. data/spec/lib/jets/process_spec.rb +18 -0
  56. data/spec/lib/jets/project_spec.rb +14 -0
  57. data/spec/spec_helper.rb +8 -2
  58. metadata +82 -15
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ea729cf2c680bcafc1d8bafec528b5d12297950d
4
- data.tar.gz: 907b75758d90f747f01ad72e603f2f3259c3044a
3
+ metadata.gz: 1960e461a36b47bd872fe91a5c58569569c4a1e9
4
+ data.tar.gz: cb9d30dfd4cf492849a3927afcaf6d4297defa90
5
5
  SHA512:
6
- metadata.gz: cc68a50b42bc8f9188d4393a3cba1b4f24dedf69f38ee30dbb5c523481c561c0d4f83e96fd867119c14d15b3c7f4d9d677a0a412d937850fe96c0b4ce553525b
7
- data.tar.gz: 148b5348a24575a33fd362940fc3e5b821a14342471e8b38e6463811bffc28aed52f5a8946cb10d820a8c8cd9b62b068bbff29890af3c88f6c9037e5c73738c9
6
+ metadata.gz: 65362fbb12abc9d06b55af768278ecaaeebcd5467d444c97a2de4b733cf802daa078f81b3d493e1ca383e7e8a858aa5fd54e1da11e28bc2dcd88e112df56b608
7
+ data.tar.gz: c6083ef3edcc89f4354d3f84b4cd2b8c079fcad914164f9c1862d8055f10df4fdc8d9c489e54141b1d9d51ca85d1a69c09e054782e14595f8b8d65ae7f26fed0
@@ -0,0 +1 @@
1
+ 2.2.2
@@ -0,0 +1,7 @@
1
+ # Change Log
2
+
3
+ All notable changes to this project will be documented in this file.
4
+ This project *tries* to adhere to [Semantic Versioning](http://semver.org/), even before v1.0.
5
+
6
+ ## [0.1.2]
7
+ - Fix bundled gems.
@@ -0,0 +1,63 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ jets (0.2.0)
5
+ activesupport
6
+ colorize
7
+ hashie
8
+ thor
9
+
10
+ GEM
11
+ remote: https://rubygems.org/
12
+ specs:
13
+ activesupport (5.1.4)
14
+ concurrent-ruby (~> 1.0, >= 1.0.2)
15
+ i18n (~> 0.7)
16
+ minitest (~> 5.1)
17
+ tzinfo (~> 1.1)
18
+ byebug (9.1.0)
19
+ codeclimate-test-reporter (1.0.8)
20
+ simplecov (<= 0.13)
21
+ colorize (0.8.1)
22
+ concurrent-ruby (1.0.5)
23
+ diff-lcs (1.3)
24
+ docile (1.1.5)
25
+ hashie (3.5.6)
26
+ i18n (0.9.0)
27
+ concurrent-ruby (~> 1.0)
28
+ json (2.1.0)
29
+ minitest (5.10.3)
30
+ rake (12.2.1)
31
+ rspec (3.7.0)
32
+ rspec-core (~> 3.7.0)
33
+ rspec-expectations (~> 3.7.0)
34
+ rspec-mocks (~> 3.7.0)
35
+ rspec-core (3.7.0)
36
+ rspec-support (~> 3.7.0)
37
+ rspec-expectations (3.7.0)
38
+ diff-lcs (>= 1.2.0, < 2.0)
39
+ rspec-support (~> 3.7.0)
40
+ rspec-mocks (3.7.0)
41
+ diff-lcs (>= 1.2.0, < 2.0)
42
+ rspec-support (~> 3.7.0)
43
+ rspec-support (3.7.0)
44
+ simplecov (0.13.0)
45
+ docile (~> 1.1.0)
46
+ json (>= 1.8, < 3)
47
+ simplecov-html (~> 0.10.0)
48
+ simplecov-html (0.10.2)
49
+ thor (0.20.0)
50
+ thread_safe (0.3.6)
51
+ tzinfo (1.2.3)
52
+ thread_safe (~> 0.1)
53
+
54
+ PLATFORMS
55
+ ruby
56
+
57
+ DEPENDENCIES
58
+ bundler
59
+ byebug
60
+ codeclimate-test-reporter
61
+ jets!
62
+ rake
63
+ rspec
data/Guardfile CHANGED
@@ -1,12 +1,22 @@
1
- guard "rspec" do
2
- watch(%r{^spec/.+_spec\.rb$})
3
- watch(%r{^lib/(.+)\.rb$}) { "spec/jets_spec.rb" }
4
- watch(%r{^lib/jets/(.+)\.rb$}) { "spec/jets_spec.rb" }
5
- watch("spec/spec_helper.rb") { "spec/jets_spec.rb" }
6
- watch(%r{^lib/jets/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
7
- end
8
-
9
- guard "bundler" do
1
+ guard "bundler", cmd: "bundle" do
10
2
  watch("Gemfile")
11
3
  watch(/^.+\.gemspec/)
12
4
  end
5
+
6
+ guard :rspec, cmd: "bundle exec rspec" do
7
+ require "guard/rspec/dsl"
8
+ dsl = Guard::RSpec::Dsl.new(self)
9
+
10
+ # RSpec files
11
+ rspec = dsl.rspec
12
+ watch(rspec.spec_helper) { rspec.spec_dir }
13
+ watch(rspec.spec_support) { rspec.spec_dir }
14
+ watch(rspec.spec_files)
15
+
16
+ # Ruby files
17
+ ruby = dsl.ruby
18
+ puts "ruby.lib_files #{ruby.lib_files.inspect}"
19
+ dsl.watch_spec_files_for(ruby.lib_files)
20
+
21
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
22
+ end
data/README.md CHANGED
@@ -12,28 +12,21 @@ TODO: Write a gem description
12
12
 
13
13
  Add this line to your application's Gemfile:
14
14
 
15
- gem "jets"
15
+ ```sh
16
+ gem "jets"
17
+ ```
16
18
 
17
19
  And then execute:
18
20
 
19
- $ bundle
21
+ ```sh
22
+ $ bundle
23
+ ```
20
24
 
21
25
  Or install it yourself as:
22
26
 
23
- $ gem install jets
24
-
25
- ## Usage
26
-
27
- <pre>
28
- git clone https://github.com/tongueroo/jets.git
29
- mv jets <project_name>
30
- cd <project_name>
31
- rake rename
32
- rm -rf .git
33
- git init
34
- git add .
35
- git commit -m "init commit"
36
- </pre>
27
+ ```
28
+ $ gem install jets
29
+ ```
37
30
 
38
31
  ## Contributing
39
32
 
@@ -8,9 +8,9 @@ Gem::Specification.new do |spec|
8
8
  spec.version = Jets::VERSION
9
9
  spec.authors = ["Tung Nguyen"]
10
10
  spec.email = ["tongueroo@gmail.com"]
11
- spec.description = %q{}
12
- spec.summary = %q{}
13
- spec.homepage = ""
11
+ spec.description = %q{Test}
12
+ spec.summary = %q{Test}
13
+ spec.homepage = "https://github.com/tongueroo/jets"
14
14
  spec.license = "MIT"
15
15
 
16
16
  spec.files = `git ls-files`.split($/)
@@ -21,10 +21,14 @@ Gem::Specification.new do |spec|
21
21
  spec.add_dependency "thor"
22
22
  spec.add_dependency "hashie"
23
23
  spec.add_dependency "colorize"
24
+ spec.add_dependency "activesupport"
24
25
 
25
- spec.add_development_dependency "bundler", "~> 1.3"
26
+ spec.add_development_dependency "bundler"
27
+ spec.add_development_dependency "byebug"
26
28
  spec.add_development_dependency "rake"
27
- spec.add_development_dependency "guard"
28
- spec.add_development_dependency "guard-bundler"
29
- spec.add_development_dependency "guard-rspec"
29
+ spec.add_development_dependency "rspec"
30
+ # ruby_dep-1.5.0 requires ruby version >= 2.2.5, which is incompatible with the current version, ruby 2.2.2p95
31
+ # spec.add_development_dependency "guard"
32
+ # spec.add_development_dependency "guard-bundler"
33
+ # spec.add_development_dependency "guard-rspec"
30
34
  end
@@ -1,7 +1,18 @@
1
1
  $:.unshift(File.expand_path("../", __FILE__))
2
2
  require "jets/version"
3
+ require "active_support/core_ext/string"
4
+ require "colorize"
5
+ require 'pp'
3
6
 
4
7
  module Jets
8
+ autoload :Util, "jets/util"
5
9
  autoload :Command, "jets/command"
6
10
  autoload :CLI, "jets/cli"
11
+ autoload :Build, 'jets/build'
12
+ autoload :Process, 'jets/process'
13
+ autoload :BaseController, 'jets/base_controller'
14
+ autoload :Project, 'jets/project'
15
+ autoload :Cfn, 'jets/cfn'
16
+
17
+ extend Util
7
18
  end
@@ -0,0 +1,54 @@
1
+ require 'json'
2
+
3
+ module Jets
4
+ class BaseController
5
+ attr_reader :event, :context
6
+ def initialize(event, context)
7
+ @event = event
8
+ @context = context
9
+ end
10
+
11
+ # The public methods defined in the user's custom class will become
12
+ # lambda functions.
13
+ # Returns Example:
14
+ # ["FakeController#handler1", "FakeController#handler2"]
15
+ def lambda_functions
16
+ # public_instance_methods(false) - to not include inherited methods
17
+ self.class.public_instance_methods(false) - Object.public_instance_methods
18
+ end
19
+
20
+ def self.lambda_functions
21
+ new(nil, nil).lambda_functions
22
+ end
23
+
24
+ private
25
+ def render(options={})
26
+ # render json: {"mytestdata": "value1"}, status: 200, headers: {...}
27
+ if options.has_key?(:json)
28
+ # Transform the structure to Lambda Proxy structure
29
+ # {statusCode: ..., body: ..., headers: }
30
+ status = options.delete(:status)
31
+ body = options.delete(:json)
32
+ result = options.merge(
33
+ statusCode: status,
34
+ body: body
35
+ )
36
+ # render text: "text"
37
+ elsif options.has_key?(:text)
38
+ result = options.delete(:text)
39
+ else
40
+ raise "Unsupported render option. Only :text and :json supported. options #{options.inspect}"
41
+ end
42
+
43
+ result
44
+ end
45
+
46
+ # API Gateway LAMBDA_PROXY wraps the event in its own structure.
47
+ # We unwrap the "body" before sending it back
48
+ # For regular Lambda function calls, no need to unwrap but need to
49
+ # transform it to a string with JSON.dump.
50
+ def normalize_event_body(event)
51
+ body = event.has_key?("body") ? event["body"] : JSON.dump(event)
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,46 @@
1
+ class Jets::Build
2
+ autoload :LambdaDeducer, "jets/build/lambda_deducer"
3
+ autoload :HandlerGenerator, "jets/build/handler_generator"
4
+ autoload :TravelingRuby, "jets/build/traveling_ruby"
5
+
6
+ def initialize(options)
7
+ @options = options
8
+ end
9
+
10
+ def run
11
+ puts "Building project for Lambda..."
12
+ build
13
+ end
14
+
15
+ def build
16
+ puts "Building node shim handlers..."
17
+ controller_paths.each do |path|
18
+ deducer = LambdaDeducer.new(path)
19
+ generator = HandlerGenerator.new(deducer.class_name, *deducer.functions)
20
+ generator.run
21
+ end
22
+
23
+ puts "Building TravelingRuby..."
24
+ TravelingRuby.new.build unless @options[:noop]
25
+
26
+ puts "Building Lambda functions as CloudFormation templates"
27
+
28
+ end
29
+
30
+ def controller_paths
31
+ paths = []
32
+ expression = "#{Jets.root}app/controllers/**/*.rb"
33
+ Dir.glob(expression).each do |path|
34
+ next unless File.file?(path)
35
+ next if path.include?("application_controller.rb")
36
+
37
+ paths << relative_path(path)
38
+ end
39
+ paths
40
+ end
41
+
42
+ # Rids of the Jets.root at beginning
43
+ def relative_path(path)
44
+ path.sub(Jets.root, '')
45
+ end
46
+ end
@@ -0,0 +1,46 @@
1
+ require "fileutils"
2
+ require "erb"
3
+
4
+ class Jets::Build
5
+ class HandlerGenerator
6
+ # Jets::Build::HandlerGenerator.new(
7
+ # "PostsController",
8
+ # :create, :update
9
+ # )
10
+ def initialize(class_name, *methods)
11
+ @class_name = class_name
12
+ @methods = methods
13
+ end
14
+
15
+ def run
16
+ js_path = "#{Jets.root}handlers/#{process_type.pluralize}/#{module_name}.js"
17
+ FileUtils.mkdir_p(File.dirname(js_path))
18
+
19
+ template_path = File.expand_path('../templates/handler.js', __FILE__)
20
+ template = IO.read(template_path)
21
+
22
+ # Set used ERB variables:
23
+ @process_type = process_type
24
+ @functions = @methods.map do |m|
25
+ {
26
+ name: m,
27
+ handler: handler(m)
28
+ }
29
+ end
30
+ result = ERB.new(template, nil, "-").result(binding)
31
+ IO.write(js_path, result)
32
+ end
33
+
34
+ def process_type
35
+ @class_name.underscore.split('_').last
36
+ end
37
+
38
+ def handler(method)
39
+ "handlers/#{process_type.pluralize}/#{module_name}.#{method}"
40
+ end
41
+
42
+ def module_name
43
+ @class_name.sub(/Controller$/,'').underscore
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,23 @@
1
+ # TODO: move the handler_generator.rb deducing methods into here
2
+ class Jets::Build
3
+ class LambdaDeducer
4
+ attr_reader :handlers
5
+ def initialize(path)
6
+ @path = path
7
+ end
8
+
9
+ def class_name
10
+ @path.sub(%r{app/(\w+)/},'').sub('.rb','').classify
11
+ end
12
+
13
+ def functions
14
+ # Example: require "./app/controllers/posts_controller.rb"
15
+ require_path = @path.starts_with?('/') ? @path : "#{Jets.root}#{@path}"
16
+ require require_path
17
+
18
+ class_name
19
+ klass = class_name.constantize
20
+ klass.lambda_functions
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,149 @@
1
+ 'use strict';
2
+
3
+ const spawn = require('child_process').spawn;
4
+
5
+ // Once hooked up to API Gateway can use the curl command to test:
6
+ // curl -s -X POST -d @event.json https://endpoint | jq .
7
+
8
+ // Filters out lines so only the error lines remain.
9
+ // Uses the "RubyError: " marker to find the starting error lines.
10
+ //
11
+ // Input: String
12
+ // random line
13
+ // RubyError: RuntimeError: error in submethod
14
+ // line1
15
+ // line2
16
+ // line3
17
+ //
18
+ // Output: String
19
+ // RubyError: RuntimeError: error in submethod
20
+ // line1
21
+ // line2
22
+ // line3
23
+ function filterErrorLines(text) {
24
+ var lines = text.split("\n")
25
+ var markerIndex = lines.findIndex(line => line.startsWith("RubyError: ") )
26
+ lines = lines.filter((line, index) => index >= markerIndex )
27
+ return lines.join("\n")
28
+ }
29
+
30
+ // Produces an Error object that displays in the AWS Lambda test console nicely.
31
+ // The backtrace are the ruby lines, not the nodejs shim error lines.
32
+ // The json payload in the Lambda console looks something like this:
33
+ //
34
+ // {
35
+ // "errorMessage": "RubyError: RuntimeError: error in submethod",
36
+ // "errorType": "RubyError",
37
+ // "stackTrace": [
38
+ // [
39
+ // "line1",
40
+ // "line2",
41
+ // "line3"
42
+ // ]
43
+ // ]
44
+ // }
45
+ //
46
+ // Input: String
47
+ // RubyError: RuntimeError: error in submethod
48
+ // line1
49
+ // line2
50
+ // line3
51
+ //
52
+ // Output: Error object
53
+ // { RubyError: RuntimeError: error in submethod
54
+ // line1
55
+ // line2
56
+ // line3 name: 'RubyError' }
57
+ function customError(text) {
58
+ text = filterErrorLines(text) // filter for error lines only
59
+ var lines = text.split("\n")
60
+ var message = lines[0]
61
+ var error = new Error(message)
62
+ error.name = message.split(':')[0]
63
+ error.stack = lines.slice(0, lines.length-1) // drop final empty line
64
+ .map(e => e.replace(/^\s+/g,'')) // trim leading whitespaces
65
+ .join("\n")
66
+ return error
67
+ }
68
+
69
+ <% @functions.each do |function| %>
70
+ module.exports.<%= function[:name] %> = (event, context, callback) => {
71
+ // Command: bin/jets process controller [event] [context] [handler]
72
+ var args = [
73
+ "process",
74
+ "<%= @process_type %>", // controller (singular)
75
+ JSON.stringify(event), // event
76
+ JSON.stringify(context), // context
77
+ "<%= function[:handler] %>" // IE: handlers/controllers/posts.update
78
+ ]
79
+ var ruby = spawn("bin/jets", args);
80
+
81
+ // string concatation in javascript is faster than array concatation
82
+ // http://bit.ly/2gBMDs6
83
+ var stdout_buffer = ""; // stdout buffer
84
+ // In the processor_command we do NOT call puts directly and write to stdout
85
+ // because it will mess up the eventual response that we want API Gateway to
86
+ // process.
87
+ // The Lambda prints out function to whatever the return value the ruby method
88
+ ruby.stdout.on('data', function(data) {
89
+ // Not using console.log because it decorates output with a newline.
90
+ //
91
+ // Uncomment process.stdout.write to see stdout streamed for debugging.
92
+ // process.stdout.write(data)
93
+ stdout_buffer += data;
94
+ });
95
+
96
+ // react to potential errors
97
+ var stderr_buffer = "";
98
+ ruby.stderr.on('data', function(data) {
99
+ // not using console.error because it decorates output with a newline
100
+ stderr_buffer += data
101
+ process.stderr.write(data)
102
+ });
103
+
104
+ //finalize when ruby process is done.
105
+ ruby.on('close', function(exit_code) {
106
+ // http://docs.aws.amazon.com/lambda/latest/dg/nodejs-prog-model-handler.html#nodejs-prog-model-handler-callback
107
+
108
+ // succcess
109
+ if (exit_code == 0) {
110
+ var result
111
+ try {
112
+ result = JSON.parse(stdout_buffer)
113
+ } catch(e) {
114
+ // if json cannot be parse assume simple text output intended
115
+ process.stderr.write("WARN: error parsing json, assuming plain text is desired.")
116
+ result = stdout_buffer
117
+ }
118
+ callback(null, result);
119
+
120
+ // callback(null, stdout_buffer);
121
+ } else {
122
+
123
+ // TODO: if this works, allow a way to not decorate the error in case
124
+ // it actually errors in javascript land
125
+ // Customize error object with ruby error info
126
+ var error = customError(stderr_buffer)
127
+ callback(error);
128
+ // console.log("error!")
129
+ }
130
+ });
131
+ }
132
+ <% end %>
133
+
134
+ // for local testing
135
+ if (process.platform == "darwin") {
136
+ // fake event and context
137
+ var event = {"hello": "world"}
138
+ // var event = {"body": {"hello": "world"}} // API Gateway wrapper structure
139
+ var context = {"fake": "context"}
140
+ module.exports.<%= @functions.first[:name] %>(event, context, (error, message) => {
141
+ console.error("\nLOCAL TESTING OUTPUT")
142
+ if (error) {
143
+ console.error("error message: %o", error)
144
+ } else {
145
+ console.error("success message %o", message)
146
+ // console.log(JSON.stringify(message)) // stringify
147
+ }
148
+ })
149
+ }