Files
computer-playbook/utils/annotations/message.py
T
kevinveenbirkenbachandClaude Sonnet 4.6 0ce6b1347c refactor(annotations): move to utils/annotations, add step summary support
- Move utils/gha/annotations.py → utils/annotations/message.py
- Move scripts/gha/summarize_annotations.py → utils/annotations/summarize.py
- Keep utils/gha/annotations.py as compatibility shim
- Update all imports across codebase to utils.annotations.message
- Wire tee + summary step into test-code-lint, unit, and integration workflows
- Add unit tests for utils/annotations/summarize (20 tests)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-08 14:05:24 +02:00

101 lines
2.5 KiB
Python

"""GitHub Actions workflow command annotations.
Outputs structured log messages that GitHub Actions renders as annotations
(warning triangles, error markers, etc.) in the job summary and log view.
See: https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/workflow-commands-for-github-actions#setting-a-warning-message
"""
from __future__ import annotations
import os
from typing import Iterable, Optional
def in_github_actions() -> bool:
return os.environ.get("GITHUB_ACTIONS") == "true"
def _emit(
level: str,
message: str,
*,
title: Optional[str] = None,
file: Optional[str] = None,
line: Optional[int] = None,
col: Optional[int] = None,
) -> None:
if in_github_actions():
parts = []
if title:
parts.append(f"title={title}")
if file:
parts.append(f"file={file}")
if line is not None:
parts.append(f"line={line}")
if col is not None:
parts.append(f"col={col}")
props = ",".join(parts)
prefix = f"::{level} {props}::" if props else f"::{level}::"
print(f"{prefix}{message}", flush=True)
else:
label = f"[{level.upper()}]"
if title:
label += f" {title}"
print(f"{label}: {message}", flush=True)
def warning(
message: str,
*,
title: Optional[str] = None,
file: Optional[str] = None,
line: Optional[int] = None,
col: Optional[int] = None,
) -> None:
_emit("warning", message, title=title, file=file, line=line, col=col)
def error(
message: str,
*,
title: Optional[str] = None,
file: Optional[str] = None,
line: Optional[int] = None,
col: Optional[int] = None,
) -> None:
_emit("error", message, title=title, file=file, line=line, col=col)
def notice(
message: str,
*,
title: Optional[str] = None,
file: Optional[str] = None,
line: Optional[int] = None,
col: Optional[int] = None,
) -> None:
_emit("notice", message, title=title, file=file, line=line, col=col)
def warning_each(
items: Iterable[str],
*,
title: Optional[str] = None,
file: Optional[str] = None,
) -> None:
"""Emit one warning annotation per item in *items*."""
for item in items:
warning(item, title=title, file=file)
def error_each(
items: Iterable[str],
*,
title: Optional[str] = None,
file: Optional[str] = None,
) -> None:
"""Emit one error annotation per item in *items*."""
for item in items:
error(item, title=title, file=file)