hanami-cli 2.1.0.rc2 → 2.1.0.rc3

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: aa0dbea6bc97ed3a7d08b703e7fd9091def9a8326196d36c071fb8e3e9be654b
4
- data.tar.gz: dc27646ce5d395932bd2fbddef1678cb5445959494270b8d5f149462a8648e90
3
+ metadata.gz: 7e96c42bbcc6493a575f9ba2c716e7129259e4364dedd4a7d1757a239eea224e
4
+ data.tar.gz: 51f4237d382a25f7d35547b578434889ef16bd365a411413033b13301eed91d2
5
5
  SHA512:
6
- metadata.gz: ac1ab34a920c7fea44106a75c8c7e8d1bb0e475ff1e48ce2ef9b87912e9da6c83aa847ddf56661db13a01551fd58e098245b6d8ac277ca7143c69681d97d5ece
7
- data.tar.gz: 706d1bb79523ba8a7efd9281267de12a0f275be0b60374c619b132371e0339335e6a4112b7a53ed5951e57293799aea1b4a89ac5bf6f9ee718daec4e1c44ee45
6
+ metadata.gz: fa474b08d69848674a29519a8fe5752ec2ac971f002e43a866c24db9d6f7293a0e0ff57ac160d73d0866107dd84bc39ba523be51d59a2e2a4cf04b4a2f293331
7
+ data.tar.gz: 4c1a7da944c8159bc99eb550b07661455c60a2e5fea933ead880e6deb0d1fdf459b8c68d526eededf3305a0105a5db06854d67fc8a357086e7c6a592f869a688
data/.rubocop.yml CHANGED
@@ -13,6 +13,8 @@ Lint/DuplicateBranch:
13
13
  Lint/EmptyFile:
14
14
  Exclude:
15
15
  - 'spec/fixtures/**/*.rb'
16
+ Lint/NonLocalExitFromIterator:
17
+ Enabled: false
16
18
  Naming/HeredocDelimiterNaming:
17
19
  Enabled: false
18
20
  Naming/MethodParameterName:
data/CHANGELOG.md CHANGED
@@ -2,6 +2,21 @@
2
2
 
3
3
  Hanami Command Line Interface
4
4
 
5
+ ## v2.1.0.rc3 - 2024-02-16
6
+
7
+ ### Changed
8
+
9
+ - [Tim Riley] For `hanami assets` commands, run a separate assets compilation process per slice (in parallel).
10
+ - [Tim Riley] Generate compiled assets into separate folders per slice, each with its own `assets.js` manifest file: `public/assets/` for the app, and `public/assets/[slice_name]/` for each slice.
11
+ - [Tim Riley] For `hanami assets` commands, directly detect and invoke the `config/assets.js` files. Look for this file within each slice, and fall back to the app-level file.
12
+ - [Tim Riley] Do not generate `"scripts": {"assets": "..."}` section in new app's `package.json`.
13
+ - [Tim Riley] Subclasses of `Hanami::CLI::Command` receive default args to their `#initialize` methods, and do not need to re-declare default args themselves.
14
+ - [Philip Arndt] Alphabetically sort hanami gems in the new app `Gemfile`.
15
+
16
+ ### Fixed
17
+
18
+ - [Nishiki (錦華)] Strip invalid characters from module name when generating new app.
19
+
5
20
  ## v2.1.0.rc2 - 2023-11-08
6
21
 
7
22
  ### Added
data/Gemfile CHANGED
@@ -19,6 +19,8 @@ gem "dry-files", github: "dry-rb/dry-files", branch: "main"
19
19
 
20
20
  gem "rack"
21
21
 
22
+ gem "hanami-devtools", github: "hanami/devtools", branch: "main"
23
+
22
24
  group :test do
23
25
  gem "pry"
24
26
  end
@@ -13,18 +13,40 @@ module Hanami
13
13
  class Command < Dry::CLI::Command
14
14
  # Returns a new command.
15
15
  #
16
- # This method does not need to be called directly when creating comments for the CLI. Commands
16
+ # Provides default values so they can be available to any subclasses defining their own
17
+ # {#initialize} methods.
18
+ #
19
+ # @see #initialize
20
+ #
21
+ # @since 2.1.0
22
+ # @api public
23
+ def self.new(
24
+ out: $stdout,
25
+ err: $stderr,
26
+ fs: Hanami::CLI::Files.new,
27
+ inflector: Dry::Inflector.new,
28
+ **opts
29
+ )
30
+ super(out: out, err: err, fs: fs, inflector: inflector, **opts)
31
+ end
32
+
33
+ # Returns a new command.
34
+ #
35
+ # This method does not need to be called directly when creating commands for the CLI. Commands
17
36
  # are registered as classes, and the CLI framework will initialize the command when needed.
18
- # This means that all parameters for `#initialize` should also be given default arguments.
37
+ # This means that all parameters for `#initialize` should also be given default arguments. See
38
+ # {.new} for the standard default arguments for all commands.
19
39
  #
20
40
  # @param out [IO] I/O stream for standard command output
21
41
  # @param err [IO] I/O stream for comment errror output
22
42
  # @param fs [Hanami::CLI::Files] object for managing file system interactions
23
43
  # @param inflector [Dry::Inflector] inflector for any command-level inflections
24
44
  #
45
+ # @see .new
46
+ #
25
47
  # @since 2.0.0
26
48
  # @api public
27
- def initialize(out: $stdout, err: $stderr, fs: Hanami::CLI::Files.new, inflector: Dry::Inflector.new)
49
+ def initialize(out:, err:, fs:, inflector:)
28
50
  super()
29
51
  @out = out
30
52
  @err = err
@@ -2,28 +2,71 @@
2
2
 
3
3
  require "shellwords"
4
4
  require_relative "../command"
5
- require_relative "../../../system_call"
5
+ require_relative "../../../interactive_system_call"
6
6
 
7
7
  module Hanami
8
8
  module CLI
9
9
  module Commands
10
10
  module App
11
11
  module Assets
12
+ # Base class for assets commands.
13
+ #
14
+ # Finds slices with assets present (anything in an `assets/` dir), then forks a child
15
+ # process for each slice to run the assets command (`config/assets.js`) for the slice.
16
+ #
17
+ # Prefers the slice's own `config/assets.js` if present, otherwise falls back to the
18
+ # app-level file.
19
+ #
20
+ # Passes `--path` and `--dest` arguments to this command to compile assets for the given
21
+ # slice only and save them into a dedicated directory (`public/assets/` for the app,
22
+ # `public/[slice_name]/` for slices).
23
+ #
24
+ # @see Watch
25
+ # @see Compile
26
+ #
12
27
  # @since 2.1.0
13
28
  # @api private
14
29
  class Command < App::Command
15
- def initialize(config: app.config.assets, system_call: SystemCall.new, **)
16
- super()
17
- @system_call = system_call
30
+ # @since 2.1.0
31
+ # @api private
32
+ def initialize(
33
+ out:, err:,
34
+ config: app.config.assets,
35
+ system_call: InteractiveSystemCall.new(out: out, err: err, exit_after: false),
36
+ **opts
37
+ )
38
+ super(out: out, err: err, **opts)
39
+
18
40
  @config = config
41
+ @system_call = system_call
19
42
  end
20
43
 
21
44
  # @since 2.1.0
22
45
  # @api private
23
46
  def call(**)
24
- cmd, *args = cmd_with_args
47
+ slices = slices_with_assets
48
+
49
+ if slices.empty?
50
+ out.puts "No assets found."
51
+ return
52
+ end
53
+
54
+ slices.each do |slice|
55
+ unless assets_config(slice)
56
+ out.puts "No assets config found for #{slice}. Please create a config/assets.js."
57
+ return
58
+ end
59
+ end
60
+
61
+ pids = slices_with_assets.map { |slice| fork_child_assets_command(slice) }
25
62
 
26
- system_call.call(cmd, *args)
63
+ Signal.trap("INT") do
64
+ pids.each do |pid|
65
+ Process.kill(sig, pid)
66
+ end
67
+ end
68
+
69
+ Process.waitall
27
70
  end
28
71
 
29
72
  private
@@ -38,8 +81,65 @@ module Hanami
38
81
 
39
82
  # @since 2.1.0
40
83
  # @api private
41
- def cmd_with_args
42
- [config.package_manager_run_command, "assets"]
84
+ def fork_child_assets_command(slice)
85
+ Process.fork do
86
+ cmd, *args = assets_command(slice)
87
+ system_call.call(cmd, *args, out_prefix: "[#{slice.slice_name}] ")
88
+ rescue Interrupt
89
+ # When this has been interrupted (by the Signal.trap handler in #call), catch the
90
+ # interrupt and exit cleanly, without showing the default full backtrace.
91
+ end
92
+ end
93
+
94
+ # @since 2.1.0
95
+ # @api private
96
+ def assets_command(slice)
97
+ cmd = [config.node_command, assets_config(slice).to_s, "--"]
98
+
99
+ if slice.eql?(slice.app)
100
+ cmd << "--path=app"
101
+ cmd << "--dest=public/assets"
102
+ else
103
+ cmd << "--path=#{slice.root.relative_path_from(slice.app.root)}"
104
+ cmd << "--dest=public/assets/#{slice.slice_name}"
105
+ end
106
+
107
+ cmd
108
+ end
109
+
110
+ # @since 2.1.0
111
+ # @api private
112
+ def slices_with_assets
113
+ slices = app.slices.with_nested + [app]
114
+ slices.select { |slice| slice_assets?(slice) }
115
+ end
116
+
117
+ # @since 2.1.0
118
+ # @api private
119
+ def slice_assets?(slice)
120
+ assets_path =
121
+ if slice.app.eql?(slice)
122
+ slice.root.join("app", "assets")
123
+ else
124
+ slice.root.join("assets")
125
+ end
126
+
127
+ assets_path.directory?
128
+ end
129
+
130
+ # Returns the path to the assets config (`config/assets.js`) for the given slice.
131
+ #
132
+ # Prefers a config file local to the slice, otherwise falls back to app-level config.
133
+ # Returns nil if no config can be found.
134
+ #
135
+ # @since 2.1.0
136
+ # @api private
137
+ def assets_config(slice)
138
+ config = slice.root.join("config", "assets.js")
139
+ return config if config.exist?
140
+
141
+ config = slice.app.root.join("config", "assets.js")
142
+ config if config.exist?
43
143
  end
44
144
 
45
145
  # @since 2.1.0
@@ -7,22 +7,25 @@ module Hanami
7
7
  module Commands
8
8
  module App
9
9
  module Assets
10
+ # Compiles assets for each slice.
11
+ #
10
12
  # @since 2.1.0
11
13
  # @api private
12
14
  class Compile < Assets::Command
13
15
  desc "Compile assets for deployments"
14
16
 
17
+ private
18
+
15
19
  # @since 2.1.0
16
20
  # @api private
17
- def cmd_with_args
18
- result = super
21
+ def assets_command(slice)
22
+ cmd = super
19
23
 
20
24
  if config.subresource_integrity.any?
21
- result << "--"
22
- result << "--sri=#{escape(config.subresource_integrity.join(','))}"
25
+ cmd << "--sri=#{escape(config.subresource_integrity.join(','))}"
23
26
  end
24
27
 
25
- result
28
+ cmd
26
29
  end
27
30
  end
28
31
  end
@@ -1,28 +1,25 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "command"
4
- require_relative "../../../interactive_system_call"
5
4
 
6
5
  module Hanami
7
6
  module CLI
8
7
  module Commands
9
8
  module App
10
9
  module Assets
10
+ # Watches for asset changes within each slice.
11
+ #
11
12
  # @since 2.1.0
12
13
  # @api private
13
14
  class Watch < Assets::Command
14
15
  desc "Start assets watch mode"
15
16
 
16
- def initialize(config: app.config.assets, system_call: InteractiveSystemCall.new, **)
17
- super(config: config, system_call: system_call)
18
- end
19
-
20
17
  private
21
18
 
22
19
  # @since 2.1.0
23
20
  # @api private
24
- def cmd_with_args
25
- super + ["--", "--watch"]
21
+ def assets_command(slice)
22
+ super + ["--watch"]
26
23
  end
27
24
  end
28
25
  end
@@ -15,23 +15,28 @@ module Hanami
15
15
 
16
16
  # @since 2.1.0
17
17
  # @api private
18
- def initialize(interactive_system_call: InteractiveSystemCall.new, **)
19
- @interactive_system_call = interactive_system_call
20
- super()
18
+ def initialize(
19
+ out:, err:,
20
+ system_call: InteractiveSystemCall.new(out: out, err: err),
21
+ **opts
22
+ )
23
+ super(out: out, err: err, **opts)
24
+
25
+ @system_call = system_call
21
26
  end
22
27
 
23
28
  # @since 2.1.0
24
29
  # @api private
25
30
  def call(**)
26
31
  bin, args = executable
27
- interactive_system_call.call(bin, *args)
32
+ system_call.call(bin, *args)
28
33
  end
29
34
 
30
35
  private
31
36
 
32
37
  # @since 2.1.0
33
38
  # @api private
34
- attr_reader :interactive_system_call
39
+ attr_reader :system_call
35
40
 
36
41
  # @since 2.1.0
37
42
  # @api private
@@ -61,12 +61,16 @@ module Hanami
61
61
 
62
62
  # @since 2.0.0
63
63
  # @api private
64
- def initialize(fs: Hanami::CLI::Files.new, inflector: Dry::Inflector.new,
65
- naming: Naming.new(inflector: inflector),
66
- generator: Generators::App::Action.new(fs: fs, inflector: inflector), **)
64
+ def initialize(
65
+ fs:, inflector:,
66
+ naming: Naming.new(inflector: inflector),
67
+ generator: Generators::App::Action.new(fs: fs, inflector: inflector),
68
+ **opts
69
+ )
70
+ super(fs: fs, inflector: inflector, **opts)
71
+
67
72
  @naming = naming
68
73
  @generator = generator
69
- super(fs: fs)
70
74
  end
71
75
 
72
76
  # rubocop:disable Metrics/ParameterLists
@@ -34,13 +34,12 @@ module Hanami
34
34
  # @since 2.0.0
35
35
  # @api private
36
36
  def initialize(
37
- fs: Hanami::CLI::Files.new,
38
- inflector: Dry::Inflector.new,
37
+ fs:, inflector:,
39
38
  generator: Generators::App::Part.new(fs: fs, inflector: inflector),
40
- **
39
+ **opts
41
40
  )
41
+ super(fs: fs, inflector: inflector, **opts)
42
42
  @generator = generator
43
- super(fs: fs)
44
43
  end
45
44
 
46
45
  # @since 2.0.0
@@ -23,10 +23,13 @@ module Hanami
23
23
 
24
24
  # @since 2.0.0
25
25
  # @api private
26
- def initialize(fs: Hanami::CLI::Files.new, inflector: Dry::Inflector.new,
27
- generator: Generators::App::Slice.new(fs: fs, inflector: inflector), **)
26
+ def initialize(
27
+ fs:, inflector:,
28
+ generator: Generators::App::Slice.new(fs: fs, inflector: inflector),
29
+ **opts
30
+ )
31
+ super(fs: fs, inflector: inflector, **opts)
28
32
  @generator = generator
29
- super(fs: fs)
30
33
  end
31
34
 
32
35
  # @since 2.0.0
@@ -33,13 +33,12 @@ module Hanami
33
33
  # @since 2.0.0
34
34
  # @api private
35
35
  def initialize(
36
- fs: Hanami::CLI::Files.new,
37
- inflector: Dry::Inflector.new,
36
+ fs:, inflector:,
38
37
  generator: Generators::App::View.new(fs: fs, inflector: inflector),
39
- **
38
+ **opts
40
39
  )
40
+ super(fs: fs, inflector: inflector, **opts)
41
41
  @generator = generator
42
- super(fs: fs)
43
42
  end
44
43
 
45
44
  # @since 2.0.0
@@ -46,8 +46,8 @@ module Hanami
46
46
 
47
47
  # @since 2.0.0
48
48
  # @api private
49
- def initialize(server: Hanami::CLI::Server.new)
50
- super()
49
+ def initialize(server: Hanami::CLI::Server.new, **opts)
50
+ super(**opts)
51
51
  @server = server
52
52
  end
53
53
 
@@ -63,17 +63,16 @@ module Hanami
63
63
  # @since 2.0.0
64
64
  # @api private
65
65
  def initialize(
66
- fs: Hanami::CLI::Files.new,
67
- inflector: Dry::Inflector.new,
66
+ fs:, inflector:,
68
67
  bundler: CLI::Bundler.new(fs: fs),
69
68
  generator: Generators::Gem::App.new(fs: fs, inflector: inflector),
70
69
  system_call: SystemCall.new,
71
- **other
70
+ **opts
72
71
  )
72
+ super(fs: fs, inflector: inflector, **opts)
73
73
  @bundler = bundler
74
74
  @generator = generator
75
75
  @system_call = system_call
76
- super(fs: fs, inflector: inflector, **other)
77
76
  end
78
77
 
79
78
  # rubocop:enable Metrics/ParameterLists
@@ -98,7 +97,7 @@ module Hanami
98
97
  bundler.install!
99
98
 
100
99
  unless skip_assets
101
- out.puts "Running npm install..."
100
+ out.puts "Running NPM install..."
102
101
  system_call.call("npm", ["install"]).tap do |result|
103
102
  unless result.successful?
104
103
  puts "NPM ERROR:"
@@ -35,6 +35,7 @@ module Hanami
35
35
  if context.bundled_assets?
36
36
  fs.write(fs.join(directory, "assets", "js", "app.js"), t("app_js.erb", context))
37
37
  fs.write(fs.join(directory, "assets", "css", "app.css"), t("app_css.erb", context))
38
+ fs.write(fs.join(directory, "assets", "images", "favicon.ico"), file("favicon.ico"))
38
39
  end
39
40
 
40
41
  # fs.write(fs.join(directory, "/entities.rb"), t("entities.erb", context))
@@ -64,6 +65,10 @@ module Hanami
64
65
  end
65
66
 
66
67
  alias_method :t, :template
68
+
69
+ def file(path)
70
+ File.read(File.join(__dir__, "slice", path))
71
+ end
67
72
  end
68
73
  end
69
74
  end
@@ -38,13 +38,13 @@ module Hanami
38
38
  # @since 2.1.0
39
39
  # @api private
40
40
  def stylesheet_erb_tag
41
- %(<%= stylesheet_tag "#{slice}/app" %>)
41
+ %(<%= stylesheet_tag "app" %>)
42
42
  end
43
43
 
44
44
  # @since 2.1.0
45
45
  # @api private
46
46
  def javascript_erb_tag
47
- %(<%= javascript_tag "#{slice}/app" %>)
47
+ %(<%= javascript_tag "app" %>)
48
48
  end
49
49
 
50
50
  private
@@ -53,7 +53,7 @@ module Hanami
53
53
  # @since 2.0.0
54
54
  # @api private
55
55
  def camelized_app_name
56
- inflector.camelize(app)
56
+ inflector.camelize(app).gsub(/[^\p{Alnum}]/, "")
57
57
  end
58
58
 
59
59
  # @since 2.0.0
@@ -4,6 +4,8 @@ await assets.run();
4
4
 
5
5
  // To provide additional esbuild (https://esbuild.github.io) options, use the following:
6
6
  //
7
+ // Read more at: https://guides.hanamirb.org/assets/overview/
8
+ //
7
9
  // await assets.run({
8
10
  // esbuildOptionsFn: (args, esbuildOptions) => {
9
11
  // // Add to esbuildOptions here. Use `args.watch` as a condition for different options for
@@ -3,13 +3,13 @@
3
3
  source "https://rubygems.org"
4
4
 
5
5
  <%= hanami_gem("hanami") %>
6
- <%= hanami_gem("router") %>
7
- <%= hanami_gem("controller") %>
8
- <%= hanami_gem("validations") %>
9
- <%= hanami_gem("view") %>
10
6
  <%- if generate_assets? -%>
11
7
  <%= hanami_gem("assets") %>
12
8
  <%- end -%>
9
+ <%= hanami_gem("controller") %>
10
+ <%= hanami_gem("router") %>
11
+ <%= hanami_gem("validations") %>
12
+ <%= hanami_gem("view") %>
13
13
 
14
14
  gem "dry-types", "~> 1.0", ">= 1.6.1"
15
15
  gem "puma"
@@ -2,9 +2,6 @@
2
2
  "name": "<%= underscored_app_name %>",
3
3
  "private": true,
4
4
  "type": "module",
5
- "scripts": {
6
- "assets": "node config/assets.js"
7
- },
8
5
  "dependencies": {
9
6
  <%= hanami_assets_npm_package %>
10
7
  }
@@ -9,15 +9,16 @@ module Hanami
9
9
  class InteractiveSystemCall
10
10
  # @api private
11
11
  # @since 2.1.0
12
- def initialize(out: $stdout, err: $stderr)
12
+ def initialize(out: $stdout, err: $stderr, exit_after: true)
13
13
  @out = out
14
14
  @err = err
15
+ @exit_after = exit_after
15
16
  super()
16
17
  end
17
18
 
18
19
  # @api private
19
20
  # @since 2.1.0
20
- def call(cmd, *args, env: {})
21
+ def call(cmd, *args, env: {}, out_prefix: "")
21
22
  ::Bundler.with_unbundled_env do
22
23
  threads = []
23
24
  exit_status = 0
@@ -26,14 +27,14 @@ module Hanami
26
27
  Open3.popen3(env, command(cmd, *args)) do |_stdin, stdout, stderr, wait_thr|
27
28
  threads << Thread.new do
28
29
  stdout.each_line do |line|
29
- out.puts(line)
30
+ out.puts("#{out_prefix}#{line}")
30
31
  end
31
32
  rescue IOError # FIXME: Check if this is legit
32
33
  end
33
34
 
34
35
  threads << Thread.new do
35
36
  stderr.each_line do |line|
36
- err.puts(line)
37
+ err.puts("#{out_prefix}#{line}")
37
38
  end
38
39
  rescue IOError # FIXME: Check if this is legit
39
40
  end
@@ -44,7 +45,7 @@ module Hanami
44
45
  end
45
46
  # rubocop:enable Lint/SuppressedException
46
47
 
47
- exit(exit_status.exitstatus)
48
+ exit(exit_status.exitstatus) if @exit_after
48
49
  end
49
50
  end
50
51
 
@@ -6,6 +6,6 @@ module Hanami
6
6
  #
7
7
  # @api public
8
8
  # @since 2.0.0
9
- VERSION = "2.1.0.rc2"
9
+ VERSION = "2.1.0.rc3"
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.rc2
4
+ version: 2.1.0.rc3
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-11-08 00:00:00.000000000 Z
11
+ date: 2024-02-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -250,6 +250,7 @@ files:
250
250
  - lib/hanami/cli/generators/app/slice/app_js.erb
251
251
  - lib/hanami/cli/generators/app/slice/app_layout.erb
252
252
  - lib/hanami/cli/generators/app/slice/entities.erb
253
+ - lib/hanami/cli/generators/app/slice/favicon.ico
253
254
  - lib/hanami/cli/generators/app/slice/helpers.erb
254
255
  - lib/hanami/cli/generators/app/slice/keep.erb
255
256
  - lib/hanami/cli/generators/app/slice/repository.erb
@@ -326,11 +327,11 @@ required_ruby_version: !ruby/object:Gem::Requirement
326
327
  version: '3.0'
327
328
  required_rubygems_version: !ruby/object:Gem::Requirement
328
329
  requirements:
329
- - - ">"
330
+ - - ">="
330
331
  - !ruby/object:Gem::Version
331
- version: 1.3.1
332
+ version: '0'
332
333
  requirements: []
333
- rubygems_version: 3.4.21
334
+ rubygems_version: 3.5.6
334
335
  signing_key:
335
336
  specification_version: 4
336
337
  summary: Hanami CLI