dmg가 조용히 사라졌다 — electron-builder YAML 들여쓰기 삽질기
안녕하세요, 천상나타입니다.
Electron으로 데스크톱 앱을 몇 개 만들어서 GitHub Releases로 배포하고 있습니다. 얼마 전 mac 자동 업데이트가 안 된다는 걸 발견해서 수정 커밋을 넣고 5개 앱을 전부 재릴리스했는데요, 그 뒤 정리가 안 된 상태를 확인하다가 버그가 하나가 아니라 두 개였다는 걸 알게 됐습니다. 그리고 둘 다 원인이 어이없을 정도로 작았어요 — YAML 들여쓰기 한 번, 그리고 파일 필터 정규식에 빠진 글자 세 개.
오늘은 그 삽질기를 정리합니다. electron-builder와 electron-updater를 쓰시는 분은 제발 같은 실수를 안 하셨으면 해서요.
배경: 왜 zip을 넣었나
electron-updater로 mac 자동 업데이트를 구현하면, 앱은 릴리스의 latest-mac.yml을 읽어서 새 버전이 있나 확인합니다. 그런데 실제 다운로드 단계에서 업데이터가 찾는 건 dmg가 아니라 .zip이에요. mac의 자동 업데이트는 Squirrel.Mac 기반이라 zip 아카이브를 요구합니다.
릴리스에 dmg만 올라가 있으면 체크는 되는데 다운로드에서 ERR_UPDATER_ZIP_FILE_NOT_FOUND로 죽습니다. 그래서 electron-builder.yml의 mac 타겟에 zip을 추가했어요.
mac:
target:
- target: dmg
- target: zip # 이 줄을 추가
arch:
- x64
- arm64
자, 보이시나요? 이게 사고의 전부입니다.
사건 1 — mac-x64.dmg가 조용히 증발했다
재릴리스 후 다운로드 페이지 데이터를 동기화하는 스크립트를 돌렸더니 5개 앱 전부 "mac-x64 에셋 없음"이라며 갱신을 거부했어요. 하나도 아니고 전부가요.
릴리스 에셋 목록을 열어보니 정말로 mac-arm64.dmg만 있고 mac-x64.dmg가 없었습니다. 그리고 범인은 바로 위에 추가한 그 한 줄이었어요.

저는 "arch: [x64, arm64]는 target 전체에 적용되는 줄" 알았는데, YAML에서 arch는 바로 위 - target: 아이템의 속성입니다. - target: zip을 dmg와 arch 사이에 끼워 넣으면서 arch가 zip의 자식이 돼버렸고, dmg는 arch가 사라졌어요.
arch를 생략한 dmg 타겟은 빌드 머신의 아키텍처만 만듭니다. CI 러너가 arm64 mac이었으니 arm64 dmg 하나만 나온 거죠. 에러도 안 났습니다. 빌드는 "정상"이었으니까요.
사건 2 — zip은 빌드만 됐고 업로드가 안 됐다
이게 더 뼈아픈데요. 에셋 목록을 자세히 보면 이상한 게 있었습니다.
MyViewer-1.2.8-mac-arm64.zip.blockmap ← 있음
MyViewer-1.2.8-mac-x64.zip.blockmap ← 있음
MyViewer-1.2.8-mac-x64.zip ← 없음?!
zip의 부산물(blockmap)은 있는데 zip 본체가 없는 상태였어요. blockmap은 zip을 만들면서 같이 생성되는 파일이니까, zip은 분명 빌드됐습니다. 근데 릴리스에는 안 올라간 거죠.

원인은 업로드 스크립트의 파일 필터였습니다. CI와 로컬 배포 스크립트 둘 다 이런 패턴으로 올릴 파일을 골라요.
CI: files=(release/*.exe release/*.dmg release/*.yml release/*.blockmap)
로컬: /\.(exe|dmg|yml|blockmap)$/
zip이 없습니다. dmg만 만들던 시절에 짜둔 필터를 그대로 쓰고 있었던 거예요. zip 타겟은 추가했으면서 "그걸 어디로 올리는지"는 아무도 안 봤던 겁니다 — 자동 업데이트 수정 커밋이, 목적 달성이 불가능한 상태로 머지된 셈이죠.
해결: 5개 레포 일괄 수정 + 재릴리스
수정은 간단했습니다. 각 레포에서 세 군데만 고쳤어요.
# electron-builder.yml — dmg에도 arch 복원
target:
- target: dmg
arch: [x64, arm64]
- target: zip
arch: [x64, arm64]
# 업로드 glob 두 곳에 zip 추가
files=(release/*.exe release/*.dmg release/*.yml release/*.zip release/*.blockmap)
/\.(exe|dmg|yml|blockmap|zip)$/
그리고 각 레포에서 npm version patch로 태그를 찍어 CI를 다시 돌렸습니다. 이번엔 에셋이 제대로 채워졌어요.

덤 — 패키징 없이 업데이트 체크만 재현하는 법
검증하면서 알게 된 팁인데요, 앱을 빌드·설치하지 않고도 업데이트 체크 로직만 돌려볼 수 있습니다. electron-updater는 개발 모드에서 dev-app-update.yml을 읽도록 강제할 수 있어요.
const { app } = require('electron');
const { autoUpdater } = require('electron-updater');
autoUpdater.forceDevUpdateConfig = true; // 패키징 안 돼 있어도 설정 파일 사용
autoUpdater.autoDownload = false;
app.whenReady().then(async () => {
autoUpdater.on('error', e => console.error('ERR:', e.message));
autoUpdater.on('update-available', i => console.log('AVAILABLE:', i.version));
await autoUpdater.checkForUpdates();
});
# dev-app-update.yml — publish 설정과 동일하게
provider: github
owner: myname
repo: myapp-releases
releaseType: release
같은 폴더에 두고 npx electron .으로 실행하면 "Found version 1.2.8 (url: ...-x64.zip, ...-arm64.zip)" 같은 로그가 바로 나옵니다. dmg 받아서 설치하고 앱 켜서 확인하는 루프를 30초짜리로 줄여줬어요.
배운 점
YAML 수정은 diff가 아니라 파서로 확인. 이번 버그는 git diff로 봐도 "+1줄"이라 깔끔해 보였는데, 실제 의미는 "아이템 소유권 이전"이었어요. 이후로는 yml을 고치면 이렇게 확인합니다.
const y = require('js-yaml').load(fs.readFileSync('electron-builder.yml'));
console.log(y.mac.target); // [{target:'dmg',arch:[...]}, {target:'zip',arch:[...]}]
"빌드 산출물"과 "배포된 에셋"은 다르다. 빌드 로그에 파일이 찍혀도 업로드 단계 필터에서 버려질 수 있습니다. 릴리스 후엔 CI 로그가 아니라 실제 릴리스 에셋 목록을 보는 습관이 생겼어요 — 이상한 부산물(본체 없는 blockmap)이 최고의 단서였습니다.
자동 업데이트는 끝까지 검증해야 한다. 체크 성공 ≠ 다운로드 성공 ≠ 설치 성공입니다. 참고로 무서명(또는 ad-hoc 서명) mac 앱은 Squirrel.Mac이 설치 단계에서 서명을 검증해서, 체크·다운로드가 돼도 최종 적용이 막힐 수 있어요. 제대로 자동 업데이트를 쓰려면 Developer ID 서명이 필요합니다.
정리
- 들여쓰기:
- target: zip삽입이 dmg의arch를 빼앗아 x64 dmg 증발 → 타겟별로arch명시 - 업로드: zip 타겟 추가했으면 업로드 glob/정규식에도
.zip추가 - 검증:
forceDevUpdateConfig+dev-app-update.yml로 체크만 빠르게 재현 - 원칙: "빌드됨"을 "배포됨"으로 착각하지 말 것, 릴리스 에셋으로 확인할 것
한 줄의 들여쓰기가 mac 인텔 사용자 전부의 다운로드를 막고 있었던 셈인데, 에러 메시지 하나 없이 조용히 깨져 있던 게 제일 무서웠습니다. YAML을 만질 땐 diff가 아니라 결과물을 보자 — 이번 삽질의 교훈입니다.
'IT > Tool' 카테고리의 다른 글
| Playwright 심화 — 세션 저장, 파일 업로드, SPA 대기 (실전 삽질기) (0) | 2026.11.14 |
|---|---|
| API가 없는 사이트를 자동화하는 법 — Playwright 입문 (0) | 2026.11.14 |
| [연금 저축 공제 환급 계산기] 연소득, 연금 저축 입금액을 입력하시면 연말 정산시 세액 공제액을 계산해드립니다. - 바로 변환해드려요. (0) | 2020.05.11 |
| 로또번호를 입력하시면 해당번호로 당첨될 뻔한 내역을 알려 드립니다. - 바로 변환해드립니다. (0) | 2020.04.21 |
| [LOTTO] 로또 회차번호를 선택하시면 1등 당첨내역 정보를 알려드려요. (0) | 2020.04.18 |