# 05. 릴리스와 버전 관리 > 이 문서는 **버전 번호·git 태그·릴리스 절차의 정본**이다. "몇 버전을 언제 어떻게 배포하는가"는 > 여기서 결정한다. 배포판을 실제로 만드는 절차(원드라이브 zip 등 수동 전달)의 실측 로그는 > `docs/HANDOFF.md`에 있다. ## 1. 버전 번호 — Semantic Versioning `pyproject.toml`의 `[project].version`이 유일한 버전 정본이다. 형식은 `MAJOR.MINOR.PATCH`. | 자리 | 올리는 경우 | |---|---| | `MAJOR` | 설정 파일 스키마·CLI 인자·DB 스키마 등 하위 호환을 깨는 변경 | | `MINOR` | 하위 호환을 유지하며 기능을 추가(새 시트, 새 알림 채널, 새 CLI 서브커맨드 등) | | `PATCH` | 버그 수정, 문서 정리, 리팩터링 등 사용자에게 보이는 동작 변화가 없는 변경 | `0.y.z` 동안은(1.0.0 이전) 아직 안정 API 이전이므로 `MINOR`도 하위 호환을 깰 수 있다. ## 2. 릴리스 절차 ```text 1. CHANGELOG.md의 [Unreleased] 섹션을 확정 버전으로 승격 - 헤더를 `## [X.Y.Z] - YYYY-MM-DD`로 바꾼다 - 문서 하단 비교 링크(compare/... , releases/tag/...)를 갱신한다 2. pyproject.toml의 version = "X.Y.Z" 를 갱신 3. 필수 smoke 실행 (AGENTS.md §3) .\.venv\Scripts\python.exe -m compileall -q src tests .\.venv\Scripts\python.exe -m dmf_crawler --help .\.venv\Scripts\python.exe -m dmf_crawler doctor --json .\.venv\Scripts\python.exe -m pytest tests -q 4. (선택) 배포용 빌드 산출물 생성 — §3 참고 5. 커밋: "release: vX.Y.Z" 6. 태그: git tag -a vX.Y.Z -m "vX.Y.Z" 7. 푸시: git push origin main --follow-tags 8. git.chanpaca.net 웹에서 Releases → 해당 태그로 릴리스 노트 작성 (본문은 CHANGELOG.md의 해당 버전 섹션을 그대로 옮긴다) ``` 태그 이름은 항상 `v` 접두어를 붙인다(`v0.1.0`, `v1.2.0`). `pyproject.toml`의 버전 문자열에는 `v`를 붙이지 않는다. ## 3. 빌드 산출물 (`dist/`) — `src/`와의 구분 이 저장소는 **소스와 빌드 산출물을 엄격히 분리**한다. | 디렉터리 | 내용 | git 추적 여부 | |---|---|---| | `src/dmf_crawler/` | 사람이 직접 편집하는 유일한 소스. 여기가 정본이다 | 추적함 | | `dist/` | `python -m build` 등이 만들어내는 sdist(`.tar.gz`)·wheel(`.whl`) | **추적 안 함**(`.gitignore`) | | `build/` | setuptools가 빌드 중 쓰는 임시 작업 디렉터리 | **추적 안 함** | | `*.egg-info/` | 패키지 메타데이터 캐시 | **추적 안 함** | `dist/`는 **항상 `src/`에서 재생성 가능**해야 한다. `dist/` 안의 파일을 손으로 고쳐서는 안 된다 (고칠 게 있으면 `src/`를 고치고 다시 빌드한다). 그래서 저장소에 커밋하지 않는다 — 커밋하면 "소스와 다른 빌드 산출물이 정본 행세를 하는" 사고가 난다. 빌드 확인 절차: ```powershell .\.venv\Scripts\python.exe -m pip install --upgrade build .\.venv\Scripts\python.exe -m build # dist/dmf_crawler-X.Y.Z-py3-none-any.whl + .tar.gz 생성 .\.venv\Scripts\python.exe -m pip install --force-reinstall dist\dmf_crawler-*.whl .\.venv\Scripts\python.exe -m dmf_crawler --help ``` 이 프로그램은 PyPI에 올리지 않는다(`pyproject.toml`의 `Private :: Do Not Upload` 분류자). `dist/`의 wheel은 다른 PC에 수동 배포할 때만 쓴다. 일반 사용자 배포는 `bootstrap.cmd` + 저장소 zip 방식을 쓴다(§4). ## 4. 최종 사용자 배포판 (zip) 이 프로그램은 개발자가 아닌 사용자가 쓴다(`AGENTS.md`, `README.md` 참고). PyPI/wheel 설치가 아니라 **저장소를 그대로 zip으로 압축해 전달**하는 방식을 쓴다. 실제 배포 사례와 포함/제외 감사 체크리스트는 `docs/HANDOFF.md`의 "릴리즈 배포본 생성 완료" 절을 참고한다. 핵심 원칙만 요약한다: - 포함: `bootstrap.cmd`, `README.md`, `LICENSE`, `pyproject.toml`, `src/`, `config/`, `scripts/`, `prompts/`, `docs/`(필요한 만큼). - 제외: `.venv/`, `data/`, `logs/`, `reports/`, `backup/`, `state/`, `tests/`, `__pycache__/`, `*.pyc`, `*.lnk`, `config.local.toml`, `service_key*`, `*oauth-token*`. - 압축 해제 후 `bootstrap.cmd`가 정상 동작하는지 새 폴더에서 재현 검증한다. ## 5. 원격 저장소 정본 원격은 `git.chanpaca.net`(공개 저장소, Forgejo)이다. ```powershell git remote -v # origin ssh://git@git.chanpaca.net:2222/yunchan/DMF_Crawler.git ``` 이 저장소는 **공개(public)**다. 그래서 커밋 전 다음을 반드시 확인한다(AGENTS.md §6와 동일): - API 키·OAuth 토큰·웹훅 URL·비밀번호가 코드·설정·로그·문서 어디에도 평문으로 없는가 - `config/config.local.toml`, `*.sqlite3`, `data/`, `logs/`, `reports/`, `backup/`, `state/`가 `.gitignore`로 실제로 제외되는가 (`git status` 로 커밋 전 매번 확인) - 실제 수집 데이터(엑셀·DB)가 우연히 스테이징되지 않았는가