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
2 changes: 1 addition & 1 deletion minecraft-java/template.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ spec:
displayName: Minecraft (Java Edition)
game: minecraft-java
# Kept in lockstep with module.yaml's bundle version from 2.5.0 onward.
version: 2.8.1
version: 2.9.0
categories: [Sandbox, Survival, Building, Modded, Creative]
# Brand color the dashboard tints this game's icon/accents with.
accentColor: "#5b9a3e"
Expand Down
2 changes: 1 addition & 1 deletion nuclear-option/module.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
apiVersion: gameplane.local/module/v1
name: nuclear-option
displayName: Nuclear Option
version: 1.0.0
version: 1.0.1
game: nuclear-option
categories: [Simulation, PvP]
summary: Nuclear Option dedicated server with remote-command console, runtime-downloaded via SteamCMD.
Expand Down
11 changes: 4 additions & 7 deletions nuclear-option/template.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ spec:
displayName: Nuclear Option
game: nuclear-option
# Kept in lockstep with module.yaml's bundle version.
version: 1.0.0
version: 1.0.1
categories: [Simulation, PvP]
# Brand color for the dashboard.
accentColor: "#ff6b6b"
Expand Down Expand Up @@ -166,12 +166,9 @@ spec:
list:
command: "get-player-list"
entryRegex: '"steamId":\s*"(?P<name>[^"]*)"'
kick:
command: "kick-player {{.Player}}"
ban:
command: "banlist-add {{.Player}}"
unban:
command: "banlist-remove {{.Player}}"
kick: "kick-player {{.Player}}"
ban: "banlist-add {{.Player}}"
unban: "banlist-remove {{.Player}}"
# Note: the server returns 2000 (success) for kick/ban/unban even when
# the Steam ID is not present, so the agent cannot distinguish a
# successful moderation from a silent no-op.
Expand Down
2 changes: 1 addition & 1 deletion the-isle/module.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ name: the-isle
displayName: The Isle
version: 1.0.0
game: the-isle
categories: [Survival, Dinosaur, Open World, Multiplayer]
categories: [Survival, Dinosaurs, Open World, Multiplayer]
summary: The Isle dedicated server (Unreal Engine 4) with Source RCON remote administration and persistent world save management.
homepage: https://findtheisle.com/
license: MIT
Expand Down
2 changes: 1 addition & 1 deletion the-isle/template.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ spec:
displayName: The Isle
game: the-isle
version: 1.0.0
categories: [Survival, Dinosaur, Open World, Multiplayer]
categories: [Survival, Dinosaurs, Open World, Multiplayer]
accentColor: "#22543d"
description: |
The Isle dedicated multiplayer server running on Unreal Engine.
Expand Down
42 changes: 40 additions & 2 deletions validate.py
Original file line number Diff line number Diff line change
Expand Up @@ -1026,6 +1026,31 @@ def rule_credential_fields_must_be_password(spec: dict) -> list[Finding]:
return findings


def rule_template_version_matches_module(spec: dict, module_version: str | None, template_path: Path) -> list[Finding]:
"""Rule 11: GameTemplate spec.version must match module.yaml version.

Historical bug: minecraft-java template.yaml had spec.version 2.8.1 while
module.yaml was 2.9.0, causing the wizard to display an outdated version.
The template must stay in lockstep with the module's declared version.
"""
if module_version is None:
return []

spec_version = spec.get("version", "")
if spec_version == module_version:
return []

return [
Finding(
ERROR,
"template-version-drift",
f"{template_path.name}: spec.version={spec_version!r} but module.yaml version={module_version!r} — "
"these must match exactly (template versions say 'lockstep'). Update template spec.version to "
f"{module_version!r}.",
)
]


LAYOUT_ENFORCED_MODULES = {
"cs2",
"palworld",
Expand Down Expand Up @@ -1103,7 +1128,7 @@ def collect_image_refs(spec: dict) -> list[str]:
return out


def validate_module(spec: dict, cache: dict[str, dict]) -> list[Finding]:
def validate_module(spec: dict, cache: dict[str, dict], module_dir: Path | None = None) -> list[Finding]:
findings: list[Finding] = []
image_refs = collect_image_refs(spec)
if not image_refs:
Expand All @@ -1129,6 +1154,19 @@ def validate_module(spec: dict, cache: dict[str, dict]) -> list[Finding]:
findings += rule_mods_loaders_requires_versions(spec)
findings += rule_credential_fields_must_be_password(spec)
findings += rule_images_pinned(spec)

# Check template version matches module.yaml if we have the module dir
if module_dir:
module_yaml_path = module_dir / "module.yaml"
if module_yaml_path.exists():
try:
module_doc = yaml.safe_load(module_yaml_path.read_text())
module_version = (module_doc or {}).get("version")
template_path = module_dir / "template.yaml"
findings += rule_template_version_matches_module(spec, module_version, template_path)
except yaml.YAMLError:
pass # module.yaml parse error already reported elsewhere if relevant

return findings


Expand Down Expand Up @@ -1348,7 +1386,7 @@ def main(argv: list[str]) -> int:
continue
spec = (doc or {}).get("spec") or {}

findings = rule_directory_layout(module_dir) + validate_module(spec, cache)
findings = rule_directory_layout(module_dir) + validate_module(spec, cache, module_dir)
if module_dir.name in offline_findings:
findings.extend(offline_findings[module_dir.name])
print(f"== {module_dir.name} ==")
Expand Down
Loading