hanami-cli 2.1.0.beta2 → 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.
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