diff --git a/.github/workflows/generators.yml b/.github/workflows/generators.yml index 7c8e5ae8..23fa7219 100644 --- a/.github/workflows/generators.yml +++ b/.github/workflows/generators.yml @@ -25,7 +25,7 @@ jobs: tailwind: [true, false] ruby: ['3.3'] node: ['22'] - inertia_version: ['1.2.0', 'next', 'beta'] + inertia_version: ['1.2.0', '1.3', '2.0'] exclude: # 1.2.0 does not support typescript - typescript: true diff --git a/docs/guide/csrf-protection.md b/docs/guide/csrf-protection.md index e882de31..92c59414 100644 --- a/docs/guide/csrf-protection.md +++ b/docs/guide/csrf-protection.md @@ -57,6 +57,9 @@ Axios automatically checks for the existence of an `XSRF-TOKEN` cookie. If it's The easiest way to implement this is using server-side middleware. Simply include the `XSRF-TOKEN` cookie on each response, and then verify the token using the `X-XSRF-TOKEN` header sent in the requests from axios. (That's basically what `inertia_rails` does). +> [!NOTE] +> `X-XSRF-TOKEN` header only works for [Inertia requests](/guide/the-protocol#inertia-responses). If you want to send a normal request you can use `X-CSRF-TOKEN` instead. + ## Handling mismatches When a CSRF token mismatch occurs, Rails raises the `ActionController::InvalidAuthenticityToken` error. Since that isn't a valid Inertia response, the error is shown in a modal. diff --git a/lib/generators/inertia/install/install_generator.rb b/lib/generators/inertia/install/install_generator.rb index 6eca99eb..7686a164 100644 --- a/lib/generators/inertia/install/install_generator.rb +++ b/lib/generators/inertia/install/install_generator.rb @@ -78,8 +78,7 @@ def install_inertia template 'initializer.rb', file_path('config/initializers/inertia_rails.rb') say 'Installing Inertia npm packages' - add_dependencies(*FRAMEWORKS[framework]['packages']) - add_dependencies(inertia_package) + add_dependencies(inertia_package, *FRAMEWORKS[framework]['packages']) unless File.read(vite_config_path).include?(FRAMEWORKS[framework]['vite_plugin_import']) say "Adding Vite plugin for #{framework}" diff --git a/lib/generators/inertia/install/templates/react/InertiaExample.tsx b/lib/generators/inertia/install/templates/react/InertiaExample.tsx index 8c9886c8..b5061b03 100644 --- a/lib/generators/inertia/install/templates/react/InertiaExample.tsx +++ b/lib/generators/inertia/install/templates/react/InertiaExample.tsx @@ -18,7 +18,7 @@ export default function InertiaExample({ name }: { name: string }) {

Hello {name}!

- + Inertia logo diff --git a/lib/generators/inertia/install/templates/react/inertia.ts b/lib/generators/inertia/install/templates/react/inertia.ts index d835cefd..9998974d 100644 --- a/lib/generators/inertia/install/templates/react/inertia.ts +++ b/lib/generators/inertia/install/templates/react/inertia.ts @@ -10,13 +10,13 @@ type ResolvedComponent = { createInertiaApp({ // Set default page title - // see https://inertia-rails.netlify.app/guide/title-and-meta + // see https://inertia-rails.dev/guide/title-and-meta // // title: title => title ? `${title} - App` : 'App', // Disable progress bar // - // see https://inertia-rails.netlify.app/guide/progress-indicators + // see https://inertia-rails.dev/guide/progress-indicators // progress: false, resolve: (name) => { @@ -30,7 +30,7 @@ createInertiaApp({ // To use a default layout, import the Layout component // and use the following line. - // see https://inertia-rails.netlify.app/guide/pages#default-layouts + // see https://inertia-rails.dev/guide/pages#default-layouts // // page.default.layout ||= (page) => createElement(Layout, null, page) diff --git a/lib/generators/inertia/install/templates/svelte/InertiaExample.svelte b/lib/generators/inertia/install/templates/svelte/InertiaExample.svelte index 4c6d2942..cdaf667b 100644 --- a/lib/generators/inertia/install/templates/svelte/InertiaExample.svelte +++ b/lib/generators/inertia/install/templates/svelte/InertiaExample.svelte @@ -16,7 +16,7 @@

Hello {name}!

- + diff --git a/lib/generators/inertia/install/templates/svelte/InertiaExample.ts.svelte b/lib/generators/inertia/install/templates/svelte/InertiaExample.ts.svelte index 0ed1c7df..a4e31248 100644 --- a/lib/generators/inertia/install/templates/svelte/InertiaExample.ts.svelte +++ b/lib/generators/inertia/install/templates/svelte/InertiaExample.ts.svelte @@ -16,7 +16,7 @@

Hello {name}!

- + diff --git a/lib/generators/inertia/install/templates/svelte/inertia.js b/lib/generators/inertia/install/templates/svelte/inertia.js index e5d0fd89..f664c984 100644 --- a/lib/generators/inertia/install/templates/svelte/inertia.js +++ b/lib/generators/inertia/install/templates/svelte/inertia.js @@ -3,13 +3,13 @@ import { mount } from 'svelte'; createInertiaApp({ // Set default page title - // see https://inertia-rails.netlify.app/guide/title-and-meta + // see https://inertia-rails.dev/guide/title-and-meta // // title: title => title ? `${title} - App` : 'App', // Disable progress bar // - // see https://inertia-rails.netlify.app/guide/progress-indicators + // see https://inertia-rails.dev/guide/progress-indicators // progress: false, resolve: (name) => { @@ -23,7 +23,7 @@ createInertiaApp({ // To use a default layout, import the Layout component // and use the following line. - // see https://inertia-rails.netlify.app/guide/pages#default-layouts + // see https://inertia-rails.dev/guide/pages#default-layouts // // return { default: page.default, layout: page.layout || Layout } diff --git a/lib/generators/inertia/install/templates/svelte/inertia.ts.tt b/lib/generators/inertia/install/templates/svelte/inertia.ts.tt index 0529c589..a4d5b344 100644 --- a/lib/generators/inertia/install/templates/svelte/inertia.ts.tt +++ b/lib/generators/inertia/install/templates/svelte/inertia.ts.tt @@ -3,13 +3,13 @@ import { mount } from 'svelte' createInertiaApp({ // Set default page title - // see https://inertia-rails.netlify.app/guide/title-and-meta + // see https://inertia-rails.dev/guide/title-and-meta // // title: title => title ? `${title} - App` : 'App', // Disable progress bar // - // see https://inertia-rails.netlify.app/guide/progress-indicators + // see https://inertia-rails.dev/guide/progress-indicators // progress: false, resolve: (name) => { @@ -23,7 +23,7 @@ createInertiaApp({ // To use a default layout, import the Layout component // and use the following line. - // see https://inertia-rails.netlify.app/guide/pages#default-layouts + // see https://inertia-rails.dev/guide/pages#default-layouts // // return { default: page.default, layout: page.layout || Layout } @@ -32,7 +32,7 @@ createInertiaApp({ setup({ el, App, props }) { if (el) { -<%= " // @ts-expect-error 1.3.0 beta contains types mismatch\n" if inertia_resolved_version == Gem::Version.new('1.3.0-beta.2') -%> +<%= " // @ts-expect-error 1.3.0 contains types mismatch\n" if inertia_resolved_version.release == Gem::Version.new('1.3.0') -%> mount(App, { target: el, props }) } else { console.error( diff --git a/lib/generators/inertia/install/templates/svelte4/InertiaExample.svelte b/lib/generators/inertia/install/templates/svelte4/InertiaExample.svelte index 96785fe5..b3589c42 100644 --- a/lib/generators/inertia/install/templates/svelte4/InertiaExample.svelte +++ b/lib/generators/inertia/install/templates/svelte4/InertiaExample.svelte @@ -20,7 +20,7 @@

Hello {name}!

- + diff --git a/lib/generators/inertia/install/templates/svelte4/InertiaExample.ts.svelte b/lib/generators/inertia/install/templates/svelte4/InertiaExample.ts.svelte index b0d42e3a..0ae6e21a 100644 --- a/lib/generators/inertia/install/templates/svelte4/InertiaExample.ts.svelte +++ b/lib/generators/inertia/install/templates/svelte4/InertiaExample.ts.svelte @@ -20,7 +20,7 @@

Hello {name}!

- + diff --git a/lib/generators/inertia/install/templates/svelte4/inertia.js b/lib/generators/inertia/install/templates/svelte4/inertia.js index 807786a7..e0b39358 100644 --- a/lib/generators/inertia/install/templates/svelte4/inertia.js +++ b/lib/generators/inertia/install/templates/svelte4/inertia.js @@ -2,13 +2,13 @@ import { createInertiaApp } from '@inertiajs/svelte' createInertiaApp({ // Set default page title - // see https://inertia-rails.netlify.app/guide/title-and-meta + // see https://inertia-rails.dev/guide/title-and-meta // // title: title => title ? `${title} - App` : 'App', // Disable progress bar // - // see https://inertia-rails.netlify.app/guide/progress-indicators + // see https://inertia-rails.dev/guide/progress-indicators // progress: false, resolve: (name) => { @@ -22,7 +22,7 @@ createInertiaApp({ // To use a default layout, import the Layout component // and use the following lines. - // see https://inertia-rails.netlify.app/guide/pages#default-layouts + // see https://inertia-rails.dev/guide/pages#default-layouts // // return { default: page.default, layout: page.layout || Layout } diff --git a/lib/generators/inertia/install/templates/svelte4/inertia.ts.tt b/lib/generators/inertia/install/templates/svelte4/inertia.ts.tt index cb59b054..6655d224 100644 --- a/lib/generators/inertia/install/templates/svelte4/inertia.ts.tt +++ b/lib/generators/inertia/install/templates/svelte4/inertia.ts.tt @@ -2,13 +2,13 @@ import { createInertiaApp, type ResolvedComponent } from '@inertiajs/svelte' createInertiaApp({ // Set default page title - // see https://inertia-rails.netlify.app/guide/title-and-meta + // see https://inertia-rails.dev/guide/title-and-meta // // title: title => title ? `${title} - App` : 'App', // Disable progress bar // - // see https://inertia-rails.netlify.app/guide/progress-indicators + // see https://inertia-rails.dev/guide/progress-indicators // progress: false, resolve: (name) => { @@ -22,7 +22,7 @@ createInertiaApp({ // To use a default layout, import the Layout component // and use the following line. - // see https://inertia-rails.netlify.app/guide/pages#default-layouts + // see https://inertia-rails.dev/guide/pages#default-layouts // // return { default: page.default, layout: page.layout || Layout } @@ -31,7 +31,7 @@ createInertiaApp({ setup({ el, App, props }) { if (el) { - <%= "// @ts-expect-error 1.3.0 beta contains types mismatch\n" if inertia_resolved_version == Gem::Version.new('1.3.0-beta.2') -%> + <%= "// @ts-expect-error 1.3.0 beta contains types mismatch\n" if inertia_resolved_version.release == Gem::Version.new('1.3.0') -%> new App({ target: el, props }) } else { console.error( diff --git a/lib/generators/inertia/install/templates/vue/InertiaExample.ts.vue b/lib/generators/inertia/install/templates/vue/InertiaExample.ts.vue index 93da5091..7392fc5a 100644 --- a/lib/generators/inertia/install/templates/vue/InertiaExample.ts.vue +++ b/lib/generators/inertia/install/templates/vue/InertiaExample.ts.vue @@ -5,7 +5,7 @@

Hello {{ name }}!

- + diff --git a/lib/generators/inertia/install/templates/vue/InertiaExample.vue b/lib/generators/inertia/install/templates/vue/InertiaExample.vue index 9070f466..dd14bdea 100644 --- a/lib/generators/inertia/install/templates/vue/InertiaExample.vue +++ b/lib/generators/inertia/install/templates/vue/InertiaExample.vue @@ -5,7 +5,7 @@

Hello {{ name }}!

- + diff --git a/lib/generators/inertia/install/templates/vue/inertia.ts b/lib/generators/inertia/install/templates/vue/inertia.ts index ad8b2756..7b043f6a 100644 --- a/lib/generators/inertia/install/templates/vue/inertia.ts +++ b/lib/generators/inertia/install/templates/vue/inertia.ts @@ -3,13 +3,13 @@ import { createApp, DefineComponent, h } from 'vue' createInertiaApp({ // Set default page title - // see https://inertia-rails.netlify.app/guide/title-and-meta + // see https://inertia-rails.dev/guide/title-and-meta // // title: title => title ? `${title} - App` : 'App', // Disable progress bar // - // see https://inertia-rails.netlify.app/guide/progress-indicators + // see https://inertia-rails.dev/guide/progress-indicators // progress: false, resolve: (name) => { @@ -20,7 +20,7 @@ createInertiaApp({ // To use a default layout, import the Layout component // and use the following lines. - // see https://inertia-rails.netlify.app/guide/pages#default-layouts + // see https://inertia-rails.dev/guide/pages#default-layouts // // const page = pages[`../pages/${name}.vue`] // page.default.layout = page.default.layout || Layout diff --git a/lib/inertia_rails/generators/helper.rb b/lib/inertia_rails/generators/helper.rb index d8f8166f..f3a9e669 100644 --- a/lib/inertia_rails/generators/helper.rb +++ b/lib/inertia_rails/generators/helper.rb @@ -4,14 +4,16 @@ module InertiaRails module Generators module Helper class << self - def guess_the_default_framework - package = Rails.root.join('package.json').read - case package - when %r{@inertiajs/react} + def guess_the_default_framework(package_json_path = Rails.root.join('package.json')) + package_json = JSON.parse(package_json_path.read) + dependencies = package_json['dependencies'] || {} + + if dependencies['@inertiajs/react'] 'react' - when %r{@inertiajs/svelte} - package.match?(/"svelte": "\^5/) ? 'svelte' : 'svelte4' - when %r{@inertiajs/vue3} + elsif dependencies['@inertiajs/svelte'] + version = dependencies['svelte'].gsub(/[\^~]/, '') # Remove ^ or ~ from version + version.start_with?('5') ? 'svelte' : 'svelte4' + elsif dependencies['@inertiajs/vue3'] 'vue' else Thor::Shell::Basic.new.say_error 'Could not determine the Inertia.js framework you are using.' diff --git a/lib/inertia_rails/renderer.rb b/lib/inertia_rails/renderer.rb index c671493c..f556a7b4 100644 --- a/lib/inertia_rails/renderer.rb +++ b/lib/inertia_rails/renderer.rb @@ -41,7 +41,7 @@ def render end if @request.headers['X-Inertia'] @response.set_header('X-Inertia', 'true') - @render_method.call json: page, status: @response.status, content_type: Mime[:json] + @render_method.call json: page.to_json, status: @response.status, content_type: Mime[:json] else return render_ssr if configuration.ssr_enabled rescue nil @render_method.call template: 'inertia', layout: layout, locals: view_data.merge(page: page) diff --git a/lib/inertia_rails/rspec.rb b/lib/inertia_rails/rspec.rb index 2f274a8d..17fffd07 100644 --- a/lib/inertia_rails/rspec.rb +++ b/lib/inertia_rails/rspec.rb @@ -32,8 +32,9 @@ def set_values(params) else # Sequential Inertia request @view_data = {} - @props = params[:json][:props] - @component = params[:json][:component] + json = JSON.parse(params[:json]) + @props = json["props"] + @component = json["component"] end end end @@ -81,8 +82,7 @@ def inertia_tests_setup? RSpec::Matchers.define :have_exact_props do |expected_props| match do |inertia| - # Computed props have symbolized keys. - expect(inertia.props).to eq expected_props.deep_symbolize_keys + expect(inertia.props).to eq expected_props end failure_message do |inertia| @@ -92,8 +92,7 @@ def inertia_tests_setup? RSpec::Matchers.define :include_props do |expected_props| match do |inertia| - # Computed props have symbolized keys. - expect(inertia.props).to include expected_props.deep_symbolize_keys + expect(inertia.props).to include expected_props end failure_message do |inertia| diff --git a/spec/fixtures/package_json_files/empty_package.json b/spec/fixtures/package_json_files/empty_package.json new file mode 100644 index 00000000..18a1e415 --- /dev/null +++ b/spec/fixtures/package_json_files/empty_package.json @@ -0,0 +1,3 @@ +{ + "dependencies": {} +} diff --git a/spec/fixtures/package_json_files/react_package.json b/spec/fixtures/package_json_files/react_package.json new file mode 100644 index 00000000..5495cbcb --- /dev/null +++ b/spec/fixtures/package_json_files/react_package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "@inertiajs/react": "1.0.0" + } +} diff --git a/spec/fixtures/package_json_files/svelte4_package.json b/spec/fixtures/package_json_files/svelte4_package.json new file mode 100644 index 00000000..0dbf43d4 --- /dev/null +++ b/spec/fixtures/package_json_files/svelte4_package.json @@ -0,0 +1,6 @@ +{ + "dependencies": { + "@inertiajs/svelte": "1.0.0", + "svelte": "^4.0.0" + } +} diff --git a/spec/fixtures/package_json_files/svelte5_caret_package.json b/spec/fixtures/package_json_files/svelte5_caret_package.json new file mode 100644 index 00000000..5d12c146 --- /dev/null +++ b/spec/fixtures/package_json_files/svelte5_caret_package.json @@ -0,0 +1,6 @@ +{ + "dependencies": { + "@inertiajs/svelte": "1.0.0", + "svelte": "^5.0.0" + } +} diff --git a/spec/fixtures/package_json_files/svelte5_exact_package.json b/spec/fixtures/package_json_files/svelte5_exact_package.json new file mode 100644 index 00000000..ce87e0aa --- /dev/null +++ b/spec/fixtures/package_json_files/svelte5_exact_package.json @@ -0,0 +1,6 @@ +{ + "dependencies": { + "@inertiajs/svelte": "1.0.0", + "svelte": "5.0.0" + } +} diff --git a/spec/fixtures/package_json_files/svelte5_tilde_package.json b/spec/fixtures/package_json_files/svelte5_tilde_package.json new file mode 100644 index 00000000..bce22e10 --- /dev/null +++ b/spec/fixtures/package_json_files/svelte5_tilde_package.json @@ -0,0 +1,6 @@ +{ + "dependencies": { + "@inertiajs/svelte": "1.0.0", + "svelte": "~5.0.0" + } +} diff --git a/spec/fixtures/package_json_files/vue_package.json b/spec/fixtures/package_json_files/vue_package.json new file mode 100644 index 00000000..87c68965 --- /dev/null +++ b/spec/fixtures/package_json_files/vue_package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "@inertiajs/vue3": "1.0.0" + } +} diff --git a/spec/generators/generators_helper_spec.rb b/spec/generators/generators_helper_spec.rb new file mode 100644 index 00000000..89a24221 --- /dev/null +++ b/spec/generators/generators_helper_spec.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +require 'thor' +require_relative '../../lib/inertia_rails/generators/helper' + +RSpec.describe InertiaRails::Generators::Helper, type: :helper do + describe '#guess_the_default_framework' do + let(:package_json_path) { Pathname.new(File.expand_path("spec/fixtures/package_json_files/#{fixture_file_name}", Dir.pwd)) } + + shared_examples 'framework detection' do |file_name, expected_framework| + let(:fixture_file_name) { file_name } + + it "returns #{expected_framework.inspect} when inspect \"#{file_name}\"" do + expect(described_class.guess_the_default_framework(package_json_path)).to eq(expected_framework) + end + end + + it_behaves_like 'framework detection', 'react_package.json', 'react' + it_behaves_like 'framework detection', 'svelte5_caret_package.json', 'svelte' + it_behaves_like 'framework detection', 'svelte5_exact_package.json', 'svelte' + it_behaves_like 'framework detection', 'svelte5_tilde_package.json', 'svelte' + it_behaves_like 'framework detection', 'svelte4_package.json', 'svelte4' + it_behaves_like 'framework detection', 'vue_package.json', 'vue' + + # Handle exception + context 'when framework cannot be determined' do + let(:fixture_file_name) { 'empty_package.json' } + + it 'raises an error' do + allow(described_class).to receive(:exit) # Prevent `exit` from terminating the test + expect(Thor::Shell::Basic).to receive_message_chain(:new, :say_error) + .with('Could not determine the Inertia.js framework you are using.') + described_class.guess_the_default_framework(package_json_path) + end + end + end +end diff --git a/spec/inertia/rails_mimic_spec.rb b/spec/inertia/rails_mimic_spec.rb index 70bfdd03..9e0e8b89 100644 --- a/spec/inertia/rails_mimic_spec.rb +++ b/spec/inertia/rails_mimic_spec.rb @@ -6,7 +6,7 @@ it 'has the props' do get instance_props_test_path - expect_inertia.to have_exact_props({'name' => 'Brandon', 'sport' => 'hockey'}) + expect_inertia.to have_exact_props({name: 'Brandon', sport: 'hockey'}) end end @@ -31,7 +31,7 @@ get default_render_test_path expect_inertia.to render_component('inertia_rails_mimic/default_render_test') - expect_inertia.to include_props({'name' => 'Brian'}) + expect_inertia.to include_props({name: 'Brian'}) end context 'a rendering transformation is provided' do diff --git a/spec/inertia/rspec_helper_spec.rb b/spec/inertia/rspec_helper_spec.rb index 12a6fb37..e05cd635 100644 --- a/spec/inertia/rspec_helper_spec.rb +++ b/spec/inertia/rspec_helper_spec.rb @@ -38,15 +38,15 @@ def puts(thing) before { get props_path, headers: {'X-Inertia': true} } it 'has props' do - expect_inertia.to have_exact_props({name: 'Brandon', sport: 'hockey'}) + expect_inertia.to have_exact_props({"name" => 'Brandon', "sport" => 'hockey'}) end it 'includes props' do - expect_inertia.to include_props({sport: 'hockey'}) + expect_inertia.to include_props({"sport" => 'hockey'}) end it 'can retrieve props' do - expect(inertia.props[:name]).to eq 'Brandon' + expect(inertia.props["name"]).to eq 'Brandon' end end @@ -139,7 +139,7 @@ def puts(thing) expect_inertia.to have_exact_props({ someProperty: { property_a: "some value", - 'property_b' => "this value", + property_b: "this value", }, property_c: "some other value" })