Skip to content

Feature/plugin dev sdk - #2

Merged
Detritalw merged 2 commits into
mainfrom
feature/plugin-dev-sdk
Jul 13, 2026
Merged

Feature/plugin dev sdk#2
Detritalw merged 2 commits into
mainfrom
feature/plugin-dev-sdk

Conversation

@Detritalw

Copy link
Copy Markdown
Member

No description provided.

Provide offline scaffolding, validation, packaging, local install helpers, and a local LauncherClient so plugin authors can develop without hand-editing manifests or manually copying files.
Remove accidental bytecode artifacts from the repository so the plugin SDK working tree stays clean.
@Detritalw
Detritalw merged commit 2ad7faf into main Jul 13, 2026
5 checks passed
@Detritalw
Detritalw deleted the feature/plugin-dev-sdk branch July 13, 2026 02:44

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces an offline plugin SDK and CLI tools for Bloret Launcher, enabling users to scaffold, validate, inspect, build, and install plugins, as well as a LauncherClient for local HTTP API communication. The review feedback suggests three key robustness improvements: handling existing directories as build output paths in build_plugin, safely normalizing the permissions field in normalize_manifest to prevent type errors, and ensuring scaffold_plugin correctly handles file paths as destinations without raising a NotADirectoryError.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread BLAPI/plugin/build.py
LOGGER.error("Plugin build refused due to %d validation error(s)", len(errors))
raise ValidationError(errors)
manifest = load_manifest(root)
archive = Path(output) if output is not None else root.parent / "{}-{}.zip".format(manifest.get("id", root.name), manifest.get("version", "dev") or "dev")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

If the output argument is an existing directory, build_plugin will attempt to open that directory as a zip file (e.g., zipfile.ZipFile("dist", "w")), which will raise an IsADirectoryError or PermissionError. Since the README explicitly suggests BLAPI plugin build my-plugin -o dist as a standard usage, we should handle the case where output is a directory by appending the default zip filename inside it.

Suggested change
archive = Path(output) if output is not None else root.parent / "{}-{}.zip".format(manifest.get("id", root.name), manifest.get("version", "dev") or "dev")
default_filename = "{}-{}.zip".format(manifest.get("id", root.name), manifest.get("version", "dev") or "dev")
if output is not None:
out_path = Path(output)
archive = out_path / default_filename if out_path.is_dir() else out_path
else:
archive = root.parent / default_filename

Comment thread BLAPI/plugin/manifest.py
entry["process"] = str(raw_entry.get("process") or "")
entry["qml_page"] = str(raw_entry.get("qml_page") or raw_entry.get("qml") or "")
result["entry"] = entry
result["permissions"] = sorted(set(result.get("permissions") or []))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

If permissions in the manifest is not a list (for example, if it is an integer or a string), normalize_manifest will either crash with a TypeError or split the string into individual characters. We should safely normalize permissions only if it is a list, set, or tuple, and default to an empty list otherwise.

Suggested change
result["permissions"] = sorted(set(result.get("permissions") or []))
raw_permissions = result.get("permissions")
if isinstance(raw_permissions, (list, set, tuple)):
result["permissions"] = sorted(set(p for p in raw_permissions if isinstance(p, str)))
else:
result["permissions"] = []

Comment thread BLAPI/plugin/scaffold.py
Comment on lines +34 to +35
if target.exists() and any(target.iterdir()) and not force:
raise FileExistsError("destination is not empty: {!s}".format(target))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

If destination exists but is a file, calling any(target.iterdir()) will raise a NotADirectoryError instead of a FileExistsError. We should check if target is a directory before calling iterdir(), or raise FileExistsError if it is a file.

Suggested change
if target.exists() and any(target.iterdir()) and not force:
raise FileExistsError("destination is not empty: {!s}".format(target))
if target.exists() and not force:
if not target.is_dir() or any(target.iterdir()):
raise FileExistsError("destination already exists or is not empty: {!s}".format(target))

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant