ask-local-rails 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 6d9458eaa4ba6e80881586689b17a54398f3aa17a04b47d4ef0985eb5713129b
4
+ data.tar.gz: 71db3c0e17c54220ba5229404d94472b673f389576942eeade4c365da5477d28
5
+ SHA512:
6
+ metadata.gz: 74ff28edb6caeb714ccfccb0c77970bbd22f9a4736f0cfc422a3ec29c63a611c6390cfdbf6d00cd02e588ff74d45bbe1a61f87f3140d006f69cae5f6b85c1e9d
7
+ data.tar.gz: 94b9d2cde3ab259e4f2dcaf6d38a55ff07ce4e83d88fe91884b37a45ba28b78615d912e3b3dce498aa301491b8969e06e55932be9967cdefc4f96ac0b5548d2a
data/CHANGELOG.md ADDED
@@ -0,0 +1,11 @@
1
+ # Changelog
2
+
3
+ ## [0.1.0] — Unreleased
4
+
5
+ ### Added
6
+
7
+ - Initial release: Railtie (host-authorization warning in development),
8
+ `ask_local:install` generator (hosts, Cable origins, Procfile `$PORT`
9
+ rewrite, initializer, `ask-local.json`), `Ask::Local::Rails` helpers
10
+ (`url`, `host`, `proxied?`, `host_patterns`, `cable_origins`), and the
11
+ `local_dev` agent skill.
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Kaka Ruto
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,54 @@
1
+ # ask-local-rails
2
+
3
+ [![Gem Version](https://badge.fury.io/rb/ask-local-rails.svg)](https://badge.fury.io/rb/ask-local-rails)
4
+
5
+ Rails integration for [ask-local](https://github.com/ask-rb/ask-local).
6
+ Railtie, install generator, and helpers wiring a Rails app into stable
7
+ `https://<app>.localhost` URLs. Requires Rails 7.1+.
8
+
9
+ ## Installation
10
+
11
+ ```ruby
12
+ gem "ask-local-rails"
13
+ ```
14
+
15
+ ```bash
16
+ bundle install
17
+ rails generate ask_local:install
18
+ ```
19
+
20
+ The generator:
21
+
22
+ - creates `config/initializers/ask_local.rb`
23
+ - allows `.localhost` hosts + Cable origins in `development.rb`
24
+ - rewrites hardcoded `-p 3000` to `-p $PORT` in `Procfile.dev`/`Procfile`
25
+ (compound lines it cannot classify are left alone with a warning)
26
+ - creates an empty `ask-local.json` if missing
27
+
28
+ ## Helpers
29
+
30
+ ```ruby
31
+ Ask::Local::Rails.url # => "https://fix-ui.myapp.localhost" (or fallback)
32
+ Ask::Local::Rails.host # => "fix-ui.myapp.localhost"
33
+ Ask::Local::Rails.proxied? # => true when booted through ask-local
34
+ ```
35
+
36
+ Use `url` for mailer hosts, OmniAuth callbacks, and webhook targets —
37
+ never hardcode `localhost:3000`.
38
+
39
+ ## Agent skill
40
+
41
+ Ships `local_dev` under `ask/skills/` (auto-discovered by ask-skills):
42
+ boot via `ask-local`, wire via `ask-local get`, callbacks from
43
+ `ASK_LOCAL_URL`, `doctor`/`list`/`prune` for troubleshooting.
44
+
45
+ ## Development
46
+
47
+ ```bash
48
+ bundle install
49
+ bundle exec rake test
50
+ ```
51
+
52
+ ## License
53
+
54
+ MIT
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ask
4
+ module Local
5
+ # Helpers for app code that needs the stable local URL.
6
+ # Never hardcode localhost:3000 — read the injected env instead.
7
+ module Rails
8
+ module_function
9
+
10
+ # Full public URL, e.g. https://fix-ui.myapp.localhost
11
+ def url(fallback: "http://localhost:3000")
12
+ ENV["ASK_LOCAL_URL"] || fallback
13
+ end
14
+
15
+ # Bare hostname, e.g. fix-ui.myapp.localhost
16
+ def host(fallback: "localhost")
17
+ url(fallback: nil)&.sub(%r{\Ahttps?://}, "")&.split(":")&.first || fallback
18
+ end
19
+
20
+ # False outside ask-local (plain `rails s`).
21
+ def proxied?
22
+ !ENV["ASK_LOCAL_URL"].nil? && !ENV["ASK_LOCAL_URL"].empty?
23
+ end
24
+
25
+ # Host authorization patterns for config.hosts in development.
26
+ # TLDs default to ASK_LOCAL_TLD (comma separated) or "localhost",
27
+ # so custom-TLD setups work without regenerating config.
28
+ def host_patterns(tlds: nil)
29
+ tld_list(tlds).flat_map do |tld|
30
+ [/\A.+\.#{Regexp.escape(tld)}\z/, /\A#{Regexp.escape(tld)}\z/]
31
+ end
32
+ end
33
+
34
+ # Action Cable origin patterns for development.
35
+ def cable_origins(tlds: nil)
36
+ tld_list(tlds).map { |tld| %r{\Ahttps?://.+\.#{Regexp.escape(tld)}(:\d+)?\z} }
37
+ end
38
+
39
+ def tld_list(tlds)
40
+ list = tlds || ENV["ASK_LOCAL_TLD"]&.split(",")&.map(&:strip)
41
+ list = list.to_a.reject(&:nil?).map(&:to_s).reject(&:empty?)
42
+ list.empty? ? ["localhost"] : list.map(&:downcase).uniq
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ask
4
+ module Local
5
+ module Rails
6
+ # Rewrites hardcoded ports in Procfile lines to -p $PORT so the
7
+ # ask-local runner's injected PORT wins. Pure file logic, no Rails
8
+ # dependency, so it is unit-testable without a Rails app context.
9
+ module ProcfileRewrite
10
+ COMPOUND = /&&|\|\||[|;]/.freeze
11
+ HARDCODED_PORT = /(?:-p|--port)[=\s]+\d+/.freeze
12
+
13
+ module_function
14
+
15
+ # Returns true when the file changed. Yields (status, message)
16
+ # pairs for say_status. Compound lines it cannot classify are
17
+ # left alone with a warning — the portless lesson: never
18
+ # silently rewrite what you cannot parse.
19
+ def rewrite(path)
20
+ lines = File.readlines(path, chomp: true)
21
+ changed = false
22
+ skipped = []
23
+ updated = lines.map do |line|
24
+ stripped = line.strip
25
+ if stripped.empty? || stripped.start_with?("#") || !stripped.include?(":")
26
+ line
27
+ elsif stripped.match?(COMPOUND)
28
+ skipped << line
29
+ line
30
+ else
31
+ rewritten = line.gsub(HARDCODED_PORT, '-p $PORT')
32
+ changed ||= (rewritten != line)
33
+ rewritten
34
+ end
35
+ end
36
+ File.write(path, "#{updated.join("\n")}\n") if changed
37
+ if block_given?
38
+ skipped.each { |l| yield(:skip, "#{path}: compound line left alone: #{l.strip}") }
39
+ yield(:rewrite, path) if changed
40
+ end
41
+ changed
42
+ rescue SystemCallError
43
+ false
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ask
4
+ module Local
5
+ module Rails
6
+ class Railtie < ::Rails::Railtie
7
+ # Warn at boot when Host Authorization would reject the .localhost
8
+ # origin the proxy serves. The generator normally adds the patterns;
9
+ # this catches apps that skipped it or run custom TLDs.
10
+ initializer "ask_local_rails.hosts" do |app|
11
+ next unless ::Rails.env.development?
12
+ next unless Ask::Local::Rails.proxied?
13
+
14
+ host = Ask::Local::Rails.host
15
+ begin
16
+ authorized = app.config.hosts.any? do |pattern|
17
+ pattern.is_a?(Regexp) ? host.match?(pattern) : pattern == host
18
+ end
19
+ unless authorized
20
+ ::Rails.logger&.warn(
21
+ "[ask-local] Host #{host} is not in config.hosts. " \
22
+ "Run: rails generate ask_local:install"
23
+ )
24
+ end
25
+ rescue StandardError
26
+ nil
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ask
4
+ module Local
5
+ module Rails
6
+ VERSION = "0.1.0"
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "ask/local/rails/version"
4
+ require_relative "ask/local/rails/helpers"
5
+ require_relative "ask/local/rails/procfile_rewrite"
6
+ require_relative "ask/local/rails/railtie" if defined?(::Rails::Railtie)
7
+
8
+ module Ask
9
+ module Local
10
+ module Rails
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+
5
+ module AskLocal
6
+ module Generators
7
+ class InstallGenerator < ::Rails::Generators::Base
8
+ source_root File.expand_path("templates", __dir__)
9
+
10
+ desc "Wires a Rails app into ask-local: hosts, Cable origins, Procfile $PORT, initializer"
11
+
12
+ class_option :tld, type: :string, default: "localhost",
13
+ desc: "TLD ask-local serves this app under"
14
+
15
+ def create_initializer
16
+ template "initializer.rb", "config/initializers/ask_local.rb"
17
+ end
18
+
19
+ def patch_development_hosts
20
+ tld = options[:tld]
21
+ sentinel = "Ask::Local::Rails.host_patterns"
22
+ path = app_path("config/environments/development.rb")
23
+ return unless File.file?(path)
24
+
25
+ content = File.read(path)
26
+ return if content.include?(sentinel)
27
+
28
+ inject_into_file path,
29
+ "\n # ask-local: allow stable .#{tld} URLs (https://<app>.#{tld}).\n" \
30
+ " config.hosts.concat(Ask::Local::Rails.host_patterns(tlds: [#{tld.dump}]))\n",
31
+ after: "Rails.application.configure do\n"
32
+ end
33
+
34
+ def patch_action_cable
35
+ tld = options[:tld]
36
+ sentinel = "Ask::Local::Rails.cable_origins"
37
+ path = app_path("config/environments/development.rb")
38
+ return unless File.file?(path)
39
+
40
+ content = File.read(path)
41
+ return if content.include?(sentinel)
42
+
43
+ inject_into_file path,
44
+ "\n # ask-local: allow Action Cable connections from .#{tld} origins.\n" \
45
+ " config.action_cable.allowed_request_origins ||= []\n" \
46
+ " config.action_cable.allowed_request_origins.concat(Ask::Local::Rails.cable_origins(tlds: [#{tld.dump}]))\n",
47
+ after: "Rails.application.configure do\n"
48
+ end
49
+
50
+ def rewrite_procfile_ports
51
+ %w[Procfile.dev Procfile].each do |file|
52
+ path = app_path(file)
53
+ next unless File.file?(path)
54
+
55
+ Ask::Local::Rails::ProcfileRewrite.rewrite(path) do |status, message|
56
+ say_status status, message, status == :rewrite ? :green : :yellow
57
+ end
58
+ end
59
+ end
60
+
61
+ def create_ask_local_config
62
+ return if File.file?(app_path("ask-local.json"))
63
+
64
+ create_file "ask-local.json", "{}\n"
65
+ end
66
+
67
+ private
68
+
69
+ # Generators may run with a destination_root different from the
70
+ # process cwd (test harnesses, custom destinations) — resolve file
71
+ # checks against destination_root, which is the app root in real
72
+ # Rails usage.
73
+ def app_path(relative)
74
+ File.expand_path(relative, destination_root)
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ # ask-local integration for this Rails app.
4
+ #
5
+ # When booted through ask-local, the runner injects ASK_LOCAL_URL
6
+ # (e.g. https://fix-ui.myapp.localhost). Use Ask::Local::Rails.url
7
+ # anywhere the app must know its own address instead of hardcoding
8
+ # localhost:3000:
9
+ #
10
+ # # config/environments/development.rb
11
+ # config.action_mailer.default_url_options = {
12
+ # host: Ask::Local::Rails.host
13
+ # }
14
+ #
15
+ # Nothing to configure here — this file documents the integration.
16
+ # See Ask::Local::Rails.host_patterns / .cable_origins for the entries
17
+ # the install generator added to config/environments/development.rb.
18
+ #
19
+ # --- Asset dev servers (Vite-Ruby / Shakapacker) --------------------------
20
+ # Run the dev server through ask-local as its own service and let Rails
21
+ # assets point at it, avoiding mixed-content and cert juggling:
22
+ #
23
+ # ask-local --service webpack -- bin/shakapacker-dev-server
24
+ # -> https://webpack.<app>.localhost
25
+ #
26
+ # # config/environments/development.rb
27
+ # config.action_controller.asset_host =
28
+ # proc { |src| "//webpack.#{Ask::Local::Rails.host}" if src.start_with?("/packs") }
29
+ #
30
+ # # config/shakapacker.yml (or vite.json): set dev_server.public to
31
+ # # webpack.<app>.localhost and https: false — the proxy terminates TLS.
metadata ADDED
@@ -0,0 +1,125 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ask-local-rails
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Kaka Ruto
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: rails
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '7.1'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '7.1'
26
+ - !ruby/object:Gem::Dependency
27
+ name: ask-local
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 0.1.0
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: 0.1.0
40
+ - !ruby/object:Gem::Dependency
41
+ name: minitest
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '5.25'
47
+ type: :development
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '5.25'
54
+ - !ruby/object:Gem::Dependency
55
+ name: mocha
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '3.1'
61
+ type: :development
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '3.1'
68
+ - !ruby/object:Gem::Dependency
69
+ name: rake
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '13.0'
75
+ type: :development
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: '13.0'
82
+ description: 'Railtie, install generator, and helpers wiring a Rails app into ask-local''s
83
+ stable .localhost URLs: host authorization, Action Cable origins, Procfile $PORT
84
+ rewrite, and an agent skill for local development.'
85
+ email:
86
+ - kaka@myrrlabs.com
87
+ executables: []
88
+ extensions: []
89
+ extra_rdoc_files: []
90
+ files:
91
+ - CHANGELOG.md
92
+ - LICENSE
93
+ - README.md
94
+ - lib/ask-local-rails.rb
95
+ - lib/ask/local/rails/helpers.rb
96
+ - lib/ask/local/rails/procfile_rewrite.rb
97
+ - lib/ask/local/rails/railtie.rb
98
+ - lib/ask/local/rails/version.rb
99
+ - lib/generators/ask_local/install/install_generator.rb
100
+ - lib/generators/ask_local/install/templates/initializer.rb
101
+ homepage: https://github.com/ask-rb/ask-local-rails
102
+ licenses:
103
+ - MIT
104
+ metadata:
105
+ homepage_uri: https://github.com/ask-rb/ask-local-rails
106
+ source_code_uri: https://github.com/ask-rb/ask-local-rails
107
+ changelog_uri: https://github.com/ask-rb/ask-local-rails/blob/master/CHANGELOG.md
108
+ rdoc_options: []
109
+ require_paths:
110
+ - lib
111
+ required_ruby_version: !ruby/object:Gem::Requirement
112
+ requirements:
113
+ - - ">="
114
+ - !ruby/object:Gem::Version
115
+ version: '3.2'
116
+ required_rubygems_version: !ruby/object:Gem::Requirement
117
+ requirements:
118
+ - - ">="
119
+ - !ruby/object:Gem::Version
120
+ version: '0'
121
+ requirements: []
122
+ rubygems_version: 4.0.18
123
+ specification_version: 4
124
+ summary: Rails integration for ask-local
125
+ test_files: []