gemstar 1.1.1 → 1.3
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 +27 -0
- data/README.md +4 -2
- data/lib/gemstar/cache_warmer.rb +51 -4
- data/lib/gemstar/cargo_lock_file.rb +138 -0
- data/lib/gemstar/change_log.rb +287 -58
- data/lib/gemstar/cli.rb +8 -2
- data/lib/gemstar/commands/diff.rb +13 -2
- data/lib/gemstar/commands/server.rb +99 -14
- data/lib/gemstar/crates_io_metadata.rb +140 -0
- data/lib/gemstar/data/ruby_gems_metadata.json +10 -0
- data/lib/gemstar/npm_metadata.rb +2 -2
- data/lib/gemstar/outputs/html.rb +2 -0
- data/lib/gemstar/project.rb +208 -15
- data/lib/gemstar/pypi_metadata.rb +182 -0
- data/lib/gemstar/ruby_gems_metadata.rb +33 -5
- data/lib/gemstar/uv_lock_file.rb +114 -0
- data/lib/gemstar/version.rb +1 -1
- data/lib/gemstar/web/app.rb +504 -79
- data/lib/gemstar/web/templates/app.css +137 -4
- data/lib/gemstar/web/templates/app.js.erb +209 -23
- data/lib/gemstar.rb +4 -0
- metadata +8 -4
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
--line: #ddcfbf;
|
|
9
9
|
--accent: #b44d25;
|
|
10
10
|
--accent-soft: rgba(180, 77, 37, 0.14);
|
|
11
|
+
--focus: #2563c9;
|
|
11
12
|
--green: #2f8f5b;
|
|
12
13
|
--green-soft: rgba(47, 143, 91, 0.12);
|
|
13
14
|
--red: #a9473c;
|
|
@@ -182,6 +183,63 @@
|
|
|
182
183
|
opacity: 0.55;
|
|
183
184
|
cursor: not-allowed;
|
|
184
185
|
}
|
|
186
|
+
.command-dialog-backdrop {
|
|
187
|
+
position: fixed;
|
|
188
|
+
inset: 0;
|
|
189
|
+
z-index: 20;
|
|
190
|
+
display: grid;
|
|
191
|
+
place-items: center;
|
|
192
|
+
padding: 1rem;
|
|
193
|
+
background: rgba(31, 27, 23, 0.42);
|
|
194
|
+
}
|
|
195
|
+
.command-dialog {
|
|
196
|
+
width: min(48rem, 100%);
|
|
197
|
+
max-height: min(42rem, calc(100vh - 2rem));
|
|
198
|
+
display: grid;
|
|
199
|
+
grid-template-rows: auto minmax(10rem, 1fr) auto;
|
|
200
|
+
overflow: hidden;
|
|
201
|
+
border: 1px solid var(--line);
|
|
202
|
+
border-radius: 0.45rem;
|
|
203
|
+
background: #fff;
|
|
204
|
+
box-shadow: 0 1.25rem 3rem rgba(31, 27, 23, 0.24);
|
|
205
|
+
}
|
|
206
|
+
.command-dialog-header {
|
|
207
|
+
display: grid;
|
|
208
|
+
gap: 0.3rem;
|
|
209
|
+
padding: 0.85rem 1rem 0.7rem;
|
|
210
|
+
border-bottom: 1px solid #ece8df;
|
|
211
|
+
}
|
|
212
|
+
.command-dialog-header h2 {
|
|
213
|
+
margin: 0;
|
|
214
|
+
font-size: 1rem;
|
|
215
|
+
line-height: 1.15;
|
|
216
|
+
}
|
|
217
|
+
.command-dialog-command {
|
|
218
|
+
margin: 0;
|
|
219
|
+
color: var(--muted);
|
|
220
|
+
font-family: "SFMono-Regular", "Cascadia Code", monospace;
|
|
221
|
+
font-size: 0.78rem;
|
|
222
|
+
overflow-wrap: anywhere;
|
|
223
|
+
}
|
|
224
|
+
.command-dialog-output {
|
|
225
|
+
min-height: 0;
|
|
226
|
+
margin: 0;
|
|
227
|
+
padding: 0.85rem 1rem;
|
|
228
|
+
overflow: auto;
|
|
229
|
+
white-space: pre-wrap;
|
|
230
|
+
background: #181614;
|
|
231
|
+
color: #f8efe4;
|
|
232
|
+
font-size: 0.76rem;
|
|
233
|
+
line-height: 1.45;
|
|
234
|
+
}
|
|
235
|
+
.command-dialog-actions {
|
|
236
|
+
display: flex;
|
|
237
|
+
justify-content: flex-end;
|
|
238
|
+
gap: 0.35rem;
|
|
239
|
+
padding: 0.65rem 0.75rem;
|
|
240
|
+
border-top: 1px solid #ece8df;
|
|
241
|
+
background: #fff;
|
|
242
|
+
}
|
|
185
243
|
.toolbar-meta {
|
|
186
244
|
color: var(--muted);
|
|
187
245
|
display: flex;
|
|
@@ -358,6 +416,11 @@
|
|
|
358
416
|
min-height: 0;
|
|
359
417
|
overflow: auto;
|
|
360
418
|
}
|
|
419
|
+
.detail:focus {
|
|
420
|
+
outline: none;
|
|
421
|
+
border-color: var(--focus);
|
|
422
|
+
box-shadow: 0 0 0 1px var(--focus);
|
|
423
|
+
}
|
|
361
424
|
.detail-hero,
|
|
362
425
|
.panel-heading {
|
|
363
426
|
display: flex;
|
|
@@ -366,6 +429,16 @@
|
|
|
366
429
|
align-items: start;
|
|
367
430
|
flex-wrap: wrap;
|
|
368
431
|
}
|
|
432
|
+
.detail-hero {
|
|
433
|
+
position: sticky;
|
|
434
|
+
top: -0.62rem;
|
|
435
|
+
z-index: 2;
|
|
436
|
+
margin: -0.62rem -0.8rem 0 -0.5rem;
|
|
437
|
+
padding: 0.62rem 0.8rem 0.5rem 0.5rem;
|
|
438
|
+
border-bottom: 1px solid #f0ede6;
|
|
439
|
+
background: rgba(255, 255, 255, 0.96);
|
|
440
|
+
backdrop-filter: blur(0.45rem);
|
|
441
|
+
}
|
|
369
442
|
.detail-hero-copy {
|
|
370
443
|
display: grid;
|
|
371
444
|
gap: 0.2rem;
|
|
@@ -384,6 +457,16 @@
|
|
|
384
457
|
min-width: 0;
|
|
385
458
|
flex: 1 1 auto;
|
|
386
459
|
}
|
|
460
|
+
.detail-heading {
|
|
461
|
+
display: flex;
|
|
462
|
+
align-items: center;
|
|
463
|
+
flex-wrap: wrap;
|
|
464
|
+
gap: 0.5rem;
|
|
465
|
+
min-width: 0;
|
|
466
|
+
}
|
|
467
|
+
.detail-hero-copy .detail-heading h2 {
|
|
468
|
+
flex: 0 1 auto;
|
|
469
|
+
}
|
|
387
470
|
.detail-hero-copy h2 {
|
|
388
471
|
margin: 0;
|
|
389
472
|
font-size: 2.35rem;
|
|
@@ -396,6 +479,32 @@
|
|
|
396
479
|
font-weight: 600;
|
|
397
480
|
color: var(--muted);
|
|
398
481
|
}
|
|
482
|
+
.detail-package-type {
|
|
483
|
+
display: inline-flex;
|
|
484
|
+
align-items: center;
|
|
485
|
+
gap: 0.28rem;
|
|
486
|
+
flex: 0 0 auto;
|
|
487
|
+
border: 1px solid var(--line);
|
|
488
|
+
border-radius: 999px;
|
|
489
|
+
background: #faf8f3;
|
|
490
|
+
color: var(--muted);
|
|
491
|
+
padding: 0.16rem 0.42rem 0.16rem 0.3rem;
|
|
492
|
+
font-size: 0.68rem;
|
|
493
|
+
font-weight: 700;
|
|
494
|
+
line-height: 1;
|
|
495
|
+
letter-spacing: 0.01em;
|
|
496
|
+
}
|
|
497
|
+
.detail-package-type-icon {
|
|
498
|
+
display: inline-flex;
|
|
499
|
+
width: 0.9rem;
|
|
500
|
+
height: 0.9rem;
|
|
501
|
+
color: var(--ink);
|
|
502
|
+
}
|
|
503
|
+
.detail-package-type-icon svg {
|
|
504
|
+
display: block;
|
|
505
|
+
width: 100%;
|
|
506
|
+
height: 100%;
|
|
507
|
+
}
|
|
399
508
|
.detail-title-row .link-strip {
|
|
400
509
|
margin-left: auto;
|
|
401
510
|
align-self: start;
|
|
@@ -455,6 +564,12 @@
|
|
|
455
564
|
background: #fff;
|
|
456
565
|
padding: 0.5rem;
|
|
457
566
|
}
|
|
567
|
+
.detail-inline-actions {
|
|
568
|
+
display: flex;
|
|
569
|
+
flex-wrap: wrap;
|
|
570
|
+
gap: 0.35rem;
|
|
571
|
+
margin-top: 0.55rem;
|
|
572
|
+
}
|
|
458
573
|
.detail-loading-shell {
|
|
459
574
|
min-height: 14rem;
|
|
460
575
|
display: grid;
|
|
@@ -522,9 +637,18 @@
|
|
|
522
637
|
overflow: hidden;
|
|
523
638
|
}
|
|
524
639
|
.revision-future {
|
|
525
|
-
|
|
526
|
-
border
|
|
527
|
-
background:
|
|
640
|
+
position: relative;
|
|
641
|
+
border: 2px solid rgba(37, 99, 201, 0.3);
|
|
642
|
+
background:
|
|
643
|
+
repeating-linear-gradient(
|
|
644
|
+
135deg,
|
|
645
|
+
rgba(37, 99, 201, 0.018) 0,
|
|
646
|
+
rgba(37, 99, 201, 0.018) 0.5rem,
|
|
647
|
+
rgba(255, 255, 255, 0) 0.5rem,
|
|
648
|
+
rgba(255, 255, 255, 0) 1.2rem
|
|
649
|
+
),
|
|
650
|
+
#fbfdff;
|
|
651
|
+
box-shadow: inset 0.24rem 0 0 rgba(37, 99, 201, 0.42);
|
|
528
652
|
}
|
|
529
653
|
.revision-current {
|
|
530
654
|
border-color: rgba(47, 143, 91, 0.25);
|
|
@@ -557,7 +681,8 @@
|
|
|
557
681
|
background: rgba(124, 124, 133, 0.09);
|
|
558
682
|
}
|
|
559
683
|
.revision-future .revision-card-titlebar {
|
|
560
|
-
background: rgba(
|
|
684
|
+
background: rgba(37, 99, 201, 0.07);
|
|
685
|
+
border-bottom-color: rgba(37, 99, 201, 0.14);
|
|
561
686
|
}
|
|
562
687
|
.revision-card-actions {
|
|
563
688
|
display: inline-flex;
|
|
@@ -637,4 +762,12 @@
|
|
|
637
762
|
.detail {
|
|
638
763
|
overflow: visible;
|
|
639
764
|
}
|
|
765
|
+
.detail-hero {
|
|
766
|
+
position: static;
|
|
767
|
+
margin: 0;
|
|
768
|
+
padding: 0;
|
|
769
|
+
border-bottom: 0;
|
|
770
|
+
background: transparent;
|
|
771
|
+
backdrop-filter: none;
|
|
772
|
+
}
|
|
640
773
|
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
const projectSelect = document.querySelector("[data-project-select]");
|
|
3
3
|
const fromSelect = document.querySelector("[data-from-select]");
|
|
4
4
|
const toSelect = document.querySelector("[data-to-select]");
|
|
5
|
+
const projectActionButtons = Array.from(document.querySelectorAll("[data-project-action]"));
|
|
5
6
|
const sidebarPanel = document.querySelector("[data-sidebar-panel]");
|
|
6
7
|
const filterButtons = Array.from(document.querySelectorAll("[data-filter-button]"));
|
|
7
8
|
const ecosystemButtons = Array.from(document.querySelectorAll("[data-ecosystem-button]"));
|
|
@@ -19,6 +20,12 @@
|
|
|
19
20
|
const emptyDetailHtml = <%= empty_detail_html_json %>;
|
|
20
21
|
const packageCollectionLabel = <%= (@selected_project&.package_collection_label || "Packages").downcase.dump %>;
|
|
21
22
|
const detailDisclosureStorageKey = "gemstar.detailDisclosureOpen";
|
|
23
|
+
const pageWasReloaded = (() => {
|
|
24
|
+
const navigationEntry = performance.getEntriesByType && performance.getEntriesByType("navigation")[0];
|
|
25
|
+
if (navigationEntry) return navigationEntry.type === "reload";
|
|
26
|
+
return performance.navigation && performance.navigation.type === 1;
|
|
27
|
+
})();
|
|
28
|
+
let pendingManualDetailRefresh = pageWasReloaded;
|
|
22
29
|
|
|
23
30
|
const visibleGemLinks = () => gemLinks.filter((link) => !link.hidden);
|
|
24
31
|
const currentSelectedIndex = () => visibleGemLinks().findIndex((link) => link.classList.contains("is-selected"));
|
|
@@ -28,7 +35,9 @@
|
|
|
28
35
|
});
|
|
29
36
|
};
|
|
30
37
|
const packageNameParam = (url) => url.searchParams.get("package") || url.searchParams.get("gem");
|
|
38
|
+
const packageIdParam = (url) => url.searchParams.get("package_id");
|
|
31
39
|
const requestedGemName = () => packageNameParam(new URL(window.location.href));
|
|
40
|
+
const requestedPackageId = () => packageIdParam(new URL(window.location.href));
|
|
32
41
|
const currentDetailGemName = () => {
|
|
33
42
|
if (!detailPanel || !detailPanel.dataset.detailUrl) return null;
|
|
34
43
|
|
|
@@ -38,6 +47,15 @@
|
|
|
38
47
|
return null;
|
|
39
48
|
}
|
|
40
49
|
};
|
|
50
|
+
const currentDetailPackageId = () => {
|
|
51
|
+
if (!detailPanel || !detailPanel.dataset.detailUrl) return null;
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
return packageIdParam(new URL(detailPanel.dataset.detailUrl, window.location.origin));
|
|
55
|
+
} catch (_error) {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
};
|
|
41
59
|
const detailNeedsInitialFetch = () => detailPanel && detailPanel.dataset.detailDeferred === "true" && !!detailPanel.dataset.detailUrl;
|
|
42
60
|
const isSidebarFocused = () => document.activeElement === sidebarPanel;
|
|
43
61
|
const isDetailFocused = () => detailPanel && document.activeElement === detailPanel;
|
|
@@ -47,6 +65,73 @@
|
|
|
47
65
|
const focusDetail = () => {
|
|
48
66
|
if (detailPanel) detailPanel.focus({ preventScroll: true });
|
|
49
67
|
};
|
|
68
|
+
const closeCommandErrorDialog = () => {
|
|
69
|
+
const overlay = document.querySelector("[data-command-error-dialog]");
|
|
70
|
+
if (overlay) overlay.remove();
|
|
71
|
+
};
|
|
72
|
+
const showCommandErrorDialog = (command, output) => {
|
|
73
|
+
closeCommandErrorDialog();
|
|
74
|
+
|
|
75
|
+
const overlay = document.createElement("div");
|
|
76
|
+
overlay.className = "command-dialog-backdrop";
|
|
77
|
+
overlay.dataset.commandErrorDialog = "true";
|
|
78
|
+
|
|
79
|
+
const dialog = document.createElement("section");
|
|
80
|
+
dialog.className = "command-dialog";
|
|
81
|
+
dialog.setAttribute("role", "alertdialog");
|
|
82
|
+
dialog.setAttribute("aria-modal", "true");
|
|
83
|
+
dialog.setAttribute("aria-labelledby", "command-dialog-title");
|
|
84
|
+
|
|
85
|
+
const header = document.createElement("header");
|
|
86
|
+
header.className = "command-dialog-header";
|
|
87
|
+
|
|
88
|
+
const title = document.createElement("h2");
|
|
89
|
+
title.id = "command-dialog-title";
|
|
90
|
+
title.textContent = "Command failed";
|
|
91
|
+
|
|
92
|
+
const commandLine = document.createElement("p");
|
|
93
|
+
commandLine.className = "command-dialog-command";
|
|
94
|
+
commandLine.textContent = command;
|
|
95
|
+
|
|
96
|
+
header.append(title, commandLine);
|
|
97
|
+
|
|
98
|
+
const outputField = document.createElement("pre");
|
|
99
|
+
outputField.className = "command-dialog-output";
|
|
100
|
+
outputField.tabIndex = 0;
|
|
101
|
+
outputField.textContent = output || "No output captured.";
|
|
102
|
+
|
|
103
|
+
const actions = document.createElement("div");
|
|
104
|
+
actions.className = "command-dialog-actions";
|
|
105
|
+
|
|
106
|
+
const copyButton = document.createElement("button");
|
|
107
|
+
copyButton.type = "button";
|
|
108
|
+
copyButton.className = "action";
|
|
109
|
+
copyButton.textContent = "Copy";
|
|
110
|
+
copyButton.addEventListener("click", () => {
|
|
111
|
+
navigator.clipboard.writeText(`Command failed: ${command}\n\n${output || ""}`)
|
|
112
|
+
.then(() => {
|
|
113
|
+
copyButton.textContent = "Copied";
|
|
114
|
+
setTimeout(() => {
|
|
115
|
+
copyButton.textContent = "Copy";
|
|
116
|
+
}, 1400);
|
|
117
|
+
})
|
|
118
|
+
.catch(() => {
|
|
119
|
+
outputField.focus();
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
const closeButton = document.createElement("button");
|
|
124
|
+
closeButton.type = "button";
|
|
125
|
+
closeButton.className = "action action-primary";
|
|
126
|
+
closeButton.textContent = "Close";
|
|
127
|
+
closeButton.addEventListener("click", closeCommandErrorDialog);
|
|
128
|
+
|
|
129
|
+
actions.append(copyButton, closeButton);
|
|
130
|
+
dialog.append(header, outputField, actions);
|
|
131
|
+
overlay.append(dialog);
|
|
132
|
+
document.body.append(overlay);
|
|
133
|
+
closeButton.focus({ preventScroll: true });
|
|
134
|
+
};
|
|
50
135
|
const storedDetailDisclosureOpen = () => {
|
|
51
136
|
try {
|
|
52
137
|
return window.localStorage.getItem(detailDisclosureStorageKey) === "true";
|
|
@@ -76,6 +161,7 @@
|
|
|
76
161
|
const applyGemFilter = (filter) => {
|
|
77
162
|
currentFilter = filter;
|
|
78
163
|
const pinnedGemName = requestedGemName();
|
|
164
|
+
const pinnedPackageId = requestedPackageId();
|
|
79
165
|
const searchTerm = currentSearch.trim().toLowerCase();
|
|
80
166
|
|
|
81
167
|
filterButtons.forEach((button) => {
|
|
@@ -88,7 +174,7 @@
|
|
|
88
174
|
|
|
89
175
|
gemLinks.forEach((link) => {
|
|
90
176
|
const updated = link.dataset.gemUpdated === "true";
|
|
91
|
-
const pinned = pinnedGemName && link.dataset.gemName === pinnedGemName;
|
|
177
|
+
const pinned = pinnedPackageId ? link.dataset.packageId === pinnedPackageId : pinnedGemName && link.dataset.gemName === pinnedGemName;
|
|
92
178
|
const matchesScope = currentPackageScope === "all" || link.dataset.packageScope === currentPackageScope;
|
|
93
179
|
const matchesSearch = searchTerm === "" || link.dataset.gemName.toLowerCase().includes(searchTerm);
|
|
94
180
|
link.hidden = (!matchesScope || (filter === "updated" && !updated && !pinned) || !matchesSearch);
|
|
@@ -109,16 +195,34 @@
|
|
|
109
195
|
}
|
|
110
196
|
};
|
|
111
197
|
|
|
112
|
-
const
|
|
198
|
+
const keepSidebarLinkVisible = (link) => {
|
|
199
|
+
if (!sidebarPanel || !link) return;
|
|
200
|
+
|
|
201
|
+
const panelBounds = sidebarPanel.getBoundingClientRect();
|
|
202
|
+
const stickyHeader = sidebarPanel.querySelector(".sidebar-header");
|
|
203
|
+
const headerBounds = stickyHeader && stickyHeader.getBoundingClientRect();
|
|
204
|
+
const linkBounds = link.getBoundingClientRect();
|
|
205
|
+
const visibleTop = headerBounds ? Math.max(panelBounds.top, headerBounds.bottom) : panelBounds.top;
|
|
206
|
+
|
|
207
|
+
if (linkBounds.top < visibleTop) {
|
|
208
|
+
sidebarPanel.scrollTop -= visibleTop - linkBounds.top;
|
|
209
|
+
} else if (linkBounds.bottom > panelBounds.bottom) {
|
|
210
|
+
sidebarPanel.scrollTop += linkBounds.bottom - panelBounds.bottom;
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
const syncSidebarSelection = (packageId = null, gemName = null, keepVisible = false) => {
|
|
215
|
+
const effectivePackageId = packageId || requestedPackageId();
|
|
113
216
|
const effectiveGemName = gemName || requestedGemName();
|
|
114
|
-
if (!effectiveGemName) {
|
|
217
|
+
if (!effectivePackageId && !effectiveGemName) {
|
|
115
218
|
clearSidebarSelection();
|
|
116
219
|
return;
|
|
117
220
|
}
|
|
118
221
|
|
|
119
222
|
let selectedLink = null;
|
|
120
223
|
gemLinks.forEach((link) => {
|
|
121
|
-
const
|
|
224
|
+
const identityMatches = effectivePackageId ? link.dataset.packageId === effectivePackageId : link.dataset.gemName === effectiveGemName;
|
|
225
|
+
const matches = !link.hidden && identityMatches;
|
|
122
226
|
link.classList.toggle("is-selected", matches);
|
|
123
227
|
if (matches) {
|
|
124
228
|
selectedLink = link;
|
|
@@ -126,7 +230,7 @@
|
|
|
126
230
|
});
|
|
127
231
|
|
|
128
232
|
if (keepVisible && sidebarPanel && selectedLink) {
|
|
129
|
-
selectedLink
|
|
233
|
+
keepSidebarLinkVisible(selectedLink);
|
|
130
234
|
}
|
|
131
235
|
};
|
|
132
236
|
|
|
@@ -170,7 +274,15 @@
|
|
|
170
274
|
</section>
|
|
171
275
|
`;
|
|
172
276
|
|
|
173
|
-
const fetchDetail = (url, historyMode = "push") => {
|
|
277
|
+
const fetchDetail = (url, historyMode = "push", options = {}) => {
|
|
278
|
+
const requestUrl = new URL(url, window.location.origin);
|
|
279
|
+
if (options.refresh) {
|
|
280
|
+
requestUrl.searchParams.set("refresh", "1");
|
|
281
|
+
}
|
|
282
|
+
if (options.useGh) {
|
|
283
|
+
requestUrl.searchParams.set("use_gh", "1");
|
|
284
|
+
requestUrl.searchParams.set("refresh", "1");
|
|
285
|
+
}
|
|
174
286
|
const normalizedUrl = new URL(url, window.location.origin).toString();
|
|
175
287
|
const requestToken = ++detailRequestToken;
|
|
176
288
|
activeDetailUrl = normalizedUrl;
|
|
@@ -180,7 +292,7 @@
|
|
|
180
292
|
replaceDetail(loadingDetailHtml(normalizedUrl));
|
|
181
293
|
}, 1000);
|
|
182
294
|
|
|
183
|
-
fetch(
|
|
295
|
+
fetch(requestUrl.toString(), { headers: { "X-Requested-With": "gemstar-detail" } })
|
|
184
296
|
.then((response) => response.text())
|
|
185
297
|
.then((html) => {
|
|
186
298
|
stopDetailLoading();
|
|
@@ -197,7 +309,7 @@
|
|
|
197
309
|
window.history[historyMethod]({}, "", pageUrl);
|
|
198
310
|
}
|
|
199
311
|
const detailUrl = new URL(url, window.location.origin);
|
|
200
|
-
syncSidebarSelection(packageNameParam(detailUrl));
|
|
312
|
+
syncSidebarSelection(packageIdParam(detailUrl), packageNameParam(detailUrl));
|
|
201
313
|
})
|
|
202
314
|
.catch(() => {
|
|
203
315
|
stopDetailLoading();
|
|
@@ -209,7 +321,7 @@
|
|
|
209
321
|
|
|
210
322
|
stopDetailPoll();
|
|
211
323
|
stopDetailLoading();
|
|
212
|
-
syncSidebarSelection(link.dataset.gemName, keepVisible);
|
|
324
|
+
syncSidebarSelection(link.dataset.packageId, link.dataset.gemName, keepVisible);
|
|
213
325
|
fetchDetail(link.dataset.detailUrl || link.href, historyMode);
|
|
214
326
|
|
|
215
327
|
focusSidebar();
|
|
@@ -217,13 +329,17 @@
|
|
|
217
329
|
|
|
218
330
|
const ensureInitialGemSelection = () => {
|
|
219
331
|
const requested = requestedGemName();
|
|
332
|
+
const requestedId = requestedPackageId();
|
|
220
333
|
|
|
221
334
|
const detailGemName = currentDetailGemName();
|
|
222
|
-
const
|
|
335
|
+
const detailPackageId = currentDetailPackageId();
|
|
336
|
+
const detailLink = detailPackageId ?
|
|
337
|
+
visibleGemLinks().find((link) => link.dataset.packageId === detailPackageId) :
|
|
338
|
+
(detailGemName ? visibleGemLinks().find((link) => link.dataset.gemName === detailGemName) : null);
|
|
223
339
|
|
|
224
340
|
if (detailLink) {
|
|
225
|
-
syncSidebarSelection(
|
|
226
|
-
if (!requested) {
|
|
341
|
+
syncSidebarSelection(detailLink.dataset.packageId, detailLink.dataset.gemName, true);
|
|
342
|
+
if (!requested && !requestedId) {
|
|
227
343
|
const pageUrl = new URL(window.location.href);
|
|
228
344
|
const detailUrl = new URL(detailLink.dataset.detailUrl || detailLink.href, window.location.origin);
|
|
229
345
|
pageUrl.search = detailUrl.search;
|
|
@@ -232,16 +348,21 @@
|
|
|
232
348
|
window.history.replaceState({}, "", pageUrl);
|
|
233
349
|
}
|
|
234
350
|
if (detailNeedsInitialFetch()) {
|
|
235
|
-
fetchDetail(detailLink.dataset.detailUrl || detailLink.href, requested ? "none" : "replace");
|
|
351
|
+
fetchDetail(detailLink.dataset.detailUrl || detailLink.href, requested || requestedId ? "none" : "replace", { refresh: pendingManualDetailRefresh });
|
|
352
|
+
pendingManualDetailRefresh = false;
|
|
236
353
|
}
|
|
237
354
|
return;
|
|
238
355
|
}
|
|
239
356
|
|
|
240
|
-
if (requested) return;
|
|
357
|
+
if (requested || requestedId) return;
|
|
241
358
|
|
|
242
359
|
const firstVisibleLink = visibleGemLinks()[0];
|
|
243
360
|
if (firstVisibleLink) {
|
|
244
|
-
|
|
361
|
+
const refresh = pendingManualDetailRefresh;
|
|
362
|
+
pendingManualDetailRefresh = false;
|
|
363
|
+
syncSidebarSelection(firstVisibleLink.dataset.packageId, firstVisibleLink.dataset.gemName, true);
|
|
364
|
+
fetchDetail(firstVisibleLink.dataset.detailUrl || firstVisibleLink.href, "replace", { refresh });
|
|
365
|
+
focusSidebar();
|
|
245
366
|
}
|
|
246
367
|
};
|
|
247
368
|
|
|
@@ -260,7 +381,7 @@
|
|
|
260
381
|
scheduleDetailPoll();
|
|
261
382
|
}
|
|
262
383
|
|
|
263
|
-
syncSidebarSelection(null, true);
|
|
384
|
+
syncSidebarSelection(null, null, true);
|
|
264
385
|
applyGemFilter(currentFilter);
|
|
265
386
|
ensureInitialGemSelection();
|
|
266
387
|
|
|
@@ -280,7 +401,7 @@
|
|
|
280
401
|
const url = new URL(window.location.href);
|
|
281
402
|
url.searchParams.set("filter", currentFilter);
|
|
282
403
|
window.history.replaceState({}, "", url);
|
|
283
|
-
syncSidebarSelection(null, true);
|
|
404
|
+
syncSidebarSelection(null, null, true);
|
|
284
405
|
});
|
|
285
406
|
});
|
|
286
407
|
|
|
@@ -294,7 +415,7 @@
|
|
|
294
415
|
const url = new URL(window.location.href);
|
|
295
416
|
url.searchParams.set("scope", currentPackageScope);
|
|
296
417
|
window.history.replaceState({}, "", url);
|
|
297
|
-
syncSidebarSelection(null, true);
|
|
418
|
+
syncSidebarSelection(null, null, true);
|
|
298
419
|
});
|
|
299
420
|
});
|
|
300
421
|
|
|
@@ -305,10 +426,69 @@
|
|
|
305
426
|
if (sidebarPanel) {
|
|
306
427
|
sidebarPanel.scrollTop = 0;
|
|
307
428
|
}
|
|
308
|
-
syncSidebarSelection(null, true);
|
|
429
|
+
syncSidebarSelection(null, null, true);
|
|
309
430
|
});
|
|
310
431
|
}
|
|
311
432
|
|
|
433
|
+
document.addEventListener("click", (event) => {
|
|
434
|
+
const button = event.target.closest("[data-detail-use-gh]");
|
|
435
|
+
if (!button || !detailPanel || !detailPanel.dataset.detailUrl) return;
|
|
436
|
+
|
|
437
|
+
event.preventDefault();
|
|
438
|
+
button.disabled = true;
|
|
439
|
+
button.textContent = "Checking...";
|
|
440
|
+
fetchDetail(detailPanel.dataset.detailUrl, "none", { refresh: true, useGh: true });
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
projectActionButtons.forEach((button) => {
|
|
444
|
+
button.addEventListener("click", () => {
|
|
445
|
+
const actionId = button.dataset.projectAction;
|
|
446
|
+
const command = button.dataset.projectActionCommand || actionId;
|
|
447
|
+
if (!actionId) return;
|
|
448
|
+
if (!window.confirm(`Run ${command}?`)) return;
|
|
449
|
+
|
|
450
|
+
const originalText = button.textContent;
|
|
451
|
+
projectActionButtons.forEach((actionButton) => {
|
|
452
|
+
actionButton.disabled = true;
|
|
453
|
+
});
|
|
454
|
+
button.textContent = "Running...";
|
|
455
|
+
|
|
456
|
+
const params = new URLSearchParams();
|
|
457
|
+
params.set("project", "<%= selected_project_index %>");
|
|
458
|
+
|
|
459
|
+
fetch(`/actions/${encodeURIComponent(actionId)}`, {
|
|
460
|
+
method: "POST",
|
|
461
|
+
headers: {
|
|
462
|
+
"Content-Type": "application/x-www-form-urlencoded;charset=UTF-8",
|
|
463
|
+
"X-Requested-With": "gemstar-action"
|
|
464
|
+
},
|
|
465
|
+
body: params.toString()
|
|
466
|
+
})
|
|
467
|
+
.then((response) => response.json().then((payload) => ({ response, payload })))
|
|
468
|
+
.then(({ response, payload }) => {
|
|
469
|
+
if (!response.ok || !payload.ok) {
|
|
470
|
+
const output = payload && payload.output ? payload.output : "";
|
|
471
|
+
const failedCommand = payload && payload.command ? payload.command : command;
|
|
472
|
+
showCommandErrorDialog(failedCommand, output);
|
|
473
|
+
button.textContent = originalText;
|
|
474
|
+
projectActionButtons.forEach((actionButton) => {
|
|
475
|
+
actionButton.disabled = false;
|
|
476
|
+
});
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
window.location.reload();
|
|
481
|
+
})
|
|
482
|
+
.catch((error) => {
|
|
483
|
+
showCommandErrorDialog(command, error.message);
|
|
484
|
+
button.textContent = originalText;
|
|
485
|
+
projectActionButtons.forEach((actionButton) => {
|
|
486
|
+
actionButton.disabled = false;
|
|
487
|
+
});
|
|
488
|
+
});
|
|
489
|
+
});
|
|
490
|
+
});
|
|
491
|
+
|
|
312
492
|
const navigate = (params) => {
|
|
313
493
|
const url = new URL(window.location.href);
|
|
314
494
|
Object.entries(params).forEach(([key, value]) => {
|
|
@@ -328,23 +508,29 @@
|
|
|
328
508
|
event.target.value = "<%= selected_project_index %>";
|
|
329
509
|
return;
|
|
330
510
|
}
|
|
331
|
-
navigate({ project: event.target.value, from: null, to: "worktree", filter: currentFilter, scope: currentPackageScope, package: null, gem: null });
|
|
511
|
+
navigate({ project: event.target.value, from: null, to: "worktree", filter: currentFilter, scope: currentPackageScope, package: null, package_id: null, gem: null });
|
|
332
512
|
});
|
|
333
513
|
}
|
|
334
514
|
|
|
335
515
|
if (fromSelect) {
|
|
336
516
|
fromSelect.addEventListener("change", (event) => {
|
|
337
|
-
navigate({ from: event.target.value, filter: currentFilter, scope: currentPackageScope, package: null, gem: null });
|
|
517
|
+
navigate({ from: event.target.value, filter: currentFilter, scope: currentPackageScope, package: null, package_id: null, gem: null });
|
|
338
518
|
});
|
|
339
519
|
}
|
|
340
520
|
|
|
341
521
|
if (toSelect) {
|
|
342
522
|
toSelect.addEventListener("change", (event) => {
|
|
343
|
-
navigate({ to: event.target.value, filter: currentFilter, scope: currentPackageScope, package: null, gem: null });
|
|
523
|
+
navigate({ to: event.target.value, filter: currentFilter, scope: currentPackageScope, package: null, package_id: null, gem: null });
|
|
344
524
|
});
|
|
345
525
|
}
|
|
346
526
|
|
|
347
527
|
document.addEventListener("keydown", (event) => {
|
|
528
|
+
if (event.key === "Escape" && document.querySelector("[data-command-error-dialog]")) {
|
|
529
|
+
event.preventDefault();
|
|
530
|
+
closeCommandErrorDialog();
|
|
531
|
+
return;
|
|
532
|
+
}
|
|
533
|
+
|
|
348
534
|
const tagName = document.activeElement && document.activeElement.tagName;
|
|
349
535
|
if (tagName === "INPUT" || tagName === "TEXTAREA" || tagName === "SELECT") return;
|
|
350
536
|
|
|
@@ -392,7 +578,7 @@
|
|
|
392
578
|
if (!links.length) return;
|
|
393
579
|
|
|
394
580
|
const selectedGem = currentSelectedIndex();
|
|
395
|
-
const currentIndex = selectedGem >= 0 ? selectedGem :
|
|
581
|
+
const currentIndex = selectedGem >= 0 ? selectedGem : (event.key === "ArrowUp" ? links.length : -1);
|
|
396
582
|
let nextIndex = null;
|
|
397
583
|
|
|
398
584
|
if (event.key === "ArrowDown") nextIndex = Math.min(currentIndex + 1, links.length - 1);
|
data/lib/gemstar.rb
CHANGED
|
@@ -9,6 +9,8 @@ require "gemstar/commands/cache"
|
|
|
9
9
|
require "gemstar/commands/diff"
|
|
10
10
|
require "gemstar/commands/server"
|
|
11
11
|
require "gemstar/config"
|
|
12
|
+
require "gemstar/cargo_lock_file"
|
|
13
|
+
require "gemstar/crates_io_metadata"
|
|
12
14
|
require "gemstar/outputs/basic"
|
|
13
15
|
require "gemstar/outputs/html"
|
|
14
16
|
require "gemstar/outputs/markdown"
|
|
@@ -19,8 +21,10 @@ require "gemstar/importmap_file"
|
|
|
19
21
|
require "gemstar/lock_file"
|
|
20
22
|
require "gemstar/npm_metadata"
|
|
21
23
|
require "gemstar/package_lock_file"
|
|
24
|
+
require "gemstar/pypi_metadata"
|
|
22
25
|
require "gemstar/project"
|
|
23
26
|
require "gemstar/remote_repository"
|
|
24
27
|
require "gemstar/utils"
|
|
25
28
|
require "gemstar/ruby_gems_metadata"
|
|
29
|
+
require "gemstar/uv_lock_file"
|
|
26
30
|
require "gemstar/git_repo"
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: gemstar
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: '1.3'
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Florian Dejako
|
|
@@ -219,8 +219,8 @@ dependencies:
|
|
|
219
219
|
- - "~>"
|
|
220
220
|
- !ruby/object:Gem::Version
|
|
221
221
|
version: '1.9'
|
|
222
|
-
description:
|
|
223
|
-
|
|
222
|
+
description: Browse and compare changelogs for Ruby gems, JavaScript packages, Python
|
|
223
|
+
packages, and Rust crates in your projects.
|
|
224
224
|
email:
|
|
225
225
|
- fdejako@gmail.com
|
|
226
226
|
executables:
|
|
@@ -236,6 +236,7 @@ files:
|
|
|
236
236
|
- lib/gemstar/cache.rb
|
|
237
237
|
- lib/gemstar/cache_cli.rb
|
|
238
238
|
- lib/gemstar/cache_warmer.rb
|
|
239
|
+
- lib/gemstar/cargo_lock_file.rb
|
|
239
240
|
- lib/gemstar/change_log.rb
|
|
240
241
|
- lib/gemstar/cli.rb
|
|
241
242
|
- lib/gemstar/commands/cache.rb
|
|
@@ -243,6 +244,7 @@ files:
|
|
|
243
244
|
- lib/gemstar/commands/diff.rb
|
|
244
245
|
- lib/gemstar/commands/server.rb
|
|
245
246
|
- lib/gemstar/config.rb
|
|
247
|
+
- lib/gemstar/crates_io_metadata.rb
|
|
246
248
|
- lib/gemstar/data/importmap_package_metadata.json
|
|
247
249
|
- lib/gemstar/data/ruby_gems_metadata.json
|
|
248
250
|
- lib/gemstar/git_hub.rb
|
|
@@ -255,10 +257,12 @@ files:
|
|
|
255
257
|
- lib/gemstar/outputs/markdown.rb
|
|
256
258
|
- lib/gemstar/package_lock_file.rb
|
|
257
259
|
- lib/gemstar/project.rb
|
|
260
|
+
- lib/gemstar/pypi_metadata.rb
|
|
258
261
|
- lib/gemstar/remote_repository.rb
|
|
259
262
|
- lib/gemstar/request_logger.rb
|
|
260
263
|
- lib/gemstar/ruby_gems_metadata.rb
|
|
261
264
|
- lib/gemstar/utils.rb
|
|
265
|
+
- lib/gemstar/uv_lock_file.rb
|
|
262
266
|
- lib/gemstar/version.rb
|
|
263
267
|
- lib/gemstar/web/app.rb
|
|
264
268
|
- lib/gemstar/web/templates/app.css
|
|
@@ -288,5 +292,5 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
288
292
|
requirements: []
|
|
289
293
|
rubygems_version: 3.6.9
|
|
290
294
|
specification_version: 4
|
|
291
|
-
summary:
|
|
295
|
+
summary: Changelog viewer for Ruby, JavaScript, Python, and Rust dependencies.
|
|
292
296
|
test_files: []
|