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.
- {chaindiff-0.3.0/src/chaindiff.egg-info → chaindiff-0.3.1}/PKG-INFO +2 -2
- {chaindiff-0.3.0 → chaindiff-0.3.1}/README.md +1 -1
- {chaindiff-0.3.0 → chaindiff-0.3.1}/pyproject.toml +1 -1
- {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/__init__.py +1 -1
- {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/releases/avalanchego.json +1 -1
- {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/releases/besu.json +1 -1
- {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/releases/bor.json +1 -1
- {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/releases/bsc.json +1 -1
- {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/releases/erigon.json +1 -1
- {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/releases/external-node.json +1 -1
- {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/releases/geth.json +8 -1
- {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/releases/heimdall.json +8 -1
- {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/releases/juno.json +1 -1
- {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/releases/l2geth.json +1 -1
- {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/releases/lighthouse.json +1 -1
- {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/releases/linea-besu.json +1 -1
- {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/releases/maru.json +1 -1
- {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/releases/nethermind.json +1 -1
- {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/releases/nimbus.json +8 -1
- {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/releases/nitro.json +1 -1
- {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/releases/op-geth.json +1 -1
- {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/releases/op-node.json +9 -2
- {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/releases/op-reth.json +9 -2
- {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/releases/pathfinder.json +8 -1
- {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/releases/prysm.json +8 -1
- {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/releases/reth.json +1 -1
- {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/releases/teku.json +8 -1
- chaindiff-0.3.1/src/chaindiff/versionbump.py +143 -0
- {chaindiff-0.3.0 → chaindiff-0.3.1/src/chaindiff.egg-info}/PKG-INFO +2 -2
- {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff.egg-info/SOURCES.txt +2 -0
- {chaindiff-0.3.0 → chaindiff-0.3.1}/tests/test_catalog.py +8 -8
- chaindiff-0.3.1/tests/test_versionbump.py +94 -0
- {chaindiff-0.3.0 → chaindiff-0.3.1}/LICENSE +0 -0
- {chaindiff-0.3.0 → chaindiff-0.3.1}/setup.cfg +0 -0
- {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/__main__.py +0 -0
- {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/catalog.py +0 -0
- {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/cli.py +0 -0
- {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/configparse.py +0 -0
- {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/advisories/avalanchego.json +0 -0
- {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/advisories/besu.json +0 -0
- {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/advisories/bor.json +0 -0
- {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/advisories/bsc.json +0 -0
- {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/advisories/erigon.json +0 -0
- {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/advisories/external-node.json +0 -0
- {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/advisories/geth.json +0 -0
- {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/advisories/heimdall.json +0 -0
- {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/advisories/juno.json +0 -0
- {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/advisories/l2geth.json +0 -0
- {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/advisories/lighthouse.json +0 -0
- {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/advisories/linea-besu.json +0 -0
- {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/advisories/maru.json +0 -0
- {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/advisories/nethermind.json +0 -0
- {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/advisories/nimbus.json +0 -0
- {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/advisories/nitro.json +0 -0
- {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/advisories/op-geth.json +0 -0
- {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/advisories/op-node.json +0 -0
- {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/advisories/op-reth.json +0 -0
- {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/advisories/pathfinder.json +0 -0
- {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/advisories/prysm.json +0 -0
- {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/advisories/reth.json +0 -0
- {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/advisories/teku.json +0 -0
- {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/clients.json +0 -0
- {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/flags/besu.json +0 -0
- {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/flags/erigon.json +0 -0
- {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/flags/geth.json +0 -0
- {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/flags/lighthouse.json +0 -0
- {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/flags/nethermind.json +0 -0
- {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/flags/nimbus.json +0 -0
- {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/flags/prysm.json +0 -0
- {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/flags/reth.json +0 -0
- {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/flags/teku.json +0 -0
- {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/networks/arbitrum-one.json +0 -0
- {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/networks/avalanche.json +0 -0
- {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/networks/base.json +0 -0
- {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/networks/bsc.json +0 -0
- {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/networks/ethereum.json +0 -0
- {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/networks/gnosis.json +0 -0
- {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/networks/linea.json +0 -0
- {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/networks/op-mainnet.json +0 -0
- {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/networks/polygon.json +0 -0
- {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/networks/scroll.json +0 -0
- {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/networks/starknet.json +0 -0
- {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/data/networks/zksync-era.json +0 -0
- {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/detect.py +0 -0
- {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/evaluate.py +0 -0
- {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/models.py +0 -0
- {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/network.py +0 -0
- {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/pair.py +0 -0
- {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/refresh.py +0 -0
- {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/report.py +0 -0
- {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/scan.py +0 -0
- {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff/versions.py +0 -0
- {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff.egg-info/dependency_links.txt +0 -0
- {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff.egg-info/entry_points.txt +0 -0
- {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff.egg-info/requires.txt +0 -0
- {chaindiff-0.3.0 → chaindiff-0.3.1}/src/chaindiff.egg-info/top_level.txt +0 -0
- {chaindiff-0.3.0 → chaindiff-0.3.1}/tests/test_advisories.py +0 -0
- {chaindiff-0.3.0 → chaindiff-0.3.1}/tests/test_cli.py +0 -0
- {chaindiff-0.3.0 → chaindiff-0.3.1}/tests/test_detect.py +0 -0
- {chaindiff-0.3.0 → chaindiff-0.3.1}/tests/test_evaluate.py +0 -0
- {chaindiff-0.3.0 → chaindiff-0.3.1}/tests/test_flags.py +0 -0
- {chaindiff-0.3.0 → chaindiff-0.3.1}/tests/test_network.py +0 -0
- {chaindiff-0.3.0 → chaindiff-0.3.1}/tests/test_pair.py +0 -0
- {chaindiff-0.3.0 → chaindiff-0.3.1}/tests/test_refresh.py +0 -0
- {chaindiff-0.3.0 → chaindiff-0.3.1}/tests/test_scan.py +0 -0
- {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.
|
|
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
|
|
|
@@ -2,9 +2,16 @@
|
|
|
2
2
|
"schema_version": 1,
|
|
3
3
|
"client": "geth",
|
|
4
4
|
"github": "ethereum/go-ethereum",
|
|
5
|
-
"fetched_at": "2026-10-
|
|
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-
|
|
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,9 +2,16 @@
|
|
|
2
2
|
"schema_version": 1,
|
|
3
3
|
"client": "nimbus",
|
|
4
4
|
"github": "status-im/nimbus-eth2",
|
|
5
|
-
"fetched_at": "2026-10-
|
|
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,9 +2,16 @@
|
|
|
2
2
|
"schema_version": 1,
|
|
3
3
|
"client": "op-node",
|
|
4
4
|
"github": "ethereum-optimism/optimism",
|
|
5
|
-
"fetched_at": "2026-10-
|
|
6
|
-
"ignored_tag_count":
|
|
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-
|
|
6
|
-
"ignored_tag_count":
|
|
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-
|
|
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-
|
|
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,9 +2,16 @@
|
|
|
2
2
|
"schema_version": 1,
|
|
3
3
|
"client": "teku",
|
|
4
4
|
"github": "Consensys-Incorporated/teku",
|
|
5
|
-
"fetched_at": "2026-10-
|
|
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.
|
|
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).
|
|
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).
|
|
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).
|
|
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).
|
|
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).
|
|
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).
|
|
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).
|
|
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).
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|