karcytics-sdk 2.0.0__py3-none-any.whl
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.
- karcytics_sdk/__init__.py +10 -0
- karcytics_sdk/cli/__init__.py +0 -0
- karcytics_sdk/cli/commands/__init__.py +0 -0
- karcytics_sdk/cli/commands/diagnostics.py +81 -0
- karcytics_sdk/cli/commands/migrate.py +112 -0
- karcytics_sdk/cli/commands/scaffold.py +450 -0
- karcytics_sdk/cli/commands/security.py +109 -0
- karcytics_sdk/cli/main.py +43 -0
- karcytics_sdk/contrib/__init__.py +38 -0
- karcytics_sdk/contrib/image_utils.py +682 -0
- karcytics_sdk/host/__init__.py +40 -0
- karcytics_sdk/host/ai.py +421 -0
- karcytics_sdk/host/core_services.py +160 -0
- karcytics_sdk/host/docs.py +45 -0
- karcytics_sdk/host/marketplace_cache.py +157 -0
- karcytics_sdk/host/sign_plugin.py +478 -0
- karcytics_sdk/host/trust_manager.py +487 -0
- karcytics_sdk/host/trust_overrides.py +113 -0
- karcytics_sdk/host/trust_path.py +71 -0
- karcytics_sdk/host/trust_storage.py +102 -0
- karcytics_sdk/interfaces/__init__.py +5 -0
- karcytics_sdk/interfaces/i_event_bus.py +15 -0
- karcytics_sdk/interfaces/i_logger.py +26 -0
- karcytics_sdk/interfaces/i_task_scheduler.py +17 -0
- karcytics_sdk/plugin/__init__.py +137 -0
- karcytics_sdk/plugin/analysis.py +207 -0
- karcytics_sdk/plugin/base.py +347 -0
- karcytics_sdk/plugin/components.py +671 -0
- karcytics_sdk/plugin/context.py +29 -0
- karcytics_sdk/plugin/daemon.py +693 -0
- karcytics_sdk/plugin/dialogs.py +259 -0
- karcytics_sdk/plugin/events.py +85 -0
- karcytics_sdk/plugin/interfaces.py +46 -0
- karcytics_sdk/plugin/io.py +189 -0
- karcytics_sdk/plugin/logging.py +53 -0
- karcytics_sdk/plugin/managed_task.py +48 -0
- karcytics_sdk/plugin/manifest.py +32 -0
- karcytics_sdk/plugin/manifest_parser.py +78 -0
- karcytics_sdk/plugin/preferences.py +37 -0
- karcytics_sdk/plugin/ribbon.py +81 -0
- karcytics_sdk/plugin/security_parser.py +78 -0
- karcytics_sdk/plugin/signals.py +45 -0
- karcytics_sdk/plugin/state.py +57 -0
- karcytics_sdk/plugin/theme_fallback.py +95 -0
- karcytics_sdk/plugin/validation.py +114 -0
- karcytics_sdk/plugin/wizard.py +443 -0
- karcytics_sdk/plugin/workflow.py +74 -0
- karcytics_sdk/py.typed +1 -0
- karcytics_sdk/sdk_cli.py +119 -0
- karcytics_sdk/testing/__init__.py +3 -0
- karcytics_sdk/testing/contract.py +72 -0
- karcytics_sdk-2.0.0.dist-info/METADATA +156 -0
- karcytics_sdk-2.0.0.dist-info/RECORD +56 -0
- karcytics_sdk-2.0.0.dist-info/WHEEL +5 -0
- karcytics_sdk-2.0.0.dist-info/entry_points.txt +2 -0
- karcytics_sdk-2.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"""Karcytics SDK ā Software Development Kit for Karcytics desktop plugins.
|
|
2
|
+
|
|
3
|
+
Provides two main namespaces:
|
|
4
|
+
- plugin: Clean API for plugin development with zero unnecessary dependencies.
|
|
5
|
+
- host: Host-facing APIs for the Core application and SDK signing CLI.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from . import host, interfaces, plugin
|
|
9
|
+
|
|
10
|
+
__all__ = ["plugin", "host", "interfaces"]
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
def setup_diagnostics_parser(subparsers):
|
|
2
|
+
# Command: sbom
|
|
3
|
+
sbom_parser = subparsers.add_parser("sbom", help="Generates and prints the SBOM in the specified format.")
|
|
4
|
+
sbom_parser.add_argument(
|
|
5
|
+
"--format", type=str, choices=["--json", "--markdown"], default="--markdown", help="Output format"
|
|
6
|
+
)
|
|
7
|
+
sbom_parser.set_defaults(func=generate_sbom)
|
|
8
|
+
|
|
9
|
+
# Command: evaluate
|
|
10
|
+
eval_parser = subparsers.add_parser("evaluate", help="Perform comprehensive CLI evaluation of a plugin.")
|
|
11
|
+
eval_parser.add_argument("plugin_dir", type=str, help="Path to the plugin directory.")
|
|
12
|
+
eval_parser.set_defaults(func=evaluate_plugin)
|
|
13
|
+
|
|
14
|
+
# Command: doctor
|
|
15
|
+
doctor_parser = subparsers.add_parser("doctor", help="Evaluate a plugin against new SDK architecture.")
|
|
16
|
+
doctor_parser.add_argument("plugin_dir", type=str, help="Path to the plugin directory.")
|
|
17
|
+
doctor_parser.set_defaults(func=doctor_plugin)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def generate_sbom(args) -> bool:
|
|
21
|
+
"""Generates and prints the SBOM in the specified format."""
|
|
22
|
+
try:
|
|
23
|
+
import importlib
|
|
24
|
+
|
|
25
|
+
karcytics_core_sbom = importlib.import_module("karcytics.core.sbom")
|
|
26
|
+
generator = karcytics_core_sbom.SBOMGenerator()
|
|
27
|
+
|
|
28
|
+
if args.format == "--json":
|
|
29
|
+
print(generator.to_json())
|
|
30
|
+
else:
|
|
31
|
+
print(generator.to_markdown())
|
|
32
|
+
return True
|
|
33
|
+
except ImportError:
|
|
34
|
+
print("ERROR: SBOM generation is only supported when running inside the Karcytics main application.")
|
|
35
|
+
return False
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def evaluate_plugin(args) -> bool:
|
|
39
|
+
"""Perform comprehensive CLI evaluation of a plugin."""
|
|
40
|
+
import logging
|
|
41
|
+
import sys
|
|
42
|
+
from pathlib import Path
|
|
43
|
+
|
|
44
|
+
from PyQt6.QtWidgets import QApplication
|
|
45
|
+
|
|
46
|
+
from karcytics_sdk.plugin.manifest_parser import ManifestParser
|
|
47
|
+
|
|
48
|
+
print("Running Plugin Diagnostics Evaluator (evaluate)...")
|
|
49
|
+
_app = QApplication.instance() or QApplication(sys.argv)
|
|
50
|
+
logging.basicConfig(level=logging.INFO)
|
|
51
|
+
|
|
52
|
+
try:
|
|
53
|
+
parser = ManifestParser()
|
|
54
|
+
manifest = parser.parse_file(str(Path(args.plugin_dir) / "pyproject.toml"))
|
|
55
|
+
print(f"Manifest parsed successfully: {manifest.get('id')} v{manifest.get('version')}")
|
|
56
|
+
|
|
57
|
+
dependencies = manifest.get("python_dependencies") or manifest.get("dependencies", {})
|
|
58
|
+
if dependencies:
|
|
59
|
+
print("Auditing Plugin Dependencies...")
|
|
60
|
+
all_pinned = True
|
|
61
|
+
for dep, version in dependencies.items():
|
|
62
|
+
if any(c in version for c in (">", "<", "*", "^", "~")):
|
|
63
|
+
print(f"WARNING: Dependency '{dep}' is not pinned. Recommend exact pinning.")
|
|
64
|
+
all_pinned = False
|
|
65
|
+
else:
|
|
66
|
+
print(f"Dependency '{dep}' is pinned to version '{version}'")
|
|
67
|
+
|
|
68
|
+
if all_pinned:
|
|
69
|
+
print("All declared dependencies are securely pinned.")
|
|
70
|
+
|
|
71
|
+
return True
|
|
72
|
+
except Exception as e:
|
|
73
|
+
print(f"Evaluation failed: {e}")
|
|
74
|
+
return False
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def doctor_plugin(args) -> bool:
|
|
78
|
+
"""Evaluate a plugin against new SDK architecture (plugin.toml, strict imports)."""
|
|
79
|
+
print(f"Running Plugin Conformance Evaluator (doctor) on {args.plugin_dir}...")
|
|
80
|
+
print("Note: Scaffolded for Phase 1. Full static/runtime checks to be implemented.")
|
|
81
|
+
return True
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def setup_migrate_parser(subparsers):
|
|
6
|
+
# Command: migrate
|
|
7
|
+
migrate_parser = subparsers.add_parser("migrate", help="Migrate a legacy plugin to the new architecture.")
|
|
8
|
+
migrate_parser.add_argument("plugin_dir", type=str, help="Path to the plugin directory.")
|
|
9
|
+
migrate_parser.set_defaults(func=migrate_plugin)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def migrate_plugin(args) -> bool: # noqa: C901
|
|
13
|
+
"""Migrate a legacy plugin to the new architecture (pyproject.toml, src/ layout)."""
|
|
14
|
+
print(f"Migrating legacy plugin at {args.plugin_dir} to new SDK architecture...")
|
|
15
|
+
|
|
16
|
+
p_dir = Path(args.plugin_dir)
|
|
17
|
+
manifest_path = p_dir / "manifest.json"
|
|
18
|
+
|
|
19
|
+
if not manifest_path.exists():
|
|
20
|
+
print("ERROR: manifest.json not found. This doesn't look like a valid legacy plugin.")
|
|
21
|
+
return False
|
|
22
|
+
|
|
23
|
+
with open(manifest_path, encoding="utf-8") as f:
|
|
24
|
+
manifest_data = json.load(f)
|
|
25
|
+
|
|
26
|
+
plugin_id = manifest_data.get("id", p_dir.name.lower().replace("-", "_").replace(" ", "_"))
|
|
27
|
+
|
|
28
|
+
# 1. Generate pyproject.toml
|
|
29
|
+
print("1. Creating pyproject.toml and deprecating manifest.json...")
|
|
30
|
+
|
|
31
|
+
toml_path = p_dir / "pyproject.toml"
|
|
32
|
+
|
|
33
|
+
# Extract dependencies
|
|
34
|
+
dependencies_str = ""
|
|
35
|
+
if "python_dependencies" in manifest_data:
|
|
36
|
+
for dep, ver in manifest_data["python_dependencies"].items():
|
|
37
|
+
dependencies_str += f' "{dep}{ver}",\n'
|
|
38
|
+
|
|
39
|
+
# Build authors block for project
|
|
40
|
+
project_authors = ""
|
|
41
|
+
if "authors" in manifest_data:
|
|
42
|
+
for author in manifest_data["authors"]:
|
|
43
|
+
name = author.get("name", "Unknown")
|
|
44
|
+
project_authors += f' {{ name = "{name}" }},\n'
|
|
45
|
+
|
|
46
|
+
# Build authors block for plugin
|
|
47
|
+
plugin_authors = ""
|
|
48
|
+
if "authors" in manifest_data:
|
|
49
|
+
for author in manifest_data["authors"]:
|
|
50
|
+
name = author.get("name", "Unknown")
|
|
51
|
+
role = author.get("role", "Developer")
|
|
52
|
+
perms_str = json.dumps(author.get("permissions", []))
|
|
53
|
+
plugin_authors += f' {{ name = "{name}", role = "{role}", permissions = {perms_str} }},\n'
|
|
54
|
+
|
|
55
|
+
clean_project_authors = project_authors.rstrip(",\\n")
|
|
56
|
+
clean_deps = dependencies_str.rstrip(",\\n")
|
|
57
|
+
clean_plugin_authors = plugin_authors.rstrip(",\\n")
|
|
58
|
+
reqs_json = json.dumps(manifest_data.get("requires", ["task_scheduler", "logger", "event_bus"]))
|
|
59
|
+
|
|
60
|
+
toml_content = f'''[project]
|
|
61
|
+
name = "{manifest_data.get("name", p_dir.name.title())}"
|
|
62
|
+
version = "{manifest_data.get("version", "1.0.0")}"
|
|
63
|
+
description = "{manifest_data.get("description", "")}"
|
|
64
|
+
readme = "README.md"
|
|
65
|
+
requires-python = ">=3.11"
|
|
66
|
+
authors = [
|
|
67
|
+
{clean_project_authors}
|
|
68
|
+
]
|
|
69
|
+
dependencies = [
|
|
70
|
+
{clean_deps}
|
|
71
|
+
]
|
|
72
|
+
|
|
73
|
+
[tool.biopro.plugin]
|
|
74
|
+
id = "{plugin_id}"
|
|
75
|
+
min_core_version = "{manifest_data.get("min_core_version", "1.4.9")}"
|
|
76
|
+
entry_point = "biopro_plugins.{plugin_id}:initialize"
|
|
77
|
+
requires = {reqs_json}
|
|
78
|
+
authors = [
|
|
79
|
+
{clean_plugin_authors}
|
|
80
|
+
]
|
|
81
|
+
'''
|
|
82
|
+
with open(toml_path, "w", encoding="utf-8") as f:
|
|
83
|
+
f.write(toml_content)
|
|
84
|
+
|
|
85
|
+
print(
|
|
86
|
+
" -> Renaming manifest.json to manifest.json.deprecated (DO NOT DELETE yet, the core still uses it internally!)"
|
|
87
|
+
)
|
|
88
|
+
manifest_path.rename(p_dir / "manifest.json.deprecated")
|
|
89
|
+
|
|
90
|
+
# 2. Setup src structure
|
|
91
|
+
print("2. Restructuring python files into src/...")
|
|
92
|
+
src_dir = p_dir / "src"
|
|
93
|
+
src_dir.mkdir(exist_ok=True)
|
|
94
|
+
|
|
95
|
+
plugin_pkg_dir = src_dir / "biopro_plugins" / plugin_id
|
|
96
|
+
plugin_pkg_dir.mkdir(parents=True, exist_ok=True)
|
|
97
|
+
|
|
98
|
+
# Move all .py files except setup.py to the new package directory
|
|
99
|
+
py_files_moved = 0
|
|
100
|
+
for file in p_dir.glob("*.py"):
|
|
101
|
+
if file.name not in {"setup.py", "conftest.py"}:
|
|
102
|
+
print(f" -> Moving {file.name} to {plugin_pkg_dir.relative_to(p_dir)}")
|
|
103
|
+
file.rename(plugin_pkg_dir / file.name)
|
|
104
|
+
py_files_moved += 1
|
|
105
|
+
|
|
106
|
+
if py_files_moved == 0:
|
|
107
|
+
# Create an empty __init__.py if no files were moved
|
|
108
|
+
(plugin_pkg_dir / "__init__.py").touch()
|
|
109
|
+
|
|
110
|
+
print("\nā
Migration complete! Please verify your imports in the new src/biopro_plugins/ folder.")
|
|
111
|
+
print("Run 'karcytics-sdk sign .' to sign the new structure.")
|
|
112
|
+
return True
|
|
@@ -0,0 +1,450 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def setup_scaffold_parser(subparsers):
|
|
5
|
+
# Command: create-manifest
|
|
6
|
+
manifest_parser = subparsers.add_parser("create-manifest", help="Bootstraps a fresh manifest.json for a plugin.")
|
|
7
|
+
manifest_parser.add_argument("plugin_dir", type=str, help="Path to the plugin directory.")
|
|
8
|
+
manifest_parser.add_argument("--id", type=str, help="Custom plugin ID (snake_case).")
|
|
9
|
+
manifest_parser.add_argument("--name", type=str, help="Custom plugin display name.")
|
|
10
|
+
manifest_parser.add_argument("--version", type=str, help="Custom plugin version.")
|
|
11
|
+
manifest_parser.add_argument("--desc", type=str, help="Custom plugin description.")
|
|
12
|
+
manifest_parser.set_defaults(func=create_manifest)
|
|
13
|
+
|
|
14
|
+
# Command: bootstrap
|
|
15
|
+
bootstrap_parser = subparsers.add_parser("bootstrap", help="Create a complete boilerplate plugin skeleton.")
|
|
16
|
+
bootstrap_parser.add_argument("plugin_dir", type=str, help="Path to the plugin directory.")
|
|
17
|
+
bootstrap_parser.set_defaults(func=bootstrap_plugin)
|
|
18
|
+
|
|
19
|
+
# Command: init
|
|
20
|
+
init_parser = subparsers.add_parser("init", help="Scaffold a fresh plugin repository.")
|
|
21
|
+
init_parser.add_argument("plugin_name", type=str, help="Name of the new plugin.")
|
|
22
|
+
init_parser.set_defaults(func=init_plugin)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def create_manifest(args) -> bool:
|
|
26
|
+
"""Interactive/Scriptable bootstrapping for a pyproject.toml config."""
|
|
27
|
+
p_dir = Path(args.plugin_dir)
|
|
28
|
+
p_dir.mkdir(parents=True, exist_ok=True)
|
|
29
|
+
toml_path = p_dir / "pyproject.toml"
|
|
30
|
+
|
|
31
|
+
if toml_path.exists():
|
|
32
|
+
print(f"ā ļø pyproject.toml already exists at {toml_path}. Aborting to prevent overwrite.")
|
|
33
|
+
return False
|
|
34
|
+
|
|
35
|
+
# Gather inputs or default
|
|
36
|
+
p_id = args.id or p_dir.name.lower().replace("-", "_").replace(" ", "_")
|
|
37
|
+
p_name = args.name or p_dir.name.title()
|
|
38
|
+
p_version = args.version or "1.0.0"
|
|
39
|
+
p_description = args.desc or f"A high-performance Karcytics data plugin analyzing {p_name}."
|
|
40
|
+
|
|
41
|
+
toml_content = f'''[project]
|
|
42
|
+
name = "{p_name}"
|
|
43
|
+
version = "{p_version}"
|
|
44
|
+
description = "{p_description}"
|
|
45
|
+
readme = "README.md"
|
|
46
|
+
requires-python = ">=3.11"
|
|
47
|
+
authors = [
|
|
48
|
+
{{ name = "Developer Name" }}
|
|
49
|
+
]
|
|
50
|
+
dependencies = [
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
[project.optional-dependencies]
|
|
54
|
+
dev = [
|
|
55
|
+
"pytest",
|
|
56
|
+
"pytest-qt",
|
|
57
|
+
"ruff",
|
|
58
|
+
"mkdocs-material"
|
|
59
|
+
]
|
|
60
|
+
|
|
61
|
+
[tool.biopro.plugin]
|
|
62
|
+
id = "{p_id}"
|
|
63
|
+
min_core_version = "1.4.9"
|
|
64
|
+
entry_point = "biopro_plugins.{p_id}:initialize"
|
|
65
|
+
requires = ["task_scheduler", "logger", "event_bus"]
|
|
66
|
+
authors = [
|
|
67
|
+
{{ name = "Developer Name", role = "Developer", permissions = ["read_workspace", "write_assets"] }}
|
|
68
|
+
]
|
|
69
|
+
'''
|
|
70
|
+
with open(toml_path, "w", encoding="utf-8") as f:
|
|
71
|
+
f.write(toml_content)
|
|
72
|
+
|
|
73
|
+
print(f"š Successfully created plugin configuration at: {toml_path}")
|
|
74
|
+
return True
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def bootstrap_plugin(args) -> bool:
|
|
78
|
+
"""Create a complete boilerplate plugin skeleton with documentation and source template."""
|
|
79
|
+
p_dir = Path(args.plugin_dir)
|
|
80
|
+
p_dir.mkdir(parents=True, exist_ok=True)
|
|
81
|
+
|
|
82
|
+
# 1. Create subfolders
|
|
83
|
+
(p_dir / "src").mkdir(parents=True, exist_ok=True)
|
|
84
|
+
(p_dir / "docs").mkdir(parents=True, exist_ok=True)
|
|
85
|
+
workflows_dir = p_dir / ".github" / "workflows"
|
|
86
|
+
workflows_dir.mkdir(parents=True, exist_ok=True)
|
|
87
|
+
|
|
88
|
+
# 2. Create manifest.json (pyproject.toml)
|
|
89
|
+
import argparse
|
|
90
|
+
|
|
91
|
+
create_args = argparse.Namespace(plugin_dir=args.plugin_dir, id=None, name=None, version=None, desc=None)
|
|
92
|
+
create_manifest(create_args)
|
|
93
|
+
|
|
94
|
+
# 3. Create a clean __init__.py boilerplate implementing AnalysisBase
|
|
95
|
+
init_file = p_dir / "src" / "__init__.py"
|
|
96
|
+
init_content = """from karcytics_sdk.plugin import AnalysisBase
|
|
97
|
+
|
|
98
|
+
class CustomAnalysisPlugin(AnalysisBase):
|
|
99
|
+
\"\"\"Boilerplate analysis plugin demonstrating safe SDK interaction.\"\"\"
|
|
100
|
+
|
|
101
|
+
def execute(self, workspace_context):
|
|
102
|
+
\"\"\"Executes primary data processing workflow.
|
|
103
|
+
|
|
104
|
+
Args:
|
|
105
|
+
workspace_context: The host application environment and loaded data assets.
|
|
106
|
+
\"\"\"
|
|
107
|
+
self.logger.info("Executing custom boilerplate analysis workflow...")
|
|
108
|
+
|
|
109
|
+
# Access workspace variables
|
|
110
|
+
assets = workspace_context.get_assets()
|
|
111
|
+
self.logger.info(f"Loaded {len(assets)} raw assets in current workspace.")
|
|
112
|
+
|
|
113
|
+
# Complete work and publish progress
|
|
114
|
+
self.publish_progress(100, "Boilerplate execution completed.")
|
|
115
|
+
return {"status": "success", "processed_assets": len(assets)}
|
|
116
|
+
"""
|
|
117
|
+
with open(init_file, "w", encoding="utf-8") as f:
|
|
118
|
+
f.write(init_content)
|
|
119
|
+
|
|
120
|
+
# 4. Create a README.md inside docs/
|
|
121
|
+
readme_file = p_dir / "docs" / "01_getting_started.md"
|
|
122
|
+
readme_content = f"""# Getting Started with {p_dir.name.title()}
|
|
123
|
+
|
|
124
|
+
Welcome to your freshly bootstrapped Karcytics plugin!
|
|
125
|
+
|
|
126
|
+
## Architecture
|
|
127
|
+
This plugin is developed using the Karcytics-SDK. It exposes a single data analysis pipeline extending `AnalysisBase`.
|
|
128
|
+
|
|
129
|
+
## Getting Started
|
|
130
|
+
1. Edit `src/__init__.py` to implement your custom data algorithms.
|
|
131
|
+
2. Maintain your documentation under the `docs/` folder for local integration with the Karcytics Help Center.
|
|
132
|
+
3. Sign your plugin before loading using:
|
|
133
|
+
```bash
|
|
134
|
+
karcytics-sdk sign .
|
|
135
|
+
```
|
|
136
|
+
"""
|
|
137
|
+
with open(readme_file, "w", encoding="utf-8") as f:
|
|
138
|
+
f.write(readme_content)
|
|
139
|
+
|
|
140
|
+
# 5. Create GitHub Actions workflows
|
|
141
|
+
ci_file = workflows_dir / "ci.yml"
|
|
142
|
+
ci_content = """name: CI ā Tests & Lint
|
|
143
|
+
|
|
144
|
+
on:
|
|
145
|
+
push:
|
|
146
|
+
branches: [main, develop]
|
|
147
|
+
pull_request:
|
|
148
|
+
|
|
149
|
+
jobs:
|
|
150
|
+
test:
|
|
151
|
+
runs-on: ${{ matrix.os }}
|
|
152
|
+
strategy:
|
|
153
|
+
matrix:
|
|
154
|
+
os: [ubuntu-latest, macos-latest, windows-latest]
|
|
155
|
+
steps:
|
|
156
|
+
- uses: actions/checkout@v4
|
|
157
|
+
- name: Install uv
|
|
158
|
+
uses: astral-sh/setup-uv@v2
|
|
159
|
+
with:
|
|
160
|
+
version: "latest"
|
|
161
|
+
- name: Set up Python
|
|
162
|
+
uses: actions/setup-python@v5
|
|
163
|
+
with:
|
|
164
|
+
python-version: "3.11"
|
|
165
|
+
- name: Install system Qt deps (Ubuntu)
|
|
166
|
+
if: matrix.os == 'ubuntu-latest'
|
|
167
|
+
run: sudo apt-get update && sudo apt-get install -y libegl1 libxkbcommon-x11-0 libxcb-cursor0
|
|
168
|
+
- name: Install dependencies
|
|
169
|
+
run: |
|
|
170
|
+
uv sync
|
|
171
|
+
uv pip install git+https://github.com/KalaimaranB/Karcytics-SDK.git
|
|
172
|
+
shell: bash
|
|
173
|
+
- name: Run tests
|
|
174
|
+
run: uv run pytest tests/ -v
|
|
175
|
+
env:
|
|
176
|
+
QT_QPA_PLATFORM: offscreen
|
|
177
|
+
shell: bash
|
|
178
|
+
|
|
179
|
+
lint:
|
|
180
|
+
runs-on: ubuntu-latest
|
|
181
|
+
steps:
|
|
182
|
+
- uses: actions/checkout@v4
|
|
183
|
+
- name: Install uv
|
|
184
|
+
uses: astral-sh/setup-uv@v2
|
|
185
|
+
with:
|
|
186
|
+
version: "latest"
|
|
187
|
+
- name: Set up Python
|
|
188
|
+
uses: actions/setup-python@v5
|
|
189
|
+
with:
|
|
190
|
+
python-version: "3.11"
|
|
191
|
+
- run: uv pip install --system ruff
|
|
192
|
+
- run: ruff check src/ tests/
|
|
193
|
+
"""
|
|
194
|
+
with open(ci_file, "w", encoding="utf-8") as f:
|
|
195
|
+
f.write(ci_content)
|
|
196
|
+
|
|
197
|
+
deploy_docs_file = workflows_dir / "deploy-docs.yml"
|
|
198
|
+
deploy_docs_content = """name: Deploy Documentation
|
|
199
|
+
|
|
200
|
+
on:
|
|
201
|
+
push:
|
|
202
|
+
branches:
|
|
203
|
+
- main
|
|
204
|
+
workflow_dispatch:
|
|
205
|
+
|
|
206
|
+
permissions:
|
|
207
|
+
contents: write
|
|
208
|
+
|
|
209
|
+
jobs:
|
|
210
|
+
deploy:
|
|
211
|
+
runs-on: ubuntu-latest
|
|
212
|
+
steps:
|
|
213
|
+
- uses: actions/checkout@v4
|
|
214
|
+
- name: Configure Git Credentials
|
|
215
|
+
run: |
|
|
216
|
+
git config user.name github-actions[bot]
|
|
217
|
+
git config user.email 41898282+github-actions[bot]@users.noreply.github.com
|
|
218
|
+
- name: Install uv
|
|
219
|
+
uses: astral-sh/setup-uv@v2
|
|
220
|
+
with:
|
|
221
|
+
version: "latest"
|
|
222
|
+
- uses: actions/setup-python@v5
|
|
223
|
+
with:
|
|
224
|
+
python-version: "3.11"
|
|
225
|
+
- run: echo "cache_id=$(date --utc '+%V')" >> $GITHUB_ENV
|
|
226
|
+
- uses: actions/cache@v4
|
|
227
|
+
with:
|
|
228
|
+
key: mkdocs-material-${{ env.cache_id }}
|
|
229
|
+
path: .cache
|
|
230
|
+
restore-keys: |
|
|
231
|
+
mkdocs-material-
|
|
232
|
+
- name: Install dependencies
|
|
233
|
+
run: |
|
|
234
|
+
uv pip install --system mkdocs-material "mkdocstrings[python]"
|
|
235
|
+
- name: Build and Deploy
|
|
236
|
+
env:
|
|
237
|
+
PYTHONPATH: src
|
|
238
|
+
run: mkdocs gh-deploy --force
|
|
239
|
+
"""
|
|
240
|
+
with open(deploy_docs_file, "w", encoding="utf-8") as f:
|
|
241
|
+
f.write(deploy_docs_content)
|
|
242
|
+
|
|
243
|
+
release_file = workflows_dir / "release.yml"
|
|
244
|
+
release_content = """name: Auto-Release Plugin
|
|
245
|
+
|
|
246
|
+
on:
|
|
247
|
+
push:
|
|
248
|
+
branches:
|
|
249
|
+
- main
|
|
250
|
+
|
|
251
|
+
env:
|
|
252
|
+
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
|
253
|
+
|
|
254
|
+
jobs:
|
|
255
|
+
check_version:
|
|
256
|
+
name: Check Version Bump
|
|
257
|
+
runs-on: ubuntu-latest
|
|
258
|
+
outputs:
|
|
259
|
+
changed: ${{ steps.check.outputs.changed }}
|
|
260
|
+
version: ${{ steps.check.outputs.version }}
|
|
261
|
+
plugin_id: ${{ steps.check.outputs.plugin_id }}
|
|
262
|
+
release_notes: ${{ steps.check.outputs.release_notes }}
|
|
263
|
+
steps:
|
|
264
|
+
- name: Checkout Code
|
|
265
|
+
uses: actions/checkout@v4
|
|
266
|
+
- name: Set up Python
|
|
267
|
+
uses: actions/setup-python@v5
|
|
268
|
+
with:
|
|
269
|
+
python-version: "3.11"
|
|
270
|
+
- name: Check Version
|
|
271
|
+
id: check
|
|
272
|
+
run: |
|
|
273
|
+
python -c "
|
|
274
|
+
import sys
|
|
275
|
+
if sys.version_info >= (3, 11):
|
|
276
|
+
import tomllib as toml
|
|
277
|
+
else:
|
|
278
|
+
import toml
|
|
279
|
+
|
|
280
|
+
with open('pyproject.toml', 'rb') as f:
|
|
281
|
+
data = toml.load(f)
|
|
282
|
+
|
|
283
|
+
project = data.get('project', {})
|
|
284
|
+
plugin = data.get('tool', {}).get('biopro', {}).get('plugin', {})
|
|
285
|
+
|
|
286
|
+
plugin_id = plugin.get('id', '')
|
|
287
|
+
version = project.get('version', '')
|
|
288
|
+
release_notes = plugin.get('release_notes', 'No release notes provided.')
|
|
289
|
+
|
|
290
|
+
import os
|
|
291
|
+
import uuid
|
|
292
|
+
|
|
293
|
+
with open(os.environ['GITHUB_OUTPUT'], 'a') as f:
|
|
294
|
+
f.write(f'plugin_id={plugin_id}\\n')
|
|
295
|
+
f.write(f'version={version}\\n')
|
|
296
|
+
|
|
297
|
+
eof_marker = str(uuid.uuid4())
|
|
298
|
+
f.write(f'release_notes<<{eof_marker}\\n')
|
|
299
|
+
f.write(f'{release_notes}\\n')
|
|
300
|
+
f.write(f'{eof_marker}\\n')
|
|
301
|
+
"
|
|
302
|
+
VERSION=$(python -c "import tomllib; print(tomllib.load(open('pyproject.toml', 'rb'))['project']['version'])")
|
|
303
|
+
TAG_NAME="v$VERSION"
|
|
304
|
+
|
|
305
|
+
if git ls-remote --tags origin | grep -q "refs/tags/$TAG_NAME"; then
|
|
306
|
+
echo "Version $VERSION already released. Skipping build."
|
|
307
|
+
echo "changed=false" >> $GITHUB_OUTPUT
|
|
308
|
+
else
|
|
309
|
+
echo "New version $VERSION detected. Proceeding with release."
|
|
310
|
+
echo "changed=true" >> $GITHUB_OUTPUT
|
|
311
|
+
fi
|
|
312
|
+
|
|
313
|
+
release:
|
|
314
|
+
name: Evaluate, Sign & Release
|
|
315
|
+
needs: check_version
|
|
316
|
+
if: needs.check_version.outputs.changed == 'true'
|
|
317
|
+
runs-on: ubuntu-latest
|
|
318
|
+
permissions:
|
|
319
|
+
contents: write
|
|
320
|
+
steps:
|
|
321
|
+
- name: Checkout Code
|
|
322
|
+
uses: actions/checkout@v4
|
|
323
|
+
- name: Tag Repository
|
|
324
|
+
run: |
|
|
325
|
+
git config user.name "github-actions[bot]"
|
|
326
|
+
git config user.email "github-actions[bot]@users.noreply.github.com"
|
|
327
|
+
TAG_NAME="v${{ needs.check_version.outputs.version }}"
|
|
328
|
+
git tag $TAG_NAME
|
|
329
|
+
git push origin $TAG_NAME
|
|
330
|
+
- name: Install uv
|
|
331
|
+
uses: astral-sh/setup-uv@v2
|
|
332
|
+
with:
|
|
333
|
+
version: "latest"
|
|
334
|
+
- name: Set up Python
|
|
335
|
+
uses: actions/setup-python@v5
|
|
336
|
+
with:
|
|
337
|
+
python-version: "3.11"
|
|
338
|
+
- name: Install Qt System Dependencies
|
|
339
|
+
run: sudo apt-get update && sudo apt-get install -y libegl1 libxkbcommon-x11-0 libxcb-cursor0
|
|
340
|
+
- name: Install Karcytics SDK
|
|
341
|
+
run: |
|
|
342
|
+
uv pip install --system git+https://github.com/KalaimaranB/Karcytics-SDK.git
|
|
343
|
+
- name: SDK Evaluate Plugin
|
|
344
|
+
run: |
|
|
345
|
+
karcytics-sdk evaluate .
|
|
346
|
+
env:
|
|
347
|
+
QT_QPA_PLATFORM: offscreen
|
|
348
|
+
- name: Execute Project Signing
|
|
349
|
+
run: |
|
|
350
|
+
karcytics-sdk project-sign .
|
|
351
|
+
env:
|
|
352
|
+
KARCYTICS_PROJECT_PRIVATE_KEY: ${{ secrets.KARCYTICS_PROJECT_PRIVATE_KEY }}
|
|
353
|
+
QT_QPA_PLATFORM: offscreen
|
|
354
|
+
- name: Build Release ZIP
|
|
355
|
+
run: |
|
|
356
|
+
PLUGIN_ID="${{ needs.check_version.outputs.plugin_id }}"
|
|
357
|
+
NEW_VERSION="${{ needs.check_version.outputs.version }}"
|
|
358
|
+
ZIP_NAME="${PLUGIN_ID}_v${NEW_VERSION}.zip"
|
|
359
|
+
zip -r "$ZIP_NAME" . \\
|
|
360
|
+
--exclude "*.git*" \\
|
|
361
|
+
--exclude "*tests/*" \\
|
|
362
|
+
--exclude "*__pycache__/*" \\
|
|
363
|
+
--exclude "*.venv/*" \\
|
|
364
|
+
--exclude "*.plugin_venv/*" \\
|
|
365
|
+
--exclude "*.DS_Store" \\
|
|
366
|
+
--exclude "*.pem" \\
|
|
367
|
+
--exclude "*.key" \\
|
|
368
|
+
--exclude "*.pytest_cache/*" \\
|
|
369
|
+
--exclude "*.github/*"
|
|
370
|
+
- name: Create GitHub Release
|
|
371
|
+
uses: softprops/action-gh-release@v2
|
|
372
|
+
with:
|
|
373
|
+
tag_name: "v${{ needs.check_version.outputs.version }}"
|
|
374
|
+
name: "${{ needs.check_version.outputs.plugin_id }} v${{ needs.check_version.outputs.version }}"
|
|
375
|
+
body: |
|
|
376
|
+
### Release Notes
|
|
377
|
+
${{ needs.check_version.outputs.release_notes }}
|
|
378
|
+
|
|
379
|
+
---
|
|
380
|
+
*Auto-generated changes below:*
|
|
381
|
+
generate_release_notes: true
|
|
382
|
+
files: "${{ needs.check_version.outputs.plugin_id }}_v${{ needs.check_version.outputs.version }}.zip"
|
|
383
|
+
- name: Checkout BioPro-Distribution
|
|
384
|
+
uses: actions/checkout@v4
|
|
385
|
+
with:
|
|
386
|
+
repository: KalaimaranB/BioPro-Distribution
|
|
387
|
+
token: ${{ secrets.DIST_PAT }}
|
|
388
|
+
path: dist-repo
|
|
389
|
+
- name: Update registry.json
|
|
390
|
+
run: |
|
|
391
|
+
cd dist-repo
|
|
392
|
+
python3 - <<'EOF2'
|
|
393
|
+
import json, sys
|
|
394
|
+
with open("registry.json") as f:
|
|
395
|
+
reg = json.load(f)
|
|
396
|
+
plugin_id = "${{ needs.check_version.outputs.plugin_id }}"
|
|
397
|
+
new_version = "${{ needs.check_version.outputs.version }}"
|
|
398
|
+
tag_name = f"v{new_version}"
|
|
399
|
+
zip_name = f"{plugin_id}_{tag_name}.zip"
|
|
400
|
+
download_url = f"https://github.com/${{ github.repository }}/releases/download/{tag_name}/{zip_name}"
|
|
401
|
+
if plugin_id in reg.get("plugins", {}):
|
|
402
|
+
reg["plugins"][plugin_id]["version"] = new_version
|
|
403
|
+
reg["plugins"][plugin_id]["download_url"] = download_url
|
|
404
|
+
with open("registry.json", "w") as f:
|
|
405
|
+
json.dump(reg, f, indent=2)
|
|
406
|
+
print(f"Updated {plugin_id} in registry.json")
|
|
407
|
+
else:
|
|
408
|
+
print(f"Error: {plugin_id} not found in registry.json")
|
|
409
|
+
sys.exit(1)
|
|
410
|
+
EOF2
|
|
411
|
+
- name: Open Registry Update PR
|
|
412
|
+
uses: peter-evans/create-pull-request@v6
|
|
413
|
+
with:
|
|
414
|
+
token: ${{ secrets.DIST_PAT }}
|
|
415
|
+
path: dist-repo
|
|
416
|
+
branch: "auto/update-${{ needs.check_version.outputs.plugin_id }}-v${{ needs.check_version.outputs.version }}"
|
|
417
|
+
title: "chore: bump ${{ needs.check_version.outputs.plugin_id }} to v${{ needs.check_version.outputs.version }}"
|
|
418
|
+
commit-message: "chore: bump ${{ needs.check_version.outputs.plugin_id }} to ${{ needs.check_version.outputs.version }}"
|
|
419
|
+
body: |
|
|
420
|
+
## Automated Registry Update
|
|
421
|
+
Plugin **`${{ needs.check_version.outputs.plugin_id }}`** was released at tag `v${{ needs.check_version.outputs.version }}`.
|
|
422
|
+
### Developer Release Notes
|
|
423
|
+
> ${{ needs.check_version.outputs.release_notes }}
|
|
424
|
+
|
|
425
|
+
This PR updates `registry.json` with the latest version.
|
|
426
|
+
> ā ļø **Review before merging:** Verify the download URL is correct and the ZIP is valid.
|
|
427
|
+
add-paths: registry.json
|
|
428
|
+
"""
|
|
429
|
+
with open(release_file, "w", encoding="utf-8") as f:
|
|
430
|
+
f.write(release_content)
|
|
431
|
+
|
|
432
|
+
print(f"\nš Successfully bootstrapped boilerplate plugin at: {p_dir}")
|
|
433
|
+
print("Structure Created:")
|
|
434
|
+
print(" āāā pyproject.toml (Configuration)")
|
|
435
|
+
print(" āāā src/")
|
|
436
|
+
print(" ā āāā __init__.py (Core plugin logic)")
|
|
437
|
+
print(" āāā docs/")
|
|
438
|
+
print(" ā āāā 01_getting_started.md (Documentation)")
|
|
439
|
+
print(" āāā .github/workflows/")
|
|
440
|
+
print(" āāā ci.yml, deploy-docs.yml, release.yml")
|
|
441
|
+
print("\nGet started by running:")
|
|
442
|
+
print(f' cd "{p_dir}" && karcytics-sdk init-identity && karcytics-sdk sign .')
|
|
443
|
+
return True
|
|
444
|
+
|
|
445
|
+
|
|
446
|
+
def init_plugin(args) -> bool:
|
|
447
|
+
"""Scaffold a fresh plugin repository with src/ layout and plugin.toml."""
|
|
448
|
+
print(f"Scaffolding new plugin '{args.plugin_name}' with karcytics-sdk init...")
|
|
449
|
+
print("Note: Scaffolded for Phase 1.")
|
|
450
|
+
return True
|