diff --git a/.github/workflows/generators.yml b/.github/workflows/generators.yml index ba560106..fc98834d 100644 --- a/.github/workflows/generators.yml +++ b/.github/workflows/generators.yml @@ -18,14 +18,23 @@ jobs: test: runs-on: ubuntu-latest strategy: + fail-fast: false matrix: - framework: [react, vue, svelte4, svelte] + framework: [react] typescript: [true, false] tailwind: [true, false] ruby: ['3.3'] node: ['22'] + inertia_version: ['1.2.0', '1.3.0-beta.2', '2.0.0-beta.2'] + exclude: + # 1.2.0 does not support typescript + - typescript: true + inertia_version: '1.2.0' + # 1.2.0 doesn't support Svelte 5 + - framework: svelte + inertia_version: '1.2.0' - name: ${{ matrix.framework }} (TS:${{ matrix.typescript }}, TW:${{ matrix.tailwind }}) + name: ${{ matrix.framework }} (TS:${{ matrix.typescript }}, TW:${{ matrix.tailwind }}, Inertia:${{ matrix.inertia_version }}) steps: - uses: actions/checkout@v4 @@ -48,7 +57,7 @@ jobs: tmp/bundle_cache tmp/npm_cache ~/.npm - key: ${{ runner.os }}-deps-${{ matrix.framework }}-${{ hashFiles('**/Gemfile.lock') }}-${{ github.sha }} + key: ${{ runner.os }}-deps-${{ matrix.framework }}-${{ matrix.inertia_version }}-${{ hashFiles('**/Gemfile.lock') }}-${{ github.sha }} restore-keys: | ${{ runner.os }}-deps-${{ matrix.framework }}- ${{ runner.os }}-deps- @@ -60,13 +69,13 @@ jobs: run: | ts_flag=${{ matrix.typescript && '--typescript' || '--no-typescript' }} tw_flag=${{ matrix.tailwind && '--tailwind' || '--no-tailwind' }} - bin/generate_scaffold_example --framework=${{ matrix.framework }} $ts_flag $tw_flag + bin/generate_scaffold_example --framework=${{ matrix.framework }} --inertia-version=${{ matrix.inertia_version }} $ts_flag $tw_flag - name: Upload test artifacts if: failure() - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: - name: test-output-${{ matrix.framework }}-ts${{ matrix.typescript }}-tw${{ matrix.tailwind }} + name: test-output-${{ matrix.framework }}-ts${{ matrix.typescript }}-tw${{ matrix.tailwind }}-v${{ matrix.inertia_version }} path: | tmp/scaffold_example/log tmp/scaffold_example/tmp/screenshots diff --git a/bin/generate_scaffold_example b/bin/generate_scaffold_example index 44cf3fe6..e127efb9 100755 --- a/bin/generate_scaffold_example +++ b/bin/generate_scaffold_example @@ -26,13 +26,17 @@ OptionParser.new do |opts| opts.on('--[no-]tailwind', 'Enable/disable Tailwind') do |t| options[:tailwind] = t end + + opts.on('--inertia-version VERSION', 'Specify Inertia version') do |v| + options[:inertia_version] = v + end end.parse! # Build generator args string generator_args = "--framework=#{options[:framework]}" generator_args += ' --typescript' if options[:typescript] -generator_args += ' --install-vite' -generator_args += ' --install-tailwind' if options[:tailwind] +generator_args += ' --tailwind' if options[:tailwind] +generator_args += " --inertia-version=#{options[:inertia_version]}" if options[:inertia_version] # Setup paths relative to project root project_root = File.expand_path('..', __dir__) @@ -57,25 +61,28 @@ system("rails new #{app_dir} -J") # Install and configure with caching Dir.chdir(app_dir) do # Configure bundler to use cache in project root - system("bundle config set --local path '#{gem_cache}'") + system("bundle config set --local path '#{gem_cache}'", exception: true) # Configure npm to use cache in project root - system("npm config set cache '#{npm_cache}'") + system("npm config set cache '#{npm_cache}'", exception: true) # Install dependencies - system('bundle add inertia_rails --path ../../') - system('bundle add bcrypt') - system('bin/rails active_storage:install') + system('bundle add inertia_rails --path ../../', exception: true) + system('bundle add bcrypt', exception: true) + system('bin/rails active_storage:install', exception: true) # Run install generator with configured options - system("bin/rails g inertia:install --no-interactive --force #{generator_args}") + system("bin/rails g inertia:install --no-interactive --force --vite #{generator_args} --verbose", exception: true) # Generate a scaffold - system('bin/rails g inertia:scaffold user name email admin:boolean password:digest avatar:attachment') - system('bin/rails g inertia:scaffold post content:text published_at:date gallery:attachments') - system('bin/rails db:migrate') + system('bin/rails g inertia:scaffold user name email admin:boolean password:digest avatar:attachment', exception: true) + system('bin/rails g inertia:scaffold post content:text published_at:date gallery:attachments', exception: true) + system('bin/rails db:migrate', exception: true) # Run tests - system('bin/rails test') - system('bin/rails test:system') + system('bin/rails test', exception: true) + system('bin/rails test:system', exception: true) + + # Lint code + system('npm run check', exception: true) if options[:typescript] end diff --git a/docs/cookbook/integrating-shadcn-ui.md b/docs/cookbook/integrating-shadcn-ui.md index c8729b5e..82303646 100644 --- a/docs/cookbook/integrating-shadcn-ui.md +++ b/docs/cookbook/integrating-shadcn-ui.md @@ -14,7 +14,7 @@ If you're starting fresh, create a new Rails application with Inertia (or skip t rails new -JA shadcn-inertia-rails cd shadcn-inertia-rails -rails generate inertia:install `--framework=react --typescript --install-vite --install-tailwind --no-interactive` +rails generate inertia:install `--framework=react --typescript --vite --tailwind --no-interactive` Installing Inertia's Rails adapter ... ``` @@ -25,7 +25,7 @@ Installing Inertia's Rails adapter rails new -JA shadcn-inertia-rails cd shadcn-inertia-rails -rails generate inertia:install --framework=react --install-vite --install-tailwind --no-interactive +rails generate inertia:install --framework=react --vite --tailwind --no-interactive Installing Inertia's Rails adapter ... ``` diff --git a/lib/generators/inertia/install/install_generator.rb b/lib/generators/inertia/install/install_generator.rb index 119001a0..6eca99eb 100644 --- a/lib/generators/inertia/install/install_generator.rb +++ b/lib/generators/inertia/install/install_generator.rb @@ -34,10 +34,10 @@ class InstallGenerator < Rails::Generators::Base class_option :interactive, type: :boolean, default: true, desc: 'Whether to prompt for optional installations' - class_option :install_tailwind, type: :boolean, default: false, - desc: 'Whether to install Tailwind CSS' - class_option :install_vite, type: :boolean, default: false, - desc: 'Whether to install Vite Ruby' + class_option :tailwind, type: :boolean, default: false, + desc: 'Whether to install Tailwind CSS' + class_option :vite, type: :boolean, default: false, + desc: 'Whether to install Vite Ruby' class_option :example_page, type: :boolean, default: true, desc: 'Whether to add an example Inertia page' @@ -127,6 +127,11 @@ def install_typescript end add_dependencies(*FRAMEWORKS[framework]['packages_ts']) + + say 'Copying adding scripts to package.json' + run 'npm pkg set scripts.check="svelte-check --tsconfig ./tsconfig.json && tsc -p tsconfig.node.json"' if svelte? + run 'npm pkg set scripts.check="vue-tsc -p tsconfig.app.json && tsc -p tsconfig.node.json"' if framework == 'vue' + run 'npm pkg set scripts.check="tsc -p tsconfig.app.json && tsc -p tsconfig.node.json"' if framework == 'react' end def install_example_page @@ -236,13 +241,13 @@ def vite_config_path def install_vite? return @install_vite if defined?(@install_vite) - @install_vite = options[:install_vite] || yes?('Would you like to install Vite Ruby? (y/n)', :green) + @install_vite = options[:vite] || yes?('Would you like to install Vite Ruby? (y/n)', :green) end def install_tailwind? return @install_tailwind if defined?(@install_tailwind) - @install_tailwind = options[:install_tailwind] || yes?('Would you like to install Tailwind CSS? (y/n)', :green) + @install_tailwind = options[:tailwind] || yes?('Would you like to install Tailwind CSS? (y/n)', :green) end def typescript? diff --git a/lib/generators/inertia/install/templates/react/inertia.js b/lib/generators/inertia/install/templates/react/inertia.js index f00cad69..87d08702 100644 --- a/lib/generators/inertia/install/templates/react/inertia.js +++ b/lib/generators/inertia/install/templates/react/inertia.js @@ -15,20 +15,29 @@ createInertiaApp({ resolve: (name) => { const pages = import.meta.glob('../pages/**/*.jsx', { eager: true }) - return pages[`../pages/${name}.jsx`] + const page = pages[`../pages/${name}.jsx`] + if (!page) { + console.error(`Missing Inertia page component: '${name}.jsx'`) + } // To use a default layout, import the Layout component // and use the following lines. // see https://inertia-rails.netlify.app/guide/pages#default-layouts // - // const page = pages[`../pages/${name}.jsx`] // page.default.layout ||= (page) => createElement(Layout, null, page) - // return page + + return page }, setup({ el, App, props }) { - const root = createRoot(el) - - root.render(createElement(App, props)) + if (el) { + createRoot(el).render(createElement(App, props)) + } else { + console.error( + 'Missing root element.\n\n' + + 'If you see this error, it probably means you load Inertia.js on non-Inertia pages.\n' + + 'Consider moving <%%= vite_javascript_tag "inertia" %> to the Inertia-specific layout instead.' + ) + } }, }) diff --git a/lib/generators/inertia/install/templates/react/inertia.ts b/lib/generators/inertia/install/templates/react/inertia.ts index 4ef3a280..16f153fa 100644 --- a/lib/generators/inertia/install/templates/react/inertia.ts +++ b/lib/generators/inertia/install/templates/react/inertia.ts @@ -16,21 +16,30 @@ createInertiaApp({ // progress: false, resolve: (name) => { - const pages = import.meta.glob('../pages/**/*.tsx', { eager: true }) - return pages[`../pages/${name}.tsx`] + const pages = import.meta.glob('../pages/**/*.tsx', {eager: true}) + const page = pages[`../pages/${name}.tsx`] + if (!page) { + console.error(`Missing Inertia page component: '${name}.tsx'`) + } // To use a default layout, import the Layout component - // and use the following lines. + // and use the following line. // see https://inertia-rails.netlify.app/guide/pages#default-layouts // - // const page = pages[`../pages/${name}.tsx`] // page.default.layout ||= (page) => createElement(Layout, null, page) - // return page - }, - setup({ el, App, props }) { - const root = createRoot(el) + return page + }, - root.render(createElement(App, props)) + setup({el, App, props}) { + if (el) { + createRoot(el).render(createElement(App, props)) + } else { + console.error( + 'Missing root element.\n\n' + + 'If you see this error, it probably means you load Inertia.js on non-Inertia pages.\n' + + 'Consider moving <%%= vite_typescript_tag "inertia" %> to the Inertia-specific layout instead.' + ) + } }, }) diff --git a/lib/generators/inertia/install/templates/svelte/inertia.js b/lib/generators/inertia/install/templates/svelte/inertia.js index 1ea6c22f..e289e297 100644 --- a/lib/generators/inertia/install/templates/svelte/inertia.js +++ b/lib/generators/inertia/install/templates/svelte/inertia.js @@ -14,17 +14,29 @@ createInertiaApp({ resolve: (name) => { const pages = import.meta.glob('../pages/**/*.svelte', { eager: true }) - return pages[`../pages/${name}.svelte`] + const page = pages[`../pages/${name}.svelte`] + if (!page) { + console.error(`Missing Inertia page component: '${name}.svelte'`) + } // To use a default layout, import the Layout component - // and use the following lines. + // and use the following line. // see https://inertia-rails.netlify.app/guide/pages#default-layouts // - // const page = pages[`../pages/${name}.svelte`] // return { default: page.default, layout: page.layout || Layout } + + return page }, setup({ el, App, props }) { - mount(App, { target: el, props }) + if (el) { + mount(App, { target: el, props }) + } else { + console.error( + 'Missing root element.\n\n' + + 'If you see this error, it probably means you load Inertia.js on non-Inertia pages.\n' + + 'Consider moving <%%= vite_javascript_tag "inertia" %> to the Inertia-specific layout instead.' + ) + } }, }) diff --git a/lib/generators/inertia/install/templates/svelte/inertia.ts b/lib/generators/inertia/install/templates/svelte/inertia.ts index d6d55cd6..f524dbda 100644 --- a/lib/generators/inertia/install/templates/svelte/inertia.ts +++ b/lib/generators/inertia/install/templates/svelte/inertia.ts @@ -14,17 +14,30 @@ createInertiaApp({ resolve: (name) => { const pages = import.meta.glob('../pages/**/*.svelte', { eager: true }) - return pages[`../pages/${name}.svelte`] + const page = pages[`../pages/${name}.svelte`] + if (!page) { + console.error(`Missing Inertia page component: '${name}.svelte'`) + } // To use a default layout, import the Layout component - // and use the following lines. + // and use the following line. // see https://inertia-rails.netlify.app/guide/pages#default-layouts // - // const page = pages[`../pages/${name}.svelte`] // return { default: page.default, layout: page.layout || Layout } + + return page }, setup({ el, App, props }) { - mount(App, { target: el, props }) + if (el) { + <%= '// @ts-expect-error 1.3.0 beta contains types mismatch' if inertia_resolved_version == Gem::Version.new('1.3.0-beta.2') -%> + mount(App, { target: el, props }) + } else { + console.error( + 'Missing root element.\n\n' + + 'If you see this error, it probably means you load Inertia.js on non-Inertia pages.\n' + + 'Consider moving <%%= vite_typescript_tag "inertia" %> to the Inertia-specific layout instead.' + ) + } }, }) diff --git a/lib/generators/inertia/install/templates/svelte4/inertia.js b/lib/generators/inertia/install/templates/svelte4/inertia.js index 7c288a6e..7afb70ab 100644 --- a/lib/generators/inertia/install/templates/svelte4/inertia.js +++ b/lib/generators/inertia/install/templates/svelte4/inertia.js @@ -13,17 +13,29 @@ createInertiaApp({ resolve: (name) => { const pages = import.meta.glob('../pages/**/*.svelte', { eager: true }) - return pages[`../pages/${name}.svelte`] + const page = pages[`../pages/${name}.svelte`] + if (!page) { + console.error(`Missing Inertia page component: '${name}.svelte'`) + } // To use a default layout, import the Layout component // and use the following lines. // see https://inertia-rails.netlify.app/guide/pages#default-layouts // - // const page = pages[`../pages/${name}.svelte`] // return { default: page.default, layout: page.layout || Layout } + + return page }, setup({ el, App, props }) { - new App({ target: el, props }) + if (el) { + new App({ target: el, props }) + } else { + console.error( + 'Missing root element.\n\n' + + 'If you see this error, it probably means you load Inertia.js on non-Inertia pages.\n' + + 'Consider moving <%%= vite_javascript_tag "inertia" %> to the Inertia-specific layout instead.' + ) + } }, }) diff --git a/lib/generators/inertia/install/templates/svelte4/inertia.ts b/lib/generators/inertia/install/templates/svelte4/inertia.ts index a4a75495..ec543b2b 100644 --- a/lib/generators/inertia/install/templates/svelte4/inertia.ts +++ b/lib/generators/inertia/install/templates/svelte4/inertia.ts @@ -13,17 +13,29 @@ createInertiaApp({ resolve: (name) => { const pages = import.meta.glob('../pages/**/*.svelte', { eager: true }) - return pages[`../pages/${name}.svelte`] + const page = pages[`../pages/${name}.svelte`] + if (!page) { + console.error(`Missing Inertia page component: '${name}.svelte'`) + } // To use a default layout, import the Layout component - // and use the following lines. + // and use the following line. // see https://inertia-rails.netlify.app/guide/pages#default-layouts // - // const page = pages[`../pages/${name}.svelte`] // return { default: page.default, layout: page.layout || Layout } + + return page }, setup({ el, App, props }) { - new App({ target: el, props }) + if (el) { + new App({ target: el, props }) + } else { + console.error( + 'Missing root element.\n\n' + + 'If you see this error, it probably means you load Inertia.js on non-Inertia pages.\n' + + 'Consider moving <%%= vite_javascript_tag "inertia" %> to the Inertia-specific layout instead.' + ) + } }, }) diff --git a/lib/generators/inertia_templates/scaffold/templates/react/Form.tsx.tt b/lib/generators/inertia_templates/scaffold/templates/react/Form.tsx.tt index 6fdb23f9..3ec75444 100644 --- a/lib/generators/inertia_templates/scaffold/templates/react/Form.tsx.tt +++ b/lib/generators/inertia_templates/scaffold/templates/react/Form.tsx.tt @@ -1,7 +1,10 @@ import { FormEvent } from 'react' -import { useForm, InertiaFormProps } from '@inertiajs/react' +import { useForm <%= ', InertiaFormProps' if inertia_resolved_version.release >= Gem::Version.new('2.0.0') %>} from '@inertiajs/react' import { <%= inertia_model_type %>, <%= inertia_model_form_type %> } from './types' - +<% if inertia_resolved_version.release < Gem::Version.new('2.0.0') %> +// Temporary fix for InertiaFormProps not being exported from @inertiajs/react +type InertiaFormProps> = ReturnType> +<% end %> interface FormProps { <%= singular_table_name %>: <%= inertia_model_type %> onSubmit: (form: InertiaFormProps<<%= inertia_model_form_type %>>) => void diff --git a/lib/generators/inertia_templates/scaffold/templates/react/One.jsx.tt b/lib/generators/inertia_templates/scaffold/templates/react/One.jsx.tt index 5497c21f..b085e03d 100644 --- a/lib/generators/inertia_templates/scaffold/templates/react/One.jsx.tt +++ b/lib/generators/inertia_templates/scaffold/templates/react/One.jsx.tt @@ -17,7 +17,7 @@ export default function <%= inertia_component_name %>({ <%= singular_table_name ))} <% else -%> - {<%= singular_table_name %>.<%= attribute.column_name %>.toString()} + {<%= singular_table_name %>.<%= attribute.column_name %>?.toString()}

<% end -%> <% end -%> diff --git a/lib/generators/inertia_templates/scaffold/templates/react/One.tsx.tt b/lib/generators/inertia_templates/scaffold/templates/react/One.tsx.tt index 9b0f1ace..fa62a5fe 100644 --- a/lib/generators/inertia_templates/scaffold/templates/react/One.tsx.tt +++ b/lib/generators/inertia_templates/scaffold/templates/react/One.tsx.tt @@ -23,7 +23,7 @@ export default function <%= inertia_component_name %>({ <%= singular_table_name ))} <% else -%> - {<%= singular_table_name %>.<%= attribute.column_name %>.toString()} + {<%= singular_table_name %>.<%= attribute.column_name %>?.toString()}

<% end -%> <% end -%> diff --git a/lib/generators/inertia_templates/scaffold/templates/react/Show.tsx.tt b/lib/generators/inertia_templates/scaffold/templates/react/Show.tsx.tt index cdb7ed05..485afe19 100644 --- a/lib/generators/inertia_templates/scaffold/templates/react/Show.tsx.tt +++ b/lib/generators/inertia_templates/scaffold/templates/react/Show.tsx.tt @@ -1,4 +1,3 @@ -import { MouseEvent } from 'react' import { Link, Head } from '@inertiajs/react' import { <%= inertia_model_type %> } from './types' import <%= inertia_component_name %> from './<%= inertia_component_name %>' diff --git a/lib/generators/inertia_tw_templates/scaffold/templates/react/Form.tsx.tt b/lib/generators/inertia_tw_templates/scaffold/templates/react/Form.tsx.tt index d23e00ea..063f3cc3 100644 --- a/lib/generators/inertia_tw_templates/scaffold/templates/react/Form.tsx.tt +++ b/lib/generators/inertia_tw_templates/scaffold/templates/react/Form.tsx.tt @@ -1,7 +1,10 @@ import { FormEvent } from 'react' -import { useForm, InertiaFormProps } from '@inertiajs/react' +import { useForm <%= ', InertiaFormProps' if inertia_resolved_version.release >= Gem::Version.new('2.0.0') %>} from '@inertiajs/react' import { <%= inertia_model_type %>, <%= inertia_model_form_type %> } from './types' - +<% if inertia_resolved_version.release < Gem::Version.new('2.0.0') %> +// Temporary fix for InertiaFormProps not being exported from @inertiajs/react +type InertiaFormProps> = ReturnType> +<% end %> interface FormProps { <%= singular_table_name %>: <%= inertia_model_type %> onSubmit: (form: InertiaFormProps<<%= inertia_model_form_type %>>) => void diff --git a/lib/generators/inertia_tw_templates/scaffold/templates/react/Show.tsx.tt b/lib/generators/inertia_tw_templates/scaffold/templates/react/Show.tsx.tt index 30f4af52..66acd33a 100644 --- a/lib/generators/inertia_tw_templates/scaffold/templates/react/Show.tsx.tt +++ b/lib/generators/inertia_tw_templates/scaffold/templates/react/Show.tsx.tt @@ -1,4 +1,3 @@ -import { MouseEvent } from 'react' import { Link, Head } from '@inertiajs/react' import { <%= inertia_model_type %> } from './types' import <%= inertia_component_name %> from './<%= inertia_component_name %>' diff --git a/lib/inertia_rails/generators/helper.rb b/lib/inertia_rails/generators/helper.rb index 1d2d86e8..13f2e5f4 100644 --- a/lib/inertia_rails/generators/helper.rb +++ b/lib/inertia_rails/generators/helper.rb @@ -15,6 +15,7 @@ def guess_the_default_framework 'vue' else Thor::Shell::Basic.new.say_error 'Could not determine the Inertia.js framework you are using.' + exit 1 end end @@ -83,6 +84,12 @@ def js_resources_path route_url end + def inertia_resolved_version + @inertia_resolved_version ||= Gem::Version.new( + `npm show @inertiajs/core@#{options[:inertia_version]} version`.strip + ) + end + def ts_type(attribute) case attribute.type when :float, :decimal, :integer diff --git a/spec/generators/install/install_generator_spec.rb b/spec/generators/install/install_generator_spec.rb index 35972db4..e7341194 100644 --- a/spec/generators/install/install_generator_spec.rb +++ b/spec/generators/install/install_generator_spec.rb @@ -44,8 +44,8 @@ expect { generator }.to raise_error(SystemExit) end - context 'with --install-vite' do - let(:args) { super() + %w[--install-vite] } + context 'with --vite' do + let(:args) { super() + %w[--vite] } it 'installs Vite' do expect { generator }.not_to raise_error @@ -62,8 +62,8 @@ end end - context 'with --install-tailwind' do - let(:args) { super() + %w[--install-tailwind] } + context 'with --tailwind' do + let(:args) { super() + %w[--tailwind] } before { prepare_application }