ember-cli-rails 0.12.3 → 0.13.0

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: '097b4299ffb3c7809ee87c5e84e5a053cde5872b1ca113369127c03f009e0482'
4
- data.tar.gz: 29cd17391b62a9b5ab139d4ad88f13391524ae1bd5b834000b986c19a7c81faf
3
+ metadata.gz: 88933ba7736d53f0cf1f9fc79e6b9997469517188a1b904571222f7e57d15091
4
+ data.tar.gz: fdd038c1e831096567b4bbdef03ed722c40ff11da77e87a29c3059073109d620
5
5
  SHA512:
6
- metadata.gz: 9b43b02eae5b58a6ac82893404952acca30f500f6570140f5f877857a606671f516ef750b9429c19babd14c45522e175188cf867f570c9565cb0ed56446a2257
7
- data.tar.gz: 609df4876c70f9abbcf63b103e0ce11f68e873c9c9549eb6f505f859d141ac9e3e68150abb91a7a3944a49224bd5ad2e69943a67dc5ad705f8366ab03e964bcf
6
+ metadata.gz: 10ad87d14effd255d9b13ecfd73ed2a26290199ea410d3204280cee51a2e90dd39c9802cf2201787d119fbafd7da10b29fee6d9e2305f6cf67d81b9946e8c0df
7
+ data.tar.gz: e6deabf31ce35858c53174386ffb9ae94cbbbf7a6b2eab5a72cec66deb607040b184c217a124b77ac06ac414d77ec86d6d62198f0fb425119657ca460403d509
data/CHANGELOG.md CHANGED
@@ -1,6 +1,26 @@
1
- main
1
+ 0.13.0
2
2
  ------
3
3
 
4
+ * Serve Vite-based applications from Vite's development server in
5
+ `development`, so that changes are hot-reloaded instead of requiring a
6
+ restart. Configure it with the `dev_server` option, or opt out with
7
+ `dev_server: false`
8
+ * Require `ember-cli-rails-assets >= 0.9.0`, which serves the
9
+ `include_ember_script_tags` startup tags from Vite's development server
10
+ when the application is served from it
11
+ * Add an `origin` option to `dev_server`, separating the origin the browser
12
+ loads assets from, from the address the development server binds to — for
13
+ containers, where the server must bind to `0.0.0.0` but browsers refuse
14
+ that address as an origin
15
+
16
+ 0.13.0.beta1
17
+ ------
18
+
19
+ * Support Ember applications generated with the Vite-based blueprint
20
+ (`ember-cli >= 6.8`), without `ember-cli-rails-addon`
21
+ * Require `ember-cli-rails-assets >= 0.8.0`, which adds Vite support to
22
+ `include_ember_script_tags`
23
+
4
24
  0.12.3
5
25
  ------
6
26
 
data/README.md CHANGED
@@ -81,6 +81,10 @@ c.app :frontend, path: "~/projects/my-ember-app"
81
81
 
82
82
  - `yarn` - enables the [yarn](https://github.com/yarnpkg/yarn) package manager when installing dependencies
83
83
 
84
+ - `dev_server` - configures [Vite's development server](#vite-based-applications)
85
+ for Vite-based applications in `development`. Pass `false` to opt out of it,
86
+ or a Hash of `host`, `port`, and `timeout` settings.
87
+
84
88
  ```ruby
85
89
  EmberCli.configure do |c|
86
90
  c.app :adminpanel # path defaults to `Rails.root.join("adminpanel")`
@@ -90,7 +94,8 @@ EmberCli.configure do |c|
90
94
  end
91
95
  ```
92
96
 
93
- Next, install the [ember-cli-rails-addon][addon]:
97
+ Next, if your Ember application uses the classic (Broccoli-based) build
98
+ (generated with `ember-cli < 6.8`), install the [ember-cli-rails-addon][addon]:
94
99
 
95
100
  ```bash
96
101
  $ cd path/to/frontend
@@ -111,6 +116,10 @@ For instance, if you're using the `0.6.x` version of the gem, specify
111
116
  }
112
117
  ```
113
118
 
119
+ If your Ember application uses the Vite-based build (generated with
120
+ `ember-cli >= 6.8`), do **not** install the addon: it is incompatible with the
121
+ Vite-based build, and `ember-cli-rails` does not require it there.
122
+
114
123
  [addon]: https://github.com/rondale-sc/ember-cli-rails-addon/
115
124
  [semver]: http://semver.org/
116
125
 
@@ -171,6 +180,84 @@ suites, configure the `default` task to depend on both `spec` and `ember:test`.
171
180
  task default: [:spec, "ember:test"]
172
181
  ```
173
182
 
183
+ ### Vite-based applications
184
+
185
+ When Rails is running in development mode, classic (Broccoli-based) Ember
186
+ applications are built with `ember build --watch`, so changes to the Ember
187
+ application are picked up automatically.
188
+
189
+ Vite-based Ember applications (generated with `ember-cli >= 6.8`) are served by
190
+ Vite's own development server instead — the same one the application's
191
+ `npm start` script runs. `ember-cli-rails` starts it on the first request,
192
+ waits for it to accept connections, and shuts it down when Rails exits.
193
+
194
+ Rails still renders the application's `index.html`, but reads it from the
195
+ development server rather than from disk. The root-relative URLs in that
196
+ document are rewritten to point at the development server, so the browser
197
+ loads the application's modules — and Vite's HMR client — from it directly.
198
+ Changes to the Ember application are hot-reloaded without reloading the page,
199
+ and without restarting Rails.
200
+
201
+ Assets that the Ember application references relatively (an `<img>` in a
202
+ template, for instance) are still requested from Rails, which proxies them to
203
+ the development server.
204
+
205
+ **Configuring the development server**
206
+
207
+ By default the development server listens on an available port on `127.0.0.1`,
208
+ and `ember-cli-rails` waits up to 30 seconds for it to start:
209
+
210
+ ```rb
211
+ EmberCli.configure do |c|
212
+ c.app :frontend, dev_server: { host: "127.0.0.1", port: 4200, timeout: 60 }
213
+ end
214
+ ```
215
+
216
+ If something is already listening on the configured `host` and `port`,
217
+ `ember-cli-rails` uses it instead of starting a second server. That makes it
218
+ possible to run `npm start` yourself, on a port the initializer names, and have
219
+ Rails serve the application from it.
220
+
221
+ `host` is both the address the development server binds to and — together with
222
+ `port` — the origin the browser loads the application's modules from. When the
223
+ two differ, configure the browser-facing origin separately with `origin`. The
224
+ typical case is Rails running in a container: the development server must bind
225
+ to `0.0.0.0` to be reachable through the container's published port, but
226
+ browsers refuse to connect to `http://0.0.0.0`:
227
+
228
+ ```rb
229
+ EmberCli.configure do |c|
230
+ c.app :frontend, dev_server: {
231
+ host: "0.0.0.0",
232
+ port: 4200,
233
+ origin: "http://localhost:4200",
234
+ }
235
+ end
236
+ ```
237
+
238
+ With `host: "0.0.0.0"`, Rails itself reaches the development server through the
239
+ loopback interface.
240
+
241
+ The development server's output is written to
242
+ `log/ember-<app>.<environment>.log`.
243
+
244
+ **Opting out**
245
+
246
+ To build the application once, synchronously, on the first request instead of
247
+ running a development server, disable it:
248
+
249
+ ```rb
250
+ EmberCli.configure do |c|
251
+ c.app :frontend, dev_server: false
252
+ end
253
+ ```
254
+
255
+ Changes to the Ember application are then only picked up by restarting the
256
+ Rails server.
257
+
258
+ The development server is only used in `development`. `test` and `production`
259
+ are served from the output of `ember build`, as they always have been.
260
+
174
261
  ## Deploy
175
262
 
176
263
  When Rails is running in production mode, EmberCLI-Rails stops doing runtime
@@ -449,6 +536,24 @@ actively supported method of serving EmberCLI applications.
449
536
  However, for the sake of backwards compatibility, `ember-cli-rails` supports
450
537
  injecting the EmberCLI-generated assets into an existing Rails layout.
451
538
 
539
+ **Note:** for Vite-based applications (generated with `ember-cli >= 6.8`),
540
+ use `include_ember_script_tags` on its own. It emits everything the
541
+ application needs to boot — the configuration `<meta>` tag, the stylesheet
542
+ and `modulepreload` links, and the ES module script tags — extracted from
543
+ the generated `index.html`. `include_ember_stylesheet_tags` only supports
544
+ classic (Broccoli-based) applications.
545
+
546
+ When the application is served by [Vite's development
547
+ server](#vite-based-applications), `include_ember_script_tags` (from
548
+ [ember-cli-rails-assets]) reads `index.html` from the server and emits the
549
+ startup tags with absolute URLs pointing at it — nothing is built, and changes
550
+ to the application are picked up by reloading the page.
551
+
552
+ With the development server disabled (`dev_server: false`), the helpers read
553
+ the output of `ember build` instead: the application is built once,
554
+ synchronously, on the first request, and changes to it are only picked up by
555
+ restarting the Rails server.
556
+
452
557
  Following the example above, configure the mounted EmberCLI application to be
453
558
  served by a custom controller (`ApplicationController`, in this case).
454
559
 
@@ -604,6 +709,11 @@ make sure it's configured to run a single worker process.
604
709
  Without restricting the server to a single process, [it is possible for multiple
605
710
  EmberCLI runners to clobber each others' work][#94].
606
711
 
712
+ This does not apply to Vite-based applications served by [Vite's development
713
+ server](#vite-based-applications): nothing is written to a shared build
714
+ directory. Give the application a fixed `port` so that the workers share a
715
+ single development server rather than starting one each.
716
+
607
717
  [Puma]: https://github.com/puma/puma
608
718
  [Unicorn]: https://rubygems.org/gems/unicorn
609
719
  [#94]: https://github.com/tricknotes/ember-cli-rails/issues/94#issuecomment-77627453
@@ -654,7 +764,23 @@ if (environment === 'development') {
654
764
 
655
765
  This project supports:
656
766
 
657
- * EmberCLI versions `>= 1.13.13`
767
+ * EmberCLI versions `>= 1.13.13` using the classic (Broccoli-based) build,
768
+ together with [ember-cli-rails-addon][addon]
769
+ * EmberCLI versions `>= 6.8` using the Vite-based build, without the addon
770
+
771
+ The test suite currently exercises the `5.12.x` and `6.7.x` (classic) and
772
+ `7.0.x` (Vite) series.
773
+
774
+ Note the following limitations for Vite-based applications:
775
+
776
+ * `include_ember_script_tags` emits the full set of tags the application
777
+ needs to boot, stylesheets included; `include_ember_stylesheet_tags` is
778
+ classic-only and must not be called for Vite-based applications
779
+ * in development, the application is served by [Vite's development
780
+ server](#vite-based-applications) rather than out of a build directory. The
781
+ asset helpers serve their tags from the development server too; with
782
+ `dev_server: false` they read the build output, which is built once,
783
+ synchronously, on the first request
658
784
 
659
785
  ## Ruby and Rails support
660
786
 
data/lib/ember_cli/app.rb CHANGED
@@ -2,7 +2,9 @@ require "html_page/renderer"
2
2
  require "ember_cli/path_set"
3
3
  require "ember_cli/shell"
4
4
  require "ember_cli/build_monitor"
5
+ require "ember_cli/deploy/dev_server"
5
6
  require "ember_cli/deploy/file"
7
+ require "ember_cli/dev_server"
6
8
 
7
9
  module EmberCli
8
10
  class App
@@ -49,13 +51,15 @@ module EmberCli
49
51
 
50
52
  def build
51
53
  unless EmberCli.skip?
52
- if development?
53
- build_and_watch
54
- elsif test?
55
- compile
54
+ if dev_server?
55
+ # Vite's own development server rebuilds and hot-reloads the
56
+ # application, so there is nothing to build ahead of time.
57
+ dev_server.start
58
+ else
59
+ build_for_environment
60
+
61
+ @build.wait!
56
62
  end
57
-
58
- @build.wait!
59
63
  end
60
64
  end
61
65
 
@@ -99,6 +103,24 @@ module EmberCli
99
103
  deploy.to_rack
100
104
  end
101
105
 
106
+ def dev_server
107
+ @dev_server ||= DevServer.new(
108
+ name: name,
109
+ paths: paths,
110
+ shell: @shell,
111
+ options: dev_server_options,
112
+ )
113
+ end
114
+
115
+ # Whether this application is served by Vite's development server rather
116
+ # than out of the directory `ember build` writes to.
117
+ def dev_server?
118
+ strategy = deploy_strategy
119
+
120
+ strategy.is_a?(Class) &&
121
+ strategy.ancestors.include?(EmberCli::Deploy::DevServer)
122
+ end
123
+
102
124
  private
103
125
 
104
126
  def development?
@@ -117,12 +139,38 @@ module EmberCli
117
139
  strategy = options.fetch(:deploy, {})
118
140
 
119
141
  if strategy.respond_to?(:fetch)
120
- strategy.fetch(rails_env, EmberCli::Deploy::File)
142
+ strategy.fetch(rails_env) { default_deploy_strategy }
121
143
  else
122
144
  strategy
123
145
  end
124
146
  end
125
147
 
148
+ def default_deploy_strategy
149
+ if development? && paths.vite? && dev_server_enabled?
150
+ EmberCli::Deploy::DevServer
151
+ else
152
+ EmberCli::Deploy::File
153
+ end
154
+ end
155
+
156
+ def dev_server_option
157
+ options.fetch(:dev_server, true)
158
+ end
159
+
160
+ def dev_server_enabled?
161
+ dev_server_option != false
162
+ end
163
+
164
+ def dev_server_options
165
+ option = dev_server_option
166
+
167
+ if option.respond_to?(:fetch)
168
+ option
169
+ else
170
+ {}
171
+ end
172
+ end
173
+
126
174
  def rails_env
127
175
  Rails.env.to_s.to_sym
128
176
  end
@@ -131,6 +179,21 @@ module EmberCli
131
179
  EmberCli.env
132
180
  end
133
181
 
182
+ def build_for_environment
183
+ if development?
184
+ if paths.vite?
185
+ # The Vite-based blueprint (`ember-cli >= 6.8`) has no
186
+ # `ember-cli-rails-addon` to manage the build lock, so build
187
+ # synchronously instead of watching for changes.
188
+ compile
189
+ else
190
+ build_and_watch
191
+ end
192
+ elsif test?
193
+ compile
194
+ end
195
+ end
196
+
134
197
  def build_and_watch
135
198
  prepare
136
199
  @shell.build_and_watch
@@ -17,6 +17,20 @@ module EmberCli
17
17
  ember_build(watch: watch)
18
18
  end
19
19
 
20
+ # Boots Vite's development server for an application generated with the
21
+ # Vite-based blueprint (`ember-cli >= 6.8`). This is what the blueprint's
22
+ # own `npm start` script runs.
23
+ def dev_server(host:, port:)
24
+ line = Terrapin::CommandLine.new(paths.vite, [
25
+ "--host :host",
26
+ "--port :port",
27
+ "--strictPort",
28
+ "--clearScreen false",
29
+ ].join(" "))
30
+
31
+ line.command(host: host.to_s, port: port.to_s)
32
+ end
33
+
20
34
  private
21
35
 
22
36
  attr_reader :options, :paths
@@ -0,0 +1,47 @@
1
+ require "ember_cli/dev_server_proxy"
2
+ require "ember_cli/errors"
3
+
4
+ module EmberCli
5
+ module Deploy
6
+ # Serves an Ember application from its Vite development server instead of
7
+ # from the `dist` directory written by `ember build`.
8
+ #
9
+ # The `index.html` served by the development server refers to its assets
10
+ # (`/@vite/client` included) with root-relative URLs. Rails serves the
11
+ # document from its own origin, so those URLs are rewritten to absolute
12
+ # URLs pointing at the development server. Loading them from there means
13
+ # the Vite client opens its HMR WebSocket against the development server
14
+ # directly, without Rails proxying it.
15
+ class DevServer
16
+ ROOT_RELATIVE_URL = %r{(\s)(src|href)=(["'])/(?!/)}i
17
+
18
+ def initialize(app)
19
+ @app = app
20
+ end
21
+
22
+ def mountable?
23
+ true
24
+ end
25
+
26
+ def to_rack
27
+ DevServerProxy.new(app)
28
+ end
29
+
30
+ def index_html
31
+ rewrite_root_relative_urls(dev_server.index_html)
32
+ end
33
+
34
+ private
35
+
36
+ attr_reader :app
37
+
38
+ delegate :dev_server, to: :app
39
+
40
+ def rewrite_root_relative_urls(html)
41
+ html.gsub(ROOT_RELATIVE_URL) do
42
+ "#{$1}#{$2}=#{$3}#{dev_server.origin}/"
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,187 @@
1
+ require "monitor"
2
+ require "net/http"
3
+ require "socket"
4
+ require "uri"
5
+
6
+ require "ember_cli/errors"
7
+
8
+ module EmberCli
9
+ # Manages the Vite development server backing a single Ember application.
10
+ #
11
+ # The server is booted lazily, and the Ember application's `index.html` and
12
+ # assets are read from it over HTTP instead of from the `dist` directory
13
+ # written by `ember build`.
14
+ class DevServer
15
+ DEFAULT_HOST = "127.0.0.1".freeze
16
+ DEFAULT_TIMEOUT = 30
17
+ POLL_INTERVAL = 0.1
18
+ CONNECT_TIMEOUT = 1
19
+
20
+ def initialize(name:, paths:, shell:, options: {})
21
+ @name = name
22
+ @paths = paths
23
+ @shell = shell
24
+ @options = options.respond_to?(:fetch) ? options : {}
25
+ @monitor = Monitor.new
26
+ end
27
+
28
+ def host
29
+ @host ||= option(:host) { DEFAULT_HOST }.to_s
30
+ end
31
+
32
+ def port
33
+ @port ||= option(:port) { available_port }.to_i
34
+ end
35
+
36
+ def timeout
37
+ @timeout ||= option(:timeout) { DEFAULT_TIMEOUT }.to_f
38
+ end
39
+
40
+ # The origin the browser loads the application's assets from. It defaults
41
+ # to the address the development server binds to, but can be configured
42
+ # separately for setups where the two differ — a development server bound
43
+ # to `0.0.0.0` inside a container, reached by the browser through a
44
+ # published port, for instance.
45
+ def origin
46
+ @origin ||= option(:origin) { "http://#{host}:#{port}" }.to_s.chomp("/")
47
+ end
48
+
49
+ # Boots the development server unless something is already listening on
50
+ # its address, and blocks until it accepts connections.
51
+ def start
52
+ return true if listening?
53
+
54
+ # Requests are served concurrently, so make sure only one of them boots
55
+ # the development server.
56
+ monitor.synchronize do
57
+ return true if listening?
58
+
59
+ shell.start_dev_server(host: host, port: port)
60
+
61
+ wait_until_listening!
62
+ end
63
+ end
64
+
65
+ def index_html
66
+ response = get("/")
67
+
68
+ unless response.is_a?(Net::HTTPSuccess)
69
+ fail BuildError.new(<<~MSG)
70
+ #{name.inspect} failed to serve an `index.html` file.
71
+
72
+ #{origin}/ responded with #{response.code} #{response.message}:
73
+
74
+ #{response.body}
75
+ MSG
76
+ end
77
+
78
+ response.body.to_s
79
+ end
80
+
81
+ def get(path, headers = {})
82
+ request(Net::HTTP::Get, path, headers)
83
+ end
84
+
85
+ def head(path, headers = {})
86
+ request(Net::HTTP::Head, path, headers)
87
+ end
88
+
89
+ def options_request(path, headers = {})
90
+ request(Net::HTTP::Options, path, headers)
91
+ end
92
+
93
+ private
94
+
95
+ attr_reader :monitor, :name, :options, :paths, :shell
96
+
97
+ def option(key)
98
+ options.fetch(key) { options.fetch(key.to_s) { yield } }
99
+ end
100
+
101
+ def request(request_class, path, headers)
102
+ start
103
+
104
+ uri = URI.join("http://#{connect_host}:#{port}", path)
105
+ # Pass `nil` as the proxy address so that a `http_proxy` environment
106
+ # variable never routes requests for the local server through a proxy.
107
+ Net::HTTP.start(uri.hostname, uri.port, nil, read_timeout: timeout) do |http|
108
+ http.request(request_class.new(uri, headers))
109
+ end
110
+ rescue SystemCallError, IOError, Timeout::Error => error
111
+ fail BuildError.new(<<~MSG)
112
+ #{name.inspect} could not reach its development server at #{origin}.
113
+
114
+ #{error.class}: #{error.message}
115
+
116
+ Its output is written to #{paths.log}.
117
+ MSG
118
+ end
119
+
120
+ # The address Rails connects to the development server on. `0.0.0.0` asks
121
+ # the server to listen on every interface, but is not an address to
122
+ # connect to, so requests go to the loopback interface instead.
123
+ def connect_host
124
+ if host == "0.0.0.0"
125
+ "127.0.0.1"
126
+ else
127
+ host
128
+ end
129
+ end
130
+
131
+ def listening?
132
+ Socket.tcp(connect_host, port, connect_timeout: CONNECT_TIMEOUT, &:close)
133
+
134
+ true
135
+ rescue SystemCallError, IOError
136
+ false
137
+ end
138
+
139
+ def wait_until_listening!
140
+ deadline = now + timeout
141
+
142
+ loop do
143
+ return true if listening?
144
+
145
+ unless shell.dev_server_running?
146
+ fail BuildError.new(<<~MSG)
147
+ #{name.inspect} failed to start its development server on #{origin}.
148
+
149
+ Its output is written to #{paths.log}.
150
+ MSG
151
+ end
152
+
153
+ if now >= deadline
154
+ fail BuildError.new(<<~MSG)
155
+ #{name.inspect} timed out after #{timeout.round} seconds waiting for
156
+ its development server to listen on #{origin}.
157
+
158
+ Its output is written to #{paths.log}.
159
+
160
+ Configure a longer timeout with:
161
+
162
+ EmberCli.configure do |config|
163
+ config.app #{name.to_sym.inspect}, dev_server: { timeout: 60 }
164
+ end
165
+
166
+ MSG
167
+ end
168
+
169
+ sleep POLL_INTERVAL
170
+ end
171
+ end
172
+
173
+ def now
174
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
175
+ end
176
+
177
+ def available_port
178
+ server = TCPServer.new(host, 0)
179
+
180
+ begin
181
+ server.addr[1]
182
+ ensure
183
+ server.close
184
+ end
185
+ end
186
+ end
187
+ end
@@ -0,0 +1,103 @@
1
+ require "rack"
2
+
3
+ module EmberCli
4
+ # Forwards requests made to an Ember application's mount point to its Vite
5
+ # development server.
6
+ #
7
+ # Assets referenced by the Ember application itself (rather than by its
8
+ # `index.html`) are resolved against the document's URL, so they are
9
+ # requested from Rails. Proxying them keeps those references working the
10
+ # same way they do when the application is served out of `dist`.
11
+ class DevServerProxy
12
+ ALLOWED_VERBS = %w[GET HEAD OPTIONS].freeze
13
+ # Hop-by-hop headers, plus the `Content-Encoding` header describing a body
14
+ # that `Net::HTTP` may have decoded already.
15
+ SKIPPED_HEADERS = %w[
16
+ connection
17
+ content-encoding
18
+ keep-alive
19
+ proxy-authenticate
20
+ proxy-authorization
21
+ te
22
+ trailer
23
+ transfer-encoding
24
+ upgrade
25
+ ].freeze
26
+
27
+ def initialize(app)
28
+ @app = app
29
+ end
30
+
31
+ def call(env)
32
+ request = Rack::Request.new(env)
33
+
34
+ unless ALLOWED_VERBS.include?(request.request_method)
35
+ return method_not_allowed
36
+ end
37
+
38
+ rack_response(proxy(request))
39
+ end
40
+
41
+ private
42
+
43
+ attr_reader :app
44
+
45
+ delegate :dev_server, to: :app
46
+
47
+ def proxy(request)
48
+ path = path_for(request)
49
+ headers = { "accept-encoding" => "identity" }
50
+
51
+ case request.request_method
52
+ when "HEAD" then dev_server.head(path, headers)
53
+ when "OPTIONS" then dev_server.options_request(path, headers)
54
+ else dev_server.get(path, headers)
55
+ end
56
+ end
57
+
58
+ def path_for(request)
59
+ path = request.path_info.presence || "/"
60
+ query = request.query_string
61
+
62
+ if query.present?
63
+ "#{path}?#{query}"
64
+ else
65
+ path
66
+ end
67
+ end
68
+
69
+ def rack_response(response)
70
+ body = response.body.to_s
71
+
72
+ [response.code.to_i, response_headers(response, body), [body]]
73
+ end
74
+
75
+ def response_headers(response, body)
76
+ headers = {}
77
+
78
+ response.each_header do |name, value|
79
+ unless SKIPPED_HEADERS.include?(name.downcase)
80
+ headers[name.downcase] = value
81
+ end
82
+ end
83
+
84
+ headers["content-length"] ||= body.bytesize.to_s
85
+
86
+ headers
87
+ end
88
+
89
+ def method_not_allowed
90
+ body = "Method Not Allowed"
91
+
92
+ [
93
+ 405,
94
+ {
95
+ "allow" => ALLOWED_VERBS.join(", "),
96
+ "content-type" => "text/plain",
97
+ "content-length" => body.bytesize.to_s,
98
+ },
99
+ [body],
100
+ ]
101
+ end
102
+ end
103
+ end
@@ -39,6 +39,13 @@ module EmberCli
39
39
  root.join("bower.json")
40
40
  end
41
41
 
42
+ # Apps generated with the Vite-based blueprint (`ember-cli >= 6.8`)
43
+ # ship a Vite config file at their root.
44
+ def vite?
45
+ %w[vite.config.mjs vite.config.js vite.config.ts].
46
+ any? { |config| root.join(config).exist? }
47
+ end
48
+
42
49
  def ember
43
50
  @ember ||= begin
44
51
  root.join("node_modules", "ember-cli", "bin", "ember").tap do |path|
@@ -57,6 +64,24 @@ module EmberCli
57
64
  end
58
65
  end
59
66
 
67
+ def vite
68
+ @vite ||= begin
69
+ root.join("node_modules", ".bin", "vite").tap do |path|
70
+ unless path.executable?
71
+ fail DependencyError.new <<-MSG.strip_heredoc
72
+ No `vite` executable found for `#{app_name}`.
73
+
74
+ Install it:
75
+
76
+ $ cd #{root}
77
+ $ #{package_manager} install
78
+
79
+ MSG
80
+ end
81
+ end
82
+ end
83
+ end
84
+
60
85
  def lockfile
61
86
  @lockfile ||= tmp.join("build.lock")
62
87
  end
@@ -20,16 +20,44 @@ module EmberCli
20
20
  def build_and_watch
21
21
  unless running?
22
22
  lock_buildfile
23
- self.pid = spawn ember.build(watch: true)
23
+ self.pid = spawn(
24
+ ember.build(watch: true),
25
+ err: paths.build_error_file.to_s,
26
+ )
24
27
  detach
25
28
  end
26
29
  end
27
30
 
31
+ def start_dev_server(host:, port:)
32
+ unless dev_server_running?
33
+ # Run the development server in its own process group so that the
34
+ # whole tree can be signaled when Rails exits.
35
+ self.dev_server_pid = spawn(
36
+ ember.dev_server(host: host, port: port),
37
+ out: [paths.log.to_s, "a"],
38
+ err: [:child, :out],
39
+ pgroup: true,
40
+ )
41
+ Process.detach(dev_server_pid)
42
+ end
43
+
44
+ dev_server_pid
45
+ end
46
+
47
+ def dev_server_running?
48
+ process_running?(dev_server_pid)
49
+ end
50
+
28
51
  def stop
29
52
  if pid.present?
30
- Process.kill(:INT, pid)
53
+ signal(pid)
31
54
  self.pid = nil
32
55
  end
56
+
57
+ if dev_server_pid.present?
58
+ signal(-dev_server_pid)
59
+ self.dev_server_pid = nil
60
+ end
33
61
  end
34
62
 
35
63
  def install
@@ -58,7 +86,7 @@ module EmberCli
58
86
 
59
87
  private
60
88
 
61
- attr_accessor :pid
89
+ attr_accessor :dev_server_pid, :pid
62
90
  attr_reader :ember, :env, :options, :paths
63
91
 
64
92
  delegate :run, :run!, to: :runner
@@ -80,15 +108,21 @@ module EmberCli
80
108
  ].select(&:exist?)
81
109
  end
82
110
 
83
- def spawn(command)
111
+ def spawn(command, **redirects)
84
112
  Kernel.spawn(
85
113
  env,
86
114
  command,
87
115
  chdir: paths.root.to_s,
88
- err: paths.build_error_file.to_s,
116
+ **redirects,
89
117
  ) || exit(1)
90
118
  end
91
119
 
120
+ def signal(process_id)
121
+ Process.kill(:INT, process_id)
122
+ rescue Errno::ESRCH, Errno::EPERM
123
+ nil
124
+ end
125
+
92
126
  def runner
93
127
  Runner.new(
94
128
  options: { chdir: paths.root.to_s },
@@ -99,7 +133,11 @@ module EmberCli
99
133
  end
100
134
 
101
135
  def running?
102
- pid.present? && Process.getpgid(pid)
136
+ process_running?(pid)
137
+ end
138
+
139
+ def process_running?(process_id)
140
+ process_id.present? && !!Process.getpgid(process_id)
103
141
  rescue Errno::ESRCH
104
142
  false
105
143
  end
@@ -1,3 +1,3 @@
1
1
  module EmberCli
2
- VERSION = "0.12.3".freeze
2
+ VERSION = "0.13.0".freeze
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ember-cli-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.12.3
4
+ version: 0.13.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Pavel Pravosud
@@ -18,7 +18,7 @@ dependencies:
18
18
  requirements:
19
19
  - - ">="
20
20
  - !ruby/object:Gem::Version
21
- version: 0.6.2
21
+ version: 0.9.0
22
22
  - - "<"
23
23
  - !ruby/object:Gem::Version
24
24
  version: '1.0'
@@ -28,7 +28,7 @@ dependencies:
28
28
  requirements:
29
29
  - - ">="
30
30
  - !ruby/object:Gem::Version
31
- version: 0.6.2
31
+ version: 0.9.0
32
32
  - - "<"
33
33
  - !ruby/object:Gem::Version
34
34
  version: '1.0'
@@ -117,7 +117,7 @@ dependencies:
117
117
  version: 3.6.0
118
118
  - - "<"
119
119
  - !ruby/object:Gem::Version
120
- version: '7.0'
120
+ version: '9.0'
121
121
  type: :development
122
122
  prerelease: false
123
123
  version_requirements: !ruby/object:Gem::Requirement
@@ -127,7 +127,7 @@ dependencies:
127
127
  version: 3.6.0
128
128
  - - "<"
129
129
  - !ruby/object:Gem::Version
130
- version: '7.0'
130
+ version: '9.0'
131
131
  - !ruby/object:Gem::Dependency
132
132
  name: capybara-selenium
133
133
  requirement: !ruby/object:Gem::Requirement
@@ -177,7 +177,10 @@ files:
177
177
  - lib/ember_cli/build_monitor.rb
178
178
  - lib/ember_cli/command.rb
179
179
  - lib/ember_cli/configuration.rb
180
+ - lib/ember_cli/deploy/dev_server.rb
180
181
  - lib/ember_cli/deploy/file.rb
182
+ - lib/ember_cli/dev_server.rb
183
+ - lib/ember_cli/dev_server_proxy.rb
181
184
  - lib/ember_cli/ember_constraint.rb
182
185
  - lib/ember_cli/engine.rb
183
186
  - lib/ember_cli/errors.rb
@@ -214,7 +217,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
214
217
  - !ruby/object:Gem::Version
215
218
  version: '0'
216
219
  requirements: []
217
- rubygems_version: 4.0.6
220
+ rubygems_version: 4.0.20
218
221
  specification_version: 4
219
222
  summary: Integration between Ember CLI and Rails
220
223
  test_files: []