├── .github ├── .DS_Store ├── images │ ├── edge.png │ ├── chrome.png │ ├── firefox.png │ ├── safari.png │ ├── safari.webp │ ├── bofa_logo.png │ ├── edge_beta.png │ ├── edge_dev.png │ ├── chrome_beta.png │ ├── chrome_dev.png │ ├── edge_canary.png │ ├── chrome_canary.png │ ├── firefox_developer.png │ ├── firefox_nightly.png │ ├── safari_technology.png │ └── safari_technology.webp ├── FUNDING.yml ├── workflows │ └── generate_scripts.yml └── actions │ ├── generate_edge_latest.py │ ├── generate_rss_feed.py │ ├── generate_firefox_latest.py │ ├── generate_chrome_latest.py │ └── generate_readme.py ├── latest_firefox_files ├── firefox_all_version_info.yaml ├── firefox_all_version_info.json ├── firefox_latest_versions.yaml ├── firefox_all_version_info.xml ├── firefox_latest_versions.json ├── firefox_latest_versions.xml └── firefox_rss.xml ├── LICENSE ├── latest_edge_files ├── edge_latest_versions.yaml ├── edge_latest_versions.xml ├── edge_latest_versions.json └── edge_rss.xml ├── latest_chrome_files ├── chrome_latest_versions.yaml ├── chrome_latest_versions.json ├── chrome_latest_versions.xml └── chrome_rss.xml ├── latest_safari_files ├── safari_all_catalog_pkg.yaml ├── safari_all_catalog_pkg.xml ├── safari_all_catalog_pkg.json ├── safari_latest_versions.yaml ├── safari_latest_versions.xml ├── safari_latest_versions.json ├── safari_all_history.xml ├── safari_all_history.yaml └── safari_all_history.json └── README.md /.github/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cocopuff2u/BOFA/HEAD/.github/.DS_Store -------------------------------------------------------------------------------- /.github/images/edge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cocopuff2u/BOFA/HEAD/.github/images/edge.png -------------------------------------------------------------------------------- /.github/images/chrome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cocopuff2u/BOFA/HEAD/.github/images/chrome.png -------------------------------------------------------------------------------- /.github/images/firefox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cocopuff2u/BOFA/HEAD/.github/images/firefox.png -------------------------------------------------------------------------------- /.github/images/safari.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cocopuff2u/BOFA/HEAD/.github/images/safari.png -------------------------------------------------------------------------------- /.github/images/safari.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cocopuff2u/BOFA/HEAD/.github/images/safari.webp -------------------------------------------------------------------------------- /.github/images/bofa_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cocopuff2u/BOFA/HEAD/.github/images/bofa_logo.png -------------------------------------------------------------------------------- /.github/images/edge_beta.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cocopuff2u/BOFA/HEAD/.github/images/edge_beta.png -------------------------------------------------------------------------------- /.github/images/edge_dev.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cocopuff2u/BOFA/HEAD/.github/images/edge_dev.png -------------------------------------------------------------------------------- /.github/images/chrome_beta.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cocopuff2u/BOFA/HEAD/.github/images/chrome_beta.png -------------------------------------------------------------------------------- /.github/images/chrome_dev.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cocopuff2u/BOFA/HEAD/.github/images/chrome_dev.png -------------------------------------------------------------------------------- /.github/images/edge_canary.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cocopuff2u/BOFA/HEAD/.github/images/edge_canary.png -------------------------------------------------------------------------------- /.github/images/chrome_canary.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cocopuff2u/BOFA/HEAD/.github/images/chrome_canary.png -------------------------------------------------------------------------------- /.github/images/firefox_developer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cocopuff2u/BOFA/HEAD/.github/images/firefox_developer.png -------------------------------------------------------------------------------- /.github/images/firefox_nightly.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cocopuff2u/BOFA/HEAD/.github/images/firefox_nightly.png -------------------------------------------------------------------------------- /.github/images/safari_technology.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cocopuff2u/BOFA/HEAD/.github/images/safari_technology.png -------------------------------------------------------------------------------- /.github/images/safari_technology.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cocopuff2u/BOFA/HEAD/.github/images/safari_technology.webp -------------------------------------------------------------------------------- /latest_firefox_files/firefox_all_version_info.yaml: -------------------------------------------------------------------------------- 1 | last_updated: December 21, 2025 04:03 AM EST 2 | FIREFOX_AURORA: '' 3 | FIREFOX_DEVEDITION: 147.0b6 4 | FIREFOX_ESR: 140.6.0esr 5 | FIREFOX_ESR115: 115.31.0esr 6 | FIREFOX_ESR_NEXT: '' 7 | FIREFOX_NIGHTLY: 148.0a1 8 | LAST_MERGE_DATE: '2025-12-08' 9 | LAST_RELEASE_DATE: '2025-12-09' 10 | LAST_SOFTFREEZE_DATE: '2025-12-04' 11 | LAST_STRINGFREEZE_DATE: '2025-12-05' 12 | LATEST_FIREFOX_DEVEL_VERSION: 147.0b6 13 | LATEST_FIREFOX_OLDER_VERSION: 3.6.28 14 | LATEST_FIREFOX_RELEASED_DEVEL_VERSION: 147.0b6 15 | LATEST_FIREFOX_VERSION: 146.0.1 16 | NEXT_MERGE_DATE: '2026-01-12' 17 | NEXT_RELEASE_DATE: '2026-01-13' 18 | NEXT_SOFTFREEZE_DATE: '2026-01-08' 19 | NEXT_STRINGFREEZE_DATE: '2026-01-09' 20 | -------------------------------------------------------------------------------- /latest_firefox_files/firefox_all_version_info.json: -------------------------------------------------------------------------------- 1 | { 2 | "last_updated": "December 21, 2025 04:03 AM EST", 3 | "FIREFOX_AURORA": "", 4 | "FIREFOX_DEVEDITION": "147.0b6", 5 | "FIREFOX_ESR": "140.6.0esr", 6 | "FIREFOX_ESR115": "115.31.0esr", 7 | "FIREFOX_ESR_NEXT": "", 8 | "FIREFOX_NIGHTLY": "148.0a1", 9 | "LAST_MERGE_DATE": "2025-12-08", 10 | "LAST_RELEASE_DATE": "2025-12-09", 11 | "LAST_SOFTFREEZE_DATE": "2025-12-04", 12 | "LAST_STRINGFREEZE_DATE": "2025-12-05", 13 | "LATEST_FIREFOX_DEVEL_VERSION": "147.0b6", 14 | "LATEST_FIREFOX_OLDER_VERSION": "3.6.28", 15 | "LATEST_FIREFOX_RELEASED_DEVEL_VERSION": "147.0b6", 16 | "LATEST_FIREFOX_VERSION": "146.0.1", 17 | "NEXT_MERGE_DATE": "2026-01-12", 18 | "NEXT_RELEASE_DATE": "2026-01-13", 19 | "NEXT_SOFTFREEZE_DATE": "2026-01-08", 20 | "NEXT_STRINGFREEZE_DATE": "2026-01-09" 21 | } -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 12 | polar: # Replace with a single Polar username 13 | buy_me_a_coffee: cocopuff2u # Replace with a single Buy Me a Coffee username 14 | thanks_dev: # Replace with a single thanks.dev username 15 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 16 | -------------------------------------------------------------------------------- /latest_firefox_files/firefox_latest_versions.yaml: -------------------------------------------------------------------------------- 1 | last_updated: December 21, 2025 04:03 AM EST 2 | stable: 3 | version: 146.0.1 4 | release_time: December 18, 2025 12:00 AM EST 5 | download: https://download-installer.cdn.mozilla.net/pub/firefox/releases/146.0.1/mac/en-US/Firefox%20146.0.1.pkg 6 | beta: 7 | version: 147.0b6 8 | release_time: December 19, 2025 12:00 AM EST 9 | download: https://download-installer.cdn.mozilla.net/pub/firefox/releases/147.0b6/mac/en-US/Firefox%20147.0b6.pkg 10 | dev: 11 | version: 147.0b6 12 | release_time: December 19, 2025 12:00 AM EST 13 | download: https://download-installer.cdn.mozilla.net/pub/devedition/releases/147.0b6/mac/en-US/Firefox%20147.0b6.dmg 14 | esr: 15 | version: 140.6.0 16 | release_time: December 09, 2025 12:00 AM EST 17 | download: https://download-installer.cdn.mozilla.net/pub/firefox/releases/140.6.0esr/mac/en-US/Firefox%20140.6.0esr.pkg 18 | nightly: 19 | version: 148.0a1 20 | release_time: December 08, 2025 12:00 AM EST 21 | download: https://download-installer.cdn.mozilla.net/pub/firefox/nightly/latest-mozilla-central/firefox-148.0a1.en-US.mac.pkg 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Cody Keats 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /latest_edge_files/edge_latest_versions.yaml: -------------------------------------------------------------------------------- 1 | EdgeInsiderVersions: 2 | last_updated: December 21, 2025 04:03 AM EST 3 | Version: 4 | - Channel: current 5 | Date: December 20, 2025 09:55 AM EST 6 | Location: https://msedge.sf.dl.delivery.mp.microsoft.com/filestreamingservice/files/39c16afe-6ffe-42f8-aa82-4dd057fcf1d5/MicrosoftEdge-143.0.3650.96.pkg 7 | Version: 143.0.3650.96 8 | - Channel: canary 9 | Date: December 19, 2025 10:31 PM EST 10 | Location: https://msedge.sf.dl.delivery.mp.microsoft.com/filestreamingservice/files/e29231e8-d401-4b4b-aaf3-2af51cb905ba/MicrosoftEdgeCanary-145.0.3748.0.pkg 11 | Version: 145.0.3748.0 12 | - Channel: dev 13 | Date: December 17, 2025 07:59 PM EST 14 | Location: https://msedge.sf.dl.delivery.mp.microsoft.com/filestreamingservice/files/ebe973a8-55db-41ee-b197-e3d85466aa2e/MicrosoftEdgeDev-145.0.3734.2.pkg 15 | Version: 145.0.3734.2 16 | - Channel: beta 17 | Date: December 15, 2025 08:16 PM EST 18 | Location: https://msedge.sf.dl.delivery.mp.microsoft.com/filestreamingservice/files/4a250b5a-2acf-49a9-ada3-469fdc7dc046/MicrosoftEdgeBeta-144.0.3719.18.pkg 19 | Version: 144.0.3719.18 20 | -------------------------------------------------------------------------------- /latest_firefox_files/firefox_all_version_info.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | December 21, 2025 04:03 AM EST 4 | 5 | 147.0b6 6 | 140.6.0esr 7 | 115.31.0esr 8 | 9 | 148.0a1 10 | 2025-12-08 11 | 2025-12-09 12 | 2025-12-04 13 | 2025-12-05 14 | 147.0b6 15 | 3.6.28 16 | 147.0b6 17 | 146.0.1 18 | 2026-01-12 19 | 2026-01-13 20 | 2026-01-08 21 | 2026-01-09 22 | -------------------------------------------------------------------------------- /latest_firefox_files/firefox_latest_versions.json: -------------------------------------------------------------------------------- 1 | { 2 | "last_updated": "December 21, 2025 04:03 AM EST", 3 | "stable": { 4 | "version": "146.0.1", 5 | "release_time": "December 18, 2025 12:00 AM EST", 6 | "download": "https://download-installer.cdn.mozilla.net/pub/firefox/releases/146.0.1/mac/en-US/Firefox%20146.0.1.pkg" 7 | }, 8 | "beta": { 9 | "version": "147.0b6", 10 | "release_time": "December 19, 2025 12:00 AM EST", 11 | "download": "https://download-installer.cdn.mozilla.net/pub/firefox/releases/147.0b6/mac/en-US/Firefox%20147.0b6.pkg" 12 | }, 13 | "dev": { 14 | "version": "147.0b6", 15 | "release_time": "December 19, 2025 12:00 AM EST", 16 | "download": "https://download-installer.cdn.mozilla.net/pub/devedition/releases/147.0b6/mac/en-US/Firefox%20147.0b6.dmg" 17 | }, 18 | "esr": { 19 | "version": "140.6.0", 20 | "release_time": "December 09, 2025 12:00 AM EST", 21 | "download": "https://download-installer.cdn.mozilla.net/pub/firefox/releases/140.6.0esr/mac/en-US/Firefox%20140.6.0esr.pkg" 22 | }, 23 | "nightly": { 24 | "version": "148.0a1", 25 | "release_time": "December 08, 2025 12:00 AM EST", 26 | "download": "https://download-installer.cdn.mozilla.net/pub/firefox/nightly/latest-mozilla-central/firefox-148.0a1.en-US.mac.pkg" 27 | } 28 | } -------------------------------------------------------------------------------- /latest_edge_files/edge_latest_versions.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | December 21, 2025 04:03 AM EST 4 | 5 | current 6 | December 20, 2025 09:55 AM EST 7 | https://msedge.sf.dl.delivery.mp.microsoft.com/filestreamingservice/files/39c16afe-6ffe-42f8-aa82-4dd057fcf1d5/MicrosoftEdge-143.0.3650.96.pkg 8 | 143.0.3650.96 9 | 10 | 11 | canary 12 | December 19, 2025 10:31 PM EST 13 | https://msedge.sf.dl.delivery.mp.microsoft.com/filestreamingservice/files/e29231e8-d401-4b4b-aaf3-2af51cb905ba/MicrosoftEdgeCanary-145.0.3748.0.pkg 14 | 145.0.3748.0 15 | 16 | 17 | dev 18 | December 17, 2025 07:59 PM EST 19 | https://msedge.sf.dl.delivery.mp.microsoft.com/filestreamingservice/files/ebe973a8-55db-41ee-b197-e3d85466aa2e/MicrosoftEdgeDev-145.0.3734.2.pkg 20 | 145.0.3734.2 21 | 22 | 23 | beta 24 | December 15, 2025 08:16 PM EST 25 | https://msedge.sf.dl.delivery.mp.microsoft.com/filestreamingservice/files/4a250b5a-2acf-49a9-ada3-469fdc7dc046/MicrosoftEdgeBeta-144.0.3719.18.pkg 26 | 144.0.3719.18 27 | 28 | -------------------------------------------------------------------------------- /latest_firefox_files/firefox_latest_versions.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | December 21, 2025 04:03 AM EST 4 | 5 | 146.0.1 6 | December 18, 2025 12:00 AM EST 7 | https://download-installer.cdn.mozilla.net/pub/firefox/releases/146.0.1/mac/en-US/Firefox%20146.0.1.pkg 8 | 9 | 10 | 147.0b6 11 | December 19, 2025 12:00 AM EST 12 | https://download-installer.cdn.mozilla.net/pub/firefox/releases/147.0b6/mac/en-US/Firefox%20147.0b6.pkg 13 | 14 | 15 | 147.0b6 16 | December 19, 2025 12:00 AM EST 17 | https://download-installer.cdn.mozilla.net/pub/devedition/releases/147.0b6/mac/en-US/Firefox%20147.0b6.dmg 18 | 19 | 20 | 140.6.0 21 | December 09, 2025 12:00 AM EST 22 | https://download-installer.cdn.mozilla.net/pub/firefox/releases/140.6.0esr/mac/en-US/Firefox%20140.6.0esr.pkg 23 | 24 | 25 | 148.0a1 26 | December 08, 2025 12:00 AM EST 27 | https://download-installer.cdn.mozilla.net/pub/firefox/nightly/latest-mozilla-central/firefox-148.0a1.en-US.mac.pkg 28 | 29 | -------------------------------------------------------------------------------- /latest_chrome_files/chrome_latest_versions.yaml: -------------------------------------------------------------------------------- 1 | beta: 2 | download_link: https://dl.google.com/chrome/mac/beta/accept_tos%3Dhttps%253A%252F%252Fwww.google.com%252Fintl%252Fen_ph%252Fchrome%252Fterms%252F%26_and_accept_tos%3Dhttps%253A%252F%252Fpolicies.google.com%252Fterms/googlechrome.pkg 3 | time: December 17, 2025 02:00 PM EST 4 | version: 144.0.7559.31 5 | canary: 6 | download_link: https://dl.google.com/chrome/mac/universal/canary/googlechromecanary.dmg 7 | time: December 21, 2025 08:53 AM EST 8 | version: 145.0.7590.0 9 | canary_asan: 10 | download_link: https://dl.google.com/chrome/mac/universal/canary/googlechromecanary.dmg 11 | time: December 20, 2025 10:33 PM EST 12 | version: 145.0.7589.1 13 | dev: 14 | download_link: https://dl.google.com/chrome/mac/universal/dev/googlechromedev.dmg 15 | time: December 11, 2025 07:26 PM EST 16 | version: 145.0.7572.2 17 | extended: 18 | download_link: https://dl.google.com/chrome/mac/stable/accept_tos%3Dhttps%253A%252F%252Fwww.google.com%252Fintl%252Fen_ph%252Fchrome%252Fterms%252F%26_and_accept_tos%3Dhttps%253A%252F%252Fpolicies.google.com%252Fterms/googlechrome.pkg 19 | time: December 18, 2025 07:56 PM EST 20 | version: 142.0.7444.243 21 | last_updated: December 21, 2025 04:03 AM EST 22 | stable: 23 | download_link: https://dl.google.com/chrome/mac/stable/accept_tos%3Dhttps%253A%252F%252Fwww.google.com%252Fintl%252Fen_ph%252Fchrome%252Fterms%252F%26_and_accept_tos%3Dhttps%253A%252F%252Fpolicies.google.com%252Fterms/googlechrome.pkg 24 | time: December 18, 2025 08:08 PM EST 25 | version: 143.0.7499.170 26 | -------------------------------------------------------------------------------- /latest_edge_files/edge_latest_versions.json: -------------------------------------------------------------------------------- 1 | { 2 | "EdgeInsiderVersions": { 3 | "last_updated": "December 21, 2025 04:03 AM EST", 4 | "Version": [ 5 | { 6 | "Channel": "current", 7 | "Date": "December 20, 2025 09:55 AM EST", 8 | "Location": "https://msedge.sf.dl.delivery.mp.microsoft.com/filestreamingservice/files/39c16afe-6ffe-42f8-aa82-4dd057fcf1d5/MicrosoftEdge-143.0.3650.96.pkg", 9 | "Version": "143.0.3650.96" 10 | }, 11 | { 12 | "Channel": "canary", 13 | "Date": "December 19, 2025 10:31 PM EST", 14 | "Location": "https://msedge.sf.dl.delivery.mp.microsoft.com/filestreamingservice/files/e29231e8-d401-4b4b-aaf3-2af51cb905ba/MicrosoftEdgeCanary-145.0.3748.0.pkg", 15 | "Version": "145.0.3748.0" 16 | }, 17 | { 18 | "Channel": "dev", 19 | "Date": "December 17, 2025 07:59 PM EST", 20 | "Location": "https://msedge.sf.dl.delivery.mp.microsoft.com/filestreamingservice/files/ebe973a8-55db-41ee-b197-e3d85466aa2e/MicrosoftEdgeDev-145.0.3734.2.pkg", 21 | "Version": "145.0.3734.2" 22 | }, 23 | { 24 | "Channel": "beta", 25 | "Date": "December 15, 2025 08:16 PM EST", 26 | "Location": "https://msedge.sf.dl.delivery.mp.microsoft.com/filestreamingservice/files/4a250b5a-2acf-49a9-ada3-469fdc7dc046/MicrosoftEdgeBeta-144.0.3719.18.pkg", 27 | "Version": "144.0.3719.18" 28 | } 29 | ] 30 | } 31 | } -------------------------------------------------------------------------------- /latest_safari_files/safari_all_catalog_pkg.yaml: -------------------------------------------------------------------------------- 1 | safari_versions: 2 | last_updated: December 21, 2025 04:03 AM EST 3 | release: 4 | - os: BigSur 5 | version: 16.6.1 6 | URL: https://swcdn.apple.com/content/downloads/47/04/042-27539-A_JOWCKWG03T/q1askvrrids8ykmi9ok73aqmj05kzskcya/Safari16.6.1BigSurAuto.pkg 7 | Size: 129.20 MB 8 | PostDate: October 05, 2023 09:21 PM 9 | - os: Monterey 10 | version: '17.6' 11 | URL: https://swcdn.apple.com/content/downloads/19/54/062-47822-A_BHCA3624RA/oixd7i5b8y3g67u6x0upt45m0u2xotc4eh/Safari17.6MontereyAuto.pkg 12 | Size: 148.80 MB 13 | PostDate: August 15, 2024 07:58 PM 14 | - os: Ventura 15 | version: '18.6' 16 | URL: https://swcdn.apple.com/content/downloads/18/08/093-01291-A_8PAQLVAPMY/q858ihftlht01sdwcf9oqqzkuvm12laqlx/Safari18.6VenturaAuto.pkg 17 | Size: 180.39 MB 18 | PostDate: August 05, 2025 06:04 PM 19 | - os: Sonoma 20 | version: '18.6' 21 | URL: https://swcdn.apple.com/content/downloads/52/08/093-01292-A_RB1WZOT879/679v9stbm7s0ccdcebzsfju4kp761uzgl7/Safari18.6SonomaAuto.pkg 22 | Size: 184.83 MB 23 | PostDate: August 05, 2025 06:04 PM 24 | - os: Sonoma 25 | version: '26.2' 26 | URL: https://swcdn.apple.com/content/downloads/04/19/093-20315-A_TYCM4L84CV/im7455ga9jdpnb48ecqg38ii8n5m9yo3e8/Safari26.2SonomaAuto.pkg 27 | Size: 210.51 MB 28 | PostDate: December 12, 2025 07:03 PM 29 | - os: Sequoia 30 | version: '26.2' 31 | URL: https://swcdn.apple.com/content/downloads/45/25/093-20308-A_XCI9S5B5ZA/8qijyjx97dkmrkpcpgft9bpzp93sakg8no/Safari26.2SequoiaAuto.pkg 32 | Size: 219.48 MB 33 | PostDate: December 12, 2025 07:04 PM 34 | -------------------------------------------------------------------------------- /latest_chrome_files/chrome_latest_versions.json: -------------------------------------------------------------------------------- 1 | { 2 | "last_updated": "December 21, 2025 04:03 AM EST", 3 | "stable": { 4 | "version": "143.0.7499.170", 5 | "time": "December 18, 2025 08:08 PM EST", 6 | "download_link": "https://dl.google.com/chrome/mac/stable/accept_tos%3Dhttps%253A%252F%252Fwww.google.com%252Fintl%252Fen_ph%252Fchrome%252Fterms%252F%26_and_accept_tos%3Dhttps%253A%252F%252Fpolicies.google.com%252Fterms/googlechrome.pkg" 7 | }, 8 | "extended": { 9 | "version": "142.0.7444.243", 10 | "time": "December 18, 2025 07:56 PM EST", 11 | "download_link": "https://dl.google.com/chrome/mac/stable/accept_tos%3Dhttps%253A%252F%252Fwww.google.com%252Fintl%252Fen_ph%252Fchrome%252Fterms%252F%26_and_accept_tos%3Dhttps%253A%252F%252Fpolicies.google.com%252Fterms/googlechrome.pkg" 12 | }, 13 | "beta": { 14 | "version": "144.0.7559.31", 15 | "time": "December 17, 2025 02:00 PM EST", 16 | "download_link": "https://dl.google.com/chrome/mac/beta/accept_tos%3Dhttps%253A%252F%252Fwww.google.com%252Fintl%252Fen_ph%252Fchrome%252Fterms%252F%26_and_accept_tos%3Dhttps%253A%252F%252Fpolicies.google.com%252Fterms/googlechrome.pkg" 17 | }, 18 | "dev": { 19 | "version": "145.0.7572.2", 20 | "time": "December 11, 2025 07:26 PM EST", 21 | "download_link": "https://dl.google.com/chrome/mac/universal/dev/googlechromedev.dmg" 22 | }, 23 | "canary": { 24 | "version": "145.0.7590.0", 25 | "time": "December 21, 2025 08:53 AM EST", 26 | "download_link": "https://dl.google.com/chrome/mac/universal/canary/googlechromecanary.dmg" 27 | }, 28 | "canary_asan": { 29 | "version": "145.0.7589.1", 30 | "time": "December 20, 2025 10:33 PM EST", 31 | "download_link": "https://dl.google.com/chrome/mac/universal/canary/googlechromecanary.dmg" 32 | } 33 | } -------------------------------------------------------------------------------- /.github/workflows/generate_scripts.yml: -------------------------------------------------------------------------------- 1 | name: Run Generate Scripts 2 | 3 | on: 4 | schedule: 5 | - cron: '0 */1 * * *' # Runs every 1 hours 6 | workflow_dispatch: 7 | 8 | jobs: 9 | run-generate-scripts: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - name: Checkout repository 14 | uses: actions/checkout@v2 15 | 16 | - name: Set time zone 17 | run: | 18 | sudo timedatectl set-timezone America/New_York 19 | 20 | - name: Set up Python 21 | uses: actions/setup-python@v2 22 | with: 23 | python-version: '3.x' 24 | 25 | - name: Install dependencies 26 | run: | 27 | python -m pip install --upgrade pip 28 | pip install requests beautifulsoup4 lxml pandas pyyaml pytz 29 | 30 | - name: Run generate_safari_latest.py 31 | run: python .github/actions/generate_safari_latest.py 32 | 33 | - name: Run generate_firefox_latest.py 34 | run: python .github/actions/generate_firefox_latest.py 35 | 36 | - name: Run generate_edge_latest.py 37 | run: python .github/actions/generate_edge_latest.py 38 | 39 | - name: Run generate_chrome_latest.py 40 | run: python .github/actions/generate_chrome_latest.py 41 | 42 | - name: Run generate_readme.py 43 | run: python .github/actions/generate_readme.py 44 | 45 | - name: Run generate_rss_feed.py 46 | run: python .github/actions/generate_rss_feed.py 47 | 48 | - name: Commit changes 49 | continue-on-error: true 50 | env: 51 | TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }} 52 | run: | 53 | git config --global user.name 'github-actions[bot]' 54 | git config --global user.email 'github-actions[bot]@users.noreply.github.com' 55 | git add . 56 | git commit -m "Automated update by GitHub Actions" || echo "No changes to commit" 57 | git push https://github-actions[bot]:$TOKEN@github.com/cocopuff2u/BOFA.git 58 | -------------------------------------------------------------------------------- /latest_chrome_files/chrome_latest_versions.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | December 21, 2025 04:03 AM EST 4 | 5 | 143.0.7499.170 6 | December 18, 2025 08:08 PM EST 7 | https://dl.google.com/chrome/mac/stable/accept_tos%3Dhttps%253A%252F%252Fwww.google.com%252Fintl%252Fen_ph%252Fchrome%252Fterms%252F%26_and_accept_tos%3Dhttps%253A%252F%252Fpolicies.google.com%252Fterms/googlechrome.pkg 8 | 9 | 10 | 142.0.7444.243 11 | December 18, 2025 07:56 PM EST 12 | https://dl.google.com/chrome/mac/stable/accept_tos%3Dhttps%253A%252F%252Fwww.google.com%252Fintl%252Fen_ph%252Fchrome%252Fterms%252F%26_and_accept_tos%3Dhttps%253A%252F%252Fpolicies.google.com%252Fterms/googlechrome.pkg 13 | 14 | 15 | 144.0.7559.31 16 | December 17, 2025 02:00 PM EST 17 | https://dl.google.com/chrome/mac/beta/accept_tos%3Dhttps%253A%252F%252Fwww.google.com%252Fintl%252Fen_ph%252Fchrome%252Fterms%252F%26_and_accept_tos%3Dhttps%253A%252F%252Fpolicies.google.com%252Fterms/googlechrome.pkg 18 | 19 | 20 | 145.0.7572.2 21 | December 11, 2025 07:26 PM EST 22 | https://dl.google.com/chrome/mac/universal/dev/googlechromedev.dmg 23 | 24 | 25 | 145.0.7590.0 26 | December 21, 2025 08:53 AM EST 27 | https://dl.google.com/chrome/mac/universal/canary/googlechromecanary.dmg 28 | 29 | 30 | 145.0.7589.1 31 | December 20, 2025 10:33 PM EST 32 | https://dl.google.com/chrome/mac/universal/canary/googlechromecanary.dmg 33 | 34 | 35 | -------------------------------------------------------------------------------- /latest_safari_files/safari_all_catalog_pkg.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | December 21, 2025 04:03 AM EST 4 | 5 | BigSur 6 | 16.6.1 7 | https://swcdn.apple.com/content/downloads/47/04/042-27539-A_JOWCKWG03T/q1askvrrids8ykmi9ok73aqmj05kzskcya/Safari16.6.1BigSurAuto.pkg 8 | 129.20 MB 9 | October 05, 2023 09:21 PM 10 | 11 | 12 | Monterey 13 | 17.6 14 | https://swcdn.apple.com/content/downloads/19/54/062-47822-A_BHCA3624RA/oixd7i5b8y3g67u6x0upt45m0u2xotc4eh/Safari17.6MontereyAuto.pkg 15 | 148.80 MB 16 | August 15, 2024 07:58 PM 17 | 18 | 19 | Ventura 20 | 18.6 21 | https://swcdn.apple.com/content/downloads/18/08/093-01291-A_8PAQLVAPMY/q858ihftlht01sdwcf9oqqzkuvm12laqlx/Safari18.6VenturaAuto.pkg 22 | 180.39 MB 23 | August 05, 2025 06:04 PM 24 | 25 | 26 | Sonoma 27 | 18.6 28 | https://swcdn.apple.com/content/downloads/52/08/093-01292-A_RB1WZOT879/679v9stbm7s0ccdcebzsfju4kp761uzgl7/Safari18.6SonomaAuto.pkg 29 | 184.83 MB 30 | August 05, 2025 06:04 PM 31 | 32 | 33 | Sonoma 34 | 26.2 35 | https://swcdn.apple.com/content/downloads/04/19/093-20315-A_TYCM4L84CV/im7455ga9jdpnb48ecqg38ii8n5m9yo3e8/Safari26.2SonomaAuto.pkg 36 | 210.51 MB 37 | December 12, 2025 07:03 PM 38 | 39 | 40 | Sequoia 41 | 26.2 42 | https://swcdn.apple.com/content/downloads/45/25/093-20308-A_XCI9S5B5ZA/8qijyjx97dkmrkpcpgft9bpzp93sakg8no/Safari26.2SequoiaAuto.pkg 43 | 219.48 MB 44 | December 12, 2025 07:04 PM 45 | 46 | -------------------------------------------------------------------------------- /latest_safari_files/safari_all_catalog_pkg.json: -------------------------------------------------------------------------------- 1 | { 2 | "safari_versions": { 3 | "last_updated": "December 21, 2025 04:03 AM EST", 4 | "release": [ 5 | { 6 | "os": "BigSur", 7 | "version": "16.6.1", 8 | "URL": "https://swcdn.apple.com/content/downloads/47/04/042-27539-A_JOWCKWG03T/q1askvrrids8ykmi9ok73aqmj05kzskcya/Safari16.6.1BigSurAuto.pkg", 9 | "Size": "129.20 MB", 10 | "PostDate": "October 05, 2023 09:21 PM" 11 | }, 12 | { 13 | "os": "Monterey", 14 | "version": "17.6", 15 | "URL": "https://swcdn.apple.com/content/downloads/19/54/062-47822-A_BHCA3624RA/oixd7i5b8y3g67u6x0upt45m0u2xotc4eh/Safari17.6MontereyAuto.pkg", 16 | "Size": "148.80 MB", 17 | "PostDate": "August 15, 2024 07:58 PM" 18 | }, 19 | { 20 | "os": "Ventura", 21 | "version": "18.6", 22 | "URL": "https://swcdn.apple.com/content/downloads/18/08/093-01291-A_8PAQLVAPMY/q858ihftlht01sdwcf9oqqzkuvm12laqlx/Safari18.6VenturaAuto.pkg", 23 | "Size": "180.39 MB", 24 | "PostDate": "August 05, 2025 06:04 PM" 25 | }, 26 | { 27 | "os": "Sonoma", 28 | "version": "18.6", 29 | "URL": "https://swcdn.apple.com/content/downloads/52/08/093-01292-A_RB1WZOT879/679v9stbm7s0ccdcebzsfju4kp761uzgl7/Safari18.6SonomaAuto.pkg", 30 | "Size": "184.83 MB", 31 | "PostDate": "August 05, 2025 06:04 PM" 32 | }, 33 | { 34 | "os": "Sonoma", 35 | "version": "26.2", 36 | "URL": "https://swcdn.apple.com/content/downloads/04/19/093-20315-A_TYCM4L84CV/im7455ga9jdpnb48ecqg38ii8n5m9yo3e8/Safari26.2SonomaAuto.pkg", 37 | "Size": "210.51 MB", 38 | "PostDate": "December 12, 2025 07:03 PM" 39 | }, 40 | { 41 | "os": "Sequoia", 42 | "version": "26.2", 43 | "URL": "https://swcdn.apple.com/content/downloads/45/25/093-20308-A_XCI9S5B5ZA/8qijyjx97dkmrkpcpgft9bpzp93sakg8no/Safari26.2SequoiaAuto.pkg", 44 | "Size": "219.48 MB", 45 | "PostDate": "December 12, 2025 07:04 PM" 46 | } 47 | ] 48 | } 49 | } -------------------------------------------------------------------------------- /latest_safari_files/safari_latest_versions.yaml: -------------------------------------------------------------------------------- 1 | safari_versions: 2 | last_updated: December 21, 2025 04:03 AM EST 3 | Safari_Technology_Preview: 4 | - macos: Tahoe 5 | version: '234' 6 | PostDate: December 19, 2025 7 | URL: https://secure-appldnld.apple.com/STP/089-90716-20251219-01965ddc-c14d-467b-be0c-fcd0ac21afe6/SafariTechnologyPreview.dmg 8 | ReleaseNotes: https://developer.apple.com/documentation/safari-technology-preview-release-notes 9 | - macos: Sequoia 10 | version: '234' 11 | PostDate: December 19, 2025 12 | URL: https://secure-appldnld.apple.com/STP/089-91901-20251219-292738a2-5cb9-4a4f-957c-b4056231d091/SafariTechnologyPreview.dmg 13 | ReleaseNotes: https://developer.apple.com/documentation/safari-technology-preview-release-notes 14 | release: 15 | - major_version: '26' 16 | full_version: 26.3 (20623.2.2) 17 | released: December 15, 2025 18 | release_notes: https://developer.apple.com/documentation/safari-release-notes/safari-26_3-release-notes 19 | - major_version: '18' 20 | full_version: 18.6 (20621.3.11) 21 | released: July 29, 2025 22 | release_notes: https://developer.apple.com/documentation/safari-release-notes/safari-18_6-release-notes 23 | - major_version: '17' 24 | full_version: 17.6 (19618.3.11) 25 | released: July 29, 2024 26 | release_notes: https://developer.apple.com/documentation/safari-release-notes/safari-17_6-release-notes 27 | - major_version: '16' 28 | full_version: 16.6 (18615.3.12) 29 | released: July 24, 2023 30 | release_notes: https://developer.apple.com/documentation/safari-release-notes/safari-16_6-release-notes 31 | - major_version: '15' 32 | full_version: 15.6 (17613.3.9) 33 | released: July 20, 2022 34 | release_notes: https://developer.apple.com/documentation/safari-release-notes/safari-15_6-release-notes 35 | - major_version: '14' 36 | full_version: 14.1 (16611.1.21) 37 | released: April 26, 2021 38 | release_notes: https://developer.apple.com/documentation/safari-release-notes/safari-14_1-release-notes 39 | - major_version: '13' 40 | full_version: 13.1 (15609.1.20) 41 | released: March 24, 2020 42 | release_notes: https://developer.apple.com/documentation/safari-release-notes/safari-13_1-release_notes 43 | - major_version: '12' 44 | full_version: 12.1 (14607.1.40) 45 | released: March 25, 2019 46 | release_notes: https://developer.apple.com/documentation/safari-release-notes/safari-12_1-release-notes 47 | -------------------------------------------------------------------------------- /latest_safari_files/safari_latest_versions.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | December 21, 2025 04:03 AM EST 4 | 5 | Tahoe 6 | 234 7 | December 19, 2025 8 | https://secure-appldnld.apple.com/STP/089-90716-20251219-01965ddc-c14d-467b-be0c-fcd0ac21afe6/SafariTechnologyPreview.dmg 9 | https://developer.apple.com/documentation/safari-technology-preview-release-notes 10 | 11 | 12 | Sequoia 13 | 234 14 | December 19, 2025 15 | https://secure-appldnld.apple.com/STP/089-91901-20251219-292738a2-5cb9-4a4f-957c-b4056231d091/SafariTechnologyPreview.dmg 16 | https://developer.apple.com/documentation/safari-technology-preview-release-notes 17 | 18 | 19 | 26 20 | 26.3 (20623.2.2) 21 | December 15, 2025 22 | https://developer.apple.com/documentation/safari-release-notes/safari-26_3-release-notes 23 | 24 | 25 | 18 26 | 18.6 (20621.3.11) 27 | July 29, 2025 28 | https://developer.apple.com/documentation/safari-release-notes/safari-18_6-release-notes 29 | 30 | 31 | 17 32 | 17.6 (19618.3.11) 33 | July 29, 2024 34 | https://developer.apple.com/documentation/safari-release-notes/safari-17_6-release-notes 35 | 36 | 37 | 16 38 | 16.6 (18615.3.12) 39 | July 24, 2023 40 | https://developer.apple.com/documentation/safari-release-notes/safari-16_6-release-notes 41 | 42 | 43 | 15 44 | 15.6 (17613.3.9) 45 | July 20, 2022 46 | https://developer.apple.com/documentation/safari-release-notes/safari-15_6-release-notes 47 | 48 | 49 | 14 50 | 14.1 (16611.1.21) 51 | April 26, 2021 52 | https://developer.apple.com/documentation/safari-release-notes/safari-14_1-release-notes 53 | 54 | 55 | 13 56 | 13.1 (15609.1.20) 57 | March 24, 2020 58 | https://developer.apple.com/documentation/safari-release-notes/safari-13_1-release_notes 59 | 60 | 61 | 12 62 | 12.1 (14607.1.40) 63 | March 25, 2019 64 | https://developer.apple.com/documentation/safari-release-notes/safari-12_1-release-notes 65 | 66 | -------------------------------------------------------------------------------- /latest_safari_files/safari_latest_versions.json: -------------------------------------------------------------------------------- 1 | { 2 | "safari_versions": { 3 | "last_updated": "December 21, 2025 04:03 AM EST", 4 | "Safari_Technology_Preview": [ 5 | { 6 | "macos": "Tahoe", 7 | "version": "234", 8 | "PostDate": "December 19, 2025", 9 | "URL": "https://secure-appldnld.apple.com/STP/089-90716-20251219-01965ddc-c14d-467b-be0c-fcd0ac21afe6/SafariTechnologyPreview.dmg", 10 | "ReleaseNotes": "https://developer.apple.com/documentation/safari-technology-preview-release-notes" 11 | }, 12 | { 13 | "macos": "Sequoia", 14 | "version": "234", 15 | "PostDate": "December 19, 2025", 16 | "URL": "https://secure-appldnld.apple.com/STP/089-91901-20251219-292738a2-5cb9-4a4f-957c-b4056231d091/SafariTechnologyPreview.dmg", 17 | "ReleaseNotes": "https://developer.apple.com/documentation/safari-technology-preview-release-notes" 18 | } 19 | ], 20 | "release": [ 21 | { 22 | "major_version": "26", 23 | "full_version": "26.3 (20623.2.2)", 24 | "released": "December 15, 2025", 25 | "release_notes": "https://developer.apple.com/documentation/safari-release-notes/safari-26_3-release-notes" 26 | }, 27 | { 28 | "major_version": "18", 29 | "full_version": "18.6 (20621.3.11)", 30 | "released": "July 29, 2025", 31 | "release_notes": "https://developer.apple.com/documentation/safari-release-notes/safari-18_6-release-notes" 32 | }, 33 | { 34 | "major_version": "17", 35 | "full_version": "17.6 (19618.3.11)", 36 | "released": "July 29, 2024", 37 | "release_notes": "https://developer.apple.com/documentation/safari-release-notes/safari-17_6-release-notes" 38 | }, 39 | { 40 | "major_version": "16", 41 | "full_version": "16.6 (18615.3.12)", 42 | "released": "July 24, 2023", 43 | "release_notes": "https://developer.apple.com/documentation/safari-release-notes/safari-16_6-release-notes" 44 | }, 45 | { 46 | "major_version": "15", 47 | "full_version": "15.6 (17613.3.9)", 48 | "released": "July 20, 2022", 49 | "release_notes": "https://developer.apple.com/documentation/safari-release-notes/safari-15_6-release-notes" 50 | }, 51 | { 52 | "major_version": "14", 53 | "full_version": "14.1 (16611.1.21)", 54 | "released": "April 26, 2021", 55 | "release_notes": "https://developer.apple.com/documentation/safari-release-notes/safari-14_1-release-notes" 56 | }, 57 | { 58 | "major_version": "13", 59 | "full_version": "13.1 (15609.1.20)", 60 | "released": "March 24, 2020", 61 | "release_notes": "https://developer.apple.com/documentation/safari-release-notes/safari-13_1-release_notes" 62 | }, 63 | { 64 | "major_version": "12", 65 | "full_version": "12.1 (14607.1.40)", 66 | "released": "March 25, 2019", 67 | "release_notes": "https://developer.apple.com/documentation/safari-release-notes/safari-12_1-release-notes" 68 | } 69 | ] 70 | } 71 | } -------------------------------------------------------------------------------- /latest_firefox_files/firefox_rss.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | BOFA - Firefox RSS Feed 5 | https://bofa.cocolabs.dev/ 6 | Mozilla Firefox for Mac 7 | http://www.rssboard.org/rss-specification 8 | 9 | en-US 10 | 60 11 | Thu, 18 Dec 2025 00:00:00 +0000 12 | 13 | https://bofa.cocolabs.dev/images/bofa_logo.png 14 | BOFA - Firefox RSS Feed 15 | https://bofa.cocolabs.dev/ 16 | 17 | 18 | New Firefox Release 19 | https://download-installer.cdn.mozilla.net/pub/firefox/releases/146.0.1/mac/en-US/Firefox%20146.0.1.pkg 20 | <br>Version: 146.0.1<br>Release Notes: <a href="https://www.mozilla.org/en-US/firefox/notes/">Release Notes</a> 21 | Thu, 18 Dec 2025 00:00:00 +0000 22 | https://download-installer.cdn.mozilla.net/pub/firefox/releases/146.0.1/mac/en-US/Firefox%20146.0.1.pkg 23 | 24 | 25 | New Firefox Release 26 | https://download-installer.cdn.mozilla.net/pub/firefox/releases/146.0/mac/en-US/Firefox%20146.0.pkg 27 | <br>Version: 146.0<br>Release Notes: <a href="https://www.mozilla.org/en-US/firefox/notes/">Release Notes</a> 28 | Tue, 09 Dec 2025 00:00:00 +0000 29 | https://download-installer.cdn.mozilla.net/pub/firefox/releases/146.0/mac/en-US/Firefox%20146.0.pkg 30 | 31 | 32 | New Firefox Release 33 | https://download-installer.cdn.mozilla.net/pub/firefox/releases/145.0.2/mac/en-US/Firefox%20145.0.2.pkg 34 | <br>Version: 145.0.2<br>Release Notes: <a href="https://www.mozilla.org/en-US/firefox/notes/">Release Notes</a> 35 | Tue, 25 Nov 2025 00:00:00 +0000 36 | https://download-installer.cdn.mozilla.net/pub/firefox/releases/145.0.2/mac/en-US/Firefox%20145.0.2.pkg 37 | 38 | 39 | New Firefox Release 40 | https://download-installer.cdn.mozilla.net/pub/firefox/releases/145.0.1/mac/en-US/Firefox%20145.0.1.pkg 41 | <br>Version: 145.0.1<br>Release Notes: <a href="https://www.mozilla.org/en-US/firefox/notes/">Release Notes</a> 42 | Tue, 18 Nov 2025 00:00:00 +0000 43 | https://download-installer.cdn.mozilla.net/pub/firefox/releases/145.0.1/mac/en-US/Firefox%20145.0.1.pkg 44 | 45 | 46 | New Firefox Release 47 | https://download-installer.cdn.mozilla.net/pub/firefox/releases/145.0/mac/en-US/Firefox%20145.0.pkg 48 | <br>Version: 145.0<br>Release Notes: <a href="https://www.mozilla.org/en-US/firefox/notes/">Release Notes</a> 49 | Tue, 11 Nov 2025 00:00:00 +0000 50 | https://download-installer.cdn.mozilla.net/pub/firefox/releases/145.0/mac/en-US/Firefox%20145.0.pkg 51 | 52 | 53 | New Firefox Release 54 | https://download-installer.cdn.mozilla.net/pub/firefox/releases/144.0.2/mac/en-US/Firefox%20144.0.2.pkg 55 | <br>Version: 144.0.2<br>Release Notes: <a href="https://www.mozilla.org/en-US/firefox/notes/">Release Notes</a> 56 | Tue, 28 Oct 2025 00:00:00 +0000 57 | https://download-installer.cdn.mozilla.net/pub/firefox/releases/144.0.2/mac/en-US/Firefox%20144.0.2.pkg 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /latest_edge_files/edge_rss.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | BOFA - Edge RSS Feed 5 | https://bofa.cocolabs.dev/ 6 | Microsoft Edge for Mac 7 | http://www.rssboard.org/rss-specification 8 | 9 | en-US 10 | 60 11 | Sat, 20 Dec 2025 09:55:00 +0000 12 | 13 | https://bofa.cocolabs.dev/images/bofa_logo.png 14 | BOFA - Edge RSS Feed 15 | https://bofa.cocolabs.dev/ 16 | 17 | 18 | New Edge Release 19 | https://msedge.sf.dl.delivery.mp.microsoft.com/filestreamingservice/files/39c16afe-6ffe-42f8-aa82-4dd057fcf1d5/MicrosoftEdge-143.0.3650.96.pkg 20 | <br>Version: 143.0.3650.96<br>Release Notes: <a href="https://learn.microsoft.com/en-us/deployedge/microsoft-edge-relnote-stable-channel">Release Notes</a> 21 | Thu, 18 Dec 2025 20:24:00 +0000 22 | https://msedge.sf.dl.delivery.mp.microsoft.com/filestreamingservice/files/39c16afe-6ffe-42f8-aa82-4dd057fcf1d5/MicrosoftEdge-143.0.3650.96.pkg 23 | 24 | 25 | New Edge Release 26 | https://msedge.sf.dl.delivery.mp.microsoft.com/filestreamingservice/files/a266e4b4-62a7-4b4e-91c2-ec8be69d5031/MicrosoftEdge-143.0.3650.80.pkg 27 | <br>Version: 143.0.3650.80<br>Release Notes: <a href="https://learn.microsoft.com/en-us/deployedge/microsoft-edge-relnote-stable-channel">Release Notes</a> 28 | Thu, 11 Dec 2025 17:58:00 +0000 29 | https://msedge.sf.dl.delivery.mp.microsoft.com/filestreamingservice/files/a266e4b4-62a7-4b4e-91c2-ec8be69d5031/MicrosoftEdge-143.0.3650.80.pkg 30 | 31 | 32 | New Edge Release 33 | https://msedge.sf.dl.delivery.mp.microsoft.com/filestreamingservice/files/152afd5e-2d99-431f-8bde-471a5e352088/MicrosoftEdge-143.0.3650.75.pkg 34 | <br>Version: 143.0.3650.75<br>Release Notes: <a href="https://learn.microsoft.com/en-us/deployedge/microsoft-edge-relnote-stable-channel">Release Notes</a> 35 | Tue, 09 Dec 2025 14:51:00 +0000 36 | https://msedge.sf.dl.delivery.mp.microsoft.com/filestreamingservice/files/152afd5e-2d99-431f-8bde-471a5e352088/MicrosoftEdge-143.0.3650.75.pkg 37 | 38 | 39 | New Edge Release 40 | https://msedge.sf.dl.delivery.mp.microsoft.com/filestreamingservice/files/d45a1a8b-e452-454b-8e78-5f6103c85ea4/MicrosoftEdge-143.0.3650.66.pkg 41 | <br>Version: 143.0.3650.66<br>Release Notes: <a href="https://learn.microsoft.com/en-us/deployedge/microsoft-edge-relnote-stable-channel">Release Notes</a> 42 | Thu, 04 Dec 2025 23:40:00 +0000 43 | https://msedge.sf.dl.delivery.mp.microsoft.com/filestreamingservice/files/d45a1a8b-e452-454b-8e78-5f6103c85ea4/MicrosoftEdge-143.0.3650.66.pkg 44 | 45 | 46 | New Edge Release 47 | https://msedge.sf.dl.delivery.mp.microsoft.com/filestreamingservice/files/755247f9-4b1e-4506-b8c1-838f84a56aef/MicrosoftEdge-142.0.3595.94.pkg 48 | <br>Version: 142.0.3595.94<br>Release Notes: <a href="https://learn.microsoft.com/en-us/deployedge/microsoft-edge-relnote-stable-channel">Release Notes</a> 49 | Thu, 20 Nov 2025 19:06:00 +0000 50 | https://msedge.sf.dl.delivery.mp.microsoft.com/filestreamingservice/files/755247f9-4b1e-4506-b8c1-838f84a56aef/MicrosoftEdge-142.0.3595.94.pkg 51 | 52 | 53 | New Edge Release 54 | https://msedge.sf.dl.delivery.mp.microsoft.com/filestreamingservice/files/997fd156-7117-4719-91bb-749ed9848827/MicrosoftEdge-142.0.3595.90.pkg 55 | <br>Version: 142.0.3595.90<br>Release Notes: <a href="https://learn.microsoft.com/en-us/deployedge/microsoft-edge-relnote-stable-channel">Release Notes</a> 56 | Tue, 18 Nov 2025 17:07:00 +0000 57 | https://msedge.sf.dl.delivery.mp.microsoft.com/filestreamingservice/files/997fd156-7117-4719-91bb-749ed9848827/MicrosoftEdge-142.0.3595.90.pkg 58 | 59 | 60 | New Edge Release 61 | https://msedge.sf.dl.delivery.mp.microsoft.com/filestreamingservice/files/f46834c6-c739-468a-a1ca-90453d0743e5/MicrosoftEdge-142.0.3595.80.pkg 62 | <br>Version: 142.0.3595.80<br>Release Notes: <a href="https://learn.microsoft.com/en-us/deployedge/microsoft-edge-relnote-stable-channel">Release Notes</a> 63 | Thu, 13 Nov 2025 19:37:00 +0000 64 | https://msedge.sf.dl.delivery.mp.microsoft.com/filestreamingservice/files/f46834c6-c739-468a-a1ca-90453d0743e5/MicrosoftEdge-142.0.3595.80.pkg 65 | 66 | 67 | New Edge Release 68 | https://msedge.sf.dl.delivery.mp.microsoft.com/filestreamingservice/files/99d2f1f6-1009-4e0b-bd37-4db143a6d322/MicrosoftEdge-142.0.3595.65.pkg 69 | <br>Version: 142.0.3595.65<br>Release Notes: <a href="https://learn.microsoft.com/en-us/deployedge/microsoft-edge-relnote-stable-channel">Release Notes</a> 70 | Thu, 06 Nov 2025 20:40:00 +0000 71 | https://msedge.sf.dl.delivery.mp.microsoft.com/filestreamingservice/files/99d2f1f6-1009-4e0b-bd37-4db143a6d322/MicrosoftEdge-142.0.3595.65.pkg 72 | 73 | 74 | New Edge Release 75 | https://msedge.sf.dl.delivery.mp.microsoft.com/filestreamingservice/files/f6ef0103-7312-4ec8-975b-c69a480b52c2/MicrosoftEdge-142.0.3595.53.pkg 76 | <br>Version: 142.0.3595.53<br>Release Notes: <a href="https://learn.microsoft.com/en-us/deployedge/microsoft-edge-relnote-stable-channel">Release Notes</a> 77 | Sun, 02 Nov 2025 05:35:00 +0000 78 | https://msedge.sf.dl.delivery.mp.microsoft.com/filestreamingservice/files/f6ef0103-7312-4ec8-975b-c69a480b52c2/MicrosoftEdge-142.0.3595.53.pkg 79 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /latest_chrome_files/chrome_rss.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | BOFA - Chrome RSS Feed 5 | https://bofa.cocolabs.dev/ 6 | Google Chrome for Mac 7 | http://www.rssboard.org/rss-specification 8 | 9 | en-US 10 | 60 11 | Thu, 18 Dec 2025 20:08:00 +0000 12 | 13 | https://bofa.cocolabs.dev/images/bofa_logo.png 14 | BOFA - Chrome RSS Feed 15 | https://bofa.cocolabs.dev/ 16 | 17 | 18 | New Chrome Release 19 | https://dl.google.com/chrome/mac/stable/accept_tos%3Dhttps%253A%252F%252Fwww.google.com%252Fintl%252Fen_ph%252Fchrome%252Fterms%252F%26_and_accept_tos%3Dhttps%253A%252F%252Fpolicies.google.com%252Fterms/googlechrome.pkg 20 | <br>Version: 143.0.7499.170<br>Release Notes: <a href="https://chromereleases.googleblog.com/">Release Notes</a> 21 | Thu, 18 Dec 2025 20:08:00 +0000 22 | https://dl.google.com/chrome/mac/stable/accept_tos%3Dhttps%253A%252F%252Fwww.google.com%252Fintl%252Fen_ph%252Fchrome%252Fterms%252F%26_and_accept_tos%3Dhttps%253A%252F%252Fpolicies.google.com%252Fterms/googlechrome.pkg 23 | 24 | 25 | New Chrome Release 26 | https://dl.google.com/chrome/mac/stable/accept_tos%3Dhttps%253A%252F%252Fwww.google.com%252Fintl%252Fen_ph%252Fchrome%252Fterms%252F%26_and_accept_tos%3Dhttps%253A%252F%252Fpolicies.google.com%252Fterms/googlechrome.pkg 27 | <br>Version: 143.0.7499.147<br>Release Notes: <a href="https://chromereleases.googleblog.com/">Release Notes</a> 28 | Tue, 16 Dec 2025 19:22:00 +0000 29 | https://dl.google.com/chrome/mac/stable/accept_tos%3Dhttps%253A%252F%252Fwww.google.com%252Fintl%252Fen_ph%252Fchrome%252Fterms%252F%26_and_accept_tos%3Dhttps%253A%252F%252Fpolicies.google.com%252Fterms/googlechrome.pkg 30 | 31 | 32 | New Chrome Release 33 | https://dl.google.com/chrome/mac/stable/accept_tos%3Dhttps%253A%252F%252Fwww.google.com%252Fintl%252Fen_ph%252Fchrome%252Fterms%252F%26_and_accept_tos%3Dhttps%253A%252F%252Fpolicies.google.com%252Fterms/googlechrome.pkg 34 | <br>Version: 143.0.7499.110<br>Release Notes: <a href="https://chromereleases.googleblog.com/">Release Notes</a> 35 | Wed, 10 Dec 2025 18:11:00 +0000 36 | https://dl.google.com/chrome/mac/stable/accept_tos%3Dhttps%253A%252F%252Fwww.google.com%252Fintl%252Fen_ph%252Fchrome%252Fterms%252F%26_and_accept_tos%3Dhttps%253A%252F%252Fpolicies.google.com%252Fterms/googlechrome.pkg 37 | 38 | 39 | New Chrome Release 40 | https://dl.google.com/chrome/mac/stable/accept_tos%3Dhttps%253A%252F%252Fwww.google.com%252Fintl%252Fen_ph%252Fchrome%252Fterms%252F%26_and_accept_tos%3Dhttps%253A%252F%252Fpolicies.google.com%252Fterms/googlechrome.pkg 41 | <br>Version: 143.0.7499.41<br>Release Notes: <a href="https://chromereleases.googleblog.com/">Release Notes</a> 42 | Tue, 02 Dec 2025 18:33:00 +0000 43 | https://dl.google.com/chrome/mac/stable/accept_tos%3Dhttps%253A%252F%252Fwww.google.com%252Fintl%252Fen_ph%252Fchrome%252Fterms%252F%26_and_accept_tos%3Dhttps%253A%252F%252Fpolicies.google.com%252Fterms/googlechrome.pkg 44 | 45 | 46 | New Chrome Release 47 | https://dl.google.com/chrome/mac/stable/accept_tos%3Dhttps%253A%252F%252Fwww.google.com%252Fintl%252Fen_ph%252Fchrome%252Fterms%252F%26_and_accept_tos%3Dhttps%253A%252F%252Fpolicies.google.com%252Fterms/googlechrome.pkg 48 | <br>Version: 142.0.7444.176<br>Release Notes: <a href="https://chromereleases.googleblog.com/">Release Notes</a> 49 | Mon, 17 Nov 2025 20:46:00 +0000 50 | https://dl.google.com/chrome/mac/stable/accept_tos%3Dhttps%253A%252F%252Fwww.google.com%252Fintl%252Fen_ph%252Fchrome%252Fterms%252F%26_and_accept_tos%3Dhttps%253A%252F%252Fpolicies.google.com%252Fterms/googlechrome.pkg 51 | 52 | 53 | New Chrome Release 54 | https://dl.google.com/chrome/mac/stable/accept_tos%3Dhttps%253A%252F%252Fwww.google.com%252Fintl%252Fen_ph%252Fchrome%252Fterms%252F%26_and_accept_tos%3Dhttps%253A%252F%252Fpolicies.google.com%252Fterms/googlechrome.pkg 55 | <br>Version: 142.0.7444.162<br>Release Notes: <a href="https://chromereleases.googleblog.com/">Release Notes</a> 56 | Tue, 11 Nov 2025 18:38:00 +0000 57 | https://dl.google.com/chrome/mac/stable/accept_tos%3Dhttps%253A%252F%252Fwww.google.com%252Fintl%252Fen_ph%252Fchrome%252Fterms%252F%26_and_accept_tos%3Dhttps%253A%252F%252Fpolicies.google.com%252Fterms/googlechrome.pkg 58 | 59 | 60 | New Chrome Release 61 | https://dl.google.com/chrome/mac/stable/accept_tos%3Dhttps%253A%252F%252Fwww.google.com%252Fintl%252Fen_ph%252Fchrome%252Fterms%252F%26_and_accept_tos%3Dhttps%253A%252F%252Fpolicies.google.com%252Fterms/googlechrome.pkg 62 | <br>Version: N/A<br>Release Notes: <a href="https://chromereleases.googleblog.com/">Release Notes</a> 63 | Tue, 11 Nov 2025 00:00:00 +0000 64 | https://dl.google.com/chrome/mac/stable/accept_tos%3Dhttps%253A%252F%252Fwww.google.com%252Fintl%252Fen_ph%252Fchrome%252Fterms%252F%26_and_accept_tos%3Dhttps%253A%252F%252Fpolicies.google.com%252Fterms/googlechrome.pkg 65 | 66 | 67 | New Chrome Release 68 | https://dl.google.com/chrome/mac/stable/accept_tos%3Dhttps%253A%252F%252Fwww.google.com%252Fintl%252Fen_ph%252Fchrome%252Fterms%252F%26_and_accept_tos%3Dhttps%253A%252F%252Fpolicies.google.com%252Fterms/googlechrome.pkg 69 | <br>Version: 142.0.7444.135<br>Release Notes: <a href="https://chromereleases.googleblog.com/">Release Notes</a> 70 | Wed, 05 Nov 2025 19:58:00 +0000 71 | https://dl.google.com/chrome/mac/stable/accept_tos%3Dhttps%253A%252F%252Fwww.google.com%252Fintl%252Fen_ph%252Fchrome%252Fterms%252F%26_and_accept_tos%3Dhttps%253A%252F%252Fpolicies.google.com%252Fterms/googlechrome.pkg 72 | 73 | 74 | New Chrome Release 75 | https://dl.google.com/chrome/mac/stable/accept_tos%3Dhttps%253A%252F%252Fwww.google.com%252Fintl%252Fen_ph%252Fchrome%252Fterms%252F%26_and_accept_tos%3Dhttps%253A%252F%252Fpolicies.google.com%252Fterms/googlechrome.pkg 76 | <br>Version: 142.0.7444.60<br>Release Notes: <a href="https://chromereleases.googleblog.com/">Release Notes</a> 77 | Mon, 03 Nov 2025 21:18:00 +0000 78 | https://dl.google.com/chrome/mac/stable/accept_tos%3Dhttps%253A%252F%252Fwww.google.com%252Fintl%252Fen_ph%252Fchrome%252Fterms%252F%26_and_accept_tos%3Dhttps%253A%252F%252Fpolicies.google.com%252Fterms/googlechrome.pkg 79 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /latest_safari_files/safari_all_history.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | December 21, 2025 04:03 AM EST 4 | 5 | 26 6 | 26.3 (20623.2.2) 7 | December 15, 2025 8 | https://developer.apple.com/documentation/safari-release-notes/safari-26_3-release-notes 9 | 10 | 11 | 26 12 | 26.2 (20623.1.14) 13 | December 12, 2025 14 | https://developer.apple.com/documentation/safari-release-notes/safari-26_2-release-notes 15 | 16 | 17 | 26 18 | 26.1 (20622.2.11) 19 | November 3, 2025 20 | https://developer.apple.com/documentation/safari-release-notes/safari-26_1-release-notes 21 | 22 | 23 | 26 24 | 26.0 (20622.1.22) 25 | September 15, 2025 26 | https://developer.apple.com/documentation/safari-release-notes/safari-26-release-notes 27 | 28 | 29 | 18 30 | 18.6 (20621.3.11) 31 | July 29, 2025 32 | https://developer.apple.com/documentation/safari-release-notes/safari-18_6-release-notes 33 | 34 | 35 | 18 36 | 18.5 (20621.2.5) 37 | May 12, 2025 38 | https://developer.apple.com/documentation/safari-release-notes/safari-18_5-release-notes 39 | 40 | 41 | 18 42 | 18.4 (20621.1.15) 43 | March 31, 2025 44 | https://developer.apple.com/documentation/safari-release-notes/safari-18_4-release-notes 45 | 46 | 47 | 18 48 | 18.3 (20620.2.4) 49 | January 27, 2025 50 | https://developer.apple.com/documentation/safari-release-notes/safari-18_3-release-notes 51 | 52 | 53 | 18 54 | 18.2 (20620.1.16) 55 | December 11, 2024 56 | https://developer.apple.com/documentation/safari-release-notes/safari-18_2-release-notes 57 | 58 | 59 | 18 60 | 18.1 (20619.2.8) 61 | October 28, 2024 62 | https://developer.apple.com/documentation/safari-release-notes/safari-18_1-release-notes 63 | 64 | 65 | 18 66 | 18.0.1 (20619.1.26.30) 67 | October 3, 2024 68 | https://developer.apple.com/documentation/safari-release-notes/safari-18_0_1-release-notes 69 | 70 | 71 | 18 72 | 18.0 (20619.1.26) 73 | September 16, 2024 74 | https://developer.apple.com/documentation/safari-release-notes/safari-18-release-notes 75 | 76 | 77 | 17 78 | 17.6 (19618.3.11) 79 | July 29, 2024 80 | https://developer.apple.com/documentation/safari-release-notes/safari-17_6-release-notes 81 | 82 | 83 | 17 84 | 17.5 (19618.2.12) 85 | May 13, 2024 86 | https://developer.apple.com/documentation/safari-release-notes/safari-17_5-release-notes 87 | 88 | 89 | 17 90 | 17.4 (19618.1.15) 91 | March 5, 2024 92 | https://developer.apple.com/documentation/safari-release-notes/safari-17_4-release-notes 93 | 94 | 95 | 17 96 | 17.3 (19617.2.4) 97 | January 22, 2024 98 | https://developer.apple.com/documentation/safari-release-notes/safari-17_3-release-notes 99 | 100 | 101 | 17 102 | 17.2 (19617.1.17) 103 | December 11, 2023 104 | https://developer.apple.com/documentation/safari-release-notes/safari-17_2-release-notes 105 | 106 | 107 | 17 108 | 17.1 (19616.2.9) 109 | October 25, 2023 110 | https://developer.apple.com/documentation/safari-release-notes/safari-17_1-release-notes 111 | 112 | 113 | 17 114 | 17 (19616.1.27) 115 | September 18, 2023 116 | https://developer.apple.com/documentation/safari-release-notes/safari-17-release-notes 117 | 118 | 119 | 16 120 | 16.6 (18615.3.12) 121 | July 24, 2023 122 | https://developer.apple.com/documentation/safari-release-notes/safari-16_6-release-notes 123 | 124 | 125 | 16 126 | 16.5 (18615.2.9) 127 | May 18, 2023 128 | https://developer.apple.com/documentation/safari-release-notes/safari-16_5-release-notes 129 | 130 | 131 | 16 132 | 16.4 (18615.1.26) 133 | March 27, 2023 134 | https://developer.apple.com/documentation/safari-release-notes/safari-16_4-release-notes 135 | 136 | 137 | 16 138 | 16.3 (18614.4.6) 139 | January 23, 2023 140 | https://developer.apple.com/documentation/safari-release-notes/safari-16_3-release-notes 141 | 142 | 143 | 16 144 | 16.2 (18614.3.7) 145 | December 13, 2022 146 | https://developer.apple.com/documentation/safari-release-notes/safari-16_2-release-notes 147 | 148 | 149 | 16 150 | 16.1 (18614.2.9) 151 | October 24, 2022 152 | https://developer.apple.com/documentation/safari-release-notes/safari-16_1-release-notes 153 | 154 | 155 | 16 156 | 16 (18614.1.25) 157 | September 12, 2022 158 | https://developer.apple.com/documentation/safari-release-notes/safari-16-release-notes 159 | 160 | 161 | 15 162 | 15.6 (17613.3.9) 163 | July 20, 2022 164 | https://developer.apple.com/documentation/safari-release-notes/safari-15_6-release-notes 165 | 166 | 167 | 15 168 | 15.5 (17613.2.7) 169 | May 16, 2022 170 | https://developer.apple.com/documentation/safari-release-notes/safari-15_5-release-notes 171 | 172 | 173 | 15 174 | 15.4 (17613.1.17) 175 | March 14, 2022 176 | https://developer.apple.com/documentation/safari-release-notes/safari-15_4-release-notes 177 | 178 | 179 | 15 180 | 15.2 (17612.3.6) 181 | December 13, 2021 182 | https://developer.apple.com/documentation/safari-release-notes/safari-15_2-release-notes 183 | 184 | 185 | 15 186 | 15 (17612.1.27) 187 | September 20, 2021 188 | https://developer.apple.com/documentation/safari-release-notes/safari-15-release-notes 189 | 190 | 191 | 14 192 | 14.1 (16611.1.21) 193 | April 26, 2021 194 | https://developer.apple.com/documentation/safari-release-notes/safari-14_1-release-notes 195 | 196 | 197 | 14 198 | 14 (16610.1.28) 199 | September 16, 2020 200 | https://developer.apple.com/documentation/safari-release-notes/safari-14-release-notes 201 | 202 | 203 | 13 204 | 13.1 (15609.1.20) 205 | March 24, 2020 206 | https://developer.apple.com/documentation/safari-release-notes/safari-13_1-release_notes 207 | 208 | 209 | 13 210 | 13 (15608.2.11) 211 | September 19, 2019 212 | https://developer.apple.com/documentation/safari-release-notes/safari-13-release-notes 213 | 214 | 215 | 12 216 | 12.1 (14607.1.40) 217 | March 25, 2019 218 | https://developer.apple.com/documentation/safari-release-notes/safari-12_1-release-notes 219 | 220 | 221 | 12 222 | 12 (14606.1.36) 223 | September 17, 2018 224 | https://developer.apple.com/documentation/safari-release-notes/safari-12-release-notes 225 | 226 | 227 | -------------------------------------------------------------------------------- /latest_safari_files/safari_all_history.yaml: -------------------------------------------------------------------------------- 1 | - major_version: '26' 2 | release_notes: doc://com.apple.Safari-Release-Notes/documentation/Safari-Release-Notes/safari-26_3-release-notes 3 | release_notes_url: https://developer.apple.com/documentation/safari-release-notes/safari-26_3-release-notes 4 | title: Safari 26.3 Beta Release Notes 5 | released: December 15, 2025 6 | version: 26.3 (20623.2.2) 7 | - major_version: '26' 8 | release_notes: doc://com.apple.Safari-Release-Notes/documentation/Safari-Release-Notes/safari-26_2-release-notes 9 | release_notes_url: https://developer.apple.com/documentation/safari-release-notes/safari-26_2-release-notes 10 | title: Safari 26.2 Release Notes 11 | released: December 12, 2025 12 | version: 26.2 (20623.1.14) 13 | - major_version: '26' 14 | release_notes: doc://com.apple.Safari-Release-Notes/documentation/Safari-Release-Notes/safari-26_1-release-notes 15 | release_notes_url: https://developer.apple.com/documentation/safari-release-notes/safari-26_1-release-notes 16 | title: Safari 26.1 Release Notes 17 | released: November 3, 2025 18 | version: 26.1 (20622.2.11) 19 | - major_version: '26' 20 | release_notes: doc://com.apple.Safari-Release-Notes/documentation/Safari-Release-Notes/safari-26-release-notes 21 | release_notes_url: https://developer.apple.com/documentation/safari-release-notes/safari-26-release-notes 22 | title: Safari 26.0 Release Notes 23 | released: September 15, 2025 24 | version: 26.0 (20622.1.22) 25 | - major_version: '18' 26 | release_notes: doc://com.apple.Safari-Release-Notes/documentation/Safari-Release-Notes/safari-18_6-release-notes 27 | release_notes_url: https://developer.apple.com/documentation/safari-release-notes/safari-18_6-release-notes 28 | title: Safari 18.6 Release Notes 29 | released: July 29, 2025 30 | version: 18.6 (20621.3.11) 31 | - major_version: '18' 32 | release_notes: doc://com.apple.Safari-Release-Notes/documentation/Safari-Release-Notes/safari-18_5-release-notes 33 | release_notes_url: https://developer.apple.com/documentation/safari-release-notes/safari-18_5-release-notes 34 | title: Safari 18.5 Release Notes 35 | released: May 12, 2025 36 | version: 18.5 (20621.2.5) 37 | - major_version: '18' 38 | release_notes: doc://com.apple.Safari-Release-Notes/documentation/Safari-Release-Notes/safari-18_4-release-notes 39 | release_notes_url: https://developer.apple.com/documentation/safari-release-notes/safari-18_4-release-notes 40 | title: Safari 18.4 Release Notes 41 | released: March 31, 2025 42 | version: 18.4 (20621.1.15) 43 | - major_version: '18' 44 | release_notes: doc://com.apple.Safari-Release-Notes/documentation/Safari-Release-Notes/safari-18_3-release-notes 45 | release_notes_url: https://developer.apple.com/documentation/safari-release-notes/safari-18_3-release-notes 46 | title: Safari 18.3 Release Notes 47 | released: January 27, 2025 48 | version: 18.3 (20620.2.4) 49 | - major_version: '18' 50 | release_notes: doc://com.apple.Safari-Release-Notes/documentation/Safari-Release-Notes/safari-18_2-release-notes 51 | release_notes_url: https://developer.apple.com/documentation/safari-release-notes/safari-18_2-release-notes 52 | title: Safari 18.2 Release Notes 53 | released: December 11, 2024 54 | version: 18.2 (20620.1.16) 55 | - major_version: '18' 56 | release_notes: doc://com.apple.Safari-Release-Notes/documentation/Safari-Release-Notes/safari-18_1-release-notes 57 | release_notes_url: https://developer.apple.com/documentation/safari-release-notes/safari-18_1-release-notes 58 | title: Safari 18.1 Release Notes 59 | released: October 28, 2024 60 | version: 18.1 (20619.2.8) 61 | - major_version: '18' 62 | release_notes: doc://com.apple.Safari-Release-Notes/documentation/Safari-Release-Notes/safari-18_0_1-release-notes 63 | release_notes_url: https://developer.apple.com/documentation/safari-release-notes/safari-18_0_1-release-notes 64 | title: Safari 18.0.1 Release Notes 65 | released: October 3, 2024 66 | version: 18.0.1 (20619.1.26.30) 67 | - major_version: '18' 68 | release_notes: doc://com.apple.Safari-Release-Notes/documentation/Safari-Release-Notes/safari-18-release-notes 69 | release_notes_url: https://developer.apple.com/documentation/safari-release-notes/safari-18-release-notes 70 | title: Safari 18.0 Release Notes 71 | released: September 16, 2024 72 | version: 18.0 (20619.1.26) 73 | - major_version: '17' 74 | release_notes: doc://com.apple.Safari-Release-Notes/documentation/Safari-Release-Notes/safari-17_6-release-notes 75 | release_notes_url: https://developer.apple.com/documentation/safari-release-notes/safari-17_6-release-notes 76 | title: Safari 17.6 Release Notes 77 | released: July 29, 2024 78 | version: 17.6 (19618.3.11) 79 | - major_version: '17' 80 | release_notes: doc://com.apple.Safari-Release-Notes/documentation/Safari-Release-Notes/safari-17_5-release-notes 81 | release_notes_url: https://developer.apple.com/documentation/safari-release-notes/safari-17_5-release-notes 82 | title: Safari 17.5 Release Notes 83 | released: May 13, 2024 84 | version: 17.5 (19618.2.12) 85 | - major_version: '17' 86 | release_notes: doc://com.apple.Safari-Release-Notes/documentation/Safari-Release-Notes/safari-17_4-release-notes 87 | release_notes_url: https://developer.apple.com/documentation/safari-release-notes/safari-17_4-release-notes 88 | title: Safari 17.4 Release Notes 89 | released: March 5, 2024 90 | version: 17.4 (19618.1.15) 91 | - major_version: '17' 92 | release_notes: doc://com.apple.Safari-Release-Notes/documentation/Safari-Release-Notes/safari-17_3-release-notes 93 | release_notes_url: https://developer.apple.com/documentation/safari-release-notes/safari-17_3-release-notes 94 | title: Safari 17.3 Release Notes 95 | released: January 22, 2024 96 | version: 17.3 (19617.2.4) 97 | - major_version: '17' 98 | release_notes: doc://com.apple.Safari-Release-Notes/documentation/Safari-Release-Notes/safari-17_2-release-notes 99 | release_notes_url: https://developer.apple.com/documentation/safari-release-notes/safari-17_2-release-notes 100 | title: Safari 17.2 Release Notes 101 | released: December 11, 2023 102 | version: 17.2 (19617.1.17) 103 | - major_version: '17' 104 | release_notes: doc://com.apple.Safari-Release-Notes/documentation/Safari-Release-Notes/safari-17_1-release-notes 105 | release_notes_url: https://developer.apple.com/documentation/safari-release-notes/safari-17_1-release-notes 106 | title: Safari 17.1 Release Notes 107 | released: October 25, 2023 108 | version: 17.1 (19616.2.9) 109 | - major_version: '17' 110 | release_notes: doc://com.apple.Safari-Release-Notes/documentation/Safari-Release-Notes/safari-17-release-notes 111 | release_notes_url: https://developer.apple.com/documentation/safari-release-notes/safari-17-release-notes 112 | title: Safari 17 Release Notes 113 | released: September 18, 2023 114 | version: 17 (19616.1.27) 115 | - major_version: '16' 116 | release_notes: doc://com.apple.Safari-Release-Notes/documentation/Safari-Release-Notes/safari-16_6-release-notes 117 | release_notes_url: https://developer.apple.com/documentation/safari-release-notes/safari-16_6-release-notes 118 | title: Safari 16.6 Release Notes 119 | released: July 24, 2023 120 | version: 16.6 (18615.3.12) 121 | - major_version: '16' 122 | release_notes: doc://com.apple.Safari-Release-Notes/documentation/Safari-Release-Notes/safari-16_5-release-notes 123 | release_notes_url: https://developer.apple.com/documentation/safari-release-notes/safari-16_5-release-notes 124 | title: Safari 16.5 Release Notes 125 | released: May 18, 2023 126 | version: 16.5 (18615.2.9) 127 | - major_version: '16' 128 | release_notes: doc://com.apple.Safari-Release-Notes/documentation/Safari-Release-Notes/safari-16_4-release-notes 129 | release_notes_url: https://developer.apple.com/documentation/safari-release-notes/safari-16_4-release-notes 130 | title: Safari 16.4 Release Notes 131 | released: March 27, 2023 132 | version: 16.4 (18615.1.26) 133 | - major_version: '16' 134 | release_notes: doc://com.apple.Safari-Release-Notes/documentation/Safari-Release-Notes/safari-16_3-release-notes 135 | release_notes_url: https://developer.apple.com/documentation/safari-release-notes/safari-16_3-release-notes 136 | title: Safari 16.3 Release Notes 137 | released: January 23, 2023 138 | version: 16.3 (18614.4.6) 139 | - major_version: '16' 140 | release_notes: doc://com.apple.Safari-Release-Notes/documentation/Safari-Release-Notes/safari-16_2-release-notes 141 | release_notes_url: https://developer.apple.com/documentation/safari-release-notes/safari-16_2-release-notes 142 | title: Safari 16.2 Release Notes 143 | released: December 13, 2022 144 | version: 16.2 (18614.3.7) 145 | - major_version: '16' 146 | release_notes: doc://com.apple.Safari-Release-Notes/documentation/Safari-Release-Notes/safari-16_1-release-notes 147 | release_notes_url: https://developer.apple.com/documentation/safari-release-notes/safari-16_1-release-notes 148 | title: Safari 16.1 Release Notes 149 | released: October 24, 2022 150 | version: 16.1 (18614.2.9) 151 | - major_version: '16' 152 | release_notes: doc://com.apple.Safari-Release-Notes/documentation/Safari-Release-Notes/safari-16-release-notes 153 | release_notes_url: https://developer.apple.com/documentation/safari-release-notes/safari-16-release-notes 154 | title: Safari 16 Release Notes 155 | released: September 12, 2022 156 | version: 16 (18614.1.25) 157 | - major_version: '15' 158 | release_notes: doc://com.apple.Safari-Release-Notes/documentation/Safari-Release-Notes/safari-15_6-release-notes 159 | release_notes_url: https://developer.apple.com/documentation/safari-release-notes/safari-15_6-release-notes 160 | title: Safari 15.6 Release Notes 161 | released: July 20, 2022 162 | version: 15.6 (17613.3.9) 163 | - major_version: '15' 164 | release_notes: doc://com.apple.Safari-Release-Notes/documentation/Safari-Release-Notes/safari-15_5-release-notes 165 | release_notes_url: https://developer.apple.com/documentation/safari-release-notes/safari-15_5-release-notes 166 | title: Safari 15.5 Release Notes 167 | released: May 16, 2022 168 | version: 15.5 (17613.2.7) 169 | - major_version: '15' 170 | release_notes: doc://com.apple.Safari-Release-Notes/documentation/Safari-Release-Notes/safari-15_4-release-notes 171 | release_notes_url: https://developer.apple.com/documentation/safari-release-notes/safari-15_4-release-notes 172 | title: Safari 15.4 Release Notes 173 | released: March 14, 2022 174 | version: 15.4 (17613.1.17) 175 | - major_version: '15' 176 | release_notes: doc://com.apple.Safari-Release-Notes/documentation/Safari-Release-Notes/safari-15_2-release-notes 177 | release_notes_url: https://developer.apple.com/documentation/safari-release-notes/safari-15_2-release-notes 178 | title: Safari 15.2 Release Notes 179 | released: December 13, 2021 180 | version: 15.2 (17612.3.6) 181 | - major_version: '15' 182 | release_notes: doc://com.apple.Safari-Release-Notes/documentation/Safari-Release-Notes/safari-15-release-notes 183 | release_notes_url: https://developer.apple.com/documentation/safari-release-notes/safari-15-release-notes 184 | title: Safari 15 Release Notes 185 | released: September 20, 2021 186 | version: 15 (17612.1.27) 187 | - major_version: '14' 188 | release_notes: doc://com.apple.Safari-Release-Notes/documentation/Safari-Release-Notes/safari-14_1-release-notes 189 | release_notes_url: https://developer.apple.com/documentation/safari-release-notes/safari-14_1-release-notes 190 | title: Safari 14.1 Release Notes 191 | released: April 26, 2021 192 | version: 14.1 (16611.1.21) 193 | - major_version: '14' 194 | release_notes: doc://com.apple.Safari-Release-Notes/documentation/Safari-Release-Notes/safari-14-release-notes 195 | release_notes_url: https://developer.apple.com/documentation/safari-release-notes/safari-14-release-notes 196 | title: Safari 14 Release Notes 197 | released: September 16, 2020 198 | version: 14 (16610.1.28) 199 | - major_version: '13' 200 | release_notes: doc://com.apple.Safari-Release-Notes/documentation/Safari-Release-Notes/safari-13_1-release_notes 201 | release_notes_url: https://developer.apple.com/documentation/safari-release-notes/safari-13_1-release_notes 202 | title: Safari 13.1 Release Notes 203 | released: March 24, 2020 204 | version: 13.1 (15609.1.20) 205 | - major_version: '13' 206 | release_notes: doc://com.apple.Safari-Release-Notes/documentation/Safari-Release-Notes/safari-13-release-notes 207 | release_notes_url: https://developer.apple.com/documentation/safari-release-notes/safari-13-release-notes 208 | title: Safari 13 Release Notes 209 | released: September 19, 2019 210 | version: 13 (15608.2.11) 211 | - major_version: '12' 212 | release_notes: doc://com.apple.Safari-Release-Notes/documentation/Safari-Release-Notes/safari-12_1-release-notes 213 | release_notes_url: https://developer.apple.com/documentation/safari-release-notes/safari-12_1-release-notes 214 | title: Safari 12.1 Release Notes 215 | released: March 25, 2019 216 | version: 12.1 (14607.1.40) 217 | - major_version: '12' 218 | release_notes: doc://com.apple.Safari-Release-Notes/documentation/Safari-Release-Notes/safari-12-release-notes 219 | release_notes_url: https://developer.apple.com/documentation/safari-release-notes/safari-12-release-notes 220 | title: Safari 12 Release Notes 221 | released: September 17, 2018 222 | version: 12 (14606.1.36) 223 | -------------------------------------------------------------------------------- /.github/actions/generate_edge_latest.py: -------------------------------------------------------------------------------- 1 | import os 2 | import requests 3 | import plistlib 4 | import xml.etree.ElementTree as ET 5 | from xml.dom import minidom 6 | from datetime import datetime 7 | import re 8 | import json 9 | import yaml 10 | from collections import defaultdict 11 | import pytz 12 | 13 | # Define the Eastern Time Zone 14 | eastern = pytz.timezone('US/Eastern') 15 | 16 | def fetch_edge_latest(channel, url): 17 | response = requests.get(url) 18 | response.raise_for_status() 19 | 20 | xml_content = response.text 21 | 22 | output_dir = "latest_edge_files" 23 | os.makedirs(output_dir, exist_ok=True) 24 | print(f"Directory '{output_dir}' created or already exists.") 25 | 26 | output_file = os.path.join(output_dir, f"edge_{channel}_version.xml") 27 | with open(output_file, "w") as file: 28 | file.write(xml_content) 29 | print(f"File '{output_file}' written successfully.") 30 | return output_file 31 | 32 | def extract_info_from_xml(file_path): 33 | try: 34 | with open(file_path, 'rb') as file: 35 | plist_data = plistlib.load(file) 36 | 37 | # Debug prints to check the structure of the plist 38 | print(f"Parsing plist file: {file_path}") 39 | print(plist_data) 40 | 41 | date = plist_data[0].get('Date', 'N/A') 42 | location = plist_data[0].get('Location', 'N/A') 43 | title = plist_data[0].get('Title', 'N/A') 44 | 45 | # Format the title to only include numbers and special characters 46 | version = re.sub(r'[^0-9.]+', '', title) 47 | 48 | # Format the date to a user-readable format 49 | if date != 'N/A': 50 | date = date.astimezone(eastern).strftime('%B %d, %Y %I:%M %p %Z') 51 | 52 | return { 53 | "channel": os.path.basename(file_path).split('_')[1], 54 | "date": date, 55 | "location": location, 56 | "version": version 57 | } 58 | except Exception as e: 59 | print(f"Error parsing file {file_path}: {e}") 60 | return { 61 | "channel": os.path.basename(file_path).split('_')[1], 62 | "date": 'N/A', 63 | "location": 'N/A', 64 | "version": 'N/A' 65 | } 66 | 67 | def create_summary_xml(info_list, insider_info_list, output_file): 68 | root = ET.Element("EdgeLatestVersions") 69 | 70 | # Add last_updated element 71 | last_updated = ET.SubElement(root, "last_updated") 72 | last_updated.text = datetime.now(eastern).strftime('%B %d, %Y %I:%M %p %Z') 73 | 74 | for info in info_list: 75 | entry = ET.SubElement(root, "Version") 76 | ET.SubElement(entry, "Channel").text = info["channel"] 77 | ET.SubElement(entry, "Date").text = info["date"] 78 | ET.SubElement(entry, "Location").text = info["location"] 79 | ET.SubElement(entry, "Version").text = info["version"] 80 | 81 | for info in insider_info_list: 82 | entry = ET.SubElement(root, "Version") 83 | ET.SubElement(entry, "Channel").text = f"insider_{info['channel']}" 84 | ET.SubElement(entry, "Date").text = info["date"] 85 | ET.SubElement(entry, "Location").text = info["location"] 86 | ET.SubElement(entry, "Version").text = info["version"] 87 | 88 | tree = ET.ElementTree(root) 89 | xml_str = ET.tostring(root, encoding='utf-8') 90 | pretty_xml_str = minidom.parseString(xml_str).toprettyxml(indent=" ") 91 | 92 | with open(output_file, "w") as file: 93 | file.write(pretty_xml_str) 94 | print(f"Summary file '{output_file}' written successfully.") 95 | 96 | def update_last_updated_in_xml(file_path): 97 | tree = ET.parse(file_path) 98 | root = tree.getroot() 99 | 100 | # Find or create last_updated element 101 | last_updated = root.find("last_updated") 102 | if last_updated is None: 103 | last_updated = ET.Element("last_updated") 104 | root.insert(0, last_updated) 105 | 106 | last_updated.text = datetime.now(eastern).strftime('%B %d, %Y %I:%M %p %Z') 107 | 108 | tree = ET.ElementTree(root) 109 | xml_str = ET.tostring(root, encoding='utf-8') 110 | pretty_xml_str = minidom.parseString(xml_str).toprettyxml(indent=" ") 111 | 112 | # Remove extra newlines and spaces 113 | pretty_xml_str = "\n".join([line for line in pretty_xml_str.split("\n") if line.strip()]) 114 | 115 | with open(file_path, "w") as file: 116 | file.write(pretty_xml_str) 117 | print(f"Updated file '{file_path}' with last_updated.") 118 | 119 | def fetch_edge_insider_canary_version(url): 120 | response = requests.get(url) 121 | response.raise_for_status() 122 | 123 | releases = response.json() 124 | macos_releases = [release for release in releases[0]["Releases"] if release["Platform"] == "MacOS"] 125 | 126 | if not macos_releases: 127 | print("No MacOS releases found.") 128 | return None 129 | 130 | latest_release = max(macos_releases, key=lambda x: x["PublishedTime"]) 131 | 132 | artifact = next((artifact for artifact in latest_release["Artifacts"] if artifact["ArtifactName"] == "pkg"), None) 133 | location = artifact["Location"] if artifact else "N/A" 134 | 135 | return { 136 | "channel": "canary", 137 | "date": datetime.strptime(latest_release["PublishedTime"], '%Y-%m-%dT%H:%M:%S').astimezone(eastern).strftime('%B %d, %Y %I:%M %p %Z'), 138 | "location": location, 139 | "version": latest_release["ProductVersion"] 140 | } 141 | 142 | def create_canary_xml(info, output_file): 143 | root = ET.Element("EdgeCanaryVersion") 144 | 145 | # Add last_updated element 146 | last_updated = ET.SubElement(root, "last_updated") 147 | last_updated.text = datetime.now(eastern).strftime('%B %d, %Y %I:%M %p %Z') 148 | 149 | entry = ET.SubElement(root, "Version") 150 | ET.SubElement(entry, "Channel").text = info["channel"] 151 | ET.SubElement(entry, "Date").text = info["date"] 152 | ET.SubElement(entry, "Location").text = info["location"] 153 | ET.SubElement(entry, "Version").text = info["version"] 154 | 155 | tree = ET.ElementTree(root) 156 | xml_str = ET.tostring(root, encoding='utf-8') 157 | pretty_xml_str = minidom.parseString(xml_str).toprettyxml(indent=" ") 158 | 159 | with open(output_file, "w") as file: 160 | file.write(pretty_xml_str) 161 | print(f"Canary file '{output_file}' written successfully.") 162 | 163 | def fetch_edge_insider_version(url, channel): 164 | response = requests.get(url) 165 | response.raise_for_status() 166 | 167 | releases = response.json() 168 | macos_releases = [release for release in releases[0]["Releases"] if release["Platform"] == "MacOS"] 169 | 170 | if not macos_releases: 171 | print(f"No MacOS releases found for {channel}.") 172 | return None 173 | 174 | latest_release = max(macos_releases, key=lambda x: x["PublishedTime"]) 175 | 176 | artifact = next((artifact for artifact in latest_release["Artifacts"] if artifact["ArtifactName"] == "pkg"), None) 177 | location = artifact["Location"] if artifact else "N/A" 178 | 179 | return { 180 | "channel": channel, 181 | "date": datetime.strptime(latest_release["PublishedTime"], '%Y-%m-%dT%H:%M:%S').astimezone(eastern).strftime('%B %d, %Y %I:%M %p %Z'), 182 | "location": location, 183 | "version": latest_release["ProductVersion"] 184 | } 185 | 186 | def create_insider_versions_xml(info_list, output_file): 187 | root = ET.Element("EdgeInsiderVersions") 188 | 189 | # Add last_updated element 190 | last_updated = ET.SubElement(root, "last_updated") 191 | last_updated.text = datetime.now(eastern).strftime('%B %d, %Y %I:%M %p %Z') 192 | 193 | for info in info_list: 194 | entry = ET.SubElement(root, "Version") 195 | ET.SubElement(entry, "Channel").text = info["channel"] 196 | ET.SubElement(entry, "Date").text = info["date"] 197 | ET.SubElement(entry, "Location").text = info["location"] 198 | ET.SubElement(entry, "Version").text = info["version"] 199 | 200 | tree = ET.ElementTree(root) 201 | xml_str = ET.tostring(root, encoding='utf-8') 202 | pretty_xml_str = minidom.parseString(xml_str).toprettyxml(indent=" ") 203 | 204 | with open(output_file, "w") as file: 205 | file.write(pretty_xml_str) 206 | print(f"Insider versions file '{output_file}' written successfully.") 207 | 208 | def convert_xml_to_json(xml_file, json_file): 209 | tree = ET.parse(xml_file) 210 | root = tree.getroot() 211 | 212 | def etree_to_dict(t): 213 | d = {t.tag: {} if t.attrib else None} 214 | children = list(t) 215 | if children: 216 | dd = defaultdict(list) 217 | for dc in map(etree_to_dict, children): 218 | for k, v in dc.items(): 219 | dd[k].append(v) 220 | d = {t.tag: {k: v[0] if len(v) == 1 else v for k, v in dd.items()}} 221 | if t.attrib: 222 | d[t.tag].update(('@' + k, v) for k, v in t.attrib.items()) 223 | if t.text: 224 | text = t.text.strip() 225 | if children or t.attrib: 226 | if text: 227 | d[t.tag]['#text'] = text 228 | else: 229 | d[t.tag] = text 230 | return d 231 | 232 | data_dict = etree_to_dict(root) 233 | with open(json_file, "w") as file: 234 | json.dump(data_dict, file, indent=4) 235 | print(f"JSON file '{json_file}' written successfully.") 236 | 237 | def convert_xml_to_yaml(xml_file, yaml_file): 238 | tree = ET.parse(xml_file) 239 | root = tree.getroot() 240 | 241 | def etree_to_dict(t): 242 | d = {t.tag: {} if t.attrib else None} 243 | children = list(t) 244 | if children: 245 | dd = defaultdict(list) 246 | for dc in map(etree_to_dict, children): 247 | for k, v in dc.items(): 248 | dd[k].append(v) 249 | d = {t.tag: {k: v[0] if len(v) == 1 else v for k, v in dd.items()}} 250 | if t.attrib: 251 | d[t.tag].update(('@' + k, v) for k, v in t.attrib.items()) 252 | if t.text: 253 | text = t.text.strip() 254 | if children or t.attrib: 255 | if text: 256 | d[t.tag]['#text'] = text 257 | else: 258 | d[t.tag] = text 259 | return d 260 | 261 | data_dict = etree_to_dict(root) 262 | 263 | # Create a new dictionary with the desired order 264 | if "EdgeLatestVersions" in data_dict: 265 | edge_versions = data_dict["EdgeLatestVersions"] 266 | ordered_dict = { 267 | "EdgeLatestVersions": { 268 | "last_updated": edge_versions.get("last_updated", ""), 269 | "Version": edge_versions.get("Version", []) 270 | } 271 | } 272 | data_dict = ordered_dict 273 | 274 | with open(yaml_file, "w") as file: 275 | yaml.dump(data_dict, file, default_flow_style=False, sort_keys=False) 276 | print(f"YAML file '{yaml_file}' written successfully.") 277 | 278 | def convert_plist_to_json(xml_file, json_file): 279 | try: 280 | with open(xml_file, 'rb') as file: 281 | plist_data = plistlib.load(file) 282 | 283 | # Add last_updated field 284 | output_data = { 285 | "last_updated": datetime.now(eastern).strftime('%B %d, %Y %I:%M %p %Z'), 286 | "plist_data": plist_data 287 | } 288 | 289 | with open(json_file, 'w') as f: 290 | json.dump(output_data, f, indent=2, default=str) 291 | print(f"JSON file '{json_file}' written successfully.") 292 | except Exception as e: 293 | print(f"Error converting plist to JSON: {e}") 294 | 295 | def convert_plist_to_yaml(xml_file, yaml_file): 296 | try: 297 | with open(xml_file, 'rb') as file: 298 | plist_data = plistlib.load(file) 299 | 300 | # Add last_updated field 301 | output_data = { 302 | "last_updated": datetime.now(eastern).strftime('%B %d, %Y %I:%M %p %Z'), 303 | "plist_data": plist_data 304 | } 305 | 306 | with open(yaml_file, 'w') as f: 307 | yaml.dump(output_data, f, default_flow_style=False, sort_keys=False, allow_unicode=True) 308 | print(f"YAML file '{yaml_file}' written successfully.") 309 | except Exception as e: 310 | print(f"Error converting plist to YAML: {e}") 311 | 312 | if __name__ == "__main__": 313 | # Remove the old channels dictionary and use insider_channels as channels 314 | channels = { 315 | "current": "https://edgeupdates.microsoft.com/api/products/stable", 316 | "canary": "https://edgeupdates.microsoft.com/api/products/canary", 317 | "dev": "https://edgeupdates.microsoft.com/api/products/dev", 318 | "beta": "https://edgeupdates.microsoft.com/api/products/beta" 319 | } 320 | 321 | info_list = [] 322 | for channel, url in channels.items(): 323 | info = fetch_edge_insider_version(url, channel) 324 | if info: 325 | info_list.append(info) 326 | 327 | output_file = os.path.join("latest_edge_files", "edge_latest_versions.xml") 328 | create_insider_versions_xml(info_list, output_file) 329 | update_last_updated_in_xml(output_file) 330 | 331 | # Convert XML to JSON and YAML 332 | convert_xml_to_json(output_file, os.path.join("latest_edge_files", "edge_latest_versions.json")) 333 | convert_xml_to_yaml(output_file, os.path.join("latest_edge_files", "edge_latest_versions.yaml")) 334 | -------------------------------------------------------------------------------- /latest_safari_files/safari_all_history.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "major_version": "26", 4 | "release_notes": "doc://com.apple.Safari-Release-Notes/documentation/Safari-Release-Notes/safari-26_3-release-notes", 5 | "release_notes_url": "https://developer.apple.com/documentation/safari-release-notes/safari-26_3-release-notes", 6 | "title": "Safari 26.3 Beta Release Notes", 7 | "released": "December 15, 2025", 8 | "version": "26.3 (20623.2.2)" 9 | }, 10 | { 11 | "major_version": "26", 12 | "release_notes": "doc://com.apple.Safari-Release-Notes/documentation/Safari-Release-Notes/safari-26_2-release-notes", 13 | "release_notes_url": "https://developer.apple.com/documentation/safari-release-notes/safari-26_2-release-notes", 14 | "title": "Safari 26.2 Release Notes", 15 | "released": "December 12, 2025", 16 | "version": "26.2 (20623.1.14)" 17 | }, 18 | { 19 | "major_version": "26", 20 | "release_notes": "doc://com.apple.Safari-Release-Notes/documentation/Safari-Release-Notes/safari-26_1-release-notes", 21 | "release_notes_url": "https://developer.apple.com/documentation/safari-release-notes/safari-26_1-release-notes", 22 | "title": "Safari 26.1 Release Notes", 23 | "released": "November 3, 2025", 24 | "version": "26.1 (20622.2.11)" 25 | }, 26 | { 27 | "major_version": "26", 28 | "release_notes": "doc://com.apple.Safari-Release-Notes/documentation/Safari-Release-Notes/safari-26-release-notes", 29 | "release_notes_url": "https://developer.apple.com/documentation/safari-release-notes/safari-26-release-notes", 30 | "title": "Safari 26.0 Release Notes", 31 | "released": "September 15, 2025", 32 | "version": "26.0 (20622.1.22)" 33 | }, 34 | { 35 | "major_version": "18", 36 | "release_notes": "doc://com.apple.Safari-Release-Notes/documentation/Safari-Release-Notes/safari-18_6-release-notes", 37 | "release_notes_url": "https://developer.apple.com/documentation/safari-release-notes/safari-18_6-release-notes", 38 | "title": "Safari 18.6 Release Notes", 39 | "released": "July 29, 2025", 40 | "version": "18.6 (20621.3.11)" 41 | }, 42 | { 43 | "major_version": "18", 44 | "release_notes": "doc://com.apple.Safari-Release-Notes/documentation/Safari-Release-Notes/safari-18_5-release-notes", 45 | "release_notes_url": "https://developer.apple.com/documentation/safari-release-notes/safari-18_5-release-notes", 46 | "title": "Safari 18.5 Release Notes", 47 | "released": "May 12, 2025", 48 | "version": "18.5 (20621.2.5)" 49 | }, 50 | { 51 | "major_version": "18", 52 | "release_notes": "doc://com.apple.Safari-Release-Notes/documentation/Safari-Release-Notes/safari-18_4-release-notes", 53 | "release_notes_url": "https://developer.apple.com/documentation/safari-release-notes/safari-18_4-release-notes", 54 | "title": "Safari 18.4 Release Notes", 55 | "released": "March 31, 2025", 56 | "version": "18.4 (20621.1.15)" 57 | }, 58 | { 59 | "major_version": "18", 60 | "release_notes": "doc://com.apple.Safari-Release-Notes/documentation/Safari-Release-Notes/safari-18_3-release-notes", 61 | "release_notes_url": "https://developer.apple.com/documentation/safari-release-notes/safari-18_3-release-notes", 62 | "title": "Safari 18.3 Release Notes", 63 | "released": "January 27, 2025", 64 | "version": "18.3 (20620.2.4)" 65 | }, 66 | { 67 | "major_version": "18", 68 | "release_notes": "doc://com.apple.Safari-Release-Notes/documentation/Safari-Release-Notes/safari-18_2-release-notes", 69 | "release_notes_url": "https://developer.apple.com/documentation/safari-release-notes/safari-18_2-release-notes", 70 | "title": "Safari 18.2 Release Notes", 71 | "released": "December 11, 2024", 72 | "version": "18.2 (20620.1.16)" 73 | }, 74 | { 75 | "major_version": "18", 76 | "release_notes": "doc://com.apple.Safari-Release-Notes/documentation/Safari-Release-Notes/safari-18_1-release-notes", 77 | "release_notes_url": "https://developer.apple.com/documentation/safari-release-notes/safari-18_1-release-notes", 78 | "title": "Safari 18.1 Release Notes", 79 | "released": "October 28, 2024", 80 | "version": "18.1 (20619.2.8)" 81 | }, 82 | { 83 | "major_version": "18", 84 | "release_notes": "doc://com.apple.Safari-Release-Notes/documentation/Safari-Release-Notes/safari-18_0_1-release-notes", 85 | "release_notes_url": "https://developer.apple.com/documentation/safari-release-notes/safari-18_0_1-release-notes", 86 | "title": "Safari 18.0.1 Release Notes", 87 | "released": "October 3, 2024", 88 | "version": "18.0.1 (20619.1.26.30)" 89 | }, 90 | { 91 | "major_version": "18", 92 | "release_notes": "doc://com.apple.Safari-Release-Notes/documentation/Safari-Release-Notes/safari-18-release-notes", 93 | "release_notes_url": "https://developer.apple.com/documentation/safari-release-notes/safari-18-release-notes", 94 | "title": "Safari 18.0 Release Notes", 95 | "released": "September 16, 2024", 96 | "version": "18.0 (20619.1.26)" 97 | }, 98 | { 99 | "major_version": "17", 100 | "release_notes": "doc://com.apple.Safari-Release-Notes/documentation/Safari-Release-Notes/safari-17_6-release-notes", 101 | "release_notes_url": "https://developer.apple.com/documentation/safari-release-notes/safari-17_6-release-notes", 102 | "title": "Safari 17.6 Release Notes", 103 | "released": "July 29, 2024", 104 | "version": "17.6 (19618.3.11)" 105 | }, 106 | { 107 | "major_version": "17", 108 | "release_notes": "doc://com.apple.Safari-Release-Notes/documentation/Safari-Release-Notes/safari-17_5-release-notes", 109 | "release_notes_url": "https://developer.apple.com/documentation/safari-release-notes/safari-17_5-release-notes", 110 | "title": "Safari 17.5 Release Notes", 111 | "released": "May 13, 2024", 112 | "version": "17.5 (19618.2.12)" 113 | }, 114 | { 115 | "major_version": "17", 116 | "release_notes": "doc://com.apple.Safari-Release-Notes/documentation/Safari-Release-Notes/safari-17_4-release-notes", 117 | "release_notes_url": "https://developer.apple.com/documentation/safari-release-notes/safari-17_4-release-notes", 118 | "title": "Safari 17.4 Release Notes", 119 | "released": "March 5, 2024", 120 | "version": "17.4 (19618.1.15)" 121 | }, 122 | { 123 | "major_version": "17", 124 | "release_notes": "doc://com.apple.Safari-Release-Notes/documentation/Safari-Release-Notes/safari-17_3-release-notes", 125 | "release_notes_url": "https://developer.apple.com/documentation/safari-release-notes/safari-17_3-release-notes", 126 | "title": "Safari 17.3 Release Notes", 127 | "released": "January 22, 2024", 128 | "version": "17.3 (19617.2.4)" 129 | }, 130 | { 131 | "major_version": "17", 132 | "release_notes": "doc://com.apple.Safari-Release-Notes/documentation/Safari-Release-Notes/safari-17_2-release-notes", 133 | "release_notes_url": "https://developer.apple.com/documentation/safari-release-notes/safari-17_2-release-notes", 134 | "title": "Safari 17.2 Release Notes", 135 | "released": "December 11, 2023", 136 | "version": "17.2 (19617.1.17)" 137 | }, 138 | { 139 | "major_version": "17", 140 | "release_notes": "doc://com.apple.Safari-Release-Notes/documentation/Safari-Release-Notes/safari-17_1-release-notes", 141 | "release_notes_url": "https://developer.apple.com/documentation/safari-release-notes/safari-17_1-release-notes", 142 | "title": "Safari 17.1 Release Notes", 143 | "released": "October 25, 2023", 144 | "version": "17.1 (19616.2.9)" 145 | }, 146 | { 147 | "major_version": "17", 148 | "release_notes": "doc://com.apple.Safari-Release-Notes/documentation/Safari-Release-Notes/safari-17-release-notes", 149 | "release_notes_url": "https://developer.apple.com/documentation/safari-release-notes/safari-17-release-notes", 150 | "title": "Safari 17 Release Notes", 151 | "released": "September 18, 2023", 152 | "version": "17 (19616.1.27)" 153 | }, 154 | { 155 | "major_version": "16", 156 | "release_notes": "doc://com.apple.Safari-Release-Notes/documentation/Safari-Release-Notes/safari-16_6-release-notes", 157 | "release_notes_url": "https://developer.apple.com/documentation/safari-release-notes/safari-16_6-release-notes", 158 | "title": "Safari 16.6 Release Notes", 159 | "released": "July 24, 2023", 160 | "version": "16.6 (18615.3.12)" 161 | }, 162 | { 163 | "major_version": "16", 164 | "release_notes": "doc://com.apple.Safari-Release-Notes/documentation/Safari-Release-Notes/safari-16_5-release-notes", 165 | "release_notes_url": "https://developer.apple.com/documentation/safari-release-notes/safari-16_5-release-notes", 166 | "title": "Safari 16.5 Release Notes", 167 | "released": "May 18, 2023", 168 | "version": "16.5 (18615.2.9)" 169 | }, 170 | { 171 | "major_version": "16", 172 | "release_notes": "doc://com.apple.Safari-Release-Notes/documentation/Safari-Release-Notes/safari-16_4-release-notes", 173 | "release_notes_url": "https://developer.apple.com/documentation/safari-release-notes/safari-16_4-release-notes", 174 | "title": "Safari 16.4 Release Notes", 175 | "released": "March 27, 2023", 176 | "version": "16.4 (18615.1.26)" 177 | }, 178 | { 179 | "major_version": "16", 180 | "release_notes": "doc://com.apple.Safari-Release-Notes/documentation/Safari-Release-Notes/safari-16_3-release-notes", 181 | "release_notes_url": "https://developer.apple.com/documentation/safari-release-notes/safari-16_3-release-notes", 182 | "title": "Safari 16.3 Release Notes", 183 | "released": "January 23, 2023", 184 | "version": "16.3 (18614.4.6)" 185 | }, 186 | { 187 | "major_version": "16", 188 | "release_notes": "doc://com.apple.Safari-Release-Notes/documentation/Safari-Release-Notes/safari-16_2-release-notes", 189 | "release_notes_url": "https://developer.apple.com/documentation/safari-release-notes/safari-16_2-release-notes", 190 | "title": "Safari 16.2 Release Notes", 191 | "released": "December 13, 2022", 192 | "version": "16.2 (18614.3.7)" 193 | }, 194 | { 195 | "major_version": "16", 196 | "release_notes": "doc://com.apple.Safari-Release-Notes/documentation/Safari-Release-Notes/safari-16_1-release-notes", 197 | "release_notes_url": "https://developer.apple.com/documentation/safari-release-notes/safari-16_1-release-notes", 198 | "title": "Safari 16.1 Release Notes", 199 | "released": "October 24, 2022", 200 | "version": "16.1 (18614.2.9)" 201 | }, 202 | { 203 | "major_version": "16", 204 | "release_notes": "doc://com.apple.Safari-Release-Notes/documentation/Safari-Release-Notes/safari-16-release-notes", 205 | "release_notes_url": "https://developer.apple.com/documentation/safari-release-notes/safari-16-release-notes", 206 | "title": "Safari 16 Release Notes", 207 | "released": "September 12, 2022", 208 | "version": "16 (18614.1.25)" 209 | }, 210 | { 211 | "major_version": "15", 212 | "release_notes": "doc://com.apple.Safari-Release-Notes/documentation/Safari-Release-Notes/safari-15_6-release-notes", 213 | "release_notes_url": "https://developer.apple.com/documentation/safari-release-notes/safari-15_6-release-notes", 214 | "title": "Safari 15.6 Release Notes", 215 | "released": "July 20, 2022", 216 | "version": "15.6 (17613.3.9)" 217 | }, 218 | { 219 | "major_version": "15", 220 | "release_notes": "doc://com.apple.Safari-Release-Notes/documentation/Safari-Release-Notes/safari-15_5-release-notes", 221 | "release_notes_url": "https://developer.apple.com/documentation/safari-release-notes/safari-15_5-release-notes", 222 | "title": "Safari 15.5 Release Notes", 223 | "released": "May 16, 2022", 224 | "version": "15.5 (17613.2.7)" 225 | }, 226 | { 227 | "major_version": "15", 228 | "release_notes": "doc://com.apple.Safari-Release-Notes/documentation/Safari-Release-Notes/safari-15_4-release-notes", 229 | "release_notes_url": "https://developer.apple.com/documentation/safari-release-notes/safari-15_4-release-notes", 230 | "title": "Safari 15.4 Release Notes", 231 | "released": "March 14, 2022", 232 | "version": "15.4 (17613.1.17)" 233 | }, 234 | { 235 | "major_version": "15", 236 | "release_notes": "doc://com.apple.Safari-Release-Notes/documentation/Safari-Release-Notes/safari-15_2-release-notes", 237 | "release_notes_url": "https://developer.apple.com/documentation/safari-release-notes/safari-15_2-release-notes", 238 | "title": "Safari 15.2 Release Notes", 239 | "released": "December 13, 2021", 240 | "version": "15.2 (17612.3.6)" 241 | }, 242 | { 243 | "major_version": "15", 244 | "release_notes": "doc://com.apple.Safari-Release-Notes/documentation/Safari-Release-Notes/safari-15-release-notes", 245 | "release_notes_url": "https://developer.apple.com/documentation/safari-release-notes/safari-15-release-notes", 246 | "title": "Safari 15 Release Notes", 247 | "released": "September 20, 2021", 248 | "version": "15 (17612.1.27)" 249 | }, 250 | { 251 | "major_version": "14", 252 | "release_notes": "doc://com.apple.Safari-Release-Notes/documentation/Safari-Release-Notes/safari-14_1-release-notes", 253 | "release_notes_url": "https://developer.apple.com/documentation/safari-release-notes/safari-14_1-release-notes", 254 | "title": "Safari 14.1 Release Notes", 255 | "released": "April 26, 2021", 256 | "version": "14.1 (16611.1.21)" 257 | }, 258 | { 259 | "major_version": "14", 260 | "release_notes": "doc://com.apple.Safari-Release-Notes/documentation/Safari-Release-Notes/safari-14-release-notes", 261 | "release_notes_url": "https://developer.apple.com/documentation/safari-release-notes/safari-14-release-notes", 262 | "title": "Safari 14 Release Notes", 263 | "released": "September 16, 2020", 264 | "version": "14 (16610.1.28)" 265 | }, 266 | { 267 | "major_version": "13", 268 | "release_notes": "doc://com.apple.Safari-Release-Notes/documentation/Safari-Release-Notes/safari-13_1-release_notes", 269 | "release_notes_url": "https://developer.apple.com/documentation/safari-release-notes/safari-13_1-release_notes", 270 | "title": "Safari 13.1 Release Notes", 271 | "released": "March 24, 2020", 272 | "version": "13.1 (15609.1.20)" 273 | }, 274 | { 275 | "major_version": "13", 276 | "release_notes": "doc://com.apple.Safari-Release-Notes/documentation/Safari-Release-Notes/safari-13-release-notes", 277 | "release_notes_url": "https://developer.apple.com/documentation/safari-release-notes/safari-13-release-notes", 278 | "title": "Safari 13 Release Notes", 279 | "released": "September 19, 2019", 280 | "version": "13 (15608.2.11)" 281 | }, 282 | { 283 | "major_version": "12", 284 | "release_notes": "doc://com.apple.Safari-Release-Notes/documentation/Safari-Release-Notes/safari-12_1-release-notes", 285 | "release_notes_url": "https://developer.apple.com/documentation/safari-release-notes/safari-12_1-release-notes", 286 | "title": "Safari 12.1 Release Notes", 287 | "released": "March 25, 2019", 288 | "version": "12.1 (14607.1.40)" 289 | }, 290 | { 291 | "major_version": "12", 292 | "release_notes": "doc://com.apple.Safari-Release-Notes/documentation/Safari-Release-Notes/safari-12-release-notes", 293 | "release_notes_url": "https://developer.apple.com/documentation/safari-release-notes/safari-12-release-notes", 294 | "title": "Safari 12 Release Notes", 295 | "released": "September 17, 2018", 296 | "version": "12 (14606.1.36)" 297 | } 298 | ] -------------------------------------------------------------------------------- /.github/actions/generate_rss_feed.py: -------------------------------------------------------------------------------- 1 | import xml.etree.ElementTree as ET 2 | from datetime import datetime 3 | import os 4 | 5 | # Get the root directory of the project (assuming the script is inside a subfolder like '/update_readme_scripts/') 6 | project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 7 | repo_root = os.path.dirname(project_root) # go up from .github to repo root 8 | 9 | # Define the correct paths based on the project root 10 | # latest_xml_path = os.path.join(repo_root, 'latest_raw_files', 'macos_standalone_latest.xml') 11 | # Replace single-feed path with a directory + per-package filenames 12 | # FEEDS_DIR = os.path.join(repo_root, 'latest_raw_files', 'macos_standalone_rss') 13 | # FEED_BASE_URL = "https://BOFA.cocolabs.dev/rss_feeds" 14 | 15 | # Print the paths to verify if they are correct 16 | # print(f"Latest XML Path: {latest_xml_path}") 17 | # print(f"RSS Feeds Directory: {FEEDS_DIR}") 18 | 19 | def indent(elem, level=0): 20 | i = "\n" + level * " " 21 | if len(elem): 22 | if not elem.text or not elem.text.strip(): 23 | elem.text = i + " " 24 | if not elem.tail or not elem.tail.strip(): 25 | elem.tail = i 26 | for subelem in elem: 27 | indent(subelem, level + 1) 28 | if not subelem.tail or not subelem.tail.strip(): 29 | subelem.tail = i 30 | else: 31 | if level and (not elem.tail or not elem.tail.strip()): 32 | elem.tail = i 33 | 34 | # Constants for URLs 35 | SITE_URL = "https://bofa.cocolabs.dev/" 36 | 37 | # Remove the static PACKAGES list and replace with dynamic parsing 38 | # --- Begin new code for browser XML parsing --- 39 | 40 | # Paths to browser XML files 41 | chrome_xml_path = os.path.join(repo_root, 'latest_chrome_files', 'chrome_latest_versions.xml') 42 | edge_xml_path = os.path.join(repo_root, 'latest_edge_files', 'edge_latest_versions.xml') 43 | firefox_xml_path = os.path.join(repo_root, 'latest_firefox_files', 'firefox_latest_versions.xml') 44 | 45 | def parse_chrome_packages(): 46 | pkgs = [] 47 | if not os.path.exists(chrome_xml_path): 48 | return pkgs 49 | tree = ET.parse(chrome_xml_path) 50 | root = tree.getroot() 51 | for channel in ['stable']: 52 | node = root.find(channel) 53 | if node is not None: 54 | version = node.find('version').text if node.find('version') is not None else '' 55 | download = node.find('download_link').text if node.find('download_link') is not None else '' 56 | release_time = node.find('release_time').text if node.find('release_time') is not None else '' 57 | pkgs.append({ 58 | "name": "Chrome", 59 | "feed_filename": "chrome_rss.xml", 60 | "channel_title": "BOFA - Chrome RSS Feed", 61 | "channel_description": "Google Chrome for Mac", 62 | "release_notes_url": "https://chromereleases.googleblog.com/", 63 | "item_title": "New Chrome Release", 64 | "image_url": "https://bofa.cocolabs.dev/images/bofa_logo.png", 65 | "short_version": version, 66 | "update_download": download, 67 | "last_updated": release_time, 68 | }) 69 | return pkgs 70 | 71 | def parse_edge_packages(): 72 | pkgs = [] 73 | if not os.path.exists(edge_xml_path): 74 | return pkgs 75 | tree = ET.parse(edge_xml_path) 76 | root = tree.getroot() 77 | for version in root.findall('Version'): 78 | channel = version.find('Channel').text if version.find('Channel') is not None else '' 79 | if channel != 'current': 80 | continue # Only process the 'current' channel 81 | ver = version.find('Version').text if version.find('Version') is not None else '' 82 | download = version.find('Location').text if version.find('Location') is not None else '' 83 | date = version.find('Date').text if version.find('Date') is not None else '' 84 | pkgs.append({ 85 | "name": "Edge", 86 | "feed_filename": "edge_rss.xml", 87 | "channel_title": "BOFA - Edge RSS Feed", 88 | "channel_description": "Microsoft Edge for Mac", 89 | "release_notes_url": "https://learn.microsoft.com/en-us/deployedge/microsoft-edge-relnote-stable-channel", 90 | "item_title": "New Edge Release", 91 | "image_url": "https://bofa.cocolabs.dev/images/bofa_logo.png", 92 | "short_version": ver, 93 | "update_download": download, 94 | "last_updated": date, 95 | }) 96 | return pkgs 97 | 98 | def parse_firefox_packages(): 99 | pkgs = [] 100 | if not os.path.exists(firefox_xml_path): 101 | return pkgs 102 | tree = ET.parse(firefox_xml_path) 103 | root = tree.getroot() 104 | # Get the root-level for the feed's lastBuildDate if needed 105 | root_last_updated = root.find('last_updated').text if root.find('last_updated') is not None else '' 106 | for channel in ['stable']: 107 | node = root.find(channel) 108 | if node is not None: 109 | version = node.find('version').text if node.find('version') is not None else '' 110 | download = node.find('download').text if node.find('download') is not None else '' 111 | release_time = node.find('release_time').text if node.find('release_time') is not None else '' 112 | # Use for last_updated (pubDate) 113 | pkgs.append({ 114 | "name": "Firefox", 115 | "feed_filename": "firefox_rss.xml", 116 | "channel_title": "BOFA - Firefox RSS Feed", 117 | "channel_description": "Mozilla Firefox for Mac", 118 | "release_notes_url": "https://www.mozilla.org/en-US/firefox/notes/", 119 | "item_title": "New Firefox Release", 120 | "image_url": "https://bofa.cocolabs.dev/images/bofa_logo.png", 121 | "short_version": version, 122 | "update_download": download, 123 | "last_updated": release_time, 124 | }) 125 | return pkgs 126 | 127 | # Combine all browser packages 128 | PACKAGES = parse_chrome_packages() + parse_edge_packages() + parse_firefox_packages() 129 | 130 | # --- End new code for browser XML parsing --- 131 | 132 | # Helper: get all textual content from an element (text + children text/tails) 133 | def _get_all_text(el: ET.Element) -> str: 134 | parts = [] 135 | if el.text: 136 | parts.append(el.text) 137 | for child in el: 138 | if child.text: 139 | parts.append(child.text) 140 | if child.tail: 141 | parts.append(child.tail) 142 | return "".join(parts) 143 | 144 | # Build the as escaped HTML text (no child elements, no CDATA) 145 | def _set_description_with_link(desc_el: ET.Element, version: str, release_notes_url: str) -> None: 146 | desc_el.clear() 147 | desc_el.text = ( 148 | "
" 149 | f"Version: {version}" 150 | "
" 151 | f"Release Notes: Release Notes" 152 | ) 153 | 154 | # Register atom namespace for proper find/create 155 | ET.register_namespace('atom', 'http://www.w3.org/2005/Atom') 156 | ATOM_NS = {'atom': 'http://www.w3.org/2005/Atom'} 157 | 158 | def _ensure_feed_exists(feed_path: str) -> None: 159 | if not os.path.exists(feed_path) or os.path.getsize(feed_path) == 0: 160 | rss = ET.Element('rss', {'version': '2.0', 'xmlns:atom': 'http://www.w3.org/2005/Atom'}) 161 | ET.SubElement(rss, 'channel') 162 | tree = ET.ElementTree(rss) 163 | indent(rss) 164 | os.makedirs(os.path.dirname(feed_path), exist_ok=True) 165 | tree.write(feed_path, encoding='UTF-8', xml_declaration=True) 166 | 167 | def _find_package_node(root: ET.Element, package_name: str): 168 | for package in root.findall('package'): 169 | name_el = package.find('name') 170 | if name_el is not None and name_el.text == package_name: 171 | return package 172 | return None 173 | 174 | def _update_rss_for_package(pkg_conf: dict) -> None: 175 | # Use the new fields directly from pkg_conf 176 | short_version = (pkg_conf.get('short_version') or "").strip() 177 | update_download = (pkg_conf.get('update_download') or "").strip() 178 | last_updated = (pkg_conf.get('last_updated') or "").strip() 179 | 180 | # Compute per-package feed paths/urls 181 | feed_filename = pkg_conf['feed_filename'] 182 | # feed_path = os.path.join(FEEDS_DIR, feed_filename) 183 | # feed_url = f"{FEED_BASE_URL}/{feed_filename}" 184 | 185 | # Only write to browser-specific folders 186 | browser_folder = None 187 | if feed_filename.startswith("chrome_"): 188 | browser_folder = os.path.join(repo_root, "latest_chrome_files") 189 | elif feed_filename.startswith("edge_"): 190 | browser_folder = os.path.join(repo_root, "latest_edge_files") 191 | elif feed_filename.startswith("firefox_"): 192 | browser_folder = os.path.join(repo_root, "latest_firefox_files") 193 | if not browser_folder: 194 | print(f"{pkg_conf['name']}: Unknown browser type for feed file {feed_filename}; skipping.") 195 | return 196 | feed_path = os.path.join(browser_folder, feed_filename) 197 | feed_url = "" # Not used anymore 198 | 199 | # Prepare dates 200 | try: 201 | import re 202 | dt_str = last_updated 203 | # Remove trailing timezone abbreviation (e.g., " EST" or " EDT") 204 | dt_str_clean = re.sub(r' [A-Z]{2,4}$', '', dt_str) 205 | dt_obj = datetime.strptime(dt_str_clean, "%B %d, %Y %I:%M %p") 206 | last_build_date_text = dt_obj.strftime("%a, %d %b %Y %H:%M:%S +0000") 207 | except Exception: 208 | try: 209 | # Try parsing as date only (fallback for edge cases) 210 | dt_obj = datetime.strptime(last_updated, "%B %d, %Y") 211 | last_build_date_text = dt_obj.strftime("%a, %d %b %Y 00:00:00 +0000") 212 | except Exception: 213 | last_build_date_text = datetime.utcnow().strftime("%a, %d %b %Y 00:00:00 +0000") 214 | 215 | # Ensure feed file exists 216 | _ensure_feed_exists(feed_path) 217 | 218 | # Parse the RSS feed 219 | rss_tree = ET.parse(feed_path) 220 | rss_root = rss_tree.getroot() 221 | channel = rss_root.find('channel') 222 | 223 | # Initialize channel-level elements if they do not exist 224 | title = channel.find('title') 225 | link = channel.find('link') 226 | description = channel.find('description') 227 | docs = channel.find('docs') 228 | 229 | if title is None: 230 | title = ET.SubElement(channel, 'title') 231 | title.text = pkg_conf['channel_title'] 232 | 233 | if link is None: 234 | link = ET.SubElement(channel, 'link') 235 | # Always ensure canonical channel link points to the site (not the feed URL) 236 | link.text = SITE_URL 237 | 238 | if description is None: 239 | description = ET.SubElement(channel, 'description') 240 | description.text = pkg_conf['channel_description'] 241 | 242 | if docs is None: 243 | docs = ET.SubElement(channel, 'docs') 244 | docs.text = "http://www.rssboard.org/rss-specification" 245 | 246 | # Add/update atom:link rel="self" for the feed URL 247 | atom_link = channel.find('atom:link', ATOM_NS) 248 | if atom_link is None: 249 | atom_link = ET.SubElement(channel, '{http://www.w3.org/2005/Atom}link', { 250 | 'href': feed_url, 251 | 'rel': 'self', 252 | 'type': 'application/rss+xml' 253 | }) 254 | else: 255 | atom_link.set('href', feed_url) 256 | atom_link.set('rel', 'self') 257 | atom_link.set('type', 'application/rss+xml') 258 | 259 | # Add/ensure language, ttl, and lastBuildDate 260 | language = channel.find('language') 261 | if language is None: 262 | language = ET.SubElement(channel, 'language') 263 | language.text = 'en-US' 264 | 265 | ttl = channel.find('ttl') 266 | if ttl is None: 267 | ttl = ET.SubElement(channel, 'ttl') 268 | ttl.text = '60' 269 | 270 | last_build_date = channel.find('lastBuildDate') 271 | if last_build_date is None: 272 | last_build_date = ET.SubElement(channel, 'lastBuildDate') 273 | last_build_date.text = last_build_date_text 274 | 275 | # Add the element above the elements 276 | image = channel.find('image') 277 | if image is None: 278 | image = ET.Element('image') 279 | url = ET.SubElement(image, 'url') 280 | url.text = pkg_conf['image_url'] 281 | img_title = ET.SubElement(image, 'title') 282 | img_title.text = pkg_conf['channel_title'] 283 | img_link = ET.SubElement(image, 'link') 284 | img_link.text = SITE_URL 285 | first_item_index = next((i for i, elem in enumerate(channel) if elem.tag == 'item'), len(channel)) 286 | channel.insert(first_item_index, image) 287 | else: 288 | # Ensure image link points to the site URL 289 | img_link = image.find('link') 290 | if img_link is None: 291 | img_link = ET.SubElement(image, 'link') 292 | img_link.text = SITE_URL 293 | 294 | # Remove duplicate non-atom channel elements (keep the first one as canonical) 295 | links = channel.findall('link') 296 | if links: 297 | links[0].text = SITE_URL 298 | for extra in links[1:]: 299 | channel.remove(extra) 300 | else: 301 | ET.SubElement(channel, 'link').text = SITE_URL 302 | 303 | # Check if the package version already exists in the RSS feed 304 | existing_version = False 305 | for item in channel.findall('item'): 306 | title_el = item.find('title') 307 | desc_el = item.find('description') 308 | if not (title_el is None or desc_el is None): 309 | desc_text = _get_all_text(desc_el) 310 | if (short_version in (title_el.text or "")) or (short_version in desc_text): 311 | _set_description_with_link(desc_el, short_version, pkg_conf['release_notes_url']) 312 | existing_version = True 313 | print(f"{pkg_conf['name']}: version already in RSS feed") 314 | break 315 | 316 | # If the version is not already in the feed, add it 317 | if not existing_version: 318 | new_item = ET.Element('item') 319 | title_el = ET.SubElement(new_item, 'title') 320 | title_el.text = pkg_conf['item_title'] 321 | link_el = ET.SubElement(new_item, 'link') 322 | link_el.text = update_download 323 | desc_el = ET.SubElement(new_item, 'description') 324 | _set_description_with_link(desc_el, short_version, pkg_conf['release_notes_url']) 325 | pubDate = ET.SubElement(new_item, 'pubDate') 326 | pubDate.text = last_build_date_text 327 | guid = ET.SubElement(new_item, 'guid') 328 | guid.text = update_download 329 | guid.set('isPermaLink', 'false') 330 | 331 | # Insert the new item into the RSS feed 332 | first_item_index = next((i for i, elem in enumerate(channel) if elem.tag == 'item'), len(channel)) 333 | channel.insert(first_item_index, new_item) 334 | print(f"{pkg_conf['name']}: RSS feed updated with new version") 335 | 336 | # Always write updates (even if only header normalization happened) 337 | indent(rss_root) 338 | rss_tree.write(feed_path, encoding='UTF-8', xml_declaration=True) 339 | 340 | print(f"Wrote RSS feed to {feed_path}") 341 | 342 | # Process each configured package 343 | for pkg in PACKAGES: 344 | # No need to look up in latest.xml; use the pkg_conf fields directly 345 | if not pkg.get('short_version') or not pkg.get('update_download'): 346 | print(f"{pkg['name']}: missing version or download; skipping.") 347 | continue 348 | _update_rss_for_package(pkg) -------------------------------------------------------------------------------- /.github/actions/generate_firefox_latest.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import xml.etree.ElementTree as ET 3 | from datetime import datetime 4 | import re 5 | import subprocess 6 | import os 7 | import json 8 | import yaml 9 | import pytz 10 | 11 | # Fetch Firefox release and devedition JSON data 12 | url_releases = "https://product-details.mozilla.org/1.0/firefox.json" 13 | response_releases = requests.get(url_releases) 14 | data_releases = response_releases.json() 15 | 16 | url_devedition = "https://product-details.mozilla.org/1.0/devedition.json" 17 | response_devedition = requests.get(url_devedition) 18 | data_devedition = response_devedition.json() 19 | 20 | # Helper to get the final download URL for a Firefox product using curl 21 | def fetch_download_url(url): 22 | try: 23 | return subprocess.check_output( 24 | ["curl", url, "-s", "-L", "-I", "-o", "/dev/null", "-w", "%{url_effective}"] 25 | ).decode("utf-8").strip() 26 | except subprocess.CalledProcessError: 27 | return "N/A" 28 | 29 | # Get download URLs for all main Firefox channels 30 | firefox_download_url = fetch_download_url("https://download.mozilla.org/?product=firefox-pkg-latest-ssl&os=osx") 31 | firefox_esr_download_url = fetch_download_url("https://download.mozilla.org/?product=firefox-esr-pkg-latest-ssl&os=osx") 32 | firefox_beta_download_url = fetch_download_url("https://download.mozilla.org/?product=firefox-beta-pkg-latest-ssl&os=osx") 33 | firefox_nightly_download_url = fetch_download_url("https://download.mozilla.org/?product=firefox-nightly-pkg-latest-ssl&os=osx") 34 | firefox_devedition_download_url = fetch_download_url("https://download.mozilla.org/?product=firefox-devedition-latest-ssl&os=osx") 35 | 36 | # Build the main XML for latest Firefox versions 37 | root = ET.Element("mac_versions") 38 | 39 | # Add last_updated timestamp to the XML 40 | eastern = pytz.timezone('US/Eastern') 41 | last_updated = ET.SubElement(root, "last_updated") 42 | last_updated.text = datetime.now(eastern).strftime("%B %d, %Y %I:%M %p %Z") 43 | 44 | # Find the newest stable release (major or stability) 45 | stable_release = None 46 | for release_key, release in data_releases["releases"].items(): 47 | if re.match(r'^firefox-\d+(\.\d+)*$', release_key): 48 | if release.get("category") in ("major", "stability"): 49 | if stable_release is None or release.get("date", "") > stable_release.get("date", ""): 50 | stable_release = release 51 | 52 | # Add stable release info to XML 53 | stable = ET.SubElement(root, "stable") 54 | if stable_release: 55 | ET.SubElement(stable, "version").text = stable_release.get("version", "N/A") 56 | try: 57 | dt = datetime.strptime(stable_release.get("date", ""), "%Y-%m-%d") 58 | dt = eastern.localize(dt) 59 | ET.SubElement(stable, "release_time").text = dt.strftime("%B %d, %Y %I:%M %p %Z") 60 | except Exception: 61 | ET.SubElement(stable, "release_time").text = stable_release.get("date", "N/A") 62 | else: 63 | ET.SubElement(stable, "version").text = "N/A" 64 | ET.SubElement(stable, "release_time").text = "N/A" 65 | ET.SubElement(stable, "download").text = firefox_download_url 66 | 67 | # Find the newest beta release (dev category) 68 | beta_release = None 69 | for release_key, release in data_releases["releases"].items(): 70 | if re.match(r'^firefox-\d+(\.\d+)*b\d+$', release_key): 71 | if release.get("category") == "dev": 72 | if beta_release is None or release.get("date", "") > beta_release.get("date", ""): 73 | beta_release = release 74 | 75 | # Add beta release info to XML 76 | beta = ET.SubElement(root, "beta") 77 | if beta_release: 78 | ET.SubElement(beta, "version").text = beta_release.get("version", "N/A") 79 | try: 80 | dt = datetime.strptime(beta_release.get("date", ""), "%Y-%m-%d") 81 | dt = eastern.localize(dt) 82 | ET.SubElement(beta, "release_time").text = dt.strftime("%B %d, %Y %I:%M %p %Z") 83 | except Exception: 84 | ET.SubElement(beta, "release_time").text = beta_release.get("date", "N/A") 85 | else: 86 | ET.SubElement(beta, "version").text = "N/A" 87 | ET.SubElement(beta, "release_time").text = "N/A" 88 | ET.SubElement(beta, "download").text = firefox_beta_download_url 89 | 90 | # Find the newest Developer Edition release 91 | dev_release = None 92 | for release in data_devedition["releases"].values(): 93 | if dev_release is None or release.get("date", "") > dev_release.get("date", ""): 94 | dev_release = release 95 | 96 | # Add Developer Edition info to XML 97 | dev = ET.SubElement(root, "dev") 98 | if dev_release: 99 | ET.SubElement(dev, "version").text = dev_release.get("version", "N/A") 100 | try: 101 | dt = datetime.strptime(dev_release.get("date", ""), "%Y-%m-%d") 102 | dt = eastern.localize(dt) 103 | ET.SubElement(dev, "release_time").text = dt.strftime("%B %d, %Y %I:%M %p %Z") 104 | except Exception: 105 | ET.SubElement(dev, "release_time").text = dev_release.get("date", "N/A") 106 | else: 107 | ET.SubElement(dev, "version").text = "N/A" 108 | ET.SubElement(dev, "release_time").text = "N/A" 109 | ET.SubElement(dev, "download").text = firefox_devedition_download_url 110 | 111 | # Find the newest ESR release 112 | esr_release = None 113 | for release_key, release in data_releases["releases"].items(): 114 | if release_key.endswith("esr") and release.get("category") == "esr": 115 | if esr_release is None or release.get("date", "") > esr_release.get("date", ""): 116 | esr_release = release 117 | 118 | # Add ESR info to XML 119 | esr = ET.SubElement(root, "esr") 120 | if esr_release: 121 | ET.SubElement(esr, "version").text = esr_release.get("version", "N/A") 122 | try: 123 | dt = datetime.strptime(esr_release.get("date", ""), "%Y-%m-%d") 124 | dt = eastern.localize(dt) 125 | ET.SubElement(esr, "release_time").text = dt.strftime("%B %d, %Y %I:%M %p %Z") 126 | except Exception: 127 | ET.SubElement(esr, "release_time").text = esr_release.get("date", "N/A") 128 | else: 129 | ET.SubElement(esr, "version").text = "N/A" 130 | ET.SubElement(esr, "release_time").text = "N/A" 131 | ET.SubElement(esr, "download").text = firefox_esr_download_url 132 | 133 | # Add Nightly info to XML 134 | url_versions = "https://product-details.mozilla.org/1.0/firefox_versions.json" 135 | response_versions = requests.get(url_versions) 136 | data_versions = response_versions.json() 137 | 138 | nightly = ET.SubElement(root, "nightly") 139 | ET.SubElement(nightly, "version").text = data_versions.get("FIREFOX_NIGHTLY", "N/A") 140 | try: 141 | dt = datetime.strptime(data_versions.get("LAST_MERGE_DATE", ""), "%Y-%m-%d") 142 | dt = eastern.localize(dt) 143 | ET.SubElement(nightly, "release_time").text = dt.strftime("%B %d, %Y %I:%M %p %Z") 144 | except Exception: 145 | ET.SubElement(nightly, "release_time").text = data_versions.get("LAST_MERGE_DATE", "N/A") 146 | ET.SubElement(nightly, "download").text = firefox_nightly_download_url 147 | 148 | # Pretty-print XML for readability 149 | def pretty_print_xml(element, level=0): 150 | indent = " " 151 | if len(element): 152 | element.text = "\n" + indent * (level + 1) 153 | for child in element: 154 | pretty_print_xml(child, level + 1) 155 | child.tail = "\n" + indent * level 156 | if level and (not element.tail or not element.tail.strip()): 157 | element.tail = "\n" + indent * level 158 | 159 | # Save the main XML, JSON, and YAML for latest versions 160 | pretty_print_xml(root) 161 | xml_data = ET.tostring(root, encoding='utf8', method='xml').decode() 162 | 163 | output_dir = os.path.join(os.getcwd(), 'latest_firefox_files') 164 | os.makedirs(output_dir, exist_ok=True) 165 | with open(os.path.join(output_dir, "firefox_latest_versions.xml"), "w") as f: 166 | f.write(xml_data) 167 | print("firefox_latest_versions.xml created successfully in latest_firefox_files.") 168 | 169 | # Convert XML to dict for JSON/YAML export 170 | def xml_to_dict(element): 171 | if len(element) == 0: 172 | return element.text 173 | result = {} 174 | for child in element: 175 | child_dict = xml_to_dict(child) 176 | if child.tag in result: 177 | if not isinstance(result[child.tag], list): 178 | result[child.tag] = [result[child.tag]] 179 | result[child.tag].append(child_dict) 180 | else: 181 | result[child.tag] = child_dict 182 | return result 183 | 184 | data_dict = xml_to_dict(root) 185 | json_data = json.dumps(data_dict, indent=2) 186 | yaml_data = yaml.dump(data_dict, sort_keys=False) 187 | 188 | with open(os.path.join(output_dir, "firefox_latest_versions.json"), "w") as f: 189 | f.write(json_data) 190 | with open(os.path.join(output_dir, "firefox_latest_versions.yaml"), "w") as f: 191 | f.write(yaml_data) 192 | print("firefox_latest_versions.json and firefox_latest_versions.yaml created successfully in latest_firefox_files.") 193 | 194 | # Helper to get the current last_updated string 195 | def get_last_updated_str(): 196 | eastern = pytz.timezone('US/Eastern') 197 | return datetime.now(eastern).strftime("%B %d, %Y %I:%M %p %Z") 198 | 199 | def make_pkg_links(version, date=None): 200 | """ 201 | Returns two links for the given version string. 202 | If date is prior to 2022-01-01, returns the base ftp.mozilla.org URL for both. 203 | Otherwise, just construct the URLs (do not check with curl for speed). 204 | """ 205 | safe_version = str(version) 206 | # If date is provided and prior to 2022-01-01, return base URL for both 207 | if date: 208 | try: 209 | if date < "2022-01-01": 210 | return [ 211 | "https://ftp.mozilla.org/pub/firefox/releases/", 212 | "https://ftp.mozilla.org/pub/firefox/releases/" 213 | ] 214 | except Exception: 215 | pass 216 | base_url = f"https://ftp.mozilla.org/pub/firefox/releases/{safe_version}/mac/en-US/" 217 | pkg_name = f"Firefox%20{safe_version}.pkg" 218 | dmg_name = f"Firefox%20{safe_version}.dmg" 219 | # Do not check with curl, just return the constructed URLs 220 | pkg_url = base_url + pkg_name 221 | dmg_url = base_url + dmg_name 222 | return [pkg_url, dmg_url] 223 | 224 | # Write all Firefox release history files (all channels, newest first) 225 | def write_firefox_all_history_files(): 226 | url = "https://product-details.mozilla.org/1.0/firefox.json" 227 | data = requests.get(url).json() 228 | releases = [] 229 | for key, info in data.get("releases", {}).items(): 230 | entry = dict(key=key) 231 | entry.update(info) 232 | # Add links for each release (use 'version' field) 233 | version = entry.get("version") 234 | date = entry.get("date") 235 | if version: 236 | links = make_pkg_links(version, date) 237 | entry["download_pkg"] = links[0] 238 | entry["download_dmg"] = links[1] 239 | releases.append(entry) 240 | releases.sort(key=lambda x: x.get("date", ""), reverse=True) 241 | root = ET.Element("firefox_all_history") 242 | last_updated = ET.SubElement(root, "last_updated") 243 | last_updated.text = get_last_updated_str() 244 | for rel in releases: 245 | rel_elem = ET.SubElement(root, "release") 246 | for k, v in rel.items(): 247 | if k not in ("download_pkg", "download_dmg"): 248 | ET.SubElement(rel_elem, k).text = str(v) 249 | # Only add one pkg and one dmg link per release 250 | if rel.get("download_pkg"): 251 | ET.SubElement(rel_elem, "download_pkg").text = rel["download_pkg"] 252 | if rel.get("download_dmg"): 253 | ET.SubElement(rel_elem, "download_dmg").text = rel["download_dmg"] 254 | pretty_print_xml(root) 255 | xml_data = ET.tostring(root, encoding='utf8', method='xml').decode() 256 | json_obj = {"last_updated": get_last_updated_str(), "releases": releases} 257 | json_data = json.dumps(json_obj, indent=2) 258 | yaml_data = yaml.dump(json_obj, sort_keys=False) 259 | with open(os.path.join(output_dir, "firefox_all_history.xml"), "w") as f: 260 | f.write(xml_data) 261 | with open(os.path.join(output_dir, "firefox_all_history.json"), "w") as f: 262 | f.write(json_data) 263 | with open(os.path.join(output_dir, "firefox_all_history.yaml"), "w") as f: 264 | f.write(yaml_data) 265 | print("firefox_all_history.xml, .json, .yaml created successfully in latest_firefox_files.") 266 | 267 | # Write Firefox beta/dev history files (newest first) 268 | def write_firefox_beta_dev_history_files(): 269 | url = "https://product-details.mozilla.org/1.0/firefox_history_development_releases.json" 270 | data = requests.get(url).json() 271 | releases = [] 272 | for key, info in data.items(): 273 | # info is a date string 274 | entry = dict(version=key, date=info) 275 | links = make_pkg_links(key, info) 276 | entry["download_pkg"] = links[0] 277 | entry["download_dmg"] = links[1] 278 | releases.append(entry) 279 | releases.sort(key=lambda x: x.get("date", ""), reverse=True) 280 | root = ET.Element("firefox_beta_dev_history") 281 | last_updated = ET.SubElement(root, "last_updated") 282 | last_updated.text = get_last_updated_str() 283 | for rel in releases: 284 | rel_elem = ET.SubElement(root, "release") 285 | for k, v in rel.items(): 286 | if k not in ("download_pkg", "download_dmg"): 287 | ET.SubElement(rel_elem, k).text = str(v) 288 | # Only add one pkg and one dmg link per release 289 | ET.SubElement(rel_elem, "download_pkg").text = rel["download_pkg"] 290 | ET.SubElement(rel_elem, "download_dmg").text = rel["download_dmg"] 291 | pretty_print_xml(root) 292 | xml_data = ET.tostring(root, encoding='utf8', method='xml').decode() 293 | json_obj = {"last_updated": get_last_updated_str(), "releases": releases} 294 | json_data = json.dumps(json_obj, indent=2) 295 | yaml_data = yaml.dump(json_obj, sort_keys=False) 296 | with open(os.path.join(output_dir, "firefox_beta_dev_history.xml"), "w") as f: 297 | f.write(xml_data) 298 | with open(os.path.join(output_dir, "firefox_beta_dev_history.json"), "w") as f: 299 | f.write(json_data) 300 | with open(os.path.join(output_dir, "firefox_beta_dev_history.yaml"), "w") as f: 301 | f.write(yaml_data) 302 | print("firefox_beta_dev_history.xml, .json, .yaml created successfully in latest_firefox_files.") 303 | 304 | # Write all Firefox version info files (structure as-is, with last_updated at the top) 305 | def write_firefox_all_version_info_files(): 306 | url = "https://product-details.mozilla.org/1.0/firefox_versions.json" 307 | data = requests.get(url).json() 308 | # XML 309 | def dict_to_xml(parent, d): 310 | for k, v in d.items(): 311 | if isinstance(v, dict): 312 | child = ET.SubElement(parent, k) 313 | dict_to_xml(child, v) 314 | else: 315 | ET.SubElement(parent, k).text = str(v) 316 | root = ET.Element("firefox_all_version_info") 317 | last_updated = ET.SubElement(root, "last_updated") 318 | last_updated.text = get_last_updated_str() 319 | dict_to_xml(root, data) 320 | pretty_print_xml(root) 321 | xml_data = ET.tostring(root, encoding='utf8', method='xml').decode() 322 | # JSON/YAML with last_updated at the top 323 | json_obj = {"last_updated": get_last_updated_str()} 324 | json_obj.update(data) 325 | json_data = json.dumps(json_obj, indent=2) 326 | yaml_data = yaml.dump(json_obj, sort_keys=False) 327 | # Write files 328 | with open(os.path.join(output_dir, "firefox_all_version_info.xml"), "w") as f: 329 | f.write(xml_data) 330 | with open(os.path.join(output_dir, "firefox_all_version_info.json"), "w") as f: 331 | f.write(json_data) 332 | with open(os.path.join(output_dir, "firefox_all_version_info.yaml"), "w") as f: 333 | f.write(yaml_data) 334 | print("firefox_all_version_info.xml, .json, .yaml created successfully in latest_firefox_files.") 335 | 336 | # Run all history/version info writers 337 | write_firefox_all_history_files() 338 | write_firefox_beta_dev_history_files() 339 | write_firefox_all_version_info_files() 340 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # **BOFA** 2 | **B**rowser **O**verview **F**eed for **A**pple 3 | 4 | MOFA Image 5 | 6 | Welcome to the **BOFA** repository! This resource tracks the latest versions of major web browsers for macOS. Feeds are automatically updated every hour from XML and JSON links directly from vendors. 7 | 8 | We welcome community contributions—fork the repository, ask questions, or share insights to help keep this resource accurate and useful for everyone. Check out the user-friendly website version below for an easier browsing experience! 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 24 | 25 |
🌟 Explore the BOFA Website 🌟⭐ Support the Project – Give it a Star! ⭐
🌐 Visit: bofa.cocolabs.dev 🌐 20 | 21 | GitHub Repo Stars 22 | 23 |
26 | 27 | 28 | ## Latest Stable Browser Versions 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 |
Chrome
Chrome
143.0.7499.170

Last Update:
December 18, 2025

Release Notes
Firefox
Firefox
146.0.1

Last Update:
December 18, 2025

Release Notes
Edge
Edge
143.0.3650.96

Last Update:
December 20, 2025

Release Notes
Safari
Safari
26.3 (20623.2.2)

Last Update:
December 15, 2025

Release Notes
38 | 39 | 40 | ## Browser Packages 41 | 42 | All links below direct to the official browser vendor. The links provided will always download the latest available version as of the last scan update. 43 | 44 | **Chrome**: [**_Raw XML_**](latest_chrome_files/chrome_latest_versions.xml) [**_Raw YAML_**](latest_chrome_files/chrome_latest_versions.yaml) [**_Raw JSON_**](latest_chrome_files/chrome_latest_versions.json) | **Firefox**: [**_Raw XML_**](latest_firefox_files/firefox_latest_versions.xml) [**_Raw YAML_**](latest_firefox_files/firefox_latest_versions.yaml) [**_Raw JSON_**](latest_firefox_files/firefox_latest_versions.json) 45 | 46 | **Edge**: [**_Raw XML_**](latest_edge_files/edge_latest_versions.xml) [**_Raw YAML_**](latest_edge_files/edge_latest_versions.yaml) [**_Raw JSON_**](latest_edge_files/edge_latest_versions.json) | **Safari**: [**_Raw XML_**](latest_safari_files/safari_latest_versions.xml) [**_Raw YAML_**](latest_safari_files/safari_latest_versions.yaml) [**_Raw JSON_**](latest_safari_files/safari_latest_versions.json) 47 | 48 | _Last Updated: December 21, 2025 04:04 AM EST (Automatically Updated every hour)_ 49 | 50 |
51 | 52 | | **Browser** | **CFBundle Version** | **CFBundle Identifier** | **Download** | 53 | |------------|-------------------|---------------------|------------| 54 | | **Chrome**
_Release Notes_

Last Updated:
December 18, 2025 | `143.0.7499.170` | `com.google.Chrome` | Download Chrome | 55 | | **Chrome** Extended Stable

_Requires `TargetChannel` policy; link is for Stable._


Last Updated:
December 18, 2025 | `142.0.7444.243` | `com.google.Chrome` | Download Chrome | 56 | | **Chrome** Beta
_Release Notes_

Last Updated:
December 17, 2025 | `144.0.7559.31` | `com.google.Chrome.beta` | Download Chrome | 57 | | **Chrome** Dev
_Release Notes_

Last Updated:
December 11, 2025 | `145.0.7572.2` | `com.google.Chrome.dev` | Download Chrome | 58 | | **Chrome** Canary

Last Updated:
December 21, 2025 | `145.0.7590.0` | `com.google.Chrome.canary` | Download Chrome | 59 | | **Chrome** Canary ASAN

Last Updated:
December 20, 2025 | `145.0.7589.1` | `com.google.Chrome.canary` | Download Chrome | 60 | | **Firefox**
_Release Notes_

Last Updated:
December 18, 2025 | `146.0.1` | `org.mozilla.firefox` | Download Firefox | 61 | | **Firefox** Beta
_Release Notes_

Last Updated:
December 19, 2025 | `147.0b6` | `org.mozilla.firefoxbeta` | Download Firefox | 62 | | **Firefox** Developer
_Release Notes_

Last Updated:
December 19, 2025 | `147.0b6` | `org.mozilla.firefoxdev` | Download Firefox | 63 | | **Firefox** ESR
_Release Notes_

Last Updated:
December 09, 2025 | `140.6.0` | `org.mozilla.firefoxesr` | Download Firefox | 64 | | **Firefox** Nightly
_Release Notes_

Last Updated:
December 08, 2025 | `148.0a1` | `org.mozilla.nightly` | Download Firefox | 65 | | **Edge**
_Release Notes_

Last Updated:
December 20, 2025 | `143.0.3650.96` | `com.microsoft.edgemac` | Download Edge | 66 | | **Edge** Beta
_Release Notes_

Last Updated:
December 15, 2025 | `144.0.3719.18` | `com.microsoft.edgemac.beta` | Download Edge | 67 | | **Edge** Developer

Last Updated:
December 17, 2025 | `145.0.3734.2` | `com.microsoft.edgemac.dev` | Download Edge | 68 | | **Edge** Canary

Last Updated:
December 19, 2025 | `145.0.3748.0` | `com.microsoft.edgemac.canary` | Download Edge | 69 | 70 | 71 | | **Browser** | **Version** | **CFBundle Identifier** | **Release Notes** | 72 | |------------|-------------------|---------------------|------------| 73 | | **Safari**

Released:
December 15, 2025 | `26.3 (20623.2.2)` | `com.apple.Safari` |
Safari Release Notes | 74 | | **Safari**

Released:
July 29, 2025 | `18.6 (20621.3.11)` | `com.apple.Safari` |
Safari Release Notes | 75 | | **Safari**

Released:
July 29, 2024 | `17.6 (19618.3.11)` | `com.apple.Safari` |
Safari Release Notes | 76 | | **Safari**

Released:
July 24, 2023 | `16.6 (18615.3.12)` | `com.apple.Safari` |
Safari Release Notes | 77 | | **Safari**

Released:
July 20, 2022 | `15.6 (17613.3.9)` | `com.apple.Safari` |
Safari Release Notes | 78 | | **Safari**

Released:
April 26, 2021 | `14.1 (16611.1.21)` | `com.apple.Safari` |
Safari Release Notes | 79 | | **Safari**

Released:
March 24, 2020 | `13.1 (15609.1.20)` | `com.apple.Safari` |
Safari Release Notes | 80 | | **Safari**

Released:
March 25, 2019 | `12.1 (14607.1.40)` | `com.apple.Safari` |
Safari Release Notes | 81 | 82 | | **Browser** | **Version** | **CFBundle Identifier** | **Download** | 83 | |------------|-------------------|---------------------|------------| 84 | | **Safari Technology Preview (Tahoe)**

Post Date:
December 19, 2025 | `234` | `com.apple.SafariTechnologyPreview` |
Download Safari Technology Preview <sup>(Tahoe)</sup>
| 85 | | **Safari Technology Preview (Sequoia)**

Post Date:
December 19, 2025 | `234` | `com.apple.SafariTechnologyPreview` |
Download Safari Technology Preview <sup>(Sequoia)</sup>
| 86 | 87 | 88 | ## Browser Settings Management 89 | 90 | View your current browser policies and explore available policy options: 91 | 92 | ### Chrome Chrome 93 | 1. **View Current Policies**: Enter `chrome://policy` in your address bar to see active policies 94 | 2. **Available Options**: [Chrome Enterprise Policy Documentation](https://chromeenterprise.google/policies/) 95 | 96 | ### Firefox Firefox 97 | 1. **View Current Policies**: Enter `about:policies` in your address bar to see active policies 98 | 2. **Available Options**: [Firefox Policy Documentation](https://mozilla.github.io/policy-templates/) 99 | 100 | ### Edge Edge 101 | 1. **View Current Policies**: Enter `edge://policy` in your address bar to see active policies 102 | 2. **Available Options**: [Edge Policy Documentation](https://learn.microsoft.com/en-us/deployedge/microsoft-edge-policies) 103 | 104 | ### Safari Safari 105 | 1. **View Current Policies**: Open System Settings > Profiles & Device Management 106 | 2. **Available Options**: [Safari Configuration Profile Reference](https://support.apple.com/guide/deployment/welcome/web) 107 | 108 | -------------------------------------------------------------------------------- /.github/actions/generate_chrome_latest.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | import xml.etree.ElementTree as ET 3 | from xml.dom import minidom 4 | import json 5 | from datetime import datetime 6 | import os 7 | import yaml 8 | from pytz import timezone 9 | 10 | def fetch_chrome_versions(channel): 11 | """ 12 | Fetch Chrome version history for a given channel using the Google Version History API. 13 | """ 14 | print(f"Fetching Chrome version history for channel: {channel}") 15 | url = f"https://versionhistory.googleapis.com/v1/chrome/platforms/mac/channels/{channel}/versions" 16 | result = subprocess.run(["curl", url], capture_output=True, text=True) 17 | return result.stdout 18 | 19 | def fetch_mac_version(channel): 20 | """ 21 | Fetch the latest Mac Chrome version for a given channel. 22 | Returns a dict with version, formatted release time, and timestamp. 23 | """ 24 | print(f"Fetching latest Mac version for channel: {channel}") 25 | url = f"https://versionhistory.googleapis.com/v1/chrome/platforms/mac/channels/{channel.lower()}/versions/all/releases?filter=endtime=none" 26 | result = subprocess.run(["curl", "-s", url], capture_output=True, text=True) 27 | if not result.stdout: 28 | return {"version": "N/A", "time": "N/A", "timestamp": "N/A"} 29 | try: 30 | data = json.loads(result.stdout) 31 | releases = data.get("releases", []) 32 | if not releases: 33 | return {"version": "N/A", "time": "N/A", "timestamp": "N/A"} 34 | # Find the release with the highest fraction, or the most recent startTime 35 | latest_release = max( 36 | releases, 37 | key=lambda r: ( 38 | r.get("fraction", 0), 39 | r.get("serving", {}).get("startTime", "") 40 | ) 41 | ) 42 | version = latest_release.get("version", "N/A") 43 | start_time_str = latest_release.get("serving", {}).get("startTime", None) 44 | if start_time_str: 45 | dt = datetime.strptime(start_time_str, "%Y-%m-%dT%H:%M:%S.%fZ") 46 | dt = timezone('US/Eastern').localize(dt) 47 | release_time = dt.strftime("%B %d, %Y %I:%M %p %Z") 48 | timestamp = int(dt.timestamp() * 1000) 49 | else: 50 | release_time = "N/A" 51 | timestamp = "N/A" 52 | return {"version": version, "time": release_time, "timestamp": timestamp} 53 | except Exception as e: 54 | print(f"Error fetching mac version for channel {channel}: {e}") 55 | return {"version": "N/A", "time": "N/A", "timestamp": "N/A"} 56 | 57 | def convert_to_xml(json_data): 58 | """ 59 | Convert Chrome version data from JSON to XML format. 60 | """ 61 | root = ET.Element("versions") 62 | last_updated = ET.SubElement(root, "last_updated") 63 | last_updated.text = datetime.now(timezone('US/Eastern')).strftime("%B %d, %Y %I:%M %p %Z") 64 | for version in json_data["versions"]: 65 | version_element = ET.SubElement(root, "version") 66 | name_element = ET.SubElement(version_element, "name") 67 | name_element.text = version["name"] 68 | version_element = ET.SubElement(version_element, "version") 69 | version_element.text = version["version"] 70 | return minidom.parseString(ET.tostring(root)).toprettyxml(indent=" ") 71 | 72 | def convert_to_yaml(json_data): 73 | """ 74 | Convert Chrome version data from JSON to YAML format. 75 | """ 76 | last_updated = {"last_updated": datetime.now(timezone('US/Eastern')).strftime("%B %d, %Y %I:%M %p %Z")} 77 | json_data.update(last_updated) 78 | if "nextPageToken" in json_data and not json_data["nextPageToken"]: 79 | del json_data["nextPageToken"] 80 | return yaml.dump(json_data, default_flow_style=False) 81 | 82 | def convert_to_json(json_data): 83 | """ 84 | Convert Chrome version data to JSON string with last_updated. 85 | """ 86 | last_updated = {"last_updated": datetime.now(timezone('US/Eastern')).strftime("%B %d, %Y %I:%M %p %Z")} 87 | json_data = {**last_updated, **json_data} 88 | if "nextPageToken" in json_data and not json_data["nextPageToken"]: 89 | del json_data["nextPageToken"] 90 | return json.dumps(json_data, indent=2) 91 | 92 | def convert_mac_versions_to_xml(stable, extended, beta, dev, canary, canary_asan): 93 | """ 94 | Convert Mac Chrome channel versions to XML format. 95 | """ 96 | root = ET.Element("mac_versions") 97 | last_updated = ET.SubElement(root, "last_updated") 98 | last_updated.text = datetime.now(timezone('US/Eastern')).strftime("%B %d, %Y %I:%M %p %Z") 99 | 100 | stable_element = ET.SubElement(root, "stable") 101 | version_element = ET.SubElement(stable_element, "version") 102 | version_element.text = stable["version"] 103 | time_element = ET.SubElement(stable_element, "release_time") 104 | time_element.text = stable["time"] 105 | download_url = ET.SubElement(stable_element, "download_link") 106 | download_url.text = "https://dl.google.com/chrome/mac/stable/accept_tos%3Dhttps%253A%252F%252Fwww.google.com%252Fintl%252Fen_ph%252Fchrome%252Fterms%252F%26_and_accept_tos%3Dhttps%253A%252F%252Fpolicies.google.com%252Fterms/googlechrome.pkg" 107 | 108 | extended_element = ET.SubElement(root, "extended") 109 | version_element = ET.SubElement(extended_element, "version") 110 | version_element.text = extended["version"] 111 | time_element = ET.SubElement(extended_element, "release_time") 112 | time_element.text = extended["time"] 113 | download_url = ET.SubElement(extended_element, "download_link") 114 | download_url.text = "https://dl.google.com/chrome/mac/stable/accept_tos%3Dhttps%253A%252F%252Fwww.google.com%252Fintl%252Fen_ph%252Fchrome%252Fterms%252F%26_and_accept_tos%3Dhttps%253A%252F%252Fpolicies.google.com%252Fterms/googlechrome.pkg" 115 | 116 | beta_element = ET.SubElement(root, "beta") 117 | version_element = ET.SubElement(beta_element, "version") 118 | version_element.text = beta["version"] 119 | time_element = ET.SubElement(beta_element, "release_time") 120 | time_element.text = beta["time"] 121 | download_url = ET.SubElement(beta_element, "download_link") 122 | download_url.text = "https://dl.google.com/chrome/mac/beta/accept_tos%3Dhttps%253A%252F%252Fwww.google.com%252Fintl%252Fen_ph%252Fchrome%252Fterms%252F%26_and_accept_tos%3Dhttps%253A%252F%252Fpolicies.google.com%252Fterms/googlechrome.pkg" 123 | 124 | dev_element = ET.SubElement(root, "dev") 125 | version_element = ET.SubElement(dev_element, "version") 126 | version_element.text = dev["version"] 127 | time_element = ET.SubElement(dev_element, "release_time") 128 | time_element.text = dev["time"] 129 | download_url = ET.SubElement(dev_element, "download_link") 130 | download_url.text = "https://dl.google.com/chrome/mac/universal/dev/googlechromedev.dmg" 131 | 132 | canary_element = ET.SubElement(root, "canary") 133 | version_element = ET.SubElement(canary_element, "version") 134 | version_element.text = canary["version"] 135 | time_element = ET.SubElement(canary_element, "release_time") 136 | time_element.text = canary["time"] 137 | download_url = ET.SubElement(canary_element, "download_link") 138 | download_url.text = "https://dl.google.com/chrome/mac/universal/canary/googlechromecanary.dmg" 139 | 140 | canary_asan_element = ET.SubElement(root, "canary_asan") 141 | version_element = ET.SubElement(canary_asan_element, "version") 142 | version_element.text = canary_asan["version"] 143 | time_element = ET.SubElement(canary_asan_element, "release_time") 144 | time_element.text = canary_asan["time"] 145 | download_url = ET.SubElement(canary_asan_element, "download_link") 146 | download_url.text = "https://dl.google.com/chrome/mac/universal/canary/googlechromecanary.dmg" 147 | 148 | return minidom.parseString(ET.tostring(root)).toprettyxml(indent=" ") 149 | 150 | def convert_mac_versions_to_yaml(stable, extended, beta, dev, canary, canary_asan): 151 | """ 152 | Convert Mac Chrome channel versions to YAML format. 153 | """ 154 | last_updated = {"last_updated": datetime.now(timezone('US/Eastern')).strftime("%B %d, %Y %I:%M %p %Z")} 155 | mac_versions = { 156 | "stable": { 157 | "version": stable["version"], 158 | "time": stable["time"], 159 | "download_link": "https://dl.google.com/chrome/mac/stable/accept_tos%3Dhttps%253A%252F%252Fwww.google.com%252Fintl%252Fen_ph%252Fchrome%252Fterms%252F%26_and_accept_tos%3Dhttps%253A%252F%252Fpolicies.google.com%252Fterms/googlechrome.pkg" 160 | }, 161 | "extended": { 162 | "version": extended["version"], 163 | "time": extended["time"], 164 | "download_link": "https://dl.google.com/chrome/mac/stable/accept_tos%3Dhttps%253A%252F%252Fwww.google.com%252Fintl%252Fen_ph%252Fchrome%252Fterms%252F%26_and_accept_tos%3Dhttps%253A%252F%252Fpolicies.google.com%252Fterms/googlechrome.pkg" 165 | }, 166 | "beta": { 167 | "version": beta["version"], 168 | "time": beta["time"], 169 | "download_link": "https://dl.google.com/chrome/mac/beta/accept_tos%3Dhttps%253A%252F%252Fwww.google.com%252Fintl%252Fen_ph%252Fchrome%252Fterms%252F%26_and_accept_tos%3Dhttps%253A%252F%252Fpolicies.google.com%252Fterms/googlechrome.pkg" 170 | }, 171 | "dev": { 172 | "version": dev["version"], 173 | "time": dev["time"], 174 | "download_link": "https://dl.google.com/chrome/mac/universal/dev/googlechromedev.dmg" 175 | }, 176 | "canary": { 177 | "version": canary["version"], 178 | "time": canary["time"], 179 | "download_link": "https://dl.google.com/chrome/mac/universal/canary/googlechromecanary.dmg" 180 | }, 181 | "canary_asan": { 182 | "version": canary_asan["version"], 183 | "time": canary_asan["time"], 184 | "download_link": "https://dl.google.com/chrome/mac/universal/canary/googlechromecanary.dmg" 185 | } 186 | } 187 | mac_versions = {**last_updated, **mac_versions} 188 | return yaml.dump(mac_versions, default_flow_style=False) 189 | 190 | def convert_mac_versions_to_json(stable, extended, beta, dev, canary, canary_asan): 191 | """ 192 | Convert Mac Chrome channel versions to JSON format. 193 | """ 194 | mac_versions = { 195 | "stable": { 196 | "version": stable["version"], 197 | "time": stable["time"], 198 | "download_link": "https://dl.google.com/chrome/mac/stable/accept_tos%3Dhttps%253A%252F%252Fwww.google.com%252Fintl%252Fen_ph%252Fchrome%252Fterms%252F%26_and_accept_tos%3Dhttps%253A%252F%252Fpolicies.google.com%252Fterms/googlechrome.pkg" 199 | }, 200 | "extended": { 201 | "version": extended["version"], 202 | "time": extended["time"], 203 | "download_link": "https://dl.google.com/chrome/mac/stable/accept_tos%3Dhttps%253A%252F%252Fwww.google.com%252Fintl%252Fen_ph%252Fchrome%252Fterms%252F%26_and_accept_tos%3Dhttps%253A%252F%252Fpolicies.google.com%252Fterms/googlechrome.pkg" 204 | }, 205 | "beta": { 206 | "version": beta["version"], 207 | "time": beta["time"], 208 | "download_link": "https://dl.google.com/chrome/mac/beta/accept_tos%3Dhttps%253A%252F%252Fwww.google.com%252Fintl%252Fen_ph%252Fchrome%252Fterms%252F%26_and_accept_tos%3Dhttps%253A%252F%252Fpolicies.google.com%252Fterms/googlechrome.pkg" 209 | }, 210 | "dev": { 211 | "version": dev["version"], 212 | "time": dev["time"], 213 | "download_link": "https://dl.google.com/chrome/mac/universal/dev/googlechromedev.dmg" 214 | }, 215 | "canary": { 216 | "version": canary["version"], 217 | "time": canary["time"], 218 | "download_link": "https://dl.google.com/chrome/mac/universal/canary/googlechromecanary.dmg" 219 | }, 220 | "canary_asan": { 221 | "version": canary_asan["version"], 222 | "time": canary_asan["time"], 223 | "download_link": "https://dl.google.com/chrome/mac/universal/canary/googlechromecanary.dmg" 224 | } 225 | } 226 | last_updated = {"last_updated": datetime.now(timezone('US/Eastern')).strftime("%B %d, %Y %I:%M %p %Z")} 227 | mac_versions = {**last_updated, **mac_versions} 228 | return json.dumps(mac_versions, indent=2) 229 | 230 | def fetch_chrome_history(channel): 231 | """ 232 | Fetch Chrome release history for a given channel. 233 | Returns a list of dicts with version, release date, end date, fraction, and fraction group. 234 | """ 235 | url = f"https://versionhistory.googleapis.com/v1/chrome/platforms/mac/channels/{channel}/versions/all/releases" 236 | result = subprocess.run(["curl", "-s", url], capture_output=True, text=True) 237 | if not result.stdout: 238 | return [] 239 | try: 240 | data = json.loads(result.stdout) 241 | releases = data.get("releases", []) 242 | history = [] 243 | for release in releases: 244 | version = release.get("version") 245 | start_time = format_time(release.get("serving", {}).get("startTime")) 246 | end_time = format_time(release.get("serving", {}).get("endTime")) 247 | fraction_val = release.get("fraction", 0) 248 | # Format fraction as a percentage string, up to two decimals 249 | fraction_pct = fraction_val * 100 250 | if fraction_pct == int(fraction_pct): 251 | fraction = f"{int(fraction_pct)}%" 252 | else: 253 | fraction = f"{fraction_pct:.2f}".rstrip('0').rstrip('.') + "%" 254 | fraction_group = release.get("fractionGroup", "N/A") 255 | history.append({ 256 | "version": version, 257 | "release_date": start_time, 258 | "end_date": end_time, 259 | "fraction": fraction, 260 | "fraction_group": fraction_group 261 | }) 262 | return history 263 | except Exception as e: 264 | print(f"Error processing history: {e}") 265 | return [] 266 | 267 | def format_time(iso_time): 268 | """ 269 | Convert ISO time string to formatted date string. 270 | """ 271 | if not iso_time: 272 | return "N/A" 273 | try: 274 | dt = datetime.strptime(iso_time, "%Y-%m-%dT%H:%M:%S.%fZ") 275 | return dt.strftime("%B %d, %Y %I:%M %p") 276 | except ValueError: 277 | return "Invalid Time" 278 | 279 | def convert_history_to_json(history): 280 | """ 281 | Convert Chrome release history to JSON string. 282 | """ 283 | return json.dumps({"releases": history}, indent=2) 284 | 285 | def convert_history_to_yaml(history): 286 | """ 287 | Convert Chrome release history to YAML string. 288 | """ 289 | return yaml.dump({"releases": history}, default_flow_style=False) 290 | 291 | def convert_history_to_xml(history): 292 | """ 293 | Convert Chrome release history to XML format. 294 | """ 295 | root = ET.Element("releases") 296 | last_updated = ET.SubElement(root, "last_updated") 297 | last_updated.text = datetime.now(timezone('US/Eastern')).strftime("%B %d, %Y %I:%M %p %Z") 298 | for entry in history: 299 | release_element = ET.SubElement(root, "release") 300 | version_element = ET.SubElement(release_element, "version") 301 | version_element.text = entry["version"] 302 | start_time_element = ET.SubElement(release_element, "release_date") 303 | start_time_element.text = entry["release_date"] 304 | end_time_element = ET.SubElement(release_element, "end_date") 305 | end_time_element.text = entry["end_date"] 306 | fraction_element = ET.SubElement(release_element, "fraction") 307 | fraction_element.text = entry["fraction"] 308 | fraction_group_element = ET.SubElement(release_element, "fraction_group") 309 | fraction_group_element.text = str(entry["fraction_group"]) 310 | return minidom.parseString(ET.tostring(root)).toprettyxml(indent=" ") 311 | 312 | def main(): 313 | """ 314 | Main function to fetch, convert, and save Chrome version and history data for all channels. 315 | """ 316 | # Create the output directory if it doesn't exist 317 | output_dir = "latest_chrome_files" 318 | os.makedirs(output_dir, exist_ok=True) 319 | print(f"Output directory: {output_dir}") 320 | 321 | channels = [ 322 | {"name": "extended", "channelType": "EXTENDED"}, 323 | {"name": "stable", "channelType": "STABLE"}, 324 | {"name": "beta", "channelType": "BETA"}, 325 | {"name": "dev", "channelType": "DEV"}, 326 | {"name": "canary", "channelType": "CANARY"}, 327 | {"name": "canary_asan", "channelType": "CANARY_ASAN"} 328 | ] 329 | 330 | # Fetch and save version history for each channel in XML, YAML, and JSON 331 | for channel in channels: 332 | print(f"Processing channel: {channel['channelType']}") 333 | try: 334 | json_data = json.loads(fetch_chrome_versions(channel["name"])) 335 | except Exception as e: 336 | print(f"Error fetching versions for {channel['name']}: {e}") 337 | continue 338 | xml_data = convert_to_xml(json_data) 339 | yaml_data = convert_to_yaml(json_data) 340 | json_data_str = convert_to_json(json_data) 341 | 342 | xml_filename = os.path.join(output_dir, f"chrome_{channel['channelType'].lower()}_history.xml") 343 | yaml_filename = os.path.join(output_dir, f"chrome_{channel['channelType'].lower()}_history.yaml") 344 | json_filename = os.path.join(output_dir, f"chrome_{channel['channelType'].lower()}_history.json") 345 | 346 | with open(xml_filename, "w") as xml_file: 347 | xml_file.write(xml_data) 348 | print(f"Wrote XML: {xml_filename}") 349 | with open(yaml_filename, "w") as yaml_file: 350 | yaml_file.write(yaml_data) 351 | print(f"Wrote YAML: {yaml_filename}") 352 | with open(json_filename, "w") as json_file: 353 | json_file.write(json_data_str) 354 | print(f"Wrote JSON: {json_filename}") 355 | 356 | # Fetch and save Mac Stable, Beta, Dev, and Canary versions 357 | mac_channels = ["Stable", "Extended", "Beta", "Dev", "Canary", "Canary_ASAN"] 358 | print("Fetching Mac channel versions...") 359 | mac_versions = {} 360 | for channel in mac_channels: 361 | mac_versions[channel.lower()] = fetch_mac_version(channel) 362 | mac_versions_xml = convert_mac_versions_to_xml( 363 | mac_versions["stable"], mac_versions["extended"], mac_versions["beta"], mac_versions["dev"], mac_versions["canary"], mac_versions["canary_asan"] 364 | ) 365 | mac_versions_yaml = convert_mac_versions_to_yaml( 366 | mac_versions["stable"], mac_versions["extended"], mac_versions["beta"], mac_versions["dev"], mac_versions["canary"], mac_versions["canary_asan"] 367 | ) 368 | mac_versions_json = convert_mac_versions_to_json( 369 | mac_versions["stable"], mac_versions["extended"], mac_versions["beta"], mac_versions["dev"], mac_versions["canary"], mac_versions["canary_asan"] 370 | ) 371 | 372 | xml_filename = os.path.join(output_dir, "chrome_latest_versions.xml") 373 | yaml_filename = os.path.join(output_dir, "chrome_latest_versions.yaml") 374 | json_filename = os.path.join(output_dir, "chrome_latest_versions.json") 375 | 376 | with open(xml_filename, "w") as xml_file: 377 | xml_file.write(mac_versions_xml) 378 | print(f"Wrote Mac XML: {xml_filename}") 379 | with open(yaml_filename, "w") as yaml_file: 380 | yaml_file.write(mac_versions_yaml) 381 | print(f"Wrote Mac YAML: {yaml_filename}") 382 | with open(json_filename, "w") as json_file: 383 | json_file.write(mac_versions_json) 384 | print(f"Wrote Mac JSON: {json_filename}") 385 | 386 | # Fetch and save Chrome release history for all channels 387 | print("Fetching Chrome history for all channels...") 388 | channels = ["stable", "extended", "beta", "dev", "canary", "canary_asan"] 389 | for channel in channels: 390 | print(f"Processing channel: {channel}") 391 | history = fetch_chrome_history(channel) 392 | history_json = convert_history_to_json(history) 393 | history_yaml = convert_history_to_yaml(history) 394 | history_xml = convert_history_to_xml(history) 395 | 396 | json_filename = os.path.join(output_dir, f"chrome_{channel}_history.json") 397 | yaml_filename = os.path.join(output_dir, f"chrome_{channel}_history.yaml") 398 | xml_filename = os.path.join(output_dir, f"chrome_{channel}_history.xml") 399 | 400 | with open(json_filename, "w") as json_file: 401 | json_file.write(history_json) 402 | print(f"Wrote JSON: {json_filename}") 403 | with open(yaml_filename, "w") as yaml_file: 404 | yaml_file.write(history_yaml) 405 | print(f"Wrote YAML: {yaml_filename}") 406 | with open(xml_filename, "w") as xml_file: 407 | xml_file.write(history_xml) 408 | print(f"Wrote XML: {xml_filename}") 409 | 410 | # Convert all *_history.json files to YAML in the output directory 411 | for filename in os.listdir(output_dir): 412 | if filename.endswith("_history.json"): 413 | json_path = os.path.join(output_dir, filename) 414 | yaml_path = os.path.join(output_dir, filename.replace(".json", ".yaml")) 415 | try: 416 | with open(json_path, "r") as f: 417 | data = json.load(f) 418 | with open(yaml_path, "w") as f: 419 | yaml.dump(data, f, sort_keys=False, allow_unicode=True) 420 | except Exception as e: 421 | print(f"Error converting {json_path} to YAML: {e}") 422 | 423 | # Ensure all *_history.json and *_history.yaml files have last_updated at the top level 424 | now_str = datetime.now(timezone('US/Eastern')).strftime("%B %d, %Y %I:%M %p %Z") 425 | for filename in os.listdir(output_dir): 426 | if filename.endswith("_history.json"): 427 | json_path = os.path.join(output_dir, filename) 428 | try: 429 | with open(json_path, "r") as f: 430 | data = json.load(f) 431 | if "last_updated" not in data: 432 | data = {"last_updated": now_str, **data} 433 | with open(json_path, "w") as f: 434 | json.dump(data, f, indent=2) 435 | except Exception as e: 436 | print(f"Error updating last_updated in {json_path}: {e}") 437 | if filename.endswith("_history.yaml"): 438 | yaml_path = os.path.join(output_dir, filename) 439 | try: 440 | with open(yaml_path, "r") as f: 441 | data = yaml.safe_load(f) 442 | if data is not None and "last_updated" not in data: 443 | data = {"last_updated": now_str, **data} 444 | with open(yaml_path, "w") as f: 445 | yaml.dump(data, f, sort_keys=False, allow_unicode=True) 446 | except Exception as e: 447 | print(f"Error updating last_updated in {yaml_path}: {e}") 448 | 449 | if __name__ == "__main__": 450 | main() 451 | -------------------------------------------------------------------------------- /.github/actions/generate_readme.py: -------------------------------------------------------------------------------- 1 | import xml.etree.ElementTree as ET 2 | import os 3 | from datetime import datetime 4 | from pytz import timezone 5 | 6 | def parse_xml_file(file_path): 7 | """Parse XML file robustly, handling encoding and BOM issues.""" 8 | if not os.path.exists(file_path): 9 | raise FileNotFoundError(file_path) 10 | try: 11 | # Try normal parse first 12 | return ET.parse(file_path) 13 | except ET.ParseError: 14 | # Try reading as utf-8-sig to remove BOM if present 15 | with open(file_path, 'r', encoding='utf-8-sig') as f: 16 | content = f.read() 17 | return ET.ElementTree(ET.fromstring(content)) 18 | 19 | def read_xml_value(file_path, xpath): 20 | """Generic function to read any value from XML using xpath""" 21 | if not os.path.exists(file_path): 22 | return "N/A" 23 | try: 24 | tree = parse_xml_file(file_path) 25 | root = tree.getroot() 26 | 27 | # For Edge specific handling 28 | if 'edge' in file_path.lower(): 29 | # Updated channel map to match new XML format 30 | channel_map = { 31 | 'stable': 'current', 32 | 'dev': 'dev', 33 | 'beta': 'beta', 34 | 'canary': 'canary' 35 | } 36 | base_channel = xpath.split('/')[0] 37 | xml_channel = channel_map.get(base_channel) 38 | if xml_channel: 39 | version_element = root.find(f".//Version[Channel='{xml_channel}']") 40 | if version_element is not None: 41 | return version_element.find('Location' if 'download' in xpath else 'Version').text 42 | return "N/A" 43 | 44 | # For Firefox, look inside package element 45 | elif 'firefox' in file_path.lower(): 46 | element = root.find(f'.//package/{xpath}') 47 | else: 48 | element = root.find(xpath) 49 | 50 | return element.text if element is not None else "N/A" 51 | except Exception as e: 52 | print(f"Error processing {file_path}: {str(e)}") 53 | return f"Error: {str(e)}" 54 | 55 | def read_xml_version(file_path): 56 | if not os.path.exists(file_path): 57 | return "N/A" 58 | try: 59 | tree = parse_xml_file(file_path) 60 | root = tree.getroot() 61 | 62 | # For Edge, get the first version 63 | if 'edge' in file_path.lower(): 64 | version_element = root.find('.//Version[Channel="current"]/Version') 65 | return version_element.text if version_element is not None else "N/A" 66 | 67 | # For Firefox, get the first version 68 | elif 'firefox' in file_path.lower(): 69 | version_element = root.find('.//latest_version') 70 | return version_element.text if version_element is not None else "N/A" 71 | 72 | # For Chrome, get the first version 73 | elif 'chrome' in file_path.lower(): 74 | version_element = root.find('.//version') 75 | return version_element.text if version_element is not None else "N/A" 76 | 77 | # For Safari, get the version from Sonoma (latest macOS) 78 | elif 'safari' in file_path.lower(): 79 | try: 80 | sonoma_version = root.find('./Sonoma/version') 81 | if sonoma_version is not None and sonoma_version.text: 82 | return sonoma_version.text 83 | print(f"Warning: Could not find Safari version in {file_path}") 84 | return "N/A" 85 | except Exception as e: 86 | print(f"Error reading Safari version: {str(e)}") 87 | return "N/A" 88 | 89 | return "N/A" 90 | except Exception as e: 91 | print(f"Error reading version from {file_path}: {str(e)}") 92 | return f"Error: {str(e)}" 93 | 94 | def get_browser_lines(file_path): 95 | """Get all version lines for a browser""" 96 | if not os.path.exists(file_path): 97 | return [] 98 | try: 99 | tree = parse_xml_file(file_path) 100 | root = tree.getroot() 101 | lines = [] 102 | 103 | # Different number of lines for each browser 104 | max_lines = { 105 | 'safari': 4, 106 | 'firefox': 6, 107 | 'edge': 6, 108 | 'chrome': 4 109 | } 110 | 111 | browser_type = next((b for b in max_lines.keys() if b in file_path), None) 112 | if (browser_type): 113 | for i in range(1, max_lines[browser_type] + 1): 114 | line = root.find(f'.//line{i}/version') 115 | lines.append(line.text if line is not None else "N/A") 116 | 117 | return lines 118 | except Exception as e: 119 | return [f"Error: {str(e)}"] 120 | 121 | def get_safari_detail(xml_path, os_version, detail_type): 122 | """Get Safari version/URL details by OS version""" 123 | try: 124 | tree = parse_xml_file(xml_path) 125 | root = tree.getroot() 126 | element = root.find(f'.//{os_version}/{detail_type}') 127 | return element.text if element is not None else "N/A" 128 | except Exception as e: 129 | return f"Error: {str(e)}" 130 | 131 | def fetch_chrome_details(xml_path, version_path, download_path): 132 | # Adjust to use 'download_link' instead of 'latest_download' 133 | version = read_xml_value(xml_path, version_path) 134 | # Map download_path to the new tag 135 | # download_path is like 'stable/latest_download' or 'beta/beta_download' 136 | # We want to use the same parent as version_path, but always 'download_link' 137 | parent = version_path.split('/')[0] 138 | download = read_xml_value(xml_path, f"{parent}/download_link") 139 | return version, download 140 | 141 | def fetch_firefox_details(xml_path, version_path, download_path): 142 | # version_path and download_path are now the channel names: 'stable', 'beta', 'dev', 'esr', 'nightly' 143 | if not os.path.exists(xml_path): 144 | return "N/A", "N/A" 145 | try: 146 | tree = parse_xml_file(xml_path) 147 | root = tree.getroot() 148 | channel_elem = root.find(f'.//{version_path}') 149 | if channel_elem is not None: 150 | version_elem = channel_elem.find('version') 151 | download_elem = channel_elem.find('download') 152 | version = version_elem.text if version_elem is not None else "N/A" 153 | download = download_elem.text if download_elem is not None else "N/A" 154 | return version, download 155 | return "N/A", "N/A" 156 | except Exception as e: 157 | return f"Error: {str(e)}", f"Error: {str(e)}" 158 | 159 | def fetch_edge_details(xml_path, version_path, download_path): 160 | try: 161 | tree = parse_xml_file(xml_path) 162 | root = tree.getroot() 163 | # Use new channel mapping 164 | channel_map = { 165 | 'stable': 'current', 166 | 'dev': 'dev', 167 | 'beta': 'beta', 168 | 'canary': 'canary' 169 | } 170 | channel = channel_map.get(version_path, version_path) 171 | version_element = root.find(f".//Version[Channel='{channel}']") 172 | if version_element is not None: 173 | version = version_element.find('Version').text 174 | download = version_element.find('Location').text 175 | return version, download 176 | return "N/A", "N/A" 177 | except Exception as e: 178 | return f"Error: {str(e)}", f"Error: {str(e)}" 179 | 180 | def fetch_safari_details(xml_path, os_version, detail_type): 181 | try: 182 | tree = parse_xml_file(xml_path) 183 | root = tree.getroot() 184 | version = root.find(f'.//{os_version}/version').text 185 | download = root.find(f'.//{os_version}/URL').text 186 | return version, download 187 | except Exception as e: 188 | return f"Error: {str(e)}", f"Error: {str(e)}" 189 | 190 | # --- NEW functions for Safari (releases + tech previews) --- 191 | def fetch_safari_release(xml_path, *args, **kwargs): 192 | """Return the latest entry's full_version and a link (release_notes or fallback).""" 193 | if not os.path.exists(xml_path): 194 | return "N/A", "#" 195 | try: 196 | tree = parse_xml_file(xml_path) 197 | root = tree.getroot() 198 | release = root.find('release') # first release is the latest in the file format 199 | if release is None: 200 | return "N/A", "#" 201 | # prefer explicit checks to avoid DeprecationWarning for element truth testing 202 | full_elem = release.find('full_version') 203 | major_elem = release.find('major_version') 204 | version = ( 205 | full_elem.text if (full_elem is not None and full_elem.text) 206 | else (major_elem.text if (major_elem is not None and major_elem.text) else "N/A") 207 | ) 208 | notes_elem = release.find('release_notes') 209 | notes = notes_elem.text if (notes_elem is not None and notes_elem.text) else "#" 210 | # If notes is a doc:// link, keep it; otherwise use it as-is. 211 | return version, notes 212 | except Exception as e: 213 | return f"Error: {str(e)}", "#" 214 | 215 | def fetch_safari_tech_previews(xml_path): 216 | """Return a list of tech preview dicts: [{'macos','version','PostDate','URL','ReleaseNotes'}, ...]""" 217 | previews = [] 218 | if not os.path.exists(xml_path): 219 | return previews 220 | try: 221 | tree = parse_xml_file(xml_path) 222 | root = tree.getroot() 223 | for tp in root.findall('Safari_Technology_Preview'): 224 | previews.append({ 225 | 'macos': tp.findtext('macos', default='N/A'), 226 | 'version': tp.findtext('version', default='N/A'), 227 | 'post_date': tp.findtext('PostDate', default='N/A'), 228 | 'url': tp.findtext('URL', default='#'), 229 | 'release_notes': tp.findtext('ReleaseNotes', default='#') 230 | }) 231 | except Exception: 232 | pass 233 | return previews 234 | 235 | def generate_safari_tech_table(base_path, xml_path): 236 | """Generate a markdown table for Safari Technology Previews that matches other browser rows.""" 237 | previews = fetch_safari_tech_previews(xml_path) 238 | if not previews: 239 | return "" 240 | 241 | table = "| **Browser** | **Version** | **CFBundle Identifier** | **Download** |\n" 242 | table += "|------------|-------------------|---------------------|------------|\n" 243 | for p in previews: 244 | display = f"Safari Technology Preview ({p['macos']})" 245 | version = p['version'] 246 | bundle_id = "com.apple.SafariTechnologyPreview" 247 | # Use a technology-specific image if available 248 | image = "safari_technology.png" 249 | download = p['url'] if p['url'] and p['url'] != 'N/A' else p['release_notes'] 250 | last_updated_html = f"

Post Date:
{p['post_date']}" 251 | # center the image/link inside the table cell 252 | image_html = f'
Download {display}
' 253 | table += ( 254 | f"| **{display}** {last_updated_html} | " 255 | f"`{version}` | " 256 | f"`{bundle_id}` | " 257 | f"{image_html} |\n" 258 | ) 259 | table += "\n" 260 | return table 261 | 262 | # --- NEW: fetch_all_safari_releases + generator (table matches other browsers) --- 263 | def fetch_all_safari_releases(xml_path): 264 | """Return list of all dicts from the Safari XML (preserves file order).""" 265 | releases = [] 266 | if not os.path.exists(xml_path): 267 | return releases 268 | try: 269 | tree = parse_xml_file(xml_path) 270 | root = tree.getroot() 271 | for rel in root.findall('release'): 272 | releases.append({ 273 | 'major_version': rel.findtext('major_version', default='N/A'), 274 | 'full_version': rel.findtext('full_version', default='N/A'), 275 | 'released': rel.findtext('released', default='N/A'), 276 | # Prefer an explicit release_notes_url child when present; otherwise use release_notes text 277 | 'release_notes': rel.findtext('release_notes', default='#'), 278 | 'release_notes_url': rel.findtext('release_notes_url', default=None) 279 | }) 280 | except Exception: 281 | pass 282 | return releases 283 | 284 | def generate_safari_releases_table(base_path, xml_path): 285 | """Render a dedicated markdown table listing all Safari entries using the same layout as other browsers.""" 286 | releases = fetch_all_safari_releases(xml_path) 287 | if not releases: 288 | return "" 289 | # header + separator so Markdown renders this as a proper table 290 | table = "| **Browser** | **Version** | **CFBundle Identifier** | **Release Notes** |\n" 291 | table += "|------------|-------------------|---------------------|------------|\n" 292 | for r in releases: 293 | full_version = r['full_version'] 294 | # If version contains 'beta', mark as beta (but do NOT make the beta label bold) 295 | is_beta = 'beta' in (full_version or "").lower() 296 | display_base = "Safari" 297 | # non-bold for beta: show as plain text with a superscript; stable stays bold 298 | if is_beta: 299 | display_cell = f"{display_base} Beta" 300 | else: 301 | display_cell = f"**{display_base}**" 302 | version = full_version 303 | bundle_id = "com.apple.Safari" if is_beta else "com.apple.Safari" 304 | # Use the same Safari logo for both stable and beta 305 | image = "safari.png" 306 | 307 | # Prefer explicit URL node, otherwise fall back to release_notes text 308 | notes_url = r.get('release_notes_url') or r.get('release_notes') or '#' 309 | # Normalize relative developer links to a full URL 310 | if isinstance(notes_url, str) and notes_url and not notes_url.startswith('http'): 311 | if notes_url.startswith('/'): 312 | notes_url = 'https://developer.apple.com' + notes_url 313 | else: 314 | if notes_url.startswith('doc://') or notes_url == '#': 315 | notes_url = '#' 316 | 317 | # Render a Safari-logo icon linking to the release notes (fallback to text if no URL) 318 | if notes_url and notes_url != '#': 319 | # center the image/link inside the table cell 320 | note_link_html = f'
Safari Release Notes' 321 | else: 322 | note_link_html = 'N/A' 323 | 324 | last_updated_html = f"

Released:
{r['released']}" 325 | table += ( 326 | f"| {display_cell} {last_updated_html} | " 327 | f"`{version}` | " 328 | f"`{bundle_id}` | " 329 | f"{note_link_html} |\n" 330 | ) 331 | table += "\n" 332 | return table 333 | # --- END NEW functions --- 334 | 335 | BROWSER_CONFIGS = { 336 | 'Chrome': { 337 | 'fetch_details': fetch_chrome_details, 338 | 'channels': [ 339 | {'name': '', 'display': 'Chrome', 'version_path': 'stable/version', 'download_path': 'stable/download_link', 'bundle_id': 'com.google.Chrome', 'image': 'chrome.png', 'release_notes': 'https://chromereleases.googleblog.com/'}, 340 | {'name': 'Extended Stable', 'display': 'Chrome', 'version_path': 'extended/version', 'download_path': 'extended/download_link', 'bundle_id': 'com.google.Chrome', 'image': 'chrome.png', 'release_notes_comment': '
_Requires `TargetChannel` policy; link is for Stable._'}, 341 | {'name': 'Beta', 'display': 'Chrome', 'version_path': 'beta/version', 'download_path': 'beta/download_link', 'bundle_id': 'com.google.Chrome.beta', 'image': 'chrome_beta.png', 'release_notes': 'https://chromereleases.googleblog.com/search/label/Beta%20updates'}, 342 | {'name': 'Dev', 'display': 'Chrome', 'version_path': 'dev/version', 'download_path': 'dev/download_link', 'bundle_id': 'com.google.Chrome.dev', 'image': 'chrome_dev.png', 'release_notes': 'https://chromereleases.googleblog.com/search/label/Dev%20updates'}, 343 | {'name': 'Canary', 'display': 'Chrome', 'version_path': 'canary/version', 'download_path': 'canary/download_link', 'bundle_id': 'com.google.Chrome.canary', 'image': 'chrome_canary.png'}, 344 | {'name': 'Canary ASAN', 'display': 'Chrome', 'version_path': 'canary_asan/version', 'download_path': 'canary_asan/download_link', 'bundle_id': 'com.google.Chrome.canary', 'image': 'chrome_canary.png'} 345 | ] 346 | }, 347 | 'Firefox': { 348 | 'fetch_details': fetch_firefox_details, 349 | 'channels': [ 350 | {'name': '', 'display': 'Firefox', 'version_path': 'stable', 'download_path': 'stable', 'bundle_id': 'org.mozilla.firefox', 'image': 'firefox.png', 'release_notes': 'https://www.mozilla.org/en-US/firefox/notes/'}, 351 | {'name': 'Beta', 'display': 'Firefox', 'version_path': 'beta', 'download_path': 'beta', 'bundle_id': 'org.mozilla.firefoxbeta', 'image': 'firefox.png', 'release_notes': 'https://www.mozilla.org/en-US/firefox/beta/notes/'}, 352 | {'name': 'Developer', 'display': 'Firefox', 'version_path': 'dev', 'download_path': 'dev', 'bundle_id': 'org.mozilla.firefoxdev', 'image': 'firefox_developer.png', 'release_notes': 'https://www.mozilla.org/en-US/firefox/developer/notes/'}, 353 | {'name': 'ESR', 'display': 'Firefox', 'version_path': 'esr', 'download_path': 'esr', 'bundle_id': 'org.mozilla.firefoxesr', 'image': 'firefox.png','release_notes': 'https://www.mozilla.org/en-US/firefox/organizations/notes/'}, 354 | {'name': 'Nightly', 'display': 'Firefox', 'version_path': 'nightly', 'download_path': 'nightly', 'bundle_id': 'org.mozilla.nightly', 'image': 'firefox_nightly.png', 'release_notes': 'https://www.mozilla.org/en-US/firefox/nightly/notes/'} 355 | ] 356 | }, 357 | 'Edge': { 358 | 'fetch_details': fetch_edge_details, 359 | 'channels': [ 360 | {'name': '', 'display': 'Edge', 'version_path': 'stable', 'download_path': 'stable', 'bundle_id': 'com.microsoft.edgemac', 'image': 'edge.png', 'release_notes': 'https://learn.microsoft.com/en-us/deployedge/microsoft-edge-relnote-stable-channel'}, 361 | {'name': 'Beta', 'display': 'Edge', 'version_path': 'beta', 'download_path': 'beta', 'bundle_id': 'com.microsoft.edgemac.beta', 'image': 'edge_beta.png', 'release_notes': 'https://learn.microsoft.com/en-us/deployedge/microsoft-edge-relnote-beta-channel'}, 362 | {'name': 'Developer', 'display': 'Edge', 'version_path': 'dev', 'download_path': 'dev', 'bundle_id': 'com.microsoft.edgemac.dev', 'image': 'edge_dev.png'}, 363 | {'name': 'Canary', 'display': 'Edge', 'version_path': 'canary', 'download_path': 'canary', 'bundle_id': 'com.microsoft.edgemac.canary', 'image': 'edge_canary.png'} 364 | ] 365 | }, 366 | 'Safari': { 367 | # Use the new release-level fetcher and a single "release" channel 368 | 'fetch_details': fetch_safari_release, 369 | 'channels': [ 370 | {'name': '', 'display': 'Safari', 'version_path': 'release', 'download_path': 'release', 'bundle_id': 'com.apple.Safari', 'image': 'safari.png', 'release_notes': 'https://developer.apple.com/documentation/safari-release-notes'} 371 | ] 372 | } 373 | } 374 | 375 | def get_last_updated_from_xml(xml_path, browser, channel=None): 376 | """Extract last updated date for the main stable channel/version for each browser, formatted as 'Month day, Year'.""" 377 | if not os.path.exists(xml_path): 378 | return "N/A" 379 | def format_date(date_str): 380 | # Try to parse common formats and return 'Month day, Year' 381 | for fmt in [ 382 | "%B %d, %Y %I:%M %p %Z", # e.g., May 27, 2025 10:02 AM EDT 383 | "%B %d, %Y %I:%M %p", # e.g., May 27, 2025 10:02 AM 384 | "%B %d, %Y", # e.g., May 27, 2025 385 | "%Y-%m-%d", # e.g., 2025-05-27 386 | "%Y-%m-%d %H:%M", # e.g., 2025-05-27 10:02 387 | ]: 388 | try: 389 | dt = datetime.strptime(date_str.strip(), fmt) 390 | return dt.strftime("%B %d, %Y") 391 | except Exception: 392 | continue 393 | return date_str # fallback: return as-is 394 | 395 | try: 396 | tree = parse_xml_file(xml_path) 397 | root = tree.getroot() 398 | if browser == 'Chrome': 399 | # Use channel-specific release_time if available 400 | if channel: 401 | elem = root.find(f'.//{channel}/release_time') 402 | if elem is not None and elem.text: 403 | return format_date(elem.text) 404 | elif browser == 'Firefox': 405 | # New Firefox XML: channel is one of 'stable', 'beta', 'dev', 'esr', 'nightly' 406 | if channel: 407 | channel_elem = root.find(f'.//{channel}') 408 | if channel_elem is not None: 409 | release_elem = channel_elem.find('release_time') 410 | if release_elem is not None and release_elem.text: 411 | return format_date(release_elem.text) 412 | # fallback to 413 | elem = root.find('.//last_updated') 414 | if elem is not None and elem.text: 415 | return format_date(elem.text) 416 | elif browser == 'Edge': 417 | # Match Date for the requested channel (stable->current) 418 | channel_map = {'stable': 'current', 'beta': 'beta', 'dev': 'dev', 'canary': 'canary'} 419 | wanted = channel_map.get(channel, channel) if channel else 'current' 420 | for version in root.findall('.//Version'): 421 | ch = version.find('Channel') 422 | if ch is not None and ch.text == wanted: 423 | date_elem = version.find('Date') 424 | if date_elem is not None and date_elem.text: 425 | return format_date(date_elem.text) 426 | elif browser == 'Safari': 427 | # New Safari XML uses elements with ; 428 | # Technology previews use with . 429 | if channel: 430 | # try both PostDate (tech previews) and released (release entries) 431 | elem = root.find(f'.//{channel}/PostDate') or root.find(f'.//{channel}/released') 432 | if elem is not None and elem.text: 433 | return format_date(elem.text) 434 | # fallback: if there are entries use the first / 435 | release_released = root.find('.//release/released') 436 | if release_released is not None and release_released.text: 437 | return format_date(release_released.text) 438 | # Global fallbacks 439 | elem = root.find('.//last_updated') 440 | if elem is not None and elem.text: 441 | return format_date(elem.text) 442 | mtime = os.path.getmtime(xml_path) 443 | return datetime.fromtimestamp(mtime).strftime("%B %d, %Y") 444 | except Exception as e: 445 | return "N/A" 446 | 447 | def generate_browser_table(base_path): 448 | table_content = """| **Browser** | **CFBundle Version** | **CFBundle Identifier** | **Download** | 449 | |------------|-------------------|---------------------|------------| 450 | """ 451 | for browser, config in BROWSER_CONFIGS.items(): 452 | # Skip Safari in the main browser table (we render a dedicated Safari releases section) 453 | if browser == 'Safari': 454 | continue 455 | 456 | xml_path = os.path.join(base_path, f'latest_{browser.lower()}_files/{browser.lower()}_latest_versions.xml') 457 | for channel in config['channels']: 458 | # Fetch version and download 459 | if browser == 'Safari': 460 | version, download = config['fetch_details'](xml_path, channel['version_path'], 'URL') 461 | last_updated = get_last_updated_from_xml(xml_path, browser, channel['version_path']) 462 | elif browser == 'Chrome': 463 | version, download = config['fetch_details'](xml_path, channel['version_path'], channel['download_path']) 464 | # For Extended Stable, use the same download as Stable 465 | if channel.get('name') == 'Extended Stable': 466 | _, stable_download = config['fetch_details'](xml_path, 'stable/version', 'stable/download_link') 467 | download = stable_download 468 | last_updated = get_last_updated_from_xml(xml_path, browser, channel['version_path'].split('/')[0]) 469 | elif browser == 'Edge': 470 | version, download = config['fetch_details'](xml_path, channel['version_path'], channel['download_path']) 471 | last_updated = get_last_updated_from_xml(xml_path, browser, channel['version_path']) 472 | elif browser == 'Firefox': 473 | version, download = config['fetch_details'](xml_path, channel['version_path'], channel['download_path']) 474 | last_updated = get_last_updated_from_xml(xml_path, browser, channel['version_path']) 475 | else: 476 | version, download = "N/A", "N/A" 477 | last_updated = None 478 | 479 | channel_name = f"{channel['name']}" if channel['name'] else "" 480 | # For Extended Stable, show comment instead of release notes 481 | if 'release_notes_comment' in channel: 482 | release_notes = f"
{channel['release_notes_comment']}" 483 | else: 484 | release_notes = f"
_Release Notes_" if 'release_notes' in channel or 'release_notes' in config else "" 485 | last_updated_html = f"

Last Updated:
{last_updated}" if last_updated else "" 486 | table_content += ( 487 | f"| **{channel['display']}** {channel_name} {release_notes}{last_updated_html} | " 488 | f"`{version}` | " 489 | f"`{channel['bundle_id']}` | " 490 | f" |\n" 492 | ) 493 | # Ensure the table is followed by blank lines so subsequent sections/tables render separately 494 | return table_content + "\n\n" 495 | 496 | def generate_settings_section(): 497 | return """ 498 | ## Browser Settings Management 499 | 500 | View your current browser policies and explore available policy options: 501 | 502 | ### Chrome Chrome 503 | 1. **View Current Policies**: Enter `chrome://policy` in your address bar to see active policies 504 | 2. **Available Options**: [Chrome Enterprise Policy Documentation](https://chromeenterprise.google/policies/) 505 | 506 | ### Firefox Firefox 507 | 1. **View Current Policies**: Enter `about:policies` in your address bar to see active policies 508 | 2. **Available Options**: [Firefox Policy Documentation](https://mozilla.github.io/policy-templates/) 509 | 510 | ### Edge Edge 511 | 1. **View Current Policies**: Enter `edge://policy` in your address bar to see active policies 512 | 2. **Available Options**: [Edge Policy Documentation](https://learn.microsoft.com/en-us/deployedge/microsoft-edge-policies) 513 | 514 | ### Safari Safari 515 | 1. **View Current Policies**: Open System Settings > Profiles & Device Management 516 | 2. **Available Options**: [Safari Configuration Profile Reference](https://support.apple.com/guide/deployment/welcome/web) 517 | 518 | """ 519 | 520 | def generate_readme(): 521 | base_path = os.path.dirname(os.path.dirname(os.path.dirname(__file__))) 522 | xml_files = { 523 | 'Chrome': os.path.join(base_path, 'latest_chrome_files/chrome_latest_versions.xml'), 524 | 'Firefox': os.path.join(base_path, 'latest_firefox_files/firefox_latest_versions.xml'), 525 | 'Edge': os.path.join(base_path, 'latest_edge_files/edge_latest_versions.xml'), 526 | 'Safari': os.path.join(base_path, 'latest_safari_files/safari_latest_versions.xml') 527 | } 528 | eastern = timezone('US/Eastern') 529 | current_time = datetime.now(eastern).strftime("%B %d, %Y %I:%M %p %Z") 530 | global_last_updated = current_time 531 | 532 | # Fetch versions and download URLs with new Edge mapping 533 | chrome_version, chrome_download = fetch_chrome_details(xml_files['Chrome'], 'stable/version', 'stable/download_link') 534 | firefox_version, firefox_download = fetch_firefox_details(xml_files['Firefox'], 'stable', 'stable') 535 | edge_version, edge_download = fetch_edge_details(xml_files['Edge'], 'stable', 'stable') 536 | # Use the new release-based Safari fetcher (main browser tile) 537 | safari_version, safari_download = fetch_safari_release(xml_files['Safari']) 538 | 539 | # Fetch last updated dates from XMLs (browser-specific, channel-specific) 540 | chrome_last_updated = get_last_updated_from_xml(xml_files['Chrome'], 'Chrome', 'stable') 541 | firefox_last_updated = get_last_updated_from_xml(xml_files['Firefox'], 'Firefox', 'stable') 542 | edge_last_updated = get_last_updated_from_xml(xml_files['Edge'], 'Edge') 543 | safari_last_updated = get_last_updated_from_xml(xml_files['Safari'], 'Safari') 544 | 545 | readme_content = f"""# **BOFA** 546 | **B**rowser **O**verview **F**eed for **A**pple 547 | 548 | MOFA Image 549 | 550 | Welcome to the **BOFA** repository! This resource tracks the latest versions of major web browsers for macOS. Feeds are automatically updated every hour from XML and JSON links directly from vendors. 551 | 552 | We welcome community contributions—fork the repository, ask questions, or share insights to help keep this resource accurate and useful for everyone. Check out the user-friendly website version below for an easier browsing experience! 553 | 554 |
555 | 556 | 557 | 558 | 559 | 560 | 561 | 562 | 563 | 568 | 569 |
🌟 Explore the BOFA Website 🌟⭐ Support the Project – Give it a Star! ⭐
🌐 Visit: bofa.cocolabs.dev 🌐 564 | 565 | GitHub Repo Stars 566 | 567 |
570 | 571 | 572 | ## Latest Stable Browser Versions 573 | 574 | 575 | 576 | 577 | 578 | 579 | 580 | 581 |
Chrome
Chrome
{chrome_version}

Last Update:
{chrome_last_updated}

Release Notes
Firefox
Firefox
{firefox_version}

Last Update:
{firefox_last_updated}

Release Notes
Edge
Edge
{edge_version}

Last Update:
{edge_last_updated}

Release Notes
Safari
Safari
{safari_version}

Last Update:
{safari_last_updated}

Release Notes
582 | 583 | """ 584 | 585 | readme_content += f""" 586 | ## Browser Packages 587 | 588 | All links below direct to the official browser vendor. The links provided will always download the latest available version as of the last scan update. 589 | 590 | **Chrome**: [**_Raw XML_**](latest_chrome_files/chrome_latest_versions.xml) [**_Raw YAML_**](latest_chrome_files/chrome_latest_versions.yaml) [**_Raw JSON_**](latest_chrome_files/chrome_latest_versions.json) | **Firefox**: [**_Raw XML_**](latest_firefox_files/firefox_latest_versions.xml) [**_Raw YAML_**](latest_firefox_files/firefox_latest_versions.yaml) [**_Raw JSON_**](latest_firefox_files/firefox_latest_versions.json) 591 | 592 | **Edge**: [**_Raw XML_**](latest_edge_files/edge_latest_versions.xml) [**_Raw YAML_**](latest_edge_files/edge_latest_versions.yaml) [**_Raw JSON_**](latest_edge_files/edge_latest_versions.json) | **Safari**: [**_Raw XML_**](latest_safari_files/safari_latest_versions.xml) [**_Raw YAML_**](latest_safari_files/safari_latest_versions.yaml) [**_Raw JSON_**](latest_safari_files/safari_latest_versions.json) 593 | 594 | _Last Updated: {global_last_updated} (Automatically Updated every hour)_ 595 | 596 |
597 | 598 | """ 599 | 600 | readme_content += generate_browser_table(base_path) 601 | # Add a dedicated Safari releases table (all entries) 602 | readme_content += generate_safari_releases_table(base_path, xml_files['Safari']) 603 | # Append the new Safari Technology Preview table (if any) 604 | readme_content += generate_safari_tech_table(base_path, xml_files['Safari']) 605 | readme_content += generate_settings_section() 606 | 607 | readme_path = os.path.join(base_path, 'README.md') 608 | with open(readme_path, 'w', encoding='utf-8') as f: 609 | f.write(readme_content) 610 | 611 | if __name__ == "__main__": 612 | generate_readme() 613 | --------------------------------------------------------------------------------