gemstar 1.0.4 → 1.1.1
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/CHANGELOG.md +32 -1
- data/README.md +28 -3
- data/bin/gemstar +5 -1
- data/lib/gemstar/cache_warmer.rb +93 -35
- data/lib/gemstar/change_log.rb +319 -36
- data/lib/gemstar/cli.rb +5 -1
- data/lib/gemstar/commands/diff.rb +197 -31
- data/lib/gemstar/commands/server.rb +93 -10
- data/lib/gemstar/data/importmap_package_metadata.json +22 -0
- data/lib/gemstar/data/ruby_gems_metadata.json +9 -0
- data/lib/gemstar/git_hub.rb +3 -2
- data/lib/gemstar/git_repo.rb +41 -3
- data/lib/gemstar/importmap_file.rb +193 -0
- data/lib/gemstar/npm_metadata.rb +159 -0
- data/lib/gemstar/outputs/html.rb +53 -4
- data/lib/gemstar/outputs/markdown.rb +29 -3
- data/lib/gemstar/package_lock_file.rb +101 -0
- data/lib/gemstar/project.rb +319 -35
- data/lib/gemstar/ruby_gems_metadata.rb +113 -2
- data/lib/gemstar/version.rb +1 -1
- data/lib/gemstar/web/app.rb +422 -68
- data/lib/gemstar/web/templates/app.css +48 -0
- data/lib/gemstar/web/templates/app.js.erb +51 -16
- data/lib/gemstar/web/templates/page.html.erb +2 -1
- data/lib/gemstar.rb +3 -0
- metadata +6 -1
data/lib/gemstar/web/app.rb
CHANGED
|
@@ -2,6 +2,7 @@ require "cgi"
|
|
|
2
2
|
require "erb"
|
|
3
3
|
require "uri"
|
|
4
4
|
require "kramdown"
|
|
5
|
+
require "nokogiri"
|
|
5
6
|
require "roda"
|
|
6
7
|
|
|
7
8
|
begin
|
|
@@ -13,6 +14,7 @@ module Gemstar
|
|
|
13
14
|
module Web
|
|
14
15
|
class App < Roda
|
|
15
16
|
MISSING_METADATA = Object.new
|
|
17
|
+
CACHE_VERSION = "v7"
|
|
16
18
|
|
|
17
19
|
class << self
|
|
18
20
|
def build(projects:, config_home:, cache_warmer: nil)
|
|
@@ -44,6 +46,16 @@ module Gemstar
|
|
|
44
46
|
end
|
|
45
47
|
end
|
|
46
48
|
|
|
49
|
+
r.get "favicon.svg" do
|
|
50
|
+
response["Content-Type"] = "image/svg+xml; charset=utf-8"
|
|
51
|
+
favicon_svg
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
r.get "favicon.ico" do
|
|
55
|
+
response["Content-Type"] = "image/svg+xml; charset=utf-8"
|
|
56
|
+
favicon_svg
|
|
57
|
+
end
|
|
58
|
+
|
|
47
59
|
r.get "detail" do
|
|
48
60
|
request_cache_key = detail_request_cache_key(r.params)
|
|
49
61
|
request_cache = self.class.opts[:detail_request_cache]
|
|
@@ -62,7 +74,7 @@ module Gemstar
|
|
|
62
74
|
project_index = selected_project_index(r.params["project"])
|
|
63
75
|
project = @projects[project_index]
|
|
64
76
|
response.status = 404
|
|
65
|
-
next "Gemfile not found" unless project
|
|
77
|
+
next "Gemfile not found" unless project&.gemfile?
|
|
66
78
|
|
|
67
79
|
response["Content-Type"] = "text/plain; charset=utf-8"
|
|
68
80
|
File.read(project.gemfile_path)
|
|
@@ -86,23 +98,39 @@ module Gemstar
|
|
|
86
98
|
project = @projects[project_index]
|
|
87
99
|
return nil unless project
|
|
88
100
|
|
|
89
|
-
lockfile_stamp =
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
0
|
|
94
|
-
end
|
|
101
|
+
lockfile_stamp = File.file?(project.lockfile_path) ? File.mtime(project.lockfile_path).to_i : 0
|
|
102
|
+
importmap_stamp = File.file?(project.importmap_path) ? File.mtime(project.importmap_path).to_i : 0
|
|
103
|
+
importmap_vendor_stamp = importmap_vendor_mtime(project)
|
|
104
|
+
package_lock_stamp = File.file?(project.package_lock_path) ? File.mtime(project.package_lock_path).to_i : 0
|
|
95
105
|
|
|
96
106
|
[
|
|
107
|
+
CACHE_VERSION,
|
|
97
108
|
project_index,
|
|
98
109
|
params["from"],
|
|
99
110
|
params["to"],
|
|
100
111
|
params["filter"],
|
|
101
|
-
params["
|
|
102
|
-
|
|
112
|
+
params["scope"],
|
|
113
|
+
package_param(params),
|
|
114
|
+
lockfile_stamp,
|
|
115
|
+
importmap_stamp,
|
|
116
|
+
importmap_vendor_stamp,
|
|
117
|
+
package_lock_stamp
|
|
103
118
|
]
|
|
104
119
|
end
|
|
105
120
|
|
|
121
|
+
def importmap_vendor_mtime(project)
|
|
122
|
+
return 0 unless project&.importmap?
|
|
123
|
+
|
|
124
|
+
project.current_importmap.specs.values.filter_map do |target|
|
|
125
|
+
next unless target.to_s.end_with?(".js", ".mjs")
|
|
126
|
+
|
|
127
|
+
path = File.join(project.directory, "vendor", "javascript", target.to_s)
|
|
128
|
+
File.mtime(path).to_i if File.file?(path)
|
|
129
|
+
end.max || 0
|
|
130
|
+
rescue StandardError
|
|
131
|
+
0
|
|
132
|
+
end
|
|
133
|
+
|
|
106
134
|
def page_title
|
|
107
135
|
return "Gemstar" unless @selected_project
|
|
108
136
|
|
|
@@ -117,9 +145,15 @@ module Gemstar
|
|
|
117
145
|
@selected_from_revision_id = selected_from_revision_id(params["from"])
|
|
118
146
|
@selected_to_revision_id = selected_to_revision_id(@selected_to_revision_id)
|
|
119
147
|
@gem_states = @selected_project ? @selected_project.gem_states(from_revision_id: @selected_from_revision_id, to_revision_id: @selected_to_revision_id) : []
|
|
120
|
-
|
|
121
|
-
@
|
|
122
|
-
@
|
|
148
|
+
requested_package_name = package_param(params)
|
|
149
|
+
@requested_gem_name = requested_package_name
|
|
150
|
+
@selected_package_scope = selected_package_scope(params["scope"], requested_package_name)
|
|
151
|
+
@selected_filter = selected_filter(params["filter"], requested_package_name)
|
|
152
|
+
@selected_gem = selected_gem_state(requested_package_name)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def package_param(params)
|
|
156
|
+
params["package"] || params["gem"]
|
|
123
157
|
end
|
|
124
158
|
|
|
125
159
|
def prioritize_selected_gem
|
|
@@ -167,10 +201,24 @@ module Gemstar
|
|
|
167
201
|
@gem_states.any? { |gem| gem[:status] != :unchanged } ? "updated" : "all"
|
|
168
202
|
end
|
|
169
203
|
|
|
204
|
+
def selected_package_scope(raw_scope, raw_gem_name)
|
|
205
|
+
return "all" if @gem_states.empty?
|
|
206
|
+
|
|
207
|
+
available_scopes = available_package_scopes
|
|
208
|
+
default_scope = available_scopes == ["gems"] ? "gems" : "all"
|
|
209
|
+
return raw_scope if raw_scope == "all" || available_scopes.include?(raw_scope)
|
|
210
|
+
|
|
211
|
+
selected_gem = @gem_states.find { |gem| gem[:name] == raw_gem_name }
|
|
212
|
+
return selected_gem[:package_scope] if selected_gem
|
|
213
|
+
|
|
214
|
+
default_scope
|
|
215
|
+
end
|
|
216
|
+
|
|
170
217
|
def selected_gem_state(raw_gem_name)
|
|
171
218
|
return nil if @gem_states.empty?
|
|
172
219
|
|
|
173
|
-
exact_match = @gem_states.find { |gem| gem[:name] == raw_gem_name }
|
|
220
|
+
exact_match = @gem_states.find { |gem| gem[:name] == raw_gem_name && gem_visible_in_selected_scope?(gem) }
|
|
221
|
+
exact_match ||= @gem_states.find { |gem| gem[:name] == raw_gem_name }
|
|
174
222
|
return exact_match if exact_match
|
|
175
223
|
|
|
176
224
|
@gem_states.find { |gem| gem_visible_in_selected_filter?(gem) && gem[:status] != :unchanged } ||
|
|
@@ -180,11 +228,20 @@ module Gemstar
|
|
|
180
228
|
end
|
|
181
229
|
|
|
182
230
|
def gem_visible_in_selected_filter?(gem_state)
|
|
231
|
+
return false unless gem_visible_in_selected_scope?(gem_state)
|
|
183
232
|
return true if @selected_filter != "updated"
|
|
184
233
|
|
|
185
234
|
gem_state[:status] != :unchanged
|
|
186
235
|
end
|
|
187
236
|
|
|
237
|
+
def gem_visible_in_selected_scope?(gem_state)
|
|
238
|
+
@selected_package_scope == "all" || gem_state[:package_scope] == @selected_package_scope
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
def available_package_scopes
|
|
242
|
+
@selected_project ? @selected_project.package_scope_options.map { |option| option[:id] } : []
|
|
243
|
+
end
|
|
244
|
+
|
|
188
245
|
def render_shell
|
|
189
246
|
return render_empty_workspace if @projects.empty?
|
|
190
247
|
|
|
@@ -322,7 +379,7 @@ module Gemstar
|
|
|
322
379
|
#{render_toolbar}
|
|
323
380
|
<div class="workspace-body">
|
|
324
381
|
#{render_sidebar}
|
|
325
|
-
#{
|
|
382
|
+
#{render_initial_detail}
|
|
326
383
|
</div>
|
|
327
384
|
</main>
|
|
328
385
|
HTML
|
|
@@ -332,7 +389,7 @@ module Gemstar
|
|
|
332
389
|
<<~HTML
|
|
333
390
|
<section class="toolbar">
|
|
334
391
|
<div class="toolbar-meta">
|
|
335
|
-
<strong>#{@gem_states.count}</strong>
|
|
392
|
+
<strong>#{@gem_states.count}</strong> #{h(@selected_project&.package_collection_label&.downcase || "packages")}
|
|
336
393
|
<span>·</span>
|
|
337
394
|
<strong>#{@gem_states.count { |gem| gem[:status] != :unchanged }}</strong> changes from #{h(selected_from_revision_label)} to #{h(selected_to_revision_label)}
|
|
338
395
|
</div>
|
|
@@ -357,17 +414,18 @@ module Gemstar
|
|
|
357
414
|
<aside class="sidebar" data-sidebar-panel tabindex="0">
|
|
358
415
|
<div class="sidebar-header">
|
|
359
416
|
<div class="sidebar-header-row">
|
|
360
|
-
<h2
|
|
417
|
+
<h2>#{h(@selected_project&.package_collection_label || "Packages")}</h2>
|
|
361
418
|
<div class="list-filters" data-list-filters>
|
|
362
419
|
<button type="button" class="list-filter-button#{' is-active' if @selected_filter == "updated"}" data-filter-button="updated">Updated</button>
|
|
363
420
|
<button type="button" class="list-filter-button#{' is-active' if @selected_filter == "all"}" data-filter-button="all">All</button>
|
|
364
421
|
</div>
|
|
365
422
|
</div>
|
|
423
|
+
#{render_package_scope_filters}
|
|
366
424
|
<input
|
|
367
425
|
type="search"
|
|
368
426
|
class="gem-search"
|
|
369
427
|
data-gem-search
|
|
370
|
-
placeholder="
|
|
428
|
+
placeholder="Search"
|
|
371
429
|
autocomplete="off"
|
|
372
430
|
spellcheck="false"
|
|
373
431
|
>
|
|
@@ -380,7 +438,7 @@ module Gemstar
|
|
|
380
438
|
def render_gem_list
|
|
381
439
|
return <<~HTML if @gem_states.empty?
|
|
382
440
|
<section class="empty-panel">
|
|
383
|
-
<p>No
|
|
441
|
+
<p>No #{h(@selected_project&.package_collection_label&.downcase || "packages")} found in the current lockfile.</p>
|
|
384
442
|
</section>
|
|
385
443
|
HTML
|
|
386
444
|
|
|
@@ -388,19 +446,23 @@ module Gemstar
|
|
|
388
446
|
selected = gem[:name] == @selected_gem[:name] ? " is-selected" : ""
|
|
389
447
|
status_class = " status-#{gem[:status]}"
|
|
390
448
|
updated = gem[:status] != :unchanged
|
|
391
|
-
hidden = @selected_filter == "updated" && !updated && gem[:name] != @requested_gem_name ? ' hidden="hidden"' : ""
|
|
449
|
+
hidden = (!gem_visible_in_selected_scope?(gem) || (@selected_filter == "updated" && !updated && gem[:name] != @requested_gem_name)) ? ' hidden="hidden"' : ""
|
|
392
450
|
<<~HTML
|
|
393
451
|
<a
|
|
394
452
|
class="gem-row#{selected}#{status_class}"
|
|
395
|
-
href="#{project_query(project: @selected_project_index, from: @selected_from_revision_id, to: @selected_to_revision_id, filter: @selected_filter,
|
|
453
|
+
href="#{project_query(project: @selected_project_index, from: @selected_from_revision_id, to: @selected_to_revision_id, filter: @selected_filter, scope: @selected_package_scope, package: gem[:name])}"
|
|
396
454
|
data-gem-link="true"
|
|
397
455
|
data-gem-name="#{h(gem[:name])}"
|
|
398
456
|
data-gem-updated="#{updated}"
|
|
399
|
-
data-
|
|
457
|
+
data-package-scope="#{h(gem[:package_scope])}"
|
|
458
|
+
data-detail-url="#{h(detail_query(project: @selected_project_index, from: @selected_from_revision_id, to: @selected_to_revision_id, filter: @selected_filter, scope: @selected_package_scope, package: gem[:name]))}"
|
|
400
459
|
#{hidden}
|
|
401
460
|
>
|
|
402
461
|
<span class="gem-name-row">
|
|
403
|
-
<span class="gem-name"
|
|
462
|
+
<span class="gem-name-lockup">
|
|
463
|
+
<span class="gem-name">#{h(gem[:name])}</span>
|
|
464
|
+
<span class="package-type-tag">#{h(gem[:package_type_label])}</span>
|
|
465
|
+
</span>
|
|
404
466
|
#{updated ? '<span class="gem-updated-dot" aria-label="Updated"></span>' : ""}
|
|
405
467
|
</span>
|
|
406
468
|
<span class="gem-version">#{h(gem[:version_label])}</span>
|
|
@@ -413,7 +475,7 @@ module Gemstar
|
|
|
413
475
|
#{items}
|
|
414
476
|
</nav>
|
|
415
477
|
<section class="empty-panel gem-list-empty" data-gem-list-empty hidden="hidden">
|
|
416
|
-
<p>No updated
|
|
478
|
+
<p>No updated #{h(@selected_project&.package_collection_label&.downcase || "packages")} in this revision range.</p>
|
|
417
479
|
</section>
|
|
418
480
|
HTML
|
|
419
481
|
end
|
|
@@ -421,25 +483,32 @@ module Gemstar
|
|
|
421
483
|
def render_detail
|
|
422
484
|
return empty_detail_html unless @selected_gem
|
|
423
485
|
|
|
486
|
+
metadata = metadata_for(@selected_gem, refresh_if_missing: true)
|
|
424
487
|
cache_key = [
|
|
488
|
+
CACHE_VERSION,
|
|
425
489
|
@selected_project_index,
|
|
426
490
|
@selected_from_revision_id,
|
|
427
491
|
@selected_to_revision_id,
|
|
428
492
|
@selected_filter,
|
|
493
|
+
@selected_package_scope,
|
|
429
494
|
@selected_gem[:name],
|
|
495
|
+
@selected_gem[:package_scope],
|
|
496
|
+
@selected_gem[:package_source_file],
|
|
430
497
|
@selected_gem[:old_version],
|
|
431
498
|
@selected_gem[:new_version],
|
|
499
|
+
@selected_gem[:raw_old_version],
|
|
500
|
+
@selected_gem[:raw_new_version],
|
|
501
|
+
effective_package_version(@selected_gem, metadata || {}),
|
|
432
502
|
@selected_gem[:status]
|
|
433
503
|
]
|
|
434
504
|
detail_cache = self.class.opts[:detail_html_cache]
|
|
435
505
|
return detail_cache[cache_key] if detail_cache.key?(cache_key)
|
|
436
506
|
|
|
437
|
-
metadata = metadata_for(@selected_gem[:name], refresh_if_missing: true)
|
|
438
507
|
groups = grouped_change_sections(@selected_gem)
|
|
439
508
|
detail_pending = detail_pending?(@selected_gem[:name], metadata, groups)
|
|
440
509
|
|
|
441
510
|
detail_html = <<~HTML
|
|
442
|
-
<section class="detail" data-detail-panel tabindex="0" data-detail-pending="#{detail_pending}" data-detail-url="#{h(detail_query(project: @selected_project_index, from: @selected_from_revision_id, to: @selected_to_revision_id, filter: @selected_filter,
|
|
511
|
+
<section class="detail" data-detail-panel tabindex="0" data-detail-pending="#{detail_pending}" data-detail-url="#{h(detail_query(project: @selected_project_index, from: @selected_from_revision_id, to: @selected_to_revision_id, filter: @selected_filter, scope: @selected_package_scope, package: @selected_gem[:name]))}">
|
|
443
512
|
#{render_detail_hero(metadata)}
|
|
444
513
|
#{render_detail_loading_notice if detail_pending}
|
|
445
514
|
#{render_detail_revision_panel(groups)}
|
|
@@ -450,12 +519,33 @@ module Gemstar
|
|
|
450
519
|
detail_html
|
|
451
520
|
end
|
|
452
521
|
|
|
453
|
-
def
|
|
522
|
+
def render_initial_detail
|
|
523
|
+
return empty_detail_html(loading: true) unless @selected_gem
|
|
524
|
+
|
|
525
|
+
detail_url = detail_query(
|
|
526
|
+
project: @selected_project_index,
|
|
527
|
+
from: @selected_from_revision_id,
|
|
528
|
+
to: @selected_to_revision_id,
|
|
529
|
+
filter: @selected_filter,
|
|
530
|
+
scope: @selected_package_scope,
|
|
531
|
+
package: @selected_gem[:name]
|
|
532
|
+
)
|
|
533
|
+
|
|
534
|
+
<<~HTML
|
|
535
|
+
<section class="detail" data-detail-panel tabindex="0" data-detail-pending="false" data-detail-deferred="true" data-detail-url="#{h(detail_url)}">
|
|
536
|
+
<div class="detail-loading-shell" aria-hidden="true">
|
|
537
|
+
<div class="detail-loading-spinner"></div>
|
|
538
|
+
</div>
|
|
539
|
+
</section>
|
|
540
|
+
HTML
|
|
541
|
+
end
|
|
542
|
+
|
|
543
|
+
def empty_detail_html(loading: false)
|
|
454
544
|
<<~HTML
|
|
455
545
|
<section class="detail" data-detail-panel tabindex="0">
|
|
456
546
|
<div class="empty-panel">
|
|
457
|
-
<h2
|
|
458
|
-
<p
|
|
547
|
+
<h2>#{loading ? "Loading details" : "No gem selected"}</h2>
|
|
548
|
+
<p>#{loading ? "Preparing the selected gem details." : "Choose a gem from the list to inspect its current version and changelog revisions."}</p>
|
|
459
549
|
</div>
|
|
460
550
|
</section>
|
|
461
551
|
HTML
|
|
@@ -465,10 +555,10 @@ module Gemstar
|
|
|
465
555
|
description = metadata&.dig("info")
|
|
466
556
|
bundle_origins = Array(@selected_gem[:bundle_origins])
|
|
467
557
|
requirement_names = selected_gem_requirements
|
|
468
|
-
bundled_version =
|
|
558
|
+
bundled_version = detail_bundled_version(metadata)
|
|
469
559
|
added_on = selected_gem_added_on
|
|
470
560
|
title_url = metadata&.dig("homepage_uri")
|
|
471
|
-
title_url =
|
|
561
|
+
title_url = repo_url_for(@selected_gem, metadata: metadata) if title_url.to_s.empty?
|
|
472
562
|
title_markup = if title_url.to_s.empty?
|
|
473
563
|
h(@selected_gem[:name])
|
|
474
564
|
else
|
|
@@ -484,13 +574,24 @@ module Gemstar
|
|
|
484
574
|
</div>
|
|
485
575
|
#{render_detail_links(metadata)}
|
|
486
576
|
</div>
|
|
487
|
-
<
|
|
577
|
+
<div class="detail-subtitle">#{render_detail_subtitle(description)}</div>
|
|
488
578
|
#{render_dependency_details(bundle_origins, requirement_names, added_on)}
|
|
489
579
|
</div>
|
|
490
580
|
</section>
|
|
491
581
|
HTML
|
|
492
582
|
end
|
|
493
583
|
|
|
584
|
+
def render_detail_subtitle(description)
|
|
585
|
+
text = description.to_s.strip
|
|
586
|
+
return "<p>Metadata will appear here when package information is available.</p>" if text.empty?
|
|
587
|
+
|
|
588
|
+
options = { hard_wrap: false }
|
|
589
|
+
options[:input] = "GFM" if defined?(Kramdown::Parser::GFM)
|
|
590
|
+
with_external_links(Kramdown::Document.new(text, options).to_html)
|
|
591
|
+
rescue Kramdown::Error
|
|
592
|
+
"<p>#{h(text)}</p>"
|
|
593
|
+
end
|
|
594
|
+
|
|
494
595
|
def render_added_on(added_on)
|
|
495
596
|
return "" unless added_on
|
|
496
597
|
|
|
@@ -522,12 +623,16 @@ module Gemstar
|
|
|
522
623
|
end
|
|
523
624
|
|
|
524
625
|
def render_detail_links(metadata)
|
|
525
|
-
repo_url =
|
|
626
|
+
repo_url = repo_url_for(@selected_gem, metadata: metadata)
|
|
526
627
|
homepage_url = metadata&.dig("homepage_uri")
|
|
527
|
-
rubygems_url = "https://rubygems.org/gems/#{URI.encode_www_form_component(@selected_gem[:name])}"
|
|
528
628
|
|
|
529
629
|
buttons = []
|
|
530
|
-
|
|
630
|
+
if @selected_gem[:package_scope] == "gems"
|
|
631
|
+
rubygems_url = "https://rubygems.org/gems/#{URI.encode_www_form_component(@selected_gem[:name])}"
|
|
632
|
+
buttons << icon_button("RubyGems", rubygems_url, icon_type: :rubygems)
|
|
633
|
+
elsif homepage_url && !homepage_url.empty? && (!repo_url || homepage_url != repo_url)
|
|
634
|
+
buttons << icon_button("Source", homepage_url, icon_type: :home)
|
|
635
|
+
end
|
|
531
636
|
buttons << icon_button("GitHub", repo_url, icon_type: :github) if repo_url && !repo_url.empty?
|
|
532
637
|
buttons << icon_button("Homepage", homepage_url, icon_type: :home) if homepage_url && !homepage_url.empty?
|
|
533
638
|
|
|
@@ -575,6 +680,8 @@ module Gemstar
|
|
|
575
680
|
end
|
|
576
681
|
|
|
577
682
|
def selected_gem_requirements
|
|
683
|
+
return [] unless @selected_gem[:package_scope] == "gems"
|
|
684
|
+
|
|
578
685
|
lockfile = if @selected_gem[:new_version]
|
|
579
686
|
@selected_project&.lockfile_for_revision(@selected_to_revision_id)
|
|
580
687
|
else
|
|
@@ -586,7 +693,12 @@ module Gemstar
|
|
|
586
693
|
|
|
587
694
|
def selected_gem_added_on
|
|
588
695
|
revision_id = @selected_gem[:new_version] ? @selected_to_revision_id : @selected_from_revision_id
|
|
589
|
-
@selected_project&.
|
|
696
|
+
@selected_project&.package_added_on(
|
|
697
|
+
@selected_gem[:name],
|
|
698
|
+
package_scope: @selected_gem[:package_scope],
|
|
699
|
+
source_file: @selected_gem[:package_source_file],
|
|
700
|
+
revision_id: revision_id
|
|
701
|
+
)
|
|
590
702
|
end
|
|
591
703
|
|
|
592
704
|
def selected_gem_platform_items
|
|
@@ -617,6 +729,31 @@ module Gemstar
|
|
|
617
729
|
when :rubygems
|
|
618
730
|
remote = source[:remote]
|
|
619
731
|
[remote.to_s.empty? ? "RubyGems" : "RubyGems (#{h(remote)})"]
|
|
732
|
+
when :importmap
|
|
733
|
+
remote = source[:remote]
|
|
734
|
+
package_name = source[:package_name]
|
|
735
|
+
package_version = source[:package_version]
|
|
736
|
+
label =
|
|
737
|
+
if package_name && package_version
|
|
738
|
+
"Importmap (#{h(package_name)} @ #{h(package_version)})"
|
|
739
|
+
elsif package_name
|
|
740
|
+
"Importmap (#{h(package_name)})"
|
|
741
|
+
elsif remote.to_s.empty?
|
|
742
|
+
"Importmap"
|
|
743
|
+
else
|
|
744
|
+
"Importmap (#{h(remote)})"
|
|
745
|
+
end
|
|
746
|
+
[label]
|
|
747
|
+
when :npm
|
|
748
|
+
remote = source[:remote]
|
|
749
|
+
registry_url = source[:registry_url]
|
|
750
|
+
if remote.to_s.empty? && registry_url.to_s.empty?
|
|
751
|
+
["npm"]
|
|
752
|
+
elsif remote.to_s.empty?
|
|
753
|
+
["npm (#{h(registry_url)})"]
|
|
754
|
+
else
|
|
755
|
+
["npm (#{h(remote)})"]
|
|
756
|
+
end
|
|
620
757
|
else
|
|
621
758
|
[]
|
|
622
759
|
end
|
|
@@ -645,7 +782,8 @@ module Gemstar
|
|
|
645
782
|
from: @selected_from_revision_id,
|
|
646
783
|
to: @selected_to_revision_id,
|
|
647
784
|
filter: @selected_filter,
|
|
648
|
-
|
|
785
|
+
scope: @selected_package_scope,
|
|
786
|
+
package: name
|
|
649
787
|
)
|
|
650
788
|
|
|
651
789
|
%(<a href="#{h(href)}" data-gem-link-inline="true">#{h(name)}</a>)
|
|
@@ -664,7 +802,7 @@ module Gemstar
|
|
|
664
802
|
def render_detail_loading_notice
|
|
665
803
|
<<~HTML
|
|
666
804
|
<section class="empty-panel">
|
|
667
|
-
<p>Loading
|
|
805
|
+
<p>Loading package metadata and changelog in the background...</p>
|
|
668
806
|
</section>
|
|
669
807
|
HTML
|
|
670
808
|
end
|
|
@@ -700,7 +838,10 @@ module Gemstar
|
|
|
700
838
|
<article class="revision-card revision-#{section[:kind]}#{status_class}">
|
|
701
839
|
<header class="revision-card-header">
|
|
702
840
|
<div class="revision-card-titlebar">
|
|
703
|
-
<
|
|
841
|
+
<div class="revision-card-heading">
|
|
842
|
+
<h5>#{h(section[:title] || section[:version])}</h5>
|
|
843
|
+
#{render_release_date(section[:release_date])}
|
|
844
|
+
</div>
|
|
704
845
|
<div class="revision-card-actions">
|
|
705
846
|
#{title_links.join}
|
|
706
847
|
</div>
|
|
@@ -713,6 +854,12 @@ module Gemstar
|
|
|
713
854
|
HTML
|
|
714
855
|
end
|
|
715
856
|
|
|
857
|
+
def render_release_date(release_date)
|
|
858
|
+
return "" if release_date.to_s.empty?
|
|
859
|
+
|
|
860
|
+
%(<span class="revision-release-date" title="Estimated release date">#{h(release_date)}</span>)
|
|
861
|
+
end
|
|
862
|
+
|
|
716
863
|
def grouped_change_sections(gem_state)
|
|
717
864
|
sections = change_sections(gem_state)
|
|
718
865
|
latest = sections.select { |section| section[:kind] == :future }
|
|
@@ -732,18 +879,33 @@ module Gemstar
|
|
|
732
879
|
end
|
|
733
880
|
|
|
734
881
|
def change_sections(gem_state)
|
|
735
|
-
|
|
882
|
+
metadata_hash = metadata_for(gem_state) || {}
|
|
883
|
+
current_version = effective_package_version(gem_state, metadata_hash)
|
|
884
|
+
cache_key = [
|
|
885
|
+
CACHE_VERSION,
|
|
886
|
+
gem_state[:name],
|
|
887
|
+
gem_state[:package_scope],
|
|
888
|
+
gem_state[:package_source_file],
|
|
889
|
+
gem_state[:old_version],
|
|
890
|
+
gem_state[:new_version],
|
|
891
|
+
gem_state[:raw_old_version],
|
|
892
|
+
gem_state[:raw_new_version],
|
|
893
|
+
current_version,
|
|
894
|
+
gem_state[:status]
|
|
895
|
+
]
|
|
736
896
|
change_sections_cache = self.class.opts[:change_sections_cache]
|
|
737
897
|
return change_sections_cache[cache_key] if change_sections_cache.key?(cache_key)
|
|
738
898
|
|
|
739
|
-
return [] if gem_state[:new_version].nil? &&
|
|
740
|
-
|
|
741
|
-
|
|
899
|
+
return [] if gem_state[:new_version].nil? &&
|
|
900
|
+
gem_state[:old_version].nil? &&
|
|
901
|
+
(current_version.nil? || current_version.to_s.empty?)
|
|
902
|
+
metadata = metadata_adapter_for(gem_state)
|
|
903
|
+
return change_sections_cache[cache_key] = [] unless metadata
|
|
742
904
|
sections = resolved_sections(metadata, gem_state)
|
|
743
905
|
return change_sections_cache[cache_key] = [] if sections.nil? || sections.empty?
|
|
744
906
|
|
|
745
|
-
current_version = gem_state[:new_version] || gem_state[:old_version]
|
|
746
907
|
previous_version = gem_state[:old_version]
|
|
908
|
+
release_dates = resolved_release_dates(metadata, sections.keys, gem_state)
|
|
747
909
|
|
|
748
910
|
rendered_sections = sections.keys.filter_map do |version|
|
|
749
911
|
kind = section_kind(version, previous_version, current_version, gem_state[:status])
|
|
@@ -755,6 +917,7 @@ module Gemstar
|
|
|
755
917
|
title: content[:title],
|
|
756
918
|
kind: kind,
|
|
757
919
|
previous_version: previous_section_version(sections.keys, version),
|
|
920
|
+
release_date: release_date_for(release_dates, version),
|
|
758
921
|
html: content[:html]
|
|
759
922
|
}
|
|
760
923
|
end
|
|
@@ -769,18 +932,67 @@ module Gemstar
|
|
|
769
932
|
cached_sections = changelog.sections(cache_only: true) || {}
|
|
770
933
|
return cached_sections unless selected_gem_requires_refresh?(gem_state, cached_sections)
|
|
771
934
|
|
|
772
|
-
@metadata_cache.delete(gem_state[:name])
|
|
935
|
+
@metadata_cache.delete([gem_state[:package_scope], gem_state[:name]])
|
|
773
936
|
metadata.meta(cache_only: false, force_refresh: true)
|
|
774
937
|
metadata.repo_uri(cache_only: false, force_refresh: true)
|
|
775
|
-
|
|
938
|
+
refreshed_sections = metadata.changelog_sections(
|
|
939
|
+
versions: relevant_package_versions(gem_state, metadata),
|
|
940
|
+
cache_only: false,
|
|
941
|
+
force_refresh: true
|
|
942
|
+
)
|
|
943
|
+
cached_sections.merge(refreshed_sections || {})
|
|
944
|
+
end
|
|
945
|
+
|
|
946
|
+
def resolved_release_dates(metadata, versions, gem_state)
|
|
947
|
+
changelog = Gemstar::ChangeLog.new(metadata)
|
|
948
|
+
cached_dates = changelog.release_dates(versions: versions, cache_only: true) || {}
|
|
949
|
+
return cached_dates unless selected_gem_missing_release_dates?(gem_state, versions, cached_dates)
|
|
950
|
+
|
|
951
|
+
fetched_dates = changelog.release_dates(versions: versions, cache_only: false) || {}
|
|
952
|
+
cached_dates.merge(fetched_dates)
|
|
953
|
+
rescue StandardError
|
|
954
|
+
{}
|
|
955
|
+
end
|
|
956
|
+
|
|
957
|
+
def release_date_for(release_dates, version)
|
|
958
|
+
release_dates.find do |candidate_version, _date|
|
|
959
|
+
normalize_release_version_key(candidate_version) == normalize_release_version_key(version)
|
|
960
|
+
end&.last
|
|
961
|
+
end
|
|
962
|
+
|
|
963
|
+
def selected_gem_missing_release_dates?(gem_state, versions, release_dates)
|
|
964
|
+
return false unless @selected_gem && gem_state[:name] == @selected_gem[:name]
|
|
965
|
+
|
|
966
|
+
requested_versions = Array(versions).map { |version| normalize_release_version_key(version) }.compact
|
|
967
|
+
dated_versions = release_dates.keys.map { |version| normalize_release_version_key(version) }.compact
|
|
968
|
+
(requested_versions - dated_versions).any?
|
|
969
|
+
end
|
|
970
|
+
|
|
971
|
+
def normalize_release_version_key(version)
|
|
972
|
+
value = version.to_s.strip
|
|
973
|
+
return nil if value.empty?
|
|
974
|
+
|
|
975
|
+
value.sub(/\Av/i, "")
|
|
976
|
+
end
|
|
977
|
+
|
|
978
|
+
def relevant_package_versions(gem_state, metadata)
|
|
979
|
+
metadata_hash = metadata_for(gem_state) || {}
|
|
980
|
+
[
|
|
981
|
+
gem_state[:old_version],
|
|
982
|
+
gem_state[:new_version],
|
|
983
|
+
gem_state[:raw_old_version],
|
|
984
|
+
gem_state[:raw_new_version],
|
|
985
|
+
gem_state.dig(:source, :package_version),
|
|
986
|
+
metadata_hash["version"]
|
|
987
|
+
].compact
|
|
776
988
|
end
|
|
777
989
|
|
|
778
990
|
def selected_gem_requires_refresh?(gem_state, cached_sections)
|
|
779
991
|
return false unless @selected_gem && gem_state[:name] == @selected_gem[:name]
|
|
780
992
|
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
993
|
+
metadata = metadata_for(gem_state) || {}
|
|
994
|
+
bundled_version = effective_package_version(gem_state, metadata)
|
|
995
|
+
return false if bundled_version.nil? || bundled_version.to_s.empty?
|
|
784
996
|
has_upstream_release_source =
|
|
785
997
|
!metadata["changelog_uri"].to_s.empty? ||
|
|
786
998
|
!metadata["source_code_uri"].to_s.empty? ||
|
|
@@ -801,6 +1013,7 @@ module Gemstar
|
|
|
801
1013
|
def section_kind(version, previous_version, current_version, status)
|
|
802
1014
|
return :future if compare_versions(version, current_version) == 1
|
|
803
1015
|
return :current if status == :added && compare_versions(version, current_version) <= 0
|
|
1016
|
+
return :current if previous_version.nil? && current_version && compare_versions(version, current_version) == 0
|
|
804
1017
|
|
|
805
1018
|
lower_bound = previous_version || current_version
|
|
806
1019
|
if compare_versions(version, lower_bound) == 1 && compare_versions(version, current_version) <= 0
|
|
@@ -836,7 +1049,7 @@ module Gemstar
|
|
|
836
1049
|
return { title: heading_version.to_s, html: "<p>No changelog text available.</p>" } if text.strip.empty?
|
|
837
1050
|
|
|
838
1051
|
if heading_version
|
|
839
|
-
text = text
|
|
1052
|
+
text = strip_leading_version_heading(text, heading_version)
|
|
840
1053
|
end
|
|
841
1054
|
|
|
842
1055
|
options = { hard_wrap: false }
|
|
@@ -863,29 +1076,135 @@ module Gemstar
|
|
|
863
1076
|
{ title: title, html: fragment.to_html }
|
|
864
1077
|
end
|
|
865
1078
|
|
|
1079
|
+
def strip_leading_version_heading(text, heading_version)
|
|
1080
|
+
stripped = text.sub(/\A\s*#+\s*v?#{Regexp.escape(heading_version)}\s*\n+/i, "")
|
|
1081
|
+
return strip_leading_heading_separator(stripped) unless stripped == text
|
|
1082
|
+
|
|
1083
|
+
lines = text.lines
|
|
1084
|
+
return text if lines.empty?
|
|
1085
|
+
|
|
1086
|
+
first_line = lines.first.to_s
|
|
1087
|
+
heading_like =
|
|
1088
|
+
first_line.match?(/\A\s*v?#{Regexp.escape(heading_version)}\b/i) ||
|
|
1089
|
+
first_line.match?(/\A\s*[\[(]?v?#{Regexp.escape(heading_version)}\b/i)
|
|
1090
|
+
|
|
1091
|
+
return text unless heading_like
|
|
1092
|
+
|
|
1093
|
+
remaining = lines.drop(1)
|
|
1094
|
+
remaining.shift while remaining.first&.strip&.empty?
|
|
1095
|
+
strip_leading_heading_separator(remaining.join)
|
|
1096
|
+
end
|
|
1097
|
+
|
|
1098
|
+
def strip_leading_heading_separator(text)
|
|
1099
|
+
text.sub(/\A\s*(?:#{Regexp.escape("#")}{4,}|[-=]{3,})\s*\n+/, "")
|
|
1100
|
+
end
|
|
1101
|
+
|
|
866
1102
|
def compare_versions(left, right)
|
|
867
1103
|
Gem::Version.new(left.to_s.gsub(/-[\w\-]+$/, "")) <=> Gem::Version.new(right.to_s.gsub(/-[\w\-]+$/, ""))
|
|
868
1104
|
rescue ArgumentError
|
|
869
1105
|
left.to_s <=> right.to_s
|
|
870
1106
|
end
|
|
871
1107
|
|
|
872
|
-
def metadata_for(
|
|
873
|
-
|
|
1108
|
+
def metadata_for(package_state_or_name, refresh_if_missing: false)
|
|
1109
|
+
package_state = package_state_or_name.is_a?(Hash) ? package_state_or_name : { name: package_state_or_name, package_scope: "gems" }
|
|
1110
|
+
gem_name = package_state[:name]
|
|
1111
|
+
cache_key = [package_state[:package_scope], gem_name]
|
|
1112
|
+
cached = @metadata_cache[cache_key]
|
|
874
1113
|
return nil if cached.equal?(MISSING_METADATA)
|
|
875
1114
|
return cached if cached
|
|
876
1115
|
|
|
1116
|
+
if package_state[:package_scope] != "gems"
|
|
1117
|
+
metadata = local_package_metadata(package_state)
|
|
1118
|
+
adapter = metadata_adapter_for(package_state)
|
|
1119
|
+
remote_metadata = adapter&.meta(cache_only: true)
|
|
1120
|
+
remote_metadata = adapter&.meta(cache_only: false, force_refresh: true) if remote_metadata.nil? && refresh_if_missing
|
|
1121
|
+
metadata = metadata.compact.merge(remote_metadata || {})
|
|
1122
|
+
@metadata_cache[cache_key] = metadata || MISSING_METADATA
|
|
1123
|
+
return metadata
|
|
1124
|
+
end
|
|
1125
|
+
|
|
877
1126
|
metadata = Gemstar::RubyGemsMetadata.new(gem_name).meta(cache_only: true)
|
|
878
1127
|
if metadata.nil? && refresh_if_missing
|
|
879
1128
|
metadata = Gemstar::RubyGemsMetadata.new(gem_name).meta(cache_only: false, force_refresh: true)
|
|
880
1129
|
end
|
|
881
1130
|
|
|
882
|
-
@metadata_cache[
|
|
1131
|
+
@metadata_cache[cache_key] = metadata || MISSING_METADATA
|
|
883
1132
|
metadata
|
|
884
1133
|
rescue StandardError
|
|
885
|
-
@metadata_cache[
|
|
1134
|
+
@metadata_cache[cache_key] = MISSING_METADATA
|
|
886
1135
|
nil
|
|
887
1136
|
end
|
|
888
1137
|
|
|
1138
|
+
def local_package_metadata(package_state)
|
|
1139
|
+
source = package_state[:source] || {}
|
|
1140
|
+
remote = source[:remote].to_s
|
|
1141
|
+
repo_url = source[:repo_url].to_s
|
|
1142
|
+
package_name = source[:package_name].to_s
|
|
1143
|
+
package_version = source[:package_version].to_s
|
|
1144
|
+
registry_url = source[:registry_url].to_s
|
|
1145
|
+
provider_gem = source[:provider_gem].to_s
|
|
1146
|
+
package_name = package_state[:name].to_s if package_name.empty? && source[:type] == :npm
|
|
1147
|
+
package_version = (package_state[:new_version] || package_state[:old_version]).to_s if package_version.empty? && source[:type] == :npm
|
|
1148
|
+
registry_url = "https://www.npmjs.com/package/#{package_name}" if registry_url.empty? && !package_name.empty?
|
|
1149
|
+
info = if package_name.empty?
|
|
1150
|
+
"JavaScript package pinned in config/importmap.rb"
|
|
1151
|
+
elsif !provider_gem.empty? && !package_version.empty?
|
|
1152
|
+
"JavaScript package `#{package_name}` provided by the `#{provider_gem}` gem at version `#{package_version}`"
|
|
1153
|
+
elsif !provider_gem.empty?
|
|
1154
|
+
"JavaScript package `#{package_name}` provided by the `#{provider_gem}` gem"
|
|
1155
|
+
elsif source[:type] == :npm && !package_version.empty?
|
|
1156
|
+
"JavaScript package `#{package_name}` locked to `#{package_version}` in package-lock.json"
|
|
1157
|
+
elsif package_version.empty?
|
|
1158
|
+
"JavaScript package `#{package_name}` pinned in config/importmap.rb"
|
|
1159
|
+
else
|
|
1160
|
+
"JavaScript package `#{package_name}` pinned to `#{package_version}` in config/importmap.rb"
|
|
1161
|
+
end
|
|
1162
|
+
|
|
1163
|
+
{
|
|
1164
|
+
"info" => info,
|
|
1165
|
+
"project_uri" => registry_url.empty? ? nil : registry_url,
|
|
1166
|
+
"homepage_uri" => absolute_url?(remote) ? remote : nil,
|
|
1167
|
+
"source_code_uri" => repo_url.empty? ? nil : repo_url
|
|
1168
|
+
}
|
|
1169
|
+
end
|
|
1170
|
+
|
|
1171
|
+
def repo_url_for(package_state, metadata: nil)
|
|
1172
|
+
return nil unless package_state
|
|
1173
|
+
if package_state[:package_scope] != "gems"
|
|
1174
|
+
url = package_state.dig(:source, :repo_url) || metadata&.dig("source_code_uri")
|
|
1175
|
+
return normalize_repo_url(url)
|
|
1176
|
+
end
|
|
1177
|
+
|
|
1178
|
+
normalize_repo_url(Gemstar::RubyGemsMetadata.new(package_state[:name]).repo_uri(cache_only: true))
|
|
1179
|
+
end
|
|
1180
|
+
|
|
1181
|
+
def normalize_repo_url(url)
|
|
1182
|
+
value = url.to_s
|
|
1183
|
+
return nil if value.empty?
|
|
1184
|
+
|
|
1185
|
+
value = value.sub(/\Agit\+/, "")
|
|
1186
|
+
value = value.sub(/\Agit:\/\//, "https://")
|
|
1187
|
+
value = value.gsub(/\.git\z/, "")
|
|
1188
|
+
value
|
|
1189
|
+
end
|
|
1190
|
+
|
|
1191
|
+
def metadata_adapter_for(package_state)
|
|
1192
|
+
return Gemstar::RubyGemsMetadata.new(package_state[:name]) if package_state[:package_scope] == "gems"
|
|
1193
|
+
return nil unless package_state[:package_scope] == "js"
|
|
1194
|
+
|
|
1195
|
+
provider_gem = package_state.dig(:source, :provider_gem)
|
|
1196
|
+
return Gemstar::RubyGemsMetadata.new(provider_gem) unless provider_gem.to_s.empty?
|
|
1197
|
+
|
|
1198
|
+
package_name = package_state.dig(:source, :package_name) || package_state[:name]
|
|
1199
|
+
return nil if package_name.to_s.empty?
|
|
1200
|
+
|
|
1201
|
+
Gemstar::NpmMetadata.new(package_name)
|
|
1202
|
+
end
|
|
1203
|
+
|
|
1204
|
+
def absolute_url?(value)
|
|
1205
|
+
value.to_s.match?(%r{\Ahttps?://}i)
|
|
1206
|
+
end
|
|
1207
|
+
|
|
889
1208
|
def detail_pending?(gem_name, metadata, groups)
|
|
890
1209
|
false
|
|
891
1210
|
end
|
|
@@ -940,7 +1259,7 @@ module Gemstar
|
|
|
940
1259
|
end
|
|
941
1260
|
|
|
942
1261
|
def revision_card_links(section)
|
|
943
|
-
repo_url =
|
|
1262
|
+
repo_url = repo_url_for(@selected_gem)
|
|
944
1263
|
return [] if repo_url.to_s.empty?
|
|
945
1264
|
|
|
946
1265
|
links = []
|
|
@@ -953,25 +1272,33 @@ module Gemstar
|
|
|
953
1272
|
end
|
|
954
1273
|
|
|
955
1274
|
def fallback_current_section(gem_state, previous_sections, latest_sections)
|
|
956
|
-
|
|
1275
|
+
metadata = metadata_for(gem_state) || {}
|
|
1276
|
+
version = effective_package_version(gem_state, metadata)
|
|
957
1277
|
return nil if version.nil?
|
|
958
1278
|
return nil if previous_sections.any? { |section| section[:version] == version }
|
|
959
1279
|
return nil if latest_sections.any? { |section| section[:version] == version }
|
|
960
1280
|
|
|
961
|
-
|
|
962
|
-
repo_url = Gemstar::RubyGemsMetadata.new(gem_state[:name]).repo_uri(cache_only: true)
|
|
1281
|
+
repo_url = repo_url_for(gem_state, metadata: metadata)
|
|
963
1282
|
fallback_url =
|
|
964
1283
|
if !repo_url.to_s.empty?
|
|
965
1284
|
repo_url
|
|
966
1285
|
elsif metadata["project_uri"]
|
|
967
1286
|
metadata["project_uri"]
|
|
1287
|
+
elsif metadata["source_code_uri"]
|
|
1288
|
+
metadata["source_code_uri"]
|
|
1289
|
+
elsif metadata["homepage_uri"]
|
|
1290
|
+
metadata["homepage_uri"]
|
|
968
1291
|
else
|
|
969
1292
|
metadata["documentation_uri"]
|
|
970
1293
|
end
|
|
971
1294
|
fallback_label = if repo_url.to_s.empty?
|
|
972
|
-
|
|
1295
|
+
if gem_state[:package_scope] == "gems"
|
|
1296
|
+
metadata["project_uri"] ? "the RubyGems page" : "the gem documentation"
|
|
1297
|
+
else
|
|
1298
|
+
fallback_url == metadata["documentation_uri"] ? "the package documentation" : "the package source"
|
|
1299
|
+
end
|
|
973
1300
|
else
|
|
974
|
-
"the gem repository"
|
|
1301
|
+
gem_state[:package_scope] == "gems" ? "the gem repository" : "the package repository"
|
|
975
1302
|
end
|
|
976
1303
|
fallback_link = if fallback_url.to_s.empty?
|
|
977
1304
|
fallback_label
|
|
@@ -995,6 +1322,16 @@ module Gemstar
|
|
|
995
1322
|
nil
|
|
996
1323
|
end
|
|
997
1324
|
|
|
1325
|
+
def detail_bundled_version(metadata)
|
|
1326
|
+
effective_package_version(@selected_gem, metadata || {})
|
|
1327
|
+
end
|
|
1328
|
+
|
|
1329
|
+
def effective_package_version(package_state, metadata)
|
|
1330
|
+
package_state[:new_version] ||
|
|
1331
|
+
package_state[:old_version] ||
|
|
1332
|
+
(package_state[:package_scope] == "js" ? metadata["version"] : nil)
|
|
1333
|
+
end
|
|
1334
|
+
|
|
998
1335
|
def github_compare_url(repo_url, previous_version, current_version)
|
|
999
1336
|
return nil unless repo_url.include?("github.com")
|
|
1000
1337
|
return nil if previous_version.nil? || current_version.nil?
|
|
@@ -1024,17 +1361,18 @@ module Gemstar
|
|
|
1024
1361
|
html
|
|
1025
1362
|
end
|
|
1026
1363
|
|
|
1027
|
-
def detail_query(project:, from:, to:, filter:,
|
|
1028
|
-
"/detail?#{URI.encode_www_form(project: project, from: from, to: to, filter: filter,
|
|
1364
|
+
def detail_query(project:, from:, to:, filter:, scope:, package:)
|
|
1365
|
+
"/detail?#{URI.encode_www_form(project: project, from: from, to: to, filter: filter, scope: scope, package: package)}"
|
|
1029
1366
|
end
|
|
1030
1367
|
|
|
1031
|
-
def project_query(project:, from:, to:, filter:,
|
|
1368
|
+
def project_query(project:, from:, to:, filter:, scope:, package:)
|
|
1032
1369
|
params = {
|
|
1033
1370
|
project: project,
|
|
1034
1371
|
from: from,
|
|
1035
1372
|
to: to,
|
|
1036
1373
|
filter: filter,
|
|
1037
|
-
|
|
1374
|
+
scope: scope,
|
|
1375
|
+
package: package
|
|
1038
1376
|
}.compact
|
|
1039
1377
|
|
|
1040
1378
|
"/?#{URI.encode_www_form(params)}"
|
|
@@ -1044,21 +1382,18 @@ module Gemstar
|
|
|
1044
1382
|
render_template(
|
|
1045
1383
|
"page.html.erb",
|
|
1046
1384
|
title: h(title),
|
|
1047
|
-
favicon_data_uri: favicon_data_uri,
|
|
1048
1385
|
styles_css: template_source("app.css"),
|
|
1049
1386
|
body_html: yield
|
|
1050
1387
|
)
|
|
1051
1388
|
end
|
|
1052
1389
|
|
|
1053
|
-
def
|
|
1054
|
-
|
|
1390
|
+
def favicon_svg
|
|
1391
|
+
<<~SVG
|
|
1055
1392
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
|
1056
1393
|
<rect width="64" height="64" rx="14" fill="#b44d25"/>
|
|
1057
1394
|
<text x="32" y="44" text-anchor="middle" font-family="Avenir Next, Helvetica Neue, Segoe UI, sans-serif" font-size="34" font-weight="700" fill="#ffffff">G</text>
|
|
1058
1395
|
</svg>
|
|
1059
1396
|
SVG
|
|
1060
|
-
|
|
1061
|
-
"data:image/svg+xml,#{URI.encode_www_form_component(svg)}"
|
|
1062
1397
|
end
|
|
1063
1398
|
|
|
1064
1399
|
def render_behavior_script
|
|
@@ -1066,6 +1401,7 @@ module Gemstar
|
|
|
1066
1401
|
"app.js.erb",
|
|
1067
1402
|
empty_detail_html_json: empty_detail_html.dump,
|
|
1068
1403
|
selected_filter_json: @selected_filter.dump,
|
|
1404
|
+
selected_package_scope_json: @selected_package_scope.dump,
|
|
1069
1405
|
selected_project_index: @selected_project_index || 0
|
|
1070
1406
|
)
|
|
1071
1407
|
|
|
@@ -1091,6 +1427,24 @@ module Gemstar
|
|
|
1091
1427
|
def h(value)
|
|
1092
1428
|
CGI.escapeHTML(value.to_s)
|
|
1093
1429
|
end
|
|
1430
|
+
|
|
1431
|
+
def render_package_scope_filters
|
|
1432
|
+
options = @selected_project&.package_scope_options || []
|
|
1433
|
+
return "" if options.size <= 1
|
|
1434
|
+
|
|
1435
|
+
buttons = []
|
|
1436
|
+
buttons << %(<button type="button" class="list-filter-button#{' is-active' if @selected_package_scope == "all"}" data-ecosystem-button="all">All</button>)
|
|
1437
|
+
|
|
1438
|
+
buttons.concat(options.map do |option|
|
|
1439
|
+
%(<button type="button" class="list-filter-button#{' is-active' if @selected_package_scope == option[:id]}" data-ecosystem-button="#{h(option[:id])}">#{h(option[:label])}</button>)
|
|
1440
|
+
end)
|
|
1441
|
+
|
|
1442
|
+
<<~HTML
|
|
1443
|
+
<div class="list-filters list-filters-secondary" data-ecosystem-filters>
|
|
1444
|
+
#{buttons.join}
|
|
1445
|
+
</div>
|
|
1446
|
+
HTML
|
|
1447
|
+
end
|
|
1094
1448
|
end
|
|
1095
1449
|
end
|
|
1096
1450
|
end
|