Skip to content
Open
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
14 changes: 13 additions & 1 deletion .rhiza/rhiza.mk
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@ export PYTHON_VERSION
RHIZA_VERSION ?= $(shell cat .rhiza/.rhiza-version 2>/dev/null || echo "0.10.2")
export RHIZA_VERSION

# Default sync schedule (cron expression for GitHub Actions sync workflow)
# Override in your root Makefile to customise when sync runs.
# Example: RHIZA_SYNC_SCHEDULE = 0 9 * * 1-5 (weekdays at 9 AM UTC)
RHIZA_SYNC_SCHEDULE ?= 0 0 * * 1

export UV_NO_MODIFY_PATH := 1
export UV_VENV_CLEAR := 1

Expand All @@ -76,7 +81,7 @@ endef
export RHIZA_LOGO

# Declare phony targets for Rhiza Core
.PHONY: print-logo sync sync-experimental materialize validate readme pre-sync post-sync pre-validate post-validate
.PHONY: print-logo sync sync-experimental materialize validate readme pre-sync post-sync pre-validate post-validate _apply-sync-schedule

# Hook targets (double-colon rules allow multiple definitions)
# Note: pre-install/post-install are defined in bootstrap.mk
Expand All @@ -98,9 +103,16 @@ sync: pre-sync ## sync with template repository as defined in .rhiza/template.ym
else \
$(MAKE) install-uv; \
${UVX_BIN} "rhiza==$(RHIZA_VERSION)" sync .; \
$(MAKE) _apply-sync-schedule; \
fi
@$(MAKE) post-sync

_apply-sync-schedule: ## (internal) apply RHIZA_SYNC_SCHEDULE override to GitHub Actions sync workflow
@if [ "$(RHIZA_SYNC_SCHEDULE)" != "0 0 * * 1" ] && [ -f .github/workflows/rhiza_sync.yml ]; then \
sed -i.bak "s|cron: '[^']*'|cron: '$(RHIZA_SYNC_SCHEDULE)'|" .github/workflows/rhiza_sync.yml && rm -f .github/workflows/rhiza_sync.yml.bak; \
printf "${BLUE}[INFO] Applied custom sync schedule: $(RHIZA_SYNC_SCHEDULE)${RESET}\n"; \
fi

materialize: ## [DEPRECATED] use 'make sync' instead — materialize --force is now sync
@printf "${YELLOW}[WARN] 'make materialize' is deprecated and will be removed in a future release.${RESET}\n"
@printf "${YELLOW}[WARN] Please use 'make sync' instead (e.g. 'materialize --force' is now 'make sync').${RESET}\n"
Expand Down
128 changes: 128 additions & 0 deletions .rhiza/tests/sync/test_sync_schedule.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
"""Tests for the RHIZA_SYNC_SCHEDULE override mechanism.

These tests validate that users can override the default sync schedule
(cron expression) used in the GitHub Actions sync workflow, and that
the override is applied correctly during `make sync`.

Security Notes:
- S101 (assert usage): Asserts are used in pytest tests to validate conditions
- S603/S607 (subprocess usage): Any subprocess calls are for testing sync targets
in isolated environments with controlled inputs
- Test code operates in a controlled environment with trusted inputs
"""

from __future__ import annotations

from pathlib import Path

from sync.conftest import run_make, strip_ansi


class TestSyncScheduleVariable:
"""Tests for the RHIZA_SYNC_SCHEDULE Makefile variable."""

def test_default_sync_schedule_value(self, logger):
"""RHIZA_SYNC_SCHEDULE should default to '0 0 * * 1' (weekly Monday)."""
proc = run_make(logger, ["print-RHIZA_SYNC_SCHEDULE"], dry_run=False)
out = strip_ansi(proc.stdout)
assert "Value of RHIZA_SYNC_SCHEDULE:" in out
assert "0 0 * * 1" in out

def test_sync_schedule_overridable_via_env(self, logger, tmp_path: Path):
"""RHIZA_SYNC_SCHEDULE should be overridable via environment variable."""
import os

env = os.environ.copy()
env["RHIZA_SYNC_SCHEDULE"] = "0 9 * * 1-5"

proc = run_make(logger, ["print-RHIZA_SYNC_SCHEDULE"], dry_run=False, env=env)
out = strip_ansi(proc.stdout)
assert "0 9 * * 1-5" in out

def test_sync_schedule_overridable_via_makefile(self, logger, tmp_path: Path):
"""RHIZA_SYNC_SCHEDULE should be overridable in root Makefile."""
makefile = tmp_path / "Makefile"
original = makefile.read_text()
new_content = "RHIZA_SYNC_SCHEDULE = 0 6 * * *\n\n" + original
makefile.write_text(new_content)

proc = run_make(logger, ["print-RHIZA_SYNC_SCHEDULE"], dry_run=False)
out = strip_ansi(proc.stdout)
assert "0 6 * * *" in out


class TestApplySyncSchedule:
"""Tests for the _apply-sync-schedule target."""

def test_apply_sync_schedule_skips_when_default(self, logger, tmp_path: Path):
"""_apply-sync-schedule should not modify files when using default schedule."""
# Create a mock workflow file matching the actual rhiza_sync.yml format
workflow_dir = tmp_path / ".github" / "workflows"
workflow_dir.mkdir(parents=True)
workflow_file = workflow_dir / "rhiza_sync.yml"
original_content = "on:\n schedule:\n - cron: '0 0 * * 1' # Weekly on Monday\n"
workflow_file.write_text(original_content)

proc = run_make(logger, ["_apply-sync-schedule"], dry_run=False)
assert proc.returncode == 0

# File should remain unchanged
assert workflow_file.read_text() == original_content

def test_apply_sync_schedule_patches_workflow(self, logger, tmp_path: Path):
"""_apply-sync-schedule should patch workflow when schedule is overridden."""
# Create a mock workflow file matching the actual rhiza_sync.yml format
workflow_dir = tmp_path / ".github" / "workflows"
workflow_dir.mkdir(parents=True)
workflow_file = workflow_dir / "rhiza_sync.yml"
workflow_file.write_text("on:\n schedule:\n - cron: '0 0 * * 1' # Weekly on Monday\n")

# Override the schedule via Makefile
makefile = tmp_path / "Makefile"
original = makefile.read_text()
new_content = "RHIZA_SYNC_SCHEDULE = 0 9 * * 1-5\n\n" + original
makefile.write_text(new_content)

proc = run_make(logger, ["_apply-sync-schedule"], dry_run=False)
assert proc.returncode == 0

# File should be patched
patched = workflow_file.read_text()
assert "0 9 * * 1-5" in patched
assert "0 0 * * 1" not in patched

def test_apply_sync_schedule_handles_missing_workflow(self, logger, tmp_path: Path):
"""_apply-sync-schedule should succeed even if workflow file is missing."""
# Override the schedule but don't create workflow file
makefile = tmp_path / "Makefile"
original = makefile.read_text()
new_content = "RHIZA_SYNC_SCHEDULE = 0 6 * * *\n\n" + original
makefile.write_text(new_content)

proc = run_make(logger, ["_apply-sync-schedule"], dry_run=False)
assert proc.returncode == 0

def test_apply_sync_schedule_prints_info(self, logger, tmp_path: Path):
"""_apply-sync-schedule should print info message when patching."""
# Create a mock workflow file matching the actual rhiza_sync.yml format
workflow_dir = tmp_path / ".github" / "workflows"
workflow_dir.mkdir(parents=True)
workflow_file = workflow_dir / "rhiza_sync.yml"
workflow_file.write_text("on:\n schedule:\n - cron: '0 0 * * 1'\n")

# Override the schedule
makefile = tmp_path / "Makefile"
original = makefile.read_text()
new_content = "RHIZA_SYNC_SCHEDULE = 0 12 * * 0\n\n" + original
makefile.write_text(new_content)

proc = run_make(logger, ["_apply-sync-schedule"], dry_run=False)
out = strip_ansi(proc.stdout)
assert "Applied custom sync schedule" in out
assert "0 12 * * 0" in out

def test_sync_target_calls_apply_schedule(self, logger):
"""The sync target should include _apply-sync-schedule in dry-run output."""
proc = run_make(logger, ["sync"])
out = proc.stdout
assert "_apply-sync-schedule" in out
34 changes: 34 additions & 0 deletions docs/CUSTOMIZATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,10 +123,44 @@ PYTHON_VERSION = 3.12
# Override test coverage threshold (default: 90)
COVERAGE_FAIL_UNDER = 80

# Override the sync schedule (default: weekly on Monday at midnight UTC)
# Uses cron syntax: minute hour day-of-month month day-of-week
RHIZA_SYNC_SCHEDULE = 0 9 * * 1-5 # Weekdays at 9 AM UTC

# Include the Rhiza API (template-managed)
include .rhiza/rhiza.mk
```

### Sync Schedule Override

The `RHIZA_SYNC_SCHEDULE` variable controls the cron schedule for the GitHub Actions sync workflow (`.github/workflows/rhiza_sync.yml`). Since this file is template-managed and overwritten during sync, the schedule is automatically patched after each `make sync` to preserve your custom value.

**Default:** `0 0 * * 1` (weekly on Monday at midnight UTC)

**Examples:**

```makefile
# Daily at 6 AM UTC
RHIZA_SYNC_SCHEDULE = 0 6 * * *

# Weekdays at 9 AM UTC
RHIZA_SYNC_SCHEDULE = 0 9 * * 1-5

# First day of each month at midnight UTC
RHIZA_SYNC_SCHEDULE = 0 0 1 * *

# Every 6 hours
RHIZA_SYNC_SCHEDULE = 0 */6 * * *
```

Set this in your root `Makefile` (before the `include` line) and it will be applied automatically every time `make sync` runs. The override is also visible in the sync output:

```
[INFO] Applied custom sync schedule: 0 9 * * 1-5
```

> **Note:** For GitLab CI, the sync schedule is configured via the GitLab UI (Settings → CI/CD → Pipeline schedules), so this variable only affects GitHub Actions.

### On-Demand Configuration

You can also pass variables directly to `make` for one-off commands:
Expand Down
Loading