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/project.rb
CHANGED
|
@@ -2,31 +2,57 @@ require "time"
|
|
|
2
2
|
|
|
3
3
|
module Gemstar
|
|
4
4
|
class Project
|
|
5
|
+
REVISION_HISTORY_LIMIT = 100
|
|
6
|
+
|
|
5
7
|
attr_reader :directory
|
|
6
8
|
attr_reader :gemfile_path
|
|
7
9
|
attr_reader :lockfile_path
|
|
10
|
+
attr_reader :importmap_path
|
|
11
|
+
attr_reader :package_json_path
|
|
12
|
+
attr_reader :package_lock_path
|
|
8
13
|
attr_reader :name
|
|
9
14
|
|
|
10
15
|
def self.from_cli_argument(input)
|
|
11
16
|
expanded_input = File.expand_path(input)
|
|
12
|
-
|
|
13
|
-
|
|
17
|
+
if File.directory?(expanded_input)
|
|
18
|
+
directory = expanded_input
|
|
14
19
|
else
|
|
15
|
-
expanded_input
|
|
20
|
+
basename = File.basename(expanded_input)
|
|
21
|
+
directory =
|
|
22
|
+
case basename
|
|
23
|
+
when "Gemfile", "package.json", "package-lock.json"
|
|
24
|
+
File.dirname(expanded_input)
|
|
25
|
+
when "importmap.rb"
|
|
26
|
+
File.dirname(File.dirname(expanded_input))
|
|
27
|
+
else
|
|
28
|
+
nil
|
|
29
|
+
end
|
|
16
30
|
end
|
|
17
31
|
|
|
18
|
-
raise ArgumentError, "No
|
|
19
|
-
raise ArgumentError, "
|
|
32
|
+
raise ArgumentError, "No supported project files found for #{input}" unless directory
|
|
33
|
+
raise ArgumentError, "No supported project files found for #{input}" unless supported_project_directory?(directory)
|
|
34
|
+
|
|
35
|
+
new(directory: directory)
|
|
36
|
+
end
|
|
20
37
|
|
|
21
|
-
|
|
38
|
+
def self.supported_project_directory?(directory)
|
|
39
|
+
File.file?(File.join(directory, "Gemfile")) ||
|
|
40
|
+
File.file?(File.join(directory, "config", "importmap.rb")) ||
|
|
41
|
+
File.file?(File.join(directory, "package.json")) ||
|
|
42
|
+
File.file?(File.join(directory, "package-lock.json"))
|
|
22
43
|
end
|
|
23
44
|
|
|
24
|
-
def initialize(
|
|
25
|
-
@
|
|
26
|
-
@
|
|
45
|
+
def initialize(directory:)
|
|
46
|
+
@directory = File.expand_path(directory)
|
|
47
|
+
@gemfile_path = File.join(@directory, "Gemfile")
|
|
27
48
|
@lockfile_path = File.join(@directory, "Gemfile.lock")
|
|
49
|
+
@importmap_path = File.join(@directory, "config", "importmap.rb")
|
|
50
|
+
@package_json_path = File.join(@directory, "package.json")
|
|
51
|
+
@package_lock_path = File.join(@directory, "package-lock.json")
|
|
28
52
|
@name = File.basename(@directory)
|
|
29
53
|
@lockfile_cache = {}
|
|
54
|
+
@importmap_cache = {}
|
|
55
|
+
@package_lock_cache = {}
|
|
30
56
|
@gem_states_cache = {}
|
|
31
57
|
@gem_added_on_cache = {}
|
|
32
58
|
@history_cache = {}
|
|
@@ -50,11 +76,39 @@ module Gemstar
|
|
|
50
76
|
@current_lockfile ||= Gemstar::LockFile.new(path: lockfile_path)
|
|
51
77
|
end
|
|
52
78
|
|
|
53
|
-
def
|
|
79
|
+
def gemfile?
|
|
80
|
+
File.file?(gemfile_path)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def importmap?
|
|
84
|
+
File.file?(importmap_path)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def current_importmap
|
|
88
|
+
return nil unless importmap?
|
|
89
|
+
|
|
90
|
+
@current_importmap ||= Gemstar::ImportmapFile.new(path: importmap_path, vendor_reader: importmap_vendor_reader("worktree"))
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def package_lock?
|
|
94
|
+
File.file?(package_lock_path)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def package_json?
|
|
98
|
+
File.file?(package_json_path)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def current_package_lock
|
|
102
|
+
return nil unless package_lock?
|
|
103
|
+
|
|
104
|
+
@current_package_lock ||= Gemstar::PackageLockFile.new(path: package_lock_path)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def revision_history(limit: REVISION_HISTORY_LIMIT)
|
|
54
108
|
history_for_paths(tracked_git_paths, limit: limit)
|
|
55
109
|
end
|
|
56
110
|
|
|
57
|
-
def lockfile_revision_history(limit:
|
|
111
|
+
def lockfile_revision_history(limit: REVISION_HISTORY_LIMIT)
|
|
58
112
|
return [] unless lockfile?
|
|
59
113
|
|
|
60
114
|
relative_path = git_repo.relative_path(lockfile_path)
|
|
@@ -63,7 +117,9 @@ module Gemstar
|
|
|
63
117
|
history_for_paths([relative_path], limit: limit)
|
|
64
118
|
end
|
|
65
119
|
|
|
66
|
-
def gemfile_revision_history(limit:
|
|
120
|
+
def gemfile_revision_history(limit: REVISION_HISTORY_LIMIT)
|
|
121
|
+
return [] unless gemfile?
|
|
122
|
+
|
|
67
123
|
relative_path = git_repo.relative_path(gemfile_path)
|
|
68
124
|
return [] if relative_path.nil?
|
|
69
125
|
|
|
@@ -71,12 +127,12 @@ module Gemstar
|
|
|
71
127
|
end
|
|
72
128
|
|
|
73
129
|
def default_from_revision_id
|
|
74
|
-
|
|
130
|
+
default_changed_revision_id ||
|
|
75
131
|
gemfile_revision_history(limit: 1).first&.dig(:id) ||
|
|
76
132
|
"worktree"
|
|
77
133
|
end
|
|
78
134
|
|
|
79
|
-
def revision_options(limit:
|
|
135
|
+
def revision_options(limit: REVISION_HISTORY_LIMIT)
|
|
80
136
|
[{ id: "worktree", label: "Worktree", description: "Current Gemfile.lock in the working tree" }] +
|
|
81
137
|
revision_history(limit: limit).map do |revision|
|
|
82
138
|
{
|
|
@@ -87,6 +143,26 @@ module Gemstar
|
|
|
87
143
|
end
|
|
88
144
|
end
|
|
89
145
|
|
|
146
|
+
def package_scopes
|
|
147
|
+
scopes = []
|
|
148
|
+
scopes << :gems if gemfile? || lockfile?
|
|
149
|
+
scopes << :js if importmap? || package_lock? || package_json?
|
|
150
|
+
scopes
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def package_scope_options
|
|
154
|
+
package_scopes.map do |scope|
|
|
155
|
+
{
|
|
156
|
+
id: package_scope_id(scope),
|
|
157
|
+
label: package_scope_label(scope)
|
|
158
|
+
}
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def package_collection_label
|
|
163
|
+
package_scopes == [:gems] ? "Gems" : "Packages"
|
|
164
|
+
end
|
|
165
|
+
|
|
90
166
|
def lockfile_for_revision(revision_id)
|
|
91
167
|
cache_key = revision_id || "worktree"
|
|
92
168
|
return @lockfile_cache[cache_key] if @lockfile_cache.key?(cache_key)
|
|
@@ -102,6 +178,36 @@ module Gemstar
|
|
|
102
178
|
@lockfile_cache[cache_key] = Gemstar::LockFile.new(content: content)
|
|
103
179
|
end
|
|
104
180
|
|
|
181
|
+
def importmap_for_revision(revision_id)
|
|
182
|
+
cache_key = revision_id || "worktree"
|
|
183
|
+
return @importmap_cache[cache_key] if @importmap_cache.key?(cache_key)
|
|
184
|
+
return @importmap_cache[cache_key] = current_importmap if revision_id.nil? || revision_id == "worktree"
|
|
185
|
+
return nil unless importmap?
|
|
186
|
+
|
|
187
|
+
relative_importmap_path = git_repo.relative_path(importmap_path)
|
|
188
|
+
return nil if relative_importmap_path.nil?
|
|
189
|
+
|
|
190
|
+
content = git_repo.try_git_command(["show", "#{revision_id}:#{relative_importmap_path}"])
|
|
191
|
+
return nil if content.nil? || content.empty?
|
|
192
|
+
|
|
193
|
+
@importmap_cache[cache_key] = Gemstar::ImportmapFile.new(content: content, vendor_reader: importmap_vendor_reader(revision_id))
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def package_lock_for_revision(revision_id)
|
|
197
|
+
cache_key = revision_id || "worktree"
|
|
198
|
+
return @package_lock_cache[cache_key] if @package_lock_cache.key?(cache_key)
|
|
199
|
+
return @package_lock_cache[cache_key] = current_package_lock if revision_id.nil? || revision_id == "worktree"
|
|
200
|
+
return nil unless package_lock?
|
|
201
|
+
|
|
202
|
+
relative_package_lock_path = git_repo.relative_path(package_lock_path)
|
|
203
|
+
return nil if relative_package_lock_path.nil?
|
|
204
|
+
|
|
205
|
+
content = git_repo.try_git_command(["show", "#{revision_id}:#{relative_package_lock_path}"])
|
|
206
|
+
return nil if content.nil? || content.empty?
|
|
207
|
+
|
|
208
|
+
@package_lock_cache[cache_key] = Gemstar::PackageLockFile.new(content: content)
|
|
209
|
+
end
|
|
210
|
+
|
|
105
211
|
def gem_states(from_revision_id: default_from_revision_id, to_revision_id: "worktree")
|
|
106
212
|
cache_key = [from_revision_id, to_revision_id]
|
|
107
213
|
return @gem_states_cache[cache_key] if @gem_states_cache.key?(cache_key)
|
|
@@ -111,7 +217,7 @@ module Gemstar
|
|
|
111
217
|
from_specs = from_lockfile&.specs || {}
|
|
112
218
|
to_specs = to_lockfile&.specs || {}
|
|
113
219
|
|
|
114
|
-
|
|
220
|
+
gem_states = (from_specs.keys | to_specs.keys).map do |gem_name|
|
|
115
221
|
old_version = from_specs[gem_name]
|
|
116
222
|
new_version = to_specs[gem_name]
|
|
117
223
|
effective_lockfile = new_version ? to_lockfile : from_lockfile
|
|
@@ -119,6 +225,8 @@ module Gemstar
|
|
|
119
225
|
|
|
120
226
|
{
|
|
121
227
|
name: gem_name,
|
|
228
|
+
package_scope: "gems",
|
|
229
|
+
package_type_label: "Gem",
|
|
122
230
|
old_version: old_version,
|
|
123
231
|
new_version: new_version,
|
|
124
232
|
status: gem_status(old_version, new_version),
|
|
@@ -128,26 +236,99 @@ module Gemstar
|
|
|
128
236
|
bundle_origins: bundle_origins,
|
|
129
237
|
bundle_origin_labels: bundle_origin_labels(bundle_origins)
|
|
130
238
|
}
|
|
131
|
-
end
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
from_importmap = importmap_for_revision(from_revision_id)
|
|
242
|
+
to_importmap = importmap_for_revision(to_revision_id)
|
|
243
|
+
from_js_specs = from_importmap&.specs || {}
|
|
244
|
+
to_js_specs = to_importmap&.specs || {}
|
|
245
|
+
js_states = (from_js_specs.keys | to_js_specs.keys).map do |package_name|
|
|
246
|
+
old_target = from_js_specs[package_name]
|
|
247
|
+
new_target = to_js_specs[package_name]
|
|
248
|
+
old_source = from_importmap&.source_for(package_name) || {}
|
|
249
|
+
new_source = to_importmap&.source_for(package_name) || {}
|
|
250
|
+
old_source = enrich_importmap_source(old_source, from_lockfile)
|
|
251
|
+
new_source = enrich_importmap_source(new_source, to_lockfile)
|
|
252
|
+
effective_source = new_target ? new_source : old_source
|
|
253
|
+
old_package_version = js_package_version(old_source)
|
|
254
|
+
new_package_version = js_package_version(new_source)
|
|
255
|
+
comparison_old = old_package_version || old_target
|
|
256
|
+
comparison_new = new_package_version || new_target
|
|
257
|
+
|
|
258
|
+
{
|
|
259
|
+
name: package_name,
|
|
260
|
+
package_scope: "js",
|
|
261
|
+
package_type_label: "JS",
|
|
262
|
+
package_source_file: :importmap,
|
|
263
|
+
old_version: old_package_version,
|
|
264
|
+
new_version: new_package_version,
|
|
265
|
+
raw_old_version: old_target,
|
|
266
|
+
raw_new_version: new_target,
|
|
267
|
+
status: gem_status(comparison_old, comparison_new),
|
|
268
|
+
version_label: js_version_label(old_target, new_target, old_source, new_source),
|
|
269
|
+
platform: nil,
|
|
270
|
+
source: effective_source,
|
|
271
|
+
bundle_origins: [],
|
|
272
|
+
bundle_origin_labels: []
|
|
273
|
+
}
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
from_package_lock = package_lock_for_revision(from_revision_id)
|
|
277
|
+
to_package_lock = package_lock_for_revision(to_revision_id)
|
|
278
|
+
from_npm_specs = from_package_lock&.specs || {}
|
|
279
|
+
to_npm_specs = to_package_lock&.specs || {}
|
|
280
|
+
npm_states = (from_npm_specs.keys | to_npm_specs.keys).map do |package_name|
|
|
281
|
+
old_version = from_npm_specs[package_name]
|
|
282
|
+
new_version = to_npm_specs[package_name]
|
|
283
|
+
effective_package_lock = new_version ? to_package_lock : from_package_lock
|
|
284
|
+
|
|
285
|
+
{
|
|
286
|
+
name: package_name,
|
|
287
|
+
package_scope: "js",
|
|
288
|
+
package_type_label: "JS",
|
|
289
|
+
package_source_file: :package_lock,
|
|
290
|
+
old_version: old_version,
|
|
291
|
+
new_version: new_version,
|
|
292
|
+
status: gem_status(old_version, new_version),
|
|
293
|
+
version_label: version_label(old_version, new_version),
|
|
294
|
+
platform: nil,
|
|
295
|
+
source: effective_package_lock&.source_for(package_name),
|
|
296
|
+
bundle_origins: [],
|
|
297
|
+
bundle_origin_labels: []
|
|
298
|
+
}
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
@gem_states_cache[cache_key] = (gem_states + js_states + npm_states).sort_by { |gem| [gem[:name], gem[:package_scope], gem[:package_source_file].to_s] }
|
|
132
302
|
end
|
|
133
303
|
|
|
134
|
-
def
|
|
135
|
-
cache_key = [
|
|
304
|
+
def package_added_on(package_name, package_scope:, revision_id: "worktree", source_file: nil)
|
|
305
|
+
cache_key = [package_name, package_scope, source_file, revision_id]
|
|
136
306
|
return @gem_added_on_cache[cache_key] if @gem_added_on_cache.key?(cache_key)
|
|
137
|
-
return nil unless lockfile?
|
|
138
307
|
|
|
139
|
-
|
|
140
|
-
|
|
308
|
+
tracked_file, reader =
|
|
309
|
+
if source_file == :importmap
|
|
310
|
+
[importmap_path, method(:importmap_for_revision)]
|
|
311
|
+
elsif source_file == :package_lock
|
|
312
|
+
[package_lock_path, method(:package_lock_for_revision)]
|
|
313
|
+
elsif package_scope == "js"
|
|
314
|
+
[importmap_path, method(:importmap_for_revision)]
|
|
315
|
+
else
|
|
316
|
+
[lockfile_path, method(:lockfile_for_revision)]
|
|
317
|
+
end
|
|
318
|
+
return @gem_added_on_cache[cache_key] = nil unless File.file?(tracked_file)
|
|
141
319
|
|
|
142
|
-
|
|
320
|
+
target_snapshot = reader.call(revision_id)
|
|
321
|
+
return @gem_added_on_cache[cache_key] = nil unless target_snapshot&.specs&.key?(package_name)
|
|
322
|
+
|
|
323
|
+
relative_path = git_repo.relative_path(tracked_file)
|
|
143
324
|
return @gem_added_on_cache[cache_key] = nil if relative_path.nil?
|
|
144
325
|
|
|
145
326
|
first_seen_revision = history_for_paths([relative_path], limit: nil, reverse: true).find do |revision|
|
|
146
|
-
|
|
147
|
-
|
|
327
|
+
snapshot = reader.call(revision[:id])
|
|
328
|
+
snapshot&.specs&.key?(package_name)
|
|
148
329
|
end
|
|
149
330
|
|
|
150
|
-
return @gem_added_on_cache[cache_key] = worktree_added_on_info if first_seen_revision.nil? && revision_id == "worktree"
|
|
331
|
+
return @gem_added_on_cache[cache_key] = worktree_added_on_info(tracked_file) if first_seen_revision.nil? && revision_id == "worktree"
|
|
151
332
|
return @gem_added_on_cache[cache_key] = nil unless first_seen_revision
|
|
152
333
|
|
|
153
334
|
@gem_added_on_cache[cache_key] = {
|
|
@@ -161,18 +342,22 @@ module Gemstar
|
|
|
161
342
|
|
|
162
343
|
private
|
|
163
344
|
|
|
164
|
-
def
|
|
165
|
-
return nil unless lockfile?
|
|
166
|
-
|
|
345
|
+
def default_changed_revision_id
|
|
167
346
|
current_specs = current_lockfile&.specs || {}
|
|
347
|
+
current_importmap_specs = current_importmap&.specs || {}
|
|
348
|
+
current_package_lock_specs = current_package_lock&.specs || {}
|
|
168
349
|
|
|
169
|
-
|
|
350
|
+
revision_history(limit: REVISION_HISTORY_LIMIT).find do |revision|
|
|
170
351
|
revision_lockfile = lockfile_for_revision(revision[:id])
|
|
171
|
-
|
|
352
|
+
revision_importmap = importmap_for_revision(revision[:id])
|
|
353
|
+
revision_package_lock = package_lock_for_revision(revision[:id])
|
|
354
|
+
(revision_lockfile && revision_lockfile.specs != current_specs) ||
|
|
355
|
+
(revision_importmap && revision_importmap.specs != current_importmap_specs) ||
|
|
356
|
+
(revision_package_lock && revision_package_lock.specs != current_package_lock_specs)
|
|
172
357
|
end&.dig(:id)
|
|
173
358
|
end
|
|
174
359
|
|
|
175
|
-
def history_for_paths(paths, limit:
|
|
360
|
+
def history_for_paths(paths, limit: REVISION_HISTORY_LIMIT, reverse: false)
|
|
176
361
|
return [] if git_root.nil? || git_root.empty?
|
|
177
362
|
return [] if paths.empty?
|
|
178
363
|
|
|
@@ -199,13 +384,23 @@ module Gemstar
|
|
|
199
384
|
end
|
|
200
385
|
|
|
201
386
|
def tracked_git_paths
|
|
202
|
-
[gemfile_path, lockfile_path].filter_map do |path|
|
|
387
|
+
[gemfile_path, lockfile_path, importmap_path, package_json_path, package_lock_path, *importmap_vendor_paths].filter_map do |path|
|
|
203
388
|
next unless File.file?(path)
|
|
204
389
|
|
|
205
390
|
git_repo.relative_path(path)
|
|
206
391
|
end.uniq
|
|
207
392
|
end
|
|
208
393
|
|
|
394
|
+
def importmap_vendor_paths
|
|
395
|
+
return [] unless current_importmap
|
|
396
|
+
|
|
397
|
+
current_importmap.specs.values.filter_map do |target|
|
|
398
|
+
next unless target.to_s.end_with?(".js", ".mjs")
|
|
399
|
+
|
|
400
|
+
File.join(directory, "vendor", "javascript", target.to_s)
|
|
401
|
+
end
|
|
402
|
+
end
|
|
403
|
+
|
|
209
404
|
def gem_status(old_version, new_version)
|
|
210
405
|
return :added if old_version.nil? && !new_version.nil?
|
|
211
406
|
return :removed if !old_version.nil? && new_version.nil?
|
|
@@ -226,6 +421,77 @@ module Gemstar
|
|
|
226
421
|
"#{old_version} → #{new_version}"
|
|
227
422
|
end
|
|
228
423
|
|
|
424
|
+
def importmap_version_label(old_target, new_target)
|
|
425
|
+
old_label = importmap_target_label(old_target)
|
|
426
|
+
new_label = importmap_target_label(new_target)
|
|
427
|
+
return "new → #{new_label}" if old_target.nil? && !new_target.nil?
|
|
428
|
+
return "#{old_label} → removed" if !old_target.nil? && new_target.nil?
|
|
429
|
+
return new_label.to_s if old_target == new_target
|
|
430
|
+
|
|
431
|
+
"#{old_label} → #{new_label}"
|
|
432
|
+
end
|
|
433
|
+
|
|
434
|
+
def js_version_label(old_target, new_target, old_source, new_source)
|
|
435
|
+
old_label = js_version_label_part(old_target, old_source)
|
|
436
|
+
new_label = js_version_label_part(new_target, new_source)
|
|
437
|
+
return "new → #{new_label}" if old_target.nil? && !new_target.nil?
|
|
438
|
+
return "#{old_label} → removed" if !old_target.nil? && new_target.nil?
|
|
439
|
+
return new_label.to_s if old_label == new_label && old_target == new_target
|
|
440
|
+
return "#{new_label} (source changed)" if old_label == new_label
|
|
441
|
+
|
|
442
|
+
"#{old_label} → #{new_label}"
|
|
443
|
+
end
|
|
444
|
+
|
|
445
|
+
def js_version_label_part(target, source)
|
|
446
|
+
version = js_package_version(source)
|
|
447
|
+
return version if version && !version.empty?
|
|
448
|
+
|
|
449
|
+
requirement = source&.dig(:package_requirement)
|
|
450
|
+
return "range #{requirement}" if requirement && !requirement.empty?
|
|
451
|
+
|
|
452
|
+
importmap_target_label(target)
|
|
453
|
+
end
|
|
454
|
+
|
|
455
|
+
def js_package_version(source)
|
|
456
|
+
source && source[:package_version].to_s.empty? ? nil : source&.dig(:package_version)
|
|
457
|
+
end
|
|
458
|
+
|
|
459
|
+
def enrich_importmap_source(source, lockfile)
|
|
460
|
+
source = (source || {}).dup
|
|
461
|
+
provider_gem = source[:provider_gem]
|
|
462
|
+
return source if provider_gem.to_s.empty?
|
|
463
|
+
|
|
464
|
+
provider_version = lockfile&.specs&.[](provider_gem)
|
|
465
|
+
source[:provider_version] = provider_version unless provider_version.to_s.empty?
|
|
466
|
+
source[:package_version] ||= provider_version unless provider_version.to_s.empty?
|
|
467
|
+
source
|
|
468
|
+
end
|
|
469
|
+
|
|
470
|
+
def importmap_target_label(target)
|
|
471
|
+
return "" if target.nil?
|
|
472
|
+
|
|
473
|
+
version = target.to_s[/@(\d+(?:\.\d+)*[\w.-]*)/, 1]
|
|
474
|
+
return version if version
|
|
475
|
+
|
|
476
|
+
target.to_s.sub(%r{\Ahttps?://}, "").slice(0, 36)
|
|
477
|
+
end
|
|
478
|
+
|
|
479
|
+
def importmap_vendor_reader(revision_id)
|
|
480
|
+
lambda do |target|
|
|
481
|
+
next nil unless target.to_s.end_with?(".js", ".mjs")
|
|
482
|
+
|
|
483
|
+
vendor_path = File.join(directory, "vendor", "javascript", target.to_s)
|
|
484
|
+
if revision_id.nil? || revision_id == "worktree"
|
|
485
|
+
File.file?(vendor_path) ? File.read(vendor_path) : nil
|
|
486
|
+
else
|
|
487
|
+
relative_path = git_repo.relative_path(vendor_path)
|
|
488
|
+
next nil if relative_path.nil?
|
|
489
|
+
|
|
490
|
+
git_repo.try_git_command(["show", "#{revision_id}:#{relative_path}"])
|
|
491
|
+
end
|
|
492
|
+
end
|
|
493
|
+
end
|
|
494
|
+
|
|
229
495
|
def compare_versions(left, right)
|
|
230
496
|
Gem::Version.new(left.to_s.gsub(/-[\w\-]+$/, "")) <=> Gem::Version.new(right.to_s.gsub(/-[\w\-]+$/, ""))
|
|
231
497
|
rescue ArgumentError
|
|
@@ -241,12 +507,12 @@ module Gemstar
|
|
|
241
507
|
end.compact.uniq
|
|
242
508
|
end
|
|
243
509
|
|
|
244
|
-
def worktree_added_on_info
|
|
245
|
-
return nil unless File.file?(
|
|
510
|
+
def worktree_added_on_info(path)
|
|
511
|
+
return nil unless File.file?(path)
|
|
246
512
|
|
|
247
513
|
{
|
|
248
514
|
project_name: name,
|
|
249
|
-
date: File.mtime(
|
|
515
|
+
date: File.mtime(path).strftime("%Y-%m-%d"),
|
|
250
516
|
revision: "Worktree",
|
|
251
517
|
revision_url: nil,
|
|
252
518
|
worktree: true
|
|
@@ -259,5 +525,23 @@ module Gemstar
|
|
|
259
525
|
|
|
260
526
|
"#{repo_url}/commit/#{full_sha}"
|
|
261
527
|
end
|
|
528
|
+
|
|
529
|
+
def package_scope_id(scope)
|
|
530
|
+
case scope
|
|
531
|
+
when :gems then "gems"
|
|
532
|
+
when :js then "js"
|
|
533
|
+
when :python then "python"
|
|
534
|
+
else scope.to_s
|
|
535
|
+
end
|
|
536
|
+
end
|
|
537
|
+
|
|
538
|
+
def package_scope_label(scope)
|
|
539
|
+
case scope
|
|
540
|
+
when :gems then "Gems"
|
|
541
|
+
when :js then "JS"
|
|
542
|
+
when :python then "Python"
|
|
543
|
+
else scope.to_s.capitalize
|
|
544
|
+
end
|
|
545
|
+
end
|
|
262
546
|
end
|
|
263
547
|
end
|
|
@@ -1,23 +1,40 @@
|
|
|
1
1
|
require "open-uri"
|
|
2
2
|
require "uri"
|
|
3
3
|
require "json"
|
|
4
|
+
require "date"
|
|
5
|
+
require "time"
|
|
6
|
+
require_relative "remote_repository"
|
|
4
7
|
|
|
5
8
|
module Gemstar
|
|
6
9
|
class RubyGemsMetadata
|
|
10
|
+
RUBY_GEMS_METADATA_PATH = File.expand_path("data/ruby_gems_metadata.json", __dir__)
|
|
11
|
+
|
|
7
12
|
def initialize(gem_name)
|
|
8
13
|
@gem_name = gem_name
|
|
9
14
|
end
|
|
10
15
|
|
|
11
16
|
attr_reader :gem_name
|
|
12
17
|
|
|
18
|
+
def self.package_metadata
|
|
19
|
+
@package_metadata ||= begin
|
|
20
|
+
JSON.parse(File.read(RUBY_GEMS_METADATA_PATH))
|
|
21
|
+
rescue Errno::ENOENT, JSON::ParserError
|
|
22
|
+
{}
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def cache_key
|
|
27
|
+
"rubygems-#{gem_name}"
|
|
28
|
+
end
|
|
29
|
+
|
|
13
30
|
def meta(cache_only: false, force_refresh: false)
|
|
14
31
|
return @meta if !cache_only && defined?(@meta)
|
|
15
32
|
|
|
16
33
|
json = if cache_only
|
|
17
|
-
Cache.peek(
|
|
34
|
+
Cache.peek(cache_key)
|
|
18
35
|
else
|
|
19
36
|
url = "https://rubygems.org/api/v1/gems/#{URI.encode_www_form_component(gem_name)}.json"
|
|
20
|
-
Cache.fetch(
|
|
37
|
+
Cache.fetch(cache_key, force: force_refresh) do
|
|
21
38
|
URI.open(url).read
|
|
22
39
|
end
|
|
23
40
|
end
|
|
@@ -71,5 +88,99 @@ module Gemstar
|
|
|
71
88
|
repo
|
|
72
89
|
end
|
|
73
90
|
|
|
91
|
+
def changelog_sections(versions: nil, cache_only: false, force_refresh: false)
|
|
92
|
+
Gemstar::ChangeLog.new(self).sections(cache_only: cache_only, force_refresh: force_refresh)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def registry_release_dates(cache_only: false, force_refresh: false)
|
|
96
|
+
cache_key = "rubygems-versions-#{gem_name}"
|
|
97
|
+
json = if cache_only
|
|
98
|
+
Cache.peek(cache_key)
|
|
99
|
+
else
|
|
100
|
+
url = "https://rubygems.org/api/v1/versions/#{URI.encode_www_form_component(gem_name)}.json"
|
|
101
|
+
Cache.fetch(cache_key, force: force_refresh) do
|
|
102
|
+
URI.open(url, read_timeout: 8).read
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
Array(JSON.parse(json)).each_with_object({}) do |version, dates|
|
|
107
|
+
number = version["number"].to_s
|
|
108
|
+
created_at = version["created_at"].to_s
|
|
109
|
+
next if number.empty? || created_at.empty?
|
|
110
|
+
|
|
111
|
+
dates[number] = format_registry_release_date(created_at)
|
|
112
|
+
end.compact
|
|
113
|
+
rescue JSON::ParserError
|
|
114
|
+
{}
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def warm_cache(versions: nil)
|
|
118
|
+
meta
|
|
119
|
+
repo_uri
|
|
120
|
+
changelog_sections(versions: versions)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def discover_github_tag_sections?
|
|
124
|
+
false
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def github_tag_candidates(version)
|
|
128
|
+
raw = version.to_s
|
|
129
|
+
[raw, (raw.start_with?("v") ? raw : "v#{raw}")].uniq
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def github_tag_matches?(tag_name)
|
|
133
|
+
true
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def changelog_source(repo_uri:, cache_only: false, force_refresh: false)
|
|
137
|
+
override = package_metadata.dig("changelog")
|
|
138
|
+
if override
|
|
139
|
+
override_paths = Array(override["paths"]).compact
|
|
140
|
+
override_branches = Array(override["branches"]).compact
|
|
141
|
+
override_branches = [""] if override_branches.empty? && override["raw_base"]
|
|
142
|
+
return {
|
|
143
|
+
base: expand_metadata_template(override["raw_base"] || github_raw_base(repo_uri)),
|
|
144
|
+
paths: override_paths.empty? ? Gemstar::ChangeLog::DEFAULT_CHANGELOG_PATHS : override_paths,
|
|
145
|
+
branches: override_branches.empty? ? RemoteRepository.new(github_raw_base(repo_uri)).find_main_branch(cache_only: cache_only, force_refresh: force_refresh) : override_branches
|
|
146
|
+
}
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
base = github_raw_base(repo_uri)
|
|
150
|
+
{
|
|
151
|
+
base: base,
|
|
152
|
+
paths: Gemstar::ChangeLog::DEFAULT_CHANGELOG_PATHS,
|
|
153
|
+
branches: RemoteRepository.new(base).find_main_branch(cache_only: cache_only, force_refresh: force_refresh)
|
|
154
|
+
}
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def package_metadata
|
|
158
|
+
self.class.package_metadata.find do |pattern, _metadata|
|
|
159
|
+
File.fnmatch?(pattern, gem_name)
|
|
160
|
+
end&.last || {}
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
private
|
|
164
|
+
|
|
165
|
+
def github_raw_base(repo_uri)
|
|
166
|
+
repo_uri.sub("https://github.com", "https://raw.githubusercontent.com").chomp("/")
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def expand_metadata_template(value)
|
|
170
|
+
value.to_s.gsub("{gem_name}", gem_name)
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def format_registry_release_date(datetime)
|
|
174
|
+
if datetime.to_s.match?(/\A\d{4}-\d{2}-\d{2}\z/)
|
|
175
|
+
date = Date.strptime(datetime.to_s, "%Y-%m-%d")
|
|
176
|
+
return date.strftime("%b #{date.day}, %Y")
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
time = Time.parse(datetime.to_s).utc
|
|
180
|
+
time.strftime("%b #{time.day}, %Y")
|
|
181
|
+
rescue ArgumentError
|
|
182
|
+
nil
|
|
183
|
+
end
|
|
184
|
+
|
|
74
185
|
end
|
|
75
186
|
end
|