Skip to content

Commit 6cdce60

Browse files
committed
Add workflow for automated plugin version management
1 parent 213a5ec commit 6cdce60

File tree

5 files changed

+265
-29
lines changed

5 files changed

+265
-29
lines changed

.DS_Store

6 KB
Binary file not shown.
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
name: Add New Plugin Version
2+
3+
on:
4+
workflow_dispatch:
5+
inputs:
6+
identifier:
7+
description: 'Plugin identifier (e.g. com.github.username.plugin)'
8+
required: true
9+
type: string
10+
version:
11+
description: 'Version of the plugin (e.g. 1.0.0)'
12+
required: true
13+
type: string
14+
status:
15+
description: 'Status of the version (stable, testing, development, deprecated)'
16+
required: true
17+
type: choice
18+
options:
19+
- stable
20+
- testing
21+
- development
22+
- deprecated
23+
default: 'stable'
24+
kicad_version:
25+
description: 'Minimum KiCad version required (e.g. 8.0)'
26+
required: true
27+
type: string
28+
download_url:
29+
description: 'URL to download the plugin archive'
30+
required: true
31+
type: string
32+
33+
34+
permissions:
35+
contents: write
36+
37+
jobs:
38+
add-plugin-version:
39+
runs-on: ubuntu-latest
40+
steps:
41+
- name: Checkout repository
42+
uses: actions/checkout@v4
43+
44+
- name: Set up Python
45+
uses: actions/setup-python@v4
46+
with:
47+
python-version: '3.10'
48+
49+
- name: Install dependencies
50+
run: |
51+
python -m pip install --upgrade pip
52+
53+
- name: Process plugin version
54+
id: update_plugin
55+
run: |
56+
cd $GITHUB_WORKSPACE
57+
python scripts/add_plugin_version.py \
58+
"${{ github.event.inputs.identifier }}" \
59+
"${{ github.event.inputs.version }}" \
60+
"${{ github.event.inputs.status }}" \
61+
"${{ github.event.inputs.kicad_version }}" \
62+
"${{ github.event.inputs.download_url }}"
63+
64+
- name: Check for changes
65+
id: git-check
66+
run: |
67+
git config --local user.email "action@github.com"
68+
git config --local user.name "GitHub Action"
69+
git add packages.json repository.json
70+
git diff --staged --quiet || echo "changes=true" >> $GITHUB_OUTPUT
71+
72+
- name: Commit changes
73+
if: steps.git-check.outputs.changes == 'true'
74+
run: |
75+
git commit -m "Add version ${{ github.event.inputs.version }} for ${{ github.event.inputs.identifier }}"
76+
git push

packages.json

Lines changed: 5 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22
"packages": [
33
{
44
"$schema": "https://go.kicad.org/pcm/schemas/v1",
5-
"name": "Sivakov KiCad Components",
6-
"description": "Custom component library with symbols, footprints, and 3D models",
5+
"name": "Sivakov KiCad Component Library",
6+
"description": "Custom component library with symbols, footprints 3D models and datasheets.",
77
"description_full": "A comprehensive library of electronic components including symbols, footprints, and 3D models. Organized by component categories and mounting types for easy navigation and use in KiCad projects.",
8-
"identifier": "com.github.sivakov512.kicad-components",
8+
"identifier": "com.github.sivakov512.kicad-library",
99
"type": "library",
1010
"author": {
1111
"name": "Nikita Sivakov",
@@ -16,29 +16,10 @@
1616
},
1717
"license": "MIT",
1818
"resources": {
19-
"homepage": "https://github.com/sivakov512/kicad-components"
19+
"homepage": "https://github.com/sivakov512/kicad-library"
2020
},
2121
"tags": ["datasheets", "footprints", "library", "models", "symbols"],
22-
"versions": [
23-
{
24-
"version": "1.0.1",
25-
"status": "stable",
26-
"kicad_version": "8.0",
27-
"download_url": "https://github.com/sivakov512/kicad-components/releases/download/1.0.1/1.0.1.zip",
28-
"download_sha256": "eda77a7e4e773730c0de79c7d5399d18cf5f3835ceac46bb94ed51bc69d922ad",
29-
"download_size": 831148,
30-
"install_size": 3273265
31-
},
32-
{
33-
"version": "1.0.0",
34-
"status": "stable",
35-
"kicad_version": "8.0",
36-
"download_url": "https://github.com/sivakov512/kicad-components/releases/download/1.0.0/1.0.0.zip",
37-
"download_sha256": "b0fcfdad4ca8b8b8b6f746141ef1c8841508f529120a78b19e9288bcb2167cf0",
38-
"download_size": 1626367,
39-
"install_size": 4359685
40-
}
41-
]
22+
"versions": []
4223
}
4324
]
4425
}

repository.json

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,13 @@
99
},
1010
"name": "Sivakov KiCad repository",
1111
"packages": {
12-
"update_time_utc": "2025-05-22 02:10:00",
13-
"update_timestamp": 1716953400,
12+
"update_time_utc": "2025-05-22 16:04:48",
13+
"update_timestamp": 1747929888,
1414
"url": "https://raw.githubusercontent.com/sivakov512/kicad-pcm-index/master/packages.json"
1515
},
1616
"resources": {
17-
"update_time_utc": "2025-05-22 02:10:00",
18-
"update_timestamp": 1716953400,
17+
"update_time_utc": "2025-05-22 16:04:48",
18+
"update_timestamp": 1747929888,
1919
"url": "https://raw.githubusercontent.com/sivakov512/kicad-pcm-index/master/resources.zip"
2020
}
21-
}
21+
}

scripts/add_plugin_version.py

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
#!/usr/bin/env python3
2+
"""KiCad PCM plugin version updater"""
3+
import argparse
4+
import json
5+
import logging
6+
import sys
7+
import tempfile
8+
import time
9+
import typing
10+
import urllib.error
11+
import urllib.request
12+
import zipfile
13+
from datetime import datetime, timezone
14+
from hashlib import sha256
15+
from pathlib import Path
16+
17+
logging.basicConfig(format='%(levelname)s: %(message)s', level=logging.INFO)
18+
log = logging.getLogger('pcm-updater')
19+
20+
def fatal(msg: str, exit_code: int = 1) -> typing.NoReturn:
21+
log.error(msg)
22+
sys.exit(exit_code)
23+
24+
25+
def parse_arguments() -> argparse.Namespace:
26+
parser = argparse.ArgumentParser(
27+
description="Add or update a plugin version in KiCad PCM index"
28+
)
29+
parser.add_argument("identifier", help="Plugin identifier (e.g. com.github.username.plugin)")
30+
parser.add_argument("version", help="Version of the plugin (e.g. 1.0.0)")
31+
parser.add_argument("status", choices=["stable", "testing", "development", "deprecated"],
32+
help="Status of the version (stable, testing, development, deprecated)")
33+
parser.add_argument("kicad_version", help="Minimum KiCad version required (e.g. 8.0)")
34+
parser.add_argument("download_url", help="URL to download the plugin archive")
35+
return parser.parse_args()
36+
37+
38+
def download_and_calculate_metrics(url: str) -> typing.Dict[str, typing.Any]:
39+
"""Calculate download_size, download_sha256, and install_size metrics for plugin"""
40+
log.info(f"Downloading plugin from {url}")
41+
42+
with tempfile.TemporaryDirectory() as temp_dir:
43+
temp_path = Path(temp_dir)
44+
archive_path = temp_path / "plugin.zip"
45+
46+
try:
47+
urllib.request.urlretrieve(url, archive_path)
48+
except Exception as e:
49+
fatal(f"Download error: {e}")
50+
51+
if not archive_path.exists() or archive_path.stat().st_size == 0:
52+
fatal("Downloaded file is empty or does not exist")
53+
54+
download_size = archive_path.stat().st_size
55+
56+
file_hash = sha256()
57+
with open(archive_path, "rb") as f:
58+
for chunk in iter(lambda: f.read(4096), b""):
59+
file_hash.update(chunk)
60+
download_sha256 = file_hash.hexdigest()
61+
62+
extract_dir = temp_path / "extracted"
63+
extract_dir.mkdir(exist_ok=True)
64+
65+
try:
66+
with zipfile.ZipFile(archive_path, 'r') as zip_ref:
67+
zip_ref.extractall(extract_dir)
68+
except Exception as e:
69+
fatal(f"Error extracting ZIP archive: {e}")
70+
71+
install_size = get_directory_size(extract_dir)
72+
log.info(f"Plugin metrics - Size: {download_size} bytes, SHA256: {download_sha256[:8]}...")
73+
74+
return {
75+
"download_size": download_size,
76+
"download_sha256": download_sha256,
77+
"install_size": install_size
78+
}
79+
80+
81+
def get_directory_size(directory: Path) -> int:
82+
return sum(f.stat().st_size for f in directory.glob('**/*') if f.is_file())
83+
84+
85+
def update_packages_json(args, metrics: typing.Dict[str, typing.Any]) -> None:
86+
"""Find plugin by identifier and add/update version information in packages.json"""
87+
log.info("Updating packages.json")
88+
packages_file = Path("packages.json")
89+
90+
if not packages_file.exists():
91+
fatal("packages.json not found")
92+
93+
try:
94+
with open(packages_file, "r") as f:
95+
packages_data = json.load(f)
96+
97+
if "packages" not in packages_data:
98+
fatal("Invalid packages.json structure - 'packages' key not found")
99+
100+
plugin_found = False
101+
for package in packages_data["packages"]:
102+
if package["identifier"] == args.identifier:
103+
plugin_found = True
104+
105+
new_version = {
106+
"version": args.version,
107+
"status": args.status,
108+
"kicad_version": args.kicad_version,
109+
"download_url": args.download_url,
110+
"download_sha256": metrics["download_sha256"],
111+
"download_size": metrics["download_size"],
112+
"install_size": metrics["install_size"]
113+
}
114+
115+
for i, version in enumerate(package.get("versions", [])):
116+
if version["version"] == args.version:
117+
package["versions"][i] = new_version
118+
log.info(f"Updated existing version {args.version}")
119+
break
120+
else:
121+
if "versions" not in package:
122+
package["versions"] = []
123+
package["versions"].append(new_version)
124+
log.info(f"Added new version {args.version}")
125+
break
126+
127+
if not plugin_found:
128+
fatal(f"Plugin '{args.identifier}' not found")
129+
130+
with open(packages_file, "w") as f:
131+
json.dump(packages_data, f, indent=2, ensure_ascii=False)
132+
except Exception as e:
133+
fatal(f"Error updating packages.json: {e}")
134+
135+
136+
def update_repository_json() -> None:
137+
"""Update UTC time and Unix timestamp for packages and resources in repository.json"""
138+
log.info("Updating repository.json timestamps")
139+
repo_file = Path("repository.json")
140+
141+
if not repo_file.exists():
142+
fatal("repository.json not found")
143+
144+
try:
145+
timestamp = int(time.time())
146+
time_utc = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S")
147+
148+
with open(repo_file, "r") as f:
149+
repo_data = json.load(f)
150+
151+
if "packages" not in repo_data or "resources" not in repo_data:
152+
fatal("Invalid repository.json structure - required sections missing")
153+
154+
for section in ["packages", "resources"]:
155+
repo_data[section]["update_time_utc"] = time_utc
156+
repo_data[section]["update_timestamp"] = timestamp
157+
158+
with open(repo_file, "w") as f:
159+
json.dump(repo_data, f, indent=2, ensure_ascii=False)
160+
except Exception as e:
161+
fatal(f"Error updating repository.json: {e}")
162+
163+
164+
def main() -> None:
165+
try:
166+
args = parse_arguments()
167+
metrics = download_and_calculate_metrics(args.download_url)
168+
update_packages_json(args, metrics)
169+
update_repository_json()
170+
log.info(f"Successfully updated plugin {args.identifier} to version {args.version}")
171+
except KeyboardInterrupt:
172+
log.warning("Operation cancelled")
173+
sys.exit(1)
174+
except Exception as e:
175+
fatal(f"Unexpected error: {e}")
176+
177+
178+
if __name__ == "__main__":
179+
main()

0 commit comments

Comments
 (0)