diff --git a/package.json b/package.json index 2e1510c..d1548d0 100644 --- a/package.json +++ b/package.json @@ -53,6 +53,7 @@ "electron-vite": "^2.3.0", "eslint": "^8.57.1", "eslint-plugin-vue": "^9.32.0", + "extract-zip": "^2.0.1", "locales": "workspace:*", "lodash-es": "^4.17.21", "monaco-editor": "^0.52.2", diff --git a/packages/locales/languages/zh.json b/packages/locales/languages/zh.json index 70a2315..b57fa33 100644 --- a/packages/locales/languages/zh.json +++ b/packages/locales/languages/zh.json @@ -1,6 +1,12 @@ { "title": "aaaaaa2 {name} bbbb", "update": { + "ready": { + "hot": { + "title": "提示", + "desc": "新版本 v{version} 已经准备好更新, 下次启动程序即可自动更新" + } + }, "status": { "IDLE": "检查更新", "InitCheckingUpdate": "初始化检查更新", diff --git a/packages/locales/main.ts b/packages/locales/main.ts index 010cd8b..d217258 100644 --- a/packages/locales/main.ts +++ b/packages/locales/main.ts @@ -15,7 +15,7 @@ type FlattenKeys = FlattenObject type TranslationKey = FlattenKeys class Locale { - locale: string = "en" + locale: string = "zh" constructor() { try { @@ -29,8 +29,22 @@ class Locale { return this.locale.startsWith("zh") } - t(key: TranslationKey): string { - return this.isCN() ? get(zh, key) : get(en, key) + t(key: TranslationKey, replacements?: Record): string { + let text: string = this.isCN() ? get(zh, key) : get(en, key) + if (!text) { + text = get(zh, key) + if (!text) { + return key + } + } + if (replacements) { + // 替换所有形如 {key} 的占位符 + Object.entries(replacements).forEach(([key, value]) => { + console.log(text) + text = text.replace(new RegExp(`{${key}}`, "g"), value) + }) + } + return text } } diff --git a/packages/locales/package.json b/packages/locales/package.json index 45d79a8..8c468fc 100644 --- a/packages/locales/package.json +++ b/packages/locales/package.json @@ -1,11 +1,6 @@ { "name": "locales", "version": "1.0.0", - "description": "", - "main": "index.js", - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" - }, "keywords": [], "author": "", "license": "ISC" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 482c71c..99d90c4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,15 +14,6 @@ importers: '@electron-toolkit/utils': specifier: ^3.0.0 version: 3.0.0(electron@31.7.7) - '@types/debug': - specifier: ^4.1.12 - version: 4.1.12 - '@unocss/reset': - specifier: ^0.64.1 - version: 0.64.1 - '@vueuse/core': - specifier: ^12.7.0 - version: 12.7.0(typescript@5.7.3) electron-updater: specifier: ^6.3.9 version: 6.3.9 @@ -35,24 +26,6 @@ importers: reflect-metadata: specifier: ^0.2.2 version: 0.2.2 - sass: - specifier: ^1.85.0 - version: 1.85.0 - unplugin-auto-import: - specifier: ^19.1.0 - version: 19.1.0(@vueuse/core@12.7.0(typescript@5.7.3)) - unplugin-vue-components: - specifier: ^28.4.0 - version: 28.4.0(@babel/parser@7.26.9)(vue@3.5.13(typescript@5.7.3)) - unplugin-vue-macros: - specifier: ^2.14.2 - version: 2.14.2(@vueuse/core@12.7.0(typescript@5.7.3))(esbuild@0.23.1)(rollup@4.26.0)(typescript@5.7.3)(vite@5.4.14(@types/node@20.17.19)(sass@1.85.0))(vue-tsc@2.1.10(typescript@5.7.3))(vue@3.5.13(typescript@5.7.3)) - unplugin-vue-router: - specifier: ^0.11.2 - version: 0.11.2(rollup@4.26.0)(vue-router@4.5.0(vue@3.5.13(typescript@5.7.3)))(vue@3.5.13(typescript@5.7.3)) - vue-router: - specifier: ^4.5.0 - version: 4.5.0(vue@3.5.13(typescript@5.7.3)) devDependencies: '@electron-toolkit/eslint-config': specifier: ^1.0.2 @@ -69,12 +42,18 @@ importers: '@rushstack/eslint-patch': specifier: ^1.10.5 version: 1.10.5 + '@types/debug': + specifier: ^4.1.12 + version: 4.1.12 '@types/node': specifier: ^20.17.19 version: 20.17.19 '@unocss/preset-rem-to-px': specifier: ^0.64.1 version: 0.64.1 + '@unocss/reset': + specifier: ^0.64.1 + version: 0.64.1 '@vitejs/plugin-vue': specifier: ^5.2.1 version: 5.2.1(vite@5.4.14(@types/node@20.17.19)(sass@1.85.0))(vue@3.5.13(typescript@5.7.3)) @@ -87,6 +66,9 @@ importers: '@vue/eslint-config-typescript': specifier: ^13.0.0 version: 13.0.0(eslint-plugin-vue@9.32.0(eslint@8.57.1))(eslint@8.57.1)(typescript@5.7.3) + '@vueuse/core': + specifier: ^12.7.0 + version: 12.7.0(typescript@5.7.3) debug: specifier: ^4.4.0 version: 4.4.0 @@ -105,6 +87,9 @@ importers: eslint-plugin-vue: specifier: ^9.32.0 version: 9.32.0(eslint@8.57.1) + extract-zip: + specifier: ^2.0.1 + version: 2.0.1 locales: specifier: workspace:* version: link:packages/locales @@ -120,6 +105,9 @@ importers: rotating-file-stream: specifier: ^3.2.6 version: 3.2.6 + sass: + specifier: ^1.85.0 + version: 1.85.0 simplebar-vue: specifier: ^2.4.0 version: 2.4.0(vue@3.5.13(typescript@5.7.3)) @@ -129,6 +117,18 @@ importers: unocss: specifier: ^0.64.1 version: 0.64.1(postcss@8.4.49)(rollup@4.26.0)(vite@5.4.14(@types/node@20.17.19)(sass@1.85.0))(vue@3.5.13(typescript@5.7.3)) + unplugin-auto-import: + specifier: ^19.1.0 + version: 19.1.0(@vueuse/core@12.7.0(typescript@5.7.3)) + unplugin-vue-components: + specifier: ^28.4.0 + version: 28.4.0(@babel/parser@7.26.9)(vue@3.5.13(typescript@5.7.3)) + unplugin-vue-macros: + specifier: ^2.14.2 + version: 2.14.2(@vueuse/core@12.7.0(typescript@5.7.3))(esbuild@0.23.1)(rollup@4.26.0)(typescript@5.7.3)(vite@5.4.14(@types/node@20.17.19)(sass@1.85.0))(vue-tsc@2.1.10(typescript@5.7.3))(vue@3.5.13(typescript@5.7.3)) + unplugin-vue-router: + specifier: ^0.11.2 + version: 0.11.2(rollup@4.26.0)(vue-router@4.5.0(vue@3.5.13(typescript@5.7.3)))(vue@3.5.13(typescript@5.7.3)) vite: specifier: ^5.4.14 version: 5.4.14(@types/node@20.17.19)(sass@1.85.0) @@ -144,6 +144,9 @@ importers: vue-i18n: specifier: ^11.1.1 version: 11.1.1(vue@3.5.13(typescript@5.7.3)) + vue-router: + specifier: ^4.5.0 + version: 4.5.0(vue@3.5.13(typescript@5.7.3)) vue-tsc: specifier: ^2.1.10 version: 2.1.10(typescript@5.7.3) @@ -711,22 +714,26 @@ packages: resolution: {integrity: sha512-bb8gZvoeKExCI2r/NVCK9E4YyOkvYGaSCPxVZe8T0jz8aX+dHEOZWxK06Z/Y9mWRkJfBiCH4aOhDF1yr1t5J8Q==} engines: {node: '>= 16'} - '@intlify/message-compiler@11.0.0-rc.1': - resolution: {integrity: sha512-TGw2uBfuTFTegZf/BHtUQBEKxl7Q/dVGLoqRIdw8lFsp9g/53sYn5iD+0HxIzdYjbWL6BTJMXCPUHp9PxDTRPw==} - engines: {node: '>= 16'} - '@intlify/message-compiler@11.1.1': resolution: {integrity: sha512-4iEsUZ3aF7jXY19CJFN5VP+pPyLITD9FVsjB13z9TU1UxaZLlFsmNhvRxlPDSOfHAP5RpNF2QKKdZ3DHVf4Yzw==} engines: {node: '>= 16'} - '@intlify/shared@11.0.0-rc.1': - resolution: {integrity: sha512-8tR1xe7ZEbkabTuE/tNhzpolygUn9OaYp9yuYAF4MgDNZg06C3Qny80bes2/e9/Wm3aVkPUlCw6WgU7mQd0yEg==} + '@intlify/message-compiler@12.0.0-alpha.2': + resolution: {integrity: sha512-PD9C+oQbb7BF52hec0+vLnScaFkvnfX+R7zSbODYuRo/E2niAtGmHd0wPvEMsDhf9Z9b8f/qyDsVeZnD/ya9Ug==} engines: {node: '>= 16'} '@intlify/shared@11.1.1': resolution: {integrity: sha512-2kGiWoXaeV8HZlhU/Nml12oTbhv7j2ufsJ5vQaa0VTjzUmZVdd/nmKFRAOJ/FtjO90Qba5AnZDwsrY7ZND5udA==} engines: {node: '>= 16'} + '@intlify/shared@11.1.2': + resolution: {integrity: sha512-dF2iMMy8P9uKVHV/20LA1ulFLL+MKSbfMiixSmn6fpwqzvix38OIc7ebgnFbBqElvghZCW9ACtzKTGKsTGTWGA==} + engines: {node: '>= 16'} + + '@intlify/shared@12.0.0-alpha.2': + resolution: {integrity: sha512-P2DULVX9nz3y8zKNqLw9Es1aAgQ1JGC+kgpx5q7yLmrnAKkPR5MybQWoEhxanefNJgUY5ehsgo+GKif59SrncA==} + engines: {node: '>= 16'} + '@intlify/unplugin-vue-i18n@6.0.3': resolution: {integrity: sha512-9ZDjBlhUHtgjRl23TVcgfJttgu8cNepwVhWvOv3mUMRDAhjW0pur1mWKEUKr1I8PNwE4Gvv2IQ1xcl4RL0nG0g==} engines: {node: '>= 18'} @@ -838,25 +845,21 @@ packages: resolution: {integrity: sha512-eEwxY+0Cf76HnQwr1+Qy48qwf4dAebTHaKhzEgxLqLK6szbglnK6SThjY95YHrYWwsH1GujWiFoX51jwZNYfSw==} cpu: [arm64] os: [linux] - libc: [glibc] '@oxc-resolver/binding-linux-arm64-musl@3.0.3': resolution: {integrity: sha512-LdxbLv8qVkzro4/ZoP9MuytIL6NOVsbhoZ5Wl1KXOa/2DSxBiksrAPMSChCTyeLy6P3ebSHxQSb52ku18t1LBA==} cpu: [arm64] os: [linux] - libc: [musl] '@oxc-resolver/binding-linux-x64-gnu@3.0.3': resolution: {integrity: sha512-bN8elR9AV/DZZPdcteOWWElkz8KyxLtOvmfVl7Dnehcs6f9e+fWYKyqiKvva1jsxG4znGKCPT1gfMhpYW8QuKg==} cpu: [x64] os: [linux] - libc: [glibc] '@oxc-resolver/binding-linux-x64-musl@3.0.3': resolution: {integrity: sha512-Zy1U49BjriwbAds2ho6CGjZIk2KVn0+lrc/G5bvhQg7UJYxEkAueMGBuA5rULIhx9xVtIPsT9Q+J5Xhb4ffVNw==} cpu: [x64] os: [linux] - libc: [musl] '@oxc-resolver/binding-wasm32-wasi@3.0.3': resolution: {integrity: sha512-7rteQnn7i5f9nkFZs1VRdBqFhvOx3zWavyKkWjXYVxc9vsSLTg0moh2MRZw5dw5m/bEi1u/p3YAKJ9gdHyBhNQ==} @@ -902,42 +905,36 @@ packages: engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] - libc: [glibc] '@parcel/watcher-linux-arm-musl@2.5.0': resolution: {integrity: sha512-6uHywSIzz8+vi2lAzFeltnYbdHsDm3iIB57d4g5oaB9vKwjb6N6dRIgZMujw4nm5r6v9/BQH0noq6DzHrqr2pA==} engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] - libc: [musl] '@parcel/watcher-linux-arm64-glibc@2.5.0': resolution: {integrity: sha512-BfNjXwZKxBy4WibDb/LDCriWSKLz+jJRL3cM/DllnHH5QUyoiUNEp3GmL80ZqxeumoADfCCP19+qiYiC8gUBjA==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] - libc: [glibc] '@parcel/watcher-linux-arm64-musl@2.5.0': resolution: {integrity: sha512-S1qARKOphxfiBEkwLUbHjCY9BWPdWnW9j7f7Hb2jPplu8UZ3nes7zpPOW9bkLbHRvWM0WDTsjdOTUgW0xLBN1Q==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] - libc: [musl] '@parcel/watcher-linux-x64-glibc@2.5.0': resolution: {integrity: sha512-d9AOkusyXARkFD66S6zlGXyzx5RvY+chTP9Jp0ypSTC9d4lzyRs9ovGf/80VCxjKddcUvnsGwCHWuF2EoPgWjw==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] - libc: [glibc] '@parcel/watcher-linux-x64-musl@2.5.0': resolution: {integrity: sha512-iqOC+GoTDoFyk/VYSFHwjHhYrk8bljW6zOhPuhi5t9ulqiYq1togGJB5e3PwYVFFfeVgc6pbz3JdQyDoBszVaA==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] - libc: [musl] '@parcel/watcher-win32-arm64@2.5.0': resolution: {integrity: sha512-twtft1d+JRNkM5YbmexfcH/N4znDtjgysFaV9zvZmmJezQsKpkfLYJ+JFV3uygugK6AtIM2oADPkB2AdhBrNig==} @@ -1024,55 +1021,46 @@ packages: resolution: {integrity: sha512-paHF1bMXKDuizaMODm2bBTjRiHxESWiIyIdMugKeLnjuS1TCS54MF5+Y5Dx8Ui/1RBPVRE09i5OUlaLnv8OGnA==} cpu: [arm] os: [linux] - libc: [glibc] '@rollup/rollup-linux-arm-musleabihf@4.26.0': resolution: {integrity: sha512-cwxiHZU1GAs+TMxvgPfUDtVZjdBdTsQwVnNlzRXC5QzIJ6nhfB4I1ahKoe9yPmoaA/Vhf7m9dB1chGPpDRdGXg==} cpu: [arm] os: [linux] - libc: [musl] '@rollup/rollup-linux-arm64-gnu@4.26.0': resolution: {integrity: sha512-4daeEUQutGRCW/9zEo8JtdAgtJ1q2g5oHaoQaZbMSKaIWKDQwQ3Yx0/3jJNmpzrsScIPtx/V+1AfibLisb3AMQ==} cpu: [arm64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-arm64-musl@4.26.0': resolution: {integrity: sha512-eGkX7zzkNxvvS05ROzJ/cO/AKqNvR/7t1jA3VZDi2vRniLKwAWxUr85fH3NsvtxU5vnUUKFHKh8flIBdlo2b3Q==} cpu: [arm64] os: [linux] - libc: [musl] '@rollup/rollup-linux-powerpc64le-gnu@4.26.0': resolution: {integrity: sha512-Odp/lgHbW/mAqw/pU21goo5ruWsytP7/HCC/liOt0zcGG0llYWKrd10k9Fj0pdj3prQ63N5yQLCLiE7HTX+MYw==} cpu: [ppc64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-riscv64-gnu@4.26.0': resolution: {integrity: sha512-MBR2ZhCTzUgVD0OJdTzNeF4+zsVogIR1U/FsyuFerwcqjZGvg2nYe24SAHp8O5sN8ZkRVbHwlYeHqcSQ8tcYew==} cpu: [riscv64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-s390x-gnu@4.26.0': resolution: {integrity: sha512-YYcg8MkbN17fMbRMZuxwmxWqsmQufh3ZJFxFGoHjrE7bv0X+T6l3glcdzd7IKLiwhT+PZOJCblpnNlz1/C3kGQ==} cpu: [s390x] os: [linux] - libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.26.0': resolution: {integrity: sha512-ZuwpfjCwjPkAOxpjAEjabg6LRSfL7cAJb6gSQGZYjGhadlzKKywDkCUnJ+KEfrNY1jH5EEoSIKLCb572jSiglA==} cpu: [x64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-x64-musl@4.26.0': resolution: {integrity: sha512-+HJD2lFS86qkeF8kNu0kALtifMpPCZU80HvwztIKnYwym3KnA1os6nsX4BGSTLtS2QVAGG1P3guRgsYyMA0Yhg==} cpu: [x64] os: [linux] - libc: [musl] '@rollup/rollup-win32-arm64-msvc@4.26.0': resolution: {integrity: sha512-WUQzVFWPSw2uJzX4j6YEbMAiLbs0BUysgysh8s817doAYhR5ybqTI1wtKARQKo6cGop3pHnrUJPFCsXdoFaimQ==} @@ -4016,8 +4004,8 @@ snapshots: '@intlify/bundle-utils@10.0.0(vue-i18n@11.1.1(vue@3.5.13(typescript@5.7.3)))': dependencies: - '@intlify/message-compiler': 11.0.0-rc.1 - '@intlify/shared': 11.0.0-rc.1 + '@intlify/message-compiler': 12.0.0-alpha.2 + '@intlify/shared': 12.0.0-alpha.2 acorn: 8.14.0 escodegen: 2.1.0 estree-walker: 2.0.2 @@ -4033,26 +4021,28 @@ snapshots: '@intlify/message-compiler': 11.1.1 '@intlify/shared': 11.1.1 - '@intlify/message-compiler@11.0.0-rc.1': - dependencies: - '@intlify/shared': 11.0.0-rc.1 - source-map-js: 1.2.1 - '@intlify/message-compiler@11.1.1': dependencies: '@intlify/shared': 11.1.1 source-map-js: 1.2.1 - '@intlify/shared@11.0.0-rc.1': {} + '@intlify/message-compiler@12.0.0-alpha.2': + dependencies: + '@intlify/shared': 12.0.0-alpha.2 + source-map-js: 1.2.1 '@intlify/shared@11.1.1': {} + '@intlify/shared@11.1.2': {} + + '@intlify/shared@12.0.0-alpha.2': {} + '@intlify/unplugin-vue-i18n@6.0.3(@vue/compiler-dom@3.5.13)(eslint@8.57.1)(rollup@4.26.0)(typescript@5.7.3)(vue-i18n@11.1.1(vue@3.5.13(typescript@5.7.3)))(vue@3.5.13(typescript@5.7.3))': dependencies: '@eslint-community/eslint-utils': 4.4.1(eslint@8.57.1) '@intlify/bundle-utils': 10.0.0(vue-i18n@11.1.1(vue@3.5.13(typescript@5.7.3))) - '@intlify/shared': 11.1.1 - '@intlify/vue-i18n-extensions': 8.0.0(@intlify/shared@11.1.1)(@vue/compiler-dom@3.5.13)(vue-i18n@11.1.1(vue@3.5.13(typescript@5.7.3)))(vue@3.5.13(typescript@5.7.3)) + '@intlify/shared': 11.1.2 + '@intlify/vue-i18n-extensions': 8.0.0(@intlify/shared@11.1.2)(@vue/compiler-dom@3.5.13)(vue-i18n@11.1.1(vue@3.5.13(typescript@5.7.3)))(vue@3.5.13(typescript@5.7.3)) '@rollup/pluginutils': 5.1.4(rollup@4.26.0) '@typescript-eslint/scope-manager': 8.25.0 '@typescript-eslint/typescript-estree': 8.25.0(typescript@5.7.3) @@ -4074,11 +4064,11 @@ snapshots: - supports-color - typescript - '@intlify/vue-i18n-extensions@8.0.0(@intlify/shared@11.1.1)(@vue/compiler-dom@3.5.13)(vue-i18n@11.1.1(vue@3.5.13(typescript@5.7.3)))(vue@3.5.13(typescript@5.7.3))': + '@intlify/vue-i18n-extensions@8.0.0(@intlify/shared@11.1.2)(@vue/compiler-dom@3.5.13)(vue-i18n@11.1.1(vue@3.5.13(typescript@5.7.3)))(vue@3.5.13(typescript@5.7.3))': dependencies: '@babel/parser': 7.26.9 optionalDependencies: - '@intlify/shared': 11.1.1 + '@intlify/shared': 11.1.2 '@vue/compiler-dom': 3.5.13 vue: 3.5.13(typescript@5.7.3) vue-i18n: 11.1.1(vue@3.5.13(typescript@5.7.3)) diff --git a/src/common/event/common.ts b/src/common/event/common.ts index 4b75798..a90403b 100644 --- a/src/common/event/common.ts +++ b/src/common/event/common.ts @@ -1,4 +1,4 @@ -const keys = ["progress"] as const +const keys = ["hot-update-ready"] as const type AllKeys = (typeof keys)[number] diff --git a/src/common/event/update/main.ts b/src/common/event/update/main.ts index 2bd003c..5f40482 100644 --- a/src/common/event/update/main.ts +++ b/src/common/event/update/main.ts @@ -1,8 +1,8 @@ import { broadcast } from "main/utils" import { AllKeys } from "../common" -function emitProgress(...argu) { - broadcast("progress", ...argu) +function emitHotUpdateReady(...argu) { + broadcast("hot-update-ready", ...argu) } -export { emitProgress } +export { emitHotUpdateReady } diff --git a/src/main/commands/UpdateCommand.ts b/src/main/commands/UpdateCommand.ts new file mode 100644 index 0000000..d967bc7 --- /dev/null +++ b/src/main/commands/UpdateCommand.ts @@ -0,0 +1,10 @@ +import { inject } from "inversify" +import Updater from "main/modules/updater" + +export default class BasicCommand { + constructor(@inject(Updater) private _Updater: Updater) {} + + async triggerHotUpdate() { + await this._Updater.triggerHotUpdate() + } +} diff --git a/src/main/commands/_ioc.ts b/src/main/commands/_ioc.ts index 8542005..8c00409 100644 --- a/src/main/commands/_ioc.ts +++ b/src/main/commands/_ioc.ts @@ -1,10 +1,14 @@ import { Container, ContainerModule } from "inversify" import BasicCommand from "./BasicCommand" import TabsCommand from "./TabsCommand" +import UpdateCommand from "./UpdateCommand" + +// TODO 考虑迁移,将所有命令都注册common/event中 const modules = new ContainerModule(bind => { bind("BasicCommand").to(BasicCommand).inSingletonScope() bind("TabsCommand").to(TabsCommand).inSingletonScope() + bind("UpdateCommand").to(UpdateCommand).inSingletonScope() }) async function destroyAllCommand(ioc: Container) { diff --git a/src/main/modules/commands/index.ts b/src/main/modules/commands/index.ts index f0558ef..410934f 100644 --- a/src/main/modules/commands/index.ts +++ b/src/main/modules/commands/index.ts @@ -23,9 +23,9 @@ export default class Commands extends BaseClass { const run = await this._IOC.getAsync(splitClass[0]) if (run) { const result: Promise | any = run[splitClass[1]](...argus) - return result + return [true, result] } - return null + return [false] } public async invoke(command, ...argus) { @@ -37,8 +37,8 @@ export default class Commands extends BaseClass { ipcMain.addListener("command", async (event, key, command: string, ...argus) => { // console.log(event.sender); try { - const result = await this.handleCommand(command, ...argus) - if (result) { + const [isExist, result] = await this.handleCommand(command, ...argus) + if (isExist) { if (isPromise(result)) { result .then((res: any) => { diff --git a/src/main/modules/updater/hot/gen.ts b/src/main/modules/updater/hot/gen.ts deleted file mode 100644 index d3fd4d1..0000000 --- a/src/main/modules/updater/hot/gen.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { spawn } from "node:child_process" -import fs from "node:fs" -import path from "node:path" -import os from "node:os" -import { app } from "electron" - -function getUpdateScriptTemplate() { - return process.platform === "win32" - ? ` - @echo off - timeout /t 2 - taskkill /IM "{{EXE_NAME}}" /F - xcopy /Y /E "{{UPDATE_DIR}}\\*" "{{APP_PATH}}" - start "" "{{EXE_PATH}}" - ` - : ` - #!/bin/bash - sleep 2 - pkill -f "{{EXE_NAME}}" - cp -Rf "{{UPDATE_DIR}}/*" "{{APP_PATH}}/" - open "{{EXE_PATH}}" - ` -} - -function generateUpdateScript() { - const scriptContent = getUpdateScriptTemplate() - .replace(/{{APP_PATH}}/g, process.platform === "win32" ? "%APP_PATH%" : "$APP_PATH") - .replace(/{{UPDATE_DIR}}/g, process.platform === "win32" ? "%UPDATE_DIR%" : "$UPDATE_DIR") - .replace(/{{EXE_PATH}}/g, process.platform === "win32" ? "%EXE_PATH%" : "$EXE_PATH") - .replace(/{{EXE_NAME}}/g, process.platform === "win32" ? "%EXE_NAME%" : "$EXE_NAME") - - const scriptPath = path.join(os.tmpdir(), `update.${process.platform === "win32" ? "bat" : "sh"}`) - fs.writeFileSync(scriptPath, scriptContent) - return scriptPath -} - -app.on("will-quit", event => { - event.preventDefault() - - // 假设已下载更新到临时目录 - const updateTempDir = path.join(os.tmpdir(), "app-update") - const appPath = app.getAppPath() - const appExePath = process.execPath - - // 生成动态脚本 - const scriptPath = generateUpdateScript() - - fs.chmodSync(scriptPath, 0o755) - - // 执行脚本 - const child = spawn(scriptPath, [], { - detached: true, - shell: true, - env: { - APP_PATH: appPath, - UPDATE_DIR: updateTempDir, - EXE_PATH: appExePath, - }, - }) - child.unref() - app.exit() -}) diff --git a/src/main/modules/updater/hot/index.ts b/src/main/modules/updater/hot/index.ts new file mode 100644 index 0000000..f43a7e1 --- /dev/null +++ b/src/main/modules/updater/hot/index.ts @@ -0,0 +1,118 @@ +import { spawn } from "node:child_process" +import fs from "node:fs" +import path from "node:path" +import os from "node:os" +import { app } from "electron" +import extract from "extract-zip" +import { emitHotUpdateReady } from "common/event/Update/main" + +import _debug from "debug" +const debug = _debug("app:hot-updater") + +function getUpdateScriptTemplate() { + return process.platform === "win32" + ? ` + @echo off + timeout /t 2 + taskkill /IM "{{EXE_NAME}}" /F + xcopy /Y /E "{{UPDATE_DIR}}\\*" "{{APP_PATH}}" + start "" "{{EXE_PATH}}" + ` + : ` + #!/bin/bash + sleep 2 + pkill -f "{{EXE_NAME}}" + cp -Rf "{{UPDATE_DIR}}/*" "{{APP_PATH}}/" + open "{{EXE_PATH}}" + ` +} + +function generateUpdateScript() { + const scriptContent = getUpdateScriptTemplate() + .replace(/{{APP_PATH}}/g, process.platform === "win32" ? "%APP_PATH%" : "$APP_PATH") + .replace(/{{UPDATE_DIR}}/g, process.platform === "win32" ? "%UPDATE_DIR%" : "$UPDATE_DIR") + .replace(/{{EXE_PATH}}/g, process.platform === "win32" ? "%EXE_PATH%" : "$EXE_PATH") + .replace(/{{EXE_NAME}}/g, process.platform === "win32" ? "%EXE_NAME%" : "$EXE_NAME") + + const scriptPath = path.join(os.tmpdir(), `update.${process.platform === "win32" ? "bat" : "sh"}`) + fs.writeFileSync(scriptPath, scriptContent) + return scriptPath +} +// 标记是否需要热更新 +let shouldPerformHotUpdate = false +let isReadyUpdate = false +// 更新临时目录路径 +// 使用应用名称和随机字符串创建唯一的临时目录 +const updateTempDirPath = path.join(os.tmpdir(), `${app.getName()}-update-${Math.random().toString(36).substring(2, 15)}`) +app.once("will-quit", event => { + if (!shouldPerformHotUpdate) return + event.preventDefault() + const appPath = app.getAppPath() + const appExePath = process.execPath + const exeName = path.basename(appExePath) + // 生成动态脚本 + const scriptPath = generateUpdateScript() + + fs.chmodSync(scriptPath, 0o755) + + // 执行脚本 + const child = spawn(scriptPath, [], { + detached: true, + shell: true, + env: { + APP_PATH: appPath, + UPDATE_DIR: updateTempDirPath, + EXE_PATH: appExePath, + EXE_NAME: exeName, + }, + }) + child.unref() + app.exit() +}) + +// 下载热更新包 +export async function fetchHotUpdatePackage(updatePackageUrl: string = "https://example.com/updates/latest.zip") { + if (isReadyUpdate) return + + // 清除临时目录 + clearUpdateTempDir() + // 创建临时目录 + if (!fs.existsSync(updateTempDirPath)) { + fs.mkdirSync(updateTempDirPath, { recursive: true }) + } + + // 下载文件的本地保存路径 + const downloadPath = path.join(updateTempDirPath, "update.zip") + + try { + // 使用 fetch 下载更新包 + const response = await fetch(updatePackageUrl) + if (!response.ok) { + throw new Error(`下载失败: ${response.status} ${response.statusText}`) + } + + // 将下载内容写入文件 + const arrayBuffer = await response.arrayBuffer() + fs.writeFileSync(downloadPath, Buffer.from(arrayBuffer)) + + // 解压更新包 + await extract(downloadPath, { dir: updateTempDirPath }) + + // 删除下载的zip文件 + fs.unlinkSync(downloadPath) + isReadyUpdate = true + emitHotUpdateReady() + } catch (error) { + debug("热更新包下载失败:", error) + throw error + } +} + +function clearUpdateTempDir() { + if (!fs.existsSync(updateTempDirPath)) return + fs.rmSync(updateTempDirPath, { recursive: true }) +} + +export function flagNeedUpdate() { + shouldPerformHotUpdate = true +} diff --git a/src/main/modules/updater/index.ts b/src/main/modules/updater/index.ts index 82080bb..8f94b54 100644 --- a/src/main/modules/updater/index.ts +++ b/src/main/modules/updater/index.ts @@ -5,6 +5,8 @@ import BaseClass from "main/base/base" // import { Setting } from "../setting" import _debug from "debug" import EventEmitter from "events" +import { fetchHotUpdatePackage, flagNeedUpdate } from "./hot" +import Locales from "locales/main" const debug = _debug("app:updater") const { autoUpdater } = pkg @@ -13,10 +15,21 @@ const { autoUpdater } = pkg export class Updater extends BaseClass { public events = new EventEmitter() private timer: ReturnType | null = null + // autoReplace = false + async triggerHotUpdate(autoReplace = false) { + await fetchHotUpdatePackage() + flagNeedUpdate() + if (!autoReplace) { + dialog.showMessageBox({ + title: Locales.t("update.ready.hot.title"), + message: Locales.t("update.ready.hot.desc", { version: app.getVersion() }), + }) + } else { + app.quit() + } + } - constructor( - // @inject(Setting) private _Setting: Setting - ) { + constructor() { super() // 配置自动更新 @@ -72,7 +85,7 @@ export class Updater extends BaseClass { destroy() { // 清理工作 - if(this.timer){ + if (this.timer) { clearInterval(this.timer) this.timer = null } diff --git a/tsconfig.web.json b/tsconfig.web.json index bbf6e3d..ad767d6 100644 --- a/tsconfig.web.json +++ b/tsconfig.web.json @@ -6,6 +6,7 @@ "src/renderer/src/env.d.ts", "src/renderer/src/**/*", "src/renderer/src/**/*.vue", + "packages/locales/**/*.ts", "src/preload/*.d.ts", "src/types/**/*", "config/**/*", @@ -13,6 +14,7 @@ "src/common/**/*" ], "exclude": [ + "packages/locales/main.ts", "src/common/**/*.main.ts", "src/common/**/main.ts" ],