hotwire_native_rails 0.4.4 → 0.5.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: 6b64d65dc8f90e19441ff12633025b080fa0a99096dc3d32c2477aeda01780d1
4
- data.tar.gz: 58c56ca7d31535fdb87028170962e16d28ac589cfde0b7d5b1f04cc7214aa7e9
3
+ metadata.gz: b9a2e5493ea7c812bac6c062d58bcd6d2cecf0813e98bbe1c8b04b1d1f19624b
4
+ data.tar.gz: f8d1b8babe84f397588fb343f4941a22d633fc1364c2a32b030547e0b08e2fae
5
5
  SHA512:
6
- metadata.gz: 93ce3791ac063e486b1ff7486774f927398d05b99f70681b840bfd6725623e3f6cf7e7a4a60697dee5df7222f5a33c10467908846881e28eda3c656e9eae910e
7
- data.tar.gz: aa745e677e5099397ef9be5583b10f6ccc765cb2dd6dd5277753ae897409928881321c55ee26ff9ef91529d5566dc14a84e82f949ded6485fe5cc48894f10287
6
+ metadata.gz: 214c4723dbfb23fb8f7e056217f19804bcbda9c49bd2afb92b881e481ea6ae9b893007a8ec70b23ef9949314ec86b95b9eb582338335c6c8b36aca212d264eb4
7
+ data.tar.gz: 92d65d6d21760e3334bd109d3d2fb8851b6d84c486f7171ac3b36e91fa79b26076ea503242a4f80bbfb5754bde39b630ef7e391d27d0ef22562ef4c0b3b58cf7
@@ -1,27 +1,30 @@
1
- name: Ruby
1
+ name: Smoke test
2
2
 
3
3
  on:
4
4
  push:
5
5
  branches:
6
- - master
7
-
6
+ - main
8
7
  pull_request:
9
8
 
10
9
  jobs:
11
- build:
10
+ smoke:
12
11
  runs-on: ubuntu-latest
13
- name: Ruby ${{ matrix.ruby }}
12
+ name: ${{ matrix.flavor }}
14
13
  strategy:
14
+ fail-fast: false
15
15
  matrix:
16
- ruby:
17
- - '3.3.3'
16
+ flavor:
17
+ - tailwind4
18
+ - tailwind3
19
+ - css
18
20
 
19
21
  steps:
20
22
  - uses: actions/checkout@v4
21
23
  - name: Set up Ruby
22
24
  uses: ruby/setup-ruby@v1
23
25
  with:
24
- ruby-version: ${{ matrix.ruby }}
25
- bundler-cache: true
26
- - name: Run the default task
27
- run: bundle exec rake
26
+ ruby-version: '3.4'
27
+ - name: Install Rails
28
+ run: gem install rails --no-document
29
+ - name: Generate an app, run the generator and its tests
30
+ run: bin/smoke ${{ matrix.flavor }}
data/.gitignore CHANGED
@@ -7,3 +7,4 @@
7
7
  /spec/reports/
8
8
  /tmp/
9
9
  /hotwire_native_rails-*.gem
10
+ /Gemfile.lock
data/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.5.0] - 2026-11-14
4
+
5
+ - Generated code uses `hotwire_native_app?` instead of `turbo_native_app?` (#2). Requires `turbo-rails` >= 2.0.11.
6
+ - Fix: Tailwind 3 apps crashed the generator (`add_tailwind_3_css_variants` was undefined).
7
+ - Fix: Tailwind 4 apps did not get the `hotwire-native:` variants, because `@import "tailwindcss";` (double quotes) was not matched.
8
+ - Fix: the `link_to` override now also drops `target` for internal links written with a block, and no longer modifies the options hash passed in (#9).
9
+ - `internal_url?` compares against the request host instead of `root_url`, and treats `mailto:`/`tel:` links as external.
10
+ - README: the request variant is `:native` (`index.html+native.erb`), not `:mobile`.
11
+ - CI runs the generator in fresh Tailwind 4, Tailwind 3 and plain-CSS Rails apps (`bin/smoke`).
12
+
3
13
  ## [0.1.0] - 2024-11-30
4
14
 
5
15
  - Initial release
data/README.md CHANGED
@@ -4,6 +4,8 @@ Power pack to make your Rails app [Hotwire Native](https://native.hotwired.dev)
4
4
 
5
5
  ## Installation
6
6
 
7
+ Requires `turbo-rails` 2.0.11 or newer (for the `hotwire_native_app?` helper).
8
+
7
9
  Install the gem:
8
10
 
9
11
  ```sh
@@ -18,13 +20,11 @@ rails g hotwire_native
18
20
 
19
21
  ## Usage
20
22
 
21
- Recommended to use with [my fork of iOS Hotwire Native starter app](https://github.com/yshmarov/hotwire-native-ios/tree/superails-extensions-2/Demo).
22
-
23
23
  #### Helpers
24
24
  - `viewport_meta_tag` - forbid zooming on mobile/native
25
25
  - use `data: { turbo_action: replace_if_native }` to submit authentication forms & forms in modals
26
- - `:mobile` request variant. `index.html+mobile.erb`
27
- - override `link_to` to not open internal links in in-app browser on native app
26
+ - `:native` request variant for Hotwire Native requests: `index.html+native.erb`
27
+ - override `link_to` so internal links open in the app, not the in-app browser (drops `target="_blank"` for your own host; works with and without a block)
28
28
  - conditionally override page `<title>` for native apps
29
29
 
30
30
  #### CSS
@@ -49,14 +49,21 @@ Run the local version of the gem:
49
49
  gem 'hotwire_native_rails', path: '/Users/myusername/Documents/GitHub.nosync/hotwire_native_rails'
50
50
  ```
51
51
 
52
+ Test the generator in brand-new Rails apps (Tailwind 4, Tailwind 3 and plain CSS). Each run generates an app under `tmp/`, runs `rails g hotwire_native`, checks what it changed and runs the generated tests:
53
+
54
+ ```sh
55
+ bin/smoke tailwind4 # or tailwind3, css
56
+ bundle exec rake # all three
57
+ ```
58
+
52
59
  Make a release to rubygems:
53
60
 
54
61
  ```sh
55
- # 1. update version in version.rb
56
- # 2. zip the gem
62
+ # 1. update version in version.rb and CHANGELOG.md
63
+ # 2. build the gem
57
64
  gem build hotwire_native_rails.gemspec
58
- # 3. push the zip to rubygems
59
- gem push hotwire_native_rails-0.4.0.gem
65
+ # 3. push it to rubygems
66
+ gem push hotwire_native_rails-X.Y.Z.gem
60
67
  ```
61
68
 
62
69
  - [Github source](https://github.com/yshmarov/hotwire_native_rails)
data/Rakefile CHANGED
@@ -1,2 +1,13 @@
1
1
  require "bundler/gem_tasks"
2
+
3
+ SMOKE_FLAVORS = %w[tailwind4 tailwind3 css].freeze
4
+
5
+ desc "Run the generator in fresh Rails apps (#{SMOKE_FLAVORS.join(", ")}) and run their tests"
6
+ task :test do
7
+ # bin/smoke runs `rails new` and the new app's own bundle, so leave this gem's bundle.
8
+ Bundler.with_unbundled_env do
9
+ SMOKE_FLAVORS.each { |flavor| sh "bin/smoke #{flavor}" }
10
+ end
11
+ end
12
+
2
13
  task default: :test
data/bin/smoke ADDED
@@ -0,0 +1,59 @@
1
+ #!/usr/bin/env bash
2
+ # Smoke-tests the generator in a brand-new Rails app, the way a user runs it.
3
+ #
4
+ # bin/smoke tailwind4 rails new --css=tailwind (tailwindcss-rails 4)
5
+ # bin/smoke tailwind3 a config/tailwind.config.js app (Tailwind 3 setup)
6
+ # bin/smoke css plain CSS, no Tailwind
7
+ #
8
+ # It generates the app under tmp/, installs this checkout of the gem, runs
9
+ # `rails g hotwire_native`, checks the files it should have changed, then runs
10
+ # the tests the generator copied into the app.
11
+ set -euo pipefail
12
+
13
+ flavor="${1:-tailwind4}"
14
+ root="$(cd "$(dirname "$0")/.." && pwd)"
15
+ app="$root/tmp/smoke-$flavor"
16
+
17
+ rm -rf "$app"
18
+ mkdir -p "$root/tmp"
19
+
20
+ css_flag=()
21
+ if [ "$flavor" = "tailwind4" ]; then css_flag=(--css=tailwind); fi
22
+
23
+ rails new "$app" ${css_flag[@]+"${css_flag[@]}"} --quiet --skip-git --skip-docker --skip-kamal \
24
+ --skip-thruster --skip-solid --skip-action-mailbox --skip-action-text \
25
+ --skip-active-storage --skip-jbuilder --skip-ci --skip-rubocop --skip-brakeman \
26
+ --skip-system-test --skip-bootsnap
27
+
28
+ cd "$app"
29
+
30
+ if [ "$flavor" = "tailwind3" ]; then
31
+ printf 'module.exports = {\n plugins: [\n ]\n}\n' > config/tailwind.config.js
32
+ fi
33
+
34
+ bundle add hotwire_native_rails --path "$root"
35
+
36
+ # The generated integration tests request root_path, so give the app a page.
37
+ bin/rails generate controller pages home --skip-routes --no-helper --quiet
38
+ ruby -pi -e 'sub(/^Rails.application.routes.draw do\n/) { "#{$&} root \"pages#home\"\n" }' config/routes.rb
39
+
40
+ bin/rails generate hotwire_native
41
+
42
+ fail() { echo "SMOKE FAIL ($flavor): $*" >&2; exit 1; }
43
+ layout=app/views/layouts/application.html.erb
44
+ grep -q "viewport_meta_tag" "$layout" || fail "viewport_meta_tag not in layout"
45
+ grep -q "platform_identifier" "$layout" || fail "platform_identifier not on <html>"
46
+ grep -q "page_title" "$layout" || fail "page_title not in <title>"
47
+ grep -q "include DeviceFormat" app/controllers/application_controller.rb || fail "DeviceFormat not included"
48
+ grep -q "draw(:hotwire_native)" config/routes.rb || fail "routes not drawn"
49
+ grep -rq "turbo_native_app?" app test && fail "turbo_native_app? still generated"
50
+
51
+ case "$flavor" in
52
+ tailwind4) grep -q "@custom-variant hotwire-native" app/assets/tailwind/application.css || fail "Tailwind 4 variants not added" ;;
53
+ tailwind3) grep -q 'addVariant("hotwire-native"' config/tailwind.config.js || fail "Tailwind 3 variants not added" ;;
54
+ css) grep -q "body.hotwire-native" app/assets/stylesheets/application.css || fail "CSS class not added"
55
+ grep -q "hotwire_native_app?" "$layout" || fail "body class not added" ;;
56
+ esac
57
+
58
+ bin/rails test
59
+ echo "SMOKE OK ($flavor)"
@@ -11,6 +11,10 @@ Gem::Specification.new do |spec|
11
11
  spec.summary = "Generate Hotwire Native Rails helpers and bridge components"
12
12
  spec.homepage = "https://github.com/yshmarov/hotwire_native_rails"
13
13
  spec.license = "MIT"
14
+ spec.required_ruby_version = ">= 3.1"
15
+
16
+ # hotwire_native_app? (used by every generated helper) arrived in turbo-rails 2.0.11.
17
+ spec.add_dependency "turbo-rails", ">= 2.0.11"
14
18
 
15
19
  spec.metadata["homepage_uri"] = spec.homepage
16
20
  spec.metadata["source_code_uri"] = spec.homepage
@@ -85,7 +85,7 @@ class HotwireNativeGenerator < Rails::Generators::Base
85
85
 
86
86
  # class="hotwire-native:hidden"
87
87
  # class="non-hotwire-native:hidden"
88
- def add_tailwind_css_variants
88
+ def add_tailwind_3_css_variants
89
89
  prepend_to_file "config/tailwind.config.js", "const plugin = require('tailwindcss/plugin')\n"
90
90
 
91
91
  inject_into_file "config/tailwind.config.js", after: "plugins: [" do
@@ -100,7 +100,7 @@ class HotwireNativeGenerator < Rails::Generators::Base
100
100
  end
101
101
 
102
102
  def add_tailwind_4_css_variants
103
- inject_into_file "app/assets/tailwind/application.css", after: "@import 'tailwindcss';\n" do
103
+ inject_into_file "app/assets/tailwind/application.css", after: /@import ["']tailwindcss["'];\n/ do
104
104
  <<-CSS
105
105
  @custom-variant hotwire-native {
106
106
  html[data-hotwire-native] & {
@@ -119,7 +119,7 @@ class HotwireNativeGenerator < Rails::Generators::Base
119
119
 
120
120
  # class="hotwire-native:hidden"
121
121
  def add_hotwire_native_css
122
- gsub_file "app/views/layouts/application.html.erb", "<body>", "<body class=\"<%= \"hotwire-native\" if turbo_native_app? %>\">"
122
+ gsub_file "app/views/layouts/application.html.erb", "<body>", "<body class=\"<%= \"hotwire-native\" if hotwire_native_app? %>\">"
123
123
 
124
124
  append_to_file "app/assets/stylesheets/application.css" do
125
125
  <<-CSS
@@ -8,8 +8,8 @@ module DeviceFormat
8
8
  private
9
9
 
10
10
  def set_variant
11
- # request.variant = :mobile if turbo_native_app? || browser.device.mobile?
11
+ # request.variant = :mobile if hotwire_native_app? || browser.device.mobile?
12
12
  # request.variant = :mobile if browser.device.mobile?
13
- request.variant = :native if turbo_native_app?
13
+ request.variant = :native if hotwire_native_app?
14
14
  end
15
15
  end
@@ -10,7 +10,7 @@ module HotwireNativeHelper
10
10
  # <% content_for :title, "Sign in | My App" %>
11
11
  def page_title
12
12
  title_options = [
13
- (content_for(:hotwire_native_title) if turbo_native_app?),
13
+ (content_for(:hotwire_native_title) if hotwire_native_app?),
14
14
  content_for(:title),
15
15
  @page_title,
16
16
  Rails.application.class.module_parent.name
@@ -22,13 +22,13 @@ module HotwireNativeHelper
22
22
  # forbid zooming on mobile devices
23
23
  def viewport_meta_tag
24
24
  content = ['width=device-width,initial-scale=1']
25
- content << 'maximum-scale=1, user-scalable=0' if turbo_native_app? || browser.device.mobile?
25
+ content << 'maximum-scale=1, user-scalable=0' if hotwire_native_app? || browser.device.mobile?
26
26
  tag.meta(name: 'viewport', content: content.join(','))
27
27
  end
28
28
 
29
29
  # set on <html> tag
30
30
  def platform_identifier
31
- 'data-hotwire-native' if turbo_native_app?
31
+ 'data-hotwire-native' if hotwire_native_app?
32
32
  end
33
33
 
34
34
  # link_to 'Next', next_path, data: { turbo_action: replace_if_native }
@@ -36,15 +36,33 @@ module HotwireNativeHelper
36
36
  # caution: if you open a modal on top of the root page, and replace, it will replace the root page.
37
37
  # projects#show will replace projects#index => BAD
38
38
  def replace_if_native
39
- return 'replace' if turbo_native_app?
39
+ return 'replace' if hotwire_native_app?
40
40
 
41
41
  'advance'
42
42
  end
43
43
 
44
- # override link_to to not open internal links in in-app browser on native app
45
- def link_to(name = nil, options = nil, html_options = {}, &)
46
- html_options[:target] = '' if turbo_native_app? && internal_url?(url_for(options))
47
- super
44
+ # In the native app, drop target="_blank" from links to your own host, so they
45
+ # open in the app instead of the in-app browser. External links keep it.
46
+ # Handles both forms:
47
+ # link_to "Home", root_path, target: "_blank"
48
+ # link_to root_path, target: "_blank" do ... end
49
+ # (with a block, the URL and HTML options arrive one argument earlier).
50
+ # The caller's options hash is copied, never modified.
51
+ def link_to(name = nil, options = nil, html_options = nil, &block)
52
+ if hotwire_native_app?
53
+ url, attributes = block ? [name, options] : [options, html_options]
54
+
55
+ if url && attributes.is_a?(Hash) && internal_url?(url_for(url))
56
+ attributes = attributes.except(:target, 'target')
57
+ if block
58
+ options = attributes
59
+ else
60
+ html_options = attributes
61
+ end
62
+ end
63
+ end
64
+
65
+ super(name, options, html_options, &block)
48
66
  end
49
67
 
50
68
  # https://github.com/joemasilotti/daily-log/blob/main/rails/app/helpers/form_helper.rb
@@ -82,11 +100,14 @@ module HotwireNativeHelper
82
100
  }
83
101
  end
84
102
 
103
+ # A relative path, or an http(s) URL on the current host.
104
+ # mailto:, tel: and other schemes are never internal.
85
105
  def internal_url?(url)
86
106
  uri = URI.parse(url)
87
- return true if uri.path.present? && uri.host.blank?
88
- return true if url.include?(root_url)
107
+ return false if uri.scheme.present? && !uri.scheme.match?(/\Ahttps?\z/)
89
108
 
109
+ uri.host.blank? || uri.host == request.host
110
+ rescue URI::InvalidURIError
90
111
  false
91
112
  end
92
113
  end
@@ -7,8 +7,8 @@ class HotwireNativeHelperTest < ActionView::TestCase
7
7
  end
8
8
  end
9
9
 
10
- test 'link_to removes target for internal links in turbo native app' do
11
- def turbo_native_app?
10
+ test 'link_to removes target for internal links in hotwire native app' do
11
+ def hotwire_native_app?
12
12
  true
13
13
  end
14
14
 
@@ -16,8 +16,8 @@ class HotwireNativeHelperTest < ActionView::TestCase
16
16
  assert_no_match(/target="_blank"/, result)
17
17
  end
18
18
 
19
- test 'link_to keeps target for external links in turbo native app' do
20
- def turbo_native_app?
19
+ test 'link_to keeps target for external links in hotwire native app' do
20
+ def hotwire_native_app?
21
21
  true
22
22
  end
23
23
 
@@ -25,8 +25,8 @@ class HotwireNativeHelperTest < ActionView::TestCase
25
25
  assert_match(/target="_blank"/, result)
26
26
  end
27
27
 
28
- test 'link_to keeps target for internal links when not in turbo native app' do
29
- def turbo_native_app?
28
+ test 'link_to keeps target for internal links when not in hotwire native app' do
29
+ def hotwire_native_app?
30
30
  false
31
31
  end
32
32
 
@@ -34,8 +34,8 @@ class HotwireNativeHelperTest < ActionView::TestCase
34
34
  assert_match(/target="_blank"/, result)
35
35
  end
36
36
 
37
- test 'link_to keeps target for external links when not in turbo native app' do
38
- def turbo_native_app?
37
+ test 'link_to keeps target for external links when not in hotwire native app' do
38
+ def hotwire_native_app?
39
39
  false
40
40
  end
41
41
 
@@ -43,11 +43,42 @@ class HotwireNativeHelperTest < ActionView::TestCase
43
43
  assert_match(/target="_blank"/, result)
44
44
  end
45
45
 
46
+ test 'link_to with a block removes target for internal links in hotwire native app' do
47
+ def hotwire_native_app?
48
+ true
49
+ end
50
+
51
+ result = link_to('/foo/bar', target: '_blank') { 'Home' }
52
+ assert_no_match(/target=/, result)
53
+ assert_match(%r{href="/foo/bar"}, result)
54
+ end
55
+
56
+ test 'link_to with a block keeps target for external links in hotwire native app' do
57
+ def hotwire_native_app?
58
+ true
59
+ end
60
+
61
+ result = link_to('http://external.com', target: '_blank') { 'External' }
62
+ assert_match(/target="_blank"/, result)
63
+ end
64
+
65
+ test 'link_to does not modify the html options passed in' do
66
+ def hotwire_native_app?
67
+ true
68
+ end
69
+
70
+ html_options = { target: '_blank' }
71
+ link_to('Home', '/foo/bar', html_options)
72
+ assert_equal({ target: '_blank' }, html_options)
73
+ end
74
+
46
75
  test 'internal_url? returns true for internal links' do
47
76
  assert internal_url?('http://test.host/some_path')
77
+ assert internal_url?('/some_path')
48
78
  end
49
79
 
50
80
  test 'internal_url? returns false for external links' do
51
81
  assert_not internal_url?('http://external.com')
82
+ assert_not internal_url?('mailto:hello@test.host')
52
83
  end
53
84
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module HotwireNativeRails
4
- VERSION = "0.4.4"
4
+ VERSION = "0.5.0"
5
5
  end
metadata CHANGED
@@ -1,14 +1,28 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hotwire_native_rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.4
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yaro Shm
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-03-11 00:00:00.000000000 Z
11
- dependencies: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: turbo-rails
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: 2.0.11
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: 2.0.11
12
26
  email:
13
27
  - yshmarov@gmail.com
14
28
  executables: []
@@ -24,6 +38,7 @@ files:
24
38
  - LICENSE.txt
25
39
  - README.md
26
40
  - Rakefile
41
+ - bin/smoke
27
42
  - hotwire_native_rails.gemspec
28
43
  - lib/generators/hotwire_native/hotwire_native_generator.rb
29
44
  - lib/generators/hotwire_native/templates/controllers/concerns/device_format.rb
@@ -57,14 +72,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
57
72
  requirements:
58
73
  - - ">="
59
74
  - !ruby/object:Gem::Version
60
- version: '0'
75
+ version: '3.1'
61
76
  required_rubygems_version: !ruby/object:Gem::Requirement
62
77
  requirements:
63
78
  - - ">="
64
79
  - !ruby/object:Gem::Version
65
80
  version: '0'
66
81
  requirements: []
67
- rubygems_version: 3.6.2
82
+ rubygems_version: 4.0.20
68
83
  specification_version: 4
69
84
  summary: Generate Hotwire Native Rails helpers and bridge components
70
85
  test_files: []