github-find-action
Find workflow files that reference a specific GitHub Action.
Metadata
- Author: ropean, Claude Sonnet (Anthropic)
- Version: 1.0.0
Code
python
#!/usr/bin/env python3
"""
@title github-find-action
@description Find workflow files that reference a specific GitHub Action.
@author ropean, Claude Sonnet (Anthropic)
@version 1.0.0
@example
python github-find-action.py cloudflare/wrangler-action
python github-find-action.py cloudflare/wrangler-action@v3
python github-find-action.py cloudflare/wrangler-action "\\\\wsl.localhost\\Ubuntu\\home\\robot-u\\git"
"""
from __future__ import annotations
import argparse
import json
import os
import re
import sys
from pathlib import Path
PLATFORM_DEFAULTS = {
"win32": r"D:\Git",
"darwin": "~/Git",
"linux": "~/Git", # includes WSL
}
USES_RE = re.compile(
r"^(?P<indent>\s*)(?:-\s*)?uses:\s*(?P<value>[^#\r\n]+?)(?:\s+#.*)?$",
re.MULTILINE,
)
def default_git_dir() -> Path:
raw = PLATFORM_DEFAULTS.get(sys.platform, "~/Git")
return Path(raw).expanduser()
def normalize_package(value: str) -> str:
cleaned = value.strip().strip("\"'")
return cleaned.split("@", 1)[0].strip().casefold()
_SKIP_DIRS = {"node_modules", "__pycache__", "venv", ".venv"}
def iter_workflow_files(git_dir: Path) -> list[Path]:
results: set[Path] = set()
for root, dirs, files in os.walk(git_dir, followlinks=False):
root_path = Path(root)
if root_path.name == "workflows" and root_path.parent.name == ".github":
results.update(
root_path / f for f in files if f.endswith((".yml", ".yaml"))
)
# Prune: skip hidden dirs (except .github) and known large dirs
dirs[:] = [
d for d in dirs
if d not in _SKIP_DIRS and (not d.startswith(".") or d == ".github")
]
return sorted(results)
def find_package_references(git_dir: Path, package_name: str) -> list[dict]:
matches: list[dict] = []
normalized_target = normalize_package(package_name)
for workflow_path in iter_workflow_files(git_dir):
try:
text = workflow_path.read_text(encoding="utf-8", errors="replace")
except OSError as exc:
print(f"[warn] Cannot read {workflow_path}: {exc}", file=sys.stderr)
continue
workflow_matches: list[dict] = []
for match in USES_RE.finditer(text):
uses_value = match.group("value").strip().strip("\"'")
if normalize_package(uses_value) != normalized_target:
continue
line_number = text.count("\n", 0, match.start()) + 1
workflow_matches.append({"line": line_number, "uses": uses_value})
if not workflow_matches:
continue
try:
rel_path = workflow_path.relative_to(git_dir)
except ValueError:
rel_path = workflow_path
matches.append(
{
"workflow": str(rel_path),
"matches": workflow_matches,
}
)
return matches
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(
description="Find workflows that reference a specific GitHub Action."
)
parser.add_argument(
"pkg_name",
help="GitHub Action name, e.g.: cloudflare/wrangler-action or cloudflare/wrangler-action@v3",
)
parser.add_argument(
"git_dir",
nargs="?",
default=None,
help=f"Root directory containing git repos (default: {default_git_dir()})",
)
parser.add_argument(
"--json",
action="store_true",
help="Print JSON instead of a human-readable summary.",
)
return parser.parse_args()
def main() -> None:
args = parse_args()
git_dir = Path(args.git_dir).expanduser() if args.git_dir else default_git_dir()
if not git_dir.is_dir():
print(f"[error] Directory not found: {git_dir}", file=sys.stderr)
sys.exit(1)
matches = find_package_references(git_dir, args.pkg_name)
result = {
"git_dir": str(git_dir),
"pkg_name": args.pkg_name,
"workflow_count": len(matches),
"matches": matches,
}
if args.json:
print(json.dumps(result, indent=2, ensure_ascii=False))
return
print(f"git_dir: {git_dir}")
print(f"pkg_name: {args.pkg_name}")
print(f"workflow_count: {len(matches)}")
for item in matches:
print(f"\n{item['workflow']}")
for hit in item["matches"]:
print(f" L{hit['line']}: {hit['uses']}")
if __name__ == "__main__":
main()File Information
- Filename:
github-find-action.py - Category: python
- Language: PYTHON