@@ -50,95 +50,102 @@ module JBUpdater
5050 end
5151 end
5252
53- private def validate!
54- raise " plugins_dir required" unless @opts .plugins_dir
55- raise " Plugins dir not found: #{ @opts .plugins_dir} " unless Dir .exists?(@opts .plugins_dir.not_nil!)
56- end
57-
5853 private def detect_build_info : String ?
54+ base_name = File .basename(File .dirname(@opts .plugins_dir.not_nil!)).gsub(/\d .*$/ , " " )
55+ info_json = " "
56+ build_txt = " "
57+
58+ # Prefer explicit --ide-path
59+ if custom = @opts .ide_path
60+ {% if flag?(:darwin ) % }
61+ info_json, build_txt = ide_metadata_paths(:mac , custom)
62+ {% elsif flag?(:linux ) % }
63+ info_json, build_txt = ide_metadata_paths(:linux , custom)
64+ {% elsif flag?(:win32 ) % }
65+ info_json, build_txt = ide_metadata_paths(:windows , custom)
66+ {% else % }
67+ raise " Unsupported platform"
68+ {% end % }
69+ if result = read_build_info(info_json, build_txt, File .basename(custom))
70+ return result
71+ end
72+ end
73+
74+ candidates = [] of String
5975 {% if flag?(:darwin ) % }
60- detect_build_info_macos
76+ candidates = [ " /Applications/ #{ base_name } .app " ]
6177 {% elsif flag?(:linux ) % }
62- detect_build_info_linux
78+ candidates = [ " /opt/ #{ base_name } " , " /usr/share/ #{ base_name } " , " /snap/ #{ base_name } /current " ]
6379 {% elsif flag?(:win32 ) % }
64- detect_build_info_windows
80+ roots = [
81+ ENV [" LOCALAPPDATA" ]?,
82+ ENV [" PROGRAMFILES" ]?,
83+ ENV [" PROGRAMFILES(X86)" ]?,
84+ ].compact
85+ candidates = roots.map { |r | File .join(r, " JetBrains" , base_name) }
6586 {% else % }
6687 raise " Unsupported platform"
6788 {% end % }
68- end
6989
70- private def detect_build_info_macos : String ?
71- base = File .basename(File .dirname(@opts .plugins_dir.not_nil!)).gsub(/\d .*$/ , " " )
72- app_path = " /Applications/#{ base } .app"
73- info_json = File .join(app_path, " Contents" , " Resources" , " product-info.json" )
74- build_txt = File .join(app_path, " Contents" , " Resources" , " build.txt" )
75-
76- if File .file?(info_json)
77- data = JSON .parse(File .read(info_json))
78-
79- code =
80- data[" productCode" ]?.try(& .as_s?) ||
81- data[" product" ]?.try(& .[](" code" )).try(& .as_s?)
82-
83- build =
84- data[" buildNumber" ]?.try(& .as_s?) ||
85- data[" build" ]?.try(& .as_s?) ||
86- data[" version" ]?.try(& .as_s?)
87-
88- return " #{ code } -#{ build } " if code && build
89- elsif File .file?(build_txt)
90- build = File .read(build_txt).strip
91- return " RM-#{ build } " unless build.empty?
90+ candidates.each do |root |
91+ {% if flag?(:darwin ) % }
92+ info_json, build_txt = ide_metadata_paths(:mac , root)
93+ {% elsif flag?(:linux ) % }
94+ info_json, build_txt = ide_metadata_paths(:linux , root)
95+ {% elsif flag?(:win32 ) % }
96+ info_json, build_txt = ide_metadata_paths(:windows , root)
97+ {% end % }
98+ if result = read_build_info(info_json, build_txt, base_name)
99+ return result
100+ end
92101 end
93102
103+ JBUpdater ::Log .warn(" Could not detect build for #{ base_name } " )
94104 nil
95105 end
96106
97- private def detect_build_info_linux : String ?
98- base = File .basename(File .dirname(@opts .plugins_dir.not_nil!)).gsub(/\d .*$/ , " " )
99- candidates = [
100- " /opt/#{ base } " ,
101- " /usr/share/#{ base } " ,
102- " /snap/#{ base } /current" ,
103- ]
104-
105- candidates.each do |root |
106- info = File .join(root, " bin" , " product-info.json" )
107- if File .file?(info)
108- data = JSON .parse(File .read(info))
109- code = data[" productCode" ]?.try(& .as_s?) || data[" product" ]?.try(& .[](" code" )).try(& .as_s?)
110- build = data[" buildNumber" ]?.try(& .as_s?) || data[" build" ]?.try(& .as_s?) || data[" version" ]?.try(& .as_s?)
111- return " #{ code } -#{ build } " if code && build
112- end
107+ private def ide_metadata_paths (platform , root : String ) : {String , String }
108+ case platform
109+ when :mac
110+ {
111+ File .join(root, " Contents" , " Resources" , " product-info.json" ),
112+ File .join(root, " Contents" , " Resources" , " build.txt" ),
113+ }
114+ else
115+ {
116+ File .join(root, " bin" , " product-info.json" ),
117+ File .join(root, " build.txt" ),
118+ }
113119 end
114- nil
115120 end
116121
117- private def detect_build_info_windows : String ?
118- base = File .basename(File .dirname(@opts .plugins_dir.not_nil!)).sub(/\d .*$/ , " " )
119- roots = [
120- ENV [" LOCALAPPDATA" ]?,
121- ENV [" PROGRAMFILES" ]?,
122- ENV [" PROGRAMFILES(X86)" ]?,
123- ].compact
124-
125- roots.each do |root |
126- path = File .join(root, " JetBrains" , base)
127- info_json = File .join(path, " bin" , " product-info.json" )
128- build_txt = File .join(path, " build.txt" )
129- if File .file?(info_json)
130- j = JSON .parse(File .read(info_json))
131- code = j[" productCode" ]?.try(& .as_s?)
132- build = j[" buildNumber" ]?.try(& .as_s?)
122+ private def read_build_info (info_json : String , build_txt : String , base_name : String ) : String ?
123+ if File .file?(info_json)
124+ begin
125+ data = JSON .parse(File .read(info_json))
126+ code =
127+ data[" productCode" ]?.try(& .as_s?) ||
128+ data[" product" ]?.try(& .[](" code" )).try(& .as_s?)
129+ build =
130+ data[" buildNumber" ]?.try(& .as_s?) ||
131+ data[" build" ]?.try(& .as_s?) ||
132+ data[" version" ]?.try(& .as_s?)
133133 return " #{ code } -#{ build } " if code && build
134- elsif File .file?(build_txt)
135- build = File .read(build_txt).strip
136- return " #{ base } -#{ build } " unless build.empty?
134+ rescue ex
135+ JBUpdater ::Log .warn(" Parse error in #{ info_json } : #{ ex.message } " )
137136 end
137+ elsif File .file?(build_txt)
138+ build = File .read(build_txt).strip
139+ return " #{ base_name } -#{ build } " unless build.empty?
138140 end
139141 nil
140142 end
141143
144+ private def validate!
145+ raise " plugins_dir required" unless @opts .plugins_dir
146+ raise " Plugins dir not found: #{ @opts .plugins_dir} " unless Dir .exists?(@opts .plugins_dir.not_nil!)
147+ end
148+
142149 private def list_plugins : Nil
143150 plugins = installed_plugins
144151 if plugins.empty?
@@ -242,7 +249,6 @@ module JBUpdater
242249 HTTPClient .download(uri, tmp_zip)
243250 Utils .extract_zip(tmp_zip, target_dir)
244251
245- # Re-parse plugin.xml in the freshly extracted directory
246252 if post = PluginMeta .parse_from_dir(target_dir)
247253 compat = Utils .build_in_range?(@build .not_nil!, post.since, post.until_build)
248254 suffix = compat ? " " : " (still incompatible)"
@@ -257,7 +263,6 @@ module JBUpdater
257263
258264 # --- URL-resolution helpers ----------------------------------------------
259265
260- # Determine the correct download URL for a given plugin id
261266 private def final_uri (xml_id : String ) : URI
262267 if @opts .direct_urls.has_key?(xml_id)
263268 URI .parse(@opts .direct_urls[xml_id])
@@ -268,11 +273,9 @@ module JBUpdater
268273 end
269274 end
270275
271- # Resolve URL for a specific pinned version
272276 private def resolve_download_url_for_version (xml_id : String , version : String ) : URI
273277 base = " https://plugins.jetbrains.com/plugin/download?pluginId=#{ Utils .escape(xml_id)} &version=#{ Utils .escape(version)} "
274278 res = HTTPClient .head_or_get(base)
275-
276279 case res.status_code
277280 when 301 , 302
278281 loc = res.headers[" Location" ]?
@@ -286,11 +289,9 @@ module JBUpdater
286289 end
287290 end
288291
289- # Resolve URL for the latest compatible version via pluginManager
290292 private def resolve_download_url_via_plugin_manager (xml_id : String , build : String ) : URI
291293 base = " https://plugins.jetbrains.com/pluginManager?action=download&id=#{ Utils .escape(xml_id)} &build=#{ Utils .escape(build)} "
292294 res = HTTPClient .head_or_get(base)
293-
294295 case res.status_code
295296 when 301 , 302
296297 loc = res.headers[" Location" ]?
0 commit comments