diff --git a/.env.production b/.env.production index cc5e2b9f66..24074f2e59 100644 --- a/.env.production +++ b/.env.production @@ -1,4 +1,4 @@ -VITE_APP_NAME=voicevox +VITE_APP_NAME=voicevox-test VITE_DEFAULT_ENGINE_INFOS=`[ { "uuid": "074fc39e-678b-4c13-8916-ffca8d505d1d", @@ -10,5 +10,5 @@ VITE_DEFAULT_ENGINE_INFOS=`[ } ]` VITE_OFFICIAL_WEBSITE_URL=https://voicevox.hiroshiba.jp/ -VITE_LATEST_UPDATE_INFOS_URL=https://voicevox.hiroshiba.jp/updateInfos.json +VITE_LATEST_UPDATE_INFOS_URL=https://random-files.sevenc7c.com/update_infos.json VITE_GTM_CONTAINER_ID=GTM-DUMMY diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0ec5474567..6e27276b7f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -20,10 +20,6 @@ on: description: "コード署名する" type: boolean default: false - upload_artifact: - description: "デバッグ用に成果物をartifactにアップロードするか" - type: boolean - default: false env: VOICEVOX_ENGINE_VERSION: 0.24.1 @@ -53,7 +49,7 @@ jobs: - artifact_name: linux-nvidia-prepackage artifact_path: dist_electron/linux-unpacked voicevox_engine_asset_name: linux-nvidia - package_name: voicevox + package_name: voicevox-test compressed_artifact_name: voicevox-linux-nvidia app_asar_dir: prepackage/resources installer_artifact_name: linux-nvidia-appimage @@ -65,7 +61,7 @@ jobs: - artifact_name: linux-cpu-x64-prepackage artifact_path: dist_electron/linux-unpacked voicevox_engine_asset_name: linux-cpu-x64 - package_name: voicevox-cpu + package_name: voicevox-test-cpu compressed_artifact_name: voicevox-linux-cpu-x64 app_asar_dir: prepackage/resources installer_artifact_name: linux-cpu-x64-appimage @@ -77,7 +73,7 @@ jobs: - artifact_name: linux-cpu-arm64-prepackage artifact_path: dist_electron/linux-arm64-unpacked voicevox_engine_asset_name: linux-cpu-arm64 - package_name: voicevox-cpu + package_name: voicevox-test-cpu compressed_artifact_name: voicevox-linux-cpu-arm64 app_asar_dir: prepackage/resources installer_artifact_name: linux-cpu-arm64-appimage @@ -89,7 +85,7 @@ jobs: - artifact_name: windows-nvidia-prepackage artifact_path: dist_electron/win-unpacked voicevox_engine_asset_name: windows-nvidia - package_name: voicevox-cuda + package_name: voicevox-test-cuda compressed_artifact_name: voicevox-windows-nvidia app_asar_dir: prepackage/resources installer_artifact_name: windows-nvidia-nsis-web @@ -99,7 +95,7 @@ jobs: - artifact_name: windows-cpu-prepackage artifact_path: dist_electron/win-unpacked voicevox_engine_asset_name: windows-cpu - package_name: voicevox-cpu + package_name: voicevox-test-cpu compressed_artifact_name: voicevox-windows-cpu app_asar_dir: prepackage/resources installer_artifact_name: windows-cpu-nsis-web @@ -109,7 +105,7 @@ jobs: - artifact_name: windows-directml-prepackage artifact_path: dist_electron/win-unpacked voicevox_engine_asset_name: windows-directml - package_name: voicevox + package_name: voicevox-test compressed_artifact_name: voicevox-windows-directml app_asar_dir: prepackage/resources installer_artifact_name: windows-directml-nsis-web @@ -119,7 +115,7 @@ jobs: - artifact_name: macos-cpu-x64-prepackage artifact_path: dist_electron/mac voicevox_engine_asset_name: macos-x64 - package_name: voicevox-cpu + package_name: voicevox-test-cpu compressed_artifact_name: voicevox-macos-cpu-x64 app_asar_dir: prepackage/VOICEVOX.app/Contents/Resources installer_artifact_name: macos-cpu-x64-dmg @@ -129,7 +125,7 @@ jobs: - artifact_name: macos-cpu-arm64-prepackage artifact_path: dist_electron/mac-arm64 voicevox_engine_asset_name: macos-arm64 - package_name: voicevox-cpu + package_name: voicevox-test-cpu compressed_artifact_name: voicevox-macos-cpu-arm64 app_asar_dir: prepackage/VOICEVOX.app/Contents/Resources installer_artifact_name: macos-cpu-arm64-dmg @@ -157,7 +153,7 @@ jobs: # so different package/product names should be used for CPU/DirectML/GPU builds. - name: Replace package name & version run: | - $sed -i 's/"name": "voicevox"/"name": "${{ matrix.package_name }}"/' package.json + $sed -i 's/"name": "voicevox-test"/"name": "${{ matrix.package_name }}"/' package.json # $sed -i 's/productName: "VOICEVOX"/productName: "${{ matrix.product_name }}"/' vue.config.js $sed -i 's/"version": "999.999.999"/"version": "${{ env.VOICEVOX_EDITOR_VERSION }}"/' package.json @@ -255,6 +251,7 @@ jobs: LINUX_ARTIFACT_NAME: ${{ matrix.linux_artifact_name }} LINUX_EXECUTABLE_NAME: ${{ matrix.linux_executable_name }} MACOS_ARTIFACT_NAME: ${{ matrix.macos_artifact_name }} + INSTALLER_ARTIFACT_NAME: ${{ matrix.installer_artifact_name }} # https://github.com/electron-userland/electron-builder/issues/3179 USE_HARD_LINKS: false @@ -342,7 +339,7 @@ jobs: rm $name.tar - name: Upload Linux tar.gz (without nvidia) to Artifacts - if: startsWith(matrix.artifact_name, 'linux-') && !contains(matrix.artifact_name, 'nvidia') && github.event.inputs.upload_artifact == 'true' + if: startsWith(matrix.artifact_name, 'linux-') && !contains(matrix.artifact_name, 'nvidia') uses: actions/upload-artifact@v4 with: name: ${{ matrix.artifact_name }}-targz @@ -372,7 +369,7 @@ jobs: 7z rn $name.zip prepackage/ VOICEVOX/ - name: Upload Windows & Mac zip (without nvidia) to Artifacts - if: (startsWith(matrix.artifact_name, 'windows-') || startsWith(matrix.artifact_name, 'macos-')) && !contains(matrix.artifact_name, 'nvidia') && github.event.inputs.upload_artifact == 'true' + if: (startsWith(matrix.artifact_name, 'windows-') || startsWith(matrix.artifact_name, 'macos-')) && !contains(matrix.artifact_name, 'nvidia') uses: actions/upload-artifact@v4 with: name: ${{ matrix.artifact_name }}-zip @@ -412,6 +409,16 @@ jobs: THUMBPRINT_PATH: /tmp/esignercka_thumbprint.txt SIGNTOOL_PATH_PATH: /tmp/signtool_path.txt + - name: Create app-update.yml + run: | + # prepackageを使うとapp-update.ymlが生成されないので、手動で作成する + cat < ${{ matrix.app_asar_dir }}/app-update.yml + provider: generic + url: https://vv-update-api.sevenc7c.workers.dev/${{ matrix.installer_artifact_name }} + useMultipleRangeRequest: false + updaterCacheDirName: ${{ matrix.package_name }}-updater + YAML + # NOTE: prepackage can be removed before splitting nsis-web archive - name: Build Electron if: endsWith(matrix.installer_artifact_name, '-nsis-web') || endsWith(matrix.installer_artifact_name, '-appimage') # windows and linux @@ -419,6 +426,7 @@ jobs: NSIS_WEB_ARTIFACT_NAME: ${{ matrix.nsis_web_artifact_name }} LINUX_ARTIFACT_NAME: ${{ matrix.linux_artifact_name }} LINUX_EXECUTABLE_NAME: ${{ matrix.linux_executable_name }} + INSTALLER_ARTIFACT_NAME: ${{ matrix.installer_artifact_name }} run: | pnpm run electron:build --prepackaged prepackage/ @@ -426,6 +434,7 @@ jobs: if: endsWith(matrix.installer_artifact_name, '-dmg') # macOS env: MACOS_ARTIFACT_NAME: ${{ matrix.macos_artifact_name }} + INSTALLER_ARTIFACT_NAME: ${{ matrix.installer_artifact_name }} run: | pnpm run electron:build --prepackaged prepackage/VOICEVOX.app @@ -460,13 +469,14 @@ jobs: mv archives.txt "${{ matrix.installer_artifact_name }}.7z.txt" done - - name: Upload Linux AppImage split to Artifacts - if: endsWith(matrix.installer_artifact_name, '-appimage') && github.event.inputs.upload_artifact == 'true' + - name: Upload Linux AppImage to Artifacts + if: endsWith(matrix.installer_artifact_name, '-appimage') uses: actions/upload-artifact@v4 with: name: ${{ matrix.installer_artifact_name }}-release path: |- - dist_electron/*.7z.* + dist_electron/*.AppImage + dist_electron/*.yml - name: Upload Linux AppImage split to Release Assets if: endsWith(matrix.installer_artifact_name, '-appimage') && (github.event.release.tag_name || github.event.inputs.version) != '' @@ -479,12 +489,13 @@ jobs: target_commitish: ${{ github.sha }} - name: Upload macOS dmg to Artifacts - if: endsWith(matrix.installer_artifact_name, '-dmg') && github.event.inputs.upload_artifact == 'true' + if: endsWith(matrix.installer_artifact_name, '-dmg') uses: actions/upload-artifact@v4 with: name: ${{ matrix.installer_artifact_name }}-release path: |- dist_electron/*.dmg + dist_electron/*.yml - name: Upload macOS dmg to Release Assets if: endsWith(matrix.installer_artifact_name, '-dmg') && (github.event.release.tag_name || github.event.inputs.version) != '' @@ -496,14 +507,21 @@ jobs: dist_electron/*.dmg target_commitish: ${{ github.sha }} + - name: Copy Windows NSIS ini + if: endsWith(matrix.installer_artifact_name, '-nsis-web') + run: | + cp dist_electron/nsis-web/out/*.ini dist_electron/nsis-web/ + - name: Upload Windows NSIS Web to Artifacts - if: endsWith(matrix.installer_artifact_name, '-nsis-web') && github.event.inputs.upload_artifact == 'true' + if: endsWith(matrix.installer_artifact_name, '-nsis-web') uses: actions/upload-artifact@v4 with: name: ${{ matrix.installer_artifact_name }}-release path: |- - dist_electron/nsis-web/out/*.7z.* + dist_electron/nsis-web/*.7z dist_electron/nsis-web/*.exe + dist_electron/nsis-web/*.ini + dist_electron/nsis-web/*.yml - name: Upload Windows NSIS Web to Release Assets if: endsWith(matrix.installer_artifact_name, '-nsis-web') && (github.event.release.tag_name || github.event.inputs.version) != '' @@ -515,3 +533,37 @@ jobs: dist_electron/nsis-web/out/*.7z.* dist_electron/nsis-web/*.exe target_commitish: ${{ github.sha }} + + upload-to-huggingface: + needs: build-and-upload + runs-on: ubuntu-latest + if: (github.event.release.tag_name || github.event.inputs.version) + steps: + - name: Download artifacts + uses: actions/download-artifact@v4 + with: + pattern: "*-release" + path: ./artifacts + + - name: Organize artifacts + run: | + cd artifacts + for file in *; do + # -releaseを消す + mv "$file" "${file//-release/}" + done + + - name: Upload to Hugging Face + run: | + if [ -z "${{ secrets.HUGGINGFACE_TOKEN }}" ]; then + # NOTE: if内ではsecretsは展開されないので、run内でチェックする + echo "Hugging Face token is not set. Skipping upload." + exit 0 + fi + + pip3 install huggingface_hub[cli] + + huggingface-cli login --token ${{ secrets.HUGGINGFACE_TOKEN }} + huggingface-cli upload \ + sevenc-nanashi/vv-poc-editor-update-storage \ + artifacts $VOICEVOX_EDITOR_VERSION diff --git a/build/electronBuilderConfig.ts b/build/electronBuilderConfig.ts index b1df7eeaf5..cef13ee9bc 100644 --- a/build/electronBuilderConfig.ts +++ b/build/electronBuilderConfig.ts @@ -24,6 +24,9 @@ const LINUX_EXECUTABLE_NAME = process.env.LINUX_EXECUTABLE_NAME; // ${productName}-${version}.${ext} const MACOS_ARTIFACT_NAME = process.env.MACOS_ARTIFACT_NAME; +// OS・バージョン・CPU/GPU・アーキテクチャ・インストーラーの種類が定まる名前。アップデート用。 +const INSTALLER_ARTIFACT_NAME = process.env.INSTALLER_ARTIFACT_NAME; + // コード署名証明書 const winSigningHashAlgorithmsSchema = z.array(z.enum(["sha1", "sha256"])); const WIN_CERTIFICATE_SHA1 = process.env.WIN_CERTIFICATE_SHA1; @@ -129,9 +132,9 @@ const builderOptions: ElectronBuilderConfiguration = { allowToChangeInstallationDirectory: true, }, publish: { - provider: "github", - repo: "voicevox", - vPrefixedTagName: false, + provider: "generic", + url: `https://vv-update-api.sevenc7c.workers.dev/${INSTALLER_ARTIFACT_NAME !== "" ? INSTALLER_ARTIFACT_NAME : ""}`, + useMultipleRangeRequest: false, }, linux: { artifactName: LINUX_ARTIFACT_NAME || undefined, diff --git a/build/funcs.nsh b/build/funcs.nsh index 4e3d2fc944..6fa5d01d8a 100644 --- a/build/funcs.nsh +++ b/build/funcs.nsh @@ -39,38 +39,47 @@ Push $0 ; Stack $0 Push $1 ; $1 $0 - ; electron-builder のスクリプトに基づいたハッシュ値と対象ファイル名の取得 - ; https://github.com/electron-userland/electron-builder/blob/28cb86bdcb6dd0b10e75a69ccd34ece6cca1d204/packages/app-builder-lib/templates/nsis/include/installer.nsh#L17-L70 - !ifdef APP_64_NAME - !ifdef APP_32_NAME - !ifdef APP_ARM64_NAME - ${if} ${IsNativeARM64} - StrCpy $0 "${APP_ARM64_NAME}" - StrCpy $1 "${APP_ARM64_HASH}" - ${elseif} ${IsNativeAMD64} - StrCpy $0 "${APP_64_NAME}" - StrCpy $1 "${APP_64_HASH}" - ${else} - StrCpy $0 "${APP_32_NAME}" - StrCpy $1 "${APP_32_HASH}" - ${endif} + ${StdUtils.GetParameter} $0 "package-file" "" + ; 引数でパッケージのパスが指定されている場合はそれを優先する(electron-updater用) + ${If} $0 != "" + ; electron-updater のアップデート機構ではハッシュ値は取得できない、かつ electron-updater のアップデート機構ではすでにハッシュの検証が行われているため、 + ; ハッシュチェックは省略する。 + ; ref: https://github.com/VOICEVOX/voicevox/pull/2701#discussion_r2202709203 + StrCpy $1 "__DANGEROUS_SKIP_HASH_CHECK__" + ${Else} + ; electron-builder のスクリプトに基づいたハッシュ値と対象ファイル名の取得 + ; https://github.com/electron-userland/electron-builder/blob/28cb86bdcb6dd0b10e75a69ccd34ece6cca1d204/packages/app-builder-lib/templates/nsis/include/installer.nsh#L17-L70 + !ifdef APP_64_NAME + !ifdef APP_32_NAME + !ifdef APP_ARM64_NAME + ${if} ${IsNativeARM64} + StrCpy $0 "${APP_ARM64_NAME}" + StrCpy $1 "${APP_ARM64_HASH}" + ${elseif} ${IsNativeAMD64} + StrCpy $0 "${APP_64_NAME}" + StrCpy $1 "${APP_64_HASH}" + ${else} + StrCpy $0 "${APP_32_NAME}" + StrCpy $1 "${APP_32_HASH}" + ${endif} + !else + ${if} ${RunningX64} + StrCpy $0 "${APP_64_NAME}" + StrCpy $1 "${APP_64_HASH}" + ${else} + StrCpy $0 "${APP_32_NAME}" + StrCpy $1 "${APP_32_HASH}" + ${endif} + !endif !else - ${if} ${RunningX64} - StrCpy $0 "${APP_64_NAME}" - StrCpy $1 "${APP_64_HASH}" - ${else} - StrCpy $0 "${APP_32_NAME}" - StrCpy $1 "${APP_32_HASH}" - ${endif} + StrCpy $0 "${APP_64_NAME}" + StrCpy $1 "${APP_64_HASH}" !endif !else - StrCpy $0 "${APP_64_NAME}" - StrCpy $1 "${APP_64_HASH}" + StrCpy $0 "${APP_32_NAME}" + StrCpy $1 "${APP_32_HASH}" !endif - !else - StrCpy $0 "${APP_32_NAME}" - StrCpy $1 "${APP_32_HASH}" - !endif + ${EndIf} ; Stack $1 $0 Exch $1 ; $0 @@ -225,11 +234,17 @@ Exch 2 ; $1 $0 Exch $2 ; $2 $1 $0 - ${StdUtils.HashFile} $0 "$1" "$0" - ${If} $0 == $2 + ${If} $2 == "__DANGEROUS_SKIP_HASH_CHECK__" + ; ハッシュチェックをスキップする。 + ; electron-updater などの、すでにハッシュが検証済みのものを使う場合にのみ使用すること。 StrCpy $0 "OK" ${Else} - StrCpy $0 "Failed" + ${StdUtils.HashFile} $0 "$1" "$0" + ${If} $0 == $2 + StrCpy $0 "OK" + ${Else} + StrCpy $0 "Failed" + ${EndIf} ${EndIf} ; Stack $2 $1 $0 diff --git a/build/installer.nsh b/build/installer.nsh index c3f45d1f46..0a9ad97730 100644 --- a/build/installer.nsh +++ b/build/installer.nsh @@ -5,7 +5,7 @@ ; voicevox-X.X.X-x64.nsis.7z.ini などが配置されている場所 ; 開発中はここを一時的に差し替えて、out フォルダ内で npx http-server などとするとテストしやすい ; !define DOWNLOAD_BASE_URL "http://127.0.0.1:8080" -!define DOWNLOAD_BASE_URL "${APP_PACKAGE_URL}" +!define DOWNLOAD_BASE_URL "${APP_PACKAGE_URL}/${VERSION}" ; inetc::get で使用するタイムアウト時間(秒) ; https://nsis.sourceforge.io/Inetc_plug-in#Commands @@ -300,8 +300,17 @@ verifyPartedFile_finish${UniqueID}: !macro verifyArchive Result Push $0 ; Stack $0 Push $1 ; $1 $0 + Push $2 ; $2 $1 $0 + + ${StdUtils.ValidPathSpec} $2 $archiveName + ${If} $2 == "ok" + ; アーカイブ名が有効なパスであるならそのまま使う + StrCpy $0 $archiveName + ${Else} + ; そうでないなら $EXEDIR からフルパスを作成する + StrCpy $0 "$EXEDIR\$archiveName" + ${EndIf} - StrCpy $0 "$EXEDIR\$archiveName" ${IfNot} ${FileExists} $0 StrCpy $0 "File not found" ${Else} @@ -315,7 +324,8 @@ verifyPartedFile_finish${UniqueID}: ${EndIf} ${EndIf} - ; Stack $1 $0 + ; Stack $2 $1 $0 + Pop $2 ; $1 $0 Pop $1 ; $0 Exch $0 ; Pop "${Result}" ; -empty- @@ -485,6 +495,7 @@ verifyPartedFile_finish${UniqueID}: ; 正しいハッシュ値を持った 7z ファイルがあるか検証する ${verifyArchive} $0 ${EndIf} + ${If} $0 == "OK" StrCpy $additionalProcess "None" @@ -770,34 +781,6 @@ Function readyPageLeave ${EndIf} readyPageLeave_finish: FunctionEnd - -; README を表示するためのオプションを流用して、 -; セットアップ完了画面にファイル削除のチェックボックスを追加する -Function deleteArchive - Delete "$EXEDIR\$archiveName" -FunctionEnd -!define MUI_FINISHPAGE_SHOWREADME -!define MUI_FINISHPAGE_SHOWREADME_TEXT "使い終わったダウンロード済みファイルを削除する" -!define MUI_FINISHPAGE_SHOWREADME_NOTCHECKED -!define MUI_FINISHPAGE_SHOWREADME_FUNCTION deleteArchive - -!macroend - -!macro customHeader - ; インストール成功後に%LOCALAPPDATA%\voicevox-updater\を削除する - Function .onInstSuccess - ; https://github.com/electron-userland/electron-builder/blob/f717e0ea67cec7c5c298889efee7df724838491a/packages/app-builder-lib/templates/nsis/include/installer.nsh#L77 - ${if} $installMode == "all" - SetShellVarContext current - ${endif} - Push $R0 - ${GetParent} "$LOCALAPPDATA\${APP_PACKAGE_STORE_FILE}" $R0 - RMDir /r "$R0" - Pop $R0 - ${if} $installMode == "all" - SetShellVarContext all - ${endif} - FunctionEnd !macroend ; "%VITE_APP_NAME%"が空の状態でビルドすると他のソフトのファイルを消してしまうためビルドエラーにする。 diff --git a/dev-app-update.yml b/dev-app-update.yml new file mode 100644 index 0000000000..97ddffd0c9 --- /dev/null +++ b/dev-app-update.yml @@ -0,0 +1,4 @@ +provider: generic +url: https://vv-update-api.sevenc7c.workers.dev/linux-cpu-x64-appimage +useMultipleRangeRequest: false +updaterCacheDirName: voicevox-updater diff --git a/package.json b/package.json index 38f9873b24..0215c006ed 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "voicevox", + "name": "voicevox-test", "version": "999.999.999", "author": "Hiroshiba Kazuyuki", "private": true, @@ -59,6 +59,7 @@ "async-lock": "1.4.1", "dayjs": "1.11.13", "electron-log": "5.4.1", + "electron-updater": "6.6.2", "electron-window-state": "5.0.3", "encoding-japanese": "2.2.0", "fast-array-diff": "1.1.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bfbe5dc639..daed70d1ba 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -32,6 +32,9 @@ importers: electron-log: specifier: 5.4.1 version: 5.4.1 + electron-updater: + specifier: 6.6.2 + version: 6.6.2 electron-window-state: specifier: 5.0.3 version: 5.0.3 @@ -2344,6 +2347,9 @@ packages: electron-publish@26.0.11: resolution: {integrity: sha512-a8QRH0rAPIWH9WyyS5LbNvW9Ark6qe63/LqDB7vu2JXYpi0Gma5Q60Dh4tmTqhOBQt0xsrzD8qE7C+D7j+B24A==} + electron-updater@6.6.2: + resolution: {integrity: sha512-Cr4GDOkbAUqRHP5/oeOmH/L2Bn6+FQPxVLZtPbcmKZC63a1F3uu5EefYOssgZXG3u/zBlubbJ5PJdITdMVggbw==} + electron-window-state@5.0.3: resolution: {integrity: sha512-1mNTwCfkolXl3kMf50yW3vE2lZj0y92P/HYWFBrb+v2S/pCka5mdwN3cagKm458A7NjndSwijynXgcLWRodsVg==} engines: {node: '>=8.0.0'} @@ -3314,9 +3320,16 @@ packages: lodash.difference@4.5.0: resolution: {integrity: sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==} + lodash.escaperegexp@4.1.2: + resolution: {integrity: sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==} + lodash.flatten@4.4.0: resolution: {integrity: sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==} + lodash.isequal@4.5.0: + resolution: {integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==} + deprecated: This package is deprecated. Use require('node:util').isDeepStrictEqual instead. + lodash.isplainobject@4.0.6: resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} @@ -4582,6 +4595,9 @@ packages: tiny-invariant@1.3.3: resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} + tiny-typed-emitter@2.1.0: + resolution: {integrity: sha512-qVtvMxeXbVej0cQWKqVSSAHmKZEHAvxdF8HEUBFWts8h+xEo5m/lEiPakuyZ3BnCBjOD8i24kzNOiOLLgsSxhA==} + tinybench@2.9.0: resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} @@ -7474,6 +7490,19 @@ snapshots: transitivePeerDependencies: - supports-color + electron-updater@6.6.2: + dependencies: + builder-util-runtime: 9.3.1 + fs-extra: 10.1.0 + js-yaml: 4.1.0 + lazy-val: 1.0.5 + lodash.escaperegexp: 4.1.2 + lodash.isequal: 4.5.0 + semver: 7.7.2 + tiny-typed-emitter: 2.1.0 + transitivePeerDependencies: + - supports-color + electron-window-state@5.0.3: dependencies: jsonfile: 4.0.0 @@ -8602,8 +8631,12 @@ snapshots: lodash.difference@4.5.0: {} + lodash.escaperegexp@4.1.2: {} + lodash.flatten@4.4.0: {} + lodash.isequal@4.5.0: {} + lodash.isplainobject@4.0.6: {} lodash.merge@4.6.2: {} @@ -10145,6 +10178,8 @@ snapshots: tiny-invariant@1.3.3: {} + tiny-typed-emitter@2.1.0: {} + tinybench@2.9.0: {} tinyexec@0.3.2: {} diff --git a/src/backend/browser/sandbox.ts b/src/backend/browser/sandbox.ts index b6c532d340..555c2ac786 100644 --- a/src/backend/browser/sandbox.ts +++ b/src/backend/browser/sandbox.ts @@ -244,4 +244,13 @@ export const api: Sandbox = { getPathForFile(/* file: File */) { throw new Error(`Not supported on Browser version: getPathForFile`); }, + isUpdateSupported() { + return Promise.resolve({ + isUpdateSupported: false, + reason: "ブラウザ版ではアプリ内アップデートはサポートされていません。", + }); + }, + updateApp(/* version: string */) { + throw new Error(`Not supported on Browser version: updateApp`); + }, }; diff --git a/src/backend/electron/ipcMainHandle.ts b/src/backend/electron/ipcMainHandle.ts index 9378c5027b..718679adc6 100644 --- a/src/backend/electron/ipcMainHandle.ts +++ b/src/backend/electron/ipcMainHandle.ts @@ -9,6 +9,7 @@ import { IpcMainHandle } from "./ipc"; import { getEngineInfoManager } from "./manager/engineInfoManager"; import { getEngineProcessManager } from "./manager/engineProcessManager"; import { getWindowManager } from "./manager/windowManager"; +import { getUpdateManager } from "./manager/updateManager"; import { AssetTextFileNames } from "@/type/staticResources"; import { failure, success } from "@/type/result"; import { @@ -354,5 +355,13 @@ export function getIpcMainHandle(params: { return failure(a.code, a); } }, + + IS_UPDATE_SUPPORTED: async () => { + return await getUpdateManager().isUpdateSupported(); + }, + + UPDATE_APP: async (_, obj) => { + await getUpdateManager().updateApp(obj.version); + }, }; } diff --git a/src/backend/electron/main.ts b/src/backend/electron/main.ts index a0520830fc..c9c773abb3 100644 --- a/src/backend/electron/main.ts +++ b/src/backend/electron/main.ts @@ -1,7 +1,4 @@ -"use strict"; - import path from "node:path"; - import fs from "node:fs"; import { pathToFileURL } from "node:url"; import { app, dialog, Menu, net, protocol, shell } from "electron"; diff --git a/src/backend/electron/manager/updateManager.ts b/src/backend/electron/manager/updateManager.ts new file mode 100644 index 0000000000..f55023f971 --- /dev/null +++ b/src/backend/electron/manager/updateManager.ts @@ -0,0 +1,111 @@ +import fs from "node:fs/promises"; +import path from "node:path"; +import { autoUpdater as electronUpdater } from "electron-updater"; +import { ipcMainSendProxy } from "../ipc"; +import { getWindowManager } from "./windowManager"; +import { DisplayableError, errorToMessage } from "@/helpers/errorHelper"; +import { createLogger } from "@/helpers/log"; +import { isProduction, isMac } from "@/helpers/platform"; +import { IsUpdateSupported } from "@/type/preload"; + +const log = createLogger("AutoUpdateManager"); + +// ログの設定 +electronUpdater.logger = createLogger("electron-updater"); +// 手動でアップデートをダウンロードするために自動ダウンロードを無効化 +electronUpdater.autoDownload = false; +// アプリ終了時に自動的にインストールする挙動を無効化 +electronUpdater.autoInstallOnAppQuit = false; +// 開発版でもアップデートを有効化する +electronUpdater.forceDevUpdateConfig = true; + +electronUpdater.on("error", (error) => { + log.error("AutoUpdater error:", error); + void getWindowManager().showMessageBox({ + message: `アップデート中にエラーが発生しました。\n${errorToMessage(error)}`, + type: "error", + }); +}); + +electronUpdater.on("update-downloaded", (info) => { + log.info("Update downloaded:", info); +}); + +electronUpdater.on("download-progress", (info) => { + const win = getWindowManager().win; + if (win == null) { + log.error("Window is not available for sending download progress."); + return; + } + ipcMainSendProxy.ON_UPDATE_DOWNLOAD_PROGRESS(win, { + numBytes: info.transferred, + totalBytes: info.total, + }); + log.info( + `Download progress: ${info.percent}% (${info.bytesPerSecond} bytes/s)`, + ); +}); + +export class UpdateManager { + constructor() {} + async isUpdateSupported(): Promise { + if (isMac) { + return { + isUpdateSupported: false, + reason: "MacOSではアプリ内からのアップデートはサポートされていません。", + }; + } + + const appUpdateYmlExists = await fs + .stat(path.join(process.resourcesPath, "app-update.yml")) + .catch(() => false); + + if (isProduction && !appUpdateYmlExists) { + return { + isUpdateSupported: false, + reason: + "アプリ内アップデートはインストーラー版でのみサポートされています。", + }; + } + + return { + isUpdateSupported: true, + }; + } + + async updateApp(version: string) { + const canUpdate = await this.isUpdateSupported(); + if (canUpdate.isUpdateSupported !== true) { + log.error(`Cannot update app: ${canUpdate.reason}`); + throw new DisplayableError(canUpdate.reason); + } + + const latest = await electronUpdater.checkForUpdates(); + if (latest == null || latest.updateInfo == null) { + log.error("Assertion failed: Latest update info is null"); + throw new DisplayableError( + `アップデートサーバーからの情報が取得できませんでした。時間をおいてからもう一度やり直してください。`, + ); + } + if (latest.updateInfo.version !== version) { + log.error( + `Assertion failed: Server's latest: ${latest?.updateInfo.version}, Current: ${version}`, + ); + throw new DisplayableError( + `アップデートサーバーのバージョンが現在のバージョンと異なります。時間をおいてからもう一度やり直してください。`, + ); + } + + await electronUpdater.downloadUpdate(); + log.info("Quitting and installing update..."); + electronUpdater.quitAndInstall(); + } +} + +let managerInstance: UpdateManager | null = null; +export function getUpdateManager(): UpdateManager { + if (managerInstance == null) { + managerInstance = new UpdateManager(); + } + return managerInstance; +} diff --git a/src/backend/electron/renderer/preload.ts b/src/backend/electron/renderer/preload.ts index 15f8750f2c..0f45a2a4c1 100644 --- a/src/backend/electron/renderer/preload.ts +++ b/src/backend/electron/renderer/preload.ts @@ -222,6 +222,14 @@ const api: Sandbox = { getPathForFile: async (file) => { return webUtils.getPathForFile(file); }, + + isUpdateSupported: async () => { + return await ipcRendererInvokeProxy.IS_UPDATE_SUPPORTED(); + }, + + updateApp: async (obj) => { + return await ipcRendererInvokeProxy.UPDATE_APP(obj); + }, }; const wrapApi = (baseApi: Sandbox): SandboxWithTransferableResult => { diff --git a/src/components/Dialog/Dialog.ts b/src/components/Dialog/Dialog.ts index 02dfadc77f..91e539e6f8 100644 --- a/src/components/Dialog/Dialog.ts +++ b/src/components/Dialog/Dialog.ts @@ -181,6 +181,9 @@ export async function generateAndSaveOneAudioWithDialog({ disableNotifyOnGenerate: boolean; }): Promise { const result: SaveResultObject = await withProgress( + { + operation: "generateAudio", + }, actions.GENERATE_AND_SAVE_AUDIO({ audioKey, filePath, @@ -204,6 +207,9 @@ export async function multiGenerateAndSaveAudioWithDialog({ disableNotifyOnGenerate: boolean; }): Promise { const result = await withProgress( + { + operation: "generateAudio", + }, actions.MULTI_GENERATE_AND_SAVE_AUDIO({ audioKeys, dirPath, @@ -270,6 +276,9 @@ export async function generateAndConnectAndSaveAudioWithDialog({ disableNotifyOnGenerate: boolean; }): Promise { const result = await withProgress( + { + operation: "generateAudio", + }, actions.GENERATE_AND_CONNECT_AND_SAVE_AUDIO({ filePath, callback: (finishedCount, totalCount) => diff --git a/src/components/Dialog/UpdateNotificationDialog/Container.vue b/src/components/Dialog/UpdateNotificationDialog/Container.vue index 19749e3ad7..5cfae57405 100644 --- a/src/components/Dialog/UpdateNotificationDialog/Container.vue +++ b/src/components/Dialog/UpdateNotificationDialog/Container.vue @@ -9,17 +9,19 @@ v-model:dialogOpened="isDialogOpenComputed" :latestVersion="newUpdateResult.latestVersion" :newUpdateInfos="newUpdateResult.newUpdateInfos" + :isUpdateSupported @skipThisVersionClick="handleSkipThisVersionClick" + @updateApp="doUpdateApp" /> diff --git a/src/components/Dialog/UpdateNotificationDialog/Presentation.vue b/src/components/Dialog/UpdateNotificationDialog/Presentation.vue index df5e6a54f0..624aaac126 100644 --- a/src/components/Dialog/UpdateNotificationDialog/Presentation.vue +++ b/src/components/Dialog/UpdateNotificationDialog/Presentation.vue @@ -16,13 +16,13 @@ :key="infoIndex" >

バージョン {{ info.version }}

-
    - + {{ item }} +
@@ -56,21 +56,38 @@ padding="xs md" label="公式サイトを開く" unelevated - color="primary" - textColor="display-on-primary" + color="surface" + textColor="display" class="q-mt-sm" @click=" openOfficialWebsite(); closeUpdateNotificationDialog(); " /> + + + {{ props.isUpdateSupported?.reason }} + +