Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/pr-assistant.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: '3.12'
python-version: '3.14'

- name: Install Dependencies
run: |
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/pr-autofix.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: '3.12'
python-version: '3.14'

- name: Install & Run Python Fixes
run: |
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/pr-validation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: '3.12'
python-version: '3.14'

- name: Install Tools
run: |
Expand Down
2 changes: 1 addition & 1 deletion action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ runs:
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.11'
python-version: '3.14'

- name: Install Dependencies
shell: bash
Expand Down
89 changes: 64 additions & 25 deletions validate.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
#!/usr/bin/env python3
import os
import json
import yaml
import argparse
import json
import os
import sys
from typing import List, Dict, Any
from typing import Any, Dict, List

import yaml

# We use packaging.version for robust version comparison
try:
from packaging import version
except ImportError:
print("::warning:: 'packaging' library not found. Version comparison might be less robust.")
print(
"::warning:: 'packaging' library not found. Version comparison might be less robust."
)
version = None


def error(message: str, file: str = None, line: int = None, col: int = None):
"""Prints error in GitHub Actions format."""
location = ""
Expand All @@ -29,17 +33,23 @@ def error(message: str, file: str = None, line: int = None, col: int = None):
print(f"::error::{message}")
print(f"ERROR: {message}", file=sys.stderr)


def validate_addons_json(repo_path: str) -> Dict[str, Any]:
json_path = os.path.join(repo_path, "addons.json")
if not os.path.exists(json_path):
error("addons.json missing in repository root", file=json_path)
return None

try:
with open(json_path, 'r', encoding='utf-8') as f:
with open(json_path, "r", encoding="utf-8") as f:
data = json.load(f)
except json.JSONDecodeError as e:
error(f"Invalid JSON in addons.json: {e.msg}", file=json_path, line=e.lineno, col=e.colno)
error(
f"Invalid JSON in addons.json: {e.msg}",
file=json_path,
line=e.lineno,
col=e.colno,
)
return None

if "addons" not in data or not isinstance(data["addons"], list):
Expand All @@ -48,7 +58,10 @@ def validate_addons_json(repo_path: str) -> Dict[str, Any]:

return data

def validate_addon_directory(repo_path: str, addon_id: str, core_version: str = None) -> bool:

def validate_addon_directory(
repo_path: str, addon_id: str, core_version: str = None
) -> bool:
addon_path = os.path.join(repo_path, addon_id)
if not os.path.isdir(addon_path):
error(f"Directory not found for addon '{addon_id}'", file=addon_path)
Expand All @@ -62,10 +75,12 @@ def validate_addon_directory(repo_path: str, addon_id: str, core_version: str =

manifest = None
try:
with open(manifest_path, 'r', encoding='utf-8') as f:
with open(manifest_path, "r", encoding="utf-8") as f:
manifest = json.load(f)
except json.JSONDecodeError as e:
error(f"Invalid JSON in manifest.json for '{addon_id}': {e}", file=manifest_path)
error(
f"Invalid JSON in manifest.json for '{addon_id}': {e}", file=manifest_path
)
return False

# Required Fields Check
Expand All @@ -78,8 +93,11 @@ def validate_addon_directory(repo_path: str, addon_id: str, core_version: str =
# Domain Validation (Simple)
domain = manifest.get("domain")
if not domain.isidentifier():
error(f"Invalid domain '{domain}'. Must be a valid identifier (alphanumeric, underscore).", file=manifest_path)
return False
error(
f"Invalid domain '{domain}'. Must be a valid identifier (alphanumeric, underscore).",
file=manifest_path,
)
return False

# Check Implementations
implementations = manifest.get("implementations")
Expand All @@ -88,36 +106,53 @@ def validate_addon_directory(repo_path: str, addon_id: str, core_version: str =
# For this refactor, let's enforce 'implementations' OR 'integration.py' implicit
# But user asked for Polyglot. Let's warn if missing.
if os.path.exists(os.path.join(addon_path, "integration.py")):
# Implicit python support
pass
# Implicit python support
pass
else:
error(f"manifest.json in '{addon_id}' missing 'implementations' map, and no implicit 'integration.py' found.", file=manifest_path)
return False
error(
f"manifest.json in '{addon_id}' missing 'implementations' map, and no implicit 'integration.py' found.",
file=manifest_path,
)
return False

if implementations:
for lang, filename in implementations.items():
file_path = os.path.join(addon_path, filename)
if not os.path.exists(file_path):
error(f"Implementation file '{filename}' for '{lang}' not found in '{addon_id}'", file=file_path)
return False
error(
f"Implementation file '{filename}' for '{lang}' not found in '{addon_id}'",
file=file_path,
)
return False

# Check Core Version Compatibility
min_ver = manifest.get("min_core_version")
if min_ver and core_version and version:
try:
if version.parse(min_ver) > version.parse(core_version):
error(f"Addon '{addon_id}' requires Core >= {min_ver}, but current Core is {core_version}", file=manifest_path)
error(
f"Addon '{addon_id}' requires Core >= {min_ver}, but current Core is {core_version}",
file=manifest_path,
)
return False
except Exception as e:
error(f"Version comparison failed for '{addon_id}' (min: {min_ver}, core: {core_version}): {e}", file=manifest_path)
return False
error(
f"Version comparison failed for '{addon_id}' (min: {min_ver}, core: {core_version}): {e}",
file=manifest_path,
)
return False

return True


def main():
parser = argparse.ArgumentParser(description="Validate Addon Repository")
parser.add_argument("repo_path", help="Path to the repository root")
parser.add_argument("--core-version", help="Current version of faneX-ID Core to check compatibility against", default=None)
parser.add_argument(
"--core-version",
help="Current version of faneX-ID Core to check compatibility against",
default=None,
)
args = parser.parse_args()

repo_path = os.path.abspath(args.repo_path)
Expand All @@ -139,9 +174,12 @@ def main():
# Minimal check on addons.json entry format could persist, but directory check is more important
addon_id = addon.get("id")
if not addon_id:
error(f"Addon entry at index {i} missing 'id'", file=os.path.join(repo_path, "addons.json"))
success = False
continue
error(
f"Addon entry at index {i} missing 'id'",
file=os.path.join(repo_path, "addons.json"),
)
success = False
continue

print(f"Validating addon: {addon_id}...")

Expand All @@ -155,5 +193,6 @@ def main():
print("Validation SUCCESSFUL.")
sys.exit(0)


if __name__ == "__main__":
main()