Refactor scripts arguments handling (#456)

- remove the use of environment variables to get directory paths,
- make use of arguments / argparse instead of environment variables in `update.py` and `report.py`,
- automatically guess the data directory in `latest.py` based on the script's location,
- propagate log level to auto scripts,
- move `list_configs_from_argv` from `endoflife` module to `releasedata` module,
- use `list_products` in `latest.py` to load the product's frontmatters.
This commit is contained in:
Marc Wrobel
2025-06-28 18:23:58 +02:00
parent 1dc08689f9
commit c78d1fe2b5
66 changed files with 273 additions and 256 deletions

View File

@@ -61,7 +61,7 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
continue-on-error: true # commit even if the data was not fully updated
run: python update.py
run: python update.py -p 'website/products'
- name: Commit changes
uses: stefanzweifel/git-auto-commit-action@v6

112
README.md
View File

@@ -20,7 +20,7 @@ Common Release Data for various projects in a consistent and easy-to-parse forma
## Currently Updated
As of 2025-05-17, 308 of the 379 products tracked by endoflife.date have automatically tracked releases:
As of 2025-06-28, 316 of the 383 products tracked by endoflife.date have automatically tracked releases:
| Product | Permalink | Auto | Method(s) |
|---------|-----------|------|-----------|
@@ -31,18 +31,18 @@ As of 2025-05-17, 308 of the 379 products tracked by endoflife.date have automat
| Alpine Linux | [`/alpine-linux`](https://endoflife.date/alpine-linux) | ✔️ | git, release_table |
| Amazon CDK | [`/amazon-cdk`](https://endoflife.date/amazon-cdk) | ✔️ | git |
| Amazon Corretto | [`/amazon-corretto`](https://endoflife.date/amazon-corretto) | ✔️ | github_releases |
| Amazon EKS | [`/amazon-eks`](https://endoflife.date/amazon-eks) | ✔️ | custom, release_table |
| Amazon EKS | [`/amazon-eks`](https://endoflife.date/amazon-eks) | ✔️ | amazon-eks, release_table |
| Amazon Glue | [`/amazon-glue`](https://endoflife.date/amazon-glue) | ❌ | |
| Amazon Linux | [`/amazon-linux`](https://endoflife.date/amazon-linux) | ✔️ | docker_hub |
| Amazon Neptune | [`/amazon-neptune`](https://endoflife.date/amazon-neptune) | ✔️ | custom, release_table |
| Amazon RDS for MariaDB | [`/amazon-rds-mariadb`](https://endoflife.date/amazon-rds-mariadb) | ✔️ | custom, release_table |
| Amazon RDS for MySQL | [`/amazon-rds-mysql`](https://endoflife.date/amazon-rds-mysql) | ✔️ | custom, release_table |
| Amazon RDS for PostgreSQL | [`/amazon-rds-postgresql`](https://endoflife.date/amazon-rds-postgresql) | ✔️ | custom, release_table |
| Amazon Neptune | [`/amazon-neptune`](https://endoflife.date/amazon-neptune) | ✔️ | amazon-neptune, release_table |
| Amazon RDS for MariaDB | [`/amazon-rds-mariadb`](https://endoflife.date/amazon-rds-mariadb) | ✔️ | rds, release_table |
| Amazon RDS for MySQL | [`/amazon-rds-mysql`](https://endoflife.date/amazon-rds-mysql) | ✔️ | rds, release_table |
| Amazon RDS for PostgreSQL | [`/amazon-rds-postgresql`](https://endoflife.date/amazon-rds-postgresql) | ✔️ | rds, release_table |
| Android OS | [`/android`](https://endoflife.date/android) | ❌ | |
| Angular | [`/angular`](https://endoflife.date/angular) | ✔️ | git, release_table |
| AngularJS | [`/angularjs`](https://endoflife.date/angularjs) | ✔️ | npm |
| Ansible-core | [`/ansible-core`](https://endoflife.date/ansible-core) | ✔️ | git, release_table |
| Ansible | [`/ansible`](https://endoflife.date/ansible) | ✔️ | pypi |
| Ansible-core | [`/ansible-core`](https://endoflife.date/ansible-core) | ✔️ | git, release_table |
| antiX Linux | [`/antix`](https://endoflife.date/antix) | ✔️ | distrowatch |
| Apache ActiveMQ | [`/apache-activemq`](https://endoflife.date/apache-activemq) | ✔️ | git |
| Apache Airflow | [`/apache-airflow`](https://endoflife.date/apache-airflow) | ✔️ | pypi, release_table |
@@ -55,7 +55,7 @@ As of 2025-05-17, 308 of the 379 products tracked by endoflife.date have automat
| Apache Groovy | [`/apache-groovy`](https://endoflife.date/apache-groovy) | ✔️ | maven |
| Apache Hadoop | [`/apache-hadoop`](https://endoflife.date/apache-hadoop) | ✔️ | git |
| Apache Hop | [`/apache-hop`](https://endoflife.date/apache-hop) | ✔️ | maven |
| Apache HTTP Server | [`/apache-http-server`](https://endoflife.date/apache-http-server) | ✔️ | custom |
| Apache HTTP Server | [`/apache-http-server`](https://endoflife.date/apache-http-server) | ✔️ | apache-http-server |
| Apache Kafka | [`/apache-kafka`](https://endoflife.date/apache-kafka) | ✔️ | git, release_table |
| Apache Lucene | [`/apache-lucene`](https://endoflife.date/apache-lucene) | ✔️ | maven |
| Apache Maven | [`/apache-maven`](https://endoflife.date/apache-maven) | ✔️ | maven |
@@ -63,14 +63,14 @@ As of 2025-05-17, 308 of the 379 products tracked by endoflife.date have automat
| Apache Pulsar | [`/apache-pulsar`](https://endoflife.date/apache-pulsar) | ✔️ | github_releases, release_table |
| Apache Spark | [`/apache-spark`](https://endoflife.date/apache-spark) | ✔️ | git |
| Apache Struts | [`/apache-struts`](https://endoflife.date/apache-struts) | ✔️ | maven |
| Apache Subversion | [`/apache-subversion`](https://endoflife.date/apache-subversion) | ✔️ | custom |
| Apache Subversion | [`/apache-subversion`](https://endoflife.date/apache-subversion) | ✔️ | apache-subversion |
| API Platform | [`/api-platform`](https://endoflife.date/api-platform) | ✔️ | git |
| Apple tvOS | [`/tvos`](https://endoflife.date/tvos) | ✔️ | apple |
| Apple Watch | [`/apple-watch`](https://endoflife.date/apple-watch) | ❌ | |
| ArangoDB | [`/arangodb`](https://endoflife.date/arangodb) | ✔️ | git |
| Argo CD | [`/argo-cd`](https://endoflife.date/argo-cd) | ✔️ | git |
| Artifactory | [`/artifactory`](https://endoflife.date/artifactory) | ✔️ | custom |
| AWS Lambda | [`/aws-lambda`](https://endoflife.date/aws-lambda) | ✔️ | custom |
| Artifactory | [`/artifactory`](https://endoflife.date/artifactory) | ✔️ | artifactory |
| AWS Lambda | [`/aws-lambda`](https://endoflife.date/aws-lambda) | ✔️ | aws-lambda |
| Azul Zulu | [`/azul-zulu`](https://endoflife.date/azul-zulu) | ❌ | |
| Azure DevOps Server | [`/azure-devops-server`](https://endoflife.date/azure-devops-server) | ❌ | |
| Azure Kubernetes Service | [`/azure-kubernetes-service`](https://endoflife.date/azure-kubernetes-service) | ❌ | |
@@ -88,14 +88,14 @@ As of 2025-05-17, 308 of the 379 products tracked by endoflife.date have automat
| caddy | [`/caddy`](https://endoflife.date/caddy) | ✔️ | git |
| CakePHP | [`/cakephp`](https://endoflife.date/cakephp) | ✔️ | git |
| Calico | [`/calico`](https://endoflife.date/calico) | ✔️ | git |
| CentOS Stream | [`/centos-stream`](https://endoflife.date/centos-stream) | ❌ | |
| CentOS | [`/centos`](https://endoflife.date/centos) | ❌ | |
| CentOS Stream | [`/centos-stream`](https://endoflife.date/centos-stream) | ❌ | |
| Centreon | [`/centreon`](https://endoflife.date/centreon) | ✔️ | git, release_table |
| cert-manager | [`/cert-manager`](https://endoflife.date/cert-manager) | ✔️ | git |
| CFEngine | [`/cfengine`](https://endoflife.date/cfengine) | ✔️ | git |
| Chef Infra Client | [`/chef-infra-client`](https://endoflife.date/chef-infra-client) | ✔️ | custom |
| Chef Infra Server | [`/chef-infra-server`](https://endoflife.date/chef-infra-server) | ✔️ | custom |
| Chef InSpec | [`/chef-inspec`](https://endoflife.date/chef-inspec) | ✔️ | custom |
| Chef Infra Client | [`/chef-infra-client`](https://endoflife.date/chef-infra-client) | ✔️ | chef-infra |
| Chef Infra Server | [`/chef-infra-server`](https://endoflife.date/chef-infra-server) | ✔️ | chef-infra |
| Chef InSpec | [`/chef-inspec`](https://endoflife.date/chef-inspec) | ✔️ | chef-inspec |
| Citrix Virtual Apps and Desktops | [`/citrix-vad`](https://endoflife.date/citrix-vad) | ❌ | |
| CKEditor | [`/ckeditor`](https://endoflife.date/ckeditor) | ❌ | |
| ClamAV | [`/clamav`](https://endoflife.date/clamav) | ✔️ | git |
@@ -110,12 +110,12 @@ As of 2025-05-17, 308 of the 379 products tracked by endoflife.date have automat
| Contao | [`/contao`](https://endoflife.date/contao) | ✔️ | git |
| Contour | [`/contour`](https://endoflife.date/contour) | ✔️ | git |
| Control-M | [`/controlm`](https://endoflife.date/controlm) | ❌ | |
| Google Container-Optimized OS (COS) | [`/cos`](https://endoflife.date/cos) | ✔️ | custom |
| Couchbase Server | [`/couchbase-server`](https://endoflife.date/couchbase-server) | ✔️ | custom, release_table |
| Google Container-Optimized OS (COS) | [`/cos`](https://endoflife.date/cos) | ✔️ | cos |
| Couchbase Server | [`/couchbase-server`](https://endoflife.date/couchbase-server) | ✔️ | couchbase-server, release_table |
| Craft CMS | [`/craft-cms`](https://endoflife.date/craft-cms) | ✔️ | git, release_table |
| dbt Core | [`/dbt-core`](https://endoflife.date/dbt-core) | ✔️ | git |
| DaoCloud Enterprise | [`/dce`](https://endoflife.date/dce) | ❌ | |
| Debian | [`/debian`](https://endoflife.date/debian) | ✔️ | custom, release_table |
| Debian | [`/debian`](https://endoflife.date/debian) | ✔️ | debian, release_table |
| Deno | [`/deno`](https://endoflife.date/deno) | ✔️ | git |
| Dependency-Track | [`/dependency-track`](https://endoflife.date/dependency-track) | ✔️ | git |
| Devuan | [`/devuan`](https://endoflife.date/devuan) | ✔️ | distrowatch |
@@ -142,7 +142,7 @@ As of 2025-05-17, 308 of the 379 products tracked by endoflife.date have automat
| Fedora Linux | [`/fedora`](https://endoflife.date/fedora) | ✔️ | distrowatch |
| FFmpeg | [`/ffmpeg`](https://endoflife.date/ffmpeg) | ✔️ | git |
| FileMaker Platform | [`/filemaker`](https://endoflife.date/filemaker) | ✔️ | release_table |
| Firefox | [`/firefox`](https://endoflife.date/firefox) | ✔️ | custom |
| Firefox | [`/firefox`](https://endoflife.date/firefox) | ✔️ | firefox |
| Fluent Bit | [`/fluent-bit`](https://endoflife.date/fluent-bit) | ✔️ | git |
| Flux | [`/flux`](https://endoflife.date/flux) | ✔️ | git |
| Forgejo | [`/forgejo`](https://endoflife.date/forgejo) | ✔️ | git, release_table |
@@ -150,34 +150,34 @@ As of 2025-05-17, 308 of the 379 products tracked by endoflife.date have automat
| FreeBSD | [`/freebsd`](https://endoflife.date/freebsd) | ❌ | |
| Gatekeeper | [`/gatekeeper`](https://endoflife.date/gatekeeper) | ✔️ | git |
| Gerrit | [`/gerrit`](https://endoflife.date/gerrit) | ✔️ | git |
| Glasgow Haskell Compiler (GHC) | [`/ghc`](https://endoflife.date/ghc) | ✔️ | custom, git |
| Glasgow Haskell Compiler (GHC) | [`/ghc`](https://endoflife.date/ghc) | ✔️ | git |
| GitLab | [`/gitlab`](https://endoflife.date/gitlab) | ✔️ | git, release_table |
| Go | [`/go`](https://endoflife.date/go) | ✔️ | git |
| GoAccess | [`/goaccess`](https://endoflife.date/goaccess) | ✔️ | git |
| Godot | [`/godot`](https://endoflife.date/godot) | ✔️ | git |
| Google Kubernetes Engine | [`/google-kubernetes-engine`](https://endoflife.date/google-kubernetes-engine) | ✔️ | custom |
| Google Kubernetes Engine | [`/google-kubernetes-engine`](https://endoflife.date/google-kubernetes-engine) | ✔️ | google-kubernetes-engine |
| Google Nexus | [`/google-nexus`](https://endoflife.date/google-nexus) | ❌ | |
| Gorilla Toolkit | [`/gorilla`](https://endoflife.date/gorilla) | ❌ | |
| GraalVM | [`/graalvm`](https://endoflife.date/graalvm) | ✔️ | custom |
| GraalVM Community Edition | [`/graalvm-ce`](https://endoflife.date/graalvm-ce) | ✔️ | graalvm |
| Gradle | [`/gradle`](https://endoflife.date/gradle) | ✔️ | git |
| Grafana Loki | [`/grafana-loki`](https://endoflife.date/grafana-loki) | ✔️ | git |
| Grafana | [`/grafana`](https://endoflife.date/grafana) | ✔️ | github_releases, release_table |
| Grafana Loki | [`/grafana-loki`](https://endoflife.date/grafana-loki) | ✔️ | git |
| Grails Framework | [`/grails`](https://endoflife.date/grails) | ✔️ | git |
| Graylog | [`/graylog`](https://endoflife.date/graylog) | ✔️ | git |
| Greenlight | [`/greenlight`](https://endoflife.date/greenlight) | ✔️ | git |
| Grunt | [`/grunt`](https://endoflife.date/grunt) | ✔️ | git |
| GStreamer | [`/gstreamer`](https://endoflife.date/gstreamer) | ✔️ | git |
| Guzzle | [`/guzzle`](https://endoflife.date/guzzle) | ✔️ | git |
| HAProxy | [`/haproxy`](https://endoflife.date/haproxy) | ✔️ | custom |
| HAProxy | [`/haproxy`](https://endoflife.date/haproxy) | ✔️ | haproxy |
| Harbor | [`/harbor`](https://endoflife.date/harbor) | ✔️ | git |
| Hashicorp Packer | [`/hashicorp-packer`](https://endoflife.date/hashicorp-packer) | ✔️ | git |
| Hashicorp Vault | [`/hashicorp-vault`](https://endoflife.date/hashicorp-vault) | ✔️ | git |
| Apache HBase | [`/hbase`](https://endoflife.date/hbase) | ✔️ | git |
| IBM AIX | [`/ibm-aix`](https://endoflife.date/ibm-aix) | ✔️ | custom, release_table |
| IBM AIX | [`/ibm-aix`](https://endoflife.date/ibm-aix) | ✔️ | ibm-aix, release_table |
| IBM iSeries | [`/ibm-i`](https://endoflife.date/ibm-i) | ✔️ | release_table |
| IBM Semeru Runtime | [`/ibm-semeru-runtime`](https://endoflife.date/ibm-semeru-runtime) | ✔️ | github_releases, release_table |
| Icinga Web | [`/icinga-web`](https://endoflife.date/icinga-web) | ✔️ | git |
| Icinga | [`/icinga`](https://endoflife.date/icinga) | ✔️ | git |
| Icinga Web | [`/icinga-web`](https://endoflife.date/icinga-web) | ✔️ | git |
| Intel Processors | [`/intel-processors`](https://endoflife.date/intel-processors) | ❌ | |
| Internet Explorer | [`/internet-explorer`](https://endoflife.date/internet-explorer) | ❌ | |
| Ionic Framework | [`/ionic`](https://endoflife.date/ionic) | ✔️ | git, release_table |
@@ -192,8 +192,8 @@ As of 2025-05-17, 308 of the 379 products tracked by endoflife.date have automat
| JHipster | [`/jhipster`](https://endoflife.date/jhipster) | ✔️ | npm |
| Jira Software | [`/jira-software`](https://endoflife.date/jira-software) | ✔️ | atlassian_eol, atlassian_versions |
| Joomla! | [`/joomla`](https://endoflife.date/joomla) | ✔️ | git |
| jQuery UI | [`/jquery-ui`](https://endoflife.date/jquery-ui) | ✔️ | git |
| jQuery | [`/jquery`](https://endoflife.date/jquery) | ✔️ | git |
| jQuery UI | [`/jquery-ui`](https://endoflife.date/jquery-ui) | ✔️ | git |
| JReleaser | [`/jreleaser`](https://endoflife.date/jreleaser) | ✔️ | maven |
| Julia | [`/julia`](https://endoflife.date/julia) | ✔️ | git |
| KDE Plasma | [`/kde-plasma`](https://endoflife.date/kde-plasma) | ✔️ | git |
@@ -204,21 +204,22 @@ As of 2025-05-17, 308 of the 379 products tracked by endoflife.date have automat
| Kirby | [`/kirby`](https://endoflife.date/kirby) | ✔️ | git |
| Kong Gateway | [`/kong-gateway`](https://endoflife.date/kong-gateway) | ✔️ | git |
| Kotlin | [`/kotlin`](https://endoflife.date/kotlin) | ✔️ | github_releases |
| Kubernetes | [`/kubernetes`](https://endoflife.date/kubernetes) | ✔️ | git |
| Kubernetes CSI Node Driver Registrar | [`/kubernetes-csi-node-driver-registrar`](https://endoflife.date/kubernetes-csi-node-driver-registrar) | ✔️ | git |
| Kubernetes Node Feature Discovery | [`/kubernetes-node-feature-discovery`](https://endoflife.date/kubernetes-node-feature-discovery) | ✔️ | github_releases |
| Kubernetes | [`/kubernetes`](https://endoflife.date/kubernetes) | ✔️ | git |
| Kuma | [`/kuma`](https://endoflife.date/kuma) | ✔️ | git |
| Kuma | [`/kuma`](https://endoflife.date/kuma) | ✔️ | git, kuma |
| Kyverno | [`/kyverno`](https://endoflife.date/kyverno) | ✔️ | git |
| Laravel | [`/laravel`](https://endoflife.date/laravel) | ✔️ | git, release_table |
| LDAP Account Manager | [`/ldap-account-manager`](https://endoflife.date/ldap-account-manager) | ✔️ | git |
| LibreOffice | [`/libreoffice`](https://endoflife.date/libreoffice) | ✔️ | custom |
| LibreOffice | [`/libreoffice`](https://endoflife.date/libreoffice) | ✔️ | libreoffice |
| LineageOS | [`/lineageos`](https://endoflife.date/lineageos) | ❌ | |
| Linux Kernel | [`/linux`](https://endoflife.date/linux) | ✔️ | github_tags |
| Linux Mint | [`/linuxmint`](https://endoflife.date/linuxmint) | ✔️ | release_table |
| Liquibase | [`/liquibase`](https://endoflife.date/liquibase) | ✔️ | maven |
| Apache Log4j | [`/log4j`](https://endoflife.date/log4j) | ✔️ | maven |
| Logstash | [`/logstash`](https://endoflife.date/logstash) | ✔️ | git |
| Looker | [`/looker`](https://endoflife.date/looker) | ✔️ | custom |
| Lua | [`/lua`](https://endoflife.date/lua) | ✔️ | custom |
| Looker | [`/looker`](https://endoflife.date/looker) | ✔️ | looker |
| Lua | [`/lua`](https://endoflife.date/lua) | ✔️ | lua |
| Apple macOS | [`/macos`](https://endoflife.date/macos) | ✔️ | apple |
| Mageia | [`/mageia`](https://endoflife.date/mageia) | ✔️ | distrowatch |
| Magento | [`/magento`](https://endoflife.date/magento) | ✔️ | git |
@@ -244,7 +245,7 @@ As of 2025-05-17, 308 of the 379 products tracked by endoflife.date have automat
| Neo4j | [`/neo4j`](https://endoflife.date/neo4j) | ✔️ | git, release_table |
| Neos | [`/neos`](https://endoflife.date/neos) | ✔️ | git |
| NetApp ONTAP | [`/netapp-ontap`](https://endoflife.date/netapp-ontap) | ❌ | |
| NetBSD | [`/netbsd`](https://endoflife.date/netbsd) | ✔️ | custom |
| NetBSD | [`/netbsd`](https://endoflife.date/netbsd) | ✔️ | netbsd |
| Nextcloud | [`/nextcloud`](https://endoflife.date/nextcloud) | ✔️ | git, release_table |
| Next.js | [`/nextjs`](https://endoflife.date/nextjs) | ✔️ | npm |
| Nexus Repository | [`/nexus`](https://endoflife.date/nexus) | ✔️ | git, release_table |
@@ -254,6 +255,7 @@ As of 2025-05-17, 308 of the 379 products tracked by endoflife.date have automat
| Node.js | [`/nodejs`](https://endoflife.date/nodejs) | ✔️ | git |
| Nokia Mobile | [`/nokia`](https://endoflife.date/nokia) | ❌ | |
| Nomad | [`/nomad`](https://endoflife.date/nomad) | ✔️ | git |
| Notepad++ | [`/notepad-plus-plus`](https://endoflife.date/notepad-plus-plus) | ✔️ | git |
| NumPy | [`/numpy`](https://endoflife.date/numpy) | ✔️ | pypi |
| Nutanix AOS | [`/nutanix-aos`](https://endoflife.date/nutanix-aos) | ✔️ | nutanix |
| Nutanix Files | [`/nutanix-files`](https://endoflife.date/nutanix-files) | ✔️ | nutanix |
@@ -277,7 +279,8 @@ As of 2025-05-17, 308 of the 379 products tracked by endoflife.date have automat
| OPNsense | [`/opnsense`](https://endoflife.date/opnsense) | ✔️ | git |
| Oracle APEX | [`/oracle-apex`](https://endoflife.date/oracle-apex) | ✔️ | release_table |
| Oracle Database | [`/oracle-database`](https://endoflife.date/oracle-database) | ✔️ | release_table |
| Oracle JDK | [`/oracle-jdk`](https://endoflife.date/oracle-jdk) | ✔️ | custom, release_table |
| Oracle GraalVM | [`/oracle-graalvm`](https://endoflife.date/oracle-graalvm) | ✔️ | graalvm, release_table |
| Oracle JDK | [`/oracle-jdk`](https://endoflife.date/oracle-jdk) | ✔️ | oracle-jdk, release_table |
| Oracle Linux | [`/oracle-linux`](https://endoflife.date/oracle-linux) | ✔️ | distrowatch |
| Oracle Solaris | [`/oracle-solaris`](https://endoflife.date/oracle-solaris) | ❌ | |
| oVirt | [`/ovirt`](https://endoflife.date/ovirt) | ✔️ | git |
@@ -286,12 +289,12 @@ As of 2025-05-17, 308 of the 379 products tracked by endoflife.date have automat
| Palo Alto Networks PAN-OS | [`/panos`](https://endoflife.date/panos) | ✔️ | release_table |
| PCI-DSS | [`/pci-dss`](https://endoflife.date/pci-dss) | ❌ | |
| Perl | [`/perl`](https://endoflife.date/perl) | ✔️ | git |
| PHP | [`/php`](https://endoflife.date/php) | ✔️ | custom |
| PHP | [`/php`](https://endoflife.date/php) | ✔️ | php |
| phpBB | [`/phpbb`](https://endoflife.date/phpbb) | ✔️ | git |
| phpMyAdmin | [`/phpmyadmin`](https://endoflife.date/phpmyadmin) | ✔️ | git |
| Google Pixel Watch | [`/pixel-watch`](https://endoflife.date/pixel-watch) | ❌ | |
| Google Pixel | [`/pixel`](https://endoflife.date/pixel) | ❌ | |
| Plesk | [`/plesk`](https://endoflife.date/plesk) | ✔️ | custom |
| Google Pixel Watch | [`/pixel-watch`](https://endoflife.date/pixel-watch) | | |
| Plesk | [`/plesk`](https://endoflife.date/plesk) | ✔️ | plesk |
| Plone | [`/plone`](https://endoflife.date/plone) | ✔️ | git, release_table |
| pnpm | [`/pnpm`](https://endoflife.date/pnpm) | ✔️ | npm |
| Podman | [`/podman`](https://endoflife.date/podman) | ✔️ | git |
@@ -313,39 +316,40 @@ As of 2025-05-17, 308 of the 379 products tracked by endoflife.date have automat
| RabbitMQ | [`/rabbitmq`](https://endoflife.date/rabbitmq) | ✔️ | git |
| Rancher | [`/rancher`](https://endoflife.date/rancher) | ✔️ | git |
| Raspberry Pi | [`/raspberry-pi`](https://endoflife.date/raspberry-pi) | ❌ | |
| React Native | [`/react-native`](https://endoflife.date/react-native) | ✔️ | npm |
| React | [`/react`](https://endoflife.date/react) | ✔️ | npm |
| React Native | [`/react-native`](https://endoflife.date/react-native) | ✔️ | npm |
| Red Hat build of OpenJDK | [`/redhat-build-of-openjdk`](https://endoflife.date/redhat-build-of-openjdk) | ✔️ | redhat_lifecycles |
| Red Hat JBoss Enterprise Application Platform | [`/redhat-jboss-eap`](https://endoflife.date/redhat-jboss-eap) | ✔️ | custom, redhat_lifecycles |
| Red Hat OpenShift | [`/red-hat-openshift`](https://endoflife.date/red-hat-openshift) | ✔️ | custom |
| Red Hat Satellite | [`/redhat-satellite`](https://endoflife.date/redhat-satellite) | ✔️ | custom |
| Red Hat JBoss Enterprise Application Platform | [`/redhat-jboss-eap`](https://endoflife.date/redhat-jboss-eap) | ✔️ | red-hat-jboss-eap-7, red-hat-jboss-eap-8, redhat_lifecycles |
| Red Hat OpenShift | [`/red-hat-openshift`](https://endoflife.date/red-hat-openshift) | ✔️ | red-hat-openshift |
| Red Hat Satellite | [`/redhat-satellite`](https://endoflife.date/redhat-satellite) | ✔️ | red-hat-satellite |
| Redis | [`/redis`](https://endoflife.date/redis) | ✔️ | git, release_table |
| Redmine | [`/redmine`](https://endoflife.date/redmine) | ✔️ | git |
| Red Hat Enterprise Linux | [`/rhel`](https://endoflife.date/rhel) | ✔️ | redhat_lifecycles |
| Robo | [`/robo`](https://endoflife.date/robo) | ✔️ | git, release_table |
| Rocket.Chat | [`/rocket-chat`](https://endoflife.date/rocket-chat) | ✔️ | git |
| Rocky Linux | [`/rocky-linux`](https://endoflife.date/rocky-linux) | ✔️ | custom, release_table |
| Rocky Linux | [`/rocky-linux`](https://endoflife.date/rocky-linux) | ✔️ | release_table, rocky-linux |
| ROS | [`/ros`](https://endoflife.date/ros) | ✔️ | ros |
| ROS 2 | [`/ros-2`](https://endoflife.date/ros-2) | ❌ | |
| ROS | [`/ros`](https://endoflife.date/ros) | ❌ | |
| Roundcube Webmail | [`/roundcube`](https://endoflife.date/roundcube) | ✔️ | git |
| rtpengine | [`/rtpengine`](https://endoflife.date/rtpengine) | ✔️ | git |
| Ruby on Rails | [`/rails`](https://endoflife.date/rails) | ✔️ | git |
| Ruby | [`/ruby`](https://endoflife.date/ruby) | ✔️ | git |
| Ruby on Rails | [`/rails`](https://endoflife.date/rails) | ✔️ | git |
| Rust | [`/rust`](https://endoflife.date/rust) | ✔️ | git |
| Salt | [`/salt`](https://endoflife.date/salt) | ✔️ | git, release_table |
| Samsung Galaxy Tab | [`/samsung-galaxy-tab`](https://endoflife.date/samsung-galaxy-tab) | ✔️ | samsung-security |
| Samsung Galaxy Watch | [`/samsung-galaxy-watch`](https://endoflife.date/samsung-galaxy-watch) | ❌ | |
| Samsung Mobile | [`/samsung-mobile`](https://endoflife.date/samsung-mobile) | ✔️ | custom |
| Samsung Mobile | [`/samsung-mobile`](https://endoflife.date/samsung-mobile) | ✔️ | samsung-security |
| SapMachine | [`/sapmachine`](https://endoflife.date/sapmachine) | ✔️ | github_releases |
| Scala | [`/scala`](https://endoflife.date/scala) | ✔️ | github_releases |
| Microsoft SharePoint | [`/sharepoint`](https://endoflife.date/sharepoint) | ❌ | |
| Shopware | [`/shopware`](https://endoflife.date/shopware) | ✔️ | git |
| Silverstripe CMS | [`/silverstripe`](https://endoflife.date/silverstripe) | ✔️ | git, release_table |
| Slackware Linux | [`/slackware`](https://endoflife.date/slackware) | ✔️ | distrowatch |
| SUSE Linux Enterprise Server | [`/sles`](https://endoflife.date/sles) | | |
| SUSE Linux Enterprise Server | [`/sles`](https://endoflife.date/sles) | ✔️ | sles |
| Apache Solr | [`/solr`](https://endoflife.date/solr) | ✔️ | git |
| SonarQube | [`/sonar`](https://endoflife.date/sonar) | ✔️ | git |
| Sourcegraph | [`/sourcegraph`](https://endoflife.date/sourcegraph) | ✔️ | git |
| Splunk | [`/splunk`](https://endoflife.date/splunk) | ✔️ | custom |
| Splunk | [`/splunk`](https://endoflife.date/splunk) | ✔️ | splunk |
| Spring Boot | [`/spring-boot`](https://endoflife.date/spring-boot) | ✔️ | git, release_table |
| Spring Framework | [`/spring-framework`](https://endoflife.date/spring-framework) | ✔️ | git, release_table |
| SQLite | [`/sqlite`](https://endoflife.date/sqlite) | ✔️ | git |
@@ -362,7 +366,7 @@ As of 2025-05-17, 308 of the 379 products tracked by endoflife.date have automat
| Apache Tomcat | [`/tomcat`](https://endoflife.date/tomcat) | ✔️ | maven |
| Traefik | [`/traefik`](https://endoflife.date/traefik) | ✔️ | git, release_table |
| Twig | [`/twig`](https://endoflife.date/twig) | ✔️ | git |
| TYPO3 | [`/typo3`](https://endoflife.date/typo3) | ✔️ | custom |
| TYPO3 | [`/typo3`](https://endoflife.date/typo3) | ✔️ | typo3 |
| Ubuntu | [`/ubuntu`](https://endoflife.date/ubuntu) | ✔️ | distrowatch |
| Umbraco CMS | [`/umbraco`](https://endoflife.date/umbraco) | ✔️ | git, release_table |
| Unity | [`/unity`](https://endoflife.date/unity) | ❌ | |
@@ -372,10 +376,10 @@ As of 2025-05-17, 308 of the 379 products tracked by endoflife.date have automat
| Veeam Backup & Replication | [`/veeam-backup-and-replication`](https://endoflife.date/veeam-backup-and-replication) | ✔️ | veeam |
| Veeam Backup for Microsoft 365 | [`/veeam-backup-for-microsoft-365`](https://endoflife.date/veeam-backup-for-microsoft-365) | ✔️ | veeam |
| Veeam ONE | [`/veeam-one`](https://endoflife.date/veeam-one) | ✔️ | veeam |
| VirtualBox | [`/virtualbox`](https://endoflife.date/virtualbox) | | |
| VirtualBox | [`/virtualbox`](https://endoflife.date/virtualbox) | ✔️ | virtualbox |
| Apple visionOS | [`/visionos`](https://endoflife.date/visionos) | ✔️ | apple |
| Visual COBOL | [`/visual-cobol`](https://endoflife.date/visual-cobol) | ❌ | |
| Microsoft Visual Studio | [`/visual-studio`](https://endoflife.date/visual-studio) | ✔️ | custom |
| Microsoft Visual Studio | [`/visual-studio`](https://endoflife.date/visual-studio) | ✔️ | visual-studio |
| Vitess | [`/vitess`](https://endoflife.date/vitess) | ✔️ | git |
| VMware Cloud Foundation | [`/vmware-cloud-foundation`](https://endoflife.date/vmware-cloud-foundation) | ❌ | |
| VMware ESXi | [`/esxi`](https://endoflife.date/esxi) | ❌ | |
@@ -389,19 +393,19 @@ As of 2025-05-17, 308 of the 379 products tracked by endoflife.date have automat
| Apple watchOS | [`/watchos`](https://endoflife.date/watchos) | ✔️ | apple |
| Weakforced | [`/weakforced`](https://endoflife.date/weakforced) | ✔️ | git |
| WeeChat | [`/weechat`](https://endoflife.date/weechat) | ✔️ | git |
| Microsoft Windows | [`/windows`](https://endoflife.date/windows) | ❌ | |
| Microsoft Windows Embedded | [`/windows-embedded`](https://endoflife.date/windows-embedded) | ❌ | |
| Microsoft Nano Server | [`/windows-nano-server`](https://endoflife.date/windows-nano-server) | ❌ | |
| Microsoft Windows Server Core | [`/windows-server-core`](https://endoflife.date/windows-server-core) | ❌ | |
| Microsoft Windows Server | [`/windows-server`](https://endoflife.date/windows-server) | ❌ | |
| Microsoft Windows | [`/windows`](https://endoflife.date/windows) | ❌ | |
| Microsoft Windows Server Core | [`/windows-server-core`](https://endoflife.date/windows-server-core) | ❌ | |
| Wireshark | [`/wireshark`](https://endoflife.date/wireshark) | ✔️ | git |
| WordPress | [`/wordpress`](https://endoflife.date/wordpress) | ✔️ | git |
| XCP-ng | [`/xcp-ng`](https://endoflife.date/xcp-ng) | ✔️ | git, release_table |
| Yarn | [`/yarn`](https://endoflife.date/yarn) | ✔️ | npm |
| Yocto Project | [`/yocto`](https://endoflife.date/yocto) | ✔️ | git |
| Zabbix | [`/zabbix`](https://endoflife.date/zabbix) | ✔️ | git, release_table |
| Zabbix | [`/zabbix`](https://endoflife.date/zabbix) | ✔️ | git |
| Zentyal | [`/zentyal`](https://endoflife.date/zentyal) | ✔️ | release_table |
| Zerto | [`/zerto`](https://endoflife.date/zerto) | | |
| Zerto | [`/zerto`](https://endoflife.date/zerto) | ✔️ | release_table |
| Apache ZooKeeper | [`/zookeeper`](https://endoflife.date/zookeeper) | ✔️ | maven |
This table has been generated by [report.py](/report.py).

View File

@@ -11,7 +11,9 @@ from ruamel.yaml import YAML
from ruamel.yaml.representer import RoundTripRepresenter
from ruamel.yaml.resolver import Resolver
from src.common.endoflife import list_products
from src.common.gha import GitHubOutput
from src.common.releasedata import DATA_DIR
"""
Updates the `release`, `latest` and `latestReleaseDate` property in automatically updated pages
@@ -243,7 +245,6 @@ if __name__ == "__main__":
parser = argparse.ArgumentParser(description='Update product releases.')
parser.add_argument('product', nargs='?', help='restrict update to the given product')
parser.add_argument('-p', '--product-dir', required=True, help='path to the product directory')
parser.add_argument('-d', '--data-dir', required=True, help='path to the release data directory')
parser.add_argument('-v', '--verbose', action='store_true', help='enable verbose logging')
args = parser.parse_args()
@@ -257,10 +258,11 @@ if __name__ == "__main__":
RoundTripRepresenter.ignore_aliases = lambda x, y: True # NOQA: ARG005
products_dir = Path(args.product_dir)
product_names = [args.product] if args.product else [p.stem for p in products_dir.glob("*.md")]
data_dir = Path(__file__).resolve().parent / DATA_DIR
products = list_products(products_dir, args.product)
github_output = GitHubOutput("warning")
with github_output:
for product_name in sorted(product_names):
logging.debug(f"Processing {product_name}")
update_product(product_name, products_dir, Path(args.data_dir), github_output)
for product in products:
logging.debug(f"Processing {product.name}")
update_product(product.name, products_dir, data_dir, github_output)

View File

@@ -1,8 +1,16 @@
import argparse
import time
from pathlib import Path
from src.common import endoflife
products = endoflife.list_products()
if __name__ == "__main__":
parser = argparse.ArgumentParser(description='Create report on product automation.')
parser.add_argument('-p', '--product-dir', required=True, help='path to the product directory')
args = parser.parse_args()
products_dir = Path(args.product_dir)
products = endoflife.list_products(products_dir)
count_auto = len([product for product in products if product.auto_configs()])
print(f"As of {time.strftime('%Y-%m-%d')}, {count_auto} of the {len(products)} products"

View File

@@ -1,11 +1,11 @@
import logging
from common import dates, endoflife, http, releasedata
from common import dates, http, releasedata
"""Fetches EKS versions from AWS docs.
Now that AWS no longer publishes docs on GitHub, we use the Web Archive to get the older versions."""
for config in endoflife.list_configs_from_argv():
for config in releasedata.list_configs_from_argv():
with releasedata.ProductData(config.product) as product_data:
html = http.fetch_html(config.url)

View File

@@ -1,10 +1,10 @@
import logging
from common import dates, endoflife, http, releasedata
from common import dates, http, releasedata
"""Fetches Amazon Neptune versions from its RSS feed on docs.aws.amazon.com."""
for config in endoflife.list_configs_from_argv():
for config in releasedata.list_configs_from_argv():
with releasedata.ProductData(config.product) as product_data:
rss = http.fetch_xml(config.url)

View File

@@ -1,10 +1,10 @@
from common import dates, endoflife, releasedata
from common import dates, releasedata
from common.git import Git
"""Fetches Apache HTTP Server versions and release date from its git repository
by looking at the STATUS file of each <major>.<minor>.x branch."""
for config in endoflife.list_configs_from_argv():
for config in releasedata.list_configs_from_argv():
with releasedata.ProductData(config.product) as product_data:
git = Git(config.url)
git.setup()

View File

@@ -1,8 +1,8 @@
import logging
from common import dates, endoflife, http, releasedata
from common import dates, http, releasedata
for config in endoflife.list_configs_from_argv():
for config in releasedata.list_configs_from_argv():
with releasedata.ProductData(config.product) as product_data:
html = http.fetch_html(config.url)

View File

@@ -2,7 +2,7 @@ import logging
import re
from bs4 import BeautifulSoup
from common import dates, endoflife, http, releasedata
from common import dates, http, releasedata
"""Fetches and parses version and release date information from Apple's support website."""
@@ -22,7 +22,7 @@ URLS = [
DATE_PATTERN = re.compile(r"\b\d+\s[A-Za-z]+\s\d+\b")
for config in endoflife.list_configs_from_argv():
for config in releasedata.list_configs_from_argv():
with releasedata.ProductData(config.product) as product_data:
# URLs are cached to avoid rate limiting by support.apple.com.
soups = [BeautifulSoup(response.text, features="html5lib") for response in http.fetch_urls(URLS)]

View File

@@ -1,10 +1,10 @@
from bs4 import BeautifulSoup
from common import dates, endoflife, http, releasedata
from common import dates, http, releasedata
"""Fetches Artifactory versions from https://jfrog.com, using requests_html because JavaScript is
needed to render the page."""
for config in endoflife.list_configs_from_argv():
for config in releasedata.list_configs_from_argv():
with releasedata.ProductData(config.product) as product_data:
content = http.fetch_javascript_url(config.url, wait_until = 'networkidle')
soup = BeautifulSoup(content, 'html.parser')

View File

@@ -1,7 +1,7 @@
import logging
from bs4 import BeautifulSoup
from common import dates, endoflife, http, releasedata
from common import dates, http, releasedata
"""Fetches EOL dates from Atlassian EOL page.
@@ -9,7 +9,7 @@ This script takes a selector argument which is the product title identifier on t
`AtlassianSupportEndofLifePolicy-JiraSoftware`.
"""
for config in endoflife.list_configs_from_argv():
for config in releasedata.list_configs_from_argv():
with releasedata.ProductData(config.product) as product_data:
content = http.fetch_javascript_url(config.url)
soup = BeautifulSoup(content, features="html5lib")

View File

@@ -1,5 +1,5 @@
from bs4 import BeautifulSoup
from common import dates, endoflife, http, releasedata
from common import dates, http, releasedata
"""Fetches versions from Atlassian download-archives pages.
@@ -7,7 +7,7 @@ This script takes a single argument which is the url of the product's download-a
`https://www.atlassian.com/software/confluence/download-archives`.
"""
for config in endoflife.list_configs_from_argv():
for config in releasedata.list_configs_from_argv():
with releasedata.ProductData(config.product) as product_data:
content = http.fetch_javascript_url(config.url, wait_until='networkidle')
soup = BeautifulSoup(content, 'html5lib')

View File

@@ -1,10 +1,10 @@
import logging
from common import dates, endoflife, http, releasedata
from common import dates, http, releasedata
"""Fetches AWS lambda runtimes with their support / EOL dates from https://docs.aws.amazon.com."""
for config in endoflife.list_configs_from_argv():
for config in releasedata.list_configs_from_argv():
with releasedata.ProductData(config.product) as product_data:
html = http.fetch_html(config.url)

View File

@@ -1,9 +1,9 @@
from common import dates, endoflife, http, releasedata
from common import dates, http, releasedata
"""Fetches versions from repositories managed with cgit, such as the Linux kernel repository.
Ideally we would want to use the git repository directly, but cgit-managed repositories don't support partial clone."""
for config in endoflife.list_configs_from_argv():
for config in releasedata.list_configs_from_argv():
with releasedata.ProductData(config.product) as product_data:
html = http.fetch_html(config.url + '/refs/tags')

View File

@@ -1,4 +1,4 @@
from common import dates, endoflife, http, releasedata
from common import dates, http, releasedata
from common.git import Git
"""Fetch released versions from docs.chef.io and retrieve their date from GitHub.
@@ -7,7 +7,7 @@ docs.chef.io needs to be scraped because not all tagged versions are actually re
More context on https://github.com/endoflife-date/endoflife.date/pull/4425#discussion_r1447932411.
"""
for config in endoflife.list_configs_from_argv():
for config in releasedata.list_configs_from_argv():
with releasedata.ProductData(config.product) as product_data:
html = http.fetch_html(config.url)
released_versions = [h2.get('id') for h2 in html.find_all('h2', id=True) if h2.get('id')]

View File

@@ -1,4 +1,4 @@
from common import dates, endoflife, github, http, releasedata
from common import dates, github, http, releasedata
"""Fetch released versions from docs.chef.io and retrieve their date from GitHub.
docs.chef.io needs to be scraped because not all tagged versions are actually released.
@@ -6,7 +6,7 @@ docs.chef.io needs to be scraped because not all tagged versions are actually re
More context on https://github.com/endoflife-date/endoflife.date/pull/4425#discussion_r1447932411.
"""
for config in endoflife.list_configs_from_argv():
for config in releasedata.list_configs_from_argv():
with releasedata.ProductData(config.product) as product_data:
html = http.fetch_html(config.url)
released_versions = [h2.get('id') for h2 in html.find_all('h2', id=True) if h2.get('id')]

View File

@@ -1,6 +1,6 @@
import re
from common import dates, endoflife, http, releasedata
from common import dates, http, releasedata
"""Fetches versions from Adobe ColdFusion release notes on helpx.adobe.com.
@@ -21,7 +21,7 @@ FIXED_VERSIONS = {
"2023.0.0": dates.date(2022, 5, 16), # https://coldfusion.adobe.com/2023/05/coldfusion2023-release/
}
for config in endoflife.list_configs_from_argv():
for config in releasedata.list_configs_from_argv():
with releasedata.ProductData(config.product) as product_data:
html = http.fetch_html(config.url)

View File

@@ -1,8 +1,5 @@
import itertools
import logging
import os
import re
import sys
from datetime import datetime
from pathlib import Path
@@ -15,8 +12,6 @@ DEFAULT_VERSION_REGEX = r"^v?(?P<major>[1-9]\d*)\.(?P<minor>\d+)(\.(?P<patch>\d+
DEFAULT_VERSION_PATTERN = re.compile(DEFAULT_VERSION_REGEX)
DEFAULT_VERSION_TEMPLATE = "{{major}}{% if minor %}.{{minor}}{% if patch %}.{{patch}}{% if tiny %}.{{tiny}}{% endif %}{% endif %}{% endif %}"
PRODUCTS_PATH = Path(os.environ.get("PRODUCTS_PATH", "website/products"))
class AutoConfig:
def __init__(self, product: str, data: dict) -> None:
@@ -58,9 +53,9 @@ class AutoConfig:
class ProductFrontmatter:
def __init__(self, name: str) -> None:
self.name: str = name
self.path: Path = PRODUCTS_PATH / f"{name}.md"
def __init__(self, path: Path) -> None:
self.path: Path = path
self.name: str = path.stem
self.data = None
if self.path.is_file():
@@ -109,37 +104,19 @@ class ProductFrontmatter:
return None
def list_products(products_filter: str = None) -> list[ProductFrontmatter]:
"""Return a list of products that are using the same given update method."""
def list_products(products_dir: Path, product_name: str = None) -> list[ProductFrontmatter]:
product_names = [product_name] if product_name else sorted([p.stem for p in products_dir.glob("*.md")])
products = []
for product_file in sorted(PRODUCTS_PATH.glob("*.md")):
product_name = product_file.stem
if products_filter and product_name != products_filter:
continue
for product_name in product_names:
try:
products.append(ProductFrontmatter(product_name))
products.append(ProductFrontmatter(products_dir / f"{product_name}.md"))
except Exception as e:
logging.exception(f"failed to load product data for {product_name}: {e}")
return products
def list_configs(products_filter: str = None, methods_filter: str = None, urls_filter: str = None) -> list[AutoConfig]:
"""Return a list of auto configs, filtering by product name, method, and URL."""
products = list_products(products_filter)
configs_by_product = [p.auto_configs(methods_filter, urls_filter) for p in products]
return list(itertools.chain.from_iterable(configs_by_product)) # flatten the list of lists
def list_configs_from_argv() -> list[AutoConfig]:
products_filter = sys.argv[1] if len(sys.argv) > 1 else None
methods_filter = sys.argv[2] if len(sys.argv) > 1 else None
urls_filter = sys.argv[3] if len(sys.argv) > 2 else None
return list_configs(products_filter, methods_filter, urls_filter)
def to_identifier(s: str) -> str:
"""Convert a string to a valid endoflife.date identifier."""
identifier = s.strip().lower()

View File

@@ -1,15 +1,16 @@
import argparse
import json
import logging
import os
import sys
from datetime import datetime, timezone
from pathlib import Path
from types import TracebackType
from typing import Optional, Type
# Do not update the format: it's also used to declare groups in the GitHub Actions logs.
logging.basicConfig(format="%(message)s", level=logging.INFO)
from . import endoflife
VERSIONS_PATH = Path(os.environ.get("VERSIONS_PATH", "releases"))
SRC_DIR = Path('src')
DATA_DIR = Path('releases')
class ProductUpdateError(Exception):
@@ -108,7 +109,7 @@ class ProductVersion:
class ProductData:
def __init__(self, name: str) -> None:
self.name: str = name
self.versions_path: Path = VERSIONS_PATH / f"{name}.json"
self.versions_path: Path = DATA_DIR / f"{name}.json"
self.releases = {}
self.versions: dict[str, ProductVersion] = {}
self.updated = False
@@ -190,3 +191,21 @@ class ProductData:
def __repr__(self) -> str:
return self.name
def list_configs_from_argv() -> list[endoflife.AutoConfig]:
return parse_argv()[1]
def parse_argv() -> tuple[endoflife.ProductFrontmatter, list[endoflife.AutoConfig]]:
parser = argparse.ArgumentParser(description=sys.argv[0])
parser.add_argument('-p', '--product', required=True, help='path to the product')
parser.add_argument('-m', '--method', required=True, help='method to filter by')
parser.add_argument('-u', '--url', required=True, help='url to filter by')
parser.add_argument('-v', '--verbose', action='store_true', help='enable verbose logging')
args = parser.parse_args()
# Do not update the format: it's also used to declare groups in the GitHub Actions logs.
logging.basicConfig(format="%(message)s", level=(logging.DEBUG if args.verbose else logging.INFO))
product = endoflife.ProductFrontmatter(Path(args.product))
return product, product.auto_configs(args.method, args.url)

View File

@@ -2,7 +2,7 @@ import datetime
import re
from bs4 import BeautifulSoup
from common import dates, endoflife, http, releasedata
from common import dates, http, releasedata
MILESTONE_PATTERN = re.compile(r'COS \d+ LTS')
VERSION_PATTERN = re.compile(r"^(cos-\d+-\d+-\d+-\d+)")
@@ -14,7 +14,7 @@ def parse_date(date_text: str) -> datetime:
return dates.parse_date(date_text)
for config in endoflife.list_configs_from_argv():
for config in releasedata.list_configs_from_argv():
with releasedata.ProductData(config.product) as product_data:
main = http.fetch_url(config.url)
main_soup = BeautifulSoup(main.text, features="html5lib")

View File

@@ -1,7 +1,7 @@
import logging
from bs4 import BeautifulSoup
from common import dates, endoflife, http, releasedata
from common import dates, http, releasedata
"""Fetches versions from release notes of each minor version on docs.couchbase.com.
@@ -16,7 +16,7 @@ MANUAL_VERSIONS = {
"7.2.0": dates.date(2023, 6, 1), # https://www.couchbase.com/blog/couchbase-capella-spring-release-72/
}
for config in endoflife.list_configs_from_argv():
for config in releasedata.list_configs_from_argv():
with releasedata.ProductData(config.product) as product_data:
html = http.fetch_html(f"{config.url}/current/install/install-intro.html")

View File

@@ -1,7 +1,7 @@
from pathlib import Path
from subprocess import run
from common import dates, endoflife, releasedata
from common import dates, releasedata
from common.git import Git
"""Fetch Debian versions by parsing news in www.debian.org source repository."""
@@ -40,7 +40,7 @@ def extract_point_versions(p: releasedata.ProductData, repo_dir: Path) -> None:
(date, version) = line.split(' ')
p.declare_version(version, dates.parse_date(date))
for config in endoflife.list_configs_from_argv():
for config in releasedata.list_configs_from_argv():
with releasedata.ProductData(config.product) as product_data:
git = Git(config.url)
git.setup()

View File

@@ -1,6 +1,6 @@
from common import dates, endoflife, http, releasedata
from common import dates, http, releasedata
for config in endoflife.list_configs_from_argv():
for config in releasedata.list_configs_from_argv():
with releasedata.ProductData(config.product) as product_data:
html = http.fetch_html(f"https://distrowatch.com/index.php?distribution={config.url}")

View File

@@ -17,6 +17,6 @@ def fetch_releases(p: releasedata.ProductData, c: endoflife.AutoConfig, url: str
fetch_releases(p, c, data["next"])
for config in endoflife.list_configs_from_argv():
for config in releasedata.list_configs_from_argv():
with releasedata.ProductData(config.product) as product_data:
fetch_releases(product_data, config, f"https://hub.docker.com/v2/repositories/{config.url}/tags?page_size=100&page=1")

View File

@@ -1,7 +1,7 @@
import urllib.parse
from bs4 import BeautifulSoup
from common import dates, endoflife, http, releasedata
from common import dates, http, releasedata
"""Fetch Firefox versions with their dates from https://www.mozilla.org/.
@@ -20,7 +20,7 @@ The script will need to be updated if someday those conditions are not met."""
MAX_VERSIONS_LIMIT = 100
for config in endoflife.list_configs_from_argv():
for config in releasedata.list_configs_from_argv():
with releasedata.ProductData(config.product) as product_data:
releases_page = http.fetch_url(config.url)
releases_soup = BeautifulSoup(releases_page.text, features="html5lib")

View File

@@ -14,7 +14,7 @@ References:
import re
from typing import Any, Generator, Iterator
from common import dates, endoflife, http, releasedata
from common import dates, http, releasedata
def parse_markdown_tables(lineiter: Iterator[str]) -> Generator[list[list[Any]], Any, None]:
@@ -50,7 +50,7 @@ def maybe_markdown_table_row(line: str) -> list[str] | None:
return None
return [x.strip() for x in line.strip('|').split('|')]
for config in endoflife.list_configs_from_argv():
for config in releasedata.list_configs_from_argv():
with releasedata.ProductData(config.product) as product:
resp = http.fetch_url(config.url)
resp.raise_for_status()

View File

@@ -1,9 +1,9 @@
from common import dates, endoflife, releasedata
from common import dates, releasedata
from common.git import Git
"""Fetches versions from tags in a git repository. This replace the old update.rb script."""
for config in endoflife.list_configs_from_argv():
for config in releasedata.list_configs_from_argv():
with releasedata.ProductData(config.product) as product_data:
git = Git(config.url)
git.setup(bare=True)

View File

@@ -1,11 +1,11 @@
from common import dates, endoflife, github, releasedata
from common import dates, github, releasedata
"""Fetches versions from GitHub releases using the GraphQL API and the GitHub CLI.
Note: GraphQL API and GitHub CLI are used because it's simpler: no need to manage pagination and authentication.
"""
for config in endoflife.list_configs_from_argv():
for config in releasedata.list_configs_from_argv():
with releasedata.ProductData(config.product) as product_data:
for release in github.fetch_releases(config.url):
if release.is_prerelease:

View File

@@ -1,11 +1,11 @@
from common import dates, endoflife, github, releasedata
from common import dates, github, releasedata
"""Fetches versions from GitHub tags using the GraphQL API and the GitHub CLI.
Note: GraphQL API and GitHub CLI are used because it's simpler: no need to manage pagination and authentication.
"""
for config in endoflife.list_configs_from_argv():
for config in releasedata.list_configs_from_argv():
with releasedata.ProductData(config.product) as product_data:
for tag in github.fetch_tags(config.url):
version_str = tag.name

View File

@@ -1,6 +1,6 @@
import re
from common import dates, endoflife, http, releasedata
from common import dates, http, releasedata
# https://regex101.com/r/zPxBqT/1
VERSION_PATTERN = re.compile(r"\d.\d+\.\d+-gke\.\d+")
@@ -11,7 +11,7 @@ URL_BY_PRODUCT = {
"google-kubernetes-engine-rapid": "https://cloud.google.com/kubernetes-engine/docs/release-notes-rapid",
}
for config in endoflife.list_configs_from_argv(): # noqa: B007 multiple JSON produced for historical reasons
for config in releasedata.list_configs_from_argv(): # noqa: B007 multiple JSON produced for historical reasons
for product_name, url in URL_BY_PRODUCT.items():
with releasedata.ProductData(product_name) as product_data:
html = http.fetch_html(url)

View File

@@ -1,8 +1,8 @@
import logging
from common import dates, endoflife, http, releasedata
from common import dates, http, releasedata
for config in endoflife.list_configs_from_argv():
for config in releasedata.list_configs_from_argv():
with releasedata.ProductData(config.product) as product_data:
html = http.fetch_html(config.url)
table_selector = config.data.get("table_selector", "#previous-releases + table").strip()

View File

@@ -1,11 +1,11 @@
import re
from common import dates, endoflife, http, releasedata
from common import dates, http, releasedata
CYCLE_PATTERN = re.compile(r"^(\d+\.\d+)/$")
DATE_AND_VERSION_PATTERN = re.compile(r"^(\d{4})/(\d{2})/(\d{2})\s+:\s+(\d+\.\d+\.\d.?)$") # https://regex101.com/r/1JCnFC/1
for config in endoflife.list_configs_from_argv():
for config in releasedata.list_configs_from_argv():
with releasedata.ProductData(config.product) as product_data:
# First, get all minor releases from the download page
download_html = http.fetch_html(config.url)

View File

@@ -1,6 +1,6 @@
from common import dates, endoflife, http, releasedata
from common import dates, http, releasedata
for config in endoflife.list_configs_from_argv():
for config in releasedata.list_configs_from_argv():
with releasedata.ProductData(config.product) as product_data:
html = http.fetch_html(config.url)

View File

@@ -1,6 +1,6 @@
import logging
from common import dates, endoflife, http, releasedata
from common import dates, http, releasedata
"""Fetch version data for Kuma from https://raw.githubusercontent.com/kumahq/kuma/master/versions.yml.
"""
@@ -9,7 +9,7 @@ RELEASE_FIELD = 'release'
RELEASE_DATE_FIELD = 'releaseDate'
EOL_FIELD = 'endOfLifeDate'
for config in endoflife.list_configs_from_argv():
for config in releasedata.list_configs_from_argv():
with releasedata.ProductData(config.product) as product_data:
versions_data = http.fetch_yaml(config.url)

View File

@@ -1,10 +1,10 @@
import logging
from common import dates, endoflife, http, releasedata
from common import dates, http, releasedata
"""Fetches LibreOffice versions from https://downloadarchive.documentfoundation.org/libreoffice/old/"""
for config in endoflife.list_configs_from_argv():
for config in releasedata.list_configs_from_argv():
with releasedata.ProductData(config.product) as product_data:
html = http.fetch_html(config.url)

View File

@@ -1,14 +1,14 @@
import re
from bs4 import BeautifulSoup
from common import dates, endoflife, http, releasedata
from common import dates, http, releasedata
"""Fetch Looker versions from the Google Cloud release notes RSS feed.
"""
ANNOUNCEMENT_PATTERN = re.compile(r"includes\s+the\s+following\s+changes", re.IGNORECASE)
for config in endoflife.list_configs_from_argv():
for config in releasedata.list_configs_from_argv():
with releasedata.ProductData(config.product) as product_data:
rss = http.fetch_xml(config.url)

View File

@@ -1,13 +1,13 @@
import re
from common import dates, endoflife, http, releasedata
from common import dates, http, releasedata
"""Fetches Lua releases from lua.org."""
RELEASED_AT_PATTERN = re.compile(r"Lua\s*(?P<release>\d+\.\d+)\s*was\s*released\s*on\s*(?P<release_date>\d+\s*\w+\s*\d{4})")
VERSION_PATTERN = re.compile(r"(?P<version>\d+\.\d+\.\d+),\s*released\s*on\s*(?P<version_date>\d+\s*\w+\s*\d{4})")
for config in endoflife.list_configs_from_argv():
for config in releasedata.list_configs_from_argv():
with releasedata.ProductData(config.product) as product_data:
html = http.fetch_html(config.url, features = 'html.parser')
page_text = html.text # HTML is broken, no way to parse it with beautifulsoup

View File

@@ -1,8 +1,8 @@
from datetime import datetime, timezone
from common import endoflife, http, releasedata
from common import http, releasedata
for config in endoflife.list_configs_from_argv():
for config in releasedata.list_configs_from_argv():
with releasedata.ProductData(config.product) as product_data:
start = 0
group_id, artifact_id = config.url.split("/")

View File

@@ -1,10 +1,10 @@
import logging
from common import dates, endoflife, http, releasedata
from common import dates, http, releasedata
"""Fetches NetBSD versions and EOL information from https://www.netbsd.org/."""
for config in endoflife.list_configs_from_argv():
for config in releasedata.list_configs_from_argv():
with releasedata.ProductData(config.product) as product_data:
html = http.fetch_html(config.url)

View File

@@ -1,6 +1,6 @@
from common import dates, endoflife, http, releasedata
from common import dates, http, releasedata
for config in endoflife.list_configs_from_argv():
for config in releasedata.list_configs_from_argv():
with releasedata.ProductData(config.product) as product_data:
data = http.fetch_json(f"https://registry.npmjs.org/{config.url}")
for version_str in data["versions"]:

View File

@@ -1,8 +1,8 @@
from common import dates, endoflife, http, releasedata
from common import dates, http, releasedata
"""Fetch Nutanix products versions from https://portal.nutanix.com/api/v1."""
for config in endoflife.list_configs_from_argv():
for config in releasedata.list_configs_from_argv():
with releasedata.ProductData(config.product) as product_data:
data = http.fetch_json(f"https://portal.nutanix.com/api/v1/eol/find?type={config.url}")

View File

@@ -1,11 +1,11 @@
from bs4 import BeautifulSoup
from common import dates, endoflife, http, releasedata
from common import dates, http, releasedata
"""Fetch Java versions from https://www.java.com/releases/.
This script is using requests-html because the page needs JavaScript to render correctly."""
for config in endoflife.list_configs_from_argv():
for config in releasedata.list_configs_from_argv():
with releasedata.ProductData(config.product) as product_data:
content = http.fetch_javascript_url(config.url, wait_until='networkidle')
soup = BeautifulSoup(content, 'html.parser')

View File

@@ -1,8 +1,8 @@
from common import dates, endoflife, http, releasedata
from common import dates, http, releasedata
"""Fetches pan-os versions from https://github.com/mrjcap/panos-versions/."""
for config in endoflife.list_configs_from_argv():
for config in releasedata.list_configs_from_argv():
with releasedata.ProductData(config.product) as product_data:
versions = http.fetch_json(config.url)

View File

@@ -1,6 +1,6 @@
from common import dates, endoflife, http, releasedata
from common import dates, http, releasedata
for config in endoflife.list_configs_from_argv():
for config in releasedata.list_configs_from_argv():
with releasedata.ProductData(config.product) as product_data:
# Fetch major versions
latest_by_major = http.fetch_url(config.url).json()

View File

@@ -1,11 +1,11 @@
from common import dates, endoflife, http, releasedata
from common import dates, http, releasedata
"""Fetches versions from Plesk's change log.
Only 18.0.20.3 and later will be picked up, as the format of the change log for 18.0.20 and 18.0.19 are different and
there is no entry for GA of version 18.0.18 and older."""
for config in endoflife.list_configs_from_argv():
for config in releasedata.list_configs_from_argv():
with releasedata.ProductData(config.product) as product_data:
html = http.fetch_html(config.url)

View File

@@ -1,6 +1,6 @@
from common import dates, endoflife, http, releasedata
from common import dates, http, releasedata
for config in endoflife.list_configs_from_argv():
for config in releasedata.list_configs_from_argv():
with releasedata.ProductData(config.product) as product_data:
data = http.fetch_json(f"https://pypi.org/pypi/{config.url}/json")

View File

@@ -1,6 +1,6 @@
import logging
from common import dates, endoflife, http, releasedata
from common import dates, http, releasedata
"""Fetches Amazon RDS versions from the version management pages on AWS docs.
@@ -8,7 +8,7 @@ Pages parsed by this script are expected to have version tables with a version i
in the third column (usually named 'RDS release date').
"""
for config in endoflife.list_configs_from_argv():
for config in releasedata.list_configs_from_argv():
with releasedata.ProductData(config.product) as product_data:
html = http.fetch_html(config.url)

View File

@@ -1,10 +1,10 @@
import logging
from common import dates, endoflife, http, releasedata
from common import dates, http, releasedata
"""Fetches RedHat JBoss EAP version data for JBoss 7"""
for config in endoflife.list_configs_from_argv():
for config in releasedata.list_configs_from_argv():
with releasedata.ProductData(config.product) as product_data:
html = http.fetch_html(config.url)

View File

@@ -1,10 +1,10 @@
import re
from common import dates, endoflife, http, releasedata
from common import dates, http, releasedata
"""Fetches the latest RedHat JBoss EAP version data for JBoss 8.0"""
for config in endoflife.list_configs_from_argv():
for config in releasedata.list_configs_from_argv():
with releasedata.ProductData(config.product) as product_data:
xml = http.fetch_xml(config.url)

View File

@@ -1,6 +1,6 @@
import re
from common import dates, endoflife, releasedata
from common import dates, releasedata
from common.git import Git
"""Fetches Red Hat OpenShift versions from the documentation's git repository"""
@@ -10,7 +10,7 @@ VERSION_AND_DATE_PATTERN = re.compile(
re.MULTILINE,
)
for config in endoflife.list_configs_from_argv():
for config in releasedata.list_configs_from_argv():
with releasedata.ProductData(config.product) as product_data:
git = Git(config.url)
git.setup()

View File

@@ -1,12 +1,12 @@
import logging
from common import dates, endoflife, http, releasedata
from common import dates, http, releasedata
"""Fetches Satellite versions from access.redhat.com.
A few of the older versions, such as 'Satellite 6.1 GA Release (Build 6.1.1)', were ignored because too hard to parse."""
for config in endoflife.list_configs_from_argv():
for config in releasedata.list_configs_from_argv():
with releasedata.ProductData(config.product) as product_data:
html = http.fetch_html(config.url)

View File

@@ -1,7 +1,7 @@
import logging
import urllib.parse
from common import dates, endoflife, http, releasedata
from common import dates, http, releasedata
"""Fetches EOL dates from the Red Hat Product Life Cycle Data API.
@@ -17,7 +17,7 @@ class Mapping:
def get_field_for(self, phase_name: str) -> str | None:
return self.fields_by_phase.get(phase_name.lower(), None)
for config in endoflife.list_configs_from_argv():
for config in releasedata.list_configs_from_argv():
with releasedata.ProductData(config.product) as product_data:
name = urllib.parse.quote(config.url)
mapping = Mapping(config.data["fields"])

View File

@@ -150,7 +150,7 @@ class Field:
return f"{self.name}({self.column})"
for config in endoflife.list_configs_from_argv():
for config in releasedata.list_configs_from_argv():
with releasedata.ProductData(config.product) as product_data:
render_javascript = config.data.get("render_javascript", False)
render_javascript_click_selector = config.data.get("render_javascript_click_selector", None)

View File

@@ -1,11 +1,11 @@
import re
from common import dates, endoflife, http, releasedata
from common import dates, http, releasedata
# https://regex101.com/r/877ibq/1
VERSION_PATTERN = re.compile(r"RHEL (?P<major>\d)(\. ?(?P<minor>\d+))?(( Update (?P<minor2>\d))| GA)?")
for config in endoflife.list_configs_from_argv():
for config in releasedata.list_configs_from_argv():
with releasedata.ProductData(config.product) as product_data:
html = http.fetch_html(config.url)

View File

@@ -1,6 +1,6 @@
from common import dates, endoflife, http, releasedata
from common import dates, http, releasedata
for config in endoflife.list_configs_from_argv():
for config in releasedata.list_configs_from_argv():
with releasedata.ProductData(config.product) as product_data:
response = http.fetch_url(config.url)
for line in response.text.strip().split('\n'):

View File

@@ -1,8 +1,8 @@
import logging
from common import dates, endoflife, http, releasedata
from common import dates, http, releasedata
for config in endoflife.list_configs_from_argv():
for config in releasedata.list_configs_from_argv():
with releasedata.ProductData(config.product) as product_data:
html = http.fetch_html(config.url)

View File

@@ -12,9 +12,9 @@ it retains the date and use it as the model's EOL date.
TODAY = dates.today()
for config in endoflife.list_configs_from_argv():
frontmatter, configs = releasedata.parse_argv()
for config in configs:
with releasedata.ProductData(config.product) as product_data:
frontmatter = endoflife.ProductFrontmatter(product_data.name)
frontmatter_release_names = frontmatter.get_release_names()
# Copy EOL dates from frontmatter to product data

View File

@@ -1,8 +1,8 @@
import logging
from common import dates, endoflife, http, releasedata
from common import dates, http, releasedata
for config in endoflife.list_configs_from_argv():
for config in releasedata.list_configs_from_argv():
with releasedata.ProductData(config.product) as product_data:
html = http.fetch_html(config.url)

View File

@@ -1,6 +1,6 @@
import re
from common import dates, endoflife, http, releasedata
from common import dates, http, releasedata
VERSION_DATE_PATTERN = re.compile(r"Splunk Enterprise (?P<version>\d+\.\d+(?:\.\d+)*) was (?:first )?released on (?P<date>\w+\s\d\d?,\s\d{4})\.", re.MULTILINE)
@@ -29,7 +29,7 @@ def get_latest_minor_versions(versions: list[str]) -> list[str]:
return latest_versions
for config in endoflife.list_configs_from_argv():
for config in releasedata.list_configs_from_argv():
with releasedata.ProductData(config.product) as product_data:
html = http.fetch_html(config.url)

View File

@@ -1,6 +1,6 @@
from common import dates, endoflife, http, releasedata
from common import dates, http, releasedata
for config in endoflife.list_configs_from_argv():
for config in releasedata.list_configs_from_argv():
with releasedata.ProductData(config.product) as product_data:
data = http.fetch_json(config.url)
for v in data:

View File

@@ -1,4 +1,4 @@
from common import dates, endoflife, http, releasedata
from common import dates, http, releasedata
"""Fetches the Unity LTS releases from the Unity website. Non-LTS releases are not listed there, so this automation
is only partial.
@@ -16,7 +16,7 @@ Note that it was assumed that:
The script will need to be updated if someday those conditions are not met."""
for config in endoflife.list_configs_from_argv():
for config in releasedata.list_configs_from_argv():
with releasedata.ProductData(config.product) as product_data:
html = http.fetch_html(config.url)

View File

@@ -1,10 +1,10 @@
import re
from common import dates, endoflife, http, releasedata
from common import dates, http, releasedata
DATE_PATTERN = re.compile(r"\d{4}-\d{2}-\d{2}")
for config in endoflife.list_configs_from_argv():
for config in releasedata.list_configs_from_argv():
with releasedata.ProductData(config.product) as product_data:
wikicode = http.fetch_markdown(config.url)

View File

@@ -1,7 +1,7 @@
import logging
import re
from common import dates, endoflife, http, releasedata
from common import dates, http, releasedata
"""Fetches Veeam products versions from https://www.veeam.com.
@@ -9,7 +9,7 @@ This script takes a single argument which is the url of the versions page on htt
such as `https://www.veeam.com/kb2680`.
"""
for config in endoflife.list_configs_from_argv():
for config in releasedata.list_configs_from_argv():
with releasedata.ProductData(config.product) as product_data:
html = http.fetch_html(config.url)

View File

@@ -1,13 +1,13 @@
import logging
import re
from common import dates, endoflife, http, releasedata
from common import dates, http, releasedata
"""Fetches releases from VirtualBox download page."""
EOL_REGEX = re.compile(r"^\(no longer supported, support ended (?P<value>\d{4}/\d{2})\)$")
for config in endoflife.list_configs_from_argv():
for config in releasedata.list_configs_from_argv():
with releasedata.ProductData(config.product) as product_data:
html = http.fetch_html(config.url)

View File

@@ -1,6 +1,6 @@
from common import dates, endoflife, http, releasedata
from common import dates, http, releasedata
for config in endoflife.list_configs_from_argv():
for config in releasedata.list_configs_from_argv():
with releasedata.ProductData(config.product) as product_data:
html = http.fetch_html(config.url)

View File

@@ -1,3 +1,4 @@
import argparse
import json
import logging
import subprocess
@@ -9,9 +10,7 @@ from deepdiff import DeepDiff
from src.common.endoflife import AutoConfig, ProductFrontmatter, list_products
from src.common.gha import GitHubGroup, GitHubOutput, GitHubStepSummary
SRC_DIR = Path('src')
DATA_DIR = Path('releases')
from src.common.releasedata import DATA_DIR, SRC_DIR
class ScriptExecutionSummary:
@@ -66,7 +65,7 @@ def install_playwright() -> None:
def __delete_data(product: ProductFrontmatter) -> None:
release_data_path = DATA_DIR / f"{product.name}.json"
release_data_path = Path(__file__).resolve().parent / DATA_DIR / f"{product.name}.json"
if not release_data_path.exists() or product.is_auto_update_cumulative():
return
@@ -75,19 +74,23 @@ def __delete_data(product: ProductFrontmatter) -> None:
def __revert_data(product: ProductFrontmatter) -> None:
release_data_path = DATA_DIR / f"{product.name}.json"
release_data_path = Path(__file__).resolve().parent / DATA_DIR / f"{product.name}.json"
# check=False because the command fails if the file did not exist before
subprocess.run(f'git checkout HEAD -- {release_data_path}', timeout=10, check=False, shell=True)
logging.warning(f"reverted changes in {release_data_path}")
def __run_script(product: ProductFrontmatter, config: AutoConfig, summary: ScriptExecutionSummary) -> bool:
script = SRC_DIR / config.script
script = Path(__file__).resolve().parent / SRC_DIR / config.script
logging.info(f"start running {script} for {config}")
start = time.perf_counter()
# timeout is handled in child scripts
child = subprocess.run([sys.executable, script, config.product, str(config.method), str(config.url)])
script_args = [sys.executable, script, "-p", product.path, "-m", str(config.method), "-u", str(config.url)]
script_args = script_args + ["-v"] if logging.getLogger().isEnabledFor(logging.DEBUG) else script_args
child = subprocess.run(script_args)
success = child.returncode == 0
elapsed_seconds = time.perf_counter() - start
@@ -98,13 +101,10 @@ def __run_script(product: ProductFrontmatter, config: AutoConfig, summary: Scrip
return success
def run_scripts(summary: GitHubStepSummary, product_filter: str) -> bool:
def run_scripts(summary: GitHubStepSummary, products: list[ProductFrontmatter]) -> bool:
exec_summary = ScriptExecutionSummary()
with GitHubGroup("Load Product Data"):
product_list = list_products(product_filter)
for product in product_list:
for product in products:
if not product.has_auto_configs():
continue
@@ -166,14 +166,21 @@ def generate_commit_message(old_content: dict[Path, dict], new_content: dict[Pat
commit_message.println("")
summary.println("")
if __name__ == "__main__":
parser = argparse.ArgumentParser(description='Update product releases.')
parser.add_argument('product', nargs='?', help='restrict update to the given product')
parser.add_argument('-p', '--product-dir', required=True, help='path to the product directory')
parser.add_argument('-v', '--verbose', action='store_true', help='enable verbose logging')
args = parser.parse_args()
logging.basicConfig(format="%(message)s", level=logging.INFO)
p_filter = sys.argv[1] if len(sys.argv) > 1 else None
logging.basicConfig(format=logging.BASIC_FORMAT, level=(logging.DEBUG if args.verbose else logging.INFO))
install_playwright()
products_dir = Path(args.product_dir)
products_list = list_products(products_dir, args.product)
with GitHubStepSummary() as step_summary:
install_playwright()
some_script_failed = run_scripts(step_summary, p_filter)
some_script_failed = run_scripts(step_summary, products_list)
updated_products = get_updated_products()
step_summary.println("## Update summary\n")