fix(release): keep the updater configuration in the installer
Some checks failed
deploy-site / deploy (push) Failing after 1m6s

Installing the previous build left an app that could not update itself: the
packaging path used to guarantee the native module build does not create the
updater configuration file, so the update client had nothing to read.

That file is now written from the single feed source and its presence in the
packaged app is checked before anything is published, so an installer that
cannot update can no longer be released.
This commit is contained in:
Yun Chan 2026-09-18 16:35:03 +09:00
parent 1af3cf75c7
commit f14341ace4
28 changed files with 141 additions and 47 deletions

View file

@ -146,6 +146,9 @@ if (!existsSync(appDir)) {
// (실측 사고: better_sqlite3.node가 NODE_MODULE_VERSION 131 → Electron 130 요구)
runNodeScript('scripts/ci/fix-native-abi.mjs', ['--dir', appDir])
runNodeScript('scripts/ci/verify-native-abi.mjs', ['--dir', appDir])
// electron-builder의 --dir/--prepackaged 경로는 app-update.yml을 만들지 않는다.
// 이 파일이 없으면 electron-updater가 설정을 읽지 못해 자동 업데이트가 죽는다(실측).
runNodeScript('scripts/ci/write-app-update-yml.mjs', ['--dir', appDir])
// 이전 볼륨 정리 (같은 버전 재드 시 잔여 볼륨이 섞이지 않도록)
for (const name of readdirSync(releaseDir)) {

View file

@ -70,6 +70,9 @@ if (build) {
runNodeScript('scripts/ci/fix-native-abi.mjs', ['--dir', appDir])
runNodeScript('scripts/ci/verify-native-abi.mjs', ['--dir', appDir])
// electron-updater 설정 파일을 보장한다(없으면 자동 업데이트가 동작하지 않는다)
runNodeScript('scripts/ci/write-app-update-yml.mjs', ['--dir', appDir])
// 3) 검증된 트리에서 설치본 생성 (--prepackaged = 재빌드 없이 그대로 패키징)
console.log('[updater] electron-builder --prepackaged (NSIS x64)')
const packageResult = spawnSync(
@ -157,6 +160,13 @@ if (installerSize > MAX_UPLOAD_BYTES) {
process.exit(1)
}
// 설치본에 app-update.yml이 없으면 electron-updater가 설정을 읽지 못해 자동 업데이트가 죽는다.
const packagedUpdateConfig = join(releaseDir, 'win-unpacked', 'resources', 'app-update.yml')
if (!existsSync(packagedUpdateConfig)) {
console.error('[updater] app-update.yml 누락 — write-app-update-yml.mjs를 먼저 실행하세요.')
process.exit(1)
}
const metadata = readFileSync(metadataPath, 'utf8')
if (!metadata.includes(`version: ${version}`)) {
console.error('[updater] latest.yml의 버전이 product-version.json과 다릅니다.')

View file

@ -0,0 +1,70 @@
// scripts/ci/write-app-update-yml.mjs
// 패키징된 앱 트리에 electron-updater 설정 파일(resources/app-update.yml)을 보장한다.
//
// 배경(실측 사고): electron-builder는 `--dir`/`--prepackaged` 경로에서 app-update.yml을
// 생성하지 않는다. 그래서 1.3.3 설치본에는 이 파일이 없었고, electron-updater가 설정을
// 읽지 못해 **자동 업데이트가 동작하지 않는다**.
// (일반 `electron-builder --win nsis` 경로에서는 생성되지만, 우리는 네이티브 ABI 검증을 위해
// --dir → 검증 → --prepackaged 순서를 쓰므로 직접 만들어 준다.)
//
// 값의 출처는 런타임 SSOT인 apps/desktop/src/main/update-feed.ts의 UPDATE_FEED_URL 하나뿐이다.
//
// 사용: node scripts/ci/write-app-update-yml.mjs --dir <packagedDir>
import { existsSync, readFileSync, writeFileSync } from 'node:fs'
import { dirname, join } from 'node:path'
import { fileURLToPath } from 'node:url'
const root = join(dirname(fileURLToPath(import.meta.url)), '..', '..')
const dirFlagIndex = process.argv.indexOf('--dir')
const packagedDir = dirFlagIndex >= 0 ? process.argv[dirFlagIndex + 1] : null
if (!packagedDir || !existsSync(packagedDir)) {
console.error('사용: node scripts/ci/write-app-update-yml.mjs --dir <packagedDir>')
process.exit(1)
}
// 피드 URL은 update-feed.ts가 정본이다 (electron-builder.yml의 publish.url과 동일해야 한다).
const feedSource = readFileSync(join(root, 'apps', 'desktop', 'src', 'main', 'update-feed.ts'), 'utf8')
const feedMatch = feedSource.match(/export const UPDATE_FEED_URL\s*=\s*'([^']+)'/)
if (!feedMatch) {
console.error('[app-update] update-feed.ts에서 UPDATE_FEED_URL을 찾을 수 없습니다.')
process.exit(1)
}
const feedUrl = feedMatch[1]
const electronBuilderConfig = readFileSync(
join(root, 'apps', 'desktop', 'electron-builder.yml'),
'utf8',
)
const publishMatch = electronBuilderConfig.match(/^publish:\s*$[\s\S]*?url:\s*"([^"]+)"/m)
if (publishMatch && publishMatch[1] !== feedUrl) {
console.error(
[
'[app-update] feed URL 불일치:',
` update-feed.ts : ${feedUrl}`,
` electron-builder.yml : ${publishMatch[1]}`,
' 두 값은 같아야 합니다(자동 업데이트 계약).',
].join('\n'),
)
process.exit(1)
}
const target = join(packagedDir, 'resources', 'app-update.yml')
const contents = [
'provider: generic',
`url: ${feedUrl}`,
// electron-builder가 일반 경로에서 생성하는 값과 동일한 규칙(제품명 기반)
"updaterCacheDirName: 'd3ro-voice-updater'",
'',
].join('\n')
const existing = existsSync(target) ? readFileSync(target, 'utf8') : null
if (existing === contents) {
console.log('[app-update] 이미 최신 상태입니다')
process.exit(0)
}
writeFileSync(target, contents, 'utf8')
console.log(`[app-update] 작성: ${target}`)
console.log(contents.trimEnd())