Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
be6c022
Rewrite ModHub description for 2.1 production
SimGamerJen Oct 13, 2026
41780b3
Promote runtime version to 2.1.0.0
SimGamerJen Oct 13, 2026
15f7f77
Add 2.1.0.0 production release notes
SimGamerJen Oct 13, 2026
ad753f4
Prepare production release workflow
SimGamerJen Oct 13, 2026
c7b362c
Update README for 2.1 production
SimGamerJen Oct 13, 2026
31ce04e
Add production candidate validation workflow
SimGamerJen Oct 13, 2026
6972852
Update modDesc for current FS25 TestRunner
SimGamerJen Oct 13, 2026
bea8f5e
Remove protected calls from integration API
SimGamerJen Oct 13, 2026
08284af
Remove protected calls from crop calendar manager
SimGamerJen Oct 13, 2026
9ae655b
Remove protected call from regional profile manager
SimGamerJen Oct 13, 2026
1b4b747
Remove protected calls from native calendar sorter
SimGamerJen Oct 13, 2026
5c1f734
Remove protected calls from CCO GUI
SimGamerJen Oct 13, 2026
6797677
Remove protected calls from core UI and calendar paths
SimGamerJen Oct 13, 2026
201c757
Let NPC planning errors surface to the game log
SimGamerJen Oct 13, 2026
cd9b5b5
Remove swallowed errors from sowing and AI safeguards
SimGamerJen Oct 13, 2026
819440b
Remove remaining protected calls for ModHub compliance
SimGamerJen Oct 13, 2026
2b5eb87
Clean TestRunner long-line warning in calendar manager
SimGamerJen Oct 13, 2026
fa73e6b
Clean TestRunner long-line warnings in core
SimGamerJen Oct 13, 2026
07687c0
Clean TestRunner long-line warnings in GUI
SimGamerJen Oct 13, 2026
e8df12f
Enforce current ModHub TestRunner checks in CI
SimGamerJen Oct 13, 2026
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
22 changes: 11 additions & 11 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,27 @@ on:
version:
description: Release/tag version
required: true
default: 2.1.0.0-beta.3
default: 2.1.0.0
target_ref:
description: Branch, tag or commit to package
required: true
default: release/v2.1.0.0-beta.3
default: release/v2.1.0.0
release_title:
description: GitHub release title
required: true
default: Crop Control Override 2.1.0.0 Beta 3
default: Crop Control Override 2.1.0.0
notes_file:
description: Release notes file on target ref
required: true
default: BETA3_RELEASE_NOTES.md
default: RELEASE_NOTES_v2.1.0.0.md
prerelease:
description: Publish as a GitHub pre-release
required: true
type: boolean
default: true
default: false
push:
branches:
- release/v2.1.0.0-beta.3
- release/v2.1.0.0
paths:
- .release/READY

Expand All @@ -44,11 +44,11 @@ jobs:
timeout-minutes: 5

env:
RELEASE_VERSION: ${{ inputs.version || '2.1.0.0-beta.3' }}
RELEASE_VERSION: ${{ inputs.version || '2.1.0.0' }}
TARGET_REF: ${{ inputs.target_ref || github.ref_name }}
RELEASE_TITLE: ${{ inputs.release_title || 'Crop Control Override 2.1.0.0 Beta 3' }}
NOTES_FILE: ${{ inputs.notes_file || 'BETA3_RELEASE_NOTES.md' }}
PRERELEASE: ${{ github.event_name == 'push' || inputs.prerelease }}
RELEASE_TITLE: ${{ inputs.release_title || 'Crop Control Override 2.1.0.0' }}
NOTES_FILE: ${{ inputs.notes_file || 'RELEASE_NOTES_v2.1.0.0.md' }}
PRERELEASE: ${{ inputs.prerelease || false }}

steps:
- name: Check out requested release source
Expand All @@ -74,7 +74,7 @@ jobs:
git config user.email '41898282+github-actions[bot]@users.noreply.github.com'
git add -A
if ! git diff --cached --quiet; then
git commit -m 'Promote CCO 2.1.0.0-beta.3 release source [skip ci]'
git commit -m 'Promote CCO 2.1.0.0 release source [skip ci]'
git push origin HEAD:"${TARGET_REF}"
fi
fi
Expand Down
176 changes: 176 additions & 0 deletions .github/workflows/validate.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
name: Validate Crop Control Override

on:
pull_request:
branches:
- main
push:
branches:
- release/v2.1.0.0
workflow_dispatch:

permissions:
contents: read

jobs:
validate:
runs-on: ubuntu-latest
timeout-minutes: 5

steps:
- name: Check out source
uses: actions/checkout@v4

- name: Validate metadata and XML
shell: bash
run: |
set -euo pipefail
python - <<'PY'
import re
import xml.etree.ElementTree as ET
from pathlib import Path

expected_version = "2.1.0.0"

required_xml = [
"modDesc.xml",
"gui/CropControlOverrideMenu.xml",
"gui/guiProfiles.xml",
"l10n/l10n_en.xml",
"l10n/l10n_de.xml",
"l10n/l10n_fr.xml",
"templates/customRegionalProfile.xml",
]
for path in required_xml:
ET.parse(path)

root = ET.parse("modDesc.xml").getroot()
mod_version = root.findtext("version")
if mod_version != expected_version:
raise SystemExit(f"Unexpected modDesc version: {mod_version!r}")

if root.get("descVersion") != "113":
raise SystemExit(f"Unexpected descVersion: {root.get('descVersion')!r}")

title = root.find("title")
if title is None or (title.findtext("en") or "").strip() != "Crop Control Override":
raise SystemExit("Missing canonical English mod title")
duplicate_titles = []
for language in ("de", "fr"):
localized = title.findtext(language)
if localized is not None and localized.strip() == "Crop Control Override":
duplicate_titles.append(language)
if duplicate_titles:
raise SystemExit(
"Redundant identical title localizations: " + ", ".join(duplicate_titles)
)

runtime = Path("scripts/CropControlOverride.lua").read_text(encoding="utf-8")
match = re.search(r'VERSION\s*=\s*"([^"]+)"', runtime)
runtime_version = match.group(1) if match else None
if runtime_version != expected_version:
raise SystemExit(f"Unexpected runtime version: {runtime_version!r}")

required_source = [
"scripts/Debug.lua",
"scripts/CropControlOverrideChangeSettingsEvent.lua",
"scripts/RegionalProfileManager.lua",
"scripts/CropCalendarManager.lua",
"scripts/NativeCalendarSort.lua",
"scripts/gui/CropControlOverrideMenu.lua",
"scripts/CropControlOverride.lua",
"scripts/CCOIntegrationApi.lua",
]
missing = [path for path in required_source if not Path(path).is_file()]
if missing:
raise SystemExit("Missing required source files: " + ", ".join(missing))

protected_call_pattern = re.compile(r"\b(?:pcall|xpcall)\s*\(")
protected_calls = []
long_lines = []
for lua_path in sorted(Path("scripts").rglob("*.lua")):
source = lua_path.read_text(encoding="utf-8")
for line_number, line in enumerate(source.splitlines(), 1):
if protected_call_pattern.search(line):
protected_calls.append(f"{lua_path}:{line_number}")
if len(line) > 200:
long_lines.append(f"{lua_path}:{line_number} ({len(line)} chars)")

if protected_calls:
raise SystemExit(
"PublicLuaCheck-style protected calls remain:\n"
+ "\n".join(protected_calls)
)
if long_lines:
raise SystemExit(
"TestRunner-style Lua lines over 200 characters remain:\n"
+ "\n".join(long_lines)
)

description = root.find("description")
if description is None or description.find("en") is None:
raise SystemExit("Missing ModHub description")
english = "".join(description.find("en").itertext())
if "CHANGELOG 2.1.0.0" not in english:
raise SystemExit("2.1.0.0 changelog missing from English modDesc description")
PY

- name: Run FSM compatibility check
shell: bash
run: |
set -euo pipefail
if test -f tools/check_fsm_compat.py; then
python tools/check_fsm_compat.py
fi

- name: Build candidate ZIP
shell: bash
run: |
set -euo pipefail
rm -f FS25_CropControlOverride.zip
zip -r FS25_CropControlOverride.zip \
modDesc.xml icon_CropControlOverride.dds gui l10n scripts templates modSettings docs \
-x '*.DS_Store' '*__MACOSX*' 'docs/examples/*'

python - <<'PY'
import zipfile
import xml.etree.ElementTree as ET

required = {
"modDesc.xml",
"icon_CropControlOverride.dds",
"gui/CropControlOverrideMenu.xml",
"scripts/CropControlOverride.lua",
"scripts/CropCalendarManager.lua",
"scripts/NativeCalendarSort.lua",
"scripts/RegionalProfileManager.lua",
"scripts/CCOIntegrationApi.lua",
"templates/customRegionalProfile.xml",
}

with zipfile.ZipFile("FS25_CropControlOverride.zip", "r") as archive:
bad = archive.testzip()
if bad is not None:
raise SystemExit("Corrupt ZIP member: " + bad)

names = set(archive.namelist())
missing = sorted(required - names)
if missing:
raise SystemExit("Missing packaged files: " + ", ".join(missing))

root = ET.fromstring(archive.read("modDesc.xml"))
if root.findtext("version") != "2.1.0.0":
raise SystemExit("Unexpected packaged modDesc version")
if root.get("descVersion") != "113":
raise SystemExit("Unexpected packaged descVersion")
PY

sha256sum FS25_CropControlOverride.zip

- name: Upload production candidate
uses: actions/upload-artifact@v4
with:
name: FS25_CropControlOverride-2.1.0.0-candidate
path: FS25_CropControlOverride.zip
if-no-files-found: error
retention-days: 14
Loading
Loading