Skip to content
Merged
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
1 change: 1 addition & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Follow them for `extension-api/`, `runtime-api/`, and `golemcore/*` modules unle
- Each plugin is versioned independently with SemVer.
- The source of truth for the current plugin version is the child module `pom.xml`.
- `plugin.yaml` and `registry/` metadata must match the module version.
- Shared repository APIs use the repository-level `plugin.api.version`. They do not automatically follow individual plugin release bumps.
- Released plugin versions are immutable. Never replace the jar, checksum, or metadata of an already released `registry/<owner>/<plugin>/versions/<released>.yaml`.
- Code changes to a released plugin must ship as a new SemVer version. Do not rewrite the checksum of an existing released version in a PR.
- The checksum recorded in `registry/<owner>/<plugin>/versions/<new-version>.yaml` is derived from the artifact built by the release workflow from the repository state on the default branch.
Expand Down
5 changes: 3 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
<java.version>25</java.version>
<maven.compiler.release>${java.version}</maven.compiler.release>
<project.build.outputTimestamp>2026-03-07T00:00:00Z</project.build.outputTimestamp>
<plugin.api.version>1.0.0</plugin.api.version>
<spring.boot.version>4.0.3</spring.boot.version>
<okhttp.version>5.3.2</okhttp.version>
<playwright.version>1.49.0</playwright.version>
Expand Down Expand Up @@ -60,12 +61,12 @@
<dependency>
<groupId>me.golemcore.plugins</groupId>
<artifactId>golemcore-plugin-extension-api</artifactId>
<version>${project.version}</version>
<version>${plugin.api.version}</version>
</dependency>
<dependency>
<groupId>me.golemcore.plugins</groupId>
<artifactId>golemcore-plugin-runtime-api</artifactId>
<version>${project.version}</version>
<version>${plugin.api.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
Expand Down
63 changes: 62 additions & 1 deletion scripts/plugins_repo.py
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,34 @@ def read_dependencies(path: Path) -> list[tuple[str, str, str | None]]:
return dependencies


def read_dependency_management_versions(path: Path) -> dict[tuple[str, str], str]:
root = et.parse(path).getroot()
versions: dict[tuple[str, str], str] = {}
for dependency in root.findall("m:dependencyManagement/m:dependencies/m:dependency", POM):
group_id = read_required_text(dependency, "m:groupId", path)
artifact_id = read_required_text(dependency, "m:artifactId", path)
version = read_required_text(dependency, "m:version", path)
versions[(group_id, artifact_id)] = version
return versions


def read_pom_version(path: Path) -> str:
root = et.parse(path).getroot()
version = root.find("m:version", POM)
if version is None or version.text is None or not version.text.strip():
raise ValueError(f"Missing version in {path}")
return version.text.strip()


def read_pom_property(properties: et.Element | None, name: str, source: Path) -> str:
if properties is None:
raise ValueError(f"{source}: missing properties section")
element = properties.find(f"{{{POM_NS}}}{name}")
if element is None or element.text is None or not element.text.strip():
raise ValueError(f"{source}: {name} must be defined")
return element.text.strip()


def read_build_plugin_artifact_ids(path: Path) -> set[str]:
root = et.parse(path).getroot()
plugins = root.findall("m:build/m:plugins/m:plugin", POM)
Expand Down Expand Up @@ -551,7 +579,8 @@ def validate_repo(check_local_artifacts: bool = False) -> None:
if not plugins:
raise SystemExit("No plugins discovered")

root_pom = et.parse(ROOT / "pom.xml").getroot()
root_pom_path = ROOT / "pom.xml"
root_pom = et.parse(root_pom_path).getroot()
root_modules = [module.text.strip() for module in root_pom.findall("m:modules/m:module", POM) if module.text]
errors: list[str] = []
try:
Expand All @@ -566,6 +595,38 @@ def validate_repo(check_local_artifacts: bool = False) -> None:
if "runtime-api" not in root_modules:
errors.append(f"{ROOT / 'pom.xml'}: missing module runtime-api")

root_properties = root_pom.find("m:properties", POM)
try:
plugin_api_version = read_pom_property(root_properties, "plugin.api.version", root_pom_path)
except ValueError as error:
errors.append(str(error))
else:
if not SEMVER_RE.fullmatch(plugin_api_version):
errors.append(f"{root_pom_path}: plugin.api.version must be a valid SemVer, got {plugin_api_version}")

dependency_management_versions = read_dependency_management_versions(root_pom_path)
expected_api_version_ref = "${plugin.api.version}"
for artifact_id in ("golemcore-plugin-extension-api", "golemcore-plugin-runtime-api"):
coordinate = ("me.golemcore.plugins", artifact_id)
actual_version = dependency_management_versions.get(coordinate)
if actual_version != expected_api_version_ref:
errors.append(
f"{root_pom_path}: dependencyManagement for {coordinate[0]}:{artifact_id} must use "
f"{expected_api_version_ref}, got {actual_version or '<missing>'}"
)

for module_path in ("extension-api", "runtime-api"):
module_pom = ROOT / module_path / "pom.xml"
if not module_pom.exists():
continue
try:
module_version = read_pom_version(module_pom)
except ValueError as error:
errors.append(str(error))
continue
if plugin_api_version is not None and module_version != plugin_api_version:
errors.append(f"{module_pom}: version must match plugin.api.version {plugin_api_version}, got {module_version}")

formatter_required_modules = {"extension-api", "runtime-api"}
formatter_plugin_artifact = "formatter-maven-plugin"
for module_path in formatter_required_modules:
Expand Down