hanami-cli 2.1.0.beta1 → 2.1.0.rc1

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 (56) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +37 -0
  3. data/Gemfile +5 -2
  4. data/hanami-cli.gemspec +1 -1
  5. data/lib/hanami/cli/bundler.rb +19 -5
  6. data/lib/hanami/cli/command.rb +4 -4
  7. data/lib/hanami/cli/commands/app/assets/command.rb +69 -0
  8. data/lib/hanami/cli/commands/app/assets/compile.rb +32 -0
  9. data/lib/hanami/cli/commands/app/assets/watch.rb +32 -0
  10. data/lib/hanami/cli/commands/app/assets.rb +16 -0
  11. data/lib/hanami/cli/commands/app/command.rb +2 -2
  12. data/lib/hanami/cli/commands/app/dev.rb +45 -0
  13. data/lib/hanami/cli/commands/app/generate/action.rb +3 -2
  14. data/lib/hanami/cli/commands/app/generate/part.rb +49 -0
  15. data/lib/hanami/cli/commands/app/install.rb +16 -1
  16. data/lib/hanami/cli/commands/app.rb +9 -0
  17. data/lib/hanami/cli/commands/gem/new.rb +57 -7
  18. data/lib/hanami/cli/errors.rb +30 -0
  19. data/lib/hanami/cli/files.rb +8 -2
  20. data/lib/hanami/cli/generators/app/action/action.erb +5 -1
  21. data/lib/hanami/cli/generators/app/action/slice_action.erb +5 -1
  22. data/lib/hanami/cli/generators/app/action.rb +49 -12
  23. data/lib/hanami/cli/generators/app/part/app_base_part.erb +9 -0
  24. data/lib/hanami/cli/generators/app/part/app_part.erb +13 -0
  25. data/lib/hanami/cli/generators/app/part/slice_base_part.erb +9 -0
  26. data/lib/hanami/cli/generators/app/part/slice_part.erb +13 -0
  27. data/lib/hanami/cli/generators/app/part.rb +101 -0
  28. data/lib/hanami/cli/generators/app/part_context.rb +98 -0
  29. data/lib/hanami/cli/generators/app/slice/app_css.erb +5 -0
  30. data/lib/hanami/cli/generators/app/slice/app_js.erb +1 -0
  31. data/lib/hanami/cli/generators/app/slice/app_layout.erb +18 -0
  32. data/lib/hanami/cli/generators/app/slice.rb +9 -3
  33. data/lib/hanami/cli/generators/app/slice_context.rb +18 -0
  34. data/lib/hanami/cli/generators/context.rb +70 -3
  35. data/lib/hanami/cli/generators/gem/app/404.html +76 -5
  36. data/lib/hanami/cli/generators/gem/app/500.html +76 -5
  37. data/lib/hanami/cli/generators/gem/app/app_css.erb +5 -0
  38. data/lib/hanami/cli/generators/gem/app/app_js.erb +1 -0
  39. data/lib/hanami/cli/generators/gem/app/app_layout.erb +18 -0
  40. data/lib/hanami/cli/generators/gem/app/assets.mjs +14 -0
  41. data/lib/hanami/cli/generators/gem/app/dev +8 -0
  42. data/lib/hanami/cli/generators/gem/app/favicon.ico +0 -0
  43. data/lib/hanami/cli/generators/gem/app/gemfile.erb +14 -8
  44. data/lib/hanami/cli/generators/gem/app/gitignore.erb +4 -0
  45. data/lib/hanami/cli/generators/gem/app/package.json.erb +10 -0
  46. data/lib/hanami/cli/generators/gem/app/procfile.erb +4 -0
  47. data/lib/hanami/cli/generators/gem/app/puma.erb +37 -7
  48. data/lib/hanami/cli/generators/gem/app/routes.erb +1 -1
  49. data/lib/hanami/cli/generators/gem/app.rb +21 -4
  50. data/lib/hanami/cli/generators/version.rb +12 -0
  51. data/lib/hanami/cli/interactive_system_call.rb +64 -0
  52. data/lib/hanami/cli/system_call.rb +8 -2
  53. data/lib/hanami/cli/version.rb +1 -1
  54. metadata +28 -6
  55. data/lib/hanami/cli/generators/app/slice/layouts_app.html.erb +0 -1
  56. data/lib/hanami/cli/generators/gem/app/layouts_app.html.erb +0 -1
@@ -2,60 +2,90 @@
2
2
 
3
3
  module Hanami
4
4
  module CLI
5
+ # @since 0.1.0
6
+ # @api public
5
7
  class Error < StandardError
6
8
  end
7
9
 
10
+ # @since 2.0.0
11
+ # @api public
8
12
  class NotImplementedError < Error
9
13
  end
10
14
 
15
+ # @since 2.0.0
16
+ # @api public
11
17
  class BundleInstallError < Error
12
18
  def initialize(message)
13
19
  super("`bundle install' failed\n\n\n#{message.inspect}")
14
20
  end
15
21
  end
16
22
 
23
+ # @since 2.0.0
24
+ # @api public
17
25
  class HanamiInstallError < Error
18
26
  def initialize(message)
19
27
  super("`hanami install' failed\n\n\n#{message.inspect}")
20
28
  end
21
29
  end
22
30
 
31
+ # @since 2.1.0
32
+ # @api public
33
+ class HanamiExecError < Error
34
+ def initialize(cmd, message)
35
+ super("`bundle exec hanami #{cmd}' failed\n\n\n#{message.inspect}")
36
+ end
37
+ end
38
+
39
+ # @since 2.0.0
40
+ # @api public
23
41
  class PathAlreadyExistsError < Error
24
42
  def initialize(path)
25
43
  super("Cannot create new Hanami app in an existing path: `#{path}'")
26
44
  end
27
45
  end
28
46
 
47
+ # @since 2.0.0
48
+ # @api public
29
49
  class MissingSliceError < Error
30
50
  def initialize(slice)
31
51
  super("slice `#{slice}' is missing, please generate with `hanami generate slice #{slice}'")
32
52
  end
33
53
  end
34
54
 
55
+ # @since 2.0.0
56
+ # @api public
35
57
  class InvalidURLError < Error
36
58
  def initialize(url)
37
59
  super("invalid URL: `#{url}'")
38
60
  end
39
61
  end
40
62
 
63
+ # @since 2.0.0
64
+ # @api public
41
65
  class InvalidURLPrefixError < Error
42
66
  def initialize(url)
43
67
  super("invalid URL prefix: `#{url}'")
44
68
  end
45
69
  end
46
70
 
71
+ # @since 2.0.0
72
+ # @api public
47
73
  class InvalidActionNameError < Error
48
74
  def initialize(name)
49
75
  super("cannot parse controller and action name: `#{name}'\n\texample: `hanami generate action users.show'")
50
76
  end
51
77
  end
52
78
 
79
+ # @since 2.0.0
80
+ # @api public
53
81
  class UnknownHTTPMethodError < Error
54
82
  def initialize(name)
55
83
  super("unknown HTTP method: `#{name}'")
56
84
  end
57
85
  end
58
86
 
87
+ # @since 2.0.0
88
+ # @api public
59
89
  class UnsupportedDatabaseSchemeError < Error
60
90
  def initialize(scheme)
61
91
  super("`#{scheme}' is not a supported db scheme")
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "dry/files"
4
+
3
5
  module Hanami
4
6
  module CLI
5
7
  # @since 2.0.0
@@ -29,7 +31,7 @@ module Hanami
29
31
  def mkdir(path)
30
32
  unless exist?(path)
31
33
  super
32
- created("#{path}/")
34
+ created(_path(path))
33
35
  end
34
36
  end
35
37
 
@@ -53,7 +55,11 @@ module Hanami
53
55
  end
54
56
 
55
57
  def within_folder(path)
56
- out.puts "-> Within #{path}/"
58
+ out.puts "-> Within #{_path(path)}"
59
+ end
60
+
61
+ def _path(path)
62
+ path + ::File::SEPARATOR
57
63
  end
58
64
  end
59
65
  end
@@ -4,8 +4,12 @@ module <%= camelized_app_name %>
4
4
  module Actions
5
5
  <%= module_controller_declaration %>
6
6
  <%= module_controller_offset %>class <%= camelized_action_name %> < <%= camelized_app_name %>::Action
7
- <%= module_controller_offset %> def handle(*, response)
7
+ <%- if bundled_views? -%>
8
+ <%= module_controller_offset %> def handle(request, response)
9
+ <%- else -%>
10
+ <%= module_controller_offset %> def handle(request, response)
8
11
  <%= module_controller_offset %> response.body = self.class.name
12
+ <%- end -%>
9
13
  <%= module_controller_offset %> end
10
14
  <%= module_controller_offset %>end
11
15
  <%= module_controller_end %>
@@ -4,8 +4,12 @@ module <%= camelized_slice_name %>
4
4
  module Actions
5
5
  <%= module_controller_declaration %>
6
6
  <%= module_controller_offset %>class <%= camelized_action_name %> < <%= camelized_slice_name %>::Action
7
- <%= module_controller_offset %> def handle(*, response)
7
+ <%- if bundled_views? -%>
8
+ <%= module_controller_offset %> def handle(request, response)
9
+ <%- else -%>
10
+ <%= module_controller_offset %> def handle(request, response)
8
11
  <%= module_controller_offset %> response.body = self.class.name
12
+ <%- end -%>
9
13
  <%= module_controller_offset %> end
10
14
  <%= module_controller_offset %>end
11
15
  <%= module_controller_end %>
@@ -19,11 +19,10 @@ module Hanami
19
19
  @inflector = inflector
20
20
  end
21
21
 
22
- # rubocop:disable Layout/LineLength
23
-
24
22
  # @since 2.0.0
25
23
  # @api private
26
- def call(app, controller, action, url, http, format, skip_view, slice, context: ActionContext.new(inflector, app, slice, controller, action))
24
+ def call(app, controller, action, url, http, format, skip_view, slice, context: nil)
25
+ context ||= ActionContext.new(inflector, app, slice, controller, action)
27
26
  if slice
28
27
  generate_for_slice(controller, action, url, http, format, skip_view, slice, context)
29
28
  else
@@ -31,8 +30,6 @@ module Hanami
31
30
  end
32
31
  end
33
32
 
34
- # rubocop:enable Layout/LineLength
35
-
36
33
  private
37
34
 
38
35
  ROUTE_HTTP_METHODS = %w[get post delete put patch trace options link unlink].freeze
@@ -59,6 +56,14 @@ module Hanami
59
56
  }.freeze
60
57
  private_constant :ROUTE_RESTFUL_URL_SUFFIXES
61
58
 
59
+ # @api private
60
+ # @since 2.1.0
61
+ RESTFUL_COUNTERPART_VIEWS = {
62
+ "create" => "new",
63
+ "update" => "edit"
64
+ }.freeze
65
+ private_constant :RESTFUL_COUNTERPART_VIEWS
66
+
62
67
  PATH_SEPARATOR = "/"
63
68
  private_constant :PATH_SEPARATOR
64
69
 
@@ -80,7 +85,7 @@ module Hanami
80
85
  fs.mkdir(directory = fs.join(slice_directory, "actions", controller))
81
86
  fs.write(fs.join(directory, "#{action}.rb"), t("slice_action.erb", context))
82
87
 
83
- unless skip_view
88
+ if generate_view?(skip_view, action, directory)
84
89
  fs.mkdir(directory = fs.join(slice_directory, "views", controller))
85
90
  fs.write(fs.join(directory, "#{action}.rb"), t("slice_view.erb", context))
86
91
 
@@ -100,12 +105,15 @@ module Hanami
100
105
  fs.mkdir(directory = fs.join("app", "actions", controller))
101
106
  fs.write(fs.join(directory, "#{action}.rb"), t("action.erb", context))
102
107
 
103
- unless skip_view
104
- fs.mkdir(directory = fs.join("app", "views", controller))
105
- fs.write(fs.join(directory, "#{action}.rb"), t("view.erb", context))
108
+ view = action
109
+ view_directory = fs.join("app", "views", controller)
106
110
 
107
- fs.mkdir(directory = fs.join("app", "templates", controller))
108
- fs.write(fs.join(directory, "#{action}.#{format}.erb"),
111
+ if generate_view?(skip_view, view, view_directory)
112
+ fs.mkdir(view_directory)
113
+ fs.write(fs.join(view_directory, "#{view}.rb"), t("view.erb", context))
114
+
115
+ fs.mkdir(template_directory = fs.join("app", "templates", controller))
116
+ fs.write(fs.join(template_directory, "#{view}.#{format}.erb"),
109
117
  t(template_with_format_ext("template", format), context))
110
118
  end
111
119
  end
@@ -120,6 +128,35 @@ module Hanami
120
128
  http)} "#{route_url(controller, action, url)}", to: "#{controller.join('.')}.#{action}")
121
129
  end
122
130
 
131
+ # @api private
132
+ # @since 2.1.0
133
+ def generate_view?(skip_view, view, directory)
134
+ return false if skip_view
135
+ return generate_restful_view?(view, directory) if rest_view?(view)
136
+
137
+ true
138
+ end
139
+
140
+ # @api private
141
+ # @since 2.1.0
142
+ def generate_restful_view?(view, directory)
143
+ corresponding_action = corresponding_restful_view(view)
144
+
145
+ !fs.exist?(fs.join(directory, "#{corresponding_action}.rb"))
146
+ end
147
+
148
+ # @api private
149
+ # @since 2.1.0
150
+ def rest_view?(view)
151
+ RESTFUL_COUNTERPART_VIEWS.keys.include?(view)
152
+ end
153
+
154
+ # @api private
155
+ # @since 2.1.0
156
+ def corresponding_restful_view(view)
157
+ RESTFUL_COUNTERPART_VIEWS.fetch(view, nil)
158
+ end
159
+
123
160
  def template_with_format_ext(name, format)
124
161
  ext =
125
162
  case format.to_sym
@@ -136,7 +173,7 @@ module Hanami
136
173
  require "erb"
137
174
 
138
175
  ERB.new(
139
- File.read(__dir__ + "/action/#{path}")
176
+ File.read(__dir__ + "/action/#{path}"), trim_mode: "-",
140
177
  ).result(context.ctx)
141
178
  end
142
179
 
@@ -0,0 +1,9 @@
1
+ # auto_register: false
2
+ # frozen_string_literal: true
3
+
4
+ module <%= camelized_app_name %>
5
+ module Views
6
+ class Part < Hanami::View::Part
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,13 @@
1
+ # auto_register: false
2
+ # frozen_string_literal: true
3
+
4
+ module <%= camelized_app_name %>
5
+ module Views
6
+ module Parts
7
+ <%= module_namespace_declaration -%>
8
+ <%= module_namespace_offset %>class <%= camelized_name %> < <%= camelized_app_name %>::Views::Part
9
+ <%= module_namespace_offset %>end
10
+ <%= module_namespace_end -%>
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,9 @@
1
+ # auto_register: false
2
+ # frozen_string_literal: true
3
+
4
+ module <%= camelized_slice_name %>
5
+ module Views
6
+ class Part < <%= camelized_app_name %>::Views::Part
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,13 @@
1
+ # auto_register: false
2
+ # frozen_string_literal: true
3
+
4
+ module <%= camelized_slice_name %>
5
+ module Views
6
+ module Parts
7
+ <%= module_namespace_declaration -%>
8
+ <%= module_namespace_offset %>class <%= camelized_name %> < <%= camelized_slice_name %>::Views::Part
9
+ <%= module_namespace_offset %>end
10
+ <%= module_namespace_end -%>
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,101 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "erb"
4
+ require "dry/files"
5
+ require_relative "../../errors"
6
+
7
+ module Hanami
8
+ module CLI
9
+ module Generators
10
+ module App
11
+ # @since 2.1.0
12
+ # @api private
13
+ class Part
14
+ # @since 2.1.0
15
+ # @api private
16
+ def initialize(fs:, inflector:)
17
+ @fs = fs
18
+ @inflector = inflector
19
+ end
20
+
21
+ # @since 2.1.0
22
+ # @api private
23
+ def call(app, key, slice)
24
+ context = PartContext.new(inflector, app, slice, key)
25
+
26
+ if slice
27
+ generate_for_slice(context, slice)
28
+ else
29
+ generate_for_app(context)
30
+ end
31
+ end
32
+
33
+ private
34
+
35
+ # @since 2.1.0
36
+ # @api private
37
+ attr_reader :fs
38
+
39
+ # @since 2.1.0
40
+ # @api private
41
+ attr_reader :inflector
42
+
43
+ # @since 2.1.0
44
+ # @api private
45
+ def generate_for_slice(context, slice)
46
+ slice_directory = fs.join("slices", slice)
47
+ raise MissingSliceError.new(slice) unless fs.directory?(slice_directory)
48
+
49
+ generate_base_part_for_app(context)
50
+ generate_base_part_for_slice(context, slice)
51
+
52
+ fs.mkdir(directory = fs.join(slice_directory, "views", "parts", *context.underscored_namespace))
53
+ fs.write(fs.join(directory, "#{context.underscored_name}.rb"), t("slice_part.erb", context))
54
+ end
55
+
56
+ # @since 2.1.0
57
+ # @api private
58
+ def generate_for_app(context)
59
+ generate_base_part_for_app(context)
60
+
61
+ fs.mkdir(directory = fs.join("app", "views", "parts", *context.underscored_namespace))
62
+ fs.write(fs.join(directory, "#{context.underscored_name}.rb"), t("app_part.erb", context))
63
+ end
64
+
65
+ # @since 2.1.0
66
+ # @api private
67
+ def generate_base_part_for_app(context)
68
+ path = fs.join("app", "views", "part.rb")
69
+ return if fs.exist?(path)
70
+
71
+ fs.write(path, t("app_base_part.erb", context))
72
+ end
73
+
74
+ # @since 2.1.0
75
+ # @api private
76
+ def generate_base_part_for_slice(context, slice)
77
+ path = fs.join("slices", slice, "views", "part.rb")
78
+ return if fs.exist?(path)
79
+
80
+ fs.write(path, t("slice_base_part.erb", context))
81
+ end
82
+
83
+ # @since 2.1.0
84
+ # @api private
85
+ def template(path, context)
86
+ require "erb"
87
+
88
+ ERB.new(
89
+ File.read(__dir__ + "/part/#{path}"),
90
+ trim_mode: "-"
91
+ ).result(context.ctx)
92
+ end
93
+
94
+ # @since 2.1.0
95
+ # @api private
96
+ alias_method :t, :template
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "slice_context"
4
+ require "dry/files/path"
5
+
6
+ module Hanami
7
+ module CLI
8
+ module Generators
9
+ # @since 2.1.0
10
+ # @api private
11
+ module App
12
+ # @since 2.1.0
13
+ # @api private
14
+ class PartContext < SliceContext
15
+ # TODO: move these constants somewhere that will let us reuse them
16
+
17
+ # @since 2.1.0
18
+ # @api private
19
+ KEY_SEPARATOR = "."
20
+ private_constant :KEY_SEPARATOR
21
+
22
+ # @since 2.1.0
23
+ # @api private
24
+ INDENTATION = " "
25
+ private_constant :INDENTATION
26
+
27
+ # @since 2.1.0
28
+ # @api private
29
+ OFFSET = INDENTATION * 2
30
+ private_constant :OFFSET
31
+
32
+ # @since 2.1.0
33
+ # @api private
34
+ attr_reader :key
35
+
36
+ # @since 2.1.0
37
+ # @api private
38
+ def initialize(inflector, app, slice, key)
39
+ @key = key
40
+ super(inflector, app, slice, nil)
41
+ end
42
+
43
+ # @since 2.1.0
44
+ # @api private
45
+ def namespaces
46
+ @namespaces ||= key.split(KEY_SEPARATOR)[..-2]
47
+ end
48
+
49
+ # @since 2.1.0
50
+ # @api private
51
+ def name
52
+ @name ||= key.split(KEY_SEPARATOR)[-1]
53
+ end
54
+
55
+ # @since 2.1.0
56
+ # @api private
57
+ def camelized_name
58
+ inflector.camelize(name)
59
+ end
60
+
61
+ # @since 2.1.0
62
+ # @api private
63
+ def underscored_namespace
64
+ namespaces.map { inflector.underscore(_1) }
65
+ end
66
+
67
+ # @since 2.1.0
68
+ # @api private
69
+ def underscored_name
70
+ inflector.underscore(name)
71
+ end
72
+
73
+ # @since 2.1.0
74
+ # @api private
75
+ def module_namespace_declaration
76
+ namespaces.each_with_index.map { |token, i|
77
+ "#{OFFSET}#{INDENTATION * i}module #{inflector.camelize(token)}"
78
+ }.join($/)
79
+ end
80
+
81
+ # @since 2.1.0
82
+ # @api private
83
+ def module_namespace_end
84
+ namespaces.each_with_index.map { |_, i|
85
+ "#{OFFSET}#{INDENTATION * i}end"
86
+ }.reverse.join($/)
87
+ end
88
+
89
+ # @since 2.1.0
90
+ # @api private
91
+ def module_namespace_offset
92
+ "#{OFFSET}#{INDENTATION * namespaces.count}"
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,5 @@
1
+ body {
2
+ background-color: #fff;
3
+ color: #000;
4
+ font-family: sans-serif;
5
+ }
@@ -0,0 +1 @@
1
+ import "../css/app.css";
@@ -0,0 +1,18 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title><%= humanized_app_name %> - <%= humanized_slice_name %></title>
7
+ <%- if bundled_assets? -%>
8
+ <%%= favicon %>
9
+ <%= stylesheet_erb_tag %>
10
+ <%- end -%>
11
+ </head>
12
+ <body>
13
+ <%%= yield %>
14
+ <%- if bundled_assets? -%>
15
+ <%= javascript_erb_tag %>
16
+ <%- end -%>
17
+ </body>
18
+ </html>
@@ -30,8 +30,13 @@ module Hanami
30
30
  fs.write(fs.join(directory, "action.rb"), t("action.erb", context))
31
31
  fs.write(fs.join(directory, "view.rb"), t("view.erb", context))
32
32
  fs.write(fs.join(directory, "views", "helpers.rb"), t("helpers.erb", context))
33
- fs.write(fs.join(directory, "templates", "layouts", "app.html.erb"),
34
- File.read(File.join(__dir__, "slice", "layouts_app.html.erb")))
33
+ fs.write(fs.join(directory, "templates", "layouts", "app.html.erb"), t("app_layout.erb", context))
34
+
35
+ if context.bundled_assets?
36
+ fs.write(fs.join(directory, "assets", "js", "app.js"), t("app_js.erb", context))
37
+ fs.write(fs.join(directory, "assets", "css", "app.css"), t("app_css.erb", context))
38
+ end
39
+
35
40
  # fs.write(fs.join(directory, "/entities.rb"), t("entities.erb", context))
36
41
  # fs.write(fs.join(directory, "/repository.rb"), t("repository.erb", context))
37
42
 
@@ -53,7 +58,8 @@ module Hanami
53
58
  require "erb"
54
59
 
55
60
  ERB.new(
56
- File.read(__dir__ + "/slice/#{path}")
61
+ File.read(__dir__ + "/slice/#{path}"),
62
+ trim_mode: "-"
57
63
  ).result(context.ctx)
58
64
  end
59
65
 
@@ -29,6 +29,24 @@ module Hanami
29
29
  inflector.underscore(slice)
30
30
  end
31
31
 
32
+ # @since 2.1.0
33
+ # @api private
34
+ def humanized_slice_name
35
+ inflector.humanize(slice)
36
+ end
37
+
38
+ # @since 2.1.0
39
+ # @api private
40
+ def stylesheet_erb_tag
41
+ %(<%= css "#{slice}/app" %>)
42
+ end
43
+
44
+ # @since 2.1.0
45
+ # @api private
46
+ def javascript_erb_tag
47
+ %(<%= js "#{slice}/app" %>)
48
+ end
49
+
32
50
  private
33
51
 
34
52
  attr_reader :slice