"""**발행하기 전에** — 지금 발행하면 네이버에 닿는가. solution/backend/.venv/bin/python geo/scripts/preflight.py solution/backend/.venv/bin/python geo/scripts/preflight.py --json ★ 사이트 하나를 보는 게 아니라 **통로**를 본다. 오리진이 하나라서 여기서 한 번 확인하면 `/s/` 전부에 해당한다 — 사장님이 1,000명이 돼도 반복하지 않는다. ★ 여기서 빨간불이면 **발행해도 네이버에 안 닿는다.** 2주 뒤에 "왜 색인이 안 되지" 로 알게 되는 것과, 발행 전에 아는 것의 차이다. 점검 넷: 1. 소유확인 파일 없으면 서치어드바이저에 등록조차 못 한다 2. robots.txt Yeti·Daumoa 허용 · 사이트맵 지시 · ★ JS·CSS 를 막지 않았나 3. 루트 사이트맵 통보할 URL 목록의 출처다 4. IndexNow 키 파일 ★ 없으면 통보가 403 으로 전부 거절된다 """ import argparse import asyncio import json import os import sys _ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) for _p in (_ROOT, os.path.join(_ROOT, "solution", "backend")): if _p not in sys.path: sys.path.insert(0, _p) os.environ.setdefault("APP_ENV", "local") import httpx # noqa: E402 from geo.naver import FAIL, OK, SKIP, WARN, Finding, NaverEoReport # noqa: E402 from geo.naver import checks, notify, robots # noqa: E402 from geo.naver._http import TIMEOUT_SEC, get # noqa: E402 from services.site_payload import publish_origin # noqa: E402 MARK = {OK: " ✓", WARN: " !", FAIL: " ✗", SKIP: " ·"} async def preflight(origin: str) -> NaverEoReport: report = NaverEoReport(origin=origin.rstrip("/")) async with httpx.AsyncClient(timeout=TIMEOUT_SEC, follow_redirects=True) as client: report.findings.append(await checks.check_verification(client, report.origin)) for status, label, detail in await robots.fetch_and_judge(client, report.origin): report.findings.append(Finding("robots", label, status, detail)) res = await get(client, report.origin + "/sitemap.xml") if res is not None and res.status_code == 200 and "") report.findings.append(Finding("root_sitemap", "루트 사이트맵", OK, f"URL {n}개")) else: code = res.status_code if res else "연결실패" report.findings.append(Finding( "root_sitemap", "루트 사이트맵", FAIL, f"HTTP {code} — 통보할 URL 의 출처가 없다", )) ok, detail = await notify.check_key_file(client, report.origin) report.findings.append(Finding( "indexnow_key", "IndexNow 키 파일", OK if ok else (WARN if not notify.key() else FAIL), detail, None if ok else "이게 없으면 통보가 403 으로 전부 거절된다", )) return report async def main() -> int: parser = argparse.ArgumentParser(description="발행 전 — 네이버로 가는 통로가 뚫렸나") parser.add_argument("origin", nargs="?", default=None) parser.add_argument("--json", action="store_true") args = parser.parse_args() report = await preflight(args.origin or publish_origin()) if args.json: print(json.dumps(report.as_dict(), ensure_ascii=False, indent=2)) return 0 if report.ok else 1 print(f"[발행 전 점검] {report.origin}\n") for f in report.findings: print(f"{MARK[f.status]} {f.label}" + (f" — {f.detail}" if f.detail else "")) print(f"\n[결과] 통과 {len([f for f in report.findings if f.status == OK])} · " f"주의 {len(report.warned)} · 실패 {len(report.failed)}") if report.failed: print("\n★ 지금 발행하면 네이버에 닿지 않는다. 먼저 고칠 것:") for f in report.failed: print(f" - {f.label}: {f.detail}" + (f"\n → {f.fix}" if f.fix else "")) return 0 if report.ok else 1 if __name__ == "__main__": sys.exit(asyncio.run(main()))