// 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 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 ') 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())