@@ -214,13 +214,13 @@ def self.fetch_api_files!
214214 end
215215 end
216216
217- sig { void }
218- def self . write_names_and_aliases
217+ sig { params ( legacy_executables_fallback : T :: Boolean ) . void }
218+ def self . write_names_and_aliases ( legacy_executables_fallback : false )
219219 if Homebrew ::EnvConfig . use_internal_api?
220- Homebrew ::API ::Internal . write_formula_names_and_aliases
220+ Homebrew ::API ::Internal . write_formula_names_and_aliases ( legacy_executables_fallback : )
221221 Homebrew ::API ::Internal . write_cask_names
222222 else
223- Homebrew ::API ::Formula . write_names_and_aliases
223+ Homebrew ::API ::Formula . write_names_and_aliases ( legacy_executables_fallback : )
224224 Homebrew ::API ::Cask . write_names
225225 end
226226 end
@@ -252,6 +252,80 @@ def self.write_aliases_file!(aliases, type, regenerate:)
252252 false
253253 end
254254
255+ sig {
256+ params (
257+ formulae : T ::Hash [ String , T ::Hash [ String , T . untyped ] ] ,
258+ regenerate : T ::Boolean ,
259+ legacy_executables_fallback : T ::Boolean ,
260+ ) . returns ( T ::Boolean )
261+ }
262+ def self . write_executables_file! ( formulae , regenerate :, legacy_executables_fallback : false )
263+ executables_path = HOMEBREW_CACHE_API /"internal/executables.txt"
264+ executables_lines = formulae . filter_map do |name , hash |
265+ executables = T . cast ( hash [ "executables" ] , T . nilable ( T ::Array [ String ] ) )
266+ next if executables . blank?
267+
268+ "#{ name } :#{ executables . join ( " " ) } "
269+ end
270+ if executables_lines . empty?
271+ return false if executables_path . exist? && !executables_path . empty?
272+ return false unless legacy_executables_fallback
273+
274+ # TODO: Remove this fallback once formula JSON always includes executables.
275+ return download_executables_file_from_github_packages! ( executables_path )
276+ end
277+
278+ contents = "#{ executables_lines . sort . join ( "\n " ) } \n "
279+ if !executables_path . exist? || regenerate || executables_path . read != contents
280+ executables_path . dirname . mkpath
281+ executables_path . unlink if executables_path . exist?
282+ executables_path . write ( contents )
283+ return true
284+ end
285+
286+ false
287+ end
288+
289+ sig { params ( target : Pathname ) . returns ( T ::Boolean ) }
290+ def self . download_executables_file_from_github_packages! ( target )
291+ github_packages_url = "https://ghcr.io/v2/homebrew/command-not-found/executables"
292+ manifest_args = [
293+ "--fail" , "--location" ,
294+ "--header" , "Accept: application/vnd.oci.image.manifest.v1+json" ,
295+ "#{ github_packages_url } /manifests/latest"
296+ ]
297+ if HOMEBREW_GITHUB_PACKAGES_AUTH . present?
298+ manifest_args . insert ( -2 , "--header" , "Authorization: #{ HOMEBREW_GITHUB_PACKAGES_AUTH } " )
299+ end
300+
301+ manifest_output = Utils ::Curl . curl_output ( *manifest_args , show_error : false )
302+ return false unless manifest_output . success?
303+
304+ manifest = JSON . parse ( manifest_output . stdout )
305+ layers = T . cast ( manifest . fetch ( "layers" ) , T ::Array [ T ::Hash [ String , T . untyped ] ] )
306+ layer = layers . find do |candidate |
307+ candidate . dig ( "annotations" , "org.opencontainers.image.title" ) == target . basename . to_s
308+ end
309+ return false if layer . nil?
310+
311+ digest = T . cast ( layer [ "digest" ] , T . nilable ( String ) )
312+ return false if digest . blank?
313+
314+ download_args = [ "--fail" ]
315+ if HOMEBREW_GITHUB_PACKAGES_AUTH . present?
316+ download_args += [ "--header" , "Authorization: #{ HOMEBREW_GITHUB_PACKAGES_AUTH } " ]
317+ end
318+ download_args << "#{ github_packages_url } /blobs/#{ digest } "
319+ target . dirname . mkpath
320+ Utils ::Curl . curl_download ( *download_args , to : target , show_error : false )
321+ FileUtils . touch ( target )
322+ true
323+ rescue ErrorDuringExecution , JSON ::ParserError , KeyError , TypeError
324+ target . unlink if target . exist? && target . empty?
325+
326+ false
327+ end
328+
255329 sig {
256330 params ( json_data : T ::Hash [ String , T . untyped ] )
257331 . returns ( [ T ::Boolean , T . any ( String , T ::Array [ T . untyped ] , T ::Hash [ String , T . untyped ] ) ] )
0 commit comments