ruact 0.0.4 → 0.0.6
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 +4 -4
- data/.github/workflows/ci.yml +54 -2
- data/.rubocop_todo.yml +3 -115
- data/CHANGELOG.md +68 -17
- data/bench/server_functions_dispatch_bench.rb +109 -142
- data/bench/server_functions_dispatch_bench.results.md +29 -0
- data/docs/internal/decisions/server-functions-api.md +402 -0
- data/lib/generators/ruact/install/install_generator.rb +310 -25
- data/lib/generators/ruact/install/templates/Procfile.dev.tt +2 -0
- data/lib/generators/ruact/install/templates/dev.tt +16 -0
- data/lib/generators/ruact/install/templates/package.json.tt +17 -0
- data/lib/generators/ruact/install/templates/vite.config.js.tt +3 -1
- data/lib/generators/ruact/scaffold/scaffold_attribute.rb +114 -0
- data/lib/generators/ruact/scaffold/scaffold_form_helpers.rb +114 -0
- data/lib/generators/ruact/scaffold/scaffold_generator.rb +491 -0
- data/lib/generators/ruact/scaffold/scaffold_shadcn_preflight.rb +229 -0
- data/lib/generators/ruact/scaffold/templates/components/DeleteDialog.tsx.tt +104 -0
- data/lib/generators/ruact/scaffold/templates/components/Form.tsx.tt +212 -0
- data/lib/generators/ruact/scaffold/templates/components/List.tsx.tt +338 -0
- data/lib/generators/ruact/scaffold/templates/components/agnostic/DeleteDialog.tsx.tt +92 -0
- data/lib/generators/ruact/scaffold/templates/components/agnostic/Form.tsx.tt +174 -0
- data/lib/generators/ruact/scaffold/templates/components/agnostic/List.tsx.tt +253 -0
- data/lib/generators/ruact/scaffold/templates/controller.rb.tt +130 -0
- data/lib/generators/ruact/scaffold/templates/queries/application_query.rb.tt +13 -0
- data/lib/generators/ruact/scaffold/templates/queries/query.rb.tt +59 -0
- data/lib/generators/ruact/scaffold/templates/request_spec.rb.tt +77 -0
- data/lib/generators/ruact/scaffold/templates/views/edit.html.erb.tt +7 -0
- data/lib/generators/ruact/scaffold/templates/views/index.html.erb.tt +6 -0
- data/lib/generators/ruact/scaffold/templates/views/new.html.erb.tt +5 -0
- data/lib/generators/ruact/scaffold/templates/views/show.html.erb.tt +16 -0
- data/lib/ruact/client_manifest.rb +37 -36
- data/lib/ruact/component_contract.rb +115 -0
- data/lib/ruact/configuration.rb +80 -28
- data/lib/ruact/controller.rb +69 -421
- data/lib/ruact/doctor.rb +133 -4
- data/lib/ruact/erb_preprocessor.rb +65 -12
- data/lib/ruact/erb_preprocessor_hook.rb +4 -1
- data/lib/ruact/errors.rb +30 -44
- data/lib/ruact/html_converter.rb +22 -1
- data/lib/ruact/railtie.rb +56 -200
- data/lib/ruact/server.rb +28 -6
- data/lib/ruact/server_functions/codegen.rb +49 -188
- data/lib/ruact/server_functions/codegen_v2.rb +23 -6
- data/lib/ruact/server_functions/codegen_v2_query_params.rb +140 -0
- data/lib/ruact/server_functions/error_payload.rb +1 -1
- data/lib/ruact/server_functions/error_rendering.rb +22 -29
- data/lib/ruact/server_functions/name_bridge.rb +14 -16
- data/lib/ruact/server_functions/query_source.rb +35 -8
- data/lib/ruact/server_functions/route_source.rb +3 -4
- data/lib/ruact/server_functions/snapshot.rb +12 -139
- data/lib/ruact/server_functions/validation_errors.rb +70 -0
- data/lib/ruact/server_functions.rb +21 -25
- data/lib/ruact/signed_references.rb +162 -0
- data/lib/ruact/string_distance.rb +72 -0
- data/lib/ruact/validation_errors_collector.rb +139 -0
- data/lib/ruact/version.rb +1 -1
- data/lib/ruact/view_helper.rb +102 -0
- data/lib/ruact.rb +19 -19
- data/lib/tasks/ruact.rake +10 -53
- data/spec/fixtures/story_7_9_views/controller_request_spec_support/errors_demo/new.html.erb +3 -0
- data/spec/ruact/client_manifest_spec.rb +36 -0
- data/spec/ruact/component_contract_spec.rb +119 -0
- data/spec/ruact/configuration_spec.rb +51 -34
- data/spec/ruact/controller_request_spec.rb +264 -0
- data/spec/ruact/controller_spec.rb +63 -326
- data/spec/ruact/doctor_spec.rb +201 -0
- data/spec/ruact/erb_preprocessor_hook_spec.rb +4 -1
- data/spec/ruact/erb_preprocessor_spec.rb +127 -0
- data/spec/ruact/errors_spec.rb +0 -45
- data/spec/ruact/html_converter_spec.rb +50 -0
- data/spec/ruact/install_generator_spec.rb +591 -4
- data/spec/ruact/query_request_spec.rb +109 -1
- data/spec/ruact/scaffold_generator_spec.rb +1835 -0
- data/spec/ruact/server_bucket_request_spec.rb +142 -0
- data/spec/ruact/server_function_name_spec.rb +1 -1
- data/spec/ruact/server_functions/codegen_spec.rb +158 -269
- data/spec/ruact/server_functions/name_bridge_spec.rb +9 -9
- data/spec/ruact/server_functions/query_source_spec.rb +51 -0
- data/spec/ruact/server_functions/railtie_integration_spec.rb +71 -268
- data/spec/ruact/server_functions/rake_spec.rb +29 -29
- data/spec/ruact/server_functions/snapshot_spec.rb +53 -213
- data/spec/ruact/server_rescue_request_spec.rb +6 -6
- data/spec/ruact/server_spec.rb +8 -9
- data/spec/ruact/signed_references_spec.rb +164 -0
- data/spec/ruact/string_distance_spec.rb +38 -0
- data/spec/ruact/validation_errors_spec.rb +116 -0
- data/spec/ruact/view_helper_spec.rb +79 -0
- data/spec/spec_helper.rb +0 -5
- data/vendor/javascript/ruact-server-functions-runtime/index.d.ts +44 -47
- data/vendor/javascript/ruact-server-functions-runtime/index.js +151 -107
- data/vendor/javascript/ruact-server-functions-runtime/index.test.mjs +72 -75
- data/vendor/javascript/ruact-server-functions-runtime/package.json +2 -2
- data/vendor/javascript/ruact-server-functions-runtime/usequery.test.mjs +187 -0
- data/vendor/javascript/vite-plugin-ruact/bootstrap.test.mjs +96 -0
- data/vendor/javascript/vite-plugin-ruact/index.js +353 -7
- data/vendor/javascript/vite-plugin-ruact/manifest-contract.test.mjs +182 -0
- data/vendor/javascript/vite-plugin-ruact/package-lock.json +16 -0
- data/vendor/javascript/vite-plugin-ruact/package.json +3 -1
- data/vendor/javascript/vite-plugin-ruact/registry.test.mjs +199 -0
- data/vendor/javascript/vite-plugin-ruact/runtime/bootstrap.jsx +68 -0
- data/vendor/javascript/vite-plugin-ruact/runtime/flight-client.js +235 -0
- data/vendor/javascript/vite-plugin-ruact/runtime/ruact-router.js +473 -0
- data/vendor/javascript/vite-plugin-ruact/server-functions-codegen.mjs +114 -139
- data/vendor/javascript/vite-plugin-ruact/server-functions-codegen.test.mjs +199 -262
- data/vendor/javascript/vite-plugin-ruact/tsconfig.json +18 -0
- data/vendor/javascript/vite-plugin-ruact/tsconfig.scaffold-agnostic.json +18 -0
- data/vendor/javascript/vite-plugin-ruact/tsconfig.scaffold.json +20 -0
- data/vendor/javascript/vite-plugin-ruact/type-tests/emitted-module.test-d.ts +23 -0
- data/vendor/javascript/vite-plugin-ruact/type-tests/scaffold/PostDeleteDialog.tsx +90 -0
- data/vendor/javascript/vite-plugin-ruact/type-tests/scaffold/PostForm.tsx +238 -0
- data/vendor/javascript/vite-plugin-ruact/type-tests/scaffold/PostList.tsx +339 -0
- data/vendor/javascript/vite-plugin-ruact/type-tests/scaffold/agnostic/PostDeleteDialog.tsx +85 -0
- data/vendor/javascript/vite-plugin-ruact/type-tests/scaffold/agnostic/PostForm.tsx +216 -0
- data/vendor/javascript/vite-plugin-ruact/type-tests/scaffold/agnostic/PostList.tsx +269 -0
- data/vendor/javascript/vite-plugin-ruact/type-tests/scaffold/agnostic/ambient.d.ts +78 -0
- data/vendor/javascript/vite-plugin-ruact/type-tests/scaffold/ambient.d.ts +166 -0
- data/vendor/javascript/vite-plugin-ruact/type-tests/typed-query.test-d.ts +48 -0
- data/vendor/javascript/vite-plugin-ruact/type-tests/usequery.test-d.ts +26 -0
- metadata +55 -15
- data/lib/generators/ruact/install/templates/application.jsx.tt +0 -51
- data/lib/ruact/server_action.rb +0 -131
- data/lib/ruact/server_functions/endpoint_controller.rb +0 -237
- data/lib/ruact/server_functions/registry.rb +0 -148
- data/lib/ruact/server_functions/registry_entry.rb +0 -26
- data/lib/ruact/server_functions/standalone_context.rb +0 -103
- data/lib/ruact/server_functions/standalone_dispatcher.rb +0 -178
- data/spec/ruact/server_functions/csrf_request_spec.rb +0 -380
- data/spec/ruact/server_functions/dispatch_request_spec.rb +0 -819
- data/spec/ruact/server_functions/registry_spec.rb +0 -199
- data/spec/ruact/server_functions/standalone_action_spec.rb +0 -224
- data/spec/ruact/server_functions/standalone_context_spec.rb +0 -142
- data/spec/ruact/server_functions/standalone_dispatcher_spec.rb +0 -273
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
require "spec_helper"
|
|
4
4
|
require "tmpdir"
|
|
5
5
|
require "fileutils"
|
|
6
|
+
require "json"
|
|
6
7
|
require "ruact"
|
|
7
8
|
|
|
8
9
|
RSpec.describe Ruact do # rubocop:disable RSpec/SpecFilePathFormat
|
|
@@ -215,8 +216,6 @@ RSpec.describe Ruact do # rubocop:disable RSpec/SpecFilePathFormat
|
|
|
215
216
|
let(:gitignore_entries) do
|
|
216
217
|
[
|
|
217
218
|
"app/javascript/.ruact/server-functions.ts",
|
|
218
|
-
# Story 9.3 — route-driven (v2) parallel inspection target.
|
|
219
|
-
"app/javascript/.ruact/server-functions.next.ts",
|
|
220
219
|
"tmp/cache/ruact/"
|
|
221
220
|
]
|
|
222
221
|
end
|
|
@@ -263,8 +262,10 @@ RSpec.describe Ruact do # rubocop:disable RSpec/SpecFilePathFormat
|
|
|
263
262
|
append_gitignore_entries(tmpdir)
|
|
264
263
|
content = read_file(".gitignore")
|
|
265
264
|
expect(content).to include("app/javascript/.ruact/server-functions.ts")
|
|
266
|
-
expect(content).to include("app/javascript/.ruact/server-functions.next.ts")
|
|
267
265
|
expect(content).to include("tmp/cache/ruact/")
|
|
266
|
+
# Story 9.9 — the v1 parallel `.next` target was demolished; the
|
|
267
|
+
# generator must no longer scaffold its gitignore entry.
|
|
268
|
+
expect(content).not_to include("server-functions.next.ts")
|
|
268
269
|
end
|
|
269
270
|
|
|
270
271
|
it "is idempotent — running twice does not duplicate entries (Story 8.0a — pitfall #5)" do
|
|
@@ -279,7 +280,7 @@ RSpec.describe Ruact do # rubocop:disable RSpec/SpecFilePathFormat
|
|
|
279
280
|
|
|
280
281
|
it "does not write to .gitignore when both entries already exist" do
|
|
281
282
|
write_file(".gitignore", "/tmp\napp/javascript/.ruact/server-functions.ts\n" \
|
|
282
|
-
"
|
|
283
|
+
"tmp/cache/ruact/\n")
|
|
283
284
|
result = append_gitignore_entries(tmpdir)
|
|
284
285
|
expect(result).to eq(:already_present)
|
|
285
286
|
end
|
|
@@ -301,5 +302,591 @@ RSpec.describe Ruact do # rubocop:disable RSpec/SpecFilePathFormat
|
|
|
301
302
|
expect(content).to include("app/javascript/.ruact/server-functions.ts")
|
|
302
303
|
end
|
|
303
304
|
end
|
|
305
|
+
|
|
306
|
+
# REGRESSION (Sprint Change Proposal 2026-06-16 §4.5): a fresh `rails new`
|
|
307
|
+
# crashed `rails generate ruact:install` on Rails 8.1 / Thor because the
|
|
308
|
+
# generator called `destination_root.join(...)` — Thor returns
|
|
309
|
+
# `destination_root` as a String (`File.expand_path`), which has no
|
|
310
|
+
# path-style `#join`, so the installer raised `NoMethodError` before writing
|
|
311
|
+
# a single file. The fix wraps every site in `Pathname(destination_root)`.
|
|
312
|
+
#
|
|
313
|
+
# The "generator action helpers" tests above reimplement the file logic with
|
|
314
|
+
# `File.join`, so they never run Thor's path handling and masked the bug.
|
|
315
|
+
# These tests invoke the REAL generator against a String destination_root, so
|
|
316
|
+
# a revert of the Pathname fix fails loudly here.
|
|
317
|
+
describe "real generator invocation against a String destination_root" do
|
|
318
|
+
require "stringio"
|
|
319
|
+
require "generators/ruact/install/install_generator"
|
|
320
|
+
|
|
321
|
+
let(:app_root) { Dir.mktmpdir("ruact_install_real") }
|
|
322
|
+
|
|
323
|
+
after { FileUtils.rm_rf(app_root) }
|
|
324
|
+
|
|
325
|
+
def build_generator(root)
|
|
326
|
+
Ruact::Generators::InstallGenerator.new([], {}, destination_root: root)
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
def silently
|
|
330
|
+
original = $stdout
|
|
331
|
+
$stdout = StringIO.new
|
|
332
|
+
yield
|
|
333
|
+
ensure
|
|
334
|
+
$stdout = original
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
before do
|
|
338
|
+
FileUtils.mkdir_p(File.join(app_root, "app/controllers"))
|
|
339
|
+
File.write(File.join(app_root, "app/controllers/application_controller.rb"),
|
|
340
|
+
"class ApplicationController < ActionController::Base\nend\n")
|
|
341
|
+
FileUtils.mkdir_p(File.join(app_root, "app/views/layouts"))
|
|
342
|
+
File.write(File.join(app_root, "app/views/layouts/application.html.erb"),
|
|
343
|
+
"<!DOCTYPE html>\n<html>\n <body>\n <%= yield %>\n </body>\n</html>\n")
|
|
344
|
+
File.write(File.join(app_root, ".gitignore"), "/log/*\n")
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
it "hands the generator a String destination_root (the exact crashing condition)" do
|
|
348
|
+
expect(build_generator(app_root).destination_root).to be_a(String)
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
it "runs every path-touching action without raising and writes the files", :aggregate_failures do
|
|
352
|
+
gen = build_generator(app_root)
|
|
353
|
+
|
|
354
|
+
silently do
|
|
355
|
+
expect do
|
|
356
|
+
gen.inject_controller_concern
|
|
357
|
+
gen.inject_layout_shell
|
|
358
|
+
gen.create_components_directory
|
|
359
|
+
gen.create_server_functions_directory
|
|
360
|
+
gen.append_gitignore_entries
|
|
361
|
+
gen.create_vite_config
|
|
362
|
+
end.not_to raise_error
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
expect(File.read(File.join(app_root, "app/controllers/application_controller.rb")))
|
|
366
|
+
.to include("include Ruact::Controller")
|
|
367
|
+
expect(File.read(File.join(app_root, "app/views/layouts/application.html.erb")))
|
|
368
|
+
.to include('<div id="root"></div>')
|
|
369
|
+
expect(File).to exist(File.join(app_root, "app/javascript/components/.keep"))
|
|
370
|
+
expect(File).to exist(File.join(app_root, "app/javascript/.ruact/.gitkeep"))
|
|
371
|
+
expect(File.read(File.join(app_root, ".gitignore")))
|
|
372
|
+
.to include("app/javascript/.ruact/server-functions.ts")
|
|
373
|
+
expect(File).to exist(File.join(app_root, "vite.config.js"))
|
|
374
|
+
end
|
|
375
|
+
end
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
# Story 14.1 (FR101) — one-command install: `ruact:install` now runs
|
|
379
|
+
# `npm install` by default (behind a stubbable `run_npm_install` seam),
|
|
380
|
+
# skippable via `--skip-npm`, with an npm-on-PATH guard and outcome-aware
|
|
381
|
+
# post-install messaging. The shell-out is ALWAYS stubbed here — no real
|
|
382
|
+
# npm / network call is made in CI. Reuses the "real generator against a
|
|
383
|
+
# String destination_root" pattern (instantiate + call action methods).
|
|
384
|
+
describe "install generator — one-command npm install (Story 14.1 — FR101)", :story_14_1 do
|
|
385
|
+
require "stringio"
|
|
386
|
+
require "generators/ruact/install/install_generator"
|
|
387
|
+
|
|
388
|
+
let(:app_root) { Dir.mktmpdir("ruact_install_npm") }
|
|
389
|
+
|
|
390
|
+
after { FileUtils.rm_rf(app_root) }
|
|
391
|
+
|
|
392
|
+
def build_generator(root, opts = {})
|
|
393
|
+
Ruact::Generators::InstallGenerator.new([], opts, destination_root: root)
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
def silently
|
|
397
|
+
original = $stdout
|
|
398
|
+
$stdout = StringIO.new
|
|
399
|
+
yield
|
|
400
|
+
ensure
|
|
401
|
+
$stdout = original
|
|
402
|
+
end
|
|
403
|
+
|
|
404
|
+
# Captures and returns the generator's say/say_status stdout for text asserts.
|
|
405
|
+
def capture_stdout
|
|
406
|
+
original = $stdout
|
|
407
|
+
$stdout = StringIO.new
|
|
408
|
+
yield
|
|
409
|
+
$stdout.string
|
|
410
|
+
ensure
|
|
411
|
+
$stdout = original
|
|
412
|
+
end
|
|
413
|
+
|
|
414
|
+
describe "--skip-npm class option (AC#2)", :aggregate_failures do
|
|
415
|
+
it "is registered as a boolean class_option defaulting to false" do
|
|
416
|
+
option = Ruact::Generators::InstallGenerator.class_options[:skip_npm]
|
|
417
|
+
expect(option).not_to be_nil
|
|
418
|
+
expect(option.type).to eq(:boolean)
|
|
419
|
+
expect(option.default).to be false
|
|
420
|
+
end
|
|
421
|
+
end
|
|
422
|
+
|
|
423
|
+
describe "default run installs JS deps (AC#1, AC#6a)" do
|
|
424
|
+
it "invokes the run_npm_install seam exactly once" do
|
|
425
|
+
gen = build_generator(app_root)
|
|
426
|
+
allow(gen).to receive_messages(npm_on_path?: true, run_npm_install: true)
|
|
427
|
+
|
|
428
|
+
silently { gen.install_javascript_dependencies }
|
|
429
|
+
|
|
430
|
+
expect(gen).to have_received(:run_npm_install).once
|
|
431
|
+
expect(gen.instance_variable_get(:@npm_outcome)).to eq(:installed)
|
|
432
|
+
end
|
|
433
|
+
|
|
434
|
+
it "runs the npm step AFTER the file-producing actions and BEFORE show_post_install_message",
|
|
435
|
+
:aggregate_failures do
|
|
436
|
+
# Thor runs public action methods in SOURCE definition order, so assert
|
|
437
|
+
# on source line numbers (public_instance_methods order is not
|
|
438
|
+
# guaranteed to match definition order). Story 14.2 removed
|
|
439
|
+
# create_javascript_entry; create_vite_config is the last file-writing
|
|
440
|
+
# action before the npm step.
|
|
441
|
+
klass = Ruact::Generators::InstallGenerator
|
|
442
|
+
line = ->(name) { klass.instance_method(name).source_location.last }
|
|
443
|
+
expect(line.call(:create_vite_config)).to be < line.call(:install_javascript_dependencies)
|
|
444
|
+
expect(line.call(:install_javascript_dependencies)).to be < line.call(:show_post_install_message)
|
|
445
|
+
end
|
|
446
|
+
end
|
|
447
|
+
|
|
448
|
+
describe "run_npm_install seam (AC#6a)", :aggregate_failures do
|
|
449
|
+
it "shells out `npm install` inside the destination_root" do
|
|
450
|
+
gen = build_generator(app_root)
|
|
451
|
+
allow(gen).to receive(:inside).and_yield
|
|
452
|
+
allow(gen).to receive(:run)
|
|
453
|
+
|
|
454
|
+
silently { gen.send(:run_npm_install) }
|
|
455
|
+
|
|
456
|
+
expect(gen).to have_received(:inside).with(gen.destination_root)
|
|
457
|
+
expect(gen).to have_received(:run).with("npm install")
|
|
458
|
+
end
|
|
459
|
+
end
|
|
460
|
+
|
|
461
|
+
describe "--skip-npm opts out (AC#2, AC#6b)" do
|
|
462
|
+
it "does not invoke the run_npm_install seam and emits a skip notice" do
|
|
463
|
+
gen = build_generator(app_root, { skip_npm: true })
|
|
464
|
+
allow(gen).to receive(:run_npm_install)
|
|
465
|
+
|
|
466
|
+
output = capture_stdout { gen.install_javascript_dependencies }
|
|
467
|
+
|
|
468
|
+
expect(gen).not_to have_received(:run_npm_install)
|
|
469
|
+
expect(output).to match(/skip/i)
|
|
470
|
+
end
|
|
471
|
+
end
|
|
472
|
+
|
|
473
|
+
describe "--pretend dry run", :aggregate_failures do
|
|
474
|
+
# Thor's `run` returns nil under --pretend (the command is previewed, not
|
|
475
|
+
# executed) — that must NOT be misread as an npm failure.
|
|
476
|
+
it "treats a pretend run as a no-op, not an npm failure" do
|
|
477
|
+
gen = build_generator(app_root, { pretend: true })
|
|
478
|
+
allow(gen).to receive_messages(npm_on_path?: true, run_npm_install: nil)
|
|
479
|
+
|
|
480
|
+
run_output = capture_stdout { gen.install_javascript_dependencies }
|
|
481
|
+
|
|
482
|
+
expect(gen.instance_variable_get(:@npm_outcome)).to eq(:pretend)
|
|
483
|
+
expect(run_output).not_to match(/did not complete|reported a failure/i)
|
|
484
|
+
end
|
|
485
|
+
end
|
|
486
|
+
|
|
487
|
+
describe "npm not on PATH (AC#5)", :aggregate_failures do
|
|
488
|
+
it "does not raise, skips the seam, and prints an actionable message naming --skip-npm" do
|
|
489
|
+
gen = build_generator(app_root)
|
|
490
|
+
allow(gen).to receive(:npm_on_path?).and_return(false)
|
|
491
|
+
allow(gen).to receive(:run_npm_install)
|
|
492
|
+
|
|
493
|
+
output = nil
|
|
494
|
+
expect { output = capture_stdout { gen.install_javascript_dependencies } }
|
|
495
|
+
.not_to raise_error
|
|
496
|
+
expect(gen).not_to have_received(:run_npm_install)
|
|
497
|
+
expect(output).to include("--skip-npm")
|
|
498
|
+
expect(output).to match(/npm/i)
|
|
499
|
+
expect(output).to match(/node/i)
|
|
500
|
+
end
|
|
501
|
+
end
|
|
502
|
+
|
|
503
|
+
describe "post-install message reflects the outcome (AC#3)", :aggregate_failures do
|
|
504
|
+
it "states deps are installed and next step is bin/dev when npm ran" do
|
|
505
|
+
gen = build_generator(app_root)
|
|
506
|
+
allow(gen).to receive_messages(npm_on_path?: true, run_npm_install: true)
|
|
507
|
+
silently { gen.install_javascript_dependencies }
|
|
508
|
+
|
|
509
|
+
output = capture_stdout { gen.show_post_install_message }
|
|
510
|
+
expect(output).to include("are installed")
|
|
511
|
+
expect(output).not_to include("not yet installed")
|
|
512
|
+
expect(output).to include("bin/dev")
|
|
513
|
+
end
|
|
514
|
+
|
|
515
|
+
# AC#3 — Thor's `run` returns false on a non-zero exit (generators do not
|
|
516
|
+
# exit_on_failure?), so a FAILED `npm install` must NOT report success.
|
|
517
|
+
it "does NOT claim deps are installed when npm install fails (AC#3)" do
|
|
518
|
+
gen = build_generator(app_root)
|
|
519
|
+
allow(gen).to receive_messages(npm_on_path?: true, run_npm_install: false)
|
|
520
|
+
|
|
521
|
+
run_output = capture_stdout { gen.install_javascript_dependencies }
|
|
522
|
+
message = capture_stdout { gen.show_post_install_message }
|
|
523
|
+
|
|
524
|
+
expect(gen.instance_variable_get(:@npm_outcome)).to eq(:failed)
|
|
525
|
+
expect(run_output).to match(/did not complete|failure/i)
|
|
526
|
+
expect(message).not_to include("are installed")
|
|
527
|
+
expect(message).to include("not yet installed")
|
|
528
|
+
expect(message).to include("bin/dev")
|
|
529
|
+
end
|
|
530
|
+
|
|
531
|
+
it "tells the developer to install manually then bin/dev when skipped" do
|
|
532
|
+
gen = build_generator(app_root, { skip_npm: true })
|
|
533
|
+
silently { gen.install_javascript_dependencies }
|
|
534
|
+
|
|
535
|
+
output = capture_stdout { gen.show_post_install_message }
|
|
536
|
+
expect(output).to include("not yet installed")
|
|
537
|
+
expect(output).to include("npm install")
|
|
538
|
+
expect(output).to include("bin/dev")
|
|
539
|
+
end
|
|
540
|
+
end
|
|
541
|
+
end
|
|
542
|
+
|
|
543
|
+
# Story 14.2 (FR104) — the generator no longer leaks ruact plumbing into the
|
|
544
|
+
# user's tree. After a fresh install, `app/javascript/` holds only the user's
|
|
545
|
+
# `components/` (+ the gitignored typed `.ruact/server-functions.ts`); the
|
|
546
|
+
# bootstrap entry is the virtual module `virtual:ruact/bootstrap`, and
|
|
547
|
+
# `flight-client.js` / `ruact-router.js` live inside the gem.
|
|
548
|
+
describe "install generator — hidden plumbing, no app/javascript leak (Story 14.2 — FR104)", :story_14_2 do
|
|
549
|
+
require "stringio"
|
|
550
|
+
require "generators/ruact/install/install_generator"
|
|
551
|
+
|
|
552
|
+
let(:app_root) { Dir.mktmpdir("ruact_install_1402") }
|
|
553
|
+
|
|
554
|
+
after { FileUtils.rm_rf(app_root) }
|
|
555
|
+
|
|
556
|
+
def build_generator(root, opts = {})
|
|
557
|
+
Ruact::Generators::InstallGenerator.new([], opts, destination_root: root)
|
|
558
|
+
end
|
|
559
|
+
|
|
560
|
+
def silently
|
|
561
|
+
original = $stdout
|
|
562
|
+
$stdout = StringIO.new
|
|
563
|
+
yield
|
|
564
|
+
ensure
|
|
565
|
+
$stdout = original
|
|
566
|
+
end
|
|
567
|
+
|
|
568
|
+
before do
|
|
569
|
+
FileUtils.mkdir_p(File.join(app_root, "app/controllers"))
|
|
570
|
+
File.write(File.join(app_root, "app/controllers/application_controller.rb"),
|
|
571
|
+
"class ApplicationController < ActionController::Base\nend\n")
|
|
572
|
+
FileUtils.mkdir_p(File.join(app_root, "app/views/layouts"))
|
|
573
|
+
File.write(File.join(app_root, "app/views/layouts/application.html.erb"),
|
|
574
|
+
"<!DOCTYPE html>\n<html>\n <body>\n <%= yield %>\n </body>\n</html>\n")
|
|
575
|
+
File.write(File.join(app_root, ".gitignore"), "/log/*\n")
|
|
576
|
+
end
|
|
577
|
+
|
|
578
|
+
it "does NOT define a create_javascript_entry action (no application.jsx writer)" do
|
|
579
|
+
expect(Ruact::Generators::InstallGenerator.instance_methods).not_to include(:create_javascript_entry)
|
|
580
|
+
end
|
|
581
|
+
|
|
582
|
+
it "no longer ships the application.jsx template (its source moved into the gem runtime)" do
|
|
583
|
+
template = File.expand_path(
|
|
584
|
+
"../../lib/generators/ruact/install/templates/application.jsx.tt", __dir__
|
|
585
|
+
)
|
|
586
|
+
expect(File).not_to exist(template)
|
|
587
|
+
end
|
|
588
|
+
|
|
589
|
+
it "writes no application.jsx / flight-client.js / ruact-router.js into app/javascript", :aggregate_failures do
|
|
590
|
+
gen = build_generator(app_root)
|
|
591
|
+
silently do
|
|
592
|
+
gen.create_components_directory
|
|
593
|
+
gen.create_server_functions_directory
|
|
594
|
+
gen.append_gitignore_entries
|
|
595
|
+
gen.create_vite_config
|
|
596
|
+
end
|
|
597
|
+
|
|
598
|
+
expect(File).not_to exist(File.join(app_root, "app/javascript/application.jsx"))
|
|
599
|
+
expect(File).not_to exist(File.join(app_root, "app/javascript/flight-client.js"))
|
|
600
|
+
expect(File).not_to exist(File.join(app_root, "app/javascript/ruact-router.js"))
|
|
601
|
+
# The user's components dir + the typed registry scaffold still exist.
|
|
602
|
+
expect(File).to exist(File.join(app_root, "app/javascript/components/.keep"))
|
|
603
|
+
expect(File).to exist(File.join(app_root, "app/javascript/.ruact/.gitkeep"))
|
|
604
|
+
end
|
|
605
|
+
|
|
606
|
+
it "keeps the .ruact/server-functions.ts gitignore entry unchanged (server-functions.ts stays)" do
|
|
607
|
+
gen = build_generator(app_root)
|
|
608
|
+
silently { gen.append_gitignore_entries }
|
|
609
|
+
expect(File.read(File.join(app_root, ".gitignore")))
|
|
610
|
+
.to include("app/javascript/.ruact/server-functions.ts")
|
|
611
|
+
end
|
|
612
|
+
|
|
613
|
+
describe "migration advisory for an earlier-layout app (AC7 — no half-wired state)" do
|
|
614
|
+
def capture_stdout
|
|
615
|
+
original = $stdout
|
|
616
|
+
$stdout = StringIO.new
|
|
617
|
+
yield
|
|
618
|
+
$stdout.string
|
|
619
|
+
ensure
|
|
620
|
+
$stdout = original
|
|
621
|
+
end
|
|
622
|
+
|
|
623
|
+
it "prints the exact delete steps + virtual entry when stale plumbing files exist", :aggregate_failures do
|
|
624
|
+
%w[application.jsx flight-client.js ruact-router.js].each do |f|
|
|
625
|
+
FileUtils.mkdir_p(File.join(app_root, "app/javascript"))
|
|
626
|
+
File.write(File.join(app_root, "app/javascript", f), "// stale\n")
|
|
627
|
+
end
|
|
628
|
+
gen = build_generator(app_root)
|
|
629
|
+
output = capture_stdout { gen.advise_plumbing_migration }
|
|
630
|
+
|
|
631
|
+
expect(output).to match(/earlier ruact layout detected/i)
|
|
632
|
+
expect(output).to include("delete app/javascript/application.jsx")
|
|
633
|
+
expect(output).to include("delete app/javascript/flight-client.js")
|
|
634
|
+
expect(output).to include("delete app/javascript/ruact-router.js")
|
|
635
|
+
expect(output).to include(described_class.bootstrap_virtual_id)
|
|
636
|
+
expect(output).to include("ruact_js_assets")
|
|
637
|
+
end
|
|
638
|
+
|
|
639
|
+
it "is a no-op on a fresh install (no stale files → no notice)" do
|
|
640
|
+
gen = build_generator(app_root)
|
|
641
|
+
output = capture_stdout { gen.advise_plumbing_migration }
|
|
642
|
+
expect(output).to be_empty
|
|
643
|
+
end
|
|
644
|
+
end
|
|
645
|
+
|
|
646
|
+
describe "vite.config input targets the virtual bootstrap (AC3 — single source of truth)" do
|
|
647
|
+
let(:template_path) do
|
|
648
|
+
File.expand_path("../../lib/generators/ruact/install/templates/vite.config.js.tt", __dir__)
|
|
649
|
+
end
|
|
650
|
+
|
|
651
|
+
it "renders the input from Ruact.bootstrap_virtual_id, not a hardcoded application.jsx", :aggregate_failures do
|
|
652
|
+
content = File.read(template_path)
|
|
653
|
+
expect(content).to include("Ruact.bootstrap_virtual_id")
|
|
654
|
+
# The `input:` line must NOT hardcode the old application.jsx entry
|
|
655
|
+
# (a prose mention in a comment is fine — only the directive matters).
|
|
656
|
+
expect(content).not_to match(%r{input:\s*['"]app/javascript/application\.jsx['"]})
|
|
657
|
+
end
|
|
658
|
+
|
|
659
|
+
it "the generated input equals the id the ViewHelper/prod manifest lookup uses (no drift)" do
|
|
660
|
+
gen = build_generator(app_root)
|
|
661
|
+
silently { gen.create_vite_config }
|
|
662
|
+
generated = File.read(File.join(app_root, "vite.config.js"))
|
|
663
|
+
expect(generated).to include("input: '#{described_class.bootstrap_virtual_id}'")
|
|
664
|
+
end
|
|
665
|
+
|
|
666
|
+
it "leaves an existing vite.config.js untouched when --force is not passed" do
|
|
667
|
+
File.write(File.join(app_root, "vite.config.js"), "// hand-written\n")
|
|
668
|
+
gen = build_generator(app_root)
|
|
669
|
+
silently { gen.create_vite_config }
|
|
670
|
+
expect(File.read(File.join(app_root, "vite.config.js"))).to eq("// hand-written\n")
|
|
671
|
+
end
|
|
672
|
+
|
|
673
|
+
it "actually regenerates an existing vite.config.js under --force (the message's promise)", :aggregate_failures do
|
|
674
|
+
File.write(File.join(app_root, "vite.config.js"), "// hand-written\n")
|
|
675
|
+
gen = build_generator(app_root, { force: true })
|
|
676
|
+
silently { gen.create_vite_config }
|
|
677
|
+
regenerated = File.read(File.join(app_root, "vite.config.js"))
|
|
678
|
+
expect(regenerated).not_to include("hand-written")
|
|
679
|
+
expect(regenerated).to include("input: '#{described_class.bootstrap_virtual_id}'")
|
|
680
|
+
end
|
|
681
|
+
end
|
|
682
|
+
end
|
|
683
|
+
|
|
684
|
+
# Story 14.6 (FR101, Epic 14 DoD) — `ruact:install` now emits the missing
|
|
685
|
+
# launch pieces so the literal `bin/dev` produces a working app: a
|
|
686
|
+
# `package.json` (so 14.1's `npm install` resolves React + Vite) and a
|
|
687
|
+
# `Procfile.dev` + foreman `bin/dev` that boot BOTH Rails and the Vite dev
|
|
688
|
+
# server. Each is guarded (non-clobbering, --force overwrites) and idempotent.
|
|
689
|
+
describe "install generator — launch files: package.json + Procfile.dev + bin/dev (Story 14.6)",
|
|
690
|
+
:story_14_6 do
|
|
691
|
+
require "stringio"
|
|
692
|
+
require "generators/ruact/install/install_generator"
|
|
693
|
+
|
|
694
|
+
let(:app_root) { Dir.mktmpdir("ruact_install_1406") }
|
|
695
|
+
|
|
696
|
+
after { FileUtils.rm_rf(app_root) }
|
|
697
|
+
|
|
698
|
+
def build_generator(root, opts = {})
|
|
699
|
+
Ruact::Generators::InstallGenerator.new([], opts, destination_root: root)
|
|
700
|
+
end
|
|
701
|
+
|
|
702
|
+
def silently
|
|
703
|
+
original = $stdout
|
|
704
|
+
$stdout = StringIO.new
|
|
705
|
+
yield
|
|
706
|
+
ensure
|
|
707
|
+
$stdout = original
|
|
708
|
+
end
|
|
709
|
+
|
|
710
|
+
describe "create_package_json (AC#1 — JS deps to install)", :aggregate_failures do
|
|
711
|
+
it "writes a package.json declaring React + Vite (and a `dev` script)" do
|
|
712
|
+
gen = build_generator(app_root)
|
|
713
|
+
silently { gen.create_package_json }
|
|
714
|
+
|
|
715
|
+
path = File.join(app_root, "package.json")
|
|
716
|
+
expect(File).to exist(path)
|
|
717
|
+
pkg = JSON.parse(File.read(path))
|
|
718
|
+
expect(pkg.dig("dependencies", "react")).to be_a(String)
|
|
719
|
+
expect(pkg.dig("dependencies", "react-dom")).to be_a(String)
|
|
720
|
+
expect(pkg.dig("devDependencies", "vite")).to be_a(String)
|
|
721
|
+
expect(pkg.dig("devDependencies", "@vitejs/plugin-react")).to be_a(String)
|
|
722
|
+
expect(pkg.dig("scripts", "dev")).to eq("vite")
|
|
723
|
+
expect(pkg["type"]).to eq("module")
|
|
724
|
+
end
|
|
725
|
+
|
|
726
|
+
it "does NOT list the bundled ruact Vite plugin as an npm dependency " \
|
|
727
|
+
"(vite.config imports it by absolute path)" do
|
|
728
|
+
gen = build_generator(app_root)
|
|
729
|
+
silently { gen.create_package_json }
|
|
730
|
+
expect(File.read(File.join(app_root, "package.json"))).not_to include("vite-plugin-ruact")
|
|
731
|
+
end
|
|
732
|
+
|
|
733
|
+
it "derives a valid lowercase npm name from the app directory" do
|
|
734
|
+
gen = build_generator(app_root)
|
|
735
|
+
silently { gen.create_package_json }
|
|
736
|
+
name = JSON.parse(File.read(File.join(app_root, "package.json")))["name"]
|
|
737
|
+
expect(name).to match(/\A[a-z0-9._-]+\z/)
|
|
738
|
+
end
|
|
739
|
+
|
|
740
|
+
it "leaves an existing package.json untouched without --force (non-clobbering)" do
|
|
741
|
+
File.write(File.join(app_root, "package.json"), %({ "name": "mine" }\n))
|
|
742
|
+
gen = build_generator(app_root)
|
|
743
|
+
silently { gen.create_package_json }
|
|
744
|
+
expect(File.read(File.join(app_root, "package.json"))).to eq(%({ "name": "mine" }\n))
|
|
745
|
+
end
|
|
746
|
+
|
|
747
|
+
it "overwrites an existing package.json under --force" do
|
|
748
|
+
File.write(File.join(app_root, "package.json"), %({ "name": "mine" }\n))
|
|
749
|
+
gen = build_generator(app_root, { force: true })
|
|
750
|
+
silently { gen.create_package_json }
|
|
751
|
+
expect(File.read(File.join(app_root, "package.json"))).to include("\"vite\"")
|
|
752
|
+
end
|
|
753
|
+
end
|
|
754
|
+
|
|
755
|
+
describe "create_launch_files (Epic DoD — bin/dev boots both processes)", :aggregate_failures do
|
|
756
|
+
it "writes Procfile.dev with BOTH a Rails web process and a Vite process" do
|
|
757
|
+
gen = build_generator(app_root)
|
|
758
|
+
silently { gen.create_launch_files }
|
|
759
|
+
|
|
760
|
+
procfile = File.read(File.join(app_root, "Procfile.dev"))
|
|
761
|
+
expect(procfile).to match(/^web:.*rails server/)
|
|
762
|
+
expect(procfile).to match(/^vite:.*npm run dev/)
|
|
763
|
+
end
|
|
764
|
+
|
|
765
|
+
it "writes an executable bin/dev that execs foreman against Procfile.dev" do
|
|
766
|
+
gen = build_generator(app_root)
|
|
767
|
+
silently { gen.create_launch_files }
|
|
768
|
+
|
|
769
|
+
dev = File.join(app_root, "bin/dev")
|
|
770
|
+
expect(File).to exist(dev)
|
|
771
|
+
expect(File).to be_executable(dev)
|
|
772
|
+
body = File.read(dev)
|
|
773
|
+
expect(body).to include("foreman start -f Procfile.dev")
|
|
774
|
+
expect(body).to include("gem install foreman")
|
|
775
|
+
end
|
|
776
|
+
|
|
777
|
+
# Live clean-room fix — ruact OWNS bin/dev. `rails new` (Rails 8.x) writes a
|
|
778
|
+
# bin/dev that starts ONLY `rails server`; left in place, Vite never runs,
|
|
779
|
+
# the client manifest is never written, and the first render 500s. So the
|
|
780
|
+
# foreman launcher must TAKE OVER that default rather than skip it.
|
|
781
|
+
it "takes ownership of the Rails-default bin/dev (one that only runs rails server)" do
|
|
782
|
+
FileUtils.mkdir_p(File.join(app_root, "bin"))
|
|
783
|
+
File.write(File.join(app_root, "bin/dev"),
|
|
784
|
+
%(#!/usr/bin/env ruby\nexec "./bin/rails", "server", *ARGV\n))
|
|
785
|
+
|
|
786
|
+
gen = build_generator(app_root)
|
|
787
|
+
silently { gen.create_launch_files }
|
|
788
|
+
|
|
789
|
+
body = File.read(File.join(app_root, "bin/dev"))
|
|
790
|
+
expect(body).to include("foreman start -f Procfile.dev")
|
|
791
|
+
expect(body).not_to include('exec "./bin/rails", "server"')
|
|
792
|
+
expect(File).to be_executable(File.join(app_root, "bin/dev"))
|
|
793
|
+
end
|
|
794
|
+
|
|
795
|
+
# Idempotency (invariant 14.1) — re-running the generator must not churn:
|
|
796
|
+
# the bin/dev we wrote already drives Procfile.dev, so the second run skips
|
|
797
|
+
# it. (Detection is content-based: our launcher execs foreman against
|
|
798
|
+
# Procfile.dev, so a marker comment proves the file was NOT rewritten.)
|
|
799
|
+
it "is idempotent — a second run does not rewrite the foreman bin/dev it already wrote" do
|
|
800
|
+
gen = build_generator(app_root)
|
|
801
|
+
silently { gen.create_launch_files }
|
|
802
|
+
|
|
803
|
+
marked = "#{File.read(File.join(app_root, 'bin/dev'))}# ruact-idempotency-marker\n"
|
|
804
|
+
File.write(File.join(app_root, "bin/dev"), marked)
|
|
805
|
+
|
|
806
|
+
gen2 = build_generator(app_root)
|
|
807
|
+
silently { gen2.create_launch_files }
|
|
808
|
+
expect(File.read(File.join(app_root, "bin/dev"))).to include("# ruact-idempotency-marker")
|
|
809
|
+
end
|
|
810
|
+
|
|
811
|
+
# Codex R1 — detection must be stricter than a bare `include?("Procfile.dev")`:
|
|
812
|
+
# a bin/dev that only NAMES Procfile.dev in a comment (but still runs only
|
|
813
|
+
# `rails server`) must be taken over, not skipped — else the 500 survives.
|
|
814
|
+
it "takes ownership when Procfile.dev appears only in a comment (runs only rails server)" do
|
|
815
|
+
FileUtils.mkdir_p(File.join(app_root, "bin"))
|
|
816
|
+
File.write(File.join(app_root, "bin/dev"),
|
|
817
|
+
%(#!/usr/bin/env ruby\n# TODO: switch to Procfile.dev + foreman one day\n) +
|
|
818
|
+
%(exec "./bin/rails", "server", *ARGV\n))
|
|
819
|
+
|
|
820
|
+
gen = build_generator(app_root)
|
|
821
|
+
silently { gen.create_launch_files }
|
|
822
|
+
|
|
823
|
+
body = File.read(File.join(app_root, "bin/dev"))
|
|
824
|
+
expect(body).to include("foreman start -f Procfile.dev")
|
|
825
|
+
expect(body).not_to include('exec "./bin/rails", "server"')
|
|
826
|
+
end
|
|
827
|
+
|
|
828
|
+
# Codex R2/R3 — a runner name that is only MENTIONED (not invoked as the
|
|
829
|
+
# command) must not count as a real launcher. Each of these bin/dev files
|
|
830
|
+
# names "foreman"/"Procfile.dev" somewhere but actually runs `rails server`,
|
|
831
|
+
# so each must be taken over (else the original 500 survives).
|
|
832
|
+
[
|
|
833
|
+
["echoed in a string",
|
|
834
|
+
%(echo "switch to: foreman start -f Procfile.dev"\nexec ./bin/rails server\n)],
|
|
835
|
+
["assigned to a variable",
|
|
836
|
+
%(MSG="switch to: foreman start -f Procfile.dev"\nexec ./bin/rails server\n)],
|
|
837
|
+
["named inside a command -v test",
|
|
838
|
+
%(if command -v foreman; then echo Procfile.dev; fi\nexec ./bin/rails server\n)],
|
|
839
|
+
["referenced only in an inline comment on a runner diagnostic line",
|
|
840
|
+
%(foreman --version # TODO: switch to Procfile.dev\nexec ./bin/rails server\n)],
|
|
841
|
+
["a runner look-alike command (not the real runner)",
|
|
842
|
+
%(foreman-old start -f Procfile.dev\nexec ./bin/rails server\n)]
|
|
843
|
+
].each do |(label, contents)|
|
|
844
|
+
it "takes ownership when a foreman command is only #{label} (still runs rails server)" do
|
|
845
|
+
FileUtils.mkdir_p(File.join(app_root, "bin"))
|
|
846
|
+
File.write(File.join(app_root, "bin/dev"), "#!/usr/bin/env bash\n#{contents}")
|
|
847
|
+
|
|
848
|
+
gen = build_generator(app_root)
|
|
849
|
+
silently { gen.create_launch_files }
|
|
850
|
+
|
|
851
|
+
body = File.read(File.join(app_root, "bin/dev"))
|
|
852
|
+
expect(body).to include("gem install foreman")
|
|
853
|
+
expect(body).not_to include("exec ./bin/rails server")
|
|
854
|
+
end
|
|
855
|
+
end
|
|
856
|
+
|
|
857
|
+
# Non-clobbering of a DELIBERATE foreman launcher — a developer's own
|
|
858
|
+
# bin/dev that already drives Procfile.dev is ruact-compatible and is left
|
|
859
|
+
# untouched (it boots Vite via the same Procfile.dev ruact writes).
|
|
860
|
+
it "leaves a developer's existing foreman launcher (driving Procfile.dev) untouched" do
|
|
861
|
+
FileUtils.mkdir_p(File.join(app_root, "bin"))
|
|
862
|
+
File.write(File.join(app_root, "bin/dev"),
|
|
863
|
+
"#!/usr/bin/env bash\n# my custom launcher\nexec foreman start -f Procfile.dev\n")
|
|
864
|
+
|
|
865
|
+
gen = build_generator(app_root)
|
|
866
|
+
silently { gen.create_launch_files }
|
|
867
|
+
expect(File.read(File.join(app_root, "bin/dev"))).to include("# my custom launcher")
|
|
868
|
+
end
|
|
869
|
+
|
|
870
|
+
it "overwrites even a Procfile.dev-driving launcher under --force" do
|
|
871
|
+
FileUtils.mkdir_p(File.join(app_root, "bin"))
|
|
872
|
+
File.write(File.join(app_root, "bin/dev"),
|
|
873
|
+
"#!/usr/bin/env bash\n# my custom launcher\nexec foreman start -f Procfile.dev\n")
|
|
874
|
+
|
|
875
|
+
gen = build_generator(app_root, { force: true })
|
|
876
|
+
silently { gen.create_launch_files }
|
|
877
|
+
body = File.read(File.join(app_root, "bin/dev"))
|
|
878
|
+
expect(body).to include("gem install foreman")
|
|
879
|
+
expect(body).not_to include("# my custom launcher")
|
|
880
|
+
end
|
|
881
|
+
end
|
|
882
|
+
|
|
883
|
+
describe "action ordering (npm install needs package.json on disk first)" do
|
|
884
|
+
it "defines create_package_json BEFORE install_javascript_dependencies", :aggregate_failures do
|
|
885
|
+
klass = Ruact::Generators::InstallGenerator
|
|
886
|
+
line = ->(name) { klass.instance_method(name).source_location.last }
|
|
887
|
+
expect(line.call(:create_package_json)).to be < line.call(:install_javascript_dependencies)
|
|
888
|
+
expect(line.call(:create_launch_files)).to be < line.call(:install_javascript_dependencies)
|
|
889
|
+
end
|
|
890
|
+
end
|
|
304
891
|
end
|
|
305
892
|
end
|