diff --git a/.github/workflows/check_web_features_version.yml b/.github/workflows/check_web_features_version.yml new file mode 100644 index 0000000..6b82416 --- /dev/null +++ b/.github/workflows/check_web_features_version.yml @@ -0,0 +1,50 @@ +name: Check web-features version + +on: + workflow_dispatch: + + schedule: + - cron: '0 0 * * 0' # Run at 00:00 UTC every Sunday + +permissions: + contents: read + issues: write + +jobs: + check_web_features: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup python + uses: actions/setup-python@v5 + with: + python-version: "3.10" + + - name: Check for new version + run: | + python tools/check_web_features_version.py + + - name: Create issue for new version + uses: jayqi/failed-build-issue-action@v1 + if: failure() && github.event.pull_request == null + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + title-template: "New version of web-features available" + body-template: | + A new version of the `web-features` npm package has been published. + + Please review the [changelog](https://github.com/web-platform-dx/web-features/releases) and consider updating the package. + + To update: + 1. Review the changes in the new version + 2. Update the version in `package.json` + 3. Run `npm install` + 4. Test the changes + 5. Commit and push the updates + + --- + _This issue was automatically created by the [Check web-features version workflow](https://github.com/${{ github.repository }}/actions/workflows/check_web_features_version.yml)._ + + Please check the [logs](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}) for version details. diff --git a/.gitignore b/.gitignore index 9340436..955140e 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ _site Gemfile.lock node_modules +__pycache__ diff --git a/tools/check_web_features_version.py b/tools/check_web_features_version.py new file mode 100755 index 0000000..a21dab1 --- /dev/null +++ b/tools/check_web_features_version.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python3 + +''' +This script checks if a new major or minor version of the web-features npm package +has been published. It compares the current version in package.json with +the latest version from npm registry. + +The script detects new major or minor versions (e.g., 2.0.0 -> 3.0.0 or 2.0.0 -> 2.1.0), +but not patch versions (e.g., 2.1.0 -> 2.1.1). + +Exit codes: + 0 - No new major or minor version detected + 1 - New major or minor version detected + 2 - Error occurred +''' + +import urllib.request +import json +import sys +from version_utils import is_minor_or_major_version_change + +NPM_REGISTRY_URL = "https://registry.npmjs.org/web-features" +PACKAGE_JSON_FILE = "package.json" + +def get_latest_version(): + """Get the latest version of web-features from npm registry.""" + try: + with urllib.request.urlopen(NPM_REGISTRY_URL) as response: + data = json.load(response) + return data['dist-tags']['latest'] + except Exception as e: + print(f"Error fetching latest version from npm: {e}") + sys.exit(2) + +def get_current_version(): + """Get the current version of web-features from package.json.""" + try: + with open(PACKAGE_JSON_FILE, 'r') as file: + data = json.load(file) + if 'dependencies' in data and 'web-features' in data['dependencies']: + return data['dependencies']['web-features'] + else: + print("Error: web-features not found in package.json dependencies") + sys.exit(2) + except Exception as e: + print(f"Error reading package.json: {e}") + sys.exit(2) + +def main(): + current_version = get_current_version() + latest_version = get_latest_version() + + print(f"Current version: {current_version}") + print(f"Latest version: {latest_version}") + print(f"LATEST_VERSION={latest_version}") + + if is_minor_or_major_version_change(current_version, latest_version): + print(f"New major or minor version detected: {latest_version}") + sys.exit(1) + else: + print("No new major or minor version detected") + sys.exit(0) + +if __name__ == "__main__": + main() diff --git a/tools/update_bcd_version.py b/tools/update_bcd_version.py index 8c10ad0..dc34ffc 100755 --- a/tools/update_bcd_version.py +++ b/tools/update_bcd_version.py @@ -8,7 +8,9 @@ periodically update the version of this repository. ''' -import urllib.request, json +import urllib.request +import json +from version_utils import is_same_major_version CDN_URL = "https://unpkg.com/@mdn/browser-compat-data/data.json" VERSION_FILE = "bcd_version" @@ -17,20 +19,14 @@ # auto update the version if it is not a major version increase. # This will help us avoid accidentally breaking the site # without any human intervention. -def validateNotMajor(prevVersion, nextVersion): - prevVersion = prevVersion.split(".") - nextVersion = nextVersion.split(".") - - return (len(prevVersion) == 3 and - len(nextVersion) == 3 and - prevVersion[0] == nextVersion[0]) with urllib.request.urlopen(CDN_URL) as raw: data = json.load(raw) version = data["__meta"]["version"] with open(VERSION_FILE, "r+") as file: - if (validateNotMajor(file.read(), version)): + current_version = file.read() + if is_same_major_version(current_version, version): file.seek(0) file.write(version) file.truncate() diff --git a/tools/version_utils.py b/tools/version_utils.py new file mode 100644 index 0000000..825bbbe --- /dev/null +++ b/tools/version_utils.py @@ -0,0 +1,95 @@ +#!/usr/bin/env python3 + +''' +Shared utility functions for version parsing and comparison. +Used by both check_web_features_version.py and update_bcd_version.py. +''' + +import re + +def parse_version(version_string): + """ + Parse a semantic version string into major, minor, patch components. + + Args: + version_string: Version string (e.g., "2.1.0", "^2.0.0", "v1.2.3", "2.1.0-alpha.1") + + Returns: + Dict with 'major', 'minor', 'patch' keys, or None if invalid + """ + # Remove any leading 'v' or '^' or '~' characters + version_string = version_string.lstrip('v^~') + + # Handle pre-release versions by splitting on '-' or '+' + base_version = version_string.split('-')[0].split('+')[0] + + match = re.match(r'^(\d+)\.(\d+)\.(\d+)', base_version) + if not match: + return None + + return { + 'major': int(match.group(1)), + 'minor': int(match.group(2)), + 'patch': int(match.group(3)) + } + +def is_major_version_change(current_version, latest_version): + """ + Check if there's a major version change between two versions. + + Args: + current_version: Current version string + latest_version: Latest version string + + Returns: + True if major version changed, False otherwise + """ + current = parse_version(current_version) + latest = parse_version(latest_version) + + if not current or not latest: + return False + + return current['major'] != latest['major'] + +def is_minor_or_major_version_change(current_version, latest_version): + """ + Check if there's a major or minor version change between two versions. + Ignores patch-only updates. + + Args: + current_version: Current version string + latest_version: Latest version string + + Returns: + True if major or minor version increased, False otherwise + """ + current = parse_version(current_version) + latest = parse_version(latest_version) + + if not current or not latest: + return False + + # Major version increase OR same major but minor version increase + return (latest['major'] > current['major'] or + (current['major'] == latest['major'] and + latest['minor'] > current['minor'])) + +def is_same_major_version(current_version, latest_version): + """ + Check if two versions have the same major version. + + Args: + current_version: Current version string + latest_version: Latest version string + + Returns: + True if same major version, False otherwise + """ + current = parse_version(current_version) + latest = parse_version(latest_version) + + if not current or not latest: + return False + + return current['major'] == latest['major']