hanami-cli 2.1.0.beta2 → 2.1.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 85a528645e9205c15ccdf4b81cc32a3fb0789a8a7f07c56554c1b8b0e66b9e91
4
- data.tar.gz: 5937fd333d11a7f5131d0298dc609c8a92e233249bc0b2e9e8f28705977f20b4
3
+ metadata.gz: '054178514bf7e420e00cd8da8a148cd4d479609668c180507fadbcd7a086f15d'
4
+ data.tar.gz: 3ef8c388ad8c8734179ce042d3eb966dea6df881b72e7615f0b104a1890106a5
5
5
  SHA512:
6
- metadata.gz: df19a905d03fec4665ce80dd89730dee6337312e32e0e923e9424a387d66b8f9605e8392a5a73bd4627251b96c57b4a95f4df32062d8a53d42041d69841c10d6
7
- data.tar.gz: e4931266c0517de1bb33bc0f862c73da58366d6d5707c0eacf48fd38b08b8d7ae767e8fd7c81f1f048b96ffda5977674e556e281c51ab1f2a4eb30c6ceaa7ade
6
+ metadata.gz: c8e1abc0ded22d56ee2acd5dec6d9f7079c0331d187b1d4ff4b26858faf50ff870016e1adfa6b785673d39b6d0dcf097dca7c1b0526da442e1939ce0952f3799
7
+ data.tar.gz: 8ecc3ceefc10856cbd2f60ec24bd1ecde442764610dbb7d179f064080ac434fa4829374efb05513c67420ab8b76b0280ad594fd566824fdfba0e8d14afc209fb
data/CHANGELOG.md CHANGED
@@ -2,6 +2,25 @@
2
2
 
3
3
  Hanami Command Line Interface
4
4
 
5
+ ## v2.1.0.rc1 - 2023-11-01
6
+
7
+ ### Added
8
+
9
+ - [Tim Riley] `hanami new` to generate `bin/dev` as configuration for `hanami dev`
10
+ - [Luca Guidi] Introducing `hanami generate part` to generate view parts
11
+
12
+ ### Fixed
13
+
14
+ - [Luca Guidi] `hanami new` generates a fully documented Puma configuration in `config/puma.rb`
15
+
16
+ ### Changed
17
+
18
+ - [Tim Riley] `hanami new` generates a `config/assets.mjs` as Assets configuration
19
+ - [Tim Riley] `hanami new` generates a leaner `package.json`
20
+ - [Tim Riley] `hanami new` doesn't generate a default root route anymore
21
+ - [Aaron Moodie & Tim Riley] `hanami new` to generate a redesigned 404 and 500 error pages
22
+ - [Luca Guidi] When generating a RESTful action, skip `create`, if `new` is present, and `update`, if `edit` is present
23
+
5
24
  ## v2.1.0.beta2 - 2023-10-04
6
25
 
7
26
  ### Added
data/Gemfile CHANGED
@@ -15,6 +15,8 @@ gem "hanami-controller", github: "hanami/controller", branch: "main"
15
15
  gem "hanami-router", github: "hanami/router", branch: "main"
16
16
  gem "hanami-utils", github: "hanami/utils", branch: "main"
17
17
 
18
+ gem "dry-files", github: "dry-rb/dry-files", branch: "main"
19
+
18
20
  gem "rack"
19
21
 
20
22
  group :test do
@@ -23,7 +23,7 @@ module Hanami
23
23
  def call(**)
24
24
  cmd, *args = cmd_with_args
25
25
 
26
- system_call.call(cmd, *args, env: env)
26
+ system_call.call(cmd, *args)
27
27
  end
28
28
 
29
29
  private
@@ -39,20 +39,7 @@ module Hanami
39
39
  # @since 2.1.0
40
40
  # @api private
41
41
  def cmd_with_args
42
- [
43
- config.package_manager_executable,
44
- config.package_manager_command,
45
- config.executable
46
- ]
47
- end
48
-
49
- # @since 2.1.0
50
- # @api private
51
- def env
52
- ENV.to_h.merge(
53
- "ESBUILD_ENTRY_POINTS" => entry_points,
54
- "ESBUILD_OUTDIR" => destination
55
- )
42
+ [config.package_manager_run_command, "assets"]
56
43
  end
57
44
 
58
45
  # @since 2.1.0
@@ -22,11 +22,7 @@ module Hanami
22
22
  # @since 2.1.0
23
23
  # @api private
24
24
  def cmd_with_args
25
- super +
26
- [
27
- "--",
28
- "--watch"
29
- ]
25
+ super + ["--", "--watch"]
30
26
  end
31
27
  end
32
28
  end
@@ -13,16 +13,6 @@ module Hanami
13
13
  # @api private
14
14
  desc "Start the application in development mode"
15
15
 
16
- # @since 2.1.0
17
- # @api private
18
- option :procfile, type: :string, desc: "Path to Procfile", aliases: ["-f"]
19
-
20
- # @since 2.1.0
21
- # @api private
22
- example [
23
- "-f /path/to/Procfile",
24
- ]
25
-
26
16
  # @since 2.1.0
27
17
  # @api private
28
18
  def initialize(interactive_system_call: InteractiveSystemCall.new, **)
@@ -32,8 +22,8 @@ module Hanami
32
22
 
33
23
  # @since 2.1.0
34
24
  # @api private
35
- def call(procfile: nil, **)
36
- bin, args = executable(procfile: procfile)
25
+ def call(**)
26
+ bin, args = executable
37
27
  interactive_system_call.call(bin, *args)
38
28
  end
39
29
 
@@ -45,10 +35,8 @@ module Hanami
45
35
 
46
36
  # @since 2.1.0
47
37
  # @api private
48
- def executable(procfile: nil)
49
- # TODO: support other implementations of Foreman
50
- # See: https://github.com/ddollar/foreman#ports
51
- ["foreman", ["start", "-f", procfile || "Procfile.dev"]]
38
+ def executable
39
+ [::File.join("bin", "dev")]
52
40
  end
53
41
  end
54
42
  end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/inflector"
4
+ require "dry/files"
5
+ require "shellwords"
6
+
7
+ module Hanami
8
+ module CLI
9
+ module Commands
10
+ module App
11
+ module Generate
12
+ # @since 2.1.0
13
+ # @api private
14
+ class Part < App::Command
15
+ argument :name, required: true, desc: "Part name"
16
+ option :slice, required: false, desc: "Slice name"
17
+
18
+ example [
19
+ %(book (MyApp::Views::Parts::Book)),
20
+ %(book --slice=admin (Admin::Views::Parts::Book)),
21
+ ]
22
+ attr_reader :generator
23
+ private :generator
24
+
25
+ # @since 2.0.0
26
+ # @api private
27
+ def initialize(
28
+ fs: Hanami::CLI::Files.new,
29
+ inflector: Dry::Inflector.new,
30
+ generator: Generators::App::Part.new(fs: fs, inflector: inflector),
31
+ **
32
+ )
33
+ @generator = generator
34
+ super(fs: fs)
35
+ end
36
+
37
+ # @since 2.0.0
38
+ # @api private
39
+ def call(name:, slice: nil, **)
40
+ slice = inflector.underscore(Shellwords.shellescape(slice)) if slice
41
+
42
+ generator.call(app.namespace, name, slice)
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -33,37 +33,9 @@ module Hanami
33
33
  # @api private
34
34
  option :head, type: :boolean, desc: "Install head deps", default: DEFAULT_HEAD
35
35
 
36
- # @since 2.1.0
37
- # @api private
38
- def initialize(system_call: SystemCall.new, **)
39
- @system_call = system_call
40
- super()
41
- end
42
-
43
36
  # @since 2.0.0
44
37
  # @api private
45
38
  def call(head: DEFAULT_HEAD, **)
46
- install_hanami_assets!(head: head)
47
- end
48
-
49
- private
50
-
51
- # @since 2.1.0
52
- # @api private
53
- attr_reader :system_call
54
-
55
- # @since 2.1.0
56
- # @api private
57
- def install_hanami_assets!(head:)
58
- return unless Hanami.bundled?("hanami-assets")
59
-
60
- system_call.call("npm", ["init", "-y"])
61
-
62
- if head
63
- system_call.call("npm", %w[install https://github.com/hanami/assets-js])
64
- else
65
- system_call.call("npm", %w[install hanami-assets])
66
- end
67
39
  end
68
40
  end
69
41
  end
@@ -31,6 +31,7 @@ module Hanami
31
31
  prefix.register "slice", Generate::Slice
32
32
  prefix.register "action", Generate::Action
33
33
  prefix.register "view", Generate::View
34
+ prefix.register "part", Generate::Part
34
35
  end
35
36
  end
36
37
  end
@@ -58,6 +58,8 @@ module Hanami
58
58
  ]
59
59
  # rubocop:enable Layout/LineLength
60
60
 
61
+ # rubocop:disable Metrics/ParameterLists
62
+
61
63
  # @since 2.0.0
62
64
  # @api private
63
65
  def initialize(
@@ -65,13 +67,19 @@ module Hanami
65
67
  inflector: Dry::Inflector.new,
66
68
  bundler: CLI::Bundler.new(fs: fs),
67
69
  generator: Generators::Gem::App.new(fs: fs, inflector: inflector),
70
+ system_call: SystemCall.new,
68
71
  **other
69
72
  )
70
73
  @bundler = bundler
71
74
  @generator = generator
75
+ @system_call = system_call
72
76
  super(fs: fs, inflector: inflector, **other)
73
77
  end
74
78
 
79
+ # rubocop:enable Metrics/ParameterLists
80
+
81
+ # rubocop:disable Metrics/AbcSize
82
+
75
83
  # @since 2.0.0
76
84
  # @api private
77
85
  def call(app:, head: HEAD_DEFAULT, skip_install: SKIP_INSTALL_DEFAULT, skip_assets: SKIP_ASSETS_DEFAULT, **)
@@ -88,17 +96,25 @@ module Hanami
88
96
  else
89
97
  out.puts "Running Bundler install..."
90
98
  bundler.install!
99
+
100
+ unless skip_assets
101
+ out.puts "Running npm install..."
102
+ system_call.call("npm", ["install"])
103
+ end
104
+
91
105
  out.puts "Running Hanami install..."
92
106
  run_install_commmand!(head: head)
93
107
  end
94
108
  end
95
109
  end
96
110
  end
111
+ # rubocop:enable Metrics/AbcSize
97
112
 
98
113
  private
99
114
 
100
115
  attr_reader :bundler
101
116
  attr_reader :generator
117
+ attr_reader :system_call
102
118
 
103
119
  def run_install_commmand!(head:)
104
120
  head_flag = head ? " --head" : ""
@@ -56,6 +56,14 @@ module Hanami
56
56
  }.freeze
57
57
  private_constant :ROUTE_RESTFUL_URL_SUFFIXES
58
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
+
59
67
  PATH_SEPARATOR = "/"
60
68
  private_constant :PATH_SEPARATOR
61
69
 
@@ -77,7 +85,7 @@ module Hanami
77
85
  fs.mkdir(directory = fs.join(slice_directory, "actions", controller))
78
86
  fs.write(fs.join(directory, "#{action}.rb"), t("slice_action.erb", context))
79
87
 
80
- unless skip_view
88
+ if generate_view?(skip_view, action, directory)
81
89
  fs.mkdir(directory = fs.join(slice_directory, "views", controller))
82
90
  fs.write(fs.join(directory, "#{action}.rb"), t("slice_view.erb", context))
83
91
 
@@ -97,12 +105,15 @@ module Hanami
97
105
  fs.mkdir(directory = fs.join("app", "actions", controller))
98
106
  fs.write(fs.join(directory, "#{action}.rb"), t("action.erb", context))
99
107
 
100
- unless skip_view
101
- fs.mkdir(directory = fs.join("app", "views", controller))
102
- fs.write(fs.join(directory, "#{action}.rb"), t("view.erb", context))
108
+ view = action
109
+ view_directory = fs.join("app", "views", controller)
103
110
 
104
- fs.mkdir(directory = fs.join("app", "templates", controller))
105
- 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"),
106
117
  t(template_with_format_ext("template", format), context))
107
118
  end
108
119
  end
@@ -117,6 +128,35 @@ module Hanami
117
128
  http)} "#{route_url(controller, action, url)}", to: "#{controller.join('.')}.#{action}")
118
129
  end
119
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
+
120
160
  def template_with_format_ext(name, format)
121
161
  ext =
122
162
  case format.to_sym
@@ -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
@@ -27,12 +27,12 @@ module Hanami
27
27
  def hanami_gem(name)
28
28
  gem_name = name == "hanami" ? "hanami" : "hanami-#{name}"
29
29
 
30
- %(gem "#{gem_name}", #{hanami_version(name)})
30
+ %(gem "#{gem_name}", #{hanami_gem_version(name)})
31
31
  end
32
32
 
33
33
  # @since 2.0.0
34
34
  # @api private
35
- def hanami_version(gem_name)
35
+ def hanami_gem_version(gem_name)
36
36
  if hanami_head?
37
37
  %(github: "hanami/#{gem_name}", branch: "main")
38
38
  else
@@ -40,6 +40,16 @@ module Hanami
40
40
  end
41
41
  end
42
42
 
43
+ # @since 2.1.0
44
+ # @api private
45
+ def hanami_assets_npm_package
46
+ if hanami_head?
47
+ %("hanami-assets": "hanami/assets-js#main")
48
+ else
49
+ %("hanami-assets": "#{Version.npm_package_requirement}")
50
+ end
51
+ end
52
+
43
53
  # @since 2.0.0
44
54
  # @api private
45
55
  def camelized_app_name
@@ -82,6 +92,14 @@ module Hanami
82
92
  Hanami.bundled?("hanami-assets")
83
93
  end
84
94
 
95
+ # @since 2.1.0
96
+ # @api private
97
+ #
98
+ # @see https://rubyreferences.github.io/rubychanges/3.1.html#values-in-hash-literals-and-keyword-arguments-can-be-omitted
99
+ def ruby_omit_hash_values?
100
+ RUBY_VERSION >= "3.1"
101
+ end
102
+
85
103
  private
86
104
 
87
105
  # @since 2.0.0
@@ -1,11 +1,82 @@
1
1
  <!DOCTYPE html>
2
- <html>
3
- <head>
4
- <title>The page you were looking for doesn’t exist (404)</title>
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>The page you were looking for doesn’t exist (404)</title>
7
+ <style>
8
+ :root {
9
+ --foreground-rgb: 0, 0, 0;
10
+ --background-rgb: 255, 255, 255;
11
+ --font-sans: ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;
12
+ }
13
+
14
+ @media (prefers-color-scheme: dark) {
15
+ :root {
16
+ --foreground-rgb: 255, 255, 255;
17
+ --background-rgb: 0, 0, 0;
18
+ }
19
+ }
20
+
21
+ * {
22
+ box-sizing: border-box;
23
+ margin: 0;
24
+ padding: 0;
25
+ }
26
+
27
+ body,
28
+ html {
29
+ max-width: 100vw;
30
+ overflow-x: hidden;
31
+ font-size: 100%;
32
+ }
33
+
34
+ body {
35
+ color: rgb(var(--foreground-rgb));
36
+ background: rgb(var(--background-rgb));
37
+ font-family: var(--font-sans);
38
+ font-style: normal;
39
+ }
40
+
41
+ main {
42
+ display: flex;
43
+ flex-direction: column;
44
+ align-items: center;
45
+ justify-content: center;
46
+ height: 100vh;
47
+ padding: 0 4vw;
48
+ }
49
+
50
+ .message {
51
+ display: flex;
52
+ gap: 1rem;
53
+ flex-direction: column;
54
+ text-align: center;
55
+ }
56
+
57
+ .message h1 {
58
+ font-size: 2rem;
59
+ font-weight: 500;
60
+ }
61
+
62
+ p {
63
+ line-height: 1.6;
64
+ }
65
+
66
+ @media (prefers-color-scheme: dark) {
67
+ html {
68
+ color-scheme: dark;
69
+ }
70
+ }
71
+ </style>
5
72
  </head>
6
73
  <body>
7
74
  <!-- This file lives in public/404.html -->
8
- <h1>The page you were looking for doesn’t exist.</h1>
9
- <p>You may have mistyped the address or the page may have moved.</p>
75
+ <main>
76
+ <div class="message">
77
+ <h1>404</h1>
78
+ <p>The page you were looking for doesn’t exist.</p>
79
+ </div>
80
+ </main>
10
81
  </body>
11
82
  </html>
@@ -1,11 +1,82 @@
1
1
  <!DOCTYPE html>
2
- <html>
3
- <head>
4
- <title>We’re sorry, but something went wrong (500)</title>
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>We’re sorry, but something went wrong (500)</title>
7
+ <style>
8
+ :root {
9
+ --foreground-rgb: 0, 0, 0;
10
+ --background-rgb: 255, 255, 255;
11
+ --font-sans: ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;
12
+ }
13
+
14
+ @media (prefers-color-scheme: dark) {
15
+ :root {
16
+ --foreground-rgb: 255, 255, 255;
17
+ --background-rgb: 0, 0, 0;
18
+ }
19
+ }
20
+
21
+ * {
22
+ box-sizing: border-box;
23
+ margin: 0;
24
+ padding: 0;
25
+ }
26
+
27
+ body,
28
+ html {
29
+ max-width: 100vw;
30
+ overflow-x: hidden;
31
+ font-size: 100%;
32
+ }
33
+
34
+ body {
35
+ color: rgb(var(--foreground-rgb));
36
+ background: rgb(var(--background-rgb));
37
+ font-family: var(--font-sans);
38
+ font-style: normal;
39
+ }
40
+
41
+ main {
42
+ display: flex;
43
+ flex-direction: column;
44
+ align-items: center;
45
+ justify-content: center;
46
+ height: 100vh;
47
+ padding: 0 4vw;
48
+ }
49
+
50
+ .message {
51
+ display: flex;
52
+ gap: 1rem;
53
+ flex-direction: column;
54
+ text-align: center;
55
+ }
56
+
57
+ .message h1 {
58
+ font-size: 2rem;
59
+ font-weight: 500;
60
+ }
61
+
62
+ p {
63
+ line-height: 1.6;
64
+ }
65
+
66
+ @media (prefers-color-scheme: dark) {
67
+ html {
68
+ color-scheme: dark;
69
+ }
70
+ }
71
+ </style>
5
72
  </head>
6
73
  <body>
7
74
  <!-- This file lives in public/500.html -->
8
- <h1>We’re sorry, but something went wrong.</h1>
9
- <p>If you are the application owner, check the logs for more information.</p>
75
+ <main>
76
+ <div class="message">
77
+ <h1>500</h1>
78
+ <p>We’re sorry, but something went wrong.</p>
79
+ </div>
80
+ </main>
10
81
  </body>
11
82
  </html>
@@ -0,0 +1,14 @@
1
+ import * as assets from "hanami-assets";
2
+
3
+ await assets.run();
4
+
5
+ // To provide additional esbuild (https://esbuild.github.io) options, use the following:
6
+ //
7
+ // await assets.run({
8
+ // esbuildOptionsFn: (args, esbuildOptions) => {
9
+ // // Add to esbuildOptions here. Use `args.watch` as a condition for different options for
10
+ // // compile vs watch.
11
+ //
12
+ // return esbuildOptions;
13
+ // }
14
+ // });
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env sh
2
+
3
+ if ! gem list foreman -i --silent; then
4
+ echo "Installing foreman..."
5
+ gem install foreman
6
+ fi
7
+
8
+ exec foreman start -f Procfile.dev "$@"
@@ -0,0 +1,10 @@
1
+ {
2
+ "name": "<%= underscored_app_name %>",
3
+ "private": true,
4
+ "scripts": {
5
+ "assets": "node config/assets.mjs"
6
+ },
7
+ "dependencies": {
8
+ <%= hanami_assets_npm_package %>
9
+ }
10
+ }
@@ -1,17 +1,47 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ #
4
+ # Environment and port
5
+ #
6
+ port ENV.fetch("<%= Hanami::Port::ENV_VAR %>", <%= Hanami::Port::DEFAULT %>)
7
+ environment ENV.fetch("HANAMI_ENV", "development")
8
+
9
+ #
10
+ # Threads within each Puma/Ruby process (aka worker)
11
+ #
12
+
13
+ # Configure the minimum and maximum number of threads to use to answer requests.
3
14
  max_threads_count = ENV.fetch("HANAMI_MAX_THREADS", 5)
4
15
  min_threads_count = ENV.fetch("HANAMI_MIN_THREADS") { max_threads_count }
16
+
5
17
  threads min_threads_count, max_threads_count
6
18
 
7
- port ENV.fetch("<%= Hanami::Port::ENV_VAR %>", <%= Hanami::Port::DEFAULT %>)
8
- environment ENV.fetch("HANAMI_ENV", "development")
9
- workers ENV.fetch("HANAMI_WEB_CONCURRENCY", 0)
19
+ #
20
+ # Workers (aka Puma/Ruby processes)
21
+ #
10
22
 
11
- if ENV.fetch("HANAMI_WEB_CONCURRENCY", 0) > 0
12
- on_worker_boot do
23
+ puma_concurrency = Integer(ENV.fetch("HANAMI_WEB_CONCURRENCY", 0))
24
+ puma_cluster_mode = puma_concurrency > 1
25
+
26
+ # How many worker (Puma/Ruby) processes to run.
27
+ # Typically this is set to the number of available cores.
28
+ workers puma_concurrency
29
+
30
+ #
31
+ # Cluster mode (aka multiple workers)
32
+ #
33
+
34
+ if puma_cluster_mode
35
+ # Preload the application before starting the workers. Only in cluster mode.
36
+ preload_app!
37
+
38
+ # Code to run immediately before master process forks workers (once on boot).
39
+ #
40
+ # These hooks can block if necessary to wait for background operations unknown
41
+ # to puma to finish before the process terminates. This can be used to close
42
+ # any connections to remote servers (database, redis, …) that were opened when
43
+ # preloading the code.
44
+ before_fork do
13
45
  Hanami.shutdown
14
46
  end
15
47
  end
16
-
17
- preload_app!
@@ -2,6 +2,6 @@
2
2
 
3
3
  module <%= camelized_app_name %>
4
4
  class Routes < Hanami::Routes
5
- root { "Hello from Hanami" }
5
+ # Add your routes here. See https://guides.hanamirb.org/routing/overview/ for details.
6
6
  end
7
7
  end
@@ -43,6 +43,9 @@ module Hanami
43
43
  fs.write("Procfile.dev", t("procfile.erb", context))
44
44
  fs.write("config.ru", t("config_ru.erb", context))
45
45
 
46
+ fs.write("bin/dev", file("dev"))
47
+ fs.chmod("bin/dev", 0o755)
48
+
46
49
  fs.write("config/app.rb", t("app.erb", context))
47
50
  fs.write("config/settings.rb", t("settings.erb", context))
48
51
  fs.write("config/routes.rb", t("routes.erb", context))
@@ -58,13 +61,15 @@ module Hanami
58
61
  fs.write("app/templates/layouts/app.html.erb", t("app_layout.erb", context))
59
62
 
60
63
  if context.generate_assets?
64
+ fs.write("package.json", t("package.json.erb", context))
65
+ fs.write("config/assets.mjs", file("assets.mjs"))
61
66
  fs.write("app/assets/js/app.js", t("app_js.erb", context))
62
67
  fs.write("app/assets/css/app.css", t("app_css.erb", context))
63
- fs.write("app/assets/images/favicon.ico", File.read(File.join(__dir__, "app", "favicon.ico")))
68
+ fs.write("app/assets/images/favicon.ico", file("favicon.ico"))
64
69
  end
65
70
 
66
- fs.write("public/404.html", File.read(File.join(__dir__, "app", "404.html")))
67
- fs.write("public/500.html", File.read(File.join(__dir__, "app", "500.html")))
71
+ fs.write("public/404.html", file("404.html"))
72
+ fs.write("public/500.html", file("500.html"))
68
73
  end
69
74
 
70
75
  def template(path, context)
@@ -77,6 +82,10 @@ module Hanami
77
82
  end
78
83
 
79
84
  alias_method :t, :template
85
+
86
+ def file(path)
87
+ File.read(File.join(__dir__, "app", path))
88
+ end
80
89
  end
81
90
  end
82
91
  end
@@ -26,6 +26,18 @@ module Hanami
26
26
  "~> #{result}"
27
27
  end
28
28
 
29
+ def self.npm_package_requirement
30
+ result = version
31
+ # Change "2.1.0.beta2.1" to "2.1.0-beta.2" (the only format tolerable by `npm install`)
32
+ if prerelease?
33
+ result = result
34
+ .sub(/\.(alpha|beta|rc)/, '-\1')
35
+ .sub(/(alpha|beta|rc)(.+)\.(.+)$/, '\1.\2')
36
+ end
37
+
38
+ "^#{result}"
39
+ end
40
+
29
41
  # @since 2.0.0
30
42
  # @api private
31
43
  def self.prerelease?
@@ -6,6 +6,6 @@ module Hanami
6
6
  #
7
7
  # @api public
8
8
  # @since 2.0.0
9
- VERSION = "2.1.0.beta2"
9
+ VERSION = "2.1.0.rc1"
10
10
  end
11
11
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hanami-cli
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.0.beta2
4
+ version: 2.1.0.rc1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Luca Guidi
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-10-04 00:00:00.000000000 Z
11
+ date: 2023-11-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -216,6 +216,7 @@ files:
216
216
  - lib/hanami/cli/commands/app/dev.rb
217
217
  - lib/hanami/cli/commands/app/generate.rb
218
218
  - lib/hanami/cli/commands/app/generate/action.rb
219
+ - lib/hanami/cli/commands/app/generate/part.rb
219
220
  - lib/hanami/cli/commands/app/generate/slice.rb
220
221
  - lib/hanami/cli/commands/app/generate/view.rb
221
222
  - lib/hanami/cli/commands/app/install.rb
@@ -237,6 +238,12 @@ files:
237
238
  - lib/hanami/cli/generators/app/action/template.html.erb
238
239
  - lib/hanami/cli/generators/app/action/view.erb
239
240
  - lib/hanami/cli/generators/app/action_context.rb
241
+ - lib/hanami/cli/generators/app/part.rb
242
+ - lib/hanami/cli/generators/app/part/app_base_part.erb
243
+ - lib/hanami/cli/generators/app/part/app_part.erb
244
+ - lib/hanami/cli/generators/app/part/slice_base_part.erb
245
+ - lib/hanami/cli/generators/app/part/slice_part.erb
246
+ - lib/hanami/cli/generators/app/part_context.rb
240
247
  - lib/hanami/cli/generators/app/slice.rb
241
248
  - lib/hanami/cli/generators/app/slice/action.erb
242
249
  - lib/hanami/cli/generators/app/slice/app_css.erb
@@ -265,13 +272,16 @@ files:
265
272
  - lib/hanami/cli/generators/gem/app/app_css.erb
266
273
  - lib/hanami/cli/generators/gem/app/app_js.erb
267
274
  - lib/hanami/cli/generators/gem/app/app_layout.erb
275
+ - lib/hanami/cli/generators/gem/app/assets.mjs
268
276
  - lib/hanami/cli/generators/gem/app/config_ru.erb
277
+ - lib/hanami/cli/generators/gem/app/dev
269
278
  - lib/hanami/cli/generators/gem/app/env.erb
270
279
  - lib/hanami/cli/generators/gem/app/favicon.ico
271
280
  - lib/hanami/cli/generators/gem/app/gemfile.erb
272
281
  - lib/hanami/cli/generators/gem/app/gitignore.erb
273
282
  - lib/hanami/cli/generators/gem/app/helpers.erb
274
283
  - lib/hanami/cli/generators/gem/app/keep.erb
284
+ - lib/hanami/cli/generators/gem/app/package.json.erb
275
285
  - lib/hanami/cli/generators/gem/app/procfile.erb
276
286
  - lib/hanami/cli/generators/gem/app/puma.erb
277
287
  - lib/hanami/cli/generators/gem/app/rakefile.erb