jets 0.0.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
+ }