scripts/ had ~80 flat files. Entry points (ci_*.sh, codegen.sh, run_tests.sh, format_native.sh, setup_hooks.sh, upload-symbols.*) and the shared pubspec_version.py stay at the root; checkers, generators, maestro tooling and release tooling move into subdirectories with their tests. Updated every reference: workflow steps, guard-test glob, Docker COPY paths and .dockerignore whitelist, website audit path, dart test imports, and regenerated the five outputs whose headers embed generator paths.
63 lines
2.0 KiB
Python
Executable File
63 lines
2.0 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Require immutable commit pins for remote GitHub Actions dependencies."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
import workflow_yaml
|
|
|
|
ROOT = Path(__file__).resolve().parents[2]
|
|
WORKFLOWS = ROOT / ".github" / "workflows"
|
|
ACTIONS = ROOT / ".github" / "actions"
|
|
REMOTE_RE = re.compile(r"^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+(?:/[A-Za-z0-9_./-]+)?@[0-9a-fA-F]{40}$")
|
|
|
|
|
|
def iter_action_files(directory: Path = ACTIONS):
|
|
"""Local composite actions run in the same trust boundary as the workflows."""
|
|
yield from sorted((*directory.glob("*/action.yml"), *directory.glob("*/action.yaml")))
|
|
|
|
|
|
def iter_uses_references(path: Path):
|
|
return workflow_yaml.iter_uses_references(path.read_text(encoding="utf-8"))
|
|
|
|
|
|
def validate_reference(reference: str) -> str | None:
|
|
if reference.startswith("./"):
|
|
return None
|
|
if REMOTE_RE.fullmatch(reference):
|
|
return None
|
|
return "remote actions must use a full 40-character commit SHA"
|
|
|
|
|
|
def main(argv: list[str] | None = None) -> int:
|
|
args = list(sys.argv[1:] if argv is None else argv)
|
|
paths = (
|
|
[Path(value) for value in args]
|
|
if args
|
|
else [*workflow_yaml.iter_workflow_files(WORKFLOWS), *iter_action_files()]
|
|
)
|
|
violations = []
|
|
for path in paths:
|
|
for line_number, reference in iter_uses_references(path):
|
|
reason = validate_reference(reference)
|
|
if reason:
|
|
try:
|
|
display_path = path.resolve().relative_to(ROOT)
|
|
except ValueError:
|
|
display_path = path
|
|
violations.append(f"{display_path}:{line_number}: {reference!r}: {reason}")
|
|
if violations:
|
|
print("Mutable or malformed GitHub Actions references:", file=sys.stderr)
|
|
for violation in violations:
|
|
print(f" {violation}", file=sys.stderr)
|
|
return 1
|
|
print(f"Workflow action pins verified ({len(paths)} files).")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|