여러 줄 주석이 설명보다 경위(예전·실측·지적)를 적고 있어 읽는 사람이 결론을 찾기 어려웠다. - ts·tsx·js·mjs·css·py 478개: 여러 줄 주석은 첫 문장 한 줄로, 과거형·날짜 문장은 삭제 - 주석 위치는 TypeScript 파서·파이썬 tokenize/ast 로 찾는다 — 문자열 안의 # · /* 는 건드리지 않는다 - eslint·ts·noqa·type: ignore 같은 지시 주석은 그대로 둔다 파이썬 275개 정리 전후 AST 동일, TS 298개 주석 뺀 토큰 동일(빈 JSX 주석 10곳만 차이). site·frontend·admin tsc, site vitest 105 passed Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
70 lines
3.0 KiB
Python
70 lines
3.0 KiB
Python
"""발행된 사이트 **전부**를 Azure Blob(`$web`)에 다시 올린다."""
|
|
import argparse, os, sys
|
|
|
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
os.environ.setdefault("APP_ENV", "local")
|
|
|
|
from azure.storage.blob import BlobServiceClient # noqa: E402
|
|
|
|
from services import azure_static # noqa: E402
|
|
|
|
|
|
def site_slugs(root) -> list[str]:
|
|
base = root / azure_static.SITE_ROOT_DIR
|
|
if not base.is_dir():
|
|
raise SystemExit(f"산출물 디렉터리가 없습니다: {base}")
|
|
# index.html 이 있는 디렉토리만 발행본으로 본다(작업 중 잔재를 올리지 않는다).
|
|
return sorted(d.name for d in base.iterdir() if d.is_dir() and (d / "index.html").is_file())
|
|
|
|
|
|
def main() -> None:
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("--dry-run", action="store_true", help="업로드 없이 대상만 출력")
|
|
args = parser.parse_args()
|
|
|
|
root = azure_static.output_dir()
|
|
slugs = site_slugs(root)
|
|
print(f"[republish] 산출물: {root} · 사이트 {len(slugs)}개")
|
|
|
|
if args.dry_run:
|
|
for slug in slugs:
|
|
print(f" - {slug}")
|
|
return
|
|
|
|
if not azure_static.is_configured():
|
|
raise SystemExit("AZURE_STORAGE_CONNECTION_STRING 이 비어 있습니다 — 올릴 곳이 없습니다.")
|
|
|
|
container_name = os.environ.get("AZURE_STORAGE_CONTAINER", azure_static.DEFAULT_CONTAINER).strip()
|
|
container_name = container_name or azure_static.DEFAULT_CONTAINER
|
|
prefix = os.environ.get("AZURE_STORAGE_PREFIX", azure_static.DEFAULT_PREFIX).strip().strip("/")
|
|
|
|
service = BlobServiceClient.from_connection_string(os.environ["AZURE_STORAGE_CONNECTION_STRING"].strip())
|
|
container = service.get_container_client(container_name)
|
|
|
|
# ── 공용 자산 한 번 ──
|
|
shared = azure_static._upload_shared(container, root, prefix)
|
|
print(f"[republish] 공용 {len(shared)}개 (컨테이너 {container_name} · 접두사 {prefix or '(없음)'})")
|
|
|
|
# ── 사이트별 ── 한 사이트가 실패해도 나머지는 계속 올린다.
|
|
failed: list[tuple[str, str]] = []
|
|
for i, slug in enumerate(slugs, 1):
|
|
try:
|
|
files = azure_static._upload_tree(container, root, f"{azure_static.SITE_ROOT_DIR}/{slug}", prefix)
|
|
site_prefix = "/".join(p for p in (prefix, azure_static.SITE_ROOT_DIR, slug) if p)
|
|
removed = azure_static._remove_stale_site_files(container, site_prefix, files)
|
|
print(f" [{i}/{len(slugs)}] {slug} — 파일 {len(files)}개, 정리 {removed}개")
|
|
except Exception as ex: # noqa: BLE001 — 사이트별 실패 격리가 목적이다
|
|
failed.append((slug, f"{type(ex).__name__}: {ex}"))
|
|
print(f" [{i}/{len(slugs)}] {slug} — 실패: {type(ex).__name__}: {ex}")
|
|
|
|
if failed:
|
|
print(f"\n[republish] 실패 {len(failed)}개:")
|
|
for slug, reason in failed:
|
|
print(f" - {slug}: {reason}")
|
|
raise SystemExit(1)
|
|
print(f"\n[republish] 완료 — {len(slugs)}개 사이트")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|