지난주에 저장소 40개의 버전 번호가 전부 거짓말이었다는 글을 썼습니다. 릴리스 도구가 버전을 xcodebuild 커맨드라인 인자로만 넘기고 저장소에는 되쓰지 않아서, 스토어엔 1.9.5가 올라가 있는데 커밋된 파일은 1.0인 채로 몇 년을 산 이야기였습니다.
오늘 그걸 고쳤습니다. 업로드가 성공하면 project 파일에 버전을 되쓰고 chore: bump 커밋을 남기는 함수 하나. 40줄이면 끝날 일이라고 생각했습니다.
40줄은 맞았습니다. 함정이 두 개였을 뿐입니다.
당신의 프로젝트에서 "버전"이라는 값은 정확히 몇 개의 파일에 적혀 있습니까? 바로 대답할 수 있습니까?
함정 1: 빌드가 읽는 파일과 재생성이 읽는 파일이 다릅니다
xcodebuild는 project.pbxproj를 읽습니다. 그러니 되쓰기도 거기에 하면 될 것 같습니다.
그런데 이 플릿의 27개 앱은 xcodegen을 씁니다. xcodegen에게 project.pbxproj는 산출물이고, 진짜 시드는 project.yml입니다. pbxproj만 고치고 yml을 안 고치면, 다음번 누군가 xcodegen generate를 도는 순간 되쓰기가 조용히 증발합니다 — yml에 남아 있던 낡은 버전으로.
이건 가설이 아닙니다. 이 플릿에서 실제로 한 번 터졌던 경로입니다. 재생성이 스테일 시드를 읽어 빌드번호가 1로 돌아갔고, 애플이 build 1 DUPLICATE로 반려했습니다.
그래서 되쓰기는 두 파일 다 갱신합니다. project.yml이 있으면 시드까지, 없으면 pbxproj만.
함정 2: "스토어가 진실"도 항상은 아닙니다
되쓰기는 앞으로의 드리프트만 막습니다. 이미 벌어진 40개의 갭은 일괄 백필로 메꿔야 했습니다. 방법은 자명해 보입니다: 스토어의 라이브 버전을 받아와 저장소에 쓴다. 스토어가 진실이니까.
dry-run 출력을 훑는데 한 줄이 눈에 걸렸습니다.
BUMP brewra: 1.2 -> 1.1나머지 40개는 전부 앞으로 가는데 이 앱만 뒤로 갑니다. 저장소가 1.2인데 스토어 라이브는 1.1. 확인해 보니 1.2가 지금 심사 대기 중이었습니다. 저장소는 미래를 들고 있고, 스토어의 "라이브"는 과거를 보여주고 있었던 겁니다.
여기서 그냥 --apply를 눌렀다면 심사 중인 버전을 저장소에서 후퇴시키고, 다음 릴리스가 그 낡은 시드에서 출발했을 겁니다. 고치려던 바로 그 버그를, 고치는 도구가 재생산하는 구조입니다.
당신이라면 어떻게 막겠습니까? 심사 상태를 API로 조회해서 분기하겠습니까? 저는 더 값싼 규칙을 골랐습니다: 버전은 뒤로 못 간다. 저장소 값이 스토어 값보다 크거나 같으면 건너뜁니다. 심사 중이든 아니든, 후퇴만 안 하면 이 도구가 낼 수 있는 최악의 사고가 사라집니다.
결과
40개 앱 백필 완료, 2개 스킵(하나는 심사 대기로 저장소가 앞섬, 하나는 첫 출시 심사 중이라 라이브 없음). 적용 직후 같은 스크립트를 다시 돌려 변경 0건 — 멱등성을 눈으로 확인하고 커밋했습니다.
자가진단 세 개:
- 당신의 "버전"은 몇 개 파일에 적혀 있습니까? 그중 코드 생성기가 읽는 시드는 어느 쪽입니까?
- 백필·마이그레이션 도구에 역행 가드가 있습니까? 아니면 "소스가 진실"이라는 가정 하나에 기대고 있습니까?
- 그 도구를 두 번 연속 돌리면 두 번째는 변경 0건입니까?
솔직한 부분
이 함정 둘 다 제가 예견한 게 아닙니다. 하나는 과거에 이미 터진 사고 기록이 막아줬고, 하나는 dry-run 출력에서 딱 한 줄이 이상해 보여서 잡혔습니다. 설계가 아니라 습관이 잡은 겁니다 — 파괴적 일괄 작업 전에 계획을 전부 출력해서 훑는 습관.
오늘 백필 같은 일괄 수정을 앞두고 있다면, --apply 전에 dry-run 출력을 끝까지 스크롤해 보세요. 방향이 반대인 줄이 딱 하나 섞여 있을지 모릅니다. 당신의 brewra는 뭐였습니까 — 댓글로 알려주세요.