rails-wayback 0.1.1 → 0.2.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 +4 -4
- data/README.md +116 -7
- data/Rakefile +17 -1
- data/app/controllers/rails_wayback/assets_controller.rb +78 -0
- data/app/controllers/rails_wayback/pages_controller.rb +50 -9
- data/config/routes.rb +5 -0
- data/lib/generators/rails_wayback/install/install_generator.rb +5 -3
- data/lib/generators/rails_wayback/install/templates/initializer.rb.tt +18 -1
- data/lib/rails_wayback/asset_provenance.rb +41 -0
- data/lib/rails_wayback/bar_middleware.rb +235 -67
- data/lib/rails_wayback/bar_renderer.css +24 -3
- data/lib/rails_wayback/bar_renderer.html.erb +28 -7
- data/lib/rails_wayback/bar_renderer.js +346 -117
- data/lib/rails_wayback/bar_renderer.rb +69 -12
- data/lib/rails_wayback/bar_search.js +73 -0
- data/lib/rails_wayback/cache_inventory.rb +199 -0
- data/lib/rails_wayback/cli.rb +69 -2
- data/lib/rails_wayback/configuration.rb +13 -2
- data/lib/rails_wayback/controller_extensions.rb +71 -20
- data/lib/rails_wayback/doctor.rb +123 -0
- data/lib/rails_wayback/engine.rb +20 -19
- data/lib/rails_wayback/engine_mount.rb +15 -0
- data/lib/rails_wayback/git.rb +89 -8
- data/lib/rails_wayback/historical_asset_body.rb +41 -0
- data/lib/rails_wayback/historical_asset_helper.rb +68 -0
- data/lib/rails_wayback/historical_asset_resolver.rb +102 -0
- data/lib/rails_wayback/ref_policy.rb +64 -0
- data/lib/rails_wayback/render_context.rb +24 -0
- data/lib/rails_wayback/render_provenance.rb +84 -0
- data/lib/rails_wayback/render_tracker.rb +34 -0
- data/lib/rails_wayback/version.rb +1 -1
- data/lib/rails_wayback/view_source.rb +171 -24
- data/lib/rails_wayback.rb +42 -6
- data/lib/tasks/rails_wayback.rake +17 -0
- data/rails-wayback.gemspec +2 -5
- metadata +18 -33
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: '083ed3185650e0e8a199b639e18a5a2fe69fcd75d76871803e8bf74aea1afb31'
|
|
4
|
+
data.tar.gz: e7c79e78e2838c107d622d7a73fc793174b2678815e5239a6bf632ff39375fe8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3a179537ef40d42489189aeddecdd2f885eb8ed2d1233b9379d95e322ab0e8c23c73a568c1d2e5303c7e64ffcd2d74bd1baf5df04060c7f650ce6cd23711064b
|
|
7
|
+
data.tar.gz: 40c9c0c81cbef0580603672c0114dcf63f0fc312a243bc64260ede1736d4d3cb61b9e080035122ff12088f66a0c67cc10be1bd365203e5f823614356aa0ca970
|
data/README.md
CHANGED
|
@@ -50,11 +50,20 @@ Turn the UI on when you need it, off when you don't:
|
|
|
50
50
|
bundle exec rails-wayback on # enable and inject the bar in every page
|
|
51
51
|
bundle exec rails-wayback off # disable it
|
|
52
52
|
bundle exec rails-wayback status # print current state
|
|
53
|
+
bundle exec rails-wayback cache # report ref cache usage and limits
|
|
54
|
+
bundle exec rails-wayback prune # apply configured LRU cache limits
|
|
53
55
|
bundle exec rails-wayback clean # drop the tmp/rails_wayback ref cache
|
|
56
|
+
bundle exec rails-wayback doctor # check dependencies and app readiness
|
|
54
57
|
```
|
|
55
58
|
|
|
56
59
|
Same commands as rake tasks: `rake wayback:on`, `wayback:off`,
|
|
57
|
-
`wayback:status`, `wayback:clean
|
|
60
|
+
`wayback:status`, `wayback:cache`, `wayback:prune`, `wayback:clean`,
|
|
61
|
+
`wayback:doctor`.
|
|
62
|
+
|
|
63
|
+
Run `bin/rails wayback:doctor` from a Rails application to check Git and `tar`,
|
|
64
|
+
the repository root, cache write access, configured view and asset paths, and
|
|
65
|
+
the active Rails environment. Warnings do not fail the command; missing
|
|
66
|
+
dependencies or invalid runtime requirements return a non-zero exit status.
|
|
58
67
|
|
|
59
68
|
Activation lasts for the current Rails server session. Every new server start
|
|
60
69
|
begins with `rails-wayback` disabled, while `on` and `off` can still be used at
|
|
@@ -63,10 +72,16 @@ any time without restarting Rails.
|
|
|
63
72
|
With the gem enabled, load any page of your app. A dark bar appears at the
|
|
64
73
|
bottom with:
|
|
65
74
|
|
|
66
|
-
- `Current
|
|
67
|
-
|
|
75
|
+
- `Current ref` — dropdown of trusted branches, remote-tracking branches, and
|
|
76
|
+
tags already stored in the local repository. Its search box filters the
|
|
77
|
+
loaded refs by name, label, or type.
|
|
78
|
+
- `Current commit` — dropdown of the recent commits on that ref. Its search
|
|
79
|
+
box filters the loaded commits by SHA, subject, author, or date.
|
|
68
80
|
- `Travel` — reloads the current URL using views from that commit.
|
|
69
81
|
- `Return to HEAD` — appears while traveling; goes back to live views.
|
|
82
|
+
It uses a server-side reset endpoint, which is also available directly at
|
|
83
|
+
`/rails-wayback/reset` if a historical template prevents the page from
|
|
84
|
+
rendering.
|
|
70
85
|
|
|
71
86
|
There is no dedicated `/rails-wayback` page. The gem stays quiet on assets,
|
|
72
87
|
ActionCable, and every URL under `/rails-wayback/*.json` (used by the bar to
|
|
@@ -83,13 +98,42 @@ fetch git data).
|
|
|
83
98
|
3. The same before_action calls `prepend_view_path` with the sandbox, so
|
|
84
99
|
Action View resolves templates from the ref before falling back to the
|
|
85
100
|
current tree.
|
|
86
|
-
4.
|
|
101
|
+
4. Standard Rails asset helpers resolve browser-ready files from the ref's
|
|
102
|
+
`public/` directory to immutable, SHA-specific engine URLs. Missing files
|
|
103
|
+
fall back to the current Rails asset resolver.
|
|
104
|
+
5. Your controller runs as usual, sets its `@` variables, and renders. The
|
|
87
105
|
visual layer (layouts, partials, ERB) comes from the past; the data and
|
|
88
106
|
business logic come from the present.
|
|
89
107
|
|
|
90
108
|
If a historical template references a helper or assign that no longer
|
|
91
109
|
exists, you'll get the same `ActionView` error you'd get from any missing
|
|
92
|
-
piece — the app itself never changes.
|
|
110
|
+
piece — the app itself never changes. Open `/rails-wayback/reset` to clear the
|
|
111
|
+
travel cookies if that error prevents the toolbar from rendering.
|
|
112
|
+
|
|
113
|
+
The toolbar tracks templates, partials, layouts, and collections. While
|
|
114
|
+
traveling it labels a response as historical, current fallback, or mixed so
|
|
115
|
+
you can tell when Rails filled a missing historical file from the current
|
|
116
|
+
view tree. It also reports historical public assets and current asset
|
|
117
|
+
fallbacks discovered through standard Rails helpers.
|
|
118
|
+
|
|
119
|
+
### Historical public assets
|
|
120
|
+
|
|
121
|
+
While traveling, existing calls to `asset_path`, `image_tag`,
|
|
122
|
+
`stylesheet_link_tag`, `javascript_include_tag`, and the other standard Rails
|
|
123
|
+
asset helpers automatically look for browser-ready files in the selected
|
|
124
|
+
commit's `public/` directory. A historical file is served from a URL such as
|
|
125
|
+
`/rails-wayback/refs/<sha>/assets/images/logo.svg`; the SHA prevents browser
|
|
126
|
+
and proxy caches from mixing revisions. Asset responses reauthorize the ref,
|
|
127
|
+
reject unsafe paths and escaping symlinks, use immutable caching, and keep a
|
|
128
|
+
cache lease for the complete streamed response.
|
|
129
|
+
|
|
130
|
+
If a public file is absent, the normal current Rails resolver remains the
|
|
131
|
+
fallback and the toolbar reports it. Direct hard-coded URLs such as
|
|
132
|
+
`/assets/application.css` bypass Rails helpers and are not rewritten.
|
|
133
|
+
Historical Propshaft/Sprockets compilation, Sass or TypeScript processing,
|
|
134
|
+
import maps, and JavaScript/CSS bundlers are not reproduced yet; `app/assets`
|
|
135
|
+
continues to be extracted for comparison but is not served by this initial
|
|
136
|
+
static-file strategy.
|
|
93
137
|
|
|
94
138
|
## Configuration (optional)
|
|
95
139
|
|
|
@@ -97,14 +141,71 @@ piece — the app itself never changes.
|
|
|
97
141
|
|
|
98
142
|
```ruby
|
|
99
143
|
RailsWayback.configure do |config|
|
|
144
|
+
# config.allowed_environments = %w[development test]
|
|
145
|
+
# config.trusted_ref_patterns = ["refs/heads/*"]
|
|
100
146
|
# config.view_paths = %w[app/views]
|
|
101
147
|
# config.asset_paths = %w[app/assets public]
|
|
102
148
|
# config.max_commits = 50
|
|
149
|
+
# config.max_cached_refs = 25
|
|
150
|
+
# config.max_cache_bytes = 512 * 1024 * 1024
|
|
151
|
+
# config.max_response_bytes = 2 * 1024 * 1024
|
|
103
152
|
end
|
|
104
153
|
```
|
|
105
154
|
|
|
106
|
-
|
|
107
|
-
|
|
155
|
+
Rails Wayback refuses to activate outside `development` and `test` by
|
|
156
|
+
default, even if its toggle file exists. Adding another environment is an
|
|
157
|
+
explicit opt-in; historical templates execute inside the host Rails process,
|
|
158
|
+
so only trusted refs should be used. Travel accepts only full commit SHAs and,
|
|
159
|
+
by default, the commit must be reachable from a local branch. Narrow
|
|
160
|
+
`trusted_ref_patterns` to branches such as `refs/heads/main` or
|
|
161
|
+
`refs/heads/release/*`, or explicitly add locally stored remote-tracking refs
|
|
162
|
+
and tags:
|
|
163
|
+
|
|
164
|
+
```ruby
|
|
165
|
+
config.trusted_ref_patterns = [
|
|
166
|
+
"refs/heads/*",
|
|
167
|
+
"refs/remotes/origin/review/*",
|
|
168
|
+
"refs/tags/preview-*"
|
|
169
|
+
]
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Matching refs appear in the toolbar and can be used for commit discovery.
|
|
173
|
+
Ref and commit search is performed in the browser over the data already loaded
|
|
174
|
+
by the toolbar; commit search is therefore limited to `max_commits` entries for
|
|
175
|
+
the selected ref and does not run additional Git commands or network requests.
|
|
176
|
+
Rails Wayback never runs `git fetch`: remote-tracking refs must already exist
|
|
177
|
+
locally, symbolic aliases such as `origin/HEAD` are omitted, and untrusted refs
|
|
178
|
+
cannot be queried through the commits endpoint. Detached commits remain blocked.
|
|
179
|
+
|
|
180
|
+
This policy reduces accidental execution but is not a sandbox. Historical ERB
|
|
181
|
+
and other executable template handlers run with the Rails server's filesystem,
|
|
182
|
+
database, credentials, environment, and network permissions. Historical
|
|
183
|
+
JavaScript also executes in the browser under the development application's
|
|
184
|
+
origin. The toolbar shows a permanent warning and asks for confirmation on the
|
|
185
|
+
first travel action in each browser session. The response-size limit applies only to
|
|
186
|
+
toolbar injection; streaming, encoded, file, partial, and larger responses are
|
|
187
|
+
returned untouched. Toolbar CSS and JavaScript are served from the mounted
|
|
188
|
+
engine, so CSP policies should allow same-origin styles, scripts, and fetches.
|
|
189
|
+
Rails-generated CSP nonces are copied to both asset tags automatically.
|
|
190
|
+
Everything above has a sensible default. Delete the initializer if you don't
|
|
191
|
+
need to change anything.
|
|
192
|
+
|
|
193
|
+
Cached refs use least-recently-used access metadata. After materialising a new
|
|
194
|
+
ref, Rails Wayback automatically removes the oldest refs until the configured
|
|
195
|
+
count and byte limits are met. A shared cache lease protects each ref for the
|
|
196
|
+
complete historical render, so automatic or manual `prune` and `clean`
|
|
197
|
+
operations wait for active previews before removing their files.
|
|
198
|
+
`rails-wayback cache` reports logical payload size and file count, while
|
|
199
|
+
`rails-wayback prune` applies the limits immediately. `rails-wayback clean`
|
|
200
|
+
removes all ref data and per-ref lock files. Set either cache limit to `nil` to
|
|
201
|
+
disable it.
|
|
202
|
+
|
|
203
|
+
## Compatibility
|
|
204
|
+
|
|
205
|
+
Rails Wayback requires Ruby 3.3 or newer and Rails 8.0 or newer. CI runs the
|
|
206
|
+
complete matrix of Ruby 3.3, 3.4, and 4.0 against Rails 8.0 and 8.1.
|
|
207
|
+
Compatibility here means that the gem's suite passes on that combination; it
|
|
208
|
+
does not extend the upstream maintenance or security lifetime of Ruby or Rails.
|
|
108
209
|
|
|
109
210
|
## Development
|
|
110
211
|
|
|
@@ -113,6 +214,14 @@ bundle install
|
|
|
113
214
|
bundle exec rspec
|
|
114
215
|
```
|
|
115
216
|
|
|
217
|
+
Rails dependency sets are managed with Appraisal. Regenerate their Gemfiles
|
|
218
|
+
after changing `Appraisals`, then run an individual Rails line with:
|
|
219
|
+
|
|
220
|
+
```bash
|
|
221
|
+
bundle exec appraisal generate
|
|
222
|
+
bundle exec appraisal rails-8-0 rspec
|
|
223
|
+
```
|
|
224
|
+
|
|
116
225
|
## Release
|
|
117
226
|
|
|
118
227
|
Releases are cut by pushing an annotated `vX.Y.Z` git tag. GitHub Actions runs
|
data/Rakefile
CHANGED
|
@@ -2,10 +2,26 @@
|
|
|
2
2
|
|
|
3
3
|
require "bundler/gem_tasks"
|
|
4
4
|
|
|
5
|
+
default_tasks = []
|
|
6
|
+
|
|
5
7
|
begin
|
|
6
8
|
require "rspec/core/rake_task"
|
|
7
9
|
RSpec::Core::RakeTask.new(:spec)
|
|
8
|
-
|
|
10
|
+
default_tasks << :spec
|
|
9
11
|
rescue LoadError
|
|
10
12
|
# RSpec is only available in development
|
|
11
13
|
end
|
|
14
|
+
|
|
15
|
+
begin
|
|
16
|
+
require "rubocop/rake_task"
|
|
17
|
+
RuboCop::RakeTask.new(:rubocop) do |task|
|
|
18
|
+
task.options = ["--parallel"]
|
|
19
|
+
end
|
|
20
|
+
desc "Run RuboCop"
|
|
21
|
+
task lint: :rubocop
|
|
22
|
+
default_tasks << :rubocop
|
|
23
|
+
rescue LoadError
|
|
24
|
+
# RuboCop is only available in development
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
task default: default_tasks
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "digest"
|
|
4
|
+
require "rack/mime"
|
|
5
|
+
require "rails_wayback/bar_renderer"
|
|
6
|
+
|
|
7
|
+
module RailsWayback
|
|
8
|
+
# Serves the toolbar's bundled assets without depending on the host
|
|
9
|
+
# application's asset pipeline.
|
|
10
|
+
class AssetsController < ApplicationController
|
|
11
|
+
CACHE_CONTROL = "public, max-age=31536000, immutable"
|
|
12
|
+
|
|
13
|
+
def stylesheet
|
|
14
|
+
render_asset(BarRenderer::STYLES, "text/css")
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def javascript
|
|
18
|
+
render_asset(BarRenderer::SCRIPT, "application/javascript")
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def historical
|
|
22
|
+
selection = RailsWayback::RefPolicy.new.authorize(params[:sha])
|
|
23
|
+
return render_rejection(selection) if selection.rejected?
|
|
24
|
+
|
|
25
|
+
source = RailsWayback::ViewSource.new
|
|
26
|
+
resolution = resolve_asset(source, selection.sha, params[:path])
|
|
27
|
+
return head :not_found unless resolution
|
|
28
|
+
|
|
29
|
+
set_historical_headers(selection.sha, resolution)
|
|
30
|
+
return head :ok if request.head?
|
|
31
|
+
|
|
32
|
+
self.response_body = RailsWayback::HistoricalAssetBody.new(
|
|
33
|
+
sha: selection.sha,
|
|
34
|
+
public_path: resolution.public_path,
|
|
35
|
+
view_source: source
|
|
36
|
+
)
|
|
37
|
+
rescue RailsWayback::Git::GitError, RailsWayback::MaterializationError => e
|
|
38
|
+
render json: { error: e.message }, status: :service_unavailable
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
def resolve_asset(source, sha, path)
|
|
44
|
+
resolution = nil
|
|
45
|
+
source.with_materialization(sha) do |root|
|
|
46
|
+
resolution = RailsWayback::HistoricalAssetResolver.new.resolve_public_path(
|
|
47
|
+
root: root,
|
|
48
|
+
public_path: path
|
|
49
|
+
)
|
|
50
|
+
end
|
|
51
|
+
resolution
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def set_historical_headers(sha, resolution)
|
|
55
|
+
content_type = Rack::Mime.mime_type(File.extname(resolution.public_path), "application/octet-stream")
|
|
56
|
+
response.content_type = content_type
|
|
57
|
+
response.set_header("Content-Length", resolution.size_bytes.to_s)
|
|
58
|
+
response.set_header("Cache-Control", CACHE_CONTROL)
|
|
59
|
+
response.set_header("X-Content-Type-Options", "nosniff")
|
|
60
|
+
response.set_header("ETag", %("#{Digest::SHA256.hexdigest("#{sha}:#{resolution.public_path}")}"))
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def render_rejection(selection)
|
|
64
|
+
status = case selection.reason
|
|
65
|
+
when :invalid_format then :bad_request
|
|
66
|
+
when :unknown_ref then :not_found
|
|
67
|
+
else :forbidden
|
|
68
|
+
end
|
|
69
|
+
render json: { error: selection.message }, status: status
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def render_asset(content, content_type)
|
|
73
|
+
response.set_header("Cache-Control", CACHE_CONTROL)
|
|
74
|
+
response.set_header("X-Content-Type-Options", "nosniff")
|
|
75
|
+
render plain: content, content_type: content_type
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
@@ -1,28 +1,40 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "uri"
|
|
4
|
+
|
|
3
5
|
module RailsWayback
|
|
4
6
|
# JSON endpoints consumed by the bar's JavaScript to populate the
|
|
5
7
|
# branch and commit selectors. All rendering of the actual UI lives
|
|
6
8
|
# in the middleware; this controller only exposes git metadata.
|
|
7
9
|
class PagesController < ApplicationController
|
|
10
|
+
WAYBACK_COOKIES = %w[rails_wayback_ref rails_wayback_branch].freeze
|
|
11
|
+
Result = Struct.new(:value, :error)
|
|
12
|
+
private_constant :Result
|
|
13
|
+
|
|
8
14
|
def branches
|
|
9
15
|
current_branch = safe_call { git.current_branch }
|
|
10
16
|
current_commit = safe_call { git.current_commit }
|
|
11
|
-
|
|
17
|
+
references = safe_call { git.references }
|
|
18
|
+
list = references.value || []
|
|
12
19
|
|
|
13
20
|
render json: {
|
|
14
21
|
current_branch: current_branch.value,
|
|
15
22
|
current_commit: current_commit.value,
|
|
16
|
-
branches:
|
|
17
|
-
|
|
23
|
+
branches: list.select { |reference| reference.kind == :branch }.map(&:name),
|
|
24
|
+
refs: list.map { |reference| reference_json(reference) },
|
|
25
|
+
errors: [references.error].compact
|
|
18
26
|
}
|
|
19
27
|
end
|
|
20
28
|
|
|
21
29
|
def commits
|
|
22
|
-
|
|
23
|
-
result = safe_call
|
|
30
|
+
selector = params[:branch].to_s.sub(/\.json\z/, "")
|
|
31
|
+
result = safe_call do
|
|
32
|
+
reference = git.reference(selector)
|
|
33
|
+
[reference, git.commits(reference.full_name)]
|
|
34
|
+
end
|
|
35
|
+
reference, commits = result.value
|
|
24
36
|
|
|
25
|
-
list = (
|
|
37
|
+
list = (commits || []).map do |commit|
|
|
26
38
|
{
|
|
27
39
|
sha: commit.sha,
|
|
28
40
|
short_sha: commit.short_sha,
|
|
@@ -33,15 +45,22 @@ module RailsWayback
|
|
|
33
45
|
end
|
|
34
46
|
|
|
35
47
|
render json: {
|
|
36
|
-
branch:
|
|
48
|
+
branch: reference&.name || selector,
|
|
49
|
+
ref: reference && reference_json(reference),
|
|
37
50
|
commits: list,
|
|
38
51
|
error: result.error
|
|
39
52
|
}
|
|
40
53
|
end
|
|
41
54
|
|
|
42
|
-
|
|
55
|
+
# Emergency escape hatch that does not render any host application view.
|
|
56
|
+
# It remains usable when a historical template crashes before the injected
|
|
57
|
+
# toolbar can render its normal Return to HEAD control.
|
|
58
|
+
def reset
|
|
59
|
+
WAYBACK_COOKIES.each { |name| response.delete_cookie(name, path: "/") }
|
|
60
|
+
redirect_to safe_return_to, allow_other_host: false
|
|
61
|
+
end
|
|
43
62
|
|
|
44
|
-
|
|
63
|
+
private
|
|
45
64
|
|
|
46
65
|
def safe_call
|
|
47
66
|
Result.new(yield, nil)
|
|
@@ -49,5 +68,27 @@ module RailsWayback
|
|
|
49
68
|
Rails.logger.warn("[rails-wayback] #{e.class}: #{e.message}") if defined?(Rails)
|
|
50
69
|
Result.new(nil, "#{e.class}: #{e.message}")
|
|
51
70
|
end
|
|
71
|
+
|
|
72
|
+
def reference_json(reference)
|
|
73
|
+
{
|
|
74
|
+
full_name: reference.full_name,
|
|
75
|
+
name: reference.name,
|
|
76
|
+
type: reference.kind,
|
|
77
|
+
label: reference.label
|
|
78
|
+
}
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def safe_return_to
|
|
82
|
+
target = params[:return_to].to_s
|
|
83
|
+
return "/" if target.empty? || target.include?("\\")
|
|
84
|
+
|
|
85
|
+
uri = URI.parse(target)
|
|
86
|
+
return "/" unless target.start_with?("/") && !target.start_with?("//")
|
|
87
|
+
return "/" if uri.scheme || uri.host
|
|
88
|
+
|
|
89
|
+
target
|
|
90
|
+
rescue URI::InvalidURIError
|
|
91
|
+
"/"
|
|
92
|
+
end
|
|
52
93
|
end
|
|
53
94
|
end
|
data/config/routes.rb
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
RailsWayback::Engine.routes.draw do
|
|
4
|
+
get "assets/bar.css", to: "assets#stylesheet", format: false, as: :bar_stylesheet
|
|
5
|
+
get "assets/bar.js", to: "assets#javascript", format: false, as: :bar_javascript
|
|
6
|
+
get "refs/:sha/assets/*path", to: "assets#historical", format: false, as: :historical_asset
|
|
7
|
+
|
|
4
8
|
# `format: false` keeps Rails from parsing a trailing `.json` as an
|
|
5
9
|
# implicit format, and the glob `*branch` allows branch names that
|
|
6
10
|
# contain slashes (e.g. "feature/foo"). The controller trims a
|
|
7
11
|
# trailing `.json` defensively in case a client still appends it.
|
|
8
12
|
get "branches", to: "pages#branches", format: false, as: :branches
|
|
9
13
|
get "commits/*branch", to: "pages#commits", format: false, as: :commits
|
|
14
|
+
get "reset", to: "pages#reset", format: false, as: :reset
|
|
10
15
|
end
|
|
@@ -17,8 +17,9 @@ module RailsWayback
|
|
|
17
17
|
ROUTE
|
|
18
18
|
|
|
19
19
|
routes_file = "config/routes.rb"
|
|
20
|
+
routes_path = File.join(destination_root, routes_file)
|
|
20
21
|
|
|
21
|
-
if File.file?(
|
|
22
|
+
if File.file?(routes_path) && File.read(routes_path).include?("RailsWayback::Engine")
|
|
22
23
|
say_status :skip, "RailsWayback::Engine mount already present in #{routes_file}"
|
|
23
24
|
else
|
|
24
25
|
route route_snippet.strip
|
|
@@ -27,10 +28,11 @@ module RailsWayback
|
|
|
27
28
|
|
|
28
29
|
def update_gitignore
|
|
29
30
|
gitignore = ".gitignore"
|
|
31
|
+
gitignore_path = File.join(destination_root, gitignore)
|
|
30
32
|
entry = "/tmp/rails_wayback/\n"
|
|
31
|
-
if File.file?(
|
|
33
|
+
if File.file?(gitignore_path) && !File.read(gitignore_path).include?("tmp/rails_wayback")
|
|
32
34
|
append_to_file(gitignore, entry)
|
|
33
|
-
elsif !File.file?(
|
|
35
|
+
elsif !File.file?(gitignore_path)
|
|
34
36
|
create_file(gitignore, entry)
|
|
35
37
|
end
|
|
36
38
|
end
|
|
@@ -8,11 +8,28 @@
|
|
|
8
8
|
# so you can pick a branch/commit and travel to that ref's views for
|
|
9
9
|
# the current URL. Delete this file if the defaults below work for you.
|
|
10
10
|
RailsWayback.configure do |config|
|
|
11
|
+
# Rails Wayback is unavailable outside these environments, even if its
|
|
12
|
+
# toggle file exists. Add another environment only as an explicit opt-in.
|
|
13
|
+
# config.allowed_environments = %w[development test]
|
|
14
|
+
# Commits must be reachable from one of these Git ref patterns.
|
|
15
|
+
# Add refs/remotes/... or refs/tags/... patterns to show locally stored
|
|
16
|
+
# remote-tracking branches or tags. Rails Wayback never fetches from Git.
|
|
17
|
+
# config.trusted_ref_patterns = ["refs/heads/*"]
|
|
18
|
+
|
|
11
19
|
# Directories (relative to Rails.root) whose contents are pulled from
|
|
12
|
-
# the target git ref
|
|
20
|
+
# the target git ref. Browser-ready files under historical public/ are
|
|
21
|
+
# discovered through standard Rails helpers; pipeline builds are not run.
|
|
13
22
|
# config.view_paths = %w[app/views]
|
|
14
23
|
# config.asset_paths = %w[app/assets public]
|
|
15
24
|
|
|
16
25
|
# How many recent commits per branch are shown in the bar dropdown.
|
|
17
26
|
# config.max_commits = 50
|
|
27
|
+
# Both LRU limits are applied automatically after a new ref is cached.
|
|
28
|
+
# Set either value to nil to disable that limit.
|
|
29
|
+
# config.max_cached_refs = 25
|
|
30
|
+
# config.max_cache_bytes = 512 * 1024 * 1024
|
|
31
|
+
|
|
32
|
+
# Maximum buffered HTML response size that may receive the toolbar.
|
|
33
|
+
# Streaming, encoded, file, and larger responses are always left untouched.
|
|
34
|
+
# config.max_response_bytes = 2 * 1024 * 1024
|
|
18
35
|
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RailsWayback
|
|
4
|
+
module AssetProvenance
|
|
5
|
+
EVENT_PREFIX = "rails_wayback.asset."
|
|
6
|
+
ORIGINS = %i[historical current].freeze
|
|
7
|
+
|
|
8
|
+
module_function
|
|
9
|
+
|
|
10
|
+
def record(origin:, identifier:)
|
|
11
|
+
return unless ORIGINS.include?(origin.to_sym)
|
|
12
|
+
|
|
13
|
+
RailsWayback::RenderContext.current&.record(
|
|
14
|
+
event: "#{EVENT_PREFIX}#{origin}",
|
|
15
|
+
identifier: identifier
|
|
16
|
+
)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def paths_by_origin(entries)
|
|
20
|
+
result = { historical: [], current: [] }
|
|
21
|
+
Array(entries).each do |entry|
|
|
22
|
+
event = entry_value(entry, :event)
|
|
23
|
+
next unless event.start_with?(EVENT_PREFIX)
|
|
24
|
+
|
|
25
|
+
origin = event.delete_prefix(EVENT_PREFIX).to_sym
|
|
26
|
+
next unless result.key?(origin)
|
|
27
|
+
|
|
28
|
+
identifier = entry_value(entry, :identifier)
|
|
29
|
+
result[origin] << identifier unless identifier.empty?
|
|
30
|
+
end
|
|
31
|
+
result.transform_values(&:uniq)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def entry_value(entry, key)
|
|
35
|
+
return "" unless entry.is_a?(Hash)
|
|
36
|
+
|
|
37
|
+
(entry[key] || entry[key.to_s]).to_s
|
|
38
|
+
end
|
|
39
|
+
private_class_method :entry_value
|
|
40
|
+
end
|
|
41
|
+
end
|