{"id":35312339,"url":"https://github.com/willynilly/same-version","last_synced_at":"2026-04-14T17:31:41.306Z","repository":{"id":297909603,"uuid":"998270191","full_name":"willynilly/same-version","owner":"willynilly","description":"Automatically ensures your software version metadata is consistent across key project files.","archived":false,"fork":false,"pushed_at":"2025-06-15T00:12:53.000Z","size":123,"stargazers_count":0,"open_issues_count":7,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-09-05T10:43:24.099Z","etag":null,"topics":["citation","cli","codemeta","codemeta-json","developer-tools","devops","fair-data","fair-software","github-actions","metadata","open-science","package-json","pre-commit-hook","pyproject-toml","setup-py","software-versioning","versioning","zenodo-citation-metadata"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/willynilly.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":"CITATION.cff","codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-06-08T08:40:04.000Z","updated_at":"2025-06-15T00:12:08.000Z","dependencies_parsed_at":"2025-09-05T10:42:21.684Z","dependency_job_id":null,"html_url":"https://github.com/willynilly/same-version","commit_stats":null,"previous_names":["willynilly/check-software-version-for-citation","willynilly/version-consistency","willynilly/same-version"],"tags_count":10,"template":false,"template_full_name":null,"purl":"pkg:github/willynilly/same-version","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/willynilly%2Fsame-version","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/willynilly%2Fsame-version/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/willynilly%2Fsame-version/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/willynilly%2Fsame-version/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/willynilly","download_url":"https://codeload.github.com/willynilly/same-version/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/willynilly%2Fsame-version/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31808505,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-14T11:13:53.975Z","status":"ssl_error","status_checked_at":"2026-04-14T11:13:53.299Z","response_time":153,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["citation","cli","codemeta","codemeta-json","developer-tools","devops","fair-data","fair-software","github-actions","metadata","open-science","package-json","pre-commit-hook","pyproject-toml","setup-py","software-versioning","versioning","zenodo-citation-metadata"],"created_at":"2025-12-30T17:48:35.955Z","updated_at":"2026-04-14T17:31:41.299Z","avatar_url":"https://github.com/willynilly.png","language":"Python","readme":"# 🚀 same-version\n\nAutomatically ensures your software version metadata is consistent across key project files.\n\n---\n\n## 📦 What does it do?\n\n`same-version` checks that software version metadata is consistent across your project files. It can stop GitHub pull requests and local git commits/pushes of projects with inconsistent software version metadata.\n\nHelps ensure:\n\n✅ Reproducibility  \n✅ Correct citations  \n✅ Consistent packaging metadata  \n✅ Accurate DOIs (Zenodo)  \n✅ Cross-language version consistency (Python / JS / metadata)\n\nIt can be used in **three ways**, each of which have different capabilities:\n\n### 1️⃣ GitHub Action\n\n- Runs in GitHub Actions\n- Can block:\n  - Inconsistent **pull requests** (which helps prevent inconsistent **tags** and **GitHub releases** )\n- Can report (but only after the tag/release has been created):\n  - Incorrect **tags**\n  - Incorrect **GitHub releases**\n\n  _Note: GitHub Actions cannot block tags or releases after they have been created.  \nThis workflow runs after the tag or release exists and can report problems, but cannot prevent them from appearing in GitHub UI._\n\n\n### 2️⃣ Pre-commit hook\n\n- Runs automatically **before each commit and push** (if enabled)\n- Can block:\n  - Inconsistent **commits**\n  - Inconsistent **tags being pushed**\n\n### 3️⃣ Python CLI\n\n- You can run `same-version` manually from the command line\n- Useful for:\n  - Local checks before release\n  - CI checks outside of GitHub Actions\n  - Automated scripts\n\n---\n\n## 📋 Features\n\n✅ Ultra-conservative version normalization and strict equality comparison using [Verple](https://pypi.org/project/verple/)\n\n✅ Compare software version metadata from:\n- GitHub tag/release\n- `CITATION.cff`\n- `pyproject.toml` (Python)\n- `setup.py` (Python, via static analysis only — no execution)\n- `setup.cfg` (Python)\n- Python files with `__version__` assignment (static parsing only — must be assigned directly to a string literal)\n- `codemeta.json` (General)\n- `.zenodo.json` (General)\n- `package.json` (JS/TypeScript)\n- `composer.json` (PHP)\n- `Cargo.toml` (Rust)\n- `pom.xml` (Java)\n- `.nuspec` (.NET/C#/NuGet)\n- `DESCRIPTION` (R)\n- `ro-crate-metadata.json` (RO-Crate)\n\n\n✅ Cross-language support (e.g., Python, R, JS/TypeScript, Java, Rust, PHP, C#)\n\n✅ Cross-standard support for FAIR and Open Science metadata (e.g., CFF, CodeMeta, RO-Crate, Zenodo)\n\n✅ Lightweight, pure Python — no third-party services  \n\n✅ Easy to configure via GitHub Action inputs  \n\n✅ Suitable for reproducible research and software citation best practices\n\n✅ Blocks inconsistent GitHub pull requests (via GitHub Action) \n\n✅ Reports inconsistent GitHub releases/tags (via GitHub Action) \n\n✅ Blocks inconsistent commits and tags (via Pre-commit hook)\n\n✅ Modular and extendable for additional software version metadata (via Python CLI)\n\n---\n\n## 🔍 How are versions compared?\n\n`same-version` automatically normalizes all detected versions using [Verple](https://pypi.org/project/verple/), which provides a strict, ultra-conservative version model that fully supports PEP 440, SemVer, Calendar Versioning, and hybrid schemes across ecosystems.\n\n- Verple parses versions from many ecosystems (Python, JavaScript, Rust, Java, PHP, R, etc.).\n- Unlike many versioning libraries that attempt to relax equivalence rules, Verple enforces **strict equality**: two versions are only considered equal if every component of the version string matches exactly — including release, pre-release, post-release, dev-release, and local identifiers.\n- This ensures that version mismatches across files are caught even if the differences are subtle (e.g. `1.0.0` vs `1.0.0+build123` are considered different).\n- For ordering (less-than, greater-than comparisons), Verple allows comparisons only when local identifiers are identical. If local identifiers differ, ordering comparisons are rejected to avoid any ambiguity.\n\n---\n\n## 🔬 Why is Verple ultra-conservative?\n\nMany packaging standards (SemVer, PEP 440) have nuanced rules for equality and ordering:\n\n- SemVer ignores build metadata when determining equality or ordering.\n- PEP 440 may allow local identifiers to affect ordering but not equality.\n\nHowever, for the purpose of cross-file version consistency checking (the core goal of `same-version`), such leniency can mask subtle inconsistencies that may later cause confusion or deployment issues.\n\nBy using Verple's conservative model:\n\n✅ Any discrepancy between files will be surfaced explicitly.  \n✅ No silent equivalence is assumed across ecosystems.  \n✅ Comparison logic remains simple, transparent, and safe for reproducibility.\n\n---\n\n## 🔬 When is this behavior helpful?\n\nVerple's strict comparison is **ideal for version consistency checks**, where exact agreement across files is required:\n\n- ✅ Reproducible research outputs\n- ✅ FAIR/Open Science metadata harmonization\n- ✅ Software citation accuracy\n- ✅ Multi-language package version alignment (Python, JS, R, Rust, etc.)\n- ✅ CI/CD pipelines validating metadata consistency\n\n---\n\n## ⚠ When might this behavior be limiting?\n\nVerple’s ultra-conservative model may not be ideal for:\n\n- Dependency resolution (where lenient comparisons are often useful)\n- Compatibility checks (where you care about version ranges, not exact equality)\n- Package managers that intentionally ignore metadata differences\n\nFor those use cases, specialized dependency resolution libraries (e.g. `packaging`, `semver`, `dephell`) may be more appropriate.\n\n---\n\n## 🔍 What files does it check?\n\n| File             | Original Version Format | Normalization |\n|------------------|-------------------------|----------------|\n| `CITATION.cff`   | PEP 440 / free text     | Verple |\n| `pyproject.toml` | PEP 440                 | Verple |\n| `setup.cfg`       | PEP 440                 | Verple |\n| `setup.py`       | PEP 440                 | Verple |\n| `package.json`   | SemVer                  | Verple |\n| `codemeta.json`  | Free text               | Verple |\n| `.zenodo.json`   | Free text               | Verple |\n| `composer.json`  | PHP Composer (SemVer-like) | Verple |\n| `Cargo.toml`     | Rust Cargo (SemVer-like) | Verple |\n| `pom.xml`        | Maven (loosely SemVer)  | Verple |\n| `.nuspec`        | NuGet (SemVer-like)     | Verple |\n| `DESCRIPTION`    | Free text               | Verple |\n| `__version__` file | PEP 440 (usually)     | Verple |\n| `ro-crate-metadata.json` | Free text | Verple |\n\n\u003e ✅ All formats are normalized automatically to Verple before comparison.\n \n\n---\n\n## ⚙️ Inputs\n\n| CLI Parameter                 | GitHub Action Input                     | Description            | Required                 | Default           |\n|-------------------------------|-----------------------------------------|--------------------|---------------------|-------------------|\n| `--base-version`         | `base_version`                     | Base version from which to compare all other versions  | No | *(empty)*      |\n| `--check-github-event`         | `check_github_event`                     | Check GitHut events? (`true` or `false`)  | No | `false`      |\n| `--github-event-name`         | `github_event_name`                     | GitHub event name (`push` or `release` or `pull_request`) | No  | *(empty)*      |\n| `--github-event-ref`                | `github_event_ref`                            | GitHub ref (for `push` event)       | No           | *(empty)*      |\n| `--github-event-release-tag`        | `github_event_release_tag`                    | GitHub release tag name (for `release` event)  | No | *(empty)*      |\n| `--fail-for-missing-file`     | `fail_for_missing_file`                 | Fail for any checked file that is missing| No | `false`           |\n| `--check-citation-cff`        | `check_citation_cff`                    | Check `CITATION.cff`? (`true/false`)   | No  | `true`            |\n| `--citation-cff-path`         | `citation_cff_path`                     | Path to `CITATION.cff`             | No      | `CITATION.cff`    |\n| `--check-pyproject-toml`      | `check_pyproject_toml`                  | Check `pyproject.toml`? (`true/false`)  | No  | `true`            |\n| `--pyproject-toml-path`       | `pyproject_toml_path`                   | Path to `pyproject.toml`         | No        | `pyproject.toml`  |\n| `--check-codemeta-json`       | `check_codemeta_json`                   | Check `codemeta.json`? (`true/false`)  | No  | `true`            |\n| `--codemeta-json-path`        | `codemeta_json_path`                    | Path to `codemeta.json`             | No     | `codemeta.json`   |\n| `--check-zenodo-json`         | `check_zenodo_json`                     | Check `.zenodo.json`? (`true/false`)  | No   | `true`            |\n| `--zenodo-json-path`          | `zenodo_json_path`                      | Path to `.zenodo.json`              | No     | `.zenodo.json`    |\n| `--check-package-json`        | `check_package_json`                    | Check `package.json`? (`true/false`)  | No   | `true`            |\n| `--package-json-path`         | `package_json_path`                     | Path to `package.json`               | No    | `package.json`    |\n| `--check-setup-py`            | `check_setup_py`                        | Check `setup.py`? (`true/false`)     | No    | `true`            |\n| `--setup-py-path`             | `setup_py_path`                         | Path to `setup.py`                 | No      | `setup.py`        |\n| `--check-setup-cfg`            | `check_setup_cfg`                        | Check `setup.cfg`? (`true/false`)     | No    | `true`            |\n| `--setup-cfg-path`             | `setup_cfg_path`                         | Path to `setup.cfg`                 | No      | `setup.cfg`        |\n| `--check-py-version-assignment`            | `check_py_version_assignment`                        | Check Python file with `__version__` assignment? (`true/false`)     | No    | `false`            |\n| `--py-version-assignment-path`             | `py_version_assignment_path`                         | Path to Python file with `__version__` assignment                 | No      | *(empty)*        |\n| `--check-composer-json`        | `check_composer_json`                    | Check `composer.json`? (`true/false`)  | No   | `true`            |\n| `--composer-json-path`         | `composer_json_path`                     | Path to `composer.json`               | No    | `composer.json`    |\n| `--check-ro-crate-metadata-json`        | `check_ro_crate_metadata_json`                    | Check `ro-crate-metadata.json`? (`true/false`)  | No   | `false`            |\n| `--ro-crate-metadata-json-path`         | `ro_crate_metadata_json_path`                     | Path to `ro-crate-metadata.json`               | No    | `ro-crate-metadata.json`    |\n| `--ro-crate-metadata-json-id`         | `ro_crate_metadata_json_id`                     | @id of resource in `ro-crate-metadata.json`               | No    | *(empty)*    |\n| `--check-cargo-toml`      | `check_cargo_toml`                  | Check `Cargo.toml`? (`true/false`)  | No  | `true`            |\n| `--cargo-toml-path`       | `cargo_toml_path`                   | Path to `Cargo.toml`         | No        | `Cargo.toml`  |\n| `--check-r-description`      | `check_r_description`                  | Check R `DESCRIPTION` file? (`true/false`)  | No  | `true`            |\n| `--r-description-path`       | `r_description_path`                   | Path to R `DESCRIPTION` file         | No        | `DESCRIPTION`  |\n| `--check-pom-xml`      | `check_pom_xml`                  | Check `pom.xml`? (`true/false`)  | No  | `true`            |\n| `--pom-xml-path`       | `pom_xml_path`                   | Path to `pom.xml`         | No        | `pom.xml`  |\n| `--check-nuspec`      | `check_nu_spec`                  | Check `.nuspec`? (`true/false`)  | No  | `true`            |\n| `--nuspec-path`       | `nuspec_path`                   | Path to `.nuspec`         | No        | `.nuspec`  |\n\n\n---\n\n\n## 🎯 When does it run?\n\n### GitHub Action:\n\n- On **pull requests** (blocks inconsistent PRs)\n- On **push of version tags** (reports incorrect tags after tag creation)\n- On **published GitHub releases** (reports incorrect releases after release creation)\n- Manually (via `workflow_dispatch`)\n\n\n### Pre-commit hook:\n\n- **Before each commit** (`pre-commit` hook)\n- **Before pushing commits or tags** (`pre-push` hook)\n\n### CLI:\n\n- **Anytime**, on demand\n\n---\n\n## 🛠 How to use\n\n---\n\n### 1️⃣ Using in GitHub Actions\n\n#### To block inconsistent pull requests:\n\n```yaml\nname: Check version consistency on pull requests for Python project using pyproject.toml\n\non:\n  pull_request:\n\njobs:\n  check-version:\n    runs-on: ubuntu-latest\n\n    steps:\n\n      - uses: actions/checkout@v4\n\n      - name: Install Python\n        uses: actions/setup-python@v5\n        with:\n          python-version: \"\u003e=3.10\"\n\n      - name: Run same-version\n        uses: willynilly/same-version@v7.1.0\n        with:\n          fail_for_missing_file: false\n          check_github_event: true\n          github_event_name: ${{ github.event_name }}\n          github_event_ref: ${{ github.ref }}\n          github_event_release_tag: ${{ github.event.release.tag_name }}\n          check_pyproject_toml: true\n          check_citation_cff: true\n          check_codemeta_json: true\n          check_zenodo_json: true\n          check_setup_cfg: false\n          check_setup_py: false\n          check_r_description: false\n          check_cargo_toml: false\n          check_py_version_assignment: false\n          check_pom_xml: false\n          check_nuspec: false\n          check_composer_json: false\n          check_ro_crate_metadata_json: false\n```\n\n---\n\n#### To report (BUT NOT BLOCK) incorrect tags/releases:\n\n```yaml\nname: Check version consistency on tags/releases for Python project using pyproject.toml\n\non:\n  push:\n    tags:\n      - 'v*.*.*'\n  release:\n    types: [published]\n\njobs:\n  check-version:\n    runs-on: ubuntu-latest\n\n    steps:\n\n      - uses: actions/checkout@v4\n\n      - name: Install Python\n        uses: actions/setup-python@v5\n        with:\n          python-version: \"\u003e=3.10\"\n\n      - name: Run same-version\n        uses: willynilly/same-version@v7.1.0\n        with:\n          fail_for_missing_file: false\n          check_github_event: true\n          github_event_name: ${{ github.event_name }}\n          github_event_ref: ${{ github.ref }}\n          github_event_release_tag: ${{ github.event.release.tag_name }}\n          check_pyproject_toml: true\n          check_citation_cff: true\n          check_codemeta_json: true\n          check_zenodo_json: true\n          check_setup_cfg: false\n          check_setup_py: false\n          check_r_description: false\n          check_cargo_toml: false\n          check_py_version_assignment: false\n          check_pom_xml: false\n          check_nuspec: false\n          check_composer_json: false\n          check_ro_crate_metadata_json: false\n```\n\n---\n\n### 2️⃣ Using with pre-commit hooks\n\nYou can configure a pre-commit hook to block:\n\n✅ Commits with inconsistent version metadata (`pre-commit`)  \n✅ Tags with inconsistent version metadata (`pre-push`)\n\n#### Adding the hook:\n\nAdd to your `.pre-commit-config.yaml`:\n\n```yaml\nrepos:\n  - repo: https://github.com/willynilly/same-version\n    rev: v7.1.0  # Use latest tag\n    hooks:\n      - id: same-version\n        stages: [pre-commit, pre-push]\n\n```\n\n#### Installing the hooks:\n\n```bash\n# Install the pre-commit tool if you have not already installed it\npip install pre-commit\n```\n\n```bash\n# Install for both pre-commit and pre-push\npre-commit install -t pre-commit -t pre-push\n```\n\n#### Manually run the hook (optional):\n\n```bash\npre-commit run same-version --all-files\n```\n\n---\n\n### 3️⃣ Using the CLI manually\n\nAfter installing the package:\n\n```bash\npip install same-version  # or pip install .\n```\n\nRun the CLI:\n\n```bash\nsame-version\n```\n\nBy default, it will scan all files, but not GitHub events. You can specify additiona parameters (see the `action.yml` of this GitHub Action for a robust example).\n\nBy default, the tool will not fail if some of the files are missing. This inclusively checks as many file types as possible without additional configuration.\nHowever, you may want to be strict and fail if any files used during checking is missing.\nHere's an example of failing if any files are missing for a Python project that uses a CITATION.CFF file and pyproject.toml file, any of the other supported files that contain software version metatadata (e.g., codemeta.json, setup.py, package.json, etc.)  \n\n```bash\nsame-version --fail-for-missing-file \"true\" --check-package-json \"false\" --check-codemeta-json \"false\" --check-setup-py \"false\" --check-zenodo-json \"false\"\n```\n\nYou can integrate this into:\n\n✅ Local release scripts  \n✅ CI pipelines (non-GitHub)  \n✅ Manual checks\n\n---\n\n## 🤝 Contributing\n\nPull requests and contributions are welcome!\n\nTo set up your development environment:\n\n```bash\ngit clone https://github.com/willynilly/same-version.git\ncd same-version\npip install -e .[testing,dev]\npre-commit install -t pre-commit -t pre-push\npre-commit run --all-files\n```\n\n---\n\n## 🔍 Comparisons with similar tools\n\n| Tool / Action | Scope / Limitations |\n|---------------|--------------------|\n| [`check-version`](https://github.com/marketplace/actions/check-version) | Compares one or two files (e.g. `package.json` or `pyproject.toml`) against Git tag; no cross-file or multi-ecosystem checks |\n| [`validate-version-tag-action`](https://github.com/marketplace/actions/validate-version-tag-action) | Focused on NPM (`package.json` only); no support for Python, metadata standards, or multi-file consistency |\n| [`python-semantic-release`](https://python-semantic-release.readthedocs.io/) | Automated release tool (version bumping, changelogs); not designed for cross-file or cross-language version consistency |\n| `check-tag-version` (various community actions) | Typically limited to checking one file type; lacks support for multiple ecosystems and scientific metadata standards |\n\n✅ **`same-version` is currently the only GitHub Action that provides:**\n\n- Ultra-conservative version normalization using **Verple** (strict, cross-standard comparison)\n- Cross-ecosystem version consistency checks, including:\n    - Python (`pyproject.toml`, `setup.py`, `__version__`)\n    - JavaScript (`package.json`)\n    - Scientific metadata (`CITATION.cff`, `codemeta.json`, `.zenodo.json`, `ro-crate-metadata.json`)\n    - Other languages (`composer.json`, `Cargo.toml`, `pom.xml`, `.nuspec`, `DESCRIPTION`)\n- Strict equality across files using full version fields: release, pre-release, post-release, dev-release, and local identifiers\n- Lightweight, pure Python implementation — fully offline, no third-party services\n- Flexible use in GitHub Actions, pre-commit hooks, or standalone CLI\n\n---\n\n## 📜 License\n\nApache License 2.0 — free to use, fork, extend 🚀\n\n---\n\n## 🙏 Acknowledgements\n\nInspired by best practices for reproducible research and software citation!\n\n---\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwillynilly%2Fsame-version","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwillynilly%2Fsame-version","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwillynilly%2Fsame-version/lists"}