jets 1.1.5 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +7 -0
- data/Gemfile.lock +10 -6
- data/README/testing.md +5 -1
- data/jets.gemspec +1 -0
- data/lib/jets.rb +5 -1
- data/lib/jets/application.rb +39 -19
- data/lib/jets/aws_services.rb +16 -10
- data/lib/jets/aws_services/stack_status.rb +7 -0
- data/lib/jets/booter.rb +6 -2
- data/lib/jets/builders/code_builder.rb +14 -0
- data/lib/jets/builders/handler_generator.rb +15 -0
- data/lib/jets/builders/shim_vars/app.rb +4 -3
- data/lib/jets/builders/shim_vars/shared.rb +8 -4
- data/lib/jets/builders/templates/shim.js +7 -3
- data/lib/jets/camelizer.rb +2 -1
- data/lib/jets/cfn/builders.rb +0 -1
- data/lib/jets/cfn/builders/api_deployment_builder.rb +27 -0
- data/lib/jets/cfn/builders/api_gateway_builder.rb +22 -2
- data/lib/jets/cfn/ship.rb +38 -6
- data/lib/jets/commands/call.rb +0 -1
- data/lib/jets/commands/call/guesser.rb +0 -3
- data/lib/jets/commands/clean/log.rb +18 -0
- data/lib/jets/commands/console.rb +1 -1
- data/lib/jets/commands/import/sequence.rb +2 -3
- data/lib/jets/commands/runner.rb +1 -1
- data/lib/jets/commands/sequence.rb +0 -1
- data/lib/jets/commands/templates/skeleton/config/application.rb.tt +11 -0
- data/lib/jets/commands/url.rb +32 -7
- data/lib/jets/controller/base.rb +21 -5
- data/lib/jets/controller/layout.rb +0 -3
- data/lib/jets/controller/middleware/local/api_gateway.rb +2 -5
- data/lib/jets/controller/middleware/local/mimic_aws_call.rb +2 -2
- data/lib/jets/controller/params.rb +42 -10
- data/lib/jets/controller/rack/adapter.rb +5 -2
- data/lib/jets/controller/rack/env.rb +17 -8
- data/lib/jets/controller/renderers/rack_renderer.rb +1 -1
- data/lib/jets/controller/rendering.rb +4 -1
- data/lib/jets/core.rb +8 -16
- data/lib/jets/internal/app/functions/jets/base_path.rb +153 -0
- data/lib/jets/klass.rb +38 -5
- data/lib/jets/lambda/dsl.rb +0 -2
- data/lib/jets/mega/request.rb +44 -13
- data/lib/jets/mega/request/source.rb +21 -0
- data/lib/jets/middleware/configurator.rb +1 -1
- data/lib/jets/middleware/default_stack.rb +2 -2
- data/lib/jets/resource.rb +1 -0
- data/lib/jets/resource/api_gateway.rb +5 -3
- data/lib/jets/resource/api_gateway/base_path.rb +5 -0
- data/lib/jets/resource/api_gateway/base_path/function.rb +42 -0
- data/lib/jets/resource/api_gateway/base_path/mapping.rb +44 -0
- data/lib/jets/resource/api_gateway/base_path/role.rb +76 -0
- data/lib/jets/resource/api_gateway/cors.rb +1 -1
- data/lib/jets/resource/api_gateway/deployment.rb +9 -5
- data/lib/jets/resource/api_gateway/domain_name.rb +56 -0
- data/lib/jets/resource/api_gateway/method.rb +3 -4
- data/lib/jets/resource/api_gateway/resource.rb +4 -3
- data/lib/jets/resource/api_gateway/rest_api.rb +42 -14
- data/lib/jets/resource/api_gateway/rest_api/change_detection.rb +42 -0
- data/lib/jets/resource/api_gateway/rest_api/logical_id.rb +59 -0
- data/lib/jets/resource/api_gateway/rest_api/routes.rb +127 -0
- data/lib/jets/resource/child_stack/api_deployment.rb +5 -1
- data/lib/jets/resource/function.rb +3 -20
- data/lib/jets/resource/function/environment.rb +23 -0
- data/lib/jets/resource/iam/application_role.rb +1 -1
- data/lib/jets/resource/route53.rb +3 -0
- data/lib/jets/resource/route53/record_set.rb +70 -0
- data/lib/jets/router.rb +2 -0
- data/lib/jets/ruby_server.rb +6 -3
- data/lib/jets/stack.rb +1 -3
- data/lib/jets/stack/main/dsl.rb +1 -1
- data/lib/jets/stack/main/extensions/lambda.rb +4 -2
- data/lib/jets/turbine.rb +0 -3
- data/lib/jets/version.rb +1 -1
- data/vendor/jets-gems/lib/jets/gems.rb +1 -0
- data/vendor/jets-gems/lib/jets/gems/agree.rb +41 -0
- data/vendor/jets-gems/lib/jets/gems/check.rb +15 -2
- metadata +30 -2
@@ -0,0 +1,42 @@
|
|
1
|
+
class Jets::Resource::ApiGateway::RestApi
|
2
|
+
class ChangeDetection
|
3
|
+
extend Memoist
|
4
|
+
include Jets::AwsServices
|
5
|
+
|
6
|
+
def changed?
|
7
|
+
return false unless parent_stack_exists?
|
8
|
+
current_binary_media_types != new_binary_media_types ||
|
9
|
+
Routes.changed?
|
10
|
+
end
|
11
|
+
|
12
|
+
def new_binary_media_types
|
13
|
+
rest_api = Jets::Resource::ApiGateway::RestApi.new
|
14
|
+
rest_api.binary_media_types
|
15
|
+
end
|
16
|
+
memoize :new_binary_media_types
|
17
|
+
|
18
|
+
# Duplicated in rest_api/change_detection.rb, base_path/role.rb, rest_api/routes.rb
|
19
|
+
def current_binary_media_types
|
20
|
+
return nil unless parent_stack_exists?
|
21
|
+
|
22
|
+
stack = cfn.describe_stacks(stack_name: parent_stack_name).stacks.first
|
23
|
+
|
24
|
+
api_gateway_stack_arn = lookup(stack[:outputs], "ApiGateway")
|
25
|
+
|
26
|
+
stack = cfn.describe_stacks(stack_name: api_gateway_stack_arn).stacks.first
|
27
|
+
rest_api_id = lookup(stack[:outputs], "RestApi")
|
28
|
+
|
29
|
+
resp = apigateway.get_rest_api(rest_api_id: rest_api_id)
|
30
|
+
resp.binary_media_types
|
31
|
+
end
|
32
|
+
memoize :current_binary_media_types
|
33
|
+
|
34
|
+
def parent_stack_exists?
|
35
|
+
stack_exists?(parent_stack_name)
|
36
|
+
end
|
37
|
+
|
38
|
+
def parent_stack_name
|
39
|
+
Jets::Naming.parent_stack_name
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
class Jets::Resource::ApiGateway::RestApi
|
2
|
+
class LogicalId
|
3
|
+
extend Memoist
|
4
|
+
include Jets::AwsServices
|
5
|
+
|
6
|
+
def get
|
7
|
+
return default unless stack_exists?(parent_stack_name) && api_gateway_exists?
|
8
|
+
|
9
|
+
change_detection = ChangeDetection.new
|
10
|
+
if change_detection.changed?
|
11
|
+
new_id
|
12
|
+
else
|
13
|
+
current
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# Takes current logical id and increments the number that is appended to it.
|
18
|
+
#
|
19
|
+
# Examples:
|
20
|
+
#
|
21
|
+
# RestApi => RestApi1
|
22
|
+
# RestApi1 => RestApi2
|
23
|
+
# RestApi2 => RestApi3
|
24
|
+
# RestApi7 => RestApi8
|
25
|
+
def new_id
|
26
|
+
regexp = /(\d+)/
|
27
|
+
md = current.match(regexp)
|
28
|
+
if md
|
29
|
+
current.gsub(regexp,'') + (md[1].to_i + 1).to_s
|
30
|
+
else
|
31
|
+
current + "1"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def current
|
36
|
+
resources = cfn.describe_stack_resources(stack_name: api_gateway_stack_arn).stack_resources
|
37
|
+
rest_api = resources.find { |r| r.resource_type == 'AWS::ApiGateway::RestApi' }
|
38
|
+
rest_api.logical_resource_id
|
39
|
+
end
|
40
|
+
memoize :current
|
41
|
+
|
42
|
+
def api_gateway_stack_arn
|
43
|
+
stack = cfn.describe_stacks(stack_name: parent_stack_name).stacks.first
|
44
|
+
lookup(stack[:outputs], "ApiGateway") # api_gateway_stack_arn
|
45
|
+
end
|
46
|
+
|
47
|
+
def api_gateway_exists?
|
48
|
+
!!api_gateway_stack_arn
|
49
|
+
end
|
50
|
+
|
51
|
+
def parent_stack_name
|
52
|
+
Jets::Naming.parent_stack_name
|
53
|
+
end
|
54
|
+
|
55
|
+
def default
|
56
|
+
"RestApi"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
class Jets::Resource::ApiGateway::RestApi
|
2
|
+
class Routes
|
3
|
+
extend Memoist
|
4
|
+
include Jets::AwsServices
|
5
|
+
|
6
|
+
def self.changed?
|
7
|
+
new.changed?
|
8
|
+
end
|
9
|
+
|
10
|
+
def changed?
|
11
|
+
deployed_routes = build
|
12
|
+
deployed_routes.each do |deployed_route|
|
13
|
+
new_route = find_comparable_route(deployed_route)
|
14
|
+
if new_route && new_route.to != deployed_route.to
|
15
|
+
# change in already deployed route has been detected, requires bluegreen deploy
|
16
|
+
return true
|
17
|
+
end
|
18
|
+
end
|
19
|
+
false # Reaching here means no routes have been changed in a way that requires a bluegreen deploy
|
20
|
+
end
|
21
|
+
|
22
|
+
# Build up deployed routes from the existing CloudFormation resources.
|
23
|
+
def build
|
24
|
+
routes = []
|
25
|
+
|
26
|
+
resources, position = [], true
|
27
|
+
while position
|
28
|
+
position = nil if position == true # start of loop
|
29
|
+
resp = apigateway.get_resources(
|
30
|
+
rest_api_id: rest_api_id,
|
31
|
+
position: position,
|
32
|
+
)
|
33
|
+
resources += resp.items
|
34
|
+
position = resp.position
|
35
|
+
end
|
36
|
+
|
37
|
+
resources.each do |resource|
|
38
|
+
resource_methods = resource.resource_methods
|
39
|
+
next if resource_methods.nil?
|
40
|
+
|
41
|
+
resource_methods.each do |http_verb, resource_method|
|
42
|
+
# puts "#{http_verb} #{resource.path} | resource.id #{resource.id}"
|
43
|
+
# puts to(resource.id, http_verb)
|
44
|
+
|
45
|
+
# Test changing config.cors and CloudFormation does an in-place update
|
46
|
+
# on the resource. So no need to do bluegreen deployments for OPTIONS.
|
47
|
+
next if http_verb == "OPTIONS"
|
48
|
+
|
49
|
+
path = recreate_path(resource.path)
|
50
|
+
method = http_verb.downcase.to_sym
|
51
|
+
to = to(resource.id, http_verb)
|
52
|
+
route = Jets::Route.new(path: path, method: method, to: to)
|
53
|
+
routes << route
|
54
|
+
end
|
55
|
+
end
|
56
|
+
routes
|
57
|
+
end
|
58
|
+
|
59
|
+
# Find a route that has the same path and method. This is a comparable route
|
60
|
+
# Then we will compare the to or controller action to see if an already
|
61
|
+
# deployed route has been changed.
|
62
|
+
def find_comparable_route(deployed_route)
|
63
|
+
new_routes.find do |new_route|
|
64
|
+
new_route.path == deployed_route.path &&
|
65
|
+
new_route.method == deployed_route.method
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def new_routes
|
70
|
+
Jets::Router.routes
|
71
|
+
end
|
72
|
+
memoize :new_routes
|
73
|
+
|
74
|
+
def recreate_path(path)
|
75
|
+
path = path.gsub(%r{^/},'')
|
76
|
+
path = path.sub(/{(.*)\+}/, '*\1')
|
77
|
+
path.sub(/{(.*)}/, ':\1')
|
78
|
+
end
|
79
|
+
|
80
|
+
def to(resource_id, http_method)
|
81
|
+
uri = method_uri(resource_id, http_method)
|
82
|
+
recreate_to(uri) unless uri.nil?
|
83
|
+
end
|
84
|
+
|
85
|
+
def method_uri(resource_id, http_method)
|
86
|
+
resp = apigateway.get_method(
|
87
|
+
rest_api_id: rest_api_id,
|
88
|
+
resource_id: resource_id,
|
89
|
+
http_method: http_method
|
90
|
+
)
|
91
|
+
resp.method_integration.uri
|
92
|
+
end
|
93
|
+
|
94
|
+
# Parses method uri and recreates a Route to argument.
|
95
|
+
# So:
|
96
|
+
# "arn:aws:apigateway:us-west-2:lambda:path/2015-03-31/functions/arn:aws:lambda:us-west-2:112233445566:function:demo-test-posts_controller-new/invocations"
|
97
|
+
# Returns:
|
98
|
+
# posts#new
|
99
|
+
def recreate_to(method_uri)
|
100
|
+
md = method_uri.match(/function:(.*)\//)
|
101
|
+
function_name = md[1] # IE: demo-dev-posts_controller-new
|
102
|
+
controller_action = function_name.sub("#{Jets.project_namespace}-", '')
|
103
|
+
md = controller_action.match(/(.*)_controller-(.*)/)
|
104
|
+
controller = md[1]
|
105
|
+
controller = controller.gsub('-','/')
|
106
|
+
action = md[2]
|
107
|
+
"#{controller}##{action}" # IE: posts#new
|
108
|
+
end
|
109
|
+
|
110
|
+
# Duplicated in rest_api/change_detection.rb, base_path/role.rb, rest_api/routes.rb
|
111
|
+
def rest_api_id
|
112
|
+
stack_name = Jets::Naming.parent_stack_name
|
113
|
+
return default unless stack_exists?(stack_name)
|
114
|
+
|
115
|
+
stack = cfn.describe_stacks(stack_name: stack_name).stacks.first
|
116
|
+
|
117
|
+
api_gateway_stack_arn = lookup(stack[:outputs], "ApiGateway")
|
118
|
+
|
119
|
+
# resources = cfn.describe_stack_resources(stack_name: api_gateway_stack_arn).stack_resources
|
120
|
+
stack = cfn.describe_stacks(stack_name: api_gateway_stack_arn).stacks.first
|
121
|
+
rest_api_id = lookup(stack[:outputs], "RestApi")
|
122
|
+
end
|
123
|
+
memoize :rest_api_id
|
124
|
+
|
125
|
+
# apigateway.get_rest_api(rest_api_id: rest_api_id) # resp
|
126
|
+
end
|
127
|
+
end
|
@@ -19,9 +19,13 @@ module Jets::Resource::ChildStack
|
|
19
19
|
end
|
20
20
|
|
21
21
|
def parameters
|
22
|
-
{
|
22
|
+
p = {
|
23
|
+
IamRole: "!GetAtt IamRole.Arn",
|
23
24
|
RestApi: "!GetAtt ApiGateway.Outputs.RestApi",
|
25
|
+
S3Bucket: "!Ref S3Bucket",
|
24
26
|
}
|
27
|
+
p[:DomainName] = "!GetAtt ApiGateway.Outputs.DomainName" if Jets.custom_domain?
|
28
|
+
p
|
25
29
|
end
|
26
30
|
|
27
31
|
def depends_on
|
@@ -1,5 +1,8 @@
|
|
1
1
|
class Jets::Resource
|
2
2
|
class Function < Jets::Resource::Base
|
3
|
+
autoload :Environment, 'jets/resource/function/environment'
|
4
|
+
include Environment
|
5
|
+
|
3
6
|
def initialize(task)
|
4
7
|
@task = task
|
5
8
|
@app_class = task.class_name.to_s
|
@@ -30,26 +33,6 @@ class Jets::Resource
|
|
30
33
|
finalize_properties!(props)
|
31
34
|
end
|
32
35
|
|
33
|
-
def env_properties
|
34
|
-
env_vars = Jets::Dotenv.load!(true)
|
35
|
-
variables = environment.merge(env_vars)
|
36
|
-
{environment: { variables: variables }}
|
37
|
-
end
|
38
|
-
|
39
|
-
def environment
|
40
|
-
env = Jets.config.environment ? Jets.config.environment.to_h : {}
|
41
|
-
env.deep_merge(jets_env)
|
42
|
-
end
|
43
|
-
|
44
|
-
# These jets env variables are always included
|
45
|
-
def jets_env
|
46
|
-
env = {}
|
47
|
-
env[:JETS_ENV] = Jets.env.to_s
|
48
|
-
env[:JETS_ENV_EXTRA] = Jets.config.env_extra if Jets.config.env_extra
|
49
|
-
env[:JETS_STAGE] = Jets::Resource::ApiGateway::Deployment.stage_name
|
50
|
-
env
|
51
|
-
end
|
52
|
-
|
53
36
|
# Global properties example:
|
54
37
|
# jets defaults are in jets/default/application.rb.
|
55
38
|
# Your application's default config/application.rb then get used. Example:
|
@@ -0,0 +1,23 @@
|
|
1
|
+
class Jets::Resource::Function
|
2
|
+
module Environment
|
3
|
+
def env_properties
|
4
|
+
env_vars = Jets::Dotenv.load!(true)
|
5
|
+
variables = environment.merge(env_vars)
|
6
|
+
{environment: { variables: variables }}
|
7
|
+
end
|
8
|
+
|
9
|
+
def environment
|
10
|
+
env = Jets.config.environment ? Jets.config.environment.to_h : {}
|
11
|
+
env.deep_merge(jets_env)
|
12
|
+
end
|
13
|
+
|
14
|
+
# These jets env variables are always included
|
15
|
+
def jets_env
|
16
|
+
env = {}
|
17
|
+
env[:JETS_ENV] = Jets.env.to_s
|
18
|
+
env[:JETS_ENV_EXTRA] = Jets.config.env_extra if Jets.config.env_extra
|
19
|
+
env[:JETS_STAGE] = Jets::Resource::ApiGateway::Deployment.stage_name
|
20
|
+
env
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# CloudFormation Docs AWS::Route53::RecordSet: https://amzn.to/2BtP9s5
|
2
|
+
#
|
3
|
+
# Example:
|
4
|
+
#
|
5
|
+
# DnsRecord:
|
6
|
+
# Type: AWS::Route53::RecordSet
|
7
|
+
# Properties:
|
8
|
+
# HostedZoneName: !Ref 'HostedZoneResource'
|
9
|
+
# Comment: DNS name for my instance.
|
10
|
+
# Name: !Join ['', [!Ref 'Ec2Instance', ., !Ref 'AWS::Region', ., !Ref 'HostedZone', .]]
|
11
|
+
# Type: A
|
12
|
+
# TTL: '900'
|
13
|
+
# ResourceRecords:
|
14
|
+
# - !GetAtt Ec2Instance.PublicIp
|
15
|
+
module Jets::Resource::Route53
|
16
|
+
class RecordSet < Jets::Resource::Base
|
17
|
+
def definition
|
18
|
+
{
|
19
|
+
dns_record: {
|
20
|
+
type: "AWS::Route53::RecordSet",
|
21
|
+
properties: {
|
22
|
+
hosted_zone_name: hosted_zone_name,
|
23
|
+
comment: "DNS record managed by Jets",
|
24
|
+
name: name,
|
25
|
+
type: "CNAME",
|
26
|
+
ttl: "60",
|
27
|
+
resource_records: [
|
28
|
+
cname,
|
29
|
+
],
|
30
|
+
}
|
31
|
+
}
|
32
|
+
}
|
33
|
+
end
|
34
|
+
|
35
|
+
def cname
|
36
|
+
if endpoint_types.include?("REGIONAL")
|
37
|
+
"!GetAtt DomainName.RegionalDomainName"
|
38
|
+
else
|
39
|
+
"!GetAtt DomainName.DistributionDomainName"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def domain_name
|
44
|
+
Jets::Resource::ApiGateway::DomainName.new
|
45
|
+
end
|
46
|
+
memoize :domain_name
|
47
|
+
|
48
|
+
def endpoint_types
|
49
|
+
domain_name.endpoint_types
|
50
|
+
end
|
51
|
+
|
52
|
+
# IE: demo-dev.mydomain.com
|
53
|
+
def name
|
54
|
+
# Weird looking but correct: domain_name is object and domain_name is also method
|
55
|
+
domain_name.domain_name
|
56
|
+
end
|
57
|
+
|
58
|
+
# IE: mydomain.com
|
59
|
+
def hosted_zone_name
|
60
|
+
name = Jets.config.domain.hosted_zone_name
|
61
|
+
name.ends_with?('.') ? name : "#{name}." # add trailing period if missing
|
62
|
+
end
|
63
|
+
|
64
|
+
def outputs
|
65
|
+
{
|
66
|
+
"DnsRecord" => "!Ref DnsRecord",
|
67
|
+
}
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
data/lib/jets/router.rb
CHANGED
@@ -26,6 +26,8 @@ module Jets
|
|
26
26
|
post "#{name}", to: "#{name}#create"
|
27
27
|
get "#{name}/:id/edit", to: "#{name}#edit" unless api_mode?
|
28
28
|
put "#{name}/:id", to: "#{name}#update"
|
29
|
+
post "#{name}/:id", to: "#{name}#update" # for binary uploads
|
30
|
+
patch "#{name}/:id", to: "#{name}#update"
|
29
31
|
delete "#{name}/:id", to: "#{name}#delete"
|
30
32
|
end
|
31
33
|
|
data/lib/jets/ruby_server.rb
CHANGED
@@ -17,7 +17,6 @@ module Jets
|
|
17
17
|
|
18
18
|
def run
|
19
19
|
Jets.boot(stringio: true) # outside of child process for COW
|
20
|
-
Jets.eager_load!
|
21
20
|
|
22
21
|
# INT - ^C
|
23
22
|
trap('INT') do
|
@@ -94,9 +93,10 @@ module Jets
|
|
94
93
|
loop do
|
95
94
|
client = server.accept # Wait for a client to connect
|
96
95
|
|
97
|
-
input_completed, event, handler = nil, nil, nil
|
96
|
+
input_completed, event, context, handler = nil, nil, nil
|
98
97
|
unless input_completed
|
99
98
|
event = client.gets&.strip # text or nil
|
99
|
+
context = client.gets&.strip # text or nil
|
100
100
|
handler = client.gets&.strip # text or nil
|
101
101
|
# The event is nil when a client connects and immediately disconnects without sending data
|
102
102
|
if event.nil?
|
@@ -107,9 +107,12 @@ module Jets
|
|
107
107
|
input_completed = true
|
108
108
|
end
|
109
109
|
|
110
|
+
# puts "ruby_server.rb event: #{event.inspect}"
|
111
|
+
# puts "ruby_server.rb context: #{context.inspect}"
|
112
|
+
|
110
113
|
result = event['_prewarm'] ?
|
111
114
|
prewarm_request(event) :
|
112
|
-
standard_request(event,
|
115
|
+
standard_request(event, context, handler)
|
113
116
|
|
114
117
|
Jets::IO.flush # flush output and write to disk for node shim
|
115
118
|
|
data/lib/jets/stack.rb
CHANGED
@@ -1,5 +1,3 @@
|
|
1
|
-
require 'active_support/concern'
|
2
|
-
|
3
1
|
module Jets
|
4
2
|
class Stack
|
5
3
|
autoload :Definition, 'jets/stack/definition' # Registration and definitions
|
@@ -75,7 +73,7 @@ module Jets
|
|
75
73
|
|
76
74
|
def eager_load_shared_resources!
|
77
75
|
ActiveSupport::Dependencies.autoload_paths += ["#{Jets.root}app/shared/resources"]
|
78
|
-
Dir.glob("#{Jets.root}app/shared/resources
|
76
|
+
Dir.glob("#{Jets.root}app/shared/resources/**/*.rb").select do |path|
|
79
77
|
next if !File.file?(path) or path =~ %r{/javascript/} or path =~ %r{/views/}
|
80
78
|
|
81
79
|
class_name = path
|