ruby-live-reload 0.2.7
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 +7 -0
- data/LICENSE +21 -0
- data/README.md +3 -0
- data/bin/rlr +4 -0
- data/bin/ruby-live-reload +3 -0
- data/lib/ruby_live_reload/options.rb +70 -0
- data/lib/ruby_live_reload/version.rb +3 -0
- data/lib/ruby_live_reload.rb +212 -0
- metadata +146 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: d9145bba59fc4b7d871e48c6c2c5b6e8d2d66f991a267540c9f74867ad3c5728
|
|
4
|
+
data.tar.gz: 87908f936203b08cf1e03408da428bc73f01bc5a880612d525eebcf169dfc835
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 53d12a050bcf33fa115496e1176a39f1d4bac46b1426d9dd7184b7da7e2226ebf9f337b2402452d006c9ee1f8f27592e36a5d9a7d806489b81d971693121a48a
|
|
7
|
+
data.tar.gz: 41c6fc10519d6d69792619fba889edb6edec12a2b9ef1d1a8fe3a8b77372ce7e81feee6f7eac1db8bdc1c516494a401995ef00236170a5497cbf62f71c066825
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 James
|
|
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
data/bin/rlr
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
module RubyLiveReload
|
|
2
|
+
class Options
|
|
3
|
+
|
|
4
|
+
private
|
|
5
|
+
|
|
6
|
+
attr_writer :host, :port, :directory, :proxy, :message
|
|
7
|
+
|
|
8
|
+
def initialize
|
|
9
|
+
@host = "127.0.0.1"
|
|
10
|
+
@port = 8080
|
|
11
|
+
@directory = Dir.pwd
|
|
12
|
+
@proxy = nil
|
|
13
|
+
@message = nil
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
public
|
|
17
|
+
|
|
18
|
+
attr_reader :host, :port, :directory, :proxy, :message
|
|
19
|
+
|
|
20
|
+
def self.parse(args)
|
|
21
|
+
|
|
22
|
+
unless [String, Array].include? args.class
|
|
23
|
+
raise <<~ERROR.strip
|
|
24
|
+
Provide an array or a space-separated string of arguments: ["-p", 9090, "--host", "198.168.0.42"] or "-p 9090 --host 192.168.0.42"
|
|
25
|
+
ERROR
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
args = args.split " " if args.is_a? String
|
|
29
|
+
|
|
30
|
+
instance = Options.new
|
|
31
|
+
|
|
32
|
+
OptionParser.new do |options|
|
|
33
|
+
options.banner = "Usage: rlr [instance]"
|
|
34
|
+
options.release = VERSION
|
|
35
|
+
|
|
36
|
+
options.on("-b HOST", "--bind HOST", "Hostname") do |host|
|
|
37
|
+
instance.send :host=, host
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
options.on("-p PORT", "--port PORT", "Port") do |port|
|
|
41
|
+
instance.send :port=, port
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
options.on("--proxy URL", "Url of the proxied app") do |proxy|
|
|
45
|
+
instance.send :proxy=, proxy
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
options.on("-d PATH", "--directory PATH", "Path") do |path|
|
|
49
|
+
directory = if path.start_with?("/")
|
|
50
|
+
path
|
|
51
|
+
else
|
|
52
|
+
File.join(Dir.pwd, path)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# TODO Validate path is a directory
|
|
56
|
+
|
|
57
|
+
instance.send :directory=, directory
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
options.on("-v", "--version", "Version") do
|
|
61
|
+
instance.send :message=, VERSION
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
end.parse(args)
|
|
65
|
+
|
|
66
|
+
return instance
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
end
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
require "optparse"
|
|
2
|
+
require "sinatra/base"
|
|
3
|
+
require "filewatcher"
|
|
4
|
+
require "faraday"
|
|
5
|
+
|
|
6
|
+
require_relative "ruby_live_reload/version.rb"
|
|
7
|
+
require_relative "ruby_live_reload/options.rb"
|
|
8
|
+
|
|
9
|
+
# https://blog.appsignal.com/2024/11/27/server-sent-events-and-websockets-in-rack-for-ruby.html
|
|
10
|
+
|
|
11
|
+
module RubyLiveReload
|
|
12
|
+
|
|
13
|
+
class Server < Sinatra::Base
|
|
14
|
+
|
|
15
|
+
set :quiet, true
|
|
16
|
+
set :server_settings, { max_threads: 256, Silent: true }
|
|
17
|
+
|
|
18
|
+
set :clients, Set.new
|
|
19
|
+
|
|
20
|
+
on_start do
|
|
21
|
+
puts <<~_
|
|
22
|
+
Ruby Live Reload running with #{settings.threads} threads!
|
|
23
|
+
Watching #{settings.directory}
|
|
24
|
+
|
|
25
|
+
http://#{settings.bind}:#{settings.port}
|
|
26
|
+
|
|
27
|
+
Ctrl-c to stop
|
|
28
|
+
_
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@filewatcher_thread = Thread.new do
|
|
32
|
+
@filewatcher = Filewatcher.new File.join(settings.directory, "**", "*.*")
|
|
33
|
+
|
|
34
|
+
@filewatcher.watch do |changes|
|
|
35
|
+
settings.clients.each do |client|
|
|
36
|
+
client << "data: " + changes.to_s + "\n\n"
|
|
37
|
+
rescue
|
|
38
|
+
client.close
|
|
39
|
+
settings.clients.delete client
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
settings.clients.each do |client|
|
|
44
|
+
client.close
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
rescue
|
|
48
|
+
exit # Watcher thread crash
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
on_stop do
|
|
53
|
+
@filewatcher&.stop()
|
|
54
|
+
@filewatcher_thread&.join
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
get "/ruby-live-reload-sse", provides: "text/event-stream" do
|
|
58
|
+
stream :keep_open do |client|
|
|
59
|
+
if settings.clients.add? client
|
|
60
|
+
client.callback do # on connection closed
|
|
61
|
+
settings.clients.delete client
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Throttle Sinatra scheduler since Filewatcher is used to trigger SSE
|
|
66
|
+
sleep 1
|
|
67
|
+
|
|
68
|
+
# Heartbeat will detect disconnected client and free the thread
|
|
69
|
+
client << "event: heartbeat\n\n"
|
|
70
|
+
rescue
|
|
71
|
+
settings.clients.delete client
|
|
72
|
+
client.close
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Tweak to remove 404 if favicon is missing
|
|
77
|
+
get "/favicon.ico" do
|
|
78
|
+
path = File.join(settings.directory, "favicon.ico")
|
|
79
|
+
|
|
80
|
+
if File.exist? path
|
|
81
|
+
send_file path
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
204
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
get "*" do
|
|
88
|
+
headers \
|
|
89
|
+
"Cache-Control" => "max-age=0, no-cache, no-store, must-revalidate",
|
|
90
|
+
"Expires"=> "Thu, 01 Jan 1970 00:00:00 GMT",
|
|
91
|
+
"Pragma" => "no-cache"
|
|
92
|
+
|
|
93
|
+
splat = File.join params["splat"]
|
|
94
|
+
path = File.join(settings.directory, splat)
|
|
95
|
+
is_asset = !([".html", ".htm", ".xhtml"].include? File.extname(path))
|
|
96
|
+
|
|
97
|
+
if File.directory?(path) && !path.end_with?("/")
|
|
98
|
+
# Add / to the end of URL so browser correctly handles relative paths
|
|
99
|
+
redirect "#{splat}/"
|
|
100
|
+
elsif File.file?(path) && is_asset
|
|
101
|
+
send_file path
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
response = if settings.proxy
|
|
105
|
+
# TODO Handle response other than HTML
|
|
106
|
+
# --wrap to enclose arbitrary text within HTML to allow snippet injection?
|
|
107
|
+
# What about arbitrary files? Images, CSS, etc?
|
|
108
|
+
Faraday.get(File.join(settings.proxy + splat)).body
|
|
109
|
+
elsif File.file? path
|
|
110
|
+
File.read path
|
|
111
|
+
elsif File.file? File.join(path, "index.html")
|
|
112
|
+
File.read File.join(path, "index.html")
|
|
113
|
+
elsif File.directory?(path) && !response
|
|
114
|
+
children = Dir.glob("*", base: path).sort_by { |s| [File.directory?(s).to_s, s.downcase] }
|
|
115
|
+
links = children.map do |child|
|
|
116
|
+
<<-LI
|
|
117
|
+
<li>
|
|
118
|
+
<a href="#{child}">
|
|
119
|
+
#{child + (File.directory?(child) ? "/" : "")}
|
|
120
|
+
</a>
|
|
121
|
+
</li>
|
|
122
|
+
LI
|
|
123
|
+
end.join
|
|
124
|
+
|
|
125
|
+
<<-LISTING
|
|
126
|
+
<!DOCTYPE html>
|
|
127
|
+
<html lang="en">
|
|
128
|
+
<head>
|
|
129
|
+
<meta charset="UTF-8">
|
|
130
|
+
<title>RLR Directory Listing</title>
|
|
131
|
+
</head>
|
|
132
|
+
<body>
|
|
133
|
+
<h1>Ruby Live Reload</h1>
|
|
134
|
+
|
|
135
|
+
<p>#{path}</p>
|
|
136
|
+
<ul>
|
|
137
|
+
#{links}
|
|
138
|
+
</ul>
|
|
139
|
+
</body>
|
|
140
|
+
</html>
|
|
141
|
+
LISTING
|
|
142
|
+
elsif !response
|
|
143
|
+
status 404
|
|
144
|
+
|
|
145
|
+
<<-NOT_FOUND
|
|
146
|
+
<!DOCTYPE html>
|
|
147
|
+
<html lang="en">
|
|
148
|
+
<head>
|
|
149
|
+
<meta charset="UTF-8">
|
|
150
|
+
<title></title>
|
|
151
|
+
</head>
|
|
152
|
+
<body>
|
|
153
|
+
<h1>File Not Found</h1>
|
|
154
|
+
|
|
155
|
+
<p>#{path}</p>
|
|
156
|
+
</body>
|
|
157
|
+
</html>
|
|
158
|
+
NOT_FOUND
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
client_js = <<-JS
|
|
162
|
+
<script defer>
|
|
163
|
+
const source = new EventSource('/ruby-live-reload-sse')
|
|
164
|
+
|
|
165
|
+
source.onmessage = (m) => {
|
|
166
|
+
location.reload()
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
source.onerror = (m) => {
|
|
170
|
+
console.group('Ruby Live Reload')
|
|
171
|
+
console.warn('Cannot reach server, reconnecting...')
|
|
172
|
+
console.groupEnd()
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
addEventListener("beforeunload", (event) => {
|
|
176
|
+
source.close()
|
|
177
|
+
|
|
178
|
+
return false // Do not show confirm dialog
|
|
179
|
+
})
|
|
180
|
+
</script>
|
|
181
|
+
JS
|
|
182
|
+
|
|
183
|
+
head_inject = response.sub!(/<\/head>/, "#{client_js}</head>")
|
|
184
|
+
body_inject = unless head_inject
|
|
185
|
+
response.sub!(/<body(.*)>/, "<body#{$+}>#{client_js}")
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
if head_inject || body_inject
|
|
189
|
+
content_type :html
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
response
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
options = Options.parse(ARGV)
|
|
199
|
+
|
|
200
|
+
if message = options.message
|
|
201
|
+
puts message
|
|
202
|
+
else
|
|
203
|
+
Server.run!({
|
|
204
|
+
bind: options.host,
|
|
205
|
+
port: options.port,
|
|
206
|
+
directory: options.directory,
|
|
207
|
+
proxy: options.proxy,
|
|
208
|
+
threads: 16,
|
|
209
|
+
})
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: ruby-live-reload
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.2.7
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- James Hoffman
|
|
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: rake
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: 13.2.0
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: 13.2.0
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: minitest-reporters
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: 1.7.1
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: 1.7.1
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: sinatra
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - "~>"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: 4.1.0
|
|
47
|
+
type: :runtime
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - "~>"
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: 4.1.0
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: puma
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - "~>"
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: 6.5.0
|
|
61
|
+
type: :runtime
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - "~>"
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: 6.5.0
|
|
68
|
+
- !ruby/object:Gem::Dependency
|
|
69
|
+
name: rackup
|
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
|
71
|
+
requirements:
|
|
72
|
+
- - "~>"
|
|
73
|
+
- !ruby/object:Gem::Version
|
|
74
|
+
version: 2.2.0
|
|
75
|
+
type: :runtime
|
|
76
|
+
prerelease: false
|
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - "~>"
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: 2.2.0
|
|
82
|
+
- !ruby/object:Gem::Dependency
|
|
83
|
+
name: filewatcher
|
|
84
|
+
requirement: !ruby/object:Gem::Requirement
|
|
85
|
+
requirements:
|
|
86
|
+
- - "~>"
|
|
87
|
+
- !ruby/object:Gem::Version
|
|
88
|
+
version: 2.0.0
|
|
89
|
+
type: :runtime
|
|
90
|
+
prerelease: false
|
|
91
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
92
|
+
requirements:
|
|
93
|
+
- - "~>"
|
|
94
|
+
- !ruby/object:Gem::Version
|
|
95
|
+
version: 2.0.0
|
|
96
|
+
- !ruby/object:Gem::Dependency
|
|
97
|
+
name: faraday
|
|
98
|
+
requirement: !ruby/object:Gem::Requirement
|
|
99
|
+
requirements:
|
|
100
|
+
- - "~>"
|
|
101
|
+
- !ruby/object:Gem::Version
|
|
102
|
+
version: 2.12.0
|
|
103
|
+
type: :runtime
|
|
104
|
+
prerelease: false
|
|
105
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
106
|
+
requirements:
|
|
107
|
+
- - "~>"
|
|
108
|
+
- !ruby/object:Gem::Version
|
|
109
|
+
version: 2.12.0
|
|
110
|
+
description: Serve a web app and enable page refresh when files change
|
|
111
|
+
email: james@jhoffman.ca
|
|
112
|
+
executables:
|
|
113
|
+
- rlr
|
|
114
|
+
- ruby-live-reload
|
|
115
|
+
extensions: []
|
|
116
|
+
extra_rdoc_files: []
|
|
117
|
+
files:
|
|
118
|
+
- LICENSE
|
|
119
|
+
- README.md
|
|
120
|
+
- bin/rlr
|
|
121
|
+
- bin/ruby-live-reload
|
|
122
|
+
- lib/ruby_live_reload.rb
|
|
123
|
+
- lib/ruby_live_reload/options.rb
|
|
124
|
+
- lib/ruby_live_reload/version.rb
|
|
125
|
+
homepage: https://github.com/thecodingjames/ruby-live-reload
|
|
126
|
+
licenses:
|
|
127
|
+
- MIT
|
|
128
|
+
metadata: {}
|
|
129
|
+
rdoc_options: []
|
|
130
|
+
require_paths:
|
|
131
|
+
- lib
|
|
132
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
133
|
+
requirements:
|
|
134
|
+
- - ">="
|
|
135
|
+
- !ruby/object:Gem::Version
|
|
136
|
+
version: '0'
|
|
137
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
138
|
+
requirements:
|
|
139
|
+
- - ">="
|
|
140
|
+
- !ruby/object:Gem::Version
|
|
141
|
+
version: '0'
|
|
142
|
+
requirements: []
|
|
143
|
+
rubygems_version: 3.6.9
|
|
144
|
+
specification_version: 4
|
|
145
|
+
summary: A Ruby live server for web development
|
|
146
|
+
test_files: []
|