chaindiff 0.3.0__tar.gz → 0.3.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. {chaindiff-0.3.0/src/chaindiff.egg-info → chaindiff-0.3.1}/PKG-INFO +2 -2
  2. {chaindiff-0.3.0 → chaindiff-0.3.1}/README.md +1 -1
  3. {chaindiff-0.3.0 → chaindiff-0.3.1}/pyproject.toml +1 -1
  4. {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/__init__.py +1 -1
  5. {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/releases/avalanchego.json +1 -1
  6. {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/releases/besu.json +1 -1
  7. {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/releases/bor.json +1 -1
  8. {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/releases/bsc.json +1 -1
  9. {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/releases/erigon.json +1 -1
  10. {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/releases/external-node.json +1 -1
  11. {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/releases/geth.json +8 -1
  12. {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/releases/heimdall.json +8 -1
  13. {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/releases/juno.json +1 -1
  14. {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/releases/l2geth.json +1 -1
  15. {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/releases/lighthouse.json +1 -1
  16. {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/releases/linea-besu.json +1 -1
  17. {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/releases/maru.json +1 -1
  18. {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/releases/nethermind.json +1 -1
  19. {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/releases/nimbus.json +8 -1
  20. {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/releases/nitro.json +1 -1
  21. {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/releases/op-geth.json +1 -1
  22. {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/releases/op-node.json +9 -2
  23. {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/releases/op-reth.json +9 -2
  24. {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/releases/pathfinder.json +8 -1
  25. {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/releases/prysm.json +8 -1
  26. {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/releases/reth.json +1 -1
  27. {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/releases/teku.json +8 -1
  28. chaindiff-0.3.1/src/chaindiff/versionbump.py +143 -0
  29. {chaindiff-0.3.0 → chaindiff-0.3.1/src/chaindiff.egg-info}/PKG-INFO +2 -2
  30. {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff.egg-info/SOURCES.txt +2 -0
  31. {chaindiff-0.3.0 → chaindiff-0.3.1}/tests/test_catalog.py +8 -8
  32. chaindiff-0.3.1/tests/test_versionbump.py +94 -0
  33. {chaindiff-0.3.0 → chaindiff-0.3.1}/LICENSE +0 -0
  34. {chaindiff-0.3.0 → chaindiff-0.3.1}/setup.cfg +0 -0
  35. {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/__main__.py +0 -0
  36. {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/catalog.py +0 -0
  37. {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/cli.py +0 -0
  38. {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/configparse.py +0 -0
  39. {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/advisories/avalanchego.json +0 -0
  40. {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/advisories/besu.json +0 -0
  41. {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/advisories/bor.json +0 -0
  42. {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/advisories/bsc.json +0 -0
  43. {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/advisories/erigon.json +0 -0
  44. {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/advisories/external-node.json +0 -0
  45. {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/advisories/geth.json +0 -0
  46. {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/advisories/heimdall.json +0 -0
  47. {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/advisories/juno.json +0 -0
  48. {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/advisories/l2geth.json +0 -0
  49. {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/advisories/lighthouse.json +0 -0
  50. {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/advisories/linea-besu.json +0 -0
  51. {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/advisories/maru.json +0 -0
  52. {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/advisories/nethermind.json +0 -0
  53. {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/advisories/nimbus.json +0 -0
  54. {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/advisories/nitro.json +0 -0
  55. {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/advisories/op-geth.json +0 -0
  56. {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/advisories/op-node.json +0 -0
  57. {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/advisories/op-reth.json +0 -0
  58. {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/advisories/pathfinder.json +0 -0
  59. {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/advisories/prysm.json +0 -0
  60. {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/advisories/reth.json +0 -0
  61. {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/advisories/teku.json +0 -0
  62. {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/clients.json +0 -0
  63. {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/flags/besu.json +0 -0
  64. {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/flags/erigon.json +0 -0
  65. {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/flags/geth.json +0 -0
  66. {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/flags/lighthouse.json +0 -0
  67. {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/flags/nethermind.json +0 -0
  68. {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/flags/nimbus.json +0 -0
  69. {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/flags/prysm.json +0 -0
  70. {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/flags/reth.json +0 -0
  71. {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/flags/teku.json +0 -0
  72. {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/networks/arbitrum-one.json +0 -0
  73. {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/networks/avalanche.json +0 -0
  74. {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/networks/base.json +0 -0
  75. {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/networks/bsc.json +0 -0
  76. {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/networks/ethereum.json +0 -0
  77. {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/networks/gnosis.json +0 -0
  78. {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/networks/linea.json +0 -0
  79. {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/networks/op-mainnet.json +0 -0
  80. {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/networks/polygon.json +0 -0
  81. {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/networks/scroll.json +0 -0
  82. {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/networks/starknet.json +0 -0
  83. {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/networks/zksync-era.json +0 -0
  84. {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/detect.py +0 -0
  85. {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/evaluate.py +0 -0
  86. {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/models.py +0 -0
  87. {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/network.py +0 -0
  88. {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/pair.py +0 -0
  89. {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/refresh.py +0 -0
  90. {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/report.py +0 -0
  91. {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/scan.py +0 -0
  92. {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/versions.py +0 -0
  93. {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff.egg-info/dependency_links.txt +0 -0
  94. {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff.egg-info/entry_points.txt +0 -0
  95. {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff.egg-info/requires.txt +0 -0
  96. {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff.egg-info/top_level.txt +0 -0
  97. {chaindiff-0.3.0 → chaindiff-0.3.1}/tests/test_advisories.py +0 -0
  98. {chaindiff-0.3.0 → chaindiff-0.3.1}/tests/test_cli.py +0 -0
  99. {chaindiff-0.3.0 → chaindiff-0.3.1}/tests/test_detect.py +0 -0
  100. {chaindiff-0.3.0 → chaindiff-0.3.1}/tests/test_evaluate.py +0 -0
  101. {chaindiff-0.3.0 → chaindiff-0.3.1}/tests/test_flags.py +0 -0
  102. {chaindiff-0.3.0 → chaindiff-0.3.1}/tests/test_network.py +0 -0
  103. {chaindiff-0.3.0 → chaindiff-0.3.1}/tests/test_pair.py +0 -0
  104. {chaindiff-0.3.0 → chaindiff-0.3.1}/tests/test_refresh.py +0 -0
  105. {chaindiff-0.3.0 → chaindiff-0.3.1}/tests/test_scan.py +0 -0
  106. {chaindiff-0.3.0 → chaindiff-0.3.1}/tests/test_versions.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: chaindiff
3
- Version: 0.3.0
3
+ Version: 0.3.1
4
4
  Summary: Decide whether a node client upgrade is safe.
5
5
  Author: ehsanhajian
6
6
  License-Expression: MIT
@@ -262,7 +262,7 @@ A stale catalog cannot return exit 0 for `latest`. A setting with no rule is rep
262
262
 
263
263
  `check`, `plan`, and `scan` use the catalog shipped with the package. `refresh` is the only command that talks to GitHub. Set `GH_TOKEN` or `GITHUB_TOKEN` if you hit the unauthenticated rate limit.
264
264
 
265
- GitHub Actions refreshes the catalog twice a week, Monday and Thursday at 06:15 UTC, and merges the update when the release lists change. Run `chaindiff refresh` any time. `chaindiff refresh --client geth` limits the fetch to one client.
265
+ GitHub Actions refreshes the catalog twice a week, Monday and Thursday at 06:15 UTC, and merges the update when the release lists change. When a new stable tag appears, that run publishes the next patch release, such as 0.3.1, so a fresh `pip install` includes the tag. A prerelease does not publish a ChainDiff release. The new tag is not reviewed. Run `chaindiff refresh` any time. `chaindiff refresh --client geth` limits the fetch to one client.
266
266
 
267
267
  The stored catalog is tags, dates, titles, and URLs. Release-note bodies stay on GitHub. A release title that mentions a security fix is reported as a warning. That warning does not by itself make the upgrade safe or unsafe.
268
268
 
@@ -237,7 +237,7 @@ A stale catalog cannot return exit 0 for `latest`. A setting with no rule is rep
237
237
 
238
238
  `check`, `plan`, and `scan` use the catalog shipped with the package. `refresh` is the only command that talks to GitHub. Set `GH_TOKEN` or `GITHUB_TOKEN` if you hit the unauthenticated rate limit.
239
239
 
240
- GitHub Actions refreshes the catalog twice a week, Monday and Thursday at 06:15 UTC, and merges the update when the release lists change. Run `chaindiff refresh` any time. `chaindiff refresh --client geth` limits the fetch to one client.
240
+ GitHub Actions refreshes the catalog twice a week, Monday and Thursday at 06:15 UTC, and merges the update when the release lists change. When a new stable tag appears, that run publishes the next patch release, such as 0.3.1, so a fresh `pip install` includes the tag. A prerelease does not publish a ChainDiff release. The new tag is not reviewed. Run `chaindiff refresh` any time. `chaindiff refresh --client geth` limits the fetch to one client.
241
241
 
242
242
  The stored catalog is tags, dates, titles, and URLs. Release-note bodies stay on GitHub. A release title that mentions a security fix is reported as a warning. That warning does not by itself make the upgrade safe or unsafe.
243
243
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "chaindiff"
3
- version = "0.3.0"
3
+ version = "0.3.1"
4
4
  description = "Decide whether a node client upgrade is safe."
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -1,3 +1,3 @@
1
1
  """Decide whether a node client upgrade is safe."""
2
2
 
3
- __version__ = "0.3.0"
3
+ __version__ = "0.3.1"
@@ -2,7 +2,7 @@
2
2
  "schema_version": 1,
3
3
  "client": "avalanchego",
4
4
  "github": "ava-labs/avalanchego",
5
- "fetched_at": "2026-10-07T06:03:39Z",
5
+ "fetched_at": "2026-10-12T15:15:39Z",
6
6
  "ignored_tag_count": 1,
7
7
  "releases": [
8
8
  {
@@ -2,7 +2,7 @@
2
2
  "schema_version": 1,
3
3
  "client": "besu",
4
4
  "github": "besu-eth/besu",
5
- "fetched_at": "2026-10-02T12:57:35Z",
5
+ "fetched_at": "2026-10-12T15:15:39Z",
6
6
  "ignored_tag_count": 0,
7
7
  "releases": [
8
8
  {
@@ -2,7 +2,7 @@
2
2
  "schema_version": 1,
3
3
  "client": "bor",
4
4
  "github": "0xPolygon/bor",
5
- "fetched_at": "2026-10-06T07:37:26Z",
5
+ "fetched_at": "2026-10-12T15:15:39Z",
6
6
  "ignored_tag_count": 4,
7
7
  "releases": [
8
8
  {
@@ -2,7 +2,7 @@
2
2
  "schema_version": 1,
3
3
  "client": "bsc",
4
4
  "github": "bnb-chain/bsc",
5
- "fetched_at": "2026-10-07T05:56:42Z",
5
+ "fetched_at": "2026-10-12T15:15:39Z",
6
6
  "ignored_tag_count": 2,
7
7
  "releases": [
8
8
  {
@@ -2,7 +2,7 @@
2
2
  "schema_version": 1,
3
3
  "client": "erigon",
4
4
  "github": "erigontech/erigon",
5
- "fetched_at": "2026-10-02T12:59:57Z",
5
+ "fetched_at": "2026-10-12T15:15:39Z",
6
6
  "ignored_tag_count": 107,
7
7
  "releases": [
8
8
  {
@@ -2,7 +2,7 @@
2
2
  "schema_version": 1,
3
3
  "client": "external-node",
4
4
  "github": "matter-labs/zksync-era",
5
- "fetched_at": "2026-10-07T12:25:55Z",
5
+ "fetched_at": "2026-10-12T15:15:39Z",
6
6
  "ignored_tag_count": 83,
7
7
  "releases": [
8
8
  {
@@ -2,9 +2,16 @@
2
2
  "schema_version": 1,
3
3
  "client": "geth",
4
4
  "github": "ethereum/go-ethereum",
5
- "fetched_at": "2026-10-02T12:57:35Z",
5
+ "fetched_at": "2026-10-12T15:15:39Z",
6
6
  "ignored_tag_count": 0,
7
7
  "releases": [
8
+ {
9
+ "tag": "v1.17.8",
10
+ "name": "Protein Sludge Pack (v1.17.8)",
11
+ "published_at": "2026-10-08T16:35:36Z",
12
+ "prerelease": false,
13
+ "url": "https://github.com/ethereum/go-ethereum/releases/tag/v1.17.8"
14
+ },
8
15
  {
9
16
  "tag": "v1.17.7",
10
17
  "name": "Intact Neuro-Coil (v1.17.7)",
@@ -2,9 +2,16 @@
2
2
  "schema_version": 1,
3
3
  "client": "heimdall",
4
4
  "github": "0xPolygon/heimdall-v2",
5
- "fetched_at": "2026-10-06T07:37:39Z",
5
+ "fetched_at": "2026-10-12T15:15:39Z",
6
6
  "ignored_tag_count": 0,
7
7
  "releases": [
8
+ {
9
+ "tag": "v0.12.2-beta",
10
+ "name": "v0.12.2-beta",
11
+ "published_at": "2026-10-07T21:21:37Z",
12
+ "prerelease": true,
13
+ "url": "https://github.com/0xPolygon/heimdall-v2/releases/tag/v0.12.2-beta"
14
+ },
8
15
  {
9
16
  "tag": "v0.12.1",
10
17
  "name": "v0.12.1",
@@ -2,7 +2,7 @@
2
2
  "schema_version": 1,
3
3
  "client": "juno",
4
4
  "github": "NethermindEth/juno",
5
- "fetched_at": "2026-10-07T12:36:30Z",
5
+ "fetched_at": "2026-10-12T15:15:39Z",
6
6
  "ignored_tag_count": 0,
7
7
  "releases": [
8
8
  {
@@ -2,7 +2,7 @@
2
2
  "schema_version": 1,
3
3
  "client": "l2geth",
4
4
  "github": "scroll-tech/go-ethereum",
5
- "fetched_at": "2026-10-07T06:42:21Z",
5
+ "fetched_at": "2026-10-12T15:15:39Z",
6
6
  "ignored_tag_count": 0,
7
7
  "releases": [
8
8
  {
@@ -2,7 +2,7 @@
2
2
  "schema_version": 1,
3
3
  "client": "lighthouse",
4
4
  "github": "sigp/lighthouse",
5
- "fetched_at": "2026-10-02T12:57:35Z",
5
+ "fetched_at": "2026-10-12T15:15:39Z",
6
6
  "ignored_tag_count": 0,
7
7
  "releases": [
8
8
  {
@@ -2,7 +2,7 @@
2
2
  "schema_version": 1,
3
3
  "client": "linea-besu",
4
4
  "github": "LFDT-Lineth/lineth-monorepo",
5
- "fetched_at": "2026-10-07T06:27:39Z",
5
+ "fetched_at": "2026-10-12T15:15:39Z",
6
6
  "ignored_tag_count": 324,
7
7
  "releases": [
8
8
  {
@@ -2,7 +2,7 @@
2
2
  "schema_version": 1,
3
3
  "client": "maru",
4
4
  "github": "LFDT-Lineth/lineth-monorepo",
5
- "fetched_at": "2026-10-07T06:27:39Z",
5
+ "fetched_at": "2026-10-12T15:15:39Z",
6
6
  "ignored_tag_count": 330,
7
7
  "releases": [
8
8
  {
@@ -2,7 +2,7 @@
2
2
  "schema_version": 1,
3
3
  "client": "nethermind",
4
4
  "github": "NethermindEth/nethermind",
5
- "fetched_at": "2026-10-02T12:57:35Z",
5
+ "fetched_at": "2026-10-12T15:15:39Z",
6
6
  "ignored_tag_count": 39,
7
7
  "releases": [
8
8
  {
@@ -2,9 +2,16 @@
2
2
  "schema_version": 1,
3
3
  "client": "nimbus",
4
4
  "github": "status-im/nimbus-eth2",
5
- "fetched_at": "2026-10-02T12:57:35Z",
5
+ "fetched_at": "2026-10-12T15:15:39Z",
6
6
  "ignored_tag_count": 2,
7
7
  "releases": [
8
+ {
9
+ "tag": "v26.10.0",
10
+ "name": "v26.10.0",
11
+ "published_at": "2026-10-04T11:28:04Z",
12
+ "prerelease": false,
13
+ "url": "https://github.com/status-im/nimbus-eth2/releases/tag/v26.10.0"
14
+ },
8
15
  {
9
16
  "tag": "v26.9.1",
10
17
  "name": "v26.9.1",
@@ -2,7 +2,7 @@
2
2
  "schema_version": 1,
3
3
  "client": "nitro",
4
4
  "github": "OffchainLabs/nitro",
5
- "fetched_at": "2026-10-06T04:50:39Z",
5
+ "fetched_at": "2026-10-12T15:15:39Z",
6
6
  "ignored_tag_count": 53,
7
7
  "releases": [
8
8
  {
@@ -2,7 +2,7 @@
2
2
  "schema_version": 1,
3
3
  "client": "op-geth",
4
4
  "github": "ethereum-optimism/op-geth",
5
- "fetched_at": "2026-10-05T08:11:05Z",
5
+ "fetched_at": "2026-10-12T15:15:39Z",
6
6
  "ignored_tag_count": 0,
7
7
  "releases": [
8
8
  {
@@ -2,9 +2,16 @@
2
2
  "schema_version": 1,
3
3
  "client": "op-node",
4
4
  "github": "ethereum-optimism/optimism",
5
- "fetched_at": "2026-10-05T08:13:19Z",
6
- "ignored_tag_count": 2311,
5
+ "fetched_at": "2026-10-12T15:15:39Z",
6
+ "ignored_tag_count": 2317,
7
7
  "releases": [
8
+ {
9
+ "tag": "op-node/v1.19.9",
10
+ "name": "op-node v1.19.9",
11
+ "published_at": "2026-10-07T08:42:44Z",
12
+ "prerelease": false,
13
+ "url": "https://github.com/ethereum-optimism/optimism/releases/tag/op-node/v1.19.9"
14
+ },
8
15
  {
9
16
  "tag": "op-node/v1.19.8",
10
17
  "name": "op-node v1.19.8",
@@ -2,9 +2,16 @@
2
2
  "schema_version": 1,
3
3
  "client": "op-reth",
4
4
  "github": "ethereum-optimism/optimism",
5
- "fetched_at": "2026-10-05T08:13:19Z",
6
- "ignored_tag_count": 2355,
5
+ "fetched_at": "2026-10-12T15:15:39Z",
6
+ "ignored_tag_count": 2361,
7
7
  "releases": [
8
+ {
9
+ "tag": "op-reth/v2.6.0",
10
+ "name": "op-reth v2.6.0",
11
+ "published_at": "2026-10-07T08:42:36Z",
12
+ "prerelease": false,
13
+ "url": "https://github.com/ethereum-optimism/optimism/releases/tag/op-reth/v2.6.0"
14
+ },
8
15
  {
9
16
  "tag": "op-reth/v2.5.0",
10
17
  "name": "op-reth v2.5.0",
@@ -2,9 +2,16 @@
2
2
  "schema_version": 1,
3
3
  "client": "pathfinder",
4
4
  "github": "software-mansion/pathfinder",
5
- "fetched_at": "2026-10-07T12:36:30Z",
5
+ "fetched_at": "2026-10-12T15:15:39Z",
6
6
  "ignored_tag_count": 0,
7
7
  "releases": [
8
+ {
9
+ "tag": "v0.24.2",
10
+ "name": "v0.24.2",
11
+ "published_at": "2026-10-07T15:59:15Z",
12
+ "prerelease": false,
13
+ "url": "https://github.com/software-mansion/pathfinder/releases/tag/v0.24.2"
14
+ },
8
15
  {
9
16
  "tag": "v0.24.1",
10
17
  "name": "v0.24.1",
@@ -2,9 +2,16 @@
2
2
  "schema_version": 1,
3
3
  "client": "prysm",
4
4
  "github": "OffchainLabs/prysm",
5
- "fetched_at": "2026-10-02T12:57:35Z",
5
+ "fetched_at": "2026-10-12T15:15:39Z",
6
6
  "ignored_tag_count": 1,
7
7
  "releases": [
8
+ {
9
+ "tag": "v7.2.1",
10
+ "name": "v.7.2.1",
11
+ "published_at": "2026-10-05T21:18:08Z",
12
+ "prerelease": false,
13
+ "url": "https://github.com/OffchainLabs/prysm/releases/tag/v7.2.1"
14
+ },
8
15
  {
9
16
  "tag": "v7.2.0",
10
17
  "name": "v7.2.0",
@@ -2,7 +2,7 @@
2
2
  "schema_version": 1,
3
3
  "client": "reth",
4
4
  "github": "paradigmxyz/reth",
5
- "fetched_at": "2026-10-02T12:57:35Z",
5
+ "fetched_at": "2026-10-12T15:15:39Z",
6
6
  "ignored_tag_count": 0,
7
7
  "releases": [
8
8
  {
@@ -2,9 +2,16 @@
2
2
  "schema_version": 1,
3
3
  "client": "teku",
4
4
  "github": "Consensys-Incorporated/teku",
5
- "fetched_at": "2026-10-02T12:57:35Z",
5
+ "fetched_at": "2026-10-12T15:15:39Z",
6
6
  "ignored_tag_count": 2,
7
7
  "releases": [
8
+ {
9
+ "tag": "26.10.0",
10
+ "name": "26.10.0",
11
+ "published_at": "2026-10-08T22:05:15Z",
12
+ "prerelease": false,
13
+ "url": "https://github.com/Consensys-Incorporated/teku/releases/tag/26.10.0"
14
+ },
8
15
  {
9
16
  "tag": "26.9.1",
10
17
  "name": "26.9.1",
@@ -0,0 +1,143 @@
1
+ """Bump the patch version when the catalog gains a stable client tag."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import re
7
+ import sys
8
+ from pathlib import Path
9
+
10
+ _VERSION_LINE = re.compile(r'(?m)^version = "(\d+\.\d+\.\d+)"\s*$')
11
+ _INIT_LINE = re.compile(r'(?m)^__version__ = "(\d+\.\d+\.\d+)"\s*$')
12
+ _PATCH = re.compile(r"^(\d+)\.(\d+)\.(\d+)$")
13
+
14
+
15
+ def stable_tags(releases_dir: Path) -> dict[str, set[str]]:
16
+ found: dict[str, set[str]] = {}
17
+ if not releases_dir.is_dir():
18
+ return found
19
+ for path in sorted(releases_dir.glob("*.json")):
20
+ payload = json.loads(path.read_text())
21
+ tags = {
22
+ str(item.get("tag", ""))
23
+ for item in payload.get("releases", [])
24
+ if item.get("tag") and not item.get("prerelease")
25
+ }
26
+ found[path.stem] = tags
27
+ return found
28
+
29
+
30
+ def new_stable_tags(
31
+ before: dict[str, set[str]],
32
+ after: dict[str, set[str]],
33
+ ) -> list[tuple[str, str]]:
34
+ fresh: list[tuple[str, str]] = []
35
+ for client in sorted(after):
36
+ added = after[client] - before.get(client, set())
37
+ fresh.extend((client, tag) for tag in sorted(added))
38
+ return fresh
39
+
40
+
41
+ def bump_patch(version: str) -> str:
42
+ matched = _PATCH.fullmatch(version.strip())
43
+ if matched is None:
44
+ raise ValueError(f"{version} is not a major.minor.patch version")
45
+ major, minor, patch = matched.groups()
46
+ return f"{major}.{minor}.{int(patch) + 1}"
47
+
48
+
49
+ def _read_version(text: str, pattern: re.Pattern[str], path: Path) -> str:
50
+ matched = pattern.search(text)
51
+ if matched is None:
52
+ raise ValueError(f"{path} has no major.minor.patch version")
53
+ return matched.group(1)
54
+
55
+
56
+ def bump_project(root: Path) -> str:
57
+ pyproject_path = root / "pyproject.toml"
58
+ init_path = root / "src" / "chaindiff" / "__init__.py"
59
+ pyproject = pyproject_path.read_text()
60
+ init = init_path.read_text()
61
+ current = _read_version(pyproject, _VERSION_LINE, pyproject_path)
62
+ init_version = _read_version(init, _INIT_LINE, init_path)
63
+ if current != init_version:
64
+ raise ValueError(f"pyproject.toml is {current} and __init__.py is {init_version}")
65
+ updated = bump_patch(current)
66
+ pyproject_path.write_text(_VERSION_LINE.sub(f'version = "{updated}"', pyproject, count=1))
67
+ init_path.write_text(_INIT_LINE.sub(f'__version__ = "{updated}"', init, count=1))
68
+ return updated
69
+
70
+
71
+ def release_notes(tags: list[tuple[str, str]], version: str) -> str:
72
+ lines = [
73
+ f"ChainDiff {version} includes new stable client tags.",
74
+ "",
75
+ "New stable releases:",
76
+ ]
77
+ lines.extend(f"- {client} {tag}" for client, tag in tags)
78
+ lines.extend(
79
+ [
80
+ "",
81
+ "These tags are in the catalog. They are not reviewed.",
82
+ "A check that targets one of them stays review required until an advisory is added.",
83
+ "",
84
+ ]
85
+ )
86
+ return "\n".join(lines)
87
+
88
+
89
+ def _releases_dir(root: Path) -> Path:
90
+ return root / "src" / "chaindiff" / "data" / "releases"
91
+
92
+
93
+ def snapshot(root: Path, destination: Path) -> None:
94
+ payload = {client: sorted(tags) for client, tags in stable_tags(_releases_dir(root)).items()}
95
+ destination.write_text(json.dumps(payload))
96
+
97
+
98
+ def _load_snapshot(path: Path) -> dict[str, set[str]]:
99
+ payload = json.loads(path.read_text())
100
+ return {str(client): set(tags) for client, tags in payload.items()}
101
+
102
+
103
+ def bump_if_new(root: Path, before_path: Path, notes_path: Path) -> str | None:
104
+ fresh = new_stable_tags(_load_snapshot(before_path), stable_tags(_releases_dir(root)))
105
+ if not fresh:
106
+ return None
107
+ version = bump_project(root)
108
+ notes = release_notes(fresh, version)
109
+ notes_path.write_text(notes)
110
+ notes_path.with_name("new-version.txt").write_text(version + "\n")
111
+ commit_path = notes_path.with_name("new-stable-commit.txt")
112
+ commit_path.write_text(
113
+ "Refresh the catalog for new stable client releases.\n\n" + notes
114
+ )
115
+ return version
116
+
117
+
118
+ def main(argv: list[str] | None = None) -> int:
119
+ args = list(sys.argv[1:] if argv is None else argv)
120
+ if len(args) == 3 and args[0] == "snapshot":
121
+ snapshot(Path(args[1]), Path(args[2]))
122
+ return 0
123
+ if len(args) == 4 and args[0] == "bump-if-new":
124
+ try:
125
+ version = bump_if_new(Path(args[1]), Path(args[2]), Path(args[3]))
126
+ except ValueError as exc:
127
+ print(str(exc), file=sys.stderr)
128
+ return 1
129
+ if version is None:
130
+ print("No new stable client tag.")
131
+ else:
132
+ print(f"Patch version is {version}.")
133
+ return 0
134
+ print(
135
+ "usage: python -m chaindiff.versionbump snapshot ROOT DEST\n"
136
+ " python -m chaindiff.versionbump bump-if-new ROOT BEFORE NOTES",
137
+ file=sys.stderr,
138
+ )
139
+ return 2
140
+
141
+
142
+ if __name__ == "__main__":
143
+ raise SystemExit(main())
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: chaindiff
3
- Version: 0.3.0
3
+ Version: 0.3.1
4
4
  Summary: Decide whether a node client upgrade is safe.
5
5
  Author: ehsanhajian
6
6
  License-Expression: MIT
@@ -262,7 +262,7 @@ A stale catalog cannot return exit 0 for `latest`. A setting with no rule is rep
262
262
 
263
263
  `check`, `plan`, and `scan` use the catalog shipped with the package. `refresh` is the only command that talks to GitHub. Set `GH_TOKEN` or `GITHUB_TOKEN` if you hit the unauthenticated rate limit.
264
264
 
265
- GitHub Actions refreshes the catalog twice a week, Monday and Thursday at 06:15 UTC, and merges the update when the release lists change. Run `chaindiff refresh` any time. `chaindiff refresh --client geth` limits the fetch to one client.
265
+ GitHub Actions refreshes the catalog twice a week, Monday and Thursday at 06:15 UTC, and merges the update when the release lists change. When a new stable tag appears, that run publishes the next patch release, such as 0.3.1, so a fresh `pip install` includes the tag. A prerelease does not publish a ChainDiff release. The new tag is not reviewed. Run `chaindiff refresh` any time. `chaindiff refresh --client geth` limits the fetch to one client.
266
266
 
267
267
  The stored catalog is tags, dates, titles, and URLs. Release-note bodies stay on GitHub. A release title that mentions a security fix is reported as a warning. That warning does not by itself make the upgrade safe or unsafe.
268
268
 
@@ -14,6 +14,7 @@ src/chaindiff/pair.py
14
14
  src/chaindiff/refresh.py
15
15
  src/chaindiff/report.py
16
16
  src/chaindiff/scan.py
17
+ src/chaindiff/versionbump.py
17
18
  src/chaindiff/versions.py
18
19
  src/chaindiff.egg-info/PKG-INFO
19
20
  src/chaindiff.egg-info/SOURCES.txt
@@ -99,4 +100,5 @@ tests/test_network.py
99
100
  tests/test_pair.py
100
101
  tests/test_refresh.py
101
102
  tests/test_scan.py
103
+ tests/test_versionbump.py
102
104
  tests/test_versions.py
@@ -126,35 +126,35 @@ def test_shipped_catalog_drops_known_non_releases():
126
126
  _, bsc = load_releases("bsc")
127
127
  assert any(item.tag == "v1.7.8" and not item.prerelease for item in bsc)
128
128
  assert any(item.tag == "v1.8.0-alpha" and item.prerelease for item in bsc)
129
- assert latest_stable(bsc).tag == "v1.7.8"
129
+ assert latest_stable(bsc).version >= parse_version("1.7.8")
130
130
  _, avalanchego = load_releases("avalanchego")
131
131
  assert any(item.tag == "v1.15.1" and not item.prerelease for item in avalanchego)
132
132
  assert any(item.tag == "v1.15.0-fuji" and item.prerelease for item in avalanchego)
133
- assert latest_stable(avalanchego).tag == "v1.15.1"
133
+ assert latest_stable(avalanchego).version >= parse_version("1.15.1")
134
134
  _, linea_besu = load_releases("linea-besu")
135
135
  assert any(item.tag == "releases/linea-besu-package/v2.3.0" and not item.prerelease for item in linea_besu)
136
136
  assert any(item.tag == "releases/linea-besu-package/v2.1.1" and item.prerelease for item in linea_besu)
137
- assert latest_stable(linea_besu).tag == "releases/linea-besu-package/v2.3.0"
137
+ assert latest_stable(linea_besu).version >= parse_version("2.3.0")
138
138
  assert all(item.tag.startswith("releases/linea-besu-package/") for item in linea_besu)
139
139
  assert all(not item.tag.startswith("releases/maru/") for item in linea_besu)
140
140
  assert all(not item.tag.startswith("releases/coordinator/") for item in linea_besu)
141
141
  _, maru = load_releases("maru")
142
142
  assert any(item.tag == "releases/maru/v1.4.0" and not item.prerelease for item in maru)
143
143
  assert any(item.tag == "releases/maru/v1.3.0" and not item.prerelease for item in maru)
144
- assert latest_stable(maru).tag == "releases/maru/v1.4.0"
144
+ assert latest_stable(maru).version >= parse_version("1.4.0")
145
145
  assert all(item.tag.startswith("releases/maru/") for item in maru)
146
146
  assert all(not item.tag.startswith("releases/linea-besu-package/") for item in maru)
147
147
  assert all(not item.tag.startswith("releases/coordinator/") for item in maru)
148
148
  _, l2geth = load_releases("l2geth")
149
149
  assert any(item.tag == "scroll-v5.10.2" and not item.prerelease for item in l2geth)
150
150
  assert any(item.tag == "scroll-v5.8.52-fix" and item.prerelease for item in l2geth)
151
- assert latest_stable(l2geth).tag == "scroll-v5.10.2"
151
+ assert latest_stable(l2geth).version >= parse_version("5.10.2")
152
152
  assert all(item.tag.startswith("scroll-") for item in l2geth)
153
153
  assert all(not item.tag.startswith("v1.") for item in l2geth)
154
154
  _, external_node = load_releases("external-node")
155
155
  assert any(item.tag == "core-v31.5.0" and not item.prerelease for item in external_node)
156
156
  assert any(item.tag == "core-v29.4.0" and not item.prerelease for item in external_node)
157
- assert latest_stable(external_node).tag == "core-v31.5.0"
157
+ assert latest_stable(external_node).version >= parse_version("31.5.0")
158
158
  assert all(item.tag.startswith("core-") for item in external_node)
159
159
  assert all(not item.tag.startswith("prover-") for item in external_node)
160
160
  assert all(not item.tag.startswith("contract_verifier-") for item in external_node)
@@ -162,11 +162,11 @@ def test_shipped_catalog_drops_known_non_releases():
162
162
  _, pathfinder = load_releases("pathfinder")
163
163
  assert any(item.tag == "v0.24.0" and not item.prerelease for item in pathfinder)
164
164
  assert any(item.tag == "v0.22.8-beta.1" and item.prerelease for item in pathfinder)
165
- assert latest_stable(pathfinder).tag == "v0.24.1"
165
+ assert latest_stable(pathfinder).version >= parse_version("0.24.0")
166
166
  _, juno = load_releases("juno")
167
167
  assert any(item.tag == "v0.16.6" and not item.prerelease for item in juno)
168
168
  assert any(item.tag == "v0.16.6-rc.4" and item.prerelease for item in juno)
169
- assert latest_stable(juno).tag == "v0.16.8"
169
+ assert latest_stable(juno).version >= parse_version("0.16.6")
170
170
 
171
171
 
172
172
  def test_release_roundtrip(tmp_path, monkeypatch):
@@ -0,0 +1,94 @@
1
+ import json
2
+ from pathlib import Path
3
+
4
+ import pytest
5
+
6
+ from chaindiff.versionbump import (
7
+ bump_if_new,
8
+ bump_patch,
9
+ bump_project,
10
+ new_stable_tags,
11
+ snapshot,
12
+ stable_tags,
13
+ )
14
+
15
+
16
+ def _catalog(root: Path, releases: dict[str, list[dict]]) -> None:
17
+ directory = root / "src" / "chaindiff" / "data" / "releases"
18
+ directory.mkdir(parents=True, exist_ok=True)
19
+ for client, items in releases.items():
20
+ payload = {"schema_version": 1, "client": client, "releases": items}
21
+ (directory / f"{client}.json").write_text(json.dumps(payload))
22
+
23
+
24
+ def _project(root: Path, version: str = "0.3.0") -> None:
25
+ (root / "pyproject.toml").write_text(f'[project]\nversion = "{version}"\n')
26
+ init = root / "src" / "chaindiff" / "__init__.py"
27
+ init.parent.mkdir(parents=True, exist_ok=True)
28
+ init.write_text(f'__version__ = "{version}"\n')
29
+
30
+
31
+ def test_stable_tags_skip_prereleases(tmp_path):
32
+ _catalog(
33
+ tmp_path,
34
+ {
35
+ "geth": [
36
+ {"tag": "v1.17.8", "prerelease": False},
37
+ {"tag": "v1.17.9-rc.1", "prerelease": True},
38
+ ]
39
+ },
40
+ )
41
+ assert stable_tags(tmp_path / "src" / "chaindiff" / "data" / "releases") == {
42
+ "geth": {"v1.17.8"}
43
+ }
44
+
45
+
46
+ def test_new_stable_tags_ignore_an_unchanged_catalog():
47
+ before = {"geth": {"v1.17.7"}}
48
+ after = {"geth": {"v1.17.7", "v1.17.8"}, "lighthouse": {"v8.2.4"}}
49
+ assert new_stable_tags(before, after) == [("geth", "v1.17.8"), ("lighthouse", "v8.2.4")]
50
+ assert new_stable_tags(before, {"geth": {"v1.17.7"}}) == []
51
+
52
+
53
+ def test_bump_patch_increments_the_last_component():
54
+ assert bump_patch("0.3.0") == "0.3.1"
55
+ assert bump_patch("0.3.9") == "0.3.10"
56
+ with pytest.raises(ValueError):
57
+ bump_patch("0.3")
58
+
59
+
60
+ def test_bump_project_keeps_the_two_version_files_together(tmp_path):
61
+ _project(tmp_path, "0.3.1")
62
+ assert bump_project(tmp_path) == "0.3.2"
63
+ assert 'version = "0.3.2"' in (tmp_path / "pyproject.toml").read_text()
64
+ assert '__version__ = "0.3.2"' in (tmp_path / "src/chaindiff/__init__.py").read_text()
65
+
66
+
67
+ def test_bump_if_new_writes_notes_only_for_a_new_stable_tag(tmp_path):
68
+ _project(tmp_path)
69
+ _catalog(tmp_path, {"geth": [{"tag": "v1.17.7", "prerelease": False}]})
70
+ before = tmp_path / "before.json"
71
+ notes = tmp_path / "notes.txt"
72
+ snapshot(tmp_path, before)
73
+ assert bump_if_new(tmp_path, before, notes) is None
74
+ assert not notes.exists()
75
+ assert 'version = "0.3.0"' in (tmp_path / "pyproject.toml").read_text()
76
+
77
+ _catalog(
78
+ tmp_path,
79
+ {
80
+ "geth": [
81
+ {"tag": "v1.17.7", "prerelease": False},
82
+ {"tag": "v1.17.8", "prerelease": False},
83
+ {"tag": "v1.17.9-rc.1", "prerelease": True},
84
+ ],
85
+ "lighthouse": [{"tag": "v8.2.4", "prerelease": False}],
86
+ },
87
+ )
88
+ assert bump_if_new(tmp_path, before, notes) == "0.3.1"
89
+ text = notes.read_text()
90
+ assert "geth v1.17.8" in text
91
+ assert "lighthouse v8.2.4" in text
92
+ assert "v1.17.9-rc.1" not in text
93
+ assert "not reviewed" in text
94
+ assert (tmp_path / "new-version.txt").read_text().strip() == "0.3.1"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes