mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-06-25 22:15:33 -04:00
ci: enforce store-listing metadata length limits (#5854)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
25
.github/workflows/pull-request.yml
vendored
25
.github/workflows/pull-request.yml
vendored
@@ -96,6 +96,24 @@ jobs:
|
||||
print('check-changes filter is aligned with settings.gradle module roots.')
|
||||
PY
|
||||
|
||||
# 1c. STORE METADATA: Enforce store-listing length limits (e.g. the F-Droid /
|
||||
# Play 80-char short_description). These files are mirrored from Crowdin, so
|
||||
# this guard intentionally runs on the translation-sync PRs too (no
|
||||
# scheduled-updates / l10n_main skip) -- that is where overlength translations
|
||||
# land. It is a standalone lightweight job, decoupled from the Gradle build so
|
||||
# a one-line translation fix never triggers a full assemble/test cycle.
|
||||
check-metadata:
|
||||
name: Check Store Metadata
|
||||
if: github.repository == 'meshtastic/Meshtastic-Android'
|
||||
runs-on: ubuntu-24.04-arm
|
||||
timeout-minutes: 5
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: Validate store listing metadata lengths
|
||||
run: python3 scripts/check-metadata-length.py
|
||||
|
||||
# 2. VALIDATION & BUILD: Delegate to reusable-check.yml
|
||||
# We disable coverage and desktop builds for PRs to keep feedback fast
|
||||
# (< 10 mins). Desktop compilation is already covered by the :desktopApp:test
|
||||
@@ -117,7 +135,7 @@ jobs:
|
||||
runs-on: ubuntu-24.04-arm
|
||||
timeout-minutes: 5
|
||||
permissions: {}
|
||||
needs: [check-changes, verify-check-changes-filter, validate-and-build]
|
||||
needs: [check-changes, verify-check-changes-filter, check-metadata, validate-and-build]
|
||||
if: always()
|
||||
steps:
|
||||
- name: Check Workflow Status
|
||||
@@ -127,6 +145,11 @@ jobs:
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "${{ needs.check-metadata.result }}" == "failure" || "${{ needs.check-metadata.result }}" == "cancelled" ]]; then
|
||||
echo "::error::Store metadata length check failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# If changes were detected but build failed, fail the status check
|
||||
if [[ "${{ needs.check-changes.outputs.android }}" == "true" && ("${{ needs.validate-and-build.result }}" == "failure" || "${{ needs.validate-and-build.result }}" == "cancelled") ]]; then
|
||||
echo "::error::Android Check failed"
|
||||
|
||||
9
.github/workflows/scheduled-updates.yml
vendored
9
.github/workflows/scheduled-updates.yml
vendored
@@ -101,6 +101,15 @@ jobs:
|
||||
- name: Fix file permissions
|
||||
run: sudo chown -R $USER:$USER .
|
||||
|
||||
# Early warning for overlength store-listing translations just pulled from
|
||||
# Crowdin. Non-blocking on purpose: a hard failure here would abort the
|
||||
# firmware/hardware/graphs job and stop the PR from being created. The
|
||||
# hard gate is the check-metadata job in pull-request.yml, which runs
|
||||
# against the PR this workflow opens.
|
||||
- name: Check store metadata lengths
|
||||
continue-on-error: true
|
||||
run: python3 scripts/check-metadata-length.py
|
||||
|
||||
- name: Gradle Setup
|
||||
uses: ./.github/actions/gradle-setup
|
||||
with:
|
||||
|
||||
79
scripts/check-metadata-length.py
Executable file
79
scripts/check-metadata-length.py
Executable file
@@ -0,0 +1,79 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Validate Fastlane store-listing metadata against store length limits.
|
||||
|
||||
The ``fastlane/metadata/android`` tree is a mirror of Crowdin: translations are
|
||||
downloaded on a schedule (see ``.github/workflows/scheduled-updates.yml``).
|
||||
Crowdin's max-length toggle only blocks *new* submissions; translations entered
|
||||
before enforcement was enabled are grandfathered in and keep syncing down. This
|
||||
script is the repo-side guard that catches them regardless of Crowdin state.
|
||||
|
||||
Lengths are measured in Unicode code points (what Google Play and F-Droid /
|
||||
IzzyOnDroid count), not bytes -- a byte count badly over-reports Cyrillic and
|
||||
CJK strings.
|
||||
|
||||
Exit status is non-zero if any file exceeds its limit, so it can gate CI.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# Repo root = parent of this script's directory (scripts/).
|
||||
REPO_ROOT = Path(__file__).resolve().parent.parent
|
||||
METADATA_DIR = REPO_ROOT / "fastlane" / "metadata" / "android"
|
||||
|
||||
# Per-file character limits. Keys are file names under each locale directory.
|
||||
# 80 is the F-Droid summary / Google Play short-description limit; 30 is the
|
||||
# Play title limit. Add more entries here to extend coverage.
|
||||
LIMITS = {
|
||||
"short_description.txt": 80,
|
||||
"title.txt": 30,
|
||||
}
|
||||
|
||||
# Running inside GitHub Actions enables ::error:: annotations on the PR.
|
||||
IN_GITHUB_ACTIONS = os.environ.get("GITHUB_ACTIONS") == "true"
|
||||
|
||||
|
||||
def char_count(path: Path) -> int:
|
||||
"""Code-point length of a metadata file, ignoring trailing whitespace."""
|
||||
return len(path.read_text(encoding="utf-8").rstrip())
|
||||
|
||||
|
||||
def main() -> int:
|
||||
if not METADATA_DIR.is_dir():
|
||||
print(f"error: metadata directory not found: {METADATA_DIR}", file=sys.stderr)
|
||||
return 2
|
||||
|
||||
violations: list[tuple[Path, int, int]] = []
|
||||
|
||||
for file_name, limit in sorted(LIMITS.items()):
|
||||
for path in sorted(METADATA_DIR.glob(f"*/{file_name}")):
|
||||
count = char_count(path)
|
||||
if count > limit:
|
||||
violations.append((path, count, limit))
|
||||
|
||||
if not violations:
|
||||
print("All store-listing metadata is within length limits.")
|
||||
return 0
|
||||
|
||||
print("Store-listing metadata exceeds length limits:\n")
|
||||
for path, count, limit in violations:
|
||||
rel = path.relative_to(REPO_ROOT)
|
||||
message = f"{rel} is {count} chars (limit {limit})"
|
||||
print(f" - {message}")
|
||||
if IN_GITHUB_ACTIONS:
|
||||
# Annotate the offending file directly in the PR diff view.
|
||||
print(f"::error file={rel}::{message}")
|
||||
|
||||
print(
|
||||
"\nThese files are mirrored from Crowdin. Fix them at the source "
|
||||
"(shorten or remove the overlength translation so it is re-translated), "
|
||||
"then re-sync -- editing the mirror here is overwritten on the next sync."
|
||||
)
|
||||
return 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user