diff --git a/CHANGELOG.md b/CHANGELOG.md
index 75062a8..84cf8b1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -13,6 +13,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Cloud-optional backup (encrypted, opt-in)
- Plugin system for custom pipelines
+## [1.3.4] - 2026-09-18
+
+### Fixed
+- **Automatic updates could not start on 1.3.3**: its installer was missing the updater
+ configuration file, because the packaging path used to guarantee the native module build
+ does not create it. The file is now written from the single feed source and its presence
+ in the packaged app is verified before publishing, so an installer that cannot update is
+ never shipped.
+
## [1.3.3] - 2026-09-18
### Fixed
diff --git a/apps/admin-swagger/openapi.json b/apps/admin-swagger/openapi.json
index 6c1edc1..6dc41cd 100644
--- a/apps/admin-swagger/openapi.json
+++ b/apps/admin-swagger/openapi.json
@@ -3,7 +3,7 @@
"info": {
"title": "D3RO-VOICE Admin API",
"description": "Admin CRM Edge Functions for user management, subscription CRUD, payment history, and audit logs.",
- "version": "1.3.3"
+ "version": "1.3.4"
},
"servers": [
{
diff --git a/apps/admin/package.json b/apps/admin/package.json
index 6b80dc6..d3cac23 100644
--- a/apps/admin/package.json
+++ b/apps/admin/package.json
@@ -1,6 +1,6 @@
{
"name": "@d3ro/admin",
- "version": "1.3.3",
+ "version": "1.3.4",
"private": true,
"description": "D3RO Voice Admin CRM — SaaS 관리 도구",
"scripts": {
diff --git a/apps/api-server/D3ROVoice.Api.csproj b/apps/api-server/D3ROVoice.Api.csproj
index 42a627f..17327fb 100644
--- a/apps/api-server/D3ROVoice.Api.csproj
+++ b/apps/api-server/D3ROVoice.Api.csproj
@@ -2,7 +2,7 @@
net10.0
- 1.3.3
+ 1.3.4
enable
enable
diff --git a/apps/desktop/package.json b/apps/desktop/package.json
index eed0ba2..e27248c 100644
--- a/apps/desktop/package.json
+++ b/apps/desktop/package.json
@@ -1,6 +1,6 @@
{
"name": "@d3ro/desktop",
- "version": "1.3.3",
+ "version": "1.3.4",
"productName": "d3ro-voice",
"description": "로컬 AI 음성 어시스턴트 (Electron)",
"main": "./out/main/index.js",
diff --git a/apps/mobile-rn/android/app/build.gradle b/apps/mobile-rn/android/app/build.gradle
index 45de6b8..4414a73 100644
--- a/apps/mobile-rn/android/app/build.gradle
+++ b/apps/mobile-rn/android/app/build.gradle
@@ -151,8 +151,8 @@ def versionSettingsValid = configuredVersionName != null &&
configuredVersionName ==~ strictSemver &&
configuredVersionCodeValue != null &&
configuredVersionCodeValue <= 2100000000L
-def resolvedVersionName = versionSettingsValid ? configuredVersionName : "1.3.3"
-def resolvedVersionCode = versionSettingsValid ? configuredVersionCodeValue.toInteger() : 1031003
+def resolvedVersionName = versionSettingsValid ? configuredVersionName : "1.3.4"
+def resolvedVersionCode = versionSettingsValid ? configuredVersionCodeValue.toInteger() : 1031004
def requiredReleaseSettings = [
D3RO_RELEASE_STORE_FILE: releaseStoreFilePath,
diff --git a/apps/mobile-rn/ios/D3ROVoice.xcodeproj/project.pbxproj b/apps/mobile-rn/ios/D3ROVoice.xcodeproj/project.pbxproj
index a610a49..cb97790 100644
--- a/apps/mobile-rn/ios/D3ROVoice.xcodeproj/project.pbxproj
+++ b/apps/mobile-rn/ios/D3ROVoice.xcodeproj/project.pbxproj
@@ -257,7 +257,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
- CURRENT_PROJECT_VERSION = 1031003;
+ CURRENT_PROJECT_VERSION = 1031004;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = D3ROVoice/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 15.1;
@@ -265,7 +265,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
- MARKETING_VERSION = 1.3.3;
+ MARKETING_VERSION = 1.3.4;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
@@ -287,14 +287,14 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
- CURRENT_PROJECT_VERSION = 1031003;
+ CURRENT_PROJECT_VERSION = 1031004;
INFOPLIST_FILE = D3ROVoice/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 15.1;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
- MARKETING_VERSION = 1.3.3;
+ MARKETING_VERSION = 1.3.4;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
diff --git a/apps/mobile-rn/metadata/android/en-US/changelogs/1031004.txt b/apps/mobile-rn/metadata/android/en-US/changelogs/1031004.txt
new file mode 100644
index 0000000..27365b8
--- /dev/null
+++ b/apps/mobile-rn/metadata/android/en-US/changelogs/1031004.txt
@@ -0,0 +1 @@
+Fixed an installer that was missing its update configuration.
diff --git a/apps/mobile-rn/metadata/android/ko-KR/changelogs/1031004.txt b/apps/mobile-rn/metadata/android/ko-KR/changelogs/1031004.txt
new file mode 100644
index 0000000..0d0d3e4
--- /dev/null
+++ b/apps/mobile-rn/metadata/android/ko-KR/changelogs/1031004.txt
@@ -0,0 +1 @@
+자동 업데이트 설정이 누락되던 문제를 고쳤습니다.
diff --git a/apps/mobile-rn/package-lock.json b/apps/mobile-rn/package-lock.json
index 8275499..7ef41af 100644
--- a/apps/mobile-rn/package-lock.json
+++ b/apps/mobile-rn/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "@d3ro/mobile-rn",
- "version": "1.3.3",
+ "version": "1.3.4",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@d3ro/mobile-rn",
- "version": "1.3.3",
+ "version": "1.3.4",
"dependencies": {
"@d3ro/api-client": "file:../../packages/api-client",
"@d3ro/core": "file:../../packages/core",
@@ -62,7 +62,7 @@
},
"../..": {
"name": "d3ro-voice-monorepo",
- "version": "1.3.3",
+ "version": "1.3.4",
"license": "MIT",
"workspaces": [
"apps/desktop",
@@ -81,7 +81,7 @@
},
"../../packages/api-client": {
"name": "@d3ro/api-client",
- "version": "1.3.3",
+ "version": "1.3.4",
"license": "MIT",
"dependencies": {
"@d3ro/core": "*",
@@ -98,7 +98,7 @@
},
"../../packages/core": {
"name": "@d3ro/core",
- "version": "1.3.3",
+ "version": "1.3.4",
"license": "MIT",
"dependencies": {
"docx": "^9.6.1"
@@ -109,7 +109,7 @@
},
"../../packages/i18n": {
"name": "@d3ro/i18n",
- "version": "1.3.3",
+ "version": "1.3.4",
"license": "MIT",
"devDependencies": {
"@types/react": "^19.0.0"
@@ -120,7 +120,7 @@
},
"../../packages/ui-native": {
"name": "@d3ro/ui-native",
- "version": "1.3.3",
+ "version": "1.3.4",
"license": "MIT",
"devDependencies": {
"@types/react": "*"
diff --git a/apps/mobile-rn/package.json b/apps/mobile-rn/package.json
index d8807f5..b11ec4a 100644
--- a/apps/mobile-rn/package.json
+++ b/apps/mobile-rn/package.json
@@ -1,6 +1,6 @@
{
"name": "@d3ro/mobile-rn",
- "version": "1.3.3",
+ "version": "1.3.4",
"private": true,
"scripts": {
"android": "react-native run-android",
diff --git a/apps/web/package.json b/apps/web/package.json
index ba7e257..9d46c88 100644
--- a/apps/web/package.json
+++ b/apps/web/package.json
@@ -1,6 +1,6 @@
{
"name": "@d3ro/web",
- "version": "1.3.3",
+ "version": "1.3.4",
"private": true,
"description": "D3RO Voice 웹 앱 — Next.js 기반 SaaS 인터페이스",
"scripts": {
diff --git a/apps/web/src/components/layout/sidebar.tsx b/apps/web/src/components/layout/sidebar.tsx
index fdecc28..e593e0d 100644
--- a/apps/web/src/components/layout/sidebar.tsx
+++ b/apps/web/src/components/layout/sidebar.tsx
@@ -139,7 +139,7 @@ export function Sidebar(): React.ReactElement {
- v1.3.3
+ v1.3.4
diff --git a/bucket/d3ro-voice.json b/bucket/d3ro-voice.json
index cc74cce..f1eb5e9 100644
--- a/bucket/d3ro-voice.json
+++ b/bucket/d3ro-voice.json
@@ -1,15 +1,15 @@
{
- "version": "1.3.3",
+ "version": "1.3.4",
"description": "로컬 AI 음성 어시스턴트 (faster-whisper + Ollama, 100% 오프라인 지원)",
"homepage": "https://d3ro.chanpaca.net",
"license": "MIT",
"architecture": {
"64bit": {
"url": [
- "https://git.chanpaca.net/api/packages/yunchan/generic/d3ro-voice/portable-1.3.3/D3RO-Voice-1.3.3-x64-portable.7z.001"
+ "https://git.chanpaca.net/api/packages/yunchan/generic/d3ro-voice/portable-1.3.4/D3RO-Voice-1.3.4-x64-portable.7z.001"
],
"hash": [
- "b4bb847a7f25732cf0abf8516f6c708d6d9e6ab7d1fd813abf7795a0466c17b0"
+ "259e4803d3edbc1d4e6fd79ff8d034e75fb55f2ebac9e0f6459d0880531852e4"
]
}
},
@@ -27,7 +27,7 @@
"architecture": {
"64bit": {
"url": [
- "https://git.chanpaca.net/api/packages/yunchan/generic/d3ro-voice/portable-$version/D3RO-Voice-1.3.3-x64-portable.7z.001"
+ "https://git.chanpaca.net/api/packages/yunchan/generic/d3ro-voice/portable-$version/D3RO-Voice-1.3.4-x64-portable.7z.001"
]
}
}
diff --git a/docs/map/10-feature-catalog.md b/docs/map/10-feature-catalog.md
index de94cc0..20e1714 100644
--- a/docs/map/10-feature-catalog.md
+++ b/docs/map/10-feature-catalog.md
@@ -195,7 +195,7 @@ Status quick-reference: `[x]` done+verified · `[~]` partial/unverified · `[ ]`
| INFRA-16 | Desktop STT engine packaging | [x] | `apps/desktop/scripts/setup-sidecar.mjs` + `build-sidecar.mjs`, `electron-builder.yml` `extraResources` (`sidecar-dist/sidecar` → `resources/sidecar`, `resources/ffmpeg`), and `scripts/ci/verify-sidecar-bundle.mjs` run in `package-windows`/`package-macos` before electron-builder. Verified on the real bundle: `sidecar.exe` + `_internal` including `faster_whisper/assets/silero_vad_v6.onnx`, plus a packaged-engine transcription round-trip on GPU. |
| INFRA-17 | 서명 없는 배포 채널 (portable + Scoop) | [x] | `scripts/ci/build-portable.mjs` (95MiB 7z 분할 볼륨 + Scoop 매니페스트), `scripts/ci/publish-portable-release.mjs`, `scripts/local/install-d3ro-voice.ps1`, `bucket/` 버킷, `.forgejo/workflows/portable.yml`; 7z 분할 볼륨(Scoop, 162MiB) + zip 분할 부품(수동 설치, 243MiB, 7-Zip 불필요); updater feed와 분리. 2026-09-18 `portable-1.3.1` 게시 + 실제 설치 검증. |
| INFRA-18 | 로컬 런타임 온디맨드 설치 | [x] | `RuntimeProvisioner`(부품 다운로드 + SHA-256 검증 + tar 해제, `%APPDATA%/d3ro-voice/runtime`), `POST runtime:ensure` / `runtime:progress` IPC, 설정 > STT 상태/내려받기 UI. 설치본에서 엔진/ffmpeg를 분리해 189MB → 90.6MiB, 업데이트 피드 게시 복구. 2026-09-18 실제 feed 통합 검증(엔진 94.4MiB/17초, ffmpeg 21.7MiB/5초). |
-| INFRA-19 | 네이티브 ABI 패키징 게이트 | [x] | `scripts/ci/verify-native-abi.mjs`(패키징된 `better_sqlite3.node`가 Electron ABI인지 호스트 Node 로드 거부로 판별) + `scripts/ci/fix-native-abi.mjs`(로컬 잠금 우회용 주입). GitLab/Forgejo/GitHub 패키징 단계에 검증 삽입. 2026-09-18: Node ABI 모듈이 설치본에 들어가 앱이 시작 즉시 죽은 사고의 재발 방지. |
+| INFRA-19 | 네이티브 ABI + updater 설정 게이트 | [x] | `scripts/ci/verify-native-abi.mjs`(패키징된 `better_sqlite3.node`가 Electron ABI인지 호스트 Node 로드 거부로 판별) + `scripts/ci/fix-native-abi.mjs`(로컬 잠금 우회용 주입). GitLab/Forgejo/GitHub 패키징 단계에 검증 삽입. 2026-09-18: Node ABI 모듈로 앱이 시작 즉시 죽은 사고 + 누락으로 자동 업데이트가 죽은 사고를 함께 방지(). |
---
diff --git a/package-lock.json b/package-lock.json
index 571d0db..e229380 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "d3ro-voice-monorepo",
- "version": "1.3.3",
+ "version": "1.3.4",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "d3ro-voice-monorepo",
- "version": "1.3.3",
+ "version": "1.3.4",
"license": "MIT",
"workspaces": [
"apps/desktop",
@@ -25,7 +25,7 @@
},
"apps/admin": {
"name": "@d3ro/admin",
- "version": "1.3.3",
+ "version": "1.3.4",
"dependencies": {
"@d3ro/api-client": "*",
"@d3ro/core": "*",
@@ -109,7 +109,7 @@
},
"apps/desktop": {
"name": "@d3ro/desktop",
- "version": "1.3.3",
+ "version": "1.3.4",
"license": "MIT",
"dependencies": {
"@d3ro/core": "*",
@@ -158,7 +158,7 @@
},
"apps/web": {
"name": "@d3ro/web",
- "version": "1.3.3",
+ "version": "1.3.4",
"dependencies": {
"@d3ro/api-client": "*",
"@d3ro/core": "*",
@@ -16861,7 +16861,7 @@
},
"packages/api-client": {
"name": "@d3ro/api-client",
- "version": "1.3.3",
+ "version": "1.3.4",
"license": "MIT",
"dependencies": {
"@d3ro/core": "*",
@@ -16878,7 +16878,7 @@
},
"packages/core": {
"name": "@d3ro/core",
- "version": "1.3.3",
+ "version": "1.3.4",
"license": "MIT",
"dependencies": {
"docx": "^9.6.1"
@@ -16889,7 +16889,7 @@
},
"packages/i18n": {
"name": "@d3ro/i18n",
- "version": "1.3.3",
+ "version": "1.3.4",
"license": "MIT",
"devDependencies": {
"@types/react": "^19.0.0"
@@ -16900,7 +16900,7 @@
},
"packages/ui": {
"name": "@d3ro/ui",
- "version": "1.3.3",
+ "version": "1.3.4",
"license": "MIT",
"dependencies": {
"@d3ro/core": "*"
@@ -16920,7 +16920,7 @@
},
"packages/ui-native": {
"name": "@d3ro/ui-native",
- "version": "1.3.3",
+ "version": "1.3.4",
"license": "MIT",
"devDependencies": {
"@types/react": "*"
diff --git a/package.json b/package.json
index 92c41de..c707f99 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "d3ro-voice-monorepo",
- "version": "1.3.3",
+ "version": "1.3.4",
"private": true,
"description": "D3RO Voice — 멀티플랫폼 AI 음성 어시스턴트 (Monorepo)",
"author": "D3RO",
diff --git a/packages/api-client/package.json b/packages/api-client/package.json
index 95675ee..cc4e37d 100644
--- a/packages/api-client/package.json
+++ b/packages/api-client/package.json
@@ -1,6 +1,6 @@
{
"name": "@d3ro/api-client",
- "version": "1.3.3",
+ "version": "1.3.4",
"private": true,
"description": "D3RO Voice API 클라이언트 — Supabase 래퍼 (web/desktop/mobile 공유)",
"license": "MIT",
diff --git a/packages/core/package.json b/packages/core/package.json
index 9c3d921..f754567 100644
--- a/packages/core/package.json
+++ b/packages/core/package.json
@@ -1,6 +1,6 @@
{
"name": "@d3ro/core",
- "version": "1.3.3",
+ "version": "1.3.4",
"private": true,
"description": "D3RO Voice 공유 비즈니스 로직 — 타입, 에러, IPC 채널, 상수, 유틸",
"license": "MIT",
diff --git a/packages/i18n/package.json b/packages/i18n/package.json
index d3e4b06..6cbd967 100644
--- a/packages/i18n/package.json
+++ b/packages/i18n/package.json
@@ -1,6 +1,6 @@
{
"name": "@d3ro/i18n",
- "version": "1.3.3",
+ "version": "1.3.4",
"private": true,
"description": "D3RO Voice 공유 i18n — 12개 locale, 타입 안전 키, Context, 포맷 유틸",
"license": "MIT",
diff --git a/packages/ui-native/package.json b/packages/ui-native/package.json
index ef44250..f5e10a9 100644
--- a/packages/ui-native/package.json
+++ b/packages/ui-native/package.json
@@ -1,6 +1,6 @@
{
"name": "@d3ro/ui-native",
- "version": "1.3.3",
+ "version": "1.3.4",
"private": true,
"description": "D3RO Voice React Native DS — MetalCard/PhosphorText/Led/WaveBars etc.",
"license": "MIT",
diff --git a/packages/ui/package.json b/packages/ui/package.json
index facc0d6..f61a54c 100644
--- a/packages/ui/package.json
+++ b/packages/ui/package.json
@@ -1,6 +1,6 @@
{
"name": "@d3ro/ui",
- "version": "1.3.3",
+ "version": "1.3.4",
"private": true,
"description": "D3RO Voice 공유 UI — 디자인 시스템 컴포넌트 + 테마 토큰 + CSS 변수 맵",
"license": "MIT",
diff --git a/release/product-version.json b/release/product-version.json
index aa03c00..c59625f 100644
--- a/release/product-version.json
+++ b/release/product-version.json
@@ -1,8 +1,8 @@
{
"schemaVersion": 1,
- "version": "1.3.3",
- "androidVersionCode": 1031003,
- "iosBuildNumber": 1031003,
+ "version": "1.3.4",
+ "androidVersionCode": 1031004,
+ "iosBuildNumber": 1031004,
"releaseDate": "2026-09-18",
"desktopLicensePublicKeyId": "5c52b765135ee2531c681b53cd4dc0e96fff8737f496c0b04ba8334fc81a887f"
}
diff --git a/scripts/ci/build-portable.mjs b/scripts/ci/build-portable.mjs
index c9dd870..91fa3c1 100644
--- a/scripts/ci/build-portable.mjs
+++ b/scripts/ci/build-portable.mjs
@@ -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)) {
diff --git a/scripts/ci/publish-updater-release.mjs b/scripts/ci/publish-updater-release.mjs
index 69ae3e8..fe72a08 100644
--- a/scripts/ci/publish-updater-release.mjs
+++ b/scripts/ci/publish-updater-release.mjs
@@ -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과 다릅니다.')
diff --git a/scripts/ci/write-app-update-yml.mjs b/scripts/ci/write-app-update-yml.mjs
new file mode 100644
index 0000000..0a8fe03
--- /dev/null
+++ b/scripts/ci/write-app-update-yml.mjs
@@ -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
+
+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())
\ No newline at end of file
diff --git a/site/package-lock.json b/site/package-lock.json
index 172e126..6257b11 100644
--- a/site/package-lock.json
+++ b/site/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "d3ro-voice-site",
- "version": "1.3.3",
+ "version": "1.3.4",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "d3ro-voice-site",
- "version": "1.3.3",
+ "version": "1.3.4",
"dependencies": {
"react": "^19.0.0",
"react-dom": "^19.0.0"
diff --git a/site/package.json b/site/package.json
index f557d13..80b6c44 100644
--- a/site/package.json
+++ b/site/package.json
@@ -1,7 +1,7 @@
{
"name": "d3ro-voice-site",
"private": true,
- "version": "1.3.3",
+ "version": "1.3.4",
"type": "module",
"scripts": {
"dev": "vite --port 5199 --host",