Compare commits

..
17 Commits
Author SHA1 Message Date
PiEgg ba9fd5c1af 🎉 Release: v2.5.3 2026-03-06 14:38:46 +08:00
PiEggandGitHub c7ca0de0c3 🐛 Fix(update): correct latest version lookup with beta channel (#1396) (#1397) 2026-03-03 16:03:29 +08:00
PiEggandGitHub 0e452e1459 🐛 Fix(plugin): refresh plugin config dialog state (#1395)
ISSUE_CLOSED: #1394
2026-03-03 15:11:31 +08:00
PiEggandGitHub f05ab6348a 📦 Chore: add oz agent for issues (#1392) 2026-02-26 22:41:36 +08:00
qrzbingandGitHub 5d0f01675c 📝 Docs(custom): update README (#1390) 2026-02-24 20:04:36 +08:00
PiEgg 87d92d51e0 📝 Docs: update sponsor 2026-02-21 22:07:58 +08:00
PiEgg ac9a00bf25 📝 Docs: update docs 2026-02-10 22:16:26 +08:00
PiEgg 84fe4259eb 🎉 Release: v2.5.2 2026-02-10 22:08:05 +08:00
PiEgg a1f76cac9a 🐛 Fix: s.ee upload error
ISSUES CLOSED: #1385
2026-02-10 22:07:38 +08:00
PiEgg d0defeede9 📝 Docs: update readme for s.ee 2026-02-10 15:47:32 +08:00
PiEgg 5008a92842 📝 Docs: add 2.5.1 docs 2026-02-10 15:26:38 +08:00
PiEgg 905e34aefe 🎉 Release: v2.5.1 2026-02-10 12:21:13 +08:00
PiEgg 592f9855f8 📦 Chore(notification): add notification for windows sign (#1387) 2026-02-10 11:38:46 +08:00
PiEgg e2e05aef49 📦 Chore: update picgo version to support s.ee
ISSUES CLOSED: #1385
2026-02-10 10:30:55 +08:00
PiEggandGitHub 781cf30c55 Feature: windows signature (#1386) 2026-02-10 10:25:53 +08:00
PiEgg 27464dcf29 📦 Chore: update link 2026-02-04 10:54:46 +08:00
PiEgg ef07c15085 🐛 Fix(plugin): plugin search error
ISSUES CLOSED: #1383
2026-01-31 08:13:12 +08:00
18 changed files with 770 additions and 186 deletions
+2 -2
View File
@@ -22,7 +22,7 @@ body:
label: 前置阅读 | Pre-reading
description: 我已经自行查找、阅读以下内容(阅读了请打勾) | I have searched and read the following on my own (Please tick after reading)
options:
- label: "[文档/Doc](https://picgo.github.io/PicGo-Doc/)"
- label: "[文档/Doc](https://docs.picgo.app/gui/)"
required: true
- label: "[Issues](https://github.com/Molunerfinn/PicGo/issues?q=is%3Aissue+sort%3Aupdated-desc+is%3Aclosed)"
required: true
@@ -65,4 +65,4 @@ body:
最后,喜欢 PicGo 的话不妨给它点个 star~
如果可以的话,请我喝杯咖啡?首页有赞助二维码,谢谢你的支持!
Finally, if you like PicGo, give it a star~
Buy me a cup of coffee if you can? There is a sponsorship QR code on the homepage, thank you for your support!
Buy me a cup of coffee if you can? There is a sponsorship QR code on the homepage, thank you for your support!
@@ -0,0 +1,71 @@
name: Issue Duplicate Detection
on:
issues:
types:
- opened
- edited
- reopened
permissions:
contents: read
issues: write
jobs:
detect-duplicate-issues:
runs-on: ubuntu-latest
steps:
- name: Run Warp Agent duplicate detector
uses: warpdotdev/warp-agent-action@v1
id: duplicate_detector
env:
GH_TOKEN: ${{ github.token }}
with:
warp_api_key: ${{ secrets.WARP_API_KEY }}
profile: ${{ vars.WARP_AGENT_PROFILE || '' }}
prompt: |
You are triaging duplicate GitHub issues for this repository.
Repository: ${{ github.repository }}
Target issue number: #${{ github.event.issue.number }}
Target issue URL: ${{ github.event.issue.html_url }}
Target issue title: ${{ github.event.issue.title }}
Target issue body:
${{ github.event.issue.body }}
Use the GitHub CLI with GH_TOKEN for all operations.
Workflow requirements:
1. Gather issue context from title/body/error messages/symptoms/components.
- Run:
gh issue view ${{ github.event.issue.number }} --repo ${{ github.repository }} --json number,title,body,url,state,labels
2. Search with multiple strategies:
- title keywords
- error message fragments
- symptom words
- component/module names
Example command pattern:
gh issue list --repo ${{ github.repository }} --state all --search "<query>"
3. Inspect every candidate in detail:
gh issue view <candidate_number> --repo ${{ github.repository }} --json number,title,body,url,state
4. Duplicate threshold:
- only mark duplicate when confidence >= 90%
- same root cause + very similar symptoms/errors/components
5. Exclusions:
- never include pull requests
- never include the current issue itself (#${{ github.event.issue.number }})
6. If confidence is insufficient or no duplicates exist, exit without commenting.
7. If duplicates exist, create or update exactly one comment on issue #${{ github.event.issue.number }}:
- first line must be: <!-- issue-duplicate-detector -->
- include markdown bullet list with title + link:
- [Issue title](${{ github.server_url }}/${{ github.repository }}/issues/123)
8. Before posting, check existing comments on the target issue:
- if marker comment exists, update that comment
- otherwise create a new comment
9. Do not comment on any other issue.
Expected comment format:
<!-- issue-duplicate-detector -->
Detected potentially duplicate issues:
- [Duplicate issue title](${{ github.server_url }}/${{ github.repository }}/issues/123)
- [Another duplicate issue](${{ github.server_url }}/${{ github.repository }}/issues/456)
+63 -3
View File
@@ -26,11 +26,19 @@ on:
required: true
default: false
type: boolean
skip_notarize:
description: "Skip Notarization (true/false)"
skip_mac_notarize:
description: "Skip Mac Notarization (true/false)"
required: true
default: false
type: boolean
win_signing_mode:
description: "Windows Signing Mode: release-signing(default) or test-signing"
required: true
default: "release-signing"
type: choice
options:
- release-signing
- test-signing
env:
NODE_VERSION: 22.x
@@ -82,7 +90,7 @@ jobs:
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
SKIP_NOTARIZE: ${{ inputs.skip_notarize }}
SKIP_NOTARIZE: ${{ inputs.skip_mac_notarize }}
- name: Upload Artifact
uses: actions/upload-artifact@v4
@@ -138,11 +146,63 @@ jobs:
- name: Install dependencies
run: pnpm install --frozen-lockfile
# 1. Build (Unsigned)
- name: Build Windows App (${{ matrix.arch }})
run: pnpm run build && pnpm exec electron-builder --config electron-builder.config.ts --win ${{matrix.target}} ${{ matrix.build_arch }} --publish never
env:
GH_TOKEN: ${{ secrets.GH_TOKEN }}
# 2. Upload Unsigned Artifact to SignPath (Intermediate Step)
- name: Upload Unsigned Artifact for Signing
id: upload-unsigned
uses: actions/upload-artifact@v4
with:
name: unsigned-${{ matrix.arch }}
path: dist/*.exe
retention-days: 1
if-no-files-found: error
- name: Notification for Signing Start
shell: bash
env:
NOTIFY_URL: ${{ secrets.SIGN_NOTIFICATION }}
run: curl "$NOTIFY_URL"
# 3. Submit to SignPath and Wait
- name: Sign Artifact with SignPath
uses: signpath/github-action-submit-signing-request@v2
env:
SIGNPATH_SIGNING_POLICY_SLUG: |
${{ (github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/v') || inputs.win_signing_mode == 'release-signing')
&& 'release-signing'
|| 'test-signing' }}
ARTIFACT_SLUG: |
${{ (matrix.arch == 'x64-ia32')
&& 'PicGo-Windows'
|| (matrix.arch == 'arm64')
&& 'PicGo-Windows-ARM64'
|| '' }}
with:
api-token: "${{ secrets.SIGNPATH_API_TOKEN }}"
organization-id: "${{ secrets.SIGNPATH_ORGANIZATION_ID }}"
project-slug: "${{ secrets.SIGNPATH_PROJECT_SLUG }}"
signing-policy-slug: "${{ env.SIGNPATH_SIGNING_POLICY_SLUG }}"
github-artifact-id: "${{ steps.upload-unsigned.outputs.artifact-id }}"
artifact-configuration-slug: "${{ env.ARTIFACT_SLUG }}"
wait-for-completion: true
output-artifact-directory: "signed-artifact"
# 4. Replace Unsigned with Signed & Fix Blockmap (Critical for Auto-Update)
- name: Replace Unsigned with Signed & Update latest.yml
shell: powershell
run: |
# Move signed artifacts to dist folder, overwriting existing ones
Move-Item -Path "signed-artifact\*.exe" -Destination "dist\" -Force
Write-Host "✅ Signed artifacts moved to dist folder."
# Run the Node.js script to update latest.yml
node scripts/update-win-yaml.js
- name: Upload Artifact
uses: actions/upload-artifact@v4
with:
+58
View File
@@ -1,3 +1,61 @@
## :tada: 2.5.3 (2026-03-06)
### :bug: Bug Fixes
* **plugin:** refresh plugin config dialog state ([#1395](https://github.com/Molunerfinn/PicGo/issues/1395)) ([0e452e1](https://github.com/Molunerfinn/PicGo/commit/0e452e1)), closes [#1394](https://github.com/Molunerfinn/PicGo/issues/1394)
* **update:** correct latest version lookup with beta channel ([#1396](https://github.com/Molunerfinn/PicGo/issues/1396)) ([#1397](https://github.com/Molunerfinn/PicGo/issues/1397)) ([c7ca0de](https://github.com/Molunerfinn/PicGo/commit/c7ca0de))
### :package: Chore
* add oz agent for issues ([#1392](https://github.com/Molunerfinn/PicGo/issues/1392)) ([f05ab63](https://github.com/Molunerfinn/PicGo/commit/f05ab63))
### :pencil: Documentation
* **custom:** update README ([#1390](https://github.com/Molunerfinn/PicGo/issues/1390)) ([5d0f016](https://github.com/Molunerfinn/PicGo/commit/5d0f016))
* update docs ([ac9a00b](https://github.com/Molunerfinn/PicGo/commit/ac9a00b))
* update sponsor ([87d92d5](https://github.com/Molunerfinn/PicGo/commit/87d92d5))
## :tada: 2.5.2 (2026-02-10)
### :bug: Bug Fixes
* s.ee upload error ([a1f76ca](https://github.com/Molunerfinn/PicGo/commit/a1f76ca)), closes [#1385](https://github.com/Molunerfinn/PicGo/issues/1385)
### :pencil: Documentation
* add 2.5.1 docs ([5008a92](https://github.com/Molunerfinn/PicGo/commit/5008a92))
* update readme for s.ee ([d0defee](https://github.com/Molunerfinn/PicGo/commit/d0defee))
## :tada: 2.5.1 (2026-02-10)
### :sparkles: Features
* windows signature ([#1386](https://github.com/Molunerfinn/PicGo/issues/1386)) ([781cf30](https://github.com/Molunerfinn/PicGo/commit/781cf30))
### :bug: Bug Fixes
* **plugin:** plugin search error ([ef07c15](https://github.com/Molunerfinn/PicGo/commit/ef07c15)), closes [#1383](https://github.com/Molunerfinn/PicGo/issues/1383)
### :package: Chore
* **notification:** add notification for windows sign ([#1387](https://github.com/Molunerfinn/PicGo/issues/1387)) ([592f985](https://github.com/Molunerfinn/PicGo/commit/592f985))
* update link ([27464dc](https://github.com/Molunerfinn/PicGo/commit/27464dc))
* update picgo version to support s.ee ([e2e05ae](https://github.com/Molunerfinn/PicGo/commit/e2e05ae)), closes [#1385](https://github.com/Molunerfinn/PicGo/issues/1385)
# :tada: 2.5.0 (2026-01-27)
+20
View File
@@ -162,3 +162,23 @@ An official PicGo image host (if any) would be built into PicGo out of the box
不可信。所有打着「PicGo 官方图床」旗号的第三方插件(包括不限于 www.picgo.net 等)都不是 PicGo 官方提供的图床或服务,请勿轻信。
PicGo 不会以“第三方插件”的形式要求你另外下载安装所谓的 PicGo 官方图床。如果 PicGo 真的做了官方图床,一定是开箱即用的内置在本体里的。如果你需要使用第三方图床,请优先参考 PicGo 官方维护的插件集合与社区仓库,并自行甄别来源与安全性。
## 14. SM.MS migrated to S.EE: how should I update my config? / SM.MS 迁移到 S.EE 后,配置应该怎么改?
SM.MS uploader has migrated to **S.EE** and changed from the original free plan to a paid service.
To continue uploading normally:
1. Get your API token from [https://s.ee/user/dashboard/](https://s.ee/user/dashboard/).
2. Check your `picBed.smms.backupDomain`:
- if it is an old domain such as `sm.ms` or `smms.app`, remove this field, or
- change it to `s.ee`.
SM.MS 上传器已迁移到 **S.EE**,并且服务已从原本免费改为收费。
如需继续正常上传:
1. 到 [https://s.ee/user/dashboard/](https://s.ee/user/dashboard/) 获取 API Token。
2. 检查你的 `picBed.smms.backupDomain`
- 如果是旧域名(如 `sm.ms``smms.app`),请删除该字段,或
- 改为 `s.ee`
+18 -1
View File
@@ -10,6 +10,22 @@
</div>
<div align="center" markdown="1">
<sup>Sponsored by:</sup>
<br>
<a href="https://console.neon.tech/app/?promo=PicGo">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://neon.com/brand/neon-logo-dark-color.svg">
<source media="(prefers-color-scheme: light)" srcset="https://neon.com/brand/neon-logo-light-color.svg">
<img alt="Neon sponsorship" width="400" src="https://neon.com/brand/neon-logo-dark-color.svg">
</picture>
</a>
### [Fast Postgres Databases for Teams and Agents](https://console.neon.tech/app/?promo=PicGo)
</div>
---
[中文](./README_zh-CN.md) | **English**
@@ -49,7 +65,7 @@ Whether youre writing a blog post, taking notes, or authoring developer docs,
PicGo supports mainstream Image hosts out of the box, and can be extended indefinitely through its plugin system:
- **China cloud vendors**: Qiniu, Tencent Cloud COS, UPYUN, Alibaba Cloud OSS
- **International / open platforms**: GitHub, SM.MS, Imgur
- **International / open platforms**: GitHub, SM.MS(S.EE), Imgur
- **More options via plugins**: AWS S3, Cloudflare R2, MinIO, and more
> **Note**: PicGo itself will no longer add new third-party Image hosts by default. You can build Image host plugins yourself—see [PicGo-Core](https://docs.picgo.app/core/).
@@ -92,6 +108,7 @@ If youre new to PicGo, start with the [User Guide](https://docs.picgo.app/gui
| [Chocolatey](https://chocolatey.org/) | `choco install picgo` | Windows | Thanks to @iYato |
| [Homebrew](https://brew.sh/) | `brew install picgo --cask` | macOS | Thanks to @womeimingzi11 |
| [AUR](https://aur.archlinux.org/packages/yay) | `yay -S picgo-appimage` | Arch Linux | Thanks to @houbaron |
| [Nix](https://search.nixos.org/packages?channel=unstable&query=picgo&show=picgo) | `nix-shell -p picgo` | Nix/NixOS | Thanks to @qrzbing |
## Screenshots
+17 -1
View File
@@ -10,6 +10,21 @@
</div>
<div align="center" markdown="1">
<sup>Sponsored by:</sup>
<br>
<a href="https://console.neon.tech/app/?promo=PicGo">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://neon.com/brand/neon-logo-dark-color.svg">
<source media="(prefers-color-scheme: light)" srcset="https://neon.com/brand/neon-logo-light-color.svg">
<img alt="Neon sponsorship" width="400" src="https://neon.com/brand/neon-logo-dark-color.svg">
</picture>
</a>
### [Fast Postgres Databases for Teams and Agents](https://console.neon.tech/app/?promo=PicGo)
</div>
---
**中文** | [English](./README.md)
@@ -50,7 +65,7 @@
PicGo 原生支持主流图床平台,并可通过插件系统无限扩展:
- **国内云厂商**:七牛云、腾讯云 COS、又拍云、阿里云 OSS
- **国际/开源平台**GitHub、SM.MS、Imgur
- **国际/开源平台**GitHub、SM.MS(S.EE)、Imgur
- **更多支持**:通过插件支持 AWS S3、Cloudflare R2、MinIO 等第三方图床
> **注意**:PicGo 本体不再增加默认的第三方图床支持。你可以自行开发第三方图床插件。详见 [PicGo-Core](https://docs.picgo.app/core/)。
@@ -93,6 +108,7 @@ PicGo 打造了全方位的上传体验,让“传图”这件事变得前所
| [Chocolatey](https://chocolatey.org/) | `choco install picgo` | Windows | 感谢 @iYato 的贡献 |
| [Homebrew](https://brew.sh/) | `brew install picgo --cask` | macOS | 感谢 @womeimingzi11 的贡献 |
| [AUR](https://aur.archlinux.org/packages/yay) | `yay -S picgo-appimage` | Arch-Linux | 感谢 @houbaron 的贡献 |
| [Nix](https://search.nixos.org/packages?channel=unstable&query=picgo&show=picgo) | `nix-shell -p picgo` | Nix/NixOS | 感谢 @qrzbing 的贡献 |
## 应用截图
+21
View File
@@ -0,0 +1,21 @@
# PicGo 2.5.1 Changelog
## Features
- Update: Bump `picgo` dependency to `^2.0.1` to support legacy `sm.ms` migrate to `s.ee` (#1385)
## Bug Fixes
- Fix: Plugin search no longer throws when an npm package description is empty (#1383)
## Other
- Update: Refresh the documentation link in the GitHub bug report template
----------
## Features
- 更新:将 `picgo` 依赖升级到 `^2.0.1` 以支持 `s.ee`。参考 #1385
## Bug Fixes
- 修复:插件搜索在 npm 包描述为空时会报错的问题。参考 #1383
## Other
- 更新:更新 GitHub 问题模板中的文档链接
+9
View File
@@ -0,0 +1,9 @@
# PicGo 2.5.2 Changelog
## Bug Fixes
- Fix: Resolve `s.ee` compatibility issues that could report upload failure even when the upload actually succeeded (#1385)
----------
## Bug Fixes
- 修复:解决 `s.ee` 兼容性问题,上传实际成功时仍可能提示上传失败(#1385
+8 -5
View File
@@ -10,7 +10,8 @@ This guide describes how to generate a consolidated changelog for any PicGo rele
## Structure
- Target file: `changelog/X.Y.Z.md` (replace with the series version)
- Three top-level sections: `## Features`, `## Bug Fixes`, `## Other`
- Use these top-level sections when they contain items: `## Features`, `## Bug Fixes`, `## Other`
- Omit any empty section title entirely (do not output a heading with no bullets)
- Do **not** nest content under individual beta version headers; merge all items into these sections
- Keep items in chronological order (early → late) within each section, mirroring the beta sequence
- Keep inline issue references, thanks, and notes as-is
@@ -33,18 +34,20 @@ This guide describes how to generate a consolidated changelog for any PicGo rele
- Features ↔ “Feature(s)” or “Features” blocks
- Bug Fixes ↔ “Bug Fixes” blocks
- Other ↔ “Other”, “Notice”, or misc notes that are not features/bugs
- If one section has no items after merging, skip that section heading in both EN and ZH halves
3) Omit “国内可下载链接” (or any download links) entirely
4) Drop beta subheadings; keep only section-level bullets in chronological order
5) Ensure images remain adjacent to their bullets with indentation
6) After the English sections are complete, insert `----------` on its own line
7) Append the Chinese translation, preserving bullet order and images, under `## Features`, `## Bug Fixes`, `## Other` again (same section titles, just Chinese content; no “(Chinese)” suffix)
7) Append the Chinese translation, preserving bullet order and images, reusing only the section headings that appeared in English (same heading set/order; no “(Chinese)” suffix)
8) Save the result to `changelog/X.Y.Z.md`
## Quick checklist
- [ ] All features present with images kept
- [ ] All bug fixes present
- [ ] All Other notes present
- [ ] All non-empty Features items present with images kept
- [ ] All non-empty Bug Fixes items present
- [ ] All non-empty Other notes present
- [ ] No beta headers
- [ ] No download links
- [ ] No empty section headings
- [ ] Chronological ordering preserved
- [ ] Chinese translation present with matching bullets/images after `----------`
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "picgo",
"version": "2.5.0",
"version": "2.5.3",
"private": true,
"main": "dist_electron/main/index.js",
"description": "A powerful & simple image uploader for creators.",
@@ -55,7 +55,7 @@
"mime-types": "^3.0.2",
"mitt": "^3.0.1",
"multer": "^1.4.5-lts.1",
"picgo": "^2.0.0",
"picgo": "^2.0.3",
"qrcode.vue": "^3.3.3",
"semver": "^7.7.3",
"shell-path": "2.1.0",
+86 -49
View File
@@ -75,8 +75,8 @@ importers:
specifier: ^1.4.5-lts.1
version: 1.4.5-lts.2
picgo:
specifier: ^2.0.0
version: 2.0.0
specifier: ^2.0.3
version: 2.0.3
qrcode.vue:
specifier: ^3.3.3
version: 3.6.0(vue@3.5.25(typescript@5.9.3))
@@ -1431,8 +1431,8 @@ packages:
peerDependencies:
eslint: '>=9.0.0'
'@sxzz/popperjs-es@2.11.7':
resolution: {integrity: sha512-Ccy0NlLkzr0Ex2FKvh2X+OyERHXJ88XJ1MXtsI9y9fGexlaXaVTPzBCRBwIxFkORuOb+uBqeu+RqnpgYTEZRUQ==}
'@sxzz/popperjs-es@2.11.8':
resolution: {integrity: sha512-wOwESXvvED3S8xBmcPWHs2dUuzrE4XiZeFu7e1hROIJkm02a49N120pmOXxY33sBb6hArItm5W5tcg1cBtV+HQ==}
'@szmarczak/http-timer@4.0.6':
resolution: {integrity: sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==}
@@ -1938,8 +1938,8 @@ packages:
resolution: {integrity: sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==}
deprecated: Critical security vulnerability fixed in v0.21.1. For more information, see https://github.com/axios/axios/pull/3410
axios@1.13.3:
resolution: {integrity: sha512-ERT8kdX7DZjtUm7IitEyV7InTHAF42iJuMArIiDIV5YtPanJkgw4hw5Dyg9fh0mihdWNn1GKaeIWErfe56UQ1g==}
axios@1.13.6:
resolution: {integrity: sha512-ChTCHMouEe2kn713WHbQGcuYrr6fXTBiu460OTwWrWob16g1bXn4vtz07Ope7ewMozJAnEquLk5lWQWtBig9DQ==}
babel-polyfill@6.26.0:
resolution: {integrity: sha512-F2rZGQnAdaHWQ8YAoeRbukc7HS9QgdgeyJ0rQDd485v9opwuPvjpPFcOOT/WmkKTdgy9ESgSPXDcTNpzrGr6iQ==}
@@ -2148,8 +2148,8 @@ packages:
citty@0.1.6:
resolution: {integrity: sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==}
citty@0.2.0:
resolution: {integrity: sha512-8csy5IBFI2ex2hTVpaHN2j+LNE199AgiI7y4dMintrr8i0lQiFn+0AWMZrWdHKIgMOer65f8IThysYhoReqjWA==}
citty@0.2.1:
resolution: {integrity: sha512-kEV95lFBhQgtogAPlQfJJ0WGVSokvLr/UEoFPiKKOXF7pl98HfUVUD0ejsuTCld/9xH9vogSywZ5KqHzXrZpqg==}
cli-cursor@2.1.0:
resolution: {integrity: sha512-8lgKz8LmCRYZZQDpRyT2m5rKJ08TnU4tR9FFFW2rxpxR1FzWi4PQ/NfyODchAatHaUgnSPVcx/R5w6NuTBzFiw==}
@@ -2542,8 +2542,8 @@ packages:
resolution: {integrity: sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==}
engines: {node: '>=18'}
default-browser@5.4.0:
resolution: {integrity: sha512-XDuvSq38Hr1MdN47EDvYtx3U0MTqpCEn+F6ft8z2vYDzMrvQhVp0ui9oQdqW3MvK3vqUETglt1tVGgjLuJ5izg==}
default-browser@5.5.0:
resolution: {integrity: sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==}
engines: {node: '>=18'}
default-shell@1.0.1:
@@ -2645,6 +2645,10 @@ packages:
resolution: {integrity: sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==}
engines: {node: '>=12'}
dotenv@17.3.1:
resolution: {integrity: sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA==}
engines: {node: '>=12'}
dpdm@3.14.0:
resolution: {integrity: sha512-YJzsFSyEtj88q5eTELg3UWU7TVZkG1dpbF4JDQ3t1b07xuzXmdoGeSz9TKOke1mUuOpWlk4q+pBh+aHzD6GBTg==}
hasBin: true
@@ -2883,6 +2887,10 @@ packages:
resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==}
engines: {node: '>=0.10'}
esquery@1.7.0:
resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==}
engines: {node: '>=0.10'}
esrecurse@4.3.0:
resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==}
engines: {node: '>=4.0'}
@@ -2987,8 +2995,8 @@ packages:
resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==}
engines: {node: '>=16.0.0'}
filelist@1.0.4:
resolution: {integrity: sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==}
filelist@1.0.6:
resolution: {integrity: sha512-5giy2PkLYY1cP39p17Ech+2xlpTRL9HLspOfEgm0L6CwBXBTgsK5ou0JtzYuepxkaQ/tvhCFIJ5uXo0OrM2DxA==}
fill-range@7.1.1:
resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
@@ -3071,6 +3079,10 @@ packages:
resolution: {integrity: sha512-Xr9F6z6up6Ws+NjzMCZc6WXg2YFRlrLP9NQDO3VQrWrfiojdhS56TzueT88ze0uBdCTwEIhQ3ptnmKeWGFAe0A==}
engines: {node: '>=14.14'}
fs-extra@11.3.3:
resolution: {integrity: sha512-VWSRii4t0AFm6ixFFmLLx1t7wS1gh+ckoa84aOeapGum0h+EZd1EhEumSB+ZdDLnEPuucsVB9oB7cxJHap6Afg==}
engines: {node: '>=14.14'}
fs-extra@6.0.1:
resolution: {integrity: sha512-GnyIkKhhzXZUWFCaJzvyDLEEgDkPfb4/TPvJCJVuS8MWZgoSsErf++QpiAlDnKFcqhRlm+tIOcencCjyJE6ZCA==}
@@ -3297,8 +3309,8 @@ packages:
resolution: {integrity: sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==}
engines: {node: '>=0.10.0'}
hono@4.11.6:
resolution: {integrity: sha512-ofIiiHyl34SV6AuhE3YT2mhO5HRWokce+eUYE82TsP6z0/H3JeJcjVWEMSIAiw2QkjDOEpES/lYsg8eEbsLtdw==}
hono@4.12.3:
resolution: {integrity: sha512-SFsVSjp8sj5UumXOOFlkZOG6XS9SJDKw0TbwFeV+AJ8xlST8kxK5Z/5EYa111UY8732lK2S/xB653ceuaoGwpg==}
engines: {node: '>=16.9.0'}
hosted-git-info@2.8.9:
@@ -3618,8 +3630,8 @@ packages:
resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==}
engines: {node: '>=8'}
is-wsl@3.1.0:
resolution: {integrity: sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==}
is-wsl@3.1.1:
resolution: {integrity: sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==}
engines: {node: '>=16'}
isarray@1.0.0:
@@ -3820,6 +3832,9 @@ packages:
lodash@4.17.21:
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
lodash@4.17.23:
resolution: {integrity: sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==}
log-symbols@2.2.0:
resolution: {integrity: sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==}
engines: {node: '>=4'}
@@ -3970,8 +3985,8 @@ packages:
minimatch@3.1.2:
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
minimatch@5.1.6:
resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==}
minimatch@5.1.9:
resolution: {integrity: sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==}
engines: {node: '>=10'}
minimatch@9.0.5:
@@ -4152,8 +4167,8 @@ packages:
resolution: {integrity: sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ==}
engines: {node: '>=0.10.0'}
nypm@0.6.4:
resolution: {integrity: sha512-1TvCKjZyyklN+JJj2TS3P4uSQEInrM/HkkuSXsEzm1ApPgBffOn8gFguNnZf07r/1X6vlryfIqMUkJKQMzlZiw==}
nypm@0.6.5:
resolution: {integrity: sha512-K6AJy1GMVyfyMXRVB88700BJqNUkByijGJM8kEHpLdcAt+vSQAVfkWWHYzuRXHSY6xA2sNc5RjTj0p9rE2izVQ==}
engines: {node: '>=18'}
hasBin: true
@@ -4349,8 +4364,8 @@ packages:
pend@1.2.0:
resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==}
picgo@2.0.0:
resolution: {integrity: sha512-FefLRpMHuYP19C6TpKHs83kot9BQxOT9GwpwM6DxFh4YW35Ttg1IwdQwVQAgWYLA7Pzwpd6Aro+yw+x07XMz/Q==}
picgo@2.0.3:
resolution: {integrity: sha512-FkCIUSeJkmyAWYw+uTsi+18LOMYoVFqZjg8l5d5+2gYnv4gFZ7OlJYCnLfhC4WpvK1Q7JECSc16ZykgXnyW/8A==}
engines: {node: '>= 20.19.0'}
hasBin: true
@@ -4752,6 +4767,11 @@ packages:
engines: {node: '>=10'}
hasBin: true
semver@7.7.4:
resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==}
engines: {node: '>=10'}
hasBin: true
serialize-error@7.0.1:
resolution: {integrity: sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==}
engines: {node: '>=10'}
@@ -6365,7 +6385,7 @@ snapshots:
dependencies:
cross-dirname: 0.1.0
debug: 4.4.3
fs-extra: 11.3.2
fs-extra: 11.3.3
minimist: 1.2.8
postject: 1.0.0-alpha.6
transitivePeerDependencies:
@@ -6511,9 +6531,9 @@ snapshots:
'@floating-ui/utils@0.2.10': {}
'@hono/node-server@1.19.9(hono@4.11.6)':
'@hono/node-server@1.19.9(hono@4.12.3)':
dependencies:
hono: 4.11.6
hono: 4.12.3
'@humanfs/core@0.19.1': {}
@@ -7195,7 +7215,7 @@ snapshots:
estraverse: 5.3.0
picomatch: 4.0.3
'@sxzz/popperjs-es@2.11.7': {}
'@sxzz/popperjs-es@2.11.8': {}
'@szmarczak/http-timer@4.0.6':
dependencies:
@@ -7834,7 +7854,7 @@ snapshots:
transitivePeerDependencies:
- supports-color
axios@1.13.3:
axios@1.13.6:
dependencies:
follow-redirects: 1.15.11
form-data: 4.0.5
@@ -8084,7 +8104,7 @@ snapshots:
dependencies:
consola: 3.4.2
citty@0.2.0: {}
citty@0.2.1: {}
cli-cursor@2.1.0:
dependencies:
@@ -8519,7 +8539,7 @@ snapshots:
default-browser-id@5.0.1: {}
default-browser@5.4.0:
default-browser@5.5.0:
dependencies:
bundle-name: 4.1.0
default-browser-id: 5.0.1
@@ -8628,6 +8648,8 @@ snapshots:
dotenv@17.2.3: {}
dotenv@17.3.1: {}
dpdm@3.14.0:
dependencies:
chalk: 4.1.2
@@ -8716,7 +8738,7 @@ snapshots:
'@electron/asar': 3.4.1
debug: 4.4.3
fs-extra: 7.0.1
lodash: 4.17.21
lodash: 4.17.23
temp: 0.9.4
optionalDependencies:
'@electron/windows-sign': 1.2.2
@@ -8736,7 +8758,7 @@ snapshots:
'@ctrl/tinycolor': 3.6.1
'@element-plus/icons-vue': 2.3.2(vue@3.5.25(typescript@5.9.3))
'@floating-ui/dom': 1.7.4
'@popperjs/core': '@sxzz/popperjs-es@2.11.7'
'@popperjs/core': '@sxzz/popperjs-es@2.11.8'
'@types/lodash': 4.17.21
'@types/lodash-es': 4.17.12
'@vueuse/core': 9.13.0(vue@3.5.25(typescript@5.9.3))
@@ -9030,6 +9052,10 @@ snapshots:
dependencies:
estraverse: 5.3.0
esquery@1.7.0:
dependencies:
estraverse: 5.3.0
esrecurse@4.3.0:
dependencies:
estraverse: 5.3.0
@@ -9140,9 +9166,9 @@ snapshots:
dependencies:
flat-cache: 4.0.1
filelist@1.0.4:
filelist@1.0.6:
dependencies:
minimatch: 5.1.6
minimatch: 5.1.9
fill-range@7.1.1:
dependencies:
@@ -9239,6 +9265,13 @@ snapshots:
jsonfile: 6.2.0
universalify: 2.0.1
fs-extra@11.3.3:
dependencies:
graceful-fs: 4.2.11
jsonfile: 6.2.0
universalify: 2.0.1
optional: true
fs-extra@6.0.1:
dependencies:
graceful-fs: 4.2.11
@@ -9352,7 +9385,7 @@ snapshots:
consola: 3.4.2
defu: 6.1.4
node-fetch-native: 1.6.7
nypm: 0.6.4
nypm: 0.6.5
pathe: 2.0.3
git-raw-commits@1.3.6:
@@ -9519,7 +9552,7 @@ snapshots:
dependencies:
parse-passwd: 1.0.0
hono@4.11.6: {}
hono@4.12.3: {}
hosted-git-info@2.8.9: {}
@@ -9840,7 +9873,7 @@ snapshots:
dependencies:
is-docker: 2.2.1
is-wsl@3.1.0:
is-wsl@3.1.1:
dependencies:
is-inside-container: 1.0.0
@@ -9865,7 +9898,7 @@ snapshots:
jake@10.9.4:
dependencies:
async: 3.2.6
filelist: 1.0.4
filelist: 1.0.6
picocolors: 1.1.1
jiti@1.21.7: {}
@@ -10021,6 +10054,8 @@ snapshots:
lodash@4.17.21: {}
lodash@4.17.23: {}
log-symbols@2.2.0:
dependencies:
chalk: 2.4.2
@@ -10186,7 +10221,7 @@ snapshots:
dependencies:
brace-expansion: 1.1.12
minimatch@5.1.6:
minimatch@5.1.9:
dependencies:
brace-expansion: 2.0.2
@@ -10364,9 +10399,9 @@ snapshots:
number-is-nan@1.0.1: {}
nypm@0.6.4:
nypm@0.6.5:
dependencies:
citty: 0.2.0
citty: 0.2.1
pathe: 2.0.3
tinyexec: 1.0.2
@@ -10423,7 +10458,7 @@ snapshots:
open@11.0.0:
dependencies:
default-browser: 5.4.0
default-browser: 5.5.0
define-lazy-prop: 3.0.0
is-in-ssh: 1.0.0
is-inside-container: 1.0.0
@@ -10561,24 +10596,24 @@ snapshots:
pend@1.2.0: {}
picgo@2.0.0:
picgo@2.0.3:
dependencies:
'@hono/node-server': 1.19.9(hono@4.11.6)
'@hono/node-server': 1.19.9(hono@4.12.3)
'@picgo/i18n': 1.0.0
'@picgo/store': 2.1.0
axios: 1.13.3
axios: 1.13.6
chalk: 2.4.2
commander: 8.3.0
comment-json: 4.5.1
cross-spawn: 6.0.6
dayjs: 1.11.19
dotenv: 17.2.3
dotenv: 17.3.1
ejs: 3.1.10
form-data: 4.0.5
fs-extra: 6.0.1
giget: 2.0.0
globby: 11.1.0
hono: 4.11.6
hono: 4.12.3
image-size: 0.8.3
inquirer: 6.5.2
is-wsl: 2.2.0
@@ -10979,6 +11014,8 @@ snapshots:
semver@7.7.3: {}
semver@7.7.4: {}
serialize-error@7.0.1:
dependencies:
type-fest: 0.13.1
@@ -11633,8 +11670,8 @@ snapshots:
eslint-scope: 8.4.0
eslint-visitor-keys: 4.2.1
espree: 10.4.0
esquery: 1.6.0
semver: 7.7.3
esquery: 1.7.0
semver: 7.7.4
transitivePeerDependencies:
- supports-color
@@ -11769,7 +11806,7 @@ snapshots:
wsl-utils@0.3.1:
dependencies:
is-wsl: 3.1.0
is-wsl: 3.1.1
powershell-utils: 0.1.0
xml-name-validator@4.0.0: {}
+79
View File
@@ -0,0 +1,79 @@
const fs = require('fs')
const path = require('path')
const crypto = require('crypto')
const yaml = require('js-yaml')
const distDir = path.join(__dirname, '../dist')
const yamlPath = path.join(distDir, 'latest.yml')
if (!fs.existsSync(yamlPath)) {
console.log('⚠️ latest.yml not found in dist/. Skipping update.')
process.exit(0)
}
console.log(`Reading ${yamlPath}...`)
const yamlContent = fs.readFileSync(yamlPath, 'utf8')
let doc
try {
doc = yaml.load(yamlContent)
} catch (e) {
console.error('❌ Failed to parse latest.yml:', e)
process.exit(1)
}
// Get all .exe files in dist directory
const files = fs.readdirSync(distDir).filter(f => f.endsWith('.exe'))
if (files.length === 0) {
console.log('⚠️ No .exe files found in dist/.')
process.exit(0)
}
let updated = false
files.forEach(file => {
const filePath = path.join(distDir, file)
// 1. Calculate new Hash and Size
const buffer = fs.readFileSync(filePath)
const hash = crypto.createHash('sha512').update(buffer).digest('base64')
const size = fs.statSync(filePath).size
console.log(`Processing ${file}:`)
console.log(` -> New Hash: ${hash}`)
console.log(` -> New Size: ${size}`)
// 2. Update entries in 'files' list
if (Array.isArray(doc.files)) {
const fileEntry = doc.files.find(f => f.url === file)
if (fileEntry) {
fileEntry.sha512 = hash
fileEntry.size = size
updated = true
console.log(' -> Updated file entry in yaml object')
}
}
// 3. Update root path entry (if exists)
// electron-builder's latest.yml usually has root path, sha512, size
// corresponding to the main file of current build (usually x64 or current arch)
if (doc.path === file) {
doc.sha512 = hash
doc.size = size
updated = true
console.log(' -> Updated root path entry in yaml object')
}
})
if (updated) {
// 4. Dump back to file
// lineWidth: -1 prevents long strings from wrapping, keeping it clean
const newYamlContent = yaml.dump(doc, { lineWidth: -1 })
fs.writeFileSync(yamlPath, newYamlContent, 'utf8')
console.log('✅ latest.yml updated successfully.')
console.log('New yaml content:')
console.log(newYamlContent)
} else {
console.log('⚠️ No matching entries found in latest.yml to update.')
}
+108
View File
@@ -0,0 +1,108 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { RELEASE_URL, RELEASE_URL_BACKUP } from '#/utils/static'
import { getLatestVersion } from '~/main/utils/getLatestVersion'
const { axiosGetMock } = vi.hoisted(() => {
return {
axiosGetMock: vi.fn()
}
})
vi.mock('axios', () => {
return {
default: {
get: axiosGetMock
}
}
})
describe('main/utils/getLatestVersion', () => {
beforeEach(() => {
axiosGetMock.mockReset()
})
it('returns stable release when beta channel is older', async () => {
axiosGetMock.mockResolvedValueOnce({
data: [
{ tag_name: 'v2.5.2', prerelease: false, draft: false },
{ tag_name: 'v2.4.2-beta.0', prerelease: true, draft: false }
]
})
const version = await getLatestVersion(true)
expect(version).toBe('2.5.2')
expect(axiosGetMock).toHaveBeenCalledWith(RELEASE_URL, {
headers: {
Referer: 'https://github.com'
}
})
})
it('returns prerelease when beta channel has a newer version', async () => {
axiosGetMock.mockResolvedValueOnce({
data: [
{ tag_name: 'v2.5.2', prerelease: false, draft: false },
{ tag_name: 'v2.6.0-beta.1', prerelease: true, draft: false }
]
})
const version = await getLatestVersion(true)
expect(version).toBe('2.6.0-beta.1')
})
it('ignores prerelease when beta updates are disabled', async () => {
axiosGetMock.mockResolvedValueOnce({
data: [
{ tag_name: 'v2.6.0-beta.1', prerelease: true, draft: false },
{ tag_name: 'v2.5.2', prerelease: false, draft: false }
]
})
const version = await getLatestVersion(false)
expect(version).toBe('2.5.2')
})
it('fallback compares stable and beta backup metadata when beta updates are enabled', async () => {
axiosGetMock.mockRejectedValueOnce(new Error('network down'))
axiosGetMock.mockResolvedValueOnce({
data: 'version: 2.5.2'
})
axiosGetMock.mockResolvedValueOnce({
data: 'version: 2.4.2-beta.0'
})
const version = await getLatestVersion(true)
expect(version).toBe('2.5.2')
expect(axiosGetMock).toHaveBeenNthCalledWith(2, `${RELEASE_URL_BACKUP}/latest.yml`, {
headers: {
Referer: 'https://github.com'
}
})
expect(axiosGetMock).toHaveBeenNthCalledWith(3, `${RELEASE_URL_BACKUP}/latest.beta.yml`, {
headers: {
Referer: 'https://github.com'
}
})
})
it('fallback uses only stable backup metadata when beta updates are disabled', async () => {
axiosGetMock.mockRejectedValueOnce(new Error('network down'))
axiosGetMock.mockResolvedValueOnce({
data: 'version: 2.5.2'
})
const version = await getLatestVersion(false)
expect(version).toBe('2.5.2')
expect(axiosGetMock).toHaveBeenCalledTimes(2)
expect(axiosGetMock).toHaveBeenNthCalledWith(2, `${RELEASE_URL_BACKUP}/latest.yml`, {
headers: {
Referer: 'https://github.com'
}
})
})
})
+3
View File
@@ -187,6 +187,9 @@ const buildPluginPageMenu = (plugin: IPicGoPlugin) => {
}
}]
for (const i in plugin.config) {
if (i === 'uploader') {
continue
}
if (plugin.config[i].config.length > 0) {
const obj = {
label: T('CONFIG_THING', {
+98 -23
View File
@@ -1,34 +1,109 @@
// for referer policy, we can't use it in renderer
import axios from 'axios'
import { RELEASE_URL, RELEASE_URL_BACKUP } from '../../universal/utils/static'
import semver from 'semver'
import yaml from 'js-yaml'
interface IGithubRelease {
tag_name?: string
name?: string
prerelease?: boolean
draft?: boolean
}
interface IReleaseYAML {
version?: unknown
}
const REQUEST_HEADERS = {
Referer: 'https://github.com'
}
function normalizeVersion (version: unknown): string {
if (typeof version !== 'string') {
return ''
}
const normalized = version.trim().replace(/^v/i, '')
return semver.valid(normalized) ?? ''
}
function pickLatestVersion (versions: string[]): string {
return versions.reduce((latest, current) => {
if (latest === '' || semver.gt(current, latest)) {
return current
}
return latest
}, '')
}
async function fetchLatestVersionFromGitHub (isCheckBetaUpdate: boolean): Promise<string> {
const response = await axios.get(RELEASE_URL, {
headers: REQUEST_HEADERS
})
const releaseList: IGithubRelease[] = Array.isArray(response.data) ? response.data : []
const versions = releaseList.flatMap((release) => {
if (release.draft) {
return []
}
if (!isCheckBetaUpdate && release.prerelease) {
return []
}
const version = normalizeVersion(release.tag_name ?? release.name)
return version ? [version] : []
})
return pickLatestVersion(versions)
}
async function fetchVersionFromBackupYAML (fileName: 'latest.yml' | 'latest.beta.yml'): Promise<string> {
const response = await axios.get(`${RELEASE_URL_BACKUP}/${fileName}`, {
headers: REQUEST_HEADERS
})
const releaseInfo = yaml.load(response.data)
if (typeof releaseInfo !== 'object' || releaseInfo === null) {
return ''
}
return normalizeVersion((releaseInfo as IReleaseYAML).version)
}
async function fetchLatestVersionFromBackup (isCheckBetaUpdate: boolean): Promise<string> {
if (!isCheckBetaUpdate) {
return fetchVersionFromBackupYAML('latest.yml')
}
const settled = await Promise.allSettled([
fetchVersionFromBackupYAML('latest.yml'),
fetchVersionFromBackupYAML('latest.beta.yml')
])
const versions: string[] = []
settled.forEach((item) => {
if (item.status === 'fulfilled' && item.value) {
versions.push(item.value)
}
})
return pickLatestVersion(versions)
}
export const getLatestVersion = async (isCheckBetaUpdate: boolean = false) => {
let res: string = ''
try {
res = await axios.get(RELEASE_URL, {
headers: {
Referer: 'https://github.com'
}
}).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`, {
headers: {
Referer: 'https://github.com'
}
})
const r = yaml.load(result.data) as IStringKeyMap
return r.version
})
const version = await fetchLatestVersionFromGitHub(isCheckBetaUpdate)
if (version) {
return version
}
} catch (err) {
console.log(err)
}
return res
try {
return await fetchLatestVersionFromBackup(isCheckBetaUpdate)
} catch (err) {
console.log(err)
return ''
}
}
+14 -7
View File
@@ -27,7 +27,7 @@
</div>
</template>
<script lang="ts" setup>
import { reactive, ref, watch, toRefs } from 'vue'
import { computed, reactive, ref, watch } from 'vue'
import { getConfig } from '@/utils/dataSender'
import { useRoute } from 'vue-router'
import BaseConfigForm from './form/BaseConfigForm.vue'
@@ -35,7 +35,7 @@ import { useConfigForm } from '@/hooks/useConfigForm'
import { useStore } from '@/hooks/useStore'
interface IProps {
config: any[]
config: IPicGoPluginConfig[]
type: 'uploader' | 'transformer' | 'plugin'
id: string
colorMode?: 'white' | 'dark'
@@ -46,7 +46,7 @@ const $route = useRoute()
const $form = ref<IFormInstance>()
const configList = ref<IPicGoPluginConfig[]>([])
const formModel = reactive<IStringKeyMap>({})
const isUploader = props.type === 'uploader'
const isUploader = computed(() => props.type === 'uploader')
const store = useStore()
async function validate (): Promise<IStringKeyMap | false> {
@@ -72,9 +72,16 @@ function getConfigType () {
const handleConfigForm = useConfigForm()
function resetFormModel () {
Object.keys(formModel).forEach((key) => {
delete formModel[key]
})
}
async function handleConfig (inputConfig: IPicGoPluginConfig[]) {
const currentConfig = await getCurConfigFormData()
const resetConfig = isUploader && !$route.params.configId
const resetConfig = isUploader.value && !$route.params.configId
resetFormModel()
Object.assign(formModel, currentConfig)
configList.value = handleConfigForm(inputConfig, formModel, currentConfig, resetConfig)
}
@@ -82,7 +89,7 @@ async function handleConfig (inputConfig: IPicGoPluginConfig[]) {
async function getCurConfigFormData () {
const configId = $route.params.configId
const configType = getConfigType()
if (isUploader) {
if (isUploader.value) {
const cachedList = store?.state.appConfig?.uploader?.[props.id]?.configList
const curTypeConfigList = Array.isArray(cachedList)
? cachedList
@@ -99,8 +106,8 @@ async function resetConfig () {
Object.assign(formModel, config)
}
watch(toRefs(props.config), (val: IPicGoPluginConfig[]) => {
handleConfig(val)
watch(() => props.config, async (val: IPicGoPluginConfig[]) => {
await handleConfig(val)
}, {
deep: true,
immediate: true
+93 -93
View File
@@ -170,6 +170,7 @@
>
<config-form
:id="configName"
:key="configFormKey"
ref="$configForm"
:config="config"
:type="currentType"
@@ -198,10 +199,7 @@ import { Close, Download, Goods, Remove, Setting } from '@element-plus/icons-vue
import { T as $T } from '@/i18n/index'
import ConfigForm from '@/components/ConfigForm.vue'
import { debounce, DebouncedFunc } from 'lodash'
import {
ipcRenderer,
IpcRendererEvent
} from 'electron'
import type { IpcRendererEvent } from 'electron'
import { handleStreamlinePluginName } from '~/universal/utils/common'
import {
OPEN_URL,
@@ -212,23 +210,25 @@ import {
GET_PICBEDS,
PICGO_HANDLE_PLUGIN_DONE
} from '#/events/constants'
import { computed, ref, onBeforeMount, onBeforeUnmount, watch } from 'vue'
import { computed, ref, onBeforeMount, watch } from 'vue'
import { getConfig, saveConfig, sendRPC, sendToMain } from '@/utils/dataSender'
import { showNotification } from '@/utils/notification'
import { ElMessageBox } from 'element-plus'
import axios from 'axios'
import { getRendererStaticFileUrl } from '@/utils/static'
import { IRPCActionType } from '~/universal/types/enum'
import { useIPCOn } from '@/hooks/useIPC'
const $confirm = ElMessageBox.confirm
const searchText = ref('')
const pluginList = ref<IPicGoPlugin[]>([])
const config = ref<any[]>([])
const config = ref<IPicGoPluginConfig[]>([])
const currentType = ref<'plugin' | 'uploader' | 'transformer'>('plugin')
const configName = ref('')
const dialogVisible = ref(false)
const pluginNameList = ref<string[]>([])
const loading = ref(true)
const needReload = ref(false)
const configFormKey = computed(() => `${currentType.value}:${configName.value}`)
const pluginListToolTip = $T('PLUGIN_LIST')
const importLocalPluginToolTip = $T('PLUGIN_IMPORT_LOCAL')
// const id = ref('')
@@ -263,85 +263,94 @@ watch(dialogVisible, (val: boolean) => {
}
})
onBeforeMount(async () => {
ipcRenderer.on('hideLoading', () => {
loading.value = false
})
ipcRenderer.on(PICGO_HANDLE_PLUGIN_DONE, (evt: IpcRendererEvent, fullName: string) => {
pluginList.value.forEach(item => {
if (item.fullName === fullName || (item.name === fullName)) {
item.ing = false
}
})
loading.value = false
})
ipcRenderer.on('pluginList', (evt: IpcRendererEvent, list: IPicGoPlugin[]) => {
pluginList.value = list
pluginNameList.value = list.map(item => item.fullName)
loading.value = false
})
ipcRenderer.on('installPlugin', (evt: IpcRendererEvent, { success, body }: {
success: boolean,
body: string
}) => {
loading.value = false
pluginList.value.forEach(item => {
if (item.fullName === body) {
item.ing = false
item.hasInstall = success
}
})
})
ipcRenderer.on('updateSuccess', (evt: IpcRendererEvent, plugin: string) => {
loading.value = false
pluginList.value.forEach(item => {
if (item.fullName === plugin) {
item.ing = false
item.hasInstall = true
}
getPicBeds()
})
handleReload()
getPluginList()
})
ipcRenderer.on('uninstallSuccess', (evt: IpcRendererEvent, plugin: string) => {
loading.value = false
pluginList.value = pluginList.value.filter(item => {
if (item.fullName === plugin) { // restore Uploader & Transformer after uninstalling
if (item.config.transformer.name) {
handleRestoreState('transformer', item.config.transformer.name)
}
if (item.config.uploader.name) {
handleRestoreState('uploader', item.config.uploader.name)
}
getPicBeds()
}
return item.fullName !== plugin
})
pluginNameList.value = pluginNameList.value.filter(item => item !== plugin)
})
ipcRenderer.on(PICGO_CONFIG_PLUGIN, (evt: IpcRendererEvent, _currentType: 'plugin' | 'transformer' | 'uploader', _configName: string, _config: any) => {
currentType.value = _currentType
configName.value = _configName
dialogVisible.value = true
config.value = _config
})
ipcRenderer.on(PICGO_HANDLE_PLUGIN_ING, (evt: IpcRendererEvent, fullName: string) => {
pluginList.value.forEach(item => {
if (item.fullName === fullName || (item.name === fullName)) {
item.ing = true
}
})
loading.value = true
})
ipcRenderer.on(PICGO_TOGGLE_PLUGIN, (evt: IpcRendererEvent, fullName: string, enabled: boolean) => {
const plugin = pluginList.value.find(item => item.fullName === fullName)
if (plugin) {
plugin.enabled = enabled
getPicBeds()
needReload.value = true
useIPCOn('hideLoading', () => {
loading.value = false
})
useIPCOn(PICGO_HANDLE_PLUGIN_DONE, (evt: IpcRendererEvent, fullName: string) => {
pluginList.value.forEach(item => {
if (item.fullName === fullName || (item.name === fullName)) {
item.ing = false
}
})
loading.value = false
})
useIPCOn('pluginList', (evt: IpcRendererEvent, list: IPicGoPlugin[]) => {
pluginList.value = list
pluginNameList.value = list.map(item => item.fullName)
loading.value = false
})
useIPCOn('installPlugin', (evt: IpcRendererEvent, { success, body }: {
success: boolean,
body: string
}) => {
loading.value = false
pluginList.value.forEach(item => {
if (item.fullName === body) {
item.ing = false
item.hasInstall = success
}
})
})
useIPCOn('updateSuccess', (evt: IpcRendererEvent, plugin: string) => {
loading.value = false
pluginList.value.forEach(item => {
if (item.fullName === plugin) {
item.ing = false
item.hasInstall = true
}
getPicBeds()
})
handleReload()
getPluginList()
})
useIPCOn('uninstallSuccess', (evt: IpcRendererEvent, plugin: string) => {
loading.value = false
pluginList.value = pluginList.value.filter(item => {
if (item.fullName === plugin) { // restore Uploader & Transformer after uninstalling
if (item.config.transformer.name) {
handleRestoreState('transformer', item.config.transformer.name)
}
if (item.config.uploader.name) {
handleRestoreState('uploader', item.config.uploader.name)
}
getPicBeds()
}
return item.fullName !== plugin
})
pluginNameList.value = pluginNameList.value.filter(item => item !== plugin)
})
useIPCOn(PICGO_CONFIG_PLUGIN, (evt: IpcRendererEvent, _currentType: 'plugin' | 'transformer' | 'uploader', _configName: string, _config: IPicGoPluginConfig[]) => {
currentType.value = _currentType
configName.value = _configName
config.value = _config
dialogVisible.value = true
})
useIPCOn(PICGO_HANDLE_PLUGIN_ING, (evt: IpcRendererEvent, fullName: string) => {
pluginList.value.forEach(item => {
if (item.fullName === fullName || (item.name === fullName)) {
item.ing = true
}
})
loading.value = true
})
useIPCOn(PICGO_TOGGLE_PLUGIN, (evt: IpcRendererEvent, fullName: string, enabled: boolean) => {
const plugin = pluginList.value.find(item => item.fullName === fullName)
if (plugin) {
plugin.enabled = enabled
getPicBeds()
needReload.value = true
}
})
onBeforeMount(async () => {
getPluginList()
getSearchResult = debounce(_getSearchResult, 50)
needReload.value = await getConfig<boolean>('needReload') || false
@@ -461,7 +470,7 @@ function _getSearchResult (val: string) {
})
.filter((item: INPMSearchResultObject) => {
// filter out fake picgo plugins from picgo.net
if (item.package.description.includes('picgo.net') || item.package.description.includes('PicGo官方')) {
if (item.package.description?.includes('picgo.net') || item.package.description?.includes('PicGo官方')) {
return false
}
return true
@@ -536,15 +545,6 @@ function handleImportLocalPlugin () {
loading.value = true
}
onBeforeUnmount(() => {
ipcRenderer.removeAllListeners('pluginList')
ipcRenderer.removeAllListeners('installPlugin')
ipcRenderer.removeAllListeners('uninstallSuccess')
ipcRenderer.removeAllListeners('updateSuccess')
ipcRenderer.removeAllListeners('hideLoading')
ipcRenderer.removeAllListeners(PICGO_HANDLE_PLUGIN_DONE)
})
</script>
<script lang="ts">
export default {