From c92641483977995e11b79928e44d556586015eab Mon Sep 17 00:00:00 2001 From: PiEgg Date: Sun, 14 Aug 2022 20:45:16 +0800 Subject: [PATCH] :sparkles: Feature: add dist upload to cos & update checkupdate logic --- .github/workflows/main.yml | 3 + .gitignore | 1 + appveyor.yml | 1 + package.json | 7 +- scripts/config.js | 50 ++++++++++++ scripts/upload-dist-to-cos.js | 103 ++++++++++++++++++++++++ src/main/events/picgoCoreIPC.ts | 10 ++- src/main/utils/updateChecker.ts | 21 ++--- src/renderer/pages/PicGoSetting.vue | 43 +++------- src/renderer/pages/Plugin.vue | 14 +++- src/universal/events/constants.ts | 1 + src/universal/utils/getLatestVersion.ts | 25 ++++++ src/universal/utils/static.ts | 4 + vue.config.js | 2 +- yarn.lock | 17 +++- 15 files changed, 252 insertions(+), 50 deletions(-) create mode 100644 scripts/config.js create mode 100644 scripts/upload-dist-to-cos.js create mode 100644 src/universal/utils/getLatestVersion.ts diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e349071..17ea7c7 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -51,5 +51,8 @@ jobs: - name: Build & release app run: | yarn release + yarn upload-dist env: GH_TOKEN: ${{ secrets.GH_TOKEN }} + PICGO_ENV_COS_SECRET_ID: ${{ secrets.PICGO_ENV_COS_SECRET_ID }} + PICGO_ENV_COS_SECRET_KEY: ${{ secrets.PICGO_ENV_COS_SECRET_KEY }} diff --git a/.gitignore b/.gitignore index 126dedb..1409d29 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,4 @@ docs/dist/ dist_electron/ test.js .env +scripts/*.yml diff --git a/appveyor.yml b/appveyor.yml index 616611c..3bbc79a 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -27,5 +27,6 @@ install: build_script: #- yarn test - yarn release + - yarn upload-dist test: false diff --git a/package.json b/package.json index e80ca26..dfe4b2c 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,8 @@ "postuninstall": "electron-builder install-app-deps", "cz": "git-cz", "bump": "bump-version", - "release": "vue-cli-service electron:build --publish always" + "release": "vue-cli-service electron:build --publish always", + "upload-dist": "node ./scripts/upload-dist-to-cos.js" }, "main": "background.js", "husky": { @@ -38,9 +39,11 @@ "@picgo/i18n": "^1.0.0", "@picgo/store": "2.0.1", "axios": "^0.19.0", + "compare-versions": "^4.1.3", "core-js": "^3.3.2", "element-ui": "^2.13.0", "fs-extra": "^10.0.0", + "js-yaml": "^4.1.0", "keycode": "^2.2.0", "lodash-id": "^0.14.0", "lowdb": "^1.0.0", @@ -59,6 +62,7 @@ "@picgo/bump-version": "^1.1.2", "@types/fs-extra": "^9.0.13", "@types/inquirer": "^6.5.0", + "@types/js-yaml": "^4.0.5", "@types/lowdb": "^1.0.9", "@types/node": "^16.10.2", "@types/request-promise-native": "^1.0.17", @@ -75,6 +79,7 @@ "@vue/eslint-config-typescript": "^7.0.0", "conventional-changelog": "^3.1.18", "cz-customizable": "^6.2.0", + "dotenv": "^16.0.1", "electron": "^16.0.6", "electron-devtools-installer": "^3.2.0", "eslint": "^7.32.0", diff --git a/scripts/config.js b/scripts/config.js new file mode 100644 index 0000000..43e6a46 --- /dev/null +++ b/scripts/config.js @@ -0,0 +1,50 @@ +// different platform has different format + +// macos +const darwin = [{ + appNameWithPrefix: 'PicGo-', + ext: '.dmg', + arch: '-arm64', + 'version-file': 'latest-mac.yml' +}, { + appNameWithPrefix: 'PicGo-', + ext: '.dmg', + arch: '-x64', + 'version-file': 'latest-mac.yml' +}] + +const linux = [{ + appNameWithPrefix: 'PicGo-', + ext: '.AppImage', + arch: '', + 'version-file': 'latest-linux.yml' +}, { + appNameWithPrefix: 'picgo_', + ext: '.snap', + arch: '_amd64', + 'version-file': 'latest-linux.yml' +}] + +// windows +const win32 = [{ + appNameWithPrefix: 'PicGo-Setup-', + ext: '.exe', + arch: '-ia32', + 'version-file': 'latest.yml' +}, { + appNameWithPrefix: 'PicGo-Setup-', + ext: '.exe', + arch: '-x64', + 'version-file': 'latest.yml' +}, { + appNameWithPrefix: 'PicGo-Setup-', + ext: '.exe', + arch: '', // 32 & 64 + 'version-file': 'latest.yml' +}] + +module.exports = { + darwin, + linux, + win32 +} diff --git a/scripts/upload-dist-to-cos.js b/scripts/upload-dist-to-cos.js new file mode 100644 index 0000000..7c3ea76 --- /dev/null +++ b/scripts/upload-dist-to-cos.js @@ -0,0 +1,103 @@ +// upload dist bundled-app to cos +require('dotenv').config() +const crypto = require('crypto') +const fs = require('fs') +const mime = require('mime-types') +const pkg = require('../package.json') +const configList = require('./config') +const axios = require('axios').default +const path = require('path') +const distPath = path.join(__dirname, '../dist_electron') + +const BUCKET = 'picgo-1251750343' +const AREA = 'ap-chengdu' +const VERSION = pkg.version +const FILE_PATH = `${VERSION}/` +const SECRET_ID = process.env.PICGO_ENV_COS_SECRET_ID +const SECRET_KEY = process.env.PICGO_ENV_COS_SECRET_KEY + +// https://cloud.tencent.com/document/product/436/7778#signature +/** + * @param {string} fileName + * @returns + */ +const generateSignature = (fileName, folder = FILE_PATH) => { + const secretKey = SECRET_KEY + const area = AREA + const bucket = BUCKET + const path = folder + const today = Math.floor(new Date().getTime() / 1000) + const tomorrow = today + 86400 + const signTime = `${today};${tomorrow}` + const signKey = crypto.createHmac('sha1', secretKey).update(signTime).digest('hex') + const httpString = `put\n/${path}${fileName}\n\nhost=${bucket}.cos.${area}.myqcloud.com\n` + const sha1edHttpString = crypto.createHash('sha1').update(httpString).digest('hex') + const stringToSign = `sha1\n${signTime}\n${sha1edHttpString}\n` + const signature = crypto.createHmac('sha1', signKey).update(stringToSign).digest('hex') + return { + signature, + signTime + } +} + +/** + * + * @param {string} fileName + * @param {Buffer} fileBuffer + * @param {{ signature: string, signTime: string }} signature + * @returns + */ +const getReqOptions = (fileName, fileBuffer, signature, folder = FILE_PATH) => { + return { + method: 'PUT', + url: `http://${BUCKET}.cos.${AREA}.myqcloud.com/${encodeURI(folder)}${encodeURI(fileName)}`, + headers: { + Host: `${BUCKET}.cos.${AREA}.myqcloud.com`, + Authorization: `q-sign-algorithm=sha1&q-ak=${SECRET_ID}&q-sign-time=${signature.signTime}&q-key-time=${signature.signTime}&q-header-list=host&q-url-param-list=&q-signature=${signature.signature}`, + contentType: mime.lookup(fileName), + useAgent: `PicGo;${pkg.version};null;null` + }, + maxContentLength: Infinity, + maxBodyLength: Infinity, + data: fileBuffer, + resolveWithFullResponse: true + } +} + +const uploadFile = async () => { + try { + const platform = process.platform + if (configList[platform]) { + let versionFileHasUploaded = false + for (const [index, config] of configList[platform].entries()) { + const fileName = `${config.appNameWithPrefix}${VERSION}${config.arch}${config.ext}` + const filePath = path.join(distPath, fileName) + const versionFilePath = path.join(distPath, config['version-file']) + let versionFileName = config['version-file'] + if (VERSION.toLocaleLowerCase().includes('beta')) { + versionFileName = versionFileName.replace('.yml', '.beta.yml') + } + // upload dist file + const signature = generateSignature(fileName) + const reqOptions = getReqOptions(fileName, fs.readFileSync(filePath), signature) + console.log('[PicGo Dist] Uploading...', fileName, `${index + 1}/${configList[platform].length}`) + await axios.request(reqOptions) + + // upload version file + if (!versionFileHasUploaded) { + const signature = generateSignature(versionFileName, '') + const reqOptions = getReqOptions(versionFileName, fs.readFileSync(versionFilePath), signature, '') + console.log('[PicGo Version File] Uploading...', versionFileName) + await axios.request(reqOptions) + versionFileHasUploaded = true + } + } + } else { + console.warn('platform not supported!', platform) + } + } catch (e) { + console.error(e) + } +} + +uploadFile() diff --git a/src/main/events/picgoCoreIPC.ts b/src/main/events/picgoCoreIPC.ts index 168b799..20bece1 100644 --- a/src/main/events/picgoCoreIPC.ts +++ b/src/main/events/picgoCoreIPC.ts @@ -26,7 +26,8 @@ import { PICGO_REMOVE_BY_ID_DB, PICGO_OPEN_FILE, PASTE_TEXT, - OPEN_WINDOW + OPEN_WINDOW, + DEFAULT_LOGO } from '#/events/constants' import { GalleryDB } from 'apis/core/datastore' @@ -366,6 +367,12 @@ const handleOpenWindow = () => { }) } +const handleDefaultLogo = () => { + ipcMain.on(DEFAULT_LOGO, (event: IpcMainEvent) => { + event.sender.send(DEFAULT_LOGO, path.join(__static, 'roundLogo.png')) + }) +} + export default { listen () { handleGetPluginList() @@ -379,6 +386,7 @@ export default { handleImportLocalPlugin() handleOpenFile() handleOpenWindow() + handleDefaultLogo() }, // TODO: separate to single file handlePluginUninstall, diff --git a/src/main/utils/updateChecker.ts b/src/main/utils/updateChecker.ts index 4f3533b..c8e4b32 100644 --- a/src/main/utils/updateChecker.ts +++ b/src/main/utils/updateChecker.ts @@ -1,12 +1,12 @@ import { dialog, shell } from 'electron' import db from '~/main/apis/core/datastore' -import axios from 'axios' import pkg from 'root/package.json' import { lt } from 'semver' import { T } from '~/universal/i18n' +import { getLatestVersion } from '#/utils/getLatestVersion' const version = pkg.version -const releaseUrl = 'https://api.github.com/repos/Molunerfinn/PicGo/releases/latest' -const releaseUrlBackup = 'https://cdn.jsdelivr.net/gh/Molunerfinn/PicGo@latest/package.json' +// const releaseUrl = 'https://api.github.com/repos/Molunerfinn/PicGo/releases' +// const releaseUrlBackup = 'https://picgo-1251750343.cos.ap-chengdu.myqcloud.com' const downloadUrl = 'https://github.com/Molunerfinn/PicGo/releases/latest' const checkVersion = async () => { @@ -16,17 +16,10 @@ const checkVersion = async () => { showTip = true } if (showTip) { - let res: any - try { - res = await axios.get(releaseUrl).catch(async () => { - const result = await axios.get(releaseUrlBackup) - return result - }) - } catch (err) { - console.log(err) - } - if (res.status === 200) { - const latest = res.data.version || res.data.name + const isCheckBetaUpdate = db.get('settings.checkBetaUpdate') !== false + const res: string = await getLatestVersion(isCheckBetaUpdate) + if (res !== '') { + const latest = res const result = compareVersion2Update(version, latest) if (result) { dialog.showMessageBox({ diff --git a/src/renderer/pages/PicGoSetting.vue b/src/renderer/pages/PicGoSetting.vue index 4f3d2da..0b3155d 100644 --- a/src/renderer/pages/PicGoSetting.vue +++ b/src/renderer/pages/PicGoSetting.vue @@ -393,10 +393,9 @@ import { import { Component, Vue } from 'vue-property-decorator' import { T, languageList } from '~/universal/i18n' import { enforceNumber } from '~/universal/utils/common' -// import db from '#/datastore' -const releaseUrl = 'https://api.github.com/repos/Molunerfinn/PicGo/releases/latest' -const releaseUrlBackup = 'https://cdn.jsdelivr.net/gh/Molunerfinn/PicGo@latest/package.json' -const downloadUrl = 'https://github.com/Molunerfinn/PicGo/releases/latest' +import { getLatestVersion } from '#/utils/getLatestVersion' +import { compare } from 'compare-versions' +import { STABLE_RELEASE_URL, BETA_RELEASE_URL } from '#/utils/static' const customLinkRule = (rule: string, value: string, callback: (arg0?: Error) => void) => { if (!/\$url/.test(value)) { return callback(new Error(T('TIPS_MUST_CONTAINS_URL'))) @@ -635,39 +634,23 @@ export default class extends Vue { }) } - compareVersion2Update (current: string, latest: string) { - const currentVersion = current.split('.').map(item => parseInt(item)) - const latestVersion = latest.split('.').map(item => parseInt(item)) - - for (let i = 0; i < 3; i++) { - if (currentVersion[i] < latestVersion[i]) { - return true - } - if (currentVersion[i] > latestVersion[i]) { - return false - } - } - return false + compareVersion2Update (current: string, latest: string): boolean { + return compare(current, latest, '<') } - checkUpdate () { + async checkUpdate () { this.checkUpdateVisible = true - this.$http.get(releaseUrl) - .then(res => { - this.latestVersion = res.data.name - }).catch(async () => { - this.$http.get(releaseUrlBackup) - .then(res => { - this.latestVersion = res.data.version - }).catch(() => { - this.latestVersion = this.$T('TIPS_NETWORK_ERROR') - }) - }) + const version = await getLatestVersion(this.form.checkBetaUpdate) + if (version) { + this.latestVersion = version + } else { + this.latestVersion = this.$T('TIPS_NETWORK_ERROR') + } } confirmCheckVersion () { if (this.needUpdate) { - ipcRenderer.send(OPEN_URL, downloadUrl) + ipcRenderer.send(OPEN_URL, this.form.checkBetaUpdate ? BETA_RELEASE_URL : STABLE_RELEASE_URL) } this.checkUpdateVisible = false } diff --git a/src/renderer/pages/Plugin.vue b/src/renderer/pages/Plugin.vue index 398cfee..2cb9cff 100644 --- a/src/renderer/pages/Plugin.vue +++ b/src/renderer/pages/Plugin.vue @@ -120,7 +120,8 @@ import { PICGO_CONFIG_PLUGIN, PICGO_HANDLE_PLUGIN_ING, PICGO_TOGGLE_PLUGIN, - SHOW_PLUGIN_PAGE_MENU + SHOW_PLUGIN_PAGE_MENU, + DEFAULT_LOGO } from '#/events/constants' @Component({ @@ -144,7 +145,7 @@ export default class extends Vue { importLocalPluginToolTip = this.$T('PLUGIN_IMPORT_LOCAL') id = '' os = '' - defaultLogo: string = 'this.src="https://cdn.jsdelivr.net/gh/Molunerfinn/PicGo@dev/public/roundLogo.png"' + defaultLogo: string = '' get npmSearchText () { return this.searchText.match('picgo-plugin-') ? this.searchText @@ -247,11 +248,19 @@ export default class extends Vue { this.needReload = true } }) + ipcRenderer.on(DEFAULT_LOGO, (evt: IpcRendererEvent, logoPath) => { + this.defaultLogo = `this.src="${logoPath.replace(/\\/g, '/')}"` + }) this.getPluginList() this.getSearchResult = debounce(this.getSearchResult, 50) + this.getDefaultLogo() this.needReload = await this.getConfig('needReload') || false } + getDefaultLogo () { + ipcRenderer.send(DEFAULT_LOGO) + } + async buildContextMenu (plugin: IPicGoPlugin) { ipcRenderer.send(SHOW_PLUGIN_PAGE_MENU, plugin) } @@ -439,6 +448,7 @@ export default class extends Vue { ipcRenderer.removeAllListeners('uninstallSuccess') ipcRenderer.removeAllListeners('updateSuccess') ipcRenderer.removeAllListeners('hideLoading') + ipcRenderer.removeAllListeners(DEFAULT_LOGO) } } diff --git a/src/universal/events/constants.ts b/src/universal/events/constants.ts index adeda90..eef52a0 100644 --- a/src/universal/events/constants.ts +++ b/src/universal/events/constants.ts @@ -34,3 +34,4 @@ export const SHOW_MAIN_PAGE_DONATION = 'SHOW_MAIN_PAGE_DONATION' export const FORCE_UPDATE = 'FORCE_UPDATE' export const CHANGE_LANGUAGE = 'CHANGE_LANGUAGE' export const OPEN_WINDOW = 'OPEN_WINDOW' +export const DEFAULT_LOGO = 'DEFAULT_LOGO' diff --git a/src/universal/utils/getLatestVersion.ts b/src/universal/utils/getLatestVersion.ts new file mode 100644 index 0000000..c81a3bb --- /dev/null +++ b/src/universal/utils/getLatestVersion.ts @@ -0,0 +1,25 @@ +import axios from 'axios' +import { RELEASE_URL, RELEASE_URL_BACKUP } from './static' +import yaml from 'js-yaml' + +export const getLatestVersion = async (isCheckBetaUpdate: boolean = false) => { + let res: string = '' + try { + res = await axios.get(RELEASE_URL).then(r => { + const list = r.data as IStringKeyMap[] + if (isCheckBetaUpdate) { + const betaList = list.filter(item => item.name.includes('beta')) + return betaList[0].name + } + const normalList = list.filter(item => !item.name.includes('beta')) + return normalList[0].name + }).catch(async () => { + const result = await axios.get(isCheckBetaUpdate ? `${RELEASE_URL_BACKUP}/latest.beta.yml` : `${RELEASE_URL_BACKUP}/latest.yml`) + const r = yaml.load(result.data) as IStringKeyMap + return r.version + }) + } catch (err) { + console.log(err) + } + return res +} diff --git a/src/universal/utils/static.ts b/src/universal/utils/static.ts index 382945b..046e545 100644 --- a/src/universal/utils/static.ts +++ b/src/universal/utils/static.ts @@ -1 +1,5 @@ export const CLIPBOARD_IMAGE_FOLDER = 'picgo-clipboard-images' +export const RELEASE_URL = 'https://api.github.com/repos/Molunerfinn/PicGo/releases' +export const RELEASE_URL_BACKUP = 'https://picgo-1251750343.cos.ap-chengdu.myqcloud.com' +export const STABLE_RELEASE_URL = 'https://github.com/Molunerfinn/PicGo/releases/latest' +export const BETA_RELEASE_URL = 'https://github.com/Molunerfinn/PicGo/releases' diff --git a/vue.config.js b/vue.config.js index ac907b9..a591843 100644 --- a/vue.config.js +++ b/vue.config.js @@ -81,7 +81,7 @@ const config = { win: { icon: 'build/icons/icon.ico', // eslint-disable-next-line no-template-curly-in-string - artifactName: 'PicGo Setup ${version}-${arch}.exe', + artifactName: 'PicGo-Setup-${version}-${arch}.exe', target: [{ target: 'nsis', arch: [ diff --git a/yarn.lock b/yarn.lock index f2e0941..d9a1283 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1538,6 +1538,11 @@ "@types/through" "*" rxjs "^6.4.0" +"@types/js-yaml@^4.0.5": + version "4.0.5" + resolved "https://registry.npmmirror.com/@types/js-yaml/-/js-yaml-4.0.5.tgz#738dd390a6ecc5442f35e7f03fa1431353f7e138" + integrity sha512-FhpRzf927MNQdRZP0J5DLIdTXhjLYzeUTmLAu69mnVksLH9CJY3IuSeEgbKUki7GQZm0WqDkGzyxju2EZGD2wA== + "@types/json-schema@^7.0.4", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.7": version "7.0.9" resolved "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz#97edc9037ea0c38585320b28964dde3b39e4660d" @@ -3907,6 +3912,11 @@ compare-version@^0.1.2: resolved "https://registry.npmjs.org/compare-version/-/compare-version-0.1.2.tgz#0162ec2d9351f5ddd59a9202cba935366a725080" integrity sha1-AWLsLZNR9d3VmpICy6k1NmpyUIA= +compare-versions@^4.1.3: + version "4.1.3" + resolved "https://registry.npmmirror.com/compare-versions/-/compare-versions-4.1.3.tgz#8f7b8966aef7dc4282b45dfa6be98434fc18a1a4" + integrity sha512-WQfnbDcrYnGr55UwbxKiQKASnTtNnaAWVi8jZyy8NTpVAXWACSne8lMD1iaIo9AiU6mnuLvSVshCzewVuWxHUg== + component-emitter@^1.2.1: version "1.3.0" resolved "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" @@ -5146,6 +5156,11 @@ dotenv-expand@^5.1.0: resolved "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-5.1.0.tgz#3fbaf020bfd794884072ea26b1e9791d45a629f0" integrity sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA== +dotenv@^16.0.1: + version "16.0.1" + resolved "https://registry.npmmirror.com/dotenv/-/dotenv-16.0.1.tgz#8f8f9d94876c35dac989876a5d3a82a267fdce1d" + integrity sha512-1K6hR6wtk2FviQ4kEiSjFiH5rpzEVi8WW0x96aztHVMhEspNpc4DVOUTEHtEva5VThQ8IaBX1Pe4gSzpVVUsKQ== + dotenv@^8.2.0: version "8.6.0" resolved "https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz#061af664d19f7f4d8fc6e4ff9b584ce237adcb8b" @@ -8140,7 +8155,7 @@ js-yaml@^3.13.1: js-yaml@^4.1.0: version "4.1.0" - resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + resolved "https://registry.npmmirror.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== dependencies: argparse "^2.0.1"