gemstar 1.0.2 → 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 +39 -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 +337 -31
- data/lib/gemstar/cli.rb +5 -1
- data/lib/gemstar/commands/diff.rb +197 -31
- data/lib/gemstar/commands/server.rb +94 -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_repo.rb +41 -3
- data/lib/gemstar/importmap_file.rb +193 -0
- data/lib/gemstar/lock_file.rb +91 -9
- 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 +325 -37
- data/lib/gemstar/ruby_gems_metadata.rb +77 -2
- data/lib/gemstar/version.rb +1 -2
- data/lib/gemstar/web/app.rb +418 -70
- data/lib/gemstar/web/templates/app.css +40 -5
- data/lib/gemstar/web/templates/app.js.erb +119 -15
- data/lib/gemstar/web/templates/page.html.erb +2 -1
- data/lib/gemstar.rb +3 -0
- metadata +7 -2
data/lib/gemstar/web/app.rb
CHANGED
|
@@ -13,6 +13,7 @@ module Gemstar
|
|
|
13
13
|
module Web
|
|
14
14
|
class App < Roda
|
|
15
15
|
MISSING_METADATA = Object.new
|
|
16
|
+
CACHE_VERSION = "v6"
|
|
16
17
|
|
|
17
18
|
class << self
|
|
18
19
|
def build(projects:, config_home:, cache_warmer: nil)
|
|
@@ -44,6 +45,16 @@ module Gemstar
|
|
|
44
45
|
end
|
|
45
46
|
end
|
|
46
47
|
|
|
48
|
+
r.get "favicon.svg" do
|
|
49
|
+
response["Content-Type"] = "image/svg+xml; charset=utf-8"
|
|
50
|
+
favicon_svg
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
r.get "favicon.ico" do
|
|
54
|
+
response["Content-Type"] = "image/svg+xml; charset=utf-8"
|
|
55
|
+
favicon_svg
|
|
56
|
+
end
|
|
57
|
+
|
|
47
58
|
r.get "detail" do
|
|
48
59
|
request_cache_key = detail_request_cache_key(r.params)
|
|
49
60
|
request_cache = self.class.opts[:detail_request_cache]
|
|
@@ -62,7 +73,7 @@ module Gemstar
|
|
|
62
73
|
project_index = selected_project_index(r.params["project"])
|
|
63
74
|
project = @projects[project_index]
|
|
64
75
|
response.status = 404
|
|
65
|
-
next "Gemfile not found" unless project
|
|
76
|
+
next "Gemfile not found" unless project&.gemfile?
|
|
66
77
|
|
|
67
78
|
response["Content-Type"] = "text/plain; charset=utf-8"
|
|
68
79
|
File.read(project.gemfile_path)
|
|
@@ -86,23 +97,39 @@ module Gemstar
|
|
|
86
97
|
project = @projects[project_index]
|
|
87
98
|
return nil unless project
|
|
88
99
|
|
|
89
|
-
lockfile_stamp =
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
0
|
|
94
|
-
end
|
|
100
|
+
lockfile_stamp = File.file?(project.lockfile_path) ? File.mtime(project.lockfile_path).to_i : 0
|
|
101
|
+
importmap_stamp = File.file?(project.importmap_path) ? File.mtime(project.importmap_path).to_i : 0
|
|
102
|
+
importmap_vendor_stamp = importmap_vendor_mtime(project)
|
|
103
|
+
package_lock_stamp = File.file?(project.package_lock_path) ? File.mtime(project.package_lock_path).to_i : 0
|
|
95
104
|
|
|
96
105
|
[
|
|
106
|
+
CACHE_VERSION,
|
|
97
107
|
project_index,
|
|
98
108
|
params["from"],
|
|
99
109
|
params["to"],
|
|
100
110
|
params["filter"],
|
|
101
|
-
params["
|
|
102
|
-
|
|
111
|
+
params["scope"],
|
|
112
|
+
package_param(params),
|
|
113
|
+
lockfile_stamp,
|
|
114
|
+
importmap_stamp,
|
|
115
|
+
importmap_vendor_stamp,
|
|
116
|
+
package_lock_stamp
|
|
103
117
|
]
|
|
104
118
|
end
|
|
105
119
|
|
|
120
|
+
def importmap_vendor_mtime(project)
|
|
121
|
+
return 0 unless project&.importmap?
|
|
122
|
+
|
|
123
|
+
project.current_importmap.specs.values.filter_map do |target|
|
|
124
|
+
next unless target.to_s.end_with?(".js", ".mjs")
|
|
125
|
+
|
|
126
|
+
path = File.join(project.directory, "vendor", "javascript", target.to_s)
|
|
127
|
+
File.mtime(path).to_i if File.file?(path)
|
|
128
|
+
end.max || 0
|
|
129
|
+
rescue StandardError
|
|
130
|
+
0
|
|
131
|
+
end
|
|
132
|
+
|
|
106
133
|
def page_title
|
|
107
134
|
return "Gemstar" unless @selected_project
|
|
108
135
|
|
|
@@ -117,9 +144,15 @@ module Gemstar
|
|
|
117
144
|
@selected_from_revision_id = selected_from_revision_id(params["from"])
|
|
118
145
|
@selected_to_revision_id = selected_to_revision_id(@selected_to_revision_id)
|
|
119
146
|
@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
|
-
@
|
|
147
|
+
requested_package_name = package_param(params)
|
|
148
|
+
@requested_gem_name = requested_package_name
|
|
149
|
+
@selected_package_scope = selected_package_scope(params["scope"], requested_package_name)
|
|
150
|
+
@selected_filter = selected_filter(params["filter"], requested_package_name)
|
|
151
|
+
@selected_gem = selected_gem_state(requested_package_name)
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def package_param(params)
|
|
155
|
+
params["package"] || params["gem"]
|
|
123
156
|
end
|
|
124
157
|
|
|
125
158
|
def prioritize_selected_gem
|
|
@@ -167,10 +200,24 @@ module Gemstar
|
|
|
167
200
|
@gem_states.any? { |gem| gem[:status] != :unchanged } ? "updated" : "all"
|
|
168
201
|
end
|
|
169
202
|
|
|
203
|
+
def selected_package_scope(raw_scope, raw_gem_name)
|
|
204
|
+
return "all" if @gem_states.empty?
|
|
205
|
+
|
|
206
|
+
available_scopes = available_package_scopes
|
|
207
|
+
default_scope = available_scopes == ["gems"] ? "gems" : "all"
|
|
208
|
+
return raw_scope if raw_scope == "all" || available_scopes.include?(raw_scope)
|
|
209
|
+
|
|
210
|
+
selected_gem = @gem_states.find { |gem| gem[:name] == raw_gem_name }
|
|
211
|
+
return selected_gem[:package_scope] if selected_gem
|
|
212
|
+
|
|
213
|
+
default_scope
|
|
214
|
+
end
|
|
215
|
+
|
|
170
216
|
def selected_gem_state(raw_gem_name)
|
|
171
217
|
return nil if @gem_states.empty?
|
|
172
218
|
|
|
173
|
-
exact_match = @gem_states.find { |gem| gem[:name] == raw_gem_name }
|
|
219
|
+
exact_match = @gem_states.find { |gem| gem[:name] == raw_gem_name && gem_visible_in_selected_scope?(gem) }
|
|
220
|
+
exact_match ||= @gem_states.find { |gem| gem[:name] == raw_gem_name }
|
|
174
221
|
return exact_match if exact_match
|
|
175
222
|
|
|
176
223
|
@gem_states.find { |gem| gem_visible_in_selected_filter?(gem) && gem[:status] != :unchanged } ||
|
|
@@ -180,11 +227,20 @@ module Gemstar
|
|
|
180
227
|
end
|
|
181
228
|
|
|
182
229
|
def gem_visible_in_selected_filter?(gem_state)
|
|
230
|
+
return false unless gem_visible_in_selected_scope?(gem_state)
|
|
183
231
|
return true if @selected_filter != "updated"
|
|
184
232
|
|
|
185
233
|
gem_state[:status] != :unchanged
|
|
186
234
|
end
|
|
187
235
|
|
|
236
|
+
def gem_visible_in_selected_scope?(gem_state)
|
|
237
|
+
@selected_package_scope == "all" || gem_state[:package_scope] == @selected_package_scope
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
def available_package_scopes
|
|
241
|
+
@selected_project ? @selected_project.package_scope_options.map { |option| option[:id] } : []
|
|
242
|
+
end
|
|
243
|
+
|
|
188
244
|
def render_shell
|
|
189
245
|
return render_empty_workspace if @projects.empty?
|
|
190
246
|
|
|
@@ -227,7 +283,7 @@ module Gemstar
|
|
|
227
283
|
</div>
|
|
228
284
|
<div class="picker-row">
|
|
229
285
|
<label class="picker picker-project">
|
|
230
|
-
<span class="picker-prefix"
|
|
286
|
+
<span class="picker-prefix" data-text-label="true">Project:</span>
|
|
231
287
|
<select data-project-select>
|
|
232
288
|
#{project_options_html}
|
|
233
289
|
<option value="" disabled>────────</option>
|
|
@@ -322,7 +378,7 @@ module Gemstar
|
|
|
322
378
|
#{render_toolbar}
|
|
323
379
|
<div class="workspace-body">
|
|
324
380
|
#{render_sidebar}
|
|
325
|
-
#{
|
|
381
|
+
#{render_initial_detail}
|
|
326
382
|
</div>
|
|
327
383
|
</main>
|
|
328
384
|
HTML
|
|
@@ -332,7 +388,7 @@ module Gemstar
|
|
|
332
388
|
<<~HTML
|
|
333
389
|
<section class="toolbar">
|
|
334
390
|
<div class="toolbar-meta">
|
|
335
|
-
<strong>#{@gem_states.count}</strong>
|
|
391
|
+
<strong>#{@gem_states.count}</strong> #{h(@selected_project&.package_collection_label&.downcase || "packages")}
|
|
336
392
|
<span>·</span>
|
|
337
393
|
<strong>#{@gem_states.count { |gem| gem[:status] != :unchanged }}</strong> changes from #{h(selected_from_revision_label)} to #{h(selected_to_revision_label)}
|
|
338
394
|
</div>
|
|
@@ -357,17 +413,18 @@ module Gemstar
|
|
|
357
413
|
<aside class="sidebar" data-sidebar-panel tabindex="0">
|
|
358
414
|
<div class="sidebar-header">
|
|
359
415
|
<div class="sidebar-header-row">
|
|
360
|
-
<h2
|
|
416
|
+
<h2>#{h(@selected_project&.package_collection_label || "Packages")}</h2>
|
|
361
417
|
<div class="list-filters" data-list-filters>
|
|
362
418
|
<button type="button" class="list-filter-button#{' is-active' if @selected_filter == "updated"}" data-filter-button="updated">Updated</button>
|
|
363
419
|
<button type="button" class="list-filter-button#{' is-active' if @selected_filter == "all"}" data-filter-button="all">All</button>
|
|
364
420
|
</div>
|
|
365
421
|
</div>
|
|
422
|
+
#{render_package_scope_filters}
|
|
366
423
|
<input
|
|
367
424
|
type="search"
|
|
368
425
|
class="gem-search"
|
|
369
426
|
data-gem-search
|
|
370
|
-
placeholder="
|
|
427
|
+
placeholder="Search"
|
|
371
428
|
autocomplete="off"
|
|
372
429
|
spellcheck="false"
|
|
373
430
|
>
|
|
@@ -380,7 +437,7 @@ module Gemstar
|
|
|
380
437
|
def render_gem_list
|
|
381
438
|
return <<~HTML if @gem_states.empty?
|
|
382
439
|
<section class="empty-panel">
|
|
383
|
-
<p>No
|
|
440
|
+
<p>No #{h(@selected_project&.package_collection_label&.downcase || "packages")} found in the current lockfile.</p>
|
|
384
441
|
</section>
|
|
385
442
|
HTML
|
|
386
443
|
|
|
@@ -388,19 +445,23 @@ module Gemstar
|
|
|
388
445
|
selected = gem[:name] == @selected_gem[:name] ? " is-selected" : ""
|
|
389
446
|
status_class = " status-#{gem[:status]}"
|
|
390
447
|
updated = gem[:status] != :unchanged
|
|
391
|
-
hidden = @selected_filter == "updated" && !updated && gem[:name] != @requested_gem_name ? ' hidden="hidden"' : ""
|
|
448
|
+
hidden = (!gem_visible_in_selected_scope?(gem) || (@selected_filter == "updated" && !updated && gem[:name] != @requested_gem_name)) ? ' hidden="hidden"' : ""
|
|
392
449
|
<<~HTML
|
|
393
450
|
<a
|
|
394
451
|
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,
|
|
452
|
+
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
453
|
data-gem-link="true"
|
|
397
454
|
data-gem-name="#{h(gem[:name])}"
|
|
398
455
|
data-gem-updated="#{updated}"
|
|
399
|
-
data-
|
|
456
|
+
data-package-scope="#{h(gem[:package_scope])}"
|
|
457
|
+
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
458
|
#{hidden}
|
|
401
459
|
>
|
|
402
460
|
<span class="gem-name-row">
|
|
403
|
-
<span class="gem-name"
|
|
461
|
+
<span class="gem-name-lockup">
|
|
462
|
+
<span class="gem-name">#{h(gem[:name])}</span>
|
|
463
|
+
<span class="package-type-tag">#{h(gem[:package_type_label])}</span>
|
|
464
|
+
</span>
|
|
404
465
|
#{updated ? '<span class="gem-updated-dot" aria-label="Updated"></span>' : ""}
|
|
405
466
|
</span>
|
|
406
467
|
<span class="gem-version">#{h(gem[:version_label])}</span>
|
|
@@ -413,7 +474,7 @@ module Gemstar
|
|
|
413
474
|
#{items}
|
|
414
475
|
</nav>
|
|
415
476
|
<section class="empty-panel gem-list-empty" data-gem-list-empty hidden="hidden">
|
|
416
|
-
<p>No updated
|
|
477
|
+
<p>No updated #{h(@selected_project&.package_collection_label&.downcase || "packages")} in this revision range.</p>
|
|
417
478
|
</section>
|
|
418
479
|
HTML
|
|
419
480
|
end
|
|
@@ -421,25 +482,32 @@ module Gemstar
|
|
|
421
482
|
def render_detail
|
|
422
483
|
return empty_detail_html unless @selected_gem
|
|
423
484
|
|
|
485
|
+
metadata = metadata_for(@selected_gem, refresh_if_missing: true)
|
|
424
486
|
cache_key = [
|
|
487
|
+
CACHE_VERSION,
|
|
425
488
|
@selected_project_index,
|
|
426
489
|
@selected_from_revision_id,
|
|
427
490
|
@selected_to_revision_id,
|
|
428
491
|
@selected_filter,
|
|
492
|
+
@selected_package_scope,
|
|
429
493
|
@selected_gem[:name],
|
|
494
|
+
@selected_gem[:package_scope],
|
|
495
|
+
@selected_gem[:package_source_file],
|
|
430
496
|
@selected_gem[:old_version],
|
|
431
497
|
@selected_gem[:new_version],
|
|
498
|
+
@selected_gem[:raw_old_version],
|
|
499
|
+
@selected_gem[:raw_new_version],
|
|
500
|
+
effective_package_version(@selected_gem, metadata || {}),
|
|
432
501
|
@selected_gem[:status]
|
|
433
502
|
]
|
|
434
503
|
detail_cache = self.class.opts[:detail_html_cache]
|
|
435
504
|
return detail_cache[cache_key] if detail_cache.key?(cache_key)
|
|
436
505
|
|
|
437
|
-
metadata = metadata_for(@selected_gem[:name], refresh_if_missing: true)
|
|
438
506
|
groups = grouped_change_sections(@selected_gem)
|
|
439
507
|
detail_pending = detail_pending?(@selected_gem[:name], metadata, groups)
|
|
440
508
|
|
|
441
509
|
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,
|
|
510
|
+
<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
511
|
#{render_detail_hero(metadata)}
|
|
444
512
|
#{render_detail_loading_notice if detail_pending}
|
|
445
513
|
#{render_detail_revision_panel(groups)}
|
|
@@ -450,12 +518,33 @@ module Gemstar
|
|
|
450
518
|
detail_html
|
|
451
519
|
end
|
|
452
520
|
|
|
453
|
-
def
|
|
521
|
+
def render_initial_detail
|
|
522
|
+
return empty_detail_html(loading: true) unless @selected_gem
|
|
523
|
+
|
|
524
|
+
detail_url = detail_query(
|
|
525
|
+
project: @selected_project_index,
|
|
526
|
+
from: @selected_from_revision_id,
|
|
527
|
+
to: @selected_to_revision_id,
|
|
528
|
+
filter: @selected_filter,
|
|
529
|
+
scope: @selected_package_scope,
|
|
530
|
+
package: @selected_gem[:name]
|
|
531
|
+
)
|
|
532
|
+
|
|
533
|
+
<<~HTML
|
|
534
|
+
<section class="detail" data-detail-panel tabindex="0" data-detail-pending="false" data-detail-deferred="true" data-detail-url="#{h(detail_url)}">
|
|
535
|
+
<div class="detail-loading-shell" aria-hidden="true">
|
|
536
|
+
<div class="detail-loading-spinner"></div>
|
|
537
|
+
</div>
|
|
538
|
+
</section>
|
|
539
|
+
HTML
|
|
540
|
+
end
|
|
541
|
+
|
|
542
|
+
def empty_detail_html(loading: false)
|
|
454
543
|
<<~HTML
|
|
455
544
|
<section class="detail" data-detail-panel tabindex="0">
|
|
456
545
|
<div class="empty-panel">
|
|
457
|
-
<h2
|
|
458
|
-
<p
|
|
546
|
+
<h2>#{loading ? "Loading details" : "No gem selected"}</h2>
|
|
547
|
+
<p>#{loading ? "Preparing the selected gem details." : "Choose a gem from the list to inspect its current version and changelog revisions."}</p>
|
|
459
548
|
</div>
|
|
460
549
|
</section>
|
|
461
550
|
HTML
|
|
@@ -465,10 +554,10 @@ module Gemstar
|
|
|
465
554
|
description = metadata&.dig("info")
|
|
466
555
|
bundle_origins = Array(@selected_gem[:bundle_origins])
|
|
467
556
|
requirement_names = selected_gem_requirements
|
|
468
|
-
bundled_version =
|
|
557
|
+
bundled_version = detail_bundled_version(metadata)
|
|
469
558
|
added_on = selected_gem_added_on
|
|
470
559
|
title_url = metadata&.dig("homepage_uri")
|
|
471
|
-
title_url =
|
|
560
|
+
title_url = repo_url_for(@selected_gem, metadata: metadata) if title_url.to_s.empty?
|
|
472
561
|
title_markup = if title_url.to_s.empty?
|
|
473
562
|
h(@selected_gem[:name])
|
|
474
563
|
else
|
|
@@ -484,13 +573,24 @@ module Gemstar
|
|
|
484
573
|
</div>
|
|
485
574
|
#{render_detail_links(metadata)}
|
|
486
575
|
</div>
|
|
487
|
-
<
|
|
576
|
+
<div class="detail-subtitle">#{render_detail_subtitle(description)}</div>
|
|
488
577
|
#{render_dependency_details(bundle_origins, requirement_names, added_on)}
|
|
489
578
|
</div>
|
|
490
579
|
</section>
|
|
491
580
|
HTML
|
|
492
581
|
end
|
|
493
582
|
|
|
583
|
+
def render_detail_subtitle(description)
|
|
584
|
+
text = description.to_s.strip
|
|
585
|
+
return "<p>Metadata will appear here when package information is available.</p>" if text.empty?
|
|
586
|
+
|
|
587
|
+
options = { hard_wrap: false }
|
|
588
|
+
options[:input] = "GFM" if defined?(Kramdown::Parser::GFM)
|
|
589
|
+
with_external_links(Kramdown::Document.new(text, options).to_html)
|
|
590
|
+
rescue Kramdown::Error
|
|
591
|
+
"<p>#{h(text)}</p>"
|
|
592
|
+
end
|
|
593
|
+
|
|
494
594
|
def render_added_on(added_on)
|
|
495
595
|
return "" unless added_on
|
|
496
596
|
|
|
@@ -516,17 +616,22 @@ module Gemstar
|
|
|
516
616
|
next if origin[:type] != :direct && display_path.empty?
|
|
517
617
|
|
|
518
618
|
linked_path = linked_gem_chain(["Gemfile", *display_path])
|
|
519
|
-
origin[:type] == :direct ? gemfile_link("Gemfile") : linked_path
|
|
619
|
+
label = origin[:type] == :direct ? gemfile_link("Gemfile") : linked_path
|
|
620
|
+
origin[:requirement] ? "#{label} (#{h(origin[:requirement])})" : label
|
|
520
621
|
end.uniq
|
|
521
622
|
end
|
|
522
623
|
|
|
523
624
|
def render_detail_links(metadata)
|
|
524
|
-
repo_url =
|
|
625
|
+
repo_url = repo_url_for(@selected_gem, metadata: metadata)
|
|
525
626
|
homepage_url = metadata&.dig("homepage_uri")
|
|
526
|
-
rubygems_url = "https://rubygems.org/gems/#{URI.encode_www_form_component(@selected_gem[:name])}"
|
|
527
627
|
|
|
528
628
|
buttons = []
|
|
529
|
-
|
|
629
|
+
if @selected_gem[:package_scope] == "gems"
|
|
630
|
+
rubygems_url = "https://rubygems.org/gems/#{URI.encode_www_form_component(@selected_gem[:name])}"
|
|
631
|
+
buttons << icon_button("RubyGems", rubygems_url, icon_type: :rubygems)
|
|
632
|
+
elsif homepage_url && !homepage_url.empty? && (!repo_url || homepage_url != repo_url)
|
|
633
|
+
buttons << icon_button("Source", homepage_url, icon_type: :home)
|
|
634
|
+
end
|
|
530
635
|
buttons << icon_button("GitHub", repo_url, icon_type: :github) if repo_url && !repo_url.empty?
|
|
531
636
|
buttons << icon_button("Homepage", homepage_url, icon_type: :home) if homepage_url && !homepage_url.empty?
|
|
532
637
|
|
|
@@ -540,14 +645,18 @@ module Gemstar
|
|
|
540
645
|
def render_dependency_details(bundle_origins, requirement_names, added_on)
|
|
541
646
|
required_by = dependency_origin_items(bundle_origins)
|
|
542
647
|
requires = Array(requirement_names).compact.uniq.map { |name| internal_gem_link(name) }
|
|
648
|
+
platforms = selected_gem_platform_items
|
|
649
|
+
source_items = selected_gem_source_items
|
|
543
650
|
added_markup = render_added_on(added_on)
|
|
544
|
-
return "" if required_by.empty? && requires.empty? && added_markup.empty?
|
|
651
|
+
return "" if required_by.empty? && requires.empty? && platforms.empty? && source_items.empty? && added_markup.empty?
|
|
545
652
|
|
|
546
653
|
<<~HTML
|
|
547
654
|
<details class="detail-disclosure">
|
|
548
655
|
<summary><span class="detail-disclosure-caret" aria-hidden="true"></span><h3>Details</h3></summary>
|
|
549
656
|
<div class="detail-disclosure-panel">
|
|
550
657
|
#{added_markup}
|
|
658
|
+
#{render_dependency_popover_section("Platforms", platforms)}
|
|
659
|
+
#{render_dependency_popover_section("Source", source_items)}
|
|
551
660
|
#{render_dependency_popover_section("Required by", required_by)}
|
|
552
661
|
#{render_dependency_popover_section("Requires", requires)}
|
|
553
662
|
</div>
|
|
@@ -570,6 +679,8 @@ module Gemstar
|
|
|
570
679
|
end
|
|
571
680
|
|
|
572
681
|
def selected_gem_requirements
|
|
682
|
+
return [] unless @selected_gem[:package_scope] == "gems"
|
|
683
|
+
|
|
573
684
|
lockfile = if @selected_gem[:new_version]
|
|
574
685
|
@selected_project&.lockfile_for_revision(@selected_to_revision_id)
|
|
575
686
|
else
|
|
@@ -581,7 +692,70 @@ module Gemstar
|
|
|
581
692
|
|
|
582
693
|
def selected_gem_added_on
|
|
583
694
|
revision_id = @selected_gem[:new_version] ? @selected_to_revision_id : @selected_from_revision_id
|
|
584
|
-
@selected_project&.
|
|
695
|
+
@selected_project&.package_added_on(
|
|
696
|
+
@selected_gem[:name],
|
|
697
|
+
package_scope: @selected_gem[:package_scope],
|
|
698
|
+
source_file: @selected_gem[:package_source_file],
|
|
699
|
+
revision_id: revision_id
|
|
700
|
+
)
|
|
701
|
+
end
|
|
702
|
+
|
|
703
|
+
def selected_gem_platform_items
|
|
704
|
+
platform = @selected_gem[:platform]
|
|
705
|
+
return [] if platform.to_s.empty?
|
|
706
|
+
|
|
707
|
+
[h(platform)]
|
|
708
|
+
end
|
|
709
|
+
|
|
710
|
+
def selected_gem_source_items
|
|
711
|
+
source = @selected_gem[:source] || {}
|
|
712
|
+
source_type = source[:type]
|
|
713
|
+
|
|
714
|
+
case source_type
|
|
715
|
+
when :path
|
|
716
|
+
location = source[:path] || source[:remote]
|
|
717
|
+
return [] if location.to_s.empty?
|
|
718
|
+
|
|
719
|
+
["Path (#{h(location)})"]
|
|
720
|
+
when :git
|
|
721
|
+
remote = source[:remote]
|
|
722
|
+
pieces = ["Git"]
|
|
723
|
+
pieces << h(remote) unless remote.to_s.empty?
|
|
724
|
+
pieces << "@#{h(source[:branch])}" if source[:branch]
|
|
725
|
+
pieces << "##{h(source[:tag])}" if source[:tag]
|
|
726
|
+
pieces << h(source[:revision].to_s[0, 8]) if source[:revision]
|
|
727
|
+
[pieces.join(" ")]
|
|
728
|
+
when :rubygems
|
|
729
|
+
remote = source[:remote]
|
|
730
|
+
[remote.to_s.empty? ? "RubyGems" : "RubyGems (#{h(remote)})"]
|
|
731
|
+
when :importmap
|
|
732
|
+
remote = source[:remote]
|
|
733
|
+
package_name = source[:package_name]
|
|
734
|
+
package_version = source[:package_version]
|
|
735
|
+
label =
|
|
736
|
+
if package_name && package_version
|
|
737
|
+
"Importmap (#{h(package_name)} @ #{h(package_version)})"
|
|
738
|
+
elsif package_name
|
|
739
|
+
"Importmap (#{h(package_name)})"
|
|
740
|
+
elsif remote.to_s.empty?
|
|
741
|
+
"Importmap"
|
|
742
|
+
else
|
|
743
|
+
"Importmap (#{h(remote)})"
|
|
744
|
+
end
|
|
745
|
+
[label]
|
|
746
|
+
when :npm
|
|
747
|
+
remote = source[:remote]
|
|
748
|
+
registry_url = source[:registry_url]
|
|
749
|
+
if remote.to_s.empty? && registry_url.to_s.empty?
|
|
750
|
+
["npm"]
|
|
751
|
+
elsif remote.to_s.empty?
|
|
752
|
+
["npm (#{h(registry_url)})"]
|
|
753
|
+
else
|
|
754
|
+
["npm (#{h(remote)})"]
|
|
755
|
+
end
|
|
756
|
+
else
|
|
757
|
+
[]
|
|
758
|
+
end
|
|
585
759
|
end
|
|
586
760
|
|
|
587
761
|
def linked_gem_chain(names)
|
|
@@ -607,7 +781,8 @@ module Gemstar
|
|
|
607
781
|
from: @selected_from_revision_id,
|
|
608
782
|
to: @selected_to_revision_id,
|
|
609
783
|
filter: @selected_filter,
|
|
610
|
-
|
|
784
|
+
scope: @selected_package_scope,
|
|
785
|
+
package: name
|
|
611
786
|
)
|
|
612
787
|
|
|
613
788
|
%(<a href="#{h(href)}" data-gem-link-inline="true">#{h(name)}</a>)
|
|
@@ -626,7 +801,7 @@ module Gemstar
|
|
|
626
801
|
def render_detail_loading_notice
|
|
627
802
|
<<~HTML
|
|
628
803
|
<section class="empty-panel">
|
|
629
|
-
<p>Loading
|
|
804
|
+
<p>Loading package metadata and changelog in the background...</p>
|
|
630
805
|
</section>
|
|
631
806
|
HTML
|
|
632
807
|
end
|
|
@@ -694,17 +869,31 @@ module Gemstar
|
|
|
694
869
|
end
|
|
695
870
|
|
|
696
871
|
def change_sections(gem_state)
|
|
697
|
-
|
|
872
|
+
metadata_hash = metadata_for(gem_state) || {}
|
|
873
|
+
current_version = effective_package_version(gem_state, metadata_hash)
|
|
874
|
+
cache_key = [
|
|
875
|
+
CACHE_VERSION,
|
|
876
|
+
gem_state[:name],
|
|
877
|
+
gem_state[:package_scope],
|
|
878
|
+
gem_state[:package_source_file],
|
|
879
|
+
gem_state[:old_version],
|
|
880
|
+
gem_state[:new_version],
|
|
881
|
+
gem_state[:raw_old_version],
|
|
882
|
+
gem_state[:raw_new_version],
|
|
883
|
+
current_version,
|
|
884
|
+
gem_state[:status]
|
|
885
|
+
]
|
|
698
886
|
change_sections_cache = self.class.opts[:change_sections_cache]
|
|
699
887
|
return change_sections_cache[cache_key] if change_sections_cache.key?(cache_key)
|
|
700
888
|
|
|
701
|
-
return [] if gem_state[:new_version].nil? &&
|
|
702
|
-
|
|
703
|
-
|
|
889
|
+
return [] if gem_state[:new_version].nil? &&
|
|
890
|
+
gem_state[:old_version].nil? &&
|
|
891
|
+
(current_version.nil? || current_version.to_s.empty?)
|
|
892
|
+
metadata = metadata_adapter_for(gem_state)
|
|
893
|
+
return change_sections_cache[cache_key] = [] unless metadata
|
|
704
894
|
sections = resolved_sections(metadata, gem_state)
|
|
705
895
|
return change_sections_cache[cache_key] = [] if sections.nil? || sections.empty?
|
|
706
896
|
|
|
707
|
-
current_version = gem_state[:new_version] || gem_state[:old_version]
|
|
708
897
|
previous_version = gem_state[:old_version]
|
|
709
898
|
|
|
710
899
|
rendered_sections = sections.keys.filter_map do |version|
|
|
@@ -731,18 +920,35 @@ module Gemstar
|
|
|
731
920
|
cached_sections = changelog.sections(cache_only: true) || {}
|
|
732
921
|
return cached_sections unless selected_gem_requires_refresh?(gem_state, cached_sections)
|
|
733
922
|
|
|
734
|
-
@metadata_cache.delete(gem_state[:name])
|
|
923
|
+
@metadata_cache.delete([gem_state[:package_scope], gem_state[:name]])
|
|
735
924
|
metadata.meta(cache_only: false, force_refresh: true)
|
|
736
925
|
metadata.repo_uri(cache_only: false, force_refresh: true)
|
|
737
|
-
|
|
926
|
+
refreshed_sections = metadata.changelog_sections(
|
|
927
|
+
versions: relevant_package_versions(gem_state, metadata),
|
|
928
|
+
cache_only: false,
|
|
929
|
+
force_refresh: true
|
|
930
|
+
)
|
|
931
|
+
cached_sections.merge(refreshed_sections || {})
|
|
932
|
+
end
|
|
933
|
+
|
|
934
|
+
def relevant_package_versions(gem_state, metadata)
|
|
935
|
+
metadata_hash = metadata_for(gem_state) || {}
|
|
936
|
+
[
|
|
937
|
+
gem_state[:old_version],
|
|
938
|
+
gem_state[:new_version],
|
|
939
|
+
gem_state[:raw_old_version],
|
|
940
|
+
gem_state[:raw_new_version],
|
|
941
|
+
gem_state.dig(:source, :package_version),
|
|
942
|
+
metadata_hash["version"]
|
|
943
|
+
].compact
|
|
738
944
|
end
|
|
739
945
|
|
|
740
946
|
def selected_gem_requires_refresh?(gem_state, cached_sections)
|
|
741
947
|
return false unless @selected_gem && gem_state[:name] == @selected_gem[:name]
|
|
742
948
|
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
949
|
+
metadata = metadata_for(gem_state) || {}
|
|
950
|
+
bundled_version = effective_package_version(gem_state, metadata)
|
|
951
|
+
return false if bundled_version.nil? || bundled_version.to_s.empty?
|
|
746
952
|
has_upstream_release_source =
|
|
747
953
|
!metadata["changelog_uri"].to_s.empty? ||
|
|
748
954
|
!metadata["source_code_uri"].to_s.empty? ||
|
|
@@ -763,6 +969,7 @@ module Gemstar
|
|
|
763
969
|
def section_kind(version, previous_version, current_version, status)
|
|
764
970
|
return :future if compare_versions(version, current_version) == 1
|
|
765
971
|
return :current if status == :added && compare_versions(version, current_version) <= 0
|
|
972
|
+
return :current if previous_version.nil? && current_version && compare_versions(version, current_version) == 0
|
|
766
973
|
|
|
767
974
|
lower_bound = previous_version || current_version
|
|
768
975
|
if compare_versions(version, lower_bound) == 1 && compare_versions(version, current_version) <= 0
|
|
@@ -798,7 +1005,7 @@ module Gemstar
|
|
|
798
1005
|
return { title: heading_version.to_s, html: "<p>No changelog text available.</p>" } if text.strip.empty?
|
|
799
1006
|
|
|
800
1007
|
if heading_version
|
|
801
|
-
text = text
|
|
1008
|
+
text = strip_leading_version_heading(text, heading_version)
|
|
802
1009
|
end
|
|
803
1010
|
|
|
804
1011
|
options = { hard_wrap: false }
|
|
@@ -825,29 +1032,135 @@ module Gemstar
|
|
|
825
1032
|
{ title: title, html: fragment.to_html }
|
|
826
1033
|
end
|
|
827
1034
|
|
|
1035
|
+
def strip_leading_version_heading(text, heading_version)
|
|
1036
|
+
stripped = text.sub(/\A\s*#+\s*v?#{Regexp.escape(heading_version)}\s*\n+/i, "")
|
|
1037
|
+
return strip_leading_hash_separator(stripped) unless stripped == text
|
|
1038
|
+
|
|
1039
|
+
lines = text.lines
|
|
1040
|
+
return text if lines.empty?
|
|
1041
|
+
|
|
1042
|
+
first_line = lines.first.to_s
|
|
1043
|
+
heading_like =
|
|
1044
|
+
first_line.match?(/\A\s*v?#{Regexp.escape(heading_version)}\b/i) ||
|
|
1045
|
+
first_line.match?(/\A\s*[\[(]?v?#{Regexp.escape(heading_version)}\b/i)
|
|
1046
|
+
|
|
1047
|
+
return text unless heading_like
|
|
1048
|
+
|
|
1049
|
+
remaining = lines.drop(1)
|
|
1050
|
+
remaining.shift while remaining.first&.strip&.empty?
|
|
1051
|
+
strip_leading_hash_separator(remaining.join)
|
|
1052
|
+
end
|
|
1053
|
+
|
|
1054
|
+
def strip_leading_hash_separator(text)
|
|
1055
|
+
text.sub(/\A\s*#{Regexp.escape("#")}{4,}\s*\n+/, "")
|
|
1056
|
+
end
|
|
1057
|
+
|
|
828
1058
|
def compare_versions(left, right)
|
|
829
1059
|
Gem::Version.new(left.to_s.gsub(/-[\w\-]+$/, "")) <=> Gem::Version.new(right.to_s.gsub(/-[\w\-]+$/, ""))
|
|
830
1060
|
rescue ArgumentError
|
|
831
1061
|
left.to_s <=> right.to_s
|
|
832
1062
|
end
|
|
833
1063
|
|
|
834
|
-
def metadata_for(
|
|
835
|
-
|
|
1064
|
+
def metadata_for(package_state_or_name, refresh_if_missing: false)
|
|
1065
|
+
package_state = package_state_or_name.is_a?(Hash) ? package_state_or_name : { name: package_state_or_name, package_scope: "gems" }
|
|
1066
|
+
gem_name = package_state[:name]
|
|
1067
|
+
cache_key = [package_state[:package_scope], gem_name]
|
|
1068
|
+
cached = @metadata_cache[cache_key]
|
|
836
1069
|
return nil if cached.equal?(MISSING_METADATA)
|
|
837
1070
|
return cached if cached
|
|
838
1071
|
|
|
1072
|
+
if package_state[:package_scope] != "gems"
|
|
1073
|
+
metadata = local_package_metadata(package_state)
|
|
1074
|
+
adapter = metadata_adapter_for(package_state)
|
|
1075
|
+
remote_metadata = adapter&.meta(cache_only: true)
|
|
1076
|
+
remote_metadata = adapter&.meta(cache_only: false, force_refresh: true) if remote_metadata.nil? && refresh_if_missing
|
|
1077
|
+
metadata = metadata.compact.merge(remote_metadata || {})
|
|
1078
|
+
@metadata_cache[cache_key] = metadata || MISSING_METADATA
|
|
1079
|
+
return metadata
|
|
1080
|
+
end
|
|
1081
|
+
|
|
839
1082
|
metadata = Gemstar::RubyGemsMetadata.new(gem_name).meta(cache_only: true)
|
|
840
1083
|
if metadata.nil? && refresh_if_missing
|
|
841
1084
|
metadata = Gemstar::RubyGemsMetadata.new(gem_name).meta(cache_only: false, force_refresh: true)
|
|
842
1085
|
end
|
|
843
1086
|
|
|
844
|
-
@metadata_cache[
|
|
1087
|
+
@metadata_cache[cache_key] = metadata || MISSING_METADATA
|
|
845
1088
|
metadata
|
|
846
1089
|
rescue StandardError
|
|
847
|
-
@metadata_cache[
|
|
1090
|
+
@metadata_cache[cache_key] = MISSING_METADATA
|
|
848
1091
|
nil
|
|
849
1092
|
end
|
|
850
1093
|
|
|
1094
|
+
def local_package_metadata(package_state)
|
|
1095
|
+
source = package_state[:source] || {}
|
|
1096
|
+
remote = source[:remote].to_s
|
|
1097
|
+
repo_url = source[:repo_url].to_s
|
|
1098
|
+
package_name = source[:package_name].to_s
|
|
1099
|
+
package_version = source[:package_version].to_s
|
|
1100
|
+
registry_url = source[:registry_url].to_s
|
|
1101
|
+
provider_gem = source[:provider_gem].to_s
|
|
1102
|
+
package_name = package_state[:name].to_s if package_name.empty? && source[:type] == :npm
|
|
1103
|
+
package_version = (package_state[:new_version] || package_state[:old_version]).to_s if package_version.empty? && source[:type] == :npm
|
|
1104
|
+
registry_url = "https://www.npmjs.com/package/#{package_name}" if registry_url.empty? && !package_name.empty?
|
|
1105
|
+
info = if package_name.empty?
|
|
1106
|
+
"JavaScript package pinned in config/importmap.rb"
|
|
1107
|
+
elsif !provider_gem.empty? && !package_version.empty?
|
|
1108
|
+
"JavaScript package `#{package_name}` provided by the `#{provider_gem}` gem at version `#{package_version}`"
|
|
1109
|
+
elsif !provider_gem.empty?
|
|
1110
|
+
"JavaScript package `#{package_name}` provided by the `#{provider_gem}` gem"
|
|
1111
|
+
elsif source[:type] == :npm && !package_version.empty?
|
|
1112
|
+
"JavaScript package `#{package_name}` locked to `#{package_version}` in package-lock.json"
|
|
1113
|
+
elsif package_version.empty?
|
|
1114
|
+
"JavaScript package `#{package_name}` pinned in config/importmap.rb"
|
|
1115
|
+
else
|
|
1116
|
+
"JavaScript package `#{package_name}` pinned to `#{package_version}` in config/importmap.rb"
|
|
1117
|
+
end
|
|
1118
|
+
|
|
1119
|
+
{
|
|
1120
|
+
"info" => info,
|
|
1121
|
+
"project_uri" => registry_url.empty? ? nil : registry_url,
|
|
1122
|
+
"homepage_uri" => absolute_url?(remote) ? remote : nil,
|
|
1123
|
+
"source_code_uri" => repo_url.empty? ? nil : repo_url
|
|
1124
|
+
}
|
|
1125
|
+
end
|
|
1126
|
+
|
|
1127
|
+
def repo_url_for(package_state, metadata: nil)
|
|
1128
|
+
return nil unless package_state
|
|
1129
|
+
if package_state[:package_scope] != "gems"
|
|
1130
|
+
url = package_state.dig(:source, :repo_url) || metadata&.dig("source_code_uri")
|
|
1131
|
+
return normalize_repo_url(url)
|
|
1132
|
+
end
|
|
1133
|
+
|
|
1134
|
+
normalize_repo_url(Gemstar::RubyGemsMetadata.new(package_state[:name]).repo_uri(cache_only: true))
|
|
1135
|
+
end
|
|
1136
|
+
|
|
1137
|
+
def normalize_repo_url(url)
|
|
1138
|
+
value = url.to_s
|
|
1139
|
+
return nil if value.empty?
|
|
1140
|
+
|
|
1141
|
+
value = value.sub(/\Agit\+/, "")
|
|
1142
|
+
value = value.sub(/\Agit:\/\//, "https://")
|
|
1143
|
+
value = value.gsub(/\.git\z/, "")
|
|
1144
|
+
value
|
|
1145
|
+
end
|
|
1146
|
+
|
|
1147
|
+
def metadata_adapter_for(package_state)
|
|
1148
|
+
return Gemstar::RubyGemsMetadata.new(package_state[:name]) if package_state[:package_scope] == "gems"
|
|
1149
|
+
return nil unless package_state[:package_scope] == "js"
|
|
1150
|
+
|
|
1151
|
+
provider_gem = package_state.dig(:source, :provider_gem)
|
|
1152
|
+
return Gemstar::RubyGemsMetadata.new(provider_gem) unless provider_gem.to_s.empty?
|
|
1153
|
+
|
|
1154
|
+
package_name = package_state.dig(:source, :package_name) || package_state[:name]
|
|
1155
|
+
return nil if package_name.to_s.empty?
|
|
1156
|
+
|
|
1157
|
+
Gemstar::NpmMetadata.new(package_name)
|
|
1158
|
+
end
|
|
1159
|
+
|
|
1160
|
+
def absolute_url?(value)
|
|
1161
|
+
value.to_s.match?(%r{\Ahttps?://}i)
|
|
1162
|
+
end
|
|
1163
|
+
|
|
851
1164
|
def detail_pending?(gem_name, metadata, groups)
|
|
852
1165
|
false
|
|
853
1166
|
end
|
|
@@ -902,7 +1215,7 @@ module Gemstar
|
|
|
902
1215
|
end
|
|
903
1216
|
|
|
904
1217
|
def revision_card_links(section)
|
|
905
|
-
repo_url =
|
|
1218
|
+
repo_url = repo_url_for(@selected_gem)
|
|
906
1219
|
return [] if repo_url.to_s.empty?
|
|
907
1220
|
|
|
908
1221
|
links = []
|
|
@@ -915,25 +1228,33 @@ module Gemstar
|
|
|
915
1228
|
end
|
|
916
1229
|
|
|
917
1230
|
def fallback_current_section(gem_state, previous_sections, latest_sections)
|
|
918
|
-
|
|
1231
|
+
metadata = metadata_for(gem_state) || {}
|
|
1232
|
+
version = effective_package_version(gem_state, metadata)
|
|
919
1233
|
return nil if version.nil?
|
|
920
1234
|
return nil if previous_sections.any? { |section| section[:version] == version }
|
|
921
1235
|
return nil if latest_sections.any? { |section| section[:version] == version }
|
|
922
1236
|
|
|
923
|
-
|
|
924
|
-
repo_url = Gemstar::RubyGemsMetadata.new(gem_state[:name]).repo_uri(cache_only: true)
|
|
1237
|
+
repo_url = repo_url_for(gem_state, metadata: metadata)
|
|
925
1238
|
fallback_url =
|
|
926
1239
|
if !repo_url.to_s.empty?
|
|
927
1240
|
repo_url
|
|
928
1241
|
elsif metadata["project_uri"]
|
|
929
1242
|
metadata["project_uri"]
|
|
1243
|
+
elsif metadata["source_code_uri"]
|
|
1244
|
+
metadata["source_code_uri"]
|
|
1245
|
+
elsif metadata["homepage_uri"]
|
|
1246
|
+
metadata["homepage_uri"]
|
|
930
1247
|
else
|
|
931
1248
|
metadata["documentation_uri"]
|
|
932
1249
|
end
|
|
933
1250
|
fallback_label = if repo_url.to_s.empty?
|
|
934
|
-
|
|
1251
|
+
if gem_state[:package_scope] == "gems"
|
|
1252
|
+
metadata["project_uri"] ? "the RubyGems page" : "the gem documentation"
|
|
1253
|
+
else
|
|
1254
|
+
fallback_url == metadata["documentation_uri"] ? "the package documentation" : "the package source"
|
|
1255
|
+
end
|
|
935
1256
|
else
|
|
936
|
-
"the gem repository"
|
|
1257
|
+
gem_state[:package_scope] == "gems" ? "the gem repository" : "the package repository"
|
|
937
1258
|
end
|
|
938
1259
|
fallback_link = if fallback_url.to_s.empty?
|
|
939
1260
|
fallback_label
|
|
@@ -957,6 +1278,16 @@ module Gemstar
|
|
|
957
1278
|
nil
|
|
958
1279
|
end
|
|
959
1280
|
|
|
1281
|
+
def detail_bundled_version(metadata)
|
|
1282
|
+
effective_package_version(@selected_gem, metadata || {})
|
|
1283
|
+
end
|
|
1284
|
+
|
|
1285
|
+
def effective_package_version(package_state, metadata)
|
|
1286
|
+
package_state[:new_version] ||
|
|
1287
|
+
package_state[:old_version] ||
|
|
1288
|
+
(package_state[:package_scope] == "js" ? metadata["version"] : nil)
|
|
1289
|
+
end
|
|
1290
|
+
|
|
960
1291
|
def github_compare_url(repo_url, previous_version, current_version)
|
|
961
1292
|
return nil unless repo_url.include?("github.com")
|
|
962
1293
|
return nil if previous_version.nil? || current_version.nil?
|
|
@@ -986,17 +1317,18 @@ module Gemstar
|
|
|
986
1317
|
html
|
|
987
1318
|
end
|
|
988
1319
|
|
|
989
|
-
def detail_query(project:, from:, to:, filter:,
|
|
990
|
-
"/detail?#{URI.encode_www_form(project: project, from: from, to: to, filter: filter,
|
|
1320
|
+
def detail_query(project:, from:, to:, filter:, scope:, package:)
|
|
1321
|
+
"/detail?#{URI.encode_www_form(project: project, from: from, to: to, filter: filter, scope: scope, package: package)}"
|
|
991
1322
|
end
|
|
992
1323
|
|
|
993
|
-
def project_query(project:, from:, to:, filter:,
|
|
1324
|
+
def project_query(project:, from:, to:, filter:, scope:, package:)
|
|
994
1325
|
params = {
|
|
995
1326
|
project: project,
|
|
996
1327
|
from: from,
|
|
997
1328
|
to: to,
|
|
998
1329
|
filter: filter,
|
|
999
|
-
|
|
1330
|
+
scope: scope,
|
|
1331
|
+
package: package
|
|
1000
1332
|
}.compact
|
|
1001
1333
|
|
|
1002
1334
|
"/?#{URI.encode_www_form(params)}"
|
|
@@ -1006,21 +1338,18 @@ module Gemstar
|
|
|
1006
1338
|
render_template(
|
|
1007
1339
|
"page.html.erb",
|
|
1008
1340
|
title: h(title),
|
|
1009
|
-
favicon_data_uri: favicon_data_uri,
|
|
1010
1341
|
styles_css: template_source("app.css"),
|
|
1011
1342
|
body_html: yield
|
|
1012
1343
|
)
|
|
1013
1344
|
end
|
|
1014
1345
|
|
|
1015
|
-
def
|
|
1016
|
-
|
|
1346
|
+
def favicon_svg
|
|
1347
|
+
<<~SVG
|
|
1017
1348
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
|
1018
1349
|
<rect width="64" height="64" rx="14" fill="#b44d25"/>
|
|
1019
1350
|
<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>
|
|
1020
1351
|
</svg>
|
|
1021
1352
|
SVG
|
|
1022
|
-
|
|
1023
|
-
"data:image/svg+xml,#{URI.encode_www_form_component(svg)}"
|
|
1024
1353
|
end
|
|
1025
1354
|
|
|
1026
1355
|
def render_behavior_script
|
|
@@ -1028,6 +1357,7 @@ module Gemstar
|
|
|
1028
1357
|
"app.js.erb",
|
|
1029
1358
|
empty_detail_html_json: empty_detail_html.dump,
|
|
1030
1359
|
selected_filter_json: @selected_filter.dump,
|
|
1360
|
+
selected_package_scope_json: @selected_package_scope.dump,
|
|
1031
1361
|
selected_project_index: @selected_project_index || 0
|
|
1032
1362
|
)
|
|
1033
1363
|
|
|
@@ -1053,6 +1383,24 @@ module Gemstar
|
|
|
1053
1383
|
def h(value)
|
|
1054
1384
|
CGI.escapeHTML(value.to_s)
|
|
1055
1385
|
end
|
|
1386
|
+
|
|
1387
|
+
def render_package_scope_filters
|
|
1388
|
+
options = @selected_project&.package_scope_options || []
|
|
1389
|
+
return "" if options.size <= 1
|
|
1390
|
+
|
|
1391
|
+
buttons = []
|
|
1392
|
+
buttons << %(<button type="button" class="list-filter-button#{' is-active' if @selected_package_scope == "all"}" data-ecosystem-button="all">All</button>)
|
|
1393
|
+
|
|
1394
|
+
buttons.concat(options.map do |option|
|
|
1395
|
+
%(<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>)
|
|
1396
|
+
end)
|
|
1397
|
+
|
|
1398
|
+
<<~HTML
|
|
1399
|
+
<div class="list-filters list-filters-secondary" data-ecosystem-filters>
|
|
1400
|
+
#{buttons.join}
|
|
1401
|
+
</div>
|
|
1402
|
+
HTML
|
|
1403
|
+
end
|
|
1056
1404
|
end
|
|
1057
1405
|
end
|
|
1058
1406
|
end
|